README.TXT

The \Mssdk\Samples\Graphics directory contains samples, tools,
and sample media files for the following Platform SDK components:

* Win32 Audio, Input, and Video
* Broadcast Architecture
* DirectAnimation 5.0
* DirectShow 5.0
* DirectX 5.0 Foundation
* GDI
* Image Color Management (ICM) 2.0
* OpenGL

The build environment (headers, libraries, and minimum build
tools) for the Platform SDK must be installed in order to build
any of the samples in the \Mssdk\Samples\Graphics directory. The
most recent version of the build environment is available at
http://www.microsoft.com/msdn/sdk/platform.htm.
Some components may also require you to install runtimes, depending
on the platform on which you are trying to run the sample.
Redistributables are available in the \Mssdk\Redist subdirectory
on the Platform SDK compact disc.

Documentation for the components listed above can be installed
from the Platform SDK compact disc, or can be viewed online at
http://premium.microsoft.com/msdn/library/. This is a FREE service;
registration is necessary.

If you do not see any subdirectories in the \Mssdk\Samples\Graphics
node, you'll need to install the Graphics and Multimedia components
from the Platform SDK compact disc or download and install them
from http://www.microsoft.com/msdn/sdk/platform.htm.


README.TXT

The ACMAPP sample displays wave file format, and plays, records, and converts wave files.


AADRVS.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//--------------------------------------------------------------------------;
//
//  aadrvs.c
//
//  Description:
//      Routines to get and display information from ACM drivers.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <memory.h>
#include "tlb.h"

#include "appport.h"
#include "acmapp.h"

#include "debug.h"

TCHAR   gszFormatDriversTitle[] = TEXT("Id\t4!Name\t12!Priority\t3!Support\t6!Full Name");
TCHAR   gszFormatDriversList[]  = TEXT("%.04Xh\t%s\t%lu%s\t%.08lXh\t%s");

TCHAR   gszFormatDriverFormatsTitle[] = TEXT("Id\t4!Index\t2!Tag\t2!Support\t5!cbwfx\t2!Format");
TCHAR   gszFormatDriverFormatsList[]  = TEXT("%.04Xh\t%lu\t%lu\t%.08lXh\t%u\t%-s");

static HACMDRIVERID     ghadidSelected;
static HACMDRIVER       ghadSelected;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayDriverDetails
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      HACMDRIVER had:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplayDriverDetails
(
    HWND            hedit,
    HACMDRIVERID    hadid
)
{
    static TCHAR    szDisplayTitle[]  = TEXT("[Driver Details]\r\n");

    MMRESULT            mmr;
    ACMDRIVERDETAILS    add;

    //
    //
    //
    MEditPrintF(hedit, szDisplayTitle);

    MEditPrintF(hedit, TEXT("%25s: %.04Xh"), (LPTSTR)TEXT("Driver Identifier Handle"), hadid);

    //
    //
    //
    //
    add.cbStruct = sizeof(add);
    mmr = acmDriverDetails(hadid, &add, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        //
        //  this should never happen..
        //
        MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("ERROR GETTING DRIVER DETAILS"), mmr);
        return (FALSE);
    }

    //
    //
    //
    //
    MEditPrintF(hedit, TEXT("%25s: %lu bytes (requested %lu)"),
                   (LPTSTR)TEXT("Size of Driver Details"),
                   add.cbStruct, (DWORD)sizeof(add));

    //
    //  this would be bad
    //
    if (add.cbStruct < sizeof(add))
        return (0L);

    MEditPrintF(hedit, TEXT("%25s: %.08lXh (%s)"), (LPTSTR)TEXT("FCC Type"),
                   add.fccType,
                   (ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC == add.fccType) ?
                   (LPTSTR)TEXT("'audc'") : (LPTSTR)TEXT("**INVALID**"));

    MEditPrintF(hedit, TEXT("%25s: %.08lXh (%s)"), (LPTSTR)TEXT("FCC Compressor"),
                   add.fccComp,
                   (ACMDRIVERDETAILS_FCCCOMP_UNDEFINED == add.fccComp) ?
                   (LPTSTR)TEXT("correct") : (LPTSTR)TEXT("**INVALID**"));

    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Manufacturer Id"), add.wMid);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Product Id"), add.wPid);

    MEditPrintF(hedit, TEXT("%25s: %u.%.02u (Build %.03u)"), (LPTSTR)TEXT("ACM Version Required"),
                   HIWORD(add.vdwACM) >> 8,
                   HIWORD(add.vdwACM) & 0x00FF,
                   LOWORD(add.vdwACM));

    MEditPrintF(hedit, TEXT("%25s: %u.%.02u (Build %.03u)"), (LPTSTR)TEXT("CODEC Version"),
                   HIWORD(add.vdwDriver) >> 8,
                   HIWORD(add.vdwDriver) & 0x00FF,
                   LOWORD(add.vdwDriver));

    MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("Standard Support"), add.fdwSupport);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Count Format Tags"), add.cFormatTags);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Count Filter Tags"), add.cFilterTags);
    MEditPrintF(hedit, TEXT("%25s: %.04Xh"), (LPTSTR)TEXT("Custom Icon Handle"), add.hicon);
    MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Short Name"), (LPTSTR)add.szShortName);
    MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Long Name"), (LPTSTR)add.szLongName);
    MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Copyright"), (LPTSTR)add.szCopyright);
    MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Licensing"), (LPTSTR)add.szLicensing);
    MEditPrintF(hedit, TEXT("%25s: '%s'\r\n"), (LPTSTR)TEXT("Features"), (LPTSTR)add.szFeatures);

    //
    //
    //
    if (0 != (ACMDRIVERDETAILS_SUPPORTF_HARDWARE & add.fdwSupport))
    {
        TCHAR       ach[40];
        DWORD       dw;

        mmr = acmMetrics((HACMOBJ)hadid, ACM_METRIC_HARDWARE_WAVE_INPUT, &dw);
        AcmAppGetErrorString(mmr, ach);
        MEditPrintF(hedit, TEXT("%25s: %ld (mmr = %s, [%u])"), (LPTSTR)TEXT("Wave Input Device"), dw, (LPTSTR)ach, mmr);

        mmr = acmMetrics((HACMOBJ)hadid, ACM_METRIC_HARDWARE_WAVE_OUTPUT, &dw);
        AcmAppGetErrorString(mmr, ach);
        MEditPrintF(hedit, TEXT("%25s: %ld (mmr = %s, [%u])"), (LPTSTR)TEXT("Wave Output Device"), dw, (LPTSTR)ach, mmr);
    }

    return (TRUE);
} // AcmAppDisplayDriverDetails()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayFormats
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      HACMDRIVER had:
//
//      ACMFORMATTAGDETAILS paftd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplayFormats
(
    HWND                    hedit,
    HACMDRIVER              had,
    LPACMFORMATTAGDETAILS   paftd
)
{
    MMRESULT            mmr;
    UINT                u;
    ACMFORMATDETAILS    afd;
    PWAVEFORMATEX       pwfx;

    if (0 == paftd->cStandardFormats)
        return (TRUE);

    pwfx = (PWAVEFORMATEX)LocalAlloc(LPTR, (UINT)paftd->cbFormatSize);
    if (NULL == pwfx)
        return (FALSE);

    //
    //
    //
    //
    for (u = 0; u < paftd->cStandardFormats; u++)
    {
        afd.cbStruct        = sizeof(afd);
        afd.dwFormatIndex   = u;
        afd.dwFormatTag     = paftd->dwFormatTag;
        afd.fdwSupport      = 0L;
        afd.pwfx            = pwfx;
        afd.cbwfx           = paftd->cbFormatSize;
        afd.szFormat[0]     = '\0';

        mmr = acmFormatDetails(had, &afd, ACM_FORMATDETAILSF_INDEX);
        if (MMSYSERR_NOERROR != mmr)
        {
            //
            //  this should never happen..
            //
            MEditPrintF(hedit, TEXT("%25s: err = %.04Xh"), (LPTSTR)TEXT("ERROR GETTING FORMAT DETAILS"), mmr);
            continue;
        }

        //
        //  this would be bad
        //
        if (afd.cbStruct < sizeof(afd))
        {
            MEditPrintF(hedit, TEXT("%25s: %lu bytes (requested %lu)"), (LPTSTR)TEXT("Size of Format Details"),
                        afd.cbStruct, (DWORD)sizeof(afd));
            continue;
        }

        MEditPrintF(hedit, TEXT("%15s %u: '%s'"), (LPTSTR)TEXT("Format"), u, (LPTSTR)afd.szFormat);

    }

    LocalFree((HLOCAL)pwfx);

    return (TRUE);
} // AcmAppDisplayFormats()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayDriverTags
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      HACMDRIVER had:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplayDriverTags
(
    HWND            hedit,
    HACMDRIVER      had
)
{
    static TCHAR    szDisplayTitle[]      = TEXT("[Driver Tags]\r\n");
    static TCHAR    szDisplayFormatTags[] = TEXT("\r\n[Format Tags]");
    static TCHAR    szDisplayFilterTags[] = TEXT("\r\n[Filter Tags]");

    TCHAR               ach[APP_MAX_STRING_CHARS];
    ACMDRIVERDETAILS    add;
    MMRESULT            mmr;
    UINT                u;
    HACMDRIVERID        hadid;

    //
    //
    //
    MEditPrintF(hedit, szDisplayTitle);

    mmr = acmDriverID((HACMOBJ)had, &hadid, 0L);
    MEditPrintF(hedit, TEXT("%25s: %.04Xh"), (LPTSTR)TEXT("Driver Identifier Handle"), hadid);
    MEditPrintF(hedit, TEXT("%25s: %.04Xh"), (LPTSTR)TEXT("Driver Handle"), had);

    //
    //
    //
    //
    add.cbStruct = sizeof(add);
    mmr = acmDriverDetails(hadid, &add, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        //
        //  this should never happen..
        //
        MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("ERROR GETTING INFO"), mmr);
        return (FALSE);
    }

    MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Name"), (LPTSTR)add.szShortName);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Count Format Tags"), add.cFormatTags);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Count Filter Tags"), add.cFilterTags);

    if (0 != add.cFormatTags)
        MEditPrintF(hedit, szDisplayFormatTags);

    for (u = 0; u < add.cFormatTags; u++)
    {
        ACMFORMATTAGDETAILS aftd;
        WAVEFORMATEX        wfx;

        MEditPrintF(hedit, TEXT("\r\n%25s: %u"), (LPTSTR)TEXT("Format Tag Index"), u);

        _fmemset(&aftd, 0, sizeof(aftd));

        aftd.cbStruct         = sizeof(aftd);
        aftd.dwFormatTagIndex = u;
        mmr = acmFormatTagDetails(had, &aftd, ACM_FORMATTAGDETAILSF_INDEX);
        if (MMSYSERR_NOERROR != mmr)
        {
            MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("ERROR GETTING TAGS"), mmr);
            return (FALSE);
        }

        MEditPrintF(hedit, TEXT("%25s: %lu bytes (requested %lu)"),
                    (LPTSTR)TEXT("Size of Tag Details"),
                    aftd.cbStruct, (DWORD)sizeof(aftd));

        //
        //  this would be bad
        //
        if (aftd.cbStruct < sizeof(aftd))
            continue;

        wfx.wFormatTag = LOWORD(aftd.dwFormatTag);
        AcmAppGetFormatDescription(&wfx, ach, NULL);
        MEditPrintF(hedit, TEXT("%25s: [%lu], %s"), (LPTSTR)TEXT("Format Tag"), aftd.dwFormatTag, (LPTSTR)ach);
        MEditPrintF(hedit, TEXT("%25s: %u bytes"), (LPTSTR)TEXT("Format Size (Max)"), aftd.cbFormatSize);
        MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("Standard Support"), aftd.fdwSupport);
        MEditPrintF(hedit, TEXT("%25s: %lu"), (LPTSTR)TEXT("Standard Formats"), aftd.cStandardFormats);
        MEditPrintF(hedit, TEXT("%25s: '%s'"), (LPTSTR)TEXT("Format Tag Name"), (LPTSTR)aftd.szFormatTag);

        AcmAppDisplayFormats(hedit, had, &aftd);
    }

    if (0 != add.cFilterTags)
        MEditPrintF(hedit, szDisplayFilterTags);

    for (u = 0; u < add.cFilterTags; u++)
    {
        ACMFILTERTAGDETAILS aftd;
        WAVEFILTER          wfltr;

        MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Filter Tag Index"), u);

        _fmemset(&aftd, 0, sizeof(aftd));

        aftd.cbStruct         = sizeof(aftd);
        aftd.dwFilterTagIndex = u;
        mmr = acmFilterTagDetails(had, &aftd, ACM_FILTERTAGDETAILSF_INDEX);
        if (MMSYSERR_NOERROR != mmr)
        {
            //
            //
            //
            MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("ERROR GETTING TAGS"), mmr);

            return (FALSE);
        }

        MEditPrintF(hedit, TEXT("%25s: %lu bytes (requested %lu)"),
                    (LPTSTR)TEXT("Size of Tag Details"),
                    aftd.cbStruct, (DWORD)sizeof(aftd));

        //
        //  this would be bad
        //
        if (aftd.cbStruct < sizeof(aftd))
            continue;

        wfltr.dwFilterTag = aftd.dwFilterTag;
        AcmAppGetFilterDescription(&wfltr, ach, NULL);
        MEditPrintF(hedit, TEXT("%25s: [%lu], %s"), (LPTSTR)TEXT("Filter Tag"), aftd.dwFilterTag, (LPTSTR)ach);
        MEditPrintF(hedit, TEXT("%25s: %lu bytes"), (LPTSTR)TEXT("Format Size (Max)"), aftd.cbFilterSize);
        MEditPrintF(hedit, TEXT("%25s: %.08lXh"), (LPTSTR)TEXT("Standard Support"), aftd.fdwSupport);
        MEditPrintF(hedit, TEXT("%25s: %lu"), (LPTSTR)TEXT("Standard Filters"), aftd.cStandardFilters);
        MEditPrintF(hedit, TEXT("%25s: '%s'\r\n"), (LPTSTR)TEXT("Filter Tag Name"), (LPTSTR)aftd.szFilterTag);
    }

    return (TRUE);
} // AcmAppDisplayDriverTags()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverDetailsDlgProc
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriverDetailsDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    HACMDRIVERID        hadid;
    HWND                hedit;
    UINT                uId;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hedit = GetDlgItem(hwnd, IDD_AADETAILS_EDIT_DETAILS);
            SetWindowFont(hedit, ghfontApp, FALSE);

            //
            //  clear the display
            //
            AppHourGlass(TRUE);
            SetWindowRedraw(hedit, FALSE);
            MEditPrintF(hedit, NULL);

            hadid = (HACMDRIVERID)(UINT)lParam;
            if (NULL == hadid)
            {
                MEditPrintF(hedit, TEXT("\r\n\r\nhmm..."));
            }
            else
            {
                AcmAppDisplayDriverDetails(hedit, hadid);
            }

            Edit_SetSel(hedit, (WPARAM)0, (LPARAM)0);

            SetWindowRedraw(hedit, TRUE);
            AppHourGlass(FALSE);

            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);

            if ((IDOK == uId) || (IDCANCEL == uId))
            {
                EndDialog(hwnd, (IDOK == uId));
            }
            break;
    }

    return (FALSE);
} // AcmAppDriverDetailsDlgProc()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverFormatDetailsDlgProc
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriverFormatDetailsDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    HACMDRIVERID        hadid;
    HWND                hlb;
    HWND                hedit;
    int                 n;
    UINT                uId;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hlb = GetDlgItem(GetParent(hwnd), IDD_AADRIVERFORMATS_LIST_FORMATS);
            n   = ListBox_GetCurSel(hlb);
            if (LB_ERR == n)
                return (TRUE);

            hadid = (HACMDRIVERID)(UINT)ListBox_GetItemData(hlb, n);
            hedit = GetDlgItem(hwnd, IDD_AADETAILS_EDIT_DETAILS);
            SetWindowFont(hedit, ghfontApp, FALSE);

            //
            //  clear the display
            //
            AppHourGlass(TRUE);
            SetWindowRedraw(hedit, FALSE);
            MEditPrintF(hedit, NULL);

            MEditPrintF(hedit, TEXT("\r\n\r\nFormat Details!"));

            Edit_SetSel(hedit, (WPARAM)0, (LPARAM)0);

            SetWindowRedraw(hedit, TRUE);
            AppHourGlass(FALSE);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == uId) || (IDCANCEL == uId))
            {
                EndDialog(hwnd, (IDOK == uId));
            }
            break;
    }

    return (FALSE);
} // AcmAppDriverFormatDetailsDlgProc()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverFormatEnumCallback
//
//  Description:
//
//
//  Arguments:
//      HACMDRIVERID hadid:
//
//      DWORD dwInstance:
//
//      DWORD fdwSupport:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriverFormatEnumCallback
(
    HACMDRIVERID        hadid,
    LPACMFORMATDETAILS  pafd,
    DWORD               dwInstance,
    DWORD               fdwSupport
)
{
    TCHAR               ach[APP_MAX_STRING_CHARS];
    HWND                hlb;
    int                 nIndex;
    LPARAM              lParam;
    UINT                cbwfx;

    //
    //
    //
    hlb = (HWND)(UINT)dwInstance;

    cbwfx = SIZEOF_WAVEFORMATEX(pafd->pwfx);

    wsprintf(ach, gszFormatDriverFormatsList,
             hadid,
             pafd->dwFormatIndex,
             pafd->dwFormatTag,
             pafd->fdwSupport,
             cbwfx,
             (LPTSTR)pafd->szFormat);

    AcmAppDebugLog(ach);
    AcmAppDebugLog(TEXT("\r\n"));

    nIndex = ListBox_AddString(hlb, ach);
    lParam = (LPARAM)(UINT)hadid;
    ListBox_SetItemData(hlb, nIndex, lParam);

    //
    //  return TRUE to continue with enumeration
    //
    return (TRUE);
} // AcmAppDriverFormatEnumCallback()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverFormatsDlgProc
//
//  Description:
//      This dialog procedure is used to display driver formats.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriverFormatsDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    static PTABBEDLISTBOX   ptlb;
    HWND                    hwndStatic;
    MMRESULT                mmr;
    RECT                    rc;
    PAINTSTRUCT             ps;
    UINT                    uId;
    UINT                    uCode;

    HACMDRIVER              had;
    ACMFORMATDETAILS        afd;
    PWAVEFORMATEX           pwfx;
    DWORD                   cbwfx;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hwndStatic = GetDlgItem(hwnd, IDD_AADRIVERFORMATS_STATIC_POSITION);

            GetWindowRect(hwndStatic, &rc);
            ScreenToClient(hwnd, (LPPOINT)&rc.left);
            ScreenToClient(hwnd, (LPPOINT)&rc.right);

            ShowWindow(hwndStatic, SW_HIDE);

            EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERFORMATS_BTN_DETAILS), FALSE);

            ptlb = TlbCreate(hwnd, IDD_AADRIVERFORMATS_LIST_FORMATS, &rc);
            if (NULL == ptlb)
                return (TRUE);

            TlbSetFont(ptlb, GetStockFont(SYSTEM_FONT), FALSE);
            TlbSetTitleAndTabs(ptlb, gszFormatDriverFormatsTitle, FALSE);

            SetWindowPos(ptlb->hlb, GetDlgItem(hwnd, IDOK),
                            0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

            ListBox_ResetContent(ptlb->hlb);

            AcmAppDebugLog(NULL);

            had = (HACMDRIVER)(UINT)lParam;

            mmr = acmMetrics((HACMOBJ)had, ACM_METRIC_MAX_SIZE_FORMAT, &cbwfx);
            if (MMSYSERR_NOERROR != mmr)
                return (TRUE);

            pwfx = (PWAVEFORMATEX)LocalAlloc(LPTR, (UINT)cbwfx);
            if (NULL == pwfx)
                return (TRUE);

            _fmemset(&afd, 0, sizeof(afd));

            afd.cbStruct    = sizeof(afd);
            afd.dwFormatTag = WAVE_FORMAT_UNKNOWN;
            afd.pwfx        = pwfx;
            afd.cbwfx       = cbwfx;

            //
            //
            //
            SetWindowRedraw(ptlb->hlb, FALSE);

            mmr = acmFormatEnum(had,
                                &afd,
                                AcmAppDriverFormatEnumCallback,
                                (DWORD)(UINT)ptlb->hlb,
                                0L);
            if (MMSYSERR_NOERROR == mmr)
            {
                ListBox_SetCurSel(ptlb->hlb, 0);
                EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERFORMATS_BTN_DETAILS), TRUE);
            }

            SetWindowRedraw(ptlb->hlb, TRUE);
            LocalFree((HLOCAL)pwfx);

            return (TRUE);

        case WM_PAINT:
            if (NULL != ptlb)
            {
                BeginPaint(hwnd, &ps);
                TlbPaint(ptlb, hwnd, ps.hdc);
                EndPaint(hwnd, &ps);
            }
            break;

        case WM_COMMAND:
            uId   = GET_WM_COMMAND_ID(wParam, lParam);
            uCode = GET_WM_COMMAND_CMD(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                case IDCANCEL:
                    if (NULL != ptlb)
                    {
                        //
                        //  hadidk! don't destroy the listbox window, but
                        //  free all other memory for TLB. the listbox
                        //  window will be destroyed when the dialog is
                        //  destroyed.
                        //
                        ptlb->hlb = NULL;

                        TlbDestroy(ptlb);
                        ptlb = NULL;
                    }

                    EndDialog(hwnd, (IDOK == uId));
                    break;

                case IDD_AADRIVERFORMATS_BTN_DETAILS:
                    DialogBoxParam(ghinst,
                                   DLG_AADETAILS,
                                   hwnd,
                                   AcmAppDriverFormatDetailsDlgProc,
                                   uId);
                    break;

                case IDD_AADRIVERFORMATS_LIST_FORMATS:
                    switch (uCode)
                    {
                        case LBN_SELCHANGE:
                            break;

                        case LBN_DBLCLK:
                            DialogBoxParam(ghinst,
                                           DLG_AADETAILS,
                                           hwnd,
                                           AcmAppDriverFormatDetailsDlgProc,
                                           IDD_AADRIVERFORMATS_BTN_DETAILS);
                            break;
                    }
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDriverFormatsDlgProc()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverEnumCallback
//
//  Description:
//
//
//  Arguments:
//      HACMDRIVERID hadid:
//
//      DWORD dwInstance:
//
//      DWORD fdwSupport:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriverEnumCallback
(
    HACMDRIVERID        hadid,
    DWORD               dwInstance,
    DWORD               fdwSupport
)
{
    static TCHAR    szBogus[]       = TEXT("????");

    MMRESULT            mmr;
    TCHAR               ach[APP_MAX_STRING_CHARS];
    HWND                hlb;
    int                 n;
    ACMDRIVERDETAILS    add;
    BOOL                fDisabled;
    DWORD               dwPriority;

    //
    //
    //
    hlb = (HWND)(UINT)dwInstance;

    add.cbStruct = sizeof(add);
    mmr = acmDriverDetails(hadid, &add, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        lstrcpy(add.szShortName, szBogus);
        lstrcpy(add.szLongName,  szBogus);
    }

    dwPriority = (DWORD)-1L;
    acmMetrics((HACMOBJ)hadid, ACM_METRIC_DRIVER_PRIORITY, &dwPriority);

    fDisabled = (0 != (ACMDRIVERDETAILS_SUPPORTF_DISABLED & fdwSupport));

    wsprintf(ach, gszFormatDriversList,
             hadid,
             (LPTSTR)add.szShortName,
             dwPriority,
             fDisabled ? (LPTSTR)TEXT(" (disabled)") : (LPTSTR)gszNull,
             fdwSupport,
             (LPTSTR)add.szLongName);

    AcmAppDebugLog(ach);
    AcmAppDebugLog(TEXT("\r\n"));

    n = ListBox_AddString(hlb, ach);
    ListBox_SetItemData(hlb, n, (LPARAM)(UINT)hadid);

    //
    //  return TRUE to continue with enumeration (FALSE will stop the
    //  enumerator)
    //
    return (TRUE);
} // AcmAppDriverEnumCallback()

//--------------------------------------------------------------------------;
//
//  HACMDRIVERID AcmAppGetSelectedDriver
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//  Return (HACMDRIVERID):
//
//
//--------------------------------------------------------------------------;

HACMDRIVERID FNLOCAL AcmAppGetSelectedDriver
(
    HWND            hwnd
)
{
    HWND            hlb;
    UINT            u;
    HACMDRIVERID    hadid;

    hlb = GetDlgItem(hwnd, IDD_AADRIVERS_LIST_DRIVERS);

    u = (UINT)ListBox_GetCurSel(hlb);
    if (LB_ERR == u)
    {
        DPF(0, "!AcmAppGetSelectedDriver: apparently there is no selected driver?");
        return (NULL);
    }

    hadid = (HACMDRIVERID)(UINT)ListBox_GetItemData(hlb, u);
    if (NULL == hadid)
    {
        DPF(0, "!AcmAppGetSelectedDriver: NULL item data for selected driver!!?");
        return (NULL);
    }

    return (hadid);
} // AcmAppGetSelectedDriver()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriverSelected
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

HACMDRIVER FNLOCAL AcmAppDriverSelected
(
    HWND                    hwnd
)
{
    LRESULT             lr;
    MMRESULT            mmr;
    BOOL                f;
    HACMDRIVERID        hadid;
    HACMDRIVER          had;
    DWORD               fdwSupport;

    f = FALSE;

    //
    //
    //
    if (NULL != ghadSelected)
    {
        mmr = acmDriverClose(ghadSelected, 0L);
        if (MMSYSERR_NOERROR != mmr)
        {
            DPF(0, "!AcmAppDriverSelected: driver %.04Xh failed to close! mmr=%u", ghadSelected, mmr);
        }

        ghadSelected = NULL;
    }

    had = NULL;

    //
    //
    //
    hadid = AcmAppGetSelectedDriver(hwnd);
    if (NULL != hadid)
    {
        mmr = acmMetrics((HACMOBJ)hadid, ACM_METRIC_DRIVER_SUPPORT, &fdwSupport);
        if (MMSYSERR_NOERROR != mmr)
        {
            fdwSupport = ACMDRIVERDETAILS_SUPPORTF_DISABLED;

            //
            //  !!! this should NEVER EVER EVER HAPPEN !!!
            //
            DPF(0, "!AcmAppDriverSelected: driver id %.04Xh failed to give support! mmr=%u", hadid, mmr);
        }

        if (0 == (ACMDRIVERDETAILS_SUPPORTF_DISABLED & fdwSupport))
        {
            mmr = acmDriverOpen(&had, hadid, 0L);
            if (MMSYSERR_NOERROR != mmr)
            {
                DPF(0, "!AcmAppDriverSelected: driver id %.04Xh failed to open! mmr=%u", hadid, mmr);
            }
        }

        //
        //
        //
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_DETAILS), TRUE);

        lr = acmDriverMessage((HACMDRIVER)hadid, ACMDM_DRIVER_ABOUT, -1L, 0L);
        f = (MMSYSERR_NOERROR == lr);
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_ABOUT), f);

lr = acmDriverMessage((HACMDRIVER)hadid, DRV_QUERYCONFIGURE, 0L, 0L);
        f = (0L != lr);
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_CONFIG), f);
    }
    else
    {
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_DETAILS), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_ABOUT), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_CONFIG), FALSE);
    }

    //
    //
    //
    if (NULL == had)
    {
        //
        //
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_FORMATS), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_FILTERS), FALSE);
    }
    else
    {
        f = (0 == (ACMDRIVERDETAILS_SUPPORTF_DISABLED & fdwSupport));
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_FORMATS), f);

        f = (0 != (ACMDRIVERDETAILS_SUPPORTF_FILTER & fdwSupport));
        EnableWindow(GetDlgItem(hwnd, IDD_AADRIVERS_BTN_FILTERS), f);
    }

    //
    //
    //
    ghadidSelected = hadid;
    ghadSelected   = had;

    return (had);
} // AcmAppDriverSelected()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDriversDlgProc
//
//  Description:
//      This dialog procedure is used to display ACM driver capabilities.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDriversDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PTABBEDLISTBOX      ptlb;
    HWND                hstat;
    MMRESULT            mmr;
    LRESULT             lr;
    RECT                rc;
    PAINTSTRUCT         ps;
    UINT                uId;
    UINT                uCode;
    UINT                u;

    ptlb = (PTABBEDLISTBOX)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hstat = GetDlgItem(hwnd, IDD_AADRIVERS_STATIC_POSITION);

            GetWindowRect(hstat, &rc);
            ScreenToClient(hwnd, (LPPOINT)&rc.left);
            ScreenToClient(hwnd, (LPPOINT)&rc.right);

            ShowWindow(hstat, SW_HIDE);

            ptlb = TlbCreate(hwnd, IDD_AADRIVERS_LIST_DRIVERS, &rc);
            SetWindowLong(hwnd, DWL_USER, (LONG)(UINT)ptlb);
            if (NULL == ptlb)
            {
                EndDialog(hwnd, FALSE);
                return (TRUE);
            }

            //
            //
            //
            TlbSetFont(ptlb, GetStockFont(SYSTEM_FONT), FALSE);
            TlbSetTitleAndTabs(ptlb, gszFormatDriversTitle, FALSE);

            SetWindowPos(ptlb->hlb, GetDlgItem(hwnd, IDOK),
                            0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

            SendMessage(hwnd, WM_ACMAPP_ACM_NOTIFY, 0, 0L);
            return (TRUE);

        case WM_PAINT:
            if (NULL != ptlb)
            {
                BeginPaint(hwnd, &ps);
                TlbPaint(ptlb, hwnd, ps.hdc);
                EndPaint(hwnd, &ps);
            }
            break;

        case WM_ACMAPP_ACM_NOTIFY:
            u = (UINT)ListBox_GetCurSel(ptlb->hlb);
            if (LB_ERR == u)
            {
                u = 0;
            }

            SetWindowRedraw(ptlb->hlb, FALSE);
            ListBox_ResetContent(ptlb->hlb);

            AcmAppDebugLog(NULL);

            //
            //
            //
            mmr = acmDriverEnum(AcmAppDriverEnumCallback,
                                (DWORD)(UINT)ptlb->hlb,
                                ACM_DRIVERENUMF_DISABLED);
            if (MMSYSERR_NOERROR != mmr)
            {
                //
                //  this will let us know something is wrong!
                //
                EnableWindow(GetDlgItem(hwnd, IDOK), FALSE);
            }

            ListBox_SetCurSel(ptlb->hlb, u);
            SetWindowRedraw(ptlb->hlb, TRUE);

            AcmAppDriverSelected(hwnd);
            break;

        case WM_COMMAND:
            uId   = GET_WM_COMMAND_ID(wParam, lParam);
            uCode = GET_WM_COMMAND_CMD(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                case IDCANCEL:
                    if (NULL != ptlb)
                    {
                        //
                        //  hadidk! don't destroy the listbox window, but
                        //  free all other memory for TLB. the listbox
                        //  window will be destroyed when the dialog is
                        //  destroyed.
                        //
                        ptlb->hlb = NULL;

                        TlbDestroy(ptlb);
                        ptlb = NULL;
                    }

                    if (NULL != ghadSelected)
                    {
                        acmDriverClose(ghadSelected, 0L);
                        ghadSelected = NULL;
                    }

                    EndDialog(hwnd, (IDOK == uId));
                    break;

                case IDD_AADRIVERS_BTN_DETAILS:
                    DialogBoxParam(ghinst,
                                   DLG_AADETAILS,
                                   hwnd,
                                   AcmAppDriverDetailsDlgProc,
                                   (LPARAM)(UINT)ghadidSelected);
                    break;

                case IDD_AADRIVERS_BTN_FORMATS:
                    if (NULL != ghadSelected)
                    {
                        DialogBoxParam(ghinst,
                                       DLG_AADRIVERFORMATS,
                                       hwnd,
                                       AcmAppDriverFormatsDlgProc,
                                       (LPARAM)(UINT)ghadSelected);
                    }
                    break;

                case IDD_AADRIVERS_BTN_FILTERS:
#if 0
                    if (NULL != ghadSelected)
                    {
                        DialogBoxParam(ghinst,
                                       DLG_AADRIVERFILTERS,
                                       hwnd,
                                       AcmAppDriverFiltersDlgProc,
                                       (LPARAM)(UINT)ghadSelected);
                    }
#endif
                    break;

                case IDD_AADRIVERS_BTN_CONFIG:
                    if (NULL == ghadidSelected)
                        break;

                    lr = acmDriverMessage((HACMDRIVER)ghadidSelected,
                                          DRV_CONFIGURE,
                                          (LPARAM)(UINT)hwnd,
                                          0L);
                    switch (lr)
                    {
                        case DRVCNF_CANCEL:
                            //
                            //  user canceled the configuration (no
                            //  configuration information was changed)
                            //
                            break;

                        case DRVCNF_OK:
                            //
                            //  user changed AND accepted configuration
                            //  changes--applications should refresh
                            //  anything they have for this driver
                            //
                            break;

                        case DRVCNF_RESTART:
                            //
                            //  user changed and accepted configuration
                            //  changes--however, Windows must be
                            //  restarted for the changes to take
                            //  affect.
                            //
#pragma message("----AcmAppDriversDlgProc: must do DRVCNF_RESTART!")
                            break;

                        default:
                            DPF(0, "!configure: driver returned bogus value=%lu!", lr);
                            break;
                    }
                    break;

                case IDD_AADRIVERS_BTN_ABOUT:
                    if (NULL != ghadidSelected)
                    {
                        //
                        //  some driver was actually silly enough to provide
                        //  a custom about box... i'm glad i don't have to
                        //  port, maintain, and support this driver!
                        //
                        lr = acmDriverMessage((HACMDRIVER)ghadidSelected,
                                              ACMDM_DRIVER_ABOUT,
                                              (LPARAM)(UINT)hwnd,
                                              0L);
                    }
                    break;

                case IDD_AADRIVERS_BTN_ABLE:
                    if (NULL != ghadidSelected)
                    {
                        DWORD       fdwSupport;
                        DWORD       fdwPriority;

                        mmr = acmMetrics((HACMOBJ)ghadidSelected,
                                         ACM_METRIC_DRIVER_SUPPORT,
                                         &fdwSupport);

                        if (MMSYSERR_NOERROR != mmr)
                        {
                            MessageBeep(0);
                            break;
                        }

                        if (0 == (ACMDRIVERDETAILS_SUPPORTF_DISABLED & fdwSupport))
                        {
                            fdwPriority = ACM_DRIVERPRIORITYF_DISABLE;
                        }
                        else
                        {
                            fdwPriority = ACM_DRIVERPRIORITYF_ENABLE;
                        }

                        mmr = acmDriverPriority(ghadidSelected,
                                                0L,
                                                fdwPriority);
                        if (MMSYSERR_NOERROR != mmr)
                        {
                            MessageBeep(0);
                            break;
                        }
                    }
                    break;

                case IDD_AADRIVERS_BTN_TOTOP:
                    if (NULL != ghadidSelected)
                    {
                        mmr = acmDriverPriority(ghadidSelected, 1, 0L);
                        if (MMSYSERR_NOERROR != mmr)
                        {
                            MessageBeep(0);
                            break;
                        }
                    }
                    break;

                case IDD_AADRIVERS_LIST_DRIVERS:
                    switch (uCode)
                    {
                        case LBN_SELCHANGE:
                            AcmAppDriverSelected(hwnd);
                            break;

                        case LBN_DBLCLK:
                            if (GetKeyState(VK_CONTROL) < 0)
                            {
                                uId = IDD_AADRIVERS_BTN_ABLE;
                            }
                            else if (GetKeyState(VK_SHIFT) < 0)
                            {
                                uId = IDD_AADRIVERS_BTN_TOTOP;
                            }
                            else
                            {
                                uId = IDD_AADRIVERS_BTN_DETAILS;
                            }

                            FORWARD_WM_COMMAND(hwnd,
                                               uId,
                                               GetDlgItem(hwnd, uId),
                                               1,
                                               SendMessage);
                            break;
                    }
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDriversDlgProc()


AAFILE.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  aafile.c
//
//  Description:
//      Contains routines to deal with the FILE menu - loading and saving
//      files, and also contains some support routines for dealing with
//      RIFF files.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>

#include <mmreg.h>
#include <msacm.h>

#include "appport.h"
#include "waveio.h"
#include "acmapp.h"

#include "debug.h"

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  DWORD DosGetFileAttributes
//
//  Description:
//      INT 21 - DOS 2+ - GET FILE ATTRIBUTES
//          AX = 4300h
//          DS:DX -> ASCIZ file name or directory name without trailing slash
//
//      Return: CF set on error
//              AX = error code (01h,02h,03h,05h) (see AH=59h)
//              CF clear if successful
//              CX = file attributes (see AX=4301h)
//
//      SeeAlso: AX=4301h, INT 2F/AX=110Fh
//
//      --------
//      INT 21 - DOS 2+ - PUT FILE ATTRIBUTES (CHMOD)
//          AX = 4301h
//          CX = file attribute bits
//              bit 0 = read only
//                  1 = hidden file
//                  2 = system file
//                  3 = volume label
//                  4 = subdirectory
//                  5 = written since backup ("archive" bit)
//                  8 = shareable (Novell NetWare)
//          DS:DX -> ASCIZ file name
//
//      Return: CF set on error
//              AX = error code (01h,02h,03h,05h) (see AH=59h)
//          CF clear if successful
//
//      Note:   will not change volume label or directory attributes
//
//      SeeAlso: AX=4300h, INT 2F/AX=110Eh
//
//
//  Arguments:
//      LPTSTR pszFilePath:
//
//  Return (DWORD):
//
//
//--------------------------------------------------------------------------;
#ifndef WIN32
#pragma optimize("", off)
DWORD FNGLOBAL DosGetFileAttributes
(
    LPTSTR          pszFilePath
)
{
    WORD        fwDosAttributes;

    _asm
    {
        push    ds
        mov     ax, 4300h
        lds     dx, pszFilePath
        int     21h
        jnc     Get_File_Attributes_Continue

        xor     cx, cx

Get_File_Attributes_Continue:

        mov     fwDosAttributes, cx
        pop     ds
    }

    return ((DWORD)fwDosAttributes);
} // DosGetFileAttributes()
#pragma optimize("", on)
#endif

//--------------------------------------------------------------------------;
//
//  DWORD DosGetDateTime
//
//  Description:
//
//  Arguments:
//      HFILE hf:
//
//  Return (DWORD):
//
//
//--------------------------------------------------------------------------;
#ifndef WIN32
#pragma optimize("", off)
DWORD FNGLOBAL DosGetDateTime
(
    HFILE       hf
)
{
    WORD        wDosDate;
    WORD        wDosTime;

    _asm
    {
        mov     ax, 5700h
        mov     bx, hf
        int     21h
        jnc     Get_Date_Time_Continue

        xor     cx, cx
        xor     dx, dx

Get_Date_Time_Continue:

        mov     wDosDate, dx
        mov     wDosTime, cx
    }

    return ((DWORD)MAKELONG(wDosDate, wDosTime));
} // DosGetDateTime()
#pragma optimize("", on)
#endif

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppFileSaveModified
//
//  Description:
//      This function tests if the current file has been modified, and
//      if it has it gives the option of saving the file.
//
//      NOTE! This function does *NOT* clear the modified bit for the
//      file. The calling function must do this if necessary.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PACMAPPFILEDESC paafd: Pointer to file descriptor.
//
//  Return (BOOL):
//      Returns TRUE if the calling function should continue--the file was
//      either saved or the user does not wish to save it. Returns FALSE
//      if the calling function should cancel its operation--the user
//      wants to keep the data, but it has not been saved.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppFileSaveModified
(
    HWND            hwnd,
    PACMAPPFILEDESC paafd
)
{
    BOOL    f;
    int     n;

    //
    //  check if the contents of the file have been modified--if they have
    //  then ask the user if they want to save the current contents...
    //
    f = (0 != (ACMAPPFILEDESC_STATEF_MODIFIED & paafd->fdwState));
    if (f)
    {
        //
        //  display an appropriate message box asking for the user's opinion
        //
        n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION| MB_SETFOREGROUND,
                      TEXT("The file '%s' has been modified. Do you want to save these changes?"),
                      (LPSTR)paafd->szFilePath);
        switch (n)
        {
            case IDYES:
                f = AppFileSave(hwnd, paafd, FALSE);
                if (f)
                    break;

                // -- fall through --

            case IDCANCEL:
                //
                //  don't continue!
                //
                return (FALSE);

            case IDNO:
                break;
        }
    }

    //
    //  ok to continue...
    //
    return (TRUE);
} // AcmAppFileSaveModified()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppFileNew
//
//  Description:
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PACMAPPFILEDESC paafd: Pointer to file descriptor.
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppFileNew
(
    HWND                hwnd,
    PACMAPPFILEDESC     paafd
)
{
    TCHAR               szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR               szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    MMRESULT            mmr;
    LPWAVEFORMATEX      pwfx;
    DWORD               cbwfx;
    BOOL                f;
    ACMFORMATCHOOSE     afc;
    HMMIO               hmmio;
    MMCKINFO            ckRIFF;
    MMCKINFO            ck;
    DWORD               cSamples;

    if (!gfAcmAvailable)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppFileNew() called when ACM is not installed!"));
        return (FALSE);
    }

    //
    //  test for a modified file first...
    //
    f = AcmAppFileSaveModified(hwnd, paafd);
    if (!f)
        return (FALSE);

    //
    //  get a filename
    //
    szFileTitle[0] = '\0';
    szFilePath[0]  = '\0';

    f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GETFILENAMEF_SAVE);
    if (!f)
        return (FALSE);

    //
    //
    //
    //
    //
    //
    mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &cbwfx);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppFileNew() acmMetrics failed mmr=%u!"), mmr);
        return (FALSE);
    }

    pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, cbwfx);
    if (NULL == pwfx)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppFileNew() GlobalAllocPtr(%lu) failed!"), cbwfx);
        return (FALSE);
    }

    //
    //
    //
    //
    f = FALSE;

    //
    //  initialize the ACMFORMATCHOOSE members
    //
    memset(&afc, 0, sizeof(afc));

    afc.cbStruct        = sizeof(afc);
    afc.fdwStyle        = ACMFORMATCHOOSE_STYLEF_SHOWHELP;
    afc.hwndOwner       = hwnd;
    afc.pwfx            = pwfx;
    afc.cbwfx           = cbwfx;
    afc.pszTitle        = TEXT("ACM App: New Format Choice");

    afc.szFormatTag[0]  = '\0';
    afc.szFormat[0]     = '\0';
    afc.pszName         = NULL;
    afc.cchName         = 0;

    afc.fdwEnum         = 0;
    afc.pwfxEnum        = NULL;

    afc.hInstance       = NULL;
    afc.pszTemplateName = NULL;
    afc.lCustData       = 0L;
    afc.pfnHook         = NULL;

    //
    //
    //
    mmr = acmFormatChoose(&afc);
    if (MMSYSERR_NOERROR != mmr)
    {
        if (ACMERR_CANCELED != mmr)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                    TEXT("acmFormatChoose() failed with error = %u!"), mmr);
        }

        GlobalFreePtr(pwfx);
        return (FALSE);
    }

    //
    //
    //
    hmmio = mmioOpen(szFilePath,
                     NULL,
                     MMIO_CREATE | MMIO_WRITE | MMIO_EXCLUSIVE | MMIO_ALLOCBUF);
    if (NULL == hmmio)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppFileNew() cannot create file '%s'!"), (LPSTR)szFilePath);

        GlobalFreePtr(pwfx);
        return (FALSE);
    }

    //
    //  create the RIFF chunk of form type 'WAVE'
    //
    ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    ckRIFF.cksize  = 0L;
    mmioCreateChunk(hmmio, &ckRIFF, MMIO_CREATERIFF);

    //
    //  now create the destination fmt, fact, and data chunks _in that order_
    //
    //  hmmio is now descended into the 'RIFF' chunk--create the format chunk
    //  and write the format header into it
    //
    cbwfx = SIZEOF_WAVEFORMATEX(pwfx);

    ck.ckid   = mmioFOURCC('f', 'm', 't', ' ');
    ck.cksize = 0L;
    mmioCreateChunk(hmmio, &ck, 0);

    mmioWrite(hmmio, (HPSTR)pwfx, cbwfx);
    mmioAscend(hmmio, &ck, 0);

    //
    //  create the 'fact' chunk (not necessary for PCM--but is nice to have)
    //  since we are not writing any data to this file (yet), we set the
    //  samples contained in the file to 0..
    //
    ck.ckid   = mmioFOURCC('f', 'a', 'c', 't');
    ck.cksize = 0L;
    mmioCreateChunk(hmmio, &ck, 0);

    cSamples  = 0L;
    mmioWrite(hmmio, (HPSTR)&cSamples, sizeof(DWORD));
    mmioAscend(hmmio, &ck, 0);

    //
    //  create the data chunk with no data..
    //
    ck.ckid   = mmioFOURCC('d', 'a', 't', 'a');
    ck.cksize = 0L;
    mmioCreateChunk(hmmio, &ck, 0);
    mmioAscend(hmmio, &ck, 0);

    mmioAscend(hmmio, &ckRIFF, 0);

    mmioClose(hmmio, 0);

    //
    //
    //
    GlobalFreePtr(pwfx);

    lstrcpy(paafd->szFilePath, szFilePath);
    lstrcpy(paafd->szFileTitle, szFileTitle);

    return (AcmAppFileOpen(hwnd, paafd));

    //
    //  success
    //
    return (TRUE);
} // AcmAppFileNew()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppFileOpen
//
//  Description:
//      This function opens the specified file and get the important info
//      from it.
//
//      NOTE! This function does NOT check for a modified file! It is
//      assumed that the calling function took care of everything before
//      calling this function.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PACMAPPFILEDESC paafd: Pointer to file descriptor.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if an error occurred. If an error does occur, then the contents
//      of the file descriptor will remain unchanged.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppFileOpen
(
    HWND            hwnd,
    PACMAPPFILEDESC paafd
)
{
    WAVEIOCB    wio;
    WIOERR      werr;

#ifdef WIN32
    HANDLE      hf;
#else
    #define SEEK_SET        0       // flags for _lseek
    #define SEEK_CUR        1
    #define SEEK_END        2

    HFILE       hf;
    OFSTRUCT    of;
    DWORD       dw;
#endif
    DWORD       cbFileSize;
    BOOL        fReturn;

    //
    //  blow previous stuff...
    //
    if (NULL != paafd->pwfx)
    {
        GlobalFreePtr(paafd->pwfx);
        paafd->pwfx  = NULL;
        paafd->cbwfx = 0;
    }

    paafd->fdwState          = 0L;
    paafd->cbFileSize        = 0L;
    paafd->uDosChangeDate    = 0;
    paafd->uDosChangeTime    = 0;
    paafd->fdwFileAttributes = 0L;
    paafd->dwDataBytes       = 0L;
    paafd->dwDataSamples     = 0L;

    //
    //  open the file for reading..
    //
#ifdef WIN32
    hf = CreateFile(paafd->szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, 0, 0);
    if (INVALID_HANDLE_VALUE == hf)
        return (FALSE);
#else
    of.cBytes = sizeof(of);
    hf = OpenFile(paafd->szFilePath, &of, OF_READ);
    if (HFILE_ERROR == hf)
        return (FALSE);
#endif

    //
    //  assume the worst
    //
    fReturn = FALSE;

    //
    //  determine the length in _bytes_ of the file
    //
#ifdef WIN32
    cbFileSize = GetFileSize((HANDLE)hf, NULL);
#else
    cbFileSize = _llseek(hf, 0L, SEEK_END);
    _llseek(hf, 0L, SEEK_SET);
#endif

    //
    //
    //
    //
    paafd->cbFileSize        = cbFileSize;

#ifdef WIN32
{
    BY_HANDLE_FILE_INFORMATION  bhfi;
    WORD                        wDosChangeDate;
    WORD                        wDosChangeTime;

    GetFileInformationByHandle(hf, &bhfi);

    paafd->fdwFileAttributes = bhfi.dwFileAttributes;

    FileTimeToDosDateTime(&bhfi.ftLastWriteTime,
                          &wDosChangeDate, &wDosChangeTime);

    paafd->uDosChangeDate = (UINT)wDosChangeDate;
    paafd->uDosChangeTime = (UINT)wDosChangeTime;
}
#else
    paafd->fdwFileAttributes = DosGetFileAttributes(paafd->szFilePath);

    dw = DosGetDateTime(hf);
    paafd->uDosChangeDate = LOWORD(dw);
    paafd->uDosChangeTime = HIWORD(dw);
#endif

    //
    //  now return the fully qualified path and title for the file
    //
#ifndef WIN32
    lstrcpy(paafd->szFilePath, of.szPathName);
#endif
    AppGetFileTitle(paafd->szFilePath, paafd->szFileTitle);

#ifdef WIN32
    CloseHandle(hf);
#else
    _lclose(hf);
#endif

    //
    //
    //
    //
    werr = wioFileOpen(&wio, paafd->szFilePath, 0L);
    if (WIOERR_NOERROR == werr)
    {
        UINT        cbwfx;

        cbwfx = SIZEOF_WAVEFORMATEX(wio.pwfx);

        paafd->pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, cbwfx);
        if (NULL != paafd->pwfx)
        {
            _fmemcpy(paafd->pwfx, wio.pwfx, cbwfx);

            paafd->cbwfx         = cbwfx;

            paafd->dwDataBytes   = wio.dwDataBytes;
            paafd->dwDataSamples = wio.dwDataSamples;

            fReturn = TRUE;
        }

        wioFileClose(&wio, 0L);
    }
    else
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
                  TEXT("The file '%s' cannot be loaded as a wave file (wio error=%u)."),
                  (LPTSTR)paafd->szFilePath, werr);
    }

    //
    //  !!! before returning, we really should try to display a error
    //      message... memory error, etc..
    //
    return (fReturn);
} // AcmAppFileOpen()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppOpenInstance
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      LPCTSTR pszFilePath:
//
//      BOOL fForceOpen:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppOpenInstance
(
    HWND                    hwnd,
    LPCTSTR                 pszFilePath,
    BOOL                    fForceOpen
)
{
    TCHAR               szCmdLine[APP_MAX_FILE_PATH_CHARS * 2];
    BOOL                f;
    UINT                uDosErr;

    //
    //
    //
    if (!fForceOpen)
    {
        if (0 == (APP_OPTIONSF_AUTOOPEN * gfuAppOptions))
        {
            return (TRUE);
        }
    }

    //
    //
    //
    if (0 == GetModuleFileName(ghinst, szCmdLine, SIZEOF(szCmdLine)))
    {
        //
        //  this would be fatal
        //
        AppMsgBox(hwnd, MB_ICONEXCLAMATION | MB_OK,
                  TEXT("GetModuleFileName() is unable to return self reference!"));

        return (FALSE);
    }

    lstrcat(szCmdLine, TEXT(" "));
    lstrcat(szCmdLine, pszFilePath);

#ifdef WIN32
{
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;

    //
    //  perform the equivalent of WinExec in NT, but we use a Unicode string
    //
    memset(&si, 0, sizeof(si));
    si.cb           = sizeof(si);
    si.dwFlags      = STARTF_USESHOWWINDOW;
    si.wShowWindow  = SW_SHOW;

    f = CreateProcess(NULL,
                      szCmdLine,
                      NULL,
                      NULL,
                      FALSE,
                      0,
                      NULL,
                      NULL,
                      &si,
                      &pi);

    if (f)
    {
        //
        //  as the docs say.. wait 10 second for process to go idle before
        //  continuing.
        //
        WaitForInputIdle(pi.hProcess, 10000);

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else
    {
        uDosErr = GetLastError();
    }
}
#else
{
    uDosErr = WinExec(szCmdLine, SW_SHOW);
    f = (uDosErr >= 32);
}
#endif

    if (!f)
    {
        AppMsgBox(hwnd, MB_ICONEXCLAMATION | MB_OK,
                  TEXT("WinExec(%s) failed! DOS error=%u."),
                  (LPTSTR)szCmdLine, uDosErr);

    }

    return (f);
} // AcmAppOpenInstance()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppFileSave
//
//  Description:
//      This function saves the file to the specified file.
//
//      NOTE! This function does NOT bring up a save file chooser dialog
//      if the file path is invalid. The calling function is responsible
//      for making sure the file path is valid before calling this function.
//
//      This function also does NOT modify the 'modified' bit of the file
//      descriptor. This is up to the calling function.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PACMAPPFILEDESC paafd: Pointer to file descriptor.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if an error occurred. If an error does occur, then the contents
//      of the file descriptor was not saved.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppFileSave
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle,
    UINT                    fuSave
)
{

    return (FALSE);
} // AcmAppFileSave()

//==========================================================================;
//==========================================================================;
//==========================================================================;
//==========================================================================;

//
//
//

#define IDD_INFOLIST            100
#define IDD_INFOINFO            101
#define IDD_INFOTEXT            102

#ifdef RC_INVOKED

#define DLG_INFOEDIT            31

#else

#define DLG_INFOEDIT            MAKEINTRESOURCE(31)

#endif

////////////////////////////////////////////////////////////////////////////

typedef struct tCHUNK
{
    FOURCC  fcc;
    DWORD   cksize;
    BYTE    data[];
} CHUNK, * PCHUNK, far * LPCHUNK;

typedef struct tDISP
{
    DWORD   cfid;   // Clipboard id of data
    HANDLE  h;      // handle to data
    struct tDISP *  next;    // next in list
} DISP;

typedef struct tINFODATA
{
    WORD    index;  // index into aINFO
    WORD    wFlags; // flags for chunk
    DWORD   dwINFOOffset;   // offset in file to INFO chunk

#define INFOCHUNK_MODIFIED  1
#define INFOCHUNK_REVERT    2   // command to revert to original text

    LPCTSTR   lpText; // text of modified chunk.  None if NULL.

    struct tINFODATA  near *  pnext; // next read sub-chunk
} INFODATA, * PINFODATA, FAR * LPINFODATA;

typedef struct tINFOCHUNK
{
    LPTSTR   lpChunk;    // complete chunk in memory (GlobalPtr)
    DWORD   cksize;     // size of chunk data
    PINFODATA   pHead;  // first sub-chunk data
} INFOCHUNK, * PINFOCHUNK, FAR * LPINFOCHUNK;

////////////////////////////////////////////////////////////////////////////
//
//  error returns from RIFF functions
//
#define RIFFERR_BASE         (0)
#define RIFFERR_NOERROR      (0)
#define RIFFERR_ERROR        (RIFFERR_BASE+1)
#define RIFFERR_BADPARAM     (RIFFERR_BASE+2)
#define RIFFERR_FILEERROR    (RIFFERR_BASE+3)
#define RIFFERR_NOMEM        (RIFFERR_BASE+4)
#define RIFFERR_BADFILE      (RIFFERR_BASE+5)

////////////////////////////////////////////////////////////////////////////
//
//  public function prototypes
//

#define RIFFAPI  FAR PASCAL

BOOL RIFFAPI riffCopyList(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck);
BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck);

LRESULT RIFFAPI  riffInitINFO(INFOCHUNK FAR * FAR * lplpInfo);
LRESULT RIFFAPI  riffReadINFO(HMMIO hmmio, const LPMMCKINFO lpck, LPINFOCHUNK lpInfo);
LRESULT RIFFAPI  riffEditINFO(HWND hwnd, LPINFOCHUNK lpInfo, HINSTANCE hInst);
LRESULT RIFFAPI  riffFreeINFO(INFOCHUNK FAR * FAR * lpnpInfo);
LRESULT RIFFAPI riffWriteINFO(HMMIO hmmioDst, LPINFOCHUNK lpInfo);

LRESULT RIFFAPI  riffReadDISP(HMMIO hmmio, LPMMCKINFO lpck, DISP FAR * FAR * lpnpDisp);
LRESULT RIFFAPI  riffFreeDISP(DISP FAR * FAR * lpnpDisp);
LRESULT RIFFAPI riffWriteDISP(HMMIO hmmio, DISP FAR * FAR * lpnpDisp);

LRESULT NEAR PASCAL riffParseINFO(const LPINFOCHUNK lpInfo);

/** BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
 *
 *  DESCRIPTION:
 *
 *
 *  ARGUMENTS:
 *      (LPWAVECONVCB lpwc, LPMMCKINFO lpck)
 *
 *  RETURN (BOOL NEAR PASCAL):
 *
 *
 *  NOTES:
 *
 **  */

BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
{
    MMCKINFO    ck;
    HPSTR       hpBuf;

    //
    //
    //
    hpBuf = (HPSTR)GlobalAllocPtr(GHND, lpck->cksize);
    if (!hpBuf)
        return (FALSE);

    ck.ckid   = lpck->ckid;
    ck.cksize = lpck->cksize;
    if (mmioCreateChunk(hmmioDst, &ck, 0))
        goto rscc_Error;

    if (mmioRead(hmmioSrc, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
        goto rscc_Error;

    if (mmioWrite(hmmioDst, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
        goto rscc_Error;

    if (mmioAscend(hmmioDst, &ck, 0))
        goto rscc_Error;

    if (hpBuf)
        GlobalFreePtr(hpBuf);

    return (TRUE);

rscc_Error:

    if (hpBuf)
        GlobalFreePtr(hpBuf);

    return (FALSE);
} /* RIFFSupCopyChunk() */

/** BOOL RIFFAPI riffCopyList(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
 *
 *  DESCRIPTION:
 *
 *
 *  ARGUMENTS:
 *  (HMMIO hmmioSrc, HMMIO hmmioDst, LPMMCKINFO lpck)
 *
 *  RETURN (BOOL NEAR PASCAL):
 *
 *
 *  NOTES:
 *
 ** */

BOOL RIFFAPI riffCopyList(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
{
    MMCKINFO    ck;
    HPSTR       hpBuf;
    DWORD       dwCopySize;

    hpBuf = (HPSTR)GlobalAllocPtr(GHND, lpck->cksize);
    if (!hpBuf)
        return (FALSE);

    dwCopySize=lpck->cksize;

    // mmio leaves us after LIST ID

    ck.ckid   = lpck->ckid;
    ck.cksize = dwCopySize;
    ck.fccType= lpck->fccType;

    if (mmioCreateChunk(hmmioDst, &ck, MMIO_CREATELIST))
        goto rscl_Error;

    // we already wrote 'LIST' ID, so reduce byte count
    dwCopySize-=sizeof(FOURCC);

    if (mmioRead(hmmioSrc, hpBuf, dwCopySize) != (LONG)dwCopySize)
        goto rscl_Error;

    if (mmioWrite(hmmioDst, hpBuf, dwCopySize) != (LONG)dwCopySize)
        goto rscl_Error;

    if (mmioAscend(hmmioDst, &ck, 0))
        goto rscl_Error;

    if (hpBuf)
        GlobalFreePtr(hpBuf);

    return (TRUE);

rscl_Error:

    if (hpBuf)
        GlobalFreePtr(hpBuf);

    return (FALSE);
} /* RIFFSupCopyList() */

/////////////////////////////////////////////////////////////////////////////

typedef struct tINFO
{
    PTSTR       pFOURCC;
    PTSTR       pShort;
    PTSTR       pLong;
} INFO;

static INFO aINFO[]=
{
TEXT("IARL"), TEXT("Archival Location"),  TEXT("Indicates where the subject of the file is archived."),
TEXT("IART"), TEXT("Artist"),             TEXT("Lists the artist of the original subject of the file. For example, \"Michaelangelo.\""),
TEXT("ICMS"), TEXT("Commissioned"),       TEXT("Lists the name of the person or organization that commissioned the subject of the file. For example, \"Pope Julian II.\""),
TEXT("ICMT"), TEXT("Comments"),           TEXT("Provides general comments about the file or the subject of the file. If the comment is several sentences long, end each sentence with a period. Do not include newline characters."),
TEXT("ICOP"), TEXT("Copyright"),          TEXT("Records the copyright information for the file. For example, \"Copyright Encyclopedia International 1991.\" If there are multiple copyrights, separate them by a semicolon followed by a space."),
TEXT("ICRD"), TEXT("Creation date"),      TEXT("Specifies the date the subject of the file was created. List dates in year-month-day format, padding one-digit months and days with a zero on the left. For example, \"1553-05-03\" for May 3, 1553."),
TEXT("ICRP"), TEXT("Cropped"),            TEXT("Describes whether an image has been cropped and, if so, how it was cropped. For example, \"lower right corner.\""),
TEXT("IDIM"), TEXT("Dimensions"),         TEXT("Specifies the size of the original subject of the file. For example, \"8.5 in h, 11 in w.\""),
TEXT("IDPI"), TEXT("Dots Per Inch"),      TEXT("Stores dots per inch setting of the digitizer used to produce the file, such as \"300.\""),
TEXT("IENG"), TEXT("Engineer"),           TEXT("Stores the name of the engineer who worked on the file. If there are multiple engineers, separate the names by a semicolon and a blank. For example, \"Smith, John; Adams, Joe.\""),
TEXT("IGNR"), TEXT("Genre"),              TEXT("Describes the original work, such as, \"landscape,\" \"portrait,\" \"still life,\" etc."),
TEXT("IKEY"), TEXT("Keywords"),           TEXT("Provides a list of keywords that refer to the file or subject of the file. Separate multiple keywords with a semicolon and a blank. For example, \"Seattle; aerial view; scenery.\""),
TEXT("ILGT"), TEXT("Lightness"),          TEXT("Describes the changes in lightness settings on the digitizer required to produce the file. Note that the format of this information depends on hardware used."),
TEXT("IMED"), TEXT("Medium"),             TEXT("Describes the original subject of the file, such as, \"computer image,\" \"drawing,\" \"lithograph,\" and so forth."),
TEXT("INAM"), TEXT("Name"),               TEXT("Stores the title of the subject of the file, such as, \"Seattle From Above.\""),
TEXT("IPLT"), TEXT("Palette Setting"),    TEXT("Specifies the number of colors requested when digitizing an image, such as \"256.\""),
TEXT("IPRD"), TEXT("Product"),            TEXT("Specifies the name of the title the file was originally intended for, such as \"Encyclopedia of Pacific Northwest Geography.\""),
TEXT("ISBJ"), TEXT("Subject"),            TEXT("Describes the contents of the file, such as \"Aerial view of Seattle.\""),

TEXT("ISFT"), TEXT("Software"),           TEXT("Identifies the name of the software package used to create the file, such as \"Microsoft WaveEdit.\""),
TEXT("ISHP"), TEXT("Sharpness"),          TEXT("Identifies the changes in sharpness for the digitizer required to produce the file (the format depends on the hardware used)."),
TEXT("ISRC"), TEXT("Source"),             TEXT("Identifies the name of the person or organization who supplied the original subject of the file. For example, \"Trey Research.\""),
TEXT("ISRF"), TEXT("Source Form"),        TEXT("Identifies the original form of the material that was digitized, such as \"slide,\" \"paper,\" \"map,\" and so forth. This is not necessarily the same as IMED."),
TEXT("ITCH"), TEXT("Technician"),         TEXT("Identifies the technician who digitized the subject file. For example, \"Smith, John.\""),

NULL, NULL, NULL

};

void NEAR PASCAL riffInsertINFO(LPINFOCHUNK lpInfo, const PINFODATA pInfo)
{
    PINFODATA pI;

    if(!lpInfo)
        return;

    if(!lpInfo->pHead)
    {
        lpInfo->pHead=pInfo;
        return;
    }

    pI=lpInfo->pHead;
    while(pI->pnext)
    {
        pI=pI->pnext;
    }
    // insert at end
    pI->pnext=pInfo;

    return;
}

PINFODATA NEAR PASCAL riffCreateINFO(WORD id, WORD wFlags, DWORD dwInfoOffset, LPCTSTR lpText)
{
    PINFODATA pI;
    pI=(PINFODATA)LocalAlloc(LPTR,sizeof(INFODATA));
    if(!pI)
        return NULL;

    pI->index=id;
    pI->wFlags=wFlags;
    pI->dwINFOOffset=dwInfoOffset;
    pI->lpText=lpText;

    return pI;
}

LRESULT RIFFAPI riffInitINFO(INFOCHUNK FAR * FAR * lplpInfo)
{
    LPINFOCHUNK lpInfo;
    WORD        id;
    PINFODATA   pI;

    lpInfo=(LPINFOCHUNK)GlobalAllocPtr(GHND, sizeof(INFOCHUNK));
    if(!lpInfo)
        return RIFFERR_NOMEM;
    *lplpInfo=lpInfo;

    for (id=0;aINFO[id].pFOURCC;id++)
    {
        pI=riffCreateINFO(id, 0, 0L, NULL);   // create empty INFO
        riffInsertINFO(lpInfo,pI);
    }
    return RIFFERR_NOERROR;
}

LRESULT RIFFAPI riffReadINFO(HMMIO hmmio, const LPMMCKINFO lpck, LPINFOCHUNK lpInfo)
{
    DWORD       dwInfoSize;

    dwInfoSize=lpck->cksize - sizeof(FOURCC);   // take out 'INFO'

    lpInfo->cksize=dwInfoSize;
    lpInfo->lpChunk=(LPTSTR)GlobalAllocPtr(GHND, dwInfoSize);
    if(!lpInfo->lpChunk)
        return RIFFERR_NOMEM;

    if (mmioRead(hmmio, (HPSTR)lpInfo->lpChunk, dwInfoSize) != (LONG)dwInfoSize)
        return RIFFERR_FILEERROR;
    else
        return riffParseINFO(lpInfo);
}

PINFODATA NEAR PASCAL riffFindPIINFO(const LPINFOCHUNK lpInfo, FOURCC fcc)
{
    PINFODATA pI;

    pI=lpInfo->pHead;
    while(pI)
    {
        if(mmioStringToFOURCC(aINFO[pI->index].pFOURCC,0)==fcc)
            return(pI);
        pI=pI->pnext;
    }
    return NULL;
}

void NEAR PASCAL riffModifyINFO(const LPINFOCHUNK lpInfo, PINFODATA pI, WORD wFlags, DWORD dw, LPCTSTR lpText)
{
    if(!pI)
        return;

    pI->wFlags=wFlags;
    if(!(wFlags&INFOCHUNK_MODIFIED))
        pI->dwINFOOffset=dw;

    if(pI->lpText)
    {
        if(lpText)
        {
            if(!lstrcmp(lpText,pI->lpText))
            {
                // they are the same, don't bother changing...
                GlobalFreePtr(lpText);
            }
            else
            {
                GlobalFreePtr(pI->lpText);
                pI->lpText=lpText;
            }
        }
        else if(wFlags&INFOCHUNK_REVERT)
        {
            GlobalFreePtr(pI->lpText);
            pI->lpText=NULL;
        }
    }
    else if(lpText)
    {
        // if no read data, don't bother to check....
        if(!lpInfo->lpChunk && *lpText)
        {
            pI->lpText=lpText;
        }
        else if(lstrcmp(lpText, (LPTSTR)lpInfo->lpChunk+pI->dwINFOOffset))
        {       // new text...
            if(*lpText)
                // NOT the same, set...
                pI->lpText=lpText;
            else
                // new is blank, do nothing...
                GlobalFreePtr(lpText);
        }
        else
            // the same, don't bother...
            GlobalFreePtr(lpText);
    }
}

WORD NEAR PASCAL riffFindaINFO(FOURCC fcc)
{
    WORD    id;

    for (id=0;aINFO[id].pFOURCC;id++)
    {
        if(mmioStringToFOURCC(aINFO[id].pFOURCC,0)==fcc)
            return id;
    }
    return 0xFFFF;
}

LRESULT NEAR PASCAL riffParseINFO(const LPINFOCHUNK lpInfo)
{
    LPTSTR   lpBuf;
    DWORD   dwCurInfoOffset;
    PINFODATA pI;
    LPCHUNK lpck;

    lpBuf=lpInfo->lpChunk;
    for(dwCurInfoOffset=0;dwCurInfoOffset<lpInfo->cksize;)
    {
        lpck=(LPCHUNK)((LPSTR)(lpBuf+dwCurInfoOffset));
        dwCurInfoOffset+=sizeof(CHUNK);   // dwCurInfoOffset is offset of data
        pI=riffFindPIINFO(lpInfo,lpck->fcc);
        if(!pI)
        {
            int     n;

            // file contains unknown INFO chunk
            n = AppMsgBox(NULL, MB_YESNO | MB_ICONEXCLAMATION | MB_TASKMODAL,
                          TEXT("This wave file contains an unknown item in the INFO chunk: '%4.4s'.  Open anyway?"),
                          (LPCSTR)(lpck));
            if (n == IDNO)
            {
                return RIFFERR_BADFILE;
            }

        }
        else
        {
            // modify entry to show text (data) from file...
            riffModifyINFO(lpInfo, pI, 0, dwCurInfoOffset, NULL);
        }
        dwCurInfoOffset+=lpck->cksize+(lpck->cksize&1);  // skip past data
    }

    return RIFFERR_NOERROR;
}

LRESULT RIFFAPI riffFreeINFO(INFOCHUNK FAR * FAR * lplpInfo)
{
    PINFODATA   pI;
    PINFODATA   pIT;
    LPINFOCHUNK lpInfo;
    LRESULT     lr;

    lr    = RIFFERR_BADPARAM;

    if(!lplpInfo)
        goto riff_FI_Error;

    lpInfo=*lplpInfo;
    if(!lpInfo)
        goto riff_FI_Error;

    if(lpInfo->lpChunk)
        GlobalFreePtr(lpInfo->lpChunk);

    pI=lpInfo->pHead;

    while(pI)
    {
        pIT=pI;
        pI=pI->pnext;
        LocalFree((HANDLE)pIT);
    }

    //

    GlobalFreePtr(lpInfo);
    *lplpInfo=NULL;
    return RIFFERR_NOERROR;

riff_FI_Error:
    return lr;
}

TCHAR   szBuf[255];

static BOOL NEAR PASCAL riffSetupEditBoxINFO(HWND hdlg, const LPINFOCHUNK lpInfo, WORD wFlags)
{
    static PTSTR szFormat = TEXT("%-4s%c %-25s");
    PINFODATA   pI;
    WORD        iSel;
    HWND        hLB;

    hLB=GetDlgItem(hdlg, IDD_INFOLIST);
    if(wFlags&INFOCHUNK_MODIFIED)
    {
        iSel = ComboBox_GetCurSel(hLB);

    }
    else
        iSel = 0;

    ComboBox_ResetContent(hLB);

    pI=lpInfo->pHead;

    while(pI)
    {
        wsprintf(szBuf,szFormat,
            (LPCSTR)aINFO[pI->index].pFOURCC,
            (pI->dwINFOOffset || ( (pI->lpText) && (pI->lpText[0]) ) ) ?
                        '*' : ' ',
            (LPCSTR)aINFO[pI->index].pShort
            );

        ComboBox_AddString(hLB, szBuf);
        pI=pI->pnext;
    }
    ComboBox_SetCurSel(hLB, iSel);

    if(!(wFlags&INFOCHUNK_MODIFIED))
    {
        // FIRST time only
        pI=lpInfo->pHead;
        if(pI)
            if(pI->lpText)
                // Modified text
                SetDlgItemText(hdlg, IDD_INFOTEXT, (LPCTSTR)pI->lpText);
            else if(pI->dwINFOOffset)
                // default text
                SetDlgItemText(hdlg, IDD_INFOTEXT, (LPCTSTR)(lpInfo->lpChunk+pI->dwINFOOffset));
            else
                // no text
                SetDlgItemText(hdlg, IDD_INFOTEXT, (LPCTSTR)TEXT(""));
        SetDlgItemText(hdlg, IDD_INFOINFO, (LPCTSTR)aINFO[0].pLong);
    }
    return TRUE;
}

static BOOL Cls_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
    LPINFOCHUNK lpInfo;
    HFONT       hFont;
    HWND        hLB;

    lpInfo = (LPINFOCHUNK)lParam;
    if(!lpInfo)
        return FALSE;

    SetWindowLong(hwnd, DWL_USER, (LONG)lpInfo);

    hFont = GetStockFont(SYSTEM_FIXED_FONT);

    hLB=GetDlgItem(hwnd, IDD_INFOLIST);
    SetWindowFont(hLB, hFont, FALSE);

    riffSetupEditBoxINFO(hwnd, lpInfo, 0);

    return TRUE;
}

static void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
    LPINFOCHUNK lpInfo;
    PINFODATA   pI;
    WORD        iSel;
    int         i;
    LPTSTR       lpstr;

    lpInfo=(LPINFOCHUNK)GetWindowLong(hwnd, DWL_USER);

    switch(id)
    {
        case IDOK:
        case IDCANCEL:
            EndDialog(hwnd, (id == IDOK));
            break;
        case IDD_INFOLIST:
            switch(codeNotify)
            {
                case CBN_SELCHANGE:

                    iSel = ComboBox_GetCurSel(GetDlgItem(hwnd, id));
                    SetDlgItemText(hwnd, IDD_INFOINFO, (LPCTSTR)aINFO[iSel].pLong);

                    pI=lpInfo->pHead;
                    while(pI)
                    {
                        if(pI->index==iSel)
                            break;
                        pI=pI->pnext;
                    }
                    if(pI)
                    {
                        if(pI->lpText)
                            // Modified text
                            SetDlgItemText(hwnd, IDD_INFOTEXT, (LPCTSTR)pI->lpText);
                        else if(pI->dwINFOOffset)
                            // default text
                            SetDlgItemText(hwnd, IDD_INFOTEXT, (LPCTSTR)(lpInfo->lpChunk+pI->dwINFOOffset));
                        else
                            // no text
                            SetDlgItemText(hwnd, IDD_INFOTEXT, (LPCTSTR)TEXT(""));
                    }
                        else
                            SetDlgItemText(hwnd, IDD_INFOINFO, (LPCTSTR)TEXT("Can't FIND iSel"));
                    break;
            }

        case IDD_INFOTEXT:
            switch(codeNotify)
            {
                case EN_KILLFOCUS:
                    // get text out and give to current id
                    iSel=(WORD)SendDlgItemMessage(hwnd,IDD_INFOLIST,CB_GETCURSEL,0,0L);
                    pI=lpInfo->pHead;
                    while(pI)
                    {
                        if(pI->index==iSel)
                            break;
                        pI=pI->pnext;
                    }
                    if(pI)
                    {
                        i=GetDlgItemText(hwnd, IDD_INFOTEXT, szBuf,sizeof(szBuf));
                        lpstr=(LPTSTR)GlobalAllocPtr(GHND,(DWORD)i+1);
                        if(!lpstr)
                            break;

                        lstrcpy(lpstr,szBuf);

                        riffModifyINFO(lpInfo, pI, INFOCHUNK_MODIFIED, 0, lpstr);

                        riffSetupEditBoxINFO(hwnd, lpInfo, INFOCHUNK_MODIFIED);
                    }
                    else
                        SetDlgItemText(hwnd, IDD_INFOINFO, (LPCTSTR)TEXT("Can't FIND iSel"));
                    break;

            }
            break;
        case IDD_INFOINFO:
            break;
    }

}

BOOL FNGLOBAL DlgProcINFOEdit(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
            return (BOOL)(UINT)(DWORD)(LRESULT)HANDLE_WM_INITDIALOG(hdlg, wParam, lParam, Cls_OnInitDialog);

        case WM_COMMAND:
            HANDLE_WM_COMMAND(hdlg, wParam, lParam, Cls_OnCommand);
            break;
    }

    return FALSE;
}

LRESULT RIFFAPI riffEditINFO(HWND hwnd, LPINFOCHUNK lpInfo, HINSTANCE hInst)
{
    LRESULT     lr;
    DLGPROC     lpfn;
#ifdef DEBUG
    int         i;
#endif

    lr    = RIFFERR_BADPARAM;

    if(!lpInfo)
        goto riff_EI_Error;

    if (lpfn = (DLGPROC)MakeProcInstance((FARPROC)DlgProcINFOEdit, hInst))
    {
#ifdef DEBUG
        i=
#endif
        DialogBoxParam(hInst, DLG_INFOEDIT, hwnd, lpfn, (LPARAM)(LPVOID)lpInfo);
        FreeProcInstance((FARPROC)lpfn);
        lr=RIFFERR_NOERROR;
#ifdef DEBUG
        if(i==-1)
        {
            MessageBox(hwnd, TEXT("INFO Edit Error: DLG_INFOEDIT not found.  Check .RC file."), TEXT("RIFF SUP module"), MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
            lr=RIFFERR_ERROR;
        }
#endif

    }
#ifdef DEBUG
    else
    {
        MessageBox(hwnd, TEXT("INFO Edit Error: Can't MakeProcInstace()"), TEXT("RIFF SUP module"), MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
        lr=RIFFERR_ERROR;
    }
#endif

riff_EI_Error:
    return lr;
}

LRESULT RIFFAPI riffWriteINFO(HMMIO hmmioDst, LPINFOCHUNK lpInfo)
{
    LRESULT     lr;
    MMCKINFO    ck;
    MMCKINFO    ckINFO;
    PINFODATA   pI;
    LPTSTR      lpstr;
    BOOL        fList=FALSE;

    lr    = RIFFERR_BADPARAM;

    if(!hmmioDst || !lpInfo)
        goto riff_SI_Error;

    lr=RIFFERR_FILEERROR;

    ckINFO.ckid   = mmioFOURCC('L', 'I', 'S', 'T');
    ckINFO.cksize = 0;  // mmio fill fill it in later
    ckINFO.fccType= mmioFOURCC('I', 'N', 'F', 'O');

    pI=lpInfo->pHead;

    while(pI)
    {
        if(pI->lpText)
            // Modified text
            lpstr=(LPTSTR)pI->lpText;
        else if(pI->dwINFOOffset)
            // default text
            lpstr=(lpInfo->lpChunk+pI->dwINFOOffset);
        else
            // no text
            lpstr=NULL;
        if(lpstr)
        {
            if(!fList)
            {
                // only create if needed...
                if (mmioCreateChunk(hmmioDst, &ckINFO, MMIO_CREATELIST))
                    goto riff_SI_Error;
                fList=TRUE;
            }

            ck.ckid=mmioStringToFOURCC(aINFO[pI->index].pFOURCC,0);
            ck.cksize=lstrlen(lpstr)+1;
            ck.fccType=0;
            if (mmioCreateChunk(hmmioDst, &ck, 0))
                goto riff_SI_Error;

            if (mmioWrite(hmmioDst, (LPBYTE)lpstr, ck.cksize) != (LONG)(ck.cksize))
                goto riff_SI_Error;

            if (mmioAscend(hmmioDst, &ck, 0))
                goto riff_SI_Error;

        }
        pI=pI->pnext;
    }

    if(fList)
    {
        if (mmioAscend(hmmioDst, &ckINFO, 0))
            goto riff_SI_Error;
    }

    return RIFFERR_NOERROR;

riff_SI_Error:
    return lr;

}

///////////////////////////////////////////////////////////////////////////////

LRESULT RIFFAPI riffReadDISP(HMMIO hmmio, LPMMCKINFO lpck, DISP FAR * FAR * lpnpDisp)
{
    LRESULT     lr;
    lr    = RIFFERR_ERROR;

    return lr;
}

LRESULT RIFFAPI riffFreeDISP(DISP FAR * FAR * lpnpDisp)
{
    LRESULT     lr;
    lr    = RIFFERR_ERROR;

    return lr;
}

LRESULT RIFFAPI riffWriteDISP(HMMIO hmmio, DISP FAR * FAR * lpnpDisp)
{
    LRESULT     lr;
    lr    = RIFFERR_ERROR;

    return lr;
}

//==========================================================================;
//==========================================================================;
//==========================================================================;
//==========================================================================;

BOOL        gfCancelConvert;

#define WM_CONVERT_BEGIN        (WM_USER + 100)
#define WM_CONVERT_END          (WM_USER + 101)

#define BeginConvert(hwnd, paacd)   PostMessage(hwnd, WM_CONVERT_BEGIN, 0, (LPARAM)(UINT)paacd)
#define EndConvert(hwnd, f, paacd)  PostMessage(hwnd, WM_CONVERT_END, (WPARAM)f, (LPARAM)(UINT)paacd)

//--------------------------------------------------------------------------;
//
//  void AppDlgYield
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//  Return (void):
//
//--------------------------------------------------------------------------;

void FNLOCAL AppDlgYield
(
    HWND            hdlg
)
{
    MSG     msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if ((hdlg == NULL) || !IsDialogMessage(hdlg, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
} // AppDlgYield()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppConvertEnd
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppConvertEnd
(
    HWND                hdlg,
    PAACONVERTDESC      paacd
)
{
    MMRESULT            mmr;
    LPACMSTREAMHEADER   pash;

    //
    //
    //
    //
    if (NULL != paacd->hmmioSrc)
    {
        mmioClose(paacd->hmmioSrc, 0);
        paacd->hmmioSrc = NULL;
    }

    if (NULL != paacd->hmmioDst)
    {
        mmioAscend(paacd->hmmioDst, &paacd->ckDst, 0);
        mmioAscend(paacd->hmmioDst, &paacd->ckDstRIFF, 0);

        mmioClose(paacd->hmmioDst, 0);
        paacd->hmmioDst = NULL;
    }

    //
    //
    //
    //
    if (NULL != paacd->has)
    {
        pash = &paacd->ash;

        if (ACMSTREAMHEADER_STATUSF_PREPARED & pash->fdwStatus)
        {
            pash->cbSrcLength = paacd->cbSrcReadSize;
            pash->cbDstLength = paacd->cbDstBufSize;

            mmr = acmStreamUnprepareHeader(paacd->has, &paacd->ash, 0L);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                          TEXT("acmStreamUnprepareHeader() failed with error = %u!"), mmr);
            }
        }

        //
        //
        //
        acmStreamClose(paacd->has, 0L);
        paacd->has = NULL;

        if (NULL != paacd->had)
        {
            acmDriverClose(paacd->had, 0L);
            paacd->had = NULL;
        }
    }

    //
    //
    //
    //
    if (NULL != paacd->pbSrc)
    {
        GlobalFreePtr(paacd->pbSrc);
        paacd->pbSrc = NULL;
    }

    if (NULL != paacd->pbDst)
    {
        GlobalFreePtr(paacd->pbDst);
        paacd->pbDst = NULL;
    }

    return (TRUE);
} // AcmAppConvertEnd()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppConvertBegin
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppConvertBegin
(
    HWND                    hdlg,
    PAACONVERTDESC          paacd
)
{
    TCHAR               ach[40];
    MMRESULT            mmr;
    MMCKINFO            ckSrcRIFF;
    MMCKINFO            ck;
    DWORD               dw;
    LPACMSTREAMHEADER   pash;
    LPWAVEFILTER        pwfltr;

    //
    //
    //
    if (NULL != paacd->hadid)
    {
        mmr = acmDriverOpen(&paacd->had, paacd->hadid, 0L);
        if (MMSYSERR_NOERROR != mmr)
        {
            AcmAppGetErrorString(mmr, ach);
            AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                        TEXT("The selected driver (hadid=%.04Xh) cannot be opened. %s (%u)"),
                        paacd->hadid, (LPSTR)ach, mmr);
            return (FALSE);
        }
    }

    //
    //
    //
    //
    pwfltr = paacd->fApplyFilter ? paacd->pwfltr : (LPWAVEFILTER)NULL;

    mmr = acmStreamOpen(&paacd->has,
                        paacd->had,
                        paacd->pwfxSrc,
                        paacd->pwfxDst,
                        pwfltr,
                        0L,
                        0L,
                        paacd->fdwOpen);

    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("acmStreamOpen() failed with error = %u!"), mmr);

        return (FALSE);
    }

    //
    //
    //
    mmr = acmStreamSize(paacd->has,
                        paacd->cbSrcReadSize,
                        &paacd->cbDstBufSize,
                        ACM_STREAMSIZEF_SOURCE);

    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("acmStreamSize() failed with error = %u!"), mmr);

        return (FALSE);
    }

    //
    //  first try to open the file, etc.. open the given file for reading
    //  using buffered I/O
    //
    paacd->hmmioSrc = mmioOpen(paacd->szFilePathSrc,
                               NULL,
                               MMIO_READ | MMIO_DENYWRITE | MMIO_ALLOCBUF);
    if (NULL == paacd->hmmioSrc)
        goto aacb_Error;

    //
    //
    //
    paacd->hmmioDst = mmioOpen(paacd->szFilePathDst,
                               NULL,
                               MMIO_CREATE | MMIO_WRITE | MMIO_EXCLUSIVE | MMIO_ALLOCBUF);
    if (NULL == paacd->hmmioDst)
        goto aacb_Error;

    //
    //
    //
    //
    pash = &paacd->ash;
    pash->fdwStatus = 0L;

    //
    //  allocate the src and dst buffers for reading/converting data
    //
    paacd->pbSrc = (HPSTR)GlobalAllocPtr(GHND, paacd->cbSrcReadSize);
    if (NULL == paacd->pbSrc)
        goto aacb_Error;

    paacd->pbDst = (HPSTR)GlobalAllocPtr(GHND, paacd->cbDstBufSize);
    if (NULL == paacd->pbDst)
        goto aacb_Error;

    //
    //
    //
    //
    pash->cbStruct          = sizeof(*pash);
    pash->fdwStatus         = 0L;
    pash->dwUser            = 0L;
    pash->pbSrc             = paacd->pbSrc;
    pash->cbSrcLength       = paacd->cbSrcReadSize;
    pash->cbSrcLengthUsed   = 0L;
    pash->dwSrcUser         = paacd->cbSrcReadSize;
    pash->pbDst             = paacd->pbDst;
    pash->cbDstLength       = paacd->cbDstBufSize;
    pash->cbDstLengthUsed   = 0L;
    pash->dwDstUser         = paacd->cbDstBufSize;

    mmr = acmStreamPrepareHeader(paacd->has, pash, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                    TEXT("acmStreamPrepareHeader() failed with error = %u!"), mmr);

        goto aacb_Error;
    }

    //
    //  create the RIFF chunk of form type 'WAVE'
    //
    //
    paacd->ckDstRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    paacd->ckDstRIFF.cksize  = 0L;
    if (mmioCreateChunk(paacd->hmmioDst, &paacd->ckDstRIFF, MMIO_CREATERIFF))
        goto aacb_Error;

    //
    //  locate a 'WAVE' form type in a 'RIFF' thing...
    //
    ckSrcRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    if (mmioDescend(paacd->hmmioSrc, (LPMMCKINFO)&ckSrcRIFF, NULL, MMIO_FINDRIFF))
        goto aacb_Error;

    //
    //  we found a WAVE chunk--now go through and get all subchunks that
    //  we know how to deal with...
    //
    while (mmioDescend(paacd->hmmioSrc, &ck, &ckSrcRIFF, 0) == 0)
    {
        //
        //  quickly check for corrupt RIFF file--don't ascend past end!
        //
        if ((ck.dwDataOffset + ck.cksize) > (ckSrcRIFF.dwDataOffset + ckSrcRIFF.cksize))
            goto aacb_Error;

        switch (ck.ckid)
        {
            //
            //  explicitly skip these...
            //
            //
            //
            case mmioFOURCC('f', 'm', 't', ' '):
                break;

            case mmioFOURCC('d', 'a', 't', 'a'):
                break;

            case mmioFOURCC('f', 'a', 'c', 't'):
                break;

            case mmioFOURCC('J', 'U', 'N', 'K'):
                break;

            case mmioFOURCC('P', 'A', 'D', ' '):
                break;

            case mmioFOURCC('c', 'u', 'e', ' '):
                break;

            //
            //  copy chunks that are OK to copy
            //
            //
            //
            case mmioFOURCC('p', 'l', 's', 't'):
                // although without the 'cue' chunk, it doesn't make much sense
                riffCopyChunk(paacd->hmmioSrc, paacd->hmmioDst, &ck);
                break;

            case mmioFOURCC('D', 'I', 'S', 'P'):
                riffCopyChunk(paacd->hmmioSrc, paacd->hmmioDst, &ck);
                break;

            //
            //  don't copy unknown chunks
            //
            //
            //
            default:
                break;
        }

        //
        //  step up to prepare for next chunk..
        //
        mmioAscend(paacd->hmmioSrc, &ck, 0);
    }

#if 0
    //
    //  now write out possibly editted chunks...
    //
    if (riffWriteINFO(paacd->hmmioDst, (glpwio->pInfo)))
    {
        goto aacb_Error;
    }
#endif

    //
    // go back to beginning of data portion of WAVE chunk
    //
    if (-1 == mmioSeek(paacd->hmmioSrc, ckSrcRIFF.dwDataOffset + sizeof(FOURCC), SEEK_SET))
        goto aacb_Error;

    ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
    mmioDescend(paacd->hmmioSrc, &ck, &ckSrcRIFF, MMIO_FINDCHUNK);

    //
    //  now create the destination fmt, fact, and data chunks _in that order_
    //
    //
    //
    //  hmmio is now descended into the 'RIFF' chunk--create the format chunk
    //  and write the format header into it
    //
    dw = SIZEOF_WAVEFORMATEX(paacd->pwfxDst);

    paacd->ckDst.ckid   = mmioFOURCC('f', 'm', 't', ' ');
    paacd->ckDst.cksize = dw;
    if (mmioCreateChunk(paacd->hmmioDst, &paacd->ckDst, 0))
        goto aacb_Error;

    if (mmioWrite(paacd->hmmioDst, (HPSTR)paacd->pwfxDst, dw) != (LONG)dw)
        goto aacb_Error;

    if (mmioAscend(paacd->hmmioDst, &paacd->ckDst, 0) != 0)
        goto aacb_Error;

    //
    //  create the 'fact' chunk (not necessary for PCM--but is nice to have)
    //  since we are not writing any data to this file (yet), we set the
    //  samples contained in the file to 0..
    //
    paacd->ckDst.ckid   = mmioFOURCC('f', 'a', 'c', 't');
    paacd->ckDst.cksize = 0L;
    if (mmioCreateChunk(paacd->hmmioDst, &paacd->ckDst, 0))
        goto aacb_Error;

    if (mmioWrite(paacd->hmmioDst, (HPSTR)&paacd->dwSrcSamples, sizeof(DWORD)) != sizeof(DWORD))
        goto aacb_Error;

    if (mmioAscend(paacd->hmmioDst, &paacd->ckDst, 0) != 0)
        goto aacb_Error;

    //
    //  create the data chunk AND STAY DESCENDED... for reasons that will
    //  become apparent later..
    //
    paacd->ckDst.ckid   = mmioFOURCC('d', 'a', 't', 'a');
    paacd->ckDst.cksize = 0L;
    if (mmioCreateChunk(paacd->hmmioDst, &paacd->ckDst, 0))
        goto aacb_Error;

    //
    //  at this point, BOTH the src and dst files are sitting at the very
    //  beginning of their data chunks--so we can READ from the source,
    //  CONVERT the data, then WRITE it to the destination file...
    //
    return (TRUE);

    //
    //
    //
    //
aacb_Error:

    AcmAppConvertEnd(hdlg, paacd);

    return (FALSE);
} // AcmAppConvertBegin()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppConvertConvert
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppConvertConvert
(
    HWND                hdlg,
    PAACONVERTDESC     paacd
)
{
    MMRESULT            mmr;
    TCHAR               ach[40];
    DWORD               dw;
    WORD                w;
    DWORD               dwCurrent;
    WORD                wCurPercent;
    LPACMSTREAMHEADER   pash;
    DWORD               cbRead;
    DWORD               dwTime;

    wCurPercent = (WORD)-1;

    paacd->cTotalConverts    = 0L;
    paacd->dwTimeTotal       = 0L;
    paacd->dwTimeLongest     = 0L;
    if (0 == paacd->cbSrcData)
    {
        paacd->dwTimeShortest    = 0L;
        paacd->dwShortestConvert = 0L;
        paacd->dwLongestConvert  = 0L;
    }
    else
    {
        paacd->dwTimeShortest    = (DWORD)-1L;
        paacd->dwShortestConvert = (DWORD)-1L;

paacd->dwLongestConvert  = (DWORD)-1L;
    }

    pash = &paacd->ash;

    for (dwCurrent = 0; dwCurrent < paacd->cbSrcData; )
    {
        w = (WORD)((dwCurrent * 100) / paacd->cbSrcData);
        if (w != wCurPercent)
        {
            wCurPercent = w;
            wsprintf(ach, TEXT("%u%%"), wCurPercent);

            if (hdlg)
                SetDlgItemText(hdlg, IDD_AACONVERT_TXT_STATUS, ach);
        }

        AppDlgYield(hdlg);

        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        cbRead = min(paacd->cbSrcReadSize, paacd->cbSrcData - dwCurrent);
        dw = mmioRead(paacd->hmmioSrc, paacd->pbSrc, cbRead);
        if (0L == dw)
            break;

        AppDlgYield(hdlg);
        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        pash->cbSrcLength     = dw;
        pash->cbDstLengthUsed = 0L;

        dwTime = timeGetTime();

        mmr = acmStreamConvert(paacd->has,
                               &paacd->ash,
                               ACM_STREAMCONVERTF_BLOCKALIGN);

        if (MMSYSERR_NOERROR != mmr)
        {
            AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                      TEXT("acmStreamConvert() failed with error = %u!"), mmr);
            goto aacc_Error;
        }

        while (0 == (ACMSTREAMHEADER_STATUSF_DONE & ((AACONVERTDESC volatile *)paacd)->ash.fdwStatus))
            ;

        dwTime = timeGetTime() - dwTime;

        paacd->dwTimeTotal += dwTime;

        if (dwTime < paacd->dwTimeShortest)
        {
            paacd->dwTimeShortest    = dwTime;
            paacd->dwShortestConvert = paacd->cTotalConverts;
        }

        if (dwTime > paacd->dwTimeLongest)
        {
            paacd->dwTimeLongest     = dwTime;
            paacd->dwLongestConvert  = paacd->cTotalConverts;
        }

        paacd->cTotalConverts++;

        AppDlgYield(hdlg);
        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        dw = (cbRead - pash->cbSrcLengthUsed);
        if (0L != dw)
        {
            mmioSeek(paacd->hmmioSrc, -(LONG)dw, SEEK_CUR);
        }

        dwCurrent += pash->cbSrcLengthUsed;

        //
        //
        //
        dw = pash->cbDstLengthUsed;
        if (0L == dw)
            break;

        if (mmioWrite(paacd->hmmioDst, paacd->pbDst, dw) != (LONG)dw)
            goto aacc_Error;
    }

    //
    //
    //
    //
    //
    //
    wCurPercent = (WORD)-1;

    for (;paacd->cbSrcData;)
    {
        w = (WORD)((dwCurrent * 100) / paacd->cbSrcData);
        if (w != wCurPercent)
        {
            wCurPercent = w;
            wsprintf(ach, TEXT("Cleanup Pass -- %u%%"), wCurPercent);

            if (hdlg)
                SetDlgItemText(hdlg, IDD_AACONVERT_TXT_STATUS, ach);
        }

        AppDlgYield(hdlg);
        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        dw = 0L;
        cbRead = min(paacd->cbSrcReadSize, paacd->cbSrcData - dwCurrent);
        if (0L != cbRead)
        {
            dw = mmioRead(paacd->hmmioSrc, paacd->pbSrc, cbRead);
            if (0L == dw)
                break;
        }

        AppDlgYield(hdlg);
        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        pash->cbSrcLength     = dw;
        pash->cbDstLengthUsed = 0L;

        dwTime = timeGetTime();

        mmr = acmStreamConvert(paacd->has,
                               &paacd->ash,
                               ACM_STREAMCONVERTF_BLOCKALIGN |
                               ACM_STREAMCONVERTF_END);

        if (MMSYSERR_NOERROR != mmr)
        {
            AppMsgBox(hdlg, MB_OK | MB_ICONEXCLAMATION,
                      TEXT("acmStreamConvert() failed with error = %u!"), mmr);
            goto aacc_Error;
        }

        while (0 == (ACMSTREAMHEADER_STATUSF_DONE & ((AACONVERTDESC volatile *)paacd)->ash.fdwStatus))
            ;

        dwTime = timeGetTime() - dwTime;

        paacd->dwTimeTotal += dwTime;

        if (dwTime < paacd->dwTimeShortest)
        {
            paacd->dwTimeShortest    = dwTime;
            paacd->dwShortestConvert = paacd->cTotalConverts;
        }

        if (dwTime > paacd->dwTimeLongest)
        {
            paacd->dwTimeLongest     = dwTime;
            paacd->dwLongestConvert  = paacd->cTotalConverts;
        }

        paacd->cTotalConverts++;

        AppDlgYield(hdlg);
        if (gfCancelConvert)
            goto aacc_Error;

        //
        //
        //
        dw = pash->cbDstLengthUsed;
        if (0L == dw)
        {
            pash->cbDstLengthUsed = 0L;

            //
            //  BUGBUG BOBSTER
            //  What if the last conversion ate up some of the source bytes?
            //  It's possible - the codec could cache some of the data
            //  without actually converting it, right?  The pbSrc pointer
            //  might have to be incremented, and cbSrcLength might have to
            //  to be decreased by cbSrcLengthUsed.  This probably wouldn't
            //  happen with most of our codecs though...?
            //
            mmr = acmStreamConvert(paacd->has,
                                   &paacd->ash,
                                   ACM_STREAMCONVERTF_END);

            if (MMSYSERR_NOERROR == mmr)
            {
                while (0 == (ACMSTREAMHEADER_STATUSF_DONE & ((AACONVERTDESC volatile *)paacd)->ash.fdwStatus))
                    ;
            }

            dw = pash->cbDstLengthUsed;
            if (0L == dw)
                break;
        }

        if (mmioWrite(paacd->hmmioDst, paacd->pbDst, dw) != (LONG)dw)
            goto aacc_Error;

        //
        //
        //
        dw = (cbRead - pash->cbSrcLengthUsed);
        if (0L != dw)
        {
            mmioSeek(paacd->hmmioSrc, -(LONG)dw, SEEK_CUR);
        }

        dwCurrent += pash->cbSrcLengthUsed;

        if (0L == pash->cbDstLengthUsed)
            break;
    }

    if (hdlg)
        EndConvert(hdlg, !gfCancelConvert, paacd);

    return (!gfCancelConvert);

aacc_Error:

    if (hdlg)
        EndConvert(hdlg, FALSE, paacd);
    return (FALSE);
} // AcmAppConvertConvert()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppConvertDlgProc
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppConvertDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PAACONVERTDESC      paacd;
    UINT                uId;

    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            paacd = (PAACONVERTDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            SetWindowFont(GetDlgItem(hwnd, IDD_AACONVERT_TXT_INFILEPATH), ghfontApp, FALSE);
            SetWindowFont(GetDlgItem(hwnd, IDD_AACONVERT_TXT_OUTFILEPATH), ghfontApp, FALSE);
            SetWindowFont(GetDlgItem(hwnd, IDD_AACONVERT_TXT_STATUS), ghfontApp, FALSE);

            SetDlgItemText(hwnd, IDD_AACONVERT_TXT_INFILEPATH, paacd->szFilePathSrc);
            SetDlgItemText(hwnd, IDD_AACONVERT_TXT_OUTFILEPATH, paacd->szFilePathDst);

            BeginConvert(hwnd, paacd);
            return (TRUE);

        case WM_CONVERT_BEGIN:
            gfCancelConvert = FALSE;
            if (AcmAppConvertBegin(hwnd, paacd))
            {
                AcmAppConvertConvert(hwnd, paacd);
            }
            else
            {
                EndConvert(hwnd, FALSE, paacd);
            }
            break;

        case WM_CONVERT_END:
            AcmAppConvertEnd(hwnd, paacd);
            EndDialog(hwnd, !gfCancelConvert);
            break;

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            if (IDCANCEL == uId)
            {
                gfCancelConvert = TRUE;
            }
            break;
    }

    return (FALSE);
} // AcmAppConvertDlgProc()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppMultiThreadConvert
//
//  Description:
//
//
//  Arguments:
//      HWND hdlg:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

LONG AcmAppMultiThreadConvert
(
    PAACONVERTDESC      paacd
)
{
    if (AcmAppConvertBegin(NULL, paacd))
    {
        AcmAppConvertConvert(NULL, paacd);
    }

    AcmAppConvertEnd(NULL, paacd);

    AcmAppOpenInstance(NULL, paacd->szFilePathDst, FALSE);

    //
    //  clean up...
    //
    if (NULL != paacd->pwfxDst)
    {
        GlobalFreePtr(paacd->pwfxDst);
        paacd->pwfxDst = NULL;
    }

    if (NULL != paacd->pwfltr)
    {
        GlobalFreePtr(paacd->pwfltr);
        paacd->pwfltr = NULL;
    }

    paacd->pwfxSrc = NULL;

    LocalFree((HLOCAL)paacd);

    return (TRUE);

} // AcmAppMultiThreadConvert()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppFileConvert
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppFileConvert
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    BOOL                f;
    DWORD               nAvgBytesPerSec;
    DWORD               nBlockAlign;
    DWORD               dwTimeAverage;
    PAACONVERTDESC      paacd;

    paacd = (PAACONVERTDESC)LocalAlloc(LPTR, sizeof(*paacd));
    if (NULL == paacd)
    {
        return (FALSE);
    }

    //
    //
    //
    paacd->hmmioSrc      = NULL;
    paacd->hmmioDst      = NULL;

    //
    //  default to 1 second per convert buffer..
    //
    paacd->uBufferTimePerConvert = 1000;

    paacd->dwSrcSamples  = paafd->dwDataSamples;

    //
    //  compute source bytes to read (round down to nearest block for
    //  one second of data)
    //
    nAvgBytesPerSec     = paafd->pwfx->nAvgBytesPerSec;
    nBlockAlign         = paafd->pwfx->nBlockAlign;
    paacd->cbSrcReadSize = nAvgBytesPerSec - (nAvgBytesPerSec % nBlockAlign);

    paacd->cbDstBufSize  = 0L;
    paacd->fdwOpen       = 0L;

    lstrcpy(paacd->szFilePathSrc, paafd->szFilePath);
    paacd->pwfxSrc       = paafd->pwfx;
    paacd->pbSrc         = NULL;

    paacd->cbSrcData     = paafd->dwDataBytes;

    lstrcpy(paacd->szFilePathDst, gszLastSaveFile);
    paacd->pwfxDst       = NULL;
    paacd->pbDst         = NULL;

    paacd->fApplyFilter  = FALSE;
    paacd->pwfltr        = NULL;

    paacd->cTotalConverts     = 0L;
    paacd->dwTimeTotal        = 0L;
    paacd->dwTimeShortest     = (DWORD)-1L;
    paacd->dwShortestConvert  = (DWORD)-1L;
    paacd->dwTimeLongest      = 0L;
    paacd->dwLongestConvert   = (DWORD)-1L;

    //
    //
    //
    f = DialogBoxParam(ghinst,
                       DLG_AACHOOSER,
                       hwnd,
                       AcmAppDlgProcChooser,
                       (LPARAM)(UINT)paacd);
    if (f)
    {
        lstrcpy(gszLastSaveFile, paacd->szFilePathDst);

        //
        //
        //
        f = DialogBoxParam(ghinst,
                            DLG_AACONVERT,
                            hwnd,
                            AcmAppConvertDlgProc,
                            (LPARAM)(UINT)paacd);
        if (!f)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                        TEXT("Conversion aborted--destination file is corrupt!"));
        }

        if (paacd->cTotalConverts > 1)
        {
            dwTimeAverage  = paacd->dwTimeTotal;
            dwTimeAverage -= paacd->dwTimeShortest;

            dwTimeAverage /= (paacd->cTotalConverts - 1);
        }
        else
        {
            dwTimeAverage = paacd->dwTimeTotal;
        }

        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                    TEXT("Conversion Statistics:\n\nTotal Time:\t%lu ms\nTotal Converts:\t%lu\nShortest Time:\t%lu ms (on %lu)\nLongest Time:\t%lu ms (on %lu)\n\nAverage Time:\t%lu ms"),
                    paacd->dwTimeTotal,
                    paacd->cTotalConverts,
                    paacd->dwTimeShortest,
                    paacd->dwShortestConvert,
                    paacd->dwTimeLongest,
                    paacd->dwLongestConvert,
                    dwTimeAverage);

        if (f)
        {
            AcmAppOpenInstance(hwnd, paacd->szFilePathDst, FALSE);
        }
    }

    //
    //
    //
    if (NULL != paacd->pwfxDst)
    {
        GlobalFreePtr(paacd->pwfxDst);
        paacd->pwfxDst = NULL;
    }

    if (NULL != paacd->pwfltr)
    {
        GlobalFreePtr(paacd->pwfltr);
        paacd->pwfltr = NULL;
    }

    paacd->pwfxSrc = NULL;

    LocalFree((HLOCAL)paacd);

    return (f);
} // AcmAppFileConvert()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppMultiThreadedCallback
//
//  Description:
//
//
//  Arguments:
//      HACMDRIVERID hadid:
//
//      DWORD dwInstance:
//
//      DWORD fdwSupport:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppMultiThreadedCallback
(
    HACMDRIVERID        hadid,
    LPACMFORMATDETAILS  pafd,
    DWORD               dwInstance,
    DWORD               fdwSupport
)
{
    DWORD               nAvgBytesPerSec;
    DWORD               nBlockAlign;
    PAACONVERTDESC      paacd;
    PACMAPPFILEDESC     paafd;
#ifdef WIN32
    int                 i = -1;
    ACMFORMATTAGDETAILS aftd;
    MMRESULT            mmr;
    HANDLE              hThrd;
    LONG                lThreadId;
#endif

    //
    // I have sent myself (PACMAPPFILEDESC)paafd for the source
    // file.  I need to copy the relevent information from this structure
    // into a (PAACONVERTDESC)paacd and send it off to the thread created
    // below.
    //
    paafd = (PACMAPPFILEDESC)dwInstance;

    paacd = (PAACONVERTDESC)LocalAlloc(LPTR, sizeof(*paacd));
    if (NULL == paacd)
    {
        return (FALSE);
    }

    //
    //
    //
    paacd->hmmioSrc      = NULL;
    paacd->hmmioDst      = NULL;

    //
    //  default to 1 second per convert buffer..
    //
    paacd->uBufferTimePerConvert = 1000;

    paacd->dwSrcSamples  = paafd->dwDataSamples;

    //
    //  compute source bytes to read (round down to nearest block for
    //  one second of data)
    //
    nAvgBytesPerSec     = paafd->pwfx->nAvgBytesPerSec;
    nBlockAlign         = paafd->pwfx->nBlockAlign;
    paacd->cbSrcReadSize = nAvgBytesPerSec - (nAvgBytesPerSec % nBlockAlign);

    paacd->cbDstBufSize  = 0L;
    paacd->fdwOpen       = ACM_STREAMOPENF_NONREALTIME;

    lstrcpy(paacd->szFilePathSrc, paafd->szFilePath);
    paacd->pwfxSrc       = paafd->pwfx;
    paacd->pbSrc         = NULL;

    paacd->cbSrcData     = paafd->dwDataBytes;

    paacd->pwfxDst = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, SIZEOF_WAVEFORMATEX(pafd->pwfx));
    if (NULL == paacd->pwfxDst)
    {
        return (FALSE);
    }
    _fmemcpy(paacd->pwfxDst,pafd->pwfx,SIZEOF_WAVEFORMATEX(pafd->pwfx));
    paacd->pbDst         = NULL;

    paacd->fApplyFilter  = FALSE;
    paacd->pwfltr        = NULL;

    paacd->cTotalConverts     = 0L;
    paacd->dwTimeTotal        = 0L;
    paacd->dwTimeShortest     = (DWORD)-1L;
    paacd->dwShortestConvert  = (DWORD)-1L;
    paacd->dwTimeLongest      = 0L;
    paacd->dwLongestConvert   = (DWORD)-1L;

    //
    //  Under Win32 I can spawn a thread for each conversion.
    //  Under Win16 I have to wait for each of the conversions to complete.
    //
#ifdef WIN32
    //
    // Remove file extension and add extra info
    //
    lstrcpy(paacd->szFilePathDst, paafd->szFilePath);

    while ((paacd->szFilePathDst[++i] != '.') &&
           (paacd->szFilePathDst[i]   != '('));
    paacd->szFilePathDst[i] = '\0';

    //
    //  initialize all unused members of the ACMFORMATTAGDETAILS
    //  structure to zero
    //
    memset(&aftd, 0, sizeof(aftd));

    //
    //  fill in the required members of the ACMFORMATTAGDETAILS
    //  structure for the ACM_FORMATTAGDETAILSF_FORMATTAG query
    //
    aftd.cbStruct    = sizeof(aftd);
    aftd.dwFormatTag = pafd->pwfx->wFormatTag;

    mmr = acmFormatTagDetails(NULL,
                              &aftd,
                              ACM_FORMATTAGDETAILSF_FORMATTAG);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(NULL, MB_OK | MB_ICONEXCLAMATION,
                    TEXT("Conversion aborted--could not construct filename"));
    }

    //
    //  Construct a long filename based on the format type
    //
    wsprintf(paacd->szFilePathDst, "%s(%s %s).wav",
                (LPCTSTR)paacd->szFilePathDst,
                (LPCTSTR)aftd.szFormatTag,
                (LPCTSTR)pafd->szFormat);

    hThrd = CreateThread(NULL, 0,
                         (LPTHREAD_START_ROUTINE)AcmAppMultiThreadConvert,
                         (LPVOID)paacd,
                         0,
                         (LPDWORD)&lThreadId );

    if (!hThrd)
    {
        AppMsgBox(NULL, MB_OK | MB_ICONEXCLAMATION,
                    TEXT("Conversion aborted--failure doing conversion"));
    }

    //
    //  Since I don't use this handle anywhere, I will close it
    //
    CloseHandle(hThrd);
#else
    //
    //  Don't want to make too much trouble for the Win16 version.
    //
    wsprintf(paacd->szFilePathDst, "%d%c%d%d.wav",
                pafd->pwfx->wFormatTag,
                (2 == pafd->pwfx->nChannels) ? 's' : 'm',
                (UINT)(pafd->pwfx->nAvgBytesPerSec * 8 /
                 pafd->pwfx->nSamplesPerSec /
                 pafd->pwfx->nChannels),
                 pafd->pwfx->nSamplesPerSec/1000);

    AcmAppMultiThreadConvert(paacd);
#endif

    //
    //  return TRUE to continue with enumeration
    //
    return (TRUE);
} // AcmAppMultiThreadedCallback()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppMultiThreadedConvertAll
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppMultiThreadedConvertAll
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MMRESULT            mmr;
    ACMFORMATDETAILS    afd;
    DWORD               cbwfx;

    //
    //  Call acmFormatEnum to enumerated all formats that the source format
    //  can convert to.
    //
    //  Use the wave format for the enumeration
    //

    mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &cbwfx);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppMultiThreadedConvertAll() acmMetrics failed mmr=%u!"), mmr);
        return (FALSE);
    }

    memset(&afd, 0, sizeof(afd));
    afd.cbStruct    = sizeof(afd);
    afd.dwFormatTag = WAVE_FORMAT_UNKNOWN;

    afd.pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, (UINT)cbwfx);
    if (NULL == afd.pwfx)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppMultiThreadedConvertAll() LocalAlloc failed!"));
        return (FALSE);
    }

    _fmemcpy(afd.pwfx, paafd->pwfx, SIZEOF_WAVEFORMATEX(paafd->pwfx));
    afd.cbwfx = cbwfx;

    mmr = acmFormatEnum(NULL, &afd, AcmAppMultiThreadedCallback, (DWORD)(LPVOID)paafd,
            ACM_FORMATENUMF_CONVERT);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppMultiThreadedConvertAll() acmFormatEnum failed mmr=%u!"), mmr);
        return (FALSE);
    }

    GlobalFreePtr(afd.pwfx);

    return TRUE;

} // AcmAppMultiThreadedConvertAll()


AAINIT.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  aainit.c
//
//  Description:
//      This file contains initialization and termination code for the
//      application (as well as some rarely used stuff).
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#ifndef WIN32
#include <shellapi.h>
#endif
#include <mmreg.h>
#include <msacm.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

#include "acmthunk.h"
#include "appport.h"
#include "acmapp.h"

#include "debug.h"

//
//
//
TCHAR   gszSecConfig[]          = TEXT("Configuration");

TCHAR   gszKeyOptions[]         = TEXT("Options");
TCHAR   gszFormatOptions[]      = TEXT("%u");

TCHAR   gszKeyWindow[]          = TEXT("Window");
TCHAR   gszKeyFont[]            = TEXT("Font");
TCHAR   gszKeyInitialDirOpen[]  = TEXT("InitialDirOpen");
TCHAR   gszKeyInitialDirSave[]  = TEXT("InitialDirSave");
TCHAR   gszKeyLastSaveFile[]    = TEXT("LastSaveFile");

//==========================================================================;
//
//  Application helper functions and rarely called stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT AppWinIniChange
//
//  Description:
//      This function is responsible for handling the WM_WININICHANGE
//      message. This message is sent when modifications have been made
//      to WIN.INI (from SystemParametersInfo() or other sources).
//
//      An application should re-enumerate system metrics (GetSystemMetrics)
//      and system color (GetSystemColors) information that it has cached.
//      Note that checking the section that was modified should be done if
//      some enumerations are time consuming.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      LPCTSTR pszSection: Pointer to section name that has been modified
//      in WIN.INI. Note that this argument might be NULL (sent by apps
//      that were written incorrectly!). If it is NULL, then this application
//      should re-enumerate ALL metrics, colors, etc.
//
//  Return (LRESULT):
//      The return value is zero if the message is processed.
//
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppWinIniChange
(
    HWND                    hwnd,
    LPCTSTR                 pszSection
)
{
    DPF(1, "AppWinIniChanged(hwnd=%Xh, pszSection='%s')",
            hwnd, (NULL == pszSection) ? TEXT("(null)") : pszSection);

    //
    //  we processed the message...
    //
    return (0L);
} // AppWinIniChange()

//--------------------------------------------------------------------------;
//
//  HFONT AppChooseFont
//
//  Description:
//      This function is a wrapper for the ChooseFont() common dialog.
//      The purpose of this function is to let the user choose a font that
//      looks good to them--regardless of how stupid it really looks.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      HFONT hfont: Handle to current font (default for chooser dialog).
//
//      PLOGFONT plf: Pointer to optional LOGFONT structure to receive a
//      copy of the LOGFONT information for the newly chosen font.
//
//  Return (HFONT):
//      The return value is the newly chosen font. If no new font was chosen
//      then the return value is NULL.
//
//
//--------------------------------------------------------------------------;

HFONT FNGLOBAL AppChooseFont
(
    HWND                    hwnd,
    HFONT                   hfont,
    PLOGFONT                plf
)
{
    LOGFONT             lf;
    CHOOSEFONT          cf;
    BOOL                f;
    HFONT               hfontNew;

    //
    //  get the font info for the current font...
    //
    GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);

    //
    //  fill in the choosefont structure
    //
    cf.lStructSize  = sizeof(CHOOSEFONT);
    cf.hwndOwner    = hwnd;
    cf.hDC          = NULL;
    cf.Flags        = CF_SCREENFONTS |
                      CF_INITTOLOGFONTSTRUCT |
                      CF_FIXEDPITCHONLY |
                      CF_FORCEFONTEXIST;
    cf.lCustData    = 0;
    cf.lpfnHook     = NULL;
    cf.hInstance    = NULL;
    cf.nFontType    = SCREEN_FONTTYPE;
    cf.lpLogFont    = (LPLOGFONT)&lf;

    //
    //  splash a dialog into the user's face..
    //
    hfontNew = NULL;
    f = ChooseFont(&cf);
    if (f)
    {
        //
        //  create the new font..
        //
        hfontNew = CreateFontIndirect(&lf);
        if (NULL == hfontNew)
            return (NULL);

        //
        //  copy the logfont structure if caller wants it
        //
        if (NULL != plf)
            *plf = lf;
    }

    //
    //  return the new font (if one was chosen)
    //
    return (hfontNew);
} // AppChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileWriteBytes
//
//  Description:
//      This function writes a raw structure of bytes to the application's
//      ini section that can later be retrieved using AppProfileReadBytes.
//      This gives an application the ability to write any structure to
//      the ini file and restore it later--very useful.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PTSTR pszSection: Pointer to section for the stored data.
//
//      PTSTR pszKey: Pointer to key name for the stored data.
//
//      LPBYTE pbStruct: Pointer to the data to be saved.
//
//      UINT cbStruct: Count in bytes of the data to store.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if it fails.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileWriteBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
)
{
    static TCHAR achNibbleToChar[] =
    {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
    };
    #define     NIBBLE2CHAR(x)      (achNibbleToChar[x])

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;

    //
    //  if pbStruct is NULL, then erase the key from the ini file, otherwise
    //  format the raw bytes into a hex string and write that out...
    //
    fAllocated = FALSE;
    psz        = NULL;
    if (NULL != pbStruct)
    {
        //
        //  check if the quick buffer can be used for formatting the output
        //  text--if it cannot, then alloc space for it. note that space
        //  must be available for an ending checksum byte (2 bytes for high
        //  and low nibble) as well as a null terminator.
        //
        psz     = (LPTSTR)ach;
        cchTemp = cbStruct * 2 + 3;
        if (cchTemp > SIZEOF(ach))
        {
            psz = (LPTSTR)GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
            if (NULL == psz)
                return (FALSE);

            fAllocated = TRUE;
        }

        //
        //  step through all bytes in the structure and convert it to
        //  a string of hex numbers...
        //
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            //
            //  grab the next byte and add into checksum...
            //
            bChecksum += (b = *pbStruct);

            *pch++ = NIBBLE2CHAR((b >> (BYTE)4) & (BYTE)0x0F);
            *pch++ = NIBBLE2CHAR(b & (BYTE)0x0F);
        }

        //
        //  add the checksum byte to the end and null terminate the hex
        //  dumped string...
        //
        *pch++ = NIBBLE2CHAR((bChecksum >> (BYTE)4) & (BYTE)0x0F);
        *pch++ = NIBBLE2CHAR(bChecksum & (BYTE)0x0F);
        *pch   = '\0';
    }

    //
    //  write the string of hex bytes out to the ini file...
    //
    fReturn = WritePrivateProfileString(pszSection, pszKey, psz, gszAppProfile);

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileWriteBytes

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileReadBytes
//
//  Description:
//      This function reads a previously stored structure of bytes from
//      the application's ini file. This data must have been written with
//      the AppProfileWriteBytes function--it is checksumed to keep bad
//      data from blowing up the application.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PTSTR pszSection: Pointer to section that contains the data.
//
//      PTSTR pszKey: Pointer to key that contains the data.
//
//      LPBYTE pbStruct: Pointer to buffer to receive the data.
//
//      UINT cbStruct: Number of bytes expected.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if the function fails (bad checksum, missing key, etc).
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileReadBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
)
{
    //
    //  note that the following works for both upper and lower case, and
    //  will return valid values for garbage chars
    //
    #define CHAR2NIBBLE(ch) (BYTE)( ((ch) >= '0' && (ch) <= '9') ?  \
                                (BYTE)((ch) - '0') :                \
                                ((BYTE)(10 + (ch) - 'A') & (BYTE)0x0F) )

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    UINT        u;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;
    TCHAR       ch;

    //
    //  add one the the number of bytes needed to accomodate the checksum
    //  byte placed at the end by AppProfileWriteBytes...
    //
    cbStruct++;

    //
    //  check if the quick buffer can be used for retrieving the input
    //  text--if it cannot, then alloc space for it. note that there must
    //  be space available for the null terminator (the +1 below).
    //
    fAllocated = FALSE;
    psz        = (LPTSTR)ach;
    cchTemp    = cbStruct * 2 + 1;
    if (cchTemp > SIZEOF(ach))
    {
        psz = (LPTSTR)GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
        if (NULL == psz)
            return (FALSE);

        fAllocated = TRUE;
    }

    //
    //  read the hex string... if it is not the correct length, then assume
    //  error and return.
    //
    fReturn = FALSE;
    u = (UINT)GetPrivateProfileString(pszSection, pszKey, gszNull,
                                      psz, cchTemp, gszAppProfile);
    if ((cbStruct * 2) == u)
    {
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            ch = *pch++;
            b  = CHAR2NIBBLE(ch) << (BYTE)4;
            ch = *pch++;
            b |= CHAR2NIBBLE(ch);

            //
            //  if this is not the final byte (the checksum byte), then
            //  store it and accumulate checksum..
            //
            if (cbStruct != 1)
                bChecksum += (*pbStruct = b);
        }

        //
        //  check the last byte read against the checksum that we calculated
        //  if they are not equal then return error...
        //
        fReturn = (bChecksum == b);
    }

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileReadBytes

//==========================================================================;
//
//  Startup and shutdown code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooseFont
//
//  Description:
//      This function lets the user choose a new font for the script window.
//      After a new font is chosen, the font structure is stored to the
//      .ini file so it can be restored on the next run of this application.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      The return value is TRUE if a new font was chosen. It is FALSE if
//      the user canceled the operation.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppChooseFont
(
    HWND                    hwnd
)
{
    LOGFONT             lf;
    HWND                hedit;
    HFONT               hfont;
    HFONT               hfontNew;

    hedit = GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY);

    //
    //  get the current font and pass it to the choose font dialog
    //
    hfont = GetWindowFont(hedit);

    hfontNew = AppChooseFont(hwnd, hfont, &lf);
    if (NULL == hfontNew)
        return (FALSE);

    //
    //  select the new font into the window and delete the old one
    //
    SetWindowFont(hedit, hfontNew, TRUE);
    DeleteFont(hfont);

    ghfontApp = hfontNew;

    //
    //  save the complete description of the chosen font so there can be
    //  no strangness in the font mapping next run. this is overkill, but
    //  it works...
    //
    AppProfileWriteBytes(gszSecConfig, gszKeyFont, (LPBYTE)&lf, sizeof(lf));

    return (TRUE);
} // AcmAppChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppSettingsRestore
//
//  Description:
//      This function restores state information for the application. This
//      function is called just after the main window is created (it has
//      not been ShowWindow()'d). This function will generate the call
//      to ShowWindow before returning.
//
//  Arguments:
//      HWND hwnd: Handle to main window that has just been created but
//      not shown.
//
//      int nCmdShow: The state that the application window should show as.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppSettingsRestore
(
    HWND                    hwnd,
    int                     nCmdShow
)
{
    WINDOWPLACEMENT     wp;
    LOGFONT             lf;
    RECT                rc;
    RECT                rcWindow;
    POINT               pt;
    int                 n;
    BOOL                f;

    //
    //  restore the previous Options state...
    //
    gfuAppOptions = GetPrivateProfileInt(gszSecConfig, gszKeyOptions,
                                         gfuAppOptions, gszAppProfile);

    //
    //  restore the user's preferred font.
    //
    ghfontApp = GetStockFont(SYSTEM_FIXED_FONT);
    f = AppProfileReadBytes(gszSecConfig, gszKeyFont, (LPBYTE)&lf, sizeof(lf));
    if (f)
    {
        HFONT   hfont;

        hfont = CreateFontIndirect(&lf);
        if (NULL != hfont)
        {
            ghfontApp = hfont;
        }
    }

    SetWindowFont(GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY), ghfontApp, FALSE);

    //
    //
    //
    GetPrivateProfileString(gszSecConfig, gszKeyInitialDirOpen, gszNull,
                            gszInitialDirOpen, SIZEOF(gszInitialDirOpen),
                            gszAppProfile);

    GetPrivateProfileString(gszSecConfig, gszKeyInitialDirSave, gszNull,
                            gszInitialDirSave, SIZEOF(gszInitialDirSave),
                            gszAppProfile);

    GetPrivateProfileString(gszSecConfig, gszKeyLastSaveFile, gszNull,
                            gszLastSaveFile, SIZEOF(gszLastSaveFile),
                            gszAppProfile);

    //
    //  grab the stored window position and size from the .ini file...
    //  there must be four arguments stored or the entry is considered
    //  invalid.
    //
    f = AppProfileReadBytes(gszSecConfig, gszKeyWindow,
                            (LPBYTE)&rcWindow, sizeof(rcWindow));
    if (f)
    {
        //
        //  to make sure the user can always get at the window, check to
        //  see if the midpoint of the caption is visible--if it is not,
        //  then default to the default position used when creating the
        //  window.
        //
        n = (rcWindow.right - rcWindow.left) / 2;
        pt.x = (n + rcWindow.left);

        n = GetSystemMetrics(SM_CYCAPTION) / 2 + GetSystemMetrics(SM_CXFRAME);
        pt.y = (n + rcWindow.top);

        GetWindowRect(GetDesktopWindow(), &rc);
        if (PtInRect(&rc, pt))
        {
            //
            //  fill out the window placement structure--default the
            //  maximized and minimized states to default placement by
            //  getting its current placement.
            //
            wp.length = sizeof(wp);
            GetWindowPlacement(hwnd, &wp);

            wp.showCmd          = nCmdShow;
#if 0
            wp.rcNormalPosition = rcWindow;
#else
            n = rcWindow.right - rcWindow.left;
            wp.rcNormalPosition.right  = wp.rcNormalPosition.left + n;

            n = rcWindow.bottom - rcWindow.top;
            wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + n;
#endif

            SetWindowPlacement(hwnd, &wp);
            return (TRUE);
        }
    }

    //
    //  show defaulted and succeed
    //
    ShowWindow(hwnd, nCmdShow);

    return (TRUE);
} // AcmAppSettingsRestore()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppSettingsSave
//
//  Description:
//      This function saves the current state information for the application.
//      It is called just before the main window is closed (destroyed); or
//      as Windows is exiting (query end session).
//
//      Note that this function should not destroy any resources--it can
//      be called at any time to save a snapshot of the application state.
//
//  Arguments:
//      HWND hwnd: Handle to main window that will be destroyed shortly.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppSettingsSave
(
    HWND                    hwnd
)
{
    TCHAR               ach[APP_MAX_STRING_RC_CHARS];
    WINDOWPLACEMENT     wp;
    PRECT               prc;
    BOOL                f;

    //
    //  save the current option settings--note that we ALWAYS turn off the
    //  debug logging option so the app doesn't try to OutputDebugString
    //  unexpectedly during the next session...
    //
    gfuAppOptions &= ~APP_OPTIONSF_DEBUGLOG;
    if (GetPrivateProfileInt(gszSecConfig, gszKeyOptions, 0, gszAppProfile) != gfuAppOptions)
    {
        wsprintf(ach, gszFormatOptions, gfuAppOptions);
        WritePrivateProfileString(gszSecConfig, gszKeyOptions, ach, gszAppProfile);
    }

    //
    //
    //
    //
    WritePrivateProfileString(gszSecConfig, gszKeyInitialDirOpen,
                              gszInitialDirOpen, gszAppProfile);

    WritePrivateProfileString(gszSecConfig, gszKeyInitialDirSave,
                              gszInitialDirSave, gszAppProfile);

    WritePrivateProfileString(gszSecConfig, gszKeyLastSaveFile,
                              gszLastSaveFile, gszAppProfile);

    //
    //  save the current window placement--only store the size and location
    //  of the restored window. maximized and minimized states should
    //  remain defaulted on the next invocation of this application.
    //
    wp.length = sizeof(wp);
    f = GetWindowPlacement(hwnd, &wp);
    if (f)
    {
        prc = &wp.rcNormalPosition;

        //
        //  save the _bounding rectangle_ of the restored window state...
        //
        AppProfileWriteBytes(gszSecConfig, gszKeyWindow, (LPBYTE)prc, sizeof(*prc));
    }

    //
    //  succeed
    //
    return (TRUE);
} // AcmAppSettingsSave()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppShutdown
//
//  Description:
//      This function is called to gracefully shut down the application.
//      If the application should not be closed, a FALSE value is returned.
//      This function is called for WM_CLOSE and WM_QUERYENDSESSION
//      messages...
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PACMAPPFILEDESC paafd: Pointer to current file description.
//
//  Return (BOOL):
//      Returns TRUE if the application can proceed with close. Returns
//      FALSE if the application should NOT be closed.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppShutdown
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    BOOL                f;

    //
    //  check if the script has been modified without saving. if the user
    //  cancels the operation, then we will NOT close the application.
    //
    f = AcmAppFileSaveModified(hwnd, paafd);
    if (!f)
        return (FALSE);

    //
    //
    //
    if (NULL != ghadidNotify)
    {
        acmDriverRemove(ghadidNotify, 0L);
        ghadidNotify = NULL;
    }

    //
    //  save any settings that should be saved on app termination...
    //
    AcmAppSettingsSave(hwnd);

    //
    //  allow closing of application...
    //
    return (TRUE);
} // AcmAppShutdown()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppInit
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppInit
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    LPTSTR                  pszCmdLine,
    int                     nCmdShow
)
{
    BOOL                f;
    DWORD               dwVersion;

    //
    //  !!! VERY IMPORTANT !!!
    //
    //  the ACM may or may not be installed. this application is using
    //  the ACMTHUNK.C file to dynamically link to the ACM. if the
    //  acmThunkInitialize() API fails, then the ACM is *NOT* installed
    //  and none of the API's should be used.
    //
    //  if the ACM *is* installed, then the version MUST be at least
    //  V2.00 for the API's to be available (earlier versions do not
    //  supply the API we need).
    //
    acmThunkInitialize();

    dwVersion = acmGetVersion();
    if (0x0200 <= HIWORD(dwVersion))
    {
        MMRESULT        mmr;

        gfAcmAvailable = TRUE;

        mmr = acmDriverAdd(&ghadidNotify,
                            ghinst,
                            (LPARAM)(UINT)hwnd,
                            WM_ACMAPP_ACM_NOTIFY,
                            ACM_DRIVERADDF_NOTIFYHWND);
        if (MMSYSERR_NOERROR != mmr)
        {
            DPF(0, "!AppCreate: acmDriverAdd failed to add notify window! mmr=%u", mmr);
        }
    }
    else
    {
        if (0L == dwVersion)
        {
            AppMsgBoxId(NULL, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
                        IDS_ERROR_ACM_NOT_PRESENT);
        }
        else
        {
            AppMsgBoxId(NULL, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
                        IDS_ERROR_ACM_TOO_OLD,
                        HIWORD(dwVersion) >> 8,
                        HIWORD(dwVersion) & 0x00FF);
        }

        gfAcmAvailable = FALSE;
    }

    //
    //
    //
    AcmAppSettingsRestore(hwnd, nCmdShow);

    //
    //  strip the command line..
    //
    if (NULL != pszCmdLine)
    {
        while (('\0' != *pszCmdLine) && (' ' == *pszCmdLine))
            pszCmdLine++;
    }

    //
    //  if there is a command line, assume it is a filename for a script
    //  and try to open it. otherwise, just initialize the script window.
    //
    if ((NULL != pszCmdLine) && ('\0' != *pszCmdLine))
    {
        //
        //  attempt to open the specified file..
        //
        lstrcpy(paafd->szFilePath, pszCmdLine);
        f = AcmAppFileOpen(hwnd, paafd);
        if (f)
        {
            AppTitle(hwnd, paafd->szFileTitle);
            AcmAppDisplayFileProperties(hwnd, paafd);
        }
        else
        {
            //
            //  opening the command line file was untriumphant..
            //
            AppMsgBoxId(hwnd, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
                        IDS_ERROR_OPEN_FAILED, (LPSTR)paafd->szFilePath);

            paafd->szFilePath[0]  = '\0';
            paafd->szFileTitle[0] = '\0';
            AppFileNew(hwnd, paafd, FALSE);
        }
    }
    else
    {
        AppFileNew(hwnd, paafd, FALSE);
    }

    return (TRUE);
} // AcmAppInit()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppExit
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppExit
(
    void
)
{
    acmThunkTerminate();

    return (TRUE);
} // AcmAppExit()

//==========================================================================;
//
//  Initialization and exit code...

//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppRegisterPenApp
//
//  Description:
//      This function is used to register and de-register an application
//      as being 'Pen Enhanced.' If the Windows installation is Pen enabled
//      then this function allows the RC Manager to replace all 'Edit'
//      controls with 'HEdit' controls.
//
//      This function must be called to register the application BEFORE
//      creating any edit controls.
//
//  Arguments:
//      BOOL fRegister: If this argument is TRUE, the app is registered
//      with the RC Manager as being Pen aware. If this argument is FALSE
//      the app is de-registered.
//
//  Return (BOOL):
//      The return value is TRUE if Windows for Pen Computing is installed
//      on the system. The return value is FALSE if the Windows installation
//      is not Pen enabled.
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AppRegisterPenApp
(
    BOOL                    fRegister
)
{
    #define RPA_DEFAULT     0x0001

    typedef void (FAR PASCAL *PENWINREGISTERPROC)(UINT, BOOL);

    static char                 szRegPenApp[]   = "RegisterPenApp";
    static PENWINREGISTERPROC   pfnRegPenApp    = NULL;

    HINSTANCE   hinstPenWin;

    //
    //  check if Windows for Pen Computing is installed--and if it is,
    //  dyna-link to the RegisterPenApp() function.
    //
    //  if Pens are not supported, then return FALSE.
    //
    if (NULL == pfnRegPenApp)
    {
        hinstPenWin = (HINSTANCE)GetSystemMetrics(SM_PENWINDOWS);
        if (NULL == hinstPenWin)
            return (FALSE);

        pfnRegPenApp = (PENWINREGISTERPROC)GetProcAddress(hinstPenWin, szRegPenApp);
        if (NULL == pfnRegPenApp)
            return (FALSE);
    }

    //
    //  either enable or disable the RC Manager's Pen meathooks..
    //
    (*pfnRegPenApp)(RPA_DEFAULT, fRegister);

    return (TRUE);
} // AppRegisterPenApp()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCreate
//
//  Description:
//      This function is called to handle the WM_CREATE message for the
//      applications window. The application should finish the creation
//      of the window (create controls, allocate resources, etc). The
//      window has not been displayed (CreateWindow[Ex] has not returned).
//
//  Arguments:
//      HWND hwnd: Handle to the window that is in the process of being
//      created.
//
//      LPCREATESTRUCT pcs: Pointer to a CREATESTRUCT that contains info
//      about the window being created.
//
//  Return (LRESULT):
//      The return value should be nonzero if the application wishes to
//      let the window finish being created. A return of zero tells
//      CreateWindow[Ex] to fail the creation of the window.
//
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppCreate
(
    HWND                    hwnd,
    LPCREATESTRUCT          pcs
)
{
    static TCHAR        szEdit[]    = TEXT("edit");

    HWND                hedit;

    DPF(1, "AppCreate(hwnd=%Xh, cs.x=%d, cs.y=%d, cs.cx=%d, cs.cy=%d)",
            hwnd, pcs->x, pcs->y, pcs->cx, pcs->cy);

    //
    //  create the driver selection listbox
    //
    hedit = CreateWindow(szEdit, gszNull,
                         ES_LEFT | ES_MULTILINE | WS_TABSTOP |
                         ES_AUTOVSCROLL | ES_AUTOHSCROLL |
                         ES_NOHIDESEL | WS_BORDER | WS_VSCROLL | WS_HSCROLL |
                         WS_VISIBLE | WS_CHILD | ES_READONLY,
                         0, 0, 0, 0, hwnd, (HMENU)IDD_ACMAPP_EDIT_DISPLAY,
                         pcs->hInstance, NULL);
    if (NULL == hedit)
        return (0L);

    //
    //  return nonzero to succeed the creation of the window
    //
    return (1L);
} // AppCreate()

//--------------------------------------------------------------------------;
//
//  LRESULT AppQueryEndSession
//
//  Description:
//      This function handles the WM_QUERYENDSESSION. This message is sent
//      by USER when ExitWindows has been called to end the Windows session.
//      This function can stop Windows from exiting if it is not convenient
//      for Windows to end.
//
//      Giving the user the option to save modified data before continueing
//      with the shutdown of Windows is a good idea.
//
//      Telling Windows to continue with the exit procedure does not
//      necessarily mean Windows will exit. All applications are queried
//      for shutdown approval. When the actual decision is made on whether
//      Windows will exit, WM_ENDSESSION will be sent with the result.
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//  Return (LRESULT):
//      Returns zero to STOP Windows from exiting. Returns non-zero to
//      allows windows to shut down.
//
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND                    hwnd
)
{
    BOOL                f;

    DPF(1, "AppQueryEndSession(hwnd=%Xh)", hwnd);

    //
    //  attempt to shut down--if this fails (user canceled it, etc) then
    //  do NOT allow the Windows to exit.
    //
    f = AcmAppShutdown(hwnd, &gaafd);
    if (!f)
        return (0L);

    //
    //  tell Windows to proceed with the shutdown process!
    //
    return (1L);
} // AppQueryEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppEndSession
//
//  Description:
//      This function is called to handle the WM_ENDSESSION message. This
//      message is generated after the application answers the
//      WM_QUERYENDSESSION message. The purpose of the WM_ENDSESSION
//      message is to tell the application if Windows will be exiting
//      (TRUE  == fEndSession) or the end session was canceled by an
//      application (FALSE == fEndSession).
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//      BOOL fEndSession: TRUE if Windows is exiting. FALSE if the end
//      session was canceled.
//
//  Return (LRESULT):
//      Returns zero if the message is processed. Note that an application
//      cannot halt the termination of Windows from this message--the
//      WM_QUERYENDSESSION is the only message that allows that behaviour.
//      If fEndSession is TRUE, Windows *WILL* exit--whether you like it
//      or not.
//
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppEndSession
(
    HWND                    hwnd,
    BOOL                    fEndSession
)
{
    DPF(1, "AppEndSession(hwnd=%Xh, fEndSession=%d)", hwnd, fEndSession);

    //
    //  we processed the message, return zero..
    //
    return (0L);
} // AppEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppClose
//
//  Description:
//      This function handles the WM_CLOSE message for the application.
//      If the application should close, DestroyWindow() must be called
//      by this function. Otherwise the application will not close.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_CLOSE message.
//
//  Return (LRESULT):
//      There return value is zero. The DestroyWindow function will have
//      been called if the application should actually close.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppClose
(
    HWND                    hwnd
)
{
    HWND                hedit;
    HFONT               hfont;
    BOOL                f;

    //
    //  if the Shift key is held down during the close message, then just
    //  save the current state but don't destroy the window... this is
    //  useful if the user does not want to exit the app and rerun it
    //  to make sure the state is saved--just before the user does something
    //  that may crash Windows or something..
    //
    if (GetKeyState(VK_SHIFT) < 0)
    {
        //
        //  save any settings that should be saved on app termination...
        //
        AcmAppSettingsSave(hwnd);
        return (0L);
    }

    //
    //  attempt to shut down--if this fails (user canceled it, etc) then
    //  do NOT allow the window to be destroyed.
    //
    f = AcmAppShutdown(hwnd, &gaafd);
    if (!f)
        return (0L);

    //
    //  destroy the font we are using... before deleting the font, select
    //  the system font back into the window so the font won't be 'in use'
    //  anymore.
    //
    hedit = GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY);

    hfont = GetWindowFont(hedit);
    SetWindowFont(hedit, NULL, FALSE);
    DeleteFont(hfont);

    ghfontApp = NULL;

    //
    //  make the window close and terminate the application
    //
    DestroyWindow(hwnd);

    return (0L);
} // AppClose()

//--------------------------------------------------------------------------;
//
//  BOOL AppInit
//
//  Description:
//      This function is called to initialize a new instance of the
//      application. We want to parse our command line, create our window,
//      allocate resources, etc.
//
//      The arguments passed to this function are exactly the same as
//      those passed to WinMain.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPTSTR pszCmdLine: Points to null-terminated unparsed command line.
//      If the application is compiled for Unicode, then this argument is
//      ignored.
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (HWND):
//      Returns the newly created handle to the applications main window.
//      This handle is NULL if something went wrong and tells the application
//      to exit immediately.
//
//
//--------------------------------------------------------------------------;

HWND FNGLOBAL AppInit
(
    HINSTANCE               hinst,
    HINSTANCE               hinstPrev,
    LPTSTR                  pszCmdLine,
    int                     nCmdShow
)
{
    HWND                hwnd;
    WNDCLASS            wc;

    DPF(1, "AppInit(hinst=%Xh, hinstPrev=%Xh, pszCmdLine='%s', nCmdShow=%d)",
            hinst, hinstPrev, pszCmdLine, nCmdShow);

    LoadString(hinst, IDS_APP_NAME, gszAppName, SIZEOF(gszAppName));
    LoadString(hinst, IDS_FILE_UNTITLED, gszFileUntitled, SIZEOF(gszFileUntitled));

    //
    //  determine whether a new window class needs to be registered for
    //  this application. for Win 16, this only needs to be done for the
    //  first instance of the application created. for Win 32, this must
    //  be done for EVERY instance of the application.
    //
    if (NULL == hinstPrev)
    {
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = (WNDPROC)AppWndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = hinst;
        wc.hIcon         = LoadIcon(hinst, ICON_APP);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = MENU_APP;
        wc.lpszClassName = gszAppName;

        if (!RegisterClass(&wc))
            return (NULL);
    }

    //
    //  if Windows for Pen Computing is installed, then allow the RC
    //  Manager to replace all edit controls created from this point on
    //  with hedit controls
    //
    AppRegisterPenApp(TRUE);

    //
    //  create the application's main window
    //
    //  style bits available:
    //      WS_EX_ACCEPTFILES   :  will receive WM_DROPFILES messages
    //      WS_EX_DLGMODALFRAME :  creates window with double border
    //      WS_EX_NOPARENTNOTIFY:  won't receive WM_PARENTNOTIFY messages
    //      WS_EX_TOPMOST       :  puts window in topmost space
    //      WS_EX_TRANSPARENT   :  a very bizarre style indeed (Win 16 only)
    //
    hwnd = CreateWindowEx(WS_EX_ACCEPTFILES | WS_EX_NOPARENTNOTIFY,
                          gszAppName,
                          gszAppName,
                          WS_OVERLAPPEDWINDOW,
                          APP_WINDOW_XOFFSET,
                          APP_WINDOW_YOFFSET,
                          APP_WINDOW_WIDTH,
                          APP_WINDOW_HEIGHT,
                          NULL,
                          NULL,
                          hinst,
                          NULL);

    if (NULL == hwnd)
        return (NULL);

#ifdef UNICODE
    //
    //  GetCommandLine() returns a pointer to our command line. but this
    //  command line includes the complete command line used to launch
    //  the application--which is different than the pszCmdLine argument
    //  passed through WinMain()...
    //
    //  so, skip over the command name to get to the argument string
    //
    pszCmdLine = GetCommandLine();
    if (NULL != pszCmdLine)
    {
        while (('\0' != *pszCmdLine) && (' ' != *pszCmdLine++))
            ;
    }
#endif

    //
    //
    //
    //
    AcmAppInit(hwnd, &gaafd, pszCmdLine, nCmdShow);

    //
    //  finally, get the window displayed and return success
    //
    //  the ShowWindow call is made during AcmAppInit
    //
//  ShowWindow(hwnd, nCmdShow);
//  UpdateWindow(hwnd);

    return (hwnd);
} // AppInit()

//--------------------------------------------------------------------------;
//
//  int AppExit
//
//  Description:
//      This function is called just before the application exits from
//      WinMain. Its purpose is to clean up any resources that were allocated
//      for running the application: brushes, heaps, etc..
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application that is exiting.
//
//      int nResult: The result of the WM_QUIT message (in wParam of the
//      MSG structure. This argument will usually be 0 (even if the message
//      loop was never entered).
//
//  Return (int):
//      The return value is usually nResult--be we give this function the
//      opportunity to modify its value.
//
//
//--------------------------------------------------------------------------;

int FNGLOBAL AppExit
(
    HINSTANCE               hinst,
    int                     nResult
)
{
    DPF(1, "AppExit(hinst=%Xh, nResult=%d)", hinst, nResult);

    AcmAppExit();

    //
    //  if Windows for Pen Computing is installed, then de-register the
    //  application so the RC Manager knows we will no longer need its
    //  services...
    //
    AppRegisterPenApp(FALSE);

    return (nResult);
} // AppExit()

//==========================================================================;
//
//  Misc rarely used application dialogs and stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AboutDlgProc
//
//  Description:
//      This dialog procedure is used for the ubiquitous about box.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AboutDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    UINT                u;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == u) || (IDCANCEL == u))
            {
                EndDialog(hwnd, (IDOK == u));
            }
            break;
    }

    return (FALSE);
} // AboutDlgProc()


AAPLYREC.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  aaplyrec.c
//
//  Description:
//      An play/record dialog based on MCI Wave.
//
//
//  Known Bugs In MCIWAVE for Windows 3.1:
//  ======================================
//
//  o   If you are running SHARE (or the equivelant) and attempt to open
//      a file that is open by another application, you will receive the
//      following error:
//
//          "Cannot find the specified file. Make sure the path and
//           filename are correct..."
//
//      This is of course wrong. The problem is the file cannot be opened
//      read/write by multiple applications.
//
//  o   Opening a wave file that is more than about three seconds long and
//      issueing the following commands will hang Windows:
//
//          open xxx.wav alias zyz          ; open > 3 second file
//          delete zyz from 1000 to 2000    ; remove some data
//          delete zyz from 1000 to 2000    ; do it again and you hang
//
//      I think this will also happen with the following commands:
//
//          open c:\win31\ding.wav alias zyz
//          delete zyz to 100
//
//  o   You cannot play or record data with a synchronous wave device. This
//      is due to MCIWAVE relying on the ability to 'stream' data. This is
//      not really a bug, just a limitation.
//
//  o   Block alignment is not correctly handled for non-PCM wave files
//      when editing (includes recording). It is illegal to start recording
//      in the middle of a block. The result if this happens is garbage
//      data. This occurs if you insert data with a partial block at the
//      end also...
//
//  o   It is possible to kill Windows by issueing the following commands
//      without yielding between sending the commands:
//
//          capability <file> inputs
//          close <file>
//          open <file>
//
//      Should be able to fix the problem by Yield'ing.
//
//  o   Saving to the same wave file twice without closing the device deletes
//      the existing file. Don't save twice.
//
//  o   Saving an 'empty' wave file does not work. Issueing the following
//      commands demonstrates the problem:
//
//          open new type waveaudio alias zyz
//          save zyz as c:\zyz.wav
//
//      You will receive an 'out of memory error' which is completely bogus.
//      Just don't save empty files.
//
//  o   Setting the time format to bytes doesn't work with compressed files.
//      Sigh...
//
//  o   And there are others with less frequently used commands.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdlib.h>
#include <memory.h>
#include <mmreg.h>
#include <msacm.h>

#include "muldiv32.h"
#include "appport.h"
#include "acmapp.h"

#include "debug.h"

#ifndef _MCIERROR_
#define _MCIERROR_
typedef DWORD           MCIERROR;   // error return code, 0 means no error
#endif

TCHAR           gszAlias[]          = TEXT("zyzthing");

BOOL            gfFileOpen;
BOOL            gfDirty;
BOOL            gfTimerGoing;

UINT            guPlayRecordStatus;

#define AAPLAYRECORD_TIMER_RESOLUTION       54

#define AAPLAYRECORD_MAX_MCI_COMMAND_CHARS  255

#define AAPLAYRECORD_STATUS_NOT_READY       0
#define AAPLAYRECORD_STATUS_PAUSED          1
#define AAPLAYRECORD_STATUS_PLAYING         2
#define AAPLAYRECORD_STATUS_STOPPED         3
#define AAPLAYRECORD_STATUS_RECORDING       4
#define AAPLAYRECORD_STATUS_SEEKING         5

#define AAPLAYRECORD_STATUS_NOT_OPEN        (UINT)-1

//--------------------------------------------------------------------------;
//
//  MCIERROR AcmPlayRecordSendCommand
//
//  Description:
//      The string is of the form "verb params" our device name is inserted
//      after the verb and send to the device.
//
//  Arguments:
//      HWND hwnd:
//
//      PSTR pszCommand:
//
//      PSTR pszReturn:
//
//      UINT cbReturn:
//
//      BOOL fErrorBox:
//
//  Return (MCIERROR):
//
//
//--------------------------------------------------------------------------;

MCIERROR FNLOCAL AcmPlayRecordSendCommand
(
    HWND                    hwnd,
    PTSTR                   pszCommand,
    PTSTR                   pszReturn,
    UINT                    cbReturn,
    BOOL                    fErrorBox
)
{
    MCIERROR            mcierr;
    TCHAR               ach[AAPLAYRECORD_MAX_MCI_COMMAND_CHARS * 2];
    TCHAR              *pch;
    PTSTR               psz;

    pch = pszCommand;

    while (('\t' == *pch) || (' ' == *pch))
    {
        pch++;
    }

    if (0 == lstrlen(pch))
    {
        return (MMSYSERR_NOERROR);
    }

    pch = ach;
    psz = pszCommand;
    while (('\0' != *psz) && (' ' != *psz))
    {
        *pch++ = *psz++;
    }

    *pch++ = ' ';

    lstrcpy(pch, gszAlias);
    lstrcat(pch, psz);

    mcierr = mciSendString(ach, pszReturn, cbReturn, hwnd);
    if (MMSYSERR_NOERROR != mcierr)
    {
        if (fErrorBox)
        {
            int         n;

            n = wsprintf(ach, TEXT("Command: '%s'\n\nMCI Wave Error (%lu): "),
                            (LPTSTR)pszCommand, mcierr);

            mciGetErrorString(mcierr, &ach[n], SIZEOF(ach) - n);
            MessageBox(hwnd, ach, TEXT("MCI Wave Error"), MB_ICONEXCLAMATION | MB_OK);
        }
    }

    return (mcierr);
} // AcmPlayRecordSendCommand()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordCommand
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordCommand
(
    HWND                    hwnd
)
{
    TCHAR               ach[AAPLAYRECORD_MAX_MCI_COMMAND_CHARS * 2];
    HWND                hedit;
    MCIERROR            mcierr;

    //
    //
    //
    hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_COMMAND);
    Edit_SetSel(hedit, 0, -1);

    Edit_GetText(hedit, ach, SIZEOF(ach));

    mcierr = AcmPlayRecordSendCommand(hwnd, ach, ach, SIZEOF(ach), FALSE);
    if (MMSYSERR_NOERROR != mcierr)
    {
        mciGetErrorString(mcierr, ach, SIZEOF(ach));
    }

    hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_RESULT);
    Edit_SetText(hedit, ach);

    return (TRUE);
} // AcmAppPlayRecordCommand()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordSetPosition
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hsb:
//
//      UINT uCode:
//
//      int nPos:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordSetPosition
(
    HWND                    hwnd,
    HWND                    hsb,
    UINT                    uCode,
    int                     nPos
)
{
    MCIERROR            mcierr;
    TCHAR               szPosition[40];
    TCHAR               szLength[40];
    LONG                lLength;
    LONG                lPosition;
    LONG                lPageInc;
    int                 nRange;
    int                 nMinPos;

    //
    //
    //
    //
    if (AAPLAYRECORD_STATUS_NOT_OPEN == guPlayRecordStatus)
    {
        return (FALSE);
    }

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status length"), szLength, SIZEOF(szLength), FALSE);
    if (MMSYSERR_NOERROR != mcierr)
    {
        return (FALSE);
    }

    lLength = _tcstol(szLength, NULL, 10);
    if (0L == lLength)
    {
        return (FALSE);
    }

    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status position"), szPosition, SIZEOF(szPosition), FALSE);
    if (MMSYSERR_NOERROR != mcierr)
    {
        return (FALSE);
    }

    lPosition = _tcstol(szPosition, NULL, 10);

    lPageInc = (lLength / 10);
    if (0L == lPageInc)
    {
        lPageInc = 1;
    }

    //
    //
    //
    switch (uCode)
    {
        case SB_PAGEDOWN:
            lPosition = min(lLength, lPosition + lPageInc);
            break;

        case SB_LINEDOWN:
            lPosition = min(lLength, lPosition + 1);
            break;

        case SB_PAGEUP:
            lPosition -= lPageInc;

            //-- fall through --//

        case SB_LINEUP:
            lPosition = (lPosition < 1) ? 0 : (lPosition - 1);
            break;

        case SB_TOP:
            lPosition = 0;
            break;

        case SB_BOTTOM:
            lPosition = lLength;
            break;

        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
            GetScrollRange(hsb, SB_CTL, &nMinPos, &nRange);
            lPosition = (DWORD)MulDivRN((DWORD)nPos, (DWORD)lLength, (DWORD)nRange);
            break;

        default:
            return (FALSE);
    }

    //
    //
    //
    wsprintf(szPosition, TEXT("seek to %lu"), lPosition);
    AcmPlayRecordSendCommand(hwnd, szPosition, NULL, 0, FALSE);

    return (TRUE);
} // AcmAppPlayRecordSetPosition()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordStatus
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordStatus
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    TCHAR               ach[AAPLAYRECORD_MAX_MCI_COMMAND_CHARS];
    TCHAR               szMode[40];
    TCHAR               szPosition[40];
    TCHAR               szLength[40];
    TCHAR               szFormat[40];
    MCIERROR            mcierr;
    UINT                uStatus;
    BOOL                fStartTimer;
    BOOL                fPlay;
    BOOL                fPause;
    BOOL                fStop;
    BOOL                fStart;
    BOOL                fEnd;
    BOOL                fRecord;
    BOOL                fCommand;
    UINT                uIdFocus;
    DWORD               dwLength;
    DWORD               dwPosition;
    HWND                hsb;

    //
    //
    //
    if (AAPLAYRECORD_STATUS_NOT_OPEN != guPlayRecordStatus)
    {
        mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status mode"), szMode, SIZEOF(szMode), FALSE);
        if (MMSYSERR_NOERROR != mcierr)
        {
            guPlayRecordStatus = AAPLAYRECORD_STATUS_NOT_OPEN;
        }
    }

    //
    //  assume all buttons disabled
    //
    fStartTimer = FALSE;
    fPlay       = FALSE;
    fPause      = FALSE;
    fStop       = FALSE;
    fStart      = FALSE;
    fEnd        = FALSE;
    fRecord     = FALSE;
    fCommand    = TRUE;
    uIdFocus    = IDOK;
    dwPosition  = 0L;
    dwLength    = 0L;
    lstrcpy(szFormat, TEXT("???"));

    hsb = GetDlgItem(hwnd, IDD_AAPLAYRECORD_SCROLL_POSITION);

    //
    //
    //
    if (AAPLAYRECORD_STATUS_NOT_OPEN == guPlayRecordStatus)
    {
        lstrcpy(szMode, TEXT("not open"));
    }
    else if (0 == lstrcmpi(TEXT("not ready"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_NOT_READY;

        fStartTimer = TRUE;
    }
    else if (0 == lstrcmpi(TEXT("paused"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_PAUSED;

        fPause      = TRUE;
        fStop       = TRUE;
    }
    else if (0 == lstrcmpi(TEXT("playing"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_PLAYING;

        fStartTimer = TRUE;
        fPause      = TRUE;
        fStop       = TRUE;
    }
    else if (0 == lstrcmpi(TEXT("stopped"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_STOPPED;

        fPlay       = TRUE;
        fStart      = TRUE;
        fEnd        = TRUE;
        fRecord     = TRUE;
    }
    else if (0 == lstrcmpi(TEXT("recording"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_RECORDING;

        fStartTimer = TRUE;
        fPause      = TRUE;
        fStop       = TRUE;
    }
    else if (0 == lstrcmpi(TEXT("seeking"), szMode))
    {
        uStatus = AAPLAYRECORD_STATUS_SEEKING;

        fStartTimer = TRUE;
        fStop       = TRUE;
    }

    //
    //
    //
    //
    //
    if (fStartTimer)
    {
        if (!gfTimerGoing)
        {
            SetTimer(hwnd, 1, AAPLAYRECORD_TIMER_RESOLUTION, NULL);
            gfTimerGoing = TRUE;
        }
    }
    else if (gfTimerGoing)
    {
        KillTimer(hwnd, 1);
        gfTimerGoing = FALSE;
    }

    //
    //
    //
    //
    if (AAPLAYRECORD_STATUS_NOT_OPEN != guPlayRecordStatus)
    {
        //
        //
        //
        mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status position"), szPosition, SIZEOF(szPosition), FALSE);
        if (MMSYSERR_NOERROR == mcierr)
        {
            dwPosition = _tcstoul(szPosition, NULL, 10);
        }

        mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status length"), szLength, SIZEOF(szLength), FALSE);
        if (MMSYSERR_NOERROR == mcierr)
        {
            dwLength   = _tcstoul(szLength, NULL, 10);
        }

        mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status time format"), szFormat, SIZEOF(szFormat), FALSE);
    }

    //
    //
    //
    //
    if (uStatus != guPlayRecordStatus)
    {
        if (GetFocus() != hsb)
        {
            LRESULT             lr;

            lr = SendMessage(hwnd, DM_GETDEFID, 0, 0L);
            if (DC_HASDEFID == HIWORD(lr))
            {
                UINT        uIdDefId;

                uIdDefId = LOWORD(lr);
                if (IDOK != uIdDefId)
                {
                    HWND        hwndDefId;

                    hwndDefId = GetDlgItem(hwnd, uIdDefId);

                    Button_SetStyle(hwndDefId, BS_PUSHBUTTON, TRUE);
                }
            }

            SendMessage(hwnd, DM_SETDEFID, IDOK, 0L);

            SetFocus(GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_COMMAND));
        }

        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_PLAY),   fPlay  );
        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_PAUSE),  fPause );
        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_STOP),   fStop  );
        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_START),  fStart );
        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_END),    fEnd   );
        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_BTN_RECORD), fRecord);

        EnableWindow(GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_COMMAND), fCommand);
        EnableWindow(GetDlgItem(hwnd, IDOK), fCommand);

        if (AAPLAYRECORD_STATUS_PAUSED == uStatus)
            SetDlgItemText(hwnd, IDD_AAPLAYRECORD_BTN_PAUSE, TEXT("Resum&e"));
        else
            SetDlgItemText(hwnd, IDD_AAPLAYRECORD_BTN_PAUSE, TEXT("Paus&e"));

        guPlayRecordStatus = uStatus;
    }

    //
    //
    //
    AppFormatBigNumber(szPosition, dwPosition);
    AppFormatBigNumber(szLength, dwLength);

    wsprintf(ach, TEXT("%s: %14s (%s) %s"),
                 (LPSTR)szMode,
                 (LPSTR)szPosition,
                 (LPSTR)szLength,
                 (LPSTR)szFormat);

    SetDlgItemText(hwnd, IDD_AAPLAYRECORD_TXT_POSITION, ach);

    //
    //
    //
    //
    {
        int         nRange;
        int         nValue;
        int         nMinPos;
        int         nMaxPos;

        GetScrollRange(hsb, SB_CTL, &nMinPos, &nMaxPos);

        nRange = (int)min(dwLength, 32767L);

        if (nMaxPos != nRange)
        {
            SetScrollRange(hsb, SB_CTL, 0, nRange, FALSE);
        }

        //
        //
        //
        nValue = 0;
        if (0L != dwLength)
        {
            nValue = (int)MulDivRN(dwPosition, nRange, dwLength);
        }

        if (nValue != GetScrollPos(hsb, SB_CTL))
        {
            SetScrollPos(hsb, SB_CTL, nValue, TRUE);
        }
    }

    return (TRUE);
} // AcmAppPlayRecordStatus()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordRecord
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordRecord
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("record insert"), NULL, 0, TRUE);
    if (MMSYSERR_NOERROR == mcierr)
    {
        gfDirty = TRUE;
    }

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordRecord()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordStart
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordStart
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("seek to start"), NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordStart()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordEnd
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordEnd
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("seek to end"), NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordEnd()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordStop
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordStop
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("stop"), NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordStop()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordPause
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordPause
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;
    PTSTR               psz;

    //
    //
    //
    if (AAPLAYRECORD_STATUS_PAUSED == guPlayRecordStatus)
        psz = TEXT("resume");
    else
        psz = TEXT("pause");

    mcierr = AcmPlayRecordSendCommand(hwnd, psz, NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordPause()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordPlay
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordPlay
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;
    TCHAR               szPosition[40];
    TCHAR               szLength[40];

    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status position"), szPosition, SIZEOF(szPosition), TRUE);
    if (MMSYSERR_NOERROR != mcierr)
    {
        return (FALSE);
    }

    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("status length"), szLength, SIZEOF(szLength), TRUE);
    if (MMSYSERR_NOERROR != mcierr)
    {
        return (FALSE);
    }

    if (0 == lstrcmp(szPosition, szLength))
    {
        AcmPlayRecordSendCommand(hwnd, TEXT("seek to start"), NULL, 0, TRUE);
    }

    //
    //
    //
    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("play"), NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordPlay()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordClose
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordClose
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    MCIERROR            mcierr;

    if (gfDirty)
    {
        UINT    u;

        u = MessageBox(hwnd, TEXT("Save newly recorded data?"), TEXT("Save"),
                       MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2);
        if (IDYES == u)
        {
            AppHourGlass(TRUE);
            mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("save"), NULL, 0, TRUE);
            AppHourGlass(FALSE);
        }
        else
        {
            gfDirty = FALSE;
        }
    }

    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("close"), NULL, 0, TRUE);

    return (MMSYSERR_NOERROR == mcierr);
} // AcmAppPlayRecordClose()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordOpen
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordOpen
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    UINT                    uWaveInId,
    UINT                    uWaveOutId
)
{
    TCHAR               ach[AAPLAYRECORD_MAX_MCI_COMMAND_CHARS];
    MCIERROR            mcierr;

    guPlayRecordStatus = AAPLAYRECORD_STATUS_NOT_OPEN;

    gfTimerGoing       = FALSE;
    gfFileOpen         = FALSE;

    gfDirty = FALSE;

    if (NULL == paafd->pwfx)
    {
        MessageBox(hwnd, TEXT("No wave file currently selected."),
                    TEXT("Open Error"), MB_ICONEXCLAMATION | MB_OK);
        return (FALSE);
    }

    wsprintf(ach, TEXT("open \"%s\" alias %s"), (LPSTR)paafd->szFilePath, (LPSTR)gszAlias);

    mcierr = mciSendString(ach, NULL, 0, NULL);
    if (MMSYSERR_NOERROR != mcierr)
    {
        mciGetErrorString(mcierr, ach, SIZEOF(ach));
        MessageBox(hwnd, ach, TEXT("Open Error"), MB_ICONEXCLAMATION | MB_OK);

        return (FALSE);
    }

    gfFileOpen         = TRUE;
    guPlayRecordStatus = AAPLAYRECORD_STATUS_NOT_READY;

    mcierr = AcmPlayRecordSendCommand(hwnd, TEXT("set time format samples"), NULL, 0, TRUE);

    if (WAVE_MAPPER != uWaveInId)
    {
        wsprintf(ach, TEXT("set input %u"), uWaveInId);
        mcierr = AcmPlayRecordSendCommand(hwnd, ach, NULL, 0, TRUE);
    }

    if (WAVE_MAPPER != uWaveOutId)
    {
        wsprintf(ach, TEXT("set output %u"), uWaveOutId);
        mcierr = AcmPlayRecordSendCommand(hwnd, ach, NULL, 0, TRUE);
    }

    return (TRUE);
} // AcmAppPlayRecordOpen()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecordInitCommands
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppPlayRecordInitCommands
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    static PTSTR    pszCommands[] =
    {
        TEXT("play"),
        TEXT("play to Y"),
        TEXT("play from X to Y"),

        TEXT(""),
        TEXT("capability can eject"),
        TEXT("capability can play"),
        TEXT("capability can record"),
        TEXT("capability can save"),
        TEXT("capability compound device"),
        TEXT("capability device type"),
        TEXT("capability has audio"),
        TEXT("capability has video"),
        TEXT("capability inputs"),
        TEXT("capability outputs"),
        TEXT("capability uses files"),

        TEXT(""),
        TEXT("cue input !"),
        TEXT("cue output !"),

        TEXT(""),
        TEXT("delete to Y !"),
        TEXT("delete from X to Y !"),

        TEXT(""),
        TEXT("info input"),
        TEXT("info file"),
        TEXT("info output"),
        TEXT("info product"),

        TEXT(""),
        TEXT("pause"),

        TEXT(""),
        TEXT("record insert !"),
        TEXT("record overwrite !"),
        TEXT("record to Y !"),
        TEXT("record from X to Y !"),

        TEXT(""),
        TEXT("resume"),

        TEXT(""),
        TEXT("save"),
        TEXT("save FILENAME"),

        TEXT(""),
        TEXT("seek to Y"),
        TEXT("seek to start"),
        TEXT("seek to end"),

        TEXT(""),
        TEXT("set alignment X"),
        TEXT("set any input"),
        TEXT("set any output"),
        TEXT("set audio all off"),
        TEXT("set audio all on"),
        TEXT("set audio left off"),
        TEXT("set audio left on"),
        TEXT("set audio right off"),
        TEXT("set audio right on"),
        TEXT("set bitspersample X"),
        TEXT("set bytespersec X"),
        TEXT("set channels X"),
        TEXT("set format tag X"),
        TEXT("set format tag pcm"),
        TEXT("set input X"),
        TEXT("set output X"),
        TEXT("set samplespersec X"),
        TEXT("set time format bytes"),
        TEXT("set time format milliseconds"),
        TEXT("set time format samples"),

        TEXT(""),
        TEXT("status alignment"),
        TEXT("status bitspersample"),
        TEXT("status bytespersec"),
        TEXT("status channels"),
        TEXT("status current track"),
        TEXT("status format tag"),
        TEXT("status input"),
        TEXT("status length"),
        TEXT("status length track X"),
        TEXT("status level"),
        TEXT("status media present"),
        TEXT("status mode"),
        TEXT("status number of tracks"),
        TEXT("status output"),
        TEXT("status position"),
        TEXT("status position track X"),
        TEXT("status ready"),
        TEXT("status samplespersec"),
        TEXT("status start position"),
        TEXT("status time format"),

        TEXT(""),
        TEXT("stop"),
        NULL
    };

    HWND                hcb;
    UINT                u;
    PTSTR               psz;

    //
    //
    //
    hcb = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_COMMAND);

    for (u = 0; psz = pszCommands[u]; u++)

{
        ComboBox_AddString(hcb, psz);
    }

    ComboBox_SetCurSel(hcb, 0);

    return (TRUE);
} // AcmAppPlayRecordInitCommands()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppPlayRecord
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppPlayRecord
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PACMAPPFILEDESC     paafd;
    UINT                uId;
    HWND                hedit;
    HFONT               hfont;

    paafd = (PACMAPPFILEDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            paafd = (PACMAPPFILEDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

//          hfont = GetStockFont(ANSI_FIXED_FONT);
            hfont = ghfontApp;

            hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_TXT_POSITION);
            SetWindowFont(hedit, hfont, FALSE);

            hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_COMMAND);
            SetWindowFont(hedit, hfont, FALSE);

            hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_RESULT);
            SetWindowFont(hedit, hfont, FALSE);

            AcmAppPlayRecordInitCommands(hwnd, paafd);

            AcmAppPlayRecordOpen(hwnd, paafd, guWaveInId, guWaveOutId);
            AcmAppPlayRecordStatus(hwnd, paafd);

            //
            //  if the format is non-PCM, display a little warning so the
            //  user knows that not everything may work correctly when
            //  dealing working with MCI Wave..
            //
            if ((NULL != paafd->pwfx) &&
                (WAVE_FORMAT_PCM != paafd->pwfx->wFormatTag))
            {
                hedit = GetDlgItem(hwnd, IDD_AAPLAYRECORD_EDIT_RESULT);
                Edit_SetText(hedit, TEXT("WARNING! There are known bugs with MCI Wave EDITING operations on non-PCM formats--see the README.TXT with this application. (end)\r\n\r\nRemember, DON'T PANIC!\r\n\r\n\r\n\r\n\r\n\r\n- zYz -"));
            }
            return (TRUE);

        case WM_TIMER:
            AcmAppPlayRecordStatus(hwnd, paafd);
            break;

        case WM_HSCROLL:
            HANDLE_WM_HSCROLL(hwnd, wParam, lParam, AcmAppPlayRecordSetPosition);

            guPlayRecordStatus = AAPLAYRECORD_STATUS_SEEKING;
            AcmAppPlayRecordStatus(hwnd, paafd);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDD_AAPLAYRECORD_BTN_PLAY:
                    AcmAppPlayRecordPlay(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDD_AAPLAYRECORD_BTN_PAUSE:
                    AcmAppPlayRecordPause(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDD_AAPLAYRECORD_BTN_STOP:
                    AcmAppPlayRecordStop(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDD_AAPLAYRECORD_BTN_START:
                    AcmAppPlayRecordStart(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDD_AAPLAYRECORD_BTN_END:
                    AcmAppPlayRecordEnd(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDD_AAPLAYRECORD_BTN_RECORD:
                    AcmAppPlayRecordRecord(hwnd, paafd);
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDOK:
                    AcmAppPlayRecordCommand(hwnd);

                    guPlayRecordStatus = AAPLAYRECORD_STATUS_SEEKING;
                    AcmAppPlayRecordStatus(hwnd, paafd);
                    break;

                case IDCANCEL:
                    if (gfFileOpen)
                    {
                        AcmAppPlayRecordStop(hwnd, paafd);
                        AcmAppPlayRecordStatus(hwnd, paafd);

                        AcmAppPlayRecordClose(hwnd, paafd);
                    }

                    EndDialog(hwnd, gfDirty);
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppPlayRecord()


AAPROPS.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//--------------------------------------------------------------------------;
//
//  aaprops.c
//
//  Description:
//      Get and display information on a .wav file and its format.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>

#include <mmreg.h>
#include <msacm.h>

#include "muldiv32.h"

#include "appport.h"
#include "acmapp.h"

#include "debug.h"

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppGetErrorString
//
//  Description:
//
//
//  Arguments:
//      MMRESULT mmr:
//
//      PTSTR psz:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppGetErrorString
(
    MMRESULT                mmr,
    LPTSTR                  pszError
)
{
    PTSTR               psz;

    switch (mmr)
    {
        case MMSYSERR_NOERROR:
            psz = TEXT("MMSYSERR_NOERROR");
            break;

        case MMSYSERR_ERROR:
            psz = TEXT("MMSYSERR_ERROR");
            break;

        case MMSYSERR_BADDEVICEID:
            psz = TEXT("MMSYSERR_BADDEVICEID");
            break;

        case MMSYSERR_NOTENABLED:
            psz = TEXT("MMSYSERR_NOTENABLED");
            break;

        case MMSYSERR_ALLOCATED:
            psz = TEXT("MMSYSERR_ALLOCATED");
            break;

        case MMSYSERR_INVALHANDLE:
            psz = TEXT("MMSYSERR_INVALHANDLE");
            break;

        case MMSYSERR_NODRIVER:
            psz = TEXT("MMSYSERR_NODRIVER");
            break;

        case MMSYSERR_NOMEM:
            psz = TEXT("MMSYSERR_NOMEM");
            break;

        case MMSYSERR_NOTSUPPORTED:
            psz = TEXT("MMSYSERR_NOTSUPPORTED");
            break;

        case MMSYSERR_BADERRNUM:
            psz = TEXT("MMSYSERR_BADERRNUM");
            break;

        case MMSYSERR_INVALFLAG:
            psz = TEXT("MMSYSERR_INVALFLAG");
            break;

        case MMSYSERR_INVALPARAM:
            psz = TEXT("MMSYSERR_INVALPARAM");
            break;

        case WAVERR_BADFORMAT:
            psz = TEXT("WAVERR_BADFORMAT");
            break;

        case WAVERR_STILLPLAYING:
            psz = TEXT("WAVERR_STILLPLAYING");
            break;

        case WAVERR_UNPREPARED:
            psz = TEXT("WAVERR_UNPREPARED");
            break;

        case WAVERR_SYNC:
            psz = TEXT("WAVERR_SYNC");
            break;

        case ACMERR_NOTPOSSIBLE:
            psz = TEXT("ACMERR_NOTPOSSIBLE");
            break;

        case ACMERR_BUSY:
            psz = TEXT("ACMERR_BUSY");
            break;

        case ACMERR_UNPREPARED:
            psz = TEXT("ACMERR_UNPREPARED");
            break;

        case ACMERR_CANCELED:
            psz = TEXT("ACMERR_CANCELED");
            break;

        default:
            lstrcpy(pszError, TEXT("(unknown)"));
            return (FALSE);
    }

    lstrcpy(pszError, psz);
    return (TRUE);
} // AcmAppGetErrorString()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppGetFormatDescription
//
//  Description:
//
//
//  Arguments:
//      LPWAVEFORMATEX pwfx:
//
//      LPSTR pszFormatTag:
//
//      LPSTR pszFormat:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

TCHAR   gszIntl[]           = TEXT("Intl");
TCHAR   gszIntlList[]       = TEXT("sList");
TCHAR   gszIntlDecimal[]    = TEXT("sDecimal");
TCHAR   gchIntlList         = ',';
TCHAR   gchIntlDecimal      = '.';

BOOL FNGLOBAL AcmAppGetFormatDescription
(
    LPWAVEFORMATEX          pwfx,
    LPTSTR                  pszFormatTag,
    LPTSTR                  pszFormat
)
{
    MMRESULT            mmr;
    BOOL                f;

    f = TRUE;

    //
    //  get the name for the format tag of the specified format
    //
    if (NULL != pszFormatTag)
    {
        ACMFORMATTAGDETAILS aftd;

        //
        //  initialize all unused members of the ACMFORMATTAGDETAILS
        //  structure to zero
        //
        memset(&aftd, 0, sizeof(aftd));

        //
        //  fill in the required members of the ACMFORMATTAGDETAILS
        //  structure for the ACM_FORMATTAGDETAILSF_FORMATTAG query
        //
        aftd.cbStruct    = sizeof(aftd);
        aftd.dwFormatTag = pwfx->wFormatTag;

        //
        //  ask the ACM to find the first available driver that
        //  supports the specified format tag
        //
        mmr = acmFormatTagDetails(NULL,
                                  &aftd,
                                  ACM_FORMATTAGDETAILSF_FORMATTAG);
        if (MMSYSERR_NOERROR == mmr)
        {
            //
            //  copy the format tag name into the caller's buffer
            //
            lstrcpy(pszFormatTag, aftd.szFormatTag);
        }
        else
        {
            PTSTR           psz;

            //
            //  no ACM driver is available that supports the
            //  specified format tag
            //

            f   = FALSE;
            psz = NULL;

            //
            //  the following stuff if proof that the world does NOT need
            //  yet another ADPCM algorithm!!
            //
            switch (pwfx->wFormatTag)
            {
                case WAVE_FORMAT_UNKNOWN:
                    psz = TEXT("** RESERVED INVALID TAG **");
                    break;

                case WAVE_FORMAT_PCM:
                    psz = TEXT("PCM");
                    break;

                case WAVE_FORMAT_ADPCM:
                    psz = TEXT("Microsoft ADPCM");
                    break;

                case 0x0003:
                    psz = TEXT("MV's *UNREGISTERED* ADPCM");
                    break;

                case WAVE_FORMAT_IBM_CVSD:
                    psz = TEXT("IBM CVSD");
                    break;

                case WAVE_FORMAT_ALAW:
                    psz = TEXT("A-Law");
                    break;

                case WAVE_FORMAT_MULAW:
                    psz = TEXT("u-Law");
                    break;

                case WAVE_FORMAT_OKI_ADPCM:
                    psz = TEXT("OKI ADPCM");
                    break;

                case WAVE_FORMAT_IMA_ADPCM:
                    psz = TEXT("IMA/DVI ADPCM");
                    break;

                case WAVE_FORMAT_DIGISTD:
                    psz = TEXT("DIGI STD");
                    break;

                case WAVE_FORMAT_DIGIFIX:
                    psz = TEXT("DIGI FIX");
                    break;

                case WAVE_FORMAT_YAMAHA_ADPCM:
                    psz = TEXT("Yamaha ADPCM");
                    break;

                case WAVE_FORMAT_SONARC:
                    psz = TEXT("Sonarc");
                    break;

                case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
                    psz = TEXT("DSP Group TrueSpeech");
                    break;

                case WAVE_FORMAT_ECHOSC1:
                    psz = TEXT("Echo SC1");
                    break;

                case WAVE_FORMAT_AUDIOFILE_AF36:
                    psz = TEXT("Audiofile AF36");
                    break;

                case WAVE_FORMAT_CREATIVE_ADPCM:
                    psz = TEXT("Creative Labs ADPCM");
                    break;

                case WAVE_FORMAT_APTX:
                    psz = TEXT("APTX");
                    break;

                case WAVE_FORMAT_AUDIOFILE_AF10:
                    psz = TEXT("Audiofile AF10");
                    break;

                case WAVE_FORMAT_DOLBY_AC2:
                    psz = TEXT("Dolby AC2");
                    break;

                case WAVE_FORMAT_MEDIASPACE_ADPCM:
                    psz = TEXT("Media Space ADPCM");
                    break;

                case WAVE_FORMAT_SIERRA_ADPCM:
                    psz = TEXT("Sierra ADPCM");
                    break;

                case WAVE_FORMAT_G723_ADPCM:
                    psz = TEXT("CCITT G.723 ADPCM");
                    break;

                case WAVE_FORMAT_GSM610:
                    psz = TEXT("GSM 6.10");
                    break;

                case WAVE_FORMAT_G721_ADPCM:
                    psz = TEXT("CCITT G.721 ADPCM");
                    break;

                case WAVE_FORMAT_DEVELOPMENT:
                    psz = TEXT("** RESERVED DEVELOPMENT ONLY TAG **");
                    break;

                default:
                    wsprintf(pszFormatTag, TEXT("[%u] (unknown)"), pwfx->wFormatTag);
                    break;
            }

            if (NULL != psz)
            {
                lstrcpy(pszFormatTag, psz);
            }
        }
    }

    //
    //  get the description of the attributes for the specified
    //  format
    //
    if (NULL != pszFormat)
    {
        ACMFORMATDETAILS    afd;

        //
        //  initialize all unused members of the ACMFORMATDETAILS
        //  structure to zero
        //
        memset(&afd, 0, sizeof(afd));

        //
        //  fill in the required members of the ACMFORMATDETAILS
        //  structure for the ACM_FORMATDETAILSF_FORMAT query
        //
        afd.cbStruct    = sizeof(afd);
        afd.dwFormatTag = pwfx->wFormatTag;
        afd.pwfx        = pwfx;

        //
        //  the cbwfx member must be initialized to the total size
        //  in bytes needed for the specified format. for a PCM
        //  format, the cbSize member of the WAVEFORMATEX structure
        //  is not valid.
        //
        if (WAVE_FORMAT_PCM == pwfx->wFormatTag)
        {
            afd.cbwfx   = sizeof(PCMWAVEFORMAT);
        }
        else
        {
            afd.cbwfx   = sizeof(WAVEFORMATEX) + pwfx->cbSize;
        }

        //
        //  ask the ACM to find the first available driver that
        //  supports the specified format
        //
        mmr = acmFormatDetails(NULL, &afd, ACM_FORMATDETAILSF_FORMAT);
        if (MMSYSERR_NOERROR == mmr)
        {
            //
            //  copy the format attributes description into the caller's
            //  buffer
            //
            lstrcpy(pszFormat, afd.szFormat);
        }
        else
        {
            TCHAR           ach[2];
            TCHAR           szChannels[24];
            UINT            cBits;

            //
            //  no ACM driver is available that supports the
            //  specified format
            //

            f = FALSE;

            //
            //
            //
            ach[0] = gchIntlList;
            ach[1] = '\0';

            GetProfileString(gszIntl, gszIntlList, ach, ach, sizeof(ach));
            gchIntlList = ach[0];

            ach[0] = gchIntlDecimal;
            ach[1] = '\0';

            GetProfileString(gszIntl, gszIntlDecimal, ach, ach, sizeof(ach));
            gchIntlDecimal = ach[0];

            //
            //  compute the bit depth--this _should_ be the same as
            //  wBitsPerSample, but isn't always...
            //
            cBits = (UINT)(pwfx->nAvgBytesPerSec * 8 /
                           pwfx->nSamplesPerSec /
                           pwfx->nChannels);

            if ((1 == pwfx->nChannels) || (2 == pwfx->nChannels))
            {
                if (1 == pwfx->nChannels)
                    lstrcpy(szChannels, TEXT("Mono"));
                else
                    lstrcpy(szChannels, TEXT("Stereo"));

                wsprintf(pszFormat, TEXT("%lu%c%.03u kHz%c %u Bit%c %s"),
                            pwfx->nSamplesPerSec / 1000,
                            gchIntlDecimal,
                            (UINT)(pwfx->nSamplesPerSec % 1000),
                            gchIntlList,
                            cBits,
                            gchIntlList,
                            (LPTSTR)szChannels);
            }
            else
            {
                wsprintf(pszFormat, TEXT("%lu%c%.03u kHz%c %u Bit%c %u Channels"),
                            pwfx->nSamplesPerSec / 1000,
                            gchIntlDecimal,
                            (UINT)(pwfx->nSamplesPerSec % 1000),
                            gchIntlList,
                            cBits,
                            gchIntlList,
                            pwfx->nChannels);
            }
        }
    }

    //
    //
    //
    return (f);
} // AcmAppGetFormatDescription()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppGetFilterDescription
//
//  Description:
//
//
//  Arguments:
//      LPWAVEFILTER pwfltr:
//
//      LPSTR pszFilterTag:
//
//      LPSTR pszFilter:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppGetFilterDescription
(
    LPWAVEFILTER            pwfltr,
    LPTSTR                  pszFilterTag,
    LPTSTR                  pszFilter
)
{
    MMRESULT            mmr;
    BOOL                f;

    f = TRUE;

    //
    //  get the name for the filter tag of the specified filter
    //
    if (NULL != pszFilterTag)
    {
        ACMFILTERTAGDETAILS aftd;

        //
        //  initialize all unused members of the ACMFILTERTAGDETAILS
        //  structure to zero
        //
        memset(&aftd, 0, sizeof(aftd));

        //
        //  fill in the required members of the ACMFILTERTAGDETAILS
        //  structure for the ACM_FILTERTAGDETAILSF_FILTERTAG query
        //
        aftd.cbStruct    = sizeof(aftd);
        aftd.dwFilterTag = pwfltr->dwFilterTag;

        //
        //  ask the ACM to find the first available driver that
        //  supports the specified filter tag
        //
        mmr = acmFilterTagDetails(NULL,
                                  &aftd,
                                  ACM_FILTERTAGDETAILSF_FILTERTAG);
        if (MMSYSERR_NOERROR == mmr)
        {
            //
            //  copy the filter tag name into the caller's buffer
            //
            lstrcpy(pszFilterTag, aftd.szFilterTag);
        }
        else
        {
            PTSTR           psz;

            psz = NULL;
            f   = FALSE;

            //
            //  no ACM driver is available that supports the
            //  specified filter tag
            //
            switch (pwfltr->dwFilterTag)
            {
                case WAVE_FILTER_UNKNOWN:
                    psz = TEXT("** RESERVED INVALID TAG **");
                    break;

                case WAVE_FILTER_VOLUME:
                    psz = TEXT("Microsoft Volume Filter");
                    break;

                case WAVE_FILTER_ECHO:
                    psz = TEXT("Microsoft Echo Filter");
                    break;

                case WAVE_FILTER_DEVELOPMENT:
                    psz = TEXT("** RESERVED DEVELOPMENT ONLY TAG **");
                    break;

                default:
                    wsprintf(pszFilterTag, TEXT("[%lu] (unknown)"),pwfltr->dwFilterTag);
                    break;
            }

            if (NULL != psz)
            {
                lstrcpy(pszFilterTag, psz);
            }
        }
    }

    //
    //  get the description of the attributes for the specified
    //  filter
    //
    if (NULL != pszFilter)
    {
        ACMFILTERDETAILS    afd;

        //
        //  initialize all unused members of the ACMFILTERDETAILS
        //  structure to zero
        //
        memset(&afd, 0, sizeof(afd));

        //
        //  fill in the required members of the ACMFILTERDETAILS
        //  structure for the ACM_FILTERDETAILSF_FILTER query
        //
        afd.cbStruct    = sizeof(afd);
        afd.dwFilterTag = pwfltr->dwFilterTag;
        afd.pwfltr      = pwfltr;
        afd.cbwfltr     = pwfltr->cbStruct;

        //
        //  ask the ACM to find the first available driver that
        //  supports the specified filter
        //
        mmr = acmFilterDetails(NULL, &afd, ACM_FILTERDETAILSF_FILTER);
        if (MMSYSERR_NOERROR == mmr)
        {
            //
            //  copy the filter attributes description into the caller's
            //  buffer
            //
            lstrcpy(pszFilter, afd.szFilter);
        }
        else
        {
            //
            //  no ACM driver is available that supports the
            //  specified filter
            //
            f = FALSE;

            wsprintf(pszFilter, TEXT("Unknown Filter %lu, %.08lXh"),
                        pwfltr->dwFilterTag, pwfltr->fdwFilter);
        }
    }

    //
    //
    //
    return (f);
} // AcmAppGetFilterDescription()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDumpExtraHeaderData
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      LPWAVEFORMATEX pwfx:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDumpExtraHeaderData
(
    HWND            hedit,
    LPWAVEFORMATEX  pwfx
)
{
    static TCHAR    szDisplayTitle[]  = TEXT("Offset Data Bytes");

    if ((WAVE_FORMAT_PCM == pwfx->wFormatTag) || (0 == pwfx->cbSize))
        return (TRUE);

    MEditPrintF(hedit, szDisplayTitle);
    MEditPrintF(hedit, TEXT("------ -----------------------------------------------"));

    //
    //  !!! this is really horrible code !!!
    //
{
    #define ACMAPP_DUMP_BYTES_PER_LINE  16

    UINT    u;
    UINT    v;

    for (u = 0; u < pwfx->cbSize; u += ACMAPP_DUMP_BYTES_PER_LINE)
    {
        MEditPrintF(hedit, TEXT("~0x%.04X"), u);

        for (v = 0; v < ACMAPP_DUMP_BYTES_PER_LINE; v++)
        {
            if ((u + v) >= pwfx->cbSize)
                break;

            MEditPrintF(hedit, TEXT("~ %.02X"), ((LPBYTE)(pwfx + 1))[u + v]);
        }

        MEditPrintF(hedit, gszNull);
    }

    #undef ACMAPP_DUMP_BYTES_PER_LINE
}

    return (TRUE);
} // AcmAppDumpExtraHeaderData()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayFileProperties
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PACMAPPFILEDESC paafd:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AcmAppDisplayFileProperties
(
    HWND            hwnd,
    PACMAPPFILEDESC paafd
)
{
    static TCHAR    szInvalidWaveFile[] = TEXT("No File");
    static TCHAR    szDisplayTitle[]    = TEXT("[Wave File Format Properties]\r\n");

    MMRESULT        mmr;
    TCHAR           szFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
    TCHAR           ach[APP_MAX_STRING_CHARS];
    DWORD           dw;
    LPWAVEFORMATEX  pwfx;
    HWND            hedit;
    HMENU           hmenu;
    BOOL            fCanPlayRecord;
    BOOL            f;

    //
    //  clear the display
    //
    AppHourGlass(TRUE);

    hedit = GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY);
    SetWindowRedraw(hedit, FALSE);

    MEditPrintF(hedit, NULL);

    //
    //
    //
    MEditPrintF(hedit, szDisplayTitle);

    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Title"), (LPTSTR)paafd->szFileTitle);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Full Path"), (LPTSTR)paafd->szFilePath);

    AppFormatBigNumber(ach, paafd->cbFileSize);
    MEditPrintF(hedit, TEXT("%25s: %s bytes"), (LPTSTR)TEXT("Total File Size"), (LPTSTR)ach);

    AppFormatDosDateTime(ach, paafd->uDosChangeDate, paafd->uDosChangeTime);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Last Change Date/Time"), (LPTSTR)ach);

    dw = paafd->fdwFileAttributes;
    MEditPrintF(hedit, TEXT("%25s: %c %c%c%c%c %c%c%c%c (%.08lXh)"),
                    (LPTSTR)TEXT("Attributes"),
                    (dw & FILE_ATTRIBUTE_TEMPORARY) ? 't' : '-',
                    (dw & FILE_ATTRIBUTE_NORMAL)    ? 'n' : '-',
                    (dw & 0x00000040)               ? '?' : '-',
                    (dw & FILE_ATTRIBUTE_ARCHIVE)   ? 'a' : '-',
                    (dw & FILE_ATTRIBUTE_DIRECTORY) ? 'd' : '-',
                    (dw & 0x00000008)               ? '?' : '-',
                    (dw & FILE_ATTRIBUTE_SYSTEM)    ? 's' : '-',
                    (dw & FILE_ATTRIBUTE_HIDDEN)    ? 'h' : '-',
                    (dw & FILE_ATTRIBUTE_READONLY)  ? 'r' : '-',
                    dw);

    pwfx = paafd->pwfx;
    if (NULL == pwfx)
    {
        fCanPlayRecord = FALSE;
        goto AA_Display_File_Properties_Exit;
    }

    //
    //
    //
    //
    f = AcmAppGetFormatDescription(pwfx, szFormatTag, ach);
    MEditPrintF(hedit, TEXT("\r\n%25s: %s%s"), (LPTSTR)TEXT("Format"),
                f ? (LPTSTR)gszNull : (LPTSTR)TEXT("*"),
                (LPTSTR)szFormatTag);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Attributes"), (LPTSTR)ach);

    AppFormatBigNumber(ach, paafd->dwDataBytes);
    MEditPrintF(hedit, TEXT("\r\n%25s: %s bytes"), (LPTSTR)TEXT("Data Size"), (LPTSTR)ach);

    AppFormatBigNumber(ach, paafd->dwDataBytes / pwfx->nAvgBytesPerSec);
    dw = paafd->dwDataBytes % pwfx->nAvgBytesPerSec;
    dw = (dw * 1000) / pwfx->nAvgBytesPerSec;
    MEditPrintF(hedit, TEXT("%25s: %s.%.03lu seconds"), (LPTSTR)TEXT("Play Time (avg bytes)"), (LPTSTR)ach, dw);

    AppFormatBigNumber(ach, paafd->dwDataSamples);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Total Samples"), (LPTSTR)ach);

    AppFormatBigNumber(ach, paafd->dwDataSamples / pwfx->nSamplesPerSec);
    dw = paafd->dwDataSamples % pwfx->nSamplesPerSec;
    dw = (dw * 1000) / pwfx->nSamplesPerSec;
    MEditPrintF(hedit, TEXT("%25s: %s.%.03lu seconds"), (LPTSTR)TEXT("Play Time (samples)"), (LPTSTR)ach, dw);

    //
    //
    //
    MEditPrintF(hedit, TEXT("\r\n%25s: %u"), (LPTSTR)TEXT("Format Tag"), pwfx->wFormatTag);
    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Channels"), pwfx->nChannels);

    AppFormatBigNumber(ach, pwfx->nSamplesPerSec);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Samples Per Second"), (LPTSTR)ach);

    AppFormatBigNumber(ach, pwfx->nAvgBytesPerSec);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Avg Bytes Per Second"), (LPTSTR)ach);

    AppFormatBigNumber(ach, pwfx->nBlockAlign);
    MEditPrintF(hedit, TEXT("%25s: %s"), (LPTSTR)TEXT("Block Alignment"), (LPTSTR)ach);

    MEditPrintF(hedit, TEXT("%25s: %u"), (LPTSTR)TEXT("Bits Per Sample"), pwfx->wBitsPerSample);

    if (WAVE_FORMAT_PCM != pwfx->wFormatTag)
    {
        AppFormatBigNumber(ach, pwfx->cbSize);
        MEditPrintF(hedit, TEXT("%25s: %s bytes\r\n"), (LPTSTR)TEXT("Extra Format Information"), (LPTSTR)ach);

        AcmAppDumpExtraHeaderData(hedit, pwfx);
    }

    //
    //  note that we do NOT set the 'WAVE_ALLOWSYNC' bit on queries because
    //  the player/recorder dialog uses MCIWAVE--which cannot work with
    //  SYNC devices.
    //
    mmr = waveOutOpen(NULL,
                      guWaveOutId,
#if (WINVER < 0x0400)
                      (LPWAVEFORMAT)pwfx,
#else
                      pwfx,
#endif
                      0L, 0L, WAVE_FORMAT_QUERY);

    fCanPlayRecord = (MMSYSERR_NOERROR == mmr);

    if (!fCanPlayRecord)
    {
        //
        //  this situation can happen with the 'preferred' device settings
        //  for the Sound Mapper.
        //
        mmr = waveInOpen(NULL,
                         guWaveInId,
#if (WINVER < 0x0400)
                         (LPWAVEFORMAT)pwfx,
#else
                         pwfx,
#endif
                         0L, 0L, WAVE_FORMAT_QUERY);

        fCanPlayRecord = (MMSYSERR_NOERROR == mmr);
    }

AA_Display_File_Properties_Exit:

    hmenu = GetMenu(hwnd);
    EnableMenuItem(hmenu, IDM_PLAYRECORD,
                   MF_BYCOMMAND | (fCanPlayRecord ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar(hwnd);

    Edit_SetSel(hedit, (WPARAM)0, (LPARAM)0);

    SetWindowRedraw(hedit, TRUE);
    AppHourGlass(FALSE);

    return (fCanPlayRecord);
} // AcmAppDisplayFileProperties()


AASYSINF.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//--------------------------------------------------------------------------;
//
//  aasysinf.c
//
//  Description:
//      Respond to the View/System Information menu item - display info on
//      the user's system and the ACM.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>

#include <mmreg.h>
#include <msacm.h>

#include "appport.h"
#include "acmapp.h"

#include "debug.h"

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplaySystemInfo
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplaySystemInfo
(
    HWND            hedit
)
{
    static TCHAR    szDisplayTitle[]  = TEXT("[System Information]\r\n");

    DWORD       dw;

    //
    //
    //
    MEditPrintF(hedit, szDisplayTitle);

    //
    //
    //
    dw = GetVersion();
    MEditPrintF(hedit, TEXT("%23s: %u.%.02u"),
#ifdef WIN32
                    (LPTSTR)TEXT("WinMM Version"),
#else
                    (LPTSTR)TEXT("MMSystem Version"),
#endif
                    HIWORD(dw), LOWORD(dw));

    //
    //
    //
    MEditPrintF(hedit, TEXT("~%23s: "), (LPTSTR)TEXT("ACM Version"));
    dw = acmGetVersion();
    if (0L == dw)
    {
        MEditPrintF(hedit, TEXT("Not Installed"));
        return (TRUE);
    }

    //
    //  if the 'build' number is zero, it is a _retail_ version of the
    //  ACM that is installed. if it is non-zero, it is the debug version.
    //
    if (0 == LOWORD(dw))
    {
        MEditPrintF(hedit, TEXT("%u.%.02u (retail)"),
                        HIWORD(dw) >> 8, HIWORD(dw) & 0x00FF);
    }
    else
    {
        MEditPrintF(hedit, TEXT("%u.%.02u (debug build %u)"),
                        HIWORD(dw) >> 8, HIWORD(dw) & 0x00FF, LOWORD(dw));
    }

    //
    //
    //
    acmMetrics(NULL, ACM_METRIC_COUNT_DRIVERS, &dw);
    MEditPrintF(hedit, TEXT("\r\n%23s: %lu"), (LPTSTR)TEXT("Enabled Drivers"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_DISABLED, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Disabled Drivers"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_CODECS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total CODEC's"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_CONVERTERS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total Converters"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_FILTERS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total Filters"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_HARDWARE, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Hardware Drivers"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_LOCAL_DRIVERS, &dw);
    MEditPrintF(hedit, TEXT("\r\n%23s: %lu"), (LPTSTR)TEXT("Enabled Local Drivers"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_LOCAL_DISABLED, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Disabled Local Drivers"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_LOCAL_CODECS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total Local CODEC's"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_LOCAL_CONVERTERS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total Local Converters"), dw);

    acmMetrics(NULL, ACM_METRIC_COUNT_LOCAL_FILTERS, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Total Local Filters"), dw);

    acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dw);
    MEditPrintF(hedit, TEXT("\r\n%23s: %lu"), (LPTSTR)TEXT("Max Format Bytes"), dw);

    acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FILTER, &dw);
    MEditPrintF(hedit, TEXT("%23s: %lu"), (LPTSTR)TEXT("Max Filter Bytes"), dw);

    return (TRUE);
} // AcmAppDisplaySystemInfo()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppSystemInfoDlgProc
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppSystemInfoDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    HWND                hedit;
    UINT                uId;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hedit = GetDlgItem(hwnd, IDD_AADETAILS_EDIT_DETAILS);
            SetWindowFont(hedit, ghfontApp, FALSE);

            //
            //  clear the display
            //
            AppHourGlass(TRUE);
            SetWindowRedraw(hedit, FALSE);
            MEditPrintF(hedit, NULL);

            AcmAppDisplaySystemInfo(hedit);

            Edit_SetSel(hedit, (WPARAM)0, (LPARAM)0);

            SetWindowRedraw(hedit, TRUE);
            AppHourGlass(FALSE);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == uId) || (IDCANCEL == uId))
            {
                EndDialog(hwnd, (IDOK == uId));
            }
            break;
    }

    return (FALSE);
} // AcmAppSystemInfoDlgProc()


AAWAVDEV.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  aawavdev.c
//
//  Description:
//      Get and display information on the wave devices installed in the
//      system.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>

#include <mmreg.h>
#include <msacm.h>

#include "appport.h"
#include "acmapp.h"

#include "debug.h"

//
//  WAVE[IN|OUT]CAPS
//
//
//
PTSTR gaszWaveInOutCapsFormats[32] =
{
    TEXT("8M11"),           // Bit 0    WAVE_FORMAT_1M08
    TEXT("8S11"),           // Bit 1    WAVE_FORMAT_1S08
    TEXT("16M11"),          // Bit 2    WAVE_FORMAT_1M16
    TEXT("16S11"),          // Bit 3    WAVE_FORMAT_1S16
    TEXT("8M22"),           // Bit 4    WAVE_FORMAT_2M08
    TEXT("8S22"),           // Bit 5    WAVE_FORMAT_2S08
    TEXT("16M22"),          // Bit 6    WAVE_FORMAT_2M16
    TEXT("16S22"),          // Bit 7    WAVE_FORMAT_2S16
    TEXT("8M44"),           // Bit 8    WAVE_FORMAT_4M08
    TEXT("8S44"),           // Bit 9    WAVE_FORMAT_4S08
    TEXT("16M44"),          // Bit 10   WAVE_FORMAT_4M16
    TEXT("16S44"),          // Bit 11   WAVE_FORMAT_4S16
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

PTSTR gaszWaveOutCapsSupport[32] =
{
    TEXT("Pitch"),          // Bit 0    WAVECAPS_PITCH
    TEXT("Playback Rate"),  // Bit 1    WAVECAPS_PLAYBACKRATE
    TEXT("Volume"),         // Bit 2    WAVECAPS_VOLUME
    TEXT("L/R Volume"),     // Bit 3    WAVECAPS_LRVOLUME
    TEXT("Sync"),           // Bit 4    WAVECAPS_SYNC
    NULL,                   // Bit 5
    NULL,                   // Bit 6
    NULL,                   // Bit 7
    NULL,                   // Bit 8
    NULL,                   // Bit 9
    NULL,                   // Bit 10
    NULL,                   // Bit 11
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  MMRESULT AcmAppWaveInGetDevCaps
//
//  Description:
//
//
//  Arguments:
//      UINT uDevId:
//
//      LPWAVEINCAPS pwic:
//
//  Return (MMRESULT):
//
//
//--------------------------------------------------------------------------;

MMRESULT FNLOCAL AcmAppWaveInGetDevCaps
(
    UINT                    uDevId,
    LPWAVEINCAPS            pwic
)
{
    MMRESULT            mmr;

    //
    //
    //
    mmr = waveInGetDevCaps(uDevId, pwic, sizeof(*pwic));
    if (MMSYSERR_NOERROR == mmr)
    {
        //
        //  because some people shipped drivers without testing.
        //
        pwic->szPname[SIZEOF(pwic->szPname) - 1] = '\0';
    }
    else
    {
        _fmemset(pwic, 0, sizeof(*pwic));

        if (MMSYSERR_BADDEVICEID == mmr)
        {
            return (mmr);
        }

        if (WAVE_MAPPER == uDevId)
        {
            lstrcpy(pwic->szPname, TEXT("Default Wave Input Mapper"));
        }
        else
        {
            wsprintf(pwic->szPname, TEXT("Bad Wave Input Device %u"), uDevId);
        }
    }

    return (MMSYSERR_NOERROR);
} // AcmAppWaveInGetDevCaps()

//--------------------------------------------------------------------------;
//
//  MMRESULT AcmAppWaveOutGetDevCaps
//
//  Description:
//
//
//  Arguments:
//      UINT uDevId:
//
//      LPWAVEOUTCAPS pwoc:
//
//  Return (MMRESULT):
//
//
//--------------------------------------------------------------------------;

MMRESULT FNLOCAL AcmAppWaveOutGetDevCaps
(
    UINT                    uDevId,
    LPWAVEOUTCAPS           pwoc
)
{
    MMRESULT            mmr;

    //
    //
    //
    mmr = waveOutGetDevCaps(uDevId, pwoc, sizeof(*pwoc));
    if (MMSYSERR_NOERROR == mmr)
    {
        //
        //  because some people shipped drivers without testing.
        //
        pwoc->szPname[SIZEOF(pwoc->szPname) - 1] = '\0';
    }
    else
    {
        _fmemset(pwoc, 0, sizeof(*pwoc));

        if (MMSYSERR_BADDEVICEID == mmr)
        {
            return (mmr);
        }

        if (WAVE_MAPPER == uDevId)
        {
            lstrcpy(pwoc->szPname, TEXT("Default Wave Output Mapper"));
        }
        else
        {
            wsprintf(pwoc->szPname, TEXT("Bad Wave Output Device %u"), uDevId);
        }
    }

    return (MMSYSERR_NOERROR);
} // AcmAppWaveOutGetDevCaps()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayWaveInDevCaps
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      UINT uDevId:
//
//      LPWAVEINCAPS pwic:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplayWaveInDevCaps
(
    HWND                    hedit,
    UINT                    uDevId,
    LPWAVEINCAPS            pwic,
    LPWAVEFORMATEX          pwfx
)
{
    static TCHAR        szDisplayTitle[]    = TEXT("[Wave Input Device Capabilities]\r\n");
    TCHAR               ach[40];
    PTSTR               psz;
    UINT                u;
    UINT                v;
    DWORD               dw;

    SetWindowRedraw(hedit, FALSE);

    MEditPrintF(hedit, NULL);
    MEditPrintF(hedit, szDisplayTitle);

    //
    //
    //
    if (NULL != pwfx)
    {
        TCHAR           szFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
        TCHAR           szFormat[ACMFORMATDETAILS_FORMAT_CHARS];
        MMRESULT        mmr;
        HWAVEIN         hwi;

        //
        //
        //
        AcmAppGetFormatDescription(pwfx, szFormatTag, szFormat);
        MEditPrintF(hedit, TEXT("%17s: %s"), (LPTSTR)TEXT("Format"), (LPTSTR)szFormatTag);
        MEditPrintF(hedit, TEXT("%17s: %s"), (LPTSTR)TEXT("Attributes"), (LPTSTR)szFormat);

        //
        //
        //
        MEditPrintF(hedit, TEXT("~%17s: "), (LPTSTR)TEXT("Recordable"));
        mmr = waveInOpen(&hwi, uDevId,
#if (WINVER < 0x0400)
                         (LPWAVEFORMAT)pwfx,
#else
                         pwfx,
#endif
                         0L, 0L, 0L);

        if (MMSYSERR_NOERROR == mmr)
        {
            MEditPrintF(hedit, gszYes);
            waveInClose(hwi);
            hwi = NULL;
        }
        else
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
        }

        //
        //
        //
        MEditPrintF(hedit, TEXT("~%17s: "), (LPTSTR)TEXT("(Query)"));
        mmr = waveInOpen(NULL, uDevId,
#if (WINVER < 0x0400)
                         (LPWAVEFORMAT)pwfx,
#else
                         pwfx,
#endif
                         0L, 0L, WAVE_FORMAT_QUERY);

        if (MMSYSERR_NOERROR == mmr)
        {
            MEditPrintF(hedit, gszYes);
        }
        else
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
        }

        MEditPrintF(hedit, gszNull);
    }

    //
    //
    //
    MEditPrintF(hedit, TEXT("%17s: %d"), (LPTSTR)TEXT("Device Id"), uDevId);

    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Manufacturer Id"), pwic->wMid);
    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Product Id"), pwic->wPid);
    MEditPrintF(hedit, TEXT("%17s: %u.%.02u"), (LPTSTR)TEXT("Driver Version"),
                (pwic->vDriverVersion >> 8),
                (pwic->vDriverVersion & 0x00FF));
    MEditPrintF(hedit, TEXT("%17s: '%s'"), (LPTSTR)TEXT("Device Name"), (LPTSTR)pwic->szPname);
    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Channels"), pwic->wChannels);

    //
    //
    //
    //
    MEditPrintF(hedit, TEXT("%17s: %.08lXh"), (LPTSTR)TEXT("Standard Formats"), pwic->dwFormats);
    for (v = u = 0, dw = pwic->dwFormats; (0L != dw); u++)
    {
        if ((BYTE)dw & (BYTE)1)
        {
            psz = gaszWaveInOutCapsFormats[u];
            if (NULL == psz)
            {
                wsprintf(ach, TEXT("Unknown%u"), u);
                psz = ach;
            }

            if (0 == (v % 4))
            {
                if (v != 0)
                {
                    MEditPrintF(hedit, gszNull);
                }
                MEditPrintF(hedit, TEXT("~%19s%s"), (LPTSTR)gszNull, (LPTSTR)psz);
            }
            else
            {
                MEditPrintF(hedit, TEXT("~, %s"), (LPTSTR)psz);
            }

            v++;
        }

        dw >>= 1;
    }
    MEditPrintF(hedit, gszNull);

    SetWindowRedraw(hedit, TRUE);

    return (TRUE);
} // AcmAppDisplayWaveInDevCaps()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDisplayWaveOutDevCaps
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      UINT uDevId:
//
//      LPWAVEOUTCAPS pwoc:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppDisplayWaveOutDevCaps
(
    HWND                    hedit,
    UINT                    uDevId,
    LPWAVEOUTCAPS           pwoc,
    LPWAVEFORMATEX          pwfx
)
{
    static TCHAR    szDisplayTitle[]    = TEXT("[Wave Output Device Capabilities]\r\n");
    TCHAR           ach[40];
    PTSTR           psz;
    UINT            u;
    UINT            v;
    DWORD           dw;

    SetWindowRedraw(hedit, FALSE);

    //
    //
    //
    MEditPrintF(hedit, NULL);
    MEditPrintF(hedit, szDisplayTitle);

    //
    //
    //
    if (NULL != pwfx)
    {
        TCHAR           szFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
        TCHAR           szFormat[ACMFORMATDETAILS_FORMAT_CHARS];
        MMRESULT        mmr;
        HWAVEOUT        hwo;
        DWORD           fdwOpen;

        fdwOpen = (0 != (WAVECAPS_SYNC & pwoc->dwSupport)) ? 0L : WAVE_ALLOWSYNC;

        //
        //
        //
        AcmAppGetFormatDescription(pwfx, szFormatTag, szFormat);
        MEditPrintF(hedit, TEXT("%17s: %s"), (LPTSTR)TEXT("Format"), (LPTSTR)szFormatTag);
        MEditPrintF(hedit, TEXT("%17s: %s"), (LPTSTR)TEXT("Attributes"), (LPTSTR)szFormat);

        //
        //
        //
        MEditPrintF(hedit, TEXT("~%17s: "), (LPTSTR)TEXT("Playable"));
        mmr = waveOutOpen(&hwo, uDevId,
#if (WINVER < 0x0400)
                          (LPWAVEFORMAT)pwfx,
#else
                          pwfx,
#endif
                          0L, 0L, fdwOpen);

        if (MMSYSERR_NOERROR == mmr)
        {
            MEditPrintF(hedit, gszYes);
            waveOutClose(hwo);
            hwo = NULL;
        }
        else
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
        }

        //
        //
        //
        MEditPrintF(hedit, TEXT("~%17s: "), (LPTSTR)TEXT("(Query)"));
        mmr = waveOutOpen(NULL, uDevId,
#if (WINVER < 0x0400)
                          (LPWAVEFORMAT)pwfx,
#else
                          pwfx,
#endif
                          0L, 0L, fdwOpen | WAVE_FORMAT_QUERY);

        if (MMSYSERR_NOERROR == mmr)
        {
            MEditPrintF(hedit, gszYes);
        }
        else
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
        }

        MEditPrintF(hedit, gszNull);
    }

    MEditPrintF(hedit, TEXT("%17s: %d"), (LPTSTR)TEXT("Device Id"), uDevId);

    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Manufacturer Id"), pwoc->wMid);
    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Product Id"), pwoc->wPid);
    MEditPrintF(hedit, TEXT("%17s: %u.%.02u"), (LPTSTR)TEXT("Driver Version"),
                (pwoc->vDriverVersion >> 8),
                (pwoc->vDriverVersion & 0x00FF));
    MEditPrintF(hedit, TEXT("%17s: '%s'"), (LPTSTR)TEXT("Device Name"), (LPTSTR)pwoc->szPname);
    MEditPrintF(hedit, TEXT("%17s: %u"), (LPTSTR)TEXT("Channels"), pwoc->wChannels);

    //
    //
    //
    //
    MEditPrintF(hedit, TEXT("%17s: %.08lXh"), (LPTSTR)TEXT("Standard Formats"), pwoc->dwFormats);
    for (v = u = 0, dw = pwoc->dwFormats; (0L != dw); u++)
    {
        if ((BYTE)dw & (BYTE)1)
        {
            psz = gaszWaveInOutCapsFormats[u];
            if (NULL == psz)
            {
                wsprintf(ach, TEXT("Unknown%u"), u);
                psz = ach;
            }

            if (0 == (v % 4))
            {
                if (v != 0)
                {
                    MEditPrintF(hedit, gszNull);
                }
                MEditPrintF(hedit, TEXT("~%19s%s"), (LPTSTR)gszNull, (LPTSTR)psz);
            }
            else
            {
                MEditPrintF(hedit, TEXT("~, %s"), (LPTSTR)psz);
            }

            v++;
        }

        dw >>= 1;
    }
    MEditPrintF(hedit, gszNull);

    //
    //
    //
    //
    MEditPrintF(hedit, TEXT("%17s: %.08lXh"), (LPTSTR)TEXT("Standard Support"), pwoc->dwSupport);
    for (v = u = 0, dw = pwoc->dwSupport; dw; u++)
    {
        if ((BYTE)dw & (BYTE)1)
        {
            psz = gaszWaveOutCapsSupport[u];
            if (NULL == psz)
            {
                wsprintf(ach, TEXT("Unknown%u"), u);
                psz = ach;
            }

            if (0 == (v % 4))
            {
                if (v != 0)
                {
                    MEditPrintF(hedit, gszNull);
                }
                MEditPrintF(hedit, TEXT("~%19s%s"), (LPTSTR)gszNull, (LPTSTR)psz);
            }
            else
            {
                MEditPrintF(hedit, TEXT("~, %s"), (LPTSTR)psz);
            }

            v++;
        }

        dw >>= 1;
    }
    MEditPrintF(hedit, gszNull);

    SetWindowRedraw(hedit, TRUE);

    return (TRUE);
} // AcmAppDisplayWaveOutDevCaps()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppWaveDeviceDlgProc
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppWaveDeviceDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LONG                lDevice;
    BOOL                fInput;
    UINT                uDevId;

    UINT                cWaveDevs;
    UINT                u;
    UINT                uId;
    UINT                uCmd;
    HWND                hcb;
    HWND                hedit;
    HFONT               hfont;

    WAVEINCAPS          wic;
    WAVEOUTCAPS         woc;

    lDevice = GetWindowLong(hwnd, DWL_USER);
    uDevId  = (UINT)(int)(short)LOWORD(lDevice);
    fInput  = (BOOL)HIWORD(lDevice);

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
//          hfont = GetStockFont(ANSI_FIXED_FONT);
            hfont = ghfontApp;
            hedit = GetDlgItem(hwnd, IDD_AAWAVEDEVICE_EDIT_CAPABILITIES);
            SetWindowFont(hedit, hfont, FALSE);

            uDevId = (UINT)(int)(short)LOWORD(lParam);
            fInput = (BOOL)HIWORD(lParam);
            SetWindowLong(hwnd, DWL_USER, lParam);

            //
            //
            //
            hcb = GetDlgItem(hwnd, IDD_AAWAVEDEVICE_COMBO_DEVICE);
            SetWindowFont(hcb, hfont, FALSE);

            if (fInput)
                cWaveDevs = waveInGetNumDevs() + 1;
            else
                cWaveDevs = waveOutGetNumDevs() + 1;

            for (u = (UINT)WAVE_MAPPER; (0 != cWaveDevs); u++, cWaveDevs--)
            {
                if (fInput)
                {
                    AcmAppWaveInGetDevCaps(u, &wic);
                    ComboBox_AddString(hcb, wic.szPname);
                }
                else
                {
                    AcmAppWaveOutGetDevCaps(u, &woc);
                    ComboBox_AddString(hcb, woc.szPname);
                }

                if (uDevId == u)
                {
                    hedit = GetDlgItem(hwnd, IDD_AAWAVEDEVICE_EDIT_CAPABILITIES);

                    if (fInput)
                        AcmAppDisplayWaveInDevCaps(hedit, uDevId, &wic, gaafd.pwfx);
                    else
                        AcmAppDisplayWaveOutDevCaps(hedit, uDevId, &woc, gaafd.pwfx);
                }
            }

            ComboBox_SetCurSel(hcb, uDevId + 1);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);

            switch (uId)
            {
                case IDOK:
                    hcb    = GetDlgItem(hwnd, IDD_AAWAVEDEVICE_COMBO_DEVICE);
                    uDevId = ComboBox_GetCurSel(hcb);
                    if (CB_ERR != uDevId)
                    {
                        EndDialog(hwnd, uDevId - 1);
                        break;
                    }

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, uDevId);
                    break;

                case IDD_AAWAVEDEVICE_COMBO_DEVICE:
                    uCmd = GET_WM_COMMAND_CMD(wParam, lParam);
                    hcb  = GET_WM_COMMAND_HWND(wParam, lParam);
                    switch (uCmd)
                    {
                        case CBN_SELCHANGE:
                            uDevId = ComboBox_GetCurSel(hcb);
                            if (CB_ERR == uDevId)
                                break;

                            uDevId--;

                            hedit = GetDlgItem(hwnd, IDD_AAWAVEDEVICE_EDIT_CAPABILITIES);
                            if (fInput)
                            {
                                AcmAppWaveInGetDevCaps(uDevId, &wic);
                                AcmAppDisplayWaveInDevCaps(hedit, uDevId, &wic, gaafd.pwfx);
                            }
                            else
                            {
                                AcmAppWaveOutGetDevCaps(uDevId, &woc);
                                AcmAppDisplayWaveOutDevCaps(hedit, uDevId, &woc, gaafd.pwfx);
                            }
                            break;
                    }
            }
            return (TRUE);
    }

    return (FALSE);
} // AcmAppWaveDeviceDlgProc()


ACMAPP.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  acmapp.c
//
//  Description:
//      This is a sample application that demonstrates how to use the
//      Audio Compression Manager API's in Windows. This application is
//      also useful as an ACM driver test.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <shellapi.h>
#include <stdarg.h>
#include <memory.h>
#include <mmreg.h>
#include <msacm.h>

#include "appport.h"
#include "acmapp.h"

#include "debug.h"

//
//  globals, no less
//
HINSTANCE       ghinst;
BOOL            gfAcmAvailable;
UINT            gfuAppOptions       = APP_OPTIONSF_AUTOOPEN;
HFONT           ghfontApp;
HACMDRIVERID    ghadidNotify;

UINT            guWaveInId          = (UINT)WAVE_MAPPER;
UINT            guWaveOutId         = (UINT)WAVE_MAPPER;

TCHAR           gszNull[]           = TEXT("");
TCHAR           gszAppProfile[]     = TEXT("acmapp.ini");
TCHAR           gszYes[]            = TEXT("Yes");
TCHAR           gszNo[]             = TEXT("No");

TCHAR           gszAppName[APP_MAX_APP_NAME_CHARS];
TCHAR           gszFileUntitled[APP_MAX_FILE_TITLE_CHARS];

TCHAR           gszInitialDirOpen[APP_MAX_FILE_PATH_CHARS];
TCHAR           gszInitialDirSave[APP_MAX_FILE_PATH_CHARS];

TCHAR           gszLastSaveFile[APP_MAX_FILE_PATH_CHARS];

ACMAPPFILEDESC  gaafd;

//==========================================================================;
//
//  Application helper functions
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int AppMsgBox
//
//  Description:
//      This function displays a message for the application in a standard
//      message box.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      PTSTR pszFormat: Format string used for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() function.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBox
(
    HWND                    hwnd,
    UINT                    fuStyle,
    PTSTR                   pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[1024];
    int         n;

    //
    //  format and display the message..
    //
    va_start(va, pszFormat);
#ifdef WIN32
    wvsprintf(ach, pszFormat, va);
#else
    wvsprintf(ach, pszFormat, (LPSTR)va);
#endif
    va_end(va);

    n = MessageBox(hwnd, ach, gszAppName, fuStyle);

    return (n);
} // AppMsgBox()

//--------------------------------------------------------------------------;
//
//  int AppMsgBoxId
//
//  Description:
//      This function displays a message for the application. The message
//      text is retrieved from the string resource table using LoadString.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() if the string
//      resource specified by uIdsFormat is valid. The return value is zero
//      if the string resource failed to load.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBoxId
(
    HWND                    hwnd,
    UINT                    fuStyle,
    UINT                    uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
        //
        //  format and display the message..
        //
        va_start(va, uIdsFormat);
#ifdef WIN32
        wvsprintf(ach, szFormat, va);
#else
        wvsprintf(ach, szFormat, (LPSTR)va);
#endif
        va_end(va);

        n = MessageBox(hwnd, ach, gszAppName, fuStyle);
    }

    return (n);
} // AppMsgBoxId()

//--------------------------------------------------------------------------;
//
//  void AppHourGlass
//
//  Description:
//      This function changes the cursor to that of the hour glass or
//      back to the previous cursor.
//
//      This function can be called recursively.
//
//  Arguments:
//      BOOL fHourGlass: TRUE if we need the hour glass.  FALSE if we need
//      the arrow back.
//
//  Return (void):
//      On return, the cursor will be what was requested.
//
//--------------------------------------------------------------------------;

void FNGLOBAL AppHourGlass
(
    BOOL                    fHourGlass
)
{
    static HCURSOR  hcur;
    static UINT     uWaiting = 0;

    if (fHourGlass)
    {
        if (!uWaiting)
        {
            hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE);
        }

        uWaiting++;
    }
    else
    {
        --uWaiting;

        if (!uWaiting)
        {
            ShowCursor(FALSE);
            SetCursor(hcur);
        }
    }
} // AppHourGlass()

//--------------------------------------------------------------------------;
//
//  BOOL AppYield
//
//  Description:
//      This function yields by dispatching all messages stacked up in the
//      application queue.
//
//  Arguments:
//      HWND hwnd: Handle to main window of application if not yielding
//      for a dialog. Handle to dialog box if yielding for a dialog box.
//
//      BOOL fIsDialog: TRUE if being called to yield for a dialog box.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppYield
(
    HWND                    hwnd,
    BOOL                    fIsDialog
)
{
    MSG     msg;

    if (fIsDialog)
    {
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if ((NULL == hwnd) || !IsDialogMessage(hwnd, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }
    else
    {
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (TRUE);
} // AppYield()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowText
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      PTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowText
(
    HWND                    hwnd,
    PTSTR                   pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the string in the window...
    //
    va_start(va, pszFormat);
#ifdef WIN32
    n = wvsprintf(ach, pszFormat, va);
#else
    n = wvsprintf(ach, pszFormat, (LPSTR)va);
#endif
    va_end(va);

    SetWindowText(hwnd, ach);

    return (n);
} // AppSetWindowText()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowTextId
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result. The format string is extracted from the string
//      table using LoadString() on the uIdsFormat argument.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was. This value is zero if the LoadString() function fails
//      for the uIdsFormat argument.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowTextId
(
    HWND                    hwnd,
    UINT                    uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
        //
        //  format and display the string in the window...
        //
        va_start(va, uIdsFormat);
#ifdef WIN32
        n = wvsprintf(ach, szFormat, va);
#else
        n = wvsprintf(ach, szFormat, (LPSTR)va);
#endif
        va_end(va);

        SetWindowText(hwnd, ach);
    }

    return (n);
} // AppSetWindowTextId()

//--------------------------------------------------------------------------;
//
//  BOOL AppFormatBigNumber
//
//  Description:
//
//
//  Arguments:
//      LPTSTR pszNumber:
//
//      DWORD dw:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFormatBigNumber
(
    LPTSTR                  pszNumber,
    DWORD                   dw
)
{
    //
    //  this is ugly...
    //
    //
    if (dw >= 1000000000L)
    {
        wsprintf(pszNumber, TEXT("%u,%03u,%03u,%03u"),
                            (WORD)(dw / 1000000000L),
                            (WORD)((dw % 1000000000L) / 1000000L),
                            (WORD)((dw % 1000000L) / 1000),
                            (WORD)(dw % 1000));
    }
    else if (dw >= 1000000L)
    {
        wsprintf(pszNumber, TEXT("%u,%03u,%03u"),
                            (WORD)(dw / 1000000L),
                            (WORD)((dw % 1000000L) / 1000),
                            (WORD)(dw % 1000));
    }
    else if (dw >= 1000)
    {
        wsprintf(pszNumber, TEXT("%u,%03u"),
                            (WORD)(dw / 1000),
                            (WORD)(dw % 1000));
    }
    else
    {
        wsprintf(pszNumber, TEXT("%lu"), dw);
    }

    return (TRUE);
} // AppFormatBigNumber()

//--------------------------------------------------------------------------;
//
//  BOOL AppFormatDosDateTime
//
//  Description:
//
//
//  Arguments:
//      LPTSTR pszDateTime:
//
//      UINT uDosDate:
//
//      UINT uDosTime:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFormatDosDateTime
(
    LPTSTR                  pszDateTime,
    UINT                    uDosDate,
    UINT                    uDosTime
)
{
    static TCHAR        szFormatDateTime[]  = TEXT("%.02u/%.02u/%.02u  %.02u:%.02u:%.02u");

    UINT                uDateMonth;
    UINT                uDateDay;
    UINT                uDateYear;
    UINT                uTimeHour;
    UINT                uTimeMinute;
    UINT                uTimeSecond;

    //
    //
    //
    uTimeHour   = uDosTime >> 11;
    uTimeMinute = (uDosTime & 0x07E0) >> 5;
    uTimeSecond = (uDosTime & 0x001F) << 1;

    uDateMonth  = (uDosDate & 0x01E0) >> 5;
    uDateDay    = (uDosDate & 0x001F);
    uDateYear   = (uDosDate >> 9) + 80;

    //
    //
    //
    //
    wsprintf(pszDateTime, szFormatDateTime,
             uDateMonth,
             uDateDay,
             uDateYear,
             uTimeHour,
             uTimeMinute,
             uTimeSecond);

    return (TRUE);
} // AppFormatDosDateTime()

//--------------------------------------------------------------------------;
//
//  void AcmAppDebugLog
//
//  Description:
//      This function logs information to the debugger if the Debug Log
//      option is set. You can then run DBWin (or something similar)
//      to redirect the output whereever you want. Very useful for debugging
//      ACM drivers.
//
//  Arguments:
//      PTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (void):
//      None.
//
//--------------------------------------------------------------------------;

void FNCGLOBAL AcmAppDebugLog
(
    PTSTR                   pszFormat,
    ...
)
{
    static  TCHAR   szDebugLogSeparator[] = TEXT("=============================================================================\r\n");

    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];

    //
    //  !!! UNICODE !!!
    //
    //
    if (0 != (APP_OPTIONSF_DEBUGLOG & gfuAppOptions))
    {
        if (NULL == pszFormat)
        {
            OutputDebugString(szDebugLogSeparator);
            return;
        }

        //
        //  format and display the string in a message box...
        //
        va_start(va, pszFormat);
#ifdef WIN32
        wvsprintf(ach, pszFormat, va);
#else
        wvsprintf(ach, pszFormat, (LPSTR)va);
#endif
        va_end(va);

        OutputDebugString(ach);
    }
} // AcmAppDebugLog()

//--------------------------------------------------------------------------;
//
//  int MEditPrintF
//
//  Description:
//      This function is used to print formatted text into a Multiline
//      Edit Control as if it were a standard console display. This is
//      a very easy way to display small amounts of text information
//      that can be scrolled and copied to the clip-board.
//
//  Arguments:
//      HWND hedit: Handle to a Multiline Edit control.
//
//      PTSTR pszFormat: Pointer to any valid format for wsprintf. If
//      this argument is NULL, then the Multiline Edit Control is cleared
//      of all text.
//
//
//  Return (int):
//      Returns the number of characters written into the edit control.
//
//  Notes:
//      The pszFormat string can contain combinations of escapes that
//      modify the default behaviour of this function. Escapes are single
//      character codes placed at the _beginning_ of the format string.
//
//      Current escapes defined are:
//
//      ~   :   Suppresses the default CR/LF added to the end of the
//              printed line. Since the most common use of this function
//              is to output a whole line of text with a CR/LF, that is
//              the default.
//
//      `   :   Suppresses logging to the debug terminal (regardless of
//              the global debug log options flag).
//
//
//--------------------------------------------------------------------------;

int FNCGLOBAL MEditPrintF
(
    HWND                    hedit,
    PTSTR                   pszFormat,
    ...
)
{
    static  TCHAR   szCRLF[]              = TEXT("\r\n");

    va_list     va;
    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    int         n;
    BOOL        fCRLF;
    BOOL        fDebugLog;

    //
    //  default the escapes
    //
    fCRLF     = TRUE;
    fDebugLog = TRUE;

    //
    //  if the pszFormat argument is NULL, then just clear all text in
    //  the edit control..
    //
    if (NULL == pszFormat)
    {
        SetWindowText(hedit, gszNull);

        AcmAppDebugLog(NULL);

        return (0);
    }

    //
    //  format and display the string in the window... first search for
    //  escapes to modify default behaviour.
    //
    for (;;)
    {
        switch (*pszFormat)
        {
            case '~':
                fCRLF = FALSE;
                pszFormat++;
                continue;

            case '`':
                fDebugLog = FALSE;
                pszFormat++;
                continue;
        }

        break;
    }

    va_start(va, pszFormat);
#ifdef WIN32
    n = wvsprintf(ach, pszFormat, va);
#else
    n = wvsprintf(ach, pszFormat, (LPSTR)va);
#endif
    va_end(va);

    Edit_SetSel(hedit, (WPARAM)-1, (LPARAM)-1);
    Edit_ReplaceSel(hedit, ach);

    if (fDebugLog)
    {
        AcmAppDebugLog(ach);
    }

    if (fCRLF)
    {
        Edit_SetSel(hedit, (WPARAM)-1, (LPARAM)-1);
        Edit_ReplaceSel(hedit, szCRLF);

        if (fDebugLog)
        {
            AcmAppDebugLog(szCRLF);
        }
    }

    return (n);
} // MEditPrintF()

//--------------------------------------------------------------------------;
//
//  BOOL AppGetFileTitle
//
//  Description:
//      This function extracts the file title from a file path and returns
//      it in the caller's specified buffer.
//
//  Arguments:
//      PTSTR pszFilePath: Pointer to null terminated file path.
//
//      PTSTR pszFileTitle: Pointer to buffer to receive the file title.
//
//  Return (BOOL):
//      Always returns TRUE. But should return FALSE if this function
//      checked for bogus values, etc.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppGetFileTitle
(
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle
)
{
    #define IS_SLASH(c)     ('/' == (c) || '\\' == (c))

    PTSTR       pch;

    //
    //  scan to the end of the file path string..
    //
    for (pch = pszFilePath; '\0' != *pch; pch++)
        ;

    //
    //  now scan back toward the beginning of the string until a slash (\),
    //  colon, or start of the string is encountered.
    //
    while ((pch >= pszFilePath) && !IS_SLASH(*pch) && (':' != *pch))
    {
        pch--;
    }

    //
    //  finally, copy the 'title' into the destination buffer.. skip ahead
    //  one char since the above loop steps back one too many chars...
    //
    lstrcpy(pszFileTitle, ++pch);

    return (TRUE);
} // AppGetFileTitle()

//--------------------------------------------------------------------------;
//
//  BOOL AppGetFileName
//
//  Description:
//      This function is a wrapper for the Get[Open/Save]FileName commdlg
//      chooser dialogs. Based on the fuFlags argument, this function will
//      display the appropriate chooser dialog and return the result.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      PTSTR pszFilePath: Pointer to buffer to receive the file path.
//
//      PTSTR pszFileTitle: Pointer to buffer to receive the file title.
//      This argument may be NULL, in which case no title will be returned.
//
//      UINT fuFlags:
//
//  Return (BOOL):
//      The return value is TRUE if a file was chosen. It is FALSE if the
//      user canceled the operation.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppGetFileName
(
    HWND                    hwnd,
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle,
    UINT                    fuFlags
)
{
    #define APP_OFN_FLAGS_SAVE  (OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT)
    #define APP_OFN_FLAGS_OPEN  (OFN_HIDEREADONLY | OFN_FILEMUSTEXIST)

    TCHAR               szExtDefault[APP_MAX_EXT_DEFAULT_CHARS];
    TCHAR               szExtFilter[APP_MAX_EXT_FILTER_CHARS];
    OPENFILENAME        ofn;
    BOOL                f;
    PTCHAR              pch;

    //
    //  get the extension filter and default extension for this application
    //
    LoadString(ghinst, IDS_OFN_EXT_DEF, szExtDefault, SIZEOF(szExtDefault));
    LoadString(ghinst, IDS_OFN_EXT_FILTER, szExtFilter, SIZEOF(szExtFilter));

    //
    //  NOTE! building the filter string for the OPENFILENAME structure
    //  is a bit more difficult when dealing with Unicode and C8's new
    //  optimizer. it joyfully removes literal '\0' characters from
    //  strings that are concatted together. if you try making each
    //  string separate (array of pointers to strings), the compiler
    //  will dword align them... etc, etc.
    //
    //  if you can think of a better way to build the silly filter string
    //  for common dialogs and still work in Win 16 and Win 32 [Unicode]
    //  i'd sure like to hear about it...
    //
    for (pch = &szExtFilter[0]; '\0' != *pch; pch++)
    {
        if ('!' == *pch)
            *pch = '\0';
    }

    //
    //  initialize the OPENFILENAME members
    //
    memset(&ofn, 0, sizeof(OPENFILENAME));

    pszFilePath[0]          = '\0';
    if (pszFileTitle)
        pszFileTitle[0]     = '\0';

    ofn.lStructSize         = sizeof(OPENFILENAME);
    ofn.hwndOwner           = hwnd;
    ofn.lpstrFilter         = szExtFilter;
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0L;
    ofn.nFilterIndex        = 1L;
    ofn.lpstrFile           = pszFilePath;
    ofn.nMaxFile            = APP_MAX_FILE_PATH_CHARS;
    ofn.lpstrFileTitle      = pszFileTitle;
    ofn.nMaxFileTitle       = pszFileTitle ? APP_MAX_FILE_TITLE_CHARS : 0;
    if (fuFlags & APP_GETFILENAMEF_SAVE)
    {
        ofn.lpstrInitialDir = gszInitialDirSave;
    }
    else
    {
        ofn.lpstrInitialDir = gszInitialDirOpen;
    }
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = szExtDefault;

    //
    //  if the fuFlags.APP_GETFILENAMEF_SAVE bit is set, then call
    //  GetSaveFileName() otherwise call GetOpenFileName(). why commdlg was
    //  designed with two separate functions for save and open only clark
    //  knows.
    //
    if (fuFlags & APP_GETFILENAMEF_SAVE)
    {
        ofn.Flags = APP_OFN_FLAGS_SAVE;
        f = GetSaveFileName(&ofn);
        if (f)
        {
            if (NULL != pszFilePath)
            {
                lstrcpy(gszInitialDirSave, pszFilePath);

                pch = &gszInitialDirSave[lstrlen(gszInitialDirSave) - 1];
                for ( ; gszInitialDirSave != pch; pch--)
                {
                    if ('\\' == *pch)
                    {
                        *pch = '\0';
                        break;
                    }
                }
            }
        }
    }
    else
    {
        ofn.Flags = APP_OFN_FLAGS_OPEN;
        f = GetOpenFileName(&ofn);
        if (f)
        {
            if (NULL != pszFilePath)
            {
                lstrcpy(gszInitialDirOpen, pszFilePath);

                pch = &gszInitialDirOpen[lstrlen(gszInitialDirOpen) - 1];
                for ( ; gszInitialDirOpen != pch; pch--)
                {
                    if ('\\' == *pch)
                    {
                        *pch = '\0';
                        break;
                    }
                }
            }
        }
    }

    return (f);
} // AppGetFileName()

//--------------------------------------------------------------------------;
//
//  BOOL AppTitle
//
//  Description:
//      This function formats and sets the title text of the application's
//      window.
//
//  Arguments:
//      HWND hwnd: Handle to application window to set title text for.
//
//      PTSTR pszFileTitle: Pointer to file title to display.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppTitle
(
    HWND                    hwnd,
    PTSTR                   pszFileTitle
)
{
    static  TCHAR   szFormatTitle[]     = TEXT("%s - %s");

    TCHAR       ach[APP_MAX_FILE_PATH_CHARS];

    //
    //  format the title text as 'AppName - FileTitle'
    //
    wsprintf(ach, szFormatTitle, (LPSTR)gszAppName, (LPSTR)pszFileTitle);
    SetWindowText(hwnd, ach);

    return (TRUE);
} // AppTitle()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileNew
//
//  Description:
//      This function is called to handle the IDM_FILE_NEW message. It is
//      responsible for clearing the working area for a new unnamed file.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PACMAPPFILEDESC paafd: Pointer to current file descriptor.
//
//  Return (BOOL):
//      The return value is TRUE if the working area was cleared and is
//      ready for new stuff. The return value is FALSE if the user canceled
//      the operation.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFileNew
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    BOOL                    fCreate
)
{
    BOOL                f;

    if (fCreate)
    {
        f = AcmAppFileNew(hwnd, paafd);
        if (!f)
            return (FALSE);
    }
    else
    {
        //
        //  if there is currently a file path, then we have to do some real
        //  work...
        //
        if ('\0' != paafd->szFilePath[0])
        {
            f = AcmAppFileNew(hwnd, paafd);
            if (!f)
                return (FALSE);
        }

        //
        //  blow away the old file path and title; set the window title
        //  and return success
        //
        lstrcpy(paafd->szFilePath, gszFileUntitled);
        lstrcpy(paafd->szFileTitle, gszFileUntitled);
    }

    AppTitle(hwnd, paafd->szFileTitle);

    AcmAppDisplayFileProperties(hwnd, paafd);

    return (TRUE);
} // AppFileNew()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileOpen
//
//  Description:
//      This function handles the IDM_FILE_OPEN message. It is responsible
//      for getting a new file name from the user and opening that file
//      if possible.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PACMAPPFILEDESC paafd: Pointer to current file descriptor.
//
//  Return (BOOL):
//      The return value is TRUE if a new file was selected and opened.
//      It is FALSE if the user canceled the operation.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AppFileOpen
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
)
{
    TCHAR               szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR               szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    BOOL                f;

    //
    //  first test for a modified file that has not been saved. if the
    //  return value is FALSE we should cancel the File.Open operation.
    //
    f = AcmAppFileSaveModified(hwnd, paafd);
    if (!f)
        return (FALSE);

    //
    //  get the file name of the new file into temporary buffers (so
    //  if we fail to open it we can back out cleanly).
    //
    f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GETFILENAMEF_OPEN);
    if (!f)
        return (FALSE);

    //!!!
    //  read the new file...
    //
    lstrcpy(paafd->szFilePath, szFilePath);
    lstrcpy(paafd->szFileTitle, szFileTitle);

    f = AcmAppFileOpen(hwnd, paafd);
    if (f)
    {
        //
        //  set the window title text...
        //
        AppTitle(hwnd, szFileTitle);
        AcmAppDisplayFileProperties(hwnd, paafd);
    }

    return (f);
} // AppFileOpen()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileSave
//
//  Description:
//      This function handles the IDM_FILE_SAVE[AS] messages. It is
//      responsible for saving the current file. If a file name needs
//      to be specified then the save file dialog is displayed.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PACMAPPFILEDESC paafd: Pointer to current file descriptor.
//
//      BOOL fSaveAs: TRUE if the save file chooser should be displayed
//      before saving the file. FALSE if should operate like File.Save.
//
//  Return (BOOL):
//      The return value is TRUE if the file was saved. It is FALSE if the
//      user canceled the operation or the file does not need saved.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFileSave
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    BOOL                    fSaveAs
)
{
    TCHAR               szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR               szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    BOOL                f;

    //
    //
    //
    lstrcpy(szFilePath, paafd->szFilePath);
    lstrcpy(szFileTitle, paafd->szFileTitle);

    //
    //  check if we should bring up the save file chooser dialog...
    //
    if (fSaveAs || (0 == lstrcmp(paafd->szFileTitle, gszFileUntitled)))
    {
        //
        //  get the file name for saving the data to into temporary
        //  buffers (so if we fail to save it we can back out cleanly).
        //
        f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GETFILENAMEF_SAVE);
        if (!f)
            return (FALSE);
    }

    //
    //  save the file...
    //
    f = AcmAppFileSave(hwnd, paafd, szFilePath, szFileTitle, 0);
    if (f)
    {
        //
        //  changes have been saved, so clear the modified bit...
        //
        paafd->fdwState &= ~ACMAPPFILEDESC_STATEF_MODIFIED;

        AppTitle(hwnd, paafd->szFileTitle);

        AcmAppDisplayFileProperties(hwnd, paafd);
    }

    return (f);
} // AppFileSave()

//==========================================================================;
//
//  Main application window handling code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT AppInitMenuPopup
//
//  Description:
//      This function handles the WM_INITMENUPOPUP message. This message
//      is sent to the window owning the menu that is going to become
//      active. This gives an application the ability to modify the menu
//      before it is displayed (disable/add items, etc).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      HMENU hmenu: Handle to the menu that is to become active.
//
//      int nItem: Specifies the zero-based relative position of the menu
//      item that invoked the popup menu.
//
//      BOOL fSysMenu: Specifies whether the popup menu is a System menu
//      (TRUE) or it is not a System menu (FALSE).
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppInitMenuPopup
(
    HWND                    hwnd,
    HMENU                   hmenu,
    int                     nItem,
    BOOL                    fSysMenu
)
{
    BOOL                f;
    int                 nSelStart;
    int                 nSelEnd;
    HWND                hedit;

    DPF(4, "AppInitMenuPopup(hwnd=%Xh, hmenu=%Xh, nItem=%d, fSysMenu=%d)",
            hwnd, hmenu, nItem, fSysMenu);

    //
    //  if the system menu is what got hit, succeed immediately... this
    //  application has no stuff in the system menu.
    //
    if (fSysMenu)
        return (0L);

    //
    //  initialize the menu that is being 'popped up'
    //
    switch (nItem)
    {
        case APP_MENU_ITEM_FILE:
            EnableMenuItem(hmenu, IDM_FILE_NEW,
                           (UINT)(gfAcmAvailable ? MF_ENABLED : MF_GRAYED));

            f = (NULL != gaafd.pwfx);
            EnableMenuItem(hmenu, IDM_FILE_SNDPLAYSOUND_PLAY,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hmenu, IDM_FILE_SNDPLAYSOUND_STOP,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));

            f = (NULL != gaafd.pwfx) && gfAcmAvailable;
            EnableMenuItem(hmenu, IDM_FILE_CONVERT,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hmenu, IDM_FILE_CONVERT_ALL,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));

            //
            //  if the file has been modified, then enable the File.Save
            //  menu
            //
            f = (0 != (gaafd.fdwState & ACMAPPFILEDESC_STATEF_MODIFIED));
            EnableMenuItem(hmenu, IDM_FILE_SAVE,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));

            // f = (NULL != gaafd.pwfx);
            EnableMenuItem(hmenu, IDM_FILE_SAVEAS,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_EDIT:
            //
            //  check to see if something is selected in the display
            //  window and enable/disable Edit menu options appropriately
            //
            hedit = GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY);
            Edit_GetSelEx(hedit, &nSelStart, &nSelEnd);

            f = (nSelStart != nSelEnd);
            EnableMenuItem(hmenu, WM_COPY,  (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_VIEW:
            EnableMenuItem(hmenu, IDM_VIEW_ACM_DRIVERS,
                           (UINT)(gfAcmAvailable ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_OPTIONS:
            f = (0 != waveInGetNumDevs());
            EnableMenuItem(hmenu, IDM_OPTIONS_WAVEINDEVICE,  (UINT)(f ? MF_ENABLED : MF_GRAYED));

            f = (0 != waveOutGetNumDevs());
            EnableMenuItem(hmenu, IDM_OPTIONS_WAVEOUTDEVICE, (UINT)(f ? MF_ENABLED : MF_GRAYED));

            //
            //  make sure the options that need a checkmark are checked..
            //
            f = (0 != (APP_OPTIONSF_AUTOOPEN & gfuAppOptions));
            CheckMenuItem(hmenu, IDM_OPTIONS_AUTOOPEN,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));

            f = (0 != (APP_OPTIONSF_DEBUGLOG & gfuAppOptions));
            CheckMenuItem(hmenu, IDM_OPTIONS_DEBUGLOG,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));
            break;
    }

    //
    //  we processed the message--return 0...
    //
    return (0L);
} // AppInitMenuPopup()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCommand
//
//  Description:
//      This function handles the WM_COMMAND message.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the WM_COMMAND message.
//
//      int nId: Control or menu item identifier.
//
//      HWND hwndCtl: Handle of control if the message is from a control.
//      This argument is NULL if the message was not generated by a control.
//
//      UINT uCode: Notification code. This argument is 1 if the message
//      was generated by an accelerator. If the message is from a menu,
//      this argument is 0.
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppCommand
(
    HWND                    hwnd,
    int                     nId,
    HWND                    hwndCtl,
    UINT                    uCode
)
{
    BOOL                f;
    DWORD               dw;
    UINT                uDevId;

    switch (nId)
    {
        case IDM_FILE_NEW:
            AppFileNew(hwnd, &gaafd, TRUE);
            break;

        case IDM_FILE_OPEN:
            AppFileOpen(hwnd, &gaafd);
            break;

        case IDM_FILE_SAVE:
            AppFileSave(hwnd, &gaafd, FALSE);
            break;

        case IDM_FILE_SAVEAS:
            AppFileSave(hwnd, &gaafd, TRUE);
            break;

        case IDM_FILE_SNDPLAYSOUND_PLAY:
            if (NULL == gaafd.pwfx)
            {
                MessageBeep((UINT)-1);
                break;
            }

            AppHourGlass(TRUE);
            dw = timeGetTime();
            f  = sndPlaySound(gaafd.szFilePath, SND_ASYNC | SND_NODEFAULT);
            dw = timeGetTime() - dw;
            AppHourGlass(FALSE);

            if (0 != (APP_OPTIONSF_DEBUGLOG & gfuAppOptions))
            {
                AcmAppDebugLog(NULL);
                AcmAppDebugLog(TEXT("sndPlaySound(%s) took %lu milliseconds to %s.\r\n"),
                                (LPTSTR)gaafd.szFilePath,
                                dw,
                                f ? (LPTSTR)TEXT("succeed") : (LPTSTR)TEXT("fail"));
            }

            if (!f)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                          TEXT("The file '%s' cannot be played by sndPlaySound."),
                          (LPSTR)gaafd.szFilePath);
            }
            break;

        case IDM_FILE_SNDPLAYSOUND_STOP:
            sndPlaySound(NULL, 0L);
            break;

        case IDM_FILE_CONVERT:
            AcmAppFileConvert(hwnd, &gaafd);
            break;

        case IDM_FILE_CONVERT_ALL:
            AcmAppMultiThreadedConvertAll(hwnd, &gaafd);
            break;

        case IDM_FILE_ABOUT:
            DialogBox(ghinst, DLG_ABOUT, hwnd, AboutDlgProc);
            break;

        case IDM_FILE_EXIT:
            FORWARD_WM_CLOSE(hwnd, SendMessage);
            break;

        case WM_COPY:
            //
            //  pass on edit messages received to the display window
            //
            SendMessage(GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY), nId, 0, 0L);
            break;

        case IDM_EDIT_SELECTALL:
            Edit_SetSel(GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY), 0, -1);
            break;

        case IDM_UPDATE:
            AcmAppFileOpen(hwnd, &gaafd);

            AcmAppDisplayFileProperties(hwnd, &gaafd);
            break;

        case IDM_VIEW_SYSTEMINFO:
            DialogBox(ghinst, DLG_AADETAILS, hwnd, AcmAppSystemInfoDlgProc);
            break;

        case IDM_VIEW_ACM_DRIVERS:
            DialogBox(ghinst, DLG_AADRIVERS, hwnd, AcmAppDriversDlgProc);
            break;

        case IDM_OPTIONS_WAVEINDEVICE:
            uDevId = DialogBoxParam(ghinst, DLG_AAWAVEDEVICE, hwnd,
                                    AcmAppWaveDeviceDlgProc,
                                    MAKELONG((WORD)guWaveInId, TRUE));

            if (uDevId != guWaveInId)
            {
                guWaveInId = uDevId;
                AcmAppDisplayFileProperties(hwnd, &gaafd);
            }
            break;

        case IDM_OPTIONS_WAVEOUTDEVICE:
            uDevId = DialogBoxParam(ghinst, DLG_AAWAVEDEVICE, hwnd,
                                    AcmAppWaveDeviceDlgProc,
                                    MAKELONG((WORD)guWaveOutId, FALSE));

            if (uDevId != guWaveOutId)
            {
                guWaveOutId = uDevId;
                AcmAppDisplayFileProperties(hwnd, &gaafd);
            }
            break;

        case IDM_OPTIONS_AUTOOPEN:
            gfuAppOptions ^= APP_OPTIONSF_AUTOOPEN;
            break;

        case IDM_OPTIONS_DEBUGLOG:
            gfuAppOptions ^= APP_OPTIONSF_DEBUGLOG;
            break;

        case IDM_OPTIONS_FONT:
            AcmAppChooseFont(hwnd);
            break;

        case IDM_PLAYRECORD:
            if (NULL == gaafd.pwfx)
            {
                f = AppFileNew(hwnd, &gaafd, TRUE);
                if (!f)
                    break;

                if (NULL == gaafd.pwfx)
                    break;
            }

            f = DialogBoxParam(ghinst, DLG_AAPLAYRECORD, hwnd,
                               AcmAppPlayRecord, (LPARAM)(LPVOID)&gaafd);
            if (f)
            {
                AcmAppFileOpen(hwnd, &gaafd);

                AcmAppDisplayFileProperties(hwnd, &gaafd);
            }
            break;
    }

    return (0L);
} // AppCommand()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcDragDropContinue
//
//  Description:
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcDragDropContinue
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    UINT                uId;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            AppSetWindowText(hwnd, TEXT("File %u of %u"), LOWORD(lParam), HIWORD(lParam));
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == uId) || (IDCANCEL == uId))
            {
                EndDialog(hwnd, uId);
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcDragDropContinue()

//--------------------------------------------------------------------------;
//
//  LRESULT AppDropFiles
//
//  Description:
//      This function handles the WM_DROPFILES message. This message is
//      sent when files are 'dropped' on the window from file manager
//      (or other drag/drop servers made by ISV's that figured out the
//      undocumented internal workings of the SHELL).
//
//      A window must be registered to receive these messages either by
//      called DragAcceptFiles() or using CreateWindowEx() with the
//      WS_EX_ACCEPTFILES style bit.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the message.
//
//      HDROP hdrop: Handle to drop structure.
//
//  Return (LRESULT):
//      Returns 0 if the message is processed.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppDropFiles
(
    HWND                    hwnd,
    HDROP                   hdrop
)
{
    TCHAR               szFilePath[APP_MAX_FILE_PATH_CHARS];
    UINT                cFiles;
    UINT                u;
    BOOL                f;
    int                 n;

    //
    //  first test for a file that has not been saved. if the return
    //  value is FALSE we should cancel the drop operation.
    //
    f = AcmAppFileSaveModified(hwnd, &gaafd);
    if (!f)
    {
        goto App_Drop_Files_Exit;
    }

    //
    //  get number of files dropped on our window
    //
    cFiles = DragQueryFile(hdrop, (UINT)-1, NULL, 0);

    DPF(4, "AppDropFiles(hwnd=%Xh, hdrop=%Xh)--cFiles=%u", hwnd, hdrop, cFiles);

    //
    //  step through each file and stop on the one the user wants or
    //  the last file (whichever comes first).
    //
    for (u = 0; u < cFiles; u++)
    {
        //
        //  get the next file name and try to open it--if not a valid
        //  file, then skip to the next one (if there is one).
        //
        DragQueryFile(hdrop, u, szFilePath, SIZEOF(szFilePath));

        //
        //  !!! destructive !!!
        //
        //  attempt to open the file
        //
        lstrcpy(gaafd.szFilePath, szFilePath);
        lstrcpy(gaafd.szFileTitle, gszNull);

        f = AcmAppFileOpen(hwnd, &gaafd);
        if (!f)
        {
            continue;
        }

        AppTitle(hwnd, gaafd.szFileTitle);
        AcmAppDisplayFileProperties(hwnd, &gaafd);

        //
        //  if this is NOT the last file in the list of files that are
        //  being dropped on us, then bring up a box asking if we should
        //  continue or stop where we are..
        //
        if ((cFiles - 1) != u)
        {
            n = DialogBoxParam(ghinst,
                               DLG_AADRAGDROP,
                               hwnd,
                               AcmAppDlgProcDragDropContinue,
                               MAKELPARAM((WORD)(u + 1), (WORD)cFiles));
            if (IDCANCEL == n)
                break;
        }
    }

    //
    //  tell the shell to release the memory it allocated for beaming
    //  the file name(s) over to us... return 0 to show we processed
    //  the message.
    //
App_Drop_Files_Exit:

    DragFinish(hdrop);
    return (0L);
} // AppDropFiles()

//--------------------------------------------------------------------------;
//
//  LRESULT AppSize
//
//  Description:
//      This function handles the WM_SIZE message for the application's
//      window. This message is sent to the application window after the
//      size has changed (but before it is painted).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_SIZE message.
//
//      UINT fuSizeType: Specifies the type of resizing requested. This
//      argument is one of the following: SIZE_MAXIMIZED, SIZE_MINIMIZED,
//      SIZE_RESTORED, SIZE_MAXHIDE, or SIZE_MAXSHOW.
//
//      int nWidth: Width of the new client area for the window.
//
//      int nHeight: Height of the new client area for the window.
//
//  Return (LRESULT):
//      Returns zero if the application processes the message.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppSize
(
    HWND                    hwnd,
    UINT                    fuSizeType,
    int                     nWidth,
    int                     nHeight
)
{
    HWND                hedit;
    RECT                rc;

    DPF(4, "AppSize(hwnd=%Xh, fuSizeType=%u, nWidth=%d, nHeight=%d)",
            hwnd, fuSizeType, nWidth, nHeight);

    //
    //  unless this application is the one being resized then don't waste
    //  time computing stuff that doesn't matter. this applies to being
    //  minimized also because this application does not have a custom
    //  minimized state.
    //
    if ((SIZE_RESTORED != fuSizeType) && (SIZE_MAXIMIZED != fuSizeType))
        return (0L);

    //
    //
    //
    GetClientRect(hwnd, &rc);
    InflateRect(&rc, 1, 1);

    hedit = GetDlgItem(hwnd, IDD_ACMAPP_EDIT_DISPLAY);
    SetWindowPos(hedit,
                 NULL,
                 rc.left,
                 rc.top,
                 rc.right - rc.left,
                 rc.bottom - rc.top,
                 SWP_NOZORDER);

    //
    //  we processed the message..
    //
    return (0L);
} // AppSize()

//--------------------------------------------------------------------------;
//
//  LRESULT AcmAppNotify
//
//  Description:
//
//
//  Arguments:
//
//  Return (LRESULT):
//
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AcmAppNotify
(
    HWND                hwnd,
    WPARAM              wParam,
    LPARAM              lParam
)
{
    HWND                hwndNext;

    DPF(1, "AcmAppNotify: hwnd=%.04Xh, wParam=%.04Xh, lParam2=%.08lXh",
        hwnd, wParam, lParam);

    //
    //
    //
    hwndNext = GetWindow(hwnd, GW_HWNDFIRST);
    while (NULL != hwndNext)
    {
        if (GetParent(hwndNext) == hwnd)
        {
            SendMessage(hwndNext, WM_ACMAPP_ACM_NOTIFY, wParam, lParam);
        }

        hwndNext = GetWindow(hwndNext, GW_HWNDNEXT);
    }

    //
    //  now force an update to our display in case driver [dis/en]able
    //  changed what is playable/recordable.
    //
    AcmAppDisplayFileProperties(hwnd, &gaafd);

    //
    //
    //
    return (1L);
} // AcmAppNotify()

//--------------------------------------------------------------------------;
//
//  LRESULT AppWndProc
//
//  Description:
//      This is the main application window procedure.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (LRESULT):
//      The return value depends on the message that is being processed.
//
//--------------------------------------------------------------------------;

LRESULT FNEXPORT AppWndProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LRESULT             lr;

    switch (uMsg)
    {
        case WM_CREATE:
            lr = HANDLE_WM_CREATE(hwnd, wParam, lParam, AppCreate);
            return (lr);

        case WM_WININICHANGE:
            HANDLE_WM_WININICHANGE(hwnd, wParam, lParam, AppWinIniChange);
            return (0L);

        case WM_INITMENUPOPUP:
            HANDLE_WM_INITMENUPOPUP(hwnd, wParam, lParam, AppInitMenuPopup);
            return (0L);

        case WM_COMMAND:
            lr = HANDLE_WM_COMMAND(hwnd, wParam, lParam, AppCommand);
            return (lr);

        case WM_DROPFILES:
            //
            //  some windowsx.h files have a screwed up message cracker for
            //  WM_DROPFILES. because this is a sample app, i don't want
            //  people having trouble with bogus windowsx.h files, so crack
            //  the message manually... you should use the message cracker
            //  if you know your windowsx.h file is good.
            //
            //  lr = HANDLE_WM_DROPFILES(hwnd, wParam, lParam, AppDropFiles);
            //
            lr = AppDropFiles(hwnd, (HDROP)wParam);
            return (lr);

        case WM_SIZE:
            //
            //  handle what we want for sizing, and then always call the
            //  default handler...
            //
            HANDLE_WM_SIZE(hwnd, wParam, lParam, AppSize);
            break;

        case WM_QUERYENDSESSION:
            lr = HANDLE_WM_QUERYENDSESSION(hwnd, wParam, lParam, AppQueryEndSession);
            return (lr);

        case WM_ENDSESSION:
            HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, AppEndSession);
            return (0L);

        case WM_CLOSE:
            HANDLE_WM_CLOSE(hwnd, wParam, lParam, AppClose);
            return (0L);

        case WM_DESTROY:
            PostQuitMessage(0);
            return (0L);

        case WM_ACMAPP_ACM_NOTIFY:
            AcmAppNotify(hwnd, wParam, lParam);
            return (0L);
    }

    return (DefWindowProc(hwnd, uMsg, wParam, lParam));
} // AppWndProc()

//==========================================================================;
//
//  Main entry and message dispatching code
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int WinMain
//
//  Description:
//      This function is called by the system as the initial entry point
//      for a Windows application.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPSTR pszCmdLine: Points to null-terminated unparsed command line.
//      This string is strictly ANSI regardless of whether the application
//      is built for Unicode. To get the Unicode equivalent call the
//      GetCommandLine() function (Win 32 only).
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (int):
//      Returns result from WM_QUIT message (in wParam of MSG structure) if
//      the application is able to enter its message loop. Returns 0 if
//      the application is not able to enter its message loop.
//
//--------------------------------------------------------------------------;

int PASCAL WinMain
(
    HINSTANCE               hinst,
    HINSTANCE               hinstPrev,
    LPSTR                   pszCmdLine,
    int                     nCmdShow
)
{
    int                 nResult;
    HWND                hwnd;
    MSG                 msg;
    HACCEL              haccl;

    DbgInitialize(TRUE);

    //
    //  our documentation states that WinMain is supposed to return 0 if
    //  we do not enter our message loop--so assume the worst...
    //
    nResult = 0;

    //
    //  make our instance handle global for convenience..
    //
    ghinst = hinst;

    //
    //  init some stuff, create window, etc.. note the explicit cast of
    //  pszCmdLine--this is to mute a warning (and an ugly ifdef) when
    //  compiling for Unicode. see AppInit() for more details.
    //
    hwnd = AppInit(hinst, hinstPrev, (LPTSTR)pszCmdLine, nCmdShow);
    if (hwnd)
    {
        haccl = LoadAccelerators(hinst, ACCEL_APP);

        //
        //  dispatch messages
        //
        while (GetMessage(&msg, NULL, 0, 0))
        {
            //
            //  do all the special stuff required for this application
            //  when dispatching messages..
            //
            if (!TranslateAccelerator(hwnd, haccl, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        //
        //  return result of WM_QUIT message.
        //
        nResult = (int)msg.wParam;
    }

    //
    //  shut things down, clean up, etc.
    //
    nResult = AppExit(hinst, nResult);

    return (nResult);
} // WinMain()


ACMAPP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  acmapp.h
//
//  Description:
//      This is a sample application that demonstrates how to use the
//      Audio Compression Manager API's in Windows. This application is
//      also useful as an ACM CODEC driver test.
//
//
//==========================================================================;

#ifndef _INC_ACMAPP
#define _INC_ACMAPP                 // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Application Version Information:
//
//
//
//
//  NOTE! all string resources that will be used in app.rcv for the
//  version resource information *MUST* have an explicit \0 terminator!
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#if WINVER < 0x0400

#define APP_VERSION_MAJOR           3
#define APP_VERSION_MINOR           50
#define APP_VERSION_BUILD           0

#ifdef DEBUG
#define APP_ABOUTBOX_DESCRIPTION    "ACM Sample Application version 3.50 (debug)"
#else
#define APP_ABOUTBOX_DESCRIPTION    "ACM Sample Application version 3.50"
#endif
#define APP_ABOUTBOX_PRODUCTNAME    "Microsoft Windows NT"

#define APP_VERSION_STRING_RC       "Version 3.50\0"
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT\0"

#else

#define APP_VERSION_MAJOR           4
#define APP_VERSION_MINOR           0
#define APP_VERSION_BUILD           0

#ifdef DEBUG
#define APP_ABOUTBOX_DESCRIPTION    "ACM Sample Application version 4.00 (debug)"
#else
#define APP_ABOUTBOX_DESCRIPTION    "ACM Sample Application version 4.00"
#endif
#define APP_ABOUTBOX_PRODUCTNAME    "Microsoft Windows"

#define APP_VERSION_STRING_RC       "Version 4.00\0"
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows\0"

#endif  // WINVER

#define APP_VERSION_COMPANYNAME_RC  "Microsoft Corporation\0"
#define APP_VERSION_DESCRIPTION_RC  "ACM Sample Application\0"
#ifdef WIN32
#define APP_VERSION_NAME_RC         "acmapp32.exe\0"
#else
#define APP_VERSION_NAME_RC         "acmapp16.exe\0"
#endif
#define APP_VERSION_COPYRIGHT_RC    "Copyright 1992 - 1998 Microsoft Corp.\0"

//
//
//
#ifndef SIZEOF_WAVEFORMATEX
#define SIZEOF_WAVEFORMATEX(pwfx)   ((WAVE_FORMAT_PCM==(pwfx)->wFormatTag)?sizeof(PCMWAVEFORMAT):(sizeof(WAVEFORMATEX)+(pwfx)->cbSize))
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc defines for misc sizes and things...
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  bilingual. this allows the same identifier to be used in resource files
//  and code without having to decorate the id in your code.
//
#ifdef RC_INVOKED
    #define RCID(id)    id
#else
    #define RCID(id)    MAKEINTRESOURCE(id)
#endif

//
//  misc. defines
//
#define APP_MAX_APP_NAME_CHARS      30
#define APP_MAX_APP_NAME_BYTES      (APP_MAX_APP_NAME_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_RC_CHARS     512
#define APP_MAX_STRING_RC_BYTES     (APP_MAX_STRING_RC_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_ERROR_CHARS  512
#define APP_MAX_STRING_ERROR_BYTES  (APP_MAX_STRING_ERROR_CHARS * sizeof(TCHAR))

#define APP_MAX_STRING_CHARS        128
#define APP_MAX_STRING_BYTES        (APP_MAX_STRING_CHARS * sizeof(TCHAR))
#define APP_MAX_NUMBER_CHARS        144
#define APP_MAX_NUMBER_BYTES        (APP_MAX_NUMBER_CHARS * sizeof(TCHAR))

#define APP_MAX_FILE_PATH_CHARS     144
#define APP_MAX_FILE_PATH_BYTES     (APP_MAX_FILE_PATH_CHARS * sizeof(TCHAR))
#define APP_MAX_FILE_TITLE_CHARS    APP_MAX_FILE_PATH_CHARS
#define APP_MAX_FILE_TITLE_BYTES    (APP_MAX_FILE_TITLE_CHARS * sizeof(TCHAR))

#define APP_MAX_STRING_INT_CHARS    18
#define APP_MAX_STRING_INT_BYTES    (APP_MAX_STRING_INT_CHARS * sizeof(TCHAR))

#define APP_MAX_EXT_DEFAULT_CHARS   4
#define APP_MAX_EXT_DEFAULT_BYTES   (APP_MAX_EXT_DEFAULT_CHARS * sizeof(TCHAR))
#define APP_MAX_EXT_FILTER_CHARS    256
#define APP_MAX_EXT_FILTER_BYTES    (APP_MAX_EXT_FILTER_CHARS * sizeof(TCHAR))

#define APP_WINDOW_XOFFSET          CW_USEDEFAULT
#define APP_WINDOW_YOFFSET          CW_USEDEFAULT
#define APP_WINDOW_WIDTH            460             // CW_USEDEFAULT
#define APP_WINDOW_HEIGHT           400             // CW_USEDEFAULT

//
//  resource defines...
//
#define ICON_APP                    RCID(10)
#define ACCEL_APP                   RCID(15)

//
//  the application menu...
//
//  NOTE! for our Edit menu, we use the following defines from windows.h--
//  so don't reuse these defines for menu items!
//
//      #define WM_CUT      0x0300
//      #define WM_COPY     0x0301
//      #define WM_PASTE    0x0302
//      #define WM_CLEAR    0x0303
//      #define WM_UNDO     0x0304
//
#define MENU_APP                        RCID(20)
#define APP_MENU_ITEM_FILE              0
#define IDM_FILE_NEW                    1100
#define IDM_FILE_OPEN                   1101
#define IDM_FILE_SAVE                   1102
#define IDM_FILE_SAVEAS                 1103
#define IDM_FILE_SNDPLAYSOUND_PLAY      1104
#define IDM_FILE_SNDPLAYSOUND_STOP      1105
#define IDM_FILE_CONVERT                1106
#define IDM_FILE_CONVERT_ALL            1107
#define IDM_FILE_ABOUT                  1109
#define IDM_FILE_EXIT                   1110

#define APP_MENU_ITEM_EDIT              1
#define IDM_EDIT_SELECTALL              1200

#define APP_MENU_ITEM_VIEW              2
#define IDM_VIEW_SYSTEMINFO             1300
#define IDM_VIEW_ACM_DRIVERS            1301

#define APP_MENU_ITEM_PLAYER            3
#define IDM_PLAYRECORD                  1400

#define APP_MENU_ITEM_OPTIONS           4
#define IDM_OPTIONS_WAVEINDEVICE        1500
#define IDM_OPTIONS_WAVEOUTDEVICE       1501
#define IDM_OPTIONS_AUTOOPEN            1505
#define IDM_OPTIONS_DEBUGLOG            1506
#define IDM_OPTIONS_FONT                1509

#define IDM_UPDATE                      1600

//
//  the main window control id's...
//
#define IDD_ACMAPP_EDIT_DISPLAY                 200

//
//  misc dlg boxes...
//
#define DLG_ABOUT                               RCID(50)
#define IDC_STATIC                              -1
#define IDD_ABOUT_VERSION_OS                    100
#define IDD_ABOUT_VERSION_PLATFORM              101

#define DLG_AADRAGDROP                          RCID(55)

#define DLG_AADETAILS                           RCID(70)
#define IDD_AADETAILS_EDIT_DETAILS              100

#define DLG_AADRIVERS                           RCID(75)
#define IDD_AADRIVERS_STATIC_POSITION           100
#define IDD_AADRIVERS_LIST_DRIVERS              101
#define IDD_AADRIVERS_BTN_DETAILS               102
#define IDD_AADRIVERS_BTN_FORMATS               103
#define IDD_AADRIVERS_BTN_FILTERS               104
#define IDD_AADRIVERS_BTN_ABOUT                 105
#define IDD_AADRIVERS_BTN_CONFIG                106
#define IDD_AADRIVERS_BTN_ABLE                  107
#define IDD_AADRIVERS_BTN_TOTOP                 108

#define DLG_AADRIVERFORMATS                     RCID(80)
#define IDD_AADRIVERFORMATS_STATIC_POSITION     100
#define IDD_AADRIVERFORMATS_LIST_FORMATS        101
#define IDD_AADRIVERFORMATS_BTN_DETAILS         102

#define DLG_AAWAVEDEVICE                        RCID(85)
#define IDD_AAWAVEDEVICE_COMBO_DEVICE           100
#define IDD_AAWAVEDEVICE_EDIT_CAPABILITIES      101

#define DLG_AACHOOSER                           RCID(90)
#define IDD_AACHOOSER_TXT_FILE_INPUT            100
#define IDD_AACHOOSER_TXT_FORMAT_INPUT          101
#define IDD_AACHOOSER_EDIT_FILE_OUTPUT          110
#define IDD_AACHOOSER_BTN_BROWSE                111
#define IDD_AACHOOSER_COMBO_DRIVER              120
#define IDD_AACHOOSER_BTN_PROPERTIES            121
#define IDD_AACHOOSER_TXT_FORMAT                130
#define IDD_AACHOOSER_BTN_FORMAT                131
#define IDD_AACHOOSER_BTN_FORMAT_OPTIONS        132
#define IDD_AACHOOSER_CHECK_FILTER              140
#define IDD_AACHOOSER_TXT_FILTER                141
#define IDD_AACHOOSER_BTN_FILTER                142
#define IDD_AACHOOSER_BTN_FILTER_OPTIONS        143
#define IDD_AACHOOSER_CHECK_NONREALTIME         150
#define IDD_AACHOOSER_CHECK_ASYNC               151
#define IDD_AACHOOSER_TXT_TIME                  160
#define IDD_AACHOOSER_SCROLL_TIME               161
#define IDD_AACHOOSER_EDIT_DETAILS              170

#define DLG_AAFORMATENUM                        RCID(91)
#define IDD_AAFORMATENUM_CHECK_CONVERT          100
#define IDD_AAFORMATENUM_CHECK_SUGGEST          101
#define IDD_AAFORMATENUM_CHECK_HARDWARE         102
#define IDD_AAFORMATENUM_CHECK_INPUT            103
#define IDD_AAFORMATENUM_CHECK_OUTPUT           104
#define IDD_AAFORMATENUM_CHECK_WFORMATTAG       105
#define IDD_AAFORMATENUM_EDIT_WFORMATTAG        106
#define IDD_AAFORMATENUM_CHECK_NCHANNELS        107
#define IDD_AAFORMATENUM_EDIT_NCHANNELS         108
#define IDD_AAFORMATENUM_CHECK_NSAMPLESPERSEC   109
#define IDD_AAFORMATENUM_EDIT_NSAMPLESPERSEC    110
#define IDD_AAFORMATENUM_CHECK_WBITSPERSAMPLE   111
#define IDD_AAFORMATENUM_EDIT_WBITSPERSAMPLE    112
#define IDD_AAFORMATENUM_EDIT_NAVGBYTESPERSEC   113
#define IDD_AAFORMATENUM_EDIT_NBLOCKALIGN       114
#define IDD_AAFORMATENUM_EDIT_EXTRA             115

#define DLG_AAFILTERENUM                        RCID(92)
#define IDD_AAFILTERENUM_CHECK_DWFILTERTAG      100
#define IDD_AAFILTERENUM_EDIT_DWFILTERTAG       101

#define DLG_AAPROPERTIES                        RCID(93)
#define IDD_AAPROPERTIES_COMBO_SOURCE           100
#define IDD_AAPROPERTIES_BTN_SOURCE             101
#define IDD_AAPROPERTIES_COMBO_DESTINATION      102
#define IDD_AAPROPERTIES_BTN_DESTINATION        103

#define DLG_AAFORMATSTYLE                               RCID(94)
#define IDD_AAFORMATSTYLE_CHECK_SHOWHELP                100
#define IDD_AAFORMATSTYLE_CHECK_ENABLEHOOK              101
#define IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATE          102
#define IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATEHANDLE    103
#define IDD_AAFORMATSTYLE_CHECK_INITTOWFXSTRUCT         104

#define DLG_AAFILTERSTYLE                               RCID(95)
#define IDD_AAFILTERSTYLE_CHECK_SHOWHELP                100
#define IDD_AAFILTERSTYLE_CHECK_ENABLEHOOK              101
#define IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATE          102
#define IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATEHANDLE    103
#define IDD_AAFILTERSTYLE_CHECK_INITTOFILTERSTRUCT      104

#define DLG_AAFORMATCHOOSE_TEMPLATE             RCID(98)
#define DLG_AAFILTERCHOOSE_TEMPLATE             RCID(99)

#define DLG_AACONVERT                           RCID(100)
#define IDD_AACONVERT_TXT_INFILEPATH            100
#define IDD_AACONVERT_TXT_OUTFILEPATH           101
#define IDD_AACONVERT_TXT_STATUS                102

#define DLG_AAPLAYRECORD                        RCID(110)
#define IDD_AAPLAYRECORD_BTN_PLAY               100
#define IDD_AAPLAYRECORD_BTN_PAUSE              101
#define IDD_AAPLAYRECORD_BTN_STOP               102
#define IDD_AAPLAYRECORD_BTN_START              103
#define IDD_AAPLAYRECORD_BTN_END                104
#define IDD_AAPLAYRECORD_BTN_RECORD             105
#define IDD_AAPLAYRECORD_SCROLL_POSITION        110
#define IDD_AAPLAYRECORD_TXT_POSITION           115
#define IDD_AAPLAYRECORD_EDIT_COMMAND           120
#define IDD_AAPLAYRECORD_EDIT_RESULT            121

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  string resources
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define IDS_APP_NAME                100
#define IDS_FILE_UNTITLED           101

#define IDS_OFN_EXT_DEF             125
#define IDS_OFN_EXT_FILTER          126

#define IDS_ERROR_ACM_NOT_PRESENT   500
#define IDS_ERROR_ACM_TOO_OLD       501

#define IDS_ERROR_OPEN_FAILED       550

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//
//
//
typedef struct tACMAPPFILEDESC
{
    DWORD           fdwState;

    TCHAR           szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    TCHAR           szFilePath[APP_MAX_FILE_PATH_CHARS];

    DWORD           cbFileSize;
    UINT            uDosChangeDate;
    UINT            uDosChangeTime;
    DWORD           fdwFileAttributes;

    LPWAVEFORMATEX  pwfx;
    UINT            cbwfx;

    DWORD           dwDataBytes;
    DWORD           dwDataSamples;

} ACMAPPFILEDESC, *PACMAPPFILEDESC;

//
//  ACMAPPINST.fdwState flags
//
#define ACMAPPFILEDESC_STATEF_MODIFIED  0x80000000L

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  APPINIT.C -- Public helper functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

LRESULT FNGLOBAL AppCreate
(
    HWND                    hwnd,
    LPCREATESTRUCT          pcs
);

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND                    hwnd
);

LRESULT FNGLOBAL AppEndSession
(
    HWND                    hwnd,
    BOOL                    fEndSession
);

LRESULT FNGLOBAL AppClose
(
    HWND                    hwnd
);

HWND FNGLOBAL AppInit
(
    HINSTANCE               hinst,
    HINSTANCE               hinstPrev,
    LPTSTR                  pszCmdLine,
    int                     nCmdShow
);

int FNGLOBAL AppExit
(
    HINSTANCE               hinst,
    int                     nResult
);

LRESULT FNGLOBAL AppGetWindowsVersion
(
    PTSTR                   pszEnvironment,
    PTSTR                   pszPlatform
);

LRESULT FNGLOBAL AppWinIniChange
(
    HWND                    hwnd,
    LPCTSTR                 pszSection
);

HFONT FNGLOBAL AppChooseFont
(
    HWND                    hwnd,
    HFONT                   hFont,
    PLOGFONT                plf
);

BOOL FNEXPORT AboutDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNGLOBAL AppProfileWriteBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
);

BOOL FNGLOBAL AppProfileReadBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  APP.C -- Public helper functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

int FNCGLOBAL AppMsgBox
(
    HWND                    hwnd,
    UINT                    fuStyle,
    PTSTR                   pszFormat,
    ...
);

int FNCGLOBAL AppMsgBoxId
(
    HWND                    hwnd,
    UINT                    fuStyle,
    UINT                    uIdsFormat,
    ...
);

void FNGLOBAL AppHourGlass
(
    BOOL                    fHourGlass
);

BOOL FNGLOBAL AppYield
(
    HWND                    hwnd,
    BOOL                    fIsDialog
);

BOOL FNGLOBAL AppTitle
(
    HWND                    hwnd,
    PTSTR                   pszFileTitle
);

int FNCGLOBAL AppSetWindowText
(
    HWND                    hwnd,
    PTSTR                   pszFormat,
    ...
);

int FNCGLOBAL AppSetWindowTextId
(
    HWND                    hwnd,
    UINT                    uIdsFormat,
    ...
);

BOOL FNGLOBAL AppFormatBigNumber
(
    LPTSTR                  pszNumber,
    DWORD                   dw
);

BOOL FNGLOBAL AppFormatDosDateTime
(
    LPTSTR                  pszDateTime,
    UINT                    uDosDate,
    UINT                    uDosTime
);

void FNCGLOBAL AcmAppDebugLog
(
    PTSTR                   pszFormat,
    ...
);

int FNCGLOBAL MEditPrintF
(
    HWND                    hedit,
    PTSTR                   pszFormat,
    ...
);

BOOL FNGLOBAL AppGetFileTitle
(
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle
);

BOOL FNGLOBAL AppGetFileName
(
    HWND                    hwnd,
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle,
    UINT                    fuFlags
);

BOOL FNGLOBAL AppFileNew
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    BOOL                    fCreate
);

//
//  fuFlags for AppGetFileName()...
//
#define APP_GETFILENAMEF_OPEN       0x0000
#define APP_GETFILENAMEF_SAVE       0x0001

BOOL FNGLOBAL AppFileSave
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    BOOL                    fSaveAs
);

LRESULT FNEXPORT AppWndProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  file manipulation functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//
//
//
#ifndef WIN32
DWORD FNGLOBAL DosGetFileAttributes
(
    LPTSTR                  pszFilePath
);

#ifndef FILE_ATTRIBUTE_READONLY
#define FILE_ATTRIBUTE_READONLY         0x00000001
#define FILE_ATTRIBUTE_HIDDEN           0x00000002
#define FILE_ATTRIBUTE_SYSTEM           0x00000004
#define FILE_ATTRIBUTE_DIRECTORY        0x00000010
#define FILE_ATTRIBUTE_ARCHIVE          0x00000020
#define FILE_ATTRIBUTE_NORMAL           0x00000080
#define FILE_ATTRIBUTE_TEMPORARY        0x00000100
#define FILE_ATTRIBUTE_ATOMIC_WRITE     0x00000200
#define FILE_ATTRIBUTE_XACTION_WRITE    0x00000400
#endif
#endif

MMRESULT FNGLOBAL AcmAppFormatChoose
(
    HWND                    hwnd,
    LPWAVEFORMATEX          pwfx,
    UINT                    cbwfx,
    DWORD                   fdwStyle
);

BOOL FNGLOBAL AcmAppFileSaveModified
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppFileNew
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppFileOpen
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppFileSave
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd,
    PTSTR                   pszFilePath,
    PTSTR                   pszFileTitle,
    UINT                    fuSave
);

BOOL FNGLOBAL AcmAppFileConvert
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppMultiThreadedConvertAll
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppFileFilter
(
    HWND                    hwnd,
    PACMAPPFILEDESC         paafd
);

BOOL FNGLOBAL AcmAppDisplayFileProperties
(
    HWND                    hedit,
    PACMAPPFILEDESC         paafd
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FNEXPORT AcmAppDriversDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNGLOBAL AcmAppGetFormatDescription
(
    LPWAVEFORMATEX          pwfx,
    LPTSTR                  pszFormatTag,
    LPTSTR                  pszFormat
);

BOOL FNGLOBAL AcmAppGetFilterDescription
(
    LPWAVEFILTER            pwfltr,
    LPTSTR                  pszFilterTag,
    LPTSTR                  pszFilter
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FNEXPORT AcmAppSystemInfoDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNEXPORT AcmAppPlayRecord
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNEXPORT AcmAppWaveDeviceDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNGLOBAL AcmAppGetErrorString
(
    MMRESULT                mmr,
    LPTSTR                  pszError
);

//
//
//
#define WM_ACMAPP_ACM_NOTIFY        (WM_USER + 100)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FNGLOBAL AcmAppChooseFont
(
    HWND                    hwnd
);

//
//
//
//
typedef struct tAACONVERTDESC
{
    HACMDRIVERID        hadid;
    HACMDRIVER          had;
    HACMSTREAM          has;
    DWORD               fdwOpen;

    HMMIO               hmmioSrc;
    HMMIO               hmmioDst;

    MMCKINFO            ckDst;
    MMCKINFO            ckDstRIFF;

    UINT                uBufferTimePerConvert;

    TCHAR               szFilePathSrc[APP_MAX_FILE_PATH_CHARS];
    LPWAVEFORMATEX      pwfxSrc;
    LPBYTE              pbSrc;
    DWORD               dwSrcSamples;
    DWORD               cbSrcData;
    DWORD               cbSrcReadSize;
    TCHAR               szSrcFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
    TCHAR               szSrcFormat[ACMFORMATDETAILS_FORMAT_CHARS];

    TCHAR               szFilePathDst[APP_MAX_FILE_PATH_CHARS];
    LPWAVEFORMATEX      pwfxDst;
    LPBYTE              pbDst;
    DWORD               cbDstBufSize;
    TCHAR               szDstFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
    TCHAR               szDstFormat[ACMFORMATDETAILS_FORMAT_CHARS];

    BOOL                fApplyFilter;
    LPWAVEFILTER        pwfltr;
    TCHAR               szFilterTag[ACMFILTERTAGDETAILS_FILTERTAG_CHARS];
    TCHAR               szFilter[ACMFILTERDETAILS_FILTER_CHARS];

    ACMSTREAMHEADER     ash;

    DWORD               cTotalConverts;
    DWORD               dwTimeTotal;
    DWORD               dwTimeShortest;
    DWORD               dwShortestConvert;
    DWORD               dwTimeLongest;
    DWORD               dwLongestConvert;

} AACONVERTDESC, *PAACONVERTDESC;

BOOL FNEXPORT AcmAppDlgProcChooser
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  global variables, etc.
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  flags for gfuAppOptions
//
#define APP_OPTIONSF_AUTOOPEN   0x0001
#define APP_OPTIONSF_DEBUGLOG   0x0004

extern HINSTANCE            ghinst;
extern BOOL                 gfAcmAvailable;
extern UINT                 gfuAppOptions;
extern HFONT                ghfontApp;
extern HACMDRIVERID         ghadidNotify;

extern UINT                 guWaveInId;
extern UINT                 guWaveOutId;

extern TCHAR                gszNull[];
extern TCHAR                gszAppProfile[];
extern TCHAR                gszYes[];
extern TCHAR                gszNo[];

extern TCHAR                gszAppName[APP_MAX_APP_NAME_CHARS];
extern TCHAR                gszFileUntitled[APP_MAX_FILE_TITLE_CHARS];

extern TCHAR                gszInitialDirOpen[APP_MAX_FILE_PATH_CHARS];
extern TCHAR                gszInitialDirSave[APP_MAX_FILE_PATH_CHARS];

extern TCHAR                gszLastSaveFile[APP_MAX_FILE_PATH_CHARS];

extern ACMAPPFILEDESC       gaafd;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_ACMAPP


ACMTHUNK.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  acmthunk.c
//
//  Description:
//      This is a thunk layer to the Audio Compression Manager. It's
//      purpose is to allow an application to use the ACM only if it is
//      available (like under Win 3.1 and NT where the ACM may or may not
//      be installed).
//
//      There are two requirements for using this module:
//
//      1.  Compile and link with this module before linking to MMSYSTEM.LIB.
//          Do *NOT* link with MSACM.LIB.
//
//      2.  Before calling any other functions in ACM function set, call
//          acmThunkInitialize(). This will cause all dyna-linking to occur.
//
//      3.  Before exiting your application, call acmThunkTerminate().
//          This will unlink to the ACM and free allocated resources.
//
//      NOTE! this could be written more efficiently in Assembly by doing
//      a jump to the correct API in the ACM, but this would not be
//      portable (easily) on NT. So the arguments will be repushed on the
//      stack.. such is life.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>

//
//
//
#ifdef WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif

//==========================================================================;
//
//  Prototypes and String Defines for Dyna-Linking to the ACM
//
//
//==========================================================================;

#define ACMINST_NOT_PRESENT     NULL
#define ACMINST_TRY_LINKING     (HINSTANCE)(UINT)-1

static HINSTANCE    ghinstAcm   = ACMINST_TRY_LINKING;

#ifdef WIN32
TCHAR BCODE gszAcmModuleName[]  = TEXT("MSACM32.DLL");
#else
char BCODE  gszAcmModuleName[]  = "MSACM.DLL";
#endif

FARPROC    *gpafnAcmFunctions;

PSTR BCODE  gapszAcmFunctions[] =
{
    "acmGetVersion",
    "acmMetrics",

    "acmDriverEnum",
#ifdef WIN32
    "acmDriverDetailsW",
    "acmDriverDetailsA",
#else
    "acmDriverDetails",
#endif
#ifdef WIN32
    "acmDriverAddW",
    "acmDriverAddA",
#else
    "acmDriverAdd",
#endif
    "acmDriverRemove",
    "acmDriverOpen",
    "acmDriverClose",
    "acmDriverMessage",
    "acmDriverID",
    "acmDriverPriority",

#ifdef WIN32
    "acmFormatTagDetailsW",
    "acmFormatTagDetailsA",
#else
    "acmFormatTagDetails",
#endif
#ifdef WIN32
    "acmFormatTagEnumW",
    "acmFormatTagEnumA",
#else
    "acmFormatTagEnum",
#endif
#ifdef WIN32
    "acmFormatChooseW",
    "acmFormatChooseA",
#else
    "acmFormatChoose",
#endif
#ifdef WIN32
    "acmFormatDetailsW",
    "acmFormatDetailsA",
#else
    "acmFormatDetails",
#endif
#ifdef WIN32
    "acmFormatEnumW",
    "acmFormatEnumA",
#else
    "acmFormatEnum",
#endif
    "acmFormatSuggest",

#ifdef WIN32
    "acmFilterTagDetailsW",
    "acmFilterTagDetailsA",
#else
    "acmFilterTagDetails",
#endif
#ifdef WIN32
    "acmFilterTagEnumW",
    "acmFilterTagEnumA",
#else
    "acmFilterTagEnum",
#endif
#ifdef WIN32
    "acmFilterChooseW",
    "acmFilterChooseA",
#else
    "acmFilterChoose",
#endif
#ifdef WIN32
    "acmFilterDetailsW",
    "acmFilterDetailsA",
#else
    "acmFilterDetails",
#endif
#ifdef WIN32
    "acmFilterEnumW",
    "acmFilterEnumA",
#else
    "acmFilterEnum",
#endif

    "acmStreamOpen",
    "acmStreamClose",
    "acmStreamSize",
    "acmStreamConvert",
    "acmStreamReset",
    "acmStreamPrepareHeader",
    "acmStreamUnprepareHeader"
};

#ifdef WIN32
//
//  For Win32
//
enum
{
     ACMTHUNK_GETVERSION = 0,
     ACMTHUNK_METRICS,
     ACMTHUNK_DRIVERENUM,
     ACMTHUNK_DRIVERDETAILSW,
     ACMTHUNK_DRIVERDETAILSA,
     ACMTHUNK_DRIVERADDW,
     ACMTHUNK_DRIVERADDA,
     ACMTHUNK_DRIVERREMOVE,
     ACMTHUNK_DRIVEROPEN,
     ACMTHUNK_DRIVERCLOSE,
     ACMTHUNK_DRIVERMESSAGE,
     ACMTHUNK_DRIVERID,
     ACMTHUNK_DRIVERPRIORITY,
     ACMTHUNK_FORMATTAGDETAILSW,
     ACMTHUNK_FORMATTAGDETAILSA,
     ACMTHUNK_FORMATTAGENUMW,
     ACMTHUNK_FORMATTAGENUMA,
     ACMTHUNK_FORMATCHOOSEW,
     ACMTHUNK_FORMATCHOOSEA,
     ACMTHUNK_FORMATDETAILSW,
     ACMTHUNK_FORMATDETAILSA,
     ACMTHUNK_FORMATENUMW,
     ACMTHUNK_FORMATENUMA,
     ACMTHUNK_FORMATSUGGEST,
     ACMTHUNK_FILTERTAGDETAILSW,
     ACMTHUNK_FILTERTAGDETAILSA,
     ACMTHUNK_FILTERTAGENUMW,
     ACMTHUNK_FILTERTAGENUMA,
     ACMTHUNK_FILTERCHOOSEW,
     ACMTHUNK_FILTERCHOOSEA,
     ACMTHUNK_FILTERDETAILSW,
     ACMTHUNK_FILTERDETAILSA,
     ACMTHUNK_FILTERENUMW,
     ACMTHUNK_FILTERENUMA,
     ACMTHUNK_STREAMOPEN,
     ACMTHUNK_STREAMCLOSE,
     ACMTHUNK_STREAMSIZE,
     ACMTHUNK_STREAMCONVERT,
     ACMTHUNK_STREAMRESET,
     ACMTHUNK_STREAMPREPAREHEADER,
     ACMTHUNK_STREAMUNPREPAREHEADER,

     ACMTHUNK_MAX_FUNCTIONS
};

#ifdef _UNICODE
#define ACMTHUNK_DRIVERDETAILS    ACMTHUNK_DRIVERDETAILSW
#define ACMTHUNK_DRIVERADD    ACMTHUNK_DRIVERADDW
#define ACMTHUNK_FORMATTAGDETAILS   ACMTHUNK_FORMATTAGDETAILSW
#define ACMTHUNK_FORMATTAGENUM    ACMTHUNK_FORMATTAGENUMW
#define ACMTHUNK_FORMATCHOOSE    ACMTHUNK_FORMATCHOOSEW
#define ACMTHUNK_FORMATDETAILS    ACMTHUNK_FORMATDETAILSW
#define ACMTHUNK_FORMATENUM    ACMTHUNK_FORMATENUMW
#define ACMTHUNK_FILTERTAGDETAILS   ACMTHUNK_FILTERTAGDETAILSW
#define ACMTHUNK_FILTERTAGENUM    ACMTHUNK_FILTERTAGENUMW
#define ACMTHUNK_FILTERCHOOSE    ACMTHUNK_FILTERCHOOSEW
#define ACMTHUNK_FILTERDETAILS    ACMTHUNK_FILTERDETAILSW
#define ACMTHUNK_FILTERENUM    ACMTHUNK_FILTERENUMW

#else
#define ACMTHUNK_DRIVERDETAILS    ACMTHUNK_DRIVERDETAILSA
#define ACMTHUNK_DRIVERADD    ACMTHUNK_DRIVERADDA
#define ACMTHUNK_FORMATTAGDETAILS   ACMTHUNK_FORMATTAGDETAILSA
#define ACMTHUNK_FORMATTAGENUM    ACMTHUNK_FORMATTAGENUMA
#define ACMTHUNK_FORMATCHOOSE    ACMTHUNK_FORMATCHOOSEA
#define ACMTHUNK_FORMATDETAILS    ACMTHUNK_FORMATDETAILSA
#define ACMTHUNK_FORMATENUM    ACMTHUNK_FORMATENUMA
#define ACMTHUNK_FILTERTAGDETAILS   ACMTHUNK_FILTERTAGDETAILSA
#define ACMTHUNK_FILTERTAGENUM    ACMTHUNK_FILTERTAGENUMA
#define ACMTHUNK_FILTERCHOOSE    ACMTHUNK_FILTERCHOOSEA
#define ACMTHUNK_FILTERDETAILS    ACMTHUNK_FILTERDETAILSA
#define ACMTHUNK_FILTERENUM    ACMTHUNK_FILTERENUMA

#endif

#else     // if Win32
//
//  For Win16
//
enum
{
     ACMTHUNK_GETVERSION = 0,
     ACMTHUNK_METRICS,
     ACMTHUNK_DRIVERENUM,
     ACMTHUNK_DRIVERDETAILS,
     ACMTHUNK_DRIVERADD,
     ACMTHUNK_DRIVERREMOVE,
     ACMTHUNK_DRIVEROPEN,
     ACMTHUNK_DRIVERCLOSE,
     ACMTHUNK_DRIVERMESSAGE,
     ACMTHUNK_DRIVERID,
     ACMTHUNK_DRIVERPRIORITY,
     ACMTHUNK_FORMATTAGDETAILS,
     ACMTHUNK_FORMATTAGENUM,
     ACMTHUNK_FORMATCHOOSE,
     ACMTHUNK_FORMATDETAILS,
     ACMTHUNK_FORMATENUM,
     ACMTHUNK_FORMATSUGGEST,
     ACMTHUNK_FILTERTAGDETAILS,
     ACMTHUNK_FILTERTAGENUM,
     ACMTHUNK_FILTERCHOOSE,
     ACMTHUNK_FILTERDETAILS,
     ACMTHUNK_FILTERENUM,
     ACMTHUNK_STREAMOPEN,
     ACMTHUNK_STREAMCLOSE,
     ACMTHUNK_STREAMSIZE,
     ACMTHUNK_STREAMCONVERT,
     ACMTHUNK_STREAMRESET,
     ACMTHUNK_STREAMPREPAREHEADER,
     ACMTHUNK_STREAMUNPREPAREHEADER,

     ACMTHUNK_MAX_FUNCTIONS
};

#endif// if WIN32 else

#define ACMTHUNK_SIZE_TABLE_BYTES   (ACMTHUNK_MAX_FUNCTIONS * sizeof(FARPROC))

//==========================================================================;
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL acmThunkInitialize
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL acmThunkInitialize
(
    void
)
{
    DWORD (ACMAPI *pfnAcmGetVersion)
    (
        void
    );

    UINT            fuErrorMode;
    DWORD           dwVersion;
    UINT            u;

    //
    //  if we have already linked to the API's, then just succeed...
    //
    if (NULL != gpafnAcmFunctions)
    {
        //
        //  someone isn't satisfied with calling this API only once?
        //
        return (TRUE);
    }

    //
    //  if we have already tried to link to the ACM, then fail this
    //  call--it isn't present.
    //
    if (ACMINST_TRY_LINKING != ghinstAcm)
        return (FALSE);

    //
    //  try to get a handle on the ACM--if we cannot do this, then fail
    //
    fuErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);
    ghinstAcm = LoadLibrary(gszAcmModuleName);
    SetErrorMode(fuErrorMode);
    if (ghinstAcm < (HINSTANCE)HINSTANCE_ERROR)
    {
        ghinstAcm = ACMINST_NOT_PRESENT;
        return (FALSE);
    }

    (FARPROC)pfnAcmGetVersion = GetProcAddress(ghinstAcm, gapszAcmFunctions[ACMTHUNK_GETVERSION]);
    if (NULL == pfnAcmGetVersion)
    {
        FreeLibrary(ghinstAcm);
        ghinstAcm = ACMINST_NOT_PRESENT;

        return (FALSE);
    }

    //
    //  allocate our array of function pointers to the ACM... note that
    //  this is dynamically allocated so if the ACM is _not_ present,
    //  then this code and data takes up very little space.
    //
    gpafnAcmFunctions = (FARPROC *)LocalAlloc(LPTR, ACMTHUNK_SIZE_TABLE_BYTES);
    if (NULL == gpafnAcmFunctions)
    {
        FreeLibrary(ghinstAcm);
        ghinstAcm = ACMINST_NOT_PRESENT;

        return (FALSE);
    }

    gpafnAcmFunctions[ACMTHUNK_GETVERSION] = (FARPROC)pfnAcmGetVersion;

    //
    //  if the version of the ACM is *NOT* V2.00 or greater, then
    //  all other API's are unavailable--so don't waste time trying
    //  to link to them.
    //
    dwVersion = (* pfnAcmGetVersion)();
    if (0x0200 > HIWORD(dwVersion))
    {
        return (TRUE);
    }

    //
    //  yipee! the ACM V2.00 or greater appears to be installed and
    //  happy with us--so link to the rest of the nifty cool API's.
    //
    //  start at index 1 since we already linked to acmGetVersion above
    //
    for (u = 1; u < ACMTHUNK_MAX_FUNCTIONS; u++)
    {
        gpafnAcmFunctions[u] = GetProcAddress(ghinstAcm, gapszAcmFunctions[u]);
    }

    //
    //  finally, return success
    //
    return (TRUE);
} // acmThunkInitialize()

//--------------------------------------------------------------------------;
//
//  BOOL acmThunkTerminate
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL acmThunkTerminate
(
    void
)
{
    //
    //
    //
    if (NULL != gpafnAcmFunctions)
    {
        LocalFree((HLOCAL)gpafnAcmFunctions);

        gpafnAcmFunctions = NULL;

        FreeLibrary(ghinstAcm);
        ghinstAcm = ACMINST_TRY_LINKING;
    }

    return (TRUE);
} // acmThunkTerminate()

//==========================================================================;
//
//  General Information API's
//
//
//==========================================================================;

DWORD ACMAPI acmGetVersion
(
    void
)
{
    DWORD (ACMAPI *pfnAcmGetVersion)
    (
        void
    );

    DWORD           dwVersion;

    if (NULL == gpafnAcmFunctions)
        return (0L);

    (FARPROC)pfnAcmGetVersion = gpafnAcmFunctions[ACMTHUNK_GETVERSION];
    if (NULL == pfnAcmGetVersion)
        return (0L);

    dwVersion = (* pfnAcmGetVersion)();

    return (dwVersion);
} // acmGetVersion()

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmMetrics
(
    HACMOBJ                 hao,
    UINT                    uMetric,
    LPVOID                  pMetric
)
{
    MMRESULT (ACMAPI *pfnAcmMetrics)
    (
        HACMOBJ                 hao,
        UINT                    uMetric,
        LPVOID                  pMetric
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmMetrics = gpafnAcmFunctions[ACMTHUNK_METRICS];
    if (NULL == pfnAcmMetrics)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmMetrics)(hao, uMetric, pMetric);

    return (mmr);
} // acmMetrics()

//==========================================================================;
//
//  ACM Driver Management API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverEnum
(
    ACMDRIVERENUMCB         fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmDriverEnum)
    (
        ACMDRIVERENUMCB         fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverEnum = gpafnAcmFunctions[ACMTHUNK_DRIVERENUM];
    if (NULL == pfnAcmDriverEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverEnum)(fnCallback, dwInstance, fdwEnum);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverDetails
(
    HACMDRIVERID            hadid,
    LPACMDRIVERDETAILS      padd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmDriverDetails)
    (
        HACMDRIVERID            hadid,
        LPACMDRIVERDETAILS      padd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverDetails = gpafnAcmFunctions[ACMTHUNK_DRIVERDETAILS];
    if (NULL == pfnAcmDriverDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverDetails)(hadid, padd, fdwDetails);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmDriverDetailsA
(
    HACMDRIVERID            hadid,
    LPACMDRIVERDETAILSA     padd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmDriverDetails)
    (
        HACMDRIVERID            hadid,
        LPACMDRIVERDETAILSA     padd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverDetails = gpafnAcmFunctions[ACMTHUNK_DRIVERDETAILSA];
    if (NULL == pfnAcmDriverDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverDetails)(hadid, padd, fdwDetails);

    return (mmr);
}
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverAdd
(
    LPHACMDRIVERID          phadid,
    HINSTANCE               hinstModule,
    LPARAM                  lParam,
    DWORD                   dwPriority,
    DWORD                   fdwAdd
)
{
    MMRESULT (ACMAPI *pfnAcmDriverAdd)
    (
        LPHACMDRIVERID          phadid,
        HINSTANCE               hinstModule,
        LPARAM                  lParam,
        DWORD                   dwPriority,
        DWORD                   fdwAdd
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverAdd = gpafnAcmFunctions[ACMTHUNK_DRIVERADD];
    if (NULL == pfnAcmDriverAdd)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverAdd)(phadid, hinstModule, lParam, dwPriority, fdwAdd);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmDriverAddA
(
    LPHACMDRIVERID          phadid,
    HINSTANCE               hinstModule,
    LPARAM                  lParam,
    DWORD                   dwPriority,
    DWORD                   fdwAdd
)
{
    MMRESULT (ACMAPI *pfnAcmDriverAdd)
    (
        LPHACMDRIVERID          phadid,
        HINSTANCE               hinstModule,
        LPARAM                  lParam,
        DWORD                   dwPriority,
        DWORD                   fdwAdd
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverAdd = gpafnAcmFunctions[ACMTHUNK_DRIVERADDA];
    if (NULL == pfnAcmDriverAdd)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverAdd)(phadid, hinstModule, lParam, dwPriority, fdwAdd);

    return (mmr);
}
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverRemove
(
    HACMDRIVERID            hadid,
    DWORD                   fdwRemove
)
{
    MMRESULT (ACMAPI *pfnAcmDriverRemove)
    (
        HACMDRIVERID            hadid,
        DWORD                   fdwRemove
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverRemove = gpafnAcmFunctions[ACMTHUNK_DRIVERREMOVE];
    if (NULL == pfnAcmDriverRemove)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverRemove)(hadid, fdwRemove);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverOpen
(
    LPHACMDRIVER            phad,
    HACMDRIVERID            hadid,
    DWORD                   fdwOpen
)
{
    MMRESULT (ACMAPI *pfnAcmDriverOpen)
    (
        LPHACMDRIVER            phad,
        HACMDRIVERID            hadid,
        DWORD                   fdwOpen
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverOpen = gpafnAcmFunctions[ACMTHUNK_DRIVEROPEN];
    if (NULL == pfnAcmDriverOpen)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverOpen)(phad, hadid, fdwOpen);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverClose
(
    HACMDRIVER              had,
    DWORD                   fdwClose
)
{
    MMRESULT (ACMAPI *pfnAcmDriverClose)
    (
        HACMDRIVER              had,
        DWORD                   fdwClose
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverClose = gpafnAcmFunctions[ACMTHUNK_DRIVERCLOSE];
    if (NULL == pfnAcmDriverClose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverClose)(had, fdwClose);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

LRESULT ACMAPI acmDriverMessage
(
    HACMDRIVER              had,
    UINT                    uMsg,
    LPARAM                  lParam1,
    LPARAM                  lParam2
)
{
    LRESULT (ACMAPI *pfnAcmDriverMessage)
    (
        HACMDRIVER              had,
        UINT                    uMsg,
        LPARAM                  lParam1,
        LPARAM                  lParam2
    );

    LRESULT         lr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverMessage = gpafnAcmFunctions[ACMTHUNK_DRIVERMESSAGE];
    if (NULL == pfnAcmDriverMessage)
        return (MMSYSERR_ERROR);

    lr = (* pfnAcmDriverMessage)(had, uMsg, lParam1, lParam2);

    return (lr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverID
(
    HACMOBJ                 hao,
    LPHACMDRIVERID          phadid,
    DWORD                   fdwDriverId
)
{
    MMRESULT (ACMAPI *pfnAcmDriverId)
    (
        HACMOBJ                 hao,
        LPHACMDRIVERID          phadid,
        DWORD                   fdwDriverId
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverId = gpafnAcmFunctions[ACMTHUNK_DRIVERID];
    if (NULL == pfnAcmDriverId)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverId)(hao, phadid, fdwDriverId);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmDriverPriority
(
    HACMDRIVERID            hadid,
    DWORD                   dwPriority,
    DWORD                   fdwPriority
)
{
    MMRESULT (ACMAPI *pfnAcmDriverPriority)
    (
        HACMDRIVERID            hadid,
        DWORD                   dwPriority,
        DWORD                   fdwPriority
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmDriverPriority = gpafnAcmFunctions[ACMTHUNK_DRIVERPRIORITY];
    if (NULL == pfnAcmDriverPriority)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmDriverPriority)(hadid, dwPriority, fdwPriority);

    return (mmr);
}

//==========================================================================;
//
//  Format Tag Information API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatTagDetails
(
    HACMDRIVER              had,
    LPACMFORMATTAGDETAILS   paftd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFormatTagDetails)
    (
        HACMDRIVER              had,
        LPACMFORMATTAGDETAILS   paftd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatTagDetails = gpafnAcmFunctions[ACMTHUNK_FORMATTAGDETAILS];
    if (NULL == pfnAcmFormatTagDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatTagDetails)(had, paftd, fdwDetails);

    return (mmr);
} // acmFormatTagDetails()

#ifdef _UNICODE
MMRESULT ACMAPI acmFormatTagDetailsA
(
    HACMDRIVER              had,
    LPACMFORMATTAGDETAILSA  paftd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFormatTagDetails)
    (
        HACMDRIVER              had,
        LPACMFORMATTAGDETAILSA  paftd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatTagDetails = gpafnAcmFunctions[ACMTHUNK_FORMATTAGDETAILSA];
    if (NULL == pfnAcmFormatTagDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatTagDetails)(had, paftd, fdwDetails);

    return (mmr);
} // acmFormatTagDetails()
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatTagEnum
(
    HACMDRIVER              had,
    LPACMFORMATTAGDETAILS   paftd,
    ACMFORMATTAGENUMCB      fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFormatTagEnum)
    (
        HACMDRIVER              had,
        LPACMFORMATTAGDETAILS   paftd,
        ACMFORMATTAGENUMCB      fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatTagEnum = gpafnAcmFunctions[ACMTHUNK_FORMATTAGENUM];
    if (NULL == pfnAcmFormatTagEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatTagEnum)(had, paftd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
} // acmFormatTagEnum()

#ifdef _UNICODE
MMRESULT ACMAPI acmFormatTagEnumA
(
    HACMDRIVER              had,
    LPACMFORMATTAGDETAILSA  paftd,
    ACMFORMATTAGENUMCBA     fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFormatTagEnum)
    (
        HACMDRIVER              had,
        LPACMFORMATTAGDETAILSA  paftd,
        ACMFORMATTAGENUMCBA     fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatTagEnum = gpafnAcmFunctions[ACMTHUNK_FORMATTAGENUMA];
    if (NULL == pfnAcmFormatTagEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatTagEnum)(had, paftd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
} // acmFormatTagEnum()
#endif

//==========================================================================;
//
//  Format Information API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatChoose
(
    LPACMFORMATCHOOSE       pafmtc
)
{
    MMRESULT (ACMAPI *pfnAcmFormatChoose)
    (
        LPACMFORMATCHOOSE       pafmtc
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatChoose = gpafnAcmFunctions[ACMTHUNK_FORMATCHOOSE];
    if (NULL == pfnAcmFormatChoose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatChoose)(pafmtc);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmFormatChooseA
(
    LPACMFORMATCHOOSEA      pafmtc
)
{
    MMRESULT (ACMAPI *pfnAcmFormatChoose)
    (
        LPACMFORMATCHOOSEA      pafmtc
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatChoose = gpafnAcmFunctions[ACMTHUNK_FORMATCHOOSEA];
    if (NULL == pfnAcmFormatChoose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatChoose)(pafmtc);

    return (mmr);
}
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatDetails
(
    HACMDRIVER              had,
    LPACMFORMATDETAILS      pafd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFormatDetails)
    (
        HACMDRIVER              had,
        LPACMFORMATDETAILS      pafd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatDetails = gpafnAcmFunctions[ACMTHUNK_FORMATDETAILS];
    if (NULL == pfnAcmFormatDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatDetails)(had, pafd, fdwDetails);

    return (mmr);
} // acmFormatDetails()

#ifdef _UNICODE
MMRESULT ACMAPI acmFormatDetailsA
(
    HACMDRIVER              had,
    LPACMFORMATDETAILSA     pafd,
    DWORD                   fdwDetails

)
{
    MMRESULT (ACMAPI *pfnAcmFormatDetails)
    (
        HACMDRIVER              had,
        LPACMFORMATDETAILSA     pafd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatDetails = gpafnAcmFunctions[ACMTHUNK_FORMATDETAILSA];
    if (NULL == pfnAcmFormatDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatDetails)(had, pafd, fdwDetails);

    return (mmr);
} // acmFormatDetails()
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatEnum
(
    HACMDRIVER              had,
    LPACMFORMATDETAILS      pafd,
    ACMFORMATENUMCB         fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFormatEnum)
    (
        HACMDRIVER              had,
        LPACMFORMATDETAILS      pafd,
        ACMFORMATENUMCB         fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatEnum = gpafnAcmFunctions[ACMTHUNK_FORMATENUM];
    if (NULL == pfnAcmFormatEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatEnum)(had, pafd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmFormatEnumA
(
    HACMDRIVER              had,
    LPACMFORMATDETAILSA     pafd,
    ACMFORMATENUMCBA        fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFormatEnum)
    (
        HACMDRIVER              had,
        LPACMFORMATDETAILSA     pafd,
        ACMFORMATENUMCBA        fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatEnum = gpafnAcmFunctions[ACMTHUNK_FORMATENUMA];
    if (NULL == pfnAcmFormatEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatEnum)(had, pafd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
}
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFormatSuggest
(
    HACMDRIVER          had,
    LPWAVEFORMATEX      pwfxSrc,
    LPWAVEFORMATEX      pwfxDst,
    DWORD               cbwfxDst,
    DWORD               fdwSuggest
)
{
    MMRESULT (ACMAPI *pfnAcmFormatSuggest)
    (
        HACMDRIVER          had,
        LPWAVEFORMATEX      pwfxSrc,
        LPWAVEFORMATEX      pwfxDst,
        DWORD               cbwfxDst,
        DWORD               fdwSuggest
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFormatSuggest = gpafnAcmFunctions[ACMTHUNK_FORMATSUGGEST];
    if (NULL == pfnAcmFormatSuggest)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFormatSuggest)(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);

    return (mmr);
}

//==========================================================================;
//
//  Filter Tag Information API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFilterTagDetails
(
    HACMDRIVER              had,
    LPACMFILTERTAGDETAILS   paftd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFilterTagDetails)
    (
        HACMDRIVER              had,
        LPACMFILTERTAGDETAILS   paftd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterTagDetails = gpafnAcmFunctions[ACMTHUNK_FILTERTAGDETAILS];
    if (NULL == pfnAcmFilterTagDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterTagDetails)(had, paftd, fdwDetails);

    return (mmr);
} // acmFilterTagDetails()

#ifdef _UNICODE
MMRESULT ACMAPI acmFilterTagDetailsA
(
    HACMDRIVER              had,
    LPACMFILTERTAGDETAILSA  paftd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFilterTagDetails)
    (
        HACMDRIVER              had,
        LPACMFILTERTAGDETAILSA  paftd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterTagDetails = gpafnAcmFunctions[ACMTHUNK_FILTERTAGDETAILSA];
    if (NULL == pfnAcmFilterTagDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterTagDetails)(had, paftd, fdwDetails);

    return (mmr);
} // acmFilterTagDetails()
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFilterTagEnum
(
    HACMDRIVER              had,
    LPACMFILTERTAGDETAILS   paftd,
    ACMFILTERTAGENUMCB      fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFilterTagEnum)
    (
        HACMDRIVER              had,
        LPACMFILTERTAGDETAILS   paftd,
        ACMFILTERTAGENUMCB      fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterTagEnum = gpafnAcmFunctions[ACMTHUNK_FILTERTAGENUM];
    if (NULL == pfnAcmFilterTagEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterTagEnum)(had, paftd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
} // acmFilterTagEnum()

#ifdef _UNICODE
MMRESULT ACMAPI acmFilterTagEnumA
(
    HACMDRIVER              had,
    LPACMFILTERTAGDETAILSA  paftd,
    ACMFILTERTAGENUMCBA     fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFilterTagEnum)
    (
        HACMDRIVER              had,
        LPACMFILTERTAGDETAILSA  paftd,
        ACMFILTERTAGENUMCBA     fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterTagEnum = gpafnAcmFunctions[ACMTHUNK_FILTERTAGENUMA];
    if (NULL == pfnAcmFilterTagEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterTagEnum)(had, paftd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
} // acmFilterTagEnum()
#endif

//==========================================================================;
//
//  Filter Information API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFilterChoose
(
    LPACMFILTERCHOOSE       pafltrc
)
{
    MMRESULT (ACMAPI *pfnAcmFilterChoose)
    (
        LPACMFILTERCHOOSE       pafltrc
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterChoose = gpafnAcmFunctions[ACMTHUNK_FILTERCHOOSE];
    if (NULL == pfnAcmFilterChoose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterChoose)(pafltrc);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmFilterChooseA
(
    LPACMFILTERCHOOSEA      pafltrc
)
{
    MMRESULT (ACMAPI *pfnAcmFilterChoose)
    (
        LPACMFILTERCHOOSEA      pafltrc
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterChoose = gpafnAcmFunctions[ACMTHUNK_FILTERCHOOSEA];
    if (NULL == pfnAcmFilterChoose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterChoose)(pafltrc);

    return (mmr);
}
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFilterDetails
(
    HACMDRIVER              had,
    LPACMFILTERDETAILS      pafd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFilterDetails)
    (
        HACMDRIVER              had,
        LPACMFILTERDETAILS      pafd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterDetails = gpafnAcmFunctions[ACMTHUNK_FILTERDETAILS];
    if (NULL == pfnAcmFilterDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterDetails)(had, pafd, fdwDetails);

    return (mmr);
} // acmFilterDetails()

#ifdef _UNICODE
MMRESULT ACMAPI acmFilterDetailsA
(
    HACMDRIVER              had,
    LPACMFILTERDETAILSA     pafd,
    DWORD                   fdwDetails
)
{
    MMRESULT (ACMAPI *pfnAcmFilterDetails)
    (
        HACMDRIVER              had,
        LPACMFILTERDETAILSA     pafd,
        DWORD                   fdwDetails
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterDetails = gpafnAcmFunctions[ACMTHUNK_FILTERDETAILSA];
    if (NULL == pfnAcmFilterDetails)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterDetails)(had, pafd, fdwDetails);

    return (mmr);
} // acmFilterDetails()
#endif

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmFilterEnum
(
    HACMDRIVER              had,
    LPACMFILTERDETAILS      pafd,
    ACMFILTERENUMCB         fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFilterEnum)
    (
        HACMDRIVER              had,
        LPACMFILTERDETAILS      pafd,
        ACMFILTERENUMCB         fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterEnum = gpafnAcmFunctions[ACMTHUNK_FILTERENUM];
    if (NULL == pfnAcmFilterEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterEnum)(had, pafd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
}

#ifdef _UNICODE
MMRESULT ACMAPI acmFilterEnumA
(
    HACMDRIVER              had,
    LPACMFILTERDETAILSA     pafd,
    ACMFILTERENUMCBA        fnCallback,
    DWORD                   dwInstance,
    DWORD                   fdwEnum
)
{
    MMRESULT (ACMAPI *pfnAcmFilterEnum)
    (
        HACMDRIVER              had,
        LPACMFILTERDETAILSA     pafd,
        ACMFILTERENUMCBA        fnCallback,
        DWORD                   dwInstance,
        DWORD                   fdwEnum
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmFilterEnum = gpafnAcmFunctions[ACMTHUNK_FILTERENUMA];
    if (NULL == pfnAcmFilterEnum)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmFilterEnum)(had, pafd, fnCallback, dwInstance, fdwEnum);

    return (mmr);
}
#endif

//==========================================================================;
//
//  ACM Stream Management API's
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamOpen
(
    LPHACMSTREAM            phas,
    HACMDRIVER              had,
    LPWAVEFORMATEX          pwfxSrc,
    LPWAVEFORMATEX          pwfxDst,
    LPWAVEFILTER            pwfltr,
    DWORD                   dwCallback,
    DWORD                   dwInstance,
    DWORD                   fdwOpen
)
{
    MMRESULT (ACMAPI *pfnAcmStreamOpen)
    (
        LPHACMSTREAM            phas,
        HACMDRIVER              had,
        LPWAVEFORMATEX          pwfxSrc,
        LPWAVEFORMATEX          pwfxDst,
        LPWAVEFILTER            pwfltr,
        DWORD                   dwCallback,
        DWORD                   dwInstance,
        DWORD                   fdwOpen
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamOpen = gpafnAcmFunctions[ACMTHUNK_STREAMOPEN];
    if (NULL == pfnAcmStreamOpen)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamOpen)(phas, had, pwfxSrc, pwfxDst, pwfltr, dwCallback, dwInstance, fdwOpen);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamClose
(
    HACMSTREAM              has,
    DWORD                   fdwClose
)
{
    MMRESULT (ACMAPI *pfnAcmStreamClose)
    (
        HACMSTREAM              has,
        DWORD                   fdwClose
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamClose = gpafnAcmFunctions[ACMTHUNK_STREAMCLOSE];
    if (NULL == pfnAcmStreamClose)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamClose)(has, fdwClose);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamSize
(
    HACMSTREAM              has,
    DWORD                   cbInput,
    LPDWORD                 pdwOutputBytes,
    DWORD                   fdwSize
)
{
    MMRESULT (ACMAPI *pfnAcmStreamSize)
    (
        HACMSTREAM              has,
        DWORD                   cbInput,
        LPDWORD                 pdwOutputBytes,
        DWORD                   fdwSize
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamSize = gpafnAcmFunctions[ACMTHUNK_STREAMSIZE];
    if (NULL == pfnAcmStreamSize)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamSize)(has, cbInput, pdwOutputBytes, fdwSize);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamConvert
(
    HACMSTREAM              has,
    LPACMSTREAMHEADER       pash,
    DWORD                   fdwConvert
)
{
    MMRESULT (ACMAPI *pfnAcmStreamConvert)
    (
        HACMSTREAM              has,
        LPACMSTREAMHEADER       pash,
        DWORD                   fdwConvert
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamConvert = gpafnAcmFunctions[ACMTHUNK_STREAMCONVERT];
    if (NULL == pfnAcmStreamConvert)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamConvert)(has, pash, fdwConvert);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamReset
(
    HACMSTREAM              has,
    DWORD                   fdwReset
)
{
    MMRESULT (ACMAPI *pfnAcmStreamReset)
    (
        HACMSTREAM              has,
        DWORD                   fdwReset
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamReset = gpafnAcmFunctions[ACMTHUNK_STREAMRESET];
    if (NULL == pfnAcmStreamReset)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamReset)(has, fdwReset);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamPrepareHeader
(
    HACMSTREAM              has,
    LPACMSTREAMHEADER       pash,
    DWORD                   fdwPrepare
)
{
    MMRESULT (ACMAPI *pfnAcmStreamPrepareHeader)
    (
        HACMSTREAM              has,
        LPACMSTREAMHEADER       pash,
        DWORD                   fdwPrepare
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamPrepareHeader = gpafnAcmFunctions[ACMTHUNK_STREAMPREPAREHEADER];
    if (NULL == pfnAcmStreamPrepareHeader)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamPrepareHeader)(has, pash, fdwPrepare);

    return (mmr);
}

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

MMRESULT ACMAPI acmStreamUnprepareHeader
(
    HACMSTREAM              has,
    LPACMSTREAMHEADER       pash,
    DWORD                   fdwUnprepare
)
{
    MMRESULT (ACMAPI *pfnAcmStreamUnprepareHeader)
    (
        HACMSTREAM              has,
        LPACMSTREAMHEADER       pash,
        DWORD                   fdwUnprepare
    );

    MMRESULT        mmr;

    if (NULL == gpafnAcmFunctions)
        return (MMSYSERR_ERROR);

    (FARPROC)pfnAcmStreamUnprepareHeader = gpafnAcmFunctions[ACMTHUNK_STREAMUNPREPAREHEADER];
    if (NULL == pfnAcmStreamUnprepareHeader)
        return (MMSYSERR_ERROR);

    mmr = (* pfnAcmStreamUnprepareHeader)(has, pash, fdwUnprepare);

    return (mmr);
}


ACMTHUNK.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  acmthunk.h
//
//  Description:
//      Contains function prototypes for the thunking code in acmthunk.c.
//
//
//==========================================================================;

#ifndef _INC_ACMTHUNK
#define _INC_ACMTHUNK

#ifdef __cplusplus
extern "C"
{
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FAR PASCAL acmThunkInitialize
(
    void
);

BOOL FAR PASCAL acmThunkTerminate
(
    void
);

#ifdef __cplusplus
}
#endif
#endif  // _INC_ACMTHUNK


APPPORT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  appport.h
//
//  Description:
//      Win 16/32 Portability Stuff
//
//      This file contains common macros to help with writing code that
//      cross compiles between Win 32 and Win 16. This file should be
//      included _after_ windows.h and windowsx.h.
//
//==========================================================================;

#ifndef _INC_APPPORT
#define _INC_APPPORT                // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 32
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef WIN32
    #ifndef FNLOCAL
    #ifdef DEBUG
        #define FNLOCAL     _stdcall
        #define FNCLOCAL    _stdcall
    #else
        #define FNLOCAL     static _stdcall
        #define FNCLOCAL    static _stdcall
    #endif
        #define FNGLOBAL    _stdcall
        #define FNCGLOBAL   _stdcall
        #define FNWCALLBACK CALLBACK
        #define FNEXPORT    CALLBACK
    #endif

    #include <tchar.h>

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)    \
        ((DWORD)SendMessage((hwndCtl), EM_GETSEL, (WPARAM)pnS, (LPARAM)pnE))

    #define HUGE

    //
    //  for compiling Unicode
    //
    #ifdef UNICODE
        #define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)   sizeof(x)
    #endif

    #define BCODE
    #define BDATA
    #define BSTACK

    //
    //  win32 apps [usually] don't have to worry about 'huge' data
    //
    #if !defined hmemcpy
#define hmemcpymemcpy
    #endif
#endif // #ifdef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 16
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef WIN32
    #ifndef FNLOCAL
    #ifdef DEBUG
        #define FNLOCAL     NEAR PASCAL
        #define FNCLOCAL    NEAR _cdecl
    #else
        #define FNLOCAL     static NEAR PASCAL
        #define FNCLOCAL    static NEAR _cdecl
    #endif
        #define FNGLOBAL    FAR PASCAL
        #define FNCGLOBAL   FAR _cdecl
    #ifdef _WINDLL
        #define FNWCALLBACK FAR PASCAL _loadds
        #define FNEXPORT    FAR PASCAL _export
    #else
        #define FNWCALLBACK FAR PASCAL _export
        #define FNEXPORT    FAR PASCAL _export
    #endif
    #endif

    #ifndef _tcstod
        #define _tcstod     strtod
        #define _tcstol     strtol
        #define _tcstoul    strtoul
    #endif

    //
    //
    //
    //
    #ifndef NOXACTALLC
        #undef GlobalAllocPtr
        #undef GlobalReAllocPtr

        static __inline LPVOID GlobalExactLock(HGLOBAL hg, DWORD cb)
        {
            return (LPVOID)((hg) ? (LPVOID)((LPBYTE)GlobalLock(hg) + (GlobalSize(hg) - (cb))) : (LPVOID)NULL);
        }

        #define GlobalAllocPtr(flags, cb)   \
                    (GlobalExactLock(GlobalAlloc((flags), (cb)), (cb)))
        #define GlobalReAllocPtr(lp, cbNew, flags)  \
                    (GlobalUnlockPtr(lp), GlobalExactLock(GlobalReAlloc(GlobalPtrHandle(lp) , (cbNew), (flags)), (cbNew)))
    #endif

    //
    //
    //
    //
    #ifndef FIELD_OFFSET
    #define FIELD_OFFSET(type, field)    ((LONG)&(((type *)0)->field))
    #endif

    //
    //  based code makes since only in win 16 (to try and keep stuff out of
    //  our fixed data segment...
    //
    #define BCODE           _based(_segname("_CODE"))
    #define BDATA           _based(_segname("_DATA"))
    #define BSTACK          _based(_segname("_STACK"))

    #define HUGE            _huge

    #define UNALIGNED

    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif

    #ifndef _MMRESULT_
    #define _MMRESULT_
    typedef UINT        MMRESULT;
    #endif

    #define TEXT(a)         a
    #define SIZEOF(x)       sizeof(x)

    //
    //
    //
    #define CharNext        AnsiNext
    #define CharPrev        AnsiPrev

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)                        \
    {                                                               \
        DWORD   dw;                                                 \
        dw = (DWORD)SendMessage((hwndCtl), EM_GETSEL, 0, 0L);       \
        *pnE = (int)HIWORD(dw);                                     \
        *pnS = (int)LOWORD(dw);                                     \
    }

    //
    //  common message cracker macros available in windowx.h on NT--these
    //  should be added to the Win 16 windowsx.h and probably will be
    //  in the future.
    //
    //  there is a windowsx.h16 that ships with the NT PDK that defines
    //  these macros. so if that version is being used, don't redefine
    //  message crackers.
    //

#ifndef WM_CTLCOLORMSGBOX
    #define WM_CTLCOLORMSGBOX           0x0132
    #define WM_CTLCOLOREDIT             0x0133
    #define WM_CTLCOLORLISTBOX          0x0134
    #define WM_CTLCOLORBTN              0x0135
    #define WM_CTLCOLORDLG              0x0136
    #define WM_CTLCOLORSCROLLBAR        0x0137
    #define WM_CTLCOLORSTATIC           0x0138
#endif

#ifndef GET_WM_ACTIVATE_STATE
    #define GET_WM_ACTIVATE_STATE(wp, lp)           (wp)
    #define GET_WM_ACTIVATE_FMINIMIZED(wp, lp)      (BOOL)HIWORD(lp)
    #define GET_WM_ACTIVATE_HWND(wp, lp)            (HWND)LOWORD(lp)
    #define GET_WM_ACTIVATE_MPS(s, fmin, hwnd)      (WPARAM)(s), MAKELONG(hwnd, fmin)

    #define GET_WM_CHARTOITEM_CHAR(wp, lp)          (CHAR)(wp)
    #define GET_WM_CHARTOITEM_POS(wp, lp)           HIWORD(lp)
    #define GET_WM_CHARTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_CHARTOITEM_MPS(ch, pos, hwnd)    (WPARAM)(ch), MAKELONG(hwnd, pos)

    #define GET_WM_COMMAND_ID(wp, lp)               (wp)
    #define GET_WM_COMMAND_HWND(wp, lp)             (HWND)LOWORD(lp)
    #define GET_WM_COMMAND_CMD(wp, lp)              HIWORD(lp)
    #define GET_WM_COMMAND_MPS(id, hwnd, cmd)       (WPARAM)(id), MAKELONG(hwnd, cmd)

    #define GET_WM_CTLCOLOR_HDC(wp, lp, msg)        (HDC)(wp)
    #define GET_WM_CTLCOLOR_HWND(wp, lp, msg)       (HWND)LOWORD(lp)
    #define GET_WM_CTLCOLOR_TYPE(wp, lp, msg)       HIWORD(lp)
    #define GET_WM_CTLCOLOR_MPS(hdc, hwnd, type)    (WPARAM)(hdc), MAKELONG(hwnd, type)

    #define GET_WM_MENUSELECT_CMD(wp, lp)           (wp)
    #define GET_WM_MENUSELECT_FLAGS(wp, lp)         LOWORD(lp)
    #define GET_WM_MENUSELECT_HMENU(wp, lp)         (HMENU)HIWORD(lp)
    #define GET_WM_MENUSELECT_MPS(cmd, f, hmenu)    (WPARAM)(cmd), MAKELONG(f, hmenu)

    // Note: the following are for interpreting MDIclient to MDI child messages.
    #define GET_WM_MDIACTIVATE_FACTIVATE(hwnd, wp, lp)  (BOOL)(wp)
    #define GET_WM_MDIACTIVATE_HWNDDEACT(wp, lp)        (HWND)HIWORD(lp)
    #define GET_WM_MDIACTIVATE_HWNDACTIVATE(wp, lp)     (HWND)LOWORD(lp)

    // Note: the following is for sending to the MDI client window.
    #define GET_WM_MDIACTIVATE_MPS(f, hwndD, hwndA) (WPARAM)(hwndA), 0

    #define GET_WM_MDISETMENU_MPS(hmenuF, hmenuW)   0, MAKELONG(hmenuF, hmenuW)

    #define GET_WM_MENUCHAR_CHAR(wp, lp)            (CHAR)(wp)
    #define GET_WM_MENUCHAR_HMENU(wp, lp)           (HMENU)LOWORD(lp)
    #define GET_WM_MENUCHAR_FMENU(wp, lp)           (BOOL)HIWORD(lp)
    #define GET_WM_MENUCHAR_MPS(ch, hmenu, f)       (WPARAM)(ch), MAKELONG(hmenu, f)

    #define GET_WM_PARENTNOTIFY_MSG(wp, lp)         (wp)
    #define GET_WM_PARENTNOTIFY_ID(wp, lp)          HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_HWNDCHILD(wp, lp)   (HWND)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_X(wp, lp)           (INT)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_Y(wp, lp)           (INT)HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_MPS(msg, id, hwnd)  (WPARAM)(msg), MAKELONG(hwnd, id)
    #define GET_WM_PARENTNOTIFY2_MPS(msg, x, y)     (WPARAM)(msg), MAKELONG(x, y)

    #define GET_WM_VKEYTOITEM_CODE(wp, lp)          (wp)
    #define GET_WM_VKEYTOITEM_ITEM(wp, lp)          (INT)HIWORD(lp)
    #define GET_WM_VKEYTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_VKEYTOITEM_MPS(code, item, hwnd) (WPARAM)(code), MAKELONG(hwnd, item)

    #define GET_EM_SETSEL_START(wp, lp)             LOWORD(lp)
    #define GET_EM_SETSEL_END(wp, lp)               HIWORD(lp)
    #define GET_EM_SETSEL_MPS(iStart, iEnd)         0, MAKELONG(iStart, iEnd)

    #define GET_EM_LINESCROLL_MPS(vert, horz)       0, MAKELONG(vert, horz)

    #define GET_WM_CHANGECBCHAIN_HWNDNEXT(wp, lp)   (HWND)LOWORD(lp)

    #define GET_WM_HSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_HSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_HSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_HSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)

    #define GET_WM_VSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_VSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_VSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_VSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)
#endif

#endif // #ifndef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_APPPORT


DEBUG.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.c
//
//  Description:
//      This file contains code yanked from several places to provide debug
//      support that works in win 16 and win 32.
//
//
//==========================================================================;

#ifdef DEBUG

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include "debug.h"

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifndef WIN32
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
#endif

//
//
//
BOOL    __gfDbgEnabled          = TRUE;         // master enable
UINT    __guDbgLevel            = 0;            // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF
//
//  Description:
//
//
//  Arguments:
//      LPSTR szFormat:
//
//      va_list va:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF
(
    LPSTR                   szFormat,
    va_list                 va
)
{
    char                ach[DEBUG_MAX_LINE_LEN];
    BOOL                fDebugBreak = FALSE;
    BOOL                fPrefix     = TRUE;
    BOOL                fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch (*szFormat)
        {
            case '!':
                fDebugBreak = TRUE;
                szFormat++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFormat++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFormat++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
    {
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");
    }

#ifdef WIN32
    wvsprintfA(ach + lstrlenA(ach), szFormat, va);
#else
    wvsprintf(ach + lstrlenA(ach), szFormat, (LPSTR)va);
#endif

    if (fCRLF)
    {
        lstrcatA(ach, "\r\n");
    }

    OutputDebugStringA(ach);

    if (fDebugBreak)
    {
        DebugBreak();
    }
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf
//
//  Description:
//      dprintf() is called by the DPF() macro if DEBUG is defined at compile
//      time. It is recommended that you only use the DPF() macro to call
//      this function--so you don't have to put #ifdef DEBUG around all
//      of your code.
//
//  Arguments:
//      UINT uDbgLevel:
//
//      LPSTR szFormat:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf
(
    UINT                    uDbgLevel,
    LPSTR                   szFormat,
    ...
)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFormat);
    DbgVPrintF(szFormat, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (BOOL):
//      Returns the previous debugging state.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable
(
    BOOL                    fEnable
)
{
    BOOL                fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel
//
//  Description:
//
//
//  Arguments:
//      UINT uLevel:
//
//  Return (UINT):
//      Returns the previous debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel
(
    UINT                    uLevel
)
{
    UINT                uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgGetLevel
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (UINT):
//      Returns the current debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgGetLevel
(
    void
)
{
    return (__guDbgLevel);
} // DbgGetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (UINT):
//      Returns the debugging level that was set.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize
(
    BOOL                    fEnable
)
{
    UINT                uLevel;

    uLevel = GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, (UINT)-1);
    if ((UINT)-1 == uLevel)
    {
        //
        //  if the debug key is not present, then force debug output to
        //  be disabled. this way running a debug version of a component
        //  on a non-debugging machine will not generate output unless
        //  the debug key exists.
        //
        uLevel  = 0;
        fEnable = FALSE;
    }

    DbgSetLevel(uLevel);
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

#endif // #ifdef DEBUG


DEBUG.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.h
//
//  Description:
//
//
//
//==========================================================================;

#ifndef _INC_DEBUG
#define _INC_DEBUG
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "ACMAPP"    // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//  #pragma message(REMIND("this is a reminder"))
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define DEBUG_QUOTE(x)      #x
#define DEBUG_QQUOTE(y)     DEBUG_QUOTE(y)
#define REMIND(sz)          __FILE__ "(" DEBUG_QQUOTE(__LINE__) ") : " sz

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
#endif

#ifdef __cplusplus
}
#endif
#endif  // _INC_DEBUG


MULDIV32.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  muldiv32.h
//
//  Description:
//      math routines for 32 bit signed and unsiged numbers.
//
//      MulDiv32(a,b,c) = (a * b) / c         (round down, signed)
//
//      MulDivRD(a,b,c) = (a * b) / c         (round down, unsigned)
//      MulDivRN(a,b,c) = (a * b + c/2) / c   (round nearest, unsigned)
//      MulDivRU(a,b,c) = (a * b + c-1) / c   (round up, unsigned)
//
//==========================================================================;

#ifndef _INC_MULDIV32
#define _INC_MULDIV32

#ifndef INLINE
#define INLINE __inline
#endif

#ifdef WIN32

    //----------------------------------------------------------------------;
    //
    //  Win 32
    //
    //----------------------------------------------------------------------;

    #ifdef _X86_

        //
        //  Use 32-bit x86 assembly.
        //

        #pragma warning(disable:4035 4704)

        INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     imul    ebx              //  imul ebx
            _asm     idiv    ecx              //  idiv ecx
            _asm shld edx, eax, 16     //  shld edx, eax, 16

        } // MulDiv32()

        INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     mov     ebx,ecx          //  mov  ebx,ecx
            _asm     shr     ebx,1            //  sar  ebx,1
            _asm     add     eax,ebx          //  add  eax,ebx
            _asm     adc     edx,0            //  adc  edx,0
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDiv32()

        INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     mov     ebx,ecx          //  mov  ebx,ecx
            _asm     dec     ebx              //  dec  ebx
            _asm     add     eax,ebx          //  add  eax,ebx
            _asm     adc     edx,0            //  adc  edx,0
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDivRU32()

        INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDivRD32()

        #pragma warning(default:4035 4704)

    #else

        //
        //  Use C9 __int64 support for Daytona RISC platforms.
        //

        INLINE LONG MulDiv32( LONG a, LONG b, LONG c )
        {
            return (LONG)( Int32x32To64(a,b) / c );
        }

        INLINE DWORD MulDivRD( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( UInt32x32To64(a,b) / c );
        }

        INLINE DWORD MulDivRN( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( (UInt32x32To64(a,b)+c/2) / c );
        }

        INLINE DWORD MulDivRU( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( (UInt32x32To64(a,b)+c-1) / c );
        }

    #endif

#else

    //----------------------------------------------------------------------;
    //
    //  Win 16
    //
    //----------------------------------------------------------------------;

    #pragma warning(disable:4035 4704)

    //
    //  Compile for 16-bit - we can use x86 with proper opcode prefixes
    //    to get 32-bit instructions.
    //

    INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    imul    bx              //  imul ebx
        _asm _emit 0x66 _asm    idiv    cx              //  idiv ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDiv32()

    INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    mov     bx,cx           //  mov  ebx,ecx
        _asm _emit 0x66 _asm    shr     bx,1            //  sar  ebx,1
        _asm _emit 0x66 _asm    add     ax,bx           //  add  eax,ebx
        _asm _emit 0x66 _asm    adc     dx,0            //  adc  edx,0
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDiv32()

    INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    mov     bx,cx           //  mov  ebx,ecx
        _asm _emit 0x66 _asm    dec     bx              //  dec  ebx
        _asm _emit 0x66 _asm    add     ax,bx           //  add  eax,ebx
        _asm _emit 0x66 _asm    adc     dx,0            //  adc  edx,0
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDivRU32()

    INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDivRD32()

    #pragma warning(default:4035 4704)

#endif

//
//  some code references these by other names.
//
#define muldiv32    MulDivRN
#define muldivrd32  MulDivRD
#define muldivru32  MulDivRU

#endif  // _INC_MULDIV32


AACHOOSE.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  aachoose.c
//
//  Description:
//      Contains dialog procedures and user-interface functions which allow
//      the user to select ACM drivers and wave formats.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include <stdlib.h>

#include <mmreg.h>
#include <msacm.h>

#include "muldiv32.h"

#include "appport.h"
#include "waveio.h"
#include "acmapp.h"

#include "debug.h"

TCHAR           gszBogus[]      = TEXT("????");

TCHAR BCODE     gszAcmAppHelpFormat[] = TEXT("choo_win.hlp");
TCHAR BCODE     gszAcmAppHelpFilter[] = TEXT("fil_win.hlp");

LPTSTR          gpszAcmAppHelp;
UINT            guMsgHelp;

//==========================================================================;
//
//
//
//
//==========================================================================;

typedef struct tAAFORMATENUM {
    DWORD    fdwEnum;
    LPWAVEFORMATEX  pwfxEnum;
} AAFORMATENUM, NEAR *NPAAFORMATENUM, *PAAFORMATENUM, FAR *LPAAFORMATENUM;

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcFormatStyle
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcFormatStyle
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PACMFORMATCHOOSE    pafc;
    HFONT               hfont;
    UINT                uId;
    BOOL                f;
    DWORD               fdwStyle;

    pafc = (PACMFORMATCHOOSE)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            pafc = (PACMFORMATCHOOSE)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            hfont = ghfontApp;

            //
            //
            //
            fdwStyle = pafc->fdwStyle;

            f = (0 != (ACMFORMATCHOOSE_STYLEF_SHOWHELP & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFORMATSTYLE_CHECK_SHOWHELP, f);

            f = (0 != (ACMFORMATCHOOSE_STYLEF_ENABLEHOOK & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLEHOOK, f);

            f = (0 != (ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATE & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATE, f);

            f = (0 != (ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATEHANDLE, f);

            f = (0 != (ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFORMATSTYLE_CHECK_INITTOWFXSTRUCT, f);

            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                    fdwStyle = 0L;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATSTYLE_CHECK_SHOWHELP);
                    if (f) fdwStyle |= ACMFORMATCHOOSE_STYLEF_SHOWHELP;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLEHOOK);
                    if (f) fdwStyle |= ACMFORMATCHOOSE_STYLEF_ENABLEHOOK;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATE);
                    if (f) fdwStyle |= ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATSTYLE_CHECK_ENABLETEMPLATEHANDLE);
                    if (f) fdwStyle |= ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATSTYLE_CHECK_INITTOWFXSTRUCT);
                    if (f) fdwStyle |= ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;

                    pafc->fdwStyle = fdwStyle;

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcFormatStyle()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcFilterStyle
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcFilterStyle
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PACMFILTERCHOOSE    pafc;
    HFONT               hfont;
    UINT                uId;
    BOOL                f;
    DWORD               fdwStyle;

    pafc = (PACMFILTERCHOOSE)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            pafc = (PACMFILTERCHOOSE)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            hfont = ghfontApp;

            //
            //
            //
            fdwStyle = pafc->fdwStyle;

            f = (0 != (ACMFILTERCHOOSE_STYLEF_SHOWHELP & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFILTERSTYLE_CHECK_SHOWHELP, f);

            f = (0 != (ACMFILTERCHOOSE_STYLEF_ENABLEHOOK & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLEHOOK, f);

            f = (0 != (ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATE & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATE, f);

            f = (0 != (ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATEHANDLE & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATEHANDLE, f);

            f = (0 != (ACMFILTERCHOOSE_STYLEF_INITTOFILTERSTRUCT & fdwStyle));
            CheckDlgButton(hwnd, IDD_AAFILTERSTYLE_CHECK_INITTOFILTERSTRUCT, f);

            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                    fdwStyle = 0L;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERSTYLE_CHECK_SHOWHELP);
                    if (f) fdwStyle |= ACMFILTERCHOOSE_STYLEF_SHOWHELP;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLEHOOK);
                    if (f) fdwStyle |= ACMFILTERCHOOSE_STYLEF_ENABLEHOOK;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATE);
                    if (f) fdwStyle |= ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERSTYLE_CHECK_ENABLETEMPLATEHANDLE);
                    if (f) fdwStyle |= ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATEHANDLE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERSTYLE_CHECK_INITTOFILTERSTRUCT);
                    if (f) fdwStyle |= ACMFILTERCHOOSE_STYLEF_INITTOFILTERSTRUCT;

                    pafc->fdwStyle = fdwStyle;

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcFilterStyle()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcFilterEnum
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcFilterEnum
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    TCHAR               ach[14];
    PWAVEFILTER         pwfltr;
    HWND                hedit;
    HFONT               hfont;
    UINT                uId;
    BOOL                f;
    DWORD               fdwEnum;

    pwfltr = (PWAVEFILTER)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            pwfltr = (PWAVEFILTER)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            hfont = ghfontApp;

            //
            //  the initial fdwEnum flags are passed in fdwFilter
            //  because i'm too lazy to make another silly structure..
            //
            fdwEnum = pwfltr->fdwFilter;

            f = (0 != (ACM_FILTERENUMF_DWFILTERTAG & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFILTERENUM_CHECK_DWFILTERTAG, f);

            hedit = GetDlgItem(hwnd, IDD_AAFILTERENUM_EDIT_DWFILTERTAG);
            SetWindowFont(hedit, hfont, FALSE);
            AppSetWindowText(hedit, TEXT("%u"), pwfltr->dwFilterTag);

            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                    fdwEnum = 0L;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFILTERENUM_CHECK_DWFILTERTAG);
                    if (f) fdwEnum |= ACM_FILTERENUMF_DWFILTERTAG;

                    hedit = GetDlgItem(hwnd, IDD_AAFILTERENUM_EDIT_DWFILTERTAG);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfltr->dwFilterTag = _tcstoul(ach, NULL, 10);

                    pwfltr->fdwFilter = fdwEnum;

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcFilterEnum()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcFormatEnum
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcFormatEnum
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    TCHAR               ach[14];
    LPAAFORMATENUMpaafe;
    LPWAVEFORMATEX      pwfx;
    DWORD               fdwEnum;
    HWND                hedit;
    HFONT               hfont;
    UINT                uId;
    BOOL                f;

    paafe   = (LPAAFORMATENUM)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
    SetWindowLong(hwnd, DWL_USER, lParam);

    paafe   = (LPAAFORMATENUM)lParam;
    pwfx    = paafe->pwfxEnum;
    fdwEnum = paafe->fdwEnum;

    hfont = ghfontApp;

            f = (0 != (ACM_FORMATENUMF_WFORMATTAG & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_WFORMATTAG, f);

            f = (0 != (ACM_FORMATENUMF_NCHANNELS & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_NCHANNELS, f);

            f = (0 != (ACM_FORMATENUMF_NSAMPLESPERSEC & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_NSAMPLESPERSEC, f);

            f = (0 != (ACM_FORMATENUMF_WBITSPERSAMPLE & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_WBITSPERSAMPLE, f);

            f = (0 != (ACM_FORMATENUMF_CONVERT & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_CONVERT, f);

            f = (0 != (ACM_FORMATENUMF_SUGGEST & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_SUGGEST, f);

            f = (0 != (ACM_FORMATENUMF_HARDWARE & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_HARDWARE, f);

            f = (0 != (ACM_FORMATENUMF_INPUT & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_INPUT, f);

            f = (0 != (ACM_FORMATENUMF_OUTPUT & fdwEnum));
            CheckDlgButton(hwnd, IDD_AAFORMATENUM_CHECK_OUTPUT, f);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_WFORMATTAG);
            SetWindowFont(hedit, hfont, FALSE);
            AppSetWindowText(hedit, TEXT("%u"), pwfx->wFormatTag);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NCHANNELS);
            SetWindowFont(hedit, hfont, FALSE);
            AppSetWindowText(hedit, TEXT("%u"), pwfx->nChannels);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NSAMPLESPERSEC);
            SetWindowFont(hedit, hfont, FALSE);
            AppSetWindowText(hedit, TEXT("%lu"), pwfx->nSamplesPerSec);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_WBITSPERSAMPLE);
            SetWindowFont(hedit, hfont, FALSE);
    AppSetWindowText(hedit, TEXT("%u"), pwfx->wBitsPerSample);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NAVGBYTESPERSEC);
            SetWindowFont(hedit, hfont, FALSE);
    AppSetWindowText(hedit, TEXT("%lu"), pwfx->nAvgBytesPerSec);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NBLOCKALIGN);
            SetWindowFont(hedit, hfont, FALSE);
    AppSetWindowText(hedit, TEXT("%u"), pwfx->nBlockAlign);

            hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_EXTRA);
            SetWindowFont(hedit, hfont, FALSE);
    if ( (pwfx->wFormatTag != WAVE_FORMAT_PCM) && (pwfx->cbSize != 0) )
    {
LPTSTR lptstr;
DWORD  cch;
DWORD  cb;

cch = 1 + pwfx->cbSize * 3;
cb  = cch * sizeof(TCHAR);

lptstr = (LPTSTR)GlobalAllocPtr(GHND, cb);
if (NULL != lptstr)
{
    LPTSTR lptstrT;
    LPBYTE lpb;
    DWORD i;

    lptstrT = lptstr;
    lpb = ((LPBYTE)pwfx) + sizeof(*pwfx);

    for (i=0; i<pwfx->cbSize; i++)
    {
wsprintf(lptstrT, TEXT("%02X "), *lpb);
lptstrT += 3;
lpb++;
    }

    SetWindowText(hedit, lptstr);

    GlobalFreePtr(lptstr);
    lptstr = NULL;
}
    }
    //
    //TODO: Allow the extra data to be edited by the user.
    //
    SendMessage(hedit, EM_SETREADONLY, TRUE, 0L);

            return (TRUE);

        case WM_COMMAND:
    pwfx = paafe->pwfxEnum;
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:

                    fdwEnum = 0L;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_WFORMATTAG);
                    if (f) fdwEnum |= ACM_FORMATENUMF_WFORMATTAG;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_NCHANNELS);
                    if (f) fdwEnum |= ACM_FORMATENUMF_NCHANNELS;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_NSAMPLESPERSEC);
                    if (f) fdwEnum |= ACM_FORMATENUMF_NSAMPLESPERSEC;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_WBITSPERSAMPLE);
                    if (f) fdwEnum |= ACM_FORMATENUMF_WBITSPERSAMPLE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_CONVERT);
                    if (f) fdwEnum |= ACM_FORMATENUMF_CONVERT;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_SUGGEST);
                    if (f) fdwEnum |= ACM_FORMATENUMF_SUGGEST;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_HARDWARE);
                    if (f) fdwEnum |= ACM_FORMATENUMF_HARDWARE;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_INPUT);
                    if (f) fdwEnum |= ACM_FORMATENUMF_INPUT;

                    f = IsDlgButtonChecked(hwnd, IDD_AAFORMATENUM_CHECK_OUTPUT);
                    if (f) fdwEnum |= ACM_FORMATENUMF_OUTPUT;

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_WFORMATTAG);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->wFormatTag = (WORD)_tcstoul(ach, NULL, 10);

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NCHANNELS);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->nChannels = (WORD)_tcstoul(ach, NULL, 10);

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NSAMPLESPERSEC);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->nSamplesPerSec = _tcstoul(ach, NULL, 10);

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_WBITSPERSAMPLE);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->wBitsPerSample = (WORD)_tcstoul(ach, NULL, 10);

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NAVGBYTESPERSEC);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->nAvgBytesPerSec = _tcstoul(ach, NULL, 10);

                    hedit = GetDlgItem(hwnd, IDD_AAFORMATENUM_EDIT_NBLOCKALIGN);
                    Edit_GetText(hedit, ach, SIZEOF(ach));
                    pwfx->nBlockAlign = (WORD)_tcstoul(ach, NULL, 10);

    paafe->fdwEnum = fdwEnum;

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcFormatEnum()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcProperties
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcProperties
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    MMRESULT            mmr;
    TCHAR               ach[14];
    PAACONVERTDESC      paacd;
    HWND                hcb;
    HFONT               hfont;
    UINT                uId;
    DWORD               cb;

    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            paacd = (PAACONVERTDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            hfont = ghfontApp;

            hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_SOURCE);
            SetWindowFont(hcb, hfont, FALSE);

            wsprintf(ach, TEXT("%lu"), paacd->cbSrcReadSize);
            ComboBox_AddString(hcb, ach);

            wsprintf(ach, TEXT("%lu"), paacd->cbSrcData);
            ComboBox_AddString(hcb, ach);

            wsprintf(ach, TEXT("%u"), paacd->pwfxSrc->nBlockAlign);
            ComboBox_AddString(hcb, ach);

            ComboBox_AddString(hcb, TEXT("1"));
            ComboBox_AddString(hcb, TEXT("2147483648"));
            ComboBox_AddString(hcb, TEXT("4294967295"));

            ComboBox_SetCurSel(hcb, 0);

            mmr = acmStreamSize(paacd->has,
                                paacd->cbSrcReadSize,
                                &paacd->cbDstBufSize,
                                ACM_STREAMSIZEF_SOURCE);

            hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_DESTINATION);
            SetWindowFont(hcb, hfont, FALSE);

            wsprintf(ach, TEXT("%lu"), paacd->cbDstBufSize);
            ComboBox_AddString(hcb, ach);

            wsprintf(ach, TEXT("%u"), paacd->pwfxDst->nBlockAlign);
            ComboBox_AddString(hcb, ach);

            ComboBox_AddString(hcb, TEXT("1"));
            ComboBox_AddString(hcb, TEXT("2147483648"));
            ComboBox_AddString(hcb, TEXT("4294967295"));

            ComboBox_SetCurSel(hcb, 0);

            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDD_AAPROPERTIES_BTN_SOURCE:
                    hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_SOURCE);
                    Edit_GetText(hcb, ach, SIZEOF(ach));
                    cb = _tcstoul(ach, NULL, 10);

                    mmr = acmStreamSize(paacd->has, cb, &cb, ACM_STREAMSIZEF_SOURCE);

                    wsprintf(ach, TEXT("%lu"), cb);

                    hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_DESTINATION);
                    Edit_SetText(hcb, ach);
                    break;

                case IDD_AAPROPERTIES_BTN_DESTINATION:
                    hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_DESTINATION);
                    Edit_GetText(hcb, ach, SIZEOF(ach));
                    cb = _tcstoul(ach, NULL, 10);

                    mmr = acmStreamSize(paacd->has, cb, &cb, ACM_STREAMSIZEF_DESTINATION);

                    wsprintf(ach, TEXT("%lu"), cb);

                    hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_SOURCE);
                    Edit_SetText(hcb, ach);
                    break;

                case IDOK:
                    hcb = GetDlgItem(hwnd, IDD_AAPROPERTIES_COMBO_SOURCE);
                    Edit_GetText(hcb, ach, SIZEOF(ach));
                    paacd->cbSrcReadSize = _tcstoul(ach, NULL, 10);

                    // -- fall through -- //

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcProperties()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserFormatSuggest
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserFormatSuggest
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd
)
{
    MMRESULT            mmr;
    LPWAVEFORMATEX      pwfx;
    DWORD               cbwfx;
    DWORD               cbwfxSrc;

    //
    //  this should never fail
    //
    mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &cbwfx);
    if (MMSYSERR_NOERROR != mmr)
    {
        DPF(0, "!AcmAppChooserFormatSuggest() acmMetrics failed mmr=%u!", mmr);
        return (FALSE);
    }

    //
    //  just in case no ACM driver is installed for the source format and
    //  the source has a larger format size than the largest enabled ACM
    //  driver...
    //
    cbwfxSrc = SIZEOF_WAVEFORMATEX(paacd->pwfxSrc);
    cbwfx    = max(cbwfx, cbwfxSrc);

    pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, cbwfx);
    if (NULL == pwfx)
    {
        DPF(0, "!AcmAppChooserFormatSuggest() GlobalAllocPtr(%lu) failed!", cbwfx);
        return (FALSE);
    }

    //
    //  'suggest anything'
    //
    mmr = acmFormatSuggest(NULL, paacd->pwfxSrc, pwfx, cbwfx, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  TEXT("AcmAppChooserFormatSuggest() there is no suggested destination format. Defaulting to source format."));

        _fmemcpy(pwfx, paacd->pwfxSrc, (UINT)cbwfxSrc);
    }

    //
    //
    //
    if (NULL != paacd->pwfxDst)
    {
        GlobalFreePtr(paacd->pwfxDst);
    }

    paacd->pwfxDst = pwfx;
    AcmAppGetFormatDescription(pwfx, paacd->szDstFormatTag, paacd->szDstFormat);

    return (TRUE);
} // AcmAppChooserFormatSuggest()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserSaveFile
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserSaveFile
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd
)
{
    TCHAR               szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR               szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    HWND                hedit;
    BOOL                f;

    hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_FILE_OUTPUT);
    Edit_GetText(hedit, paacd->szFilePathDst, SIZEOF(paacd->szFilePathDst));

    lstrcpy(szFilePath, paacd->szFilePathDst);

    f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GETFILENAMEF_SAVE);
    if (f)
    {
        lstrcpy(paacd->szFilePathDst, szFilePath);
        Edit_SetText(hedit, paacd->szFilePathDst);
    }

    return (f);
} // AcmAppChooserSaveFile()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserProperties
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserProperties
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd
)
{
    MMRESULT            mmr;
    BOOL                f;

    //
    //
    //
    if (NULL != paacd->hadid)
    {
        mmr = acmDriverOpen(&paacd->had, paacd->hadid, 0L);
        if (MMSYSERR_NOERROR != mmr)
        {
            return (FALSE);
        }
    }

    mmr = acmStreamOpen(&paacd->has,
                        paacd->had,
                        paacd->pwfxSrc,
                        paacd->pwfxDst,
                        paacd->fApplyFilter ? paacd->pwfltr : (LPWAVEFILTER)NULL,
                        0L,
                        0L,
                        paacd->fdwOpen);

    if (MMSYSERR_NOERROR == mmr)
    {
        f = DialogBoxParam(ghinst,
                            DLG_AAPROPERTIES,
                            hwnd,
                            AcmAppDlgProcProperties,
                            (LPARAM)(UINT)paacd);

        acmStreamClose(paacd->has, 0L);
        paacd->has = NULL;
    }

    if (NULL != paacd->had)
    {
        acmDriverClose(paacd->had, 0L);
        paacd->had = NULL;
    }

    return (f);
} // AcmAppChooserProperties()

//--------------------------------------------------------------------------;
//
//  UINT AcmAppChooserFormatHook

//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (UINT):
//
//
//--------------------------------------------------------------------------;

UINT FNWCALLBACK AcmAppChooserFormatHook
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    static BOOL         fVerify;
    LPWAVEFORMATEX      pwfx;
    WAVEFORMATEX        wfx;
    TCHAR               szFormatTag[ACMFORMATTAGDETAILS_FORMATTAG_CHARS];
    TCHAR               szFormat[ACMFORMATDETAILS_FORMAT_CHARS];
    PAACONVERTDESC      paacd;
    UINT                uId;
    int                 n;
    BOOL                f;

    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            paacd = (PAACONVERTDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            DPF(1, "AcmAppChooserFormatHook(%.04Xh, WM_INITDIALOG, %u, %lu): %.04Xh",
                hwnd, wParam, lParam, paacd);

            fVerify = TRUE;

            return (TRUE);

        case MM_ACM_FORMATCHOOSE:
            switch (wParam)
            {
                case FORMATCHOOSE_FORMATTAG_VERIFY:
                    if (!fVerify)
                        break;

                    wfx.wFormatTag = LOWORD(lParam);

                    AcmAppGetFormatDescription(&wfx, szFormatTag, NULL);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add format tag [%lu], '%s'?"),
                                    lParam, (LPTSTR)szFormatTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);

                case FORMATCHOOSE_FORMAT_VERIFY:
                    if (!fVerify)
                        break;

                    pwfx = (LPWAVEFORMATEX)lParam;

                    AcmAppGetFormatDescription(pwfx, szFormatTag, szFormat);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add format '%s' for format tag [%u], '%s'?"),
                                    (LPTSTR)szFormat,
                                    pwfx->wFormatTag,
                                    (LPTSTR)szFormatTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);

                case FORMATCHOOSE_CUSTOM_VERIFY:
                    if (!fVerify)
                        break;

                    pwfx = (LPWAVEFORMATEX)lParam;

                    AcmAppGetFormatDescription(pwfx, szFormatTag, szFormat);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add CUSTOM format '%s' for format tag [%u], '%s'?"),
                                    (LPTSTR)szFormat,
                                    pwfx->wFormatTag,
                                    (LPTSTR)szFormatTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);
            }
            break;

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                case IDCANCEL:
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppChooserFormatHook()

//--------------------------------------------------------------------------;
//
//  UINT AcmAppChooserFilterHook
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (UINT):
//
//
//--------------------------------------------------------------------------;

UINT FNWCALLBACK AcmAppChooserFilterHook
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    static BOOL         fVerify;
    WAVEFILTER          wfltr;
    LPWAVEFILTER        pwfltr;
    TCHAR               szFilterTag[ACMFILTERTAGDETAILS_FILTERTAG_CHARS];
    TCHAR               szFilter[ACMFILTERDETAILS_FILTER_CHARS];
    PAACONVERTDESC      paacd;
    UINT                uId;
    int                 n;
    BOOL                f;

    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
            paacd = (PAACONVERTDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            DPF(1, "AcmAppChooserFilterHook(%.04Xh, WM_INITDIALOG, %u, %lu): %.04Xh",
                hwnd, wParam, lParam, paacd);

            fVerify = TRUE;

            return (TRUE);

        case MM_ACM_FILTERCHOOSE:
            switch (wParam)
            {
                case FILTERCHOOSE_FILTERTAG_VERIFY:
                    if (!fVerify)
                        break;

                    wfltr.dwFilterTag = lParam;

                    AcmAppGetFilterDescription(&wfltr, szFilterTag, NULL);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add filter tag [%lu], '%s'?"),
                                    lParam, (LPTSTR)szFilterTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);

                case FILTERCHOOSE_FILTER_VERIFY:
                    if (!fVerify)
                        break;

                    pwfltr = (LPWAVEFILTER)lParam;

                    AcmAppGetFilterDescription(pwfltr, szFilterTag, szFilter);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add filter '%s' for filter tag [%lu], '%s'?"),
                                    (LPTSTR)szFilter,
                                    pwfltr->dwFilterTag,
                                    (LPTSTR)szFilterTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);

                case FILTERCHOOSE_CUSTOM_VERIFY:
                    if (!fVerify)
                        break;

                    pwfltr = (LPWAVEFILTER)lParam;

                    AcmAppGetFilterDescription(pwfltr, szFilterTag, szFilter);

                    n = AppMsgBox(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION,
                                    TEXT("Add CUSTOM filter '%s' for filter tag [%lu], '%s'?"),
                                    (LPTSTR)szFilter,
                                    pwfltr->dwFilterTag,
                                    (LPTSTR)szFilterTag);

                    fVerify = (IDCANCEL != n);

                    if (!fVerify)
                        break;

                    f = (IDYES == n);

                    SetWindowLong(hwnd, DWL_MSGRESULT, f);
                    return (TRUE);
            }
            break;

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                case IDCANCEL:
                    break;
            }
            break;
    }

    return (FALSE);
} // AcmAppChooserFilterHook()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserFormat
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//      BOOL fOptions:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserFormat
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd,
    BOOL                    fOptions
)
{
    ACMFORMATCHOOSE    afc;
    MMRESULT    mmr;
    LPWAVEFORMATEX    pwfx;
    DWORD    cbwfx;
    DWORD    cbwfxSrc;
    DWORD    fdwSuggest;
    DWORD    fdwStyle;
    DWORD    fdwEnum;
    LPWAVEFORMATEX    pwfxEnum;
    AAFORMATENUM    aafe;
    BOOL    f;
    HRSRC    hrsrc;

    pwfxEnum   = NULL;

    //
    //
    //
    fdwSuggest = 0L;
    fdwStyle   = 0L;
    fdwEnum    = 0L;

    //
    //  this should never fail
    //
    mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &cbwfx);
    if (MMSYSERR_NOERROR != mmr)
    {
        DPF(0, "!AcmAppChooserFormat() acmMetrics failed mmr=%u!", mmr);
        return (FALSE);
    }

    //
    //  just in case no ACM driver is installed for the source format and
    //  the source has a larger format size than the largest enabled ACM
    //  driver...
    //
    cbwfxSrc = SIZEOF_WAVEFORMATEX(paacd->pwfxSrc);
    cbwfx    = max(cbwfx, cbwfxSrc);

    pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, cbwfx);
    if (NULL == pwfx)
    {
        DPF(0, "!AcmAppChooserFormat() GlobalAllocPtr(%lu) failed!", cbwfx);
        return (FALSE);
    }

    pwfxEnum = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, cbwfx);
    if (NULL == pwfxEnum)
    {
        DPF(0, "!AcmAppChooserFormat() GlobalAllocPtr(%lu) failed!", cbwfx);
GlobalFreePtr(pwfx);
        return (FALSE);
    }

    _fmemcpy(pwfxEnum, paacd->pwfxSrc, (UINT)cbwfxSrc);

    //
    //
    //
    if (fOptions)
    {
aafe.fdwEnum = fdwEnum;
aafe.pwfxEnum = pwfxEnum;

        f = DialogBoxParam(ghinst,
                           DLG_AAFORMATENUM,
                           hwnd,
                           AcmAppDlgProcFormatEnum,
                           (LPARAM)(LPAAFORMATENUM)&aafe);
        if (!f)
        {
    GlobalFreePtr(pwfxEnum);
    GlobalFreePtr(pwfx);
            return (FALSE);
        }

        fdwEnum = aafe.fdwEnum;

    }

    if (fOptions)
    {
        afc.fdwStyle = fdwStyle;

        f = DialogBoxParam(ghinst,
                           DLG_AAFORMATSTYLE,
                           hwnd,
                           AcmAppDlgProcFormatStyle,
                           (LPARAM)(UINT)&afc);
        if (!f)
        {
            return (FALSE);
        }

        fdwStyle = afc.fdwStyle;
    }

    //
    //
    //
    //
    if (0 != (fdwStyle & ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT))
    {
        if (NULL != paacd->pwfxDst)
        {
            _fmemcpy(pwfx, paacd->pwfxDst, SIZEOF_WAVEFORMATEX(paacd->pwfxDst));
        }
        else
        {
            _fmemcpy(pwfx, paacd->pwfxSrc, (UINT)cbwfxSrc);
        }
    }

    //
    //
    //
    //
    if (0 == (ACMFORMATCHOOSE_STYLEF_SHOWHELP & fdwStyle))
    {
        guMsgHelp = 0;
    }
    else
    {
        guMsgHelp = RegisterWindowMessage(ACMHELPMSGSTRING);
        if (0 == guMsgHelp)
        {
            fdwStyle &= ~ACMFORMATCHOOSE_STYLEF_SHOWHELP;
        }
        else
        {
            gpszAcmAppHelp = gszAcmAppHelpFormat;
        }
    }

    //
    //  initialize the ACMFORMATCHOOSE members
    //
    memset(&afc, 0, sizeof(afc));

    afc.cbStruct        = sizeof(afc);
    afc.fdwStyle        = fdwStyle;
    afc.hwndOwner       = hwnd;
    afc.pwfx            = pwfx;
    afc.cbwfx           = cbwfx;
    afc.pszTitle        = TEXT("Destination Format Choice");

    afc.szFormatTag[0]  = '\0';
    afc.szFormat[0]     = '\0';
    afc.pszName         = NULL;
    afc.cchName         = 0;

    afc.fdwEnum         = fdwEnum;
    if (0L == (afc.fdwEnum & (ACM_FORMATENUMF_WFORMATTAG |
                              ACM_FORMATENUMF_NCHANNELS |
                              ACM_FORMATENUMF_NSAMPLESPERSEC |
                              ACM_FORMATENUMF_WBITSPERSAMPLE |
                              ACM_FORMATENUMF_CONVERT |
                              ACM_FORMATENUMF_SUGGEST)))
    {
        afc.pwfxEnum    = NULL;
    }
    else
    {
afc.pwfxEnum = pwfxEnum;
    }

    //
    //
    //
    hrsrc = NULL;

    if (0L == (afc.fdwStyle & (ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATE |
                               ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE)))
    {
        afc.hInstance       = NULL;
        afc.pszTemplateName = NULL;
    }
    else
    {
        if (0L != (afc.fdwStyle & ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE))
        {
            //
            //  ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE
            //
            hrsrc = FindResource(ghinst, DLG_AAFORMATCHOOSE_TEMPLATE, RT_DIALOG);

            afc.hInstance       = (HINSTANCE)LoadResource(ghinst, hrsrc);
            afc.pszTemplateName = NULL;
        }
        else
        {
            afc.hInstance       = ghinst;
            afc.pszTemplateName = DLG_AAFORMATCHOOSE_TEMPLATE;
        }
    }

    if (0L == (afc.fdwStyle & ACMFORMATCHOOSE_STYLEF_ENABLEHOOK))
    {
        afc.lCustData       = 0L;
        afc.pfnHook         = NULL;
    }
    else
    {
        afc.lCustData       = (LPARAM)(UINT)paacd;
        afc.pfnHook         = AcmAppChooserFormatHook;
    }

    //
    //
    //
    mmr = acmFormatChoose(&afc);

    if (NULL != hrsrc)
    {
        FreeResource((HGLOBAL)afc.hInstance);
    }

    //
    //
    //
    if (0 != guMsgHelp)
    {
        WinHelp(hwnd, gszAcmAppHelpFormat, HELP_QUIT, 0L);
        guMsgHelp = 0;
    }

    if (MMSYSERR_NOERROR != mmr)
    {
        if (ACMERR_CANCELED != mmr)
        {
            TCHAR       ach[40];

            AcmAppGetErrorString(mmr, ach);
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      TEXT("acmFormatChoose() failed with error %s, [%u]."),
                      (LPTSTR)ach, mmr);
        }

GlobalFreePtr(pwfxEnum);
        GlobalFreePtr(pwfx);
        return (FALSE);
    }

    //
    //
    //
    if (NULL != paacd->pwfxDst)
    {
        GlobalFreePtr(paacd->pwfxDst);
    }

    paacd->pwfxDst = pwfx;
    lstrcpy(paacd->szDstFormatTag, afc.szFormatTag);
    lstrcpy(paacd->szDstFormat, afc.szFormat);

    GlobalFreePtr(pwfxEnum);

    return (TRUE);
} // AcmAppChooserFormat()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserFilter
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//      BOOL fOptions:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserFilter
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd,
    BOOL                    fOptions
)
{
    ACMFILTERCHOOSE     afc;
    MMRESULT            mmr;
    LPWAVEFILTER        pwfltr;
    DWORD               cbwfltr;
    DWORD               fdwStyle;
    DWORD               fdwEnum;
    WAVEFILTER          wfltrEnum;
    BOOL                f;
    HRSRC               hrsrc;

    //
    //
    //
    fdwStyle   = 0L;
    fdwEnum    = 0L;

    if (fOptions)
    {
        _fmemset(&wfltrEnum, 0, sizeof(wfltrEnum));

        wfltrEnum.cbStruct    = sizeof(wfltrEnum);
        wfltrEnum.dwFilterTag = paacd->pwfxSrc->wFormatTag;

        wfltrEnum.fdwFilter   = fdwEnum;

        f = DialogBoxParam(ghinst,
                           DLG_AAFILTERENUM,
                           hwnd,
                           AcmAppDlgProcFilterEnum,
                           (LPARAM)(UINT)&wfltrEnum);
        if (!f)
        {
            return (FALSE);
        }

        fdwEnum = wfltrEnum.fdwFilter;

        wfltrEnum.fdwFilter = 0L;
    }

    if (fOptions)
    {
        afc.fdwStyle = fdwStyle;

        f = DialogBoxParam(ghinst,
                           DLG_AAFILTERSTYLE,
                           hwnd,
                           AcmAppDlgProcFilterStyle,
                           (LPARAM)(UINT)&afc);
        if (!f)
        {
            return (FALSE);
        }

        fdwStyle = afc.fdwStyle;
    }

    //
    //  this should never fail
    //
    mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FILTER, &cbwfltr);
    if (MMSYSERR_NOERROR != mmr)
    {
        DPF(0, "!AcmAppChooserFilter() acmMetrics failed mmr=%u!", mmr);
        return (FALSE);
    }

    //
    //
    //
    cbwfltr = max(cbwfltr, sizeof(WAVEFILTER));
    pwfltr  = (LPWAVEFILTER)GlobalAllocPtr(GHND, cbwfltr);
    if (NULL == pwfltr)
    {
        DPF(0, "!AcmAppChooserFilter() GlobalAllocPtr(%lu) failed!", cbwfltr);
        return (FALSE);
    }

    //
    //
    //
    if ((NULL != paacd->pwfltr) && (0L == fdwEnum))
    {
        fdwStyle |= ACMFILTERCHOOSE_STYLEF_INITTOFILTERSTRUCT;

        _fmemcpy(pwfltr, paacd->pwfltr, (UINT)paacd->pwfltr->cbStruct);
    }

    //
    //
    //
    //
    if (0 == (ACMFILTERCHOOSE_STYLEF_SHOWHELP & fdwStyle))
    {
        guMsgHelp = 0;
    }
    else
    {
        guMsgHelp = RegisterWindowMessage(ACMHELPMSGSTRING);
        if (0 == guMsgHelp)
        {
            fdwStyle &= ~ACMFILTERCHOOSE_STYLEF_SHOWHELP;
        }
        else
        {
            gpszAcmAppHelp = gszAcmAppHelpFilter;
        }
    }

    //
    //  initialize the ACMFILTERCHOOSE members
    //
    memset(&afc, 0, sizeof(afc));

    afc.cbStruct        = sizeof(afc);
    afc.fdwStyle        = fdwStyle;
    afc.hwndOwner       = hwnd;
    afc.pwfltr          = pwfltr;
    afc.cbwfltr         = cbwfltr;
    afc.pszTitle        = TEXT("Apply Filter Choice");

    afc.szFilterTag[0]  = '\0';
    afc.szFilter[0]     = '\0';
    afc.pszName         = NULL;
    afc.cchName         = 0;

    afc.fdwEnum         = fdwEnum;
    if (0L == (afc.fdwEnum & ACM_FILTERENUMF_DWFILTERTAG))
    {
        afc.pwfltrEnum  = NULL;
    }
    else
    {
        afc.pwfltrEnum  = &wfltrEnum;
    }

    if (0L == (afc.fdwStyle & (ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATE |
                               ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATEHANDLE)))
    {
        afc.hInstance       = NULL;
        afc.pszTemplateName = NULL;
    }
    else
    {
        if (0L != (afc.fdwStyle & ACMFORMATCHOOSE_STYLEF_ENABLETEMPLATEHANDLE))
        {
            //
            //  ACMFILTERCHOOSE_STYLEF_ENABLETEMPLATEHANDLE
            //
            hrsrc = FindResource(ghinst, DLG_AAFILTERCHOOSE_TEMPLATE, RT_DIALOG);

            afc.hInstance       = (HINSTANCE)LoadResource(ghinst, hrsrc);
            afc.pszTemplateName = NULL;
        }
        else
        {
            afc.hInstance       = ghinst;
            afc.pszTemplateName = DLG_AAFILTERCHOOSE_TEMPLATE;
        }
    }

    if (0L == (afc.fdwStyle & ACMFILTERCHOOSE_STYLEF_ENABLEHOOK))
    {
        afc.lCustData       = 0L;
        afc.pfnHook         = NULL;
    }
    else
    {
        afc.lCustData       = (LPARAM)(UINT)paacd;
        afc.pfnHook         = AcmAppChooserFilterHook;
    }

    //
    //
    //
    mmr = acmFilterChoose(&afc);

    if (NULL != hrsrc)
    {
        FreeResource((HGLOBAL)afc.hInstance);
    }

    //
    //
    //
    if (0 != guMsgHelp)
    {
        WinHelp(hwnd, gszAcmAppHelpFilter, HELP_QUIT, 0L);
        guMsgHelp = 0;
    }

    if (MMSYSERR_NOERROR != mmr)
    {
        if (ACMERR_CANCELED != mmr)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      TEXT("acmFilterChoose() failed with error = %u!"), mmr);
        }

        GlobalFreePtr(pwfltr);
        return (FALSE);
    }

    //
    //
    //
    if (NULL != paacd->pwfltr)
    {
        GlobalFreePtr(paacd->pwfltr);
    }

    paacd->pwfltr = pwfltr;
    lstrcpy(paacd->szFilterTag, afc.szFilterTag);
    lstrcpy(paacd->szFilter, afc.szFilter);

    return (TRUE);
} // AcmAppChooserFilter()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserDriverEnumCallback
//
//  Description:
//
//
//  Arguments:
//      HACMDRIVERID hadid:
//
//      DWORD dwInstance:
//
//      DWORD fdwSupport:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppChooserDriverEnumCallback
(
    HACMDRIVERID            hadid,
    DWORD                   dwInstance,
    DWORD                   fdwSupport
)
{
    MMRESULT            mmr;
    HWND                hcb;
    int                 n;
    ACMDRIVERDETAILS    add;

    //
    //  skip anything that does not support what we're after (for example,
    //  this will skip _HARDWARE only drivers that do not support stream
    //  functionality).
    //
    if (0 == (fdwSupport & (ACMDRIVERDETAILS_SUPPORTF_CODEC |
                            ACMDRIVERDETAILS_SUPPORTF_CONVERTER |
                            ACMDRIVERDETAILS_SUPPORTF_FILTER)))
    {
        return (TRUE);
    }

    //
    //
    //
    hcb = (HWND)(UINT)dwInstance;

    add.cbStruct = sizeof(add);
    mmr = acmDriverDetails(hadid, &add, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        lstrcpy(add.szLongName,  gszBogus);
    }

    AcmAppDebugLog(add.szLongName);
    AcmAppDebugLog(TEXT("\r\n"));

    n = ComboBox_AddString(hcb, add.szLongName);
    ComboBox_SetItemData(hcb, n, (LPARAM)(UINT)hadid);

    //
    //  return TRUE to continue with enumeration (FALSE will stop the
    //  enumerator)
    //
    return (TRUE);
} // AcmAppChooserDriverEnumCallback()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserUpdateDisplay
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      PAACONVERTDESC paacd:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserUpdateDisplay
(
    HWND                    hwnd,
    PAACONVERTDESC          paacd
)
{
    HWND                hedit;
    HWND                htxt;
    HWND                hcb;
    HWND                hsb;
    int                 n;
    MMRESULT            mmr;
    ACMDRIVERDETAILS    add;
    TCHAR               ach[40];
    LPWAVEFILTER        pwfltr;
    HACMDRIVERID        hadid;
    BOOL                f;
    DWORD               fdwOpen;
    int                 nValue;
    int                 nMinPos;
    int                 nMaxPos;
    DWORD               cbSrc;
    DWORD               cbDst;

    //
    //
    //
    htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FILE_INPUT);
    SetWindowText(htxt, paacd->szFilePathSrc);

    AcmAppGetFormatDescription(paacd->pwfxSrc, paacd->szSrcFormatTag, paacd->szSrcFormat);
    AppFormatBigNumber(ach, paacd->cbSrcData);
    AppFormatBigNumber(&ach[20], paacd->cbSrcData / paacd->pwfxSrc->nBlockAlign);

    htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FORMAT_INPUT);
    AppSetWindowText(htxt, TEXT("%s: %s\r\nAlignment=%u, Data=%s bytes, %s blocks"),
                        (LPTSTR)paacd->szSrcFormatTag,
                        (LPTSTR)paacd->szSrcFormat,
                        paacd->pwfxSrc->nBlockAlign,
                        (LPTSTR)ach, (LPTSTR)&ach[20]);

    hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_FILE_OUTPUT);
    Edit_GetText(hedit, paacd->szFilePathDst, SIZEOF(paacd->szFilePathDst));

    //
    //
    //
    nValue = (int)paacd->uBufferTimePerConvert;
    hsb = GetDlgItem(hwnd, IDD_AACHOOSER_SCROLL_TIME);
    GetScrollRange(hsb, SB_CTL, &nMinPos, &nMaxPos);
    if (nValue != GetScrollPos(hsb, SB_CTL))
    {
        SetScrollPos(hsb, SB_CTL, nValue, TRUE);

        if (nValue == nMaxPos)
        {
            lstrcpy(ach, TEXT("(ALL)"));
        }
        else if (nValue == nMinPos)
        {
            lstrcpy(ach, TEXT("(Auto)"));
        }
        else
        {
            wsprintf(ach, TEXT("%u.%.03u"), nValue / 1000, nValue % 1000);
        }

        htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_TIME);
        SetWindowText(htxt, ach);
    }

    //
    //
    //
    if (nValue == nMaxPos)
    {
        cbSrc = paacd->cbSrcData;
    }
    else if (nValue == nMinPos)
    {
        //
        //  could do something real here--for now, just do '1/8th of
        //  a second'..
        //
        cbSrc = paacd->pwfxSrc->nAvgBytesPerSec;
        cbSrc = MulDivRN(cbSrc, 175, 1000);
    }
    else
    {
        cbSrc = paacd->pwfxSrc->nAvgBytesPerSec;
        cbSrc = MulDivRN(cbSrc, (UINT)nValue, 1000);
    }

    paacd->cbSrcReadSize = cbSrc;

    //
    //
    //
    paacd->hadid = NULL;

    hcb = GetDlgItem(hwnd, IDD_AACHOOSER_COMBO_DRIVER);
    n   = ComboBox_GetCurSel(hcb);
    if (LB_ERR != n)
    {
        paacd->hadid = (HACMDRIVERID)(UINT)ComboBox_GetItemData(hcb, n);
    }

    //
    //
    //
    //
    htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FORMAT);
    if (NULL == paacd->pwfxDst)
    {
        SetWindowText(htxt, TEXT("(no format selected)"));
    }
    else
    {
        AppSetWindowText(htxt, TEXT("%s: %s\r\nAlignment=%u"),
                            (LPTSTR)paacd->szDstFormatTag,
                            (LPTSTR)paacd->szDstFormat,
                            paacd->pwfxDst->nBlockAlign);
    }

    //
    //
    //
    //
    htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FILTER);
    if (NULL == paacd->pwfltr)
    {
        SetWindowText(htxt, TEXT("(no filter selected)"));
    }
    else
    {
        AppSetWindowText(htxt, TEXT("%s: %s"),
                            (LPTSTR)paacd->szFilterTag,
                            (LPTSTR)paacd->szFilter);
    }

    hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_DETAILS);
    MEditPrintF(hedit, NULL);

    if (NULL == paacd->pwfxDst)
    {
        MEditPrintF(hedit, TEXT("hadid=%.04Xh\r\npwfxDst=%.08lXh\r\npwfltr=%.08lXh"),
                        paacd->hadid,
                        paacd->pwfxDst,
                        paacd->pwfltr);
        return (FALSE);
    }

    //
    //
    //
    if (NULL != paacd->hadid)
    {
        mmr = acmDriverOpen(&paacd->had, paacd->hadid, 0L);
        if (MMSYSERR_NOERROR != mmr)
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("The selected driver (hadid=%.04Xh) cannot be opened. %s (%u)"),
                        paacd->hadid, (LPSTR)ach, mmr);
            return (FALSE);
        }
    }

    SetWindowRedraw(hedit, FALSE);

    //
    //
    //
    f = IsDlgButtonChecked(hwnd, IDD_AACHOOSER_CHECK_FILTER);
    pwfltr = (f ? paacd->pwfltr : (LPWAVEFILTER)NULL);

    paacd->fApplyFilter = f;

    fdwOpen = 0L;
    f = IsDlgButtonChecked(hwnd, IDD_AACHOOSER_CHECK_NONREALTIME);
    if (f)
    {
        fdwOpen |= ACM_STREAMOPENF_NONREALTIME;
    }

    f = IsDlgButtonChecked(hwnd, IDD_AACHOOSER_CHECK_ASYNC);
    if (f)
    {
        fdwOpen |= ACM_STREAMOPENF_ASYNC;
    }

    paacd->fdwOpen = fdwOpen;

    //
    //
    //
    MEditPrintF(hedit, TEXT("~%12s: "), (LPTSTR)TEXT("Stream Open"));

mmr = acmStreamOpen(&paacd->has,
                        paacd->had,
                        paacd->pwfxSrc,
                        paacd->pwfxDst,
                        pwfltr,
                        0L,
                        0L,
                        fdwOpen);

    if (MMSYSERR_NOERROR == mmr)
    {
        TCHAR       szSrc[20];
        BOOL        fSrcAligned;
        BOOL        fDstAligned;

        acmDriverID((HACMOBJ)paacd->has, &hadid, 0L);

        add.cbStruct = sizeof(add);
        mmr = acmDriverDetails(hadid, &add, 0L);
        if (MMSYSERR_NOERROR != mmr)
        {
            lstrcpy(add.szLongName,  gszBogus);
        }

        MEditPrintF(hedit, TEXT("%s, %s"), (LPTSTR)gszYes, (LPTSTR)add.szLongName);

        fSrcAligned = (0 == (cbSrc % paacd->pwfxSrc->nBlockAlign));
        AppFormatBigNumber(szSrc, cbSrc);

        mmr = acmStreamSize(paacd->has, cbSrc, &cbDst, ACM_STREAMSIZEF_SOURCE);
        if (MMSYSERR_NOERROR == mmr)
        {
            TCHAR       szDst[20];

            fDstAligned = (0 == (cbDst % paacd->pwfxDst->nBlockAlign));
            AppFormatBigNumber(szDst, cbDst);

            if (cbSrc < cbDst)
            {
                cbDst = MulDivRN(cbDst, 10, cbSrc);
                cbSrc = 10;
            }
            else
            {
                cbSrc = MulDivRN(cbSrc, 10, cbDst);
                cbDst = 10;
            }

            MEditPrintF(hedit, TEXT("%12s: Src=%c%10s, Dst=%c%10s  (%lu.%lu:%lu.%lu)"),
                        (LPTSTR)TEXT("Buffer Size"),
                        fSrcAligned ? '*' : ' ',
                        (LPTSTR)szSrc,
                        fDstAligned ? '*' : ' ',
                        (LPTSTR)szDst,
                        cbSrc / 10, cbSrc % 10,
                        cbDst / 10, cbDst % 10);
        }
        else
        {
            AcmAppGetErrorString(mmr, ach);
            MEditPrintF(hedit, TEXT("%12s: Src=%c%10s, %s (%u)"), (LPTSTR)TEXT("Buffer Size"),
                        fSrcAligned ? '*' : ' ',
                        (LPTSTR)szSrc, (LPTSTR)ach, mmr);
        }

        acmStreamClose(paacd->has, 0L);
        paacd->has = NULL;
    }
    else
    {
        AcmAppGetErrorString(mmr, ach);
        MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
    }

    //
    //
    //
    MEditPrintF(hedit, TEXT("~%12s: "), (LPTSTR)TEXT("(Query)"));
    mmr = acmStreamOpen(NULL,
                        paacd->had,
                        paacd->pwfxSrc,
                        paacd->pwfxDst,
                        pwfltr,
                        0L,
                        0L,
                        fdwOpen | ACM_STREAMOPENF_QUERY);

    if (MMSYSERR_NOERROR == mmr)
    {
        MEditPrintF(hedit, gszYes);
    }
    else
    {
        AcmAppGetErrorString(mmr, ach);
        MEditPrintF(hedit, TEXT("%s, %s (%u)"), (LPTSTR)gszNo, (LPSTR)ach, mmr);
    }

    if (NULL != paacd->had)
    {
        acmDriverClose(paacd->had, 0L);
        paacd->had = NULL;
    }

    SetWindowRedraw(hedit, TRUE);

    return (MMSYSERR_NOERROR == mmr);
} // AcmAppChooserUpdateDisplay()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppChooserScrollConvertTime
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hsb:
//
//      UINT uCode:
//
//      int nPos:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AcmAppChooserScrollConvertTime
(
    HWND                    hwnd,
    HWND                    hsb,
    UINT                    uCode,
    int                     nPos
)
{
    PAACONVERTDESC      paacd;
    int                 nMinPos;
    int                 nMaxPos;
    HWND                htxt;
    TCHAR               ach[40];

    GetScrollRange(hsb, SB_CTL, &nMinPos, &nMaxPos);

    if ((SB_THUMBPOSITION != uCode) && (SB_THUMBTRACK != uCode))
    {
        nPos = GetScrollPos(hsb, SB_CTL);
    }

    //
    //
    //
    switch (uCode)
    {
        case SB_PAGEDOWN:
            if (GetKeyState(VK_CONTROL) < 0)
                nPos = min(nMaxPos, nPos + 100);
            else
                nPos = min(nMaxPos, nPos + 500);
            break;

        case SB_LINEDOWN:
            if (GetKeyState(VK_CONTROL) < 0)
                nPos = min(nMaxPos, nPos + 1);
            else
                nPos = min(nMaxPos, nPos + 10);
            break;

        case SB_PAGEUP:
            if (GetKeyState(VK_CONTROL) < 0)
                nPos = max(nMinPos, nPos - 100);
            else
                nPos = max(nMinPos, nPos - 500);
            break;

        case SB_LINEUP:
            if (GetKeyState(VK_CONTROL) < 0)
                nPos = max(nMinPos, nPos - 1);
            else
                nPos = max(nMinPos, nPos - 10);
            break;

        case SB_TOP:
            if (GetKeyState(VK_CONTROL) < 0)
                nPos = nMinPos;
            else
                nPos = 1000;
            break;

        case SB_BOTTOM:
            nPos = nMaxPos;
            break;

        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
            break;

        default:
            return (FALSE);
    }

    //
    //
    //
    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    paacd->uBufferTimePerConvert = (UINT)nPos;

    SetScrollPos(hsb, SB_CTL, nPos, TRUE);

    if (nPos == nMaxPos)
    {
        lstrcpy(ach, TEXT("(ALL)"));
    }
    else if (nPos == nMinPos)
    {
        lstrcpy(ach, TEXT("(Auto)"));
    }
    else
    {
        wsprintf(ach, TEXT("%u.%.03u"), nPos / 1000, nPos % 1000);
    }

    htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_TIME);
    SetWindowText(htxt, ach);

    //
    //
    //
    return (TRUE);
} // AcmAppChooserScrollConvertTime()

//--------------------------------------------------------------------------;
//
//  BOOL AcmAppDlgProcChooser
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AcmAppDlgProcChooser
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    PAACONVERTDESC      paacd;
    HWND                hedit;
    HWND                htxt;
    HWND                hcb;
    HWND                hsb;
    HFONT               hfont;
    UINT                uId;
    UINT                uCmd;
    BOOL                f;

    //
    //
    //
    if ((0 != guMsgHelp) && (uMsg == guMsgHelp))
    {
        WinHelp(hwnd, gpszAcmAppHelp, HELP_CONTENTS, 0L);
        return (TRUE);
    }

    paacd = (PAACONVERTDESC)(UINT)GetWindowLong(hwnd, DWL_USER);

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            paacd = (PAACONVERTDESC)(UINT)lParam;

            SetWindowLong(hwnd, DWL_USER, lParam);

            if (NULL == paacd->pwfxSrc)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                          TEXT("You must select a source file to convert."));
                EndDialog(hwnd, FALSE);
                return (TRUE);
            }

            if (NULL == paacd->pwfxDst)
            {
                AcmAppChooserFormatSuggest(hwnd, paacd);
            }

            hfont = GetStockFont(SYSTEM_FONT);

            htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FILE_INPUT);
            SetWindowFont(htxt, hfont, FALSE);

            htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FORMAT_INPUT);
            SetWindowFont(htxt, hfont, FALSE);

            hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_FILE_OUTPUT);
            SetWindowFont(hedit, hfont, FALSE);
            Edit_SetText(hedit, paacd->szFilePathDst);

            hcb = GetDlgItem(hwnd, IDD_AACHOOSER_COMBO_DRIVER);
            SetWindowFont(hcb, hfont, FALSE);

            htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FORMAT);
            SetWindowFont(htxt, hfont, FALSE);

            htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_FILTER);
            SetWindowFont(htxt, hfont, FALSE);

            hfont = ghfontApp;

            htxt = GetDlgItem(hwnd, IDD_AACHOOSER_TXT_TIME);
            SetWindowFont(htxt, hfont, FALSE);

            hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_DETAILS);
            SetWindowFont(hedit, hfont, FALSE);

            hsb = GetDlgItem(hwnd, IDD_AACHOOSER_SCROLL_TIME);
            SetScrollRange(hsb, SB_CTL, 0, 10000, FALSE);

            //
            //
            //
            CheckDlgButton(hwnd, IDD_AACHOOSER_CHECK_NONREALTIME, TRUE);

            SendMessage(hwnd, WM_ACMAPP_ACM_NOTIFY, 0, 0L);
            return (TRUE);

        case WM_ACMAPP_ACM_NOTIFY:
            AppHourGlass(TRUE);
            hcb = GetDlgItem(hwnd, IDD_AACHOOSER_COMBO_DRIVER);

            SetWindowRedraw(hcb, FALSE);
            ComboBox_ResetContent(hcb);

            ComboBox_AddString(hcb, TEXT("[ACM Driver Mapper]"));

            AcmAppDebugLog(NULL);
            acmDriverEnum(AcmAppChooserDriverEnumCallback, (DWORD)(UINT)hcb, 0L);

            ComboBox_SetCurSel(hcb, 0);
            SetWindowRedraw(hcb, TRUE);

            f = AcmAppChooserUpdateDisplay(hwnd, paacd);
            EnableWindow(GetDlgItem(hwnd, IDOK), f);
            EnableWindow(GetDlgItem(hwnd, IDD_AACHOOSER_BTN_PROPERTIES), f);

            AppHourGlass(FALSE);
            break;

        case WM_HSCROLL:
            f = (BOOL)HANDLE_WM_HSCROLL(hwnd, wParam, lParam, AcmAppChooserScrollConvertTime);
            f = TRUE;
            if (f)
            {
                AppHourGlass(TRUE);

                f = AcmAppChooserUpdateDisplay(hwnd, paacd);
                EnableWindow(GetDlgItem(hwnd, IDOK), f);
                EnableWindow(GetDlgItem(hwnd, IDD_AACHOOSER_BTN_PROPERTIES), f);

                AppHourGlass(FALSE);
            }
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            f   = FALSE;

            switch (uId)
            {
                case IDD_AACHOOSER_BTN_BROWSE:
                    f = AcmAppChooserSaveFile(hwnd, paacd);
                    break;

                case IDD_AACHOOSER_BTN_PROPERTIES:
                    f = AcmAppChooserProperties(hwnd, paacd);
                    break;

                case IDD_AACHOOSER_BTN_FORMAT_OPTIONS:
                case IDD_AACHOOSER_BTN_FORMAT:
                    f = (IDD_AACHOOSER_BTN_FORMAT_OPTIONS == uId);
                    f = AcmAppChooserFormat(hwnd, paacd, f);
                    break;

                case IDD_AACHOOSER_BTN_FILTER_OPTIONS:
                case IDD_AACHOOSER_BTN_FILTER:
                    f = (IDD_AACHOOSER_BTN_FILTER_OPTIONS == uId);
                    f = AcmAppChooserFilter(hwnd, paacd, f);
                    break;

                case IDD_AACHOOSER_COMBO_DRIVER:
                    uCmd = GET_WM_COMMAND_CMD(wParam, lParam);
                    switch (uCmd)
                    {
                        case CBN_SELCHANGE:
                            f = TRUE;
                            break;
                    }
                    break;

                case IDD_AACHOOSER_CHECK_FILTER:
                case IDD_AACHOOSER_CHECK_NONREALTIME:
                case IDD_AACHOOSER_CHECK_ASYNC:
                    f = TRUE;
                    break;

                case IDOK:
                    hedit = GetDlgItem(hwnd, IDD_AACHOOSER_EDIT_FILE_OUTPUT);
                    Edit_GetText(hedit, paacd->szFilePathDst, SIZEOF(paacd->szFilePathDst));

                case IDCANCEL:
                    EndDialog(hwnd, (IDOK == uId));
                    break;
            }

            //
            //
            //
            if (f)
            {
                AppHourGlass(TRUE);

                f = AcmAppChooserUpdateDisplay(hwnd, paacd);
                EnableWindow(GetDlgItem(hwnd, IDOK), f);
                EnableWindow(GetDlgItem(hwnd, IDD_AACHOOSER_BTN_PROPERTIES), f);

                AppHourGlass(FALSE);
            }
            break;
    }

    return (FALSE);
} // AcmAppDlgProcChooser()


TLB.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  tlb.c
//
//  Description:
//      Contains routines to support tabbed list boxes.  That way, we can
//      set a tab-stop in the list box, and have a second column of text
//      line up for all the strings in the list box.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include <memory.h>

#ifdef WIN32
    #include <tchar.h>
#else
    #ifndef _tcstod
        #define _tcstod     strtod
        #define _tcstol     strtol
        #define _tcstoul    strtoul
    #endif
#endif

#include "tlb.h"

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int GetRealTextMetrics
//
//  Description:
//      This function gets the textmetrics of the font currently selected
//      into the hdc.  It returns the average char width as the return value.
//
//      This function computes the average character width correctly by
//      using GetTextExtent() on the string "abc...xzyABC...XYZ" which works
//      out much better for proportional fonts. This is also necessary
//      for correct alignment between dialog and client units.
//
//      Note that this function returns the same TEXTMETRIC values that
//      GetTextMetrics() does, it simply has a different return value.
//
//  Arguments:
//      HDC hdc:
//
//      LPTEXTMETRIC ptm:
//
//  Return (int):
//
//
//--------------------------------------------------------------------------;

int FAR PASCAL GetRealTextMetrics
(
    HDC                     hdc,
    LPTEXTMETRIC            ptm
)
{
    TCHAR               achAlphabet[26 * 2];    // upper and lower case
    SIZE                sSize;
    UINT                u;
    int                 nAveWidth;

    //
    //  get the text metrics of the current font. note that GetTextMetrics
    //  gets the incorrect nAveCharWidth value for proportional fonts.
    //
    GetTextMetrics(hdc, ptm);
    nAveWidth = ptm->tmAveCharWidth;

    //
    //  if it's not a variable pitch font GetTextMetrics was correct
    //  so just return.
    //
    if (ptm->tmPitchAndFamily & FIXED_PITCH)
    {
        //
        //
        //
        for (u = 0; u < 26; u++)
        {
            achAlphabet[u]      = (TCHAR)(u + (UINT)'a');
            achAlphabet[u + 26] = (TCHAR)(u + (UINT)'A');
        }

        //
        //  round up
        //
        GetTextExtentPoint(hdc, achAlphabet, SIZEOF(achAlphabet), &sSize);
        nAveWidth = ((sSize.cx / 26) + 1) / 2;
    }

    //
    //  return the calculated average char width
    //
    return (nAveWidth);
} // GetRealTextMetrics()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL TlbPaint
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      HWND hwnd:
//
//      HDC hdc:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbPaint
(
    PTABBEDLISTBOX          ptlb,
    HWND                    hwnd,
    HDC                     hdc
)
{
    RECT                rc;
    HFONT               hfont;
    COLORREF            crBk;
    COLORREF            crText;
    int                 nHeight;

    //
    //
    //
    hfont = GetWindowFont(ptlb->hlb);
    if (NULL == hfont)
        hfont = GetStockFont(SYSTEM_FONT);

    hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);

    crBk   = SetBkColor(hdc, GetSysColor(COLOR_ACTIVECAPTION));
    crText = SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));

    //
    //  compute bounding rect for title only
    //
    rc = ptlb->rc;
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rc, NULL, 0, NULL);
    TabbedTextOut(hdc, rc.left, rc.top,
                  ptlb->pszTitleText,
                  ptlb->cchTitleText,
                  ptlb->uTabStops,
                  ptlb->panTitleTabs, 0);

    //
    //  restore the dc
    //
    SetBkColor(hdc, crBk);
    SetTextColor(hdc, crText);

    SelectObject(hdc, hfont);

    return (TRUE);
} // TlbPaint()

//--------------------------------------------------------------------------;
//
//  BOOL TlbMove
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      PRECT prc:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbMove
(
    PTABBEDLISTBOX          ptlb,
    PRECT                   prc,
    BOOL                    fRedraw
)
{
    RECT                rc;
    int                 nHeight;
    HWND                hwnd;

    hwnd = GetParent(ptlb->hlb);

    //
    //  invalidate only the region occupied by the current title bar. this
    //  will make sure that area gets repainted. the listbox portion will
    //  be invalidated correctly by the SetWindowPos() function below..
    //
    rc = ptlb->rc;

    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    InvalidateRect(hwnd, &rc, TRUE);

    //
    //  now move the listbox--we modify values in the rect structure, so
    //  copy to local storage
    //
    rc = *prc;

    //
    //  leave room at the top of the bounding rect for the title text
    //
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.top += nHeight;

    SetWindowPos(ptlb->hlb, NULL, rc.left, rc.top, rc.right - rc.left,
                 rc.bottom - rc.top, SWP_NOZORDER);

    //
    //  save the new location and invalidate the area so it is repainted
    //
    ptlb->rc = *prc;
    InvalidateRect(hwnd, prc, TRUE);

    if (fRedraw)
    {
        UpdateWindow(hwnd);
    }

    return (TRUE);
} // TlbMove()

//--------------------------------------------------------------------------;
//
//  BOOL TlbRecalcTabs
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL NEAR PASCAL TlbRecalcTabs
(
    PTABBEDLISTBOX          ptlb
)
{
    static TCHAR szGonzoThing[] = TEXT("M");

    int                 anTabsList[TLB_MAX_TAB_STOPS];
    HDC                 hdc;
    HFONT               hfont;
    TEXTMETRIC          tm;
    int                 nAveCharWidth;

    UINT                u;
    int                 nWidth;
    int                 nPrevTabTitle;
    int                 nPrevTabList;
    SIZE                sSize;

    //
    //
    //
    hdc = GetDC(NULL);
    {
        //
        //  get the average char width and height of the current font so we
        //  can compute tabs correctly. note that GetTextMetrics is pretty
        //  bogus when it comes to the average char width--it normally gives
        //  you the width of the character 'x'. what it should be is the
        //  average width of all capitals and lower case letters..
        //
        hfont = GetWindowFont(ptlb->hlb);
        if (NULL == hfont)
            hfont = GetStockFont(SYSTEM_FONT);

        hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);

#if 0
        GetTextMetrics(hdc, &tm);
        nAveCharWidth = tm.tmAveCharWidth;
#else
        nAveCharWidth = GetRealTextMetrics(hdc, &tm);
#endif
        ptlb->nFontHeight = tm.tmHeight;

        //
        //
        //
        GetTextExtentPoint(hdc, szGonzoThing, 1, &sSize);

        //
        //
        //
        hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);
    }
    ReleaseDC(NULL, hdc);

    //
    //  calculate the width of each column
    //
    nPrevTabTitle = 0;
    nPrevTabList  = 0;
    for (u = 0; u < ptlb->uTabStops; u++)
    {
//      nWidth = nAveCharWidth * ptlb->panTabs[u] + nAveCharWidth * 2;
        nWidth = sSize.cx * ptlb->panTabs[u] + (sSize.cx * 2);

        //
        //  set tabstop for title text--this is in client units
        //  for TabbedTextOut in TlbPaint
        //
        ptlb->panTitleTabs[u] = nPrevTabTitle + nWidth;
        nPrevTabTitle = ptlb->panTitleTabs[u];

        //
        //  set tabstop for listbox--this is in dialog units
        //
        anTabsList[u] = nPrevTabList + MulDiv(nWidth, 4, nAveCharWidth);
        nPrevTabList  = anTabsList[u];
    }

    //
    //  now setup the tabstops in the listbox
    //
    if (0 == ptlb->uTabStops)
    {
ListBox_SetTabStops(ptlb->hlb, ptlb->uTabStops, NULL);
    }
    else
    {
ListBox_SetTabStops(ptlb->hlb, ptlb->uTabStops, anTabsList);
    }

    return (TRUE);
} // TlbRecalcTabs()

//--------------------------------------------------------------------------;
//
//  HFONT TlbSetFont
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      HFONT hfont:
//
//      BOOL fRedraw:
//
//  Return (HFONT):
//
//
//--------------------------------------------------------------------------;

HFONT FAR PASCAL TlbSetFont
(
    PTABBEDLISTBOX          ptlb,
    HFONT                   hfont,
    BOOL                    fRedraw
)
{
    HFONT               hfontOld;

    //
    //
    //
    hfontOld = GetWindowFont(ptlb->hlb);
    SetWindowFont(ptlb->hlb, hfont, FALSE);

    TlbRecalcTabs(ptlb);
    TlbMove(ptlb, &ptlb->rc, fRedraw);

    return (hfontOld);
} // TlbSetFont()

//--------------------------------------------------------------------------;
//
//  BOOL TlbSetTitleAndTabs
//
//  Description:
//      This function sets the title text and tab stops for a Tabbed List
//      Box (TLB). The pszTitleFormat specifies the title text for each
//      column along with the tabstop position for each column. The format
//      of this string is as follows:
//
//      <columnname1>\t<tab1>!<columnname2>
//
//      TCHAR   szTlbThings[] = TEXT("Index\t6!Code\t5!Name");
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      PTSTR pszTitleFormat:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbSetTitleAndTabs
(
    PTABBEDLISTBOX          ptlb,
    PTSTR                   pszTitleFormat,
    BOOL                    fRedraw
)
{
    TCHAR               szTitleText[TLB_MAX_TITLE_CHARS];
    int                 anTabs[TLB_MAX_TAB_STOPS];
    PTSTR               pch;
    PTSTR               pchTitleText;
    UINT                uTabStops;
    UINT                cchTitleText;
    HWND                hwnd;

    //
    //  parse the title format counting tab stops and actual size of title
    //  text
    //
    uTabStops    = 0;
    pchTitleText = szTitleText;
    for (pch = pszTitleFormat; '\0' != *pch; )
    {
        TCHAR       ch;

        //
        //  scan to tab
        //
        while ('\0' != (ch = *pch))
        {
            *pchTitleText++ = *pch++;

            if ('\t' == ch)
                break;
        }

        if ('\0' == ch)
            break;

        //
        //  grab the next tab stop value
        //
        anTabs[uTabStops] = (int)_tcstol(pch, NULL, 10);
        uTabStops++;

        //
        //  skip to start of next column name
        //
        while ('!' != *pch++)
            ;
    }

    //
    //  terminate the converted title text
    //
    *pchTitleText = '\0';
    cchTitleText = lstrlen(szTitleText);

    //
    //  free the memory used for the previous tab stops and title text
    //
    if (NULL != ptlb->panTabs)
    {
        LocalFree((HLOCAL)ptlb->panTabs);

        ptlb->uTabStops    = 0;
        ptlb->panTabs      = NULL;
        ptlb->panTitleTabs = NULL;
    }

    if (NULL != ptlb->pszTitleText)
    {
        LocalFree((HLOCAL)ptlb->pszTitleText);

        ptlb->cchTitleText = 0;
        ptlb->pszTitleText = NULL;
    }

    //
    //  allocate new space for tab stops. there are two different tab
    //  arrays:
    //
    //      panTabs: original tab values as passed by caller. these are
    //      virtual tab locations represented as number of characters. we
    //      need to keep these values for recomputing the real tabs when
    //      the font changes.
    //
    //      panTitleTabs: these values are computed by TlbRecalcTabs and
    //      are actual tab positions in client coordinates for the title
    //      text (needed for TabbedTextOut in TlbPaint).
    //
    //  the tabs for the listbox are computed and set in TlbRecalcTabs
    //
    if (0 != uTabStops)
    {
        ptlb->panTabs = (PINT)LocalAlloc(LPTR, (uTabStops * sizeof(int)) * 2);
        if (NULL == ptlb->panTabs)
            return (FALSE);

        ptlb->uTabStops    = uTabStops;
        ptlb->panTitleTabs = ptlb->panTabs + uTabStops;
        memcpy(ptlb->panTabs, anTabs, uTabStops * sizeof(int));
    }

    //
    //  allocate space for the converted title text (stripped of the tab
    //  spacing values). this string is passed directly to TabbedTextOut
    //  in TlbPaint.
    //
    if (0 != cchTitleText)
    {
        ptlb->pszTitleText = (PTSTR)LocalAlloc(LPTR, (cchTitleText + 1) * sizeof(TCHAR));
        if (NULL == ptlb->pszTitleText)
            return (FALSE);

        ptlb->cchTitleText = cchTitleText;
        lstrcpy(ptlb->pszTitleText, szTitleText);
    }

    //
    //
    //
    TlbRecalcTabs(ptlb);

    //
    //  force a complete repaint of the title text and listbox--redraw
    //  immediately if we are supposed to
    //
    hwnd = GetParent(ptlb->hlb);
    InvalidateRect(hwnd, &ptlb->rc, TRUE);
    if (fRedraw)
    {
        UpdateWindow(hwnd);
    }

    return (TRUE);
} // TlbSetTitleAndTabs()

//--------------------------------------------------------------------------;
//
//  PTABBEDLISTBOX TlbDestroy
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//  Return (PTABBEDLISTBOX):
//
//
//--------------------------------------------------------------------------;

PTABBEDLISTBOX FAR PASCAL TlbDestroy
(
    PTABBEDLISTBOX          ptlb
)
{
    HWND                hwnd;
    int                 nHeight;

    //
    //  get rid of the listbox
    //
    if (NULL != ptlb->hlb)
    {
        DestroyWindow(ptlb->hlb);

        //
        //  invalidate area where title text was so it will be clean
        //
        nHeight = min(ptlb->nFontHeight, ptlb->rc.bottom - ptlb->rc.top);
        ptlb->rc.bottom = ptlb->rc.top + nHeight;

        hwnd = GetParent(ptlb->hlb);
        InvalidateRect(hwnd, &ptlb->rc, TRUE);

    }

    //
    //  free the memory used for tab stops and title text
    //
    if (NULL != ptlb->panTabs)
        LocalFree((HLOCAL)ptlb->panTabs);

    if (NULL != ptlb->pszTitleText)
        LocalFree((HLOCAL)ptlb->pszTitleText);

    LocalFree((HLOCAL)ptlb);

    return (NULL);
} // TlbDestroy()

//--------------------------------------------------------------------------;
//
//  PTABBEDLISTBOX TlbCreate
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      int nId:
//
//      PRECT prc:
//
//  Return (PTABBEDLISTBOX):
//
//
//--------------------------------------------------------------------------;

PTABBEDLISTBOX FAR PASCAL TlbCreate
(
    HWND                    hwnd,
    int                     nId,
    PRECT                   prc
)
{
    #define TLB_DEF_STYLE   (WS_VISIBLE|WS_CHILD|WS_VSCROLL|WS_BORDER|  \
                             WS_TABSTOP|WS_GROUP|LBS_NOTIFY|            \
                             LBS_NOINTEGRALHEIGHT|LBS_USETABSTOPS)

    static TCHAR    szNull[]    = TEXT("");
    static TCHAR    szListBox[] = TEXT("ListBox");

    PTABBEDLISTBOX      ptlb;
    HINSTANCE           hinst;

    //
    //  create a new instance data structure..
    //
    ptlb = (PTABBEDLISTBOX)LocalAlloc(LPTR, sizeof(*ptlb));
    if (NULL == ptlb)
        return (NULL);

    //
    //  create the listbox
    //
    hinst = GetWindowInstance(hwnd);

    ptlb->hlb = CreateWindow(szListBox, szNull, TLB_DEF_STYLE,
                             0, 0, 0, 0, hwnd, (HMENU)nId, hinst, NULL);
    if (NULL == ptlb->hlb)
    {
        TlbDestroy(ptlb);
        return (NULL);
    }

    TlbRecalcTabs(ptlb);

    if (NULL != prc)
    {
        ptlb->rc = *prc;
        TlbMove(ptlb, prc, FALSE);
    }

    return (ptlb);
} // TlbCreate()


TLB.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//--------------------------------------------------------------------------;
//
//  tlb.h
//
//  Description:
//      Contains function prototypes for the tabbed listbox functions in
//      tlb.c.  Also contains Win16/Win32 portability definitions.
//
//
//==========================================================================;

#ifndef _INC_TLB
#define _INC_TLB                    // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

#ifdef WIN32
    //
    //  for compiling Unicode
    //
    #ifndef SIZEOF
    #ifdef UNICODE
        #define SIZEOF(x)       (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)       sizeof(x)
    #endif
    #endif
#else
    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef TEXT
    #define TEXT(a)             a
    #endif

    #ifndef SIZEOF
    #define SIZEOF(x)           sizeof(x)
    #endif

    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif
#endif

//
//
//
//
typedef struct tTABBEDLISTBOX
{
    HWND            hlb;

    int             nFontHeight;
    RECT            rc;

    UINT            uTabStops;
    PINT            panTabs;
    PINT            panTitleTabs;

    UINT            cchTitleText;
    PTSTR           pszTitleText;

} TABBEDLISTBOX, *PTABBEDLISTBOX;

#define TLB_MAX_TAB_STOPS           20      // max number of columns
#define TLB_MAX_TITLE_CHARS         512

//
//
//
//
//
BOOL FAR PASCAL TlbPaint
(
    PTABBEDLISTBOX          ptlb,
    HWND                    hwnd,
    HDC                     hdc
);

BOOL FAR PASCAL TlbMove
(
    PTABBEDLISTBOX          ptlb,
    PRECT                   prc,
    BOOL                    fRedraw
);

HFONT FAR PASCAL TlbSetFont
(
    PTABBEDLISTBOX          ptlb,
    HFONT                   hfont,
    BOOL                    fRedraw
);

BOOL FAR PASCAL TlbSetTitleAndTabs
(
    PTABBEDLISTBOX          ptlb,
    PTSTR                   pszTitleFormat,
    BOOL                    fRedraw
);

PTABBEDLISTBOX FAR PASCAL TlbDestroy
(
    PTABBEDLISTBOX          ptlb
);

PTABBEDLISTBOX FAR PASCAL TlbCreate
(
    HWND                    hwnd,
    int                     nId,
    PRECT                   prc
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_TLB


WAVEIO.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  waveio.c
//
//  Description:
//      Contains routines for opening and closing RIFF WAVE files.
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <memory.h>

#include "appport.h"
#include "waveio.h"

#include "debug.h"

//--------------------------------------------------------------------------;
//
//  WIOERR wioFileClose
//
//  Description:
//
//
//  Arguments:
//      LPWAVEIOCB pwio:
//
//      DWORD fdwClose:
//
//  Return (WIOERR):
//
//--------------------------------------------------------------------------;

WIOERR WIOAPI wioFileClose
(
    LPWAVEIOCB      pwio,
    DWORD           fdwClose
)
{
    //
    //  validate a couple of things...
    //
    if (NULL == pwio)
        return (WIOERR_BADPARAM);

    //
    //  get rid of stuff...
    //
//  wioStopWave(pwio);

    if (NULL != pwio->hmmio)
    {
        mmioClose(pwio->hmmio, 0);
    }

//  FreeWaveHeaders(lpwio);

#if 0
    if (pwio->pInfo)
        riffFreeINFO(&(lpwio->pInfo));

    if (pwio->pDisp)
        riffFreeDISP(&(lpwio->pDisp));
#endif

    if (NULL != pwio->pwfx)
        GlobalFreePtr(pwio->pwfx);

    _fmemset(pwio, 0, sizeof(*pwio));

    return (WIOERR_NOERROR);
} // wioFileClose()

//--------------------------------------------------------------------------;
//
//  WIOERR wioFileOpen
//
//  Description:
//
//
//  Arguments:
//      LPWAVEIOCB pwio:
//
//      LPCTSTR pszFilePath:
//
//      DWORD fdwOpen:
//
//  Return (WIOERR):
//
//
//--------------------------------------------------------------------------;

WIOERR WIOAPI wioFileOpen
(
    LPWAVEIOCB      pwio,
    LPCTSTR         pszFilePath,
    DWORD           fdwOpen
)
{
    UINT        u;
    TCHAR       ach[255];
    WIOERR      werr;
    HMMIO       hmmio;
    MMCKINFO    ckRIFF;
    MMCKINFO    ck;
    DWORD       dw;

    //
    //  validate a couple of things...
    //
    if (NULL == pwio)
        return (WIOERR_BADPARAM);

    //
    //  default our error return (assume the worst)
    //
    _fmemset(pwio, 0, sizeof(*pwio));
    werr = WIOERR_FILEERROR;

    pwio->dwFlags   = fdwOpen;

    //
    //  first try to open the file, etc.. open the given file for reading
    //  using buffered I/O
    //
    hmmio = mmioOpen((LPTSTR)pszFilePath, NULL, MMIO_READ | MMIO_ALLOCBUF);
    if (NULL == hmmio)
        goto wio_Open_Error;

    pwio->hmmio     = hmmio;

    //
    //  locate a 'WAVE' form type...
    //
    ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    if (mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF))
        goto wio_Open_Error;

    //
    //  we found a WAVE chunk--now go through and get all subchunks that
    //  we know how to deal with...
    //
    pwio->dwDataSamples = (DWORD)-1L;

#if 0
    if (lrt=riffInitINFO(&wio.pInfo))
    {
        lr=lrt;
        goto wio_Open_Error;
    }
#endif

    //
    //
    //
    while (MMSYSERR_NOERROR == mmioDescend(hmmio, &ck, &ckRIFF, 0))
    {
        //
        //  quickly check for corrupt RIFF file--don't ascend past end!
        //
        if ((ck.dwDataOffset + ck.cksize) > (ckRIFF.dwDataOffset + ckRIFF.cksize))
        {
            DPF(1, "wioFileOpen() FILE MIGHT BE CORRUPT!");
            DPF(1, "    ckRIFF.dwDataOffset: %lu", ckRIFF.dwDataOffset);
            DPF(1, "          ckRIFF.cksize: %lu", ckRIFF.cksize);
            DPF(1, "        ck.dwDataOffset: %lu", ck.dwDataOffset);
            DPF(1, "              ck.cksize: %lu", ck.cksize);

            wsprintf(ach, TEXT("This wave file might be corrupt. The RIFF chunk.ckid '%.08lX' (data offset at %lu) specifies a cksize of %lu that extends beyond what the RIFF header cksize of %lu allows. Attempt to load?"),
                     ck.ckid, ck.dwDataOffset, ck.cksize, ckRIFF.cksize);
            u = MessageBox(NULL, ach, TEXT("wioFileOpen"),
                           MB_YESNO | MB_ICONEXCLAMATION | MB_TASKMODAL);
            if (IDNO == u)
            {
                werr = WIOERR_BADFILE;
                goto wio_Open_Error;
            }
        }

        switch (ck.ckid)
        {
            case mmioFOURCC('L', 'I', 'S', 'T'):
                if (ck.fccType == mmioFOURCC('I', 'N', 'F', 'O'))
                {
#if 0
                    if(lrt=riffReadINFO(hmmio, &ck, wio.pInfo))
                    {
                        lr=lrt;
                        goto wio_Open_Error;
                    }
#endif
                }
                break;

            case mmioFOURCC('D', 'I', 'S', 'P'):
#if 0
                riffReadDISP(hmmio, &ck, &(wio.pDisp));
#endif
                break;

            case mmioFOURCC('f', 'm', 't', ' '):
                //
                //  !?! another format chunk !?!
                //
                if (NULL != pwio->pwfx)
                    break;

                //
                //  get size of the format chunk, allocate and lock memory
                //  for it. we always alloc a complete extended format header
                //  (even for PCM headers that do not have the cbSize field
                //  defined--we just set it to zero).
                //
                dw = ck.cksize;
                if (dw < sizeof(WAVEFORMATEX))
                    dw = sizeof(WAVEFORMATEX);

                pwio->pwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dw);
                if (NULL == pwio->pwfx)
                {
                    werr = WIOERR_NOMEM;
                    goto wio_Open_Error;
                }

                //
                //  read the format chunk
                //
                werr = WIOERR_FILEERROR;
                dw = ck.cksize;
                if (mmioRead(hmmio, (HPSTR)pwio->pwfx, dw) != (LONG)dw)
                    goto wio_Open_Error;
                break;

            case mmioFOURCC('d', 'a', 't', 'a'):
                //
                //  !?! multiple data chunks !?!
                //
                if (0L != pwio->dwDataBytes)
                    break;

                //
                //  just hang on to the total length in bytes of this data
                //  chunk.. and the offset to the start of the data
                //
                pwio->dwDataBytes  = ck.cksize;
                pwio->dwDataOffset = ck.dwDataOffset;
                break;

            case mmioFOURCC('f', 'a', 'c', 't'):
                //
                //  !?! multiple fact chunks !?!
                //
                if (-1L != pwio->dwDataSamples)
                    break;

                //
                //  read the first dword in the fact chunk--it's the only
                //  info we need (and is currently the only info defined for
                //  the fact chunk...)
                //
                //  if this fails, dwDataSamples will remain -1 so we will
                //  deal with it later...
                //
                mmioRead(hmmio, (HPSTR)&pwio->dwDataSamples, sizeof(DWORD));
                break;
        }

        //
        //  step up to prepare for next chunk..
        //
        mmioAscend(hmmio, &ck, 0);
    }

    //
    //  if no fmt chunk was found, then die!
    //
    if (NULL == pwio->pwfx)
    {
        werr = WIOERR_ERROR;
        goto wio_Open_Error;
    }

    //
    //  all wave files other than PCM are _REQUIRED_ to have a fact chunk
    //  telling the number of samples that are contained in the file. it
    //  is optional for PCM (and if not present, we compute it here).
    //
    //  if the file is not PCM and the fact chunk is not found, then fail!
    //
    if (-1L == pwio->dwDataSamples)
    {
        if (WAVE_FORMAT_PCM == pwio->pwfx->wFormatTag)
        {
            pwio->dwDataSamples = pwio->dwDataBytes / pwio->pwfx->nBlockAlign;
        }
        else
        {
            //
            //  !!! HACK HACK HACK !!!
            //
            //  although this should be considered an invalid wave file, we
            //  will bring up a message box describing the error--hopefully
            //  people will start realizing that something is missing???
            //
            u = MessageBox(NULL, TEXT("This wave file does not have a 'fact' chunk and requires one! This is completely invalid and MUST be fixed! Attempt to load it anyway?"),
                            TEXT("wioFileOpen"), MB_YESNO | MB_ICONEXCLAMATION | MB_TASKMODAL);
            if (IDNO == u)
            {
                werr = WIOERR_BADFILE;
                goto wio_Open_Error;
            }

            //
            //  !!! need to hack stuff in here !!!
            //
            pwio->dwDataSamples = 0L;
        }
    }

    //
    //  cool! no problems..
    //
    return (WIOERR_NOERROR);

    //
    //  return error (after minor cleanup)
    //
wio_Open_Error:

    wioFileClose(pwio, 0L);
    return (werr);
} // wioFileOpen()


WAVEIO.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  waveio.h
//
//  Description:
//      Contains structure definitions and prototypes for the functions in
//      waveio.c.  Also contains Win16/Win32 portability definitions.
//
//
//==========================================================================;

#ifndef _INC_WAVEIO
#define _INC_WAVEIO                 // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

#ifdef WIN32
    //
    //  for compiling Unicode
    //
    #ifndef SIZEOF
    #ifdef UNICODE
        #define SIZEOF(x)       (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)       sizeof(x)
    #endif
    #endif
#else
    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef TEXT
    #define TEXT(a)             a
    #endif

    #ifndef SIZEOF
    #define SIZEOF(x)           sizeof(x)
    #endif

    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif
#endif

//
//
//
//
//
#ifdef WIN32
    #define WIOAPI      _stdcall
#else
#ifdef _WINDLL
    #define WIOAPI      FAR PASCAL _loadds
#else
    #define WIOAPI      FAR PASCAL
#endif
#endif

//
//
//
typedef UINT        WIOERR;

//
//
//
//
typedef struct tWAVEIOCB
{
    DWORD           dwFlags;
    HMMIO           hmmio;

    DWORD           dwDataOffset;
    DWORD           dwDataBytes;
    DWORD           dwDataSamples;

    LPWAVEFORMATEX  pwfx;

#if 0
    HWAVEOUT        hwo;
    DWORD           dwBytesLeft;
    DWORD           dwBytesPerBuffer;

    DISP FAR *      pDisp;
    INFOCHUNK FAR * pInfo;
#endif

} WAVEIOCB, *PWAVEIOCB, FAR *LPWAVEIOCB;

//
//  error returns from waveio functions
//
#define WIOERR_BASE             (0)
#define WIOERR_NOERROR          (0)
#define WIOERR_ERROR            (WIOERR_BASE+1)
#define WIOERR_BADHANDLE        (WIOERR_BASE+2)
#define WIOERR_BADFLAGS         (WIOERR_BASE+3)
#define WIOERR_BADPARAM         (WIOERR_BASE+4)
#define WIOERR_BADSIZE          (WIOERR_BASE+5)
#define WIOERR_FILEERROR        (WIOERR_BASE+6)
#define WIOERR_NOMEM            (WIOERR_BASE+7)
#define WIOERR_BADFILE          (WIOERR_BASE+8)
#define WIOERR_NODEVICE         (WIOERR_BASE+9)
#define WIOERR_BADFORMAT        (WIOERR_BASE+10)
#define WIOERR_ALLOCATED        (WIOERR_BASE+11)
#define WIOERR_NOTSUPPORTED     (WIOERR_BASE+12)

//
//  function prototypes and flag definitions
//
WIOERR WIOAPI wioFileClose
(
    LPWAVEIOCB              pwio,
    DWORD                   fdwClose
);

WIOERR WIOAPI wioFileOpen
(
    LPWAVEIOCB              pwio,
    LPCTSTR                 pszFilePath,
    DWORD                   fdwOpen
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_WAVEIO


README.TXT

MIDI IDF (Instrument Definition File) editor.


DRAWSTR.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
*****************************************************************************/

/*+
 *  drawstr.h
 *
 *  structures & define & prototypes for the structure dumper
 *
 *
 *-==================================================================*/

#if !defined DRAWSTR_H
#define DRAWSTR_H

typedef struct _dsfield {
   LPTSTR psz;
   UINT   off;
   UINT   siz;
   UINT   fmt;
   UINT   aux;
   UINT   array;
   } DSFIELD, * PDSFIELD;

typedef struct _dsfieldtbl {
   PDSFIELD pFields;
   LPCTSTR  pszName;
   UINT     cbSize;
   } DSFIELDTBL, * PDSFIELDTBL;

typedef struct _dsbuff {
   LPTSTR  ptr;
   UINT    cch;
   } DSBUFF, *PDSBUFF;

typedef struct _dslineinfo {
   UINT   nMaxLine;
   UINT   nCurLine;
   UINT   nMaxDepth;
   UINT   nCurDepth;
   int    cyLine;
   struct _dsline {
      LPVOID lpv;
      UINT   uStruct;
      UINT   uField;
      WORD   uDepth;
      WORD   uMember;
      } aLine[10];
   } DSLINEINFO, * PDSLINEINFO;

typedef struct _viewinit {
   PDSFIELDTBL pTable;
   UINT        uStruct;
   UINT        uRepeat;
   LPVOID      lpData;
   } VIEWINIT, *PVIEWINIT;

// global preferences.
//
typedef struct _dspref {
    BOOL bShowAddresses;
    } DSPREF;
extern DSPREF dspref;

#if !defined FIELDOFF
#define FIELDSIZ(st,field)  (unsigned int) (sizeof(((st *)0)->field))
#define FIELDREFSIZ(st,field)  (unsigned int) (sizeof(*(((st *)0)->field)))
#define FIELDOFF(st,field)  (unsigned int) (&(((st *)0)->field))
#define FLD(st,field)  FIELDOFF(st,field), FIELDSIZ(st,field)
#define PFLD(st,field) FIELDOFF(st,field), FIELDREFSIZ(st,field)
#endif

#ifndef NUMELMS
#define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
#endif

#define AS_NONE         0
#define AS_SZ           1
#define AS_ACH          2
#define AS_INT          3
#define AS_UINT         4
#define AS_HEX          5
#define AS_BITFIELD     6
#define AS_HANDLE       7
#define AS_HWND         8
#define AS_MODE         9
#define AS_ERROR        10
#define AS_XMODE        11

#define AS_RECT         16
#define AS_POINT        17
#define AS_POINTS       18
#define AS_SIZE         19
#define AS_FLOAT        20
#define AS_BYTES        21
#define AS_DUMP         22

#define AS_STRUCT      0x0100
#define IS_EDIT        0x0200
#define AS_REF         0x1000
#define AS_ARRAY       0x4000

VOID FAR PASCAL DrawDsLabels (
   HDC          hDC,
   PDSFIELDTBL  pTable,
   PDSLINEINFO  lpLineInfo,
   LPRECT       lpRect,
   POINT        ptOffset);

VOID FAR PASCAL DrawDsData (
   HDC          hDC,
   PDSFIELDTBL  pTable,     // info used to label and format fields in struct
   PDSLINEINFO  lpLineInfo, //
   LPRECT       lpRect,     // rectangle to clip drawing to
   POINT        ptOffset);  // scrolling offset for upper left corner

int FAR PASCAL GetDsLabelWidth (
   HDC          hDC,
   PDSFIELDTBL  pTable,      // info used to label and format fields in struct
   PDSLINEINFO  lpLineInfo); //

PDSLINEINFO WINAPI AllocDsLineInfo (
   UINT         nMax);

VOID FAR PASCAL BuildDsLineInfo (
   PDSFIELDTBL pTable,      //in:
   UINT        uStruct,     //in:
   LPVOID      lpData,      //in:
   WORD        nMember,     //in: index into lpData
   WORD        nLastMember, //in: index into lpData
   PDSLINEINFO pLineInfo);  //in,out:

BOOL WINAPI ExpandDsLineInfo (
   PDSFIELDTBL pTable,     // info used to label and format fields in struct
   UINT        nLine,      // line to expand (if possible)
   PDSLINEINFO pLineInfo);

BOOL WINAPI ContractDsLineInfo (
   PDSFIELDTBL pTable,     // info used to label and format fields in struct
   UINT        nLine,      // line to expand (if possible)
   PDSLINEINFO pLineInfo);

LRESULT WINAPI ViewWndProc (
   HWND    hWnd,           // handle of spy window
   UINT    wMsgID,         // id of current message
   WPARAM  wParam,         // word argument, depends on message id
   LPARAM  lParam);        // long argument, depends on message id

VOID WINAPI DrawDragEdge(
   HWND    hWnd,
   LPPOINT lppt,
   LPPOINT lpptLastEdge);

#define VM_GETDATA   (WM_USER + 100)
#define View_GetData(hWnd) (LPVOID)SendMessage(hWnd, VM_GETDATA, 0, 0)
#define VM_SETDATA   (WM_USER + 101)
#define View_SetData(hWnd,lpv) (LPVOID)SendMessage(hWnd, VM_SETDATA, 0, (LPARAM)lpv)

#define VM_INVALIDATE (WM_USER + 102)
#define View_InvalidateLine(hWnd,nLine) SendMessage(hWnd, VM_INVALIDATE, 0, (LPARAM)nLine)
#define View_InvalidateLines(hWnd,nLine,nCount) SendMessage(hWnd, VM_INVALIDATE, (WPARAM)nCount, (LPARAM)nLine)

#define VM_SETSEL    (WM_USER + 103)
#define View_SetSel(hWnd, nLine) SendMessage (hWnd, VM_SETSEL, 0, (LPARAM)nLine);

#define VM_EDITNEXT  (WM_USER + 104)
#define View_EditNext(hWnd,bPrev) SendMessage (hWnd, VM_EDITNEXT, (WPARAM)bPrev, 0);

// notifications
//
#define VN_CHANGE  (0U-800U)

#endif // drawstr_h


GLOBALS.H

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  globals.h
//**
//**  DESCRIPTION:
//**
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

#ifndef GLOBALS_H
#define GLOBALS_H

#include "drawstr.h"

#ifndef NUMELMS
 #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
#endif

#define FCC_RIFF MAKEFOURCC('R','I','F','F')
#define FCC_LIST MAKEFOURCC('L','I','S','T')
#define FCC_IDF  MAKEFOURCC('I','D','F',' ')
#define FCC_MMAP MAKEFOURCC('M','M','A','P')

#define FCC_hdr  MAKEFOURCC('h','d','r',' ')
#define FCC_inst MAKEFOURCC('i','n','s','t')
#define FCC_caps MAKEFOURCC('c','a','p','s')
#define FCC_chan MAKEFOURCC('c','h','a','n')
#define FCC_pmap MAKEFOURCC('p','m','a','p')
#define FCC_gkey MAKEFOURCC('g','k','e','y')
#define FCC_dkey MAKEFOURCC('d','k','e','y')

#define FCC_key  MAKEFOURCC('k','e','y',' ')
#define FCC_chnl MAKEFOURCC('c','h','n','l')

typedef struct _riff {
    DWORD fcc;
    DWORD cb;
    } RIFF, * LPRIFF;

typedef struct _rifflist {
    DWORD fccList;
    DWORD cbList;
    DWORD fccType;
    } RIFFLIST, * LPRIFFLIST;

#define NEXTRIFF(pRiff) ((LPRIFF)((LPBYTE)((LPRIFF)pRiff+1) + ((LPRIFF)pRiff)->cb + (((LPRIFF)pRiff)->cb & 1)))

#define MAX_CHANNEL  16
#define MAX_PATCH    128
#define MAX_KEYMAP   128
#define MAX_NAME     128

// iFlags fields
//
#define CHANNEL_IS_DRUM 1
#define CHANNEL_IS_MUTE 2
typedef struct _channel {
    int     iFlags;
    DWORD   cbInit;
    LPDWORD lpInit;
    } CHANNEL, *LPCHANNEL;

typedef struct _instrument {
    BOOL    bChanged;

    // hdr fields
    //
    DWORD   dwVersion;
    DWORD   dwCreator;
    CHAR    szInstID[MAX_NAME];

    // inst fields
    //
    DWORD   dwManufactID;
    DWORD   dwProductID;
    DWORD   dwRevision;
    TCHAR   szManufact[MAX_NAME];
    TCHAR   szProduct[MAX_NAME];

    // caps fields
    //
    DWORD   dwFlags;
    DWORD   dwBasicChannel;
    DWORD   nChannels;
    DWORD   nInstrumentPolyphony;
    DWORD   nChannelPolyphony;

    // chnl fields
    //
    CHANNEL aChannel[MAX_CHANNEL];

    // pmap fields
    //
    BYTE    aPatch[MAX_PATCH];

    // key fields
    //
    BYTE    aGenKeymap[MAX_KEYMAP];
    BYTE    aDrumKeymap[MAX_KEYMAP];

    } INSTRUMENT, *LPINSTRUMENT;

typedef struct _idf_header {
   TCHAR  szFile[MAX_PATH];
   BOOL   bChanged;

   HANDLE hEditHeap; // heap used for edit'ed copies of IDF data

   HWND   hWndHead;
   HWND   hWndTree;

   POINT  ptClient;
   SIZE   sClient;
   UINT   cxTree; // width of tree in pixels

   HWND   hWndInstrument;

   BOOL   bReadOnly;
   HANDLE hFile;
   HANDLE hSection;
   LPBYTE pFileBase;
   DWORD  cbFile;

   TCHAR  szTreeLabel[32];
   TCHAR  szViewLabel[32];

   INSTRUMENT instrum; // currently displayed instrument
   VIEWINIT   vi;   // instrument view info
   struct _instrum_info * piSelect; // currently selected instrument
   LPINSTRUMENT pInstrumSelect; // currently selecte instrument data

   UINT   nInstrum;
   struct _instrum_info {
      HWND         hWnd;
      LPINSTRUMENT pInstrum;
      LPRIFFLIST   pList;
      } ai[128];

   } IDFHEAD, *LPIDFHEAD;

struct _globalstate {
   TCHAR        szDefFile[MAX_PATH];
   LPIDFHEAD    pIDF;
   IDFHEAD      idf;
   };

#ifdef DECLARE_VARS
 struct _globalstate gs = {0};
#else
 extern struct _globalstate gs;
#endif

#endif // GLOBALS_H


HEAD.C

//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  head.c
//**
//**  DESCRIPTION:
//**     Window proc for IDF head pane
//**
//************************************************************************

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commctrl.h>

#include "idfedit.h"
#include "tridee.h"

/*+
 *
 *-=================================================================*/

STATICFN void LoadTree (
   HWND      hWndT,
   struct _instrum_info * piSelect,
   LPIDFHEAD pih)
{
   HTREEITEM htiSelect = NULL;
   HTREEITEM htiParent = TVI_ROOT;
   struct _instrum_info * pi;
   UINT  ii;

   TreeView_DeleteAllItems(hWndT);

   for (pi = &pih->ai[ii = 0]; ii < pih->nInstrum; ++ii, ++pi)
      {
      TV_INSERTSTRUCT ti;
      HTREEITEM       hti;

      ZeroMemory (&ti, sizeof(ti));

      ti.hParent        = TVI_ROOT;
      ti.hInsertAfter   = TVI_SORT;
      ti.item.mask      = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
      ti.item.state     = TVIS_EXPANDED;
      ti.item.stateMask = 0xFF;
      ti.item.lParam    = (LPARAM)pi;

      if (pi->pInstrum)
         ti.item.pszText = pi->pInstrum->szInstID;
      else if (pi->pList)
         {
         LPRIFF pRiff = FindListChunk(pi->pList, FCC_hdr);
         if (pRiff && pRiff->cb >= sizeof(IDFHEADER))
            ti.item.pszText = ((LPIDFHEADER)(pRiff+1))->abInstID;
         }

      assert (ti.item.pszText);
      if ( ! ti.item.pszText)
         continue;

      hti = TreeView_InsertItem (hWndT, &ti);

      if (piSelect && (piSelect == pi))
         htiSelect = hti;
      }

   if (htiSelect)
      {
      assert (piSelect);
      TreeView_SelectItem (hWndT, htiSelect);
      }
}

/*+
 *  HeadPrivate
 *
 *-=================================================================*/

static LONG WINAPI HeadPrivate (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   LPIDFHEAD  pih;

   pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);

   switch (wMsgID)
      {
      case HM_REFRESH_TREE:
         LoadTree(pih->hWndTree, pih->piSelect, pih);
         InvalidateRect (hWnd, NULL, FALSE);
         break;
      }

   return lRet;
   }

/*+
 *  HeadCommands
 *
 *-=================================================================*/

LONG WINAPI HeadCommands (
   HWND   hWnd,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   LPIDFHEAD  pih;

   pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);

   switch (GET_WM_COMMAND_ID(wParam,lParam))
      {
      }

   return lRet;
   }

/*+
 *
 *
 *-=================================================================*/

STATICFN BOOL WINAPI HandleInstrumentsSelChange (
    HWND      hWnd,
    LPNMHDR   lpnm,
    LPIDFHEAD pih)
{
   LPNM_TREEVIEW pntv = (LPVOID)lpnm;
   LPTV_ITEM     pti  = &pntv->itemNew;
   //TV_ITEM       ti;
   struct _instrum_info * pi;
   //char          szSingle[MAX_ALIAS];
   //BOOL          bChange = FALSE;

   if (!pih)
      return FALSE;

   // if there was a valid previous selection.
   // make sure that there a now changes to be
   // saved off before we allow selection to proceed.
   //
   pi = (LPVOID)pntv->itemNew.lParam;
   if (pi && !pi->pInstrum &&
       (pih->piSelect == pi) &&
       (pih->pInstrumSelect == &pih->instrum) &&
       (pih->instrum.bChanged))
      {
      assert (0);
      // need to save changes made to the instrument data
      //
      pi->pInstrum = CopyForEditing (&pih->instrum, sizeof(pih->instrum));
      pih->bChanged = TRUE;
      }

   // setup ti to get text & # of children
   // from the IDF filename entry.
   //
   //ti.mask       = TVIF_PARAM;
   //ti.mask       = TVIF_TEXT | TVIF_PARAM;
   //ti.pszText    = szSingle;
   //ti.cchTextMax = NUMELMS(szSingle);
   //ti.hItem      = pti->hItem;
   //TreeView_GetItem (lpnm->hwndFrom, &ti);
   //pi = (LPVOID)ti.lParam;
   pi = (LPVOID)pti->lParam;
   if (!pi)
      return FALSE;

   // if the selected instrument has no editable data set,
   // copy the raw RIFF data into the temp edit set
   // and display instrument settings from there.
   //
   pih->piSelect = NULL;
   pih->pInstrumSelect = NULL;
   if (pih->nInstrum)
      {
      pih->piSelect = pi;
      pih->pInstrumSelect = pi->pInstrum;
      if (!pi->pInstrum)
         {
         CopyInstrumData (&pih->instrum, pi->pList);
         pih->pInstrumSelect = &pih->instrum;
         }
      }

   View_SetData (pih->hWndInstrument, pih->pInstrumSelect);
   View_InvalidateLines (pih->hWndInstrument, 0, 0);
   return TRUE;
}

/*+
 *  HeadNotifications
 *
 *-=================================================================*/

LONG WINAPI HeadNotifications (
   HWND     hWnd,
   LPNMHDR  lpnm)
   {
   LONG       lRet = 0l;               // return value from this routine
   LPIDFHEAD  pih;

   pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);

   switch (lpnm->idFrom)
      {
      case IDL_INSTRUMENTS:
         switch (lpnm->code)
            {
            case TVN_SELCHANGED:
                HandleInstrumentsSelChange (hWnd, lpnm, pih);
                break;

            //case NM_DBLCLK:
            //    break;

            case TVN_ENDLABELEDIT:
                {
                TV_DISPINFO * ptv = (LPVOID)lpnm;
                struct _instrum_info * pi = (LPVOID)ptv->item.lParam;

                if (!pi)
                   break;

                if (!pi->pInstrum)
                   {
                   if (pih->piSelect == pi && pih->pInstrumSelect == &pih->instrum)
                      pi->pInstrum = CopyForEditing (&pih->instrum, sizeof(pih->instrum));
                   else
                      {
                      assert (0); // shoundn't be able to label edit without first selecting
                      pi->pInstrum = HeapAlloc (gs.idf.hEditHeap, 0, sizeof(INSTRUMENT));
                      CopyInstrumData (pi->pInstrum, pi->pList);
                      }
                   }

                lstrcpyn (pi->pInstrum->szInstID, ptv->item.pszText, NUMELMS(pi->pInstrum->szInstID));
                pi->pInstrum->bChanged = TRUE;
                pih->bChanged = TRUE;
                View_InvalidateLines (pih->hWndInstrument, 0, 0);
                lRet = 1;
                }
                break;
            }
         break;

      case IDC_CURRENT_INSTRUM:
         switch (lpnm->code)
            {
            case VN_CHANGE:
               if (pih->pInstrumSelect)
                  {
                  if (pih->pInstrumSelect == &pih->instrum)
                     {
                     assert (!pih->piSelect->pInstrum);
                     if ( ! pih->piSelect->pInstrum)
                        {
                        pih->pInstrumSelect =
                        pih->piSelect->pInstrum = CopyForEditing (&pih->instrum, sizeof(pih->instrum));
                        View_SetData (pih->hWndInstrument, pih->pInstrumSelect);
                        View_InvalidateLines (pih->hWndInstrument, 0, 0);
                        }
                     }
                  pih->pInstrumSelect->bChanged = TRUE;
                  pih->bChanged = TRUE;
                  }
               else
                  {
                  assert (0);
                  }
               break;
            }
         break;
      }

   return lRet;
   }

/*+
 *  HeadWndProc
 *
 *-=================================================================*/

LONG CALLBACK HeadWndProc (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   if (wMsgID >= WM_USER && wMsgID < 0x8000)
      lRet = HeadPrivate (hWnd, wMsgID, wParam, lParam);
   else
   switch (wMsgID)
      {
      case WM_COMMAND:
         lRet = HeadCommands (hWnd, wParam, lParam);
         break;

      case WM_NOTIFY:
         lRet = HeadNotifications (hWnd, (LPNMHDR)lParam);
         break;

      case WM_SIZE:
         {
         LPIDFHEAD pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);
         HDC hDC;
         TEXTMETRIC tm;
         #define BORDER 0

         hDC = GetDC (hWnd);
         //if (pih->hFont)
         //   SelectObject (hDC, pih->hFont);
         GetTextMetrics (hDC, &tm);
         pih->ptClient.y = tm.tmHeight + tm.tmExternalLeading + 6;
         pih->ptClient.x = 0;
         ReleaseDC (hWnd, hDC);

         pih->sClient.cx = LOWORD(lParam) - pih->ptClient.x;
         pih->sClient.cy = HIWORD(lParam) - pih->ptClient.y;

         SetWindowPos (pih->hWndTree, NULL,
                       pih->ptClient.x, pih->ptClient.y,
                       pih->cxTree, pih->sClient.cy,
                       SWP_NOZORDER);

         SetWindowPos (pih->hWndInstrument, NULL,
                       pih->ptClient.x + pih->cxTree + BORDER, pih->ptClient.y,
                       pih->sClient.cx - pih->cxTree - BORDER, pih->sClient.cy,
                       SWP_NOZORDER);
         }
         break;

      case WM_CREATE:
         {
         LPCREATESTRUCT    lpCreate = (LPCREATESTRUCT)lParam;
         LPIDFHEAD         pih;

         SetWindowLong (hWnd, GWL_USERDATA, 0);

         pih = (LPVOID)lpCreate->lpCreateParams;
         if (!pih)
             return -1;
         SetWindowLong (hWnd, GWL_USERDATA, (LONG)pih);

         pih->cxTree = LOWORD(GetDialogBaseUnits()) * 20;

         pih->hWndTree =
             CreateWindowEx (fdwExStyle | WS_EX_NOPARENTNOTIFY,
                             WC_TREEVIEW,
                             "",
                             TVS_DISABLEDRAGDROP |
                             TVS_SHOWSELALWAYS |
                             TVS_EDITLABELS |
                             WS_CHILD | WS_VISIBLE | WS_BORDER,
                             0, 0, pih->cxTree, 0,
                             hWnd,
                             (HMENU)IDL_INSTRUMENTS,
                             hInst,
                             NULL);
         if ( ! pih->hWndTree)
            lRet = -1;

         pih->hWndInstrument =
             CreateWindowEx (fdwExStyle | WS_EX_NOPARENTNOTIFY,
                             cszInstrumClass,
                             "",
                             WS_CHILD | WS_VISIBLE | WS_BORDER,
                             pih->cxTree + 6, 0, 200, 0,
                             hWnd,
                             (HMENU)IDC_CURRENT_INSTRUM,
                             hInst,
                             &pih->vi);
         if ( ! pih->hWndInstrument)
            lRet = -1;

         LoadTree (pih->hWndTree, &pih->ai[0], pih);
         LoadString (hInst, IDS_TREE_LABEL, pih->szTreeLabel, NUMELMS(pih->szTreeLabel));
         LoadString (hInst, IDS_VIEW_LABEL, pih->szViewLabel, NUMELMS(pih->szViewLabel));

         TrideeCreate(hWnd);
         }
         break;

      case WM_DESTROY:
         {
         LPIDFHEAD pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);
         if (pih && pih != &gs.idf)
            HeapFree (GetProcessHeap(), 0, pih);
         SetWindowLong (hWnd, GWL_USERDATA, 0);
         TrideeDestroy(hWnd);
         }
         break;

      case WM_ERASEBKGND:
         {
         RECT rc;
         LPIDFHEAD pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);
         HDC  hDC = (HDC)wParam;
         int  cx = 2;
         int  cy = 4;

         lRet = DefWindowProc (hWnd, wMsgID, wParam, lParam);

         GetClientRect (hWnd, &rc);

         SetTextColor (hDC, GetSysColor (COLOR_BTNTEXT));
         SetBkColor (hDC, GetSysColor (COLOR_BTNFACE));

         SetTextAlign (hDC, TA_TOP | TA_LEFT);
         ExtTextOut (hDC, cx, cy, ETO_CLIPPED, &rc,
                     pih->szTreeLabel, lstrlen(pih->szTreeLabel), NULL);

         cx = pih->ptClient.x + pih->cxTree + BORDER +2;
         ExtTextOut (hDC, cx, cy, ETO_CLIPPED, &rc,
                     pih->szViewLabel, lstrlen(pih->szViewLabel), NULL);

         rc.bottom = rc.top + 2;
         TrideeWellShadow(hDC, &rc);
         }
         break;

      case WM_PAINT:
         {
         PAINTSTRUCT ps;           // structure for BeginPaint
         HDC         hDC;          // DC to draw info into

         LPIDFHEAD pih = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);

         // and draw the info into our client area
         //
         hDC = BeginPaint (hWnd, &ps);
         EndPaint (hWnd, &ps);
         }
         break;

      default:
         lRet = DefWindowProc (hWnd, wMsgID, wParam, lParam);
      }

   return lRet;
   }


IDF.C

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  idf.c
//**
//**  DESCRIPTION:
//**    main worker code for IDF editing
//**
//************************************************************************

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include "idfedit.h"

#include <commdlg.h>
#include "drawstr.h"

VOID FreeIDFFile (LPIDFHEAD);

/*+ PromptForIDFName();
 *
 *
 *  DESCRIPTION:
 *     This function is a wrapper for the Get[Open/Save]FileName commdlg
 *     chooser dialogs. Based on the fuFlags argument, this function
 *     will display the appropriate chooser dialog and return the result.
 *
 *  ARGUMENTS:
 *     HWND    hwnd          - Handle to parent window.
 *     LPSTR   lpszPath      - Pointer to the buffer to receive the
 *                             the file path.
 *     LPSTR   lpszTitle     - Pointer to the buffer to receive the
 *                             file the file title, NULL if no title
 *                             is wanted.
 *     BOOL     fSave        - TRUE if we are to save a file,
 *                             FALSE if we are to open the file.
 *
 *  RETURNS:
 *     BOOL  -  TRUE if a file was chosen. FALSE if the user canceled
 *              the operation.
 *
 *-=======================================================================*/

BOOL WINAPI PromptForIDFName (
    HWND    hwnd,
    LPTSTR  lpszPath,
    LPTSTR  lpszTitle,
    BOOL    fSave)
{
   OPENFILENAME   ofn;
   TCHAR          szExtDefault[4];
   TCHAR          szExtFilter[256];
   LPTSTR         pstr;
   BOOL           bRet;

   //  Get the extension filter and default extension.
   //
   LoadString (hInst, IDS_OFN_EXT_DEF, szExtDefault, NUMELMS(szExtDefault));
   LoadString (hInst, IDS_OFN_EXT_FILTER, szExtFilter, NUMELMS(szExtFilter));

   // Parse the bang out of the filter string, replace with a NULL.
   //
   for (pstr = szExtFilter; *pstr; pstr = AnsiNext(pstr))
      if (TEXT('!') == *pstr)
         *pstr = 0;

   // set the default path to *.ext
   //
   lpszPath[0] = TEXT('*');
   if (szExtDefault[0])
      {
      lpszPath[1] = TEXT('.');
      lstrcpy (lpszPath+2, szExtDefault);
      }

   // If there is a title then reset it also.
   //
   if (lpszTitle)
      lpszTitle[0] = 0;

   //  Initialize the OPENFILENAME structure elements.
   //
   ZeroMemory(&ofn, sizeof(ofn));
   ofn.lStructSize    = sizeof(OPENFILENAME);
   ofn.hwndOwner      = hwnd;
   ofn.lpstrFilter    = szExtFilter;
   ofn.nFilterIndex   = 1;
   ofn.lpstrFile      = lpszPath;
   ofn.nMaxFile       = MAX_PATH;
   ofn.lpstrFileTitle = lpszTitle;
   ofn.nMaxFileTitle  = lpszTitle ? MAX_PATH : 0;
   ofn.lpstrDefExt    = szExtDefault;

   //  If the fSave is TRUE, then call GetSaveFileName()
   //  otherwise call GetOpenFileName().
   //
   if (fSave)
      {
      // Set the OPENFILENAME flags to save and prompt if we
      // will overwrite an existing file.
      //
      ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
      bRet = GetSaveFileName(&ofn);
      }
   else
      {
      // Set the OPENFILENAME flags to open and the file
      // must exist if we are opening.
      //
      ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
      bRet = GetOpenFileName(&ofn);
      }

   return bRet;
}

STATICFN UINT fnMapLabel (
   LPTSTR    psz,
   UINT      cch,
   UINT      uDummy,
   UINT      uIndex,
   UINT      uMsgID)
{
   UINT   lRet = 0;

   switch (uMsgID)
   {
      case WM_GETTEXT:
         lstrcpy (psz, "  0   1   2   3   4   5   6   7   8   9");
         lRet = lstrlen (psz);
         break;
   }

   return lRet;
}

#define BLANK_LINE "", 0,0,AS_HEX,0,0

// 0
//
#undef STDFLD
#define STDFLD(fld) FLD(INSTRUMENT,fld)
static DSFIELD aINSTRUMENTfields[] = {
    "IDF Version", STDFLD(dwVersion),            AS_HEX | IS_EDIT, 0, 0,
    "IDF Creator", STDFLD(dwCreator),            AS_HEX | IS_EDIT, 0, 0,
//  "Instrument Identifier",  STDFLD(szInstID),             AS_SZ | IS_EDIT,  0, 0,

    BLANK_LINE,
    "Manufacturer ID",      STDFLD(dwManufactID),         AS_INT | IS_EDIT, 0, 0,
    "Product ID",           STDFLD(dwProductID),          AS_INT | IS_EDIT, 0, 0,
    "Product Rev.",         STDFLD(dwRevision),           AS_INT | IS_EDIT, 0, 0,
    "Manufacturer",         STDFLD(szManufact),           AS_SZ | IS_EDIT, 0, 0,
    "Product Name",         STDFLD(szProduct),            AS_SZ | IS_EDIT, 0, 0,

    BLANK_LINE,
    "Supports General MIDI",   STDFLD(dwFlags), AS_BITFIELD | IS_EDIT, 0, 0,
    "Supports SysEx Messages", STDFLD(dwFlags), AS_BITFIELD | IS_EDIT, 1, 0,
    "Basic Channel",           STDFLD(dwBasicChannel),       AS_INT | IS_EDIT, 0, 0,
    "Number of Channels",      STDFLD(nChannels),            AS_INT | IS_EDIT, 0, 0,
    "Instrument Polyphony",    STDFLD(nInstrumentPolyphony), AS_INT | IS_EDIT, 0, 0,
    "Channel Polyphony",       STDFLD(nChannelPolyphony),    AS_INT | IS_EDIT, 0, 0,

    // pmap fields
    //
    BLANK_LINE,
    "Patch Map",       0, 0, AS_XMODE, (DWORD)fnMapLabel, 0,
    " %02d",           FIELDOFF(INSTRUMENT,aPatch), 10, AS_BYTES | IS_EDIT | AS_ARRAY, 0, 12,
    " 12",             FIELDOFF(INSTRUMENT,aPatch) + 120, 8, AS_BYTES | IS_EDIT, 0, 0,

    // key fields
    //
    BLANK_LINE,
    "General Key Map", 0, 0,  AS_XMODE, (DWORD)fnMapLabel, 0,
    " %02d",           FIELDOFF(INSTRUMENT,aGenKeymap), 10, AS_BYTES | IS_EDIT | AS_ARRAY, 0, 12,
    " 12",             FIELDOFF(INSTRUMENT,aGenKeymap) + 120, 8, AS_BYTES | IS_EDIT, 0, 0,

    BLANK_LINE,
    "Drum Key Map",    0, 0,  AS_XMODE, (DWORD)fnMapLabel, 0,
    " %02d",           FIELDOFF(INSTRUMENT,aDrumKeymap), 10, AS_BYTES | IS_EDIT | AS_ARRAY, 0, 12,
    " 12",             FIELDOFF(INSTRUMENT,aDrumKeymap) + 120, 8, AS_BYTES | IS_EDIT, 0, 0,

    // chnl fields
    //
    BLANK_LINE,
    "Channel %2d",     FIELDOFF(INSTRUMENT,aChannel), 0, AS_ARRAY | AS_STRUCT, 1, 16,
    // CHANNEL aChannel[MAX_CHANNEL];

    NULL, (UINT)-1, 0, AS_NONE, 0, 0,
    };

#include <stdlib.h>

STATICFN UINT AsciiToBytes (
   LPDWORD lpv,
   UINT    cb,
   LPTSTR  psz)
   {
   UINT   jj;

   for (jj = 0; ; ++jj)
      {
      ULONG uu;
      LPTSTR pszStop;
      while (*psz == '\t' || *psz == ' ')
         ++psz;

      pszStop = psz;
      uu = strtoul (psz, &pszStop, 16);
      if (pszStop == psz)
         break;

      if (jj < cb/sizeof(*lpv))
         lpv[jj] = uu;

      psz = pszStop;
      }

   return jj * sizeof(*lpv);
   }

STATICFN VOID BytesToAscii (
   LPTSTR  psz,
   UINT    cch,
   LPDWORD lpv,
   UINT    cbData)
{
   UINT jj;
   UINT cb;

   for (jj = 0; jj < (cbData+sizeof(*lpv)-1)/sizeof(*lpv); ++jj)
      {
      wsprintf (psz, "%08lX ", lpv[jj]);
      cch -= (cb = lstrlen(psz));
      psz += cb;
      *psz = 0;
      if (cch < cb + 4)
         {
         *psz++ = '.';
         *psz++ = '.';
         *psz++ = '.';
         *psz++ = 0;
         break;
         }
      }
}

const TCHAR cszNone[] = "<none>";

STATICFN UINT fnChanData (
   LPTSTR    psz,
   UINT      cch,
   LPCHANNEL pChan,
   UINT      uIndex,
   UINT      uMsgID)
{
   LPDWORD lpvData = pChan->lpInit;
   UINT    cbData  = pChan->cbInit;
   UINT    lRet = 0;

   switch (uMsgID)
   {
      case WM_GETTEXT:
         if (!lpvData || !cbData)
            lstrcpy (psz, cszNone);
         else
            BytesToAscii (psz, cch, lpvData, cbData);
         lRet = lstrlen (psz);
         break;

      case WM_SETTEXT:
         {
         UINT uu;

         // if we dont have a writable pointer to sysex data,
         // we must allocate an editable one before we go further.
         //
         if (IsBadWritePtr (lpvData,1))
            cbData = 0;

         // if the data is not the string <none>
         // parse it as HEX data and use that to set the sysex
         // data.  if it is <none> remove the sysex data.
         //
         if (!psz || ! lstrcmpi(psz, cszNone))
            uu = 0;
         else
            uu = AsciiToBytes (lpvData, cbData, psz);

         // if there was more data than would fit in the current
         // sysex buffer. allocate a new buffer and try again
         //
         if (uu > cbData)
            {
            if (cbData && lpvData)
               HeapFree (gs.idf.hEditHeap, 0, lpvData);

            lpvData = HeapAlloc (gs.idf.hEditHeap, 0, uu);
            AsciiToBytes (lpvData, uu, psz);
            }

         // update pChan pointers to reflect the new sysex data
         //
         pChan->lpInit = lpvData;
         pChan->cbInit = uu;
         }
         break;

      case WM_COPY:
         {
         HGLOBAL hMem = GlobalAlloc (GHND, 4 + cbData * 3);
         if (!(psz = GlobalLock (hMem)))
            break;

         // put the sysex data on the clipboard as ASCII
         //
         BytesToAscii (psz, 4 + cbData * 3, lpvData, cbData);
         GlobalUnlock (hMem);
         if (!OpenClipboard (hWndMain))
            GlobalFree (hMem);
         else
            {
            EmptyClipboard ();
            SetClipboardData (CF_TEXT, hMem);
            CloseClipboard ();
            lRet = 1;
            }
         }
         break;

      case WM_PASTE:
         {
         HGLOBAL hMem;

         // grap text from the clipboard and use it as the sysex data
         // however, ignore the data if it's not valid hex data
         //
         if (!OpenClipboard (hWndMain))
            break;
         if (hMem = GetClipboardData (CF_TEXT))
            {
            if (psz = GlobalLock (hMem))
               {
               fnChanData (psz, GlobalSize(hMem), pChan, uIndex, WM_SETTEXT);
               GlobalUnlock (hMem);
               lRet = 1;
               }
            }
         CloseClipboard ();
         }
         break;
   }

   return lRet;
}

// 0
//
#undef STDFLD
#define STDFLD(fld) #fld, FLD(CHANNEL,fld)
static DSFIELD aCHANNELfields[] = {
    "Mute This Channel", FLD(CHANNEL,iFlags), AS_BITFIELD | IS_EDIT, 1, 0,
    "Is Drum Channel",   FLD(CHANNEL,iFlags), AS_BITFIELD | IS_EDIT, 0, 0,
    //"Init Data",  0, sizeof(CHANNEL), AS_XMODE | IS_EDIT, (DWORD)fnChanData, 0,
    NULL, (UINT)-1, 0, AS_NONE, 0, 0,
    };

static DSFIELDTBL aStructs[] = {
    aINSTRUMENTfields,   "INSTRUMENT", sizeof(INSTRUMENT),
    aCHANNELfields,      "CHANNEL",    sizeof(CHANNEL),
    };

/*+ FindListChunk
 *
 *-======================================================================*/

LPRIFF FindListChunk (
   LPRIFFLIST pList,
   DWORD      fccToFind)
{
   UINT   cbRemain;
   LPRIFF pRiff;

   // in this code, we expect to have only MMAP lists
   //
   assert (pList);
   assert (pList->cbList > sizeof(RIFF) + sizeof(DWORD));
   assert (pList->fccType == FCC_MMAP);

   cbRemain = pList->cbList - sizeof(DWORD);
   assert (cbRemain < 0x10000); // reasonableness check

   // scan for the requested chunk
   //
   pRiff = (LPRIFF)(pList+1);
   for (;;)
      {
      UINT cbAdvance = pRiff->cb + (pRiff->cb & 1) + sizeof(RIFF);
      if (pRiff->fcc == fccToFind)
         return pRiff;
      if (cbRemain <= cbAdvance)
         break;

      cbRemain -= cbAdvance;
      pRiff = NEXTRIFF(pRiff);
      }

   return NULL;
}

/*+
 *
 *-======================================================================*/

LPVOID CopyForEditing (
   LPVOID pData,
   UINT   cbData)
{
   LPVOID pDataT;

   pDataT = HeapAlloc (gs.idf.hEditHeap, 0, cbData);
   if (pDataT)
      CopyMemory (pDataT, pData, cbData);
   return pDataT;
}

/*+
 *
 *-======================================================================*/

void FreeInstrumEdits (
   LPINSTRUMENT pInstrum)
{
   UINT      ii;

   // free anything that has been dynamically allocated for editing
   // purposes.  (currently only sysex channel init data)
   //
   for (ii = 0; ii < NUMELMS(pInstrum->aChannel); ++ii)
      {
      LPCHANNEL pChan = &pInstrum->aChannel[ii];

      // if the pointer is a valid writeable pointer, assume it
      // cam from the edit heap.
      //
      if (pChan->lpInit && !IsBadWritePtr(pChan->lpInit, 1))
         HeapFree (gs.idf.hEditHeap, 0, pChan->lpInit);

      ZeroMemory (pChan, sizeof(*pChan));
      }
}

/*+ IdentityMap
 *
 *-======================================================================*/

STATICFN void _inline IdentityMap (
   LPBYTE abData)
{
   BYTE ii;

   assert (MAX_PATCH == MAX_KEYMAP);

   for (ii = 0; ii < MAX_KEYMAP; ++ii)
      abData[ii] = ii;
}

/*+ IsIdentityMap
 *
 *-======================================================================*/

STATICFN BOOL IsIdentityMap (
   LPBYTE abData)
{
   BYTE ii;

   assert (MAX_PATCH == MAX_KEYMAP);

   for (ii = 0; ii < MAX_KEYMAP; ++ii)
      if (abData[ii] != ii)
         return FALSE;
   return TRUE;
}

/*+
 *
 *-======================================================================*/

void CopyInstrumData (
   LPINSTRUMENT pInstrum,
   LPRIFFLIST   pList)
{
   LPRIFF pRiff;
   UINT   ii;

   #pragma message (SQUAWK "free channel data first?")

   // initialize to defaults for optional fields in the IDF file
   //
   FreeInstrumEdits (pInstrum);
   ZeroMemory (pInstrum, sizeof(*pInstrum));
   IdentityMap (pInstrum->aPatch);
   IdentityMap (pInstrum->aGenKeymap);
   IdentityMap (pInstrum->aDrumKeymap);
   pInstrum->aChannel[9].iFlags = CHANNEL_IS_DRUM;

   assert (pList);
   if (!pList)
      return;

   // is there a header chunk? if so, then
   // copy it's data into the instrum structure
   //
   pRiff = FindListChunk (pList, FCC_hdr);
   if (pRiff && pRiff->cb >= sizeof(IDFHEADER))
      {
      LPIDFHEADER phdr = (LPVOID)(pRiff+1);

      pInstrum->dwVersion = phdr->dwVersion;
      pInstrum->dwCreator = phdr->dwCreator;

      // copy instrument id, and force a NULL terminator
      //
      CopyMemory (pInstrum->szInstID,
                  phdr->abInstID,
                  max(phdr->cbInstID, NUMELMS(pInstrum->szInstID)));
      pInstrum->szInstID[NUMELMS(pInstrum->szInstID)-1] = 0;
      }

   // manufacturer info
   //
   pRiff = FindListChunk (pList, FCC_inst);
   if (pRiff && pRiff->cb >= sizeof(IDFINSTINFO))
      {
      LPIDFINSTINFO pinst = (LPVOID)(pRiff+1);

      #pragma message (SQUAWK "get tables of manufactures & products")

      pInstrum->dwManufactID = pinst->dwManufactID;
      pInstrum->dwProductID = pinst->dwProductID;
      pInstrum->dwRevision = pinst->dwRevision;

     #ifdef UNICODE
      CopyMemory (pInstrum->szManufact, pinst->abData,
                  max(pinst->cbManufactUNICODE, sizeof(pInstrum->szManufact)));
     #else
      CopyMemory (pInstrum->szManufact, pinst->abData,
                  max(pinst->cbManufactASCII, sizeof(pInstrum->szManufact)));
     #endif
      pInstrum->szManufact[NUMELMS(pInstrum->szManufact)-1] = 0;

     #ifdef UNICODE
      CopyMemory (pInstrum->szProduct,
                  pinst->abData + pinst->cbManufactASCII + pinst->cbManufactUNICODE,
                  max(pinst->cbProductUNICODE, sizeof(pInstrum->szProduct)));
     #else
      CopyMemory (pInstrum->szProduct,
                  pinst->abData + pinst->cbManufactASCII + pinst->cbManufactUNICODE,
                  max(pinst->cbProductASCII, sizeof(pInstrum->szProduct)));
     #endif
      pInstrum->szProduct[NUMELMS(pInstrum->szProduct)-1] = 0;
      }

   // capabilities
   //
   pRiff = FindListChunk (pList, FCC_caps);
   if (pRiff && pRiff->cb >= sizeof(IDFINSTCAPS))
      {
      LPIDFINSTCAPS pcaps = (LPVOID)(pRiff+1);

      pInstrum->dwFlags = pcaps->fdwFlags;
      pInstrum->dwBasicChannel = pcaps->dwBasicChannel;
      pInstrum->nChannels      = pcaps->cNumChannels;
      pInstrum->nInstrumentPolyphony = pcaps->cInstrumentPolyphony;
      pInstrum->nChannelPolyphony = pcaps->cChannelPolyphony;
      }

   // per channel init data
   //
   pRiff = FindListChunk (pList, FCC_chan);
   if (pRiff && pRiff->cb >= sizeof(IDFCHANNELHDR))
      {
      LPIDFCHANNELHDR  pchan = (LPVOID)(pRiff+1);
      LPIDFCHANNELINFO pinfo = (LPVOID)(pchan+1);
      UINT cbRemain = pRiff->cb - sizeof(IDFCHANNELHDR);

      for (ii = 0; ii < NUMELMS(pInstrum->aChannel); ++ii)
      {
         pInstrum->aChannel[ii].iFlags &= ~CHANNEL_IS_MUTE;
         if (pchan->dwDrumMask & (1 << ii))
            pInstrum->aChannel[ii].iFlags |= CHANNEL_IS_DRUM;
         else
         {
            pInstrum->aChannel[ii].iFlags &= ~CHANNEL_IS_DRUM;
            if (!(pchan->dwGeneralMask & (1 << ii)))
               pInstrum->aChannel[ii].iFlags |= CHANNEL_IS_MUTE;
         }
      }

      for (ii = 0; /*ii < pchan->cNumChannels*/ ;++ii)
         {
         if (cbRemain < sizeof(IDFCHANNELINFO) ||
             cbRemain < pinfo->cbStruct)
            break;

         if (pinfo->dwChannel < NUMELMS(pInstrum->aChannel))
            {
            LPCHANNEL pChan = &pInstrum->aChannel[pinfo->dwChannel];
            pChan->cbInit = pinfo->cbInitData;
            pChan->lpInit = (LPVOID)pinfo->abData;
            }

         cbRemain -= pinfo->cbStruct;
         if (cbRemain < sizeof(IDFCHANNELINFO))
            break;

         pinfo = (LPVOID)((LPBYTE)pinfo + pinfo->cbStruct);
         }
      }
   else
      {
      pRiff = FindListChunk (pList, FCC_chnl);
      if (pRiff && pRiff->cb >= sizeof(IDFCHNLHDR))
         {
         LPIDFCHNLHDR pchnl = (LPVOID)(pRiff+1);
         LPIDFCHNLINFO pinfo = (LPVOID)(pchnl+1);
         UINT cbRemain = pRiff->cb;

         if (cbRemain >= sizeof(IDFCHNLHDR) + sizeof(IDFCHNLINFO))
            {
            cbRemain -= sizeof(IDFCHNLHDR);

            for (ii = 0; ii < pchnl->cNumChannels; ++ii)
               {
               if (cbRemain < pinfo->cbStruct)
                  break;

               if (pinfo->dwChannel < NUMELMS(pInstrum->aChannel))
                  {
                  LPCHANNEL pChan = &pInstrum->aChannel[pinfo->dwChannel];

                  // for now, just point channel data at riff data, we'll
                  // copy it when they try to actually edit it.
                  //
                  if (pinfo->fdwChannel & IDFCHNLINFO_F_DRUM_CHANNEL)
                     {
                     pChan->iFlags |= CHANNEL_IS_DRUM;
                     pChan->cbInit = pinfo->cbDrumInitData;
                     pChan->lpInit = (LPVOID)(pinfo->abData + pinfo->cbGeneralInitData);
                     }
                  else
                     {
                     pChan->iFlags &= ~CHANNEL_IS_DRUM;
                     pChan->cbInit = pinfo->cbGeneralInitData;
                     pChan->lpInit = (LPVOID)pinfo->abData;
                     }
                  }

               cbRemain -= pinfo->cbStruct;
               if (cbRemain < sizeof(IDFCHNLINFO))
                  break;

               pinfo = (LPVOID)((LPBYTE)pinfo + pinfo->cbStruct);
               }
            }
         }
      }

   // patch map
   //
   pRiff = FindListChunk (pList, FCC_pmap);
   if (pRiff && pRiff->cb >= sizeof(IDFPATCHMAPHDR))
      {
      LPIDFPATCHMAPHDR ppmap = (LPVOID)(pRiff+1);
      CopyMemory (pInstrum->aPatch, ppmap->abPatchMap, sizeof(pInstrum->aPatch));
      }

   //
   // key map.  we support old style 'key' chunk.  but will override
   // it with the data from a new style 'gkey' & 'dkey' chunk if both
   // are found
   //

   // old style 'key ' chunk
   //
   pRiff = FindListChunk (pList, FCC_key);
   if (pRiff && pRiff->cb >= sizeof(IDFKEYHDR))
      {
      LPIDFKEYHDR pkey = (LPVOID)(pRiff+1);
      LPIDFKEY    pmap = (LPVOID)(pkey+1);
      UINT        nMaps = pkey->cNumKeyMaps;
      UINT        cbRemain = pRiff->cb - sizeof(IDFKEYHDR);

      // copy keymaps as we find them, we only recognize 2 differnent
      // keymap types however.  and we only honor the last instance
      // each type that we find
      //
      while (nMaps > 0 && cbRemain >= sizeof(IDFKEY))
         {
         if (IDFKEY_F_GENERAL_CHANNEL == pmap->fdwKeyMap)
             CopyMemory (pInstrum->aGenKeymap, pmap->abKeyMap, sizeof(pInstrum->aGenKeymap));
         else if (IDFKEY_F_DRUM_CHANNEL == pmap->fdwKeyMap)
             CopyMemory (pInstrum->aDrumKeymap, pmap->abKeyMap, sizeof(pInstrum->aDrumKeymap));
         --nMaps;
         ++pmap;
         cbRemain -= sizeof(IDFKEY);
         }
      }

   // 'gkey' chunk
   //
   pRiff = FindListChunk (pList, FCC_gkey);
   if (pRiff && pRiff->cb >= sizeof(IDFKEYMAP))
      {
      LPIDFKEYMAP pgkey = (LPVOID)(pRiff+1);

      assert (sizeof(pgkey->abKeyMap) == sizeof(pInstrum->aGenKeymap));
      CopyMemory (pInstrum->aGenKeymap, pgkey->abKeyMap, sizeof(pInstrum->aGenKeymap));
      }

   // 'dkey' chunk
   //
   pRiff = FindListChunk (pList, FCC_dkey);
   if (pRiff && pRiff->cb >= sizeof(IDFKEYMAP))
      {
      LPIDFKEYMAP pdkey = (LPVOID)(pRiff+1);

      assert (sizeof(pdkey->abKeyMap) == sizeof(pInstrum->aDrumKeymap));
      CopyMemory (pInstrum->aDrumKeymap, pdkey->abKeyMap, sizeof(pInstrum->aDrumKeymap));
      }
}

/*+ SaveInstrumToRiff
 *
 *-======================================================================*/

VOID SaveInstrumToRiff(
    LPRIFFLIST   pList,
    LPINSTRUMENT pInstrum)
{
    LPRIFF pRiff = (LPVOID)(pList+1);

    // hdr
    {
    LPIDFHEADER phdr = (LPVOID)(pRiff+1);
    UINT        cb = lstrlen(pInstrum->szInstID) + 1;
    phdr->cbStruct = sizeof(*phdr) + cb;
    phdr->dwVersion = pInstrum->dwVersion;
    phdr->dwCreator = pInstrum->dwCreator;
    phdr->cbInstID = cb;
    lstrcpy (phdr->abInstID, pInstrum->szInstID);
    phdr->abInstID[cb-1] = 0;
    pRiff->fcc = FCC_hdr;
    pRiff->cb = phdr->cbStruct;
    pRiff = NEXTRIFF(pRiff);
    }

    // inst
    //
    {
    LPIDFINSTINFO pinst = (LPVOID)(pRiff+1);
    UINT          cb1 = lstrlen(pInstrum->szManufact)+1;
    UINT          cb2 = lstrlen(pInstrum->szProduct)+1;
    cb1 += (cb1 & 1);
    cb2 += (cb2 & 2);
    pinst->cbStruct = sizeof(*pinst) + (cb1 + cb2) * 3;
    pinst->dwManufactID = pInstrum->dwManufactID;
    pinst->dwProductID = pInstrum->dwProductID;
    pinst->dwRevision = pInstrum->dwRevision;
    pinst->cbManufactASCII = cb1;
    pinst->cbManufactUNICODE = cb1 * 2;
    pinst->cbProductASCII = cb2;
    pinst->cbProductUNICODE = cb2 * 2;
    ZeroMemory (pinst->abData, (cb1 + cb2) * 3);
   #ifdef UNICODE
    #error not yet implemented
   #else
    lstrcpy (pinst->abData, pInstrum->szManufact);
    MultiByteToWideChar(CP_ACP, 0, pInstrum->szManufact,
                        cb1, (LPWSTR)(pinst->abData + cb1),  cb1);
    lstrcpy (pinst->abData + (cb1 * 3), pInstrum->szProduct);
    MultiByteToWideChar(CP_ACP, 0, pInstrum->szProduct,
                        cb2, (LPWSTR)(pinst->abData + (cb1 * 3) + cb2), cb2);
   #endif
    pRiff->fcc = FCC_inst;
    pRiff->cb = pinst->cbStruct;
    pRiff = NEXTRIFF(pRiff);
    }

    // caps
    //
    {
    LPIDFINSTCAPS pcaps = (LPVOID)(pRiff+1);
    pcaps->cbStruct = sizeof(*pcaps);
    pcaps->fdwFlags = pInstrum->dwFlags;
    pcaps->dwBasicChannel = pInstrum->dwBasicChannel;
    pcaps->cNumChannels = pInstrum->nChannels;
    pcaps->cInstrumentPolyphony = pInstrum->nInstrumentPolyphony;
    pcaps->cChannelPolyphony = pInstrum->nChannelPolyphony;
    pRiff->fcc = FCC_caps;
    pRiff->cb = pcaps->cbStruct;
    pRiff = NEXTRIFF(pRiff);
    }

    //
    // chan chunk
    //
    {
    LPIDFCHANNELHDR  pchan = (LPVOID)(pRiff+1);
    LPIDFCHANNELINFO pinfo = (LPVOID)(pchan+1);
    UINT cbTotal = 0;
    UINT ii;

    pchan->cbStruct = sizeof(*pchan);
    pchan->dwDrumMask = 0;
    pchan->dwGeneralMask = 0x0000FFFF;
    for (ii = 0; ii < NUMELMS(pInstrum->aChannel); ++ii)
        if (pInstrum->aChannel[ii].iFlags & CHANNEL_IS_MUTE)
            pchan->dwGeneralMask &= ~(1 << ii);
        else if (pInstrum->aChannel[ii].iFlags & CHANNEL_IS_DRUM)
            pchan->dwDrumMask |= (1 << ii);

    pchan->dwGeneralMask &= ~(pchan->dwDrumMask);
    pchan->fdwFlags = 0;

    cbTotal = pchan->cbStruct;

    for (ii = 0; ii < NUMELMS(pInstrum->aChannel); ++ii)
       {
       LPCHANNEL pChan = &pInstrum->aChannel[ii];

       // if we have non-default channel info. append
       // channel info data
       //
       if (pChan->cbInit)
          {
          UINT cbData = (pChan->cbInit + 3) & ~3;

          pinfo->dwChannel = ii;
          pinfo->cbInitData = cbData;
          ZeroMemory (pinfo->abData, cbData);
          CopyMemory (pinfo->abData, pChan->lpInit, pChan->cbInit);

          // advance pinfo to the next channel header
          //
          //++pchan->cNumChannels;
          pinfo->cbStruct = sizeof(*pinfo) + cbData;
          cbTotal += pinfo->cbStruct;
          pinfo = (LPVOID)(((LPBYTE)pinfo) + pinfo->cbStruct);
          }
       }

    // save the chan chunk
    //
    pRiff->fcc = FCC_chan;
    pRiff->cb = cbTotal;
    pRiff = NEXTRIFF(pRiff);
    }

    // pmap
    //
    {
    if (!IsIdentityMap(pInstrum->aPatch))
       {
       LPIDFPATCHMAPHDR ppmap = (LPVOID)(pRiff+1);
       ppmap->cbStruct = sizeof(*ppmap);
       CopyMemory (ppmap->abPatchMap, pInstrum->aPatch, sizeof(ppmap->abPatchMap));
       pRiff->fcc = FCC_pmap;
       pRiff->cb = ppmap->cbStruct;
       pRiff = NEXTRIFF(pRiff);
       }

    //
    // gkey chunk (general keymap)
    //
    if (!IsIdentityMap(pInstrum->aGenKeymap))
       {
       LPIDFKEYMAP pgkey = (LPVOID)(pRiff+1);
       pgkey->cbStruct = sizeof(*pgkey);
       CopyMemory (pgkey->abKeyMap, pInstrum->aGenKeymap, sizeof(pgkey->abKeyMap));
       pRiff->fcc = FCC_gkey;
       pRiff->cb = sizeof(*pgkey);
       pRiff = NEXTRIFF(pRiff);
       }
    // dkey chunk (drum keymap)
    //
    if (!IsIdentityMap(pInstrum->aDrumKeymap))
       {
       LPIDFKEYMAP pdkey = (LPVOID)(pRiff+1);
       pdkey->cbStruct = sizeof(*pdkey);
       CopyMemory (pdkey->abKeyMap, pInstrum->aDrumKeymap, sizeof(pdkey->abKeyMap));
       pRiff->fcc = FCC_dkey;
       pRiff->cb = sizeof(*pdkey);
       pRiff = NEXTRIFF(pRiff);
       }
    }

    pList->fccList = FCC_LIST;
    pList->cbList = (DWORD)pRiff - (DWORD)pList - sizeof(RIFF);
    pList->fccType = FCC_MMAP;
}

/*+ GetBackupName
 *
 *-======================================================================*/

BOOL GetBackupName (
   LPTSTR pszBak,
   LPTSTR pszName)
{
   LPTSTR psz;
   lstrcpy (pszBak, pszName);
   psz = pszBak + lstrlen(pszBak);
   while (psz > pszBak)
      {
      if (*psz == TEXT(':'))
         break;
      if (*psz == TEXT('\\'))
         break;
      if (*psz == TEXT('.'))
         *psz = 0;

      --psz;
      }
   lstrcat (pszBak, TEXT(".id-"));
   return TRUE;
}

/*+ InstrumMaxRiffDataSize
 *
 *-======================================================================*/

STATICFN UINT InstrumMaxRiffDataSize (
   LPINSTRUMENT pInstrum)
{
   UINT cbData;
   UINT ii;

   cbData = (sizeof(RIFF) * 6)
            + sizeof(IDFHEADER) + MAX_NAME
            + sizeof(IDFINSTINFO) + (MAX_NAME * 6)
            + sizeof(IDFINSTCAPS)
            + sizeof(IDFCHNLHDR)
            + sizeof(IDFPATCHMAPHDR)
            + sizeof(IDFKEYHDR) + (sizeof(IDFKEY) * 2);

   for (ii = 0; ii < NUMELMS(pInstrum->aChannel); ++ii)
       {
       LPCHANNEL pChan = &pInstrum->aChannel[ii];
       cbData += pChan->cbInit + sizeof(IDFCHNLINFO);
       }
   return cbData;
}

/*+ SaveIDFToFile
 *
 *-======================================================================*/

BOOL SaveIDFToFile (
   LPIDFHEAD pIDF,
   LPTSTR    pszFileIn)
{
   HANDLE hFile = NULL;
   HANDLE hSection = NULL;
   LPBYTE pBase = NULL;
   TCHAR  szBakName[MAX_PATH];
   LPTSTR pszFile = pszFileIn;
   DWORD  cbFile;
   UINT   ii;
   LPRIFFLIST pList;

   // are we saving to the same name? if so
   // rename the old file to a backup name
   //
   szBakName[0] = 0;
   if (!lstrcmpi(pszFile, pIDF->szFile))

{
      if (pIDF->bReadOnly)
         return FALSE;

      GetBackupName (szBakName, pszFile);
      pszFile = szBakName;
   }

   // determine necessary file size
   //
   cbFile = sizeof(RIFFLIST) * 2;
   for (ii = 0; ii < pIDF->nInstrum; ++ii)
      {
      cbFile += sizeof(RIFFLIST);
      if (pIDF->ai[ii].pInstrum)
         {
         cbFile += InstrumMaxRiffDataSize (pIDF->ai[ii].pInstrum);
         }
      else if (pIDF->ai[ii].pList)
         {
         cbFile += pIDF->ai[ii].pList->cbList;
         }
      else
         {
         assert (0); // should never get here
         }
      }

   // open a writable mapped file of the size
   // necessary to write the IDF data
   //
   hFile = CreateFile (pszFile,
                       GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ,
                       NULL, // security
                       OPEN_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);
   if (INVALID_HANDLE_VALUE == hFile)
      goto error_exit;

   SetFilePointer (hFile, cbFile, NULL, FILE_BEGIN);
   SetEndOfFile (hFile);

   hSection = CreateFileMapping (hFile, NULL,
                                 PAGE_READWRITE, 0, 0, NULL);
   if (INVALID_HANDLE_VALUE == hSection)
      goto error_exit;

   pBase = MapViewOfFile (hSection, FILE_MAP_ALL_ACCESS, 0, 0, 0);
   if (NULL == pBase)
      goto error_exit;

   // Create RIFF data into the memory mapped file
   //
   pList = (LPVOID)pBase;
   pList->fccList = FCC_RIFF;
   pList->cbList = 0;
   pList->fccType = FCC_IDF;
   ++pList;
   for (ii = 0; ii < pIDF->nInstrum; ++ii)
      {
      struct _instrum_info * pi = &pIDF->ai[ii];

      pList->cbList = 0;

      if (pi->pInstrum)
         {
         SaveInstrumToRiff(pList, pi->pInstrum);
         }
      else if (pi->pList)
         {
         CopyMemory (pList, pi->pList,
                     pi->pList->cbList + sizeof(RIFF));
         }
      else
         {
         assert(0);
         }

      if (pList->cbList > 0)
         pList = (LPVOID) NEXTRIFF(pList);
      }
   cbFile = (DWORD)pList - (DWORD)pBase;
   ((LPRIFF)pBase)->cb = cbFile - sizeof(RIFF);

   UnmapViewOfFile (pBase);
   CloseHandle (hSection);

   SetFilePointer (hFile, cbFile, NULL, FILE_BEGIN);
   SetEndOfFile (hFile);
   CloseHandle (hFile);

   // close the existing file, delete the old file,
   // rename the new file to the previous filename
   // and open the new file & display contents
   //
   FreeIDFFile (pIDF);
   if (pszFile == szBakName)
      {
      pszFile = pszFileIn;
      DeleteFile (pszFile);
      if ( ! MoveFile (szBakName, pszFile))
         {
         // inexplicable failure copy the back over the origonal,
         // int this case we delete the backup and return failure
         //
         DeleteFile (szBakName);
         return FALSE;
         }
      }

   // return success
   //
   return TRUE;

error_exit:
   if (pBase)
      UnmapViewOfFile (pBase);
   if (hSection && INVALID_HANDLE_VALUE != hSection)
      CloseHandle (hSection);
   if (hFile && INVALID_HANDLE_VALUE != hFile)
      CloseHandle (hFile);
   return FALSE;
}

/*+
 *
 *-======================================================================*/

VOID FreeIDFFile (
   LPIDFHEAD pIDF)
{

   if (pIDF->pFileBase)
      UnmapViewOfFile (pIDF->pFileBase), pIDF->pFileBase = NULL;

   if (pIDF->hSection && INVALID_HANDLE_VALUE != pIDF->hSection)
      CloseHandle (pIDF->hSection), pIDF->hSection = NULL;

   if (pIDF->hFile && INVALID_HANDLE_VALUE != pIDF->hFile)
      CloseHandle (pIDF->hFile), pIDF->hFile = NULL;

   if (gs.idf.hEditHeap)
      HeapDestroy (gs.idf.hEditHeap);
   gs.idf.hEditHeap = HeapCreate (0, 0x1000, 0x100000);

   gs.idf.vi.pTable = aStructs;
   gs.idf.vi.lpData = &gs.idf.instrum;

   pIDF->bChanged = FALSE;
   pIDF->nInstrum = 0;
   pIDF->piSelect = NULL;
   pIDF->pInstrumSelect = NULL;
   ZeroMemory (pIDF->ai, sizeof(pIDF->ai));
   ZeroMemory (&pIDF->instrum, sizeof(pIDF->instrum));

   pIDF->szFile[0] = 0;
}

typedef struct _idffile {
    DWORD fccRIFF;
    DWORD cbRIFF;
    DWORD fccIDF;
    } IDFFILE, * LPIDFFILE;

/*+
 *
 *-======================================================================*/

BOOL LoadIDFFromFile (
   LPIDFHEAD pIDF,
   LPTSTR    pszFile)
{
   LPIDFFILE  pIdfFile;
   LPRIFFLIST pList;
   DWORD      cbRemain;
   UINT       ii;

   if (!pszFile)
      pszFile = pIDF->szFile;

   pIDF->bReadOnly = FALSE;
   pIDF->hFile = CreateFile (pszFile,
                             GENERIC_READ | GENERIC_WRITE,
                             FILE_SHARE_READ,
                             NULL, // security
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL,
                             NULL);
   if (INVALID_HANDLE_VALUE == pIDF->hFile)
   {
      pIDF->bReadOnly = TRUE;
      pIDF->hFile = CreateFile (pszFile,
                                GENERIC_READ,
                                FILE_SHARE_READ,
                                NULL, // security
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL);

      if (INVALID_HANDLE_VALUE == pIDF->hFile)
         goto error_exit;
   }

   pIDF->hSection = CreateFileMapping (pIDF->hFile, NULL,
                                       PAGE_READONLY, //PAGE_READWRITE,
                                       0, 0, NULL);
   if (INVALID_HANDLE_VALUE == pIDF->hSection)
      goto error_exit;

   pIDF->pFileBase = MapViewOfFile (pIDF->hSection, FILE_MAP_READ, 0, 0, 0);
   if (NULL == pIDF->pFileBase)
      goto error_exit;
   pIDF->cbFile = GetFileSize (pIDF->hFile, NULL);

   // verify that this is indeed a valid IDF file
   //
   pIdfFile = (LPVOID)pIDF->pFileBase;
   if (pIdfFile->fccRIFF != FCC_RIFF ||
       pIdfFile->cbRIFF + sizeof(RIFF) > pIDF->cbFile ||
       pIdfFile->fccIDF != FCC_IDF)
       goto error_exit;

   lstrcpy (pIDF->szFile, pszFile);

   // now parse the IDF file and find the instruments therein
   //
   pList = (LPVOID)(pIdfFile + 1);
   cbRemain = pIdfFile->cbRIFF;

   for (ii = 0;
        pList->fccList == FCC_LIST && (ii < NUMELMS(pIDF->ai));
        ++ii, pList = (LPVOID) NEXTRIFF(pList))
      {
      UINT cbAdvance = pList->cbList + (pList->cbList & 1) + sizeof(RIFF);
      pIDF->ai[ii].pList = pList;
      pIDF->nInstrum = ii+1;
      if (cbAdvance > cbRemain)
         break;
      cbRemain -= cbAdvance;
      }

   if (pIDF->nInstrum)
      pIDF->piSelect = &pIDF->ai[0];

   pIDF->bChanged = FALSE;

   return TRUE;

error_exit:
  #if defined DEBUG || defined _DEBUG
   {
   TCHAR sz[512];
   lstrcpy (sz, "LoadIDFFromFile failed: ");
   FormatMessage (FORMAT_MESSAGE_IGNORE_INSERTS
                  | FORMAT_MESSAGE_FROM_SYSTEM,
                  0,
                  GetLastError(),
                  0,
                  sz + lstrlen(sz),
                  NUMELMS(sz) - lstrlen(sz),
                  NULL);
   lstrcat (sz, "\r\n");
   OutputDebugString (sz);
   }
  #endif
   FreeIDFFile (pIDF);
   return FALSE;
}

/*+ NewIDFInstrum
 *
 *-======================================================================*/

LPINSTRUMENT WINAPI NewIDFInstrum (
   LPIDFHEAD  pIDF,
   LPRIFFLIST pList, // optional RIFF init data for instrument
   LPSTR      pszInstrument)
{
   static VIEWINIT vi;
   LPINSTRUMENT    lpi;

   if (pIDF->nInstrum >= NUMELMS(pIDF->ai))
      return NULL;

   lpi = HeapAlloc (gs.idf.hEditHeap, HEAP_ZERO_MEMORY, sizeof(INSTRUMENT));
   if (lpi)
      {
      static struct {
          RIFFLIST    list;
          RIFF        rhdr;
          IDFHEADER   hdr;
          RIFF        rcaps;
          IDFINSTCAPS caps;
          }  gmMMAP = {
              FCC_LIST, sizeof(gmMMAP) - sizeof(RIFF), FCC_MMAP, //list
              FCC_hdr, sizeof(IDFHEADER),           // rhdr
              sizeof(IDFHEADER), 0x100, 1, 1, 0,    // hdr
              FCC_caps, sizeof(IDFINSTCAPS),
              sizeof(IDFINSTCAPS),          // caps size
              IDFINSTCAPS_F_GENERAL_MIDI,   // caps flags
              1,                            // caps basic channel
              16,                           // caps num channels
              16,                           // instrument polyphony
              16,                           // channel polyphony
          };

      // set default instrument data
      //
      if (!pList)
         pList = &gmMMAP.list;
      CopyInstrumData (lpi, pList);

      if (pszInstrument)
         lstrcpyA (lpi->szInstID, pszInstrument);

      pIDF->ai[pIDF->nInstrum].pList = pList;
      pIDF->ai[pIDF->nInstrum].pInstrum = lpi;
      pIDF->piSelect = &pIDF->ai[pIDF->nInstrum];
      ++pIDF->nInstrum;

      pIDF->bChanged = TRUE;
      }

   return lpi;
}

/*+ DeleteInstrum
 *
 *-======================================================================*/

VOID DeleteInstrum (
   LPIDFHEAD pIDF)
{
   UINT ii;

   // figure out which slot in the instrument array
   // is the selected one.
   //
   for (ii = 0; ii < pIDF->nInstrum; ++ii)
      {
      if (pIDF->piSelect == &pIDF->ai[ii])
         {
         struct _instrum_info info;
         assert (pIDF->nInstrum > 0);

         // exchange the selected instrument with the one
         // at the end of the list. then decrement the
         // count of valid instruments.
         //
         info = pIDF->ai[ii];
         pIDF->ai[ii] = pIDF->ai[pIDF->nInstrum-1];
         pIDF->ai[pIDF->nInstrum-1] = info;
         --pIDF->nInstrum;

         pIDF->bChanged = TRUE;
         Head_RefreshTree (pIDF->hWndHead);
         break;
         }
      }
}

/*+ CopyInstrumToClip
 *
 *-======================================================================*/

VOID CopyInstrumToClip (
   LPIDFHEAD pIDF)
{
   UINT ii;

   for (ii = 0; ii < pIDF->nInstrum; ++ii)
      {
      if (pIDF->piSelect == &pIDF->ai[ii])
         {
         HGLOBAL hMem;
         if (pIDF->piSelect->pInstrum)
            {
            LPRIFFLIST pList;
            UINT   cb = InstrumMaxRiffDataSize (pIDF->piSelect->pInstrum);

            hMem = GlobalAlloc (GHND | GMEM_ZEROINIT, cb);
            if (!(pList = GlobalLock (hMem)))
               return;

            SaveInstrumToRiff (pList, pIDF->piSelect->pInstrum);
            GlobalUnlock (hMem);
            GlobalReAlloc (hMem, pList->cbList + sizeof(RIFF), 0);
            }
         else if (pIDF->piSelect->pList)
            {
            LPBYTE lpv;
            UINT   cb = pIDF->piSelect->pList->cbList + sizeof(RIFF);
            hMem = GlobalAlloc (GHND, cb);
            if (!(lpv = GlobalLock (hMem)))
               return;
            CopyMemory (lpv, pIDF->piSelect->pList, cb);
            GlobalUnlock (hMem);
            }

         if (!OpenClipboard (hWndMain))
            GlobalFree (hMem);
         else
            {
            EmptyClipboard ();
            SetClipboardData (CF_RIFF, hMem);
            CloseClipboard ();
            }
         }
      }
}

/*+ PasteInstrum
 *
 *-======================================================================*/

VOID PasteInstrum (
   LPIDFHEAD pIDF)
{
   HGLOBAL      hMem;
   LPRIFFLIST   pList;

   if (!OpenClipboard(hWndMain))
      return;

   if (hMem = GetClipboardData(CF_RIFF))
      {
      if (pList = GlobalLock (hMem))
         {
         if (pList->fccList == FCC_LIST && pList->fccType == FCC_MMAP)
            {
            NewIDFInstrum (pIDF, pList, NULL);
            Head_RefreshTree (pIDF->hWndHead);
            }
         GlobalUnlock (hMem);
         }
      }

   CloseClipboard ();

   return;
}


IDFEDIT.H

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  IDFEDIT.H
//**
//**  DESCRIPTION:
//**
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

#if !defined IDFEDIT_H
#define IDFEDIT_H

#include <idf.h>
#include "oldidf.h"
#include "res.h"
#include "globals.h"

// main.c
//
int  WINAPI ErrorBox(
   UINT    wStringID,      // stringtable ID
   UINT    wType,          // type of message box (MB_xxx flags)
   ...);

#define EB_FATAL (MB_OK | MB_ICONEXCLAMATION)
#define EB_ERROR (MB_OK | MB_ICONHAND)
#define EB_QUERY (MB_YESNO | MB_ICONQUESTION)
#define EB_INFO  (MB_OK | MB_ICONINFORMATION)
#define EB_YNC   (MB_YESNOCANCEL | MB_ICONQUESTION)

#if !defined SZCODE
  #define SZCODE STATIC CONST TCHAR
#endif

// debug stuff
//
#define SQUAWKNUMZ(num) #num
#define SQUAWKNUM(num) SQUAWKNUMZ(num)
#define SQUAWK __FILE__ "(" SQUAWKNUM(__LINE__) ") : --"
#if defined _DEBUG || defined DEBUG || defined DEBUG_RETAIL
  #define STATICFN
  #define DEBUGLINE __FILE__ "(" SQUAWKNUM(__LINE__) ") "
  #ifdef _X86_
    #define INLINE_BREAK _asm {int 3}
  #else
    #define INLINE_BREAK DebugBreak()
  #endif
  #define assert(exp) {\
      if (!(exp)) {\
          OutputDebugString(DEBUGLINE "assert failed: " #exp "\r\n"); \
      INLINE_BREAK; \
      }\
  }
#else
  #define STATICFN static
  #define assert(a) ((void)0)
  #define INLINE_BREAK
#endif

// idf.c
//
BOOL WINAPI PromptForIDFName (
    HWND    hwnd,
    LPTSTR  lpszPath,
    LPTSTR  lpszTitle,
    BOOL    fSave);

BOOL LoadIDFFromFile (
   LPIDFHEAD pIDF,
   LPTSTR    pszFile);

BOOL SaveIDFToFile (
   LPIDFHEAD pIDF,
   LPTSTR    pszFileIn);

VOID FreeIDFFile (
   LPIDFHEAD pIDF);

LPINSTRUMENT WINAPI NewIDFInstrum (
   LPIDFHEAD  pIDF,
   LPRIFFLIST pList,
   LPSTR      pszInstrument);

VOID DeleteInstrum (
   LPIDFHEAD pIDF);

VOID CopyInstrumToClip (
   LPIDFHEAD pIDF);

VOID PasteInstrum (
   LPIDFHEAD pIDF);

LPRIFF FindListChunk (
   LPRIFFLIST pList,
   DWORD      fccToFind);

void CopyInstrumData (
   LPINSTRUMENT pInstrum,
   LPRIFFLIST   pList);

LPVOID CopyForEditing (
   LPVOID pData,
   UINT cbData);

// head.c
//
LONG CALLBACK HeadWndProc (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam);

#define HM_REFRESH_TREE   (WM_USER+1)
#define Head_RefreshTree(hWnd) SendMessage (hWnd, HM_REFRESH_TREE, 0, 0);

// ======================== Global Variables =======================

// if NO_VARS is defined, dont refer to global variables
// at all.  this is typically used when creating binary
// data using structures defined in this file.
//
#ifndef NO_VARS
//
// if DECLARE_VARS is defined, create global variables
// otherwise just refer to them as externs.  this works
// so long as only one module defines DECLARE_VARS (usually winmain)
//
#ifdef DECLARE_VARS

 #define PUBLIC
 PUBLIC CONST TCHAR cszHdrClass[]  = "IDFHdr";
 PUBLIC CONST TCHAR cszInstrumClass[]  = "IDFInstrum";

#else

 #define PUBLIC extern
 PUBLIC CONST TCHAR cszHdrClass[];
 PUBLIC CONST TCHAR cszInstrumClass[];

#endif

PUBLIC HINSTANCE hInst;       // WinMain, set from hInstance
PUBLIC HWND      hWndMain;    // WinMain, set from CreateWindow
PUBLIC TCHAR     szApp[32];   // WinMain, set from IDS_APPNAME string
PUBLIC DWORD     fdwExStyle;  // WinMain
#ifdef USE_MDI
 PUBLIC HWND      hWndClient;  // handle to MDI Client
#endif

#endif // end of NO_VARS ifdef

#endif // IDFEDIT_H


MAIN.C

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  main.c
//**
//**  DESCRIPTION:
//**     Performs window class registration, creations and message
//**     polling.
//**
//************************************************************************

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdlib.h>
#include <stdio.h>

#define DECLARE_VARS
#include "idfedit.h"

/*+ ErrorBox ()
 *
 *  bring up a message box with a string from the stringtable
 *
 *-=================================================================*/

#include <stdarg.h>

int WINAPI ErrorBox (UINT wStringID, UINT wType, ...)
{
   char  szFormat[512];
   char  szErr[512];
   int   cbSize;
   va_list va;

   va_start (va, wType);
   cbSize = LoadString (hInst, wStringID, szFormat, NUMELMS(szFormat));
   if (!cbSize)
      wsprintf (szErr, "Error %d", wStringID);
   else
      wvsprintf (szErr, szFormat, va);
   va_end (va);

   return MessageBox (hWndMain, szErr, szApp, wType);
}

/*+ GetWindowPosFromIni
 *
 * retrieve the window position information from IDFEDIT.ini
 *
 *-=================================================================*/

static TCHAR cszProfile[] = TEXT ("MsIdfEd.ini");
static TCHAR cszSection[] = TEXT ("General");
static TCHAR cszWindow[]  = TEXT ("Window");
static TCHAR cszWindowDef[] = TEXT ("");
static TCHAR cszWindowFmt[] = TEXT ("%d,%d,%d,%d");

BOOL WINAPI GetWindowPosFromIni (
   LPRECT lprc)
   {
   TCHAR sz[100];
   RECT  rcScreen;
   RECT  rc;

   GetPrivateProfileString (cszSection, cszWindow, cszWindowDef,
                            sz, NUMELMS (sz), cszProfile);

   if ( ! SystemParametersInfo (SPI_GETWORKAREA, 0, &rcScreen, FALSE))
      {
      rcScreen.top = rcScreen.left = 0;
      rcScreen.right = 640;
      rcScreen.bottom = 480;
      }

   sscanf (sz, cszWindowFmt,
           &lprc->left, &lprc->top,
           &lprc->right, &lprc->bottom);

   if ( ! IntersectRect (&rc, &rcScreen, lprc))
      *lprc = rcScreen;

   return ! IsRectEmpty (lprc);
   }

/*+ SaveWindowPosToIni
 *
 * store the window position information in dragn.ini
 *
 *-=================================================================*/

BOOL WINAPI SaveWindowPosToIni (
   LPRECT lprc)
   {
   TCHAR sz[100];

   wsprintf (sz, cszWindowFmt,
            lprc->left,
            lprc->top,
            lprc->right - lprc->left,
            lprc->bottom - lprc->top);

   WritePrivateProfileString (cszSection, cszWindow, sz, cszProfile);

   return TRUE;
   }

/*+ AboutDlgProc()
 *
 *  callback function for the about dialog
 *
 *-=================================================================*/

BOOL CALLBACK AboutDlgProc (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   BOOL bRet = FALSE;

   switch (wMsgID)
   {
      case WM_COMMAND:
      {
         UINT  uID     = GET_WM_COMMAND_ID(wParam, lParam);
         //UINT Notify   = GET_WM_COMMAND_CMD(wParam, lParam);
         //HWND hWndCtl  = GET_WM_COMMAND_HWND(wParam, lParam);

         if (uID == IDOK || uID == IDCANCEL)
         {
            EndDialog(hWnd, uID);
            bRet = TRUE;
         }
         break;
      }
   }

   return bRet;
   }

/*+ GetActiveData
 *
 * helper function, gets data space from the active MDI child
 * window
 *
 *-=================================================================*/

static LPVOID GetActiveData ()
   {
   if (gs.pIDF && gs.pIDF->szFile[0])
      return gs.pIDF;
   return NULL;
   }

/*+
 *
 *-======================================================================*/

STATICFN VOID WINAPI SetStandardCaption (VOID)
   {
   TCHAR sz[MAX_PATH + 20];
   LoadString (hInst, IDS_CAPTION_BAR, sz, NUMELMS (sz));
   lstrcat (sz, TEXT(" - "));
   if (gs.szDefFile[0])
      lstrcat (sz, gs.szDefFile);
   else
      {
      UINT cb = lstrlen(sz);
      LoadString (hInst, IDS_DEF_FILE_NAME, sz + cb, NUMELMS(sz) - cb);
      }
   SetWindowText (hWndMain, sz);
   }

/*+ SaveAndCloseHeader
 *
 *-======================================================================*/

STATICFN BOOL SaveAndCloseHeader (
   HWND hWnd,
   BOOL bSaveAs)
   {
   LPIDFHEAD pIDF = gs.pIDF;
   BOOL      bRet;

   if (!pIDF)
      return TRUE;

   if (!pIDF->szFile[0])
      bSaveAs = TRUE;

   if (pIDF->bReadOnly)
   {
      ErrorBox (IDS_ERR_READONLY, EB_INFO, gs.szDefFile);
      bSaveAs = TRUE;
   }

   if (bSaveAs)
      {
      if (! PromptForIDFName(hWnd, gs.szDefFile, NULL, TRUE))
         return FALSE;
      }
   else
      lstrcpy (gs.szDefFile, pIDF->szFile);

   if (!(bRet = SaveIDFToFile (pIDF, gs.szDefFile)))
      ErrorBox (IDS_ERR_SAVE, EB_ERROR, gs.szDefFile);

   SetStandardCaption();
   return bRet;
   }

/*+ QuerySaveChanges
 *
 *-======================================================================*/

BOOL QuerySaveChanges (
   HWND hWnd,
   BOOL bSetForeground)
   {
   UINT  idBtn;
   DWORD dwEB = EB_YNC;

   if (!gs.pIDF)
      return TRUE;

   if (bSetForeground)
      dwEB |= MB_SETFOREGROUND;

   idBtn = ErrorBox (IDS_QUERY_SAVE, dwEB, gs.pIDF->szFile);
   if (idBtn == IDYES)
      return SaveAndCloseHeader (hWnd, FALSE);
   else if (idBtn == IDCANCEL)
      return FALSE;
   return TRUE;
   }

/*+ OpenHeader
 *
 *-======================================================================*/

STATICFN LONG WINAPI OpenHeader (
   HWND hWnd,
   BOOL bPrompt)
   {
   TCHAR szFile[MAX_PATH];
   TCHAR szTitle[MAX_PATH];
   LPIDFHEAD pIDF;

   pIDF = gs.pIDF = &gs.idf;
   if (gs.idf.bChanged && ! QuerySaveChanges (hWnd, FALSE))
      return 0;

   FreeIDFFile (gs.pIDF);

   szFile[0] = 0;
   if (bPrompt)
      {
      if ( ! PromptForIDFName (hWnd, szFile, szTitle, FALSE))
         return 0;

      lstrcpy (gs.szDefFile, szFile);
      LoadIDFFromFile (pIDF, szFile);
      }

   if (!pIDF->hWndHead)
      {
      RECT rc;
      GetClientRect (hWnd, &rc);

      pIDF->hWndHead = CreateWindowEx (fdwExStyle | WS_EX_NOPARENTNOTIFY,
                                       cszHdrClass,
                                       "",
                                       WS_CHILD | WS_VISIBLE,
                                       0, 0, rc.right, rc.bottom,
                                       hWnd,
                                       (HMENU)1,
                                       hInst,
                                       pIDF);
      }
   else
      {
      Head_RefreshTree (pIDF->hWndHead);
      }

   SetStandardCaption();

   return (LONG)pIDF->hWndHead;
   }

/*+ MainCommands
 *
 *-=================================================================*/

LONG WINAPI MainCommands (
   HWND   hWnd,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG lRet     = 1;
   WORD wID      = GET_WM_COMMAND_ID (wParam, lParam);
   WORD wNotify  = GET_WM_COMMAND_CMD (wParam, lParam);
   HWND hWndCtl  = GET_WM_COMMAND_HWND (wParam, lParam);

   switch (wID)
      {
      case IDM_ABOUT:
         return DialogBox (hInst,
                           MAKEINTRESOURCE(IDD_ABOUT),
                           hWnd,
                           AboutDlgProc);
         break;

      case IDM_HELP:
         ErrorBox (IDS_ERR_NOHELP, MB_OK);
         break;

      case IDM_FILEOPEN:
         lRet = OpenHeader (hWnd, TRUE);
         break;

      case IDM_FILENEW:
         lRet = OpenHeader (hWnd, FALSE);
         if (gs.pIDF)
         {
            NewIDFInstrum (gs.pIDF, NULL, "<untitled>");
            gs.pIDF->bChanged = FALSE;
            Head_RefreshTree (gs.pIDF->hWndHead);
         }
         break;

      case IDM_FILESAVE:
      case IDM_FILESAVEAS:
         lRet = SaveAndCloseHeader (hWnd, wID == IDM_FILESAVEAS);
         if (lRet)
         {
            // Saving has the side effect of also closing the file
            // so we need to re-open it after the save completes
            //
            LoadIDFFromFile (gs.pIDF, gs.szDefFile);
         }
         Head_RefreshTree (gs.pIDF->hWndHead);
         break;

      case IDM_NEW_INSTRUMENT:
         NewIDFInstrum (gs.pIDF, NULL, "<untitled>");
         Head_RefreshTree (gs.pIDF->hWndHead);
         break;

      case IDM_FILEEXIT:
         PostMessage (hWnd, WM_CLOSE, 0, 0);
         break;

      //case IDM_EDITUNDO:
      //   break;

      case IDM_EDITCUT:
      case IDM_EDITCOPY:
         if (gs.pIDF && gs.pIDF->piSelect)
            {
            CopyInstrumToClip (gs.pIDF);
            if (wID == IDM_EDITCUT)
               DeleteInstrum (gs.pIDF);
            }
         break;

      case IDM_EDITPASTE:
         PasteInstrum (gs.pIDF);
         break;

      case IDM_EDITDELETE:
         if (gs.pIDF && gs.pIDF->piSelect)
            DeleteInstrum (gs.pIDF);
         break;
      }

   return lRet;
   }

/*+ MainWndProc
 *
 *-=================================================================*/

LRESULT CALLBACK MainWndProc (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG  lRet = 0;         // return value from this routine

   switch (wMsgID)
      {
      case WM_COMMAND:
         lRet = MainCommands (hWnd, wParam, lParam);
         break;

      case WM_NOTIFY:
         break;

      case WM_SIZE:
         if (gs.idf.hWndHead)
            SetWindowPos (gs.idf.hWndHead, NULL,
                          0, 0, LOWORD(lParam), HIWORD(lParam),
                          SWP_NOZORDER);
         break;

      case WM_CREATE:
         {
         // send ourselves a command to create a document window.
         //
         PostMessage (hWnd, WM_COMMAND, IDM_FILEOPEN, 0);
         }
         break;

      case WM_INITMENUPOPUP:
         {
         HMENU  hPopup = (HMENU)wParam;
         UINT   uPos   = (UINT) LOWORD (lParam);
         BOOL   fSystemMenu = HIWORD (lParam);
         UINT   uFirstID;

         // if this is for the system menu, go no further.
         //
         if (fSystemMenu)
            break;

         // if this is the 'File' popup, do menu initialization
         //
         uFirstID = GetMenuItemID (hPopup, 0);
         if (uFirstID == IDM_FILENEW)
            {
            EnableMenuItem (hPopup, IDM_FILESAVE, gs.pIDF ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem (hPopup, IDM_FILESAVEAS, gs.pIDF ? MF_ENABLED : MF_GRAYED);
            }
         else if (uFirstID == IDM_EDITCUT)
            {
            BOOL bCanPaste = IsClipboardFormatAvailable(CF_RIFF);
            BOOL bCanCut = (gs.idf.piSelect != NULL);
            // BOOL bCanUndo = FALSE;

            // EnableMenuItem (hPopup, IDM_EDITUNDO, bCanUndo ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem (hPopup, IDM_EDITCUT, bCanCut ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem (hPopup, IDM_EDITDELETE, bCanCut ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem (hPopup, IDM_EDITCOPY, bCanCut ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem (hPopup, IDM_EDITPASTE, bCanPaste ? MF_ENABLED : MF_GRAYED);
            }
         break;
         }

      case WM_CLOSE:
      case WM_QUERYENDSESSION:
         {
         WINDOWPLACEMENT wpl;

         if (gs.idf.bChanged &&
             !QuerySaveChanges (hWnd, wMsgID == WM_QUERYENDSESSION))
            break;

         //
         // before we shutdown, save the current size and position
         // of the window to the INI file.
         //
         ZeroMemory (&wpl, sizeof(wpl));
         wpl.length = sizeof(wpl);
         GetWindowPlacement (hWnd, &wpl);

         SaveWindowPosToIni (&wpl.rcNormalPosition);

         lRet = DefWindowProc (hWnd, wMsgID, wParam, lParam);
         break;
         }

      case WM_DESTROY:
         // close the app (by causing an exit from the message loop)
         PostQuitMessage (0);
         break;

      default:
         lRet = DefWindowProc (hWnd, wMsgID, wParam, lParam);
      }
   return lRet;
   }

/*+ RegClasses ()
 *
 *  Registers all window classes that will be
 *   used in this application.
 *
 *  Called from WinMain
 *
 *  returns: the return value of the call to RegisterClass ()
 *          TRUE   if successful registration
 *          FALSE  if registration failed
 *
 *-=================================================================*/

static CONST TCHAR cszMainClass[] = "Main";

extern LRESULT WINAPI ViewWndProc (HWND, UINT, WPARAM, LPARAM);

BOOL WINAPI RegClasses (
   HANDLE hInstance)
   {
   WNDCLASS  wc;
   TCHAR     ach[2];

   LoadString(hInstance, IDS_IS_RTL, ach, sizeof(ach)/sizeof(ach[0]));
   fdwExStyle = (ach[0] == '1') ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0;

   //InitCommonControls();

   // register the main window class
   //
   ZeroMemory (&wc, sizeof(wc));
   wc.lpszClassName = cszMainClass;
   wc.lpfnWndProc   = MainWndProc;
   wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
   wc.hIcon         = LoadIcon (hInst, MAKEINTRESOURCE (IDR_MAIN));
   wc.lpszMenuName  = MAKEINTRESOURCE (IDR_MAIN);
   wc.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
   wc.hInstance     = hInstance;
   if (!RegisterClass (&wc))
       return FALSE;

   // register the idf instrument window class
   //
   ZeroMemory (&wc, sizeof(wc));
   wc.lpszClassName = cszHdrClass;
   wc.lpfnWndProc   = HeadWndProc;
   wc.style         = CS_VREDRAW | CS_HREDRAW  | CS_DBLCLKS;
   wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
   wc.hIcon         = LoadIcon (hInst, MAKEINTRESOURCE (IDR_MAIN));
   wc.lpszMenuName  = NULL;
   wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
   wc.hInstance     = hInstance;
   if (!RegisterClass (&wc))
       return FALSE;

   ZeroMemory (&wc, sizeof(wc));
   wc.lpszClassName = cszInstrumClass;
   wc.lpfnWndProc   = ViewWndProc;
   wc.style         = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
   wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
   wc.hIcon         = LoadIcon (hInst, MAKEINTRESOURCE (IDR_INSTRUM));
   wc.lpszMenuName  = NULL;
   wc.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
   wc.hInstance     = hInstance;
   if (!RegisterClass (&wc))
       return FALSE;

   return TRUE;
   }

/*+ CreateMainWindow ()
 *
 *  Creates the main window for the application.
 *
 *  Called from WinMain
 *
 *  returns:  a valid window handle if CreateWindow succeeds
 *            or NULL if CreateWindow Fails
 *
 *-=================================================================*/

HWND WINAPI CreateMainWindow (
   int nCmdShow)
   {
   HWND   hWnd;
   TCHAR  szTitle[100];
   RECT   rc;

   LoadString (hInst, IDS_CAPTION_BAR, szTitle, NUMELMS (szTitle));

   // get x, y, cx, cy positions from ini files
   //
   if ( ! GetWindowPosFromIni (&rc))
      rc.left = rc.right = CW_USEDEFAULT;

   hWnd = CreateWindowEx (fdwExStyle,        // RTL style
                       cszMainClass,         // class
                       szTitle,              // title
                       WS_OVERLAPPEDWINDOW,
                       rc.left,
                       rc.top,               // position: x,y
                       rc.right,
                       rc.bottom,            // size: width, height
                       NULL,                 // parent
                       NULL,                 // menu or child id
                       hInst,                // instance
                       NULL);                // params to pass on to WM_CREATE

   if (hWnd)
      ShowWindow (hWnd, nCmdShow);

   return hWnd;
   }

/*+ WinMain ()
 *
 *-=================================================================*/

int WINAPI WinMain (
   HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR lpszCmdLine,
   int nCmdShow)
   {
   MSG     msg;      // temp for current message
   HACCEL  hAccel;

   // save off instance handle in a global. THIS MUST BE DONE FIRST!
   // also load the application name into a global string
   //
   hInst = hInstance;
   LoadString (hInst, IDS_APPNAME, szApp, NUMELMS (szApp));

   // if this is not the first instance, register the class
   // if class registration fails, put up a message box and quit
   if (!RegClasses (hInstance))
       return ErrorBox (IDS_ERR_REGCLASSES, EB_FATAL);

   hAccel = LoadAccelerators (hInst, MAKEINTRESOURCE(IDR_MAIN));

   // create the main window and save it's window handle in a global
   // if the creation fails, put up a message box and quit.
   //
   hWndMain = CreateMainWindow (nCmdShow);
   if ( !hWndMain)
      return ErrorBox (IDS_ERR_CREATEMAIN, EB_FATAL);

   // loop forever getting messages and dispatching them
   // to the appropriate window.
   //
   while (GetMessage (&msg, NULL, 0,0))
   {
      if (TranslateAccelerator (hWndMain, hAccel, &msg))
         continue;

      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }

   // return the wParam of the last message (the QUIT message)
   //
   return msg.wParam;
   }


OLDIDF.H

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  IDF.h
//**
//**  DESCRIPTION:
//**     This file contains the format of the .IDF files.
//**
//**  HISTORY:
//**     04/29/93       created.
//**     07/04/93       added UNICODE for displayable strings.
//**     09/04/93       added keymaps.
//**     09/05/93       added ID's for channel types.
//**
//************************************************************************

#if 0

/*
@doc  EXTERNAL SDK

@types   IDFHEADER |
   This is the format of the "hdr " chunk in a Microsoft IDF file.
   This will be the first subchunk of a "MMAP" list chunk.  This will
   provide information on the IDF itself; what version, who created it,
   and a unique ASCII ID for the instrument.

@field   DWORD | cbStruct |
   This is the inclusive size of the header structure.

@field   DWORD | dwVersion |
   This is the version of the IDF file.
   under.

@field   DWORD | dwCreator |
   This is the creator ID for the IDF.

@field   DWORD | cbInstID |
   This is the size of the unique instrument identifier including the NULL.

@field   BYTE | abInstID[] |
   This is the actual ASCII bytes of the unique instrument id. This _IS_
   NULL terminated.  There is no UNICODE version of this since this will
   only be used internally and not displayed to the user.

@othertype  IDFHEADER * | PIDFHEADER |
   A memory model dependant pointer to the structure.

@othertype  IDFHEADER FAR* | LPIDFHEADER |
   A far pointer to the structure.
*/

typedef struct tag_IDFHEADER
{
   DWORD cbStruct;

   DWORD dwVersion;
   DWORD dwCreator;

   DWORD cbInstID;
   BYTE  abInstID[1];
} IDFHEADER, *PIDFHEADER, FAR *LPIDFHEADER;

/*
@doc  EXTERNAL SDK

@types   IDFINSTINFO |
   This is the format of the "inst" chunk in a Microsoft IDF file.
   This chunk will contain information on the instrument, e.g. who
   manufactured it, the name of the instrument, it's manufacturer and
   product id's and a revision number.

@field   DWORD | cbStruct |
   This is the inclusive size of the instrument information structure.

@field   DWORD | dwManufactID |
   Manufacturer's ID.

@field   DWORD | dwProductID |
   Product's ID.

@field   DWORD | dwRevision |
   Revision of the product.

@field   DWORD | cbManufactASCII |
   Length of the ASCII version of the manufacturuers name.

@field   DWORD | cbManufactUNICODE |
   Length of the UNICODE version of the manufacturuers name.

@field   DWORD | cbProductASCII |
   Length of the ASCII version of the product name.

@field   DWORD | cbProductUNICODE |
   Length of the UNICODE version of the product name.

@field   BYTE | abData[] |
   Contains the ASCII and UNICODE strings for the manufacturer and product
   names.  NOTE that all strings are separated by a NULL and the NULL IS
   counted in the string lengths.

@othertype  IDFINSTINFO * | PIDFINSTINFO |
   A memory model dependant pointer to the structure.

@othertype  IDFINSTINFO FAR * | LPIDFINSTINFO |
   A far pointer to the structure.

@comm The idea is that the cbManufactXXXXX and cbProductXXXXX will be the
      offsets into the bData array of bytes and it will contain a complete
      string that you can strcpy out.  That is why the NULL is included in
      the byte count and in the actual data.
*/

typedef struct tag_IDFINSTINFO
{
   DWORD cbStruct;

   DWORD dwManufactID;
   DWORD dwProductID;
   DWORD dwRevision;

   DWORD cbManufactASCII;
   DWORD cbManufactUNICODE;
   DWORD cbProductASCII;
   DWORD cbProductUNICODE;

   BYTE  abData[1];
} IDFINSTINFO, FAR *LPIDFINSTINFO;

/*
@doc  EXTERNAL SDK

@types   IDFINSTCAPS |
   This is the format of the "caps" chunk in a Microsoft IDF file.
   This chunk will contain information on the MIDI capabilities of
   the device. e.g. basic channel of the instrument, number of channels
   that the instrument has available. the polyphony of the instrument,
   whether or not it supports General MIDI, etc....

@field   DWORD | cbStruct |
   This is the size of the capabilities structure.

@field   DWORD | fdwFlags |
   Flags specifiying additional capabilities of an instrument.

@flag IDFINSTCAPS_F_GENERAL_MIDI |
   Instrument supports General MIDI.

@flag IDFINSTCAPS_F_SYSTEMEXCLUSIVE |
   Instrument supports system exclusive messages

@field   DWORD | dwBasicChannel |
   The basic channel for the instrument.

@field   DWORD | cNumChannels |
   Number of channels that the instrument supports to.

@field   DWORD | cInstrumentPolyphony |
   The total polyphony for the instrument.

@field   DWORD | cChannelPolyphony |
   The polyphony per channel.

@othertype  IDFINSTCAPS * | PIDFINSTCAPS |
   A memory model dependant pointer to the structure.

@othertype  IDFINSTCAPS FAR * | LPIDFINSTCAPS |
   A far pointer to the structure.
*/

typedef struct tag_IDFINSTCAPS
{
   DWORD cbStruct;
   DWORD fdwFlags;
   DWORD dwBasicChannel;
   DWORD cNumChannels;
   DWORD cInstrumentPolyphony;
   DWORD cChannelPolyphony;
} IDFINSTCAPS, *PIDFINSTCAPS, FAR *LPIDFINSTCAPS;

#define  IDFINSTCAPS_F_GENERAL_MIDI       0x00000001
#define  IDFINSTCAPS_F_SYSTEMEXCLUSIVE    0x00000002

#endif

/*
@doc  EXTERNAL SDK

@types   IDFCHNLHDR |
   This is the format of the "chnl" chunk in a Microsoft IDF file.
   This contains a description on what "type" a channel is, i.e.
   is it a General MIDI channel, a drum channel, etc... Directly
   following this header is the actual data on each channel.

@field   DWORD | cbStruct |
   This is the size of the channel header structure.

@field   DWORD | fdwFlags |
   Flags describing the channel type information.

@flag IDFCHNLHDR_F_GENERAL_MIDI |
   If this flag is set then any channels that are not defined in the
   IDF are General MIDI channel types.  If this flag is _NOT_ set then
   any channels that are not defined in the IDF are undefined and should
   NOT be used in mapping.

@field   DWORD | cNumChannels |
   This is the number of channels that follows the header.

@othertype  IDFCHNLHDR * | PIDFCHNLHDR |
   A memory model dependant pointer to the structure.

@othertype  IDFCHNLHDR FAR * | LPIDFCHNLHDR |
   A far pointer to the structure.
*/

typedef struct tag_IDFCHNLHDR
{
   DWORD cbStruct;

   DWORD cNumChannels;
   DWORD fdwFlags;
} IDFCHNLHDR, *PIDFCHNLHDR, FAR *LPIDFCHNLHDR;

#define  IDFCHNLHDR_F_GENERAL_MIDI     0x00000001

/*
@doc  EXTERNAL SDK

@types    IDFCHNLINFO |
   This is the format of the actual channel information for Microsoft
   authored IDF files. This is the Microsoft supported format for
   channel information.

@field   DWORD | dwChannel |
   This is the channel number that the structure defines.

@field   DWORD | fdwChannel |
   Defines the possible types this channel can be.

   @flag    IDFCHNLINFO_F_GENERAL_CHANNEL |
       Indicates that this channel may be a general channel.

   @flag    IDFCHNLINFO_F_DRUM_CHANNEL |
       Indicates that this channel may be a drum channel.

@field   DWORD | cbGeneralInitData |
    Specifies the length of the data which should be sent to initialize
    the channel to a general channel. This data will be sent whenever the
    channel is allocated as a general channel. If no initialization data
    is desired, this field should be set to zero. If more than one channel
    type is specified in the <f fdwChannel> field, initialization data
    must be specified and this field may not be zero.

@field   DWORD | cbDrumInitData |
    Specifies the length of the data which should be sent to initialize
    the channel to a drum channel. This data will be sent whenever the
    channel is allocated as a drum channel. If no initialization data
    is desired, this field should be set to zero. If more than one channel
    type is specified in the <f fdwChannel> field, initialization data
    must be specified and this field may not be zero.

@field    BYTE | abData[] |
    This field contains the actual initialization data to set the channel
    to general or drum. <f abData> contains first the initialization
    sequence to set the channel to a general channel, followed by
    the sequence to initialize the channel to a drum channel.
    <f cbGeneralInitData> and <f cbDrumInitData> should indicate the lengths
    of these sequences, byte aligned. However, the actual sequence should
    be padded so that it is actually DWORD aligned (i.e. even multiple
    of four bytes). <f cbStruct> should reflect the padded length of
    the sequences.

@othertype  IDFCHNLINFO * | PIDFCHNLINFO |
   A memory model dependant pointer to the structure.

@othertype  IDFCHNLINFO FAR * | LPIDFCHNLINFO |
   A far pointer to the structure.
*/

typedef struct tag_IDFCHNLINFO
{
   DWORD cbStruct;

   DWORD dwChannel;
   DWORD fdwChannel;

   DWORD cbGeneralInitData;
   DWORD cbDrumInitData;

   BYTE  abData[];

} IDFCHNLINFO, *PIDFCHNLINFO, FAR *LPIDFCHNLINFO;

//
// Currently defined channel types.
//
#define  IDFCHNLINFO_F_GENERAL_CHANNEL      0x00000001
#define  IDFCHNLINFO_F_DRUM_CHANNEL         0x00000002

#if 0

/*
@doc  EXTERNAL SDK

@types   IDFPATCHMAPHDR |
   This is the format of the "map " chunk in a Microsoft IDF file.
   This chunk contains information on the patch map used for the
   instrument. Directly following this header is the actual mapping
   information for each patch.

@field   DWORD | cbStruct |
   This is the size of the patch map header structure.

@field   BYTE | abPatchMap[128] |
   This array contains the actual patch map. The incoming patch is
   used to index the array; the array contents are the new patch value
   and must be in the range 0x00-0x7F. Patch maps will only apply to
   channels of type general. If the instrument needs a patch change
   on a drum channel, it should be included in the initialization data
   in the IDFCHANNELINFO.

@othertype  IDFPATCHMAPHDR * | PIDFPATCHMAPHDR |
   A memory model dependant pointer to the structure.

@othertype  IDFPATCHMAPHDR FAR * | LPIDFPATCHMAPHDR |
   A far pointer to the structure.
*/

typedef struct tag_IDFPATCHMAPHDR
{
   DWORD cbStruct;
   BYTE  abPatchMap[128];

} IDFPATCHMAPHDR, *PIDFPATCHMAPHDR, FAR *LPIDFPATCHMAPHDR;

#endif // 0

/*
@doc  EXTERNAL SDK

@types   IDFKEYHDR |
   This is the format of the "key " chunk in a Microsoft IDF file.
   This chunk contains information on the all of the key maps used
   for a given instrument. The information that is in this structure
   pertains to all of the key maps for the instrument.  It contains
   the total number of key maps for the instrument and whether or not
   the key maps are General MIDI.

@field   DWORD | cbStruct |
   This is the size of the key map header structure.

@field   DWORD | cNumKeyMaps |
   This is the number of key maps that follow the header.

@field   DWORD | cbKeyMap |
   This is the size of each key map that follows the header.

@othertype  IDFPATCHMAPHDR * | PIDFPATCHMAPHDR |
   A memory model dependant pointer to the structure.

@othertype  IDFPATCHMAPHDR FAR * | LPIDFPATCHMAPHDR |
   A far pointer to the structure.
*/

typedef struct tag_IDFKEYHDR
{
   DWORD cbStruct;
   DWORD cNumKeyMaps;
   DWORD cbKeyMap;
} IDFKEYHDR, *PIDFKEYHDR, FAR *LPIDFKEYHDR;

/*
@doc  EXTERNAL SDK

@types   IDFKEY |
   This is a keymap that follows the key map header.

@field   DWORD | cbStruct |
   This is the size of the key map header structure.

@field   DWORD | fdwKeyMapType |
   This field specifies the channel type this key map is valid for.

@flag IDFKEY_F_GENERAL_CHANNEL      0x00000001
@flag IDFKEY_F_DRUM_CHANNEL         0x00000002

@field   BYTE | abKeyMap[128] |
    This field contains the actual key map. The incoming key number from
    a note on or note off message is used to index this array; the array
    contents are the new key value. If the high bit it set in the new
    key value, then the note on or note off will be ignore; otherwise,
    it will be transmitted with the new key value.

@othertype  IDFKEY * | PIDFKEY |
   A memory model dependant pointer to the structure.

@othertype  IDFKEY FAR * | LPIDFKEY |
   A far pointer to the structure.
*/

typedef struct tag_IDFKEY
{
   DWORD cbStruct;
   DWORD fdwKeyMap;
   BYTE  abKeyMap[128];
} IDFKEY, *PIDFKEY, FAR *LPIDFKEY;

#define IDFKEY_F_GENERAL_CHANNEL      0x00000001L
#define IDFKEY_F_DRUM_CHANNEL         0x00000002L


DRAWSTR.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
*****************************************************************************/

/*+ DrawStr.c
 *
 *-=================================================================*/

#include <windows.h>
#include <windowsx.h>

#include <string.h>
#include <memory.h>
#include <stdlib.h>

#include "drawstr.h"
#include "tridee.h"
#include "res.h"
#include "idfedit.h"

CONST LOGFONT lfMain = {
    12,                        //int   lfHeight;
    0,                         //int   lfWidth;
    0,                         //int   lfEscapement;
    0,                         //int   lfOrientation;
    FW_DONTCARE,               //int   lfWeight;
    0,                         //BYTE  lfItalic;
    0,                         //BYTE  lfUnderline;
    0,                         //BYTE  lfStrikeOut;
    ANSI_CHARSET,              //BYTE  lfCharSet;
    OUT_DEFAULT_PRECIS,        //BYTE  lfOutPrecision;
    CLIP_DEFAULT_PRECIS,       //BYTE  lfClipPrecision;
    DEFAULT_QUALITY,           //BYTE  lfQuality;
    DEFAULT_PITCH | FF_SWISS,  //BYTE  lfPitchAndFamily;
    "MS Sans Serif",           //BYTE  lfFaceName[LF_FACESIZE];
    };

DSPREF dspref = {0};
BOOL   bInEditingMode;

#ifdef USE_MDI
 #define DefProc DefMDIChildProc
#else
 #define DefProc DefWindowProc
#endif

/*+  AccessFilter
 *
 *   exception filter for access violations
 *
 *   this function is a filter for an except block. It must
 *   return one of the following DWORD values:
 *      EXCEPTION_EXECUTE_HANDLER
 *      EXCEPTION_CONTINUE_SEARCH
 *      EXCEPTION_CONTINUE_EXECUTION
 *
 *-=================================================================*/

DWORD AccessFilter (
   DWORD dwExceptCode)
{
   if (dwExceptCode == EXCEPTION_ACCESS_VIOLATION)
      return EXCEPTION_EXECUTE_HANDLER;
   return EXCEPTION_CONTINUE_SEARCH;
}

/*+ DrawDsLabels
 *
 *-=================================================================*/

VOID WINAPI DrawDsLabels (
   HDC          hDC,
   PDSFIELDTBL  pTable,
   PDSLINEINFO  pLineInfo,
   LPRECT       lpRect,
   POINT        ptOffset)
   {
   WORD        wOldAlign;  // temp, prev state of alignment
   UINT        ii;         // general index variable
   int         cx;         // current x position to draw at
   int         cy;         // current y position to draw at
   int         cyText;
   PDSFIELD    pField;
   struct _dsline * pLine;
   char        sz[100];

   // do some simple error checking, if not a valid DC
   // dont go any further.
   //
   if (!hDC)
      return;

   // get the text metrics and initialize the max width variable
   //
   cyText = pLineInfo->cyLine;

   // setup the current x & y locations for writing text
   //
   cx = lpRect->left - ptOffset.x;
   cy = lpRect->top - ptOffset.y;

   // draw each of the labels making a vertical
   // column starting 'cx,cy'.  But be sure not to
   // draw outside of the rectangle 'lpRect'
   //
   wOldAlign = SetTextAlign (hDC, TA_TOP | TA_LEFT);

   // clear the background
   //
   ExtTextOut (hDC, cx, cy, ETO_CLIPPED | ETO_OPAQUE, lpRect, "", 0, NULL);

   // draw the labels
   //
   for (pLine = &pLineInfo->aLine[ii = 0];
        ii < pLineInfo->nCurLine;
        ++ii, ++pLine, cy += cyText)
      {
      if (cy < lpRect->top - cyText)
         continue;

      if (cy > lpRect->bottom)
         break;

      pField = pTable[pLine->uStruct].pFields;
      pField += pLine->uField;

      wsprintf (sz, pField->psz, pLine->uMember);

      if (sz[0] == ' ')
         {
         SetTextAlign (hDC, TA_TOP | TA_RIGHT);
         cx = lpRect->right - 2;
         }
      else
         {
         SetTextAlign (hDC, TA_TOP | TA_LEFT);
         cx = lpRect->left - ptOffset.x + (pLine->uDepth-1) * 16;
         }

      // draw the label pointed to by pFields[ii].psz
      // then increment cy by the height of a label
      //
      ExtTextOut (hDC,
                  cx,
                  cy,
                  ETO_CLIPPED,
                  lpRect,
                  sz,
                  lstrlen(sz),
                  NULL);
      }

   // restore text alignment and return
   //
   SetTextAlign (hDC, wOldAlign);

   return;
   }

/*+ GetFieldText
 *
 *-=================================================================*/

VOID WINAPI GetFieldText (
   PDSFIELD  pField,
   LPVOID    lpData,
   UINT      uMember,
   LPSTR     pszIn,
   UINT      cchIn,
   PDSBUFF   pds)
   {
   UINT    cb;
   LPBYTE  lp;

   pds->ptr = pszIn;
   pds->cch = cchIn;
   if (pds->cch > 10) // allow a bit of slop
      pds->cch -= 10;
   pds->ptr[0] = 0;

   __try
      {
      if (pField->fmt & AS_REF)
         {
         lp = *(LPBYTE *)lpData;
         lp += (uMember * pField->siz);

         if (dspref.bShowAddresses)
            {
            wsprintf (pds->ptr, "@%08x: ", lpData);
            cb = lstrlen(pds->ptr);
            pds->ptr += cb;
            pds->cch -= cb;
            }
         }
      else
         {
         lp = (LPBYTE)lpData + (uMember * pField->siz);

         if (dspref.bShowAddresses)
            {
            wsprintf (pds->ptr, "@%08x: ", lp);
            cb = lstrlen(pds->ptr);
            pds->ptr += cb;
            pds->cch -= cb;
            }
         }

      if (lp == NULL)
         lstrcpy (pds->ptr, "<null>");
      else
      switch (pField->fmt & 0xFF)
         {
         case AS_NONE:
            //wsprintf (pds->ptr, "%08X = ", lp);
            break;

         case AS_SZ:
            strncpy (pds->ptr, (LPTSTR)lp, pds->cch);
            break;

         case AS_MODE:
            {
            UINT (*pfnMode)(LPSTR, UINT, DWORD) = (LPVOID)pField->aux;

            if (pField->siz == sizeof(DWORD))
                pfnMode (pds->ptr, pds->cch, *(DWORD *)lp);
            else
                pfnMode (pds->ptr, pds->cch, (DWORD)lp);
            }
            break;

         case AS_XMODE:
            {
            UINT (*pfnMode)(LPSTR, UINT, UINT, UINT, UINT) = (LPVOID)pField->aux;

            if (pField->siz == sizeof(DWORD))
                pfnMode (pds->ptr, pds->cch, *(UINT *)lp, pField->array, WM_GETTEXT);
            else
                pfnMode (pds->ptr, pds->cch, (UINT)lp, pField->array, WM_GETTEXT);
            }
            break;

         case AS_ERROR:
            {
            UINT cb;
            DWORD dwError = *(LPDWORD)lp;

            pds->ptr[0] = 0;
            if (dwError)
                FormatMessage (FORMAT_MESSAGE_IGNORE_INSERTS
                               | FORMAT_MESSAGE_FROM_SYSTEM,
                               0,
                               dwError,
                               0,
                               pds->ptr,
                               pds->cch,
                               NULL);

            // get rid of terminating CR/LF if it exists
            //
            cb = lstrlen(pds->ptr);
            if (cb > 2)
               pds->ptr[cb-2] = 0;
            }
            break;

         case AS_ACH:
            {
            UINT jj;
            for (jj = 0; jj < pds->cch-1 && jj < pField->siz; ++jj)
                pds->ptr[jj] = lp[jj] > 31 ? lp[jj] : '.';
            pds->ptr[jj] = 0;
            }
            break;

         case AS_DUMP:
            {
            UINT   jj;
            UINT   cb;

            for (jj = 0; jj < pField->siz; jj += sizeof(WORD))
               {
               cb = wsprintf (pds->ptr, "%04X ", *(WORD *)(lp+jj));
               if (cb >= pds->cch)
                  {
                  pds->ptr[pds->cch-1] = 0;
                  break;
                  }

               pds->cch -= cb;
               pds->ptr += cb;
               }

            for (jj = 0; jj < pds->cch-1 && jj < pField->siz; ++jj)
                pds->ptr[jj] = lp[jj] > 31 ? lp[jj] : '.';
            pds->ptr[jj] = 0;
            }
            break;

         case AS_HEX:
            {
            UINT   jj;
            UINT   cb;

            if (pField->siz == sizeof(DWORD))
                pds->ptr += wsprintf (pds->ptr, "%08X", *(DWORD *)lp);
            else
            for (jj = 0; jj < pField->siz; jj += sizeof(WORD))
               {
               cb = wsprintf (pds->ptr, "%04X ", *(WORD *)(lp+jj));
               if (cb >= pds->cch)
                  {
                  pds->ptr[pds->cch-1] = 0;
                  break;
                  }

               pds->cch -= cb;
               pds->ptr += cb;
               }
            *pds->ptr = 0;
            }
            break;

         case AS_INT:
         case AS_UINT:
            if (sizeof(WORD) == pField->siz)
               wsprintf (pds->ptr, "%d", *(WORD *)lp);
            else if (sizeof(DWORD) == pField->siz)
               wsprintf (pds->ptr, "%d", *(DWORD *)lp);
            else
               {
               int  jj;
               UINT cb;

               for (jj = pField->siz-2; jj >= 0; jj -= sizeof(WORD))
                  {
                  cb = wsprintf (pds->ptr, "%d ", *(WORD *)(lp+jj));
                  if (cb >= pds->cch)
                     {
                     pds->ptr[pds->cch-1] = 0;
                     break;
                     }
                  pds->ptr += cb;
                  pds->ptr -= cb;
                  }
               *pds->ptr = 0;
               }
            break;

         case AS_HANDLE:
            wsprintf (pds->ptr, "%08X", *(UINT *)lp);
            break;

         case AS_BITFIELD:
            lstrcpy (pds->ptr, ((*(UINT *)lp & (1 << pField->aux)) ? "TRUE" : "FALSE"));
            break;

         case AS_RECT:
            wsprintf (pds->ptr, "%d,%d,%d,%d", *(LPRECT)lp);
            break;

         case AS_POINT:
         case AS_SIZE:
            wsprintf (pds->ptr, "%d,%d", *(LPPOINT)lp);
            break;

         case AS_POINTS:
            wsprintf (pds->ptr, "%d,%d", ((SHORT *)lp)[0], ((SHORT *)lp)[1]);
            break;

         case AS_FLOAT:
            lstrcpy (pds->ptr, "<float not supported>");
            break;

         case AS_BYTES:
            {
            UINT   jj;
            LPTSTR psz = pds->ptr;

            for (jj = 0; jj < pField->siz; ++jj)
               {
               wsprintf (psz, "%3d ", *(BYTE *)((LPSTR)lp+jj));
               psz += lstrlen(psz);
               *psz = 0;
               if (psz > (pds->ptr + pds->cch - 10))
                  {
                  *psz++ = '.';
                  *psz++ = '.';
                  *psz++ = '.';
                  *psz++ = 0;
                  break;
                  }
               }
            }
            break;
         }
      }
   __except (AccessFilter(GetExceptionCode()))
      {
      lstrcpy (pds->ptr, "<no access>");
      }

   return;
   }

/*+ SetFieldText
 *
 *
 *-=================================================================*/

BOOL SetFieldText (
   struct _dsline * pLine,
   PDSFIELD         pField,
   PDSBUFF          pds)
{
   LPBYTE lpv;

   lpv = pLine->lpv;
   if (pField->fmt & AS_REF)
     lpv = *(LPBYTE *)lpv;
   lpv += (pLine->uMember * pField->siz);

   if (!pds->cch)
      return FALSE;
   pds->ptr[pds->cch-1] = 0;

   switch (pField->fmt & 0xFF)
      {
      case AS_SZ:
         strncpy (lpv, pds->ptr, min(pField->siz, pds->cch));
         break;

      case AS_BITFIELD:
         {
         LPTSTR psz = pds->ptr;

         while (*psz == '\t' && *psz == ' ')
            ++psz;

         if ((*psz & ~0x20) == 'T' || (*psz & ~0x20) == 'Y' || *psz == '1')
            *(LPDWORD)lpv |= (1 << pField->aux);
         else if ((*psz & ~0x20) == 'F' || (*psz & ~0x20) == 'N' || *psz == '0')
            *(LPDWORD)lpv &= ~(1 << pField->aux);
         }
         break;

      case AS_XMODE:
         {
         UINT (*pfnMode)(LPSTR, UINT, UINT, UINT, UINT) = (LPVOID)pField->aux;

         if (pField->siz == sizeof(DWORD))
             pfnMode (pds->ptr, pds->cch, *(UINT *)lpv, pField->array, WM_SETTEXT);
         else
             pfnMode (pds->ptr, pds->cch, (UINT)lpv, pField->array, WM_SETTEXT);
         }
         break;

      case AS_UINT:
         {
         LPSTR pszStop = pds->ptr;
         DWORD dw = (DWORD)strtoul (pds->ptr, &pszStop, 10);
         if (pszStop != pds->ptr)
            {
            if (pField->siz == sizeof(DWORD))
               *(LPDWORD)lpv = dw;
            else if (pField->siz == sizeof(WORD))
               *(LPWORD)lpv = LOWORD(dw);
            }
         }
         break;

      case AS_INT:
         {
         LPSTR pszStop = pds->ptr;
         LONG  ii = strtol (pds->ptr, &pszStop, 10);
         if (pszStop != pds->ptr)
            {
            if (pField->siz == sizeof(LONG))
               *(LPLONG)lpv = ii;
            else if (pField->siz == sizeof(short))
               *(short int *)lpv = (short int)ii;
            }
         }
         break;

      case AS_BYTES:
         {
         UINT   jj;
         LPTSTR psz;

         psz = pds->ptr;
         for (jj = 0; jj < pField->siz; ++jj)
            {
            ULONG uu;
            LPTSTR pszStop;
            while (*psz == '\t' || *psz == ' ')
               ++psz;

            pszStop = psz;
            uu = strtoul (psz, &pszStop, 10);
            if (pszStop == psz)
               break;

            lpv[jj] = (BYTE)uu;

            psz = pszStop;
            }
         }
         break;

      case AS_HEX:
         {
         DWORD  dw = 0;
         LPTSTR psz;

         psz = pds->ptr;
         while (*psz == '\t' || *psz == ' ')
             ++psz;

         if (psz[0] == '0' && (psz[1] == 'X' || psz[1] == 'x'))
             psz += 2;

         while (*psz)
            {
            if (*psz >= '0' && *psz <= '9')
               dw = (dw << 4) + *psz - '0';
            else if ((*psz >= 'A' && *psz <= 'F') ||
                     (*psz >= 'a' && *psz <= 'f'))
               dw = (dw << 4) + (*psz & ~0x20) - 'A' + 10;
            else
               break;
            ++psz;
            }

         if (dw != 0 || (psz > pds->ptr && psz[-1] == '0'))
            {
            if (pField->siz == sizeof(DWORD))
               *(LPDWORD)lpv = dw;
            else if (pField->siz == sizeof(WORD))
               *(LPWORD)lpv = LOWORD(dw);
            }
         }
         break;
      }

   return TRUE;
}

/*+ DrawDsData
 *
 *-=================================================================*/

VOID WINAPI DrawDsData (
   HDC          hDC,
   PDSFIELDTBL  pTable,
   PDSLINEINFO  pLineInfo, //
   LPRECT       lpRect,     // rectangle to clip drawing to
   POINT        ptOffset)   // scrolling offset for upper left corner
   {
   WORD        wOldAlign;  // temp, prev state of alignment
   UINT        ii;         // general index variable
   int         cx;         // current x position to draw at
   int         cy;         // current y position to draw at
   int         cyText;
   PDSFIELD    pField;
   struct _dsline * pLine;

   // do some simple error checking, if not a valid DC
   // dont go any further.
   //
   if (!hDC)
      return;

   // get the text metrics and initialize the max width variable
   //
   cyText = pLineInfo->cyLine;

   // setup the current x & y locations for writing text
   //
   cx = lpRect->left - ptOffset.x;
   cy = lpRect->top - ptOffset.y;

   // draw each of the labels making a vertical
   // column starting 'cx,cy'.  But be sure not to
   // draw outside of the rectangle 'lpRect'
   //
   wOldAlign = SetTextAlign (hDC, TA_TOP | TA_LEFT);

   // clear the background
   //
   ExtTextOut (hDC, cx, cy, ETO_CLIPPED | ETO_OPAQUE, lpRect, "", 0, NULL);

   // draw the data
   //
   for (pLine = &pLineInfo->aLine[ii = 0];
        ii < pLineInfo->nCurLine;
        ++ii, ++pLine, cy += cyText)
      {
      char   sz[1024]; // buffer for formatting text info
      LPBYTE lpv;
      DSBUFF ds;
      UINT   cb;

      if (cy < lpRect->top - cyText)
         continue;
      if (cy > lpRect->bottom)
         break;

      pField = pTable[pLine->uStruct].pFields;
      pField += pLine->uField;
      lpv = pLine->lpv;

      sz[0] = 0;
      GetFieldText (pField, lpv, pLine->uMember, sz, NUMELMS(sz), &ds);
      if (cb = lstrlen (sz))
         ExtTextOut (hDC,
                     cx, cy,
                     ETO_CLIPPED, lpRect, sz, cb, NULL);
      }

   // restore text alignment and return
   //
   SetTextAlign (hDC, wOldAlign);

   return;
   }

/*+ GetDsLabelWidth
 *
 *-=================================================================*/

int WINAPI GetDsLabelWidth (
   HDC          hDC,
   PDSFIELDTBL  pTable,
   PDSLINEINFO  pLineInfo)
   {
   int  cxWidth = 0;
   UINT ii;
   struct _dsline * pLine = pLineInfo->aLine;

   for (pLine = &pLineInfo->aLine[ii = 0];
        ii < pLineInfo->nCurLine;
        ++ii, ++pLine)
      {
      PDSFIELD pFields = pTable[pLine->uStruct].pFields;
      SIZE     size = {0,0};

      GetTextExtentPoint (hDC,
                          pFields[pLine->uField].psz,
                          lstrlen (pFields[pLine->uField].psz),
                          &size);

      size.cx += (pLine->uDepth * 16) - 16;

      if (cxWidth < size.cx)
         cxWidth = size.cx;
      }

   return cxWidth;
   }

/*+ AllocDsLineInfo
 *
 *-=================================================================*/

PDSLINEINFO WINAPI AllocDsLineInfo (
   UINT nMax)
   {
   PDSLINEINFO pLineInfo;

   pLineInfo = GlobalAllocPtr (GHND, sizeof(DSLINEINFO)
                               + sizeof(struct _dsline) * nMax);
   pLineInfo->nMaxLine = nMax;
   pLineInfo->nMaxDepth = 10;
   pLineInfo->nCurLine = 0;
   pLineInfo->nCurDepth = 0;
   return pLineInfo;
   }

/*+ BuildDsLineInfo
 *
 *-=================================================================*/

VOID WINAPI BuildDsLineInfo (
   PDSFIELDTBL  pTable,
   UINT         uStruct,    //
   LPVOID       lpData,     // pointer to structure to show
   WORD         nMember,
   WORD         nLastMember,
   PDSLINEINFO  pLineInfo)
   {
   UINT      uField;
   PDSFIELD  pField;
   UINT      cbStruct;

   if (!pTable)
     return;
   pField = pTable[uStruct].pFields;
   cbStruct = pTable[uStruct].cbSize;

   if (pLineInfo->nCurDepth >= pLineInfo->nMaxDepth)
      return;

   ++pLineInfo->nCurDepth;

   do
   {
      // now build the lines
      //
      for (uField = 0; pField->psz != NULL; ++pField, ++uField)
         {
         UINT   ix;

         if (pLineInfo->nCurLine >= pLineInfo->nMaxLine)
            break;

         ix = 0;
         do {
            struct _dsline * pLine;
            LPBYTE lpv;

            if (pLineInfo->nCurLine >= pLineInfo->nMaxLine)
               break;

            pLine = &pLineInfo->aLine[pLineInfo->nCurLine];
            ++pLineInfo->nCurLine;

            pLine->uStruct = uStruct;
            pLine->uField = uField;
            pLine->uDepth = pLineInfo->nCurDepth;
            pLine->uMember = ix;
            pLine->lpv = (LPVOID)-2;

            __try
               {
               lpv = (LPBYTE)lpData + (cbStruct * nMember) + pField->off;

               pLine->lpv = lpv;

               if (HIWORD(lpv)) // simple validity check...
                  {
                  lpv += (ix * pField->siz);
                  if ((pField->fmt & 0x1FF) == AS_STRUCT)
                      BuildDsLineInfo (pTable, pField->aux, lpv,
                                       (WORD)ix, (WORD)ix, pLineInfo);
                  }
               }
            __except (AccessFilter(GetExceptionCode()))
               {
               }
            } while (++ix < pField->array);
         }

   } while (++nMember <= nLastMember);

   --pLineInfo->nCurDepth;
   return;
   }

/*+ GetViewExtent
 *
 *-=================================================================*/

int WINAPI GetViewExtent (
   HDC         hDC,
   PVIEWINIT   pvim,
   PDSLINEINFO pLineInfo,
   LPSIZE      lpItemSize,
   LPSIZE      lpTotalSize)
   {
   TEXTMETRIC tm;

   pLineInfo->nCurLine = 0;
   pLineInfo->nCurDepth = 0;

   BuildDsLineInfo (pvim->pTable,
                    pvim->uStruct,
                    pvim->lpData,
                    0, 0,
                    pLineInfo);

   GetTextMetrics (hDC, &tm);
   pLineInfo->cyLine = tm.tmHeight + tm.tmExternalLeading;

   lpItemSize->cx = GetDsLabelWidth (hDC, pvim->pTable, pLineInfo);
   lpItemSize->cy = pLineInfo->cyLine;

   lpTotalSize->cx = lpItemSize->cx * 2;
   lpTotalSize->cy = pLineInfo->cyLine * pLineInfo->nCurLine;

   return pLineInfo->nCurLine;
   }

/*+
 *  ViewWndProc
 *
 *-=================================================================*/

// misc manifest constants
//
#define HTLABEL 1
#define HTEDGE  2
#define HTDATA  3

typedef struct _viewstate {
   UINT        wLabelWidth;
   SIZE        sTotal;
   SIZE        sLabel;
   SIZE        sScroll;
   POINT       ptVisible;
   //HBRUSH      hBrFace;
   HFONT       hFontLabel;
   HFONT       hFontData;
   HMENU       hMenu;
   WORD        wHit;
   UINT        nHitLine;
   HCURSOR     hCurEdge;
   POINT       ptEdgeBegin;
   int         cxEdgeSlop;
   POINT       ptLastEdge;
   int         nWindowCnt;
   PDSLINEINFO pLineInfo;
   int         cyLine;
   int         cyWnd;
   VIEWINIT    vi;
   } VIEWSTATE, *PVIEWSTATE;

/*+
 *
 *
 *-=================================================================*/

VOID WINAPI UpdateScrollRanges (
   PVIEWSTATE pv,
   HWND       hWnd)
   {
   RECT rc;

   pv->sTotal.cy = pv->cyLine * pv->pLineInfo->nCurLine;

   GetClientRect (hWnd, &rc);
   pv->cyWnd = (rc.bottom - rc.top);
   pv->cyWnd -= (pv->cyWnd % pv->cyLine);
   pv->sScroll.cy = max (pv->sTotal.cy - pv->cyWnd, 0);
   pv->ptVisible.y = min (pv->sScroll.cy, pv->ptVisible.y);

   SetScrollRange (hWnd, SB_VERT, 0, pv->sScroll.cy, FALSE);
   SetScrollPos (hWnd, SB_VERT, pv->ptVisible.y, TRUE);
   }

/*+
 *  ViewPrivate
 *
 *-=================================================================*/

UINT WINAPI EditInPlace (
   HWND       hWnd,
   PVIEWSTATE pv,
   UINT       nLine);

LONG WINAPI ViewPrivate (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   PVIEWSTATE pv;

   pv = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);
   if (!pv)
      return 0;

   switch (wMsgID)
      {
      case VM_GETDATA:
         lRet = (LRESULT)pv->vi.lpData;
         break;

      case VM_SETDATA:
         lRet = (LRESULT)pv->vi.lpData;
         pv->vi.lpData = (LPVOID)lParam;
         if (lRet != (LRESULT)pv->vi.lpData)
            {
            RECT rc;

            pv->pLineInfo->nCurLine = 0;
            pv->pLineInfo->nCurDepth = 0;

            BuildDsLineInfo (pv->vi.pTable,
                             pv->vi.uStruct,
                             pv->vi.lpData,
                             0, 0,
                             pv->pLineInfo);

            GetClientRect (hWnd, &rc);
            rc.left = pv->wLabelWidth + 5;
            InvalidateRect (hWnd, &rc, FALSE);
            }
         break;

      case VM_INVALIDATE:
         {
         RECT  rc;
         int   yLine;

         if (!wParam && !lParam)
            InvalidateRect (hWnd, NULL, FALSE);

         GetClientRect (hWnd, &rc);
         yLine = ((int)lParam * pv->cyLine) - pv->ptVisible.y;
         if (yLine >= rc.top && yLine <= rc.bottom)
            {
            rc.left += pv->wLabelWidth + 5;
            rc.top += yLine;
            rc.bottom = rc.top + (pv->cyLine * max(1, wParam));
            InvalidateRect (hWnd, &rc, FALSE);
            }
         }
         break;

      case VM_SETSEL:
         {
         UINT nLine = (UINT)lParam;
         BOOL bPrev = (BOOL)wParam;

         while (nLine < pv->pLineInfo->nCurLine)
            {
            struct _dsline * pLine = &pv->pLineInfo->aLine[nLine];
            PDSFIELD pField = pv->vi.pTable[pLine->uStruct].pFields + pLine->uField;
            if (pField->fmt & IS_EDIT)
               {
               RECT rc;
               int  yLine;

               GetClientRect (hWnd, &rc);
               yLine = (int)(nLine * pv->cyLine) - pv->ptVisible.y;
               if (yLine >= rc.top && (yLine + pv->cyLine) <= rc.bottom)
                  pv->nHitLine = nLine, lRet = 1;
               break;
               }
            if (bPrev && nLine == 0)
                break;
            nLine = bPrev ? nLine-1 : nLine+1;
            }

         }
         break;

      case VM_EDITNEXT:
         {
         UINT nLine = pv->nHitLine;
         BOOL bPrev = (BOOL)wParam;

         if (bPrev && nLine == 0)
            break;
         nLine = bPrev ? nLine-1 : nLine+1;

         if (ViewPrivate (hWnd, VM_SETSEL, bPrev, nLine))
            {
            EditInPlace (hWnd, pv, pv->nHitLine);
            }
         }
      }

   return lRet;
   }

/*+
 *
 *
 *-=================================================================*/

LRESULT Notify (
   HWND hWnd,
   UINT code)
{
   NMHDR nm;

   nm.hwndFrom = hWnd;
   nm.idFrom = GetDlgCtrlID(hWnd);
   nm.code = code;
   return SendMessage (GetParent(hWnd), WM_NOTIFY, nm.idFrom, (LPARAM)(LPVOID)&nm);
}

/*+
 *  EditInPlace
 *
 *-=================================================================*/

UINT WINAPI EditInPlace (
   HWND       hWnd,
   PVIEWSTATE pv,
   UINT       nLine)
   {
   PDSFIELDTBL  pTable = pv->vi.pTable;
   PDSLINEINFO  pLineInfo = pv->pLineInfo;
   struct _dsline * pLine;
   PDSFIELD         pField;
   TCHAR        sz[1000];
   HWND         hWndEdit;
   RECT         rc;
   MSG          msg;
   UINT         uId;
   DSBUFF       ds;

   if (nLine >= pLineInfo->nCurLine)
      return 0;

   pLine = &pLineInfo->aLine[nLine];
   pField = pTable[pLine->uStruct].pFields + pLine->uField;

   GetFieldText (pField, pLine->lpv, pLine->uMember, sz, NUMELMS(sz), &ds);

   GetClientRect (hWnd, &rc);
   rc.left += pv->wLabelWidth + 5;
   rc.top = (nLine * pv->cyLine) - pv->ptVisible.y -2;
   rc.bottom = rc.top + pv->cyLine + 4;

   hWndEdit = CreateWindowEx (fdwExStyle | WS_EX_NOPARENTNOTIFY,
                              "Edit",
                              sz,
                              ES_AUTOHSCROLL |
                              ES_LEFT |
                              WS_CHILD | WS_BORDER,
                              rc.left, rc.top,
                              rc.right - rc.left, rc.bottom - rc.top,
                              hWnd,
                              (HMENU)IDE_VALUE,
                              GetWindowInstance(hWnd),
                              NULL);
   if ( ! hWndEdit)
      return 0;

   SendMessage (hWndEdit, WM_SETFONT, (WPARAM)pv->hFontData, 0);
   Edit_SetSel (hWndEdit, 0, NUMELMS(sz)+2);
   ShowWindow (hWndEdit, SW_SHOWNORMAL);
   SetFocus (hWndEdit);

   uId = IDOK;
   bInEditingMode = TRUE;
   do {
      if (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
         {
         if (msg.hwnd != hWndEdit &&
             (msg.message == WM_LBUTTONDOWN ||
              msg.message == WM_RBUTTONDOWN))
            break;

         if (msg.message == WM_QUIT)
            break;

         if (msg.message == WM_SYSKEYDOWN)
            break;

         GetMessage (&msg, NULL, 0, 0);
         if ((msg.hwnd == hWndEdit) && (msg.message == WM_KEYDOWN))
            {
            if (LOWORD(msg.wParam) == VK_RETURN)
               break;

else if (LOWORD(msg.wParam) == VK_TAB)
               {
               PostMessage (hWnd, VM_EDITNEXT, (WPARAM)GetKeyState(VK_SHIFT), 0);
               break;
               }
            else if (LOWORD(msg.wParam) == VK_ESCAPE)
               {
               uId = IDCANCEL;
               break;
               }
            }

         TranslateMessage (&msg);
         DispatchMessage (&msg);
         }
      else
         {
         //SendMessage (hWnd, WM_ENTERIDLE, 0, 0);
         WaitMessage();
         }

      } while (bInEditingMode && GetFocus() == hWndEdit);

   bInEditingMode = FALSE;
   if (uId == IDOK)
   {
       GetWindowText (hWndEdit, sz, NUMELMS(sz));
       ds.ptr = sz;
       ds.cch = lstrlen(ds.ptr)+1;
       SetFieldText (pLine, pField, &ds);
       ViewPrivate (hWnd, VM_INVALIDATE, 0, nLine);
       Notify (hWnd, VN_CHANGE);
   }
   DestroyWindow (hWndEdit);

   return uId;
}

/*+
 *  CopyLineToClip
 *
 *-=================================================================*/

LONG WINAPI CopyLineToClip (
   HWND       hWnd,
   PVIEWSTATE pv,
   UINT       nLine)
{
   PDSFIELDTBL  pTable = pv->vi.pTable;
   PDSLINEINFO  pLineInfo = pv->pLineInfo;
   HGLOBAL      hMem;
   struct _dsline * pLine;
   PDSFIELD         pField;
   UINT         cfType;

   pLine = &pLineInfo->aLine[nLine];
   pField = pTable[pLine->uStruct].pFields + pLine->uField;

   switch (pField->fmt & 0xFF)
      {
      case AS_BYTES:
      case AS_DUMP:
         {
         LPRIFF pRiff;
         LPBYTE lpv;

         lpv = pLine->lpv;
         if (pField->fmt & AS_REF)
            lpv = *(LPBYTE *)lpv;
         lpv += (pLine->uMember * pField->siz);

         cfType = CF_RIFF;
         hMem = GlobalAlloc (GHND, pField->siz + sizeof(RIFF));
         if (!(pRiff = GlobalLock (hMem)))
            return 0;

         pRiff->fcc = MAKEFOURCC('B','y','t','e');
         pRiff->cb = pField->siz;
         CopyMemory (pRiff+1, lpv, pField->siz);
         GlobalUnlock (hMem);
         }
         break;

      case AS_XMODE:
         {
         UINT (*pfnMode)(LPSTR, UINT, UINT, UINT, UINT) = (LPVOID)pField->aux;
         LPBYTE lpv;

         lpv = pLine->lpv;
         if (pField->fmt & AS_REF)
            lpv = *(LPBYTE *)lpv;
         lpv += (pLine->uMember * pField->siz);

         if (pField->siz == sizeof(DWORD))
             {
             if (pfnMode (NULL, (UINT)hWnd, *(UINT *)lpv, pField->array, WM_COPY))
                return 0;
             }
         else
             {
             if (pfnMode (NULL, (UINT)hWnd, (UINT)lpv, pField->array, WM_COPY))
                return 0;
             }
         }
         // fall through
      default:
         {
         DSBUFF ds;
         LPSTR  psz;
         UINT   cch = 128 * 5 + 10;
         cfType = CF_TEXT;

         hMem = GlobalAlloc (GHND, cch);
         if (!(psz = GlobalLock (hMem)))
            return 0;

         // get data for the current line into the global
         // object.
         //
         GetFieldText (pField, pLine->lpv, pLine->uMember, psz, cch, &ds);

         // truncate the global object to fit the size of the data
         //
         GlobalUnlock (hMem);
         GlobalReAlloc (hMem, lstrlen(psz) + 2, 0);
         }
         break;
      }

   if (OpenClipboard (hWnd))
       {
       EmptyClipboard ();
       SetClipboardData (cfType, hMem);
       CloseClipboard ();
       }
   else
       GlobalFree (hMem);

   return 0;
}

/*+
 *  PasteLineFromClip
 *
 *-=================================================================*/

LONG WINAPI PasteLineFromClip (
   HWND       hWnd,
   PVIEWSTATE pv,
   UINT       nLine)
{
   HGLOBAL hMem;
   PDSFIELDTBL  pTable = pv->vi.pTable;
   PDSLINEINFO  pLineInfo = pv->pLineInfo;
   struct _dsline * pLine = &pLineInfo->aLine[nLine];
   PDSFIELD     pField = pTable[pLine->uStruct].pFields + pLine->uField;
   LONG lRet = 0;

   switch (pField->fmt & 0xFF)
      {
      case AS_BYTES:
      case AS_DUMP:
          if (OpenClipboard (hWnd))
             {
             if (hMem = GetClipboardData (CF_RIFF))
               {
               LPRIFF pRiff;
               LPBYTE lpv;

               lpv = pLine->lpv;
               if (pField->fmt & AS_REF)
                  lpv = *(LPBYTE *)lpv;
               lpv += (pLine->uMember * pField->siz);

               if (pRiff = GlobalLock (hMem))
                  {
                  if (pRiff->fcc == MAKEFOURCC('B','y','t','e') &&
                      pRiff->cb == pField->siz)
                     {
                     CopyMemory (lpv, pRiff+1, pField->siz);
                     lRet = 2;
                     }
                  GlobalUnlock (hMem);
                  }
               }
            CloseClipboard ();
            }
         break;

      case AS_XMODE:
         {
         UINT (*pfnMode)(LPSTR, UINT, UINT, UINT, UINT) = (LPVOID)pField->aux;
         LPBYTE lpv;

         lpv = pLine->lpv;
         if (pField->fmt & AS_REF)
            lpv = *(LPBYTE *)lpv;
         lpv += (pLine->uMember * pField->siz);

         if (pField->siz == sizeof(DWORD))
             lRet = pfnMode (NULL, (UINT)hWnd, *(UINT *)lpv, pField->array, WM_PASTE);
         else
             lRet = pfnMode (NULL, (UINT)hWnd, (UINT)lpv, pField->array, WM_PASTE);

         // fall through if pfnMode handled the paste operation
         //
         if (lRet != 0)
            break;
         }
         // fall through
      default:
         if (OpenClipboard (hWnd))
            {
            if (hMem = GetClipboardData (CF_TEXT))
               {
               DSBUFF ds;
               ds.ptr = GlobalLock (hMem);
               if (ds.ptr)
                  {
                  ds.cch = lstrlen(ds.ptr)+1;
                  SetFieldText (pLine, pField, &ds);
                  lRet = 2;
                  }
               }
            CloseClipboard ();
            }
         break;
      }

   if (lRet > 0)
   {
      ViewPrivate (hWnd, VM_INVALIDATE, 0, nLine);
      Notify (hWnd, VN_CHANGE);
   }

   return lRet;
}

/*+
 *  ViewCommands
 *
 *-=================================================================*/

LONG WINAPI ViewCommands (
   HWND   hWnd,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   PVIEWSTATE pv;

   pv = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);
   if (!pv)
      return 0;

   switch (GET_WM_COMMAND_ID(wParam,lParam))
      {
      case IDM_OBJEDIT:
          EditInPlace   (hWnd, pv, pv->nHitLine);
          //EditLineValue (hWnd, pv, pv->nHitLine);
          break;

      case IDM_EDITCOPY:
          CopyLineToClip (hWnd, pv, pv->nHitLine);
          break;

      case IDM_EDITPASTE:
          PasteLineFromClip (hWnd, pv, pv->nHitLine);
          break;
      }

   return lRet;
   }

/*+
 *  ViewWndProc
 *
 *-=================================================================*/

LONG CALLBACK ViewWndProc (
   HWND   hWnd,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam)
   {
   LONG       lRet = 0l;               // return value from this routine
   PVIEWSTATE pv;

   pv = (LPVOID) GetWindowLong (hWnd, GWL_USERDATA);

   if (wMsgID >= WM_USER && wMsgID < 0x8000)
      lRet = ViewPrivate (hWnd, wMsgID, wParam, lParam);
   else
   switch (wMsgID)
      {
      case WM_NCHITTEST:
         lRet = DefProc (hWnd, wMsgID, wParam, lParam);
         if (lRet == HTCLIENT)
            {
            POINT pt = {MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y};

            ScreenToClient (hWnd, &pt);

            pv->wHit = HTEDGE;
            if (pt.x <= (int)pv->wLabelWidth -4)
               pv->wHit = HTLABEL;
            else if (pt.x >= (int)pv->wLabelWidth +4)
               pv->wHit = HTDATA;
            }
         break;

     #if 0
      case WM_SETCURSOR:
         if (LOWORD (lParam) == HTCLIENT &&
             pv->wHit == HTEDGE &&
             pv->hCurEdge != NULL)
            SetCursor (pv->hCurEdge);
         else
            lRet = DefProc (hWnd, wMsgID, wParam, lParam);
         break;
     #endif

      case WM_LBUTTONDBLCLK:
         {
         POINT pt = {MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y};
         PDSFIELDTBL  pTable = pv->vi.pTable;
         PDSLINEINFO  pLineInfo = pv->pLineInfo;
         UINT         nLine = (pt.y + pv->ptVisible.y) / pv->cyLine;

         lRet = DefProc (hWnd, wMsgID, wParam, lParam);

         if (nLine < pLineInfo->nCurLine)
             {
             struct _dsline * pLine = &pLineInfo->aLine[nLine];
             PDSFIELD pField = pTable[pLine->uStruct].pFields + pLine->uField;

             if ((pField->fmt & IS_EDIT) && pv->hMenu)
                {
                pv->nHitLine = nLine;
                SetCapture (hWnd);
                }
            }
         break;
         }

      case WM_LBUTTONUP:
         {
         if (GetCapture() == hWnd)
            {
            POINT pt = {MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y};
            PDSFIELDTBL  pTable = pv->vi.pTable;
            PDSLINEINFO  pLineInfo = pv->pLineInfo;
            UINT         nLine = (pt.y + pv->ptVisible.y) / pv->cyLine;

            ReleaseCapture ();

            if (nLine == pv->nHitLine)
               PostMessage (hWnd, WM_COMMAND, IDM_OBJEDIT, 0);
            }
         lRet = DefProc (hWnd, wMsgID, wParam, lParam);
         break;
         }

      case WM_LBUTTONDOWN:
      case WM_RBUTTONDOWN:
         SetFocus (hWnd);
         lRet = DefProc (hWnd, wMsgID, wParam, lParam);
         break;

      case WM_RBUTTONUP:
         {
         POINT pt = {MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y};
         PDSFIELDTBL  pTable = pv->vi.pTable;
         PDSLINEINFO  pLineInfo = pv->pLineInfo;
         UINT         nLine = (pt.y + pv->ptVisible.y) / pv->cyLine;

         lRet = DefProc (hWnd, wMsgID, wParam, lParam);

         if (nLine < pLineInfo->nCurLine)
             {
             struct _dsline * pLine = &pLineInfo->aLine[nLine];
             PDSFIELD pField = pTable[pLine->uStruct].pFields + pLine->uField;

             //if (pv->wHit == HTLABEL)
             //   {
             //   }

             if ((pField->fmt & IS_EDIT) && pv->hMenu)
                {
                pv->nHitLine = nLine;
                ClientToScreen (hWnd, &pt);
                TrackPopupMenu (pv->hMenu,
                               #if (WINVER >= 0x400)
                                TPM_TOPALIGN |
                               #endif
                                TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                                pt.x, pt.y,
                                0,
                                hWnd,
                                NULL);
                }
            }
         break;

         }

      case WM_COMMAND:
         lRet = ViewCommands (hWnd, wParam, lParam);
         break;

      case WM_SIZE:
         lRet = DefProc (hWnd, wMsgID, wParam, lParam);
         UpdateScrollRanges (pv, hWnd);
         break;

      case WM_KEYDOWN:
         {
         int  nNewPos = -1;

         switch (LOWORD(wParam))
            {
            case VK_UP:
               nNewPos = pv->ptVisible.y - pv->cyLine;
               break;

            case VK_DOWN:
               nNewPos = pv->ptVisible.y + pv->cyLine;
               break;

            case VK_PRIOR:
               nNewPos = pv->ptVisible.y - (pv->cyWnd - pv->cyLine);
               break;

            case VK_NEXT:
               nNewPos = pv->ptVisible.y + (pv->cyWnd - pv->cyLine);
               break;
            }

         if (nNewPos != -1)
            {
            nNewPos = min (nNewPos, pv->sScroll.cy);
            nNewPos = max (nNewPos, 0);
            ScrollWindow (hWnd, 0, pv->ptVisible.y - nNewPos, NULL, NULL);
            pv->ptVisible.y = nNewPos;
            SetScrollPos (hWnd, SB_VERT, pv->ptVisible.y, TRUE);
            }
         break;
         }

     case WM_VSCROLL:
         {
         int  nPos;
         int  nNewPos = -1;
         WORD wScrollCode;

         wScrollCode = LOWORD (wParam);
         nPos        = (int)HIWORD (wParam);

         switch (wScrollCode)
            {
            case SB_LINEUP:
               nNewPos = pv->ptVisible.y - pv->cyLine;
               break;

            case SB_LINEDOWN:
               nNewPos = pv->ptVisible.y + pv->cyLine;
               break;

            case SB_PAGEUP:
               nNewPos = pv->ptVisible.y - (pv->cyWnd - pv->cyLine);
               break;

            case SB_PAGEDOWN:
               nNewPos = pv->ptVisible.y + (pv->cyWnd - pv->cyLine);
               break;

            case SB_THUMBPOSITION:
               nNewPos = nPos - (nPos % pv->cyLine);
               break;
            }

         if (nNewPos != -1)
            {
            nNewPos = min (nNewPos, pv->sScroll.cy);
            nNewPos = max (nNewPos, 0);
            ScrollWindow (hWnd, 0, pv->ptVisible.y - nNewPos, NULL, NULL);
            pv->ptVisible.y = nNewPos;
            SetScrollPos (hWnd, SB_VERT, pv->ptVisible.y, TRUE);
            }
         break;
         }

      case WM_ERASEBKGND:
         {
         RECT rc;
         lRet = DefWindowProc (hWnd, wMsgID, wParam, lParam);

         GetClientRect (hWnd, &rc);
         //rc.left = pv->wLabelWidth + 3;
         //TrideeWellShadow ( (HDC)wParam, &rc);

         SetTextColor ( (HDC)wParam, RGB (0,0,128));
         //SetBkColor ( (HDC)wParam, GetSysColor (COLOR_BTNFACE));

         //rc.left = 0;
         rc.right = pv->wLabelWidth;
         //TrideeWellShadow ( (HDC)wParam, &rc);
         if (pv->hFontLabel)
            SelectObject ((HDC)wParam, pv->hFontLabel);

         DrawDsLabels ((HDC)wParam,
                       pv->vi.pTable,
                       pv->pLineInfo,
                       &rc,
                       pv->ptVisible);
         break;
         }

      case WM_PAINT:
         {
         PAINTSTRUCT ps;           // structure for BeginPaint
         HDC         hDC;          // DC to draw info into
         RECT        rcClip;       // clip rect for drawing

         // and draw the info into our client area
         //
         hDC = BeginPaint (hWnd, &ps);
         GetClientRect (hWnd, &rcClip);
         rcClip.left += pv->wLabelWidth + 5;

         if (pv->hFontData)
            SelectObject (hDC, pv->hFontData);
         SetTextColor (hDC, GetSysColor (COLOR_WINDOWTEXT));
         SetBkColor (hDC, GetSysColor (COLOR_WINDOW));

         DrawDsData (hDC,
                     pv->vi.pTable,
                     pv->pLineInfo,
                     &rcClip,
                     pv->ptVisible);

         EndPaint (hWnd, &ps);
         break;
         }

      case WM_CREATE:
         {
         HDC               hDC;
         char              sz[120];
         LPCREATESTRUCT    lpCreate = (LPCREATESTRUCT)lParam;
         POINT             pt = {0,0};

         pv = LocalAlloc (LPTR, sizeof(VIEWSTATE));
         if (!pv)
            return -1;
         SetWindowLong (hWnd, GWL_USERDATA, (LONG)pv);

         // init local state information
         //
         pv->pLineInfo = AllocDsLineInfo (1000);
         //pv->hBrFace = TrideeCreate (hWnd);
          //CreateFontIndirect (&lfMain);
         pv->hFontLabel = GetStockObject (DEFAULT_GUI_FONT);
         pv->hFontData  = GetStockObject (ANSI_FIXED_FONT);

         pv->hMenu = CreatePopupMenu ();
         //AppendMenuItem (pv->hMenu, &mii);
         AppendMenu (pv->hMenu, MF_STRING, IDM_OBJEDIT, "&Edit...");
         AppendMenu (pv->hMenu, MF_STRING, IDM_EDITCOPY, "&Copy");
         AppendMenu (pv->hMenu, MF_STRING, IDM_EDITPASTE, "&Paste");

         // get init stuff from last parameter (if possible)
         //
        #ifdef USE_MDI
         {
         LPMDICREATESTRUCT lpMcs;
         lpMcs = (LPMDICREATESTRUCT) (lpCreate->lpCreateParams);
         lpMcs->x = lpMcs->y = CW_USEDEFAULT;
         lpMcs->cx = lpMcs->cy = CW_USEDEFAULT;
         if (lpMcs->lParam)
            pv->vi = *(PVIEWINIT)lpMcs->lParam;
         }
        #else
         if (lpCreate->lpCreateParams)
            pv->vi = *(PVIEWINIT)(lpCreate->lpCreateParams);
        #endif

         hDC = GetDC (hWnd);
         if (pv->hFontLabel)
            SelectObject (hDC, pv->hFontLabel);

         GetViewExtent (hDC,
                        &pv->vi,
                        pv->pLineInfo,
                        &pv->sLabel,
                        &pv->sTotal);

         pv->cyLine = pv->pLineInfo->cyLine;
         pv->wLabelWidth = pv->sLabel.cx + 16;
         ReleaseDC (hWnd, hDC);

         pv->hCurEdge = LoadCursor (lpCreate->hInstance,
                                    MAKEINTRESOURCE (IDC_EDGE));

         wsprintf (sz, "%s:%08lxx",
                   pv->vi.pTable[pv->vi.uStruct].pszName,
                   pv->vi.lpData);

         SetWindowText (hWnd, sz);

         break;
         }

      case WM_DESTROY:
         {
         //TrideeDestroy (hWnd);

         // delete font if not using stock object
         //DeleteObject (pv->hFontLabel);

         break;            // return FALSE;
         }

      default:
         lRet = DefProc (hWnd, wMsgID, wParam, lParam);
      }

   return lRet;
   }

/*+ DrawDragEdge
 *
 *
 *-=================================================================*/

VOID WINAPI DrawDragEdge (HWND hWnd, LPPOINT lppt, LPPOINT lpptLastEdge)
   {
   HDC  hDC;
   RECT rc;

   hDC = GetDC (hWnd);

   GetClientRect (hWnd, &rc);

   if (lpptLastEdge)
      PatBlt (hDC, lpptLastEdge->x, rc.top, 3, rc.bottom - rc.top, PATINVERT);

   if (lppt)
      {
      PatBlt (hDC, lppt->x, rc.top, 3, rc.bottom - rc.top, PATINVERT);
      if (lpptLastEdge)
         *lpptLastEdge = *lppt;
      }

   ReleaseDC (hWnd, hDC);
   }


RES.H

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  res.h
//**
//**  DESCRIPTION:
//**
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

#ifndef RES_H
#define RES_H

#define IDR_MAIN             1
#define IDR_INSTRUM          2

#define IDC_EDGE             3

#define IDD_LINEEDIT         97
#define IDD_IDFEDITDLG       98
#define IDD_ABOUT            99

//
// MENU DEFINITIONS
//
#define  IDM_FILE_BASE        (100)
#define  IDM_FILENEW          (IDM_FILE_BASE)
#define  IDM_FILEOPEN         (IDM_FILE_BASE + 1)
#define  IDM_FILESAVE         (IDM_FILE_BASE + 2)
#define  IDM_FILESAVEAS       (IDM_FILE_BASE + 3)
#define  IDM_NEW_INSTRUMENT   (IDM_FILE_BASE + 6)
#define  IDM_FILEEXIT         (IDM_FILE_BASE + 9)

#define  IDM_EDIT_BASE        (200)
#define  IDM_EDITUNDO         (IDM_EDIT_BASE + 1)
#define  IDM_EDITCUT          (IDM_EDIT_BASE + 2)
#define  IDM_EDITCOPY         (IDM_EDIT_BASE + 3)
#define  IDM_EDITPASTE        (IDM_EDIT_BASE + 4)
#define  IDM_EDITDELETE       (IDM_EDIT_BASE + 5)
#define  IDM_EDITSELECTALL    (IDM_EDIT_BASE + 6)

#define  IDM_WINDOW_BASE      (300)
#define  IDM_TILE             (IDM_WINDOW_BASE)
#define  IDM_CASCADE          (IDM_WINDOW_BASE + 1)
#define  IDM_ARRANGEICONS     (IDM_WINDOW_BASE + 2)
#define  IDM_MDIFIRSTCHILD    (IDM_WINDOW_BASE + 10)

#define  IDM_OPTIONS_BASE     (700)

#define  IDM_OBJECT_BASE      (800)
#define  IDM_OBJEDIT          (IDM_OBJECT_BASE)

#define  IDM_HELP_BASE        (900)
#define  IDM_HELP             (IDM_HELP_BASE)
#define  IDM_ABOUT            (IDM_HELP_BASE + 9)

//
// jmk
//
#define  IDS_ERR_REGCLASSES   64
#define  IDS_ERR_CREATEMAIN   65
#define  IDS_ERR_MDICLIENT    66
#define  IDS_ERR_NOHELP       67
#define  IDS_ERR_SAVE         68
#define  IDS_ERR_READONLY     69

//
// STRING TABLE DEFINITIONS
//
#define  STRINGTABLE_BASE           (0)
#define  IDS_APPNAME                (STRINGTABLE_BASE + 1)
#define  IDS_OFN_EXT_DEF            (STRINGTABLE_BASE + 2)
#define  IDS_OFN_EXT_FILTER         (STRINGTABLE_BASE + 3)
#define  IDS_DEF_FILE_NAME          (STRINGTABLE_BASE + 4)
#define  IDS_SAVE_CHANGES           (STRINGTABLE_BASE + 5)
#define  IDS_MESSAGE_BOX_TITLE      (STRINGTABLE_BASE + 6)
#define  IDS_CHANNEL                (STRINGTABLE_BASE + 7)
#define  IDS_CAPTION_BAR            (STRINGTABLE_BASE + 8)
#define  IDS_QUERY_SAVE             (STRINGTABLE_BASE + 9)

#define  IDS_CHANNEL_TYPE_BASE      (STRINGTABLE_BASE + 10)
#define  IDS_CHANNEL_TYPE_GENERAL   (IDS_CHANNEL_TYPE_BASE + 0)
#define  IDS_CHANNEL_TYPE_DRUM      (IDS_CHANNEL_TYPE_BASE + 1)
#define  IDS_CHANNEL_TYPE_LIGHTING  (IDS_CHANNEL_TYPE_BASE + 2)

#define  IDS_TREE_LABEL             (STRINGTABLE_BASE + 16)
#define  IDS_VIEW_LABEL             (STRINGTABLE_BASE + 17)

#define  IDS_YES                    (STRINGTABLE_BASE + 20)
#define  IDS_NO                     (STRINGTABLE_BASE + 21)

#define  IDS_GENERAL_INIT           (STRINGTABLE_BASE + 22)
#define  IDS_DRUM_INIT              (STRINGTABLE_BASE + 23)
#define  IDS_IS_RTL                 (STRINGTABLE_BASE + 24)
//
// ERROR VALUES
//
#define  IDFERR_BASE                (STRINGTABLE_BASE + 50)
#define  IDFERR_BADREAD             (IDFERR_BASE + 0)
#define  IDFERR_NOMEM               (IDFERR_BASE + 1)
#define  IDFERR_BADCHUNK            (IDFERR_BASE + 2)
#define  IDFERR_CANNOTCREATEFILE    (IDFERR_BASE + 3)
#define  IDFERR_CANNOTFINDCHUNK     (IDFERR_BASE + 4)
#define  IDFERR_BADWRITE            (IDFERR_BASE + 5)
#define  IDFERR_BADNUMBER           (IDFERR_BASE + 6)
#define  IDFERR_CANNOTCREATECHUNK   (IDFERR_BASE + 7)

#define  IDS_BAD_HEX_INPUT          (IDFERR_BASE+30)

#define  IDS_SECTION_NAME_BASE   (STRINGTABLE_BASE + 100)

#define  IDS_TITLE_BAR_BASE      (STRINGTABLE_BASE + 150)
#define  IDS_TITLE_BAR_HEADER    (IDS_TITLE_BAR_BASE + 0)
#define  IDS_TITLE_BAR_INSTINFO  (IDS_TITLE_BAR_BASE + 1)
#define  IDS_TITLE_BAR_INSTCAPS  (IDS_TITLE_BAR_BASE + 2)
#define  IDS_TITLE_BAR_CHANNELS  (IDS_TITLE_BAR_BASE + 3)
#define  IDS_TITLE_BAR_PATCHES   (IDS_TITLE_BAR_BASE + 4)
#define  IDS_TITLE_BAR_KEYMAPS   (IDS_TITLE_BAR_BASE + 5)

#define  IDS_HEADER_BASE         (STRINGTABLE_BASE + 200)

#define  IDS_INST_BASE           (STRINGTABLE_BASE + 300)

#define  IDS_CAPS_BASE           (STRINGTABLE_BASE + 400)

#define  GENERAL_PATCH           (STRINGTABLE_BASE + 500)
#define  DRUM_KEY                (STRINGTABLE_BASE + 700)

//
// DIALOG INCLUDE FILE's
//
#define  IDL_INSTRUMENTS         1001
#define  IDC_CURRENT_INSTRUM     1002

// drawstr's editing dialog
//
#define IDE_VALUE            11
#define IDC_VALUELABEL       12

// obsolete
//
#define ID_CURRENT_INSTRUMENT       1102
#define ID_PREVIOUS_INSTRUMENT      1103
#define ID_NEXT_INSTRUMENT          1104
#define ID_IDF_CURRENT_SECTION      1101
#define ID_EDIT_BOX                 1106
#define ID_HEADER_TEXT_LEFT         1107
#define ID_LIST_BOX                 1109
#define ID_HEADER_TEXT_RIGHT        1108
#define ID_INSTRUMENT_SCROLL        1110
#define ID_DRUM_CHANNELS            1111
#define ID_EDIT_ENTER               1105

#endif


TRIDEE.C

//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//**
//**  tridee.c
//**
//**  DESCRIPTION:
//**    routines to draw 3d borders
//**
//************************************************************************

#include <windows.h>
#include <windowsx.h>

#include "res.h"
#include "tridee.h"

// declare variables global to this module
//
static COLORREF crFace;
static COLORREF crHilite;
static COLORREF crShadow;

static HBRUSH hBrFace = NULL;
static HBRUSH hBrShadow = NULL;
static HBRUSH hBrHilite = NULL;
static BOOL   bIs3D = FALSE;
static int    cUsers = 0;

/*+ TrideeCreate
 *
 * Register a new client for the Tridee stuff.
 *
 * on the first client we create the brushes that we will need.
 * on others we just release the useage count.
 *
 * this routine is intended to be called in the WM_CREATE case of the
 * client.
 *
 *-=================================================================*/

HBRUSH FAR PASCAL TrideeCreate(HWND hWnd)
{
   hWnd;

   if(!cUsers++)
   {
      // create the button face brush, if we fail this,
      // set the use count back to 0 and return failure to
      // the caller.
      //
      crFace = GetSysColor(COLOR_BTNFACE);
      crShadow = GetSysColor(COLOR_BTNSHADOW);
      crHilite = GetSysColor(COLOR_BTNHIGHLIGHT);

      hBrFace = CreateSolidBrush(crFace);
      if(!hBrFace)
      {
         cUsers = 0;
         return NULL;
      }

      // if the button face color is white, assume we are
      // on a EGA or Mono system, and dont even try to create
      // hilite and shadow brushes.
      bIs3D = (crFace != RGB(255,255,255));
      if (!bIs3D)
         hBrShadow = hBrHilite = NULL;
      else
      {
         // create brushes for the hilite and shadow
         //
         hBrShadow = CreateSolidBrush(crShadow);
         hBrHilite = CreateSolidBrush(crHilite);
      }
   }

   return hBrFace;
}

/*+ TrideeDestroy
 *
 * DeRegister a client for the tridee stuff.
 *
 * when the count of clients gets to 0, we destroy the brushes.
 *
 * this routine is intended to be used in the WM_DESTROY case of
 * the client.
 *
 *-=================================================================*/

BOOL FAR PASCAL TrideeDestroy(HWND hWnd)
{
   hWnd;

   if(!--cUsers)
   {
      // free the GDI objects
      if(hBrFace)
         DeleteObject(hBrFace), hBrFace = NULL;
      if(hBrHilite)
         DeleteObject(hBrHilite), hBrHilite = NULL;
      if(hBrShadow)
         DeleteObject(hBrShadow), hBrShadow = NULL;
   }
   return TRUE;
}

/*+ TrideeWellShadow
 *
 * draw a well border just inside the supplied rectangle.
 * (a well has a hilite on lower and right edges and shadow
 * on upper and left)
 *
 * this routine does not fill the recangle, it merely paints
 * the 'frame'
 *
 *-=================================================================*/

VOID FAR PASCAL TrideeWellShadow(HDC hDC, LPRECT lprc)
{
   // draw the left and upper shadow
   //
   SelectObject(hDC, hBrShadow);
   PatBlt(hDC, lprc->left, lprc->top, 2,
                     lprc->bottom - lprc->top -2, PATCOPY);
   PatBlt(hDC, lprc->left, lprc->top,
                     lprc->right - lprc->left, 2, PATCOPY);

   // now draw the bottom and right hilite
   //
   SelectObject(hDC, hBrHilite);
   PatBlt(hDC, lprc->right-1, lprc->top+1,
                1, lprc->bottom - lprc->top -2, PATCOPY);
   PatBlt(hDC, lprc->left+1, lprc->bottom-1,
                lprc->right -lprc->left-1, 1, PATCOPY);

}


TRIDEE.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
*****************************************************************************/

// tridee.c
//
HBRUSH WINAPI TrideeCreate (
   HWND   hWnd);

BOOL WINAPI TrideeDestroy (
   HWND   hWnd);

VOID WINAPI TrideeWellShadow (
   HDC    hDC,
   LPRECT lprc);

int WINAPI DisplayAbout(
   HWND  hWnd);

BOOL WINAPI AboutDlgProc(
   HWND   hDlg,
   UINT   wMsgID,
   WPARAM wParam,
   LPARAM lParam);

BOOL WINAPI GetWindowPos(
   LPRECT lprc);

BOOL WINAPI SaveWindowPos(
   LPRECT lprc);


README.TXT

The LOWPASS sample is a low pass filter for waveform audio.


LOWPASS.H

/***********************************************************************
 *  lowpass.h - Header file for LOWPASS sample application.
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 *  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
 *  A PARTICULAR PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
 ***********************************************************************/

#define LOADSTRBUFSIZE 256// 255 + nul is biggest stringtable string.

/* Constants for dialogs.
 */
#define IDM_ABOUT       11          // menu items

#define ID_INPUTFILEEDIT    101     // input file name edit box
#define ID_OUTPUTFILEEDIT   102     // output file name edit box

/* Function Prototypes
 */
// WinMain is prototyped in windows.h or its children
void DoLowPass(HWND hwnd);
BOOL FAR PASCAL AboutDlgProc(HWND hwnd, UINT wMsg,
WPARAM wParam, LPARAM lParam);
BOOL WINAPI LowPassDlgProc(HWND hwnd, UINT wMsg,
WPARAM wParam, LPARAM lParam);


LOWPASS.C

/***********************************************************************
 * lowpass.c - WinMain() and dialog procedures for LOWPASS, along with
 * initialization and support code.
 *
 * LOWPASS is a sample application illustrating how to use the multimedia
 * file I/O services to read and write RIFF files.
 *
 * LOWPASS runs a simple low-pass filter over an 8-bit-per-sample
 * mono WAVE file.  Note that this program does not copy unknown chunks
 * to the output file.
 ************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 *  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
 *  A PARTICULAR PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
 *********************************************************************** */

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "lowpass.h"
#include "strings.h"

/* Globals
 */
char     gszAppName[] = "LowPass";  // for title bar, etc.
HINSTANCE   ghInst;           // app's instance handle
LPSTR    lpstrLoadStrBuf   = NULL;

/* DoLowPass - Gets the name of the input and output WAVE files from
 *  the dialog box; reads waveform data from the input file, performs
 *  a simple low-pass filter by averaging adjacent samples, and writes
 *  the filtered waveform data to the output WAVE file.
 *
 * Params:  hWnd - Window handle for our dialog box.
 *
 * Returns:
 */
void DoLowPass(
HWND hWnd)
{
   char         achInFile[200];  // name of input file
   char         achOutFile[200]; // name of output file
   HMMIO        hmmioIn = NULL;  // handle to open input WAVE file
   HMMIO        hmmioOut = NULL; // handle to open output WAVE file
   MMCKINFO       ckInRIFF;   // chunk info. for input RIFF chunk
   MMCKINFO       ckOutRIFF;  // chunk info. for output RIFF chunk
   MMCKINFO       ckIn;    // info. for a chunk in input file
   MMCKINFO       ckOut;      // info. for a chunk in output file
   PCMWAVEFORMAT  pcmWaveFormat; // contents of 'fmt' chunks
   MMIOINFO       mmioinfoIn; // current status of <hmmioIn>
   MMIOINFO       mmioinfoOut;   // current status of <hmmioOut>
   long         lSamples;  // number of samples to filter
   BYTE         abSamples[3]; // this, last, and before-last sample

   /* Read filenames from dialog box fields.
    */
   achInFile[0] = 0;
   GetDlgItemText(hWnd, ID_INPUTFILEEDIT, achInFile, sizeof(achInFile));
   achOutFile[0] = 0;
   GetDlgItemText(hWnd, ID_OUTPUTFILEEDIT, achOutFile, sizeof(achOutFile));

   /* Open the input file for reading using buffered I/O.
    */
   hmmioIn = mmioOpen(achInFile, NULL, MMIO_ALLOCBUF | MMIO_READ);
   if (hmmioIn == NULL)
      goto LOWPASS_ERROR_CANNOT_READ;  // cannot open WAVE file

   /* Open the output file for writing using buffered I/O. Note that
    * if the file exists, the MMIO_CREATE flag causes it to be truncated
    * to zero length.  This means of course that if the SAME FILE is given
    * for both input and output, the input file will be truncated right here
    * and the program will then (surprise!) be unable to read it!
    */
   hmmioOut = mmioOpen(achOutFile, NULL,
                  MMIO_ALLOCBUF | MMIO_WRITE | MMIO_CREATE);
   if (hmmioOut == NULL)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot open WAVE file

   /* Descend the input file into the 'RIFF' chunk.
    */
   if (mmioDescend(hmmioIn, &ckInRIFF, NULL, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_READ;  // end-of-file, probably

   /* Make sure the input file is a WAVE file.
    */
   if ((ckInRIFF.ckid != FOURCC_RIFF) ||
      (ckInRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
      goto LOWPASS_ERROR_BAD_FORMAT;

   /* Search the input file for for the 'fmt ' chunk.
    */
   ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
   if (mmioDescend(hmmioIn, &ckIn, &ckInRIFF, MMIO_FINDCHUNK) != 0)
      goto LOWPASS_ERROR_BAD_FORMAT;   // no 'fmt ' chunk

   /* Expect the 'fmt' chunk to be at least as large as <pcmWaveFormat>;
    * if there are extra parameters at the end, we'll ignore them
    */
   if (ckIn.cksize < (long) sizeof(pcmWaveFormat))
      goto LOWPASS_ERROR_BAD_FORMAT;   // 'fmt ' chunk too small

   /* Read the 'fmt ' chunk into <pcmWaveFormat>.
    */
   if (mmioRead(hmmioIn, (HPSTR) &pcmWaveFormat,
      (long) sizeof(pcmWaveFormat)) != (long) sizeof(pcmWaveFormat))
      goto LOWPASS_ERROR_CANNOT_READ;  // truncated file, probably

   /* Ascend the input file out of the 'fmt ' chunk.
    */
   if (mmioAscend(hmmioIn, &ckIn, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_READ;  // truncated file, probably

   /* Make sure the input file is an 8-bit mono PCM WAVE file.
    */
   if ((pcmWaveFormat.wf.wFormatTag != WAVE_FORMAT_PCM) ||
      (pcmWaveFormat.wf.nChannels != 1) ||
      (pcmWaveFormat.wBitsPerSample != 8))
      goto LOWPASS_ERROR_BAD_FORMAT;   // bad input file format

   /* Search the input file for for the 'data' chunk.
    */
   ckIn.ckid = mmioFOURCC('d', 'a', 't', 'a');
   if (mmioDescend(hmmioIn, &ckIn, &ckInRIFF, MMIO_FINDCHUNK) != 0)
      goto LOWPASS_ERROR_BAD_FORMAT;   // no 'data' chunk

   /* Create the output file RIFF chunk of form type 'WAVE'.
    */
   ckOutRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
   if (mmioCreateChunk(hmmioOut, &ckOutRIFF, MMIO_CREATERIFF) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* We are now descended into the 'RIFF' chunk we just created.
    * Now create the 'fmt ' chunk. Since we know the size of this chunk,
    * specify it in the MMCKINFO structure so MMIO doesn't have to seek
    * back and set the chunk size after ascending from the chunk.
    */
   ckOut.ckid = mmioFOURCC('f', 'm', 't', ' ');
   ckOut.cksize = sizeof(pcmWaveFormat);  // we know the size of this ck.
   if (mmioCreateChunk(hmmioOut, &ckOut, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* Write the PCMWAVEFORMAT structure to the 'fmt ' chunk.
    */
   if (mmioWrite(hmmioOut, (HPSTR) &pcmWaveFormat, sizeof(pcmWaveFormat))
      != sizeof(pcmWaveFormat))
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
    */
   if (mmioAscend(hmmioOut, &ckOut, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* Create the 'data' chunk that holds the waveform samples.
    */
   ckOut.ckid = mmioFOURCC('d', 'a', 't', 'a');
   if (mmioCreateChunk(hmmioOut, &ckOut, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* Read samples from the 'data' chunk of the input file, and write
    * samples to the 'data' chunk of the output file.  Each sample in
    * the output file equals the average of the corresponding sample
    * in the input file and the previous two samples from the input file.
    * Access the I/O buffers of <hmmioIn> and <hmmioOut> directly,
    * since this is faster than calling mmioRead() and mmioWrite()
    * for each sample.
    */
   abSamples[0] = abSamples[1] = abSamples[2] = 128;

   /* Begin direct access of the I/O buffers.
    */
   if (mmioGetInfo(hmmioIn, &mmioinfoIn, 0) != 0)
      goto LOWPASS_ERROR_UNKNOWN;
   if (mmioGetInfo(hmmioOut, &mmioinfoOut, 0) != 0)
      goto LOWPASS_ERROR_UNKNOWN;

   /* For each input sample, compute and write the output sample.
    */
   for (lSamples = ckIn.cksize; lSamples > 0; lSamples--)
   {
      /* If we are at the end of the input file I/O buffer, fill it.
       * Test to see that we don't hit end of file while (lSamples > 0).
       */
      if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
      {
         if (mmioAdvance(hmmioIn, &mmioinfoIn, MMIO_READ) != 0)
            goto LOWPASS_ERROR_CANNOT_READ;

         if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
            goto LOWPASS_ERROR_CANNOT_READ;
      }

      /* If we are the end of the output file I/O buffer, flush it.
       */
      if (mmioinfoOut.pchNext == mmioinfoOut.pchEndWrite)
      {
         mmioinfoOut.dwFlags |= MMIO_DIRTY;
         if (mmioAdvance(hmmioOut, &mmioinfoOut, MMIO_WRITE) != 0)
            goto LOWPASS_ERROR_CANNOT_WRITE;
      }

      /* Keep track of the last 3 samples so we can average.
       */
      abSamples[2] = abSamples[1];  // next-to-last sample
      abSamples[1] = abSamples[0];  // last sample
      abSamples[0] = *(mmioinfoIn.pchNext)++; // current sample

      /* The output file sample is the average of the last
       * 3 input file samples.
       */
      *(mmioinfoOut.pchNext)++ = (BYTE) (((int) abSamples[0]
         + (int) abSamples[1] + (int) abSamples[2]) / 3);
   }

   /* We are through processing samples, end direct access of
    * the I/O buffers. Set the MMIO_DIRTY flag for the output buffer
    * to flush it.
    */
   if (mmioSetInfo(hmmioIn, &mmioinfoIn, 0) != 0)
      goto LOWPASS_ERROR_UNKNOWN;
   mmioinfoOut.dwFlags |= MMIO_DIRTY;
   if (mmioSetInfo(hmmioOut, &mmioinfoOut, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot flush, probably

   /* Ascend the output file out of the 'data' chunk -- this will cause
    * the chunk size of the 'data' chunk to be written.
    */
   if (mmioAscend(hmmioOut, &ckOut, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* Ascend the output file out of the 'RIFF' chunk -- this will cause
    * the chunk size of the 'RIFF' chunk to be written.
    */
   if (mmioAscend(hmmioOut, &ckOutRIFF, 0) != 0)
      goto LOWPASS_ERROR_CANNOT_WRITE; // cannot write file, probably

   /* We are done -- files are closed below.
    */
   goto EXIT_FUNCTION;

LOWPASS_ERROR_BAD_FORMAT:

   LoadString(ghInst, IDS_MUSTBEPCMWAVE, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   MessageBox(hWnd, lpstrLoadStrBuf,
         gszAppName, MB_OK | MB_ICONEXCLAMATION);
   goto EXIT_FUNCTION;

LOWPASS_ERROR_CANNOT_READ:

   LoadString(ghInst, IDS_CANTREADINPUTWAV, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   MessageBox(hWnd, lpstrLoadStrBuf,
         gszAppName, MB_OK | MB_ICONEXCLAMATION);
   goto EXIT_FUNCTION;

LOWPASS_ERROR_CANNOT_WRITE:

   LoadString(ghInst, IDS_CANTWRITEOUTPUT, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   MessageBox(hWnd, lpstrLoadStrBuf,
         gszAppName, MB_OK | MB_ICONEXCLAMATION);
   goto EXIT_FUNCTION;

LOWPASS_ERROR_UNKNOWN:

   LoadString(ghInst, IDS_UNKOWNERROR, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   MessageBox(hWnd, lpstrLoadStrBuf,
         gszAppName, MB_OK | MB_ICONEXCLAMATION);
   goto EXIT_FUNCTION;

EXIT_FUNCTION:

   /* Close the files (unless they weren't opened successfully).
    */
   if (hmmioIn != NULL)
      mmioClose(hmmioIn, 0);
   if (hmmioOut != NULL)
      mmioClose(hmmioOut, 0);
}

/* WinMain - Entry point for LowPass.
 */
int PASCAL WinMain(
HINSTANCE hInst,
HINSTANCE hPrev,
LPSTR lpszCmdLine,
int iCmdShow)
{
   // Keep string memory out of WIN16 DS.
   lpstrLoadStrBuf = GlobalAllocPtr(GMEM_MOVEABLE, LOADSTRBUFSIZE);
   if(lpstrLoadStrBuf == NULL)
   {
      // don't use LoadString here; it fails when memory is low
      MessageBox(NULL, "No memory left.",
            NULL, MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
      return FALSE;
   }
   /* Save instance handle for dialog boxes.
    */
   ghInst = hInst;

   /* Display our dialog box.
    */
   DialogBox(ghInst, "LOWPASSBOX", NULL, LowPassDlgProc);
   if(lpstrLoadStrBuf)
      GlobalFreePtr(lpstrLoadStrBuf);
   return TRUE;
}

/* AboutDlgProc - Dialog procedure function for ABOUTBOX dialog box.
 */
BOOL FAR PASCAL AboutDlgProc(
HWND hWnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam)
{
   switch (wMsg)
   {
   case WM_INITDIALOG:
      return TRUE;

   case WM_COMMAND:
      if (wParam == IDOK || wParam == IDCANCEL)
         EndDialog(hWnd, TRUE);
      break;
   }
   return FALSE;
}

/* LowPassDlgProc - Dialog procedure function for LOWPASSBOX dialog box.
 */
BOOL WINAPI LowPassDlgProc(
HWND hWnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam)
{
   HMENU hmenuSystem;   // system menu
   HCURSOR  ghcurSave;  // previous cursor

   switch (wMsg)
   {
   case WM_INITDIALOG:
      /* Append "About" menu item to system menu.
       */
      hmenuSystem = GetSystemMenu(hWnd, FALSE);
      AppendMenu(hmenuSystem, MF_SEPARATOR, 0, NULL);
      LoadString(ghInst, IDS_ABOUTMENUCAPTION, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      AppendMenu(hmenuSystem, MF_STRING, IDM_ABOUT,
         lpstrLoadStrBuf);
      return TRUE;

   case WM_SYSCOMMAND:
      switch (wParam)   // WIN16 and 32 have same message packing
      {
         case IDM_ABOUT:
            /* Display "About" dialog box.
             */
            DialogBox(ghInst, "ABOUTBOX", hWnd, AboutDlgProc);
            break;
      }
      break;

   case WM_COMMAND:
      switch (wParam)
      {
         case IDOK:  // "Begin"
            /* Set "busy" cursor, filter input file, restore cursor.
             */
            ghcurSave = SetCursor(LoadCursor(NULL, IDC_WAIT));
            DoLowPass(hWnd);
            SetCursor(ghcurSave);
            break;

         case IDCANCEL: // "Done"
            EndDialog(hWnd, TRUE);
            break;
      }
      break;
   }
   return FALSE;
}


STRINGS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by STRINGS.RC
//
#define IDS_MUSTBEPCMWAVE               112
#define IDS_CANTREADINPUTWAV            113
#define IDS_CANTWRITEOUTPUT             114
#define IDS_UNKOWNERROR                 115
#define IDS_ABOUTMENUCAPTION            116

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

The MCIAPP sample is an MCI test application.


APP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  app.h
//
//  Description:
//      This is a sample application that demonstrates how to use the
//      Media Control Interface (MCI) in Windows. This application is
//      also useful as an MCI device tester.
//
//  History:
//      11/ 8/92    created.
//
//==========================================================================;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Application Version Information:
//
//
//
//
//  NOTE! all string resources that will be used in app.rcv for the
//  version resource information *MUST* have an explicit \0 terminator!
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define APP_VERSION_MAJOR           4
#define APP_VERSION_MINOR           0
#define APP_VERSION_BUILD           0
#ifdef UNICODE
#define APP_VERSION_STRING_RC       "Version 4.00 (Unicode Enabled)\0"
#else
#define APP_VERSION_STRING_RC       "Version 4.00\0"
#endif

#define APP_VERSION_NAME_RC         "mciapp.exe\0"
#define APP_VERSION_BYLINE_RC       "\0"
#define APP_VERSION_COMPANYNAME_RC  "Microsoft Corporation\0"
#define APP_VERSION_COPYRIGHT_RC    "Copyright 1985 - 1998 Microsoft Corporation\0"

#ifdef WIN32
#if (defined(_X86_)) || (defined(i386))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Win32 (i386)\0"
#endif
#if (defined(_MIPS_)) || (defined(MIPS))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT (MIPS)\0"
#endif
#if (defined(_ALPHA_)) || (defined(ALPHA))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT (Alpha)\0"
#endif
#ifndef APP_VERSION_PRODUCTNAME_RC
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT\0"
#endif
#else
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows\0"
#endif

#ifdef DEBUG
#define APP_VERSION_DESCRIPTION_RC  "MCI Script Application (debug)\0"
#else
#define APP_VERSION_DESCRIPTION_RC  "MCI Script Application\0"
#endif

//
//  Unicode versions (if UNICODE is defined)... the resource compiler
//  cannot deal with the TEXT() macro.
//
#define APP_VERSION_STRING          TEXT(APP_VERSION_STRING_RC)
#define APP_VERSION_NAME            TEXT(APP_VERSION_NAME_RC)
#define APP_VERSION_BYLINE          TEXT(APP_VERSION_BYLINE_RC)
#define APP_VERSION_COMPANYNAME     TEXT(APP_VERSION_COMPANYNAME_RC)
#define APP_VERSION_COPYRIGHT       TEXT(APP_VERSION_COPYRIGHT_RC)
#define APP_VERSION_PRODUCTNAME     TEXT(APP_VERSION_PRODUCTNAME_RC)
#define APP_VERSION_DESCRIPTION     TEXT(APP_VERSION_DESCRIPTION_RC)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc defines for misc sizes and things...
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  bilingual. this allows the same identifier to be used in resource files
//  and code without having to decorate the id in your code.
//
#ifdef RC_INVOKED
    #define RCID(id)    id
#else
    #define RCID(id)    MAKEINTRESOURCE(id)
#endif

//
//  misc. defines
//
#define APP_MAX_APP_NAME_CHARS      30
#define APP_MAX_APP_NAME_BYTES      (APP_MAX_APP_NAME_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_RC_CHARS     512
#define APP_MAX_STRING_RC_BYTES     (APP_MAX_STRING_RC_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_ERROR_CHARS  512
#define APP_MAX_STRING_ERROR_BYTES  (APP_MAX_STRING_ERROR_CHARS * sizeof(TCHAR))
#define APP_MAX_FILE_PATH_CHARS     144
#define APP_MAX_FILE_PATH_BYTES     (APP_MAX_FILE_PATH_CHARS * sizeof(TCHAR))
#define APP_MAX_FILE_TITLE_CHARS    16
#define APP_MAX_FILE_TITLE_BYTES    (APP_MAX_FILE_TITLE_CHARS * sizeof(TCHAR))

#define APP_MAX_STRING_INT_CHARS    18
#define APP_MAX_STRING_INT_BYTES    (APP_MAX_STRING_INT_CHARS * sizeof(TCHAR))

#define APP_MAX_EXT_DEFAULT_CHARS   4
#define APP_MAX_EXT_DEFAULT_BYTES   (APP_MAX_EXT_DEFAULT_CHARS * sizeof(TCHAR))
#define APP_MAX_EXT_FILTER_CHARS    256
#define APP_MAX_EXT_FILTER_BYTES    (APP_MAX_EXT_FILTER_CHARS * sizeof(TCHAR))

#define APP_WINDOW_XOFFSET          CW_USEDEFAULT
#define APP_WINDOW_YOFFSET          CW_USEDEFAULT
#define APP_WINDOW_WIDTH            500 //CW_USEDEFAULT
#define APP_WINDOW_HEIGHT           300 //CW_USEDEFAULT

//
//  resource defines...
//
#define ICON_APP                    RCID(10)
#define ACCEL_APP                   RCID(15)

//
//  the application menu...
//
//  NOTE! for our Edit menu, we use the following defines from windows.h--
//  so don't reuse these defines for menu items!
//
//      #define WM_CUT      0x0300
//      #define WM_COPY     0x0301
//      #define WM_PASTE    0x0302
//      #define WM_CLEAR    0x0303
//      #define WM_UNDO     0x0304
//
#define MENU_APP                    RCID(20)
#define APP_MENU_ITEM_FILE          0
#define IDM_FILE_NEW                1100
#define IDM_FILE_OPEN               1101
#define IDM_FILE_SAVE               1102
#define IDM_FILE_SAVEAS             1103
#define IDM_FILE_ABOUT              1109
#define IDM_FILE_EXIT               1110

#define APP_MENU_ITEM_EDIT          1
#define IDM_EDIT_SELECTALL          1200

#define APP_MENU_ITEM_DEVICE        2
#define IDM_DEVICE_NONE             1300
#define IDM_DEVICE_OPENLIST         1398
#define IDM_DEVICE_CLOSEALL         1399

#define APP_MENU_ITEM_OPTIONS       3
#define IDM_OPTIONS_EDITONLY        1400
#define IDM_OPTIONS_DEBUGLOG        1401
#define IDM_OPTIONS_YIELDEXEC       1402
#define IDM_OPTIONS_FONT            1410

#define IDD_ACCL_ABORT              666

//
//  the main window control id's...
//
#define IDD_APP_BTN_STEP            100
#define IDD_APP_BTN_STOP            IDD_APP_BTN_STEP
#define IDD_APP_BTN_GO              101
#define IDD_APP_BTN_RUN             102
#define IDD_APP_EDIT_RUNCOUNT       103
#define IDD_APP_TEXT_OUTPUT         110
#define IDD_APP_EDIT_SCRIPT         150
#define IDD_APP_TEXT_STATUS         175
#define IDD_APP_BOX_SEPARATOR1      176
#define IDD_APP_TEXT_NOTIFY         177
#define IDD_APP_BOX_SEPARATOR2      178
#define IDD_APP_TEXT_OPTIONS        179

//
//  misc dlg boxes...
//
#define DLG_ABOUT                   RCID(50)
#define IDD_ABOUT_VERSION_OS        100
#define IDD_ABOUT_VERSION_PLATFORM  101

#define DLG_MCIERR                  RCID(51)

//
//  current open mci devices list window...
//
#define DLG_MCIDEVS                 RCID(52)
#define IDD_MCIDEVS_BTN_INFO        100
#define IDD_MCIDEVS_LIST_OPEN       101

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  string resources
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define IDS_APP_NAME                100
#define IDS_FILE_UNTITLED           101

#define IDS_OFN_EXT_DEF             125
#define IDS_OFN_EXT_FILTER          126

#define IDS_DROP_CONTINUE           150

#define IDS_EXEC_ABORT              175

#define IDS_MCI_SCRIPT_CREATED      200
#define IDS_MCI_SCRIPT_OPENED       201
#define IDS_MCI_SCRIPT_SAVED        202
#define IDS_MCI_SCRIPT_CHANGED      203

#define IDS_MCI_NOTIFY_SUCCESSFUL   225
#define IDS_MCI_NOTIFY_SUPERSEDED   226
#define IDS_MCI_NOTIFY_ABORTED      227
#define IDS_MCI_NOTIFY_FAILURE      228
#define IDS_MCI_NOTIFY_UNKNOWN      229

#define IDS_WARN_OPEN_DEVICES       400

#define IDS_ERROR_OPEN_FAILED       500

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  APPINIT.C -- Public helper functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

LRESULT FNGLOBAL AppCreate
(
    HWND            hwnd,
    LPCREATESTRUCT  pcs
);

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND            hwnd
);

LRESULT FNGLOBAL AppEndSession
(
    HWND            hwnd,
    BOOL            fEndSession
);

LRESULT FNGLOBAL AppClose
(
    HWND            hwnd
);

HWND FNGLOBAL AppInit
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPTSTR          pszCmdLine,
    int             nCmdShow
);

int FNGLOBAL AppExit
(
    HINSTANCE       hinst,
    int             nResult
);

LRESULT FNGLOBAL AppGetWindowsVersion
(
    PTSTR           pszEnvironment,
    PTSTR           pszPlatform
);

LRESULT FNGLOBAL AppWinIniChange
(
    HWND            hwnd,
    LPCTSTR         pszSection
);

HFONT FNGLOBAL AppChooseFont
(
    HWND            hwnd,
    HFONT           hFont,
    PLOGFONT        plf
);

#define APP_CFF_NORMAL          0x0000
#define APP_CFF_ITALIC          0x0001
#define APP_CFF_UNDERLINE       0x0002
#define APP_CFF_STRIKEOUT       0x0004
#define APP_CFF_BOLD            0x0008

BOOL FNEXPORT AboutDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL FNGLOBAL AppProfileWriteBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
);

BOOL FNGLOBAL AppProfileReadBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  APP.C -- Public helper functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

int FNCGLOBAL AppMsgBox
(
    HWND            hwnd,
    UINT            fuStyle,
    PCTSTR          pszFormat,
    ...
);

int FNCGLOBAL AppMsgBoxId
(
    HWND            hwnd,
    UINT            fuStyle,
    UINT            uIdsFormat,
    ...
);

void FNGLOBAL AppHourGlass
(
    BOOL            fHourGlass
);

BOOL FNGLOBAL AppYield
(
    HWND            hwnd,
    BOOL            fIsDialog
);

int FNGLOBAL AppDialogBox
(
    HWND            hwnd,
    LPCTSTR         pszDlg,
    DLGPROC         pfnDlg,
    LPARAM          lParam
);

BOOL FNGLOBAL AppTitle
(
    HWND            hwnd,
    PCTSTR          pszFileTitle
);

int FNCGLOBAL AppSetWindowText
(
    HWND            hwnd,
    PCTSTR          pszFormat,
    ...
);

int FNCGLOBAL AppSetWindowTextId
(
    HWND            hwnd,
    UINT            uIdsFormat,
    ...
);

BOOL FNGLOBAL AppGetFileTitle
(
    PCTSTR          pszFilePath,
    PTSTR           pszFileTitle
);

BOOL FNGLOBAL AppGetFileName
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    UINT            fuFlags
);

BOOL FNGLOBAL AppFileNew
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
);

//
//  fuFlags for AppGetFileName()...
//
#define APP_GFNF_OPEN       0x0000
#define APP_GFNF_SAVE       0x0001

BOOL FNGLOBAL AppFileSave
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    BOOL            fSaveAs
);

LRESULT FNEXPORT AppWndProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  global variables, etc.
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  flags for gfuAppOptions
//
#define APP_OPTF_EDITONLY       0x0001
#define APP_OPTF_YIELDEXEC      0x0002
#define APP_OPTF_DEBUGLOG       0x0004
#ifdef UNICODE
#define APP_OPTF_UNICODE        0x0008
#endif

#define APP_OPTF_DEVICELIST     0x8000

extern HINSTANCE    ghinst;
extern UINT         gfuAppOptions;

extern TCHAR        gszAppSection[];
extern TCHAR        gszNull[];

extern TCHAR        gszAppName[APP_MAX_APP_NAME_CHARS];
extern TCHAR        gszFileUntitled[APP_MAX_FILE_TITLE_CHARS];
extern TCHAR        gszAppFileTitle[APP_MAX_FILE_TITLE_CHARS];
extern TCHAR        gszAppFilePath[APP_MAX_FILE_PATH_CHARS];


APPINIT.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  appinit.c
//
//  Description:
//      This file contains initialization and termination code for the
//      application (as well as some rarely used stuff).
//
//  History:
//      11/ 8/92    created.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#ifndef WIN32
#include <shellapi.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include "appport.h"
#include "app.h"
#include "mciapp.h"

#include "debug.h"

//==========================================================================;
//
//  Application helper functions and rarely called stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  DWORD AppGetWindowsVersion
//
//  Description:
//      This function returns the version of Windows that the application
//      is running on plus some platform information.
//
//  Arguments:
//      PTSTR pach: Options pointer to buffer to receive text string of
//      the Windows version and platform.
//
//  Return (LRESULT):
//      The return value will be the version and platform information of
//      the current operating system in the following format:
//
//      0xPPPPMMRR where:
//
//      MM      :   major version of Windows
//      RR      :   minor version (revision) of Windows
//      PPPP    :   the platform the application is running on which
//                  will be one of the following:
//
//                  #ifdef WIN32
//                      the HIWORD() is RESERVED except for the high bit:
//                          high bit is 0 = Windows NT
//                          high bit is 1 = Win32s/Windows 3.1
//                  #else
//                      0xMMRR = Major and Minor version of [MS-]DOS
//                      GetWinFlags() & 0x8000 = Windows on OS/2 (WLO)
//                      GetWinFlags() & 0x4000 = Windows on Windows NT (WOW)
//                  #endif
//
//  History:
//       2/13/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppGetWindowsVersion
(
    PTSTR           pszEnvironment,
    PTSTR           pszPlatform
)
{

    BYTE    bVerWinMajor;
    BYTE    bVerWinMinor;
    UINT    uVerEnv;
    DWORD   dw;
    LRESULT lr;

    dw = GetVersion();

    //
    //  massage the version information into something intelligent
    //
    //
    bVerWinMajor = LOBYTE(LOWORD(dw));
    bVerWinMinor = HIBYTE(LOWORD(dw));
    uVerEnv      = HIWORD(dw);
    lr = MAKELPARAM(((UINT)bVerWinMajor << 8) | bVerWinMinor, uVerEnv);

    //
    //  if caller wants the environment string version...
    //
    if (NULL != pszEnvironment)
    {
    //
    //
    //
#ifdef WIN32
{
    static TCHAR    szFormatVersion[]   = TEXT("%s Version %u.%.2u");
    static TCHAR    szEnvWinNT[]        = TEXT("Windows NT");
    static TCHAR    szEnvWin32s[]       = TEXT("Win32s");

    wsprintf(pszEnvironment, szFormatVersion,
             (LPSTR)((0x8000 & uVerEnv) ? szEnvWin32s : szEnvWinNT),
             bVerWinMajor, bVerWinMinor);
}
#else
{
#ifndef WF_WINNT
    #define WF_WINNT        0x4000
    #define WF_WLO          0x8000
#endif
#ifndef WF_CPUMASK
    #define WF_CPUMASK      0xFC000000
    #define WF_CPU_X86      0
    #define WF_CPU_R4000    1
    #define WF_CPU_ALPHA    2
    #define WF_CPU_CLIPPER  3
#endif

    static TCHAR    szFormatSubSys[]= TEXT("Windows Version %u.%.2u (%s%s)\n%s Subsystem, DOS Version %u.%.2u");
    static TCHAR    szFormatDOS[]   = TEXT("Windows Version %u.%.2u (%s%s)\nDOS Version %u.%.2u");
    static TCHAR    szSubSysWLO[]   = TEXT("WLO");
    static TCHAR    szSubSysWOW[]   = TEXT("WOW");
    static TCHAR    szModeEnhanced[]= TEXT("Enhanced");
    static TCHAR    szModeStandard[]= TEXT("Standard");
    static TCHAR    szEnvPaging[]   = TEXT(", Paging");

    DWORD   dwWinFlags;
    PTSTR   pszMode;

    BYTE    bVerEnvMajor    = HIBYTE(LOWORD(uVerEnv));
    BYTE    bVerEnvMinor    = LOBYTE(LOWORD(uVerEnv));

    dwWinFlags = GetWinFlags();

    pszMode = (dwWinFlags & WF_ENHANCED) ? szModeEnhanced : szModeStandard;
    if (dwWinFlags & (WF_WLO | WF_WINNT))
    {
        wsprintf(pszEnvironment, szFormatSubSys, bVerWinMajor, bVerWinMinor,
                 (LPSTR)pszMode,
                 (LPSTR)((dwWinFlags & WF_PAGING) ? szEnvPaging : gszNull),
                 (LPSTR)((dwWinFlags & WF_WINNT) ? szSubSysWOW : szSubSysWLO),
                 bVerEnvMajor, bVerEnvMinor);
    }
    else
    {
        wsprintf(pszEnvironment, szFormatDOS, bVerWinMajor, bVerWinMinor,
                 (LPSTR)pszMode,
                 (LPSTR)((dwWinFlags & WF_PAGING) ? szEnvPaging : gszNull),
                 bVerEnvMajor, bVerEnvMinor);
    }
}
#endif
    }

    //
    //  if caller wants the platform string version...
    //
    if (NULL != pszPlatform)
    {
#ifdef WIN32
{
    static TCHAR    szFormatPlatform[]  = TEXT("%s%u, %u Processor(s)");
    static TCHAR    szProcessorIntel[]  = TEXT("Intel ");
    static TCHAR    szProcessorMIPS[]   = TEXT("MIPS R");
    static TCHAR    szProcessorAlpha[]  = TEXT("DEC Alpha ");
    static TCHAR    szProcessorUnknown[]  = TEXT("Unknown");

    SYSTEM_INFO sysinfo;
    PTSTR       pszProcessor;

    //
    //  this is absolutely silly. one would think that the dwOemId member
    //  would provide something useful like the processor class... but
    //  no, it doesn't--it is always 0.
    //
    GetSystemInfo(&sysinfo);
    switch (sysinfo.dwProcessorType)
    {
        case PROCESSOR_INTEL_386:
        case PROCESSOR_INTEL_486:
            pszProcessor = szProcessorIntel;
            break;

        case PROCESSOR_MIPS_R4000:
            pszProcessor = szProcessorMIPS;
            break;

        case PROCESSOR_ALPHA_21064:
            pszProcessor = szProcessorAlpha;
            break;

        default:
            pszProcessor = szProcessorUnknown;
            break;
    }

    //
    //
    //
    if (pszProcessor != szProcessorUnknown)
        wsprintf(pszPlatform, szFormatPlatform, (LPSTR)pszProcessor,
                 sysinfo.dwProcessorType, sysinfo.dwNumberOfProcessors);
    else
        lstrcpy(pszPlatform, pszProcessor);
}
#else
{
    static TCHAR    szPlat286[]         = TEXT("80286");
    static TCHAR    szPlat386[]         = TEXT("80386");
    static TCHAR    szPlat486[]         = TEXT("i486");
    static TCHAR    szPlatR4000[]       = TEXT("MIPS R4000, Emulation: ");
    static TCHAR    szPlatAlpha21064[]  = TEXT("Alpha 21064, Emulation: ");
    static TCHAR    szPlatClipper[]     = TEXT("Clipper, Emulation: ");
    static TCHAR    szPlat80x87[]       = TEXT(", 80x87");

    DWORD   dwWinFlags;

    dwWinFlags = GetWinFlags();
    pszPlatform[0] = '\0';

    if (dwWinFlags & (WF_WLO | WF_WINNT))
    {
        switch ((dwWinFlags & WF_CPUMASK) >> 26)
        {
            case WF_CPU_X86:
                break;

            case WF_CPU_R4000:
                lstrcpy(pszPlatform, szPlatR4000);
                break;

            case WF_CPU_ALPHA:
                lstrcpy(pszPlatform, szPlatAlpha21064);
                break;

            case WF_CPU_CLIPPER:
                lstrcpy(pszPlatform, szPlatClipper);
                break;
        }
    }

    if (dwWinFlags & WF_CPU286)
        lstrcat(pszPlatform, szPlat286);
    else if (dwWinFlags & WF_CPU386)
        lstrcat(pszPlatform, szPlat386);
    else if (dwWinFlags & WF_CPU486)
        lstrcat(pszPlatform, szPlat486);

    if (dwWinFlags & WF_80x87)
        lstrcat(pszPlatform, szPlat80x87);
}
#endif
    }

    //
    //  return the result
    //
    return (lr);
} // AppGetWindowsVersion()

//--------------------------------------------------------------------------;
//
//  LRESULT AppWinIniChange
//
//  Description:
//      This function is responsible for handling the WM_WININICHANGE
//      message. This message is sent when modifications have been made
//      to WIN.INI (from SystemParametersInfo() or other sources).
//
//      An application should re-enumerate system metrics (GetSystemMetrics)
//      and system color (GetSystemColors) information that it has cached.
//      Note that checking the section that was modified should be done if
//      some enumerations are time consuming.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      LPCTSTR pszSection: Pointer to section name that has been modified
//      in WIN.INI. Note that this argument might be NULL (sent by apps
//      that were written incorrectly!). If it is NULL, then this application
//      should re-enumerate ALL metrics, colors, etc.
//
//  Return (LRESULT):
//      The return value is zero if the message is processed.
//
//  History:
//       2/15/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppWinIniChange
(
    HWND            hwnd,
    LPCTSTR         pszSection
)
{
    DPF(0, "AppWinIniChanged(hwnd=%Xh, pszSection='%s')",
            hwnd, (NULL == pszSection) ? TEXT("(null)") : pszSection);

    //
    //  we processed the message...
    //
    return (0L);
} // AppWinIniChange()

//--------------------------------------------------------------------------;
//
//  HFONT AppChooseFont
//
//  Description:
//      This function is a wrapper for the ChooseFont() common dialog.
//      The purpose of this function is to let the user choose a font that
//      looks good to them--regardless of how stupid it really looks.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      HFONT hfont: Handle to current font (default for chooser dialog).
//
//      PLOGFONT plf: Pointer to optional LOGFONT structure to receive a
//      copy of the LOGFONT information for the newly chosen font.
//
//  Return (HFONT):
//      The return value is the newly chosen font. If no new font was chosen
//      then the return value is NULL.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

HFONT FNGLOBAL AppChooseFont
(
    HWND            hwnd,
    HFONT           hfont,
    PLOGFONT        plf
)
{
    LOGFONT     lf;
    CHOOSEFONT  cf;
    BOOL        f;
    HFONT       hfontNew;

    //
    //  get the font info for the current font...
    //
    GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);

    //
    //  fill in the choosefont structure
    //
    cf.lStructSize  = sizeof(CHOOSEFONT);
    cf.hwndOwner    = hwnd;
    cf.hDC          = NULL;
    cf.Flags        = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
    cf.lCustData    = 0;
    cf.lpfnHook     = NULL;
    cf.hInstance    = NULL;
    cf.nFontType    = SCREEN_FONTTYPE;
    cf.lpLogFont    = (LPLOGFONT)&lf;

    //
    //  splash a dialog into the user's face..
    //
    hfontNew = NULL;
    f = ChooseFont(&cf);
    if (f)
    {
        //
        //  create the new font..
        //
        hfontNew = CreateFontIndirect(&lf);
        if (NULL == hfontNew)
            return (NULL);

        //
        //  copy the logfont structure if caller wants it
        //
        if (NULL != plf)
            *plf = lf;
    }

    //
    //  return the new font (if one was chosen)
    //
    return (hfontNew);
} // AppChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileWriteBytes
//
//  Description:
//      This function writes a raw structure of bytes to the application's
//      ini section that can later be retrieved using AppProfileReadBytes.
//      This gives an application the ability to write any structure to
//      the ini file and restore it later--very useful.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PCTSTR pszKey: Pointer to key name for the stored data.
//
//      LPBYTE pbStruct: Pointer to the data to be saved.
//
//      UINT cbStruct: Count in bytes of the data to store.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if it fails.
//
//  History:
//       3/10/93   created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileWriteBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
)
{
    static TCHAR achNibbleToChar[] =
    {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
    };
    #define     NIBBLE2CHAR(x)      (achNibbleToChar[x])

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;

    //
    //  if pbStruct is NULL, then erase the key from the ini file, otherwise
    //  format the raw bytes into a hex string and write that out...
    //
    fAllocated = FALSE;
    psz        = NULL;
    if (NULL != pbStruct)
    {
        //
        //  check if the quick buffer can be used for formatting the output
        //  text--if it cannot, then alloc space for it. note that space
        //  must be available for an ending checksum byte (2 bytes for high
        //  and low nibble) as well as a null terminator.
        //
        psz     = (LPTSTR)ach;
        cchTemp = cbStruct * 2 + 3;
        if (cchTemp > SIZEOF(ach))
        {
            psz = GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
            if (NULL == psz)
                return (FALSE);

            fAllocated = TRUE;
        }

        //
        //  step through all bytes in the structure and convert it to
        //  a string of hex numbers...
        //
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            //
            //  grab the next byte and add into checksum...
            //
            bChecksum += (b = *pbStruct);

            *pch++ = NIBBLE2CHAR((b >> (BYTE)4) & (BYTE)0x0F);
            *pch++ = NIBBLE2CHAR(b & (BYTE)0x0F);
        }

        //
        //  add the checksum byte to the end and null terminate the hex
        //  dumped string...
        //
        *pch++ = NIBBLE2CHAR((bChecksum >> (BYTE)4) & (BYTE)0x0F);
        *pch++ = NIBBLE2CHAR(bChecksum & (BYTE)0x0F);
        *pch   = '\0';
    }

    //
    //  write the string of hex bytes out to the ini file...
    //
    fReturn = WriteProfileString(gszAppSection, pszKey, psz);

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileWriteBytes

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileReadBytes
//
//  Description:
//      This function reads a previously stored structure of bytes from
//      the application's ini file. This data must have been written with
//      the AppProfileWriteBytes function--it is checksumed to keep bad
//      data from blowing up the application.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PCTSTR pszKey: Pointer to key that contains the data.
//
//      LPBYTE pbStruct: Pointer to buffer to receive the data.
//
//      UINT cbStruct: Number of bytes expected.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if the function fails (bad checksum, missing key, etc).
//
//  History:
//       3/10/93   created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileReadBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
)
{
    //
    //  note that the following works for both upper and lower case, and
    //  will return valid values for garbage chars
    //
    #define CHAR2NIBBLE(ch) (BYTE)( ((ch) >= '0' && (ch) <= '9') ?  \
                                (BYTE)((ch) - '0') :                \
                                ((BYTE)(10 + (ch) - 'A') & (BYTE)0x0F) )

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    UINT        u;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;
    TCHAR       ch;

    //
    //  add one the the number of bytes needed to accomodate the checksum
    //  byte placed at the end by AppProfileWriteBytes...
    //
    cbStruct++;

    //
    //  check if the quick buffer can be used for retrieving the input
    //  text--if it cannot, then alloc space for it. note that there must
    //  be space available for the null terminator (the +1 below).
    //
    fAllocated = FALSE;
    psz        = (LPTSTR)ach;
    cchTemp    = cbStruct * 2 + 1;
    if (cchTemp > SIZEOF(ach))
    {
        psz = GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
        if (NULL == psz)
            return (FALSE);

        fAllocated = TRUE;
    }

    //
    //  read the hex string... if it is not the correct length, then assume
    //  error and return.
    //
    fReturn = FALSE;
    u = (UINT)GetProfileString(gszAppSection, pszKey, gszNull, psz, cchTemp);
    if ((cbStruct * 2) == u)
    {
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            ch = *pch++;
            b  = CHAR2NIBBLE(ch) << (BYTE)4;
            ch = *pch++;
            b |= CHAR2NIBBLE(ch);

            //
            //  if this is not the final byte (the checksum byte), then
            //  store it and accumulate checksum..
            //
            if (cbStruct != 1)
                bChecksum += (*pbStruct = b);
        }

        //
        //  check the last byte read against the checksum that we calculated
        //  if they are not equal then return error...
        //
        fReturn = (bChecksum == b);
    }

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileReadBytes

//==========================================================================;
//
//  Initialization and exit code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppRegisterPenApp
//
//  Description:
//      This function is used to register and de-register an application
//      as being 'Pen Enhanced.' If the Windows installation is Pen enabled
//      then this function allows the RC Manager to replace all 'Edit'
//      controls with 'HEdit' controls.
//
//      This function must be called to register the application BEFORE
//      creating any edit controls.
//
//  Arguments:
//      BOOL fRegister: If this argument is TRUE, the app is registered
//      with the RC Manager as being Pen aware. If this argument is FALSE
//      the app is de-registered.
//
//  Return (BOOL):
//      The return value is TRUE if Windows for Pen Computing is installed
//      on the system. The return value is FALSE if the Windows installation
//      is not Pen enabled.
//
//  History:
//       3/ 1/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AppRegisterPenApp
(
    BOOL    fRegister
)
{
    #define RPA_DEFAULT     0x0001

    typedef void (FAR PASCAL *PENWINREGISTERPROC)(UINT, BOOL);

    static char                 szRegPenApp[]   = "RegisterPenApp";
    static PENWINREGISTERPROC   pfnRegPenApp    = NULL;

    HINSTANCE   hinstPenWin;

    //
    //  check if Windows for Pen Computing is installed--and if it is,
    //  dyna-link to the RegisterPenApp() function.
    //
    //  if Pens are not supported, then return FALSE.
    //
    if (NULL == pfnRegPenApp)
    {
        hinstPenWin = (HINSTANCE)GetSystemMetrics(SM_PENWINDOWS);
        if (NULL == hinstPenWin)
            return (FALSE);

        (FARPROC)pfnRegPenApp = GetProcAddress(hinstPenWin, szRegPenApp);
        if (NULL == pfnRegPenApp)
            return (FALSE);
    }

    //
    //  either enable or disable the RC Manager's Pen meathooks..
    //
    (*pfnRegPenApp)(RPA_DEFAULT, fRegister);

    return (TRUE);
} // AppRegisterPenApp()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCreate
//
//  Description:
//      This function is called to handle the WM_CREATE message for the
//      applications window. The application should finish the creation
//      of the window (create controls, allocate resources, etc). The
//      window has not been displayed (CreateWindow[Ex] has not returned).
//
//  Arguments:
//      HWND hwnd: Handle to the window that is in the process of being
//      created.
//
//      LPCREATESTRUCT pcs: Pointer to a CREATESTRUCT that contains info
//      about the window being created.
//
//  Return (LRESULT):
//      The return value should be nonzero if the application wishes to
//      let the window finish being created. A return of zero tells
//      CreateWindow[Ex] to fail the creation of the window.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppCreate
(
    HWND            hwnd,
    LPCREATESTRUCT  pcs
)
{
    static TCHAR    szButtonStep[]  = TEXT("&Step");
    static TCHAR    szButtonGo[]    = TEXT("&Go");
    static TCHAR    szButtonRun[]   = TEXT("&Run");

    static TCHAR    szTextCount[]   = TEXT("&Count: ");
    static TCHAR    szTextOutput[]  = TEXT("Output: ");
    static TCHAR    szTextOne[]     = TEXT("1");        // !!!

    static TCHAR    szBunnySmag[]   = TEXT("zYzBunnySmag");
    static TCHAR    szButton[]      = TEXT("button");
    static TCHAR    szEdit[]        = TEXT("edit");
    static TCHAR    szStatic[]      = TEXT("static");

    HINSTANCE   hinst;
    HFONT       hfont;
    HFONT       hfontFixed;
    TEXTMETRIC  tm;
    HDC         hdc;
    HWND        hwndButton;
    HWND        hwndScript;
    HWND        hwndStatus;
    SIZE        sSize;
    int         nHeightButton;
    int         nWidthButton;
    int         nHeightText;

    DPF(0, "AppCreate(hwnd=%Xh, cs.x=%d, cs.y=%d, cs.cx=%d, cs.cy=%d)",
            hwnd, pcs->x, pcs->y, pcs->cx, pcs->cy);

    hinst = pcs->hInstance;

    //
    //  create the button bar and status bar--use a non-bold var font.
    //  to use the non-bold font and make the controls a 'proper' size,
    //  we need to get the metrics of the font.. note that the extent
    //  we use for the width of the buttons on the button bar are
    //  computed using the longest text we expect to display
    //
    hfont = GetStockFont(SYSTEM_FONT);
    hfontFixed = GetStockFont(SYSTEM_FIXED_FONT);
    hdc = GetDC(hwnd);
    {
        hfont = SelectFont(hdc, hfont);
        GetTextMetrics(hdc, &tm);
        GetTextExtentPoint(hdc, szButton, SIZEOF(szButton), (LPSIZE)&sSize);
        hfont = SelectFont(hdc, hfont);
    }
    ReleaseDC(hwnd, hdc);

    //
    //  compute some almost random numbers for width and height...
    //
    nHeightText   = (int)(tm.tmHeight + tm.tmExternalLeading);
    nHeightButton = nHeightText * 3 / 2 + 2;
    nWidthButton  = sSize.cx * 3 / 2;

    //
    //  create the script window--use a non-bold fixed font...
    //
    hwndScript = CreateWindow(szEdit, gszNull,
                              ES_LEFT | ES_MULTILINE | WS_TABSTOP |
                              ES_AUTOVSCROLL | ES_AUTOHSCROLL |
                              ES_NOHIDESEL | WS_BORDER | WS_VSCROLL |
                              WS_HSCROLL | WS_VISIBLE | WS_CHILD,
                              0, 0, 0, 0, hwnd, (HMENU)IDD_APP_EDIT_SCRIPT,
                              hinst, NULL);
    SetWindowFont(hwndScript, hfontFixed, FALSE);

    //
    //  when creating a multiline edit control, the default text limit is
    //  32k _even in Win 32_. note that in Win 32 you can set text length
    //  to >32k without sending EM_LIMITTEXT, but you cannot EDIT the text!
    //
    //  so, send a limit of 'max int' which is the largest size this
    //  application can deal with (selections, etc are integers):
    //
    //      Win 16  : 0x7FFF _bytes_
    //      Win 32  : 0x7FFFFFFF _characters_
    //
    Edit_LimitText(hwndScript, (int)((UINT)-1 << 1 >> 1));

    //
    //  the status bar
    //
    hwndStatus = CreateWindow(szEdit, gszNull,
                              ES_LEFT | ES_AUTOHSCROLL | ES_READONLY |
                              WS_VISIBLE | WS_CHILD,
                              0, 0, 0, nHeightText,
                              hwnd, (HMENU)IDD_APP_TEXT_STATUS, hinst, NULL);
    SetWindowFont(hwndStatus, hfont, FALSE);

    hwndStatus = CreateWindow(szStatic, gszNull,
                              WS_BORDER | WS_VISIBLE | WS_CHILD,
                              0, 0, 0, nHeightText,
                              hwnd, (HMENU)IDD_APP_BOX_SEPARATOR1, hinst, NULL);

    hwndStatus = CreateWindow(szEdit, gszNull,
                              ES_LEFT | ES_AUTOHSCROLL | ES_READONLY |
                              WS_VISIBLE | WS_CHILD,
                              0, 0, 0, nHeightText,
                              hwnd, (HMENU)IDD_APP_TEXT_NOTIFY, hinst, NULL);
    SetWindowFont(hwndStatus, hfont, FALSE);

    hwndStatus = CreateWindow(szStatic, gszNull,
                              WS_BORDER | WS_VISIBLE | WS_CHILD,
                              0, 0, 0, nHeightText,
                              hwnd, (HMENU)IDD_APP_BOX_SEPARATOR2, hinst, NULL);

hwndStatus = CreateWindow(szStatic, gszNull,
                              SS_CENTER | WS_VISIBLE | WS_CHILD,
                              0, 0, 0, nHeightText,
                              hwnd, (HMENU)IDD_APP_TEXT_OPTIONS, hinst, NULL);
    SetWindowFont(hwndStatus, hfontFixed, FALSE);

    //
    //  the button bar
    //
    hwndButton = CreateWindow(szButton, szButtonStep,
                              BS_PUSHBUTTON | WS_TABSTOP | WS_VISIBLE | WS_CHILD,
                              0, 0, nWidthButton, nHeightButton,
                              hwnd, (HMENU)IDD_APP_BTN_STEP, hinst, NULL);
    SetWindowFont(hwndButton, hfont, FALSE);

    hwndButton = CreateWindow(szButton, szButtonGo,
                              BS_PUSHBUTTON | WS_TABSTOP | WS_VISIBLE | WS_CHILD,
                              nWidthButton, 0, nWidthButton, nHeightButton,
                              hwnd, (HMENU)IDD_APP_BTN_GO, hinst, NULL);
    SetWindowFont(hwndButton, hfont, FALSE);

    hwndButton = CreateWindow(szButton, szButtonRun,
                              BS_PUSHBUTTON | WS_TABSTOP | WS_VISIBLE | WS_CHILD,
                              nWidthButton * 2, 0, nWidthButton, nHeightButton,
                              hwnd, (HMENU)IDD_APP_BTN_RUN, hinst, NULL);
    SetWindowFont(hwndButton, hfont, FALSE);

    //
    //
    //
    hwndStatus = CreateWindow(szStatic, szTextCount,
                              SS_RIGHT | WS_VISIBLE | WS_CHILD,
                              nWidthButton * 3, (nHeightButton - nHeightText) / 2,
                              nWidthButton, nHeightText,
                              hwnd, (HMENU)-1, hinst, NULL);
    SetWindowFont(hwndStatus, hfont, FALSE);

    hwndScript = CreateWindow(szEdit, szTextOne,
                              ES_LEFT | ES_AUTOHSCROLL | WS_BORDER |
                              WS_TABSTOP | WS_VISIBLE | WS_CHILD,
                              nWidthButton * 4, 0, nWidthButton, nHeightButton,
                              hwnd, (HMENU)IDD_APP_EDIT_RUNCOUNT, hinst, NULL);
    SetWindowFont(hwndScript, hfont, FALSE);

    //
    //
    //
    hwndStatus = CreateWindow(szStatic, szTextOutput,
                              SS_RIGHT | WS_VISIBLE | WS_CHILD,
                              nWidthButton * 5, (nHeightButton - nHeightText) / 2,
                              nWidthButton, nHeightText,
                              hwnd, (HMENU)-1, hinst, NULL);
    SetWindowFont(hwndStatus, hfont, FALSE);

    hwndStatus = CreateWindow(szEdit, gszNull,
                              ES_LEFT | ES_AUTOHSCROLL | ES_READONLY |
                              WS_VISIBLE | WS_CHILD,
                              nWidthButton * 6, (nHeightButton - nHeightText) / 2,
                              nWidthButton, nHeightText,
                              hwnd, (HMENU)IDD_APP_TEXT_OUTPUT, hinst, NULL);
    SetWindowFont(hwndStatus, hfont, FALSE);

    //
    //  we want the focus to default to the script window so the user
    //  can immediately start typing...
    //
    SetFocus(hwndScript);

    //
    //  return nonzero to succeed the creation of the window
    //
    return (1L);
} // AppCreate()

//--------------------------------------------------------------------------;
//
//  LRESULT AppQueryEndSession
//
//  Description:
//      This function handles the WM_QUERYENDSESSION. This message is sent
//      by USER when ExitWindows has been called to end the Windows session.
//      This function can stop Windows from exiting if it is not convenient
//      for Windows to end.
//
//      Giving the user the option to save modified data before continueing
//      with the shutdown of Windows is a good idea.
//
//      Telling Windows to continue with the exit procedure does not
//      necessarily mean Windows will exit. All applications are queried
//      for shutdown approval. When the actual decision is made on whether
//      Windows will exit, WM_ENDSESSION will be sent with the result.
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//  Return (LRESULT):
//      Returns zero to STOP Windows from exiting. Returns non-zero to
//      allows windows to shut down.
//
//  History:
//       2/ 9/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND            hwnd
)
{
    BOOL    f;

    DPF(0, "AppQueryEndSession(hwnd=%Xh)", hwnd);

    //
    //  attempt to shut down--if this fails (user canceled it, etc) then
    //  do NOT allow the Windows to exit.
    //
    f = MciAppShutdown(hwnd, gszAppFilePath, gszAppFileTitle);
    if (!f)
        return (0L);

    //
    //  tell Windows to proceed with the shutdown process!
    //
    return (1L);
} // AppQueryEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppEndSession
//
//  Description:
//      This function is called to handle the WM_ENDSESSION message. This
//      message is generated after the application answers the
//      WM_QUERYENDSESSION message. The purpose of the WM_ENDSESSION
//      message is to tell the application if Windows will be exiting
//      (TRUE  == fEndSession) or the end session was canceled by an
//      application (FALSE == fEndSession).
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//      BOOL fEndSession: TRUE if Windows is exiting. FALSE if the end
//      session was canceled.
//
//  Return (LRESULT):
//      Returns zero if the message is processed. Note that an application
//      cannot halt the termination of Windows from this message--the
//      WM_QUERYENDSESSION is the only message that allows that behaviour.
//      If fEndSession is TRUE, Windows *WILL* exit--whether you like it
//      or not.
//
//  History:
//       2/ 9/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppEndSession
(
    HWND            hwnd,
    BOOL            fEndSession
)
{
    DPF(0, "AppEndSession(hwnd=%Xh, fEndSession=%d)", hwnd, fEndSession);

    //
    //  we processed the message, return zero..
    //
    return (0L);
} // AppEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppClose
//
//  Description:
//      This function handles the WM_CLOSE message for the application.
//      If the application should close, DestroyWindow() must be called
//      by this function. Otherwise the application will not close.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_CLOSE message.
//
//  Return (LRESULT):
//      There return value is zero. The DestroyWindow function will have
//      been called if the application should actually close.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppClose
(
    HWND            hwnd
)
{
    HWND        hwndScript;
    HFONT       hfont;
    BOOL        f;

    //
    //  if the Shift key is held down during the close message, then just
    //  save the current state but don't destroy the window... this is
    //  useful if the user does not want to exit the app and rerun it
    //  to make sure the state is saved--just before the user does something
    //  that may crash Windows or something..
    //
    if (GetKeyState(VK_SHIFT) < 0)
    {
        //
        //  save any settings that should be saved on app termination...
        //
        MciAppSettingsSave(hwnd);
        return (0L);
    }

    //
    //  attempt to shut down--if this fails (user canceled it, etc) then
    //  do NOT allow the window to be destroyed.
    //
    f = MciAppShutdown(hwnd, gszAppFilePath, gszAppFileTitle);
    if (!f)
        return (0L);

    //
    //  destroy the font we are using... before deleting the font, select
    //  the system font back into the script window so the font won't
    //  be 'in use' anymore.
    //
    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);

    hfont = GetWindowFont(hwndScript);
    SetWindowFont(hwndScript, NULL, FALSE);
    DeleteFont(hfont);

    //
    //  make the window close and terminate the application
    //
    DestroyWindow(hwnd);

    return (0L);
} // AppClose()

//--------------------------------------------------------------------------;
//
//  BOOL AppInit
//
//  Description:
//      This function is called to initialize a new instance of the
//      application. We want to parse our command line, create our window,
//      allocate resources, etc.
//
//      The arguments passed to this function are exactly the same as
//      those passed to WinMain.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPTSTR pszCmdLine: Points to null-terminated unparsed command line.
//      If the application is compiled for Unicode, then this argument is
//      ignored.
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (HWND):
//      Returns the newly created handle to the applications main window.
//      This handle is NULL if something went wrong and tells the application
//      to exit immediately.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

HWND FNGLOBAL AppInit
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPTSTR          pszCmdLine,
    int             nCmdShow
)
{
    HWND        hwnd;
    WNDCLASS    wc;

    DPF(0, "AppInit(hinst=%Xh, hinstPrev=%Xh, pszCmdLine='%s', nCmdShow=%d)",
            hinst, hinstPrev, pszCmdLine, nCmdShow);

    LoadString(hinst, IDS_APP_NAME, gszAppName, SIZEOF(gszAppName));
    LoadString(hinst, IDS_FILE_UNTITLED, gszFileUntitled, SIZEOF(gszFileUntitled));

    //
    //  determine whether a new window class needs to be registered for
    //  this application. for Win 16, this only needs to be done for the
    //  first instance of the application created. for Win 32, this must
    //  be done for EVERY instance of the application.
    //
    if (NULL == hinstPrev)
    {
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = (WNDPROC)AppWndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = hinst;
        wc.hIcon         = LoadIcon(hinst, ICON_APP);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = MENU_APP;
        wc.lpszClassName = gszAppName;

        if (!RegisterClass(&wc))
            return (NULL);
    }

    //
    //  if Windows for Pen Computing is installed, then allow the RC
    //  Manager to replace all edit controls created from this point on
    //  with hedit controls
    //
    AppRegisterPenApp(TRUE);

    //
    //  create the application's main window
    //
    //  style bits available:
    //      WS_EX_ACCEPTFILES   :  will receive WM_DROPFILES messages
    //      WS_EX_DLGMODALFRAME :  creates window with double border
    //      WS_EX_NOPARENTNOTIFY:  won't receive WM_PARENTNOTIFY messages
    //      WS_EX_TOPMOST       :  puts window in topmost space
    //      WS_EX_TRANSPARENT   :  a very bizarre style indeed (Win 16 only)
    //
    hwnd = CreateWindowEx(WS_EX_ACCEPTFILES | WS_EX_NOPARENTNOTIFY,
                          gszAppName,
                          gszAppName,
                          WS_OVERLAPPEDWINDOW,
                          APP_WINDOW_XOFFSET,
                          APP_WINDOW_YOFFSET,
                          APP_WINDOW_WIDTH,
                          APP_WINDOW_HEIGHT,
                          NULL,
                          NULL,
                          hinst,
                          NULL);

    if (NULL == hwnd)
        return (NULL);

#ifdef UNICODE
    //
    //  GetCommandLine() returns a pointer to our command line. but this
    //  command line includes the complete command line used to launch
    //  the application--which is different than the pszCmdLine argument
    //  passed through WinMain()...
    //
    //  so, skip over the command name to get to the argument string
    //
    pszCmdLine = GetCommandLine();
    if (NULL != pszCmdLine)
    {
        while (('\0' != *pszCmdLine) && (' ' != *pszCmdLine++))
            ;
    }
#endif

    //
    //
    //
    //
    MciAppInit(hwnd, gszAppFilePath, gszAppFileTitle, pszCmdLine, nCmdShow);

    //
    //  finally, get the window displayed and return success
    //
    //  the ShowWindow call is made during MciAppInit
    //
//  ShowWindow(hwnd, nCmdShow);
//  UpdateWindow(hwnd);

    return (hwnd);
} // AppInit()

//--------------------------------------------------------------------------;
//
//  int AppExit
//
//  Description:
//      This function is called just before the application exits from
//      WinMain. Its purpose is to clean up any resources that were allocated
//      for running the application: brushes, heaps, etc..
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application that is exiting.
//
//      int nResult: The result of the WM_QUIT message (in wParam of the
//      MSG structure. This argument will usually be 0 (even if the message
//      loop was never entered).
//
//  Return (int):
//      The return value is usually nResult--be we give this function the
//      opportunity to modify its value.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

int FNGLOBAL AppExit
(
    HINSTANCE       hinst,
    int             nResult
)
{
    DPF(0, "AppExit(hinst=%Xh, nResult=%d)", hinst, nResult);

    MciAppExit();

    //
    //  if Windows for Pen Computing is installed, then de-register the
    //  application so the RC Manager knows we will no longer need its
    //  services...
    //
    AppRegisterPenApp(FALSE);

    return (nResult);
} // AppExit()

//==========================================================================;
//
//  Misc rarely used application dialogs and stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AboutDlgProc
//
//  Description:
//      This dialog procedure is used for the ubiquitous about box.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AboutDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    HWND    hwndT;
    PTSTR   pach;
    UINT    u;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            //
            //  display some OS version information
            //
            //
            pach = (PTSTR)LocalAlloc(LPTR, APP_MAX_STRING_RC_BYTES);
            if (NULL == pach)
                return (TRUE);

            AppGetWindowsVersion(pach, NULL);
            hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_OS);
            SetWindowText(hwndT, pach);

            AppGetWindowsVersion(NULL, pach);
            hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_PLATFORM);
            SetWindowText(hwndT, pach);

            LocalFree((HLOCAL)pach);
            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == u) || (IDCANCEL == u))
            {
                EndDialog(hwnd, (IDOK == u));
            }
            break;
    }

    return (FALSE);
} // AboutDlgProc()


APPPORT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  appport.h -- Win 16/32 Portability Stuff
//
//  Description:
//      This file contains common macros to help with writing code that
//      cross compiles between Win 32 and Win 16. This file should be
//      included _after_ windows.h and windowsx.h.
//
//  History:
//      11/ 8/92    created.
//
//==========================================================================;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 32
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef WIN32
    #ifndef FNLOCAL
        #define FNLOCAL     _stdcall
        #define FNCLOCAL    _stdcall
        #define FNGLOBAL    _stdcall
        #define FNCGLOBAL   _stdcall
        #define FNCALLBACK  CALLBACK
        #define FNEXPORT    CALLBACK
    #endif

    #ifndef PCTSTR
        typedef const PTSTR     PCTSTR;
    #endif

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)    \
        ((DWORD)SendMessage((hwndCtl), EM_GETSEL, (WPARAM)pnS, (LPARAM)pnE))

    //
    //  for compiling Unicode
    //
    #ifdef UNICODE
        #define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)   sizeof(x)
    #endif

    //
    //  win32 apps [usually] don't have to worry about 'huge' data
    //
    #if !defined hmemcpy
#define hmemcpymemcpy
    #endif
#endif // #ifdef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 16
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef WIN32
    #ifndef FNLOCAL
        #define FNLOCAL     NEAR PASCAL
        #define FNCLOCAL    NEAR _cdecl
        #define FNGLOBAL    FAR PASCAL
        #define FNCGLOBAL   FAR _cdecl
        #define FNCALLBACK  CALLBACK
        #define FNEXPORT    CALLBACK _export
    #endif

    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef const PTSTR     PCTSTR;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif

    #define TEXT(a)         a
    #define SIZEOF(x)       sizeof(x)

    //
    //
    //
    #define CharNext        AnsiNext
    #define CharPrev        AnsiPrev

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)                        \
    {                                                               \
        DWORD   dw;                                                 \
        dw = (DWORD)SendMessage((hwndCtl), EM_GETSEL, 0, 0L);       \
        *pnE = (int)HIWORD(dw);                                     \
        *pnS = (int)LOWORD(dw);                                     \
    }

    //
    //  common message cracker macros available in windowx.h on NT--these
    //  should be added to the Win 16 windowsx.h and probably will be
    //  in the future.
    //
    //  there is a windowsx.h16 that ships with the NT PDK that defines
    //  these macros. so if that version is being used, don't redefine
    //  message crackers.
    //

#ifndef WM_CTLCOLORMSGBOX
    #define WM_CTLCOLORMSGBOX           0x0132
    #define WM_CTLCOLOREDIT             0x0133
    #define WM_CTLCOLORLISTBOX          0x0134
    #define WM_CTLCOLORBTN              0x0135
    #define WM_CTLCOLORDLG              0x0136
    #define WM_CTLCOLORSCROLLBAR        0x0137
    #define WM_CTLCOLORSTATIC           0x0138
#endif

#ifndef GET_WM_ACTIVATE_STATE
    #define GET_WM_ACTIVATE_STATE(wp, lp)           (wp)
    #define GET_WM_ACTIVATE_FMINIMIZED(wp, lp)      (BOOL)HIWORD(lp)
    #define GET_WM_ACTIVATE_HWND(wp, lp)            (HWND)LOWORD(lp)
    #define GET_WM_ACTIVATE_MPS(s, fmin, hwnd)      (WPARAM)(s), MAKELONG(hwnd, fmin)

    #define GET_WM_CHARTOITEM_CHAR(wp, lp)          (CHAR)(wp)
    #define GET_WM_CHARTOITEM_POS(wp, lp)           HIWORD(lp)
    #define GET_WM_CHARTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_CHARTOITEM_MPS(ch, pos, hwnd)    (WPARAM)(ch), MAKELONG(hwnd, pos)

    #define GET_WM_COMMAND_ID(wp, lp)               (wp)
    #define GET_WM_COMMAND_HWND(wp, lp)             (HWND)LOWORD(lp)
    #define GET_WM_COMMAND_CMD(wp, lp)              HIWORD(lp)
    #define GET_WM_COMMAND_MPS(id, hwnd, cmd)       (WPARAM)(id), MAKELONG(hwnd, cmd)

    #define GET_WM_CTLCOLOR_HDC(wp, lp, msg)        (HDC)(wp)
    #define GET_WM_CTLCOLOR_HWND(wp, lp, msg)       (HWND)LOWORD(lp)
    #define GET_WM_CTLCOLOR_TYPE(wp, lp, msg)       HIWORD(lp)
    #define GET_WM_CTLCOLOR_MPS(hdc, hwnd, type)    (WPARAM)(hdc), MAKELONG(hwnd, type)

    #define GET_WM_MENUSELECT_CMD(wp, lp)           (wp)
    #define GET_WM_MENUSELECT_FLAGS(wp, lp)         LOWORD(lp)
    #define GET_WM_MENUSELECT_HMENU(wp, lp)         (HMENU)HIWORD(lp)
    #define GET_WM_MENUSELECT_MPS(cmd, f, hmenu)    (WPARAM)(cmd), MAKELONG(f, hmenu)

    // Note: the following are for interpreting MDIclient to MDI child messages.
    #define GET_WM_MDIACTIVATE_FACTIVATE(hwnd, wp, lp)  (BOOL)(wp)
    #define GET_WM_MDIACTIVATE_HWNDDEACT(wp, lp)        (HWND)HIWORD(lp)
    #define GET_WM_MDIACTIVATE_HWNDACTIVATE(wp, lp)     (HWND)LOWORD(lp)

    // Note: the following is for sending to the MDI client window.
    #define GET_WM_MDIACTIVATE_MPS(f, hwndD, hwndA) (WPARAM)(hwndA), 0

    #define GET_WM_MDISETMENU_MPS(hmenuF, hmenuW)   0, MAKELONG(hmenuF, hmenuW)

    #define GET_WM_MENUCHAR_CHAR(wp, lp)            (CHAR)(wp)
    #define GET_WM_MENUCHAR_HMENU(wp, lp)           (HMENU)LOWORD(lp)
    #define GET_WM_MENUCHAR_FMENU(wp, lp)           (BOOL)HIWORD(lp)
    #define GET_WM_MENUCHAR_MPS(ch, hmenu, f)       (WPARAM)(ch), MAKELONG(hmenu, f)

    #define GET_WM_PARENTNOTIFY_MSG(wp, lp)         (wp)
    #define GET_WM_PARENTNOTIFY_ID(wp, lp)          HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_HWNDCHILD(wp, lp)   (HWND)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_X(wp, lp)           (INT)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_Y(wp, lp)           (INT)HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_MPS(msg, id, hwnd)  (WPARAM)(msg), MAKELONG(hwnd, id)
    #define GET_WM_PARENTNOTIFY2_MPS(msg, x, y)     (WPARAM)(msg), MAKELONG(x, y)

    #define GET_WM_VKEYTOITEM_CODE(wp, lp)          (wp)
    #define GET_WM_VKEYTOITEM_ITEM(wp, lp)          (INT)HIWORD(lp)
    #define GET_WM_VKEYTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_VKEYTOITEM_MPS(code, item, hwnd) (WPARAM)(code), MAKELONG(hwnd, item)

    #define GET_EM_SETSEL_START(wp, lp)             LOWORD(lp)
    #define GET_EM_SETSEL_END(wp, lp)               HIWORD(lp)
    #define GET_EM_SETSEL_MPS(iStart, iEnd)         0, MAKELONG(iStart, iEnd)

    #define GET_EM_LINESCROLL_MPS(vert, horz)       0, MAKELONG(vert, horz)

    #define GET_WM_CHANGECBCHAIN_HWNDNEXT(wp, lp)   (HWND)LOWORD(lp)

    #define GET_WM_HSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_HSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_HSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_HSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)

    #define GET_WM_VSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_VSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_VSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_VSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)
#endif

#endif // #ifndef WIN32


DEBUG.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.c
//
//  Description:
//      This file contains code yanked from several places to provide debug
//      support that works in win 16 and win 32.
//
//  History:
//      11/23/92    created.
//
//==========================================================================;

#ifdef DEBUG

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include "debug.h"

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifdef WIN32
#ifdef UNICODE
    #include <wchar.h>
#endif
#else
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define wvsprintfA          wvsprintf
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
#endif

//
//
//
BOOL    __gfDbgEnabled  = TRUE;     // master enable
UINT    __guDbgLevel    = 0;        // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF(LPSTR szFmt, LPSTR va)
//
//  Description:
//
//
//  Arguments:
//
//  Return (void):
//
//
//  History:
//      11/28/92    created.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF(LPSTR szFmt, LPSTR va)
{
    char    ach[DEBUG_MAX_LINE_LEN];
    BOOL    fDebugBreak = FALSE;
    BOOL    fPrefix     = TRUE;
    BOOL    fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch(*szFmt)
        {
            case '!':
                fDebugBreak = TRUE;
                szFmt++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFmt++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFmt++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");

    wvsprintfA(ach + lstrlenA(ach), szFmt, va);

    if (fCRLF)
        lstrcatA(ach, "\r\n");

    OutputDebugStringA(ach);

    if (fDebugBreak)
        DebugBreak();
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf(UINT uDbgLevel, LPSTR szFmt, ...)
//
//  Description:
//      dprintf() is called by the DPF macro if DEBUG is defined at compile
//      time.
//
//      The messages will be send to COM1: like any debug message. To
//      enable debug output, add the following to WIN.INI :
//
//      [debug]
//      ICSAMPLE=1
//
//  Arguments:
//
//  Return (void):
//
//
//  History:
//      11/23/92    created.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFmt);
    DbgVPrintF(szFmt, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable(BOOL fEnable)
//
//  Description:
//
//
//  Arguments:
//
//  Return (BOOL):
//
//
//  History:
//      11/28/92    created.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable(BOOL fEnable)
{
    BOOL    fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel(UINT uLevel)
//
//  Description:
//
//
//  Arguments:
//
//  Return (UINT):
//
//
//  History:
//      11/24/92    created.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel(UINT uLevel)
{
    UINT    uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize(void)
//
//  Description:
//
//
//  Arguments:
//
//  Return (UINT):
//
//
//  History:
//      11/24/92    created.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize(BOOL fEnable)
{
    DbgSetLevel(GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, 0));
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

#endif // #ifdef DEBUG


DEBUG.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.h
//
//  Description:
//
//  History:
//      11/23/92    created.
//
//==========================================================================;

#ifndef _INC_DEBUG
#define _INC_DEBUG
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
//
#define DEBUG_SECTION       "Debug"         // section name for
#define DEBUG_MODULE_NAME   "MCIAPP"        // key name and prefix for output
#define DEBUG_MAX_LINE_LEN  255             // max line length (bytes)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define DPF      dprintf
#else
    #define DbgEnable(x)        FALSE
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #pragma warning(disable:4002)
    #define DPF()
#endif

#ifdef __cplusplus
}
#endif
#endif  // _INC_DEBUG


MCIAPP.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mciapp.c
//
//  Description:
//      This is a sample application that demonstrates how to use the
//      Media Control Interface (MCI) in Windows. This application is
//      also useful as an MCI device tester.
//
//  History:
//       2/ 6/93    created.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "appport.h"
#include "app.h"
#include "mciapp.h"

#include "debug.h"

//
//
//
BOOL            gfExecuting;
BOOL            gfAbortExec;

HWND            ghwndDevices;

TCHAR           gszNone[]           = TEXT("(none)");

//--------------------------------------------------------------------------;
//
//  BOOL MciAppFileSaveModified
//
//  Description:
//      This function tests if the current script has been modified, and
//      if it has it gives the option of saving the file.
//
//      NOTE! This function does *NOT* clear the modified bit for the
//      script window. The calling function must do this if necessary.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PTSTR pszFilePath: Pointer to null terminated file path of current
//      script. This buffer will receive the new file path if one is chosen.
//
//      PTSTR pszFileTitle: Pointer to null terminated file title of
//      current script. This buffer will receive the new file title if one
//      is chosen.
//
//  Return (BOOL):
//      Returns TRUE if the calling function should continue--the file was
//      either saved or the user does not wish to save it. Returns FALSE
//      if the calling function should cancel its operation--the user
//      wants to keep the data, but it has not been saved.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppFileSaveModified
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
    BOOL    f;
    int     n;
    HWND    hwndScript;

    //
    //  check if the contents of the script window have been modified--if
    //  they have then ask the user if they want to save the current
    //  contents...
    //
    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    f = Edit_GetModify(hwndScript);
    if (f)
    {
        //
        //  display an appropriate message box asking for the user's opinion
        //
        n = AppMsgBoxId(hwnd, MB_YESNOCANCEL | MB_ICONQUESTION| MB_SETFOREGROUND,
                        IDS_MCI_SCRIPT_CHANGED, (LPSTR)pszFilePath);
        switch (n)
        {
            case IDYES:
                f = AppFileSave(hwnd, pszFilePath, pszFileTitle, FALSE);
                if (f)
                    break;

                // -- fall through --

            case IDCANCEL:
                //
                //  don't continue!
                //
                return (FALSE);

            case IDNO:
                break;
        }
    }

    //
    //  ok to continue...
    //
    return (TRUE);
} // MciAppFileSaveModified()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppFileNew
//
//  Description:
//      This function simply clears the script window. If a modified script
//      will be destroyed, then the user is asked if the script should be
//      saved first.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PTSTR pszFilePath: Pointer to null terminated file path of current
//      script. This buffer will receive the newly initialized file path.
//
//      PTSTR pszFileTitle: Pointer to null terminated file title of
//      current script. This buffer will receive the newly initialized file
//      title.
//
//  Return (BOOL):
//      The return value is TRUE if the script window was cleared. It is
//      FALSE if the user canceled the operation.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppFileNew
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
    BOOL    f;
    HWND    hwndScript;

    //
    //  test for a modified script first...
    //
    f = MciAppFileSaveModified(hwnd, pszFilePath, pszFileTitle);
    if (!f)
        return (FALSE);

    //
    //  blow away all the text and set the modified bit to 'NOT'
    //
    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    SetWindowText(hwndScript, gszNull);
    Edit_SetModify(hwndScript, FALSE);

    //
    //  success
    //
    return (TRUE);
} // MciAppFileNew()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppFileOpen
//
//  Description:
//      This function opens the specified text file and copies the contents
//      of the file into the script edit control.
//
//      NOTE! This function does NOT check for a modified script! It is
//      assumed that the calling function took care of everything before
//      calling this function.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PTSTR pszFilePath: Pointer to null terminated file path to open as
//      an MCI script. This buffer will be returned with a fully qualified
//      path of the file that was opened (if successful).
//
//      PTSTR pszFileTitle: Pointer to buffer to receive the file title of
//      the newly opened file. This buffer does not need to be initialized.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if an error occurred. If an error does occur, then the contents
//      of the script window, pszFilePath and pszFileTitle will remain
//      unchanged.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppFileOpen
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
#ifdef UNICODE
    HANDLE      hf;
#else
    #define SEEK_SET        0       // flags for _lseek
    #define SEEK_CUR        1
    #define SEEK_END        2

    HFILE       hf;
    OFSTRUCT    of;
#endif
    HWND        hwndScript;
    UINT        uFileLen;
    LPTSTR      psz;
    BOOL        fReturn;

    //
    //  open the file for reading..
    //
#ifdef UNICODE
    hf = CreateFile(pszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, 0, 0);
    if (INVALID_HANDLE_VALUE == hf)
        return (FALSE);
#else
    of.cBytes = sizeof(of);
    hf = OpenFile(pszFilePath, &of, OF_READ);
    if (HFILE_ERROR == hf)
        return (FALSE);
#endif

    //
    //  assume the worst
    //
    fReturn = FALSE;

    //
    //  determine the length in _bytes_ of the file--note that win 16 is
    //  limited to 32k so big files get truncated... improperly.
    //
    //  !!! need to handle files that are too big better !!!
    //
#ifdef WIN32
    uFileLen = (UINT)GetFileSize((HANDLE)hf, NULL);
#else
    uFileLen = (UINT)_llseek(hf, 0L, SEEK_END);
    _llseek(hf, 0L, SEEK_SET);
#endif

    //
    //  now read the contents of the file into a buffer--display an hour
    //  glass in case the file is large and/or we are reading from a slow
    //  device... note that the memory allocation may take some time if
    //  the pager is in meltdown mode.
    //
    AppHourGlass(TRUE);
    for (;;)
    {
        //
        //  allocate enough memory to hold the complete image of the file
        //  plus one character for good measure (the forced null termination
        //  could fault if don't add one char to size).
        //
        psz = GlobalAllocPtr(GMEM_MOVEABLE, uFileLen + sizeof(TCHAR));
        if (NULL == psz)
            break;

        //
        //  read the file and copy the contents into the script window
        //
#ifdef UNICODE
        fReturn = ReadFile(hf, psz, uFileLen, (LPDWORD)&uFileLen, NULL);
        if (!fReturn)
            break;
#else
        uFileLen = _lread(hf, psz, uFileLen);
        if ((UINT)-1 == uFileLen)
            break;
#endif

        //
        //  make sure the text gets null terminated, then shove it into
        //  the script window.
        //
        psz[uFileLen / sizeof(TCHAR)] = '\0';

        hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
#ifdef UNICODE
        //
        //  determine whether the text is Unicode or ANSI: Unicode files
        //  all start with a Byte Order Mark (BOM) that will be 0xFEFF in
        //  the proper Endian for the file.
        //
        //  !!! this application only deals with ANSI and Little Endian
        //      Unicode files--should handle more gracefully if not...
        //
        if ((uFileLen >= sizeof(TCHAR)) && (psz[0] == 0xFEFF))
        {
            //
            //  set the window text as Unicode--note that we do not send
            //  the BOM, the edit control doesn't want it...
            //
            gfuAppOptions |= APP_OPTF_UNICODE;
            SetWindowTextW(hwndScript, (LPCWSTR)&psz[1]);
        }
        else
        {
            gfuAppOptions &= ~APP_OPTF_UNICODE;
            SetWindowTextA(hwndScript, (LPCSTR)psz);
        }
#else
        SetWindowText(hwndScript, (LPCTSTR)psz);
#endif

        //
        //  now return the fully qualified path and title for the file
        //!!!
#ifndef UNICODE
        lstrcpy(pszFilePath, of.szPathName);
#endif
        if (NULL != pszFileTitle)
        {
            AppGetFileTitle(pszFilePath, pszFileTitle);
        }

        fReturn = TRUE;
        break;
    }
    AppHourGlass(FALSE);

    //
    //  free memory (if allocated) and close the file. return the result
    //  of our attempt...
    //
    if (psz)
        GlobalFreePtr(psz);

#ifdef UNICODE
    CloseHandle(hf);
#else
    _lclose(hf);
#endif

    //
    //  !!! before returning, we really should try to display a error
    //      message... memory error, etc..
    //
    return (fReturn);
} // MciAppFileOpen()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppFileSave
//
//  Description:
//      This function saves the current script to the specified file.
//
//      NOTE! This function does NOT bring up a save file chooser dialog
//      if the file path is invalid. The calling function is responsible
//      for making sure the file path is valid before calling this function.
//
//      This function also does NOT modify the 'modified' bit of the script
//      window. This is up to the calling function.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PCTSTR pszFilePath: Pointer to null terminated file path to save
//      the script to.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if an error occurred. If an error does occur, then the contents
//      of the script window was not saved.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppFileSave
(
    HWND            hwnd,
    PCTSTR          pszFilePath
)
{
#ifdef UNICODE
    HANDLE      hf;
#else
    HFILE       hf;
    OFSTRUCT    of;
#endif
    HWND        hwndScript;
    UINT        uFileLen;
    DWORD       cbBytes;
    LPTSTR      psz;
    BOOL        fReturn;

    //
    //  create the new file--if it already exists, this will open and init
    //  it to zero length.
    //
#ifdef UNICODE
    hf = CreateFile(pszFilePath, GENERIC_WRITE, FILE_SHARE_READ,
                    NULL, CREATE_ALWAYS, 0, 0);
    if (INVALID_HANDLE_VALUE == hf)
        return (FALSE);
#else
    of.cBytes = sizeof(of);
    hf = OpenFile(pszFilePath, &of, OF_CREATE);
    if (HFILE_ERROR == hf)
        return (FALSE);
#endif

    //
    //  assume the worst
    //
    fReturn = FALSE;

    //
    //  get the length in bytes of the script--we add 1 to the result
    //  because GetWindowTextLength returns the length in bytes NOT including
    //  the null terminator.
    //
    //  !!! UNICODE !!!
    //
    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    uFileLen = (UINT)GetWindowTextLength(hwndScript) + 1;

    //!!! nFileLen = nFileLen*sizeof(TCHAR);  We write ASCII

    //
    //  allocate a buffer to hold the script text, get the text, and write
    //  it out. display an hour glass in case the file is large and/or we
    //  are writing to a slow device...
    //
    AppHourGlass(TRUE);
    for (;;)
    {
        //
        //  allocate enough memory to hold the complete image of the script
        //
        psz = GlobalAllocPtr(GMEM_MOVEABLE, uFileLen + sizeof(TCHAR));
        if (NULL == psz)
            break;

        //
        //  read the contents of the script window and write it to the
        //  new file
        //
#ifdef UNICODE
        // !!!! Save ASCII file
        GetWindowTextA(hwndScript, (LPSTR)psz, uFileLen);
        WriteFile(hf, psz, uFileLen, &cbBytes, NULL);
#else
        GetWindowText(hwndScript, (LPTSTR)psz, uFileLen);
        cbBytes = (DWORD)_lwrite(hf, (LPSTR)psz, uFileLen);
#endif

        //
        //  succeed
        //
        fReturn = TRUE;
        break;
    }
    AppHourGlass(FALSE);

    //
    //  free memory (if allocated) and close the file. return the result
    //  of our attempt...
    //
    if (psz)
        GlobalFreePtr(psz);

#ifdef UNICODE
    CloseHandle(hf);
#else
    _lclose(hf);
#endif

    //
    //  !!! before returning, we really should try to display an error
    //      message... memory error, etc..
    //
    return (fReturn);
} // MciAppFileSave()

//==========================================================================;
//
//  MCI Device List stuff
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  UINT MciAppGetNumDevices
//
//  Description:
//      This function sends a command to MCI querying it as to how many
//      devices are currently open in the system. This number is the
//      return value.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (UINT):
//      Returns the number of currently open MCI devices in the system.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

UINT FNGLOBAL MciAppGetNumDevices
(
    HWND            hwnd
)
{
    MCI_SYSINFO_PARMS   msip;
    MCIERROR            mciError;
    DWORD               dwDevices;

    //
    //  set things up so that MCI puts the number of open devices directly
    //  into dwDevices
    //
    msip.lpstrReturn = (LPVOID)&dwDevices;
    msip.dwRetSize   = sizeof(dwDevices);

    //
    //  ask MCI how many open devices are in the system--if this errors
    //  then return 0...
    //
    mciError = mciSendCommand(MCI_ALL_DEVICE_ID,
                              MCI_SYSINFO,
                              MCI_SYSINFO_OPEN | MCI_SYSINFO_QUANTITY,
                              (DWORD)(LPVOID)&msip);

    if (MMSYSERR_NOERROR == mciError)
        return ((UINT)dwDevices);

    return (0);
} // MciAppGetNumDevices()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppDeviceListUpdate
//
//  Description:
//      This function updates the MCI Device List window if it is displayed.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      BOOL fForceUpdate: Forces the list to be updated even if number
//      of devices has not changed.
//
//  Return (BOOL):
//      Returns TRUE if the device list needed to be updated. FALSE if
//      the list was fine...
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MciAppDeviceListUpdate
(
    HWND            hwnd,
    HWND            hwndDevices,
    BOOL            fForceUpdate
)
{
    static TCHAR    szFormatDevice[]    = TEXT("%2d. '%s'");
    static UINT     uLastNumDevices;

    TCHAR               ach[APP_MAX_STRING_RC_CHARS];
    TCHAR               szDevName[APP_MAX_STRING_RC_CHARS];
    MCI_SYSINFO_PARMS   msip;
    MCIERROR            mciError;
    HWND                hwndList;
    UINT                uNumDevs;
    UINT                u;

    //
    //  if the devices window is not displayed, then fail..
    //
    if (NULL == hwndDevices)
        return (FALSE);

    uNumDevs = MciAppGetNumDevices(hwnd);

    //
    //  if not being forced to update list, then make a quick check to
    //  see if we should update the list...
    //
    if (!fForceUpdate && (uNumDevs == uLastNumDevices))
        return (FALSE);

    //
    //  really update the device list...
    //
    uLastNumDevices = uNumDevs;

    //
    //  initialize the devices listbox...
    //
    hwndList = GetDlgItem(hwndDevices, IDD_MCIDEVS_LIST_OPEN);
    ListBox_ResetContent(hwndList);

    SetWindowRedraw(hwndList, FALSE);

    //
    //  get the name of each open device in the system and add it to the
    //  device list box...
    //
    for (u = 1; u <= uNumDevs; ++u)
    {
        msip.dwNumber    = u;
        msip.lpstrReturn = (LPVOID)&szDevName;
        msip.dwRetSize   = SIZEOF(szDevName);

        //
        //  get the name--if an error is encountered, then skip to the
        //  next device...
        //
        mciError = mciSendCommand(MCI_ALL_DEVICE_ID,
                                  MCI_SYSINFO,
                                  MCI_SYSINFO_OPEN | MCI_SYSINFO_NAME,
                                  (DWORD)(LPVOID)&msip);

        if (MMSYSERR_NOERROR != mciError)
            continue;

        //
        //  add the device name to the listbox..
        //
        wsprintf(ach, szFormatDevice, u, (LPSTR)szDevName);
        ListBox_AddString(hwndList, ach);
    }

    if (0 != uNumDevs)
        ListBox_SetCurSel(hwndList, 0);

    SetWindowRedraw(hwndList, TRUE);
    InvalidateRect(hwndList, NULL, TRUE);

    //
    //  set the info button to the correct state: enabled if there are
    //  devices open--disabled if no devices are open.
    //
    EnableWindow(GetDlgItem(hwndDevices, IDD_MCIDEVS_BTN_INFO), 0 != uNumDevs);

    return (TRUE);
} // MciAppDeviceListUpdate()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppCloseAllDevices
//
//  Description:
//      This function sends the MCI command "close all" and then updates
//      the displayed info for the application.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      Always returns TRUE.
//
//  History:
//       2/ 8/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppCloseAllDevices
(
    HWND            hwnd
)
{
    static TCHAR    szCloseAll[]    = TEXT("close all");

    //
    //  close all open devices and update the device list if it is being
    //  displayed...
    //
    mciSendString(szCloseAll, NULL, 0, NULL);
    MciAppDeviceListUpdate(hwnd, ghwndDevices, FALSE);

    return (TRUE);
} // MciAppCloseAllDevices()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppDeviceDlgProc
//
//  Description:
//      Callback function for the dialog box which displays a list of the
//      currently opened MCI devices.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT MciAppDeviceDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    RECT    rcApp;
    RECT    rc;
    HFONT   hfont;
    HWND    hwndParent;
    UINT    u;
    int     n;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            //
            //  move the window so it sits in the upper right corner of the
            //  main window by default...
            //
            GetWindowRect(GetParent(hwnd), &rcApp);
            GetWindowRect(hwnd, &rc);

            n = (int)(rc.right - rc.left);
            rc.left = rcApp.right - n - 30;
            MoveWindow(hwnd, (int)rc.left, (int)rc.top,
                       n, (int)(rc.bottom - rc.top), FALSE);

            //
            //
            //
            hfont = GetStockFont(SYSTEM_FIXED_FONT);
            SetWindowFont(GetDlgItem(hwnd, IDD_MCIDEVS_LIST_OPEN), hfont, FALSE);

            //
            //  update the information displayed in the listbox
            //
            MciAppDeviceListUpdate(GetParent(hwnd), hwnd, TRUE);

            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            switch (u)
            {
                case IDCANCEL:
                case IDOK:
                    //
                    //  return button id of the one that was pressed...
                    //
                    hwndParent = GetParent(hwnd);
                    EndDialog(hwnd, u);

                    ghwndDevices = NULL;
                    gfuAppOptions &= ~APP_OPTF_DEVICELIST;
                    SetFocus(GetDlgItem(hwndParent, IDD_APP_EDIT_SCRIPT));
                    break;

                case IDD_MCIDEVS_BTN_INFO:
                    n = ListBox_GetCurSel(GetDlgItem(hwnd, IDD_MCIDEVS_LIST_OPEN));
                    if (LB_ERR == n)
                        break;

                    break;
            }
            break;
    }

    return (FALSE);
} // MciAppDeviceDlgProc()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppDeviceList
//
//  Description:
//      This function either displays or destroys the MCI device list
//      window.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      BOOL fActivate: TRUE if the device list window should keep
//      the activation. FALSE if the current active window should remain
//      active.
//
//  Return (BOOL):
//      The return value is TRUE if the device list is displayed. It is
//      FALSE if the device list has been canceled/closed.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppDeviceList
(
    HWND            hwnd,
    BOOL            fActivate
)
{
    HWND        hwndFocus;

    //
    //  should we display or destroy it?
    //
    if (0 != (gfuAppOptions & APP_OPTF_DEVICELIST))
    {
        if (NULL != ghwndDevices)
            return (TRUE);

        hwndFocus = GetFocus();

        ghwndDevices = CreateDialog(ghinst, DLG_MCIDEVS, hwnd, (DLGPROC)MciAppDeviceDlgProc);
        if (NULL  == ghwndDevices)
        {
            gfuAppOptions &= ~APP_OPTF_DEVICELIST;
            return (FALSE);
        }

        //
        //  note that the window will 'flash' because we used CreateDialog
        //  when creating the device list window. we keep the flash to
        //  a minimum by creating the device window hidden and then showing
        //  it... if you want to get rid of the flash completely, then
        //  use CreateWindow and write a lot more code.
        //
        if (fActivate)
        {
            ShowWindow(ghwndDevices, SW_SHOW);
        }
        else
        {
            SetActiveWindow(hwnd);
            SetFocus(hwndFocus);
            ShowWindow(ghwndDevices, SW_SHOWNA);
        }

        return (TRUE);
    }
    else
    {
        if (NULL != ghwndDevices)
        {
            EndDialog(ghwndDevices, IDOK);
            ghwndDevices = NULL;
        }

        return (FALSE);
    }
} // MciAppDeviceList()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MciAppUpdateOptions
//
//  Description:
//      This function updates the options status window to reflect the
//      current status of the options.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      Always returns TRUE.
//
//  History:
//       2/ 8/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppUpdateOptions
(
    HWND            hwnd
)
{
#ifdef UNICODE
    static TCHAR    szFormatOptions[]   = TEXT("%c%c%c%c");
#else
    static TCHAR    szFormatOptions[]   = TEXT("%c%c%c");
#endif

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];

    //
    //  format an appropriate string for the options
    //
    wsprintf(ach, szFormatOptions,
#ifdef UNICODE
             (0 != (gfuAppOptions & APP_OPTF_UNICODE))   ? 'U' : '-',
#endif
             (0 != (gfuAppOptions & APP_OPTF_EDITONLY))  ? 'E' : '-',
             (0 != (gfuAppOptions & APP_OPTF_YIELDEXEC)) ? 'Y' : '-',
             (0 != (gfuAppOptions & APP_OPTF_DEBUGLOG))  ? 'L' : '-');

    SetWindowText(GetDlgItem(hwnd, IDD_APP_TEXT_OPTIONS), ach);

    return (TRUE);
} // MciAppUpdateOptions()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppResetStatus
//
//  Description:
//      This function resets all of the status windows (status, notify,
//      and output). This is used when a new script is created/opened.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      Always returns TRUE.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppResetStatus
(
    HWND            hwnd
)
{
    //
    //  nuke all status text...
    //
    SetWindowText(GetDlgItem(hwnd, IDD_APP_TEXT_OUTPUT), gszNone);
    SetWindowText(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS), gszNull);
    SetWindowText(GetDlgItem(hwnd, IDD_APP_TEXT_NOTIFY), gszNull);

    MciAppUpdateOptions(hwnd);

    return (TRUE);
} // MciAppResetStatus()

//--------------------------------------------------------------------------;
//
//  void MciAppDebugLog
//
//  Description:
//      This function logs information to the debugger if the Debug Log
//      option is set. You can then run DBWin (or something similar)
//      to redirect the output whereever you want. Very useful for debugging
//      MCI drivers.
//
//  Arguments:
//      PCTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (void):
//      None.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

void FNCGLOBAL MciAppDebugLog
(
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];

    //

//  !!! UNICODE !!!
    //
    //
    if (0 != (gfuAppOptions & APP_OPTF_DEBUGLOG))
    {
        //
        //  format and display the string in a message box...
        //
        va_start(va, pszFormat);
        wvsprintf(ach, pszFormat, va);
        va_end(va);

        OutputDebugString(gszAppName);
        OutputDebugString(TEXT(": "));
        OutputDebugString(ach);
        OutputDebugString(TEXT("\r\n"));
    }
} // MciAppDebugLog()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppHandleNotify
//
//  Description:
//      This function handles displaying the notification message from
//      commands sent with the 'notify' option.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      UINT fuNotify: Notification flags (wParam) from MM_MCINOTIFY message.
//
//      UINT uId: Device id sending notification.
//
//  Return (BOOL):
//      Always returns TRUE.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppHandleNotify
(
    HWND            hwnd,
    UINT            fuNotify,
    UINT            uId
)
{
    static TCHAR    szFormatNotify[]        = TEXT("Notify(%u,%u): %s");
    static TCHAR    szDBFormatNotify[]      = TEXT("    MCI Notify: Id=%u, Flag(%u)='%s'");

    HWND        hwndNotify;
    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    UINT        uIds;

    //
    //
    //
    //
    switch (fuNotify)
    {
        case MCI_NOTIFY_SUCCESSFUL:
            uIds = IDS_MCI_NOTIFY_SUCCESSFUL;
            break;

        case MCI_NOTIFY_SUPERSEDED:
            uIds = IDS_MCI_NOTIFY_SUPERSEDED;
            break;

        case MCI_NOTIFY_ABORTED:
            uIds = IDS_MCI_NOTIFY_ABORTED;
            break;

        case MCI_NOTIFY_FAILURE:
            uIds = IDS_MCI_NOTIFY_FAILURE;
            break;

        default:
            uIds = IDS_MCI_NOTIFY_UNKNOWN;
            break;
    }

    LoadString(ghinst, uIds, ach, SIZEOF(ach));
    hwndNotify = GetDlgItem(hwnd, IDD_APP_TEXT_NOTIFY);
    AppSetWindowText(hwndNotify, szFormatNotify, uId, fuNotify, (LPSTR)ach);

    //
    //  !!! UNICODE !!!
    //
    MciAppDebugLog(szDBFormatNotify, uId, fuNotify, (LPSTR)ach);

    return (TRUE);
} // MciAppHandleNotify()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppErrorDlgProc
//
//  Description:
//      Callback function for the dialog box which occurs during the
//      execution of an error in a loop of script commands. It displays
//      Abort, Continue and Ignore buttons.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT MciAppErrorDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    UINT    u;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            switch (u)
            {
                case IDABORT:
                case IDOK:
                case IDIGNORE:
                    //
                    //  return button id of the one that was pressed...
                    //
                    EndDialog(hwnd, u);
                    break;
            }
            break;
    }

    return (FALSE);
} // MciAppErrorDlgProc()

//--------------------------------------------------------------------------;
//
//  MCIERROR MciAppSendString
//
//  Description:
//      Sends the specified string command to an MCI device via the MCI
//      string interface. Any return strings from MCI devices are displayed
//      in the 'Output' text box. Error values are displayed in the status
//      bar.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PCTSTR pszCommand: Pointer to the string command to be executed.
//
//  Return (MCIERROR):
//      The return value is the result of the mciSendString() function.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

MCIERROR FNGLOBAL MciAppSendString
(
    HWND            hwnd,
    PCTSTR          pszCommand
)
{
    static TCHAR    szFormatDBSendString[]  = TEXT("mciSendString('%s')");
    static TCHAR    szFormatOutput[]        = TEXT("'%s'");
    static TCHAR    szFormatDBOutput[]      = TEXT("    MCI Output: '%s'");
    static TCHAR    szFormatError[]         = TEXT("MCI Error: [%lu], %s");
    static TCHAR    szFormatDBError[]       = TEXT("    MCI Error : [%lu], '%s'");

    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    HWND        hwndOutput;
    HWND        hwndStatus;
    MCIERROR    mciError;

    //
    //  reset the notify window text..
    //
    SetWindowText(GetDlgItem(hwnd, IDD_APP_TEXT_NOTIFY), gszNull);

    //
    //  send the string command to MCI--capture the return value and have
    //  the notification code go to the main window (MM_MCINOTIFY)...
    //
    //  !!! UNICODE !!!
    //
    MciAppDebugLog(szFormatDBSendString, (LPSTR)pszCommand);
    mciError = mciSendString(pszCommand, ach, SIZEOF(ach), hwnd);

    //
    //  if the command errored, then beep the _speaker_ (-1). don't want
    //  to use wave devices because many mci scripts use them..
    //
    if (MMSYSERR_NOERROR != mciError)
        MessageBeep((UINT)-1);

    MciAppDebugLog(szFormatDBOutput, (LPSTR)ach);

    //
    //  put the text message returned by MCI into the 'Output' box
    //
    hwndOutput = GetDlgItem(hwnd, IDD_APP_TEXT_OUTPUT);
    if ('\0' == ach[0])
        SetWindowText(hwndOutput, gszNone);
    else
        AppSetWindowText(hwndOutput, szFormatOutput, (LPSTR)ach);

    //
    //  decode the error number returned by MCI and display the string in
    //  the status bar...
    //
    //  !!! UNICODE !!!
    //
    mciGetErrorString(mciError, ach, SIZEOF(ach));
    MciAppDebugLog(szFormatDBError, mciError, (LPSTR)ach);

    hwndStatus = GetDlgItem(hwnd, IDD_APP_TEXT_STATUS);
    AppSetWindowText(hwndStatus, szFormatError, mciError, (LPSTR)ach);

    //
    //  update our list of currently open devices and stuff...
    //
    MciAppDeviceListUpdate(hwnd, ghwndDevices, FALSE);

    return (mciError);
} // MciAppSendString()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppGetLine
//
//  Description:
//      Retrieves the contents of line (uLine) from the script window.
//      The string is placed in the pszLineBuffer. All leading and trailing
//      spaces (and comments) are removed.
//
//  Arguments:
//      HWND hwndScript: Handle to script window (multiline edit control)
//      to extract nLine from.
//
//      int nLine: Line index (zero based) to retrieve.
//
//      PTSTR pszBuffer: Pointer to string buffer to receive nLine's
//      contents.
//
//      UINT cchBuffer: Size of pszBuffer is _characters_.
//
//  Return (BOOL):
//      The return value is TRUE if nLine was extracted from the script
//      window. The return value is FALSE if nLine is out of range for
//      the current contents of the script window.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MciAppGetLine
(
    HWND            hwndScript,
    int             nLine,
    PTSTR           pszBuffer,
    UINT            cchBuffer
)
{
    #define IS_SPACE(c)     (' '  == (c) || '\t' == (c))
    #define IS_EOL(c)       ('\n' == (c) || '\r' == (c))
    #define IS_WHITE(c)     (IS_SPACE(c) || IS_EOL(c))

    int     nNumLines;

    //
    //  find out how many lines are in the script window--if it is out of
    //  range then fail....
    //
    nNumLines = Edit_GetLineCount(hwndScript);
    if ((nLine < 0) || (nLine >= nNumLines))
    {
        pszBuffer[0] = '\0';
        return (FALSE);
    }

    //
    //  read the line requested into the string buffer..
    //
    cchBuffer = Edit_GetLine(hwndScript, nLine, pszBuffer, cchBuffer);

    //
    //  strip trailing spaces
    //
    while ((cchBuffer > 0) && IS_WHITE(pszBuffer[cchBuffer - 1]))
    {
        cchBuffer--;
    }

    pszBuffer[cchBuffer] = '\0';

    return (TRUE);
} // MciAppGetLine()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppInternalCommand
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//  History:
//       2/20/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MciAppInternalCommand
(
    HWND            hwnd,
    PTSTR           pszCommandLine,
    BOOL            fYield
)
{
    static TCHAR    szWhiteToken[]      = " \t\n";
    static TCHAR    szTokenSleep[]      = "sleep";
    static TCHAR    szTokenPause[]      = "pause";

    static TCHAR    szFormatError[]     = TEXT("INTERNAL Error: Command (%s), %s.");
    static TCHAR    szFormatDBError[]   = TEXT("    ICmd Error: Command (%s), %s.");

    static TCHAR    szSleepComplete[]   = TEXT("Sleep complete.");
    static TCHAR    szSleepDBComplete[] = TEXT("      Sleeping:  sleep complete.");

    static TCHAR    szErrorBadCmd[]     = TEXT("unrecognized command");
    static TCHAR    szErrorBadParam[]   = TEXT("invalid parameter");
    static TCHAR    szErrorParamRqd[]   = TEXT("parameter(s) required");

    static TCHAR    szFormatSleeping[]  = TEXT("Sleeping for %lu %s...");
    static TCHAR    szSeconds[]         = TEXT("seconds");
    static TCHAR    szMilliseconds[]    = TEXT("milliseconds");
    static TCHAR    szMinutes[]         = TEXT("minutes");
    static TCHAR    szHours[]           = TEXT("hours");

    PTSTR       pchToken;
    PTSTR       pchCommand;
    HWND        hwndStatus;
    long        l;
    DWORD       dwSleep;
    PTSTR       szSleepUnit;

    //
    //  parse the internal command line...
    //
    pchToken = strtok(pszCommandLine, szWhiteToken);
    if (NULL == pchToken)
        return (TRUE);

    hwndStatus = GetDlgItem(hwnd, IDD_APP_TEXT_STATUS);
    pchCommand = pchToken;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  sleep x [ms | s | m | h]        (default is milliseconds)
//  pause x [ms | s | m | h]        (default is milliseconds)
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

    if ((0 == lstrcmpi(pchCommand, szTokenSleep)) ||
        (0 == lstrcmpi(pchCommand, szTokenPause)))
    {
        pchToken = strtok(NULL, szWhiteToken);
        if (NULL == pchToken)
        {
            MessageBeep((UINT)-1);
            AppSetWindowText(hwndStatus, szFormatError,
                             (LPSTR)pchCommand, (LPSTR)szErrorParamRqd);
            MciAppDebugLog(szFormatDBError, (LPSTR)pchCommand,
                           (LPSTR)szErrorParamRqd);
            return (TRUE);
        }

        //
        //  assume the first token after sleep/pause command is a
        //  number--do not allow zero or negative numbers!
        //
        l = atol(pchToken);
        if (0 >= l)
        {
            MessageBeep((UINT)-1);
            AppSetWindowText(hwndStatus, szFormatError,
                             (LPSTR)pchCommand, (LPSTR)szErrorBadParam);
            MciAppDebugLog(szFormatDBError, (LPSTR)pchCommand,
                           (LPSTR)szErrorBadParam);
            return (TRUE);
        }

        //
        //  now see what unit
        //
        dwSleep     = (DWORD)l;
        szSleepUnit = szMilliseconds;
        pchToken    = strtok(NULL, szWhiteToken);
        if (NULL != pchToken)
        {
            switch (pchToken[0])
            {
                case 's':
                case 'S':
                    dwSleep *= 1000;
                    szSleepUnit = szSeconds;
                    break;

                case 'm':
                case 'M':
                    //
                    //  if unit marker is not 'ms' then assume minutes
                    //
                    if ('s' != pchToken[1])
                    {
                        dwSleep *= 1000L * 60;
                        szSleepUnit = szMinutes;
                    }
                    break;

                case 'h':
                case 'H':
                    dwSleep *= 1000L * 60 * 60;
                    szSleepUnit = szHours;
                    break;
            }
        }

        //
        //
        //
        AppSetWindowText(hwndStatus, szFormatSleeping, l, (LPSTR)szSleepUnit);
        MciAppDebugLog(szFormatSleeping, l, (LPSTR)szSleepUnit);

        if (!fYield)
            UpdateWindow(hwndStatus);

        //
        //  !!! broken !!!
        //
        //
#ifdef WIN32
        Sleep(dwSleep);
#else
{
        DWORD   dwStart;

        if (!fYield)
            AppHourGlass(TRUE);

        dwStart = timeGetTime();
        while ((timeGetTime() - dwStart) < dwSleep)
        {
            if (fYield)
            {
                AppYield(hwnd, FALSE);
                if (gfAbortExec)
                    break;
            }
            else
            {
                if (GetAsyncKeyState(VK_ESCAPE) < 0)
                    break;
            }
        }

        if (!fYield)
            AppHourGlass(FALSE);
}
#endif

        AppSetWindowText(hwndStatus, szSleepComplete);
        MciAppDebugLog(szSleepDBComplete);

        if (!fYield)
            UpdateWindow(hwndStatus);

        return (TRUE);
    }

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  unrecognized command...
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

    MessageBeep((UINT)-1);
    AppSetWindowText(hwndStatus, szFormatError,
                     (LPSTR)pchCommand, (LPSTR)szErrorBadCmd);
    MciAppDebugLog(szFormatDBError, (LPSTR)pchCommand, (LPSTR)szErrorBadCmd);

    return (TRUE);
} // MciAppInternalCommand()

//--------------------------------------------------------------------------;
//
//  MCIERROR MciAppSingleStep
//
//  Description:
//      Executes one line from the script window.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (MCIERROR):
//      The return value is the MCIERROR return value from mciSendString.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

MCIERROR FNGLOBAL MciAppSingleStep
(
    HWND            hwnd
)
{
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    HWND        hwndScript;
    int         nLine;
    int         nLineStart;
    int         nSelStart;
    int         nSelEnd;
    MCIERROR    mciError;
    BOOL        fDidOne;

    //
    //  initialize a bunch of stuff...
    //
    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    mciError   = MMSYSERR_NOERROR;
    fDidOne    = FALSE;
    nLineStart = Edit_LineFromChar(hwndScript, -1);
    nLine      = nLineStart;

    //
    //  step through the script starting with the current line until we
    //  execute one non-comment/blank line...
    //
    for ( ; MciAppGetLine(hwndScript, nLine, ach, SIZEOF(ach)); nLine++)
    {
        //
        //  select the current line that is going to be executed....
        //
        nSelStart = Edit_LineIndex(hwndScript, nLine);
        nSelEnd   = Edit_LineIndex(hwndScript, nLine + 1);
        if (nSelEnd < nSelStart)
            nSelEnd = 0x7FFF;
        else if (nSelEnd > nSelStart)
            nSelEnd -= 1;

        Edit_SetSel(hwndScript, nSelStart, nSelEnd);

        //
        //  anything worth doing something with?
        //
        if (('\0' == ach[0]) || (';' == ach[0]))
            continue;

        //
        //  if first character is '!' then process rest of line as an
        //  internale MCI App command...
        //
        if ('!' == ach[0])
        {
            MciAppInternalCommand(hwnd, &ach[1], FALSE);
            continue;
        }

        //
        //  if we have actually executed a line, then break out... we do
        //  this so the above code will have selected the next line for
        //  single stepping.
        //
        if (fDidOne)
            break;

        //
        //  send the command on the current line to MCI via the string
        //  interface.
        //
        mciError = MciAppSendString(hwnd, ach);
        fDidOne  = TRUE;
    }

    SetFocus(hwndScript);
    return (mciError);
} // MciAppSingleStep()

//--------------------------------------------------------------------------;
//
//  MCIERROR MciAppExecute
//
//  Description:
//      Executes the MCI command which is currently selected in the script
//      window. If fSingleStep is TRUE, then only this one line will be
//      executed. Otherwise, every line from the currently selected line to
//      the last line in the script window will be executed sequentially.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      BOOL fYield: TRUE if execution should yield between MCI commands.
//
//  Return (MCIERROR):
//      The return value is the last MCIERROR return value from executing.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

MCIERROR FNGLOBAL MciAppExecute
(
    HWND            hwnd,
    BOOL            fYield
)
{
    static TCHAR    szFormatRuncount[]     = TEXT("%u");

    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    HWND        hwndScript;
    HWND        hwndRuncount;
    UINT        uRunCount;
    UINT        u;
    UINT        uLinesExecuted;
    int         n;
    int         nLine;
    int         nLineStart;
    int         nSelStart;
    int         nSelEnd;
    MCIERROR    mciError;
    BOOL        fIgnoreErrors;

    hwndScript   = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    hwndRuncount = GetDlgItem(hwnd, IDD_APP_EDIT_RUNCOUNT);

    uRunCount  = GetDlgItemInt(hwnd, IDD_APP_EDIT_RUNCOUNT, NULL, TRUE);

    uLinesExecuted = 0;
    mciError       = MMSYSERR_NOERROR;
    fIgnoreErrors  = FALSE;

    gfExecuting    = TRUE;
    gfAbortExec    = FALSE;

    //
    //  if we are yielding, then disable a bunch of controls...
    //
    //  if we are not yielding during then bring up an hour glass to show
    //  we be busy... don't bother with disabling controls--they can't
    //  be used
    //
    if (fYield)
    {
        static TCHAR    szButtonStop[]  = TEXT("&Stop");

        SetWindowText(GetDlgItem(hwnd, IDD_APP_BTN_STEP), szButtonStop);

        EnableWindow(GetDlgItem(hwnd, IDD_APP_BTN_GO),        FALSE);
        EnableWindow(GetDlgItem(hwnd, IDD_APP_BTN_RUN),       FALSE);
        EnableWindow(GetDlgItem(hwnd, IDD_APP_EDIT_RUNCOUNT), FALSE);
    }
    else
    {
        AppHourGlass(TRUE);
    }

    //
    //  go through every line in the script window from the currently
    //  selected line to the last line...
    //
    nLineStart = Edit_LineFromChar(hwndScript, -1);
    for (u = uRunCount; u; u--)
    {
        nLine = nLineStart;
        for ( ; MciAppGetLine(hwndScript, nLine, ach, SIZEOF(ach)); nLine++)
        {
            //
            //  select the current line that is going to be executed....
            //
            nSelStart = Edit_LineIndex(hwndScript, nLine);
            nSelEnd   = Edit_LineIndex(hwndScript, nLine + 1);
            if (nSelEnd < nSelStart)
                nSelEnd = 0x7FFF;
            else if (nSelEnd > nSelStart)
                nSelEnd -= 1;

            Edit_SetSel(hwndScript, nSelStart, nSelEnd);

            //
            //  check for abort...
            //
            if (gfAbortExec)
            {
                n = AppMsgBoxId(hwnd, MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL,
                                IDS_EXEC_ABORT);
                if (IDYES == n)
                    goto MciApp_Execute_Exit;

                gfAbortExec = FALSE;
            }

            //
            //  anything worth doing something with?
            //
            if (('\0' == ach[0]) || (';' == ach[0]))
                continue;

            //
            //  if first character is '!' then process rest of line as an
            //  internale MCI App command...
            //
            if ('!' == ach[0])
            {
                MciAppInternalCommand(hwnd, &ach[1], fYield);
                continue;
            }

            //
            //  send the command on the current line to MCI via the string
            //  interface.
            //
            mciError = MciAppSendString(hwnd, ach);
            if ((MMSYSERR_NOERROR != mciError) && !fIgnoreErrors)
            {
                n = DialogBox(ghinst, DLG_MCIERR, hwnd, (DLGPROC)MciAppErrorDlgProc);
                switch (n)
                {
                    case IDABORT:
                        goto MciApp_Execute_Exit;

                    case IDIGNORE:
                        fIgnoreErrors = TRUE;
                        break;
                }
            }

            uLinesExecuted++;

            //
            //  yield like a good little app..
            //
            if (fYield)
                AppYield(hwnd, FALSE);
            else
            {
                UpdateWindow(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS));
                UpdateWindow(GetDlgItem(hwnd, IDD_APP_TEXT_OUTPUT));
            }
        }

        //
        //  SetDlgItemInt() won't update the field unless we yield
        //  so set window text instead... in case not yielding.
        //
        SetDlgItemInt(hwnd, IDD_APP_EDIT_RUNCOUNT, u, TRUE);
        if (fYield)
            AppYield(hwnd, FALSE);
        else
            UpdateWindow(GetDlgItem(hwnd, IDD_APP_EDIT_RUNCOUNT));
    }

MciApp_Execute_Exit:

    gfExecuting = FALSE;
    if (fYield)
    {
        static TCHAR    szButtonStep[]  = TEXT("&Step");

        SetWindowText(GetDlgItem(hwnd, IDD_APP_BTN_STEP), szButtonStep);

        EnableWindow(GetDlgItem(hwnd, IDD_APP_BTN_GO),        TRUE);
        EnableWindow(GetDlgItem(hwnd, IDD_APP_BTN_RUN),       TRUE);
        EnableWindow(GetDlgItem(hwnd, IDD_APP_EDIT_RUNCOUNT), TRUE);
    }
    else
    {
        AppHourGlass(FALSE);
    }

    SetDlgItemInt(hwnd, IDD_APP_EDIT_RUNCOUNT, uRunCount, TRUE);
    SetFocus(hwndScript);

    return (mciError);
} // MciAppExecute()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppEnterLine
//
//  Description:
//      This function handles entering a new line into the script window.
//      This may involve executing the new line if Options.Edit Only is
//      not selected.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      BOOL fEditOnly: TRUE if in data entry mode (won't single step the
//      current line before inserting CR/LF). FALSE if the current line
//      should be executed and a CR/LF inserted at the _END_ of the
//      current line.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppEnterLine
(
    HWND            hwnd,
    BOOL            fEditOnly
)
{
    static TCHAR    szEOL[]     = TEXT("\r\n");

    HWND    hwndScript;
    int     n;

    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);

    //
    //  if Options.Edit Only is checked or the user pressed Alt+Enter,
    //  then act like a simple edit control...
    //
    if (fEditOnly)
    {
        Edit_ReplaceSel(hwndScript, szEOL);
        return (TRUE);
    }

    //
    //  get current line (containing caret)--if there is a
    //  selection, the line number of the line containing the
    //  _beginning_ of the selection is retrieved.
    //
    SetFocus(hwndScript);
    n = Edit_LineFromChar(hwndScript, -1);
    MciAppSingleStep(hwnd);
    n = Edit_LineIndex(hwndScript, n);
    Edit_SetSel(hwndScript, n, n);

    //
    //  now force CR/LF at the _end_ of the current line
    //
    FORWARD_WM_KEYDOWN(hwndScript, VK_END, 1, 0, SendMessage);
    FORWARD_WM_KEYUP(hwndScript, VK_END, 1, 0, SendMessage);
    Edit_ReplaceSel(hwndScript, szEOL);

    return (TRUE);
} // MciAppEnterLine()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MciAppDispatchMessage
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//  History:
//       2/ 8/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppDispatchMessage
(
    HWND            hwnd,
    PMSG            pmsg
)
{
    static HWND     hwndScript;
    static HACCEL   haccl;

    UINT        u;

    //
    //  if this is the first time through, cache some stuff...
    //
    if (NULL == hwndScript)
        hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);

    if (NULL == haccl)
        haccl = LoadAccelerators(ghinst, ACCEL_APP);

    //
    //  peek at the message being sent. if it is one of the special
    //  things for the script window, then deal with it...
    //
    //  !!! this is somewhat bogus !!!
    //
    if ((WM_KEYDOWN == pmsg->message) || (WM_SYSKEYDOWN == pmsg->message))
    {
        if (GetActiveWindow() != hwnd)
            goto MciApp_Dispatch_Continue;

        switch (pmsg->wParam)
        {
            case VK_RETURN:
                if (GetFocus() != hwndScript)
                    break;

                //
                //  force a line to be 'executed' and stuff... if Alt+Enter
                //  was used, then reverse the logic of 'Edit Only' by
                //  setting uCode to 0 (from menu)
                //
                u = (GetKeyState(VK_MENU) < 0) ? 0 : 1;
                FORWARD_WM_COMMAND(hwnd, IDOK, hwndScript, u, PostMessage);
                return (TRUE);

            case VK_TAB:
                if (WM_SYSKEYDOWN == pmsg->message)
                    break;

                if (GetKeyState(VK_CONTROL) < 0)
                {
                    if (GetFocus() == hwndScript)
                    {
                        SetFocus(GetDlgItem(hwnd, IDD_APP_BTN_STEP));
                        return (TRUE);
                    }

                    SetFocus(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT));
                    return (TRUE);
                }
                else if (GetFocus() == hwndScript)
                {
                    static TCHAR    szTab[]     = TEXT("\t");
                    Edit_ReplaceSel(hwndScript, szTab);
                    return (TRUE);
                }
                break;
        }
    }

MciApp_Dispatch_Continue:

    //
    //  update the device list if it needs to be updated--also
    //  do the floating dialog box message thing...
    //
    if (NULL != ghwndDevices)
    {
        MciAppDeviceListUpdate(hwnd, ghwndDevices, FALSE);

        if (IsDialogMessage(ghwndDevices, pmsg))
            return (TRUE);
    }

    //
    //  take care of accelerators and dialog box style things...
    //
    if (TranslateAccelerator(hwnd, haccl, pmsg) ||
        IsDialogMessage(hwnd, pmsg))

{
        return (TRUE);
    }

    TranslateMessage(pmsg);
    DispatchMessage(pmsg);

    return (TRUE);
} // MciAppDispatchMessage()

//==========================================================================;
//
//  Startup and shutdown code...
//
//
//==========================================================================;

TCHAR   gszKeyOptions[]     = TEXT("Options");
TCHAR   gszFormatOptions[]  = TEXT("%u");

TCHAR   gszKeyWindow[]      = TEXT("Window");
TCHAR   gszKeyFont[]        = TEXT("Font");

//--------------------------------------------------------------------------;
//
//  BOOL MciAppChooseFont
//
//  Description:
//      This function lets the user choose a new font for the script window.
//      After a new font is chosen, the font structure is stored to the
//      .ini file so it can be restored on the next run of this application.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      The return value is TRUE if a new font was chosen. It is FALSE if
//      the user canceled the operation.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppChooseFont
(
    HWND            hwnd
)
{
    LOGFONT     lf;
    HWND        hwndScript;
    HFONT       hfont;
    HFONT       hfontNew;

    hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);

    //
    //  get the current script font and pass it to the choose font dialog
    //
    hfont = GetWindowFont(hwndScript);

    hfontNew = AppChooseFont(hwnd, hfont, &lf);
    if (NULL == hfontNew)
        return (FALSE);

    //
    //  select the new font into the script window and delete the old one
    //
    SetWindowFont(hwndScript, hfontNew, TRUE);
    DeleteFont(hfont);

    //
    //  save the complete description of the chosen font so there can be
    //  no strangness in the font mapping next run. this is overkill, but
    //  it works...
    //
    AppProfileWriteBytes(gszKeyFont, (LPBYTE)&lf, sizeof(lf));

    return (TRUE);
} // MciAppChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppSettingsRestore
//
//  Description:
//      This function restores state information for the application. This
//      function is called just after the main window is created (it has
//      not been ShowWindow()'d). This function will generate the call
//      to ShowWindow before returning.
//
//  Arguments:
//      HWND hwnd: Handle to main window that has just been created but
//      not shown.
//
//      int nCmdShow: The state that the application window should show as.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//       2/15/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MciAppSettingsRestore
(
    HWND            hwnd,
    int             nCmdShow
)
{
    static TCHAR    szSecExtensions[]   = TEXT("Extensions");
    static TCHAR    szKeyMCI[]          = TEXT("mci");
    static TCHAR    szValAssocMCI[]     = TEXT("mciapp.exe ^.mci");
    static TCHAR    szKeyMCS[]          = TEXT("mcs");
    static TCHAR    szValAssocMCS[]     = TEXT("mciapp.exe ^.mcs");

    TCHAR           ach[APP_MAX_STRING_RC_CHARS];
    WINDOWPLACEMENT wp;
    HFONT           hfont;
    LOGFONT         lf;
    UINT            u;
    RECT            rc;
    RECT            rcWindow;
    POINT           pt;
    int             n;
    BOOL            f;

    //
    //  restore the previous Options state...
    //
    gfuAppOptions = GetProfileInt(gszAppSection, gszKeyOptions, gfuAppOptions);

    //
    //  we want to make sure that the association for .MCI and .MCS files is
    //  to run MCIAPP.EXE. this way any app that ShellExecute's a .MCI or
    //  .MCS file will run this app...
    //
    //  in WIN.INI:
    //
    //      [Extensions]
    //      mci = mciapp.exe ^.mci
    //      mcs = mciapp.exe ^.mcs
    //
    u = (UINT)GetProfileString(szSecExtensions, szKeyMCI, gszNull, ach, SIZEOF(ach));
    if ((0 == u) || lstrcmpi(ach, szValAssocMCI))
    {
        WriteProfileString(szSecExtensions, szKeyMCI, szValAssocMCI);
    }

    u = (UINT)GetProfileString(szSecExtensions, szKeyMCS, gszNull, ach, SIZEOF(ach));
    if ((0 == u) || lstrcmpi(ach, szValAssocMCS))
    {
        WriteProfileString(szSecExtensions, szKeyMCS, szValAssocMCS);
    }

    //
    //  restore the user's preferred font.
    //
    f = AppProfileReadBytes(gszKeyFont, (LPBYTE)&lf, sizeof(lf));
    if (f)
    {
        hfont = CreateFontIndirect(&lf);
        if (NULL != hfont)
        {
            SetWindowFont(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT), hfont, FALSE);
        }
    }

    //
    //  grab the stored window position and size from the .ini file...
    //  there must be four arguments stored or the entry is considered
    //  invalid.
    //
    f = AppProfileReadBytes(gszKeyWindow, (LPBYTE)&rcWindow, sizeof(rcWindow));
    if (f)
    {
        //
        //  to make sure the user can always get at the window, check to
        //  see if the midpoint of the caption is visible--if it is not,
        //  then default to the default position used when creating the
        //  window.
        //
        n = (rcWindow.right - rcWindow.left) / 2;
        pt.x = (n + rcWindow.left);

        n = GetSystemMetrics(SM_CYCAPTION) / 2 + GetSystemMetrics(SM_CXFRAME);
        pt.y = (n + rcWindow.top);

        GetWindowRect(GetDesktopWindow(), &rc);
        if (PtInRect(&rc, pt))
        {
            //
            //  fill out the window placement structure--default the
            //  maximized and minimized states to default placement by
            //  getting its current placement.
            //
            wp.length = sizeof(wp);
            GetWindowPlacement(hwnd, &wp);

            wp.showCmd          = nCmdShow;
            wp.rcNormalPosition = rcWindow;

            SetWindowPlacement(hwnd, &wp);
            return (TRUE);
        }
    }

    //
    //  show defaulted and succeed
    //
    ShowWindow(hwnd, nCmdShow);
    return (TRUE);
} // MciAppSettingsRestore()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppSettingsSave
//
//  Description:
//      This function saves the current state information for the application.
//      It is called just before the main window is closed (destroyed); or
//      as Windows is exiting (query end session).
//
//      Note that this function should not destroy any resources--it can
//      be called at any time to save a snapshot of the application state.
//
//  Arguments:
//      HWND hwnd: Handle to main window that will be destroyed shortly.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//       2/15/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppSettingsSave
(
    HWND            hwnd
)
{
    TCHAR           ach[APP_MAX_STRING_RC_CHARS];
    WINDOWPLACEMENT wp;
    PRECT           prc;
    BOOL            f;

    //
    //  save the current option settings--note that we ALWAYS turn off the
    //  debug logging option so the app doesn't try to OutputDebugString
    //  unexpectedly during the next session...
    //
    gfuAppOptions &= ~APP_OPTF_DEBUGLOG;
    if (GetProfileInt(gszAppSection, gszKeyOptions, 0) != gfuAppOptions)
    {
        wsprintf(ach, gszFormatOptions, gfuAppOptions);
        WriteProfileString(gszAppSection, gszKeyOptions, ach);
    }

    //
    //  save the current window placement--only store the size and location
    //  of the restored window. maximized and minimized states should
    //  remain defaulted on the next invocation of this application.
    //
    wp.length = sizeof(wp);
    f = GetWindowPlacement(hwnd, &wp);
    if (f)
    {
        prc = &wp.rcNormalPosition;

        DPF(0, "WindowPlacement: show=%d, minX=%d, minY=%d, maxX=%d, maxY=%d",
             wp.showCmd, wp.ptMinPosition.x, wp.ptMinPosition.y,
             wp.ptMaxPosition.x, wp.ptMaxPosition.y);

        DPF(0, "                 normX=%d, normY=%d, normW=%d, normH=%d",
             prc->left, prc->top, prc->right, prc->bottom);

        //
        //  save the _bounding rectangle_ of the restored window state...
        //
        AppProfileWriteBytes(gszKeyWindow, (LPBYTE)prc, sizeof(*prc));
    }

    //
    //  succeed
    //
    return (TRUE);
} // MciAppSettingsSave()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppShutdown
//
//  Description:
//      This function is called to gracefully shut down the application.
//      If the application should not be closed, a FALSE value is returned.
//      This function is called for WM_CLOSE and WM_QUERYENDSESSION
//      messages...
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//      PTSTR pszFilePath: Pointer to current file path of script. If the
//      file needs to be saved, then this buffer will receive the new
//      file path if one is chosen.
//
//      PTSTR pszFilePath: Pointer to current file title of script. If the
//      file needs to be saved, then this buffer will receive the new
//      file title if one is chosen.
//
//  Return (BOOL):
//      Returns TRUE if the application can proceed with close. Returns
//      FALSE if the application should NOT be closed.
//
//  History:
//       2/ 9/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppShutdown
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
    int         n;
    UINT        u;
    BOOL        f;

    //
    //  if we are currently executing a script (Run or Go), things would
    //  get very messy if we tried to close the application. so disallow
    //  it!
    //
    //  !!! this is rude--bring up dialog with the problem statement !!!
    //
    if (gfExecuting)
    {
        MessageBeep((UINT)-1);
        return (FALSE);
    }

    //
    //  check if the script has been modified without saving. if the user
    //  cancels the operation, then we will NOT close the application.
    //
    f = MciAppFileSaveModified(hwnd, pszFilePath, pszFileTitle);
    if (!f)
        return (FALSE);

    //
    //  if devices are still open, ask user if we should close them down
    //  before exiting. the only reason we don't do this by default is
    //  for application cleanup testing in mmsystem... i suppose it is
    //  a nifty reminder for scripts that don't close devices also.
    //
    u = MciAppGetNumDevices(hwnd);
    if (0 != u)
    {
        n = AppMsgBoxId(hwnd, MB_YESNOCANCEL | MB_ICONEXCLAMATION | MB_TASKMODAL
                        | MB_SETFOREGROUND, IDS_WARN_OPEN_DEVICES, u);
        switch (n)
        {
            case IDYES:
                MciAppCloseAllDevices(hwnd);
                break;

            case IDNO:
                break;

            case IDCANCEL:
                return (FALSE);
        }
    }

    //
    //  save any settings that should be saved on app termination...
    //
    MciAppSettingsSave(hwnd);

    //
    //  allow closing of application...
    //
    return (TRUE);
} // MciAppShutdown()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppInit
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//  History:
//       2/15/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppInit
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    LPTSTR          pszCmdLine,
    int             nCmdShow
)
{
    BOOL        f;

    //
    //
    //
    MciAppSettingsRestore(hwnd, nCmdShow);

    //
    //  strip the command line..
    //
    if (NULL != pszCmdLine)
    {
        while (('\0' != *pszCmdLine) && (' ' == *pszCmdLine))
            pszCmdLine++;
    }

    //
    //  if there is a command line, assume it is a filename for a script
    //  and try to open it. otherwise, just initialize the script window.
    //
    if ((NULL != pszCmdLine) && ('\0' != *pszCmdLine))
    {
        //
        //  attempt to open the specified file..
        //
        lstrcpy(pszFilePath, pszCmdLine);
        f = MciAppFileOpen(hwnd, pszFilePath, pszFileTitle);
        if (f)
        {
            AppTitle(hwnd, pszFileTitle);
            SetFocus(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT));
        }
        else
        {
            //
            //  opening the command line file was untriumphant..
            //
            AppMsgBoxId(hwnd, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL,
                        IDS_ERROR_OPEN_FAILED, (LPSTR)pszFilePath);

            pszFilePath[0]  = '\0';
            pszFileTitle[0] = '\0';
            AppFileNew(hwnd, pszFilePath, pszFileTitle);
        }
    }
    else
    {
        AppFileNew(hwnd, pszFilePath, pszFileTitle);
    }

    //
    //
    //
    //
    MciAppDeviceList(hwnd, FALSE);

    return (TRUE);
} // MciAppInit()

//--------------------------------------------------------------------------;
//
//  BOOL MciAppExit
//
//  Description:
//
//
//  Arguments:
//
//
//  Return (BOOL):
//
//
//  History:
//       2/15/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MciAppExit
(
    void
)
{
    //
    //  clean up any resources and stuff we have allocated...
    //

    return (TRUE);
} // MciAppExit()


MCIAPP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mciapp.h
//
//  Description:
//      This file contains public definitions, etc for the MCI App specific
//      code.
//
//  History:
//       2/ 7/93    created.
//
//==========================================================================;

//
//
//
#ifndef _MCIERROR_
#define _MCIERROR_
typedef DWORD       MCIERROR;
#endif

#ifndef _MCIDEVICEID_
#define _MCIDEVICEID_
typedef UINT        MCIDEVICEID;
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  file manipulation functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FNGLOBAL MciAppFileSaveModified
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
);

BOOL FNGLOBAL MciAppFileNew
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
);

BOOL FNGLOBAL MciAppFileOpen
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
);

BOOL FNGLOBAL MciAppFileSave
(
    HWND            hwnd,
    PCTSTR          pszFilePath
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc functions
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

BOOL FNGLOBAL MciAppChooseFont
(
    HWND            hwnd
);

BOOL FNGLOBAL MciAppUpdateOptions
(
    HWND            hwnd
);

BOOL FNGLOBAL MciAppResetStatus
(
    HWND            hwnd
);

BOOL FNGLOBAL MciAppHandleNotify
(
    HWND            hwnd,
    UINT            fuNotify,
    UINT            uId
);

MCIERROR FNGLOBAL MciAppSingleStep
(
    HWND            hwnd
);

MCIERROR FNGLOBAL MciAppExecute
(
    HWND            hwnd,
    BOOL            fYield
);

BOOL FNGLOBAL MciAppEnterLine
(
    HWND            hwnd,
    BOOL            fEditOnly
);

UINT FNGLOBAL MciAppGetNumDevices
(
    HWND            hwnd
);

BOOL FNGLOBAL MciAppCloseAllDevices
(
    HWND            hwnd
);

BOOL FNEXPORT MciAppDeviceDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL FNGLOBAL MciAppDeviceList
(
    HWND            hwnd,
    BOOL            fActivate
);

BOOL FNGLOBAL MciAppDispatchMessage
(
    HWND            hwnd,
    PMSG            pmsg
);

BOOL FNGLOBAL MciAppSettingsSave
(
    HWND            hwnd
);

BOOL FNGLOBAL MciAppShutdown
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
);

BOOL FNGLOBAL MciAppInit
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    LPTSTR          pszCmdLine,
    int             nCmdShow
);

BOOL FNGLOBAL MciAppExit
(
    void
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  global variables, etc.
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

extern BOOL         gfExecuting;
extern BOOL         gfAbortExec;


APP.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  app.c
//
//  Description:
//      This is a sample application that demonstrates how to use the
//      Media Control Interface (MCI) in Windows. This application is
//      also useful as an MCI device tester.
//
//  History:
//      11/ 8/92    created.
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <shellapi.h>
#include <stdarg.h>
#include <memory.h>
#include "appport.h"
#include "app.h"
#include "mciapp.h"

#include "debug.h"

//
//  globals, no less
//
HINSTANCE       ghinst;
UINT            gfuAppOptions       = APP_OPTF_YIELDEXEC;

TCHAR           gszAppSection[]     = TEXT("MCI App");
TCHAR           gszNull[]           = TEXT("");

TCHAR           gszAppName[APP_MAX_APP_NAME_CHARS];
TCHAR           gszFileUntitled[APP_MAX_FILE_TITLE_CHARS];
TCHAR           gszAppFileTitle[APP_MAX_FILE_TITLE_CHARS];
TCHAR           gszAppFilePath[APP_MAX_FILE_PATH_CHARS];

//==========================================================================;
//
//  Application helper functions
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int AppMsgBox
//
//  Description:
//      This function displays a message for the application in a standard
//      message box.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      PCTSTR pszFormat: Format string used for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() function.
//
//  History:
//       2/13/93    created.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBox
(
    HWND            hwnd,
    UINT            fuStyle,
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the message..
    //
    va_start(va, pszFormat);
    wvsprintf(ach, pszFormat, va);
    va_end(va);

    n = MessageBox(hwnd, ach, gszAppName, fuStyle);

    return (n);
} // AppMsgBox()

//--------------------------------------------------------------------------;
//
//  int AppMsgBoxId
//
//  Description:
//      This function displays a message for the application. The message
//      text is retrieved from the string resource table using LoadString.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() if the string
//      resource specified by uIdsFormat is valid. The return value is zero
//      if the string resource failed to load.
//
//  History:
//       2/13/93    created.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBoxId
(
    HWND            hwnd,
    UINT            fuStyle,
    UINT            uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
        //
        //  format and display the message..
        //
        va_start(va, uIdsFormat);
        wvsprintf(ach, szFormat, va);
        va_end(va);

        n = MessageBox(hwnd, ach, gszAppName, fuStyle);
    }

    return (n);
} // AppMsgBoxId()

//--------------------------------------------------------------------------;
//
//  void AppHourGlass
//
//  Description:
//      This function changes the cursor to that of the hour glass or
//      back to the previous cursor.
//
//      This function can be called recursively.
//
//  Arguments:
//      BOOL fHourGlass: TRUE if we need the hour glass.  FALSE if we need
//      the arrow back.
//
//  Return (void):
//      On return, the cursor will be what was requested.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

void FNGLOBAL AppHourGlass
(
    BOOL            fHourGlass
)
{
    static HCURSOR  hcur;
    static UINT     uWaiting = 0;

    if (fHourGlass)
    {
        if (!uWaiting)
        {
            hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE);
        }

        uWaiting++;
    }
    else
    {
        --uWaiting;

        if (!uWaiting)
        {
            ShowCursor(FALSE);
            SetCursor(hcur);
        }
    }
} // AppHourGlass()

//--------------------------------------------------------------------------;
//
//  BOOL AppYield
//
//  Description:
//      This function yields by dispatching all messages stacked up in the
//      application queue.
//
//  Arguments:
//      HWND hwnd: Handle to main window of application if not yielding
//      for a dialog. Handle to dialog box if yielding for a dialog box.
//
//      BOOL fIsDialog: TRUE if being called to yield for a dialog box.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppYield
(
    HWND            hwnd,
    BOOL            fIsDialog
)
{
    MSG     msg;

    if (fIsDialog)
    {
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if ((NULL == hwnd) || !IsDialogMessage(hwnd, &msg))
            {
                //
                //  see comment below..
                //
                MciAppDispatchMessage(GetParent(hwnd), &msg);
            }
        }
    }
    else
    {
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            //
            //  normally, we would only do the following here:
            //
            //      TranslateMessage(&msg);
            //      DispatchMessage(&msg);
            //
            //  but this app is special, so dispatch messages in a
            //  special way...
            //
            MciAppDispatchMessage(hwnd, &msg);
        }
    }

    return (TRUE);
} // AppYield()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowText
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      PCTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowText
(
    HWND            hwnd,
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the string in the window...
    //
    va_start(va, pszFormat);
    n = wvsprintf(ach, pszFormat, va);
    va_end(va);

    SetWindowText(hwnd, ach);

    return (n);
} // AppSetWindowText()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowTextId
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result. The format string is extracted from the string
//      table using LoadString() on the uIdsFormat argument.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was. This value is zero if the LoadString() function fails
//      for the uIdsFormat argument.
//
//  History:
//       2/ 7/93    created.
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowTextId
(
    HWND            hwnd,
    UINT            uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
        //
        //  format and display the string in the window...
        //
        va_start(va, uIdsFormat);
        n = wvsprintf(ach, szFormat, va);
        va_end(va);

        SetWindowText(hwnd, ach);
    }

    return (n);
} // AppSetWindowTextId()

//--------------------------------------------------------------------------;
//
//  BOOL AppGetFileTitle
//
//  Description:
//      This function extracts the file title from a file path and returns
//      it in the caller's specified buffer.
//
//  Arguments:
//      PCTSTR pszFilePath: Pointer to null terminated file path.
//
//      PTSTR pszFileTitle: Pointer to buffer to receive the file title.
//
//  Return (BOOL):
//      Always returns TRUE. But should return FALSE if this function
//      checked for bogus values, etc.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppGetFileTitle
(
    PCTSTR          pszFilePath,
    PTSTR           pszFileTitle
)
{
    #define IS_SLASH(c)     ('/' == (c) || '\\' == (c))

    PTSTR       pch;

    //
    //  scan to the end of the file path string..
    //
    for (pch = pszFilePath; '\0' != *pch; pch++)
        ;

    //
    //  now scan back toward the beginning of the string until a slash (\),
    //  colon, or start of the string is encountered.
    //
    while ((pch >= pszFilePath) && !IS_SLASH(*pch) && (':' != *pch))
    {
        pch--;
    }

    //
    //  finally, copy the 'title' into the destination buffer.. skip ahead
    //  one char since the above loop steps back one too many chars...
    //
    lstrcpy(pszFileTitle, ++pch);

    return (TRUE);
} // AppGetFileTitle()

//--------------------------------------------------------------------------;
//
//  BOOL AppGetFileName
//
//  Description:
//      This function is a wrapper for the Get[Open/Save]FileName commdlg
//      chooser dialogs. Based on the fuFlags argument, this function will
//      display the appropriate chooser dialog and return the result.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      PTSTR pszFilePath: Pointer to buffer to receive the file path.
//
//      PTSTR pszFileTitle: Pointer to buffer to receive the file title.
//      This argument may be NULL, in which case no title will be returned.
//
//      UINT fuFlags:
//
//  Return (BOOL):
//      The return value is TRUE if a file was chosen. It is FALSE if the
//      user canceled the operation.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppGetFileName
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    UINT            fuFlags
)
{
    #define APP_OFN_FLAGS_SAVE  (OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT)
    #define APP_OFN_FLAGS_OPEN  (OFN_HIDEREADONLY | OFN_FILEMUSTEXIST)

    TCHAR           szExtDefault[APP_MAX_EXT_DEFAULT_CHARS];
    TCHAR           szExtFilter[APP_MAX_EXT_FILTER_CHARS];
    OPENFILENAME    ofn;
    BOOL            f;
    PTCHAR          pch;

    //
    //  get the extension filter and default extension for this application
    //
    LoadString(ghinst, IDS_OFN_EXT_DEF, szExtDefault, SIZEOF(szExtDefault));
    LoadString(ghinst, IDS_OFN_EXT_FILTER, szExtFilter, SIZEOF(szExtFilter));

    //
    //  NOTE! building the filter string for the OPENFILENAME structure
    //  is a bit more difficult when dealing with Unicode and C8's new
    //  optimizer. it joyfully removes literal '\0' characters from
    //  strings that are concatted together. if you try making each
    //  string separate (array of pointers to strings), the compiler
    //  will dword align them... etc, etc.
    //
    //  if you can think of a better way to build the silly filter string
    //  for common dialogs and still work in Win 16 and Win 32 [Unicode]
    //  i'd sure like to hear about it...
    //
    for (pch = &szExtFilter[0]; '\0' != *pch; pch++)
    {
        if ('!' == *pch)
            *pch = '\0';
    }

    //
    //  initialize the OPENFILENAME members
    //
    memset(&ofn, 0, sizeof(OPENFILENAME));

    pszFilePath[0]          = '\0';
    if (pszFileTitle)
        pszFileTitle[0]     = '\0';

    ofn.lStructSize         = sizeof(OPENFILENAME);
    ofn.hwndOwner           = hwnd;
    ofn.lpstrFilter         = szExtFilter;
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0L;
    ofn.nFilterIndex        = 1L;
    ofn.lpstrFile           = pszFilePath;
    ofn.nMaxFile            = APP_MAX_FILE_PATH_CHARS;
    ofn.lpstrFileTitle      = pszFileTitle;
    ofn.nMaxFileTitle       = pszFileTitle ? APP_MAX_FILE_TITLE_CHARS : 0;
    ofn.lpstrInitialDir     = NULL;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = szExtDefault;

    //
    //  if the fuFlags.APP_GFNF_SAVE bit is set, then call GetSaveFileName()
    //  otherwise call GetOpenFileName(). why commdlg was designed with
    //  two separate functions for save and open only clark knows.
    //
    if (fuFlags & APP_GFNF_SAVE)
    {
        ofn.Flags = APP_OFN_FLAGS_SAVE;
        f = GetSaveFileName(&ofn);
    }
    else
    {
        ofn.Flags = APP_OFN_FLAGS_OPEN;
        f = GetOpenFileName(&ofn);
    }

    return (f);
} // AppGetFileName()

//--------------------------------------------------------------------------;
//
//  BOOL AppTitle
//
//  Description:
//      This function formats and sets the title text of the application's
//      window.
//
//  Arguments:
//      HWND hwnd: Handle to application window to set title text for.
//
//      PCTSTR pszFileTitle: Pointer to file title to display.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppTitle
(
    HWND            hwnd,
    PCTSTR          pszFileTitle
)
{
    static  TCHAR   szFormatTitle[]     = TEXT("%s - %s");

    TCHAR       ach[APP_MAX_FILE_PATH_CHARS];

    //
    //  format the title text as 'AppName - FileTitle'
    //
    wsprintf(ach, szFormatTitle, (LPSTR)gszAppName, (LPSTR)pszFileTitle);
    SetWindowText(hwnd, ach);

    return (TRUE);
} // AppTitle()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileNew
//
//  Description:
//      This function is called to handle the IDM_FILE_NEW message. It is
//      responsible for clearing the working area for a new unnamed file.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PTSTR pszFilePath: Pointer to current null terminated file path.
//      This buffer will be reinitialized if the function succeeds.
//
//      PTSTR pszFileTitle: Pointer to current null terminated file title.
//      This buffer will be reinitialized if the function succeeds.
//
//  Return (BOOL):
//      The return value is TRUE if the working area was cleared and is
//      ready for new stuff. The return value is FALSE if the user canceled
//      the operation.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFileNew
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
    BOOL    f;

    //
    //  if there is currently a file path, then we have to do some real
    //  work...
    //
    if ('\0' != pszFilePath[0])
    {
        f = MciAppFileNew(hwnd, pszFilePath, pszFileTitle);
        if (!f)
            return (FALSE);
    }

    //
    //  blow away the old file path and title; set the window title and
    //  return success
    //
    lstrcpy(pszFilePath,  gszFileUntitled);
    lstrcpy(pszFileTitle, gszFileUntitled);

    AppTitle(hwnd, pszFileTitle);

    MciAppResetStatus(hwnd);
    AppSetWindowTextId(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS),
                       IDS_MCI_SCRIPT_CREATED, (LPSTR)pszFilePath);

    SetFocus(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT));

    return (TRUE);
} // AppFileNew()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileOpen
//
//  Description:
//      This function handles the IDM_FILE_OPEN message. It is responsible
//      for getting a new file name from the user and opening that file
//      if possible.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PTSTR pszFilePath: Pointer to current null terminated file path.
//      This buffer will contain the new file path if one is selected.
//
//      PTSTR pszFileTitle: Pointer to current null terminated file title.
//      This buffer will contain the new file title if one is selected.
//
//  Return (BOOL):
//      The return value is TRUE if a new file was selected and opened.
//      It is FALSE if the user canceled the operation.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL AppFileOpen
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle
)
{
    TCHAR       szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR       szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    BOOL        f;

    //
    //  first test for a modified script that has not been saved. if the
    //  return value is FALSE we should cancel the File.Open operation.
    //
    f = MciAppFileSaveModified(hwnd, pszFilePath, pszFileTitle);
    if (!f)
        return (FALSE);

    //
    //  get the file name of the new script into temporary buffers (so
    //  if we fail to open it we can back out cleanly).
    //
    f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GFNF_OPEN);
    if (!f)
        return (FALSE);

    //
    //  read the new script...
    //
    f = MciAppFileOpen(hwnd, szFilePath, NULL);
    if (f)
    {
        //
        //  copy the new file path and title into the global buffers and
        //  set the window title text...
        //
        lstrcpy(gszAppFilePath,  szFilePath);
        lstrcpy(gszAppFileTitle, szFileTitle);

        AppTitle(hwnd, szFileTitle);
        MciAppResetStatus(hwnd);

        AppSetWindowTextId(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS),
                           IDS_MCI_SCRIPT_OPENED, (LPSTR)szFilePath);

        SetFocus(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT));
    }

    return (f);
} // AppFileOpen()

//--------------------------------------------------------------------------;
//
//  BOOL AppFileSave
//
//  Description:
//      This function handles the IDM_FILE_SAVE[AS] messages. It is
//      responsible for saving the current file. If a file name needs
//      to be specified then the save file dialog is displayed.
//
//  Arguments:
//      HWND hwnd: Handle to application window.
//
//      PTSTR pszFilePath: Pointer to current null terminated file path.
//      This buffer will contain the new file path if one is selected.
//
//      PTSTR pszFileTitle: Pointer to current null terminated file title.
//      This buffer will contain the new file title if one is selected.
//
//      BOOL fSaveAs: TRUE if the save file chooser should be displayed
//      before saving the file. FALSE if should operate like File.Save.
//
//  Return (BOOL):
//      The return value is TRUE if the file was saved. It is FALSE if the
//      user canceled the operation or the file does not need saved.
//
//  History:
//       2/ 6/93    created.
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppFileSave
(
    HWND            hwnd,
    PTSTR           pszFilePath,
    PTSTR           pszFileTitle,
    BOOL            fSaveAs
)
{
    TCHAR       szFilePath[APP_MAX_FILE_PATH_CHARS];
    TCHAR       szFileTitle[APP_MAX_FILE_TITLE_CHARS];
    BOOL        f;

    //
    //  check if we should bring up the save file chooser dialog...
    //
    if (fSaveAs || !lstrcmp(pszFileTitle, gszFileUntitled))
    {
        //
        //  get the file name for saving the script to into temporary
        //  buffers (so if we fail to save it we can back out cleanly).
        //
        f = AppGetFileName(hwnd, szFilePath, szFileTitle, APP_GFNF_SAVE);
        if (!f)
            return (FALSE);
    }
    else
    {
        //
        //  copy the file path into our temporary buffer so we don't have
        //  to special case this on MciAppFileSave..
        //
        lstrcpy(szFilePath,  pszFilePath);
        lstrcpy(szFileTitle, pszFileTitle);
    }

    //
    //  save the script...
    //
    f = MciAppFileSave(hwnd, szFilePath);
    if (f)
    {
        //
        //  copy the (possibly) new file path and title into the global
        //  buffers and set the window title text...
        //
        lstrcpy(gszAppFilePath,  szFilePath);
        lstrcpy(gszAppFileTitle, szFileTitle);

        AppTitle(hwnd, szFileTitle);

        //
        //  changes have been saved, so clear the modified bit...
        //
        Edit_SetModify(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT), FALSE);

        AppSetWindowTextId(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS),
                           IDS_MCI_SCRIPT_SAVED, (LPSTR)szFilePath);

        SetFocus(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT));
    }

    return (f);
} // AppFileSave()

//==========================================================================;
//
//  Main application window handling code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT AppInitMenuPopup
//
//  Description:
//      This function handles the WM_INITMENUPOPUP message. This message
//      is sent to the window owning the menu that is going to become
//      active. This gives an application the ability to modify the menu
//      before it is displayed (disable/add items, etc).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      HMENU hmenu: Handle to the menu that is to become active.
//
//      int nItem: Specifies the zero-based relative position of the menu
//      item that invoked the popup menu.
//
//      BOOL fSysMenu: Specifies whether the popup menu is a System menu
//      (TRUE) or it is not a System menu (FALSE).
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//       1/ 2/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppInitMenuPopup
(
    HWND            hwnd,
    HMENU           hmenu,
    int             nItem,
    BOOL            fSysMenu
)
{
    BOOL    f;
    int     nSelStart;
    int     nSelEnd;
    HWND    hwndScript;

    DPF(0, "AppInitMenuPopup(hwnd=%Xh, hmenu=%Xh, nItem=%d, fSysMenu=%d)",
            hwnd, hmenu, nItem, fSysMenu);

    //
    //  if the system menu is what got hit, succeed immediately... this
    //  application has no stuff in the system menu.
    //
    if (fSysMenu)
        return (0L);

    //
    //  initialize the menu that is being 'popped up'
    //
    switch (nItem)
    {
        case APP_MENU_ITEM_FILE:
            //
            //  if the script has been modified, then enable the File.Save
            //  menu
            //
            hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
            f = Edit_GetModify(hwndScript);
            EnableMenuItem(hmenu, IDM_FILE_SAVE,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_EDIT:
            //
            //  check to see if something is selected in the script edit
            //  window and enable/disable Edit menu options appropriately
            //
            hwndScript = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
            Edit_GetSelEx(hwndScript, &nSelStart, &nSelEnd);

            f = (nSelStart != nSelEnd);
            EnableMenuItem(hmenu, WM_CUT,   (UINT)(f ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hmenu, WM_COPY,  (UINT)(f ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hmenu, WM_CLEAR, (UINT)(f ? MF_ENABLED : MF_GRAYED));

            f = Edit_CanUndo(hwndScript);
            EnableMenuItem(hmenu, WM_UNDO,  (UINT)(f ? MF_ENABLED : MF_GRAYED));

            f = IsClipboardFormatAvailable(CF_TEXT);
            EnableMenuItem(hmenu, WM_PASTE, (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_DEVICE:
            //
            //
            //
            f = (0 != (gfuAppOptions & APP_OPTF_DEVICELIST));
            CheckMenuItem(hmenu, IDM_DEVICE_OPENLIST,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));

            f = (MciAppGetNumDevices(hwnd) != 0);
            EnableMenuItem(hmenu, IDM_DEVICE_CLOSEALL,
                           (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;

        case APP_MENU_ITEM_OPTIONS:
            //
            //  make sure the options that need a checkmark are checked..
            //
            f = (0 != (gfuAppOptions & APP_OPTF_EDITONLY));
            CheckMenuItem(hmenu, IDM_OPTIONS_EDITONLY,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));

            f = (0 != (gfuAppOptions & APP_OPTF_YIELDEXEC));
            CheckMenuItem(hmenu, IDM_OPTIONS_YIELDEXEC,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));

            f = (0 != (gfuAppOptions & APP_OPTF_DEBUGLOG));
            CheckMenuItem(hmenu, IDM_OPTIONS_DEBUGLOG,
                          (UINT)(f ? MF_CHECKED : MF_UNCHECKED));
            break;
    }

    //
    //  we processed the message--return 0...
    //
    return (0L);
} // AppInitMenuPopup()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCommand
//
//  Description:
//      This function handles the WM_COMMAND message.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the WM_COMMAND message.
//
//      int nId: Control or menu item identifier.
//
//      HWND hwndCtl: Handle of control if the message is from a control.

//      This argument is NULL if the message was not generated by a control.
//
//      UINT uCode: Notification code. This argument is 1 if the message
//      was generated by an accelerator. If the message is from a menu,
//      this argument is 0.
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppCommand
(
    HWND            hwnd,
    int             nId,
    HWND            hwndCtl,
    UINT            uCode
)
{
    BOOL        f;

    if (gfExecuting)
    {
        if ((IDD_ACCL_ABORT == nId) || (IDD_APP_BTN_STOP == nId))
            gfAbortExec = TRUE;

        return (0L);
    }

    switch (nId)
    {
        case IDM_FILE_NEW:
            AppFileNew(hwnd, gszAppFilePath, gszAppFileTitle);
            break;

        case IDM_FILE_OPEN:
            AppFileOpen(hwnd, gszAppFilePath, gszAppFileTitle);
            break;

        case IDM_FILE_SAVE:
            AppFileSave(hwnd, gszAppFilePath, gszAppFileTitle, FALSE);
            break;

        case IDM_FILE_SAVEAS:
            AppFileSave(hwnd, gszAppFilePath, gszAppFileTitle, TRUE);
            break;

        case IDM_FILE_ABOUT:
            DialogBox(ghinst, DLG_ABOUT, hwnd, (DLGPROC)AboutDlgProc);
            break;

        case IDM_FILE_EXIT:
            FORWARD_WM_CLOSE(hwnd, SendMessage);
            break;

        case WM_UNDO:
        case WM_CUT:
        case WM_COPY:
        case WM_PASTE:
        case WM_CLEAR:
            //
            //  pass edit messages received to the script edit window
            //
            SendMessage(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT), nId, 0, 0L);
            break;

        case IDM_EDIT_SELECTALL:
            Edit_SetSel(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT), 0, -1);
            break;

        case IDM_DEVICE_CLOSEALL:
            MciAppCloseAllDevices(hwnd);
            break;

        case IDM_OPTIONS_EDITONLY:
            gfuAppOptions ^= APP_OPTF_EDITONLY;
            MciAppUpdateOptions(hwnd);
            break;

        case IDM_OPTIONS_YIELDEXEC:
            gfuAppOptions ^= APP_OPTF_YIELDEXEC;
            MciAppUpdateOptions(hwnd);
            break;

        case IDM_OPTIONS_DEBUGLOG:
            gfuAppOptions ^= APP_OPTF_DEBUGLOG;
            MciAppUpdateOptions(hwnd);
            break;

        case IDM_DEVICE_OPENLIST:
            //
            //  bring up the device list dialog--if this event was triggered
            //  by an accelerator (uCode == 1) then do not give the focus
            //  to the dialog. if this event was triggered by the user
            //  selecting the menu option with the mouse, then give the
            //  focus to the device list dialog...
            //
            gfuAppOptions ^= APP_OPTF_DEVICELIST;
            MciAppDeviceList(hwnd, (1 != uCode));
            break;

        case IDM_OPTIONS_FONT:
            MciAppChooseFont(hwnd);
            break;

        case IDD_APP_BTN_STEP:
            //
            //  execute the current line in the script window
            //
            MciAppSingleStep(hwnd);
            break;

        case IDD_APP_BTN_RUN:
            //
            //  execute every line in the script window starting from the
            //  _first_ line..
            //
            Edit_SetSel(GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT), 0, 0);

            // -- Fall Through --

        case IDD_APP_BTN_GO:
            //
            //  execute every line in the script window starting from the
            //  _current_ line..
            //
            f = (0 != (gfuAppOptions & APP_OPTF_YIELDEXEC));
            MciAppExecute(hwnd, f);
            break;

        case IDOK:
            //
            //  when we receive an IDOK for the script window (user pressed
            //  return key), 'enter' the new line...
            //
            //  uCode will be 0 if Alt+Enter was pressed, so reverse the
            //  logic of the Edit Only option in this case
            //
            if (GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT) == hwndCtl)
            {
                f = (0 != (gfuAppOptions & APP_OPTF_EDITONLY));
                MciAppEnterLine(hwnd, (1 == uCode) ? f : !f);
            }
            break;
    }

    return (0L);
} // AppCommand()

//--------------------------------------------------------------------------;
//
//  LRESULT AppDropFiles
//
//  Description:
//      This function handles the WM_DROPFILES message. This message is
//      sent when files are 'dropped' on the window from file manager
//      (or other drag/drop servers made by ISV's that figured out the
//      undocumented internal workings of the SHELL).
//
//      A window must be registered to receive these messages either by
//      called DragAcceptFiles() or using CreateWindowEx() with the
//      WS_EX_ACCEPTFILES style bit.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the message.
//
//      HDROP hdrop: Handle to drop structure.
//
//  Return (LRESULT):
//      Returns 0 if the message is processed.
//
//  History:
//       2/ 8/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppDropFiles
(
    HWND            hwnd,
    HDROP           hdrop
)
{
    TCHAR       szFileTitle[APP_MAX_FILE_PATH_CHARS];
    TCHAR       szFilePath[APP_MAX_FILE_PATH_CHARS];
    UINT        uNumFiles;
    UINT        u;
    BOOL        f;
    int         n;

    //
    //  if executing a script and this message is received, we bail...
    //  too much code to fix this...
    //
    if (gfExecuting)
    {
        MessageBeep((UINT)-1);
        goto App_Drop_Files_Exit;
    }

    //
    //  first test for a modified script that has not been saved. if the
    //  return value is FALSE we should cancel the drop operation.
    //
    f = MciAppFileSaveModified(hwnd, gszAppFilePath, gszAppFileTitle);
    if (!f)
        goto App_Drop_Files_Exit;

    //
    //  get number of files dropped on our window
    //
    uNumFiles = DragQueryFile(hdrop, (UINT)-1, NULL, 0);

    DPF(0, "AppDropFiles(hwnd=%Xh, hdrop=%Xh)--uNumFiles=%u",
             hwnd, hdrop, uNumFiles);

    //
    //  step through each file and stop on the one the user wants or
    //  the last file (whichever comes first).
    //
    for (u = 0; u < uNumFiles; u++)
    {
        //
        //  get the next file name and try to open it--if not a valid
        //  file, then skip to the next one (if there is one).
        //
        DragQueryFile(hdrop, u, szFilePath, SIZEOF(szFilePath));

        //
        //  attempt to open the file
        //
        f = MciAppFileOpen(hwnd, szFilePath, szFileTitle);
        if (!f)
            continue;

        //
        //  update display info
        //
        lstrcpy(gszAppFilePath, szFilePath);
        lstrcpy(gszAppFileTitle, szFileTitle);

        AppTitle(hwnd, szFileTitle);
        MciAppResetStatus(hwnd);

        AppSetWindowTextId(GetDlgItem(hwnd, IDD_APP_TEXT_STATUS),
                           IDS_MCI_SCRIPT_OPENED, (LPSTR)szFilePath);

        //
        //  if this is NOT the last file in the list of files that are
        //  being dropped on us, then bring up a box asking if we should
        //  continue or stop where we are..
        //
        if ((uNumFiles - 1) != u)
        {
            n = AppMsgBoxId(hwnd, MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL,
                            IDS_DROP_CONTINUE);
            if (IDNO == n)
                break;
        }
    }

    //
    //  tell the shell to release the memory it allocated for beaming
    //  the file name(s) over to us... return 0 to show we processed
    //  the message.
    //
App_Drop_Files_Exit:

    DragFinish(hdrop);
    return (0L);
} // AppDropFiles()

//--------------------------------------------------------------------------;
//
//  LRESULT AppSize
//
//  Description:
//      This function handles the WM_SIZE message for the application's
//      window. This message is sent to the application window after the
//      size has changed (but before it is painted).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_SIZE message.
//
//      UINT fuSizeType: Specifies the type of resizing requested. This
//      argument is one of the following: SIZE_MAXIMIZED, SIZE_MINIMIZED,
//      SIZE_RESTORED, SIZE_MAXHIDE, or SIZE_MAXSHOW.
//
//      int nWidth: Width of the new client area for the window.
//
//      int nHeight: Height of the new client area for the window.
//
//  Return (LRESULT):
//      Returns zero if the application processes the message.
//
//  History:
//       2/ 5/93    created.
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppSize
(
    HWND            hwnd,
    UINT            fuSizeType,
    int             nWidth,
    int             nHeight
)
{
    HWND    hwndButton;
    HWND    hwndOutput;
    HWND    hwndScript;
    HWND    hwndStatus;
    HWND    hwndSeparator;
    HWND    hwndNotify;
    HWND    hwndOptions;
    RECT    rc;
    int     nHeightButton;
    int     nWidthButton;
    int     nHeightScript;
    int     nHeightStatus;
    int     n, m;
    int     nWidthNotify;

    DPF(0, "AppSize(hwnd=%Xh, fuSizeType=%u, nWidth=%d, nHeight=%d)",
            hwnd, fuSizeType, nWidth, nHeight);

    //
    //  unless this application is the one being resized then don't waste
    //  time computing stuff that doesn't matter. this applies to being
    //  minimized also because this application does not have a custom
    //  minimized state.
    //
    if ((SIZE_RESTORED != fuSizeType) && (SIZE_MAXIMIZED != fuSizeType))
        return (0L);

    //
    //  for this application, we have three different sections that need
    //  to be fit into the newly sized client area--it looks roughly like
    //  this:
    //
    //  +-------------------------------------------+
    //  | - |        MCI App - ZYZSMAG.MCI      |^|v| <- title bar
    //  +-------------------------------------------+
    //  | File Edit Options                         | <- menu bar
    //  +-------------------------------------------+
    //  |{Step}{ Go }{ Run } Count:[x ] Output: zyz | <- button bar
    //  +-------------------------------------------+
    //  | MCI script window                         | <- script window
    //  |                                           |
    //  |                                           |
    //  |                                           |
    //  |                                           |
    //  |                                           |
    //  |                                           |
    //  +-------------------------------------------+
    //  | Silly status bar      | Notify: zyz | EYL | <- options status
    //  +-------------------------------------------+
    //       ^                  ^   ^
    //       +- status bar      |   +- notify window
    //                          |
    //                          +----- separator
    //
    //  the 'button bar' does not have to move or change size (except the
    //  width of the output window)
    //
    //  the 'script window' should fill all space between the bottom of
    //  the button bar and the top of the status bar.
    //
    //  the 'status bar' should sit on the bottom of the window and
    //  remain a constant height.
    //
    //  the 'notify window' should sit on the bottom of the window to the
    //  right of the status bar and remain a constant height.
    //
    //  the 'options status' window should sit in the bottom right corner
    //  and remain a constant width and height.
    //
    //  the 'separators' should be a visible separator between the status
    //  bar, notify window, and options status.
    //

    //
    //  first grab handles to all the windows we need to muck with...
    //
    hwndButton  = GetDlgItem(hwnd, IDD_APP_BTN_GO);
    hwndOutput  = GetDlgItem(hwnd, IDD_APP_TEXT_OUTPUT);
    hwndScript  = GetDlgItem(hwnd, IDD_APP_EDIT_SCRIPT);
    hwndStatus  = GetDlgItem(hwnd, IDD_APP_TEXT_STATUS);
    hwndNotify  = GetDlgItem(hwnd, IDD_APP_TEXT_NOTIFY);
    hwndOptions = GetDlgItem(hwnd, IDD_APP_TEXT_OPTIONS);

    //
    //  get the height of the button bar and status bar
    //
    GetClientRect(hwndButton, &rc);
    nHeightButton = (int)(rc.bottom - rc.top);
    nWidthButton  = (int)(rc.right - rc.left);

    //
    //  change width of output window..
    //
    GetClientRect(hwndStatus, &rc);
    nHeightStatus = (int)(rc.bottom - rc.top);

    n = nWidthButton * 6;
    m = (nWidth > (n + 2)) ? (nWidth - n - 2) : 0;
    rc.top = (nHeightButton - nHeightStatus) / 2;
    MoveWindow(hwndOutput, n, (int)rc.top, m, nHeightStatus, TRUE);

    //
    //  calculate the new height for the script window and move it to just
    //  below the bottom of the button bar and above the status bar (which
    //  is the height of the button bar)... note that we put the left and
    //  right edges of the script window outside of the client area because
    //  it looks better that way.
    //
    nHeightScript = nHeight - (nHeightButton * 2);
    if (nHeightScript < 0)
        nHeightScript = 0;

    MoveWindow(hwndScript, -1, nHeightButton, nWidth + 2, nHeightScript, TRUE);

    //
    //  put the status bar sitting on top of the bottom of the app window
    //  note that we center it in a space that is as high as the button
    //  bar so it looks better... it is assumed (correctly) that the button
    //  bar is taller than the status bar control.
    //
    //  ~                                           ~
    //  |                                           |
    //  +-------------------------------------------+
    //  | Silly status bar      | Notify: zyz | EYL |
    //  +-------------------------------------------+
    //  ^                       ^             ^     ^
    //  +------ min = 20 -------+-min = ~130 -+- 40-+
    //
    //  there is pad of 2 on the left and right of each text window (giving
    //  a separator a effective width of 5).
    //
    //
    nWidthNotify = (nWidth < (20 + 5 + 130 + 5 + 40)) ?
                        (nWidth - (20 + 5 + 5 + 40)) : 130;

    hwndSeparator = GetDlgItem(hwnd, IDD_APP_BOX_SEPARATOR1);
    MoveWindow(hwndSeparator, nWidth - nWidthNotify - 3 - 5 - 40,
                              nHeight - nHeightButton,
                              1, nHeightButton, TRUE);

    n = nHeightStatus + ((nHeightButton - nHeightStatus) / 2);
    MoveWindow(hwndStatus, 2, nHeight - n,
                           nWidth - nWidthNotify - 8 - 5 - 40,
                           nHeightStatus, TRUE);

    MoveWindow(hwndNotify, nWidth - nWidthNotify - 5 - 40, nHeight - n,
                           nWidthNotify - 2, nHeightStatus, TRUE);

    hwndSeparator = GetDlgItem(hwnd, IDD_APP_BOX_SEPARATOR2);
    MoveWindow(hwndSeparator, nWidth - 3 - 40,
                              nHeight - nHeightButton,
                              1, nHeightButton, TRUE);

    MoveWindow(hwndOptions, nWidth - 40, nHeight - n,
                           40 - 2, nHeightStatus, TRUE);

    //
    //  we processed the message..
    //
    return (0L);
} // AppSize()

//--------------------------------------------------------------------------;
//
//  LRESULT AppWndProc
//
//  Description:
//      This is the main application window procedure.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (LRESULT):
//      The return value depends on the message that is being processed.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

LRESULT FNEXPORT AppWndProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    LRESULT     lr;

    switch (uMsg)
    {
        case WM_CREATE:
            lr = HANDLE_WM_CREATE(hwnd, wParam, lParam, AppCreate);
            return (lr);

        case WM_WININICHANGE:
            HANDLE_WM_WININICHANGE(hwnd, wParam, lParam, AppWinIniChange);
            return (0L);

        case WM_INITMENUPOPUP:
            HANDLE_WM_INITMENUPOPUP(hwnd, wParam, lParam, AppInitMenuPopup);
            return (0L);

        case WM_COMMAND:
            lr = HANDLE_WM_COMMAND(hwnd, wParam, lParam, AppCommand);
            return (lr);

        case WM_DROPFILES:
            //
            //  some windowsx.h files have a screwed up message cracker for
            //  WM_DROPFILES. because this is a sample app, i don't want
            //  people having trouble with bogus windowsx.h files, so crack
            //  the message manually... you should use the message cracker
            //  if you know your windowsx.h file is good.
            //
            //  lr = HANDLE_WM_DROPFILES(hwnd, wParam, lParam, AppDropFiles);
            //
            lr = AppDropFiles(hwnd, (HDROP)wParam);
            return (lr);

        case WM_SIZE:
            //
            //  handle what we want for sizing, and then always call the
            //  default handler...
            //
            HANDLE_WM_SIZE(hwnd, wParam, lParam, AppSize);
            break;

        case WM_QUERYENDSESSION:
            lr = HANDLE_WM_QUERYENDSESSION(hwnd, wParam, lParam, AppQueryEndSession);
            return (lr);

        case WM_ENDSESSION:
            HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, AppEndSession);
            return (0L);

        case WM_CLOSE:
            HANDLE_WM_CLOSE(hwnd, wParam, lParam, AppClose);
            return (0L);

        case WM_DESTROY:
            PostQuitMessage(0);
            return (0L);

        case MM_MCINOTIFY:
            MciAppHandleNotify(hwnd, wParam, LOWORD(lParam));
            break;
    }

    return (DefWindowProc(hwnd, uMsg, wParam, lParam));
} // AppWndProc()

//==========================================================================;
//
//  Main entry and message dispatching code
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int WinMain
//
//  Description:
//      This function is called by the system as the initial entry point
//      for a Windows application.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPSTR pszCmdLine: Points to null-terminated unparsed command line.
//      This string is strictly ANSI regardless of whether the application
//      is built for Unicode. To get the Unicode equivalent call the
//      GetCommandLine() function (Win 32 only).
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (int):
//      Returns result from WM_QUIT message (in wParam of MSG structure) if
//      the application is able to enter its message loop. Returns 0 if
//      the application is not able to enter its message loop.
//
//  History:
//      11/ 8/92    created.
//
//--------------------------------------------------------------------------;

int PASCAL WinMain
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPSTR           pszCmdLine,
    int             nCmdShow
)
{
    int     nResult;
    HWND    hwnd;
    MSG     msg;

    //
    //  our documentation states that WinMain is supposed to return 0 if
    //  we do not enter our message loop--so assume the worst...
    //
    nResult = 0;

    //
    //  make our instance handle global for convenience..
    //
    ghinst = hinst;

    //
    //  init some stuff, create window, etc.. note the explicit cast of
    //  pszCmdLine--this is to mute a warning (and an ugly ifdef) when
    //  compiling for Unicode. see AppInit() for more details.
    //
    hwnd = AppInit(hinst, hinstPrev, (LPTSTR)pszCmdLine, nCmdShow);
    if (hwnd)
    {
        //
        //  dispatch messages
        //
        while (GetMessage(&msg, NULL, 0, 0))
        {
            //
            //  do all the special stuff required for this application
            //  when dispatching messages..
            //
            MciAppDispatchMessage(hwnd, &msg);
        }

        //
        //  return result of WM_QUIT message.
        //
        nResult = (int)msg.wParam;
    }

    //
    //  shut things down, clean up, etc.
    //
    nResult = AppExit(hinst, nResult);

    return (nResult);
} // WinMain()


README.TXT

The MCIPLAY samples is a simple video playback application that uses MCIWnd.


MCIPLAY.C

/*----------------------------------------------------------------------------*\
 *
 *  MCIPLAY:
 *
 *    Sample app showing the use of MCIWnd
 *
 *    MCIPLAY is a MDI aplication that demonstates the following:
 *
 *      - Using the MCIWnd window class to play MCI files.
 *
 *----------------------------------------------------------------------------*/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define  STRICT
#define  INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <vfw.h>

#include "mciplay.h"

#define BUFSIZE 260
#define MCIPLAY_APP_CLASS "MCIPlay_App"
#define MCIPLAY_DOC_CLASS MCIWND_WINDOW_CLASS

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

#define ISCHECKED(hwnd,id) (BOOL)(GetMenuState(GetMenu(hwnd), id, 0) & MF_CHECKED)

/*-------------------------------------------------------------------------*\
|                                                                          |
|   g l o b a l   v a r i a b l e s                                        |
|                                                                          |
\*------------------------------------------------------------------------*/

    HINSTANCE ghInstApp;        /* Instance handle */
    HACCEL ghAccelApp;
    HWND ghwndApp;          /* Handle to parent window */
    HWND ghwndMdi;          /* Handle to MCI client window */
    char gszBuffer[BUFSIZE];

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

LRESULT CALLBACK AppWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK AppAbout(HWND, UINT, WPARAM, LPARAM);

static BOOL AppInit(HINSTANCE, HINSTANCE, LPSTR, int);
static HWND mdiCreateDoc(LPSTR, LPSTR, LPARAM);
static HWND mdiCreateClient(HWND, HMENU);
static LRESULT mdiSendMessage(HWND, HWND, UINT, WPARAM, LPARAM);
static BOOL FormatFilterString(UINT, LPSTR);
static void MCI_OnCommand(HWND, int, HWND, UINT);
static void mdiFilterSendMessage(HWND, UINT, WPARAM, LPARAM);
static void MCI_OnInitMenuPopup(HWND, HMENU, int, BOOL);
static void MCI_OnSize(HWND, UINT, int, int);

/*----------------------------------------------------------------------------*\
|   AppAbout( hDlg, msg, wParam, lParam )                                      |
|                                                                              |
|   Description:                                                               |
|       This function handles messages belonging to the "About" dialog box.    |
|       The only message that it looks for is WM_COMMAND, indicating the use   |
|       has pressed the "OK" button.  When this happens, it takes down         |
|       the dialog box.                                                        |
|                                                                              |
|   Arguments:                                                                 |
|       hDlg            window handle of about dialog window                   |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if message has been processed, else FALSE                         |
|                                                                              |
\*----------------------------------------------------------------------------*/
BOOL CALLBACK AppAbout(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
    switch (msg) {
        case WM_COMMAND:
        EndDialog(hwnd,TRUE);
        return TRUE;
    }
    return FALSE;
}

/*----------------------------------------------------------------------------*\
|   AppInit ( hInstance, hPrevInstance )                                       |
|                                                                              |
|   Description:                                                               |
|       This is called when the application is first loaded into               |
|       memory.  It performs all initialization that doesn't need to be done   |
|       once per instance.                                                     |
|                                                                              |
|   Arguments:                                                                 |
|   hPrevInstance   instance handle of previous instance                       |
|       hInstance       instance handle of current instance                    |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if successful, FALSE if not                                       |
|                                                                              |
\*----------------------------------------------------------------------------*/
static BOOL AppInit(
HINSTANCE hInst,
HINSTANCE hPrev,
LPSTR szCmd,
int sw)
{
    WNDCLASS cls;
    WORD wVer;
    char szAppName[BUFSIZE];

    /* Save instance handle for DialogBox */
    ghInstApp = hInst;

    LoadString( ghInstApp, IDS_APPNAME, szAppName, BUFSIZE );

    /* first let's make sure we are running on 1.1 */
    wVer = HIWORD(VideoForWindowsVersion());
    if (wVer < 0x010a){
        char szTitle[BUFSIZE];
        /* oops, we are too old, blow out of here */
        LoadString( ghInstApp, IDS_APPERR, szTitle, BUFSIZE );
        LoadString( ghInstApp, IDS_VFWTOOOLD, gszBuffer, BUFSIZE );
        MessageBeep(MB_ICONHAND);
        MessageBox(NULL, gszBuffer, szTitle, MB_OK|MB_ICONSTOP);
        return FALSE;
    }

    ghAccelApp = LoadAccelerators(ghInstApp, "AppAccel");

    if (!hPrev) {
        cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
        cls.hIcon          = LoadIcon(hInst,"AppIcon");
        cls.lpszMenuName   = "AppMenu";
        cls.lpszClassName  = MCIPLAY_APP_CLASS;
        cls.hbrBackground  = (HBRUSH)(COLOR_APPWORKSPACE+1);
        cls.hInstance      = hInst;
        cls.style          = 0;
        cls.lpfnWndProc    = (WNDPROC)AppWndProc;
        cls.cbClsExtra     = 0;
        cls.cbWndExtra     = 0;

        if (!RegisterClass(&cls))
            return FALSE;
    }

    // !!! Not using MCIWndCreate, so we must init ourselves
    if (!MCIWndRegisterClass())
        return FALSE;

    ghwndApp = CreateWindow(
        MCIPLAY_APP_CLASS,
        szAppName,
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT,
        0,
        CW_USEDEFAULT,
        0,
        (HWND)NULL,   /* no parent */
        (HMENU)NULL,      /* use class menu */
        (HANDLE)hInst,     /* handle to window instance */
        (LPSTR)NULL);     /* no params to pass on */

    /* Make window visible according to the way the app is activated */
    ShowWindow(ghwndApp,sw);

    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInstance, hPrevInstance, lpszCmdLine, cmdShow )                  |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|       hInstance       instance handle of this instance of the app            |
|       hPrevInstance   instance handle of previous instance, NULL if first    |
|       lpszCmdLine     ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR szCmdLine,
int sw)
{
    MSG msg;

    if (!AppInit(hInstance,hPrevInstance,szCmdLine,sw))
        return FALSE;

    //Polling messages from event queue
    for (;;) {
        if (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)) {
            if (msg.message == WM_QUIT)
                break;

            if (ghAccelApp && ghwndApp && TranslateAccelerator(ghwndApp, ghAccelApp, &msg))
                continue;

            if(!TranslateMDISysAccel(ghwndMdi, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        } else {
            // idle time here, DONT BE A PIG!
            WaitMessage();
        }
    }
    return msg.wParam;
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

static HWND mdiCreateDoc(
LPSTR szClass,
LPSTR szTitle,
LPARAM l)
{
    MDICREATESTRUCT mdics;

    mdics.szClass   = szClass;
    mdics.szTitle   = szTitle;
    mdics.hOwner    = ghInstApp;
    mdics.x         = CW_USEDEFAULT;
    mdics.y         = 0;
    mdics.cx        = CW_USEDEFAULT;
    mdics.cy        = 0;
    mdics.style     = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | MCIWNDF_SHOWALL;
    mdics.lParam    = l;

    return FORWARD_WM_MDICREATE(ghwndMdi, (LPMDICREATESTRUCT)&mdics, SendMessage);
}

/*----------------------------------------------------------------------------*\
|   mdiCreateClient()                                                          |
|                                                                              |
|   Description:                                                               |
|                                                                              |
|   Arguments:                                                                 |
|                                                                              |
|   Returns:                                                                   |
|   HWND if successful, NULL otherwise                                         |
|                                                                              |
\*----------------------------------------------------------------------------*/
static HWND mdiCreateClient(
HWND hwndP,
HMENU hmenuWindow)
{
    CLIENTCREATESTRUCT ccs;

    ccs.hWindowMenu = hmenuWindow;
    ccs.idFirstChild = 100;

    return CreateWindow(
            "MDICLIENT",
            NULL,
            WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
            0,
            0,
            0,
            0,
            hwndP,
            0,
            ghInstApp,
            (LPVOID)&ccs);
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

static LRESULT mdiSendMessage(
HWND hwndMdi,
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
    if (hwnd == (HWND)-1) {
        for (hwnd = GetWindow(hwndMdi, GW_CHILD); hwnd;
                    hwnd = GetWindow(hwnd, GW_HWNDNEXT))
            SendMessage(hwnd, msg, wParam, lParam);
        return 0L;
    } else {
        if (hwnd == NULL)
            hwnd = FORWARD_WM_MDIGETACTIVE(hwndMdi, SendMessage);
        if (hwnd)
            return SendMessage(hwnd, msg, wParam, lParam);
    }
}

//----------------------------------------------------------------------------
// MCI_OnCommand - Handles WM_COMMAND
//----------------------------------------------------------------------------
static void MCI_OnCommand(
HWND hwnd,
int id,
HWND hwndCtl,
UINT codeNotify)
{
    HWND hwndNew;
    HWND hwndMovie = FORWARD_WM_MDIGETACTIVE(ghwndMdi, SendMessage);

    switch(id) {
        case MENU_ABOUT:
            DialogBox(ghInstApp,MAKEINTRESOURCE(ABOUTBOX),hwnd,AppAbout);
            break;

        case MENU_EXIT:
            PostMessage(hwnd,WM_CLOSE,0,0L);
            break;

        case MENU_CLOSE:
            PostMessage(hwndMovie, WM_CLOSE, 0, 0L);
            break;

        case MENU_NEW:
            LoadString( ghInstApp, IDS_UNTITLLED, gszBuffer, BUFSIZE );
            if (hwndNew = mdiCreateDoc(MCIPLAY_DOC_CLASS, gszBuffer, 0))
                SetFocus(hwndNew);
            break;

        case MENU_OPEN:
        {
            OPENFILENAME ofn;
            char szFileName[BUFSIZE];
            char szFilter[BUFSIZE];

            FormatFilterString( IDS_FILTERSTRING, szFilter );

            /* prompt user for file to open */
            ofn.lStructSize = sizeof(OPENFILENAME);
            ofn.hwndOwner = hwnd;
            ofn.hInstance = NULL;
            ofn.lpstrFilter = szFilter;
            ofn.lpstrCustomFilter = NULL;
            ofn.nMaxCustFilter = 0;
            ofn.nFilterIndex = 0;
            *szFileName = '\0';
            ofn.lpstrFile = szFileName;
            ofn.nMaxFile = BUFSIZE;
            ofn.lpstrFileTitle = NULL;
            ofn.nMaxFileTitle = 0;
            ofn.lpstrInitialDir = NULL;
            ofn.lpstrTitle = "Open";
            ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
            ofn.nFileOffset = 0;
            ofn.nFileExtension = 0;
            ofn.lpstrDefExt = NULL;
            ofn.lCustData = 0;
            ofn.lpfnHook = NULL;
            ofn.lpTemplateName = NULL;

            if (GetOpenFileName(&ofn))
                if (hwndNew = mdiCreateDoc(MCIPLAY_DOC_CLASS, 0, (LPARAM)(LPSTR)szFileName))
                    SetFocus(hwndNew);
        }
        break;

        case WM_MDITILE:
        case WM_MDICASCADE:
        case WM_MDIICONARRANGE:
            SendMessage(ghwndMdi, id, 0, 0);
            break;

        /* Movie Menu */
        case IDM_PLAY:
            MCIWndPlay(hwndMovie);
            break;

        case IDM_RPLAY:
            MCIWndPlayReverse(hwndMovie);
            break;

        case IDM_STOP:
            MCIWndStop(hwndMovie);
            break;

        case IDM_HOME:
            MCIWndHome(hwndMovie);
            break;

        case IDM_END:
            MCIWndEnd(hwndMovie);
            break;

        case IDM_STEP:
            MCIWndStep(hwndMovie, 1);
            break;

        case IDM_RSTEP:
            MCIWndStep(hwndMovie, -1);
            break;

        /* Styles POPUP */

        case IDM_SAutosizeWindow:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOAUTOSIZEWINDOW,
                ISCHECKED(hwnd,id) ? MCIWNDF_NOAUTOSIZEWINDOW : 0);
            break;

        case IDM_SAutosizeMovie:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOAUTOSIZEMOVIE,
                ISCHECKED(hwnd,id) ? MCIWNDF_NOAUTOSIZEMOVIE : 0);
            break;

        case IDM_SPlaybar:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOPLAYBAR,
                ISCHECKED(hwnd,id) ? MCIWNDF_NOPLAYBAR : 0);
            break;

        case IDM_SRecord:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_RECORD,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_RECORD);
            break;

        case IDM_SMenu:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOMENU,
                ISCHECKED(hwnd,id) ? MCIWNDF_NOMENU : 0);
            break;

        case IDM_SErrorDlg:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOERRORDLG,
                ISCHECKED(hwnd,id) ? MCIWNDF_NOERRORDLG : 0);
            break;

        case IDM_SShowName:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_SHOWNAME,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_SHOWNAME);
            break;

        case IDM_SShowMode:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_SHOWMODE,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_SHOWMODE);
            break;

        case IDM_SShowPos:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_SHOWPOS,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_SHOWPOS);
            break;

        case IDM_SNotifyMedia:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOTIFYMEDIA,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_NOTIFYMEDIA);
            break;

        case IDM_SNotifyMode:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOTIFYMODE,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_NOTIFYMODE);
            break;

        case IDM_SNotifyPos:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOTIFYPOS,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_NOTIFYPOS);
            break;

        case IDM_SNotifySize:
            MCIWndChangeStyles(hwndMovie, MCIWNDF_NOTIFYSIZE,
                ISCHECKED(hwnd,id) ? 0 : MCIWNDF_NOTIFYSIZE);
            break;

        default:
            FORWARD_WM_COMMAND(NULL, id, hwndCtl, codeNotify, mdiFilterSendMessage);
            break;
    }
    return;
}

/*----------------------------------------------------------------------------*/
static void mdiFilterSendMessage(
HWND    hwnd,
UINT    msg,
WPARAM  wParam,
LPARAM  lParam)
{
    mdiSendMessage(ghwndMdi, hwnd, msg, wParam, lParam);
}

//----------------------------------------------------------------------------
// MCI_OnInitMenuPopup - Handles WM_INITMENUPOPUP
//----------------------------------------------------------------------------
static void MCI_OnInitMenuPopup(
HWND hwnd,
HMENU hMenu,
int item,
BOOL fSystemMenu)
{
    HMENU hStylesMenu = GetSubMenu(GetSubMenu(GetMenu(hwnd), 1), 10);
    HMENU hFileMenu = GetSubMenu(GetMenu(hwnd), 0);
    HMENU hMovieMenu = GetSubMenu(GetMenu(hwnd), 1);
    HMENU hWindowMenu = GetSubMenu(GetMenu(hwnd), 2);
    HWND  hwndMovie = FORWARD_WM_MDIGETACTIVE(ghwndMdi, SendMessage);
    UINT f;

    // be sure this isn't the system menu
    if (fSystemMenu)
        return;

    //
    // Check the styles properly when styles is chosen
    // !!! Make sure position constants don't change!
    // Dont forget that some of these styles use multiple bits
    // in wStyles - therefore check carefully.
    if ((hMenu == hStylesMenu) && hwndMovie) {

        UINT  wStyles = MCIWndGetStyles(hwndMovie);

        CheckMenuItem(hStylesMenu, IDM_SAutosizeWindow,
            ((wStyles & MCIWNDF_NOAUTOSIZEWINDOW) == MCIWNDF_NOAUTOSIZEWINDOW) ? MF_UNCHECKED : MF_CHECKED);
        CheckMenuItem(hStylesMenu, IDM_SAutosizeMovie,
            ((wStyles & MCIWNDF_NOAUTOSIZEMOVIE) == MCIWNDF_NOAUTOSIZEMOVIE) ? MF_UNCHECKED : MF_CHECKED);
        CheckMenuItem(hStylesMenu, IDM_SPlaybar,
            ((wStyles & MCIWNDF_NOPLAYBAR) == MCIWNDF_NOPLAYBAR) ? MF_UNCHECKED : MF_CHECKED);
        CheckMenuItem(hStylesMenu, IDM_SRecord,
            ((wStyles & MCIWNDF_RECORD) == MCIWNDF_RECORD) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SMenu,
            ((wStyles & MCIWNDF_NOMENU) == MCIWNDF_NOMENU) ? MF_UNCHECKED :MF_CHECKED);
        CheckMenuItem(hStylesMenu, IDM_SErrorDlg,
            ((wStyles & MCIWNDF_NOERRORDLG) == MCIWNDF_NOERRORDLG) ? MF_UNCHECKED :MF_CHECKED);
        CheckMenuItem(hStylesMenu, IDM_SShowName,
            ((wStyles & MCIWNDF_SHOWNAME) == MCIWNDF_SHOWNAME) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SShowMode,
            ((wStyles & MCIWNDF_SHOWMODE) == MCIWNDF_SHOWMODE) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SShowPos,
            ((wStyles & MCIWNDF_SHOWPOS) == MCIWNDF_SHOWPOS) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SNotifyMedia,
            ((wStyles & MCIWNDF_NOTIFYMEDIA) == MCIWNDF_NOTIFYMEDIA) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SNotifyMode,
            ((wStyles & MCIWNDF_NOTIFYMODE) == MCIWNDF_NOTIFYMODE) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SNotifyPos,
            ((wStyles & MCIWNDF_NOTIFYPOS) == MCIWNDF_NOTIFYPOS) ? MF_CHECKED :MF_UNCHECKED);
        CheckMenuItem(hStylesMenu, IDM_SNotifySize,
            ((wStyles & MCIWNDF_NOTIFYSIZE) == MCIWNDF_NOTIFYSIZE) ? MF_CHECKED :MF_UNCHECKED);
    }

    //
    // Enable/Disable the stuff under the MOVIE popup
    // !!! Make sure position constants don't change!
    //
    else if (hMenu == hMovieMenu) {

        EnableMenuItem( hMenu, 10,
                        MF_BYPOSITION | (hwndMovie ? MF_ENABLED : MF_GRAYED) );

        if ( !hwndMovie
           || MCIWndGetMode(hwndMovie, NULL, 0) == MCI_MODE_NOT_READY) {

            EnableMenuItem( hFileMenu, MENU_CLOSE,
                            hwndMovie ? MF_ENABLED : MF_GRAYED );

            EnableMenuItem(hMenu, IDM_STOP, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_PLAY, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_RPLAY, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_HOME, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_END, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_STEP, MF_GRAYED);
            EnableMenuItem(hMenu, IDM_RSTEP, MF_GRAYED);

        } else {

            EnableMenuItem(hFileMenu, MENU_CLOSE, MF_ENABLED);
            f = MCIWndGetMode(hwndMovie, NULL, 0) != MCI_MODE_STOP;
            EnableMenuItem(hMenu, IDM_PLAY, !f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem(hMenu, IDM_RPLAY, !f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem(hMenu, IDM_STOP, f ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem(hMenu, IDM_HOME, MF_ENABLED);
            EnableMenuItem(hMenu, IDM_END,  MF_ENABLED);
            EnableMenuItem(hMenu, IDM_STEP, MF_ENABLED);
            EnableMenuItem(hMenu, IDM_RSTEP,MF_ENABLED);
        }
    }

    //
    // Enable/Disable the stuff under the WINDOW menu
    // !!! Make sure position constants don't change!
    //
    else if (hMenu == hWindowMenu) {

            EnableMenuItem(hMenu, 0,
                        MF_BYPOSITION | (hwndMovie ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hMenu, 1,
                        MF_BYPOSITION | (hwndMovie ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hMenu, 2,
                        MF_BYPOSITION | (hwndMovie ? MF_ENABLED : MF_GRAYED));
    }

    //
    // Enable/Disable the stuff under the FILE menu
    //
    else if (hMenu == hFileMenu) {

        EnableMenuItem( hFileMenu, MENU_CLOSE,
                        hwndMovie ? MF_ENABLED : MF_GRAYED);
    }

    FORWARD_WM_INITMENUPOPUP(NULL, hMenu, item,
                             fSystemMenu, mdiFilterSendMessage);
    return;
}

//----------------------------------------------------------------------------
// MCI_OnSize - Handles WM_SIZE
//----------------------------------------------------------------------------
static void MCI_OnSize(
HWND hwnd,
UINT state,
int cx,
int cy)
{
    MoveWindow(ghwndMdi,0,0,cx,cy,TRUE);
    return;
}

/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, msg, wParam, lParam )                                    |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|       hwnd            window handle for the parent window                    |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LRESULT CALLBACK AppWndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{

    switch (msg) {
       case WM_CREATE:
        ghwndMdi = mdiCreateClient(hwnd, GetSubMenu(GetMenu(hwnd), GetMenuItemCount(GetMenu(hwnd))-1));
        break;

        case WM_COMMAND:
            HANDLE_WM_COMMAND(hwnd, wParam, lParam, MCI_OnCommand);
            break;

    case WM_PALETTECHANGED:
        mdiSendMessage(ghwndMdi, (HWND)-1, msg, wParam, lParam);
        break;

    case WM_QUERYNEWPALETTE:
        return mdiSendMessage(ghwndMdi, NULL, msg, wParam, lParam);
            break;

        case WM_INITMENUPOPUP:
        HANDLE_WM_INITMENUPOPUP(hwnd, wParam, lParam, MCI_OnInitMenuPopup);
        break;

    case WM_SIZE:
        HANDLE_WM_SIZE(hwnd, wParam, lParam, MCI_OnSize);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return (0);
    }
    return DefFrameProc(hwnd,ghwndMdi,msg,wParam,lParam);
}

/*--------------------------------------------------------------+
| FormatFilterString                                            |
|                                                               |
+--------------------------------------------------------------*/
static BOOL FormatFilterString(
UINT uID,
LPSTR lpszString )
{
    int nLength, nCtr = 0;
    char chWildCard;

    *lpszString = 0;

    nLength = LoadString( ghInstApp, uID, lpszString, BUFSIZE );

    chWildCard = lpszString[nLength-1];

    while( lpszString[nCtr] ) {
        if( lpszString[nCtr] == chWildCard )
            lpszString[nCtr] = 0;
        nCtr++;
    }

    return TRUE;
}


MCIPLAY.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* dialogs */
#define ABOUTBOX        1

/* Menu Items */

#define MENU_ABOUT1000
#define MENU_EXIT1001
#define MENU_NEW1002
#define MENU_CLOSE1003
#define MENU_OPEN1004

/* Movie menu */
#define IDM_STOP        9
#define IDM_PLAY10
#define IDM_HOME11
#define IDM_END 12
#define IDM_STEP13
#define IDM_RSTEP14
#define IDM_RPLAY15
#define IDM_PAUSE       16

#define IDM_SAutosizeWindow17
#define IDM_SAutosizeMovie18
#define IDM_SPlaybar19
#define IDM_SMenu20
#define IDM_SShowName21
#define IDM_SShowMode22
#define IDM_SShowPos23
#define IDM_SNotifyMedia24
#define IDM_SNotifyMode25
#define IDM_SNotifyPos26
#define IDM_SNotifySize27
#define IDM_SRecord28
#define IDM_SErrorDlg29

//String Table ID's

#define IDS_APPNAME1
#define IDS_APPERR2
#define IDS_VFWTOOOLD3
#define IDS_UNTITLLED4
#define IDS_NOOPEN5
#define IDS_FILTERSTRING6


README.TXT

The MCIPUZZL sample lets you make a 15-square puzzle of a playing video sequence.
Shows how to use installable draw procedures.


MCIPUZZL.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*----------------------------------------------------------------------------*\
 *
 *  MCIPUZZLE:
 *
 *    Sample app showing the use of MCIWnd, installable draw procedure
 *
 *    MCIPUZZLE is an application that demonstrates the following:
 *
 *      - Using the MCIWnd window class to play MCI files.
 *
 *      - Using an installable draw procedure with MCIAVI
 *
 *      - Implementing a version of the classic "15" puzzle
 *
 *----------------------------------------------------------------------------*/

#define  STRICT
#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <digitalv.h>
#include <mciavi.h>
#include <vfw.h>

#include "mcipuzzl.h"
#include "puzzle.h"
#include "puzzproc.h"

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

#define BUFSIZE 260
#define MCIPLAY_APP_CLASS "MCIPuzzle_App"
#define MCIPLAY_DOC_CLASS MCIWND_WINDOW_CLASS
typedef MCI_DGV_SETVIDEO_PARMS * LP_MCI_DGV;

/*-------------------------------------------------------------------------*\
|                                                                          |
|   g l o b a l   v a r i a b l e s                                        |
|                                                                          |
\*------------------------------------------------------------------------*/

static  HINSTANCE ghInstApp;            /* Instance handle */
static  HACCEL    ghAccelApp;
static  HWND      ghwndApp;             /* Handle to parent window */
static  HWND      ghwndMCI;             /* Handle to MCI client window */
static  char      gszBuffer[BUFSIZE];
static  DWORD     gdwHook = 0L;

PUZZLE            gPuzzle;

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

LRESULT CALLBACK AppWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK AppAbout(HWND, UINT, WPARAM, LPARAM);

static BOOL AppInit(HINSTANCE, HINSTANCE, LPSTR, int);
static BOOL FormatFilterString(UINT, LPSTR);
static void MCI_OnCommand(HWND, int, HWND, UINT);
static void MCI_OnInitMenuPopup(HWND, HMENU, int, BOOL);
static void MCI_OnSize(HWND, UINT, int, int);
static void MCI_OnParentNotify(HWND, UINT, HWND, int);
static void Handle_NotifyMedia(HWND,WPARAM);
static void Handle_NotifySize(HWND,WPARAM);

/*----------------------------------------------------------------------------*\
|   AppAbout( hDlg, msg, wParam, lParam )                                      |
|                                                                              |
|   Description:                                                               |
|       This function handles messages belonging to the "About" dialog box.    |
|       The only message that it looks for is WM_COMMAND, indicating the user  |
|       has pressed the "OK" button.  When this happens, it takes down         |
|       the dialog box.                                                        |
|                                                                              |
|   Arguments:                                                                 |
|       hDlg            window handle of about dialog window                   |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if message has been processed, else FALSE                         |
|                                                                              |
\*----------------------------------------------------------------------------*/
BOOL CALLBACK AppAbout(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
        switch (msg) {
                case WM_COMMAND:
                EndDialog(hwnd,TRUE);
                return TRUE;
        }
        return FALSE;
}

/*----------------------------------------------------------------------------*\
|   AppInit ( hInstance, hPrevInstance )                                       |
|                                                                              |
|   Description:                                                               |
|       This is called when the application is first loaded into               |
|       memory.  It performs all initialization that doesn't need to be done   |
|       once per instance.                                                     |
|                                                                              |
|   Arguments:                                                                 |
|       hPrevInstance   instance handle of previous instance                   |
|       hInstance       instance handle of current instance                    |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if successful, FALSE if not                                       |
|                                                                              |
\*----------------------------------------------------------------------------*/
static BOOL AppInit(
HINSTANCE hInst,
HINSTANCE hPrev,
LPSTR szCmd,
int sw)
{
        WNDCLASS cls;
        WORD wVer;
        char szAppName[BUFSIZE];

        /* Save instance handle for DialogBox */
        ghInstApp = hInst;

        LoadString( ghInstApp, IDS_APPNAME, szAppName, BUFSIZE );

        /* first let's make sure we are running on 1.1 */
        wVer = HIWORD(VideoForWindowsVersion());
        if (wVer < 0x010a){
                char szTitle[BUFSIZE];
                /* oops, we are too old, blow out of here */
                LoadString( ghInstApp, IDS_APPERR, szTitle, BUFSIZE );
                LoadString( ghInstApp, IDS_VFWTOOOLD, gszBuffer, BUFSIZE );
                MessageBeep(MB_ICONHAND);
                MessageBox(NULL, gszBuffer, szTitle, MB_OK|MB_ICONSTOP);
                return FALSE;
        }

        ghAccelApp = LoadAccelerators(ghInstApp, "AppAccel");

        if (!hPrev) {
                cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
                cls.hIcon          = LoadIcon(hInst,"AppIcon");
                cls.lpszMenuName   = "AppMenu";
                cls.lpszClassName  = MCIPLAY_APP_CLASS;
                cls.hbrBackground  = (HBRUSH)COLOR_APPWORKSPACE+1;
                cls.hInstance      = hInst;
                cls.style          = 0;
                cls.lpfnWndProc    = (WNDPROC)AppWndProc;
                cls.cbClsExtra     = 0;
                cls.cbWndExtra     = 0;

                if (!RegisterClass(&cls))
                        return FALSE;
        }

        ghwndApp = CreateWindow(
                MCIPLAY_APP_CLASS,
                szAppName,
                WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                CW_USEDEFAULT,
                0,
                160,
                120,
                (HWND)NULL,       /* no parent */
                (HMENU)NULL,      /* use class menu */
                (HANDLE)hInst,     /* handle to window instance */
                (LPSTR)NULL);     /* no params to pass on */

        InitPuzzle(&gPuzzle, TRUE /* FALSE to not scramble */);

        /* Make window visible according to the way the app is activated */
        ShowWindow(ghwndApp,sw);

        return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInstance, hPrevInstance, lpszCmdLine, cmdShow )                  |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|       hInstance       instance handle of this instance of the app            |
|       hPrevInstance   instance handle of previous instance, NULL if first    |
|       lpszCmdLine     ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR szCmdLine,
int sw)
{
        MSG msg;

        if (!AppInit(hInstance,hPrevInstance,szCmdLine,sw))
                return FALSE;

        //Polling messages from event queue
        for (;;) {
                if (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)) {
                        if (msg.message == WM_QUIT)
                                break;

                        if (ghAccelApp && ghwndApp && TranslateAccelerator(ghwndApp, ghAccelApp, &msg))
                                continue;

                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                        } else {
                        // idle time here, DONT BE A PIG!
                        WaitMessage();
                }
        }
        return msg.wParam;
}

//----------------------------------------------------------------------------
// MCI_OnCommand - Handles WM_COMMAND
//----------------------------------------------------------------------------
static void MCI_OnCommand(
HWND hwnd,
int id,
HWND hwndCtl,
UINT codeNotify)
{
        switch(id) {
                case MENU_ABOUT:
                DialogBox(ghInstApp,MAKEINTRESOURCE(ABOUTBOX),hwnd,AppAbout);
                break;

                case MENU_EXIT:
                PostMessage(hwnd,WM_CLOSE,0,0L);
                break;

                case MENU_CLOSE:
                PostMessage(ghwndMCI, WM_CLOSE, 0, 0L);
                break;

                case MENU_OPEN:
                {
                        OPENFILENAME ofn;
                        char szFileName[BUFSIZE];
                        char szFilter[BUFSIZE];

                        FormatFilterString( IDS_FILTERSTRING, szFilter );

                        /* prompt user for file to open */
                        ofn.lStructSize = sizeof(OPENFILENAME);
                        ofn.hwndOwner = hwnd;
                        ofn.hInstance = NULL;
                        ofn.lpstrFilter = szFilter;
                        ofn.lpstrCustomFilter = NULL;
                        ofn.nMaxCustFilter = 0;
                        ofn.nFilterIndex = 0;
                        *szFileName = '\0';
                        ofn.lpstrFile = szFileName;
                        ofn.nMaxFile = BUFSIZE;
                        ofn.lpstrFileTitle = NULL;
                        ofn.nMaxFileTitle = 0;
                        ofn.lpstrInitialDir = NULL;
                        ofn.lpstrTitle = "Open";
                        ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
                        ofn.nFileOffset = 0;
                        ofn.nFileExtension = 0;
                        ofn.lpstrDefExt = NULL;
                        ofn.lCustData = 0;
                        ofn.lpfnHook = NULL;
                        ofn.lpTemplateName = NULL;

                        if (GetOpenFileName(&ofn)) {
                                MCIWndOpen(ghwndMCI, (LPARAM)(LPSTR)szFileName, 0);
                                InitPuzzle(&gPuzzle, TRUE /* FALSE to not scramble */);
                        }
                }
                break;

                /* Movie Menu (for use by accelerators) */
                case IDM_PLAY:
                        MCIWndPlay(ghwndMCI);
                        break;

                case IDM_RPLAY:
                        MCIWndPlayReverse(ghwndMCI);
                        break;

      // Solve or scramble the puzzle, as appropriate
                case IDM_SOLVE:
                case IDM_SCRAMBLE:
                    InitPuzzle(&gPuzzle, id == IDM_SCRAMBLE);
                    InvalidateRect(ghwndMCI, NULL, FALSE);
                    break;
        }
        return;
}

//----------------------------------------------------------------------------
// MCI_OnSize - Handles WM_SIZE
//----------------------------------------------------------------------------
static void MCI_OnSize(
HWND hwnd,
UINT state,
int cx,
int cy)
{
if (!IsIconic(hwnd)) {
        MoveWindow(ghwndMCI,0,0,cx,cy,TRUE);
        }
}
//----------------------------------------------------------------------------
// MCI_OnParentNotify - Handles WM_PARENTNOTIFY
//----------------------------------------------------------------------------
static void MCI_OnParentNotify(
HWND hwnd,
UINT msg,
HWND hwndChild,
int idChild)
{
    if (msg == WM_LBUTTONDOWN && ghwndMCI) {
        RECT    rc;
        POINT   pt;

        pt.x = (int)LOWORD((DWORD)hwndChild);
        pt.y = (int)HIWORD((DWORD)hwndChild);

        ClientToScreen(hwnd, &pt);

        MCIWndGetDest(ghwndMCI, &rc);

        if (rc.bottom != rc.top && rc.right != rc.left) {
            ScreenToClient(ghwndMCI, &pt);

            ClickPuzzle(&gPuzzle,
                    (pt.x - rc.left) * PSIZE / (rc.right - rc.left),
                    (pt.y - rc.top) * PSIZE / (rc.bottom - rc.top));

            InvalidateRect(ghwndMCI, &rc, FALSE);
        }
    }
    return;
}
/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, msg, wParam, lParam )                                    |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|       hwnd            window handle for the parent window                    |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LRESULT CALLBACK AppWndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{

    switch (msg) {
       case WM_CREATE:
                ghwndMCI = MCIWndCreate(hwnd, ghInstApp, WS_CHILD | WS_VISIBLE |
                        MCIWNDF_NOTIFYMEDIA | MCIWNDF_NOTIFYSIZE | MCIWNDF_NOMENU, 0);
                break;

        case WM_COMMAND:
                HANDLE_WM_COMMAND(hwnd, wParam, lParam, MCI_OnCommand);
                break;

        case WM_PALETTECHANGED:
        case WM_QUERYNEWPALETTE:
        case WM_ACTIVATE:
                // Forward palette-related messages through to the MCIWnd,
                // so it can do the right thing.
                if (ghwndMCI)
                        return SendMessage(ghwndMCI, msg, wParam, lParam);
                break;

        case WM_SIZE:
                HANDLE_WM_SIZE(hwnd, wParam, lParam, MCI_OnSize);
                break;

        case WM_DESTROY:
                PostQuitMessage(0);
                return (0);
                break;

       case WM_PARENTNOTIFY:
                HANDLE_WM_PARENTNOTIFY(hwnd, wParam, lParam, MCI_OnParentNotify);
                break;

       case MCIWNDM_NOTIFYMEDIA:
                Handle_NotifyMedia(hwnd, wParam);
                break;

       case MCIWNDM_NOTIFYSIZE:
                Handle_NotifySize(hwnd, wParam);
                break;
        }

        return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*--------------------------------------------------------------+
| Handle_NotifyMedia                                            |
|                                                               |
+--------------------------------------------------------------*/
static void Handle_NotifyMedia(
HWND hwnd,
WPARAM wParam)
{
        MCI_DGV_SETVIDEO_PARMS dgv;
        DWORD dw;
        UINT uDevice;

        if (ghwndMCI == 0)
                ghwndMCI = (HWND) wParam;

        if (!gdwHook)
                gdwHook = (DWORD)ICAVIDrawProc;

        dgv.dwValue = gdwHook;

        dgv.dwItem = MCI_AVI_SETVIDEO_DRAW_PROCEDURE;

        uDevice = MCIWndGetDeviceID(ghwndMCI);
        if (uDevice) {
                dw = mciSendCommand(uDevice, MCI_SETVIDEO,
                               MCI_DGV_SETVIDEO_ITEM | MCI_DGV_SETVIDEO_VALUE,
                               (DWORD)(LP_MCI_DGV)&dgv);

                if (dw != 0) {
                        char szTitle[BUFSIZE];

                        LoadString( ghInstApp, IDS_APPERR, szTitle, BUFSIZE );
                        LoadString( ghInstApp, IDS_MCIERROR, gszBuffer, BUFSIZE );

                        MessageBox(hwnd, gszBuffer, szTitle, MB_OK | MB_ICONHAND);
                }
        }
        return;
}

/*--------------------------------------------------------------+
| Handle_NotifySize                                             |
|                                                               |
+--------------------------------------------------------------*/
static void Handle_NotifySize(
HWND hwnd,
WPARAM wParam)
{
RECT rc;

if (!IsIconic(hwnd)) {
        if (ghwndMCI == 0)
                ghwndMCI = (HWND) wParam;

        GetWindowRect(ghwndMCI, &rc);
        AdjustWindowRect(&rc, GetWindowLong(hwnd, GWL_STYLE), TRUE);
        SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left,
rc.bottom - rc.top,
                SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);
}
}
/*--------------------------------------------------------------+
| FormatFilterString                                            |
|                                                               |
+--------------------------------------------------------------*/
static BOOL FormatFilterString(
UINT uID,
LPSTR lpszString )
{
        int nLength, nCtr = 0;
        char chWildCard;

        *lpszString = 0;

        nLength = LoadString( ghInstApp, uID, lpszString, BUFSIZE );

        chWildCard = lpszString[nLength-1];

        while( lpszString[nCtr] ) {
                if( lpszString[nCtr] == chWildCard )
                        lpszString[nCtr] = 0;
                nCtr++;
        }

        return TRUE;
}


MCIPUZZL.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* dialogs */
#define ABOUTBOX        1

/* Menu Items */

#define MENU_ABOUT1000
#define MENU_EXIT1001
#define MENU_NEW1002
#define MENU_CLOSE1003
#define MENU_CLOSEALL1004
#define MENU_OPEN1005

#define IDM_PLAY10
#define IDM_RPLAY15

#define IDM_SOLVE95
#define IDM_SCRAMBLE96

//String Table ID's

#define IDS_APPNAME1
#define IDS_APPERR2
#define IDS_VFWTOOOLD3
#define IDS_MCIERROR4
#define IDS_NOOPEN5
#define IDS_FILTERSTRING6


PUZZLE.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define  STRICT
#include <windows.h>
#include <mmsystem.h>
#include <stdlib.h>
#include "puzzle.h"

#define MEM_COPY( hpd, hps, size ) memcpy( hpd, hps, size )
#define DATA_PTR BYTE *

#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (DWORD)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)

//
// Initialize the puzzle, and optionally simulate 1000 clicks on the puzzle
//
void InitPuzzle(
LPPUZZLE p,
BOOL fScramble)
{
        int i,j;

        // Set the puzzle to a "solved" state
        for (i = 0; i < PSIZE; i++)
                for (j = 0; j < PSIZE; j++)
                        p->a[i][j] = i + j * PSIZE;

        // Put the "hole" in the lower right corner.
        p->a[PSIZE-1][PSIZE-1] = -1;
        p->hx = PSIZE - 1;
        p->hy = PSIZE - 1;

        if(fScramble) {
                // Make things really be random
                srand((unsigned int) timeGetTime());

                for (i = 0; i < 1000; i++) {
                        // Click on a random square
                        int r = rand() % PSIZE;
                        int s = rand() % PSIZE;
                        ClickPuzzle(p, r, s);
                }
        }
}

//
// Given a puzzle, and x & y in puzzle coordinates, move squares around
// or not as appropriate, given how such puzzles work.
//
void ClickPuzzle(
LPPUZZLE p,
int x,
int y)
{
        int i;

        if (x < 0 || x >= PSIZE)
                return;

        if (y < 0 || y >= PSIZE)
                return;

        if (x == p->hx) {
                if (y < p->hy) {
                        for (i = p->hy; i > y; i--)
                                p->a[x][i] = p->a[x][i-1];
                } else if (y > p->hy) {
                        for (i = p->hy; i < y; i++)
                                p->a[x][i] = p->a[x][i+1];
                }
                p->hy = y;
                p->a[x][y] = -1;
        } else if (y == p->hy) {
                if (x < p->hx) {
                        for (i = p->hx; i > x; i--)
                                p->a[i][y] = p->a[i-1][y];
                } else if (x > p->hx) {
                        for (i = p->hx; i < x; i++)
                                p->a[i][y] = p->a[i+1][y];
                }
                p->hx = x;
                p->a[x][y] = -1;
        }

        // We could potentially see if the puzzle was solved here.
        // If we do that, the prototype should change to
        // BOOL ClickPuzzle(LPPUZZLE p, int x, int y, BOOL fCheckSolved)
        // where we would pass TRUE for fCheckSolved if the call was
        // a result of the user really clicking, and not a call from
        // InitPuzzle() or something....
}

//
// Given a puzzle, map the input picture to the output picture with squares
// rearranged.
//
// Works on any RGB DIB.  Doesn't work on bitmaps, probably, so could be a
// problem with Todd's new DrawDib.
//
void MixPicture(
LPPUZZLE p,
LPBITMAPINFOHEADER lpbi,
LPBYTE lpIn,
LPBYTE lpOut)
{
        int i,j;
        int y;
        char achHack[1024] = {0};
        int dx = ((int) lpbi->biWidth / PSIZE) * ((int) lpbi->biBitCount / 8);
        int dy = (int) lpbi->biHeight / PSIZE;
        LONG lRowBytes = DIBWIDTHBYTES(*lpbi);
        DATA_PTR lpI;
        DATA_PTR lpO;

        for (i = 0; i < PSIZE; i++) {
                for (j = 0; j < PSIZE; j++) {
                        // Get pointer to square we're copying into
                        lpO = (DATA_PTR) lpOut + (PSIZE - 1 - j) * dy * lRowBytes + dx * i;

                        if (p->a[i][j] >= 0) {
                                // Get pointer to square we're copying from
                                lpI = (DATA_PTR) lpIn +
                                                (PSIZE - 1 - (p->a[i][j] / PSIZE)) * dy * lRowBytes +
                                                dx * (p->a[i][j] % PSIZE);

                                // do the copy
                                for (y = 0; y < dy; y++) {
                                        MEM_COPY(lpO, lpI, dx);
                                        lpO += lRowBytes;
                                        lpI += lRowBytes;
                                }
                        } else {
                                // clear the square to zeroes
                                for (y = 0; y < dy; y++) {
                                        MEM_COPY(lpO, achHack, dx);
                                        lpO += lRowBytes;
                                }
                        }
                }
        }
}


PUZZLE.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define PSIZE   4       // Puzzle is 4x4

typedef struct {
    int a[PSIZE][PSIZE];        // square # at position [x][y]
    int hx;                     // coordinates of "hole"
    int hy;
} PUZZLE;

typedef PUZZLE * LPPUZZLE;

void InitPuzzle(LPPUZZLE, BOOL);
void ClickPuzzle(LPPUZZLE, int, int);
void MixPicture(LPPUZZLE, LPBITMAPINFOHEADER, LPBYTE, LPBYTE);


PUZZPROC.C

/****************************************************************************
 *
 *  PUZZPROC.C
 *
 *  Modification of standard AVI drawing handler.
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define  STRICT
#define  INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdlib.h>
#include <vfw.h>

#include "puzzproc.h"
#include "puzzle.h"

#define LOCAL  __stdcall
#define SZCODE char

static SZCODE szDescription[] = "Microsoft Puzzle Draw handler";
static SZCODE szName[]        = "MS Puzzle";

#define FOURCC_AVIDraw          mmioFOURCC('P','U','Z','Z')
#define VERSION_AVIDraw         0x00010000      // 1.00

extern PUZZLE   gPuzzle;

/***************************************************************************
 ***************************************************************************/

typedef struct {
    HDRAWDIB            hdd;
    HDC                 hdc;            // HDC to draw to
    int                 xDst;           // destination rectangle
    int                 yDst;
    int                 dxDst;
    int                 dyDst;
    int                 xSrc;           // source rectangle
    int                 ySrc;
    int                 dxSrc;
    int                 dySrc;
    LPBYTE              lpBuffer;
} INSTINFO;

/***************************************************************************
 ***************************************************************************/

typedef ICOPEN *                LPICOPEN;
typedef ICINFO *                LPICINFO;
typedef ICDRAW *                LPICDRAW;
typedef ICDRAWSUGGEST *         LPICDRAWSUGGEST;
typedef ICDRAWBEGIN *           LPICDRAWBEGIN;
typedef INSTINFO *              LPINSTINFO;

/***************************************************************************
 ***************************************************************************/

// static stuff in this file.
static LONG LOCAL AVIDrawOpen(LPICOPEN);
static LONG LOCAL AVIDrawClose(LPINSTINFO);
static LONG LOCAL AVIDrawGetInfo(LPICINFO, LONG);
static LONG LOCAL AVIDrawQuery(LPINSTINFO, LPBITMAPINFOHEADER);
static LONG LOCAL AVIDrawSuggestFormat(LPINSTINFO, LPICDRAWSUGGEST, LONG);
static LONG LOCAL AVIDrawBegin(LPINSTINFO, LPICDRAWBEGIN, LONG);
static LONG LOCAL AVIDraw(LPINSTINFO, LPICDRAW, LONG);
static LONG LOCAL AVIDrawEnd(LPINSTINFO);
static LONG LOCAL AVIDrawChangePalette(LPINSTINFO, LPBITMAPINFOHEADER);

/***************************************************************************
 ***************************************************************************/

LONG CALLBACK ICAVIDrawProc(
DWORD id,
HDRVR hDriver,
UINT uiMessage,
LPARAM lParam1,
LPARAM lParam2)
{
        LPINSTINFO pi = (LPINSTINFO)id;

        switch (uiMessage) {

                case DRV_LOAD:
                case DRV_FREE:
                        return 1L;

                //open
                case DRV_OPEN:
                        if (lParam2 == 0L)
                                return 1L;
                        else
                                return AVIDrawOpen((LPICOPEN)lParam2);

                //close
                case DRV_CLOSE:
                        return AVIDrawClose(pi);

                //Configure and Info messages
                case DRV_QUERYCONFIGURE:    // configuration from drivers applet
                        return 0;

                case DRV_CONFIGURE:
                        return 1;

                case ICM_CONFIGURE:
                case ICM_ABOUT:
                        return ICERR_UNSUPPORTED;

                case ICM_GETINFO:
                        return AVIDrawGetInfo((LPICINFO)lParam1, lParam2);

                //state messages
                case ICM_GETSTATE:
                case ICM_SETSTATE:
                        return 0L;

                //draw messages
                case ICM_DRAW_QUERY:
                        return AVIDrawQuery(pi, (LPBITMAPINFOHEADER)lParam1);

                case ICM_DRAW_SUGGESTFORMAT:
                        return AVIDrawSuggestFormat(pi, (LPICDRAWSUGGEST) lParam1, lParam2);

                case ICM_DRAW_BEGIN:
                        return AVIDrawBegin(pi, (LPICDRAWBEGIN) lParam1, lParam2);

                case ICM_DRAW_REALIZE:
                        pi->hdc = (HDC) lParam1;
                        if (!pi->hdc || !pi->hdd)
                                break;
                        return DrawDibRealize(pi->hdd, pi->hdc, (BOOL) lParam2);

                case ICM_DRAW_GET_PALETTE:
                        if (!pi->hdd)
                                break;
                        return (LONG) (UINT) DrawDibGetPalette(pi->hdd);

                case ICM_DRAW:
                        return AVIDraw(pi, (LPICDRAW)lParam1, lParam2);

                case ICM_DRAW_CHANGEPALETTE:
                        return AVIDrawChangePalette(pi, (LPBITMAPINFOHEADER) lParam1);

                case ICM_DRAW_END:
                        return AVIDrawEnd(pi);

                //standard driver messages
                case DRV_DISABLE:
                case DRV_ENABLE:
                        return 1L;

                case DRV_INSTALL:
                case DRV_REMOVE:
                        return 1L;
        }
        if (uiMessage < DRV_USER)
                return DefDriverProc(id,hDriver,uiMessage,lParam1,lParam2);
        else
                return ICERR_UNSUPPORTED;
}
/*****************************************************************************
 *
 * AVIDrawOpen() is called from the DRV_OPEN message
 *
 ****************************************************************************/

static LONG LOCAL AVIDrawOpen(
LPICOPEN icopen)
{
        LPINSTINFO pinst;

        // refuse to open if we are not being opened as a video draw device
        if (icopen->fccType != streamtypeVIDEO)
                return 0L;

        if (icopen->dwFlags == ICMODE_COMPRESS)
                return 0L;

        if (icopen->dwFlags == ICMODE_DECOMPRESS)
                return 0L;

        pinst = (LPINSTINFO)GlobalAllocPtr(GHND, sizeof(INSTINFO));

        if (!pinst) {
                icopen->dwError = ICERR_MEMORY;
                return 0L;
        }

        // init structure
        pinst->hdd = DrawDibOpen();

        // return success.
        icopen->dwError = ICERR_OK;

        return (LONG)pinst;
}
/*****************************************************************************
 *
 * AVIDrawClose() is called on the DRV_CLOSE message.
 *
 ****************************************************************************/
static LONG LOCAL AVIDrawClose(
LPINSTINFO pi)
{
        if (pi->hdd)
                DrawDibClose(pi->hdd);

        if (pi->lpBuffer)
                GlobalFreePtr(pi->lpBuffer);

        GlobalFreePtr(pi);
        return 1L;
}
/*****************************************************************************
 *
 * AVIDrawGetInfo() implements the ICM_GETINFO message
 *
 ****************************************************************************/
static LONG LOCAL AVIDrawGetInfo(
LPICINFO icinfo,
LONG lSize)
{
        if (icinfo == NULL)
                return sizeof(ICINFO);

        if (lSize < sizeof(ICINFO))
                return 0L;

        icinfo->dwSize          = sizeof(ICINFO);
        icinfo->fccType         = ICTYPE_VIDEO;
        icinfo->fccHandler      = FOURCC_AVIDraw;
        icinfo->dwFlags         = VIDCF_DRAW;
        icinfo->dwVersion       = VERSION_AVIDraw;
        icinfo->dwVersionICM    = ICVERSION;
        MultiByteToWideChar(CP_ACP, 0, szDescription, -1, icinfo->szDescription, sizeof(icinfo->szDescription));
        MultiByteToWideChar(CP_ACP, 0, szName, -1, icinfo->szDescription, sizeof(icinfo->szDescription));
        return sizeof(ICINFO);
}
/*****************************************************************************
 *
 * AVIDrawQuery() implements ICM_DRAW_QUERY
 *
 ****************************************************************************/
static LONG LOCAL AVIDrawQuery(
LPINSTINFO pi,
LPBITMAPINFOHEADER lpbiIn)
{
        // determine if the input DIB data is in a format we like.
        if (lpbiIn == NULL)
                return ICERR_BADFORMAT;

        // determine if the input DIB data is in a format we like.
        if (lpbiIn->biCompression != BI_RGB)
                return ICERR_BADFORMAT;

        return ICERR_OK;
}
/*****************************************************************************
 *
 * AVIDrawSuggestFormat() implements ICM_DRAW_SUGGESTFORMAT
 *
 ****************************************************************************/

static LONG LOCAL AVIDrawSuggestFormat(
LPINSTINFO pi,
LPICDRAWSUGGEST lpicd,
LONG cbicd)
{
        HIC hic;
        LONG l;

        if (lpicd->lpbiSuggest == NULL)
                return sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);

        // Call COMPMAN to get a good format to display data in....
        hic = ICGetDisplayFormat(NULL, lpicd->lpbiIn, lpicd->lpbiSuggest,
                                                0, lpicd->dxDst, lpicd->dyDst);

        if (hic)
                ICClose(hic);

        l = lpicd->lpbiSuggest->biClrUsed;

        if (lpicd->lpbiSuggest)
                if (lpicd->lpbiSuggest->biCompression == BI_RLE8)
                        lpicd->lpbiSuggest->biCompression = BI_RGB;

        // !!! Should check this format here to make sure it's RGB...
        // !!! If not, we could force it to 8-bit....

        l = l * sizeof(RGBQUAD);
        return l + sizeof(BITMAPINFOHEADER);
}

/*****************************************************************************
 *
 * AVIDrawBegin() implements ICM_DRAW_BEGIN
 *
 ****************************************************************************/

static LONG LOCAL AVIDrawBegin(
LPINSTINFO pi,
LPICDRAWBEGIN lpicd,
LONG cbicd)
{
        LONG l = AVIDrawQuery(pi, lpicd->lpbi);

        if ((l != 0) || (lpicd->dwFlags & ICDRAW_QUERY))
                return l;

        // Copy over whatever we want to remember
        pi->hdc = lpicd->hdc;
        pi->xDst = lpicd->xDst;
        pi->yDst = lpicd->yDst;
        pi->dxDst = lpicd->dxDst;
        pi->dyDst = lpicd->dyDst;
        pi->xSrc = lpicd->xSrc;
        pi->ySrc = lpicd->ySrc;
        pi->dxSrc = lpicd->dxSrc;
        pi->dySrc = lpicd->dySrc;

        SetStretchBltMode(pi->hdc, COLORONCOLOR);

        if (!DrawDibBegin(
                        pi->hdd,
                        pi->hdc,
                        pi->dxDst,
                        pi->dyDst,
                        lpicd->lpbi,
                        pi->dxSrc,
                        pi->dySrc,
                        0)) {  // !!! Flags?
                return ICERR_UNSUPPORTED;
        }

        // !!! error check

        // Allocate a buffer for the scrambled picture
        if (pi->lpBuffer)
                GlobalFreePtr(pi->lpBuffer);

        pi->lpBuffer = GlobalAllocPtr(GMEM_MOVEABLE, lpicd->lpbi->biSizeImage);

        if (!pi->lpBuffer)
                return ICERR_MEMORY;

        return ICERR_OK;
}
/*****************************************************************************
 *
 * AVIDraw() implements ICM_DRAW
 *
 ****************************************************************************/

static LONG LOCAL AVIDraw(
LPINSTINFO pi,
LPICDRAW lpicd,
LONG cbicd)
{
        UINT wFlags = DDF_SAME_HDC;

        if ((lpicd->dwFlags & ICDRAW_NULLFRAME) || lpicd->lpData == NULL) {
                if (lpicd->dwFlags & ICDRAW_UPDATE)
                        wFlags |= DDF_UPDATE;
                else
                        return ICERR_OK;            // nothing to draw
        }

        if (lpicd->dwFlags & ICDRAW_PREROLL)
                wFlags |= DDF_DONTDRAW;

        if (lpicd->dwFlags & ICDRAW_HURRYUP)
                wFlags |= DDF_HURRYUP;

        // This is the only part that actually has to do with the puzzle:
        // Mix up the picture into our extra buffer.
        if (lpicd->lpData)
                MixPicture(&gPuzzle, lpicd->lpFormat, lpicd->lpData, pi->lpBuffer);

        if (!DrawDibDraw(
                        pi->hdd,
                        pi->hdc,
                        pi->xDst,
                        pi->yDst,
                        pi->dxDst,
                        pi->dyDst,
                        lpicd->lpFormat,
                        pi->lpBuffer,
                        pi->xSrc,
                        pi->ySrc,
                        pi->dxSrc,
                        pi->dySrc,
                        wFlags)) {
                if (wFlags & DDF_UPDATE)
                        return ICERR_CANTUPDATE;
                else
                        return ICERR_UNSUPPORTED;
        }

        return ICERR_OK;
}
/*****************************************************************************
 *
 * AVIDrawChangePalette() implements ICM_DRAW_CHANGE_PALETTE
 *
 ****************************************************************************/

static LONG LOCAL AVIDrawChangePalette(
LPINSTINFO pi,
LPBITMAPINFOHEADER lpbi)
{
        PALETTEENTRY ape[256];
        LPRGBQUAD lprgb = (LPRGBQUAD) ((LPBYTE) lpbi + lpbi->biSize);
        int i;

        for (i = 0; i < (int) lpbi->biClrUsed; i++) {
                ape[i].peRed = lprgb[i].rgbRed;
                ape[i].peGreen = lprgb[i].rgbGreen;
                ape[i].peBlue = lprgb[i].rgbBlue;
                ape[i].peFlags = 0;
        }

        DrawDibChangePalette(pi->hdd, 0, (int) lpbi->biClrUsed, (LPPALETTEENTRY)ape);
        return ICERR_OK;
}
/*****************************************************************************
 *
 * AVIDrawEnd() implements ICM_DRAW_END
 *
 ****************************************************************************/

static LONG LOCAL AVIDrawEnd(
LPINSTINFO pi)
{
        // Note: do not call DrawDibEnd here, as we still may be asked to
        // update our current display, and calling DrawDibEnd would wipe
        // that out.

        return ICERR_OK;
}


PUZZPROC.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

LONG CALLBACK ICAVIDrawProc(DWORD, HDRVR, UINT, LPARAM, LPARAM);


README.TXT

The MIDIMON sample displays textual listing of MIDI input messages.


ABOUT.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *    about.h
 */

/* Function prototypes
 */
void About(HANDLE hInstance, HWND hWnd);
int FAR PASCAL AboutDlgProc(HWND, UINT, WPARAM, LPARAM);


CALLBACK.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* callback.c - Contains the low-level MIDI input callback function for
 *      MIDIMon.  This module also contains the LibMain() and WEP()
 *      DLL routines, and other functions accessed by the callback.
 *
 *      Because this module contains a low-level callback function,
 *      this entire module must reside in a FIXED code segment in a DLL.
 *      The data segment must be FIXED as well, since it accessed by
 *      the callback.
 */

#include <windows.h>
#include <mmsystem.h>
#include "midimon.h"
#include "circbuf.h"
#include "instdata.h"
#include "callback.h"

/* midiInputHandler - Low-level callback function to handle MIDI input.
 *      Installed by midiInOpen().  The input handler takes incoming
 *      MIDI events and places them in the circular input buffer.  It then
 *      notifies the application by posting a MM_MIDIINPUT message.
 *
 *      This function is accessed at interrupt time, so it should be as
 *      fast and efficient as possible.  You can't make any
 *      Windows calls here, except PostMessage().  The only Multimedia
 *      Windows call you can make are timeGetSystemTime(), midiOutShortMsg().
 *
 *
 * Param:   hMidiIn - Handle for the associated input device.
 *          wMsg - One of the MIM_***** messages.
 *          dwInstance - Points to CALLBACKINSTANCEDATA structure.
 *          dwParam1 - MIDI data.
 *          dwParam2 - Timestamp (in milliseconds)
 *
 * Return:  void
 */
void FAR PASCAL midiInputHandler(
HMIDIIN hMidiIn,
WORD wMsg,
DWORD dwInstance,
DWORD dwParam1,
DWORD dwParam2)
{
    EVENT event;

    switch(wMsg)
    {
        case MIM_OPEN:
            break;

        /* The only error possible is invalid MIDI data, so just pass
         * the invalid data on so we'll see it.
         */
        case MIM_ERROR:
        case MIM_DATA:
            event.fdwEvent = (wMsg == MIM_ERROR) ? EVNT_F_ERROR : 0;
            event.dwDevice = ((LPCALLBACKINSTANCEDATA)dwInstance)->dwDevice;
            event.data = dwParam1;
            event.timestamp = dwParam2;

            /* Send the MIDI event to the MIDI Mapper, put it in the
             * circular input buffer, and notify the application that
             * data was received.
             */
            if(((LPCALLBACKINSTANCEDATA)dwInstance)->hMapper)
                midiOutShortMsg(
                            ((LPCALLBACKINSTANCEDATA)dwInstance)->hMapper,
                              dwParam1);

            PutEvent(((LPCALLBACKINSTANCEDATA)dwInstance)->lpBuf,
                       (LPEVENT) &event);

            PostMessage(((LPCALLBACKINSTANCEDATA)dwInstance)->hWnd,
                          MM_MIDIINPUT, 0, 0L);

            break;

        default:
            break;
    }
}

/* PutEvent - Puts an EVENT in a CIRCULARBUFFER.  If the buffer is full,
 *      it sets the wError element of the CIRCULARBUFFER structure
 *      to be non-zero.
 *
 * Params:  lpBuf - Points to the CIRCULARBUFFER.
 *          lpEvent - Points to the EVENT.
 *
 * Return:  void
*/
void FAR PASCAL PutEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent)
{
    /* If the buffer is full, set an error and return.
     */
    if(lpBuf->dwCount >= lpBuf->dwSize){
        lpBuf->wError = 1;
        return;
    }

    /* Put the event in the buffer, bump the head pointer and the byte count.
     */
    *lpBuf->lpHead = *lpEvent;

    ++lpBuf->lpHead;
    ++lpBuf->dwCount;

    /* Wrap the head pointer, if necessary.
     */
    if(lpBuf->lpHead >= lpBuf->lpEnd)
        lpBuf->lpHead = lpBuf->lpStart;
}


CALLBACK.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *  callback.h
 */

/* Function prototypes
 */
void FAR PASCAL midiInputHandler(HMIDIIN, WORD, DWORD, DWORD, DWORD);
void FAR PASCAL PutEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent);


CIRCBUF.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * circbuf.c - Routines to manage the circular MIDI input buffer.
 *      This buffer is filled by the low-level callback function and
 *      emptied by the application.  Since this buffer is accessed
 *      by a low-level callback, memory for it must be allocated
 *      exactly as shown in AllocCircularBuffer().
 */

#include <windows.h>
#include "midimon.h"
#include "circbuf.h"

/*
 * AllocCircularBuffer -    Allocates memory for a CIRCULARBUFFER structure
 * and a buffer of the specified size.  Each memory block is allocated
 * with GlobalAlloc() using GMEM_SHARE and GMEM_MOVEABLE flags, locked
 * with GlobalLock(), and page-locked with GlobalPageLock().
 *
 * Params:  dwSize - The size of the buffer, in events.
 *
 * Return:  A pointer to a CIRCULARBUFFER structure identifying the
 *      allocated display buffer.  NULL if the buffer could not be allocated.
 */
LPCIRCULARBUFFER AllocCircularBuffer(DWORD dwSize)
{
    HANDLE hMem;
    LPCIRCULARBUFFER lpBuf;
    LPEVENT lpMem;

    /* Allocate and lock a CIRCULARBUFFER structure.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE,
                       (DWORD)sizeof(CIRCULARBUFFER));
    if(hMem == NULL)
        return NULL;

    lpBuf = (LPCIRCULARBUFFER)GlobalLock(hMem);
    if(lpBuf == NULL)
    {
        GlobalFree(hMem);
        return NULL;
    }

    /* Page lock the memory.  Global memory blocks accessed by
     * low-level callback functions must be page locked.
     */
#ifndef _WIN32
    GlobalSmartPageLock((HGLOBAL)HIWORD(lpBuf));
#endif

    /* Save the memory handle.
     */
    lpBuf->hSelf = hMem;

    /* Allocate and lock memory for the actual buffer.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, dwSize * sizeof(EVENT));
    if(hMem == NULL)
    {
#ifndef _WIN32
        GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }

    lpMem = (LPEVENT)GlobalLock(hMem);
    if(lpMem == NULL)
    {
        GlobalFree(hMem);
#ifndef _WIN32
        GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }

    /* Page lock the memory.  Global memory blocks accessed by
     * low-level callback functions must be page locked.
     */
#ifndef _WIN32
    GlobalSmartPageLock((HGLOBAL)HIWORD(lpMem));
#endif

    /* Set up the CIRCULARBUFFER structure.
     */
    lpBuf->hBuffer = hMem;
    lpBuf->wError = 0;
    lpBuf->dwSize = dwSize;
    lpBuf->dwCount = 0L;
    lpBuf->lpStart = lpMem;
    lpBuf->lpEnd = lpMem + dwSize;
    lpBuf->lpTail = lpMem;
    lpBuf->lpHead = lpMem;

    return lpBuf;
}

/* FreeCircularBuffer - Frees the memory for the given CIRCULARBUFFER
 * structure and the memory for the buffer it references.
 *
 * Params:  lpBuf - Points to the CIRCULARBUFFER to be freed.
 *
 * Return:  void
 */
void FreeCircularBuffer(LPCIRCULARBUFFER lpBuf)
{
    HANDLE hMem;

    /* Free the buffer itself.
     */
#ifndef _WIN32
    GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf->lpStart));
#endif
    GlobalUnlock(lpBuf->hBuffer);
    GlobalFree(lpBuf->hBuffer);

    /* Free the CIRCULARBUFFER structure.
     */
    hMem = lpBuf->hSelf;
#ifndef _WIN32
    GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
    GlobalUnlock(hMem);
    GlobalFree(hMem);
}

/* GetEvent - Gets a MIDI event from the circular input buffer.  Events
 *  are removed from the buffer.  The corresponding PutEvent() function
 *  is called by the low-level callback function, so it must reside in
 *  the callback DLL.  PutEvent() is defined in the CALLBACK.C module.
 *
 * Params:  lpBuf - Points to the circular buffer.
 *          lpEvent - Points to an EVENT structure that is filled with the
 *              retrieved event.
 *
 * Return:  Returns non-zero if successful, zero if there are no
 *   events to get.
 */
WORD FAR PASCAL GetEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent)
{
    /* If no event available, return.
     */
    if(lpBuf->dwCount <= 0)
        return 0;

    /* Get the event.
     */
    *lpEvent = *lpBuf->lpTail;

    /* Decrement the byte count, bump the tail pointer.
     */
    --lpBuf->dwCount;
    ++lpBuf->lpTail;

    /* Wrap the tail pointer, if necessary.
     */
    if(lpBuf->lpTail >= lpBuf->lpEnd)
        lpBuf->lpTail = lpBuf->lpStart;

    return 1;
}


CIRCBUF.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *  circbuf.h
 */

/* Structure to manage the circular input buffer.
 */
typedef struct circularBuffer_tag
{
    HANDLE  hSelf;          /* handle to this structure */
    HANDLE  hBuffer;        /* buffer handle */
    WORD    wError;         /* error flags */
    DWORD   dwSize;         /* buffer size (in EVENTS) */
    DWORD   dwCount;        /* byte count (in EVENTS) */
    LPEVENT lpStart;        /* ptr to start of buffer */
    LPEVENT lpEnd;          /* ptr to end of buffer (last byte + 1) */
    LPEVENT lpHead;         /* ptr to head (next location to fill) */
    LPEVENT lpTail;         /* ptr to tail (next location to empty) */
} CIRCULARBUFFER;
typedef CIRCULARBUFFER FAR *LPCIRCULARBUFFER;

/* Function prototypes
 */
LPCIRCULARBUFFER AllocCircularBuffer(DWORD dwSize);
void FreeCircularBuffer(LPCIRCULARBUFFER lpBuf);
WORD FAR PASCAL GetEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent);


DISPLAY.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * display.c - Functions to manage the display buffer and convert a
 *      MIDI event to a text string for display.
 *
 *      The display buffer is filled by the application's WndProc()
 *      function when it receives an MM_MIDIINPUT message.  This message
 *      is sent by the low-level callback function upon reception of a
 *      MIDI event.  When the display buffer becomes full, newly added
 *      events overwrite the oldest events in the buffer.
 */

#include <windows.h>
#include <stdio.h>
#include "midimon.h"
#include "circbuf.h"
#include "display.h"

/* MIDI event-name strings
 */
char szEventNames[8][24] =
{
                    "Note Off",
                    "Note On",
                    "Key Aftertouch",
                    "Control Change",
                    "Program Change",
                    "Channel Aftertouch",
                    "Pitch Bend",
                    "System Message"
};

char szSysMsgNames[16][24] =
{
                    "System Exclusive",
                    "MTC Quarter Frame",
                    "Song Position Pointer",
                    "Song Select",
                    "Undefined",
                    "Undefined",
                    "Tune Request",
                    "System Exclusive End",
                    "Timing Clock",
                    "Undefined",
                    "Start",
                    "Continue",
                    "Stop",
                    "Undefined",
                    "Active Sensing",
                    "System Reset"
};

/* GetDisplayText - Takes a MIDI event and creates a text string for display.
 *
 * Params:  npText - Points to a string that the function fills.
 *          lpEvent - Points to a MIDI event.
 *
 * Return:  The number of characters in the text string pointed to by npText.
 */
int GetDisplayText(NPSTR npText, LPEVENT lpEvent)
{
    BYTE bStatus, bStatusRaw, bChannel, bData1, bData2;
    char chErr;
    DWORD dwTimestamp;

    bStatusRaw  = LOBYTE(LOWORD(lpEvent->data));
    bStatus     = (BYTE) (bStatusRaw & (BYTE) 0xf0);
    bChannel    = (BYTE) (bStatusRaw & (BYTE) 0x0f);
    bData1      = HIBYTE(LOWORD(lpEvent->data));
    bData2      = LOBYTE(HIWORD(lpEvent->data));
    dwTimestamp = lpEvent->timestamp;
    chErr       = (lpEvent->fdwEvent & EVNT_F_ERROR) ? '*' : ' ';

    switch(bStatus)
    {
        /* Three byte events
         */
        case NOTEOFF:
        case NOTEON:
        case KEYAFTERTOUCH:
        case CONTROLCHANGE:
        case PITCHBEND:
            /* A note on with a velocity of 0 is a note off
             */
            if((bStatus == NOTEON) && (bData2 == 0))
                bStatus = NOTEOFF;

            return(sprintf(npText, FORMAT3, dwTimestamp, bStatusRaw, bData1,
                    bData2, bChannel, chErr, &szEventNames[(bStatus-0x80) >> 4][0]));
            break;

        /* Two byte events
         */
        case PROGRAMCHANGE:
        case CHANAFTERTOUCH:
            return(sprintf(npText, FORMAT2, dwTimestamp, bStatusRaw, bData1,
                    bChannel, chErr, &szEventNames[(bStatus-0x80) >> 4][0]));
            break;

        /* MIDI system events (0xf0 - 0xff)
         */
        case SYSTEMMESSAGE:
            switch(bStatusRaw)
            {
                /* Two byte system events
                 */
                case MTCQUARTERFRAME:
                case SONGSELECT:
                    return(sprintf(npText, FORMAT2X, dwTimestamp, bStatusRaw,
                            bData1,
                            chErr,
                            &szSysMsgNames[(bStatusRaw & 0x0f)][0]));
                    break;

                /* Three byte system events
                 */
                case SONGPOSPTR:
                    return(sprintf(npText, FORMAT3X, dwTimestamp, bStatusRaw,
                            bData1, bData2, chErr,
                            &szSysMsgNames[(bStatusRaw & 0x0f)][0]));
                    break;

                /* One byte system events
                 */
                default:
                    return(sprintf(npText, FORMAT1X, dwTimestamp, bStatusRaw,
                            chErr, &szSysMsgNames[(bStatusRaw & 0x0f)][0]));
                    break;
            }
            break;

        default:
            return(sprintf(npText, FORMAT3X, dwTimestamp, bStatusRaw, bData1,
                    bData2, chErr, GetStringRes(IDS_UNKNOWN_EVENT)));
            break;
    }
}

/* AddDisplayEvent - Puts a MIDI event in the display buffer.  The display
 *      buffer is a circular buffer.  Once it is full, newly added events
 *      overwrite the oldest events in the buffer.
 *
 * Params:  lpBuf - Points to the display buffer.
 *          lpEvent - Points to a MIDI event.
 *
 * Return:  void
 */
void AddDisplayEvent(LPDISPLAYBUFFER lpBuf, LPEVENT lpEvent)
{
    /* Put the event in the buffer, bump the head pointer and byte count.
     */
    *lpBuf->lpHead = *lpEvent;
    ++lpBuf->lpHead;
    ++lpBuf->dwCount;

    /* Wrap pointer, if necessary.
     */
    if(lpBuf->lpHead == lpBuf->lpEnd)
        lpBuf->lpHead = lpBuf->lpStart;

    /* A full buffer is a full buffer, no more.
     */
    lpBuf->dwCount = min(lpBuf->dwCount, lpBuf->dwSize);
}

/* GetDisplayEvent - Retrieves a MIDI event from the display buffer.
 *      Unlike the input buffer, the event is not removed from the buffer.
 *
 * Params:  lpBuf - Points to the display buffer.
 *          lpEvent - Points to an EVENT structure that is filled with
 *              the retrieved MIDI event.
 *          wNum - Specifies which event to retrieve.
 *
 * Return:  void
 */
void GetDisplayEvent(LPDISPLAYBUFFER lpBuf, LPEVENT lpEvent, DWORD wNum)
{
    LPEVENT lpFirstEvent, lpThisEvent;

    /* Get pointer to the first (oldest) event in buffer.
     */
    if(lpBuf->dwCount < lpBuf->dwSize)      // buffer is not yet full
        lpFirstEvent = lpBuf->lpStart;

    else                                    // buffer is full
        lpFirstEvent = lpBuf->lpHead;

    /* Offset pointer to point to requested event; wrap pointer.
     */
    lpThisEvent = lpFirstEvent + wNum;
    if(lpThisEvent >= lpBuf->lpEnd)
        lpThisEvent = lpBuf->lpStart + (lpThisEvent - lpBuf->lpEnd);

    /* Get the requested event.
     */
    *(lpEvent) = *lpThisEvent;
}

/* AllocDisplayBuffer - Allocates memory for a DISPLAYBUFFER structure
 *      and a buffer of the specified size.  Each memory block is allocated
 *      with GlobalAlloc() using GMEM_SHARE and GMEM_MOVEABLE flags and
 *      locked with GlobalLock().  Since this buffer is only accessed by the
 *      application, and not the low-level callback function, it does not
 *      have to be page locked.
 *
 * Params:  dwSize - The size of the buffer, in events.
 *
 * Return:  A pointer to a DISPLAYBUFFER structure identifying the
 *      allocated display buffer.  NULL if the buffer could not be allocated.
*/
LPDISPLAYBUFFER AllocDisplayBuffer(DWORD dwSize)
{
    HANDLE hMem;
    LPDISPLAYBUFFER lpBuf;
    LPEVENT lpMem;

    /* Allocate and lock a DISPLAYBUFFER structure.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE,
                       (DWORD)sizeof(DISPLAYBUFFER));
    if(hMem == NULL)
        return NULL;

    lpBuf = (LPDISPLAYBUFFER)GlobalLock(hMem);
    if(lpBuf == NULL)
    {
        GlobalFree(hMem);
        return NULL;
    }

    /* Save the handle.
     */
    lpBuf->hSelf = hMem;

    /* Allocate and lock memory for the actual buffer.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, dwSize * sizeof(EVENT));
    if(hMem == NULL)
    {
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }
    lpMem = (LPEVENT)GlobalLock(hMem);
    if(lpMem == NULL)
    {
        GlobalFree(hMem);
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }

    /* Set up the DISPLAYBUFFER structure.
     */
    lpBuf->hBuffer = hMem;
    lpBuf->wError = 0;
    lpBuf->dwSize = dwSize;
    lpBuf->dwCount = 0L;
    lpBuf->lpStart = lpMem;
    lpBuf->lpEnd = lpMem + dwSize;
    lpBuf->lpTail = lpMem;
    lpBuf->lpHead = lpMem;

    return lpBuf;
}

/* FreeDisplayBuffer - Frees the memory for a display buffer.
 *
 * Params:  lpBuf - Points to the DISPLAYBUFFER to be freed.
 *
 * Return:  void
 */
void FreeDisplayBuffer(LPDISPLAYBUFFER lpBuf)
{
    HANDLE hMem;

    /* Unlock and free the buffer itself.
     */
    GlobalUnlock(lpBuf->hBuffer);
    GlobalFree(lpBuf->hBuffer);

    /* Unlock and free the DISPLAYBUFFER structure.
     */
    hMem = lpBuf->hSelf;
    GlobalUnlock(hMem);
    GlobalFree(hMem);
}

/* ResetDisplayBuffer - Empties a display buffer.
 *
 * Params:  lpBuf - Points to a display buffer.
 *
 * Return:  void
 */
void ResetDisplayBuffer(LPDISPLAYBUFFER lpBuf)
{
    /* Reset the pointers and event count.
     */
    lpBuf->lpHead = lpBuf->lpStart;
    lpBuf->lpTail = lpBuf->lpStart;
    lpBuf->dwCount = 0L;
    lpBuf->wError = 0;
}


DISPLAY.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *  display.h
 */

/* MIDI status byte definitions
 */
#define NOTEON          0x90
#define NOTEOFF         0x80
#define KEYAFTERTOUCH   0xa0
#define CONTROLCHANGE   0xb0
#define PROGRAMCHANGE   0xc0
#define CHANAFTERTOUCH  0xd0
#define PITCHBEND       0xe0
#define SYSTEMMESSAGE   0xf0
#define BEGINSYSEX      0xf0
#define MTCQUARTERFRAME 0xf1
#define SONGPOSPTR      0xf2
#define SONGSELECT      0xf3

/* Format strings used by GetDisplayText()
 */
#define FORMAT3  " %08lX  %02X     %02X    %02X    %2d   %c   %-21s "
#define FORMAT2  " %08lX  %02X     %02X    --    %2d   %c   %-21s "
#define FORMAT3X " %08lX  %02X     %02X    %02X    --   %c   %-21s "
#define FORMAT2X " %08lX  %02X     %02X    --    --   %c   %-21s "
#define FORMAT1X " %08lX  %02X     --    --    --   %c   %-21s "

/* Data structure to manage a display buffer.
 */
typedef struct displayBuffer_tag
{
    HANDLE  hSelf;          /* handle to this structure */
    HANDLE  hBuffer;        /* buffer handle */
    WORD    wError;         /* error flags */
    DWORD   dwSize;         /* buffer size (in EVENTS) */
    DWORD   dwCount;        /* byte count (in EVENTS) */
    LPEVENT lpStart;        /* ptr to start of buffer */
    LPEVENT lpEnd;          /* ptr to end of buffer (last byte + 1) */
    LPEVENT lpHead;         /* ptr to head (next location to fill) */
    LPEVENT lpTail;         /* ptr to tail (next location to empty) */
} DISPLAYBUFFER;
typedef DISPLAYBUFFER FAR *LPDISPLAYBUFFER;

/* Function prototypes
 */
int GetDisplayText(NPSTR npText, LPEVENT lpEvent);
void AddDisplayEvent(LPDISPLAYBUFFER lpBuf, LPEVENT lpEvent);
void GetDisplayEvent(LPDISPLAYBUFFER lpBuf, LPEVENT lpEvent, DWORD wNum);
LPDISPLAYBUFFER AllocDisplayBuffer(DWORD dwSize);
void FreeDisplayBuffer(LPDISPLAYBUFFER lpBuf);
void ResetDisplayBuffer(LPDISPLAYBUFFER lpBuf);


FILTER.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * filter.c - Routines to filter MIDI events.
 */

#include <windows.h>
#include "midimon.h"
#include "display.h"
#include "filter.h"

/* CheckEventFilter - Checks the given EVENT against the given FILTER.
 *
 * Params:  lpEvent - Points to an EVENT.
 *          lpFilter - Points to a FILTER structure.
 *
 * Return:  Returns 1 if the event is filtered, 0 if it is not filtered.
 */
BOOL CheckEventFilter(LPEVENT lpEvent, LPFILTER lpFilter)
{
    BYTE bStatus, bStatusRaw, bChannel, bData1, bData2;

    /* Get the essential info from the EVENT.
     */
    bStatusRaw = LOBYTE(LOWORD(lpEvent->data));
    bStatus = bStatusRaw & (BYTE) 0xf0;
    bChannel = LOBYTE(LOWORD(lpEvent->data)) & (BYTE) 0x0f;
    bData1 = HIBYTE(LOWORD(lpEvent->data));
    bData2 = LOBYTE(HIWORD(lpEvent->data));

    /* Do channel filtering for all but system events.
     */
    if(bStatus != SYSTEMMESSAGE){
        if(lpFilter->channel[bChannel])
            return 1;
    }

    /* Do event-type filtering.
     */
    switch(bStatus){
        case NOTEOFF:
            if(lpFilter->event.noteOff)
                return 1;
            break;

        case NOTEON:
            /* A note on with a velocity of 0 is a note off.
             */
            if(bData2 == 0){
                if(lpFilter->event.noteOff)
                    return 1;
                break;
            }

            if(lpFilter->event.noteOn)
                return 1;
            break;

        case KEYAFTERTOUCH:
            if(lpFilter->event.keyAftertouch)
                return 1;
            break;

        case CONTROLCHANGE:
            if(lpFilter->event.controller)
                return 1;

            /* Channel mode messages can be filtered.
             */
            if((bData1 >= 121) && lpFilter->event.channelMode)
                return 1;
            break;

        case PROGRAMCHANGE:
            if(lpFilter->event.progChange)
                return 1;
            break;

        case CHANAFTERTOUCH:
            if(lpFilter->event.chanAftertouch)
                return 1;
            break;

        case PITCHBEND:
            if(lpFilter->event.pitchBend)
                return 1;
            break;

        case SYSTEMMESSAGE:
            /* System common messages.
             */
            if((bStatusRaw < 0xf8) && (lpFilter->event.sysCommon))
                return 1;

            /* Active sensing messages.
             */
            if((bStatusRaw == 0xfe) && (lpFilter->event.activeSense))
                return 1;

            /* System real time messages.
             */
            if((bStatusRaw >= 0xf8) && lpFilter->event.sysRealTime)
                return 1;
            break;

        default:
            break;
    }

    return 0;
}


FILTER.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *   filter.h
 */

/* Structure to specify a filter for MIDI events.
 */
typedef struct filter_tag
{
    BOOL channel[16];
    struct {
        BOOL noteOn;
        BOOL noteOff;
        BOOL keyAftertouch;
        BOOL controller;
        BOOL progChange;
        BOOL chanAftertouch;
        BOOL pitchBend;
        BOOL channelMode;

        BOOL sysEx;
        BOOL sysCommon;
        BOOL sysRealTime;
        BOOL activeSense;
        BOOL pad0;
        BOOL pad1;
        BOOL pad2;
        BOOL pad3;
    } event;
} FILTER;

typedef FILTER FAR *LPFILTER;

/* Function prototypes
 */
BOOL CheckEventFilter(LPEVENT lpEvent, LPFILTER lpFilter);


INSTDATA.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * instdata.c - Functions to allocate and free the instance
 *      data structure passed to the low-level callback function.
 */

#include <windows.h>
#include <mmsystem.h>
#include "midimon.h"
#include "circbuf.h"
#include "instdata.h"

/* AllocCallbackInstanceData - Allocates a CALLBACKINSTANCEDATA
 *      structure.  This structure is used to pass information to the
 *      low-level callback function, each time it receives a message.
 *
 *      Because this structure is accessed by the low-level callback
 *      function, it must be allocated using GlobalAlloc() with the
 *      GMEM_SHARE and GMEM_MOVEABLE flags and page-locked with
 *      GlobalPageLock().
 *
 * Params:  void
 *
 * Return:  A pointer to the allocated CALLBACKINSTANCE data structure.
 */
LPCALLBACKINSTANCEDATA FAR PASCAL AllocCallbackInstanceData(void)
{
    HANDLE hMem;
    LPCALLBACKINSTANCEDATA lpBuf;

    /* Allocate and lock global memory.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE,
                       (DWORD)sizeof(CALLBACKINSTANCEDATA));
    if(hMem == NULL)
        return NULL;

    lpBuf = (LPCALLBACKINSTANCEDATA)GlobalLock(hMem);
    if(lpBuf == NULL){
        GlobalFree(hMem);
        return NULL;
    }

    /* Page lock the memory.
     */
    //GlobalPageLock((HGLOBAL)HIWORD(lpBuf));

    /* Save the handle.
     */
    lpBuf->hSelf = hMem;

    return lpBuf;
}

/* FreeCallbackInstanceData - Frees the given CALLBACKINSTANCEDATA structure.
 *
 * Params:  lpBuf - Points to the CALLBACKINSTANCEDATA structure to be freed.
 *
 * Return:  void
 */
void FAR PASCAL FreeCallbackInstanceData(LPCALLBACKINSTANCEDATA lpBuf)
{
    HANDLE hMem;

    /* Save the handle until we're through here.
     */
    hMem = lpBuf->hSelf;

    /* Free the structure.
     */
    //GlobalPageUnlock((HGLOBAL)HIWORD(lpBuf));
    GlobalUnlock(hMem);
    GlobalFree(hMem);
}


INSTDATA.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * instdata.h
 */

/* Structure to pass instance data from the application
   to the low-level callback function.
 */
typedef struct callbackInstance_tag
{
    HWND                hWnd;
    HANDLE              hSelf;
    DWORD               dwDevice;
    LPCIRCULARBUFFER    lpBuf;
    HMIDIOUT            hMapper;
} CALLBACKINSTANCEDATA;
typedef CALLBACKINSTANCEDATA FAR *LPCALLBACKINSTANCEDATA;

/* Function prototypes
 */
LPCALLBACKINSTANCEDATA FAR PASCAL AllocCallbackInstanceData(void);
void FAR PASCAL FreeCallbackInstanceData(LPCALLBACKINSTANCEDATA lpBuf);


MIDIMON.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* midimon.c - WinMain() and WndProc() functions for MIDIMon, along
 *      with some initialization and error reporting functions.
 *
 * MIDIMon is a Windows with Multimedia application that records and displays
 *  incoming MIDI information.  It uses a low-level callback function to
 *  get timestamped MIDI input.  The callback function puts the incoming
 *  MIDI event information (source device, timestamp, and raw MIDI
 *  data) in a circular input buffer and notifies the application by posting
 *  a MM_MIDIINPUT message.  When the application processes the MM_MIDIINPUT
 *  message, it removes the MIDI event from the input buffer and puts it in
 *  a display buffer.  Information in the display buffer is converted to
 *  text and displayed in a scrollable window.  Incoming MIDI data can be sent
 *  to the MIDI Mapper if the user chooses.  Filtering is provided for the
 *  display buffer, but not for data sent to the Mapper.
 *

 */

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include "midimon.h"
#include "about.h"
#include "circbuf.h"
#include "display.h"
#include "prefer.h"
#include "instdata.h"
#include "callback.h"
#include "filter.h"

HANDLE hInst;                           // Instance handle for application
char szAppName[20];                     // Application name
HWND hMainWnd;                          // Main window handle
HMIDIOUT hMapper = 0;                   // Handle to MIDI Mapper
UINT wNumDevices = 0; // Number of MIDI input devices opened
BOOL bRecordingEnabled = 1;             // Enable/disable recording flag
int  nNumBufferLines = 0; // Number of lines in display buffer
RECT rectScrollClip;                    // Clipping rectangle for scrolling

LPCIRCULARBUFFER lpInputBuffer;         // Input buffer structure
LPDISPLAYBUFFER lpDisplayBuffer;        // Display buffer structure
PREFERENCES preferences;                // User preferences structure
EVENT incomingEvent;                    // Incoming MIDI event structure

MIDIINCAPS midiInCaps[MAX_NUM_DEVICES]; // Device capabilities structures
HMIDIIN hMidiIn[MAX_NUM_DEVICES];       // MIDI input device handles

// Callback instance data pointers
LPCALLBACKINSTANCEDATA lpCallbackInstanceData[MAX_NUM_DEVICES];

// Display filter structure
FILTER filter = {
                    0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0
                };

// Virtual key to scroll message translation structure
KEYTOSCROLL keyToScroll [] = {
                                VK_HOME,  WM_VSCROLL, SB_TOP,
                                VK_END,   WM_VSCROLL, SB_BOTTOM,
                                VK_PRIOR, WM_VSCROLL, SB_PAGEUP,
                                VK_NEXT,  WM_VSCROLL, SB_PAGEDOWN,
                                VK_UP,    WM_VSCROLL, SB_LINEUP,
                                VK_DOWN,  WM_VSCROLL, SB_LINEDOWN,
                                VK_LEFT,  WM_HSCROLL, SB_LINEUP,
                                VK_RIGHT, WM_HSCROLL, SB_LINEDOWN
                             };

#define NUMKEYS (sizeof (keyToScroll) / sizeof (keyToScroll[0]))

                                                                        //
/* WinMain - Entry point for MIDIMon.
 */
int PASCAL WinMain(hInstance,hPrevInstance,lpszCmdLine,cmdShow)
HINSTANCE hInstance,hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
    MSG msg;
    UINT  wRtn;
    PREFERENCES preferences;
    char szErrorText[256];
    unsigned int i;

    hInst = hInstance;

    /* Get preferred user setup.
     */
    getPreferences(&preferences);

    /* Initialize application.
     */
    LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName));
    if (hPrevInstance || !InitFirstInstance(hInstance))
        return 0;

    /* Create a display window.
     */
    hMainWnd = CreateWindow(szAppName,
                        szAppName,
                        WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
                        preferences.iInitialX,
                        preferences.iInitialY,
                        preferences.iInitialW,
                        preferences.iInitialH,
                        (HWND)NULL,
                        (HMENU)NULL,
                        hInstance,
                        (LPSTR)NULL);

    if (!hMainWnd)
        return 1;

    /* Hide scroll bars for now.
     */
    SetScrollRange(hMainWnd, SB_VERT, 0, 0, FALSE);
    SetScrollRange(hMainWnd, SB_HORZ, 0, 0, FALSE);

    /* Show the display window.
     */
    ShowWindow(hMainWnd, cmdShow);
    UpdateWindow(hMainWnd);

    /* Get the number of MIDI input devices.  Then get the capabilities of
     * each device.  We don't use the capabilities information right now,
     * but we could use it to report the name of the device that received
     * each MIDI event.
     */
    wNumDevices = midiInGetNumDevs();
    if (!wNumDevices) {
        Error(GetStringRes(IDS_NOMIDIIN));
        PostQuitMessage(0);
    }
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) &midiInCaps[i],
                                sizeof(MIDIINCAPS));
        if(wRtn) {
            midiInGetErrorText(wRtn, (LPSTR)szErrorText,
                               sizeof(szErrorText));
            Error(szErrorText);
            Error("midiInGetDevCaps");
        }
    }

    /* Allocate a circular buffer for low-level MIDI input.  This buffer
     * is filled by the low-level callback function and emptied by the
     * application when it receives MM_MIDIINPUT messages.
     */
    lpInputBuffer = AllocCircularBuffer(
                        (DWORD)(INPUT_BUFFER_SIZE * sizeof(EVENT)));
    if (lpInputBuffer == NULL) {
        Error(GetStringRes(IDS_NOMEM_IBUF));
        return 1;
    }

    /* Allocate a display buffer.  Incoming events from the circular input
     * buffer are put into this buffer for display.
     */
    lpDisplayBuffer = AllocDisplayBuffer((DWORD)(DISPLAY_BUFFER_SIZE));
    if (lpDisplayBuffer == NULL) {
        Error(GetStringRes(IDS_NOMEM_DBUF));
        FreeCircularBuffer(lpInputBuffer);
        return 1;
    }

    /* Open all MIDI input devices after allocating and setting up
     * instance data for each device.  The instance data is used to
     * pass buffer management information between the application and
     * the low-level callback function.  It also includes a device ID,
     * a handle to the MIDI Mapper, and a handle to the application's
     * display window, so the callback can notify the window when input
     * data is available.  A single callback function is used to service
     * all opened input devices.
     */
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
    {
        if ((lpCallbackInstanceData[i] = AllocCallbackInstanceData()) == NULL)
        {
            Error(GetStringRes(IDS_NOMEM));
            FreeCircularBuffer(lpInputBuffer);
            FreeDisplayBuffer(lpDisplayBuffer);
            return 1;
        }
        lpCallbackInstanceData[i]->hWnd = hMainWnd;
        lpCallbackInstanceData[i]->dwDevice = i;
        lpCallbackInstanceData[i]->lpBuf = lpInputBuffer;
        lpCallbackInstanceData[i]->hMapper = hMapper;

        wRtn = midiInOpen((LPHMIDIIN)&hMidiIn[i],
                          i,
                          (DWORD)midiInputHandler,
                          (DWORD)lpCallbackInstanceData[i],
                          CALLBACK_FUNCTION);
        if(wRtn)
        {
            FreeCallbackInstanceData(lpCallbackInstanceData[i]);
            midiInGetErrorText(wRtn, (LPSTR)szErrorText, sizeof(szErrorText));
            Error(szErrorText);
            wsprintf(szErrorText, "midiInOpen(%u)", i);
            Error(szErrorText);
        }
    }

    /* Start MIDI input.
     */
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        if (hMidiIn[i])
            midiInStart(hMidiIn[i]);
    }

    /* Standard Windows message processing loop.  We don't drop out of
     * this loop until the user quits the application.
     */
    while (GetMessage(&msg, NULL, 0, 0)) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
    }

    /* Stop, reset, close MIDI input.  Free callback instance data.
     */
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        if (hMidiIn[i]) {
            midiInStop(hMidiIn[i]);
            midiInReset(hMidiIn[i]);
            midiInClose(hMidiIn[i]);
            FreeCallbackInstanceData(lpCallbackInstanceData[i]);
        }
    }

    /* Close the MIDI Mapper, if it's open.
     */
    if (hMapper)
        midiOutClose(hMapper);

    /* Free input and display buffers.
     */
    FreeCircularBuffer(lpInputBuffer);
    FreeDisplayBuffer(lpDisplayBuffer);

    return (msg.wParam);
}

                                                                        //
/* WndProc - Main window procedure function.
 */
LRESULT FAR PASCAL WndProc(
  HWND  hWnd,
  UINT  message,
  WPARAM  wParam,
  LPARAM  lParam)
{
    PAINTSTRUCT ps;
    HFONT hFont;
    HBRUSH hBrush;
//!!    HPEN hPen;
    HDC hDC;
    TEXTMETRIC tm;
    static BOOL  bWindowCreated = 0;
    static int  wChar, hChar;
    static int   maxClientWidth;
    static int  wClient, hClient;
    static int  nVscrollMax = 0;
    static int  nHscrollMax = 0;
    static int  nVscrollPos = 0;
    static int  nHscrollPos = 0;
    static int   nNumCharsPerLine = 0;
    static int   nNumDisplayLines = 0;
    static int   nNumDisplayChars = 0;
    BOOL bNeedToUpdate = FALSE;
    int  nVscrollInc, nHscrollInc;
    int  nPaintBeg, nPaintEnd;
    int          i;
    SIZE  size;

    char szDisplayTextBuffer[120];

    switch(message)
    {
        case WM_CREATE:
            hDC = GetDC(hWnd);

            /* Set the font we want to use.
             */
            hFont = GetStockObject(SYSTEM_FIXED_FONT);
            SelectObject(hDC, hFont);

            /* Get text metrics and calculate the number of characters
             * per line and the maximum width required for the client area.
             */
            GetTextMetrics(hDC, &tm);
    wChar = tm.tmAveCharWidth;
    hChar = tm.tmHeight + tm.tmExternalLeading;
    nNumCharsPerLine = strlen(GetStringRes(IDS_LABEL)) - 1;

    GetTextExtentPoint(hDC,
       szDisplayTextBuffer,
       sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)),
       &size);
    maxClientWidth = size.cx;

            ReleaseDC(hWnd, hDC);

            bWindowCreated = 1;
            break;

        case WM_SIZE:
    hClient = (int)HIWORD(lParam);
    wClient = (int)LOWORD(lParam);

            /* Get new client area and adjust scroll clip rectangle.
             */
            GetClientRect(hWnd, (LPRECT)&rectScrollClip);
            rectScrollClip.top += hChar;

            /* Calculate new display metrics.  We subtract 1 from
             * nNumDisplayLines to allow room for the label line.
             */
            nNumDisplayLines = hClient / hChar - 1;
            nNumDisplayChars = wClient / wChar;

            /* Calculate and set new scroll bar calibrations.
             */
    nVscrollMax = max(0, nNumBufferLines - nNumDisplayLines);
    nVscrollPos = min(nVscrollPos, nVscrollMax);
    nHscrollMax = max(0, nNumCharsPerLine - nNumDisplayChars);
    nHscrollPos = min(nHscrollPos, nHscrollMax);
            SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
            SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
            SetScrollRange(hWnd, SB_HORZ, 0, nHscrollMax, FALSE);
            SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);
            break;

        case WM_GETMINMAXINFO:
            /* Limit the maximum width of the window.
             */
    if(bWindowCreated) {
((LPPOINT)lParam)[4].x = maxClientWidth +
 (2 * GetSystemMetrics(SM_CXFRAME)) +
 (GetSystemMetrics(SM_CXVSCROLL));
    }

  //*((LPWORD)lParam + 8) = maxClientWidth +
  // (2 * GetSystemMetrics(SM_CXFRAME)) +
  //(GetSystemMetrics(SM_CXVSCROLL));
            break;

        case WM_COMMAND:
            /* Process menu messages.
             */
            CommandMsg(hWnd, wParam, lParam);
            break;

        case WM_VSCROLL:
            /* Determine how much to scroll vertically.
             */
    switch (LOWORD(wParam))
            {
                case SB_TOP:
                    nVscrollInc = -nVscrollPos;
                    break;

                case SB_BOTTOM:
                    nVscrollInc = nVscrollMax - nVscrollPos;
                    break;

                case SB_LINEUP:
                    nVscrollInc = -1;
                    break;

                case SB_LINEDOWN:
                    nVscrollInc = 1;
                    break;

                case SB_PAGEUP:
                    nVscrollInc = min (-1, -nNumDisplayLines);
                    break;

                case SB_PAGEDOWN:
    nVscrollInc = max(1, nNumDisplayLines);
                    break;

                case SB_THUMBTRACK:
    nVscrollInc = ((int)HIWORD(wParam) - nVscrollPos);
                    break;

                default:
                    nVscrollInc = 0;

            }

            /* Limit the scroll range and do the scroll.  We use the
             * rectScrollClip rectangle because we don't want to scroll
             * the entire window, only the part below the display label line.
             */
    if(nVscrollInc = max(-nVscrollPos,
 min(nVscrollInc,
     nVscrollMax - nVscrollPos)))
            {
                nVscrollPos += nVscrollInc;
                ScrollWindow(hWnd, 0, -hChar * nVscrollInc,
                              (LPRECT)&rectScrollClip,
                              (LPRECT)&rectScrollClip);
                UpdateWindow(hWnd);
                SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
            }
            break;

        case WM_HSCROLL:
            /* Determine how much to scroll horizontally.
             */
    switch (LOWORD(wParam))
            {
                case SB_LINEUP:
                    nHscrollInc = -1;
                    break;

                case SB_LINEDOWN:
                    nHscrollInc = 1;
                    break;

                case SB_PAGEUP:
    nHscrollInc = min(-1, -nNumDisplayChars);
                    break;

                case SB_PAGEDOWN:
    nHscrollInc = max(1, nNumDisplayChars);
                    break;

                case SB_THUMBTRACK:
    nHscrollInc = ((int)HIWORD(wParam) - nHscrollPos);
                    break;

                default:
                    nHscrollInc = 0;
            }

            /* Limit the scroll range and to the scroll.
             */
    if(nHscrollInc = max(-nHscrollPos,
 min(nHscrollInc,
     nHscrollMax - nHscrollPos)))
            {
                nHscrollPos += nHscrollInc;
                ScrollWindow(hWnd, -wChar * nHscrollInc, 0, NULL, NULL);
                UpdateWindow(hWnd);
                SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);
            }
            break;

        case WM_KEYDOWN:
            /* Translate keystrokes to scroll message.
             */
            for (i = 0; i < NUMKEYS; i++)
                if (wParam == keyToScroll[i].wVirtKey)
                {
                    SendMessage(hWnd, keyToScroll[i].iMessage,
                                keyToScroll[i].wRequest, 0L);
                    break;
                }
            break;

        case WM_PAINT:
            BeginPaint(hWnd, &ps);

            hBrush = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
            FillRect(ps.hdc, &ps.rcPaint, hBrush);
            DeleteObject(hBrush);

            /* Set up text attributes.
             */
            hFont = GetStockObject(SYSTEM_FIXED_FONT);
            SelectObject(ps.hdc, hFont);
            SetBkMode(ps.hdc, TRANSPARENT);

            /* Put up the display label if we're asked to repaint the
             * top line of the screen.
             */
    if(ps.rcPaint.top < hChar)
            {
                TextOut(ps.hdc, wChar * (0 - nHscrollPos),
                        0, szDisplayTextBuffer,
                        sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)));
MoveToEx(ps.hdc, wChar * (0 - nHscrollPos), hChar - 1, NULL);
                LineTo(ps.hdc, wClient, hChar - 1);

                ps.rcPaint.top = hChar;
            }

            /* Calculate the beginning and ending line numbers that we need
             * to paint.  These line numbers refer to lines in the display
             * buffer, not to lines in the display window.
             */
    nPaintBeg = max (0, nVscrollPos + ps.rcPaint.top / hChar - 1);
            nPaintEnd = min(nNumBufferLines,
                              nVscrollPos + ps.rcPaint.bottom / hChar + 1);

            /* Get the appropriate events from the display buffer, convert
             * to a text string and paint the text on the display.
             */
            for (i = nPaintBeg; i < nPaintEnd; i++)
            {
                GetDisplayEvent(lpDisplayBuffer, (LPEVENT)&incomingEvent, i);
                TextOut(ps.hdc,
                        wChar * (0 - nHscrollPos),
                        hChar * (1 - nVscrollPos + i),
                        szDisplayTextBuffer,
                        GetDisplayText(szDisplayTextBuffer,
                                       (LPEVENT)&incomingEvent));
            }

            EndPaint(hWnd, &ps);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case MM_MIDIINPUT:
            /* This is a custom message sent by the low level callback
             * function telling us that there is at least one MIDI event
             * in the input buffer.  We empty the input buffer, and put
             * each event in the display buffer, if it's not filtered.
             * If the input buffer is being filled as fast we can empty
             * it, then we'll stay in this loop and not process any other
             * Windows messages, or yield to other applications.  We need
             * something to restrict the amount of time we spend here...
             */
            while(GetEvent(lpInputBuffer, (LPEVENT)&incomingEvent))
            {
                if(!bRecordingEnabled)
                    continue;

                if(!CheckEventFilter((LPEVENT)&incomingEvent,
                                    (LPFILTER)&filter))
                {
                    AddDisplayEvent(lpDisplayBuffer,
                                    (LPEVENT)&incomingEvent);
                    ++nNumBufferLines;
                    nNumBufferLines = min(nNumBufferLines,
  DISPLAY_BUFFER_SIZE - 1);
    bNeedToUpdate = TRUE;
                }
            }

            /* Recalculate vertical scroll bar range, and force
             * the display to be updated.
     */

    if (bNeedToUpdate) {
      nVscrollMax = max(0, nNumBufferLines - nNumDisplayLines);
      nVscrollPos = nVscrollMax;
      SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
      SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
      InvalidateRect(hMainWnd, (LPRECT)&rectScrollClip, 0);
      UpdateWindow(hMainWnd);
    }

            break;

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

                                                                        //
/* CommandMsg - Processes WM_COMMAND messages.
 *
 * Params:  hWnd - Handle to the window receiving the message.
 *    wParam - Parameter of the WM_COMMAND message.
 *    lParam - Parameter of the WM_COMMAND message.
 *
 * Return:  void
 */
VOID  CommandMsg(
  HWND  hWnd,
  WPARAM  wParam,
  LPARAM  lParam)
{
    PREFERENCES preferences;
    RECT rectWindow;
    UINT  wRtn;
    HMENU hMenu;
    unsigned int i;
    char szErrorText[256];
    WORD  wCommand;

    wCommand = LOWORD(wParam);

    /* Process any WM_COMMAND messages we want */
    switch (wCommand) {
        case IDM_ABOUT:
            About(hInst, hWnd);
            break;

        case IDM_EXIT:
            PostMessage(hWnd, WM_CLOSE, 0, 0l);
            break;

        case IDM_SENDTOMAPPER:
            /* We use hMapper as a toggle between sending events to the
             * Mapper and not sending events.
             */
            if(hMapper) {
                /* Close the Mapper and reset hMapper.  Uncheck the menu item.
                 * Clear Mapper handle in the instance data for each device.
                 */
                wRtn = midiOutClose(hMapper);
                if(wRtn != 0)
                {
                    midiOutGetErrorText(wRtn, (LPSTR)szErrorText,
                                        sizeof(szErrorText));
                    Error(szErrorText);
                    Error("midiOutClose");
                }
                hMapper = 0;

                for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
                    lpCallbackInstanceData[i]->hMapper = hMapper;

DoMenuItemCheck(hWnd, wCommand, FALSE);
            }

            else {
                /* Open the MIDI Mapper, put the Mapper handle in the instance
                 * data for each device and check the menu item.
                 */
wRtn = midiOutOpen((LPHMIDIOUT) &hMapper, (UINT) MIDIMAPPER,
                                    0L, 0L, 0L);

                if(wRtn != 0) {             // error opening Mapper
                    midiOutGetErrorText(wRtn, (LPSTR)szErrorText,
                                        sizeof(szErrorText));
                    Error("midiOutOpen");
                    Error(szErrorText);
                    hMapper = 0;
                }

                else {                      // Mapper opened successfully
                    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
                        lpCallbackInstanceData[i]->hMapper = hMapper;

    DoMenuItemCheck(hWnd, wCommand, TRUE);
                }
            }

            break;

        case IDM_SAVESETUP:
            /* Save the current location and size of the display window
             * in the MIDIMON.INI file.
             */
            GetWindowRect(hMainWnd, (LPRECT)&rectWindow);
            preferences.iInitialX = rectWindow.left;
            preferences.iInitialY = rectWindow.top;
            preferences.iInitialW = rectWindow.right - rectWindow.left;
            preferences.iInitialH = rectWindow.bottom - rectWindow.top;

            setPreferences((LPPREFERENCES) &preferences);
            break;

        case IDM_STARTSTOP:
            /* Toggle between recording into the display buffer and not
             * recording.  Toggle the menu item between "Start" to "Stop"
             * accordingly.
             */
            hMenu = GetMenu(hWnd);
            if(bRecordingEnabled)
            {
                ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
                           GetStringRes(IDS_START));
                bRecordingEnabled = 0;
            }
            else
            {
                ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
                           GetStringRes(IDS_STOP));
                bRecordingEnabled = 1;
            }
            DrawMenuBar(hWnd);
            break;

        case IDM_CLEAR:
            /* Reset the display buffer, recalibrate the scroll bars,
             * and force an update of the display.
             */
            ResetDisplayBuffer(lpDisplayBuffer);
            nNumBufferLines = 0;
            SetScrollRange(hWnd, SB_VERT, 0, 0, FALSE);

            InvalidateRect(hWnd, (LPRECT)&rectScrollClip, 0);
            UpdateWindow(hWnd);

            break;

        /* Set up filter structure for MIDI channel filtering.
         */
        case IDM_FILTCHAN0:
        case IDM_FILTCHAN1:
        case IDM_FILTCHAN2:
        case IDM_FILTCHAN3:
        case IDM_FILTCHAN4:
        case IDM_FILTCHAN5:
        case IDM_FILTCHAN6:
        case IDM_FILTCHAN7:
        case IDM_FILTCHAN8:
        case IDM_FILTCHAN9:
        case IDM_FILTCHAN10:
        case IDM_FILTCHAN11:
        case IDM_FILTCHAN12:
        case IDM_FILTCHAN13:
        case IDM_FILTCHAN14:
        case IDM_FILTCHAN15:
    filter.channel[wCommand - IDM_FILTCHAN0] =
!filter.channel[wCommand - IDM_FILTCHAN0];
    DoMenuItemCheck(hWnd, wCommand,
filter.channel[wCommand - IDM_FILTCHAN0]);
            break;

        /* Setup filter structure for MIDI event filtering.
         */
        case IDM_NOTEOFF:
            filter.event.noteOff = !filter.event.noteOff;
    DoMenuItemCheck(hWnd, wCommand, filter.event.noteOff);
            break;
        case IDM_NOTEON:
            filter.event.noteOn = !filter.event.noteOn;
    DoMenuItemCheck(hWnd, wCommand, filter.event.noteOn);
            break;
        case IDM_POLYAFTERTOUCH:
            filter.event.keyAftertouch = !filter.event.keyAftertouch;
    DoMenuItemCheck(hWnd, wCommand, filter.event.keyAftertouch);
            break;
        case IDM_CONTROLCHANGE:
            filter.event.controller = !filter.event.controller;
    DoMenuItemCheck(hWnd, wCommand, filter.event.controller);
            break;
        case IDM_PROGRAMCHANGE:
            filter.event.progChange = !filter.event.progChange;
    DoMenuItemCheck(hWnd, wCommand, filter.event.progChange);
            break;
        case IDM_CHANNELAFTERTOUCH:
            filter.event.chanAftertouch = !filter.event.chanAftertouch;
    DoMenuItemCheck(hWnd, wCommand, filter.event.chanAftertouch);
            break;
        case IDM_PITCHBEND:
            filter.event.pitchBend = !filter.event.pitchBend;
    DoMenuItemCheck(hWnd, wCommand, filter.event.pitchBend);
            break;
        case IDM_CHANNELMODE:
            filter.event.channelMode = !filter.event.channelMode;
    DoMenuItemCheck(hWnd, wCommand, filter.event.channelMode);
            break;
        case IDM_SYSTEMEXCLUSIVE:
            filter.event.sysEx = !filter.event.sysEx;
    DoMenuItemCheck(hWnd, wCommand, filter.event.sysEx);
            break;
        case IDM_SYSTEMCOMMON:
            filter.event.sysCommon = !filter.event.sysCommon;
    DoMenuItemCheck(hWnd, wCommand, filter.event.sysCommon);
            break;
        case IDM_SYSTEMREALTIME:
            filter.event.sysRealTime = !filter.event.sysRealTime;
    DoMenuItemCheck(hWnd, wCommand, filter.event.sysRealTime);
            break;
        case IDM_ACTIVESENSE:
            filter.event.activeSense = !filter.event.activeSense;
    DoMenuItemCheck(hWnd, wCommand, filter.event.activeSense);
            break;

        default:
            break;
    }
}

                                                                        //
/* InitFirstInstance - Performs initializaion for the first instance
 *      of the application.
 *
 * Params:  hInstance - Instance handle.
 *
 * Return:  Returns 1 if there were no errors.  Otherwise, returns 0.
 */
BOOL InitFirstInstance(hInstance)
HANDLE hInstance;
{
    WNDCLASS wc;

    /* Define the class of window we want to register.
     */
    wc.lpszClassName    = szAppName;
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon            = LoadIcon(hInstance,"Icon");
    wc.lpszMenuName     = "Menu";
    wc.hbrBackground    = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.hInstance        = hInstance;
    wc.lpfnWndProc      = WndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;

    if(!RegisterClass(&wc))
        return FALSE;

    return TRUE;
}

                                                                        //
/* DoMenuItemCheck - Checks and unchecks menu items.

*
 * Params:  hWnd - Window handle for window associated with menu items.
 *          wMenuItem - The menu ID for the menu item.
 *          newState - The new checked/unchecked state of the menu item.
 *
 * Return:  void
*/
void  DoMenuItemCheck(
  HWNDhWnd,
  WORDwMenuItem,
  BOOLnewState)
{
    HMENU hMenu;

    hMenu = GetMenu(hWnd);
    CheckMenuItem(hMenu, wMenuItem, (newState ? MF_CHECKED: MF_UNCHECKED));
}

/* Error - Beeps and shows an error message.
 *
 * Params:  szMsg - Points to a NULL-terminated string containing the
 *              error message.
 *
 * Return:  Returns the return value from the MessageBox() call.
 *          Since this message box has only a single button, the
 *          return value isn't too meaningful.
 */
int Error(szMsg)
LPSTR szMsg;
{
    MessageBeep(0);
    return MessageBox(hMainWnd, szMsg, szAppName, MB_OK);
}

/******************************************************************************\
*
*  FUNCTION:GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:Load the resource string with the ID given, and return a
*pointer to it.Notice that the buffer is common memory so
*the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


MIDIMON.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *    midimon.h
 */

/* Compile-time application metrics
 */
#define MAX_NUM_DEVICES         8       // max # of MIDI input devices
#define INPUT_BUFFER_SIZE       200     // size of input buffer in events
#define DISPLAY_BUFFER_SIZE     1000    // size of display buffer in events

/* Menu IDs
 */
#define IDM_SAVE                101
#define IDM_EXIT                102
#define IDM_SETBUFSIZE          201
#define IDM_SETDISPLAY          202
#define IDM_SAVESETUP           203
#define IDM_SENDTOMAPPER        204
#define IDM_FILTCHAN0           300
#define IDM_FILTCHAN1           301
#define IDM_FILTCHAN2           302
#define IDM_FILTCHAN3           303
#define IDM_FILTCHAN4           304
#define IDM_FILTCHAN5           305
#define IDM_FILTCHAN6           306
#define IDM_FILTCHAN7           307
#define IDM_FILTCHAN8           308
#define IDM_FILTCHAN9           309
#define IDM_FILTCHAN10          310
#define IDM_FILTCHAN11          311
#define IDM_FILTCHAN12          312
#define IDM_FILTCHAN13          313
#define IDM_FILTCHAN14          314
#define IDM_FILTCHAN15          315
#define IDM_NOTEOFF             316
#define IDM_NOTEON              317
#define IDM_POLYAFTERTOUCH      318
#define IDM_CONTROLCHANGE       319
#define IDM_PROGRAMCHANGE       320
#define IDM_CHANNELAFTERTOUCH   321
#define IDM_PITCHBEND           322
#define IDM_CHANNELMODE         323
#define IDM_SYSTEMEXCLUSIVE     324
#define IDM_SYSTEMCOMMON        325
#define IDM_SYSTEMREALTIME      326
#define IDM_ACTIVESENSE         327
#define IDM_STARTSTOP           400
#define IDM_CLEAR               500
#define IDM_ABOUT               600

/* String resource IDs
 */
#define IDS_APPNAME             1

#define IDS_LABEL16
#define IDS_UNKNOWN_EVENT17
#define IDS_NOMIDIIN18
#define IDS_NOMEM_IBUF19
#define IDS_NOMEM_DBUF20
#define IDS_NOMEM21
#define IDS_START22
#define IDS_STOP23
#define IDS_WRITEERR24

/* Custom messages sent by low-level callback to application
 */
#define MM_MIDIINPUT    WM_USER + 0

/* The label for the display window.
 */
// now defined as IDS_LABEL in midimon.rc stringtable

/* Structure for translating virtual key messages to scroll messages.
 */
typedef struct keyToScroll_tag
{
     WORD wVirtKey;
     int  iMessage;
     WORD wRequest;
} KEYTOSCROLL;

/* Structure to represent a single MIDI event.
 */

#define EVNT_F_ERROR    0x00000001L

typedef struct event_tag
{
    DWORD fdwEvent;
    DWORD dwDevice;
    DWORD timestamp;
    DWORD data;
} EVENT;
typedef EVENT FAR *LPEVENT;

/* Function prototypes
 */
LRESULT FAR PASCAL WndProc(HWND, UINT, WPARAM , LPARAM);
VOID CommandMsg(HWND hWnd, WPARAM wParam, LPARAM lParam);
void DoMenuItemCheck(HWND hWnd, WORD menuItem, BOOL newState);
void SetupCustomChecks(HANDLE hInstance, HWND hWnd);
BOOL InitFirstInstance(HANDLE);
int Error(LPSTR msg);

LPTSTR GetStringRes (int id);


PREFER.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * prefer.c - Routines to get and set user preferences.
 */

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include "midimon.h"
#include "prefer.h"

/* getPreferences - Reads .INI file and gets the setup preferences.
 *      Currently, the only user preferences are window location and size.
 *      If the .INI file does not exist, returns default values.
 *
 * Params:  lpPreferences - Points to a PREFERENCES data structure that
 *              is filled with the retrieved user preferences.
 *
 * Return:  void
*/
void getPreferences(LPPREFERENCES lpPreferences)
{
    lpPreferences->iInitialX =
        GetPrivateProfileInt(INI_DISPLAYWINDOW, INI_X, DEF_X, INI_FILENAME);

    lpPreferences->iInitialY =
        GetPrivateProfileInt(INI_DISPLAYWINDOW, INI_Y, DEF_Y, INI_FILENAME);

    lpPreferences->iInitialW =
        GetPrivateProfileInt(INI_DISPLAYWINDOW, INI_W, DEF_W, INI_FILENAME);

    lpPreferences->iInitialH =
        GetPrivateProfileInt(INI_DISPLAYWINDOW, INI_H, DEF_H, INI_FILENAME);
}

/* setPreferences - Writes the .INI file with the given setup preferences.
 *
 * Params:  lpPreferences - Points to a PREFERENCES data structure containing
 *              the user preferences.
 *
 * Return:  void
 */
void setPreferences(LPPREFERENCES lpPreferences)
{
    char szTempString[20];

    while (1) { // This now aborts after one error.
sprintf(szTempString, "%d", lpPreferences->iInitialX);
if(WritePrivateProfileString(INI_DISPLAYWINDOW, INI_X,
  (LPSTR) szTempString, INI_FILENAME) == 0)
break;

sprintf(szTempString, "%d", lpPreferences->iInitialY);
if(WritePrivateProfileString(INI_DISPLAYWINDOW, INI_Y,
  (LPSTR) szTempString, INI_FILENAME) == 0)
break;

sprintf(szTempString, "%d", lpPreferences->iInitialW);
if(WritePrivateProfileString(INI_DISPLAYWINDOW, INI_W,
  (LPSTR) szTempString, INI_FILENAME) == 0)
break;

sprintf(szTempString, "%d", lpPreferences->iInitialH);
if(WritePrivateProfileString(INI_DISPLAYWINDOW, INI_H,
  (LPSTR) szTempString, INI_FILENAME) == 0)
break;
return;
}
Error(GetStringRes(IDS_WRITEERR));
}


PREFER.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 *  prefer.h
 */

/* MIDIMON.INI entries.
 */
#define INI_FILENAME        (LPSTR) "MIDIMON.INI"
#define INI_DISPLAYWINDOW   (LPSTR) "Display Window"
#define INI_X               (LPSTR) "x"
#define INI_Y               (LPSTR) "y"
#define INI_W               (LPSTR) "w"
#define INI_H               (LPSTR) "h"

/* Default values for preference variables.
 */
#define DEF_X               20
#define DEF_Y               20
#define DEF_W               481
#define DEF_H               256

/* Data structure used to specify user preferences.
 */
typedef struct preferences_tag
{
    int iInitialX;
    int iInitialY;
    int iInitialW;
    int iInitialH;
    DWORD dwInputBufferSize;
    DWORD dwDisplayBufferSize;
    int wDisplayFormat;
} PREFERENCES;
typedef PREFERENCES FAR *LPPREFERENCES;

/* Function prototypes
 */
void getPreferences(LPPREFERENCES);
void setPreferences(LPPREFERENCES);


ABOUT.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
 * about.c - Show the "About" box.
 */

#include <windows.h>
#include "about.h"

/* About - Shows the "About MIDI Monitor" dialog.
 *
 * Params:  hWnd - The application's main window handle.
 *          hInstance - The application's instance handle.
 *
 * Returns: void
 */

void About(
  HANDLE  hInstance,
  HWND  hWnd)
{
    DialogBox(hInstance, "About", hWnd, AboutDlgProc);
}

/* AboutDlgProc - The dialog procedure for the "About MIDI Monitor" dialog.
 *
 * Params:  hDlg - Specifies the associated dialog box.
 *          msg - Specifies the message from the dialog box.
 *    wParam - 32 bits of message-dependent data.
 *          lParam - 32 bits of message-dependent data.
 *
 * Returns: Non-zero if the message is processed, zero otherwise.
 */

int FAR PASCAL AboutDlgProc(
  HWND  hDlg,
  UINT  msg,
  WPARAM  wParam,
  LPARAM  lParam)
{
    switch (msg) {
      case WM_INITDIALOG:
        break;

      case WM_COMMAND:
        EndDialog(hDlg, TRUE);
        break;

      default:
        return FALSE;
        break;
    }

    return (TRUE);
}


README.TXT

Sample MIDI player.


DEBUG.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Debug.H
*
* Public include file for debug output
*
*****************************************************************************/

#ifndef _INC_DEBUG
#define _INC_DEBUG
#ifdef __cplusplus
extern "C"
{
#endif

#define  ISRDEBUG             1
#define  DEBUG_SECTION        "debug"        /* section name for                */
#define  DEBUG_MODULE_NAME    "MIDIPLYR"     /* key name and prefix for output  */
#define  DEBUG_MAX_LINE_LEN   255            /* max line length (bytes)         */

#define  DRV_ENABLE_DEBUG     (DRV_USER+1)   /* Enable/disable debug message    */
#define  DRV_SET_DEBUG_LEVEL  (DRV_USER+2)   /* Message to set the debug level  */

#ifdef DEBUG
    VOID WINAPI WinAssert(LPSTR lpstrExp, LPSTR lpstrFile, DWORD dwLine);
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define assert(exp) \
        ( (exp) ? (void) 0 : WinAssert(#exp, __FILE__, __LINE__) )

    #define DPF                  dprintf

    #define D1(sz)               dprintf(1,sz)
    #define D2(sz)               dprintf(2,sz)
    #define D3(sz)               dprintf(3,sz)
    #define D4(sz)               dprintf(4,sz)
#else
    #define assert(exp)          ((void)0)

    #define DbgEnable(x)         FALSE
    #define DbgSetLevel(x)       0
    #define DbgInitialize(x)     0

    #define DPF                  1 ? (void)0 : (void)

    #define D1(sz)
    #define D2(sz)
    #define D3(sz)
    #define D4(sz)
#endif

#ifdef __cplusplus
}
#endif
#endif


GLOBAL.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Global.H
*
* #define's everybody needs
*
*****************************************************************************/

#ifndef _GLOBAL_
#define _GLOBAL_

#ifndef _WIN32
#define  BCODE                  __based(__segname("_CODE"))
#define  BSTACK                 __based(__segname("_STACK"))
#define  BSEG(x)                __based(__segname(x))
#define  HUGE                   __huge
#else
#define  BCODE
#define  BSTACK
#define  BSEG(x)
#define  HUGE
#endif

/* Allow visibility of static functions for debug
*/
#ifdef DEBUG
#define  PUBLIC
#define  PRIVATE
#else
#define  PUBLIC
#define  PRIVATE                static
#endif

#define  FNLOCAL                NEAR PASCAL
#define  FNGLOBAL               FAR PASCAL
#define  FNEXPORT               FAR PASCAL __export __loadds

#endif


MAINWND.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* MainWnd.C
*
* Message handlers and UI for the main window and associated controls
*
*****************************************************************************/

#pragma warning(disable:4756)

#define _INC_SHELLAPI
#include <windows.h>
#undef _INC_SHELLAPI

#include <shellapi.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <commctrl.h>
#include <ctype.h>

#include "debug.h"

#include "MIDIPlyr.H"

#define BITMAP_COUNT    6           /* Number of buttons in bitmap */

PRIVATE HWND            ghWndToolbar                    = NULL;
PRIVATE HWND            ghWndStatus                     = NULL;
PRIVATE HWND            ghWndTime                       = NULL;
PRIVATE char            gszAppTitle[80]                 = "";
PRIVATE int             gnSB_TFPaneSize                 = 0;
PRIVATE char            gszOpenName[MAX_FILEPATH]       = "";
PRIVATE char            gszOpenTitle[MAX_FILEPATH]      = "";
PRIVATE char BCODE      gszFilter[]                      =
    "MIDI File (*.MID;*.RMI)\0*.MID;*.RMI\0"
    "All Files (*.*)\0*.*\0";

PRIVATE char BCODE      gszDefExtension[]               = "MID";
PRIVATE BOOL            gbAutoPlay                      = TRUE;
PRIVATE UINT            guDevice                        = 0;

PRIVATE TBBUTTON gatbButton[] =
{
    {0, -1,             TBSTATE_ENABLED, TBSTYLE_SEP,    0, 0,  0, -1},
    {0, IDM_OPEN,       TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0,  0, -1},
    {0, -1,             TBSTATE_ENABLED, TBSTYLE_SEP,    0, 0,  0, -1},
    {2, IDM_PLAY,       0,               TBSTYLE_BUTTON, 0, 0,  0, -1},
    {3, IDM_STOP,       0,               TBSTYLE_BUTTON, 0, 0,  0, -1},
    {4, IDM_PAUSE,      0,               TBSTYLE_BUTTON, 0, 0,  0, -1},
};

#define BUTTON_COUNT (sizeof(gatbButton)/sizeof(gatbButton[0]))

PRIVATE VOID FNLOCAL InitToolbar(HWND hWnd);
PRIVATE VOID FNLOCAL InitStatusBar(HWND hWnd);
PRIVATE VOID FNLOCAL ResizeStatusBar(HWND hWnd);
PRIVATE VOID FNLOCAL SyncUI(HWND hWnd);
PRIVATE VOID FNLOCAL SetOneAction(HWND hWnd, int nMenuID, BOOL fEnable);
PRIVATE VOID FNLOCAL AttemptFileOpen(HWND hWnd);
PRIVATE BOOL FNLOCAL PrerollAndWait(HWND hWnd);

PRIVATE BOOL FNLOCAL MWnd_OnCreate(HWND hWnd, CREATESTRUCT FAR* lpCreateStruct);
PRIVATE VOID FNLOCAL MWnd_OnGetMinMaxInfo(HWND hWnd, MINMAXINFO FAR* lpMinMaxInfo);
PRIVATE VOID FNLOCAL MWnd_OnSize(HWND hWnd, UINT state, int cx, int cy);
PRIVATE VOID FNLOCAL MWnd_OnPaint(HWND hWnd);
PRIVATE VOID FNLOCAL MWnd_OnDropFiles(HWND hWnd, HDROP hDrop);
PRIVATE VOID FNLOCAL MWnd_OnFileOpen(HWND hWnd);
PRIVATE VOID FNLOCAL MWnd_OnCommandToggleChild(HWND hWnd, UINT id);
PRIVATE VOID FNLOCAL MWnd_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify);
PRIVATE VOID FNLOCAL MWnd_OnDestroy(HWND hWnd);

/*****************************************************************************
*
* InitToolbar
*
* Called to create the toolbar
*
* HWND hWnd                 - Application window which toolbar is a child of
*
* Create and show the toolbar window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL InitToolbar(
    HWND                    hWnd)
{
    ghWndToolbar = CreateToolbarEx(hWnd,
                                   WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
                                   IDC_TOOLBAR,
                                   BITMAP_COUNT,
                                   ghInst,
                                   IDB_TOOLBAR,
                                   gatbButton,
                                   BUTTON_COUNT,
                                   0,  0,
                                   0,  0,
                                   sizeof(TBBUTTON));

    if (ghWndToolbar)
        ShowWindow(ghWndToolbar, SW_RESTORE);
}

/*****************************************************************************
*
* InitStatusBar
*
* Called to create the status bar
*
* HWND hWnd                 - Application window which status bar is a child of
*
* Create and show the status window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL InitStatusBar(
    HWND                    hWnd)
{
    HWND                    hWndDesktop;
    HFONT                   hFontStat;
    HDC                     hDC;
    UINT                    idx;
    SIZE                    size;

    ghWndStatus = CreateStatusWindow(WS_CHILD|SBARS_SIZEGRIP,
                                     "",
                                     hWnd,
                                     IDC_STATBAR);

    if (ghWndStatus)
    {
        hWndDesktop = GetDesktopWindow();
        hFontStat = (HFONT)SendMessage(ghWndStatus, WM_GETFONT, 0, 0L);
        hDC = GetDC(hWndDesktop);

        if (hFontStat != (HFONT)NULL && hDC != (HDC)NULL)
        {
            hFontStat = (HFONT)SelectObject(hDC, hFontStat);

            gnSB_TFPaneSize = 0;
            for (idx = 0; idx < N_TIME_FORMATS; idx++)
            {
                GetTextExtentPoint(hDC,
                                   grgszTimeFormats[idx],
                                   lstrlen(grgszTimeFormats[idx]),
                                   &size);

                gnSB_TFPaneSize = max(gnSB_TFPaneSize, size.cx);
            }

            SelectObject(hDC, hFontStat);

            gnSB_TFPaneSize *= 2;
        }

        if (hDC != (HDC)NULL)
            ReleaseDC(hWndDesktop, hDC);

        ResizeStatusBar(hWnd);

        FORWARD_WM_COMMAND(hWnd, gnTimeFormat, 0, 0, SendMessage);
        ShowWindow(ghWndStatus, SW_RESTORE);
    }
}

/*****************************************************************************
*
* ResizeStatusBar
*
* Force the status bar to resize to fit in the main window
*
* HWND hWnd                 - Application window which status bar is a child of
*
* Figure out the pane sizes and send a message to the status bar to set them.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL ResizeStatusBar(
    HWND                    hWnd)
{
    RECT                    rc;
    int                     rnPaneEdge[SB_N_PANES];

    GetClientRect(hWnd, &rc);

    /* SB_SETPARTS expects:
    **  wParam == Number of panes in status bar.
    **  lParam == Pointer to an array of int's indicating the right-hand
    **            coordinate of each pane.
    */
    rnPaneEdge[SB_PANE_STATE] = rc.right - gnSB_TFPaneSize;
    rnPaneEdge[SB_PANE_TFMT]  = -1;

    SendMessage(ghWndStatus,
                SB_SETPARTS,
                SB_N_PANES,
                (DWORD)(LPINT)(rnPaneEdge));
}

/*****************************************************************************
*
* SyncUI
*
* Bring all UI elements into sync with the state of the sequencer
*
* HWND hWnd                 - Application main window
*
* Build a flag word of the actions which are allowed from the current state.
* Set the menu items and toolbar buttons for each action appropriately.
* Show the current state as a string in the status bar.
* Depress the pause button if the sequencer is paused.
* Cause the time window to update.
*
*****************************************************************************/
#define SUI_F_CANPLAY       0x0001
#define SUI_F_CANPAUSE      0x0002
#define SUI_F_CANSTOP       0x0004
#define SUI_F_CANSELDEVICE  0x0008

PRIVATE VOID FNLOCAL SyncUI(
    HWND                    hWnd)
{
    WORD                    wActionFlags;
    UINT                    uState;
    char                    szState[40];
    BOOL                    fPress;

    wActionFlags = 0;
    uState = SEQ_S_NOFILE;

    if (gpSeq != NULL)
    {
        uState = gpSeq->uState;
        switch (uState)
        {
        case SEQ_S_NOFILE:
            wActionFlags = SUI_F_CANSELDEVICE;
            break;

        case SEQ_S_OPENED:
        case SEQ_S_PREROLLED:
            wActionFlags = SUI_F_CANPLAY|SUI_F_CANSELDEVICE;
            break;

        case SEQ_S_PAUSED:
        case SEQ_S_PLAYING:
            wActionFlags = SUI_F_CANPAUSE|SUI_F_CANSTOP;
            break;

        case SEQ_S_PREROLLING:
        case SEQ_S_STOPPING:
//            assert(0);
            wActionFlags = 0;
            break;
        }
    }

    fPress = (gpSeq->uState == SEQ_S_PAUSED);
    SendMessage(ghWndToolbar,
                TB_PRESSBUTTON,
                IDM_PAUSE,
                fPress);

    SetOneAction(hWnd, IDM_PLAY,   wActionFlags & SUI_F_CANPLAY);
    SetOneAction(hWnd, IDM_PAUSE,  wActionFlags & SUI_F_CANPAUSE);
    SetOneAction(hWnd, IDM_STOP,   wActionFlags & SUI_F_CANSTOP);

    EnableMenuItem(GetMenu(hWnd),
                   POS_PLAYTHRU,
                   MF_BYPOSITION|((wActionFlags & SUI_F_CANSELDEVICE) ? MF_ENABLED : MF_DISABLED));

    DrawMenuBar(hWnd);

    szState[0] = '\0';
    LoadString(ghInst, IDS_STATES + uState, szState, sizeof(szState));
    SendMessage(ghWndStatus, SB_SETTEXT, SB_PANE_STATE, (LPARAM)(LPSTR)szState);

    InvalidateRect(ghWndTime, NULL, TRUE);
}

/*****************************************************************************
*
* SetOneAction
*
* Update the state of one action in both the toolbar and action menu
*
* HWND hWnd                 - Application main window
* int nMenuID               - Menu ID of action
* BOOL fEnable              - Enable or disable this action
*
*****************************************************************************/
PRIVATE VOID FNLOCAL SetOneAction(
    HWND                hWnd,
    int                 nMenuID,
    BOOL                fEnable)
{
    EnableMenuItem(GetMenu(hWnd),
                   nMenuID,
                   MF_BYCOMMAND|(fEnable ? MF_ENABLED : MF_DISABLED));

    SendMessage(ghWndToolbar,
                TB_ENABLEBUTTON,
                nMenuID,
                (DWORD)fEnable);
}

/*****************************************************************************
*
* AttemptFileOpen
*
* Try to open the given file in the sequencer.
*
* HWND hWnd                 - Application main window
*
* Stop and close the current file.
* Open the new file.
* Preroll the new file.
* Set the title test for the main window.
* Call SyncUI to update available actions.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL AttemptFileOpen(
    HWND                    hWnd)
{
    MMRESULT                mmrc;
    PSTR                    pStrFile    = gszUntitled;

    /* Stop, close, etc. if we're still playing
    */
    DPF(1, "AttemptFileOpen: Calling seqStop(); state = %u", gpSeq->uState);

    mmrc = seqStop(gpSeq);
if (mmrc != MMSYSERR_NOERROR)
{
Error(hWnd, IDS_STOPFAILED, mmrc);
return;
}

    DPF(1, "Calling seqCloseFile(); state = %u", gpSeq->uState);
    seqCloseFile(gpSeq);

    DPF(1, "Calling seqOpenFile(); state = %u", gpSeq->uState);
    /* Open new file
    */

    gpSeq->pstrFile = gszOpenName;
    mmrc = seqOpenFile(gpSeq);
    if (mmrc != MMSYSERR_NOERROR)
    {
        Error(hWnd, IDS_OPENFAILED, mmrc);
        return;
    }

    pStrFile = gszOpenTitle;

    wsprintf(gszAppTitle, gszAppTitleMask, (LPSTR)pStrFile);
    SetWindowText(hWnd, gszAppTitle);

    SyncUI(hWnd);
}

/*****************************************************************************
*
* PrerollAndWait
*
* Prerolls the sequencer using the current device ID and file.
*
* HWND hWnd                 - Current window
*
* Just call preroll and loop processing messages until done.
*
*****************************************************************************/
PRIVATE BOOL FNLOCAL PrerollAndWait(
    HWND                    hWnd)
{
    PREROLL                 preroll;
    MMRESULT                mmrc;

    preroll.tkBase = 0;
    preroll.tkEnd  = gpSeq->tkLength;

    gpSeq->uDeviceID = guDevice;

    if (MMSYSERR_NOERROR != (mmrc = seqPreroll(gpSeq, &preroll)))
{
        Error(hWnd, IDS_PREROLLFAILED, mmrc);
        return FALSE;
}

return TRUE;
}

/*****************************************************************************
*
* MWnd_OnCreate
*
* Handle WM_CREATE message to main application window.
*
* HWND hWnd                 - Window handle
* CREATESTRUCT FAR* lpCreateStruct
*                           - Pointer to creation parameters for the window.
*
* Returns TRUE on success. Returning FALSE will cause the window to be
* destroyed and the application will exit.
*
* Set the default time format.
* Create the tool and status bars.
* Create the time window as a child of the main app window and show it.
* Set the main window's title to show no document ('Untitled').
* Accept drag/drop files.
* Call SyncUI to update the enable status of the toolbar and menu items.
*
*****************************************************************************/
PRIVATE BOOL FNLOCAL MWnd_OnCreate(
    HWND                    hWnd,
    CREATESTRUCT FAR*       lpCreateStruct)
{
    HMENU                   hMenu;
    HMENU                   hMenuOptions;
    HMENU                   hMenuPlayThru;
    UINT                    cDevs;
    UINT                    idx;
    RECT                    rc;
    MIDIOUTCAPS             moutCaps;

    gnTimeFormat = IDS_TF_FIRST;

    InitToolbar(hWnd);
    InitStatusBar(hWnd);

    hMenu = GetMenu(hWnd);
    hMenuOptions = GetSubMenu(hMenu, POS_OPTIONS);
    hMenuPlayThru = GetSubMenu(hMenu, POS_PLAYTHRU);

    AppendMenu(hMenuOptions, MF_SEPARATOR, 0, NULL);

    for (idx = 0; idx < N_TIME_FORMATS; idx++)
    {
        AppendMenu(hMenuOptions,
                   MF_ENABLED|MF_STRING,
                   IDS_TF_FIRST + idx,
                   grgszTimeFormats[idx]);
    }

    cDevs = midiOutGetNumDevs();
    if (cDevs)
        AppendMenu(hMenuPlayThru, MF_SEPARATOR, 0, NULL);

    for (idx = 0; idx < cDevs; idx++)
    {
        if (midiOutGetDevCaps(idx, &moutCaps, sizeof(moutCaps)) == MMSYSERR_NOERROR)
        {
            AppendMenu(hMenuPlayThru,
                       MF_ENABLED|MF_STRING,
                       IDM_DEVICES + idx,
                       moutCaps.szPname);
        }
    }

    CheckMenuItem(hMenu, IDM_TOOLBAR, MF_BYCOMMAND|MF_CHECKED);
    CheckMenuItem(hMenu, IDM_STATUS,  MF_BYCOMMAND|MF_CHECKED);
    CheckMenuItem(hMenu, IDM_AUTOPLAY,MF_BYCOMMAND|MF_CHECKED);
    CheckMenuItem(hMenu, gnTimeFormat,MF_BYCOMMAND|MF_CHECKED);
    CheckMenuItem(hMenu, IDM_DEVICES,  MF_BYCOMMAND|MF_CHECKED);

    GetClientRect(hWnd, &rc);

    ghWndTime = CreateWindow(
        gszTWndClass,
        NULL,
        WS_CHILD,
        rc.left, rc.top,
        rc.right-rc.left, rc.bottom-rc.top,
        hWnd,
        NULL,
        lpCreateStruct->hInstance,
        NULL);

    ShowWindow(ghWndTime, SW_RESTORE);

    wsprintf(gszAppTitle, gszAppTitleMask, (LPSTR)gszUntitled);
    SetWindowText(hWnd, gszAppTitle);

    DragAcceptFiles(hWnd, TRUE);

    SyncUI(hWnd);

    return TRUE;
}

/*****************************************************************************
*
* MWnd_OnGetMinMaxSize
*
* Handle WM_GETMINMAXSIZE message to main application window.
*
* HWND hWnd                 - Window handle
* MINMAXINFO FAR* lpMinMaxInfo
*                           - Pointer to min/max tracking information
*
* This message is sent to a window before resize tracking begins. The
* lpMinMaxInfo structure contains the minimum and maximum x and y values
* the window can be resized to.
*
* We don't allow resizing small enough to cause the status bar and toolbar
* to overlap so they don't try to draw over each other.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnGetMinMaxInfo(
    HWND                    hWnd,
    MINMAXINFO FAR*         lpMinMaxInfo)
{
    RECT                    rc;

    GetWindowRect(hWnd, &rc);

    /* Only go small enough that our client area after children is zero.
    */
    lpMinMaxInfo->ptMinTrackSize.y =
        (rc.bottom - rc.top) -
        (grcTWnd.bottom - grcTWnd.top);
}

/*****************************************************************************
*
* MWnd_OnSize
*
* Handle WM_SIZE message to main application window.
*
* HWND hWnd                 - Window handle
* UINT state                - Some SIZE_xxx code indicating what type of
*                             size operation this is.
* int  cx, cy               - New x and y size of the window's client area.
*
* Get the new client area.
* Adjust the client area for the toolbar and status bar if they exist.
* Make sure the client area isn't a negative height and adjust if it is.
* Resize the time window to fit in our new client area.
* Forward the WM_SIZE to the time window so it can resize its font.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnSize(
    HWND                    hWnd,
    UINT                    state,
    int                     cx,
    int                     cy)
{
    RECT                    rc;
    RECT                    rcClient;

    GetClientRect(hWnd, &rcClient);
    if (ghWndToolbar != NULL)
    {
        /* Cause the toolbar to be aware of the size change
        */
        FORWARD_WM_SIZE(ghWndToolbar, SIZE_RESTORED, 0, 0, SendMessage);

        GetWindowRect(ghWndToolbar, &rc);
        rcClient.top += (rc.bottom - rc.top);
    }

    if (ghWndStatus != NULL)
    {
        ResizeStatusBar(hWnd);

        /* Cause the status bar to be aware of the size change
        */
        FORWARD_WM_SIZE(ghWndStatus, SIZE_RESTORED, 0, 0, SendMessage);

        GetWindowRect(ghWndStatus, &rc);
        rcClient.bottom -= (rc.bottom - rc.top);
    }

    /* Do we need to resize entire window so the tool/status bars
    ** don't overlap? (The only case where this can happen is
    ** on creation of one of the two -- we set minimum tracking so
    ** a user can't manually resize the window to cause this
    ** condition).
    */
    if (rcClient.bottom < rcClient.top)
    {
        GetWindowRect(hWnd, &rc);
        SetWindowPos(hWnd,
                     (HWND)NULL,
                     0, 0,
                     rc.right - rc.left + 1,
                     rc.bottom - rc.top + 1 - rcClient.top - rcClient.bottom,
                     SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
    }

    SetWindowPos(ghWndTime,
                 (HWND)NULL,
                 rcClient.left,
                 rcClient.top,
                 rcClient.right - rcClient.left,
                 rcClient.bottom - rcClient.top,
                 SWP_NOACTIVATE|SWP_NOZORDER);

    FORWARD_WM_SIZE(ghWndTime, SIZE_RESTORED, 0, 0, SendMessage);
}

/*****************************************************************************
*
* MWnd_OnPaint
*
* Handle WM_PAINT message to main application window.
*
* HWND hWnd                 - Window handle
*
* Just do a BeginPaint/EndPaint pair so USER will mark the area
*   as valid. All the real work of painting the time is done
*   by the WM_PAINT handler for the time window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnPaint(
    HWND                    hWnd)
{
    PAINTSTRUCT             ps;
    HDC                     hDC;

    hDC = BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
}

/*****************************************************************************
*
* MWnd_OnDropFiles
*
* Handle WM_DROPFILES message to main application window.
*
* HWND hWnd                 - Window handle
* HDROP hDrop               - Handle to dropped file information
*
* Get the 0th filename and free the drop handle.
* Extract the file title from the full pathname.
* Open the file.
* If we opened successfully, start playback by forwarding a WM_COMMAND
*   of IDM_PLAY to the main window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnDropFiles(
    HWND                    hWnd,
    HDROP                   hDrop)
{
    PSTR                    pStr;

    /* For multiple selections, we only accept the first file
    */
    DragQueryFile(hDrop, 0, gszOpenName, sizeof(gszOpenName));
    DragFinish(hDrop);

    /* We don't get OpenTitle like we do from GetOpenFileName; need to
    ** figure this out for ourselves
    */
    pStr = gszOpenName + lstrlen(gszOpenName) - 1;

    while (pStr >= gszOpenName && *pStr != '/' && *pStr != '\\' && *pStr != ':')
        pStr--;

    pStr++;
    lstrcpy(gszOpenTitle, pStr);

    AttemptFileOpen(hWnd);

    if (gbAutoPlay && gpSeq->uState == SEQ_S_OPENED)
        FORWARD_WM_COMMAND(hWnd, IDM_PLAY, (HWND)NULL, 0, SendMessage);
}

/*****************************************************************************
*
* MWnd_OnFileOpen
*
* Handle WM_COMMAND/IDM_OPEN message to main application window.
*
* HWND hWnd                 - Window handle
*
* Fill in the OPENFILENAME struct and call GetOpenFileName.
* If not canceled, try to open the file.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnFileOpen(
    HWND                    hWnd)
{
    OPENFILENAME            ofn;

    *gszOpenName = '\0';

ofn.lStructSize= sizeof(OPENFILENAME);
ofn.hwndOwner= hWnd;
ofn.lpstrFilter= gszFilter;
ofn.lpstrCustomFilter= (LPSTR)NULL;
ofn.nMaxCustFilter= 0L;
ofn.nFilterIndex= 1L;
ofn.lpstrFile= gszOpenName;
ofn.nMaxFile= MAX_FILEPATH;
ofn.lpstrFileTitle= gszOpenTitle;
ofn.nMaxFileTitle= MAX_FILEPATH;
ofn.lpstrTitle= (LPSTR)NULL;
ofn.lpstrInitialDir= (LPSTR)NULL;
ofn.Flags= OFN_HIDEREADONLY|OFN_FILEMUSTEXIST;
ofn.nFileOffset= 0;
ofn.nFileExtension= 0;
ofn.lpstrDefExt= gszDefExtension;

    if (!GetOpenFileName(&ofn))
        return;

    AttemptFileOpen(hWnd);
}

/*****************************************************************************
*
* MWnd_OnCommandToggleChild
*
* Handle WM_COMMAND message of toggle tool or status bar to main application
* window.
*
* HWND hWnd                 - Window handle
* UINT id                   - Control id of menu selection; either
*                             IDM_TOOLBAR or IDM_STATUS
*
* Get the current menu item check state.
* Destroy or create the child as needed.
* Send a WM_SIZE to the main window so client area will be recalculated.
* Toggle the menu item check state.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnCommandToggleChild(
    HWND                    hWnd,
    UINT                    id)
{
    HMENU                   hMenu;
    UINT                    uState;
    HWND*                   phWnd;

    phWnd = (id == IDM_TOOLBAR) ? &ghWndToolbar : &ghWndStatus;

    hMenu = GetMenu(hWnd);
    uState = GetMenuState(hMenu, id, MF_BYCOMMAND);
    if (uState & MF_CHECKED)
    {
        DestroyWindow(*phWnd);
        *phWnd = NULL;
    }
    else
    {
        if (id == IDM_TOOLBAR)
            InitToolbar(hWnd);
        else
            InitStatusBar(hWnd);
    }

    SendMessage(hWnd, WM_SIZE, 0, 0L);

    uState ^= MF_CHECKED;
    uState &= MF_CHECKED;
    CheckMenuItem(hMenu, id, MF_BYCOMMAND|uState);

    SyncUI(hWnd);
}

/*****************************************************************************
*
* MWnd_OnCommand
*
* Handle WM_COMMAND message to main application window.
*
* HWND hWnd                 - Window handle
* int id                    - id of control or menu causing WM_COMMAND
* HWND hwndCtl              - Window handle of child control, if any
* UINT codeNotify           - Notification code if this message is from a
*                             control.
*
* For a press of the toolbar buttons or their menu equivalents, just load
* a resource string and display it in the status bar.
*
* For an exit request, send ourselves a WM_CLOSE message.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnCommand(
    HWND                    hWnd,
    int                     id,
    HWND                    hWndCtl,
    UINT                    codeNotify)
{
    HMENU                   hMenu;
    int                     nIdxFormat;
    LPSTR                   lpstr;

    if (id >= IDS_TF_FIRST && id <= IDS_TF_LAST)
    {
        if (NULL != ghWndStatus)
        {
            nIdxFormat = id - IDS_TF_FIRST;

            lpstr = (LPSTR)(grgszTimeFormats[nIdxFormat]);

            SendMessage(ghWndStatus,
                        SB_SETTEXT,
                        SB_PANE_TFMT,
                        (LPARAM)lpstr);

        }

        hMenu = GetMenu(hWnd);

        CheckMenuItem(hMenu, gnTimeFormat, MF_UNCHECKED|MF_BYCOMMAND);
        CheckMenuItem(hMenu, id, MF_CHECKED|MF_BYCOMMAND);

        gnTimeFormat = id;

        /* Force time window to update font and repaint entire time string
         */

        if(ghWndTime)// for when WM_COMMAND is called before WM_CREATE
        FORWARD_WM_SIZE(ghWndTime, SIZE_RESTORED, 0, 0, SendMessage);
    }
    else if (id >= IDM_DEVMIN && id <= IDM_DEVMAX)
    {
        hMenu = GetMenu(hWnd);

        CheckMenuItem(hMenu, guDevice + IDM_DEVICES, MF_UNCHECKED|MF_BYCOMMAND);
        guDevice = id - IDM_DEVICES;
        CheckMenuItem(hMenu, guDevice + IDM_DEVICES, MF_CHECKED|MF_BYCOMMAND);
    }
    else switch(id)
    {
        case IDM_OPEN:
            MWnd_OnFileOpen(hWnd);
            break;

        case IDM_TOOLBAR:
        case IDM_STATUS:
            MWnd_OnCommandToggleChild(hWnd, id);
            break;

        case IDM_AUTOPLAY:
            gbAutoPlay = !gbAutoPlay;
            CheckMenuItem(GetMenu(hWnd),
                          IDM_AUTOPLAY,
                          MF_BYCOMMAND|(gbAutoPlay ? MF_CHECKED : MF_UNCHECKED));
            break;

        case IDM_PLAY:
            FORWARD_WM_COMMAND(ghWndTime, IDM_PLAY, 0, 0, SendMessage);

if (gpSeq->uState != SEQ_S_OPENED)
DPF(1, "IDM_PLAY: State %u", gpSeq->uState);

            if (PrerollAndWait(hWnd))
            seqStart(gpSeq);

            SyncUI(hWnd);
            break;

        case IDM_STOP:
            FORWARD_WM_COMMAND(ghWndTime, IDM_STOP, 0, 0, SendMessage);

            seqStop(gpSeq);
            SyncUI(hWnd);
            break;

        case IDM_PAUSE:
            if (gpSeq->uState == SEQ_S_PAUSED)
            {
                seqRestart(gpSeq);
            }
            else
            {
                seqPause(gpSeq);
            }

            SyncUI(hWnd);
            break;

        case IDM_SYNCUI:
            SyncUI(hWnd);
            break;

        case IDM_EXIT:
            SendMessage(hWnd, WM_CLOSE, 0, 0L);
            break;
    }
}

/*****************************************************************************
*
* MWnd_OnDestroy
*
* Handle WM_DESTROY message to main application window.
*
* HWND hWnd                 - Window handle
*
* Our main application window has been closed. PostQuitMessage so the main
* message loop will exit and the app can terminate.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnDestroy(
    HWND                    hWnd)
{
    seqStop(gpSeq);
    PostQuitMessage(0);
}

/*****************************************************************************
*
* MWnd_WndProc
*
* Window procedure for main application window.
*
* HWND hWnd                 - Window handle
* UINT msg                  - Message code
* WPARAM wParam             - Message specific parameter
* LPARAM lParam             - Message specific parameter

*
* Dispatch messages we care about to the appropriate handler, else just
* call DefWindowProc.
*
* Note this use of message cracker macros from windowsx.h. Using these
* macros will shield you from the differences between Win16 and Win32;
* if your app is cross-compilable, you should use these and save yourself
* some headaches!
*
*****************************************************************************/
LRESULT CALLBACK MWnd_WndProc(
    HWND                    hWnd,
    UINT                    msg,
    WPARAM                  wParam,
    LPARAM                  lParam)
{
    switch( msg )
    {
        HANDLE_MSG(hWnd, WM_CREATE,         MWnd_OnCreate);
        HANDLE_MSG(hWnd, WM_GETMINMAXINFO,  MWnd_OnGetMinMaxInfo);
        HANDLE_MSG(hWnd, WM_SIZE,           MWnd_OnSize);
        HANDLE_MSG(hWnd, WM_PAINT,          MWnd_OnPaint);
        HANDLE_MSG(hWnd, WM_DROPFILES,      MWnd_OnDropFiles);
        HANDLE_MSG(hWnd, WM_COMMAND,        MWnd_OnCommand);
        HANDLE_MSG(hWnd, WM_DESTROY,        MWnd_OnDestroy);

        case MMSG_DONE:
            FORWARD_WM_COMMAND(ghWndTime, IDM_STOP, 0, 0, SendMessage);

if (gpSeq->uState != SEQ_S_OPENED)
DPF(1, "MMSG_DONE and state %u", gpSeq->uState);

            SyncUI(hWnd);
            break;

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

    return 0;
}


MIDIPLYR.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* MIDIPlyr.C
*
* Initialization code for the polymessage MIDI playback app.
*
*****************************************************************************/

#pragma warning(disable:4756)

#define _INC_SHELLAPI
#include <windows.h>
#undef _INC_SHELLAPI

#include <shellapi.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <commctrl.h>
#include <ctype.h>

#include "debug.h"

#include "MIDIPlyr.H"

PUBLIC  char BCODE      gszMWndClass[]      = "MIDIPlyrMWnd";
PUBLIC  char BCODE      gszTWndClass[]      = "MIDIPlyrTWnd";
PUBLIC  HINSTANCE       ghInst              = NULL;
PUBLIC  PSEQ            gpSeq               = NULL;
PUBLIC  char            gszUntitled[80]     = "";
PUBLIC  char            gszAppLongName[80]  = "";
PUBLIC  char            gszAppTitleMask[80] = "";
PUBLIC  char            grgszTimeFormats[N_TIME_FORMATS][CB_TIME_FORMATS] = {0};
PUBLIC  RECT            grcTWnd             = {0, 0, 0, 0};
PUBLIC  int             gnTimeFormat        = 0;

PRIVATE BOOL FNLOCAL InitApp(HINSTANCE hInst);
PRIVATE BOOL FNLOCAL InitInstance(HINSTANCE hInst, int nCmdShow);
PRIVATE VOID FNLOCAL TerminateInstance(VOID);

/*****************************************************************************
*
* WinMain
*
* Called by C startup code.
*
* HANDLE hInst              - Instance handle of this instance
* HANDLE hPrevInst          - Instance handle of previous instance or
*                             NULL if we are the first instance
* LPSTR lpstrCmdLine        - Any command line arguments
* int nCmdShow              - Code for ShowWindow which tells us what state
*                             to initially show the main application window.
*
* Initialize application if first instance.
* Initialize instance.
* Stay in main message processing loop until exit.
*
*****************************************************************************/
int PASCAL WinMain(
    HINSTANCE                  hInst,
    HINSTANCE                  hPrevInst,
    LPSTR                   lpstrCmdLine,
    int                     nCmdShow)
{
    MSG                     msg;

    if (hPrevInst == NULL)
        if (!InitApp(hInst))
            return 0;

    if (!InitInstance(hInst, nCmdShow))
    {
        TerminateInstance();
        return 0;
    }

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    TerminateInstance();

    return (msg.wParam);
}

/*****************************************************************************
*
* InitApp
*
* Called for one-time initialization if we are the first app instance.
*
* HANDLE hInst              - Instance handle of this instance
*
* Returns TRUE on success.
*
* Register the window class for the main window and the time window.
*
*****************************************************************************/
BOOL FNLOCAL InitApp(
    HINSTANCE               hInst)
{
    WNDCLASS                wc;

    InitCommonControls();

    /* Don't specify CS_HREDRAW or CS_VREDRAW if you're going to use the
    ** commctrl status or toolbar -- invalidate the (remaining) client
    ** area yourself if you want this behavior. This will allow the child
    ** control redraws to be much more efficient.
    */
    wc.style =          0;
    wc.lpfnWndProc =    MWnd_WndProc;
    wc.cbClsExtra =     0;
    wc.cbWndExtra =     0;
    wc.hInstance =      hInst;
    wc.hIcon =          LoadIcon(hInst, MAKEINTRESOURCE(ID_ICON));
    wc.hCursor =        LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszMenuName =   MAKEINTRESOURCE(ID_MENU);
    wc.lpszClassName =  (LPCSTR)gszMWndClass;

    RegisterClass(&wc);

    wc.style =          CS_HREDRAW|CS_VREDRAW;
    wc.lpfnWndProc =    TWnd_WndProc;
    wc.cbClsExtra =     0;
    wc.cbWndExtra =     0;
    wc.hInstance =      hInst;
    wc.hIcon =          LoadIcon(hInst, MAKEINTRESOURCE(ID_ICON));
    wc.hCursor =        LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName =   NULL;
    wc.lpszClassName =  (LPCSTR)gszTWndClass;

    RegisterClass(&wc);

    return TRUE;
}

/*****************************************************************************
*
* InitInstance
*
* Called once per instance of application.
*
* HANDLE hInst              - Instance handle of this instance
* int nCmdShow              - Code for ShowWindow which tells us what state
*                             to initially show the main application window.
* Returns TRUE on success.
*
* Initialize debug library.
* Save the instance handle.
* Load global resource strings.
* Allocate and initialize the global sequencer structure.
* Create the main window.
* Add time formats to the options menu.
* Show the main window.
*
*****************************************************************************/
BOOL FNLOCAL InitInstance(
    HINSTANCE               hInst,
    int                     nCmdShow)
{
    HWND                    hWnd;
    int                     idx;

    DbgInitialize(TRUE);

    ghInst = hInst;

    LoadString(hInst, IDS_APPTITLEMASK, gszAppTitleMask, sizeof(gszAppTitleMask));
    LoadString(hInst, IDS_APPNAME,      gszAppLongName,  sizeof(gszAppLongName));
    LoadString(hInst, IDS_UNTITLED,     gszUntitled,     sizeof(gszUntitled));

    for (idx = 0; idx < N_TIME_FORMATS; idx++)
    {
        *grgszTimeFormats[idx] = '\0';
        LoadString(hInst,
                   IDS_TF_FIRST+idx,
                   grgszTimeFormats[idx],
                   sizeof(grgszTimeFormats[idx]));
    }

    if ((gpSeq = (PSEQ)LocalAlloc(LPTR, sizeof(SEQ))) == NULL)
        return FALSE;

    gpSeq->cBuffer  = C_MIDI_BUFFERS;
    gpSeq->cbBuffer = CB_MIDI_BUFFERS;

    if (seqAllocBuffers(gpSeq) != MMSYSERR_NOERROR)
        return FALSE;

    hWnd = CreateWindow(
        gszMWndClass,
        NULL,
        WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,
        CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,
        HWND_DESKTOP,
        NULL,
        hInst,
        NULL);

    if (hWnd == (HWND)NULL)
        return FALSE;

    gpSeq->hWnd = hWnd;

    ShowWindow(hWnd, nCmdShow);

    return TRUE;
}

/*****************************************************************************
*
* TerminateInstance
*
* Release any resources for the current instance
*
*****************************************************************************/
VOID FNLOCAL TerminateInstance(
    VOID)
{
    if (gpSeq != NULL)
    {
        seqFreeBuffers(gpSeq);
        LocalFree((HLOCAL)gpSeq);
        gpSeq = NULL;
    }
}


MIDIPLYR.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* MIDIPlyr.H
*
* Main include file for the polymessage MIDI playback app.
*
*****************************************************************************/

#ifndef _MIDIPLYR_
#define _MIDIPLYR_

#include "seq.h"

#define MAX_FILEPATH        256

/* Number and size of playback buffers to keep around
*/
#define C_MIDI_BUFFERS      4
#define CB_MIDI_BUFFERS     1024

/* Popup menu positions in main menu bar
*/
#define POS_FILE            0
#define POS_ACTIONS         1
#define POS_OPTIONS         2
#define POS_PLAYTHRU        3

/* Status bar pane indices
*/
#define SB_N_PANES          2
#define SB_PANE_STATE       0
#define SB_PANE_TFMT        1

/* Resource ID's
*/
#define ID_ICON             50
#define ID_MENU             51

#define IDM_EXIT            100
#define IDM_ABOUT           101
#define IDM_OPEN            102
#define IDM_PLAY            103
#define IDM_PAUSE           104
#define IDM_STOP            105
#define IDM_TOOLBAR         106
#define IDM_STATUS          107
#define IDM_AUTOPLAY        108

#define IDM_DEVMIN          129
#define IDM_MAPPER          129         /* MUST be IDM_DEVICES - 1 */
#define IDM_DEVICES         130         /* 129 thru 149 */
#define IDM_DEVMAX          149

#define IDM_SYNCUI          150         /* DEBUG */

#define IDB_TOOLBAR         200

#define IDC_TOOLBAR         300
#define IDC_STATBAR         301

#define IDS_APPTITLEMASK    1000
#define IDS_APPNAME         1001
#define IDS_UNTITLED        1002

/* ID's for these must be contiguous !!!
** Note that we also use these as IDM_ items in the Options menu
*/
#define IDS_TF_FIRST        1010
#define IDS_HMS             1010
#define IDS_TICKS           1011
#define IDS_TF_LAST         1011

/* ID's for sequencer state descriptions
** These must also be contigous and in the same order as the SEQ_S
** states in seq.h
*/
#define IDS_STATES          1020
#define IDS_NOFILE          (IDS_STATES + SEQ_S_NOFILE)
#define IDS_OPENED          (IDS_STATES + SEQ_S_OPENED)
#define IDS_PREROLLING      (IDS_STATES + SEQ_S_PREROLLING)
#define IDS_PREROLLED       (IDS_STATES + SEQ_S_PREROLLED)
#define IDS_PLAYING         (IDS_STATES + SEQ_S_PLAYING)
#define IDS_PAUSED          (IDS_STATES + SEQ_S_PAUSED)
#define IDS_STOPPING        (IDS_STATES + SEQ_S_STOPPING)

#define N_TIME_FORMATS      (IDS_TF_LAST - IDS_TF_FIRST + 1)
#define CB_TIME_FORMATS     40

#define IDS_OPENFAILED      1050
#define IDS_PREROLLFAILED   1051
#define IDS_TESTERR         1052
#define IDS_STOPFAILED1053

/* Globals
*/
extern  HINSTANCE       ghInst;
extern  char BCODE      gszMWndClass[];
extern  char BCODE      gszTWndClass[];
extern  PSEQ            gpSeq;
extern  char            gszUntitled[80];
extern  char            gszAppLongName[80];
extern  char            gszAppTitleMask[80];
extern  char            grgszTimeFormats[N_TIME_FORMATS][CB_TIME_FORMATS];
extern  RECT            grcTWnd;
extern  int             gnTimeFormat;

/* MainWnd.C
*/
VOID FNLOCAL InitToolbar(
    HWND                    hWnd);

VOID FNLOCAL InitToolbar(
    HWND                    hWnd);

LRESULT CALLBACK MWnd_WndProc(
    HWND                    hWnd,
    UINT                    msg,
    WPARAM                  wParam,
    LPARAM                  lParam);

/* TimeWnd.C
*/
LRESULT CALLBACK TWnd_WndProc(
    HWND                    hWnd,
    UINT                    msg,
    WPARAM                  wParam,
    LPARAM                  lParam);

/* UiUtils.C
*/
VOID FNLOCAL MessagePump(
    VOID);

VOID FNLOCAL Error(
     HWND                   hWnd,
     int                    nErrDesc,
     MMRESULT               mmrc);

VOID FNLOCAL EmbossedTextOut(
     HDC                    hDC,
     int                    x,
     int                    y,
     LPSTR                  lpsz,
     UINT                   cb,
     COLORREF               crText,
     COLORREF               crShadow,
     int                    cx,
     int                    cy);

HFONT FNLOCAL CreateScaledFont(
     HDC                    hDC,
     LPRECT                 lpRect,
     LPSTR                  lpszFormat,
     int                    anPosX[],
     int* nPosY);

#endif


MULDIV32.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
*  MulDiv32.h
*
*  Description:
*      math routines for 32 bit signed and unsiged numbers.
*
*      MulDiv32(a,b,c) = (a * b) / c         (round down, signed)
*
*      MulDivRD(a,b,c) = (a * b) / c         (round down, unsigned)
*      MulDivRN(a,b,c) = (a * b + c/2) / c   (round nearest, unsigned)
*      MulDivRU(a,b,c) = (a * b + c-1) / c   (round up, unsigned)
*
*****************************************************************************/

#ifndef _INC_MULDIV32
#define _INC_MULDIV32

#ifndef INLINE
#define INLINE __inline
#endif

#ifdef _WIN32

    //----------------------------------------------------------------------;
    //
    //  Win 32
    //
    //----------------------------------------------------------------------;

    #ifdef _X86_

        //
        //  Use 32-bit x86 assembly.
        //

        #pragma warning(disable:4035 4704)

#define MulDivRN MulDiv// Doesn't work with /Ox; MSVC20 messes up
// the inline asm code, instead of including
// it prolog/verbatim/epilog.
// So use the Windows function for now
        #if 0
        INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     imul    ebx              //  imul ebx
            _asm     idiv    ecx              //  idiv ecx
            _asm shld edx, eax, 16     //  shld edx, eax, 16

        } // MulDiv32()

        INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     mov     ebx,ecx          //  mov  ebx,ecx
            _asm     shr     ebx,1            //  sar  ebx,1
            _asm     add     eax,ebx          //  add  eax,ebx
            _asm     adc     edx,0            //  adc  edx,0
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDiv32()

        INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     mov     ebx,ecx          //  mov  ebx,ecx
            _asm     dec     ebx              //  dec  ebx
            _asm     add     eax,ebx          //  add  eax,ebx
            _asm     adc     edx,0            //  adc  edx,0
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDivRU32()

        INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
        {
            _asm     mov     eax,dword ptr a  //  mov  eax, a
            _asm     mov     ebx,dword ptr b  //  mov  ebx, b
            _asm     mov     ecx,dword ptr c  //  mov  ecx, c
            _asm     mul     ebx              //  mul  ebx
            _asm     div     ecx              //  div  ecx
            _asm     shld    edx, eax, 16     //  shld edx, eax, 16

        } // MulDivRD32()
        #endif

        #pragma warning(default:4035 4704)

    #else

        //
        //  Use C9 __int64 support for Daytona RISC platforms.
        //

        INLINE LONG MulDiv32( LONG a, LONG b, LONG c )
        {
            return (LONG)( Int32x32To64(a,b) / c );
        }

        INLINE DWORD MulDivRD( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( UInt32x32To64(a,b) / c );
        }

        INLINE DWORD MulDivRN( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( (UInt32x32To64(a,b)+c/2) / c );
        }

        INLINE DWORD MulDivRU( DWORD a, DWORD b, DWORD c )
        {
            return (DWORD)( (UInt32x32To64(a,b)+c-1) / c );
        }

    #endif

#else

    //----------------------------------------------------------------------;
    //
    //  Win 16
    //
    //----------------------------------------------------------------------;

    #pragma warning(disable:4035 4704)

    //
    //  Compile for 16-bit - we can use x86 with proper opcode prefixes
    //    to get 32-bit instructions.
    //

    INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    imul    bx              //  imul ebx
        _asm _emit 0x66 _asm    idiv    cx              //  idiv ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDiv32()

    INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    mov     bx,cx           //  mov  ebx,ecx
        _asm _emit 0x66 _asm    shr     bx,1            //  sar  ebx,1
        _asm _emit 0x66 _asm    add     ax,bx           //  add  eax,ebx
        _asm _emit 0x66 _asm    adc     dx,0            //  adc  edx,0
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDiv32()

    INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    mov     bx,cx           //  mov  ebx,ecx
        _asm _emit 0x66 _asm    dec     bx              //  dec  ebx
        _asm _emit 0x66 _asm    add     ax,bx           //  add  eax,ebx
        _asm _emit 0x66 _asm    adc     dx,0            //  adc  edx,0
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDivRU32()

    INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
    {
        _asm _emit 0x66 _asm    mov     ax,word ptr a   //  mov  eax, a
        _asm _emit 0x66 _asm    mov     bx,word ptr b   //  mov  ebx, b
        _asm _emit 0x66 _asm    mov     cx,word ptr c   //  mov  ecx, c
        _asm _emit 0x66 _asm    mul     bx              //  mul  ebx
        _asm _emit 0x66 _asm    div     cx              //  div  ecx
        _asm _emit 0x66                                 //  shld edx, eax, 16
        _asm _emit 0x0F
        _asm _emit 0xA4
        _asm _emit 0xC2
        _asm _emit 0x10

    } // MulDivRD32()

    #pragma warning(default:4035 4704)

#endif

//
//  some code references these by other names.
//
#define muldiv32    MulDivRN
#define muldivrd32  MulDivRD
#define muldivru32  MulDivRU

#endif  // _INC_MULDIV32


DEBUG.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Debug.C
*
* Debug output routines
*
*****************************************************************************/

#ifdef   DEBUG
#include <windows.h>
#include <mmsystem.h>
#include <stdarg.h>

#include "global.h"
#include "debug.h"

PRIVATE VOID FAR CDECL DbgVPrintF(LPSTR szFmt, LPSTR va);

/*
**  Since we don't UNICODE our debugging messages, use the ASCII entry
**  points regardless of how we are compiled.
*/
#define wvsprintfA          wvsprintf
#define GetProfileIntA      GetProfileInt
#define OutputDebugStringA  OutputDebugStr

#define lstrcatA            lstrcat
#define lstrlenA            lstrlen

BOOL                        __gfDbgEnabled  = TRUE;
UINT                        __guDbgLevel    = 0;

WORD                        wDebugLevel     = 0;

/*****************************************************************************
*
* WinAssert
*
* Cause a debug out on an assert; breaking to the debugger if installed.
*
* lpstrExp                  - Assert expression string
* lpstrFile                 - File of assert
* dwLine                    - Line number of assert
*
* Handle WM_CREATE message to main application window.
*
* HWND hWnd                 - Window handle
* CREATESTRUCT FAR* lpCreateStruct
*                           - Pointer to creation parameters for the window.
*
*****************************************************************************/
VOID WINAPI WinAssert(
    LPSTR                   lpstrExp,
    LPSTR                   lpstrFile,
    DWORD                   dwLine)
{
    static char szWork[256];
    static char BCODE szFormat[] =
        "!Assert: %s#%lu [%s]";

    dprintf(0, (LPSTR)szFormat, (LPSTR)lpstrFile, dwLine, (LPSTR)lpstrExp);
}

/*****************************************************************************
*
* DbgVPrintF
*
* Format and print a debug output string
*
* szFmt                     - Format string
* va                        - printf style argument list
*
* The following characters have special meanings when they are
* the first in the string:
*
*   !   Causes a break into the debugger after the output has been printed
*   `   Causes no module prefix to be printed
*   ~   Causes no CR/LF pair to follow the string
*
*****************************************************************************/
VOID FAR CDECL DbgVPrintF(
    LPSTR                   szFmt,
    LPSTR                   va)
{
    char                    ach[DEBUG_MAX_LINE_LEN];
    BOOL                    fDebugBreak = FALSE;
    BOOL                    fPrefix     = TRUE;
    BOOL                    fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch(*szFmt)
        {
            case '!':
                fDebugBreak = TRUE;
                szFmt++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFmt++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFmt++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");

    wvsprintfA(ach + lstrlenA(ach), szFmt, (LPSTR)va);

    if (fCRLF)
        lstrcatA(ach, "\r\n");

    OutputDebugStringA(ach);

    if (fDebugBreak)
        DebugBreak();
}

/*****************************************************************************
*
* dprintf
*
* User-level function to print a debug string
*
* uDbgLevel                 - Debug level of this message
* szFmt                     - Format string for this message
* ...                       - printf style arguments
*
* The message will be printed only if debugging is enabled and the messages's
* debug level is less than or equal to the current debug level.
*
* This function is called by the DPF() macro in Debug.H.
*
*****************************************************************************/
void FAR CDECL dprintf(
    UINT                    uDbgLevel,
    LPSTR                   szFmt,
    ...)
{
    va_list                 va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFmt);
    DbgVPrintF(szFmt, (LPSTR)va);
    va_end(va);
}

/*****************************************************************************
*
* DbgEnable
*
* Enable or disable debug output
*
* fEnable                   - Enable or disable output
*
* Returns the old state.
*
*****************************************************************************/
BOOL WINAPI DbgEnable(
    BOOL                    fEnable)
{
    BOOL                    fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
}

/*****************************************************************************
*
* DbgSetLevel
*
* Set the current level for debug output
*
* uLevel                    - New level for debug output
*
* Returns the old level.
*
*****************************************************************************/
UINT WINAPI DbgSetLevel(
    UINT                    uLevel)
{
    UINT                    uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = wDebugLevel = uLevel;

    return (uOldLevel);
}

/*****************************************************************************
*
* DbgInitialize
*
* Get debug output settings from WIN.INI for this module
*
* fEnable                   - Enable or disable debug output
*
* Returns the current debug level
*
* The debug level will be read from the [debug] section of WIN.INI,
* from an entry with this module's name as #define'd in Debug.H.
*
* WIN.INI:
*  [Debug]
*  Module=3
*
*****************************************************************************/
UINT WINAPI DbgInitialize(BOOL fEnable)
{
    DbgSetLevel(GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, 0));
    DbgEnable(fEnable);

    return (__guDbgLevel);
}

#endif


SEQ.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Seq.H
*
* Public include file for sequencer interface.
*
*****************************************************************************/

#ifndef _SEQ_
#define _SEQ_

#include "global.h"
#include "smf.h"

#define  VERSION_MINOR              0x00
#define  VERSION_MAJOR              0x04
#define  SEQ_VERSION                ((DWORD)(WORD)((BYTE)VERSION_MINOR | (((WORD)(BYTE)VERSION_MAJOR) << 8)))

#define SEQ_F_EOF           0x00000001L
#define SEQ_F_COLONIZED     0x00000002L
#define SEQ_F_WAITING       0x00000004L

#define SEQ_S_NOFILE        0
#define SEQ_S_OPENED        1
#define SEQ_S_PREROLLING    2
#define SEQ_S_PREROLLED     3
#define SEQ_S_PLAYING       4
#define SEQ_S_PAUSED        5
#define SEQ_S_STOPPING      6
#define SEQ_S_RESET         7

#define MMSG_DONE                   (WM_USER+20)

typedef struct tag_preroll
{
    TICKS       tkBase;
    TICKS       tkEnd;
}   PREROLL,
    FAR *LPPREROLL;

typedef struct tag_seq NEAR* PSEQ;
typedef struct tag_seq
{
    DWORD       cBuffer;            /* Number of streaming buffers to alloc         */
    DWORD       cbBuffer;           /* Size of each streaming buffer                */
    LPSTR       pstrFile;           /* Pointer to filename to open                  */
    UINT        uDeviceID;          /* Requested MIDI device ID for MMSYSTEM        */
    UINT        uMCIDeviceID;       /* Our MCI device ID given to us                */
    UINT        uMCITimeFormat;     /* Current time format                          */
    UINT        uMCITimeDiv;        /* MCI_SEQ_DIV_xxx for current file             */
HWNDhWnd;/* Where to post MMSG_DONE when done playing*/

    UINT        uState;             /* Sequencer state (SEQ_S_xxx)                  */
    TICKS       tkLength;           /* Length of longest track                      */
    DWORD       cTrk;               /* Number of tracks                             */
    MMRESULT    mmrcLastErr;        /* Error return from last sequencer operation   */

    PSEQ        pNext;              /* Link to next PSEQ                            */
    HSMF        hSmf;               /* Handle to open file                          */
    HMIDIOUT    hmidi;              /* Handle to open MIDI device                   */
    DWORD       dwTimeDivision;     /* File time division                           */

    LPBYTE      lpbAlloc;           /* Streaming buffers -- initial allocation      */
    LPMIDIHDR   lpmhFree;           /* Streaming buffers -- free list               */
    LPMIDIHDR   lpmhPreroll;        /* Streaming buffers -- preroll buffer          */
    DWORD       cbPreroll;          /* Streaming buffers -- size of lpmhPreroll     */
    UINT        uBuffersInMMSYSTEM; /* Streaming buffers -- in use                  */

    TICKS       tkBase;             /* Where playback started from in stream        */
    TICKS       tkEnd;              /* Where playback should end                    */

    DWORD       fdwSeq;             /* Various sequencer flags                      */

}   SEQ,
    NEAR *PSEQ;

/* sequence.c
*/

MMRESULT FNLOCAL seqAllocBuffers(
    PSEQ                pseq);

VOID FNLOCAL seqFreeBuffers(
    PSEQ                pseq);

MMRESULT FNLOCAL seqOpenFile(
    PSEQ                pseq);

MMRESULT FNLOCAL seqCloseFile(
    PSEQ                pseq);

MMRESULT FNLOCAL seqPreroll(
    PSEQ                pseq,
    LPPREROLL           lppreroll);

MMRESULT FNLOCAL seqStart(
    PSEQ                pseq);

MMRESULT FNLOCAL seqPause(
    PSEQ                pseq);

MMRESULT FNLOCAL seqRestart(
    PSEQ                pseq);

MMRESULT FNLOCAL seqStop(
    PSEQ                pseq);

MMRESULT FNLOCAL seqTime(
    PSEQ                pseq,
    PTICKS              pTicks);

TICKS FNLOCAL seqMillisecsToTicks(
    PSEQ                pseq,
    DWORD               msOffset);

DWORD FNLOCAL seqTicksToMillisecs(
    PSEQ                pseq,
    TICKS               tkOffset);

#endif


SEQUENCE.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Sequence.C
*
* Sequencer engine for MIDI player app
*
*****************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <limits.h>

#include "debug.h"
#include "seq.h"

PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
PRIVATE MMRESULT FNLOCAL XlatSMFErr(SMFRESULT smfrc);

/***************************************************************************
*
* seqAllocBuffers
*
* Allocate buffers for this instance.
*
* pSeq                      - The sequencer instance to allocate buffers for.
*
* Returns
*   MMSYSERR_NOERROR If the operation was successful.
*
*   MCIERR_OUT_OF_MEMORY  If there is insufficient memory for
*     the requested number and size of buffers.
*
* seqAllocBuffers allocates playback buffers based on the
* cbBuffer and cBuffer fields of pSeq. cbBuffer specifies the
* number of bytes in each buffer, and cBuffer specifies the
* number of buffers to allocate.
*
* seqAllocBuffers must be called before any other sequencer call
* on a newly allocted SEQUENCE structure. It must be paired with
* a call to seqFreeBuffers, which should be the last call made
* before the SEQUENCE structure is discarded.
*
***************************************************************************/
MMRESULT FNLOCAL seqAllocBuffers(
    PSEQ                    pSeq)
{
    DWORD                   dwEachBufferSize;
    DWORD                   dwAlloc;
    UINT                    i;
    LPBYTE                  lpbWork;

    assert(pSeq != NULL);

    pSeq->uState    = SEQ_S_NOFILE;
    pSeq->lpmhFree  = NULL;
    pSeq->lpbAlloc  = NULL;
    pSeq->hSmf      = (HSMF)NULL;

    /* First make sure we can allocate the buffers they asked for
    */
    dwEachBufferSize = sizeof(MIDIHDR) + (DWORD)(pSeq->cbBuffer);
    dwAlloc          = dwEachBufferSize * (DWORD)(pSeq->cBuffer);

    pSeq->lpbAlloc = GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE, dwAlloc);
    if (NULL == pSeq->lpbAlloc)
        return MCIERR_OUT_OF_MEMORY;

    /* Initialize all MIDIHDR's and throw them into a free list
    */
    pSeq->lpmhFree = NULL;

    lpbWork = pSeq->lpbAlloc;
    for (i=0; i < pSeq->cBuffer; i++)
    {
        ((LPMIDIHDR)lpbWork)->lpNext            = pSeq->lpmhFree;

        ((LPMIDIHDR)lpbWork)->lpData            = lpbWork + sizeof(MIDIHDR);
        ((LPMIDIHDR)lpbWork)->dwBufferLength    = pSeq->cbBuffer;
        ((LPMIDIHDR)lpbWork)->dwBytesRecorded   = 0;
        ((LPMIDIHDR)lpbWork)->dwUser            = (DWORD)(UINT)pSeq;
        ((LPMIDIHDR)lpbWork)->dwFlags           = 0;

        pSeq->lpmhFree = (LPMIDIHDR)lpbWork;

        lpbWork += dwEachBufferSize;
    }

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqFreeBuffers
*
* Free buffers for this instance.
*
* pSeq                      - The sequencer instance to free buffers for.
*
* seqFreeBuffers frees all allocated memory belonging to the
* given sequencer instance pSeq. It must be the last call
* performed on the instance before it is destroyed.
*
****************************************************************************/
VOID FNLOCAL seqFreeBuffers(
    PSEQ                    pSeq)
{
    LPMIDIHDR               lpmh;

    assert(pSeq != NULL);

    if (NULL != pSeq->lpbAlloc)
    {
        lpmh = (LPMIDIHDR)pSeq->lpbAlloc;
        assert(!(lpmh->dwFlags & MHDR_PREPARED));

        GlobalFreePtr(pSeq->lpbAlloc);
    }
}

/***************************************************************************
*
* seqOpenFile
*
* Associates a MIDI file with the given sequencer instance.
*
* pSeq                      - The sequencer instance.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If there is already a file open
*     on this instance.
*
*   MCIERR_OUT_OF_MEMORY If there was insufficient memory to
*     allocate internal buffers on the file.
*
*   MCIERR_INVALID_FILE If initial attempts to parse the file
*     failed (such as the file is not a MIDI or RMI file).
*
* seqOpenFile may only be called if there is no currently open file
* on the instance. It must be paired with a call to seqCloseFile
* when operations on this file are complete.
*
* The pstrFile field of pSeq contains the name of the file
* to open. This name will be passed directly to mmioOpen; it may
* contain a specifcation for a custom MMIO file handler. The task
* context used for all I/O will be the task which calls seqOpenFile.
*
***************************************************************************/
MMRESULT FNLOCAL seqOpenFile(
    PSEQ                    pSeq)
{
    MMRESULT                rc      = MMSYSERR_NOERROR;
    SMFOPENFILESTRUCT       sofs;
    SMFFILEINFO             sfi;
    SMFRESULT               smfrc;
    DWORD                   cbBuffer;

    assert(pSeq != NULL);

    if (pSeq->uState != SEQ_S_NOFILE)
    {
        return MCIERR_UNSUPPORTED_FUNCTION;
    }

    assert(pSeq->pstrFile != NULL);

    sofs.pstrName     = pSeq->pstrFile;

    smfrc = smfOpenFile(&sofs);
    if (SMF_SUCCESS != smfrc)
    {
        rc = XlatSMFErr(smfrc);
        goto Seq_Open_File_Cleanup;
    }

    pSeq->hSmf = sofs.hSmf;
    smfGetFileInfo(pSeq->hSmf, &sfi);

    pSeq->dwTimeDivision = sfi.dwTimeDivision;
    pSeq->tkLength       = sfi.tkLength;
    pSeq->cTrk           = sfi.dwTracks;

    /* Track buffers must be big enough to hold the state data returned
    ** by smfSeek()
    */
    cbBuffer = min(pSeq->cbBuffer, smfGetStateMaxSize());

Seq_Open_File_Cleanup:
    if (MMSYSERR_NOERROR != rc)
        seqCloseFile(pSeq);
    else
        pSeq->uState = SEQ_S_OPENED;

    return rc;
}

/***************************************************************************
*
* seqCloseFile
*
* Deassociates a MIDI file with the given sequencer instance.
*
* pSeq                      -  The sequencer instance.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     stopped.
*
* A call to seqCloseFile must be paired with a prior call to
* seqOpenFile. All buffers associated with the file will be
* freed and the file will be closed. The sequencer must be
* stopped before this call will be accepted.
*
***************************************************************************/
MMRESULT FNLOCAL seqCloseFile(
    PSEQ                    pSeq)
{
    LPMIDIHDR               lpmh;

    assert(pSeq != NULL);

    if (SEQ_S_OPENED != pSeq->uState)
        return MCIERR_UNSUPPORTED_FUNCTION;

    if ((HSMF)NULL != pSeq->hSmf)
    {
        smfCloseFile(pSeq->hSmf);
        pSeq->hSmf = (HSMF)NULL;
    }

    /* If we were prerolled, need to clean up -- have an open MIDI handle
    ** and buffers in the ready queue
    */

    for (lpmh = pSeq->lpmhFree; lpmh; lpmh = lpmh->lpNext)
        midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh));

    if (pSeq->lpmhPreroll)
        midiOutUnprepareHeader(pSeq->hmidi, pSeq->lpmhPreroll, sizeof(*pSeq->lpmhPreroll));

    if (pSeq->hmidi != NULL)
    {
        midiStreamClose((HMIDISTRM)(pSeq->hmidi));
        pSeq->hmidi = NULL;
    }

    pSeq->uState = SEQ_S_NOFILE;

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqPreroll
*
* Prepares the file for playback at the given position.
*
* pSeq                      - The sequencer instance.
*
* lpPreroll                 - Specifies the starting and ending tick
*                             positions to play between.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     opened or prerolled.
*
* Open the device so we can initialize channels.
*
* Loop through the tracks. For each track, seek to the given position and
* send the init data SMF gives us to the handle.
*
* Wait for all init buffers to finish.
*
* Unprepare the buffers (they're only ever sent here; the sequencer
* engine merges them into a single stream during normal playback) and
* refill them with the first chunk of data from the track.
*
*
****************************************************************************/
MMRESULT FNLOCAL seqPreroll(
    PSEQ                    pSeq,
    LPPREROLL               lpPreroll)
{
    SMFRESULT           smfrc;
    MMRESULT            mmrc        = MMSYSERR_NOERROR;
    MIDIPROPTIMEDIV     mptd;
    LPMIDIHDR           lpmh = NULL;
    LPMIDIHDR           lpmhPreroll = NULL;
    DWORD               cbPrerollBuffer;
    UINT                uDeviceID;

    assert(pSeq != NULL);

    pSeq->mmrcLastErr = MMSYSERR_NOERROR;

    if (pSeq->uState != SEQ_S_OPENED &&
        pSeq->uState != SEQ_S_PREROLLED)
        return MCIERR_UNSUPPORTED_FUNCTION;

pSeq->tkBase = lpPreroll->tkBase;
pSeq->tkEnd = lpPreroll->tkEnd;

    if (pSeq->hmidi)
    {
        // Recollect buffers from MMSYSTEM back into free queue
        //
        pSeq->uState = SEQ_S_RESET;
        midiOutReset(pSeq->hmidi);

while (pSeq->uBuffersInMMSYSTEM)
Sleep(0);
    }

    pSeq->uBuffersInMMSYSTEM = 0;
    pSeq->uState = SEQ_S_PREROLLING;

    //
    // We've successfully opened the file and all of the tracks; now
    // open the MIDI device and set the time division.
    //
    // NOTE: seqPreroll is equivalent to seek; device might already be open
    //
    if (NULL == pSeq->hmidi)
    {
        uDeviceID = pSeq->uDeviceID;
        if ((mmrc = midiStreamOpen(&(HMIDISTRM)pSeq->hmidi,
                                   &uDeviceID,
                                   1,
                                   (DWORD)seqMIDICallback,
                                   0,
                                   CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
        {
            pSeq->hmidi = NULL;
            goto seq_Preroll_Cleanup;
        }

        mptd.cbStruct  = sizeof(mptd);
        mptd.dwTimeDiv = pSeq->dwTimeDivision;
        if ((mmrc = midiStreamProperty(
                                       (HMIDISTRM)pSeq->hmidi,
                                       (LPBYTE)&mptd,
                                       MIDIPROP_SET|MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR)
        {
            DPF(1, "midiStreamProperty() -> %04X", (WORD)mmrc);
            midiStreamClose((HMIDISTRM)pSeq->hmidi);
            pSeq->hmidi = NULL;
            mmrc = MCIERR_DEVICE_NOT_READY;
            goto seq_Preroll_Cleanup;
        }
    }

    mmrc = MMSYSERR_NOERROR;

    //
    //  Allocate a preroll buffer.  Then if we don't have enough room for
    //  all the preroll info, we make the buffer larger.
    //
    if (!pSeq->lpmhPreroll)
    {
        cbPrerollBuffer = 4096;
        lpmhPreroll = (LPMIDIHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,
                                                            cbPrerollBuffer);
    }
    else
    {
        cbPrerollBuffer = pSeq->cbPreroll;
        lpmhPreroll = pSeq->lpmhPreroll;
    }

    lpmhPreroll->lpNext            = pSeq->lpmhFree;
    lpmhPreroll->lpData            = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
    lpmhPreroll->dwBufferLength    = cbPrerollBuffer - sizeof(MIDIHDR);
    lpmhPreroll->dwBytesRecorded   = 0;
    lpmhPreroll->dwUser            = (DWORD)(UINT)pSeq;
    lpmhPreroll->dwFlags           = 0;

    do
    {
        smfrc = smfSeek(pSeq->hSmf, pSeq->tkBase, lpmhPreroll);
        if( SMF_SUCCESS != smfrc )
        {
            if( ( SMF_NO_MEMORY != smfrc )  ||
                ( cbPrerollBuffer >= 32768L ) )
            {
                DPF(1, "smfSeek() returned %lu", (DWORD)smfrc);

                GlobalFreePtr(lpmhPreroll);
                pSeq->lpmhPreroll = NULL;

                mmrc = XlatSMFErr(smfrc);
                goto seq_Preroll_Cleanup;
            }
            else   //  Try to grow buffer.
            {
                cbPrerollBuffer *= 2;
                lpmh = (LPMIDIHDR)GlobalReAllocPtr( lpmhPreroll, cbPrerollBuffer, 0 );
                if( NULL == lpmh )
                {
                    DPF(2,"seqPreroll - realloc failed, aborting preroll.");
                    mmrc = MCIERR_OUT_OF_MEMORY;
                    goto seq_Preroll_Cleanup;
                }

                lpmhPreroll = lpmh;
                lpmhPreroll->lpData = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
                lpmhPreroll->dwBufferLength = cbPrerollBuffer - sizeof(MIDIHDR);

                pSeq->lpmhPreroll = lpmhPreroll;
                pSeq->cbPreroll = cbPrerollBuffer;
            }
        }
    } while( SMF_SUCCESS != smfrc );

    if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
    {
        DPF(1, "midiOutPrepare(preroll) -> %lu!", (DWORD)mmrc);

        mmrc = MCIERR_DEVICE_NOT_READY;
        goto seq_Preroll_Cleanup;
    }

    ++pSeq->uBuffersInMMSYSTEM;

    if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
    {
        DPF(1, "midiStreamOut(preroll) -> %lu!", (DWORD)mmrc);

        mmrc = MCIERR_DEVICE_NOT_READY;
        --pSeq->uBuffersInMMSYSTEM;
        goto seq_Preroll_Cleanup;
    }
    DPF(3,"seqPreroll: midiStreamOut(0x%x,0x%lx,%u) returned %u.",pSeq->hmidi,lpmhPreroll,sizeof(MIDIHDR),mmrc);

    pSeq->fdwSeq &= ~SEQ_F_EOF;
    while (pSeq->lpmhFree)
    {
        lpmh = pSeq->lpmhFree;
        pSeq->lpmhFree = lpmh->lpNext;

        smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);
        if (SMF_SUCCESS != smfrc && SMF_END_OF_FILE != smfrc)
        {
            DPF(1, "SFP: smfReadEvents() -> %u", (UINT)smfrc);
            mmrc = XlatSMFErr(smfrc);
            goto seq_Preroll_Cleanup;
        }

        if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            DPF(1, "SFP: midiOutPrepareHeader failed");
            goto seq_Preroll_Cleanup;
        }

        if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            DPF(1, "SFP: midiStreamOut failed");
            goto seq_Preroll_Cleanup;
        }

        ++pSeq->uBuffersInMMSYSTEM;

        if (SMF_END_OF_FILE == smfrc)
        {
            pSeq->fdwSeq |= SEQ_F_EOF;
            break;
        }
    }

seq_Preroll_Cleanup:
    if (MMSYSERR_NOERROR != mmrc)
    {
        pSeq->uState = SEQ_S_OPENED;
        pSeq->fdwSeq &= ~SEQ_F_WAITING;
    }
    else
    {
        pSeq->uState = SEQ_S_PREROLLED;
    }

    return mmrc;
}

/***************************************************************************
*
* seqStart
*
* Starts playback at the current position.
*
* pSeq                      - The sequencer instance.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     stopped.
*
*   MCIERR_DEVICE_NOT_READY If the underlying MIDI device could
*     not be opened or fails any call.
*
* The sequencer must be prerolled before seqStart may be called.
*
* Just feed everything in the ready queue to the device.
*
***************************************************************************/
MMRESULT FNLOCAL seqStart(
    PSEQ                    pSeq)
{
    assert(NULL != pSeq);

    if (SEQ_S_PREROLLED != pSeq->uState)
    {
        DPF(1, "seqStart(): State is wrong! [%u]", pSeq->uState);
        return MCIERR_UNSUPPORTED_FUNCTION;
    }

    pSeq->uState = SEQ_S_PLAYING;

    return midiStreamRestart((HMIDISTRM)pSeq->hmidi);
}

/***************************************************************************
*
* seqPause
*
* Pauses playback of the instance.
*
* pSeq                      - The sequencer instance.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     playing.
*
* The sequencer must be playing before seqPause may be called.
* Pausing the sequencer will cause all currently on notes to be turned
* off. This may cause playback to be slightly inaccurate on restart
* due to missing notes.
*
***************************************************************************/
MMRESULT FNLOCAL seqPause(
    PSEQ                    pSeq)
{
    assert(NULL != pSeq);

    if (SEQ_S_PLAYING != pSeq->uState)
        return MCIERR_UNSUPPORTED_FUNCTION;

    pSeq->uState = SEQ_S_PAUSED;
    midiStreamPause((HMIDISTRM)pSeq->hmidi);

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqRestart
*
* Restarts playback of an instance after a pause.
*
* pSeq                      - The sequencer instance.
*
* Returns
*    MMSYSERR_NOERROR If the operation is successful.
*
*    MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     paused.
*
* The sequencer must be paused before seqRestart may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqRestart(
    PSEQ                    pSeq)
{
    assert(NULL != pSeq);

    if (SEQ_S_PAUSED != pSeq->uState)
        return MCIERR_UNSUPPORTED_FUNCTION;

    pSeq->uState = SEQ_S_PLAYING;
    midiStreamRestart((HMIDISTRM)pSeq->hmidi);

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqStop
*
* Totally stops playback of an instance.
*
* pSeq                      - The sequencer instance.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     paused or playing.
*
* The sequencer must be paused or playing before seqStop may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqStop(
    PSEQ                    pSeq)
{
    assert(NULL != pSeq);

    /* Automatic success if we're already stopped
    */
    if (SEQ_S_PLAYING != pSeq->uState &&
        SEQ_S_PAUSED != pSeq->uState)
    {
        pSeq->fdwSeq &= ~SEQ_F_WAITING;
        return MMSYSERR_NOERROR;
    }

    pSeq->uState = SEQ_S_STOPPING;
    pSeq->fdwSeq |= SEQ_F_WAITING;

    if (MMSYSERR_NOERROR != (pSeq->mmrcLastErr = midiStreamStop((HMIDISTRM)pSeq->hmidi)))
    {
        DPF(1, "midiOutStop() returned %lu in seqStop()!", (DWORD)pSeq->mmrcLastErr);

        pSeq->fdwSeq &= ~SEQ_F_WAITING;
        return MCIERR_DEVICE_NOT_READY;
    }

while (pSeq->uBuffersInMMSYSTEM)
Sleep(0);

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqTime
*
* Determine the current position in playback of an instance.
*
* pSeq                      - The sequencer instance.
*
* pTicks                    - A pointer to a DWORD where the current position
*                             in ticks will be returned.
*
* Returns
*   MMSYSERR_NOERROR If the operation is successful.
*
*   MCIERR_DEVICE_NOT_READY If the underlying device fails to report
*     the position.
*
*   MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
*     paused or playing.
*
* The sequencer must be paused, playing or prerolled before seqTime
* may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqTime(
    PSEQ                    pSeq,
    PTICKS                  pTicks)
{
    MMRESULT                mmr;
    MMTIME                  mmt;

    assert(pSeq != NULL);

    if (SEQ_S_PLAYING != pSeq->uState &&
        SEQ_S_PAUSED != pSeq->uState &&
        SEQ_S_PREROLLING != pSeq->uState &&
        SEQ_S_PREROLLED != pSeq->uState &&
        SEQ_S_OPENED != pSeq->uState)
    {
        DPF(1, "seqTime(): State wrong! [is %u]", pSeq->uState);
        return MCIERR_UNSUPPORTED_FUNCTION;
    }

    *pTicks = 0;
    if (SEQ_S_OPENED != pSeq->uState)
    {
        *pTicks = pSeq->tkBase;
        if (SEQ_S_PREROLLED != pSeq->uState)
        {
            mmt.wType = TIME_TICKS;
            mmr = midiStreamPosition((HMIDISTRM)pSeq->hmidi, &mmt, sizeof(mmt));
            if (MMSYSERR_NOERROR != mmr)
            {
                DPF(1, "midiStreamPosition() returned %lu", (DWORD)mmr);
                return MCIERR_DEVICE_NOT_READY;
            }

            *pTicks += mmt.u.ticks;
        }
    }

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqMillisecsToTicks
*
* Given a millisecond offset in the output stream, returns the associated
* tick position.
*
* pSeq                      - The sequencer instance.
*
* msOffset                  - The millisecond offset into the stream.
*
* Returns the number of ticks into the stream.
*
***************************************************************************/
TICKS FNLOCAL seqMillisecsToTicks(
    PSEQ                    pSeq,
    DWORD                   msOffset)
{
    return smfMillisecsToTicks(pSeq->hSmf, msOffset);
}

/***************************************************************************
*
* seqTicksToMillisecs
*
* Given a tick offset in the output stream, returns the associated
* millisecond position.
*
* pSeq                      - The sequencer instance.
*
* tkOffset                  - The tick offset into the stream.
*
* Returns the number of milliseconds into the stream.
*
***************************************************************************/
DWORD FNLOCAL seqTicksToMillisecs(
    PSEQ                    pSeq,
    TICKS                   tkOffset)
{
    return smfTicksToMillisecs(pSeq->hSmf, tkOffset);
}

/***************************************************************************
*
* seqMIDICallback
*
* Called by the system when a buffer is done
*
* dw1                       - The buffer that has completed playback.
*
***************************************************************************/
PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
LPMIDIHDRlpmh= (LPMIDIHDR)dw1;
    PSEQ                    pSeq;
    MMRESULT                mmrc;
    SMFRESULT               smfrc;

if (uMsg != MOM_DONE)
return;

assert(NULL != lpmh);

    pSeq = (PSEQ)(lpmh->dwUser);

    assert(pSeq != NULL);

    --pSeq->uBuffersInMMSYSTEM;

    if (SEQ_S_RESET == pSeq->uState)
    {
        // We're recollecting buffers from MMSYSTEM
        //
if (lpmh != pSeq->lpmhPreroll)
{
        lpmh->lpNext   = pSeq->lpmhFree;
        pSeq->lpmhFree = lpmh;
}

        return;
    }

    if ((SEQ_S_STOPPING == pSeq->uState) || (pSeq->fdwSeq & SEQ_F_EOF))
    {
        /*
        ** Reached EOF, just put the buffer back on the free
        ** list
        */
if (lpmh != pSeq->lpmhPreroll)
{
        lpmh->lpNext   = pSeq->lpmhFree;
        pSeq->lpmhFree = lpmh;
}

        if (MMSYSERR_NOERROR != (mmrc = midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            DPF(1, "midiOutUnprepareHeader failed in seqBufferDone! (%lu)", (DWORD)mmrc);
        }

        if (0 == pSeq->uBuffersInMMSYSTEM)
        {
            DPF(1, "seqBufferDone: normal sequencer shutdown.");

            /* Totally done! Free device and notify.
            */
            midiStreamClose((HMIDISTRM)pSeq->hmidi);

            pSeq->hmidi = NULL;
            pSeq->uState = SEQ_S_OPENED;
            pSeq->mmrcLastErr = MMSYSERR_NOERROR;
            pSeq->fdwSeq &= ~SEQ_F_WAITING;

        // lParam indicates whether or not to preroll again. Don't if we were explicitly
        // stopped.
        //
            PostMessage(pSeq->hWnd, MMSG_DONE, (WPARAM)pSeq, (LPARAM)(SEQ_S_STOPPING != pSeq->uState));
        }
    }
    else
    {
        /*
        ** Not EOF yet; attempt to fill another buffer
        */
        smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);

        switch(smfrc)
        {
            case SMF_SUCCESS:
                break;

            case SMF_END_OF_FILE:
                pSeq->fdwSeq |= SEQ_F_EOF;
                smfrc = SMF_SUCCESS;
                break;

            default:
                DPF(1, "smfReadEvents returned %lu in callback!", (DWORD)smfrc);
                pSeq->uState = SEQ_S_STOPPING;
                break;
        }

        if (SMF_SUCCESS == smfrc)
        {
            ++pSeq->uBuffersInMMSYSTEM;
            mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh));
            if (MMSYSERR_NOERROR != mmrc)
            {
                DPF(1, "seqBufferDone(): midiStreamOut() returned %lu!", (DWORD)mmrc);

                --pSeq->uBuffersInMMSYSTEM;
                pSeq->uState = SEQ_S_STOPPING;
            }
        }
    }
}

/***************************************************************************
*
* XlatSMFErr
*
* Translates an error from the SMF layer into an appropriate MCI error.
*
* smfrc                     - The return code from any SMF function.
*
* Returns
*   A parallel error from the MCI error codes.
*
***************************************************************************/
PRIVATE MMRESULT FNLOCAL XlatSMFErr(
    SMFRESULT               smfrc)
{
    switch(smfrc)
    {
        case SMF_SUCCESS:
            return MMSYSERR_NOERROR;

        case SMF_NO_MEMORY:
            return MCIERR_OUT_OF_MEMORY;

        case SMF_INVALID_FILE:
        case SMF_OPEN_FAILED:
        case SMF_INVALID_TRACK:
            return MCIERR_INVALID_FILE;

        default:
            return MCIERR_UNSUPPORTED_FUNCTION;
    }
}


SMF.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* SMF.C
*
* MIDI File access routines.
*
*****************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include "muldiv32.h"
#include "smf.h"
#include "smfi.h"
#include "debug.h"

PRIVATE SMFRESULT FNLOCAL smfInsertParmData(
    PSMF                    pSmf,
    TICKS                   tkDelta,
    LPMIDIHDR               lpmh);

/*****************************************************************************
*
* smfOpenFile
*
* This function opens a MIDI file for access.
*
* psofs                     - Specifies the file to open and associated
*                             parameters. Contains a valid HSMF handle
*                             on success.
*
* Returns
*   SMF_SUCCESS The specified file was opened.
*
*   SMF_OPEN_FAILED The specified file could not be opened because it
*     did not exist or could not be created on the disk.
*
*   SMF_INVALID_FILE The specified file was corrupt or not a MIDI file.
*
*   SMF_NO_MEMORY There was insufficient memory to open the file.
*
*   SMF_INVALID_PARM The given flags or time division in the
*     SMFOPENFILESTRUCT were invalid.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfOpenFile(
    PSMFOPENFILESTRUCT      psofs)
{
    HMMIO                   hmmio = (HMMIO)NULL;
    PSMF                    pSmf;
    SMFRESULT               smfrc = SMF_SUCCESS;
    MMIOINFO                mmioinfo;
    MMCKINFO                ckRIFF;
    MMCKINFO                ckDATA;

    assert(psofs != NULL);
    assert(psofs->pstrName != NULL);

    /* Verify that the file can be opened or created
    */
    _fmemset(&mmioinfo, 0, sizeof(mmioinfo));

    hmmio = mmioOpen(psofs->pstrName, &mmioinfo, MMIO_READ|MMIO_ALLOCBUF);
    if ((HMMIO)NULL == hmmio)
    {
        DPF(1, "smfOpenFile: mmioOpen failed!");
        return SMF_OPEN_FAILED;
    }

    /* Now see if we can create the handle structure
    */
    pSmf = (PSMF)LocalAlloc(LPTR, sizeof(SMF));
    if (NULL == pSmf)
    {
        DPF(1, "smfOpenFile: LocalAlloc failed!");
        smfrc = SMF_NO_MEMORY;
        goto smf_Open_File_Cleanup;
    }

    lstrcpy(pSmf->szName, psofs->pstrName);
    pSmf->fdwSMF = 0;
    pSmf->pTempoMap = NULL;

    /* Pull the entire file into a block of memory.
    */
    _fmemset(&ckRIFF, 0, sizeof(ckRIFF));

    if (0 == mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF) &&
        ckRIFF.fccType == FOURCC_RMID)
    {
        ckDATA.ckid = FOURCC_data;

        if (0 == mmioDescend(hmmio, &ckDATA, &ckRIFF, MMIO_FINDCHUNK))
        {
            pSmf->cbImage   = ckDATA.cksize;
        }
        else
        {
            DPF(1, "smfOpenFile: Could not descend into RIFF DATA chunk!");
            smfrc = SMF_INVALID_FILE;
            goto smf_Open_File_Cleanup;
        }
    }
    else
    {
        mmioSeek(hmmio, 0L, SEEK_SET);

        pSmf->cbImage = mmioSeek(hmmio, 0L, SEEK_END);
        mmioSeek(hmmio, 0L, SEEK_SET);
    }

    if (NULL == (pSmf->hpbImage = GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE, pSmf->cbImage)))
    {
        DPF(1, "smfOpenFile: No memory for image! [%08lX]", pSmf->cbImage);
        smfrc = SMF_NO_MEMORY;
        goto smf_Open_File_Cleanup;
    }

    if (pSmf->cbImage != (DWORD)mmioRead(hmmio, pSmf->hpbImage, pSmf->cbImage))
    {
        DPF(1, "smfOpenFile: Read error on image!");
        smfrc = SMF_INVALID_FILE;
        goto smf_Open_File_Cleanup;
    }

    /* If the file exists, parse it just enough to pull out the header and
    ** build a track index.
    */
    smfrc = smfBuildFileIndex((PSMF BSTACK *)&pSmf);
    if (MMSYSERR_NOERROR != smfrc)
    {
        DPF(1, "smfOpenFile: smfBuildFileIndex failed! [%lu]", (DWORD)smfrc);
    }

smf_Open_File_Cleanup:

    mmioClose(hmmio, 0);

    if (SMF_SUCCESS != smfrc)
    {
        if (NULL != pSmf)
        {
            if (NULL != pSmf->hpbImage)
            {
                GlobalFreePtr(pSmf->hpbImage);
            }

            LocalFree((HLOCAL)pSmf);
        }
    }
    else
    {
        psofs->hSmf = (HSMF)pSmf;
    }

    return smfrc;
}

/*****************************************************************************
*
* smfCloseFile
*
* This function closes an open MIDI file.
*
* hSmf                      - The handle of the open file to close.
*
* Returns
*   SMF_SUCCESS The specified file was closed.
*   SMF_INVALID_PARM The given handle was not valid.
*
* Any track handles opened from this file handle are invalid after this
* call.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfCloseFile(
    HSMF                    hSmf)
{
    PSMF                    pSmf        = (PSMF)hSmf;

    assert(pSmf != NULL);

    /*
    ** Free up handle memory
    */

    if (NULL != pSmf->hpbImage)
        GlobalFreePtr(pSmf->hpbImage);

    LocalFree((HLOCAL)pSmf);

    return SMF_SUCCESS;
}

/******************************************************************************
*
* smfGetFileInfo This function gets information about the MIDI file.
*
* hSmf                      - Specifies the open MIDI file to inquire about.
*
* psfi                      - A structure which will be filled in with
*                             information about the file.
*
* Returns
*   SMF_SUCCESS Information was gotten about the file.
*   SMF_INVALID_PARM The given handle was invalid.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfGetFileInfo(
    HSMF                    hSmf,
    PSMFFILEINFO            psfi)
{
    PSMF                    pSmf = (PSMF)hSmf;

    assert(pSmf != NULL);
    assert(psfi != NULL);

    /*
    ** Just fill in the structure with useful information.
    */
    psfi->dwTracks      = pSmf->dwTracks;
    psfi->dwFormat      = pSmf->dwFormat;
    psfi->dwTimeDivision= pSmf->dwTimeDivision;
    psfi->tkLength      = pSmf->tkLength;

    return SMF_SUCCESS;
}

/******************************************************************************
*
* smfTicksToMillisecs
*
* This function returns the millisecond offset into the file given the
* tick offset.
*
* hSmf                      - Specifies the open MIDI file to perform
*                             the conversion on.
*
* tkOffset                  - Specifies the tick offset into the stream
*                             to convert.
*
* Returns the number of milliseconds from the start of the stream.
*
* The conversion is performed taking into account the file's time division and
* tempo map from the first track. Note that the same millisecond value
* might not be valid at a later time if the tempo track is rewritten.
*
*****************************************************************************/
DWORD FNLOCAL smfTicksToMillisecs(
    HSMF                    hSmf,
    TICKS                   tkOffset)
{
    PSMF                    pSmf            = (PSMF)hSmf;
    PTEMPOMAPENTRY          pTempo;
    UINT                    idx;
    UINT                    uSMPTE;
    DWORD                   dwTicksPerSec;

    assert(pSmf != NULL);

    if (tkOffset > pSmf->tkLength)
    {
        DPF(1, "sTTM: Clipping ticks to file length!");
        tkOffset = pSmf->tkLength;
    }

    /* SMPTE time is easy -- no tempo map, just linear conversion
    ** Note that 30-Drop means nothing to us here since we're not
    ** converting to a colonized format, which is where dropping
    ** happens.
    */
    if (pSmf->dwTimeDivision & 0x8000)
    {
        uSMPTE = -(int)(char)((pSmf->dwTimeDivision >> 8)&0xFF);
        if (29 == uSMPTE)
            uSMPTE = 30;

        dwTicksPerSec = (DWORD)uSMPTE *
                        (DWORD)(BYTE)(pSmf->dwTimeDivision & 0xFF);

        return (DWORD)muldiv32(tkOffset, 1000L, dwTicksPerSec);
    }

    /* Walk the tempo map and find the nearest tick position. Linearly
    ** calculate the rest (using MATH.ASM)
    */

    pTempo = pSmf->pTempoMap;
    assert(pTempo != NULL);

    for (idx = 0; idx < pSmf->cTempoMap; idx++, pTempo++)
        if (tkOffset < pTempo->tkTempo)
            break;
    pTempo--;

    /* pTempo is the tempo map entry preceding the requested tick offset.
    */

    return pTempo->msBase + muldiv32(tkOffset-pTempo->tkTempo,
                                     pTempo->dwTempo,
                                     1000L*pSmf->dwTimeDivision);

}

/******************************************************************************
*
* smfMillisecsToTicks
*
* This function returns the nearest tick offset into the file given the
* millisecond offset.
*
* hSmf                      - Specifies the open MIDI file to perform the
*                             conversion on.
*
* msOffset                  - Specifies the millisecond offset into the stream
*                             to convert.
*
* Returns the number of ticks from the start of the stream.
*
* The conversion is performed taking into account the file's time division and
* tempo map from the first track. Note that the same tick value
* might not be valid at a later time if the tempo track is rewritten.
* If the millisecond value does not exactly map to a tick value, then
* the tick value will be rounded down.
*
*****************************************************************************/
TICKS FNLOCAL smfMillisecsToTicks(
    HSMF                    hSmf,
    DWORD                   msOffset)
{
    PSMF                    pSmf            = (PSMF)hSmf;
    PTEMPOMAPENTRY          pTempo;
    UINT                    idx;
    UINT                    uSMPTE;
    DWORD                   dwTicksPerSec;
    TICKS                   tkOffset;

    assert(pSmf != NULL);

    /* SMPTE time is easy -- no tempo map, just linear conversion
    ** Note that 30-Drop means nothing to us here since we're not
    ** converting to a colonized format, which is where dropping
    ** happens.
    */
    if (pSmf->dwTimeDivision & 0x8000)
    {
        uSMPTE = -(int)(char)((pSmf->dwTimeDivision >> 8)&0xFF);
        if (29 == uSMPTE)
            uSMPTE = 30;

        dwTicksPerSec = (DWORD)uSMPTE *
                        (DWORD)(BYTE)(pSmf->dwTimeDivision & 0xFF);

        return (DWORD)muldiv32(msOffset, dwTicksPerSec, 1000L);
    }

    /* Walk the tempo map and find the nearest millisecond position. Linearly
    ** calculate the rest (using MATH.ASM)
    */
    pTempo = pSmf->pTempoMap;
    assert(pTempo != NULL);

    for (idx = 0; idx < pSmf->cTempoMap; idx++, pTempo++)
        if (msOffset < pTempo->msBase)
            break;
    pTempo--;

    /* pTempo is the tempo map entry preceding the requested tick offset.
    */

    tkOffset = pTempo->tkTempo + muldiv32(msOffset-pTempo->msBase,
                                     1000L*pSmf->dwTimeDivision,
                                     pTempo->dwTempo);

    if (tkOffset > pSmf->tkLength)
    {
        DPF(1, "sMTT: Clipping ticks to file length!");
        tkOffset = pSmf->tkLength;
    }

    return tkOffset;
}

/******************************************************************************
*
* smfReadEvents
*
* This function reads events from a track.
*
* hSmf                      - Specifies the file to read data from.
*
* lpmh                      - Contains information about the buffer to fill.
*
* tkMax                     - Specifies a cutoff point in the stream
*                             beyond which events will not be read.
*
* Return@rdes
*   SMF_SUCCESS The events were successfully read.
*   SMF_END_OF_TRACK There are no more events to read in this track.
*   SMF_INVALID_FILE A disk error occured on the file.
*
* @xref <f smfWriteEvents>
*****************************************************************************/
SMFRESULT FNLOCAL smfReadEvents(
    HSMF                    hSmf,
    LPMIDIHDR               lpmh,
    TICKS                   tkMax)
{
    PSMF                    pSmf = (PSMF)hSmf;
    SMFRESULT               smfrc;
    EVENT                   event;
    LPDWORD                 lpdw;
    DWORD                   dwTempo;

    assert(pSmf != NULL);
    assert(lpmh != NULL);

    /*
    ** Read events from the track and pack them into the buffer in polymsg
    ** format.
    **
    ** If a SysEx or meta would go over a buffer boundry, split it.
    */
    lpmh->dwBytesRecorded = 0;
    if (pSmf->dwPendingUserEvent)
    {
        smfrc = smfInsertParmData(pSmf, (TICKS)0, lpmh);
        if (SMF_SUCCESS != smfrc)
        {
            DPF(1, "smfInsertParmData() -> %u", (UINT)smfrc);
            return smfrc;
        }
    }

    lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);

    if (pSmf->fdwSMF & SMF_F_EOF)
    {
        return SMF_END_OF_FILE;
    }

    while(TRUE)
    {
        assert(lpmh->dwBytesRecorded <= lpmh->dwBufferLength);

        /* If we know ahead of time we won't have room for the
        ** event, just break out now. We need 2 DWORD's for the
        ** terminator event and at least 2 DWORD's for any
        ** event we might store - this will allow us a full
        ** short event or the delta time and stub for a long
        ** event to be split.
        */
        if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4*sizeof(DWORD))
        {
            break;
        }

        smfrc = smfGetNextEvent(pSmf, (SPEVENT)&event, tkMax);
        if (SMF_SUCCESS != smfrc)
        {
            /* smfGetNextEvent doesn't set this because smfSeek uses it
            ** as well and needs to distinguish between reaching the
            ** seek point and reaching end-of-file.
            **
            ** To the user, however, we present the selection between
            ** their given tkBase and tkEnd as the entire file, therefore
            ** we want to translate this into EOF.
            */
            if (SMF_REACHED_TKMAX == smfrc)
            {
                pSmf->fdwSMF |= SMF_F_EOF;
            }

            DPF(1, "smfReadEvents: smfGetNextEvent() -> %u", (UINT)smfrc);
            break;
        }

        if (MIDI_SYSEX > EVENT_TYPE(event))
        {
            *lpdw++ = (DWORD)event.tkDelta;
            *lpdw++ = 0;
            *lpdw++ = (((DWORD)MEVT_SHORTMSG)<<24) |
                      ((DWORD)EVENT_TYPE(event)) |
                      (((DWORD)EVENT_CH_B1(event)) << 8) |
                      (((DWORD)EVENT_CH_B2(event)) << 16);

            lpmh->dwBytesRecorded += 3*sizeof(DWORD);
        }
        else if (MIDI_META == EVENT_TYPE(event) &&
                 MIDI_META_EOT == EVENT_META_TYPE(event))
        {
            /* These are ignoreable since smfReadNextEvent()
            ** takes care of track merging
            */
        }
        else if (MIDI_META == EVENT_TYPE(event) &&
                 MIDI_META_TEMPO == EVENT_META_TYPE(event))
        {
            if (event.cbParm != 3)
            {
                DPF(1, "smfReadEvents: Corrupt tempo event");
                return SMF_INVALID_FILE;
            }

            dwTempo = (((DWORD)MEVT_TEMPO)<<24)|
                      (((DWORD)event.hpbParm[0])<<16)|
                      (((DWORD)event.hpbParm[1])<<8)|
                      ((DWORD)event.hpbParm[2]);

            *lpdw++ = (DWORD)event.tkDelta;
            *lpdw++ = 0;
            *lpdw++ = dwTempo;

            lpmh->dwBytesRecorded += 3*sizeof(DWORD);
        }
        else if (MIDI_META != EVENT_TYPE(event))
        {
            /* Must be F0 or F7 system exclusive or FF meta
            ** that we didn't recognize
            */
            pSmf->cbPendingUserEvent = event.cbParm;
            pSmf->hpbPendingUserEvent = event.hpbParm;
            pSmf->fdwSMF &= ~SMF_F_INSERTSYSEX;

            switch(EVENT_TYPE(event))
            {
                case MIDI_SYSEX:
                    pSmf->fdwSMF |= SMF_F_INSERTSYSEX;

                    ++pSmf->cbPendingUserEvent;

                    /* Falling through...
                    */

                case MIDI_SYSEXEND:
                    pSmf->dwPendingUserEvent = ((DWORD)MEVT_LONGMSG) << 24;
                    break;
            }

            smfrc = smfInsertParmData(pSmf, event.tkDelta, lpmh);
            if (SMF_SUCCESS != smfrc)
            {
                DPF(1, "smfInsertParmData[2] %u", (UINT)smfrc);
                return smfrc;
            }

            lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);
        }
    }

    return (pSmf->fdwSMF & SMF_F_EOF) ? SMF_END_OF_FILE : SMF_SUCCESS;
}

/******************************************************************************
*
* smfInsertParmData
*
* Inserts pending long data from a track into the given buffer.
*
* pSmf                      - Specifies the file to read data from.
*
* tkDelta                   - Specfices the tick delta for the data.
*
* lpmh                      - Contains information about the buffer to fill.
*
* Returns
*   SMF_SUCCESS The events were successfully read.
*   SMF_INVALID_FILE A disk error occured on the file.
*
* Fills as much data as will fit while leaving room for the buffer
* terminator.
*
* If the long data is depleted, resets pSmf->dwPendingUserEvent so
* that the next event may be read.
*
*****************************************************************************/
PRIVATE SMFRESULT FNLOCAL smfInsertParmData(
    PSMF                    pSmf,
    TICKS                   tkDelta,
    LPMIDIHDR               lpmh)
{
    DWORD                   dwLength;
    DWORD                   dwRounded;
    LPDWORD                 lpdw;

    assert(pSmf != NULL);
    assert(lpmh != NULL);

    /* Can't fit 4 DWORD's? (tkDelta + stream-id + event + some data)
    ** Can't do anything.
    */
    assert(lpmh->dwBufferLength >= lpmh->dwBytesRecorded);

    if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4*sizeof(DWORD))
    {
        if (0 == tkDelta)
            return SMF_SUCCESS;

        /* If we got here with a real delta, that means smfReadEvents screwed
        ** up calculating left space and we should flag it somehow.
        */
        DPF(1, "Can't fit initial piece of SysEx into buffer!");
        return SMF_INVALID_FILE;
    }

    lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);

    dwLength = lpmh->dwBufferLength - lpmh->dwBytesRecorded - 3*sizeof(DWORD);
    dwLength = min(dwLength, pSmf->cbPendingUserEvent);

    *lpdw++ = (DWORD)tkDelta;
    *lpdw++ = 0L;
    *lpdw++ = (pSmf->dwPendingUserEvent & 0xFF000000L) | (dwLength & 0x00FFFFFFL);

    dwRounded = (dwLength + 3) & (~3L);

    if (pSmf->fdwSMF & SMF_F_INSERTSYSEX)
    {
        *((LPBYTE)lpdw)++ = MIDI_SYSEX;
        pSmf->fdwSMF &= ~SMF_F_INSERTSYSEX;
        --dwLength;
        --pSmf->cbPendingUserEvent;
    }

    if (dwLength & 0x80000000L)
    {
        DPF(1, "dwLength %08lX  dwBytesRecorded %08lX  dwBufferLength %08lX", dwLength, lpmh->dwBytesRecorded, lpmh->dwBufferLength);
        DPF(1, "cbPendingUserEvent %08lX  dwPendingUserEvent %08lX dwRounded %08lX", pSmf->cbPendingUserEvent, pSmf->dwPendingUserEvent, dwRounded);
        DPF(1, "Offset into MIDI image %08lX", (DWORD)(pSmf->hpbPendingUserEvent - pSmf->hpbImage));
        DPF(1, "!hmemcpy is about to fault");
    }

    hmemcpy(lpdw, pSmf->hpbPendingUserEvent, dwLength);
    if (0 == (pSmf->cbPendingUserEvent -= dwLength))
        pSmf->dwPendingUserEvent = 0;

    lpmh->dwBytesRecorded += 3*sizeof(DWORD) + dwRounded;

    return SMF_SUCCESS;
}

/******************************************************************************
*
* smfSeek
*
* This function moves the file pointer within a track
* and gets the state of the track at the new position. It returns a buffer of
* state information which can be used to set up to play from the new position.
*
* hSmf                      - Handle of file to seek within
*
* tkPosition                - The position to seek to in the track.
*
* lpmh                      - A buffer to contain the state information.
*
* Returns
*   SMF_SUCCESS | The state was successfully read.
*   SMF_END_OF_TRACK | The pointer was moved to end of track and no state
*     information was returned.
*   SMF_INVALID_PARM | The given handle or buffer was invalid.
*   SMF_NO_MEMORY | There was insufficient memory in the given buffer to
*     contain all of the state data.
*
* The state information in the buffer includes patch changes, tempo changes,
* time signature, key signature,
* and controller information. Only the most recent of these paramters before
* the current position will be stored. The state buffer will be returned
* in polymsg format so that it may be directly transmitted over the MIDI
* bus to bring the state up to date.
*
* The buffer is mean to be sent as a streaming buffer; i.e. immediately
* followed by the first data buffer. If the requested tick position
* does not exist in the file, the last event in the buffer
* will be a MEVT_NOP with a delta time calculated to make sure that
* the next stream event plays at the proper time.
*
* The meta events (tempo, time signature, key signature) will be the
* first events in the buffer if they exist.
*
* Use smfGetStateMaxSize to determine the maximum size of the state
* information buffer. State information that will not fit into the given
* buffer will be lost.
*
* On return, the dwBytesRecorded field of lpmh will contain the
* actual number of bytes stored in the buffer.
*
*****************************************************************************/

typedef struct tag_keyframe
{
    /*
    ** Meta events. All FF's indicates never seen.
    */
    BYTE        rbTempo[3];

    /*
    ** MIDI channel messages. FF indicates never seen.
    */
    BYTE        rbProgram[16];
    BYTE        rbControl[16*120];
}   KEYFRAME,
    FAR *PKEYFRAME;

#define KF_EMPTY ((BYTE)0xFF)

SMFRESULT FNLOCAL smfSeek(
    HSMF                    hSmf,
    TICKS                   tkPosition,
    LPMIDIHDR               lpmh)
{
    PSMF                    pSmf    = (PSMF)hSmf;
    PTRACK                  ptrk;
    DWORD                   idxTrack;
    SMFRESULT               smfrc;
    EVENT                   event;
    LPDWORD                 lpdw;
    BYTE                    bEvent;
    UINT                    idx;
    UINT                    idxChannel;
    UINT                    idxController;

    static KEYFRAME         kf;

    _fmemset(&kf, 0xFF, sizeof(kf));

    pSmf->tkPosition = 0;
    pSmf->fdwSMF &= ~SMF_F_EOF;

    for (ptrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; ptrk++)
    {
        ptrk->pSmf              = pSmf;
        ptrk->tkPosition        = 0;
        ptrk->cbLeft            = ptrk->smti.cbLength;
        ptrk->hpbImage          = pSmf->hpbImage + ptrk->idxTrack;
        ptrk->bRunningStatus    = 0;
        ptrk->fdwTrack          = 0;
    }

    while (SMF_SUCCESS == (smfrc = smfGetNextEvent(pSmf, (SPEVENT)&event, tkPosition)))
    {
        if (MIDI_META == (bEvent = EVENT_TYPE(event)))
        {
            if (EVENT_META_TYPE(event) == MIDI_META_TEMPO)
            {
                if (event.cbParm != sizeof(kf.rbTempo))
                    return SMF_INVALID_FILE;

                hmemcpy((HPBYTE)kf.rbTempo, event.hpbParm, event.cbParm);
            }
        }
        else switch(bEvent & 0xF0)
        {
            case MIDI_PROGRAMCHANGE:
                kf.rbProgram[bEvent & 0x0F] = EVENT_CH_B1(event);
                break;

            case MIDI_CONTROLCHANGE:
                kf.rbControl[(((WORD)bEvent & 0x0F)*120) + EVENT_CH_B1(event)] =
                    EVENT_CH_B2(event);
                break;
        }
    }

    if (SMF_REACHED_TKMAX != smfrc)
    {
        return smfrc;
    }

    /* Build lpmh from keyframe
    */
    lpmh->dwBytesRecorded = 0;
    lpdw = (LPDWORD)lpmh->lpData;

    /* Tempo change event?
    */
    if (KF_EMPTY != kf.rbTempo[0] ||
        KF_EMPTY != kf.rbTempo[1] ||
        KF_EMPTY != kf.rbTempo[2])
    {
        if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
            return SMF_NO_MEMORY;

        *lpdw++ = 0;
        *lpdw++ = 0;
        *lpdw++ = (((DWORD)kf.rbTempo[0])<<16)|
                  (((DWORD)kf.rbTempo[1])<<8)|
                  ((DWORD)kf.rbTempo[2])|
                  (((DWORD)MEVT_TEMPO) << 24);

        lpmh->dwBytesRecorded += 3*sizeof(DWORD);
    }

    /* Program change events?
    */
    for (idx = 0; idx < 16; idx++)
    {
        if (KF_EMPTY != kf.rbProgram[idx])
        {
            if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
                return SMF_NO_MEMORY;

            *lpdw++ = 0;
            *lpdw++ = 0;
            *lpdw++ = (((DWORD)MEVT_SHORTMSG) << 24)      |
                      ((DWORD)MIDI_PROGRAMCHANGE)         |
                      ((DWORD)idx)                        |
                      (((DWORD)kf.rbProgram[idx]) << 8);

            lpmh->dwBytesRecorded += 3*sizeof(DWORD);
        }
    }

    /* Controller events?
    */
    idx = 0;
    for (idxChannel = 0; idxChannel < 16; idxChannel++)
    {
        for (idxController = 0; idxController < 120; idxController++)
        {
            if (KF_EMPTY != kf.rbControl[idx])
            {
                if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
                    return SMF_NO_MEMORY;

                *lpdw++ = 0;
                *lpdw++ = 0;
                *lpdw++ = (((DWORD)MEVT_SHORTMSG << 24)     |
                          ((DWORD)MIDI_CONTROLCHANGE)       |
                          ((DWORD)idxChannel)               |
                          (((DWORD)idxController) << 8)     |
                          (((DWORD)kf.rbControl[idx]) << 16));

                lpmh->dwBytesRecorded += 3*sizeof(DWORD);
            }

            idx++;
        }
    }

    /* Force all tracks to be at tkPosition. We are guaranteed that
    ** all tracks will be past the event immediately preceding tkPosition;
    ** this will force correct delta-ticks to be generated so that events
    ** on all tracks will line up properly on a seek into the middle of the
    ** file.
    */
    for (ptrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; ptrk++)
    {
        ptrk->tkPosition        = tkPosition;
    }

    return SMF_SUCCESS;
}

/******************************************************************************
*
* smfGetStateMaxSize
*
* This function returns the maximum sizeof buffer that is needed to
* hold the state information returned by f smfSeek.
*
* pdwSize                   - Gets the size in bytes that should be allocated
*                             for the state buffer.
*
* Returns the state size in bytes.
*
*****************************************************************************/
DWORD FNLOCAL smfGetStateMaxSize(
    VOID)
{
    return  3*sizeof(DWORD) +           /* Tempo                */
            3*16*sizeof(DWORD) +        /* Patch changes        */
            3*16*120*sizeof(DWORD) +    /* Controller changes   */
            3*sizeof(DWORD);            /* Time alignment NOP   */
}


SMF.H

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* SMF.H
*
* Public include file for Standard MIDI File access routines.
*
*****************************************************************************/

#ifndef _SMF_
#define _SMF_

#include "global.h"

typedef DWORD SMFRESULT;
typedef DWORD TICKS;
typedef TICKS FAR *PTICKS;
typedef BYTE HUGE *HPBYTE;

#define MAX_TICKS           ((TICKS)0xFFFFFFFFL)

#define SMF_SUCCESS         (0L)
#define SMF_INVALID_FILE    (1L)
#define SMF_NO_MEMORY       (2L)
#define SMF_OPEN_FAILED     (3L)
#define SMF_INVALID_TRACK   (4L)
#define SMF_META_PENDING    (5L)
#define SMF_ALREADY_OPEN    (6L)
#define SMF_END_OF_TRACK    (7L)
#define SMF_NO_META         (8L)
#define SMF_INVALID_PARM    (9L)
#define SMF_INVALID_BUFFER  (10L)
#define SMF_END_OF_FILE     (11L)
#define SMF_REACHED_TKMAX   (12L)

DECLARE_HANDLE(HSMF);

typedef struct tag_smfopenstruct
{
    LPSTR               pstrName;
    DWORD               dwTimeDivision;
    HSMF                hSmf;
}   SMFOPENFILESTRUCT,
    FAR *PSMFOPENFILESTRUCT;

extern SMFRESULT FNLOCAL smfOpenFile(
    PSMFOPENFILESTRUCT  psofs);

extern SMFRESULT FNLOCAL smfCloseFile(
    HSMF                hsmf);

typedef struct tag_smffileinfo
{
    DWORD               dwTracks;
    DWORD               dwFormat;
    DWORD               dwTimeDivision;
    TICKS               tkLength;
}   SMFFILEINFO,
    FAR *PSMFFILEINFO;

extern SMFRESULT FNLOCAL smfGetFileInfo(
    HSMF                hsmf,
    PSMFFILEINFO        psfi);

extern DWORD FNLOCAL smfTicksToMillisecs(
    HSMF                hsmf,
    TICKS               tkOffset);

extern DWORD FNLOCAL smfMillisecsToTicks(
    HSMF                hsmf,
    DWORD               msOffset);

extern SMFRESULT FNLOCAL smfReadEvents(
    HSMF                hsmf,
    LPMIDIHDR           lpmh,
    TICKS               tkMax);

extern SMFRESULT FNLOCAL smfSeek(
    HSMF                hsmf,
    TICKS               tkPosition,
    LPMIDIHDR           lpmh);

extern DWORD FNLOCAL smfGetStateMaxSize(
    void);

/* Buffer described by LPMIDIHDR is in polymsg format, except that it
** can contain meta-events (which will be ignored during playback by
** the current system). This means we can use the pack functions, etc.
*/
#define PMSG_META       ((BYTE)0xC0)

#endif


SMFI.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
#ifndef _SMFI_
#define _SMFI_

/* Handle structure for HSMF
*/

#define SMF_TF_EOT          0x00000001L
#define SMF_TF_INVALID      0x00000002L

typedef struct tag_tempomapentry
{
    TICKS           tkTempo;
    DWORD           msBase;
    DWORD           dwTempo;
}   TEMPOMAPENTRY,
    *PTEMPOMAPENTRY;

typedef struct tag_smf *PSMF;

typedef struct tag_track
{
    PSMF            pSmf;

    DWORD           idxTrack;

    TICKS           tkPosition;
    DWORD           cbLeft;
    HPBYTE          hpbImage;
    BYTE            bRunningStatus;

    DWORD           fdwTrack;

    struct
    {
        TICKS       tkLength;
        DWORD       cbLength;
    }
    smti;

}   TRACK,
    *PTRACK;

#define SMF_F_EOF               0x00000001L
#define SMF_F_INSERTSYSEX       0x00000002L

#define C_TEMPO_MAP_CHK     16
typedef struct tag_smf
{
    char            szName[128];
    HPBYTE          hpbImage;
    DWORD           cbImage;
    HTASK           htask;

    TICKS           tkPosition;
    TICKS           tkLength;
    DWORD           dwFormat;
    DWORD           dwTracks;
    DWORD           dwTimeDivision;
    DWORD           fdwSMF;

    DWORD           cTempoMap;
    DWORD           cTempoMapAlloc;
    HLOCAL          hTempoMap;
    PTEMPOMAPENTRY  pTempoMap;

    DWORD           dwPendingUserEvent;
    DWORD           cbPendingUserEvent;
    HPBYTE          hpbPendingUserEvent;

    TRACK           rTracks[];
}   SMF;

typedef struct tagEVENT
{
    TICKS           tkDelta;
    BYTE            abEvent[3];

    DWORD           cbParm;
    HPBYTE          hpbParm;
}   EVENT,
    BSTACK *SPEVENT;

#define EVENT_TYPE(event)       ((event).abEvent[0])
#define EVENT_CH_B1(event)      ((event).abEvent[1])
#define EVENT_CH_B2(event)      ((event).abEvent[2])

#define EVENT_META_TYPE(event)  ((event).abEvent[1])

SMFRESULT FNLOCAL smfBuildFileIndex(
    PSMF BSTACK *       ppsmf);

DWORD FNLOCAL smfGetVDword(
    HPBYTE              hpbImage,
    DWORD               dwLeft,
    DWORD BSTACK *      pdw);

SMFRESULT FNLOCAL smfGetNextEvent(
    PSMF                psmf,
    SPEVENT             pevent,
    TICKS               tkMax);

/*
** Useful macros when dealing with hi-lo format integers
*/
#define DWORDSWAP(dw) \
    ((((dw)>>24)&0x000000FFL)|\
    (((dw)>>8)&0x0000FF00L)|\
    (((dw)<<8)&0x00FF0000L)|\
    (((dw)<<24)&0xFF000000L))

#define WORDSWAP(w) \
    ((((w)>>8)&0x00FF)|\
    (((w)<<8)&0xFF00))

#define FOURCC_RMID     mmioFOURCC('R','M','I','D')
#define FOURCC_data     mmioFOURCC('d','a','t','a')
#define FOURCC_MThd     mmioFOURCC('M','T','h','d')
#define FOURCC_MTrk     mmioFOURCC('M','T','r','k')

typedef struct tag_chunkhdr
{
    FOURCC  fourccType;
    DWORD   dwLength;
}   CHUNKHDR,
    *PCHUNKHDR;

#pragma pack(1)// override cl32 default packing, to match disk file.
typedef struct tag_filehdr
{
    WORD    wFormat;
    WORD    wTracks;
    WORD    wDivision;
}   FILEHDR,
    *PFILEHDR;
#pragma pack()

/* NOTE: This is arbitrary and only used if there is a tempo map but no
** entry at tick 0.
*/
#define MIDI_DEFAULT_TEMPO      (500000L)

#define MIDI_MSG                ((BYTE)0x80)
#define MIDI_NOTEOFF            ((BYTE)0x80)
#define MIDI_NOTEON             ((BYTE)0x90)
#define MIDI_POLYPRESSURE       ((BYTE)0xA0)
#define MIDI_CONTROLCHANGE      ((BYTE)0xB0)
#define MIDI_PROGRAMCHANGE      ((BYTE)0xC0)
#define MIDI_CHANPRESSURE       ((BYTE)0xD0)
#define MIDI_PITCHBEND          ((BYTE)0xE0)
#define MIDI_META               ((BYTE)0xFF)
#define MIDI_SYSEX              ((BYTE)0xF0)
#define MIDI_SYSEXEND           ((BYTE)0xF7)

#define MIDI_META_TRACKNAME     ((BYTE)0x03)
#define MIDI_META_EOT           ((BYTE)0x2F)
#define MIDI_META_TEMPO         ((BYTE)0x51)
#define MIDI_META_TIMESIG       ((BYTE)0x58)
#define MIDI_META_KEYSIG        ((BYTE)0x59)
#define MIDI_META_SEQSPECIFIC   ((BYTE)0x7F)

#endif


SMFREAD.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* SMFRead.C
*
* MIDI File access routines.
*
*****************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include "muldiv32.h"
#include "smf.h"
#include "smfi.h"
#include "debug.h"

PRIVATE UINT grbChanMsgLen[] =
{
    0,                      /* 0x   not a status byte   */
    0,                      /* 1x   not a status byte   */
    0,                      /* 2x   not a status byte   */
    0,                      /* 3x   not a status byte   */
    0,                      /* 4x   not a status byte   */
    0,                      /* 5x   not a status byte   */
    0,                      /* 6x   not a status byte   */
    0,                      /* 7x   not a status byte   */
    3,                      /* 8x   Note off            */
    3,                      /* 9x   Note on             */
    3,                      /* Ax   Poly pressure       */
    3,                      /* Bx   Control change      */
    2,                      /* Cx   Program change      */
    2,                      /* Dx   Chan pressure       */
    3,                      /* Ex   Pitch bend change   */
    0,                      /* Fx   SysEx (see below)   */
} ;

/******************************************************************************
*
* smfBuildFileIndex
*
* Preliminary parsing of a MIDI file.
*
* ppSmf                     - Pointer to a returned SMF structure if the
*                             file is successfully parsed.
*
* Returns
*   SMF_SUCCESS The events were successfully read.
*   SMF_NO_MEMORY Out of memory to build key frames.
*   SMF_INVALID_FILE A disk or parse error occured on the file.
*
* This function validates the format of and existing MIDI or RMI file
* and builds the handle structure which will refer to it for the
* lifetime of the instance.
*
* The file header information will be read and verified, and
* smfBuildTrackIndices will be called on every existing track
* to build keyframes and validate the track format.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfBuildFileIndex(
    PSMF BSTACK *           ppSmf)
{
    SMFRESULT               smfrc;
    UNALIGNED CHUNKHDR *    pCh;
    FILEHDR FAR *           pFh;
    DWORD                   idx;
    PSMF                    pSmf,
                            pSmfTemp;
    PTRACK                  pTrk;
    WORD                    wMemory;
    DWORD                   dwLeft;
    HPBYTE                  hpbImage;

    DWORD                   idxTrack;
    EVENT                   event;
    BOOL                    fFirst;
    DWORD                   dwLength;
    HLOCAL                  hLocal;
    PTEMPOMAPENTRY          pTempo;

    assert(ppSmf != NULL);

    pSmf = *ppSmf;

    assert(pSmf != NULL);

    /* MIDI data image is already in hpbImage (already extracted from
    ** RIFF header if necessary).
    */

    /* Validate MIDI header
    */
    dwLeft   = pSmf->cbImage;
    hpbImage = pSmf->hpbImage;

    if (dwLeft < sizeof(CHUNKHDR))
        return SMF_INVALID_FILE;

    pCh = (CHUNKHDR FAR *)hpbImage;

    dwLeft   -= sizeof(CHUNKHDR);
    hpbImage += sizeof(CHUNKHDR);

    if (pCh->fourccType != FOURCC_MThd)
        return SMF_INVALID_FILE;

    dwLength = DWORDSWAP(pCh->dwLength);
    if (dwLength < sizeof(FILEHDR) || dwLength > dwLeft)
        return SMF_INVALID_FILE;

    pFh = (FILEHDR FAR *)hpbImage;

    dwLeft   -= dwLength;
    hpbImage += dwLength;

    pSmf->dwFormat       = (DWORD)(WORDSWAP(pFh->wFormat));
    pSmf->dwTracks       = (DWORD)(WORDSWAP(pFh->wTracks));
    pSmf->dwTimeDivision = (DWORD)(WORDSWAP(pFh->wDivision));

    /*
    ** We've successfully parsed the header. Now try to build the track
    ** index.
    **
    ** We only check out the track header chunk here; the track will be
    ** preparsed after we do a quick integretiy check.
    */
    wMemory = sizeof(SMF) + (WORD)(pSmf->dwTracks*sizeof(TRACK));
    pSmfTemp = (PSMF)LocalReAlloc((HLOCAL)pSmf, wMemory, LMEM_MOVEABLE|LMEM_ZEROINIT);

    if (NULL == pSmfTemp)
    {
        DPF(1, "No memory for extended pSmf");
        return SMF_NO_MEMORY;
    }

    pSmf = *ppSmf = pSmfTemp;
    pTrk = pSmf->rTracks;

    for (idx=0; idx<pSmf->dwTracks; idx++)
    {
        if (dwLeft < sizeof(CHUNKHDR))
            return SMF_INVALID_FILE;

        pCh = (CHUNKHDR FAR *)hpbImage;

        dwLeft   -= sizeof(CHUNKHDR);
        hpbImage += sizeof(CHUNKHDR);

        if (pCh->fourccType != FOURCC_MTrk)
            return SMF_INVALID_FILE;

        pTrk->idxTrack      = (DWORD)(hpbImage - pSmf->hpbImage);
        pTrk->smti.cbLength = DWORDSWAP(pCh->dwLength);

        if (pTrk->smti.cbLength > dwLeft)
        {
            DPF(1, "Track longer than file!");
            return SMF_INVALID_FILE;
        }

        dwLeft   -= pTrk->smti.cbLength;
        hpbImage += pTrk->smti.cbLength;

        pTrk++;
    }

    /* File looks OK. Now preparse, doing the following:
    ** (1) Build tempo map so we can convert to/from ticks quickly
    ** (2) Determine actual tick length of file
    ** (3) Validate all events in all tracks
    */
    pSmf->tkPosition = 0;
    pSmf->fdwSMF &= ~SMF_F_EOF;

    for (pTrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; pTrk++)
    {
        pTrk->pSmf              = pSmf;
        pTrk->tkPosition        = 0;
        pTrk->cbLeft            = pTrk->smti.cbLength;
        pTrk->hpbImage          = pSmf->hpbImage + pTrk->idxTrack;
        pTrk->bRunningStatus    = 0;
        pTrk->fdwTrack          = 0;
    }

    while (SMF_SUCCESS == (smfrc = smfGetNextEvent(pSmf, (EVENT BSTACK *)&event, MAX_TICKS)))
    {
        if (MIDI_META == event.abEvent[0] &&
            MIDI_META_TEMPO == event.abEvent[1])
        {
            if (3 != event.cbParm)
            {
                return SMF_INVALID_FILE;
            }

            if (pSmf->cTempoMap == pSmf->cTempoMapAlloc)
            {
                if (NULL != pSmf->hTempoMap)
                {
                    LocalUnlock(pSmf->hTempoMap);
                }

                pSmf->cTempoMapAlloc += C_TEMPO_MAP_CHK;
                fFirst = FALSE;
                if (0 == pSmf->cTempoMap)
                {
                    hLocal = LocalAlloc(LHND, (UINT)(pSmf->cTempoMapAlloc*sizeof(TEMPOMAPENTRY)));
                    fFirst = TRUE;
                }
                else
                {
                    hLocal = LocalReAlloc(pSmf->hTempoMap, (UINT)(pSmf->cTempoMapAlloc*sizeof(TEMPOMAPENTRY)), LHND);
                }

                if (NULL == hLocal)
                {
                    return SMF_NO_MEMORY;
                }

                pSmf->pTempoMap = (PTEMPOMAPENTRY)LocalLock(pSmf->hTempoMap = hLocal);
            }

            if (fFirst && pSmf->tkPosition != 0)
            {
                /* Inserting first event and the absolute time is zero.
                ** Use defaults of 500,000 uSec/qn from MIDI spec
                */

                pTempo = &pSmf->pTempoMap[pSmf->cTempoMap++];

                pTempo->tkTempo = 0;
                pTempo->msBase  = 0;
                pTempo->dwTempo = MIDI_DEFAULT_TEMPO;

                fFirst = FALSE;
            }

            pTempo = &pSmf->pTempoMap[pSmf->cTempoMap++];

            pTempo->tkTempo = pSmf->tkPosition;
            if (fFirst)
                pTempo->msBase = 0;
            else
            {
                /* NOTE: Better not be here unless we're q/n format!
                */
                pTempo->msBase = (pTempo-1)->msBase +
                                 muldiv32(pTempo->tkTempo-((pTempo-1)->tkTempo),
                                          (pTempo-1)->dwTempo,
                                          1000L*pSmf->dwTimeDivision);
            }
            pTempo->dwTempo = (((DWORD)event.hpbParm[0])<<16)|
                              (((DWORD)event.hpbParm[1])<<8)|
                              ((DWORD)event.hpbParm[2]);
        }
    }

if (0 == pSmf->cTempoMap)
{
DPF(1, "File contains no tempo map! Insert default tempo.");

hLocal = LocalAlloc(LHND, sizeof(TEMPOMAPENTRY));
if (!hLocal)
return SMF_NO_MEMORY;

        pSmf->pTempoMap = (PTEMPOMAPENTRY)LocalLock(pSmf->hTempoMap = hLocal);
pSmf->cTempoMap = 1;
pSmf->cTempoMapAlloc = 1;

pSmf->pTempoMap->tkTempo = 0;
        pSmf->pTempoMap->msBase  = 0;
        pSmf->pTempoMap->dwTempo = MIDI_DEFAULT_TEMPO;
}

    if (SMF_END_OF_FILE == smfrc || SMF_SUCCESS == smfrc)
    {
        pSmf->tkLength = pSmf->tkPosition;
        smfrc = SMF_SUCCESS;
    }

    return smfrc;
}

/******************************************************************************
*
* smfGetNextEvent
*
* Read the next event from the given file.
*
* pSmf                      - File to read the event from.
*
* pEvent                    - Pointer to an event structure which will receive
*                             basic information about the event.
*
* tkMax                     - Tick destination. An attempt to read past this
*                             position in the file will fail.
*
* Returns
*   SMF_SUCCESS The events were successfully read.
*   SMF_END_OF_FILE There are no more events to read in this track.
*   SMF_REACHED_TKMAX No event was read because <p tkMax> was reached.
*   SMF_INVALID_FILE A disk or parse error occured on the file.
*
* This is the lowest level of parsing for a raw MIDI stream. The basic
* information about one event in the file will be returned in pEvent.
*
* Merging data from all tracks into one stream is performed here.
*
* pEvent->tkDelta will contain the tick delta for the event.
*
* pEvent->abEvent will contain a description of the event.
*  pevent->abEvent[0] will contain
*    F0 or F7 for a System Exclusive message.
*    FF for a MIDI file meta event.
*    The status byte of any other MIDI message. (Running status will
*    be tracked and expanded).
*
* pEvent->cbParm will contain the number of bytes of paramter data
*   which is still in the file behind the event header already read.
*   This data may be read with <f smfGetTrackEventData>. Any unread
*   data will be skipped on the next call to <f smfGetNextTrackEvent>.
*
* Channel messages (0x8? - 0xE?) will always be returned fully in
*   pevent->abEvent.
*
*  Meta events will contain the meta type in pevent->abEvent[1].
*
*  System exclusive events will contain only an 0xF0 or 0xF7 in
*    pevent->abEvent[0].
*
*  The following fields in pTrk are used to maintain state and must
*  be updated if a seek-in-track is performed:
*
*  bRunningStatus contains the last running status message or 0 if
*   there is no valid running status.
*
*  hpbImage is a pointer into the file image of the first byte of
*   the event to follow the event just read.
*
*  dwLeft contains the number of bytes from hpbImage to the end
*   of the track.
*
*
* Get the next due event from all (in-use?) tracks
*
* For all tracks
*  If not end-of-track
*   decode event delta time without advancing through buffer
*   event_absolute_time = track_tick_time + track_event_delta_time
*   relative_time = event_absolute_time - last_stream_time
*   if relative_time is lowest so far
*    save this track as the next to pull from, along with times
*
* If we found a track with a due event
*  Advance track pointer past event, saving ptr to parm data if needed
*  track_tick_time += track_event_delta_time
*  last_stream_time = track_tick_time
* Else
*  Mark and return end_of_file
*
*****************************************************************************/
SMFRESULT FNLOCAL smfGetNextEvent(
    PSMF                    pSmf,
    EVENT BSTACK *          pEvent,
    TICKS                   tkMax)
{
    PTRACK                  pTrk;
    PTRACK                  pTrkFound;
    DWORD                   idxTrack;
    TICKS                   tkEventDelta;
    TICKS                   tkRelTime;
    TICKS                   tkMinRelTime;
    BYTE                    bEvent;
    DWORD                   dwGotTotal;
    DWORD                   dwGot;
    DWORD                   cbEvent;

    assert(pSmf != NULL);
    assert(pEvent != NULL);

    if (pSmf->fdwSMF & SMF_F_EOF)
    {
        return SMF_END_OF_FILE;
    }

    pTrkFound       = NULL;
    tkMinRelTime    = MAX_TICKS;

    for (pTrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; pTrk++)
    {
        if (pTrk->fdwTrack & SMF_TF_EOT)
            continue;

        if (!smfGetVDword(pTrk->hpbImage, pTrk->cbLeft, (DWORD BSTACK *)&tkEventDelta))
        {
            DPF(1, "Hit end of track w/o end marker!");
            return SMF_INVALID_FILE;
        }

        tkRelTime = pTrk->tkPosition + tkEventDelta - pSmf->tkPosition;

        if (tkRelTime < tkMinRelTime)
        {
            tkMinRelTime = tkRelTime;
            pTrkFound = pTrk;
        }
    }

    if (!pTrkFound)
    {
        pSmf->fdwSMF |= SMF_F_EOF;
        return SMF_END_OF_FILE;
    }

    pTrk = pTrkFound;

    if (pSmf->tkPosition + tkMinRelTime >= tkMax)
    {
        return SMF_REACHED_TKMAX;
    }

    pTrk->hpbImage += (dwGot = smfGetVDword(pTrk->hpbImage, pTrk->cbLeft, (DWORD BSTACK *)&tkEventDelta));
    pTrk->cbLeft   -= dwGot;

    /* We MUST have at least three bytes here (cause we haven't hit
    ** the end-of-track meta yet, which is three bytes long). Checking
    ** against three means we don't have to check how much is left
    ** in the track again for any short event, which is most cases.
    */
    if (pTrk->cbLeft < 3)
    {
        return SMF_INVALID_FILE;
    }

    pTrk->tkPosition += tkEventDelta;
    pEvent->tkDelta = pTrk->tkPosition - pSmf->tkPosition;
    pSmf->tkPosition = pTrk->tkPosition;

    bEvent = *pTrk->hpbImage++;

    if (MIDI_MSG > bEvent)
    {
        if (0 == pTrk->bRunningStatus)
        {
            return SMF_INVALID_FILE;
        }

        dwGotTotal = 1;
        pEvent->abEvent[0] = pTrk->bRunningStatus;
        pEvent->abEvent[1] = bEvent;
        if (3 == grbChanMsgLen[(pTrk->bRunningStatus >> 4) & 0x0F])
        {
            pEvent->abEvent[2] = *pTrk->hpbImage++;
            dwGotTotal++;
        }
    }
    else if (MIDI_SYSEX > bEvent)
    {
        pTrk->bRunningStatus = bEvent;

        dwGotTotal = 2;
        pEvent->abEvent[0] = bEvent;
        pEvent->abEvent[1] = *pTrk->hpbImage++;
        if (3 == grbChanMsgLen[(bEvent >> 4) & 0x0F])
        {
            pEvent->abEvent[2] = *pTrk->hpbImage++;
            dwGotTotal++;
        }
    }
    else
    {
        pTrk->bRunningStatus = 0;
        if (MIDI_META == bEvent)
        {
            pEvent->abEvent[0] = MIDI_META;
            if (MIDI_META_EOT == (pEvent->abEvent[1] = *pTrk->hpbImage++))
            {
                pTrk->fdwTrack |= SMF_TF_EOT;
            }

            dwGotTotal = 2;
        }
        else if (MIDI_SYSEX == bEvent || MIDI_SYSEXEND == bEvent)
        {
            pEvent->abEvent[0] = bEvent;
            dwGotTotal = 1;
        }
        else
        {
            return SMF_INVALID_FILE;
        }

        if (0 == (dwGot = smfGetVDword(pTrk->hpbImage, pTrk->cbLeft - 2, (DWORD BSTACK *)&cbEvent)))
        {
            return SMF_INVALID_FILE;
        }

        pTrk->hpbImage  += dwGot;
        dwGotTotal      += dwGot;

        if (dwGotTotal + cbEvent > pTrk->cbLeft)
        {
            return SMF_INVALID_FILE;
        }

        pEvent->cbParm  = cbEvent;
        pEvent->hpbParm = pTrk->hpbImage;

        pTrk->hpbImage += cbEvent;
        dwGotTotal     += cbEvent;
    }

    assert(pTrk->cbLeft >= dwGotTotal);

    pTrk->cbLeft -= dwGotTotal;

    return SMF_SUCCESS;
}

/******************************************************************************
*
* smfGetVDword
*
* Reads a variable length DWORD from the given file.
*
* hpbImage                  - Pointer to the first byte of the VDWORD.
*
* dwLeft                    - Bytes left in image
*
* pDw                       - Pointer to a DWORD to store the result in.
*                             track.
*
* Returns the number of bytes consumed from the stream.
*
* A variable length DWORD stored in a MIDI file contains one or more
* bytes. Each byte except the last has the high bit set; only the
* low 7 bits are significant.
*
*****************************************************************************/
DWORD FNLOCAL smfGetVDword(
    HPBYTE                  hpbImage,
    DWORD                   dwLeft,
    DWORD BSTACK *          pDw)
{
    BYTE                    b;
    DWORD                   dwUsed  = 0;

    assert(hpbImage != NULL);
    assert(pDw != NULL);

    *pDw = 0;

    do
    {
        if (!dwLeft)
        {
            return 0;
        }

        b = *hpbImage++;
        dwLeft--;
        dwUsed++;

        *pDw = (*pDw << 7) | (b & 0x7F);
    } while (b&0x80);

    return dwUsed;
}


TIMEWND.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* TimeWnd.C
*
* Message handlers and other code for the time window
*
*****************************************************************************/

#pragma warning(disable:4756)

#define _INC_SHELLAPI
#include <windows.h>
#undef _INC_SHELLAPI

#include <shellapi.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <commctrl.h>
#include <ctype.h>

#include "debug.h"

#include "MIDIPlyr.H"

/* Into gszFormatHMS[]
*/
#define HOUR_INDEX          0
#define MIN_INDEX           3
#define SEC_INDEX           6

#define MIN_SEP_INDEX       2
#define SEC_SEP_INDEX       5

#define TIMER_ID            0
#define TIMER_INTERVAL      100

PRIVATE HFONT           ghFont          = NULL;
PRIVATE char BCODE      gszFormatHMS[]  = "44:44:44";
PRIVATE char BCODE      gszFormatTicks[]= "444444444";

#define MAX_CBFMT (max(sizeof(gszFormatHMS), sizeof(gszFormatTicks)))

PRIVATE BOOL            gbRepaint       = TRUE;
PRIVATE int             gnPosY;
PRIVATE int             ganPosX[MAX_CBFMT];

PRIVATE int             gnTimerOn       = 0;

PRIVATE VOID NEAR PASCAL PaintTime(HDC hDC);

PRIVATE BOOL NEAR PASCAL TWnd_OnCreate(HWND hWnd, CREATESTRUCT FAR* lpCreateStruct);
PRIVATE VOID NEAR PASCAL TWnd_OnSize(HWND hWnd, UINT state, int cx, int cy);
PRIVATE VOID NEAR PASCAL TWnd_OnPaint(HWND hWnd);
PRIVATE VOID NEAR PASCAL TWnd_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify);
PRIVATE VOID NEAR PASCAL TWnd_OnDestroy(HWND hWnd);
PRIVATE VOID NEAR PASCAL TWnd_OnTimer(HWND hWnd, UINT id);

/*****************************************************************************
*
* PaintTime
*
* Paint the current sequencer time into the time window.
*
* HWND hDC                  - DC to paint time into
*
* Repaint the time. Unless gbRepaint is set, just repaint the sections
* of the time that need updating. Clear gbRepaint when done.
*
*****************************************************************************/
static UINT oldh = 0;
static UINT oldm = 0;
static UINT olds = 0;

static char szOldTicks[MAX_CBFMT];

PRIVATE VOID NEAR PASCAL PaintTime(
    HDC                     hDC)
{
    TICKS                   tkTime;
    DWORD                   msTime;
    int                     ii;
    int                     cb;
    char                    szWork[MAX_CBFMT];
    HFONT                   hFont;
    UINT                    h;
    UINT                    m;
    UINT                    s;
    BOOL                    fRepaintedSec;
    BOOL                    fRepaintedMin;

    if (MMSYSERR_NOERROR != seqTime(gpSeq, &tkTime))
        return;

    SetBkColor(hDC, RGB(0xFF, 0xFF, 0xFF));
    hFont = SelectObject(hDC, ghFont);

    if (gnTimeFormat == IDS_TICKS)
    {
        wsprintf(szWork, "%9lu", (DWORD)tkTime);

        for (ii = 0; ii < 9; ii++)
        {
            if (gbRepaint || szOldTicks[ii] != szWork[ii])
                EmbossedTextOut(
                                hDC,
                                ganPosX[ii],
                                gnPosY,
                                szWork+ii,
                                1,
                                (COLORREF)-1,
                                (COLORREF)-1,
                                3,
                                3);
            szOldTicks[ii] = szWork[ii];
        }
    }
    else
    {
        msTime = seqTicksToMillisecs(gpSeq, tkTime) / 1000L;

        h = (UINT)(msTime / 3600L);    msTime %= 3600L;
        m = (UINT)(msTime / 60L);      msTime %= 60L;
        s = (UINT)msTime;

        cb = lstrlen(gszFormatHMS);

        if (gbRepaint || h != oldh)
        {
            szWork[0] = '0' + (char)(h / 10);
            szWork[1] = '0' + (char)(h % 10);
            EmbossedTextOut(
                            hDC,
                            ganPosX[HOUR_INDEX],
                            gnPosY,
                            szWork,
                            2,
                            (COLORREF)-1,
                            (COLORREF)-1,
                            3,
                            3);
            oldh = h;
        }

        fRepaintedMin = FALSE;
        if (gbRepaint || m != oldm)
        {
            szWork[0] = gszFormatHMS[MIN_SEP_INDEX];
            szWork[1] = '0' + (char)(m / 10);
            szWork[2] = '0' + (char)(m % 10);
            EmbossedTextOut(
                            hDC,
                            ganPosX[MIN_SEP_INDEX],
                            gnPosY,
                            szWork,
                            3,
                            (COLORREF)-1,
                            (COLORREF)-1,
                            3,
                            3);
            oldm = m;
            fRepaintedMin = TRUE;
        }

        fRepaintedSec = FALSE;
        if (gbRepaint || s != olds)
        {
            szWork[0] = gszFormatHMS[SEC_SEP_INDEX];
            szWork[1] = '0' + (char)(s / 10);
            szWork[2] = '0' + (char)(s % 10);
            EmbossedTextOut(
                            hDC,
                            ganPosX[SEC_SEP_INDEX],
                            gnPosY,
                            szWork,
                            3,
                            (COLORREF)-1,
                            (COLORREF)-1,
                            3,
                            3);
            olds = s;
            fRepaintedSec = TRUE;
        }

        /* If we're doing a full repaint, then update the separators
         */
        if (gbRepaint)
            for (ii=0; ii < cb; ii++)
                if (!isdigit(gszFormatHMS[ii]))
                {
                    if (SEC_SEP_INDEX == ii && fRepaintedSec)
                        continue;

                    if (MIN_SEP_INDEX == ii && fRepaintedMin)
                        continue;

                    EmbossedTextOut(
                                    hDC,
                                    ganPosX[ii],
                                    gnPosY,
                                    gszFormatHMS+ii,
                                    1,
                                    (COLORREF)-1,
                                    (COLORREF)-1,
                                    3,
                                    3);
                }
    }

    gbRepaint = FALSE;
    SelectObject(hDC, hFont);
}

/*****************************************************************************
*
* TWnd_OnCreate
*
* Handle WM_CREATE message to time window.
*
* HWND hWnd                 - Window handle
* CREATESTRUCT FAR* lpCreateStruct
*                           - Pointer to creation parameters for the window.
*
* Returns TRUE on success. Returning FALSE will cause the window to be
* destroyed and the application will exit.
*
* Just return TRUE so the window will be created.
*
*****************************************************************************/
BOOL NEAR PASCAL TWnd_OnCreate(
    HWND                    hWnd,
    CREATESTRUCT FAR*       lpCreateStruct)
{
    return TRUE;
}

/*****************************************************************************
*
* TWnd_OnSize
*
* Handle WM_SIZE message to time window.
*
* HWND hWnd                 - Window handle
* UINT state                - Some SIZE_xxx code indicating what type of
*                             size operation this is.
* int  cx, cy               - New x and y size of the window's client area.
*
* Get the new client rect into grcTWnd.
* Destroy and recreate the font scaled to show the time at the correct size.
* Force the time to fully repaint.
*
*****************************************************************************/
VOID NEAR PASCAL TWnd_OnSize(
    HWND                    hWnd,
    UINT                    state,
    int                     cx,
    int                     cy)
{
    HDC                     hDC;

    GetClientRect(hWnd, &grcTWnd);

    if (ghFont != (HFONT)NULL)
        DeleteObject(ghFont);

    hDC = GetDC(hWnd);
    ghFont = CreateScaledFont(hDC,
                              &grcTWnd,
                              (gnTimeFormat == IDS_TICKS) ? gszFormatTicks : gszFormatHMS,
                              ganPosX,
                              &gnPosY);
    ReleaseDC(hWnd, hDC);

    gbRepaint = TRUE;
    InvalidateRect(hWnd, NULL, TRUE);
}

/*****************************************************************************
*
* TWnd_OnPaint
*
* Handle WM_PAINT message to time window.
*
* HWND hWnd                 - Window handle
*
* Repaint the 3D inset borders around the edge of the client area.
* Repaint the time.
*
*****************************************************************************/
VOID NEAR PASCAL TWnd_OnPaint( HWND hWnd) { PAINTSTRUCT ps; HDC hDC;
HBRUSH hBrOld; int nWidth; int nHeight;

    RECT                    rc;

    GetClientRect(hWnd, &rc);
    nWidth  = rc.right;
    nHeight = rc.bottom;

    hDC = BeginPaint(hWnd, &ps);

    hBrOld = (HBRUSH)SelectObject(hDC, GetStockObject(GRAY_BRUSH));
    PatBlt(hDC, 0, 0, 1, nHeight-1, PATCOPY);
    PatBlt(hDC, 1, 0, nWidth-2, 1, PATCOPY);

    SelectObject(hDC, GetStockObject(BLACK_BRUSH));
    PatBlt(hDC, 1, 1, 1, nHeight-3, PATCOPY);
    PatBlt(hDC, 2, 1, nWidth-4, 1, PATCOPY);

    SelectObject(hDC, GetStockObject(WHITE_BRUSH));
    PatBlt(hDC, rc.right-1, 0, 1, nHeight-1, PATCOPY);
    PatBlt(hDC, 0, rc.bottom-1, nWidth, 1, PATCOPY);

    SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
    PatBlt(hDC, rc.right-2, 1, 1, nHeight-2, PATCOPY);
    PatBlt(hDC, 1, rc.bottom-2, nWidth-2, 1, PATCOPY);

    SelectObject(hDC, hBrOld);

    gbRepaint = TRUE;
    PaintTime(hDC);

    EndPaint(hWnd, &ps);
}

/*****************************************************************************
*
* TWnd_OnCommand
*
* Handle WM_COMMAND message to the time window.
*
* HWND hWnd                 - Window handle
* int id                    - id of control or menu causing WM_COMMAND
* HWND hwndCtl              - Window handle of child control, if any
* UINT codeNotify           - Notification code if this message is from a
*                             control.
*
* We will receive IDM_PLAY and IDM_STOP messages forwarded from the main
* application window whenver playback starts or stops. Use these messages
* to create or kill a timer which will be used to update the time.
*
*****************************************************************************/
VOID NEAR PASCAL TWnd_OnCommand(
    HWND                    hWnd,
    int                     id,
    HWND                    hWndCtl,
    UINT                    codeNotify)
{
    switch(id)
    {
        case IDM_PLAY:
            if (0 == gnTimerOn)
            {
                gnTimerOn++;
                SetTimer(hWnd, TIMER_ID, TIMER_INTERVAL, 0);
            }

            UpdateWindow(hWnd);
            break;

        case IDM_STOP:
            if (0 == gnTimerOn)
            {
                gnTimerOn--;
                KillTimer(hWnd, TIMER_ID);
            }

            UpdateWindow(hWnd);
            break;
    }
}

/*****************************************************************************
*
* TWnd_OnDestroy
*
* Handle WM_DESTROY message to the time window.
*
* HWND hWnd                 - Window handle
*
* If we're being destroyed and the timer is still running, stop it.
*
*****************************************************************************/
VOID NEAR PASCAL TWnd_OnDestroy(
    HWND                    hWnd)
{
    if (gnTimerOn)
    {
        KillTimer(hWnd, TIMER_ID);
    }
}

/*****************************************************************************
*
* TWnd_OnTimer
*
* Handle WM_TIMER message to the time window.
*
* HWND hWnd                 - Window handle
* UINT id                   - Timer ID
*
* Update the time.
*
*****************************************************************************/
PRIVATE VOID NEAR PASCAL TWnd_OnTimer(
    HWND                    hWnd,
    UINT                    id)
{
    HDC                     hDC;

    hDC = GetDC(hWnd);
    PaintTime(hDC);
    ReleaseDC(hWnd, hDC);
}

/*****************************************************************************
*
* TWnd_WndProc
*
* Window procedure for main application window.
*
* HWND hWnd                 - Window handle
* UINT msg                  - Message code
* WPARAM wParam             - Message specific parameter
* LPARAM lParam             - Message specific parameter
*
* Dispatch messages we care about to the appropriate handler, else just
* call DefWindowProc.
*
* Note this use of message cracker macros from windowsx.h. Using these
* macros will shield you from the differences between Win16 and Win32;
* if your app is cross-compilable, you should use these and save yourself
* some headaches!
*
*****************************************************************************/
LRESULT CALLBACK TWnd_WndProc(
    HWND                    hWnd,
    UINT                    msg,
    WPARAM                  wParam,
    LPARAM                  lParam)
{
    switch( msg )
    {
        HANDLE_MSG(hWnd, WM_CREATE,         TWnd_OnCreate);
        HANDLE_MSG(hWnd, WM_SIZE,           TWnd_OnSize);
        HANDLE_MSG(hWnd, WM_PAINT,          TWnd_OnPaint);
        HANDLE_MSG(hWnd, WM_COMMAND,        TWnd_OnCommand);
        HANDLE_MSG(hWnd, WM_DESTROY,        TWnd_OnDestroy);
        HANDLE_MSG(hWnd, WM_TIMER,          TWnd_OnTimer);

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

    return 0;
}


UIUTILS.C

/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* UiUtils.C
*
* UI utility routines
*
*****************************************************************************/

#pragma warning(disable:4756)

#define _INC_SHELLAPI
#include <windows.h>
#undef _INC_SHELLAPI

#include <shellapi.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <commctrl.h>
#include <ctype.h>

#include "debug.h"

#include "MIDIPlyr.H"

static char BCODE           gszErrFmt[]             = "%s:\n%s";
static char BCODE           gszFace[]               = "arial";
static char                 gszErrDescTxt[256];
static char                 gszErrCodeTxt[256];
static char                 gszErrStr[512];

/*****************************************************************************
*
* Error
*
* Puts up a general error dialog
*
* hWnd                      - Handle of the owner window
* nErrDesc                  - id into the string table of the message
* mmrc                      - Return code from MMSYSTEM or lower layer
*                             which caused the error.
*
* Just formats and puts up an error dialog.
*
* For convenience, all of the sequencer functions return MMSYSERR_ or
* MCIERR_ codes, so we can use mciGetErrorString instead of inventing
* our own error messages again.
*
*****************************************************************************/
VOID FNLOCAL Error(
    HWND                    hWnd,
    int                     nErrDesc,
    MMRESULT                mmrc)
{
    LoadString(ghInst, nErrDesc, gszErrDescTxt, sizeof(gszErrDescTxt));
    mciGetErrorString(mmrc, gszErrCodeTxt, sizeof(gszErrCodeTxt));

    wsprintf(gszErrStr, gszErrFmt, (LPSTR)gszErrDescTxt, (LPSTR)gszErrCodeTxt);
    MessageBox(hWnd, gszErrStr, gszAppLongName, MB_ICONEXCLAMATION|MB_OK);
}

/*****************************************************************************
*
* MessagePump
*
* Process messages
*
* This function is called when, in certain cases, we need to wait for a
* window callback to complete. Since callbacks are posted, not sent, we
* need to process messages while waiting.
*
* Note that some documentation refers to this operation as a 'directed
* yield' if messages are being processed for a particular window. This
* is misleading; Yield() merely allows other tasks in the system to run
* and does absolutely nothing to the message queue.
*
*****************************************************************************/
VOID NEAR PASCAL MessagePump(
    VOID)
{
    MSG                     msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

/*****************************************************************************
*
* EmbossedTextOut
*
* Draw embossed text in the given device context
*
* hDC                       - hDC to draw in
* x, y                      - Upper left corner of text
* lpsz                      - Pointer to the text
* cb                        - Length of text
* crText                    - Color for text face
* crShadow                  - Color for text shadow
* cx, cy                    - Offset for shadow
*
* The text will be drawn with the currently selected font.
*
* If cb == -1, the lstrlen(lpsz) will be used.
*
* If crText == -1, COLOR_BTNTEXT will be used.
*
* If crShadow == -1, COLOR_BTNSHADOW will be used.
*
*****************************************************************************/
VOID NEAR PASCAL EmbossedTextOut(
    HDC                     hDC,
    int                     x,
    int                     y,
    LPSTR                   lpsz,
    UINT                    cb,
    COLORREF                crText,
    COLORREF                crShadow,
    int                     cx,
    int                     cy)
{
    COLORREF                crOld;
    UINT                    uMode;
    SIZE                    sizeText;
    RECT                    rcText;

    /* If text length is -1, use lstrlen to get the length
    ** of the text.
    */
    if (cb == -1)
        cb = lstrlen(lpsz);

    /* If the shadow or text color is -1, use the
    ** system color for that one.
    */
    if (crShadow == (COLORREF)-1)
        crShadow = GetSysColor (COLOR_BTNSHADOW);
    if (crText == (COLORREF)-1)
        crText = GetSysColor (COLOR_BTNTEXT);

    /* setup the DC, saving off the old values
    */
    uMode = SetBkMode(hDC, OPAQUE);
    crOld = SetTextColor(hDC, crShadow);

    /* Draw the text at the desired offset using the
    ** shadow color, then again at the normal position
    ** using the text color.  This will the text an 'Embossed'
    ** or 'drop shadowed' look depending on what shadow color
    ** and offset are used.
    */
    GetTextExtentPoint32(hDC, lpsz, cb, &sizeText);
    rcText.left   = x;    rcText.right  = x+cx+sizeText.cx;
    rcText.top    = y;    rcText.bottom = y+cy+sizeText.cy;
    ExtTextOut(hDC, x+cx, y+cy, ETO_OPAQUE, &rcText, lpsz, cb, NULL);
    SetBkMode(hDC, TRANSPARENT);
    SetTextColor(hDC, crText);
    ExtTextOut(hDC, x, y, 0, NULL, lpsz, cb, NULL);

    /* restore the DC
    */
    SetTextColor(hDC, crOld);
    SetBkMode(hDC, uMode);
}

/*****************************************************************************
*
* CreateScaledFont
*
* Create a font scaled so that the given string will fit in the given
* rect, but be as large as possible while maintaining correct aspect ratio.
*
* hDC                       - DC to calculate font for
* lpRect                    - Rectangle to fit text into
* lpszFormat                - Format string to fit into rect
* anPosX[]                  - Will contain the X coordinates for each char
* anPosY                    - Will contain the Y coordinate for the string
*
* Returns HFONT or NULL if one could not be created
*
*****************************************************************************/
HFONT NEAR PASCAL CreateScaledFont(
    HDC                     hDC,
    LPRECT                  lpRect,
    LPSTR                   lpszFormat,
    int                     anPosX[],
    int*                    nPosY)
{
    LOGFONT                 lf;
    HFONT                   hFont;
    HFONT                   h;
    LONG                    FormatWidth;
    LONG                    ScaledClientWidth;
    LONG                    ScaledClientHeight;
    LONG                    AspectN;
    LONG                    AspectD;
    int                     nPosX;
    UINT                    cb;
    UINT                    ii;
    UINT                    jj;
    SIZE                    size;

    ScaledClientHeight =  ((lpRect->bottom - lpRect->top)) * 3 / 4;
    ScaledClientWidth  =  ((lpRect->right  - lpRect->left)) * 3 / 4;

    _fmemset(&lf, 0, sizeof(lf));
    lf.lfHeight         = -(int)ScaledClientHeight;
    lf.lfWeight         = FW_BOLD;
    lf.lfCharSet        = ANSI_CHARSET;
    lf.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
    lf.lfQuality        = PROOF_QUALITY;
    lf.lfPitchAndFamily = FF_ROMAN|DEFAULT_PITCH;
    lstrcpy(lf.lfFaceName, gszFace);

    hFont = CreateFontIndirect(&lf);
    h = SelectObject(hDC, hFont);

    cb = lstrlen(lpszFormat);
    GetTextExtentPoint(hDC, lpszFormat, cb, &size);

    AspectN = (LONG)size.cx;
    AspectD = (LONG)size.cy;

    FormatWidth = (ScaledClientHeight*AspectN)/AspectD;

    if (FormatWidth > ScaledClientWidth)
    {
        ScaledClientHeight =
            (ScaledClientWidth*AspectD)/AspectN;
    SelectObject(hDC, h);
        DeleteObject(hFont);

        lf.lfHeight = -(int)ScaledClientHeight;

        hFont = CreateFontIndirect(&lf);

        SelectObject(hDC, hFont);
        GetTextExtentPoint(hDC, lpszFormat, cb, &size);
    }

    *nPosY  = grcTWnd.top  + (grcTWnd.bottom- grcTWnd.top  - size.cy)/2;
    nPosX   = grcTWnd.left + (grcTWnd.right - grcTWnd.left - size.cx)/2;

    ii = 0;
    for (jj=0; jj < cb; jj++)
    {
        if (jj != 0)
            GetTextExtentPoint(hDC, lpszFormat, jj, &size);
        else
            size.cx = 0;

        anPosX[ii++] = nPosX + size.cx;
    }

    SelectObject(hDC, h);

    return hFont;
}


README.TXT

Sample mixer application.


DEBUG.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.c
//
//  Description:
//      This file contains code yanked from several places to provide debug
//      support that works in win 16 and win 32.
//
//
//==========================================================================;

#ifdef DEBUG
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdarg.h>
#include "debug.h"

BOOL    __gfDbgEnabled          = TRUE;         // master enable
UINT    __guDbgLevel            = 0;            // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF
//
//  Description:
//
//
//  Arguments:
//      LPSTR szFormat:
//
//      va_list va:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF
(
    LPSTR                   szFormat,
    va_list                 va
)
{
    char                ach[DEBUG_MAX_LINE_LEN];
    BOOL                fDebugBreak = FALSE;
    BOOL                fPrefix     = TRUE;
    BOOL                fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch (*szFormat)
        {
            case '!':
                fDebugBreak = TRUE;
                szFormat++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFormat++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFormat++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
    {
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");
    }

    wvsprintfA(ach + lstrlenA(ach), szFormat, va);

    if (fCRLF)
    {
        lstrcatA(ach, "\r\n");
    }

    OutputDebugStringA(ach);

    if (fDebugBreak)
    {
        DebugBreak();
    }
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf
//
//  Description:
//      dprintf() is called by the DPF() macro if DEBUG is defined at compile
//      time. It is recommended that you only use the DPF() macro to call
//      this function--so you don't have to put #ifdef DEBUG around all
//      of your code.
//
//  Arguments:
//      UINT uDbgLevel:
//
//      LPSTR szFormat:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf
(
    UINT                    uDbgLevel,
    LPSTR                   szFormat,
    ...
)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFormat);
    DbgVPrintF(szFormat, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (BOOL):
//      Returns the previous debugging state.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable
(
    BOOL                    fEnable
)
{
    BOOL                fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel
//
//  Description:
//
//
//  Arguments:
//      UINT uLevel:
//
//  Return (UINT):
//      Returns the previous debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel
(
    UINT                    uLevel
)
{
    UINT                uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgGetLevel
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (UINT):
//      Returns the current debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgGetLevel
(
    void
)
{
    return (__guDbgLevel);
} // DbgGetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (UINT):
//      Returns the debugging level that was set.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize
(
    BOOL                    fEnable
)
{
    UINT                uLevel;

    uLevel = GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, (UINT)-1);
    if ((UINT)-1 == uLevel)
    {
        //
        //  if the debug key is not present, then force debug output to
        //  be disabled. this way running a debug version of a component
        //  on a non-debugging machine will not generate output unless
        //  the debug key exists.
        //
        uLevel  = 0;
        fEnable = FALSE;
    }

    DbgSetLevel(uLevel);
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

#endif // #ifdef DEBUG


DEBUG.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.h
//
//  Description:
//
//
//
//==========================================================================;

#ifndef _INC_DEBUG
#define _INC_DEBUG
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "MIXAPP"    // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//  #pragma message(REMIND("this is a reminder"))
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define DEBUG_QUOTE(x)      #x
#define DEBUG_QQUOTE(y)     DEBUG_QUOTE(y)
#define REMIND(sz)          __FILE__ "(" DEBUG_QQUOTE(__LINE__) ") : " sz

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char ach[] = sz; OutputDebugStr(ach);}
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
#endif

#ifdef __cplusplus
}
#endif
#endif  // _INC_DEBUG


MACTRLS.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mactrls.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include "mixapp.h"

#include "debug.h"

TCHAR       gszLineControlsFormatTitle[]= TEXT("Index\t2!Ctrl Type\t8!Ctrl ID\t6!Flags\t6!Items\t3!Name");
TCHAR       gszLineControlsFormatList[] = TEXT("%u\t%-s\t%.08lXh\t%.08lXh\t%lu\t%-s");

//--------------------------------------------------------------------------;
//
//  BOOL MixAppGetControlTypeName
//
//  Description:
//
//
//  Arguments:
//      LPMIXERCONTROl pmxctrl:
//
//      LPTSTR szControlType:
//
//  Return (BOOL):
//
//  History:
//      09/24/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MixAppGetControlTypeName
(
    LPMIXERCONTROL          pmxctrl,
    LPTSTR                  szControlType
)
{
    switch (pmxctrl->dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_CUSTOM:
            lstrcpy(szControlType, "Custom!");
            break;

        case MIXERCONTROL_CONTROLTYPE_BOOLEANMETER:
            lstrcpy(szControlType, "Boolean Meter");
            break;

        case MIXERCONTROL_CONTROLTYPE_SIGNEDMETER:
            lstrcpy(szControlType, "Signed Meter");
            break;

        case MIXERCONTROL_CONTROLTYPE_PEAKMETER:
            lstrcpy(szControlType, "Peak Meter");
            break;

        case MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER:
            lstrcpy(szControlType, "Unsigned Meter");
            break;

        case MIXERCONTROL_CONTROLTYPE_BOOLEAN:
            lstrcpy(szControlType, "Boolean Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_ONOFF:
            lstrcpy(szControlType, "On/Off Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_MUTE:
            lstrcpy(szControlType, "Mute Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_MONO:
            lstrcpy(szControlType, "Mono Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_LOUDNESS:
            lstrcpy(szControlType, "Loudness Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_STEREOENH:
            lstrcpy(szControlType, "Stereo Enh Switch");
            break;

        case MIXERCONTROL_CONTROLTYPE_BUTTON:
            lstrcpy(szControlType, "Button");
            break;

        case MIXERCONTROL_CONTROLTYPE_DECIBELS:
            lstrcpy(szControlType, "Decibels Number");
            break;

        case MIXERCONTROL_CONTROLTYPE_SIGNED:
            lstrcpy(szControlType, "Signed Number");
            break;

        case MIXERCONTROL_CONTROLTYPE_PERCENT:
            lstrcpy(szControlType, "Percent Number");
            break;

        case MIXERCONTROL_CONTROLTYPE_UNSIGNED:
            lstrcpy(szControlType, "Unsigned Number");
            break;

        case MIXERCONTROL_CONTROLTYPE_SLIDER:
            lstrcpy(szControlType, "Slider");
            break;

        case MIXERCONTROL_CONTROLTYPE_PAN:
            lstrcpy(szControlType, "Pan Slider");
            break;

        case MIXERCONTROL_CONTROLTYPE_QSOUNDPAN:
            lstrcpy(szControlType, "Q-Sound Pan Slider");
            break;

        case MIXERCONTROL_CONTROLTYPE_FADER:
            lstrcpy(szControlType, "Fader");
            break;

        case MIXERCONTROL_CONTROLTYPE_VOLUME:
            lstrcpy(szControlType, "Volume Fader");
            break;

        case MIXERCONTROL_CONTROLTYPE_BASS:
            lstrcpy(szControlType, "Bass Fader");
            break;

        case MIXERCONTROL_CONTROLTYPE_TREBLE:
            lstrcpy(szControlType, "Treble Fader");
            break;

        case MIXERCONTROL_CONTROLTYPE_EQUALIZER:
            lstrcpy(szControlType, "Equalizer Fader");
            break;

        case MIXERCONTROL_CONTROLTYPE_SINGLESELECT:
            lstrcpy(szControlType, "Single-Sel List");
            break;

        case MIXERCONTROL_CONTROLTYPE_MUX:
            lstrcpy(szControlType, "MUX List");
            break;

        case MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT:
            lstrcpy(szControlType, "Multi-Sel List");
            break;

        case MIXERCONTROL_CONTROLTYPE_MIXER:
            lstrcpy(szControlType, "Mixer List");
            break;

        case MIXERCONTROL_CONTROLTYPE_MICROTIME:
            lstrcpy(szControlType, "Microsecond Time");
            break;

        case MIXERCONTROL_CONTROLTYPE_MILLITIME:
            lstrcpy(szControlType, "Millisecond Time");
            break;

        default:
            DPF(0, "!MixAppGetControlTypeName: invalid control type %.08lXh!", pmxctrl->dwControlType);
            lstrcpy(szControlType, "** NOT VALID **");
            return (FALSE);
    }

    return (TRUE);
} // MixAppGetControlTypeName()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDisplayControl
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      DWORD dwControlID:
//
//  Return (BOOL):
//
//  History:
//      07/21/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppDisplayControl
(
    HWND                    hwnd,
    DWORD                   dwControlID
)
{
    MIXERLINECONTROLS   mxlc;
    MMRESULT            mmr;
    BOOL                f;
    MACONTROLINSTANCE   maci;

    //
    //
    //
    //
    mxlc.cbStruct    = sizeof(mxlc);
////mxlc.dwLineID    = 0;
    mxlc.dwControlID = dwControlID;
////mxlc.cControls   = 1;
    mxlc.cbmxctrl    = sizeof(gmxctrl);
    mxlc.pamxctrl    = &gmxctrl;

    mmr = mixerGetLineControls((HMIXEROBJ)ghmx, &mxlc, MIXER_GETLINECONTROLSF_ONEBYID);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetLineControls(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  dwControlID, ghmx, mmr);
        return (FALSE);
    }

    //
    //
    //
    if (MIXERCONTROL_CONTROLF_DISABLED & gmxctrl.fdwControl)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "This control (ctrlid=%.08lXh) is disabled.",
                  dwControlID);
        return (FALSE);
    }

    //
    //
    //
    gfDisplayingControl = TRUE;
    gdwControlID        = dwControlID;

    //
    //
    //
    maci.hmx        = ghmx;
    maci.pmxl       = &gmxl;
    maci.pmxctrl    = &gmxctrl;

    //
    //
    //
    //
    f = FALSE;
    switch (MIXERCONTROL_CT_CLASS_MASK & gmxctrl.dwControlType)
    {
        case MIXERCONTROL_CT_CLASS_CUSTOM:
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "Control type class CUSTOM not done yet!");
            break;

        case MIXERCONTROL_CT_CLASS_METER:
            f = DialogBoxParam(ghinst,
                               DLG_MIXAPP_CONTROL,
                               hwnd,
                               MixAppDlgProcControlMeter,
                               (LPARAM)(LPVOID)&maci);
            break;

        case MIXERCONTROL_CT_CLASS_SWITCH:
            f = DialogBoxParam(ghinst,
                               DLG_MIXAPP_CONTROL,
                               hwnd,
                               MixAppDlgProcControlSwitch,
                               (LPARAM)(LPVOID)&maci);
            break;

        case MIXERCONTROL_CT_CLASS_NUMBER:
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "Control type class NUMBER not done yet!");
            break;

        case MIXERCONTROL_CT_CLASS_SLIDER:
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "Control type class SLIDER not done yet!");
            break;

        case MIXERCONTROL_CT_CLASS_FADER:
            f = DialogBoxParam(ghinst,
                               DLG_MIXAPP_CONTROL,
                               hwnd,
                               MixAppDlgProcControlFader,
                               (LPARAM)(LPVOID)&maci);
            break;

        case MIXERCONTROL_CT_CLASS_LIST:
            f = DialogBoxParam(ghinst,
                               DLG_MIXAPP_CONTROL,
                               hwnd,
                               MixAppDlgProcControlList,
                               (LPARAM)(LPVOID)&maci);
            break;

        default:
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "Unknown control type class=%.08lXh!",
                      gmxctrl.dwControlType);
            break;
    }

    gfDisplayingControl = FALSE;

    //
    //
    //
    if (!f)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "Do not know how to deal with control type=%.08lXh!",
                  gmxctrl.dwControlType);
    }

    return (f);
} // MixAppDisplayControl()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcControlInfo
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      07/12/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcControlInfo
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    HWND                hedit;
    UINT                u;
    MIXERLINECONTROLS   mxlc;
    MIXERCONTROL        mxctrl;
    MMRESULT            mmr;

    //
    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            hedit = GetDlgItem(hwnd, IDD_CONTROLINFO_EDIT_INFO);
            SetWindowFont(hedit, GetStockFont(SYSTEM_FIXED_FONT), FALSE);

            //
            //
            //
            //
            mxlc.cbStruct       = sizeof(mxlc);
////////////mxlc.dwLineID       = 0;
            mxlc.dwControlID    = lParam;
////////////mxlc.cControls      = 1;
            mxlc.cbmxctrl       = sizeof(mxctrl);
            mxlc.pamxctrl       = &mxctrl;

            mmr = mixerGetLineControls((HMIXEROBJ)ghmx, &mxlc, MIXER_GETLINECONTROLSF_ONEBYID);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                          "mixerGetLineControls(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                          lParam, ghmx, mmr);
            }
            else
            {
                static TCHAR    szDisplayTitle[]  = TEXT("[Control Info]\r\n");

                //
                //
                //
                MEditPrintF(hedit, NULL);
                MEditPrintF(hedit, szDisplayTitle);

                MEditPrintF(hedit, "%25s: %.04Xh", (LPTSTR)"Mixer Handle",
                            ghmx);

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"Line ID",
                            mxlc.dwLineID);

                //
                //
                //
                MEditPrintF(hedit, "%25s: %lu bytes (requested %lu)", (LPTSTR)"Size of Control Info",
                            mxctrl.cbStruct, (DWORD)sizeof(mxctrl));

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"Control ID",
                            mxctrl.dwControlID);

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"Control Type",
                            mxctrl.dwControlType);

                MEditPrintF(hedit, "~%25s: %.08lXh", (LPTSTR)"Control Flags",
                            mxctrl.fdwControl);

                if (MIXERCONTROL_CONTROLF_DISABLED & mxctrl.fdwControl)
                    MEditPrintF(hedit, "~, disabled");

                if (MIXERCONTROL_CONTROLF_MULTIPLE & mxctrl.fdwControl)
                    MEditPrintF(hedit, "~, multiple");

                if (MIXERCONTROL_CONTROLF_UNIFORM & mxctrl.fdwControl)
                    MEditPrintF(hedit, "~, uniform");

                if (~(MIXERCONTROL_CONTROLF_UNIFORM | MIXERCONTROL_CONTROLF_MULTIPLE | MIXERCONTROL_CONTROLF_DISABLED) & mxctrl.fdwControl)
                    MEditPrintF(hedit, "~, *INVALID FLAGS*");

                MEditPrintF(hedit, "");

                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Multiple Items",
                            mxctrl.cMultipleItems);

                MEditPrintF(hedit, "%25s: '%s'", (LPTSTR)"Short Name",
                            (LPTSTR)mxctrl.szShortName);

                MEditPrintF(hedit, "%25s: '%s'", (LPTSTR)"Name",
                            (LPTSTR)mxctrl.szName);

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"dwMinimum",
                            mxctrl.Bounds.dwMinimum);

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"dwMaximum",
                            mxctrl.Bounds.dwMaximum);

                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"cSteps",
                            mxctrl.Metrics.cSteps);
            }

            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == u) || (IDCANCEL == u))
            {
                EndDialog(hwnd, (IDOK == u));
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcControlInfo()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcLineControls
//
//  Description:
//      This dialog procedure is used to display driver capabilities.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcLineControls
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    static PTABBEDLISTBOX   ptlb;
    HWND                hwndStatic;
    MMRESULT            mmr;
    RECT                rc;
    PAINTSTRUCT         ps;
    UINT                uId;
    UINT                uCode;
    int                 n;
    LRESULT             lr;

    UINT                cbmxctrls;
    MIXERLINECONTROLS   mxlc;
    PMIXERCONTROL       pamxctrl;

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            hwndStatic = GetDlgItem(hwnd, IDD_LINECONTROLS_STATIC_POSITION);

            GetWindowRect(hwndStatic, &rc);
            ScreenToClient(hwnd, (LPPOINT)&rc.left);
            ScreenToClient(hwnd, (LPPOINT)&rc.right);

            ShowWindow(hwndStatic, SW_HIDE);

            EnableWindow(GetDlgItem(hwnd, IDD_LINECONTROLS_BTN_INFO), FALSE);
            EnableWindow(GetDlgItem(hwnd, IDD_LINECONTROLS_BTN_SETTINGS), FALSE);

            ptlb = TlbCreate(hwnd, IDD_LINECONTROLS_LIST_CONTROLS, &rc);
            if (NULL == ptlb)
                return (TRUE);

            TlbSetFont(ptlb, GetStockFont(SYSTEM_FONT), FALSE);
            TlbSetTitleAndTabs(ptlb, gszLineControlsFormatTitle, FALSE);

            SetWindowPos(ptlb->hlb, GetDlgItem(hwnd, IDOK),
                            0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

            ListBox_ResetContent(ptlb->hlb);

            //
            //
            //
            //
            gmxl.cbStruct     = sizeof(gmxl);
            gmxl.dwLineID     = lParam;

            mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &gmxl, MIXER_GETLINEINFOF_LINEID);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                        "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                        lParam, ghmx, mmr);
                EndDialog(hwnd, FALSE);
                return (TRUE);
            }

            if (0 == gmxl.cControls)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                            "There are no controls associated with the selected line.");
                EndDialog(hwnd, FALSE);
                return (TRUE);
            }

            cbmxctrls = sizeof(*pamxctrl) * (UINT)gmxl.cControls;

            pamxctrl = (PMIXERCONTROL)LocalAlloc(LPTR, cbmxctrls);
            if (NULL == pamxctrl)
            {
                EndDialog(hwnd, FALSE);
                return (TRUE);
            }

            SetWindowRedraw(ptlb->hlb, FALSE);

            //
            //
            //
            //
            mxlc.cbStruct       = sizeof(mxlc);
            mxlc.dwLineID       = lParam;
////////////mxlc.dwControlID    = 0;
////////////mxlc.dwControlType  = 0;
            mxlc.cControls      = gmxl.cControls;
            mxlc.cbmxctrl       = sizeof(*pamxctrl);
            mxlc.pamxctrl       = pamxctrl;

            mmr = mixerGetLineControls((HMIXEROBJ)ghmx, &mxlc, MIXER_GETLINECONTROLSF_ALL);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                        "mixerGetLineControls(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                        lParam, ghmx, mmr);
            }
            else
            {
                TCHAR       ach[128];
                TCHAR       szControlType[64];
                UINT        u;
                int         nIndex;

                if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
                    MixAppDebugLog(NULL);

                for (u = 0; u < mxlc.cControls; u++)
                {
                    MixAppGetControlTypeName(&pamxctrl[u], szControlType);

                    //
                    //
                    //
                    wsprintf(ach, gszLineControlsFormatList,
                            u,
                            (LPTSTR)szControlType,
                            pamxctrl[u].dwControlID,
                            pamxctrl[u].fdwControl,
                            pamxctrl[u].cMultipleItems,
                            (LPSTR)pamxctrl[u].szName);

                    if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
                    {
                        MixAppDebugLog(ach);
                        MixAppDebugLog(gszCRLF);
                    }

                    nIndex = ListBox_AddString(ptlb->hlb, ach);
                    ListBox_SetItemData(ptlb->hlb, nIndex, pamxctrl[u].dwControlID);
                }

                ListBox_SetCurSel(ptlb->hlb, 0);
                EnableWindow(GetDlgItem(hwnd, IDD_LINECONTROLS_BTN_INFO), TRUE);
                EnableWindow(GetDlgItem(hwnd, IDD_LINECONTROLS_BTN_SETTINGS), TRUE);
            }

            SetWindowRedraw(ptlb->hlb, TRUE);
            LocalFree((HLOCAL)pamxctrl);
            return (TRUE);

        case WM_PAINT:
            if (NULL != ptlb)
            {
                BeginPaint(hwnd, &ps);
                TlbPaint(ptlb, hwnd, ps.hdc);
                EndPaint(hwnd, &ps);
            }
            break;

        case WM_COMMAND:
            uId   = GET_WM_COMMAND_ID(wParam, lParam);
            uCode = GET_WM_COMMAND_CMD(wParam, lParam);
            switch (uId)
            {
                case IDOK:
                case IDCANCEL:
                    if (NULL != ptlb)
                    {
                        //
                        //  hadidk! don't destroy the listbox window, but
                        //  free all other memory for TLB. the listbox
                        //  window will be destroyed when the dialog is
                        //  destroyed.
                        //
                        ptlb->hlb = NULL;

                        TlbDestroy(ptlb);
                        ptlb = NULL;
                    }

                    EndDialog(hwnd, TRUE);
                    break;

                case IDD_LINECONTROLS_BTN_INFO:
                    n  = ListBox_GetCurSel(ptlb->hlb);
                    if (LB_ERR == n)
                        break;

                    lr = ListBox_GetItemData(ptlb->hlb, n);
                    DialogBoxParam(ghinst,
                                   DLG_CONTROLINFO,
                                   hwnd,
                                   MixAppDlgProcControlInfo,
                                   lr);
                    break;

                case IDD_LINECONTROLS_BTN_SETTINGS:
                    n  = ListBox_GetCurSel(ptlb->hlb);
                    if (LB_ERR == n)
                        break;

                    lr = ListBox_GetItemData(ptlb->hlb, n);
                    MixAppDisplayControl(hwnd, lr);
                    break;

                case IDD_LINECONTROLS_LIST_CONTROLS:
                    switch (uCode)
                    {
                        case LBN_SELCHANGE:
                            break;

                        case LBN_DBLCLK:
                            n  = ListBox_GetCurSel(ptlb->hlb);
                            if (LB_ERR == n)
                                break;

                            lr = ListBox_GetItemData(ptlb->hlb, n);
                            if (GetKeyState(VK_CONTROL) < 0)
                            {
                                DialogBoxParam(ghinst,
                                               DLG_CONTROLINFO,
                                               hwnd,
                                               MixAppDlgProcControlInfo,
                                               lr);
                            }
                            else
                            {
                                MixAppDisplayControl(hwnd, lr);
                            }
                            break;
                    }
                    break;
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcLineControls

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT MixAppControlChange
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwControlID:
//
//  Return (LRESULT):
//
//  History:
//      07/21/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL MixAppControlChange
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
)
{
    DPF(1, "MixAppControlChange(hwnd=%Xh, hmx=%Xh, dwControlID=%.08lXh)",
        hwnd, hmx, dwControlID);

    //
    //
    //
    if (gfDisplayingControl &&
        (dwControlID == gdwControlID) &&
        (NULL != ghdlgControl))
    {
        SendMessage(ghdlgControl, MM_MIXM_CONTROL_CHANGE, (WPARAM)hmx, dwControlID);
    }

    //
    //  return zero because we handled the message
    //
    return (0L);
} // MixAppControlChange()


MACUSTOM.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  macustom.c
//
//  Description:
//
//
//  History:
// 9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

//#include <msmixmgr.h>

#include "appport.h"
#include "mixapp.h"

#include "debug.h"


MAFADER.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mafader.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "muldiv32.h"

#include "mixapp.h"

#include "debug.h"

//
//
//
typedef struct tMACONTROLINSTANCE_FADER
{
    LPMACONTROLINSTANCE             pmaci;
    int                             nRange;
    int                             nPageInc;
    MIXERCONTROLDETAILS_UNSIGNED    pmxcd_u[];

} MACONTROLINSTANCE_FADER, *PMACONTROLINSTANCE_FADER;

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlChangeFader
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwControlID:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlChangeFader
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
)
{
    MMRESULT                        mmr;
    HWND                            htxt;
    PMACONTROLINSTANCE_FADER        pmaci_fader;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_UNSIGNED   pmxcd_u;
    int                             nRange;
    int                             nValue;
    UINT                            cChannels;
    UINT                            cMultipleItems;
    UINT                            u;
    UINT                            uIndex;
    UINT                            v;
    MIXERCONTROLDETAILS             mxcd;
    HWND                            hsbFocus;
    HWND                            hsb;

    hsbFocus = GetFocus();
    if (NULL == hsbFocus)
    {
        hsbFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
    }
    else
    {
        uIndex = GetDlgCtrlID(hsbFocus);
        if (uIndex < IDD_MACONTROL_MULTICHANNEL_BASE)
        {
            hsbFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
        }
    }

    //
    //
    //
    pmaci_fader = (PMACONTROLINSTANCE_FADER)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_fader->pmaci;
    pmxl        = pmaci->pmxl;
    pmxctrl     = pmaci->pmxctrl;
    pmxcd_u     = &pmaci_fader->pmxcd_u[0];
    nRange      = pmaci_fader->nRange;

    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_u);
    mxcd.paDetails      = pmxcd_u;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            nValue = (int)MulDivRN(pmxcd_u[uIndex].dwValue, nRange, 0xFFFF);

            //
            //  Windows is stupid and forces an update to scrollbars
            //  (a flickering paint) even when the position does not
            //  change... so don't update if it is already in the
            //  right position.
            //
            hsb = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex);

            if (hsbFocus == hsb)
            {
                htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
                AppSetWindowText(htxt, "mapped=%d, dwValue=%lu",
                                 nValue,
                                 pmxcd_u[uIndex].dwValue);
            }

            if (nRange - nValue == GetScrollPos(hsb, SB_CTL))
                continue;

            //
            //  note that we _invert_ the position since a fader will
            //  increase approaching the top (opposite of scrollbar
            //  logic)
            //
            SetScrollPos(hsb, SB_CTL, nRange - nValue, TRUE);
        }
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_u);
    mxcd.paDetails      = pmxcd_u;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    for (v = 0; v < cMultipleItems; v++)
    {
        uIndex = v;

        nValue = (int)MulDivRN(pmxcd_u[uIndex].dwValue, nRange, 0xFFFF);

        hsb = GetDlgItem(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex);
        if (hsbFocus == hsb)
        {
            htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
            AppSetWindowText(htxt, "mapped=%d, dwValue=%lu",
                             nValue,
                             pmxcd_u[0].dwValue);
        }

        if (nRange - nValue == GetScrollPos(hsb, SB_CTL))
            continue;

        //
        //  note that we _invert_ the position since a fader will
        //  increase approaching the top (opposite of scrollbar
        //  logic)
        //
        SetScrollPos(hsb, SB_CTL, nRange - nValue, TRUE);
    }

    return (TRUE);
} // MixAppControlChangeFader()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlSetFader
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hsb:
//
//      UINT uCode:
//
//      int nPos:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlSetFader
(
    HWND                    hwnd,
    HWND                    hsb,
    UINT                    uCode,
    int                     nPos
)
{
    PMACONTROLINSTANCE_FADER        pmaci_fader;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_UNSIGNED   pmxcd_u;
    MMRESULT                        mmr;
    int                             nRange;
    int                             nPageInc;
    int                             nValue;
    UINT                            cChannels;
    UINT                            uIndex;
    MIXERCONTROLDETAILS             mxcd;

    //
    //
    //
    pmaci_fader = (PMACONTROLINSTANCE_FADER)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_fader->pmaci;
    pmxl        = pmaci->pmxl;
    pmxctrl     = pmaci->pmxctrl;
    pmxcd_u     = &pmaci_fader->pmxcd_u[0];
    nRange      = pmaci_fader->nRange;
    nPageInc    = pmaci_fader->nPageInc;

    uIndex = GetDlgCtrlID(hsb);

    if (uIndex < IDD_MACONTROL_UNIFORM_BASE)
    {
        cChannels = (UINT)pmxl->cChannels;
        if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
            cChannels = 1;

        uIndex -= IDD_MACONTROL_MULTICHANNEL_BASE;
    }
    else
    {
        cChannels = 1;

        uIndex -= IDD_MACONTROL_UNIFORM_BASE;
    }

    nValue = GetScrollPos(hsb, SB_CTL);

    switch (uCode)
    {
        case SB_PAGEDOWN:
            nValue = (int)min(nRange, (LONG)nValue + nPageInc);
            break;

        case SB_LINEDOWN:
            nValue = (int)min(nRange, (LONG)nValue + 1);
            break;

        case SB_PAGEUP:
            nValue -= nPageInc;

            //-- fall through --//

        case SB_LINEUP:
            nValue = (nValue < 1) ? 0 : (nValue - 1);
            break;

        case SB_TOP:
            //
            //  !!! would be a VERY BAD IDEA to go full volume !!!
            //
            nValue = nRange / 2;
            break;

        case SB_BOTTOM:
            nValue = nRange;
            break;

        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
            nValue = nPos;
            break;

        default:
            return (FALSE);
    }

    //
    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_u);
    mxcd.paDetails      = pmxcd_u;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    pmxcd_u[uIndex].dwValue  = (DWORD)MulDivRN((nRange - nValue), 0xFFFF, nRange);

    mmr = mixerSetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerSetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

// * commented out to stop sliders getting out of synch    *
// * between different instances of the app. AGuy 22.12.93 *
//  SetScrollPos(hsb, SB_CTL, nValue, TRUE);

#if 0
    if (0 == uIndex)
    {
        htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
        AppSetWindowText(htxt, "mapped=%d, dwValue=%lu",
                         nValue,
                         pmxcd_u[0].dwValue);
    }
#endif

    return (TRUE);
} // MixAppControlSetFader()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppLineChangeFader
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwLineID:
//
//  Return (BOOL):
//
//  History:
//      09/24/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppLineChangeFader
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
)
{
    MMRESULT                    mmr;
    PMACONTROLINSTANCE_FADER    pmaci_fader;
    LPMACONTROLINSTANCE         pmaci;
    LPMIXERLINE                 pmxl;
    MIXERLINE                   mxl;
    BOOL                        fSource;
    BOOL                        fActive;
    BOOL                        fDisconnected;
    HWND                        htxt;

    //
    //
    //
    pmaci_fader = (PMACONTROLINSTANCE_FADER)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_fader->pmaci;
    pmxl        = pmaci->pmxl;

    if (pmxl->dwLineID != dwLineID)
    {
        DPF(0, "!MixAppLineChangeFader: why am i getting notifications for some other line??");

        //
        //  this would be a bug in this app... keep from bombing though
        //  until i fix it.
        //
        dwLineID = pmxl->dwLineID;
    }

    //
    //  get the current state of the line that changed.. this is actually
    //  silly that you have to do this just to determine if the line went
    //  [in]active. sigh..
    //
    mxl.cbStruct      = sizeof(mxl);
    mxl.dwLineID      = dwLineID;

    mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_LINEID);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  dwLineID, ghmx, mmr);

        return (FALSE);
    }

    fSource       = (0 != (MIXERLINE_LINEF_SOURCE & mxl.fdwLine));
    fActive       = (0 != (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine));
    fDisconnected = (0 != (MIXERLINE_LINEF_DISCONNECTED & mxl.fdwLine));

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_LINEINFO);
    AppSetWindowText(htxt, "(%s), '%s', %s, %s",
                     fSource ? (LPSTR)"src" : (LPSTR)"DST",
                     (LPSTR)mxl.szShortName,
                     fActive ? (LPSTR)"ACTIVE" : (LPSTR)"inactive",
                     fDisconnected ? (LPSTR)"DISCONNECTED" : (LPSTR)"connected");

    return (fActive);
} // MixAppLineChangeFader()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppInitDialogFader
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hwndFocus:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppInitDialogFader
(
    HWND                    hwnd,
    HWND                    hwndFocus,
    LPARAM                  lParam
)
{
    #define FSB_DEF_STYLE   (WS_VISIBLE | WS_CHILD | SBS_VERT | WS_TABSTOP)

    static TCHAR        szScrollBar[] = TEXT("scrollbar");
    static TCHAR        szTitle[]     = TEXT("Fader Class: '%s'");

    TCHAR                       szControlType[64];
    LPMACONTROLINSTANCE         pmaci;
    PMACONTROLINSTANCE_FADER    pmaci_fader;

    LPMIXERLINE                 pmxl;
    LPMIXERCONTROL              pmxctrl;

    HWND                        htxt;
    UINT                        u;
    UINT                        v;
    HWND                        hsb;
    RECT                        rcM;
    RECT                        rcU;
    int                         cxvsb;
    UINT                        cb;
    UINT                        cChannels;
    UINT                        cMultipleItems;
    UINT                        uIndex;
    int                         nRange;
    int                         nPageInc;

    //
    //
    //
    pmaci = (LPMACONTROLINSTANCE)lParam;
    if (NULL == pmaci)
    {
        DPF(0, "!MixAppInitDialogFader: pmaci passed in lParam is NULL!?!");
        return (FALSE);
    }

    pmxl    = pmaci->pmxl;
    pmxctrl = pmaci->pmxctrl;

    if (!MixAppGetControlTypeName(pmxctrl, szControlType))
    {
        return (FALSE);
    }

    //
    //
    //
    switch (pmxctrl->dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_FADER:
        case MIXERCONTROL_CONTROLTYPE_VOLUME:
        case MIXERCONTROL_CONTROLTYPE_BASS:
        case MIXERCONTROL_CONTROLTYPE_TREBLE:
        case MIXERCONTROL_CONTROLTYPE_EQUALIZER:
            break;

        default:
            DPF(0, "!MixAppInitDialogFader: pmxctrl->dwControlType=%.08lXh not valid fader!", pmxctrl->dwControlType);
            return (FALSE);
    }

    //
    //
    //
    AppSetWindowText(hwnd, szTitle, (LPTSTR)szControlType);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_SHORT_NAME, pmxctrl->szShortName);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_LONG_NAME,  pmxctrl->szName);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_BOUNDS);
    AppSetWindowText(htxt, "dwMinimum=%lu, dwMaximum=%lu",
                     pmxctrl->Bounds.dwMinimum,
                     pmxctrl->Bounds.dwMaximum);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_METRICS);
    AppSetWindowText(htxt, "cSteps=%lu",
                     pmxctrl->Metrics.cSteps);

    //
    //
    //
    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    cb  = sizeof(*pmaci_fader);
    cb += cChannels * cMultipleItems * sizeof(pmaci_fader->pmxcd_u[0]);
    pmaci_fader = (PMACONTROLINSTANCE_FADER)LocalAlloc(LPTR, cb);
    if (NULL == pmaci_fader)
    {
        DPF(0, "!MixAppInitDialogFader: failed trying to alloc %u bytes for control instance!", cb);
        return (FALSE);
    }

    //
    //
    //
    SetWindowLong(hwnd, DWL_USER, (LPARAM)(UINT)pmaci_fader);

    nRange   = (int)min(32767, pmxctrl->Metrics.cSteps - 1);
    nPageInc = nRange / 10;
    if (0 == nPageInc)
        nPageInc = 1;

    pmaci_fader->pmaci    = pmaci;
    pmaci_fader->nRange   = nRange;
    pmaci_fader->nPageInc = nPageInc;

    //
    //
    //
    //
    cxvsb = GetSystemMetrics(SM_CXVSCROLL);

    hsb = GetDlgItem(hwnd, IDD_MACONTROL_GRP_MULTICHANNEL);
    GetWindowRect(hsb, &rcM);

    InflateRect(&rcM, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcM.left);
    ScreenToClient(hwnd, (LPPOINT)&rcM.right);

    rcM.right = rcM.left + cxvsb;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            hsb = CreateWindow(szScrollBar, gszNull, FSB_DEF_STYLE,
                               rcM.left, rcM.top,
                               rcM.right - rcM.left,
                               rcM.bottom - rcM.top,
                               hwnd, (HMENU)(IDD_MACONTROL_MULTICHANNEL_BASE + uIndex),
                               ghinst, NULL);

            SetScrollRange(hsb, SB_CTL, 0, nRange, FALSE);

            rcM.left  += cxvsb + 4;
            rcM.right += cxvsb + 4;
        }

        //
        //  add more separation between channels
        //
        rcM.left  += cxvsb;
        rcM.right += cxvsb;
    }

    //
    //
    //
    //
    hsb = GetDlgItem(hwnd, IDD_MACONTROL_GRP_UNIFORM);
    GetWindowRect(hsb, &rcU);

    InflateRect(&rcU, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcU.left);
    ScreenToClient(hwnd, (LPPOINT)&rcU.right);

    rcU.right = rcU.left + cxvsb;

    for (v = 0; v < cMultipleItems; v++)
    {
        hsb = CreateWindow(szScrollBar, gszNull, FSB_DEF_STYLE,
                            rcU.left, rcU.top,
                            rcU.right - rcU.left,
                            rcU.bottom - rcU.top,
                            hwnd, (HMENU)(IDD_MACONTROL_UNIFORM_BASE + v),
                            ghinst, NULL);

        SetScrollRange(hsb, SB_CTL, 0, nRange, FALSE);

        rcU.left  += cxvsb + 4;
        rcU.right += cxvsb + 4;
    }

    //
    //
    //
    SendMessage(hwnd,
                MM_MIXM_LINE_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxl->dwLineID);

    SendMessage(hwnd,
                MM_MIXM_CONTROL_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxctrl->dwControlID);

    return (TRUE);
} // MixAppInitDialogFader()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcControlFader
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      07/22/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcControlFader
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LRESULT             lr;
    HLOCAL              hl;
    UINT                uId;

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            DPF(0, "WM_INITDIALOG");
            lr = HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, MixAppInitDialogFader);
            if (FALSE == lr)
            {
                EndDialog(hwnd, FALSE);
            }
            else
            {
                ghdlgControl = hwnd;
            }
            return (TRUE);

        case MM_MIXM_LINE_CHANGE:
            DPF(0, "MM_MIXM_LINE_CHANGE");
            MixAppLineChangeFader(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case MM_MIXM_CONTROL_CHANGE:
            DPF(0, "MM_MIXM_CONTROL_CHANGE");
            MixAppControlChangeFader(hwnd, (HMIXER)wParam, lParam);
            DPF(0, "done...");
            return (TRUE);

        case WM_VSCROLL:
            DPF(0,"WM_SCROLL");
            HANDLE_WM_VSCROLL(hwnd, wParam, lParam, MixAppControlSetFader);
            return (TRUE);

        case WM_COMMAND:
            DPF(0, "WM_COMMAND");
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDCANCEL:
                case IDOK:
                    hl = (HLOCAL)(UINT)GetWindowLong(hwnd, DWL_USER);
                    LocalFree(hl);

                    EndDialog(hwnd, TRUE);
                    ghdlgControl = NULL;
                    break;
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcControlFader()


MAINIT.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mainit.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>

#include "mixapp.h"

#include "debug.h"

//
//
//
TCHAR       gszAppProfile[]     = TEXT("mixapp.ini");

TCHAR       gszSecConfig[]      = TEXT("Configuration");

TCHAR       gszKeyOptions[]     = TEXT("Options");
TCHAR       gszFormatOptions[]  = TEXT("%u");

TCHAR       gszKeyWindow[]      = TEXT("Window");
TCHAR       gszKeyFont[]        = TEXT("Font");

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileWriteBytes
//
//  Description:
//      This function writes a raw structure of bytes to the application's
//      ini section that can later be retrieved using AppProfileReadBytes.
//      This gives an application the ability to write any structure to
//      the ini file and restore it later--very useful.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PTSTR pszSection: Pointer to section for the stored data.
//
//      PTSTR pszKey: Pointer to key name for the stored data.
//
//      LPBYTE pbStruct: Pointer to the data to be saved.
//
//      UINT cbStruct: Count in bytes of the data to store.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if it fails.
//
//  History:
//       3/10/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileWriteBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
)
{
    static TCHAR achNibbleToChar[] =
    {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
    };
    #define     NIBBLE2CHAR(x)      (achNibbleToChar[x])

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;

    //
    //  if pbStruct is NULL, then erase the key from the ini file, otherwise
    //  format the raw bytes into a hex string and write that out...
    //
    fAllocated = FALSE;
    psz        = NULL;
    if (NULL != pbStruct)
    {
        //
        //  check if the quick buffer can be used for formatting the output
        //  text--if it cannot, then alloc space for it. note that space
        //  must be available for an ending checksum byte (2 bytes for high
        //  and low nibble) as well as a null terminator.
        //
        psz     = (LPTSTR)ach;
        cchTemp = cbStruct * 2 + 3;
        if (cchTemp > SIZEOF(ach))
        {
            psz = (LPTSTR)GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
            if (NULL == psz)
                return (FALSE);

            fAllocated = TRUE;
        }

        //
        //  step through all bytes in the structure and convert it to
        //  a string of hex numbers...
        //
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            //
            //  grab the next byte and add into checksum...
            //
            bChecksum = (BYTE) (bChecksum + (b = *pbStruct));

            *pch++ = NIBBLE2CHAR((b >> (BYTE)4) & (BYTE)0x0F);
            *pch++ = NIBBLE2CHAR(b & (BYTE)0x0F);
        }

        //
        //  add the checksum byte to the end and null terminate the hex
        //  dumped string...
        //
        *pch++ = NIBBLE2CHAR((bChecksum >> (BYTE)4) & (BYTE)0x0F);
        *pch++ = NIBBLE2CHAR(bChecksum & (BYTE)0x0F);
        *pch   = '\0';
    }

    //
    //  write the string of hex bytes out to the ini file...
    //
    fReturn = WritePrivateProfileString(pszSection, pszKey, psz, gszAppProfile);

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileWriteBytes

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileReadBytes
//
//  Description:
//      This function reads a previously stored structure of bytes from
//      the application's ini file. This data must have been written with
//      the AppProfileWriteBytes function--it is checksumed to keep bad
//      data from blowing up the application.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PTSTR pszSection: Pointer to section that contains the data.
//
//      PTSTR pszKey: Pointer to key that contains the data.
//
//      LPBYTE pbStruct: Pointer to buffer to receive the data.
//
//      UINT cbStruct: Number of bytes expected.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if the function fails (bad checksum, missing key, etc).
//
//  History:
//       3/10/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileReadBytes
(
    PTSTR                   pszSection,
    PTSTR                   pszKey,
    LPBYTE                  pbStruct,
    UINT                    cbStruct
)
{
    //
    //  note that the following works for both upper and lower case, and
    //  will return valid values for garbage chars
    //
    #define CHAR2NIBBLE(ch) (BYTE)( ((ch) >= '0' && (ch) <= '9') ?  \
                                (BYTE)((ch) - '0') :                \
                                ((BYTE)(10 + (ch) - 'A') & (BYTE)0x0F) )

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    UINT        u;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;
    TCHAR       ch;

    //
    //  add one the the number of bytes needed to accomodate the checksum
    //  byte placed at the end by AppProfileWriteBytes...
    //
    cbStruct++;

    //
    //  check if the quick buffer can be used for retrieving the input
    //  text--if it cannot, then alloc space for it. note that there must
    //  be space available for the null terminator (the +1 below).
    //
    fAllocated = FALSE;
    psz        = (LPTSTR)ach;
    cchTemp    = cbStruct * 2 + 1;
    if (cchTemp > SIZEOF(ach))
    {
        psz = (LPTSTR)GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
        if (NULL == psz)
            return (FALSE);

        fAllocated = TRUE;
    }

    //
    //  read the hex string... if it is not the correct length, then assume
    //  error and return.
    //
    fReturn = FALSE;
    u = (UINT)GetPrivateProfileString(pszSection, pszKey, gszNull,
                                      psz, cchTemp, gszAppProfile);
    if ((cbStruct * 2) == u)
    {
        bChecksum = 0;
        for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
        {
            ch = *pch++;
            b  = (BYTE) (CHAR2NIBBLE(ch) << (BYTE)4);
            ch = *pch++;
            b |= CHAR2NIBBLE(ch);

            //
            //  if this is not the final byte (the checksum byte), then
            //  store it and accumulate checksum..
            //
            if (cbStruct != 1)
                bChecksum = (BYTE) (bChecksum + (*pbStruct = b));
        }

        //
        //  check the last byte read against the checksum that we calculated
        //  if they are not equal then return error...
        //
        fReturn = (bChecksum == b);
    }

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
        GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileReadBytes

//--------------------------------------------------------------------------;
//
//  DWORD AppGetWindowsVersion
//
//  Description:
//      This function returns the version of Windows that the application
//      is running on plus some platform information.
//
//  Arguments:
//      PTSTR pach: Options pointer to buffer to receive text string of
//      the Windows version and platform.
//
//  Return (LRESULT):
//      The return value will be the version and platform information of
//      the current operating system in the following format:
//
//      0xPPPPMMRR where:
//
//      MM      :   major version of Windows
//      RR      :   minor version (revision) of Windows
//      PPPP    :   the platform the application is running on which
//                  the HIWORD() is RESERVED except for the high bit:
//                      high bit is 0 = Windows NT
//                      high bit is 1 = Win32s/Windows 3.1
//
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppGetWindowsVersion
(
    PTSTR                   pszEnvironment,
    PTSTR                   pszPlatform
)
{

    BYTE    bVerWinMajor;
    BYTE    bVerWinMinor;
    UINT    uVerEnv;
    DWORD   dw;
    LRESULT lr;

    dw = GetVersion();

    //
    //  massage the version information into something intelligent
    //
    //
    bVerWinMajor = LOBYTE(LOWORD(dw));
    bVerWinMinor = HIBYTE(LOWORD(dw));
    uVerEnv      = HIWORD(dw);
    lr = MAKELPARAM(((UINT)bVerWinMajor << 8) | bVerWinMinor, uVerEnv);

    //
    //  if caller wants the environment string version...
    //
    if (NULL != pszEnvironment)
    {
        static TCHAR    szFormatVersion[]   = TEXT("Win32 Version %u.%.2u");

        wsprintf(pszEnvironment, szFormatVersion,
                 bVerWinMajor, bVerWinMinor);
    }

    //
    //  if caller wants the platform string version...
    //
    if (NULL != pszPlatform)
    {
        static TCHAR    szFormatPlatform[]  = TEXT("%s%u, %u Processor(s)");
        static TCHAR    szProcessorIntel[]  = TEXT("Intel ");
        static TCHAR    szProcessorMIPS[]   = TEXT("MIPS R");
        static TCHAR    szProcessorAlpha[]  = TEXT("DEC Alpha ");
        static TCHAR    szProcessorDunno[]  = TEXT("Dunno zYz");

        SYSTEM_INFO sysinfo;
        PTSTR       pszProcessor;

        //
        //  this is absolutely silly. one would think that the dwOemId member
        //  would provide something useful like the processor class... but
        //  no, it doesn't--it is always 0.
        //
        GetSystemInfo(&sysinfo);
        switch (sysinfo.dwProcessorType)
        {
            case PROCESSOR_INTEL_386:
            case PROCESSOR_INTEL_486:
                pszProcessor = szProcessorIntel;
                break;

            case PROCESSOR_MIPS_R4000:
                pszProcessor = szProcessorMIPS;
                break;

            case PROCESSOR_ALPHA_21064:
                pszProcessor = szProcessorAlpha;
                break;

            default:
                pszProcessor = szProcessorDunno;
                break;
        }

        //
        //
        //
        wsprintf(pszPlatform, szFormatPlatform, (LPSTR)pszProcessor,
                 sysinfo.dwProcessorType, sysinfo.dwNumberOfProcessors);
    }

    //
    //  return the result
    //
    return (lr);
} // AppGetWindowsVersion()

//--------------------------------------------------------------------------;
//
//  HFONT AppChooseFont
//
//  Description:
//      This function is a wrapper for the ChooseFont() common dialog.
//      The purpose of this function is to let the user choose a font that
//      looks good to them--regardless of how stupid it really looks.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      HFONT hfont: Handle to current font (default for chooser dialog).
//
//      PLOGFONT plf: Pointer to optional LOGFONT structure to receive a
//      copy of the LOGFONT information for the newly chosen font.
//
//  Return (HFONT):
//      The return value is the newly chosen font. If no new font was chosen
//      then the return value is NULL.
//
//--------------------------------------------------------------------------;

HFONT FNGLOBAL AppChooseFont
(
    HWND                    hwnd,
    HFONT                   hfont,
    PLOGFONT                plf
)
{
    LOGFONT             lf;
    CHOOSEFONT          cf;
    BOOL                f;
    HFONT               hfontNew;

    //
    //  get the font info for the current font...
    //
    GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);

    //
    //  fill in the choosefont structure
    //
    cf.lStructSize  = sizeof(CHOOSEFONT);
    cf.hwndOwner    = hwnd;
    cf.hDC          = NULL;
    cf.Flags        = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
    cf.lCustData    = 0;
    cf.lpfnHook     = NULL;
    cf.hInstance    = NULL;
    cf.nFontType    = SCREEN_FONTTYPE;
    cf.lpLogFont    = (LPLOGFONT)&lf;

    //
    //  splash a dialog into the user's face..
    //
    hfontNew = NULL;
    f = ChooseFont(&cf);
    if (f)
    {
        //
        //  create the new font..
        //
        hfontNew = CreateFontIndirect(&lf);
        if (NULL == hfontNew)
            return (NULL);

        //
        //  copy the logfont structure if caller wants it
        //
        if (NULL != plf)
            *plf = lf;
    }

    //
    //  return the new font (if one was chosen)
    //
    return (hfontNew);
} // AppChooseFont()

//==========================================================================;
//
//  Misc rarely used application dialogs and stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AboutDlgProc
//
//  Description:
//      This dialog procedure is used for the ubiquitous about box.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK AboutDlgProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    HWND                hwndT;
    PTSTR               pach;
    UINT                u;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            //
            //  display some OS version information
            //
            //
            pach = (PTSTR)LocalAlloc(LPTR, APP_MAX_STRING_RC_BYTES);
            if (NULL == pach)
                return (TRUE);

            AppGetWindowsVersion(pach, NULL);
            hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_OS);
            SetWindowText(hwndT, pach);

            AppGetWindowsVersion(NULL, pach);
            hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_PLATFORM);
            SetWindowText(hwndT, pach);
            LocalFree((HLOCAL)pach);
            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == u) || (IDCANCEL == u))
            {
                EndDialog(hwnd, (IDOK == u));
            }
            break;
    }

    return (FALSE);
} // AboutDlgProc()

//==========================================================================;
//
//  Initialization and exit code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppChooseFont
//
//  Description:
//      This function lets the user choose a new font for the script window.
//      After a new font is chosen, the font structure is stored to the
//      .ini file so it can be restored on the next run of this application.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      The return value is TRUE if a new font was chosen. It is FALSE if
//      the user canceled the operation.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MixAppChooseFont
(
    HWND                    hwnd
)
{
    LOGFONT             lf;
    HWND                hlb;
    HFONT               hfont;
    HFONT               hfontNew;

    //
    //  get the current font and pass it to the choose font dialog
    //
    hlb   = GetDlgItem(hwnd, IDD_APP_LIST_LINES);
    hfont = GetWindowFont(hlb);

    hfontNew = AppChooseFont(hwnd, hfont, &lf);
    if (NULL == hfontNew)
        return (FALSE);

    //
    //  select the new font into the script window and delete the old one
    //
    TlbSetFont(gptlbLines, hfontNew, TRUE);
    DeleteFont(hfont);

    //
    //  save the complete description of the chosen font so there can be
    //  no strangness in the font mapping next run. this is overkill, but
    //  it works...
    //
    AppProfileWriteBytes(gszSecConfig, gszKeyFont, (LPBYTE)&lf, sizeof(lf));

    return (TRUE);
} // MixAppChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppSettingsRestore
//
//  Description:
//      This function restores state information for the application. This
//      function is called just after the main window is created (it has
//      not been ShowWindow()'d). This function will generate the call
//      to ShowWindow before returning.
//
//  Arguments:
//      HWND hwnd: Handle to main window that has just been created but
//      not shown.
//
//      int nCmdShow: The state that the application window should show as.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppSettingsRestore
(
    HWND                    hwnd,
    int                     nCmdShow
)
{
    WINDOWPLACEMENT     wp;
    HFONT               hfont;
    LOGFONT             lf;
    RECT                rc;
    RECT                rcWindow;
    POINT               pt;
    int                 n;
    BOOL                f;

    //
    //  restore the user's preferred font.
    //
    hfont = NULL;
    f = AppProfileReadBytes(gszSecConfig, gszKeyFont, (LPBYTE)&lf, sizeof(lf));
    if (f)
    {
        hfont = CreateFontIndirect(&lf);
    }

    if (NULL == hfont)
    {
        hfont = GetStockFont(SYSTEM_FONT);
    }

    TlbSetFont(gptlbLines, hfont, TRUE);

    //
    //  grab the stored window position and size from the .ini file...
    //  there must be four arguments stored or the entry is considered
    //  invalid.
    //
    f = AppProfileReadBytes(gszSecConfig, gszKeyWindow,
                            (LPBYTE)&rcWindow, sizeof(rcWindow));
    if (f)
    {
        //
        //  to make sure the user can always get at the window, check to
        //  see if the midpoint of the caption is visible--if it is not,
        //  then default to the default position used when creating the
        //  window.
        //
        n = (rcWindow.right - rcWindow.left) / 2;
        pt.x = (n + rcWindow.left);

        n = GetSystemMetrics(SM_CYCAPTION) / 2 + GetSystemMetrics(SM_CXFRAME);
        pt.y = (n + rcWindow.top);

        GetWindowRect(GetDesktopWindow(), &rc);
        if (PtInRect(&rc, pt))
        {
            //
            //  fill out the window placement structure--default the
            //  maximized and minimized states to default placement by
            //  getting its current placement.
            //
            wp.length = sizeof(wp);
            GetWindowPlacement(hwnd, &wp);

            wp.showCmd          = nCmdShow;
            wp.rcNormalPosition = rcWindow;

            SetWindowPlacement(hwnd, &wp);
            return (TRUE);
        }
    }

    //
    //  show defaulted and succeed
    //
    ShowWindow(hwnd, nCmdShow);
    return (TRUE);
} // MixAppSettingsRestore()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppSettingsSave
//
//  Description:
//      This function saves the current state information for the application.
//      It is called just before the main window is closed (destroyed); or
//      as Windows is exiting (query end session).
//
//      Note that this function should not destroy any resources--it can
//      be called at any time to save a snapshot of the application state.
//
//  Arguments:
//      HWND hwnd: Handle to main window that will be destroyed shortly.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppSettingsSave
(
    HWND                    hwnd
)
{
    WINDOWPLACEMENT     wp;
    PRECT               prc;
    BOOL                f;

    //
    //  save the current window placement--only store the size and location
    //  of the restored window. maximized and minimized states should
    //  remain defaulted on the next invocation of this application.
    //
    wp.length = sizeof(wp);
    f = GetWindowPlacement(hwnd, &wp);
    if (f)
    {
        prc = &wp.rcNormalPosition;

        DPF(1, "WindowPlacement: show=%d, minX=%d, minY=%d, maxX=%d, maxY=%d",
             wp.showCmd, wp.ptMinPosition.x, wp.ptMinPosition.y,
             wp.ptMaxPosition.x, wp.ptMaxPosition.y);

        DPF(1, "                 normX=%d, normY=%d, normW=%d, normH=%d",
             prc->left, prc->top, prc->right, prc->bottom);

        //
        //  save the _bounding rectangle_ of the restored window state...
        //
        AppProfileWriteBytes(gszSecConfig, gszKeyWindow, (LPBYTE)prc, sizeof(*prc));
    }

    //
    //  succeed
    //
    return (TRUE);
} // MixAppSettingsSave()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCreate
//
//  Description:
//      This function is called to handle the WM_CREATE message for the
//      application's window. The application should finish the creation
//      of the window (create controls, allocate resources, etc). The
//      window has not been displayed (CreateWindow[Ex] has not returned).
//
//  Arguments:
//      HWND hwnd: Handle to the window that is in the process of being
//      created.
//
//      LPCREATESTRUCT pcs: Pointer to a CREATESTRUCT that contains info
//      about the window being created.
//
//  Return (LRESULT):
//      The return value should be nonzero if the application wishes to
//      let the window finish being created. A return of zero tells
//      CreateWindow[Ex] to fail the creation of the window.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppCreate
(
    HWND                    hwnd,
    LPCREATESTRUCT          pcs
)
{
    extern TCHAR       gszLineFormatTitle[];

    DPF(1, "AppCreate(hwnd=%Xh, cs.x=%d, cs.y=%d, cs.cx=%d, cs.cy=%d)",
            hwnd, pcs->x, pcs->y, pcs->cx, pcs->cy);

    //
    //  create the driver selection listbox
    //
    gptlbLines = TlbCreate(hwnd, IDD_APP_LIST_LINES, NULL);
    if (NULL == gptlbLines)
        return (0L);

    TlbSetTitleAndTabs(gptlbLines, gszLineFormatTitle, FALSE);

    //
    //  default to device id zero... a real app would allow configuration
    //  and set to previous selection. but this is not a real app.
    //
    ghmx = MixAppNewDevice(hwnd, NULL, 0);
    if (NULL == ghmx)
    {
        //
        //  must be having a very bad day..
        //
        return (0L);
    }

    //
    //
    //
    MixAppRefreshLineList(hwnd, gptlbLines);

    //
    //  return nonzero to succeed the creation of the window
    //
    return (1L);
} // AppCreate()

//--------------------------------------------------------------------------;
//
//  LRESULT AppQueryEndSession
//
//  Description:
//      This function handles the WM_QUERYENDSESSION. This message is sent
//      by USER when ExitWindows has been called to end the Windows session.
//      This function can stop Windows from exiting if it is not convenient
//      for Windows to end.
//
//      Giving the user the option to save modified data before continueing
//      with the shutdown of Windows is a good idea.
//
//      Telling Windows to continue with the exit procedure does not
//      necessarily mean Windows will exit. All applications are queried
//      for shutdown approval. When the actual decision is made on whether
//      Windows will exit, WM_ENDSESSION will be sent with the result.
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//  Return (LRESULT):
//      Returns zero to STOP Windows from exiting. Returns non-zero to
//      allows windows to shut down.
//
//  History:
//       2/ 9/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND                    hwnd
)
{
    DPF(1, "AppQueryEndSession(hwnd=%Xh)", hwnd);

    //
    //  tell Windows to proceed with the shutdown process!
    //
    return (1L);
} // AppQueryEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppEndSession
//
//  Description:
//      This function is called to handle the WM_ENDSESSION message. This

//      message is generated after the application answers the
//      WM_QUERYENDSESSION message. The purpose of the WM_ENDSESSION
//      message is to tell the application if Windows will be exiting
//      (TRUE  == fEndSession) or the end session was canceled by an
//      application (FALSE == fEndSession).
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//      BOOL fEndSession: TRUE if Windows is exiting. FALSE if the end
//      session was canceled.
//
//  Return (LRESULT):
//      Returns zero if the message is processed. Note that an application
//      cannot halt the termination of Windows from this message--the
//      WM_QUERYENDSESSION is the only message that allows that behaviour.
//      If fEndSession is TRUE, Windows *WILL* exit--whether you like it
//      or not.
//
//  History:
//       2/ 9/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppEndSession
(
    HWND                    hwnd,
    BOOL                    fEndSession
)
{
    DPF(1, "AppEndSession(hwnd=%Xh, fEndSession=%d)", hwnd, fEndSession);

    //
    //  we processed the message, return zero..
    //
    return (0L);
} // AppEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppClose
//
//  Description:
//      This function handles the WM_CLOSE message for the application.
//      If the application should close, DestroyWindow() must be called
//      by this function. Otherwise the application will not close.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_CLOSE message.
//
//  Return (LRESULT):
//      There return value is zero. The DestroyWindow function will have
//      been called if the application should actually close.
//
//  History:
//       2/ 6/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppClose
(
    HWND                    hwnd
)
{
    HWND                hlb;
    HFONT               hfont;
    MMRESULT            mmr;

    DPF(1, "AppClose(hwnd=%Xh)", hwnd);

    //
    //  save any settings that should be saved on app termination...
    //
    MixAppSettingsSave(hwnd);

    //
    //  if the Shift key is held down during the close message, then just
    //  save the current state but don't destroy the window... this is
    //  useful if the user does not want to exit the app and rerun it
    //  to make sure the state is saved--just before the user does something
    //  that may crash Windows or something..
    //
    if (GetKeyState(VK_SHIFT) < 0)
    {
        return (0L);
    }

    //
    //  destroy the font we are using... before deleting the font, select
    //  the system font back into the script window so the font won't
    //  be 'in use' anymore.
    //
    hlb = GetDlgItem(hwnd, IDD_APP_LIST_LINES);

    hfont = GetWindowFont(hlb);
    SetWindowFont(hlb, NULL, FALSE);
    DeleteFont(hfont);

    //
    //
    //
    if (NULL != ghmx)
    {
        mmr = mixerClose(ghmx);
        if (MMSYSERR_NOERROR != mmr)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "mixerClose() failed on hmx=%.04Xh, mmr=%u!",
                      ghmx, mmr);
        }

        ghmx = NULL;
    }

    gptlbLines->hlb = NULL;
    TlbDestroy(gptlbLines);
    gptlbLines = NULL;

    //
    //  make the window close and terminate the application
    //
    DestroyWindow(hwnd);

    return (0L);
} // AppClose()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppInit
//
//  Description:
//      This function is called to initialize a new instance of the
//      application. We want to parse our command line, create our window,
//      allocate resources, etc.
//
//      The arguments passed to this function are exactly the same as
//      those passed to WinMain.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPTSTR pszCmdLine: Points to null-terminated unparsed command line.
//      If the application is compiled for Unicode, then this argument is
//      ignored.
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (HWND):
//      Returns the newly created handle to the applications main window.
//      This handle is NULL if something went wrong and tells the application
//      to exit immediately.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

HWND FNGLOBAL AppInit
(
    HINSTANCE               hinst,
    HINSTANCE               hinstPrev,
    LPTSTR                  pszCmdLine,
    int                     nCmdShow
)
{
    LRESULT CALLBACK AppWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    HWND                hwnd;
    WNDCLASS            wc;
    UINT                cMixerDevs;

    DPF(1, "AppInit(hinst=%Xh, hinstPrev=%Xh, pszCmdLine='%s', nCmdShow=%d)",
            hinst, hinstPrev, pszCmdLine, nCmdShow);

    LoadString(hinst, IDS_APP_NAME, gszAppName, SIZEOF(gszAppName));

    //
    //  WARNING DANGER WARNING DANGER WARNING DANGER WARNING DANGER WARNING
    //
    //      BEFORE calling any other mixer API's, we must call the
    //      mixerGetNumDevs() function to let the mixer thunk library
    //      dynamically link to all the mixer API's!
    //
    //  WARNING DANGER WARNING DANGER WARNING DANGER WARNING DANGER WARNING
    //
    cMixerDevs = mixerGetNumDevs();
    if (0 == cMixerDevs)
    {
        AppMsgBox(NULL, MB_OK | MB_ICONEXCLAMATION,
                  "There are no mixer devices installed. This application is useless.");
        return (NULL);
    }

    //
    //  determine whether a new window class needs to be registered for
    //  this application. for Win 16, this only needs to be done for the
    //  first instance of the application created. for Win 32, this must
    //  be done for EVERY instance of the application.
    //
    if (NULL == hinstPrev)
    {
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = (WNDPROC)AppWndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = hinst;
        wc.hIcon         = LoadIcon(hinst, ICON_APP);
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = MENU_APP;
        wc.lpszClassName = gszAppName;

        if (!RegisterClass(&wc))
            return (NULL);
    }

    //
    //  create the application's main window
    //
    //  style bits available:
    //      WS_EX_ACCEPTFILES   :  will receive WM_DROPFILES messages
    //      WS_EX_DLGMODALFRAME :  creates window with double border
    //      WS_EX_NOPARENTNOTIFY:  won't receive WM_PARENTNOTIFY messages
    //      WS_EX_TOPMOST       :  puts window in topmost space
    //      WS_EX_TRANSPARENT   :  a very bizarre style indeed (Win 16 only)
    //
    hwnd = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
                          gszAppName,
                          gszAppName,
                          WS_OVERLAPPEDWINDOW,
                          APP_WINDOW_XOFFSET,
                          APP_WINDOW_YOFFSET,
                          APP_WINDOW_WIDTH,
                          APP_WINDOW_HEIGHT,
                          NULL,
                          NULL,
                          hinst,
                          NULL);

    if (NULL == hwnd)
        return (NULL);

#ifdef UNICODE
    //
    //  the application--which is different than the pszCmdLine argument
    //  passed through WinMain()...
    //
    //  so, skip over the command name to get to the argument string
    //
    pszCmdLine = GetCommandLine();
    if (NULL != pszCmdLine)
    {
        while (('\0' != *pszCmdLine) && (' ' != *pszCmdLine++))
            ;
    }
#endif

    //
    //
    //
    //
    MixAppSettingsRestore(hwnd, nCmdShow);

    //
    //  finally, get the window displayed and return success
    //
    //  the ShowWindow call is made during MixAppInit
    //
//  ShowWindow(hwnd, nCmdShow);
//  UpdateWindow(hwnd);

    return (hwnd);
} // AppInit()

//--------------------------------------------------------------------------;
//
//  int AppExit
//
//  Description:
//      This function is called just before the application exits from
//      WinMain. Its purpose is to clean up any resources that were allocated
//      for running the application: brushes, heaps, etc..
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application that is exiting.
//
//      int nResult: The result of the WM_QUIT message (in wParam of the
//      MSG structure. This argument will usually be 0 (even if the message
//      loop was never entered).
//
//  Return (int):
//      The return value is usually nResult--be we give this function the
//      opportunity to modify its value.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

int FNGLOBAL AppExit
(
    HINSTANCE               hinst,
    int                     nResult
)
{
    DPF(1, "AppExit(hinst=%Xh, nResult=%d)", hinst, nResult);

    //
    //
    //
    //

    return (nResult);
} // AppExit()


MALINES.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  malines.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include "mixapp.h"

#include "debug.h"

TCHAR       gszLineFormatTitle[]        = TEXT("Type\t3!Component\t10!Line ID\t6!Flags\t6!Ctrls\t3!Conns\t3!Name");
TCHAR       gszLineFormatList[]         = TEXT("%-s%c\t%-s\t%.08lXh\t%.08lXh\t%lu\t%lu\t%-s");

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppGetComponentName
//
//  Description:
//
//
//  Arguments:
//      LPMIXERLINE pmxl:
//
//      LPTSTR szComponent:
//
//  Return (BOOL):
//
//  History:
//      08/18/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppGetComponentName
(
    LPMIXERLINE             pmxl,
    LPTSTR                  szComponent
)
{
    //
    //
    //
    if (0 == (MIXERLINE_LINEF_SOURCE & pmxl->fdwLine))
    {
        switch (pmxl->dwComponentType)
        {
            case MIXERLINE_COMPONENTTYPE_DST_UNDEFINED:
                lstrcpy(szComponent, "Undefined");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_DIGITAL:
                lstrcpy(szComponent, "Digital");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_LINE:
                lstrcpy(szComponent, "Line Level");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_MONITOR:
                lstrcpy(szComponent, "Monitor");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
                lstrcpy(szComponent, "Speakers");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_HEADPHONES:
                lstrcpy(szComponent, "Headphones");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_TELEPHONE:
                lstrcpy(szComponent, "Telephone");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
                lstrcpy(szComponent, "Wave Input");
                break;

            case MIXERLINE_COMPONENTTYPE_DST_VOICEIN:
                lstrcpy(szComponent, "Voice Recognition");
                break;

            default:
                lstrcpy(szComponent, "NOT VALID");
                break;
        }
    }
    else
    {
        switch (pmxl->dwComponentType)
        {
            case MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED:
                lstrcpy(szComponent, "Undefined");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_DIGITAL:
                lstrcpy(szComponent, "Digital");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_LINE:
                lstrcpy(szComponent, "Line Level");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
                lstrcpy(szComponent, "Microphone");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
                lstrcpy(szComponent, "Synthesizer");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
                lstrcpy(szComponent, "Compact Disc");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE:
                lstrcpy(szComponent, "Telephone");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER:
                lstrcpy(szComponent, "PC Speaker");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
                lstrcpy(szComponent, "Wave Out");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY:
                lstrcpy(szComponent, "Auxiliary");
                break;

            case MIXERLINE_COMPONENTTYPE_SRC_ANALOG:
                lstrcpy(szComponent, "Analog");
                break;

            default:
                lstrcpy(szComponent, "NOT VALID");
                break;
        }
    }

    return (TRUE);
} // MixAppGetComponentName()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcLineInfo
//
//  Description:
//      This dialog procedure is used to display driver capabilities.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcLineInfo
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    HWND                hedit;
    UINT                u;
    MIXERLINE           mxl;
    MMRESULT            mmr;
    TCHAR               ach[64];

    switch (uMsg)
    {
        case WM_INITDIALOG:
            hedit = GetDlgItem(hwnd, IDD_LINEINFO_EDIT_INFO);
            SetWindowFont(hedit, GetStockFont(SYSTEM_FIXED_FONT), FALSE);

            //
            //
            //
            //
            mxl.cbStruct      = sizeof(mxl);
            mxl.dwLineID      = lParam;

            mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_LINEID);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                          "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                          lParam, ghmx, mmr);
            }
            else
            {
                static TCHAR    szDisplayTitle[]  = TEXT("[Line Info]\r\n");

                //
                //
                //
                MEditPrintF(hedit, NULL);
                MEditPrintF(hedit, szDisplayTitle);

                MEditPrintF(hedit, "%25s: %.04Xh", (LPTSTR)"Mixer Handle", ghmx);

                //
                //
                //
                MEditPrintF(hedit, "%25s: %lu bytes (requested %lu)", (LPTSTR)"Size of Line Info", mxl.cbStruct, (DWORD)sizeof(mxl));
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Destination", mxl.dwDestination);
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Source", mxl.dwSource);
                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"Line ID", mxl.dwLineID);
                MEditPrintF(hedit, "~%25s: %.08lXh", (LPTSTR)"Line Flags", mxl.fdwLine);

                if (MIXERLINE_LINEF_SOURCE & mxl.fdwLine)
                    MEditPrintF(hedit, "~, source");
                else
                    MEditPrintF(hedit, "~, destination");

                if (MIXERLINE_LINEF_DISCONNECTED & mxl.fdwLine)
                    MEditPrintF(hedit, "~, disconnected");

                if (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine)
                    MEditPrintF(hedit, "~, active");

                if (~(MIXERLINE_LINEF_SOURCE | MIXERLINE_LINEF_DISCONNECTED | MIXERLINE_LINEF_ACTIVE) & mxl.fdwLine)
                    MEditPrintF(hedit, "~, *INVALID FLAGS*");

                MEditPrintF(hedit, "");
                MEditPrintF(hedit, "%25s: %.08lXh", (LPTSTR)"Driver User DWord", mxl.dwUser);

                MixAppGetComponentName(&mxl, ach);
                MEditPrintF(hedit, "%25s: %lu, (%s)", (LPTSTR)"Component Type", mxl.dwComponentType, (LPTSTR)ach);
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Num Channels", mxl.cChannels);
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Num Connections", mxl.cConnections);
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Num Controls", mxl.cControls);
                MEditPrintF(hedit, "%25s: '%s'", (LPTSTR)"Short Name", (LPTSTR)mxl.szShortName);
                MEditPrintF(hedit, "%25s: '%s'", (LPTSTR)"Name", (LPTSTR)mxl.szName);
                MEditPrintF(hedit, "\r\n%25s: %lu", (LPTSTR)"Target Type", mxl.Target.dwType);
                MEditPrintF(hedit, "%25s: %lu", (LPTSTR)"Device ID", mxl.Target.dwDeviceID);
                MEditPrintF(hedit, "%25s: %u", (LPTSTR)"Manufacturer ID", mxl.Target.wMid);
                MEditPrintF(hedit, "%25s: %u", (LPTSTR)"Product ID", mxl.Target.wPid);
                MEditPrintF(hedit, "%25s: %u.%.02u", (LPTSTR)"Version",
                            mxl.Target.vDriverVersion >> 8,
                            mxl.Target.vDriverVersion & 0x00FF);
                MEditPrintF(hedit, "%25s: '%s'", (LPTSTR)"Product Name", (LPTSTR)mxl.Target.szPname);
            }

            //
            //  return nonzero to set the input focus to the control
            //  identified by the (hwndFocus = (HWND)wParam) argument.
            //  a zero return tells the dialog manager that this function
            //  has set the focus using SetFocus.
            //
            return (TRUE);

        case WM_COMMAND:
            u = GET_WM_COMMAND_ID(wParam, lParam);
            if ((IDOK == u) || (IDCANCEL == u))
            {
                EndDialog(hwnd, (IDOK == u));
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcLineInfo()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppRefreshLineList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle of main window.
//
//  Return (BOOL):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MixAppRefreshLineList
(
    HWND                    hwnd,
    PTABBEDLISTBOX          ptlb
)
{
    static TCHAR        szLineTypeDst[] = TEXT("DST");
    static TCHAR        szLineTypeSrc[] = TEXT("  src");

    MMRESULT            mmr;
    UINT                u;
    UINT                v;
    UINT                cConnections;
    MIXERLINE           mxl;
    TCHAR               ach[128];
    TCHAR               szComponent[64];
    int                 nIndex;
    MIXERCAPS           mxcaps;

    //
    //
    //
    SetWindowRedraw(ptlb->hlb, FALSE);
    ListBox_ResetContent(ptlb->hlb);

    if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
        MixAppDebugLog(NULL);

    //
    //
    //
    //
    mmr = mixerGetDevCaps((UINT)ghmx, &mxcaps, sizeof(mxcaps));
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                    "mixerGetDevCaps() failed on hmx=%.04Xh, mmr=%u!",
                    ghmx, mmr);

        mxcaps.cDestinations = 0;
    }

    for (u = 0; u < mxcaps.cDestinations; u++)
    {
        mxl.cbStruct      = sizeof(mxl);
        mxl.dwDestination = u;

        mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_DESTINATION);
        if (MMSYSERR_NOERROR != mmr)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "mixerGetLineInfo(dst=%u) failed on hmx=%.04Xh, mmr=%u!",
                      u, ghmx, mmr);
            continue;
        }

        MixAppGetComponentName(&mxl, szComponent);

        //
        //
        //
        wsprintf(ach, gszLineFormatList,
                 (LPSTR)szLineTypeDst,
                 (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine) ? '*' : ' ',
                 (LPTSTR)szComponent,
                 mxl.dwLineID,
                 mxl.fdwLine,
                 mxl.cControls,
                 mxl.cConnections,
                 (LPSTR)mxl.szName);

        if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
        {
            MixAppDebugLog(ach);
            MixAppDebugLog(gszCRLF);
        }

        nIndex = ListBox_AddString(ptlb->hlb, ach);
        ListBox_SetItemData(ptlb->hlb, nIndex, mxl.dwLineID);

        cConnections = (UINT)mxl.cConnections;
        for (v = 0; v < cConnections; v++)
        {
            mxl.cbStruct      = sizeof(mxl);
            mxl.dwDestination = u;
            mxl.dwSource      = v;

            mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_SOURCE);
            if (MMSYSERR_NOERROR != mmr)
            {
                AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                          "mixerGetLineInfo(src=%u) failed on hmx=%.04Xh, mmr=%u!",
                          v, ghmx, mmr);
                continue;
            }

            MixAppGetComponentName(&mxl, szComponent);

            //
            //
            //
            wsprintf(ach, gszLineFormatList,
                     (LPSTR)szLineTypeSrc,
                     (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine) ? '*' : ' ',
                     (LPTSTR)szComponent,
                     mxl.dwLineID,
                     mxl.fdwLine,
                     mxl.cControls,
                     mxl.cConnections,
                     (LPSTR)mxl.szName);

            if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
            {
                MixAppDebugLog(ach);
                MixAppDebugLog(gszCRLF);
            }

            nIndex = ListBox_AddString(ptlb->hlb, ach);
            ListBox_SetItemData(ptlb->hlb, nIndex, mxl.dwLineID);
        }
    }

    //
    //
    //
    SetWindowRedraw(ptlb->hlb, TRUE);

    return (TRUE);
} // MixAppRefreshLineList()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT MixAppLineChange
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwLineID:
//
//  Return (LRESULT):
//
//  History:
//      07/21/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL MixAppLineChange
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
)
{
    DPF(1, "MixAppLineChange(hwnd=%Xh, hmx=%Xh, dwLineID=%.08lXh)",
        hwnd, hmx, dwLineID);

    //
    //
    //
    if (gfDisplayingControl &&
        (dwLineID == gmxl.dwLineID) &&
        (NULL != ghdlgControl))
    {
        SendMessage(ghdlgControl, MM_MIXM_LINE_CHANGE, (WPARAM)hmx, dwLineID);
    }

    MixAppRefreshLineList(hwnd, gptlbLines);

    //
    //  return zero because we handled the message
    //
    return (0L);
} // MixAppLineChange()


MALIST.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  malist.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include "mixapp.h"

#include "debug.h"

//
//
//
typedef struct tMACONTROLINSTANCE_LIST
{
    LPMACONTROLINSTANCE             pmaci;
    BOOL                            fSingleSelect;
    MIXERCONTROLDETAILS_BOOLEAN     pmxcd_f[];

} MACONTROLINSTANCE_LIST, *PMACONTROLINSTANCE_LIST;

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlChangeList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwControlID:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlChangeList
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
)
{
    MMRESULT                        mmr;
    HWND                            htxt;
    PMACONTROLINSTANCE_LIST         pmaci_list;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_BOOLEAN    pmxcd_f;
    UINT                            cChannels;
    UINT                            cMultipleItems;
    UINT                            u;
    UINT                            uIndex;
    UINT                            v;
    BOOL                            fValue;
    MIXERCONTROLDETAILS             mxcd;
    HWND                            hcheckFocus;
    HWND                            hcheck;

    hcheckFocus = GetFocus();
    if (NULL == hcheckFocus)
    {
        hcheckFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
    }
    else
    {
        uIndex = GetDlgCtrlID(hcheckFocus);
        if (uIndex < IDD_MACONTROL_MULTICHANNEL_BASE)
        {
            hcheckFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
        }
    }

    //
    //
    //
    pmaci_list = (PMACONTROLINSTANCE_LIST)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci      = pmaci_list->pmaci;
    pmxl       = pmaci->pmxl;
    pmxctrl    = pmaci->pmxctrl;
    pmxcd_f    = &pmaci_list->pmxcd_f[0];

    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            fValue = (BOOL)pmxcd_f[uIndex].fValue;

            hcheck = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex);
            if (hcheckFocus == hcheck)
            {
                htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
                AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                                fValue,
                                pmxcd_f[uIndex].fValue);
            }

            if (fValue == (BOOL)IsDlgButtonChecked(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex))
                continue;

            CheckDlgButton(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex, fValue);
        }
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    for (v = 0; v < cMultipleItems; v++)
    {
        uIndex = v;

        fValue = (BOOL)pmxcd_f[uIndex].fValue;

        hcheck = GetDlgItem(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex);
        if (hcheckFocus == hcheck)
        {
            htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
            AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                             fValue,
                             pmxcd_f[uIndex].fValue);
        }

        if (fValue == (BOOL)IsDlgButtonChecked(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex))
            continue;

        CheckDlgButton(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex, fValue);
    }

    return (TRUE);
} // MixAppControlChangeList()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlSetList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hcheck:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlSetList
(
    HWND                    hwnd,
    HWND                    hcheck
)
{
    PMACONTROLINSTANCE_LIST         pmaci_list;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_BOOLEAN    pmxcd_f;
    MMRESULT                        mmr;
    BOOL                            fValue;
    UINT                            cChannels;
    UINT                            uIndex;
    MIXERCONTROLDETAILS             mxcd;

    //
    //
    //
    pmaci_list = (PMACONTROLINSTANCE_LIST)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci      = pmaci_list->pmaci;
    pmxl       = pmaci->pmxl;
    pmxctrl    = pmaci->pmxctrl;
    pmxcd_f    = &pmaci_list->pmxcd_f[0];

    uIndex = GetDlgCtrlID(hcheck);

    if (pmaci_list->fSingleSelect)
    {
        fValue = TRUE;
    }
    else
    {
        //
        //  get current value and toggle..
        //
        fValue = (0 == IsDlgButtonChecked(hwnd, uIndex));
    }

    if (uIndex < IDD_MACONTROL_UNIFORM_BASE)
    {
        cChannels = (UINT)pmxl->cChannels;
        if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
            cChannels = 1;

        uIndex -= IDD_MACONTROL_MULTICHANNEL_BASE;
    }
    else
    {
        cChannels = 1;

        uIndex -= IDD_MACONTROL_UNIFORM_BASE;
    }

    //
    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    if (pmaci_list->fSingleSelect)
    {
        UINT        u;
        UINT        v;
        UINT        cMultipleItems;

        //
        //  reset all settings in the correct channel
        //
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

        u = uIndex / (cChannels * cMultipleItems);

        for (v = 0; v < cMultipleItems; v++)
        {
            pmxcd_f[(u * cMultipleItems) + v].fValue = FALSE;
        }
    }

    pmxcd_f[uIndex].fValue = fValue;

    mmr = mixerSetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerSetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    CheckDlgButton(hwnd, GetDlgCtrlID(hcheck), (BOOL)pmxcd_f[uIndex].fValue);
#if 0
    if (0 == uIndex)
    {
        htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
        AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                         nValue,
                         pmxcd_f[uIndex].fValue);
    }
#endif

    return (TRUE);
} // MixAppControlSetList()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppLineChangeList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwLineID:
//
//  Return (BOOL):
//
//  History:
//      09/24/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppLineChangeList
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
)
{
    MMRESULT                    mmr;
    PMACONTROLINSTANCE_LIST     pmaci_list;
    LPMACONTROLINSTANCE         pmaci;
    LPMIXERLINE                 pmxl;
    MIXERLINE                   mxl;
    BOOL                        fSource;
    BOOL                        fActive;
    BOOL                        fDisconnected;
    HWND                        htxt;

    //
    //
    //
    pmaci_list = (PMACONTROLINSTANCE_LIST)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_list->pmaci;
    pmxl        = pmaci->pmxl;

    if (pmxl->dwLineID != dwLineID)
    {
        DPF(0, "!MixAppLineChangeList: why am i getting notifications for some other line??");

        //
        //  this would be a bug in this app... keep from bombing though
        //  until i fix it.
        //
        dwLineID = pmxl->dwLineID;
    }

    //
    //  get the current state of the line that changed.. this is actually
    //  silly that you have to do this just to determine if the line went
    //  [in]active. sigh..
    //
    mxl.cbStruct      = sizeof(mxl);
    mxl.dwLineID      = dwLineID;

    mmr = mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_LINEID);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  dwLineID, hmx, mmr);

        return (FALSE);
    }

    fSource       = (0 != (MIXERLINE_LINEF_SOURCE & mxl.fdwLine));
    fActive       = (0 != (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine));
    fDisconnected = (0 != (MIXERLINE_LINEF_DISCONNECTED & mxl.fdwLine));

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_LINEINFO);
    AppSetWindowText(htxt, "(%s), '%s', %s, %s",
                     fSource ? (LPSTR)"src" : (LPSTR)"DST",
                     (LPSTR)mxl.szShortName,
                     fActive ? (LPSTR)"ACTIVE" : (LPSTR)"inactive",
                     fDisconnected ? (LPSTR)"DISCONNECTED" : (LPSTR)"connected");

    return (fActive);
} // MixAppLineChangeList()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppInitDialogList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hwndFocus:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppInitDialogList
(
    HWND                    hwnd,
    HWND                    hwndFocus,
    LPARAM                  lParam
)
{
    #define FCB_DEF_STYLE   (WS_VISIBLE | WS_CHILD | BS_CHECKBOX | WS_TABSTOP)

    static TCHAR        szButton[]  = TEXT("button");
    static TCHAR        szTitle[]   = TEXT("List Class: '%s'");

    MMRESULT                    mmr;
    TCHAR                       szControlType[64];
    LPMACONTROLINSTANCE         pmaci;
    PMACONTROLINSTANCE_LIST     pmaci_list;

    LPMIXERLINE                 pmxl;
    LPMIXERCONTROL              pmxctrl;

    HWND                        htxt;
    UINT                        u;
    UINT                        v;
    HWND                        hcheck;
    RECT                        rcM;
    RECT                        rcU;
    int                         cycap;
    UINT                        cb;
    UINT                        cChannels;
    UINT                        cMultipleItems;
    UINT                        uIndex;
    BOOL                        fSingleSelect;
    PMIXERCONTROLDETAILS_LISTTEXT pmxcd_lt;
    MIXERCONTROLDETAILS         mxcd;

    //
    //
    //
    pmaci = (LPMACONTROLINSTANCE)lParam;
    if (NULL == pmaci)
    {
        DPF(0, "!MixAppInitDialogList: pmaci passed in lParam is NULL!?!");
        return (FALSE);
    }

    pmxl    = pmaci->pmxl;
    pmxctrl = pmaci->pmxctrl;

    if (!MixAppGetControlTypeName(pmxctrl, szControlType))
    {
        return (FALSE);
    }

    //
    //
    //
    switch (pmxctrl->dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_SINGLESELECT:
        case MIXERCONTROL_CONTROLTYPE_MUX:
        case MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT:
        case MIXERCONTROL_CONTROLTYPE_MIXER:
            break;

        default:
            DPF(0, "!MixAppInitDialogList: pmxctrl->dwControlType=%.08lXh not valid list type!", pmxctrl->dwControlType);
            return (FALSE);
    }

    fSingleSelect = (MIXERCONTROL_CT_SC_LIST_SINGLE == (MIXERCONTROL_CT_SUBCLASS_MASK & pmxctrl->dwControlType));

    //
    //
    //
    AppSetWindowText(hwnd, szTitle, (LPTSTR)szControlType);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_SHORT_NAME, pmxctrl->szShortName);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_LONG_NAME,  pmxctrl->szName);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_BOUNDS);
    AppSetWindowText(htxt, "dwMinimum=%lu, dwMaximum=%lu",
                     pmxctrl->Bounds.dwMinimum,
                     pmxctrl->Bounds.dwMaximum);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_METRICS);
    AppSetWindowText(htxt, "N/A");

    //
    //
    //
    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    cb  = sizeof(*pmaci_list);
    cb += cChannels * cMultipleItems * sizeof(pmaci_list->pmxcd_f[0]);
    pmaci_list = (PMACONTROLINSTANCE_LIST)LocalAlloc(LPTR, cb);
    if (NULL == pmaci_list)
    {
        DPF(0, "!MixAppInitDialogList: failed trying to alloc %u bytes for control instance!", cb);
        return (FALSE);
    }

    //
    //
    //
    SetWindowLong(hwnd, DWL_USER, (LPARAM)(UINT)pmaci_list);

    pmaci_list->pmaci         = pmaci;
    pmaci_list->fSingleSelect = fSingleSelect;

    //
    //
    //
    cb = cChannels * cMultipleItems * sizeof(*pmxcd_lt);
    pmxcd_lt = (PMIXERCONTROLDETAILS_LISTTEXT)LocalAlloc(LPTR, cb);
    if (NULL == pmaci_list)
    {
        DPF(0, "!MixAppInitDialogList: failed trying to alloc %u bytes for control list text!", cb);
        return (FALSE);
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_lt);
    mxcd.paDetails      = pmxcd_lt;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_LISTTEXT);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                    "mixerGetControlDetails(ctrlid=%.08lXh) on LISTTEXT failed on hmx=%.04Xh, mmr=%u!",
                    pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    //
    //
    //
    //
    cycap = GetSystemMetrics(SM_CYCAPTION);

    hcheck = GetDlgItem(hwnd, IDD_MACONTROL_GRP_MULTICHANNEL);
    GetWindowRect(hcheck, &rcM);

    InflateRect(&rcM, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcM.left);
    ScreenToClient(hwnd, (LPPOINT)&rcM.right);

    rcM.bottom = rcM.top + cycap;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            hcheck = CreateWindow(szButton,
                                  pmxcd_lt[uIndex].szName,
                                  FCB_DEF_STYLE,
                                  rcM.left, rcM.top,
                                  rcM.right - rcM.left,
                                  rcM.bottom - rcM.top,
                                  hwnd, (HMENU)(IDD_MACONTROL_MULTICHANNEL_BASE + uIndex),
                                  ghinst, NULL);

            rcM.top    += cycap + 4;
            rcM.bottom += cycap + 4;
        }

        //
        //  add more separation between channels
        //
        rcM.top    += cycap;
        rcM.bottom += cycap;
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_lt);
    mxcd.paDetails      = pmxcd_lt;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_LISTTEXT);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                    "mixerGetControlDetails(ctrlid=%.08lXh) on LISTTEXT failed on hmx=%.04Xh, mmr=%u!",
                    pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    //
    //
    //
    //
    hcheck = GetDlgItem(hwnd, IDD_MACONTROL_GRP_UNIFORM);
    GetWindowRect(hcheck, &rcU);

    InflateRect(&rcU, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcU.left);
    ScreenToClient(hwnd, (LPPOINT)&rcU.right);

    rcU.bottom = rcU.top + cycap;

    for (v = 0; v < cMultipleItems; v++)
    {
        hcheck = CreateWindow(szButton,
                              pmxcd_lt[v].szName,
                              FCB_DEF_STYLE,
                              rcU.left, rcU.top,
                              rcU.right - rcU.left,
                              rcU.bottom - rcU.top,
                              hwnd, (HMENU)(IDD_MACONTROL_UNIFORM_BASE + v),
                              ghinst, NULL);

        rcU.top    += cycap + 4;
        rcU.bottom += cycap + 4;
    }

    LocalFree((HLOCAL)pmxcd_lt);

    //
    //
    //
    SendMessage(hwnd,
                MM_MIXM_LINE_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxl->dwLineID);

    SendMessage(hwnd,
                MM_MIXM_CONTROL_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxctrl->dwControlID);

    return (TRUE);
} // MixAppInitDialogList()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcControlList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      07/22/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcControlList
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LRESULT             lr;
    HLOCAL              hl;
    UINT                uId;

    if (WM_COMMAND == uMsg)
    {
        uId = GET_WM_COMMAND_ID(wParam, lParam);
        if (IDD_MACONTROL_GRP_MULTICHANNEL <= uId)
        {
            MixAppControlSetList(hwnd, GET_WM_COMMAND_HWND(wParam, lParam));
            return (TRUE);
        }
    }

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            lr = HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, MixAppInitDialogList);
            if (FALSE == lr)
            {
                EndDialog(hwnd, FALSE);
            }
            else
            {
                ghdlgControl = hwnd;
            }
            return (TRUE);

        case MM_MIXM_LINE_CHANGE:
            MixAppLineChangeList(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case MM_MIXM_CONTROL_CHANGE:
            MixAppControlChangeList(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDCANCEL:
                case IDOK:
                    hl = (HLOCAL)(UINT)GetWindowLong(hwnd, DWL_USER);
                    LocalFree(hl);

                    EndDialog(hwnd, TRUE);
                    ghdlgControl = NULL;
                    break;
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcControlList()


MAMETER.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mameter.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "muldiv32.h"

#include "mixapp.h"

#include "debug.h"

//
//
//
typedef struct tMACONTROLINSTANCE_METER
{
    LPMACONTROLINSTANCE             pmaci;
    BOOL                            fSigned;
    DWORD                           dwRange;
    MIXERCONTROLDETAILS_SIGNED      pmxcd_s[];

} MACONTROLINSTANCE_METER, *PMACONTROLINSTANCE_METER;

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlChangeMeter
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwControlID:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlChangeMeter
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
)
{
    MMRESULT                        mmr;
    HWND                            htxt;
    PMACONTROLINSTANCE_METER        pmaci_meter;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_SIGNED     pmxcd_s;
    UINT                            cChannels;
    UINT                            cMultipleItems;
    UINT                            u;
    UINT                            uIndex;
    UINT                            v;
    MIXERCONTROLDETAILS             mxcd;
    HWND                            hsbFocus;
    HWND                            hsb;
    BOOL                            fSigned;
    DWORD                           dwRange;
    DWORD                           dwValue;
    int                             nValue;

    hsbFocus = GetFocus();
    if (NULL == hsbFocus)
    {
        hsbFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
    }
    else
    {
        uIndex = GetDlgCtrlID(hsbFocus);
        if (uIndex < IDD_MACONTROL_MULTICHANNEL_BASE)
        {
            hsbFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
        }
    }

    //
    //
    //
    pmaci_meter = (PMACONTROLINSTANCE_METER)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_meter->pmaci;
    pmxl        = pmaci->pmxl;
    pmxctrl     = pmaci->pmxctrl;
    pmxcd_s     = &pmaci_meter->pmxcd_s[0];
    fSigned     = pmaci_meter->fSigned;
    dwRange     = pmaci_meter->dwRange;

    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_s);
    mxcd.paDetails      = pmxcd_s;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            if (fSigned)
            {
                dwValue = (DWORD)(pmxcd_s[uIndex].lValue -
                                  pmxctrl->Bounds.lMinimum);
            }
            else
            {
                dwValue  = (DWORD)pmxcd_s[uIndex].lValue;
                dwValue -= pmxctrl->Bounds.dwMinimum;
            }

            nValue = (int)MulDivRN(dwValue, 32767, dwRange);

            //
            //  Windows is stupid and forces an update to scrollbars
            //  (a flickering paint) even when the position does not
            //  change... so don't update if it is already in the
            //  right position.
            //
            hsb = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex);

            if (hsbFocus == hsb)
            {
                htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
                if (fSigned)
                {
                    AppSetWindowText(htxt, "mapped=%d, lValue=%ld",
                                     nValue,
                                     pmxcd_s[uIndex].lValue);
                }
                else
                {
                    AppSetWindowText(htxt, "mapped=%d, dwValue=%lu",
                                     nValue,
                                     pmxcd_s[uIndex].lValue);
                }
            }

            if (32767 - nValue == GetScrollPos(hsb, SB_CTL))
                continue;

            //
            //  note that we _invert_ the position since a meter will
            //  increase approaching the top (opposite of scrollbar
            //  logic)
            //
            SetScrollPos(hsb, SB_CTL, 32767 - nValue, TRUE);
        }
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_s);
    mxcd.paDetails      = pmxcd_s;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    for (v = 0; v < cMultipleItems; v++)
    {
        uIndex = v;

        if (fSigned)
        {
            dwValue = (DWORD)(pmxcd_s[uIndex].lValue -
                              pmxctrl->Bounds.lMinimum);
        }
        else
        {
            dwValue  = (DWORD)pmxcd_s[uIndex].lValue;
            dwValue -= pmxctrl->Bounds.dwMinimum;
        }

        nValue = (int)MulDivRN(dwValue, 32767, dwRange);

        hsb = GetDlgItem(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex);
        if (hsbFocus == hsb)
        {
            htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
            if (fSigned)
            {
                AppSetWindowText(htxt, "mapped=%d, lValue=%ld",
                                 nValue,
                                 pmxcd_s[uIndex].lValue);
            }
            else
            {
                AppSetWindowText(htxt, "mapped=%d, dwValue=%lu",
                                 nValue,
                                 pmxcd_s[uIndex].lValue);
            }
        }

        if (32767 - nValue == GetScrollPos(hsb, SB_CTL))
            continue;

        //
        //  note that we _invert_ the position since a meter will
        //  increase approaching the top (opposite of scrollbar
        //  logic)
        //
        SetScrollPos(hsb, SB_CTL, 32767 - nValue, TRUE);
    }

    return (TRUE);
} // MixAppControlChangeMeter()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppLineChangeMeter
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwLineID:
//
//  Return (BOOL):
//
//  History:
//      09/24/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppLineChangeMeter
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
)
{
    MMRESULT                    mmr;
    PMACONTROLINSTANCE_METER    pmaci_meter;
    LPMACONTROLINSTANCE         pmaci;
    LPMIXERLINE                 pmxl;
    MIXERLINE                   mxl;
    BOOL                        fSource;
    BOOL                        fActive;
    BOOL                        fDisconnected;
    HWND                        htxt;

    //
    //
    //
    pmaci_meter = (PMACONTROLINSTANCE_METER)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_meter->pmaci;
    pmxl        = pmaci->pmxl;

    if (pmxl->dwLineID != dwLineID)
    {
        DPF(0, "!MixAppLineChangeMeter: why am i getting notifications for some other line??");

        //
        //  this would be a bug in this app... keep from bombing though
        //  until i fix it.
        //
        dwLineID = pmxl->dwLineID;
    }

    //
    //  get the current state of the line that changed.. this is actually
    //  silly that you have to do this just to determine if the line went
    //  [in]active. sigh..
    //
    mxl.cbStruct      = sizeof(mxl);
    mxl.dwLineID      = dwLineID;

    mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_LINEID);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  dwLineID, ghmx, mmr);

        return (FALSE);
    }

    fSource       = (0 != (MIXERLINE_LINEF_SOURCE & mxl.fdwLine));
    fActive       = (0 != (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine));
    fDisconnected = (0 != (MIXERLINE_LINEF_DISCONNECTED & mxl.fdwLine));

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_LINEINFO);
    AppSetWindowText(htxt, "(%s), '%s', %s, %s",
                     fSource ? (LPSTR)"src" : (LPSTR)"DST",
                     (LPSTR)mxl.szShortName,
                     fActive ? (LPSTR)"ACTIVE" : (LPSTR)"inactive",
                     fDisconnected ? (LPSTR)"DISCONNECTED" : (LPSTR)"connected");

    return (fActive);
} // MixAppLineChangeMeter()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppInitDialogMeter
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hwndFocus:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppInitDialogMeter
(
    HWND                    hwnd,
    HWND                    hwndFocus,
    LPARAM                  lParam
)
{
    #define FSB_DEF_STYLE   (WS_VISIBLE | WS_CHILD | SBS_VERT | WS_TABSTOP)

    static TCHAR        szScrollBar[] = TEXT("scrollbar");
    static TCHAR        szTitle[]     = TEXT("Meter Class: '%s'");

    TCHAR                       szControlType[64];
    LPMACONTROLINSTANCE         pmaci;
    PMACONTROLINSTANCE_METER    pmaci_meter;

    LPMIXERLINE                 pmxl;
    LPMIXERCONTROL              pmxctrl;

    HWND                        htxt;
    UINT                        u;
    UINT                        v;
    HWND                        hsb;
    RECT                        rcM;
    RECT                        rcU;
    int                         cxvsb;
    UINT                        cb;
    UINT                        cChannels;
    UINT                        cMultipleItems;
    UINT                        uIndex;
    DWORD                       dwRange;
    BOOL                        fSigned;

    //
    //
    //
    pmaci = (LPMACONTROLINSTANCE)lParam;
    if (NULL == pmaci)
    {
        DPF(0, "!MixAppInitDialogMeter: pmaci passed in lParam is NULL!?!");
        return (FALSE);
    }

    pmxl    = pmaci->pmxl;
    pmxctrl = pmaci->pmxctrl;

    if (!MixAppGetControlTypeName(pmxctrl, szControlType))
    {
        return (FALSE);
    }

    //
    //
    //
    switch (pmxctrl->dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_SIGNEDMETER:
        case MIXERCONTROL_CONTROLTYPE_PEAKMETER:
        case MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER:
            break;

        default:
            DPF(0, "!MixAppInitDialogMeter: pmxctrl->dwControlType=%.08lXh not valid meter!", pmxctrl->dwControlType);
            return (FALSE);
    }

    fSigned = (MIXERCONTROL_CT_UNITS_SIGNED == (MIXERCONTROL_CT_UNITS_MASK & pmxctrl->dwControlType));

    //
    //
    //
    AppSetWindowText(hwnd, szTitle, (LPTSTR)szControlType);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_SHORT_NAME, pmxctrl->szShortName);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_LONG_NAME,  pmxctrl->szName);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_BOUNDS);
    if (fSigned)
    {
        AppSetWindowText(htxt, "lMinimum=%ld, lMaximum=%ld",
                         pmxctrl->Bounds.lMinimum,
                         pmxctrl->Bounds.lMaximum);

        dwRange = (pmxctrl->Bounds.lMaximum - pmxctrl->Bounds.lMinimum);
    }
    else
    {
        AppSetWindowText(htxt, "dwMinimum=%lu, dwMaximum=%lu",
                         pmxctrl->Bounds.dwMinimum,
                         pmxctrl->Bounds.dwMaximum);

        dwRange = (pmxctrl->Bounds.dwMaximum - pmxctrl->Bounds.dwMinimum);
    }

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_METRICS);
    AppSetWindowText(htxt, "dwRange=%lu",
                     dwRange);

    //
    //
    //
    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    cb  = sizeof(*pmaci_meter);
    cb += cChannels * cMultipleItems * sizeof(pmaci_meter->pmxcd_s[0]);
    pmaci_meter = (PMACONTROLINSTANCE_METER)LocalAlloc(LPTR, cb);
    if (NULL == pmaci_meter)
    {
        DPF(0, "!MixAppInitDialogMeter: failed trying to alloc %u bytes for control instance!", cb);
        return (FALSE);
    }

    //
    //
    //
    SetWindowLong(hwnd, DWL_USER, (LPARAM)(UINT)pmaci_meter);

    pmaci_meter->pmaci    = pmaci;
    pmaci_meter->fSigned  = fSigned;
    pmaci_meter->dwRange  = dwRange;

    //
    //
    //
    //
    cxvsb = GetSystemMetrics(SM_CXVSCROLL);

    hsb = GetDlgItem(hwnd, IDD_MACONTROL_GRP_MULTICHANNEL);
    GetWindowRect(hsb, &rcM);

    InflateRect(&rcM, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcM.left);
    ScreenToClient(hwnd, (LPPOINT)&rcM.right);

    rcM.right = rcM.left + cxvsb;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            hsb = CreateWindow(szScrollBar, gszNull, FSB_DEF_STYLE,
                               rcM.left, rcM.top,
                               rcM.right - rcM.left,
                               rcM.bottom - rcM.top,
                               hwnd, (HMENU)(IDD_MACONTROL_MULTICHANNEL_BASE + uIndex),
                               ghinst, NULL);

            SetScrollRange(hsb, SB_CTL, 0, 32767, FALSE);

            rcM.left  += cxvsb + 4;
            rcM.right += cxvsb + 4;
        }

        //
        //  add more separation between channels
        //
        rcM.left  += cxvsb;
        rcM.right += cxvsb;
    }

    //
    //
    //
    //
    hsb = GetDlgItem(hwnd, IDD_MACONTROL_GRP_UNIFORM);
    GetWindowRect(hsb, &rcU);

    InflateRect(&rcU, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcU.left);
    ScreenToClient(hwnd, (LPPOINT)&rcU.right);

    rcU.right = rcU.left + cxvsb;

    for (v = 0; v < cMultipleItems; v++)
    {
        hsb = CreateWindow(szScrollBar, gszNull, FSB_DEF_STYLE,
                            rcU.left, rcU.top,
                            rcU.right - rcU.left,
                            rcU.bottom - rcU.top,
                            hwnd, (HMENU)(IDD_MACONTROL_UNIFORM_BASE + v),
                            ghinst, NULL);

        SetScrollRange(hsb, SB_CTL, 0, 32767, FALSE);

        rcU.left  += cxvsb + 4;
        rcU.right += cxvsb + 4;
    }

    //
    //
    //
    SendMessage(hwnd,
                MM_MIXM_LINE_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxl->dwLineID);

    SendMessage(hwnd,
                MM_MIXM_CONTROL_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxctrl->dwControlID);

    return (TRUE);
} // MixAppInitDialogMeter()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcControlMeter
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      07/22/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcControlMeter
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    static  BOOL        fTimerActive;
    LRESULT             lr;
    HLOCAL              hl;
    UINT                uId;
    BOOL                fLineActive;

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            fTimerActive = FALSE;

            lr = HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, MixAppInitDialogMeter);
            if (FALSE == lr)
            {
                EndDialog(hwnd, FALSE);
            }
            else
            {
                ghdlgControl = hwnd;
            }
            return (TRUE);

        case MM_MIXM_LINE_CHANGE:
            fLineActive = MixAppLineChangeMeter(hwnd, (HMIXER)wParam, lParam);
            if (fLineActive)
            {
                if (!fTimerActive)
                {
                    fTimerActive = (0 != SetTimer(hwnd, 1, 100, NULL));
                }
            }
            else
            {
                if (fTimerActive)
                {
                    KillTimer(hwnd, 1);
                    fTimerActive = FALSE;
                }
            }
            return (TRUE);

        case WM_TIMER:
        case MM_MIXM_CONTROL_CHANGE:
            MixAppControlChangeMeter(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDCANCEL:
                case IDOK:
                    if (fTimerActive)
                    {
                        KillTimer(hwnd, 1);
                        fTimerActive = FALSE;
                    }

                    hl = (HLOCAL)(UINT)GetWindowLong(hwnd, DWL_USER);
                    LocalFree(hl);

                    EndDialog(hwnd, TRUE);
                    ghdlgControl = NULL;
                    break;
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcControlMeter()


MANUMBER.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  manumber.c
//
//  Description:
//
//
//  History:
// 9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

//#include <msmixmgr.h>

#include "appport.h"
#include "mixapp.h"

#include "debug.h"


MASLIDER.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  maslider.c
//
//  Description:
//
//
//  History:
// 9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

//#include <msmixmgr.h>

#include "appport.h"
#include "mixapp.h"

#include "debug.h"


MASWITCH.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  maswitch.c
//
//  Description:
//
//
//  History:
//       9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include "mixapp.h"

#include "debug.h"

//
//
//
typedef struct tMACONTROLINSTANCE_SWITCH
{
    LPMACONTROLINSTANCE             pmaci;
    MIXERCONTROLDETAILS_BOOLEAN     pmxcd_f[];

} MACONTROLINSTANCE_SWITCH, *PMACONTROLINSTANCE_SWITCH;

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlChangeSwitch
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwControlID:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlChangeSwitch
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
)
{
    MMRESULT                        mmr;
    HWND                            htxt;
    PMACONTROLINSTANCE_SWITCH       pmaci_switch;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_BOOLEAN    pmxcd_f;
    UINT                            cChannels;
    UINT                            cMultipleItems;
    UINT                            u;
    UINT                            uIndex;
    UINT                            v;
    BOOL                            fValue;
    MIXERCONTROLDETAILS             mxcd;
    HWND                            hcheckFocus;
    HWND                            hcheck;

    hcheckFocus = GetFocus();
    if (NULL == hcheckFocus)
    {
        hcheckFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
    }
    else
    {
        uIndex = GetDlgCtrlID(hcheckFocus);
        if (uIndex < IDD_MACONTROL_MULTICHANNEL_BASE)
        {
            hcheckFocus = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE);
        }
    }

    //
    //
    //
    pmaci_switch = (PMACONTROLINSTANCE_SWITCH)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci        = pmaci_switch->pmaci;
    pmxl         = pmaci->pmxl;
    pmxctrl      = pmaci->pmxctrl;
    pmxcd_f      = &pmaci_switch->pmxcd_f[0];

    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            fValue = (BOOL)pmxcd_f[uIndex].fValue;

            hcheck = GetDlgItem(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex);
            if (hcheckFocus == hcheck)
            {
                htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
                AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                                fValue,
                                pmxcd_f[uIndex].fValue);
            }

            if (fValue == (BOOL)IsDlgButtonChecked(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex))
                continue;

            CheckDlgButton(hwnd, IDD_MACONTROL_MULTICHANNEL_BASE + uIndex, fValue);
        }
    }

    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    for (v = 0; v < cMultipleItems; v++)
    {
        uIndex = v;

        fValue = (BOOL)pmxcd_f[uIndex].fValue;

        hcheck = GetDlgItem(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex);
        if (hcheckFocus == hcheck)
        {
            htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
            AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                             fValue,
                             pmxcd_f[uIndex].fValue);
        }

        if (fValue == (BOOL)IsDlgButtonChecked(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex))
            continue;

        CheckDlgButton(hwnd, IDD_MACONTROL_UNIFORM_BASE + uIndex, fValue);
    }

    return (TRUE);
} // MixAppControlChangeSwitch()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppControlSetSwitch
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hcheck:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppControlSetSwitch
(
    HWND                    hwnd,
    HWND                    hcheck
)
{
    PMACONTROLINSTANCE_SWITCH       pmaci_switch;
    LPMACONTROLINSTANCE             pmaci;
    LPMIXERLINE                     pmxl;
    LPMIXERCONTROL                  pmxctrl;
    PMIXERCONTROLDETAILS_BOOLEAN    pmxcd_f;
    MMRESULT                        mmr;
    BOOL                            fValue;
    UINT                            cChannels;
    UINT                            uIndex;
    MIXERCONTROLDETAILS             mxcd;

    //
    //
    //
    pmaci_switch = (PMACONTROLINSTANCE_SWITCH)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci        = pmaci_switch->pmaci;
    pmxl         = pmaci->pmxl;
    pmxctrl      = pmaci->pmxctrl;
    pmxcd_f      = &pmaci_switch->pmxcd_f[0];

    uIndex = GetDlgCtrlID(hcheck);

    //
    //  get value and toggle..
    //
    fValue = (0 == IsDlgButtonChecked(hwnd, uIndex));

    if (uIndex < IDD_MACONTROL_UNIFORM_BASE)
    {
        cChannels = (UINT)pmxl->cChannels;
        if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
            cChannels = 1;

        uIndex -= IDD_MACONTROL_MULTICHANNEL_BASE;
    }
    else
    {
        cChannels = 1;

        uIndex -= IDD_MACONTROL_UNIFORM_BASE;
    }

    //
    //
    //
    //
    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = pmxctrl->dwControlID;
    mxcd.cChannels      = cChannels;
    mxcd.cMultipleItems = pmxctrl->cMultipleItems;
    mxcd.cbDetails      = sizeof(*pmxcd_f);
    mxcd.paDetails      = pmxcd_f;

    mmr = mixerGetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    pmxcd_f[uIndex].fValue = fValue;

    mmr = mixerSetControlDetails((HMIXEROBJ)pmaci->hmx, &mxcd, 0L);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerSetControlDetails(ctrlid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  pmxctrl->dwControlID, pmaci->hmx, mmr);
        return (FALSE);
    }

    CheckDlgButton(hwnd, GetDlgCtrlID(hcheck), (BOOL)pmxcd_f[uIndex].fValue);
#if 0
    if (0 == uIndex)
    {
        htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_VALUE);
        AppSetWindowText(htxt, "mapped=%d, fValue=%ld",
                         nValue,
                         pmxcd_f[uIndex].fValue);
    }
#endif

    return (TRUE);
} // MixAppControlSetSwitch()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppLineChangeSwitch
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmx:
//
//      DWORD dwLineID:
//
//  Return (BOOL):
//
//  History:
//      09/24/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppLineChangeSwitch
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
)
{
    MMRESULT                    mmr;
    PMACONTROLINSTANCE_SWITCH   pmaci_switch;
    LPMACONTROLINSTANCE         pmaci;
    LPMIXERLINE                 pmxl;
    MIXERLINE                   mxl;
    BOOL                        fSource;
    BOOL                        fActive;
    BOOL                        fDisconnected;
    HWND                        htxt;

    //
    //
    //
    pmaci_switch = (PMACONTROLINSTANCE_SWITCH)(UINT)GetWindowLong(hwnd, DWL_USER);
    pmaci       = pmaci_switch->pmaci;
    pmxl        = pmaci->pmxl;

    if (pmxl->dwLineID != dwLineID)
    {
        DPF(0, "!MixAppLineChangeSwitch: why am i getting notifications for some other line??");

        //
        //  this would be a bug in this app... keep from bombing though
        //  until i fix it.
        //
        dwLineID = pmxl->dwLineID;
    }

    //
    //  get the current state of the line that changed.. this is actually
    //  silly that you have to do this just to determine if the line went
    //  [in]active. sigh..
    //
    mxl.cbStruct      = sizeof(mxl);
    mxl.dwLineID      = dwLineID;

    mmr = mixerGetLineInfo((HMIXEROBJ)ghmx, &mxl, MIXER_GETLINEINFOF_LINEID);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                  "mixerGetLineInfo(lineid=%.08lXh) failed on hmx=%.04Xh, mmr=%u!",
                  dwLineID, ghmx, mmr);

        return (FALSE);
    }

    fSource       = (0 != (MIXERLINE_LINEF_SOURCE & mxl.fdwLine));
    fActive       = (0 != (MIXERLINE_LINEF_ACTIVE & mxl.fdwLine));
    fDisconnected = (0 != (MIXERLINE_LINEF_DISCONNECTED & mxl.fdwLine));

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_LINEINFO);
    AppSetWindowText(htxt, "(%s), '%s', %s, %s",
                     fSource ? (LPSTR)"src" : (LPSTR)"DST",
                     (LPSTR)mxl.szShortName,
                     fActive ? (LPSTR)"ACTIVE" : (LPSTR)"inactive",
                     fDisconnected ? (LPSTR)"DISCONNECTED" : (LPSTR)"connected");

    return (fActive);
} // MixAppLineChangeSwitch()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppInitDialogSwitch
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HWND hwndFocus:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      09/22/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppInitDialogSwitch
(
    HWND                    hwnd,
    HWND                    hwndFocus,
    LPARAM                  lParam
)
{
    #define FCB_DEF_STYLE   (WS_VISIBLE | WS_CHILD | BS_CHECKBOX | WS_TABSTOP)

    static TCHAR        szButton[]  = TEXT("button");
    static TCHAR        szTitle[]   = TEXT("Switch Class: '%s'");

    TCHAR                       szControlType[64];
    LPMACONTROLINSTANCE         pmaci;
    PMACONTROLINSTANCE_SWITCH   pmaci_switch;

    LPMIXERLINE                 pmxl;
    LPMIXERCONTROL              pmxctrl;

    HWND                        htxt;
    UINT                        u;
    UINT                        v;
    HWND                        hcheck;
    RECT                        rcM;
    RECT                        rcU;
    int                         cycap;
    UINT                        cb;
    UINT                        cChannels;
    UINT                        cMultipleItems;
    UINT                        uIndex;

    //
    //
    //
    pmaci = (LPMACONTROLINSTANCE)lParam;
    if (NULL == pmaci)
    {
        DPF(0, "!MixAppInitDialogSwitch: pmaci passed in lParam is NULL!?!");
        return (FALSE);
    }

    pmxl    = pmaci->pmxl;
    pmxctrl = pmaci->pmxctrl;

    if (!MixAppGetControlTypeName(pmxctrl, szControlType))
    {
        return (FALSE);
    }

    //
    //
    //
    switch (pmxctrl->dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_BOOLEAN:
        case MIXERCONTROL_CONTROLTYPE_ONOFF:
        case MIXERCONTROL_CONTROLTYPE_MUTE:
        case MIXERCONTROL_CONTROLTYPE_MONO:
        case MIXERCONTROL_CONTROLTYPE_LOUDNESS:
        case MIXERCONTROL_CONTROLTYPE_STEREOENH:
            break;

        default:
            DPF(0, "!MixAppInitDialogSwitch: pmxctrl->dwControlType=%.08lXh not valid switch!", pmxctrl->dwControlType);
            return (FALSE);
    }

    //
    //
    //
    AppSetWindowText(hwnd, szTitle, (LPTSTR)szControlType);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_SHORT_NAME, pmxctrl->szShortName);
    SetDlgItemText(hwnd, IDD_MACONTROL_TXT_LONG_NAME,  pmxctrl->szName);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_BOUNDS);
    AppSetWindowText(htxt, "dwMinimum=%lu, dwMaximum=%lu",
                     pmxctrl->Bounds.dwMinimum,
                     pmxctrl->Bounds.dwMaximum);

    htxt = GetDlgItem(hwnd, IDD_MACONTROL_TXT_METRICS);
    AppSetWindowText(htxt, "N/A");

    //
    //
    //
    cChannels = (UINT)pmxl->cChannels;
    if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
        cChannels = 1;

    cMultipleItems = 1;
    if (MIXERCONTROL_CONTROLF_MULTIPLE & pmxctrl->fdwControl)
        cMultipleItems = (UINT)pmxctrl->cMultipleItems;

    cb  = sizeof(*pmaci_switch);
    cb += cChannels * cMultipleItems * sizeof(pmaci_switch->pmxcd_f[0]);
    pmaci_switch = (PMACONTROLINSTANCE_SWITCH)LocalAlloc(LPTR, cb);
    if (NULL == pmaci_switch)
    {
        DPF(0, "!MixAppInitDialogSwitch: failed trying to alloc %u bytes for control instance!", cb);
        return (FALSE);
    }

    //
    //
    //
    SetWindowLong(hwnd, DWL_USER, (LPARAM)(UINT)pmaci_switch);

    pmaci_switch->pmaci    = pmaci;

    //
    //
    //
    //
    cycap = GetSystemMetrics(SM_CYCAPTION);

    hcheck = GetDlgItem(hwnd, IDD_MACONTROL_GRP_MULTICHANNEL);
    GetWindowRect(hcheck, &rcM);

    InflateRect(&rcM, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcM.left);
    ScreenToClient(hwnd, (LPPOINT)&rcM.right);

    rcM.bottom = rcM.top + cycap;

    for (u = 0; u < cChannels; u++)
    {
        for (v = 0; v < cMultipleItems; v++)
        {
            uIndex = (u * cMultipleItems) + v;

            hcheck = CreateWindow(szButton, pmxctrl->szShortName, FCB_DEF_STYLE,
                                  rcM.left, rcM.top,
                                  rcM.right - rcM.left,
                                  rcM.bottom - rcM.top,
                                  hwnd, (HMENU)(IDD_MACONTROL_MULTICHANNEL_BASE + uIndex),
                                  ghinst, NULL);

            rcM.top    += cycap + 4;
            rcM.bottom += cycap + 4;
        }

        //
        //  add more separation between channels
        //
        rcM.top    += cycap;
        rcM.bottom += cycap;
    }

    //
    //
    //
    //
    hcheck = GetDlgItem(hwnd, IDD_MACONTROL_GRP_UNIFORM);
    GetWindowRect(hcheck, &rcU);

    InflateRect(&rcU, -10, -20);
    ScreenToClient(hwnd, (LPPOINT)&rcU.left);
    ScreenToClient(hwnd, (LPPOINT)&rcU.right);

    rcU.bottom = rcU.top + cycap;

    for (v = 0; v < cMultipleItems; v++)
    {
        hcheck = CreateWindow(szButton, pmxctrl->szShortName, FCB_DEF_STYLE,
                              rcU.left, rcU.top,
                              rcU.right - rcU.left,
                              rcU.bottom - rcU.top,
                              hwnd, (HMENU)(IDD_MACONTROL_UNIFORM_BASE + v),
                              ghinst, NULL);

        rcU.top    += cycap + 4;
        rcU.bottom += cycap + 4;
    }

    //
    //
    //
    SendMessage(hwnd,
                MM_MIXM_LINE_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxl->dwLineID);

    SendMessage(hwnd,
                MM_MIXM_CONTROL_CHANGE,
                (WPARAM)pmaci->hmx,
                pmxctrl->dwControlID);

    return (TRUE);
} // MixAppInitDialogSwitch()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcControlSwitch
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      07/22/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcControlSwitch
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LRESULT             lr;
    HLOCAL              hl;
    UINT                uId;

    if (WM_COMMAND == uMsg)
    {
        uId = GET_WM_COMMAND_ID(wParam, lParam);
        if (IDD_MACONTROL_GRP_MULTICHANNEL <= uId)
        {
            MixAppControlSetSwitch(hwnd, GET_WM_COMMAND_HWND(wParam, lParam));
            return (TRUE);
        }
    }

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            lr = HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, MixAppInitDialogSwitch);
            if (FALSE == lr)
            {
                EndDialog(hwnd, FALSE);
            }
            else
            {
                ghdlgControl = hwnd;
            }
            return (TRUE);

        case MM_MIXM_LINE_CHANGE:
            MixAppLineChangeSwitch(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case MM_MIXM_CONTROL_CHANGE:
            MixAppControlChangeSwitch(hwnd, (HMIXER)wParam, lParam);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);
            switch (uId)
            {
                case IDCANCEL:
                case IDOK:
                    hl = (HLOCAL)(UINT)GetWindowLong(hwnd, DWL_USER);
                    LocalFree(hl);

                    EndDialog(hwnd, TRUE);
                    ghdlgControl = NULL;
                    break;
            }
            break;
    }

    return (FALSE);
} // MixAppDlgProcControlSwitch()


MATIME.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  matime.c
//
//  Description:
//
//
//  History:
// 9/21/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

//#include <msmixmgr.h>

#include "appport.h"
#include "mixapp.h"

#include "debug.h"


MIXAPP.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mixapp.c
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <stdarg.h>

#include "mixapp.h"

#include "debug.h"

//
//  globals, no less
//
HINSTANCE   ghinst;
UINT        gfuAppOptions;

TCHAR       gszNull[]           = TEXT("");
TCHAR       gszCRLF[]           = TEXT("\r\n");

TCHAR       gszAppName[APP_MAX_APP_NAME_CHARS];

//
//
//
PTABBEDLISTBOX      gptlbLines;
HMIXER              ghmx;

BOOL                gfDisplayingControl     = FALSE;
DWORD               gdwControlID;
HWND                ghdlgControl;
MIXERLINE           gmxl;
MIXERCONTROL        gmxctrl;

//==========================================================================;
//
//  Application helper functions
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  void AppHourGlass
//
//  Description:
//      This function changes the cursor to that of the hour glass or
//      back to the previous cursor.
//
//      This function can be called recursively.
//
//  Arguments:
//      BOOL fHourGlass: TRUE if we need the hour glass.  FALSE if we need
//      the arrow back.
//
//  Return (void):
//      On return, the cursor will be what was requested.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

void FNGLOBAL AppHourGlass
(
    BOOL                    fHourGlass
)
{
    static HCURSOR  hcur;
    static UINT     uWaiting = 0;

    if (fHourGlass)
    {
        if (!uWaiting)
        {
            hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
            ShowCursor(TRUE);
        }

        uWaiting++;
    }
    else
    {
        --uWaiting;

        if (!uWaiting)
        {
            ShowCursor(FALSE);
            SetCursor(hcur);
        }
    }
} // AppHourGlass()

//--------------------------------------------------------------------------;
//
//  int AppMsgBox
//
//  Description:
//      This function displays a message for the application in a standard
//      message box.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      PTSTR pszFormat: Format string used for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() function.
//
//  History:
//       2/13/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBox
(
    HWND                    hwnd,
    UINT                    fuStyle,
    PTSTR                  pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the message..
    //
    va_start(va, pszFormat);
    wvsprintf(ach, pszFormat, va);
    va_end(va);

    n = MessageBox(hwnd, ach, gszAppName, fuStyle);

    return (n);
} // AppMsgBox()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowText
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      PTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowText
(
    HWND                    hwnd,
    PTSTR                  pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the string in the window...
    //
    va_start(va, pszFormat);
    n = wvsprintf(ach, pszFormat, va);
    va_end(va);

    SetWindowText(hwnd, ach);

    return (n);
} // AppSetWindowText()

//--------------------------------------------------------------------------;
//
//  void MixAppDebugLog
//
//  Description:
//      This function logs information to the debugger if the Debug Log
//      option is set. You can then run DBWin (or something similar)
//      to redirect the output whereever you want. Very useful for debugging
//      ACM drivers.
//
//  Arguments:
//      PTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (void):
//      None.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

void FNCGLOBAL MixAppDebugLog
(
    PTSTR                  pszFormat,
    ...
)
{
    static  TCHAR   szDebugLogSeparator[] = TEXT("=============================================================================\r\n");

    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];

    //
    //  !!! UNICODE !!!
    //
    //
    if (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions))
    {
        if (NULL == pszFormat)
        {
            OutputDebugString(szDebugLogSeparator);
            return;
        }

        //
        //  format and display the string in a message box...
        //
        va_start(va, pszFormat);
        wvsprintf(ach, pszFormat, va);
        va_end(va);

        OutputDebugString(ach);
    }
} // MixAppDebugLog()

//--------------------------------------------------------------------------;
//
//  int MEditPrintF
//
//  Description:
//      This function is used to print formatted text into a Multiline
//      Edit Control as if it were a standard console display. This is
//      a very easy way to display small amounts of text information
//      that can be scrolled and copied to the clip-board.
//
//  Arguments:
//      HWND hedit: Handle to a Multiline Edit control.
//
//      PTSTR pszFormat: Pointer to any valid format for wsprintf. If
//      this argument is NULL, then the Multiline Edit Control is cleared
//      of all text.
//
//
//  Return (int):
//      Returns the number of characters written into the edit control.
//
//  Notes:
//      The pszFormat string can contain combinations of escapes that
//      modify the default behaviour of this function. Escapes are single
//      character codes placed at the _beginning_ of the format string.
//
//      Current escapes defined are:
//
//      ~   :   Suppresses the default CR/LF added to the end of the
//              printed line. Since the most common use of this function
//              is to output a whole line of text with a CR/LF, that is
//              the default.
//
//      `   :   Suppresses logging to the debug terminal (regardless of
//              the global debug log options flag).
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL MEditPrintF
(
    HWND                    hedit,
    PTSTR                   pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    int         n;
    BOOL        fCRLF;
    BOOL        fDebugLog;

    //
    //  default the escapes
    //
    fCRLF     = TRUE;
    fDebugLog = (0 != (APP_OPTF_DEBUGLOG & gfuAppOptions));

    //
    //  if the pszFormat argument is NULL, then just clear all text in
    //  the edit control..
    //
    if (NULL == pszFormat)
    {
        SetWindowText(hedit, gszNull);

        if (fDebugLog)
            MixAppDebugLog(NULL);

        return (0);
    }

    //
    //  format and display the string in the window... first search for
    //  escapes to modify default behaviour.
    //
    for (;;)
    {
        switch (*pszFormat)
        {
            case '~':
                fCRLF = FALSE;
                pszFormat++;
                continue;

            case '`':
                fDebugLog = FALSE;
                pszFormat++;
                continue;
        }

        break;
    }

    va_start(va, pszFormat);
    n = wvsprintf(ach, pszFormat, va);
    va_end(va);

    Edit_SetSel(hedit, (WPARAM)-1, (LPARAM)-1);
    Edit_ReplaceSel(hedit, ach);

    if (fDebugLog)
        MixAppDebugLog(ach);

    if (fCRLF)
    {
        Edit_SetSel(hedit, (WPARAM)-1, (LPARAM)-1);
        Edit_ReplaceSel(hedit, gszCRLF);

        if (fDebugLog)
            MixAppDebugLog(gszCRLF);
    }

    return (n);
} // MEditPrintF()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  HMIXER MixAppNewDevice
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      HMIXER hmxCur:
//
//      UINT uMxId:
//
//  Return (HMIXER):
//
//  History:
//      09/25/93
//
//--------------------------------------------------------------------------;

HMIXER FNGLOBAL MixAppNewDevice
(
    HWND                    hwnd,
    HMIXER                  hmxCur,
    UINT                    uMxId
)
{
    MMRESULT            mmr;
    HMIXER              hmx;
    MIXERCAPS           mxcaps;

    //
    //
    //
    mmr = mixerGetDevCaps(uMxId, &mxcaps, sizeof(mxcaps));
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(NULL, MB_OK | MB_ICONEXCLAMATION,
                "mixerGetDevCaps() failed on uMxId=%u, mmr=%u!",
                uMxId, mmr);
        return (hmxCur);
    }

    //
    //
    //
    mmr = mixerOpen(&hmx, uMxId, (DWORD)(UINT)hwnd, 0L, CALLBACK_WINDOW);
    if (MMSYSERR_NOERROR != mmr)
    {
        AppMsgBox(NULL, MB_OK | MB_ICONEXCLAMATION,
                "mixerOpen() failed on uMxId=%u, mmr=%u!",
                uMxId, mmr);
        return (hmxCur);
    }

    if (NULL != hmxCur)
    {
        mmr = mixerClose(hmxCur);
        if (MMSYSERR_NOERROR != mmr)
        {
            AppMsgBox(hwnd, MB_OK | MB_ICONEXCLAMATION,
                      "mixerClose() failed on hmx=%.04Xh, mmr=%u!",
                      hmxCur, mmr);
        }

        hmxCur = NULL;
    }

    AppSetWindowText(hwnd, "Mixer Device: %s", (LPSTR)mxcaps.szPname);

    return (hmx);
} // MixAppNewDevice()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDisplayDevCaps
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      UINT uMxId:
//
//      LPMIXERCAPS pmxcaps:
//
//  Return (BOOL):
//
//  History:
//      09/25/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MixAppDisplayDevCaps
(
    HWND                    hedit,
    UINT                    uMxId,
    LPMIXERCAPS             pmxcaps
)
{
    static TCHAR    szDisplayTitle[]    = TEXT("[Mixer Device Capabilities]\r\n");

    SetWindowRedraw(hedit, FALSE);

    //
    //
    //
    MEditPrintF(hedit, NULL);
    MEditPrintF(hedit, szDisplayTitle);

    MEditPrintF(hedit, "%18s: %u", (LPTSTR)"Device Id", uMxId);

    MEditPrintF(hedit, "%18s: %u", (LPTSTR)"Manufacturer Id", pmxcaps->wMid);
    MEditPrintF(hedit, "%18s: %u", (LPTSTR)"Product Id", pmxcaps->wPid);
    MEditPrintF(hedit, "%18s: %u.%.02u", (LPTSTR)"Driver Version",
                (pmxcaps->vDriverVersion >> 8),
                (pmxcaps->vDriverVersion & 0x00FF));
    MEditPrintF(hedit, "%18s: '%s'", (LPTSTR)"Device Name", (LPSTR)pmxcaps->szPname);
    MEditPrintF(hedit, "%18s: %.08lXh", (LPTSTR)"Support Flags", pmxcaps->fdwSupport);
    MEditPrintF(hedit, "%18s: %lu", (LPTSTR)"Destinations", pmxcaps->cDestinations);

    SetWindowRedraw(hedit, TRUE);

    return (TRUE);
} // MixAppDisplayDevCaps()

//--------------------------------------------------------------------------;
//
//  BOOL MixAppDlgProcDevice
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      UINT uMsg:
//
//      WPARAM wParam:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      09/25/93
//
//--------------------------------------------------------------------------;

BOOL CALLBACK MixAppDlgProcDevice
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    UINT                cMixerDevs;
    UINT                u;
    UINT                uMxId;
    UINT                uId;
    UINT                uCmd;
    HWND                hcb;
    HWND                hedit;
    MIXERCAPS           mxcaps;

    //
    //
    //
    switch (uMsg)
    {
        case WM_INITDIALOG:
            hedit = GetDlgItem(hwnd, IDD_MADEVICE_EDIT_CAPABILITIES);
            SetWindowFont(hedit, GetStockFont(SYSTEM_FIXED_FONT), FALSE);

            SetWindowLong(hwnd, DWL_USER, lParam);

            uMxId = (UINT)lParam;

            //
            //
            //
            hcb = GetDlgItem(hwnd, IDD_MADEVICE_COMBO_DEVICE);

            cMixerDevs = mixerGetNumDevs();
            for (u = 0; u < cMixerDevs; u++)
            {
                mixerGetDevCaps(u, &mxcaps, sizeof(mxcaps));
                ComboBox_AddString(hcb, mxcaps.szPname);

                if (uMxId == u)
                {
                    hedit = GetDlgItem(hwnd, IDD_MADEVICE_EDIT_CAPABILITIES);
                    MixAppDisplayDevCaps(hedit, uMxId, &mxcaps);
                }
            }

            ComboBox_SetCurSel(hcb, uMxId);
            return (TRUE);

        case WM_COMMAND:
            uId = GET_WM_COMMAND_ID(wParam, lParam);

            switch (uId)
            {
                case IDOK:
                    hcb   = GetDlgItem(hwnd, IDD_MADEVICE_COMBO_DEVICE);
                    uMxId = ComboBox_GetCurSel(hcb);
                    if (CB_ERR != uMxId)
                    {
                        EndDialog(hwnd, uMxId);
                        break;
                    }

                    // -- fall through -- //

                case IDCANCEL:
                    uMxId = (UINT)GetWindowLong(hwnd, DWL_USER);
                    EndDialog(hwnd, uMxId);
                    break;

                case IDD_MADEVICE_COMBO_DEVICE:
                    uCmd = GET_WM_COMMAND_CMD(wParam, lParam);
                    hcb  = GET_WM_COMMAND_HWND(wParam, lParam);
                    switch (uCmd)
                    {
                        case CBN_SELCHANGE:
                            uMxId = ComboBox_GetCurSel(hcb);
                            if (CB_ERR == uMxId)
                                break;

                            mixerGetDevCaps(uMxId, &mxcaps, sizeof(mxcaps));

                            hedit = GetDlgItem(hwnd, IDD_MADEVICE_EDIT_CAPABILITIES);
                            MixAppDisplayDevCaps(hedit, uMxId, &mxcaps);
                            break;
                    }
            }
            return (TRUE);
    }

    return (FALSE);
} // MixAppDlgProcDevice()

//==========================================================================;
//
//  Main application window handling code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT AppInitMenuPopup
//
//  Description:
//      This function handles the WM_INITMENUPOPUP message. This message
//      is sent to the window owning the menu that is going to become
//      active. This gives an application the ability to modify the menu
//      before it is displayed (disable/add items, etc).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      HMENU hmenu: Handle to the menu that is to become active.
//
//      int nItem: Specifies the zero-based relative position of the menu
//      item that invoked the popup menu.
//
//      BOOL fSysMenu: Specifies whether the popup menu is a System menu
//      (TRUE) or it is not a System menu (FALSE).
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppInitMenuPopup
(
    HWND                    hwnd,
    HMENU                   hmenu,
    int                     nItem,
    BOOL                    fSysMenu
)
{
    int                 n;
    BOOL                f;

    DPF(1, "AppInitMenuPopup(hwnd=%Xh, hmenu=%Xh, nItem=%d, fSysMenu=%d)",
            hwnd, hmenu, nItem, fSysMenu);

    //
    //  if the system menu is what got hit, succeed immediately... this
    //  application has no stuff in the system menu.
    //
    if (fSysMenu)
        return (0L);

    //
    //  initialize the menu that is being 'popped up'
    //
    switch (nItem)
    {
        case APP_MENU_ITEM_FILE:
            f = (0 != mixerGetNumDevs());
            EnableMenuItem(hmenu, IDM_FILE_MIXER_DEVICE, (UINT)(f ? MF_ENABLED : MF_GRAYED));

            f = (0 != (gfuAppOptions & APP_OPTF_DEBUGLOG));
            CheckMenuItem(hmenu, IDM_FILE_DEBUG_LOG, (UINT)(f ? MF_CHECKED : MF_UNCHECKED));
            break;

        case APP_MENU_ITEM_VIEW:
            n = ListBox_GetCurSel(gptlbLines->hlb);
            f = (LB_ERR != n);

            EnableMenuItem(hmenu, IDM_VIEW_LINE_INFO,  (UINT)(f ? MF_ENABLED : MF_GRAYED));
            EnableMenuItem(hmenu, IDM_VIEW_LINE_CONTROLS,  (UINT)(f ? MF_ENABLED : MF_GRAYED));
            break;
    }

    //
    //  we processed the message--return 0...
    //
    return (0L);
} // AppInitMenuPopup()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCommand
//
//  Description:
//      This function handles the WM_COMMAND message.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the WM_COMMAND message.
//
//      int nId: Control or menu item identifier.
//
//      HWND hwndCtl: Handle of control if the message is from a control.
//      This argument is NULL if the message was not generated by a control.
//
//      UINT uCode: Notification code. This argument is 1 if the message
//      was generated by an accelerator. If the message is from a menu,
//      this argument is 0.
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppCommand
(
    HWND                    hwnd,
    int                     nId,
    HWND                    hwndCtl,
    UINT                    uCode
)
{
    int                 n;
    UINT                uMxId;
    LRESULT             lr;

    switch (nId)
    {
        case IDM_FILE_MIXER_DEVICE:
            mixerGetID((HMIXEROBJ)ghmx, &uMxId, MIXER_OBJECTF_HMIXER);
            n = DialogBoxParam(ghinst, DLG_MIXAPP_DEVICE, hwnd, (DLGPROC)MixAppDlgProcDevice, uMxId);
            if ((UINT)n != uMxId)
            {
                uMxId = (UINT)n;
                ghmx  = MixAppNewDevice(hwnd, ghmx, uMxId);

                MixAppRefreshLineList(hwnd, gptlbLines);
            }
            break;

        case IDM_FILE_FONT:
            MixAppChooseFont(hwnd);
            break;

        case IDM_FILE_DEBUG_LOG:
            gfuAppOptions ^= APP_OPTF_DEBUGLOG;
            break;

        case IDM_FILE_ABOUT:
            DialogBox(ghinst, DLG_ABOUT, hwnd, (DLGPROC)AboutDlgProc);
            break;

        case IDM_FILE_EXIT:
            FORWARD_WM_CLOSE(hwnd, SendMessage);
            break;

        case IDM_VIEW_LINE_INFO:
            n  = ListBox_GetCurSel(gptlbLines->hlb);
            if (LB_ERR == n)
                break;

            lr = ListBox_GetItemData(gptlbLines->hlb, n);
            DialogBoxParam(ghinst, DLG_LINEINFO, hwnd, (DLGPROC)MixAppDlgProcLineInfo, lr);
            break;

        case IDM_VIEW_LINE_CONTROLS:
            n  = ListBox_GetCurSel(gptlbLines->hlb);
            if (LB_ERR == n)
                break;

            lr = ListBox_GetItemData(gptlbLines->hlb, n);
            DialogBoxParam(ghinst, DLG_LINECONTROLS, hwnd, (DLGPROC)MixAppDlgProcLineControls, lr);
            break;

        case IDM_UPDATE:
            MixAppRefreshLineList(hwnd, gptlbLines);
            break;

        case IDD_APP_LIST_LINES:
            switch (uCode)
            {
                case LBN_SELCHANGE:
                    break;

                case LBN_DBLCLK:
                    n  = ListBox_GetCurSel(hwndCtl);
                    if (LB_ERR == n)
                        break;

                    lr = ListBox_GetItemData(hwndCtl, n);
                    if (GetKeyState(VK_CONTROL) < 0)
                    {
                        DialogBoxParam(ghinst, DLG_LINEINFO, hwnd, (DLGPROC)MixAppDlgProcLineInfo, lr);
                    }
                    else
                    {
                        DialogBoxParam(ghinst, DLG_LINECONTROLS, hwnd, (DLGPROC)MixAppDlgProcLineControls, lr);
                    }
                    break;
            }
            break;
    }

    return (0L);
} // AppCommand()

//--------------------------------------------------------------------------;
//
//  LRESULT AppSize
//
//  Description:
//      This function handles the WM_SIZE message for the application's
//      window. This message is sent to the application window after the
//      size has changed (but before it is painted).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_SIZE message.
//
//      UINT fuSizeType: Specifies the type of resizing requested. This
//      argument is one of the following: SIZE_MAXIMIZED, SIZE_MINIMIZED,
//      SIZE_RESTORED, SIZE_MAXHIDE, or SIZE_MAXSHOW.
//
//      int nWidth: Width of the new client area for the window.
//
//      int nHeight: Height of the new client area for the window.
//
//  Return (LRESULT):
//      Returns zero if the application processes the message.
//
//  History:
//       2/ 5/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppSize
(
    HWND                    hwnd,
    UINT                    fuSizeType,
    int                     nWidth,
    int                     nHeight
)
{
    RECT                rc;

    DPF(1, "AppSize(hwnd=%Xh, fuSizeType=%u, nWidth=%d, nHeight=%d)",
            hwnd, fuSizeType, nWidth, nHeight);

    //
    //  unless this application is the one being resized then don't waste
    //  time computing stuff that doesn't matter. this applies to being
    //  minimized also because this application does not have a custom
    //  minimized state.
    //
    if ((SIZE_RESTORED != fuSizeType) && (SIZE_MAXIMIZED != fuSizeType))
        return (0L);

    //
    //  size the devices listbox to be the total size of the client area--
    //  inflate the rect by one so borders are not visible. note that
    //  we need to leave room at the top for the title text which is one
    //  line of text in height...
    //
    GetClientRect(hwnd, &rc);
    InflateRect(&rc, 1, 1);

    TlbMove(gptlbLines, &rc, FALSE);

    //
    //  we processed the message..
    //
    return (0L);
} // AppSize()

//--------------------------------------------------------------------------;
//
//  LRESULT AppPaint
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//  Return (LRESULT):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppPaint
(
    HWND                    hwnd
)
{
    PAINTSTRUCT         ps;

    //
    //
    //
    BeginPaint(hwnd, &ps);

    TlbPaint(gptlbLines, hwnd, ps.hdc);

    EndPaint(hwnd, &ps);

    //
    //  we processed the message
    //
    return (0L);
} // AppPaint()

//--------------------------------------------------------------------------;
//
//  LRESULT AppWndProc
//
//  Description:
//      This is the main application window procedure.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (LRESULT):
//      The return value depends on the message that is being processed.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT CALLBACK AppWndProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
)
{
    LRESULT             lr;

    switch (uMsg)
    {
        case WM_CREATE:
            lr = HANDLE_WM_CREATE(hwnd, wParam, lParam, AppCreate);
            return (lr);

        case WM_INITMENUPOPUP:
            HANDLE_WM_INITMENUPOPUP(hwnd, wParam, lParam, AppInitMenuPopup);
            return (0L);

        case WM_COMMAND:
            lr = HANDLE_WM_COMMAND(hwnd, wParam, lParam, AppCommand);
            return (lr);

        case WM_SIZE:
            //
            //  handle what we want for sizing, and then always call the
            //  default handler...
            //
            HANDLE_WM_SIZE(hwnd, wParam, lParam, AppSize);
            break;

        case WM_PAINT:
            HANDLE_WM_PAINT(hwnd, wParam, lParam, AppPaint);
            break;

        case WM_QUERYENDSESSION:
            lr = HANDLE_WM_QUERYENDSESSION(hwnd, wParam, lParam, AppQueryEndSession);
            return (lr);

        case WM_ENDSESSION:
            HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, AppEndSession);
            return (0L);

        case WM_CLOSE:
            HANDLE_WM_CLOSE(hwnd, wParam, lParam, AppClose);
            return (0L);

        case WM_DESTROY:
            PostQuitMessage(0);
            return (0L);

        case MM_MIXM_LINE_CHANGE:
            lr = MixAppLineChange(hwnd, (HMIXER)wParam, lParam);
            return (lr);

        case MM_MIXM_CONTROL_CHANGE:
            lr = MixAppControlChange(hwnd, (HMIXER)wParam, lParam);
            return (lr);
    }

    return (DefWindowProc(hwnd, uMsg, wParam, lParam));
} // AppWndProc()

//==========================================================================;
//
//  Main entry and message dispatching code
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int WinMain
//
//  Description:
//      This function is called by the system as the initial entry point
//      for a Windows application.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPSTR pszCmdLine: Points to null-terminated unparsed command line.

//      This string is strictly ANSI regardless of whether the application
//      is built for Unicode. To get the Unicode equivalent call the
//      GetCommandLine() function (Win 32 only).
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (int):
//      Returns result from WM_QUIT message (in wParam of MSG structure) if
//      the application is able to enter its message loop. Returns 0 if
//      the application is not able to enter its message loop.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

int PASCAL WinMain
(
    HINSTANCE               hinst,
    HINSTANCE               hinstPrev,
    LPSTR                   pszCmdLine,
    int                     nCmdShow
)
{
    int                 nResult;
    HWND                hwnd;
    MSG                 msg;
    HACCEL              haccl;

    // * * * temp  bodge * * *
    {
      HKEY htemp;
      RegOpenKey(HKEY_LOCAL_MACHINE, "A Value That Does Not Exist", &htemp);
    }
    // * * * * * * * * * * * *

    //
    //  our documentation states that WinMain is supposed to return 0 if
    //  we do not enter our message loop--so assume the worst...
    //
    nResult = 0;

    //
    //  make our instance handle global for convenience..
    //
    ghinst = hinst;

    //
    //  init some stuff, create window, etc.. note the explicit cast of
    //  pszCmdLine--this is to mute a warning (and an ugly ifdef) when
    //  compiling for Unicode. see AppInit() for more details.
    //
    hwnd = AppInit(hinst, hinstPrev, (LPTSTR)pszCmdLine, nCmdShow);
    if (hwnd)
    {
        haccl = LoadAccelerators(hinst, ACCEL_APP);

        //
        //  dispatch messages
        //
        while (GetMessage(&msg, NULL, 0, 0))
        {
            //
            //  do all the special stuff required for this application
            //  when dispatching messages..
            //
            if (!TranslateAccelerator(hwnd, haccl, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        //
        //  return result of WM_QUIT message.
        //
        nResult = (int)msg.wParam;
    }

    //
    //  shut things down, clean up, etc.
    //
    nResult = AppExit(hinst, nResult);

    return (nResult);
} // WinMain()


MIXAPP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mixapp.h
//
//==========================================================================;

#ifndef _INC_MIXAPP
#define _INC_MIXAPP                 // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

//
//Some useful stuff to clarify the code a bit
//
#ifndef FNLOCAL
    #ifdef DEBUG
        #define FNLOCAL     _stdcall
    #else
        #define FNLOCAL     static _stdcall
    #endif
    #define FNGLOBAL    _stdcall
    #define FNCGLOBAL   _cdecl
#endif

//
//  for compiling Unicode
//
#ifdef UNICODE
    #define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
#else
    #define SIZEOF(x)   sizeof(x)
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Application Version Information:
//
//
//
//
//  NOTE! all string resources that will be used in app.rcv for the
//  version resource information *MUST* have an explicit \0 terminator!
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define APP_VERSION_MAJOR           4
#define APP_VERSION_MINOR           0
#define APP_VERSION_BUILD           0
#ifdef UNICODE
#define APP_VERSION_STRING_RC       "Version 4.00 (Unicode Enabled)\0"
#else
#define APP_VERSION_STRING_RC       "Version 4.00\0"
#endif

#define APP_VERSION_NAME_RC         "mixapp32.exe\0"
#define APP_VERSION_BYLINE_RC       "\0"
#define APP_VERSION_COMPANYNAME_RC  "Microsoft Corporation\0"
#define APP_VERSION_COPYRIGHT_RC    "Copyright 1985 - 1998 Microsoft Corporation\0"

#if (defined(_X86_)) || (defined(i386))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows Sample Application (i386)\0"
#endif
#if (defined(_MIPS_)) || (defined(MIPS))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows Sample Application (MIPS)\0"
#endif
#if (defined(_ALPHA_)) || (defined(ALPHA))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows Sample Application (Alpha)\0"
#endif
#ifndef APP_VERSION_PRODUCTNAME_RC
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows Sample Application\0"
#endif

#ifdef DEBUG
#define APP_VERSION_DESCRIPTION_RC  "Microsoft Audio Mixer Manager Sample Application (debug)\0"
#else
#define APP_VERSION_DESCRIPTION_RC  "Microsoft Audio Mixer Manager Sample Application\0"
#endif

//
//  Unicode versions (if UNICODE is defined)... the resource compiler
//  cannot deal with the TEXT() macro.
//
#define APP_VERSION_STRING          TEXT(APP_VERSION_STRING_RC)
#define APP_VERSION_NAME            TEXT(APP_VERSION_NAME_RC)
#define APP_VERSION_BYLINE          TEXT(APP_VERSION_BYLINE_RC)
#define APP_VERSION_COMPANYNAME     TEXT(APP_VERSION_COMPANYNAME_RC)
#define APP_VERSION_COPYRIGHT       TEXT(APP_VERSION_COPYRIGHT_RC)
#define APP_VERSION_PRODUCTNAME     TEXT(APP_VERSION_PRODUCTNAME_RC)
#define APP_VERSION_DESCRIPTION     TEXT(APP_VERSION_DESCRIPTION_RC)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc defines for misc sizes and things...
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  bilingual. this allows the same identifier to be used in resource files
//  and code without having to decorate the id in your code.
//
#ifdef RC_INVOKED
    #define RCID(id)    id
#else
    #define RCID(id)    MAKEINTRESOURCE(id)
#endif

//
//  misc. defines
//
#define APP_MAX_APP_NAME_CHARS          30
#define APP_MAX_APP_NAME_BYTES          (APP_MAX_APP_NAME_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_RC_CHARS         512
#define APP_MAX_STRING_RC_BYTES         (APP_MAX_STRING_RC_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_ERROR_CHARS      512
#define APP_MAX_STRING_ERROR_BYTES      (APP_MAX_STRING_ERROR_CHARS * sizeof(TCHAR))

#define APP_WINDOW_XOFFSET              CW_USEDEFAULT
#define APP_WINDOW_YOFFSET              CW_USEDEFAULT
#define APP_WINDOW_WIDTH                500 //CW_USEDEFAULT
#define APP_WINDOW_HEIGHT               300 //CW_USEDEFAULT

//
//  resource defines...
//
#define ICON_APP                        RCID(10)
#define ACCEL_APP                       RCID(15)

//
//  the application menu...
//
//
#define MENU_APP                        RCID(20)
#define APP_MENU_ITEM_FILE              0
#define IDM_FILE_MIXER_DEVICE           1000
#define IDM_FILE_FONT                   1001
#define IDM_FILE_DEBUG_LOG              1005
#define IDM_FILE_ABOUT                  1009
#define IDM_FILE_EXIT                   1010

#define APP_MENU_ITEM_VIEW              1
#define IDM_VIEW_LINE_INFO              1050
#define IDM_VIEW_LINE_CONTROLS          1051

#define IDM_UPDATE                      1100

//
//  the main window control id's...
//
#define IDD_APP_LIST_LINES                  100

//
//  misc dlg boxes...
//
#define DLG_ABOUT                           RCID(50)
#define IDD_ABOUT_VERSION_OS                100
#define IDD_ABOUT_VERSION_PLATFORM          101

#define IDD_ABOUT_VERSION_MMSYSTEM          150

#define DLG_MIXAPP_DEVICE                   RCID(51)
#define IDD_MADEVICE_COMBO_DEVICE           100
#define IDD_MADEVICE_EDIT_CAPABILITIES      101

#define DLG_LINEINFO                        RCID(55)
#define IDD_LINEINFO_EDIT_INFO              100

#define DLG_LINECONTROLS                    RCID(60)
#define IDD_LINECONTROLS_STATIC_POSITION    100
#define IDD_LINECONTROLS_LIST_CONTROLS      101
#define IDD_LINECONTROLS_BTN_INFO           102
#define IDD_LINECONTROLS_BTN_SETTINGS       103

#define DLG_CONTROLINFO                     RCID(61)
#define IDD_CONTROLINFO_EDIT_INFO           100

//
//  mainit.c
//
//
//
BOOL CALLBACK AboutDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL FNGLOBAL MixAppChooseFont
(
    HWND            hwnd
);

LRESULT FNGLOBAL AppCreate
(
    HWND            hwnd,
    LPCREATESTRUCT  pcs
);

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND            hwnd
);

LRESULT FNGLOBAL AppEndSession
(
    HWND            hwnd,
    BOOL            fEndSession
);

LRESULT FNGLOBAL AppClose
(
    HWND            hwnd
);

HWND FNGLOBAL AppInit
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPTSTR          pszCmdLine,
    int             nCmdShow
);

int FNGLOBAL AppExit
(
    HINSTANCE               hinst,
    int                     nResult
);

//
//  macntrls.c
//
//
//
BOOL CALLBACK MixAppDlgProcLineControls
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

LRESULT FNGLOBAL MixAppControlChange
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwControlID
);

//
//  mixapp.c
//
//
//
LRESULT CALLBACK AppWndProc
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  string resources
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define IDS_APP_NAME                100

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Public function prototypes
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

void FNGLOBAL AppHourGlass
(
    BOOL                    fHourGlass
);
int FNCGLOBAL AppMsgBox
(
    HWND                    hwnd,
    UINT                    fuStyle,
    PTSTR                   pszFormat,
    ...
);
int FNCGLOBAL AppSetWindowText
(
    HWND                    hwnd,
    PTSTR                   pszFormat,
    ...
);
void FNCGLOBAL MixAppDebugLog
(
    PTSTR                   pszFormat,
    ...
);
//
//
//
//
int FNCGLOBAL MEditPrintF
(
    HWND            hedit,
    PTSTR           pszFormat,
    ...
);

HMIXER FNGLOBAL MixAppNewDevice
(
    HWND                    hwnd,
    HMIXER                  hmxCur,
    UINT                    uMxId
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  global variables, etc.
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define APP_OPTF_DEBUGLOG       0x0004

extern HINSTANCE    ghinst;
extern UINT         gfuAppOptions;

extern TCHAR        gszNull[];
extern TCHAR        gszCRLF[];

extern TCHAR        gszAppName[APP_MAX_APP_NAME_CHARS];

//
//
//
#include "tlb.h"

extern PTABBEDLISTBOX   gptlbLines;

extern HMIXER           ghmx;

extern BOOL             gfDisplayingControl;
extern DWORD            gdwControlID;
extern HWND             ghdlgControl;
extern MIXERLINE        gmxl;
extern MIXERCONTROL     gmxctrl;

BOOL CALLBACK MixAppDlgProcControlList
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

//
//  malines.c
//
//
//
BOOL CALLBACK MixAppDlgProcLineInfo
(
    HWND                    hwnd,
    UINT                    uMsg,
    WPARAM                  wParam,
    LPARAM                  lParam
);

BOOL FNGLOBAL MixAppRefreshLineList
(
    HWND                    hwnd,
    PTABBEDLISTBOX          ptlb
);

LRESULT FNGLOBAL MixAppLineChange
(
    HWND                    hwnd,
    HMIXER                  hmx,
    DWORD                   dwLineID
);

BOOL CALLBACK MixAppDlgProcControlMeter
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL CALLBACK MixAppDlgProcControlSwitch
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL CALLBACK MixAppDlgProcControlFader
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL CALLBACK MixAppDlgProcDevice
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
);

BOOL FNGLOBAL MixAppGetControlTypeName
(
    LPMIXERCONTROL          pmxctrl,
    LPTSTR                  szControlType
);

//
//
//
//
#define DLG_MIXAPP_CONTROL              RCID(42)
#define IDD_MACONTROL_GRP_MULTICHANNEL  100
#define IDD_MACONTROL_GRP_UNIFORM       101
#define IDD_MACONTROL_TXT_SHORT_NAME    102
#define IDD_MACONTROL_TXT_LONG_NAME     103
#define IDD_MACONTROL_TXT_VALUE         104
#define IDD_MACONTROL_TXT_BOUNDS        105
#define IDD_MACONTROL_TXT_METRICS       106
#define IDD_MACONTROL_TXT_LINEINFO      107

#define IDD_MACONTROL_MULTICHANNEL_BASE 200
#define IDD_MACONTROL_UNIFORM_BASE      300

//
//
//
typedef struct tMACONTROLINSTANCE
{
    HMIXER                      hmx;
    LPMIXERLINE                 pmxl;
    LPMIXERCONTROL              pmxctrl;

} MACONTROLINSTANCE, FAR *LPMACONTROLINSTANCE;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_APPPORT


MULDIV32.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  muldiv32.h
//
//  Description:
//      math routines for 32 bit signed and unsiged numbers.
//
//      MulDiv32(a,b,c) = (a * b) / c         (round down, signed)
//
//      MulDivRD(a,b,c) = (a * b) / c         (round down, unsigned)
//      MulDivRN(a,b,c) = (a * b + c/2) / c   (round nearest, unsigned)
//      MulDivRU(a,b,c) = (a * b + c-1) / c   (round up, unsigned)
//
//==========================================================================;

#ifndef _INC_MULDIV32
#define _INC_MULDIV32

#ifndef INLINE
#define INLINE __inline
#endif

#ifdef _X86_

    //
    //  Use 32-bit x86 assembly.
    //

#pragma warning(disable:4035 4704)
#pragma optimize( "", off )// Doesn't work with /Ox; MSVC20 messes up
// the inline asm code, instead of including
// it prolog/verbatim/epilog.
#define MulDivRN MulDiv// So use the Windows function for now
    INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     imul    ebx              //  imul ebx
        _asm     idiv    ecx              //  idiv ecx
        _asm shld edx, eax, 16     //  shld edx, eax, 16

    } // MulDiv32()

    #if 0
    INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     mov     ebx,ecx          //  mov  ebx,ecx
        _asm     shr     ebx,1            //  sar  ebx,1
        _asm     add     eax,ebx          //  add  eax,ebx
        _asm     adc     edx,0            //  adc  edx,0
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDiv32()
#endif

    INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     mov     ebx,ecx          //  mov  ebx,ecx
        _asm     dec     ebx              //  dec  ebx
        _asm     add     eax,ebx          //  add  eax,ebx
        _asm     adc     edx,0            //  adc  edx,0
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDivRU32()

    INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDivRD32()

    #pragma warning(default:4035 4704)
#pragma optimize( "", on )

#else

    //
    //  Use C9 __int64 support for Daytona RISC platforms.
    //

    INLINE LONG MulDiv32( LONG a, LONG b, LONG c )
    {
        return (LONG)( Int32x32To64(a,b) / c );
    }

    INLINE DWORD MulDivRD( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( UInt32x32To64(a,b) / c );
    }

    INLINE DWORD MulDivRN( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( (UInt32x32To64(a,b)+c/2) / c );
    }

    INLINE DWORD MulDivRU( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( (UInt32x32To64(a,b)+c-1) / c );
    }

#endif

//
//  some code references these by other names.
//
#define muldiv32    MulDivRN
#define muldivrd32  MulDivRD
#define muldivru32  MulDivRU

#endif  // _INC_MULDIV32


APPPORT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  appport.h -- Win 16/32 Portability Stuff
//
//  Description:
//      This file contains common macros to help with writing code that
//      cross compiles between Win 32 and Win 16. This file should be
//      included _after_ windows.h and windowsx.h.
//
//  History:
//11/ 8/92
//
//==========================================================================;

#ifndef _INC_APPPORT
#define _INC_APPPORT                // #defined if file has been included

#ifndef RC_INVOKED
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 32
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef WIN32
    #ifndef FNLOCAL
    #ifdef DEBUG
        #define FNLOCAL     _stdcall
        #define FNCLOCAL    _cdecl
    #else
        #define FNLOCAL     static _stdcall
        #define FNCLOCAL    static _cdecl
    #endif
        #define FNGLOBAL    _stdcall
        #define FNCGLOBAL   _cdecl
        #define FNWCALLBACK CALLBACK
        #define FNEXPORT    CALLBACK
    #endif

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)    \
        ((DWORD)SendMessage((hwndCtl), EM_GETSEL, (WPARAM)pnS, (LPARAM)pnE))

    #define HUGE

    //
    //  for compiling Unicode
    //
    #ifdef UNICODE
        #define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)   sizeof(x)
    #endif

    #define BCODE
    #define BDATA
    #define BSTACK

    //
    //  win32 apps [usually] don't have to worry about 'huge' data
    //
    #ifndef hmemcpy
    #define hmemcpymemcpy
    #endif
#endif // #ifdef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 16
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef WIN32
    #ifndef FNLOCAL
    #ifdef DEBUG
        #define FNLOCAL     NEAR PASCAL
        #define FNCLOCAL    NEAR _cdecl
    #else
        #define FNLOCAL     static NEAR PASCAL
        #define FNCLOCAL    static NEAR _cdecl
    #endif
        #define FNGLOBAL    FAR PASCAL
        #define FNCGLOBAL   FAR _cdecl
    #ifdef _WINDLL
        #define FNWCALLBACK FAR PASCAL _loadds
        #define FNEXPORT    FAR PASCAL _export
    #else
        #define FNWCALLBACK FAR PASCAL _export
        #define FNEXPORT    FAR PASCAL _export
    #endif
    #endif

    //
    //
    //
    //
    #ifndef FIELD_OFFSET
    #define FIELD_OFFSET(type, field)    ((LONG)&(((type *)0)->field))
    #endif

    //
    //  based code makes since only in win 16 (to try and keep stuff out of
    //  our fixed data segment...
    //
    #define BCODE           _based(_segname("_CODE"))
    #define BDATA           _based(_segname("_DATA"))
    #define BSTACK          _based(_segname("_STACK"))

    #define HUGE            _huge

    #define UNALIGNED

    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif

    #ifndef _MMRESULT_
    #define _MMRESULT_
    typedef UINT        MMRESULT;
    #endif

    #define TEXT(a)         a
    #define SIZEOF(x)       sizeof(x)

    //
    //
    //
    #define CharNext        AnsiNext
    #define CharPrev        AnsiPrev

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)                        \
    {                                                               \
        DWORD   dw;                                                 \
        dw = (DWORD)SendMessage((hwndCtl), EM_GETSEL, 0, 0L);       \
        *pnE = (int)HIWORD(dw);                                     \
        *pnS = (int)LOWORD(dw);                                     \
    }

    //
    //  common message cracker macros available in windowx.h on NT--these
    //  should be added to the Win 16 windowsx.h and probably will be
    //  in the future.
    //
    //  there is a windowsx.h16 that ships with the NT PDK that defines
    //  these macros. so if that version is being used, don't redefine
    //  message crackers.
    //

#ifndef WM_CTLCOLORMSGBOX
    #define WM_CTLCOLORMSGBOX           0x0132
    #define WM_CTLCOLOREDIT             0x0133
    #define WM_CTLCOLORLISTBOX          0x0134
    #define WM_CTLCOLORBTN              0x0135
    #define WM_CTLCOLORDLG              0x0136
    #define WM_CTLCOLORSCROLLBAR        0x0137
    #define WM_CTLCOLORSTATIC           0x0138
#endif

#ifndef GET_WM_ACTIVATE_STATE
    #define GET_WM_ACTIVATE_STATE(wp, lp)           (wp)
    #define GET_WM_ACTIVATE_FMINIMIZED(wp, lp)      (BOOL)HIWORD(lp)
    #define GET_WM_ACTIVATE_HWND(wp, lp)            (HWND)LOWORD(lp)
    #define GET_WM_ACTIVATE_MPS(s, fmin, hwnd)      (WPARAM)(s), MAKELONG(hwnd, fmin)

    #define GET_WM_CHARTOITEM_CHAR(wp, lp)          (CHAR)(wp)
    #define GET_WM_CHARTOITEM_POS(wp, lp)           HIWORD(lp)
    #define GET_WM_CHARTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_CHARTOITEM_MPS(ch, pos, hwnd)    (WPARAM)(ch), MAKELONG(hwnd, pos)

    #define GET_WM_COMMAND_ID(wp, lp)               (wp)
    #define GET_WM_COMMAND_HWND(wp, lp)             (HWND)LOWORD(lp)
    #define GET_WM_COMMAND_CMD(wp, lp)              HIWORD(lp)
    #define GET_WM_COMMAND_MPS(id, hwnd, cmd)       (WPARAM)(id), MAKELONG(hwnd, cmd)

    #define GET_WM_CTLCOLOR_HDC(wp, lp, msg)        (HDC)(wp)
    #define GET_WM_CTLCOLOR_HWND(wp, lp, msg)       (HWND)LOWORD(lp)
    #define GET_WM_CTLCOLOR_TYPE(wp, lp, msg)       HIWORD(lp)
    #define GET_WM_CTLCOLOR_MPS(hdc, hwnd, type)    (WPARAM)(hdc), MAKELONG(hwnd, type)

    #define GET_WM_MENUSELECT_CMD(wp, lp)           (wp)
    #define GET_WM_MENUSELECT_FLAGS(wp, lp)         LOWORD(lp)
    #define GET_WM_MENUSELECT_HMENU(wp, lp)         (HMENU)HIWORD(lp)
    #define GET_WM_MENUSELECT_MPS(cmd, f, hmenu)    (WPARAM)(cmd), MAKELONG(f, hmenu)

    // Note: the following are for interpreting MDIclient to MDI child messages.
    #define GET_WM_MDIACTIVATE_FACTIVATE(hwnd, wp, lp)  (BOOL)(wp)
    #define GET_WM_MDIACTIVATE_HWNDDEACT(wp, lp)        (HWND)HIWORD(lp)
    #define GET_WM_MDIACTIVATE_HWNDACTIVATE(wp, lp)     (HWND)LOWORD(lp)

    // Note: the following is for sending to the MDI client window.
    #define GET_WM_MDIACTIVATE_MPS(f, hwndD, hwndA) (WPARAM)(hwndA), 0

    #define GET_WM_MDISETMENU_MPS(hmenuF, hmenuW)   0, MAKELONG(hmenuF, hmenuW)

    #define GET_WM_MENUCHAR_CHAR(wp, lp)            (CHAR)(wp)
    #define GET_WM_MENUCHAR_HMENU(wp, lp)           (HMENU)LOWORD(lp)
    #define GET_WM_MENUCHAR_FMENU(wp, lp)           (BOOL)HIWORD(lp)
    #define GET_WM_MENUCHAR_MPS(ch, hmenu, f)       (WPARAM)(ch), MAKELONG(hmenu, f)

    #define GET_WM_PARENTNOTIFY_MSG(wp, lp)         (wp)
    #define GET_WM_PARENTNOTIFY_ID(wp, lp)          HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_HWNDCHILD(wp, lp)   (HWND)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_X(wp, lp)           (INT)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_Y(wp, lp)           (INT)HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_MPS(msg, id, hwnd)  (WPARAM)(msg), MAKELONG(hwnd, id)
    #define GET_WM_PARENTNOTIFY2_MPS(msg, x, y)     (WPARAM)(msg), MAKELONG(x, y)

    #define GET_WM_VKEYTOITEM_CODE(wp, lp)          (wp)
    #define GET_WM_VKEYTOITEM_ITEM(wp, lp)          (INT)HIWORD(lp)
    #define GET_WM_VKEYTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_VKEYTOITEM_MPS(code, item, hwnd) (WPARAM)(code), MAKELONG(hwnd, item)

    #define GET_EM_SETSEL_START(wp, lp)             LOWORD(lp)
    #define GET_EM_SETSEL_END(wp, lp)               HIWORD(lp)
    #define GET_EM_SETSEL_MPS(iStart, iEnd)         0, MAKELONG(iStart, iEnd)

    #define GET_EM_LINESCROLL_MPS(vert, horz)       0, MAKELONG(vert, horz)

    #define GET_WM_CHANGECBCHAIN_HWNDNEXT(wp, lp)   (HWND)LOWORD(lp)

    #define GET_WM_HSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_HSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_HSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_HSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)

    #define GET_WM_VSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_VSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_VSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_VSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)
#endif

#endif // #ifndef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_APPPORT


TLB.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  tlb.c
//
//  Description:
//
//
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include <memory.h>

#include "tlb.h"

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int GetRealTextMetrics
//
//  Description:
//      This function gets the textmetrics of the font currently selected
//      into the hdc.  It returns the average char width as the return value.
//
//      This function computes the average character width correctly by
//      using GetTextExtent() on the string "abc...xzyABC...XYZ" which works
//      out much better for proportional fonts. This is also necessary
//      for correct alignment between dialog and client units.
//
//      Note that this function returns the same TEXTMETRIC values that
//      GetTextMetrics() does, it simply has a different return value.
//
//  Arguments:
//      HDC hdc:
//
//      LPTEXTMETRIC ptm:
//
//  Return (int):
//
//
//--------------------------------------------------------------------------;

int FAR PASCAL GetRealTextMetrics
(
    HDC                     hdc,
    LPTEXTMETRIC            ptm
)
{
    TCHAR               achAlphabet[26 * 2];    // upper and lower case
    SIZE                sSize;
    UINT                u;
    int                 nAveWidth;

    //
    //  get the text metrics of the current font. note that GetTextMetrics
    //  gets the incorrect nAveCharWidth value for proportional fonts.
    //
    GetTextMetrics(hdc, ptm);
    nAveWidth = ptm->tmAveCharWidth;

    //
    //  if it's not a variable pitch font GetTextMetrics was correct
    //  so just return.
    //
    if (ptm->tmPitchAndFamily & FIXED_PITCH)
    {
        //
        //
        //
        for (u = 0; u < 26; u++)
        {
            achAlphabet[u]      = (TCHAR)(u + (UINT)'a');
            achAlphabet[u + 26] = (TCHAR)(u + (UINT)'A');
        }

        //
        //  round up
        //
        GetTextExtentPoint(hdc, achAlphabet, SIZEOF(achAlphabet), &sSize);
        nAveWidth = ((sSize.cx / 26) + 1) / 2;
    }

    //
    //  return the calculated average char width
    //
    return (nAveWidth);
} // GetRealTextMetrics()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL TlbPaint
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      HWND hwnd:
//
//      HDC hdc:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbPaint
(
    PTABBEDLISTBOX          ptlb,
    HWND                    hwnd,
    HDC                     hdc
)
{
    RECT                rc;
    HFONT               hfont;
    COLORREF            crBk;
    COLORREF            crText;
    int                 nHeight;

    //
    //
    //
    hfont = GetWindowFont(ptlb->hlb);
    if (NULL == hfont)
        hfont = GetStockFont(SYSTEM_FONT);

    hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);

    crBk   = SetBkColor(hdc, GetSysColor(COLOR_ACTIVECAPTION));
    crText = SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));

    //
    //  compute bounding rect for title only
    //
    rc = ptlb->rc;
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rc, NULL, 0, NULL);
    TabbedTextOut(hdc, rc.left, rc.top,
                  ptlb->pszTitleText,
                  ptlb->cchTitleText,
                  ptlb->uTabStops,
                  ptlb->panTitleTabs, 0);

    //
    //  restore the dc
    //
    SetBkColor(hdc, crBk);
    SetTextColor(hdc, crText);

    SelectObject(hdc, hfont);

    return (TRUE);
} // TlbPaint()

//--------------------------------------------------------------------------;
//
//  BOOL TlbMove
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      PRECT prc:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbMove
(
    PTABBEDLISTBOX          ptlb,
    PRECT                   prc,
    BOOL                    fRedraw
)
{
    RECT                rc;
    int                 nHeight;
    HWND                hwnd;

    hwnd = GetParent(ptlb->hlb);

    //
    //  invalidate only the region occupied by the current title bar. this
    //  will make sure that area gets repainted. the listbox portion will
    //  be invalidated correctly by the SetWindowPos() function below..
    //
    rc = ptlb->rc;

    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    InvalidateRect(hwnd, &rc, TRUE);

    //
    //  now move the listbox--we modify values in the rect structure, so
    //  copy to local storage
    //
    rc = *prc;

    //
    //  leave room at the top of the bounding rect for the title text
    //
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.top += nHeight;

    SetWindowPos(ptlb->hlb, NULL, rc.left, rc.top, rc.right - rc.left,
                 rc.bottom - rc.top, SWP_NOZORDER);

    //
    //  save the new location and invalidate the area so it is repainted
    //
    ptlb->rc = *prc;
    InvalidateRect(hwnd, prc, TRUE);

    if (fRedraw)
    {
        UpdateWindow(hwnd);
    }

    return (TRUE);
} // TlbMove()

//--------------------------------------------------------------------------;
//
//  BOOL TlbRecalcTabs
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL NEAR PASCAL TlbRecalcTabs
(
    PTABBEDLISTBOX          ptlb
)
{
    static TCHAR szGonzoThing[] = TEXT("M");

    int                 anTabsList[TLB_MAX_TAB_STOPS];
    HDC                 hdc;
    HFONT               hfont;
    TEXTMETRIC          tm;
    int                 nAveCharWidth;

    UINT                u;
    int                 nWidth;
    int                 nPrevTabTitle;
    int                 nPrevTabList;
    SIZE                sSize;

    //
    //
    //
    hdc = GetDC(NULL);
    {
        //
        //  get the average char width and height of the current font so we
        //  can compute tabs correctly. note that GetTextMetrics is pretty
        //  bogus when it comes to the average char width--it normally gives
        //  you the width of the character 'x'. what it should be is the
        //  average width of all capitals and lower case letters..
        //
        hfont = GetWindowFont(ptlb->hlb);
        if (NULL == hfont)
            hfont = GetStockFont(SYSTEM_FONT);

        hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);

#if 0
        GetTextMetrics(hdc, &tm);
        nAveCharWidth = tm.tmAveCharWidth;
#else
        nAveCharWidth = GetRealTextMetrics(hdc, &tm);
#endif
        ptlb->nFontHeight = tm.tmHeight;

        //
        //
        //
        GetTextExtentPoint(hdc, szGonzoThing, 1, &sSize);

        //
        //
        //
        hfont = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);
    }
    ReleaseDC(NULL, hdc);

    //
    //  calculate the width of each column
    //
    nPrevTabTitle = 0;
    nPrevTabList  = 0;
    for (u = 0; u < ptlb->uTabStops; u++)
    {
//      nWidth = nAveCharWidth * ptlb->panTabs[u] + nAveCharWidth * 2;
        nWidth = sSize.cx * ptlb->panTabs[u] + (sSize.cx * 2);

        //
        //  set tabstop for title text--this is in client units
        //  for TabbedTextOut in TlbPaint
        //
        ptlb->panTitleTabs[u] = nPrevTabTitle + nWidth;
        nPrevTabTitle = ptlb->panTitleTabs[u];

        //
        //  set tabstop for listbox--this is in dialog units
        //
        anTabsList[u] = nPrevTabList + MulDiv(nWidth, 4, nAveCharWidth);
        nPrevTabList  = anTabsList[u];
    }

    //
    //  now setup the tabstops in the listbox
    //
    if (ptlb->uTabStops)
    {
#pragma message("WARNING: Workaround for Listbox bug")
// Warning, in the current beta Windows, sending 0 tabstops and non-null
// tablist will cause a page fault.

            ListBox_SetTabStops(ptlb->hlb, ptlb->uTabStops, anTabsList);
    }

    return (TRUE);
} // TlbRecalcTabs()

//--------------------------------------------------------------------------;
//
//  HFONT TlbSetFont
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      HFONT hfont:
//
//      BOOL fRedraw:
//
//  Return (HFONT):
//
//
//--------------------------------------------------------------------------;

HFONT FAR PASCAL TlbSetFont
(
    PTABBEDLISTBOX          ptlb,
    HFONT                   hfont,
    BOOL                    fRedraw
)
{
    HFONT               hfontOld;

    //
    //
    //
    hfontOld = GetWindowFont(ptlb->hlb);
    SetWindowFont(ptlb->hlb, hfont, FALSE);

    TlbRecalcTabs(ptlb);
    TlbMove(ptlb, &ptlb->rc, fRedraw);

    return (hfontOld);
} // TlbSetFont()

//--------------------------------------------------------------------------;
//
//  BOOL TlbSetTitleAndTabs
//
//  Description:
//      This function sets the title text and tab stops for a Tabbed List
//      Box (TLB). The pszTitleFormat specifies the title text for each
//      column along with the tabstop position for each column. The format
//      of this string is as follows:
//
//      <columnname1>\t<tab1>!<columnname2>
//
//      TCHAR   szTlbThings[] = TEXT("Index\t6!Code\t5!Name");
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//      PTSTR pszTitleFormat:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//
//--------------------------------------------------------------------------;

BOOL FAR PASCAL TlbSetTitleAndTabs
(
    PTABBEDLISTBOX          ptlb,
    PTSTR                   pszTitleFormat,
    BOOL                    fRedraw
)
{
    TCHAR               szTitleText[TLB_MAX_TITLE_CHARS];
    int                 anTabs[TLB_MAX_TAB_STOPS];
    PTSTR               pch;
    PTSTR               pchTitleText;
    UINT                uTabStops;
    UINT                cchTitleText;
    HWND                hwnd;

    //
    //  parse the title format counting tab stops and actual size of title
    //  text
    //
    uTabStops    = 0;
    pchTitleText = szTitleText;
    for (pch = pszTitleFormat; '\0' != *pch; )
    {
        TCHAR       ch;

        //
        //  scan to tab
        //
        while ('\0' != (ch = *pch))
        {
            *pchTitleText++ = *pch++;

            if ('\t' == ch)
                break;
        }

        if ('\0' == ch)
            break;

        //
        //  grab the next tab stop value
        //
        anTabs[uTabStops] = atoi(pch);
        uTabStops++;

        //
        //  skip to start of next column name
        //
        while ('!' != *pch++)
            ;
    }

    //
    //  terminate the converted title text
    //
    *pchTitleText = '\0';
    cchTitleText = lstrlen(szTitleText);

    //
    //  free the memory used for the previous tab stops and title text
    //
    if (NULL != ptlb->panTabs)
    {
        LocalFree((HLOCAL)ptlb->panTabs);

        ptlb->uTabStops    = 0;
        ptlb->panTabs      = NULL;
        ptlb->panTitleTabs = NULL;
    }

    if (NULL != ptlb->pszTitleText)
    {
        LocalFree((HLOCAL)ptlb->pszTitleText);

        ptlb->cchTitleText = 0;
        ptlb->pszTitleText = NULL;
    }

    //
    //  allocate new space for tab stops. there are two different tab
    //  arrays:
    //
    //      panTabs: original tab values as passed by caller. these are
    //      virtual tab locations represented as number of characters. we
    //      need to keep these values for recomputing the real tabs when
    //      the font changes.
    //
    //      panTitleTabs: these values are computed by TlbRecalcTabs and
    //      are actual tab positions in client coordinates for the title
    //      text (needed for TabbedTextOut in TlbPaint).
    //
    //  the tabs for the listbox are computed and set in TlbRecalcTabs
    //
    if (0 != uTabStops)
    {
        ptlb->panTabs = (PINT)LocalAlloc(LPTR, (uTabStops * sizeof(int)) * 2);
        if (NULL == ptlb->panTabs)
            return (FALSE);

        ptlb->uTabStops    = uTabStops;
        ptlb->panTitleTabs = ptlb->panTabs + uTabStops;
        memcpy(ptlb->panTabs, anTabs, uTabStops * sizeof(int));
    }

    //
    //  allocate space for the converted title text (stripped of the tab
    //  spacing values). this string is passed directly to TabbedTextOut
    //  in TlbPaint.
    //
    if (0 != cchTitleText)
    {
        ptlb->pszTitleText = (PTSTR)LocalAlloc(LPTR, (cchTitleText + 1) * sizeof(TCHAR));
        if (NULL == ptlb->pszTitleText)
            return (FALSE);

        ptlb->cchTitleText = cchTitleText;
        lstrcpy(ptlb->pszTitleText, szTitleText);
    }

    //
    //
    //
    TlbRecalcTabs(ptlb);

    //
    //  force a complete repaint of the title text and listbox--redraw
    //  immediately if we are supposed to
    //
    hwnd = GetParent(ptlb->hlb);
    InvalidateRect(hwnd, &ptlb->rc, TRUE);
    if (fRedraw)
    {
        UpdateWindow(hwnd);
    }

    return (TRUE);
} // TlbSetTitleAndTabs()

//--------------------------------------------------------------------------;
//
//  PTABBEDLISTBOX TlbDestroy
//
//  Description:
//
//
//  Arguments:
//      PTABBEDLISTBOX ptlb:
//
//  Return (PTABBEDLISTBOX):
//
//
//--------------------------------------------------------------------------;

PTABBEDLISTBOX FAR PASCAL TlbDestroy
(
    PTABBEDLISTBOX          ptlb
)
{
    HWND                hwnd;
    int                 nHeight;

    //
    //  get rid of the listbox
    //
    if (NULL != ptlb->hlb)
    {
        DestroyWindow(ptlb->hlb);

        //
        //  invalidate area where title text was so it will be clean
        //
        nHeight = min(ptlb->nFontHeight, ptlb->rc.bottom - ptlb->rc.top);
        ptlb->rc.bottom = ptlb->rc.top + nHeight;

        hwnd = GetParent(ptlb->hlb);
        InvalidateRect(hwnd, &ptlb->rc, TRUE);

    }

    //
    //  free the memory used for tab stops and title text
    //
    if (NULL != ptlb->panTabs)
        LocalFree((HLOCAL)ptlb->panTabs);

    if (NULL != ptlb->pszTitleText)
        LocalFree((HLOCAL)ptlb->pszTitleText);

    LocalFree((HLOCAL)ptlb);

    return (NULL);
} // TlbDestroy()

//--------------------------------------------------------------------------;
//
//  PTABBEDLISTBOX TlbCreate
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      int nId:
//
//      PRECT prc:
//
//  Return (PTABBEDLISTBOX):
//
//
//--------------------------------------------------------------------------;

PTABBEDLISTBOX FAR PASCAL TlbCreate
(
    HWND                    hwnd,
    int                     nId,
    PRECT                   prc
)
{
    #define TLB_DEF_STYLE   (WS_VISIBLE|WS_CHILD|WS_VSCROLL|WS_BORDER|  \
                             WS_TABSTOP|WS_GROUP|LBS_NOTIFY|            \
                             LBS_NOINTEGRALHEIGHT|LBS_USETABSTOPS)

    static TCHAR    szNull[]    = TEXT("");
    static TCHAR    szListBox[] = TEXT("ListBox");

    PTABBEDLISTBOX      ptlb;
    HINSTANCE           hinst;

    //
    //  create a new instance data structure..
    //
    ptlb = (PTABBEDLISTBOX)LocalAlloc(LPTR, sizeof(*ptlb));
    if (NULL == ptlb)
        return (NULL);

    //
    //  create the listbox
    //
    hinst = GetWindowInstance(hwnd);

    ptlb->hlb = CreateWindow(szListBox, szNull, TLB_DEF_STYLE,
                             0, 0, 0, 0, hwnd, (HMENU)nId, hinst, NULL);
    if (NULL == ptlb->hlb)
    {
        TlbDestroy(ptlb);
        return (NULL);
    }

    TlbRecalcTabs(ptlb);

    if (NULL != prc)
    {
        ptlb->rc = *prc;
        TlbMove(ptlb, prc, FALSE);
    }

    return (ptlb);
} // TlbCreate()


TLB.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  tlb.h
//
//==========================================================================;

#ifndef _INC_TLB
#define _INC_TLB                    // #defined if file has been included

#ifndef RC_INVOKED
#pragma warning(disable:4103)
#pragma pack(1)                     // assume byte packing throughout
#endif

#ifndef EXTERN_C
#ifdef __cplusplus
    #define EXTERN_C extern "C"
#else
    #define EXTERN_C extern
#endif
#endif

#ifdef __cplusplus
extern "C"                          // assume C declarations for C++
{
#endif

//
//  for compiling Unicode
//
#ifndef SIZEOF
#ifdef UNICODE
    #define SIZEOF(x)       (sizeof(x)/sizeof(WCHAR))
#else
    #define SIZEOF(x)       sizeof(x)
#endif
#endif

//
//
//
//
typedef struct tTABBEDLISTBOX
{
    HWND            hlb;

    int             nFontHeight;
    RECT            rc;

    UINT            uTabStops;
    PINT            panTabs;
    PINT            panTitleTabs;

    UINT            cchTitleText;
    PTSTR           pszTitleText;

} TABBEDLISTBOX, *PTABBEDLISTBOX;

#define TLB_MAX_TAB_STOPS           20      // max number of columns
#define TLB_MAX_TITLE_CHARS         512

//
//
//
//
//
BOOL FAR PASCAL TlbPaint
(
    PTABBEDLISTBOX          ptlb,
    HWND                    hwnd,
    HDC                     hdc
);

BOOL FAR PASCAL TlbMove
(
    PTABBEDLISTBOX          ptlb,
    PRECT                   prc,
    BOOL                    fRedraw
);

HFONT FAR PASCAL TlbSetFont
(
    PTABBEDLISTBOX          ptlb,
    HFONT                   hfont,
    BOOL                    fRedraw
);

BOOL FAR PASCAL TlbSetTitleAndTabs
(
    PTABBEDLISTBOX          ptlb,
    PTSTR                   pszTitleFormat,
    BOOL                    fRedraw
);

PTABBEDLISTBOX FAR PASCAL TlbDestroy
(
    PTABBEDLISTBOX          ptlb
);

PTABBEDLISTBOX FAR PASCAL TlbCreate
(
    HWND                    hwnd,
    int                     nId,
    PRECT                   prc
);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef RC_INVOKED
#pragma pack()                      // revert to default packing
#endif

#ifdef __cplusplus
}                                   // end of extern "C" {
#endif

#endif // _INC_TLB


README.TXT

The MMCAPS sample demonstrates the multimedia capabilities of the hardware.


DEBUG.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.c
//
//  Description:
//      This file contains code yanked from several places to provide debug
//      support that works in win 16 and win 32.
//
//  History:
//      11/23/92
//
//==========================================================================;

#ifdef DEBUG

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include "debug.h"

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifdef WIN32
    #include <wchar.h>
#else
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define wvsprintfA          wvsprintf
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
#endif

//
//
//
BOOL    __gfDbgEnabled  = TRUE;     // master enable
UINT    __guDbgLevel    = 0;        // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF(LPSTR szFmt, LPSTR va)
//
//  Description:
//
//
//  Arguments:
//
//  Return (void):
//
//
//  History:
//      11/28/92
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF(LPSTR szFmt, LPSTR va)
{
    char    ach[DEBUG_MAX_LINE_LEN];
    BOOL    fDebugBreak = FALSE;
    BOOL    fPrefix     = TRUE;
    BOOL    fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
switch(*szFmt)
{
    case '!':
fDebugBreak = TRUE;
szFmt++;
continue;

    case '`':
fPrefix = FALSE;
szFmt++;
continue;

    case '~':
fCRLF = FALSE;
szFmt++;
continue;
}

break;
    }

    if (fDebugBreak)
    {
ach[0] = '\007';
ach[1] = '\0';
    }

    if (fPrefix)
lstrcatA(ach, DEBUG_MODULE_NAME ": ");

    wvsprintfA(ach + lstrlenA(ach), szFmt, va);

    if (fCRLF)
lstrcatA(ach, "\r\n");

    OutputDebugStringA(ach);

    if (fDebugBreak)
DebugBreak();
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf(UINT uDbgLevel, LPSTR szFmt, ...)
//
//  Description:
//      dprintf() is called by the DPF macro if DEBUG is defined at compile
//      time.
//
//      The messages will be send to COM1: like any debug message. To
//      enable debug output, add the following to WIN.INI :
//
//      [debug]
//      ICSAMPLE=1
//
//  Arguments:
//
//  Return (void):
//
//
//  History:
//      11/23/92
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
return;

    va_start(va, szFmt);
    DbgVPrintF(szFmt, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable(BOOL fEnable)
//
//  Description:
//
//
//  Arguments:
//
//  Return (BOOL):
//
//
//  History:
//      11/28/92
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable(BOOL fEnable)
{
    BOOL    fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel(UINT uLevel)
//
//  Description:
//
//
//  Arguments:
//
//  Return (UINT):
//
//
//  History:
//      11/24/92
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel(UINT uLevel)
{
    UINT    uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize(void)
//
//  Description:
//
//
//  Arguments:
//
//  Return (UINT):
//
//
//  History:
//      11/24/92
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize(BOOL fEnable)
{
    DbgSetLevel(GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, 0));
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

#endif // #ifdef DEBUG


DEBUG.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  debug.h
//
//  Description:
//
//
//  Notes:
//
//  History:
//
//==========================================================================;

#ifndef _INC_DEBUG
#define _INC_DEBUG
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
//
#define DEBUG_SECTION       "Debug"         // section name for
#define DEBUG_MODULE_NAME   "MMCAPS"        // key name and prefix for output
#define DEBUG_MAX_LINE_LEN  255             // max line length (bytes)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define DPF      dprintf
#else
    #define DbgEnable(x)        FALSE
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #pragma warning(disable:4002)
    #define DPF()
#endif

#ifdef __cplusplus
}
#endif
#endif  // _INC_DEBUG


LOWLEVEL.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  lowlevel.c
//
//  Description:
//
//
//  History:
//       5/16/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

//#include "msmixmgr.h"

#include "appport.h"
#include "mmcaps.h"

#include "debug.h"

//
//
//
TCHAR       gszDeviceFormatTitle[]  = TEXT("Type\t8!Index\t5!Version\t7!Product Name");
TCHAR       gszDeviceFormatList[]   = TEXT("%-9s\t%d\t%u.%.02u\t%-32s");

//
//
//
//
TCHAR       gszBogusCaps[]       = TEXT("????");
TCHAR       gszTimerDevice[]     = TEXT("Internal PC Timer");
TCHAR       gszDefaultMapper[]   = TEXT("Default Mapper");

#define MMCAPS_DEVTYPE_UNKNOWN      0
#define MMCAPS_DEVTYPE_AUXILIARY    1
#define MMCAPS_DEVTYPE_JOYSTICK     2
#define MMCAPS_DEVTYPE_MIDIIN       3
#define MMCAPS_DEVTYPE_MIDIOUT      4
#define MMCAPS_DEVTYPE_MIXER        5
#define MMCAPS_DEVTYPE_TIMER        6
#define MMCAPS_DEVTYPE_WAVEIN       7
#define MMCAPS_DEVTYPE_WAVEOUT      8

PTSTR gaszDeviceType[] =
{
    gszUnknown,
    TEXT("Auxiliary"),
    TEXT("Joystick"),
    TEXT("MIDI In"),
    TEXT("MIDI Out"),
    TEXT("Mixer"),
    TEXT("Timer"),
    TEXT("Wave In"),
    TEXT("Wave Out")
};

//
//  AUXCAPS
//
//
//
PTSTR gaszAuxCapsTechnology[] =
{
    gszNotSpecified,                //
    TEXT("CD-Audio"),               // AUXCAPS_CDAUDIO
    TEXT("Auxiliary Input")         // AUXCAPS_AUXIN
};

#define AUXCAPS_TECHNOLOGY_LAST     AUXCAPS_AUXIN

PTSTR gaszAuxCapsSupport[32] =
{
    TEXT("Volume"),         // Bit 0    AUXCAPS_VOLUME
    TEXT("L/R Volume"),     // Bit 1    AUXCAPS_LRVOLUME
    NULL,                   // Bit 2
    NULL,                   // Bit 3
    NULL,                   // Bit 4
    NULL,                   // Bit 5
    NULL,                   // Bit 6
    NULL,                   // Bit 7
    NULL,                   // Bit 8
    NULL,                   // Bit 9
    NULL,                   // Bit 10
    NULL,                   // Bit 11
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

//
//  MIDI[IN|OUT]CAPS
//
//
//
PTSTR gaszMidiOutCapsTechnology[] =
{
    gszNotSpecified,
    TEXT("MIDI Port"),                  // MOD_MIDIPORT
    TEXT("Internal Synth"),             // MOD_SYNTH
    TEXT("Internal Square Wave Synth"), // MOD_SQSYNTH
    TEXT("Internal FM Synth"),          // MOD_FMSYNTH
    TEXT("MIDI Mapper")                 // MOD_MAPPER
};

#define MIDIOUTCAPS_TECHNOLOGY_LAST     MOD_MAPPER

PTSTR gaszMidiOutCapsSupport[32] =
{
    TEXT("Volume"),         // Bit 0    MIDICAPS_VOLUME
    TEXT("L/R Volume"),     // Bit 1    MIDICAPS_LRVOLUME
    TEXT("Patch Caching"),  // Bit 2    MIDICAPS_CACHE
    TEXT("Poly Message"),   // Bit 3    MIDICAPS_POLYMSG (Win 4)
    NULL,                   // Bit 4
    NULL,                   // Bit 5
    NULL,                   // Bit 6
    NULL,                   // Bit 7
    NULL,                   // Bit 8
    NULL,                   // Bit 9
    NULL,                   // Bit 10
    NULL,                   // Bit 11
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

//
//  MIXERCAPS
//
//
//
PTSTR gaszMixerCapsSupport[32] =
{
    NULL,                   // Bit 0
    NULL,                   // Bit 1
    NULL,                   // Bit 2
    NULL,                   // Bit 3
    NULL,                   // Bit 4
    NULL,                   // Bit 5
    NULL,                   // Bit 6
    NULL,                   // Bit 7
    NULL,                   // Bit 8
    NULL,                   // Bit 9
    NULL,                   // Bit 10
    NULL,                   // Bit 11
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

//
//  WAVE[IN|OUT]CAPS
//
//
//
PTSTR gaszWaveInOutCapsFormats[32] =
{
    TEXT("8M11"),           // Bit 0    WAVE_FORMAT_1M08
    TEXT("8S11"),           // Bit 1    WAVE_FORMAT_1S08
    TEXT("16M11"),          // Bit 2    WAVE_FORMAT_1M16
    TEXT("16S11"),          // Bit 3    WAVE_FORMAT_1S16
    TEXT("8M22"),           // Bit 4    WAVE_FORMAT_2M08
    TEXT("8S22"),           // Bit 5    WAVE_FORMAT_2S08
    TEXT("16M22"),          // Bit 6    WAVE_FORMAT_2M16
    TEXT("16S22"),          // Bit 7    WAVE_FORMAT_2S16
    TEXT("8M44"),           // Bit 8    WAVE_FORMAT_4M08
    TEXT("8S44"),           // Bit 9    WAVE_FORMAT_4S08
    TEXT("16M44"),          // Bit 10   WAVE_FORMAT_4M16
    TEXT("16S44"),          // Bit 11   WAVE_FORMAT_4S16
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

PTSTR gaszWaveOutCapsSupport[32] =
{
    TEXT("Pitch"),          // Bit 0    WAVECAPS_PITCH
    TEXT("Playback Rate"),  // Bit 1    WAVECAPS_PLAYBACKRATE
    TEXT("Volume"),         // Bit 2    WAVECAPS_VOLUME
    TEXT("L/R Volume"),     // Bit 3    WAVECAPS_LRVOLUME
    TEXT("Sync"),           // Bit 4    WAVECAPS_SYNC
    NULL,                   // Bit 5
    NULL,                   // Bit 6
    NULL,                   // Bit 7
    NULL,                   // Bit 8
    NULL,                   // Bit 9
    NULL,                   // Bit 10
    NULL,                   // Bit 11
    NULL,                   // Bit 12
    NULL,                   // Bit 13
    NULL,                   // Bit 14
    NULL,                   // Bit 15
    NULL,                   // Bit 16
    NULL,                   // Bit 17
    NULL,                   // Bit 18
    NULL,                   // Bit 19
    NULL,                   // Bit 20
    NULL,                   // Bit 21
    NULL,                   // Bit 22
    NULL,                   // Bit 23
    NULL,                   // Bit 24
    NULL,                   // Bit 25
    NULL,                   // Bit 26
    NULL,                   // Bit 27
    NULL,                   // Bit 28
    NULL,                   // Bit 29
    NULL,                   // Bit 30
    NULL                    // Bit 31
};

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailAuxiliary
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailAuxiliary
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    PSTR            psz;
    MMRESULT        mmr;
    AUXCAPS         ac;
    UINT            u;
    DWORD           dw;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_AUXILIARY]);

    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = auxGetDevCaps(nDevId, &ac, sizeof(ac));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&ac, 0, sizeof(ac));
if (-1 != nDevId)
{
    lstrcpy(ac.szPname, gszBogusCaps);
}
else
{
    if (0 != auxGetNumDevs())
    {
ac.wMid           = MM_MICROSOFT;
ac.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(ac.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)ac.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(ac.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(ac.wMid, NULL, ac.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (ac.vDriverVersion >> 8), (BYTE)ac.vDriverVersion);

    if (ac.wTechnology > AUXCAPS_TECHNOLOGY_LAST)
    {
wsprintf(ach, "[%u], Unknown", ac.wTechnology);
psz = ach;
    }
    else
    {
psz = gaszAuxCapsTechnology[ac.wTechnology];
    }

    AppMEditPrintF(hedit, "       Technology: %s\r\n", (LPSTR)psz);

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Support: [%.08lXh]", ac.dwSupport);
    for (u = 0, dw = ac.dwSupport; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszAuxCapsSupport[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;
    }
    AppMEditPrintF(hedit, "\r\n");

    return (TRUE);
} // MMCapsDetailAuxiliary()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailJoystick
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailJoystick
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    MMRESULT        mmr;
    JOYCAPS         jc;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_JOYSTICK]);

    AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = joyGetDevCaps(nDevId, &jc, sizeof(jc));
    if (MMSYSERR_NOERROR != mmr)
    {
lstrcpy(jc.szPname, gszBogusCaps);
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)jc.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(jc.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(jc.wMid, NULL, jc.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: (sigh)\r\n");

    AppMEditPrintF(hedit, "          Buttons: %u\r\n", jc.wNumButtons);
    AppMEditPrintF(hedit, "    Minimum X Pos: %u\r\n", jc.wXmin);
    AppMEditPrintF(hedit, "    Maximum X Pos: %u\r\n", jc.wXmax);
    AppMEditPrintF(hedit, "    Minimum Y Pos: %u\r\n", jc.wYmin);
    AppMEditPrintF(hedit, "    Maximum Y Pos: %u\r\n", jc.wYmax);
    AppMEditPrintF(hedit, "    Minimum Z Pos: %u\r\n", jc.wZmin);
    AppMEditPrintF(hedit, "    Maximum Z Pos: %u\r\n", jc.wZmax);
    AppMEditPrintF(hedit, "   Minimum Period: %u\r\n", jc.wPeriodMin);
    AppMEditPrintF(hedit, "   Maximum Period: %u\r\n", jc.wPeriodMax);

    return (TRUE);
} // MMCapsDetailJoystick()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailMidiIn
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailMidiIn
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    MMRESULT        mmr;
    MIDIINCAPS      mic;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_MIDIIN]);

    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = midiInGetDevCaps(nDevId, &mic, sizeof(mic));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&mic, 0, sizeof(mic));
if (-1 != nDevId)
{
    lstrcpy(mic.szPname, gszBogusCaps);
}
else
{
    if (0 != midiInGetNumDevs())
    {
mic.wMid           = MM_MICROSOFT;
mic.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(mic.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)mic.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(mic.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(mic.wMid, NULL, mic.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (mic.vDriverVersion >> 8), (BYTE)mic.vDriverVersion);

    return (TRUE);
} // MMCapsDetailMidiIn()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailMidiOut
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailMidiOut
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    PSTR            psz;
    MMRESULT        mmr;
    MIDIOUTCAPS     moc;
    UINT            u;
    DWORD           dw;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_MIDIOUT]);

    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = midiOutGetDevCaps(nDevId, &moc, sizeof(moc));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&moc, 0, sizeof(moc));
if (-1 != nDevId)
{
    lstrcpy(moc.szPname, gszBogusCaps);
}
else
{
    if (0 != midiOutGetNumDevs())
    {
moc.wMid           = MM_MICROSOFT;
moc.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(moc.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)moc.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(moc.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(moc.wMid, NULL, moc.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (moc.vDriverVersion >> 8), (BYTE)moc.vDriverVersion);

    if (moc.wTechnology > MIDIOUTCAPS_TECHNOLOGY_LAST)
    {
wsprintf(ach, "[%u], Unknown", moc.wTechnology);
psz = ach;
    }
    else
    {
psz = gaszMidiOutCapsTechnology[moc.wTechnology];
    }

    AppMEditPrintF(hedit, "       Technology: %s\r\n", (LPSTR)psz);
    AppMEditPrintF(hedit, " Voices (Patches): %u (if internal synth)\r\n", moc.wVoices);
    AppMEditPrintF(hedit, "        Polyphony: %u (if internal synth)\r\n", moc.wNotes);
    AppMEditPrintF(hedit, "     Channel Mask: %.04Xh (if internal synth)\r\n", moc.wChannelMask);

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Support: [%.08lXh]", moc.dwSupport);
    for (u = 0, dw = moc.dwSupport; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszMidiOutCapsSupport[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;
    }
    AppMEditPrintF(hedit, "\r\n");

    return (TRUE);
} // MMCapsDetailMidiOut()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailMixer
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailMixer
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    PSTR            psz;
    MMRESULT        mmr;
    MIXERCAPS       mxc;
    UINT            u;
    DWORD           dw;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_MIXER]);

#ifdef MIXER_MAPPER
    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);
#else
    AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);
#endif

    //
    //
    //
    mmr = mixerGetDevCaps(nDevId, &mxc, sizeof(mxc));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&mxc, 0, sizeof(mxc));
if (-1 != nDevId)
{
    lstrcpy(mxc.szPname, gszBogusCaps);
}
else
{
    if (0 != mixerGetNumDevs())
    {
mxc.wMid           = MM_MICROSOFT;
mxc.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(mxc.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)mxc.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(mxc.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(mxc.wMid, NULL, mxc.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (mxc.vDriverVersion >> 8), (BYTE)mxc.vDriverVersion);

    AppMEditPrintF(hedit, "     Destinations: %u\r\n", mxc.cDestinations);

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Support: [%.08lXh]", mxc.fdwSupport);
    for (u = 0, dw = mxc.fdwSupport; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszMixerCapsSupport[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;
    }
    AppMEditPrintF(hedit, "\r\n");

    return (TRUE);
} // MMCapsDetailMixer()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailTimer
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailTimer
(
    HWND            hedit,
    int             nDevId
)
{
    MMRESULT        mmr;
    TIMECAPS        tc;
    MMVERSION       uMMSysVer;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_TIMER]);

    AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);
    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)gszTimerDevice);

    mmr = timeGetDevCaps(&tc, sizeof(tc));
    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    uMMSysVer = (MMVERSION)GetVersion();
    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (uMMSysVer >> 8), (BYTE)uMMSysVer);

    AppMEditPrintF(hedit, "   Minimum Period: %u\r\n", tc.wPeriodMin);
    AppMEditPrintF(hedit, "   Maximum Period: %u\r\n", tc.wPeriodMax);

    return (TRUE);
} // MMCapsDetailTimer()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailWaveIn
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailWaveIn
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    PSTR            psz;
    MMRESULT        mmr;
    WAVEINCAPS      wic;
    UINT            u;
    DWORD           dw;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_WAVEIN]);

    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = waveInGetDevCaps(nDevId, &wic, sizeof(wic));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&wic, 0, sizeof(wic));
if (-1 != nDevId)
{
    lstrcpy(wic.szPname, gszBogusCaps);
}
else
{
    if (0 != waveInGetNumDevs())
    {
wic.wMid           = MM_MICROSOFT;
wic.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(wic.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)wic.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(wic.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(wic.wMid, NULL, wic.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (wic.vDriverVersion >> 8), (BYTE)wic.vDriverVersion);

    AppMEditPrintF(hedit, "         Channels: %u\r\n", wic.wChannels);

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Formats: [%.08lXh]", wic.dwFormats);
    for (u = 0, dw = wic.dwFormats; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszWaveInOutCapsFormats[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;
    }
    AppMEditPrintF(hedit, "\r\n");

    return (TRUE);
} // MMCapsDetailWaveIn()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailWaveOut
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      int nDevId:
//
//  Return (BOOL):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsDetailWaveOut
(
    HWND            hedit,
    int             nDevId
)
{
    TCHAR           ach[MMCAPS_MAX_STRING_MIDPID_CHARS];
    PSTR            psz;
    MMRESULT        mmr;
    WAVEOUTCAPS     woc;
    UINT            u;
    DWORD           dw;

    AppMEditPrintF(hedit, "      Device Type: %s\r\n",
 (LPSTR)gaszDeviceType[MMCAPS_DEVTYPE_WAVEOUT]);

    if (-1 == nDevId)
AppMEditPrintF(hedit, "       Index (Id): %d (Mapper)\r\n", nDevId);
    else
AppMEditPrintF(hedit, "       Index (Id): %d\r\n", nDevId);

    //
    //
    //
    mmr = waveOutGetDevCaps(nDevId, &woc, sizeof(woc));
    if (MMSYSERR_NOERROR != mmr)
    {
_fmemset(&woc, 0, sizeof(woc));
if (-1 != nDevId)
{
    lstrcpy(woc.szPname, gszBogusCaps);
}
else
{
    if (0 != waveOutGetNumDevs())
    {
woc.wMid           = MM_MICROSOFT;
woc.vDriverVersion = (MMVERSION)GetVersion();
lstrcpy(woc.szPname, gszDefaultMapper);
mmr = MMSYSERR_NOERROR;
    }
}
    }

    AppMEditPrintF(hedit, "     Product Name: %s\r\n", (LPSTR)woc.szPname);

    if (MMSYSERR_NOERROR != mmr)
return (TRUE);

    //
    //
    //
    //
    MMCapsMidAndPid(woc.wMid, ach, 0, NULL);
    AppMEditPrintF(hedit, "  Manufacturer Id: %s\r\n", (LPSTR)ach);

    MMCapsMidAndPid(woc.wMid, NULL, woc.wPid, ach);
    AppMEditPrintF(hedit, "       Product Id: %s\r\n", (LPSTR)ach);

    AppMEditPrintF(hedit, "   Driver Version: %u.%.02u\r\n",
 (woc.vDriverVersion >> 8), (BYTE)woc.vDriverVersion);

    AppMEditPrintF(hedit, "         Channels: %u\r\n", woc.wChannels);

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Formats: [%.08lXh]", woc.dwFormats);
    for (u = 0, dw = woc.dwFormats; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszWaveInOutCapsFormats[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;
    }
    AppMEditPrintF(hedit, "\r\n");

    //
    //
    //
    //
    AppMEditPrintF(hedit, " Standard Support: [%.08lXh]", woc.dwSupport);
    for (u = 0, dw = woc.dwSupport; dw; u++)
    {
if ((BYTE)dw & (BYTE)1)
{
    psz = gaszWaveOutCapsSupport[u];
    if (NULL == psz)
    {
wsprintf(ach, "Unknown%u", u);
psz = ach;
    }

    AppMEditPrintF(hedit, ", %s", (LPSTR)psz);
}

dw >>= 1;

}
    AppMEditPrintF(hedit, "\r\n");

    return (TRUE);
} // MMCapsDetailWaveOut()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDetailLowLevel
//
//  Description:
//
//
//  Arguments:
//      HWND hedit:
//
//      LPARAM lParam:
//
//  Return (BOOL):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MMCapsDetailLowLevel
(
    HWND            hedit,
    LPARAM          lParam
)
{
    int         nDevId;
    UINT        uDevType;

    //
    //  HIWORD(lParam): MMCAPS_DEVTYPE_*
    //  LOWORD(lParam): Device index (id)
    //
    nDevId   = (int)(short)LOWORD(lParam);
    uDevType = HIWORD(lParam);

    //
    //
    //
    //
    switch (uDevType)
    {
case MMCAPS_DEVTYPE_AUXILIARY:
    MMCapsDetailAuxiliary(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_JOYSTICK:
    MMCapsDetailJoystick(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_MIDIIN:
    MMCapsDetailMidiIn(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_MIDIOUT:
    MMCapsDetailMidiOut(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_MIXER:
    MMCapsDetailMixer(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_TIMER:
    MMCapsDetailTimer(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_WAVEIN:
    MMCapsDetailWaveIn(hedit, nDevId);
    break;

case MMCAPS_DEVTYPE_WAVEOUT:
    MMCapsDetailWaveOut(hedit, nDevId);
    break;
    }

    //
    //
    //
    return (TRUE);
} // MMCapsDetailLowLevel()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsEnumerateLowLevel
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//      BOOL fComplete:
//
//  Return (BOOL):
//
//  History:
//      05/18/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MMCapsEnumerateLowLevel
(
    PZYZTABBEDLISTBOX   ptlb,
    BOOL                fComplete
)
{
    TCHAR       ach[128];
    MMRESULT    mmr;
    int         n;
    int         nDevs;
    int         nIndex;
    LPARAM      lParam;
    UINT        uDevType;
    MMVERSION   uMMSysVer;
    HWND        hlb;

    //
    //
    //
    //
    //
    if (fComplete)
    {
TlbSetTitleAndTabs(ptlb, gszDeviceFormatTitle, FALSE);
    }

    hlb = ptlb->hlb;

    uMMSysVer = (MMVERSION)GetVersion();

    //
    //
    //
    nDevs = auxGetNumDevs();
    for (n = -1; n < nDevs; n++)
    {
AUXCAPS         ac;

mmr = auxGetDevCaps(n, &ac, sizeof(ac));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
ac.vDriverVersion = 0;
lstrcpy(ac.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

ac.vDriverVersion = uMMSysVer;
lstrcpy(ac.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_AUXILIARY;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (ac.vDriverVersion >> 8), (BYTE)ac.vDriverVersion,
 (LPSTR)ac.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    nDevs = joyGetNumDevs();
    for (n = 0; n < nDevs; n++)
    {
JOYCAPS         jc;

mmr = joyGetDevCaps(n, &jc, sizeof(jc));
if (MMSYSERR_NOERROR != mmr)
{
    lstrcpy(jc.szPname, gszBogusCaps);
}

//
//
//
uDevType = MMCAPS_DEVTYPE_JOYSTICK;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, 0, 0, (LPSTR)jc.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    nDevs = midiInGetNumDevs();
    for (n = -1; n < nDevs; n++)
    {
MIDIINCAPS      mic;

mmr = midiInGetDevCaps(n, &mic, sizeof(mic));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
mic.vDriverVersion = 0;
lstrcpy(mic.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

mic.vDriverVersion = uMMSysVer;
lstrcpy(mic.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_MIDIIN;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (mic.vDriverVersion >> 8), (BYTE)mic.vDriverVersion,
 (LPSTR)mic.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    nDevs = midiOutGetNumDevs();
    for (n = -1; n < nDevs; n++)
    {
MIDIOUTCAPS     moc;

mmr = midiOutGetDevCaps(n, &moc, sizeof(moc));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
moc.vDriverVersion = 0;
lstrcpy(moc.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

moc.vDriverVersion = uMMSysVer;
lstrcpy(moc.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_MIDIOUT;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (moc.vDriverVersion >> 8), (BYTE)moc.vDriverVersion,
 (LPSTR)moc.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    //
    //
#ifndef WIN32
    nDevs = mixerGetNumDevs();
#ifdef MIXER_MAPPER
    for (n = -1; n < nDevs; n++)
#else
    for (n = 0; n < nDevs; n++)
#endif
    {
MIXERCAPS       mxc;

mmr = mixerGetDevCaps(n, &mxc, sizeof(mxc));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
mxc.vDriverVersion = 0;
lstrcpy(mxc.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

mxc.vDriverVersion = uMMSysVer;
lstrcpy(mxc.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_MIXER;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (mxc.vDriverVersion >> 8), (BYTE)mxc.vDriverVersion,
 (LPSTR)mxc.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }
#endif

    //
    //
    //
    {
TIMECAPS    tc;

mmr = timeGetDevCaps(&tc, sizeof(tc));
if (MMSYSERR_NOERROR == mmr)
{
    //
    //
    //
    uDevType = MMCAPS_DEVTYPE_TIMER;
    wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
     0, (uMMSysVer >> 8), (BYTE)uMMSysVer,
     (LPSTR)gszTimerDevice);

    nIndex = ListBox_AddString(hlb, ach);
    lParam = MAKELPARAM(0, (WORD)uDevType);
    ListBox_SetItemData(hlb, nIndex, lParam);
}
    }

    //
    //
    //
    nDevs = waveInGetNumDevs();
    for (n = -1; n < nDevs; n++)
    {
WAVEINCAPS      wic;

mmr = waveInGetDevCaps(n, &wic, sizeof(wic));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
wic.vDriverVersion = 0;
lstrcpy(wic.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

wic.vDriverVersion = uMMSysVer;
lstrcpy(wic.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_WAVEIN;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (wic.vDriverVersion >> 8), (BYTE)wic.vDriverVersion,
 (LPSTR)wic.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    nDevs = waveOutGetNumDevs();
    for (n = -1; n < nDevs; n++)
    {
WAVEOUTCAPS     woc;

mmr = waveOutGetDevCaps(n, &woc, sizeof(woc));
if (MMSYSERR_NOERROR != mmr)
{
    if (-1 != n)
    {
woc.vDriverVersion = 0;
lstrcpy(woc.szPname, gszBogusCaps);
    }
    else
    {
if (0 == nDevs)
    break;

woc.vDriverVersion = uMMSysVer;
lstrcpy(woc.szPname, gszDefaultMapper);
    }
}

//
//
//
uDevType = MMCAPS_DEVTYPE_WAVEOUT;
wsprintf(ach, gszDeviceFormatList, (LPSTR)gaszDeviceType[uDevType],
 n, (woc.vDriverVersion >> 8), (BYTE)woc.vDriverVersion,
 (LPSTR)woc.szPname);

nIndex = ListBox_AddString(hlb, ach);
lParam = MAKELPARAM((WORD)n, (WORD)uDevType);
ListBox_SetItemData(hlb, nIndex, lParam);
    }

    //
    //
    //
    return (TRUE);
} // MMCapsEnumerateLowLevel()


MIDSPIDS.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  midspids.c
//
//
//  Description:
//
//   !!! WARNING DANGER WARNING DANGER WARNING DANGER WARNING DANGER !!!
//
//      This code assumes that the receiving buffers are large enough
//      to contain the largest Mid and Pid--so if some of the strings get
//      obnoxiously long, make sure you update the following defines in
//      MMCAPS.H:
//
//          MMCAPS_MAX_STRING_MID_CHARS
//          MMCAPS_MAX_STRING_PID_CHARS
//
//      Also, this code is horribly disgusting. Its purpose is to convert
//      Manufacturer specific Product Id's to human readable text. And
//      since no standard was defined on how to allocate these Id's,
//      all kinds of inconsistent schemes emerged.
//
//      Therefore, I chose the brute force approach. You are more than
//      welcome to clean this up by finding patterns, etc for each
//      manufacturer--I have better things to do.
//
//   !!! WARNING DANGER WARNING DANGER WARNING DANGER WARNING DANGER !!!
//
//  History:
//       5/13/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <stdarg.h>

#include "appport.h"
#include "mmcaps.h"

#include "debug.h"

//==========================================================================;
//
//  Manufacturer and Product Id Conversion Hacks
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_MICROSOFT
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_MICROSOFT
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]        = TEXT("Microsoft Corporation");
    static PTSTR aszProductId[] =
    {
NULL,                                            // 0
TEXT("MIDI Mapper"),                             // 1  MM_MIDI_MAPPER
TEXT("Wave Mapper"),                             // 2  MM_WAVE_MAPPER
TEXT("Sound Blaster MIDI output port"),          // 3  MM_SNDBLST_MIDIOUT
TEXT("Sound Blaster MIDI input port"),           // 4  MM_SNDBLST_MIDIIN
TEXT("Sound Blaster internal synthesizer"),      // 5  MM_SNDBLST_SYNTH
TEXT("Sound Blaster waveform output"),           // 6  MM_SNDBLST_WAVEOUT
TEXT("Sound Blaster waveform input"),            // 7  MM_SNDBLST_WAVEIN
NULL,                                            // 8
TEXT("Ad Lib-compatible synthesizer"),           // 9  MM_ADLIB
TEXT("MPU401-compatible MIDI output port"),      // 10 MM_MPU401_MIDIOUT
TEXT("MPU401-compatible MIDI input port"),       // 11 MM_MPU401_MIDIIN
TEXT("Joystick adapter"),                        // 12 MM_PC_JOYSTICK
TEXT("PC Speaker waveform output"),              // 13 MM_PCSPEAKER_WAVEOUT
TEXT("MS Audio Board waveform input"),           // 14 MM_MSFT_WSS_WAVEIN
TEXT("MS Audio Board waveform output"),          // 15 MM_MSFT_WSS_WAVEOUT
TEXT("MS Audio Board Stereo FM synthesizer"),    // 16 MM_MSFT_WSS_FMSYNTH_STEREO
TEXT("MS Audio Board Mixer Driver"),             // 17 MM_MSFT_WSS_MIXER
TEXT("MS OEM Audio Board waveform input"),       // 18 MM_MSFT_WSS_OEM_WAVEIN
TEXT("MS OEM Audio Board waveform Output"),      // 19 MM_MSFT_WSS_OEM_WAVEOUT
TEXT("MS OEM Audio Board Stereo FM synthesizer"),// 20 MM_MSFT_WSS_OEM_FMSYNTH_STEREO
TEXT("MS Audio Board Auxiliary Port"),           // 21 MM_MSFT_WSS_AUX
TEXT("MS OEM Audio Auxiliary Port"),             // 22 MM_MSFT_WSS_OEM_AUX
TEXT("MS vanilla driver waveform input"),        // 23 MM_MSFT_GENERIC_WAVEIN
TEXT("MS vanilla driver waveform output"),       // 24 MM_MSFT_GENERIC_WAVEOUT
TEXT("MS vanilla driver MIDI input"),            // 25 MM_MSFT_GENERIC_MIDIIN
TEXT("MS vanilla driver external MIDI output"),  // 26 MM_MSFT_GENERIC_MIDIOUT
TEXT("MS vanilla driver MIDI synthesizer"),      // 27 MM_MSFT_GENERIC_MIDISYNTH
TEXT("MS vanilla driver aux (line in)"),         // 28 MM_MSFT_GENERIC_AUX_LINE
TEXT("MS vanilla driver aux (mic)"),             // 29 MM_MSFT_GENERIC_AUX_MIC
TEXT("MS vanilla driver aux (CD)"),              // 30 MM_MSFT_GENERIC_AUX_CD
TEXT("MS OEM Audio Board Mixer Driver"),         // 31 MM_MSFT_WSS_OEM_MIXER
    };

    #define PRODUCTID_MICROSOFT_LAST    MM_MSFT_WSS_OEM_MIXER

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    if ((uPid <= PRODUCTID_MICROSOFT_LAST) && (psz = aszProductId[uPid]))
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    pszPid[0] = '\0';
    return (FALSE);
} // MMCapsMidPid_MM_MICROSOFT()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_CREATIVE
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_CREATIVE
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Creative Labs Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_CREATIVE_SB15_WAVEIN:
    psz = TEXT("SB (r) 1.5 waveform input");
    break;

case MM_CREATIVE_SB20_WAVEIN:
    psz = TEXT("SB (r) 2.0 waveform input");
    break;

case MM_CREATIVE_SBPRO_WAVEIN:
    psz = TEXT("SB Pro (r) waveform input");
    break;

case MM_CREATIVE_SBP16_WAVEIN:
    psz = TEXT("SBP16 (r) waveform input");
    break;

case MM_CREATIVE_SB15_WAVEOUT:
    psz = TEXT("SB (r) 1.5 waveform output");
    break;

case MM_CREATIVE_SB20_WAVEOUT:
    psz = TEXT("SB (r) 2.0 waveform output");
    break;

case MM_CREATIVE_SBPRO_WAVEOUT:
    psz = TEXT("SB Pro (r) waveform output");
    break;

case MM_CREATIVE_SBP16_WAVEOUT:
    psz = TEXT("SBP16 (r) waveform output");
    break;

case MM_CREATIVE_MIDIOUT:
    psz = TEXT("SB (r) MIDI output port");
    break;

case MM_CREATIVE_MIDIIN:
    psz = TEXT("SB (r) MIDI input port");
    break;

case MM_CREATIVE_FMSYNTH_MONO:
    psz = TEXT("SB (r) FM synthesizer");
    break;

case MM_CREATIVE_FMSYNTH_STEREO:
    psz = TEXT("SB Pro (r) stereo FM synthesizer");
    break;

case MM_CREATIVE_AUX_CD:
    psz = TEXT("SB Pro (r) aux (CD)");
    break;

case MM_CREATIVE_AUX_LINE:
    psz = TEXT("SB Pro (r) aux (line in)");
    break;

case MM_CREATIVE_AUX_MIC:
    psz = TEXT("SB Pro (r) aux (mic)");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_CREATIVE()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_MEDIAVISION
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_MEDIAVISION
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Media Vision Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_PROAUD_MIDIOUT:
    psz = TEXT("MediaVision MIDI output port");
    break;

case MM_PROAUD_MIDIIN:
    psz = TEXT("MediaVision MIDI input port");
    break;

case MM_PROAUD_SYNTH:
    psz = TEXT("MediaVision synthesizer");
    break;

case MM_PROAUD_WAVEOUT:
    psz = TEXT("MediaVision Waveform output");
    break;

case MM_PROAUD_WAVEIN:
    psz = TEXT("MediaVision Waveform input");
    break;

case MM_PROAUD_MIXER:
    psz = TEXT("MediaVision Mixer");
    break;

case MM_PROAUD_AUX:
    psz = TEXT("MediaVision aux");
    break;

case MM_MEDIAVISION_THUNDER:
    psz = TEXT("Thunderboard Sound Card");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_MEDIAVISION()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_FUJITSU
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_FUJITSU
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Fujitsu");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_FUJITSU()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ARTISOFT
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ARTISOFT
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Artisoft Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_ARTISOFT_SBWAVEIN:
    psz = TEXT("Artisoft Sounding Board waveform input");
    break;

case MM_ARTISOFT_SBWAVEOUT:
    psz = TEXT("Artisoft Sounding Board waveform output");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_ARTISOFT()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_TURTLE_BEACH
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_TURTLE_BEACH
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Turtle Beach");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_TURTLE_BEACH()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_IBM
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_IBM
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("International Bussiness Machines Corp.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_MMOTION_WAVEAUX:
    psz = TEXT("IBM M-Motion Auxiliary Device");
    break;

case MM_MMOTION_WAVEOUT:
    psz = TEXT("IBM M-Motion Waveform Output");
    break;

case MM_MMOTION_WAVEIN:
    psz = TEXT("IBM M-Motion Waveform Input");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_IBM()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_VOCALTEC
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_VOCALTEC
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Vocaltec LTD.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_VOCALTEC_WAVEOUT:
    psz = TEXT("Vocaltec Waveform output port");
    break;

case MM_VOCALTEC_WAVEIN:
    psz = TEXT("Waveform input port");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_VOCALTEC()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ROLAND
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ROLAND
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Roland");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_ROLAND_MPU401_MIDIOUT:
    psz = TEXT("MM_ROLAND_MPU401_MIDIOUT");
    break;

case MM_ROLAND_MPU401_MIDIIN:
    psz = TEXT("MM_ROLAND_MPU401_MIDIIN");
    break;

case MM_ROLAND_SMPU_MIDIOUTA:
    psz = TEXT("MM_ROLAND_SMPU_MIDIOUTA");
    break;

case MM_ROLAND_SMPU_MIDIOUTB:
    psz = TEXT("MM_ROLAND_SMPU_MIDIOUTB");
    break;

case MM_ROLAND_SMPU_MIDIINA:
    psz = TEXT("MM_ROLAND_SMPU_MIDIINA");
    break;

case MM_ROLAND_SMPU_MIDIINB:
    psz = TEXT("MM_ROLAND_SMPU_MIDIINB");
    break;

case MM_ROLAND_SC7_MIDIOUT:
    psz = TEXT("MM_ROLAND_SC7_MIDIOUT");
    break;

case MM_ROLAND_SC7_MIDIIN:
    psz = TEXT("MM_ROLAND_SC7_MIDIIN");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_ROLAND()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_DIGISPEECH
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_DIGISPEECH
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Digispeech, Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
//case MM_DIGISP_WAVEOUT:
case MM_DSP_SOLUTIONS_WAVEOUT:
    psz = TEXT("Digispeech Waveform output port");
    break;

//case MM_DIGISP_WAVEIN:
case MM_DSP_SOLUTIONS_WAVEIN:
    psz = TEXT("Digispeech Waveform input port");
    break;
case MM_DSP_SOLUTIONS_SYNTH:
case MM_DSP_SOLUTIONS_AUX:
    break;

    #define  MM_DSP_SOLUTIONS_WAVEOUT       1
#define  MM_DSP_SOLUTIONS_WAVEIN            2
#define  MM_DSP_SOLUTIONS_SYNTH             3
#define  MM_DSP_SOLUTIONS_AUX               4

    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_DIGISPEECH()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_NEC
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_NEC
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("NEC");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_NEC()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ATI
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ATI
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("ATI");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_ATI()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_WANGLABS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_WANGLABS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Wang Laboratories, Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_WANGLABS_WAVEIN1:
    psz = TEXT("Wave input on Wang models: Exec 4010, 4030 and 3450; PC 251/25C, PC 461/25S and PC 461/33C");
    break;

case MM_WANGLABS_WAVEOUT1:
    psz = TEXT("Wave output on Wang models: Exec 4010, 4030 and 3450; PC 251/25C, PC 461/25S and PC 461/33C");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_WANGLABS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_TANDY
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_TANDY
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Tandy Corporation");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_TANDY()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_VOYETRA
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_VOYETRA
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Voyetra");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_VOYETRA()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ANTEX
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ANTEX
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Antex");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_ANTEX()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ICL_PS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ICL_PS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("ICL PS");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_ICL_PS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_INTEL
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_INTEL
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Intel");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_INTELOPD_WAVEIN:
    psz = TEXT("HID2 WaveAudio Input driver");
    break;

case MM_INTELOPD_WAVEOUT:
    psz = TEXT("HID2 WaveAudio Output driver");
    break;

case MM_INTELOPD_AUX:
    psz = TEXT("HID2 Auxiliary driver (required for mixing functions)");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_INTEL()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_GRAVIS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_GRAVIS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Gravis");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_GRAVIS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_VAL
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_VAL
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Video Associates Labs");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_VAL()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_INTERACTIVE
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_INTERACTIVE
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("InterActive, Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_INTERACTIVE_WAVEIN:
    psz = TEXT("MM_INTERACTIVE_WAVEIN or WAVEOUT ??");
    break;

#if 0
//  mmreg.h has in and out defined as same value... how quaint.
case MM_INTERACTIVE_WAVEOUT:
    psz = TEXT("MM_INTERACTIVE_WAVEOUT");
    break;
#endif
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_INTERACTIVE()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_YAMAHA
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_YAMAHA
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid

)
{
    static TCHAR szMid[]    = TEXT("Yamaha Corp. of America");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_YAMAHA_GSS_SYNTH:
    psz = TEXT("Yamaha Gold Sound Standard FM sythesis driver");
    break;

case MM_YAMAHA_GSS_WAVEOUT:
    psz = TEXT("Yamaha Gold Sound Standard wave output driver");
    break;

case MM_YAMAHA_GSS_WAVEIN:
    psz = TEXT("Yamaha Gold Sound Standard wave input driver");
    break;

case MM_YAMAHA_GSS_MIDIOUT:
    psz = TEXT("Yamaha Gold Sound Standard midi output driver");
    break;

case MM_YAMAHA_GSS_MIDIIN:
    psz = TEXT("Yamaha Gold Sound Standard midi input driver");
    break;

case MM_YAMAHA_GSS_AUX:
    psz = TEXT("Yamaha Gold Sound Standard auxillary driver for mixer functions");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_YAMAHA()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_EVEREX
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_EVEREX
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Everex Systems, Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_EVEREX_CARRIER:
    psz = TEXT("Everex Carrier SL/25 Notebook");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_EVEREX()

BOOL FNLOCAL MMCapsMidPid_MM_ECHO
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Echo Speech Corporation");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_ECHO_SYNTH:
    psz = TEXT("Echo EuSythesis driver");
    break;

case MM_ECHO_WAVEOUT:
    psz = TEXT("Wave output driver");
    break;

case MM_ECHO_WAVEIN:
    psz = TEXT("Wave input driver");
    break;

case MM_ECHO_MIDIOUT:
    psz = TEXT("MIDI output driver");
    break;

case MM_ECHO_MIDIIN:
    psz = TEXT("MIDI input driver");
    break;

case MM_ECHO_AUX:
    psz = TEXT("auxillary driver for mixer functions");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
}

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_SIERRA
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_SIERRA
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Sierra Semiconductor");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_SIERRA_ARIA_MIDIOUT:
    psz = TEXT("Sierra Aria MIDI output");
    break;

case MM_SIERRA_ARIA_MIDIIN:
    psz = TEXT("Sierra Aria MIDI input");
    break;

case MM_SIERRA_ARIA_SYNTH:
    psz = TEXT("Sierra Aria Synthesizer");
    break;

case MM_SIERRA_ARIA_WAVEOUT:
    psz = TEXT("Sierra Aria Waveform output");
    break;

case MM_SIERRA_ARIA_WAVEIN:
    psz = TEXT("Sierra Aria Waveform input");
    break;

case MM_SIERRA_ARIA_AUX:
    psz = TEXT("Sierra Aria Auxiliary device");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_SIERRA()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_CAT
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_CAT
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Computer Aided Technologies");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_CAT()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_APPS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_APPS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("APPS Software International");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_APPS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_DSP_GROUP
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_DSP_GROUP
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("DSP Group, Inc.");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_DSP_GROUP_TRUESPEECH:
    psz = TEXT("High quality 9.54:1 Speech Compression Vocoder");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_DSP_GROUP()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_MELABS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_MELABS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("microEngineering Labs");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_MELABS_MIDI2GO:
    psz = TEXT("parallel port MIDI interface");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_MELABS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_COMPUTER_FRIENDS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_COMPUTER_FRIENDS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Computer Friends, Inc");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_COMPUTER_FRIENDS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_ESS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_ESS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("ESS Technology");

    PTSTR       psz;

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //
    psz = NULL;
    switch (uPid)
    {
case MM_ESS_AMWAVEOUT:
    psz = TEXT("ESS Audio Magician Waveform Output Port");
    break;

case MM_ESS_AMWAVEIN:
    psz = TEXT("ESS Audio Magician Waveform Input Port");
    break;

case MM_ESS_AMAUX:
    psz = TEXT("ESS Audio Magician Auxiliary Port");
    break;

case MM_ESS_AMSYNTH:
    psz = TEXT("ESS Audio Magician Internal Music Synthesizer Port");
    break;

case MM_ESS_AMMIDIOUT:
    psz = TEXT("ESS Audio Magician MIDI Output Port");
    break;

case MM_ESS_AMMIDIIN:
    psz = TEXT("ESS Audio Magician MIDI Input Port");
    break;
    }

    if (NULL != psz)
    {
lstrcat(pszPid, psz);
return (TRUE);
    }

    return (FALSE);
} // MMCapsMidPid_MM_ESS()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_AUDIOFILE
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_AUDIOFILE
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Audio, Inc.");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_AUDIOFILE()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_MOTOROLA
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_MOTOROLA
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Motorola, Inc.");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_MOTOROLA()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidPid_MM_CANOPUS
//
//  Description:
//
//
//  Arguments:
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsMidPid_MM_CANOPUS
(
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR szMid[]    = TEXT("Canopus Co., Ltd.");

    if (NULL != pszMid)
lstrcpy(pszMid, szMid);

    if (NULL == pszPid)
return (TRUE);

    //
    //
    //

    return (FALSE);
} // MMCapsMidPid_MM_CANOPUS()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsMidAndPid
//
//  Description:
//
//
//  Arguments:
//      UINT uMid:
//
//      PTSTR pszMid:
//
//      UINT uPid:
//
//      PTSTR pszPid:
//
//  Return (BOOL):
//
//  History:
//      05/13/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MMCapsMidAndPid
(
    UINT            uMid,
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
)
{
    static TCHAR    szUnknown[]     = TEXT("Unknown");
    static TCHAR    szFormatId[]    = TEXT("[%u], %s");

    TCHAR       achMid[MMCAPS_MAX_STRING_MID_CHARS];
    TCHAR       achPid[MMCAPS_MAX_STRING_PID_CHARS];
    BOOL        f;

    switch (uMid)
    {
case MM_MICROSOFT:
    f = MMCapsMidPid_MM_MICROSOFT(achMid, uPid, achPid);
    break;

case MM_CREATIVE:
    f = MMCapsMidPid_MM_CREATIVE(achMid, uPid, achPid);
    break;

case MM_MEDIAVISION:
    f = MMCapsMidPid_MM_MEDIAVISION(achMid, uPid, achPid);
    break;

case MM_FUJITSU:
    f = MMCapsMidPid_MM_FUJITSU(achMid, uPid, achPid);
    break;

case MM_ARTISOFT:
    f = MMCapsMidPid_MM_ARTISOFT(achMid, uPid, achPid);
    break;

case MM_TURTLE_BEACH:
    f = MMCapsMidPid_MM_TURTLE_BEACH(achMid, uPid, achPid);
    break;

case MM_IBM:
    f = MMCapsMidPid_MM_IBM(achMid, uPid, achPid);
    break;

case MM_VOCALTEC:
    f = MMCapsMidPid_MM_VOCALTEC(achMid, uPid, achPid);
    break;

case MM_ROLAND:
    f = MMCapsMidPid_MM_ROLAND(achMid, uPid, achPid);
    break;

//case MM_DIGISPEECH:
case MM_DSP_SOLUTIONS:
    f = MMCapsMidPid_MM_DIGISPEECH(achMid, uPid, achPid);
    break;

case MM_NEC:
    f = MMCapsMidPid_MM_NEC(achMid, uPid, achPid);
    break;

case MM_ATI:
    f = MMCapsMidPid_MM_ATI(achMid, uPid, achPid);
    break;

case MM_WANGLABS:
    f = MMCapsMidPid_MM_WANGLABS(achMid, uPid, achPid);
    break;

case MM_TANDY:
    f = MMCapsMidPid_MM_TANDY(achMid, uPid, achPid);
    break;

case MM_VOYETRA:
    f = MMCapsMidPid_MM_VOYETRA(achMid, uPid, achPid);
    break;

case MM_ANTEX:
    f = MMCapsMidPid_MM_ANTEX(achMid, uPid, achPid);
    break;

case MM_ICL_PS:
    f = MMCapsMidPid_MM_ICL_PS(achMid, uPid, achPid);
    break;

case MM_INTEL:
    f = MMCapsMidPid_MM_INTEL(achMid, uPid, achPid);
    break;

case MM_GRAVIS:
    f = MMCapsMidPid_MM_GRAVIS(achMid, uPid, achPid);
    break;

case MM_VAL:
    f = MMCapsMidPid_MM_VAL(achMid, uPid, achPid);
    break;

case MM_INTERACTIVE:
    f = MMCapsMidPid_MM_INTERACTIVE(achMid, uPid, achPid);
    break;

case MM_YAMAHA:
    f = MMCapsMidPid_MM_YAMAHA(achMid, uPid, achPid);
    break;

case MM_EVEREX:
    f = MMCapsMidPid_MM_EVEREX(achMid, uPid, achPid);
    break;

case MM_ECHO:
    f = MMCapsMidPid_MM_ECHO(achMid, uPid, achPid);
    break;

case MM_SIERRA:
    f = MMCapsMidPid_MM_SIERRA(achMid, uPid, achPid);
    break;

case MM_CAT:
    f = MMCapsMidPid_MM_CAT(achMid, uPid, achPid);
    break;

case MM_APPS:
    f = MMCapsMidPid_MM_APPS(achMid, uPid, achPid);
    break;

case MM_DSP_GROUP:
    f = MMCapsMidPid_MM_DSP_GROUP(achMid, uPid, achPid);
    break;

case MM_MELABS:
    f = MMCapsMidPid_MM_MELABS(achMid, uPid, achPid);
    break;

case MM_COMPUTER_FRIENDS:
    f = MMCapsMidPid_MM_COMPUTER_FRIENDS(achMid, uPid, achPid);
    break;

case MM_ESS:
    f = MMCapsMidPid_MM_ESS(achMid, uPid, achPid);
    break;

case MM_AUDIOFILE:
    f = MMCapsMidPid_MM_AUDIOFILE(achMid, uPid, achPid);
    break;

case MM_MOTOROLA:
    f = MMCapsMidPid_MM_MOTOROLA(achMid, uPid, achPid);
    break;

case MM_CANOPUS:
    f = MMCapsMidPid_MM_CANOPUS(achMid, uPid, achPid);
    break;

default:
    lstrcpy(achMid, szUnknown);
    break;
    }

    //
    //
    //
    if (NULL != pszMid)
    {
wsprintf(pszMid, szFormatId, uMid, (LPTSTR)achMid);
    }

    if (NULL != pszPid)
    {
if (f)
    wsprintf(pszPid, szFormatId, uPid, (LPTSTR)achPid);
else
    wsprintf(pszPid, szFormatId, uPid, (LPTSTR)szUnknown);
    }

    //
    //
    //
    return (f);
} // MMCapsMidAndPid()


MMCAPS.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mmcaps.c
//
//  Description:
//
//
//  History:
//      11/ 8/92
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <stdarg.h>

#include "appport.h"
#include "mmcaps.h"

#include "debug.h"

//
//  globals, no less
//
HINSTANCE   ghinst;
TCHAR       gszAppSection[]     = TEXT("MMCaps");
TCHAR       gszNull[]           = TEXT("");

TCHAR       gszAppName[APP_MAX_APP_NAME_CHARS];

//
//
//
PZYZTABBEDLISTBOX   gptlbDrivers;

TCHAR       gszUnknown[]        = TEXT("Unknown");
TCHAR       gszNotSpecified[]   = TEXT("Not Specified");

UINT        guDriverType        = MMCAPS_DRIVERTYPE_LOWLEVEL;

//==========================================================================;
//
//  Application helper functions
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileWriteBytes
//
//  Description:
//      This function writes a raw structure of bytes to the application's
//      ini section that can later be retrieved using AppProfileReadBytes.
//      This gives an application the ability to write any structure to
//      the ini file and restore it later--very useful.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PCTSTR pszKey: Pointer to key name for the stored data.
//
//      LPBYTE pbStruct: Pointer to the data to be saved.
//
//      UINT cbStruct: Count in bytes of the data to store.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if it fails.
//
//  History:
//       3/10/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileWriteBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
)
{
    static TCHAR achNibbleToChar[] =
    {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
    };
    #define     NIBBLE2CHAR(x)      (achNibbleToChar[x])

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;

    //
    //  if pbStruct is NULL, then erase the key from the ini file, otherwise
    //  format the raw bytes into a hex string and write that out...
    //
    fAllocated = FALSE;
    psz        = NULL;
    if (NULL != pbStruct)
    {
//
//  check if the quick buffer can be used for formatting the output
//  text--if it cannot, then alloc space for it. note that space
//  must be available for an ending checksum byte (2 bytes for high
//  and low nibble) as well as a null terminator.
//
psz     = (LPTSTR)ach;
cchTemp = cbStruct * 2 + 3;
if (cchTemp > SIZEOF(ach))
{
    psz = GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
    if (NULL == psz)
return (FALSE);

    fAllocated = TRUE;
}

//
//  step through all bytes in the structure and convert it to
//  a string of hex numbers...
//
bChecksum = 0;
for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
{
    //
    //  grab the next byte and add into checksum...
    //
    bChecksum += (b = *pbStruct);

    *pch++ = NIBBLE2CHAR((b >> (BYTE)4) & (BYTE)0x0F);
    *pch++ = NIBBLE2CHAR(b & (BYTE)0x0F);
}

//
//  add the checksum byte to the end and null terminate the hex
//  dumped string...
//
*pch++ = NIBBLE2CHAR((bChecksum >> (BYTE)4) & (BYTE)0x0F);
*pch++ = NIBBLE2CHAR(bChecksum & (BYTE)0x0F);
*pch   = '\0';
    }

    //
    //  write the string of hex bytes out to the ini file...
    //
    fReturn = WriteProfileString(gszAppSection, pszKey, psz);

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileWriteBytes

//--------------------------------------------------------------------------;
//
//  BOOL AppProfileReadBytes
//
//  Description:
//      This function reads a previously stored structure of bytes from
//      the application's ini file. This data must have been written with
//      the AppProfileWriteBytes function--it is checksumed to keep bad
//      data from blowing up the application.
//
//      NOTE! Starting with Windows for Workgroups 3.1 there are two new
//      profile functions that provide the same functionality of this
//      function. Specifically, these functions are GetPrivateProfileStruct
//      and WritePrivateProfileStruct. These new functions are provided
//      by the Common Controls DLL. The prototypes are as follows:
//
//      BOOL GetPrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      BOOL WritePrivateProfileStruct
//      (
//          LPSTR       szSection,
//          LPSTR       szKey,
//          LPBYTE      lpStruct,
//          UINT        uSizeStruct,
//          LPSTR       szFile
//      );
//
//      If you are building an application that is for Window for Workgroups
//      or newer versions of Windows, you will probably want to use the
//      above functions.
//
//  Arguments:
//      PCTSTR pszKey: Pointer to key that contains the data.
//
//      LPBYTE pbStruct: Pointer to buffer to receive the data.
//
//      UINT cbStruct: Number of bytes expected.
//
//  Return (BOOL):
//      The return value is TRUE if the function is successful. It is FALSE
//      if the function fails (bad checksum, missing key, etc).
//
//  History:
//       3/10/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL AppProfileReadBytes
(
    PCTSTR          pszKey,
    LPBYTE          pbStruct,
    UINT            cbStruct
)
{
    //
    //  note that the following works for both upper and lower case, and
    //  will return valid values for garbage chars
    //
    #define CHAR2NIBBLE(ch) (BYTE)( ((ch) >= '0' && (ch) <= '9') ?  \
(BYTE)((ch) - '0') :                \
((BYTE)(10 + (ch) - 'A') & (BYTE)0x0F) )

    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    LPTSTR      psz;
    LPTSTR      pch;
    UINT        cchTemp;
    UINT        u;
    BOOL        fAllocated;
    BOOL        fReturn;
    BYTE        b;
    BYTE        bChecksum;
    TCHAR       ch;

    //
    //  add one the the number of bytes needed to accomodate the checksum
    //  byte placed at the end by AppProfileWriteBytes...
    //
    cbStruct++;

    //
    //  check if the quick buffer can be used for retrieving the input
    //  text--if it cannot, then alloc space for it. note that there must
    //  be space available for the null terminator (the +1 below).
    //
    fAllocated = FALSE;
    psz        = (LPTSTR)ach;
    cchTemp    = cbStruct * 2 + 1;
    if (cchTemp > SIZEOF(ach))
    {
psz = GlobalAllocPtr(GHND, cchTemp * sizeof(TCHAR));
if (NULL == psz)
    return (FALSE);

fAllocated = TRUE;
    }

    //
    //  read the hex string... if it is not the correct length, then assume
    //  error and return.
    //
    fReturn = FALSE;
    u = (UINT)GetProfileString(gszAppSection, pszKey, gszNull, psz, cchTemp);
    if ((cbStruct * 2) == u)
    {
bChecksum = 0;
for (pch = psz; 0 != cbStruct; cbStruct--, pbStruct++)
{
    ch = *pch++;
    b  = CHAR2NIBBLE(ch) << (BYTE)4;
    ch = *pch++;
    b |= CHAR2NIBBLE(ch);

    //
    //  if this is not the final byte (the checksum byte), then
    //  store it and accumulate checksum..
    //
    if (cbStruct != 1)
bChecksum += (*pbStruct = b);
}

//
//  check the last byte read against the checksum that we calculated
//  if they are not equal then return error...
//
fReturn = (bChecksum == b);
    }

    //
    //  free the temporary buffer if one was allocated (lots of bytes!)
    //
    if (fAllocated)
GlobalFreePtr(psz);

    return (fReturn);
} // AppProfileReadBytes

//--------------------------------------------------------------------------;
//
//  int AppMsgBox
//
//  Description:
//      This function displays a message for the application in a standard
//      message box.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      PCTSTR pszFormat: Format string used for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() function.
//
//  History:
//       2/13/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBox
(
    HWND            hwnd,
    UINT            fuStyle,
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the message..
    //
    va_start(va, pszFormat);
    wvsprintf(ach, pszFormat, va);
    va_end(va);

    n = MessageBox(hwnd, ach, gszAppName, fuStyle);

    return (n);
} // AppMsgBox()

//--------------------------------------------------------------------------;
//
//  int AppMsgBoxId
//
//  Description:
//      This function displays a message for the application. The message
//      text is retrieved from the string resource table using LoadString.
//
//      Note that this function takes any valid argument list that can
//      be passed to wsprintf. Because of this, the application must
//      remember to cast near string pointers to FAR when built for Win 16.
//      You will get a nice GP fault if you do not cast them correctly.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for message box holding the
//      message.
//
//      UINT fuStyle: Style flags for MessageBox().
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the result of MessageBox() if the string
//      resource specified by uIdsFormat is valid. The return value is zero
//      if the string resource failed to load.
//
//  History:
//       2/13/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMsgBoxId
(
    HWND            hwnd,
    UINT            fuStyle,
    UINT            uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
//
//  format and display the message..
//
va_start(va, uIdsFormat);
wvsprintf(ach, szFormat, va);
va_end(va);

n = MessageBox(hwnd, ach, gszAppName, fuStyle);
    }

    return (n);
} // AppMsgBoxId()

//--------------------------------------------------------------------------;
//
//  void AppHourGlass
//
//  Description:
//      This function changes the cursor to that of the hour glass or
//      back to the previous cursor.
//
//      This function can be called recursively.
//
//  Arguments:
//      BOOL fHourGlass: TRUE if we need the hour glass.  FALSE if we need
//      the arrow back.
//
//  Return (void):
//      On return, the cursor will be what was requested.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

void FNGLOBAL AppHourGlass
(
    BOOL            fHourGlass
)
{
    static HCURSOR  hcur;
    static UINT     uWaiting = 0;

    if (fHourGlass)
    {
if (!uWaiting)
{
    hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
    ShowCursor(TRUE);
}

uWaiting++;
    }
    else
    {
--uWaiting;

if (!uWaiting)
{
    ShowCursor(FALSE);
    SetCursor(hcur);
}
    }
} // AppHourGlass()

//--------------------------------------------------------------------------;
//
//  int AppDialogBox
//
//  Description:
//      This function is used to display a dialog modal box.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for new dialog.
//
//      LPCSTR pszDlg: Dialog template to use.
//
//      DLGPROC pfnDlg: Pointer to dialog procedure.
//
//      LPARAM lParam: Any lParam to be passed as lParam for WM_INITDIALOG.
//
//  Return (int):
//      The return value is the nResult from EndDialog.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

int FNGLOBAL AppDialogBox
(
    HWND            hwnd,
    LPCTSTR         pszDlg,
    DLGPROC         pfnDlg,
    LPARAM          lParam
)
{
    int     nResult;

    //
    //  !!! NT doesn't need this--neither does Win 3.1 with C7/C8 !!!
    //
    //
    nResult = 0;
    pfnDlg  = (DLGPROC)MakeProcInstance((FARPROC)pfnDlg, ghinst);
    if (NULL != pfnDlg)
    {
nResult = DialogBoxParam(ghinst, pszDlg, hwnd, pfnDlg, lParam);
FreeProcInstance((FARPROC)pfnDlg);
    }

    return (nResult);
} // AppDialogBox()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowText
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      PCTSTR pszFormat: Pointer to any valid format for wsprintf.
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowText
(
    HWND            hwnd,
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    //
    //  format and display the string in the window...
    //
    va_start(va, pszFormat);
    n = wvsprintf(ach, pszFormat, va);
    va_end(va);

    SetWindowText(hwnd, ach);

    return (n);
} // AppSetWindowText()

//--------------------------------------------------------------------------;
//
//  int AppSetWindowTextId
//
//  Description:
//      This function formats a string and sets the specified window text
//      to the result. The format string is extracted from the string
//      table using LoadString() on the uIdsFormat argument.
//
//  Arguments:
//      HWND hwnd: Handle to window to receive the new text.
//
//      UINT uIdsFormat: String resource id to be loaded with LoadString()
//      and used a the format string for wvsprintf().
//
//  Return (int):
//      The return value is the number of bytes that the resulting window
//      text was. This value is zero if the LoadString() function fails
//      for the uIdsFormat argument.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppSetWindowTextId
(
    HWND            hwnd,
    UINT            uIdsFormat,
    ...
)
{
    va_list     va;
    TCHAR       szFormat[APP_MAX_STRING_RC_CHARS];
    TCHAR       ach[APP_MAX_STRING_ERROR_CHARS];
    int         n;

    n = LoadString(ghinst, uIdsFormat, szFormat, SIZEOF(szFormat));
    if (0 != n)
    {
//
//  format and display the string in the window...
//
va_start(va, uIdsFormat);
n = wvsprintf(ach, szFormat, va);
va_end(va);

SetWindowText(hwnd, ach);
    }

    return (n);
} // AppSetWindowTextId()

//--------------------------------------------------------------------------;
//
//  int AppMEditPrintF
//
//  Description:
//      This function is used to print formatted text into a Multiline
//      Edit Control as if it were a standard console display. This is
//      a very easy way to display small amounts of text information
//      that can be scrolled and copied to the clip-board.
//
//  Arguments:
//      HWND hedit: Handle to a Multiline Edit control.
//
//      PCTSTR pszFormat: Pointer to any valid format for wsprintf. If
//      this argument is NULL, then the Multiline Edit Control is cleared
//      of all text.
//
//  Return (int):
//      Returns the number of characters written into the edit control.
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

int FNCGLOBAL AppMEditPrintF
(
    HWND            hedit,
    PCTSTR          pszFormat,
    ...
)
{
    va_list     va;
    TCHAR       ach[APP_MAX_STRING_RC_CHARS];
    int         n;

    //
    //  if the pszFormat argument is NULL, then just clear all text in
    //  the edit control..
    //
    if (NULL == pszFormat)
    {
SetWindowText(hedit, gszNull);
return (0);
    }

    //
    //  format and display the string in the window...
    //
    va_start(va, pszFormat);
    n = wvsprintf(ach, pszFormat, va);
    va_end(va);

    Edit_SetSel(hedit, (WPARAM)-1, (LPARAM)-1);
    Edit_ReplaceSel(hedit, ach);

    return (n);
} // AppMEditPrintF()

//--------------------------------------------------------------------------;
//
//  DWORD AppGetWindowsVersion
//
//  Description:
//      This function returns the version of Windows that the application
//      is running on plus some platform information.
//
//  Arguments:
//      PTSTR pach: Options pointer to buffer to receive text string of
//      the Windows version and platform.
//
//  Return (LRESULT):
//      The return value will be the version and platform information of
//      the current operating system in the following format:
//
//      0xPPPPMMRR where:
//
//      MM      :   major version of Windows
//      RR      :   minor version (revision) of Windows
//      PPPP    :   the platform the application is running on which
//                  will be one of the following:
//
//                  #ifdef WIN32
//                      the HIWORD() is RESERVED except for the high bit:
//                          high bit is 0 = Windows NT
//                          high bit is 1 = Win32s/Windows 3.1
//                  #else
//                      0xMMRR = Major and Minor version of [MS-]DOS
//                      GetWinFlags() & 0x8000 = Windows on OS/2 (WLO)
//                      GetWinFlags() & 0x4000 = Windows on Windows NT (WOW)
//                  #endif
//
//  History:
//       2/13/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppGetWindowsVersion
(
    PTSTR           pszEnvironment,
    PTSTR           pszPlatform
)
{

    BYTE    bVerWinMajor;
    BYTE    bVerWinMinor;
    UINT    uVerEnv;
    DWORD   dw;
    LRESULT lr;

    dw = GetVersion();

    //
    //  massage the version information into something intelligent
    //
    //
    bVerWinMajor = LOBYTE(LOWORD(dw));
    bVerWinMinor = HIBYTE(LOWORD(dw));
    uVerEnv      = HIWORD(dw);
    lr = MAKELPARAM(((UINT)bVerWinMajor << 8) | bVerWinMinor, uVerEnv);

    //
    //  if caller wants the environment string version...
    //
    if (NULL != pszEnvironment)
    {
    //
    //
    //
#ifdef WIN32
{
    static TCHAR    szFormatVersion[]   = TEXT("%s Version %u.%.2u");
    static TCHAR    szEnvWinNT[]        = TEXT("Windows NT");
    static TCHAR    szEnvWin32s[]       = TEXT("Win32s");

    wsprintf(pszEnvironment, szFormatVersion,
     (LPSTR)((0x8000 & uVerEnv) ? szEnvWin32s : szEnvWinNT),
     bVerWinMajor, bVerWinMinor);
}
#else
{
#ifndef WF_WINNT
    #define WF_CPUR4000         0x0100
    #define WF_CPUALPHA21064    0x0200
    #define WF_WINNT            0x4000
    #define WF_WLO              0x8000
#endif

    static TCHAR    szFormatSubSys[]= TEXT("Windows Version %u.%.2u (%s%s)\n%s Subsystem, DOS Version %u.%.2u");
    static TCHAR    szFormatDOS[]   = TEXT("Windows Version %u.%.2u (%s%s)\nDOS Version %u.%.2u");
    static TCHAR    szSubSysWLO[]   = TEXT("WLO");
    static TCHAR    szSubSysWOW[]   = TEXT("WOW");
    static TCHAR    szModeEnhanced[]= TEXT("Enhanced");
    static TCHAR    szModeStandard[]= TEXT("Standard");
    static TCHAR    szEnvPaging[]   = TEXT(", Paging");

    DWORD   dwWinFlags;
    PTSTR   pszMode;

    BYTE    bVerEnvMajor    = HIBYTE(LOWORD(uVerEnv));
    BYTE    bVerEnvMinor    = LOBYTE(LOWORD(uVerEnv));

    dwWinFlags = GetWinFlags();

    pszMode = (dwWinFlags & WF_ENHANCED) ? szModeEnhanced : szModeStandard;
    if (dwWinFlags & (WF_WLO | WF_WINNT))
    {
wsprintf(pszEnvironment, szFormatSubSys, bVerWinMajor, bVerWinMinor,
 (LPSTR)pszMode,
 (LPSTR)((dwWinFlags & WF_PAGING) ? szEnvPaging : gszNull),
 (LPSTR)((dwWinFlags & WF_WINNT) ? szSubSysWOW : szSubSysWLO),
 bVerEnvMajor, bVerEnvMinor);
    }
    else
    {
wsprintf(pszEnvironment, szFormatDOS, bVerWinMajor, bVerWinMinor,
 (LPSTR)pszMode,
 (LPSTR)((dwWinFlags & WF_PAGING) ? szEnvPaging : gszNull),
 bVerEnvMajor, bVerEnvMinor);
    }
}
#endif
    }

    //
    //  if caller wants the platform string version...
    //
    if (NULL != pszPlatform)
    {
#ifdef WIN32
{
    static TCHAR    szFormatPlatform[]  = TEXT("%s%u, %u Processor(s)");
    static TCHAR    szProcessorIntel[]  = TEXT("Intel ");
    static TCHAR    szProcessorMIPS[]   = TEXT("MIPS R");
    static TCHAR    szProcessorAlpha[]  = TEXT("DEC Alpha ");
    static TCHAR    szProcessorDunno[]  = TEXT("Dunno zYz");

    SYSTEM_INFO sysinfo;
    PTSTR       pszProcessor;

    //
    //  this is absolutely silly. one would think that the dwOemId member
    //  would provide something useful like the processor class... but
    //  no, it doesn't--it is always 0.
    //
    GetSystemInfo(&sysinfo);
    switch (sysinfo.dwProcessorType)
    {
case PROCESSOR_INTEL_386:
case PROCESSOR_INTEL_486:
    pszProcessor = szProcessorIntel;
    break;

case PROCESSOR_MIPS_R4000:
    pszProcessor = szProcessorMIPS;
    break;

case PROCESSOR_ALPHA_21064:
    pszProcessor = szProcessorAlpha;
    break;

default:
    pszProcessor = szProcessorDunno;
    break;
    }

    //
    //
    //
    wsprintf(pszPlatform, szFormatPlatform, (LPSTR)pszProcessor,
     sysinfo.dwProcessorType, sysinfo.dwNumberOfProcessors);
}
#else
{
    static TCHAR    szPlat286[]         = TEXT("80286");
    static TCHAR    szPlat386[]         = TEXT("80386");
    static TCHAR    szPlat486[]         = TEXT("i486");
    static TCHAR    szPlatR4000[]       = TEXT("MIPS R4000, Emulation: ");
    static TCHAR    szPlatAlpha21064[]  = TEXT("Alpha 21064, Emulation: ");
    static TCHAR    szPlat80x87[]       = TEXT(", 80x87");

    DWORD   dwWinFlags;

    dwWinFlags = GetWinFlags();
    pszPlatform[0] = '\0';

    if (dwWinFlags & (WF_WLO | WF_WINNT))
    {
if (dwWinFlags & WF_CPUR4000)
    lstrcpy(pszPlatform, szPlatR4000);
else if (dwWinFlags & WF_CPUALPHA21064)
    lstrcpy(pszPlatform, szPlatAlpha21064);
    }

    if (dwWinFlags & WF_CPU286)
lstrcat(pszPlatform, szPlat286);
    else if (dwWinFlags & WF_CPU386)
lstrcat(pszPlatform, szPlat386);
    else if (dwWinFlags & WF_CPU486)
lstrcat(pszPlatform, szPlat486);

    if (dwWinFlags & WF_80x87)
lstrcat(pszPlatform, szPlat80x87);
}
#endif
    }

    //
    //  return the result
    //
    return (lr);
} // AppGetWindowsVersion()

//--------------------------------------------------------------------------;
//
//  HFONT AppChooseFont
//
//  Description:
//      This function is a wrapper for the ChooseFont() common dialog.
//      The purpose of this function is to let the user choose a font that
//      looks good to them--regardless of how stupid it really looks.
//
//  Arguments:
//      HWND hwnd: Handle to parent window for chooser dialog.
//
//      HFONT hfont: Handle to current font (default for chooser dialog).
//
//      PLOGFONT plf: Pointer to optional LOGFONT structure to receive a
//      copy of the LOGFONT information for the newly chosen font.
//
//  Return (HFONT):
//      The return value is the newly chosen font. If no new font was chosen
//      then the return value is NULL.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

HFONT FNGLOBAL AppChooseFont
(
    HWND            hwnd,
    HFONT           hfont,
    PLOGFONT        plf
)
{
    LOGFONT     lf;
    CHOOSEFONT  cf;
    BOOL        f;
    HFONT       hfontNew;

    //
    //  get the font info for the current font...
    //
    GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);

    //
    //  fill in the choosefont structure
    //
    cf.lStructSize  = sizeof(CHOOSEFONT);
    cf.hwndOwner    = hwnd;
    cf.hDC          = NULL;
    cf.Flags        = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
    cf.lCustData    = 0;
    cf.lpfnHook     = NULL;
    cf.hInstance    = NULL;
    cf.nFontType    = SCREEN_FONTTYPE;
    cf.lpLogFont    = (LPLOGFONT)&lf;

    //
    //  splash a dialog into the user's face..
    //
    hfontNew = NULL;
    f = ChooseFont(&cf);
    if (f)
    {
//
//  create the new font..
//
hfontNew = CreateFontIndirect(&lf);
if (NULL == hfontNew)
    return (NULL);

//
//  copy the logfont structure if caller wants it
//
if (NULL != plf)
    *plf = lf;
    }

    //
    //  return the new font (if one was chosen)
    //
    return (hfontNew);
} // AppChooseFont()

//==========================================================================;
//
//  Misc rarely used application dialogs and stuff...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AboutDlgProc
//
//  Description:
//      This dialog procedure is used for the ubiquitous about box.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For

//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT AboutDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    HWND    hwndT;
    PTSTR   pach;
    UINT    u;

    switch (uMsg)
    {
case WM_INITDIALOG:
    //
    //  display some OS version information
    //
    //
    pach = (PTSTR)LocalAlloc(LPTR, APP_MAX_STRING_RC_BYTES);
    if (NULL == pach)
return (TRUE);

    AppGetWindowsVersion(pach, NULL);
    hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_OS);
    SetWindowText(hwndT, pach);

    AppGetWindowsVersion(NULL, pach);
    hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_PLATFORM);
    SetWindowText(hwndT, pach);

    wsprintf(pach, "MMREG.H V%u.%.02u",
     (_INC_MMREG / 100), (_INC_MMREG % 100));
    hwndT = GetDlgItem(hwnd, IDD_ABOUT_VERSION_MMSYSTEM);
    SetWindowText(hwndT, pach);

    LocalFree((HLOCAL)pach);

    //
    //  return nonzero to set the input focus to the control
    //  identified by the (hwndFocus = (HWND)wParam) argument.
    //  a zero return tells the dialog manager that this function
    //  has set the focus using SetFocus.
    //
    return (TRUE);

case WM_COMMAND:
    u = GET_WM_COMMAND_ID(wParam, lParam);
    if ((IDOK == u) || (IDCANCEL == u))
    {
EndDialog(hwnd, (IDOK == u));
    }
    break;
    }

    return (FALSE);
} // AboutDlgProc()

//==========================================================================;
//
//  Initialization and exit code...
//
//
//==========================================================================;

TCHAR   gszKeyWindow[]      = TEXT("Window");
TCHAR   gszKeyFont[]        = TEXT("Font");

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsChooseFont
//
//  Description:
//      This function lets the user choose a new font for the script window.
//      After a new font is chosen, the font structure is stored to the
//      .ini file so it can be restored on the next run of this application.
//
//  Arguments:
//      HWND hwnd: Handle to main window.
//
//  Return (BOOL):
//      The return value is TRUE if a new font was chosen. It is FALSE if
//      the user canceled the operation.
//
//  History:
//       2/ 7/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL MMCapsChooseFont
(
    HWND            hwnd
)
{
    LOGFONT     lf;
    HWND        hlb;
    HFONT       hfont;
    HFONT       hfontNew;

    hlb = GetDlgItem(hwnd, IDD_APP_LIST_DEVICES);

    //
    //  get the current font and pass it to the choose font dialog
    //
    hfont = GetWindowFont(gptlbDrivers->hlb);

    hfontNew = AppChooseFont(hwnd, hfont, &lf);
    if (NULL == hfontNew)
return (FALSE);

    //
    //  select the new font into the script window and delete the old one
    //
    TlbSetFont(gptlbDrivers, hfontNew, TRUE);
    DeleteFont(hfont);

    //
    //  save the complete description of the chosen font so there can be
    //  no strangness in the font mapping next run. this is overkill, but
    //  it works...
    //
    AppProfileWriteBytes(gszKeyFont, (LPBYTE)&lf, sizeof(lf));

    return (TRUE);
} // MMCapsChooseFont()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsSettingsRestore
//
//  Description:
//      This function restores state information for the application. This
//      function is called just after the main window is created (it has
//      not been ShowWindow()'d). This function will generate the call
//      to ShowWindow before returning.
//
//  Arguments:
//      HWND hwnd: Handle to main window that has just been created but
//      not shown.
//
//      int nCmdShow: The state that the application window should show as.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsSettingsRestore
(
    HWND            hwnd,
    int             nCmdShow
)
{
    WINDOWPLACEMENT wp;
    PRECT           prc;
    HFONT           hfont;
    LOGFONT         lf;
    RECT            rc;
    POINT           pt;
    int             n;
    BOOL            f;

    //
    //  restore the user's preferred font.
    //
    hfont = NULL;
    f = AppProfileReadBytes(gszKeyFont, (LPBYTE)&lf, sizeof(lf));
    if (f)
    {
hfont = CreateFontIndirect(&lf);
    }

    if (NULL == hfont)
    {
hfont = GetStockFont(ANSI_VAR_FONT);
    }

    TlbSetFont(gptlbDrivers, hfont, TRUE);

    //
    //  grab the stored window position and size from the .ini file...
    //  there must be four arguments stored or the entry is considered
    //  invalid.
    //
    prc = &wp.rcNormalPosition;
    f = AppProfileReadBytes(gszKeyWindow, (LPBYTE)prc, sizeof(*prc));
    if (f)
    {
//
//  to make sure the user can always get at the window, check to
//  see if the midpoint of the caption is visible--if it is not,
//  then default to the default position used when creating the
//  window.
//
n = (prc->right - prc->left) / 2;
pt.x = (n + prc->left);

n = GetSystemMetrics(SM_CYCAPTION) / 2 + GetSystemMetrics(SM_CXFRAME);
pt.y = (n + prc->top);

GetWindowRect(GetDesktopWindow(), &rc);
if (PtInRect(&rc, pt))
{
    //
    //  fill out the window placement structure--default the
    //  maximized and minimized states to default placement by
    //  getting its current placement.
    //
    wp.length = sizeof(wp);
    GetWindowPlacement(hwnd, &wp);

    wp.flags           = 0;
    wp.showCmd         = nCmdShow;

    SetWindowPlacement(hwnd, &wp);
    return (TRUE);
}
    }

    //
    //  show defaulted and succeed
    //
    ShowWindow(hwnd, nCmdShow);
    return (TRUE);
} // MMCapsSettingsRestore()

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsSettingsSave
//
//  Description:
//      This function saves the current state information for the application.
//      It is called just before the main window is closed (destroyed); or
//      as Windows is exiting (query end session).
//
//      Note that this function should not destroy any resources--it can
//      be called at any time to save a snapshot of the application state.
//
//  Arguments:
//      HWND hwnd: Handle to main window that will be destroyed shortly.
//
//  Return (BOOL):
//      The return value is always TRUE.
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsSettingsSave
(
    HWND            hwnd
)
{
    WINDOWPLACEMENT wp;
    PRECT           prc;
    BOOL            f;

    //
    //  save the current window placement--only store the size and location
    //  of the restored window. maximized and minimized states should
    //  remain defaulted on the next invocation of this application.
    //
    wp.length = sizeof(wp);
    f = GetWindowPlacement(hwnd, &wp);
    if (f)
    {
prc = &wp.rcNormalPosition;

DPF(0, "WindowPlacement: show=%d, minX=%d, minY=%d, maxX=%d, maxY=%d",
     wp.showCmd, wp.ptMinPosition.x, wp.ptMinPosition.y,
     wp.ptMaxPosition.x, wp.ptMaxPosition.y);

DPF(0, "                 normX=%d, normY=%d, normW=%d, normH=%d",
     prc->left, prc->top, prc->right, prc->bottom);

//
//  save the _bounding rectangle_ of the restored window state...
//
AppProfileWriteBytes(gszKeyWindow, (LPBYTE)prc, sizeof(*prc));
    }

    //
    //  succeed
    //
    return (TRUE);
} // MMCapsSettingsSave()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsDlgProc
//
//  Description:
//      This dialog procedure is used to display driver capabilities.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (BOOL):
//      The return value is specific to the message that was received. For
//      the most part, it is FALSE if this dialog procedure does not handle
//      a message.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

BOOL FNEXPORT MMCapsDlgProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    HWND        hedit;
    UINT        u;

    switch (uMsg)
    {
case WM_INITDIALOG:
    hedit = GetDlgItem(hwnd, IDD_DEVCAPS_EDIT_DETAILS);
    SetWindowFont(hedit, GetStockFont(ANSI_FIXED_FONT), FALSE);

    //
    //
    //
    switch (guDriverType)
    {
case MMCAPS_DRIVERTYPE_LOWLEVEL:
    MMCapsDetailLowLevel(hedit, lParam);
    break;

#if 0
case MMCAPS_DRIVERTYPE_MCI:
    MMCapsDetailMCI(hedit, lParam);
    break;

case MMCAPS_DRIVERTYPE_ACM:
    MMCapsDetailACM(hedit, lParam);
    break;

case MMCAPS_DRIVERTYPE_VIDEO:
    MMCapsDetailVideo(hedit, lParam);
    break;
#endif
    }

    //
    //  return nonzero to set the input focus to the control
    //  identified by the (hwndFocus = (HWND)wParam) argument.
    //  a zero return tells the dialog manager that this function
    //  has set the focus using SetFocus.
    //
    return (TRUE);

case WM_COMMAND:
    u = GET_WM_COMMAND_ID(wParam, lParam);
    if ((IDOK == u) || (IDCANCEL == u))
    {
EndDialog(hwnd, (IDOK == u));
    }
    break;
    }

    return (FALSE);
} // MMCapsDlgProc()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL MMCapsRefreshDriverList
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd: Handle of main window.
//
//  Return (BOOL):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL MMCapsRefreshDriverList
(
    PZYZTABBEDLISTBOX   ptlb
)
{
    static UINT     uIdPrev = (UINT)-1;

    BOOL        fComplete;

    //
    //
    //
    SetWindowRedraw(ptlb->hlb, FALSE);
    ListBox_ResetContent(ptlb->hlb);

    //
    //  only force complete update if the driver type is different from
    //  previous...
    //
    fComplete = (guDriverType != uIdPrev);
    uIdPrev = guDriverType;

    //
    //
    //
    switch (guDriverType)
    {
case MMCAPS_DRIVERTYPE_LOWLEVEL:
    MMCapsEnumerateLowLevel(ptlb, fComplete);
    break;

#if 0
case MMCAPS_DRIVERTYPE_MCI:
    MMCapsEnumerateMCI(ptlb, fComplete);
    break;

case MMCAPS_DRIVERTYPE_ACM:
    MMCapsEnumerateACM(ptlb, fComplete);
    break;

case MMCAPS_DRIVERTYPE_VIDEO:
    MMCapsEnumerateVideo(ptlb, fComplete);
    break;

case MMCAPS_DRIVERTYPE_DRIVERS:
    MMCapsEnumerateDrivers(ptlb, fComplete);
    break;
#endif

    }

    //
    //
    //
    SetWindowRedraw(ptlb->hlb, TRUE);

    return (TRUE);
} // MMCapsRefreshDriverList()

//==========================================================================;
//
//  Main application window handling code...
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  LRESULT AppCreate
//
//  Description:
//      This function is called to handle the WM_CREATE message for the
//      application's window. The application should finish the creation
//      of the window (create controls, allocate resources, etc). The
//      window has not been displayed (CreateWindow[Ex] has not returned).
//
//  Arguments:
//      HWND hwnd: Handle to the window that is in the process of being
//      created.
//
//      LPCREATESTRUCT pcs: Pointer to a CREATESTRUCT that contains info
//      about the window being created.
//
//  Return (LRESULT):
//      The return value should be nonzero if the application wishes to
//      let the window finish being created. A return of zero tells
//      CreateWindow[Ex] to fail the creation of the window.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppCreate
(
    HWND            hwnd,
    LPCREATESTRUCT  pcs
)
{
    DPF(0, "AppCreate(hwnd=%Xh, cs.x=%d, cs.y=%d, cs.cx=%d, cs.cy=%d)",
    hwnd, pcs->x, pcs->y, pcs->cx, pcs->cy);

    //
    //  create the driver selection listbox
    //
    gptlbDrivers = TlbCreate(hwnd, IDD_APP_LIST_DEVICES, NULL);
    if (NULL == gptlbDrivers)
return (0L);

    //
    //
    //
    MMCapsRefreshDriverList(gptlbDrivers);

    //
    //  we want the focus to default to the device listbox window
    //
    SetFocus(gptlbDrivers->hlb);

    //
    //  return nonzero to succeed the creation of the window
    //
    return (1L);
} // AppCreate()

//--------------------------------------------------------------------------;
//
//  LRESULT AppQueryEndSession
//
//  Description:
//      This function handles the WM_QUERYENDSESSION. This message is sent
//      by USER when ExitWindows has been called to end the Windows session.
//      This function can stop Windows from exiting if it is not convenient
//      for Windows to end.
//
//      Giving the user the option to save modified data before continueing
//      with the shutdown of Windows is a good idea.
//
//      Telling Windows to continue with the exit procedure does not
//      necessarily mean Windows will exit. All applications are queried
//      for shutdown approval. When the actual decision is made on whether
//      Windows will exit, WM_ENDSESSION will be sent with the result.
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//  Return (LRESULT):
//      Returns zero to STOP Windows from exiting. Returns non-zero to
//      allows windows to shut down.
//
//  History:
//       2/ 9/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppQueryEndSession
(
    HWND            hwnd
)
{
    DPF(0, "AppQueryEndSession(hwnd=%Xh)", hwnd);

    //
    //  tell Windows to proceed with the shutdown process!
    //
    return (1L);
} // AppQueryEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppEndSession
//
//  Description:
//      This function is called to handle the WM_ENDSESSION message. This
//      message is generated after the application answers the
//      WM_QUERYENDSESSION message. The purpose of the WM_ENDSESSION
//      message is to tell the application if Windows will be exiting
//      (TRUE  == fEndSession) or the end session was canceled by an
//      application (FALSE == fEndSession).
//
//  Arguments:
//      HWND hwnd: Handle to window that received the message.
//
//      BOOL fEndSession: TRUE if Windows is exiting. FALSE if the end
//      session was canceled.
//
//  Return (LRESULT):
//      Returns zero if the message is processed. Note that an application
//      cannot halt the termination of Windows from this message--the
//      WM_QUERYENDSESSION is the only message that allows that behaviour.
//      If fEndSession is TRUE, Windows *WILL* exit--whether you like it
//      or not.
//
//  History:
//       2/ 9/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppEndSession
(
    HWND            hwnd,
    BOOL            fEndSession
)
{
    DPF(0, "AppEndSession(hwnd=%Xh, fEndSession=%d)", hwnd, fEndSession);

    //
    //  we processed the message, return zero..
    //
    return (0L);
} // AppEndSession()

//--------------------------------------------------------------------------;
//
//  LRESULT AppClose
//
//  Description:
//      This function handles the WM_CLOSE message for the application.
//      If the application should close, DestroyWindow() must be called
//      by this function. Otherwise the application will not close.
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_CLOSE message.
//
//  Return (LRESULT):
//      There return value is zero. The DestroyWindow function will have
//      been called if the application should actually close.
//
//  History:
//       2/ 6/93
//
//--------------------------------------------------------------------------;

LRESULT FNGLOBAL AppClose
(
    HWND            hwnd
)
{
    HWND        hlb;
    HFONT       hfont;

    DPF(0, "AppClose(hwnd=%Xh)", hwnd);

    //
    //  save any settings that should be saved on app termination...
    //
    MMCapsSettingsSave(hwnd);

    //
    //  if the Shift key is held down during the close message, then just
    //  save the current state but don't destroy the window... this is
    //  useful if the user does not want to exit the app and rerun it
    //  to make sure the state is saved--just before the user does something
    //  that may crash Windows or something..
    //
    if (GetKeyState(VK_SHIFT) < 0)
    {
return (0L);
    }

    //
    //  destroy the font we are using... before deleting the font, select
    //  the system font back into the script window so the font won't
    //  be 'in use' anymore.
    //
    hlb = GetDlgItem(hwnd, IDD_APP_LIST_DEVICES);

    hfont = GetWindowFont(hlb);
    SetWindowFont(hlb, NULL, FALSE);
    DeleteFont(hfont);

    //
    //  make the window close and terminate the application
    //
    DestroyWindow(hwnd);

    return (0L);
} // AppClose()

//--------------------------------------------------------------------------;
//
//  LRESULT AppInitMenuPopup
//
//  Description:
//      This function handles the WM_INITMENUPOPUP message. This message
//      is sent to the window owning the menu that is going to become
//      active. This gives an application the ability to modify the menu
//      before it is displayed (disable/add items, etc).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_INITMENUPOPUP
//      message.
//
//      HMENU hmenu: Handle to the menu that is to become active.
//
//      int nItem: Specifies the zero-based relative position of the menu
//      item that invoked the popup menu.
//
//      BOOL fSysMenu: Specifies whether the popup menu is a System menu
//      (TRUE) or it is not a System menu (FALSE).
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//       1/ 2/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppInitMenuPopup
(
    HWND            hwnd,
    HMENU           hmenu,
    int             nItem,
    BOOL            fSysMenu
)
{
    UINT        u;

    DPF(0, "AppInitMenuPopup(hwnd=%Xh, hmenu=%Xh, nItem=%d, fSysMenu=%d)",
    hwnd, hmenu, nItem, fSysMenu);

    //
    //  if the system menu is what got hit, succeed immediately... this
    //  application has no stuff in the system menu.
    //
    if (fSysMenu)
return (0L);

    //
    //  initialize the menu that is being 'popped up'
    //
    switch (nItem)
    {
case APP_MENU_ITEM_FILE:
    break;

case APP_MENU_ITEM_DRIVERS:
    for (u = IDM_DRIVERS_LOWLEVEL; u <= IDM_DRIVERS_DRIVERS; u++)
    {
UINT    uCheck;

uCheck = (u == guDriverType) ? MF_CHECKED : MF_UNCHECKED;
CheckMenuItem(hmenu, u, uCheck);
    }
    break;
    }

    //
    //  we processed the message--return 0...
    //
    return (0L);
} // AppInitMenuPopup()

//--------------------------------------------------------------------------;
//
//  LRESULT AppCommand
//
//  Description:
//      This function handles the WM_COMMAND message.
//
//  Arguments:
//      HWND hwnd: Handle to window receiving the WM_COMMAND message.
//
//      int nId: Control or menu item identifier.
//
//      HWND hwndCtl: Handle of control if the message is from a control.
//      This argument is NULL if the message was not generated by a control.
//
//      UINT uCode: Notification code. This argument is 1 if the message
//      was generated by an accelerator. If the message is from a menu,
//      this argument is 0.
//
//  Return (LRESULT):
//      Returns zero if the message is processed.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppCommand
(
    HWND            hwnd,
    int             nId,
    HWND            hwndCtl,
    UINT            uCode
)
{
    int         n;
    LRESULT     lr;

    switch (nId)
    {
case IDM_FILE_FONT:
    MMCapsChooseFont(hwnd);
    break;

case IDM_FILE_ABOUT:
    AppDialogBox(hwnd, DLG_ABOUT, (DLGPROC)AboutDlgProc, 0L);
    break;

case IDM_FILE_EXIT:
    FORWARD_WM_CLOSE(hwnd, SendMessage);
    break;

case IDM_DRIVERS_LOWLEVEL:
case IDM_DRIVERS_MCI:
case IDM_DRIVERS_ACM:
case IDM_DRIVERS_VIDEO:
case IDM_DRIVERS_DRIVERS:
    if ((UINT)nId == guDriverType)
break;

    guDriverType = (UINT)nId;

    // -- fall through -- //

case IDM_UPDATE:
    MMCapsRefreshDriverList(gptlbDrivers);
    break;

case IDD_APP_LIST_DEVICES:
    switch (uCode)
    {
case LBN_SELCHANGE:
    break;

case LBN_DBLCLK:
    n  = ListBox_GetCurSel(hwndCtl);
    lr = ListBox_GetItemData(hwndCtl, n);
    AppDialogBox(hwnd, DLG_DEVCAPS, (DLGPROC)MMCapsDlgProc, lr);
    break;
    }
    break;
    }

    return (0L);
} // AppCommand()

//--------------------------------------------------------------------------;
//
//  LRESULT AppSize
//
//  Description:
//      This function handles the WM_SIZE message for the application's
//      window. This message is sent to the application window after the
//      size has changed (but before it is painted).
//
//  Arguments:
//      HWND hwnd: Handle to window that generated the WM_SIZE message.
//
//      UINT fuSizeType: Specifies the type of resizing requested. This
//      argument is one of the following: SIZE_MAXIMIZED, SIZE_MINIMIZED,
//      SIZE_RESTORED, SIZE_MAXHIDE, or SIZE_MAXSHOW.
//
//      int nWidth: Width of the new client area for the window.
//
//      int nHeight: Height of the new client area for the window.
//
//  Return (LRESULT):
//      Returns zero if the application processes the message.
//
//  History:
//       2/ 5/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppSize
(
    HWND            hwnd,
    UINT            fuSizeType,
    int             nWidth,
    int             nHeight
)
{
    RECT        rc;

    DPF(0, "AppSize(hwnd=%Xh, fuSizeType=%u, nWidth=%d, nHeight=%d)",
    hwnd, fuSizeType, nWidth, nHeight);

    //
    //  unless this application is the one being resized then don't waste
    //  time computing stuff that doesn't matter. this applies to being
    //  minimized also because this application does not have a custom
    //  minimized state.
    //
    if ((SIZE_RESTORED != fuSizeType) && (SIZE_MAXIMIZED != fuSizeType))
return (0L);

    //
    //  size the devices listbox to be the total size of the client area--
    //  inflate the rect by one so borders are not visible. note that
    //  we need to leave room at the top for the title text which is one
    //  line of text in height...
    //
    GetClientRect(hwnd, &rc);
    InflateRect(&rc, 1, 1);

    TlbMove(gptlbDrivers, &rc, FALSE);

    //
    //  we processed the message..
    //
    return (0L);
} // AppSize()

//--------------------------------------------------------------------------;
//
//  LRESULT AppPaint
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//  Return (LRESULT):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

LRESULT FNLOCAL AppPaint
(
    HWND            hwnd
)
{
    PAINTSTRUCT ps;

    //
    //
    //
    BeginPaint(hwnd, &ps);

    TlbPaint(gptlbDrivers, hwnd, ps.hdc);

    EndPaint(hwnd, &ps);

    //
    //  we processed the message
    //
    return (0L);
} // AppPaint()

//--------------------------------------------------------------------------;
//
//  LRESULT AppWndProc
//
//  Description:
//      This is the main application window procedure.
//
//  Arguments:
//      HWND hwnd: Handle to window.
//
//      UINT uMsg: Message being sent to the window.
//
//      WPARAM wParam: Specific argument to message.
//
//      LPARAM lParam: Specific argument to message.
//
//  Return (LRESULT):
//      The return value depends on the message that is being processed.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

LRESULT FNEXPORT AppWndProc
(
    HWND            hwnd,
    UINT            uMsg,
    WPARAM          wParam,
    LPARAM          lParam
)
{
    LRESULT     lr;

    switch (uMsg)
    {
case WM_CREATE:
    lr = HANDLE_WM_CREATE(hwnd, wParam, lParam, AppCreate);
    return (lr);

case WM_INITMENUPOPUP:
    HANDLE_WM_INITMENUPOPUP(hwnd, wParam, lParam, AppInitMenuPopup);
    return (0L);

case WM_COMMAND:
    lr = HANDLE_WM_COMMAND(hwnd, wParam, lParam, AppCommand);
    return (lr);

case WM_SIZE:
    //
    //  handle what we want for sizing, and then always call the
    //  default handler...
    //
    HANDLE_WM_SIZE(hwnd, wParam, lParam, AppSize);
    break;

case WM_PAINT:
    HANDLE_WM_PAINT(hwnd, wParam, lParam, AppPaint);
    break;

case WM_QUERYENDSESSION:
    lr = HANDLE_WM_QUERYENDSESSION(hwnd, wParam, lParam, AppQueryEndSession);
    return (lr);

case WM_ENDSESSION:
    HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, AppEndSession);
    return (0L);

case WM_CLOSE:
    HANDLE_WM_CLOSE(hwnd, wParam, lParam, AppClose);
    return (0L);

case WM_DESTROY:
    PostQuitMessage(0);
    return (0L);
    }

    return (DefWindowProc(hwnd, uMsg, wParam, lParam));
} // AppWndProc()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL AppInit
//
//  Description:
//      This function is called to initialize a new instance of the
//      application. We want to parse our command line, create our window,
//      allocate resources, etc.
//
//      The arguments passed to this function are exactly the same as
//      those passed to WinMain.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPTSTR pszCmdLine: Points to null-terminated unparsed command line.
//      If the application is compiled for Unicode, then this argument is
//      ignored.
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (HWND):
//      Returns the newly created handle to the applications main window.

//      This handle is NULL if something went wrong and tells the application
//      to exit immediately.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

HWND FNGLOBAL AppInit
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPTSTR          pszCmdLine,
    int             nCmdShow
)
{
    LRESULT FNEXPORT AppWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    HWND        hwnd;
    WNDCLASS    wc;

    DPF(0, "AppInit(hinst=%Xh, hinstPrev=%Xh, pszCmdLine='%s', nCmdShow=%d)",
    hinst, hinstPrev, pszCmdLine, nCmdShow);

    LoadString(hinst, IDS_APP_NAME, gszAppName, SIZEOF(gszAppName));

    //
    //  determine whether a new window class needs to be registered for
    //  this application. for Win 16, this only needs to be done for the
    //  first instance of the application created. for Win 32, this must
    //  be done for EVERY instance of the application.
    //
    if (NULL == hinstPrev)
    {
wc.style         = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   = (WNDPROC)AppWndProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hinst;
wc.hIcon         = LoadIcon(hinst, ICON_APP);
wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName  = MENU_APP;
wc.lpszClassName = gszAppName;

if (!RegisterClass(&wc))
    return (NULL);
    }

    //
    //  create the application's main window
    //
    //  style bits available:
    //      WS_EX_ACCEPTFILES   :  will receive WM_DROPFILES messages
    //      WS_EX_DLGMODALFRAME :  creates window with double border
    //      WS_EX_NOPARENTNOTIFY:  won't receive WM_PARENTNOTIFY messages
    //      WS_EX_TOPMOST       :  puts window in topmost space
    //      WS_EX_TRANSPARENT   :  a very bizarre style indeed (Win 16 only)
    //
    hwnd = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
  gszAppName,
  gszAppName,
  WS_OVERLAPPEDWINDOW,
  APP_WINDOW_XOFFSET,
  APP_WINDOW_YOFFSET,
  APP_WINDOW_WIDTH,
  APP_WINDOW_HEIGHT,
  NULL,
  NULL,
  hinst,
  NULL);

    if (NULL == hwnd)
return (NULL);

#ifdef UNICODE
    //
    //  the application--which is different than the pszCmdLine argument
    //  passed through WinMain()...
    //
    //  so, skip over the command name to get to the argument string
    //
    pszCmdLine = GetCommandLine();
    if (NULL != pszCmdLine)
    {
while (('\0' != *pszCmdLine) && (' ' != *pszCmdLine++))
    ;
    }
#endif

    //
    //
    //
    //
    MMCapsSettingsRestore(hwnd, nCmdShow);

    //
    //  finally, get the window displayed and return success
    //
    //  the ShowWindow call is made during MMCapsInit
    //
//  ShowWindow(hwnd, nCmdShow);
//  UpdateWindow(hwnd);

    return (hwnd);
} // AppInit()

//--------------------------------------------------------------------------;
//
//  int AppExit
//
//  Description:
//      This function is called just before the application exits from
//      WinMain. Its purpose is to clean up any resources that were allocated
//      for running the application: brushes, heaps, etc..
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application that is exiting.
//
//      int nResult: The result of the WM_QUIT message (in wParam of the
//      MSG structure. This argument will usually be 0 (even if the message
//      loop was never entered).
//
//  Return (int):
//      The return value is usually nResult--be we give this function the
//      opportunity to modify its value.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

int FNGLOBAL AppExit
(
    HINSTANCE       hinst,
    int             nResult
)
{
    DPF(0, "AppExit(hinst=%Xh, nResult=%d)", hinst, nResult);

    //
    //
    //
    //

    return (nResult);
} // AppExit()

//==========================================================================;
//
//  Main entry and message dispatching code
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int WinMain
//
//  Description:
//      This function is called by the system as the initial entry point
//      for a Windows application.
//
//  Arguments:
//      HINSTANCE hinst: Identifies the current instance of the
//      application.
//
//      HINSTANCE hinstPrev: Identifies the previous instance of the
//      application (NULL if first instance). For Win 32, this argument
//      is _always_ NULL.
//
//      LPSTR pszCmdLine: Points to null-terminated unparsed command line.
//      This string is strictly ANSI regardless of whether the application
//      is built for Unicode. To get the Unicode equivalent call the
//      GetCommandLine() function (Win 32 only).
//
//      int nCmdShow: How the main window for the application is to be
//      shown by default.
//
//  Return (int):
//      Returns result from WM_QUIT message (in wParam of MSG structure) if
//      the application is able to enter its message loop. Returns 0 if
//      the application is not able to enter its message loop.
//
//  History:
//      11/ 8/92
//
//--------------------------------------------------------------------------;

int PASCAL WinMain
(
    HINSTANCE       hinst,
    HINSTANCE       hinstPrev,
    LPSTR           pszCmdLine,
    int             nCmdShow
)
{
    int     nResult;
    HWND    hwnd;
    MSG     msg;
    HACCEL  haccl;

    //
    //  our documentation states that WinMain is supposed to return 0 if
    //  we do not enter our message loop--so assume the worst...
    //
    nResult = 0;

    //
    //  make our instance handle global for convenience..
    //
    ghinst = hinst;

    //
    //  init some stuff, create window, etc.. note the explicit cast of
    //  pszCmdLine--this is to mute a warning (and an ugly ifdef) when
    //  compiling for Unicode. see AppInit() for more details.
    //
    hwnd = AppInit(hinst, hinstPrev, (LPTSTR)pszCmdLine, nCmdShow);
    if (hwnd)
    {
haccl = LoadAccelerators(hinst, ACCEL_APP);

//
//  dispatch messages
//
while (GetMessage(&msg, NULL, 0, 0))
{
    //
    //  do all the special stuff required for this application
    //  when dispatching messages..
    //
    if (!TranslateAccelerator(hwnd, haccl, &msg))
    {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }
}

//
//  return result of WM_QUIT message.
//
nResult = (int)msg.wParam;
    }

    //
    //  shut things down, clean up, etc.
    //
    nResult = AppExit(hinst, nResult);

    return (nResult);
} // WinMain()


MMCAPS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  mmcaps.h
//
//  Description:
//
//
//  History:
//      11/ 8/92
//
//==========================================================================;

//
//  NOTE! we keep a copy of MMREG.H in this project so we can update
//  things by using 'diff'
//
#include "mmreg.h"
#include "zyztlb.h"

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Application Version Information:
//
//
//
//
//  NOTE! all string resources that will be used in app.rcv for the
//  version resource information *MUST* have an explicit \0 terminator!
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define APP_VERSION_MAJOR    4
#define APP_VERSION_MINOR           0
#define APP_VERSION_BUILD           0
#ifdef UNICODE
#define APP_VERSION_STRING_RC    "Version 4.00 (Unicode Enabled)\0"
#else
#define APP_VERSION_STRING_RC    "Version 4.00\0"
#endif

#ifdef WIN32
#define APP_VERSION_NAME_RC         "mmcaps32.exe\0"
#else
#define APP_VERSION_NAME_RC         "mmcaps16.exe\0"
#endif
#define APP_VERSION_COMPANYNAME_RC  "Microsoft Corporation\0"
#define APP_VERSION_COPYRIGHT_RC    "Copyright 1997 - 1998 Microsoft Corporation 1992-1995\0"

#ifdef WIN32
#if (defined(_X86_)) || (defined(i386))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT (i386)\0"
#endif
#if (defined(_MIPS_)) || (defined(MIPS))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT (MIPS)\0"
#endif
#if (defined(_ALPHA_)) || (defined(ALPHA))
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT (Alpha)\0"
#endif
#ifndef APP_VERSION_PRODUCTNAME_RC
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows NT\0"
#endif
#else
#define APP_VERSION_PRODUCTNAME_RC  "Microsoft Windows\0"
#endif

#ifdef DEBUG
#define APP_VERSION_DESCRIPTION_RC  "Multimedia Device Capabilities (debug)\0"
#else
#define APP_VERSION_DESCRIPTION_RC  "Multimedia Device Capabilities\0"
#endif

//
//  Unicode versions (if UNICODE is defined)... the resource compiler
//  cannot deal with the TEXT() macro.
//
#define APP_VERSION_STRING          TEXT(APP_VERSION_STRING_RC)
#define APP_VERSION_NAME            TEXT(APP_VERSION_NAME_RC)
#define APP_VERSION_COMPANYNAME     TEXT(APP_VERSION_COMPANYNAME_RC)
#define APP_VERSION_COPYRIGHT       TEXT(APP_VERSION_COPYRIGHT_RC)
#define APP_VERSION_PRODUCTNAME     TEXT(APP_VERSION_PRODUCTNAME_RC)
#define APP_VERSION_DESCRIPTION     TEXT(APP_VERSION_DESCRIPTION_RC)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  misc defines for misc sizes and things...
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  bilingual. this allows the same identifier to be used in resource files
//  and code without having to decorate the id in your code.
//
#ifdef RC_INVOKED
    #define RCID(id)    id
#else
    #define RCID(id)    MAKEINTRESOURCE(id)
#endif

//
//  misc. defines
//
#define APP_MAX_APP_NAME_CHARS      30
#define APP_MAX_APP_NAME_BYTES      (APP_MAX_APP_NAME_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_RC_CHARS     512
#define APP_MAX_STRING_RC_BYTES     (APP_MAX_STRING_RC_CHARS * sizeof(TCHAR))
#define APP_MAX_STRING_ERROR_CHARS  512
#define APP_MAX_STRING_ERROR_BYTES  (APP_MAX_STRING_ERROR_CHARS * sizeof(TCHAR))

#define APP_WINDOW_XOFFSET          CW_USEDEFAULT
#define APP_WINDOW_YOFFSET          CW_USEDEFAULT
#define APP_WINDOW_WIDTH            500 //CW_USEDEFAULT
#define APP_WINDOW_HEIGHT           300 //CW_USEDEFAULT

//
//
//
//
#define MMCAPS_MAX_STRING_MID_CHARS 80
#define MMCAPS_MAX_STRING_MID_BYTES (MMCAPS_MAX_STRING_MID_CHARS * sizeof(TCHAR))
#define MMCAPS_MAX_STRING_PID_CHARS 128
#define MMCAPS_MAX_STRING_PID_BYTES (MMCAPS_MAX_STRING_PID_CHARS * sizeof(TCHAR))

//
//  max for pid or mid plus some
//
#define MMCAPS_MAX_STRING_MIDPID_CHARS  132

//
//  resource defines...
//
#define ICON_APP                    RCID(10)
#define ACCEL_APP                   RCID(15)

//
//  the application menu...
//
//
#define MENU_APP                    RCID(20)
#define APP_MENU_ITEM_FILE          0
#define IDM_FILE_FONT               1000
#define IDM_FILE_ABOUT              1009
#define IDM_FILE_EXIT               1010

#define APP_MENU_ITEM_DRIVERS       1
#define IDM_DRIVERS_LOWLEVEL        1050
#define IDM_DRIVERS_MCI             1051
#define IDM_DRIVERS_ACM             1052
#define IDM_DRIVERS_VIDEO           1053
#define IDM_DRIVERS_DRIVERS         1054

#define IDM_UPDATE                  1100

//
//
//
#define MMCAPS_DRIVERTYPE_LOWLEVEL  IDM_DRIVERS_LOWLEVEL
#define MMCAPS_DRIVERTYPE_MCI       IDM_DRIVERS_MCI
#define MMCAPS_DRIVERTYPE_ACM       IDM_DRIVERS_ACM
#define MMCAPS_DRIVERTYPE_VIDEO     IDM_DRIVERS_VIDEO
#define MMCAPS_DRIVERTYPE_DRIVERS   IDM_DRIVERS_DRIVERS

//
//  the main window control id's...
//
#define IDD_APP_LIST_DEVICES        100

//
//  misc dlg boxes...
//
#define DLG_ABOUT                   RCID(50)
#define IDD_ABOUT_VERSION_OS        100
#define IDD_ABOUT_VERSION_PLATFORM  101

#define IDD_ABOUT_VERSION_MMSYSTEM  150

#define DLG_DEVCAPS                 RCID(55)
#define IDD_DEVCAPS_EDIT_DETAILS    100

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  string resources
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#define IDS_APP_NAME                100

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Public function prototypes
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

//
//  mmcaps.c
//
//
//
int FNCGLOBAL AppMEditPrintF
(
    HWND            hedit,
    PCTSTR          pszFormat,
    ...
);

//
//  midspids.c
//
//
//
BOOL FNGLOBAL MMCapsMidAndPid
(
    UINT            uMid,
    PTSTR           pszMid,
    UINT            uPid,
    PTSTR           pszPid
);

//
//
//
//
BOOL FNGLOBAL MMCapsEnumerateLowLevel(PZYZTABBEDLISTBOX ptlb, BOOL fComplete);
BOOL FNGLOBAL MMCapsEnumerateMCI(PZYZTABBEDLISTBOX ptlb, BOOL fComplete);
BOOL FNGLOBAL MMCapsEnumerateACM(PZYZTABBEDLISTBOX ptlb, BOOL fComplete);
BOOL FNGLOBAL MMCapsEnumerateVideo(PZYZTABBEDLISTBOX ptlb, BOOL fComplete);
BOOL FNGLOBAL MMCapsEnumerateDrivers(PZYZTABBEDLISTBOX ptlb, BOOL fComplete);

BOOL FNGLOBAL MMCapsDetailLowLevel(HWND hedit, LPARAM lParam);
BOOL FNGLOBAL MMCapsDetailMCI(HWND hedit, LPARAM lParam);
BOOL FNGLOBAL MMCapsDetailACM(HWND hedit, LPARAM lParam);
BOOL FNGLOBAL MMCapsDetailVideo(HWND hedit, LPARAM lParam);

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  global variables, etc.
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

extern HINSTANCE    ghinst;

extern TCHAR        gszAppSection[];
extern TCHAR        gszNull[];

extern TCHAR        gszAppName[APP_MAX_APP_NAME_CHARS];

//
//
//
extern TCHAR        gszUnknown[];
extern TCHAR        gszNotSpecified[];


APPPORT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  appport.h -- Win 16/32 Portability Stuff
//
//  Description:
//      This file contains common macros to help with writing code that
//      cross compiles between Win 32 and Win 16. This file should be
//      included _after_ windows.h and windowsx.h.
//
//  History:
//      11/ 8/92    created.
//
//==========================================================================;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 32
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef WIN32
    #ifndef FNLOCAL
#define FNLOCAL     _stdcall
#define FNCLOCAL    _stdcall
#define FNGLOBAL    _stdcall
#define FNCGLOBAL   _stdcall
#define FNCALLBACK  CALLBACK
#define FNEXPORT    CALLBACK
    #endif

    #ifndef PCTSTR
typedef const PTSTR     PCTSTR;
    #endif

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)    \
((DWORD)SendMessage((hwndCtl), EM_GETSEL, (WPARAM)pnS, (LPARAM)pnE))

    //
    //  for compiling Unicode
    //
    #ifdef UNICODE
#define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
    #else
#define SIZEOF(x)   sizeof(x)
    #endif

    //
    //  win32 apps [usually] don't have to worry about 'huge' data
    //
    #if !defined hmemcpy
#define hmemcpy memcpy
    #endif
#endif // #ifdef WIN32

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 16
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef WIN32
    #ifndef FNLOCAL
#define FNLOCAL     NEAR PASCAL
#define FNCLOCAL    NEAR _cdecl
#define FNGLOBAL    FAR PASCAL
#define FNCGLOBAL   FAR _cdecl
#define FNCALLBACK  CALLBACK
#define FNEXPORT    CALLBACK _export
    #endif

    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef _TCHAR_DEFINED
#define _TCHAR_DEFINED
typedef char            TCHAR, *PTCHAR;
typedef unsigned char   TBYTE, *PTUCHAR;

typedef PSTR            PTSTR, PTCH;
typedef const PTSTR     PCTSTR;
typedef LPSTR           LPTSTR, LPTCH;
typedef LPCSTR          LPCTSTR;
    #endif

    #define TEXT(a)         a
    #define SIZEOF(x)       sizeof(x)

    //
    //
    //
    #define CharNext        AnsiNext
    #define CharPrev        AnsiPrev

    //
    //
    //
    #define Edit_GetSelEx(hwndCtl, pnS, pnE)                        \
    {                                                               \
DWORD   dw;                                                 \
dw = (DWORD)SendMessage((hwndCtl), EM_GETSEL, 0, 0L);       \
*pnE = (int)HIWORD(dw);                                     \
*pnS = (int)LOWORD(dw);                                     \
    }

    //
    //  common message cracker macros available in windowx.h on NT--these
    //  should be added to the Win 16 windowsx.h and probably will be
    //  in the future.
    //
    //  there is a windowsx.h16 that ships with the NT PDK that defines
    //  these macros. so if that version is being used, don't redefine
    //  message crackers.
    //

#ifndef WM_CTLCOLORMSGBOX
    #define WM_CTLCOLORMSGBOX           0x0132
    #define WM_CTLCOLOREDIT             0x0133
    #define WM_CTLCOLORLISTBOX          0x0134
    #define WM_CTLCOLORBTN              0x0135
    #define WM_CTLCOLORDLG              0x0136
    #define WM_CTLCOLORSCROLLBAR        0x0137
    #define WM_CTLCOLORSTATIC           0x0138
#endif

#ifndef GET_WM_ACTIVATE_STATE
    #define GET_WM_ACTIVATE_STATE(wp, lp)           (wp)
    #define GET_WM_ACTIVATE_FMINIMIZED(wp, lp)      (BOOL)HIWORD(lp)
    #define GET_WM_ACTIVATE_HWND(wp, lp)            (HWND)LOWORD(lp)
    #define GET_WM_ACTIVATE_MPS(s, fmin, hwnd)      (WPARAM)(s), MAKELONG(hwnd, fmin)

    #define GET_WM_CHARTOITEM_CHAR(wp, lp)          (CHAR)(wp)
    #define GET_WM_CHARTOITEM_POS(wp, lp)           HIWORD(lp)
    #define GET_WM_CHARTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_CHARTOITEM_MPS(ch, pos, hwnd)    (WPARAM)(ch), MAKELONG(hwnd, pos)

    #define GET_WM_COMMAND_ID(wp, lp)               (wp)
    #define GET_WM_COMMAND_HWND(wp, lp)             (HWND)LOWORD(lp)
    #define GET_WM_COMMAND_CMD(wp, lp)              HIWORD(lp)
    #define GET_WM_COMMAND_MPS(id, hwnd, cmd)       (WPARAM)(id), MAKELONG(hwnd, cmd)

    #define GET_WM_CTLCOLOR_HDC(wp, lp, msg)        (HDC)(wp)
    #define GET_WM_CTLCOLOR_HWND(wp, lp, msg)       (HWND)LOWORD(lp)
    #define GET_WM_CTLCOLOR_TYPE(wp, lp, msg)       HIWORD(lp)
    #define GET_WM_CTLCOLOR_MPS(hdc, hwnd, type)    (WPARAM)(hdc), MAKELONG(hwnd, type)

    #define GET_WM_MENUSELECT_CMD(wp, lp)           (wp)
    #define GET_WM_MENUSELECT_FLAGS(wp, lp)         LOWORD(lp)
    #define GET_WM_MENUSELECT_HMENU(wp, lp)         (HMENU)HIWORD(lp)
    #define GET_WM_MENUSELECT_MPS(cmd, f, hmenu)    (WPARAM)(cmd), MAKELONG(f, hmenu)

    // Note: the following are for interpreting MDIclient to MDI child messages.
    #define GET_WM_MDIACTIVATE_FACTIVATE(hwnd, wp, lp)  (BOOL)(wp)
    #define GET_WM_MDIACTIVATE_HWNDDEACT(wp, lp)        (HWND)HIWORD(lp)
    #define GET_WM_MDIACTIVATE_HWNDACTIVATE(wp, lp)     (HWND)LOWORD(lp)

    // Note: the following is for sending to the MDI client window.
    #define GET_WM_MDIACTIVATE_MPS(f, hwndD, hwndA) (WPARAM)(hwndA), 0

    #define GET_WM_MDISETMENU_MPS(hmenuF, hmenuW)   0, MAKELONG(hmenuF, hmenuW)

    #define GET_WM_MENUCHAR_CHAR(wp, lp)            (CHAR)(wp)
    #define GET_WM_MENUCHAR_HMENU(wp, lp)           (HMENU)LOWORD(lp)
    #define GET_WM_MENUCHAR_FMENU(wp, lp)           (BOOL)HIWORD(lp)
    #define GET_WM_MENUCHAR_MPS(ch, hmenu, f)       (WPARAM)(ch), MAKELONG(hmenu, f)

    #define GET_WM_PARENTNOTIFY_MSG(wp, lp)         (wp)
    #define GET_WM_PARENTNOTIFY_ID(wp, lp)          HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_HWNDCHILD(wp, lp)   (HWND)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_X(wp, lp)           (INT)LOWORD(lp)
    #define GET_WM_PARENTNOTIFY_Y(wp, lp)           (INT)HIWORD(lp)
    #define GET_WM_PARENTNOTIFY_MPS(msg, id, hwnd)  (WPARAM)(msg), MAKELONG(hwnd, id)
    #define GET_WM_PARENTNOTIFY2_MPS(msg, x, y)     (WPARAM)(msg), MAKELONG(x, y)

    #define GET_WM_VKEYTOITEM_CODE(wp, lp)          (wp)
    #define GET_WM_VKEYTOITEM_ITEM(wp, lp)          (INT)HIWORD(lp)
    #define GET_WM_VKEYTOITEM_HWND(wp, lp)          (HWND)LOWORD(lp)
    #define GET_WM_VKEYTOITEM_MPS(code, item, hwnd) (WPARAM)(code), MAKELONG(hwnd, item)

    #define GET_EM_SETSEL_START(wp, lp)             LOWORD(lp)
    #define GET_EM_SETSEL_END(wp, lp)               HIWORD(lp)
    #define GET_EM_SETSEL_MPS(iStart, iEnd)         0, MAKELONG(iStart, iEnd)

    #define GET_EM_LINESCROLL_MPS(vert, horz)       0, MAKELONG(vert, horz)

    #define GET_WM_CHANGECBCHAIN_HWNDNEXT(wp, lp)   (HWND)LOWORD(lp)

    #define GET_WM_HSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_HSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_HSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_HSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)

    #define GET_WM_VSCROLL_CODE(wp, lp)             (wp)
    #define GET_WM_VSCROLL_POS(wp, lp)              LOWORD(lp)
    #define GET_WM_VSCROLL_HWND(wp, lp)             (HWND)HIWORD(lp)
    #define GET_WM_VSCROLL_MPS(code, pos, hwnd)     (WPARAM)(code), MAKELONG(pos, hwnd)
#endif

#endif // #ifndef WIN32


ZYZTLB.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  zyztlb.c
//
//  Description:
//
//
//  History:
//       5/18/93
//
//==========================================================================;

#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>

#include "appport.h"
#include "zyztlb.h"

#include "debug.h"

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  int GetRealTextMetrics
//
//  Description:
//      This function gets the textmetrics of the font currently selected
//      into the hdc.  It returns the average char width as the return value.
//
//      This function computes the average character width correctly by
//      using GetTextExtent() on the string "abc...xzyABC...XYZ" which works
//      out much better for proportional fonts. This is also necessary
//      for correct alignment between dialog and client units.
//
//      Note that this function returns the same TEXTMETRIC values that
//      GetTextMetrics() does, it simply has a different return value.
//
//  Arguments:
//      HDC hdc:
//
//      LPTEXTMETRIC ptm:
//
//  Return (int):
//
//  History:
//      05/11/93
//
//--------------------------------------------------------------------------;

int FNGLOBAL GetRealTextMetrics
(
    HDC             hdc,
    LPTEXTMETRIC    ptm
)
{
    TCHAR       achAlphabet[26 * 2];    // upper and lower case
    SIZE        sSize;
    UINT        u;
    int         nAveWidth;

    //
    //  get the text metrics of the current font. note that GetTextMetrics
    //  gets the incorrect nAveCharWidth value for proportional fonts.
    //
    GetTextMetrics(hdc, ptm);
    nAveWidth = ptm->tmAveCharWidth;

    //
    //  if it's not a variable pitch font GetTextMetrics was correct
    //  so just return.
    //
    if (ptm->tmPitchAndFamily & FIXED_PITCH)
    {
//
//
//
for (u = 0; u < 26; u++)
{
    achAlphabet[u]      = (TCHAR)(u + (UINT)'a');
    achAlphabet[u + 26] = (TCHAR)(u + (UINT)'A');
}

//
//  round up
//
GetTextExtentPoint(hdc, achAlphabet, SIZEOF(achAlphabet), &sSize);
nAveWidth = ((sSize.cx / 26) + 1) / 2;
    }

    //
    //  return the calculated average char width
    //
    return (nAveWidth);
} // GetRealTextMetrics()

//==========================================================================;
//
//
//
//
//==========================================================================;

//--------------------------------------------------------------------------;
//
//  BOOL TlbPaint
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//      HWND hwnd:
//
//      HDC hdc:
//
//  Return (BOOL):
//
//  History:
//      05/17/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL TlbPaint
(
    PZYZTABBEDLISTBOX   ptlb,
    HWND                hwnd,
    HDC                 hdc
)
{
    RECT        rc;
    HFONT       hfont;
    COLORREF    crBk;
    COLORREF    crText;
    int         nHeight;

    //
    //
    //
    hfont = GetWindowFont(ptlb->hlb);
    if (NULL == hfont)
hfont = GetStockFont(SYSTEM_FONT);

    hfont = SelectObject(hdc, hfont);

    crBk   = SetBkColor(hdc, GetSysColor(COLOR_ACTIVECAPTION));
    crText = SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT));

    //
    //  compute bounding rect for title only
    //
    rc = ptlb->rc;
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rc, NULL, 0, NULL);
    TabbedTextOut(hdc, rc.left, rc.top,
  ptlb->pszTitleText,
  ptlb->cchTitleText,
  ptlb->uTabStops,
  ptlb->panTitleTabs, 0);

    //
    //  restore the dc
    //
    SetBkColor(hdc, crBk);
    SetTextColor(hdc, crText);

    SelectObject(hdc, hfont);

    return (TRUE);
} // TlbPaint()

//--------------------------------------------------------------------------;
//
//  BOOL TlbMove
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//      PRECT prc:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL TlbMove
(
    PZYZTABBEDLISTBOX   ptlb,
    PRECT               prc,
    BOOL                fRedraw
)
{
    RECT        rc;
    int         nHeight;
    HWND        hwnd;

    hwnd = GetParent(ptlb->hlb);

    //
    //  invalidate only the region occupied by the current title bar. this
    //  will make sure that area gets repainted. the listbox portion will
    //  be invalidated correctly by the SetWindowPos() function below..
    //
    rc = ptlb->rc;

    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.bottom = rc.top + nHeight;

    InvalidateRect(hwnd, &rc, TRUE);

    //
    //  now move the listbox--we modify values in the rect structure, so
    //  copy to local storage
    //
    rc = *prc;

    //
    //  leave room at the top of the bounding rect for the title text
    //
    nHeight = min(ptlb->nFontHeight, rc.bottom - rc.top);
    rc.top += nHeight;

    SetWindowPos(ptlb->hlb, NULL, rc.left, rc.top, rc.right - rc.left,
 rc.bottom - rc.top, SWP_NOZORDER);

    //
    //  save the new location and invalidate the area so it is repainted
    //
    ptlb->rc = *prc;
    InvalidateRect(hwnd, prc, TRUE);

    if (fRedraw)
    {
UpdateWindow(hwnd);
    }

    return (TRUE);
} // TlbMove()

//--------------------------------------------------------------------------;
//
//  BOOL TlbRecalcTabs
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//  Return (BOOL):
//
//  History:
//      05/18/93
//
//--------------------------------------------------------------------------;

BOOL FNLOCAL TlbRecalcTabs
(
    PZYZTABBEDLISTBOX   ptlb
)
{
    static TCHAR szGonzoThing[] = TEXT("M");

    int         anTabsList[TLB_MAX_TAB_STOPS];
    HDC         hdc;
    HFONT       hfont;
    TEXTMETRIC  tm;
    int         nAveCharWidth;

    UINT        u;
    int         nWidth;
    int         nPrevTabTitle;
    int         nPrevTabList;
    SIZE        sSize;

    //
    //
    //
    hdc = GetDC(NULL);
    {
//
//  get the average char width and height of the current font so we
//  can compute tabs correctly. note that GetTextMetrics is pretty
//  bogus when it comes to the average char width--it normally gives
//  you the width of the character 'x'. what it should be is the
//  average width of all capitals and lower case letters..
//
hfont = GetWindowFont(ptlb->hlb);
if (NULL == hfont)
    hfont = GetStockFont(SYSTEM_FONT);

hfont = SelectObject(hdc, hfont);

#if 0
GetTextMetrics(hdc, &tm);
nAveCharWidth = tm.tmAveCharWidth;
#else
nAveCharWidth = GetRealTextMetrics(hdc, &tm);
#endif
ptlb->nFontHeight = tm.tmHeight;

//
//
//
GetTextExtentPoint(hdc, szGonzoThing, 1, &sSize);

//
//
//
hfont = SelectObject(hdc, hfont);
    }
    ReleaseDC(NULL, hdc);

    //
    //  calculate the width of each column
    //
    nPrevTabTitle = 0;
    nPrevTabList  = 0;
    for (u = 0; u < ptlb->uTabStops; u++)
    {
//      nWidth = nAveCharWidth * ptlb->panTabs[u] + nAveCharWidth * 2;
nWidth = sSize.cx * ptlb->panTabs[u] + (sSize.cx * 2);

//
//  set tabstop for title text--this is in client units
//  for TabbedTextOut in TlbPaint
//
ptlb->panTitleTabs[u] = nPrevTabTitle + nWidth;
nPrevTabTitle = ptlb->panTitleTabs[u];

//
//  set tabstop for listbox--this is in dialog units
//
anTabsList[u] = nPrevTabList + MulDiv(nWidth, 4, nAveCharWidth);
nPrevTabList  = anTabsList[u];
    }

    //
    //  now setup the tabstops in the listbox
    //
    ListBox_SetTabStops(ptlb->hlb, ptlb->uTabStops, anTabsList);

    return (TRUE);
} // TlbRecalcTabs()

//--------------------------------------------------------------------------;
//
//  HFONT TlbSetFont
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//      HFONT hfont:
//
//      BOOL fRedraw:
//
//  Return (HFONT):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

HFONT FNGLOBAL TlbSetFont
(
    PZYZTABBEDLISTBOX   ptlb,
    HFONT               hfont,
    BOOL                fRedraw
)
{
    HFONT       hfontOld;

    //
    //
    //
    hfontOld = GetWindowFont(ptlb->hlb);
    SetWindowFont(ptlb->hlb, hfont, FALSE);

    TlbRecalcTabs(ptlb);
    TlbMove(ptlb, &ptlb->rc, fRedraw);

    return (hfontOld);
} // TlbSetFont()

//--------------------------------------------------------------------------;
//
//  BOOL TlbSetTitleAndTabs
//
//  Description:
//      This function sets the title text and tab stops for a Tabbed List
//      Box (TLB). The pszTitleFormat specifies the title text for each
//      column along with the tabstop position for each column. The format
//      of this string is as follows:
//
//      <columnname1>\t<tab1>!<columnname2>
//
//      TCHAR   szTlbThings[] = TEXT("Index\t6!Code\t5!Name");
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//      PTSTR pszTitleFormat:
//
//      BOOL fRedraw:
//
//  Return (BOOL):
//
//  History:
//      05/18/93
//
//--------------------------------------------------------------------------;

BOOL FNGLOBAL TlbSetTitleAndTabs
(
    PZYZTABBEDLISTBOX   ptlb,
    PTSTR               pszTitleFormat,
    BOOL                fRedraw
)
{
    TCHAR       szTitleText[TLB_MAX_TITLE_CHARS];
    int         anTabs[TLB_MAX_TAB_STOPS];
    PTSTR       pch;
    PTSTR       pchTitleText;
    UINT        uTabStops;
    UINT        cchTitleText;
    HWND        hwnd;

    //
    //  parse the title format counting tab stops and actual size of title
    //  text
    //
    uTabStops    = 0;
    pchTitleText = szTitleText;
    for (pch = pszTitleFormat; '\0' != *pch; )
    {
TCHAR       ch;

//
//  scan to tab
//
while ('\0' != (ch = *pch))
{
    *pchTitleText++ = *pch++;

    if ('\t' == ch)
break;
}

if ('\0' == ch)
    break;

//
//  grab the next tab stop value
//
anTabs[uTabStops] = atoi(pch);
uTabStops++;

//
//  skip to start of next column name
//
while ('!' != *pch++)
    ;
    }

    //
    //  terminate the converted title text
    //
    *pchTitleText = '\0';
    cchTitleText = lstrlen(szTitleText);

    //
    //  free the memory used for the previous tab stops and title text
    //
    if (NULL != ptlb->panTabs)
    {
LocalFree((HLOCAL)ptlb->panTabs);

ptlb->uTabStops    = 0;
ptlb->panTabs      = NULL;
ptlb->panTitleTabs = NULL;
    }

    if (NULL != ptlb->pszTitleText)
    {
LocalFree((HLOCAL)ptlb->pszTitleText);

ptlb->cchTitleText = 0;
ptlb->pszTitleText = NULL;
    }

    //
    //  allocate new space for tab stops. there are two different tab
    //  arrays:
    //
    //      panTabs: original tab values as passed by caller. these are
    //      virtual tab locations represented as number of characters. we
    //      need to keep these values for recomputing the real tabs when
    //      the font changes.
    //
    //      panTitleTabs: these values are computed by TlbRecalcTabs and
    //      are actual tab positions in client coordinates for the title
    //      text (needed for TabbedTextOut in TlbPaint).
    //
    //  the tabs for the listbox are computed and set in TlbRecalcTabs
    //
    if (0 != uTabStops)
    {
ptlb->panTabs = (PINT)LocalAlloc(LPTR, (uTabStops * sizeof(int)) * 2);
if (NULL == ptlb->panTabs)
    return (FALSE);

ptlb->uTabStops    = uTabStops;
ptlb->panTitleTabs = ptlb->panTabs + uTabStops;
memcpy(ptlb->panTabs, anTabs, uTabStops * sizeof(int));
    }

    //
    //  allocate space for the converted title text (stripped of the tab
    //  spacing values). this string is passed directly to TabbedTextOut
    //  in TlbPaint.
    //
    if (0 != cchTitleText)
    {
ptlb->pszTitleText = (PTSTR)LocalAlloc(LPTR, (cchTitleText + 1) * sizeof(TCHAR));
if (NULL == ptlb->pszTitleText)
    return (FALSE);

ptlb->cchTitleText = cchTitleText;
lstrcpy(ptlb->pszTitleText, szTitleText);
    }

    //
    //
    //
    TlbRecalcTabs(ptlb);

    //
    //  force a complete repaint of the title text and listbox--redraw
    //  immediately if we are supposed to
    //
    hwnd = GetParent(ptlb->hlb);
    InvalidateRect(hwnd, &ptlb->rc, TRUE);
    if (fRedraw)
    {
UpdateWindow(hwnd);
    }
} // TlbSetTitleAndTabs()

//--------------------------------------------------------------------------;
//
//  PZYZTABBEDLISTBOX TlbDestroy
//
//  Description:
//
//
//  Arguments:
//      PZYZTABBEDLISTBOX ptlb:
//
//  Return (PZYZTABBEDLISTBOX):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

PZYZTABBEDLISTBOX FNGLOBAL TlbDestroy
(
    PZYZTABBEDLISTBOX   ptlb
)
{
    HWND        hwnd;
    int         nHeight;

    //
    //  get rid of the listbox
    //
    if (NULL != ptlb->hlb)
    {
DestroyWindow(ptlb->hlb);

//
//  invalidate area where title text was so it will be clean
//
nHeight = min(ptlb->nFontHeight, ptlb->rc.bottom - ptlb->rc.top);
ptlb->rc.bottom = ptlb->rc.top + nHeight;

hwnd = GetParent(ptlb->hlb);
InvalidateRect(hwnd, &ptlb->rc, TRUE);

//
//  free the memory used for tab stops and title text
//
if (NULL != ptlb->panTabs)
    LocalFree((HLOCAL)ptlb->panTabs);

if (NULL != ptlb->pszTitleText)
    LocalFree((HLOCAL)ptlb->pszTitleText);
    }

    LocalFree((HLOCAL)ptlb);

    return (NULL);
} // TlbDestroy()

//--------------------------------------------------------------------------;
//
//  PZYZTABBEDLISTBOX TlbCreate
//
//  Description:
//
//
//  Arguments:
//      HWND hwnd:
//
//      int nId:
//
//      PRECT prc:
//
//  Return (PZYZTABBEDLISTBOX):
//
//  History:
//      05/16/93
//
//--------------------------------------------------------------------------;

PZYZTABBEDLISTBOX FNGLOBAL TlbCreate
(
    HWND                hwnd,
    int                 nId,
    PRECT               prc
)
{
    #define TLB_DEF_STYLE   (WS_VISIBLE|WS_CHILD|WS_VSCROLL|WS_BORDER|  \
     LBS_NOTIFY|LBS_NOINTEGRALHEIGHT|LBS_USETABSTOPS)

    static TCHAR    szNull[]    = TEXT("");
    static TCHAR    szListBox[] = TEXT("ListBox");

    PZYZTABBEDLISTBOX   ptlb;
    HINSTANCE           hinst;

    //
    //  create a new instance data structure..
    //
    ptlb = (PZYZTABBEDLISTBOX)LocalAlloc(LPTR, sizeof(*ptlb));
    if (NULL == ptlb)
return (NULL);

    //
    //  create the listbox
    //
    hinst = GetWindowInstance(hwnd);

    ptlb->hlb = CreateWindow(szListBox, szNull, TLB_DEF_STYLE,
     0, 0, 0, 0, hwnd, (HMENU)nId, hinst, NULL);
    if (NULL == ptlb->hlb)
    {
TlbDestroy(ptlb);
return (NULL);
    }

    TlbRecalcTabs(ptlb);

    if (NULL != prc)
    {
ptlb->rc = *prc;
TlbMove(ptlb, prc, FALSE);
    }

    return (ptlb);
} // TlbCreate()


ZYZTLB.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//  A PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//==========================================================================;
//
//  zyztlb.h
//
//  Description:
//
//
//  History:
//       5/18/93
//
//==========================================================================;

//
//
//
//
typedef struct tZYZTABBEDLISTBOX
{
    HWND            hlb;

    int             nFontHeight;
    RECT            rc;

    UINT            uTabStops;
    PINT            panTabs;
    PINT            panTitleTabs;

    UINT            cchTitleText;
    PTSTR           pszTitleText;

} ZYZTABBEDLISTBOX, *PZYZTABBEDLISTBOX;

#define TLB_MAX_TAB_STOPS           20      // max number of columns
#define TLB_MAX_TITLE_CHARS         512

//
//
//
//
//
BOOL FNGLOBAL TlbPaint
(
    PZYZTABBEDLISTBOX   ptlb,
    HWND                hwnd,
    HDC                 hdc
);

BOOL FNGLOBAL TlbMove
(
    PZYZTABBEDLISTBOX   ptlb,
    PRECT               prc,
    BOOL                fRedraw
);

HFONT FNGLOBAL TlbSetFont
(
    PZYZTABBEDLISTBOX   ptlb,
    HFONT               hfont,
    BOOL                fRedraw
);

BOOL FNGLOBAL TlbSetTitleAndTabs
(
    PZYZTABBEDLISTBOX   ptlb,
    PTSTR               pszTitleFormat,
    BOOL                fRedraw
);

PZYZTABBEDLISTBOX FNGLOBAL TlbDestroy
(
    PZYZTABBEDLISTBOX   ptlb
);

PZYZTABBEDLISTBOX FNGLOBAL TlbCreate
(
    HWND                hwnd,
    int                 nId,
    PRECT               prc
);


README.TXT

Simple application that will play a waveform in reverse.


REVERSE.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/* reverse.c - WinMain() and WndProc() for REVERSE, along with
 * initialization and support code.
 *
 * REVERSE is a Windows sample application that illustrates how to use
 * the low-level waveform playback services. It also shows how to use
 * the multimedia file I/O services to read data from a WAVE file.
 *
 * REVERSE plays a WAVE waveform audio file backwards.
 */

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "reverse.h"
#include "strings.h"

#define MAX_FILENAME_SIZE 128

/* Global variables.
 */
char     szAppName[]    = "Reverse";   // application name
HANDLEhInstApp = NULL;     // instance handle
HWND     hwndApp     = NULL;     // main window handle
HWND     hwndName = NULL;     // filename window handle
HWND     hwndPlay = NULL;     // "Play" button window handle
HWND     hwndQuit = NULL;     // "Exit" button window handle
HWAVEOUT hWaveOut = NULL;
LPWAVEHDR   lpWaveHdr   = NULL;
HPSTR    lpData      = NULL;     // waveform data block
LPSTR    lpstrLoadStrBuf   = NULL;

/* WinMain - Entry point for Reverse.
 */
int PASCAL WinMain(
HINSTANCE hInst,
HINSTANCE hPrev,
LPSTR szCmdLine,
int cmdShow)
{
   MSG      msg;
   WNDCLASS wc;

   hInstApp =  hInst;

   /* Define and register a window class for the main window.
    */
   if (!hPrev)
   {
      wc.hCursor     = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon    = LoadIcon(hInst, szAppName);
      wc.lpszMenuName      = szAppName;
      wc.lpszClassName  = szAppName;
      wc.hbrBackground  = GetStockBrush(LTGRAY_BRUSH);
      wc.hInstance      = hInst;
      wc.style    = 0;
      wc.lpfnWndProc    = WndProc;
      wc.cbWndExtra     = 0;
      wc.cbClsExtra     = 0;

      if (!RegisterClass(&wc))
         return FALSE;
   }
   // Keep string memory out of WIN16 DS.
   lpstrLoadStrBuf = GlobalAllocPtr(GMEM_MOVEABLE, LOADSTRBUFSIZE);
   if(lpstrLoadStrBuf == NULL)
   {
      // don't use LoadString here; it fails when memory is low
      MessageBox(NULL, GetStringRes(IDS_NOMEM),
            NULL, MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
      return FALSE;
   }
   /* Create and show the main window.
    */
   LoadString(hInstApp, IDS_REVERSEWNDTITLE, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   hwndApp = CreateWindow (szAppName,  // class name
         lpstrLoadStrBuf,  // title from string resource
         WS_OVERLAPPEDWINDOW, // style bits
         CW_USEDEFAULT,    // x position
         CW_USEDEFAULT,    // y position
         WMAIN_DX,      // x size
         WMAIN_DY,      // y size
         (HWND)NULL,    // parent window
         (HMENU)NULL,      // use class menu
         (HANDLE)hInst,    // instance handle
         (LPSTR)NULL    // no params to pass on
         );
   /* Create child windows for the "Play" and "Exit" buttons
    * and for an edit field to enter filenames.
    */
   LoadString(hInstApp, IDS_PLAYBUTTONTEXT, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   hwndPlay = CreateWindow( "BUTTON", lpstrLoadStrBuf,
         WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
         PLAY_X, PLAY_Y,
         PLAY_DX, PLAY_DY,
         hwndApp, (HMENU)IDB_PLAY, hInstApp, NULL );
   if( !hwndPlay )
      return( FALSE );

   LoadString(hInstApp, IDS_EXITBUTTONTEXT, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   hwndQuit = CreateWindow( "BUTTON", lpstrLoadStrBuf,
         WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
         QUIT_X, QUIT_Y,
         QUIT_DX, QUIT_DY,
         hwndApp, (HMENU)IDB_QUIT, hInstApp, NULL );
   if( !hwndQuit )
      return( FALSE );

   hwndName = CreateWindow("EDIT","",
         WS_CHILD|WS_VISIBLE|WS_BORDER|ES_AUTOHSCROLL,
         NAME_X, NAME_Y,
         NAME_DX, NAME_DY,
         hwndApp, (HMENU)IDE_NAME, hInstApp, NULL);
   if( !hwndName )
      return( FALSE );
   Edit_LimitText(hwndName, MAX_FILENAME_SIZE - 1);

   ShowWindow(hwndApp,cmdShow);

   /* Add about dialog to system menu.
    */
   LoadString(hInstApp, IDS_ABOUTMENUTEXT, lpstrLoadStrBuf,
      LOADSTRBUFSIZE);
   AppendMenu(GetSystemMenu(hwndApp, FALSE),
      MF_STRING | MF_ENABLED, IDM_ABOUT, lpstrLoadStrBuf);

   /* The main message processing loop. Nothing special here.
   */
   while (GetMessage(&msg,NULL,0,0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   return msg.wParam;
}

/* WndProc - Main window procedure function.
 */
LONG FAR PASCAL WndProc(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
   switch (msg)
   {
      HANDLE_MSG(hWnd, WM_DESTROY, ReverseOnDestroy);

      HANDLE_MSG(hWnd, WM_SYSCOMMAND, ReverseOnSysCommand);

      case WM_SETFOCUS:
         SetFocus(hwndName);
         return 0;

      HANDLE_MSG(hWnd, WM_COMMAND, ReverseOnCommand);
      case MM_WOM_DONE:
         /* This message indicates a waveform data block has
          * been played and can be freed. Clean up the
          * preparation done previously on the header.
          */
         waveOutUnprepareHeader( (HWAVEOUT) wParam,
            (LPWAVEHDR) lParam, sizeof(WAVEHDR) );

         /* free all memory associated with the data block
          */
         cleanup();
         /* Close the waveform output device.
          */
         waveOutClose( (HWAVEOUT) wParam );
         hWaveOut = NULL;

         /* Reenable both button controls.
          */
         EnableWindow( hwndPlay, TRUE );
         EnableWindow( hwndQuit, TRUE );
         SetFocus(hwndName);

         break;
   }
   return MyDefProc(hWnd,msg,wParam,lParam);
}

void ReverseOnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
   /* Process messages sent by the child window controls.
    */
   switch (id)
   {
      case IDE_NAME:    // filename edit control
         return;

      case IDB_PLAY: // "Play" button
         if (codeNotify == BN_CLICKED)
            ReversePlay();
         break;

      case IDB_QUIT: // "Exit" button
         if (codeNotify == BN_CLICKED)
            PostQuitMessage(0);
         break;
   }
   FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, MyDefProc);
}

void ReverseOnDestroy(
HWND hwnd)
{
   if(lpstrLoadStrBuf)
      GlobalFreePtr(lpstrLoadStrBuf);
   if (hWaveOut)
   {
      waveOutReset(hWaveOut);
      waveOutUnprepareHeader(hWaveOut, lpWaveHdr,
         sizeof(WAVEHDR) );
      cleanup();
      waveOutClose(hWaveOut);
   }
   PostQuitMessage(0);
   FORWARD_WM_DESTROY(hwnd, MyDefProc);
}

void ReverseOnSysCommand(
HWND hwnd,
UINT cmd,
int x,
int y)
{
   switch (cmd)
   {
      case IDM_ABOUT:
         /* Show ABOUTBOX dialog box.
          */
         DialogBox(hInstApp, "ABOUTBOX", hwnd, AppAbout);
         break;
   }
   FORWARD_WM_SYSCOMMAND(hwnd, cmd, x, y, MyDefProc);
}

/* AppAbout -- Dialog procedure for ABOUTBOX dialog box.
 */
BOOL FAR PASCAL AppAbout(
HWND hDlg,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
   switch (msg)
   {
      case WM_COMMAND:
         if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK
            || GET_WM_COMMAND_ID(wParam, lParam)
            == IDCANCEL)
            EndDialog(hDlg,TRUE);
         break;

      case WM_INITDIALOG:
         return TRUE;
   }
   return FALSE;
}

/* ReversePlay - Gets a filename from the edit control, then uses
 * the multimedia file I/O services to read data from the requested
 * WAVE file. If the file is a proper WAVE file, ReversePlay() calls
 * the Interchange() function to reverse the order of the waveform
 * samples in the file. It then plays the reversed waveform data.
 *
 * Note that ReversePlay() only handles a single waveform data block.
 * If the requested WAVE file will not fit in a single data block, it
 * will not be played. The size of a single data block depends on the
 * amount of available system memory.
 *
 * Params: void
 *
 * Return: void
 */
void ReversePlay()
{
   HMMIO          hmmio;
   MMCKINFO       mmckinfoParent;
   MMCKINFO       mmckinfoSubchunk;
   DWORD          dwFmtSize;
   char           szFileName[ MAX_FILENAME_SIZE ];
   DWORD          dwResult;
   HANDLE         hFormat;
   WAVEFORMATEX   *pFormat;
   DWORD          dwDataSize;
   HPSTR          hpch1, hpch2;
   WORD           wBlockSize;
   HANDLE         hData    = NULL;

   /* Get the filename from the edit control.
    */
   if (!GetWindowText( hwndName, (LPSTR)szFileName, MAX_FILENAME_SIZE))
   {
      LoadString(hInstApp, IDS_FAILEDTOGETFNAME, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* Open the given file for reading using buffered I/O.
    */
   if(!(hmmio = mmioOpen(szFileName, NULL, MMIO_READ | MMIO_ALLOCBUF)))
   {
      LoadString(hInstApp, IDS_FAILEDTOOPENFILE, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* Locate a 'RIFF' chunk with a 'WAVE' form type
    * to make sure it's a WAVE file.
    */
   mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E');
   if (mmioDescend(hmmio, &mmckinfoParent, NULL, MMIO_FINDRIFF))
   {
      LoadString(hInstApp, IDS_NOTAWAVEFILE, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      mmioClose(hmmio, 0);
      return;
   }

   /* Now, find the format chunk (form type 'fmt '). It should be
    * a subchunk of the 'RIFF' parent chunk.
    */
   mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
   if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent,
      MMIO_FINDCHUNK))
   {
      LoadString(hInstApp, IDS_WAVEFILECORRUPT, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      mmioClose(hmmio, 0);
      return;
   }

   /* Get the size of the format chunk, allocate and lock memory for it.
    */
   dwFmtSize = mmckinfoSubchunk.cksize;
   hFormat = LocalAlloc(LMEM_MOVEABLE, LOWORD(dwFmtSize));
   if (!hFormat)
   {
      MessageBox(hwndApp, GetStringRes(IDS_NOMEM),
            NULL, MB_OK | MB_ICONEXCLAMATION);
      mmioClose(hmmio, 0);
      return;
   }
   pFormat = (WAVEFORMATEX *) LocalLock(hFormat);
   if (!pFormat)
   {
      MessageBox(hwndApp, GetStringRes(IDS_NOMEM_LK),
            NULL, MB_OK | MB_ICONEXCLAMATION);
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      return;
   }

   /* Read the format chunk.
    */
   if (mmioRead(hmmio, (HPSTR) pFormat, dwFmtSize) != (LONG) dwFmtSize)
   {
      LoadString(hInstApp, IDS_FAILEDREADFMTCHNK, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      return;
   }

   /* Make sure it's a PCM file.
    */
   if (pFormat->wFormatTag != WAVE_FORMAT_PCM)
   {
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      LoadString(hInstApp, IDS_NOTAPCMFILE, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* Make sure a waveform output device supports this format.
    */
   #if (WINVER >= 0x0400)
   if (waveOutOpen(&hWaveOut, WAVE_MAPPER, pFormat, 0, 0L,
               WAVE_FORMAT_QUERY))
        #else
   if (waveOutOpen(&hWaveOut, WAVE_MAPPER, (LPWAVEFORMAT)pFormat, 0, 0L,
               WAVE_FORMAT_QUERY))
        #endif
   {
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      LoadString(hInstApp, IDS_CANTPLAYFORMAT, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* Ascend out of the format subchunk.
    */
   mmioAscend(hmmio, &mmckinfoSubchunk, 0);

   /* Find the data subchunk.
    */
   mmckinfoSubchunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
   if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent,
      MMIO_FINDCHUNK))
   {
      LoadString(hInstApp, IDS_NODATACHUNK, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      return;
   }

   /* Get the size of the data subchunk.
    */
   dwDataSize = mmckinfoSubchunk.cksize;
   if (dwDataSize == 0L)
   {
      LoadString(hInstApp, IDS_CHUNKHASNODATA, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      return;
   }

   /* Open a waveform output device.
    */
   #if (WINVER >= 0x0400)
   if (waveOutOpen(&hWaveOut, WAVE_MAPPER,
      pFormat, (UINT)hwndApp, 0L, CALLBACK_WINDOW))
        #else
   if (waveOutOpen(&hWaveOut, WAVE_MAPPER,
      (LPWAVEFORMAT)pFormat, (UINT)hwndApp, 0L, CALLBACK_WINDOW))
        #endif
   {
      LoadString(hInstApp, IDS_FAILEDOPENDEVICE, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      LocalUnlock( hFormat );
      LocalFree( hFormat );
      mmioClose(hmmio, 0);
      return;
   }

   /* Save block alignment info for later use.
    */
   wBlockSize = pFormat->nBlockAlign;

   /* We're done with the format header, free it.
    */
   LocalUnlock( hFormat );
   LocalFree( hFormat );

   /* Allocate and lock memory for the waveform data.
    */
   lpData = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, dwDataSize );
   if (!lpData)
   {
      MessageBox(hwndApp, GetStringRes(IDS_NOMEM_DT),
            NULL, MB_OK | MB_ICONEXCLAMATION);
      mmioClose(hmmio, 0);
      return;
   }

   /* Read the waveform data subchunk.
    */
   if(mmioRead(hmmio, lpData, dwDataSize) != (LONG) dwDataSize)
   {
      LoadString(hInstApp, IDS_FAILEDREADCHUNK, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      GlobalFreePtr( lpData );
      mmioClose(hmmio, 0);
      return;
   }

   /* We're done with the file, close it.
    */
   mmioClose(hmmio, 0);

   /* Reverse the sound for playing.
    */
   hpch1 = lpData;
   hpch2 = lpData + dwDataSize - 1;
   while (hpch1 < hpch2)
   {
      Interchange( hpch1, hpch2, wBlockSize );
      hpch1 += wBlockSize;
      hpch2 -= wBlockSize;
   }

   /* Allocate a waveform data header. The WAVEHDR must be
    * globally allocated and locked.
    */
   lpWaveHdr = (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                  (DWORD) sizeof(WAVEHDR));
   if (!lpWaveHdr)
   {
      GlobalFreePtr( lpData );
      MessageBox(hwndApp, GetStringRes(IDS_NOMEM_HR),
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* If you need instance data for a waveform data block, allocate some
    * memory and store the pointer in lpWaveHdr->dwUser, before the call
    * to waveOutPrepareHeader(). The code inside the #if 0 / #endif, and
    * the commented-out lpWaveHdr->dwUser = ... illustrate this.
    * Don't forget to free the instance memory when you're done with it,
    * or on error bailout.
    */
   #if 0
   lpYourData = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, sizeof(YOURDATA));
   if (!lpYourData)
   {
      GlobalFreePtr( lpData );
      GlobalFreePtr( lpWaveHdr );
      MessageBox(hwndApp, GetStringRes(IDS_NOMEM_IS),
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }
   #endif
   /* Set up WAVEHDR structure and prepare it to be written to wave device.
    */
   lpWaveHdr->lpData = lpData;
   lpWaveHdr->dwBufferLength = dwDataSize;
   lpWaveHdr->dwFlags = 0L;
   lpWaveHdr->dwLoops = 0L;
   // lpWaveHdr->dwUser = (DWORD) lpYourData; // save instance data ptr
   if(waveOutPrepareHeader(hWaveOut, lpWaveHdr, sizeof(WAVEHDR)))
   {
      cleanup();
      LoadString(hInstApp, IDS_UNABLEPREPAREHDR, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);

      return;
   }

   /* Then the data block can be sent to the output device.
    */
   dwResult = waveOutWrite(hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
   if (dwResult != 0)
   {
      waveOutUnprepareHeader( hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
      cleanup();
      LoadString(hInstApp, IDS_FAILEDWRITEDEVICE, lpstrLoadStrBuf,
         LOADSTRBUFSIZE);
      MessageBox(hwndApp, lpstrLoadStrBuf,
            NULL, MB_OK | MB_ICONEXCLAMATION);
      return;
   }

   /* Disable input to the button controls.
    */
   EnableWindow(hwndPlay, FALSE);
   EnableWindow(hwndQuit, FALSE);
}

/* Interchange - Interchanges two samples at the given positions.
 *
 * Params:  hpchPos1 - Points to one sample.
 *       hpchPos2 - Points to the other sample.
 *       wLength  - The length of a sample in bytes.
 *
 * Return: void
 */
void Interchange(
HPSTR hpchPos1,
HPSTR hpchPos2,
WORD wLength)
{
   WORD  wPlace;
   BYTE  bTemp;

   for (wPlace = 0; wPlace < wLength; wPlace++)
   {
      bTemp = hpchPos1[wPlace];
      hpchPos1[wPlace] = hpchPos2[wPlace];
      hpchPos2[wPlace] = bTemp;
   }
}

VOID cleanup(void)
{
   // if you add wave instance data, this is a good place to free it.
   GlobalFreePtr( lpWaveHdr );
   GlobalFreePtr( lpData );
}

/******************************************************************************\
*
*  FUNCTION:    GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:    Load the resource string with the ID given, and return a
*               pointer to it.  Notice that the buffer is common memory so
*               the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


REVERSE.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* reverse.h - Header file for REVERSE sample application.
 *

 */

/*
 *  Constants
 */
#define LOADSTRBUFSIZE 256 // 255 + nul is biggest stringtable string.

#ifndef GET_WM_COMMAND_ID// not in WIN16 windowsx.h
#define GET_WM_COMMAND_ID(wp, lp)               LOWORD(wp)
#endif

// Child window identifiers
#define IDE_NAME        200
#define IDB_PLAY        201
#define IDB_QUIT        202

// Window Position and size definitions
#define WMAIN_DX        207
#define WMAIN_DY        120
#define NAME_DX         180
#define NAME_DY         30
#define NAME_X          10
#define NAME_Y          10
#define PLAY_DX         85
#define PLAY_DY         35
#define PLAY_X          10
#define PLAY_Y          50
#define QUIT_DX         85
#define QUIT_DY         35
#define QUIT_X          105
#define QUIT_Y          50

#define IDM_ABOUT           0x101

/*
 *  Function prototypes
 */
BOOL FAR PASCAL AppAbout(HWND, UINT ,WPARAM, LPARAM);
LONG FAR PASCAL WndProc(HWND, UINT ,WPARAM, LPARAM);
void Interchange(HPSTR, HPSTR, WORD);
void ReversePlay(void);
VOID cleanup(void);

LPTSTR GetStringRes(int id);

// message cracker things
#define MyDefProc    DefWindowProc
void ReverseOnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
void ReverseOnDestroy(HWND hwnd);
void ReverseOnSysCommand(HWND hwnd, UINT cmd, int x, int y);


STRINGS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by STRINGS.RC
//
#define IDS_FAILEDTOOPENFILE            101
#define IDS_FAILEDTOGETFNAME            102
#define IDS_NOTAWAVEFILE                103
#define IDS_WAVEFILECORRUPT             104
#define IDS_FAILEDREADFMTCHNK           105
#define IDS_NOTAPCMFILE                 106
#define IDS_CANTPLAYFORMAT              107
#define IDS_NODATACHUNK                 108
#define IDS_CHUNKHASNODATA              109
#define IDS_FAILEDOPENDEVICE            110
#define IDS_FAILEDREADCHUNK             111
#define IDS_UNABLEPREPAREHDR            112
#define IDS_FAILEDWRITEDEVICE           113
#define IDS_REVERSEWNDTITLE             114
#define IDS_PLAYBUTTONTEXT              115
#define IDS_EXITBUTTONTEXT              116
#define IDS_ABOUTMENUTEXT               117

#define IDS_NOMEM16
#define IDS_NOMEM_LK17
#define IDS_NOMEM_DT18
#define IDS_NOMEM_HR19
#define IDS_NOMEM_IS20

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

Simple application showing the SndPlaySound API.


RESOURCE.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1994 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by SOUNDAPP.RC
//
#define IDS_ABOUT_TITLE                 1
#define IDS_ABOUT_TEXT                  2
#define IDS_FILTERSTRING                3
#define IDS_SELECT_FILE                 4
#define IDD_SOUND_DLG                   101
#define IDR_MENU                        105
#define IDD_DIALOG_ABOUT                108
#define IDC_SYNC                        1000
#define IDC_ASYNC                       1001
#define IDC_NODEFAULT                   1002
#define IDC_LOOP                        1004
#define IDC_NOSTOP                      1005
#define IDC_BUTTON_PLAY                 1006
#define IDC_BUTTON_STOP                 1007
#define IDC_BUTTON_OPEN                 1008
#define IDC_RADIO1                      1012
#define IDC_EDIT_FILE                   1014
#define ID_FILE_EXIT                    40003
#define ID_OPTIONS_SYNC                 40004
#define ID_OPTIONS_ASYNC                40005
#define ID_OPTIONS_NODEFAULT            40006
#define ID_OPTIONS_LOOP                 40008
#define ID_OPTIONS_NOSTOP               40009
#define ID_HELP_ABOUT                   40010
#define ID_FILE_SELECT                  40011
#define IDC_OPTIONS                     -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        110
#define _APS_NEXT_COMMAND_VALUE         40012
#define _APS_NEXT_CONTROL_VALUE         1015
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SOUNDAPP.C

/**************************************************************************
 *
 *SOUNDAPP:
 *
 *A sample app showing the sndPlaySound API
 *
 **************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1994 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
#pragma warning(disable: 4001)
#define __SOUNDAPP_C
#define Unused(var)(var=var)

// header dependancies
#include <windows.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <windowsx.h>
#include "resource.h"

//message cracker prototypes
void SoundApp_OnActivate(HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized);
void SoundApp_OnDestroy(HWND hwnd);
void SoundApp_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
BOOL About_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
BOOL About_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);

//function prototypes
long PASCAL WndProc (HWND, UINT, UINT, LONG);
BOOL CALLBACK AboutDlgProc(HWND hdlg, UINT mMsg, WPARAM wParam, LPARAM lParam);
LRESULT AboutDlgProc2(HWND hdlg, UINT mMsg,WPARAM wParam, LPARAM lParam);

//global variables
HANDLE hInst;
BOOL bRecursing = FALSE;

////////////////////////////////////////////////////////////////////////////////
//WinMain
//
//Purpose: The main procedure
//
//Parameters: HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam,
//int nCmdShow
//
//Returns: int
//////////////////////////////////////////////////////////////////////////////////
int PASCAL WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdParam,
int nCmdShow)

{
static char szAppName[] = "SoundApp";
HWND        hwnd;
MSG         msg;
WNDCLASS    wndclass;

Unused(lpszCmdParam);

if (!hPrevInstance)
{
wndclass.style         = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc   = WndProc;
wndclass.cbClsExtra    = 0 ;
wndclass.cbWndExtra    = DLGWINDOWEXTRA;
wndclass.hInstance     = hInstance;
wndclass.hIcon         = LoadIcon (hInstance, "IDI_SOUNDAPP");
wndclass.hCursor       = LoadCursor (NULL, IDI_APPLICATION);
wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName  = NULL;
wndclass.lpszClassName = szAppName;

RegisterClass (&wndclass);
}

hInst = hInstance;
hwnd = CreateDialog(hInstance, "SoundApp", 0, NULL);
CreateDialog(hInstance, "ID_SOUNDAPP_ABOUT", hwnd, NULL);
ShowWindow (hwnd, nCmdShow);

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}

/////////////////////////////////////////////////////////////////////////
//WndProc
//
//Purpose: The window procedure
//
//Parameters: HWND hwnd, UINT message, UINT wParam, LONG lParam
//
//Returns: long
///////////////////////////////////////////////////////////////////////////
long PASCAL WndProc (
HWND hwnd,
UINT message,
UINT wParam,
LONG lParam)

{
switch (message)
{
HANDLE_MSG(hwnd, WM_DESTROY, SoundApp_OnDestroy);
HANDLE_MSG(hwnd, WM_ACTIVATE, SoundApp_OnActivate);
HANDLE_MSG(hwnd, WM_COMMAND, SoundApp_OnCommand);
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
}

//////////////////////////////////////////////////////////////////////
//SoundApp_OnActivate
//
//Purpose: processes a WM_ACTIVATE message
//Initializes controls in the dialog the first time it is activated:
//Checks Synchronous, disables Loop and Stop, and gets default sound
//from win.ini or registry.
//
//Parameters: HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized
//
//Returns: void
//////////////////////////////////////////////////////////////////////
void SoundApp_OnActivate(
HWND hwnd,
UINT state,
HWND hwndActDeact,
BOOL fMinimized)

{
static BOOL fFirstTime = TRUE;
HMENU hMenu;
char SoundBuff[256];
int i=0;

if (fFirstTime)
{
hMenu = GetMenu(hwnd);
SendMessage(GetDlgItem(hwnd, IDC_SYNC), BM_SETCHECK, 1, 0);
CheckMenuItem(hMenu, ID_OPTIONS_SYNC,MF_CHECKED);
EnableWindow(GetDlgItem(hwnd, IDC_LOOP), FALSE);
EnableMenuItem(hMenu, ID_OPTIONS_LOOP, MF_DISABLED | MF_GRAYED);
EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_STOP), FALSE);
GetProfileString("Sounds", "SystemDefault", ",Default", SoundBuff, 256);
//get rid of everything after the filename
while(SoundBuff[i] != ',' && i<255)
{
i++;
}
SoundBuff[i] = 0;
SendMessage(GetDlgItem(hwnd, IDC_EDIT_FILE), WM_SETTEXT, 0, (LPARAM)SoundBuff);
fFirstTime = FALSE;
}
}

//////////////////////////////////////////////////////////////////////
//SoundApp_OnDestroy
//
//Purpose: processes a WM_DESTROY message
//
//Parameters: handle to the main window
//
//Returns: void
//////////////////////////////////////////////////////////////////////
void SoundApp_OnDestroy(HWND hwnd)
{
PostQuitMessage (0);
}

///////////////////////////////////////////////////////////////////////
//SoundApp_OnCommand
//
//Purpose: processes a WM_COMMAND message
//details below for each command.
//
//Parameters: HWND hwnd, int id, HWND hwndCtl, UINT codeNotify
//
//Returns: void
///////////////////////////////////////////////////////////////////////
void SoundApp_OnCommand(
HWND hwnd,
int id,
HWND hwndCtl,
UINT codeNotify)

{
static LPCTSTR lpszSoundName = NULL;//name of the sound to play
static UINT uFlags = SND_SYNC;//option flags for sndPlaySound
HWND hButton;
HMENU hMenu;
LRESULT nCheckState;
char SoundBuff[256];
static fFirstTime = TRUE;

OPENFILENAME ofn;       // common dialog box structure
char szDirName[256];    // directory string
staticchar szFile[256];       // filename string
char szFileTitle[256];  // file-title string
char szFilter[256];     // filter string
char chReplace;         // string separator for szFilter
int i, cbString;        // integer count variables
char szSelectFile[256];// title of select file dialog

//first get default sound name from win.ini or registry
if (fFirstTime)
{
GetProfileString("Sounds", "SystemDefault", ",Default", SoundBuff, 256);
//get rid of everything after the filename in the ini string
i=0;
while(SoundBuff[i] != ',' && i<255)
{
i++;
}
SoundBuff[i] = 0;
if (i == 0)
{
lpszSoundName = NULL;
}
else
{
lpszSoundName = SoundBuff;
}
fFirstTime = FALSE;
}
hMenu = GetMenu(hwnd);

//if File|Exit is selected, quit
if(id == ID_FILE_EXIT)
{
PostQuitMessage (0);
}

//if Play button is clicked, play sound
if(id == IDC_BUTTON_PLAY)
{
sndPlaySound(lpszSoundName, uFlags);
}

//if Stop button is pushed, stop playing sound
if(id == IDC_BUTTON_STOP)
{
sndPlaySound(NULL, SND_SYNC);
}

//if File|Select File or the Select File button is selected, show the
//file open dialog and get the name of the file to play
if(id == ID_FILE_SELECT || id == IDC_BUTTON_OPEN)
{
//Set up structure for file dialog
GetWindowsDirectory(szDirName, sizeof(szDirName));
szFile[0] = '\0';
LoadString(NULL, IDS_SELECT_FILE, szSelectFile, sizeof(szSelectFile));
cbString = LoadString(NULL, IDS_FILTERSTRING, szFilter, sizeof(szFilter));
chReplace = szFilter[cbString - 1];
for (i = 0; szFilter[i] != '\0'; i++)
{
if (szFilter[i] == chReplace)
szFilter[i] = '\0';
}

ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = szSelectFile;
ofn.lpstrDefExt = NULL;
ofn.Flags = OFN_SHAREAWARE | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

//Put up Open File dialog to get filename
if (GetOpenFileName(&ofn))
{
lpszSoundName = ofn.lpstrFile;
hButton = GetDlgItem(hwnd, IDC_EDIT_FILE);
SendMessage(hButton, WM_SETTEXT, 0, (LPARAM)ofn.lpstrFileTitle);
}
}

//if Help|About is selected, show the About SoundApp dialog
if(id == ID_HELP_ABOUT)
{
FARPROC lpfnAbout;

lpfnAbout = MakeProcInstance((FARPROC)AboutDlgProc, hInst);
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG_ABOUT), hwnd, (DLGPROC)lpfnAbout);
FreeProcInstance(lpfnAbout);
}

//if Options|Sync or the Sync button is selected, set checks and flags
if(id == IDC_SYNC || id == ID_OPTIONS_SYNC)
{
hButton = GetDlgItem(hwnd, IDC_SYNC);
nCheckState = SendMessage(hButton, BM_GETCHECK, 0, 0);
//if Synchronous ws unchecked
if (nCheckState == 0)
{
//check Synchronous checkbox, uncheck Asynchronous checkbox,
//check Synchronous menu, uncheck Asynchronous menu,
//uncheck Loop checkbox and menu,
//disable Loop checkbox and menu,
//disable Stop button, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 1, 0);
SendMessage(GetDlgItem(hwnd, IDC_ASYNC),BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_SYNC,MF_CHECKED);
CheckMenuItem(hMenu, ID_OPTIONS_ASYNC, MF_UNCHECKED);
SendMessage(GetDlgItem(hwnd, IDC_LOOP), BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_LOOP,MF_UNCHECKED);
EnableWindow(GetDlgItem(hwnd, IDC_LOOP), FALSE);
EnableMenuItem(hMenu, ID_OPTIONS_LOOP, MF_DISABLED | MF_GRAYED);
EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_STOP), FALSE);
uFlags = uFlags & ~SND_ASYNC & ~SND_LOOP;
}
}

//if Options|Async or the Async button is selected, set checks and flags
if(id == IDC_ASYNC || id == ID_OPTIONS_ASYNC)
{
hButton = GetDlgItem(hwnd, IDC_ASYNC);
nCheckState = SendMessage(hButton, BM_GETCHECK, 0, 0);
//if Asynchronous was unchecked
if (nCheckState == 0)
{
//check Asynchronous checkbox, uncheck Synchronous checkbox,
//check Asynchronous menu, uncheck Synchronous menu,
//enable Loop checkbox and menu,
//enable Stop button, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 1, 0);
SendMessage(GetDlgItem(hwnd, IDC_SYNC),BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_ASYNC,MF_CHECKED);
CheckMenuItem(hMenu, ID_OPTIONS_SYNC, MF_UNCHECKED);
EnableWindow(GetDlgItem(hwnd, IDC_LOOP), TRUE);
EnableMenuItem(hMenu, ID_OPTIONS_LOOP, MF_ENABLED);
EnableWindow(GetDlgItem(hwnd, IDC_BUTTON_STOP), TRUE);
uFlags = uFlags | SND_ASYNC;
}
}

//if Options|NoDefault or the NoDefault button is selected, set checks and flags
if(id == ID_OPTIONS_NODEFAULT || id == IDC_NODEFAULT)
{
hButton = GetDlgItem(hwnd, IDC_NODEFAULT);
nCheckState = SendMessage(hButton, BM_GETCHECK, 0, 0);
//if NoDefault was unchecked
if (nCheckState == 0)
{
//check NoDefault checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 1, 0);
CheckMenuItem(hMenu, ID_OPTIONS_NODEFAULT, MF_CHECKED);
uFlags = uFlags | SND_NODEFAULT;
}
else
{
//uncheck NoDefault checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_NODEFAULT, MF_UNCHECKED);
uFlags = uFlags& ~SND_NODEFAULT;
}
}

//if Options|Loop or the Loop button is selected, set checks and flags
if(id == ID_OPTIONS_LOOP ||id == IDC_LOOP)
{
hButton = GetDlgItem(hwnd, IDC_LOOP);
nCheckState = SendMessage(hButton, BM_GETCHECK, 0, 0);
//if Loop was unchecked
if (nCheckState == 0)
{
//check Loop checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 1, 0);
CheckMenuItem(hMenu, ID_OPTIONS_LOOP,MF_CHECKED);
uFlags = uFlags | SND_LOOP;
}
else
{
//uncheck Loop checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_LOOP,MF_UNCHECKED);
uFlags = uFlags& ~SND_LOOP;
}
}

//if Options|NoStop or the NoStop button is selected, set checks and flags
if(id == ID_OPTIONS_NOSTOP || id == IDC_NOSTOP)
{
hButton = GetDlgItem(hwnd, IDC_NOSTOP);
nCheckState = SendMessage(hButton, BM_GETCHECK, 0, 0);
//if NoStop was unchecked
if (nCheckState == 0)
{
//check NoStop checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 1, 0);
CheckMenuItem(hMenu, ID_OPTIONS_NOSTOP,MF_CHECKED);
uFlags = uFlags | SND_NOSTOP;
}
else
{
//uncheck NoStop checkbox and menu, and set sndPlaySound flags.
SendMessage(hButton, BM_SETCHECK, 0, 0);
CheckMenuItem(hMenu, ID_OPTIONS_NOSTOP,MF_UNCHECKED);
uFlags = uFlags& ~SND_NOSTOP;
}
}
}

/////////////////////////////////////////////////////////////////////////////
//AboutDlgProc
//
//Purpose: Calls AboutDlgProc2
//This makes it possible to use messge crackers in the dialog procedure
//
//Parameters:HWND hdlg, UINT mMsg, WPARAM wParam, LPARAM lParam
//
//Returns: BOOL
///////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK AboutDlgProc(
HWND hdlg,
UINT mMsg,
WPARAM wParam,
LPARAM lParam)

{
CheckDefDlgRecursion(&bRecursing);
return(SetDlgMsgResult(hdlg, mMsg, AboutDlgProc2(hdlg, mMsg, wParam, lParam)));
}

/////////////////////////////////////////////////////////////////////////////
//AboutDlgProc2
//
//Purpose: handle messages to the About dialog
//
//Parameters: HWND hdlg, UINT mMsg, WPARAM wParam, LPARAM lParam
//
//Returns: LRESULT
///////////////////////////////////////////////////////////////////////////////
LRESULT AboutDlgProc2(
HWND hdlg,
UINT mMsg,
WPARAM wParam,
LPARAM lParam)

    {
switch(mMsg)
{
HANDLE_MSG(hdlg, WM_INITDIALOG, About_OnInitDialog);
HANDLE_MSG(hdlg, WM_COMMAND, About_OnCommand);
default:
return(DefDlgProcEx(hdlg, mMsg, wParam, lParam, &bRecursing));
}
}

/////////////////////////////////////////////////////////////////////////
//About_OnInitDialog
//
//Purpose: Initialize About dialog. Set focus to the OK button.
//
//Parameters: HWND hdlg, HWND hwndFocus, LPARAM lParam
//
//Returns: TRUE
//////////////////////////////////////////////////////////////////////
BOOL About_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam)
{
SetFocus(GetDlgItem(hdlg, IDOK));
return TRUE;
}

/////////////////////////////////////////////////////////////////////////
//About_OnCommand
//
//Purpose: Closes the About dialog when the OK button is clicked
//
//Parameters: HWND hdlg, int id, HWND hwndCtl, UINT codeNotify
//
//Returns: BOOL
//////////////////////////////////////////////////////////////////////
BOOL About_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify)
{
if(id == IDOK)
{
EndDialog(hdlg, TRUE);
return TRUE;
}
return FALSE;
}


README.TXT

Sample AVIFile file handler for waveform audio files.  To install,
execute dseqfile.reg and copy dll to system dir. Can be used with AVIEDIT or AVIVIEW.


EXTRA.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

typedef struct FARSTRUCT {
LPVOIDlp;
LONGcb;
} EXTRA, FAR * LPEXTRA;

HRESULT ReadExtra(
LPEXTRA extra,
DWORD ckid,
LPVOID lpData,
LONG FAR *lpcbData);
HRESULT WriteExtra(
LPEXTRA extra,
DWORD ckid,
LPVOID lpData,
LONG cbData);

HRESULT ReadIntoExtra(
LPEXTRA extra,
HMMIO hmmio,
MMCKINFO FAR *lpck);

LONG FindChunkAndKeepExtras(
LPEXTRA extra,
HMMIO hmmio,
MMCKINFO FAR* lpck,
MMCKINFO FAR* lpckParent,
UINT uFlags);


FACTORY1.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*
**
**FACTORY1.C - All of the class factory stuff for our handler
**
*/
#defineINITGUID
#define INC_OLE2

#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include <mmsystem.h>
#include <vfw.h>

#include "wavefile.h"

HMODULE ghModule = NULL;// global HMODULE/HINSTANCE for resource access
//
// Here are the function prototypes for the methods for the class factory
//
STDMETHODIMP WaveFactoryQueryInterface(
LPCLASSFACTORY pcf,
REFIID iid,
void FAR* FAR* ppv);
STDMETHODIMP_(ULONG) WaveFactoryAddRef(
LPCLASSFACTORY pcf);
STDMETHODIMP_(ULONG) WaveFactoryRelease(
LPCLASSFACTORY pcf);
STDMETHODIMP WaveFactoryCreateInstance(
LPCLASSFACTORY pcf,
IUnknown FAR* pUnknownOuter,
REFIID riid,
void FAR* FAR* ppv);
STDMETHODIMP WaveFactoryLockServer(
LPCLASSFACTORY pcf,
BOOL fLock);
HRESULT WaveFactoryCreate(
REFCLSIDrclsid,
REFIIDriid,
void FAR* FAR*ppv);

//
// And here's the Vtbl...
//
IClassFactoryVtblWaveFactoryVtbl = {
WaveFactoryQueryInterface,
WaveFactoryAddRef,
WaveFactoryRelease,
WaveFactoryCreateInstance,
WaveFactoryLockServer};

/*--------*/

EXTERN_C BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID);
EXTERN_C BOOL APIENTRY DllMain(
HANDLE hModule,
DWORD dwReason,
LPVOID lpReserved )
{
switch( dwReason)
{
case DLL_PROCESS_ATTACH:
if(ghModule == NULL)
ghModule = (HMODULE)hModule;
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

/*--------*/

BOOL FAR PASCAL WEP(
BOOLfSystemExit)
{
return TRUE;
}

/*--------*/

//
// Can we be unloaded? Only if our reference count AND lock counts are both
// zero.
//
STDAPI DllCanUnloadNow(
void)
{
return ResultFromScode((uLockCount || uUseCount) ? S_FALSE : S_OK);
}

/*--------*/

//
// Create a new class factory object.  We must allocate the space for our
// structure ourselves since we're using C.
//
HRESULT WaveFactoryCreate(
REFCLSIDrclsid,
REFIIDriid,
void FAR* FAR*ppv)
{
LPWAVEFACTORYpcf;
IUnknown FAR*pUnknown;
HRESULT hresult;

/*
** Allocate the new class factory object from the heap
*/
pcf = (LPWAVEFACTORY)GlobalAllocPtr(GMEM_MOVEABLE,sizeof(WAVEFACTORY));
if (pcf == NULL)
return ResultFromScode(E_OUTOFMEMORY);

/* Initialize our structure */
pcf->lpVtbl = &WaveFactoryVtbl;
pcf->clsid = (CLSID FAR *)rclsid;
pcf->ulRef = 0;
pUnknown = (IUnknown FAR *)pcf;

/*
** Get the specified interface from the class factory
** (If it's not IClassFactory, it will fail....)
*/
hresult = pUnknown->lpVtbl->QueryInterface(pUnknown, riid, ppv);

if (FAILED(GetScode(hresult)))
GlobalFreePtr(pcf);
return hresult;
}

/*--------*/

//
// Retrieve the class object from the DLL... Create an instance.
//
STDAPI DllGetClassObject(
REFCLSIDrclsid,
REFIIDriid,
void FAR* FAR*ppv)
{
HRESULThresult;

/*
** Create a class factory object...
*/
hresult = WaveFactoryCreate(rclsid, riid, ppv);
return hresult;
}

/*--------*/

//
// Query Interface.. We support Unknown and our own interface
//
STDMETHODIMP WaveFactoryQueryInterface(
LPCLASSFACTORY pcf,
REFIIDiid,
void FAR* FAR*ppv)
{
if (IsEqualIID(iid, &IID_IUnknown))
*ppv = pcf;
else if (IsEqualIID(iid, &IID_IClassFactory))
*ppv = pcf;
else
return ResultFromScode(E_NOINTERFACE);
// remember to increase our reference count
WaveFactoryAddRef(pcf);
return NOERROR;
}

/*--------*/

//
// Increase our reference count
//
STDMETHODIMP_(ULONG) WaveFactoryAddRef(
LPCLASSFACTORY pcf)
{
LPWAVEFACTORY pwf = (LPWAVEFACTORY) pcf;

return ++pwf->ulRef;
}

/*--------*/

//
// Decrease our reference count -- free the memory for the structure if we're
// down to zero.
//
STDMETHODIMP_(ULONG) WaveFactoryRelease(
LPCLASSFACTORY pcf)
{
LPWAVEFACTORY pwf = (LPWAVEFACTORY) pcf;

if (!--pwf->ulRef) {
GlobalFreePtr(pwf);
return 0;
}
return pwf->ulRef;
}

/*--------*/

//
// Create an object of our class
//
STDMETHODIMP WaveFactoryCreateInstance(
LPCLASSFACTORYpcf,
IUnknown FAR*pUnknownOuter,
REFIIDriid,
void FAR* FAR*ppv)
{
// Actually create a real object using the CAVIFile class....
return WaveFileCreate(pUnknownOuter, riid, ppv);
}

/*--------*/

//
// The standard LockServer function.
//
STDMETHODIMP WaveFactoryLockServer(
LPCLASSFACTORYpcf,
BOOLfLock)
{
if (fLock)
uLockCount++;
else
uLockCount--;
return NOERROR;
}

/*--------*/


MULDIV32.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  muldiv32.h
//
//  Description:
//      math routines for 32 bit signed and unsiged numbers.
//
//      MulDiv32(a,b,c) = (a * b) / c         (round down, signed)
//
//      MulDivRD(a,b,c) = (a * b) / c         (round down, unsigned)
//      MulDivRN(a,b,c) = (a * b + c/2) / c   (round nearest, unsigned)
//      MulDivRU(a,b,c) = (a * b + c-1) / c   (round up, unsigned)
//
//==========================================================================;

#ifndef _INC_MULDIV32
#define _INC_MULDIV32

#ifndef INLINE
#define INLINE __inline
#endif

#ifdef _X86_

    //
    //  Use 32-bit x86 assembly.
    //

    #pragma warning(disable:4035 4704)

    INLINE LONG MulDiv32(LONG a,LONG b,LONG c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     imul    ebx              //  imul ebx
        _asm     idiv    ecx              //  idiv ecx
        _asm shld edx, eax, 16     //  shld edx, eax, 16

    } // MulDiv32()

    INLINE DWORD MulDivRN(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     mov     ebx,ecx          //  mov  ebx,ecx
        _asm     shr     ebx,1            //  sar  ebx,1
        _asm     add     eax,ebx          //  add  eax,ebx
        _asm     adc     edx,0            //  adc  edx,0
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDiv32()

    INLINE DWORD MulDivRU(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     mov     ebx,ecx          //  mov  ebx,ecx
        _asm     dec     ebx              //  dec  ebx
        _asm     add     eax,ebx          //  add  eax,ebx
        _asm     adc     edx,0            //  adc  edx,0
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDivRU32()

    INLINE DWORD MulDivRD(DWORD a,DWORD b,DWORD c)
    {
        _asm     mov     eax,dword ptr a  //  mov  eax, a
        _asm     mov     ebx,dword ptr b  //  mov  ebx, b
        _asm     mov     ecx,dword ptr c  //  mov  ecx, c
        _asm     mul     ebx              //  mul  ebx
        _asm     div     ecx              //  div  ecx
        _asm     shld    edx, eax, 16     //  shld edx, eax, 16

    } // MulDivRD32()

    #pragma warning(default:4035 4704)

#else

    //
    //  Use C9 __int64 support for Daytona RISC platforms.
    //

    INLINE LONG MulDiv32( LONG a, LONG b, LONG c )
    {
        return (LONG)( Int32x32To64(a,b) / c );
    }

    INLINE DWORD MulDivRD( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( UInt32x32To64(a,b) / c );
    }

    INLINE DWORD MulDivRN( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( (UInt32x32To64(a,b)+c/2) / c );
    }

    INLINE DWORD MulDivRU( DWORD a, DWORD b, DWORD c )
    {
        return (DWORD)( (UInt32x32To64(a,b)+c-1) / c );
    }

#endif

//
//  some code references these by other names.
//
#define muldiv32    MulDivRN
#define muldivrd32  MulDivRD
#define muldivru32  MulDivRU

#endif  // _INC_MULDIV32


EXTRA.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  EXTRA.C
 *
 *  Routines for reading and managing extra chunks in a RIFF file besides
 *  the actual stream data.
 *
 ***************************************************************************/

#define   NOCOMPMAN
#define   NODRAWDIB
#define   NOVIDEO
#define   NOAVIFMT
#define   NOMMREG
#define   NOMCIWND
#define   NOAVICAP
#define   NOMSACM

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#include <stdarg.h>
#include <vfw.h>

#include "extra.h"

//
// Search the EXTRA chunks for a particular chunk ID and return it's data.
//
HRESULT ReadExtra(
LPEXTRA extra,
DWORD ckid,
LPVOID lpData,
LONG FAR *lpcbData)
{

#define lpdw ((DWORD FAR *) lp)

LPBYTE lp = (LPBYTE) extra->lp;
LONG cb = extra->cb;

while (cb > 0) {
if (lpdw[0] == ckid) {
if (!lpData) {
*lpcbData = lpdw[1];
return AVIERR_OK;
}

hmemcpy(lpData, lp + 2 * sizeof(DWORD), min((LONG) lpdw[1], *lpcbData));
*lpcbData = lpdw[1];

return ResultFromScode(AVIERR_OK);
}
cb -= lpdw[1] + sizeof(DWORD) * 2;
lp += lpdw[1] + sizeof(DWORD) * 2;
}
#undef lpdw
*lpcbData = 0;
return ResultFromScode(AVIERR_NODATA);
}

//
// Write data for the given chunk ID into the extra data
//
HRESULT WriteExtra(
LPEXTRA extra,
DWORD ckid,
LPVOID lpData,
LONG cbData)
{
LPBYTE lp;

cbData += sizeof(DWORD) * 2;
if (extra->lp) {
lp = (LPBYTE) GlobalReAllocPtr(extra->lp, extra->cb + cbData, GMEM_MOVEABLE);
} else {
lp = (LPBYTE) GlobalAllocPtr(GMEM_MOVEABLE, cbData);
}

if (!lp)
return ResultFromScode(AVIERR_MEMORY);

// build RIFF chunk in block
((DWORD FAR *) (lp + extra->cb))[0] = ckid;
((DWORD FAR *) (lp + extra->cb))[1] = cbData - sizeof(DWORD) * 2;

hmemcpy(lp + extra->cb + sizeof(DWORD) * 2,
lpData,
cbData - sizeof(DWORD) * 2);
extra->lp = lp;
extra->cb += cbData;

return ResultFromScode(AVIERR_OK);
}

//
// Reads the data from the given chunk into the EXTRA pile.
//
HRESULT ReadIntoExtra(
LPEXTRA extra,
HMMIO hmmio,
MMCKINFO FAR * lpck)
{
LPBYTElp;
LONGcbData = lpck->cksize + sizeof(DWORD) * 2;

if (extra->lp) {
lp = (LPBYTE) GlobalReAllocPtr(extra->lp, extra->cb + cbData, GMEM_MOVEABLE);
} else {
lp = (LPBYTE) GlobalAllocPtr(GMEM_MOVEABLE, cbData);
}

if (!lp)
return ResultFromScode(AVIERR_MEMORY);

// build RIFF chunk in block
((DWORD FAR *) (lp + extra->cb))[0] = lpck->ckid;
((DWORD FAR *) (lp + extra->cb))[1] = lpck->cksize;

cbData += (cbData & 1);

mmioSeek(hmmio, lpck->dwDataOffset, SEEK_SET);
if (mmioRead(hmmio, (HPSTR) lp + extra->cb + sizeof(DWORD) * 2, lpck->cksize) !=
(LONG) lpck->cksize)
return ResultFromScode(AVIERR_FILEREAD);

extra->lp = lp;
extra->cb += cbData;

return ResultFromScode(AVIERR_OK);
}

//
// Look for a specific chunk.  Throw all of the extra stuff we didn't want
// into the EXTRA pile.
//
LONG FindChunkAndKeepExtras(
LPEXTRA extra, HMMIO hmmio,
MMCKINFO FAR* lpck,
MMCKINFO FAR* lpckParent,
UINT uFlags)
{
FOURCCckidFind;// chunk ID to find (or NULL)
FOURCCfccTypeFind;// form/list type to find (or NULL)
LONGlRet;

/* figure out what chunk id and form/list type to search for */
if (uFlags & MMIO_FINDCHUNK)
ckidFind = lpck->ckid, fccTypeFind = 0;
else if (uFlags & MMIO_FINDRIFF)
ckidFind = FOURCC_RIFF, fccTypeFind = lpck->fccType;
else if (uFlags & MMIO_FINDLIST)
ckidFind = FOURCC_LIST, fccTypeFind = lpck->fccType;
else
ckidFind = fccTypeFind = (FOURCC) -1; // keep looking indefinitely

for (;;) {
lRet = mmioDescend(hmmio, lpck, lpckParent, 0);
if (lRet) {
if (uFlags == 0 && lRet == MMIOERR_CHUNKNOTFOUND)
lRet = 0;
return lRet;
}

if ((!ckidFind || lpck->ckid == ckidFind) &&
(!fccTypeFind || lpck->fccType == fccTypeFind))
return 0;

lRet = (LONG) ReadIntoExtra(extra, hmmio, lpck);
if (lRet != AVIERR_OK)
return lRet;
}
}


WAVEFILE.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  WAVEFILE.C
 *
 *  An implementation in C of an AVI File Handler to read standard windows
 *  WAV files as if they were an AVI file with one audio stream.
 *
 ***************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <ctype.h>
#include <vfw.h>
#include "MulDiv32.h"
#include "wavefile.h"

#define formtypeWAVEmmioFOURCC('W', 'A', 'V', 'E')
#define ckidWAVEFORMATmmioFOURCC('f', 'm', 't', ' ')
#define ckidWAVEDATAmmioFOURCC('d', 'a', 't', 'a')

LPSTR FAR FileName(LPCSTR lszPath);
//
// Function prototypes and Vtbl for the Unknown interface
//
STDMETHODIMP WaveUnknownQueryInterface(LPUNKNOWN pu, REFIID iid, void FAR* FAR* ppv);
STDMETHODIMP_(ULONG) WaveUnknownAddRef(LPUNKNOWN pu);
STDMETHODIMP_(ULONG) WaveUnknownRelease(LPUNKNOWN pu);

IUnknownVtbl UnknownVtbl = {
WaveUnknownQueryInterface,
WaveUnknownAddRef,
WaveUnknownRelease
};

//
// Function prototypes and Vtbl for the AVIFile interface
//
STDMETHODIMP WaveFileQueryInterface(PAVIFILE pf, REFIID iid, void FAR* FAR* ppv);
STDMETHODIMP_(ULONG) WaveFileAddRef(PAVIFILE pf);
STDMETHODIMP_(ULONG) WaveFileRelease(PAVIFILE pf);
STDMETHODIMP WaveFileInfo(PAVIFILE pf, AVIFILEINFOW FAR * pfi, LONG lSize);
STDMETHODIMP WaveFileGetStream(PAVIFILE pf, PAVISTREAM FAR * ppavi, DWORD fccType, LONG lParam);
STDMETHODIMP WaveFileCreateStream(PAVIFILE pf, PAVISTREAM FAR *ppstream, AVISTREAMINFOW FAR *psi);
STDMETHODIMP WaveFileWriteData(PAVIFILE pf, DWORD ckid, LPVOID lpData, LONG cbData);
STDMETHODIMP WaveFileReadData(PAVIFILE pf, DWORD ckid, LPVOID lpData, LONG FAR *lpcbData);
STDMETHODIMP WaveFileEndRecord(PAVIFILE pf);
STDMETHODIMP WaveFileDeleteStream(PAVIFILE pf, DWORD fccType, LONG lParam);

IAVIFileVtbl FileVtbl = {
WaveFileQueryInterface,
WaveFileAddRef,
WaveFileRelease,
WaveFileInfo,
WaveFileGetStream,
WaveFileCreateStream,
WaveFileWriteData,
WaveFileReadData,
WaveFileEndRecord,
WaveFileDeleteStream
};

STDMETHODIMP WavePersistQueryInterface(LPPERSISTFILE pf, REFIID iid, void FAR* FAR* ppv);
STDMETHODIMP_(ULONG) WavePersistAddRef(LPPERSISTFILE pf);
STDMETHODIMP_(ULONG) WavePersistRelease(LPPERSISTFILE pf);
STDMETHODIMP WavePersistGetClassID (LPPERSISTFILE ppf, LPCLSID lpClassID);
STDMETHODIMP WavePersistIsDirty (LPPERSISTFILE ppf);
STDMETHODIMP WavePersistLoad (LPPERSISTFILE ppf,
      LPCOLESTR lpszFileName, DWORD grfMode);
STDMETHODIMP WavePersistSave (LPPERSISTFILE ppf,
      LPCOLESTR lpszFileName, BOOL fRemember);
STDMETHODIMP WavePersistSaveCompleted (LPPERSISTFILE ppf,
       LPCOLESTR lpszFileName);
STDMETHODIMP WavePersistGetCurFile (LPPERSISTFILE ppf,
    LPOLESTR FAR * lplpszFileName);

IPersistFileVtbl PersistVtbl = {
WavePersistQueryInterface,
WavePersistAddRef,
WavePersistRelease,
WavePersistGetClassID,
WavePersistIsDirty,
WavePersistLoad,
WavePersistSave,
WavePersistSaveCompleted,
WavePersistGetCurFile
};

//
// Function prototypes and Vtbl for the AVIStream interface
//
STDMETHODIMP WaveStreamQueryInterface(PAVISTREAM ps, REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP WaveStreamCreate(PAVISTREAM ps, LONG lParam1, LONG lParam2);
STDMETHODIMP_(ULONG) WaveStreamAddRef(PAVISTREAM ps);
STDMETHODIMP_(ULONG) WaveStreamRelease(PAVISTREAM ps);
STDMETHODIMP WaveStreamInfo(PAVISTREAM ps, AVISTREAMINFOW FAR * psi, LONG lSize);
STDMETHODIMP_(LONG) WaveStreamFindSample(PAVISTREAM ps, LONG lPos, LONG lFlags);
STDMETHODIMP WaveStreamReadFormat(PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG FAR *lpcbFormat);
STDMETHODIMP WaveStreamSetFormat(PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG cbFormat);
STDMETHODIMP WaveStreamRead(PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG FAR * plBytes,LONG FAR * plSamples);
STDMETHODIMP WaveStreamWrite(PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpData, LONG cbData, DWORD dwFlags, LONG FAR *plSampWritten, LONG FAR *plBytesWritten);
STDMETHODIMP WaveStreamDelete(PAVISTREAM ps, LONG lStart, LONG lSamples);
STDMETHODIMP WaveStreamReadData(PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG FAR *lpcb);
STDMETHODIMP WaveStreamWriteData(PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG cb);
STDMETHODIMP WaveStreamSetInfo(PAVISTREAM ps, AVISTREAMINFOW FAR * psi, LONG lSize);

IAVIStreamVtbl StreamVtbl = {
WaveStreamQueryInterface,
WaveStreamAddRef,
WaveStreamRelease,
WaveStreamCreate,
WaveStreamInfo,
WaveStreamFindSample,
WaveStreamReadFormat,
WaveStreamSetFormat,
WaveStreamRead,
WaveStreamWrite,
WaveStreamDelete,
WaveStreamReadData,
WaveStreamWriteData,
WaveStreamSetInfo
};

#if !defined UNICODE

int LoadUnicodeString(HINSTANCE hinst, UINT wID, LPWSTR lpBuffer, int cchBuffer)
{
    char    ach[128];
    int    i;

    i = LoadString(hinst, wID, ach, sizeof(ach));

    if (i > 0)
MultiByteToWideChar(CP_ACP, 0, ach, -1, lpBuffer, cchBuffer);

    return i;
}

#else
#define LoadUnicodeString   LoadString
#endif

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/*--------*/

UINTuUseCount;// the reference count for our objects
UINTuLockCount;// our lock count for LockServer

/*--------*/

//
// Create a new instance.  Since this is a C implementation we have to
// allocate space for our structure ourselves.
//
HRESULT WaveFileCreate(
IUnknown FAR*pUnknownOuter,
REFIIDriid,
void FAR* FAR*ppv)
{
IUnknown FAR*pUnknown;
LPWAVESTUFFpWaveStuff;
HRESULThresult;

// Allocate space for our structure
pWaveStuff = (LPWAVESTUFF)GlobalAllocPtr(GMEM_MOVEABLE,
sizeof(WAVESTUFF));
if (!pWaveStuff)
return ResultFromScode(E_OUTOFMEMORY);

// Initialize the Vtbls
pWaveStuff->AVIFile = &FileVtbl;
pWaveStuff->AVIStream = &StreamVtbl;
pWaveStuff->Unknown = &UnknownVtbl;
pWaveStuff->Persist = &PersistVtbl;

// Set up our controlling object
pUnknown = (IUnknown FAR *)&pWaveStuff->Unknown;
if (pUnknownOuter)
pWaveStuff->pUnknownOuter = pUnknownOuter;
else
pWaveStuff->pUnknownOuter =(IUnknown FAR *)&pWaveStuff->Unknown;

// Initial the things in our structure
pWaveStuff->refs = 0;
pWaveStuff->hmmio = NULL;
pWaveStuff->lpFormat = NULL;
pWaveStuff->cbFormat = 0L;
pWaveStuff->fDirty = FALSE;
pWaveStuff->extra.lp = NULL;
pWaveStuff->extra.cb = 0L;

// Call our Query interface to increment our ref count and get a
// pointer to our interface to return.
hresult = pUnknown->lpVtbl->QueryInterface(pUnknown, riid, ppv);

if (FAILED(GetScode(hresult)))
GlobalFreePtr(pWaveStuff);
return hresult;
}

/*--------*/

//
// Query interface from all three interfaces comes here.  We support the
// Unknown interface, AVIStream and AVIFile.
//
STDMETHODIMP WaveUnknownQueryInterface(
LPUNKNOWNpu,
REFIIDiid,
void FAR* FAR*ppv)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_UNKNOWN(pu);

if (IsEqualIID(iid, &IID_IUnknown))
*ppv = (LPVOID)&pWaveStuff->Unknown;
else if (IsEqualIID(iid, &IID_IAVIFile))
*ppv = (LPVOID)&pWaveStuff->AVIFile;
else if (IsEqualIID(iid, &IID_IAVIStream))
*ppv = (LPVOID)&pWaveStuff->AVIStream;
else if (IsEqualIID(iid, &IID_IPersistFile))
*ppv = (LPVOID)&pWaveStuff->Persist;
else
return ResultFromScode(E_NOINTERFACE);
pu->lpVtbl->AddRef(pu);
return NOERROR;
}

/*--------*/

//
// Increase our reference count.  AddRef for all three interfaces comes here.
//
STDMETHODIMP_(ULONG) WaveUnknownAddRef(
LPUNKNOWNpu)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_UNKNOWN(pu);

uUseCount++;
return ++pWaveStuff->refs;
}

/*--------*/

//
// Decrease our reference count.  Release for all three interfaces comes here.
//
STDMETHODIMP_(ULONG) WaveUnknownRelease(
LPUNKNOWN pu)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_UNKNOWN(pu);

uUseCount--;

//
// Ref count is zero.  Close the file.  If we've been writing to it, it's
// clean-up time!
//
if (!--p->refs) {
LONG lRet = AVIERR_OK;

if (p->fDirty) {
MMCKINFO ckRIFF;
MMCKINFO ck;

mmioSeek(p->hmmio, 0, SEEK_SET);

/* create the output file RIFF chunk of form type 'WAVE' */
ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
ckRIFF.cksize = 0L;// let MMIO figure out ck. size
if (mmioCreateChunk(p->hmmio, &ckRIFF, MMIO_CREATERIFF) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

ck.ckid = mmioFOURCC('f', 'm', 't', ' ');
ck.cksize = p->cbFormat;// we know the size of this ck.
if (mmioCreateChunk(p->hmmio, &ck, 0) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

if (mmioWrite(p->hmmio, (HPSTR) p->lpFormat, p->cbFormat) != p->cbFormat)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

/* ascend out of the 'fmt' chunk, back into 'RIFF' chunk */
if (mmioAscend(p->hmmio, &ck, 0) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

// If there was extra stuff here, we need to fill it!
if (mmioSeek(p->hmmio, 0, SEEK_CUR)
+ 2 * (LRESULT)sizeof(DWORD)
!= (LRESULT) p->ckData.dwDataOffset) {
/* create the 'data' chunk that holds the waveform samples */
ck.ckid = mmioFOURCC('J', 'U', 'N', 'K');
ck.cksize = 0;
if (mmioCreateChunk(p->hmmio, &ck, 0) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

mmioSeek(p->hmmio,
p->ckData.dwDataOffset - 2 * sizeof(DWORD),
SEEK_SET);

if (mmioAscend(p->hmmio, &ck, 0) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably
}

/* create the 'data' chunk that holds the waveform samples */
ck.ckid = mmioFOURCC('d', 'a', 't', 'a');
ck.cksize = p->ckData.cksize;
if (mmioCreateChunk(p->hmmio, &ck, 0) != 0)
goto ERROR_CANNOT_WRITE;// cannot write file, probably

mmioSeek(p->hmmio, p->ckData.cksize, SEEK_CUR);

mmioAscend(p->hmmio, &ck, 0);

if (p->extra.cb) {
if (mmioWrite(p->hmmio, (HPSTR) p->extra.lp, p->extra.cb) != p->extra.cb)
goto ERROR_CANNOT_WRITE;
}

if (mmioAscend(p->hmmio, &ckRIFF, 0) != 0)
goto ERROR_CANNOT_WRITE;

if (mmioFlush(p->hmmio, 0) != 0)
goto ERROR_CANNOT_WRITE;
}

goto success;

ERROR_CANNOT_WRITE:
lRet = AVIERR_FILEWRITE;

success:
if (p->hmmio)
mmioClose(p->hmmio, 0);

if (p->lpFormat)
GlobalFreePtr(p->lpFormat);

// Free the memory for our structure.
GlobalFreePtr(p);
return 0;
}
return p->refs;
}

//
// Use our controlling object to call QueryInterface on Unknown
//
STDMETHODIMP WaveFileQueryInterface(
PAVIFILEpf,
REFIIDiid,
void FAR* FAR*ppv)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_FILE(pf);

return pWaveStuff->pUnknownOuter->lpVtbl->QueryInterface(
pWaveStuff->pUnknownOuter, iid, ppv);
}

/*--------*/

//
// Use our controlling object to call AddRef on Unknown
//
STDMETHODIMP_(ULONG) WaveFileAddRef(
PAVIFILEpf)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_FILE(pf);

return pWaveStuff->pUnknownOuter->lpVtbl->AddRef(
pWaveStuff->pUnknownOuter);
}

/*--------*/

//
// Use our controlling object to call Release on Unknown
//
STDMETHODIMP_(ULONG) WaveFileRelease(
PAVIFILEpf)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_FILE(pf);

return pWaveStuff->pUnknownOuter->lpVtbl->Release(
pWaveStuff->pUnknownOuter);
}

/*--------*/

//
// Use our controlling object to call QueryInterface on Unknown
//
STDMETHODIMP WavePersistQueryInterface(
LPPERSISTFILEppf,
REFIIDiid,
void FAR* FAR*ppv)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_PERSIST(ppf);

return pWaveStuff->pUnknownOuter->lpVtbl->QueryInterface(
pWaveStuff->pUnknownOuter, iid, ppv);
}

/*--------*/

//
// Use our controlling object to call AddRef on Unknown
//
STDMETHODIMP_(ULONG) WavePersistAddRef(
LPPERSISTFILEppf)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_PERSIST(ppf);

return pWaveStuff->pUnknownOuter->lpVtbl->AddRef(
pWaveStuff->pUnknownOuter);
}

/*--------*/

//
// Use our controlling object to call Release on Unknown
//
STDMETHODIMP_(ULONG) WavePersistRelease(
LPPERSISTFILEppf)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_PERSIST(ppf);

return pWaveStuff->pUnknownOuter->lpVtbl->Release(
pWaveStuff->pUnknownOuter);
}

/*--------*/

//
// Use our controlling object to call QueryInterface on Unknown
//
STDMETHODIMP WaveStreamQueryInterface(
PAVISTREAMps,
REFIIDiid,
void FAR* FAR*ppv)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_STREAM(ps);

return pWaveStuff->pUnknownOuter->lpVtbl->QueryInterface(
pWaveStuff->pUnknownOuter, iid, ppv);
}

/*--------*/

//
// Use our controlling object to call AddRef on Unknown
//
STDMETHODIMP_(ULONG) WaveStreamAddRef(
PAVISTREAMps)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_STREAM(ps);

return pWaveStuff->pUnknownOuter->lpVtbl->AddRef(
pWaveStuff->pUnknownOuter);
}

/*--------*/

//
// Use our controlling object to call Release on Unknown
//
STDMETHODIMP_(ULONG) WaveStreamRelease(
PAVISTREAMps)
{
// Get a pointer to our structure
LPWAVESTUFF pWaveStuff = WAVESTUFF_FROM_STREAM(ps);

return pWaveStuff->pUnknownOuter->lpVtbl->Release(
pWaveStuff->pUnknownOuter);
}

/*--------*/

#define SLASH(c)((c) == '/' || (c) == '\\')

/*--------------------------------------------------------------+
| FileName  - return a pointer to the filename part of szPath   |
|             with no preceding path.                           |
+--------------------------------------------------------------*/
LPSTR FAR FileName(
LPCSTR lszPath)
{
LPCSTR lszCur;

for (lszCur = lszPath + lstrlen(lszPath); lszCur > lszPath && !SLASH(*lszCur) && *lszCur != ':';)
lszCur = AnsiPrev(lszPath, lszCur);
if (lszCur == lszPath)
return (LPSTR)lszCur;
else
return (LPSTR)(lszCur + 1);
}

/*--------*/

//
// The Open Method for our File interface - Open a WAVE file
//
STDMETHODIMP WaveFileOpen(
PAVIFILE pf,
LPCSTR szFile,
UINT mode)
{
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);
UINTui;
charach[80];

// !!! Assumptions about the AVIFILE.DLL (which calls us):
// We will only see READWRITE mode, never only WRITE mode.

//
// try to open the actual file, first with share, then without.
// You may need to use specific flags in order to open a file
// that's already open by somebody else.
//

// If the first attempt fails, no system error box, please.
ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);
p->hmmio = mmioOpen((LPSTR) szFile, NULL, MMIO_ALLOCBUF | mode);
if (!p->hmmio && ((mode & MMIO_RWMODE) == OF_READ)) {
// if the open fails, try again without the share flags.
mode &= ~(MMIO_SHAREMODE);
p->hmmio = mmioOpen((LPSTR) szFile, NULL, MMIO_ALLOCBUF | mode);
}
SetErrorMode(ui);

//
// Now set up our structure
//

p->mode = mode;

if (!p->hmmio)
goto error;

_fmemset(&p->avistream, 0, sizeof(p->avistream));

// If this is defined, we pretend that the data is at FPSHACK "frames"
// per second in the main header, otherwise we use the sample
// rate of the audio, which looks somewhat strange in MPlayer.
#define FPSHACK1000

_fmemset(&p->avihdr, 0, sizeof(p->avihdr));

#ifdef FPSHACK
//
// Initialize our AVIFILEHEADER
//
p->avihdr.dwRate = FPSHACK;
p->avihdr.dwScale = 1;
#endif

p->avihdr.dwStreams = 1;
LoadUnicodeString(ghModule, IDS_FILETYPE, p->avihdr.szFileType,
sizeof(p->avihdr.szFileType));

//
// Initialize our AVISTREAMHEADER
//
LoadString(ghModule, IDS_STREAMNAME, ach, sizeof(ach));
#if !defined UNICODE
{
    char    achTemp[64];

    wsprintf(achTemp, ach, FileName(szFile));

    MultiByteToWideChar(CP_ACP, 0, achTemp, -1,
p->avistream.szName, 64);
}
#else
wsprintf(p->avistream.szName, ach, FileName(szFile));
#endif

if (mode & OF_CREATE) {// Brand new file
p->avistream.fccType = streamtypeAUDIO;
p->avistream.fccHandler = 0;
p->avistream.dwFlags = 0;
p->avistream.wPriority = 0;
p->avistream.wLanguage = 0;
p->avistream.dwInitialFrames = 0;
p->avistream.dwScale = 0;
p->avistream.dwRate = 0;
p->avistream.dwStart = 0;
p->avistream.dwLength = 0;
p->avistream.dwSuggestedBufferSize = 0;
p->avistream.dwSampleSize = 0;

p->fDirty = TRUE;

} else {// read the existing file to get info

MMCKINFOck;
MMCKINFOckRIFF;
/* Read RIFF chunk */
if (mmioDescend(p->hmmio, &ckRIFF, NULL, 0) != 0)
goto error;

if (ckRIFF.ckid != FOURCC_RIFF)
goto error;

if (ckRIFF.fccType != formtypeWAVE)
goto error;

/* Read WAVE format chunk */
ck.ckid = ckidWAVEFORMAT;
if (FindChunkAndKeepExtras(&p->extra, p->hmmio, &ck, &ckRIFF, MMIO_FINDCHUNK))
goto error;

p->cbFormat = ck.cksize;
p->lpFormat = (LPWAVEFORMAT) GlobalAllocPtr(GMEM_MOVEABLE, ck.cksize);

if (p->lpFormat == NULL)
goto error;

if (mmioRead(p->hmmio,
(HPSTR) p->lpFormat,
(LONG)ck.cksize) != (LONG)ck.cksize)
goto error;

/* Ascend out of stream header */
if (mmioAscend(p->hmmio, &ck, 0) != 0)
goto error;

/* Find big data chunk */
p->ckData.ckid = ckidWAVEDATA;
if (FindChunkAndKeepExtras(&p->extra, p->hmmio, &p->ckData, &ckRIFF, MMIO_FINDCHUNK))
goto error;

p->fDirty = FALSE;

p->avistream.fccType = streamtypeAUDIO;
p->avistream.fccHandler = 0;
p->avistream.dwFlags = 0;
p->avistream.wPriority = 0;
p->avistream.wLanguage = 0;
p->avistream.dwInitialFrames = 0;
p->avistream.dwScale = p->lpFormat->nBlockAlign;
p->avistream.dwRate = p->lpFormat->nAvgBytesPerSec;
p->avistream.dwStart = 0;
p->avistream.dwLength = p->ckData.cksize / p->lpFormat->nBlockAlign;
p->avistream.dwSuggestedBufferSize = 0;
p->avistream.dwSampleSize = p->lpFormat->nBlockAlign;

#ifdef FPSHACK
p->avihdr.dwLength = MulDiv32(p->avistream.dwLength,
p->avistream.dwScale * FPSHACK,
p->avistream.dwRate);
#else
p->avihdr.dwScale = 1;
p->avihdr.dwRate = p->lpFormat->nSamplesPerSec;
p->avihdr.dwLength = MulDiv32(p->ckData.cksize,
p->lpFormat->nSamplesPerSec,
p->lpFormat->nAvgBytesPerSec);
#endif

mmioAscend(p->hmmio, &p->ckData, 0);

// Read extra data at end of file....
if (FindChunkAndKeepExtras(&p->extra, p->hmmio, &ckRIFF, &ck, 0)
!= AVIERR_OK)
goto error;
}

//
// all done return success.
//
return ResultFromScode(0); // success

error:
return ResultFromScode(AVIERR_FILEREAD);
}

//
// Get a stream from the file... Each WAVE file has exactly 1 audio stream.
//
STDMETHODIMP WaveFileGetStream(
PAVIFILE pf,
PAVISTREAM FAR * ppavi,
DWORD fccType,
LONG lParam)
{
int iStreamWant;
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

iStreamWant = (int)lParam;

if (p->lpFormat == NULL)
return ResultFromScode(AVIERR_BADPARAM);

// We only support one stream
if (iStreamWant != 0)
return ResultFromScode(AVIERR_BADPARAM);

// We only support audio streams
if (fccType && fccType != streamtypeAUDIO)
return ResultFromScode(AVIERR_BADPARAM);

// increase the reference count
p->AVIStream->AddRef((PAVISTREAM)&p->AVIStream);

// Return a pointer to our stream Vtbl
*ppavi = (PAVISTREAM) &(p->AVIStream);
return ResultFromScode(AVIERR_OK);
}

STDMETHODIMP WaveFileDeleteStream(PAVIFILE pf, DWORD fccType, LONG lParam)
{
int iStreamWant;
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

iStreamWant = (int)lParam;

if (p->lpFormat == NULL)
return ResultFromScode(AVIERR_BADPARAM);

// We only support one stream
if (iStreamWant != 0)
return ResultFromScode(AVIERR_BADPARAM);

// We only support audio streams
if (fccType && fccType != streamtypeAUDIO)
return ResultFromScode(AVIERR_BADPARAM);

GlobalFreePtr(p->lpFormat);
p->lpFormat = NULL;

return NOERROR;
}

//
// We don't support the Save Method of the File Interface (We don't save)
//
STDMETHODIMP WaveFileSave(
PAVIFILE pf,
LPCSTR szFile,
AVICOMPRESSOPTIONS FAR *lpOptions,
AVISAVECALLBACK lpfnCallback)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

//
// Method to create a stream in a WAVE file.  We only support this for blank
// WAVE files.
//
STDMETHODIMP WaveFileCreateStream(
PAVIFILE pf,
PAVISTREAM FAR *ppstream,
AVISTREAMINFOW FAR *psi)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

// We can't add a second stream to a file
if (p->lpFormat)
return ResultFromScode(AVIERR_UNSUPPORTED);

// We only like audio....
if (psi->fccType != streamtypeAUDIO)
return ResultFromScode(AVIERR_UNSUPPORTED);

// Increase our reference count.
p->AVIStream->AddRef((PAVISTREAM)&p->AVIStream);

p->cbFormat = 0;
p->lpFormat = NULL;

// Return a pointer to our stream Vtbl.
*ppstream = (PAVISTREAM) &(p->AVIStream);

return ResultFromScode(AVIERR_OK);
}

//
// The WriteData Method of the File interface
//
STDMETHODIMP WaveFileWriteData(
PAVIFILE pf,
DWORD ckid,
LPVOID lpData,
LONG cbData)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

// Write the data in the Wave File.
return ResultFromScode(WriteExtra(&p->extra, ckid, lpData, cbData));
}

//
// The ReadData Method of the File interface
//
STDMETHODIMP WaveFileReadData(
PAVIFILE pf,
DWORD ckid,
LPVOID lpData,
LONG FAR *lpcbData)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

// Read the data from the file
return ResultFromScode(ReadExtra(&p->extra, ckid, lpData, lpcbData));
}

//
// The EndRecord Method of the File interface.. this doesn't need to do
// anything.. (no concept of interleaving or packaging streams)
//
STDMETHODIMP WaveFileEndRecord(
PAVIFILE pf)
{
return ResultFromScode(AVIERR_OK);
}

//
// The Info Method of the File interface
//
STDMETHODIMP WaveFileInfo(
PAVIFILE pf,
AVIFILEINFOW FAR * pfi,
LONG lSize)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_FILE(pf);

// Return an AVIFILEHEADER.
hmemcpy(pfi, &p->avihdr, min(lSize, sizeof(p->avihdr)));
return 0;
}

//
// The Create Method of the Stream interface. We can't create streams that
// aren't attached to the file.
//
STDMETHODIMP WaveStreamCreate(
PAVISTREAMps,
LONG lParam1,
LONG lParam2)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

//
// The FindSample Method of the Stream interface
//
STDMETHODIMP_(LONG) WaveStreamFindSample(
PAVISTREAMps,
LONG lPos, LONG lFlags)
{
if (lFlags & FIND_FORMAT) {
if ((lFlags & FIND_NEXT) && lPos > 0)
return -1;
else
return 0;
}

return lPos;
}

//
// The ReadFormat Method of the Stream interface
//
STDMETHODIMP WaveStreamReadFormat(
PAVISTREAMps,
LONG lPos,
LPVOID lpFormat,
LONG FAR *lpcbFormat)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_STREAM(ps);

// No buffer to fill in, this means return the size needed.
if (lpFormat == NULL || *lpcbFormat == 0) {
*lpcbFormat = p->cbFormat;
return 0;
}

// Give them the WAVE format.
hmemcpy(lpFormat, p->lpFormat, min(*lpcbFormat, p->cbFormat));

// Our buffer is too small
if (*lpcbFormat < p->cbFormat)
return ResultFromScode(AVIERR_BUFFERTOOSMALL);

*lpcbFormat = p->cbFormat;

return 0;
}

//
// The Info Method of the Stream interface
//
STDMETHODIMP WaveStreamInfo(
PAVISTREAMps,
AVISTREAMINFOW FAR * psi,
LONG lSize)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_STREAM(ps);

// give them an AVISTREAMINFO
hmemcpy(psi, &p->avistream, min(lSize, sizeof(p->avistream)));
return 0;
}

STDMETHODIMP WaveStreamSetInfo(PAVISTREAM ps, AVISTREAMINFOW FAR * psi, LONG lSize)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/*
invalid lPos return error

if lPos + lSamples is invalid trim lSamples to fit.

lpBuffer == NULL

cbBuffer == 0 && lSamples > 0
return size of lSamples sample.
else
return the exactly the number of bytes and sample
you would have read if lpBuffer was not zero.

NOTE return means fill in *plBytes and *plSamples.

lpBuffer != NULL

lSamples == -1      read convenient amount (just fill buffer)
lSamples == 0       fill buffer with as many samples that will fit.
lSamples >  0       read lSamples (or as much will fit in cbBuffer)

fill in *plBytes   with bytes actualy read
fill in *plSamples with samples actualy read

*/

//
// The Read Method for the Stream Interface - Read some wave data
STDMETHODIMP WaveStreamRead(
PAVISTREAMps,
LONGlStart,
LONGlSamples,
LPVOIDlpBuffer,
LONGcbBuffer,
LONG FAR *plBytes,
LONG FAR *plSamples)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_STREAM(ps);

LONGlSampleSize;
LONGlSeek;
LONGlRead;

// Invalid position
if (lStart < 0 || lStart > (LONG) p->avistream.dwLength) {
ack:
if (plBytes)
*plBytes = 0;
if (plSamples)
*plSamples = 0;
return 0;
}

// Can't read quite this much data
if (lSamples + lStart > (LONG) p->avistream.dwLength)
lSamples = p->avistream.dwLength - lStart;

lSampleSize = p->avistream.dwSampleSize;

// We have fixed-length samples

if (lpBuffer == NULL) {
if (cbBuffer > 0 && lSamples > 0)
// Trim how many samples we'd really be able to read
lSamples = min(lSamples, cbBuffer / lSampleSize);
else if (lSamples <= 0)
    // Use as many as will fit
lSamples = cbBuffer / lSampleSize;
} else {
if (lSamples > 0)
// Trim how many samples we'd really be able to read
lSamples = min(lSamples, cbBuffer / lSampleSize);
else
// Use as many as will fit
lSamples = cbBuffer / lSampleSize;
}

//
// a NULL buffer means return the size buffer needed to read
// the given sample.
//
if (lpBuffer == NULL || cbBuffer == 0) {
if (plBytes)
*plBytes = lSamples * lSampleSize;;
if (plSamples)
*plSamples = lSamples;
return 0;
}

// Buffer too small!
if (cbBuffer < lSampleSize)
goto ack;

// Seek and read

cbBuffer = lSamples * lSampleSize;

lSeek = p->ckData.dwDataOffset + lSampleSize * lStart;
lRead = lSamples * lSampleSize;

if (mmioSeek(p->hmmio, lSeek, SEEK_SET) != lSeek)
goto ack;

if (mmioRead(p->hmmio, (HPSTR) lpBuffer, lRead) != lRead)
goto ack;

//
// success return number of bytes and number of samples read
//
if (plBytes)
*plBytes = lRead;

if (plSamples)
*plSamples = lSamples;

return ResultFromScode(AVIERR_OK);
}

//
// The SetFormat Method of the Stream interface- called on an empty WAVE file
// before writing data to it.
//
STDMETHODIMP WaveStreamSetFormat(
PAVISTREAM ps,
LONG lPos,
LPVOID lpFormat,
LONG cbFormat)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_STREAM(ps);

// We can only do this to an empty wave file
if (p->lpFormat) {
if (cbFormat != p->cbFormat ||
_fmemcmp(lpFormat, p->lpFormat, (int) cbFormat))
return ResultFromScode(AVIERR_UNSUPPORTED);

return NOERROR;
}

// Go ahead and set the format!

p->cbFormat = cbFormat;
p->lpFormat = (LPWAVEFORMAT) GlobalAllocPtr(GMEM_MOVEABLE, cbFormat);

if (p->lpFormat == NULL)
return ResultFromScode(AVIERR_MEMORY);

hmemcpy(p->lpFormat, lpFormat, cbFormat);

p->ckData.dwDataOffset = cbFormat + 7 * sizeof(DWORD);
p->ckData.cksize = 0;
p->avistream.dwScale = p->lpFormat->nBlockAlign;
p->avistream.dwRate = p->lpFormat->nAvgBytesPerSec;
p->avistream.dwLength = 0;
p->avistream.dwSampleSize = p->lpFormat->nBlockAlign;

#ifndef FPSHACK
p->avihdr.dwScale = 1;
p->avihdr.dwRate = p->lpFormat->nSamplesPerSec;
#endif
return ResultFromScode(AVIERR_OK);
}

//
// The Write Method of the Stream interface - write some wave data
//
STDMETHODIMP WaveStreamWrite(
PAVISTREAM ps,
LONG lStart,
LONG lSamples,
LPVOID lpData,
LONG cbData,
DWORD dwFlags,
LONG FAR *plSampWritten,
LONG FAR *plBytesWritten)
{
// Get a pointer to our structure
LPWAVESTUFF p = WAVESTUFF_FROM_STREAM(ps);

if ((p->mode & (OF_WRITE | OF_READWRITE)) == 0)
return ResultFromScode(AVIERR_READONLY);

// < 0 means "at end"
if (lStart < 0)
// !!!
lStart = p->avistream.dwStart + p->avistream.dwLength;

p->fDirty = TRUE;

mmioSeek(p->hmmio,
p->ckData.dwDataOffset +
lStart * p->avistream.dwSampleSize,
SEEK_SET);

if (mmioWrite(p->hmmio, (HPSTR) lpData, cbData) != cbData)
return ResultFromScode(AVIERR_FILEWRITE);

p->avistream.dwLength = max((LONG) p->avistream.dwLength,
lStart + lSamples);

p->ckData.cksize = max(p->ckData.cksize,
lStart * p->avistream.dwSampleSize + cbData);

#ifdef FPSHACK
p->avihdr.dwLength = MulDiv32(p->avistream.dwLength * FPSHACK,
p->avistream.dwScale,
p->avistream.dwRate);
#else
p->avihdr.dwLength = MulDiv32(p->ckData.cksize,
p->lpFormat->nSamplesPerSec,
p->lpFormat->nAvgBytesPerSec);
#endif

if (plSampWritten)
*plSampWritten = lSamples;

if (plBytesWritten)
*plBytesWritten = cbData;

return ResultFromScode(AVIERR_OK);
}

//
// The Delete Method of the Stream interface - we don't cut from wave files
//
STDMETHODIMP WaveStreamDelete(
PAVISTREAM ps,
LONG lStart,
LONG lSamples)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

//
// We also don't support ReadData and WriteData for the Stream Interface
//

STDMETHODIMP WaveStreamReadData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG FAR *lpcb)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP WaveStreamWriteData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG cb)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP WaveFileReserved(
PAVIFILE pf)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP WaveStreamReserved(
PAVISTREAM ps)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

/*      -       -       -       -       -       -       -       -       */

// *** IPersist methods ***
STDMETHODIMP WavePersistGetClassID (LPPERSISTFILE ppf, LPCLSID lpClassID)
{
    // Get a pointer to our structure
    LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

    hmemcpy(lpClassID, &CLSID_AVIWaveFileReader, sizeof(CLSID));
    return NOERROR;
}

// *** IPersistFile methods ***
STDMETHODIMP WavePersistIsDirty (LPPERSISTFILE ppf)
{
    // Get a pointer to our structure
    LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

    return pfile->fDirty ? NOERROR : ResultFromScode(S_FALSE);
}

STDMETHODIMP WavePersistLoad (LPPERSISTFILE ppf,
      LPCOLESTR lpszFileName, DWORD grfMode)
{
    // Get a pointer to our structure
    LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

#if !defined UNICODE
    char    achTemp[256];

    // Internally, we're using ANSI, but this interface is defined
    // to always accept UNICODE under WIN32, so we have to convert.
    WideCharToMultiByte(CP_ACP, 0, lpszFileName, -1,
achTemp, sizeof(achTemp), NULL, NULL);
#else
    #define achTemplpszFileName
#endif

    return WaveFileOpen((PAVIFILE) &pfile->AVIFile, achTemp, (UINT) grfMode);
}

STDMETHODIMP WavePersistSave (LPPERSISTFILE ppf,
      LPCOLESTR lpszFileName, BOOL fRemember)
{
// Get a pointer to our structure
LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

    return ResultFromScode(E_FAIL);
}

STDMETHODIMP WavePersistSaveCompleted (LPPERSISTFILE ppf,
       LPCOLESTR lpszFileName)
{
// Get a pointer to our structure
LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

    return NOERROR;
}

STDMETHODIMP WavePersistGetCurFile (LPPERSISTFILE ppf,
    LPOLESTR FAR * lplpszFileName)
{
    // Get a pointer to our structure
    LPWAVESTUFF pfile = WAVESTUFF_FROM_PERSIST(ppf);

    return ResultFromScode(E_FAIL);
}


WAVEFILE.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  WAVEFILE.H
 *
 *  header file for routines for reading WAVE files
 *
 ***************************************************************************/

/*--------*/
#include "extra.h"
#include "wavefile.rc"

extern HMODULE ghModule; // = NULL; // global HMODULE/HINSTANCE for resource access
/*--------*/

/*
** This class is used to implement a handler for a type of file with only
** one stream.  In this case, we don't have to worry about allocating more
** than one stream object for each file object, so we can combine the
** two together in a single class.
**
*/

HRESULT WaveFileCreate(
IUnknown FAR*pUnknownOuter,
REFIIDriid,
void FAR* FAR*ppv);

typedef struct {

/*
** This implementation of a file handler is done in C, not C++, so a few
** things work differently than in C++.  Our structure contains Vtbls
** (pointer to function tables) for three interfaces... Unknown, AVIStream,
** and AVIFile, as well as our private data we need to implement the
** handler.
**
*/

IAVIStreamVtbl FAR*AVIStream;
IAVIFileVtbl FAR*AVIFile;
IUnknownVtbl FAR*Unknown;
IPersistFileVtbl FAR*Persist;

// This is our controlling object.
IUnknown FAR*pUnknownOuter;

//
// WaveFile instance data
//
HMMIOhmmio;// file I/O

MMCKINFOckData;

LONGrefs;// for UNKNOWN
AVISTREAMINFOWavistream;// for STREAM

LPWAVEFORMATlpFormat;// stream format
LONGcbFormat;
BOOLfDirty;
UINTmode;
EXTRAextra;
AVIFILEINFOWavihdr;
} WAVESTUFF, FAR *LPWAVESTUFF;

/*
** Whenever a function is called with a pointer to one of our Vtbls, we need
** to back up and get a pointer to the beginning of our structure.  Depending
** on which pointer we are passed, we need to back up a different number of
** bytes.  C++ would make this easier, by declaring backpointers.
*/

WAVESTUFF ws;
#define WAVESTUFF_FROM_UNKNOWN(pu)(LPWAVESTUFF)((LPBYTE)(pu) - ((LPBYTE)&ws.Unknown - (LPBYTE)&ws))
#define WAVESTUFF_FROM_FILE(pf)(LPWAVESTUFF)((LPBYTE)(pf) - ((LPBYTE)&ws.AVIFile - (LPBYTE)&ws))
#define WAVESTUFF_FROM_STREAM(ps)(LPWAVESTUFF)((LPBYTE)(ps) - ((LPBYTE)&ws.AVIStream - (LPBYTE)&ws))
#define WAVESTUFF_FROM_PERSIST(ppf)(LPWAVESTUFF)((LPBYTE)(ppf) - ((LPBYTE)&ws.Persist - (LPBYTE)&ws))

/*--------*/

/*
** This class is our version of the IClassFactory interface.
**
** COMPOBJ.DLL expects our DllGetClassObject() function to return an
** IClassFactory object which it can then call to get the object that
** implements the IAVIFile interface to actually open the file.  Whew.
*/
typedef struct {
IClassFactoryVtbl FAR *lpVtbl;

/*
** Data local to this object: reference counts for the object
*/
ULONGulRef;
CLSID FAR *clsid;
} WAVEFACTORY, FAR *LPWAVEFACTORY;

/*--------*/

/*
** These variables help keep track of whether the DLL is still in use,
** so that when our DllCanUnloadNow() function is called, we know what
** to say.
*/

extern UINTuUseCount;
extern UINTuLockCount;

/*--------*/

//
// This is our unique identifier
//
//  NOTE: If you modify this sample code to do something else, you MUST
//    CHANGE THIS!
//
//  Run uuidgen.exe from the tools directory and get your own GUID.
//  DO NOT USE THIS ONE!
//
//
//
DEFINE_GUID(CLSID_AVIWaveFileReader, 0x00020003, 0, 0, 0xC0,0,0,0,0,0,0,0x46);


DDUTIL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddutil.h"

/*
 *  DDLoadBitmap
 *
 *  create a DirectDrawSurface from a bitmap resource.
 *
 */
extern "C" IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap, int dx, int dy)
{
    HBITMAP             hbm;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    IDirectDrawSurface *pdds;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, dx, dy, LR_CREATEDIBSECTION);

    if (hbm == NULL)
        hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, dx, dy, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
        return NULL;

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);      // get size of bitmap

    //
    // create a DirectDrawSurface for this bitmap
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
        return NULL;

    DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);

    DeleteObject(hbm);

    return pdds;
}

/*
 *  DDReLoadBitmap
 *
 *  load a bitmap from a file or resource into a directdraw surface.
 *  normaly used to re-load a surface after a restore.
 *
 */
HRESULT DDReLoadBitmap(IDirectDrawSurface *pdds, LPCSTR szBitmap)
{
    HBITMAP             hbm;
    HRESULT             hr;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (hbm == NULL)
        hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
    {
        OutputDebugString("handle is null\n");
        return E_FAIL;
    }

    hr = DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);
    if (hr != DD_OK)
    {
        OutputDebugString("ddcopybitmap failed\n");
    }

    DeleteObject(hbm);
    return hr;
}

/*
 *  DDCopyBitmap
 *
 *  draw a bitmap into a DirectDrawSurface
 *
 */
extern "C" HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy)
{
    HDC                 hdcImage;
    HDC                 hdc;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    HRESULT             hr;

    if (hbm == NULL || pdds == NULL)
        return E_FAIL;

    //
    // make sure this surface is restored.
    //
    pdds->Restore();

    //
    //  select bitmap into a memoryDC so we can use it.
    //
    hdcImage = CreateCompatibleDC(NULL);
    if (!hdcImage)
        OutputDebugString("createcompatible dc failed\n");
    SelectObject(hdcImage, hbm);

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);    // get size of bitmap
    dx = dx == 0 ? bm.bmWidth  : dx;    // use the passed size, unless zero
    dy = dy == 0 ? bm.bmHeight : dy;

    //
    // get size of surface.
    //
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
    pdds->GetSurfaceDesc(&ddsd);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)
    {
        StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);
        pdds->ReleaseDC(hdc);
    }

    DeleteDC(hdcImage);

    return hr;
}

//
//  DDLoadPalette
//
//  Create a DirectDraw palette object from a bitmap resoure
//
//  if the resource does not exist or NULL is passed create a
//  default 332 palette.
//
extern "C" IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap)
{
    IDirectDrawPalette* ddpal;
    int                 i;
    int                 n;
    int                 fh;
    HRSRC               h;
    LPBITMAPINFOHEADER  lpbi;
    PALETTEENTRY        ape[256];
    RGBQUAD *           prgb;

    //
    // build a 332 palette as the default.
    //
    for (i=0; i<256; i++)
    {
        ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
        ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
        ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
        ape[i].peFlags = (BYTE)0;
    }

    //
    // get a pointer to the bitmap resource.
    //
    if (szBitmap && (h = FindResource(NULL, szBitmap, RT_BITMAP)))
    {
        lpbi = (LPBITMAPINFOHEADER)LockResource(LoadResource(NULL, h));
        if (!lpbi)
            OutputDebugString("lock resource failed\n");
        prgb = (RGBQUAD*)((BYTE*)lpbi + lpbi->biSize);

        if (lpbi == NULL || lpbi->biSize < sizeof(BITMAPINFOHEADER))
            n = 0;
        else if (lpbi->biBitCount > 8)
            n = 0;
        else if (lpbi->biClrUsed == 0)
            n = 1 << lpbi->biBitCount;
        else
            n = lpbi->biClrUsed;

        //
        //  a DIB color table has its colors stored BGR not RGB
        //  so flip them around.
        //
        for(i=0; i<n; i++ )
        {
            ape[i].peRed   = prgb[i].rgbRed;
            ape[i].peGreen = prgb[i].rgbGreen;
            ape[i].peBlue  = prgb[i].rgbBlue;
            ape[i].peFlags = 0;
        }
    }
    else if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)
    {
        BITMAPFILEHEADER bf;
        BITMAPINFOHEADER bi;

        _lread(fh, &bf, sizeof(bf));
        _lread(fh, &bi, sizeof(bi));
        _lread(fh, ape, sizeof(ape));
        _lclose(fh);

        if (bi.biSize != sizeof(BITMAPINFOHEADER))
            n = 0;
        else if (bi.biBitCount > 8)
            n = 0;
        else if (bi.biClrUsed == 0)
            n = 1 << bi.biBitCount;
        else
            n = bi.biClrUsed;

        //
        //  a DIB color table has its colors stored BGR not RGB
        //  so flip them around.
        //
        for(i=0; i<n; i++ )
        {
            BYTE r = ape[i].peRed;
            ape[i].peRed  = ape[i].peBlue;
            ape[i].peBlue = r;
        }
    }

    pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);

    return ddpal;
}

/*
 * DDColorMatch
 *
 * convert a RGB color to a pysical color.
 *
 * we do this by leting GDI SetPixel() do the color matching
 * then we lock the memory and see what it got mapped to.
 */
extern "C" DWORD DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb)
{
    COLORREF rgbT;
    HDC hdc;
    DWORD dw = CLR_INVALID;
    DDSURFACEDESC ddsd;
    HRESULT hres;

    //
    //  use GDI SetPixel to color match for us
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
        rgbT = GetPixel(hdc, 0, 0);             // save current pixel value
        SetPixel(hdc, 0, 0, rgb);               // set our value
        pdds->ReleaseDC(hdc);
    }

    //
    // now lock the surface so we can read back the converted color
    //
    ddsd.dwSize = sizeof(ddsd);
    while ((hres = pdds->Lock(NULL, &ddsd, 0, NULL)) == DDERR_WASSTILLDRAWING)
        ;

    if (hres == DD_OK)
    {
        dw  = *(DWORD *)ddsd.lpSurface;                     // get DWORD
        dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount)-1;  // mask it to bpp
        pdds->Unlock(NULL);
    }

    //
    //  now put the color that was there back.
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
        SetPixel(hdc, 0, 0, rgbT);
        pdds->ReleaseDC(hdc);
    }

    return dw;
}

/*
 * DDSetColorKey
 *
 * set a color key for a surface, given a RGB.
 * if you pass CLR_INVALID as the color key, the pixel
 * in the upper-left corner will be used.
 */
extern "C" HRESULT DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb)
{
    DDCOLORKEY          ddck;

    ddck.dwColorSpaceLowValue  = DDColorMatch(pdds, rgb);
    ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;
    return pdds->SetColorKey(DDCKEY_SRCBLT, &ddck);
}


DDUTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif  /* __cplusplus */

extern IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap);
extern IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap, int dx, int dy);
extern HRESULT              DDReLoadBitmap(IDirectDrawSurface *pdds, LPCSTR szBitmap);
extern HRESULT              DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy);
extern DWORD                DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb);
extern HRESULT              DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb);

#ifdef __cplusplus
}
#endif  /* __cplusplus */


DONUT.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donut.cpp
 *
 *  This sample takes the ddraw donut sample from the DirectX SDK and
 *  adds two animating text strings to it using DirectX Animation.
 *
 *  It shows how one can add DXA animation to an existing direct draw
 *  application.  Since DXA can render to a direct draw surface, this sample
 *  creates an offscreen surface and hands it to DXA.  Two DXA views are
 *  created, one with a rotating text string (view1), the other with a
 *  text string moving up and down (view2).

 *  On every frame, four things are performed:
 *  1) view1 is asked to render (to the offscreen surface)
 *  2) blt donut to the same offscreen surface
 *  3) view2 is asked to render (to the offscreen surface)
 *  4) blt from the offscreen surface to the primary.
 *
 *  Creating multiple views allows DXA animation to be both under and
 *  above the donut.
 *
 ***************************************************************************/

#define NAME "Donut"
#define TITLE "Donut"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddrawex.h>
#include "ddutil.h"
#include "dxa.h"
#include "resource.h"

char szBitmap[] = "DONUT";

BOOL            gExclusive = FALSE;
BOOL            bPaused = FALSE;
HWND            hWndMain;

IDirectDrawFactory      *lpDDF;
LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSDA;        // Offscreen surface for DA
LPDIRECTDRAWSURFACE     lpDDSDonut;     // Offscreen surface for donut
LPDIRECTDRAWPALETTE     lpDDPal;        // DirectDraw palette
LPDIRECTDRAWCLIPPER     lpClipper;      // Clipper for the primary surface
BOOL                    bActive;        // is application active?
RECT                    windowRect;     // windows' client rectangle

/*
 * releaseSurfaces
 *
 * Release the global surfaces and clipper.
 */
void releaseSurfaces( void )
{
    if( lpDDSPrimary != NULL )
    {
        lpDDSPrimary->Release();
        lpDDSPrimary = NULL;
    }
    if( lpDDSDA != NULL )
    {
        lpDDSDA->Release();
        lpDDSDA = NULL;
    }
    if( lpDDSDonut != NULL )
    {
        lpDDSDonut->Release();
        lpDDSDonut = NULL;
    }
    if( lpClipper != NULL )
    {
        lpClipper->Release();
        lpClipper = NULL;
    }
}

/*
 * finiObjects
 *
 * Finish with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
        releaseSurfaces();
        if( lpDDPal != NULL )
        {
            lpDDPal->Release();
            lpDDPal = NULL;
        }
        lpDD->Release();
        lpDD = NULL;
    }

    // Clean up the screen on exit
    RedrawWindow( NULL, NULL, NULL, RDW_INVALIDATE | RDW_ERASE |
                     RDW_ALLCHILDREN );

} /* finiObjects */

/*
 * initFail
 *
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hWnd, LPCTSTR msg )
{
    finiObjects();
    MessageBox( hWnd, msg, TITLE, MB_OK );
    DestroyWindow( hWnd );
    return FALSE;

} /* initFail */

/*
 * createSurfaces
 *
 * Create the front and back buffers.
 */
BOOL createSurfaces( void )
{
    HRESULT             ddrval;
    DDSURFACEDESC       ddsd;

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
        return false;

    if(!gExclusive)
    {
        //
        // now create a DirectDrawClipper object.
        //
        ddrval = lpDD->CreateClipper(0, &lpClipper, NULL);

        if( ddrval == DD_OK )
        {
            ddrval = lpClipper->SetHWnd(0, hWndMain);

            if( ddrval == DD_OK )
            {
                ddrval = lpDDSPrimary->SetClipper(lpClipper);
            }
        }
        if( ddrval != DD_OK )
            return false;
    }

    //
    // Create a DirectDrawSurface to hand to Direct Animation.
    //
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = WINDOW_WIDTH;
    ddsd.dwHeight = WINDOW_HEIGHT;

    ddrval = lpDD->CreateSurface(&ddsd, &lpDDSDA, NULL);
    if (ddrval != DD_OK)
        return false;

    lpDDPal = DDLoadPalette(lpDD, szBitmap);
    if (lpDDPal)
    {
        lpDDSPrimary->SetPalette(lpDDPal);
    }

    lpDDSDonut = DDLoadBitmap(lpDD, szBitmap, 0, 0);
    if( lpDDSDonut == NULL )
    {
        return false;
    }

    // set color key to black
    DDSetColorKey(lpDDSDonut, RGB(0,0,0));
    return true;
} /* createSurfaces */

/*
 * recreateSurfaces
 *
 * The screen resolution or bitdepth has changed, re-create
 * all surfaces.
 */
BOOL recreateSurfaces( void )
{
    releaseSurfaces();

    if (createSurfaces())
    {
        return resetDXASurfaces(lpDDSDA);
    }
    return false;
} /* recreateSurfaces */

/*
 * restoreAll
 *
 * Restore all lost objects
 */
BOOL restoreAll( void )
{
    HRESULT     ddrval;

    ddrval = lpDDSPrimary->Restore();
    if (ddrval == DDERR_WRONGMODE)
    {
        if (recreateSurfaces())
            return true;

    }
    else if (ddrval == DD_OK)
    {
        ddrval = lpDDSDA->Restore();
        if( ddrval == DD_OK )
        {
            ddrval = lpDDSDonut->Restore();
            if( ddrval == DD_OK )
            {
                DDReLoadBitmap(lpDDSDonut, szBitmap);
                return true;
            }
        }
    }

    return initFail(hWndMain, "DirectDraw: Can't Recreate Surfaces");
} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORD        lastTickCount = 0;
    static int          currentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORD               thisTickCount;
    DWORD               delay = 17;
    HRESULT             ddrval;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= delay)
    {
        return;
    }

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) > delay)
    {
        // Move to next frame;
        lastTickCount = thisTickCount;
        currentFrame++;
        if(currentFrame > 59)
        {
            currentFrame = 0;
        }
    }

    // DA doesn't like lost surface.  We should restore lost surface
    // before asking DA to render.
    if (lpDDSDA && lpDDSDA->IsLost() == DDERR_SURFACELOST)
    {
        // This function will destroy the window if failed.
        if (!restoreAll())
            return;
    }

    // Ask DA to render to the DA surface the next image/sound for view1.
    tick(view1);

    // The image in the DA model (defined in dxa.cpp) has a background
    // image, so we don't need to clear the background before we draw here.
    RECT  donutSrcRect;
    donutSrcRect.left   = currentFrame%10*64;
    donutSrcRect.top    = currentFrame/10*64;
    donutSrcRect.right  = currentFrame%10*64 + 64;
    donutSrcRect.bottom = currentFrame/10*64 + 64;

    // Blt the donut to the center of the DDSDonut surface.
    RECT donutDestRect;
    donutDestRect.left   = WINDOW_WIDTH/2-32;
    donutDestRect.top    = WINDOW_HEIGHT/2-32;
    donutDestRect.right  = donutDestRect.left + 64;
    donutDestRect.bottom = donutDestRect.top + 64;

    // Blit the donut for the next frame to the DA surface.
    while( 1 )
    {
        ddrval = lpDDSDA->Blt(&donutDestRect, lpDDSDonut,
            &donutSrcRect, DDBLT_WAIT|DDBLT_KEYSRC, NULL);

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            ddrval = restoreAll();
            if( ddrval != DD_OK )
            {
                return;
            }
        }

        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }

    // Ask DA to render to the DA surface the next image/sound for view2.
    tick(view2);

    // Blit from the DA surface to the screen.
    RECT daRect;
    daRect.left = 0;
    daRect.top = 0;
    daRect.right = WINDOW_WIDTH;
    daRect.bottom = WINDOW_HEIGHT;
    RECT screenRect;
    screenRect.left = windowRect.left;
    screenRect.top = windowRect.top;
    screenRect.right = windowRect.left + WINDOW_WIDTH;
    screenRect.bottom = windowRect.top + WINDOW_HEIGHT;

    while( 1 )
    {
        ddrval = lpDDSPrimary->Blt(&screenRect, lpDDSDA,
                                   &daRect, DDBLT_WAIT, NULL);

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            ddrval = restoreAll();
            if( ddrval != DD_OK )
            {
                return;
            }
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }

} /* updateFrame */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
                            WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
    case WM_SIZE:
    case WM_MOVE:
        if (IsIconic(hWnd))
        {
            bPaused = true;
        }

        if (!gExclusive)
        {
            GetClientRect(hWnd, &windowRect);
            ClientToScreen(hWnd, (LPPOINT)&windowRect);
            ClientToScreen(hWnd, (LPPOINT)&windowRect+1);
        } else {
            SetRect(&windowRect, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        }
        break;

    case WM_ACTIVATEAPP:
        bActive = wParam && GetForegroundWindow() == hWnd;

        // Unpause when we're no longer iconic.
        if (bPaused && bActive)
        {
            bPaused = false;
        }
        break;

    case WM_SETCURSOR:
        if (gExclusive && bActive)
        {
            SetCursor(NULL);
            return TRUE;
        }
        break;

    case WM_PALETTECHANGED:
        if ((HWND)wParam == hWnd)
            break;
        // fall through to WM_QUERYNEWPALETTE
    case WM_QUERYNEWPALETTE:
        // install our palette here
        if (lpDDPal)
        {
            lpDDSPrimary->SetPalette(lpDDPal);
        }
        DDReLoadBitmap(lpDDSDonut, szBitmap);
        break;

    case WM_CREATE:
        break;

    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd,WM_CLOSE,0,0);
            break;
        }
        break;

    case WM_DESTROY:
        finiObjects();
        PostQuitMessage( 0 );
        break;
    }

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

} /* WindowProc */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASS            wc;
    HRESULT             ddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    DWORD wndStyle = WS_OVERLAPPEDWINDOW;
    if( gExclusive)
        wndStyle = WS_POPUP;

    hWndMain = CreateWindowEx(
        WS_EX_APPWINDOW,
        NAME,
        TITLE,
        wndStyle,
        0,
        0,
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    ShowWindow( hWndMain, nCmdShow );
    UpdateWindow( hWndMain );

    /*
     * create the main DirectDraw object
     */

    ddrval = CoCreateInstance(CLSID_DirectDrawFactory,
                              NULL, CLSCTX_INPROC_SERVER,
                              IID_IDirectDrawFactory,
                              (void **) & lpDDF);

    char strDDFailed[] = "DirectDraw Init Failed";
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    ddrval = lpDDF->CreateDirectDraw(NULL, hWndMain, DDSCL_NORMAL, 0, NULL, &lpDD);
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    // Get exclusive mode if requested
    if(gExclusive)
    {
        ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    }
    else
    {
        ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_NORMAL );
    }
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    if (!createSurfaces())
    {
        return initFail(hWndMain, strDDFailed);
    }

    // Create a DAView object and construct the model.
    if (!initDXAViewObj(lpDDSDA))
    {
        return initFail(hWndMain, "DirectX Animation Init FAILED");
    }

    return TRUE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;
    LPSTR       c;

    for(c=lpCmdLine; *c != '\0'; c++)
    {
        switch( *c )
        {
            case 'X': gExclusive = TRUE; break;
        }
    }

    if( !doInit( hInstance, nCmdShow ) )
    {
        return FALSE;
    }

    while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( !GetMessage( &msg, NULL, 0, 0 ) )
                return msg.wParam;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if (!bPaused && (bActive || !gExclusive))
        {
            updateFrame();
        }
        else
        {
            WaitMessage();
        }
    }
} /* WinMain */


DXA.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dxa.cpp
 *
 *  This file contains functions that make use of DirectX Animation
 *  to render animation to a ddraw surface.
 *
 ***************************************************************************/

#include "dxa.h"
#include <stdio.h>
#include <sys\timeb.h>
#include <tchar.h>

IDAViewPtr view1 = NULL;
IDAViewPtr view2 = NULL;
/*
 * StartOle
 *
 * Start the Ole services.
 */
struct StartOle {
    StartOle() { CoInitialize( NULL ); }
    ~StartOle() { CoUninitialize(); }
} _inst_StartOle;

/*
 * dump_com_error
 *
 * Dump the com error should an exception occurs in InitDXAViewObj.
 * This is mainly for debugging purpose.
 */
void dump_com_error( _com_error &e )
{
    char buf[2048];

    sprintf(buf, _T( "Oops - hit an error!\n\tCode = %08lx\n\tCode meaning = %s\n" ),
            e.Error(), e.ErrorMessage());
    OutputDebugString(buf);
}

/*
 * initDXAViewObj
 *
 * Create the DAView object, construct the model, then start the model.
 *
 */
BOOL initDXAViewObj(IUnknown *ddsurf)
{
    try {
        IDAStaticsPtr e;

        // Create the statics object.
        e.CreateInstance(L"DirectAnimation.DAStatics");

        // Create the two DAView objects.  One will be rendered under
        // the donut, the other above it.
        view1.CreateInstance(L"DirectAnimation.DAView");
        view2.CreateInstance(L"DirectAnimation.DAView");

        // Construct the image for model1, which will be rendered
        // under the donut.
        IDAColorPtr col1 = e->ColorHslAnim(e->LocalTime, e->DANumber(0.5),
                                                         e->DANumber(0.5));
        IDAFontStylePtr font = e->Font("Helvetica", 14, col1);
        IDAImagePtr txtImg1 = e->StringImage("DXA Under Donut", font);
        IDAImagePtr model1 = txtImg1->Transform(e->Rotate2Anim(e->LocalTime));

        // Tell the DAView object where to render the model.
        view1->SetViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        view1->IDirectDrawSurface = ddsurf;
        view1->CompositeDirectlyToTarget = TRUE;
        view1->StartModel(e->Overlay(model1, e->SolidColorImage(e->Black)), e->Silence, 0);

        // Construct the image for model2, which will be rendered
        // above the donut.
        IDAImagePtr txtImg2 = e->StringImage("DXA Above Donut", font);
        IDAImagePtr model2 = txtImg2->Transform(e->Translate2Anim(
                                                e->DANumber(0),
                                                e->Mul(e->Sin(e->LocalTime),
                                                       e->DANumber(0.01))));

        view2->SetViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        view2->IDirectDrawSurface = ddsurf;
        view2->CompositeDirectlyToTarget = TRUE;
        view2->StartModel(model2, e->Silence, 0);
    } catch (_com_error &e) {
        dump_com_error( e );
        return false;
    }
    return true;
}

BOOL resetDXASurfaces(IUnknown *ddsurf)
{
    try {
        view1->IDirectDrawSurface = ddsurf;
        view2->IDirectDrawSurface = ddsurf;
    } catch (_com_error &e) {
        dump_com_error( e );
        return false;
    }
    return true;
}

/*
 * tick
 *
 * Ask DA to sample and display the model.
 *
 */
void tick(IDAViewPtr view) {
    static double startTime = 0;
    struct _timeb timebuffer;

    if (view != NULL) {
        _ftime( &timebuffer );
        double thisTime = timebuffer.time + ( timebuffer.millitm / 1000.0 );

        if ( startTime == 0 )  {
            startTime = thisTime;
            thisTime = 0;
        }
        else
            thisTime -= startTime;

        view->Tick(thisTime);
        view->Render();
    }
}


DXA.H

#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

using namespace DAnim;

BOOL initDXAViewObj(IUnknown *ddsurf);
BOOL resetDXASurfaces(IUnknown *ddsurf);
void tick(IDAViewPtr view);

extern IDAViewPtr view1, view2;
#define WINDOW_WIDTH    150
#define WINDOW_HEIGHT   150


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Donut.rc
//
#define IDI_ICON1                       101
#define IDI_ICON2                       102

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


CTLHOST.CPP

//////////////////////////////////////////////////////////////////////////
//
//  controlhost.cpp
//
//  This file contains the implementation of the CControlHost class.
//  This class is used to host ActiveX Controls.
//
//  (C) Copyright 1997 by Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////

#define INITGUID
#include <exdispid.h>
#include <olectl.h>
#include "dactl.h"
#include "ctlhost.h"
#include <comdef.h>

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::CControlHost                      [CONSTRUCTOR]
//
CControlHost::CControlHost(IUnknown *pUnk)
    :_hwnd(NULL), _punkOuter(NULL),
    _cRef(0), _punk(NULL), _pvc(NULL),
    _dwTBSink(0)
{
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::~CControlHost                     [DESTRUCTOR]
//
CControlHost::~CControlHost()
{
    DeleteControl();    // insurance
}

//////////////////////////////////////////////////////////////////////////
//
//
//
HRESULT CControlHost::SetHwnd(HWND hwnd)
{
    _hwnd = hwnd;
    if (!GetClientRect(_hwnd, &_rcPos))
        SetRectEmpty(&_rcPos);

    return S_OK;
}
//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::QueryInterface                    [IUnknown]
//
HRESULT CControlHost::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    if (ppvObj == NULL)
        return E_INVALIDARG;

    *ppvObj = NULL;

    if (IsEqualIID(riid, IID_IOleInPlaceSite))
        *ppvObj = (IOleInPlaceSite *)this;
    else if (IsEqualIID(riid, IID_IOleControlSite))
        *ppvObj = (IOleControlSite *)this;
    else if (IsEqualIID(riid, IID_IOleInPlaceSiteWindowless))
        *ppvObj = (IOleInPlaceSiteWindowless *)this;
    else if (IsEqualIID(riid, IID_IDispatch))
        *ppvObj = (IDispatch *)this;
    else
        return (E_NOINTERFACE);

    if(*ppvObj)
        AddRef();

    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::AddRef                            [IUnknown]
//
ULONG CControlHost::AddRef()
{
    return (_cRef++);
}

ULONG CControlHost::Release()
{
    _cRef--;

    if (_cRef > 0)
        return (_cRef);

    _cRef = 0;
    delete this;

    return (0);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SaveObject                        [IOleClientSite]
//
HRESULT CControlHost::SaveObject()
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetMoniker                        [IOleClientSite]
//
HRESULT CControlHost::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER * ppMk)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetContainer                      [IOleClientSite]
//
HRESULT CControlHost::GetContainer(LPOLECONTAINER * ppContainer )
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::ShowObject                        [IOleClientSite]
//
HRESULT CControlHost::ShowObject()
{
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnShowWindow                      [IOleClientSite]
//
HRESULT CControlHost::OnShowWindow(BOOL fShow)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::RequestNewObjectLayout            [IOleClientSite]
//
HRESULT CControlHost::RequestNewObjectLayout()
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetWindow                         [IOleWindow]
//
HRESULT CControlHost::GetWindow(HWND * lphwnd)
{
    if (_hwnd == NULL)
        return (S_FALSE);

    *lphwnd = _hwnd;
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::ContextSensitiveHelp              [IOleWindow]
//
HRESULT CControlHost::ContextSensitiveHelp(BOOL fEnterMode)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::CanInPlaceActivate                [IOleInPlaceSite]
//
HRESULT CControlHost::CanInPlaceActivate(void)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnInPlaceActivate                 [IOleInPlaceSite]
//
HRESULT CControlHost::OnInPlaceActivate(void)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnUIActivate                      [IOleInPlaceSite]
//
HRESULT CControlHost::OnUIActivate(void)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetWindowContext                  [IOleInPlaceSite]
//
HRESULT CControlHost::GetWindowContext (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppIIPUIWin,
                                  LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
    *ppFrame = this;
    AddRef();

    *ppIIPUIWin = NULL;

    CopyRect(lprcPosRect, &_rcPos);
    CopyRect(lprcClipRect, &_rcPos);

    lpFrameInfo->cb             = sizeof(OLEINPLACEFRAMEINFO);
    lpFrameInfo->fMDIApp        = FALSE;
    lpFrameInfo->hwndFrame      = _hwnd;
    lpFrameInfo->haccel         = 0;
    lpFrameInfo->cAccelEntries  = 0;

    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::Scroll                            [IOleInPlaceSite]
//
HRESULT CControlHost::Scroll(SIZE scrollExtent)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnUIDeactivate                    [IOleInPlaceSite]
//
HRESULT CControlHost::OnUIDeactivate(BOOL fUndoable)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnInPlaceDeactivate               [IOleInPlaceSite]
//
HRESULT CControlHost::OnInPlaceDeactivate(void)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::DiscardUndoState                  [IOleInPlaceSite]
//
HRESULT CControlHost::DiscardUndoState(void)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::DeactivateAndUndo                 [IOleInPlaceSite]
//
HRESULT CControlHost::DeactivateAndUndo(void)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnPosRectChange                   [IOleInPlaceSite]
//
HRESULT CControlHost::OnPosRectChange(LPCRECT lprcPosRect)
{
    RECT rcPos, rcClient;
    HWND hwnd;
    GetWindow(&hwnd);
    GetClientRect(hwnd, &rcClient);
    GetWindowRect(hwnd, &rcPos);

    if(rcClient.bottom < lprcPosRect->bottom)
        rcPos.bottom += lprcPosRect->bottom - rcClient.bottom;
    else
        rcPos.bottom -= rcClient.bottom - lprcPosRect->bottom;

    if(rcClient.right < lprcPosRect->right)
        rcPos.right += lprcPosRect->right - rcClient.right;
    else
        rcPos.right -= rcClient.right - lprcPosRect->right;

    MoveWindow( hwnd,
                rcPos.left,
                rcPos.top,
                rcPos.right - rcPos.left,
                rcPos.bottom - rcPos.top,
                TRUE);

    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnInPlaceActivateEx               [IOleInPlaceSiteEx]
//
HRESULT CControlHost::OnInPlaceActivateEx(BOOL *pfNoRedraw, DWORD dwFlags)
{
    OleLockRunning(_punk, TRUE, FALSE);
    HRESULT hr = E_FAIL;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnInPlaceDeactivateEx             [IOleInPlaceSiteEx]
//
HRESULT CControlHost::OnInPlaceDeactivateEx(BOOL fNoRedraw)
{
    OleLockRunning(_punk, FALSE, FALSE);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::RequestUIActivate                 [IOleInPlaceSiteEx]
//
HRESULT CControlHost::RequestUIActivate(void)
{
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::CanWindowlessActivate             [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::CanWindowlessActivate(void)
{
    return (TRUE); // m_bCanWindowlessActivate);
}
//////////////////////////////////////////////////////////////////////////
//                                                  [IOleInPlaceSiteWindowless]
//  CControlHost::GetCapture
//
HRESULT CControlHost::GetCapture(void)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetCapture                        [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::SetCapture(BOOL fCapture)
{

    if (fCapture)
    {
        ::SetCapture(_hwnd);
        _bCapture = TRUE;
    }
    else
    {
        ::ReleaseCapture();
        _bCapture = FALSE;
    }
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetFocus                          [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::GetFocus(void)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetFocus                          [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::SetFocus(BOOL fFocus)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetDC                             [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::GetDC(LPCRECT pRect, DWORD grfFlags, HDC *phDC)
{
    if (!phDC)
        return E_POINTER;

    *phDC = ::GetDC(_hwnd);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::ReleaseDC                         [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::ReleaseDC(HDC hDC)
{
    ::ReleaseDC(_hwnd, hDC);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::InvalidateRect                    [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::InvalidateRect(LPCRECT pRect, BOOL fErase)
{
    ::InvalidateRect(_hwnd, pRect, fErase);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::InvalidateRgn                     [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::InvalidateRgn(HRGN hRGN, BOOL fErase)
{
    ::InvalidateRgn(_hwnd, hRGN, fErase);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::ScrollRect                        [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::ScrollRect(INT dx, INT dy, LPCRECT pRectScroll, LPCRECT pRectClip)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::AdjustRect                        [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::AdjustRect(LPRECT prc)
{
    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnDefWindowMessage                [IOleInPlaceSiteWindowless]
//
HRESULT CControlHost::OnDefWindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
{
    *plResult = ::DefWindowProc(_hwnd, msg, wParam, lParam);
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetBorder                         [IOleInPlaceUIWindow]
//
HRESULT CControlHost::GetBorder(LPRECT lprectBorder)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::RequestBorderSpace                [IOleInPlaceUIWindow]
//
HRESULT CControlHost::RequestBorderSpace(LPCBORDERWIDTHS lpborderwidths)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetBorderSpace                    [IOleInPlaceUIWindow]
//
HRESULT CControlHost::SetBorderSpace(LPCBORDERWIDTHS lpborderwidths)
{
    if(_punkOuter)
        return ((CControlHost*)_punkOuter)->SetBorderSpace(lpborderwidths);

    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetActiveObject                   [IOleInPlaceUIWindow]
//
HRESULT CControlHost::SetActiveObject(IOleInPlaceActiveObject * pActiveObject, LPCOLESTR lpszObjName)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::InsertMenus                       [IOleInPlaceFrame]
//
HRESULT CControlHost::InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetMenu                           [IOleInPlaceFrame]
//
HRESULT CControlHost::SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::RemoveMenus                       [IOleInPlaceFrame]
//
HRESULT CControlHost::RemoveMenus(HMENU hmenuShared)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::SetStatusText                     [IOleInPlaceFrame]
//
HRESULT CControlHost::SetStatusText(LPCOLESTR pszStatusText)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::EnableModeless                    [IOleInPlaceFrame]
//
HRESULT CControlHost::EnableModeless(BOOL fEnable)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::TranslateAccelerator              [IOleInPlaceFrame]
//
HRESULT CControlHost::TranslateAccelerator(LPMSG lpmsg, WORD wID)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnControlInfoChanged              [IOleControlSite]
//
HRESULT CControlHost::OnControlInfoChanged()
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::LockInPlaceActive                 [IOleControlSite]
//
HRESULT CControlHost::LockInPlaceActive(BOOL fLock)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetExtendedControl                [IOleControlSite]
//
HRESULT CControlHost::GetExtendedControl(IDispatch **ppDisp)
{
    if (ppDisp == NULL)
        return (E_INVALIDARG);

    *ppDisp = (IDispatch *)this;
    AddRef();

    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::TransformCoords                   [IOleControlSite]
//
HRESULT CControlHost::TransformCoords(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::TranslateAccelerator              [IOleControlSite]
//
HRESULT CControlHost::TranslateAccelerator(LPMSG pMsg, DWORD grfModifiers)
{
    return (S_FALSE);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::OnFocus                           [IOleControlSite]
//
HRESULT CControlHost::OnFocus(BOOL fGotFocus)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::ShowPropertyFrame                 [IOleControlSite]
//
HRESULT CControlHost::ShowPropertyFrame(void)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetIDsOfNames                     [IDispatch]
//
HRESULT CControlHost::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
{
    HRESULT     hr;                     // standard ole return code
    LPOLESTR    pName;
    DISPID      *pdispid;

    hr      = S_OK;
    pName   = *rgszNames;
    pdispid = rgdispid;

    for (UINT i=0; i<cNames; i++)
    {
        if (pName != NULL)
        {
            if (_wcsicmp(pName, L"basehref") == 0)
                *pdispid = DISPID_BASEHREF;
            else if (_wcsicmp(pName, L"align") == 0)
                *pdispid = DISPID_ALIGN;
            else
            {
                *pdispid = DISPID_UNKNOWN;
                hr       = DISP_E_UNKNOWNNAME;
            }
        }

        pdispid++;
        pName++;
    }

    return (hr);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetTypeInfo                       [IDispatch]
//
HRESULT CControlHost::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::GetTypeInfoCount                  [IDispatch]
//
HRESULT CControlHost::GetTypeInfoCount(unsigned int FAR * pctinfo)
{
    return (E_NOTIMPL);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::Invoke                            [IDispatch]
//
HRESULT CControlHost::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
{
    HRESULT     hr = S_OK;  // standard ole return code

    // The following code are the DISPIDs for the ambient properties
    // that we will support.
    if ((pvarResult == NULL) && (wFlags == DISPATCH_PROPERTYGET))
        return (E_INVALIDARG);

    switch (dispid)
    {
        case DISPID_NAVIGATECOMPLETE:
            hr = S_OK;

        case DISPID_AMBIENT_DISPLAYNAME:
            if (!pvarResult->bstrVal)
                pvarResult->bstrVal = ::SysAllocString(L"");

            hr = S_OK;

            // If we STILL don't have a bstrVal.  Clean up and return an empty variant.
            if (!pvarResult->bstrVal)
            {
                VariantInit(pvarResult);
                hr = E_FAIL;
            }

            break;

        case DISPID_AMBIENT_USERMODE:
        case DISPID_AMBIENT_MESSAGEREFLECT:
            pvarResult->vt      = VT_BOOL;
            pvarResult->boolVal = TRUE;
            hr                  = S_OK;
            break;

        case DISPID_AMBIENT_SHOWHATCHING:
        case DISPID_AMBIENT_SHOWGRABHANDLES:
        case DISPID_AMBIENT_SUPPORTSMNEMONICS:
            pvarResult->vt      = VT_BOOL;
            pvarResult->boolVal = FALSE;
            hr                  = S_OK;
            break;

        // Not yet implemented!
        case DISPID_AMBIENT_BACKCOLOR:
        case DISPID_AMBIENT_FORECOLOR:
        case DISPID_AMBIENT_UIDEAD:
        case DISPID_AMBIENT_AUTOCLIP:
            hr = S_OK;
            break;

        // Extender Properties
        case DISPID_BASEHREF:
        case DISPID_ALIGN:
            hr = DISP_E_MEMBERNOTFOUND;
            break;

        default:
            hr = DISP_E_MEMBERNOTFOUND;
    }

    return (hr);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::CreateControl                     [HELPER]
//
HRESULT CControlHost::CreateControl()
{
    HRESULT             hr;             // standard ole return code
    IOleObject          *pIOleObject;   // IOleObject interface pointer
    IPersistStreamInit  *pps;           // IPersistStreamInit interface pointer

    // Create a DAViewerControl.
    _pvc = new CDAViewerCtl();

    // Check if the creation succeeded.
    if (!_pvc)
        return E_OUTOFMEMORY;

    hr = _pvc->GetIUnknown(&_punk);
    if (FAILED(hr)) {
        return (hr);
    }

    AddRef();  // addref the container

    hr = _punk->QueryInterface(IID_IOleObject, (LPVOID *)&pIOleObject);

    if (SUCCEEDED(hr))
    {
        pIOleObject->SetClientSite(this);

        // Controls like to know they have been initialized by getting
        // called on their InitNew method.
        hr = _punk->QueryInterface(IID_IPersistStreamInit, (LPVOID *)&pps);
        if (SUCCEEDED(hr))
        {
            pps->InitNew();
            pps->Release();
        }

        // The control creation succeeded, lets create the model.
        _pvc->CreateModel();

        // Tell the control to activate and show itself.
        pIOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, _hwnd, &_rcPos);
        pIOleObject->DoVerb(OLEIVERB_SHOW, NULL, this, 0, _hwnd, &_rcPos);
        pIOleObject->Release();

        // cache some frequently needed interface pointers
        // cache the control's window handle
        _hwndControl = NULL;
        IOleInPlaceActiveObject *pipao = NULL;
        hr = _punk->QueryInterface(IID_IOleInPlaceActiveObject, (void**) &pipao);
        if (SUCCEEDED(hr))
        {
            pipao->GetWindow(&_hwndControl);
            pipao->Release();
        }
    }
    return (hr);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::DeleteControl                     [HELPER]
//
HRESULT CControlHost::DeleteControl()
{
    HRESULT     hr;                     // standard ole return code
    IOleObject  *pIOleObject;           // IOleObject interface pointer

    if (_pvc != NULL)
    {
        delete _pvc;
        _pvc = NULL;
    }

    // Close the Control and release the cached pointers.
    if (_punk != NULL)
    {
        hr = _punk->QueryInterface(IID_IOleObject, (LPVOID *)&pIOleObject);
        if (SUCCEEDED(hr))
        {
            pIOleObject->DoVerb(OLEIVERB_HIDE, NULL, this, 0, _hwnd, NULL);
            pIOleObject->Close(OLECLOSE_NOSAVE);
            pIOleObject->SetClientSite(NULL);
            pIOleObject->Release();

            _punk->Release();
            _punk = NULL;
        }

        Release();  // Release our hold on the container
    }

    return (S_OK);
}

//////////////////////////////////////////////////////////////////////////
//
//  CControlHost::QueryObject                       [HELPER]
//
HRESULT CControlHost::QueryObject(REFIID riid, void **ppvObject)
{
    HRESULT hr = E_POINTER;
    if (ppvObject)
    {
        if (_punk)
        {
            hr = _punk->QueryInterface(riid, ppvObject);
        }
        else
        {
            *ppvObject = NULL;
            hr = OLE_E_NOCONNECTION;
        }
    }
    return hr;
}


CTLHOST.H

//////////////////////////////////////////////////////////////////////////
//
//  controlhost.h
//
//  This file contains our CControlHost specification. It is used
//  to implement a generic ActiveX Control container.
//
//  (C) Copyright 1997 by Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////

#ifndef _CONTROLHOST_H_
#define _CONTROLHOST_H_

#include <ocidl.h>

#define BASE_EXTENDED_PROPERTY      0x80010000
#define DISPID_NAME                 (BASE_EXTENDED_PROPERTY | 0x00)
#define DISPID_ALIGN                (BASE_EXTENDED_PROPERTY | 0x01)
#define DISPID_BASEHREF             (BASE_EXTENDED_PROPERTY | 0x02)

class CControlHost : public IDispatch,
                     public IOleInPlaceSiteWindowless,
                     public IOleInPlaceFrame,
                     public IOleControlSite,
                     public IOleClientSite
{
    protected:
        HWND                _hwnd;              // container window handle
        HWND                _hwndControl;       // Control's window handle
        UINT                _cRef;              // IUnknown ref count
        BOOL                _bCapture;          // mouse capture flag
        RECT                _rcPos;
        DWORD               _dwTBSink;

        IUnknown           *_punk;              // copy unknown from instance structure
        void               *_punkOuter;         // parent container
        CDAViewerCtl       *_pvc;               // DAViewerControl

    public:
        void NotifyButton(long buttonId, long buttonState)
        {
            if(buttonId == 0)
                buttonId++;
        }
        void NotifyTrackBar(long position)
        {
            if(position == 0)
                position++;
        }
        void NotifyStatusBar(long position, BSTR* pstrText)
        {
            if(position == 0)
                position++;
        }

        ~CControlHost();
        CControlHost(IUnknown *);

        // *** IUnknown Methods ***
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        // *** IDispatch Methods ***
        STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames,unsigned int cNames, LCID lcid,DISPID FAR* rgdispid);
        STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
        STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
        STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);

        // *** IOleClientSite methods ***
        STDMETHOD (SaveObject)();
        STDMETHOD (GetMoniker)(DWORD, DWORD, LPMONIKER *);
        STDMETHOD (GetContainer)(LPOLECONTAINER *);
        STDMETHOD (ShowObject)();
        STDMETHOD (OnShowWindow)(BOOL);
        STDMETHOD (RequestNewObjectLayout)();

        // *** IOleWindow Methods ***
        STDMETHOD (GetWindow) (HWND * phwnd);
        STDMETHOD (ContextSensitiveHelp) (BOOL fEnterMode);

        // *** IOleInPlaceSite Methods ***
        STDMETHOD (CanInPlaceActivate) (void);
        STDMETHOD (OnInPlaceActivate) (void);
        STDMETHOD (OnUIActivate) (void);
        STDMETHOD (GetWindowContext) (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo);
        STDMETHOD (Scroll) (SIZE scrollExtent);
        STDMETHOD (OnUIDeactivate) (BOOL fUndoable);
        STDMETHOD (OnInPlaceDeactivate) (void);
        STDMETHOD (DiscardUndoState) (void);
        STDMETHOD (DeactivateAndUndo) (void);
        STDMETHOD (OnPosRectChange) (LPCRECT lprcPosRect);

        // *** IOleInPlaceSiteEx Methods ***
        STDMETHOD (OnInPlaceActivateEx) (BOOL *pfNoRedraw, DWORD dwFlags);
        STDMETHOD (OnInPlaceDeactivateEx) (BOOL fNoRedraw);
        STDMETHOD (RequestUIActivate) (void);

        // *** IOleInPlaceSiteWindowless Methods ***
        STDMETHOD (CanWindowlessActivate) (void);
        STDMETHOD (GetCapture) (void);
        STDMETHOD (SetCapture) (BOOL fCapture);
        STDMETHOD (GetFocus) (void);
        STDMETHOD (SetFocus) (BOOL fFocus);
        STDMETHOD (GetDC) (LPCRECT pRect, DWORD grfFlags, HDC *phDC);
        STDMETHOD (ReleaseDC) (HDC hDC);
        STDMETHOD (InvalidateRect) (LPCRECT pRect, BOOL fErase);
        STDMETHOD (InvalidateRgn) (HRGN hRGN, BOOL fErase);
        STDMETHOD (ScrollRect) (INT dx, INT dy, LPCRECT pRectScroll, LPCRECT pRectClip);
        STDMETHOD (AdjustRect) (LPRECT prc);
        STDMETHOD (OnDefWindowMessage) (UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult);

        // *** IOleInPlaceUIWindow Methods ***
        STDMETHOD (GetBorder)(LPRECT lprectBorder);
        STDMETHOD (RequestBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
        STDMETHOD (SetBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
        STDMETHOD (SetActiveObject)(IOleInPlaceActiveObject * pActiveObject,
                                    LPCOLESTR lpszObjName);

        // *** IOleInPlaceFrame Methods ***
        STDMETHOD (InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
        STDMETHOD (SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject);
        STDMETHOD (RemoveMenus)(HMENU hmenuShared);
        STDMETHOD (SetStatusText)(LPCOLESTR pszStatusText);
        STDMETHOD (EnableModeless)(BOOL fEnable);
        STDMETHOD (TranslateAccelerator)(LPMSG lpmsg, WORD wID);

        // *** IOleControlSite Methods ***
        STDMETHOD (OnControlInfoChanged)(void);
        STDMETHOD (LockInPlaceActive)(BOOL fLock);
        STDMETHOD (GetExtendedControl)(IDispatch **ppDisp);
        STDMETHOD (TransformCoords)(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags);
        STDMETHOD (TranslateAccelerator)(LPMSG pMsg, DWORD grfModifiers);
        STDMETHOD (OnFocus)(BOOL fGotFocus);
        STDMETHOD (ShowPropertyFrame)(void);

        // *** External Helper API ***
        HRESULT SetHwnd(HWND hwnd);

        HRESULT CreateControl();
        HRESULT DeleteControl();
        HRESULT QueryObject(REFIID riid, void **ppvObject);
};

#endif


DACTL.CPP

#include "dactl.h"

void dump_com_error( _com_error &e )
{
    char buf[2048];

    sprintf(buf, _T( "Oops - hit an error!\n\tCode = %08lx\n\tCode meaning = %s\n" ),
            e.Error(), e.ErrorMessage());
    OutputDebugString(buf);
}

// If this is placed in the scope of the smart pointers, they must be
// explicitly Release(d) before CoUninitialize() is called.  If any reference
// count is non-zero, a protection fault will occur.

CDAViewerCtl::CDAViewerCtl()
    :_vc(NULL)
{
    try   {
        _vc.CreateInstance(__uuidof(DAViewerControlWindowed));
    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

void CDAViewerCtl::CreateModel() {
    try {
        // Create the statics object
        IDAStaticsPtr e;
        e = _vc->GetMeterLibrary();

        // Import Media (geometries in this case).  The
        // GetCurrentDirectory() is used as a starting
        // point for relative file importing.
        TCHAR szMediaBase[_MAX_PATH];
        TCHAR szGeo[_MAX_PATH];

        GetModuleFileName(GetModuleHandle("DragGeoCntrl.exe"),
          szMediaBase,sizeof(szMediaBase));
        char *pos = strrchr( szMediaBase, (int)'\\' );
        int result = pos - szMediaBase + 1;
        szMediaBase[result]= NULL;

        _tcscat(szMediaBase,_T("../../../../../media/"));

        _tcscpy(szGeo,szMediaBase);

        _tcscat(szGeo,_T("geometry/"));

        // Import the cube rotate it and scale it down.
        IDAGeometryPtr geo = e->ImportGeometry(_bstr_t(szGeo) + _bstr_t("cow.x"));
        geo = geo->Transform(e->Compose3(e->Rotate3Anim(e->Vector3(1,1,1), e->LocalTime),
          e->Scale3Uniform(0.005)));

        // Make geo draggable by creating a DraggableGeometry class
        // object (_dragPtr).
        CDADrag* _dragPtr = new CDADrag();
        _dragPtr->initNotify(geo, e->Origin3, e);

        // Initialize clr.  Let it start out as red, change it to blue,
        // when the cube is grabbed, and return to red when the cube
        // is released.  The grab and release events are obtained from the
    // getGrabEvent() and getReleaseEvent() methods of the CDADrag
    // class respectively.
        IDAColorPtr clr;
        clr.CreateInstance( L"DirectAnimation.DAColor");
        clr->Init(e->Until(e->Red, _dragPtr->getGrabEvent(),
    e->Until(e->Blue, _dragPtr->getReleaseEvent(), clr)));

        // Get the GeometryBvr part of _dragPtr, by calling the getGeometryPtr
        // function of CDADrag.
        IDAGeometryPtr pickableGeo = _dragPtr->getGeometryPtr();

        // Apply clr to pickableGeo.
        pickableGeo = pickableGeo->DiffuseColor(clr);
        pickableGeo = e->UnionGeometry(pickableGeo, e->DirectionalLight);

        IDACameraPtr cam = e->PerspectiveCamera(0.1,0.05);

        // overlay the rendered image on a black background.
        IDAImagePtr model = e->Overlay(pickableGeo->Render(cam), e->SolidColorImage(e->Black));

        // And set the model's image to this image.
        _vc->PutImage( model );

        // Set the cap for the frame rate.  If we don't do this, DA
        // will hog the cpu and the mouse and keyboard won't be very
        // responsive.  If you're running in full screen mode, you may
        // want to remove this line to get better frame rate.
        _vc->put_UpdateInterval(0.2);

        // Start the model on the view.  The WndProc will
        // generate the frames.
        _vc->Start();

    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

HRESULT CDAViewerCtl::GetIUnknown(IUnknown **pUnk) {
    if (!pUnk)
        return E_POINTER;

    if (_vc == NULL)
        return E_NOINTERFACE;

    return _vc->QueryInterface(IID_IUnknown, (LPVOID *)pUnk);
}


DACTL.H

#include "dadrag.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys\timeb.h>
#include <tchar.h>

class CDAViewerCtl {

  public:
    // Construction of the control.  We use COM smart pointers in the
    // viewer control, the pointers are released when they're out of the
    // scope, so we don't need (the destructor) to release them.
    CDAViewerCtl();

    // For constructing the model.
    void CreateModel();

    // Return the IUnknown pointer of the control.
    HRESULT GetIUnknown(IUnknown **pUnk);

  private:
    IDAViewerControlPtr _vc;
};


DADRAG.CPP

#include "dadrag.h"

/***************************************
 * CDADrag
 ***************************************/

STDMETHODIMP_(ULONG) CDADrag::AddRef()  {
return InterlockedIncrement(&_cRefs);
}

STDMETHODIMP_(ULONG) CDADrag::Release()  {
ULONG refCount = InterlockedDecrement(&_cRefs);
if (!refCount) {
delete this;
return refCount;
}
return _cRefs;
}

STDMETHODIMP CDADrag::QueryInterface(REFIID riid, void **ppv)  {
if (!ppv)
return E_POINTER;

*ppv = NULL;
if (riid == IID_IUnknown)  {
*ppv = (void *)(IUnknown *)this;
} else if (riid == IID_IDABvrHook)  {
*ppv = (void *)(IDAUntilNotifier *)this;
}

if (*ppv)  {
((IUnknown *)*ppv)->AddRef();
return S_OK;
}

return E_NOINTERFACE;
}

STDMETHODIMP CDADrag::initNotify(IDAGeometryPtr geo, IDAPoint3Ptr pt, IDAStaticsPtr e)  {
   _e  = e;
  IDAPickableResultPtr pgeo = geo->Pickable();
  _pickEv = _e->AndEvent(e->LeftButtonDown, pgeo->PickEvent);

  // construct a handler for release events,
  // upon pick events it passes the puck back to this object.
  _releaseHdlr = new CDARelease();
  _releaseHdlr->initNotify(_pickEv, this, e);
  _draggablePt = (IDAPoint3Ptr)_e->UntilNotify(pt, _pickEv, this)->RunOnce();
  _releaseEv = _e->LeftButtonUp->Snapshot(_draggablePt);

  _draggableGeo = pgeo->Geometry->Transform(_e->Translate3Point(_draggablePt));

  return S_OK;
}

STDMETHODIMP CDADrag::raw_Notify(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr)  {
IDAPoint3Ptr currPt = (IDAPoint3Ptr)curRunningBvr;

  // pull apart the pair that comes from andEvent and from the
// pick event pair, ultimately to get at the local mouse
IDAPairPtr andEventPair = (IDAPairPtr)eventData;

  IDAPairPtr    pickPair   = (IDAPairPtr)(andEventPair->Second);

IDAVector3Ptr localMouse = (IDAVector3Ptr)(pickPair->Second);

IDAPoint3Ptr draggingPt = currPt->Transform(_e->Translate3Vector(localMouse));

  IDAPoint3Ptr finalPt = (IDAPoint3Ptr)_e->UntilNotify(draggingPt, _releaseEv, _releaseHdlr);

*ppBvr = (IDABehavior*)finalPt;

finalPt->AddRef();

return S_OK;
}

/***************************************
 * CDARelease
 ***************************************/

STDMETHODIMP_(ULONG) CDARelease::AddRef()  {
return InterlockedIncrement(&_cRefs);
}

STDMETHODIMP_(ULONG) CDARelease::Release()  {
ULONG refCount = InterlockedDecrement(&_cRefs);
if (!refCount) {
delete this;
return refCount;
}
return _cRefs;
}

STDMETHODIMP CDARelease::QueryInterface(REFIID riid, void **ppv)  {
if (!ppv)
return E_POINTER;

*ppv = NULL;
if (riid == IID_IUnknown)  {
*ppv = (void *)(IUnknown *)this;
} else if (riid == IID_IDABvrHook)  {
*ppv = (void *)(IDAUntilNotifier *)this;
}

if (*ppv)  {
((IUnknown *)*ppv)->AddRef();
return S_OK;
}

return E_NOINTERFACE;
}

STDMETHODIMP CDARelease::initNotify(IDAEventPtr pickEv, CDADrag* grabHdlr, IDAStaticsPtr e)  {
   _e  = e;
  _pickEv = pickEv;
  _grabHdlr = grabHdlr;
  return S_OK;
}

STDMETHODIMP CDARelease::raw_Notify(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr)  {

  // Releasing.  Freeze the image where it is, and go
  // back to waiting for a pick event.
  IDAPoint3Ptr snappedPt = (IDAPoint3Ptr)eventData;
  IDAPoint3Ptr releasePt = (IDAPoint3Ptr)_e->UntilNotify(snappedPt,
    _pickEv, _grabHdlr);

  *ppBvr = (IDABehavior*)releasePt;

releasePt->AddRef();

return S_OK;
}


DADRAG.H

// Here's the magic... It reads in the type library and makes easy
// interfaces to use automatically.
#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

using namespace DAnim;

// Used by IDAUntilNotifier, to get its definition, be
// sure to add daguid.lib to the Object/library
// module editbox, on the link tab under Settings.
EXTERN_C const IID IID_IDABvrHook;

class CDADrag;
class CDARelease;

class CDADrag : public IDAUntilNotifier  {

public:

CDADrag()  {
    _cRefs = 1;
  }

~CDADrag()  {
  }

  IDAGeometryPtr getGeometryPtr()  {
  return _draggableGeo;
};

IDAEventPtr getGrabEvent()  {
  return _pickEv;
}

IDAEventPtr getReleaseEvent()  {
  return _releaseEv;
}

IDAPoint3Ptr getPointPtr()  {
  return _draggablePt;
}

STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD(initNotify)(IDAGeometryPtr geo, IDAPoint3Ptr pt, IDAStaticsPtr e);

  // Since we are using #import, the pure virtual function Notify of the
  // IDAUntilNotifier class (refer to danim.h)  has been renamed to raw_Notify.
STDMETHOD(raw_Notify)(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr);

STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)  {
    return E_NOTIMPL;
  }

STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo **pptinfo){
    return E_NOTIMPL;
  }

STDMETHOD(GetIDsOfNames)(
REFIID riid, LPOLESTR *rgszNames, UINT cNames,
LCID lcid, DISPID *rgdispid)  {
    return E_NOTIMPL;
  }

STDMETHOD(Invoke)(
DISPID dispidMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr){
    return E_NOTIMPL;
  }

STDMETHOD(ClearEngine)(){
    return S_OK;
  }

protected:
long _cRefs;
  IDAEventPtr _pickEv;
IDAEventPtr _releaseEv;
IDAGeometryPtr _draggableGeo;
IDAPoint3Ptr _draggablePt;
  CDARelease* _releaseHdlr;
IDAStaticsPtr _e;
};

class CDARelease : public IDAUntilNotifier {

public:

  CDARelease()  {
    _cRefs = 1;
  }

  ~CDARelease()  {
  }

  STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD(initNotify)(IDAEventPtr pickEv, CDADrag* grabHdlr, IDAStaticsPtr e);

  // Since we are using #import, the pure virtual function Notify of the
  // IDAUntilNotifier class (refer to danim.h)  has been renamed to raw_Notify.
  STDMETHOD(raw_Notify)(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr);

STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)  {
    return E_NOTIMPL;
  }

STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo **pptinfo){
    return E_NOTIMPL;
  }

STDMETHOD(GetIDsOfNames)(
REFIID riid, LPOLESTR *rgszNames, UINT cNames,
LCID lcid, DISPID *rgdispid)  {
    return E_NOTIMPL;
  }

STDMETHOD(Invoke)(
DISPID dispidMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr){
    return E_NOTIMPL;
  }

STDMETHOD(ClearEngine)(){
    return S_OK;
  }

protected:
long _cRefs;
  IDAEventPtr _pickEv;
IDAStaticsPtr _e;
  CDADrag* _grabHdlr;
};


DRAGGEO.CPP

//////////////////////////////////////////////////////////////////////////
//
//  draggeo.cpp
//
//  This is the windows application that is used to host the
//  DAViewerControl.
//
//
//  (C) Copyright 1997 by Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#define INITGUID
#include <windows.h>

#include "dactl.h"
#include "ctlhost.h"
#include "resource.h"

#define WINDOW_CLASS    "DRAGGABLE GEOMETRY"
#define WINDOW_TITLE    "DRAGGABLE GEOMETRY"

VARIANT_BOOL fCaptioningOn = FALSE;
VARIANT_BOOL fStatusOn = TRUE;
VARIANT_BOOL fToolBarOn = TRUE;

// Local Function Prototypes
HRESULT loadDXAControl(HWND hwnd);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

WORD wLastMenuItem;
CControlHost *ocHost = NULL;               // oc host

//////////////////////////////////////////////////////////////////////////
//
//  WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR szCmdLine, int iCmdShow)
{
    WNDCLASS    wndclass;
    MSG         msg;
    HWND        hwnd;
    OleInitialize(NULL);

    wndclass.style              = CS_OWNDC;
    wndclass.lpfnWndProc        = WndProc;
    wndclass.cbClsExtra         = 0;
    wndclass.cbWndExtra         = sizeof(LONG)*4;
    wndclass.hInstance          = hInstance;
    wndclass.hIcon              = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wndclass.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground      = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    wndclass.lpszMenuName       = NULL;
    wndclass.lpszClassName      = WINDOW_CLASS;

    RegisterClass(&wndclass);

    hwnd = CreateWindow(WINDOW_CLASS,
                        WINDOW_TITLE,
                        WS_OVERLAPPED|
                        WS_SYSMENU,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        300,
                        300,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ocHost = new CControlHost(NULL);

    if (!ocHost)
        return FALSE;

    // Load the control.
    if(FAILED(loadDXAControl(hwnd)) )
         return FALSE;

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    OleUninitialize();

    return (msg.wParam);
}

//////////////////////////////////////////////////////////////////////////
HRESULT loadDXAControl(HWND hwnd)
{
    ocHost->SetHwnd(hwnd);
    return ocHost->CreateControl();
}

//////////////////////////////////////////////////////////////////////////
//
//  WndProc
//
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if(msg == WM_CLOSE || msg == WM_DESTROY)
    {
        if(ocHost)
        {
            ocHost->DeleteControl();
            ocHost = NULL;
        }
        PostQuitMessage(0);
        return (0);
    }

    return (DefWindowProc(hwnd, msg, wParam, lParam));
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by ctlhost.rc
//
#define IDI_ICON1                       101


DACTL.CPP

#include "dactl.h"

void dump_com_error( _com_error &e )
{
    char buf[2048];

    sprintf(buf, _T( "Oops - hit an error!\n\tCode = %08lx\n\tCode meaning = %s\n" ),
            e.Error(), e.ErrorMessage());
    OutputDebugString(buf);
}

// If this is placed in the scope of the smart pointers, they must be
// explicitly Release(d) before CoUninitialize() is called.  If any reference
// count is non-zero, a protection fault will occur.

CDAViewerCtl::CDAViewerCtl()
    :_vc(NULL)
{
    try   {
        _vc.CreateInstance(__uuidof(DAViewerControlWindowed));
    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

void CDAViewerCtl::CreateModel() {
    try {
        // Create the statics object
        IDAStaticsPtr e;
        e = _vc->GetMeterLibrary();

        CDADrag* _dragPtr = new CDADrag();

        // Create a ModifiableBehavior which will be used to change the color
        // of the square.
        IDABehaviorPtr _squareColor = e->ModifiableBehavior(e->Red);

        // Instanciate an uninitialized ColorBvr (_squareColor).
        IDAColorPtr temp;
        temp.CreateInstance( L"DirectAnimation.DAColor");

        // Initialize _squareColor.  Let it start out as red, change it to blue,
        // when the square is grabbed, and return to red when the square
        // is released.  The grab and release events are obtained from the
        // getGrabEvent() and getReleaseEvent() from the CDADrag
        // class.
        //_squareColor->Init(e->Until(e->Red, _dragPtr->getGrabEvent(),
        //  e->Until(e->Blue, _dragPtr->getReleaseEvent(), _squareColor)));

        // Create cropped square, and apply _squareColor's color behavior to it.
        _blockImg = e->SolidColorImage((IDAColorPtr)_squareColor)->Crop(e->Point2(0,0),
          e->Point2(0.005,0.005));

        _dragPtr->initNotify(_blockImg, e->Origin2, e);

        // Initialize squareClr.  Let it start out as red, change it to blue,
        // when the cube is grabbed, and return to red when the cube
        // is released.  The grab and release events are obtained from the
    // getGrabEvent() and getReleaseEvent() methods of the CDADrag
    // class respectively.
        IDAColorPtr squareClr;
        squareClr.CreateInstance( L"DirectAnimation.DAColor");
        squareClr->Init(e->Until(e->Red, _dragPtr->getGrabEvent(),
    e->Until(e->Blue, _dragPtr->getReleaseEvent(), squareClr)));

        // Apply squareClr's behavior to the square.
        _squareColor->SwitchTo(squareClr);

        // Get the ImageBvr part of grabImg, by calling the getImagePtr() method
        // of DraggableImage.
        IDAImagePtr pickableBlockImg = _dragPtr->getImagePtr();

        // overlay pickableBlockImg on a black background.
        IDAImagePtr model = e->Overlay(pickableBlockImg, e->SolidColorImage(e->Black));

        // And set the model's image to this image.
        _vc->PutImage( model );

        // Set the cap for the frame rate.  If we don't do this, DA
        // will hog the cpu and the mouse and keyboard won't be very
        // responsive.  If you're running in full screen mode, you may
        // want to remove this line to get better frame rate.
        _vc->put_UpdateInterval(0.2);

        // Start the model on the view.  The WndProc will
        // generate the frames.
        _vc->Start();

    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

HRESULT CDAViewerCtl::GetIUnknown(IUnknown **pUnk) {
    if (!pUnk)
        return E_POINTER;

    if (_vc == NULL)
        return E_NOINTERFACE;

    return _vc->QueryInterface(IID_IUnknown, (LPVOID *)pUnk);
}


DACTL.H

// Here's the magic... It reads in the type library and makes easy
// interfaces to use automatically.
#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

#include "dadrag.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys\timeb.h>
#include <tchar.h>

class CDAViewerCtl {

  public:
    // Construction of the control.  We use COM smart pointers in the
    // viewer control, the pointers are released when they're out of the
    // scope, so we don't need (the destructor) to release them.
    CDAViewerCtl();

    // For constructing the model.
    void CreateModel();

    // Return the IUnknown pointer of the control.
    HRESULT GetIUnknown(IUnknown **pUnk);

  private:
    IDAViewerControlPtr _vc;
    IDAImagePtr _blockImg;
};


DADRAG.CPP

#include "dadrag.h"

/***************************************
 * CDADrag
 ***************************************/

STDMETHODIMP_(ULONG) CDADrag::AddRef()  {
return InterlockedIncrement(&_cRefs);
}

STDMETHODIMP_(ULONG) CDADrag::Release()  {
ULONG refCount = InterlockedDecrement(&_cRefs);
if (!refCount) {
delete this;
return refCount;
}
return _cRefs;
}

STDMETHODIMP CDADrag::QueryInterface(REFIID riid, void **ppv)  {
if (!ppv)
return E_POINTER;

*ppv = NULL;
if (riid == IID_IUnknown)  {
*ppv = (void *)(IUnknown *)this;
} else if (riid == IID_IDABvrHook)  {
*ppv = (void *)(IDAUntilNotifier *)this;
}

if (*ppv)  {
((IUnknown *)*ppv)->AddRef();
return S_OK;
}

return E_NOINTERFACE;
}

STDMETHODIMP CDADrag::initNotify(IDAImagePtr im, IDAPoint2Ptr pt, IDAStaticsPtr e)  {
   _e  = e;
  IDAPickableResultPtr pim = im->Pickable();
  _pickEv = _e->AndEvent(e->LeftButtonDown, pim->PickEvent);

  // construct a handler for release events,
  // upon pick events it passes the puck back to this object.
  _releaseHdlr = new CDARelease();
  _releaseHdlr->initNotify(_pickEv, this, e);
  _draggablePt = (IDAPoint2Ptr)_e->UntilNotify(pt, _pickEv, this)->RunOnce();
  _releaseEv = _e->LeftButtonUp->Snapshot(_draggablePt);

  _draggableImg = pim->Image->Transform(_e->Translate2Point(_draggablePt));

  return S_OK;
}

STDMETHODIMP CDADrag::raw_Notify(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr)  {
IDAPoint2Ptr currPt = (IDAPoint2Ptr)curRunningBvr;

  // pull apart the pair that comes from andEvent and from the
// pick event pair, ultimately to get at the local mouse
IDAPairPtr andEventPair = (IDAPairPtr)eventData;

  IDAPairPtr    pickPair   = (IDAPairPtr)(andEventPair->Second);

IDAVector2Ptr localMouse = (IDAVector2Ptr)(pickPair->Second);

IDAPoint2Ptr draggingPt = currPt->Transform(_e->Translate2Vector(localMouse));

  IDAPoint2Ptr finalPt = (IDAPoint2Ptr)_e->UntilNotify(draggingPt, _releaseEv, _releaseHdlr);

*ppBvr = (IDABehavior*)finalPt;

finalPt->AddRef();

return S_OK;
}

/***************************************
 * CDARelease
 ***************************************/

STDMETHODIMP_(ULONG) CDARelease::AddRef()  {
return InterlockedIncrement(&_cRefs);
}

STDMETHODIMP_(ULONG) CDARelease::Release()  {
ULONG refCount = InterlockedDecrement(&_cRefs);
if (!refCount) {
delete this;
return refCount;
}
return _cRefs;
}

STDMETHODIMP CDARelease::QueryInterface(REFIID riid, void **ppv)  {
if (!ppv)
return E_POINTER;

*ppv = NULL;
if (riid == IID_IUnknown)  {
*ppv = (void *)(IUnknown *)this;
} else if (riid == IID_IDABvrHook)  {
*ppv = (void *)(IDAUntilNotifier *)this;
}

if (*ppv)  {
((IUnknown *)*ppv)->AddRef();
return S_OK;
}

return E_NOINTERFACE;
}

STDMETHODIMP CDARelease::initNotify(IDAEventPtr pickEv, CDADrag* grabHdlr, IDAStaticsPtr e)  {
   _e  = e;
  _pickEv = pickEv;
  _grabHdlr = grabHdlr;
  return S_OK;
}

STDMETHODIMP CDARelease::raw_Notify(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr)  {

  // Releasing.  Freeze the image where it is, and go
  // back to waiting for a pick event.
  IDAPoint2Ptr snappedPt = (IDAPoint2Ptr)eventData;
  IDAPoint2Ptr releasePt = (IDAPoint2Ptr)_e->UntilNotify(snappedPt,
    _pickEv, _grabHdlr);

  *ppBvr = (IDABehavior*)releasePt;

releasePt->AddRef();

return S_OK;
}


DADRAG.H

// Here's the magic... It reads in the type library and makes easy
// interfaces to use automatically.
#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

using namespace DAnim;

// Used by IDAUntilNotifier, to get its definition, be
// sure to add daguid.lib to the Object/library
// module editbox, on the link tab under Settings.
EXTERN_C const IID IID_IDABvrHook;

class CDADrag;
class CDARelease;

class CDADrag : public IDAUntilNotifier  {

public:

CDADrag()  {
    _cRefs = 1;
  }

  CDADrag::CDADrag(IDAImagePtr im, IDAPoint2Ptr pt, IDAStaticsPtr e);

~CDADrag()  {
  }

  IDAImagePtr getImagePtr()  {
  return _draggableImg;
};

IDAEventPtr getGrabEvent()  {
  return _pickEv;
}

IDAEventPtr getReleaseEvent()  {
  return _releaseEv;
}

IDAPoint2Ptr getPointPtr()  {
  return _draggablePt;
}

STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD(initNotify)(IDAImagePtr im, IDAPoint2Ptr pt, IDAStaticsPtr e);

  // Since we are using #import, the pure virtual function Notify of the
  // IDAUntilNotifier class (refer to danim.h)  has been renamed to raw_Notify.
STDMETHOD(raw_Notify)(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr);

STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)  {
    return E_NOTIMPL;
  }

STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo **pptinfo){
    return E_NOTIMPL;
  }

STDMETHOD(GetIDsOfNames)(
REFIID riid, LPOLESTR *rgszNames, UINT cNames,
LCID lcid, DISPID *rgdispid)  {
    return E_NOTIMPL;
  }

STDMETHOD(Invoke)(
DISPID dispidMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr){
    return E_NOTIMPL;
  }

STDMETHOD(ClearEngine)(){
    return S_OK;
  }

protected:
long _cRefs;
  IDAEventPtr _pickEv;
IDAEventPtr _releaseEv;
IDAImagePtr _draggableImg;
IDAPoint2Ptr _draggablePt;
  CDARelease* _releaseHdlr;
IDAStaticsPtr _e;
};

class CDARelease : public IDAUntilNotifier {

public:

  CDARelease()  {
    _cRefs = 1;
  }

  ~CDARelease()  {
  }

  STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD(initNotify)(IDAEventPtr pickEv, CDADrag* grabHdlr, IDAStaticsPtr e);

  // Since we are using #import, the pure virtual function Notify of the
  // IDAUntilNotifier class (refer to danim.h)  has been renamed to raw_Notify.
  STDMETHOD(raw_Notify)(IDABehavior* eventData,
IDABehavior* curRunningBvr,
IDAView* curView,
IDABehavior** ppBvr);

STDMETHOD(GetTypeInfoCount)(UINT *pctinfo)  {
    return E_NOTIMPL;
  }

STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo **pptinfo){
    return E_NOTIMPL;
  }

STDMETHOD(GetIDsOfNames)(
REFIID riid, LPOLESTR *rgszNames, UINT cNames,
LCID lcid, DISPID *rgdispid)  {
    return E_NOTIMPL;
  }

STDMETHOD(Invoke)(
DISPID dispidMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr){
    return E_NOTIMPL;
  }

STDMETHOD(ClearEngine)(){
    return S_OK;
  }

protected:
long _cRefs;
  IDAEventPtr _pickEv;
IDAStaticsPtr _e;
  CDADrag* _grabHdlr;
};


DRAGIMG.CPP

//////////////////////////////////////////////////////////////////////////
//
//  dragimg.cpp
//
//  This is the windows application that is used to host the
//  DAViewerControl.
//
//
//  (C) Copyright 1997 by Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#define INITGUID
#include <windows.h>

#include "dactl.h"
#include "ctlhost.h"
#include "resource.h"

#define WINDOW_CLASS    "DRAGGABLE IMAGE"
#define WINDOW_TITLE    "DRAGGABLE IMAGE"

VARIANT_BOOL fCaptioningOn = FALSE;
VARIANT_BOOL fStatusOn = TRUE;
VARIANT_BOOL fToolBarOn = TRUE;

// Local Function Prototypes
HRESULT loadDXAControl(HWND hwnd);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

WORD wLastMenuItem;
CControlHost *ocHost = NULL;               // oc host

//////////////////////////////////////////////////////////////////////////
//
//  WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR szCmdLine, int iCmdShow)
{
    WNDCLASS    wndclass;
    MSG         msg;
    HWND        hwnd;
    OleInitialize(NULL);

    wndclass.style              = CS_OWNDC;
    wndclass.lpfnWndProc        = WndProc;
    wndclass.cbClsExtra         = 0;
    wndclass.cbWndExtra         = sizeof(LONG)*4;
    wndclass.hInstance          = hInstance;
    wndclass.hIcon              = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wndclass.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground      = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    wndclass.lpszMenuName       = NULL;
    wndclass.lpszClassName      = WINDOW_CLASS;

    RegisterClass(&wndclass);

    hwnd = CreateWindow(WINDOW_CLASS,
                        WINDOW_TITLE,
                        WS_OVERLAPPED|
                        WS_SYSMENU,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        300,
                        300,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ocHost = new CControlHost(NULL);

    if (!ocHost)
        return FALSE;

    // Load the control.
    if(FAILED(loadDXAControl(hwnd)) )
         return FALSE;

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    OleUninitialize();

    return (msg.wParam);
}

//////////////////////////////////////////////////////////////////////////
HRESULT loadDXAControl(HWND hwnd)
{
    ocHost->SetHwnd(hwnd);
    return ocHost->CreateControl();
}

//////////////////////////////////////////////////////////////////////////
//
//  WndProc
//
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if(msg == WM_CLOSE || msg == WM_DESTROY)
    {
        if(ocHost)
        {
            ocHost->DeleteControl();
            ocHost = NULL;
        }
        PostQuitMessage(0);
        return (0);
    }

    return (DefWindowProc(hwnd, msg, wParam, lParam));
}


DACTL.CPP

#include "dactl.h"

void dump_com_error( _com_error &e )
{
    char buf[2048];

    sprintf(buf, _T( "Oops - hit an error!\n\tCode = %08lx\n\tCode meaning = %s\n" ),
            e.Error(), e.ErrorMessage());
    OutputDebugString(buf);
}

// If this is placed in the scope of the smart pointers, they must be
// explicitly Release(d) before CoUninitialize() is called.  If any reference
// count is non-zero, a protection fault will occur.

CDAViewerCtl::CDAViewerCtl()
    :_vc(NULL)
{
    try   {
        _vc.CreateInstance(__uuidof(DAViewerControlWindowed));
    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

void CDAViewerCtl::CreateModel() {
    try {
        // Create the statics object
        IDAStaticsPtr e;
        e = _vc->GetMeterLibrary();

        // Import Media (geometries, images and in this case).
        // The GetCurrentDirectory() is used as a starting
        // point for relative file importing.
        TCHAR szMediaBase[_MAX_PATH];
        TCHAR szImg[_MAX_PATH];
        TCHAR szGeo[_MAX_PATH];
        TCHAR szSnd[_MAX_PATH];

        GetModuleFileName(GetModuleHandle("Pick3Cntrl.exe"),
          szMediaBase,sizeof(szMediaBase));
        char *pos = strrchr( szMediaBase, (int)'\\' );
        int result = pos - szMediaBase + 1;
        szMediaBase[result]= NULL;

        _tcscat(szMediaBase,_T("../../../../../media/"));

        _tcscpy(szImg,szMediaBase);
        _tcscpy(szGeo,szMediaBase);
        _tcscpy(szSnd,szMediaBase);

        _tcscat(szImg,_T("image/"));
        _tcscat(szGeo,_T("geometry/"));
        _tcscat(szSnd,_T("sound/"));

        // Define constants
        SIZE = e->Scale3Uniform(0.25);
        PICKEVENTL = e->LeftButtonDown;
        PICKEVENTR = e->RightButtonDown;
        SPEED = e->DANumber(0.07);
        PI = 3.14159265359;

        // import background.
        IDAImagePtr stillSky = e->ImportImage(_bstr_t(szImg) + _bstr_t("cldtile.jpg"));

        IDAPoint2Ptr maxSky = stillSky->BoundingBox->Max;
        IDAImagePtr tiledSky = stillSky->Tile();
        IDAImagePtr movingSky = tiledSky->Transform(e->Translate2Anim(e->Mul(e->LocalTime,
          e->Div(maxSky->X,e->DANumber(8))), e->Mul(e->LocalTime,e->Div(maxSky->X,e->DANumber(16)))));

        // Import the geometries.
        IDAGeometryPtr rawCube =
        e->ImportGeometry( _bstr_t(szGeo) + _bstr_t( "cube.x" ) );
        rawCube = rawCube->Transform(SIZE);

        IDAGeometryPtr rawCylinder =
        e->ImportGeometry(_bstr_t(szGeo) + _bstr_t("cylinder.x"));
        rawCylinder = rawCylinder->Transform(SIZE);

        IDAGeometryPtr rawCone =
        e->ImportGeometry(_bstr_t(szGeo) + _bstr_t("cone.x"));
        rawCone = rawCone->Transform(SIZE);

        // Make the geometries pickable.
        IDAGeometryPtr cone1 = activate(rawCone, e->Green, e);

        IDAGeometryPtr cube1 = activate(rawCube, e->Magenta, e);

        IDAGeometryPtr cube2 = activate(rawCube, e->ColorHslAnim(e->Div(e->LocalTime,
          e->DANumber(8)), e->DANumber(1), e->DANumber(0.5)), e);

        IDAGeometryPtr cylinder = activate(rawCylinder, e->ColorRgb(0.8,0.4,0.4), e);

        // Construct the final geometry, scale and rotate it.
        IDAGeometryPtr multigeo = e->UnionGeometry(cone1->Transform(e->Translate3(0,1,0)),
          e->UnionGeometry(cube1->Transform(e->Translate3(0,0,1)),
            e->UnionGeometry(cube2->Transform(e->Translate3(0,0,-1)),cylinder)));

        IDAGeometryPtr geo = multigeo->Transform(e->Scale3Anim(e->Add(e->Abs(e->Sin(e->Mul(e->LocalTime,
          e->DANumber(0.2)))),e->DANumber(0.5)),e->Add(e->Abs(e->Sin(e->Mul(e->LocalTime,
            e->DANumber(0.26)))),e->DANumber(0.5)),e->Add(e->Abs(e->Sin(e->Mul(e->LocalTime,
              e->DANumber(0.14)))),e->DANumber(0.5))));

        IDATransform3Ptr transform1 = e->Rotate3Anim(e->ZVector3,
          e->Mul(e->DANumber(0.07), e->Mul(e->LocalTime, e->DANumber(1.9))));

        IDATransform3Ptr transform2 = e->Rotate3Anim(e->YVector3,
          e->Mul(e->DANumber(0.07), e->Mul(e->LocalTime, e->DANumber(PI))));

        IDAImagePtr movingGeoImg = geometryImage(geo->Transform(e->Compose3(e->Rotate3Anim(e->ZVector3,
          e->Mul(e->DANumber(0.07),e->Mul(e->LocalTime,e->DANumber(1.9)))), e->Rotate3Anim(e->YVector3,
            e->Mul(e->DANumber(0.07),e->Mul(e->LocalTime,e->DANumber(PI)))))), e);

        IDAFontStylePtr fs = e->DefaultFont->Color(e->Black);
        IDAImagePtr titleIm = e->StringImage("Left Click On An Object",
          fs)->Transform(e->Translate2(0,0.03));

        IDAImagePtr model = e->Overlay( titleIm, e->Overlay( movingGeoImg, movingSky ) );

        // And set the model's image to this image.
        _vc->PutImage( model );

        // Set the cap for the frame rate.  If we don't do this, DA
        // will hog the cpu and the mouse and keyboard won't be very
        // responsive.  If you're running in full screen mode, you may
        // want to remove this line to get better frame rate.
        _vc->put_UpdateInterval(0.2);

        // Start the model on the view.  The WndProc will
        // generate the frames.
        _vc->Start();

    } catch( _com_error &e ) {
        dump_com_error( e );
    }
}

IDAGeometryPtr CDAViewerCtl::activate(IDAGeometryPtr unpickedGeo,
                                      IDAColorPtr col, IDAStaticsPtr e)  {
  IDAPickableResultPtr pickGeo = unpickedGeo->Pickable();
  IDAEventPtr pickEvent = e->AndEvent(PICKEVENTL, pickGeo->PickEvent);

  IDANumberPtr numcyc;
  numcyc.CreateInstance( L"DirectAnimation.DANumber");
  numcyc->Init(e->Until(e->DANumber(0),pickEvent,
    e->Until(e->DANumber(1), pickEvent, numcyc)));

  IDAColorPtr colcyc;
  colcyc.CreateInstance( L"DirectAnimation.DAColor");
  colcyc->Init(e->Until(e->White, pickEvent, e->Until(col, pickEvent, colcyc)));

  IDATransform3Ptr xf = e->Rotate3Anim(e->XVector3, e->Integral(numcyc));

  return pickGeo->Geometry->DiffuseColor(colcyc)->Transform(xf);

}

IDAImagePtr CDAViewerCtl::geometryImage(IDAGeometryPtr geo, IDAStaticsPtr e)  {
  IDANumberPtr scaleFactor = e->DANumber(0.02);

  IDATransform3Ptr perspTransform;
  perspTransform.CreateInstance( L"DirectAnimation.DATransform3");
  perspTransform->Init(e->Until(e->Compose3(e->Rotate3Anim(e->XVector3,
    e->Mul(SPEED,e->LocalTime)),e->Translate3(0, 0, 0.2)),PICKEVENTR,
      e->Until(e->Rotate3Anim(e->XVector3, e->Mul(SPEED,e->LocalTime)),
        PICKEVENTR, perspTransform)));

  IDAGeometryPtr myLight =
    e->UnionGeometry(e->DirectionalLight->Transform(perspTransform), e->DirectionalLight);

  IDACameraPtr perspectiveCam = (e->PerspectiveCamera(1,0))->Transform(e->Compose3(e->Rotate3Anim(e->XVector3,
    e->Mul(SPEED,e->LocalTime)),e->Translate3(0,0,0.2)));

  IDACameraPtr parallelCam = (e->ParallelCamera(1))->Transform(e->Rotate3Anim(e->XVector3,
    e->Mul(SPEED,e->LocalTime)));

  IDACameraPtr camera;
  camera.CreateInstance( L"DirectAnimation.DACamera");
  camera->Init(e->Until(perspectiveCam, PICKEVENTR,
    e->Until(parallelCam, PICKEVENTR, camera)));

  // Display text which tells the user what camera is currently being used.
  IDAStringPtr camText;
  camText.CreateInstance( L"DirectAnimation.DAString");
  camText->Init(e->Until(e->DAString("Perspective - Right Click to Switch"), PICKEVENTR,
    e->Until(e->DAString("Parallel - Right Click to Switch"),PICKEVENTR, camText)));

  IDAFontStylePtr fs = e->DefaultFont->Color(e->Red);
  IDAImagePtr camIm  = e->StringImageAnim(camText, fs);
  camIm = camIm->Transform(e->Translate2(0, -0.03));

  return e->Overlay(camIm, e->UnionGeometry(geo->Transform(e->Scale3UniformAnim(scaleFactor)),
    myLight)->Render(camera));
}

HRESULT CDAViewerCtl::GetIUnknown(IUnknown **pUnk) {
    if (!pUnk)
        return E_POINTER;

    if (_vc == NULL)
        return E_NOINTERFACE;

    return _vc->QueryInterface(IID_IUnknown, (LPVOID *)pUnk);
}


DACTL.H

// Here's the magic... It reads in the type library and makes easy
// interfaces to use automatically.
#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

#include <stdio.h>
#include <stdlib.h>
#include <sys\timeb.h>
#include <tchar.h>
using namespace DAnim;

class CDAViewerCtl {

  public:
    // Construction of the control.  We use COM smart pointers in the
    // viewer control, the pointers are released when they're out of the
    // scope, so we don't need (the destructor) to release them.
    CDAViewerCtl();

    // For constructing the model.
    void CreateModel();

    // Return the IUnknown pointer of the control.
    HRESULT GetIUnknown(IUnknown **pUnk);

  private:
    IDAViewerControlPtr _vc;
    IDAEventPtr PICKEVENTL;
    IDAEventPtr PICKEVENTR;
    IDANumberPtr SPEED;
    IDATransform3Ptr SIZE;
    double PI;
    // Create a function to convert geometries into images.
    IDAImagePtr geometryImage(IDAGeometryPtr geo, IDAStaticsPtr e);
    // This function changes the color of a geometry when users clicks on
    // it with their mouse.
    IDAGeometryPtr activate(IDAGeometryPtr unpickedGeo, IDAColorPtr col, IDAStaticsPtr e);
};


PICK3.CPP

//////////////////////////////////////////////////////////////////////////
//
//  pick3.cpp
//
//  This is the windows application that is used to host the
//  DAViewerControl.
//
//
//  (C) Copyright 1997 by Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#define INITGUID
#include <windows.h>

#include "dactl.h"
#include "ctlhost.h"
#include "resource.h"

#define WINDOW_CLASS    "PICK3"
#define WINDOW_TITLE    "PICK3"

VARIANT_BOOL fCaptioningOn = FALSE;
VARIANT_BOOL fStatusOn = TRUE;
VARIANT_BOOL fToolBarOn = TRUE;

// Local Function Prototypes
HRESULT loadDXAControl(HWND hwnd);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

WORD wLastMenuItem;
CControlHost *ocHost = NULL;               // oc host

//////////////////////////////////////////////////////////////////////////
//
//  WinMain
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR szCmdLine, int iCmdShow)
{
    WNDCLASS    wndclass;
    MSG         msg;
    HWND        hwnd;
    OleInitialize(NULL);

    wndclass.style              = CS_OWNDC;
    wndclass.lpfnWndProc        = WndProc;
    wndclass.cbClsExtra         = 0;
    wndclass.cbWndExtra         = sizeof(LONG)*4;
    wndclass.hInstance          = hInstance;
    wndclass.hIcon              = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wndclass.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground      = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    wndclass.lpszMenuName       = NULL;
    wndclass.lpszClassName      = WINDOW_CLASS;

    RegisterClass(&wndclass);

    hwnd = CreateWindow(WINDOW_CLASS,
                        WINDOW_TITLE,
                        WS_OVERLAPPED|
                        WS_SYSMENU,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        300,
                        300,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ocHost = new CControlHost(NULL);

    if (!ocHost)
        return FALSE;

    // Load the control.
    if(FAILED(loadDXAControl(hwnd)) )
         return FALSE;

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    OleUninitialize();

    return (msg.wParam);
}

//////////////////////////////////////////////////////////////////////////
HRESULT loadDXAControl(HWND hwnd)
{
    ocHost->SetHwnd(hwnd);
    return ocHost->CreateControl();
}

//////////////////////////////////////////////////////////////////////////
//
//  WndProc
//
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if(msg == WM_CLOSE || msg == WM_DESTROY)
    {
        if(ocHost)
        {
            ocHost->DeleteControl();
            ocHost = NULL;
        }
        PostQuitMessage(0);
        return (0);
    }

    return (DefWindowProc(hwnd, msg, wParam, lParam));
}


DXA.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dxa.cpp
 *
 *  This file contains functions that make use of DirectX Animation
 *  to render animation to a ddraw surface.
 *
 ***************************************************************************/

#include "dxa.h"

IDAViewPtr DXA::_view = NULL;

/*
 * StartOle
 *
 * Start the Ole services.
 */
struct StartOle {
    StartOle() { CoInitialize( NULL ); }
    ~StartOle() { CoUninitialize(); }
} _inst_StartOle;

/*
 * dump_com_error
 *
 * Dump the com error should an exception occurs in InitDXAViewObj.
 * This is mainly for debugging purpose.
 */
void dump_com_error( _com_error &e )
{
    char buf[2048];

    sprintf(buf, _T( "Oops - hit an error!\n\tCode = %08lx\n\tCode meaning = %s\n" ),
            e.Error(), e.ErrorMessage());
    OutputDebugString(buf);
}

/*
 * initDXAViewObj
 *
 * Create the DAView object, construct the model, then start the model.
 *
 */
BOOL DXA::initDXAViewObj(IUnknown *ddsurf)
{
    try {
        IDAStaticsPtr e;

        // Create the statics object
        e.CreateInstance( L"DirectAnimation.DAStatics" );

        // Create and establish the view
        _view.CreateInstance( L"DirectAnimation.DAView" );

        // Import Media (images and sound in this case).  The
        // GetCurrentDirectory() (which will return the path to either
        // the debug or release sub directory) is used as a starting
        // point for relative file importing.
        TCHAR szMediaBase[_MAX_PATH];
        TCHAR szImg[_MAX_PATH];
        TCHAR szGeo[_MAX_PATH];
        TCHAR szSnd[_MAX_PATH];

        GetModuleFileName(GetModuleHandle("Union.exe"),
          szMediaBase,sizeof(szMediaBase));
        char *pos = strrchr( szMediaBase, (int)'\\' );
        int result = pos - szMediaBase + 1;
        szMediaBase[result]= NULL;

        _tcscat(szMediaBase,_T("../../../../../media/"));

        _tcscpy(szImg,szMediaBase);
        _tcscpy(szGeo,szMediaBase);
        _tcscpy(szSnd,szMediaBase);

        _tcscat(szImg,_T("image/"));
        _tcscat(szGeo,_T("geometry/"));
        _tcscat(szSnd,_T("sound/"));

        IDAGeometryPtr rawSphere =
          e->ImportGeometry( _bstr_t(szGeo) + _bstr_t( "sphere.x" ) );
        IDAGeometryPtr rawCube =
          e->ImportGeometry( _bstr_t(szGeo) + _bstr_t( "cube.x" ) );
        IDAGeometryPtr rawCone =
          e->ImportGeometry( _bstr_t(szGeo) + _bstr_t( "cone.x" ) );
        IDAGeometryPtr rawCylinder =
          e->ImportGeometry( _bstr_t(szGeo) + _bstr_t( "cylinder.x" ) );

        // Import a sound, supply null as the second argument since we don't
        // want a length.
        IDASoundPtr snd = e->ImportSound(  _bstr_t(szSnd) + _bstr_t( "etherial.mp2" ) )->Sound->Loop();

        // Size the objects down.
        IDATransform3Ptr scaler = e->Scale3UniformAnim( e->DANumber( 0.25 ) );
        IDAGeometryPtr sphere   = rawSphere->Transform( scaler );
        IDAGeometryPtr cube     = rawCube->Transform( scaler );
        IDAGeometryPtr cone     = rawCone->Transform( scaler );
        IDAGeometryPtr cylinder = rawCylinder->Transform( scaler );

        IDAGeometryPtr xcone    = cone->Transform( e->Rotate3Anim(
          e->GetXVector3(), e->DANumber( 3.1415927 ) ) );

        IDAColorPtr color1 = myColor( 0, 2, e );
        IDAColorPtr color2 = myColor( 0.25, 3, e );
        IDAColorPtr color3 = myColor( 0.5, 4, e );
        IDAColorPtr color4 = myColor( 0.75, 1, e );

        // Shapes to start with (many with time varying colors)
        IDAGeometryPtr shapes[] = {
            sphere->DiffuseColor( color1 ),
            sphere->DiffuseColor( color1 ),
            cone->DiffuseColor( color2 ),
            xcone->DiffuseColor( color2 ),
            cube->DiffuseColor( color3 ),
            cube->DiffuseColor( color3 ),
            cylinder->DiffuseColor( color4 ) };

        // Places to Translate them to.
        double tls[][3] = { { 0.75, 0, 0 },
                           { -0.75, 0, 0 },
                           { 0, 0.75, 0 },
                           { 0, -0.75, 0 },
                           { 0, 0, 0.75 },
                           { 0, 0, -0.75 },
                           { 0, 0, 0 } };

        // Go ahead and accumulate up the Translated geometry by cycling
        // through the arrays.
        IDAGeometryPtr geo = e->EmptyGeometry;
        for ( int i = 0; i < sizeof( shapes ) / sizeof( shapes[0] ); i++ ) {
          IDATransform3Ptr xf = e->Translate3Anim( e->DANumber( tls[i][0] ),
            e->DANumber( tls[i][1] ), e->DANumber( tls[i][2] ) );
          geo = e->UnionGeometry( geo, shapes[i]->Transform( xf ) );
        }

        // Set the whole thing in motion
        IDATransform3Ptr xf =
            e->Compose3( e->Rotate3Anim( e->GetXVector3(), e->LocalTime ),
              e->Compose3( e->Rotate3Anim( e->GetZVector3(), e->Mul( e->LocalTime, e->DANumber( 1.9 ) ) ),
                e->Compose3( e->Rotate3Anim( e->GetYVector3(), e->Mul( e->LocalTime, e->DANumber( 3.1415927 ) ) ),
                  e->Scale3UniformAnim( e->DANumber( 0.75 ) ) ) ) );

        geo = geo->Transform( xf );

        // Render into an image, and overlay atop a solid image.
        IDAImagePtr geoIm = geometryImage( e->UnionGeometry( geo, e->DirectionalLight ), e );

        IDAImagePtr model = e->Overlay( geoIm, e->SolidColorImage( e->White ) );

        // Set the size of the viewport.  Not doing this  (as done in this
        // case) will render the animation accross the entire primary surface.
        _view->SetViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        // Let the view draw to the primary surface.
        _view->IDirectDrawSurface = ddsurf;
        _view->CompositeDirectlyToTarget = TRUE;
        // Start the model on the view.
        _view->StartModel(model, snd, 0);

    } catch (_com_error &e) {
        dump_com_error( e );
        return false;
    }
    return true;
}

BOOL DXA::resetDXASurfaces(IUnknown *ddsurf)
{
    try {
        _view->IDirectDrawSurface = ddsurf;
    } catch (_com_error &e) {
        dump_com_error( e );
        return false;
    }
    return true;
}

/*
 * geometryImage
 * Renders a geometry as an image
 */
IDAImagePtr DXA::geometryImage( IDAGeometryPtr geo, IDAStaticsPtr e ) {
    IDACameraPtr cam = e->PerspectiveCameraAnim( e->DANumber( 1 ),
      e->DANumber( 0 ) )->Transform( e->Translate3Anim(
      e->DANumber( 0 ), e->DANumber( 0 ), e->DANumber( 2 ) ) );
    IDATransform2Ptr sc = e->Scale2UniformAnim( e->DANumber( 0.1 ) );

    return geo->Render( cam )->Transform( sc );
  }

/*
 * myColor
 * Create a animated color whose saturation varies over time.
 */
IDAColorPtr DXA::myColor( double hue, double saturationRate,
    IDAStaticsPtr e ) {
    IDANumberPtr sat = e->Add( e->Mul( e->Sin( e->Mul( e->LocalTime,
      e->DANumber( saturationRate ) ) ), e->DANumber( 0.5 ) ), e->DANumber( 0.5 ) );

    return e->ColorHslAnim( e->DANumber( hue ), sat, e->DANumber( 0.5 ) );
  }

/*
 * tick
 * Ask DA to sample and display the model.
 */
void DXA::tick() {
    static double startTime = 0;
    struct _timeb timebuffer;

    if (_view != NULL) {
        _ftime( &timebuffer );
        double thisTime = timebuffer.time + ( timebuffer.millitm / 1000.0 );

        if ( startTime == 0 )  {
            startTime = thisTime;
            thisTime = 0;
        }
        else
            thisTime -= startTime;

        _view->Tick(thisTime);
        _view->Render();
    }
}


DXA.H

#import "danim.dll" \
  exclude( "_RemotableHandle", "IMoniker", "IPersist", "ISequentialStream", \
  "IParseDisplayName", "IOleClientSite", "_FILETIME", "tagSTATSTG" ) \
  rename( "GUID", "DAGUID" ) \
  rename_namespace( "DAnim" )

#include <stdio.h>
#include <sys\timeb.h>
#include <tchar.h>

#define WINDOW_WIDTH    300
#define WINDOW_HEIGHT   300

using namespace DAnim;

// Create the DirectAnimation class that will render to the primary DirectDraw
// surface.
class DXA  {

  public:
BOOL initDXAViewObj(IUnknown *ddsurf);
BOOL resetDXASurfaces(IUnknown *ddsurf);
void tick();
  protected:
static IDAViewPtr _view;
    IDAImagePtr geometryImage( IDAGeometryPtr geo, IDAStaticsPtr e );
    IDAColorPtr myColor( double hue, double saturationRate, IDAStaticsPtr e );
};


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by union.rc
//
#define IDI_ICON1                       101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


UNION.CPP

/*==========================================================================
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       union.cpp
 *
 *  This sample takes a empty direct draw primary surface which is created
 *  in this file and passed to the DAView object for rendering.
 *
 ***************************************************************************/

#define NAME "UNION"
#define TITLE "UNION"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddrawex.h>
#include "resource.h"
#include "dxa.h"

BOOL            gExclusive = FALSE;
BOOL            bPaused = FALSE;
HWND            hWndMain;
DXA             Danim;

IDirectDrawFactory      *lpDDF;
LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSDA;        // Offscreen surface for DA
LPDIRECTDRAWCLIPPER     lpClipper;      // Clipper for the primary surface
BOOL                    bActive;        // is application active?
RECT                    windowRect;     // windows' client rectangle

/*
 * releaseSurfaces
 *
 * Release the global surfaces and clipper.
 */
void releaseSurfaces( void )
{
    if( lpDDSPrimary != NULL )
    {
        lpDDSPrimary->Release();
        lpDDSPrimary = NULL;
    }
    if( lpDDSDA != NULL )
    {
        lpDDSDA->Release();
        lpDDSDA = NULL;
    }
    if( lpClipper != NULL )
    {
        lpClipper->Release();
        lpClipper = NULL;
    }
}

/*
 * finiObjects
 *
 * Finish with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
        releaseSurfaces();
        lpDD->Release();
        lpDD = NULL;
    }

    // Clean up the screen on exit
    RedrawWindow( NULL, NULL, NULL, RDW_INVALIDATE | RDW_ERASE |
                     RDW_ALLCHILDREN );

} /* finiObjects */

/*
 * initFail
 *
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hWnd, LPCTSTR msg )
{
    finiObjects();
    MessageBox( hWnd, msg, TITLE, MB_OK );
    DestroyWindow( hWnd );
    return FALSE;

} /* initFail */

/*
 * createSurfaces
 *
 * Create the front and back buffers.
 */
BOOL createSurfaces( void )
{
    HRESULT             ddrval;
    DDSURFACEDESC       ddsd;

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
        return false;

    if(!gExclusive)
    {
        //
        // now create a DirectDrawClipper object.
        //
        ddrval = lpDD->CreateClipper(0, &lpClipper, NULL);

        if( ddrval == DD_OK )
        {
            ddrval = lpClipper->SetHWnd(0, hWndMain);

            if( ddrval == DD_OK )
            {
                ddrval = lpDDSPrimary->SetClipper(lpClipper);
            }
        }
        if( ddrval != DD_OK )
            return false;
    }

    //
    // Create a DirectDrawSurface to hand to Direct Animation.
    //
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |
                          DDSCAPS_3DDEVICE;
    ddsd.dwWidth = WINDOW_WIDTH;
    ddsd.dwHeight = WINDOW_HEIGHT;

    ddrval = lpDD->CreateSurface(&ddsd, &lpDDSDA, NULL);
    if (ddrval != DD_OK)
        return false;

    return true;
} /* createSurfaces */

/*
 * recreateSurfaces
 *
 * The screen resolution or bitdepth has changed, re-create
 * all surfaces.
 */
BOOL recreateSurfaces( void )
{
    releaseSurfaces();

    if (createSurfaces())
    {
        return Danim.resetDXASurfaces(lpDDSDA);
    }
    return false;
} /* recreateSurfaces */

/*
 * restoreAll
 *
 * Restore all lost objects
 */
BOOL restoreAll( void )
{
    HRESULT     ddrval;

    ddrval = lpDDSPrimary->Restore();
    if (ddrval == DDERR_WRONGMODE)
    {
        if (recreateSurfaces())
            return true;

    }
    else if (ddrval == DD_OK)
    {
        ddrval = lpDDSDA->Restore();
    }

    return initFail(hWndMain, "DirectDraw: Can't Recreate Surfaces");
} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORD        lastTickCount = 0;
    static int          currentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORD               thisTickCount;
    DWORD               delay = 17;
    HRESULT             ddrval;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= delay)
    {
        return;
    }

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) > delay)
    {
        // Move to next frame;
        lastTickCount = thisTickCount;
        currentFrame++;
        if(currentFrame > 59)
        {
            currentFrame = 0;
        }
    }

    // DA doesn't like lost surface.  We should restore lost surface
    // before asking DA to render.
    if (lpDDSDA && lpDDSDA->IsLost() == DDERR_SURFACELOST)
    {
        // This function will destroy the window if failed.
        if (!restoreAll())
            return;
    }

    // Ask DA to render to the DA surface the next image/sound for _view.
    Danim.tick();

    // Blit from the DA surface to the screen.
    RECT daRect;
    daRect.left = 0;
    daRect.top = 0;
    daRect.right = WINDOW_WIDTH;
    daRect.bottom = WINDOW_HEIGHT;
    RECT screenRect;
    screenRect.left = windowRect.left;
    screenRect.top = windowRect.top;
    screenRect.right = windowRect.left + WINDOW_WIDTH;
    screenRect.bottom = windowRect.top + WINDOW_HEIGHT;

    while( 1 )
    {
        ddrval = lpDDSPrimary->Blt(&screenRect, lpDDSDA,
                                   &daRect, DDBLT_WAIT, NULL);

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            ddrval = restoreAll();
            if( ddrval != DD_OK )
            {
                return;
            }
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }

} /* updateFrame */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
                            WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
    case WM_SIZE:
    case WM_MOVE:
        if (IsIconic(hWnd))
        {
            bPaused = true;
        }

        if (!gExclusive)
        {
            GetClientRect(hWnd, &windowRect);
            ClientToScreen(hWnd, (LPPOINT)&windowRect);
            ClientToScreen(hWnd, (LPPOINT)&windowRect+1);
        } else {
            SetRect(&windowRect, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        }
        break;

    case WM_ACTIVATEAPP:
        bActive = wParam && GetForegroundWindow() == hWnd;

        // Unpause when we're no longer iconic.
        if (bPaused && bActive)
        {
            bPaused = false;
        }
        break;

    case WM_SETCURSOR:
        if (gExclusive && bActive)
        {
            SetCursor(NULL);
            return TRUE;
        }
        break;

    case WM_CREATE:
        break;

    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd,WM_CLOSE,0,0);
            break;
        }
        break;

    case WM_DESTROY:
        finiObjects();
        PostQuitMessage( 0 );
        break;
    }

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

} /* WindowProc */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASS            wc;
    HRESULT             ddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    DWORD wndStyle = WS_OVERLAPPEDWINDOW;
    if( gExclusive)
        wndStyle = WS_POPUP;

    hWndMain = CreateWindowEx(
        WS_EX_APPWINDOW,
        NAME,
        TITLE,
        wndStyle,
        0,
        0,
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    ShowWindow( hWndMain, nCmdShow );
    UpdateWindow( hWndMain );

    /*
     * create the main DirectDraw object
     */

    ddrval = CoCreateInstance(CLSID_DirectDrawFactory,
                              NULL, CLSCTX_INPROC_SERVER,
                              IID_IDirectDrawFactory,
                              (void **) & lpDDF);

    char strDDFailed[] = "DirectDraw Init Failed";
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    ddrval = lpDDF->CreateDirectDraw(NULL, hWndMain, DDSCL_NORMAL, 0, NULL, &lpDD);
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    // Get exclusive mode if requested
    if(gExclusive)
    {
        ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    }
    else
    {
        ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_NORMAL );
    }
    if( ddrval != DD_OK )
    {
        return initFail(hWndMain, strDDFailed);
    }

    if (!createSurfaces())
    {
        return initFail(hWndMain, strDDFailed);
    }

    // Create a DAView object and construct the model.
    if (!Danim.initDXAViewObj(lpDDSDA))
    {
        return initFail(hWndMain, "DirectX Animation Init FAILED");
    }

    return TRUE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;
    LPSTR       c;

    for(c=lpCmdLine; *c != '\0'; c++)
    {
        switch( *c )
        {
            case 'X': gExclusive = TRUE; break;
        }
    }

    if( !doInit( hInstance, nCmdShow ) )
    {
        return FALSE;
    }

    while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( !GetMessage( &msg, NULL, 0, 0 ) )
                return msg.wParam;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if (!bPaused && (bActive || !gExclusive))
        {
            updateFrame();
        }
        else
        {
            WaitMessage();
        }
    }
} /* WinMain */


COFFEE.FRM

VERSION 5.00
Object = "{34F681D0-3640-11CF-9294-00AA00B8A733}#1.0#0"; "danim.dll"
Begin VB.Form coffee
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Coffee"
   ClientHeight    =   3585
   ClientLeft      =   45
   ClientTop       =   330
   ClientWidth     =   7260
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   3585
   ScaleWidth      =   7260
   StartUpPosition =   3  'Windows Default
   Begin DirectAnimationCtl.DAViewerControlWindowed DAViewerControlWindowed
      Height          =   3375
      Left            =   120
      OleObjectBlob   =   "coffee.frx":0000
      TabIndex        =   0
      Top             =   120
      Width           =   7095
   End
End
Attribute VB_Name = "coffee"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' Visual Basic verion of Coffee
Dim mediaBase, geoBase, imgBase, sndBase As String
Private Sub Form_Load()
  mediaBase = CurDir + "\"
  geoBase = mediaBase
  imgBase = mediaBase
  sndBase = mediaBase

  'Set the background
  Set imgBackGround = ImportImage(imgBase + "clouds_coffee.gif")

  'Create the final montage by layering the espresso machine, the cups and the steam
  'on top of each other.
  Set finalMtg = UnionMontage(UnionMontage(steamMontage(), _
    montage()), machineMontage())

  'Create the final image.
  Set finalImage = Overlay(finalMtg.Render(), Overlay(beans(), imgBackGround))
  Set finalImage = Overlay(finalImage, SolidColorImage(White))

  DAViewerControlWindowed.UpdateInterval = 0.2

  'Display the final image.
  DAViewerControlWindowed.Image = finalImage

  'Set the sound.
  DAViewerControlWindowed.sound = Mix(sound().Pan(-1), sound().Pan(1))

  'Start the animation.
  DAViewerControlWindowed.Start
End Sub
Function sound()
  'This function creates the sound, which starts out as silence, and then
  'changes to steam.mp2 when the image is clicked.
  Set steamDurationConst = DANumber(7.25)
  Set steamSound = ImportSound(sndBase + "steam.mp2").sound

  Set s0 = CreateObject("DirectAnimation.DASound")
    s0.Init DAStatics.Until(Silence, LeftButtonDown, _
      DAStatics.Until(steamSound.Gain(0.85), TimerAnim(steamDurationConst), s0))

  Set sound = s0
End Function
Function montage()
  'This function creates a montage of five cups, which rotate around the espresso
  'machine.  The orbit is constructed by the orbitCup function.
  pi = 3.14159265359
  total = 5
  Set cupImageX = EmptyMontage

  For i = 0 To total
    Set cupImageX = UnionMontage(cupImageX, orbitCup(Add(Mul(DANumber(i), _
      Mul(DANumber(2), Div(DANumber(pi), DANumber(total)))), LocalTime)))
  Next
  Set montage = cupImageX
End Function
Function beans()
  'This function creates the beans you see in the background.  Two images,
  'bean1.gif and bean2.gif are imported, and then moved across the screen
  'while being rotated.
  Set delay = DANumber(0.5)
  Set Size = DANumber(0.5)
  Set initBean1 = ImportImage(imgBase + "bean1.gif")
  Set initBean2 = ImportImage(imgBase + "bean2.gif")

  Set image0 = CreateObject("DirectAnimation.DAImage")
  Set image1 = CreateObject("DirectAnimation.DAImage")
  image0.Init DAStatics.Until(initBean1, TimerAnim(delay), image1)
  image1.Init DAStatics.Until(initBean2, TimerAnim(delay), image0)

  Set bean1 = image0.Transform(Scale2UniformAnim(Size))
  Set bean2 = image1.Transform(Scale2UniformAnim(Size))

  Set beans = Overlay(bean1.Transform(Translate2(-0.01, -0.01)), _
    bean2.Transform(Translate2(0.01, 0)))

  Set rain = beans.Tile()

  Set motion = Mul(LocalTime, Mul(DANumber(2), _
    Div(DANumber(0.03), DANumber(4))))

  Set beans = rain.Transform(Translate2Anim(Neg(motion), Neg(motion)))
End Function
Function machineMontage()
  'This function displays the espresso machine.
  Set steamDurationConst = DANumber(7.25)
  Set espreso1 = ImportImage(imgBase + "espreso1.gif")
  Set espreso2 = ImportImage(imgBase + "espreso2.gif")

  Set image5 = CreateObject("DirectAnimation.DAImage")
    image5.Init DAStatics.Until(espreso1, LeftButtonDown, _
      DAStatics.Until(espreso2, TimerAnim(steamDurationConst), image5))

  Set machineMontage = ImageMontage(image5, 0)
End Function
Function steamMontage()
  'This function displays the steam you see when you click on the image.
  Set steamDurationConst = DANumber(7.25)
  Dim steamImages(4)
  Set steamImages(0) = ImportImage(imgBase + "steam_1.gif")
  Set steamImages(1) = ImportImage(imgBase + "steam_2.gif")
  Set steamImages(2) = ImportImage(imgBase + "steam_3.gif")
  Set steamImages(3) = ImportImage(imgBase + "steam_4.gif")
  Set steamImages(4) = ImportImage(imgBase + "steam_5.gif")

  Set steamLen = DANumber(4)

  Set condition = GT(Add(Div(Mul(LocalTime, steamLen), _
    steamDurationConst), DANumber(1)), steamLen)

  Set result2 = Add(Div(Mul(LocalTime, steamLen), _
    steamDurationConst), DANumber(1))

  Set Index = Cond(condition, steamLen, result2)

  Set a = DAStatics.Array(steamImages)

  Set s0 = CreateObject("DirectAnimation.DAImage")
    s0.Init DAStatics.Until(EmptyImage, LeftButtonDown, _
      DAStatics.Until(a.NthAnim(Index), TimerAnim(steamDurationConst), s0))

  Set image1 = s0.Transform(Translate2(-0.0085, 0.002))

  Set steamMontage = ImageMontage(image1, -0.0001)
  End Function
Function orbitCup(angle)
  pi = 3.14159265359
  Set pos = Point3(0, 0.05, 0)
  Set pos = pos.Transform(Compose3(Rotate3Anim(XVector3, Mul(DANumber(7), _
    Div(DANumber(pi), DANumber(16)))), Rotate3Anim(ZVector3, angle)))

  Set cupAngle = LocalTime

  Set imageXX = cupImage(cupAngle).Transform(Compose2(Translate2Anim(pos.X, pos.Y), _
    Scale2UniformAnim(DAStatics.Sub(DANumber(1), _
      Mul(DAStatics.Abs(DAStatics.Cos(Div(angle, _
        DANumber(2)))), DANumber(0.5))))))

  Set orbitCup = ImageMontageAnim(imageXX, Neg(pos.Z))
End Function
Function cupImage(cupAngle)
  pi = 3.14159265359
  Dim cupImages
  cupImages = Array(ImportImage(imgBase + "cup1.gif"), _
    ImportImage(imgBase + "cup2.gif"), ImportImage(imgBase + "cup3.gif"), _
      ImportImage(imgBase + "cup4.gif"), ImportImage(imgBase + "cup5.gif"), _
        ImportImage(imgBase + "cup6.gif"), ImportImage(imgBase + "cup7.gif"), _
          ImportImage(imgBase + "cup8.gif"))

  Set Number = DANumber(7)
  Set Index = Add(DAStatics.Mod(Mul(Number, Div(cupAngle, _
    Mul(DANumber(2), DANumber(pi)))), Number), DANumber(1))

  Set a = DAStatics.Array(cupImages)

  Set cupImage = a.NthAnim(Index)
End Function


MSCUBES.FRM

VERSION 5.00
Object = "{34F681D0-3640-11CF-9294-00AA00B8A733}#1.0#0"; "danim.dll"
Begin VB.Form mscubes
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Mscubes"
   ClientHeight    =   4080
   ClientLeft      =   45
   ClientTop       =   330
   ClientWidth     =   8505
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   4080
   ScaleWidth      =   8505
   StartUpPosition =   3  'Windows Default
   Begin DirectAnimationCtl.DAViewerControlWindowed DAViewerControlWindowed
      Height          =   3855
      Left            =   120
      OleObjectBlob   =   "mscubes.frx":0000
      TabIndex        =   0
      Top             =   120
      Width           =   8295
   End
End
Attribute VB_Name = "mscubes"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' Visual Basic verion of Mscubes
Private Sub Form_Load()
  Set changingRate = CreateObject("DirectAnimation.DANumber")
    changingRate.Init DAStatics.Until(DANumber(0.2), LeftButtonDown, _
      DAStatics.Until(DANumber(0), LeftButtonDown, changingRate))

  ' Set up relative paths for media imports.  Does not work in VB
  ' debug.  Create executable.
  Dim mediaBase, geoBase, imgBase, midiBase As String
  mediaBase = CurDir + "\..\..\..\..\..\Media\"
  geoBase = mediaBase + "geometry\"
  imgBase = mediaBase + "image\"
  midiBase = mediaBase + "midi\"

  'Import the needed geometry & images & sound.
  Set geo = ImportGeometry(geoBase + "cube.x")
  Set importSnd = ImportSound(midiBase + "circus.mid")
  Set backgroundSnd = importSnd.Sound.RepeatForever()

  Dim IELogos(8)
  Set IELogos(0) = ImportImage(imgBase + "clogo1.gif")
  Set IELogos(1) = ImportImage(imgBase + "clogo2.gif")
  Set IELogos(2) = ImportImage(imgBase + "clogo3.gif")
  Set IELogos(3) = ImportImage(imgBase + "clogo4.gif")
  Set IELogos(4) = ImportImage(imgBase + "clogo5.gif")
  Set IELogos(5) = ImportImage(imgBase + "clogo6.gif")
  Set IELogos(6) = ImportImage(imgBase + "clogo7.gif")
  Set IELogos(7) = ImportImage(imgBase + "clogo8.gif")
  Set IELogos(8) = ImportImage(imgBase + "clogo9.gif")

  'Call the Movie(), which cycles through the IELogos images every second.
  Set imageCube = Movie(IELogos)

  'Set lights.
  Set pLight = PointLight.Transform(Translate3(0, 0, 4))
  Set dLight = DirectionalLight.Transform(Rotate3(YVector3, 0.5))

  'Manipulate the geometry.
  Set geo = geo.Transform(Scale3Uniform(0.5)).Texture(imageCube.MapToUnitSquare())

  'Create the four cubes and set them to rotate.
  Set srate = Mul(LocalTime, DANumber(9))
  Set cube1 = geo.Transform(Compose3(Translate3(0, 0, 2), _
    Rotate3Anim(Vector3(0, 1, 0), Mul(DANumber(0.04), srate))))
  Set cube2 = geo.Transform(Compose3(Translate3(0, 0, -2), _
    Rotate3Anim(Vector3(0, 1, 1), Mul(DANumber(0.03), srate))))
  Set cube3 = geo.Transform(Compose3(Translate3(2, 0, 0), _
    Rotate3Anim(Vector3(0, 0, 1), Mul(DANumber(0.02), srate))))
  Set cube4 = geo.Transform(Compose3(Translate3(-2, 0, 0), _
    Rotate3Anim(Vector3(1, 1, 1), Mul(DANumber(0.03), srate))))

  'Do the final prep work on the model.
  Set finalgeo = UnionGeometry(cube1, UnionGeometry(cube2, UnionGeometry(cube3, cube4)))
  Set Camera = PerspectiveCamera(5.5, 4.5).Transform(Scale3(45, 45, 1))

  'Make the complete image rotate.
  Set finalgeo = finalgeo.Transform(Rotate3Anim(YVector3, Integral(changingRate)))
  Set finalgeo = UnionGeometry(finalgeo, pLight)

  'Render and display the image.
  Set rendered_geo = finalgeo.Render(Camera)

  DAViewerControlWindowed.UpdateInterval = 0.2

  'Set the image.
  DAViewerControlWindowed.Image = Overlay(rendered_geo, DAStatics.SolidColorImage(White))

  'Set the sound.
  DAViewerControlWindowed.Sound = backgroundSnd

  'Start the animation.
  DAViewerControlWindowed.Start
End Sub

Function Movie(IELogos)
  Set movieArray = DAStatics.DAArray(IELogos)
  Set Movie = movieArray.NthAnim(DAStatics.Mod(LocalTime, DANumber(9)))
End Function


PICK3.FRM

VERSION 5.00
Object = "{34F681D0-3640-11CF-9294-00AA00B8A733}#1.0#0"; "danim.dll"
Begin VB.Form Picking
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Picking"
   ClientHeight    =   4665
   ClientLeft      =   30
   ClientTop       =   270
   ClientWidth     =   5055
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   4665
   ScaleWidth      =   5055
   StartUpPosition =   3  'Windows Default
   Begin DirectAnimationCtl.DAViewerControlWindowed DAViewerControlWindowed
      Height          =   4455
      Left            =   120
      OleObjectBlob   =   "Pick3.frx":0000
      TabIndex        =   0
      Top             =   120
      Width           =   4815
   End
End
Attribute VB_Name = "Picking"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'Pick3 Visual Basic Sample
Private Sub Form_Load()
  pi = 3.1459

  Dim size As DATransform3
  Set size = Scale3Uniform(0.25)

  Dim speed As DANumber
  Set speed = DANumber(0.07)

  ' Set up relative paths for media imports.  Does not work in VB
  ' debug.  Create executable.
  Dim mediaBase, geoBase, imgBase As String
  mediaBase = CurDir + "\..\..\..\..\..\Media\"
  geoBase = mediaBase + "geometry\"
  imgBase = mediaBase + "image\"

  'Import the geometries.
  Dim rawCube As DAGeometry
  Set rawCube = ImportGeometry(geoBase + "cube.x").Transform(size)

  Dim rawCylinder As DAGeometry
  Set rawCylinder = ImportGeometry(geoBase + "cylinder.x").Transform(size)

  Dim rawCone As DAGeometry
  Set rawCone = ImportGeometry(geoBase + "cone.x").Transform(size)

  'Import background.
  Dim stillSky As DAImage
  Set stillSky = ImportImage(imgBase + "cldtile.jpg")

  'Make the geometries pickable.
  Set cone1 = activate(rawCone, Green)
  Set cube1 = activate(rawCube, Magenta)
  Set cube2 = activate(rawCube, ColorHslAnim(Div(LocalTime, DANumber(8)), DANumber(1), DANumber(0.5)))
  Set cylinder = activate(rawCylinder, ColorRgb(0.8, 0.4, 0.4))

  'Construct the final geometry, scale and rotate it.
  Set multigeo = UnionGeometry(cone1.Transform(Translate3(0, 1, 0)), _
    UnionGeometry(cube1.Transform(Translate3(0, 0, 1)), _
    UnionGeometry(cube2.Transform(Translate3(0, 0, -1)), cylinder)))

  Set X = Add(DAStatics.Abs(DAStatics.Sin(Mul(LocalTime, _
    DANumber(0.2)))), DANumber(0.5))
  Set Y = Add(DAStatics.Abs(DAStatics.Sin(Mul(LocalTime, _
    DANumber(0.26)))), DANumber(0.5))
  Set Z = Add(DAStatics.Abs(DAStatics.Sin(Mul(LocalTime, _
    DANumber(0.14)))), DANumber(0.5))

  Set geo = multigeo.Transform(Scale3Anim(X, Y, Z))

  Set maxSky = stillSky.BoundingBox().Max()

  Set tiledSky = stillSky.Tile()
  Set movingSky = tiledSky.Transform(Translate2Anim(Mul(LocalTime, _
    Div(maxSky.X, DANumber(8))), Mul(LocalTime, Div(maxSky.X, DANumber(16)))))

  Set movingGeoImg = geometryImage(geo.Transform(Compose3(Rotate3Anim(ZVector3, _
    Mul(speed, Mul(LocalTime(), DANumber(1.9)))), _
      Rotate3Anim(YVector3, Mul(speed, Mul(LocalTime(), DANumber(pi)))))), speed)

  Set fs = DefaultFont.size(14).Color(Black)
  Set titleIm = StringImage("Left Click on an Object", fs).Transform(Translate2(0, 0.04))

  DAViewerControlWindowed.UpdateInterval = 0.2

  'Display the final image.
  DAViewerControlWindowed.Image = Overlay(titleIm, Overlay(movingGeoImg, movingSky))

  'Start the animation.
  DAViewerControlWindowed.Start
End Sub

Function activate(unpickedGeo As DAGeometry, col As DAColor) As DAGeometry
  Dim pickGeo As DAPickableResult
  Set pickGeo = unpickedGeo.Pickable()

  Dim pickEvent As DAEvent
  Set pickEvent = AndEvent(LeftButtonDown, pickGeo.pickEvent)

  Dim numcyc As DANumber
  Set numcyc = CreateObject("DirectAnimation.DANumber")
  numcyc.Init DAStatics.Until(DANumber(0), pickEvent, DAStatics.Until(DANumber(1), pickEvent, numcyc))

  Dim colcyc As DAColor
  Set colcyc = CreateObject("DirectAnimation.DAColor")
  colcyc.Init DAStatics.Until(White, pickEvent, DAStatics.Until(col, pickEvent, colcyc))

  Dim xf As DATransform3
  Set xf = Rotate3Anim(XVector3, Integral(numcyc))

  Set activate = pickGeo.Geometry.DiffuseColor(colcyc).Transform(xf)
End Function
Function geometryImage(geo As DAGeometry, speed As DANumber) As DAImage
  Dim scaleFactor As DANumber
  Set scaleFactor = DANumber(0.02)

  Dim perspTransform As DATransform3
  Set perspTransform = CreateObject("DirectAnimation.DATransform3")
    perspTransform.Init DAStatics.Until(Compose3(Rotate3Anim(XVector3, _
      Mul(speed, LocalTime)), Translate3(0, 0, 0.2)), RightButtonDown, _
        DAStatics.Until(Rotate3Anim(XVector3, Mul(speed, LocalTime)), _
          RightButtonDown, perspTransform))

  Set light = UnionGeometry(DirectionalLight.Transform(perspTransform), _
    DirectionalLight)

  Dim strcyl As DAString
  Set strcyl = CreateObject("DirectAnimation.DAString")
  strcyl.Init DAStatics.Until(DAString("Perspective - Right Click to Switch"), _
    RightButtonDown, DAStatics.Until(DAString("Parallel - Right Click to Switch"), _
      RightButtonDown, strcyl))

  Dim perspectiveCam As DACamera
  Set perspectiveCam = PerspectiveCamera(1, 0).Transform(Compose3(Rotate3Anim(XVector3, _
    Mul(speed, LocalTime)), Translate3(0, 0, 0.2)))

  Dim parallelCam As DACamera
  Set parallelCam = ParallelCamera(1).Transform(Rotate3Anim(XVector3, _
    Mul(speed, LocalTime)))

  Dim camera As DACamera
  Set camera = CreateObject("DirectAnimation.DACamera")
  camera.Init DAStatics.Until(perspectiveCam, RightButtonDown, _
    DAStatics.Until(parallelCam, RightButtonDown, camera))

  Dim fs As DAFontStyle
  Set fs = DefaultFont.size(14).Color(Red)

  Dim txtIm, xltTxt As DAImage
  Set txtIm = StringImageAnim(strcyl, fs)
  Set xltTxt = txtIm.Transform(Translate2(0, -0.045))

  Set geometryImg = UnionGeometry(geo.Transform(Scale3UniformAnim(scaleFactor)), _
    light).Render(camera)

  Set geometryImage = Overlay(xltTxt, geometryImg)
End Function


README.TXT

#----------------------------------------------------------------------------
#
#  ActiveMovie readme.txt
#
#  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
#  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
#  PURPOSE.
#
#  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
#
#----------------------------------------------------------------------------

The async sample code comprises 3 directories:

INCLUDE
-------

Defines some helper classes to create filters which conform to the
IAsyncReader interface.

Four classes are defined:

    CAsyncReader
    ------------
    To create a filter.  Defines the filter and pin.

    CAsyncOutputPin
    ---------------
    The filter's output pin

    CAsyncIo
    --------
    Methods to manage a set of async requests

    CAsyncStream
    ------------
    An abstract class that represents the source of the data

BASE
----

    Impelments

    -- CAsyncReader

    -- CAsyncOutputPin

    -- CAsyncIo

    CAsyncReader is passed a CAsyncStream object which the filter uses
    as its data source

MEMFILE
-------

    Consists of

    1.  A definition of

        --  CMemStream - a non-abstract class derived from CAsyncStream that
            wraps a memory segment as the IAsyncReader source data

        --  CMemReader - a source filter derived from CAsyncReader.  It
            just overrides the constructor of CAsyncReader to set the
            output pin's media type dependent on the file type

    1.  A main() routine that

        Opens a file

        Guesses the file's media type dependent on the file extension.  Note
        that RenderFile actually looks at the check bytes in the

            HKEY_CLASSES_ROOT\Media Type

        key of the registation data base to try to determine the media type
        and media subtype of the file.

        Creates a filter graph

        Creates a source filter of type CMemReader and adds it to the filter
        graph

        Renders the output pin of this filter

        Plays the graph through once


ASYNCIO.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <asyncio.h>

// --- CAsyncRequest ---

// implementation of CAsyncRequest representing a single
// outstanding request. All the i/o for this object is done
// in the Complete method.

// init the params for this request.
// Read is not issued until the complete call
HRESULT
CAsyncRequest::Request(
    CAsyncIo *pIo,
    CAsyncStream *pStream,
    LONGLONG llPos,
    LONG lLength,
    BOOL bAligned,
    BYTE* pBuffer,
    LPVOID pContext,// filter's context
    DWORD dwUser)// downstream filter's context
{
    m_pIo = pIo;
    m_pStream = pStream;
    m_llPos = llPos;
    m_lLength = lLength;
    m_bAligned = bAligned;
    m_pBuffer = pBuffer;
    m_pContext = pContext;
    m_dwUser = dwUser;
    m_hr = VFW_E_TIMEOUT;   // not done yet

    return S_OK;
}

// issue the i/o if not overlapped, and block until i/o complete.
// returns error code of file i/o
//
//
HRESULT
CAsyncRequest::Complete()
{

    m_pStream->Lock();

    m_hr = m_pStream->SetPointer(m_llPos);
    if (S_OK == m_hr) {

        DWORD dwActual;
        m_hr = m_pStream->Read(m_pBuffer, m_lLength, m_bAligned, &dwActual);
        if (FAILED(m_hr)) {
        } else if (dwActual != (DWORD)m_lLength) {
            // tell caller size changed - probably because of EOF
            m_lLength = (LONG) dwActual;
            m_hr = S_FALSE;
        } else {
            m_hr = S_OK;
        }
    }

    m_pStream->Unlock();
    return m_hr;
}

// --- CAsyncIo ---

// note - all events created manual reset

CAsyncIo::CAsyncIo(CAsyncStream *pStream)
 : m_hThread(NULL),
   m_evWork(TRUE),
   m_evDone(TRUE),
   m_evStop(TRUE),
   m_listWork(NAME("Work list")),
   m_listDone(NAME("Done list")),
   m_bFlushing(FALSE),
   m_cItemsOut(0),
   m_bWaiting(FALSE),
   m_pStream(pStream)
{

}

CAsyncIo::~CAsyncIo()
{
    // move everything to the done list
    BeginFlush();

    // shutdown worker thread
    CloseThread();

    // empty the done list
    POSITION pos = m_listDone.GetHeadPosition();
    while (pos) {
        CAsyncRequest* pRequest = m_listDone.GetNext(pos);
        delete pRequest;
    }
    m_listDone.RemoveAll();
}

// ready for async activity - call this before
// calling Request.
//
// start the worker thread if we need to
//
// !!! use overlapped i/o if possible
HRESULT
CAsyncIo::AsyncActive(void)
{
    return StartThread();
}

// call this when no more async activity will happen before
// the next AsyncActive call
//
// stop the worker thread if active
HRESULT
CAsyncIo::AsyncInactive(void)
{
    return CloseThread();
}

// add a request to the queue.
HRESULT
CAsyncIo::Request(
            LONGLONG llPos,
            LONG lLength,
            BOOL bAligned,
            BYTE* pBuffer,
            LPVOID pContext,
            DWORD dwUser)
{
    if (bAligned) {
        if (!IsAligned(llPos) ||
    !IsAligned(lLength) ||
    !IsAligned((LONG) pBuffer)) {
            return VFW_E_BADALIGN;
        }
    }

    CAsyncRequest* pRequest = new CAsyncRequest;

    HRESULT hr = pRequest->Request(
                            this,
                            m_pStream,
                            llPos,
                            lLength,
                            bAligned,
                            pBuffer,
                            pContext,
                            dwUser);
    if (SUCCEEDED(hr)) {
        // might fail if flushing
        hr = PutWorkItem(pRequest);
    }

    if (FAILED(hr)) {
        delete pRequest;
    }
    return hr;
}

// wait for the next request to complete
HRESULT
CAsyncIo::WaitForNext(
    DWORD dwTimeout,
    LPVOID *ppContext,
    DWORD * pdwUser,
    LONG* pcbActual)
{
    // some errors find a sample, others don't. Ensure that
    // *ppContext is NULL if no sample found
    *ppContext = NULL;

    // wait until the event is set, but since we are not
    // holding the critsec when waiting, we may need to re-wait
    for (;;) {

        if (!m_evDone.Wait(dwTimeout)) {
            // timeout occurred
            return VFW_E_TIMEOUT;
        }

        // get next event from list
        CAsyncRequest* pRequest = GetDoneItem();
        if (pRequest) {
            // found a completed request

            // check if ok
            HRESULT hr = pRequest->GetHResult();
            if (hr == S_FALSE) {

                // this means the actual length was less than
                // requested - may be ok if he aligned the end of file
                if ((pRequest->GetActualLength() +
                     pRequest->GetStart()) == Size()) {
                        hr = S_OK;
                } else {
                    // it was an actual read error
                    hr = E_FAIL;
                }
            }

            // return actual bytes read
            *pcbActual = pRequest->GetActualLength();

            // return his context
            *ppContext = pRequest->GetContext();
            *pdwUser = pRequest->GetUser();
            delete pRequest;
            return hr;
        } else {
            //  Hold the critical section while checking the
            //  list state
            CAutoLock lck(&m_csLists);
            if (m_bFlushing && !m_bWaiting) {

                // can't block as we are between BeginFlush and EndFlush

                // but note that if m_bWaiting is set, then there are some
                // items not yet complete that we should block for.

                return VFW_E_WRONG_STATE;
            }
        }

        // done item was grabbed between completion and
        // us locking m_csLists.
    }
}

// perform a synchronous read request on this thread.
// Need to hold m_csFile while doing this (done in
// request object)
HRESULT
CAsyncIo::SyncReadAligned(
            LONGLONG llPos,
            LONG lLength,
            BYTE* pBuffer,
            LONG* pcbActual
            )
{
    if (!IsAligned(llPos) ||
!IsAligned(lLength) ||
!IsAligned((LONG) pBuffer)) {
            return VFW_E_BADALIGN;
    }

    CAsyncRequest request;

    HRESULT hr = request.Request(
                    this,
                    m_pStream,
                    llPos,
                    lLength,
                    TRUE,
                    pBuffer,
                    NULL,
                    0);

    if (FAILED(hr)) {
        return hr;
    }

    hr = request.Complete();

    // return actual data length
    *pcbActual = request.GetActualLength();
    return hr;
}

HRESULT
CAsyncIo::Length(LONGLONG *pllTotal, LONGLONG* pllAvailable)
{
    *pllTotal = m_pStream->Size(pllAvailable);
    return S_OK;
}

// cancel all items on the worklist onto the done list
// and refuse further requests or further WaitForNext calls
// until the end flush
//
// WaitForNext must return with NULL only if there are no successful requests.
// So Flush does the following:
// 1. set m_bFlushing ensures no more requests succeed
// 2. move all items from work list to the done list.
// 3. If there are any outstanding requests, then we need to release the
//    critsec to allow them to complete. The m_bWaiting as well as ensuring
//    that we are signalled when they are all done is also used to indicate
//    to WaitForNext that it should continue to block.
// 4. Once all outstanding requests are complete, we force m_evDone set and
//    m_bFlushing set and m_bWaiting false. This ensures that WaitForNext will
//    not block when the done list is empty.
HRESULT
CAsyncIo::BeginFlush()
{
    // hold the lock while emptying the work list
    {
        CAutoLock lock(&m_csLists);

        // prevent further requests being queued.
        // Also WaitForNext will refuse to block if this is set
        // unless m_bWaiting is also set which it will be when we release
        // the critsec if there are any outstanding).
        m_bFlushing = TRUE;

        CAsyncRequest * preq;
        while(preq = GetWorkItem()) {
            preq->Cancel();
            PutDoneItem(preq);
        }

        // now wait for any outstanding requests to complete
        if (m_cItemsOut > 0) {

            // can be only one person waiting
            ASSERT(!m_bWaiting);

            // this tells the completion routine that we need to be
            // signalled via m_evAllDone when all outstanding items are
            // done. It also tells WaitForNext to continue blocking.
            m_bWaiting = TRUE;
        } else {
            // all done

            // force m_evDone set so that even if list is empty,
            // WaitForNext will not block
            // don't do this until we are sure that all
            // requests are on the done list.
            m_evDone.Set();
            return S_OK;
        }
    }

    ASSERT(m_bWaiting);

    // wait without holding critsec
    for (;;) {
        m_evAllDone.Wait();
        {
            // hold critsec to check
            CAutoLock lock(&m_csLists);

            if (m_cItemsOut == 0) {

                // now we are sure that all outstanding requests are on
                // the done list and no more will be accepted
                m_bWaiting = FALSE;

                // force m_evDone set so that even if list is empty,
                // WaitForNext will not block
                // don't do this until we are sure that all
                // requests are on the done list.
                m_evDone.Set();

                return S_OK;
            }
        }
    }
}

// end a flushing state
HRESULT
CAsyncIo::EndFlush()
{
    CAutoLock lock(&m_csLists);

    m_bFlushing = FALSE;

    ASSERT(!m_bWaiting);

    // m_evDone might have been set by BeginFlush - ensure it is
    // set IFF m_listDone is non-empty
    if (m_listDone.GetCount() > 0) {
        m_evDone.Set();
    } else {
        m_evDone.Reset();
    }

    return S_OK;
}

// start the thread
HRESULT
CAsyncIo::StartThread(void)
{
    if (m_hThread) {
        return S_OK;
    }

    // clear the stop event before starting
    m_evStop.Reset();

    DWORD dwThreadID;
    m_hThread = CreateThread(
                    NULL,
                    0,
                    InitialThreadProc,
                    this,
                    0,
                    &dwThreadID);
    if (!m_hThread) {
DWORD dwErr = GetLastError();
        return HRESULT_FROM_WIN32(dwErr);
    }
    return S_OK;
}

// stop the thread and close the handle
HRESULT
CAsyncIo::CloseThread(void)
{
    // signal the thread-exit object
    m_evStop.Set();

    if (m_hThread) {

        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hThread);
        m_hThread = NULL;
    }
    return S_OK;
}

// manage the list of requests. hold m_csLists and ensure
// that the (manual reset) event hevList is set when things on
// the list but reset when the list is empty.
// returns null if list empty
CAsyncRequest*
CAsyncIo::GetWorkItem()
{
    CAutoLock lck(&m_csLists);

    CAsyncRequest * preq  = m_listWork.RemoveHead();

    // force event set correctly
    if (m_listWork.GetCount() == 0) {
        m_evWork.Reset();
    }
    return preq;
}

// get an item from the done list
CAsyncRequest*
CAsyncIo::GetDoneItem()
{
    CAutoLock lock(&m_csLists);

    CAsyncRequest * preq  = m_listDone.RemoveHead();

    // force event set correctly if list now empty
    // or we're in the final stages of flushing
    // Note that during flushing the way it's supposed to work is that
    // everything is shoved on the Done list then the application is
    // supposed to suck until it gets nothing more
    //
    // Thus we should not set m_evDone unconditionally until everything
    // has moved to the done list which means we must wait until
    // cItemsOut is 0 (which is guaranteed by m_bWaiting being TRUE).

    if (m_listDone.GetCount() == 0 &&
        (!m_bFlushing || m_bWaiting)) {
        m_evDone.Reset();
    }

    return preq;
}

// put an item on the work list - fail if bFlushing
HRESULT
CAsyncIo::PutWorkItem(CAsyncRequest* pRequest)
{
    CAutoLock lock(&m_csLists);
    HRESULT hr;

    if (m_bFlushing) {
        hr = VFW_E_WRONG_STATE;
    }
    else if (m_listWork.AddTail(pRequest)) {

        // event should now be in a set state - force this
        m_evWork.Set();

        // start the thread now if not already started
        hr = StartThread();

    } else {
        hr = E_OUTOFMEMORY;
    }
    return(hr);
}

// put an item on the done list - ok to do this when
// flushing
HRESULT
CAsyncIo::PutDoneItem(CAsyncRequest* pRequest)
{
    ASSERT(CritCheckIn(&m_csLists));

    if (m_listDone.AddTail(pRequest)) {

        // event should now be in a set state - force this
        m_evDone.Set();
        return S_OK;
    } else {
        return E_OUTOFMEMORY;
    }
}

// called on thread to process any active requests
void
CAsyncIo::ProcessRequests(void)
{
    // lock to get the item and increment the outstanding count
    CAsyncRequest * preq = NULL;
    for (;;) {
        {
            CAutoLock lock(&m_csLists);

            preq = GetWorkItem();
            if (preq == NULL) {
                // done
                return;
            }

            // one more item not on the done or work list
            m_cItemsOut++;

            // release critsec
        }

        preq->Complete();

        // regain critsec to replace on done list
        {
            CAutoLock l(&m_csLists);

            PutDoneItem(preq);

            if (--m_cItemsOut == 0) {
                if (m_bWaiting) {
                    m_evAllDone.Set();
                }
            }
        }
    }
}

// the thread proc - assumes that DWORD thread param is the
// this pointer
DWORD
CAsyncIo::ThreadProc(void)
{
    HANDLE ahev[] = {m_evStop, m_evWork};

    for (;;) {
DWORD dw = WaitForMultipleObjects(
    2,
    ahev,
    FALSE,
    INFINITE);
if (dw == WAIT_OBJECT_0+1) {

    // requests need processing
    ProcessRequests();
} else {
    // any error or stop event - we should exit
    return 0;
}
    }
}

// perform a synchronous read request on this thread.
// may not be aligned - so we will have to buffer.
HRESULT
CAsyncIo::SyncRead(
            LONGLONG llPos,
            LONG lLength,
            BYTE* pBuffer)
{
    if (IsAligned(llPos) &&
IsAligned(lLength) &&
IsAligned((LONG) pBuffer)) {
            LONG cbUnused;
    return SyncReadAligned(llPos, lLength, pBuffer, &cbUnused);
    }

    // not aligned with requirements - use buffered file handle.
    //!!! might want to fix this to buffer the data ourselves?

    CAsyncRequest request;

    HRESULT hr = request.Request(
                    this,
                    m_pStream,
                    llPos,
                    lLength,
                    FALSE,
                    pBuffer,
                    NULL,
                    0);

    if (FAILED(hr)) {
        return hr;
    }

    return request.Complete();
}

//  Return the alignment
HRESULT
CAsyncIo::Alignment(LONG *pl)
{
    *pl = Alignment();
    return S_OK;
}


ASYNCRDR.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// Implementation of Io source filter methods and output pin methods for
// CAsyncReader and CAsyncOutputPin
//

#include <streams.h>
#include "asyncio.h"
#include "asyncrdr.h"

// --- CAsyncOutputPin implementation ---

CAsyncOutputPin::CAsyncOutputPin(
    HRESULT * phr,
    CAsyncReader *pReader,
    CAsyncIo *pIo,
    CCritSec * pLock)
  : CBasePin(
NAME("Async output pin"),
pReader,
pLock,
phr,
L"Output",
PINDIR_OUTPUT),
    m_pReader(pReader),
    m_pIo(pIo)
{

}

CAsyncOutputPin::~CAsyncOutputPin()
{
}

STDMETHODIMP
CAsyncOutputPin::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
    CheckPointer(ppv,E_POINTER);
    if (riid == IID_IAsyncReader) {
        m_bQueriedForAsyncReader = TRUE;
return GetInterface((IAsyncReader*) this, ppv);
    } else {
return CBasePin::NonDelegatingQueryInterface(riid, ppv);
    }
}

HRESULT
CAsyncOutputPin::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    if (iPosition < 0) {
return E_INVALIDARG;
    }
    if (iPosition > 0) {
return VFW_S_NO_MORE_ITEMS;
    }
    *pMediaType = *m_pReader->LoadType();
    return S_OK;
}

HRESULT
CAsyncOutputPin::CheckMediaType(const CMediaType* pType)
{
    CAutoLock lck(m_pLock);

    /*  We treat MEDIASUBTYPE_NULL subtype as a wild card */
    if ((m_pReader->LoadType()->majortype == pType->majortype) &&
(m_pReader->LoadType()->subtype == MEDIASUBTYPE_NULL ||
         m_pReader->LoadType()->subtype == pType->subtype)) {
    return S_OK;
    }
    return S_FALSE;
}

HRESULT
CAsyncOutputPin::InitAllocator(IMemAllocator **ppAlloc)
{
    HRESULT hr = NOERROR;
    *ppAlloc = NULL;
    CMemAllocator *pMemObject = NULL;

    /* Create a default memory allocator */

    pMemObject = new CMemAllocator(NAME("Base memory allocator"),NULL, &hr);
    if (pMemObject == NULL) {
return E_OUTOFMEMORY;
    }

    if (FAILED(hr)) {
delete pMemObject;
return hr;
    }

    /* Get a reference counted IID_IMemAllocator interface */

    hr = pMemObject->QueryInterface(IID_IMemAllocator,(void **)ppAlloc);
    if (FAILED(hr)) {
delete pMemObject;
return E_NOINTERFACE;
    }
    ASSERT(*ppAlloc != NULL);
    return NOERROR;
}

// we need to return an addrefed allocator, even if it is the preferred
// one, since he doesn't know whether it is the preferred one or not.
STDMETHODIMP
CAsyncOutputPin::RequestAllocator(
    IMemAllocator* pPreferred,
    ALLOCATOR_PROPERTIES* pProps,
    IMemAllocator ** ppActual)
{
    // we care about alignment but nothing else
    if (!pProps->cbAlign || !m_pIo->IsAligned(pProps->cbAlign)) {
       m_pIo->Alignment(&pProps->cbAlign);
    }
    ALLOCATOR_PROPERTIES Actual;
    HRESULT hr;
    if (pPreferred) {
hr = pPreferred->SetProperties(pProps, &Actual);
if (SUCCEEDED(hr) && m_pIo->IsAligned(Actual.cbAlign)) {
            pPreferred->AddRef();
    *ppActual = pPreferred;
            return S_OK;
        }
    }

    // create our own allocator
    IMemAllocator* pAlloc;
    hr = InitAllocator(&pAlloc);
    if (FAILED(hr)) {
        return hr;
    }

    //...and see if we can make it suitable
    hr = pAlloc->SetProperties(pProps, &Actual);
    if (SUCCEEDED(hr) && m_pIo->IsAligned(Actual.cbAlign)) {
        // we need to release our refcount on pAlloc, and addref
        // it to pass a refcount to the caller - this is a net nothing.
        *ppActual = pAlloc;
        return S_OK;
    }

    // failed to find a suitable allocator
    pAlloc->Release();

    // if we failed because of the IsAligned test, the error code will
    // not be failure
    if (SUCCEEDED(hr)) {
        hr = VFW_E_BADALIGN;
    }
    return hr;
}

// queue an aligned read request. call WaitForNext to get
// completion.
STDMETHODIMP
CAsyncOutputPin::Request(
    IMediaSample* pSample,
    DWORD dwUser)        // user context
{
    REFERENCE_TIME tStart, tStop;
    HRESULT hr = pSample->GetTime(&tStart, &tStop);
    if (FAILED(hr)) {
return hr;
    }

    LONGLONG llPos = tStart / UNITS;
    LONG lLength = (LONG) ((tStop - tStart) / UNITS);

    LONGLONG llTotal;
    LONGLONG llAvailable;
    hr = m_pIo->Length(&llTotal, &llAvailable);
    if (llPos + lLength > llTotal) {

        // the end needs to be aligned, but may have been aligned
        // on a coarser alignment.
        LONG lAlign;
        m_pIo->Alignment(&lAlign);
        llTotal = (llTotal + lAlign -1) & ~(lAlign-1);

        if (llPos + lLength > llTotal) {
            lLength = (LONG) (llTotal - llPos);

            // must be reducing this!
            ASSERT((llTotal * UNITS) <= tStop);
            tStop = llTotal * UNITS;
            pSample->SetTime(&tStart, &tStop);
        }
    }

    BYTE* pBuffer;
    hr = pSample->GetPointer(&pBuffer);
    if (FAILED(hr)) {
return hr;
    }

    return m_pIo->Request(
llPos,
lLength,
                        TRUE,
pBuffer,
(LPVOID)pSample,
dwUser);
}

// sync-aligned request. just like a request/waitfornext pair.
STDMETHODIMP
CAsyncOutputPin::SyncReadAligned(
                  IMediaSample* pSample)
{
    REFERENCE_TIME tStart, tStop;
    HRESULT hr = pSample->GetTime(&tStart, &tStop);
    if (FAILED(hr)) {
return hr;
    }

    LONGLONG llPos = tStart / UNITS;
    LONG lLength = (LONG) ((tStop - tStart) / UNITS);

    LONGLONG llTotal;
    LONGLONG llAvailable;
    hr = m_pIo->Length(&llTotal, &llAvailable);
    if (llPos + lLength > llTotal) {

        // the end needs to be aligned, but may have been aligned
        // on a coarser alignment.
        LONG lAlign;
        m_pIo->Alignment(&lAlign);
        llTotal = (llTotal + lAlign -1) & ~(lAlign-1);

        if (llPos + lLength > llTotal) {
            lLength = (LONG) (llTotal - llPos);

            // must be reducing this!
            ASSERT((llTotal * UNITS) <= tStop);
            tStop = llTotal * UNITS;
            pSample->SetTime(&tStart, &tStop);
        }
    }

    BYTE* pBuffer;
    hr = pSample->GetPointer(&pBuffer);
    if (FAILED(hr)) {
return hr;
    }

    LONG cbActual;
    hr = m_pIo->SyncReadAligned(
llPos,
lLength,
pBuffer,
                        &cbActual
                        );

    pSample->SetActualDataLength(cbActual);

    return hr;
}

//
// collect the next ready sample
STDMETHODIMP
CAsyncOutputPin::WaitForNext(
    DWORD dwTimeout,
    IMediaSample** ppSample,  // completed sample
    DWORD * pdwUser)// user context
{
    LONG cbActual;

    IMediaSample* pSample;
    HRESULT hr =  m_pIo->WaitForNext(
    dwTimeout,
    (LPVOID*) &pSample,
    pdwUser,
                            &cbActual
                            );
    if (SUCCEEDED(hr)) {
        pSample->SetActualDataLength(cbActual);
    }
    *ppSample = pSample;

    return hr;
}

//
// synchronous read that need not be aligned.
STDMETHODIMP
CAsyncOutputPin::SyncRead(
    LONGLONG llPosition,// absolute Io position
    LONG lLength,// nr bytes required
    BYTE* pBuffer)// write data here
{
    return m_pIo->SyncRead(llPosition, lLength, pBuffer);
}

// return the length of the file, and the length currently
// available locally. We only support locally accessible files,
// so they are always the same
STDMETHODIMP
CAsyncOutputPin::Length(
    LONGLONG* pTotal,
    LONGLONG* pAvailable)
{
    HRESULT hr = m_pIo->Length(pTotal, pAvailable);
    return hr;
}

STDMETHODIMP
CAsyncOutputPin::BeginFlush(void)
{
    return m_pIo->BeginFlush();
}

STDMETHODIMP
CAsyncOutputPin::EndFlush(void)
{
    return m_pIo->EndFlush();
}

// --- CAsyncReader implementation ---

#pragma warning(disable:4355)

CAsyncReader::CAsyncReader(
    TCHAR *pName,
    LPUNKNOWN pUnk,
    CAsyncStream *pStream,
    HRESULT *phr)
  : CBaseFilter(
      pName,
pUnk,
&m_csFilter,
CLSID_AsyncReader,
        NULL
    ),
    m_OutputPin(
phr,
this,
&m_Io,
&m_csFilter),
    m_Io(pStream)
{

}

CAsyncReader::~CAsyncReader()
{
}

int
CAsyncReader::GetPinCount()
{
    return 1;
}

CBasePin *
CAsyncReader::GetPin(int n)
{
    if ((GetPinCount() > 0) &&
(n == 0)) {
return &m_OutputPin;
    } else {
return NULL;
    }
}


ASYNCIO.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __ASYNCIO_H__
#define __ASYNCIO_H__
//
// definition of CAsyncFile object that performs file access. It provides
// asynchronous, unbuffered, aligned reads from a file, using a worker thread
// on win95 and potentially overlapped i/o if available.

// !!! Need to use real overlapped i/o if available
// currently only uses worker thread, not overlapped i/o

class CAsyncIo;
class CAsyncStream;

//
//  Model the stream we read from based on a file-like interface
//
class CAsyncStream
{
public:
    virtual ~CAsyncStream() {};
    virtual HRESULT SetPointer(LONGLONG llPos) = 0;
    virtual HRESULT Read(PBYTE pbBuffer,
                         DWORD dwBytesToRead,
                         BOOL bAlign,
                         LPDWORD pdwBytesRead) = 0;
    virtual LONGLONG Size(LONGLONG *pSizeAvailable = NULL) = 0;
    virtual DWORD Alignment() = 0;
    virtual void Lock() = 0;
    virtual void Unlock() = 0;
};

// represents a single request and performs the i/o. Can be called on either
// worker thread or app thread, but must hold pcsFile across file accesses.
// (ie across SetFilePointer/ReadFile pairs)
class CAsyncRequest
{
    CAsyncIo *m_pIo;
    CAsyncStream *m_pStream;
    LONGLONG      m_llPos;
    BOOL        m_bAligned;
    LONG m_lLength;
    BYTE* m_pBuffer;
    LPVOID m_pContext;
    DWORDm_dwUser;
    HRESULT     m_hr;

public:
    // init the params for this request. Issue the i/o
    // if overlapped i/o is possible.
    HRESULT Request(
    CAsyncIo *pIo,
        CAsyncStream *pStream,
    LONGLONG llPos,
LONG lLength,
        BOOL bAligned,
BYTE* pBuffer,
LPVOID pContext,// filter's context
DWORD dwUser);// downstream filter's context

    // issue the i/o if not overlapped, and block until i/o complete.
    // returns error code of file i/o
    HRESULT Complete();

    // cancels the i/o. blocks until i/o is no longer pending
    HRESULT Cancel()
    {
return S_OK;
    };

    // accessor functions
    LPVOID GetContext()
    {
    return m_pContext;
    };

    DWORD GetUser()
    {
return m_dwUser;
    };

    HRESULT GetHResult() {
        return m_hr;
    };

    // we set m_lLength to the actual length
    LONG GetActualLength() {
        return m_lLength;
    };

    LONGLONG GetStart() {
        return m_llPos;
    };
};

typedef CGenericList<CAsyncRequest> CRequestList;

// this class needs a worker thread, but the ones defined in classes\base
// are not suitable (they assume you have one message sent or posted per
// request, whereas here for efficiency we want just to set an event when
// there is work on the queue).
//
// we create CAsyncRequest objects and queue them on m_listWork. The worker
// thread pulls them off, completes them and puts them on m_listDone.
// The events m_evWork and m_evDone are set when the corresponding lists are
// not empty.
//
// Synchronous requests are done on the caller thread. These should be
// synchronised by the caller, but to make sure we hold m_csFile across
// the SetFilePointer/ReadFile code.
//
// Flush by calling BeginFlush. This rejects all further requests (by
// setting m_bFlushing within m_csLists), cancels all requests and moves them
// to the done list, and sets m_evDone to ensure that no WaitForNext operations
// will block. Call EndFlush to cancel this state.
//
// we support unaligned calls to SyncRead. This is done by opening the file
// twice if we are using unbuffered i/o (m_dwAlign > 1).
// !!!fix this to buffer on top of existing file handle?
class CAsyncIo
{

    CCritSec m_csReader;
    CAsyncStream *m_pStream;

    CCritSec m_csLists;      // locks access to the list and events
    BOOL m_bFlushing;        // true if between BeginFlush/EndFlush
    CRequestList m_listWork;
    CRequestList m_listDone;
    CAMEvent m_evWork;         // set when list is not empty
    CAMEvent m_evDone;

    // for correct flush behaviour: all protected by m_csLists
    LONG    m_cItemsOut;    // nr of items not on listDone or listWork
    BOOL    m_bWaiting;     // TRUE if someone waiting for m_evAllDone
    CAMEvent m_evAllDone;   // signal when m_cItemsOut goes to 0 if m_cWaiting

    CAMEvent m_evStop;         // set when thread should exit
    HANDLE m_hThread;

    LONGLONG Size() {
        ASSERT(m_pStream != NULL);
        return m_pStream->Size();
    };

    // start the thread
    HRESULT StartThread(void);

    // stop the thread and close the handle
    HRESULT CloseThread(void);

    // manage the list of requests. hold m_csLists and ensure
    // that the (manual reset) event hevList is set when things on
    // the list but reset when the list is empty.
    // returns null if list empty
    CAsyncRequest* GetWorkItem();

    // get an item from the done list
    CAsyncRequest* GetDoneItem();

    // put an item on the work list
    HRESULT PutWorkItem(CAsyncRequest* pRequest);

    // put an item on the done list
    HRESULT PutDoneItem(CAsyncRequest* pRequest);

    // called on thread to process any active requests
    void ProcessRequests(void);

    // initial static thread proc calls ThreadProc with DWORD
    // param as this
    static DWORD InitialThreadProc(LPVOID pv) {
CAsyncIo * pThis = (CAsyncIo*) pv;
return pThis->ThreadProc();
    };

    DWORD ThreadProc(void);

public:

    CAsyncIo(CAsyncStream *pStream);
    ~CAsyncIo();

    // open the file
    HRESULT Open(LPCTSTR pName);

    // ready for async activity - call this before
    // calling Request
    HRESULT AsyncActive(void);

    // call this when no more async activity will happen before
    // the next AsyncActive call
    HRESULT AsyncInactive(void);

    // queue a requested read. must be aligned.
    HRESULT Request(
    LONGLONG llPos,
LONG lLength,
                BOOL bAligned,
BYTE* pBuffer,
LPVOID pContext,
DWORD dwUser);

    // wait for the next read to complete
    HRESULT WaitForNext(
    DWORD dwTimeout,
LPVOID *ppContext,
DWORD * pdwUser,
                LONG * pcbActual
                );

    // perform a read of an already aligned buffer
    HRESULT SyncReadAligned(
    LONGLONG llPos,
LONG lLength,
BYTE* pBuffer,
                LONG* pcbActual
                );

    // perform a synchronous read. will be buffered
    // if not aligned.
    HRESULT SyncRead(
                LONGLONG llPos,
                LONG lLength,
                BYTE* pBuffer);

    // return length
    HRESULT Length(LONGLONG *pllTotal, LONGLONG* pllAvailable);

    // all Reader positions, read lengths and memory locations must
    // be aligned to this.
    HRESULT Alignment(LONG* pl);

    HRESULT BeginFlush();
    HRESULT EndFlush();

    LONG Alignment()
    {
        return m_pStream->Alignment();
    };

    BOOL IsAligned(LONG l) {
if ((l & (Alignment() -1)) == 0) {
    return TRUE;
} else {
    return FALSE;
}
    };

    BOOL IsAligned(LONGLONG ll) {
return IsAligned( (LONG) (ll & 0xffffffff));
    };
};

#endif // __ASYNCIO_H__


ASYNCRDR.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __ASYNCRDR_H__
#define __ASYNCRDR_H__

//
// AsyncRdr
//
// Defines an IO source filter.
//
// This filter (CAsyncReader) supports IBaseFilter and IFileSourceFilter interfaces from the
// filter object itself. It has a single output pin (CAsyncOutputPin)
// which supports IPin and IAsyncReader.
//
// This filter is essentially a wrapper for the CAsyncFile class that does
// all the work.
//

// the filter class (defined below)
class CAsyncReader;

// the output pin class
class CAsyncOutputPin
  : public IAsyncReader,
    public CBasePin
{
protected:
    CAsyncReader* m_pReader;
    CAsyncIo * m_pIo;

    //  This is set every time we're asked to return an IAsyncReader
    //  interface
    //  This allows us to know if the downstream pin can use
    //  this transport, otherwise we can hook up to thinks like the
    //  dump filter and nothing happens
    BOOL         m_bQueriedForAsyncReader;

    HRESULT InitAllocator(IMemAllocator **ppAlloc);

public:
    // constructor and destructor
    CAsyncOutputPin(
        HRESULT * phr,
CAsyncReader *pReader,
CAsyncIo *pIo,
        CCritSec * pLock);

    ~CAsyncOutputPin();

    // --- CUnknown ---

    // need to expose IAsyncReader
    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void**);

    // --- CBasePin methods ---

    // return the types we prefer - this will return the known
    // file type
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);

    // can we support this type?
    HRESULT CheckMediaType(const CMediaType* pType);

    // Clear the flag so we see if IAsyncReader is queried for
    HRESULT CheckConnect(IPin *pPin)
    {
        m_bQueriedForAsyncReader = FALSE;
        return CBasePin::CheckConnect(pPin);
    }

    // See if it was asked for
    HRESULT CompleteConnect(IPin *pReceivePin)
    {
        if (m_bQueriedForAsyncReader) {
            return CBasePin::CompleteConnect(pReceivePin);
        } else {
#ifdef VFW_E_NO_TRANSPORT
            return VFW_E_NO_TRANSPORT;
#else
            return E_FAIL;
#endif
        }
    }

    // --- IAsyncReader methods ---
    // pass in your preferred allocator and your preferred properties.
    // method returns the actual allocator to be used. Call GetProperties
    // on returned allocator to learn alignment and prefix etc chosen.
    // this allocator will be not be committed and decommitted by
    // the async reader, only by the consumer.
    STDMETHODIMP RequestAllocator(
                      IMemAllocator* pPreferred,
                      ALLOCATOR_PROPERTIES* pProps,
                      IMemAllocator ** ppActual);

    // queue a request for data.
    // media sample start and stop times contain the requested absolute
    // byte position (start inclusive, stop exclusive).
    // may fail if sample not obtained from agreed allocator.
    // may fail if start/stop position does not match agreed alignment.
    // samples allocated from source pin's allocator may fail
    // GetPointer until after returning from WaitForNext.
    STDMETHODIMP Request(
                     IMediaSample* pSample,
                     DWORD dwUser);        // user context

    // block until the next sample is completed or the timeout occurs.
    // timeout (millisecs) may be 0 or INFINITE. Samples may not
    // be delivered in order. If there is a read error of any sort, a
    // notification will already have been sent by the source filter,
    // and STDMETHODIMP will be an error.
    STDMETHODIMP WaitForNext(
                      DWORD dwTimeout,
                      IMediaSample** ppSample,  // completed sample
                      DWORD * pdwUser);// user context

    // sync read of data. Sample passed in must have been acquired from
    // the agreed allocator. Start and stop position must be aligned.
    // equivalent to a Request/WaitForNext pair, but may avoid the
    // need for a thread on the source filter.
    STDMETHODIMP SyncReadAligned(
                      IMediaSample* pSample);

    // sync read. works in stopped state as well as run state.
    // need not be aligned. Will fail if read is beyond actual total
    // length.
    STDMETHODIMP SyncRead(
                      LONGLONG llPosition,// absolute file position
                      LONG lLength,// nr bytes required
                      BYTE* pBuffer);// write data here

    // return total length of stream, and currently available length.
    // reads for beyond the available length but within the total length will
    // normally succeed but may block for a long period.
    STDMETHODIMP Length(
                      LONGLONG* pTotal,
                      LONGLONG* pAvailable);

    // cause all outstanding reads to return, possibly with a failure code
    // (VFW_E_TIMEOUT) indicating they were cancelled.
    // these are defined on IAsyncReader and IPin
    STDMETHODIMP BeginFlush(void);
    STDMETHODIMP EndFlush(void);

};

//
// The filter object itself. Supports IBaseFilter through
// CBaseFilter and also IFileSourceFilter directly in this object

class CAsyncReader : public CBaseFilter
{

protected:
    // filter-wide lock
    CCritSec m_csFilter;

    // all i/o done here
    CAsyncIo m_Io;

    // our output pin
    CAsyncOutputPin m_OutputPin;

    // Type we think our data is
    CMediaType            m_mt;

public:

    // construction / destruction

    CAsyncReader(
        TCHAR *pName,
        LPUNKNOWN pUnk,
        CAsyncStream *pStream,
        HRESULT *phr);
    ~CAsyncReader();

    // --- CBaseFilter methods ---
    int GetPinCount();
    CBasePin *GetPin(int n);

    // --- Access our media type
    const CMediaType *LoadType() const
    {
        return &m_mt;
    }
};

#endif //__ASYNCRDR_H__


MEMFILE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
#include <streams.h>
#include <stdio.h>
#include <asyncio.h>
#include <asyncrdr.h>
#include "memfile.h"

/*  Function prototypes */
HRESULT SelectAndRender(CMemReader *pReader, IFilterGraph **pFG);
HRESULT PlayFileWait(IFilterGraph *pFG);

/*  Read a file into memory, play it (or part of it), then exit */

int _cdecl main(int argc, char *argv[])
{
    if (argc < 2 || argc > 3) {
        printf("Usage : memfile foo.bar <Kbytes per sec>\n");
        return 0;
    }
    DWORD dwKBPerSec = argc == 2 ? INFINITE : atoi(argv[2]);
    CMediaType mt;
    mt.majortype = MEDIATYPE_Stream;
    LPTSTR lpType;
    {
        /*  Find the extension */
        int len = lstrlen(argv[1]);
        if (len >= 4 && argv[1][len - 4] == TEXT('.')) {
            lpType = argv[1] + len - 3;
        } else {
            printf("Invalid file extension\n");
            return 1;
        }
    }
    if (lstrcmpi(lpType, TEXT("mpg")) == 0) {
        mt.subtype = MEDIASUBTYPE_MPEG1System;
    } else
    if (lstrcmpi(lpType, TEXT("mpa")) == 0) {
        mt.subtype = MEDIASUBTYPE_MPEG1Audio;
    } else
    if (lstrcmpi(lpType, TEXT("mpv")) == 0) {
        mt.subtype = MEDIASUBTYPE_MPEG1Video;
    } else
    if (lstrcmpi(lpType, TEXT("dat")) == 0) {
        mt.subtype = MEDIASUBTYPE_MPEG1VideoCD;
    } else
    if (lstrcmpi(lpType, TEXT("avi")) == 0) {
        mt.subtype = MEDIASUBTYPE_Avi;
    } else
    if (lstrcmpi(lpType, TEXT("mov")) == 0) {
        mt.subtype = MEDIASUBTYPE_QTMovie;
    } else
    if (lstrcmpi(lpType, TEXT("wav")) == 0) {
        mt.subtype = MEDIASUBTYPE_WAVE;
    } else
    {
        printf("Unknown file type %s\n", lpType);
        return 1;
    }

    /*  Read the file */
    HANDLE hFile = CreateFile(argv[1],
                              GENERIC_READ,
                              FILE_SHARE_READ,
                              NULL,
                              OPEN_EXISTING,
                              0,
                              NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Could not open %s\n", argv[1]);
        return 1;
    }

    ULARGE_INTEGER uliSize;
    uliSize.LowPart = GetFileSize(hFile, &uliSize.HighPart);

    PBYTE pbMem = new BYTE[uliSize.LowPart];
    if (pbMem == NULL) {
        printf("Could not allocate %d bytes\n", uliSize.LowPart);
        return 1;
    }

    DWORD dwBytesRead;
    if (!ReadFile(hFile,
                  (LPVOID)pbMem,
                  uliSize.LowPart,
                  &dwBytesRead,
                  NULL) ||
        dwBytesRead != uliSize.LowPart) {
        printf("Could not read file\n");
        CloseHandle(hFile);
        return 1;
    }
    CloseHandle(hFile);
    HRESULT hr = S_OK;

    CoInitialize(NULL);

    CMemStream Stream(pbMem, (LONGLONG)uliSize.QuadPart, dwKBPerSec);
    CMemReader *rdr = new CMemReader(&Stream, &mt, &hr);
    if (FAILED(hr) || rdr == NULL) {
        delete rdr;
        printf("Could not create filter HRESULT 0x%8.8X\n", hr);
        CoUninitialize();
        return 1;
    }

    //  Make sure we don't accidentally go away!
    rdr->AddRef();
    IFilterGraph *pFG = NULL;
    hr = SelectAndRender(rdr, &pFG);

    if (FAILED(hr)) {
        printf("Failed to create graph and render file HRESULT 0x%8.8X",
               hr);
    } else {
        //  Play the file
        HRESULT hr = PlayFileWait(pFG);
        if (FAILED(hr)) {
            printf("Failed to play graph HRESULT 0x%8.8X",
                   hr);
        }
    }
    rdr->Release();
    if (pFG) {
        ULONG ulRelease = pFG->Release();
        if (ulRelease != 0) {
            printf("Filter graph count not 0!  was %d", ulRelease);
        }
    }
    CoUninitialize();
    return 0;
}

//  Select a filter into a graph and render its output pin, returning
//  the graph

HRESULT SelectAndRender(CMemReader *pReader, IFilterGraph **ppFG)
{
    /*  Create filter graph */
    HRESULT hr = CoCreateInstance(CLSID_FilterGraph,
                                  NULL,
                                  CLSCTX_INPROC,
                                  IID_IFilterGraph,
                                  (void**) ppFG);

    if (FAILED(hr)) {
        return hr;
    }

    /*  Get the various interfaces we need */

    /*  Add our filter */
    hr = (*ppFG)->AddFilter(pReader, NULL);

    if (FAILED(hr)) {
        return hr;
    }

    /*  Render our output pin */
    IGraphBuilder *pBuilder;
    hr = (*ppFG)->QueryInterface(IID_IGraphBuilder, (void **)&pBuilder);
    if (FAILED(hr)) {
        return hr;
    }
    hr = pBuilder->Render(pReader->GetPin(0));
    pBuilder->Release();
    return hr;
}

HRESULT PlayFileWait(IFilterGraph *pFG)
{
    IMediaControl *pMC;
    IMediaEvent *pME;
    HRESULT hr = pFG->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (FAILED(hr)) {
        return hr;
    }

    hr = pFG->QueryInterface(IID_IMediaEvent, (void **)&pME);
    if (FAILED(hr)) {
        pMC->Release();
        return hr;
    }

    OAEVENT oEvent;
    hr = pME->GetEventHandle(&oEvent);
    if (SUCCEEDED(hr)) {
        hr = pMC->Run();
    }
    if (SUCCEEDED(hr)) {
        LONG levCode;
        hr = pME->WaitForCompletion(INFINITE, &levCode);
    }
    pMC->Release();
    pME->Release();
    return hr;
}


MEMFILE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//  Define an internal filter that wraps the base CBaseReader stuff
//

class CMemStream : public CAsyncStream
{
public:
    CMemStream(LPBYTE pbData, LONGLONG llLength, DWORD dwKBPerSec = INFINITE) :
        m_pbData(pbData),
        m_llLength(llLength),
        m_llPosition(0),
        m_dwKBPerSec(dwKBPerSec)
    {
        m_dwTimeStart = timeGetTime();
    }
    HRESULT SetPointer(LONGLONG llPos)
    {
        if (llPos < 0 || llPos > m_llLength) {
            return S_FALSE;
        } else {
            m_llPosition = llPos;
            return S_OK;
        }
    }
    HRESULT Read(PBYTE pbBuffer,
                 DWORD dwBytesToRead,
                 BOOL bAlign,
                 LPDWORD pdwBytesRead)
    {
        CAutoLock lck(&m_csLock);
        DWORD dwReadLength;
        /*  Wait until the bytes are here! */
        DWORD dwTime = timeGetTime();
        if (m_llPosition + dwBytesToRead > m_llLength) {
            dwReadLength = (DWORD)(m_llLength - m_llPosition);
        } else {
            dwReadLength = dwBytesToRead;
        }
        DWORD dwTimeToArrive =
            ((DWORD)m_llPosition + dwReadLength) / m_dwKBPerSec;
        if (dwTime - m_dwTimeStart < dwTimeToArrive) {
            Sleep(dwTimeToArrive - dwTime + m_dwTimeStart);
        }
        CopyMemory((PVOID)pbBuffer, (PVOID)(m_pbData + m_llPosition),
                   dwReadLength);
        m_llPosition += dwReadLength;
        *pdwBytesRead = dwReadLength;
        return S_OK;
    }
    LONGLONG Size(LONGLONG *pSizeAvailable)
    {
        LONGLONG llCurrentAvailable =
            Int32x32To64((timeGetTime() - m_dwTimeStart),m_dwKBPerSec);
        *pSizeAvailable = min(m_llLength, llCurrentAvailable);
        return m_llLength;
    }
    DWORD Alignment()
    {
        return 1;
    }
    void Lock()
    {
        m_csLock.Lock();
    }
    void Unlock()
    {
        m_csLock.Unlock();
    }

private:
    CCritSec       m_csLock;
    const PBYTE    m_pbData;
    const LONGLONG m_llLength;
    LONGLONG       m_llPosition;
    DWORD          m_dwKBPerSec;
    DWORD          m_dwTimeStart;
};

class CMemReader : public CAsyncReader
{
public:

    //  We're not going to be CoCreate'd so we don't need registration
    //  stuff etc
    STDMETHODIMP Register()
    {
        return S_OK;
    }
    STDMETHODIMP Unregister()
    {
        return S_OK;
    }
    CMemReader(CMemStream *pStream, CMediaType *pmt, HRESULT *phr) :
        CAsyncReader(NAME("Mem Reader"), NULL, pStream, phr)
    {
        m_mt = *pmt;
    }
};


BALL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include "ball.h"

//
//
// What this samples illustrates
//
// A simple source filter that produces decompressed images showing a ball
// bouncing around. Each movement of the ball is done by generating a new
// image. We use the CSource and CSourceStream base classes to manage a
// source filter - we are a live source and so do not support any seeking.
//
//
// Summary
//
// This is a sample source filter - we produce a never ending stream of images
// that show a coloured ball bouncing around the window. Depending on the bit
// depth of the current display device we colour the ball differently. As we
// are effectively a live video stream we do not offer any seeking interfaces.
// We can supply 32,24,16 (555 and 565) as well as eight bit palettised types
//
//
// Implementation
//
// We use the CSource and CSourceStream base classes from the SDK which look
// after some of the grunge associated with source filters, in particular the
// starting the stopping of workers threads as we're activated and stopped.
// The worker thread sits in a loop asking for buffers and then calling the
// PURE virtual FillBuffer method when it has a buffer for us to fill up.
//
// For an example we also have a simple quality management implementation in
// this filter, quality management of everyone except renderers (who are the
// ones normally to initiate it) is controlled through IQualityControl. This
// is called on each frame to say how we are getting on, because this filter
// is pretty straightforward we can control the spacing of samples we send
// downstream so that we always run flat out with whatever CPU is available
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Click on the Graph
// menu and select "Insert Filters". From the dialog box double click on
// "Bouncing ball" and then dismiss the dialog. Go to the right hand pin of
// the filter box and right click, select Render. A video renderer will be
// inserted and connected up (on some displays there may be a colour space
// convertor put between them to get the pictures into a suitable format).
// Then click run on GRAPHEDT and see the ball bounce around the window...
//
//
// Files
//
// ball.cpp         Looks after drawing a moving bouncing ball
// ball.def         What APIs the DLL imports and exports
// ball.h           Class definition for the ball drawing object
// ball.rc          Version and title information resources
// ball.reg         What goes in the registry to make us work
// balluids.h       The CLSIDs for the bouncing ball filter
// fball.cpp        The real filter class implementation
// fball.h          Class definition for the main filter object
// makefile         How to build it...
// resource.h       A couple of identifiers for our resources
//
//
// Base classes used
//
// CSource          Base class for a generic source filter
// CSourceStream    A base class for a source filters stream
//
//

//
// Constructor
//
// The default arguments provide a reasonable image and ball size
//
CBall::CBall(int iImageWidth, int iImageHeight, int iBallSize) :
    m_iImageWidth(iImageWidth),
    m_iImageHeight(iImageHeight),
    m_iBallSize(iBallSize),
    m_iAvailableWidth(iImageWidth - iBallSize),
    m_iAvailableHeight(iImageHeight - iBallSize),
    m_x(0),
    m_y(0),
    m_xDir(RIGHT),
    m_yDir(UP)
{
    // Check we have some (arbitrary) space to bounce in.
    ASSERT(iImageWidth > 2*iBallSize);
    ASSERT(iImageHeight > 2*iBallSize);

    // Random position for showing off a video mixer
    m_iRandX = rand();
    m_iRandY = rand();

} // (Constructor)

//
// PlotBall
//
// Assumes the image buffer is arranged row 1,row 2,...,row n
//      in memory and that the data is contiguous.
//
void CBall::PlotBall(BYTE pFrame[], BYTE BallPixel[], int iPixelSize)
{
    ASSERT(m_x >= 0);
    ASSERT(m_x <= m_iAvailableWidth);
    ASSERT(m_y >= 0);
    ASSERT(m_y <= m_iAvailableHeight);

    // The current byte of interest in the frame
    BYTE *pBack =   pFrame;

    // Plot the ball into the correct location
    BYTE *pBall = pFrame + ( m_y * m_iImageWidth * iPixelSize) + m_x * iPixelSize;

    for (int row = 0; row < m_iBallSize; row++) {

        for (int col = 0; col < m_iBallSize; col++) {

            // For each byte fill its value from BallPixel[]
            for (int i = 0; i < iPixelSize; i++) {
                if (WithinCircle(col, row)) {
                    *pBall = BallPixel[i];
                }
                pBall++;
    }
}
pBall += m_iAvailableWidth * iPixelSize;
    }

} // PlotBall

//
// BallPosition
//
// Return the 1-dimensional position of the ball at time t millisecs
//      (note that millisecs runs out after about a month!)
//
int CBall::BallPosition(int iPixelTime, // Millisecs per pixel
                        int iLength,    // Distance between the bounce points
                        int time,       // Time in millisecs
                        int iOffset)    // For a bit of randomness
{
    // Calculate the position of an unconstrained ball (no walls)
    // then fold it back and forth to calculate the actual position

    int x = time / iPixelTime;
    x += iOffset;
    x %= 2*iLength;

    // check it is still in bounds
    if (x>iLength) {
        x = 2*iLength - x;
    }
    return x;

} // BallPosition

//
// MoveBall
//
// Set (m_x, m_y) to the new position of the ball.  move diagonally
// with speed m_v in each of x and y directions.
// Guarantees to keep the ball in valid areas of the frame.
// When it hits an edge the ball bounces in the traditional manner!.
// The boundaries are (0..m_iAvailableWidth, 0..m_iAvailableHeight)
//
void CBall::MoveBall(CRefTime rt)
{
    m_x = BallPosition( 10, m_iAvailableWidth, rt.Millisecs(), m_iRandX );
    m_y = BallPosition( 10, m_iAvailableHeight, rt.Millisecs(), m_iRandY );

} // MoveBall

//
// WithinCircle
//
// Return TRUE if (x,y) is within a circle radius S/2, centre (S/2, S/2)
//      where S is m_iBallSize else return FALSE
//
inline BOOL CBall::WithinCircle(int x, int y)
{
    unsigned int r = m_iBallSize / 2;

    if ( (x-r)*(x-r) + (y-r)*(y-r)  < r*r) {
        return TRUE;
    } else {
        return FALSE;
    }

} // WithinCircle


BALL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

class CBall
{
public:

    CBall(int iImageWidth = 320, int iImageHeight = 240, int iBallSize = 10);

    // Plots the square ball in the image buffer, at the current location.
    // Use BallPixel[] as pixel value for the ball.
    // Plots zero in all 'background' image locations.
    // iPixelSize - the number of bytes in a pixel (size of BallPixel[])
    void PlotBall(BYTE pFrame[], BYTE BallPixel[], int iPixelSize);

    // Moves the ball 1 pixel in each of the x and y directions
    void MoveBall(CRefTime rt);

private:

    enum xdir { LEFT = -1, RIGHT = 1 };
    enum ydir { UP = 1, DOWN = -1 };

    // The dimensions we can plot in, allowing for the width of the ball
    int m_iAvailableHeight, m_iAvailableWidth;

    int m_iImageHeight;         // The image height
    int m_iImageWidth;// The image width
    int m_iBallSize;// The diameter of the ball
    int m_iRandX, m_iRandY;     // For a bit of randomness
    xdir m_xDir;        // Direction the ball
    ydir m_yDir;        // Likewise vertically

    // The x position, in pixels, of the ball in the frame
    // (0 < x < m_iAvailableWidth)
    int m_x;

    // the y position, in pixels, of the ball in the frame
    // (0 < y < m_iAvailableHeight)
    int m_y;

    // Return the 1-dimensional position of the ball at time t millisecs
    int BallPosition(int iPixelTime, int iLength, int time, int iOffset);

    /// tests a given pixel to see if it should be plotted
    BOOL WithinCircle(int x, int y);

}; // CBall


BALLUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// { fd501041-8ebe-11ce-8183-00aa00577da1 }
DEFINE_GUID(CLSID_BouncingBall,
0xfd501041, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);


FBALL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <olectl.h>
#include <initguid.h>
#include "balluids.h"
#include "ball.h"
#include "fball.h"

// Setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{
    &MEDIATYPE_Video,       // Major type
    &MEDIASUBTYPE_NULL      // Minor type
};

const AMOVIESETUP_PIN sudOpPin =
{
    L"Output",              // Pin string name
    FALSE,                  // Is it rendered
    TRUE,                   // Is it an output
    FALSE,                  // Can we have none
    FALSE,                  // Can we have many
    &CLSID_NULL,            // Connects to filter
    NULL,                   // Connects to pin
    1,                      // Number of types
    &sudOpPinTypes };       // Pin details

const AMOVIESETUP_FILTER sudBallax =
{
    &CLSID_BouncingBall,    // Filter CLSID
    L"Bouncing Ball",       // String name
    MERIT_DO_NOT_USE,       // Filter merit
    1,                      // Number pins
    &sudOpPin               // Pin details
};

// COM global table of objects in this dll

CFactoryTemplate g_Templates[] = {
  { L"Bouncing Ball"
  , &CLSID_BouncingBall
  , CBouncingBall::CreateInstance
  , NULL
  , &sudBallax }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// DllRegisterServer
//
// Exported entry points for registration and unregistration
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer

//
// CreateInstance
//
// The only allowed way to create Bouncing balls!
//
CUnknown * WINAPI CBouncingBall::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    CUnknown *punk = new CBouncingBall(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return punk;

} // CreateInstance

//
// Constructor
//
// Initialise a CBallStream object so that we have a pin.
//
CBouncingBall::CBouncingBall(LPUNKNOWN lpunk, HRESULT *phr) :
    CSource(NAME("Bouncing ball"),
            lpunk,
            CLSID_BouncingBall)
{
    CAutoLock cAutoLock(&m_cStateLock);

    m_paStreams    = (CSourceStream **) new CBallStream*[1];
    if (m_paStreams == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

    m_paStreams[0] = new CBallStream(phr, this, L"A Bouncing Ball!");
    if (m_paStreams[0] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

} // (Constructor)

//
// Constructor
//
CBallStream::CBallStream(HRESULT *phr,
                         CBouncingBall *pParent,
                         LPCWSTR pPinName) :
    CSourceStream(NAME("Bouncing Ball"),phr, pParent, pPinName),
    m_iImageWidth(320),
    m_iImageHeight(240),
    m_iDefaultRepeatTime(20)
{
    CAutoLock cAutoLock(&m_cSharedState);

    m_Ball = new CBall(m_iImageWidth, m_iImageHeight);
    if (m_Ball == NULL) {
        *phr = E_OUTOFMEMORY;
    }

} // (Constructor)

//
// Destructor
//
CBallStream::~CBallStream()
{
    CAutoLock cAutoLock(&m_cSharedState);
    if( m_Ball )
        delete m_Ball;

} // (Destructor)

//
// FillBuffer
//
// Plots a ball into the supplied video buffer
//
HRESULT CBallStream::FillBuffer(IMediaSample *pms)
{
    BYTE *pData;
    long lDataLen;

    pms->GetPointer(&pData);
    lDataLen = pms->GetSize();

    // If true then we clear the output buffer and don't attempt to
    // erase a previous drawing of the ball - this will be the case
    // when we start running as the buffer will be full of rubbish
    if( m_bZeroMemory ) {
        ZeroMemory( pData, lDataLen );
    }

    {
        CAutoLock cAutoLockShared(&m_cSharedState);

        // If we haven't just cleared the buffer delete the old
        // ball and move the ball on

        if( !m_bZeroMemory ){
            BYTE aZeroes[ 4 ] = { 0, 0, 0, 0 };
            m_Ball->PlotBall(pData, aZeroes, m_iPixelSize);
            m_Ball->MoveBall(m_rtSampleTime - (LONG) m_iRepeatTime);
        }

        m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);

        // The current time is the sample's start
        CRefTime rtStart = m_rtSampleTime;

        // Increment to find the finish time
        m_rtSampleTime += (LONG)m_iRepeatTime;

        pms->SetTime((REFERENCE_TIME *) &rtStart,(REFERENCE_TIME *) &m_rtSampleTime);
    }

    m_bZeroMemory = FALSE;
    pms->SetSyncPoint(TRUE);
    return NOERROR;

} // FillBuffer

//
// Notify
//
// Alter the repeat rate according to quality management messages sent from
// the downstream filter (often the renderer).  Wind it up or down according
// to the flooding level - also skip forward if we are notified of Late-ness
//
STDMETHODIMP CBallStream::Notify(IBaseFilter * pSender, Quality q)
{
    // Adjust the repeat rate.
    if (q.Proportion<=0) {
        m_iRepeatTime = 1000;        // We don't go slower than 1 per second
    } else {
        m_iRepeatTime = m_iRepeatTime*1000/q.Proportion;
        if (m_iRepeatTime>1000) {
            m_iRepeatTime = 1000;    // We don't go slower than 1 per second
        } else if (m_iRepeatTime<10) {
            m_iRepeatTime = 10;      // We don't go faster than 100/sec
        }
    }

    // skip forwards
    if (q.Late > 0) {
        m_rtSampleTime += q.Late;
    }
    return NOERROR;

} // Notify

//
// GetMediaType
//
// I _prefer_ 5 formats - 8, 16 (*2), 24 or 32 bits per pixel and
// I will suggest these with an image size of 320x240. However
// I can accept any image size which gives me some space to bounce.
//
// A bit of fun:
//      8 bit displays get red balls
//      16 bit displays get blue
//      24 bit see green
//      And 32 bit see yellow
//
// Prefered types should be ordered by quality, zero as highest quality
// Therefore iPosition =
// 0return a 32bit mediatype
// 1return a 24bit mediatype
// 2return 16bit RGB565
// 3return a 16bit mediatype (rgb555)
// 4return 8 bit palettised format
// (iPosition > 4 is invalid)
//
HRESULT CBallStream::GetMediaType(int iPosition, CMediaType *pmt)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());
    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    // Have we run off the end of types

    if (iPosition > 4) {
        return VFW_S_NO_MORE_ITEMS;
    }

    VIDEOINFO *pvi = (VIDEOINFO *) pmt->AllocFormatBuffer(sizeof(VIDEOINFO));
    if (NULL == pvi) {
return(E_OUTOFMEMORY);
    }
    ZeroMemory(pvi, sizeof(VIDEOINFO));

    switch (iPosition) {
   case 0: {// Return our highest quality 32bit format

            // Place the RGB masks as the first 3 doublewords in the palette area
            for (int i = 0; i < 3; i++)
                pvi->TrueColorInfo.dwBitMasks[i] = bits888[i];

            SetPaletteEntries(Yellow);
        pvi->bmiHeader.biCompression = BI_BITFIELDS;
        pvi->bmiHeader.biBitCount    = 32;
    }
    break;

        case 1: {// Return our 24bit format

            SetPaletteEntries(Green);
        pvi->bmiHeader.biCompression = BI_RGB;
        pvi->bmiHeader.biBitCount    = 24;
        }
    break;

        case 2: {       // 16 bit per pixel RGB565

            // Place the RGB masks as the first 3 doublewords in the palette area
            for (int i = 0; i < 3; i++)
                pvi->TrueColorInfo.dwBitMasks[i] = bits565[i];

            SetPaletteEntries(Blue);
            pvi->bmiHeader.biCompression = BI_BITFIELDS;
        pvi->bmiHeader.biBitCount    = 16;
    }
        break;

        case 3: {// 16 bits per pixel RGB555

            // Place the RGB masks as the first 3 doublewords in the palette area
            for (int i = 0; i < 3; i++)
            pvi->TrueColorInfo.dwBitMasks[i] = bits555[i];

            SetPaletteEntries(Blue);
        pvi->bmiHeader.biCompression = BI_BITFIELDS;
        pvi->bmiHeader.biBitCount    = 16;

        }
    break;

        case 4: {// 8 bit palettised

            SetPaletteEntries(Red);
            pvi->bmiHeader.biCompression = BI_RGB;
            pvi->bmiHeader.biBitCount    = 8;
pvi->bmiHeader.biClrUsed= iPALETTE_COLORS;
    }
    break;
    }

    // (Adjust the parameters common to all formats...)

    // put the optimal palette in place
    for (int i = 0; i < iPALETTE_COLORS; i++) {
        pvi->TrueColorInfo.bmiColors[i].rgbRed      = m_Palette[i].peRed;
        pvi->TrueColorInfo.bmiColors[i].rgbBlue     = m_Palette[i].peBlue;
        pvi->TrueColorInfo.bmiColors[i].rgbGreen    = m_Palette[i].peGreen;
        pvi->TrueColorInfo.bmiColors[i].rgbReserved = 0;
    }

    pvi->bmiHeader.biSize= sizeof(BITMAPINFOHEADER);
    pvi->bmiHeader.biWidth= m_iImageWidth;
    pvi->bmiHeader.biHeight= m_iImageHeight;
    pvi->bmiHeader.biPlanes= 1;
    pvi->bmiHeader.biSizeImage= GetBitmapSize(&pvi->bmiHeader);
    pvi->bmiHeader.biClrImportant= 0;

    SetRectEmpty(&(pvi->rcSource));// we want the whole image area rendered.
    SetRectEmpty(&(pvi->rcTarget));// no particular destination rectangle

    pmt->SetType(&MEDIATYPE_Video);
    pmt->SetFormatType(&FORMAT_VideoInfo);
    pmt->SetTemporalCompression(FALSE);

    // Work out the GUID for the subtype from the header info.
    const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
    pmt->SetSubtype(&SubTypeGUID);
    pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);

    return NOERROR;

} // GetMediaType

//
// CheckMediaType
//
// We will accept 8, 16, 24 or 32 bit video formats, in any
// image size that gives room to bounce.
// Returns E_INVALIDARG if the mediatype is not acceptable
//
HRESULT CBallStream::CheckMediaType(const CMediaType *pMediaType)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());

    if ((*(pMediaType->Type()) != MEDIATYPE_Video)// we only output video!
    || !(pMediaType->IsFixedSize()) ) {// ...in fixed size samples
                return E_INVALIDARG;
    }

    // Check for the subtypes we support
    const GUID *SubType = pMediaType->Subtype();
    if ((*SubType != MEDIASUBTYPE_RGB8)
            && (*SubType != MEDIASUBTYPE_RGB565)
    && (*SubType != MEDIASUBTYPE_RGB555)
     && (*SubType != MEDIASUBTYPE_RGB24)
    && (*SubType != MEDIASUBTYPE_RGB32)) {
                return E_INVALIDARG;
    }

    // Get the format area of the media type
    VIDEOINFO *pvi = (VIDEOINFO *) pMediaType->Format();

    if (pvi == NULL)
return E_INVALIDARG;

    // Check the image size. As my default ball is 10 pixels big
    // look for at least a 20x20 image. This is an arbitary size constraint,
    // but it avoids balls that are bigger than the picture...

    if ((pvi->bmiHeader.biWidth < 20) || (pvi->bmiHeader.biHeight < 20) ) {
return E_INVALIDARG;
    }

    return S_OK;  // This format is acceptable.

} // CheckMediaType

//
// DecideBufferSize
//
// This will always be called after the format has been sucessfully
// negotiated. So we have a look at m_mt to see what size image we agreed.
// Then we can ask for buffers of the correct size to contain them.
//
HRESULT CBallStream::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());
    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    VIDEOINFO *pvi = (VIDEOINFO *) m_mt.Format();
    pProperties->cBuffers = 1;
    pProperties->cbBuffer = pvi->bmiHeader.biSizeImage;

    ASSERT(pProperties->cbBuffer);

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    // Is this allocator unsuitable

    if (Actual.cbBuffer < pProperties->cbBuffer) {
        return E_FAIL;
    }

    // Make sure that we have only 1 buffer (we erase the ball in the
    // old buffer to save having to zero a 200k+ buffer every time
    // we draw a frame)

    ASSERT( Actual.cBuffers == 1 );
    return NOERROR;

} // DecideBufferSize

//
// SetMediaType
//
// Called when a media type is agreed between filters
//
HRESULT CBallStream::SetMediaType(const CMediaType *pMediaType)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());

    // Pass the call up to my base class

    HRESULT hr = CSourceStream::SetMediaType(pMediaType);
    if (SUCCEEDED(hr)) {

        VIDEOINFO * pvi = (VIDEOINFO *) m_mt.Format();
        switch (pvi->bmiHeader.biBitCount) {
        case 8:// Make a red pixel

            m_BallPixel[0] = 10;// 0 is palette index of red
    m_iPixelSize   = 1;
    SetPaletteEntries(Red);
    break;

        case 16:// Make a blue pixel

            m_BallPixel[0] = 0xf8;// 00000000 00011111 is blue in rgb555 or rgb565
    m_BallPixel[1] = 0x0;// don't forget the byte ordering within the mask word.
    m_iPixelSize   = 2;
    SetPaletteEntries(Blue);
    break;

        case 24:// Make a green pixel

            m_BallPixel[0] = 0x0;
    m_BallPixel[1] = 0xff;
    m_BallPixel[2] = 0x0;
    m_iPixelSize   = 3;
    SetPaletteEntries(Green);
    break;

case 32:// Make a yellow pixel

            m_BallPixel[0] = 0x0;
    m_BallPixel[1] = 0x0;
    m_BallPixel[2] = 0xff;
    m_BallPixel[3] = 0xff;
    m_iPixelSize   = 4;
            SetPaletteEntries(Yellow);
    break;

        default:
            // We should never agree any other pixel sizes
    ASSERT("Tried to agree inappropriate format");
        }

         CBall *pNewBall = new CBall(pvi->bmiHeader.biWidth, pvi->bmiHeader.biHeight);

         if( pNewBall ){
             delete m_Ball;
             m_Ball = pNewBall;
         } else
             hr = E_OUTOFMEMORY;

         return NOERROR;
    } else {
        return hr;
    }

} // SetMediaType

//
// OnThreadCreate
//
// As we go active reset the stream time to zero
//
HRESULT CBallStream::OnThreadCreate()
{
    CAutoLock cAutoLockShared(&m_cSharedState);
    m_rtSampleTime = 0;

    // we need to also reset the repeat time in case the system
    // clock is turned off after m_iRepeatTime gets very big
    m_iRepeatTime = m_iDefaultRepeatTime;

    // Zero the output buffer on the first frame.
    m_bZeroMemory = TRUE;

    return NOERROR;

} // OnThreadCreate

//
// SetPaletteEntries
//
// If we set our palette to the current system palette + the colours we want
// the system has the least amount of work to do whilst plotting our images,
// if this stream is rendered to the current display. The first non reserved
// palette slot is at m_Palette[10], so put our first colour there. Also
// guarantees that black is always represented by zero in the frame buffer
//
HRESULT CBallStream::SetPaletteEntries(Colour colour)
{
    CAutoLock cAutoLock(m_pFilter->pStateLock());

    HDC hdc = GetDC(NULL);// hdc for the current display.
    UINT res = GetSystemPaletteEntries(hdc, 0, iPALETTE_COLORS, (LPPALETTEENTRY) &m_Palette);
    ReleaseDC(NULL, hdc);

    if (res == 0) {
        return E_FAIL;
    }

    switch (colour) {
        case Red:
            m_Palette[10].peBlue  = 0;
            m_Palette[10].peGreen = 0;
            m_Palette[10].peRed   = 0xff;
            break;
        case Yellow:
            m_Palette[10].peBlue  = 0;
            m_Palette[10].peGreen = 0xff;
            m_Palette[10].peRed   = 0xff;
            break;
        case Blue:
            m_Palette[10].peBlue  = 0xff;
            m_Palette[10].peGreen = 0;
            m_Palette[10].peRed   = 0;
            break;
        case Green:
            m_Palette[10].peBlue  = 0;
            m_Palette[10].peGreen = 0xff;
            m_Palette[10].peRed   = 0;
            break;
    }

    m_Palette[10].peFlags = 0;
    return NOERROR;

} // SetPaletteEntries


FBALL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Generates a movie on the fly of a bouncing ball...

// The class managing the output pin
class CBallStream;

// Main object for a bouncing ball filter
class CBouncingBall : public CSource
{

public:

    // The only allowed way to create Bouncing balls!
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

private:

    // It is only allowed to to create these objects with CreateInstance
    CBouncingBall(LPUNKNOWN lpunk, HRESULT *phr);

}; // CBouncingBall

// CBallStream manages the data flow from the output pin.
class CBallStream : public CSourceStream
{

public:

    CBallStream(HRESULT *phr, CBouncingBall *pParent, LPCWSTR pPinName);
    ~CBallStream();

    // plots a ball into the supplied video frame
    HRESULT FillBuffer(IMediaSample *pms);

    // Ask for buffers of the size appropriate to the agreed media type
    HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    // Set the agreed media type, and set up the necessary ball parameters
    HRESULT SetMediaType(const CMediaType *pMediaType);

    // Because we calculate the ball there is no reason why we
    // can't calculate it in any one of a set of formats...
    HRESULT CheckMediaType(const CMediaType *pMediaType);
    HRESULT GetMediaType(int iPosition, CMediaType *pmt);

    // Resets the stream time to zero
    HRESULT OnThreadCreate(void);

    // Quality control notifications sent to us
    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

private:

    int m_iImageHeight;                // The current image height
    int m_iImageWidth;                // And current image width
    int m_iRepeatTime;                  // Time in msec between frames
    const int m_iDefaultRepeatTime;     // Initial m_iRepeatTime
    BYTE m_BallPixel[4];        // Represents one coloured ball
    intm_iPixelSize;                // The pixel size in bytes
    PALETTEENTRY m_Palette[256];// The optimal palette for the image
    CCritSec m_cSharedState;        // Lock on m_rtSampleTime and m_Ball
    BOOL m_bZeroMemory;                 // Do we need to clear the buffer
    CRefTime m_rtSampleTime;        // The time stamp for each sample
    CBall *m_Ball;                // The current ball object

    // set up the palette appropriately
    enum Colour {Red, Blue, Green, Yellow};
    HRESULT SetPaletteEntries(Colour colour);

}; // CBallStream


RESOURCE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#define IDS_VERSION                     100
#define IDS_TITLE                       101


README.TXT

A sample app that is just like VidCap from Video For Windows but uses Quartz


AMCAP.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define ID_APP      1000

/* Menu Items */
#define MENU_EXIT           4
#define MENU_SET_CAP_FILE5
#define MENU_ALLOC_CAP_FILE6
#define MENU_START_CAP7
#define MENU_STOP_CAP8
#define MENU_CAP_AUDIO12
#define MENU_AUDIOFORMAT13
#define MENU_FRAMERATE14
#define MENU_PREVIEW15
#define MENU_VDEVICE016
#define MENU_VDEVICE117
#define MENU_VDEVICE218
#define MENU_VDEVICE319
#define MENU_VDEVICE420
#define MENU_VDEVICE521
#define MENU_VDEVICE622
#define MENU_VDEVICE723
#define MENU_VDEVICE824
#define MENU_VDEVICE925
#define MENU_ADEVICE026
#define MENU_ADEVICE127
#define MENU_ADEVICE228
#define MENU_ADEVICE329
#define MENU_ADEVICE430
#define MENU_ADEVICE531
#define MENU_ADEVICE632
#define MENU_ADEVICE733
#define MENU_ADEVICE834
#define MENU_ADEVICE935
#define MENU_ABOUT              36
#define MENU_SAVE_CAP_FILE37
#define MENU_NOMASTER38
#define MENU_AUDIOMASTER39
#define MENU_VIDEOMASTER40
#define MENU_TIMELIMIT41
#define MENU_DIALOG042
#define MENU_DIALOG143
#define MENU_DIALOG244
#define MENU_DIALOG345
#define MENU_DIALOG446
#define MENU_DIALOG547
#define MENU_DIALOG648
#define MENU_DIALOG749
#define MENU_DIALOG850
#define MENU_DIALOG951// !!! more?

// Dialogs
#define IDD_ABOUT               600
#define IDD_AllocCapFileSpace       601
#define IDD_FrameRateDialog         602
#define IDD_PressAKeyDialog         603
#define IDD_TimeLimitDialog         604

// defines for dialogs
#define IDD_SetCapFileFree          400
#define IDD_SetCapFileSize          401
#define IDC_FRAMERATE    402
#define IDC_CAPFILENAME    403
#define IDC_TIMELIMIT    404
#define IDC_USETIMELIMIT    405

// window messages
#define WM_FGNOTIFYWM_USER+1


DIALOGS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1997Microsoft Corporation.All Rights Reserved.
 *
 **************************************************************************/

#define IDD_AllocCapFileSpace           652
#define IDD_SetCapFileFree          440
#define IDD_SetCapFileSize          441


AMCAP.CPP

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#include <streams.h>
#include <mmreg.h>
#include <msacm.h>
#include "fcntl.h"
#include "io.h"
#include "stdio.h"
#include "amcap.h"
#include "status.h"

HINSTANCE ghInstApp;
HACCEL ghAccel;
HFONT  ghfontApp;
TEXTMETRIC gtm;
TCHAR gszAppName[]=TEXT("AMCAP");
HWND ghwndApp, ghwndStatus;

// forgive me for using global variables
struct _capstuff {
    char szCaptureFile[_MAX_PATH];
    WORD wCapFileSize;  // size in Meg
    ICaptureGraphBuilder *pBuilder;
    IVideoWindow *pVW;
    IMediaEventEx *pME;
    IAMDroppedFrames *pDF;
    IAMVideoCompression *pVC;
    IAMVfwCaptureDialogs *pDlg;
    IAMStreamConfig *pASC;      // for audio cap
    IAMStreamConfig *pVSC;      // for video cap
    IBaseFilter *pRender;
    IBaseFilter *pVCap, *pACap;
    IGraphBuilder *pFg;
    IFileSinkFilter *pSink;
    IConfigAviMux *pConfigAviMux;
    int  iMasterStream;
    BOOL fCaptureGraphBuilt;
    BOOL fPreviewGraphBuilt;
    BOOL fCapturing;
    BOOL fPreviewing;
    BOOL fCapAudio;
    int  iVideoDevice;
    int  iAudioDevice;
    double FrameRate;
    BOOL fWantPreview;
    long lCapStartTime;
    long lCapStopTime;
    char achFriendlyName[120];
    BOOL fUseTimeLimit;
    DWORD dwTimeLimit;
    int iFormatDialogPos;
    int iSourceDialogPos;
    int iDisplayDialogPos;
    int iVCapDialogPos;
    int iVCrossbarDialogPos;
    int iTVTunerDialogPos;
    int iACapDialogPos;
    int iACrossbarDialogPos;
    int iTVAudioDialogPos;
    int iVCapCapturePinDialogPos;
    int iVCapPreviewPinDialogPos;
    int iACapCapturePinDialogPos;
} gcap;

typedef LONG (PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
LONG WINAPI AppWndProc(HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int ErrMsg (LPTSTR sz,...);
BOOL SetCaptureFile(HWND hWnd);
BOOL SaveCaptureFile(HWND hWnd);
BOOL AllocCaptureFile(HWND hWnd);
int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam);
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL FrameRateProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL TimeLimitProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL PressAKeyProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
void TearDownGraph(void);
BOOL BuildCaptureGraph();
BOOL BuildPreviewGraph();
void UpdateStatus(BOOL fAllStats);
void AddDevicesToMenu();
void ChooseDevices(int idV, int idA);
void ChooseFrameRate();
BOOL InitCapFilters();
void FreeCapFilters();
BOOL StopPreview();
BOOL StartPreview();
BOOL StopCapture();
DWORDLONG GetSize(LPCSTR ach);
void MakeMenuOptions();
/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

// set our app's caption to be our app name followed by the capture file name
//
void SetAppCaption()
{
    char ach[_MAX_PATH + 80];
    lstrcpy(ach, gszAppName);
    if (gcap.szCaptureFile[0] != 0) {
lstrcat(ach, " - ");
lstrcat(ach, gcap.szCaptureFile);
    }
    SetWindowText(ghwndApp, ach);
}

/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)                                                     |
|                                                                              |
|   Description:                                                               |
|       This is called when the application is first loaded into               |
|       memory.  It performs all initialization that doesn't need to be done   |
|       once per instance.                                                     |
|                                                                              |
|   Arguments:                                                                 |
|       hInstance       instance handle of current instance                    |
|       hPrev           instance handle of previous instance                   |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if successful, FALSE if not                                       |
|                                                                              |
\*----------------------------------------------------------------------------*/
BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw,LPSTR szCmdLine)
{
    WNDCLASS    cls;
    HDC         hdc;

    const DWORD  dwExStyle = 0;

    CoInitialize(NULL);
    DbgInitialise(hInst);

    /* Save instance handle for DialogBoxs */
    ghInstApp = hInst;

    ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

    if (!hPrev) {
/*
 *  Register a class for the main application window
 */
cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
cls.hIcon          = LoadIcon(hInst, TEXT("AMCapIcon"));
cls.lpszMenuName   = MAKEINTATOM(ID_APP);
cls.lpszClassName  = MAKEINTATOM(ID_APP);
cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
cls.hInstance      = hInst;
cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
cls.lpfnWndProc    = (LPWNDPROC)AppWndProc;
cls.cbWndExtra     = 0;
cls.cbClsExtra     = 0;

if (!RegisterClass(&cls))
    return FALSE;
    }

    // Is this necessary?
    ghfontApp = (HFONT)GetStockObject(ANSI_VAR_FONT);
    hdc = GetDC(NULL);
    SelectObject(hdc, ghfontApp);
    GetTextMetrics(hdc, &gtm);
    ReleaseDC(NULL, hdc);

    ghwndApp=CreateWindowEx(dwExStyle,
    MAKEINTATOM(ID_APP),    // Class name
    gszAppName,             // Caption
    // Style bits
    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
    CW_USEDEFAULT, 0,       // Position
    320,300,                // Size
    (HWND)NULL,             // Parent window (no parent)
    (HMENU)NULL,            // use class menu
    hInst,                  // handle to window instance
    (LPSTR)NULL             // no params to pass on
   );

    // create the status bar
    statusInit(hInst, hPrev);
    ghwndStatus = CreateWindowEx(
    0,
    szStatusClass,
    NULL,
    WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS,
    0, 0,
    0, 0,
    ghwndApp,
    NULL,
    hInst,
    NULL);
    if (ghwndStatus == NULL) {
return(FALSE);
    }
    ShowWindow(ghwndApp,sw);

    // get the capture file name from win.ini
    GetProfileString("annie", "CaptureFile", "", gcap.szCaptureFile,
sizeof(gcap.szCaptureFile));

    // get which devices to use from win.ini
    gcap.iVideoDevice = -1;     // force update
    gcap.iAudioDevice = -1;     // force update
    int idV = GetProfileInt("annie", "VideoDevice", 0);
    int idA = GetProfileInt("annie", "AudioDevice", 0);
    AddDevicesToMenu(); // list all capture devices in the system as menu items

    // do we want audio?
    gcap.fCapAudio = GetProfileInt("annie", "CaptureAudio", TRUE);
    // do we want preview?
    gcap.fWantPreview = GetProfileInt("annie", "WantPreview", FALSE);
    // which stream should be the master? NONE(-1) means nothing special happens
    // AUDIO(1) means the video frame rate is changed before written out to keep
    // the movie in sync when the audio and video capture crystals are not the
    // same (and therefore drift out of sync after a few minutes).  VIDEO(0)
    // means the audio sample rate is changed before written out
    gcap.iMasterStream = GetProfileInt("annie", "MasterStream", -1);

    // get the frame rate from win.ini before making the graph
    int units_per_frame = GetProfileInt("annie", "FrameRate", 666667);  // 15fps
    gcap.FrameRate = 10000000. / units_per_frame;
    gcap.FrameRate = (int)(gcap.FrameRate * 100) / 100.;
    // reasonable default
    if (gcap.FrameRate <= 0.)
gcap.FrameRate = 15.0;

    // instantiate the capture filters we need to do the menu items
    // this will start previewing, if wanted
    ChooseDevices(idV, idA);    // make these the official devices we're using
    InitCapFilters();           // creates the capture filters we will use
    ChooseDevices(idV, idA);    // puts video capture device name in title bar

    SetAppCaption();
    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                              |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|       hInst           instance handle of this instance of the app            |
|       hPrev           instance handle of previous instance, NULL if first    |
|       szCmdLine       ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG     msg;

    /* Call initialization procedure */
    if (!AppInit(hInst,hPrev,sw,szCmdLine))
return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;)
    {
while (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
{
    if (msg.message == WM_QUIT)
return msg.wParam;

    if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
continue;

    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

WaitMessage();
    }

    // not reached
    return msg.wParam;
}

/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )                              |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|       hwnd            window handle for the window                           |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LONG WINAPI  AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rc;
    int cxBorder, cyBorder, cy;

    switch (msg) {

//
//
case WM_CREATE:
    break;

case WM_COMMAND:
    return AppCommand(hwnd,msg,wParam,lParam);

case WM_INITMENU:
    // we can start capture if not capturing already
    EnableMenuItem((HMENU)wParam, MENU_START_CAP,
(!gcap.fCapturing) ? MF_ENABLED :
MF_GRAYED);
    // we can stop capture if it's currently capturing
    EnableMenuItem((HMENU)wParam, MENU_STOP_CAP,
(gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);

    // We can bring up a dialog if the graph is stopped
    EnableMenuItem((HMENU)wParam, MENU_DIALOG0, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG1, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG2, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG3, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG4, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG5, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG6, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG7, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG8, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_DIALOG9, !gcap.fCapturing ?
MF_ENABLED : MF_GRAYED);

    // toggles capturing audio or not - can't be capturing right now
    // and we must have an audio capture device created
    EnableMenuItem((HMENU)wParam, MENU_CAP_AUDIO,
    (!gcap.fCapturing && gcap.pACap) ? MF_ENABLED : MF_GRAYED);
    // are we capturing audio?
    CheckMenuItem((HMENU)wParam, MENU_CAP_AUDIO,
    (gcap.fCapAudio) ? MF_CHECKED : MF_UNCHECKED);
    // change audio formats when not capturing
    EnableMenuItem((HMENU)wParam, MENU_AUDIOFORMAT, (gcap.fCapAudio &&
!gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);
    // change frame rate when not capturing
    EnableMenuItem((HMENU)wParam, MENU_FRAMERATE,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    // change time limit when not capturing
    EnableMenuItem((HMENU)wParam, MENU_TIMELIMIT,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    // change capture file name when not capturing
    EnableMenuItem((HMENU)wParam, MENU_SET_CAP_FILE,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    // pre-allocate capture file when not capturing
    EnableMenuItem((HMENU)wParam, MENU_ALLOC_CAP_FILE,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    // can save capture file when not capturing
    EnableMenuItem((HMENU)wParam, MENU_SAVE_CAP_FILE,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    // do we want preview?
    CheckMenuItem((HMENU)wParam, MENU_PREVIEW,
(gcap.fWantPreview) ? MF_CHECKED : MF_UNCHECKED);
    // can toggle preview if not capturing
    EnableMenuItem((HMENU)wParam, MENU_PREVIEW,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);

    // which is the master stream?
    EnableMenuItem((HMENU)wParam, MENU_NOMASTER,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    CheckMenuItem((HMENU)wParam, MENU_NOMASTER,
    (gcap.iMasterStream == -1) ? MF_CHECKED : MF_UNCHECKED);
    EnableMenuItem((HMENU)wParam, MENU_AUDIOMASTER,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    CheckMenuItem((HMENU)wParam, MENU_AUDIOMASTER,
    (gcap.iMasterStream == 1) ? MF_CHECKED : MF_UNCHECKED);
    EnableMenuItem((HMENU)wParam, MENU_VIDEOMASTER,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    CheckMenuItem((HMENU)wParam, MENU_VIDEOMASTER,
    (gcap.iMasterStream == 0) ? MF_CHECKED : MF_UNCHECKED);

    // can't select a new capture device when capturing
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE0,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE1,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE2,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE3,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE4,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE5,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE6,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE7,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE8,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_VDEVICE9,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE0,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE1,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE2,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE3,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE4,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE5,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE6,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE7,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE8,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_ADEVICE9,
!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);

    break;

//
// We're out of here!
//
case WM_DESTROY:
    DbgTerminate();
    CoUninitialize();
    PostQuitMessage(0);
    break;

//
//
case WM_CLOSE:
    StopPreview();
    StopCapture();
    TearDownGraph();
    FreeCapFilters();
    // store current settings in win.ini for next time
    WriteProfileString("annie", "CaptureFile", gcap.szCaptureFile);
    char ach[10];
    wsprintf(ach, "%d", gcap.iVideoDevice);
    WriteProfileString("annie", "VideoDevice", ach);
    wsprintf(ach, "%d", gcap.iAudioDevice);
    WriteProfileString("annie", "AudioDevice", ach);
    wsprintf(ach, "%d", (int)(10000000 / gcap.FrameRate));
    WriteProfileString("annie", "FrameRate", ach);
    wsprintf(ach, "%d", gcap.fCapAudio);
    WriteProfileString("annie", "CaptureAudio", ach);
    wsprintf(ach, "%d", gcap.fWantPreview);
    WriteProfileString("annie", "WantPreview", ach);
    wsprintf(ach, "%d", gcap.iMasterStream);
    WriteProfileString("annie", "MasterStream", ach);
    break;

case WM_ERASEBKGND:
    break;

// ESC will stop capture
case WM_KEYDOWN:
    if ((GetAsyncKeyState(VK_ESCAPE) & 0x01) &&
gcap.fCapturing)
StopCapture();
if (gcap.fWantPreview) {
    BuildPreviewGraph();
    StartPreview();
}
    break;

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

    // nothing to do

    EndPaint(hwnd,&ps);
    break;

case WM_TIMER:
    // update our status bar with #captured, #dropped
    UpdateStatus(FALSE);

    // is our time limit up?
    if (gcap.fUseTimeLimit) {
if ((timeGetTime() - gcap.lCapStartTime) / 1000 >=
gcap.dwTimeLimit) {
    StopCapture();
    if (gcap.fWantPreview) {
BuildPreviewGraph();
StartPreview();
    }
}
    }
    break;

case WM_SIZE:
    // make the preview window fit inside our window, taking up
    // all of our client area except for the status window at the
    // bottom
    GetClientRect(ghwndApp, &rc);
    cxBorder = GetSystemMetrics(SM_CXBORDER);
    cyBorder = GetSystemMetrics(SM_CYBORDER);
    cy = statusGetHeight() + cyBorder;
    MoveWindow(ghwndStatus, -cxBorder, rc.bottom - cy,
rc.right + (2 * cxBorder), cy + cyBorder, TRUE);
    rc.bottom -= cy;
    // this is the video renderer window showing the preview
    if (gcap.pVW)
gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
    break;

case WM_FGNOTIFY:
    // uh-oh, something went wrong while capturing - the filtergraph
    // will send us events like EC_COMPLETE, EC_USERABORT and the one
    // we care about, EC_ERRORABORT.
    if (gcap.pME && gcap.fCapturing) {
    LONG event, l1, l2;
    while (gcap.pME->GetEvent(&event, &l1, &l2, 0) == S_OK) {
if (event == EC_ERRORABORT) {
    // pME will go away in BuildPreviewGraph
    gcap.pME->FreeEventParams(event, l1, l2);
    StopCapture();
    if (gcap.fWantPreview) {
BuildPreviewGraph();
StartPreview();
    }
    ErrMsg("ERROR during capture - disk possibly full");
    break;
}
gcap.pME->FreeEventParams(event, l1, l2);
    }
    }
    break;

    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}

// Make a graph builder object we can use for capture graph building
//
BOOL MakeBuilder()
{
    // we have one already
    if (gcap.pBuilder)
return TRUE;

    HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder,
NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder,
(void **)&gcap.pBuilder);
    return (hr == NOERROR) ? TRUE : FALSE;
}

// Make a graph object we can use for capture graph building
//
BOOL MakeGraph()
{
    // we have one already
    if (gcap.pFg)
return TRUE;

    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
       IID_IGraphBuilder, (LPVOID *)&gcap.pFg);
    return (hr == NOERROR) ? TRUE : FALSE;
}

// make sure the preview window inside our window is as big as the
// dimensions of captured video, or some capture cards won't show a preview.
// (Also, it helps people tell what size video they're capturing)
// We will resize our app's window big enough so that once the status bar
// is positioned at the bottom there will be enough room for the preview
// window to be w x h
//
void ResizeWindow(int w, int h)
{
    RECT rcW, rcC;
    int xExtra, yExtra;
    int cyBorder = GetSystemMetrics(SM_CYBORDER);

    GetWindowRect(ghwndApp, &rcW);
    GetClientRect(ghwndApp, &rcC);
    xExtra = rcW.right - rcW.left - rcC.right;
    yExtra = rcW.bottom - rcW.top - rcC.bottom + cyBorder + statusGetHeight();

    rcC.right = w;
    rcC.bottom = h;
    SetWindowPos(ghwndApp, NULL, 0, 0, rcC.right + xExtra,
rcC.bottom + yExtra, SWP_NOZORDER | SWP_NOMOVE);
}

// Free all the reference counts we have so the graph can go away
//
void TearDownGraph()
{
    if (gcap.pBuilder)
gcap.pBuilder->Release();
    gcap.pBuilder = NULL;
    if (gcap.pSink)
gcap.pSink->Release();
    gcap.pSink = NULL;
    if (gcap.pConfigAviMux)
gcap.pConfigAviMux->Release();
    gcap.pConfigAviMux = NULL;
    if (gcap.pRender)
gcap.pRender->Release();
    gcap.pRender = NULL;
    if (gcap.pVW)
gcap.pVW->Release();
    gcap.pVW = NULL;
    if (gcap.pME)
gcap.pME->Release();
    gcap.pME = NULL;
    if (gcap.pFg)
gcap.pFg->Release();
    gcap.pFg = NULL;
    gcap.fCaptureGraphBuilt = FALSE;
    gcap.fPreviewGraphBuilt = FALSE;
}

// create the capture filters.  We need to talk to the video capture filter
// to bring up the format, source and display dialogs
//
BOOL InitCapFilters()
{
    HRESULT hr;
    BOOL f;
    UINT uIndex = 0;

    f = MakeBuilder();
    if (!f) {
ErrMsg("Cannot instantiate graph builder");
return FALSE;
    }

//
// First, we need a Video Capture filter, and some interfaces
//

    // we already have a video capture filter
    if (gcap.pVCap)
goto MakeAudio;

    // !!! There's got to be a way to cache these from building the menu
    // Enumerate all the video devices.  We want #gcap.iVideoDevice
    ICreateDevEnum *pCreateDevEnum;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if (hr != NOERROR)
return FALSE;
    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR)
return FALSE;
    pEm->Reset();
    ULONG cFetched;
    IMoniker *pM;
    gcap.pVCap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
// this is the one we want.  Get its name, and instantiate it.
if ((int)uIndex == gcap.iVideoDevice) {
    IPropertyBag *pBag;
    gcap.achFriendlyName[0] = 0;
    hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
    if(SUCCEEDED(hr)) {
VARIANT var;
var.vt = VT_BSTR;
hr = pBag->Read(L"FriendlyName", &var, NULL);
if (hr == NOERROR) {
    WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1,
gcap.achFriendlyName, 80, NULL, NULL);
    SysFreeString(var.bstrVal);
}
pBag->Release();
    }
    hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
    pM->Release();
    break;
}
pM->Release();
uIndex++;
    }
    pEm->Release();
    if (gcap.pVCap == NULL) {
ErrMsg("Error %x: Cannot create video capture filter", hr);
goto InitCapFiltersFail;
    }

    // we use this interface to get the name of the driver
    // Don't worry if it doesn't work:  This interface may not be available
    // until the pin is connected, or it may not be available at all.
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMVideoCompression, (void **)&gcap.pVC);

    // !!! What if this interface isn't supported?
    // we use this interface to set the frame rate and get the capture size
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMStreamConfig, (void **)&gcap.pVSC);
    if (hr != NOERROR) {
ErrMsg("Error %x: Cannot find VCapture:IAMStreamConfig", hr);
    } else {
AM_MEDIA_TYPE *pmt;
hr = gcap.pVSC->GetFormat(&pmt);        // current capture format
if (hr == NOERROR) {
    // resize our window to the new capture size
    ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
HEADER(pmt->pbFormat)->biHeight);
    DeleteMediaType(pmt);
}
    }

    // we use this interface to bring up the 3 dialogs
    // NOTE:  Only the VfW capture filter supports this.  This app only brings
    // up dialogs for legacy VfW capture drivers, since only those have dialogs
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMVfwCaptureDialogs, (void **)&gcap.pDlg);

MakeAudio:

    // we have a filter made already
    if (gcap.pACap != NULL)
return TRUE;

//
// We want an audio capture filter and some interfaces
//

    // !!! There's got to be a way to cache these from building the menu
    // Enumerate all the audio devices.  We want #gcap.iAudioDevice
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if (hr != NOERROR)
return FALSE;
    uIndex = 0;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR) {
gcap.fCapAudio = FALSE; // better not try
return FALSE;
    }
    pEm->Reset();
    gcap.pACap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
// this is the one we want!
if ((int)uIndex == gcap.iAudioDevice) {
    hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);
    pM->Release();
    break;
}
pM->Release();
uIndex++;
    }
    pEm->Release();
    if (gcap.pACap == NULL) {
gcap.fCapAudio = FALSE; // better not try
ErrMsg("Cannot create audio capture filter");
goto InitCapFiltersFail;
    }

    // !!! What if this interface isn't supported?
    // we use this interface to set the captured wave format
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pACap,
IID_IAMStreamConfig, (void **)&gcap.pASC);
    if (hr != NOERROR) {
    ErrMsg("Cannot find ACapture:IAMStreamConfig");
    }

    return TRUE;

InitCapFiltersFail:
    FreeCapFilters();
    return FALSE;
}

// all done with the capture filters and the graph builder
//
void FreeCapFilters()
{
    if (gcap.pBuilder)
gcap.pBuilder->Release();
    gcap.pBuilder = NULL;
    if (gcap.pVCap)

gcap.pVCap->Release();
    gcap.pVCap = NULL;
    if (gcap.pACap)
gcap.pACap->Release();
    gcap.pACap = NULL;
    if (gcap.pASC)
gcap.pASC->Release();
    gcap.pASC = NULL;
    if (gcap.pVSC)
gcap.pVSC->Release();
    gcap.pVSC = NULL;
    if (gcap.pDF)
gcap.pDF->Release();
    gcap.pDF = NULL;
    if (gcap.pVC)
gcap.pVC->Release();
    gcap.pVC = NULL;
    if (gcap.pDlg)
gcap.pDlg->Release();
    gcap.pDlg = NULL;
}

// build the capture graph!
//
BOOL BuildCaptureGraph()
{
    int cy, cyBorder;
    HRESULT hr;
    BOOL f;
    AM_MEDIA_TYPE *pmt;

    // we have one already
    if (gcap.fCaptureGraphBuilt)
return TRUE;

    // No rebuilding while we're running
    if (gcap.fCapturing || gcap.fPreviewing)
return FALSE;

    // We don't have the necessary capture filters
    if (gcap.pVCap == NULL)
return FALSE;
    if (gcap.pACap == NULL && gcap.fCapAudio)
return FALSE;

    // no capture file name yet... we need one first
    if (gcap.szCaptureFile[0] == 0) {
f = SetCaptureFile(ghwndApp);
if (!f)
    return f;
    }

    // we already have another graph built... tear down the old one
    if (gcap.fPreviewGraphBuilt)
TearDownGraph();

    f = MakeBuilder();
    if (!f) {
ErrMsg("Cannot instantiate graph builder");
return FALSE;
    }

//
// We need a rendering section that will write the capture file out in AVI
// file format
//

    WCHAR wach[_MAX_PATH];
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach,
_MAX_PATH);
    GUID guid = MEDIASUBTYPE_Avi;
    hr = gcap.pBuilder->SetOutputFileName(&guid, wach, &gcap.pRender,
&gcap.pSink);
    if (hr != NOERROR) {
ErrMsg("Cannot set output file");
goto SetupCaptureFail;
    }

// Now tell the AVIMUX to write out AVI files that old apps can read properly.
// If we don't, most apps won't be able to tell where the keyframes are,
// slowing down editing considerably
// Doing this will cause one seek (over the area the index will go) when
// you capture past 1 Gig, but that's no big deal.
// NOTE: This is on by default, so it's not necessary to turn it on

// Also, set the proper MASTER STREAM

    hr = gcap.pRender->QueryInterface(IID_IConfigAviMux,
(void **)&gcap.pConfigAviMux);
    if (hr == NOERROR && gcap.pConfigAviMux) {
gcap.pConfigAviMux->SetOutputCompatibilityIndex(TRUE);
if (gcap.fCapAudio) {
    hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
    if (hr != NOERROR)
ErrMsg("SetMasterStream failed!");
}
    }

//
// The graph builder created a filtergraph to do that.  Find out what it is,
// and put the video capture filter in the graph too.
//

    hr = gcap.pBuilder->GetFiltergraph(&gcap.pFg);
    if (hr != NOERROR) {
ErrMsg("Cannot get filtergraph");
goto SetupCaptureFail;
    }

    hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
    if (hr != NOERROR) {
ErrMsg("Cannot add vidcap to filtergraph");
goto SetupCaptureFail;
    }

//
// Now, add the audio capture filter to the graph?
//

    if (gcap.fCapAudio) {
hr = gcap.pFg->AddFilter(gcap.pACap, NULL);
if (hr != NOERROR) {
    ErrMsg("Cannot add audcap to filtergraph");
    goto SetupCaptureFail;
}
    }

//
// Render the video capture and preview pins - we may not have preview, so
// don't worry if it doesn't work
//

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
NULL, gcap.pRender);
    if (hr != NOERROR) {
ErrMsg("Cannot render video capture stream");
goto SetupCaptureFail;
    }

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, gcap.pVCap,
NULL, NULL);

//
// Render the audio capture pin?
//

    if (gcap.fCapAudio) {
hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
gcap.pACap, NULL, gcap.pRender);
if (hr != NOERROR) {
    ErrMsg("Cannot render audio capture stream");
    goto SetupCaptureFail;
}
    }

//
// Get the preview window to be a child of our app's window
//

    // this will go through a possible decoder, find the video renderer it's
    // connected to, and get the IVideoWindow interface on it
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, gcap.pVCap,
IID_IVideoWindow, (void **)&gcap.pVW);
    if (hr != NOERROR) {
ErrMsg("This graph cannot preview");
    } else {
RECT rc;
gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
// give the preview window all our space but where the status bar is
GetClientRect(ghwndApp, &rc);
cyBorder = GetSystemMetrics(SM_CYBORDER);
cy = statusGetHeight() + cyBorder;
rc.bottom -= cy;
gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
gcap.pVW->put_Visible(OATRUE);
    }

    // now tell it what frame rate to capture at.  Just find the format it
    // is capturing with, and leave everything alone but change the frame rate
    hr = E_FAIL;
    if (gcap.pVSC) {
hr = gcap.pVSC->GetFormat(&pmt);
if (hr == NOERROR) {
    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
    pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
    hr = gcap.pVSC->SetFormat(pmt);
    DeleteMediaType(pmt);
}
    }
    if (hr != NOERROR)
ErrMsg("Cannot set frame rate for capture");

    // now ask the filtergraph to tell us when something is completed or aborted
    // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT).  This is how we will find out
    // if the disk gets full while capturing
    hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME);
    if (hr == NOERROR) {
gcap.pME->SetNotifyWindow((LONG)ghwndApp, WM_FGNOTIFY, 0);
    }

// All done.

    // potential debug output - what the graph looks like
    // DumpGraph(gcap.pFg, 1);

    gcap.fCaptureGraphBuilt = TRUE;
    return TRUE;

SetupCaptureFail:
    TearDownGraph();
    return FALSE;
}

// build the preview graph!
//
// !!! PLEASE NOTE !!!  Some new WDM devices have totally separate capture
// and preview settings.  An application that wishes to preview and then
// capture may have to set the preview pin format using IAMStreamConfig on the
// preview pin, and then again on the capture pin to capture with that format.
// In this sample app, there is a separate page to set the settings on the
// capture pin and one for the preview pin.  To avoid the user
// having to enter the same settings in 2 dialog boxes, an app can have its own
// UI for choosing a format (the possible formats can be enumerated using
// IAMStreamConfig) and then the app can programmatically call IAMStreamConfig
// to set the format on both pins.
//
BOOL BuildPreviewGraph()
{
    int cy, cyBorder;
    HRESULT hr;
    BOOL f;
    AM_MEDIA_TYPE *pmt;
    BOOL fPreviewUsingCapturePin = FALSE;

    // we have one already
    if (gcap.fPreviewGraphBuilt)
return TRUE;

    // No rebuilding while we're running
    if (gcap.fCapturing || gcap.fPreviewing)
return FALSE;

    // We don't have the necessary capture filters
    if (gcap.pVCap == NULL)
return FALSE;
    if (gcap.pACap == NULL && gcap.fCapAudio)
return FALSE;

    // we already have another graph built... tear down the old one
    if (gcap.fCaptureGraphBuilt)
TearDownGraph();

    f = MakeBuilder();
    if (!f) {
ErrMsg("Cannot instantiate graph builder");
return FALSE;
    }

//
// make a filtergraph, give it to the graph builder and put the capture filter
// in the graph
//

    f = MakeGraph();
    if (!f) {
ErrMsg("Cannot instantiate filtergraph");
goto SetupPreviewFail;
    }
    hr = gcap.pBuilder->SetFiltergraph(gcap.pFg);
    if (hr != NOERROR) {
ErrMsg("Cannot give graph to builder");
goto SetupPreviewFail;
    }

    hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
    if (hr != NOERROR) {
ErrMsg("Error %x: Cannot add vidcap to filtergraph", hr);
goto SetupPreviewFail;
    }

//
// Render the preview pin - it may not work if there is no preview pin
//
// !!! So use the capture pin - what about latency/buffer issues?

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, gcap.pVCap,
NULL, NULL);
    if (hr != S_OK) {
gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
NULL, NULL);
fPreviewUsingCapturePin = TRUE;

// !!! Since we are using the capture pin to preview, we should probably
// make the graph have no clock, so that the time stamps on the captured
// frames don't make the renderer drop any frames.

    }

//
// Get the preview window to be a child of our app's window
//

    // this will go through a possible decoder, find the video renderer it's
    // connected to, and get the IVideoWindow interface on it
    hr = gcap.pBuilder->FindInterface(
fPreviewUsingCapturePin ? &PIN_CATEGORY_CAPTURE :
&PIN_CATEGORY_PREVIEW,
gcap.pVCap, IID_IVideoWindow, (void **)&gcap.pVW);
    if (hr != NOERROR) {
ErrMsg("This graph cannot preview properly");
    } else {
RECT rc;
gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
// give the preview window all our space but where the status bar is
GetClientRect(ghwndApp, &rc);
cyBorder = GetSystemMetrics(SM_CYBORDER);
cy = statusGetHeight() + cyBorder;
rc.bottom -= cy;
gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
gcap.pVW->put_Visible(OATRUE);
    }

    // now tell it what frame rate to capture at.  Just find the format it
    // is capturing with, and leave everything alone but change the frame rate
    // No big deal if it fails.  It's just for preview
    if (gcap.pVSC) {
hr = gcap.pVSC->GetFormat(&pmt);
if (hr == NOERROR) {
    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
    pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
    gcap.pVSC->SetFormat(pmt);
    DeleteMediaType(pmt);
}
    }

// All done.

    // potential debug output - what the graph looks like
    // DumpGraph(gcap.pFg, 1);

    gcap.fPreviewGraphBuilt = TRUE;
    return TRUE;

SetupPreviewFail:
    TearDownGraph();
    return FALSE;
}

// Start previewing
//
BOOL StartPreview()
{
    BOOL f = TRUE;

    // way ahead of you
    if (gcap.fPreviewing)
return TRUE;

    if (!gcap.fPreviewGraphBuilt)
return FALSE;

    // run the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
hr = pMC->Run();
if (FAILED(hr)) {
    // stop parts that ran
    pMC->Stop();
}
pMC->Release();
    }
    if (FAILED(hr)) {
ErrMsg("Error %x: Cannot run preview graph", hr);
return FALSE;
    }

    gcap.fPreviewing = TRUE;
    return TRUE;
}

// stop the preview graph
//
BOOL StopPreview()
{
    // way ahead of you
    if (!gcap.fPreviewing) {
return FALSE;
    }

    // stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
hr = pMC->Stop();
pMC->Release();
    }
    if (FAILED(hr)) {
ErrMsg("Error %x: Cannot stop preview graph", hr);
return FALSE;
    }

    gcap.fPreviewing = FALSE;

    // !!! get rid of menu garbage
    InvalidateRect(ghwndApp, NULL, TRUE);

    return TRUE;
}

// start the capture graph
//
BOOL StartCapture()
{
    BOOL f, fHasStreamControl;
    HRESULT hr;

    // way ahead of you
    if (gcap.fCapturing)
return TRUE;

    // or we'll get confused
    if (gcap.fPreviewing)
StopPreview();

    // or we'll crash
    if (!gcap.fCaptureGraphBuilt)
return FALSE;

    REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME;
    // show us a preview first? but don't capture quite yet...
    hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW, NULL,
gcap.fWantPreview ? NULL : &start,
gcap.fWantPreview ? &stop : NULL, 0, 0);
    if (SUCCEEDED(hr))
hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, &start,
NULL, 0, 0);

    // Do we have the ability to control capture and preview separately?
    fHasStreamControl = SUCCEEDED(hr);

    // prepare to run the graph
    IMediaControl *pMC = NULL;
    hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (FAILED(hr)) {
ErrMsg("Error %x: Cannot get IMediaControl", hr);
return FALSE;
    }

    // If we were able to turn preview on yet keep capture off, then we can
    // run the graph now for frame accurate start later yet still showing a
    // preview.   Otherwise, we can't run the graph yet without capture
    // starting too, so we'll pause it so the latency between when they
    // press a key and when capture begins is still small (but they won't have
    // a preview while they wait to press a key)

    if (fHasStreamControl)
hr = pMC->Run();
    else
hr = pMC->Pause();
    if (FAILED(hr)) {
// stop parts that started
pMC->Stop();
pMC->Release();
ErrMsg("Error %x: Cannot start graph", hr);
return FALSE;
    }

    // press a key to start capture
    f = DoDialog(ghwndApp, IDD_PressAKeyDialog, (DLGPROC)PressAKeyProc, 0);
    if (!f) {
pMC->Stop();
pMC->Release();
if (gcap.fWantPreview) {
    BuildPreviewGraph();
    StartPreview();
}
return f;
    }

    // Start capture NOW!
    if (fHasStreamControl) {
gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, NULL, &stop,
0, 0);
    } else {
hr = pMC->Run();
if (FAILED(hr)) {
    // stop parts that started
    pMC->Stop();
    pMC->Release();
    ErrMsg("Error %x: Cannot run graph", hr);
    return FALSE;
}
    }

    pMC->Release();

    // when did we start capture?
    gcap.lCapStartTime = timeGetTime();

    // 30 times a second I want to update my status bar - #captured, #dropped
    SetTimer(ghwndApp, 1, 33, NULL);

    gcap.fCapturing = TRUE;
    return TRUE;
}

// stop the capture graph
//
BOOL StopCapture()
{
    // way ahead of you
    if (!gcap.fCapturing) {
return FALSE;
    }

    // stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
hr = pMC->Stop();
pMC->Release();
    }
    if (FAILED(hr)) {
ErrMsg("Error %x: Cannot stop graph", hr);
return FALSE;
    }

    // when the graph was stopped
    gcap.lCapStopTime = timeGetTime();

    // no more status bar updates
    KillTimer(ghwndApp, 1);

    // one last time for the final count and all the stats
    UpdateStatus(TRUE);

    gcap.fCapturing = FALSE;

    // !!! get rid of menu garbage
    InvalidateRect(ghwndApp, NULL, TRUE);

    return TRUE;
}

// Let's talk about UI for a minute.  There are many programmatic interfaces
// you can use to program a capture filter or related filter to capture the
// way you want it to.... eg:  IAMStreamConfig, IAMVideoCompression,
// IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl,
// IAMVideoProcAmp, etc.
//
// But you probably want some UI to let the user play with all these settings.
// For new WDM-style capture devices, we offer some default UI you can use.
// The code below shows how to bring up all of the dialog boxes supported
// by any capture filters.
//
// The following code shows you how you can bring up all of the
// dialogs supported by a particular object at once on a big page with lots
// of thumb tabs.  You do this by starting with an interface on the object that
// you want, and using ISpecifyPropertyPages to get the whole list, and
// OleCreatePropertyFrame to bring them all up.  This way you will get custom
// property pages a filter has, too, that are not one of the standard pages that
// you know about.  There are at least 9 objects that may have property pages.
// Your app already has 2 of the object pointers, the video capture filter and
// the audio capture filter (let's call them pVCap and pACap)
// 1.  The video capture filter - pVCap
// 2.  The video capture filter's capture pin - get this by calling
//     FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX);
// 3.  The video capture filter's preview pin - get this by calling
//     FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX);
// 4.  The audio capture filter - pACap
// 5.  The audio capture filter's capture pin - get this by calling
//     FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX);
// 6.  The crossbar connected to the video capture filter - get this by calling
//     FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX);
// 7.  The crossbar connected to the audio capture filter - get this by calling
//     FindInterface(NULL, pACap, IID_IAMCrossbar, &pX);
//     NOTE:  may be the same as object #6.  Compare IUnknowns to find out.
// 8.  The TV Tuner connected to the video capture filter - get this by calling
//     FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX);
// 9.  The TV Audio connected to the audio capture filter - get this by calling
//     FindInterface(NULL, pACap, IID_IAMTVAudio, &pX);
//
// Your last choice for UI is to make your own pages, and use the results of
// your custom page to call the interfaces programmatically.

void MakeMenuOptions()
{
    HRESULT hr;
    HMENU hMenuSub = GetSubMenu(GetMenu(ghwndApp), 2); // Options menu

    // remove any old choices from the last device
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);

    int zz = 0;
    gcap.iFormatDialogPos = -1;
    gcap.iSourceDialogPos = -1;
    gcap.iDisplayDialogPos = -1;
    gcap.iVCapDialogPos = -1;
    gcap.iVCrossbarDialogPos = -1;
    gcap.iTVTunerDialogPos = -1;
    gcap.iACapDialogPos = -1;
    gcap.iACrossbarDialogPos = -1;
    gcap.iTVAudioDialogPos = -1;
    gcap.iVCapCapturePinDialogPos = -1;
    gcap.iVCapPreviewPinDialogPos = -1;
    gcap.iACapCapturePinDialogPos = -1;

    // If this device supports the old legacy UI dialogs, offer them

    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Format)) {
AppendMenuA(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, "Video Format...");
gcap.iFormatDialogPos = zz++;
    }
    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Source)) {
AppendMenuA(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, "Video Source...");
gcap.iSourceDialogPos = zz++;
    }
    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Display)) {
AppendMenuA(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, "Video Display...");
gcap.iDisplayDialogPos = zz++;
    }

    // don't bother looking for new property pages if the old ones are supported
    // or if we don't have a capture filter
    if (gcap.pVCap == NULL || gcap.iFormatDialogPos != -1)
return;

    // New WDM devices support new UI and new interfaces.
    // Your app can use some default property
    // pages for UI if you'd like (like we do here) or if you don't like our
    // dialog boxes, feel free to make your own and programmatically set
    // the capture options through interfaces like IAMCrossbar, IAMCameraControl
    // etc.

    // There are 9 objects that might support property pages.  Let's go through
    // them.

    ISpecifyPropertyPages *pSpec;
    CAUUID cauuid;

    // 1. the video capture filter itself

    hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
    if (hr == S_OK) {
        hr = pSpec->GetPages(&cauuid);
        if (hr == S_OK && cauuid.cElems > 0) {
    AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Video Capture Filter...");
    gcap.iVCapDialogPos = zz++;
    CoTaskMemFree(cauuid.pElems);
}
pSpec->Release();
    }

    // 2.  The video capture capture pin

    IAMStreamConfig *pSC;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Video Capture Pin...");
        gcap.iVCapCapturePinDialogPos = zz++;
        CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
pSC->Release();
    }

    // 3.  The video capture preview pin.
    // This basically sets the format being previewed.  Typically, you
    // want to capture and preview using the SAME format, instead of having to
    // enter the same value in 2 dialog boxes.  For a discussion on this, see
    // the comment above the MakePreviewGraph function.

    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, gcap.pVCap,
IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Video Preview Pin...");
        gcap.iVCapPreviewPinDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
pSC->Release();
    }

    // 4.  The video crossbar

    IAMCrossbar *pX;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMCrossbar, (void **)&pX);
    if (hr == S_OK) {
        hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Video Crossbar...");
        gcap.iVCrossbarDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
 pX->Release();
    }

    // 5.  The TVTuner

    IAMTVTuner *pTV;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMTVTuner, (void **)&pTV);
    if (hr == S_OK) {
        hr = pTV->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"TV Tuner...");
        gcap.iTVTunerDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
 pTV->Release();
    }

    // no audio capture, we're done
    if (gcap.pACap == NULL)
return;

    // 6.  The Audio capture filter itself

    hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
    if (hr == S_OK) {
        hr = pSpec->GetPages(&cauuid);
        if (hr == S_OK && cauuid.cElems > 0) {
            AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Audio Capture Filter...");
            gcap.iACapDialogPos = zz++;
    CoTaskMemFree(cauuid.pElems);
        }
        pSpec->Release();
    }

    // 7.  The Audio capture pin

    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pACap,
IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Audio Capture Pin...");
        gcap.iACapCapturePinDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
 pSC->Release();
    }

    // 8.  The Audio Crossbar, but only if it's different than the video
    // crossbar

    IAMCrossbar *pXV;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pACap,
IID_IAMCrossbar, (void **)&pX);
    if (hr == S_OK) {
        BOOL fUse = TRUE;
IUnknown *pUnk1, *pUnk2;
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMCrossbar, (void **)&pXV);
        if (hr == S_OK) {
    if (pX->QueryInterface(IID_IUnknown, (void **)&pUnk1) == S_OK) {
        if (pXV->QueryInterface(IID_IUnknown, (void **)&pUnk2)
== S_OK) {
    if (pUnk1 == pUnk2)
        fUse = FALSE;
    pUnk2->Release();
}
        pUnk1->Release();
    }
    pXV->Release();
}
hr = E_FAIL;
if (fUse)
            hr =pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"Audio Crossbar...");
        gcap.iACrossbarDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
 pX->Release();
    }

    // 9.  The TV Audio filter

    IAMTVAudio *pTVA;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pACap,
IID_IAMTVAudio, (void **)&pTVA);
    if (hr == S_OK) {
        hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
        AppendMenuA(hMenuSub,MF_STRING,MENU_DIALOG0+zz,"TV Audio...");
        gcap.iTVAudioDialogPos = zz++;
CoTaskMemFree(cauuid.pElems);
    }
    pSpec->Release();
        }
 pTVA->Release();
    }
}

// how many captured/dropped so far
//
void UpdateStatus(BOOL fAllStats)
{
    HRESULT hr;
    LONG lDropped, lNot, lAvgFrameSize;
    char ach[160];

    // we use this interface to get the number of captured and dropped frames
    // NOTE:  We cannot query for this interface earlier, as it may not be
    // available until the pin is connected
    if (!gcap.pDF) {
hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, gcap.pVCap,
IID_IAMDroppedFrames, (void **)&gcap.pDF);
    }

    // this filter can't tell us dropped frame info.
    if (!gcap.pDF) {
statusUpdateStatus(ghwndStatus,
"Filter cannot report capture information");
return;
    }

    hr = gcap.pDF->GetNumDropped(&lDropped);
    if (hr == S_OK)
hr = gcap.pDF->GetNumNotDropped(&lNot);
    if (hr != S_OK)
return;

    if (!fAllStats) {
LONG lTime = timeGetTime() - gcap.lCapStartTime;
wsprintf(ach, "Captured %d frames (%d dropped) %d.%dsec", lNot,
lDropped, lTime / 1000,
lTime / 100 - lTime / 1000 * 10);
statusUpdateStatus(ghwndStatus, ach);
return;
    }

    // we want all possible stats, including capture time and actual acheived
    // frame rate and data rate (as opposed to what we tried to get).  These
    // numbers are an indication that though we dropped frames just now, if we
    // chose a data rate and frame rate equal to the numbers I'm about to
    // print, we probably wouldn't drop any frames.

    // average size of frame captured
    hr = gcap.pDF->GetAverageFrameSize(&lAvgFrameSize);
    if (hr != S_OK)
return;

    // how long capture lasted
    LONG lDurMS = gcap.lCapStopTime - gcap.lCapStartTime;
    double flFrame;     // acheived frame rate
    LONG lData;         // acheived data rate

    if (lDurMS > 0) {
flFrame = (double)(LONGLONG)lNot * 1000. /
(double)(LONGLONG)lDurMS;
lData = (LONG)(LONGLONG)(lNot / (double)(LONGLONG)lDurMS *
1000. * (double)(LONGLONG)lAvgFrameSize);
    } else {
flFrame = 0.;
lData = 0;
    }

    wsprintf(ach, "Captured %d frames in %d.%d sec (%d dropped): %d.%d fps %d.%d Meg/sec",
lNot, lDurMS / 1000, lDurMS / 100 - lDurMS / 1000 * 10,
lDropped, (int)flFrame,
(int)(flFrame * 10.) - (int)flFrame * 10,
lData / 1000000,
lData / 1000 - (lData / 1000000 * 1000));
    statusUpdateStatus(ghwndStatus, ach);
}

// Check the devices we're currently using and make filters for them
//
void ChooseDevices(int idV, int idA)
{
    #define VERSIZE 40
    #define DESCSIZE 80
    int versize = VERSIZE;
    int descsize = DESCSIZE;
    WCHAR wachVer[VERSIZE], wachDesc[DESCSIZE];
    char achStatus[VERSIZE + DESCSIZE + 5], achDesc[DESCSIZE], achVer[VERSIZE];

    if (idV != gcap.iVideoDevice) {
// uncheck the old, check the new
if (gcap.iVideoDevice >= 0)     // might be uninitialized
    CheckMenuItem(GetMenu(ghwndApp), MENU_VDEVICE0 + gcap.iVideoDevice,

MF_UNCHECKED);
CheckMenuItem(GetMenu(ghwndApp), MENU_VDEVICE0 + idV, MF_CHECKED);
    }

    if (idA != gcap.iAudioDevice) {
// uncheck the old, check the new
if (gcap.iAudioDevice >= 0)     // might be uninitialized
    CheckMenuItem(GetMenu(ghwndApp), MENU_ADEVICE0 + gcap.iAudioDevice,
MF_UNCHECKED);
CheckMenuItem(GetMenu(ghwndApp), MENU_ADEVICE0 + idA, MF_CHECKED);
    }

    // they chose a new device. rebuild the graphs
    if (gcap.iVideoDevice != idV || gcap.iAudioDevice != idA) {
gcap.iVideoDevice = idV;
gcap.iAudioDevice = idA;
if (gcap.fPreviewing)
    StopPreview();
if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
    TearDownGraph();
FreeCapFilters();
InitCapFilters();
if (gcap.fWantPreview) { // were we previewing?
    BuildPreviewGraph();
    StartPreview();
}
MakeMenuOptions();// the UI choices change per device
    }

    gcap.iVideoDevice = idV;
    gcap.iAudioDevice = idA;

    // Put the video driver name in the status bar - if the filter supports
    // IAMVideoCompression::GetInfo, that's the best way to get the name and
    // the version.  Otherwise use the name we got from device enumeration
    // as a fallback.
    if (gcap.pVC) {
HRESULT hr = gcap.pVC->GetInfo(wachVer, &versize, wachDesc, &descsize,
NULL, NULL, NULL, NULL);
if (hr == S_OK) {
    WideCharToMultiByte(CP_ACP, 0, wachVer, -1, achVer, VERSIZE, NULL,
NULL);
    WideCharToMultiByte(CP_ACP, 0, wachDesc, -1, achDesc, DESCSIZE,
NULL, NULL);
    wsprintf(achStatus, "%s - %s", achDesc, achVer);
    statusUpdateStatus(ghwndStatus, achStatus);
    return;
}
    }
    statusUpdateStatus(ghwndStatus, gcap.achFriendlyName);
}

// put all installed video and audio devices in the menus
//
void AddDevicesToMenu()
{
    UINT    uIndex = 0;
    HMENU   hMenuSub;
    HRESULT hr;

    hMenuSub = GetSubMenu(GetMenu(ghwndApp), 1);        // Devices menu

    // remove the bogus separator
    RemoveMenu(hMenuSub, 0, 0);

    // enumerate all video capture devices
    ICreateDevEnum *pCreateDevEnum;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if (hr != NOERROR)
goto EnumAudio;
    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR) {
ErrMsg("Sorry, you have no video capture hardware");
goto EnumAudio;
    }
    pEm->Reset();
    ULONG cFetched;
    IMoniker *pM;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
IPropertyBag *pBag;
hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
if(SUCCEEDED(hr)) {
    VARIANT var;
    var.vt = VT_BSTR;
    hr = pBag->Read(L"FriendlyName", &var, NULL);
    if (hr == NOERROR) {
char achName[80];
WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, achName, 80,
NULL, NULL);
AppendMenuA(hMenuSub, MF_STRING, MENU_VDEVICE0 + uIndex,
achName);
SysFreeString(var.bstrVal);
    }
    pBag->Release();
}
pM->Release();
uIndex++;
    }
    pEm->Release();

    // separate the video and audio devices
    AppendMenuA(hMenuSub, MF_SEPARATOR, 0, NULL);

EnumAudio:

    // enumerate all audio capture devices
    uIndex = 0;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if (hr != NOERROR)
return;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR)
return;
    pEm->Reset();
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
IPropertyBag *pBag;
hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
if(SUCCEEDED(hr)) {
    VARIANT var;
    var.vt = VT_BSTR;
    hr = pBag->Read(L"FriendlyName", &var, NULL);
    if (hr == NOERROR) {
char achName[80];
WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, achName, 80,
NULL, NULL);
AppendMenuA(hMenuSub, MF_STRING, MENU_ADEVICE0 + uIndex,
achName);
SysFreeString(var.bstrVal);
    }
    pBag->Release();
}
pM->Release();
uIndex++;
    }
    pEm->Release();
}

// let them pick a frame rate
//
void ChooseFrameRate()
{
    HRESULT hr;
    double rate = gcap.FrameRate;

    DoDialog(ghwndApp, IDD_FrameRateDialog, (DLGPROC)FrameRateProc, 0);

    // new frame rate?
    if (gcap.FrameRate != rate) {
if (gcap.fPreviewing)
    StopPreview();
// now tell it what frame rate to capture at.  Just find the format it
// is capturing with, and leave everything else alone
if (gcap.pVSC) {
    AM_MEDIA_TYPE *pmt;
    hr = gcap.pVSC->GetFormat(&pmt);
    if (hr == NOERROR) {
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
gcap.pVSC->SetFormat(pmt);
DeleteMediaType(pmt);
    }
}
if (gcap.fWantPreview)  // we were previewing
    StartPreview();
    }
}

// let them set a capture time limit
//
void ChooseTimeLimit()
{
    DoDialog(ghwndApp, IDD_TimeLimitDialog, (DLGPROC)TimeLimitProc, 0);
}

// choose an audio capture format using ACM
//
void ChooseAudioFormat()
{
    ACMFORMATCHOOSE cfmt;
    DWORD dwSize;
    LPWAVEFORMATEX lpwfx;
    AM_MEDIA_TYPE *pmt;

    // there's no point if we can't set a new format
    if (gcap.pASC == NULL)
return;

    // What's the largest format size around?
    acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);
    HRESULT hr = gcap.pASC->GetFormat(&pmt);
    if (hr != NOERROR)
return;
    lpwfx = (LPWAVEFORMATEX)pmt->pbFormat;
    dwSize = max(dwSize, lpwfx->cbSize + sizeof(WAVEFORMATEX));

    // !!! This doesn't really map to the supported formats of the filter.
    // We should be using a property page based on IAMStreamConfig

    // Put up a dialog box initialized with the current format
    if (lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize)) {
CopyMemory(lpwfx, pmt->pbFormat, pmt->cbFormat);
_fmemset(&cfmt, 0, sizeof(ACMFORMATCHOOSE));
cfmt.cbStruct = sizeof(ACMFORMATCHOOSE);
cfmt.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
// show only formats we can capture
cfmt.fdwEnum = ACM_FORMATENUMF_HARDWARE | ACM_FORMATENUMF_INPUT;
cfmt.hwndOwner = ghwndApp;
cfmt.pwfx = lpwfx;
cfmt.cbwfx = dwSize;

// we chose a new format... so give it to the capture filter
if (!acmFormatChoose(&cfmt)) {
    if (gcap.fPreviewing)
StopPreview();  // can't call IAMStreamConfig::SetFormat
// while streaming
    ((CMediaType *)pmt)->SetFormat((LPBYTE)lpwfx,
lpwfx->cbSize + sizeof(WAVEFORMATEX));
    gcap.pASC->SetFormat(pmt);  // filter will reconnect
    if (gcap.fWantPreview)
StartPreview();
}
GlobalFreePtr(lpwfx) ;
    }
    DeleteMediaType(pmt);
}

/*----------------------------------------------------------------------------*\
|    AppCommand()
|
|    Process all of our WM_COMMAND messages.
\*----------------------------------------------------------------------------*/
LONG PASCAL AppCommand (HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;
    int id = GET_WM_COMMAND_ID(wParam, lParam);
    switch(id)
    {
//
// Our about box
//
case MENU_ABOUT:
    DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd,
(DLGPROC)AboutDlgProc);
    break;

//
// We want out of here!
//
case MENU_EXIT:
    PostMessage(hwnd,WM_CLOSE,0,0L);
    break;

// choose a capture file
//
case MENU_SET_CAP_FILE:
    SetCaptureFile(hwnd);
    break;

// pre-allocate the capture file
//
case MENU_ALLOC_CAP_FILE:
    AllocCaptureFile(hwnd);
    break;

// save the capture file
//
case MENU_SAVE_CAP_FILE:
    SaveCaptureFile(hwnd);
    break;

// start capturing
//
case MENU_START_CAP:
    if (gcap.fPreviewing)
StopPreview();
    if (gcap.fPreviewGraphBuilt)
TearDownGraph();
    BuildCaptureGraph();
    StartCapture();
    break;

// toggle preview
//
case MENU_PREVIEW:
    gcap.fWantPreview = !gcap.fWantPreview;
    if (gcap.fWantPreview) {
BuildPreviewGraph();
StartPreview();
    } else
StopPreview();
    break;

// stop capture
//
case MENU_STOP_CAP:
    StopCapture();
    if (gcap.fWantPreview) {
BuildPreviewGraph();
StartPreview();
    }
    break;

// select the master stream
//
case MENU_NOMASTER:
    gcap.iMasterStream = -1;
    if (gcap.pConfigAviMux) {
hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
if (hr != NOERROR)
    ErrMsg("SetMasterStream failed!");
    }
    break;
case MENU_AUDIOMASTER:
    gcap.iMasterStream = 1;
    if (gcap.pConfigAviMux) {
hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
if (hr != NOERROR)
    ErrMsg("SetMasterStream failed!");
    }
    break;
case MENU_VIDEOMASTER:
    gcap.iMasterStream = 0;
    if (gcap.pConfigAviMux) {
hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
if (hr != NOERROR)
    ErrMsg("SetMasterStream failed!");
    }
    break;

// toggle capturing audio
case MENU_CAP_AUDIO:
    if (gcap.fPreviewing)
StopPreview();
    gcap.fCapAudio = !gcap.fCapAudio;
    // when we capture we'll need a different graph now
    if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
TearDownGraph();
    if (gcap.fWantPreview) {
BuildPreviewGraph();
StartPreview();
    }
    break;

// choose the audio capture format
//
case MENU_AUDIOFORMAT:
    ChooseAudioFormat();
    break;

// pick a frame rate
//
case MENU_FRAMERATE:
    ChooseFrameRate();
    break;

// pick a time limit
//
case MENU_TIMELIMIT:
    ChooseTimeLimit();
    break;

// pick which video capture device to use
// pick which video capture device to use
//
case MENU_VDEVICE0:
case MENU_VDEVICE1:
case MENU_VDEVICE2:
case MENU_VDEVICE3:
case MENU_VDEVICE4:
case MENU_VDEVICE5:
case MENU_VDEVICE6:
case MENU_VDEVICE7:
case MENU_VDEVICE8:
case MENU_VDEVICE9:
    ChooseDevices(id - MENU_VDEVICE0, gcap.iAudioDevice);
    break;

// pick which audio capture device to use
//
case MENU_ADEVICE0:
case MENU_ADEVICE1:
case MENU_ADEVICE2:
case MENU_ADEVICE3:
case MENU_ADEVICE4:
case MENU_ADEVICE5:
case MENU_ADEVICE6:
case MENU_ADEVICE7:
case MENU_ADEVICE8:
case MENU_ADEVICE9:
    ChooseDevices(gcap.iVideoDevice, id - MENU_ADEVICE0);
    break;

// video format dialog
//
case MENU_DIALOG0:
case MENU_DIALOG1:
case MENU_DIALOG2:
case MENU_DIALOG3:
case MENU_DIALOG4:
case MENU_DIALOG5:
case MENU_DIALOG6:
case MENU_DIALOG7:
case MENU_DIALOG8:
case MENU_DIALOG9:

     // they want the VfW format dialog
    if (id - MENU_DIALOG0 == gcap.iFormatDialogPos) {
        hr = gcap.pDlg->ShowDialog(VfwCaptureDialog_Format, ghwndApp);
// this dialog might not work while previewing
if (hr != S_OK && gcap.fWantPreview) {
    StopPreview();
            gcap.pDlg->ShowDialog(VfwCaptureDialog_Format, ghwndApp);
}
        if (gcap.pVSC) {
    AM_MEDIA_TYPE *pmt;
    // current capture format
    hr = gcap.pVSC->GetFormat(&pmt);
    if (hr == NOERROR) {
        // resize our window to the new capture size
        ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
HEADER(pmt->pbFormat)->biHeight);
        DeleteMediaType(pmt);
    }
        }
        if (gcap.fWantPreview)
    StartPreview();
    } else if (id - MENU_DIALOG0 == gcap.iSourceDialogPos) {
        hr = gcap.pDlg->ShowDialog(VfwCaptureDialog_Source, ghwndApp);
// this dialog might not work while previewing
if (hr != S_OK && gcap.fWantPreview) {
    StopPreview();
            gcap.pDlg->ShowDialog(VfwCaptureDialog_Source, ghwndApp);
}
        if (gcap.fWantPreview)
    StartPreview();
    } else if (id - MENU_DIALOG0 == gcap.iDisplayDialogPos) {
        hr = gcap.pDlg->ShowDialog(VfwCaptureDialog_Display, ghwndApp);
// this dialog might not work while previewing
if (hr != S_OK && gcap.fWantPreview) {
    StopPreview();
            gcap.pDlg->ShowDialog(VfwCaptureDialog_Display, ghwndApp);
}
        if (gcap.fWantPreview)
    StartPreview();

    // now the code for the new dialogs

    } else if (id - MENU_DIALOG0 == gcap.iVCapDialogPos) {
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&gcap.pVCap, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}

    } else if (id - MENU_DIALOG0 == gcap.iVCapCapturePinDialogPos) {
    IAMStreamConfig *pSC;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pSC->Release();

    } else if (id - MENU_DIALOG0 == gcap.iVCapPreviewPinDialogPos) {
    IAMStreamConfig *pSC;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pSC->Release();

    } else if (id - MENU_DIALOG0 == gcap.iVCrossbarDialogPos) {
    IAMCrossbar *pX;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pVCap, IID_IAMCrossbar, (void **)&pX);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pX->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pX, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pX->Release();

    } else if (id - MENU_DIALOG0 == gcap.iTVTunerDialogPos) {
    IAMTVTuner *pTV;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pVCap, IID_IAMTVTuner, (void **)&pTV);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pTV->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pTV, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pTV->Release();

    } else if (id - MENU_DIALOG0 == gcap.iACapDialogPos) {
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&gcap.pACap, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}

    } else if (id - MENU_DIALOG0 == gcap.iACapCapturePinDialogPos) {
    IAMStreamConfig *pSC;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pACap, IID_IAMStreamConfig, (void **)&pSC);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pSC->Release();

    } else if (id - MENU_DIALOG0 == gcap.iACrossbarDialogPos) {
    IAMCrossbar *pX;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pACap, IID_IAMCrossbar, (void **)&pX);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pX->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pX, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pX->Release();

    } else if (id - MENU_DIALOG0 == gcap.iTVAudioDialogPos) {
    IAMTVAudio *pTVA;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
gcap.pACap, IID_IAMTVAudio, (void **)&pTVA);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
            hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
    if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pTVA, cauuid.cElems,
    (GUID *)cauuid.pElems, 0, 0, NULL);
    CoTaskMemFree(cauuid.pElems);
    pSpec->Release();
}
pTVA->Release();
    }

    break;

    }
    return 0L;
}

/*----------------------------------------------------------------------------*\
|   ErrMsg - Opens a Message box with a error message in it.  The user can     |
|            select the OK button to continue                                  |
\*----------------------------------------------------------------------------*/
int ErrMsg (LPTSTR sz,...)
{
    static TCHAR ach[2000];
    va_list va;

    va_start(va, sz);
    wvsprintf (ach,sz, va);
    va_end(va);
    MessageBox(ghwndApp,ach,NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
    return FALSE;
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_COMMAND:
EndDialog(hwnd, TRUE);
return TRUE;
case WM_INITDIALOG:
return TRUE;
}
return FALSE;
}

// pre-allocate the capture file
//
BOOL AllocCaptureFile(HWND hWnd)
{
    // we'll get into an infinite loop in the dlg proc setting a value
    if (gcap.szCaptureFile[0] == 0)
return FALSE;

    /*
     * show the allocate file space dialog to encourage
     * the user to pre-allocate space
     */
    if (DoDialog(hWnd, IDD_AllocCapFileSpace, (DLGPROC)AllocCapFileProc, 0)) {

// ensure repaint after dismissing dialog before
// possibly lengthy operation
UpdateWindow(ghwndApp);

// User has hit OK. Alloc requested capture file space
BOOL f = MakeBuilder();
if (!f)
    return FALSE;
WCHAR wach[_MAX_PATH];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1,
wach, _MAX_PATH);
if (gcap.pBuilder->AllocCapFile(wach,
(DWORDLONG)gcap.wCapFileSize * 1024L * 1024L) != NOERROR) {
    MessageBoxA(ghwndApp, "Error",
"Failed to pre-allocate capture file space",
MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
}
return TRUE;
    } else {
return FALSE;
    }
}

/*
 * Put up the open file dialog
 */
BOOL OpenFileDialog(HWND hWnd, LPSTR lpName, int cb)
{
    OPENFILENAMEA ofn;
    LPSTR p;
    char         achFileName[_MAX_PATH];
    char         achBuffer[_MAX_PATH] ;

    if (lpName == NULL || cb <= 0)
return FALSE;

    // start with capture file as current file name
    achFileName[0] = 0;
    lstrcpy(achFileName, gcap.szCaptureFile);

    // Get just the path info
    // Terminate the full path at the last backslash
    lstrcpy(achBuffer, achFileName);
    for (p = achBuffer + lstrlen(achBuffer); p > achBuffer; p--) {
if (*p == '\\') {
    *(p+1) = '\0';
    break;
}
    }

    _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = hWnd ;
    ofn.lpstrFilter = "Microsoft AVI\0*.avi\0\0";
    ofn.nFilterIndex = 0 ;
    ofn.lpstrFile = achFileName;
    ofn.nMaxFile = sizeof(achFileName) ;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = "Set Capture File";
    ofn.nMaxFileTitle = 0 ;
    ofn.lpstrInitialDir = achBuffer;
    ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST ;

    if (GetOpenFileNameA(&ofn)) {
// We have a capture file name
lstrcpyn(lpName, achFileName, cb);
return TRUE;
    } else {
return FALSE;
    }
}

/*
 * Put up a dialog to allow the user to select a capture file.
 */
BOOL SetCaptureFile(HWND hWnd)
{
    if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH)) {
OFSTRUCT os;

// We have a capture file name

/*
 * if this is a new file, then invite the user to
 * allocate some space
 */
if (OpenFile(gcap.szCaptureFile, &os, OF_EXIST) == HFILE_ERROR) {

    // bring up dialog, and set new file size
    BOOL f = AllocCaptureFile(hWnd);
    if (!f)
return FALSE;
}
    } else {
return FALSE;
    }

    SetAppCaption();    // new a new app caption

    // tell the file writer to use the new filename
    if (gcap.pSink) {
WCHAR wach[_MAX_PATH];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1,
wach, _MAX_PATH);
gcap.pSink->SetFileName(wach, NULL);
    }

    return TRUE;
}

/*
 * Put up a dialog to allow the user to save the contents of the capture file
 * elsewhere
 */
BOOL SaveCaptureFile(HWND hWnd)
{
    HRESULT hr;
    char achDstFile[_MAX_PATH];
    WCHAR wachDstFile[_MAX_PATH];
    WCHAR wachSrcFile[_MAX_PATH];

    if (gcap.pBuilder == NULL)
return FALSE;

    if (OpenFileDialog(hWnd, achDstFile, _MAX_PATH)) {

// We have a capture file name
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1,
wachSrcFile, _MAX_PATH);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, achDstFile, -1,
wachDstFile, _MAX_PATH);
statusUpdateStatus(ghwndStatus, "Saving capture file - please wait...");

// we need our own graph builder because the main one might not exist
ICaptureGraphBuilder *pBuilder;
hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder,
NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder,
(void **)&pBuilder);
if (hr == NOERROR) {
    // allow the user to press ESC to abort... don't ask for progress
    hr = pBuilder->CopyCaptureFile(wachSrcFile, wachDstFile,TRUE,NULL);
    pBuilder->Release();
}
if (hr == S_OK)
    statusUpdateStatus(ghwndStatus, "Capture file saved");
else if (hr == S_FALSE)
    statusUpdateStatus(ghwndStatus, "Capture file save aborted");
else
    statusUpdateStatus(ghwndStatus, "Capture file save ERROR");
return (hr == NOERROR ? TRUE : FALSE);

    } else {
return TRUE;    // they cancelled or something
    }
}

// brings up a dialog box
//
int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam)
{
    DLGPROC fn;
    int result;

    fn = (DLGPROC)MakeProcInstance(fnDialog, ghInstApp);
    result = DialogBoxParam(
ghInstApp,
MAKEINTRESOURCE(DialogID),
hwndParent,
fn,
lParam);
    FreeProcInstance(fn);

    return result;
}

//
// GetFreeDiskSpace: Function to Measure Available Disk Space
//
static long GetFreeDiskSpaceInKB(LPSTR pFile)
{
    DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters;
    char RootName[MAX_PATH];
    LPSTR ptmp;    //required arg
    ULARGE_INTEGER ulA, ulB, ulFreeBytes;

    // need to find path for root directory on drive containing
    // this file.

    GetFullPathName(pFile, sizeof(RootName), RootName, &ptmp);

    // truncate this to the name of the root directory (god how tedious)
    if (RootName[0] == '\\' && RootName[1] == '\\') {

// path begins with  \\server\share\path so skip the first
// three backslashes
ptmp = &RootName[2];
while (*ptmp && (*ptmp != '\\')) {
    ptmp++;
}
if (*ptmp) {
    // advance past the third backslash
    ptmp++;
}
    } else {
// path must be drv:\path
ptmp = RootName;
    }

    // find next backslash and put a null after it
    while (*ptmp && (*ptmp != '\\')) {
ptmp++;
    }
    // found a backslash ?
    if (*ptmp) {
// skip it and insert null
ptmp++;
*ptmp = '\0';
    }

    // the only real way of finding out free disk space is calling
    // GetDiskFreeSpaceExA, but it doesn't exist on Win95

    HINSTANCE h = LoadLibraryA("kernel32.dll");
    if (h) {
typedef BOOL (WINAPI *MyFunc)(LPCSTR RootName, PULARGE_INTEGER pulA, PULARGE_INTEGER pulB, PULARGE_INTEGER pulFreeBytes);
MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h,
"GetDiskFreeSpaceExA");
FreeLibrary(h);
if (pfnGetDiskFreeSpaceEx) {
    if (!pfnGetDiskFreeSpaceEx(RootName, &ulA, &ulB, &ulFreeBytes))
return -1;
    return (long)(ulFreeBytes.QuadPart / 1024);
}
    }

    if (!GetDiskFreeSpace(RootName, &dwSectorsPerCluster, &dwBytesPerSector,
&dwFreeClusters, &dwClusters))
return (-1);
    return(MulDiv(dwSectorsPerCluster * dwBytesPerSector,
   dwFreeClusters,
   1024));
}

// AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure
//
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int      nFreeMBs = 0 ;

    switch (Message) {
case WM_INITDIALOG:
{
    DWORDLONG        dwlFileSize = 0;
    long             lFreeSpaceInKB;

    // Get current capture file name and measure its size
    dwlFileSize = GetSize(gcap.szCaptureFile);

    // Get free disk space and add current capture file size to that.
    // Convert the available space to MBs.
    if ((lFreeSpaceInKB =
GetFreeDiskSpaceInKB(gcap.szCaptureFile)) != -1L) {
lFreeSpaceInKB += (long)(dwlFileSize / 1024);
nFreeMBs = lFreeSpaceInKB / 1024 ;
SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ;
    } else {
EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE);
    }

    gcap.wCapFileSize = (WORD) (dwlFileSize / (1024L * 1024L));

    SetDlgItemInt(hDlg, IDD_SetCapFileSize, gcap.wCapFileSize, TRUE) ;
    return TRUE ;
}

case WM_COMMAND :
    switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDOK :
{
    int         iCapFileSize ;

    iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ;
    if (iCapFileSize <= 0 || iCapFileSize > nFreeMBs) {
// You are asking for more than we have !! Sorry, ...
SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ;
SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ;
MessageBeep(MB_ICONEXCLAMATION) ;
return FALSE ;
    }
    gcap.wCapFileSize = iCapFileSize ;

    EndDialog(hDlg, TRUE) ;
    return TRUE ;
}

case IDCANCEL :
    EndDialog(hDlg, FALSE) ;
    return TRUE ;

case IDD_SetCapFileSize:
{
    long l;
    BOOL bchanged;
    char achBuffer[21];

    // check that entered size is a valid number
    GetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer, sizeof(achBuffer));
    l = atol(achBuffer);
    bchanged = FALSE;
    if (l < 1) {
l = 1;
bchanged = TRUE;
    // don't infinite loop if there's < 1 Meg free
    } else if (l > nFreeMBs && nFreeMBs > 0) {
l = nFreeMBs;
bchanged = TRUE;
    } else {
// make sure there are no non-digit chars
// atol() will ignore trailing non-digit characters
int c = 0;
while (achBuffer[c]) {

if (IsCharAlpha(achBuffer[c]) ||
!IsCharAlphaNumeric(achBuffer[c])) {

// string contains non-digit chars - reset
l = 1;
bchanged = TRUE;
break;
    }
    c++;
}
    }
    if (bchanged) {
wsprintf(achBuffer, "%ld", l);
SetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer);
    }
    break;
}
    }
    break ;
    }

    return FALSE ;
}

//
// FrameRateProc: Choose a frame rate
//
int FAR PASCAL FrameRateProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  char  ach[32];

  switch (msg) {
    case WM_INITDIALOG:
/* put the current frame rate in the box */
sprintf(ach, "%f", gcap.FrameRate, ach);
SetDlgItemText(hwnd, IDC_FRAMERATE, ach);
break;

    case WM_COMMAND:
switch(wParam){
    case IDCANCEL:
EndDialog(hwnd, FALSE);
break;

    case IDOK:
/* get the new frame rate */
GetDlgItemText(hwnd, IDC_FRAMERATE, ach, sizeof(ach));
if (atof(ach) <= 0.) {
    ErrMsg("Invalid frame rate.");
    break;
}
gcap.FrameRate = atof(ach);
EndDialog(hwnd, TRUE);
break;
}
break;

    default:
return FALSE;
  }
  return TRUE;
}

//
// TimeLimitProc: Choose a capture time limit
//
int FAR PASCAL TimeLimitProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  char  ach[32];
  DWORD   dwTimeLimit;

  switch (msg) {
    case WM_INITDIALOG:
/* put the current time limit info in the boxes */
sprintf(ach, "%d", gcap.dwTimeLimit, ach);
SetDlgItemText(hwnd, IDC_TIMELIMIT, ach);
CheckDlgButton(hwnd, IDC_USETIMELIMIT, gcap.fUseTimeLimit);
break;

    case WM_COMMAND:
switch(wParam){
    case IDCANCEL:
EndDialog(hwnd, FALSE);
break;

    case IDOK:
/* get the new time limit */
dwTimeLimit = GetDlgItemInt(hwnd, IDC_TIMELIMIT, NULL, FALSE);
gcap.dwTimeLimit = dwTimeLimit;
gcap.fUseTimeLimit = IsDlgButtonChecked(hwnd, IDC_USETIMELIMIT);
EndDialog(hwnd, TRUE);
break;
}
break;

    default:
return FALSE;
  }
  return TRUE;
}

//
// PressAKeyProc: Press OK to capture
//
int FAR PASCAL PressAKeyProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  char ach[_MAX_PATH];

  switch (msg) {
    case WM_INITDIALOG:
/* set the current file name in the box */
wsprintf(ach, "%s", gcap.szCaptureFile);
SetDlgItemText(hwnd, IDC_CAPFILENAME, ach);
break;

    case WM_COMMAND:
switch(wParam){
    case IDCANCEL:
EndDialog(hwnd, FALSE);
break;

    case IDOK:
EndDialog(hwnd, TRUE);
break;
}
break;

    default:
return FALSE;
  }
  return TRUE;
}

DWORDLONG GetSize(LPCSTR ach)
{
    HANDLE hFile = CreateFileA(ach, GENERIC_READ, FILE_SHARE_READ, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if (hFile == INVALID_HANDLE_VALUE) {
return 0;
    }

    DWORD dwSizeHigh;
    DWORD dwSizeLow = GetFileSize(hFile, &dwSizeHigh);

    DWORDLONG dwlSize = dwSizeLow + ((DWORDLONG)dwSizeHigh << 32);

    if (!CloseHandle(hFile)) {
dwlSize = 0;
    }

    return dwlSize;
}


STATUS.CPP

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   status.c: Status bar window
 *
 ***************************************************************************/

#include <streams.h>// ugh
//#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "status.h"

/* for compiling under win3.0 - we don't have this attribute */
#ifndef COLOR_BTNHIGHLIGHT
#define COLOR_BTNHIGHLIGHT 20
#endif

#ifdef _WIN32
typedef WNDPROC LPWNDPROC;
#else
typedef long (FAR PASCAL *LPWNDPROC)();
#endif

// class names for status bar and static text windows
charszStatusClass[] = "StatusClass";
charszText[]   = "SText";
int gStatusStdHeight;   // based on font metrics

static HANDLE ghFont;
static HBRUSH ghbrHL, ghbrShadow;

/* Function Prototypes */
LONG FAR PASCAL statusWndProc(HWND hwnd, unsigned msg,
UINT wParam, LONG lParam);
LONG FAR PASCAL fnText(HWND, unsigned, UINT, LONG);
static VOID NEAR PASCAL PaintText(HWND hwnd, HDC hdc);

/*
 * create the brushes we need
 */
void
statusCreateTools(void)
{
    HDC hdc;
    TEXTMETRIC tm;
    HFONT hfont;

    ghbrHL = CreateSolidBrush(GetSysColor(COLOR_BTNHIGHLIGHT));
    ghbrShadow = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));

    /* Create the font we'll use for the status bar - use system as default */
    ghFont = CreateFont(12, 0,// height, width
                0, 0,// escapement, orientation
                FW_NORMAL,// weight,
                FALSE, FALSE, FALSE,// attributes
                ANSI_CHARSET,// charset
                OUT_DEFAULT_PRECIS,// output precision
                CLIP_DEFAULT_PRECIS,// clip precision
                DEFAULT_QUALITY,// quality
                VARIABLE_PITCH | FF_MODERN,
                "Helv");

    if (ghFont == NULL) {
        ghFont = GetStockObject(SYSTEM_FONT);
    }

    // find the char size to calc standard status bar height
    hdc = GetDC(NULL);
    hfont = (HFONT)SelectObject(hdc, ghFont);
    GetTextMetrics(hdc, &tm);
    SelectObject(hdc, hfont);
    ReleaseDC(NULL, hdc);

    gStatusStdHeight = tm.tmHeight * 3 / 2;

}

void
statusDeleteTools(void)
{
    DeleteObject(ghbrHL);
    DeleteObject(ghbrShadow);

    DeleteObject(ghFont);
}

/*--------------------------------------------------------------+
| statusInit - initialize for status window, register the|
|       Window's class.|
||
+--------------------------------------------------------------*/
//#pragma alloc_text(INIT_TEXT, statusInit)
BOOL statusInit(HANDLE hInst, HANDLE hPrev)
{
  WNDCLASS  cls;

  statusCreateTools();

  if (!hPrev){
  cls.hCursor= LoadCursor(NULL, IDC_ARROW);
  cls.hIcon= NULL;
  cls.lpszMenuName= NULL;
  cls.lpszClassName= szStatusClass;
  cls.hbrBackground     = (HBRUSH) (COLOR_BTNFACE + 1);
  cls.hInstance= (HINSTANCE)hInst;
  cls.style= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  cls.lpfnWndProc= statusWndProc;
  cls.cbClsExtra= 0;
  cls.cbWndExtra= 0;

  if (!RegisterClass(&cls))
  return FALSE;

  cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
  cls.hIcon          = NULL;
  cls.lpszMenuName   = NULL;
  cls.lpszClassName  = (LPSTR)szText;
  cls.hbrBackground  = (HBRUSH) (COLOR_BTNFACE + 1);
  cls.hInstance      = (HINSTANCE)hInst;
  cls.style          = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  cls.lpfnWndProc    = (LPWNDPROC)fnText;
  cls.cbClsExtra     = 0;
  cls.cbWndExtra     = 0;
  if (!RegisterClass(&cls))
return FALSE;
  }

  return TRUE;
}

/*
 * returns the recommended height for a status bar based on the
 * character dimensions of the font used
 */
int
statusGetHeight(void)
{
    return(gStatusStdHeight);
}

/*--------------------------------------------------------------+
| statusUpdateStatus - update the status line|
||
| The argument can either be NULL, a string, or a resource ID|
| cast to a LPCSTR with MAKEINTRESOURCE.|
+--------------------------------------------------------------*/
void statusUpdateStatus(HWND hwnd, LPCSTR lpsz)
{
    charach[80];
    HWND hwndtext;

    if ((lpsz != NULL) && (HIWORD((DWORD) lpsz) == 0)) {
LoadString(GetWindowInstance(hwnd), LOWORD((DWORD) lpsz), ach, sizeof(ach));
lpsz = ach;
    }

    hwndtext = GetDlgItem(hwnd, 1);
    if (!lpsz || *lpsz == '\0') {
SetWindowText(hwndtext,"");
    } else {
SetWindowText(hwndtext, lpsz);
    }
}

/*--------------------------------------------------------------+
| statusWndProc - window proc for status window|
||
+--------------------------------------------------------------*/
LONG FAR PASCAL
statusWndProc(HWND hwnd, unsigned msg, UINT wParam, LONG lParam)
{
  PAINTSTRUCTps;
  HDChdc;
  HWND          hwndSText;

  switch(msg){
    case WM_CREATE:

    /* we need to create the static text control for the status bar */
    hwndSText = CreateWindow(
                            szText,
                            "",
                            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
            0, 0, 0, 0,
                            hwnd,
                            (HMENU) 1,  // child id
                            GetWindowInstance(hwnd),
                            NULL);

    if (!hwndSText) {
    return -1;
            }
    break;

    case WM_DESTROY:
            statusDeleteTools();
    break;

    case WM_SIZE:
        {
            RECT rc;

            GetClientRect(hwnd, &rc);

            MoveWindow(
                GetDlgItem(hwnd, 1),    // get child window handle
                2, 1,                   // xy just inside
                rc.right - 4,
                rc.bottom - 2,
                TRUE);

    break;
        }

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

            // only the background and the child window need painting

    EndPaint(hwnd, &ps);
    break;

    case WM_SYSCOLORCHANGE:
        statusDeleteTools();
        statusCreateTools();
        break;

    case WM_ERASEBKGND:
        break;

  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*
 * window proc for static text window
 */
LONG FAR PASCAL
fnText(HWND hwnd, UINT msg, UINT wParam, LONG lParam )
{
PAINTSTRUCT ps;

switch (msg) {
case WM_SETTEXT:
DefWindowProc(hwnd, msg, wParam, lParam);
InvalidateRect(hwnd,NULL,FALSE);
UpdateWindow(hwnd);
return 0L;

case WM_ERASEBKGND:
return 0L;

case WM_PAINT:
BeginPaint(hwnd, &ps);
PaintText(hwnd, ps.hdc);
EndPaint(hwnd, &ps);
return 0L;
        }

return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*--------------------------------------------------------------+
| PaintText - paint the shadowed static text field.|
||
+--------------------------------------------------------------*/
VOID NEAR PASCAL
PaintText(HWND hwnd, HDC hdc)
{
  RECT rc;
  char        ach[128];
  int  len;
  intdx, dy;
  RECTrcFill;
  HFONThfontOld;
  HBRUSH hbrSave;

  GetClientRect(hwnd, &rc);

  len = GetWindowText(hwnd,ach,sizeof(ach));

  SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
  SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));

  hfontOld = (HFONT)SelectObject(hdc, ghFont);

  rcFill.left = rc.left + 1;
  rcFill.right = rc.right - 1;
  rcFill.top = rc.top + 1;
  rcFill.bottom = rc.bottom - 1;

  /* move in some and do background and text in one swoosh */
  ExtTextOut(hdc,4,1,ETO_OPAQUE,&rcFill,ach,len,NULL);

  dx = rc.right - rc.left;
  dy = rc.bottom - rc.top;

  hbrSave = (HBRUSH)SelectObject(hdc, ghbrShadow);
  PatBlt(hdc, rc.left, rc.top, 1, dy, PATCOPY);
  PatBlt(hdc, rc.left, rc.top, dx, 1, PATCOPY);

  SelectObject(hdc, ghbrHL);
  PatBlt(hdc, rc.right-1, rc.top+1, 1, dy-1, PATCOPY);
  PatBlt(hdc, rc.left+1, rc.bottom -1, dx-1, 1,  PATCOPY);

  if (hfontOld)
  SelectObject(hdc, hfontOld);
  if (hbrSave)
  SelectObject(hdc, hbrSave);
  return ;
}


STATUS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   status.h: Status bar include file
 *
 ***************************************************************************/

/* Globals */
// class name of window to create
extern charszStatusClass[];

/* Function Prototypes */

BOOL  statusInit(HANDLE hInst, HANDLE hPrev);
void  statusUpdateStatus(HWND hwnd, LPCSTR lpsz);// update status line

/*
 * returns the recommended height for a status bar based on the
 * character dimensions of the font used
 */
int statusGetHeight(void);


AMEXTRA.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>        // ActiveMovie base class definitions
#include <mmsystem.h>       // Needed for definition of timeGetTime
#include <limits.h>         // Standard data type limit definitions
#include <measure.h>        // Used for time critical log functions

#include "amextra.h"

#pragma warning(disable:4355)

//  Implements CRenderedInputPin class

CRenderedInputPin::CRenderedInputPin(TCHAR *pObjectName,
                                     CBaseFilter *pFilter,
                                     CCritSec *pLock,
                                     HRESULT *phr,
                                     LPCWSTR pName) :
    CBaseInputPin(pObjectName, pFilter, pLock, phr, pName),
    m_bAtEndOfStream(FALSE),
    m_bCompleteNotified(FALSE)
{
}

// Flush end of stream condition - caller should do any
// necessary stream level locking before calling this

STDMETHODIMP CRenderedInputPin::EndOfStream()
{
    HRESULT hr = CheckStreaming();

    //  Do EC_COMPLETE handling for rendered pins
    if (S_OK == hr  && !m_bAtEndOfStream) {
        m_bAtEndOfStream = TRUE;
        FILTER_STATE fs;
        EXECUTE_ASSERT(SUCCEEDED(m_pFilter->GetState(0, &fs)));
        if (fs == State_Running) {
            DoCompleteHandling();
        }
    }
    return hr;
}

// Called to complete the flush

STDMETHODIMP CRenderedInputPin::EndFlush()
{
    CAutoLock lck(m_pLock);

    // Clean up renderer state
    m_bAtEndOfStream = FALSE;
    m_bCompleteNotified = FALSE;

    return CBaseInputPin::EndFlush();
}

// Notify of Run() from filter

HRESULT CRenderedInputPin::Run(REFERENCE_TIME tStart)
{
    UNREFERENCED_PARAMETER(tStart);
    m_bCompleteNotified = FALSE;
    if (m_bAtEndOfStream) {
        DoCompleteHandling();
    }
    return S_OK;
}

//  Clear status on going into paused state

HRESULT CRenderedInputPin::Active()
{
    m_bAtEndOfStream = FALSE;
    m_bCompleteNotified = FALSE;
    return CBaseInputPin::Active();
}

//  Do stuff to deliver end of stream

void CRenderedInputPin::DoCompleteHandling()
{
    ASSERT(m_bAtEndOfStream);
    if (!m_bCompleteNotified) {
        m_bCompleteNotified = TRUE;
        m_pFilter->NotifyEvent(EC_COMPLETE, 0, 0);
    }
}


AMEXTRA.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __AMEXTRA__
#define __AMEXTRA__

// Simple rendered input pin
//
// NOTE if your filter queues stuff before rendering then it may not be
// appropriate to use this class
//
// In that case queue the end of stream condition until the last sample
// is actually rendered and flush the condition appropriately

class CRenderedInputPin : public CBaseInputPin
{
public:

    CRenderedInputPin(TCHAR *pObjectName,
                      CBaseFilter *pFilter,
                      CCritSec *pLock,
                      HRESULT *phr,
                      LPCWSTR pName);

    // Override methods to track end of stream state
    STDMETHODIMP EndOfStream();
    STDMETHODIMP EndFlush();

    HRESULT Active();
    HRESULT Run(REFERENCE_TIME tStart);

protected:

    // Member variables to track state
    BOOL m_bAtEndOfStream;      // Set by EndOfStream
    BOOL m_bCompleteNotified;   // Set when we notify for EC_COMPLETE

private:
    void DoCompleteHandling();
};

#endif // __AMEXTRA__


AMFILTER.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class hierachy for streams architecture, December 1994

//=====================================================================
//=====================================================================
// The following classes are declared in this header:
//
//
// CBaseMediaFilter            Basic IMediaFilter support (abstract class)
// CBaseFilter                 Support for IBaseFilter (incl. IMediaFilter)
// CEnumPins                   Enumerate input and output pins
// CEnumMediaTypes             Enumerate the preferred pin formats
// CBasePin                    Abstract base class for IPin interface
//    CBaseOutputPin           Adds data provider member functions
//    CBaseInputPin            Implements IMemInputPin interface
// CMediaSample                Basic transport unit for IMemInputPin
// CBaseAllocator              General list guff for most allocators
//    CMemAllocator            Implements memory buffer allocation
//
//=====================================================================
//=====================================================================

#include <streams.h>

//=====================================================================
// Helpers
//=====================================================================
STDAPI CreateMemoryAllocator(IMemAllocator **ppAllocator)
{
    return CoCreateInstance(CLSID_MemoryAllocator,
                            0,
                            CLSCTX_INPROC_SERVER,
                            IID_IMemAllocator,
                            (void **)ppAllocator);
}

//  Put this one here rather than in ctlutil.cpp to avoid linking
//  anything brought in by ctlutil.cpp
STDAPI CreatePosPassThru(
    LPUNKNOWN pAgg,
    BOOL bRenderer,
    IPin *pPin,
    IUnknown **ppPassThru
)
{
    *ppPassThru = NULL;
    IUnknown *pUnkSeek;
    HRESULT hr = CoCreateInstance(CLSID_SeekingPassThru,
                                  pAgg,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IUnknown,
                                  (void **)&pUnkSeek
                                 );
    if (FAILED(hr)) {
        return hr;
    }

    ISeekingPassThru *pPassThru;
    hr = pUnkSeek->QueryInterface(IID_ISeekingPassThru, (void**)&pPassThru);
    if (FAILED(hr)) {
        pUnkSeek->Release();
        return hr;
    }
    hr = pPassThru->Init(bRenderer, pPin);
    pPassThru->Release();
    if (FAILED(hr)) {
        pUnkSeek->Release();
        return hr;
    }
    *ppPassThru = pUnkSeek;
    return S_OK;
}

#define CONNECT_TRACE_LEVEL 3

//=====================================================================
//=====================================================================
// Implements CBaseMediaFilter
//=====================================================================
//=====================================================================

/* Constructor */

CBaseMediaFilter::CBaseMediaFilter(const TCHAR*pName,
   LPUNKNOWNpUnk,
   CCritSec*pLock,
   REFCLSIDclsid) :
    CUnknown(pName, pUnk),
    m_pLock(pLock),
    m_clsid(clsid),
    m_State(State_Stopped),
    m_pClock(NULL)
{
}

/* Destructor */

CBaseMediaFilter::~CBaseMediaFilter()
{
    // must be stopped, but can't call Stop here since
    // our critsec has been destroyed.

    /* Release any clock we were using */

    if (m_pClock) {
m_pClock->Release();
m_pClock = NULL;
    }
}

/* Override this to say what interfaces we support and where */

STDMETHODIMP
CBaseMediaFilter::NonDelegatingQueryInterface(
    REFIID riid,
    void ** ppv)
{
    if (riid == IID_IMediaFilter) {
    return GetInterface((IMediaFilter *) this, ppv);
    } else if (riid == IID_IPersist) {
    return GetInterface((IPersist *) this, ppv);
    } else {
    return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

/* Return the filter's clsid */
STDMETHODIMP
CBaseMediaFilter::GetClassID(CLSID *pClsID)
{
    CheckPointer(pClsID,E_POINTER);
    ValidateReadWritePtr(pClsID,sizeof(CLSID));
    *pClsID = m_clsid;
    return NOERROR;
}

/* Override this if your state changes are not done synchronously */

STDMETHODIMP
CBaseMediaFilter::GetState(DWORD dwMSecs, FILTER_STATE *State)
{
    UNREFERENCED_PARAMETER(dwMSecs);
    CheckPointer(State,E_POINTER);
    ValidateReadWritePtr(State,sizeof(FILTER_STATE));

    *State = m_State;
    return S_OK;
}

/* Set the clock we will use for synchronisation */

STDMETHODIMP
CBaseMediaFilter::SetSyncSource(IReferenceClock *pClock)
{
    CAutoLock cObjectLock(m_pLock);

    // Ensure the new one does not go away - even if the same as the old
    if (pClock) {
pClock->AddRef();
    }

    // if we have a clock, release it
    if (m_pClock) {
m_pClock->Release();
    }

    // Set the new reference clock (might be NULL)
    // Should we query it to ensure it is a clock?  Consider for a debug build.
    m_pClock = pClock;

    return NOERROR;
}

/* Return the clock we are using for synchronisation */
STDMETHODIMP
CBaseMediaFilter::GetSyncSource(IReferenceClock **pClock)
{
    CheckPointer(pClock,E_POINTER);
    ValidateReadWritePtr(pClock,sizeof(IReferenceClock *));
    CAutoLock cObjectLock(m_pLock);

    if (m_pClock) {
// returning an interface... addref it...
m_pClock->AddRef();
    }
    *pClock = (IReferenceClock*)m_pClock;
    return NOERROR;
}

/* Put the filter into a stopped state */

STDMETHODIMP
CBaseMediaFilter::Stop()
{
    CAutoLock cObjectLock(m_pLock);

    m_State = State_Stopped;
    return S_OK;
}

/* Put the filter into a paused state */

STDMETHODIMP
CBaseMediaFilter::Pause()
{
    CAutoLock cObjectLock(m_pLock);

    m_State = State_Paused;
    return S_OK;
}

// Put the filter into a running state.

// The time parameter is the offset to be added to the samples'
// stream time to get the reference time at which they should be presented.
//
// you can either add these two and compare it against the reference clock,
// or you can call CBaseMediaFilter::StreamTime and compare that against
// the sample timestamp.

STDMETHODIMP
CBaseMediaFilter::Run(REFERENCE_TIME tStart)
{
    CAutoLock cObjectLock(m_pLock);

    // remember the stream time offset
    m_tStart = tStart;

    if (m_State == State_Stopped){
HRESULT hr = Pause();

if (FAILED(hr)) {
    return hr;
}
    }
    m_State = State_Running;
    return S_OK;
}

//
// return the current stream time - samples with start timestamps of this
// time or before should be rendered by now
HRESULT
CBaseMediaFilter::StreamTime(CRefTime& rtStream)
{
    // Caller must lock for synchronization
    // We can't grab the filter lock because we want to be able to call
    // this from worker threads without deadlocking

    if (m_pClock == NULL) {
return VFW_E_NO_CLOCK;
    }

    // get the current reference time
    HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream);
    if (FAILED(hr)) {
return hr;
    }

    // subtract the stream offset to get stream time
    rtStream -= m_tStart;

    return S_OK;
}

//=====================================================================
//=====================================================================
// Implements CBaseFilter
//=====================================================================
//=====================================================================

/* Override this to say what interfaces we support and where */

STDMETHODIMP CBaseFilter::NonDelegatingQueryInterface(REFIID riid,
                                                      void **ppv)
{
    /* Do we have this interface */

    if (riid == IID_IBaseFilter) {
return GetInterface((IBaseFilter *) this, ppv);
    } else if (riid == IID_IMediaFilter) {
return GetInterface((IMediaFilter *) this, ppv);
    } else if (riid == IID_IPersist) {
return GetInterface((IPersist *) this, ppv);
    } else if (riid == IID_IAMovieSetup) {
return GetInterface((IAMovieSetup *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

#ifdef DEBUG
STDMETHODIMP_(ULONG) CBaseFilter::NonDelegatingRelease()
{
    if (m_cRef == 1) {
        KASSERT(m_pGraph == NULL);
    }
    return CUnknown::NonDelegatingRelease();
}
#endif

/* Constructor */

CBaseFilter::CBaseFilter(const TCHAR*pName,
 LPUNKNOWNpUnk,
 CCritSec*pLock,
 REFCLSIDclsid) :
    CUnknown( pName, pUnk ),
    m_pLock(pLock),
    m_clsid(clsid),
    m_State(State_Stopped),
    m_pClock(NULL),
    m_pGraph(NULL),
    m_pSink(NULL),
    m_pName(NULL),
    m_PinVersion(1)
{
    ASSERT(pLock != NULL);
}

/* Passes in a redundant HRESULT argument */

CBaseFilter::CBaseFilter(TCHAR     *pName,
                         LPUNKNOWN  pUnk,
                         CCritSec  *pLock,
                         REFCLSID   clsid,
                         HRESULT   *phr) :
    CUnknown( pName, pUnk ),
    m_pLock(pLock),
    m_clsid(clsid),
    m_State(State_Stopped),
    m_pClock(NULL),
    m_pGraph(NULL),
    m_pSink(NULL),
    m_pName(NULL),
    m_PinVersion(1)
{
    ASSERT(pLock != NULL);
    UNREFERENCED_PARAMETER(phr);
}

/* Destructor */

CBaseFilter::~CBaseFilter()
{
// NOTE we do NOT hold references on the filtergraph for m_pGraph or m_pSink
// When we did we had the circular reference problem.  Nothing would go away.

    if (m_pName)
delete[] m_pName;

    // must be stopped, but can't call Stop here since
    // our critsec has been destroyed.

    /* Release any clock we were using */
    if (m_pClock) {
m_pClock->Release();
m_pClock = NULL;
    }
}

/* Return the filter's clsid */
STDMETHODIMP
CBaseFilter::GetClassID(CLSID *pClsID)
{
    CheckPointer(pClsID,E_POINTER);
    ValidateReadWritePtr(pClsID,sizeof(CLSID));
    *pClsID = m_clsid;
    return NOERROR;
}

/* Override this if your state changes are not done synchronously */
STDMETHODIMP
CBaseFilter::GetState(DWORD dwMSecs, FILTER_STATE *State)
{
    UNREFERENCED_PARAMETER(dwMSecs);
    CheckPointer(State,E_POINTER);
    ValidateReadWritePtr(State,sizeof(FILTER_STATE));

    *State = m_State;
    return S_OK;
}

/* Set the clock we will use for synchronisation */

STDMETHODIMP
CBaseFilter::SetSyncSource(IReferenceClock *pClock)
{
    CAutoLock cObjectLock(m_pLock);

    // Ensure the new one does not go away - even if the same as the old
    if (pClock) {
pClock->AddRef();
    }

    // if we have a clock, release it
    if (m_pClock) {
m_pClock->Release();
    }

    // Set the new reference clock (might be NULL)
    // Should we query it to ensure it is a clock?  Consider for a debug build.
    m_pClock = pClock;

    return NOERROR;
}

/* Return the clock we are using for synchronisation */
STDMETHODIMP
CBaseFilter::GetSyncSource(IReferenceClock **pClock)
{
    CheckPointer(pClock,E_POINTER);
    ValidateReadWritePtr(pClock,sizeof(IReferenceClock *));
    CAutoLock cObjectLock(m_pLock);

    if (m_pClock) {
// returning an interface... addref it...
m_pClock->AddRef();
    }
    *pClock = (IReferenceClock*)m_pClock;
    return NOERROR;
}

// override CBaseMediaFilter Stop method, to deactivate any pins this
// filter has.
STDMETHODIMP
CBaseFilter::Stop()
{
    CAutoLock cObjectLock(m_pLock);
    HRESULT hr = NOERROR;

    // notify all pins of the state change
    if (m_State != State_Stopped) {
int cPins = GetPinCount();
for (int c = 0; c < cPins; c++) {

    CBasePin *pPin = GetPin(c);

            // Disconnected pins are not activated - this saves pins worrying
            // about this state themselves. We ignore the return code to make
            // sure everyone is inactivated regardless. The base input pin
            // class can return an error if it has no allocator but Stop can
            // be used to resync the graph state after something has gone bad

            if (pPin->IsConnected()) {
                HRESULT hrTmp = pPin->Inactive();
                if (FAILED(hrTmp) && SUCCEEDED(hr)) {
                    hr = hrTmp;
                }
            }
}
    }
    m_State = State_Stopped;
    return hr;
}

// override CBaseMediaFilter Pause method to activate any pins
// this filter has (also called from Run)

STDMETHODIMP
CBaseFilter::Pause()
{
    CAutoLock cObjectLock(m_pLock);

    // notify all pins of the change to active state
    if (m_State == State_Stopped) {
int cPins = GetPinCount();
for (int c = 0; c < cPins; c++) {

    CBasePin *pPin = GetPin(c);

            // Disconnected pins are not activated - this saves pins
            // worrying about this state themselves

            if (pPin->IsConnected()) {
        HRESULT hr = pPin->Active();
        if (FAILED(hr)) {
    return hr;
        }
            }
}
    }
    m_State = State_Paused;
    return S_OK;
}

// Put the filter into a running state.

// The time parameter is the offset to be added to the samples'
// stream time to get the reference time at which they should be presented.
//
// you can either add these two and compare it against the reference clock,
// or you can call CBaseFilter::StreamTime and compare that against
// the sample timestamp.

STDMETHODIMP
CBaseFilter::Run(REFERENCE_TIME tStart)
{
    CAutoLock cObjectLock(m_pLock);

    // remember the stream time offset
    m_tStart = tStart;

    if (m_State == State_Stopped){
HRESULT hr = Pause();

if (FAILED(hr)) {
    return hr;
}
    }
    // notify all pins of the change to active state
    if (m_State != State_Running) {
int cPins = GetPinCount();
for (int c = 0; c < cPins; c++) {

    CBasePin *pPin = GetPin(c);

            // Disconnected pins are not activated - this saves pins
            // worrying about this state themselves

            if (pPin->IsConnected()) {
        HRESULT hr = pPin->Run(tStart);
        if (FAILED(hr)) {
    return hr;
        }
            }
}
    }
    m_State = State_Running;
    return S_OK;
}

//
// return the current stream time - samples with start timestamps of this
// time or before should be rendered by now
HRESULT
CBaseFilter::StreamTime(CRefTime& rtStream)
{
    // Caller must lock for synchronization
    // We can't grab the filter lock because we want to be able to call
    // this from worker threads without deadlocking

    if (m_pClock == NULL) {
return VFW_E_NO_CLOCK;
    }

    // get the current reference time
    HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream);
    if (FAILED(hr)) {
return hr;
    }

    // subtract the stream offset to get stream time
    rtStream -= m_tStart;

    return S_OK;
}

/* Create an enumerator for the pins attached to this filter */

STDMETHODIMP
CBaseFilter::EnumPins(IEnumPins **ppEnum)
{
    CheckPointer(ppEnum,E_POINTER);
    ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *));

    /* Create a new ref counted enumerator */

    *ppEnum = new CEnumPins(this,
                    NULL);

    return *ppEnum == NULL ? E_OUTOFMEMORY : NOERROR;
}

// default behaviour of FindPin is to assume pins are named
// by their pin names
STDMETHODIMP
CBaseFilter::FindPin(
    LPCWSTR Id,
    IPin ** ppPin
)
{
    CheckPointer(ppPin,E_POINTER);
    ValidateReadWritePtr(ppPin,sizeof(IPin *));

    //  We're going to search the pin list so maintain integrity
    CAutoLock lck(m_pLock);
    int iCount = GetPinCount();
    for (int i = 0; i < iCount; i++) {
        CBasePin *pPin = GetPin(i);
        ASSERT(pPin != NULL);

        if (0 == lstrcmpW(pPin->Name(), Id)) {
            //  Found one that matches
            //
            //  AddRef() and return it
            *ppPin = pPin;
            pPin->AddRef();
            return S_OK;
        }
    }
    *ppPin = NULL;
    return VFW_E_NOT_FOUND;
}

/* Return information about this filter */

STDMETHODIMP
CBaseFilter::QueryFilterInfo(FILTER_INFO * pInfo)
{
    CheckPointer(pInfo,E_POINTER);
    ValidateReadWritePtr(pInfo,sizeof(FILTER_INFO));

    if (m_pName) {
    lstrcpynW(pInfo->achName, m_pName, sizeof(pInfo->achName)/sizeof(WCHAR));
    } else {
pInfo->achName[0] = L'\0';
    }
    pInfo->pGraph = m_pGraph;
    if (m_pGraph)
m_pGraph->AddRef();
    return NOERROR;
}

/* Provide the filter with a filter graph */

STDMETHODIMP
CBaseFilter::JoinFilterGraph(
    IFilterGraph * pGraph,
    LPCWSTR pName)
{
    CAutoLock cObjectLock(m_pLock);

    // NOTE: we no longer hold references on the graph (m_pGraph, m_pSink)

    m_pGraph = pGraph;
    if (m_pGraph) {
HRESULT hr = m_pGraph->QueryInterface(IID_IMediaEventSink,
(void**) &m_pSink);
if (FAILED(hr)) {
    ASSERT(m_pSink == NULL);
}
        else m_pSink->Release();        // we do NOT keep a reference on it.
    } else {
        // if graph pointer is null, then we should
        // also release the IMediaEventSink on the same object - we don't
        // refcount it, so just set it to null
        m_pSink = NULL;
    }

    if (m_pName) {
        delete[] m_pName;
m_pName = NULL;
    }

    if (pName) {
DWORD nameLen = lstrlenW(pName)+1;
m_pName = new WCHAR[nameLen];
if (m_pName) {
    CopyMemory(m_pName, pName, nameLen*sizeof(WCHAR));
} else {
    // !!! error here?
}
    }
    return NOERROR;
}

// return a Vendor information string. Optional - may return E_NOTIMPL.
// memory returned should be freed using CoTaskMemFree
// default implementation returns E_NOTIMPL
STDMETHODIMP
CBaseFilter::QueryVendorInfo(
    LPWSTR* pVendorInfo)
{
    UNREFERENCED_PARAMETER(pVendorInfo);
    return E_NOTIMPL;
}

// send an event notification to the filter graph if we know about it.
// returns S_OK if delivered, S_FALSE if the filter graph does not sink
// events, or an error otherwise.
HRESULT
CBaseFilter::NotifyEvent(
    long EventCode,
    long EventParam1,
    long EventParam2)
{
    // Snapshot so we don't have to lock up
    IMediaEventSink *pSink = m_pSink;
    if (pSink) {
return pSink->Notify(EventCode, EventParam1, EventParam2);
    } else {
return E_NOTIMPL;
    }
}

// Request reconnect
// pPin is the pin to reconnect
// pmt is the type to reconnect with - can be NULL
// Calls ReconnectEx on the filter graph
HRESULT
CBaseFilter::ReconnectPin(
    IPin *pPin,
    AM_MEDIA_TYPE const *pmt
)
{
    IFilterGraph2 *pGraph2;
    if (m_pGraph != NULL) {
        HRESULT hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void **)&pGraph2);
        if (SUCCEEDED(hr)) {
            hr = pGraph2->ReconnectEx(pPin, pmt);
            pGraph2->Release();
            return hr;
        } else {
            return m_pGraph->Reconnect(pPin);
        }
    } else {
        return E_NOINTERFACE;
    }
}

/* This is the same idea as the media type version does for type enumeration
   on pins but for the list of pins available. So if the list of pins you
   provide changes dynamically then either override this virtual function
   to provide the version number, or more simply call IncrementPinVersion */

LONG CBaseFilter::GetPinVersion()
{
    return m_PinVersion;
}

/* Increment the current pin version cookie */

void CBaseFilter::IncrementPinVersion()
{
    InterlockedIncrement(&m_PinVersion);
}

/* register filter */

STDMETHODIMP CBaseFilter::Register()
{
    // get setup data, if it exists
    //
    LPAMOVIESETUP_FILTER psetupdata = GetSetupData();

    // check we've got data
    //
    if( NULL == psetupdata ) return S_FALSE;

    // init is ref counted so call just in case
    // we're being called cold.
    //
    HRESULT hr = CoInitialize( (LPVOID)NULL );
    ASSERT( SUCCEEDED(hr) );

    // get hold of IFilterMapper
    //
    IFilterMapper *pIFM;
    hr = CoCreateInstance( CLSID_FilterMapper
                             , NULL
                             , CLSCTX_INPROC_SERVER
                             , IID_IFilterMapper
                             , (void **)&pIFM       );
    if( SUCCEEDED(hr) )
    {
        hr = AMovieSetupRegisterFilter( psetupdata, pIFM, TRUE );
        pIFM->Release();
    }

    // and clear up
    //
    CoFreeUnusedLibraries();
    CoUninitialize();

    return NOERROR;
}

/* unregister filter */

STDMETHODIMP CBaseFilter::Unregister()
{
    // get setup data, if it exists
    //
    LPAMOVIESETUP_FILTER psetupdata = GetSetupData();

    // check we've got data
    //
    if( NULL == psetupdata ) return S_FALSE;

    // OLE init is ref counted so call
    // just in case we're being called cold.
    //
    HRESULT hr = CoInitialize( (LPVOID)NULL );
    ASSERT( SUCCEEDED(hr) );

    // get hold of IFilterMapper
    //
    IFilterMapper *pIFM;
    hr = CoCreateInstance( CLSID_FilterMapper
                             , NULL
                             , CLSCTX_INPROC_SERVER
                             , IID_IFilterMapper
                             , (void **)&pIFM       );
    if( SUCCEEDED(hr) )
    {
        hr = AMovieSetupRegisterFilter( psetupdata, pIFM, FALSE );

        // release interface
        //
        pIFM->Release();
    }

    // clear up
    //
    CoFreeUnusedLibraries();
    CoUninitialize();

    // handle one acceptable "error" - that
    // of filter not being registered!
    // (couldn't find a suitable #define'd
    // name for the error!)
    //
    if( 0x80070002 == hr)
      return NOERROR;
    else
      return hr;
}

//=====================================================================
//=====================================================================
// Implements CEnumPins
//=====================================================================
//=====================================================================

/* NOTE The implementation of this class calls the CUnknown constructor
   with a NULL unknown pointer. This has the effect of making us a self
   contained class, ie any QueryInterface, AddRef or Release calls will be
   routed to the class's NonDelegatingUnknown methods. You will typically
   find that the classes that do this then override one or more of these
   virtual functions to provide more specialised behaviour. A good example
   of this is where a class wants to keep the QueryInterface internal but
   still wants it's lifetime controlled by the external object */

CEnumPins::CEnumPins(CBaseFilter *pFilter,
     CEnumPins *pEnumPins) :
    m_Position(0),
    m_PinCount(0),
    m_pFilter(pFilter),
    m_cRef(1),               // Already ref counted
    m_PinCache(NAME("Pin Cache"))
{

#ifdef DEBUG
    m_dwCookie = DbgRegisterObjectCreation(TEXT("CEnumPins"));
#endif

    /* We must be owned by a filter derived from CBaseFilter */

    ASSERT(pFilter != NULL);

    /* Hold a reference count on our filter */
    m_pFilter->AddRef();

    /* Are we creating a new enumerator */

    if (pEnumPins == NULL) {
m_Version = m_pFilter->GetPinVersion();
m_PinCount = m_pFilter->GetPinCount();
    } else {
        ASSERT(m_Position <= m_PinCount);
        m_Position = pEnumPins->m_Position;
        m_PinCount = pEnumPins->m_PinCount;
        m_Version = pEnumPins->m_Version;
m_PinCache.AddTail(&(pEnumPins->m_PinCache));
    }
}

/* Destructor releases the reference count on our filter NOTE since we hold
   a reference count on the filter who created us we know it is safe to
   release it, no access can be made to it afterwards though as we have just
   caused the last reference count to go and the object to be deleted */

CEnumPins::~CEnumPins()
{
    m_pFilter->Release();

#ifdef DEBUG
    DbgRegisterObjectDestruction(m_dwCookie);
#endif
}

/* Override this to say what interfaces we support where */

STDMETHODIMP
CEnumPins::QueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv, E_POINTER);

    /* Do we have this interface */

    if (riid == IID_IEnumPins || riid == IID_IUnknown) {
return GetInterface((IEnumPins *) this, ppv);
    } else {
return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG)
CEnumPins::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG)
CEnumPins::Release()
{
    ULONG cRef = InterlockedDecrement(&m_cRef);
    if (cRef == 0) {
        delete this;
    }
    return cRef;
}

/* One of an enumerator's basic member functions allows us to create a cloned
   interface that initially has the same state. Since we are taking a snapshot
   of an object (current position and all) we must lock access at the start */

STDMETHODIMP
CEnumPins::Clone(IEnumPins **ppEnum)
{
    CheckPointer(ppEnum,E_POINTER);
    ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *));
    HRESULT hr = NOERROR;

    /* Check we are still in sync with the filter */
    if (AreWeOutOfSync() == TRUE) {
        *ppEnum = NULL;
        hr =  VFW_E_ENUM_OUT_OF_SYNC;
    } else {

        *ppEnum = new CEnumPins(m_pFilter,
                            this);
        if (*ppEnum == NULL) {
        hr = E_OUTOFMEMORY;
        }
    }
    return hr;
}

/* Return the next pin after the current position */

STDMETHODIMP
CEnumPins::Next(ULONG cPins,        // place this many pins...
IPin **ppPins,      // ...in this array
ULONG *pcFetched)   // actual count passed returned here
{
    CheckPointer(ppPins,E_POINTER);
    ValidateReadWritePtr(ppPins,cPins * sizeof(IPin *));

    ASSERT(ppPins);

    if (pcFetched!=NULL) {
        ValidateWritePtr(pcFetched, sizeof(ULONG));
        *pcFetched = 0;           // default unless we succeed
    }
    // now check that the parameter is valid
    else if (cPins>1) {  // pcFetched == NULL
        return E_INVALIDARG;
    }
    ULONG cFetched = 0;           // increment as we get each one.

    /* Check we are still in sync with the filter */
    if (AreWeOutOfSync() == TRUE) {
// If we are out of sync, we should refresh the enumerator.
// This will reset the position and update the other members, but
// will not clear cache of pins we have already returned.
Refresh();
    }

    /* Calculate the number of available pins */

    int cRealPins = min(m_PinCount - m_Position, (int) cPins);
    if (cRealPins == 0) {
return S_FALSE;
    }

    /* Return each pin interface NOTE GetPin returns CBasePin * not addrefed
       so we must QI for the IPin (which increments its reference count)
       If while we are retrieving a pin from the filter an error occurs we
       assume that our internal state is stale with respect to the filter
       (for example someone has deleted a pin) so we
       return VFW_E_ENUM_OUT_OF_SYNC                            */

    while (cRealPins && (m_PinCount - m_Position)) {

/* Get the next pin object from the filter */

CBasePin *pPin = m_pFilter->GetPin(m_Position++);
if (pPin == NULL) {
            // If this happend, and it's not the first time through, then we've got a problem,
            // since we should really go back and release the iPins, which we have previously
            // AddRef'ed.
            ASSERT( cFetched==0 );
            return VFW_E_ENUM_OUT_OF_SYNC;
}

/* We only want to return this pin, if it is not in our cache */
if (0 == m_PinCache.Find(pPin))
{
    /* From the object get an IPin interface */

    *ppPins = pPin;
    pPin->AddRef();

    cFetched++;
    ppPins++;

    m_PinCache.AddTail(pPin);

    cRealPins--;

}
    }

    if (pcFetched!=NULL) {

*pcFetched = cFetched;
    }

    return (cPins==cFetched ? NOERROR : S_FALSE);
}

/* Skip over one or more entries in the enumerator */

STDMETHODIMP
CEnumPins::Skip(ULONG cPins)
{
    /* Check we are still in sync with the filter */
    if (AreWeOutOfSync() == TRUE) {
        return VFW_E_ENUM_OUT_OF_SYNC;
    }

    /* Work out how many pins are left to skip over */
    /* We could position at the end if we are asked to skip too many... */
    /* ..which would match the base implementation for CEnumMediaTypes::Skip */

    ULONG PinsLeft = m_PinCount - m_Position;
    if (cPins > PinsLeft) {
return S_FALSE;
    }
    m_Position += cPins;
    return NOERROR;
}

/* Set the current position back to the start */
/* Reset has 4 simple steps:
 *
 * Set position to head of list
 * Sync enumerator with object being enumerated
 * Clear the cache of pins already returned
 * return S_OK
 */

STDMETHODIMP
CEnumPins::Reset()
{
    m_Version = m_pFilter->GetPinVersion();
    m_PinCount = m_pFilter->GetPinCount();

    m_Position = 0;

    // Clear the cache
    m_PinCache.RemoveAll();

    return S_OK;
}

/* Set the current position back to the start */
/* Refresh has 3 simple steps:
 *
 * Set position to head of list
 * Sync enumerator with object being enumerated
 * return S_OK
 */

STDMETHODIMP
CEnumPins::Refresh()
{
    m_Version = m_pFilter->GetPinVersion();
    m_PinCount = m_pFilter->GetPinCount();

    m_Position = 0;
    return S_OK;
}

//=====================================================================
//=====================================================================
// Implements CEnumMediaTypes
//=====================================================================
//=====================================================================

/* NOTE The implementation of this class calls the CUnknown constructor
   with a NULL unknown pointer. This has the effect of making us a self
   contained class, ie any QueryInterface, AddRef or Release calls will be
   routed to the class's NonDelegatingUnknown methods. You will typically
   find that the classes that do this then override one or more of these
   virtual functions to provide more specialised behaviour. A good example
   of this is where a class wants to keep the QueryInterface internal but
   still wants it's lifetime controlled by the external object */

CEnumMediaTypes::CEnumMediaTypes(CBasePin *pPin,
 CEnumMediaTypes *pEnumMediaTypes) :
    m_Position(0),
    m_pPin(pPin),
    m_cRef(1)
{

#ifdef DEBUG
    m_dwCookie = DbgRegisterObjectCreation(TEXT("CEnumMediaTypes"));
#endif

    /* We must be owned by a pin derived from CBasePin */

    ASSERT(pPin != NULL);

    /* Hold a reference count on our pin */
    m_pPin->AddRef();

    /* Are we creating a new enumerator */

    if (pEnumMediaTypes == NULL) {
m_Version = m_pPin->GetMediaTypeVersion();
return;
    }

    m_Position = pEnumMediaTypes->m_Position;
    m_Version = pEnumMediaTypes->m_Version;
}

/* Destructor releases the reference count on our base pin. NOTE since we hold
   a reference count on the pin who created us we know it is safe to release
   it, no access can be made to it afterwards though as we might have just
   caused the last reference count to go and the object to be deleted */

CEnumMediaTypes::~CEnumMediaTypes()
{
#ifdef DEBUG
    DbgRegisterObjectDestruction(m_dwCookie);
#endif
    m_pPin->Release();
}

/* Override this to say what interfaces we support where */

STDMETHODIMP
CEnumMediaTypes::QueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv, E_POINTER);

    /* Do we have this interface */

    if (riid == IID_IEnumMediaTypes || riid == IID_IUnknown) {
return GetInterface((IEnumMediaTypes *) this, ppv);
    } else {
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG)
CEnumMediaTypes::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG)
CEnumMediaTypes::Release()
{
    ULONG cRef = InterlockedDecrement(&m_cRef);
    if (cRef == 0) {
        delete this;
    }
    return cRef;
}

/* One of an enumerator's basic member functions allows us to create a cloned
   interface that initially has the same state. Since we are taking a snapshot
   of an object (current position and all) we must lock access at the start */

STDMETHODIMP
CEnumMediaTypes::Clone(IEnumMediaTypes **ppEnum)
{
    CheckPointer(ppEnum,E_POINTER);
    ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *));
    HRESULT hr = NOERROR;

    /* Check we are still in sync with the pin */
    if (AreWeOutOfSync() == TRUE) {
        *ppEnum = NULL;
        hr = VFW_E_ENUM_OUT_OF_SYNC;
    } else {

        *ppEnum = new CEnumMediaTypes(m_pPin,
                                  this);

        if (*ppEnum == NULL) {
        hr =  E_OUTOFMEMORY;
        }
    }
    return hr;
}

/* Enumerate the next pin(s) after the current position. The client using this
   interface passes in a pointer to an array of pointers each of which will
   be filled in with a pointer to a fully initialised media type format
   Return NOERROR if it all works,
          S_FALSE if fewer than cMediaTypes were enumerated.
          VFW_E_ENUM_OUT_OF_SYNC if the enumerator has been broken by
                                 state changes in the filter
   The actual count always correctly reflects the number of types in the array.
*/

STDMETHODIMP
CEnumMediaTypes::Next(ULONG cMediaTypes,          // place this many types...
      AM_MEDIA_TYPE **ppMediaTypes,   // ...in this array
      ULONG *pcFetched)           // actual count passed
{
    CheckPointer(ppMediaTypes,E_POINTER);
    ValidateReadWritePtr(ppMediaTypes,cMediaTypes * sizeof(AM_MEDIA_TYPE *));
    /* Check we are still in sync with the pin */
    if (AreWeOutOfSync() == TRUE) {
        return VFW_E_ENUM_OUT_OF_SYNC;
    }

    if (pcFetched!=NULL) {
        ValidateWritePtr(pcFetched, sizeof(ULONG));
        *pcFetched = 0;           // default unless we succeed
    }
    // now check that the parameter is valid
    else if (cMediaTypes>1) {  // pcFetched == NULL
        return E_INVALIDARG;
    }
    ULONG cFetched = 0;           // increment as we get each one.

    /* Return each media type by asking the filter for them in turn - If we
       have an error code retured to us while we are retrieving a media type
       we assume that our internal state is stale with respect to the filter
       (for example the window size changing) so we return
       VFW_E_ENUM_OUT_OF_SYNC */

    while (cMediaTypes) {

    CMediaType cmt;

HRESULT hr = m_pPin->GetMediaType(m_Position++, &cmt);
        if (S_OK != hr) {
            break;
        }

    /* We now have a CMediaType object that contains the next media type
       but when we assign it to the array position we CANNOT just assign
       the AM_MEDIA_TYPE structure because as soon as the object goes out of
       scope it will delete the memory we have just copied. The function
       we use is CreateMediaType which allocates a task memory block */

        /*  Transfer across the format block manually to save an allocate
            and free on the format block and generally go faster */

    *ppMediaTypes = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
    if (*ppMediaTypes == NULL) {
            break;
    }

        /*  Do a regular copy */
        **ppMediaTypes = (AM_MEDIA_TYPE)cmt;

        /*  Make sure the destructor doesn't free these */
        cmt.pbFormat = NULL;
        cmt.cbFormat = NULL;
        cmt.pUnk     = NULL;

    ppMediaTypes++;
    cFetched++;
    cMediaTypes--;
    }

    if (pcFetched!=NULL) {
        *pcFetched = cFetched;
    }

    return ( cMediaTypes==0 ? NOERROR : S_FALSE );
}

/* Skip over one or more entries in the enumerator */

STDMETHODIMP
CEnumMediaTypes::Skip(ULONG cMediaTypes)
{
    /* Check we are still in sync with the pin */
    if (AreWeOutOfSync() == TRUE) {
        return VFW_E_ENUM_OUT_OF_SYNC;
    }

    m_Position += cMediaTypes;

    /*  See if we're over the end */
    CMediaType cmt;
    return S_OK == m_pPin->GetMediaType(m_Position - 1, &cmt) ? S_OK : S_FALSE;
}

/* Set the current position back to the start */
/* Reset has 3 simple steps:
 *
 * set position to head of list
 * sync enumerator with object being enumerated
 * return S_OK
 */

STDMETHODIMP
CEnumMediaTypes::Reset()
{
    m_Position = 0;

    // Bring the enumerator back into step with the current state.  This
    // may be a noop but ensures that the enumerator will be valid on the
    // next call.
    m_Version = m_pPin->GetMediaTypeVersion();
    return NOERROR;
}

//=====================================================================
//=====================================================================
// Implements CBasePin
//=====================================================================
//=====================================================================

/* NOTE The implementation of this class calls the CUnknown constructor with
   a NULL outer unknown pointer. This has the effect of making us a self
   contained class, ie any QueryInterface, AddRef or Release calls will be
   routed to the class's NonDelegatingUnknown methods. You will typically
   find that the classes that do this then override one or more of these
   virtual functions to provide more specialised behaviour. A good example
   of this is where a class wants to keep the QueryInterface internal but
   still wants its lifetime controlled by the external object */

/* Constructor */

CBasePin::CBasePin(TCHAR *pObjectName,
   CBaseFilter *pFilter,
   CCritSec *pLock,
   HRESULT *phr,
   LPCWSTR pName,
   PIN_DIRECTION dir) :
    CUnknown( pObjectName, NULL ),
    m_pFilter(pFilter),
    m_pLock(pLock),
    m_pName(NULL),
    m_Connected(NULL),
    m_dir(dir),
    m_bRunTimeError(FALSE),
    m_pQSink(NULL),
    m_TypeVersion(1),
    m_tStart(),
    m_tStop(MAX_TIME),
    m_dRate(1.0)
{
    /*  WARNING - pFilter is often not a properly constituted object at
        this state (in particular QueryInterface may not work) - this
        is because its owner is often its containing object and we
        have been called from the containing object's constructor so
        the filter's owner has not yet had its CUnknown constructor
        called
    */
    ASSERT(pFilter != NULL);
    ASSERT(pLock != NULL);

    if (pName) {
DWORD nameLen = lstrlenW(pName)+1;
m_pName = new WCHAR[nameLen];
if (m_pName) {
    CopyMemory(m_pName, pName, nameLen*sizeof(WCHAR));
}
    }

#ifdef DEBUG
    m_cRef = 0;
#endif
}

/* Destructor since a connected pin holds a reference count on us there is
   no way that we can be deleted unless we are not currently connected */

CBasePin::~CBasePin()
{
    //  We don't call disconnect because if the filter is going away
    //  all the pins must have a reference count of zero so they must
    //  have been disconnected anyway - (but check the assumption)
    ASSERT(m_Connected == FALSE);

    if (m_pName)
delete[] m_pName;

    // check the internal reference count is consistent
    ASSERT(m_cRef == 0);
}

/* Override this to say what interfaces we support and where */

STDMETHODIMP
CBasePin::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    /* Do we have this interface */

    if (riid == IID_IPin) {
return GetInterface((IPin *) this, ppv);
    } else if (riid == IID_IQualityControl) {
        return GetInterface((IQualityControl *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

/* Override to increment the owning filter's reference count */

STDMETHODIMP_(ULONG)
CBasePin::NonDelegatingAddRef()
{
    ASSERT(InterlockedIncrement(&m_cRef) > 0);
    return m_pFilter->AddRef();
}

/* Override to decrement the owning filter's reference count */

STDMETHODIMP_(ULONG)
CBasePin::NonDelegatingRelease()
{
    ASSERT(InterlockedDecrement(&m_cRef) >= 0);
    return m_pFilter->Release();
}

/* Displays pin connection information */

#ifdef DEBUG
void
CBasePin::DisplayPinInfo(IPin *pReceivePin)
{

    if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) {
PIN_INFO ConnectPinInfo;
PIN_INFO ReceivePinInfo;

if (FAILED(QueryPinInfo(&ConnectPinInfo))) {
    lstrcpyW(ConnectPinInfo.achName, L"Bad Pin");
} else {
    QueryPinInfoReleaseFilter(ConnectPinInfo);
}

if (FAILED(pReceivePin->QueryPinInfo(&ReceivePinInfo))) {
    lstrcpyW(ReceivePinInfo.achName, L"Bad Pin");
} else {
    QueryPinInfoReleaseFilter(ReceivePinInfo);
}

DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying to connect Pins :")));
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("    <%ls>"), ConnectPinInfo.achName));
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("    <%ls>"), ReceivePinInfo.achName));
    }
}
#endif

/* Displays general information on the pin media type */

#ifdef DEBUG
void CBasePin::DisplayTypeInfo(IPin *pPin, const CMediaType *pmt)
{
    UNREFERENCED_PARAMETER(pPin);
    if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying media type:")));
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("    major type:  %s"),
       GuidNames[*pmt->Type()]));
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("    sub type  :  %s"),
       GuidNames[*pmt->Subtype()]));
    }
}
#endif

/* Asked to connect to a pin. A pin is always attached to an owning filter
   object so we always delegate our locking to that object. We first of all
   retrieve a media type enumerator for the input pin and see if we accept
   any of the formats that it would ideally like, failing that we retrieve
   our enumerator and see if it will accept any of our preferred types */

STDMETHODIMP
CBasePin::Connect(
    IPin * pReceivePin,
    const AM_MEDIA_TYPE *pmt   // optional media type
)
{
    CheckPointer(pReceivePin,E_POINTER);
    ValidateReadPtr(pReceivePin,sizeof(IPin));
    CAutoLock cObjectLock(m_pLock);
    DisplayPinInfo(pReceivePin);

    /* See if we are already connected */

    if (m_Connected) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected")));
return VFW_E_ALREADY_CONNECTED;
    }

    /* See if the filter is active */
    if (!IsStopped()) {
        return VFW_E_NOT_STOPPED;
    }

    // Find a mutually agreeable media type -
    // Pass in the template media type. If this is partially specified,
    // each of the enumerated media types will need to be checked against
    // it. If it is non-null and fully specified, we will just try to connect
    // with this.

    const CMediaType * ptype = (CMediaType*)pmt;
    HRESULT hr = AgreeMediaType(pReceivePin, ptype);
    if (FAILED(hr)) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type")));
BreakConnect();
return hr;
    }

    DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded")));
    return NOERROR;
}

// given a specific media type, attempt a connection (includes
// checking that the type is acceptable to this pin)
HRESULT
CBasePin::AttemptConnection(
    IPin* pReceivePin,      // connect to this pin
    const CMediaType* pmt   // using this type
)
{
    // Check that the connection is valid  -- need to do this for every
    // connect attempt since BreakConnect will undo it.
    HRESULT hr = CheckConnect(pReceivePin);
    if (FAILED(hr)) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed")));
BreakConnect();
return hr;
    }

    DisplayTypeInfo(pReceivePin, pmt);

    /* Check we will accept this media type */

    hr = CheckMediaType(pmt);
    if (hr == NOERROR) {

        /*  Make ourselves look connected otherwise ReceiveConnection
            may not be able to complete the connection
        */
        m_Connected = pReceivePin;
        m_Connected->AddRef();
        SetMediaType(pmt);

        /* See if the other pin will accept this type */

        hr = pReceivePin->ReceiveConnection((IPin *)this, pmt);
        if (SUCCEEDED(hr)) {
            /* Complete the connection */

            hr = CompleteConnect(pReceivePin);
            if (SUCCEEDED(hr)) {
                return hr;
            } else {
                DbgLog((LOG_TRACE,
                        CONNECT_TRACE_LEVEL,
                        TEXT("Failed to complete connection")));
                pReceivePin->Disconnect();
            }
        }
    } else {
// we cannot use this media type

// return a specific media type error if there is one
// or map a general failure code to something more helpful
// (in particular S_FALSE gets changed to an error code)
if (SUCCEEDED(hr) ||
    (hr == E_FAIL) ||
    (hr == E_INVALIDARG)) {
hr = VFW_E_TYPE_NOT_ACCEPTED;
}
    }

    // BreakConnect and release any connection here in case CheckMediaType
    // failed, or if we set anything up during a call back during
    // ReceiveConnection.
    BreakConnect();

    /*  If failed then undo our state */
    if (m_Connected) {
        m_Connected->Release();
        m_Connected = NULL;
    }

    return hr;
}

/* Given an enumerator we cycle through all the media types it proposes and
   firstly suggest them to our derived pin class and if that succeeds try
   them with the pin in a ReceiveConnection call. This means that if our pin
   proposes a media type we still check in here that we can support it. This
   is deliberate so that in simple cases the enumerator can hold all of the
   media types even if some of them are not really currently available */

HRESULT CBasePin::TryMediaTypes(
    IPin *pReceivePin,
    const CMediaType *pmt,
    IEnumMediaTypes *pEnum)
{
    /* Reset the current enumerator position */

    HRESULT hr = pEnum->Reset();
    if (FAILED(hr)) {
return hr;
    }

    CMediaType *pMediaType = NULL;
    ULONG ulMediaCount = 0;

    // attempt to remember a specific error code if there is one
    HRESULT hrFailure = S_OK;

    for (;;) {

/* Retrieve the next media type NOTE each time round the loop the
   enumerator interface will allocate another AM_MEDIA_TYPE structure
   If we are successful then we copy it into our output object, if
   not then we must delete the memory allocated before returning */

hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount);
if (hr != S_OK) {
    if (S_OK == hrFailure) {
hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;
    }
    return hrFailure;
}

ASSERT(ulMediaCount == 1);
ASSERT(pMediaType);

        // check that this matches the partial type (if any)

        if ((pmt == NULL) ||
            pMediaType->MatchesPartial(pmt)) {

            hr = AttemptConnection(pReceivePin, pMediaType);

    // attempt to remember a specific error code
    if (FAILED(hr) &&
SUCCEEDED(hrFailure) &&
(hr != E_FAIL) &&
(hr != E_INVALIDARG) &&
(hr != VFW_E_TYPE_NOT_ACCEPTED)) {
    hrFailure = hr;
    }
        } else {
            hr = VFW_E_NO_ACCEPTABLE_TYPES;
        }

        DeleteMediaType(pMediaType);

        if (S_OK == hr) {
            return hr;
        }
    }
}

/* This is called to make the connection, including the taask of finding
   a media type for the pin connection. pmt is the proposed media type
   from the Connect call: if this is fully specified, we will try that.
   Otherwise we enumerate and try all the input pin's types first and
   if that fails we then enumerate and try all our preferred media types.
   For each media type we check it against pmt (if non-null and partially
   specified) as well as checking that both pins will accept it.
 */

HRESULT CBasePin::AgreeMediaType(
    IPin *pReceivePin,
    const CMediaType *pmt)
{
    ASSERT(pReceivePin);
    IEnumMediaTypes *pEnumMediaTypes = NULL;

    // if the media type is fully specified then use that
    if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) {

        // if this media type fails, then we must fail the connection
        // since if pmt is nonnull we are only allowed to connect
        // using a type that matches it.

        return AttemptConnection(pReceivePin, pmt);
    }

    /* Try the other pin's enumerator */

    HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;

    HRESULT hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes);
    if (SUCCEEDED(hr)) {
ASSERT(pEnumMediaTypes);
hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);
pEnumMediaTypes->Release();
if (SUCCEEDED(hr)) {
    return NOERROR;
} else {
    // try to remember specific error codes if there are any
    if ((hr != E_FAIL) &&
(hr != E_INVALIDARG) &&
(hr != VFW_E_TYPE_NOT_ACCEPTED)) {
    hrFailure = hr;
    }
}
    }

    /* Having failed with that try our enumerator */

    hr = EnumMediaTypes(&pEnumMediaTypes);
    if (SUCCEEDED(hr)) {
ASSERT(pEnumMediaTypes);
hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);
pEnumMediaTypes->Release();
if (SUCCEEDED(hr)) {
    return NOERROR;
} else {
    // try to remember specific error codes if there are any
    if ((hr != E_FAIL) &&
(hr != E_INVALIDARG) &&
(hr != VFW_E_TYPE_NOT_ACCEPTED)) {
    hrFailure = hr;
    }
}
    }
    return hrFailure;
}

/* Called when we want to complete a connection to another filter. Failing
   this will also fail the connection and disconnect the other pin as well */

HRESULT
CBasePin::CompleteConnect(IPin *pReceivePin)
{
    UNREFERENCED_PARAMETER(pReceivePin);
    return NOERROR;
}

/* This is called to set the format for a pin connection - CheckMediaType
   will have been called to check the connection format and if it didn't
   return an error code then this (virtual) function will be invoked */

HRESULT
CBasePin::SetMediaType(const CMediaType *pmt)
{
    m_mt = *pmt;
    return NOERROR;
}

/* This is called during Connect() to provide a virtual method that can do
   any specific check needed for connection such as QueryInterface. This
   base class method just checks that the pin directions don't match */

HRESULT
CBasePin::CheckConnect(IPin * pPin)
{
    /* Check that pin directions DONT match */

    PIN_DIRECTION pd;
    pPin->QueryDirection(&pd);

    ASSERT((pd == PINDIR_OUTPUT) || (pd == PINDIR_INPUT));
    ASSERT((m_dir == PINDIR_OUTPUT) || (m_dir == PINDIR_INPUT));

    // we should allow for non-input and non-output connections?
    if (pd == m_dir) {
return VFW_E_INVALID_DIRECTION;
    }
    return NOERROR;
}

/* This is called when we realise we can't make a connection to the pin and
   must undo anything we did in CheckConnect - override to release QIs done */

HRESULT
CBasePin::BreakConnect()
{
    return NOERROR;
}

/* Called normally by an output pin on an input pin to try and establish a
   connection.
*/

STDMETHODIMP
CBasePin::ReceiveConnection(
    IPin * pConnector,      // this is the pin who we will connect to
    const AM_MEDIA_TYPE *pmt    // this is the media type we will exchange
)
{
    CheckPointer(pConnector,E_POINTER);
    CheckPointer(pmt,E_POINTER);
    ValidateReadPtr(pConnector,sizeof(IPin));
    ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE));
    CAutoLock cObjectLock(m_pLock);

    /* Are we already connected */
    if (m_Connected) {
return VFW_E_ALREADY_CONNECTED;
    }

    /* See if the filter is active */
    if (!IsStopped()) {
        return VFW_E_NOT_STOPPED;
    }

    HRESULT hr = CheckConnect(pConnector);
    if (FAILED(hr)) {
BreakConnect();
return hr;
    }

    /* Ask derived class if this media type is ok */

    CMediaType * pcmt = (CMediaType*) pmt;
    hr = CheckMediaType(pcmt);
    if (hr != NOERROR) {
// no -we don't support this media type
BreakConnect();

// return a specific media type error if there is one
// or map a general failure code to something more helpful
// (in particular S_FALSE gets changed to an error code)
if (SUCCEEDED(hr) ||
    (hr == E_FAIL) ||
    (hr == E_INVALIDARG)) {
hr = VFW_E_TYPE_NOT_ACCEPTED;
}
return hr;
    }

    /* Complete the connection */

    m_Connected = pConnector;
    m_Connected->AddRef();
    SetMediaType(pcmt);

    hr = CompleteConnect(pConnector);
    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to complete connection")));
        m_Connected->Release();
        m_Connected = NULL;
        BreakConnect();
        return hr;
    }
    return NOERROR;
}

/* Called when we want to terminate a pin connection */

STDMETHODIMP
CBasePin::Disconnect()
{
    CAutoLock cObjectLock(m_pLock);

    /* See if the filter is active */
    if (!IsStopped()) {
        return VFW_E_NOT_STOPPED;
    }

    if (m_Connected) {
BreakConnect();
m_Connected->Release();
m_Connected = NULL;
return S_OK;
    } else {
// no connection - not an error
return S_FALSE;
    }
}

/* Return an AddRef()'d pointer to the connected pin if there is one */
STDMETHODIMP
CBasePin::ConnectedTo(
    IPin **ppPin
)
{
    CheckPointer(ppPin,E_POINTER);
    ValidateReadWritePtr(ppPin,sizeof(IPin *));
    //
    //  It's pointless to lock here.
    //  The caller should ensure integrity.
    //

    IPin *pPin = m_Connected;
    *ppPin = pPin;
    if (pPin != NULL) {
        pPin->AddRef();
        return S_OK;
    } else {
        ASSERT(*ppPin == NULL);
        return VFW_E_NOT_CONNECTED;
    }
}

/* Return the media type of the connection */
STDMETHODIMP
CBasePin::ConnectionMediaType(
    AM_MEDIA_TYPE *pmt
)
{
    CheckPointer(pmt,E_POINTER);
    ValidateReadWritePtr(pmt,sizeof(AM_MEDIA_TYPE));
    CAutoLock cObjectLock(m_pLock);

    /*  Copy constructor of m_mt allocates the memory */
    if (IsConnected()) {
        CopyMediaType( pmt, &m_mt );
        return S_OK;
    } else {
        ((CMediaType *)pmt)->InitMediaType();
        return VFW_E_NOT_CONNECTED;
    }
}

/* Return information about the filter we are connect to */

STDMETHODIMP
CBasePin::QueryPinInfo(
    PIN_INFO * pInfo
)
{
    CheckPointer(pInfo,E_POINTER);
    ValidateReadWritePtr(pInfo,sizeof(PIN_INFO));

    pInfo->pFilter = m_pFilter;
    if (m_pFilter) {
m_pFilter->AddRef();
    }

    if (m_pName) {
lstrcpyW(pInfo->achName, m_pName);
    } else {
pInfo->achName[0] = L'\0';
    }

    pInfo->dir = m_dir;

    return NOERROR;
}

STDMETHODIMP
CBasePin::QueryDirection(
    PIN_DIRECTION * pPinDir
)
{
    CheckPointer(pPinDir,E_POINTER);
    ValidateReadWritePtr(pPinDir,sizeof(PIN_DIRECTION));

    *pPinDir = m_dir;
    return NOERROR;
}

// Default QueryId to return the pin's name
STDMETHODIMP
CBasePin::QueryId(
    LPWSTR * Id
)
{
    //  We're not going away because someone's got a pointer to us
    //  so there's no need to lock

    return AMGetWideString(Name(), Id);
}

/* Does this pin support this media type WARNING this interface function does
   not lock the main object as it is meant to be asynchronous by nature - if
   the media types you support depend on some internal state that is updated
   dynamically then you will need to implement locking in a derived class */

STDMETHODIMP
CBasePin::QueryAccept(
    const AM_MEDIA_TYPE *pmt
)
{
    CheckPointer(pmt,E_POINTER);
    ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE));

    /* The CheckMediaType method is valid to return error codes if the media
       type is horrible, an example might be E_INVALIDARG. What we do here
       is map all the error codes into either S_OK or S_FALSE regardless */

    HRESULT hr = CheckMediaType((CMediaType*)pmt);
    if (FAILED(hr)) {
        return S_FALSE;
    }
    // note that the only defined success codes should be S_OK and S_FALSE...
    return hr;
}

/* This can be called to return an enumerator for the pin's list of preferred
   media types. An input pin is not obliged to have any preferred formats
   although it can do. For example, the window renderer has a preferred type
   which describes a video image that matches the current window size. All
   output pins should expose at least one preferred format otherwise it is

possible that neither pin has any types and so no connection is possible */

STDMETHODIMP
CBasePin::EnumMediaTypes(
    IEnumMediaTypes **ppEnum
)
{
    CheckPointer(ppEnum,E_POINTER);
    ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *));

    /* Create a new ref counted enumerator */

    *ppEnum = new CEnumMediaTypes(this,
                          NULL);

    if (*ppEnum == NULL) {
return E_OUTOFMEMORY;
    }

    return NOERROR;
}

/* This is a virtual function that returns a media type corresponding with
   place iPosition in the list. This base class simply returns an error as
   we support no media types by default but derived classes should override */

HRESULT CBasePin::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    UNREFERENCED_PARAMETER(iPosition);
    UNREFERENCED_PARAMETER(pMediaType);
    return E_UNEXPECTED;
}

/* This is a virtual function that returns the current media type version.
   The base class initialises the media type enumerators with the value 1
   By default we always returns that same value. A Derived class may change
   the list of media types available and after doing so it should increment
   the version either in a method derived from this, or more simply by just
   incrementing the m_TypeVersion base pin variable. The type enumerators
   call this when they want to see if their enumerations are out of date */

LONG CBasePin::GetMediaTypeVersion()
{
    return m_TypeVersion;
}

/* Increment the cookie representing the current media type version */

void CBasePin::IncrementTypeVersion()
{
    InterlockedIncrement(&m_TypeVersion);
}

/* Called by IMediaFilter implementation when the state changes from Stopped
   to either paused or running and in derived classes could do things like
   commit memory and grab hardware resource (the default is to do nothing) */

HRESULT
CBasePin::Active(void)
{
    return NOERROR;
}

/* Called by IMediaFilter implementation when the state changes from
   to either paused to running and in derived classes could do things like
   commit memory and grab hardware resource (the default is to do nothing) */

HRESULT
CBasePin::Run(REFERENCE_TIME tStart)
{
    UNREFERENCED_PARAMETER(tStart);
    return NOERROR;
}

/* Also called by the IMediaFilter implementation when the state changes to
   Stopped at which point you should decommit allocators and free hardware
   resources you grabbed in the Active call (default is also to do nothing) */

HRESULT
CBasePin::Inactive(void)
{
    m_bRunTimeError = FALSE;
    return NOERROR;
}

// Called when no more data will arrive
STDMETHODIMP
CBasePin::EndOfStream(void)
{
    return S_FALSE;
}

STDMETHODIMP
CBasePin::SetSink(IQualityControl * piqc)
{
    CAutoLock cObjectLock(m_pLock);
    if (piqc) ValidateReadPtr(piqc,sizeof(IQualityControl));
    m_pQSink = piqc;
    return NOERROR;
} // SetSink

STDMETHODIMP
CBasePin::Notify(IBaseFilter * pSender, Quality q)
{
    UNREFERENCED_PARAMETER(q);
    UNREFERENCED_PARAMETER(pSender);
    DbgBreak("IQualityControl::Notify not over-ridden from CBasePin.  (IGNORE is OK)");
    return E_NOTIMPL;
} //Notify

// NewSegment notifies of the start/stop/rate applying to the data
// about to be received. Default implementation records data and
// returns S_OK.
// Override this to pass downstream.
STDMETHODIMP
CBasePin::NewSegment(
                REFERENCE_TIME tStart,
                REFERENCE_TIME tStop,
                double dRate)
{
    m_tStart = tStart;
    m_tStop = tStop;
    m_dRate = dRate;

    return S_OK;
}

//=====================================================================
//=====================================================================
// Implements CBaseOutputPin
//=====================================================================
//=====================================================================

CBaseOutputPin::CBaseOutputPin(TCHAR *pObjectName,
       CBaseFilter *pFilter,
       CCritSec *pLock,
       HRESULT *phr,
       LPCWSTR pName) :
    CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT),
    m_pAllocator(NULL),
    m_pInputPin(NULL)
{
    ASSERT(pFilter);
}

/* Since an input pin connected to us holds a reference count on us we will
   never be deleted unless all connections have already been terminated */

#if 0
CBaseOutputPin::~CBaseOutputPin()
{
}
#endif

/*   This is called after a media type has been proposed

     Try to complete the connection by agreeing the allocator
*/
HRESULT
CBaseOutputPin::CompleteConnect(IPin *pReceivePin)
{
    UNREFERENCED_PARAMETER(pReceivePin);
    return DecideAllocator(m_pInputPin, &m_pAllocator);
}

/* This method is called when the output pin is about to try and connect to
   an input pin. It is at this point that you should try and grab any extra
   interfaces that you need, in this case IMemInputPin. Because this is
   only called if we are not currently connected we do NOT need to call
   BreakConnect. This also makes it easier to derive classes from us as
   BreakConnect is only called when we actually have to break a connection
   (or a partly made connection) and not when we are checking a connection */

/* Overriden from CBasePin */

HRESULT
CBaseOutputPin::CheckConnect(IPin * pPin)
{
    HRESULT hr = CBasePin::CheckConnect(pPin);
    if (FAILED(hr)) {
return hr;
    }

    // get an input pin and an allocator interface
    hr = pPin->QueryInterface(IID_IMemInputPin, (void **) &m_pInputPin);
    if (FAILED(hr)) {
return hr;
    }
    return NOERROR;
}

/* Overriden from CBasePin */

HRESULT
CBaseOutputPin::BreakConnect()
{
    /* Release any allocator we hold */

    if (m_pAllocator) {
m_pAllocator->Release();
m_pAllocator = NULL;
    }

    /* Release any input pin interface we hold */

    if (m_pInputPin) {
m_pInputPin->Release();
m_pInputPin = NULL;
    }
    return NOERROR;
}

/* This is called when the input pin didn't give us a valid allocator */

HRESULT
CBaseOutputPin::InitAllocator(IMemAllocator **ppAlloc)
{
#if 0
    HRESULT hr = NOERROR;
    *ppAlloc = NULL;
    CMemAllocator *pMemObject = NULL;

    /* Create a default memory allocator */

    pMemObject = new CMemAllocator(NAME("Base memory allocator"),NULL, &hr);
    if (pMemObject == NULL) {
return E_OUTOFMEMORY;
    }

    if (FAILED(hr)) {
delete pMemObject;
return hr;
    }

    /* Get a reference counted IID_IMemAllocator interface */

    EXECUTE_ASSERT(SUCCEEDED(pMemObject->QueryInterface(IID_IMemAllocator,(void **)ppAlloc)));

    ASSERT(*ppAlloc != NULL);
    return NOERROR;
#else
    return CreateMemoryAllocator(ppAlloc);
#endif
}

/* Decide on an allocator, override this if you want to use your own allocator
   Override DecideBufferSize to call SetProperties. If the input pin fails
   the GetAllocator call then this will construct a CMemAllocator and call
   DecideBufferSize on that, and if that fails then we are completely hosed.
   If the you succeed the DecideBufferSize call, we will notify the input
   pin of the selected allocator. NOTE this is called during Connect() which
   therefore looks after grabbing and locking the object's critical section */

// We query the input pin for its requested properties and pass this to
// DecideBufferSize to allow it to fulfill requests that it is happy
// with (eg most people don't care about alignment and are thus happy to
// use the downstream pin's alignment request).

HRESULT
CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, IMemAllocator **ppAlloc)
{
    HRESULT hr = NOERROR;
    *ppAlloc = NULL;

    // get downstream prop request
    // the derived class may modify this in DecideBufferSize, but
    // we assume that he will consistently modify it the same way,
    // so we only get it once
    ALLOCATOR_PROPERTIES prop;
    ZeroMemory(&prop, sizeof(prop));

    // whatever he returns, we assume prop is either all zeros
    // or he has filled it out.
    pPin->GetAllocatorRequirements(&prop);

    // if he doesn't care about alignment, then set it to 1
    if (prop.cbAlign == 0) {
        prop.cbAlign = 1;
    }

    /* Try the allocator provided by the input pin */

    hr = pPin->GetAllocator(ppAlloc);
    if (SUCCEEDED(hr)) {

hr = DecideBufferSize(*ppAlloc, &prop);
if (SUCCEEDED(hr)) {
    hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
    if (SUCCEEDED(hr)) {
return NOERROR;
    }
}
    }

    /* If the GetAllocator failed we may not have an interface */

    if (*ppAlloc) {
(*ppAlloc)->Release();
*ppAlloc = NULL;
    }

    /* Try the output pin's allocator by the same method */

    hr = InitAllocator(ppAlloc);
    if (SUCCEEDED(hr)) {

        // note - the properties passed here are in the same
        // structure as above and may have been modified by
        // the previous call to DecideBufferSize
hr = DecideBufferSize(*ppAlloc, &prop);
if (SUCCEEDED(hr)) {
    hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
    if (SUCCEEDED(hr)) {
return NOERROR;
    }
}
    }

    /* Likewise we may not have an interface to release */

    if (*ppAlloc) {
(*ppAlloc)->Release();
*ppAlloc = NULL;
    }
    return hr;
}

/* This returns an empty sample buffer from the allocator WARNING the same
   dangers and restrictions apply here as described below for Deliver() */

HRESULT
CBaseOutputPin::GetDeliveryBuffer(IMediaSample ** ppSample,
                                  REFERENCE_TIME * pStartTime,
                                  REFERENCE_TIME * pEndTime,
                                  DWORD dwFlags)
{
    if (m_pAllocator != NULL) {
        return m_pAllocator->GetBuffer(ppSample,pStartTime,pEndTime,dwFlags);
    } else {
        return E_NOINTERFACE;
    }
}

/* Deliver a filled-in sample to the connected input pin. NOTE the object must
   have locked itself before calling us otherwise we may get halfway through
   executing this method only to find the filter graph has got in and
   disconnected us from the input pin. If the filter has no worker threads
   then the lock is best applied on Receive(), otherwise it should be done
   when the worker thread is ready to deliver. There is a wee snag to worker
   threads that this shows up. The worker thread must lock the object when
   it is ready to deliver a sample, but it may have to wait until a state
   change has completed, but that may never complete because the state change
   is waiting for the worker thread to complete. The way to handle this is for
   the state change code to grab the critical section, then set an abort event
   for the worker thread, then release the critical section and wait for the
   worker thread to see the event we set and then signal that it has finished
   (with another event). At which point the state change code can complete */

// note (if you've still got any breath left after reading that) that you
// need to release the sample yourself after this call. if the connected
// input pin needs to hold onto the sample beyond the call, it will addref
// the sample itself.

// of course you must release this one and call GetDeliveryBuffer for the
// next. You cannot reuse it directly.

HRESULT
CBaseOutputPin::Deliver(IMediaSample * pSample)
{
    if (m_pInputPin == NULL) {
return VFW_E_NOT_CONNECTED;
    }
    return m_pInputPin->Receive(pSample);
}

// called from elsewhere in our filter to pass EOS downstream to
// our connected input pin
HRESULT
CBaseOutputPin::DeliverEndOfStream(void)
{
    // remember this is on IPin not IMemInputPin
    if (m_Connected == NULL) {
        return VFW_E_NOT_CONNECTED;
    }
    return m_Connected->EndOfStream();
}

/* Commit the allocator's memory, this is called through IMediaFilter
   which is responsible for locking the object before calling us */

HRESULT
CBaseOutputPin::Active(void)
{
    if (m_pAllocator == NULL) {
return VFW_E_NO_ALLOCATOR;
    }
    return m_pAllocator->Commit();
}

/* Free up or unprepare allocator's memory, this is called through
   IMediaFilter which is responsible for locking the object first */

HRESULT
CBaseOutputPin::Inactive(void)
{
    m_bRunTimeError = FALSE;
    if (m_pAllocator == NULL) {
return VFW_E_NO_ALLOCATOR;
    }
    return m_pAllocator->Decommit();
}

// we have a default handling of EndOfStream which is to return
// an error, since this should be called on input pins only
STDMETHODIMP
CBaseOutputPin::EndOfStream(void)
{
    return E_UNEXPECTED;
}

// BeginFlush should be called on input pins only
STDMETHODIMP
CBaseOutputPin::BeginFlush(void)
{
    return E_UNEXPECTED;
}

// EndFlush should be called on input pins only
STDMETHODIMP
CBaseOutputPin::EndFlush(void)
{
    return E_UNEXPECTED;
}

// call BeginFlush on the connected input pin
HRESULT
CBaseOutputPin::DeliverBeginFlush(void)
{
    // remember this is on IPin not IMemInputPin
    if (m_Connected == NULL) {
        return VFW_E_NOT_CONNECTED;
    }
    return m_Connected->BeginFlush();
}

// call EndFlush on the connected input pin
HRESULT
CBaseOutputPin::DeliverEndFlush(void)
{
    // remember this is on IPin not IMemInputPin
    if (m_Connected == NULL) {
        return VFW_E_NOT_CONNECTED;
    }
    return m_Connected->EndFlush();
}
// deliver NewSegment to connected pin
HRESULT
CBaseOutputPin::DeliverNewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate)
{
    if (m_Connected == NULL) {
        return VFW_E_NOT_CONNECTED;
    }
    return m_Connected->NewSegment(tStart, tStop, dRate);
}

//=====================================================================
//=====================================================================
// Implements CBaseInputPin
//=====================================================================
//=====================================================================

/* Constructor creates a default allocator object */

CBaseInputPin::CBaseInputPin(TCHAR *pObjectName,
     CBaseFilter *pFilter,
     CCritSec *pLock,
     HRESULT *phr,
     LPCWSTR pPinName) :
    CBasePin(pObjectName, pFilter, pLock, phr, pPinName, PINDIR_INPUT),
    m_pAllocator(NULL),
    m_bReadOnly(FALSE),
    m_bFlushing(FALSE)
{
    ZeroMemory(&m_SampleProps, sizeof(m_SampleProps));
}

/* Destructor releases it's reference count on the default allocator */

CBaseInputPin::~CBaseInputPin()
{
    if (m_pAllocator != NULL) {
m_pAllocator->Release();
m_pAllocator = NULL;
    }
}

// override this to publicise our interfaces
STDMETHODIMP
CBaseInputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    /* Do we know about this interface */

    if (riid == IID_IMemInputPin) {
return GetInterface((IMemInputPin *) this, ppv);
    } else {
return CBasePin::NonDelegatingQueryInterface(riid, ppv);
    }
}

/* Return the allocator interface that this input pin would like the output
   pin to use. NOTE subsequent calls to GetAllocator should all return an
   interface onto the SAME object so we create one object at the start

   Note:
       The allocator is Release()'d on disconnect and replaced on
       NotifyAllocator().

   Override this to provide your own allocator.
*/

STDMETHODIMP
CBaseInputPin::GetAllocator(
    IMemAllocator **ppAllocator)
{
    CheckPointer(ppAllocator,E_POINTER);
    ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *));
    CAutoLock cObjectLock(m_pLock);

    if (m_pAllocator == NULL) {
#if 0
HRESULT hr = S_OK;

/* Create the new allocator object */

CMemAllocator *pMemObject = new CMemAllocator(NAME("Default memory allocator"),
      NULL, &hr);
if (pMemObject == NULL) {
    return E_OUTOFMEMORY;
}

if (FAILED(hr)) {
    ASSERT(pMemObject);
    delete pMemObject;
    return hr;
}

        m_pAllocator = pMemObject;

        /*  We AddRef() our own allocator */
        m_pAllocator->AddRef();
#else
        HRESULT hr = CreateMemoryAllocator(&m_pAllocator);
        if (FAILED(hr)) {
            return hr;
        }
#endif
    }
    ASSERT(m_pAllocator != NULL);
    *ppAllocator = m_pAllocator;
    m_pAllocator->AddRef();
    return NOERROR;
}

/* Tell the input pin which allocator the output pin is actually going to use
   Override this if you care - NOTE the locking we do both here and also in
   GetAllocator is unnecessary but derived classes that do something useful
   will undoubtedly have to lock the object so this might help remind people */

STDMETHODIMP
CBaseInputPin::NotifyAllocator(
    IMemAllocator * pAllocator,
    BOOL bReadOnly)
{
    CheckPointer(pAllocator,E_POINTER);
    ValidateReadPtr(pAllocator,sizeof(IMemAllocator));
    CAutoLock cObjectLock(m_pLock);

    IMemAllocator *pOldAllocator = m_pAllocator;
    pAllocator->AddRef();
    m_pAllocator = pAllocator;

    if (pOldAllocator != NULL) {
pOldAllocator->Release();
    }

    // the readonly flag indicates whether samples from this allocator should
    // be regarded as readonly - if true, then inplace transforms will not be
    // allowed.
    m_bReadOnly = bReadOnly;
    return NOERROR;
}

/*  Disconnect */

STDMETHODIMP CBaseInputPin::Disconnect()
{
    CAutoLock cObjectLock(m_pLock);

    /*  Call base class first because it calls Inactive() for us */
    HRESULT hr = CBasePin::Disconnect();

    if (S_OK == hr) {
        /* We don't need our allocator any more */
        if (m_pAllocator) {
            m_pAllocator->Release();
            m_pAllocator = NULL;
        }
    }
    return hr;
}

/* Do something with this media sample - this base class checks to see if the
   format has changed with this media sample and if so checks that the filter
   will accept it, generating a run time error if not. Once we have raised a
   run time error we set a flag so that no more samples will be accepted

   It is important that any filter should override this method and implement
   synchronization so that samples are not processed when the pin is
   disconnected etc
*/

STDMETHODIMP
CBaseInputPin::Receive(IMediaSample *pSample)
{
    CheckPointer(pSample,E_POINTER);
    ValidateReadPtr(pSample,sizeof(IMediaSample));
    AM_MEDIA_TYPE *pmt = NULL;
    ASSERT(pSample);

    HRESULT hr = CheckStreaming();
    if (S_OK != hr) {
        return hr;
    }

    /* Check for IMediaSample2 */
    IMediaSample2 *pSample2;
    if (SUCCEEDED(pSample->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) {
        hr = pSample2->GetProperties(sizeof(m_SampleProps), (PBYTE)&m_SampleProps);
        pSample2->Release();
        if (FAILED(hr)) {
            return hr;
        }
    } else {
        /*  Get the properties the hard way */
        m_SampleProps.cbData = sizeof(m_SampleProps);
        m_SampleProps.dwTypeSpecificFlags = 0;
        m_SampleProps.dwStreamId = AM_STREAM_MEDIA;
        m_SampleProps.dwSampleFlags = 0;
        if (S_OK == pSample->IsDiscontinuity()) {
            m_SampleProps.dwSampleFlags |= AM_SAMPLE_DATADISCONTINUITY;
        }
        if (S_OK == pSample->IsPreroll()) {
            m_SampleProps.dwSampleFlags |= AM_SAMPLE_PREROLL;
        }
        if (S_OK == pSample->IsSyncPoint()) {
            m_SampleProps.dwSampleFlags |= AM_SAMPLE_SPLICEPOINT;
        }
        if (SUCCEEDED(pSample->GetTime(&m_SampleProps.tStart,
                                       &m_SampleProps.tStop))) {
            m_SampleProps.dwSampleFlags |= AM_SAMPLE_TIMEVALID |
                                           AM_SAMPLE_STOPVALID;
        }
        if (S_OK == pSample->GetMediaType(&m_SampleProps.pMediaType)) {
            m_SampleProps.dwSampleFlags |= AM_SAMPLE_TYPECHANGED;
        }
        pSample->GetPointer(&m_SampleProps.pbBuffer);
        m_SampleProps.lActual = pSample->GetActualDataLength();
        m_SampleProps.cbBuffer = pSample->GetSize();
    }

    /* Has the format changed in this sample */

    if (!(m_SampleProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED)) {
return NOERROR;
    }

    /* Check the derived class accepts this format */
    /* This shouldn't fail as the source must call QueryAccept first */

    hr = CheckMediaType((CMediaType *)m_SampleProps.pMediaType);

    if (hr == NOERROR) {
        return NOERROR;
    }

    /* Raise a runtime error if we fail the media type */

    m_bRunTimeError = TRUE;
    EndOfStream();
    m_pFilter->NotifyEvent(EC_ERRORABORT,VFW_E_TYPE_NOT_ACCEPTED,0);
    return VFW_E_INVALIDMEDIATYPE;
}

/*  Receive multiple samples */
STDMETHODIMP
CBaseInputPin::ReceiveMultiple (
    IMediaSample **pSamples,
    long nSamples,
    long *nSamplesProcessed)
{
    CheckPointer(pSamples,E_POINTER);
    ValidateReadPtr(pSamples,nSamples * sizeof(IMediaSample *));

    HRESULT hr = S_OK;
    *nSamplesProcessed = 0;
    while (nSamples-- > 0) {
         hr = Receive(pSamples[*nSamplesProcessed]);

         /*  S_FALSE means don't send any more */
         if (hr != S_OK) {
             break;
         }
         (*nSamplesProcessed)++;
    }
    return hr;
}

/*  See if Receive() might block */
STDMETHODIMP
CBaseInputPin::ReceiveCanBlock()
{
    /*  Ask all the output pins if they block
        If there are no output pin assume we do block
    */
    int cPins = m_pFilter->GetPinCount();
    int cOutputPins = 0;
    for (int c = 0; c < cPins; c++) {
        CBasePin *pPin = m_pFilter->GetPin(c);
        PIN_DIRECTION pd;
        HRESULT hr = pPin->QueryDirection(&pd);
        if (FAILED(hr)) {
            return hr;
        }

        if (pd == PINDIR_OUTPUT) {
            IPin *pConnected;
            hr = pPin->ConnectedTo(&pConnected);
            if (SUCCEEDED(hr)) {
                ASSERT(pConnected != NULL);
                cOutputPins++;
                IMemInputPin *pInputPin;
                hr = pConnected->QueryInterface(
                                              IID_IMemInputPin,
                                              (void **)&pInputPin);
                pConnected->Release();
                if (SUCCEEDED(hr)) {
                    hr = pInputPin->ReceiveCanBlock();
                    pInputPin->Release();
                    if (hr != S_FALSE) {
                        return S_OK;
                    }
                } else {
                    /*  There's a transport we don't understand here */
                    return S_OK;
                }
            }
        }
    }
    return cOutputPins == 0 ? S_OK : S_FALSE;
}

// Default handling for BeginFlush - call at the beginning
// of your implementation (makes sure that all Receive calls
// fail). After calling this, you need to free any queued data
// and then call downstream.
STDMETHODIMP
CBaseInputPin::BeginFlush(void)
{
    //  BeginFlush is NOT synchronized with streaming but is part of
    //  a control action - hence we synchronize with the filter
    CAutoLock lck(m_pLock);

    // if we are already in mid-flush, this is probably a mistake
    // though not harmful - try to pick it up for now so I can think about it
    ASSERT(!m_bFlushing);

    // first thing to do is ensure that no further Receive calls succeed
    m_bFlushing = TRUE;

    // now discard any data and call downstream - must do that
    // in derived classes
    return S_OK;
}

// default handling for EndFlush - call at end of your implementation
// - before calling this, ensure that there is no queued data and no thread
// pushing any more without a further receive, then call downstream,
// then call this method to clear the m_bFlushing flag and re-enable
// receives
STDMETHODIMP
CBaseInputPin::EndFlush(void)
{
    //  Endlush is NOT synchronized with streaming but is part of
    //  a control action - hence we synchronize with the filter
    CAutoLock lck(m_pLock);

    // almost certainly a mistake if we are not in mid-flush
    ASSERT(m_bFlushing);

    // before calling, sync with pushing thread and ensure
    // no more data is going downstream, then call EndFlush on
    // downstream pins.

    // now re-enable Receives
    m_bFlushing = FALSE;

    return S_OK;
}

STDMETHODIMP
CBaseInputPin::Notify(IBaseFilter * pSender, Quality q)
{
    UNREFERENCED_PARAMETER(q);
    CheckPointer(pSender,E_POINTER);
    ValidateReadPtr(pSender,sizeof(IBaseFilter));
    DbgBreak("IQuality::Notify called on an input pin");
    return NOERROR;
} // Notify

/* Free up or unprepare allocator's memory, this is called through
   IMediaFilter which is responsible for locking the object first */

HRESULT
CBaseInputPin::Inactive(void)
{
    m_bRunTimeError = FALSE;
    if (m_pAllocator == NULL) {
return VFW_E_NO_ALLOCATOR;
    }

    m_bFlushing = FALSE;

    return m_pAllocator->Decommit();
}

// what requirements do we have of the allocator - override if you want
// to support other people's allocators but need a specific alignment
// or prefix.
STDMETHODIMP
CBaseInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES*pProps)
{
    UNREFERENCED_PARAMETER(pProps);
    return E_NOTIMPL;
}

//  Check if it's OK to process data
//
HRESULT
CBaseInputPin::CheckStreaming()
{
    //  Shouldn't be able to get any data if we're not connected!
    ASSERT(IsConnected());

    //  Don't process stuff in Stopped state
    if (IsStopped()) {
        return VFW_E_WRONG_STATE;
    }
    if (m_bFlushing) {
        return S_FALSE;
    }
    if (m_bRunTimeError) {
return VFW_E_RUNTIME_ERROR;
    }
    return S_OK;
}

// Pass on the Quality notification q to
// a. Our QualityControl sink (if we have one) or else
// b. to our upstream filter
// and if that doesn't work, throw it away with a bad return code
HRESULT
CBaseInputPin::PassNotify(Quality& q)
{
    // We pass the message on, which means that we find the quality sink
    // for our input pin and send it there

    DbgLog((LOG_TRACE,3,TEXT("Passing Quality notification through transform")));
    if (m_pQSink!=NULL) {
        return m_pQSink->Notify(m_pFilter, q);
    } else {
        // no sink set, so pass it upstream
        HRESULT hr;
        IQualityControl * pIQC;

        hr = VFW_E_NOT_FOUND;                   // default
        if (m_Connected) {
            m_Connected->QueryInterface(IID_IQualityControl, (void**)&pIQC);

            if (pIQC!=NULL) {
                hr = pIQC->Notify(m_pFilter, q);
                pIQC->Release();
            }
        }
        return hr;
    }

} // PassNotify

//=====================================================================
//=====================================================================
// Memory allocation class, implements CMediaSample
//=====================================================================
//=====================================================================

/* NOTE The implementation of this class calls the CUnknown constructor with
   a NULL outer unknown pointer. This has the effect of making us a self
   contained class, ie any QueryInterface, AddRef or Release calls will be
   routed to the class's NonDelegatingUnknown methods. You will typically
   find that the classes that do this then override one or more of these
   virtual functions to provide more specialised behaviour. A good example
   of this is where a class wants to keep the QueryInterface internal but
   still wants it's lifetime controlled by the external object */

/* The last two parameters have default values of NULL and zero */

CMediaSample::CMediaSample(TCHAR *pName,
   CBaseAllocator *pAllocator,
   HRESULT *phr,
   LPBYTE pBuffer,
   LONG length) :
    m_pBuffer(pBuffer),             // Initialise the buffer
    m_cbBuffer(length),             // And it's length
    m_lActual(length),              // By default, actual = length
    m_pMediaType(NULL),             // No media type change
    m_dwFlags(0),                   // Nothing set
    m_cRef(0),                      // 0 ref count
    m_dwTypeSpecificFlags(0),       // Type specific flags
    m_dwStreamId(AM_STREAM_MEDIA),  // Stream id
    m_pAllocator(pAllocator)        // Allocator
{
    /* We must have an owner and it must also be derived from class
       CBaseAllocator BUT we do not hold a reference count on it */

    ASSERT(pAllocator);
}

/* Destructor deletes the media type memory */

CMediaSample::~CMediaSample()
{
    if (m_pMediaType) {
DeleteMediaType(m_pMediaType);
    }
}

/* Override this to publicise our interfaces */

STDMETHODIMP
CMediaSample::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_IMediaSample ||
        riid == IID_IMediaSample2 ||
        riid == IID_IUnknown) {
return GetInterface((IMediaSample *) this, ppv);
    } else {
return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG)
CMediaSample::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

// --  CMediaSample lifetimes --
//
// On final release of this sample buffer it is not deleted but
// returned to the freelist of the owning memory allocator
//
// The allocator may be waiting for the last buffer to be placed on the free
// list in order to decommit all the memory, so the ReleaseBuffer() call may
// result in this sample being deleted. We also need to hold a refcount on
// the allocator to stop that going away until we have finished with this.
// However, we cannot release the allocator before the ReleaseBuffer, as the
// release may cause us to be deleted. Similarly we can't do it afterwards.
//
// Thus we must leave it to the allocator to hold an addref on our behalf.
// When he issues us in GetBuffer, he addref's himself. When ReleaseBuffer
// is called, he releases himself, possibly causing us and him to be deleted.

STDMETHODIMP_(ULONG)
CMediaSample::Release()
{
    /* Decrement our own private reference count */
    LONG lRef;
    if (m_cRef == 1) {
        lRef = 0;
        m_cRef = 0;
    } else {
        lRef = InterlockedDecrement(&m_cRef);
    }
    ASSERT(lRef >= 0);

    DbgLog((LOG_MEMORY,3,TEXT("    Unknown %X ref-- = %d"),
    this, m_cRef));

    /* Did we release our final reference count */
    if (lRef == 0) {
        /* Free all resources */
        if (m_dwFlags & Sample_TypeChanged) {
            SetMediaType(NULL);
        }
        ASSERT(m_pMediaType == NULL);
        m_dwFlags = 0;
        m_dwTypeSpecificFlags = 0;
        m_dwStreamId = AM_STREAM_MEDIA;

        /* This may cause us to be deleted */
        // Our refcount is reliably 0 thus no-one will mess with us
        m_pAllocator->ReleaseBuffer(this);
    }
    return (ULONG)lRef;
}

// set the buffer pointer and length. Used by allocators that
// want variable sized pointers or pointers into already-read data.
// This is only available through a CMediaSample* not an IMediaSample*
// and so cannot be changed by clients.
HRESULT
CMediaSample::SetPointer(BYTE * ptr, LONG cBytes)
{
    m_pBuffer = ptr;            // new buffer area (could be null)
    m_cbBuffer = cBytes;        // length of buffer
    m_lActual = cBytes;         // length of data in buffer (assume full)

    return S_OK;
}

// get me a read/write pointer to this buffer's memory. I will actually
// want to use sizeUsed bytes.
STDMETHODIMP
CMediaSample::GetPointer(BYTE ** ppBuffer)
{
    ValidateReadWritePtr(ppBuffer,sizeof(BYTE *));

    // creator must have set pointer either during
    // constructor or by SetPointer
    ASSERT(m_pBuffer);

    *ppBuffer = m_pBuffer;
    return NOERROR;
}

// return the size in bytes of this buffer
STDMETHODIMP_(LONG)
CMediaSample::GetSize(void)
{
    return m_cbBuffer;
}

// get the stream time at which this sample should start and finish.
STDMETHODIMP
CMediaSample::GetTime(
    REFERENCE_TIME * pTimeStart,     // put time here
    REFERENCE_TIME * pTimeEnd
)
{
    ValidateReadWritePtr(pTimeStart,sizeof(REFERENCE_TIME));
    ValidateReadWritePtr(pTimeEnd,sizeof(REFERENCE_TIME));

    if (!(m_dwFlags & Sample_StopValid)) {
        if (!(m_dwFlags & Sample_TimeValid)) {
            return VFW_E_SAMPLE_TIME_NOT_SET;
        } else {
            *pTimeStart = m_Start;

            //  Make sure old stuff works
            *pTimeEnd = m_Start + 1;
            return VFW_S_NO_STOP_TIME;
        }
    }

    *pTimeStart = m_Start;
    *pTimeEnd = m_End;
    return NOERROR;
}

// Set the stream time at which this sample should start and finish.
// NULL pointers means the time is reset
STDMETHODIMP
CMediaSample::SetTime(
    REFERENCE_TIME * pTimeStart,
    REFERENCE_TIME * pTimeEnd
)
{
    if (pTimeStart == NULL) {
        ASSERT(pTimeEnd == NULL);
        m_dwFlags &= ~(Sample_TimeValid | Sample_StopValid);
    } else {
        if (pTimeEnd == NULL) {
            m_Start = *pTimeStart;
            m_dwFlags |= Sample_TimeValid;
            m_dwFlags &= ~Sample_StopValid;
        } else {
            ValidateReadPtr(pTimeStart,sizeof(REFERENCE_TIME));
            ValidateReadPtr(pTimeEnd,sizeof(REFERENCE_TIME));
            ASSERT(*pTimeEnd >= *pTimeStart);

            m_Start = *pTimeStart;
            m_End = *pTimeEnd;
            m_dwFlags |= Sample_TimeValid | Sample_StopValid;
        }
    }
    return NOERROR;
}

// get the media times (eg bytes) for this sample
STDMETHODIMP
CMediaSample::GetMediaTime(
    LONGLONG * pTimeStart,
    LONGLONG * pTimeEnd
)
{
    ValidateReadWritePtr(pTimeStart,sizeof(LONGLONG));
    ValidateReadWritePtr(pTimeEnd,sizeof(LONGLONG));

    if (!(m_dwFlags & Sample_MediaTimeValid)) {
        return VFW_E_MEDIA_TIME_NOT_SET;
    }

    *pTimeStart = m_MediaStart;
    *pTimeEnd = (m_MediaStart + m_MediaEnd);
    return NOERROR;
}

// Set the media times for this sample
STDMETHODIMP
CMediaSample::SetMediaTime(
    LONGLONG * pTimeStart,
    LONGLONG * pTimeEnd
)
{
    if (pTimeStart == NULL) {
        ASSERT(pTimeEnd == NULL);
        m_dwFlags &= ~Sample_MediaTimeValid;
    } else {
        ValidateReadPtr(pTimeStart,sizeof(LONGLONG));
        ValidateReadPtr(pTimeEnd,sizeof(LONGLONG));
ASSERT(*pTimeEnd >= *pTimeStart);

        m_MediaStart = *pTimeStart;
        m_MediaEnd = (LONG)(*pTimeEnd - *pTimeStart);
        m_dwFlags |= Sample_MediaTimeValid;
    }
    return NOERROR;
}

STDMETHODIMP
CMediaSample::IsSyncPoint(void)
{
    if (m_dwFlags & Sample_SyncPoint) {
return S_OK;
    } else {
return S_FALSE;
    }
}

STDMETHODIMP
CMediaSample::SetSyncPoint(BOOL bIsSyncPoint)
{
    if (bIsSyncPoint) {
        m_dwFlags |= Sample_SyncPoint;
    } else {
        m_dwFlags &= ~Sample_SyncPoint;
    }
    return NOERROR;
}

// returns S_OK if there is a discontinuity in the data (this same is
// not a continuation of the previous stream of data
// - there has been a seek).
STDMETHODIMP
CMediaSample::IsDiscontinuity(void)
{
    if (m_dwFlags & Sample_Discontinuity) {
return S_OK;
    } else {
return S_FALSE;
    }
}

// set the discontinuity property - TRUE if this sample is not a
// continuation, but a new sample after a seek.
STDMETHODIMP
CMediaSample::SetDiscontinuity(BOOL bDiscont)
{
    // should be TRUE or FALSE
    if (bDiscont) {
        m_dwFlags |= Sample_Discontinuity;
    } else {
        m_dwFlags &= ~Sample_Discontinuity;
    }
    return S_OK;
}

STDMETHODIMP
CMediaSample::IsPreroll(void)
{
    if (m_dwFlags & Sample_Preroll) {
return S_OK;
    } else {
return S_FALSE;
    }
}

STDMETHODIMP
CMediaSample::SetPreroll(BOOL bIsPreroll)
{
    if (bIsPreroll) {
        m_dwFlags |= Sample_Preroll;
    } else {
        m_dwFlags &= ~Sample_Preroll;
    }
    return NOERROR;
}

STDMETHODIMP_(LONG)
CMediaSample::GetActualDataLength(void)
{
    return m_lActual;
}

STDMETHODIMP
CMediaSample::SetActualDataLength(LONG lActual)
{
    if (lActual > m_cbBuffer) {
        ASSERT(lActual <= GetSize());
return VFW_E_BUFFER_OVERFLOW;
    }
    m_lActual = lActual;
    return NOERROR;
}

/* These allow for limited format changes in band */

STDMETHODIMP
CMediaSample::GetMediaType(AM_MEDIA_TYPE **ppMediaType)
{
    ValidateReadWritePtr(ppMediaType,sizeof(AM_MEDIA_TYPE *));
    ASSERT(ppMediaType);

    /* Do we have a new media type for them */

    if (!(m_dwFlags & Sample_TypeChanged)) {
ASSERT(m_pMediaType == NULL);
*ppMediaType = NULL;
return S_FALSE;
    }

    ASSERT(m_pMediaType);

    /* Create a copy of our media type */

    *ppMediaType = CreateMediaType(m_pMediaType);
    if (*ppMediaType == NULL) {
return E_OUTOFMEMORY;
    }
    return NOERROR;
}

/* Mark this sample as having a different format type */

STDMETHODIMP
CMediaSample::SetMediaType(AM_MEDIA_TYPE *pMediaType)
{
    /* Delete the current media type */

    if (m_pMediaType) {
DeleteMediaType(m_pMediaType);
m_pMediaType = NULL;
    }

    /* Mechanism for resetting the format type */

    if (pMediaType == NULL) {
        m_dwFlags &= ~Sample_TypeChanged;
return NOERROR;
    }

    ASSERT(pMediaType);
    ValidateReadPtr(pMediaType,sizeof(AM_MEDIA_TYPE));

    /* Take a copy of the media type */

    m_pMediaType = CreateMediaType(pMediaType);
    if (m_pMediaType == NULL) {
        m_dwFlags &= ~Sample_TypeChanged;
return E_OUTOFMEMORY;
    }

    m_dwFlags |= Sample_TypeChanged;
    return NOERROR;
}

// Set and get properties (IMediaSample2)
STDMETHODIMP CMediaSample::GetProperties(
    DWORD cbProperties,
    BYTE * pbProperties
)
{
    if (0 != cbProperties) {
        CheckPointer(pbProperties, E_POINTER);
        //  Return generic stuff up to the length
        AM_SAMPLE2_PROPERTIES Props;
        Props.cbData     = min(cbProperties, sizeof(Props));
        Props.dwSampleFlags = m_dwFlags & ~Sample_MediaTimeValid;
        Props.dwTypeSpecificFlags = m_dwTypeSpecificFlags;
        Props.pbBuffer   = m_pBuffer;
        Props.cbBuffer   = m_cbBuffer;
        Props.lActual    = m_lActual;
        Props.tStart     = m_Start;
        Props.tStop      = m_End;
        Props.dwStreamId = m_dwStreamId;
        if (m_dwFlags & AM_SAMPLE_TYPECHANGED) {
            Props.pMediaType = m_pMediaType;
        } else {
            Props.pMediaType = NULL;
        }
        CopyMemory(pbProperties, &Props, Props.cbData);
    }
    return S_OK;
}

#define CONTAINS_FIELD(type, field, offset) \
    ((FIELD_OFFSET(type, field) + sizeof(((type *)0)->field)) <= offset)

HRESULT CMediaSample::SetProperties(
    DWORD cbProperties,
    const BYTE * pbProperties
)
{

    /*  Generic properties */
    AM_MEDIA_TYPE *pMediaType = NULL;

    if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbData, cbProperties)) {
        CheckPointer(pbProperties, E_POINTER);
        AM_SAMPLE2_PROPERTIES *pProps =
            (AM_SAMPLE2_PROPERTIES *)pbProperties;

        /*  Don't use more data than is actually there */
        if (pProps->cbData < cbProperties) {
            cbProperties = pProps->cbData;
        }
        /*  We only handle IMediaSample2 */
        if (cbProperties > sizeof(*pProps) ||
            pProps->cbData > sizeof(*pProps)) {
            return E_INVALIDARG;
        }
        /*  Do checks first, the assignments (for backout) */
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) {
            /*  Check the flags */
            if (pProps->dwSampleFlags &
                    (~Sample_ValidFlags | Sample_MediaTimeValid)) {
                return E_INVALIDARG;
            }
            /*  Check a flag isn't being set for a property
                not being provided
            */
            if ((pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) &&
                 !(m_dwFlags & AM_SAMPLE_TIMEVALID) &&
                 !CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) {
                 return E_INVALIDARG;
            }
        }
        /*  NB - can't SET the pointer or size */
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pbBuffer, cbProperties)) {

            /*  Check pbBuffer */
            if (pProps->pbBuffer != 0 && pProps->pbBuffer != m_pBuffer) {
                return E_INVALIDARG;
            }
        }
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties)) {

            /*  Check cbBuffer */
            if (pProps->cbBuffer != 0 && pProps->cbBuffer != m_cbBuffer) {
                return E_INVALIDARG;
            }
        }
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties) &&
            CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) {

            /*  Check lActual */
            if (pProps->cbBuffer < pProps->lActual) {
                return E_INVALIDARG;
            }
        }

        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) {

            /*  Check pMediaType */
            if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) {
                CheckPointer(pProps->pMediaType, E_POINTER);
                pMediaType = CreateMediaType(pProps->pMediaType);
                if (pMediaType == NULL) {
                    return E_OUTOFMEMORY;
                }
            }
        }

        /*  Now do the assignments */
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwStreamId, cbProperties)) {
            m_dwStreamId = pProps->dwStreamId;
        }
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) {
            /*  Set the flags */
            m_dwFlags = pProps->dwSampleFlags |
                                (m_dwFlags & Sample_MediaTimeValid);
            m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags;
        } else {
            if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwTypeSpecificFlags, cbProperties)) {
                m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags;
            }
        }

        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) {
            /*  Set lActual */
            m_lActual = pProps->lActual;
        }

        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) {

            /*  Set the times */
            m_End   = pProps->tStop;
        }
        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStart, cbProperties)) {

            /*  Set the times */
            m_Start = pProps->tStart;
        }

        if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) {
            /*  Set pMediaType */
            if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) {
                if (m_pMediaType != NULL) {
                    DeleteMediaType(m_pMediaType);
                }
                m_pMediaType = pMediaType;
            }
        }
    }

    return S_OK;
}

//=====================================================================
//=====================================================================
// Implements CBaseAllocator
//=====================================================================
//=====================================================================

/* Constructor overrides the default settings for the free list to request
   that it be alertable (ie the list can be cast to a handle which can be
   passed to WaitForSingleObject). Both of the allocator lists also ask for
   object locking, the all list matches the object default settings but I
   have included them here just so it is obvious what kind of list it is */

CBaseAllocator::CBaseAllocator(TCHAR *pName,
       LPUNKNOWN pUnk,
       HRESULT *phr,
                               BOOL bEvent) :
    CUnknown(pName, pUnk),
    m_lAllocated(0),
    m_bChanged(FALSE),
    m_bCommitted(FALSE),
    m_bDecommitInProgress(FALSE),
    m_lSize(0),
    m_lCount(0),
    m_lAlignment(0),
    m_lPrefix(0),
    m_hSem(NULL),
    m_lWaiting(0)
{
    if (bEvent) {
        m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
        if (m_hSem == NULL) {
            *phr = E_OUTOFMEMORY;
            return;
        }
    }
}

/* Destructor */

CBaseAllocator::~CBaseAllocator()
{
    // we can't call Decommit here since that would mean a call to a
    // pure virtual in destructor.
    // We must assume that the derived class has gone into decommit state in
    // its destructor.
    ASSERT(!m_bCommitted);
    if (m_hSem != NULL) {
        EXECUTE_ASSERT(CloseHandle(m_hSem));
    }
}

/* Override this to publicise our interfaces */

STDMETHODIMP
CBaseAllocator::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    /* Do we know about this interface */

    if (riid == IID_IMemAllocator) {
        return GetInterface((IMemAllocator *) this, ppv);
    } else {
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

/* This sets the size and count of the required samples. The memory isn't
   actually allocated until Commit() is called, if memory has already been
   allocated then assuming no samples are outstanding the user may call us
   to change the buffering, the memory will be released in Commit() */

STDMETHODIMP
CBaseAllocator::SetProperties(
                ALLOCATOR_PROPERTIES* pRequest,
                ALLOCATOR_PROPERTIES* pActual)
{
    CheckPointer(pRequest, E_POINTER);
    CheckPointer(pActual, E_POINTER);
    ValidateReadWritePtr(pActual, sizeof(ALLOCATOR_PROPERTIES));
    CAutoLock cObjectLock(this);

    ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES));

    ASSERT(pRequest->cbBuffer > 0);

    /*  Check the alignment requested */
    if (pRequest->cbAlign != 1) {
DbgLog((LOG_ERROR, 2, TEXT("Alignment requested was 0x%x, not 1"),
       pRequest->cbAlign));
return VFW_E_BADALIGN;
    }

    /* Can't do this if already committed, there is an argument that says we
       should not reject the SetProperties call if there are buffers still
       active. However this is called by the source filter, which is the same
       person who is holding the samples. Therefore it is not unreasonable
       for them to free all their samples before changing the requirements */

    if (m_bCommitted) {
return VFW_E_ALREADY_COMMITTED;
    }

    /* Must be no outstanding buffers */

    if (m_lAllocated != m_lFree.GetCount()) {
return VFW_E_BUFFERS_OUTSTANDING;
    }

    /* There isn't any real need to check the parameters as they
       will just be rejected when the user finally calls Commit */

    pActual->cbBuffer = m_lSize = pRequest->cbBuffer;
    pActual->cBuffers = m_lCount = pRequest->cBuffers;
    pActual->cbAlign = m_lAlignment = pRequest->cbAlign;
    pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix;

    m_bChanged = TRUE;
    return NOERROR;
}

STDMETHODIMP
CBaseAllocator::GetProperties(
    ALLOCATOR_PROPERTIES * pActual)
{
    CheckPointer(pActual,E_POINTER);
    ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES));

    CAutoLock cObjectLock(this);
    pActual->cbBuffer = m_lSize;
    pActual->cBuffers = m_lCount;
    pActual->cbAlign = m_lAlignment;
    pActual->cbPrefix = m_lPrefix;
    return NOERROR;
}

// get container for a sample. Blocking, synchronous call to get the
// next free buffer (as represented by an IMediaSample interface).
// on return, the time etc properties will be invalid, but the buffer
// pointer and size will be correct.

HRESULT CBaseAllocator::GetBuffer(IMediaSample **ppBuffer,
                                  REFERENCE_TIME *pStartTime,
                                  REFERENCE_TIME *pEndTime,
                                  DWORD dwFlags
                                  )
{
    UNREFERENCED_PARAMETER(pStartTime);
    UNREFERENCED_PARAMETER(pEndTime);
    UNREFERENCED_PARAMETER(dwFlags);
    CMediaSample *pSample;

    *ppBuffer = NULL;
    for (;;)
    {
        {  // scope for lock
            CAutoLock cObjectLock(this);

            /* Check we are committed */
            if (!m_bCommitted) {
                return VFW_E_NOT_COMMITTED;
            }
            pSample = (CMediaSample *) m_lFree.RemoveHead();
            if (pSample == NULL) {
                SetWaiting();
            }
        }

        /* If we didn't get a sample then wait for the list to signal */

        if (pSample) {
            break;
        }
        if (dwFlags & AM_GBF_NOWAIT) {
            return VFW_E_TIMEOUT;
        }
        ASSERT(m_hSem != NULL);
        WaitForSingleObject(m_hSem, INFINITE);
    }

    /* Addref the buffer up to one. On release
       back to zero instead of being deleted, it will requeue itself by
       calling the ReleaseBuffer member function. NOTE the owner of a
       media sample must always be derived from CBaseAllocator */

    ASSERT(pSample->m_cRef == 0);
    pSample->m_cRef = 1;
    *ppBuffer = pSample;

    return NOERROR;
}

/* Final release of a CMediaSample will call this */

STDMETHODIMP
CBaseAllocator::ReleaseBuffer(IMediaSample * pSample)
{
    CheckPointer(pSample,E_POINTER);
    ValidateReadPtr(pSample,sizeof(IMediaSample));
    BOOL bRelease = FALSE;
    {
        CAutoLock cal(this);

        /* Put back on the free list */

        m_lFree.Add((CMediaSample *)pSample);
        if (m_lWaiting != 0) {
            NotifySample();
        }

        // if there is a pending Decommit, then we need to complete it by
        // calling Free() when the last buffer is placed on the free list

    LONG l1 = m_lFree.GetCount();
    if (m_bDecommitInProgress && (l1 == m_lAllocated)) {
        Free();
        m_bDecommitInProgress = FALSE;
            bRelease = TRUE;
    }
    }

    /* For each buffer there is one AddRef, made in GetBuffer and released
       here. This may cause the allocator and all samples to be deleted */

    if (bRelease) {
        Release();
    }
    return NOERROR;
}

void
CBaseAllocator::NotifySample()
{
    if (m_lWaiting != 0) {
        ASSERT(m_hSem != NULL);
        ReleaseSemaphore(m_hSem, m_lWaiting, 0);
        m_lWaiting = 0;
    }
}

STDMETHODIMP
CBaseAllocator::Commit()
{
    /* Check we are not decommitted */
    CAutoLock cObjectLock(this);

    // cannot need to alloc or re-alloc if we are committed
    if (m_bCommitted) {
        return NOERROR;
    }

    /* Allow GetBuffer calls */

    m_bCommitted = TRUE;

    // is there a pending decommit ? if so, just cancel it
    if (m_bDecommitInProgress) {
        m_bDecommitInProgress = FALSE;

        // don't call Alloc at this point. He cannot allow SetProperties
        // between Decommit and the last free, so the buffer size cannot have
        // changed. And because some of the buffers are not free yet, he
        // cannot re-alloc anyway.
        return NOERROR;
    }

    DbgLog((LOG_MEMORY, 1, TEXT("Allocating: %ldx%ld"), m_lCount, m_lSize));

    // actually need to allocate the samples
    HRESULT hr = Alloc();
    if (FAILED(hr)) {
        m_bCommitted = FALSE;
        return hr;
    }
    AddRef();
    return NOERROR;
}

STDMETHODIMP
CBaseAllocator::Decommit()
{
    BOOL bRelease = FALSE;
    {
        /* Check we are not already decommitted */
        CAutoLock cObjectLock(this);
        if (m_bCommitted == FALSE) {
            if (m_bDecommitInProgress == FALSE) {
                return NOERROR;
            }
        }

        /* No more GetBuffer calls will succeed */
        m_bCommitted = FALSE;

        // are any buffers outstanding?
        if (m_lFree.GetCount() < m_lAllocated) {
            // please complete the decommit when last buffer is freed
            m_bDecommitInProgress = TRUE;
        } else {
            m_bDecommitInProgress = FALSE;

            // need to complete the decommit here as there are no
            // outstanding buffers

            Free();
            bRelease = TRUE;
        }

        // Tell anyone waiting that they can go now so we can
        // reject their call
        NotifySample();
    }

    if (bRelease) {
        Release();
    }
    return NOERROR;
}

/* Base definition of allocation which checks we are ok to go ahead and do
   the full allocation. We return S_FALSE if the requirements are the same */

HRESULT
CBaseAllocator::Alloc(void)
{
    /* Error if he hasn't set the size yet */
    if (m_lCount <= 0 || m_lSize <= 0 || m_lAlignment <= 0) {
return VFW_E_SIZENOTSET;
    }

    /* should never get here while buffers outstanding */
    ASSERT(m_lFree.GetCount() == m_lAllocated);

    /* If the requirements haven't changed then don't reallocate */
    if (m_bChanged == FALSE) {
return S_FALSE;
    }

    return NOERROR;
}

/*  Implement CBaseAllocator::CSampleList::Remove(pSample)
    Removes pSample from the list
*/
void
CBaseAllocator::CSampleList::Remove(CMediaSample * pSample)
{
    CMediaSample **pSearch;
    for (pSearch = &m_List;
         *pSearch != NULL;
         pSearch = &(CBaseAllocator::NextSample(*pSearch))) {
       if (*pSearch == pSample) {
           *pSearch = CBaseAllocator::NextSample(pSample);
           CBaseAllocator::NextSample(pSample) = NULL;
           m_nOnList--;
           return;
       }
    }
    DbgBreak("Couldn't find sample in list");
}

//=====================================================================
//=====================================================================
// Implements CMemAllocator
//=====================================================================
//=====================================================================

/* This goes in the factory template table to create new instances */
CUnknown *CMemAllocator::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    CUnknown *pUnkRet = new CMemAllocator(NAME("CMemAllocator"), pUnk, phr);
    return pUnkRet;
}

CMemAllocator::CMemAllocator(
    TCHAR *pName,
    LPUNKNOWN pUnk,
    HRESULT *phr)
    : CBaseAllocator(pName, pUnk, phr),
    m_pBuffer(NULL)
{
}

/* This sets the size and count of the required samples. The memory isn't
   actually allocated until Commit() is called, if memory has already been
   allocated then assuming no samples are outstanding the user may call us
   to change the buffering, the memory will be released in Commit() */
STDMETHODIMP
CMemAllocator::SetProperties(
                ALLOCATOR_PROPERTIES* pRequest,
                ALLOCATOR_PROPERTIES* pActual)
{
    CheckPointer(pActual,E_POINTER);
    ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES));
    CAutoLock cObjectLock(this);

    ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES));

    ASSERT(pRequest->cbBuffer > 0);

    SYSTEM_INFO SysInfo;
    GetSystemInfo(&SysInfo);

    /*  Check the alignment request is a power of 2 */
    if ((-pRequest->cbAlign & pRequest->cbAlign) != pRequest->cbAlign) {
DbgLog((LOG_ERROR, 1, TEXT("Alignment requested 0x%x not a power of 2!"),
       pRequest->cbAlign));
    }
    /*  Check the alignment requested */
    if (pRequest->cbAlign == 0 ||
SysInfo.dwAllocationGranularity & (pRequest->cbAlign - 1) != 0) {
DbgLog((LOG_ERROR, 1, TEXT("Invalid alignment 0x%x requested - granularity = 0x%x"),
       pRequest->cbAlign, SysInfo.dwAllocationGranularity));
return VFW_E_BADALIGN;
    }

    /* Can't do this if already committed, there is an argument that says we
       should not reject the SetProperties call if there are buffers still
       active. However this is called by the source filter, which is the same
       person who is holding the samples. Therefore it is not unreasonable
       for them to free all their samples before changing the requirements */

    if (m_bCommitted == TRUE) {
return VFW_E_ALREADY_COMMITTED;
    }

    /* Must be no outstanding buffers */

    if (m_lFree.GetCount() < m_lAllocated) {
return VFW_E_BUFFERS_OUTSTANDING;
    }

    /* There isn't any real need to check the parameters as they
       will just be rejected when the user finally calls Commit */

    // round length up to alignment - remember that prefix is included in
    // the alignment
    LONG lSize = pRequest->cbBuffer + pRequest->cbPrefix;
    LONG lRemainder = lSize % pRequest->cbAlign;
    if (lRemainder != 0) {
lSize = lSize - lRemainder + pRequest->cbAlign;
    }
    pActual->cbBuffer = m_lSize = (lSize - pRequest->cbPrefix);

    pActual->cBuffers = m_lCount = pRequest->cBuffers;
    pActual->cbAlign = m_lAlignment = pRequest->cbAlign;
    pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix;

    m_bChanged = TRUE;
    return NOERROR;
}

// override this to allocate our resources when Commit is called.

//
// note that our resources may be already allocated when this is called,
// since we don't free them on Decommit. We will only be called when in
// decommit state with all buffers free.
//
// object locked by caller
HRESULT
CMemAllocator::Alloc(void)
{
    CAutoLock lck(this);

    /* Check he has called SetProperties */
    HRESULT hr = CBaseAllocator::Alloc();
    if (FAILED(hr)) {
return hr;
    }

    /* If the requirements haven't changed then don't reallocate */
    if (hr == S_FALSE) {
ASSERT(m_pBuffer);
return NOERROR;
    }
    ASSERT(hr == S_OK); // we use this fact in the loop below

    /* Free the old resources */
    if (m_pBuffer) {
ReallyFree();
    }

    /* Create the contiguous memory block for the samples
       making sure it's properly aligned (64K should be enough!)
    */
    ASSERT(m_lAlignment != 0 &&
   (m_lSize + m_lPrefix) % m_lAlignment == 0);

    m_pBuffer = (PBYTE)VirtualAlloc(NULL,
    m_lCount * (m_lSize + m_lPrefix),
    MEM_COMMIT,
    PAGE_READWRITE);

    if (m_pBuffer == NULL) {
return E_OUTOFMEMORY;
    }

    LPBYTE pNext = m_pBuffer;
    CMediaSample *pSample;

    ASSERT(m_lAllocated == 0);

    // Create the new samples - we have allocated m_lSize bytes for each sample
    // plus m_lPrefix bytes per sample as a prefix. We set the pointer to
    // the memory after the prefix - so that GetPointer() will return a pointer
    // to m_lSize bytes.
    for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += (m_lSize + m_lPrefix)) {

pSample = new CMediaSample(
                        NAME("Default memory media sample"),
this,
                        &hr,
                        pNext + m_lPrefix,      // GetPointer() value
                        m_lSize);               // not including prefix

        ASSERT(SUCCEEDED(hr));
if (pSample == NULL) {
    return E_OUTOFMEMORY;
}

// This CANNOT fail
m_lFree.Add(pSample);
    }

    m_bChanged = FALSE;
    return NOERROR;
}

// override this to free up any resources we have allocated.
// called from the base class on Decommit when all buffers have been
// returned to the free list.
//
// caller has already locked the object.

// in our case, we keep the memory until we are deleted, so
// we do nothing here. The memory is deleted in the destructor by
// calling ReallyFree()
void
CMemAllocator::Free(void)
{
    return;
}

// called from the destructor (and from Alloc if changing size/count) to
// actually free up the memory
void
CMemAllocator::ReallyFree(void)
{
    /* Should never be deleting this unless all buffers are freed */

    ASSERT(m_lAllocated == m_lFree.GetCount());

    /* Free up all the CMediaSamples */

    CMediaSample *pSample;
    for (;;) {
        pSample = m_lFree.RemoveHead();
        if (pSample != NULL) {
            delete pSample;
        } else {
            break;
        }
    }

    m_lAllocated = 0;

    // free the block of buffer memory
    if (m_pBuffer) {
EXECUTE_ASSERT(VirtualFree(m_pBuffer, 0, MEM_RELEASE));
m_pBuffer = NULL;
    }
}

/* Destructor frees our memory resources */

CMemAllocator::~CMemAllocator()
{
    Decommit();
    ReallyFree();
}

// ------------------------------------------------------------------------
// filter registration through IFilterMapper. used if IFilterMapper is
// not found (Quartz 1.0 install)

STDAPI
AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata
                         , IFilterMapper *                  pIFM
                         , BOOL                             bRegister  )
{
  DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter")));

  // check we've got data
  //
  if( NULL == psetupdata ) return S_FALSE;

  // unregister filter
  // (as pins are subkeys of filter's CLSID key
  // they do not need to be removed separately).
  //
  DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter")));
  HRESULT hr = pIFM->UnregisterFilter( *(psetupdata->clsID) );

  if( bRegister )
  {
    // register filter
    //
    DbgLog((LOG_TRACE, 3, TEXT("= = register filter")));
    hr = pIFM->RegisterFilter( *(psetupdata->clsID)
                             , psetupdata->strName
                             , psetupdata->dwMerit    );
    if( SUCCEEDED(hr) )
    {
      // all its pins
      //
      DbgLog((LOG_TRACE, 3, TEXT("= = register filter pins")));
      for( UINT m1=0; m1 < psetupdata->nPins; m1++ )
      {
        hr = pIFM->RegisterPin( *(psetupdata->clsID)
                              , psetupdata->lpPin[m1].strName
                              , psetupdata->lpPin[m1].bRendered
                              , psetupdata->lpPin[m1].bOutput
                              , psetupdata->lpPin[m1].bZero
                              , psetupdata->lpPin[m1].bMany
                              , *(psetupdata->lpPin[m1].clsConnectsToFilter)
                              , psetupdata->lpPin[m1].strConnectsToPin );

        if( SUCCEEDED(hr) )
        {
          // and each pin's media types
          //
          DbgLog((LOG_TRACE, 3, TEXT("= = register filter pin types")));
          for( UINT m2=0; m2 < psetupdata->lpPin[m1].nMediaTypes; m2++ )
          {
            hr = pIFM->RegisterPinType( *(psetupdata->clsID)
                                      , psetupdata->lpPin[m1].strName
                                      , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMajorType)
                                      , *(psetupdata->lpPin[m1].lpMediaType[m2].clsMinorType) );
            if( FAILED(hr) ) break;
          }
          if( FAILED(hr) ) break;
        }
        if( FAILED(hr) ) break;
      }
    }
  }

  // handle one acceptable "error" - that
  // of filter not being registered!
  // (couldn't find a suitable #define'd
  // name for the error!)
  //
  if( 0x80070002 == hr)
    return NOERROR;
  else
    return hr;
}

//  Remove warnings about unreferenced inline functions
#pragma warning(disable:4514)


AMFILTER.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class hierarchy for streams architecture, December 1994

#ifndef __FILTER__
#define __FILTER__

/* The following classes are declared in this header: */

class CBaseMediaFilter;     // IMediaFilter support
class CBaseFilter;          // IBaseFilter,IMediaFilter support
class CBasePin;             // Abstract base class for IPin interface
class CEnumPins;            // Enumerate input and output pins
class CEnumMediaTypes;      // Enumerate the pin's preferred formats
class CBaseOutputPin;       // Adds data provider member functions
class CBaseInputPin;        // Implements IMemInputPin interface
class CMediaSample;         // Basic transport unit for IMemInputPin
class CBaseAllocator;       // General list guff for most allocators
class CMemAllocator;        // Implements memory buffer allocation

//=====================================================================
//=====================================================================
//
// QueryFilterInfo and QueryPinInfo AddRef the interface pointers
// they return.  You can use the macro below to release the interface.
//
//=====================================================================
//=====================================================================

#define QueryFilterInfoReleaseGraph(fi) if ((fi).pGraph) (fi).pGraph->Release();

#define QueryPinInfoReleaseFilter(pi) if ((pi).pFilter) (pi).pFilter->Release();

//=====================================================================
//=====================================================================
// Defines CBaseMediaFilter
//
// Abstract base class implementing IMediaFilter.
//
// Typically you will derive your filter from CBaseFilter rather than
// this,  unless you are implementing an object such as a plug-in
// distributor that needs to support IMediaFilter but not IBaseFilter.
//
// Note that IMediaFilter is derived from IPersist to allow query of
// class id.
//=====================================================================
//=====================================================================

class AM_NOVTABLE CBaseMediaFilter : public CUnknown,
                                     public IMediaFilter
{

protected:

    FILTER_STATE    m_State;            // current state: running, paused
    IReferenceClock *m_pClock;          // this filter's reference clock
    // note: all filters in a filter graph use the same clock

    // offset from stream time to reference time
    CRefTime        m_tStart;

    CLSID    m_clsid;            // This filters clsid
                                        // used for serialization
    CCritSec        *m_pLock;           // Object we use for locking

public:

    CBaseMediaFilter(
        const TCHAR     *pName,
        LPUNKNOWN pUnk,
        CCritSec  *pLock,
REFCLSID   clsid);

    virtual ~CBaseMediaFilter();

    DECLARE_IUNKNOWN

    // override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    //
    // --- IPersist method ---
    //

    STDMETHODIMP GetClassID(CLSID *pClsID);

    // --- IMediaFilter methods ---

    STDMETHODIMP GetState(DWORD dwMSecs, FILTER_STATE *State);

    STDMETHODIMP SetSyncSource(IReferenceClock *pClock);

    STDMETHODIMP GetSyncSource(IReferenceClock **pClock);

    // default implementation of Stop and Pause just record the
    // state. Override to activate or de-activate your filter.
    // Note that Run when called from Stopped state will call Pause
    // to ensure activation, so if you are a source or transform
    // you will probably not need to override Run.
    STDMETHODIMP Stop();
    STDMETHODIMP Pause();

    // the start parameter is the difference to be added to the
    // sample's stream time to get the reference time for
    // its presentation
    STDMETHODIMP Run(REFERENCE_TIME tStart);

    // --- helper methods ---

    // return the current stream time - ie find out what
    // stream time should be appearing now
    virtual HRESULT StreamTime(CRefTime& rtStream);

    // Is the filter currently active? (running or paused)
    BOOL IsActive() {
        CAutoLock cObjectLock(m_pLock);
        return ((m_State == State_Paused) || (m_State == State_Running));
    };
};

//=====================================================================
//=====================================================================
// Defines CBaseFilter
//
// An abstract class providing basic IBaseFilter support for pin
// enumeration and filter information reading.
//
// We cannot derive from CBaseMediaFilter since methods in IMediaFilter
// are also in IBaseFilter and would be ambiguous. Since much of the code
// assumes that they derive from a class that has m_State and other state
// directly available, we duplicate code from CBaseMediaFilter rather than
// having a member variable.
//
// Derive your filter from this, or from a derived object such as
// CTransformFilter.
//=====================================================================
//=====================================================================

class AM_NOVTABLE CBaseFilter : public CUnknown,        // Handles an IUnknown
                    public IBaseFilter,     // The Filter Interface
                    public IAMovieSetup     // For un/registration
{

friend class CBasePin;

protected:
    FILTER_STATE    m_State;            // current state: running, paused
    IReferenceClock *m_pClock;          // this graph's ref clock
    CRefTime        m_tStart;           // offset from stream time to reference time
    CLSID    m_clsid;            // This filters clsid
                                        // used for serialization
    CCritSec        *m_pLock;           // Object we use for locking

    WCHAR           *m_pName;           // Full filter name
    IFilterGraph    *m_pGraph;          // Graph we belong to
    IMediaEventSink *m_pSink;           // Called with notify events
    LONG            m_PinVersion;       // Current pin version

public:

    CBaseFilter(
        const TCHAR *pName,     // Object description
        LPUNKNOWN pUnk,         // IUnknown of delegating object
        CCritSec  *pLock,       // Object who maintains lock
REFCLSID   clsid);      // The clsid to be used to serialize this filter

    CBaseFilter(
        TCHAR     *pName,       // Object description
        LPUNKNOWN pUnk,         // IUnknown of delegating object
        CCritSec  *pLock,       // Object who maintains lock
REFCLSID   clsid,       // The clsid to be used to serialize this filter
        HRESULT   *phr);        // General OLE return code

    ~CBaseFilter();

    DECLARE_IUNKNOWN

    // override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
#ifdef DEBUG
    STDMETHODIMP_(ULONG) NonDelegatingRelease();
#endif

    //
    // --- IPersist method ---
    //

    STDMETHODIMP GetClassID(CLSID *pClsID);

    // --- IMediaFilter methods ---

    STDMETHODIMP GetState(DWORD dwMSecs, FILTER_STATE *State);

    STDMETHODIMP SetSyncSource(IReferenceClock *pClock);

    STDMETHODIMP GetSyncSource(IReferenceClock **pClock);

    // override Stop and Pause so we can activate the pins.
    // Note that Run will call Pause first if activation needed.
    // Override these if you want to activate your filter rather than
    // your pins.
    STDMETHODIMP Stop();
    STDMETHODIMP Pause();

    // the start parameter is the difference to be added to the
    // sample's stream time to get the reference time for
    // its presentation
    STDMETHODIMP Run(REFERENCE_TIME tStart);

    // --- helper methods ---

    // return the current stream time - ie find out what
    // stream time should be appearing now
    virtual HRESULT StreamTime(CRefTime& rtStream);

    // Is the filter currently active?
    BOOL IsActive() {
        CAutoLock cObjectLock(m_pLock);
        return ((m_State == State_Paused) || (m_State == State_Running));
    };

    // Is this filter stopped (without locking)
    BOOL IsStopped() {
        return (m_State == State_Stopped);
    };

    //
    // --- IBaseFilter methods ---
    //

    // pin enumerator
    STDMETHODIMP EnumPins(
                    IEnumPins ** ppEnum);

    // default behaviour of FindPin assumes pin ids are their names
    STDMETHODIMP FindPin(
        LPCWSTR Id,
        IPin ** ppPin
    );

    STDMETHODIMP QueryFilterInfo(
                    FILTER_INFO * pInfo);

    STDMETHODIMP JoinFilterGraph(
                    IFilterGraph * pGraph,
                    LPCWSTR pName);

    // return a Vendor information string. Optional - may return E_NOTIMPL.
    // memory returned should be freed using CoTaskMemFree
    // default implementation returns E_NOTIMPL
    STDMETHODIMP QueryVendorInfo(
                    LPWSTR* pVendorInfo
            );

    // --- helper methods ---

    // send an event notification to the filter graph if we know about it.
    // returns S_OK if delivered, S_FALSE if the filter graph does not sink
    // events, or an error otherwise.
    HRESULT NotifyEvent(
        long EventCode,
        long EventParam1,
        long EventParam2);

    // return the filter graph we belong to
    IFilterGraph *GetFilterGraph() {
        return m_pGraph;
    }

    // Request reconnect
    // pPin is the pin to reconnect
    // pmt is the type to reconnect with - can be NULL
    // Calls ReconnectEx on the filter graph
    HRESULT ReconnectPin(IPin *pPin, AM_MEDIA_TYPE const *pmt);

    // find out the current pin version (used by enumerators)
    virtual LONG GetPinVersion();
    void IncrementPinVersion();

    // you need to supply these to access the pins from the enumerator
    // and for default Stop and Pause/Run activation.
    virtual int GetPinCount() PURE;
    virtual CBasePin *GetPin(int n) PURE;

    // --- IAMovieSetup methods ---

    STDMETHODIMP Register();    // ask filter to register itself
    STDMETHODIMP Unregister();  // and unregister itself

    // --- setup helper methods ---
    // (override to return filters setup data)

    virtual LPAMOVIESETUP_FILTER GetSetupData(){ return NULL; }

};

//=====================================================================
//=====================================================================
// Defines CBasePin
//
// Abstract class that supports the basics of IPin
//=====================================================================
//=====================================================================

class  AM_NOVTABLE CBasePin : public CUnknown, public IPin, public IQualityControl
{

protected:

    WCHAR *         m_pName;        // This pin's name
    IPin            *m_Connected;               // Pin we have connected to
    CMediaType      m_mt;                       // Media type of connection
    PIN_DIRECTION   m_dir;                      // Direction of this pin
    CCritSec        *m_pLock;                   // Object we use for locking
    BOOL            m_bRunTimeError;            // Run time error generated
    CBaseFilter    *m_pFilter;                  // Filter we were created by
    IQualityControl *m_pQSink;                  // Target for Quality messages
    LONG            m_TypeVersion;              // Holds current type version

    CRefTime        m_tStart;                   // time from NewSegment call
    CRefTime        m_tStop;                    // time from NewSegment
    double          m_dRate;                    // rate from NewSegment

#ifdef DEBUG
    LONG            m_cRef;                     // Ref count tracing
#endif

    // displays pin connection information

#ifdef DEBUG
    void DisplayPinInfo(IPin *pReceivePin);
    void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt);
#else
    void DisplayPinInfo(IPin *pReceivePin) {};
    void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) {};
#endif

    // used to agree a media type for a pin connection

    // given a specific media type, attempt a connection (includes
    // checking that the type is acceptable to this pin)
    HRESULT
    AttemptConnection(
        IPin* pReceivePin,      // connect to this pin
        const CMediaType* pmt   // using this type
    );

    // try all the media types in this enumerator - for each that
    // we accept, try to connect using ReceiveConnection.
    HRESULT TryMediaTypes(
                        IPin *pReceivePin,      // connect to this pin
                        const CMediaType *pmt,        // proposed type from Connect
                        IEnumMediaTypes *pEnum);    // try this enumerator

    // establish a connection with a suitable mediatype. Needs to
    // propose a media type if the pmt pointer is null or partially
    // specified - use TryMediaTypes on both our and then the other pin's
    // enumerator until we find one that works.
    HRESULT AgreeMediaType(
                        IPin *pReceivePin,      // connect to this pin
                        const CMediaType *pmt);       // proposed type from Connect

public:

    CBasePin(
        TCHAR *pObjectName,         // Object description
        CBaseFilter *pFilter,       // Owning filter who knows about pins
        CCritSec *pLock,            // Object who implements the lock
        HRESULT *phr,               // General OLE return code
        LPCWSTR pName,              // Pin name for us
        PIN_DIRECTION dir);         // Either PINDIR_INPUT or PINDIR_OUTPUT

    virtual ~CBasePin();

    DECLARE_IUNKNOWN

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
    STDMETHODIMP_(ULONG) NonDelegatingRelease();
    STDMETHODIMP_(ULONG) NonDelegatingAddRef();

    // --- IPin methods ---

    // take lead role in establishing a connection. Media type pointer
    // may be null, or may point to partially-specified mediatype
    // (subtype or format type may be GUID_NULL).
    STDMETHODIMP Connect(
        IPin * pReceivePin,
        const AM_MEDIA_TYPE *pmt   // optional media type
    );

    // (passive) accept a connection from another pin
    STDMETHODIMP ReceiveConnection(
        IPin * pConnector,      // this is the initiating connecting pin
        const AM_MEDIA_TYPE *pmt   // this is the media type we will exchange
    );

    STDMETHODIMP Disconnect();

    STDMETHODIMP ConnectedTo(IPin **pPin);

    STDMETHODIMP ConnectionMediaType(AM_MEDIA_TYPE *pmt);

    STDMETHODIMP QueryPinInfo(
        PIN_INFO * pInfo
    );

    STDMETHODIMP QueryDirection(
    PIN_DIRECTION * pPinDir
    );

    STDMETHODIMP QueryId(
        LPWSTR * Id
    );

    // does the pin support this media type
    STDMETHODIMP QueryAccept(
        const AM_MEDIA_TYPE *pmt
    );

    // return an enumerator for this pins preferred media types
    STDMETHODIMP EnumMediaTypes(
        IEnumMediaTypes **ppEnum
    );

    // return an array of IPin* - the pins that this pin internally connects to
    // All pins put in the array must be AddReffed (but no others)
    // Errors: "Can't say" - FAIL, not enough slots - return S_FALSE
    // Default: return E_NOTIMPL
    // The filter graph will interpret NOT_IMPL as any input pin connects to
    // all visible output pins and vice versa.
    // apPin can be NULL if nPin==0 (not otherwise).
    STDMETHODIMP QueryInternalConnections(
        IPin* *apPin,     // array of IPin*
        ULONG *nPin       // on input, the number of slots
                          // on output  the number of pins
    ) { return E_NOTIMPL; }

    // Called when no more data will be sent
    STDMETHODIMP EndOfStream(void);

    // Begin/EndFlush still PURE

    // NewSegment notifies of the start/stop/rate applying to the data
    // about to be received. Default implementation records data and
    // returns S_OK.
    // Override this to pass downstream.
    STDMETHODIMP NewSegment(
                    REFERENCE_TIME tStart,
                    REFERENCE_TIME tStop,
                    double dRate);

    //================================================================================
    // IQualityControl methods
    //================================================================================

    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

    STDMETHODIMP SetSink(IQualityControl * piqc);

    // --- helper methods ---

    // Returns true if the pin is connected. false otherwise.
    BOOL IsConnected(void) {return (m_Connected != NULL); };
    // Return the pin this is connected to (if any)
    IPin * GetConnected() { return m_Connected; };

    // Check if our filter is currently stopped
    BOOL IsStopped() {
        return (m_pFilter->m_State == State_Stopped);
    };

    // find out the current type version (used by enumerators)
    virtual LONG GetMediaTypeVersion();
    void IncrementTypeVersion();

    // switch the pin to active (paused or running) mode
    // not an error to call this if already active
    virtual HRESULT Active(void);

    // switch the pin to inactive state - may already be inactive
    virtual HRESULT Inactive(void);

    // Notify of Run() from filter
    virtual HRESULT Run(REFERENCE_TIME tStart);

    // check if the pin can support this specific proposed type and format
    virtual HRESULT CheckMediaType(const CMediaType *) PURE;

    // set the connection to use this format (previously agreed)
    virtual HRESULT SetMediaType(const CMediaType *);

    // check that the connection is ok before verifying it
    // can be overridden eg to check what interfaces will be supported.
    virtual HRESULT CheckConnect(IPin *);

    // Set and release resources required for a connection
    virtual HRESULT BreakConnect();
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    // returns the preferred formats for a pin
    virtual HRESULT GetMediaType(int iPosition,CMediaType *pMediaType);

    // access to NewSegment values
    REFERENCE_TIME CurrentStopTime() {
        return m_tStop;
    }
    REFERENCE_TIME CurrentStartTime() {
        return m_tStart;
    }
    double CurrentRate() {
        return m_dRate;
    }

    //  Access name
    LPWSTR Name() { return m_pName; };

};

//=====================================================================
//=====================================================================
// Defines CEnumPins
//
// Pin enumerator class that works by calling CBaseFilter. This interface
// is provided by CBaseFilter::EnumPins and calls GetPinCount() and
// GetPin() to enumerate existing pins. Needs to be a separate object so
// that it can be cloned (creating an existing object at the same
// position in the enumeration)
//
// NOTE It overrides the non delegating IUnknown so that it can not
// only keep it's own reference counting lifetime but it will also still
// delegate to the outer owner. This ensures the owner is around all the
// time we are
//=====================================================================
//=====================================================================

class CEnumPins : public IEnumPins      // The interface we support
{
    int m_Position;                 // Current ordinal position
    int m_PinCount;                 // Number of pins available
    CBaseFilter *m_pFilter;         // The filter who owns us
    LONG m_Version;                 // Pin version information
    LONG m_cRef;

    typedef CGenericList<CBasePin> CPinList;

    CPinList m_PinCache;    // These pointers have not been AddRef'ed and
    // so they should not be dereferenced.  They are
    // merely kept to ID which pins have been enumerated.

#ifdef DEBUG
    DWORD m_dwCookie;
#endif

    /* If while we are retrieving a pin for example from the filter an error
       occurs we assume that our internal state is stale with respect to the
       filter (someone may have deleted all the pins). We can check before
       starting whether or not the operation is likely to fail by asking the
       filter what it's current version number is. If the filter has not
       overriden the GetPinVersion method then this will always match */

    BOOL AreWeOutOfSync() {
        return (m_pFilter->GetPinVersion() == m_Version ? FALSE : TRUE);
    };

    /* This method performs the same operations as Reset, except is does not clear
       the cache of pins already enumerated. */

    STDMETHODIMP Refresh();

public:

    CEnumPins(
        CBaseFilter *pFilter,
        CEnumPins *pEnumPins);

    virtual ~CEnumPins();

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IEnumPins
    STDMETHODIMP Next(
        ULONG cPins,         // place this many pins...
        IPin ** ppPins,      // ...in this array of IPin*
        ULONG * pcFetched    // actual count passed returned here
    );

    STDMETHODIMP Skip(ULONG cPins);
    STDMETHODIMP Reset();
    STDMETHODIMP Clone(IEnumPins **ppEnum);

};

//=====================================================================
//=====================================================================
// Defines CEnumMediaTypes
//
// Enumerates the preferred formats for input and output pins
//=====================================================================
//=====================================================================

class CEnumMediaTypes : public IEnumMediaTypes    // The interface we support
{
    int m_Position;           // Current ordinal position
    CBasePin *m_pPin;         // The pin who owns us
    LONG m_Version;           // Media type version value
    LONG m_cRef;
#ifdef DEBUG
    DWORD m_dwCookie;
#endif

    /* The media types a filter supports can be quite dynamic so we add to
       the general IEnumXXXX interface the ability to be signaled when they
       change via an event handle the connected filter supplies. Until the
       Reset method is called after the state changes all further calls to
       the enumerator (except Reset) will return E_UNEXPECTED error code */

    BOOL AreWeOutOfSync() {
        return (m_pPin->GetMediaTypeVersion() == m_Version ? FALSE : TRUE);
    };

public:

    CEnumMediaTypes(
        CBasePin *pPin,
        CEnumMediaTypes *pEnumMediaTypes);

    virtual ~CEnumMediaTypes();

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IEnumMediaTypes
    STDMETHODIMP Next(
        ULONG cMediaTypes,          // place this many pins...
        AM_MEDIA_TYPE ** ppMediaTypes,  // ...in this array
        ULONG * pcFetched           // actual count passed
    );

    STDMETHODIMP Skip(ULONG cMediaTypes);
    STDMETHODIMP Reset();
    STDMETHODIMP Clone(IEnumMediaTypes **ppEnum);
};

//=====================================================================
//=====================================================================
// Defines CBaseOutputPin
//
// class derived from CBasePin that can pass buffers to a connected pin
// that supports IMemInputPin. Supports IPin.
//
// Derive your output pin from this.
//
//=====================================================================
//=====================================================================

class  AM_NOVTABLE CBaseOutputPin : public CBasePin
{

protected:

    IMemAllocator *m_pAllocator;
    IMemInputPin *m_pInputPin;        // interface on the downstreaminput pin
                                      // set up in CheckConnect when we connect.

public:

    CBaseOutputPin(
        TCHAR *pObjectName,
        CBaseFilter *pFilter,
        CCritSec *pLock,
        HRESULT *phr,
        LPCWSTR pName);

    // override CompleteConnect() so we can negotiate an allocator
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    // negotiate the allocator and its buffer size/count and other properties
    // Calls DecideBufferSize to set properties
    virtual HRESULT DecideAllocator(IMemInputPin * pPin, IMemAllocator ** pAlloc);

    // override this to set the buffer size and count. Return an error
    // if the size/count is not to your liking.
    // The allocator properties passed in are those requested by the
    // input pin - use eg the alignment and prefix members if you have
    // no preference on these.
    virtual HRESULT DecideBufferSize(
        IMemAllocator * pAlloc,
        ALLOCATOR_PROPERTIES * ppropInputRequest
    ) PURE;

    // returns an empty sample buffer from the allocator
    virtual HRESULT GetDeliveryBuffer(IMediaSample ** ppSample,
                                      REFERENCE_TIME * pStartTime,
                                      REFERENCE_TIME * pEndTime,
                                      DWORD dwFlags);

    // deliver a filled-in sample to the connected input pin
    // note - you need to release it after calling this. The receiving
    // pin will addref the sample if it needs to hold it beyond the
    // call.
    virtual HRESULT Deliver(IMediaSample *);

    // override this to control the connection
    virtual HRESULT InitAllocator(IMemAllocator **ppAlloc);
    HRESULT CheckConnect(IPin *pPin);
    HRESULT BreakConnect();

    // override to call Commit and Decommit
    HRESULT Active(void);
    HRESULT Inactive(void);

    // we have a default handling of EndOfStream which is to return
    // an error, since this should be called on input pins only
    STDMETHODIMP EndOfStream(void);

    // called from elsewhere in our filter to pass EOS downstream to
    // our connected input pin
    virtual HRESULT DeliverEndOfStream(void);

    // same for Begin/EndFlush - we handle Begin/EndFlush since it
    // is an error on an output pin, and we have Deliver methods to
    // call the methods on the connected pin
    STDMETHODIMP BeginFlush(void);
    STDMETHODIMP EndFlush(void);
    virtual HRESULT DeliverBeginFlush(void);
    virtual HRESULT DeliverEndFlush(void);

    // deliver NewSegment to connected pin - you will need to
    // override this if you queue any data in your output pin.
    virtual HRESULT DeliverNewSegment(
                        REFERENCE_TIME tStart,
                        REFERENCE_TIME tStop,
                        double dRate);

    //================================================================================
    // IQualityControl methods
    //================================================================================

    // All inherited from CBasePin and not overridden here.
    // STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);
    // STDMETHODIMP SetSink(IQualityControl * piqc);
};

//=====================================================================
//=====================================================================
// Defines CBaseInputPin
//
// derive your standard input pin from this.
// you need to supply GetMediaType and CheckConnect etc (see CBasePin),
// and you need to supply Receive to do something more useful.
//
//=====================================================================
//=====================================================================

class AM_NOVTABLE CBaseInputPin : public CBasePin,
                                  public IMemInputPin
{

protected:

    IMemAllocator *m_pAllocator;    // Default memory allocator

    // allocator is read-only, so received samples
    // cannot be modified (probably only relevant to in-place
    // transforms
    BYTE m_bReadOnly;

//private:  this should really be private... only the MPEG code
// currently looks at it directly and it should use IsFlushing().
    // in flushing state (between BeginFlush and EndFlush)
    // if TRUE, all Receives are returned with S_FALSE
    BYTE m_bFlushing;

    // Sample properties - initalized in Receive
    AM_SAMPLE2_PROPERTIES m_SampleProps;

public:

    CBaseInputPin(
        TCHAR *pObjectName,
        CBaseFilter *pFilter,
        CCritSec *pLock,
        HRESULT *phr,
        LPCWSTR pName);

    virtual ~CBaseInputPin();

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    // return the allocator interface that this input pin

// would like the output pin to use
    STDMETHODIMP GetAllocator(IMemAllocator ** ppAllocator);

    // tell the input pin which allocator the output pin is actually
    // going to use.
    STDMETHODIMP NotifyAllocator(
                    IMemAllocator * pAllocator,
                    BOOL bReadOnly);

    // override to release our stored allocator
    STDMETHODIMP Disconnect();

    // do something with this media sample
    STDMETHODIMP Receive(IMediaSample *pSample);

    // do something with these media samples
    STDMETHODIMP ReceiveMultiple (
        IMediaSample **pSamples,
        long nSamples,
        long *nSamplesProcessed);

    // See if Receive() blocks
    STDMETHODIMP ReceiveCanBlock();

    // Default handling for BeginFlush - call at the beginning
    // of your implementation (makes sure that all Receive calls
    // fail). After calling this, you need to free any queued data
    // and then call downstream.
    STDMETHODIMP BeginFlush(void);

    // default handling for EndFlush - call at end of your implementation
    // - before calling this, ensure that there is no queued data and no thread
    // pushing any more without a further receive, then call downstream,
    // then call this method to clear the m_bFlushing flag and re-enable
    // receives
    STDMETHODIMP EndFlush(void);

    // this method is optional (can return E_NOTIMPL).
    // default implementation returns E_NOTIMPL. Override if you have
    // specific alignment or prefix needs, but could use an upstream
    // allocator
    STDMETHODIMP GetAllocatorRequirements(ALLOCATOR_PROPERTIES*pProps);

    // helper method to check the read-only flag
    BOOL IsReadOnly() {
        return m_bReadOnly;
    };

    // helper method to see if we are flushing
    BOOL IsFlushing() {
        return m_bFlushing;
    };

    //  Override this for checking whether it's OK to process samples
    //  Also call this from EndOfStream.
    virtual HRESULT CheckStreaming();

    // Pass a Quality notification on to the appropriate sink
    HRESULT PassNotify(Quality& q);

    //================================================================================
    // IQualityControl methods (from CBasePin)
    //================================================================================

    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

    // no need to override:
    // STDMETHODIMP SetSink(IQualityControl * piqc);

    // switch the pin to inactive state - may already be inactive
    virtual HRESULT Inactive(void);

    // Return sample properties pointer
    AM_SAMPLE2_PROPERTIES * SampleProps() {
        ASSERT(m_SampleProps.cbData != 0);
        return &m_SampleProps;
    }
};

//=====================================================================
//=====================================================================
// Memory allocators
//
// the shared memory transport between pins requires the input pin
// to provide a memory allocator that can provide sample objects. A
// sample object supports the IMediaSample interface.
//
// CBaseAllocator handles the management of free and busy samples. It
// allocates CMediaSample objects. CBaseAllocator is an abstract class:
// in particular it has no method of initializing the list of free
// samples. CMemAllocator is derived from CBaseAllocator and initializes
// the list of samples using memory from the standard IMalloc interface.
//
// If you want your buffers to live in some special area of memory,
// derive your allocator object from CBaseAllocator. If you derive your
// IMemInputPin interface object from CBaseMemInputPin, you will get
// CMemAllocator-based allocation etc for free and will just need to
// supply the Receive handling, and media type / format negotiation.
//=====================================================================
//=====================================================================

//=====================================================================
//=====================================================================
// Defines CMediaSample
//
// an object of this class supports IMediaSample and represents a buffer
// for media data with some associated properties. Releasing it returns
// it to a freelist managed by a CBaseAllocator derived object.
//=====================================================================
//=====================================================================

class CMediaSample : public IMediaSample2    // The interface we support
{

protected:

    friend class CBaseAllocator;

    /*  Values for dwFlags - these are used for backward compatiblity
        only now - use AM_SAMPLE_xxx
    */
    enum { Sample_SyncPoint       = 0x01,   /* Is this a sync point */
           Sample_Preroll         = 0x02,   /* Is this a preroll sample */
           Sample_Discontinuity   = 0x04,   /* Set if start of new segment */
           Sample_TypeChanged     = 0x08,   /* Has the type changed */
           Sample_TimeValid       = 0x10,   /* Set if time is valid */
           Sample_MediaTimeValid  = 0x20,   /* Is the media time valid */
           Sample_TimeDiscontinuity = 0x40, /* Time discontinuity */
           Sample_StopValid       = 0x100,  /* Stop time valid */
           Sample_ValidFlags      = 0x1FF
         };

    /* Properties, the media sample class can be a container for a format
       change in which case we take a copy of a type through the SetMediaType
       interface function and then return it when GetMediaType is called. As
       we do no internal processing on it we leave it as a pointer */

    DWORD            m_dwFlags;         /* Flags for this sample */
                                        /* Type specific flags are packed
                                           into the top word
                                        */
    DWORD            m_dwTypeSpecificFlags; /* Media type specific flags */
    LPBYTE           m_pBuffer;         /* Pointer to the complete buffer */
    LONG             m_lActual;         /* Length of data in this sample */
    LONG             m_cbBuffer;        /* Size of the buffer */
    CBaseAllocator  *m_pAllocator;      /* The allocator who owns us */
    CMediaSample     *m_pNext;          /* Chaining in free list */
    REFERENCE_TIME   m_Start;           /* Start sample time */
    REFERENCE_TIME   m_End;             /* End sample time */
    LONGLONG         m_MediaStart;      /* Real media start position */
    LONG             m_MediaEnd;        /* A difference to get the end */
    AM_MEDIA_TYPE    *m_pMediaType;     /* Media type change data */
    DWORD            m_dwStreamId;      /* Stream id */
public:
    LONG             m_cRef;            /* Reference count */

public:

    CMediaSample(
        TCHAR *pName,
        CBaseAllocator *pAllocator,
        HRESULT *phr,
        LPBYTE pBuffer = NULL,
        LONG length = 0);

    virtual ~CMediaSample();

    /* Note the media sample does not delegate to its owner */

    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // set the buffer pointer and length. Used by allocators that
    // want variable sized pointers or pointers into already-read data.
    // This is only available through a CMediaSample* not an IMediaSample*
    // and so cannot be changed by clients.
    HRESULT SetPointer(BYTE * ptr, LONG cBytes);

    // Get me a read/write pointer to this buffer's memory.
    STDMETHODIMP GetPointer(BYTE ** ppBuffer);

    STDMETHODIMP_(LONG) GetSize(void);

    // get the stream time at which this sample should start and finish.
    STDMETHODIMP GetTime(
        REFERENCE_TIME * pTimeStart,     // put time here
        REFERENCE_TIME * pTimeEnd
    );

    // Set the stream time at which this sample should start and finish.
    STDMETHODIMP SetTime(
        REFERENCE_TIME * pTimeStart,     // put time here
        REFERENCE_TIME * pTimeEnd
    );
    STDMETHODIMP IsSyncPoint(void);
    STDMETHODIMP SetSyncPoint(BOOL bIsSyncPoint);
    STDMETHODIMP IsPreroll(void);
    STDMETHODIMP SetPreroll(BOOL bIsPreroll);

    STDMETHODIMP_(LONG) GetActualDataLength(void);
    STDMETHODIMP SetActualDataLength(LONG lActual);

    // these allow for limited format changes in band

    STDMETHODIMP GetMediaType(AM_MEDIA_TYPE **ppMediaType);
    STDMETHODIMP SetMediaType(AM_MEDIA_TYPE *pMediaType);

    // returns S_OK if there is a discontinuity in the data (this same is
    // not a continuation of the previous stream of data
    // - there has been a seek).
    STDMETHODIMP IsDiscontinuity(void);
    // set the discontinuity property - TRUE if this sample is not a
    // continuation, but a new sample after a seek.
    STDMETHODIMP SetDiscontinuity(BOOL bDiscontinuity);

    // get the media times for this sample
    STDMETHODIMP GetMediaTime(
    LONGLONG * pTimeStart,
LONGLONG * pTimeEnd
    );

    // Set the media times for this sample
    STDMETHODIMP SetMediaTime(
    LONGLONG * pTimeStart,
LONGLONG * pTimeEnd
    );

    // Set and get properties (IMediaSample2)
    STDMETHODIMP GetProperties(
        DWORD cbProperties,
        BYTE * pbProperties
    );

    STDMETHODIMP SetProperties(
        DWORD cbProperties,
        const BYTE * pbProperties
    );
};

//=====================================================================
//=====================================================================
// Defines CBaseAllocator
//
// Abstract base class that manages a list of media samples
//
// This class provides support for getting buffers from the free list,
// including handling of commit and (asynchronous) decommit.
//
// Derive from this class and override the Alloc and Free functions to
// allocate your CMediaSample (or derived) objects and add them to the
// free list, preparing them as necessary.
//=====================================================================
//=====================================================================

class AM_NOVTABLE CBaseAllocator : public CUnknown,         // A non delegating IUnknown
                       public IMemAllocator,    // The interface we support
                       public CCritSec          // Provides object locking
{
    class CSampleList;
    friend class CSampleList;

    /*  Hack to get at protected member in CMediaSample */
    static CMediaSample * &NextSample(CMediaSample *pSample)
    {
        return pSample->m_pNext;
    };

    /*  Mini list class for the free list */
    class CSampleList
    {
    public:
        CSampleList() : m_List(NULL), m_nOnList(0) {};
#ifdef DEBUG
        ~CSampleList()
        {
            ASSERT(m_nOnList == 0);
        };
#endif
        CMediaSample *Head() const { return m_List; };
        CMediaSample *Next(CMediaSample *pSample) const { return CBaseAllocator::NextSample(pSample); };
        int GetCount() const { return m_nOnList; };
        void Add(CMediaSample *pSample)
        {
            ASSERT(pSample != NULL);
            CBaseAllocator::NextSample(pSample) = m_List;
            m_List = pSample;
            m_nOnList++;
        };
        CMediaSample *RemoveHead()
        {
            CMediaSample *pSample = m_List;
            if (pSample != NULL) {
                m_List = CBaseAllocator::NextSample(m_List);
                m_nOnList--;
            }
            return pSample;
        };
        void Remove(CMediaSample *pSample);

    public:
        CMediaSample *m_List;
        int           m_nOnList;
    };
protected:

    CSampleList m_lFree;        // Free list

    /*  Note to overriders of CBaseAllocator.

        We use a lazy signalling mechanism for waiting for samples.
        This means we don't call the OS if no waits occur.

        In order to implement this:

        1. When a new sample is added to m_lFree call NotifySample() which
           calls ReleaseSemaphore on m_hSem with a count of m_lWaiting and
           sets m_lWaiting to 0.
           This must all be done holding the allocator's critical section.

        2. When waiting for a sample call SetWaiting() which increments
           m_lWaiting BEFORE leaving the allocator's critical section.

        3. Actually wait by calling WaitForSingleObject(m_hSem, INFINITE)
           having left the allocator's critical section.  The effect of
           this is to remove 1 from the semaphore's count.  You MUST call
           this once having incremented m_lWaiting.

        The following are then true when the critical section is not held :
            (let nWaiting = number about to wait or waiting)

            (1) if (m_lFree.GetCount() != 0) then (m_lWaiting == 0)
            (2) m_lWaiting + Semaphore count == nWaiting

        We would deadlock if
           nWaiting != 0 &&
           m_lFree.GetCount() != 0 &&
           Semaphore count == 0

           But from (1) if m_lFree.GetCount() != 0 then m_lWaiting == 0 so
           from (2) Semaphore count == nWaiting (which is non-0) so the
           deadlock can't happen.
    */

    HANDLE m_hSem;              // For signalling
    long m_lWaiting;            // Waiting for a free element
    long m_lCount;              // how many buffers we have agreed to provide
    long m_lAllocated;          // how many buffers are currently allocated
    long m_lSize;               // agreed size of each buffer
    long m_lAlignment;          // agreed alignment
    long m_lPrefix;             // agreed prefix (preceeds GetPointer() value)
    BOOL m_bChanged;            // Have the buffer requirements changed

    // if true, we are decommitted and can't allocate memory
    BOOL m_bCommitted;
    // if true, the decommit has happened, but we haven't called Free yet
    // as there are still outstanding buffers
    BOOL m_bDecommitInProgress;

    // called to decommit the memory when the last buffer is freed
    // pure virtual - need to override this
    virtual void Free(void) PURE;

    // override to allocate the memory when commit called
    virtual HRESULT Alloc(void);

public:

    CBaseAllocator(TCHAR *, LPUNKNOWN, HRESULT *, BOOL bEvent = TRUE);
    virtual ~CBaseAllocator();

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    STDMETHODIMP SetProperties(
    ALLOCATOR_PROPERTIES* pRequest,
    ALLOCATOR_PROPERTIES* pActual);

    // return the properties actually being used on this allocator
    STDMETHODIMP GetProperties(
    ALLOCATOR_PROPERTIES* pProps);

    // override Commit to allocate memory. We handle the GetBuffer
    //state changes
    STDMETHODIMP Commit();

    // override this to handle the memory freeing. We handle any outstanding
    // GetBuffer calls
    STDMETHODIMP Decommit();

    // get container for a sample. Blocking, synchronous call to get the
    // next free buffer (as represented by an IMediaSample interface).
    // on return, the time etc properties will be invalid, but the buffer
    // pointer and size will be correct. The two time parameters are
    // optional and either may be NULL, they may alternatively be set to
    // the start and end times the sample will have attached to it
    // bPrevFramesSkipped is not used (used only by the video renderer's
    // allocator where it affects quality management in direct draw).

    STDMETHODIMP GetBuffer(IMediaSample **ppBuffer,
                           REFERENCE_TIME * pStartTime,
                           REFERENCE_TIME * pEndTime,
                           DWORD dwFlags);

    // final release of a CMediaSample will call this
    STDMETHODIMP ReleaseBuffer(IMediaSample *pBuffer);
    // obsolete:: virtual void PutOnFreeList(CMediaSample * pSample);

    // Notify that a sample is available
    void NotifySample();

    // Notify that we're waiting for a sample
    void SetWaiting() { m_lWaiting++; };
};

//=====================================================================
//=====================================================================
// Defines CMemAllocator
//
// this is an allocator based on CBaseAllocator that allocates sample
// buffers in main memory (from 'new'). You must call SetProperties
// before calling Commit.
//
// we don't free the memory when going into Decommit state. The simplest
// way to implement this without complicating CBaseAllocator is to
// have a Free() function, called to go into decommit state, that does
// nothing and a ReallyFree function called from our destructor that
// actually frees the memory.
//=====================================================================
//=====================================================================

//  Make me one from quartz.dll
STDAPI CreateMemoryAllocator(IMemAllocator **ppAllocator);

class CMemAllocator : public CBaseAllocator
{

protected:

    LPBYTE m_pBuffer;   // combined memory for all buffers

    // override to free the memory when decommit completes
    // - we actually do nothing, and save the memory until deletion.
    void Free(void);

    // called from the destructor (and from Alloc if changing size/count) to
    // actually free up the memory
    void ReallyFree(void);

    // overriden to allocate the memory when commit called
    HRESULT Alloc(void);

public:
    /* This goes in the factory template table to create new instances */
    static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *);

    STDMETHODIMP SetProperties(
    ALLOCATOR_PROPERTIES* pRequest,
    ALLOCATOR_PROPERTIES* pActual);

    CMemAllocator(TCHAR *, LPUNKNOWN, HRESULT *);
    ~CMemAllocator();
};

// helper used by IAMovieSetup implementation
STDAPI
AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata
                         , IFilterMapper *                  pIFM
                         , BOOL                             bRegister  );

#endif /* __FILTER__ */


AMVIDEO.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Helper functions for bitmap formats, March 1995

#include <streams.h>
#include <limits.h>

// These are bit field masks for true colour devices

const DWORD bits555[] = {0x007C00,0x0003E0,0x00001F};
const DWORD bits565[] = {0x00F800,0x0007E0,0x00001F};
const DWORD bits888[] = {0xFF0000,0x00FF00,0x0000FF};

// This maps bitmap subtypes into a bits per pixel value and also a name

const struct {
    const GUID *pSubtype;
    WORD BitCount;
    TCHAR *pName;
} BitCountMap[] = { &MEDIASUBTYPE_RGB1,        1,   TEXT("RGB Monochrome"),
                    &MEDIASUBTYPE_RGB4,        4,   TEXT("RGB VGA"),
                    &MEDIASUBTYPE_RGB8,        8,   TEXT("RGB 8"),
                    &MEDIASUBTYPE_RGB565,      16,  TEXT("RGB 565 (16 bit)"),
                    &MEDIASUBTYPE_RGB555,      16,  TEXT("RGB 555 (16 bit)"),
                    &MEDIASUBTYPE_RGB24,       24,  TEXT("RGB 24"),
                    &MEDIASUBTYPE_RGB32,       32,  TEXT("RGB 32"),
                    &MEDIASUBTYPE_Overlay,     0,   TEXT("Overlay"),
                    &GUID_NULL,                0,   TEXT("UNKNOWN") };

// Return the size of the bitmap as defined by this header

STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader)
{
    return DIBSIZE(*pHeader);
}

// This is called if the header has a 16 bit colour depth and needs to work
// out the detailed type from the bit fields (either RGB 565 or RGB 555)

STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader)
{
    BITMAPINFO *pbmInfo = (BITMAPINFO *) pbmiHeader;
    ASSERT(pbmiHeader->biBitCount == 16);

    // If its BI_RGB then it's RGB 555 by default

    if (pbmiHeader->biCompression == BI_RGB) {
        return MEDIASUBTYPE_RGB555;
    }

    // Compare the bit fields with RGB 555

    DWORD *pMask = (DWORD *) pbmInfo->bmiColors;
    if (pMask[0] == bits555[0]) {
        if (pMask[1] == bits555[1]) {
            if (pMask[2] == bits555[2]) {
                return MEDIASUBTYPE_RGB555;
            }
        }
    }

    // Compare the bit fields with RGB 565

    pMask = (DWORD *) pbmInfo->bmiColors;
    if (pMask[0] == bits565[0]) {
        if (pMask[1] == bits565[1]) {
            if (pMask[2] == bits565[2]) {
                return MEDIASUBTYPE_RGB565;
            }
        }
    }
    return GUID_NULL;
}

// Given a BITMAPINFOHEADER structure this returns the GUID sub type that is
// used to describe it in format negotiations. For example a video codec fills
// in the format block with a VIDEOINFO structure, it also fills in the major
// type with MEDIATYPE_VIDEO and the subtype with a GUID that matches the bit
// count, for example if it is an eight bit image then MEDIASUBTYPE_RGB8

STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader)
{
    ASSERT(pbmiHeader);

    // If it's not RGB then create a GUID from the compression type

    if (pbmiHeader->biCompression != BI_RGB) {
        if (pbmiHeader->biCompression != BI_BITFIELDS) {
            FOURCCMap FourCCMap(pbmiHeader->biCompression);
            return (const GUID) FourCCMap;
        }
    }

    // Map the RGB DIB bit depth to a image GUID

    switch(pbmiHeader->biBitCount) {
        case 1    :   return MEDIASUBTYPE_RGB1;
        case 4    :   return MEDIASUBTYPE_RGB4;
        case 8    :   return MEDIASUBTYPE_RGB8;
        case 16   :   return GetTrueColorType(pbmiHeader);
        case 24   :   return MEDIASUBTYPE_RGB24;
        case 32   :   return MEDIASUBTYPE_RGB32;
    }
    return GUID_NULL;
}

// Given a video bitmap subtype we return the number of bits per pixel it uses
// We return a WORD bit count as thats what the BITMAPINFOHEADER uses. If the
// GUID subtype is not found in the table we return an invalid USHRT_MAX

STDAPI_(WORD) GetBitCount(const GUID *pSubtype)
{
    ASSERT(pSubtype);
    const GUID *pMediaSubtype;
    INT iPosition = 0;

    // Scan the mapping list seeing if the source GUID matches any known
    // bitmap subtypes, the list is terminated by a GUID_NULL entry

    while (TRUE) {
        pMediaSubtype = BitCountMap[iPosition].pSubtype;
        if (IsEqualGUID(*pMediaSubtype,GUID_NULL)) {
            return USHRT_MAX;
        }
        if (IsEqualGUID(*pMediaSubtype,*pSubtype)) {
            return BitCountMap[iPosition].BitCount;
        }
        iPosition++;
    }
}

// Given a bitmap subtype we return a description name that can be used for
// debug purposes. In a retail build this function still returns the names
// If the subtype isn't found in the lookup table we return string UNKNOWN

STDAPI_(TCHAR *) GetSubtypeName(const GUID *pSubtype)
{
    ASSERT(pSubtype);
    const GUID *pMediaSubtype;
    INT iPosition = 0;

    // Scan the mapping list seeing if the source GUID matches any known
    // bitmap subtypes, the list is terminated by a GUID_NULL entry

    while (TRUE) {
        pMediaSubtype = BitCountMap[iPosition].pSubtype;
        if (IsEqualGUID(*pMediaSubtype,*pSubtype)) {
            return BitCountMap[iPosition].pName;
        }
        if (IsEqualGUID(*pMediaSubtype,GUID_NULL)) {
            return TEXT("UNKNOWN");
        }
        iPosition++;
    }
}

// The mechanism for describing a bitmap format is with the BITMAPINFOHEADER
// This is really messy to deal with because it invariably has fields that
// follow it holding bit fields, palettes and the rest. This function gives
// the number of bytes required to hold a VIDEOINFO that represents it. This
// count includes the prefix information (like the rcSource rectangle) the
// BITMAPINFOHEADER field, and any other colour information on the end.
//
// WARNING If you want to copy a BITMAPINFOHEADER into a VIDEOINFO always make
// sure that you use the HEADER macro because the BITMAPINFOHEADER field isn't
// right at the start of the VIDEOINFO (there are a number of other fields),
//
//     CopyMemory(HEADER(pVideoInfo),pbmi,sizeof(BITMAPINFOHEADER));
//

STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader)
{
    // Everyone has this to start with this
    LONG Size = SIZE_PREHEADER + pHeader->biSize;

    ASSERT(pHeader->biSize >= sizeof(BITMAPINFOHEADER));

    // Does this format use a palette, if the number of colours actually used
    // is zero then it is set to the maximum that are allowed for that colour
    // depth (an example is 256 for eight bits). Truecolour formats may also
    // pass a palette with them in which case the used count is non zero

    // This would scare me.
    ASSERT(pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed == 0);

    if (pHeader->biBitCount <= iPALETTE || pHeader->biClrUsed) {
        LONG Entries = (DWORD) 1 << pHeader->biBitCount;
        if (pHeader->biClrUsed) {
            Entries = pHeader->biClrUsed;
        }
        Size += Entries * sizeof(RGBQUAD);
    }

    // Truecolour formats may have a BI_BITFIELDS specifier for compression
    // type which means that room for three DWORDs should be allocated that
    // specify where in each pixel the RGB colour components may be found

    if (pHeader->biCompression == BI_BITFIELDS) {
        Size += SIZE_MASKS;
    }

    // A BITMAPINFO for a palettised image may also contain a palette map that
    // provides the information to map from a source palette to a destination
    // palette during a BitBlt for example, because this information is only
    // ever processed during drawing you don't normally store the palette map
    // nor have any way of knowing if it is present in the data structure

    return Size;
}

// Returns TRUE if the VIDEOINFO contains a palette

STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo)
{
    if (PALETTISED(pVideoInfo) == FALSE) {
        if (pVideoInfo->bmiHeader.biClrUsed == 0) {
            return FALSE;
        }
    }
    return TRUE;
}

// Return a pointer to the first entry in a palette

STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo)
{
    if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) {
        return TRUECOLOR(pVideoInfo)->bmiColors;
    }
    return COLORS(pVideoInfo);
}


CACHE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Non MFC based generic cache class, January 1995

/* This class implements a simple cache. A cache object is instantiated
   with the number of items it is to hold. An item is a pointer to an
   object derived from CBaseObject (helps reduce memory leaks). The cache
   can then have objects added to it and removed from it. The cache size
   is fixed at construction time and may therefore run out or be flooded.
   If it runs out it returns a NULL pointer, if it fills up it also returns
   a NULL pointer instead of a pointer to the object just inserted */

#include <streams.h>

/* Constructor creates an array of pointers (to CBaseObject derived objects)
   and then zero fills each position. The cache cannot be resized dynamically
   so if the number of objects that you use varies widely during the lifetime
   of the cache then there will be some inefficiency. Making the cache resize
   would be an expensive operation especially with so little to be gained */

CCache::CCache(TCHAR *pName, INT iItems) :
    CBaseObject(pName),
    m_iCacheSize(iItems),
    m_iUsed(0)
{
    ASSERT(iItems > 0);

    /* Create the array of pointers and set the cache size, if this does not
       succeed then it should probably throw an exception otherwise there is
       no way to propogate the error condition back to creating object */

    m_ppObjects = (CBaseObject **) new BYTE[iItems * sizeof(CBaseObject *)];
    ASSERT(m_ppObjects);

    ZeroMemory((PVOID) m_ppObjects,iItems * sizeof(CBaseObject *));
}

/* Destructor causes the cached objects to be deleted (if not already done)
   and then deletes the (variable length) array that was used to hold them */

CCache::~CCache()
{
    RemoveAll();
    delete m_ppObjects;
}

/* Add an item to the cache, if no space is found for it then we return NULL
   otherwise we put the object in the free space. The object is meant to be
   as small and lightweight as possible so it does not do any multithread
   protection. This is assumed to be handled by the owning (list) object */

CBaseObject *CCache::AddToCache(CBaseObject *pObject)
{
    /* Have we any room for this object */

    if (m_iUsed == m_iCacheSize) {
        return NULL;
    }

    /* Add the object to the end of the cache */

    m_ppObjects[m_iUsed] = pObject;
    m_iUsed++;
    return pObject;
}

/* This is called to retrieve one of the objects we were given to look after
   if there are none available then we return NULL otherwise the return the
   object. NOTE we do not do any processing on the object so it will be in
   exactly the same state when it is handed back as when it is installed */

CBaseObject *CCache::RemoveFromCache()
{
    /* Are all the cache positions empty */

    if (m_iUsed == 0) {
        return NULL;
    }

    /* Return the last object put into the cache */

    m_iUsed--;
    ASSERT(m_ppObjects[m_iUsed]);
    return m_ppObjects[m_iUsed];
}

/* Delete all the objects held in the cache, simply scans the list of objects
   in the cache and deletes each of them in turn. The list class uses a cache
   to manage node objects that are frequently created and destroyed and when
   the list is finally deleted we will also go at which point we also delete
   any remaining node objects. The objects must be derived from CBaseObject */

void CCache::RemoveAll()
{
    while (m_iUsed--) {
        ASSERT(m_ppObjects[m_iUsed]);
        delete m_ppObjects[m_iUsed];
    }
}


CACHE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Non MFC based generic cache class, January 1995

/* This class implements a simple cache. A cache object is instantiated
   with the number of items it is to hold. An item is a pointer to an
   object derived from CBaseObject (helps reduce memory leaks). The cache
   can then have objects added to it and removed from it. The cache size
   is fixed at construction time and may therefore run out or be flooded.
   If it runs out it returns a NULL pointer, if it fills up it also returns
   a NULL pointer instead of a pointer to the object just inserted */

/* Making these classes inherit from CBaseObject does nothing for their
   functionality but it allows us to check there are no memory leaks */

/* WARNING Be very careful when using this class, what it lets you do is
   store and retrieve objects so that you can minimise object creation
   which in turns improves efficiency. However the object you store is
   exactly the same as the object you get back which means that it short
   circuits the constructor initialisation phase. This means any class
   variables the object has (eg pointers) are highly likely to be invalid.
   Therefore ensure you reinitialise the object before using it again */

#ifndef __CACHE__
#define __CACHE__

class CCache : CBaseObject {

    /* Make copy constructor and assignment operator inaccessible */

    CCache(const CCache &refCache);
    CCache &operator=(const CCache &refCache);

private:

    /* These are initialised in the constructor. The first variable points to
       an array of pointers, each of which points to a CBaseObject derived
       object. The m_iCacheSize is the static fixed size for the cache and the
       m_iUsed defines the number of places filled with objects at any time.
       We fill the array of pointers from the start (ie m_ppObjects[0] first)
       and then only add and remove objects from the end position, so in this
       respect the array of object pointers should be treated as a stack */

    CBaseObject **m_ppObjects;
    const INT m_iCacheSize;
    INT m_iUsed;

public:

    CCache(TCHAR *pName,INT iItems);
    virtual ~CCache();

    /* Add an item to the cache */
    CBaseObject *AddToCache(CBaseObject *pObject);

    /* Remove an item from the cache */
    CBaseObject *RemoveFromCache();

    /* Delete all the objects held in the cache */
    void RemoveAll(void);

    /* Return the cache size which is set during construction */
    INT GetCacheSize(void) const {return m_iCacheSize;};
};

#endif /* __CACHE__ */


COMBASE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class hierachy for creating COM objects, December 1994

#include <streams.h>
#pragma warning( disable : 4514 )   // Disable warnings re unused inline functions

/* Define the static member variable */

LONG CBaseObject::m_cObjects = 0;

/* Constructor */

CBaseObject::CBaseObject(const TCHAR *pName)
{
    /* Increment the number of active objects */
    InterlockedIncrement(&m_cObjects);

#ifdef DEBUG
    m_dwCookie = DbgRegisterObjectCreation(pName);
#endif
}

HINSTANCEhlibOLEAut32;

/* Destructor */

CBaseObject::~CBaseObject()
{
    /* Decrement the number of objects active */
    if (InterlockedDecrement(&m_cObjects) == 0) {
if (hlibOLEAut32) {
    FreeLibrary(hlibOLEAut32);

    hlibOLEAut32 = 0;
}
    };

#ifdef DEBUG
    DbgRegisterObjectDestruction(m_dwCookie);
#endif
}

static const TCHAR szOle32Aut[]   = TEXT("OleAut32.dll");

HINSTANCE LoadOLEAut32()
{
    if (hlibOLEAut32 == 0) {

hlibOLEAut32 = LoadLibrary(szOle32Aut);
    }

    return hlibOLEAut32;
}

/* Constructor */

// We know we use "this" in the initialization list, we also know we don't modify *phr.
#pragma warning( disable : 4355 4100 )
CUnknown::CUnknown(const TCHAR *pName, LPUNKNOWN pUnk)
: CBaseObject(pName)
/* Start the object with a reference count of zero - when the      */
/* object is queried for it's first interface this may be          */
/* incremented depending on whether or not this object is          */
/* currently being aggregated upon                                 */
, m_cRef(0)
/* Set our pointer to our IUnknown interface.                      */
/* If we have an outer, use its, otherwise use ours.               */
/* This pointer effectivly points to the owner of                  */
/* this object and can be accessed by the GetOwner() method.       */
, m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) )
 /* Why the double cast?  Well, the inner cast is a type-safe cast */
 /* to pointer to a type from which we inherit.  The second is     */
 /* type-unsafe but works because INonDelegatingUnknown "behaves   */
 /* like" IUnknown. (Only the names on the methods change.)        */
{
    // Everything we need to do has been done in the initializer list
}

// This does the same as above except it has a useless HRESULT argument
// use the previous constructor, this is just left for compatibility...
CUnknown::CUnknown(TCHAR *pName, LPUNKNOWN pUnk,HRESULT *phr) :
    CBaseObject(pName),
    m_cRef(0),
    m_pUnknown( pUnk != 0 ? pUnk : reinterpret_cast<LPUNKNOWN>( static_cast<PNDUNKNOWN>(this) ) )
{
}
#pragma warning( default : 4355 4100 )

/* QueryInterface */

STDMETHODIMP CUnknown::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    CheckPointer(ppv,E_POINTER);
    ValidateReadWritePtr(ppv,sizeof(PVOID));

    /* We know only about IUnknown */

    if (riid == IID_IUnknown) {
        GetInterface((LPUNKNOWN) (PNDUNKNOWN) this, ppv);
        return NOERROR;
    } else {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

/* We have to ensure that we DON'T use a max macro, since these will typically   */
/* lead to one of the parameters being evaluated twice.  Since we are worried    */
/* about concurrency, we can't afford to access the m_cRef twice since we can't  */
/* afford to run the risk that its value having changed between accesses.        */
#ifdef max
    #undef max
#endif

template<class T> inline static T max( const T & a, const T & b )
{
    return a > b ? a : b;
}

/* AddRef */

STDMETHODIMP_(ULONG) CUnknown::NonDelegatingAddRef()
{
    LONG lRef = InterlockedIncrement( &m_cRef );
    ASSERT(lRef > 0);
    DbgLog((LOG_MEMORY,3,TEXT("    Obj %d ref++ = %d"),
           m_dwCookie, m_cRef));
    return max(ULONG(m_cRef), 1ul);
}

/* Release */

STDMETHODIMP_(ULONG) CUnknown::NonDelegatingRelease()
{
    /* If the reference count drops to zero delete ourselves */

    LONG lRef = InterlockedDecrement( &m_cRef );
    ASSERT(lRef >= 0);

    DbgLog((LOG_MEMORY,3,TEXT("    Object %d ref-- = %d"),
    m_dwCookie, m_cRef));
    if (lRef == 0) {

        // COM rules say we must protect against re-entrancy.
        // If we are an aggregator and we hold our own interfaces
        // on the aggregatee, the QI for these interfaces will
        // addref ourselves. So after doing the QI we must release
        // a ref count on ourselves. Then, before releasing the
        // private interface, we must addref ourselves. When we do
        // this from the destructor here it will result in the ref
        // count going to 1 and then back to 0 causing us to
        // re-enter the destructor. Hence we add an extra refcount here
        // once we know we will delete the object.
        // for an example aggregator see filgraph\distrib.cpp.

        m_cRef++;

        delete this;
        return ULONG(0);
    } else {
        return max(ULONG(m_cRef), 1ul);
    }
}

/* Return an interface pointer to a requesting client
   performing a thread safe AddRef as necessary */

STDAPI GetInterface(LPUNKNOWN pUnk, void **ppv)
{
    CheckPointer(ppv, E_POINTER);
    *ppv = pUnk;
    pUnk->AddRef();
    return NOERROR;
}

/* Compares two interfaces and returns TRUE if they are on the same object */

BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond)
{
    /*  Different objects can't have the same interface pointer for
        any interface
    */
    if (pFirst == pSecond) {
        return TRUE;
    }
    /*  OK - do it the hard way - check if they have the same
        IUnknown pointers - a single object can only have one of these
    */
    LPUNKNOWN pUnknown1;     // Retrieve the IUnknown interface
    LPUNKNOWN pUnknown2;     // Retrieve the other IUnknown interface
    HRESULT hr;              // General OLE return code

    ASSERT(pFirst);
    ASSERT(pSecond);

    /* See if the IUnknown pointers match */

    hr = pFirst->QueryInterface(IID_IUnknown,(void **) &pUnknown1);
    ASSERT(SUCCEEDED(hr));
    ASSERT(pUnknown1);

    hr = pSecond->QueryInterface(IID_IUnknown,(void **) &pUnknown2);
    ASSERT(SUCCEEDED(hr));
    ASSERT(pUnknown2);

    /* Release the extra interfaces we hold */

    pUnknown1->Release();
    pUnknown2->Release();
    return (pUnknown1 == pUnknown2);
}


COMBASE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class hierachy for creating COM objects, December 1994

/*

a. Derive your COM object from CUnknown

b. Make a static CreateInstance function that takes an LPUNKNOWN, an HRESULT *
   and a TCHAR *. The LPUNKNOWN defines the object to delegate IUnknown calls
   to. The HRESULT * allows error codes to be passed around constructors and
   the TCHAR * is a descriptive name that can be printed on the debugger.

   It is important that constructors only change the HRESULT * if they have
   to set an ERROR code, if it was successful then leave it alone or you may
   overwrite an error code from an object previously created.

   When you call a constructor the descriptive name should be in static store
   as we do not copy the string. To stop large amounts of memory being used
   in retail builds by all these static strings use the NAME macro,

   CMyFilter = new CImplFilter(NAME("My filter"),pUnknown,phr);
   if (FAILED(hr)) {
       return hr;
   }

   In retail builds NAME(_x_) compiles to NULL, the base CBaseObject class
   knows not to do anything with objects that don't have a name.

c. Have a constructor for your object that passes the LPUNKNOWN, HRESULT * and
   TCHAR * to the CUnknown constructor. You can set the HRESULT if you have an
   error, or just simply pass it through to the constructor.

   The object creation will fail in the class factory if the HRESULT indicates
   an error (ie FAILED(HRESULT) == TRUE)

d. Create a FactoryTemplate with your object's class id and CreateInstance
   function.

Then (for each interface) either

Multiple inheritance

1. Also derive it from ISomeInterface
2. Include DECLARE_IUNKNOWN in your class definition to declare
   implementations of QueryInterface, AddRef and Release that
   call the outer unknown
3. Override NonDelegatingQueryInterface to expose ISomeInterface by
   code something like

     if (riid == IID_ISomeInterface) {
         return GetInterface((ISomeInterface *) this, ppv);
     } else {
         return CUnknown::NonDelegatingQueryInterface(riid, ppv);
     }

4. Declare and implement the member functions of ISomeInterface.

or: Nested interfaces

1. Declare a class derived from CUnknown
2. Include DECLARE_IUNKNOWN in your class definition
3. Override NonDelegatingQueryInterface to expose ISomeInterface by
   code something like

     if (riid == IID_ISomeInterface) {
         return GetInterface((ISomeInterface *) this, ppv);
     } else {
         return CUnknown::NonDelegatingQueryInterface(riid, ppv);
     }

4. Implement the member functions of ISomeInterface. Use GetOwner() to
   access the COM object class.

And in your COM object class:

5. Make the nested class a friend of the COM object class, and declare
   an instance of the nested class as a member of the COM object class.

   NOTE that because you must always pass the outer unknown and an hResult
   to the CUnknown constructor you cannot use a default constructor, in
   other words you will have to make the member variable a pointer to the
   class and make a NEW call in your constructor to actually create it.

6. override the NonDelegatingQueryInterface with code like this:

     if (riid == IID_ISomeInterface) {
         return m_pImplFilter->
            NonDelegatingQueryInterface(IID_ISomeInterface, ppv);
     } else {
         return CUnknown::NonDelegatingQueryInterface(riid, ppv);
     }

You can have mixed classes which support some interfaces via multiple
inheritance and some via nested classes

*/

#ifndef __COMBASE__
#define __COMBASE__

// Filter Setup data structures no defined in axextend.idl

typedef REGPINTYPES
AMOVIESETUP_MEDIATYPE, * PAMOVIESETUP_MEDIATYPE, * FAR LPAMOVIESETUP_MEDIATYPE;

typedef REGFILTERPINS
AMOVIESETUP_PIN, * PAMOVIESETUP_PIN, * FAR LPAMOVIESETUP_PIN;

typedef struct _AMOVIESETUP_FILTER
{
  const CLSID * clsID;
  const WCHAR * strName;
  DWORD      dwMerit;
  UINT       nPins;
  const AMOVIESETUP_PIN * lpPin;
}
AMOVIESETUP_FILTER, * PAMOVIESETUP_FILTER, * FAR LPAMOVIESETUP_FILTER;

/* The DLLENTRY module initialises the module handle on loading */

extern HINSTANCE g_hInst;

/* On DLL load remember which platform we are running on */

extern DWORD g_amPlatform;
extern OSVERSIONINFO g_osInfo;     // Filled in by GetVersionEx

/* Version of IUnknown that is renamed to allow a class to support both
   non delegating and delegating IUnknowns in the same COM object */

#ifndef INONDELEGATINGUNKNOWN_DEFINED
DECLARE_INTERFACE(INonDelegatingUnknown)
{
    STDMETHOD(NonDelegatingQueryInterface) (THIS_ REFIID, LPVOID *) PURE;
    STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE;
    STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE;
};
#define INONDELEGATINGUNKNOWN_DEFINED
#endif

typedef INonDelegatingUnknown *PNDUNKNOWN;

/* This is the base object class that supports active object counting. As
   part of the debug facilities we trace every time a C++ object is created
   or destroyed. The name of the object has to be passed up through the class
   derivation list during construction as you cannot call virtual functions
   in the constructor. The downside of all this is that every single object
   constructor has to take an object name parameter that describes it */

class CBaseObject
{

private:

    // Disable the copy constructor and assignment by default so you will get
    //   compiler errors instead of unexpected behaviour if you pass objects
    //   by value or assign objects.
    CBaseObject(const CBaseObject& objectSrc);          // no implementation
    void operator=(const CBaseObject& objectSrc);       // no implementation

private:
    static LONG m_cObjects;     /* Total number of objects active */

protected:
#ifdef DEBUG
    DWORD m_dwCookie;           /* Cookie identifying this object */
#endif

public:

    /* These increment and decrement the number of active objects */

    CBaseObject(const TCHAR *pName);
    ~CBaseObject();

    /* Call this to find if there are any CUnknown derived objects active */

    static LONG ObjectsActive() {
        return m_cObjects;
    };
};

/* An object that supports one or more COM interfaces will be based on
   this class. It supports counting of total objects for DLLCanUnloadNow
   support, and an implementation of the core non delegating IUnknown */

class AM_NOVTABLE CUnknown : public INonDelegatingUnknown,
                 public CBaseObject
{
private:
    const LPUNKNOWN m_pUnknown; /* Owner of this object */

protected:                      /* So we can override NonDelegatingRelease() */
    volatile LONG m_cRef;       /* Number of reference counts */

public:

    CUnknown(const TCHAR *pName, LPUNKNOWN pUnk);
    virtual ~CUnknown() {};

    // This is redundant, just use the other constructor
    //   as we never touch the HRESULT in this anyway
    CUnknown(TCHAR *pName, LPUNKNOWN pUnk,HRESULT *phr);

    /* Return the owner of this object */

    LPUNKNOWN GetOwner() const {
        return m_pUnknown;
    };

    /* Called from the class factory to create a new instance, it is
       pure virtual so it must be overriden in your derived class */

    /* static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *) */

    /* Non delegating unknown implementation */

    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **);
    STDMETHODIMP_(ULONG) NonDelegatingAddRef();
    STDMETHODIMP_(ULONG) NonDelegatingRelease();
};

/* Return an interface pointer to a requesting client
   performing a thread safe AddRef as necessary */

STDAPI GetInterface(LPUNKNOWN pUnk, void **ppv);

/* The standard InterlockedXXX functions won't take volatiles */
static inline LONG WINAPI InterlockedIncrement( volatile LONG * plong )
{ return InterlockedIncrement( const_cast<LONG*>( plong ) ); }

static inline LONG WINAPI InterlockedDecrement( volatile LONG * plong )
{ return InterlockedDecrement( const_cast<LONG*>( plong ) ); }

static inline LONG InterlockedExchange( volatile LONG * plong, LONG new_value )
{ return InterlockedExchange( const_cast<LONG*>( plong ), new_value ); }

/* A function that can create a new COM object */

typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(LPUNKNOWN pUnkOuter, HRESULT *phr);

/*  A function (can be NULL) which is called from the DLL entrypoint
    routine for each factory template:

    bLoading - TRUE on DLL load, FALSE on DLL unload
    rclsid   - the m_ClsID of the entry
*/
typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid);

/* Create one of these per object class in an array so that
   the default class factory code can create new instances */

class CFactoryTemplate {

public:

    const WCHAR *              m_Name;
    const CLSID *              m_ClsID;
    LPFNNewCOMObject           m_lpfnNew;
    LPFNInitRoutine            m_lpfnInit;
    const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter;

    BOOL IsClassID(REFCLSID rclsid) const {
        return (IsEqualCLSID(*m_ClsID,rclsid));
    };

    CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) const {
        CheckPointer(phr,NULL);
        return m_lpfnNew(pUnk, phr);
    };
};

/* You must override the (pure virtual) NonDelegatingQueryInterface to return
   interface pointers (using GetInterface) to the interfaces your derived
   class supports (the default implementation only supports IUnknown) */

#define DECLARE_IUNKNOWN                                        \
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {      \
        return GetOwner()->QueryInterface(riid,ppv);            \
    };                                                          \
    STDMETHODIMP_(ULONG) AddRef() {                             \
        return GetOwner()->AddRef();                            \
    };                                                          \
    STDMETHODIMP_(ULONG) Release() {                            \
        return GetOwner()->Release();                           \
    };

HINSTANCELoadOLEAut32();

#endif /* __COMBASE__ */


CPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>

// Constructor for the base property page class. As described in the header
// file we must be initialised with dialog and title resource identifiers.
// The class supports IPropertyPage and overrides AddRef and Release calls
// to keep track of the reference counts. When the last count is released
// we call SetPageSite(NULL) and SetObjects(0,NULL) to release interfaces
// previously obtained by the property page when it had SetObjects called

CBasePropertyPage::CBasePropertyPage(TCHAR *pName,      // Debug only name
                                     LPUNKNOWN pUnk,    // COM Delegator
                                     int DialogId,      // Resource ID
                                     int TitleId) :     // To get tital
    CUnknown(pName,pUnk),
    m_DialogId(DialogId),
    m_TitleId(TitleId),
    m_hwnd(NULL),
    m_Dlg(NULL),
    m_pPageSite(NULL),
    m_bObjectSet(FALSE),
    m_bDirty(FALSE)
{
}

// Increment our reference count

STDMETHODIMP_(ULONG) CBasePropertyPage::NonDelegatingAddRef()
{
    LONG lRef = InterlockedIncrement(&m_cRef);
    ASSERT(lRef > 0);
    return max(ULONG(m_cRef),1ul);
}

// Release a reference count and protect against reentrancy

STDMETHODIMP_(ULONG) CBasePropertyPage::NonDelegatingRelease()
{
    // If the reference count drops to zero delete ourselves

    if (InterlockedDecrement(&m_cRef) == 0) {
        m_cRef++;
        SetPageSite(NULL);
        SetObjects(0,NULL);
        delete this;
        return ULONG(0);
    } else {
        return max(ULONG(m_cRef),1ul);
    }
}

// Expose our IPropertyPage interface

STDMETHODIMP
CBasePropertyPage::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    if (riid == IID_IPropertyPage) {
        return GetInterface((IPropertyPage *)this,ppv);
    } else {
        return CUnknown::NonDelegatingQueryInterface(riid,ppv);
    }
}

// Get the page info so that the page site can size itself

STDMETHODIMP CBasePropertyPage::GetPageInfo(LPPROPPAGEINFO pPageInfo)
{
    CheckPointer(pPageInfo,E_POINTER);
    WCHAR wszTitle[STR_MAX_LENGTH];
    WideStringFromResource(wszTitle,m_TitleId);
    int Length = (lstrlenW(wszTitle) + 1) * sizeof(WCHAR);

    // Allocate dynamic memory for the property page title

    LPOLESTR pszTitle = (LPOLESTR) QzTaskMemAlloc(Length);
    if (pszTitle == NULL) {
        NOTE("No caption memory");
        return E_OUTOFMEMORY;
    }

    CopyMemory(pszTitle,wszTitle,Length);

    pPageInfo->cb               = sizeof(PROPPAGEINFO);
    pPageInfo->pszTitle         = pszTitle;
    pPageInfo->pszDocString     = NULL;
    pPageInfo->pszHelpFile      = NULL;
    pPageInfo->dwHelpContext    = 0;

    // Set defaults in case GetDialogSize fails
    pPageInfo->size.cx          = 340;
    pPageInfo->size.cy          = 150;

    GetDialogSize(m_DialogId,(DLGPROC) DialogProc,0L,&pPageInfo->size);
    return NOERROR;
}

// Handles the messages for our property window

BOOL CALLBACK CBasePropertyPage::DialogProc(HWND hwnd,
                                            UINT uMsg,
                                            WPARAM wParam,
                                            LPARAM lParam)
{
    CBasePropertyPage *pPropertyPage;

    switch (uMsg) {

        case WM_INITDIALOG:

            SetWindowLong(hwnd, DWL_USER, lParam);

            // This pointer may be NULL when calculating size

            pPropertyPage = (CBasePropertyPage *) lParam;
            if (pPropertyPage == NULL) {
                return (LRESULT) 1;
            }
            pPropertyPage->m_Dlg = hwnd;
    }

    // This pointer may be NULL when calculating size

    pPropertyPage = (CBasePropertyPage *) GetWindowLong(hwnd, DWL_USER);
    if (pPropertyPage == NULL) {
        return (LRESULT) 1;
    }
    return pPropertyPage->OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}

// Tells us the object that should be informed of the property changes

STDMETHODIMP CBasePropertyPage::SetObjects(ULONG cObjects,LPUNKNOWN *ppUnk)
{
    if (cObjects == 1) {

        if ((ppUnk == NULL) || (*ppUnk == NULL)) {
            return E_POINTER;
        }

        // Set a flag to say that we have set the Object
        m_bObjectSet = TRUE ;
        return OnConnect(*ppUnk);

    } else if (cObjects == 0) {

        // Set a flag to say that we have not set the Object for the page
        m_bObjectSet = FALSE ;
        return OnDisconnect();
    }

    DbgBreak("No support for more than one object");
    return E_UNEXPECTED;
}

// Create the window we will use to edit properties

STDMETHODIMP CBasePropertyPage::Activate(HWND hwndParent,
                                         LPCRECT pRect,
                                         BOOL fModal)
{
    CheckPointer(pRect,E_POINTER);

    // Return failure if SetObject has not been called.
    if (m_bObjectSet == FALSE) {
        return E_UNEXPECTED;
    }

    if (m_hwnd) {
        return E_UNEXPECTED;
    }

    m_hwnd = CreateDialogParam(g_hInst,
                               MAKEINTRESOURCE(m_DialogId),
                               hwndParent,
                               DialogProc,
                               (LPARAM) this);
    if (m_hwnd == NULL) {
        return E_OUTOFMEMORY;
    }

    // Parent should control us so the user can tab out of property page

    DWORD dwStyle = GetWindowLong(m_hwnd, GWL_EXSTYLE);
    dwStyle = dwStyle | WS_EX_CONTROLPARENT;
    SetWindowLong(m_hwnd, GWL_EXSTYLE, dwStyle);

    OnActivate();
    Move(pRect);
    return Show(SW_SHOWNORMAL);
}

// Set the position of the property page

STDMETHODIMP CBasePropertyPage::Move(LPCRECT pRect)
{
    CheckPointer(pRect,E_POINTER);

    if (m_hwnd == NULL) {
        return E_UNEXPECTED;
    }

    MoveWindow(m_hwnd,              // Property page handle
               pRect->left,         // x coordinate
               pRect->top,          // y coordinate
               WIDTH(pRect),        // Overall window width
               HEIGHT(pRect),       // And likewise height
               TRUE);               // Should we repaint it

    return NOERROR;
}

// Display the property dialog

STDMETHODIMP CBasePropertyPage::Show(UINT nCmdShow)
{
   // Have we been activated yet

    if (m_hwnd == NULL) {
        return E_UNEXPECTED;
    }

    // Ignore wrong show flags

    if ((nCmdShow != SW_SHOW) && (nCmdShow != SW_SHOWNORMAL) && (nCmdShow != SW_HIDE)) {
        return E_INVALIDARG;
    }

    ShowWindow(m_hwnd,nCmdShow);
    InvalidateRect(m_hwnd,NULL,TRUE);
    return NOERROR;
}

// Destroy the property page dialog

STDMETHODIMP CBasePropertyPage::Deactivate(void)
{
    if (m_hwnd == NULL) {
        return E_UNEXPECTED;
    }

    // Remove WS_EX_CONTROLPARENT before DestroyWindow call

    DWORD dwStyle = GetWindowLong(m_hwnd, GWL_EXSTYLE);
    dwStyle = dwStyle & (~WS_EX_CONTROLPARENT);
    SetWindowLong(m_hwnd, GWL_EXSTYLE, dwStyle);

    OnDeactivate();

    // Destroy the dialog window

    DestroyWindow(m_hwnd);
    m_hwnd = NULL;
    return NOERROR;
}

// Tells the application property page site

STDMETHODIMP CBasePropertyPage::SetPageSite(LPPROPERTYPAGESITE pPageSite)
{
    if (pPageSite) {

        if (m_pPageSite) {
            return E_UNEXPECTED;
        }

        m_pPageSite = pPageSite;
        m_pPageSite->AddRef();

    } else {

        if (m_pPageSite == NULL) {
            return E_UNEXPECTED;
        }

        m_pPageSite->Release();
        m_pPageSite = NULL;
    }
    return NOERROR;
}

// Apply any changes so far made

STDMETHODIMP CBasePropertyPage::Apply()
{
    // In ActiveMovie 1.0 we used to check whether we had been activated or
    // not. This is too constrictive. Apply should be allowed as long as
    // SetObject was called to set an object. So we will no longer check to
    // see if we have been activated (ie., m_hWnd != NULL), but instead
    // make sure that m_bObjectSet is TRUE (ie., SetObject has been called).

    if (m_bObjectSet == FALSE) {
        return E_UNEXPECTED;
    }

    // Must have had a site set

    if (m_pPageSite == NULL) {
        return E_UNEXPECTED;
    }

    // Has anything changed

    if (m_bDirty == FALSE) {
        return NOERROR;
    }

    // Commit derived class changes

    HRESULT hr = OnApplyChanges();
    if (SUCCEEDED(hr)) {
        m_bDirty = FALSE;
    }
    return hr;
}

// Base class definition for message handling

BOOL CBasePropertyPage::OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
}


CPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __CPROP__
#define __CPROP__

// Base property page class. Filters typically expose custom properties by
// implementing special control interfaces, examples are IDirectDrawVideo
// and IQualProp on renderers. This allows property pages to be built that
// use the given interface. Applications such as the ActiveMovie OCX query
// filters for the property pages they support and expose them to the user
//
// This class provides all the framework for a property page. A property
// page is a COM object that supports IPropertyPage. We should be created
// with a resource ID for the dialog which we will load when required. We
// should also be given in the constructor a resource ID for a title string
// we will load from the DLLs STRINGTABLE. The property page titles must be
// stored in resource files so that they can be easily internationalised
//
// We have a number of virtual methods (not PURE) that may be overriden in
// derived classes to query for interfaces and so on. These functions have
// simple implementations here that just return NOERROR. Derived classes
// will almost definately have to override the message handler method called
// OnReceiveMessage. We have a static dialog procedure that calls the method
// so that derived classes don't have to fiddle around with the this pointer

class AM_NOVTABLE CBasePropertyPage : public IPropertyPage, public CUnknown
{
protected:

    LPPROPERTYPAGESITE m_pPageSite;       // Details for our property site
    HWND m_hwnd;                          // Window handle for the page
    HWND m_Dlg;                           // Actual dialog window handle
    BOOL m_bDirty;                        // Has anything been changed
    int m_TitleId;                        // Resource identifier for title
    int m_DialogId;                       // Dialog resource identifier

    static BOOL CALLBACK DialogProc(HWND hwnd,
                                    UINT uMsg,
                                    WPARAM wParam,
                                    LPARAM lParam);

private:
    BOOL m_bObjectSet ;                  // SetObject has been called or not.
public:

    CBasePropertyPage(TCHAR *pName,      // Debug only name
                      LPUNKNOWN pUnk,    // COM Delegator
                      int DialogId,      // Resource ID
                      int TitleId);      // To get tital

    virtual ~CBasePropertyPage() { };
    DECLARE_IUNKNOWN

    // Override these virtual methods

    virtual HRESULT OnConnect(IUnknown *pUnknown) { return NOERROR; };
    virtual HRESULT OnDisconnect() { return NOERROR; };
    virtual HRESULT OnActivate() { return NOERROR; };
    virtual HRESULT OnDeactivate() { return NOERROR; };
    virtual HRESULT OnApplyChanges() { return NOERROR; };
    virtual BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

    // These implement an IPropertyPage interface

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv);
    STDMETHODIMP_(ULONG) NonDelegatingRelease();
    STDMETHODIMP_(ULONG) NonDelegatingAddRef();
    STDMETHODIMP SetPageSite(LPPROPERTYPAGESITE pPageSite);
    STDMETHODIMP Activate(HWND hwndParent,LPCRECT prect,BOOL fModal);
    STDMETHODIMP Deactivate(void);
    STDMETHODIMP GetPageInfo(LPPROPPAGEINFO pPageInfo);
    STDMETHODIMP SetObjects(ULONG cObjects, LPUNKNOWN *ppUnk);
    STDMETHODIMP Show(UINT nCmdShow);
    STDMETHODIMP Move(LPCRECT prect);
    STDMETHODIMP IsPageDirty(void) { return m_bDirty ? S_OK : S_FALSE; }
    STDMETHODIMP Apply(void);
    STDMETHODIMP Help(LPCWSTR lpszHelpDir) { return E_NOTIMPL; }
    STDMETHODIMP TranslateAccelerator(LPMSG lpMsg) { return E_NOTIMPL; }
};

#endif // __CPROP__


CTLUTIL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base classes implementing IDispatch parsing for the basic control dual
// interfaces. Derive from these and implement just the custom method and
// property methods. We also implement CPosPassThru that can be used by
// renderers and transforms to pass by IMediaPosition and IMediaSeeking

#include <streams.h>
#include <limits.h>
#include "seekpt.h"

// 'bool' non standard reserved word
#pragma warning(disable:4237)

// --- CBaseDispatch implementation ----------
CBaseDispatch::~CBaseDispatch()
{
    if (m_pti) {
m_pti->Release();
    }
}

// return 1 if we support GetTypeInfo

STDMETHODIMP
CBaseDispatch::GetTypeInfoCount(UINT * pctinfo)
{
    CheckPointer(pctinfo,E_POINTER);
    ValidateReadWritePtr(pctinfo,sizeof(UINT *));
    *pctinfo = 1;
    return S_OK;
}

typedef HRESULT (STDAPICALLTYPE *LPLOADTYPELIB)(
    const OLECHAR FAR *szFile,
    ITypeLib FAR* FAR* pptlib);

typedef HRESULT (STDAPICALLTYPE *LPLOADREGTYPELIB)(REFGUID rguid,
    WORD wVerMajor,
    WORD wVerMinor,
    LCID lcid,
    ITypeLib FAR* FAR* pptlib);

// attempt to find our type library

STDMETHODIMP
CBaseDispatch::GetTypeInfo(
  REFIID riid,
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    CheckPointer(pptinfo,E_POINTER);
    ValidateReadWritePtr(pptinfo,sizeof(ITypeInfo *));
    HRESULT hr;

    *pptinfo = NULL;

    // we only support one type element
    if (0 != itinfo) {
return TYPE_E_ELEMENTNOTFOUND;
    }

    if (NULL == pptinfo) {
return E_POINTER;
    }

    // always look for neutral
    if (NULL == m_pti) {

LPLOADTYPELIB    lpfnLoadTypeLib;
LPLOADREGTYPELIB    lpfnLoadRegTypeLib;
ITypeLib    *ptlib;
HINSTANCE    hInst;

static const char  szTypeLib[]  = "LoadTypeLib";
static const char  szRegTypeLib[] = "LoadRegTypeLib";
static const WCHAR szControl[]  = L"control.tlb";

//
// Try to get the Ole32Aut.dll module handle.
//

hInst = LoadOLEAut32();
if (hInst == NULL) {
    DWORD dwError = GetLastError();
    return HRESULT_FROM_WIN32(dwError);
}
lpfnLoadRegTypeLib = (LPLOADREGTYPELIB)GetProcAddress(hInst,
      szRegTypeLib);
if (lpfnLoadRegTypeLib == NULL) {
    DWORD dwError = GetLastError();
    return HRESULT_FROM_WIN32(dwError);
}

hr = (*lpfnLoadRegTypeLib)(LIBID_QuartzTypeLib, 1, 0, // version 1.0
   lcid, &ptlib);

if (FAILED(hr)) {

    // attempt to load directly - this will fill the
    // registry in if it finds it

    lpfnLoadTypeLib = (LPLOADTYPELIB)GetProcAddress(hInst, szTypeLib);
    if (lpfnLoadTypeLib == NULL) {
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
    }

    hr = (*lpfnLoadTypeLib)(szControl, &ptlib);
    if (FAILED(hr)) {
return hr;
    }
}

hr = ptlib->GetTypeInfoOfGuid(
    riid,
    &m_pti);

ptlib->Release();

if (FAILED(hr)) {
    return hr;
}
    }

    *pptinfo = m_pti;
    m_pti->AddRef();
    return S_OK;
}

STDMETHODIMP
CBaseDispatch::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    // although the IDispatch riid is dead, we use this to pass from
    // the interface implementation class to us the iid we are talking about.

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(riid, 0, lcid, &pti);

    if (SUCCEEDED(hr)) {
hr = pti->GetIDsOfNames(rgszNames, cNames, rgdispid);

pti->Release();
    }
    return hr;
}

// --- CMediaControl implementation ---------

CMediaControl::CMediaControl(const TCHAR * name,LPUNKNOWN pUnk) :
    CUnknown(name, pUnk)
{
}

// expose our interfaces IMediaControl and IUnknown

STDMETHODIMP
CMediaControl::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IMediaControl) {
return GetInterface( (IMediaControl *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

// return 1 if we support GetTypeInfo

STDMETHODIMP
CMediaControl::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

// attempt to find our type library

STDMETHODIMP
CMediaControl::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IMediaControl,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CMediaControl::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IMediaControl,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CMediaControl::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IMediaControl *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- CMediaEvent implementation ----------

CMediaEvent::CMediaEvent(const TCHAR * name,LPUNKNOWN pUnk) :
    CUnknown(name, pUnk)
{
}

// expose our interfaces IMediaEvent and IUnknown

STDMETHODIMP
CMediaEvent::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IMediaEvent || riid == IID_IMediaEventEx) {
return GetInterface( (IMediaEventEx *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

// return 1 if we support GetTypeInfo

STDMETHODIMP
CMediaEvent::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

// attempt to find our type library

STDMETHODIMP
CMediaEvent::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IMediaEvent,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CMediaEvent::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IMediaEvent,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CMediaEvent::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IMediaEvent *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- CMediaPosition implementation ----------

CMediaPosition::CMediaPosition(const TCHAR * name,LPUNKNOWN pUnk) :
    CUnknown(name, pUnk)
{
}

CMediaPosition::CMediaPosition(const TCHAR * name,
                               LPUNKNOWN pUnk,
                               HRESULT * phr) :
    CUnknown(name, pUnk)
{
    UNREFERENCED_PARAMETER(phr);
}

// expose our interfaces IMediaPosition and IUnknown

STDMETHODIMP
CMediaPosition::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IMediaPosition) {
return GetInterface( (IMediaPosition *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

// return 1 if we support GetTypeInfo

STDMETHODIMP
CMediaPosition::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

// attempt to find our type library

STDMETHODIMP
CMediaPosition::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IMediaPosition,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CMediaPosition::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IMediaPosition,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CMediaPosition::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IMediaPosition *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- IMediaPosition and IMediaSeeking pass through class ----------

CPosPassThru::CPosPassThru(const TCHAR *pName,
   LPUNKNOWN pUnk,
   HRESULT *phr,
   IPin *pPin) :
    CMediaPosition(pName,pUnk),
    m_pPin(pPin)
{
    if (pPin == NULL) {
*phr = E_POINTER;
return;
    }
}

// Expose our IMediaSeeking and IMediaPosition interfaces

STDMETHODIMP
CPosPassThru::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    *ppv = NULL;

    if (riid == IID_IMediaSeeking) {
return GetInterface( static_cast<IMediaSeeking *>(this), ppv);
    }
    return CMediaPosition::NonDelegatingQueryInterface(riid,ppv);
}

// Return the IMediaPosition interface from our peer

HRESULT
CPosPassThru::GetPeer(IMediaPosition ** ppMP)
{
    *ppMP = NULL;

    IPin *pConnected;
    HRESULT hr = m_pPin->ConnectedTo(&pConnected);
    if (FAILED(hr)) {
return E_NOTIMPL;
    }
    IMediaPosition * pMP;
    hr = pConnected->QueryInterface(IID_IMediaPosition, (void **) &pMP);
    pConnected->Release();
    if (FAILED(hr)) {
return E_NOTIMPL;
    }

    *ppMP = pMP;
    return S_OK;
}

// Return the IMediaSeeking interface from our peer

HRESULT
CPosPassThru::GetPeerSeeking(IMediaSeeking ** ppMS)
{
    *ppMS = NULL;

    IPin *pConnected;
    HRESULT hr = m_pPin->ConnectedTo(&pConnected);
    if (FAILED(hr)) {
return E_NOTIMPL;
    }
    IMediaSeeking * pMS;
    hr = pConnected->QueryInterface(IID_IMediaSeeking, (void **) &pMS);
    pConnected->Release();
    if (FAILED(hr)) {
return E_NOTIMPL;
    }

    *ppMS = pMS;
    return S_OK;
}

// --- IMediaSeeking methods ----------

STDMETHODIMP
CPosPassThru::GetCapabilities(DWORD * pCaps)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->GetCapabilities(pCaps);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::CheckCapabilities(DWORD * pCaps)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->CheckCapabilities(pCaps);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::IsFormatSupported(const GUID * pFormat)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->IsFormatSupported(pFormat);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::QueryPreferredFormat(GUID *pFormat)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->QueryPreferredFormat(pFormat);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::SetTimeFormat(const GUID * pFormat)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->SetTimeFormat(pFormat);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::GetTimeFormat(GUID *pFormat)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->GetTimeFormat(pFormat);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::IsUsingTimeFormat(const GUID * pFormat)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->IsUsingTimeFormat(pFormat);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::ConvertTimeFormat(LONGLONG * pTarget, const GUID * pTargetFormat,
LONGLONG    Source, const GUID * pSourceFormat )
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->ConvertTimeFormat(pTarget, pTargetFormat, Source, pSourceFormat );
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::SetPositions( LONGLONG * pCurrent, DWORD CurrentFlags
  , LONGLONG * pStop, DWORD StopFlags )
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->SetPositions(pCurrent, CurrentFlags, pStop, StopFlags );
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::GetPositions(LONGLONG *pCurrent, LONGLONG * pStop)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->GetPositions(pCurrent,pStop);
    pMS->Release();
    return hr;
}

HRESULT
CPosPassThru::GetSeekingLongLong
( HRESULT (__stdcall IMediaSeeking::*pMethod)( LONGLONG * )
, LONGLONG * pll
)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (SUCCEEDED(hr))
    {
hr = (pMS->*pMethod)(pll);
pMS->Release();
    }
    return hr;
}

// If we don't have a current position then ask upstream

STDMETHODIMP
CPosPassThru::GetCurrentPosition(LONGLONG *pCurrent)
{
    // Can we report the current position
    HRESULT hr = GetMediaTime(pCurrent,NULL);
    if (SUCCEEDED(hr)) hr = NOERROR;
    else hr = GetSeekingLongLong( &IMediaSeeking::GetCurrentPosition, pCurrent );
    return hr;
}

STDMETHODIMP
CPosPassThru::GetStopPosition(LONGLONG *pStop)
{
    return GetSeekingLongLong( &IMediaSeeking::GetStopPosition, pStop );;
}

STDMETHODIMP
CPosPassThru::GetDuration(LONGLONG *pDuration)
{
    return GetSeekingLongLong( &IMediaSeeking::GetDuration, pDuration );;
}

STDMETHODIMP
CPosPassThru::GetPreroll(LONGLONG *pllPreroll)
{
    return GetSeekingLongLong( &IMediaSeeking::GetPreroll, pllPreroll );;
}

STDMETHODIMP
CPosPassThru::GetAvailable( LONGLONG *pEarliest, LONGLONG *pLatest )
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMS->GetAvailable( pEarliest, pLatest );
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::GetRate(double * pdRate)
{
    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMS->GetRate(pdRate);
    pMS->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::SetRate(double dRate)
{
    if (0.0 == dRate) {
return E_INVALIDARG;
    }

    IMediaSeeking* pMS;
    HRESULT hr = GetPeerSeeking(&pMS);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMS->SetRate(dRate);
    pMS->Release();
    return hr;
}

// --- IMediaPosition methods ----------

STDMETHODIMP
CPosPassThru::get_Duration(REFTIME * plength)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }

    hr = pMP->get_Duration(plength);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::get_CurrentPosition(REFTIME * pllTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->get_CurrentPosition(pllTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::put_CurrentPosition(REFTIME llTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->put_CurrentPosition(llTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::get_StopTime(REFTIME * pllTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->get_StopTime(pllTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::put_StopTime(REFTIME llTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->put_StopTime(llTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::get_PrerollTime(REFTIME * pllTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->get_PrerollTime(pllTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::put_PrerollTime(REFTIME llTime)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->put_PrerollTime(llTime);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::get_Rate(double * pdRate)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->get_Rate(pdRate);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::put_Rate(double dRate)
{
    if (0.0 == dRate) {
return E_INVALIDARG;
    }

    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->put_Rate(dRate);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::CanSeekForward(LONG *pCanSeekForward)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->CanSeekForward(pCanSeekForward);
    pMP->Release();
    return hr;
}

STDMETHODIMP
CPosPassThru::CanSeekBackward(LONG *pCanSeekBackward)
{
    IMediaPosition* pMP;
    HRESULT hr = GetPeer(&pMP);
    if (FAILED(hr)) {
return hr;
    }
    hr = pMP->CanSeekBackward(pCanSeekBackward);
    pMP->Release();
    return hr;
}

// --- Implements the CRendererPosPassThru class ----------

// Media times (eg current frame, field, sample etc) are passed through the
// filtergraph in media samples. When a renderer gets a sample with media
// times in it, it will call one of the RegisterMediaTime methods we expose
// (one takes an IMediaSample, the other takes the media times direct). We
// store the media times internally and return them in GetCurrentPosition.

CRendererPosPassThru::CRendererPosPassThru(const TCHAR *pName,
   LPUNKNOWN pUnk,
   HRESULT *phr,
   IPin *pPin) :
    CPosPassThru(pName,pUnk,phr,pPin),
    m_StartMedia(0),
    m_EndMedia(0),
    m_bReset(TRUE)
{
}

// Sets the media times the object should report

HRESULT
CRendererPosPassThru::RegisterMediaTime(IMediaSample *pMediaSample)
{
    ASSERT(pMediaSample);
    LONGLONG StartMedia;
    LONGLONG EndMedia;

    CAutoLock cAutoLock(&m_PositionLock);

    // Get the media times from the sample

    HRESULT hr = pMediaSample->GetTime(&StartMedia,&EndMedia);
    if (FAILED(hr))
    {
ASSERT(hr == VFW_E_SAMPLE_TIME_NOT_SET);
return hr;
    }

    m_StartMedia = StartMedia;
    m_EndMedia = EndMedia;
    m_bReset = FALSE;
    return NOERROR;
}

// Sets the media times the object should report

HRESULT
CRendererPosPassThru::RegisterMediaTime(LONGLONG StartTime,LONGLONG EndTime)
{
    CAutoLock cAutoLock(&m_PositionLock);
    m_StartMedia = StartTime;
    m_EndMedia = EndTime;
    m_bReset = FALSE;
    return NOERROR;
}

// Return the current media times registered in the object

HRESULT
CRendererPosPassThru::GetMediaTime(LONGLONG *pStartTime,LONGLONG *pEndTime)
{
    ASSERT(pStartTime);

    CAutoLock cAutoLock(&m_PositionLock);
    if (m_bReset == TRUE) {
return E_FAIL;
    }

    // We don't have to return the end time

    HRESULT hr = ConvertTimeFormat( pStartTime, 0, m_StartMedia, &TIME_FORMAT_MEDIA_TIME );
    if (pEndTime && SUCCEEDED(hr)) {
hr = ConvertTimeFormat( pEndTime, 0, m_EndMedia, &TIME_FORMAT_MEDIA_TIME );
    }
    return hr;
}

// Resets the media times we hold

HRESULT
CRendererPosPassThru::ResetMediaTime()
{
    CAutoLock cAutoLock(&m_PositionLock);
    m_StartMedia = 0;
    m_EndMedia = 0;
    m_bReset = TRUE;
    return NOERROR;
}

// Intended to be called by the owing filter during EOS processing so
// that the media times can be adjusted to the stop time.  This ensures
// that the GetCurrentPosition will actully get to the stop position.
HRESULT
CRendererPosPassThru::EOS()
{
    HRESULT hr;

    if ( m_bReset == TRUE ) hr = E_FAIL;
    else
    {
LONGLONG llStop;
if SUCCEEDED(hr=GetStopPosition(&llStop))
{
    CAutoLock cAutoLock(&m_PositionLock);
    m_StartMedia =
    m_EndMedia = llStop;
}
    }
    return hr;
}

// -- CSourceSeeking implementation ------------

CSourceSeeking::CSourceSeeking(
    const TCHAR * pName,
    LPUNKNOWN pUnk,
    HRESULT* phr,
    CCritSec * pLock) :
        CUnknown(pName, pUnk),
        m_pLock(pLock),
        m_rtStart((long)0)
{
    m_rtStop = _I64_MAX / 2;
    m_rtDuration = m_rtStop;
    m_dRateSeeking = 1.0;

    m_dwSeekingCaps = AM_SEEKING_CanSeekForwards
        | AM_SEEKING_CanSeekBackwards
        | AM_SEEKING_CanSeekAbsolute
        | AM_SEEKING_CanGetStopPos
        | AM_SEEKING_CanGetDuration;
}

HRESULT CSourceSeeking::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IMediaSeeking) {
        CheckPointer(ppv, E_POINTER);
        return GetInterface(static_cast<IMediaSeeking *>(this), ppv);
    }
    else {
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

HRESULT CSourceSeeking::IsFormatSupported(const GUID * pFormat)
{
    CheckPointer(pFormat, E_POINTER);
    // only seeking in time (REFERENCE_TIME units) is supported
    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE;
}

HRESULT CSourceSeeking::QueryPreferredFormat(GUID *pFormat)
{
    CheckPointer(pFormat, E_POINTER);
    *pFormat = TIME_FORMAT_MEDIA_TIME;
    return S_OK;
}

HRESULT CSourceSeeking::SetTimeFormat(const GUID * pFormat)
{
    CheckPointer(pFormat, E_POINTER);

    // nothing to set; just check that it's TIME_FORMAT_TIME
    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : E_INVALIDARG;
}

HRESULT CSourceSeeking::IsUsingTimeFormat(const GUID * pFormat)
{
    CheckPointer(pFormat, E_POINTER);
    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE;
}

HRESULT CSourceSeeking::GetTimeFormat(GUID *pFormat)
{
    CheckPointer(pFormat, E_POINTER);
    *pFormat = TIME_FORMAT_MEDIA_TIME;
    return S_OK;
}

HRESULT CSourceSeeking::GetDuration(LONGLONG *pDuration)
{
    CheckPointer(pDuration, E_POINTER);
    CAutoLock lock(m_pLock);
    *pDuration = m_rtDuration;
    return S_OK;
}

HRESULT CSourceSeeking::GetStopPosition(LONGLONG *pStop)
{
    CheckPointer(pStop, E_POINTER);
    CAutoLock lock(m_pLock);
    *pStop = m_rtStop;
    return S_OK;
}

HRESULT CSourceSeeking::GetCurrentPosition(LONGLONG *pCurrent)
{
    // GetCurrentPosition is typically supported only in renderers and
    // not in source filters.
    return E_NOTIMPL;
}

HRESULT CSourceSeeking::GetCapabilities( DWORD * pCapabilities )
{
    CheckPointer(pCapabilities, E_POINTER);
    *pCapabilities = m_dwSeekingCaps;
    return S_OK;
}

HRESULT CSourceSeeking::CheckCapabilities( DWORD * pCapabilities )
{
    CheckPointer(pCapabilities, E_POINTER);

    // make sure all requested capabilities are in our mask
    return (~m_dwSeekingCaps & *pCapabilities) ? S_FALSE : S_OK;
}

HRESULT CSourceSeeking::ConvertTimeFormat( LONGLONG * pTarget, const GUID * pTargetFormat,
                           LONGLONG    Source, const GUID * pSourceFormat )
{
    CheckPointer(pTarget, E_POINTER);
    // format guids can be null to indicate current format

    // since we only support TIME_FORMAT_MEDIA_TIME, we don't really
    // offer any conversions.
    if(pTargetFormat == 0 || *pTargetFormat == TIME_FORMAT_MEDIA_TIME)
    {
        if(pSourceFormat == 0 || *pSourceFormat == TIME_FORMAT_MEDIA_TIME)
        {
            *pTarget = Source;
            return S_OK;
        }
    }

    return E_INVALIDARG;
}

HRESULT CSourceSeeking::SetPositions( LONGLONG * pCurrent,  DWORD CurrentFlags
                      , LONGLONG * pStop,  DWORD StopFlags )
{
    DWORD StopPosBits = StopFlags & AM_SEEKING_PositioningBitsMask;
    DWORD StartPosBits = CurrentFlags & AM_SEEKING_PositioningBitsMask;

    if(StopFlags) {
        CheckPointer(pStop, E_POINTER);

        // accept only relative, incremental, or absolute positioning
        if(StopPosBits != StopFlags) {
            return E_INVALIDARG;
        }
    }

    if(CurrentFlags) {
        CheckPointer(pCurrent, E_POINTER);
        if(StartPosBits != AM_SEEKING_AbsolutePositioning &&
           StartPosBits != AM_SEEKING_RelativePositioning) {
            return E_INVALIDARG;
        }
    }

    // scope for autolock
    {
        CAutoLock lock(m_pLock);

        // set start position
        if(StartPosBits == AM_SEEKING_AbsolutePositioning)
        {
            m_rtStart = *pCurrent;
        }
        else if(StartPosBits == AM_SEEKING_RelativePositioning)
        {
            m_rtStart += *pCurrent;
        }

        // set stop position
        if(StopPosBits == AM_SEEKING_AbsolutePositioning)
        {
            m_rtStop = *pStop;
        }
        else if(StopPosBits == AM_SEEKING_IncrementalPositioning)
        {
            m_rtStop = m_rtStart + *pStop;
        }
        else if(StopPosBits == AM_SEEKING_RelativePositioning)
        {
            m_rtStop = m_rtStop + *pStop;
        }
    }

    HRESULT hr = S_OK;
    if(SUCCEEDED(hr) && StopPosBits) {
        hr = ChangeStop();
    }
    if(StartPosBits) {
        hr = ChangeStart();
    }

    return hr;
}

HRESULT CSourceSeeking::GetPositions( LONGLONG * pCurrent, LONGLONG * pStop )
{
    if(pCurrent) {
        *pCurrent = m_rtStart;
    }
    if(pStop) {
        *pStop = m_rtStop;
    }

    return S_OK;;
}

HRESULT CSourceSeeking::GetAvailable( LONGLONG * pEarliest, LONGLONG * pLatest )
{
    if(pEarliest) {
        *pEarliest = 0;
    }
    if(pLatest) {
        CAutoLock lock(m_pLock);
        *pLatest = m_rtDuration;
    }
    return S_OK;
}

HRESULT CSourceSeeking::SetRate( double dRate)
{
    {
        CAutoLock lock(m_pLock);
        m_dRateSeeking = dRate;
    }
    return ChangeRate();
}

HRESULT CSourceSeeking::GetRate( double * pdRate)
{
    CheckPointer(pdRate, E_POINTER);
    CAutoLock lock(m_pLock);
    *pdRate = m_dRateSeeking;
    return S_OK;
}

HRESULT CSourceSeeking::GetPreroll(LONGLONG *pPreroll)
{
    CheckPointer(pPreroll, E_POINTER);
    *pPreroll = 0;
    return S_OK;
}

// --- CSourcePosition implementation ----------

CSourcePosition::CSourcePosition(const TCHAR * pName,
 LPUNKNOWN pUnk,
 HRESULT* phr,
 CCritSec * pLock) :
    CMediaPosition(pName, pUnk),
    m_pLock(pLock),
    m_Start(CRefTime((LONGLONG)0))
{
    m_Stop = _I64_MAX;
    m_Rate = 1.0;
}

STDMETHODIMP
CSourcePosition::get_Duration(REFTIME * plength)
{
    CheckPointer(plength,E_POINTER);
    ValidateReadWritePtr(plength,sizeof(REFTIME));
    CAutoLock lock(m_pLock);

    *plength = m_Duration;
    return S_OK;
}

STDMETHODIMP
CSourcePosition::put_CurrentPosition(REFTIME llTime)
{
    m_pLock->Lock();
    m_Start = llTime;
    m_pLock->Unlock();

    return ChangeStart();
}

STDMETHODIMP
CSourcePosition::get_StopTime(REFTIME * pllTime)
{
    CheckPointer(pllTime,E_POINTER);
    ValidateReadWritePtr(pllTime,sizeof(REFTIME));
    CAutoLock lock(m_pLock);

    *pllTime = m_Stop;
    return S_OK;
}

STDMETHODIMP
CSourcePosition::put_StopTime(REFTIME llTime)
{
    m_pLock->Lock();
    m_Stop = llTime;
    m_pLock->Unlock();

    return ChangeStop();
}

STDMETHODIMP
CSourcePosition::get_PrerollTime(REFTIME * pllTime)
{
    CheckPointer(pllTime,E_POINTER);
    ValidateReadWritePtr(pllTime,sizeof(REFTIME));
    return E_NOTIMPL;
}

STDMETHODIMP
CSourcePosition::put_PrerollTime(REFTIME llTime)
{
    return E_NOTIMPL;
}

STDMETHODIMP
CSourcePosition::get_Rate(double * pdRate)
{
    CheckPointer(pdRate,E_POINTER);
    ValidateReadWritePtr(pdRate,sizeof(double));
    CAutoLock lock(m_pLock);

    *pdRate = m_Rate;
    return S_OK;
}

STDMETHODIMP
CSourcePosition::put_Rate(double dRate)
{
    m_pLock->Lock();
    m_Rate = dRate;
    m_pLock->Unlock();

    return ChangeRate();
}

// By default we can seek forwards

STDMETHODIMP
CSourcePosition::CanSeekForward(LONG *pCanSeekForward)
{
    CheckPointer(pCanSeekForward,E_POINTER);
    *pCanSeekForward = OATRUE;
    return S_OK;
}

// By default we can seek backwards

STDMETHODIMP
CSourcePosition::CanSeekBackward(LONG *pCanSeekBackward)
{
    CheckPointer(pCanSeekBackward,E_POINTER);
    *pCanSeekBackward = OATRUE;
    return S_OK;
}

// --- Implementation of CBasicAudio class ----------

CBasicAudio::CBasicAudio(const TCHAR * pName,LPUNKNOWN punk) :
    CUnknown(pName, punk)
{
}

// overriden to publicise our interfaces

STDMETHODIMP
CBasicAudio::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IBasicAudio) {
return GetInterface( (IBasicAudio *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

STDMETHODIMP
CBasicAudio::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

STDMETHODIMP
CBasicAudio::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IBasicAudio,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CBasicAudio::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IBasicAudio,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CBasicAudio::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IBasicAudio *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- IVideoWindow implementation ----------

CBaseVideoWindow::CBaseVideoWindow(const TCHAR * pName,LPUNKNOWN punk) :
    CUnknown(pName, punk)
{
}

// overriden to publicise our interfaces

STDMETHODIMP
CBaseVideoWindow::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IVideoWindow) {
return GetInterface( (IVideoWindow *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

STDMETHODIMP
CBaseVideoWindow::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

STDMETHODIMP
CBaseVideoWindow::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IVideoWindow,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CBaseVideoWindow::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IVideoWindow,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CBaseVideoWindow::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IVideoWindow *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- IBasicVideo implementation ----------

CBaseBasicVideo::CBaseBasicVideo(const TCHAR * pName,LPUNKNOWN punk) :
    CUnknown(pName, punk)
{
}

// overriden to publicise our interfaces

STDMETHODIMP
CBaseBasicVideo::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IBasicVideo) {
return GetInterface( (IBasicVideo *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

STDMETHODIMP
CBaseBasicVideo::GetTypeInfoCount(UINT * pctinfo)
{
    return m_basedisp.GetTypeInfoCount(pctinfo);
}

STDMETHODIMP
CBaseBasicVideo::GetTypeInfo(
  UINT itinfo,
  LCID lcid,
  ITypeInfo ** pptinfo)
{
    return m_basedisp.GetTypeInfo(
IID_IBasicVideo,
itinfo,
lcid,
pptinfo);
}

STDMETHODIMP
CBaseBasicVideo::GetIDsOfNames(
  REFIID riid,
  OLECHAR  ** rgszNames,
  UINT cNames,
  LCID lcid,
  DISPID * rgdispid)
{
    return m_basedisp.GetIDsOfNames(
IID_IBasicVideo,
rgszNames,
cNames,
lcid,
rgdispid);
}

STDMETHODIMP
CBaseBasicVideo::Invoke(
  DISPID dispidMember,
  REFIID riid,
  LCID lcid,
  WORD wFlags,
  DISPPARAMS * pdispparams,
  VARIANT * pvarResult,
  EXCEPINFO * pexcepinfo,
  UINT * puArgErr)
{
    // this parameter is a dead leftover from an earlier interface
    if (IID_NULL != riid) {
return DISP_E_UNKNOWNINTERFACE;
    }

    ITypeInfo * pti;
    HRESULT hr = GetTypeInfo(0, lcid, &pti);

    if (FAILED(hr)) {
return hr;
    }

    hr = pti->Invoke(
    (IBasicVideo *)this,
    dispidMember,
    wFlags,
    pdispparams,
    pvarResult,
    pexcepinfo,
    puArgErr);

    pti->Release();
    return hr;
}

// --- Implementation of Deferred Commands ----------

CDispParams::CDispParams(UINT nArgs, VARIANT* pArgs)
{
   cNamedArgs = 0;
   rgdispidNamedArgs = NULL;
   cArgs = nArgs;

    if (cArgs) {
rgvarg = new VARIANT[cArgs];

for (UINT i = 0; i < cArgs; i++) {

    VARIANT * pDest = &rgvarg[i];
    VARIANT * pSrc = &pArgs[i];

    pDest->vt = pSrc->vt;
    switch(pDest->vt) {

    case VT_I4:
pDest->lVal = pSrc->lVal;
break;

    case VT_UI1:
pDest->bVal = pSrc->bVal;
break;

    case VT_I2:
pDest->iVal = pSrc->iVal;
break;

    case VT_R4:
pDest->fltVal = pSrc->fltVal;
break;

    case VT_R8:
pDest->dblVal = pSrc->dblVal;
break;

    case VT_BOOL:
pDest->boolVal = pSrc->boolVal;
break;

    case VT_ERROR:
pDest->scode = pSrc->scode;
break;

    case VT_CY:
pDest->cyVal = pSrc->cyVal;
break;

    case VT_DATE:
pDest->date = pSrc->date;
break;

    case VT_BSTR:
if (pSrc->bstrVal == NULL) {
    pDest->bstrVal = NULL;
} else {

    // a BSTR is a WORD followed by a UNICODE string.
    // the pointer points just after the WORD

    WORD len = * (WORD*) (pSrc->bstrVal - (sizeof(WORD) / sizeof(OLECHAR)));
    OLECHAR* pch = new OLECHAR[len + (sizeof(WORD)/sizeof(OLECHAR))];
    WORD *pui = (WORD*)pch;
    *pui = len;
    pDest->bstrVal = pch + (sizeof(WORD)/sizeof(OLECHAR));
    CopyMemory(pDest->bstrVal, pSrc->bstrVal, len*sizeof(OLECHAR));
}
pDest->bstrVal = pSrc->bstrVal;
break;

    case VT_UNKNOWN:
pDest->punkVal = pSrc->punkVal;
pDest->punkVal->AddRef();
break;

    case VT_DISPATCH:
pDest->pdispVal = pSrc->pdispVal;
pDest->pdispVal->AddRef();
break;

    default:
// a type we haven't got round to adding yet!
ASSERT(0);
break;
    }
}

    } else {
rgvarg = NULL;
    }

}

CDispParams::~CDispParams()
{
    for (UINT i = 0; i < cArgs; i++) {
switch(rgvarg[i].vt) {
case VT_BSTR:
    if (rgvarg[i].bstrVal != NULL) {
OLECHAR * pch = rgvarg[i].bstrVal - (sizeof(WORD)/sizeof(OLECHAR));
delete pch;
    }
    break;

case VT_UNKNOWN:
    rgvarg[i].punkVal->Release();
    break;

case VT_DISPATCH:
    rgvarg[i].pdispVal->Release();
    break;
}
    }
    delete[] rgvarg;
}

// lifetime is controlled by refcounts (see defer.h)

CDeferredCommand::CDeferredCommand(
    CCmdQueue * pQ,
    LPUNKNOWNpUnk,
    HRESULT *phr,
    LPUNKNOWNpUnkExecutor,
    REFTIMEtime,
    GUID*iid,
    longdispidMethod,
    shortwFlags,
    longnArgs,
    VARIANT*pDispParams,
    VARIANT*pvarResult,
    short*puArgErr,
    BOOLbStream
    ) :
CUnknown(NAME("DeferredCommand"), pUnk),
m_pQueue(pQ),
m_pUnk(pUnkExecutor),
m_iid(iid),
m_dispidMethod(dispidMethod),
m_wFlags(wFlags),
m_DispParams(nArgs, pDispParams),
m_pvarResult(pvarResult),
m_bStream(bStream),
m_hrResult(E_ABORT)

{
    // convert REFTIME to REFERENCE_TIME
    COARefTime convertor(time);
    m_time = convertor;

    // no check of time validity - it's ok to queue a command that's
    // already late

    // check iid is supportable on pUnk by QueryInterface for it
    IUnknown * pInterface;
    HRESULT hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface);
    if (FAILED(hr)) {
*phr = hr;
return;
    }
    pInterface->Release();

    // !!! check dispidMethod and param/return types using typelib
    ITypeInfo *pti;
    hr = m_Dispatch.GetTypeInfo(*iid, 0, 0, &pti);
    if (FAILED(hr)) {
*phr = hr;
return;
    }
    // !!! some sort of ITypeInfo validity check here
    pti->Release();

    // Fix up the dispid for put and get
    if (wFlags == DISPATCH_PROPERTYPUT) {
        m_DispParams.cNamedArgs = 1;
        m_DispId = DISPID_PROPERTYPUT;
        m_DispParams.rgdispidNamedArgs = &m_DispId;
    }

    // all checks ok - add to queue
    hr = pQ->Insert(this);
    if (FAILED(hr)) {
*phr = hr;
    }
}

// refcounts are held by caller of InvokeAt... and by list. So if
// we get here, we can't be on the list

#if 0
CDeferredCommand::~CDeferredCommand()
{
    // this assert is invalid since if the queue is deleted while we are
    // still on the queue, we will have been removed by the queue and this
    // m_pQueue will not have been modified.
    // ASSERT(m_pQueue == NULL);

    // we don't hold a ref count on pUnk, which is the object that should
    // execute the command.
    // This is because there would otherwise be a circular refcount problem
    // since pUnk probably owns the CmdQueue object that has a refcount
    // on us.
    // The lifetime of pUnk is guaranteed by it being part of, or lifetime
    // controlled by, our parent object. As long as we are on the list, pUnk
    // must be valid. Once we are off the list, we do not use pUnk.

}
#endif

// overriden to publicise our interfaces

STDMETHODIMP
CDeferredCommand::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    if (riid == IID_IDeferredCommand) {
return GetInterface( (IDeferredCommand *) this, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

// remove from q. this will reduce the refcount by one (since the q
// holds a count) but can't make us go away since he must have a
// refcount in order to call this method.

STDMETHODIMP
CDeferredCommand::Cancel()
{
    if (m_pQueue == NULL) {
return VFW_E_ALREADY_CANCELLED;
    }

    HRESULT hr = m_pQueue->Remove(this);
    if (FAILED(hr)) {
return hr;
    }

    m_pQueue = NULL;
    return S_OK;
}

STDMETHODIMP
CDeferredCommand::Confidence(LONG* pConfidence)
{
    return E_NOTIMPL;
}

STDMETHODIMP
CDeferredCommand::GetHResult(HRESULT * phrResult)
{
    CheckPointer(phrResult,E_POINTER);
    ValidateReadWritePtr(phrResult,sizeof(HRESULT));

    if (m_pQueue != NULL) {
return E_ABORT;
    }
    *phrResult = m_hrResult;
    return S_OK;
}

// set the time to be a new time (checking that it is valid) and
// then requeue

STDMETHODIMP
CDeferredCommand::Postpone(REFTIME newtime)
{

    // check that this time is not past
    // convert REFTIME to REFERENCE_TIME
    COARefTime convertor(newtime);

    // check that the time has not passed
    if (m_pQueue->CheckTime(convertor, IsStreamTime())) {
return VFW_E_TIME_ALREADY_PASSED;
    }

    // extract from list
    HRESULT hr = m_pQueue->Remove(this);
    if (FAILED(hr)) {
return hr;
    }

    // change time
    m_time = convertor;

    // requeue
    hr = m_pQueue->Insert(this);

    return hr;
}

HRESULT
CDeferredCommand::Invoke()
{
    // check that we are still outstanding
    if (m_pQueue == NULL) {
return VFW_E_ALREADY_CANCELLED;
    }

    // get the type info
    ITypeInfo* pti;
    HRESULT hr = m_Dispatch.GetTypeInfo(GetIID(), 0, 0, &pti);
    if (FAILED(hr)) {
return hr;
    }

    // qi for the expected interface and then invoke it. Note that we have to
    // treat the returned interface as IUnknown since we don't know its type.
    IUnknown* pInterface;

    hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface);
    if (FAILED(hr)) {
pti->Release();
return hr;
    }

    EXCEPINFO expinfo;
    UINT uArgErr;
    m_hrResult = pti->Invoke(
pInterface,
GetMethod(),
GetFlags(),
GetParams(),
GetResult(),
&expinfo,
&uArgErr);

    // release the interface we QI'd for
    pInterface->Release();
    pti->Release();

    // remove from list whether or not successful
    // or we loop indefinitely
    hr = m_pQueue->Remove(this);
    m_pQueue = NULL;
    return hr;
}

// --- CCmdQueue methods ----------

CCmdQueue::CCmdQueue() :
    m_listPresentation(NAME("Presentation time command list")),
    m_listStream(NAME("Stream time command list")),
    m_evDue(TRUE),    // manual reset
    m_dwAdvise(0),
    m_pClock(NULL),
    m_bRunning(FALSE)
{
}

CCmdQueue::~CCmdQueue()
{
    // empty all our lists

    // we hold a refcount on each, so traverse and Release each
    // entry then RemoveAll to empty the list
    POSITION pos = m_listPresentation.GetHeadPosition();

    while(pos) {
CDeferredCommand* pCmd = m_listPresentation.GetNext(pos);
pCmd->Release();
    }
    m_listPresentation.RemoveAll();

    pos = m_listStream.GetHeadPosition();

    while(pos) {
CDeferredCommand* pCmd = m_listStream.GetNext(pos);
pCmd->Release();
    }
    m_listStream.RemoveAll();

    if (m_pClock) {
if (m_dwAdvise) {
    m_pClock->Unadvise(m_dwAdvise);
    m_dwAdvise = 0;
}
m_pClock->Release();
    }
}

// returns a new CDeferredCommand object that will be initialised with
// the parameters and will be added to the queue during construction.
// returns S_OK if successfully created otherwise an error and
// no object has been queued.

HRESULT
CCmdQueue::New(
    CDeferredCommand **ppCmd,
    LPUNKNOWNpUnk,// this object will execute command
    REFTIMEtime,
    GUID*iid,
    longdispidMethod,
    shortwFlags,
    longcArgs,
    VARIANT*pDispParams,
    VARIANT*pvarResult,
    short*puArgErr,
    BOOLbStream
)
{
    CAutoLock lock(&m_Lock);

    HRESULT hr = S_OK;
    *ppCmd = NULL;

    CDeferredCommand* pCmd;
    pCmd = new CDeferredCommand(
    this,
    NULL,    // not aggregated
    &hr,
    pUnk,    // this guy will execute
    time,
    iid,
    dispidMethod,
    wFlags,
    cArgs,
    pDispParams,
    pvarResult,
    puArgErr,
    bStream);

    if (pCmd == NULL) {
hr = E_OUTOFMEMORY;
    } else {
*ppCmd = pCmd;
    }
    return hr;
}

HRESULT
CCmdQueue::Insert(CDeferredCommand* pCmd)
{
    CAutoLock lock(&m_Lock);

    // addref the item
    pCmd->AddRef();

    CGenericList<CDeferredCommand> * pList;
    if (pCmd->IsStreamTime()) {
pList = &m_listStream;
    } else {
pList = &m_listPresentation;
    }
    POSITION pos = pList->GetHeadPosition();

    // seek past all items that are before us
    while (pos &&
(pList->Get(pos)->GetTime() <= pCmd->GetTime())) {

pList->GetNext(pos);
    }

    // now at end of list or in front of items that come later
    if (!pos) {
pList->AddTail(pCmd);
    } else {
pList->AddBefore(pos, pCmd);
    }

    SetTimeAdvise();
    return S_OK;
}

HRESULT
CCmdQueue::Remove(CDeferredCommand* pCmd)
{
    CAutoLock lock(&m_Lock);
    HRESULT hr = S_OK;

    CGenericList<CDeferredCommand> * pList;
    if (pCmd->IsStreamTime()) {
pList = &m_listStream;
    } else {
pList = &m_listPresentation;
    }
    POSITION pos = pList->GetHeadPosition();

    // traverse the list
    while (pos && (pList->Get(pos) != pCmd)) {
pList->GetNext(pos);
    }

    // did we drop off the end?
    if (!pos) {
hr = VFW_E_NOT_FOUND;
    } else {

// found it - now take off list
pList->Remove(pos);

// Insert did an AddRef, so release it
pCmd->Release();

// check that timer request is still for earliest time
SetTimeAdvise();
    }
    return hr;
}

// set the clock used for timing

HRESULT
CCmdQueue::SetSyncSource(IReferenceClock* pClock)
{
    CAutoLock lock(&m_Lock);

    // addref the new clock first in case they are the same
    if (pClock) {
pClock->AddRef();
    }

    // kill any advise on the old clock
    if (m_pClock) {
if (m_dwAdvise) {
    m_pClock->Unadvise(m_dwAdvise);
    m_dwAdvise = 0;
}
m_pClock->Release();
    }
    m_pClock = pClock;

    // set up a new advise
    SetTimeAdvise();
    return S_OK;
}

// set up a timer event with the reference clock

void
CCmdQueue::SetTimeAdvise(void)
{
    // make sure we have a clock to use
    if (!m_pClock) {
return;
    }

    // reset the event whenever we are requesting a new signal
    m_evDue.Reset();

    // time 0 is earliest
    CRefTime current;

    // find the earliest presentation time
    if (m_listPresentation.GetCount() > 0) {

POSITION pos = m_listPresentation.GetHeadPosition();
current = m_listPresentation.Get(pos)->GetTime();
    }

    // if we're running, check the stream times too
    if (m_bRunning) {

CRefTime t;

if (m_listStream.GetCount() > 0) {

    POSITION pos = m_listStream.GetHeadPosition();
    t = m_listStream.Get(pos)->GetTime();

    // add on stream time offset to get presentation time
    t += m_StreamTimeOffset;

    // is this earlier?
    if ((current == TimeZero) || (t < current)) {
current = t;
    }
}
    }

    // need to change?
    if ((current > TimeZero) && (current != m_tCurrentAdvise)) {
if (m_dwAdvise) {
    m_pClock->Unadvise(m_dwAdvise);
    // reset the event whenever we are requesting a new signal
    m_evDue.Reset();
}

// ask for time advice - the first two params are either
// stream time offset and stream time or
// presentation time and 0. we always use the latter
HRESULT hr = m_pClock->AdviseTime(
    (REFERENCE_TIME)current,
    TimeZero,
    (HEVENT) HANDLE(m_evDue),
    &m_dwAdvise);

ASSERT(SUCCEEDED(hr));
m_tCurrentAdvise = current;
    }
}

// switch to run mode. Streamtime to Presentation time mapping known.

HRESULT
CCmdQueue::Run(REFERENCE_TIME tStreamTimeOffset)
{
    CAutoLock lock(&m_Lock);

    m_StreamTimeOffset = tStreamTimeOffset;
    m_bRunning = TRUE;

    // ensure advise is accurate
    SetTimeAdvise();
    return S_OK;
}

// switch to Stopped or Paused mode. Time mapping not known.

HRESULT
CCmdQueue::EndRun()
{
    CAutoLock lock(&m_Lock);

    m_bRunning = FALSE;

    // check timer setting - stream times
    SetTimeAdvise();
    return S_OK;
}

// return a pointer to the next due command. Blocks for msTimeout
// milliseconds until there is a due command.
// Stream-time commands will only become due between Run and Endrun calls.
// The command remains queued until invoked or cancelled.
// Returns E_ABORT if timeout occurs, otherwise S_OK (or other error).
//
// returns an AddRef'd object

HRESULT
CCmdQueue::GetDueCommand(CDeferredCommand ** ppCmd, long msTimeout)
{
    // loop until we timeout or find a due command
    for (;;) {

{
    CAutoLock lock(&m_Lock);

    // find the earliest command
    CDeferredCommand * pCmd = NULL;

    // check the presentation time and the
    // stream time list to find the earliest

    if (m_listPresentation.GetCount() > 0) {
POSITION pos = m_listPresentation.GetHeadPosition();
pCmd = m_listPresentation.Get(pos);
    }

    if (m_bRunning && (m_listStream.GetCount() > 0)) {
POSITION pos = m_listStream.GetHeadPosition();
CDeferredCommand* pStrm = m_listStream.Get(pos);

CRefTime t = pStrm->GetTime() + m_StreamTimeOffset;
if (!pCmd || (t < pCmd->GetTime())) {
    pCmd = pStrm;
}
    }

    //if we have found one, is it due?
    if (pCmd) {
if (CheckTime(pCmd->GetTime(), pCmd->IsStreamTime())) {

    // yes it's due - addref it
    pCmd->AddRef();
    *ppCmd = pCmd;
    return S_OK;
}
    }
}

// block until the advise is signalled
if (WaitForSingleObject(m_evDue, msTimeout) != WAIT_OBJECT_0) {
    return E_ABORT;
}
    }
}

// return a pointer to a command that will be due for a given time.
// Pass in a stream time here. The stream time offset will be passed
// in via the Run method.
// Commands remain queued until invoked or cancelled.
// This method will not block. It will report E_ABORT if there are no
// commands due yet.
//
// returns an AddRef'd object

HRESULT
CCmdQueue::GetCommandDueFor(REFERENCE_TIME rtStream, CDeferredCommand**ppCmd)
{
    CAutoLock lock(&m_Lock);

    CRefTime tStream(rtStream);

    // find the earliest stream and presentation time commands
    CDeferredCommand* pStream = NULL;
    if (m_listStream.GetCount() > 0) {
POSITION pos = m_listStream.GetHeadPosition();
pStream = m_listStream.Get(pos);
    }
    CDeferredCommand* pPresent = NULL;
    if (m_listPresentation.GetCount() > 0) {
POSITION pos = m_listPresentation.GetHeadPosition();
pPresent = m_listPresentation.Get(pos);
    }

    // is there a presentation time that has passed already
    if (pPresent && CheckTime(pPresent->GetTime(), FALSE)) {
pPresent->AddRef();
*ppCmd = pPresent;
return S_OK;
    }

    // is there a stream time command due before this stream time
    if (pStream && (pStream->GetTime() <= tStream)) {
pPresent->AddRef();
*ppCmd = pStream;
return S_OK;
    }

    // if we are running, we can map presentation times to
    // stream time. In this case, is there a presentation time command
    // that will be due before this stream time is presented?
    if (m_bRunning && pPresent) {

// this stream time will appear at...
tStream += m_StreamTimeOffset;

// due before that?
if (pPresent->GetTime() <= tStream) {
    *ppCmd = pPresent;
    return S_OK;
}
    }

    // no commands due yet
    return VFW_E_NOT_FOUND;
}


CTLUTIL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base classes implementing IDispatch parsing for the basic control dual
// interfaces. Derive from these and implement just the custom method and
// property methods. We also implement CPosPassThru that can be used by
// renderers and transforms to pass by IMediaPosition and IMediaSeeking

#ifndef __CTLUTIL__
#define __CTLUTIL__

// OLE Automation has different ideas of TRUE and FALSE

#define OATRUE (-1)
#define OAFALSE (0)

// It's possible that we could replace this class with CreateStdDispatch

class CBaseDispatch
{
    ITypeInfo * m_pti;

public:

    CBaseDispatch() : m_pti(NULL) {}
    ~CBaseDispatch();

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      REFIID riid,
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);
};

class AM_NOVTABLE CMediaControl :
    public IMediaControl,
    public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CMediaControl(const TCHAR *, LPUNKNOWN);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);
};

class AM_NOVTABLE CMediaEvent :
    public IMediaEventEx,
    public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CMediaEvent(const TCHAR *, LPUNKNOWN);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);
};

class AM_NOVTABLE CMediaPosition :
    public IMediaPosition,
    public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CMediaPosition(const TCHAR *, LPUNKNOWN);
    CMediaPosition(const TCHAR *, LPUNKNOWN, HRESULT *phr);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);

};

// OA-compatibility means that we must use double as the RefTime value,
// and REFERENCE_TIME (essentially a LONGLONG) within filters.
// this class converts between the two

class COARefTime : public CRefTime {
public:

    COARefTime() {
    };

    COARefTime(CRefTime t)
        : CRefTime(t)
    {
    };

    COARefTime(REFERENCE_TIME t)
        : CRefTime(t)
    {
    };

    COARefTime(double d) {
        m_time = (LONGLONG) (d * 10000000);
    };

    operator double() {
        return double(m_time) / 10000000;
    };

    operator REFERENCE_TIME() {
        return m_time;
    };

    COARefTime& operator=(const double& rd)  {
        m_time = (LONGLONG) (rd * 10000000);
        return *this;
    }

    COARefTime& operator=(const REFERENCE_TIME& rt)  {
        m_time = rt;
        return *this;
    }

    inline BOOL operator==(const COARefTime& rt)
    {
        return m_time == rt.m_time;
    };

    inline BOOL operator!=(const COARefTime& rt)
    {
        return m_time != rt.m_time;
    };

    inline BOOL operator < (const COARefTime& rt)
    {
        return m_time < rt.m_time;
    };

    inline BOOL operator > (const COARefTime& rt)
    {
        return m_time > rt.m_time;
    };

    inline BOOL operator >= (const COARefTime& rt)
    {
        return m_time >= rt.m_time;
    };

    inline BOOL operator <= (const COARefTime& rt)
    {
        return m_time <= rt.m_time;
    };

    inline COARefTime operator+(const COARefTime& rt)
    {
        return COARefTime(m_time + rt.m_time);
    };

    inline COARefTime operator-(const COARefTime& rt)
    {
        return COARefTime(m_time - rt.m_time);
    };

    inline COARefTime operator*(LONG l)
    {
        return COARefTime(m_time * l);
    };

    inline COARefTime operator/(LONG l)
    {
        return COARefTime(m_time / l);
    };

private:
    //  Prevent bugs from constructing from LONG (which gets
    //  converted to double and then multiplied by 10000000
    COARefTime(LONG);
    operator=(LONG);
};

// A utility class that handles IMediaPosition and IMediaSeeking on behalf
// of single-input pin renderers, or transform filters.
//
// Renderers will expose this from the filter; transform filters will
// expose it from the output pin and not the renderer.
//
// Create one of these, giving it your IPin* for your input pin, and delegate
// all IMediaPosition methods to it. It will query the input pin for
// IMediaPosition and respond appropriately.
//
// Call ForceRefresh if the pin connection changes.
//
// This class no longer caches the upstream IMediaPosition or IMediaSeeking
// it acquires it on each method call. This means ForceRefresh is not needed.
// The method is kept for source compatibility and to minimise the changes
// if we need to put it back later for performance reasons.

class CPosPassThru : public IMediaSeeking, public CMediaPosition
{
    IPin *m_pPin;

    HRESULT GetPeer(IMediaPosition **ppMP);
    HRESULT GetPeerSeeking(IMediaSeeking **ppMS);

public:

    CPosPassThru(const TCHAR *, LPUNKNOWN, HRESULT*, IPin *);
    DECLARE_IUNKNOWN

    HRESULT ForceRefresh() {
        return S_OK;
    };

    // override to return an accurate current position
    virtual HRESULT GetMediaTime(LONGLONG *pStartTime,LONGLONG *pEndTime) {
        return E_FAIL;
    }

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv);

    // IMediaSeeking methods
    STDMETHODIMP GetCapabilities( DWORD * pCapabilities );
    STDMETHODIMP CheckCapabilities( DWORD * pCapabilities );
    STDMETHODIMP SetTimeFormat(const GUID * pFormat);
    STDMETHODIMP GetTimeFormat(GUID *pFormat);
    STDMETHODIMP IsUsingTimeFormat(const GUID * pFormat);
    STDMETHODIMP IsFormatSupported( const GUID * pFormat);
    STDMETHODIMP QueryPreferredFormat( GUID *pFormat);
    STDMETHODIMP ConvertTimeFormat(LONGLONG * pTarget, const GUID * pTargetFormat,
                                   LONGLONG    Source, const GUID * pSourceFormat );
    STDMETHODIMP SetPositions( LONGLONG * pCurrent, DWORD CurrentFlags
                             , LONGLONG * pStop, DWORD StopFlags );

    STDMETHODIMP GetPositions( LONGLONG * pCurrent, LONGLONG * pStop );
    STDMETHODIMP GetCurrentPosition( LONGLONG * pCurrent );
    STDMETHODIMP GetStopPosition( LONGLONG * pStop );
    STDMETHODIMP SetRate( double dRate);
    STDMETHODIMP GetRate( double * pdRate);
    STDMETHODIMP GetDuration( LONGLONG *pDuration);
    STDMETHODIMP GetAvailable( LONGLONG *pEarliest, LONGLONG *pLatest );
    STDMETHODIMP GetPreroll( LONGLONG *pllPreroll );

    // IMediaPosition properties
    STDMETHODIMP get_Duration(REFTIME * plength);
    STDMETHODIMP put_CurrentPosition(REFTIME llTime);
    STDMETHODIMP get_StopTime(REFTIME * pllTime);
    STDMETHODIMP put_StopTime(REFTIME llTime);
    STDMETHODIMP get_PrerollTime(REFTIME * pllTime);
    STDMETHODIMP put_PrerollTime(REFTIME llTime);
    STDMETHODIMP get_Rate(double * pdRate);
    STDMETHODIMP put_Rate(double dRate);
    STDMETHODIMP get_CurrentPosition(REFTIME * pllTime);
    STDMETHODIMP CanSeekForward(LONG *pCanSeekForward);
    STDMETHODIMP CanSeekBackward(LONG *pCanSeekBackward);

private:
    HRESULT GetSeekingLongLong( HRESULT (__stdcall IMediaSeeking::*pMethod)( LONGLONG * ),
                                LONGLONG * pll );
};

// Adds the ability to return a current position

class CRendererPosPassThru : public CPosPassThru
{
    CCritSec m_PositionLock;    // Locks access to our position
    LONGLONG m_StartMedia;      // Start media time last seen
    LONGLONG m_EndMedia;        // And likewise the end media
    BOOL m_bReset;              // Have media times been set

public:

    // Used to help with passing media times through graph

    CRendererPosPassThru(const TCHAR *, LPUNKNOWN, HRESULT*, IPin *);
    HRESULT RegisterMediaTime(IMediaSample *pMediaSample);
    HRESULT RegisterMediaTime(LONGLONG StartTime,LONGLONG EndTime);
    HRESULT GetMediaTime(LONGLONG *pStartTime,LONGLONG *pEndTime);
    HRESULT ResetMediaTime();
    HRESULT EOS();
};

STDAPI CreatePosPassThru(
    LPUNKNOWN pAgg,
    BOOL bRenderer,
    IPin *pPin,
    IUnknown **ppPassThru
);

// A class that handles the IDispatch part of IBasicAudio and leaves the
// properties and methods themselves pure virtual.

class AM_NOVTABLE CBasicAudio : public IBasicAudio, public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CBasicAudio(const TCHAR *, LPUNKNOWN);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);
};

// A class that handles the IDispatch part of IBasicVideo and leaves the
// properties and methods themselves pure virtual.

class AM_NOVTABLE CBaseBasicVideo : public IBasicVideo, public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CBaseBasicVideo(const TCHAR *, LPUNKNOWN);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);
};

// A class that handles the IDispatch part of IVideoWindow and leaves the
// properties and methods themselves pure virtual.

class AM_NOVTABLE CBaseVideoWindow : public IVideoWindow, public CUnknown
{
    CBaseDispatch m_basedisp;

public:

    CBaseVideoWindow(const TCHAR *, LPUNKNOWN);

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    /* IDispatch methods */
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo);

    STDMETHODIMP GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo ** pptinfo);

    STDMETHODIMP GetIDsOfNames(
      REFIID riid,
      OLECHAR  ** rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID * rgdispid);

    STDMETHODIMP Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS * pdispparams,
      VARIANT * pvarResult,
      EXCEPINFO * pexcepinfo,
      UINT * puArgErr);
};

// abstract class to help source filters with their implementation
// of IMediaPosition. Derive from this and set the duration (and stop
// position). Also override NotifyChange to do something when the properties
// change.

class AM_NOVTABLE CSourcePosition : public CMediaPosition
{

public:
    CSourcePosition(const TCHAR *, LPUNKNOWN, HRESULT*, CCritSec *);

    // IMediaPosition methods
    STDMETHODIMP get_Duration(REFTIME * plength);
    STDMETHODIMP put_CurrentPosition(REFTIME llTime);
    STDMETHODIMP get_StopTime(REFTIME * pllTime);
    STDMETHODIMP put_StopTime(REFTIME llTime);
    STDMETHODIMP get_PrerollTime(REFTIME * pllTime);
    STDMETHODIMP put_PrerollTime(REFTIME llTime);
    STDMETHODIMP get_Rate(double * pdRate);
    STDMETHODIMP put_Rate(double dRate);
    STDMETHODIMP CanSeekForward(LONG *pCanSeekForward);
    STDMETHODIMP CanSeekBackward(LONG *pCanSeekBackward);

    // override if you can return the data you are actually working on
    STDMETHODIMP get_CurrentPosition(REFTIME * pllTime) {
        return E_NOTIMPL;
    };

protected:

    // we call this to notify changes. Override to handle them
    virtual HRESULT ChangeStart() PURE;
    virtual HRESULT ChangeStop() PURE;
    virtual HRESULT ChangeRate() PURE;

    COARefTime m_Duration;
    COARefTime m_Start;
    COARefTime m_Stop;
    double m_Rate;

    CCritSec * m_pLock;
};

class AM_NOVTABLE CSourceSeeking :
    public IMediaSeeking,
    public CUnknown
{

public:

    DECLARE_IUNKNOWN;
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    // IMediaSeeking methods

    STDMETHODIMP IsFormatSupported(const GUID * pFormat);
    STDMETHODIMP QueryPreferredFormat(GUID *pFormat);
    STDMETHODIMP SetTimeFormat(const GUID * pFormat);
    STDMETHODIMP IsUsingTimeFormat(const GUID * pFormat);
    STDMETHODIMP GetTimeFormat(GUID *pFormat);
    STDMETHODIMP GetDuration(LONGLONG *pDuration);
    STDMETHODIMP GetStopPosition(LONGLONG *pStop);
    STDMETHODIMP GetCurrentPosition(LONGLONG *pCurrent);
    STDMETHODIMP GetCapabilities( DWORD * pCapabilities );
    STDMETHODIMP CheckCapabilities( DWORD * pCapabilities );
    STDMETHODIMP ConvertTimeFormat( LONGLONG * pTarget, const GUID * pTargetFormat,
                                    LONGLONG    Source, const GUID * pSourceFormat );

    STDMETHODIMP SetPositions( LONGLONG * pCurrent,  DWORD CurrentFlags
     , LONGLONG * pStop,  DWORD StopFlags );

    STDMETHODIMP GetPositions( LONGLONG * pCurrent, LONGLONG * pStop );

    STDMETHODIMP GetAvailable( LONGLONG * pEarliest, LONGLONG * pLatest );
    STDMETHODIMP SetRate( double dRate);
    STDMETHODIMP GetRate( double * pdRate);
    STDMETHODIMP GetPreroll(LONGLONG *pPreroll);

protected:

    // ctor
    CSourceSeeking(const TCHAR *, LPUNKNOWN, HRESULT*, CCritSec *);

    // we call this to notify changes. Override to handle them
    virtual HRESULT ChangeStart() PURE;
    virtual HRESULT ChangeStop() PURE;
    virtual HRESULT ChangeRate() PURE;

    CRefTime m_rtDuration;      // length of stream
    CRefTime m_rtStart;         // source will start here
    CRefTime m_rtStop;          // source will stop here
    double m_dRateSeeking;

    // seeking capabilities
    DWORD m_dwSeekingCaps;

    CCritSec * m_pLock;
};

// Base classes supporting Deferred commands.

// Deferred commands are queued by calls to methods on the IQueueCommand
// interface, exposed by the filtergraph and by some filters. A successful
// call to one of these methods will return an IDeferredCommand interface
// representing the queued command.
//
// A CDeferredCommand object represents a single deferred command, and exposes
// the IDeferredCommand interface as well as other methods permitting time
// checks and actual execution. It contains a reference to the CCommandQueue
// object on which it is queued.
//
// CCommandQueue is a base class providing a queue of CDeferredCommand
// objects, and methods to add, remove, check status and invoke the queued
// commands. A CCommandQueue object would be part of an object that
// implemented IQueueCommand.

class CCmdQueue;

// take a copy of the params and store them. Release any allocated
// memory in destructor

class CDispParams : public DISPPARAMS
{
public:
    CDispParams(UINT nArgs, VARIANT* pArgs);
    ~CDispParams();
};

// CDeferredCommand lifetime is controlled by refcounts. Caller of
// InvokeAt.. gets a refcounted interface pointer, and the CCmdQueue
// object also holds a refcount on us. Calling Cancel or Invoke takes
// us off the CCmdQueue and thus reduces the refcount by 1. Once taken
// off the queue we cannot be put back on the queue.

class CDeferredCommand
    : public CUnknown,
      public IDeferredCommand
{
public:

    CDeferredCommand(
        CCmdQueue * pQ,
        LPUNKNOWN   pUnk,               // aggregation outer unk
        HRESULT *   phr,
        LPUNKNOWN   pUnkExecutor,       // object that will execute this cmd
        REFTIME     time,
        GUID*       iid,
        long        dispidMethod,
        short       wFlags,
        long        cArgs,
        VARIANT*    pDispParams,
        VARIANT*    pvarResult,
        short*      puArgErr,
        BOOL        bStream
        );

    DECLARE_IUNKNOWN

    // override this to publicise our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    // IDeferredCommand methods
    STDMETHODIMP Cancel();
    STDMETHODIMP Confidence(
                    LONG* pConfidence);
    STDMETHODIMP Postpone(
                    REFTIME newtime);
    STDMETHODIMP GetHResult(
                    HRESULT* phrResult);

    // other public methods

    HRESULT Invoke();

    // access methods

    // returns TRUE if streamtime, FALSE if presentation time
    BOOL IsStreamTime() {
       return m_bStream;
    };

    CRefTime GetTime() {
        return m_time;
    };

    REFIID GetIID() {
        return *m_iid;
    };

    long GetMethod() {
        return m_dispidMethod;
    };

    short GetFlags() {
        return m_wFlags;
    };

    DISPPARAMS* GetParams() {
        return &m_DispParams;
    };

    VARIANT* GetResult() {
        return m_pvarResult;
    };

protected:

    CCmdQueue* m_pQueue;

    // pUnk for the interface that we will execute the command on
    LPUNKNOWN   m_pUnk;

    // stored command data
    REFERENCE_TIME     m_time;
    GUID*       m_iid;
    long        m_dispidMethod;
    short       m_wFlags;
    VARIANT*    m_pvarResult;
    BOOL        m_bStream;
    CDispParams m_DispParams;
    DISPID      m_DispId;         //  For get and put

    // we use this for ITypeInfo access
    CBaseDispatch   m_Dispatch;

    // save retval here
    HRESULT     m_hrResult;
};

// a list of CDeferredCommand objects. this is a base class providing
// the basics of access to the list. If you want to use CDeferredCommand
// objects then your queue needs to be derived from this class.

class AM_NOVTABLE CCmdQueue
{
public:
    CCmdQueue();
    virtual ~CCmdQueue();

    // returns a new CDeferredCommand object that will be initialised with
    // the parameters and will be added to the queue during construction.
    // returns S_OK if successfully created otherwise an error and
    // no object has been queued.
    virtual HRESULT  New(
        CDeferredCommand **ppCmd,
        LPUNKNOWN   pUnk,
        REFTIME     time,
        GUID*       iid,
        long        dispidMethod,
        short       wFlags,
        long        cArgs,
        VARIANT*    pDispParams,
        VARIANT*    pvarResult,
        short*      puArgErr,
        BOOL        bStream
    );

    // called by the CDeferredCommand object to add and remove itself
    // from the queue
    virtual HRESULT Insert(CDeferredCommand* pCmd);
    virtual HRESULT Remove(CDeferredCommand* pCmd);

    // Command-Due Checking
    //
    // There are two schemes of synchronisation: coarse and accurate. In
    // coarse mode, you wait till the time arrives and then execute the cmd.
    // In accurate mode, you wait until you are processing the sample that
    // will appear at the time, and then execute the command. It's up to the
    // filter which one it will implement. The filtergraph will always
    // implement coarse mode for commands queued at the filtergraph.
    //
    // If you want coarse sync, you probably want to wait until there is a
    // command due, and then execute it. You can do this by calling
    // GetDueCommand. If you have several things to wait for, get the
    // event handle from GetDueHandle() and when this is signalled then call
    // GetDueCommand. Stream time will only advance between calls to Run and
    // EndRun. Note that to avoid an extra thread there is no guarantee that
    // if the handle is set there will be a command ready. Each time the
    // event is signalled, call GetDueCommand (probably with a 0 timeout);
    // This may return E_ABORT.
    //
    // If you want accurate sync, you must call GetCommandDueFor, passing
    // as a parameter the stream time of the samples you are about to process.
    // This will return:
    //   -- a stream-time command due at or before that stream time
    //   -- a presentation-time command due at or before the
    //      time that stream time will be presented (only between Run
    //      and EndRun calls, since outside of this, the mapping from
    //      stream time to presentation time is not known.
    //   -- any presentation-time command due now.
    // This means that if you want accurate synchronisation on samples that
    // might be processed during Paused mode, you need to use
    // stream-time commands.
    //
    // In all cases, commands remain queued until Invoked or Cancelled. The
    // setting and resetting of the event handle is managed entirely by this
    // queue object.

    // set the clock used for timing
    virtual HRESULT SetSyncSource(IReferenceClock*);

    // switch to run mode. Streamtime to Presentation time mapping known.
    virtual HRESULT Run(REFERENCE_TIME tStreamTimeOffset);

    // switch to Stopped or Paused mode. Time mapping not known.
    virtual HRESULT EndRun();

    // return a pointer to the next due command. Blocks for msTimeout
    // milliseconds until there is a due command.
    // Stream-time commands will only become due between Run and Endrun calls.
    // The command remains queued until invoked or cancelled.
    // Returns E_ABORT if timeout occurs, otherwise S_OK (or other error).
    // Returns an AddRef-ed object
    virtual HRESULT GetDueCommand(CDeferredCommand ** ppCmd, long msTimeout);

    // return the event handle that will be signalled whenever
    // there are deferred commands due for execution (when GetDueCommand
    // will not block).
    HANDLE GetDueHandle() {
        return HANDLE(m_evDue);
    };

    // return a pointer to a command that will be due for a given time.
    // Pass in a stream time here. The stream time offset will be passed
    // in via the Run method.
    // Commands remain queued until invoked or cancelled.
    // This method will not block. It will report VFW_E_NOT_FOUND if there
    // are no commands due yet.
    // Returns an AddRef-ed object
    virtual HRESULT GetCommandDueFor(REFERENCE_TIME tStream, CDeferredCommand**ppCmd);

    // check if a given time is due (TRUE if it is due yet)
    BOOL CheckTime(CRefTime time, BOOL bStream) {

        // if no clock, nothing is due!
        if (!m_pClock) {
            return FALSE;
        }

        // stream time
        if (bStream) {

            // not valid if not running
            if (!m_bRunning) {
                return FALSE;
            }
            // add on known stream time offset to get presentation time
            time += m_StreamTimeOffset;
        }

        CRefTime Now;
        m_pClock->GetTime((REFERENCE_TIME*)&Now);
        return (time <= Now);
    };

protected:

    // protect access to lists etc
    CCritSec m_Lock;

    // commands queued in presentation time are stored here
    CGenericList<CDeferredCommand> m_listPresentation;

    // commands queued in stream time are stored here
    CGenericList<CDeferredCommand> m_listStream;

    // set when any commands are due
    CAMEvent m_evDue;

    // creates an advise for the earliest time required, if any
    void SetTimeAdvise(void);

    // advise id from reference clock (0 if no outstanding advise)
    DWORD m_dwAdvise;

    // advise time is for this presentation time
    CRefTime m_tCurrentAdvise;

    // the reference clock we are using (addrefed)
    IReferenceClock* m_pClock;

    // true when running
    BOOL m_bRunning;

    // contains stream time offset when m_bRunning is true
    CRefTime m_StreamTimeOffset;
};

#endif // __CTLUTIL__


DDMM.CPP

/*==========================================================================
 *
 *  Copyright (c) 1995 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       ddmm.cpp
 *  Content:    Routines for using DirectDraw on a multimonitor system
 *
 ***************************************************************************/

#include <streams.h>
#include <ddraw.h>
#include "ddmm.h"

/*
 * FindDeviceCallback
 */
typedef struct {
LPSTR   szDevice;
GUID*   lpGUID;
GUID    GUID;
BOOL    fFound;
}   FindDeviceData;

BOOL CALLBACK FindDeviceCallback(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam)
{
FindDeviceData *p = (FindDeviceData*)lParam;

if (lstrcmpiA(p->szDevice, szDevice) == 0) {
    if (lpGUID) {
p->GUID = *lpGUID;
p->lpGUID = &p->GUID;
    } else {
p->lpGUID = NULL;
    }
    p->fFound = TRUE;
    return FALSE;
}
return TRUE;
}

BOOL CALLBACK FindDeviceCallbackEx(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam, HANDLE hMonitor)
{
FindDeviceData *p = (FindDeviceData*)lParam;

if (lstrcmpiA(p->szDevice, szDevice) == 0) {
    if (lpGUID) {
p->GUID = *lpGUID;
p->lpGUID = &p->GUID;
    } else {
p->lpGUID = NULL;
    }
    p->fFound = TRUE;
    return FALSE;
}
return TRUE;
}

/*
 * DirectDrawCreateFromDevice
 *
 * create a DirectDraw object for a particular device
 */
IDirectDraw * DirectDrawCreateFromDevice(LPSTR szDevice, PDRAWCREATE DirectDrawCreateP, PDRAWENUM DirectDrawEnumerateP)
{
IDirectDraw*    pdd = NULL;
FindDeviceData  find;

if (szDevice == NULL) {
DirectDrawCreateP(NULL, &pdd, NULL);
return pdd;
}

find.szDevice = szDevice;
find.fFound   = FALSE;
DirectDrawEnumerateP(FindDeviceCallback, (LPVOID)&find);

if (find.fFound)
{
//
// In 4bpp mode the following DDraw call causes a message box to be popped
// up by DDraw (!?!).  It's DDraw's fault, but we don't like it.  So we
// make sure it doesn't happen.
//
UINT ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
DirectDrawCreateP(find.lpGUID, &pdd, NULL);
SetErrorMode(ErrorMode);
}

return pdd;
}

/*
 * DirectDrawCreateFromDeviceEx
 *
 * create a DirectDraw object for a particular device
 */
IDirectDraw * DirectDrawCreateFromDeviceEx(LPSTR szDevice, PDRAWCREATE DirectDrawCreateP, LPDIRECTDRAWENUMERATEEXA DirectDrawEnumerateExP)
{
IDirectDraw*    pdd = NULL;
FindDeviceData  find;

if (szDevice == NULL) {
DirectDrawCreateP(NULL, &pdd, NULL);
return pdd;
}

find.szDevice = szDevice;
find.fFound   = FALSE;
DirectDrawEnumerateExP(FindDeviceCallbackEx, (LPVOID)&find,
DDENUM_ATTACHEDSECONDARYDEVICES);

if (find.fFound)
{
//
// In 4bpp mode the following DDraw call causes a message box to be popped
// up by DDraw (!?!).  It's DDraw's fault, but we don't like it.  So we
// make sure it doesn't happen.
//
UINT ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
DirectDrawCreateP(find.lpGUID, &pdd, NULL);
SetErrorMode(ErrorMode);
}

return pdd;
}


DDMM.H

/*==========================================================================
 *
 *  Copyright (c) 1995 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       ddmm.cpp
 *  Content:    Routines for using DirectDraw on a multimonitor system
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif  /* __cplusplus */

// DDRAW.H might not include these
#ifndef DDENUM_ATTACHEDSECONDARYDEVICES
#define DDENUM_ATTACHEDSECONDARYDEVICES     0x00000001L
#endif
#ifndef LPDDENUMCALLBACKEXA
    typedef BOOL (FAR PASCAL * LPDDENUMCALLBACKEXA)(GUID FAR *, LPSTR, LPSTR, LPVOID, HANDLE);// wants HMONITOR
    typedef HRESULT (WINAPI * LPDIRECTDRAWENUMERATEEXA)( LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags);
#endif

typedef HRESULT (*PDRAWCREATE)(IID *,LPDIRECTDRAW *,LPUNKNOWN);
typedef HRESULT (*PDRAWENUM)(LPDDENUMCALLBACKA, LPVOID);

IDirectDraw * DirectDrawCreateFromDevice(LPSTR, PDRAWCREATE, PDRAWENUM);
IDirectDraw * DirectDrawCreateFromDeviceEx(LPSTR, PDRAWCREATE, LPDIRECTDRAWENUMERATEEXA);

#ifdef __cplusplus
}
#endif/* __cplusplus */


DLLENTRY.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// classes used to support dll entrypoints for COM objects.
//
#include <streams.h>

#ifdef DEBUG
#ifdef UNICODE
#ifndef _UNICODE
#define _UNICODE
#endif // _UNICODE
#endif // UNICODE

#include <tchar.h>
#endif // DEBUG

extern CFactoryTemplate g_Templates[];
extern int g_cTemplates;

HINSTANCE g_hInst;
DWORD  g_amPlatform;// VER_PLATFORM_WIN32_WINDOWS etc... (from GetVersionEx)
OSVERSIONINFO g_osInfo;

//
// an instance of this is created by the DLLGetClassObject entrypoint
// it uses the CFactoryTemplate object it is given to support the
// IClassFactory interface

class CClassFactory : public IClassFactory, public CBaseObject
{

private:
    const CFactoryTemplate *const m_pTemplate;

    ULONG m_cRef;

    static int m_cLocked;
public:
    CClassFactory(const CFactoryTemplate *);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
    STDMETHODIMP_(ULONG)AddRef();
    STDMETHODIMP_(ULONG)Release();

    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void **pv);
    STDMETHODIMP LockServer(BOOL fLock);

    // allow DLLGetClassObject to know about global server lock status
    static BOOL IsLocked() {
        return (m_cLocked > 0);
    };
};

// process-wide dll locked state
int CClassFactory::m_cLocked = 0;

CClassFactory::CClassFactory(const CFactoryTemplate *pTemplate)
: CBaseObject(NAME("Class Factory"))
, m_cRef(0)
, m_pTemplate(pTemplate)
{
}

STDMETHODIMP
CClassFactory::QueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER)
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    *ppv = NULL;

    // any interface on this object is the object pointer.
    if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) {
        *ppv = (LPVOID) this;
// AddRef returned interface pointer
        ((LPUNKNOWN) *ppv)->AddRef();
        return NOERROR;
    }

    return ResultFromScode(E_NOINTERFACE);
}

STDMETHODIMP_(ULONG)
CClassFactory::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CClassFactory::Release()
{
    if (--m_cRef == 0) {
        delete this;
        return 0;
    } else {
        return m_cRef;
    }
}

STDMETHODIMP
CClassFactory::CreateInstance(
    LPUNKNOWN pUnkOuter,
    REFIID riid,
    void **pv)
{
    CheckPointer(pv,E_POINTER)
    ValidateReadWritePtr(pv,sizeof(void *));

    /* Enforce the normal OLE rules regarding interfaces and delegation */

    if (pUnkOuter != NULL) {
        if (IsEqualIID(riid,IID_IUnknown) == FALSE) {
            return ResultFromScode(E_NOINTERFACE);
        }
    }

    /* Create the new object through the derived class's create function */

    HRESULT hr = NOERROR;
    CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr);

    if (pObj == NULL) {
if (SUCCEEDED(hr)) {
    hr = E_OUTOFMEMORY;
}
return hr;
    }

    /* Delete the object if we got a construction error */

    if (FAILED(hr)) {
        delete pObj;
        return hr;
    }

    /* Get a reference counted interface on the object */

    /* We wrap the non-delegating QI with NDAddRef & NDRelease. */
    /* This protects any outer object from being prematurely    */
    /* released by an inner object that may have to be created  */
    /* in order to supply the requested interface.              */
    pObj->NonDelegatingAddRef();
    hr = pObj->NonDelegatingQueryInterface(riid, pv);
    pObj->NonDelegatingRelease();
    /* Note that if NonDelegatingQueryInterface fails, it will  */
    /* not increment the ref count, so the NonDelegatingRelease */
    /* will drop the ref back to zero and the object will "self-*/
    /* destruct".  Hence we don't need additional tidy-up code  */
    /* to cope with NonDelegatingQueryInterface failing.        */

    if (SUCCEEDED(hr)) {
        ASSERT(*pv);
    }

    return hr;
}

STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
    if (fLock) {
        m_cLocked++;
    } else {
        m_cLocked--;
    }
    return NOERROR;
}

// --- COM entrypoints -----------------------------------------

//called by COM to get the class factory object for a given class
STDAPI
DllGetClassObject(
    REFCLSID rClsID,
    REFIID riid,
    void **pv)
{
    if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) {
            return E_NOINTERFACE;
    }

    // traverse the array of templates looking for one with this
    // class id
    for (int i = 0; i < g_cTemplates; i++) {
        const CFactoryTemplate * pT = &g_Templates[i];
        if (pT->IsClassID(rClsID)) {

            // found a template - make a class factory based on this
            // template

            *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT);
            if (*pv == NULL) {
                return E_OUTOFMEMORY;
            }
            ((LPUNKNOWN)*pv)->AddRef();
            return NOERROR;
        }
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}

//
//  Call any initialization routines
//
void
DllInitClasses(BOOL bLoading)
{
    int i;

    // traverse the array of templates calling the init routine
    // if they have one
    for (i = 0; i < g_cTemplates; i++) {
        const CFactoryTemplate * pT = &g_Templates[i];
        if (pT->m_lpfnInit != NULL) {
            (*pT->m_lpfnInit)(bLoading, pT->m_ClsID);
        }
    }

}

// called by COM to determine if this dll can be unloaded
// return ok unless there are outstanding objects or a lock requested
// by IClassFactory::LockServer
//
// CClassFactory has a static function that can tell us about the locks,
// and CCOMObject has a static function that can tell us about the active
// object count
STDAPI
DllCanUnloadNow()
{
    DbgLog((LOG_MEMORY,2,TEXT("DLLCanUnloadNow called - IsLocked = %d, Active objects = %d"),
        CClassFactory::IsLocked(),
        CBaseObject::ObjectsActive()));

    if (CClassFactory::IsLocked() || CBaseObject::ObjectsActive()) {
return S_FALSE;
    } else {
        return S_OK;
    }
}

// --- standard WIN32 entrypoints --------------------------------------

extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

BOOL WINAPI
DllEntryPoint(HINSTANCE hInstance, ULONG ulReason, LPVOID pv)
{

    switch (ulReason)
    {

    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hInstance);
        DbgInitialise(hInstance);
{
    // The platform identifier is used to work out whether
    // full unicode support is available or not.  Hence the
    // default will be the lowest common denominator - i.e. N/A
            g_amPlatform = VER_PLATFORM_WIN32_WINDOWS; // win95 assumed in case GetVersionEx fails

            g_osInfo.dwOSVersionInfoSize = sizeof(g_osInfo);
            if (GetVersionEx(&g_osInfo)) {
        g_amPlatform = g_osInfo.dwPlatformId;
    } else {
DbgLog((LOG_ERROR, 1, TEXT("Failed to get the OS platform, assuming Win95")));
    }
}
        g_hInst = hInstance;
        DllInitClasses(TRUE);
        break;

    case DLL_PROCESS_DETACH:
        DllInitClasses(FALSE);

#ifdef DEBUG
        if (CBaseObject::ObjectsActive()) {
            DbgSetModuleLevel(LOG_MEMORY, 2);
            TCHAR szInfo[512];
            extern TCHAR m_ModuleName[];     // Cut down module name

            TCHAR FullName[_MAX_PATH];      // Load the full path and module name
            TCHAR *pName;                   // Searches from the end for a backslash

            GetModuleFileName(NULL,FullName,_MAX_PATH);
            pName = _tcsrchr(FullName,'\\');
            if (pName == NULL) {
                pName = FullName;
            } else {
                pName++;
            }

    DWORD cch = wsprintf(szInfo, TEXT("Executable: %s  Pid %x  Tid %x. "),
    pName, GetCurrentProcessId(), GetCurrentThreadId());

            wsprintf(szInfo+cch, TEXT("Module %s, %d objects left active!"),
                     m_ModuleName, CBaseObject::ObjectsActive());
            DbgAssert(szInfo, TEXT(__FILE__),__LINE__);

    // If running remotely wait for the Assert to be acknowledged
    // before dumping out the object register
            DbgDumpObjectRegister();
        }
        DbgTerminate();
#endif
        break;
    }
    return TRUE;
}


DLLSETUP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>

//---------------------------------------------------------------------------
// defines

#define MAX_KEY_LEN  260

//---------------------------------------------------------------------------
// externally defined functions/variable

extern int g_cTemplates;
extern CFactoryTemplate g_Templates[];

//---------------------------------------------------------------------------
//
// EliminateSubKey
//
// Try to enumerate all keys under this one.
// if we find anything, delete it completely.
// Otherwise just delete it.
//
// note - this was pinched/duplicated from
// Filgraph\Mapper.cpp - so should it be in
// a lib somewhere?
//
//---------------------------------------------------------------------------

STDAPI
EliminateSubKey( HKEY hkey, LPTSTR strSubKey )
{
  HKEY hk;
  if (0 == lstrlen(strSubKey) ) {
      // defensive approach
      return E_FAIL;
  }

  LONG lreturn = RegOpenKeyEx( hkey
                             , strSubKey
                             , 0
                             , MAXIMUM_ALLOWED
                             , &hk );

  ASSERT(    lreturn == ERROR_SUCCESS
          || lreturn == ERROR_FILE_NOT_FOUND
          || lreturn == ERROR_INVALID_HANDLE );

  if( ERROR_SUCCESS == lreturn )
  {
    // Keep on enumerating the first (zero-th)
    // key and deleting that

    for( ; ; )
    {
      TCHAR Buffer[MAX_KEY_LEN];
      DWORD dw = MAX_KEY_LEN;
      FILETIME ft;

      lreturn = RegEnumKeyEx( hk
                            , 0
                            , Buffer
                            , &dw
                            , NULL
                            , NULL
                            , NULL
                            , &ft);

      ASSERT(    lreturn == ERROR_SUCCESS
              || lreturn == ERROR_NO_MORE_ITEMS );

      if( ERROR_SUCCESS == lreturn )
      {
        EliminateSubKey(hk, Buffer);
      }
      else
      {
        break;
      }
    }

    RegCloseKey(hk);
    RegDeleteKey(hkey, strSubKey);
  }

  return NOERROR;
}

//---------------------------------------------------------------------------
//
// AMovieSetupRegisterServer()
//
// registers specfied file "szFileName" as server for
// CLSID "clsServer".  A description is also required.
// The ThreadingModel and ServerType are optional, as
// they default to InprocServer32 (i.e. dll) and Both.
//
//---------------------------------------------------------------------------

STDAPI
AMovieSetupRegisterServer( CLSID   clsServer
                         , LPCWSTR szDescription
                         , LPCWSTR szFileName
                         , LPCWSTR szThreadingModel = L"Both"
                         , LPCWSTR szServerType     = L"InprocServer32" )
{
  // temp buffer
  //
  TCHAR achTemp[MAX_PATH];

  // convert CLSID uuid to string and write
  // out subkey as string - CLSID\{}
  //
  OLECHAR szCLSID[CHARS_IN_GUID];
  HRESULT hr = StringFromGUID2( clsServer
                              , szCLSID
                              , CHARS_IN_GUID );
  ASSERT( SUCCEEDED(hr) );

  // create key
  //
  HKEY hkey;
  wsprintf( achTemp, TEXT("CLSID\\%ls"), szCLSID );
  LONG lreturn = RegCreateKey( HKEY_CLASSES_ROOT
                             , (LPCTSTR)achTemp
                             , &hkey              );
  if( ERROR_SUCCESS != lreturn )
  {
    return HRESULT_FROM_WIN32(lreturn);
  }

  // set description string
  //

  wsprintf( achTemp, TEXT("%ls"), szDescription );
  lreturn = RegSetValue( hkey
                       , (LPCTSTR)NULL
                       , REG_SZ
                       , achTemp
                       , sizeof(achTemp) );
  if( ERROR_SUCCESS != lreturn )
  {
    RegCloseKey( hkey );
    return HRESULT_FROM_WIN32(lreturn);
  }

  // create CLSID\\{"CLSID"}\\"ServerType" key,
  // using key to CLSID\\{"CLSID"} passed back by
  // last call to RegCreateKey().
  //
  HKEY hsubkey;

  wsprintf( achTemp, TEXT("%ls"), szServerType );
  lreturn = RegCreateKey( hkey
                        , achTemp
                        , &hsubkey     );
  if( ERROR_SUCCESS != lreturn )
  {
    RegCloseKey( hkey );
    return HRESULT_FROM_WIN32(lreturn);
  }

  // set Server string
  //
  wsprintf( achTemp, TEXT("%ls"), szFileName );
  lreturn = RegSetValue( hsubkey
                       , (LPCTSTR)NULL
                       , REG_SZ
                       , (LPCTSTR)achTemp
                       , sizeof(TCHAR) * (lstrlen(achTemp)+1) );
  if( ERROR_SUCCESS != lreturn )
  {
    RegCloseKey( hkey );
    RegCloseKey( hsubkey );
    return HRESULT_FROM_WIN32(lreturn);
  }

  wsprintf( achTemp, TEXT("%ls"), szThreadingModel );
  lreturn = RegSetValueEx( hsubkey
                         , TEXT("ThreadingModel")
                         , 0L
                         , REG_SZ
                         , (CONST BYTE *)achTemp
                         , sizeof(TCHAR) * (lstrlen(achTemp)+1) );

  // close hkeys
  //
  RegCloseKey( hkey );
  RegCloseKey( hsubkey );

  // and return
  //
  return HRESULT_FROM_WIN32(lreturn);

}

//---------------------------------------------------------------------------
//
// AMovieSetupUnregisterServer()
//
// default ActiveMovie dll setup function
// - to use must be called from an exported
//   function named DllRegisterServer()
//
//---------------------------------------------------------------------------

STDAPI
AMovieSetupUnregisterServer( CLSID clsServer )
{
  // convert CLSID uuid to string and write
  // out subkey CLSID\{}
  //
  OLECHAR szCLSID[CHARS_IN_GUID];
  HRESULT hr = StringFromGUID2( clsServer
                              , szCLSID
                              , CHARS_IN_GUID );
  ASSERT( SUCCEEDED(hr) );

  TCHAR achBuffer[MAX_KEY_LEN];
  wsprintf( achBuffer, TEXT("CLSID\\%ls"), szCLSID );

  // delete subkey
  //

  hr = EliminateSubKey( HKEY_CLASSES_ROOT, achBuffer );
  ASSERT( SUCCEEDED(hr) );

  // return
  //
  return NOERROR;
}

//---------------------------------------------------------------------------
//
// AMovieSetupRegisterFilter through IFilterMapper2
//
//---------------------------------------------------------------------------

STDAPI
AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata
                          , IFilterMapper2 *                 pIFM2
                          , BOOL                             bRegister  )
{
  DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter")));

  // check we've got data
  //
  if( NULL == psetupdata ) return S_FALSE;

  // unregister filter
  // (as pins are subkeys of filter's CLSID key
  // they do not need to be removed separately).
  //
  DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter")));
  HRESULT hr = pIFM2->UnregisterFilter(
      0,                        // default category
      0,                        // default instance name
      *psetupdata->clsID );

  if( bRegister )
  {
    REGFILTER2 rf2;
    rf2.dwVersion = 1;
    rf2.dwMerit = psetupdata->dwMerit;
    rf2.cPins = psetupdata->nPins;
    rf2.rgPins = psetupdata->lpPin;

    // register filter
    //
    DbgLog((LOG_TRACE, 3, TEXT("= = register filter")));
    hr = pIFM2->RegisterFilter(*psetupdata->clsID
                             , psetupdata->strName
                             , 0 // moniker
                             , 0 // category
                             , NULL // instance
                             , &rf2);
  }

  // handle one acceptable "error" - that
  // of filter not being registered!
  // (couldn't find a suitable #define'd
  // name for the error!)
  //
  if( 0x80070002 == hr)
    return NOERROR;
  else
    return hr;
}

//---------------------------------------------------------------------------
//
// RegisterAllServers()
//
//---------------------------------------------------------------------------

STDAPI
RegisterAllServers( LPCWSTR szFileName, BOOL bRegister )
{
  HRESULT hr = NOERROR;

  for( int i = 0; i < g_cTemplates; i++ )
  {
    // get i'th template
    //
    const CFactoryTemplate *pT = &g_Templates[i];

    DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"),
           (LPCWSTR)pT->m_Name ));

    // register CLSID and InprocServer32
    //
    if( bRegister )
    {
      hr = AMovieSetupRegisterServer( *(pT->m_ClsID)
                                    , (LPCWSTR)pT->m_Name
                                    , szFileName );
    }
    else
    {
      hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) );
    }

    // check final error for this pass
    // and break loop if we failed
    //
    if( FAILED(hr) )
      break;
  }

  return hr;
}

//---------------------------------------------------------------------------
//
// AMovieDllRegisterServer2()
//
// default ActiveMovie dll setup function
// - to use must be called from an exported
//   function named DllRegisterServer()
//
// this function is table driven using the
// static members of the CFactoryTemplate
// class defined in the dll.
//
// it registers the Dll as the InprocServer32
// and then calls the IAMovieSetup.Register
// method.
//
//---------------------------------------------------------------------------

STDAPI
AMovieDllRegisterServer2( BOOL bRegister )
{
  HRESULT hr = NOERROR;

  DbgLog((LOG_TRACE, 2, TEXT("AMovieDllRegisterServer2()")));

  // get file name (where g_hInst is the
  // instance handle of the filter dll)
  //
  WCHAR achFileName[MAX_PATH];

  // WIN95 doesn't support GetModuleFileNameW
  //
  {
    char achTemp[MAX_PATH];

    DbgLog((LOG_TRACE, 2, TEXT("- get module file name")));

    if( 0 == GetModuleFileNameA( g_hInst
                              , achTemp
                              , sizeof(achTemp) ) )
    {
      // we've failed!
      DWORD dwerr = GetLastError();
      return HRESULT_FROM_WIN32(dwerr);
    }

    MultiByteToWideChar( CP_ACP
                       , 0L
                       , achTemp
                       , lstrlenA(achTemp) + 1
                       , achFileName
                       , sizeof(achFileName) );
  }

  //
  // first registering, register all OLE servers
  //
  if( bRegister )
  {
    DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers")));
    hr = RegisterAllServers( achFileName, TRUE );
  }

  //
  // next, register/unregister all filters
  //

  if( SUCCEEDED(hr) )
  {
    // init is ref counted so call just in case
    // we're being called cold.
    //
    DbgLog((LOG_TRACE, 2, TEXT("- CoInitialize")));
    hr = CoInitialize( (LPVOID)NULL );
    ASSERT( SUCCEEDED(hr) );

    // get hold of IFilterMapper2
    //
    DbgLog((LOG_TRACE, 2, TEXT("- obtain IFilterMapper2")));
    IFilterMapper2 *pIFM2 = 0;
    IFilterMapper *pIFM = 0;
    hr = CoCreateInstance( CLSID_FilterMapper2
                         , NULL
                         , CLSCTX_INPROC_SERVER
                         , IID_IFilterMapper2
                         , (void **)&pIFM2       );
    if(FAILED(hr))
    {
        DbgLog((LOG_TRACE, 2, TEXT("- trying IFilterMapper instead")));

        hr = CoCreateInstance(
            CLSID_FilterMapper,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IFilterMapper,
            (void **)&pIFM);
    }
    if( SUCCEEDED(hr) )
    {
      // scan through array of CFactoryTemplates
      // registering servers and filters.
      //
      DbgLog((LOG_TRACE, 2, TEXT("- register Filters")));
      for( int i = 0; i < g_cTemplates; i++ )
      {
        // get i'th template
        //
        const CFactoryTemplate *pT = &g_Templates[i];

        if( NULL != pT->m_pAMovieSetup_Filter )
        {
          DbgLog((LOG_TRACE, 2, TEXT("- - register %ls"), (LPCWSTR)pT->m_Name ));

          if(pIFM2)
          {
              hr = AMovieSetupRegisterFilter2( pT->m_pAMovieSetup_Filter, pIFM2, bRegister );
          }
          else
          {
              hr = AMovieSetupRegisterFilter( pT->m_pAMovieSetup_Filter, pIFM, bRegister );
          }
        }

        // check final error for this pass
        // and break loop if we failed
        //
        if( FAILED(hr) )
          break;
      }

      // release interface
      //
      if(pIFM2)
          pIFM2->Release();
      else
          pIFM->Release();

    }

    // and clear up
    //
    CoFreeUnusedLibraries();
    CoUninitialize();
  }

  //
  // if unregistering, unregister all OLE servers
  //
  if( SUCCEEDED(hr) && !bRegister )
  {
    DbgLog((LOG_TRACE, 2, TEXT("- register OLE Servers")));
    hr = RegisterAllServers( achFileName, FALSE );
  }

  DbgLog((LOG_TRACE, 2, TEXT("- return %0x"), hr));
  return hr;
}

//---------------------------------------------------------------------------
//
// AMovieDllRegisterServer()
//
// default ActiveMovie dll setup function
// - to use must be called from an exported
//   function named DllRegisterServer()
//
// this function is table driven using the
// static members of the CFactoryTemplate
// class defined in the dll.
//
// it registers the Dll as the InprocServer32
// and then calls the IAMovieSetup.Register
// method.
//
//---------------------------------------------------------------------------

STDAPI
AMovieDllRegisterServer( void )
{
  HRESULT hr = NOERROR;

  // get file name (where g_hInst is the
  // instance handle of the filter dll)
  //
  WCHAR achFileName[MAX_PATH];

  {
    // WIN95 doesn't support GetModuleFileNameW
    //
    char achTemp[MAX_PATH];

    if( 0 == GetModuleFileNameA( g_hInst
                              , achTemp
                              , sizeof(achTemp) ) )
    {
      // we've failed!
      DWORD dwerr = GetLastError();
      return HRESULT_FROM_WIN32(dwerr);
    }

    MultiByteToWideChar( CP_ACP
                       , 0L
                       , achTemp
                       , lstrlenA(achTemp) + 1
                       , achFileName
                       , sizeof(achFileName) );
  }

  // scan through array of CFactoryTemplates
  // registering servers and filters.
  //
  for( int i = 0; i < g_cTemplates; i++ )
  {
    // get i'th template
    //
    const CFactoryTemplate *pT = &g_Templates[i];

    // register CLSID and InprocServer32
    //
    hr = AMovieSetupRegisterServer( *(pT->m_ClsID)
                                  , (LPCWSTR)pT->m_Name
                                  , achFileName );

    // instantiate all servers and get hold of
    // IAMovieSetup, if implemented, and call
    // IAMovieSetup.Register() method
    //
    if( SUCCEEDED(hr) && (NULL != pT->m_lpfnNew) )
    {
      // instantiate object
      //
      PAMOVIESETUP psetup;
      hr = CoCreateInstance( *(pT->m_ClsID)
                           , 0
                           , CLSCTX_INPROC_SERVER
                           , IID_IAMovieSetup
                           , reinterpret_cast<void**>(&psetup) );
      if( SUCCEEDED(hr) )
      {
        hr = psetup->Unregister();
        if( SUCCEEDED(hr) )
          hr = psetup->Register();
        psetup->Release();
      }
      else
      {
        if(    (E_NOINTERFACE      == hr )
            || (VFW_E_NEED_OWNER == hr ) )
          hr = NOERROR;
      }
    }

    // check final error for this pass
    // and break loop if we failed
    //
    if( FAILED(hr) )
      break;

  } // end-for

  return hr;
}

//---------------------------------------------------------------------------
//
// AMovieDllUnregisterServer()
//
// default ActiveMovie dll uninstall function
// - to use must be called from an exported
//   function named DllRegisterServer()
//
// this function is table driven using the
// static members of the CFactoryTemplate
// class defined in the dll.
//
// it calls the IAMovieSetup.Unregister
// method and then unregisters the Dll
// as the InprocServer32
//
//---------------------------------------------------------------------------

STDAPI
AMovieDllUnregisterServer()
{
  // initialize return code
  //
  HRESULT hr = NOERROR;

  // scan through CFactory template and unregister
  // all OLE servers and filters.
  //
  for( int i = g_cTemplates; i--; )
  {
    // get i'th template
    //
    const CFactoryTemplate *pT = &g_Templates[i];

    // check method exists
    //
    if( NULL != pT->m_lpfnNew )
    {
      // instantiate object
      //
      PAMOVIESETUP psetup;
      hr = CoCreateInstance( *(pT->m_ClsID)
                           , 0
                           , CLSCTX_INPROC_SERVER
                           , IID_IAMovieSetup
                           , reinterpret_cast<void**>(&psetup) );
      if( SUCCEEDED(hr) )
      {
        hr = psetup->Unregister();
        psetup->Release();
      }
      else
      {
        if(    (E_NOINTERFACE      == hr )
            || (VFW_E_NEED_OWNER == hr ) )
           hr = NOERROR;
      }
    }

    // unregister CLSID and InprocServer32
    //
    if( SUCCEEDED(hr) )
    {
      hr = AMovieSetupUnregisterServer( *(pT->m_ClsID) );
    }

    // check final error for this pass
    // and break loop if we failed
    //
    if( FAILED(hr) )
      break;
  }

  return hr;
}


DLLSETUP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// To be self registering, OLE servers must
// export functions named DllRegisterServer
// and DllUnregisterServer.  To allow use of
// custom and default implementations the
// defaults are named AMovieDllRegisterServer
// and AMovieDllUnregisterServer.
//
// To the use the default implementation you
// must provide stub functions.
//
// i.e. STDAPI DllRegisterServer()
//      {
//        return AMovieDllRegisterServer();
//      }
//
//      STDAPI DllUnregisterServer()
//      {
//        return AMovieDllUnregisterServer();
//      }
//
//
// AMovieDllRegisterServer   calls IAMovieSetup.Register(), and
// AMovieDllUnregisterServer calls IAMovieSetup.Unregister().

STDAPI AMovieDllRegisterServer2( BOOL );
STDAPI AMovieDllRegisterServer();
STDAPI AMovieDllUnregisterServer();

// helper functions
STDAPI EliminateSubKey( HKEY, LPTSTR );

STDAPI
AMovieSetupRegisterFilter2( const AMOVIESETUP_FILTER * const psetupdata
                          , IFilterMapper2 *                 pIFM2
                          , BOOL                             bRegister  );


FOURCC.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// FOURCCMap
//
// provides a mapping between old-style multimedia format DWORDs
// and new-style GUIDs.
//
// A range of 4 billion GUIDs has been allocated to ensure that this
// mapping can be done straightforwardly one-to-one in both directions.
//
// January 95

#ifndef __FOURCC__
#define __FOURCC__

// Multimedia format types are marked with DWORDs built from four 8-bit
// chars and known as FOURCCs. New multimedia AM_MEDIA_TYPE definitions include
// a subtype GUID. In order to simplify the mapping, GUIDs in the range:
//    XXXXXXXX-0000-0010-8000-00AA00389B71
// are reserved for FOURCCs.

class FOURCCMap : public GUID
{

public:
    FOURCCMap();
    FOURCCMap(DWORD Fourcc);
    FOURCCMap(const GUID *);

    DWORD GetFOURCC(void);
    void SetFOURCC(DWORD fourcc);
    void SetFOURCC(const GUID *);

private:
    void InitGUID();
};

#define GUID_Data2      0
#define GUID_Data3     0x10
#define GUID_Data4_1   0xaa000080
#define GUID_Data4_2   0x719b3800

inline void
FOURCCMap::InitGUID() {
    Data2 = GUID_Data2;
    Data3 = GUID_Data3;
    ((DWORD *)Data4)[0] = GUID_Data4_1;
    ((DWORD *)Data4)[1] = GUID_Data4_2;
}

inline
FOURCCMap::FOURCCMap() {
    InitGUID();
    SetFOURCC( DWORD(0));
}

inline
FOURCCMap::FOURCCMap(DWORD fourcc)
{
    InitGUID();
    SetFOURCC(fourcc);
}

inline
FOURCCMap::FOURCCMap(const GUID * pGuid)
{
    InitGUID();
    SetFOURCC(pGuid);
}

inline void
FOURCCMap::SetFOURCC(const GUID * pGuid)
{
    FOURCCMap * p = (FOURCCMap*) pGuid;
    SetFOURCC(p->GetFOURCC());
}

inline void
FOURCCMap::SetFOURCC(DWORD fourcc)
{
    Data1 = fourcc;
}

inline DWORD
FOURCCMap::GetFOURCC(void)
{
    return Data1;
}

#endif /* __FOURCC__ */


MEASURE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*
   The idea is to pepper the source code with interesting measurements and
   have the last few thousand of these recorded in a circular buffer that
   can be post-processed to give interesting numbers.

   WHAT THE LOG LOOKS LIKE:

  Time (sec)   Type        Delta  Incident_Name
    0.055,41  NOTE      -.       Incident Nine  - Another note
    0.055,42  NOTE      0.000,01 Incident Nine  - Another note
    0.055,44  NOTE      0.000,02 Incident Nine  - Another note
    0.055,45  STOP      -.       Incident Eight - Also random
    0.055,47  START     -.       Incident Seven - Random
    0.055,49  NOTE      0.000,05 Incident Nine  - Another note
    ------- <etc.  there is a lot of this> ----------------
    0.125,60  STOP      0.000,03 Msr_Stop
    0.125,62  START     -.       Msr_Start
    0.125,63  START     -.       Incident Two   - Start/Stop
    0.125,65  STOP      0.000,03 Msr_Start
    0.125,66  START     -.       Msr_Stop
    0.125,68  STOP      0.000,05 Incident Two   - Start/Stop
    0.125,70  STOP      0.000,04 Msr_Stop
    0.125,72  START     -.       Msr_Start
    0.125,73  START     -.       Incident Two   - Start/Stop
    0.125,75  STOP      0.000,03 Msr_Start
    0.125,77  START     -.       Msr_Stop
    0.125,78  STOP      0.000,05 Incident Two   - Start/Stop
    0.125,80  STOP      0.000,03 Msr_Stop
    0.125,81  NOTE      -.       Incident Three - single Note
    0.125,83  START     -.       Incident Four  - Start, no stop
    0.125,85  START     -.       Incident Five  - Single Start/Stop
    0.125,87  STOP      0.000,02 Incident Five  - Single Start/Stop

Number      Average       StdDev     Smallest      Largest Incident_Name
    10     0.000,58     0.000,10     0.000,55     0.000,85 Incident One   - Note
    50     0.000,05     0.000,00     0.000,05     0.000,05 Incident Two   - Start/Stop
     1     -.           -.           -.           -.       Incident Three - single Note
     0     -.           -.           -.           -.       Incident Four  - Start, no stop
     1     0.000,02     -.           0.000,02     0.000,02 Incident Five  - Single Start/Stop
     0     -.           -.           -.           -.       Incident Six   - zero occurrences
   100     0.000,25     0.000,12     0.000,02     0.000,62 Incident Seven - Random
   100     0.000,79     0.000,48     0.000,02     0.001,92 Incident Eight - Also random
  5895     0.000,01     0.000,01     0.000,01     0.000,56 Incident Nine  - Another note
    10     0.000,03     0.000,00     0.000,03     0.000,04 Msr_Note
    50     0.000,03     0.000,00     0.000,03     0.000,04 Msr_Start
    50     0.000,04     0.000,03     0.000,03     0.000,31 Msr_Stop

  WHAT IT MEANS:
    The log shows what happened and when.  Each line shows the time at which
    something happened (see WHAT YOU CODE below) what it was that happened
    and (if approporate) the time since the corresponding previous event
    (that's the delta column).

    The statistics show how many times each event occurred, what the average
    delta time was, also the standard deviation, largest and smalles delta.

   WHAT YOU CODE:

   Before anything else executes: - register your ids

    int id1     = Msr_Register("Incident One   - Note");
    int id2     = Msr_Register("Incident Two   - Start/Stop");
    int id3     = Msr_Register("Incident Three - single Note");
    etc.

   At interesting moments:

       // To measure a repetitive event - e.g. end of bitblt to screen
       Msr_Note(Id9);             // e.g. "video frame hiting the screen NOW!"

           or

       // To measure an elapsed time e.g. time taken to decode an MPEG B-frame
       Msr_Start(Id2);            // e.g. "Starting to decode MPEG B-frame"
         . . .
       MsrStop(Id2);              //      "Finished MPEG decode"

   At the end:

       HANDLE hFile;
       hFile = CreateFile("Perf.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
       Msr_Dump(hFile);           // This writes the log out to the file
       CloseHandle(hFile);

           or

       Msr_Dump(NULL);            // This writes it to DbgLog((LOG_TRACE,0, ... ));
                                  // but if you are writing it out to the debugger
                                  // then the times are probably all garbage because
                                  // the debugger can make things run awfully slow.

    A given id should be used either for start / stop or Note calls.  If Notes
    are mixed in with Starts and Stops their statistics will be gibberish.

    If you code the calls in upper case i.e. MSR_START(idMunge); then you get
    macros which will turn into nothing unless PERF is defined.

    You can reset the statistical counts for a given id by calling Reset(Id).
    They are reset by default at the start.
    It logs Reset as a special incident, so you can see it in the log.

    The log is a circular buffer in storage (to try to minimise disk I/O).
    It overwrites the oldest entries once full.  The statistics include ALL
    incidents since the last Reset, whether still visible in the log or not.
*/

#ifndef __MEASURE__
#define __MEASURE__

#ifdef PERF
#define MSR_INIT() Msr_Init()
#define MSR_TERMINATE() Msr_Terminate()
#define MSR_REGISTER(a) Msr_Register(a)
#define MSR_RESET(a) Msr_Reset(a)
#define MSR_CONTROL(a) Msr_Control(a)
#define MSR_START(a) Msr_Start(a)
#define MSR_STOP(a) Msr_Stop(a)
#define MSR_NOTE(a) Msr_Note(a)
#define MSR_INTEGER(a,b) Msr_Integer(a,b)
#define MSR_DUMP(a) Msr_Dump(a)
#define MSR_DUMPSTATS(a) Msr_DumpStats(a)
#else
#define MSR_INIT()
#define MSR_TERMINATE()
#define MSR_REGISTER(a) 0
#define MSR_RESET(a)
#define MSR_CONTROL(a)
#define MSR_START(a)
#define MSR_STOP(a)
#define MSR_NOTE(a)
#define MSR_INTEGER(a,b)
#define MSR_DUMP(a)
#define MSR_DUMPSTATS(a)
#endif

#ifdef __cplusplus
extern "C" {
#endif

// This must be called first - (called by the DllEntry)

void WINAPI Msr_Init(void);

// Call this last to clean up (or just let it fall off the end - who cares?)

void WINAPI Msr_Terminate(void);

// Call this to get an Id for an "incident" that you can pass to Start, Stop or Note
// everything that's logged is called an "incident".

int  WINAPI Msr_Register(LPTSTR Incident);

// Reset the statistical counts for an incident

void WINAPI Msr_Reset(int Id);

// Reset all the counts for all incidents
#define MSR_RESET_ALL 0
#define MSR_PAUSE 1
#define MSR_RUN 2

void WINAPI Msr_Control(int iAction);

// log the start of an operation

void WINAPI Msr_Start(int Id);

// log the end of an operation

void WINAPI Msr_Stop(int Id);

// log a one-off or repetitive operation

void WINAPI Msr_Note(int Id);

// log an integer (on which we can see statistics later)
void WINAPI Msr_Integer(int Id, int n);

// print out all the vaialable log (it may have wrapped) and then the statistics.
// When the log wraps you lose log but the statistics are still complete.
// hFIle==NULL => use DbgLog
// otherwise hFile must have come from CreateFile or OpenFile.

void WINAPI Msr_Dump(HANDLE hFile);

// just dump the statistics - never mind the log

void WINAPI Msr_DumpStats(HANDLE hFile);

// Type definitions in case you want to declare a pointer to the dump functions
// (makes it a trifle easier to do dynamic linking
// i.e. LoadModule, GetProcAddress and call that)

// Typedefs so can declare MSR_DUMPPROC *MsrDumpStats; or whatever
typedef void WINAPI MSR_DUMPPROC(HANDLE hFile);
typedef void WINAPI MSR_CONTROLPROC(int iAction);

#ifdef __cplusplus
}
#endif

#endif // __MEASURE__


MSGTHRD.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// support for a worker thread class to which you can asynchronously post
// messages

// Message class - really just a structure.
//
class CMsg {
public:
    UINT uMsg;
    DWORD dwFlags;
    LPVOID lpParam;
    CAMEvent *pEvent;

    CMsg(UINT u, DWORD dw, LPVOID lp, CAMEvent *pEvnt)
        : uMsg(u), dwFlags(dw), lpParam(lp), pEvent(pEvnt) {}

    CMsg()
        : uMsg(0), dwFlags(0L), lpParam(NULL), pEvent(NULL) {}
};

// This is the actual thread class.  It exports all the usual thread control
// functions.  The created thread is different from a normal WIN32 thread in
// that it is prompted to perform particaular tasks by responding to messages
// posted to its message queue.
//
class AM_NOVTABLE CMsgThread {
private:
    static DWORD WINAPI DefaultThreadProc(LPVOID lpParam);
    DWORD               m_ThreadId;
    HANDLE              m_hThread;

protected:

    // if you want to override GetThreadMsg to block on other things
    // as well as this queue, you need access to this
    CGenericList<CMsg>        m_ThreadQueue;
    CCritSec                  m_Lock;
    HANDLE                    m_hSem;
    LONG                      m_lWaiting;

public:
    CMsgThread()
        : m_ThreadId(0),
        m_hThread(NULL),
        m_lWaiting(0),
        m_hSem(NULL),
        // make a list with a cache of 5 items
        m_ThreadQueue(NAME("MsgThread list"), 5)
        {
        }

    ~CMsgThread();
    // override this if you want to block on other things as well
    // as the message loop
    void virtual GetThreadMsg(CMsg *msg);

    // override this if you want to do something on thread startup
    virtual void OnThreadInit() {
    };

    BOOL CreateThread();

    BOOL WaitForThreadExit(LPDWORD lpdwExitCode) {
        if (m_hThread != NULL) {
            WaitForSingleObject(m_hThread, INFINITE);
            return GetExitCodeThread(m_hThread, lpdwExitCode);
        }
        return FALSE;
    }

    DWORD ResumeThread() {
        return ::ResumeThread(m_hThread);
    }

    DWORD SuspendThread() {
        return ::SuspendThread(m_hThread);
    }

    int GetThreadPriority() {
        return ::GetThreadPriority(m_hThread);
    }

    BOOL SetThreadPriority(int nPriority) {
        return ::SetThreadPriority(m_hThread, nPriority);
    }

    HANDLE GetThreadHandle() {
        return m_hThread;
    }

    DWORD GetThreadId() {
        return m_ThreadId;
    }

    void PutThreadMsg(UINT uMsg, DWORD dwMsgFlags,
                      LPVOID lpMsgParam, CAMEvent *pEvent = NULL) {
        CAutoLock lck(&m_Lock);
        CMsg* pMsg = new CMsg(uMsg, dwMsgFlags, lpMsgParam, pEvent);
        m_ThreadQueue.AddTail(pMsg);
        if (m_lWaiting != 0) {
            ReleaseSemaphore(m_hSem, m_lWaiting, 0);
            m_lWaiting = 0;
        }
    }

    // This is the function prototype of the function that the client
    // supplies.  It is always called on the created thread, never on
    // the creator thread.
    //
    virtual LRESULT ThreadMessageProc(
        UINT uMsg, DWORD dwFlags, LPVOID lpParam, CAMEvent *pEvent) = 0;
};


MTYPE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Class that holds and manages media type information, December 1994

// helper class that derived pin objects can use to compare media
// types etc. Has same data members as the struct AM_MEDIA_TYPE defined
// in the streams IDL file, but also has (non-virtual) functions

#include <streams.h>

CMediaType::~CMediaType(){
    FreeMediaType(*this);
}

CMediaType::CMediaType()
{
    InitMediaType();
}

CMediaType::CMediaType(const GUID * type)
{
    InitMediaType();
    majortype = *type;
}

// copy constructor does a deep copy of the format block

CMediaType::CMediaType(const AM_MEDIA_TYPE& rt)
{
    CopyMediaType(this, &rt);
}

CMediaType::CMediaType(const CMediaType& rt)
{
    CopyMediaType(this, &rt);
}

// this class inherits publicly from AM_MEDIA_TYPE so the compiler could generate
// the following assignment operator itself, however it could introduce some
// memory conflicts and leaks in the process because the structure contains
// a dynamically allocated block (pbFormat) which it will not copy correctly

CMediaType&
CMediaType::operator=(const AM_MEDIA_TYPE& rt)
{
    FreeMediaType(*this);
    CopyMediaType(this, &rt);
    return *this;
}

CMediaType&
CMediaType::operator=(const CMediaType& rt)
{
    *this = (AM_MEDIA_TYPE &) rt;
    return *this;
}

BOOL
CMediaType::operator == (const CMediaType& rt) const
{
    // I don't believe we need to check sample size or
    // temporal compression flags, since I think these must
    // be represented in the type, subtype and format somehow. They
    // are pulled out as separate flags so that people who don't understand
    // the particular format representation can still see them, but
    // they should duplicate information in the format block.

    return ((IsEqualGUID(majortype,rt.majortype) == TRUE) &&
        (IsEqualGUID(subtype,rt.subtype) == TRUE) &&
        (IsEqualGUID(formattype,rt.formattype) == TRUE) &&
        (cbFormat == rt.cbFormat) &&
        ( (cbFormat == 0) ||
          (memcmp(pbFormat, rt.pbFormat, cbFormat) == 0)));
}

BOOL
CMediaType::operator != (const CMediaType& rt) const
{
    /* Check to see if they are equal */

    if (*this == rt) {
        return FALSE;
    }
    return TRUE;
}

BOOL
CMediaType::IsValid() const
{
    return (!IsEqualGUID(majortype,GUID_NULL));
}

void
CMediaType::SetType(const GUID* ptype)
{
    majortype = *ptype;
}

void
CMediaType::SetSubtype(const GUID* ptype)
{
    subtype = *ptype;
}

ULONG
CMediaType::GetSampleSize() const {
    if (IsFixedSize()) {
        return lSampleSize;
    } else {
        return 0;
    }
}

void
CMediaType::SetSampleSize(ULONG sz) {
    if (sz == 0) {
        SetVariableSize();
    } else {
        bFixedSizeSamples = TRUE;
        lSampleSize = sz;
    }
}

void
CMediaType::SetVariableSize() {
    bFixedSizeSamples = FALSE;
}

void
CMediaType::SetTemporalCompression(BOOL bCompressed) {
    bTemporalCompression = bCompressed;
}

BOOL
CMediaType::SetFormat(BYTE * pformat, ULONG cb)
{
    if (NULL == AllocFormatBuffer(cb))
return(FALSE);

    ASSERT(pbFormat);
    memcpy(pbFormat, pformat, cb);
    return(TRUE);
}

// set the type of the media type format block, this type defines what you
// will actually find in the format pointer. For example FORMAT_VideoInfo or
// FORMAT_WaveFormatEx. In the future this may be an interface pointer to a
// property set. Before sending out media types this should be filled in.

void
CMediaType::SetFormatType(const GUID *pformattype)
{
    formattype = *pformattype;
}

// reset the format buffer

void CMediaType::ResetFormatBuffer()
{
    if (cbFormat) {
        CoTaskMemFree((PVOID)pbFormat);
    }
    cbFormat = 0;
    pbFormat = NULL;
}

// allocate length bytes for the format and return a read/write pointer
// If we cannot allocate the new block of memory we return NULL leaving
// the original block of memory untouched (as does ReallocFormatBuffer)

BYTE*
CMediaType::AllocFormatBuffer(ULONG length)
{
    ASSERT(length);

    // do the types have the same buffer size

    if (cbFormat == length) {
        return pbFormat;
    }

    // allocate the new format buffer

    BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length);
    if (pNewFormat == NULL) {
        if (length <= cbFormat) return pbFormat; //reuse the old block anyway.
        return NULL;
    }

    // delete the old format

    if (cbFormat != 0) {
        ASSERT(pbFormat);
        CoTaskMemFree((PVOID)pbFormat);
    }

    cbFormat = length;
    pbFormat = pNewFormat;
    return pbFormat;
}

// reallocate length bytes for the format and return a read/write pointer
// to it. We keep as much information as we can given the new buffer size
// if this fails the original format buffer is left untouched. The caller
// is responsible for ensuring the size of memory required is non zero

BYTE*
CMediaType::ReallocFormatBuffer(ULONG length)
{
    ASSERT(length);

    // do the types have the same buffer size

    if (cbFormat == length) {
        return pbFormat;
    }

    // allocate the new format buffer

    BYTE *pNewFormat = (PBYTE)CoTaskMemAlloc(length);
    if (pNewFormat == NULL) {
        if (length <= cbFormat) return pbFormat; //reuse the old block anyway.
        return NULL;
    }

    // copy any previous format (or part of if new is smaller)
    // delete the old format and replace with the new one

    if (cbFormat != 0) {
        ASSERT(pbFormat);
        memcpy(pNewFormat,pbFormat,min(length,cbFormat));
        CoTaskMemFree((PVOID)pbFormat);
    }

    cbFormat = length;
    pbFormat = pNewFormat;
    return pNewFormat;
}

// initialise a media type structure

void CMediaType::InitMediaType()
{
    ZeroMemory((PVOID)this, sizeof(*this));
    lSampleSize = 1;
    bFixedSizeSamples = TRUE;
}

// a partially specified media type can be passed to IPin::Connect
// as a constraint on the media type used in the connection.
// the type, subtype or format type can be null.
BOOL
CMediaType::IsPartiallySpecified(void) const
{
    if ((majortype == GUID_NULL) ||
        (formattype == GUID_NULL)) {
            return TRUE;
    } else {
        return FALSE;
    }
}

BOOL
CMediaType::MatchesPartial(const CMediaType* ppartial) const
{
    if ((ppartial->majortype != GUID_NULL) &&
        (majortype != ppartial->majortype)) {
            return FALSE;
    }
    if ((ppartial->subtype != GUID_NULL) &&
        (subtype != ppartial->subtype)) {
            return FALSE;
    }

    if (ppartial->formattype != GUID_NULL) {
        // if the format block is specified then it must match exactly
        if (formattype != ppartial->formattype) {
            return FALSE;
        }
        if (cbFormat != ppartial->cbFormat) {
            return FALSE;
        }
        if ((cbFormat != 0) &&
            (memcmp(pbFormat, ppartial->pbFormat, cbFormat) != 0)) {
                return FALSE;
        }
    }

    return TRUE;

}

// general purpose function to delete a heap allocated AM_MEDIA_TYPE structure
// which is useful when calling IEnumMediaTypes::Next as the interface
// implementation allocates the structures which you must later delete
// the format block may also be a pointer to an interface to release

void WINAPI DeleteMediaType(AM_MEDIA_TYPE *pmt)
{
    // allow NULL pointers for coding simplicity

    if (pmt == NULL) {
        return;
    }

    FreeMediaType(*pmt);
    CoTaskMemFree((PVOID)pmt);
}

// this also comes in useful when using the IEnumMediaTypes interface so
// that you can copy a media type, you can do nearly the same by creating
// a CMediaType object but as soon as it goes out of scope the destructor
// will delete the memory it allocated (this takes a copy of the memory)

AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc)
{
    ASSERT(pSrc);

    // Allocate a block of memory for the media type

    AM_MEDIA_TYPE *pMediaType =
        (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));

    if (pMediaType == NULL) {
        return NULL;
    }
    // Copy the variable length format block

    CopyMediaType(pMediaType,pSrc);

    return pMediaType;
}

//  Copy 1 media type to another

void WINAPI CopyMediaType(AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource)
{
    *pmtTarget = *pmtSource;
    if (pmtSource->cbFormat != 0) {
        ASSERT(pmtSource->pbFormat != NULL);
        pmtTarget->pbFormat = (PBYTE)CoTaskMemAlloc(pmtSource->cbFormat);
        if (pmtTarget->pbFormat == NULL) {
            pmtTarget->cbFormat = 0;
        } else {
            CopyMemory((PVOID)pmtTarget->pbFormat, (PVOID)pmtSource->pbFormat,
                       pmtTarget->cbFormat);
        }
    }
    if (pmtTarget->pUnk != NULL) {
        pmtTarget->pUnk->AddRef();
    }
}

//  Free an existing media type (ie free resources it holds)

void WINAPI FreeMediaType(AM_MEDIA_TYPE& mt)
{
    if (mt.cbFormat != 0) {
        CoTaskMemFree((PVOID)mt.pbFormat);

        // Strictly unnecessary but tidier
        mt.cbFormat = 0;
        mt.pbFormat = NULL;
    }
    if (mt.pUnk != NULL) {
        mt.pUnk->Release();
        mt.pUnk = NULL;
    }
}

//  Initialize a media type from a WAVEFORMATEX

STDAPI CreateAudioMediaType(
    const WAVEFORMATEX *pwfx,
    AM_MEDIA_TYPE *pmt,
    BOOL bSetFormat
)
{
    pmt->majortype            = MEDIATYPE_Audio;
    pmt->subtype              = FOURCCMap(pwfx->wFormatTag);
    pmt->formattype           = FORMAT_WaveFormatEx;
    pmt->bFixedSizeSamples    = TRUE;
    pmt->bTemporalCompression = FALSE;
    pmt->lSampleSize          = pwfx->nBlockAlign;
    pmt->pUnk                 = NULL;
    if (bSetFormat) {
        if (pwfx->wFormatTag == WAVE_FORMAT_PCM) {
            pmt->cbFormat         = sizeof(WAVEFORMATEX);
        } else {
            pmt->cbFormat         = sizeof(WAVEFORMATEX) + pwfx->cbSize;
        }
        pmt->pbFormat             = (PBYTE)CoTaskMemAlloc(pmt->cbFormat);
        if (pmt->pbFormat == NULL) {
            return E_OUTOFMEMORY;
        }
        if (pwfx->wFormatTag == WAVE_FORMAT_PCM) {
            CopyMemory(pmt->pbFormat, pwfx, sizeof(PCMWAVEFORMAT));
            ((WAVEFORMATEX *)pmt->pbFormat)->cbSize = 0;
        } else {
            CopyMemory(pmt->pbFormat, pwfx, pmt->cbFormat);
        }
    }
    return S_OK;
}

// eliminate very many spurious warnings from MS compiler
#pragma warning(disable:4514)


MTYPE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Class that holds and manages media type information, December 1994

#ifndef __MTYPE__
#define __MTYPE__

/* Helper class that derived pin objects can use to compare media
   types etc. Has same data members as the struct AM_MEDIA_TYPE defined
   in the streams IDL file, but also has (non-virtual) functions */

class CMediaType : public _AMMediaType {

public:

    ~CMediaType();
    CMediaType();
    CMediaType(const GUID * majortype);
    CMediaType(const AM_MEDIA_TYPE&);
    CMediaType(const CMediaType&);

    CMediaType& operator=(const CMediaType&);
    CMediaType& operator=(const AM_MEDIA_TYPE&);

    BOOL operator == (const CMediaType&) const;
    BOOL operator != (const CMediaType&) const;

    BOOL IsValid() const;

    const GUID *Type() const { return &majortype;} ;
    void SetType(const GUID *);
    const GUID *Subtype() const { return &subtype;} ;
    void SetSubtype(const GUID *);

    BOOL IsFixedSize() const {return bFixedSizeSamples; };
    BOOL IsTemporalCompressed() const {return bTemporalCompression; };
    ULONG GetSampleSize() const;

    void SetSampleSize(ULONG sz);
    void SetVariableSize();
    void SetTemporalCompression(BOOL bCompressed);

    // read/write pointer to format - can't change length without
    // calling SetFormat, AllocFormatBuffer or ReallocFormatBuffer

    BYTE*   Format() const {return pbFormat; };
    ULONG   FormatLength() const { return cbFormat; };

    void SetFormatType(const GUID *);
    const GUID *FormatType() const {return &formattype; };
    BOOL SetFormat(BYTE *pFormat, ULONG length);
    void ResetFormatBuffer();
    BYTE* AllocFormatBuffer(ULONG length);
    BYTE* ReallocFormatBuffer(ULONG length);

    void InitMediaType();

    BOOL MatchesPartial(const CMediaType* ppartial) const;
    BOOL IsPartiallySpecified(void) const;
};

/* General purpose functions to copy and delete a task allocated AM_MEDIA_TYPE
   structure which is useful when using the IEnumMediaFormats interface as
   the implementation allocates the structures which you must later delete */

void WINAPI DeleteMediaType(AM_MEDIA_TYPE *pmt);
AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc);
void WINAPI CopyMediaType(AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource);
void WINAPI FreeMediaType(AM_MEDIA_TYPE& mt);

//  Initialize a media type from a WAVEFORMATEX

STDAPI CreateAudioMediaType(
    const WAVEFORMATEX *pwfx,
    AM_MEDIA_TYPE *pmt,
    BOOL bSetFormat);

#endif /* __MTYPE__ */


OUTPUTQ.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*
    outputq.cpp

    COutputQueue class

    This is a class used by an output pin which may sometimes want
    to queue output samples on a separate thread and sometimes
    call Receive() directly on the input pin
*/

#include <streams.h>

//
//  COutputQueue Constructor :
//
//  Determines if a thread is to be created and creates resources
//
//     pInputPin  - the downstream input pin we're queueing samples to
//
//     phr        - changed to a failure code if this function fails
//                  (otherwise unchanges)
//
//     bAuto      - Ask pInputPin if it can block in Receive by calling
//                  its ReceiveCanBlock method and create a thread if
//                  it can block, otherwise not.
//
//     bQueue     - if bAuto == FALSE then we create a thread if and only
//                  if bQueue == TRUE
//
//     lBatchSize - work in batches of lBatchSize
//
//     bBatchEact - Use exact batch sizes so don't send until the
//                  batch is full or SendAnyway() is called
//
//     lListSize  - If we create a thread make the list of samples queued
//                  to the thread have this size cache
//
//     dwPriority - If we create a thread set its priority to this
//
COutputQueue::COutputQueue(
             IPin         *pInputPin,          //  Pin to send stuff to
             HRESULT      *phr,                //  'Return code'
             BOOL          bAuto,              //  Ask pin if queue or not
             BOOL          bQueue,             //  Send through queue
             LONG          lBatchSize,         //  Batch
             BOOL          bBatchExact,        //  Batch exactly to BatchSize
             LONG          lListSize,
             DWORD         dwPriority
            ) : m_lBatchSize(lBatchSize),
                m_bBatchExact(bBatchExact && (lBatchSize > 1)),
                m_hThread(NULL),
                m_hSem(NULL),
                m_List(NULL),
                m_pPin(pInputPin),
                m_ppSamples(NULL),
                m_lWaiting(0),
                m_pInputPin(NULL),
                m_bSendAnyway(FALSE),
                m_nBatched(0),
                m_bFlushing(FALSE),
                m_bFlushed(TRUE),
                m_bTerminate(FALSE),
                m_hr(S_OK)
{
    ASSERT(m_lBatchSize > 0);

    if (FAILED(*phr)) {
        return;
    }

    //  Check the input pin is OK and cache its IMemInputPin interface

    *phr = pInputPin->QueryInterface(IID_IMemInputPin, (void **)&m_pInputPin);
    if (FAILED(*phr)) {
        return;
    }

    // See if we should ask the downstream pin

    if (bAuto) {
        HRESULT hr = m_pInputPin->ReceiveCanBlock();
        if (SUCCEEDED(hr)) {
            bQueue = hr == S_OK;
        }
    }

    //  Create our sample batch

    m_ppSamples = new PMEDIASAMPLE[m_lBatchSize];
    if (m_ppSamples == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    //  If we're queueing allocate resources

    if (bQueue) {
        DbgLog((LOG_TRACE, 2, TEXT("Creating thread for output pin")));
        m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
        if (m_hSem == NULL) {
            DWORD dwError = GetLastError();
            *phr = HRESULT_FROM_WIN32(dwError);
            return;
        }
        m_List = new CSampleList(NAME("Sample Queue List"),
                                 lListSize,
                                 FALSE         // No lock
                                );
        if (m_List == NULL) {
            *phr = E_OUTOFMEMORY;
            return;
        }

        DWORD dwThreadId;
        m_hThread = CreateThread(NULL,
                                 0,
                                 InitialThreadProc,
                                 (LPVOID)this,
                                 0,
                                 &dwThreadId);
        if (m_hThread == NULL) {
            DWORD dwError = GetLastError();
            *phr = HRESULT_FROM_WIN32(dwError);
            return;
        }
        SetThreadPriority(m_hThread, dwPriority);
    } else {
        DbgLog((LOG_TRACE, 2, TEXT("Calling input pin directly - no thread")));
    }
}

//
//  COutputQueuee Destructor :
//
//  Free all resources -
//
//      Thread,
//      Batched samples
//
COutputQueue::~COutputQueue()
{
    DbgLog((LOG_TRACE, 3, TEXT("COutputQueue::~COutputQueue")));
    /*  Free our pointer */
    if (m_pInputPin != NULL) {
        m_pInputPin->Release();
    }
    if (m_hThread != NULL) {
        {
            CAutoLock lck(this);
            m_bTerminate = TRUE;
            m_hr = S_FALSE;
            NotifyThread();
        }
        DbgWaitForSingleObject(m_hThread);
        EXECUTE_ASSERT(CloseHandle(m_hThread));

        //  The thread frees the samples when asked to terminate

        ASSERT(m_List->GetCount() == 0);
        delete m_List;
    } else {
        FreeSamples();
    }
    if (m_hSem != NULL) {
        EXECUTE_ASSERT(CloseHandle(m_hSem));
    }
    delete [] m_ppSamples;
}

//
//  Call the real thread proc as a member function
//
DWORD WINAPI COutputQueue::InitialThreadProc(LPVOID pv)
{
    COutputQueue *pSampleQueue = (COutputQueue *)pv;
    CoInitialize(NULL);
    DWORD dwReturn = pSampleQueue->ThreadProc();
    CoUninitialize();
    return dwReturn;
}

//
//  Thread sending the samples downstream :
//
//  When there is nothing to do the thread sets m_lWaiting (while
//  holding the critical section) and then waits for m_hSem to be
//  set (not holding the critical section)
//
DWORD COutputQueue::ThreadProc()
{
    while (TRUE) {
        BOOL          bWait = FALSE;
        IMediaSample *pSample;
        LONG          lNumberToSend; // Local copy
        NewSegmentPacket* ppacket;

        //
        //  Get a batch of samples and send it if possible
        //  In any case exit the loop if there is a control action
        //  requested
        //
        {
            CAutoLock lck(this);
            while (TRUE) {

                if (m_bTerminate) {
                    FreeSamples();
                    return 0;
                }
                if (m_bFlushing) {
                    FreeSamples();
                    SetEvent(m_evFlushComplete);
                }

                //  Get a sample off the list

                pSample = m_List->RemoveHead();

                if (pSample != NULL &&
                    !IsSpecialSample(pSample)) {

                    //  If its just a regular sample just add it to the batch
                    //  and exit the loop if the batch is full

                    m_ppSamples[m_nBatched++] = pSample;
                    if (m_nBatched == m_lBatchSize) {
                        break;
                    }
                } else {

                    //  If there was nothing in the queue and there's nothing
                    //  to send (either because there's nothing or the batch
                    //  isn't full) then prepare to wait

                    if (pSample == NULL &&
                        (m_bBatchExact || m_nBatched == 0)) {

                        //  Tell other thread to set the event when there's
                        //  something do to

                        ASSERT(m_lWaiting == 0);
                        m_lWaiting++;
                        bWait      = TRUE;
                    } else {

                        //  We break out of the loop on SEND_PACKET unless
                        //  there's nothing to send

                        if (pSample == SEND_PACKET && m_nBatched == 0) {
                            continue;
                        }

                        if (pSample == NEW_SEGMENT) {
                            // now we need the parameters - we are
                            // guaranteed that the next packet contains them
                            ppacket = (NewSegmentPacket *) m_List->RemoveHead();
                            ASSERT(ppacket);
                        }
                        //  EOS_PACKET falls through here and we exit the loop
                        //  In this way it acts like SEND_PACKET
                    }
                    break;
                }
            }
            if (!bWait) {
                // We look at m_nBatched from the client side so keep
                // it up to date inside the critical section
                lNumberToSend = m_nBatched;  // Local copy
                m_nBatched = 0;
            }
        }

        //  Wait for some more data

        if (bWait) {
            DbgWaitForSingleObject(m_hSem);
            continue;
        }

        //  OK - send it if there's anything to send
        //  We DON'T check m_bBatchExact here because either we've got
        //  a full batch or we dropped through because we got
        //  SEND_PACKET or EOS_PACKET - both of which imply we should
        //  flush our batch

        if (lNumberToSend != 0) {
            long nProcessed;
            if (m_hr == S_OK) {
                ASSERT(!m_bFlushed);
                HRESULT hr = m_pInputPin->ReceiveMultiple(m_ppSamples,
                                                          lNumberToSend,
                                                          &nProcessed);
                /*  Don't overwrite a flushing state HRESULT */
                CAutoLock lck(this);
                if (m_hr == S_OK) {
                    m_hr = hr;
                }
                ASSERT(!m_bFlushed);
            }
            while (lNumberToSend != 0) {
                m_ppSamples[--lNumberToSend]->Release();
            }
            if (m_hr != S_OK) {

                //  In any case wait for more data - S_OK just
                //  means there wasn't an error

                DbgLog((LOG_ERROR, 2, TEXT("ReceiveMultiple returned %8.8X"),
                       m_hr));
            }
        }

        //  Check for end of stream

        if (pSample == EOS_PACKET) {

            //  We don't send even end of stream on if we've previously
            //  returned something other than S_OK
            //  This is because in that case the pin which returned
            //  something other than S_OK should have either sent
            //  EndOfStream() or notified the filter graph

            if (m_hr == S_OK) {
                DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()")));
                HRESULT hr = m_pPin->EndOfStream();
                if (FAILED(hr)) {
                    DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()")));
                }
            }
        }

        //  Data from a new source

        if (pSample == RESET_PACKET) {
            m_hr = S_OK;
            SetEvent(m_evFlushComplete);
        }

        if (pSample == NEW_SEGMENT) {
            m_pPin->NewSegment(ppacket->tStart, ppacket->tStop, ppacket->dRate);
            delete ppacket;
        }
    }
}

//  Send batched stuff anyway
void COutputQueue::SendAnyway()
{
    if (!IsQueued()) {

        //  m_bSendAnyway is a secret hack parameter to ReceiveMultiple

        m_bSendAnyway = TRUE;
        LONG nProcessed;
        ReceiveMultiple(NULL, 0, &nProcessed);
        m_bSendAnyway = FALSE;

    } else {
        CAutoLock lck(this);
        QueueSample(SEND_PACKET);
        NotifyThread();
    }
}

void
COutputQueue::NewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate)
{
    if (!IsQueued()) {
        if (S_OK == m_hr) {
            if (m_bBatchExact) {
                SendAnyway();
            }
            m_pPin->NewSegment(tStart, tStop, dRate);
        }
    } else {
        if (m_hr == S_OK) {
            //
            // we need to queue the new segment to appear in order in the
            // data, but we need to pass parameters to it. Rather than
            // take the hit of wrapping every single sample so we can tell
            // special ones apart, we queue special pointers to indicate
            // special packets, and we guarantee (by holding the
            // critical section) that the packet immediately following a
            // NEW_SEGMENT value is a NewSegmentPacket containing the
            // parameters.
            NewSegmentPacket * ppack = new NewSegmentPacket;
            if (ppack == NULL) {
                return;
            }
            ppack->tStart = tStart;
            ppack->tStop = tStop;
            ppack->dRate = dRate;

            CAutoLock lck(this);
            QueueSample(NEW_SEGMENT);
            QueueSample( (IMediaSample*) ppack);
            NotifyThread();
        }
    }
}

//
//  End of Stream is queued to output device
//
void COutputQueue::EOS()
{
    CAutoLock lck(this);
    if (!IsQueued()) {
        if (m_bBatchExact) {
            SendAnyway();
        }
        if (m_hr == S_OK) {
            DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()")));
            m_bFlushed = FALSE;
            HRESULT hr = m_pPin->EndOfStream();
            if (FAILED(hr)) {
                DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()")));
            }
        }
    } else {
        if (m_hr == S_OK) {
            m_bFlushed = FALSE;
            QueueSample(EOS_PACKET);
            NotifyThread();
        }
    }
}

//
//  Flush all the samples in the queue
//
void COutputQueue::BeginFlush()
{
    if (IsQueued()) {
        {
            CAutoLock lck(this);

            // block receives -- we assume this is done by the
            // filter in which we are a component

            // discard all queued data

            m_bFlushing = TRUE;

            //  Make sure we discard all samples from now on

            if (m_hr == S_OK) {
                m_hr = S_FALSE;
            }

            // Optimize so we don't keep calling downstream all the time

            if (m_bFlushed) {
                return;
            }

            // Make sure we really wait for the flush to complete
            m_evFlushComplete.Reset();

            NotifyThread();
        }

        // pass this downstream

        m_pPin->BeginFlush();
    } else {
        // pass downstream first to avoid deadlocks
        m_pPin->BeginFlush();
        CAutoLock lck(this);
        // discard all queued data

        m_bFlushing = TRUE;

        //  Make sure we discard all samples from now on

        if (m_hr == S_OK) {
            m_hr = S_FALSE;
        }
    }

}

//
// leave flush mode - pass this downstream
void COutputQueue::EndFlush()
{
    {
        CAutoLock lck(this);
        ASSERT(m_bFlushing);
        if (m_bFlushed && IsQueued()) {
            m_bFlushing = FALSE;
            m_hr = S_OK;
            return;
        }
    }

    // sync with pushing thread -- done in BeginFlush
    // ensure no more data to go downstream -- done in BeginFlush
    //
    // Because we are synching here there is no need to hold the critical
    // section (in fact we'd deadlock if we did!)

    if (IsQueued()) {
        m_evFlushComplete.Wait();
    } else {
        FreeSamples();
    }

    //  Be daring - the caller has guaranteed no samples will arrive
    //  before EndFlush() returns

    m_bFlushing = FALSE;
    m_bFlushed  = TRUE;

    // call EndFlush on downstream pins

    m_pPin->EndFlush();

    m_hr = S_OK;
}

//  COutputQueue::QueueSample
//
//  private method to Send a sample to the output queue
//  The critical section MUST be held when this is called

void COutputQueue::QueueSample(IMediaSample *pSample)
{
    if (NULL == m_List->AddTail(pSample)) {
        if (!IsSpecialSample(pSample)) {
            pSample->Release();
        }
    }
}

//
//  COutputQueue::Receive()
//
//  Send a single sample by the multiple sample route
//  (NOTE - this could be optimized if necessary)
//
//  On return the sample will have been Release()'d
//

HRESULT COutputQueue::Receive(IMediaSample *pSample)
{
    LONG nProcessed;
    return ReceiveMultiple(&pSample, 1, &nProcessed);
}

//
//  COutputQueue::ReceiveMultiple()
//
//  Send a set of samples to the downstream pin
//
//      ppSamples           - array of samples
//      nSamples            - how many
//      nSamplesProcessed   - How many were processed
//
//  On return all samples will have been Release()'d
//

HRESULT COutputQueue::ReceiveMultiple (
    IMediaSample **ppSamples,
    long nSamples,
    long *nSamplesProcessed)
{
    CAutoLock lck(this);
    //  Either call directly or queue up the samples

    if (!IsQueued()) {

        //  If we already had a bad return code then just return

        if (S_OK != m_hr) {

            //  If we've never received anything since the last Flush()
            //  and the sticky return code is not S_OK we must be
            //  flushing
            //  ((!A || B) is equivalent to A implies B)
            ASSERT(!m_bFlushed || m_bFlushing);

            //  We're supposed to Release() them anyway!
            *nSamplesProcessed = 0;
            for (int i = 0; i < nSamples; i++) {
                DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (direct) : Discarding %d samples code 0x%8.8X"),
                        nSamples, m_hr));
                ppSamples[i]->Release();
            }

            return m_hr;
        }
        //
        //  If we're flushing the sticky return code should be S_FALSE
        //
        ASSERT(!m_bFlushing);
        m_bFlushed = FALSE;

        ASSERT(m_nBatched < m_lBatchSize);
        ASSERT(m_nBatched == 0 || m_bBatchExact);

        //  Loop processing the samples in batches

        LONG iLost = 0;
        for (long iDone = 0;
             iDone < nSamples || (m_nBatched != 0 && m_bSendAnyway);
            ) {

//pragma message (REMIND("Implement threshold scheme"))
            ASSERT(m_nBatched < m_lBatchSize);
            if (iDone < nSamples) {
                m_ppSamples[m_nBatched++] = ppSamples[iDone++];
            }
            if (m_nBatched == m_lBatchSize ||
                nSamples == 0 && (m_bSendAnyway || !m_bBatchExact)) {
                LONG nDone;
                DbgLog((LOG_TRACE, 4, TEXT("Batching %d samples"),
                       m_nBatched));

                if (m_hr == S_OK) {
                    m_hr = m_pInputPin->ReceiveMultiple(m_ppSamples,
                                                        m_nBatched,
                                                        &nDone);
                } else {
                    nDone = 0;
                }
                iLost += m_nBatched - nDone;
                for (LONG i = 0; i < m_nBatched; i++) {
                    m_ppSamples[i]->Release();
                }
                m_nBatched = 0;
            }
        }
        *nSamplesProcessed = iDone - iLost;
        if (*nSamplesProcessed < 0) {
            *nSamplesProcessed = 0;
        }
        return m_hr;
    } else {
        /*  We're sending to our thread */

        if (m_hr != S_OK) {
            *nSamplesProcessed = 0;
            DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (queued) : Discarding %d samples code 0x%8.8X"),
                    nSamples, m_hr));
            for (int i = 0; i < nSamples; i++) {
                ppSamples[i]->Release();
            }
            return m_hr;
        }
        m_bFlushed = FALSE;
        for (long i = 0; i < nSamples; i++) {
            QueueSample(ppSamples[i]);
        }
        *nSamplesProcessed = nSamples;
        if (!m_bBatchExact ||
            m_nBatched + m_List->GetCount() >= m_lBatchSize) {
            NotifyThread();
        }
        return S_OK;
    }
}

//  Get ready for new data - cancels sticky m_hr
void COutputQueue::Reset()
{
    if (!IsQueued()) {
        m_hr = S_OK;
    } else {
        CAutoLock lck(this);
        QueueSample(RESET_PACKET);
        NotifyThread();
        m_evFlushComplete.Wait();
    }
}

//  Remove and Release() all queued and Batched samples
void COutputQueue::FreeSamples()
{
    CAutoLock lck(this);
    if (IsQueued()) {
        while (TRUE) {
            IMediaSample *pSample = m_List->RemoveHead();
            if (pSample == NULL) {
                break;
            }
            if (!IsSpecialSample(pSample)) {
                pSample->Release();
            } else {
                if (pSample == NEW_SEGMENT) {
                    //  Free NEW_SEGMENT packet
                    NewSegmentPacket *ppacket =
                        (NewSegmentPacket *) m_List->RemoveHead();
                    ASSERT(ppacket != NULL);
                    delete ppacket;
                }
            }
        }
    }
    for (int i = 0; i < m_nBatched; i++) {
        m_ppSamples[i]->Release();
    }
    m_nBatched = 0;
}

//  Notify the thread if there is something to do
//
//  The critical section MUST be held when this is called
void COutputQueue::NotifyThread()
{
    //  Optimize - no need to signal if it's not waiting
    ASSERT(IsQueued());
    if (m_lWaiting) {
        ReleaseSemaphore(m_hSem, m_lWaiting, NULL);
        m_lWaiting = 0;
    }
}

//  See if there's any work to do
//  Returns
//      TRUE  if there is nothing on the queue and nothing in the batch
//            and all data has been sent
//      FALSE otherwise
//
BOOL COutputQueue::IsIdle()
{
    CAutoLock lck(this);

    //  We're idle if
    //      there is no thread (!IsQueued()) OR
    //      the thread is waiting for more work  (m_lWaiting != 0)
    //  AND
    //      there's nothing in the current batch (m_nBatched == 0)

    if (IsQueued() && m_lWaiting == 0 || m_nBatched != 0) {
        return FALSE;
    } else {

        //  If we're idle it shouldn't be possible for there
        //  to be anything on the work queue

        ASSERT(!IsQueued() || m_List->GetCount() == 0);
        return TRUE;
    }
}


OUTPUTQ.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//  outputq.h
//
//  Defines the COutputQueue class
//
//  Makes a queue of samples and sends them
//  to an output pin
//
//  The class will optionally send the samples to the pin directly
//
//

typedef CGenericList<IMediaSample> CSampleList;

class COutputQueue : public CCritSec
{
public:
    //  Constructor
    COutputQueue(IPin      *pInputPin,          //  Pin to send stuff to
                 HRESULT   *phr,                //  'Return code'
                 BOOL       bAuto = TRUE,       //  Ask pin if blocks
                 BOOL       bQueue = TRUE,      //  Send through queue (ignored if
                                                //  bAuto set)
                 LONG       lBatchSize = 1,     //  Batch
                 BOOL       bBatchExact = FALSE,//  Batch exactly to BatchSize
                 LONG       lListSize =         //  Likely number in the list
                                DEFAULTCACHE,
                 DWORD      dwPriority =        //  Priority of thread to create
                                THREAD_PRIORITY_NORMAL
                );
    ~COutputQueue();

    // enter flush state - discard all data
    void BeginFlush();      // Begin flushing samples

    // re-enable receives (pass this downstream)
    void EndFlush();        // Complete flush of samples - downstream
                            // pin guaranteed not to block at this stage

    void EOS();             // Call this on End of stream

    void SendAnyway();      // Send batched samples anyway (if bBatchExact set)

    void NewSegment(
            REFERENCE_TIME tStart,
            REFERENCE_TIME tStop,
            double dRate);

    HRESULT Receive(IMediaSample *pSample);

    // do something with these media samples
    HRESULT ReceiveMultiple (
        IMediaSample **pSamples,
        long nSamples,
        long *nSamplesProcessed);

    void Reset();           // Reset m_hr ready for more data

    //  See if its idle or not
    BOOL IsIdle();

protected:
    static DWORD WINAPI InitialThreadProc(LPVOID pv);
    DWORD ThreadProc();
    BOOL  IsQueued()
    {
        return m_List != NULL;
    };

    //  The critical section MUST be held when this is called
    void QueueSample(IMediaSample *pSample);

    BOOL IsSpecialSample(IMediaSample *pSample)
    {
        return (DWORD)pSample > 0xFFFFFFF0;
    };

    //  Remove and Release() batched and queued samples
    void FreeSamples();

    //  Notify the thread there is something to do
    void NotifyThread();

protected:
    //  Queue 'messages'
    #define SEND_PACKET      ((IMediaSample *)0xFFFFFFFE)  // Send batch
    #define EOS_PACKET       ((IMediaSample *)0xFFFFFFFD)  // End of stream
    #define RESET_PACKET     ((IMediaSample *)0xFFFFFFFC)  // Reset m_hr
    #define NEW_SEGMENT      ((IMediaSample *)0xFFFFFFFB)  // send NewSegment

    // new segment packet is always followed by one of these
    struct NewSegmentPacket {
        REFERENCE_TIME tStart;
        REFERENCE_TIME tStop;
        double dRate;
    };

    // Remember input stuff
    IPin          * const m_pPin;
    IMemInputPin  *       m_pInputPin;
    BOOL            const m_bBatchExact;
    LONG            const m_lBatchSize;

    CSampleList   *       m_List;
    HANDLE                m_hSem;
    CAMEvent                m_evFlushComplete;
    HANDLE                m_hThread;
    IMediaSample  **      m_ppSamples;
    LONG                  m_nBatched;

    //  Wait optimization
    LONG                  m_lWaiting;
    //  Flush synchronization
    BOOL                  m_bFlushing;
    BOOL                  m_bFlushed;

    //  Terminate now
    BOOL                  m_bTerminate;

    //  Send anyway flag for batching
    BOOL                  m_bSendAnyway;

    //  Deferred 'return code'
    BOOL volatile         m_hr;
};


PSTREAM.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>

#ifdef PERF
#include <measure.h>
#endif
// #include "pstream.h"  in streams.h

//
// Constructor
//
CPersistStream::CPersistStream(IUnknown *punk, HRESULT *phr)
    : mPS_fDirty(FALSE)
{
    mPS_dwFileVersion = GetSoftwareVersion();
}

//
// Destructor
//
CPersistStream::~CPersistStream() {
    // Nothing to do
}

#if 0
SAMPLE CODE TO COPY - not active at the moment

//
// NonDelegatingQueryInterface
//
// This object supports IPersist & IPersistStream
STDMETHODIMP CPersistStream::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_IPersist) {
        return GetInterface((IPersist *) this, ppv);      // ???
    }
    else if (riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *) this, ppv);
    }
    else {
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}
#endif

//
// WriteToStream
//
// Writes to the stream (default action is to write nothing)
HRESULT CPersistStream::WriteToStream(IStream *pStream)
{
    // You can override this to do things like
    // hr = pStream->Write(MyStructure, sizeof(MyStructure), NULL);

    return NOERROR;
}

HRESULT CPersistStream::ReadFromStream(IStream * pStream)
{
    // You can override this to do things like
    // hr = pStream->Read(MyStructure, sizeof(MyStructure), NULL);

    return NOERROR;
}

//
// Load
//
// Load all the data from the given stream
STDMETHODIMP CPersistStream::Load(LPSTREAM pStm)
{
    HRESULT hr;
    // Load the version number then the data
    mPS_dwFileVersion = ReadInt(pStm, hr);
    if (FAILED(hr)) {
        return hr;
    }

    return ReadFromStream(pStm);
}  // Load

//
// Save
//
// Save the contents of this Stream.
STDMETHODIMP CPersistStream::Save(LPSTREAM pStm, BOOL fClearDirty)
{

    HRESULT hr = WriteInt(pStm, GetSoftwareVersion());
    if (FAILED(hr)) {
        return hr;
    }

    hr = WriteToStream(pStm);
    if (FAILED(hr)) {
        return hr;
    }

    mPS_fDirty = !fClearDirty;

    return hr;
} // Save

// WriteInt
//
// Writes an integer to an IStream as 11 UNICODE characters followed by one space.
// You could use this for shorts or unsigneds or anything (up to 32 bits)
// where the value isn't actually truncated by squeezing it into 32 bits.
// Values such as (unsigned) 0x80000000 would come out as -2147483648
// but would then load as 0x80000000 through ReadInt.  Cast as you please.

STDAPI WriteInt(IStream *pIStream, int n)
{
    WCHAR Buff[13];  // Allows for trailing null that we don't write
    wsprintfW(Buff, L"%011d ",n);
    return pIStream->Write(&(Buff[0]), 12*sizeof(WCHAR), NULL);
} // WriteInt

// ReadInt
//
// Reads an integer from an IStream.
// Read as 4 bytes.  You could use this for shorts or unsigneds or anything
// where the value isn't actually truncated by squeezing it into 32 bits
// Striped down subset of what sscanf can do (without dragging in the C runtime)

STDAPI_(int) ReadInt(IStream *pIStream, HRESULT &hr)
{

    int Sign = 1;
    unsigned int n = 0;    // result wil be n*Sign
    WCHAR wch;

    hr = pIStream->Read( &wch, sizeof(wch), NULL);
    if (FAILED(hr)) {
        return 0;
    }

    if (wch==L'-'){
        Sign = -1;
        hr = pIStream->Read( &wch, sizeof(wch), NULL);
        if (FAILED(hr)) {
            return 0;
        }
    }

    for( ; ; ) {
        if (wch>=L'0' && wch<=L'9') {
            n = 10*n+(int)(wch-L'0');
        } else if (  wch == L' '
                  || wch == L'\t'
                  || wch == L'\r'
                  || wch == L'\n'
                  || wch == L'\0'
                  ) {
            break;
        } else {
            hr = VFW_E_INVALID_FILE_FORMAT;
            return 0;
        }

        hr = pIStream->Read( &wch, sizeof(wch), NULL);
        if (FAILED(hr)) {
            return 0;
        }
    }

    if (n==0x80000000 && Sign==-1) {
        // This is the negative number that has no positive version!
        return (int)n;
    }
    else return (int)n * Sign;
} // ReadInt

// The microsoft C/C++ compile generates level 4 warnings to the effect that
// a particular inline function (from some base class) was not needed.
// This line gets rid of hundreds of such unwanted messages and makes
// -W4 compilation feasible:
#pragma warning(disable: 4514)


PSTREAM.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __PSTREAM__
#define __PSTREAM__

// Base class for persistent properties of filters
// (i.e. filter properties in saved graphs)

// The simplest way to use this is:
// 1. Arrange for your filter to inherit this class
// 2. Implement in your class WriteToStream and ReadFromStream
//    These will override the "do nothing" functions here.
// 3. Change your NonDelegatingQueryInterface to handle IPersistStream
// 4. Implement SizeMax to return the number of bytes of data you save.
//    If you save UNICODE data, don't forget a char is 2 bytes.
// 5. Whenever your data changes, call SetDirty()
//
// At some point you may decide to alter, or extend the format of your data.
// At that point you will wish that you had a version number in all the old
// saved graphs, so that you can tell, when you read them, whether they
// represent the old or new form.  To assist you in this, this class
// writes and reads a version number.
// When it writes, it calls GetSoftwareVersion()  to enquire what version
// of the software we have at the moment.  (In effect this is a version number
// of the data layout in the file).  It writes this as the first thing in the data.
// If you want to change the version, implement (override) GetSoftwareVersion().
// It reads this from the file into mPS_dwFileVersion before calling ReadFromStream,
// so in ReadFromStream you can check mPS_dwFileVersion to see if you are reading
// an old version file.
// Normally you should accept files whose version is no newer than the software
// version that's reading them.

// CPersistStream
//
// Implements IPersistStream.
// See 'OLE Programmers Reference (Vol 1):Structured Storage Overview' for
// more implementation information.
class CPersistStream : public IPersistStream {
    private:

        // Internal state:

    protected:
        DWORD     mPS_dwFileVersion;         // version number of file (being read)
        BOOL      mPS_fDirty;

    public:

        // IPersistStream methods

        STDMETHODIMP IsDirty()
            {return (mPS_fDirty ? S_OK : S_FALSE);}  // note FALSE means clean
        STDMETHODIMP Load(LPSTREAM pStm);
        STDMETHODIMP Save(LPSTREAM pStm, BOOL fClearDirty);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER * pcbSize)
                         // Allow 24 bytes for version.
                         { pcbSize->QuadPart = 12*sizeof(WCHAR)+SizeMax(); return NOERROR; }

        // implementation

        CPersistStream(IUnknown *punk, HRESULT *phr);
        ~CPersistStream();

        HRESULT SetDirty(BOOL fDirty)
            { mPS_fDirty = fDirty; return NOERROR;}

        // override to reveal IPersist & IPersistStream
        // STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

        // --- IPersist ---

        // You must override this to provide your own class id
        STDMETHODIMP GetClassID(CLSID *pClsid) PURE;

        // overrideable if you want
        // file version number.  Override it if you ever change format
        virtual DWORD GetSoftwareVersion(void) { return 0; }

        //=========================================================================
        // OVERRIDE THESE to read and write your data
        // OVERRIDE THESE to read and write your data
        // OVERRIDE THESE to read and write your data

        virtual int SizeMax() {return 0;}
        virtual HRESULT WriteToStream(IStream *pStream);
        virtual HRESULT ReadFromStream(IStream *pStream);
        //=========================================================================

    private:

};

// --- Useful helpers ---

// Writes an int to an IStream as UNICODE.
STDAPI WriteInt(IStream *pIStream, int n);

// inverse of WriteInt
STDAPI_(int) ReadInt(IStream *pIStream, HRESULT &hr);

#endif // __PSTREAM__


PULLPIN.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// implementation of CPullPin class - pulls data from IAsyncReader

#include <streams.h>
#include "pullpin.h"

CPullPin::CPullPin()
  : m_pReader(NULL),
    m_pAlloc(NULL),
    m_State(TM_Exit)
{

}

CPullPin::~CPullPin()
{
    Disconnect();
}

// returns S_OK if successfully connected to an IAsyncReader interface
// from this object
// Optional allocator should be proposed as a preferred allocator if
// necessary
HRESULT
CPullPin::Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync)
{
    CAutoLock lock(&m_AccessLock);

    if (m_pReader) {
return VFW_E_ALREADY_CONNECTED;
    }

    HRESULT hr = pUnk->QueryInterface(IID_IAsyncReader, (void**)&m_pReader);
    if (FAILED(hr)) {
return(hr);
    }

    hr = DecideAllocator(pAlloc, NULL);
    if (FAILED(hr)) {
Disconnect();
return hr;
    }

    LONGLONG llTotal, llAvail;
    hr = m_pReader->Length(&llTotal, &llAvail);
    if (FAILED(hr)) {
Disconnect();
return hr;
    }

    // convert from file position to reference time
    m_tDuration = llTotal * UNITS;
    m_tStop = m_tDuration;
    m_tStart = 0;

    m_bSync = bSync;

    return S_OK;
}

// disconnect any connection made in Connect
HRESULT
CPullPin::Disconnect()
{
    CAutoLock lock(&m_AccessLock);

    StopThread();

    if (m_pReader) {
m_pReader->Release();
m_pReader = NULL;
    }

    if (m_pAlloc) {
m_pAlloc->Release();
m_pAlloc = NULL;
    }
    return S_OK;
}

// agree an allocator using RequestAllocator - optional
// props param specifies your requirements (non-zero fields).
// returns an error code if fail to match requirements.
// optional IMemAllocator interface is offered as a preferred allocator
// but no error occurs if it can't be met.
HRESULT
CPullPin::DecideAllocator(
    IMemAllocator * pAlloc,
    ALLOCATOR_PROPERTIES * pProps)
{
    ALLOCATOR_PROPERTIES *pRequest;
    ALLOCATOR_PROPERTIES Request;
    if (pProps == NULL) {
Request.cBuffers = 3;
Request.cbBuffer = 64*1024;
Request.cbAlign = 0;
Request.cbPrefix = 0;
pRequest = &Request;
    } else {
pRequest = pProps;
    }
    HRESULT hr = m_pReader->RequestAllocator(
    pAlloc,
    pRequest,
    &m_pAlloc);
    return hr;
}

// start pulling data
HRESULT
CPullPin::Active(void)
{
    ASSERT(!ThreadExists());
    return StartThread();
}

// stop pulling data
HRESULT
CPullPin::Inactive(void)
{
    StopThread();

    return S_OK;
}

HRESULT
CPullPin::Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop)
{
    CAutoLock lock(&m_AccessLock);

    ThreadMsg AtStart = m_State;

    if (AtStart == TM_Start) {
BeginFlush();
PauseThread();
EndFlush();
    }

    m_tStart = tStart;
    m_tStop = tStop;

    HRESULT hr = S_OK;
    if (AtStart == TM_Start) {
hr = StartThread();
    }

    return hr;
}

HRESULT
CPullPin::Duration(REFERENCE_TIME* ptDuration)
{
    *ptDuration = m_tDuration;
    return S_OK;
}

HRESULT
CPullPin::StartThread()
{
    CAutoLock lock(&m_AccessLock);

    if (!m_pAlloc || !m_pReader) {
return E_UNEXPECTED;
    }

    HRESULT hr;
    if (!ThreadExists()) {

// commit allocator
hr = m_pAlloc->Commit();
if (FAILED(hr)) {
    return hr;
}

// start thread
if (!Create()) {
    return E_FAIL;
}
    }

    m_State = TM_Start;
    hr = (HRESULT) CallWorker(m_State);
    return hr;
}

HRESULT
CPullPin::PauseThread()
{
    CAutoLock lock(&m_AccessLock);

    if (!ThreadExists()) {
return E_UNEXPECTED;
    }

    // need to flush to ensure the thread is not blocked
    // in WaitForNext
    HRESULT hr = m_pReader->BeginFlush();
    if (FAILED(hr)) {
return hr;
    }

    m_State = TM_Pause;
    hr = CallWorker(TM_Pause);

    m_pReader->EndFlush();
    return hr;
}

HRESULT
CPullPin::StopThread()
{
    CAutoLock lock(&m_AccessLock);

    if (!ThreadExists()) {
return S_FALSE;
    }

    // need to flush to ensure the thread is not blocked
    // in WaitForNext
    HRESULT hr = m_pReader->BeginFlush();
    if (FAILED(hr)) {
return hr;
    }

    m_State = TM_Exit;
    hr = CallWorker(TM_Exit);

    m_pReader->EndFlush();

    // wait for thread to completely exit
    Close();

    // decommit allocator
    if (m_pAlloc) {
m_pAlloc->Decommit();
    }

    return S_OK;
}

DWORD
CPullPin::ThreadProc(void)
{
    while(1) {
DWORD cmd = GetRequest();
switch(cmd) {
case TM_Exit:
    Reply(S_OK);
    return 0;

case TM_Pause:
    // we are paused already
    Reply(S_OK);
    break;

case TM_Start:
    Reply(S_OK);
    Process();
    break;
}

// at this point, there should be no outstanding requests on the
// upstream filter.
// We should force begin/endflush to ensure that this is true.
// !!!Note that we may currently be inside a BeginFlush/EndFlush pair
// on another thread, but the premature EndFlush will do no harm now
// that we are idle.
m_pReader->BeginFlush();
CleanupCancelled();
m_pReader->EndFlush();
    }
}

HRESULT
CPullPin::QueueSample(
    REFERENCE_TIME& tCurrent,
    REFERENCE_TIME tAlignStop,
    BOOL bDiscontinuity
    )
{
    IMediaSample* pSample;

    HRESULT hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);
    if (FAILED(hr)) {
return hr;
    }

    LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS);
    if (tStopThis > tAlignStop) {
tStopThis = tAlignStop;
    }
    pSample->SetTime(&tCurrent, &tStopThis);
    tCurrent = tStopThis;

    pSample->SetDiscontinuity(bDiscontinuity);

    hr = m_pReader->Request(
pSample,
0);
    if (FAILED(hr)) {
pSample->Release();

CleanupCancelled();
OnError(hr);
    }
    return hr;
}

HRESULT
CPullPin::CollectAndDeliver(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop)
{
    IMediaSample* pSample = NULL;   // better be sure pSample is set
    DWORD dwUnused;
    HRESULT hr = m_pReader->WaitForNext(
INFINITE,
&pSample,
&dwUnused);
    if (FAILED(hr)) {
if (pSample) {
    pSample->Release();
}
    } else {
hr = DeliverSample(pSample, tStart, tStop);
    }
    if (FAILED(hr)) {
CleanupCancelled();
OnError(hr);
    }
    return hr;

}

HRESULT
CPullPin::DeliverSample(
    IMediaSample* pSample,
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop
    )
{
    // fix up sample if past actual stop (for sector alignment)
    REFERENCE_TIME t1, t2;
    pSample->GetTime(&t1, &t2);
    if (t2 > tStop) {
t2 = tStop;
    }

    // adjust times to be relative to (aligned) start time
    t1 -= tStart;
    t2 -= tStart;
    pSample->SetTime(&t1, &t2);

    HRESULT hr = Receive(pSample);
    pSample->Release();
    return hr;
}

void
CPullPin::Process(void)
{
    // is there anything to do?
    if (m_tStop <= m_tStart) {
EndOfStream();
return;
    }

    BOOL bDiscontinuity = TRUE;

    // if there is more than one sample at the allocator,
    // then try to queue 2 at once in order to overlap.
    // -- get buffer count and required alignment
    ALLOCATOR_PROPERTIES Actual;
    HRESULT hr = m_pAlloc->GetProperties(&Actual);

    // align the start position downwards
    REFERENCE_TIME tStart = AlignDown(m_tStart / UNITS, Actual.cbAlign) * UNITS;
    REFERENCE_TIME tCurrent = tStart;

    REFERENCE_TIME tStop = m_tStop;
    if (tStop > m_tDuration) {
tStop = m_tDuration;
    }

    // align the stop position - may be past stop, but that
    // doesn't matter
    REFERENCE_TIME tAlignStop = AlignUp(tStop / UNITS, Actual.cbAlign) * UNITS;

    DWORD dwRequest;

    if (!m_bSync) {

//  Break out of the loop either if we get to the end or we're asked
//  to do something else
while (tCurrent < tAlignStop) {

    // Break out without calling EndOfStream if we're asked to
    // do something different
    if (CheckRequest(&dwRequest)) {
return;
    }

    // queue a first sample
    if (Actual.cBuffers > 1) {

hr = QueueSample(tCurrent, tAlignStop, TRUE);
bDiscontinuity = FALSE;

if (FAILED(hr)) {
    return;
}
    }

    // loop queueing second and waiting for first..
    while (tCurrent < tAlignStop) {

hr = QueueSample(tCurrent, tAlignStop, bDiscontinuity);
bDiscontinuity = FALSE;

if (FAILED(hr)) {
    return;
}

hr = CollectAndDeliver(tStart, tStop);
if (S_OK != hr) {

    // stop if error, or if downstream filter said
    // to stop.
    return;
}
    }

    if (Actual.cBuffers > 1) {
hr = CollectAndDeliver(tStart, tStop);
if (FAILED(hr)) {
    return;
}
    }
}
    } else {

// sync version of above loop
while (tCurrent < tAlignStop) {

    // Break out without calling EndOfStream if we're asked to
    // do something different
    if (CheckRequest(&dwRequest)) {
return;
    }

    IMediaSample* pSample;

    hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);
    if (FAILED(hr)) {
OnError(hr);
return;
    }

    LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS);
    if (tStopThis > tAlignStop) {
tStopThis = tAlignStop;
    }
    pSample->SetTime(&tCurrent, &tStopThis);
    tCurrent = tStopThis;

    if (bDiscontinuity) {
pSample->SetDiscontinuity(TRUE);
bDiscontinuity = FALSE;
    }

    hr = m_pReader->SyncReadAligned(pSample);

    if (FAILED(hr)) {
pSample->Release();
OnError(hr);
return;
    }

    hr = DeliverSample(pSample, tStart, tStop);
    if (hr != S_OK) {
if (FAILED(hr)) {
    OnError(hr);
}
return;
    }
}
    }

    EndOfStream();
}

// after a flush, cancelled i/o will be waiting for collection
// and release
void
CPullPin::CleanupCancelled(void)
{
    while (1) {
IMediaSample * pSample;
DWORD dwUnused;

HRESULT hr = m_pReader->WaitForNext(
    0,          // no wait
    &pSample,
    &dwUnused);
if(pSample) {
    pSample->Release();
} else {
    // no more samples
    return;
}
    }
}


PULLPIN.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __PULLPIN_H__
#define __PULLPIN_H__

//
// CPullPin
//
// object supporting pulling data from an IAsyncReader interface.
// Given a start/stop position, calls a pure Receive method with each
// IMediaSample received.
//
// This is essentially for use in a MemInputPin when it finds itself
// connected to an IAsyncReader pin instead of a pushing pin.
//

class CPullPin : public CAMThread
{
    IAsyncReader*       m_pReader;
    REFERENCE_TIME      m_tStart;
    REFERENCE_TIME      m_tStop;
    REFERENCE_TIME      m_tDuration;
    BOOL                m_bSync;

    enum ThreadMsg {
TM_Pause,       // stop pulling and wait for next message
TM_Start,       // start pulling
TM_Exit,        // stop and exit
    };

    ThreadMsg m_State;

    // override pure thread proc from CAMThread
    DWORD ThreadProc(void);

    // running pull method (check m_bSync)
    void Process(void);

    // clean up any cancelled i/o after a flush
    void CleanupCancelled(void);

    // suspend thread from pulling, eg during seek
    HRESULT PauseThread();

    // start thread pulling - create thread if necy
    HRESULT StartThread();

    // stop and close thread
    HRESULT StopThread();

    // called from ProcessAsync to queue and collect requests
    HRESULT QueueSample(
REFERENCE_TIME& tCurrent,
REFERENCE_TIME tAlignStop,
BOOL bDiscontinuity);

    HRESULT CollectAndDeliver(
REFERENCE_TIME tStart,
REFERENCE_TIME tStop);

    HRESULT DeliverSample(
IMediaSample* pSample,
REFERENCE_TIME tStart,
REFERENCE_TIME tStop);

protected:
    IMemAllocator *     m_pAlloc;

public:
    CPullPin();
    virtual ~CPullPin();

    // returns S_OK if successfully connected to an IAsyncReader interface
    // from this object
    // Optional allocator should be proposed as a preferred allocator if
    // necessary
    // bSync is TRUE if we are to use sync reads instead of the
    // async methods.
    HRESULT Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync);

    // disconnect any connection made in Connect
    HRESULT Disconnect();

    // agree an allocator using RequestAllocator - optional
    // props param specifies your requirements (non-zero fields).
    // returns an error code if fail to match requirements.
    // optional IMemAllocator interface is offered as a preferred allocator
    // but no error occurs if it can't be met.
    virtual HRESULT DecideAllocator(
IMemAllocator* pAlloc,
ALLOCATOR_PROPERTIES * pProps);

    // set start and stop position. if active, will start immediately at
    // the new position. Default is 0 to duration
    HRESULT Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop);

    // return the total duration
    HRESULT Duration(REFERENCE_TIME* ptDuration);

    // start pulling data
    HRESULT Active(void);

    // stop pulling data
    HRESULT Inactive(void);

    // helper functions
    LONGLONG AlignDown(LONGLONG ll, LONG lAlign) {
// aligning downwards is just truncation
return ll & ~(lAlign-1);
    };

    LONGLONG AlignUp(LONGLONG ll, LONG lAlign) {
// align up: round up to next boundary
return (ll + (lAlign -1)) & ~(lAlign -1);
    };

    // GetReader returns the (addrefed) IAsyncReader interface
    // for SyncRead etc
    IAsyncReader* GetReader() {
m_pReader->AddRef();
return m_pReader;
    };

    // -- pure --

    // override this to handle data arrival
    // return value other than S_OK will stop data
    virtual HRESULT Receive(IMediaSample*) PURE;

    // override this to handle end-of-stream
    virtual HRESULT EndOfStream(void) PURE;

    // called on runtime errors that will have caused pulling
    // to stop
    // these errors are all returned from the upstream filter, who
    // will have already reported any errors to the filtergraph.
    virtual void OnError(HRESULT hr) PURE;

    // flush this pin and all downstream
    virtual HRESULT BeginFlush() PURE;
    virtual HRESULT EndFlush() PURE;

};

#endif //__PULLPIN_H__


REFCLOCK.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//  REFCLOCK.CPP
//    Implements the IReferenceClock interface

#include <streams.h>
#include <limits.h>

// 'this' used in constructor list
#pragma warning(disable:4355)

STDMETHODIMP CBaseReferenceClock::NonDelegatingQueryInterface(
    REFIID riid,
    void ** ppv)
{
    HRESULT hr;

    if (riid == IID_IReferenceClock)
    {
        hr = GetInterface((IReferenceClock *) this, ppv);
    }
    else
    {
        hr = CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
    return hr;
}

CBaseReferenceClock::~CBaseReferenceClock()
{
    if (m_TimerResolution) timeEndPeriod(m_TimerResolution);

    m_pSchedule->DumpLinkedList();

    if (m_hThread)
    {
        m_bAbort = TRUE;
        TriggerThread();
        WaitForSingleObject( m_hThread, INFINITE );
        EXECUTE_ASSERT( CloseHandle(m_hThread) );
        m_hThread = 0;
        EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) );
delete m_pSchedule;
    }
}

// A derived class may supply a hThreadEvent if it has its own thread that will take care
// of calling the schedulers Advise method.  (Refere to CBaseReferenceClock::AdviseThread()
// to see what such a thread has to do.)
CBaseReferenceClock::CBaseReferenceClock( TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr, CAMSchedule * pShed )
: CUnknown( pName, pUnk )
, m_rtLastGotTime(0)
, m_TimerResolution(0)
, m_bAbort( FALSE )
, m_pSchedule( pShed ? pShed : new CAMSchedule(CreateEvent(NULL, FALSE, FALSE, NULL)) )
, m_hThread(0)
{
    ASSERT(m_pSchedule);
    if (!m_pSchedule)
    {
*phr = E_OUTOFMEMORY;
    }
    else
    {
// Set up the highest resolution timer we can manage
TIMECAPS tc;
m_TimerResolution = (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(tc)))
    ? tc.wPeriodMin
    : 1;

timeBeginPeriod(m_TimerResolution);

/* Initialise our system times - the derived clock should set the right values */
m_dwPrevSystemTime = timeGetTime();
m_rtPrivateTime = (UNITS / MILLISECONDS) * m_dwPrevSystemTime;

#ifdef PERF
    m_idGetSystemTime = MSR_REGISTER("CBaseReferenceClock::GetTime");
#endif

if ( !pShed )
{
    DWORD ThreadID;
    m_hThread = ::CreateThread(NULL,                  // Security attributes
       (DWORD) 0,             // Initial stack size
       AdviseThreadFunction,  // Thread start address
       (LPVOID) this,         // Thread parameter
       (DWORD) 0,             // Creation flags
       &ThreadID);            // Thread identifier

    if (m_hThread)
    {
SetThreadPriority( m_hThread, THREAD_PRIORITY_TIME_CRITICAL );
    }
    else
    {
*phr = E_FAIL;
EXECUTE_ASSERT( CloseHandle(m_pSchedule->GetEvent()) );
delete m_pSchedule;
    }
}
    }
}

STDMETHODIMP CBaseReferenceClock::GetTime(REFERENCE_TIME *pTime)
{
    HRESULT hr;
    if (pTime)
    {
        REFERENCE_TIME rtNow;
        Lock();
        rtNow = GetPrivateTime();
        if (rtNow > m_rtLastGotTime)
        {
            m_rtLastGotTime = rtNow;
            hr = S_OK;
        }
        else
        {
            hr = S_FALSE;
        }
        *pTime = m_rtLastGotTime;
        Unlock();
        MSR_INTEGER(m_idGetSystemTime, LONG((*pTime) / (UNITS/MILLISECONDS)) );
    }
    else hr = E_POINTER;

    return hr;
}

/* Ask for an async notification that a time has elapsed */

STDMETHODIMP CBaseReferenceClock::AdviseTime(
    REFERENCE_TIME baseTime,         // base reference time
    REFERENCE_TIME streamTime,       // stream offset time
    HEVENT hEvent,                  // advise via this event
    DWORD *pdwAdviseCookie)         // where your cookie goes
{
    CheckPointer(pdwAdviseCookie, E_POINTER);
    *pdwAdviseCookie = 0;

    // Check that the event is not already set
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject(HANDLE(hEvent),0));

    HRESULT hr;

    const REFERENCE_TIME lRefTime = baseTime + streamTime;
    if ( lRefTime <= 0 || lRefTime == MAX_TIME )
    {
        hr = E_INVALIDARG;
    }
    else
    {
        *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( lRefTime, 0, HANDLE(hEvent), FALSE );
        hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY;
    }
    return hr;
}

/* Ask for an asynchronous periodic notification that a time has elapsed */

STDMETHODIMP CBaseReferenceClock::AdvisePeriodic(
    REFERENCE_TIME StartTime,         // starting at this time
    REFERENCE_TIME PeriodTime,        // time between notifications
    HSEMAPHORE hSemaphore,           // advise via a semaphore
    DWORD *pdwAdviseCookie)          // where your cookie goes
{
    CheckPointer(pdwAdviseCookie, E_POINTER);
    *pdwAdviseCookie = 0;

    HRESULT hr;
    if (StartTime > 0 && PeriodTime > 0 && StartTime != MAX_TIME )
    {
        *pdwAdviseCookie = m_pSchedule->AddAdvisePacket( StartTime, PeriodTime, HANDLE(hSemaphore), TRUE );
        hr = *pdwAdviseCookie ? NOERROR : E_OUTOFMEMORY;
    }
    else hr = E_INVALIDARG;

    return hr;
}

STDMETHODIMP CBaseReferenceClock::Unadvise(DWORD dwAdviseCookie)
{
    return m_pSchedule->Unadvise(dwAdviseCookie);
}

REFERENCE_TIME CBaseReferenceClock::GetPrivateTime()
{
    CAutoLock cObjectLock(this);

    /* If the clock has wrapped then the current time will be less than
     * the last time we were notified so add on the extra milliseconds
     *
     * The time period is long enough so that the likelihood of
     * successive calls spanning the clock cycle is not considered.
     */

    DWORD dwTime = timeGetTime();
    {
        m_rtPrivateTime += Int32x32To64(UNITS / MILLISECONDS, (DWORD)(dwTime - m_dwPrevSystemTime));
        m_dwPrevSystemTime = dwTime;
    }

    return m_rtPrivateTime;
}

/* Adjust the current time by the input value.  This allows an
   external time source to work out some of the latency of the clock
   system and adjust the "current" time accordingly.  The intent is
   that the time returned to the user is synchronised to a clock
   source and allows drift to be catered for.

   For example: if the clock source detects a drift it can pass a delta
   to the current time rather than having to set an explicit time.
*/

STDMETHODIMP CBaseReferenceClock::SetTimeDelta(const REFERENCE_TIME & TimeDelta)
{
#ifdef DEBUG

    // Just break if it's a really dumb value
    LONGLONG llDelta = TimeDelta > 0 ? TimeDelta : -TimeDelta;
    if (llDelta > UNITS * 1000) {
        DbgLog((LOG_TRACE, 0, TEXT("Bad Time Delta")));
        DebugBreak();
    }

    // We're going to calculate a "severity" for the time change. Max -1
    // min 8.  We'll then use this as the debug logging level for a
    // debug log message.
    const LONG usDelta = LONG(TimeDelta/10);      // Delta in micro-secs

    DWORD delta        = abs(usDelta);            // varying delta
    // Severity == 8 - ceil(log<base 8>(abs( micro-secs delta)))
    int   Severity     = 8;
    while ( delta > 0 )
    {
        delta >>= 3;                              // div 8
        Severity--;
    }

    // Sev == 0 => > 2 second delta!
    DbgLog((LOG_TIMING, Severity < 0 ? 0 : Severity,
        TEXT("Sev %2i: CSystemClock::SetTimeDelta(%8ld us) %lu -> %lu ms."),
        Severity, usDelta, DWORD(ConvertToMilliseconds(m_rtPrivateTime)),
        DWORD(ConvertToMilliseconds(TimeDelta+m_rtPrivateTime)) ));

    // Don't want the DbgBreak to fire when running stress on debug-builds.
    #ifdef BREAK_ON_SEVERE_TIME_DELTA
        if (Severity < 0)
            DbgBreakPoint(TEXT("SetTimeDelta > 16 seconds!"),
                          TEXT(__FILE__),__LINE__);
    #endif

#endif

    CAutoLock cObjectLock(this);
    m_rtPrivateTime += TimeDelta;
    // If time goes forwards, and we have advises, then we need to
    // trigger the thread so that it can re-evaluate its wait time.
    // Since we don't want the cost of the thread switches if the change
    // is really small, only do it if clock goes forward by more than
    // 0.5 millisecond.  If the time goes backwards, the thread will
    // wake up "early" (relativly speaking) and will re-evaluate at
    // that time.
    if ( TimeDelta > 5000 && m_pSchedule->GetAdviseCount() > 0 ) TriggerThread();
    return NOERROR;
}

// Thread stuff

DWORD __stdcall CBaseReferenceClock::AdviseThreadFunction(LPVOID p)
{
    return DWORD(reinterpret_cast<CBaseReferenceClock*>(p)->AdviseThread());
}

HRESULT CBaseReferenceClock::AdviseThread()
{
    DWORD dwWait = INFINITE;

    // The first thing we do is wait until something interesting happens
    // (meaning a first advise or shutdown).  This prevents us calling
    // GetPrivateTime immediately which is goodness as that is a virtual
    // routine and the derived class may not yet be constructed.  (This
    // thread is created in the base class constructor.)

    while ( !m_bAbort )
    {
        // Wait for an interesting event to happen
        DbgLog((LOG_TIMING, 3, TEXT("CBaseRefClock::AdviseThread() Delay: %lu ms"), dwWait ));
        WaitForSingleObject(m_pSchedule->GetEvent(), dwWait);
        if (m_bAbort) break;

        // There are several reasons why we need to work from the internal
        // time, mainly to do with what happens when time goes backwards.
        // Mainly, it stop us looping madly if an event is just about to
        // expire when the clock goes backward (i.e. GetTime stop for a
        // while).
        const REFERENCE_TIME  rtNow = GetPrivateTime();

        DbgLog((LOG_TIMING, 3,
              TEXT("CBaseRefClock::AdviseThread() Woke at = %lu ms"),
              ConvertToMilliseconds(rtNow) ));

        // We must add in a millisecond, since this is the resolution of our
        // WaitForSingleObject timer.  Failure to do so will cause us to loop
        // franticly for (approx) 1 a millisecond.
        m_rtNextAdvise = m_pSchedule->Advise( 10000 + rtNow );
        LONGLONG llWait = m_rtNextAdvise - rtNow;

        ASSERT( llWait > 0 );

        llWait = ConvertToMilliseconds(llWait);
        // DON'T replace this with a max!! (The type's of these things is VERY important)
        dwWait = (llWait > REFERENCE_TIME(UINT_MAX)) ? UINT_MAX : DWORD(llWait);
    };
    return NOERROR;
}


REFCLOCK.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//  REFCLOCK.H
//  Implements IReferenceClock Interface

#ifndef __BASEREFCLOCK__
#define __BASEREFCLOCK__

#include <Schedule.h>

const UINT RESOLUTION = 1;                      /* High resolution timer */
const INT ADVISE_CACHE = 4;                     /* Default cache size */
const LONGLONG MAX_TIME = 0x7FFFFFFFFFFFFFFF;   /* Maximum LONGLONG value */

inline LONGLONG WINAPI ConvertToMilliseconds(const REFERENCE_TIME& RT)
{
    /* This converts an arbitrary value representing a reference time
       into a MILLISECONDS value for use in subsequent system calls */

    return (RT / (UNITS / MILLISECONDS));
}

/* This class hierarchy will support an IReferenceClock interface so
   that an audio card (or other externally driven clock) can update the
   system wide clock that everyone uses.

   The interface will be pretty thin with probably just one update method
   This interface has not yet been defined.
 */

/* This abstract base class implements the IReferenceClock
 * interface.  Classes that actually provide clock signals (from
 * whatever source) have to be derived from this class.
 *
 * The abstract class provides implementations for:
 * CUnknown support
 *      locking support (CCritSec)
 *client advise code (creates a thread)
 *
 * Question: what can we do about quality?  Change the timer
 * resolution to lower the system load?  Up the priority of the
 * timer thread to force more responsive signals?
 *
 * During class construction we create a worker thread that is destroyed during
 * destuction.  This thread executes a series of WaitForSingleObject calls,
 * waking up when a command is given to the thread or the next wake up point
 * is reached.  The wakeup points are determined by clients making Advise
 * calls.
 *
 * Each advise call defines a point in time when they wish to be notified.  A
 * periodic advise is a series of these such events.  We maintain a list of
 * advise links and calculate when the nearest event notification is due for.
 * We then call WaitForSingleObject with a timeout equal to this time.  The
 * handle we wait on is used by the class to signal that something has changed
 * and that we must reschedule the next event.  This typically happens when
 * someone comes in and asks for an advise link while we are waiting for an
 * event to timeout.
 *
 * While we are modifying the list of advise requests we
 * are protected from interference through a critical section.  Clients are NOT
 * advised through callbacks.  One shot clients have an event set, while
 * periodic clients have a semaphore released for each event notification.  A
 * semaphore allows a client to be kept up to date with the number of events
 * actually triggered and be assured that they can't miss multiple events being
 * set.
 *
 * Keeping track of advises is taken care of by the CAMSchedule class.
 */

class CBaseReferenceClock
: public CUnknown, public IReferenceClock, public CCritSec
{
protected:
    virtual ~CBaseReferenceClock();     // Don't let me be created on the stack!
public:
    CBaseReferenceClock(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr, CAMSchedule * pSched = 0 );

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void ** ppv);

    DECLARE_IUNKNOWN

    /* IReferenceClock methods */
    // Derived classes must implement GetPrivateTime().  All our GetTime
    // does is call GetPrivateTime and then check so that time does not
    // go backwards.  A return code of S_FALSE implies that the internal
    // clock has gone backwards and GetTime time has halted until internal
    // time has caught up. (Don't know if this will be much use to folk,
    // but it seems silly not to use the return code for something useful.)
    STDMETHODIMP GetTime(REFERENCE_TIME *pTime);
    // When this is called, it sets m_rtLastGotTime to the time it returns.

    /* Provide standard mechanisms for scheduling events */

    /* Ask for an async notification that a time has elapsed */
    STDMETHODIMP AdviseTime(
        REFERENCE_TIME baseTime,        // base reference time
        REFERENCE_TIME streamTime,      // stream offset time
        HEVENT hEvent,                  // advise via this event
        DWORD *pdwAdviseCookie          // where your cookie goes
    );

    /* Ask for an asynchronous periodic notification that a time has elapsed */
    STDMETHODIMP AdvisePeriodic(
        REFERENCE_TIME StartTime,       // starting at this time
        REFERENCE_TIME PeriodTime,      // time between notifications
        HSEMAPHORE hSemaphore,          // advise via a semaphore
        DWORD *pdwAdviseCookie          // where your cookie goes
    );

    /* Cancel a request for notification(s) - if the notification was
     * a one shot timer then this function doesn't need to be called
     * as the advise is automatically cancelled, however it does no
     * harm to explicitly cancel a one-shot advise.  It is REQUIRED that
     * clients call Unadvise to clear a Periodic advise setting.
     */

    STDMETHODIMP Unadvise(DWORD dwAdviseCookie);

    /* Methods for the benefit of derived classes or outer objects */

    // GetPrivateTime() is the REAL clock.  GetTime is just a cover for
    // it.  Derived classes will probably override this method but not
    // GetTime() itself.
    // The important point about GetPrivateTime() is it's allowed to go
    // backwards.  Our GetTime() will keep returning the LastGotTime
    // until GetPrivateTime() catches up.
    virtual REFERENCE_TIME GetPrivateTime();

    /* Provide a method for correcting drift */
    STDMETHODIMP SetTimeDelta( const REFERENCE_TIME& TimeDelta );

    CAMSchedule * GetSchedule() const { return m_pSchedule; }

private:
    REFERENCE_TIME m_rtPrivateTime;     // Current best estimate of time
    DWORD          m_dwPrevSystemTime;  // Last vaule we got from timeGetTime
    REFERENCE_TIME m_rtLastGotTime;     // Last time returned by GetTime
    REFERENCE_TIME m_rtNextAdvise;      // Time of next advise
    UINT           m_TimerResolution;

#ifdef PERF
    int m_idGetSystemTime;
#endif

// Thread stuff
public:
    void TriggerThread()                // Wakes thread up.  Need to do this if
    {// time to next advise needs reevaluating.
EXECUTE_ASSERT(SetEvent(m_pSchedule->GetEvent()));
    }

private:
    BOOL           m_bAbort;            // Flag used for thread shutdown
    HANDLE         m_hThread;           // Thread handle

    HRESULT AdviseThread();             // Method in which the advise thread runs
    static DWORD __stdcall AdviseThreadFunction(LPVOID); // Function used to get there

protected:
    CAMSchedule * const m_pSchedule;
};

#endif


REFTIME.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// CRefTime
//
// Manage reference times.
// Shares same data layout as REFERENCE_TIME, but adds some (nonvirtual)
// functions providing simple comparison, conversion and arithmetic.
//
// A reference time (at the moment) is a unit of seconds represented in
// 100ns units as is used in the Win32 FILETIME structure. BUT the time
// a REFERENCE_TIME represents is NOT the time elapsed since 1/1/1601 it
// will either be stream time or reference time depending upon context
//
// This class provides simple arithmetic operations on reference times
//
// keep non-virtual otherwise the data layout will not be the same as
// REFERENCE_TIME

// -----
// note that you are safe to cast a CRefTime* to a REFERENCE_TIME*, but
// you will need to do so explicitly
// -----

#ifndef __REFTIME__
#define __REFTIME__

const LONGLONG MILLISECONDS = (1000);            // 10 ^ 3
const LONGLONG NANOSECONDS = (1000000000);       // 10 ^ 9
const LONGLONG UNITS = (NANOSECONDS / 100);      // 10 ^ 7

/*  Unfortunately an inline function here generates a call to __allmul
    - even for constants!
*/
#define MILLISECONDS_TO_100NS_UNITS(lMs) \
    Int32x32To64((lMs), (UNITS / MILLISECONDS))

class CRefTime
{
public:

    // *MUST* be the only data member so that this class is exactly
    // equivalent to a REFERENCE_TIME.
    // Also, must be *no virtual functions*

    REFERENCE_TIME m_time;

    inline CRefTime()
    {
        // default to 0 time
        m_time = 0;
    };

    inline CRefTime(LONG msecs)
    {
        m_time = MILLISECONDS_TO_100NS_UNITS(msecs);
    };

    inline CRefTime(REFERENCE_TIME rt)
    {
        m_time = rt;
    };

    inline operator REFERENCE_TIME() const
    {
        return m_time;
    };

    inline CRefTime& operator=(const CRefTime& rt)
    {
        m_time = rt.m_time;
        return *this;
    };

    inline CRefTime& operator=(const LONGLONG ll)
    {
        m_time = ll;
        return *this;
    };

    inline CRefTime& operator+=(const CRefTime& rt)
    {
        return (*this = *this + rt);
    };

    inline CRefTime& operator-=(const CRefTime& rt)
    {
        return (*this = *this - rt);
    };

    inline LONG Millisecs(void)
    {
        return (LONG)(m_time / (UNITS / MILLISECONDS));
    };

    inline LONGLONG GetUnits(void)
    {
        return m_time;
    };
};

const LONGLONG TimeZero = 0;

#endif /* __REFTIME__ */


RENBASE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>        // ActiveMovie base class definitions
#include <mmsystem.h>       // Needed for definition of timeGetTime
#include <limits.h>         // Standard data type limit definitions
#include <measure.h>        // Used for time critical log functions

#pragma warning(disable:4355)

//  Helper function for clamping time differences
int inline TimeDiff(REFERENCE_TIME rt)
{
    if (rt < - (50 * UNITS)) {
        return -(50 * UNITS);
    } else
    if (rt > 50 * UNITS) {
        return 50 * UNITS;
    } else return (int)rt;
}

// Implements the CBaseRenderer class

CBaseRenderer::CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
     TCHAR *pName,         // Debug ONLY description
     LPUNKNOWN pUnk,       // Aggregated owner object
     HRESULT *phr) :       // General OLE return code

    CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass),
    m_evComplete(TRUE),
    m_bAbort(FALSE),
    m_pPosition(NULL),
    m_ThreadSignal(TRUE),
    m_bStreaming(FALSE),
    m_bEOS(FALSE),
    m_bEOSDelivered(FALSE),
    m_pMediaSample(NULL),
    m_dwAdvise(0),
    m_pQSink(NULL),
    m_pInputPin(NULL),
    m_bRepaintStatus(TRUE),
    m_SignalTime(0),
    m_bInReceive(FALSE),
    m_EndOfStreamTimer(0)
{
    Ready();
#ifdef PERF
    m_idBaseStamp = MSR_REGISTER("BaseRenderer: sample time stamp");
    m_idBaseRenderTime = MSR_REGISTER("BaseRenderer: draw time (msec)");
    m_idBaseAccuracy = MSR_REGISTER("BaseRenderer: Accuracy (msec)");
#endif
}

// Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
// object. The object is created when somebody queries us. These are standard
// control interfaces for seeking and setting start/stop positions and rates.
// We will probably also have made an input pin based on CRendererInputPin
// that has to be deleted, it's created when an enumerator calls our GetPin

CBaseRenderer::~CBaseRenderer()
{
    ASSERT(m_bStreaming == FALSE);
    ASSERT(m_EndOfStreamTimer == 0);
    StopStreaming();
    ClearPendingSample();

    // Delete any IMediaPosition implementation

    if (m_pPosition) {
delete m_pPosition;
m_pPosition = NULL;
    }

    // Delete any input pin created

    if (m_pInputPin) {
delete m_pInputPin;
m_pInputPin = NULL;
    }

    // Release any Quality sink

    ASSERT(m_pQSink == NULL);
}

// This returns the IMediaPosition and IMediaSeeking interfaces

HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid,void **ppv)
{
    CAutoLock cRendererLock(&m_InterfaceLock);
    if (m_pPosition) {
return m_pPosition->NonDelegatingQueryInterface(riid,ppv);
    }

    HRESULT hr = NOERROR;

    // Create implementation of this dynamically since sometimes we may
    // never try and do a seek. The helper object implements a position
    // control interface (IMediaPosition) which in fact simply takes the
    // calls normally from the filter graph and passes them upstream

    m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
   CBaseFilter::GetOwner(),
   (HRESULT *) &hr,
   GetPin(0));
    if (m_pPosition == NULL) {
return E_OUTOFMEMORY;
    }

    if (FAILED(hr)) {
delete m_pPosition;
m_pPosition = NULL;
return E_NOINTERFACE;
    }
    return GetMediaPositionInterface(riid,ppv);
}

// Overriden to say what interfaces we support and where

STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    // Do we have this interface

    if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
return GetMediaPositionInterface(riid,ppv);
    } else {
return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
    }
}

// This is called whenever we change states, we have a manual reset event that
// is signalled whenever we don't won't the source filter thread to wait in us
// (such as in a stopped state) and likewise is not signalled whenever it can
// wait (during paused and running) this function sets or resets the thread
// event. The event is used to stop source filter threads waiting in Receive

HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait)
{
    if (bCanWait == TRUE) {
m_ThreadSignal.Reset();
    } else {
m_ThreadSignal.Set();
    }
    return NOERROR;
}

#ifdef DEBUG
// Dump the current renderer state to the debug terminal. The hardest part of
// the renderer is the window where we unlock everything to wait for a clock
// to signal it is time to draw or for the application to cancel everything
// by stopping the filter. If we get things wrong we can leave the thread in
// WaitForRenderTime with no way for it to ever get out and we will deadlock

void CBaseRenderer::DisplayRendererState()
{
    TCHAR DebugString[128];
    wsprintf(DebugString,TEXT("\n\nTimed out in WaitForRenderTime\n"));
    OutputDebugString(DebugString);

    // No way should this be signalled at this point

    BOOL bSignalled = m_ThreadSignal.Check();
    wsprintf(DebugString,TEXT("Signal sanity check %d\n"),bSignalled);
    OutputDebugString(DebugString);

    // Now output the current renderer state variables

    wsprintf(DebugString,TEXT("Filter state %d\n"),m_State);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Abort flag %d\n"),m_bAbort);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Streaming flag %d\n"),m_bStreaming);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Clock advise link %d\n"),m_dwAdvise);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Current media sample %x\n"),m_pMediaSample);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("EOS signalled %d\n"),m_bEOS);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("EOS delivered %d\n"),m_bEOSDelivered);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Repaint status %d\n"),m_bRepaintStatus);
    OutputDebugString(DebugString);

    // Output the delayed end of stream timer information

    wsprintf(DebugString,TEXT("End of stream timer %x\n"),m_EndOfStreamTimer);
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Deliver time %s\n"),CDisp((LONGLONG)m_SignalTime));
    OutputDebugString(DebugString);

    // Should never timeout during a flushing state

    BOOL bFlushing = m_pInputPin->IsFlushing();
    wsprintf(DebugString,TEXT("Flushing sanity check %d\n"),bFlushing);
    OutputDebugString(DebugString);

    // Display the time we were told to start at
    wsprintf(DebugString,TEXT("Last run time %s\n"),CDisp((LONGLONG)m_tStart.m_time));
    OutputDebugString(DebugString);

    // Have we got a reference clock
    if (m_pClock == NULL) return;

    // Get the current time from the wall clock

    CRefTime CurrentTime,StartTime,EndTime;
    m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime);
    CRefTime Offset = CurrentTime - m_tStart;

    // Display the current time from the clock

    wsprintf(DebugString,TEXT("Clock time %s\n"),CDisp((LONGLONG)CurrentTime.m_time));
    OutputDebugString(DebugString);
    wsprintf(DebugString,TEXT("Time difference %dms\n"),Offset.Millisecs());
    OutputDebugString(DebugString);

    // Do we have a sample ready to render
    if (m_pMediaSample == NULL) return;

    m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
    wsprintf(DebugString,TEXT("Next sample stream times (Start %d End %d ms)\n"),
   StartTime.Millisecs(),EndTime.Millisecs());
    OutputDebugString(DebugString);

    // Calculate how long it is until it is due for rendering
    CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
    wsprintf(DebugString,TEXT("Wait required %d ms\n"),Wait.Millisecs());
    OutputDebugString(DebugString);
}
#endif

// Wait until the clock sets the timer event or we're otherwise signalled. We
// set an arbitrary timeout for this wait and if it fires then we display the
// current renderer state on the debugger. It will often fire if the filter's
// left paused in an application however it may also fire during stress tests
// if the synchronisation with application seeks and state changes is faulty

#define RENDER_TIMEOUT 10000

HRESULT CBaseRenderer::WaitForRenderTime()
{
    HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent };
    DWORD Result = WAIT_TIMEOUT;

    // Wait for either the time to arrive or for us to be stopped

    OnWaitStart();
    while (Result == WAIT_TIMEOUT) {
Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT);

#ifdef DEBUG
if (Result == WAIT_TIMEOUT) DisplayRendererState();
#endif

    }
    OnWaitEnd();

    // We may have been awoken without the timer firing

    if (Result == WAIT_OBJECT_0) {
return VFW_E_STATE_CHANGED;
    }

    SignalTimerFired();
    return NOERROR;
}

// Poll waiting for Receive to complete.  This really matters when
// Receive may set the palette and cause window messages
// The problem is that if we don't really wait for a renderer to
// stop processing we can deadlock waiting for a transform which
// is calling the renderer's Receive() method because the transform's
// Stop method doesn't know to process window messages to unblock
// the renderer's Receive processing
void CBaseRenderer::WaitForReceiveToComplete()
{
    for (;;) {
if (!m_bInReceive) {
    break;
}

MSG msg;
//  Receive all interthread snedmessages
PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE);

Sleep(1);
    }

    // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
    // above just cleared the changebit which will cause some messaging
    // calls to block (waitMessage, MsgWaitFor...) now.
    // Post a dummy message to set the QS_POSTMESSAGE bit again
    if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
//  Send dummy message
PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
    }
}

// A filter can have four discrete states, namely Stopped, Running, Paused,
// Intermediate. We are in an intermediate state if we are currently trying
// to pause but haven't yet got the first sample (or if we have been flushed
// in paused state and therefore still have to wait for a sample to arrive)

// This class contains an event called m_evComplete which is signalled when
// the current state is completed and is not signalled when we are waiting to
// complete the last state transition. As mentioned above the only time we
// use this at the moment is when we wait for a media sample in paused state
// If while we are waiting we receive an end of stream notification from the
// source filter then we know no data is imminent so we can reset the event
// This means that when we transition to paused the source filter must call
// end of stream on us or send us an image otherwise we'll hang indefinately

// Simple internal way of getting the real state

FILTER_STATE CBaseRenderer::GetRealState() {
    return m_State;
}

// The renderer doesn't complete the full transition to paused states until
// it has got one media sample to render. If you ask it for its state while
// it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE

STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State)
{
    CheckPointer(State,E_POINTER);

    if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) {
*State = m_State;
return VFW_S_STATE_INTERMEDIATE;
    }
    *State = m_State;
    return NOERROR;
}

// If we're pausing and we have no samples we don't complete the transition
// to State_Paused and we return S_FALSE. However if the m_bAbort flag has
// been set then all samples are rejected so there is no point waiting for
// one. If we do have a sample then return NOERROR. We will only ever return
// VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
// (calling GetState after either being stopped or Run will NOT return this)

HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState)
{
    // Allow us to be paused when disconnected

    if (m_pInputPin->IsConnected() == FALSE) {
Ready();
return S_OK;
    }

    // Have we run off the end of stream

    if (IsEndOfStream() == TRUE) {
Ready();
return S_OK;
    }

    // Make sure we get fresh data after being stopped

    if (HaveCurrentSample() == TRUE) {
if (OldState != State_Stopped) {
    Ready();
    return S_OK;
}
    }
    NotReady();
    return S_FALSE;
}

// When we stop the filter the things we do are:-

//      Decommit the allocator being used in the connection
//      Release the source filter if it's waiting in Receive
//      Cancel any advise link we set up with the clock
//      Any end of stream signalled is now obsolete so reset
//      Allow us to be stopped when we are not connected

STDMETHODIMP CBaseRenderer::Stop()
{
    CAutoLock cRendererLock(&m_InterfaceLock);

    // Make sure there really is a state change

    if (m_State == State_Stopped) {
return NOERROR;
    }

    // Is our input pin connected

    if (m_pInputPin->IsConnected() == FALSE) {
NOTE("Input pin is not connected");
m_State = State_Stopped;
return NOERROR;
    }

    CBaseFilter::Stop();

    // If we are going into a stopped state then we must decommit whatever
    // allocator we are using it so that any source filter waiting in the
    // GetBuffer can be released and unlock themselves for a state change

    if (m_pInputPin->Allocator()) {
m_pInputPin->Allocator()->Decommit();
    }

    // Cancel any scheduled rendering

    SetRepaintStatus(TRUE);
    StopStreaming();
    SourceThreadCanWait(FALSE);
    ResetEndOfStream();
    CancelNotification();

    // There should be no outstanding clock advise
    ASSERT(CancelNotification() == S_FALSE);
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
    ASSERT(m_EndOfStreamTimer == 0);

    Ready();
    WaitForReceiveToComplete();
    m_bAbort = FALSE;
    return NOERROR;
}

// When we pause the filter the things we do are:-

//      Commit the allocator being used in the connection
//      Allow a source filter thread to wait in Receive
//      Cancel any clock advise link (we may be running)
//      Possibly complete the state change if we have data
//      Allow us to be paused when we are not connected

STDMETHODIMP CBaseRenderer::Pause()
{
    CAutoLock cRendererLock(&m_InterfaceLock);
    FILTER_STATE OldState = m_State;
    ASSERT(m_pInputPin->IsFlushing() == FALSE);

    // Make sure there really is a state change

    if (m_State == State_Paused) {
return CompleteStateChange(State_Paused);
    }

    // Has our input pin been connected

    if (m_pInputPin->IsConnected() == FALSE) {
NOTE("Input pin is not connected");
m_State = State_Paused;
return CompleteStateChange(State_Paused);
    }

    // Pause the base filter class

    HRESULT hr = CBaseFilter::Pause();
    if (FAILED(hr)) {
NOTE("Pause failed");
return hr;
    }

    // Enable EC_REPAINT events again

    SetRepaintStatus(TRUE);
    StopStreaming();
    SourceThreadCanWait(TRUE);
    CancelNotification();
    ResetEndOfStreamTimer();

    // If we are going into a paused state then we must commit whatever
    // allocator we are using it so that any source filter can call the
    // GetBuffer and expect to get a buffer without returning an error

    if (m_pInputPin->Allocator()) {
m_pInputPin->Allocator()->Commit();
    }

    // There should be no outstanding advise
    ASSERT(CancelNotification() == S_FALSE);
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
    ASSERT(m_EndOfStreamTimer == 0);
    ASSERT(m_pInputPin->IsFlushing() == FALSE);

    // When we come out of a stopped state we must clear any image we were
    // holding onto for frame refreshing. Since renderers see state changes
    // first we can reset ourselves ready to accept the source thread data
    // Paused or running after being stopped causes the current position to
    // be reset so we're not interested in passing end of stream signals

    if (OldState == State_Stopped) {
m_bAbort = FALSE;
ClearPendingSample();
    }
    return CompleteStateChange(OldState);
}

// When we run the filter the things we do are:-

//      Commit the allocator being used in the connection
//      Allow a source filter thread to wait in Receive
//      Signal the render event just to get us going
//      Start the base class by calling StartStreaming
//      Allow us to be run when we are not connected
//      Signal EC_COMPLETE if we are not connected

STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime)
{
    CAutoLock cRendererLock(&m_InterfaceLock);
    FILTER_STATE OldState = m_State;

    // Make sure there really is a state change

    if (m_State == State_Running) {
return NOERROR;
    }

    // Send EC_COMPLETE if we're not connected

    if (m_pInputPin->IsConnected() == FALSE) {
NotifyEvent(EC_COMPLETE,S_OK,0);
m_State = State_Running;
return NOERROR;
    }

    Ready();

    // Pause the base filter class

    HRESULT hr = CBaseFilter::Run(StartTime);
    if (FAILED(hr)) {
NOTE("Run failed");
return hr;
    }

    // Allow the source thread to wait
    ASSERT(m_pInputPin->IsFlushing() == FALSE);
    SourceThreadCanWait(TRUE);
    SetRepaintStatus(FALSE);

    // There should be no outstanding advise
    ASSERT(CancelNotification() == S_FALSE);
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
    ASSERT(m_EndOfStreamTimer == 0);
    ASSERT(m_pInputPin->IsFlushing() == FALSE);

    // If we are going into a running state then we must commit whatever
    // allocator we are using it so that any source filter can call the
    // GetBuffer and expect to get a buffer without returning an error

    if (m_pInputPin->Allocator()) {
m_pInputPin->Allocator()->Commit();
    }

    // When we come out of a stopped state we must clear any image we were
    // holding onto for frame refreshing. Since renderers see state changes
    // first we can reset ourselves ready to accept the source thread data
    // Paused or running after being stopped causes the current position to
    // be reset so we're not interested in passing end of stream signals

    if (OldState == State_Stopped) {
m_bAbort = FALSE;
ClearPendingSample();
    }
    return StartStreaming();
}

// Return the number of input pins we support

int CBaseRenderer::GetPinCount()
{
    return 1;
}

// We only support one input pin and it is numbered zero

CBasePin *CBaseRenderer::GetPin(int n)
{
    CAutoLock cRendererLock(&m_InterfaceLock);
    HRESULT hr = NOERROR;
    ASSERT(n == 0);

    // Should only ever be called with zero

    if (n != 0) {
return NULL;
    }

    // Create the input pin if not already done so

    if (m_pInputPin == NULL) {
m_pInputPin = new CRendererInputPin(this,&hr,L"In");
    }
    return m_pInputPin;
}

// If "In" then return the IPin for our input pin, otherwise NULL and error

STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, IPin **ppPin)
{
    CheckPointer(ppPin,E_POINTER);

    if (0==lstrcmpW(Id,L"In")) {
*ppPin = GetPin(0);
ASSERT(*ppPin);
(*ppPin)->AddRef();
    } else {
*ppPin = NULL;
return VFW_E_NOT_FOUND;
    }
    return NOERROR;
}

// Called when the input pin receives an EndOfStream notification. If we have
// not got a sample, then notify EC_COMPLETE now. If we have samples, then set
// m_bEOS and check for this on completing samples. If we're waiting to pause
// then complete the transition to paused state by setting the state event

HRESULT CBaseRenderer::EndOfStream()
{
    // Ignore these calls if we are stopped

    if (m_State == State_Stopped) {
return NOERROR;
    }

    // If we have a sample then wait for it to be rendered

    m_bEOS = TRUE;
    if (m_pMediaSample) {
return NOERROR;
    }

    // If we are waiting for pause then we are now ready since we cannot now
    // carry on waiting for a sample to arrive since we are being told there
    // won't be any. This sets an event that the GetState function picks up

    Ready();

    // Only signal completion now if we are running otherwise queue it until
    // we do run in StartStreaming. This is used when we seek because a seek
    // causes a pause where early notification of completion is misleading

    if (m_bStreaming) {
SendEndOfStream();
    }
    return NOERROR;
}

// When we are told to flush we should release the source thread

HRESULT CBaseRenderer::BeginFlush()
{
    // If paused then report state intermediate until we get some data

    if (m_State == State_Paused) {
NotReady();
    }

    SourceThreadCanWait(FALSE);
    CancelNotification();
    ClearPendingSample();
    //  Wait for Receive to complete
    WaitForReceiveToComplete();
    return NOERROR;
}

// After flushing the source thread can wait in Receive again

HRESULT CBaseRenderer::EndFlush()
{
    // Reset the current sample media time
    if (m_pPosition) m_pPosition->ResetMediaTime();

    // There should be no outstanding advise

    ASSERT(CancelNotification() == S_FALSE);
    SourceThreadCanWait(TRUE);
    return NOERROR;
}

// We can now send EC_REPAINTs if so required

HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin)
{
    SetRepaintStatus(TRUE);
    m_bAbort = FALSE;
    return NOERROR;
}

// Called when we go paused or running

HRESULT CBaseRenderer::Active()
{
    return NOERROR;
}

// Called when we go into a stopped state

HRESULT CBaseRenderer::Inactive()
{
    if (m_pPosition) {
m_pPosition->ResetMediaTime();
    }
    //  People who derive from this may want to override this behaviour
    //  to keep hold of the sample in some circumstances
    ClearPendingSample();
    return NOERROR;
}

// Tell derived classes about the media type agreed

HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt)
{
    return NOERROR;
}

// When we break the input pin connection we should reset the EOS flags. When
// we are asked for either IMediaPosition or IMediaSeeking we will create a
// CPosPassThru object to handles media time pass through. When we're handed
// samples we store (by calling CPosPassThru::RegisterMediaTime) their media
// times so we can then return a real current position of data being rendered

HRESULT CBaseRenderer::BreakConnect()
{
    // Do we have a quality management sink

    if (m_pQSink) {
m_pQSink->Release();
m_pQSink = NULL;
    }

    // Check we have a valid connection

    if (m_pInputPin->IsConnected() == FALSE) {
return S_FALSE;
    }

    // Check we are stopped before disconnecting

    if (m_State != State_Stopped) {
return VFW_E_NOT_STOPPED;
    }

    SetRepaintStatus(FALSE);
    ResetEndOfStream();
    ClearPendingSample();
    m_bAbort = FALSE;
    return NOERROR;
}

// Retrieves the sample times for this samples (note the sample times are
// passed in by reference not value). We return S_FALSE to say schedule this
// sample according to the times on the sample. We also return S_OK in
// which case the object should simply render the sample data immediately

HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample,
      REFERENCE_TIME *pStartTime,
      REFERENCE_TIME *pEndTime)
{
    ASSERT(m_dwAdvise == 0);
    ASSERT(pMediaSample);

    // If the stop time for this sample is before or the same as start time,
    // then just ignore it (release it) and schedule the next one in line
    // Source filters should always fill in the start and end times properly!

    if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) {
if (*pEndTime < *pStartTime) {
    return VFW_E_START_TIME_AFTER_END;
}
    } else {
// no time set in the sample... draw it now?
return S_OK;
    }

    // Can't synchronise without a clock so we return S_OK which tells the
    // caller that the sample should be rendered immediately without going
    // through the overhead of setting a timer advise link with the clock

    if (m_pClock == NULL) {
return S_OK;
    }
    return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime);
}

// By default all samples are drawn according to their time stamps so we
// return S_FALSE. Returning S_OK means draw immediately, this is used
// by the derived video renderer class in its quality management.

HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
   REFERENCE_TIME *ptrStart,
   REFERENCE_TIME *ptrEnd)
{
    return S_FALSE;
}

// We must always reset the current advise time to zero after a timer fires
// because there are several possible ways which lead us not to do any more
// scheduling such as the pending image being cleared after state changes

void CBaseRenderer::SignalTimerFired()
{
    m_dwAdvise = 0;
}

// Cancel any notification currently scheduled. This is called by the owning
// window object when it is told to stop streaming. If there is no timer link
// outstanding then calling this is benign otherwise we go ahead and cancel
// We must always reset the render event as the quality management code can
// signal immediate rendering by setting the event without setting an advise
// link. If we're subsequently stopped and run the first attempt to setup an
// advise link with the reference clock will find the event still signalled

HRESULT CBaseRenderer::CancelNotification()
{
    ASSERT(m_dwAdvise == 0 || m_pClock);
    DWORD dwAdvise = m_dwAdvise;

    // Have we a live advise link

    if (m_dwAdvise) {
m_pClock->Unadvise(m_dwAdvise);
SignalTimerFired();
ASSERT(m_dwAdvise == 0);
    }

    // Clear the event and return our status

    m_RenderEvent.Reset();
    return (dwAdvise ? S_OK : S_FALSE);
}

// Responsible for setting up one shot advise links with the clock
// Return FALSE if the sample is to be dropped (not drawn at all)
// Return TRUE if the sample is to be drawn and in this case also
// arrange for m_RenderEvent to be set at the appropriate time

BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample)
{
    REFERENCE_TIME StartSample, EndSample;

    // Is someone pulling our leg

    if (pMediaSample == NULL) {
return FALSE;
    }

    // Get the next sample due up for rendering.  If there aren't any ready
    // then GetNextSampleTimes returns an error.  If there is one to be done
    // then it succeeds and yields the sample times. If it is due now then
    // it returns S_OK other if it's to be done when due it returns S_FALSE

    HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
    if (FAILED(hr)) {
return FALSE;
    }

    // If we don't have a reference clock then we cannot set up the advise
    // time so we simply set the event indicating an image to render. This
    // will cause us to run flat out without any timing or synchronisation

    if (hr == S_OK) {
EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
return TRUE;
    }

    ASSERT(m_dwAdvise == 0);
    ASSERT(m_pClock);
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));

    // We do have a valid reference clock interface so we can ask it to
    // set an event when the image comes due for rendering. We pass in
    // the reference time we were told to start at and also the current
    // stream time which is the offset from the start reference time

    hr = m_pClock->AdviseTime(
    (REFERENCE_TIME) m_tStart,          // Start run time
    StartSample,                        // Stream time
    (HEVENT)(HANDLE) m_RenderEvent,     // Render notification
    &m_dwAdvise);                       // Advise cookie

    if (SUCCEEDED(hr)) {
return TRUE;
    }

    // We could not schedule the next sample for rendering despite the fact
    // we have a valid sample here. This is a fair indication that either
    // the system clock is wrong or the time stamp for the sample is duff

    ASSERT(m_dwAdvise == 0);
    return FALSE;
}

// This is called when a sample comes due for rendering. We pass the sample
// on to the derived class. After rendering we will initialise the timer for
// the next sample, NOTE signal that the last one fired first, if we don't
// do this it thinks there is still one outstanding that hasn't completed

HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample)
{
    // If the media sample is NULL then we will have been notified by the
    // clock that another sample is ready but in the mean time someone has
    // stopped us streaming which causes the next sample to be released

    if (pMediaSample == NULL) {
return S_FALSE;
    }

    // If we have stopped streaming then don't render any more samples, the
    // thread that got in and locked us and then reset this flag does not
    // clear the pending sample as we can use it to refresh any output device

    if (m_bStreaming == FALSE) {
return S_FALSE;
    }

    // Time how long the rendering takes

    OnRenderStart(pMediaSample);
    DoRenderSample(pMediaSample);
    OnRenderEnd(pMediaSample);

    return NOERROR;
}

// Checks if there is a sample waiting at the renderer

BOOL CBaseRenderer::HaveCurrentSample()
{
    CAutoLock cRendererLock(&m_RendererLock);
    return (m_pMediaSample == NULL ? FALSE : TRUE);
}

// Returns the current sample waiting at the video renderer. We AddRef the
// sample before returning so that should it come due for rendering the
// person who called this method will hold the remaining reference count
// that will stop the sample being added back onto the allocator free list

IMediaSample *CBaseRenderer::GetCurrentSample()
{
    CAutoLock cRendererLock(&m_RendererLock);
    if (m_pMediaSample) {
m_pMediaSample->AddRef();
    }
    return m_pMediaSample;
}

// Called when the source delivers us a sample. We go through a few checks to
// make sure the sample can be rendered. If we are running (streaming) then we
// have the sample scheduled with the reference clock, if we are not streaming
// then we have received an sample in paused mode so we can complete any state
// transition. On leaving this function everything will be unlocked so an app
// thread may get in and change our state to stopped (for example) in which
// case it will also signal the thread event so that our wait call is stopped

HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample)
{
    CAutoLock cRendererLock(&m_InterfaceLock);
    m_bInReceive = TRUE;

    // Check our flushing and filter state

    HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample);

    if (hr != NOERROR) {
m_bInReceive = FALSE;
return E_FAIL;
    }

    // Has the type changed on a media sample. We do all rendering
    // synchronously on the source thread, which has a side effect
    // that only one buffer is ever outstanding. Therefore when we
    // have Receive called we can go ahead and change the format
    // Since the format change can cause a SendMessage we just don't
    // lock
    if (m_pInputPin->SampleProps()->pMediaType) {
m_pInputPin->SetMediaType(
    (CMediaType *)m_pInputPin->SampleProps()->pMediaType);
    }

    CAutoLock cSampleLock(&m_RendererLock);

    ASSERT(IsActive() == TRUE);
    ASSERT(m_pInputPin->IsFlushing() == FALSE);
    ASSERT(m_pInputPin->IsConnected() == TRUE);
    ASSERT(m_pMediaSample == NULL);

    // Return an error if we already have a sample waiting for rendering
    // source pins must serialise the Receive calls - we also check that
    // no data is being sent after the source signalled an end of stream

    if (m_pMediaSample || m_bEOS || m_bAbort) {
Ready();
m_bInReceive = FALSE;
return E_UNEXPECTED;
    }

    // Store the media times from this sample
    if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);

    // Schedule the next sample if we are streaming

    if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) {
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
ASSERT(CancelNotification() == S_FALSE);
m_bInReceive = FALSE;
return VFW_E_SAMPLE_REJECTED;
    }

    // Store the sample end time for EC_COMPLETE handling
    m_SignalTime = m_pInputPin->SampleProps()->tStop;

    // BEWARE we sometimes keep the sample even after returning the thread to
    // the source filter such as when we go into a stopped state (we keep it
    // to refresh the device with) so we must AddRef it to keep it safely. If
    // we start flushing the source thread is released and any sample waiting
    // will be released otherwise GetBuffer may never return (see BeginFlush)

    m_pMediaSample = pMediaSample;
    m_pMediaSample->AddRef();

    if (m_bStreaming == FALSE) {
SetRepaintStatus(TRUE);
    }
    return NOERROR;
}

// Called by the source filter when we have a sample to render. Under normal
// circumstances we set an advise link with the clock, wait for the time to
// arrive and then render the data using the PURE virtual DoRenderSample that
// the derived class will have overriden. After rendering the sample we may
// also signal EOS if it was the last one sent before EndOfStream was called

HRESULT CBaseRenderer::Receive(IMediaSample *pSample)
{
    ASSERT(pSample);

    // It may return VFW_E_SAMPLE_REJECTED code to say don't bother

    HRESULT hr = PrepareReceive(pSample);
    ASSERT(m_bInReceive == SUCCEEDED(hr));
    if (FAILED(hr)) {
if (hr == VFW_E_SAMPLE_REJECTED) {
    return NOERROR;
}
return hr;
    }

    // We realize the palette in "PrepareRender()" so we have to give away the
    // filter lock here.
    if (m_State == State_Paused) {
PrepareRender();
// no need to use InterlockedExchange
m_bInReceive = FALSE;
{
    // We must hold both these locks
    CAutoLock cRendererLock(&m_InterfaceLock);
    if (m_State == State_Stopped)
return NOERROR;
    m_bInReceive = TRUE;
    CAutoLock cSampleLock(&m_RendererLock);
    OnReceiveFirstSample(pSample);
}
Ready();
    }
    // Having set an advise link with the clock we sit and wait. We may be
    // awoken by the clock firing or by a state change. The rendering call
    // will lock the critical section and check we can still render the data

    hr = WaitForRenderTime();
    if (FAILED(hr)) {
m_bInReceive = FALSE;
return NOERROR;
    }

    PrepareRender();

    //  Set this here and poll it until we work out the locking correctly
    //  It can't be right that the streaming stuff grabs the interface
    //  lock - after all we want to be able to wait for this stuff
    //  to complete
    m_bInReceive = FALSE;

    // We must hold both these locks
    CAutoLock cRendererLock(&m_InterfaceLock);

    // since we gave away the filter wide lock, the sate of the filter could
    // have chnaged to Stopped
    if (m_State == State_Stopped)
return NOERROR;

    CAutoLock cSampleLock(&m_RendererLock);

    // Deal with this sample

    Render(m_pMediaSample);
    ClearPendingSample();
    SendEndOfStream();
    CancelNotification();
    return NOERROR;
}

// This is called when we stop or are inactivated to clear the pending sample
// We release the media sample interface so that they can be allocated to the
// source filter again, unless of course we are changing state to inactive in
// which case GetBuffer will return an error. We must also reset the current
// media sample to NULL so that we know we do not currently have an image

HRESULT CBaseRenderer::ClearPendingSample()
{
    CAutoLock cRendererLock(&m_RendererLock);
    if (m_pMediaSample) {
m_pMediaSample->Release();
m_pMediaSample = NULL;
    }
    return NOERROR;
}

// Used to signal end of stream according to the sample end time

void CALLBACK EndOfStreamTimer(UINT uID,        // Timer identifier
       UINT uMsg,       // Not currently used
       DWORD dwUser,    // User information
       DWORD dw1,       // Windows reserved
       DWORD dw2)       // is also reserved
{
    CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser;
    NOTE1("EndOfStreamTimer called (%d)",uID);
    pRenderer->TimerCallback();
}

//  Do the timer callback work
void CBaseRenderer::TimerCallback()
{
    //  Lock for synchronization (but don't hold this lock when calling
    //  timeKillEvent)
    CAutoLock cRendererLock(&m_RendererLock);

    // See if we should signal end of stream now

    if (m_EndOfStreamTimer) {
m_EndOfStreamTimer = 0;
SendEndOfStream();
    }
}

// If we are at the end of the stream signal the filter graph but do not set
// the state flag back to FALSE. Once we drop off the end of the stream we
// leave the flag set (until a subsequent ResetEndOfStream). Each sample we
// get delivered will update m_SignalTime to be the last sample's end time.
// We must wait this long before signalling end of stream to the filtergraph

#define TIMEOUT_DELIVERYWAIT 50
#define TIMEOUT_RESOLUTION 10

HRESULT CBaseRenderer::SendEndOfStream()
{
    ASSERT(CritCheckIn(&m_RendererLock));
    if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) {
return NOERROR;
    }

    // If there is no clock then signal immediately
    if (m_pClock == NULL) {
return NotifyEndOfStream();
    }

    // How long into the future is the delivery time

    REFERENCE_TIME Signal = m_tStart + m_SignalTime;
    REFERENCE_TIME CurrentTime;
    m_pClock->GetTime(&CurrentTime);
    LONG Delay = LONG((Signal - CurrentTime) / 10000);

    // Dump the timing information to the debugger

    NOTE1("Delay until end of stream delivery %d",Delay);
    NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
    NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));

    // Wait for the delivery time to arrive

    if (Delay < TIMEOUT_DELIVERYWAIT) {
return NotifyEndOfStream();
    }

    // Signal a timer callback on another worker thread

    m_EndOfStreamTimer = timeSetEvent((UINT) Delay,       // Period of timer
      TIMEOUT_RESOLUTION, // Timer resolution
      EndOfStreamTimer,   // Callback function
      DWORD(this),        // Used information
      TIME_ONESHOT);      // Type of callback
    if (m_EndOfStreamTimer == 0) {
return NotifyEndOfStream();
    }
    return NOERROR;
}

// Signals EC_COMPLETE to the filtergraph manager

HRESULT CBaseRenderer::NotifyEndOfStream()
{
    CAutoLock cRendererLock(&m_RendererLock);
    ASSERT(m_bEOS == TRUE);
    ASSERT(m_bEOSDelivered == FALSE);
    ASSERT(m_EndOfStreamTimer == 0);

    // Has the filter changed state

    if (m_bStreaming == FALSE) {
ASSERT(m_EndOfStreamTimer == 0);
return NOERROR;
    }

    // Reset the end of stream timer
    m_EndOfStreamTimer = 0;

    // If we've been using the IMediaPosition interface, set it's start
    // and end media "times" to the stop position by hand.  This ensures
    // that we actually get to the end, even if the MPEG guestimate has
    // been bad or if the quality management dropped the last few frames

    if (m_pPosition) m_pPosition->EOS();
    m_bEOSDelivered = TRUE;
    NOTE("Sending EC_COMPLETE...");
    return NotifyEvent(EC_COMPLETE,S_OK,0);
}

// Reset the end of stream flag, this is typically called when we transfer to
// stopped states since that resets the current position back to the start so
// we will receive more samples or another EndOfStream if there aren't any. We
// keep two separate flags one to say we have run off the end of the stream
// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
// to the filter graph. We need the latter otherwise we can end up sending an
// EC_COMPLETE every time the source changes state and calls our EndOfStream

HRESULT CBaseRenderer::ResetEndOfStream()
{
    ResetEndOfStreamTimer();
    CAutoLock cRendererLock(&m_RendererLock);

    m_bEOS = FALSE;
    m_bEOSDelivered = FALSE;
    m_SignalTime = 0;

    return NOERROR;
}

// Kills any outstanding end of stream timer

void CBaseRenderer::ResetEndOfStreamTimer()
{
    ASSERT(CritCheckOut(&m_RendererLock));
    if (m_EndOfStreamTimer) {
timeKillEvent(m_EndOfStreamTimer);
m_EndOfStreamTimer = 0;
    }
}

// This is called when we start running so that we can schedule any pending
// image we have with the clock and display any timing information. If we
// don't have any sample but we have queued an EOS flag then we send it. If
// we do have a sample then we wait until that has been rendered before we
// signal the filter graph otherwise we may change state before it's done

HRESULT CBaseRenderer::StartStreaming()
{
    CAutoLock cRendererLock(&m_RendererLock);
    if (m_bStreaming == TRUE) {
return NOERROR;
    }

    // Reset the streaming times ready for running

    m_bStreaming = TRUE;
    timeBeginPeriod(1);
    OnStartStreaming();

    // There should be no outstanding advise
    ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
    ASSERT(CancelNotification() == S_FALSE);

    // If we have an EOS and no data then deliver it now

    if (m_pMediaSample == NULL) {
return SendEndOfStream();
    }

    // Have the data rendered

    ASSERT(m_pMediaSample);
    m_RenderEvent.Set();
    return NOERROR;
}

// This is called when we stop streaming so that we can set our internal flag
// indicating we are not now to schedule any more samples arriving. The state
// change methods in the filter implementation take care of cancelling any
// clock advise link we have set up and clearing any pending sample we have

HRESULT CBaseRenderer::StopStreaming()
{
    CAutoLock cRendererLock(&m_RendererLock);
    m_bEOSDelivered = FALSE;

    if (m_bStreaming == TRUE) {
m_bStreaming = FALSE;
OnStopStreaming();
timeEndPeriod(1);
    }
    return NOERROR;
}

// We have a boolean flag that is reset when we have signalled EC_REPAINT to
// the filter graph. We set this when we receive an image so that should any
// conditions arise again we can send another one. By having a flag we ensure
// we don't flood the filter graph with redundant calls. We do not set the
// event when we receive an EndOfStream call since there is no point in us
// sending further EC_REPAINTs. In particular the AutoShowWindow method and
// the DirectDraw object use this method to control the window repainting

void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
{
    CAutoLock cSampleLock(&m_RendererLock);
    m_bRepaintStatus = bRepaint;
}

// Pass the window handle to the upstream filter

void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd)
{
    IMediaEventSink *pSink;

    // Does the pin support IMediaEventSink
    HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink);
    if (SUCCEEDED(hr)) {
pSink->Notify(EC_NOTIFY_WINDOW,LONG(hwnd),0);
pSink->Release();
    }
    NotifyEvent(EC_NOTIFY_WINDOW,LONG(hwnd),0);
}

// Signal an EC_REPAINT to the filter graph. This can be used to have data
// sent to us. For example when a video window is first displayed it may
// not have an image to display, at which point it signals EC_REPAINT. The
// filtergraph will either pause the graph if stopped or if already paused
// it will call put_CurrentPosition of the current position. Setting the
// current position to itself has the stream flushed and the image resent

#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));

void CBaseRenderer::SendRepaint()
{
    CAutoLock cSampleLock(&m_RendererLock);
    ASSERT(m_pInputPin);

    // We should not send repaint notifications when...
    //    - An end of stream has been notified
    //    - Our input pin is being flushed
    //    - The input pin is not connected
    //    - We have aborted a video playback
    //    - There is a repaint already sent

    if (m_bAbort == FALSE) {
if (m_pInputPin->IsConnected() == TRUE) {
    if (m_pInputPin->IsFlushing() == FALSE) {
if (IsEndOfStream() == FALSE) {
    if (m_bRepaintStatus == TRUE) {
IPin *pPin = (IPin *) m_pInputPin;
NotifyEvent(EC_REPAINT,(long) pPin,0);
SetRepaintStatus(FALSE);
RLOG("Sending repaint");
    }
}
    }
}
    }
}

// When a video window detects a display change (WM_DISPLAYCHANGE message) it
// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
// filtergraph will stop everyone and reconnect our input pin. As we're then
// reconnected we can accept the media type that matches the new display mode
// since we may no longer be able to draw the current image type efficiently

BOOL CBaseRenderer::OnDisplayChange()
{
    // Ignore if we are not connected yet

    CAutoLock cSampleLock(&m_RendererLock);
    if (m_pInputPin->IsConnected() == FALSE) {
return FALSE;
    }

    RLOG("Notification of EC_DISPLAY_CHANGE");

    // Pass our input pin as parameter on the event

    IPin *pPin = (IPin *) m_pInputPin;
    m_pInputPin->AddRef();
    NotifyEvent(EC_DISPLAY_CHANGED,(long) pPin,0);
    SetAbortSignal(TRUE);
    ClearPendingSample();
    m_pInputPin->Release();

    return TRUE;
}

// Called just before we start drawing.
// Store the current time in m_trRenderStart to allow the rendering time to be
// logged.  Log the time stamp of the sample and how late it is (neg is early)

void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
{
#ifdef PERF
    REFERENCE_TIME trStart, trEnd;
    pMediaSample->GetTime(&trStart, &trEnd);

    MSR_INTEGER(m_idBaseStamp, (int)trStart);     // dump low order 32 bits

    m_pClock->GetTime(&m_trRenderStart);
    MSR_INTEGER(0, (int)m_trRenderStart);
    REFERENCE_TIME trStream;
    trStream = m_trRenderStart-m_tStart;     // convert reftime to stream time
    MSR_INTEGER(0,(int)trStream);

    const int trLate = (int)(trStream - trStart);
    MSR_INTEGER(m_idBaseAccuracy, trLate/10000);  // dump in mSec
#endif

} // OnRenderStart

// Called directly after drawing an image.
// calculate the time spent drawing and log it.

void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
{
#ifdef PERF
    REFERENCE_TIME trNow;
    m_pClock->GetTime(&trNow);
    MSR_INTEGER(0,(int)trNow);
    int t = (int)((trNow - m_trRenderStart)/10000);   // convert UNITS->msec
    MSR_INTEGER(m_idBaseRenderTime, t);
#endif
} // OnRenderEnd

// Constructor must be passed the base renderer object

CRendererInputPin::CRendererInputPin(CBaseRenderer *pRenderer,
     HRESULT *phr,
     LPCWSTR pPinName) :
    CBaseInputPin(NAME("Renderer pin"),
  pRenderer,
  &pRenderer->m_InterfaceLock,
  (HRESULT *) phr,
  pPinName)
{
    m_pRenderer = pRenderer;
    ASSERT(m_pRenderer);
}

// Signals end of data stream on the input pin

STDMETHODIMP CRendererInputPin::EndOfStream()
{
    CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
    CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);

    // Make sure we're streaming ok

    HRESULT hr = CheckStreaming();
    if (hr != NOERROR) {
return hr;
    }

    // Pass it onto the renderer

    hr = m_pRenderer->EndOfStream();
    if (SUCCEEDED(hr)) {
hr = CBaseInputPin::EndOfStream();
    }
    return hr;
}

// Signals start of flushing on the input pin - we do the final reset end of
// stream with the renderer lock unlocked but with the interface lock locked
// We must do this because we call timeKillEvent, our timer callback method
// has to take the renderer lock to serialise our state. Therefore holding a
// renderer lock when calling timeKillEvent could cause a deadlock condition

STDMETHODIMP CRendererInputPin::BeginFlush()
{
    CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
    {
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
CBaseInputPin::BeginFlush();
m_pRenderer->BeginFlush();
    }
    return m_pRenderer->ResetEndOfStream();
}

// Signals end of flushing on the input pin

STDMETHODIMP CRendererInputPin::EndFlush()
{
    CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
    CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);

    HRESULT hr = m_pRenderer->EndFlush();
    if (SUCCEEDED(hr)) {
hr = CBaseInputPin::EndFlush();
    }
    return hr;
}

// Pass the sample straight through to the renderer object

STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample)
{
    return m_pRenderer->Receive(pSample);
}

// Called when the input pin is disconnected

HRESULT CRendererInputPin::BreakConnect()
{
    HRESULT hr = m_pRenderer->BreakConnect();
    if (FAILED(hr)) {
return hr;
    }
    return CBaseInputPin::BreakConnect();
}

// Called when the input pin is connected

HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin)
{
    HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
    if (FAILED(hr)) {
return hr;
    }
    return CBaseInputPin::CompleteConnect(pReceivePin);
}

// Give the pin id of our one and only pin

STDMETHODIMP CRendererInputPin::QueryId(LPWSTR *Id)
{
    CheckPointer(Id,E_POINTER);

    *Id = (LPWSTR)CoTaskMemAlloc(8);
    if (*Id == NULL) {
       return E_OUTOFMEMORY;
    }
    lstrcpyW(*Id, L"In");
    return NOERROR;
}

// Will the filter accept this media type

HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
{
    return m_pRenderer->CheckMediaType(pmt);
}

// Called when we go paused or running

HRESULT CRendererInputPin::Active()
{
    return m_pRenderer->Active();
}

// Called when we go into a stopped state

HRESULT CRendererInputPin::Inactive()
{
    return m_pRenderer->Inactive();
}

// Tell derived classes about the media type agreed

HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt)
{
    HRESULT hr = CBaseInputPin::SetMediaType(pmt);
    if (FAILED(hr)) {
return hr;
    }
    return m_pRenderer->SetMediaType(pmt);
}

// We do not keep an event object to use when setting up a timer link with
// the clock but are given a pointer to one by the owning object through the
// SetNotificationObject method - this must be initialised before starting
// We can override the default quality management process to have it always
// draw late frames, this is currently done by having the following registry
// key (actually an INI key) called DrawLateFrames set to 1 (default is 0)

const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");

CBaseVideoRenderer::CBaseVideoRenderer(
      REFCLSID RenderClass, // CLSID for this renderer
      TCHAR *pName,         // Debug ONLY description
      LPUNKNOWN pUnk,       // Aggregated owner object
      HRESULT *phr) :       // General OLE return code

    CBaseRenderer(RenderClass,pName,pUnk,phr),
    m_cFramesDropped(0),
    m_cFramesDrawn(0),
    m_bSupplierHandlingQuality(FALSE)
{
    ResetStreamingTimes();

#ifdef PERF
    m_idTimeStamp       = MSR_REGISTER("Frame time stamp");
    m_idEarliness       = MSR_REGISTER("Earliness fudge");
    m_idTarget          = MSR_REGISTER("Target (mSec)");
    m_idSchLateTime     = MSR_REGISTER("mSec late when scheduled");
    m_idDecision        = MSR_REGISTER("Scheduler decision code");
    m_idQualityRate     = MSR_REGISTER("Quality rate sent");
    m_idQualityTime     = MSR_REGISTER("Quality time sent");
    m_idWaitReal        = MSR_REGISTER("Render wait");
    // m_idWait            = MSR_REGISTER("wait time recorded (msec)");
    m_idFrameAccuracy   = MSR_REGISTER("Frame accuracy (msecs)");
    m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
    //m_idSendQuality      = MSR_REGISTER("Processing Quality message");

    m_idRenderAvg       = MSR_REGISTER("Render draw time Avg");
    m_idFrameAvg        = MSR_REGISTER("FrameAvg");
    m_idWaitAvg         = MSR_REGISTER("WaitAvg");
    m_idDuration        = MSR_REGISTER("Duration");
    m_idThrottle        = MSR_REGISTER("Audio-video throttle wait");
    // m_idDebug           = MSR_REGISTER("Debug stuff");
#endif // PERF
} // Constructor

// Destructor is just a placeholder

CBaseVideoRenderer::~CBaseVideoRenderer()
{
    ASSERT(m_dwAdvise == 0);
}

// The timing functions in this class are called by the window object and by
// the renderer's allocator.
// The windows object calls timing functions as it receives media sample
// images for drawing using GDI.
// The allocator calls timing functions when it starts passing DCI/DirectDraw
// surfaces which are not rendered in the same way; The decompressor writes
// directly to the surface with no separate rendering, so those code paths
// call direct into us.  Since we only ever hand out DCI/DirectDraw surfaces
// when we have allocated one and only one image we know there cannot be any
// conflict between the two.
//
// We use timeGetTime to return the timing counts we use (since it's relative
// performance we are interested in rather than absolute compared to a clock)
// The window object sets the accuracy of the system clock (normally 1ms) by
// calling timeBeginPeriod/timeEndPeriod when it changes streaming states

// Reset all times controlling streaming.
// Set them so that
// 1. Frames will not initially be dropped
// 2. The first frame will definitely be drawn (achieved by saying that there
//    has not ben a frame drawn for a long time).

HRESULT CBaseVideoRenderer::ResetStreamingTimes()
{
    m_trLastDraw = -1000;     // set up as first frame since ages (1 sec) ago
    m_tStreamingStart = timeGetTime();
    m_trRenderAvg = 0;
    m_trFrameAvg = -1;        // -1000 fps == "unset"
    m_trDuration = 0;         // 0 - silly value
    m_trRenderLast = 0;
    m_trWaitAvg = 0;
    m_tRenderStart = 0;
    m_cFramesDrawn = 0;
    m_cFramesDropped = 0;
    m_iTotAcc = 0;
    m_iSumSqAcc = 0;
    m_iSumSqFrameTime = 0;
    m_trFrame = 0;          // hygeine - not really needed
    m_trLate = 0;           // hygeine - not really needed
    m_iSumFrameTime = 0;
    m_nNormal = 0;
    m_trEarliness = 0;
    m_trTarget = -300000;  // 30mSec early
    m_trThrottle = 0;
    m_trRememberStampForPerf = 0;

#ifdef PERF
    m_trRememberFrameForPerf = 0;
#endif

    return NOERROR;
} // ResetStreamingTimes

// Reset all times controlling streaming. Note that we're now streaming. We
// don't need to set the rendering event to have the source filter released
// as it is done during the Run processing. When we are run we immediately
// release the source filter thread and draw any image waiting (that image
// may already have been drawn once as a poster frame while we were paused)

HRESULT CBaseVideoRenderer::OnStartStreaming()
{
    ResetStreamingTimes();
    return NOERROR;
} // OnStartStreaming

// Called at end of streaming.  Fixes times for property page report

HRESULT CBaseVideoRenderer::OnStopStreaming()
{
    m_tStreamingStart = timeGetTime()-m_tStreamingStart;
    return NOERROR;
} // OnStopStreaming

// Called when we start waiting for a rendering event.
// Used to update times spent waiting and not waiting.

void CBaseVideoRenderer::OnWaitStart()
{
    MSR_START(m_idWaitReal);
} // OnWaitStart

// Called when we are awoken from the wait in the window OR by our allocator
// when it is hanging around until the next sample is due for rendering on a
// DCI/DirectDraw surface. We add the wait time into our rolling average.
// We grab the interface lock so that we're serialised with the application
// thread going through the run code - which in due course ends up calling
// ResetStreaming times - possibly as we run through this section of code

void CBaseVideoRenderer::OnWaitEnd()
{
#ifdef PERF
    MSR_STOP(m_idWaitReal);
    // for a perf build we want to know just exactly how late we REALLY are.
    // even if this means that we have to look at the clock again.

    REFERENCE_TIME trRealStream;     // the real time now expressed as stream time.
#if 0
    m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
#else
    // We will be discarding overflows like mad here!
    // This is wrong really because timeGetTime() can wrap but it's
    // only for PERF
    REFERENCE_TIME tr = timeGetTime()*10000;
    trRealStream = tr + m_llTimeOffset;
#endif
    trRealStream -= m_tStart;     // convert to stream time (this is a reftime)

    if (m_trRememberStampForPerf==0) {
// This is probably the poster frame at the start, and it is not scheduled
// in the usual way at all.  Just count it.  The rememberstamp gets set
// in ShouldDrawSampleNow, so this does bogus frame recording until we
// actually start playing.

PreparePerformanceData(0, 0);
    } else {
int trLate = (int)(trRealStream - m_trRememberStampForPerf);
int trFrame = (int)(tr - m_trRememberFrameForPerf);
PreparePerformanceData(trLate, trFrame);
    }
    m_trRememberFrameForPerf = tr;
#endif //PERF
} // OnWaitEnd

// Put data on one side that describes the lateness of the current frame.
// We don't yet know whether it will actually be drawn.  In direct draw mode,
// this decision is up to the filter upstream, and it could change its mind.
// The rules say that if it did draw it must call Receive().  One way or
// another we eventually get into either OnRenderStart or OnDirectRender and
// these both call RecordFrameLateness to update the statistics.

void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame)
{
    m_trLate = trLate;
    m_trFrame = trFrame;
} // PreparePerformanceData

// update the statistics:
// m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
// Note that because the properties page reports using these variables,
// 1. We need to be inside a critical section
// 2. They must all be updated together.  Updating the sums here and the count
// elsewhere can result in imaginary jitter (i.e. attempts to find square roots
// of negative numbers) in the property page code.

void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame)
{
    // Record how timely we are.
    int tLate = trLate/10000;

    // Best estimate of moment of appearing on the screen is average of
    // start and end draw times.  Here we have only the end time.  This may
    // tend to show us as spuriously late by up to 1/2 frame rate achieved.
    // Decoder probably monitors draw time.  We don't bother.
    MSR_INTEGER( m_idFrameAccuracy, tLate );

    // This is a hack - we can get frames that are ridiculously late
    // especially (at start-up) and they sod up the statistics.
    // So ignore things that are more than 1 sec off.
    if (tLate>1000 || tLate<-1000) {
if (m_cFramesDrawn<=1) {
    tLate = 0;
} else if (tLate>0) {
    tLate = 1000;
} else {
    tLate = -1000;
}
    }
    // The very first frame often has a bogus time, so I'm just
    // not going to count it into the statistics.   ???
    if (m_cFramesDrawn>1) {
m_iTotAcc += tLate;
m_iSumSqAcc += (tLate*tLate);
    }

    // calculate inter-frame time.  Doesn't make sense for first frame
    // second frame suffers from bogus first frame stamp.
    if (m_cFramesDrawn>2) {
int tFrame = trFrame/10000;    // convert to mSec else it overflows
// This is a hack.  It can overflow anyway (a pause can cause
// a very long inter-frame time) and it overflows at 2**31/10**7
// or about 215 seconds i.e. 3min 35sec
if (tFrame>1000||tFrame<0) tFrame = 1000;
m_iSumSqFrameTime += tFrame*tFrame;
ASSERT(m_iSumSqFrameTime>=0);
m_iSumFrameTime += tFrame;
    }
    ++m_cFramesDrawn;

} // RecordFrameLateness

void CBaseVideoRenderer::ThrottleWait()
{
    if (m_trThrottle>0) {
int iThrottle = m_trThrottle/10000;    // convert to mSec
MSR_INTEGER( m_idThrottle, iThrottle);
        DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle));
Sleep(iThrottle);
    } else {
        Sleep(0);
    }
} // ThrottleWait

// Whenever a frame is rendered it goes though either OnRenderStart
// or OnDirectRender.  Data that are generated during ShouldDrawSample
// are added to the statistics by calling RecordFrameLateness from both
// these two places.

// Called in place of OnRenderStart..OnRenderEnd
// When a DirectDraw image is drawn
void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample)
{
    int time = 0;
    m_trRenderAvg = 0;
    m_trRenderLast = 5000000;  // If we mode switch, we do NOT want this
       // to inhibit the new average getting going!
       // so we set it to half a second
    // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
    RecordFrameLateness(m_trLate, m_trFrame);
    ThrottleWait();
} // OnDirectRender

// Called just before we start drawing.  All we do is to get the current clock
// time (from the system) and return.  We have to store the start render time
// in a member variable because it isn't used until we complete the drawing
// The rest is just performance logging.

void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample)
{
    RecordFrameLateness(m_trLate, m_trFrame);
    m_tRenderStart = timeGetTime();
} // OnRenderStart

// Called directly after drawing an image.  We calculate the time spent in the
// drawing code and if this doesn't appear to have any odd looking spikes in
// it then we add it to the current average draw time.  Measurement spikes may
// occur if the drawing thread is interrupted and switched to somewhere else.

void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample)
{
    // The renderer time can vary erratically if we are interrupted so we do
    // some smoothing to help get more sensible figures out but even that is
    // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83

    int tr = (timeGetTime() - m_tRenderStart)*10000;   // convert mSec->UNITS
    if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
// DO_MOVING_AVG(m_trRenderAvg, tr);
m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
    }
    m_trRenderLast = tr;
    ThrottleWait();
} // OnRenderEnd

STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc)
{

    m_pQSink = piqc;

    return NOERROR;
} // SetSink

STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, Quality q)
{
    // NOTE:  We are NOT getting any locks here.  We could be called
    // asynchronously and possibly even on a time critical thread of
    // someone else's - so we do the minumum.  We only set one state
    // variable (an integer) and if that happens to be in the middle
    // of another thread reading it they will just get either the new
    // or the old value.  Locking would achieve no more than this.

    // It might be nice to check that we are being called from m_pGraph, but
    // it turns out to be a millisecond or so per throw!

    // This is heuristics, these numbers are aimed at being "what works"
    // rather than anything based on some theory.
    // We use a hyperbola because it's easy to calculate and it includes
    // a panic button asymptote (which we push off just to the left)
    // The throttling fits the following table (roughly)
    // Proportion   Throttle (msec)
    //     >=1000         0
    //        900         3
    //        800         7
    //        700        11
    //        600        17
    //        500        25
    //        400        35
    //        300        50
    //        200        72
    //        125       100
    //        100       112
    //         50       146
    //          0       200

    // (some evidence that we could go for a sharper kink - e.g. no throttling
    // until below the 750 mark - might give fractionally more frames on a
    // P60-ish machine).  The easy way to get these coefficients is to use
    // Renbase.xls follow the instructions therein using excel solver.

    if (q.Proportion>=1000) { m_trThrottle = 0; }
    else {
// The DWORD is to make quite sure I get unsigned arithmetic
// as the constant is between 2**31 and 2**32
m_trThrottle = -330000 + (388880000/(q.Proportion+167));
    }
    return NOERROR;
} // Notify

// Send a message to indicate what our supplier should do about quality.
// Theory:
// What a supplier wants to know is "is the frame I'm working on NOW
// going to be late?".
// F1 is the frame at the supplier (as above)
// Tf1 is the due time for F1
// T1 is the time at that point (NOW!)
// Tr1 is the time that f1 WILL actually be rendered
// L1 is the latency of the graph for frame F1 = Tr1-T1
// D1 (for delay) is how late F1 will be beyond its due time i.e.
// D1 = (Tr1-Tf1) which is what the supplier really wants to know.
// Unfortunately Tr1 is in the future and is unknown, so is L1
//
// We could estimate L1 by its value for a previous frame,
// L0 = Tr0-T0 and work off
// D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
// Rearranging terms:
// D1' = (T1-T0) + (Tr0-Tf1)
//       adding (Tf0-Tf0) and rearranging again:
//     = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
//     = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
// But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
// Late field in the quality message that we send.
// The other two terms just state what correction should be applied before
// using the lateness of F0 to predict the lateness of F1.
// (T1-T0) says how much time has actually passed (we have lost this much)
// (Tf1-Tf0) says how much time should have passed if we were keeping pace
// (we have gained this much).
//
// Suppliers should therefore work off:
//    Quality.Late + (T1-T0)  - (Tf1-Tf0)
// and see if this is "acceptably late" or even early (i.e. negative).
// They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
// the time stamps in the frames.  They get Quality.Late from us.
//

HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate,
REFERENCE_TIME trRealStream)
{
    Quality q;
    HRESULT hr;

    // If we are the main user of time, then report this as Flood/Dry.
    // If our suppliers are, then report it as Famine/Glut.
    //
    // We need to take action, but avoid hunting.  Hunting is caused by
    // 1. Taking too much action too soon and overshooting
    // 2. Taking too long to react (so averaging can CAUSE hunting).
    //
    // The reason why we use trLate as well as Wait is to reduce hunting;
    // if the wait time is coming down and about to go into the red, we do
    // NOT want to rely on some average which is only telling is that it used
    // to be OK once.

    q.TimeStamp = (REFERENCE_TIME)trRealStream;

    if (m_trFrameAvg<0) {
q.Type = Famine;      // guess
    }
    // Is the greater part of the time taken bltting or something else
    else if (m_trFrameAvg > 2*m_trRenderAvg) {
q.Type = Famine;                        // mainly other
    } else {
q.Type = Flood;                         // mainly bltting
    }

    q.Proportion = 1000;               // default

    if (m_trFrameAvg<0) {
// leave it alone - we don't know enough
    }
    else if ( trLate> 0 ) {
// try to catch up over the next second
// We could be Really, REALLY late, but rendering all the frames
// anyway, just because it's so cheap.

q.Proportion = 1000 - (int)((trLate)/(UNITS/1000));
if (q.Proportion<500) {
   q.Proportion = 500;      // don't go daft. (could've been negative!)
} else {
}

    } else if (  m_trWaitAvg>20000
      && trLate<-20000
      ){
// Go cautiously faster - aim at 2mSec wait.
if (m_trWaitAvg>=m_trFrameAvg) {
    // This can happen because of some fudges.
    // The waitAvg is how long we originally planned to wait
    // The frameAvg is more honest.
    // It means that we are spending a LOT of time waiting
    q.Proportion = 2000;    // double.
} else {
    if (m_trFrameAvg+20000 > m_trWaitAvg) {
q.Proportion
    = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
    } else {
// We're apparently spending more than the whole frame time waiting.
// Assume that the averages are slightly out of kilter, but that we
// are indeed doing a lot of waiting.  (This leg probably never
// happens, but the code avoids any potential divide by zero).
q.Proportion = 2000;
    }
}

if (q.Proportion>2000) {
    q.Proportion = 2000;    // don't go crazy.
}
    }

    // Tell the supplier how late frames are when they get rendered
    // That's how late we are now.
    // If we are in directdraw mode then the guy upstream can see the drawing
    // times and we'll just report on the start time.  He can figure out any
    // offset to apply.  If we are in DIB Section mode then we will apply an
    // extra offset which is half of our drawing time.  This is usually small
    // but can sometimes be the dominant effect.  For this we will use the
    // average drawing time rather than the last frame.  If the last frame took
    // a long time to draw and made us late, that's already in the lateness
    // figure.  We should not add it in again unless we expect the next frame
    // to be the same.  We don't, we expect the average to be a better shot.
    // In direct draw mode the RenderAvg will be zero.

    q.Late = trLate + m_trRenderAvg/2;

    // log what we're doing
    MSR_INTEGER(m_idQualityRate, q.Proportion);
    MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );

    // A specific sink interface may be set through IPin

    if (m_pQSink==NULL) {
// Get our input pin's peer.  We send quality management messages
// to any nominated receiver of these things (set in the IPin
// interface), or else to our source filter.

IQualityControl *pQC = NULL;
IPin *pOutputPin = m_pInputPin->GetConnected();
ASSERT(pOutputPin != NULL);

// And get an AddRef'd quality control interface

hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC);
if (SUCCEEDED(hr)) {
    m_pQSink = pQC;
}
    }
    if (m_pQSink) {
return m_pQSink->Notify(this,q);
    }

    return S_FALSE;

} // SendQuality

// We are called with a valid IMediaSample image to decide whether this is to
// be drawn or not.  There must be a reference clock in operation.
// Return S_OK if it is to be drawn Now (as soon as possible)
// Return S_FALSE if it is to be drawn when it's due
// Return an error if we want to drop it
// m_nNormal=-1 indicates that we dropped the previous frame and so this
// one should be drawn early.  Respect it and update it.
// Use current stream time plus a number of heuristics (detailed below)
// to make the decision

HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
REFERENCE_TIME *ptrStart,
REFERENCE_TIME *ptrEnd)
{

    // Don't call us unless there's a clock interface to synchronise with
    ASSERT(m_pClock);

    MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32));   // high order 32 bits
    MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart));         // low order 32 bits

    // We lose a bit of time depending on the monitor type waiting for the next
    // screen refresh.  On average this might be about 8mSec - so it will be
    // later than we think when the picture appears.  To compensate a bit
    // we bias the media samples by -8mSec i.e. 80000 UNITs.
    // We don't ever make a stream time negative (call it paranoia)
    if (*ptrStart>=80000) {
*ptrStart -= 80000;
*ptrEnd -= 80000;       // bias stop to to retain valid frame duration
    }

    // Cache the time stamp now.  We will want to compare what we did with what
    // we started with (after making the monitor allowance).
    m_trRememberStampForPerf = *ptrStart;

    // Get reference times (current and late)
    REFERENCE_TIME trRealStream;     // the real time now expressed as stream time.
    m_pClock->GetTime(&trRealStream);
#ifdef PERF
    // While the reference clock is expensive:
    // Remember the offset from timeGetTime and use that.
    // This overflows all over the place, but when we subtract to get
    // differences the overflows all cancel out.
    m_llTimeOffset = trRealStream-timeGetTime()*10000;
#endif
    trRealStream -= m_tStart;     // convert to stream time (this is a reftime)

    // We have to wory about two versions of "lateness".  The truth, which we
    // try to work out here and the one measured against m_trTarget which
    // includes long term feedback.  We report statistics against the truth
    // but for operational decisions we work to the target.
    // We use TimeDiff to make sure we get an integer because we
    // may actually be late (or more likely early if there is a big time
    // gap) by a very long time.
    const int trTrueLate = TimeDiff(trRealStream - *ptrStart);
    const int trLate = trTrueLate;

    MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);

    // Send quality control messages upstream, measured against target
    HRESULT hr = SendQuality(trLate, trRealStream);
    // Note: the filter upstream is allowed to this FAIL meaning "you do it".
    m_bSupplierHandlingQuality = (hr==S_OK);

    // Decision time!  Do we drop, draw when ready or draw immediately?

    const int trDuration = (int)(*ptrEnd - *ptrStart);
    {
// We need to see if the frame rate of the file has just changed.
// This would make comparing our previous frame rate with the current
// frame rate silly.  Hang on a moment though.  I've seen files
// where the frames vary between 33 and 34 mSec so as to average
// 30fps.  A minor variation like that won't hurt us.
int t = m_trDuration/32;
if (  trDuration > m_trDuration+t
   || trDuration < m_trDuration-t
   ) {
    // There's a major variation.  Reset the average frame rate to
    // exactly the current rate to disable decision 9002 for this frame,
    // and remember the new rate.
    m_trFrameAvg = trDuration;
    m_trDuration = trDuration;
}
    }

    MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
    MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
    MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
    MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
    MSR_INTEGER(m_idDuration, trDuration/10000);

#ifdef PERF
    if (S_OK==pMediaSample->IsDiscontinuity()) {
MSR_INTEGER(m_idDecision, 9000);
    }
#endif

    // Control the graceful slide back from slow to fast machine mode.
    // After a frame drop accept an early frame and set the earliness to here
    // If this frame is already later than the earliness then slide it to here
    // otherwise do the standard slide (reduce by about 12% per frame).
    // Note: earliness is normally NEGATIVE
    BOOL bJustDroppedFrame
= (  m_bSupplierHandlingQuality
  //  Can't use the pin sample properties because we might
  //  not be in Receive when we call this
  && (S_OK == pMediaSample->IsDiscontinuity())          // he just dropped one
  )
       || (m_nNormal==-1);                          // we just dropped one

    // Set m_trEarliness (slide back from slow to fast machine mode)
    if (trLate>0) {
m_trEarliness = 0;   // we are no longer in fast machine mode at all!
    } else if (  (trLate>=m_trEarliness) || bJustDroppedFrame) {
m_trEarliness = trLate;  // Things have slipped of their own accord
    } else {
m_trEarliness = m_trEarliness - m_trEarliness/8;  // graceful slide
    }

    // prepare the new wait average - but don't pollute the old one until
    // we have finished with it.
    int trWaitAvg;
    {
// We never mix in a negative wait.  This causes us to believe in fast machines
// slightly more.
int trL = trLate<0 ? -trLate : 0;
trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
    }

    int trFrame;
    {
REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
if (tr>10000000) {
    tr = 10000000;   // 1 second - arbitrarily.
}
trFrame = int(tr);
    }

    // We will DRAW this frame IF...
    if (
  // ...the time we are spending drawing is a small fraction of the total
  // observed inter-frame time so that dropping it won't help much.
  (3*m_trRenderAvg <= m_trFrameAvg)

 // ...or our supplier is NOT handling things and the next frame would
 // be less timely than this one or our supplier CLAIMS to be handling
 // things, and is now less than a full FOUR frames late.
       || ( m_bSupplierHandlingQuality
  ? (trLate <= trDuration*4)
  : (trLate+trLate < trDuration)
  )

  // ...or we are on average waiting for over eight milliseconds then
  // this may be just a glitch.  Draw it and we'll hope to catch up.
       || (m_trWaitAvg > 80000)

  // ...or we haven't drawn an image for over a second.  We will update
  // the display, which stops the video looking hung.
  // Do this regardless of how late this media sample is.
       || ((trRealStream - m_trLastDraw) > UNITS)

    ) {
HRESULT Result;

// We are going to play this frame.  We may want to play it early.
// We will play it early if we think we are in slow machine mode.
// If we think we are NOT in slow machine mode, we will still play
// it early by m_trEarliness as this controls the graceful slide back.
// and in addition we aim at being m_trTarget late rather than "on time".

BOOL bPlayASAP = FALSE;

// we will play it AT ONCE (slow machine mode) if...

    // ...we are playing catch-up
if ( bJustDroppedFrame) {
    bPlayASAP = TRUE;
    MSR_INTEGER(m_idDecision, 9001);
}

    // ...or if we are running below the true frame rate
    // exact comparisons are glitchy, for these measurements,
    // so add an extra 5% or so
else if (  (m_trFrameAvg > trDuration + trDuration/16)

   // It's possible to get into a state where we are losing ground, but
   // are a very long way ahead.  To avoid this or recover from it
   // we refuse to play early by more than 10 frames.
&& (trLate > - trDuration*10)
){
    bPlayASAP = TRUE;
    MSR_INTEGER(m_idDecision, 9002);
}
#if 0
    // ...or if we have been late and are less than one frame early
else if (  (trLate + trDuration > 0)
&& (m_trWaitAvg<=20000)
) {
    bPlayASAP = TRUE;
    MSR_INTEGER(m_idDecision, 9003);
}
#endif
// We will NOT play it at once if we are grossly early.  On very slow frame
// rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
// because we got starved (for instance by the net) and dropped one frame
// some time or other.  If we are more than 900mSec early, then wait.
if (trLate<-9000000) {
    bPlayASAP = FALSE;
}

if (bPlayASAP) {

    m_nNormal = 0;
    MSR_INTEGER(m_idDecision, 0);
    // When we are here, we are in slow-machine mode.  trLate may well
    // oscillate between negative and positive when the supplier is
    // dropping frames to keep sync.  We should not let that mislead
    // us into thinking that we have as much as zero spare time!
    // We just update with a zero wait.
    m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;

    // Assume that we draw it immediately.  Update inter-frame stats
    m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
#ifndef PERF
    // if this is NOT a perf build, then report what we know so far
    // without looking at the clock any more.  This assumes that we
    // actually wait for exactly the time we hope to.  it also reports
    // how close we get to the hacked up time stamps that we now have
    // rather than the ones we originally started with.  It will
    // therefore be a little optimistic.  However it's fast.
    PreparePerformanceData(trTrueLate, trFrame);
#endif
    m_trLastDraw = trRealStream;
    if (m_trEarliness > trLate) {
m_trEarliness = trLate;  // if we are actually early, this is neg
    }
    Result = S_OK;                   // Draw it now

} else {
    ++m_nNormal;
    // Set the average frame rate to EXACTLY the ideal rate.
    // If we are exiting slow-machine mode then we will have caught up
    // and be running ahead, so as we slide back to exact timing we will
    // have a longer than usual gap at this point.  If we record this
    // real gap then we'll think that we're running slow and go back
    // into slow-machine mode and vever get it straight.
    m_trFrameAvg = trDuration;
    MSR_INTEGER(m_idDecision, 1);

    // Play it early by m_trEarliness and by m_trTarget

    {
int trE = m_trEarliness;
                if (trE < -m_trFrameAvg) {
                    trE = -m_trFrameAvg;
                }
*ptrStart += trE;           // N.B. earliness is negative
    }

    int Delay = -trTrueLate;
    Result = Delay<=0 ? S_OK : S_FALSE;     // OK = draw now, FALSE = wait

    m_trWaitAvg = trWaitAvg;

    // Predict when it will actually be drawn and update frame stats

    if (Result==S_FALSE) {   // We are going to wait
trFrame = TimeDiff(*ptrStart-m_trLastDraw);
m_trLastDraw = *ptrStart;
    } else {
// trFrame is already = trRealStream-m_trLastDraw;
m_trLastDraw = trRealStream;
    }
#ifndef PERF
    int iAccuracy;
    if (Delay>0) {
// Report lateness based on when we intend to play it
iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf);
    } else {
// Report lateness based on playing it *now*.
iAccuracy = trTrueLate;     // trRealStream-RememberStampForPerf;
    }
    PreparePerformanceData(iAccuracy, trFrame);
#endif
}
return Result;
    }

    // We are going to drop this frame!
    // Of course in DirectDraw mode the guy upstream may draw it anyway.

    // This will probably give a large negative wack to the wait avg.
    m_trWaitAvg = trWaitAvg;

#ifdef PERF
    // Respect registry setting - debug only!
    if (m_bDrawLateFrames) {
       return S_OK;                        // draw it when it's ready
    }                                      // even though it's late.
#endif

    // We are going to drop this frame so draw the next one early
    // n.b. if the supplier is doing direct draw then he may draw it anyway
    // but he's doing something funny to arrive here in that case.

    MSR_INTEGER(m_idDecision, 2);
    m_nNormal = -1;
    return E_FAIL;                         // drop it

} // ShouldDrawSampleNow

// NOTE we're called by both the window thread and the source filter thread
// so we have to be protected by a critical section (locked before called)
// Also, when the window thread gets signalled to render an image, it always
// does so regardless of how late it is. All the degradation is done when we
// are scheduling the next sample to be drawn. Hence when we start an advise
// link to draw a sample, that sample's time will always become the last one
// drawn - unless of course we stop streaming in which case we cancel links

BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample)
{
    // We override ShouldDrawSampleNow to add quality management

    BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample);
    if (bDrawImage == FALSE) {
++m_cFramesDropped;
return FALSE;
    }

    // m_cFramesDrawn must NOT be updated here.  It has to be updated
    // in RecordFrameLateness at the same time as the other statistics.
    return TRUE;
}

// Implementation of IQualProp interface needed to support the property page
// This is how the property page gets the data out of the scheduler. We are
// passed into the constructor the owning object in the COM sense, this will
// either be the video renderer or an external IUnknown if we're aggregated.
// We initialise our CUnknown base class with this interface pointer. Then
// all we have to do is to override NonDelegatingQueryInterface to expose
// our IQualProp interface. The AddRef and Release are handled automatically
// by the base class and will be passed on to the appropriate outer object

STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(int *pcFramesDropped)
{
    CheckPointer(pcFramesDropped,E_POINTER);
    CAutoLock cVideoLock(&m_InterfaceLock);
    *pcFramesDropped = m_cFramesDropped;
    return NOERROR;
} // get_FramesDroppedInRenderer

// Set *pcFramesDrawn to the number of frames drawn since
// streaming started.

STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn)
{
    CheckPointer(pcFramesDrawn,E_POINTER);
    CAutoLock cVideoLock(&m_InterfaceLock);
    *pcFramesDrawn = m_cFramesDrawn;
    return NOERROR;
} // get_FramesDrawn

// Set iAvgFrameRate to the frames per hundred secs since
// streaming started.  0 otherwise.

STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate)
{
    CheckPointer(piAvgFrameRate,E_POINTER);
    CAutoLock cVideoLock(&m_InterfaceLock);

    int t;
    if (m_bStreaming) {
t = timeGetTime()-m_tStreamingStart;
    } else {
t = m_tStreamingStart;
    }

    if (t<=0) {
*piAvgFrameRate = 0;
ASSERT(m_cFramesDrawn == 0);
    } else {
// i is frames per hundred seconds
*piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
    }
    return NOERROR;
} // get_AvgFrameRate

// Set *piAvg to the average sync offset since streaming started
// in mSec.  The sync offset is the time in mSec between when the frame
// should have been drawn and when the frame was actually drawn.

STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset( int *piAvg)
{
    CheckPointer(piAvg,E_POINTER);
    CAutoLock cVideoLock(&m_InterfaceLock);

    if (NULL==m_pClock) {
*piAvg = 0;
return NOERROR;
    }

    // Note that we didn't gather the stats on the first frame
    // so we use m_cFramesDrawn-1 here
    if (m_cFramesDrawn<=1) {
*piAvg = 0;
    } else {
*piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1));
    }
    return NOERROR;
} // get_AvgSyncOffset

// To avoid dragging in the maths library - a cheap
// approximate integer square root.

// We do this by getting a starting guess which is between 1
// and 2 times too large, followed by THREE iterations of
// Newton Raphson.  (That will give accuracy to the nearest mSec
// for the range in question - roughly 0..1000)
//
// It would be faster to use a linear interpolation and ONE NR, but
// who cares.  If anyone does - the best linear interpolation is
// to approximates sqrt(x) by
// y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
// 0r y = x*0.41421 + 0.59467
// This minimises the maximal error in the range in question.
// (error is about +0.008883 and then one NR will give error .0000something
// (Of course these are integers, so you can't just multiply by 0.41421
// you'd have to do some sort of MulDiv).
// Anyone wanna check my maths?  (This is only for a property display!)

int isqrt(int x)
{
    int s = 1;
    // Make s an initial guess for sqrt(x)
    if (x > 0x40000000) {
       s = 0x8000;     // prevent any conceivable closed loop
    } else {
while (s*s<x) {    // loop cannot possible go more than 31 times
    s = 2*s;       // normally it goes about 6 times
}
// Three NR iterations.
if (x==0) {
   s= 0; // Wouldn't it be tragic to divide by zero whenever our
 // accuracy was perfect!
} else {
    s = (s*s+x)/(2*s);
    if (s>=0) s = (s*s+x)/(2*s);
    if (s>=0) s = (s*s+x)/(2*s);
}
    }
    return s;
}

//
//  Do estimates for standard deviations for per-frame
//  statistics
//
HRESULT CBaseVideoRenderer::GetStdDev(
    int nSamples,
    int *piResult,
    LONGLONG llSumSq,
    LONGLONG iTot
)
{
    CheckPointer(piResult,E_POINTER);
    CAutoLock cVideoLock(&m_InterfaceLock);

    if (NULL==m_pClock) {
*piResult = 0;
return NOERROR;
    }

    // If S is the Sum of the Squares of observations and
    //    T the Total (i.e. sum) of the observations and there were
    //    N observations, then an estimate of the standard deviation is
    //      sqrt( (S - T**2/N) / (N-1) )

    if (nSamples<=1) {
*piResult = 0;
    } else {
LONGLONG x;
// First frames have bogus stamps, so we get no stats for them
// So we need 2 frames to get 1 datum, so N is cFramesDrawn-1

// so we use m_cFramesDrawn-1 here
x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0);
x = x / (nSamples-1);
ASSERT(x>=0);
*piResult = isqrt((LONG)x);
    }
    return NOERROR;
}

// Set *piDev to the standard deviation in mSec of the sync offset
// of each frame since streaming started.

STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset( int *piDev)
{
    // First frames have bogus stamps, so we get no stats for them
    // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
    return GetStdDev(m_cFramesDrawn - 1,
     piDev,
     m_iSumSqAcc,
     m_iTotAcc);
} // get_DevSyncOffset

// Set *piJitter to the standard deviation in mSec of the inter-frame time
// of frames since streaming started.

STDMETHODIMP CBaseVideoRenderer::get_Jitter( int *piJitter)
{
    // First frames have bogus stamps, so we get no stats for them
    // So second frame gives bogus inter-frame time
    // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
    return GetStdDev(m_cFramesDrawn - 2,
     piJitter,
     m_iSumSqFrameTime,
     m_iSumFrameTime);
} // get_Jitter

// Overidden to return our IQualProp interface

STDMETHODIMP
CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,VOID **ppv)
{
    // We return IQualProp and delegate everything else

    if (riid == IID_IQualProp) {
return GetInterface( (IQualProp *)this, ppv);
    } else if (riid == IID_IQualityControl) {
return GetInterface( (IQualityControl *)this, ppv);
    }
    return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
}

// Override JoinFilterGraph so that, just before leaving
// the graph we can send an EC_WINDOW_DESTROYED event

STDMETHODIMP
CBaseVideoRenderer::JoinFilterGraph(IFilterGraph *pGraph,LPCWSTR pName)
{
    // Since we send EC_ACTIVATE, we also need to ensure
    // we send EC_WINDOW_DESTROYED or the resource manager may be
    // holding us as a focus object
    if (!pGraph && m_pGraph) {

// We were in a graph and now we're not
// Do this properly in case we are aggregated
IBaseFilter* pFilter;
QueryInterface(IID_IBaseFilter,(void **) &pFilter);
NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0);
pFilter->Release();
    }
    return CBaseFilter::JoinFilterGraph(pGraph, pName);
}

// This removes a large number of level 4 warnings from the
// Microsoft compiler which in this case are not very useful
#pragma warning(disable: 4514)


RENBASE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Generic ActiveX base renderer class, December 1995

#ifndef __RENBASE__
#define __RENBASE__

// Forward class declarations

class CBaseRenderer;
class CBaseVideoRenderer;
class CRendererInputPin;

// This is our input pin class that channels calls to the renderer

class CRendererInputPin : public CBaseInputPin
{
protected:

    CBaseRenderer *m_pRenderer;

public:

    CRendererInputPin(CBaseRenderer *pRenderer,
                      HRESULT *phr,
                      LPCWSTR Name);

    // Overriden from the base pin classes

    HRESULT BreakConnect();
    HRESULT CompleteConnect(IPin *pReceivePin);
    HRESULT SetMediaType(const CMediaType *pmt);
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT Active();
    HRESULT Inactive();

    // Add rendering behaviour to interface functions

    STDMETHODIMP QueryId(LPWSTR *Id);
    STDMETHODIMP EndOfStream();
    STDMETHODIMP BeginFlush();
    STDMETHODIMP EndFlush();
    STDMETHODIMP Receive(IMediaSample *pMediaSample);

    // Helper
    IMemAllocator inline *Allocator() const
    {
        return m_pAllocator;
    }
};

// Main renderer class that handles synchronisation and state changes

class CBaseRenderer : public CBaseFilter
{
protected:

    friend class CRendererInputPin;

    friend void CALLBACK EndOfStreamTimer(UINT uID,      // Timer identifier
                                          UINT uMsg,     // Not currently used
                                          DWORD dwUser,  // User information
                                          DWORD dw1,     // Windows reserved
                                          DWORD dw2);    // Is also reserved

    CRendererPosPassThru *m_pPosition;  // Media seeking pass by object
    CAMEvent m_RenderEvent;             // Used to signal timer events
    CAMEvent m_ThreadSignal;            // Signalled to release worker thread
    CAMEvent m_evComplete;              // Signalled when state complete
    BOOL m_bAbort;                      // Stop us from rendering more data
    BOOL m_bStreaming;                  // Are we currently streaming
    DWORD m_dwAdvise;                   // Timer advise cookie
    IMediaSample *m_pMediaSample;       // Current image media sample
    BOOL m_bEOS;                        // Any more samples in the stream
    BOOL m_bEOSDelivered;               // Have we delivered an EC_COMPLETE
    CRendererInputPin *m_pInputPin;     // Our renderer input pin object
    CCritSec m_InterfaceLock;           // Critical section for interfaces
    CCritSec m_RendererLock;            // Controls access to internals
    IQualityControl * m_pQSink;         // QualityControl sink
    BOOL m_bRepaintStatus;              // Can we signal an EC_REPAINT
    //  Avoid some deadlocks by tracking filter during stop
    volatile BOOL  m_bInReceive;        // Inside Receive between PrepareReceive
                                        // And actually processing the sample
    REFERENCE_TIME m_SignalTime;        // Time when we signal EC_COMPLETE
    UINT m_EndOfStreamTimer;            // Used to signal end of stream

public:

    CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
                  TCHAR *pName,         // Debug ONLY description
                  LPUNKNOWN pUnk,       // Aggregated owner object
                  HRESULT *phr);        // General OLE return code

    ~CBaseRenderer();

    // Overriden to say what interfaces we support and where

    virtual HRESULT GetMediaPositionInterface(REFIID riid,void **ppv);
    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **);

    virtual HRESULT SourceThreadCanWait(BOOL bCanWait);

#ifdef DEBUG
    // Debug only dump of the renderer state
    void DisplayRendererState();
#endif
    virtual HRESULT WaitForRenderTime();
    virtual HRESULT CompleteStateChange(FILTER_STATE OldState);

    // Return internal information about this filter

    BOOL IsEndOfStream() { return m_bEOS; };
    BOOL IsEndOfStreamDelivered() { return m_bEOSDelivered; };
    BOOL IsStreaming() { return m_bStreaming; };
    void SetAbortSignal(BOOL bAbort) { m_bAbort = bAbort; };
    virtual void OnReceiveFirstSample(IMediaSample *pMediaSample) { };
    CAMEvent *GetRenderEvent() { return &m_RenderEvent; };

    // Permit access to the transition state

    void Ready() { m_evComplete.Set(); };
    void NotReady() { m_evComplete.Reset(); };
    BOOL CheckReady() { return m_evComplete.Check(); };

    virtual int GetPinCount();
    virtual CBasePin *GetPin(int n);
    FILTER_STATE GetRealState();
    void SendRepaint();
    void SendNotifyWindow(IPin *pPin,HWND hwnd);
    BOOL OnDisplayChange();
    void SetRepaintStatus(BOOL bRepaint);

    // Override the filter and pin interface functions

    STDMETHODIMP Stop();
    STDMETHODIMP Pause();
    STDMETHODIMP Run(REFERENCE_TIME StartTime);
    STDMETHODIMP GetState(DWORD dwMSecs,FILTER_STATE *State);
    STDMETHODIMP FindPin(LPCWSTR Id, IPin **ppPin);

    // These are available for a quality management implementation

    virtual void OnRenderStart(IMediaSample *pMediaSample);
    virtual void OnRenderEnd(IMediaSample *pMediaSample);
    virtual HRESULT OnStartStreaming() { return NOERROR; };
    virtual HRESULT OnStopStreaming() { return NOERROR; };
    virtual void OnWaitStart() { };
    virtual void OnWaitEnd() { };
    virtual void PrepareRender() { };

#ifdef PERF
    REFERENCE_TIME m_trRenderStart; // Just before we started drawing
                                    // Set in OnRenderStart, Used in OnRenderEnd
    int m_idBaseStamp;              // MSR_id for frame time stamp
    int m_idBaseRenderTime;         // MSR_id for true wait time
    int m_idBaseAccuracy;           // MSR_id for time frame is late (int)
#endif

    // Quality management implementation for scheduling rendering

    virtual BOOL ScheduleSample(IMediaSample *pMediaSample);
    virtual HRESULT GetSampleTimes(IMediaSample *pMediaSample,
                                   REFERENCE_TIME *pStartTime,
                                   REFERENCE_TIME *pEndTime);

    virtual HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample,
                                        REFERENCE_TIME *ptrStart,
                                        REFERENCE_TIME *ptrEnd);

    // Lots of end of stream complexities

    void TimerCallback();
    void ResetEndOfStreamTimer();
    HRESULT NotifyEndOfStream();
    virtual HRESULT SendEndOfStream();
    virtual HRESULT ResetEndOfStream();
    virtual HRESULT EndOfStream();

    // Rendering is based around the clock

    void SignalTimerFired();
    virtual HRESULT CancelNotification();
    virtual HRESULT ClearPendingSample();

    // Called when the filter changes state

    virtual HRESULT Active();
    virtual HRESULT Inactive();
    virtual HRESULT StartStreaming();
    virtual HRESULT StopStreaming();
    virtual HRESULT BeginFlush();
    virtual HRESULT EndFlush();

    // Deal with connections and type changes

    virtual HRESULT BreakConnect();
    virtual HRESULT SetMediaType(const CMediaType *pmt);
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    // These look after the handling of data samples

    virtual HRESULT PrepareReceive(IMediaSample *pMediaSample);
    virtual HRESULT Receive(IMediaSample *pMediaSample);
    virtual BOOL HaveCurrentSample();
    virtual IMediaSample *GetCurrentSample();
    virtual HRESULT Render(IMediaSample *pMediaSample);

    // Derived classes MUST override these
    virtual HRESULT DoRenderSample(IMediaSample *pMediaSample) PURE;
    virtual HRESULT CheckMediaType(const CMediaType *) PURE;

    // Helper
    void WaitForReceiveToComplete();
};

// CBaseVideoRenderer is a renderer class (see its ancestor class) and
// it handles scheduling of media samples so that they are drawn at the
// correct time by the reference clock.  It implements a degradation
// strategy.  Possible degradation modes are:
//    Drop frames here (only useful if the drawing takes significant time)
//    Signal supplier (upstream) to drop some frame(s) - i.e. one-off skip.
//    Signal supplier to change the frame rate - i.e. ongoing skipping.
//    Or any combination of the above.
// In order to determine what's useful to try we need to know what's going
// on.  This is done by timing various operations (including the supplier).
// This timing is done by using timeGetTime as it is accurate enough and
// usually cheaper than calling the reference clock.  It also tells the
// truth if there is an audio break and the reference clock stops.
// We provide a number of public entry points (named OnXxxStart, OnXxxEnd)
// which the rest of the renderer calls at significant moments.  These do
// the timing.

// the number of frames that the sliding averages are averaged over.
// the rule is (1024*NewObservation + (AVGPERIOD-1) * PreviousAverage)/AVGPERIOD
#define AVGPERIOD 4
#define DO_MOVING_AVG(avg,obs) (avg = (1024*obs + (AVGPERIOD-1)*avg)/AVGPERIOD)
// Spot the bug in this macro - I can't. but it doesn't work!

class CBaseVideoRenderer : public CBaseRenderer,    // Base renderer class
                           public IQualProp,        // Property page guff
                           public IQualityControl   // Allow throttling
{
protected:

    // Hungarian:
    //     tFoo is the time Foo in mSec (beware m_tStart from filter.h)
    //     trBar is the time Bar by the reference clock

    //******************************************************************
    // State variables to control synchronisation
    //******************************************************************

    // Control of sending Quality messages.  We need to know whether
    // we are in trouble (e.g. frames being dropped) and where the time
    // is being spent.

    // When we drop a frame we play the next one early.
    // The frame after that is likely to wait before drawing and counting this
    // wait as spare time is unfair, so we count it as a zero wait.
    // We therefore need to know whether we are playing frames early or not.

    int m_nNormal;                  // The number of consecutive frames
                                    // drawn at their normal time (not early)
                                    // -1 means we just dropped a frame.

#ifdef PERF
    BOOL m_bDrawLateFrames;         // Don't drop any frames (debug and I'm
                                    // not keen on people using it!)
#endif

    BOOL m_bSupplierHandlingQuality;// The response to Quality messages says
                                    // our supplier is handling things.
                                    // We will allow things to go extra late
                                    // before dropping frames.  We will play
                                    // very early after he has dropped one.

    // Control of scheduling, frame dropping etc.
    // We need to know where the time is being spent so as to tell whether
    // we should be taking action here, signalling supplier or what.
    // The variables are initialised to a mode of NOT dropping frames.
    // They will tell the truth after a few frames.
    // We typically record a start time for an event, later we get the time
    // again and subtract to get the elapsed time, and we average this over
    // a few frames.  The average is used to tell what mode we are in.

    // Although these are reference times (64 bit) they are all DIFFERENCES
    // between times which are small.  An int will go up to 214 secs before
    // overflow.  Avoiding 64 bit multiplications and divisions seems
    // worth while.

    // Audio-video throttling.  If the user has turned up audio quality
    // very high (in principle it could be any other stream, not just audio)
    // then we can receive cries for help via the graph manager.  In this case
    // we put in a wait for some time after rendering each frame.
    int m_trThrottle;

    // The time taken to render (i.e. BitBlt) frames controls which component
    // needs to degrade.  If the blt is expensive, the renderer degrades.
    // If the blt is cheap it's done anyway and the supplier degrades.
    int m_trRenderAvg;              // Time frames are taking to blt
    int m_trRenderLast;             // Time for last frame blt
    int m_tRenderStart;             // Just before we started drawing (mSec)
                                    // derived from timeGetTime.

    // When frames are dropped we will play the next frame as early as we can.
    // If it was a false alarm and the machine is fast we slide gently back to
    // normal timing.  To do this, we record the offset showing just how early
    // we really are.  This will normally be negative meaning early or zero.
    int m_trEarliness;

    // Target provides slow long-term feedback to try to reduce the
    // average sync offset to zero.  Whenever a frame is actually rendered
    // early we add a msec or two, whenever late we take off a few.
    // We add or take off 1/32 of the error time.
    // Eventually we should be hovering around zero.  For a really bad case
    // where we were (say) 300mSec off, it might take 100 odd frames to
    // settle down.  The rate of change of this is intended to be slower
    // than any other mechanism in Quartz, thereby avoiding hunting.
    int m_trTarget;

    // The proportion of time spent waiting for the right moment to blt
    // controls whether we bother to drop a frame or whether we reckon that
    // we're doing well enough that we can stand a one-frame glitch.
    int m_trWaitAvg;                // Average of last few wait times
                                    // (actually we just average how early
                                    // we were).  Negative here means LATE.

    // The average inter-frame time.
    // This is used to calculate the proportion of the time used by the
    // three operations (supplying us, waiting, rendering)
    int m_trFrameAvg;               // Average inter-frame time
    int m_trDuration;               // duration of last frame.

#ifdef PERF
    // Performance logging identifiers
    int m_idTimeStamp;              // MSR_id for frame time stamp
    int m_idEarliness;              // MSR_id for earliness fudge
    int m_idTarget;                 // MSR_id for Target fudge
    int m_idWaitReal;               // MSR_id for true wait time
    int m_idWait;                   // MSR_id for wait time recorded
    int m_idFrameAccuracy;          // MSR_id for time frame is late (int)
    int m_idRenderAvg;              // MSR_id for Render time recorded (int)
    int m_idSchLateTime;            // MSR_id for lateness at scheduler
    int m_idQualityRate;            // MSR_id for Quality rate requested
    int m_idQualityTime;            // MSR_id for Quality time requested
    int m_idDecision;               // MSR_id for decision code
    int m_idDuration;               // MSR_id for duration of a frame
    int m_idThrottle;               // MSR_id for audio-video throttling
    //int m_idDebug;                  // MSR_id for trace style debugging
    //int m_idSendQuality;          // MSR_id for timing the notifications per se
#endif // PERF
    REFERENCE_TIME m_trRememberStampForPerf;  // original time stamp of frame
                                              // with no earliness fudges etc.
#ifdef PERF
    REFERENCE_TIME m_trRememberFrameForPerf;  // time when previous frame rendered

    // debug...
    int m_idFrameAvg;
    int m_idWaitAvg;
#endif

    // PROPERTY PAGE
    // This has edit fields that show the user what's happening
    // These member variables hold these counts.

    int m_cFramesDropped;           // cumulative frames dropped IN THE RENDERER
    int m_cFramesDrawn;             // Frames since streaming started seen BY THE
                                    // RENDERER (some may be dropped upstream)

    // Next two support average sync offset and standard deviation of sync offset.
    LONGLONG m_iTotAcc;                  // Sum of accuracies in mSec
    LONGLONG m_iSumSqAcc;           // Sum of squares of (accuracies in mSec)

    // Next two allow jitter calculation.  Jitter is std deviation of frame time.
    REFERENCE_TIME m_trLastDraw;    // Time of prev frame (for inter-frame times)
    LONGLONG m_iSumSqFrameTime;     // Sum of squares of (inter-frame time in mSec)
    LONGLONG m_iSumFrameTime;            // Sum of inter-frame times in mSec

    // To get performance statistics on frame rate, jitter etc, we need
    // to record the lateness and inter-frame time.  What we actually need are the
    // data above (sum, sum of squares and number of entries for each) but the data
    // is generated just ahead of time and only later do we discover whether the
    // frame was actually drawn or not.  So we have to hang on to the data
    int m_trLate;                   // hold onto frame lateness
    int m_trFrame;                  // hold onto inter-frame time

    int m_tStreamingStart;          // if streaming then time streaming started
                                    // else time of last streaming session
                                    // used for property page statistics
#ifdef PERF
    LONGLONG m_llTimeOffset;        // timeGetTime()*10000+m_llTimeOffset==ref time
#endif

public:

    CBaseVideoRenderer(REFCLSID RenderClass, // CLSID for this renderer
                       TCHAR *pName,         // Debug ONLY description
                       LPUNKNOWN pUnk,       // Aggregated owner object
                       HRESULT *phr);        // General OLE return code

    ~CBaseVideoRenderer();

    // IQualityControl methods - Notify allows audio-video throttling

    STDMETHODIMP SetSink( IQualityControl * piqc);
    STDMETHODIMP Notify( IBaseFilter * pSelf, Quality q);

    // These provide a full video quality management implementation

    void OnRenderStart(IMediaSample *pMediaSample);
    void OnRenderEnd(IMediaSample *pMediaSample);
    void OnWaitStart();
    void OnWaitEnd();
    HRESULT OnStartStreaming();
    HRESULT OnStopStreaming();
    void ThrottleWait();

    // Handle the statistics gathering for our quality management

    void PreparePerformanceData(int trLate, int trFrame);
    virtual void RecordFrameLateness(int trLate, int trFrame);
    virtual void OnDirectRender(IMediaSample *pMediaSample);
    virtual HRESULT ResetStreamingTimes();
    BOOL ScheduleSample(IMediaSample *pMediaSample);
    HRESULT ShouldDrawSampleNow(IMediaSample *pMediaSample,
                                REFERENCE_TIME *ptrStart,
                                REFERENCE_TIME *ptrEnd);

    virtual HRESULT SendQuality(REFERENCE_TIME trLate, REFERENCE_TIME trRealStream);
    STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);

    //
    //  Do estimates for standard deviations for per-frame
    //  statistics
    //
    //  *piResult = (llSumSq - iTot * iTot / m_cFramesDrawn - 1) /
    //                            (m_cFramesDrawn - 2)
    //  or 0 if m_cFramesDrawn <= 3
    //
    HRESULT GetStdDev(
        int nSamples,
        int *piResult,
        LONGLONG llSumSq,
        LONGLONG iTot
    );
public:

    // IQualProp property page support

    STDMETHODIMP get_FramesDroppedInRenderer(int *cFramesDropped);
    STDMETHODIMP get_FramesDrawn(int *pcFramesDrawn);
    STDMETHODIMP get_AvgFrameRate(int *piAvgFrameRate);
    STDMETHODIMP get_Jitter(int *piJitter);
    STDMETHODIMP get_AvgSyncOffset(int *piAvg);
    STDMETHODIMP get_DevSyncOffset(int *piDev);

    // Implement an IUnknown interface and expose IQualProp

    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,VOID **ppv);
};

#endif // __RENBASE__


SCHEDULE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//  SCHEDULE.CPP

#include <streams.h>

// DbgLog values (all on LOG_TIMING):
//
// 2 for schedulting, firing and shunting of events
// 3 for wait delays and wake-up times of event thread
// 4 for details of whats on the list when the thread awakes

/* Construct & destructors */

CAMSchedule::CAMSchedule( HANDLE ev )
: CBaseObject(TEXT("CAMSchedule"))
, head(&z, 0), z(0, MAX_TIME)
, m_dwNextCookie(0), m_dwAdviseCount(0)
, m_pAdviseCache(0), m_dwCacheCount(0)
, m_ev( ev )
{
    head.m_dwAdviseCookie = z.m_dwAdviseCookie = 0;
}

CAMSchedule::~CAMSchedule()
{
    m_Serialize.Lock();

    // Delete cache
    CAdvisePacket * p = m_pAdviseCache;
    while (p)
    {
        CAdvisePacket *const p_next = p->m_next;
        delete p;
        p = p_next;
    }

    ASSERT( m_dwAdviseCount == 0 );
    // Better to be safe than sorry
    if ( m_dwAdviseCount > 0 )
    {
        DumpLinkedList();
        while ( !head.m_next->IsZ() )
        {
            head.DeleteNext();
            --m_dwAdviseCount;
        }
    }

    // If, in the debug version, we assert twice, it means, not only
    // did we have left over advises, but we have also let m_dwAdviseCount
    // get out of sync. with the number of advises actually on the list.
    ASSERT( m_dwAdviseCount == 0 );

    m_Serialize.Unlock();
}

/* Public methods */

DWORD CAMSchedule::GetAdviseCount()
{
    // No need to lock, m_dwAdviseCount is 32bits & declared volatile
    return m_dwAdviseCount;
}

REFERENCE_TIME CAMSchedule::GetNextAdviseTime()
{
    CAutoLock lck(&m_Serialize); // Need to stop the linked list from changing
    return head.m_next->m_rtEventTime;
}

DWORD CAMSchedule::AddAdvisePacket
( const REFERENCE_TIME & time1
, const REFERENCE_TIME & time2
, HANDLE h, BOOL periodic
)
{
    // Since we use MAX_TIME as a sentry, we can't afford to
    // schedule a notification at MAX_TIME
    ASSERT( time1 < MAX_TIME );
    DWORD Result;
    CAdvisePacket * p;

    m_Serialize.Lock();

    if (m_pAdviseCache)
    {
        p = m_pAdviseCache;
        m_pAdviseCache = p->m_next;
        --m_dwCacheCount;
    }
    else
    {
        p = new CAdvisePacket();
    }
    if (p)
    {
        p->m_rtEventTime = time1; p->m_rtPeriod = time2;
        p->m_hNotify = h; p->m_bPeriodic = periodic;
        Result = AddAdvisePacket( p );
    }
    else Result = 0;

    m_Serialize.Unlock();

    return Result;
}

HRESULT CAMSchedule::Unadvise(DWORD dwAdviseCookie)
{
    HRESULT hr = S_FALSE;
    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;
    m_Serialize.Lock();
    while ( p_n = p_prev->Next() ) // The Next() method returns NULL when it hits z
    {
        if ( p_n->m_dwAdviseCookie == dwAdviseCookie )
        {
            Delete( p_prev->RemoveNext() );
            --m_dwAdviseCount;
            hr = S_OK;
    // Having found one cookie that matches, there should be no more
            #ifdef DEBUG
       while (p_n = p_prev->Next())
               {
                   ASSERT(p_n->m_dwAdviseCookie != dwAdviseCookie);
                   p_prev = p_n;
               }
            #endif
            break;
        }
        p_prev = p_n;
    };
    m_Serialize.Unlock();
    return hr;
}

REFERENCE_TIME CAMSchedule::Advise( const REFERENCE_TIME & rtTime )
{
    REFERENCE_TIME  rtNextTime;
    CAdvisePacket * pAdvise;

    DbgLog((LOG_TIMING, 2,
        TEXT("CAMSchedule::Advise( %lu ms )"), ULONG(rtTime / (UNITS / MILLISECONDS))));

    m_Serialize.Lock();

    #ifdef DEBUG
        if (DbgCheckModuleLevel(LOG_TIMING, 4)) DumpLinkedList();
    #endif

    REFERENCE_TIME rtLate, rtPrevLate = MAX_TIME;
    while ( (rtLate = rtTime - (rtNextTime = (pAdvise=head.m_next)->m_rtEventTime)) >= 0 )
    {
        ASSERT(pAdvise->m_dwAdviseCookie); // If this is zero, its the head or the tail!!
        ASSERT( rtLate <= rtPrevLate );    // If we dispatch several, the later ones should
                                           // not be as late as the earlier ones.  The ASSERT
                                           // therefore is checking that our sheduling logic
                                           // placed the packet in the right place.
rtPrevLate = rtLate;
        rtLate /= 10000;
        DbgLog((LOG_TIMING, 2,
            TEXT("CAMSchedule::Advise() Dispatching advise %lu for time stamp: %lu ms (%lu ms late)"),
      pAdvise->m_dwAdviseCookie, ULONG(pAdvise->m_rtEventTime / (UNITS / MILLISECONDS)), ULONG(rtLate) ));

        ASSERT(pAdvise->m_hNotify != INVALID_HANDLE_VALUE);

        if (pAdvise->m_bPeriodic == TRUE)
        {
            EXECUTE_ASSERT(ReleaseSemaphore(pAdvise->m_hNotify,1,NULL));
            pAdvise->m_rtEventTime += pAdvise->m_rtPeriod;
            ShuntHead();
        }
        else
        {
            ASSERT( pAdvise->m_bPeriodic == FALSE );
            EXECUTE_ASSERT(SetEvent(pAdvise->m_hNotify));
            --m_dwAdviseCount;
            Delete( head.RemoveNext() );
        }

    }

    DbgLog((LOG_TIMING, 3,
            TEXT("CAMSchedule::Advise() Next time stamp: %lu ms, for advise %lu."),
            DWORD(rtNextTime / (UNITS / MILLISECONDS)), pAdvise->m_dwAdviseCookie ));

    m_Serialize.Unlock();

    return rtNextTime;
}

/* Private methods */

DWORD CAMSchedule::AddAdvisePacket( CAdvisePacket * pPacket )
{
    ASSERT(pPacket->m_rtEventTime >= 0 && pPacket->m_rtEventTime < MAX_TIME);
    ASSERT(CritCheckIn(&m_Serialize));

    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;

    const DWORD Result = pPacket->m_dwAdviseCookie = ++m_dwNextCookie;
    // This relies on the fact that z is a sentry with a maximal m_rtEventTime
    for(;;p_prev = p_n)
    {
        p_n = p_prev->m_next;
        if ( p_n->m_rtEventTime >= pPacket->m_rtEventTime ) break;
    }
    p_prev->InsertAfter( pPacket );
    ++m_dwAdviseCount;

    DbgLog((LOG_TIMING, 2, TEXT("Added advise %lu, for thread 0x%02X, scheduled at %lu"),
    pPacket->m_dwAdviseCookie, GetCurrentThreadId(), (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) ));

    // If packet added at the head, then clock needs to re-evaluate wait time.
    if ( p_prev == &head ) SetEvent( m_ev );

    return Result;
}

void CAMSchedule::Delete( CAdvisePacket * pPacket )
{
    if ( m_dwCacheCount >= dwCacheMax ) delete pPacket;
    else
    {
        m_Serialize.Lock();
        pPacket->m_next = m_pAdviseCache;
        m_pAdviseCache = pPacket;
        ++m_dwCacheCount;
        m_Serialize.Unlock();
    }
}

// Takes the head of the list & repositions it
void CAMSchedule::ShuntHead()
{
    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;

    m_Serialize.Lock();
    CAdvisePacket *const pPacket = head.m_next;

    // This will catch both an empty list,
    // and if somehow a MAX_TIME time gets into the list
    // (which would also break this method).
    ASSERT( pPacket->m_rtEventTime < MAX_TIME );

    // This relies on the fact that z is a sentry with a maximal m_rtEventTime
    for(;;p_prev = p_n)
    {
        p_n = p_prev->m_next;
        if ( p_n->m_rtEventTime > pPacket->m_rtEventTime ) break;
    }
    // If p_prev == pPacket then we're already in the right place
    if (p_prev != pPacket)
    {
        head.m_next = pPacket->m_next;
        (p_prev->m_next = pPacket)->m_next = p_n;
    }
    #ifdef DEBUG
        DbgLog((LOG_TIMING, 2, TEXT("Periodic advise %lu, shunted to %lu"),
        pPacket->m_dwAdviseCookie, (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) ));
    #endif
    m_Serialize.Unlock();
}

#ifdef DEBUG
void CAMSchedule::DumpLinkedList()
{
    m_Serialize.Lock();
    int i=0;
    DbgLog((LOG_TIMING, 1, TEXT("CAMSchedule::DumpLinkedList() this = %08X"), DWORD(this) ));
    for ( CAdvisePacket * p = &head
        ; p
        ; p = p->m_next         , i++
        )
    {
        DbgLog((LOG_TIMING, 1, TEXT("Advise List # %lu, Cookie %d,  RefTime %lu"),
            i,
    p->m_dwAdviseCookie,
    p->m_rtEventTime / (UNITS / MILLISECONDS)
            ));
    }
    m_Serialize.Unlock();
}
#endif


SCHEDULE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//  SCHEDULE.H

#ifndef __CAMSchedule__
#define __CAMSchedule__

class CAMSchedule : private CBaseObject
{
public:
    virtual ~CAMSchedule();
    // ev is the event we should fire if the advise time needs re-evaluating
    CAMSchedule( HANDLE ev );

    DWORD GetAdviseCount();
    REFERENCE_TIME GetNextAdviseTime();

    // We need a method for derived classes to add advise packets, we return the cookie
    DWORD AddAdvisePacket( const REFERENCE_TIME & time1, const REFERENCE_TIME & time2, HANDLE h, BOOL periodic );
    // And a way to cancel
    HRESULT Unadvise(DWORD dwAdviseCookie);

    // Tell us the time please, and we'll dispatch the expired events.  We return the time of the next event.
    // NB: The time returned will be "useless" if you start adding extra Advises.  But that's the problem of
    // whoever is using this helper class (typically a clock).
    REFERENCE_TIME Advise( const REFERENCE_TIME & rtTime );

    // Get the event handle which will be set if advise time requires re-evaluation.
    HANDLE GetEvent() const { return m_ev; }

private:
    // We define the nodes that will be used in our singly linked list
    // of advise packets.  The list is ordered by time, with the
    // elements that will expire first at the front.
    class CAdvisePacket
    {
    public:
        CAdvisePacket()
        {}

        CAdvisePacket * m_next;
        DWORD           m_dwAdviseCookie;
        REFERENCE_TIME  m_rtEventTime;      // Time at which event should be set
        REFERENCE_TIME  m_rtPeriod;         // Periodic time
        HANDLE          m_hNotify;          // Handle to event or semephore
        BOOL            m_bPeriodic;        // TRUE => Periodic event

        CAdvisePacket( CAdvisePacket * next, LONGLONG time ) : m_next(next), m_rtEventTime(time)
        {}

        void InsertAfter( CAdvisePacket * p )
        {
            p->m_next = m_next;
            m_next    = p;
        }

        int IsZ() const // That is, is it the node that represents the end of the list
    { return m_next == 0; }

    CAdvisePacket * RemoveNext()
    {
        CAdvisePacket *const next = m_next;
        CAdvisePacket *const new_next = next->m_next;
        m_next = new_next;
        return next;
    }

    void DeleteNext()
    {
        delete RemoveNext();
    }

    CAdvisePacket * Next() const
    {
        CAdvisePacket * result = m_next;
        if (result->IsZ()) result = 0;
        return result;
    }

        DWORD Cookie() const
        { return m_dwAdviseCookie; }
    };

    // Structure is:
    // head -> elmt1 -> elmt2 -> z -> null
    // So an empty list is:       head -> z -> null
    // Having head & z as links makes insertaion,
    // deletion and shunting much easier.
    CAdvisePacket   head, z;            // z is both a tail and a sentry

    volatile DWORD  m_dwNextCookie;     // Strictly increasing
    volatile DWORD  m_dwAdviseCount;    // Number of elements on list

    CCritSec        m_Serialize;

    // AddAdvisePacket: adds the packet, returns the cookie (0 if failed)
    DWORD AddAdvisePacket( CAdvisePacket * pPacket );
    // Event that we should set if the packed added above will be the next to fire.
    const HANDLE m_ev;

    // A Shunt is where we have changed the first element in the
    // list and want it re-evaluating (i.e. repositioned) in
    // the list.
    void ShuntHead();

    // Rather than delete advise packets, we cache them for future use
    CAdvisePacket * m_pAdviseCache;
    DWORD           m_dwCacheCount;
    enum { dwCacheMax = 5 };             // Don't bother caching more than five

    void Delete( CAdvisePacket * pLink );// This "Delete" will cache the Link

// Attributes and methods for debugging
public:
#ifdef DEBUG
    void DumpLinkedList();
#else
    void DumpLinkedList() {}
#endif

};

#endif // __CAMSchedule__


SEEKPT.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
#include <streams.h>
#include "seekpt.h"

//==================================================================
// CreateInstance
// This goes in the factory template table to create new instances
// If there is already a mapper instance - return that, else make one
// and save it in a static variable so that forever after we can return that.
//==================================================================

CUnknown * CSeekingPassThru::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    return new CSeekingPassThru(NAME("Silly Seeking Thing"),pUnk, phr);
}

STDMETHODIMP CSeekingPassThru::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    if (riid == IID_ISeekingPassThru) {
        return GetInterface((ISeekingPassThru *) this, ppv);
    } else {
        if (m_pPosPassThru &&
            (riid == IID_IMediaSeeking ||
             riid == IID_IMediaPosition)) {
            return m_pPosPassThru->NonDelegatingQueryInterface(riid,ppv);
        } else {
            return CUnknown::NonDelegatingQueryInterface(riid, ppv);
        }
    }
}

CSeekingPassThru::CSeekingPassThru( TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr )
                            : CUnknown(pName, pUnk, phr),
                            m_pPosPassThru(NULL)
{
}

CSeekingPassThru::~CSeekingPassThru()
{
    delete m_pPosPassThru;
}

STDMETHODIMP CSeekingPassThru::Init(BOOL bRendererSeeking, IPin *pPin)
{
    HRESULT hr = NOERROR;
    if (m_pPosPassThru) {
        hr = E_FAIL;
    } else {
        m_pPosPassThru =
            bRendererSeeking ?
                new CRendererPosPassThru(
                    NAME("Render Seeking COM object"),
                    (IUnknown *)this,
                    &hr,
                    pPin) :
                new CPosPassThru(
                    NAME("Render Seeking COM object"),
                    (IUnknown *)this,
                    &hr,
                    pPin);
        if (!m_pPosPassThru) {
            hr = E_OUTOFMEMORY;
        } else {
            if (FAILED(hr)) {
                delete m_pPosPassThru;
                m_pPosPassThru = NULL;
            }
        }
    }
    return hr;
}


SEEKPT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
#ifndef __seekpt_h__
#define __seekpt_h__

class CSeekingPassThru : public ISeekingPassThru, public CUnknown
{
public:
    static CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);
    CSeekingPassThru(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr);
    ~CSeekingPassThru();

    DECLARE_IUNKNOWN;
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    STDMETHODIMP Init(BOOL bSupportRendering, IPin *pPin);

private:
    CPosPassThru              *m_pPosPassThru;
};

#endif


SOURCE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Implements CSource. A Quartz source filter 'template', March 1995

// Locking Strategy.
//
// Hold the filter critical section (m_pFilter->pStateLock()) to serialise
// access to functions. Note that, in general, this lock may be held
// by a function when the worker thread may want to hold it. Therefore
// if you wish to access shared state from the worker thread you will
// need to add another critical section object. The execption is during
// the threads processing loop, when it is safe to get the filter critical
// section from within FillBuffer().

#include <streams.h>

//
// CSource::Constructor
//
// Initialise the pin count for the filter. The user will create the pins in
// the derived class.
CSource::CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid)
    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),
      m_iPins(0),
      m_paStreams(NULL)
{
}

CSource::CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr)
    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),
      m_iPins(0),
      m_paStreams(NULL)
{
    UNREFERENCED_PARAMETER(phr);
}

//
// CSource::Destructor
//
CSource::~CSource()
{
    /*  Free our pins and pin array */
    while (m_iPins != 0) {
// deleting the pins causes them to be removed from the array...
delete m_paStreams[m_iPins - 1];
    }

    ASSERT(m_paStreams == NULL);
}

//
//  Add a new pin
//
HRESULT CSource::AddPin(CSourceStream *pStream)
{
    CAutoLock lock(&m_cStateLock);

    /*  Allocate space for this pin and the old ones */
    CSourceStream **paStreams = new CSourceStream *[m_iPins + 1];
    if (paStreams == NULL) {
        return E_OUTOFMEMORY;
    }
    if (m_paStreams != NULL) {
        CopyMemory((PVOID)paStreams, (PVOID)m_paStreams,
                   m_iPins * sizeof(m_paStreams[0]));
        paStreams[m_iPins] = pStream;
        delete [] m_paStreams;
    }
    m_paStreams = paStreams;
    m_paStreams[m_iPins] = pStream;
    m_iPins++;
    return S_OK;
}

//
//  Remove a pin - pStream is NOT deleted
//
HRESULT CSource::RemovePin(CSourceStream *pStream)
{
    int i;
    for (i = 0; i < m_iPins; i++) {
        if (m_paStreams[i] == pStream) {
            if (m_iPins == 1) {
                delete [] m_paStreams;
                m_paStreams = NULL;
            } else {
                /*  no need to reallocate */
while (++i < m_iPins)
    m_paStreams[i - 1] = m_paStreams[i];
            }
            m_iPins--;
            return S_OK;
        }
    }
    return S_FALSE;
}

//
// FindPin
//
// Set *ppPin to the IPin* that has the id Id.
// or to NULL if the Id cannot be matched.
STDMETHODIMP CSource::FindPin(LPCWSTR Id, IPin **ppPin)
{
    CheckPointer(ppPin,E_POINTER);
    ValidateReadWritePtr(ppPin,sizeof(IPin *));
    // The -1 undoes the +1 in QueryId and ensures that totally bogus
    // strings (for which WstrToInt delivers 0) give a deliver a NULL pin.
    int i = WstrToInt(Id) -1;
    *ppPin = GetPin(i);
    if (*ppPin!=NULL){
        (*ppPin)->AddRef();
        return NOERROR;
    } else {
        return VFW_E_NOT_FOUND;
    }
}

//
// FindPinNumber
//
// return the number of the pin with this IPin* or -1 if none
int CSource::FindPinNumber(IPin *iPin) {
    int i;
    for (i=0; i<m_iPins; ++i) {
        if ((IPin *)(m_paStreams[i])==iPin) {
            return i;
        }
    }
    return -1;
}

//
// GetPinCount
//
// Returns the number of pins this filter has
int CSource::GetPinCount(void) {

    CAutoLock lock(&m_cStateLock);
    return m_iPins;
}

//
// GetPin
//
// Return a non-addref'd pointer to pin n
// needed by CBaseFilter
CBasePin *CSource::GetPin(int n) {

    CAutoLock lock(&m_cStateLock);

    // n must be in the range 0..m_iPins-1
    // if m_iPins>n  && n>=0 it follows that m_iPins>0
    // which is what used to be checked (i.e. checking that we have a pin)
    if ((n >= 0) && (n < m_iPins)) {

        ASSERT(m_paStreams[n]);
return m_paStreams[n];
    }
    return NULL;
}

//

// *
// * --- CSourceStream ----
// *

//
// Set Id to point to a CoTaskMemAlloc'd
STDMETHODIMP CSourceStream::QueryId(LPWSTR *Id) {
    CheckPointer(Id,E_POINTER);
    ValidateReadWritePtr(Id,sizeof(LPWSTR));

    // We give the pins id's which are 1,2,...
    // FindPinNumber returns -1 for a bogus pin
    int i = 1+ m_pFilter->FindPinNumber(this);
    if (i<1) return VFW_E_NOT_FOUND;
    *Id = (LPWSTR)CoTaskMemAlloc(8);
    if (*Id==NULL) {
       return E_OUTOFMEMORY;
    }
    IntToWstr(i, *Id);
    return NOERROR;
}

//
// CSourceStream::Constructor
//
// increments the number of pins present on the filter
CSourceStream::CSourceStream(
    TCHAR *pObjectName,
    HRESULT *phr,
    CSource *ps,
    LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName),
      m_pFilter(ps) {

     *phr = m_pFilter->AddPin(this);
}

//
// CSourceStream::Destructor
//
// Decrements the number of pins on this filter
CSourceStream::~CSourceStream(void) {

     m_pFilter->RemovePin(this);
}

//
// CheckMediaType
//
// Do we support this type? Provides the default support for 1 type.
HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType) {

    CAutoLock lock(m_pFilter->pStateLock());

    CMediaType mt;
    GetMediaType(&mt);

    if (mt == *pMediaType) {
        return NOERROR;
    }

    return E_FAIL;
}

//
// GetMediaType/3
//
// By default we support only one type
// iPosition indexes are 0-n
HRESULT CSourceStream::GetMediaType(int iPosition, CMediaType *pMediaType) {

    CAutoLock lock(m_pFilter->pStateLock());

    if (iPosition<0) {
        return E_INVALIDARG;
    }
    if (iPosition>0) {
        return VFW_S_NO_MORE_ITEMS;
    }
    return GetMediaType(pMediaType);
}

//
// Active
//
// The pin is active - start up the worker thread
HRESULT CSourceStream::Active(void) {

    CAutoLock lock(m_pFilter->pStateLock());

    HRESULT hr;

    if (m_pFilter->IsActive()) {
return S_FALSE;// succeeded, but did not allocate resources (they already exist...)
    }

    // do nothing if not connected - its ok not to connect to
    // all pins of a source filter
    if (!IsConnected()) {
        return NOERROR;
    }

    hr = CBaseOutputPin::Active();
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT(!ThreadExists());

    // start the thread
    if (!Create()) {
        return E_FAIL;
    }

    // Tell thread to initialize. If OnThreadCreate Fails, so does this.
    hr = Init();
    if (FAILED(hr))
return hr;

    return Pause();
}

//
// Inactive
//
// Pin is inactive - shut down the worker thread
// Waits for the worker to exit before returning.
HRESULT CSourceStream::Inactive(void) {

    CAutoLock lock(m_pFilter->pStateLock());

    HRESULT hr;

    // do nothing if not connected - its ok not to connect to
    // all pins of a source filter
    if (!IsConnected()) {
        return NOERROR;
    }

    // !!! need to do this before trying to stop the thread, because
    // we may be stuck waiting for our own allocator!!!

    hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator
    if (FAILED(hr)) {
return hr;
    }

    if (ThreadExists()) {
hr = Stop();

if (FAILED(hr)) {
    return hr;
}

hr = Exit();
if (FAILED(hr)) {
    return hr;
}

Close();// Wait for the thread to exit, then tidy up.
    }

    // hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator
    //if (FAILED(hr)) {
    //return hr;
    //}

    return NOERROR;
}

//
// ThreadProc
//
// When this returns the thread exits
// Return codes > 0 indicate an error occured
DWORD CSourceStream::ThreadProc(void) {

    HRESULT hr;  // the return code from calls
    Command com;

    do {
com = GetRequest();
if (com != CMD_INIT) {
    DbgLog((LOG_ERROR, 1, TEXT("Thread expected init command")));
    Reply((DWORD) E_UNEXPECTED);
}
    } while (com != CMD_INIT);

    DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread initializing")));

    hr = OnThreadCreate(); // perform set up tasks
    if (FAILED(hr)) {
        DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadCreate failed. Aborting thread.")));
        OnThreadDestroy();
Reply(hr);// send failed return code from OnThreadCreate
        return 1;
    }

    // Initialisation suceeded
    Reply(NOERROR);

    Command cmd;
    do {
cmd = GetRequest();

switch (cmd) {

case CMD_EXIT:
    Reply(NOERROR);
    break;

case CMD_RUN:
    DbgLog((LOG_ERROR, 1, TEXT("CMD_RUN received before a CMD_PAUSE???")));
    // !!! fall through???

case CMD_PAUSE:
    Reply(NOERROR);
    DoBufferProcessingLoop();
    break;

case CMD_STOP:
    Reply(NOERROR);
    break;

default:
    DbgLog((LOG_ERROR, 1, TEXT("Unknown command %d received!"), cmd));
    Reply((DWORD) E_NOTIMPL);
    break;
}
    } while (cmd != CMD_EXIT);

    hr = OnThreadDestroy();// tidy up.
    if (FAILED(hr)) {
        DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadDestroy failed. Exiting thread.")));
        return 1;
    }

    DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread exiting")));
    return 0;
}

//
// DoBufferProcessingLoop
//
// Grabs a buffer and calls the users processing function.
// Overridable, so that different delivery styles can be catered for.
HRESULT CSourceStream::DoBufferProcessingLoop(void) {

    Command com;

    OnThreadStartPlay();

    do {
while (!CheckRequest(&com)) {

    IMediaSample *pSample;

    HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0);
    if (FAILED(hr)) {
                Sleep(1);
continue;// go round again. Perhaps the error will go away
    // or the allocator is decommited & we will be asked to
    // exit soon.
    }

    // Virtual function user will override.
    hr = FillBuffer(pSample);

    if (hr == S_OK) {
hr = Deliver(pSample);
                pSample->Release();

                // downstream filter returns S_FALSE if it wants us to
                // stop or an error if it's reporting an error.
                if(hr != S_OK)
                {
                  DbgLog((LOG_TRACE, 2, TEXT("Deliver() returned %08x; stopping"), hr));
                  return S_OK;
                }

    } else if (hr == S_FALSE) {
                // derived class wants us to stop pushing data
pSample->Release();
DeliverEndOfStream();
return S_OK;
    } else {
                // derived class encountered an error
                pSample->Release();
DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr));
                DeliverEndOfStream();
                m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0);
                return hr;
    }

            // all paths release the sample
}

        // For all commands sent to us there must be a Reply call!

if (com == CMD_RUN || com == CMD_PAUSE) {
    Reply(NOERROR);
} else if (com != CMD_STOP) {
    Reply((DWORD) E_UNEXPECTED);
    DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!")));
}
    } while (com != CMD_STOP);

    return S_FALSE;
}


SOURCE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Classes to simplify creation of ActiveX source filters that support
// continuous generation of data. It provides no support for IMediaControl
// or IMediaPosition
//
// Derive your source filter from CSource.
// During construction either:
//    Create some CSourceStream objects to manage your pins
//    Provide the user with a means of doing so eg, an IPersistFile interface.
//
// CSource provides:
//    IBaseFilter interface management
//    IMediaFilter interface management, via CBaseFilter
//    Pin counting for CBaseFilter
//
// Derive a class from CSourceStream to manage your output pin types
//  Implement GetMediaType/1 to return the type you support. If you support multiple
//   types then overide GetMediaType/3, CheckMediaType and GetMediaTypeCount.
//  Implement Fillbuffer() to put data into one buffer.
//
// CSourceStream provides:
//    IPin management via CBaseOutputPin
//    Worker thread management

#ifndef __CSOURCE__
#define __CSOURCE__

class CSourceStream;  // The class that will handle each pin

//
// CSource
//
// Override construction to provide a means of creating
// CSourceStream derived objects - ie a way of creating pins.
class CSource : public CBaseFilter {
public:

    CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr);
    CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid);
    ~CSource();

    int       GetPinCount(void);
    CBasePin *GetPin(int n);

    // -- Utilities --

    CCritSec*pStateLock(void) { return &m_cStateLock; }// provide our critical section

    HRESULT     AddPin(CSourceStream *);
    HRESULT     RemovePin(CSourceStream *);

    STDMETHODIMP FindPin(
        LPCWSTR Id,
        IPin ** ppPin
    );

    int FindPinNumber(IPin *iPin);

protected:

    int             m_iPins;       // The number of pins on this filter. Updated by CSourceStream
          // constructors & destructors.
    CSourceStream **m_paStreams;   // the pins on this filter.

    CCritSec m_cStateLock;// Lock this to serialize function accesses to the filter state

};

//
// CSourceStream
//
// Use this class to manage a stream of data that comes from a
// pin.
// Uses a worker thread to put data on the pin.
class CSourceStream : public CAMThread, public CBaseOutputPin {
public:

    CSourceStream(TCHAR *pObjectName,
                  HRESULT *phr,
                  CSource *pms,
                  LPCWSTR pName);

    virtual ~CSourceStream(void);  // virtual destructor ensures derived class destructors are called too.

protected:

    CSource *m_pFilter;// The parent of this stream

    // *
    // * Data Source
    // *
    // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are
    // * called from within the ThreadProc. They are used in the creation of
    // * the media samples this pin will provide
    // *

    // Override this to provide the worker thread a means
    // of processing a buffer
    virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE;

    // Called as the thread is created/destroyed - use to perform
    // jobs such as start/stop streaming mode
    // If OnThreadCreate returns an error the thread will exit.
    virtual HRESULT OnThreadCreate(void) {return NOERROR;};
    virtual HRESULT OnThreadDestroy(void) {return NOERROR;};
    virtual HRESULT OnThreadStartPlay(void) {return NOERROR;};

    // *
    // * Worker Thread
    // *

    HRESULT Active(void);    // Starts up the worker thread
    HRESULT Inactive(void);  // Exits the worker thread.

public:
    // thread commands
    enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT};
    HRESULT Init(void) { return CallWorker(CMD_INIT); }
    HRESULT Exit(void) { return CallWorker(CMD_EXIT); }
    HRESULT Run(void) { return CallWorker(CMD_RUN); }
    HRESULT Pause(void) { return CallWorker(CMD_PAUSE); }
    HRESULT Stop(void) { return CallWorker(CMD_STOP); }

protected:
    Command GetRequest(void) { return (Command) CAMThread::GetRequest(); }
    BOOL    CheckRequest(Command *pCom) { return CAMThread::CheckRequest( (DWORD *) pCom); }

    // override these if you want to add thread commands
    virtual DWORD ThreadProc(void);  // the thread function

    virtual HRESULT DoBufferProcessingLoop(void);    // the loop executed whilst running

    // *
    // * AM_MEDIA_TYPE support
    // *

    // If you support more than one media type then override these 2 functions
    virtual HRESULT CheckMediaType(const CMediaType *pMediaType);
    virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);  // List pos. 0-n

    // If you support only one type then override this fn.
    // This will only be called by the default implementations
    // of CheckMediaType and GetMediaType(int, CMediaType*)
    // You must override this fn. or the above 2!
    virtual HRESULT GetMediaType(CMediaType *pMediaType) {return E_UNEXPECTED;}

    STDMETHODIMP QueryId(
        LPWSTR * Id
    );
};

#endif // __CSOURCE__


STREAMS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Streams architecture overall header file, January 1995

#ifndef __STREAMS__
#define __STREAMS__

#ifdef_MSC_VER
// disable some level-4 warnings, use #pragma warning(enable:###) to re-enable
#pragma warning(disable:4100) // warning C4100: unreferenced formal parameter
#pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union
#pragma warning(disable:4511) // warning C4511: copy constructor could not be generated
#pragma warning(disable:4512) // warning C4512: assignment operator could not be generated
#pragma warning(disable:4514) // warning C4514: "unreferenced inline function has been removed"

#if _MSC_VER>=1100
#define AM_NOVTABLE __declspec(novtable)
#else
#define AM_NOVTABLE
#endif
#endif// MSC_VER

#include <windows.h>
#include <windowsx.h>
#include <olectl.h>
#include <ddraw.h>
#include <mmsystem.h>

#ifndef NUMELMS
   #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
#endif

#include <strmif.h>     // Generated IDL header file for streams interfaces

#include <reftime.h>    // Helper class for REFERENCE_TIME management
#include <wxdebug.h>    // Debug support for logging and ASSERTs
#include <amvideo.h>    // ActiveMovie video interfaces and definitions
//include amaudio.h explicitly if you need it.  it requires the DX SDK.
//#include <amaudio.h>    // ActiveMovie audio interfaces and definitions
#include <wxutil.h>     // General helper classes for threads etc
#include <combase.h>    // Base COM classes to support IUnknown
#include <dllsetup.h>   // Filter registration support functions
#include <measure.h>    // Performance measurement
#include <comlite.h>    // Light weight com function prototypes

#include <cache.h>      // Simple cache container class
#include <wxlist.h>     // Non MFC generic list class
#include <msgthrd.h>// CMsgThread
#include <mtype.h>      // Helper class for managing media types
#include <fourcc.h>     // conversions between FOURCCs and GUIDs
#include <control.h>    // generated from control.odl
#include <ctlutil.h>    // control interface utility classes
#include <evcode.h>     // event code definitions
#include <amfilter.h>   // Main streams architecture class hierachy
#include <transfrm.h>   // Generic transform filter
#include <transip.h>    // Generic transform-in-place filter
#include <uuids.h>      // declaration of type GUIDs and well-known clsids
#include <source.h>// Generic source filter
#include <outputq.h>    // Output pin queueing
#include <errors.h>     // HRESULT status and error definitions
#include <renbase.h>    // Base class for writing ActiveX renderers
#include <winutil.h>    // Helps with filters that manage windows
#include <winctrl.h>    // Implements the IVideoWindow interface
#include <videoctl.h>   // Specifically video related classes
#include <refclock.h>// Base clock class
#include <sysclock.h>// System clock
#include <pstream.h>    // IPersistStream helper class
#include <vtrans.h>     // Video Transform Filter base class
#include <amextra.h>
#include <cprop.h>      // Base property page class
#include <strmctl.h>    // IAMStreamControl support
#include <edevdefs.h>   // External device control interface defines

#else
    #ifdef DEBUG
    #pragma message("STREAMS.H included TWICE")
    #endif
#endif // __STREAMS__


STRMCTL.CPP

//==========================================================================;
//
//  Copyright (c) 1997Microsoft Corporation.All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <strmctl.h>

CBaseStreamControl::CBaseStreamControl()
: m_StreamState(STREAM_FLOWING)
, m_StreamStateOnStop(STREAM_FLOWING) // means no pending stop
, m_tStartTime(MAX_TIME)
, m_tStopTime(MAX_TIME)
, m_dwStartCookie(0)
, m_dwStopCookie(0)
, m_pRefClock(NULL)
, m_FilterState(State_Stopped)
, m_bIsFlushing(FALSE)
, m_bStopSendExtra(FALSE)
{}

CBaseStreamControl::~CBaseStreamControl()
{
    // Make sure we release the clock.
    SetSyncSource(NULL);
    return;
}

STDMETHODIMP CBaseStreamControl::StopAt(const REFERENCE_TIME * ptStop, BOOL bSendExtra, DWORD dwCookie)
{
    CAutoLock lck(&m_CritSec);
    m_bStopSendExtra = FALSE;// reset
    m_bStopExtraSent = FALSE;
    if (ptStop)
    {
        if (*ptStop == MAX_TIME)
        {
            DbgLog((LOG_TRACE,2,TEXT("StopAt: Cancel stop")));
            CancelStop();
    // If there's now a command to start in the future, we assume
    // they want to be stopped when the graph is first run
    if (m_FilterState == State_Stopped && m_tStartTime < MAX_TIME) {
        m_StreamState = STREAM_DISCARDING;
                DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
    }
            return NOERROR;
        }
        DbgLog((LOG_TRACE,2,TEXT("StopAt: %dms extra=%d"),
(int)(*ptStop/10000), bSendExtra));
// if the first command is to stop in the future, then we assume they
        // want to be started when the graph is first run
if (m_FilterState == State_Stopped && m_tStartTime > *ptStop) {
    m_StreamState = STREAM_FLOWING;
            DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
}
        m_bStopSendExtra = bSendExtra;
        m_tStopTime = *ptStop;
        m_dwStopCookie = dwCookie;
        m_StreamStateOnStop = STREAM_DISCARDING;
    }
    else
    {
        DbgLog((LOG_TRACE,2,TEXT("StopAt: now")));
// sending an extra frame when told to stop now would screw people up
        m_bStopSendExtra = FALSE;
        m_tStopTime = MAX_TIME;
        m_dwStopCookie = 0;
        m_StreamState = STREAM_DISCARDING;
        m_StreamStateOnStop = STREAM_FLOWING;// no pending stop
    }
    // we might change our mind what to do with a sample we're blocking
    m_StreamEvent.Set();
    return NOERROR;
}

STDMETHODIMP CBaseStreamControl::StartAt
( const REFERENCE_TIME *ptStart, DWORD dwCookie )
{
    CAutoLock lck(&m_CritSec);
    if (ptStart)
    {
        if (*ptStart == MAX_TIME)
        {
            DbgLog((LOG_TRACE,2,TEXT("StartAt: Cancel start")));
            CancelStart();
    // If there's now a command to stop in the future, we assume
    // they want to be started when the graph is first run
    if (m_FilterState == State_Stopped && m_tStopTime < MAX_TIME) {
                DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
        m_StreamState = STREAM_FLOWING;
    }
            return NOERROR;
        }
        DbgLog((LOG_TRACE,2,TEXT("StartAt: %dms"), (int)(*ptStart/10000)));
// if the first command is to start in the future, then we assume they
        // want to be stopped when the graph is first run
if (m_FilterState == State_Stopped && m_tStopTime >= *ptStart) {
            DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
    m_StreamState = STREAM_DISCARDING;
}
        m_tStartTime = *ptStart;
        m_dwStartCookie = dwCookie;
        // if (m_tStopTime == m_tStartTime) CancelStop();
    }
    else
    {
        DbgLog((LOG_TRACE,2,TEXT("StartAt: now")));
        m_tStartTime = MAX_TIME;
        m_dwStartCookie = 0;
        m_StreamState = STREAM_FLOWING;
    }
    // we might change our mind what to do with a sample we're blocking
    m_StreamEvent.Set();
    return NOERROR;
}

//  Retrieve information about current settings
STDMETHODIMP CBaseStreamControl::GetInfo(AM_STREAM_INFO *pInfo)
{
    if (pInfo == NULL)
return E_POINTER;

    pInfo->tStart = m_tStartTime;
    pInfo->tStop  = m_tStopTime;
    pInfo->dwStartCookie = m_dwStartCookie;
    pInfo->dwStopCookie  = m_dwStopCookie;
    pInfo->dwFlags = m_bStopSendExtra ? AM_STREAM_INFO_STOP_SEND_EXTRA : 0;
    pInfo->dwFlags |= m_tStartTime == MAX_TIME ? 0 : AM_STREAM_INFO_START_DEFINED;
    pInfo->dwFlags |= m_tStopTime == MAX_TIME ? 0 : AM_STREAM_INFO_STOP_DEFINED;
    switch (m_StreamState) {
    default:
        DbgBreak("Invalid stream state");
    case STREAM_FLOWING:
        break;
    case STREAM_DISCARDING:
        pInfo->dwFlags |= AM_STREAM_INFO_DISCARDING;
        break;
    }
    return S_OK;
}

void CBaseStreamControl::ExecuteStop()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_StreamState = m_StreamStateOnStop;
    if (m_dwStopCookie && m_pSink) {
DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STOPPED (%d)"),
m_dwStopCookie));
        m_pSink->Notify(EC_STREAM_CONTROL_STOPPED, (long)this, m_dwStopCookie);
    }
    CancelStop(); // This will do the tidy up
}

void CBaseStreamControl::ExecuteStart()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_StreamState = STREAM_FLOWING;
    if (m_dwStartCookie) {
DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STARTED (%d)"),
m_dwStartCookie));
        m_pSink->Notify(EC_STREAM_CONTROL_STARTED, (long)this, m_dwStartCookie);
    }
    CancelStart(); // This will do the tidy up
}

void CBaseStreamControl::CancelStop()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_tStopTime = MAX_TIME;
    m_dwStopCookie = 0;
    m_StreamStateOnStop = STREAM_FLOWING;
}

void CBaseStreamControl::CancelStart()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_tStartTime = MAX_TIME;
    m_dwStartCookie = 0;
}

// This guy will return one of the three StreamControlState's.  Here's what the caller
// should do for each one:
//
// STREAM_FLOWING:      Proceed as usual (render or pass the sample on)
// STREAM_DISCARDING:   Calculate the time 'til *pSampleStart and wait that long
//                      for the event handle (GetStreamEventHandle()).  If the
//                      wait expires, throw the sample away.  If the event
//fires, call me back, I've changed my mind.
//I use pSampleStart (not Stop) so that live sources don't
// block for the duration of their samples, since the clock
//will always read approximately pSampleStart when called

// All through this code, you'll notice the following rules:
// - When start and stop time are the same, it's as if start was first
// - An event is considered inside the sample when it's >= sample start time
//   but < sample stop time
// - if any part of the sample is supposed to be sent, we'll send the whole
//   thing since we don't break it into smaller pieces
// - If we skip over a start or stop without doing it, we still signal the event
//   and reset ourselves in case somebody's waiting for the event, and to make
//   sure we notice that the event is past and should be forgotten
// Here are the 19 cases that have to be handled (x=start o=stop <-->=sample):
//
// 1.xo<-->start then stop
// 2.ox<-->stop then start
// 3. x<o->start
// 4. o<x->stop then start
// 5. x<-->ostart
// 6. o<-->xstop
// 7.  <x->ostart
// 8.  <o->xno change
// 9.  <xo>start
// 10.  <ox>stop then start
// 11.  <-->xono change
// 12.  <-->oxno change
// 13. x<-->start
// 14.    <x->start
// 15.    <-->xno change
// 16.   o<-->stop
// 17.  <o->no change
// 18.  <-->ono change
// 19.    <-->no change

enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckSampleTimes
( const REFERENCE_TIME * pSampleStart, const REFERENCE_TIME * pSampleStop )
{
    CAutoLock lck(&m_CritSec);

    ASSERT(!m_bIsFlushing);
    ASSERT(pSampleStart && pSampleStop);

    // Don't ask me how I came up with the code below to handle all 19 cases
    // - DannyMi

    if (m_tStopTime >= *pSampleStart)
    {
        if (m_tStartTime >= *pSampleStop)
    return m_StreamState;// cases  8 11 12 15 17 18 19
if (m_tStopTime < m_tStartTime)
    ExecuteStop();// case 10
ExecuteStart();                         // cases 3 5 7 9 13 14
return m_StreamState;
    }

    if (m_tStartTime >= *pSampleStop)
    {
        ExecuteStop();                          // cases 6 16
        return m_StreamState;
    }

    if (m_tStartTime <= m_tStopTime)
    {
ExecuteStart();
ExecuteStop();
        return m_StreamState;// case 1
    }
    else
    {
ExecuteStop();
ExecuteStart();
        return m_StreamState;// cases 2 4
    }
}

enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckStreamState( IMediaSample * pSample )
{

    REFERENCE_TIME rtBufferStart, rtBufferStop;
    const BOOL bNoBufferTimes =
              pSample == NULL ||
              FAILED(pSample->GetTime(&rtBufferStart, &rtBufferStop));

    StreamControlState state;
    LONG lWait;

    do
        {
     // something has to break out of the blocking
            if (m_bIsFlushing || m_FilterState == State_Stopped)
return STREAM_DISCARDING;

            if (bNoBufferTimes) {
                //  Can't do anything until we get a time stamp
                state = m_StreamState;
                break;
            } else {
                state = CheckSampleTimes( &rtBufferStart, &rtBufferStop );
                if (state == STREAM_FLOWING)
    break;

// we aren't supposed to send this, but we've been
// told to send one more than we were supposed to
// (and the stop isn't still pending and we're streaming)
if (m_bStopSendExtra && !m_bStopExtraSent &&
m_tStopTime == MAX_TIME &&
m_FilterState != State_Stopped) {
    m_bStopExtraSent = TRUE;
    DbgLog((LOG_TRACE,2,TEXT("%d sending an EXTRA frame"),
    m_dwStopCookie));
    state = STREAM_FLOWING;
    break;
}
            }

            // We're in discarding mode

            // If we've no clock, discard as fast as we can
            if (!m_pRefClock) {
break;

    // If we're paused, we can't discard in a timely manner because
    // there's no such thing as stream times.  We must block until
    // we run or stop, or we'll end up throwing the whole stream away
    // as quickly as possible
    } else if (m_FilterState == State_Paused) {
lWait = INFINITE;

    } else {
        // wait until it's time for the sample until we say "discard"
        // ("discard in a timely fashion")
        REFERENCE_TIME rtNow;
                EXECUTE_ASSERT(SUCCEEDED(m_pRefClock->GetTime(&rtNow)));
                rtNow -= m_tRunStart;   // Into relative ref-time
                lWait = LONG((rtBufferStart - rtNow)/10000); // 100ns -> ms
                if (lWait < 10) break; // Not worth waiting - discard early
    }

    } while(WaitForSingleObject(GetStreamEventHandle(), lWait) != WAIT_TIMEOUT);

    return state;
}

void CBaseStreamControl::NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart )
{
    CAutoLock lck(&m_CritSec);

    // or we will get confused
    if (m_FilterState == new_state)
return;

    switch (new_state)
    {
        case State_Stopped:

            DbgLog((LOG_TRACE,2,TEXT("Filter is STOPPED")));

    // execute any pending starts and stops in the right order,
    // to make sure all notifications get sent, and we end up
    // in the right state to begin next time (??? why not?)

    if (m_tStartTime != MAX_TIME && m_tStopTime == MAX_TIME) {
ExecuteStart();
    } else if (m_tStopTime != MAX_TIME && m_tStartTime == MAX_TIME) {
ExecuteStop();
    } else if (m_tStopTime != MAX_TIME && m_tStartTime != MAX_TIME) {
if (m_tStartTime <= m_tStopTime) {
    ExecuteStart();
    ExecuteStop();
} else {
    ExecuteStop();
    ExecuteStart();
}
    }
    // always start off flowing when the graph starts streaming
    // unless told otherwise
    m_StreamState = STREAM_FLOWING;
            m_FilterState = new_state;
            break;

        case State_Running:

            DbgLog((LOG_TRACE,2,TEXT("Filter is RUNNING")));

            m_tRunStart = tStart;
            // fall-through

        default: // case State_Paused:
            m_FilterState = new_state;
    }
    // unblock!
    m_StreamEvent.Set();
}

void CBaseStreamControl::Flushing(BOOL bInProgress)
{
    CAutoLock lck(&m_CritSec);
    m_bIsFlushing = bInProgress;
    m_StreamEvent.Set();
}


STRMCTL.H

//==========================================================================;
//
//  Copyright (c) 1997Microsoft Corporation.All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __strmctl_h__
#define __strmctl_h__

class CBaseStreamControl : public IAMStreamControl
{
public:
    // Used by the implementation
    enum StreamControlState
    { STREAM_FLOWING = 0x1000,
      STREAM_DISCARDING
    };

private:
    enum StreamControlStatem_StreamState;// Current stream state
    enum StreamControlStatem_StreamStateOnStop;// State after next stop
// (i.e.Blocking or Discarding)

    REFERENCE_TIMEm_tStartTime;    // MAX_TIME implies none
    REFERENCE_TIMEm_tStopTime;    // MAX_TIME implies none
    DWORDm_dwStartCookie;    // Cookie for notification to app
    DWORDm_dwStopCookie;    // Cookie for notification to app
    volatile BOOL       m_bIsFlushing;        // No optimization pls!
    volatile BOOLm_bStopSendExtra;   // bSendExtra was set
    volatile BOOLm_bStopExtraSent;   // the extra one was sent

    CCritSecm_CritSec;    // CritSec to guard above attributes

    // Event to fire when we can come
    // out of blocking, or to come out of waiting
    // to discard if we change our minds.
    //
    CAMEventm_StreamEvent;

    // All of these methods execute immediately.  Helpers for others.
    //
    void ExecuteStop();
    void ExecuteStart();
    void CancelStop();
    void CancelStart();

    // Some things we need to be told by our owning filter
    // Your pin must also expose IAMStreamControl when QI'd for it!
    //
    IReferenceClock *m_pRefClock;    // Need it to set advises
    // Filter must tell us via
    // SetSyncSource
    IMediaEventSink *   m_pSink;            // Event sink
    // Filter must tell us after it
    // creates it in JoinFilterGraph()
    FILTER_STATEm_FilterState;    // Just need it!
    // Filter must tell us via
    // NotifyFilterState
    REFERENCE_TIMEm_tRunStart;    // Per the Run call to the filter

    // This guy will return one of the three StreamControlState's.  Here's what
    // the caller should do for each one:
    //
    // STREAM_FLOWING:Proceed as usual (render or pass the sample on)
    // STREAM_DISCARDING:Calculate the time 'til *pSampleStop and wait
    //that long for the event handle
    //(GetStreamEventHandle()).  If the wait
    //expires, throw the sample away.  If the event
    //fires, call me back - I've changed my mind.
    //
    enum StreamControlState CheckSampleTimes( const REFERENCE_TIME * pSampleStart,
      const REFERENCE_TIME * pSampleStop );

public:
    // You don't have to tell us much when we're created, but there are other
    // obligations that must be met.  See SetSyncSource & NotifyFilterState
    // below.
    //
    CBaseStreamControl();
    ~CBaseStreamControl();

    // If you want this class to work properly, there are thing you need to
    // (keep) telling it.  Filters with pins that use this class
    // should ensure that they pass through to this method any calls they
    // receive on their SetSyncSource.

    // We need a clock to see what time it is.  This is for the
    // "discard in a timely fashion" logic.  If we discard everything as
    // quick as possible, a whole 60 minute file could get discarded in the
    // first 10 seconds, and if somebody wants to turn streaming on at 30
    // minutes into the file, and they make the call more than a few seconds
    // after the graph is run, it may be too late!  That would be silly.
    // So we hold every sample until it's time has gone, then we discard it.
    // The filter should call this when it gets a SetSyncSource
    //
    void SetSyncSource( IReferenceClock * pRefClock )
    {
CAutoLock lck(&m_CritSec);
if (m_pRefClock) m_pRefClock->Release();
m_pRefClock = pRefClock;
if (m_pRefClock) m_pRefClock->AddRef();
    }

    // Set event sink for notifications
    // The filter should call this in its JoinFilterGraph after it creates the
    // IMediaEventSink
    //
    void SetFilterGraph( IMediaEventSink *pSink ) {
        m_pSink = pSink;
    }

    // Since we schedule in stream time, we need the tStart and must track the
    // state of our owning filter.
    // The app should call this ever state change
    //
    void NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart = 0 );

    // Filter should call Flushing(TRUE) in BeginFlush,
    // and Flushing(FALSE) in EndFlush.
    //
    void Flushing( BOOL bInProgress );

    // The two main methods of IAMStreamControl

    // Class adds default values suitable for immediate
    // muting and unmuting of the stream.

    STDMETHODIMP StopAt( const REFERENCE_TIME * ptStop = NULL,
 BOOL bSendExtra = FALSE,
 DWORD dwCookie = 0 );
    STDMETHODIMP StartAt( const REFERENCE_TIME * ptStart = NULL,
      DWORD dwCookie = 0 );
    STDMETHODIMP GetInfo( AM_STREAM_INFO *pInfo);

    // Helper function for pin's receive method.  Call this with
    // the sample and we'll tell you what to do with it.  We'll do a
    // WaitForSingleObject within this call if one is required.  This is
    // a "What should I do with this sample?" kind of call. We'll tell the
    // caller to either flow it or discard it.
    // If pSample is NULL we evaluate based on the current state
    // settings
    enum StreamControlState CheckStreamState( IMediaSample * pSample );

private:
    // These don't require locking, but we are relying on the fact that
    // m_StreamState can be retrieved with integrity, and is a snap shot that
    // may have just been, or may be just about to be, changed.
    HANDLE GetStreamEventHandle() const { return m_StreamEvent; }
    enum StreamControlState GetStreamState() const { return m_StreamState; }
    BOOL IsStreaming() const { return m_StreamState == STREAM_FLOWING; }
};

#endif


SYSCLOCK.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//  SYSCLOCK.CPP
//    Implements a system clock based on IReferenceClock

#include <streams.h>
#include <limits.h>

#ifdef FILTER_DLL

/* List of class IDs and creator functions for the class factory. This
   provides the link between the OLE entry point in the DLL and an object
   being created. The class factory will call the static CreateInstance
   function when it is asked to create a CLSID_SystemClock object */

CFactoryTemplate g_Templates[1] = {
    {&CLSID_SystemClock, CSystemClock::CreateInstance}
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
#endif

/* This goes in the factory template table to create new instances */
CUnknown * WINAPI CSystemClock::CreateInstance(LPUNKNOWN pUnk,HRESULT *phr)
{
    return new CSystemClock(NAME("System reference clock"),pUnk, phr);
}

CSystemClock::CSystemClock(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr) :
    CBaseReferenceClock(pName, pUnk, phr)
{
}

STDMETHODIMP CSystemClock::NonDelegatingQueryInterface(
    REFIID riid,
    void ** ppv)
{
    if (riid == IID_IPersist)
    {
        return GetInterface(static_cast<IPersist *>(this), ppv);
    }
    else
    {
        return CBaseReferenceClock::NonDelegatingQueryInterface(riid, ppv);
    }
}

/* Return the clock's clsid */
STDMETHODIMP
CSystemClock::GetClassID(CLSID *pClsID)
{
    CheckPointer(pClsID,E_POINTER);
    ValidateReadWritePtr(pClsID,sizeof(CLSID));
    *pClsID = CLSID_SystemClock;
    return NOERROR;
}


SYSCLOCK.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
// System Clock implementation of IReferenceClock
// SYSCLOCK.H

#ifndef __SYSTEMCLOCK__
#define __SYSTEMCLOCK__

//
// Base clock.  Uses timeGetTime ONLY
// Uses most of the code in the base reference clock.
// Provides GetTime
//

class CSystemClock : public CBaseReferenceClock, public IPersist
{
public:
    // We must be able to create an instance of ourselves
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);
    CSystemClock(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr);

    DECLARE_IUNKNOWN

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void ** ppv);

    // Yield up our class id so that we can be persisted
    // Implement required Ipersist method
    STDMETHODIMP GetClassID(CLSID *pClsID);

}; //CSystemClock

#endif /* __SYSTEMCLOCK__ */


TRANSFRM.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class for simple transform filters such as video decompressors

#include <streams.h>
#include <measure.h>

// =================================================================
// Implements the CTransformFilter class
// =================================================================

CTransformFilter::CTransformFilter(TCHAR     *pName,
                                   LPUNKNOWN pUnk,
                                   REFCLSID  clsid) :
    CBaseFilter(pName,pUnk,&m_csFilter, clsid),
    m_pInput(NULL),
    m_pOutput(NULL),
    m_bEOSDelivered(FALSE),
    m_bQualityChanged(FALSE),
    m_bSampleSkipped(FALSE)
{
#ifdef PERF
    RegisterPerfId();
#endif //  PERF
}

// destructor

CTransformFilter::~CTransformFilter()
{
    // Delete the pins

    delete m_pInput;
    delete m_pOutput;
}

// Transform place holder - should never be called
HRESULT CTransformFilter::Transform(IMediaSample * pIn, IMediaSample *pOut)
{
    UNREFERENCED_PARAMETER(pIn);
    UNREFERENCED_PARAMETER(pOut);
    DbgBreak("CTransformFilter::Transform() should never be called");
    return E_UNEXPECTED;
}

// return the number of pins we provide

int CTransformFilter::GetPinCount()
{
    return 2;
}

// return a non-addrefed CBasePin * for the user to addref if he holds onto it
// for longer than his pointer to us. We create the pins dynamically when they
// are asked for rather than in the constructor. This is because we want to
// give the derived class an oppportunity to return different pin objects

// We return the objects as and when they are needed. If either of these fails
// then we return NULL, the assumption being that the caller will realise the
// whole deal is off and destroy us - which in turn will delete everything.

CBasePin *
CTransformFilter::GetPin(int n)
{
    HRESULT hr = S_OK;

    // Create an input pin if necessary

    if (m_pInput == NULL) {

        m_pInput = new CTransformInputPin(NAME("Transform input pin"),
                                          this,              // Owner filter
                                          &hr,               // Result code
                                          L"XForm In");      // Pin name

        //  Can't fail
        ASSERT(SUCCEEDED(hr));
        if (m_pInput == NULL) {
            return NULL;
        }
        m_pOutput = (CTransformOutputPin *)
   new CTransformOutputPin(NAME("Transform output pin"),
                                            this,            // Owner filter
                                            &hr,             // Result code
                                            L"XForm Out");   // Pin name

        // Can't fail
        ASSERT(SUCCEEDED(hr));
        if (m_pOutput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
        }
    }

    // Return the appropriate pin

    if (n == 0) {
        return m_pInput;
    } else
    if (n == 1) {
        return m_pOutput;
    } else {
        return NULL;
    }
}

//
// FindPin
//
// If Id is In or Out then return the IPin* for that pin
// creating the pin if need be.  Otherwise return NULL with an error.

STDMETHODIMP CTransformFilter::FindPin(LPCWSTR Id, IPin **ppPin)
{
    CheckPointer(ppPin,E_POINTER);
    ValidateReadWritePtr(ppPin,sizeof(IPin *));

    if (0==lstrcmpW(Id,L"In")) {
        *ppPin = GetPin(0);
    } else if (0==lstrcmpW(Id,L"Out")) {
        *ppPin = GetPin(1);
    } else {
        *ppPin = NULL;
        return VFW_E_NOT_FOUND;
    }

    HRESULT hr = NOERROR;
    //  AddRef() returned pointer - but GetPin could fail if memory is low.
    if (*ppPin) {
        (*ppPin)->AddRef();
    } else {
        hr = E_OUTOFMEMORY;  // probably.  There's no pin anyway.
    }
    return hr;
}

// override these two functions if you want to inform something
// about entry to or exit from streaming state.

HRESULT
CTransformFilter::StartStreaming()
{
    return NOERROR;
}

HRESULT
CTransformFilter::StopStreaming()
{
    return NOERROR;
}

// override this to grab extra interfaces on connection

HRESULT
CTransformFilter::CheckConnect(PIN_DIRECTION dir,IPin *pPin)
{
    UNREFERENCED_PARAMETER(dir);
    UNREFERENCED_PARAMETER(pPin);
    return NOERROR;
}

// place holder to allow derived classes to release any extra interfaces

HRESULT
CTransformFilter::BreakConnect(PIN_DIRECTION dir)
{
    UNREFERENCED_PARAMETER(dir);
    return NOERROR;
}

// Let derived classes know about connection completion

HRESULT
CTransformFilter::CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin)
{
    UNREFERENCED_PARAMETER(direction);
    UNREFERENCED_PARAMETER(pReceivePin);
    return NOERROR;
}

// override this to know when the media type is really set

HRESULT
CTransformFilter::SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt)
{
    UNREFERENCED_PARAMETER(direction);
    UNREFERENCED_PARAMETER(pmt);
    return NOERROR;
}

// Set up our output sample
HRESULT
CTransformFilter::InitializeOutputSample(IMediaSample *pSample, IMediaSample **ppOutSample)
{
    IMediaSample *pOutSample;

    // default - times are the same

    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    DWORD dwFlags = m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0;

    // This will prevent the image renderer from switching us to DirectDraw
    // when we can't do it without skipping frames because we're not on a
    // keyframe.  If it really has to switch us, it still will, but then we
    // will have to wait for the next keyframe
    if (!(pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT)) {
dwFlags |= AM_GBF_NOTASYNCPOINT;
    }

    ASSERT(m_pOutput->m_pAllocator != NULL);
    HRESULT hr = m_pOutput->m_pAllocator->GetBuffer(
             &pOutSample
             , pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID ?
                   &pProps->tStart : NULL
             , pProps->dwSampleFlags & AM_SAMPLE_STOPVALID ?
                   &pProps->tStop : NULL
             , dwFlags
         );
    *ppOutSample = pOutSample;
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT(pOutSample);
    IMediaSample2 *pOutSample2;
    if (SUCCEEDED(pOutSample->QueryInterface(IID_IMediaSample2,
                                             (void **)&pOutSample2))) {
        /*  Modify it */
        AM_SAMPLE2_PROPERTIES OutProps;
        EXECUTE_ASSERT(SUCCEEDED(pOutSample2->GetProperties(
            FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, tStart), (PBYTE)&OutProps)
        ));
        OutProps.dwTypeSpecificFlags = pProps->dwTypeSpecificFlags;
        OutProps.dwSampleFlags =
            (OutProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED) |
            (pProps->dwSampleFlags & ~AM_SAMPLE_TYPECHANGED);
        OutProps.tStart = pProps->tStart;
        OutProps.tStop  = pProps->tStop;
        OutProps.cbData = FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId);
        hr = pOutSample2->SetProperties(
            FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, dwStreamId),
            (PBYTE)&OutProps
        );
        if (pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) {
            m_bSampleSkipped = FALSE;
        }
        pOutSample2->Release();
    } else {
        if (pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) {
            pOutSample->SetTime(&pProps->tStart,
                                &pProps->tStop);
        }
        if (pProps->dwSampleFlags & AM_SAMPLE_SPLICEPOINT) {
            pOutSample->SetSyncPoint(TRUE);
        }
        if (pProps->dwSampleFlags & AM_SAMPLE_DATADISCONTINUITY) {
            pOutSample->SetDiscontinuity(TRUE);
            m_bSampleSkipped = FALSE;
        }
        // Copy the media times

        LONGLONG MediaStart, MediaEnd;
        if (pSample->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) {
            pOutSample->SetMediaTime(&MediaStart,&MediaEnd);
        }
    }
    return S_OK;
}

// override this to customize the transform process

HRESULT
CTransformFilter::Receive(IMediaSample *pSample)
{
    /*  Check for other streams and pass them on */
    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    if (pProps->dwStreamId != AM_STREAM_MEDIA) {
        return m_pOutput->m_pInputPin->Receive(pSample);
    }
    HRESULT hr;
    ASSERT(pSample);
    IMediaSample * pOutSample;

    // If no output to deliver to then no point sending us data

    ASSERT (m_pOutput != NULL) ;

    // Set up the output sample
    hr = InitializeOutputSample(pSample, &pOutSample);

    if (FAILED(hr)) {
        return hr;
    }

    // Start timing the transform (if PERF is defined)
    MSR_START(m_idTransform);

    // have the derived class transform the data

    hr = Transform(pSample, pOutSample);

    // Stop the clock and log it (if PERF is defined)
    MSR_STOP(m_idTransform);

    if (FAILED(hr)) {
DbgLog((LOG_TRACE,1,TEXT("Error from transform")));
    } else {
        // the Transform() function can return S_FALSE to indicate that the
        // sample should not be delivered; we only deliver the sample if it's
        // really S_OK (same as NOERROR, of course.)
        if (hr == NOERROR) {
        hr = m_pOutput->m_pInputPin->Receive(pOutSample);
        } else {
            // S_FALSE returned from Transform is a PRIVATE agreement
            // We should return NOERROR from Receive() in this cause because returning S_FALSE
            // from Receive() means that this is the end of the stream and no more data should
            // be sent.
            if (S_FALSE == hr) {

                //  Release the sample before calling notify to avoid
                //  deadlocks if the sample holds a lock on the system
                //  such as DirectDraw buffers do
                pOutSample->Release();
                m_bSampleSkipped = TRUE;
                if (!m_bQualityChanged) {
                    NotifyEvent(EC_QUALITY_CHANGE,0,0);
                    m_bQualityChanged = TRUE;
                }
                return NOERROR;
            }
        }
    }

    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    pOutSample->Release();

    return hr;
}

// Return S_FALSE to mean "pass the note on upstream"
// Return NOERROR (Same as S_OK)
// to mean "I've done something about it, don't pass it on"
HRESULT CTransformFilter::AlterQuality(Quality q)
{
    UNREFERENCED_PARAMETER(q);
    return S_FALSE;
}

// EndOfStream received. Default behaviour is to deliver straight
// downstream, since we have no queued data. If you overrode Receive
// and have queue data, then you need to handle this and deliver EOS after
// all queued data is sent
HRESULT
CTransformFilter::EndOfStream(void)
{
    HRESULT hr = NOERROR;
    if (m_pOutput != NULL) {
        hr = m_pOutput->DeliverEndOfStream();
    }

    return hr;
}

// enter flush state. Receives already blocked
// must override this if you have queued data or a worker thread
HRESULT
CTransformFilter::BeginFlush(void)
{
    HRESULT hr = NOERROR;
    if (m_pOutput != NULL) {
// block receives -- done by caller (CBaseInputPin::BeginFlush)

// discard queued data -- we have no queued data

// free anyone blocked on receive - not possible in this filter

// call downstream
hr = m_pOutput->DeliverBeginFlush();
    }
    return hr;
}

// leave flush state. must override this if you have queued data
// or a worker thread
HRESULT
CTransformFilter::EndFlush(void)
{
    // sync with pushing thread -- we have no worker thread

    // ensure no more data to go downstream -- we have no queued data

    // call EndFlush on downstream pins
    ASSERT (m_pOutput != NULL);
    return m_pOutput->DeliverEndFlush();

    // caller (the input pin's method) will unblock Receives
}

// override these so that the derived filter can catch them

STDMETHODIMP
CTransformFilter::Stop()
{
    CAutoLock lck1(&m_csFilter);
    if (m_State == State_Stopped) {
        return NOERROR;
    }

    // Succeed the Stop if we are not completely connected

    ASSERT(m_pInput == NULL || m_pOutput != NULL);
    if (m_pInput == NULL || m_pInput->IsConnected() == FALSE ||
        m_pOutput->IsConnected() == FALSE) {
                m_State = State_Stopped;
                m_bEOSDelivered = FALSE;
                return NOERROR;
    }

    ASSERT(m_pInput);
    ASSERT(m_pOutput);

    // decommit the input pin before locking or we can deadlock
    m_pInput->Inactive();

    // synchronize with Receive calls

    CAutoLock lck2(&m_csReceive);
    m_pOutput->Inactive();

    // allow a class derived from CTransformFilter
    // to know about starting and stopping streaming

    HRESULT hr = StopStreaming();
    if (SUCCEEDED(hr)) {
// complete the state transition
m_State = State_Stopped;
m_bEOSDelivered = FALSE;
    }
    return hr;
}

STDMETHODIMP
CTransformFilter::Pause()
{
    CAutoLock lck(&m_csFilter);
    HRESULT hr = NOERROR;

    if (m_State == State_Paused) {
        // (This space left deliberately blank)
    }

    // If we have no input pin or it isn't yet connected then when we are
    // asked to pause we deliver an end of stream to the downstream filter.
    // This makes sure that it doesn't sit there forever waiting for
    // samples which we cannot ever deliver without an input connection.

    else if (m_pInput == NULL || m_pInput->IsConnected() == FALSE) {
        if (m_pOutput && m_bEOSDelivered == FALSE) {
            m_pOutput->DeliverEndOfStream();
            m_bEOSDelivered = TRUE;
        }
        m_State = State_Paused;
    }

    // We may have an input connection but no output connection
    // However, if we have an input pin we do have an output pin

    else if (m_pOutput->IsConnected() == FALSE) {
        m_State = State_Paused;
    }

    else {
if (m_State == State_Stopped) {
    // allow a class derived from CTransformFilter
    // to know about starting and stopping streaming
            CAutoLock lck2(&m_csReceive);
    hr = StartStreaming();
}
if (SUCCEEDED(hr)) {
    hr = CBaseFilter::Pause();
}
    }

    m_bSampleSkipped = FALSE;
    m_bQualityChanged = FALSE;
    return hr;
}

HRESULT
CTransformFilter::NewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate)
{
    if (m_pOutput != NULL) {
        return m_pOutput->DeliverNewSegment(tStart, tStop, dRate);
    }
    return S_OK;
}

// Check streaming status
HRESULT
CTransformInputPin::CheckStreaming()
{
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if (!m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    } else {
        //  Shouldn't be able to get any data if we're not connected!
        ASSERT(IsConnected());

        //  Don't process stuff in Stopped state
        if (IsStopped()) {
            return VFW_E_WRONG_STATE;
        }
        if (m_bRunTimeError) {
        return VFW_E_RUNTIME_ERROR;
        }
        return S_OK;
    }
}

// =================================================================
// Implements the CTransformInputPin class
// =================================================================

// constructor

CTransformInputPin::CTransformInputPin(
    TCHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pName)
    : CBaseInputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pName)
{
    DbgLog((LOG_TRACE,2,TEXT("CTransformInputPin::CTransformInputPin")));
    m_pTransformFilter = pTransformFilter;
}

// provides derived filter a chance to grab extra interfaces

HRESULT
CTransformInputPin::CheckConnect(IPin *pPin)
{
    HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_INPUT,pPin);
    if (FAILED(hr)) {
    return hr;
    }
    return CBaseInputPin::CheckConnect(pPin);
}

// provides derived filter a chance to release it's extra interfaces

HRESULT
CTransformInputPin::BreakConnect()
{
    //  Can't disconnect unless stopped
    ASSERT(IsStopped());
    m_pTransformFilter->BreakConnect(PINDIR_INPUT);
    return CBaseInputPin::BreakConnect();
}

// Let derived class know when the input pin is connected

HRESULT
CTransformInputPin::CompleteConnect(IPin *pReceivePin)
{
    HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_INPUT,pReceivePin);
    if (FAILED(hr)) {
        return hr;
    }
    return CBaseInputPin::CompleteConnect(pReceivePin);
}

// check that we can support a given media type

HRESULT
CTransformInputPin::CheckMediaType(const CMediaType* pmt)
{
    // Check the input type

    HRESULT hr = m_pTransformFilter->CheckInputType(pmt);
    if (S_OK != hr) {
        return hr;
    }

    // if the output pin is still connected, then we have
    // to check the transform not just the input format

    if ((m_pTransformFilter->m_pOutput != NULL) &&
        (m_pTransformFilter->m_pOutput->IsConnected())) {
            return m_pTransformFilter->CheckTransform(
                      pmt,
      &m_pTransformFilter->m_pOutput->CurrentMediaType());
    } else {
        return hr;
    }
}

// set the media type for this connection

HRESULT
CTransformInputPin::SetMediaType(const CMediaType* mtIn)
{
    // Set the base class media type (should always succeed)
    HRESULT hr = CBasePin::SetMediaType(mtIn);
    ASSERT(SUCCEEDED(hr));

    // check the transform can be done (should always succeed)
    ASSERT(SUCCEEDED(m_pTransformFilter->CheckInputType(mtIn)));

    m_pTransformFilter->SetMediaType(PINDIR_INPUT,mtIn);
    return NOERROR;
}

// =================================================================
// Implements IMemInputPin interface
// =================================================================

// provide EndOfStream that passes straight downstream
// (there is no queued data)
STDMETHODIMP
CTransformInputPin::EndOfStream(void)
{
    CAutoLock lck(&m_pTransformFilter->m_csReceive);
    HRESULT hr = CheckStreaming();
    if (S_OK == hr) {
       hr = m_pTransformFilter->EndOfStream();
    }
    return hr;
}

// enter flushing state. Call default handler to block Receives, then
// pass to overridable method in filter
STDMETHODIMP
CTransformInputPin::BeginFlush(void)
{
    CAutoLock lck(&m_pTransformFilter->m_csFilter);
    //  Are we actually doing anything?
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if (!IsConnected() ||
        !m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    }
    HRESULT hr = CBaseInputPin::BeginFlush();
    if (FAILED(hr)) {
    return hr;
    }

    return m_pTransformFilter->BeginFlush();
}

// leave flushing state.
// Pass to overridable method in filter, then call base class
// to unblock receives (finally)
STDMETHODIMP
CTransformInputPin::EndFlush(void)
{
    CAutoLock lck(&m_pTransformFilter->m_csFilter);
    //  Are we actually doing anything?
    ASSERT(m_pTransformFilter->m_pOutput != NULL);
    if (!IsConnected() ||
        !m_pTransformFilter->m_pOutput->IsConnected()) {
        return VFW_E_NOT_CONNECTED;
    }

    HRESULT hr = m_pTransformFilter->EndFlush();
    if (FAILED(hr)) {
        return hr;
    }

    return CBaseInputPin::EndFlush();
}

// here's the next block of data from the stream.
// AddRef it yourself if you need to hold it beyond the end
// of this call.

HRESULT
CTransformInputPin::Receive(IMediaSample * pSample)
{
    HRESULT hr;
    CAutoLock lck(&m_pTransformFilter->m_csReceive);
    ASSERT(pSample);

    // check all is well with the base class
    hr = CBaseInputPin::Receive(pSample);
    if (S_OK == hr) {
        hr = m_pTransformFilter->Receive(pSample);
    }
    return hr;
}

// override to pass downstream
STDMETHODIMP
CTransformInputPin::NewSegment(
    REFERENCE_TIME tStart,
    REFERENCE_TIME tStop,
    double dRate)
{
    //  Save the values in the pin
    CBasePin::NewSegment(tStart, tStop, dRate);
    return m_pTransformFilter->NewSegment(tStart, tStop, dRate);
}

// =================================================================
// Implements the CTransformOutputPin class
// =================================================================

// constructor

CTransformOutputPin::CTransformOutputPin(
    TCHAR *pObjectName,
    CTransformFilter *pTransformFilter,
    HRESULT * phr,
    LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, pTransformFilter, &pTransformFilter->m_csFilter, phr, pPinName),
      m_pPosition(NULL)
{
    DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::CTransformOutputPin")));
    m_pTransformFilter = pTransformFilter;

}

// destructor

CTransformOutputPin::~CTransformOutputPin()
{
    DbgLog((LOG_TRACE,2,TEXT("CTransformOutputPin::~CTransformOutputPin")));

    if (m_pPosition) m_pPosition->Release();
}

// overriden to expose IMediaPosition and IMediaSeeking control interfaces

STDMETHODIMP
CTransformOutputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    ValidateReadWritePtr(ppv,sizeof(PVOID));
    *ppv = NULL;

    if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {

        // we should have an input pin by now

        ASSERT(m_pTransformFilter->m_pInput != NULL);

        if (m_pPosition == NULL) {

            HRESULT hr = CreatePosPassThru(
                             GetOwner(),
                             FALSE,
                             (IPin *)m_pTransformFilter->m_pInput,
                             &m_pPosition);
            if (FAILED(hr)) {
                return hr;
            }
        }
        return m_pPosition->QueryInterface(riid, ppv);
    } else {
        return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
    }
}

// provides derived filter a chance to grab extra interfaces

HRESULT
CTransformOutputPin::CheckConnect(IPin *pPin)
{
    // we should have an input connection first

    ASSERT(m_pTransformFilter->m_pInput != NULL);
    if ((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) {
    return E_UNEXPECTED;
    }

    HRESULT hr = m_pTransformFilter->CheckConnect(PINDIR_OUTPUT,pPin);
    if (FAILED(hr)) {
    return hr;
    }
    return CBaseOutputPin::CheckConnect(pPin);
}

// provides derived filter a chance to release it's extra interfaces

HRESULT
CTransformOutputPin::BreakConnect()
{
    //  Can't disconnect unless stopped
    ASSERT(IsStopped());
    m_pTransformFilter->BreakConnect(PINDIR_OUTPUT);
    return CBaseOutputPin::BreakConnect();
}

// Let derived class know when the output pin is connected

HRESULT
CTransformOutputPin::CompleteConnect(IPin *pReceivePin)
{
    HRESULT hr = m_pTransformFilter->CompleteConnect(PINDIR_OUTPUT,pReceivePin);
    if (FAILED(hr)) {
        return hr;
    }
    return CBaseOutputPin::CompleteConnect(pReceivePin);
}

// check a given transform - must have selected input type first

HRESULT
CTransformOutputPin::CheckMediaType(const CMediaType* pmtOut)
{
    // must have selected input first
    ASSERT(m_pTransformFilter->m_pInput != NULL);
    if ((m_pTransformFilter->m_pInput->IsConnected() == FALSE)) {
        return E_INVALIDARG;
    }

    return m_pTransformFilter->CheckTransform(
    &m_pTransformFilter->m_pInput->CurrentMediaType(),
    pmtOut);
}

// called after we have agreed a media type to actually set it in which case
// we run the CheckTransform function to get the output format type again

HRESULT
CTransformOutputPin::SetMediaType(const CMediaType* pmtOut)
{
    HRESULT hr = NOERROR;
    ASSERT(m_pTransformFilter->m_pInput != NULL);

    ASSERT(m_pTransformFilter->m_pInput->CurrentMediaType().IsValid());

    // Set the base class media type (should always succeed)
    hr = CBasePin::SetMediaType(pmtOut);
    ASSERT(SUCCEEDED(hr));

#ifdef DEBUG
    if (FAILED(m_pTransformFilter->CheckTransform(&m_pTransformFilter->
m_pInput->CurrentMediaType(),pmtOut))) {
DbgLog((LOG_ERROR,0,TEXT("*** This filter is accepting an output media type")));
DbgLog((LOG_ERROR,0,TEXT("    that it can't currently transform to.  I hope")));
DbgLog((LOG_ERROR,0,TEXT("    it's smart enough to reconnect its input.")));
    }
#endif

    m_pTransformFilter->SetMediaType(PINDIR_OUTPUT,pmtOut);
    return NOERROR;
}

// pass the buffer size decision through to the main transform class

HRESULT
CTransformOutputPin::DecideBufferSize(
    IMemAllocator * pAllocator,
    ALLOCATOR_PROPERTIES* pProp)
{
    return m_pTransformFilter->DecideBufferSize(pAllocator, pProp);
}

// return a specific media type indexed by iPosition

HRESULT
CTransformOutputPin::GetMediaType(
    int iPosition,
    CMediaType *pMediaType)
{
    CAutoLock lck(&m_pTransformFilter->m_csFilter);
    ASSERT(m_pTransformFilter->m_pInput != NULL);

    //  We don't have any media types if our input is not connected

    if (m_pTransformFilter->m_pInput->IsConnected()) {
        return m_pTransformFilter->GetMediaType(iPosition,pMediaType);
    } else {
        return VFW_S_NO_MORE_ITEMS;
    }
}

// Override this if you can do something constructive to act on the
// quality message.  Consider passing it upstream as well

// Pass the quality mesage on upstream.

STDMETHODIMP
CTransformOutputPin::Notify(IBaseFilter * pSender, Quality q)
{
    UNREFERENCED_PARAMETER(pSender);
    ValidateReadPtr(pSender,sizeof(IBaseFilter));

    // First see if we want to handle this ourselves
    HRESULT hr = m_pTransformFilter->AlterQuality(q);
    if (hr!=S_FALSE) {
        return hr;        // either S_OK or a failure
    }

    // S_FALSE means we pass the message on.
    // Find the quality sink for our input pin and send it there

    ASSERT(m_pTransformFilter->m_pInput != NULL);

    return m_pTransformFilter->m_pInput->PassNotify(q);

} // Notify

// the following removes a very large number of level 4 warnings from the microsoft
// compiler output, which are not useful at all in this case.
#pragma warning(disable:4514)


TRANSFRM.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// base class from which you can derive a simple transform codec.
//
// It assumes the codec has one input and one output stream, and has no
// interest in memory management, interface negotiation or anything else.
//
// derive your class from this, and supply Transform and the media type/format
// negotiation functions. Implement that class, compile and link and
// you're done.

#ifndef __TRANSFRM__
#define __TRANSFRM__

// ======================================================================
// This is the com object that represents a simple transform filter. It
// supports IBaseFilter, IMediaFilter and two pins through nested interfaces
// ======================================================================

class CTransformFilter;

// ==================================================
// Implements the input pin
// ==================================================

class CTransformInputPin : public CBaseInputPin
{
    friend class CTransformFilter;

protected:
    CTransformFilter *m_pTransformFilter;

public:

    CTransformInputPin(
        TCHAR *pObjectName,
        CTransformFilter *pTransformFilter,
        HRESULT * phr,
        LPCWSTR pName);

    STDMETHODIMP QueryId(LPWSTR * Id)
    {
        return AMGetWideString(L"In", Id);
    }

    // Grab and release extra interfaces if required

    HRESULT CheckConnect(IPin *pPin);
    HRESULT BreakConnect();
    HRESULT CompleteConnect(IPin *pReceivePin);

    // check that we can support this output type
    HRESULT CheckMediaType(const CMediaType* mtIn);

    // set the connection media type
    HRESULT SetMediaType(const CMediaType* mt);

    // --- IMemInputPin -----

    // here's the next block of data from the stream.
    // AddRef it yourself if you need to hold it beyond the end
    // of this call.
    STDMETHODIMP Receive(IMediaSample * pSample);

    // provide EndOfStream that passes straight downstream
    // (there is no queued data)
    STDMETHODIMP EndOfStream(void);

    // passes it to CTransformFilter::BeginFlush
    STDMETHODIMP BeginFlush(void);

    // passes it to CTransformFilter::EndFlush
    STDMETHODIMP EndFlush(void);

    STDMETHODIMP NewSegment(
                        REFERENCE_TIME tStart,
                        REFERENCE_TIME tStop,
                        double dRate);

    // Check if it's OK to process samples
    virtual HRESULT CheckStreaming();

    // Media type
public:
    CMediaType& CurrentMediaType() { return m_mt; };

};

// ==================================================
// Implements the output pin
// ==================================================

class CTransformOutputPin : public CBaseOutputPin
{
    friend class CTransformFilter;

protected:
    CTransformFilter *m_pTransformFilter;

public:

    // implement IMediaPosition by passing upstream
    IUnknown * m_pPosition;

    CTransformOutputPin(
        TCHAR *pObjectName,
        CTransformFilter *pTransformFilter,
        HRESULT * phr,
        LPCWSTR pName);

    ~CTransformOutputPin();

    // override to expose IMediaPosition
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);

    // --- CBaseOutputPin ------------

    STDMETHODIMP QueryId(LPWSTR * Id)
    {
        return AMGetWideString(L"Out", Id);
    }

    // Grab and release extra interfaces if required

    HRESULT CheckConnect(IPin *pPin);
    HRESULT BreakConnect();
    HRESULT CompleteConnect(IPin *pReceivePin);

    // check that we can support this output type
    HRESULT CheckMediaType(const CMediaType* mtOut);

    // set the connection media type
    HRESULT SetMediaType(const CMediaType *pmt);

    // called from CBaseOutputPin during connection to ask for
    // the count and size of buffers we need.
    HRESULT DecideBufferSize(
                IMemAllocator * pAlloc,
                ALLOCATOR_PROPERTIES *pProp);

    // returns the preferred formats for a pin
    HRESULT GetMediaType(int iPosition,CMediaType *pMediaType);

    // inherited from IQualityControl via CBasePin
    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

    // Media type
public:
    CMediaType& CurrentMediaType() { return m_mt; };
};

class AM_NOVTABLE CTransformFilter : public CBaseFilter
{

public:

    // map getpin/getpincount for base enum of pins to owner
    // override this to return more specialised pin objects

    virtual int GetPinCount();
    virtual CBasePin * GetPin(int n);
    STDMETHODIMP FindPin(LPCWSTR Id, IPin **ppPin);

    // override state changes to allow derived transform filter
    // to control streaming start/stop
    STDMETHODIMP Stop();
    STDMETHODIMP Pause();

public:

    CTransformFilter(TCHAR *, LPUNKNOWN, REFCLSID clsid);
    ~CTransformFilter();

    // =================================================================
    // ----- override these bits ---------------------------------------
    // =================================================================

    // These must be supplied in a derived class

    virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut);

    // check if you can support mtIn
    virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE;

    // check if you can support the transform from this input to this output
    virtual HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut) PURE;

    // this goes in the factory template table to create new instances
    // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);

    // call the SetProperties function with appropriate arguments
    virtual HRESULT DecideBufferSize(
                        IMemAllocator * pAllocator,
                        ALLOCATOR_PROPERTIES *pprop) PURE;

    // override to suggest OUTPUT pin media types
    virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE;

    // =================================================================
    // ----- Optional Override Methods           -----------------------
    // =================================================================

    // you can also override these if you want to know about streaming
    virtual HRESULT StartStreaming();
    virtual HRESULT StopStreaming();

    // override if you can do anything constructive with quality notifications
    virtual HRESULT AlterQuality(Quality q);

    // override this to know when the media type is actually set
    virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt);

    // chance to grab extra interfaces on connection
    virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin);
    virtual HRESULT BreakConnect(PIN_DIRECTION dir);
    virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin);

    // chance to customize the transform process
    virtual HRESULT Receive(IMediaSample *pSample);

    // Standard setup for output sample
    HRESULT InitializeOutputSample(IMediaSample *pSample, IMediaSample **ppOutSample);

    // if you override Receive, you may need to override these three too
    virtual HRESULT EndOfStream(void);
    virtual HRESULT BeginFlush(void);
    virtual HRESULT EndFlush(void);
    virtual HRESULT NewSegment(
                        REFERENCE_TIME tStart,
                        REFERENCE_TIME tStop,
                        double dRate);

#ifdef PERF
    // Override to register performance measurement with a less generic string
    // You should do this to avoid confusion with other filters
    virtual void RegisterPerfId()
         {m_idTransform = MSR_REGISTER(TEXT("Transform"));}
#endif // PERF

// implementation details

protected:

#ifdef PERF
    int m_idTransform;                 // performance measuring id
#endif
    BOOL m_bEOSDelivered;              // have we sent EndOfStream
    BOOL m_bSampleSkipped;             // Did we just skip a frame
    BOOL m_bQualityChanged;            // Have we degraded?

    // critical section protecting filter state.

    CCritSec m_csFilter;

    // critical section stopping state changes (ie Stop) while we're
    // processing a sample.
    //
    // This critical section is held when processing
    // events that occur on the receive thread - Receive() and EndOfStream().
    //
    // If you want to hold both m_csReceive and m_csFilter then grab
    // m_csFilter FIRST - like CTransformFilter::Stop() does.

    CCritSec m_csReceive;

    // these hold our input and output pins

    friend class CTransformInputPin;
    friend class CTransformOutputPin;
    CTransformInputPin *m_pInput;
    CTransformOutputPin *m_pOutput;
};

#endif /* __TRANSFRM__ */


TRANSIP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Base class for simple Transform-In-Place filters such as audio

// How allocators are decided.
//
// An in-place transform tries to do its work in someone else's buffers.
// It tries to persuade the filters on either side to use the same allocator
// (and for that matter the same media type).  In desperation, if the downstream
// filter refuses to supply an allocator and the upstream filter offers only
// a read-only one then it will provide an allocator.
// if the upstream filter insists on a read-only allocator then the transform
// filter will (reluctantly) copy the data before transforming it.
//
// In order to pass an allocator through it needs to remember the one it got
// from the first connection to pass it on to the second one.
//
// It is good if we can avoid insisting on a particular order of connection
// (There is a precedent for insisting on the input
// being connected first.  Insisting on the output being connected first is
// not allowed.  That would break RenderFile.)
//
// The base pin classes (CBaseOutputPin and CBaseInputPin) both have a
// m_pAllocator member which is used in places like
// CBaseOutputPin::GetDeliveryBuffer and CBaseInputPin::Inactive.
// To avoid lots of extra overriding, we should keep these happy
// by using these pointers.
//
// When each pin is connected, it will set the corresponding m_pAllocator
// and will have a single ref-count on that allocator.
//
// Refcounts are acquired by GetAllocator calls which return AddReffed
// allocators and are released in one of:
//     CBaseInputPin::Disconnect
//     CBaseOutputPin::BreakConect
// In each case m_pAllocator is set to NULL after the release, so this
// is the last chance to ever release it.  If there should ever be
// multiple refcounts associated with the same pointer, this had better
// be cleared up before that happens.  To avoid such problems, we'll
// stick with one per pointer.

// RECONNECTING and STATE CHANGES
//
// Each pin could be disconnected, connected with a read-only allocator,
// connected with an upstream read/write allocator, connected with an
// allocator from downstream or connected with its own allocator.
// Five states for each pin gives a data space of 25 states.
//
// Notation:
//
// R/W == read/write
// R-O == read-only
//
// <input pin state> <output pin state> <comments>
//
// 00 means an unconnected pin.
// <- means using a R/W allocator from the upstream filter
// <= means using a R-O allocator from an upstream filter
// || means using our own (R/W) allocator.
// -> means using a R/W allocator from a downstream filter
//    (a R-O allocator from downstream is nonsense, it can't ever work).
//
//
// That makes 25 possible states.  Some states are nonsense (two different
// allocators from the same place).  These are just an artifact of the notation.
//        <=  <-  Nonsense.
//        <-  <=  Nonsense
// Some states are illegal (the output pin never accepts a R-O allocator):
//        00  <=  !! Error !!
//        <=  <=  !! Error !!
//        ||  <=  !! Error !!
//        ->  <=  !! Error !!
// Three states appears to be inaccessible:
//        ->  ||  Inaccessible
//        ||  ->  Inaccessible
//        ||  <-  Inaccessible
// Some states only ever occur as intermediates with a pending reconnect which
// is guaranteed to finish in another state.
//        ->  00  ?? unstable goes to || 00
//        00  <-  ?? unstable goes to 00 ||
//        ->  <-  ?? unstable goes to -> ->
//        <-  ||  ?? unstable goes to <- <-
//        <-  ->  ?? unstable goes to <- <-
// And that leaves 11 possible resting states:
// 1      00  00  Nothing connected.
// 2      <-  00  Input pin connected.
// 3      <=  00  Input pin connected using R-O allocator.
// 4      ||  00  Needs several state changes to get here.
// 5      00  ||  Output pin connected using our allocator
// 6      00  ->  Downstream only connected
// 7      ||  ||  Undesirable but can be forced upon us.
// 8      <=  ||  Copy forced.  <=  -> is preferable
// 9      <=  ->  OK - forced to copy.
// 10     <-  <-  Transform in place (ideal)
// 11     ->  ->  Transform in place (ideal)
//
// The object of the exercise is to ensure that we finish up in states
// 10 or 11 whenever possible.  State 10 is only possible if the upstream
// filter has a R/W allocator (the AVI splitter notoriously
// doesn't) and state 11 is only possible if the downstream filter does
// offer an allocator.
//
// The transition table (entries marked * go via a reconnect)
//
// There are 8 possible transitions:
// A: Connect upstream to filter with R-O allocator that insists on using it.
// B: Connect upstream to filter with R-O allocator but chooses not to use it.
// C: Connect upstream to filter with R/W allocator and insists on using it.
// D: Connect upstream to filter with R/W allocator but chooses not to use it.
// E: Connect downstream to a filter that offers an allocator
// F: Connect downstream to a filter that does not offer an allocator
// G: disconnect upstream
// H: Disconnect downstream
//
//            A      B      C      D      E      F      G      H
//           ---------------------------------------------------------
// 00  00 1 | 3      3      2      2      6      5      .      .      |1  00  00
// <-  00 2 | .      .      .      .      *10/11 10     1      .      |2  <-  00
// <=  00 3 | .      .      .      .      *9/11  *7/8   1      .      |3  <=  00
// ||  00 4 | .      .      .      .      *8     *7     1      .      |4  ||  00
// 00  || 5 | 8      7      *10    7      .      .      .      1      |5  00  ||
// 00  -> 6 | 9      11     *10    11     .      .      .      1      |6  00  ->
// ||  || 7 | .      .      .      .      .      .      5      4      |7  ||  ||
// <=  || 8 | .      .      .      .      .      .      5      3      |8  <=  ||
// <=  -> 9 | .      .      .      .      .      .      6      3      |9  <=  ->
// <-  <- 10| .      .      .      .      .      .      *5/6   2      |10 <-  <-
// ->  -> 11| .      .      .      .      .      .      6      *2/3   |11 ->  ->
//           ---------------------------------------------------------
//            A      B      C      D      E      F      G      H
//
// All these states are accessible without requiring any filter to
// change its behaviour but not all transitions are accessible, for
// instance a transition from state 4 to anywhere other than
// state 8 requires that the upstream filter first offer a R-O allocator
// and then changes its mind and offer R/W.  This is NOT allowable - it
// leads to things like the output pin getting a R/W allocator from
// upstream and then the input pin being told it can only have a R-O one.
// Note that you CAN change (say) the upstream filter for a different one, but
// only as a disconnect / connect, not as a Reconnect.  (Exercise for
// the reader is to see how you get into state 4).
//
// The reconnection stuff goes as follows (some of the cases shown here as
// "no reconnect" may get one to finalise media type - an old story).
// If there is a reconnect where it says "no reconnect" here then the
// reconnection must not change the allocator choice.
//
// state 2: <- 00 transition E <- <- case C <- <- (no change)
//                                   case D -> <- and then to -> ->
//
// state 2: <- 00 transition F <- <- (no reconnect)
//
// state 3: <= 00 transition E <= -> case A <= -> (no change)
//                                   case B -> ->
//                transition F <= || case A <= || (no change)
//                                   case B || ||
//
// state 4: || 00 transition E || || case B -> || and then all cases to -> ->
//                           F || || case B || || (no change)
//
// state 5: 00 || transition A <= || (no reconnect)
//                           B || || (no reconnect)
//                           C <- || all cases     <- <-
//                           D || || (unfortunate, but upstream's choice)
//
// state 6: 00 -> transition A <= -> (no reconnect)
//                           B -> -> (no reconnect)
//                           C <- -> all cases <- <-
//                           D -> -> (no reconnect)
//
// state 10:<- <- transition G 00 <- case E 00 ->
//                                   case F 00 ||
//
// state 11:-> -> transition H -> 00 case A <= 00 (schizo)
//                                   case B <= 00
//                                   case C <- 00 (schizo)
//                                   case D <- 00
//
// The Rules:
// To sort out media types:
// The input is reconnected
//    if the input pin is connected and the output pin connects
// The output is reconnected
//    If the output pin is connected
//    and the input pin connects to a different media type
//
// To sort out allocators:
// The input is reconnected
//    if the output disconnects and the input was using a downstream allocator
// The output pin calls SetAllocator to pass on a new allocator
//    if the output is connected and
//       if the input disconnects and the output was using an upstream allocator
//       if the input acquires an allocator different from the output one
//          and that new allocator is not R-O
//
// Data is copied (i.e. call getbuffer and copy the data before transforming it)
//    if the two allocators are different.

// CHAINS of filters:
//
// We sit between two filters (call them A and Z).  We should finish up
// with the same allocator on both of our pins and that should be the
// same one that A and Z would have agreed on if we hadn't been in the
// way.  Furthermore, it should not matter how many in-place transforms
// are in the way.  Let B, C, D... be in-place transforms ("us").
// Here's how it goes:
//
// 1.
// A connects to B.  They agree on A's allocator.
//   A-a->B
//
// 2.
// B connects to C.  Same story. There is no point in a reconnect, but
// B will request an input reconnect anyway.
//   A-a->B-a->C
//
// 3.
// C connects to Z.
// C insists on using A's allocator, but compromises by requesting a reconnect.
// of C's input.
//   A-a->B-?->C-a->Z
//
// We now have pending reconnects on both A--->B and B--->C
//
// 4.
// The A--->B link is reconnected.
// A asks B for an allocator.  B sees that it has a downstream connection so
// asks its downstream input pin i.e. C's input pin for an allocator.  C sees
// that it too has a downstream connection so asks Z for an allocator.
//
// Even though Z's input pin is connected, it is being asked for an allocator.
// It could refuse, in which case the chain is done and will use A's allocator
// Alternatively, Z may supply one.  A chooses either Z's or A's own one.
// B's input pin gets NotifyAllocator called to tell it the decision and it
// propagates this downstream by calling ReceiveAllocator on its output pin
// which calls NotifyAllocator on the next input pin downstream etc.
// If the choice is Z then it goes:
//   A-z->B-a->C-a->Z
//   A-z->B-z->C-a->Z
//   A-z->B-z->C-z->Z
//
// And that's IT!!  Any further (essentially spurious) reconnects peter out
// with no change in the chain.

#include <streams.h>
#include <measure.h>
#include <transip.h>

// =================================================================
// Implements the CTransInPlaceFilter class
// =================================================================

CTransInPlaceFilter::CTransInPlaceFilter
   ( TCHAR     *pName,
     LPUNKNOWN  pUnk,
     REFCLSID   clsid,
     HRESULT   *phr
   )
   : CTransformFilter(pName, pUnk, clsid)
{
#ifdef PERF
    RegisterPerfId();
#endif //  PERF

} // constructor

// return a non-addrefed CBasePin * for the user to addref if he holds onto it
// for longer than his pointer to us. We create the pins dynamically when they
// are asked for rather than in the constructor. This is because we want to
// give the derived class an oppportunity to return different pin objects

// As soon as any pin is needed we create both (this is different from the
// usual transform filter) because enumerators, allocators etc are passed
// through from one pin to another and it becomes very painful if the other
// pin isn't there.  If we fail to create either pin we ensure we fail both.

CBasePin *
CTransInPlaceFilter::GetPin(int n)
{
    HRESULT hr = S_OK;

    // Create an input pin if not already done

    if (m_pInput == NULL) {

        m_pInput = new CTransInPlaceInputPin( NAME("TransInPlace input pin")
                                            , this        // Owner filter
                                            , &hr         // Result code
                                            , L"Input"    // Pin name
                                            );

        // Constructor for CTransInPlaceInputPin can't fail
        ASSERT(SUCCEEDED(hr));
    }

    // Create an output pin if not already done

    if (m_pInput!=NULL && m_pOutput == NULL) {

        m_pOutput = new CTransInPlaceOutputPin( NAME("TransInPlace output pin")
                                              , this       // Owner filter
                                              , &hr        // Result code
                                              , L"Output"  // Pin name
                                              );

        // a failed return code should delete the object

        ASSERT(SUCCEEDED(hr));
        if (m_pOutput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
        }
    }

    // Return the appropriate pin

    ASSERT (n>=0 && n<=1);
    if (n == 0) {
        return m_pInput;
    } else if (n==1) {
        return m_pOutput;
    } else {
        return NULL;
    }

} // GetPin

// dir is the direction of our pin.
// pReceivePin is the pin we are connecting to.
HRESULT CTransInPlaceFilter::CompleteConnect(PIN_DIRECTION dir,IPin *pReceivePin)
{
    UNREFERENCED_PARAMETER(pReceivePin);
    ASSERT(m_pInput);
    ASSERT(m_pOutput);

    // if we are not part of a graph, then don't indirect the pointer
    // this probably prevents use of the filter without a filtergraph
    if (!m_pGraph) {
        return VFW_E_NOT_IN_GRAPH;
    }

    // Always reconnect the input to account for buffering changes
    //
    // Because we don't get to suggest a type on ReceiveConnection
    // we need another way of making sure the right type gets used.
    //
    // One way would be to have our EnumMediaTypes return our output
    // connection type first but more deterministic and simple is to
    // call ReconnectEx passing the type we want to reconnect with
    // via the base class ReconeectPin method.

    if (dir == PINDIR_OUTPUT) {
        if( m_pInput->IsConnected() ) {
            return ReconnectPin( m_pInput, &m_pOutput->CurrentMediaType() );
        }
        return NOERROR;
    }

    ASSERT(dir == PINDIR_INPUT);

    // Reconnect output if necessary

    if( m_pOutput->IsConnected() ) {

        if (  m_pInput->CurrentMediaType()
           != m_pOutput->CurrentMediaType()
           ) {
            return ReconnectPin( m_pOutput, &m_pInput->CurrentMediaType() );
        }
    }
    return NOERROR;

} // ComnpleteConnect

//
// DecideBufferSize
//
// Tell the output pin's allocator what size buffers we require.
// *pAlloc will be the allocator our output pin is using.

HRESULT CTransInPlaceFilter::DecideBufferSize
            ( IMemAllocator *pAlloc
            , ALLOCATOR_PROPERTIES *pProperties
            )
{
    ALLOCATOR_PROPERTIES Request, Actual;
    HRESULT hr;

    // If we are connected upstream, get his views
    if (m_pInput->IsConnected()) {
        // Get the input pin allocator, and get its size and count.
        // we don't care about his alignment and prefix.

        hr = InputPin()->PeekAllocator()->GetProperties(&Request);
        if (FAILED(hr)) {
            // Input connected but with a secretive allocator - enough!
            return hr;
        }
    } else {
        // We're reduced to blind guessing.  Let's guess one byte and if
        // this isn't enough then when the other pin does get connected
        // we can revise it.
        ZeroMemory(&Request, sizeof(Request));
        Request.cBuffers = 1;
        Request.cbBuffer = 1;
    }

    DbgLog((LOG_MEMORY,1,TEXT("Setting Allocator Requirements")));
    DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d"),
           Request.cBuffers, Request.cbBuffer));

    // Pass the allocator requirements to our output side
    // but do a little sanity checking first or we'll just hit
    // asserts in the allocator.

    pProperties->cBuffers = Request.cBuffers;
    pProperties->cbBuffer = Request.cbBuffer;
    if (pProperties->cBuffers<=0) {pProperties->cBuffers = 1; }
    if (pProperties->cbBuffer<=0) {pProperties->cbBuffer = 1; }
    hr = pAlloc->SetProperties(pProperties, &Actual);

    if (FAILED(hr)) {
        return hr;
    }

    DbgLog((LOG_MEMORY,1,TEXT("Obtained Allocator Requirements")));
    DbgLog((LOG_MEMORY,1,TEXT("Count %d, Size %d, Alignment %d"),
           Actual.cBuffers, Actual.cbBuffer, Actual.cbAlign));

    // Make sure we got the right alignment and at least the minimum required

    if (  (Request.cBuffers > Actual.cBuffers)
       || (Request.cbBuffer > Actual.cbBuffer)
       || (Request.cbAlign  > Actual.cbAlign)
       ) {
        return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

//
// Copy
//
// return a pointer to an identical copy of pSample
IMediaSample * CTransInPlaceFilter::Copy(IMediaSample *pSource)
{
    IMediaSample * pDest;

    HRESULT hr;
    REFERENCE_TIME tStart, tStop;
    const BOOL bTime = S_OK == pSource->GetTime( &tStart, &tStop);

    // this may block for an indeterminate amount of time
    hr = OutputPin()->PeekAllocator()->GetBuffer(
              &pDest
              , bTime ? &tStart : NULL
              , bTime ? &tStop : NULL
              , m_bSampleSkipped ? AM_GBF_PREVFRAMESKIPPED : 0
              );

    if (FAILED(hr)) {
        return NULL;
    }

    ASSERT(pDest);
    IMediaSample2 *pSample2;
    if (SUCCEEDED(pDest->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) {
        HRESULT hr = pSample2->SetProperties(
            FIELD_OFFSET(AM_SAMPLE2_PROPERTIES, pbBuffer),
            (PBYTE)m_pInput->SampleProps());
        pSample2->Release();
        if (FAILED(hr)) {
            pDest->Release();
            return NULL;
        }
    } else {
        if (bTime) {
            pDest->SetTime(&tStart, &tStop);
        }

        if (S_OK == pSource->IsSyncPoint()) {
            pDest->SetSyncPoint(TRUE);
        }
        if (S_OK == pSource->IsDiscontinuity() || m_bSampleSkipped) {
            pDest->SetDiscontinuity(TRUE);
        }
        if (S_OK == pSource->IsPreroll()) {
            pDest->SetPreroll(TRUE);
        }

        // Copy the media type
        AM_MEDIA_TYPE *pMediaType;
        if (S_OK == pSource->GetMediaType(&pMediaType)) {
            pDest->SetMediaType(pMediaType);
            DeleteMediaType( pMediaType );
        }

    }

    m_bSampleSkipped = FALSE;

    // Copy the sample media times
    REFERENCE_TIME TimeStart, TimeEnd;
    if (pSource->GetMediaTime(&TimeStart,&TimeEnd) == NOERROR) {
        pDest->SetMediaTime(&TimeStart,&TimeEnd);
    }

    // Copy the actual data length and the actual data.
    {
        const long lDataLength = pSource->GetActualDataLength();
        pDest->SetActualDataLength(lDataLength);

        // Copy the sample data
        {
            BYTE *pSourceBuffer, *pDestBuffer;
            long lSourceSize  = pSource->GetSize();
            long lDestSize = pDest->GetSize();

            ASSERT(lDestSize >= lSourceSize && lDestSize >= lDataLength);

            pSource->GetPointer(&pSourceBuffer);
            pDest->GetPointer(&pDestBuffer);
            ASSERT(lDestSize == 0 || pSourceBuffer != NULL && pDestBuffer != NULL);

            CopyMemory( (PVOID) pDestBuffer, (PVOID) pSourceBuffer, lDataLength );
        }
    }

    return pDest;

} // Copy

// override this to customize the transform process

HRESULT
CTransInPlaceFilter::Receive(IMediaSample *pSample)
{
    /*  Check for other streams and pass them on */
    AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps();
    if (pProps->dwStreamId != AM_STREAM_MEDIA) {
        return m_pOutput->Deliver(pSample);
    }
    HRESULT hr;

    // Start timing the TransInPlace (if PERF is defined)
    MSR_START(m_idTransInPlace);

    if (InputPin()->PeekAllocator() != OutputPin()->PeekAllocator()) {

        // We have to copy the data.

        pSample = Copy(pSample);

        if (pSample==NULL) {
            MSR_STOP(m_idTransInPlace);
            return E_UNEXPECTED;
        }
    }

    // have the derived class transform the data
    hr = Transform(pSample);

    // Stop the clock and log it (if PERF is defined)
    MSR_STOP(m_idTransInPlace);

    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, 1, TEXT("Error from TransInPlace")));
        if (InputPin()->PeekAllocator() != OutputPin()->PeekAllocator()) {
            pSample->Release();
        }
        return hr;
    }

    // the Transform() function can return S_FALSE to indicate that the
    // sample should not be delivered; we only deliver the sample if it's
    // really S_OK (same as NOERROR, of course.)
    if (hr == NOERROR) {
        hr = m_pOutput->Deliver(pSample);
    } else {
        //  But it would be an error to return this private hack
        //  to the caller ...
        if (S_FALSE == hr) {
            // S_FALSE returned from Transform is a PRIVATE agreement
            // We should return NOERROR from Receive() in this cause because returning S_FALSE
            // from Receive() means that this is the end of the stream and no more data should
            // be sent.
            m_bSampleSkipped = TRUE;
            if (!m_bQualityChanged) {
                NotifyEvent(EC_QUALITY_CHANGE,0,0);
                m_bQualityChanged = TRUE;
            }
            hr = NOERROR;
        }
    }

    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    if (InputPin()->PeekAllocator() != OutputPin()->PeekAllocator()) {
        pSample->Release();
    }

    return hr;

} // Receive

// =================================================================
// Implements the CTransInPlaceInputPin class
// =================================================================

// constructor

CTransInPlaceInputPin::CTransInPlaceInputPin
    ( TCHAR               *pObjectName
    , CTransInPlaceFilter *pFilter
    , HRESULT             *phr
    , LPCWSTR              pName
    )
    : CTransformInputPin(pObjectName,
                         pFilter,
                         phr,
                         pName)
    , m_bReadOnly(FALSE)
{
    DbgLog((LOG_TRACE, 2
           , TEXT("CTransInPlaceInputPin::CTransInPlaceInputPin")));
    m_pTIPFilter = pFilter;

} // constructor

// =================================================================
// Implements IMemInputPin interface
// =================================================================

// If the downstream filter has one then offer that (even if our own output
// pin is not using it yet.  If the upstream filter chooses it then we will
// tell our output pin to ReceiveAllocator).
// Else if our output pin is using an allocator then offer that.
//     ( This could mean offering the upstream filter his own allocator,
//       it could mean offerring our own
//     ) or it could mean offering the one from downstream
// Else fail to offer any allocator at all.

STDMETHODIMP CTransInPlaceInputPin::GetAllocator(IMemAllocator ** ppAllocator)
{
    CheckPointer(ppAllocator,E_POINTER);
    ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *));
    CAutoLock cObjectLock(m_pLock);

    if ( m_pTIPFilter->m_pOutput->IsConnected() ){
        HRESULT hr = m_pTIPFilter->OutputPin()->ConnectedIMemInputPin()
                                               ->GetAllocator( ppAllocator );
        if( SUCCEEDED( hr ) ){
            // the downstream GetAllocator will have done AddRef on it
            return hr;
        }
        else {
            *ppAllocator = m_pTIPFilter->OutputPin()->PeekAllocator();
            (*ppAllocator)->AddRef();
            return S_OK;
        }

    }

    return VFW_E_NO_ALLOCATOR;

} // GetAllocator

/* Get told which allocator the upstream output pin is actually going to use */

STDMETHODIMP
CTransInPlaceInputPin::NotifyAllocator(
    IMemAllocator * pAllocator,
    BOOL bReadOnly)
{
    HRESULT hr;
    CheckPointer(pAllocator,E_POINTER);
    ValidateReadPtr(pAllocator,sizeof(IMemAllocator));

    m_bReadOnly = bReadOnly;
    // we are an in-place transform.  We scribble on the buffer.
    // But if it's ReadOnly we're going to have to copy it first.

    CAutoLock cObjectLock(m_pLock);

    // It's possible that the old and the new are the same thing.
    // AddRef before release ensures that we don't unload it.
    pAllocator->AddRef();

    if( m_pAllocator != NULL )
        m_pAllocator->Release();

    m_pAllocator = pAllocator;    // We have an allocator for the input pin

    // Propagate the decision downstream - do this always, even if it's
    // a read-only allocator.  The Receive function will take what it can.
    if ( m_pTIPFilter->OutputPin()->IsConnected() ) {
        hr = m_pTIPFilter->OutputPin()->ReceiveAllocator(pAllocator, bReadOnly);
        if (FAILED(hr)) {
            // The output connection would be screwed by this input connection
            // so refuse it!
            return hr;
        }
    }

    return NOERROR;

} // NotifyAllocator

// EnumMediaTypes
// - pass through to our downstream filter
STDMETHODIMP CTransInPlaceInputPin::EnumMediaTypes( IEnumMediaTypes **ppEnum )
{
    // Can only pass through if connected
    if( !m_pTIPFilter->m_pOutput->IsConnected() )
        return VFW_E_NOT_CONNECTED;

    return m_pTIPFilter->m_pOutput->GetConnected()->EnumMediaTypes( ppEnum );

} // EnumMediaTypes

// CheckMediaType
// - agree to anything if not connected,
// otherwise pass through to the downstream filter.
// This assumes that the filter does not change the media type.

HRESULT CTransInPlaceInputPin::CheckMediaType(const CMediaType *pmt )
{
    HRESULT hr = m_pTIPFilter->CheckInputType(pmt);
    if (hr!=S_OK) return hr;

    if( m_pTIPFilter->m_pOutput->IsConnected() )
        return m_pTIPFilter->m_pOutput->GetConnected()->QueryAccept( pmt );
    else
        return S_OK;

} // CheckMediaType

// If upstream asks us what our requirements are, we will try to ask downstream
// if that doesn't work, we'll just take the defaults.
STDMETHODIMP
CTransInPlaceInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps)
{

    if( m_pTIPFilter->m_pOutput->IsConnected() )
        return m_pTIPFilter->OutputPin()
               ->ConnectedIMemInputPin()->GetAllocatorRequirements( pProps );
    else
        return E_NOTIMPL;

} // GetAllocatorRequirements

// =================================================================
// Implements the CTransInPlaceOutputPin class
// =================================================================

// constructor

CTransInPlaceOutputPin::CTransInPlaceOutputPin(
    TCHAR *pObjectName,
    CTransInPlaceFilter *pFilter,
    HRESULT * phr,
    LPCWSTR pPinName)
    : CTransformOutputPin( pObjectName
                         , pFilter
                         , phr
                         , pPinName)
{
    DbgLog(( LOG_TRACE, 2
           , TEXT("CTransInPlaceOutputPin::CTransInPlaceOutputPin")));
    m_pTIPFilter = pFilter;

} // constructor

// EnumMediaTypes
// - pass through to our upstream filter
STDMETHODIMP CTransInPlaceOutputPin::EnumMediaTypes( IEnumMediaTypes **ppEnum )
{
    // Can only pass through if connected.
    if( ! m_pTIPFilter->m_pInput->IsConnected() )
        return VFW_E_NOT_CONNECTED;

    return m_pTIPFilter->m_pInput->GetConnected()->EnumMediaTypes( ppEnum );

} // EnumMediaTypes

// CheckMediaType
// - agree to anything if not connected,
// otherwise pass through to the upstream filter.

HRESULT CTransInPlaceOutputPin::CheckMediaType(const CMediaType *pmt )
{

    // Assumes the type does not change.  That's why we're calling
    // CheckINPUTType here on the OUTPUT pin.
    HRESULT hr = m_pTIPFilter->CheckInputType(pmt);
    if (hr!=S_OK) return hr;

    if( m_pTIPFilter->m_pInput->IsConnected() )
        return m_pTIPFilter->m_pInput->GetConnected()->QueryAccept( pmt );
    else
        return S_OK;

} //CheckMediaType

// Decide on an allocator, override this if you want to use your own allocator
// Override DecideBufferSize to call SetProperties.
// NOTE this is called during Connect() which
// therefore looks after grabbing and locking the object's critical section
//
// pPin is our downstream peer
// ppAlloc is where we return the allocator (Note: if called from filter.cpp in
// the usual way this is in fact a pointer to our own output pin's m_pAllocator)
//
HRESULT
CTransInPlaceOutputPin::DecideAllocator(IMemInputPin *pPin, IMemAllocator **ppAlloc)
{
    // Note that *ppAlloc is almost certainly identical to m_Allocator

    HRESULT hr = NOERROR;

    // If our input pin has an allocator and it's read/write then we use it.
    // Failing that we try to get one from downstream.
    *ppAlloc = NULL;

    bool fNeedToConfigureAllocator = false;

    if (m_pTIPFilter->InputPin()) {
        if (!m_pTIPFilter->InputPin()->ReadOnly()) {
            *ppAlloc = m_pTIPFilter->InputPin()->PeekAllocator();
        }
    }

    if (*ppAlloc!=NULL) {
        // don't need to configure allocator -- upstream filter has
        // already configured it
        (*ppAlloc)->AddRef();
    } else {
        hr = VFW_E_NO_ALLOCATOR;
        if ( IsConnected() ) {
            // Get an addreffed allocator from the downstream input pin.
            hr = m_pInputPin->GetAllocator( ppAlloc );
            fNeedToConfigureAllocator = true;
        }
    }

    if (*ppAlloc==NULL) {
        // Can't get one from upstream or downstream, so must use our own.

        hr = InitAllocator(ppAlloc);
        fNeedToConfigureAllocator = true;
    }

    if(FAILED(hr))
        return hr;

    ASSERT( *ppAlloc != NULL );

    if (fNeedToConfigureAllocator) {

        ALLOCATOR_PROPERTIES prop;
        ZeroMemory(&prop, sizeof(prop));

        // Try to get requirements from downstream
        pPin->GetAllocatorRequirements(&prop);

        // if he doesn't care about alignment, then set it to 1
        if (prop.cbAlign == 0) {
            prop.cbAlign = 1;
        }

        hr = DecideBufferSize(*ppAlloc, &prop);

        if (FAILED(hr)) {
            (*ppAlloc)->Release();
            *ppAlloc = NULL;
        }
    }

    // Tell the downstream input pin
    return pPin->NotifyAllocator(*ppAlloc, FALSE);

} // DecideAllocator

/* Receive notifications from our own input pin as to which allocator we
   are actually going to use.  Only call if we are connected downstream.
   Propagate the choice to any connected downstream input pin.
*/
HRESULT
CTransInPlaceOutputPin::ReceiveAllocator(IMemAllocator * pAllocator, BOOL bReadOnly)
{
    ASSERT( IsConnected() );
    ALLOCATOR_PROPERTIES Props, Actual;

    if (bReadOnly) {

        // We cannot use a read-only allocator, but we must check that the allocator
        // we have matches the properties that we need.

        HRESULT hr;
        hr = pAllocator->GetProperties(&Props);
        if (FAILED(hr)) {
            return hr;
        }
        hr = m_pAllocator->SetProperties(&Props, &Actual);
        if (FAILED(hr)) {
            return hr;
        }
        if (  (Props.cBuffers > Actual.cBuffers)
           || (Props.cbBuffer > Actual.cbBuffer)
           || (Props.cbAlign  > Actual.cbAlign)
           ) {
            return E_FAIL;
        }
        return S_OK;

    } else {

        // Propagate the allocator.
        // It's possible that the old and the new are the same thing.
        // AddRef before release ensures that we don't unload it.
        pAllocator->AddRef();
        if (m_pAllocator != NULL)
             m_pAllocator->Release();

        m_pAllocator = pAllocator;

        // Propagate the allocator downstream
        return m_pInputPin->NotifyAllocator( pAllocator, FALSE );
    }

} // receiveAllocator


TRANSIP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// base class from which you can derive a simple Transform-In-Place filter.
// The difference between this and Transfrm.h is that Transfrm copies the data.
//
// It assumes the filter has one input and one output stream, and has no
// interest in memory management, interface negotiation or anything else.
//
// Derive your class from this, and supply Transform and the media type/format
// negotiation functions. Implement that class, compile and link and
// you're done.

#ifndef __TRANSIP__
#define __TRANSIP__

// ======================================================================
// This is the com object that represents a simple transform filter. It
// supports IBaseFilter, IMediaFilter and two pins through nested interfaces
// ======================================================================

class CTransInPlaceFilter;

// Several of the pin functions call filter functions to do the work,
// so you can often use the pin classes unaltered, just overriding the
// functions in CTransInPlaceFilter.  If that's not enough and you want
// to derive your own pin class, override GetPin in the filter to supply
// your own pin classes to the filter.

// ==================================================
// Implements the input pin
// ==================================================

class CTransInPlaceInputPin : public CTransformInputPin
{

protected:
    CTransInPlaceFilter *m_pTIPFilter;    // our filter
    BOOL                 m_bReadOnly;     // incoming stream is read only

public:

    CTransInPlaceInputPin(
        TCHAR               *pObjectName,
        CTransInPlaceFilter *pFilter,
        HRESULT             *phr,
        LPCWSTR              pName);

    // --- IMemInputPin -----

    // Provide an enumerator for media types by getting one from downstream
    STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum );

    // Say whether media type is acceptable.
    HRESULT CheckMediaType(const CMediaType* pmt);

    // Return our upstream allocator
    STDMETHODIMP GetAllocator(IMemAllocator ** ppAllocator);

    // get told which allocator the upstream output pin is actually
    // going to use.
    STDMETHODIMP NotifyAllocator(IMemAllocator * pAllocator,
                                 BOOL bReadOnly);

    // Allow the filter to see what allocator we have
    // N.B. This does NOT AddRef
    IMemAllocator * PeekAllocator()
        {  return m_pAllocator; }

    // Pass this on downstream if it ever gets called.
    STDMETHODIMP
    CTransInPlaceInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps);

    inline const BOOL ReadOnly() { return m_bReadOnly; }

};  // CTransInPlaceInputPin

// ==================================================
// Implements the output pin
// ==================================================

class CTransInPlaceOutputPin : public CTransformOutputPin
{

protected:
    // m_pFilter points to our CBaseFilter
    CTransInPlaceFilter *m_pTIPFilter;

public:

    CTransInPlaceOutputPin(
        TCHAR               *pObjectName,
        CTransInPlaceFilter *pFilter,
        HRESULT             *phr,
        LPCWSTR              pName);

    // --- CBaseOutputPin ------------

    // negotiate the allocator and its buffer size/count
    // Insists on using our own allocator.  (Actually the one upstream of us).
    virtual HRESULT DecideAllocator(IMemInputPin * pPin, IMemAllocator ** pAlloc);

    // Provide a media type enumerator.  Get it from upstream.
    STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum );

    // Say whether media type is acceptable.
    HRESULT CheckMediaType(const CMediaType* pmt);

    // This is like NotifyAllocator - only output pins do not normally have those
    // This is only called internally by our own input pin to propagate
    // allocator decisions downstream.  ReadOnly allocators are not propagated
    // but we check the properties and propagate those.
    HRESULT ReceiveAllocator(IMemAllocator * pAllocator, BOOL bReadONly);

    IMemInputPin * ConnectedIMemInputPin()
        { return m_pInputPin; }

    // Allow the filter to see what allocator we have
    // N.B. This does NOT AddRef
    IMemAllocator * PeekAllocator()
        {  return m_pAllocator; }
};  // CTransInPlaceOutputPin

class AM_NOVTABLE CTransInPlaceFilter : public CTransformFilter
{

public:

    // map getpin/getpincount for base enum of pins to owner
    // override this to return more specialised pin objects

    virtual CBasePin *GetPin(int n);

public:

    CTransInPlaceFilter(TCHAR *, LPUNKNOWN, REFCLSID clsid, HRESULT *);

    // The following are defined to avoid undefined pure virtuals.
    // Even if they are never called, they will give linkage warnings/errors

    // We override EnumMediaTypes to bypass the transform class enumerator
    // which would otherwise call this.
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType)
        {   DbgBreak("CTransInPlaceFilter::GetMediaType should never be called");
            return E_UNEXPECTED;
        }

    // This is called when we actually have to provide out own allocator.
    HRESULT DecideBufferSize(IMemAllocator*, ALLOCATOR_PROPERTIES *);

    // The functions which call this in CTransform are overridden in this
    // class to call CheckInputType with the assumption that the type
    // does not change.  In Debug builds some calls will be made and
    // we just ensure that they do not assert.
    HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)
    {
        return S_OK;
    };

    // =================================================================
    // ----- You may want to override this -----------------------------
    // =================================================================

    HRESULT CompleteConnect(PIN_DIRECTION dir,IPin *pReceivePin);

    // chance to customize the transform process
    virtual HRESULT Receive(IMediaSample *pSample);

    // =================================================================
    // ----- You MUST override these -----------------------------------
    // =================================================================

    virtual HRESULT Transform(IMediaSample *pSample) PURE;

    // this goes in the factory template table to create new instances
    // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);

#ifdef PERF
    // Override to register performance measurement with a less generic string
    // You should do this to avoid confusion with other filters
    virtual void RegisterPerfId()
         {m_idTransInPlace = MSR_REGISTER(TEXT("TransInPlace"));}
#endif // PERF

// implementation details

protected:

    IMediaSample * CTransInPlaceFilter::Copy(IMediaSample *pSource);

    int m_idTransInPlace;                 // performance measuring id

    // these hold our input and output pins

    friend class CTransInPlaceInputPin;
    friend class CTransInPlaceOutputPin;

    CTransInPlaceInputPin  *InputPin()
    {
        return (CTransInPlaceInputPin *)m_pInput;
    };
    CTransInPlaceOutputPin *OutputPin()
    {
        return (CTransInPlaceOutputPin *)m_pOutput;
    };
}; // CTransInPlaceFilter

#endif /* __TRANSIP__ */


VIDEOCTL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include "ddmm.h"

// Load a string from the resource file string table. The buffer must be at
// least STR_MAX_LENGTH bytes. The easiest way to use this is to declare a
// buffer in the property page class and use it for all string loading. It
// cannot be static as multiple property pages may be active simultaneously

TCHAR *WINAPI StringFromResource(TCHAR *pBuffer, int iResourceID)
{
    if (LoadString(g_hInst,iResourceID,pBuffer,STR_MAX_LENGTH) == 0) {
        return TEXT("");
    }
    return pBuffer;
}

// Property pages typically are called through their OLE interfaces. These
// use UNICODE strings regardless of how the binary is built. So when we
// load strings from the resource file we sometimes want to convert them
// to UNICODE. This method is passed the target UNICODE buffer and does a
// convert after loading the string (if built UNICODE this is not needed)
// On WinNT we can explicitly call LoadStringW which saves two conversions

#ifndef UNICODE

WCHAR * WINAPI WideStringFromResource(WCHAR *pBuffer, int iResourceID)
{
    *pBuffer = 0;

    if (g_amPlatform == VER_PLATFORM_WIN32_NT) {
LoadStringW(g_hInst,iResourceID,pBuffer,STR_MAX_LENGTH);
    } else {

CHAR szBuffer[STR_MAX_LENGTH];
DWORD dwStringLength = LoadString(g_hInst,iResourceID,szBuffer,STR_MAX_LENGTH);
// if we loaded a string convert it to wide characters, ensuring
// that we also null terminate the result.
if (dwStringLength++) {
    MultiByteToWideChar(CP_ACP,0,szBuffer,dwStringLength,pBuffer,STR_MAX_LENGTH);
}
    }
    return pBuffer;
}

#endif

// Helper function to calculate the size of the dialog

BOOL WINAPI GetDialogSize(int iResourceID,
                          DLGPROC pDlgProc,
                          LPARAM lParam,
                          SIZE *pResult)
{
    RECT rc;
    HWND hwnd;

    // Create a temporary property page

    hwnd = CreateDialogParam(g_hInst,
                             MAKEINTRESOURCE(iResourceID),
                             GetDesktopWindow(),
                             pDlgProc,
                             lParam);
    if (hwnd == NULL) {
        return FALSE;
    }

    GetWindowRect(hwnd, &rc);
    pResult->cx = rc.right - rc.left;
    pResult->cy = rc.bottom - rc.top;

    DestroyWindow(hwnd);
    return TRUE;
}

// Class that aggregates on the IDirectDraw interface. Although DirectDraw
// has the ability in its interfaces to be aggregated they're not currently
// implemented. This makes it difficult for various parts of Quartz that want
// to aggregate these interfaces. In particular the video renderer passes out
// media samples that expose IDirectDraw and IDirectDrawSurface. The filter
// graph manager also exposes IDirectDraw as a plug in distributor. For these
// objects we provide these aggregation classes that republish the interfaces

STDMETHODIMP CAggDirectDraw::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ASSERT(m_pDirectDraw);

    // Do we have this interface

    if (riid == IID_IDirectDraw) {
        return GetInterface((IDirectDraw *)this,ppv);
    } else {
        return CUnknown::NonDelegatingQueryInterface(riid,ppv);
    }
}

STDMETHODIMP CAggDirectDraw::Compact()
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->Compact();
}

STDMETHODIMP CAggDirectDraw::CreateClipper(DWORD dwFlags,LPDIRECTDRAWCLIPPER *lplpDDClipper,IUnknown *pUnkOuter)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->CreateClipper(dwFlags,lplpDDClipper,pUnkOuter);
}

STDMETHODIMP CAggDirectDraw::CreatePalette(DWORD dwFlags,LPPALETTEENTRY lpColorTable,LPDIRECTDRAWPALETTE *lplpDDPalette,IUnknown *pUnkOuter)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->CreatePalette(dwFlags,lpColorTable,lplpDDPalette,pUnkOuter);
}

STDMETHODIMP CAggDirectDraw::CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc,LPDIRECTDRAWSURFACE *lplpDDSurface,IUnknown *pUnkOuter)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->CreateSurface(lpDDSurfaceDesc,lplpDDSurface,pUnkOuter);
}

STDMETHODIMP CAggDirectDraw::DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface,LPDIRECTDRAWSURFACE *lplpDupDDSurface)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->DuplicateSurface(lpDDSurface,lplpDupDDSurface);
}

STDMETHODIMP CAggDirectDraw::EnumDisplayModes(DWORD dwSurfaceDescCount,LPDDSURFACEDESC lplpDDSurfaceDescList,LPVOID lpContext,LPDDENUMMODESCALLBACK lpEnumCallback)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->EnumDisplayModes(dwSurfaceDescCount,lplpDDSurfaceDescList,lpContext,lpEnumCallback);
}

STDMETHODIMP CAggDirectDraw::EnumSurfaces(DWORD dwFlags,LPDDSURFACEDESC lpDDSD,LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpEnumCallback)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->EnumSurfaces(dwFlags,lpDDSD,lpContext,lpEnumCallback);
}

STDMETHODIMP CAggDirectDraw::FlipToGDISurface()
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->FlipToGDISurface();
}

STDMETHODIMP CAggDirectDraw::GetCaps(LPDDCAPS lpDDDriverCaps,LPDDCAPS lpDDHELCaps)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetCaps(lpDDDriverCaps,lpDDHELCaps);
}

STDMETHODIMP CAggDirectDraw::GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetDisplayMode(lpDDSurfaceDesc);
}

STDMETHODIMP CAggDirectDraw::GetFourCCCodes(LPDWORD lpNumCodes,LPDWORD lpCodes)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetFourCCCodes(lpNumCodes,lpCodes);
}

STDMETHODIMP CAggDirectDraw::GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetGDISurface(lplpGDIDDSurface);
}

STDMETHODIMP CAggDirectDraw::GetMonitorFrequency(LPDWORD lpdwFrequency)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetMonitorFrequency(lpdwFrequency);
}

STDMETHODIMP CAggDirectDraw::GetScanLine(LPDWORD lpdwScanLine)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetScanLine(lpdwScanLine);
}

STDMETHODIMP CAggDirectDraw::GetVerticalBlankStatus(LPBOOL lpblsInVB)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->GetVerticalBlankStatus(lpblsInVB);
}

STDMETHODIMP CAggDirectDraw::Initialize(GUID *lpGUID)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->Initialize(lpGUID);
}

STDMETHODIMP CAggDirectDraw::RestoreDisplayMode()
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->RestoreDisplayMode();
}

STDMETHODIMP CAggDirectDraw::SetCooperativeLevel(HWND hWnd,DWORD dwFlags)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->SetCooperativeLevel(hWnd,dwFlags);
}

STDMETHODIMP CAggDirectDraw::SetDisplayMode(DWORD dwWidth,DWORD dwHeight,DWORD dwBpp)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->SetDisplayMode(dwWidth,dwHeight,dwBpp);
}

STDMETHODIMP CAggDirectDraw::WaitForVerticalBlank(DWORD dwFlags,HANDLE hEvent)
{
    ASSERT(m_pDirectDraw);
    return m_pDirectDraw->WaitForVerticalBlank(dwFlags,hEvent);
}

// Class that aggregates an IDirectDrawSurface interface. Although DirectDraw
// has the ability in its interfaces to be aggregated they're not currently
// implemented. This makes it difficult for various parts of Quartz that want
// to aggregate these interfaces. In particular the video renderer passes out
// media samples that expose IDirectDraw and IDirectDrawSurface. The filter
// graph manager also exposes IDirectDraw as a plug in distributor. For these
// objects we provide these aggregation classes that republish the interfaces

STDMETHODIMP CAggDrawSurface::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    ASSERT(m_pDirectDrawSurface);

    // Do we have this interface

    if (riid == IID_IDirectDrawSurface) {
        return GetInterface((IDirectDrawSurface *)this,ppv);
    } else {
        return CUnknown::NonDelegatingQueryInterface(riid,ppv);
    }
}

STDMETHODIMP CAggDrawSurface::AddAttachedSurface(LPDIRECTDRAWSURFACE lpDDSAttachedSurface)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->AddAttachedSurface(lpDDSAttachedSurface);
}

STDMETHODIMP CAggDrawSurface::AddOverlayDirtyRect(LPRECT lpRect)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->AddOverlayDirtyRect(lpRect);
}

STDMETHODIMP CAggDrawSurface::Blt(LPRECT lpDestRect,LPDIRECTDRAWSURFACE lpDDSrcSurface,LPRECT lpSrcRect,DWORD dwFlags,LPDDBLTFX lpDDBltFx)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Blt(lpDestRect,lpDDSrcSurface,lpSrcRect,dwFlags,lpDDBltFx);
}

STDMETHODIMP CAggDrawSurface::BltBatch(LPDDBLTBATCH lpDDBltBatch,DWORD dwCount,DWORD dwFlags)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->BltBatch(lpDDBltBatch,dwCount,dwFlags);
}

STDMETHODIMP CAggDrawSurface::BltFast(DWORD dwX,DWORD dwY,LPDIRECTDRAWSURFACE lpDDSrcSurface,LPRECT lpSrcRect,DWORD dwTrans)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->BltFast(dwX,dwY,lpDDSrcSurface,lpSrcRect,dwTrans);
}

STDMETHODIMP CAggDrawSurface::DeleteAttachedSurface(DWORD dwFlags,LPDIRECTDRAWSURFACE lpDDSAttachedSurface)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->DeleteAttachedSurface(dwFlags,lpDDSAttachedSurface);
}

STDMETHODIMP CAggDrawSurface::EnumAttachedSurfaces(LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->EnumAttachedSurfaces(lpContext,lpEnumSurfacesCallback);
}

STDMETHODIMP CAggDrawSurface::EnumOverlayZOrders(DWORD dwFlags,LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpfnCallback)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->EnumOverlayZOrders(dwFlags,lpContext,lpfnCallback);
}

STDMETHODIMP CAggDrawSurface::Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride,DWORD dwFlags)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Flip(lpDDSurfaceTargetOverride,dwFlags);
}

STDMETHODIMP CAggDrawSurface::GetAttachedSurface(LPDDSCAPS lpDDSCaps,LPDIRECTDRAWSURFACE *lplpDDAttachedSurface)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetAttachedSurface(lpDDSCaps,lplpDDAttachedSurface);
}

STDMETHODIMP CAggDrawSurface::GetBltStatus(DWORD dwFlags)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetBltStatus(dwFlags);
}

STDMETHODIMP CAggDrawSurface::GetCaps(LPDDSCAPS lpDDSCaps)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetCaps(lpDDSCaps);
}

STDMETHODIMP CAggDrawSurface::GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetClipper(lplpDDClipper);
}

STDMETHODIMP CAggDrawSurface::GetColorKey(DWORD dwFlags,LPDDCOLORKEY lpDDColorKey)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetColorKey(dwFlags,lpDDColorKey);
}

STDMETHODIMP CAggDrawSurface::GetDC(HDC *lphDC)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetDC(lphDC);
}

STDMETHODIMP CAggDrawSurface::GetFlipStatus(DWORD dwFlags)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetFlipStatus(dwFlags);
}

STDMETHODIMP CAggDrawSurface::GetOverlayPosition(LPLONG lpdwX,LPLONG lpdwY)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetOverlayPosition(lpdwX,lpdwY);
}

STDMETHODIMP CAggDrawSurface::GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetPalette(lplpDDPalette);
}

STDMETHODIMP CAggDrawSurface::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->GetPixelFormat(lpDDPixelFormat);
}

// A bit of a hack alert here. Our media samples in ActiveMovie aggregate on
// IDirectDraw and IDirectDrawSurface (ie are available through IMediaSample
// by QueryInterface). Unfortunately the underlying DirectDraw code cannot
// be aggregated so we have to use these classes. The snag is that when we
// call a different surface and pass in this interface as perhaps the source
// surface the call will fail because DirectDraw dereferences the pointer to
// get at its private data structures. Therefore we supply this hack to give
// access to the real IDirectDraw surface. A filter can call GetSurfaceDesc
// and we will fill in the lpSurface pointer with the real underlying surface

STDMETHODIMP CAggDrawSurface::GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc)
{
    ASSERT(m_pDirectDrawSurface);

    // First call down to the underlying DirectDraw

    HRESULT hr = m_pDirectDrawSurface->GetSurfaceDesc(lpDDSurfaceDesc);
    if (FAILED(hr)) {
        return hr;
    }

    // Store the real DirectDrawSurface interface
    lpDDSurfaceDesc->lpSurface = m_pDirectDrawSurface;
    return hr;
}

STDMETHODIMP CAggDrawSurface::Initialize(LPDIRECTDRAW lpDD,LPDDSURFACEDESC lpDDSurfaceDesc)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Initialize(lpDD,lpDDSurfaceDesc);
}

STDMETHODIMP CAggDrawSurface::IsLost()
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->IsLost();
}

STDMETHODIMP CAggDrawSurface::Lock(LPRECT lpDestRect,LPDDSURFACEDESC lpDDSurfaceDesc,DWORD dwFlags,HANDLE hEvent)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Lock(lpDestRect,lpDDSurfaceDesc,dwFlags,hEvent);
}

STDMETHODIMP CAggDrawSurface::ReleaseDC(HDC hDC)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->ReleaseDC(hDC);
}

STDMETHODIMP CAggDrawSurface::Restore()
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Restore();
}

STDMETHODIMP CAggDrawSurface::SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->SetClipper(lpDDClipper);
}

STDMETHODIMP CAggDrawSurface::SetColorKey(DWORD dwFlags,LPDDCOLORKEY lpDDColorKey)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->SetColorKey(dwFlags,lpDDColorKey);
}

STDMETHODIMP CAggDrawSurface::SetOverlayPosition(LONG dwX,LONG dwY)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->SetOverlayPosition(dwX,dwY);
}

STDMETHODIMP CAggDrawSurface::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->SetPalette(lpDDPalette);
}

STDMETHODIMP CAggDrawSurface::Unlock(LPVOID lpSurfaceData)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->Unlock(lpSurfaceData);
}

STDMETHODIMP CAggDrawSurface::UpdateOverlay(LPRECT lpSrcRect,LPDIRECTDRAWSURFACE lpDDDestSurface,LPRECT lpDestRect,DWORD dwFlags,LPDDOVERLAYFX lpDDOverlayFX)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->UpdateOverlay(lpSrcRect,lpDDDestSurface,lpDestRect,dwFlags,lpDDOverlayFX);
}

STDMETHODIMP CAggDrawSurface::UpdateOverlayDisplay(DWORD dwFlags)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->UpdateOverlayDisplay(dwFlags);
}

STDMETHODIMP CAggDrawSurface::UpdateOverlayZOrder(DWORD dwFlags,LPDIRECTDRAWSURFACE lpDDSReference)
{
    ASSERT(m_pDirectDrawSurface);
    return m_pDirectDrawSurface->UpdateOverlayZOrder(dwFlags,lpDDSReference);
}

// ActiveMovie must work on multiple platforms, in particular it also runs on
// Windows NT 3.51 which does not have DirectDraw capabilities. The filters
// cannot therefore link statically to the DirectDraw library. To make their
// lives that little bit easier we provide this class that manages loading
// and unloading the library and creating the initial IDirectDraw interface

CLoadDirectDraw::CLoadDirectDraw() :
    m_pDirectDraw(NULL),
    m_hDirectDraw(NULL)
{
}

// Destructor forces unload

CLoadDirectDraw::~CLoadDirectDraw()
{
    ReleaseDirectDraw();
}

// We can't be sure that DirectDraw is always available so we can't statically
// link to the library. Therefore we load the library, get the function entry
// point addresses and call them to create the driver objects. We return S_OK
// if we manage to load DirectDraw correctly otherwise we return E_NOINTERFACE
// We initialise a DirectDraw instance by explicitely loading the library and
// calling GetProcAddress on the DirectDrawCreate entry point that it exports

// On a multi monitor system, we can get the DirectDraw object for any
// monitor (device) with the optional szDevice parameter

HRESULT CLoadDirectDraw::LoadDirectDraw(LPSTR szDevice)
{
    PDRAWCREATE pDrawCreate;
    PDRAWENUM pDrawEnum;
    LPDIRECTDRAWENUMERATEEXA pDrawEnumEx;
    HRESULT hr = NOERROR;

    NOTE("Entering DoLoadDirectDraw");

    // Is DirectDraw already loaded

    if (m_pDirectDraw) {
        NOTE("Already loaded");
        ASSERT(m_hDirectDraw);
        return NOERROR;
    }

    // Make sure the library is available

    UINT ErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);
    m_hDirectDraw = LoadLibrary(TEXT("DDRAW.DLL"));
    SetErrorMode(ErrorMode);

    if (m_hDirectDraw == NULL) {
        DbgLog((LOG_ERROR,1,TEXT("Can't load DDRAW.DLL")));
        NOTE("No library");
        return E_NOINTERFACE;
    }

    // Get the DLL address for the creator function

    pDrawCreate = (PDRAWCREATE)GetProcAddress(m_hDirectDraw,"DirectDrawCreate");
    // force ANSI, we assume it
    pDrawEnum = (PDRAWENUM)GetProcAddress(m_hDirectDraw,"DirectDrawEnumerateA");
    pDrawEnumEx = (LPDIRECTDRAWENUMERATEEXA)GetProcAddress(m_hDirectDraw,
"DirectDrawEnumerateExA");

    // We don't NEED DirectDrawEnumerateEx, that's just for multimon stuff
    if (pDrawCreate == NULL || pDrawEnum == NULL) {
        DbgLog((LOG_ERROR,1,TEXT("Can't get functions: Create=%x Enum=%x"),
pDrawCreate, pDrawEnum));
        NOTE("No entry point");
        ReleaseDirectDraw();
        return E_NOINTERFACE;
    }

    DbgLog((LOG_TRACE,3,TEXT("Creating DDraw for device %s"),
szDevice ? szDevice : "<NULL>"));

    // Create a DirectDraw display provider for this device, using the fancy
    // multimon-aware version, if it exists
    if (pDrawEnumEx)
        m_pDirectDraw = DirectDrawCreateFromDeviceEx(szDevice, pDrawCreate,
pDrawEnumEx);
    else
        m_pDirectDraw = DirectDrawCreateFromDevice(szDevice, pDrawCreate,
pDrawEnum);

    if (m_pDirectDraw == NULL) {
            DbgLog((LOG_ERROR,1,TEXT("Can't create DDraw")));
            NOTE("No instance");
            ReleaseDirectDraw();
            return E_NOINTERFACE;
    }
    return NOERROR;
}

// Called to release any DirectDraw provider we previously loaded. We may be
// called at any time especially when something goes horribly wrong and when
// we need to clean up before returning so we can't guarantee that all state
// variables are consistent so free only those really allocated allocated
// This should only be called once all reference counts have been released

void CLoadDirectDraw::ReleaseDirectDraw()
{
    NOTE("Releasing DirectDraw driver");

    // Release any DirectDraw provider interface

    if (m_pDirectDraw) {
        NOTE("Releasing instance");
        m_pDirectDraw->Release();
        m_pDirectDraw = NULL;
    }

    // Decrement module load count

    if (m_hDirectDraw) {
        NOTE("Unloading library");
        FreeLibrary(m_hDirectDraw);
        m_hDirectDraw = NULL;
    }
}

// Return NOERROR (S_OK) if DirectDraw has been loaded by this object

HRESULT CLoadDirectDraw::IsDirectDrawLoaded()
{
    NOTE("Entering IsDirectDrawLoaded");

    if (m_pDirectDraw == NULL) {
        NOTE("DirectDraw not loaded");
        return S_FALSE;
    }
    return NOERROR;
}

// Return the IDirectDraw interface we look after

LPDIRECTDRAW CLoadDirectDraw::GetDirectDraw()
{
    NOTE("Entering GetDirectDraw");

    if (m_pDirectDraw == NULL) {
        NOTE("No DirectDraw");
        return NULL;
    }

    NOTE("Returning DirectDraw");
    m_pDirectDraw->AddRef();
    return m_pDirectDraw;
}

// Are we running on Direct Draw version 1?  We need to find out as
// we rely on specific bug fixes in DirectDraw 2 for fullscreen playback. To
// find out, we simply see if it supports IDirectDraw2.  Only version 2 and
// higher support this.

BOOL CLoadDirectDraw::IsDirectDrawVersion1()
{

    if (m_pDirectDraw == NULL)
return FALSE;

    IDirectDraw2 *p = NULL;
    HRESULT hr = m_pDirectDraw->QueryInterface(IID_IDirectDraw2, (void **)&p);
    if (p)
p->Release();
    if (hr == NOERROR) {
        DbgLog((LOG_TRACE,3,TEXT("Direct Draw Version 2 or greater")));
return FALSE;
    } else {
        DbgLog((LOG_TRACE,3,TEXT("Direct Draw Version 1")));
return TRUE;
    }
}


VIDEOCTL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __VIDEOCTL__
#define __VIDEOCTL__

// These help with property page implementations. The first can be used to
// load any string from a resource file. The buffer to load into is passed
// as an input parameter. The same buffer is the return value if the string
// was found otherwise it returns TEXT(""). The GetDialogSize is passed the
// resource ID of a dialog box and returns the size of it in screen pixels

#define STR_MAX_LENGTH 256
TCHAR * WINAPI StringFromResource(TCHAR *pBuffer, int iResourceID);

#ifdef UNICODE
#define WideStringFromResource StringFromResource
#else
WCHAR * WINAPI WideStringFromResource(WCHAR *pBuffer, int iResourceID);
#endif

BOOL WINAPI GetDialogSize(int iResourceID,     // Dialog box resource identifier
                          DLGPROC pDlgProc,    // Pointer to dialog procedure
                          LPARAM lParam,       // Any user data wanted in pDlgProc
                          SIZE *pResult);      // Returns the size of dialog box

// Class that aggregates an IDirectDraw interface

class CAggDirectDraw : public IDirectDraw, public CUnknown
{
protected:

    LPDIRECTDRAW m_pDirectDraw;

public:

    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv);

    // Constructor and destructor

    CAggDirectDraw(TCHAR *pName,LPUNKNOWN pUnk) :
        CUnknown(pName,pUnk),
        m_pDirectDraw(NULL) { };

    virtual CAggDirectDraw::~CAggDirectDraw() { };

    // Set the object we should be aggregating
    void SetDirectDraw(LPDIRECTDRAW pDirectDraw) {
        m_pDirectDraw = pDirectDraw;
    }

    // IDirectDraw methods

    STDMETHODIMP Compact();
    STDMETHODIMP CreateClipper(DWORD dwFlags,LPDIRECTDRAWCLIPPER *lplpDDClipper,IUnknown *pUnkOuter);
    STDMETHODIMP CreatePalette(DWORD dwFlags,LPPALETTEENTRY lpColorTable,LPDIRECTDRAWPALETTE *lplpDDPalette,IUnknown *pUnkOuter);
    STDMETHODIMP CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc,LPDIRECTDRAWSURFACE *lplpDDSurface,IUnknown *pUnkOuter);
    STDMETHODIMP DuplicateSurface(LPDIRECTDRAWSURFACE lpDDSurface,LPDIRECTDRAWSURFACE *lplpDupDDSurface);
    STDMETHODIMP EnumDisplayModes(DWORD dwSurfaceDescCount,LPDDSURFACEDESC lplpDDSurfaceDescList,LPVOID lpContext,LPDDENUMMODESCALLBACK lpEnumCallback);
    STDMETHODIMP EnumSurfaces(DWORD dwFlags,LPDDSURFACEDESC lpDDSD,LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpEnumCallback);
    STDMETHODIMP FlipToGDISurface();
    STDMETHODIMP GetCaps(LPDDCAPS lpDDDriverCaps,LPDDCAPS lpDDHELCaps);
    STDMETHODIMP GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc);
    STDMETHODIMP GetFourCCCodes(LPDWORD lpNumCodes,LPDWORD lpCodes);
    STDMETHODIMP GetGDISurface(LPDIRECTDRAWSURFACE *lplpGDIDDSurface);
    STDMETHODIMP GetMonitorFrequency(LPDWORD lpdwFrequency);
    STDMETHODIMP GetScanLine(LPDWORD lpdwScanLine);
    STDMETHODIMP GetVerticalBlankStatus(LPBOOL lpblsInVB);
    STDMETHODIMP Initialize(GUID *lpGUID);
    STDMETHODIMP RestoreDisplayMode();
    STDMETHODIMP SetCooperativeLevel(HWND hWnd,DWORD dwFlags);
    STDMETHODIMP SetDisplayMode(DWORD dwWidth,DWORD dwHeight,DWORD dwBpp);
    STDMETHODIMP WaitForVerticalBlank(DWORD dwFlags,HANDLE hEvent);
};

// Class that aggregates an IDirectDrawSurface interface

class CAggDrawSurface : public IDirectDrawSurface, public CUnknown
{
protected:

    LPDIRECTDRAWSURFACE m_pDirectDrawSurface;

public:

    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv);

    // Constructor and destructor

    CAggDrawSurface(TCHAR *pName,LPUNKNOWN pUnk) :
        CUnknown(pName,pUnk),
        m_pDirectDrawSurface(NULL) { };

    virtual ~CAggDrawSurface() { };

    // Set the object we should be aggregating
    void SetDirectDrawSurface(LPDIRECTDRAWSURFACE pDirectDrawSurface) {
        m_pDirectDrawSurface = pDirectDrawSurface;
    }

    // IDirectDrawSurface methods

    STDMETHODIMP AddAttachedSurface(LPDIRECTDRAWSURFACE lpDDSAttachedSurface);
    STDMETHODIMP AddOverlayDirtyRect(LPRECT lpRect);
    STDMETHODIMP Blt(LPRECT lpDestRect,LPDIRECTDRAWSURFACE lpDDSrcSurface,LPRECT lpSrcRect,DWORD dwFlags,LPDDBLTFX lpDDBltFx);
    STDMETHODIMP BltBatch(LPDDBLTBATCH lpDDBltBatch,DWORD dwCount,DWORD dwFlags);
    STDMETHODIMP BltFast(DWORD dwX,DWORD dwY,LPDIRECTDRAWSURFACE lpDDSrcSurface,LPRECT lpSrcRect,DWORD dwTrans);
    STDMETHODIMP DeleteAttachedSurface(DWORD dwFlags,LPDIRECTDRAWSURFACE lpDDSAttachedSurface);
    STDMETHODIMP EnumAttachedSurfaces(LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback);
    STDMETHODIMP EnumOverlayZOrders(DWORD dwFlags,LPVOID lpContext,LPDDENUMSURFACESCALLBACK lpfnCallback);
    STDMETHODIMP Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride,DWORD dwFlags);
    STDMETHODIMP GetAttachedSurface(LPDDSCAPS lpDDSCaps,LPDIRECTDRAWSURFACE *lplpDDAttachedSurface);
    STDMETHODIMP GetBltStatus(DWORD dwFlags);
    STDMETHODIMP GetCaps(LPDDSCAPS lpDDSCaps);
    STDMETHODIMP GetClipper(LPDIRECTDRAWCLIPPER *lplpDDClipper);
    STDMETHODIMP GetColorKey(DWORD dwFlags,LPDDCOLORKEY lpDDColorKey);
    STDMETHODIMP GetDC(HDC *lphDC);
    STDMETHODIMP GetFlipStatus(DWORD dwFlags);
    STDMETHODIMP GetOverlayPosition(LPLONG lpdwX,LPLONG lpdwY);
    STDMETHODIMP GetPalette(LPDIRECTDRAWPALETTE *lplpDDPalette);
    STDMETHODIMP GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat);
    STDMETHODIMP GetSurfaceDesc(LPDDSURFACEDESC lpDDSurfaceDesc);
    STDMETHODIMP Initialize(LPDIRECTDRAW lpDD,LPDDSURFACEDESC lpDDSurfaceDesc);
    STDMETHODIMP IsLost();
    STDMETHODIMP Lock(LPRECT lpDestRect,LPDDSURFACEDESC lpDDSurfaceDesc,DWORD dwFlags,HANDLE hEvent);
    STDMETHODIMP ReleaseDC(HDC hDC);
    STDMETHODIMP Restore();
    STDMETHODIMP SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper);
    STDMETHODIMP SetColorKey(DWORD dwFlags,LPDDCOLORKEY lpDDColorKey);
    STDMETHODIMP SetOverlayPosition(LONG dwX,LONG dwY);
    STDMETHODIMP SetPalette(LPDIRECTDRAWPALETTE lpDDPalette);
    STDMETHODIMP Unlock(LPVOID lpSurfaceData);
    STDMETHODIMP UpdateOverlay(LPRECT lpSrcRect,LPDIRECTDRAWSURFACE lpDDDestSurface,LPRECT lpDestRect,DWORD dwFlags,LPDDOVERLAYFX lpDDOverlayFX);
    STDMETHODIMP UpdateOverlayDisplay(DWORD dwFlags);
    STDMETHODIMP UpdateOverlayZOrder(DWORD dwFlags,LPDIRECTDRAWSURFACE lpDDSReference);
};

// ActiveMovie must work on multiple platforms, in particular it also runs on
// Windows NT 3.51 which does not have DirectDraw capabilities. The filters
// cannot therefore link statically to the DirectDraw library. To make their
// lives that little bit easier we provide this class that manages loading
// and unloading the library and creating the initial IDirectDraw interface

typedef DWORD (WINAPI *PGETFILEVERSIONINFOSIZE)(LPTSTR,LPDWORD);
typedef BOOL (WINAPI *PGETFILEVERSIONINFO)(LPTSTR,DWORD,DWORD,LPVOID);
typedef BOOL (WINAPI *PVERQUERYVALUE)(LPVOID,LPTSTR,LPVOID,PUINT);

class CLoadDirectDraw
{
    LPDIRECTDRAW m_pDirectDraw;     // The DirectDraw driver instance
    HINSTANCE m_hDirectDraw;        // Handle to the loaded library

public:

    CLoadDirectDraw();
    ~CLoadDirectDraw();

    HRESULT LoadDirectDraw(LPSTR szDevice);
    void ReleaseDirectDraw();
    HRESULT IsDirectDrawLoaded();
    LPDIRECTDRAW GetDirectDraw();
    BOOL IsDirectDrawVersion1();
};

#endif // __VIDEOCTL__


VTRANS.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <measure.h>
// #include <vtransfr.h>         // now in precomp file streams.h

CVideoTransformFilter::CVideoTransformFilter
    ( TCHAR *pName, LPUNKNOWN pUnk, REFCLSID clsid)
    : CTransformFilter(pName, pUnk, clsid)
    , m_itrLate(0)
    , m_nKeyFramePeriod(0)      // No QM until we see at least 2 key frames
    , m_nFramesSinceKeyFrame(0)
    , m_bSkipping(FALSE)
    , m_tDecodeStart(0)
    , m_itrAvgDecode(300000)    // 30mSec - probably allows skipping
    , m_bQualityChanged(FALSE)
{
#ifdef PERF
    RegisterPerfId();
#endif //  PERF
}

CVideoTransformFilter::~CVideoTransformFilter()
{
  // nothing to do
}

// Reset our quality management state

HRESULT CVideoTransformFilter::StartStreaming()
{
    m_itrLate = 0;
    m_nKeyFramePeriod = 0;       // No QM until we see at least 2 key frames
    m_nFramesSinceKeyFrame = 0;
    m_bSkipping = FALSE;
    m_tDecodeStart = 0;
    m_itrAvgDecode = 300000;     // 30mSec - probably allows skipping
    m_bQualityChanged = FALSE;
    m_bSampleSkipped = FALSE;
    return NOERROR;
}

// Overriden to reset quality management information

HRESULT CVideoTransformFilter::EndFlush()
{
    {
        //  Synchronize
        CAutoLock lck(&m_csReceive);

        // Reset our stats
        //
        // Note - we don't want to call derived classes here,
        // we only want to reset our internal variables and this
        // is a convenient way to do it
        CVideoTransformFilter::StartStreaming();
    }
    return CTransformFilter::EndFlush();
}

// Receive()
//
// Accept a sample from upstream, decide whether to process it
// or drop it.  If we process it then get a buffer from the
// allocator of the downstream connection, transform it into the
// new buffer and deliver it to the downstream filter.
// If we decide not to process it then we do not get a buffer.

HRESULT CVideoTransformFilter::Receive(IMediaSample *pSample)
{
    // If the next filter downstream is the video renderer, then it may
    // be able to operate in DirectDraw mode which saves copying the data
    // and gives higher performance.  In that case the buffer which we
    // get from GetDeliveryBuffer will be a DirectDraw buffer, and
    // drawing into this buffer draws directly onto the display surface.
    // This means that any waiting for the correct time to draw occurs
    // during GetDeliveryBuffer, and that once the buffer is given to us
    // the video renderer will count it in its statistics as a frame drawn.
    // This means that any decision to drop the frame must be taken before
    // calling GetDeliveryBuffer.

    ASSERT(CritCheckIn(&m_csReceive));
    AM_MEDIA_TYPE *pmtOut, *pmt;
#ifdef DEBUG
    FOURCCMap fccOut;
#endif
    HRESULT hr;
    ASSERT(pSample);
    IMediaSample * pOutSample;

    // If no output pin to deliver to then no point sending us data
    ASSERT (m_pOutput != NULL) ;

    if (ShouldSkipFrame(pSample)) {
        MSR_NOTE(m_idSkip);
        m_bSampleSkipped = TRUE;
        return NOERROR;
    }

    // Set up the output sample
    hr = InitializeOutputSample(pSample, &pOutSample);

    if (FAILED(hr)) {
        return hr;
    }

    m_bSampleSkipped = FALSE;

    // The source filter may dynamically ask us to start transforming from a
    // different media type than the one we're using now.  If we don't, we'll
    // draw garbage. (typically, this is a palette change in the movie,
    // but could be something more sinister like the compression type changing,
    // or even the video size changing)

#define rcS1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcSource
#define rcT1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcTarget

    pSample->GetMediaType(&pmt);
    if (pmt != NULL && pmt->pbFormat != NULL) {

// spew some debug output
ASSERT(!IsEqualGUID(pmt->majortype, GUID_NULL));
#ifdef DEBUG
        fccOut.SetFOURCC(&pmt->subtype);
LONG lCompression = HEADER(pmt->pbFormat)->biCompression;
LONG lBitCount = HEADER(pmt->pbFormat)->biBitCount;
LONG lStride = (HEADER(pmt->pbFormat)->biWidth * lBitCount + 7) / 8;
lStride = (lStride + 3) & ~3;
        DbgLog((LOG_TRACE,3,TEXT("*Changing input type on the fly to")));
        DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"),
fccOut.GetFOURCC(), lCompression, lBitCount));
        DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"),
HEADER(pmt->pbFormat)->biHeight,
rcT1.left, rcT1.top, rcT1.right, rcT1.bottom));
        DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"),
rcS1.left, rcS1.top, rcS1.right, rcS1.bottom,
lStride));
#endif

// now switch to using the new format.  I am assuming that the
// derived filter will do the right thing when its media type is
// switched and streaming is restarted.

StopStreaming();
m_pInput->CurrentMediaType() = *pmt;
DeleteMediaType(pmt);
// not much we can do if this fails
hr = StartStreaming();
    }

    // The renderer may ask us to on-the-fly to start transforming to a
    // different format.  If we don't obey it, we'll draw garbage

#define rcS ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcSource
#define rcT ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcTarget

    pOutSample->GetMediaType(&pmtOut);
    if (pmtOut != NULL && pmtOut->pbFormat != NULL) {

// spew some debug output
ASSERT(!IsEqualGUID(pmtOut->majortype, GUID_NULL));
#ifdef DEBUG
        fccOut.SetFOURCC(&pmtOut->subtype);
LONG lCompression = HEADER(pmtOut->pbFormat)->biCompression;
LONG lBitCount = HEADER(pmtOut->pbFormat)->biBitCount;
LONG lStride = (HEADER(pmtOut->pbFormat)->biWidth * lBitCount + 7) / 8;
lStride = (lStride + 3) & ~3;
        DbgLog((LOG_TRACE,3,TEXT("*Changing output type on the fly to")));
        DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"),
fccOut.GetFOURCC(), lCompression, lBitCount));
        DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"),
HEADER(pmtOut->pbFormat)->biHeight,
rcT.left, rcT.top, rcT.right, rcT.bottom));
        DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"),
rcS.left, rcS.top, rcS.right, rcS.bottom,
lStride));
#endif

// now switch to using the new format.  I am assuming that the
// derived filter will do the right thing when its media type is
// switched and streaming is restarted.

StopStreaming();
m_pOutput->CurrentMediaType() = *pmtOut;
DeleteMediaType(pmtOut);
hr = StartStreaming();

if (SUCCEEDED(hr)) {
     // a new format, means a new empty buffer, so wait for a keyframe
    // before passing anything on to the renderer.
    // !!! a keyframe may never come, so give up after 30 frames
            DbgLog((LOG_TRACE,3,TEXT("Output format change means we must wait for a keyframe")));
    m_nWaitForKey = 30;
}
    }

    // Start timing the transform (and log it if PERF is defined)

    if (SUCCEEDED(hr)) {
        m_tDecodeStart = timeGetTime();
        MSR_START(m_idTransform);

        // have the derived class transform the data
        hr = Transform(pSample, pOutSample);

        // Stop the clock (and log it if PERF is defined)
        MSR_STOP(m_idTransform);
        m_tDecodeStart = timeGetTime()-m_tDecodeStart;
        m_itrAvgDecode = m_tDecodeStart*(10000/16) + 15*(m_itrAvgDecode/16);

        // Maybe we're waiting for a keyframe still?
        if (m_nWaitForKey)
            m_nWaitForKey--;
        if (m_nWaitForKey && pSample->IsSyncPoint() == S_OK)
    m_nWaitForKey = FALSE;

        // if so, then we don't want to pass this on to the renderer
        if (m_nWaitForKey && hr == NOERROR) {
            DbgLog((LOG_TRACE,3,TEXT("still waiting for a keyframe")));
    hr = S_FALSE;
}
    }

    if (FAILED(hr)) {
        DbgLog((LOG_TRACE,1,TEXT("Error from video transform")));
    } else {
        // the Transform() function can return S_FALSE to indicate that the
        // sample should not be delivered; we only deliver the sample if it's
        // really S_OK (same as NOERROR, of course.)
        // Try not to return S_FALSE to a direct draw buffer (it's wasteful)
        // Try to take the decision earlier - before you get it.

        if (hr == NOERROR) {
        hr = m_pOutput->Deliver(pOutSample);
        } else {
            // S_FALSE returned from Transform is a PRIVATE agreement
            // We should return NOERROR from Receive() in this case because returning S_FALSE
            // from Receive() means that this is the end of the stream and no more data should
            // be sent.
            if (S_FALSE == hr) {

                //  We must Release() the sample before doing anything
                //  like calling the filter graph because having the
                //  sample means we may have the DirectDraw lock
                //  (== win16 lock on some versions)
                pOutSample->Release();
                m_bSampleSkipped = TRUE;
                if (!m_bQualityChanged) {
                    m_bQualityChanged = TRUE;
                    NotifyEvent(EC_QUALITY_CHANGE,0,0);
                }
                return NOERROR;
            }
        }
    }

    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    pOutSample->Release();
    ASSERT(CritCheckIn(&m_csReceive));

    return hr;
}

BOOL CVideoTransformFilter::ShouldSkipFrame( IMediaSample * pIn)
{
    REFERENCE_TIME trStart, trStopAt;
    HRESULT hr = pIn->GetTime(&trStart, &trStopAt);

    // Don't skip frames with no timestamps
    if (hr != S_OK)
return FALSE;

    int itrFrame = (int)(trStopAt - trStart);  // frame duration

    if(S_OK==pIn->IsSyncPoint()) {
        MSR_INTEGER(m_idFrameType, 1);
        if ( m_nKeyFramePeriod < m_nFramesSinceKeyFrame ) {
            // record the max
            m_nKeyFramePeriod = m_nFramesSinceKeyFrame;
        }
        m_nFramesSinceKeyFrame = 0;
        m_bSkipping = FALSE;
    } else {
        MSR_INTEGER(m_idFrameType, 2);
        if (  m_nFramesSinceKeyFrame>m_nKeyFramePeriod
           && m_nKeyFramePeriod>0
           ) {
            // We haven't seen the key frame yet, but we were clearly being
            // overoptimistic about how frequent they are.
            m_nKeyFramePeriod = m_nFramesSinceKeyFrame;
        }
    }

    // Whatever we might otherwise decide,
    // if we are taking only a small fraction of the required frame time to decode
    // then any quality problems are actually coming from somewhere else.
    // Could be a net problem at the source for instance.  In this case there's
    // no point in us skipping frames here.
    if (m_itrAvgDecode*4>itrFrame) {

        // Don't skip unless we are at least a whole frame late.
        // (We would skip B frames if more than 1/2 frame late, but they're safe).
        if ( m_itrLate > itrFrame ) {

            // Don't skip unless the anticipated key frame would be no more than
            // 1 frame early.  If the renderer has not been waiting (we *guess*
            // it hasn't because we're late) then it will allow frames to be
            // played early by up to a frame.

            // Let T = Stream time from now to anticipated next key frame
            // = (frame duration) * (KeyFramePeriod - FramesSinceKeyFrame)
            // So we skip if T - Late < one frame  i.e.
            //   (duration) * (freq - FramesSince) - Late < duration
            // or (duration) * (freq - FramesSince - 1) < Late

            // We don't dare skip until we have seen some key frames and have
            // some idea how often they occur and they are reasonably frequent.
            if (m_nKeyFramePeriod>0) {
                // It would be crazy - but we could have a stream with key frames
                // a very long way apart - and if they are further than about
                // 3.5 minutes apart then we could get arithmetic overflow in
                // reference time units.  Therefore we switch to mSec at this point
                int it = (itrFrame/10000)
                         * (m_nKeyFramePeriod-m_nFramesSinceKeyFrame -  1);
                MSR_INTEGER(m_idTimeTillKey, it);

                // For debug - might want to see the details - dump them as scratch pad
#ifdef VTRANSPERF
                MSR_INTEGER(0, itrFrame);
                MSR_INTEGER(0, m_nFramesSinceKeyFrame);
                MSR_INTEGER(0, m_nKeyFramePeriod);
#endif
                if (m_itrLate/10000 > it) {
                    m_bSkipping = TRUE;
                    // Now we are committed.  Once we start skipping, we
                    // cannot stop until we hit a key frame.
                } else {
#ifdef VTRANSPERF
                    MSR_INTEGER(0, 777770);  // not near enough to next key
#endif
                }
            } else {
#ifdef VTRANSPERF
                MSR_INTEGER(0, 777771);  // Next key not predictable
#endif
            }
        } else {
#ifdef VTRANSPERF
            MSR_INTEGER(0, 777772);  // Less than one frame late
            MSR_INTEGER(0, m_itrLate);
            MSR_INTEGER(0, itrFrame);
#endif
        }
    } else {
#ifdef VTRANSPERF
        MSR_INTEGER(0, 777773);  // Decode time short - not not worth skipping
        MSR_INTEGER(0, m_itrAvgDecode);
        MSR_INTEGER(0, itrFrame);
#endif
    }

    ++m_nFramesSinceKeyFrame;

    if (m_bSkipping) {
        // We will count down the lateness as we skip each frame.
        // We re-assess each frame.  The key frame might not arrive when expected.
        // We reset m_itrLate if we get a new Quality message, but actually that's
        // not likely because we're not sending frames on to the Renderer.  In
        // fact if we DID get another one it would mean that there's a long
        // pipe between us and the renderer and we might need an altogether
        // better strategy to avoid hunting!
        m_itrLate = m_itrLate - itrFrame;
    }

    MSR_INTEGER(m_idLate, (int)m_itrLate/10000 ); // Note how late we think we are
    if (m_bSkipping) {
        if (!m_bQualityChanged) {
            m_bQualityChanged = TRUE;
            NotifyEvent(EC_QUALITY_CHANGE,0,0);
        }
    }
    return m_bSkipping;
}

HRESULT CVideoTransformFilter::AlterQuality(Quality q)
{
    // to reduce the amount of 64 bit arithmetic, m_itrLate is an int.
    // +, -, >, == etc  are not too bad, but * and / are painful.
    if (m_itrLate>300000000) {
        // Avoid overflow and silliness - more than 30 secs late is already silly
        m_itrLate = 300000000;
    } else {
        m_itrLate = (int)q.Late;
    }
    // We ignore the other fields

    // We're actually not very good at handling this.  In non-direct draw mode
    // most of the time can be spent in the renderer which can skip any frame.
    // In that case we'd rather the renderer handled things.
    // Nevertheless we will keep an eye on it and if we really start getting
    // a very long way behind then we will actually skip - but we'll still tell
    // the renderer (or whoever is downstream) that they should handle quality.

    return E_FAIL;     // Tell the renderer to do his thing.

}

// This will avoid several hundred useless warnings if compiled -W4 by MS VC++ v4
#pragma warning(disable:4514)


VTRANS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Video transform base class.
// This class is derived from CTransformFilter, but is specialised to handle
// the requirements of video quality control by frame dropping.
// This is a non-in-place transform, (i.e. it copies the data) such as a decoder.

class CVideoTransformFilter : public CTransformFilter
{
  public:

    CVideoTransformFilter(TCHAR *, LPUNKNOWN, REFCLSID clsid);
    ~CVideoTransformFilter();
    HRESULT EndFlush();

    // =================================================================
    // ----- override these bits ---------------------------------------
    // =================================================================
    // The following methods are in CTransformFilter which is inherited.
    // They are mentioned here for completeness
    //
    // These MUST be supplied in a derived class
    //
    // NOTE:
    // virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut);
    // virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE;
    // virtual HRESULT CheckTransform
    //     (const CMediaType* mtIn, const CMediaType* mtOut) PURE;
    // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *);
    // virtual HRESULT DecideBufferSize
    //     (IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pprop) PURE;
    // virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE;
    //
    // These MAY also be overridden
    //
    // virtual HRESULT StopStreaming();
    // virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt);
    // virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin);
    // virtual HRESULT BreakConnect(PIN_DIRECTION dir);
    // virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin);
    // virtual HRESULT EndOfStream(void);
    // virtual HRESULT BeginFlush(void);
    // virtual HRESULT EndFlush(void);
    // virtual HRESULT NewSegment
    //     (REFERENCE_TIME tStart,REFERENCE_TIME tStop,double dRate);
#ifdef PERF

    // If you override this - ensure that you register all these ids
    // as well as any of your own,
    virtual void RegisterPerfId() {
        m_idSkip        = MSR_REGISTER("Video Transform Skip frame");
        m_idFrameType   = MSR_REGISTER("Video transform frame type");
        m_idLate        = MSR_REGISTER("Video Transform Lateness");
        m_idTimeTillKey = MSR_REGISTER("Video Transform Estd. time to next key");
        CTransformFilter::RegisterPerfId();
    }
#endif

  protected:

    // =========== QUALITY MANAGEMENT IMPLEMENTATION ========================
    // Frames are assumed to come in three types:
    // Type 1: an AVI key frame or an MPEG I frame.
    //        This frame can be decoded with no history.
    //        Dropping this frame means that no further frame can be decoded
    //        until the next type 1 frame.
    //        Type 1 frames are sync points.
    // Type 2: an AVI non-key frame or an MPEG P frame.
    //        This frame cannot be decoded unless the previous type 1 frame was
    //        decoded and all type 2 frames since have been decoded.
    //        Dropping this frame means that no further frame can be decoded
    //        until the next type 1 frame.
    // Type 3: An MPEG B frame.
    //        This frame cannot be decoded unless the previous type 1 or 2 frame
    //        has been decoded AND the subsequent type 1 or 2 frame has also
    //        been decoded.  (This requires decoding the frames out of sequence).
    //        Dropping this frame affects no other frames.  This implementation
    //        does not allow for these.  All non-sync-point frames are treated
    //        as being type 2.
    //
    // The spacing of frames of type 1 in a file is not guaranteed.  There MUST
    // be a type 1 frame at (well, near) the start of the file in order to start
    // decoding at all.  After that there could be one every half second or so,
    // there could be one at the start of each scene (aka "cut", "shot") or
    // there could be no more at all.
    // If there is only a single type 1 frame then NO FRAMES CAN BE DROPPED
    // without losing all the rest of the movie.  There is no way to tell whether
    // this is the case, so we find that we are in the gambling business.
    // To try to improve the odds, we record the greatest interval between type 1s
    // that we have seen and we bet on things being no worse than this in the
    // future.  This is of course a poor show, but it's "the only show in town".

    // You can tell if it's a type 1 frame by calling IsSyncPoint().
    // there is no architected way to test for a type 3, so you should override
    // the quality management here if you have B-frames.

    int m_nKeyFramePeriod; // the largest observed interval between type 1 frames
                           // 1 means every frame is type 1, 2 means every other.

    int m_nFramesSinceKeyFrame; // Used to count frames since the last type 1.
                                // becomes the new m_nKeyFramePeriod if greater.

    BOOL m_bSkipping;           // we are skipping to the next type 1 frame

#ifdef PERF
    int m_idFrameType;          // MSR id Frame type.  1=Key, 2="non-key"
    int m_idSkip;               // MSR id skipping
    int m_idLate;               // MSR id lateness
    int m_idTimeTillKey;        // MSR id for guessed time till next key frame.
#endif

    virtual HRESULT StartStreaming();

    HRESULT Receive(IMediaSample *pSample);

    HRESULT AlterQuality(Quality q);

    BOOL ShouldSkipFrame(IMediaSample * pIn);

    int m_itrLate;              // lateness from last Quality message
                                // (this overflows at 214 secs late).
    int m_tDecodeStart;         // timeGetTime when decode started.
    int m_itrAvgDecode;         // Average decode time in reference units.

    BOOL m_bNoSkip;             // debug - no skipping.

    // We send an EC_QUALITY_CHANGE notification to the app if we have to degrade.
    // We send one when we start degrading, not one for every frame, this means
    // we track whether we've sent one yet.
    BOOL m_bQualityChanged;

    // When non-zero, don't pass anything to renderer until next keyframe
    // If there are few keys, give up and eventually draw something
    int m_nWaitForKey;
};


WINCTRL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Video control interface base classes, December 1995

#include <streams.h>

// The control interface methods require us to be connected

#define CheckConnected(pin,code)                    \
{                                                   \
    if (pin == NULL) {                              \
        ASSERT(!TEXT("Pin not set"));               \
    } else if (pin->IsConnected() == FALSE) {       \
        return (code);                              \
    }                                               \
}

// This checks to see whether the window has a drain. An application can in
// most environments set the owner/parent of windows so that they appear in
// a compound document context (for example). In this case, the application
// would probably like to be told of any keyboard/mouse messages. Therefore
// we pass these messages on untranslated, returning TRUE if we're successful

BOOL WINAPI PossiblyEatMessage(HWND hwndDrain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (hwndDrain != NULL && !InSendMessage())
    {
        switch (uMsg)
        {
            case WM_CHAR:
            case WM_DEADCHAR:
            case WM_KEYDOWN:
            case WM_KEYUP:
            case WM_LBUTTONDBLCLK:
            case WM_LBUTTONDOWN:
            case WM_LBUTTONUP:
            case WM_MBUTTONDBLCLK:
            case WM_MBUTTONDOWN:
            case WM_MBUTTONUP:
            case WM_MOUSEACTIVATE:
            case WM_MOUSEMOVE:
            // If we pass this on we don't get any mouse clicks
            //case WM_NCHITTEST:
            case WM_NCLBUTTONDBLCLK:
            case WM_NCLBUTTONDOWN:
            case WM_NCLBUTTONUP:
            case WM_NCMBUTTONDBLCLK:
            case WM_NCMBUTTONDOWN:
            case WM_NCMBUTTONUP:
            case WM_NCMOUSEMOVE:
            case WM_NCRBUTTONDBLCLK:
            case WM_NCRBUTTONDOWN:
            case WM_NCRBUTTONUP:
            case WM_RBUTTONDBLCLK:
            case WM_RBUTTONDOWN:
            case WM_RBUTTONUP:
            case WM_SYSCHAR:
            case WM_SYSDEADCHAR:
            case WM_SYSKEYDOWN:
            case WM_SYSKEYUP:

                DbgLog((LOG_TRACE, 2, TEXT("Forwarding %x to drain")));
                PostMessage(hwndDrain, uMsg, wParam, lParam);

                return TRUE;
        }
    }
    return FALSE;
}

// This class implements the IVideoWindow control functions (dual interface)
// we support a large number of properties and methods designed to allow the
// client (whether it be an automation controller or a C/C++ application) to
// set and get a number of window related properties such as it's position.
// We also support some methods that duplicate the properties but provide a
// more direct and efficient mechanism as many values may be changed in one

CBaseControlWindow::CBaseControlWindow(
                        CBaseFilter *pFilter,        // Owning filter
                        CCritSec *pInterfaceLock,    // Locking object
                        TCHAR *pName,                // Object description
                        LPUNKNOWN pUnk,              // Normal COM ownership
                        HRESULT *phr) :              // OLE return code

    CBaseVideoWindow(pName,pUnk),
    m_pInterfaceLock(pInterfaceLock),
    m_hwndOwner(NULL),
    m_hwndDrain(NULL),
    m_bAutoShow(TRUE),
    m_pFilter(pFilter),
    m_bCursorHidden(FALSE),
    m_pPin(NULL)
{
    ASSERT(m_pFilter);
    ASSERT(m_pInterfaceLock);
    ASSERT(phr);
    m_BorderColour = VIDEO_COLOUR;
}

// Set the title caption on the base window, we don't do any field checking
// as we really don't care what title they intend to have. We can always get
// it back again later with GetWindowText. The only other complication is to
// do the necessary string conversions between ANSI and OLE Unicode strings

STDMETHODIMP CBaseControlWindow::put_Caption(BSTR strCaption)
{
    CheckPointer(strCaption,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
#ifdef UNICODE
    SetWindowText(m_hwnd, strCaption);
#else
    CHAR Caption[CAPTION];

    WideCharToMultiByte(CP_ACP,0,strCaption,-1,Caption,CAPTION,NULL,NULL);
    SetWindowText(m_hwnd, Caption);
#endif
    return NOERROR;
}

// Get the current base window title caption, once again we do no real field
// checking. We allocate a string for the window title to be filled in with
// which ensures the interface doesn't fiddle around with getting memory. A
// BSTR is a normal C string with the length at position (-1), we use the
// WriteBSTR helper function to create the caption to try and avoid OLE32

STDMETHODIMP CBaseControlWindow::get_Caption(BSTR *pstrCaption)
{
    CheckPointer(pstrCaption,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    WCHAR WideCaption[CAPTION];

#ifdef UNICODE
    GetWindowText(m_hwnd,WideCaption,CAPTION);
#else
    // Convert the ASCII caption to a UNICODE string

    TCHAR Caption[CAPTION];
    GetWindowText(m_hwnd,Caption,CAPTION);
    MultiByteToWideChar(CP_ACP,0,Caption,-1,WideCaption,CAPTION);
#endif
    return WriteBSTR(pstrCaption,WideCaption);
}

// Set the window style using GWL_EXSTYLE

STDMETHODIMP CBaseControlWindow::put_WindowStyleEx(long WindowStyleEx)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Should we be taking off WS_EX_TOPMOST

    if (GetWindowLong(m_hwnd,GWL_EXSTYLE) & WS_EX_TOPMOST) {
        if ((WindowStyleEx & WS_EX_TOPMOST) == 0) {
            SendMessage(m_hwnd,m_ShowStageTop,(WPARAM) FALSE,(LPARAM) 0);
        }
    }

    // Likewise should we be adding WS_EX_TOPMOST

    if (WindowStyleEx & WS_EX_TOPMOST) {
        SendMessage(m_hwnd,m_ShowStageTop,(WPARAM) TRUE,(LPARAM) 0);
        WindowStyleEx &= (~WS_EX_TOPMOST);
        if (WindowStyleEx == 0) return NOERROR;
    }
    return DoSetWindowStyle(WindowStyleEx,GWL_EXSTYLE);
}

// Gets the current GWL_EXSTYLE base window style

STDMETHODIMP CBaseControlWindow::get_WindowStyleEx(long *pWindowStyleEx)
{
    CheckPointer(pWindowStyleEx,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    return DoGetWindowStyle(pWindowStyleEx,GWL_EXSTYLE);
}

// Set the window style using GWL_STYLE

STDMETHODIMP CBaseControlWindow::put_WindowStyle(long WindowStyle)
{
    // These styles cannot be changed dynamically

    if ((WindowStyle & WS_DISABLED) ||
        (WindowStyle & WS_ICONIC) ||
        (WindowStyle & WS_MAXIMIZE) ||
        (WindowStyle & WS_MINIMIZE) ||
        (WindowStyle & WS_HSCROLL) ||
        (WindowStyle & WS_VSCROLL)) {

            return E_INVALIDARG;
    }

    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    return DoSetWindowStyle(WindowStyle,GWL_STYLE);
}

// Get the current GWL_STYLE base window style

STDMETHODIMP CBaseControlWindow::get_WindowStyle(long *pWindowStyle)
{
    CheckPointer(pWindowStyle,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    return DoGetWindowStyle(pWindowStyle,GWL_STYLE);
}

// Change the base window style or the extended styles depending on whether
// WindowLong is GWL_STYLE or GWL_EXSTYLE. We must call SetWindowPos to have
// the window displayed in it's new style after the change which is a little
// tricky if the window is not currently visible as we realise it offscreen.
// In most cases the client will call get_WindowStyle before they call this
// and then AND and OR in extra bit settings according to the requirements

HRESULT CBaseControlWindow::DoSetWindowStyle(long Style,long WindowLong)
{
    RECT WindowRect;

    // Get the window's visibility before setting the style
    BOOL bVisible = IsWindowVisible(m_hwnd);
    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    // Set the new style flags for the window
    SetWindowLong(m_hwnd,WindowLong,Style);
    UINT WindowFlags = SWP_SHOWWINDOW | SWP_FRAMECHANGED | SWP_NOACTIVATE;
    WindowFlags |= SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE;

    // Show the window again in the current position

    if (bVisible == TRUE) {

        SetWindowPos(m_hwnd,            // Base window handle
                     HWND_TOP,          // Just a place holder
                     0,0,0,0,           // Leave size and position
                     WindowFlags);      // Just draw it again

        return NOERROR;
    }

    // Move the window offscreen so the user doesn't see the changes

    MoveWindow((HWND) m_hwnd,                     // Base window handle
               GetSystemMetrics(SM_CXSCREEN),     // Current desktop width
               GetSystemMetrics(SM_CYSCREEN),     // Likewise it's height
               WIDTH(&WindowRect),                // Use the same width
               HEIGHT(&WindowRect),               // Keep height same to
               TRUE);                             // May as well repaint

    // Now show the previously hidden window

    SetWindowPos(m_hwnd,            // Base window handle
                 HWND_TOP,          // Just a place holder
                 0,0,0,0,           // Leave size and position
                 WindowFlags);      // Just draw it again

    EXECUTE_ASSERT(ShowWindow(m_hwnd,SW_HIDE));

    if (GetParent(m_hwnd)) {

        MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2);
    }

    MoveWindow((HWND) m_hwnd,        // Base window handle
               WindowRect.left,      // Existing x coordinate
               WindowRect.top,       // Existing y coordinate
               WIDTH(&WindowRect),   // Use the same width
               HEIGHT(&WindowRect),  // Keep height same to
               TRUE);                // May as well repaint

    return NOERROR;
}

// Get the current base window style (either GWL_STYLE or GWL_EXSTYLE)

HRESULT CBaseControlWindow::DoGetWindowStyle(long *pStyle,long WindowLong)
{
    *pStyle = GetWindowLong(m_hwnd,WindowLong);
    return NOERROR;
}

// Change the visibility of the base window, this takes the same parameters
// as the ShowWindow Win32 API does, so the client can have the window hidden
// or shown, minimised to an icon, or maximised to play in full screen mode
// We pass the request on to the base window to actually make the change

STDMETHODIMP CBaseControlWindow::put_WindowState(long WindowState)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    DoShowWindow(WindowState);
    return NOERROR;
}

// Get the current window state, this function returns a subset of the SW bit
// settings available in ShowWindow, if the window is visible then SW_SHOW is
// set, if it is hidden then the SW_HIDDEN is set, if it is either minimised
// or maximised then the SW_MINIMIZE or SW_MAXIMIZE is set respectively. The
// other SW bit settings are really set commands not readable output values

STDMETHODIMP CBaseControlWindow::get_WindowState(long *pWindowState)
{
    CheckPointer(pWindowState,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    ASSERT(pWindowState);
    *pWindowState = FALSE;

    // Is the base window iconic

    if (IsIconic(m_hwnd) == TRUE) {
        *pWindowState |= SW_MINIMIZE;
    }

    // Has the window been maximised

    if (IsZoomed(m_hwnd) == TRUE) {
        *pWindowState |= SW_MAXIMIZE;
    }

    // Is the window visible, a window is termed visible if it is somewhere on
    // the current desktop even if it is completely obscured by other windows
    // so the flag is a style for each window set with the WS_VISIBLE bit

    if (IsWindowVisible(m_hwnd) == TRUE) {
        *pWindowState |= SW_SHOW;
    } else {
        *pWindowState |= SW_HIDE;
    }
    return NOERROR;
}

// This makes sure that any palette we realise in the base window (through a
// media type or through the overlay interface) is done in the background and
// is therefore mapped to existing device entries rather than taking it over
// as it will do when we this window gets the keyboard focus. An application
// uses this to make sure it doesn't have it's palette removed by the window

STDMETHODIMP CBaseControlWindow::put_BackgroundPalette(long BackgroundPalette)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cWindowLock(&m_WindowLock);

    // Check this is a valid automation boolean type

    if (BackgroundPalette != OATRUE) {
        if (BackgroundPalette != OAFALSE) {
            return E_INVALIDARG;
        }
    }

    // Make sure the window realises any palette it has again

    m_bBackground = (BackgroundPalette == OATRUE ? TRUE : FALSE);
    PostMessage(m_hwnd,m_RealizePalette,0,0);
    PaintWindow(FALSE);

    return NOERROR;
}

// This returns the current background realisation setting

STDMETHODIMP
CBaseControlWindow::get_BackgroundPalette(long *pBackgroundPalette)
{
    CheckPointer(pBackgroundPalette,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cWindowLock(&m_WindowLock);

    // Get the current background palette setting

    *pBackgroundPalette = (m_bBackground == TRUE ? OATRUE : OAFALSE);
    return NOERROR;
}

// Change the visibility of the base window

STDMETHODIMP CBaseControlWindow::put_Visible(long Visible)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Check this is a valid automation boolean type

    if (Visible != OATRUE) {
        if (Visible != OAFALSE) {
            return E_INVALIDARG;
        }
    }

    // Convert the boolean visibility into SW_SHOW and SW_HIDE

    INT Mode = (Visible == OATRUE ? SW_SHOWNORMAL : SW_HIDE);
    DoShowWindow(Mode);
    return NOERROR;
}

// Return OATRUE if the window is currently visible otherwise OAFALSE

STDMETHODIMP CBaseControlWindow::get_Visible(long *pVisible)
{
    CheckPointer(pVisible,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // See if the base window has a WS_VISIBLE style - this will return TRUE
    // even if the window is completely obscured by other desktop windows, we
    // return FALSE if the window is not showing because of earlier calls

    BOOL Mode = IsWindowVisible(m_hwnd);
    *pVisible = (Mode == TRUE ? OATRUE : OAFALSE);
    return NOERROR;
}

// Change the left position of the base window. This keeps the window width
// and height properties the same so it effectively shunts the window left or
// right accordingly - there is the Width property to change that dimension

STDMETHODIMP CBaseControlWindow::put_Left(long Left)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bSuccess;
    RECT WindowRect;

    // Get the current window position in a RECT
    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    if (GetParent(m_hwnd)) {

        MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2);
    }

    // Adjust the coordinates ready for SetWindowPos, the window rectangle we
    // get back from GetWindowRect is in left,top,right and bottom while the
    // coordinates SetWindowPos wants are left,top,width and height values

    WindowRect.bottom = WindowRect.bottom - WindowRect.top;
    WindowRect.right = WindowRect.right - WindowRect.left;
    UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;

    bSuccess = SetWindowPos(m_hwnd,                // Window handle
                            HWND_TOP,              // Put it at the top
                            Left,                  // New left position
                            WindowRect.top,        // Leave top alone
                            WindowRect.right,      // The WIDTH (not right)
                            WindowRect.bottom,     // The HEIGHT (not bottom)
                            WindowFlags);          // Show window options

    if (bSuccess == FALSE) {
        return E_INVALIDARG;
    }
    return NOERROR;
}

// Return the current base window left position

STDMETHODIMP CBaseControlWindow::get_Left(long *pLeft)
{
    CheckPointer(pLeft,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT WindowRect;

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));
    *pLeft = WindowRect.left;
    return NOERROR;
}

// Change the current width of the base window. This property complements the
// left position property so we must keep the left edge constant and expand or
// contract to the right, the alternative would be to change the left edge so
// keeping the right edge constant but this is maybe a little more intuitive

STDMETHODIMP CBaseControlWindow::put_Width(long Width)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bSuccess;
    RECT WindowRect;

    // Adjust the coordinates ready for SetWindowPos, the window rectangle we
    // get back from GetWindowRect is in left,top,right and bottom while the
    // coordinates SetWindowPos wants are left,top,width and height values

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    if (GetParent(m_hwnd)) {

        MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2);
    }

    WindowRect.bottom = WindowRect.bottom - WindowRect.top;
    UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;

    // This seems to have a bug in that calling SetWindowPos on a window with
    // just the width changing causes it to ignore the width that you pass in
    // and sets it to a mimimum value of 110 pixels wide (Windows NT 3.51)

    bSuccess = SetWindowPos(m_hwnd,                // Window handle
                            HWND_TOP,              // Put it at the top
                            WindowRect.left,       // Leave left alone
                            WindowRect.top,        // Leave top alone
                            Width,                 // New WIDTH dimension
                            WindowRect.bottom,     // The HEIGHT (not bottom)
                            WindowFlags);          // Show window options

    if (bSuccess == FALSE) {
        return E_INVALIDARG;
    }
    return NOERROR;
}

// Return the current base window width

STDMETHODIMP CBaseControlWindow::get_Width(long *pWidth)
{
    CheckPointer(pWidth,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT WindowRect;

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));
    *pWidth = WindowRect.right - WindowRect.left;
    return NOERROR;
}

// This allows the client program to change the top position for the window in
// the same way that changing the left position does not affect the width of
// the image so changing the top position does not affect the window height

STDMETHODIMP CBaseControlWindow::put_Top(long Top)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bSuccess;
    RECT WindowRect;

    // Get the current window position in a RECT
    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    if (GetParent(m_hwnd)) {

        MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2);
    }

    // Adjust the coordinates ready for SetWindowPos, the window rectangle we
    // get back from GetWindowRect is in left,top,right and bottom while the
    // coordinates SetWindowPos wants are left,top,width and height values

    WindowRect.bottom = WindowRect.bottom - WindowRect.top;
    WindowRect.right = WindowRect.right - WindowRect.left;
    UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;

    bSuccess = SetWindowPos(m_hwnd,                // Window handle
                            HWND_TOP,              // Put it at the top
                            WindowRect.left,       // Leave left alone
                            Top,                   // New top position
                            WindowRect.right,      // The WIDTH (not right)
                            WindowRect.bottom,     // The HEIGHT (not bottom)
                            WindowFlags);          // Show window flags

    if (bSuccess == FALSE) {
        return E_INVALIDARG;
    }
    return NOERROR;
}

// Return the current base window top position

STDMETHODIMP CBaseControlWindow::get_Top(long *pTop)
{
    CheckPointer(pTop,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT WindowRect;

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));
    *pTop = WindowRect.top;
    return NOERROR;
}

// Change the height of the window, this complements the top property so when
// we change this we must keep the top position for the base window, as said
// before we could keep the bottom and grow upwards although this is perhaps
// a little more intuitive since we already have a top position property

STDMETHODIMP CBaseControlWindow::put_Height(long Height)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bSuccess;
    RECT WindowRect;

    // Adjust the coordinates ready for SetWindowPos, the window rectangle we
    // get back from GetWindowRect is in left,top,right and bottom while the
    // coordinates SetWindowPos wants are left,top,width and height values

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    if (GetParent(m_hwnd)) {

        MapWindowPoints(HWND_DESKTOP, GetParent(m_hwnd), (LPPOINT)&WindowRect, 2);
    }

    WindowRect.right = WindowRect.right - WindowRect.left;
    UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;

    bSuccess = SetWindowPos(m_hwnd,                // Window handle
                            HWND_TOP,              // Put it at the top
                            WindowRect.left,       // Leave left alone
                            WindowRect.top,        // Leave top alone
                            WindowRect.right,      // The WIDTH (not right)
                            Height,                // New height dimension
                            WindowFlags);          // Show window flags

    if (bSuccess == FALSE) {
        return E_INVALIDARG;
    }
    return NOERROR;
}

// Return the current base window height

STDMETHODIMP CBaseControlWindow::get_Height(long *pHeight)
{
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT WindowRect;

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));
    *pHeight = WindowRect.bottom - WindowRect.top;
    return NOERROR;
}

// This can be called to change the owning window. Setting the owner is done
// through this function, however to make the window a true child window the
// style must also be set to WS_CHILD. After resetting the owner to NULL an
// application should also set the style to WS_OVERLAPPED | WS_CLIPCHILDREN.

// We cannot lock the object here because the SetParent causes an interthread
// SendMessage to the owner window. If they are in GetState we will sit here
// incomplete with the critical section locked therefore blocking out source
// filter threads from accessing us. Because the source thread can't enter us
// it can't get buffers or call EndOfStream so the GetState will not complete

STDMETHODIMP CBaseControlWindow::put_Owner(OAHWND Owner)
{
    // Check we are connected otherwise reject the call

    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    m_hwndOwner = (HWND) Owner;
    HWND hwndParent = m_hwndOwner;

    // Add or remove WS_CHILD as appropriate

    LONG Style = GetWindowLong(m_hwnd,GWL_STYLE);
    if (Owner == NULL) {
        Style &= (~WS_CHILD);
    } else {
        Style |= (WS_CHILD);
    }
    SetWindowLong(m_hwnd,GWL_STYLE,Style);

    // Don't call this with the filter locked

    SetParent(m_hwnd,hwndParent);

    PaintWindow(TRUE);
    NOTE1("Changed parent %lx",hwndParent);

    return NOERROR;
}

// This complements the put_Owner to get the current owning window property
// we always return NOERROR although the returned window handle may be NULL
// to indicate no owning window (the desktop window doesn't qualify as one)
// If an application sets the owner we call SetParent, however that returns
// NULL until the WS_CHILD bit is set on, so we store the owner internally

STDMETHODIMP CBaseControlWindow::get_Owner(OAHWND *Owner)
{
    CheckPointer(Owner,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    *Owner = (OAHWND) m_hwndOwner;
    return NOERROR;
}

// And renderer supporting IVideoWindow may have an HWND set who will get any
// keyboard and mouse messages we receive posted on to them. This is separate
// from setting an owning window. By separating the two, applications may get
// messages sent on even when they have set no owner (perhaps it's maximised)

STDMETHODIMP CBaseControlWindow::put_MessageDrain(OAHWND Drain)
{
    // Check we are connected otherwise reject the call

    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    m_hwndDrain = (HWND) Drain;
    return NOERROR;
}

// Return the current message drain

STDMETHODIMP CBaseControlWindow::get_MessageDrain(OAHWND *Drain)
{
    CheckPointer(Drain,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    *Drain = (OAHWND) m_hwndDrain;
    return NOERROR;
}

// This is called by the filter graph to inform us of a message we should know
// is being sent to our owning window. We have this because as a child window
// we do not get certain messages that are only sent to top level windows. We
// must see the palette changed/changing/query messages so that we know if we
// have the foreground palette or not. We pass the message on to our window
// using SendMessage - this will cause an interthread send message to occur

STDMETHODIMP
CBaseControlWindow::NotifyOwnerMessage(long hwnd,    // Window handle
                                       long uMsg,    // Message ID
                                       long wParam,  // Parameters
                                       long lParam)  // for message
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Only interested in these Windows messages

    switch (uMsg) {

        case WM_SYSCOLORCHANGE:
        case WM_PALETTECHANGED:
        case WM_PALETTEISCHANGING:
        case WM_QUERYNEWPALETTE:
        case WM_DEVMODECHANGE:
        case WM_DISPLAYCHANGE:
        case WM_ACTIVATEAPP:

            // If we do not have an owner then ignore

            if (m_hwndOwner == NULL) {
                return NOERROR;
            }
            SendMessage(m_hwnd,uMsg,(WPARAM)wParam,(LPARAM)lParam);
    }
    return NOERROR;
}

// Allow an application to have us set the base window in the foreground. We
// have this because it is difficult for one thread to do do this to a window
// owned by another thread. We ask the base window class to do the real work

STDMETHODIMP CBaseControlWindow::SetWindowForeground(long Focus)
{
    // Check this is a valid automation boolean type

    if (Focus != OATRUE) {
        if (Focus != OAFALSE) {
            return E_INVALIDARG;
        }
    }

    // We shouldn't lock as this sends a message

    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bFocus = (Focus == OATRUE ? TRUE : FALSE);
    DoSetWindowForeground(bFocus);

    return NOERROR;
}

// This allows a client to set the complete window size and position in one
// atomic operation. The same affect can be had by changing each dimension
// in turn through their individual properties although some flashing will
// occur as each of them gets updated (they are better set at design time)

STDMETHODIMP
CBaseControlWindow::SetWindowPosition(long Left,long Top,long Width,long Height)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    BOOL bSuccess;

    // Set the new size and position
    UINT WindowFlags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;

    ASSERT(IsWindow(m_hwnd));
    bSuccess = SetWindowPos(m_hwnd,         // Window handle
                            HWND_TOP,       // Put it at the top
                            Left,           // Left position
                            Top,            // Top position
                            Width,          // Window width
                            Height,         // Window height
                            WindowFlags);   // Show window flags
    ASSERT(bSuccess);
#ifdef DEBUG
    DbgLog((LOG_TRACE, 1, TEXT("SWP failed error %d"), GetLastError()));
#endif
    if (bSuccess == FALSE) {
        return E_INVALIDARG;
    }
    return NOERROR;
}

// This complements the SetWindowPosition to return the current window place
// in device coordinates. As before the same information can be retrived by
// calling the property get functions individually but this is atomic and is

// therefore more suitable to a live environment rather than design time

STDMETHODIMP
CBaseControlWindow::GetWindowPosition(long *pLeft,long *pTop,long *pWidth,long *pHeight)
{
    // Should check the pointers are not NULL

    CheckPointer(pLeft,E_POINTER);
    CheckPointer(pTop,E_POINTER);
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT WindowRect;

    // Get the current window coordinates

    EXECUTE_ASSERT(GetWindowRect(m_hwnd,&WindowRect));

    // Convert the RECT into left,top,width and height values

    *pLeft = WindowRect.left;
    *pTop = WindowRect.top;
    *pWidth = WindowRect.right - WindowRect.left;
    *pHeight = WindowRect.bottom - WindowRect.top;

    return NOERROR;
}

// When a window is maximised or iconic calling GetWindowPosition will return
// the current window position (likewise for the properties). However if the
// restored size (ie the size we'll return to when normally shown) is needed
// then this should be used. When in a normal position (neither iconic nor
// maximised) then this returns the same coordinates as GetWindowPosition

STDMETHODIMP
CBaseControlWindow::GetRestorePosition(long *pLeft,long *pTop,long *pWidth,long *pHeight)
{
    // Should check the pointers are not NULL

    CheckPointer(pLeft,E_POINTER);
    CheckPointer(pTop,E_POINTER);
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Use GetWindowPlacement to find the restore position

    WINDOWPLACEMENT Place;
    Place.length = sizeof(WINDOWPLACEMENT);
    EXECUTE_ASSERT(GetWindowPlacement(m_hwnd,&Place));

    RECT WorkArea;

    // We must take into account any task bar present

    if (SystemParametersInfo(SPI_GETWORKAREA,0,&WorkArea,FALSE) == TRUE) {
        if (GetParent(m_hwnd) == NULL) {
            Place.rcNormalPosition.top += WorkArea.top;
            Place.rcNormalPosition.bottom += WorkArea.top;
            Place.rcNormalPosition.left += WorkArea.left;
            Place.rcNormalPosition.right += WorkArea.left;
        }
    }

    // Convert the RECT into left,top,width and height values

    *pLeft = Place.rcNormalPosition.left;
    *pTop = Place.rcNormalPosition.top;
    *pWidth = Place.rcNormalPosition.right - Place.rcNormalPosition.left;
    *pHeight = Place.rcNormalPosition.bottom - Place.rcNormalPosition.top;

    return NOERROR;
}

// Return the current border colour, if we are playing something to a subset
// of the base window display there is an outside area exposed. The default
// action is to paint this colour in the Windows background colour (defined
// as value COLOR_WINDOW) We reset to this default when we're disconnected

STDMETHODIMP CBaseControlWindow::get_BorderColor(long *Color)
{
    CheckPointer(Color,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    *Color = (long) m_BorderColour;
    return NOERROR;
}

// This can be called to set the current border colour

STDMETHODIMP CBaseControlWindow::put_BorderColor(long Color)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Have the window repainted with the new border colour

    m_BorderColour = (COLORREF) Color;
    PaintWindow(TRUE);
    return NOERROR;
}

// Delegate fullscreen handling to plug in distributor

STDMETHODIMP CBaseControlWindow::get_FullScreenMode(long *FullScreenMode)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CheckPointer(FullScreenMode,E_POINTER);
    return E_NOTIMPL;
}

// Delegate fullscreen handling to plug in distributor

STDMETHODIMP CBaseControlWindow::put_FullScreenMode(long FullScreenMode)
{
    return E_NOTIMPL;
}

// This sets the auto show property, this property causes the base window to
// be displayed whenever we change state. This allows an application to have
// to do nothing to have the window appear but still allow them to change the
// default behaviour if for example they want to keep it hidden for longer

STDMETHODIMP CBaseControlWindow::put_AutoShow(long AutoShow)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Check this is a valid automation boolean type

    if (AutoShow != OATRUE) {
        if (AutoShow != OAFALSE) {
            return E_INVALIDARG;
        }
    }

    m_bAutoShow = (AutoShow == OATRUE ? TRUE : FALSE);
    return NOERROR;
}

// This can be called to get the current auto show flag. The flag is updated
// when we connect and disconnect and through this interface all of which are
// controlled and serialised by means of the main renderer critical section

STDMETHODIMP CBaseControlWindow::get_AutoShow(long *AutoShow)
{
    CheckPointer(AutoShow,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    *AutoShow = (m_bAutoShow == TRUE ? OATRUE : OAFALSE);
    return NOERROR;
}

// Return the minimum ideal image size for the current video. This may differ
// to the actual video dimensions because we may be using DirectDraw hardware
// that has specific stretching requirements. For example the Cirrus Logic
// cards have a minimum stretch factor depending on the overlay surface size

STDMETHODIMP
CBaseControlWindow::GetMinIdealImageSize(long *pWidth,long *pHeight)
{
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    FILTER_STATE State;

    // Must not be stopped for this to work correctly

    m_pFilter->GetState(0,&State);
    if (State == State_Stopped) {
        return VFW_E_WRONG_STATE;
    }

    RECT DefaultRect = GetDefaultRect();
    *pWidth = WIDTH(&DefaultRect);
    *pHeight = HEIGHT(&DefaultRect);
    return NOERROR;
}

// Return the maximum ideal image size for the current video. This may differ
// to the actual video dimensions because we may be using DirectDraw hardware
// that has specific stretching requirements. For example the Cirrus Logic
// cards have a maximum stretch factor depending on the overlay surface size

STDMETHODIMP
CBaseControlWindow::GetMaxIdealImageSize(long *pWidth,long *pHeight)
{
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    FILTER_STATE State;

    // Must not be stopped for this to work correctly

    m_pFilter->GetState(0,&State);
    if (State == State_Stopped) {
        return VFW_E_WRONG_STATE;
    }

    RECT DefaultRect = GetDefaultRect();
    *pWidth = WIDTH(&DefaultRect);
    *pHeight = HEIGHT(&DefaultRect);
    return NOERROR;
}

// Allow an application to hide the cursor on our window

STDMETHODIMP
CBaseControlWindow::HideCursor(long HideCursor)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);

    // Check this is a valid automation boolean type

    if (HideCursor != OATRUE) {
        if (HideCursor != OAFALSE) {
            return E_INVALIDARG;
        }
    }

    m_bCursorHidden = (HideCursor == OATRUE ? TRUE : FALSE);
    return NOERROR;
}

// Returns whether we have the cursor hidden or not

STDMETHODIMP CBaseControlWindow::IsCursorHidden(long *CursorHidden)
{
    CheckPointer(CursorHidden,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    *CursorHidden = (m_bCursorHidden == TRUE ? OATRUE : OAFALSE);
    return NOERROR;
}

// This class implements the IBasicVideo control functions (dual interface)
// we support a large number of properties and methods designed to allow the
// client (whether it be an automation controller or a C/C++ application) to
// set and get a number of video related properties such as the native video
// size. We support some methods that duplicate the properties but provide a
// more direct and efficient mechanism as many values may be changed in one

CBaseControlVideo::CBaseControlVideo(
                        CBaseFilter *pFilter,        // Owning filter
                        CCritSec *pInterfaceLock,    // Locking object
                        TCHAR *pName,                // Object description
                        LPUNKNOWN pUnk,              // Normal COM ownership
                        HRESULT *phr) :              // OLE return code

    CBaseBasicVideo(pName,pUnk),
    m_pFilter(pFilter),
    m_pInterfaceLock(pInterfaceLock),
    m_pPin(NULL)
{
    ASSERT(m_pFilter);
    ASSERT(m_pInterfaceLock);
    ASSERT(phr);
}

// Return an approximate average time per frame

STDMETHODIMP CBaseControlVideo::get_AvgTimePerFrame(REFTIME *pAvgTimePerFrame)
{
    CheckPointer(pAvgTimePerFrame,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    COARefTime AvgTime(pVideoInfo->AvgTimePerFrame);
    *pAvgTimePerFrame = (REFTIME) AvgTime;

    return NOERROR;
}

// Return an approximate bit rate for the video

STDMETHODIMP CBaseControlVideo::get_BitRate(long *pBitRate)
{
    CheckPointer(pBitRate,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    *pBitRate = pVideoInfo->dwBitRate;
    return NOERROR;
}

// Return an approximate bit error rate

STDMETHODIMP CBaseControlVideo::get_BitErrorRate(long *pBitErrorRate)
{
    CheckPointer(pBitErrorRate,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    *pBitErrorRate = pVideoInfo->dwBitErrorRate;
    return NOERROR;
}

// This returns the current video width

STDMETHODIMP CBaseControlVideo::get_VideoWidth(long *pVideoWidth)
{
    CheckPointer(pVideoWidth,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    *pVideoWidth = pVideoInfo->bmiHeader.biWidth;
    return NOERROR;
}

// This returns the current video height

STDMETHODIMP CBaseControlVideo::get_VideoHeight(long *pVideoHeight)
{
    CheckPointer(pVideoHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    *pVideoHeight = pVideoInfo->bmiHeader.biHeight;
    return NOERROR;
}

// This returns the current palette the video is using as an array allocated
// by the user. To remain consistent we use PALETTEENTRY fields to return the
// colours in rather than RGBQUADs that multimedia decided to use. The memory
// is allocated by the user so we simple copy each in turn. We check that the
// number of entries requested and the start position offset are both valid
// If the number of entries evaluates to zero then we return an S_FALSE code

STDMETHODIMP CBaseControlVideo::GetVideoPaletteEntries(long StartIndex,
                                                       long Entries,
                                                       long *pRetrieved,
                                                       long *pPalette)
{
    CheckPointer(pRetrieved,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    CMediaType MediaType;

    // Get the video format from the derived class

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);

    // Is the current format palettised

    if (PALETTISED(pVideoInfo) == FALSE) {
        *pRetrieved = 0;
        return VFW_E_NO_PALETTE_AVAILABLE;
    }

    // Do they just want to know how many are available

    if (pPalette == NULL) {
        *pRetrieved = pHeader->biClrUsed;
        return NOERROR;
    }

    // Make sure the start position is a valid offset

    if (StartIndex >= (LONG) pHeader->biClrUsed || StartIndex < 0) {
        *pRetrieved = 0;
        return E_INVALIDARG;
    }

    // Correct the number we can retrieve

    LONG Available = (LONG) pHeader->biClrUsed - StartIndex;
    *pRetrieved = max(0,min(Available,Entries));
    if (*pRetrieved == 0) {
        return S_FALSE;
    }

    // Copy the palette entries to the output buffer

    PALETTEENTRY *pEntries = (PALETTEENTRY *) pPalette;
    RGBQUAD *pColours = COLORS(pVideoInfo) + StartIndex;

    for (LONG Count = 0;Count < *pRetrieved;Count++) {
        pEntries[Count].peRed = pColours[Count].rgbRed;
        pEntries[Count].peGreen = pColours[Count].rgbGreen;
        pEntries[Count].peBlue = pColours[Count].rgbBlue;
        pEntries[Count].peFlags = 0;
    }
    return NOERROR;
}

// This returns the current video dimensions as a method rather than a number
// of individual property get calls. For the same reasons as said before we
// cannot access the renderer media type directly as the window object thread
// may be updating it since dynamic format changes may change these values

STDMETHODIMP CBaseControlVideo::GetVideoSize(long *pWidth,long *pHeight)
{
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    // Get the video format from the derived class
    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    *pWidth = pVideoInfo->bmiHeader.biWidth;
    *pHeight = pVideoInfo->bmiHeader.biHeight;
    return NOERROR;
}

// Set the source video rectangle as left,top,right and bottom coordinates
// rather than left,top,width and height as per OLE automation interfaces
// Then pass the rectangle on to the window object to set the source

STDMETHODIMP
CBaseControlVideo::SetSourcePosition(long Left,long Top,long Width,long Height)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;
    SourceRect.left = Left;
    SourceRect.top = Top;
    SourceRect.right = Left + Width;
    SourceRect.bottom = Top + Height;

    // Check the source rectangle is valid

    HRESULT hr = CheckSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the source rectangle

    hr = SetSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the source rectangle in left,top,width and height rather than the
// left,top,right and bottom values that RECT uses (and which the window
// object returns through GetSourceRect) which requires a little work

STDMETHODIMP
CBaseControlVideo::GetSourcePosition(long *pLeft,long *pTop,long *pWidth,long *pHeight)
{
    // Should check the pointers are non NULL

    CheckPointer(pLeft,E_POINTER);
    CheckPointer(pTop,E_POINTER);
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT SourceRect;

    CAutoLock cInterfaceLock(m_pInterfaceLock);
    GetSourceRect(&SourceRect);

    *pLeft = SourceRect.left;
    *pTop = SourceRect.top;
    *pWidth = WIDTH(&SourceRect);
    *pHeight = HEIGHT(&SourceRect);

    return NOERROR;
}

// Set the video destination as left,top,right and bottom coordinates rather
// than the left,top,width and height uses as per OLE automation interfaces
// Then pass the rectangle on to the window object to set the destination

STDMETHODIMP
CBaseControlVideo::SetDestinationPosition(long Left,long Top,long Width,long Height)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;

    DestinationRect.left = Left;
    DestinationRect.top = Top;
    DestinationRect.right = Left + Width;
    DestinationRect.bottom = Top + Height;

    // Check the target rectangle is valid

    HRESULT hr = CheckTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the new target rectangle

    hr = SetTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the destination rectangle in left,top,width and height rather than
// the left,top,right and bottom values that RECT uses (and which the window
// object returns through GetDestinationRect) which requires a little work

STDMETHODIMP
CBaseControlVideo::GetDestinationPosition(long *pLeft,long *pTop,long *pWidth,long *pHeight)
{
    // Should check the pointers are not NULL

    CheckPointer(pLeft,E_POINTER);
    CheckPointer(pTop,E_POINTER);
    CheckPointer(pWidth,E_POINTER);
    CheckPointer(pHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    RECT DestinationRect;

    CAutoLock cInterfaceLock(m_pInterfaceLock);
    GetTargetRect(&DestinationRect);

    *pLeft = DestinationRect.left;
    *pTop = DestinationRect.top;
    *pWidth = WIDTH(&DestinationRect);
    *pHeight = HEIGHT(&DestinationRect);

    return NOERROR;
}

// Set the source left position, the source rectangle we get back from the
// window object is a true rectangle in left,top,right and bottom positions
// so all we have to do is to update the left position and pass it back. We
// must keep the current width constant when we're updating this property

STDMETHODIMP CBaseControlVideo::put_SourceLeft(long SourceLeft)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;
    GetSourceRect(&SourceRect);
    SourceRect.right = SourceLeft + WIDTH(&SourceRect);
    SourceRect.left = SourceLeft;

    // Check the source rectangle is valid

    HRESULT hr = CheckSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the source rectangle

    hr = SetSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the current left source video position

STDMETHODIMP CBaseControlVideo::get_SourceLeft(long *pSourceLeft)
{
    CheckPointer(pSourceLeft,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;

    GetSourceRect(&SourceRect);
    *pSourceLeft = SourceRect.left;
    return NOERROR;
}

// Set the source width, we get the current source rectangle and then update
// the right position to be the left position (thereby keeping it constant)
// plus the new source width we are passed in (it expands to the right)

STDMETHODIMP CBaseControlVideo::put_SourceWidth(long SourceWidth)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;
    GetSourceRect(&SourceRect);
    SourceRect.right = SourceRect.left + SourceWidth;

    // Check the source rectangle is valid

    HRESULT hr = CheckSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the source rectangle

    hr = SetSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the current source width

STDMETHODIMP CBaseControlVideo::get_SourceWidth(long *pSourceWidth)
{
    CheckPointer(pSourceWidth,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;

    GetSourceRect(&SourceRect);
    *pSourceWidth = WIDTH(&SourceRect);
    return NOERROR;
}

// Set the source top position - changing this property does not affect the
// current source height. So changing this shunts the source rectangle up and
// down appropriately. Changing the height complements this functionality by
// keeping the top position constant and simply changing the source height

STDMETHODIMP CBaseControlVideo::put_SourceTop(long SourceTop)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;
    GetSourceRect(&SourceRect);
    SourceRect.bottom = SourceTop + HEIGHT(&SourceRect);
    SourceRect.top = SourceTop;

    // Check the source rectangle is valid

    HRESULT hr = CheckSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the source rectangle

    hr = SetSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the current top position

STDMETHODIMP CBaseControlVideo::get_SourceTop(long *pSourceTop)
{
    CheckPointer(pSourceTop,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;

    GetSourceRect(&SourceRect);
    *pSourceTop = SourceRect.top;
    return NOERROR;
}

// Set the source height

STDMETHODIMP CBaseControlVideo::put_SourceHeight(long SourceHeight)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;
    GetSourceRect(&SourceRect);
    SourceRect.bottom = SourceRect.top + SourceHeight;

    // Check the source rectangle is valid

    HRESULT hr = CheckSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the source rectangle

    hr = SetSourceRect(&SourceRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the current source height

STDMETHODIMP CBaseControlVideo::get_SourceHeight(long *pSourceHeight)
{
    CheckPointer(pSourceHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT SourceRect;

    GetSourceRect(&SourceRect);
    *pSourceHeight = HEIGHT(&SourceRect);
    return NOERROR;
}

// Set the target left position, the target rectangle we get back from the
// window object is a true rectangle in left,top,right and bottom positions
// so all we have to do is to update the left position and pass it back. We
// must keep the current width constant when we're updating this property

STDMETHODIMP CBaseControlVideo::put_DestinationLeft(long DestinationLeft)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;
    GetTargetRect(&DestinationRect);
    DestinationRect.right = DestinationLeft + WIDTH(&DestinationRect);
    DestinationRect.left = DestinationLeft;

    // Check the target rectangle is valid

    HRESULT hr = CheckTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the new target rectangle

    hr = SetTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the left position for the destination rectangle

STDMETHODIMP CBaseControlVideo::get_DestinationLeft(long *pDestinationLeft)
{
    CheckPointer(pDestinationLeft,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;

    GetTargetRect(&DestinationRect);
    *pDestinationLeft = DestinationRect.left;
    return NOERROR;
}

// Set the destination width

STDMETHODIMP CBaseControlVideo::put_DestinationWidth(long DestinationWidth)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;
    GetTargetRect(&DestinationRect);
    DestinationRect.right = DestinationRect.left + DestinationWidth;

    // Check the target rectangle is valid

    HRESULT hr = CheckTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the new target rectangle

    hr = SetTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the width for the destination rectangle

STDMETHODIMP CBaseControlVideo::get_DestinationWidth(long *pDestinationWidth)
{
    CheckPointer(pDestinationWidth,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;

    GetTargetRect(&DestinationRect);
    *pDestinationWidth = WIDTH(&DestinationRect);
    return NOERROR;
}

// Set the target top position - changing this property does not affect the
// current target height. So changing this shunts the target rectangle up and
// down appropriately. Changing the height complements this functionality by
// keeping the top position constant and simply changing the target height

STDMETHODIMP CBaseControlVideo::put_DestinationTop(long DestinationTop)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;
    GetTargetRect(&DestinationRect);
    DestinationRect.bottom = DestinationTop + HEIGHT(&DestinationRect);
    DestinationRect.top = DestinationTop;

    // Check the target rectangle is valid

    HRESULT hr = CheckTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the new target rectangle

    hr = SetTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the top position for the destination rectangle

STDMETHODIMP CBaseControlVideo::get_DestinationTop(long *pDestinationTop)
{
    CheckPointer(pDestinationTop,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;

    GetTargetRect(&DestinationRect);
    *pDestinationTop = DestinationRect.top;
    return NOERROR;
}

// Set the destination height

STDMETHODIMP CBaseControlVideo::put_DestinationHeight(long DestinationHeight)
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;
    GetTargetRect(&DestinationRect);
    DestinationRect.bottom = DestinationRect.top + DestinationHeight;

    // Check the target rectangle is valid

    HRESULT hr = CheckTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }

    // Now set the new target rectangle

    hr = SetTargetRect(&DestinationRect);
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return the height for the destination rectangle

STDMETHODIMP CBaseControlVideo::get_DestinationHeight(long *pDestinationHeight)
{
    CheckPointer(pDestinationHeight,E_POINTER);
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    RECT DestinationRect;

    GetTargetRect(&DestinationRect);
    *pDestinationHeight = HEIGHT(&DestinationRect);
    return NOERROR;
}

// Reset the source rectangle to the full video dimensions

STDMETHODIMP CBaseControlVideo::SetDefaultSourcePosition()
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    HRESULT hr = SetDefaultSourceRect();
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return S_OK if we're using the default source otherwise S_FALSE

STDMETHODIMP CBaseControlVideo::IsUsingDefaultSource()
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    return IsDefaultSourceRect();
}

// Reset the video renderer to use the entire playback area

STDMETHODIMP CBaseControlVideo::SetDefaultDestinationPosition()
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    HRESULT hr = SetDefaultTargetRect();
    if (FAILED(hr)) {
        return hr;
    }
    return OnUpdateRectangles();
}

// Return S_OK if we're using the default target otherwise S_FALSE

STDMETHODIMP CBaseControlVideo::IsUsingDefaultDestination()
{
    CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    return IsDefaultTargetRect();
}

// Return a copy of the current image in the video renderer

STDMETHODIMP
CBaseControlVideo::GetCurrentImage(long *pBufferSize,long *pVideoImage)
{
    CheckPointer(pBufferSize,E_POINTER);

CheckConnected(m_pPin,VFW_E_NOT_CONNECTED);
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    FILTER_STATE State;

    // Make sure we are in a paused state

    if (pVideoImage != NULL) {
        m_pFilter->GetState(0,&State);
        if (State != State_Paused) {
            return VFW_E_NOT_PAUSED;
        }
        return GetStaticImage(pBufferSize,pVideoImage);
    }

    // Just return the memory required

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    RECT SourceRect;
    GetSourceRect(&SourceRect);
    return GetImageSize(pVideoInfo,pBufferSize,&SourceRect);
}

// An application has two ways of using GetCurrentImage, one is to pass a real
// buffer which should be filled with the current image. The other is to pass
// a NULL buffer pointer which is interpreted as asking us to return how much
// memory is required for the image. The constraints for when the latter can
// be called are much looser. To calculate the memory required we synthesize
// a VIDEOINFO that takes into account the source rectangle that's being used

HRESULT CBaseControlVideo::GetImageSize(VIDEOINFOHEADER *pVideoInfo,
                                        LONG *pBufferSize,
                                        RECT *pSourceRect)
{
    NOTE("Entering GetImageSize");
    ASSERT(pSourceRect);

    // Check we have the correct input parameters

    if (pSourceRect == NULL ||
            pVideoInfo == NULL ||
            pBufferSize == NULL) {

        return E_UNEXPECTED;
    }

    // Is the data format compatible

    if (pVideoInfo->bmiHeader.biCompression != BI_RGB) {
        if (pVideoInfo->bmiHeader.biCompression != BI_BITFIELDS) {
            return E_INVALIDARG;
        }
    }

    ASSERT(IsRectEmpty(pSourceRect) == FALSE);

    BITMAPINFOHEADER bih;
    bih.biWidth = WIDTH(pSourceRect);
    bih.biHeight = HEIGHT(pSourceRect);
    bih.biBitCount = pVideoInfo->bmiHeader.biBitCount;
    LONG Size = DIBSIZE(bih);
    Size += GetBitmapFormatSize(HEADER(pVideoInfo)) - SIZE_PREHEADER;
    *pBufferSize = Size;

    return NOERROR;
}

// Given an IMediaSample containing a linear buffer with an image and a type
// describing the bitmap make a rendering of the image into the output buffer
// This may be called by derived classes who render typical video images to
// handle the IBasicVideo GetCurrentImage method. The pVideoImage pointer may
// be NULL when passed to GetCurrentImage in which case GetImageSize will be
// called instead, which will just do the calculation of the memory required

HRESULT CBaseControlVideo::CopyImage(IMediaSample *pMediaSample,
                                     VIDEOINFOHEADER *pVideoInfo,
                                     LONG *pBufferSize,
                                     BYTE *pVideoImage,
                                     RECT *pSourceRect)
{
    NOTE("Entering CopyImage");
    ASSERT(pSourceRect);
    BYTE *pCurrentImage;

    // Check we have an image to copy

    if (pMediaSample == NULL || pSourceRect == NULL ||
            pVideoInfo == NULL || pVideoImage == NULL ||
            pBufferSize == NULL) {

        return E_UNEXPECTED;
    }

    // Is the data format compatible

    if (pVideoInfo->bmiHeader.biCompression != BI_RGB) {
        if (pVideoInfo->bmiHeader.biCompression != BI_BITFIELDS) {
            return E_INVALIDARG;
        }
    }

    ASSERT(IsRectEmpty(pSourceRect) == FALSE);

    BITMAPINFOHEADER bih;
    bih.biWidth = WIDTH(pSourceRect);
    bih.biHeight = HEIGHT(pSourceRect);
    bih.biBitCount = pVideoInfo->bmiHeader.biBitCount;
    LONG Size = GetBitmapFormatSize(HEADER(pVideoInfo)) - SIZE_PREHEADER;
    LONG Total = Size + DIBSIZE(bih);

    // Make sure we have a large enough buffer

    if (*pBufferSize < Total) {
        return E_OUTOFMEMORY;
    }

    // Copy the BITMAPINFO

    CopyMemory((PVOID)pVideoImage, (PVOID)&pVideoInfo->bmiHeader, Size);
    ((BITMAPINFOHEADER *)pVideoImage)->biWidth = WIDTH(pSourceRect);
    ((BITMAPINFOHEADER *)pVideoImage)->biHeight = HEIGHT(pSourceRect);
    ((BITMAPINFOHEADER *)pVideoImage)->biSizeImage = DIBSIZE(bih);
    BYTE *pImageData = pVideoImage + Size;

    // Get the pointer to it's image data

    HRESULT hr = pMediaSample->GetPointer(&pCurrentImage);
    if (FAILED(hr)) {
        return hr;
    }

    // Now we are ready to start copying the source scan lines

    LONG ScanLine = (pVideoInfo->bmiHeader.biBitCount / 8) * WIDTH(pSourceRect);
    LONG LinesToSkip = pVideoInfo->bmiHeader.biHeight;
    LinesToSkip -= pSourceRect->top + HEIGHT(pSourceRect);
    pCurrentImage += LinesToSkip * DIBWIDTHBYTES(pVideoInfo->bmiHeader);
    pCurrentImage += pSourceRect->left * (pVideoInfo->bmiHeader.biBitCount / 8);

    // Even money on this GP faulting sometime...

    for (LONG Line = 0;Line < HEIGHT(pSourceRect);Line++) {
        CopyMemory((PVOID)pImageData, (PVOID)pCurrentImage, ScanLine);
        pImageData += DIBWIDTHBYTES(*(BITMAPINFOHEADER *)pVideoImage);
        pCurrentImage += DIBWIDTHBYTES(pVideoInfo->bmiHeader);
    }
    return NOERROR;
}

// Called when we change media types either during connection or dynamically
// We inform the filter graph and therefore the application that the video
// size may have changed, we don't bother looking to see if it really has as
// we leave that to the application - the dimensions are the event parameters

HRESULT CBaseControlVideo::OnVideoSizeChange()
{
    // Get the video format from the derived class

    VIDEOINFOHEADER *pVideoInfo = GetVideoFormat();
    if (pVideoInfo == NULL)
    return E_OUTOFMEMORY;
    WORD Width = (WORD) pVideoInfo->bmiHeader.biWidth;
    WORD Height = (WORD) pVideoInfo->bmiHeader.biHeight;

    return m_pFilter->NotifyEvent(EC_VIDEO_SIZE_CHANGED,
                                  MAKELPARAM(Width,Height),
                                  MAKEWPARAM(0,0));
}

// Set the video source rectangle. We must check the source rectangle against
// the actual video dimensions otherwise when we come to draw the pictures we
// get access violations as GDI tries to touch data outside of the image data
// Although we store the rectangle in left, top, right and bottom coordinates
// instead of left, top, width and height as OLE uses we do take into account
// that the rectangle is used up to, but not including, the right column and
// bottom row of pixels, see the Win32 documentation on RECT for more details

HRESULT CBaseControlVideo::CheckSourceRect(RECT *pSourceRect)
{
    CheckPointer(pSourceRect,E_POINTER);
    LONG Width,Height;
    GetVideoSize(&Width,&Height);

    // Check the coordinates are greater than zero
    // and that the rectangle is valid (left<right, top<bottom)

    if ((pSourceRect->left >= pSourceRect->right) ||
       (pSourceRect->left < 0) ||
       (pSourceRect->top >= pSourceRect->bottom) ||
       (pSourceRect->top < 0)) {

        return E_INVALIDARG;
    }

    // Check the coordinates are less than the extents

    if ((pSourceRect->right > Width) ||
        (pSourceRect->bottom > Height)) {

        return E_INVALIDARG;
    }
    return NOERROR;
}

// Check the target rectangle has some valid coordinates, which amounts to
// little more than checking the destination rectangle isn't empty. Derived
// classes may call this when they have their SetTargetRect method called to
// check the rectangle validity, we do not update the rectangles passed in
// Although we store the rectangle in left, top, right and bottom coordinates
// instead of left, top, width and height as OLE uses we do take into account
// that the rectangle is used up to, but not including, the right column and
// bottom row of pixels, see the Win32 documentation on RECT for more details

HRESULT CBaseControlVideo::CheckTargetRect(RECT *pTargetRect)
{
    // Check the pointer is valid

    if (pTargetRect == NULL) {
        return E_POINTER;
    }

    // These overflow the WIDTH and HEIGHT checks

    if (pTargetRect->left > pTargetRect->right ||
            pTargetRect->top > pTargetRect->bottom) {
                return E_INVALIDARG;
    }

    // Check the rectangle has valid coordinates

    if (WIDTH(pTargetRect) <= 0 || HEIGHT(pTargetRect) <= 0) {
        return E_INVALIDARG;
    }

    ASSERT(IsRectEmpty(pTargetRect) == FALSE);
    return NOERROR;
}


WINCTRL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Video control interface base classes, December 1995

#ifndef __WINCTRL__
#define __WINCTRL__

#define ABSOL(x) (x < 0 ? -x : x)
#define NEGAT(x) (x > 0 ? -x : x)

//  Helper
BOOL WINAPI PossiblyEatMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

class CBaseControlWindow : public CBaseVideoWindow, public CBaseWindow
{
protected:

    CBaseFilter *m_pFilter;            // Pointer to owning media filter
    CBasePin *m_pPin;                  // Controls media types for connection
    CCritSec *m_pInterfaceLock;        // Externally defined critical section
    COLORREF m_BorderColour;           // Current window border colour
    BOOL m_bAutoShow;                  // What happens when the state changes
    HWND m_hwndOwner;                  // Owner window that we optionally have
    HWND m_hwndDrain;                  // HWND to post any messages received
    BOOL m_bCursorHidden;              // Should we hide the window cursor

public:

    // Internal methods for other objects to get information out

    HRESULT DoSetWindowStyle(long Style,long WindowLong);
    HRESULT DoGetWindowStyle(long *pStyle,long WindowLong);
    BOOL IsAutoShowEnabled() { return m_bAutoShow; };
    COLORREF GetBorderColour() { return m_BorderColour; };
    HWND GetOwnerWindow() { return m_hwndOwner; };
    BOOL IsCursorHidden() { return m_bCursorHidden; };

    inline BOOL PossiblyEatMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        return ::PossiblyEatMessage(m_hwndDrain, uMsg, wParam, lParam);
    }

    // Derived classes must call this to set the pin the filter is using
    // We don't have the pin passed in to the constructor (as we do with
    // the CBaseFilter object) because filters typically create the
    // pins dynamically when requested in CBaseFilter::GetPin. This can
    // not be called from our constructor because is is a virtual method

    void SetControlWindowPin(CBasePin *pPin) {
        m_pPin = pPin;
    }

public:

    CBaseControlWindow(CBaseFilter *pFilter,   // Owning media filter
                       CCritSec *pInterfaceLock,    // Locking object
                       TCHAR *pName,                // Object description
                       LPUNKNOWN pUnk,              // Normal COM ownership
                       HRESULT *phr);               // OLE return code

    // These are the properties we support

    STDMETHODIMP put_Caption(BSTR strCaption);
    STDMETHODIMP get_Caption(BSTR *pstrCaption);
    STDMETHODIMP put_AutoShow(long AutoShow);
    STDMETHODIMP get_AutoShow(long *AutoShow);
    STDMETHODIMP put_WindowStyle(long WindowStyle);
    STDMETHODIMP get_WindowStyle(long *pWindowStyle);
    STDMETHODIMP put_WindowStyleEx(long WindowStyleEx);
    STDMETHODIMP get_WindowStyleEx(long *pWindowStyleEx);
    STDMETHODIMP put_WindowState(long WindowState);
    STDMETHODIMP get_WindowState(long *pWindowState);
    STDMETHODIMP put_BackgroundPalette(long BackgroundPalette);
    STDMETHODIMP get_BackgroundPalette(long *pBackgroundPalette);
    STDMETHODIMP put_Visible(long Visible);
    STDMETHODIMP get_Visible(long *pVisible);
    STDMETHODIMP put_Left(long Left);
    STDMETHODIMP get_Left(long *pLeft);
    STDMETHODIMP put_Width(long Width);
    STDMETHODIMP get_Width(long *pWidth);
    STDMETHODIMP put_Top(long Top);
    STDMETHODIMP get_Top(long *pTop);
    STDMETHODIMP put_Height(long Height);
    STDMETHODIMP get_Height(long *pHeight);
    STDMETHODIMP put_Owner(OAHWND Owner);
    STDMETHODIMP get_Owner(OAHWND *Owner);
    STDMETHODIMP put_MessageDrain(OAHWND Drain);
    STDMETHODIMP get_MessageDrain(OAHWND *Drain);
    STDMETHODIMP get_BorderColor(long *Color);
    STDMETHODIMP put_BorderColor(long Color);
    STDMETHODIMP get_FullScreenMode(long *FullScreenMode);
    STDMETHODIMP put_FullScreenMode(long FullScreenMode);

    // And these are the methods

    STDMETHODIMP SetWindowForeground(long Focus);
    STDMETHODIMP NotifyOwnerMessage(long hwnd,long uMsg,long wParam,long lParam);
    STDMETHODIMP GetMinIdealImageSize(long *pWidth,long *pHeight);
    STDMETHODIMP GetMaxIdealImageSize(long *pWidth,long *pHeight);
    STDMETHODIMP SetWindowPosition(long Left,long Top,long Width,long Height);
    STDMETHODIMP GetWindowPosition(long *pLeft,long *pTop,long *pWidth,long *pHeight);
    STDMETHODIMP GetRestorePosition(long *pLeft,long *pTop,long *pWidth,long *pHeight);
STDMETHODIMP HideCursor(long HideCursor);
    STDMETHODIMP IsCursorHidden(long *CursorHidden);
};

// This class implements the IBasicVideo interface

class CBaseControlVideo : public CBaseBasicVideo
{
protected:

    CBaseFilter *m_pFilter;   // Pointer to owning media filter
    CBasePin *m_pPin;                   // Controls media types for connection
    CCritSec *m_pInterfaceLock;         // Externally defined critical section

public:

    // Derived classes must provide these for the implementation

    virtual HRESULT IsDefaultTargetRect() PURE;
    virtual HRESULT SetDefaultTargetRect() PURE;
    virtual HRESULT SetTargetRect(RECT *pTargetRect) PURE;
    virtual HRESULT GetTargetRect(RECT *pTargetRect) PURE;
    virtual HRESULT IsDefaultSourceRect() PURE;
    virtual HRESULT SetDefaultSourceRect() PURE;
    virtual HRESULT SetSourceRect(RECT *pSourceRect) PURE;
    virtual HRESULT GetSourceRect(RECT *pSourceRect) PURE;
    virtual HRESULT GetStaticImage(long *pBufferSize,long *pDIBImage) PURE;

    // Derived classes must override this to return a VIDEOINFO representing
    // the video format. We cannot call IPin ConnectionMediaType to get this
    // format because various filters dynamically change the type when using
    // DirectDraw such that the format shows the position of the logical
    // bitmap in a frame buffer surface, so the size might be returned as
    // 1024x768 pixels instead of 320x240 which is the real video dimensions

    virtual VIDEOINFOHEADER *GetVideoFormat() PURE;

    // Helper functions for creating memory renderings of a DIB image

    HRESULT GetImageSize(VIDEOINFOHEADER *pVideoInfo,
                         LONG *pBufferSize,
                         RECT *pSourceRect);

    HRESULT CopyImage(IMediaSample *pMediaSample,
                      VIDEOINFOHEADER *pVideoInfo,
                      LONG *pBufferSize,
                      BYTE *pVideoImage,
                      RECT *pSourceRect);

    // Override this if you want notifying when the rectangles change
    virtual HRESULT OnUpdateRectangles() { return NOERROR; };
    virtual HRESULT OnVideoSizeChange();

    // Derived classes must call this to set the pin the filter is using
    // We don't have the pin passed in to the constructor (as we do with
    // the CBaseFilter object) because filters typically create the
    // pins dynamically when requested in CBaseFilter::GetPin. This can
    // not be called from our constructor because is is a virtual method

    void SetControlVideoPin(CBasePin *pPin) {
        m_pPin = pPin;
    }

    // Helper methods for checking rectangles
    virtual HRESULT CheckSourceRect(RECT *pSourceRect);
    virtual HRESULT CheckTargetRect(RECT *pTargetRect);

public:

    CBaseControlVideo(CBaseFilter *pFilter,    // Owning media filter
                      CCritSec *pInterfaceLock,     // Serialise interface
                      TCHAR *pName,                 // Object description
                      LPUNKNOWN pUnk,               // Normal COM ownership
                      HRESULT *phr);                // OLE return code

    // These are the properties we support

    STDMETHODIMP get_AvgTimePerFrame(REFTIME *pAvgTimePerFrame);
    STDMETHODIMP get_BitRate(long *pBitRate);
    STDMETHODIMP get_BitErrorRate(long *pBitErrorRate);
    STDMETHODIMP get_VideoWidth(long *pVideoWidth);
    STDMETHODIMP get_VideoHeight(long *pVideoHeight);
    STDMETHODIMP put_SourceLeft(long SourceLeft);
    STDMETHODIMP get_SourceLeft(long *pSourceLeft);
    STDMETHODIMP put_SourceWidth(long SourceWidth);
    STDMETHODIMP get_SourceWidth(long *pSourceWidth);
    STDMETHODIMP put_SourceTop(long SourceTop);
    STDMETHODIMP get_SourceTop(long *pSourceTop);
    STDMETHODIMP put_SourceHeight(long SourceHeight);
    STDMETHODIMP get_SourceHeight(long *pSourceHeight);
    STDMETHODIMP put_DestinationLeft(long DestinationLeft);
    STDMETHODIMP get_DestinationLeft(long *pDestinationLeft);
    STDMETHODIMP put_DestinationWidth(long DestinationWidth);
    STDMETHODIMP get_DestinationWidth(long *pDestinationWidth);
    STDMETHODIMP put_DestinationTop(long DestinationTop);
    STDMETHODIMP get_DestinationTop(long *pDestinationTop);
    STDMETHODIMP put_DestinationHeight(long DestinationHeight);
    STDMETHODIMP get_DestinationHeight(long *pDestinationHeight);

    // And these are the methods

    STDMETHODIMP GetVideoSize(long *pWidth,long *pHeight);
    STDMETHODIMP SetSourcePosition(long Left,long Top,long Width,long Height);
    STDMETHODIMP GetSourcePosition(long *pLeft,long *pTop,long *pWidth,long *pHeight);
    STDMETHODIMP GetVideoPaletteEntries(long StartIndex,long Entries,long *pRetrieved,long *pPalette);
    STDMETHODIMP SetDefaultSourcePosition();
    STDMETHODIMP IsUsingDefaultSource();
    STDMETHODIMP SetDestinationPosition(long Left,long Top,long Width,long Height);
    STDMETHODIMP GetDestinationPosition(long *pLeft,long *pTop,long *pWidth,long *pHeight);
    STDMETHODIMP SetDefaultDestinationPosition();
    STDMETHODIMP IsUsingDefaultDestination();
    STDMETHODIMP GetCurrentImage(long *pBufferSize,long *pVideoImage);
};

#endif // __WINCTRL__


WINUTIL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Generic window handler base class, December 1995

#include <streams.h>
#include <limits.h>

static UINT MsgDestroy;

// Constructor

CBaseWindow::CBaseWindow(BOOL bDoGetDC) :
    m_hInstance(g_hInst),
    m_hwnd(NULL),
    m_hdc(NULL),
    m_bActivated(FALSE),
    m_pClassName(NULL),
    m_ClassStyles(0),
    m_WindowStyles(0),
    m_WindowStylesEx(0),
    m_ShowStageMessage(0),
    m_ShowStageTop(0),
    m_MemoryDC(NULL),
    m_hPalette(NULL),
    m_bBackground(FALSE),
#ifdef DEBUG
    m_bRealizing(FALSE),
#endif
    m_bNoRealize(FALSE)
{
    m_bDoGetDC = bDoGetDC;
}

// Prepare a window by spinning off a worker thread to do the creation and
// also poll the message input queue. We leave this to be called by derived
// classes because they might want to override methods like MessageLoop and
// InitialiseWindow, if we do this during construction they'll ALWAYS call
// this base class methods. We make the worker thread create the window so
// it owns it rather than the filter graph thread which is constructing us

HRESULT CBaseWindow::PrepareWindow()
{
    if (m_hwnd) return NOERROR;
    ASSERT(m_hwnd == NULL);
    ASSERT(m_hdc == NULL);

    // Get the derived object's window and class styles

    m_pClassName = GetClassWindowStyles(&m_ClassStyles,
                                        &m_WindowStyles,
                                        &m_WindowStylesEx);
    if (m_pClassName == NULL) {
        return E_FAIL;
    }

    // Register our special private messages
    m_ShowStageMessage = RegisterWindowMessage(SHOWSTAGE);
    m_ShowStageTop = RegisterWindowMessage(SHOWSTAGETOP);
    m_RealizePalette = RegisterWindowMessage(REALIZEPALETTE);

    return DoCreateWindow();
}

// Destructor just a placeholder so that we know it becomes virtual
// Derived classes MUST call DoneWithWindow in their destructors so
// that no messages arrive after the derived class constructor ends

#ifdef DEBUG
CBaseWindow::~CBaseWindow()
{
    ASSERT(m_hwnd == NULL);
    ASSERT(m_hdc == NULL);
}
#endif

// We use the sync worker event to have the window destroyed. All we do is
// signal the event and wait on the window thread handle. Trying to send it
// messages causes too many problems, furthermore to be on the safe side we
// just wait on the thread handle while it returns WAIT_TIMEOUT or there is
// a sent message to process on this thread. If the constructor failed to
// create the thread in the first place then the loop will get terminated

HRESULT CBaseWindow::DoneWithWindow()
{
    if (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()) {
        MsgDestroy = RegisterWindowMessage(TEXT("AM_DESTROY"));
        SendMessage(m_hwnd, MsgDestroy, 0, 0);
        return NOERROR;
    }
    const HWND hwnd = m_hwnd;
    if (hwnd == NULL) {
        return NOERROR;
    }

    InactivateWindow();
    NOTE("Inactivated");

    // Reset the window styles before destruction

    SetWindowLong(hwnd,GWL_STYLE,m_WindowStyles);
    ASSERT(GetParent(hwnd) == NULL);
    NOTE1("Reset window styles %d",m_WindowStyles);

    //  UnintialiseWindow sets m_hwnd to NULL so save a copy
    UninitialiseWindow();
    DbgLog((LOG_TRACE, 2, TEXT("Destroying 0x%8.8X"), hwnd));
    if (!DestroyWindow(hwnd)) {
        DbgLog((LOG_TRACE, 0, TEXT("DestroyWindow %8.8X failed code %d"),
                hwnd, GetLastError()));
        DbgBreak("");
    }

    // Reset our state so we can be prepared again

    m_pClassName = NULL;
    m_ClassStyles = 0;
    m_WindowStyles = 0;
    m_WindowStylesEx = 0;
    m_ShowStageMessage = 0;
    m_ShowStageTop = 0;

    return NOERROR;
}

// Called at the end to put the window in an inactive state. The pending list
// will always have been cleared by this time so event if the worker thread
// gets has been signaled and gets in to render something it will find both
// the state has been changed and that there are no available sample images
// Since we wait on the window thread to complete we don't lock the object

HRESULT CBaseWindow::InactivateWindow()
{
    // Has the window been activated
    if (m_bActivated == FALSE) {
        return S_FALSE;
    }

    m_bActivated = FALSE;
    ShowWindow(m_hwnd,SW_HIDE);
    return NOERROR;
}

// This displays a normal window. We ask the base window class for default
// sizes which unless overriden will return DEFWIDTH and DEFHEIGHT. We go
// through a couple of extra hoops to get the client area the right size
// as the object specifies which accounts for the AdjustWindowRectEx calls
// We also DWORD align the left and top coordinates of the window here to
// maximise the chance of being able to use DCI/DirectDraw primary surface

HRESULT CBaseWindow::ActivateWindow()
{
    // Has the window been sized and positioned already

    if (m_bActivated == TRUE || GetParent(m_hwnd) != NULL) {

        SetWindowPos(m_hwnd,            // Our window handle
                     HWND_TOP,          // Put it at the top
                     0, 0, 0, 0,        // Leave in current position
                     SWP_NOMOVE |       // Don't change it's place
                     SWP_NOSIZE);       // Change Z-order only

        return S_FALSE;
    }

    // Calculate the desired client rectangle

    RECT WindowRect, ClientRect = GetDefaultRect();
    GetWindowRect(m_hwnd,&WindowRect);
    AdjustWindowRectEx(&ClientRect,GetWindowLong(m_hwnd,GWL_STYLE),
                       FALSE,GetWindowLong(m_hwnd,GWL_EXSTYLE));

    // Align left and top edges on DWORD boundaries

    UINT WindowFlags = (SWP_NOACTIVATE | SWP_FRAMECHANGED);
    WindowRect.left -= (WindowRect.left & 3);
    WindowRect.top -= (WindowRect.top & 3);

    SetWindowPos(m_hwnd,                // Window handle
                 HWND_TOP,              // Put it at the top
                 WindowRect.left,       // Align left edge
                 WindowRect.top,        // And also top place
                 WIDTH(&ClientRect),    // Horizontal size
                 HEIGHT(&ClientRect),   // Vertical size
                 WindowFlags);          // Don't show window

    m_bActivated = TRUE;
    return NOERROR;
}

// This can be used to DWORD align the window for maximum performance

HRESULT CBaseWindow::PerformanceAlignWindow()
{
    RECT ClientRect,WindowRect;
    GetWindowRect(m_hwnd,&WindowRect);
    ASSERT(m_bActivated == TRUE);

    // Don't do this if we're owned

    if (GetParent(m_hwnd)) {
        return NOERROR;
    }

    // Align left and top edges on DWORD boundaries

    GetClientRect(m_hwnd, &ClientRect);
    MapWindowPoints(m_hwnd, HWND_DESKTOP, (LPPOINT) &ClientRect, 2);
    WindowRect.left -= (ClientRect.left & 3);
    WindowRect.top  -= (ClientRect.top  & 3);
    UINT WindowFlags = (SWP_NOACTIVATE | SWP_NOSIZE);

    SetWindowPos(m_hwnd,                // Window handle
                 HWND_TOP,              // Put it at the top
                 WindowRect.left,       // Align left edge
                 WindowRect.top,        // And also top place
                 (int) 0,(int) 0,       // Ignore these sizes
                 WindowFlags);          // Don't show window

    return NOERROR;
}

// Install a palette into the base window - we may be called by a different
// thread to the one that owns the window. We have to be careful how we do
// the palette realisation as we could be a different thread to the window
// which would cause an inter thread send message. Therefore we realise the
// palette by sending it a special message but without the window locked

HRESULT CBaseWindow::SetPalette(HPALETTE hPalette)
{
    // We must own the window lock during the change
    {
        CAutoLock cWindowLock(&m_WindowLock);
        ASSERT(hPalette);
        m_hPalette = hPalette;
    }
    return SetPalette();
}

HRESULT CBaseWindow::SetPalette()
{
    if (!m_bNoRealize) {
        SendMessage(m_hwnd, m_RealizePalette, 0, 0);
        // Make sure the device's palette is flushed
        return (GdiFlush() == FALSE ? S_FALSE : S_OK);
    } else {
        // Just select the palette
        ASSERT(m_hdc);
        ASSERT(m_MemoryDC);
        SelectPalette(m_hdc,m_hPalette,m_bBackground);
        SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);
        return S_OK;
    }
}

// Realise our palettes in the window and device contexts

HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground)
{
    //  If we grab a critical section here we can deadlock
    //  with the window thread because one of the side effects
    //  of RealizePalette is to send a WM_PALETTECHANGED message
    //  to every window in the system.  In our handling
    //  of WM_PALETTECHANGED we used to grab this CS too.
    //  The really bad case is when our renderer calls DoRealisePalette()
    //  while we're in the middle of processing a palette change
    //  for another window.
    //  So don't hold the critical section while actually realising
    //  the palette.  In any case USER is meant to manage palette
    //  handling - we shouldn't have to serialize everything as well
    if (m_hPalette == NULL) {

        return NOERROR;
    }

    // Realize the palette on the window thread
    ASSERT(m_hdc);
    ASSERT(m_MemoryDC);
    SelectPalette(m_hdc,m_hPalette,m_bBackground || bForceBackground);
    EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR);
    SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);
    EXECUTE_ASSERT(RealizePalette(m_MemoryDC) != GDI_ERROR);

    return (GdiFlush() == FALSE ? S_FALSE : S_OK);
}

// This is the global window procedure

LRESULT CALLBACK WndProc(HWND hwnd,         // Window handle
                         UINT uMsg,         // Message ID
                         WPARAM wParam,     // First parameter
                         LPARAM lParam)     // Other parameter
{

    // Get the window long that holds our window object pointer
    // If it is NULL then we are initialising the window in which
    // case the object pointer has been passed in the window creation
    // structure.  IF we get any messages before WM_NCCREATE we will
    // pass them to DefWindowProc.

    CBaseWindow *pBaseWindow = (CBaseWindow *)GetWindowLong(hwnd,0);
    if (pBaseWindow == NULL) {

// Get the structure pointer from the create struct.
// We can only do this for WM_NCCREATE which should be one of
// the first messages we receive.  Anything before this will
// have to be passed to DefWindowProc (i.e. WM_GETMINMAXINFO)

// If the message is WM_NCCREATE we set our pBaseWindow pointer
// and will then place it in the window structure

if ((uMsg != WM_NCCREATE)
    || (NULL == (pBaseWindow = *(CBaseWindow**) ((LPCREATESTRUCT)lParam)->lpCreateParams)))
{
    return(DefWindowProc(hwnd, uMsg, wParam, lParam));
}

        // Set the window LONG to be the object who created us
#ifdef DEBUG
SetLastError(0);  // because of the way SetWindowLong works
#endif
        BOOL rc = SetWindowLong(hwnd, (DWORD) 0, (LONG) pBaseWindow);
#ifdef DEBUG
if (0 == rc) {
    // SetWindowLong MIGHT have failed.  (Read the docs which admit
    // that it is awkward to work out if you have had an error.)
    LONG lasterror = GetLastError();
    ASSERT(0 == lasterror);
    // If this is not the case we have not set the pBaseWindow pointer
    // into the window structure and we will blow up.
}
#endif

    }
    // See if this is the packet of death
    if (uMsg == MsgDestroy && uMsg != 0) {
        pBaseWindow->DoneWithWindow();
        return 0;
    }
    return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}

// When the window size changes we adjust our member variables that
// contain the dimensions of the client rectangle for our window so
// that we come to render an image we will know whether to stretch

BOOL CBaseWindow::OnSize(LONG Width, LONG Height)
{
    m_Width = Width;
    m_Height = Height;
    return TRUE;
}

// This function handles the WM_CLOSE message

BOOL CBaseWindow::OnClose()
{
    ShowWindow(m_hwnd,SW_HIDE);
    return TRUE;
}

// This is called by the worker window thread when it receives a terminate
// message from the window object destructor to delete all the resources we
// allocated during initialisation. By the time the worker thread exits all
// processing will have been completed as the source filter disconnection
// flushes the image pending sample, therefore the GdiFlush should succeed

HRESULT CBaseWindow::UninitialiseWindow()
{
    // Have we already cleaned up

    if (m_hwnd == NULL) {
        ASSERT(m_hdc == NULL);
        ASSERT(m_MemoryDC == NULL);
        return NOERROR;
    }

    // Release the window resources

    EXECUTE_ASSERT(GdiFlush());

    if (m_hdc)
    {
        EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc));
        m_hdc = NULL;
    }

    if (m_MemoryDC)
    {
        EXECUTE_ASSERT(DeleteDC(m_MemoryDC));
        m_MemoryDC = NULL;
    }

    // Reset the window variables
    m_hwnd = NULL;

    return NOERROR;
}

// This is called by the worker window thread after it has created the main
// window and it wants to initialise the rest of the owner objects window
// variables such as the device contexts. We execute this function with the
// critical section still locked. Nothing in this function must generate any
// SendMessage calls to the window because this is executing on the window
// thread so the message will never be processed and we will deadlock

HRESULT CBaseWindow::InitialiseWindow(HWND hwnd)
{
    // Initialise the window variables

    ASSERT(IsWindow(hwnd));
    m_hwnd = hwnd;

    if (m_bDoGetDC)
    {
        EXECUTE_ASSERT(m_hdc = GetDC(hwnd));
        EXECUTE_ASSERT(m_MemoryDC = CreateCompatibleDC(m_hdc));

        EXECUTE_ASSERT(SetStretchBltMode(m_hdc,COLORONCOLOR));
        EXECUTE_ASSERT(SetStretchBltMode(m_MemoryDC,COLORONCOLOR));
    }

    return NOERROR;
}

HRESULT CBaseWindow::DoCreateWindow()
{
    WNDCLASS wndclass;                  // Used to register classes
    BOOL bRegistered;                   // Is this class registered
    HWND hwnd;                          // Handle to our window

    bRegistered = GetClassInfo(m_hInstance,   // Module instance
                               m_pClassName,  // Window class
                               &wndclass);                 // Info structure

    // if the window is to be used for drawing puposes and we are getting a DC
    // for the entire lifetime of the window then changes the class style to do
    // say so. If we don't set this flag then the DC comes from the cache and is
    // really bad.
    if (m_bDoGetDC)
    {
        m_ClassStyles |= CS_OWNDC;
    }

    if (bRegistered == FALSE) {

        // Register the renderer window class

        wndclass.lpszClassName = m_pClassName;
        wndclass.style         = m_ClassStyles;
        wndclass.lpfnWndProc   = (WNDPROC) WndProc;
        wndclass.cbClsExtra    = 0;
        wndclass.cbWndExtra    = sizeof(CBaseWindow *);
        wndclass.hInstance     = m_hInstance;
        wndclass.hIcon         = NULL;
        wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH) NULL;
        wndclass.lpszMenuName  = NULL;

        RegisterClass(&wndclass);
    }

    // Create the frame window.  Pass the pBaseWindow information in the
    // CreateStruct which allows our message handling loop to get hold of
    // the pBaseWindow pointer.

    CBaseWindow *pBaseWindow = this;           // The owner window object
    hwnd = CreateWindowEx(m_WindowStylesEx,  // Extended styles
                          m_pClassName,      // Registered name
                          TEXT("ActiveMovie Window"),     // Window title
                          m_WindowStyles,    // Window styles
                          CW_USEDEFAULT,                  // Start x position
                          CW_USEDEFAULT,                  // Start y position
                          DEFWIDTH,                       // Window width
                          DEFHEIGHT,                      // Window height
                          NULL,                           // Parent handle
                          NULL,                           // Menu handle
                          m_hInstance,       // Instance handle
                          &pBaseWindow);                  // Creation data

    // If we failed signal an error to the object constructor (based on the
    // last Win32 error on this thread) then signal the constructor thread
    // to continue, release the mutex to let others have a go and exit

    if (hwnd == NULL) {
        DWORD Error = GetLastError();
        return HRESULT_FROM_WIN32(Error);
    }

    // Check the window LONG is the object who created us
    ASSERT(GetWindowLong(hwnd, 0) == (LONG)this);

    // Initialise the window and then signal the constructor so that it can
    // continue and then finally unlock the object's critical section. The
    // window class is left registered even after we terminate the thread
    // as we don't know when the last window has been closed. So we allow
    // the operating system to free the class resources as appropriate

    InitialiseWindow(hwnd);

    DbgLog((LOG_TRACE, 2, TEXT("Created window class (%s) HWND(%8.8X)"),
            m_pClassName, hwnd));

    return S_OK;
}

// The base class provides some default handling and calls DefWindowProc

LRESULT CBaseWindow::OnReceiveMessage(HWND hwnd,         // Window handle
                                      UINT uMsg,         // Message ID
                                      WPARAM wParam,     // First parameter
                                      LPARAM lParam)     // Other parameter
{
    ASSERT(IsWindow(hwnd));

    if (PossiblyEatMessage(uMsg, wParam, lParam))
return 0;

    // This is sent by the IVideoWindow SetWindowForeground method. If the
    // window is invisible we will show it and make it topmost without the
    // foreground focus. If the window is visible it will also be made the
    // topmost window without the foreground focus. If wParam is TRUE then
    // for both cases the window will be forced into the foreground focus

    if (uMsg == m_ShowStageMessage) {

        BOOL bVisible = IsWindowVisible(hwnd);
        SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW |
                     (bVisible ? SWP_NOACTIVATE : 0));

        // Should we bring the window to the foreground
        if (wParam == TRUE) {
            SetForegroundWindow(hwnd);
        }
        return (LRESULT) 1;
    }

    // When we go fullscreen we have to add the WS_EX_TOPMOST style to the
    // video window so that it comes out above any task bar (this is more
    // relevant to WindowsNT than Windows95). However the SetWindowPos call
    // must be on the same thread as that which created the window. The
    // wParam parameter can be TRUE or FALSE to set and reset the topmost

    if (uMsg == m_ShowStageTop) {
        HWND HwndTop = (wParam == TRUE ? HWND_TOPMOST : HWND_NOTOPMOST);
        BOOL bVisible = IsWindowVisible(hwnd);
        SetWindowPos(hwnd, HwndTop, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE |
                     (wParam == TRUE ? SWP_SHOWWINDOW : 0) |
                     (bVisible ? SWP_NOACTIVATE : 0));
        return (LRESULT) 1;
    }

    // New palette stuff
    if (uMsg == m_RealizePalette) {
        ASSERT(m_hwnd == hwnd);
        return OnPaletteChange(m_hwnd,WM_QUERYNEWPALETTE);
    }

    switch (uMsg) {

        // Repaint the window if the system colours change

        case WM_SYSCOLORCHANGE:

            InvalidateRect(hwnd,NULL,FALSE);
            return (LRESULT) 1;

        // Somebody has changed the palette
        case WM_PALETTECHANGED:

            OnPaletteChange((HWND)wParam,uMsg);
            return (LRESULT) 0;

        // We are about to receive the keyboard focus so we ask GDI to realise
        // our logical palette again and hopefully it will be fully installed
        // without any mapping having to be done during any picture rendering

case WM_QUERYNEWPALETTE:
    ASSERT(m_hwnd == hwnd);
            return OnPaletteChange(m_hwnd,uMsg);

        // Store the width and height as useful base class members

        case WM_SIZE:

    OnSize(LOWORD(lParam), HIWORD(lParam));
            return (LRESULT) 0;

        // Intercept the WM_CLOSE messages to hide the window

        case WM_CLOSE:

            OnClose();
            return (LRESULT) 0;
    }
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

// This handles the Windows palette change messages - if we do realise our
// palette then we return TRUE otherwise we return FALSE. If our window is
// foreground application then we should get first choice of colours in the
// system palette entries. We get best performance when our logical palette
// includes the standard VGA colours (at the beginning and end) otherwise
// GDI may have to map from our palette to the device palette while drawing

LRESULT CBaseWindow::OnPaletteChange(HWND hwnd,UINT Message)
{
    // First check we are not changing the palette during closedown

    if (m_hwnd == NULL || hwnd == NULL) {
        return (LRESULT) 0;
    }
    ASSERT(!m_bRealizing);

    // Should we realise our palette again

    if ((Message == WM_QUERYNEWPALETTE || hwnd != m_hwnd)) {
        //  It seems that even if we're invisible that we can get asked
        //  to realize our palette and this can cause really ugly side-effects
        //  Seems like there's another bug but this masks it a least for the
        //  shutting down case.
        if (!IsWindowVisible(m_hwnd)) {
            DbgLog((LOG_TRACE, 1, TEXT("Realizing when invisible!")));
            return (LRESULT) 0;
        }

        // Avoid recursion with multiple graphs in the same app
#ifdef DEBUG
        m_bRealizing = TRUE;
#endif
        DoRealisePalette(Message != WM_QUERYNEWPALETTE);
#ifdef DEBUG
        m_bRealizing = FALSE;
#endif

        // Should we redraw the window with the new palette
        if (Message == WM_PALETTECHANGED) {
            InvalidateRect(m_hwnd,NULL,FALSE);
        }
    }

    return (LRESULT) 1;
}

// Return the default window rectangle

RECT CBaseWindow::GetDefaultRect()
{
    RECT DefaultRect = {0,0,DEFWIDTH,DEFHEIGHT};
    ASSERT(m_hwnd);
    // ASSERT(m_hdc);
    return DefaultRect;
}

// Return the current window width

LONG CBaseWindow::GetWindowWidth()
{
    ASSERT(m_hwnd);
    // ASSERT(m_hdc);
    return m_Width;
}

// Return the current window height

LONG CBaseWindow::GetWindowHeight()
{
    ASSERT(m_hwnd);
    // ASSERT(m_hdc);
    return m_Height;
}

// Return the window handle

HWND CBaseWindow::GetWindowHWND()
{
    ASSERT(m_hwnd);
    // ASSERT(m_hdc);
    return m_hwnd;
}

// Return the window drawing device context

HDC CBaseWindow::GetWindowHDC()
{
    ASSERT(m_hwnd);
    ASSERT(m_hdc);
    return m_hdc;
}

// Return the offscreen window drawing device context

HDC CBaseWindow::GetMemoryHDC()
{
    ASSERT(m_hwnd);
    ASSERT(m_MemoryDC);
    return m_MemoryDC;
}

// This is available to clients who want to change the window visiblity. It's
// little more than an indirection to the Win32 ShowWindow although these is
// some benefit in going through here as this function may change sometime

HRESULT CBaseWindow::DoShowWindow(LONG ShowCmd)
{
    ShowWindow(m_hwnd,ShowCmd);
    return NOERROR;
}

// Generate a WM_PAINT message for the video window

void CBaseWindow::PaintWindow(BOOL bErase)
{
    InvalidateRect(m_hwnd,NULL,bErase);
}

// Allow an application to have us set the video window in the foreground. We
// have this because it is difficult for one thread to do do this to a window
// owned by another thread. Rather than expose the message we use to execute
// the inter thread send message we provide the interface function. All we do
// is to SendMessage to the video window renderer thread with a WM_SHOWSTAGE

void CBaseWindow::DoSetWindowForeground(BOOL bFocus)
{
    SendMessage(m_hwnd,m_ShowStageMessage,(WPARAM) bFocus,(LPARAM) 0);
}

// Constructor initialises the owning object pointer. Since we are a worker
// class for the main window object we have relatively few state variables to
// look after. We are given device context handles to use later on as well as
// the source and destination rectangles (but reset them here just in case)

CDrawImage::CDrawImage(CBaseWindow *pBaseWindow) :
    m_pBaseWindow(pBaseWindow),
    m_hdc(NULL),
    m_MemoryDC(NULL),
    m_bStretch(FALSE),
    m_pMediaType(NULL),
    m_bUsingImageAllocator(FALSE)
{
    ASSERT(pBaseWindow);
    ResetPaletteVersion();
    SetRectEmpty(&m_TargetRect);
    SetRectEmpty(&m_SourceRect);

    m_perfidRenderTime = MSR_REGISTER("Single Blt time");
}

// Overlay the image time stamps on the picture. Access to this method is
// serialised by the caller. We display the sample start and end times on
// top of the video using TextOut on the device context we are handed. If
// there isn't enough room in the window for the times we don't show them

void CDrawImage::DisplaySampleTimes(IMediaSample *pSample)
{
    TCHAR szTimes[TIMELENGTH];      // Time stamp strings
    ASSERT(pSample);                // Quick sanity check
    RECT ClientRect;                // Client window size
    SIZE Size;                      // Size of text output

    // Get the time stamps and window size

    pSample->GetTime((REFERENCE_TIME*)&m_StartSample, (REFERENCE_TIME*)&m_EndSample);
    HWND hwnd = m_pBaseWindow->GetWindowHWND();
    EXECUTE_ASSERT(GetClientRect(hwnd,&ClientRect));

    // Format the sample time stamps

    wsprintf(szTimes,TEXT("%08d : %08d"),
             m_StartSample.Millisecs(),
             m_EndSample.Millisecs());

    ASSERT(lstrlen(szTimes) < TIMELENGTH);

    // Put the times in the middle at the bottom of the window

    GetTextExtentPoint32(m_hdc,szTimes,lstrlen(szTimes),&Size);
    INT XPos = ((ClientRect.right - ClientRect.left) - Size.cx) / 2;
    INT YPos = ((ClientRect.bottom - ClientRect.top) - Size.cy) * 4 / 5;

    // Check the window is big enough to have sample times displayed

    if ((XPos > 0) && (YPos > 0)) {
        TextOut(m_hdc,XPos,YPos,szTimes,lstrlen(szTimes));
    }
}

// This is called when the drawing code sees that the image has a down level
// palette cookie. We simply call the SetDIBColorTable Windows API with the
// palette that is found after the BITMAPINFOHEADER - we return no errors

void CDrawImage::UpdateColourTable(HDC hdc,BITMAPINFOHEADER *pbmi)
{
    ASSERT(pbmi->biClrUsed);
    RGBQUAD *pColourTable = (RGBQUAD *)(pbmi+1);

    // Set the new palette in the device context

    UINT uiReturn = SetDIBColorTable(hdc,(UINT) 0,
                                     pbmi->biClrUsed,
                                     pColourTable);

    // Should always succeed but check in debug builds
    ASSERT(uiReturn == pbmi->biClrUsed);
}

// No source rectangle scaling is done by the base class

RECT CDrawImage::ScaleSourceRect(const RECT *pSource)
{
    ASSERT(pSource);
    return *pSource;
}

// This is called when the funky output pin uses our allocator. The samples we
// allocate are special because the memory is shared between us and GDI thus
// removing one copy when we ask for the image to be rendered. The source type
// information is in the main renderer m_mtIn field which is initialised when
// the media type is agreed in SetMediaType, the media type may be changed on
// the fly if, for example, the source filter needs to change the palette

void CDrawImage::FastRender(IMediaSample *pMediaSample)
{
    BITMAPINFOHEADER *pbmi;     // Image format data
    DIBDATA *pDibData;          // Stores DIB information
    BYTE *pImage;               // Pointer to image data
    HBITMAP hOldBitmap;         // Store the old bitmap
    CImageSample *pSample;      // Pointer to C++ object

    ASSERT(m_pMediaType);

    // From the untyped source format block get the VIDEOINFO and subsequently

// the BITMAPINFOHEADER structure. We can cast the IMediaSample interface
    // to a CImageSample object so we can retrieve it's DIBSECTION details

    pbmi = HEADER(m_pMediaType->Format());
    pSample = (CImageSample *) pMediaSample;
    pDibData = pSample->GetDIBData();
    hOldBitmap = (HBITMAP) SelectObject(m_MemoryDC,pDibData->hBitmap);

    // Get a pointer to the real image data

    HRESULT hr = pMediaSample->GetPointer(&pImage);
    if (FAILED(hr)) {
        return;
    }

    // Do we need to update the colour table, we increment our palette cookie
    // each time we get a dynamic format change. The sample palette cookie is
    // stored in the DIBDATA structure so we try to keep the fields in sync
    // By the time we get to draw the images the format change will be done
    // so all we do is ask the renderer for what it's palette version is

    if (pDibData->PaletteVersion < GetPaletteVersion()) {
        ASSERT(pbmi->biBitCount <= iPALETTE);
        UpdateColourTable(m_MemoryDC,pbmi);
        pDibData->PaletteVersion = GetPaletteVersion();
    }

    // This allows derived classes to change the source rectangle that we do
    // the drawing with. For example a renderer may ask a codec to stretch
    // the video from 320x240 to 640x480, in which case the source we see in
    // here will still be 320x240, although the source we want to draw with
    // should be scaled up to 640x480. The base class implementation of this
    // method does nothing but return the same rectangle as we are passed in

    RECT SourceRect = ScaleSourceRect(&m_SourceRect);

    // Is the window the same size as the video

    if (m_bStretch == FALSE) {

        // Put the image straight into the window

        BitBlt(
            (HDC) m_hdc,                            // Target device HDC
            m_TargetRect.left,                      // X sink position
            m_TargetRect.top,                       // Y sink position
            m_TargetRect.right - m_TargetRect.left, // Destination width
            m_TargetRect.bottom - m_TargetRect.top, // Destination height
            m_MemoryDC,                             // Source device context
            SourceRect.left,                        // X source position
            SourceRect.top,                         // Y source position
            SRCCOPY);                               // Simple copy

    } else {

        // Stretch the image when copying to the window

        StretchBlt(
            (HDC) m_hdc,                            // Target device HDC
            m_TargetRect.left,                      // X sink position
            m_TargetRect.top,                       // Y sink position
            m_TargetRect.right - m_TargetRect.left, // Destination width
            m_TargetRect.bottom - m_TargetRect.top, // Destination height
            m_MemoryDC,                             // Source device HDC
            SourceRect.left,                        // X source position
            SourceRect.top,                         // Y source position
            SourceRect.right - SourceRect.left,     // Source width
            SourceRect.bottom - SourceRect.top,     // Source height
            SRCCOPY);                               // Simple copy
    }

    // This displays the sample times over the top of the image. This used to
    // draw the times into the offscreen device context however that actually
    // writes the text into the image data buffer which may not be writable

    #ifdef DEBUG
    DisplaySampleTimes(pMediaSample);
    #endif

    // Put the old bitmap back into the device context so we don't leak
    SelectObject(m_MemoryDC,hOldBitmap);
}

// This is called when there is a sample ready to be drawn, unfortunately the
// output pin was being rotten and didn't choose our super excellent shared
// memory DIB allocator so we have to do this slow render using boring old GDI
// SetDIBitsToDevice and StretchDIBits. The down side of using these GDI
// functions is that the image data has to be copied across from our address
// space into theirs before going to the screen (although in reality the cost
// is small because all they do is to map the buffer into their address space)

void CDrawImage::SlowRender(IMediaSample *pMediaSample)
{
    // Get the BITMAPINFOHEADER for the connection

    ASSERT(m_pMediaType);
    BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format());
    BYTE *pImage;

    // Get the image data buffer

    HRESULT hr = pMediaSample->GetPointer(&pImage);
    if (FAILED(hr)) {
        return;
    }

    // This allows derived classes to change the source rectangle that we do
    // the drawing with. For example a renderer may ask a codec to stretch
    // the video from 320x240 to 640x480, in which case the source we see in
    // here will still be 320x240, although the source we want to draw with
    // should be scaled up to 640x480. The base class implementation of this
    // method does nothing but return the same rectangle as we are passed in

    RECT SourceRect = ScaleSourceRect(&m_SourceRect);

    LONG lAdjustedSourceTop = SourceRect.top;
    // if the origin of bitmap is bottom-left, adjust soruce_rect_top
    // to be the bottom-left corner instead of the top-left.
    if (pbmi->biHeight > 0) {
       lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom;
    }
    // Is the window the same size as the video

    if (m_bStretch == FALSE) {

        // Put the image straight into the window

        SetDIBitsToDevice(
            (HDC) m_hdc,                            // Target device HDC
            m_TargetRect.left,                      // X sink position
            m_TargetRect.top,                       // Y sink position
            m_TargetRect.right - m_TargetRect.left, // Destination width
            m_TargetRect.bottom - m_TargetRect.top, // Destination height
            SourceRect.left,                        // X source position
            lAdjustedSourceTop,                     // Adjusted Y source position
            (UINT) 0,                               // Start scan line
            pbmi->biHeight,                         // Scan lines present
            pImage,                                 // Image data
            (BITMAPINFO *) pbmi,                    // DIB header
            DIB_RGB_COLORS);                        // Type of palette

    } else {

        // Stretch the image when copying to the window

        StretchDIBits(
            (HDC) m_hdc,                            // Target device HDC
            m_TargetRect.left,                      // X sink position
            m_TargetRect.top,                       // Y sink position
            m_TargetRect.right - m_TargetRect.left, // Destination width
            m_TargetRect.bottom - m_TargetRect.top, // Destination height
            SourceRect.left,                        // X source position
            lAdjustedSourceTop,                     // Adjusted Y source position
            SourceRect.right - SourceRect.left,     // Source width
            SourceRect.bottom - SourceRect.top,     // Source height
            pImage,                                 // Image data
            (BITMAPINFO *) pbmi,                    // DIB header
            DIB_RGB_COLORS,                         // Type of palette
            SRCCOPY);                               // Simple image copy
    }

    // This shows the sample reference times over the top of the image which
    // looks a little flickery. I tried using GdiSetBatchLimit and GdiFlush to
    // control the screen updates but it doesn't quite work as expected and
    // only partially reduces the flicker. I also tried using a memory context
    // and combining the two in that before doing a final BitBlt operation to
    // the screen, unfortunately this has considerable performance penalties
    // and also means that this code is not executed when compiled retail

    #ifdef DEBUG
    DisplaySampleTimes(pMediaSample);
    #endif
}

// This is called with an IMediaSample interface on the image to be drawn. We
// decide on the drawing mechanism based on who's allocator we are using. We
// may be called when the window wants an image painted by WM_PAINT messages
// We can't realise the palette here because we have the renderer lock, any
// call to realise may cause an interthread send message to the window thread
// which may in turn be waiting to get the renderer lock before servicing it

BOOL CDrawImage::DrawImage(IMediaSample *pMediaSample)
{
    ASSERT(m_hdc);
    ASSERT(m_MemoryDC);
    NotifyStartDraw();

    // If the output pin used our allocator then the samples passed are in
    // fact CVideoSample objects that contain CreateDIBSection data that we
    // use to do faster image rendering, they may optionally also contain a
    // DirectDraw surface pointer in which case we do not do the drawing

    if (m_bUsingImageAllocator == FALSE) {
        SlowRender(pMediaSample);
        EXECUTE_ASSERT(GdiFlush());
        NotifyEndDraw();
        return TRUE;
    }

    // This is a DIBSECTION buffer

    FastRender(pMediaSample);
    EXECUTE_ASSERT(GdiFlush());
    NotifyEndDraw();
    return TRUE;
}

// This is called by the owning window object after it has created the window
// and it's drawing contexts. We are constructed with the base window we'll
// be drawing into so when given the notification we retrive the device HDCs
// to draw with. We cannot call these in our constructor as they are virtual

void CDrawImage::SetDrawContext()
{
    m_MemoryDC = m_pBaseWindow->GetMemoryHDC();
    m_hdc = m_pBaseWindow->GetWindowHDC();
}

// This is called to set the target rectangle in the video window, it will be
// called whenever a WM_SIZE message is retrieved from the message queue. We
// simply store the rectangle and use it later when we do the drawing calls

void CDrawImage::SetTargetRect(RECT *pTargetRect)
{
    ASSERT(pTargetRect);
    m_TargetRect = *pTargetRect;
    SetStretchMode();
}

// Return the current target rectangle

void CDrawImage::GetTargetRect(RECT *pTargetRect)
{
    ASSERT(pTargetRect);
    *pTargetRect = m_TargetRect;
}

// This is called when we want to change the section of the image to draw. We
// use this information in the drawing operation calls later on. We must also
// see if the source and destination rectangles have the same dimensions. If
// not we must stretch during the drawing rather than a direct pixel copy

void CDrawImage::SetSourceRect(RECT *pSourceRect)
{
    ASSERT(pSourceRect);
    m_SourceRect = *pSourceRect;
    SetStretchMode();
}

// Return the current source rectangle

void CDrawImage::GetSourceRect(RECT *pSourceRect)
{
    ASSERT(pSourceRect);
    *pSourceRect = m_SourceRect;
}

// This is called when either the source or destination rectanges change so we
// can update the stretch flag. If the rectangles don't match we stretch the
// video during the drawing otherwise we call the fast pixel copy functions
// NOTE the source and/or the destination rectangle may be completely empty

void CDrawImage::SetStretchMode()
{
    // Calculate the overall rectangle dimensions

    LONG SourceWidth = m_SourceRect.right - m_SourceRect.left;
    LONG SinkWidth = m_TargetRect.right - m_TargetRect.left;
    LONG SourceHeight = m_SourceRect.bottom - m_SourceRect.top;
    LONG SinkHeight = m_TargetRect.bottom - m_TargetRect.top;

    m_bStretch = TRUE;
    if (SourceWidth == SinkWidth) {
        if (SourceHeight == SinkHeight) {
            m_bStretch = FALSE;
        }
    }
}

// Tell us whose allocator we are using. This should be called with TRUE if
// the filter agrees to use an allocator based around the CImageAllocator
// SDK base class - whose image buffers are made through CreateDIBSection.
// Otherwise this should be called with FALSE and we will draw the images
// using SetDIBitsToDevice and StretchDIBitsToDevice. None of these calls
// can handle buffers which have non zero strides (like DirectDraw uses)

void CDrawImage::NotifyAllocator(BOOL bUsingImageAllocator)
{
    m_bUsingImageAllocator = bUsingImageAllocator;
}

// Are we using the image DIBSECTION allocator

BOOL CDrawImage::UsingImageAllocator()
{
    return m_bUsingImageAllocator;
}

// We need the media type of the connection so that we can get the BITMAPINFO
// from it. We use that in the calls to draw the image such as StretchDIBits
// and also when updating the colour table held in shared memory DIBSECTIONs

void CDrawImage::NotifyMediaType(CMediaType *pMediaType)
{
    m_pMediaType = pMediaType;
}

// We store in this object a cookie maintaining the current palette version.
// Each time a palettised format is changed we increment this value so that
// when we come to draw the images we look at the colour table value they
// have and if less than the current we know to update it. This version is
// only needed and indeed used when working with shared memory DIBSECTIONs

LONG CDrawImage::GetPaletteVersion()
{
    return m_PaletteVersion;
}

// Resets the current palette version number

void CDrawImage::ResetPaletteVersion()
{
    m_PaletteVersion = PALETTE_VERSION;
}

// Increment the current palette version

void CDrawImage::IncrementPaletteVersion()
{
    m_PaletteVersion++;
}

// Constructor must initialise the base allocator. Each sample we create has a
// palette version cookie on board. When the source filter changes the palette
// during streaming the window object increments an internal cookie counter it
// keeps as well. When it comes to render the samples it looks at the cookie
// values and if they don't match then it knows to update the sample's colour
// table. However we always create samples with a cookie of PALETTE_VERSION
// If there have been multiple format changes and we disconnect and reconnect
// thereby causing the samples to be reallocated we will create them with a
// cookie much lower than the current version, this isn't a problem since it
// will be seen by the window object and the versions will then be updated

CImageAllocator::CImageAllocator(CBaseFilter *pFilter,
                                 TCHAR *pName,
                                 HRESULT *phr) :
    CBaseAllocator(pName,NULL,phr),
    m_pFilter(pFilter)
{
    ASSERT(phr);
    ASSERT(pFilter);
}

// Check our DIB buffers have been released

#ifdef DEBUG
CImageAllocator::~CImageAllocator()
{
    ASSERT(m_bCommitted == FALSE);
}
#endif

// Called from destructor and also from base class to free resources. We work
// our way through the list of media samples deleting the DIBSECTION created
// for each. All samples should be back in our list so there is no chance a
// filter is still using one to write on the display or hold on a pending list

void CImageAllocator::Free()
{
    ASSERT(m_lAllocated == m_lFree.GetCount());
    EXECUTE_ASSERT(GdiFlush());
    CImageSample *pSample;
    DIBDATA *pDibData;

    while (m_lFree.GetCount() != 0) {
        pSample = (CImageSample *) m_lFree.RemoveHead();
        pDibData = pSample->GetDIBData();
        EXECUTE_ASSERT(DeleteObject(pDibData->hBitmap));
        EXECUTE_ASSERT(CloseHandle(pDibData->hMapping));
        delete pSample;
    }

    m_lAllocated = 0;
}

// Prepare the allocator by checking all the input parameters

STDMETHODIMP CImageAllocator::CheckSizes(ALLOCATOR_PROPERTIES *pRequest)
{
    // Check we have a valid connection

    if (m_pMediaType == NULL) {
        return VFW_E_NOT_CONNECTED;
    }

    // NOTE We always create a DIB section with the source format type which
    // may contain a source palette. When we do the BitBlt drawing operation
    // the target display device may contain a different palette (we may not
    // have the focus) in which case GDI will do after the palette mapping

    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pMediaType->Format();

    // When we call CreateDIBSection it implicitly maps only enough memory
    // for the image as defined by thee BITMAPINFOHEADER. If the user asks
    // for an image smaller than this then we reject the call, if they ask
    // for an image larger than this then we return what they can have

    if ((DWORD) pRequest->cbBuffer < pVideoInfo->bmiHeader.biSizeImage) {
        return E_INVALIDARG;
    }

    // Reject buffer prefixes

    if (pRequest->cbPrefix > 0) {
        return E_INVALIDARG;
    }

    pRequest->cbBuffer = pVideoInfo->bmiHeader.biSizeImage;
    return NOERROR;
}

// Agree the number of media sample buffers and their sizes. The base class
// this allocator is derived from allows samples to be aligned only on byte
// boundaries NOTE the buffers are not allocated until the Commit call

STDMETHODIMP CImageAllocator::SetProperties(
    ALLOCATOR_PROPERTIES * pRequest,
    ALLOCATOR_PROPERTIES * pActual)
{
    ALLOCATOR_PROPERTIES Adjusted = *pRequest;

    // Check the parameters fit with the current connection

    HRESULT hr = CheckSizes(&Adjusted);
    if (FAILED(hr)) {
        return hr;
    }
    return CBaseAllocator::SetProperties(&Adjusted, pActual);
}

// Commit the memory by allocating the agreed number of media samples. For
// each sample we are committed to creating we have a CImageSample object
// that we use to manage it's resources. This is initialised with a DIBDATA
// structure that contains amongst other things the GDI DIBSECTION handle
// We will access the renderer media type during this so we must have locked
// (to prevent the format changing for example). The class overrides Commit
// and Decommit to do this locking (base class Commit in turn calls Alloc)

HRESULT CImageAllocator::Alloc(void)
{
    ASSERT(m_pMediaType);
    CImageSample *pSample;
    DIBDATA DibData;

    // Check the base allocator says it's ok to continue

    HRESULT hr = CBaseAllocator::Alloc();
    if (FAILED(hr)) {
        return hr;
    }

    // We create a new memory mapped object although we don't map it into our
    // address space because GDI does that in CreateDIBSection. It is possible
    // that we run out of resources before creating all the samples in which
    // case the available sample list is left with those already created

    ASSERT(m_lAllocated == 0);
    while (m_lAllocated < m_lCount) {

        // Create and initialise a shared memory GDI buffer

        HRESULT hr = CreateDIB(m_lSize,DibData);
        if (FAILED(hr)) {
            return hr;
        }

        // Create the sample object and pass it the DIBDATA

        pSample = CreateImageSample(DibData.pBase,m_lSize);
        if (pSample == NULL) {
            EXECUTE_ASSERT(DeleteObject(DibData.hBitmap));
            EXECUTE_ASSERT(CloseHandle(DibData.hMapping));
            return E_OUTOFMEMORY;
        }

        // Add the completed sample to the available list

        pSample->SetDIBData(&DibData);
        m_lFree.Add(pSample);
        m_lAllocated++;
    }
    return NOERROR;
}

// We have a virtual method that allocates the samples so that a derived class
// may override it and allocate more specialised sample objects. So long as it
// derives its samples from CImageSample then all this code will still work ok

CImageSample *CImageAllocator::CreateImageSample(LPBYTE pData,LONG Length)
{
    HRESULT hr = NOERROR;
    CImageSample *pSample;

    // Allocate the new sample and check the return codes

    pSample = new CImageSample((CBaseAllocator *) this,   // Base class
                               NAME("Video sample"),      // DEBUG name
                               (HRESULT *) &hr,           // Return code
                               (LPBYTE) pData,            // DIB address
                               (LONG) Length);            // Size of DIB

    if (pSample == NULL || FAILED(hr)) {
        delete pSample;
        return NULL;
    }
    return pSample;
}

// This function allocates a shared memory block for use by the source filter
// generating DIBs for us to render. The memory block is created in shared
// memory so that GDI doesn't have to copy the memory when we do a BitBlt

HRESULT CImageAllocator::CreateDIB(LONG InSize,DIBDATA &DibData)
{
    BITMAPINFO *pbmi;       // Format information for pin
    BYTE *pBase;            // Pointer to the actual image
    HANDLE hMapping;        // Handle to mapped object
    HBITMAP hBitmap;        // DIB section bitmap handle

    // Create a file mapping object and map into our address space

    hMapping = CreateFileMapping(hMEMORY,         // Use system page file
                                 NULL,            // No security attributes
                                 PAGE_READWRITE,  // Full access to memory
                                 (DWORD) 0,       // Less than 4Gb in size
                                 InSize,          // Size of buffer
                                 NULL);           // No name to section
    if (hMapping == NULL) {
        DWORD Error = GetLastError();
        return HRESULT_FROM_WIN32(Error);
    }

    // NOTE We always create a DIB section with the source format type which
    // may contain a source palette. When we do the BitBlt drawing operation
    // the target display device may contain a different palette (we may not
    // have the focus) in which case GDI will do after the palette mapping

    pbmi = (BITMAPINFO *) HEADER(m_pMediaType->Format());
    if (m_pMediaType == NULL) {
        DbgBreak("Invalid media type");
    }

    hBitmap = CreateDIBSection((HDC) NULL,          // NO device context
                               pbmi,                // Format information
                               DIB_RGB_COLORS,      // Use the palette
                               (VOID **) &pBase,    // Pointer to image data
                               hMapping,            // Mapped memory handle
                               (DWORD) 0);          // Offset into memory

    if (hBitmap == NULL || pBase == NULL) {
        EXECUTE_ASSERT(CloseHandle(hMapping));
        DWORD Error = GetLastError();
        return HRESULT_FROM_WIN32(Error);
    }

    // Initialise the DIB information structure

    DibData.hBitmap = hBitmap;
    DibData.hMapping = hMapping;
    DibData.pBase = pBase;
    DibData.PaletteVersion = PALETTE_VERSION;
    GetObject(hBitmap,sizeof(DIBSECTION),(VOID *)&DibData.DibSection);

    return NOERROR;
}

// We use the media type during the DIBSECTION creation

void CImageAllocator::NotifyMediaType(CMediaType *pMediaType)
{
    m_pMediaType = pMediaType;
}

// Overriden to increment the owning object's reference count

STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingAddRef()
{
    return m_pFilter->AddRef();
}

// Overriden to decrement the owning object's reference count

STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingRelease()
{
    return m_pFilter->Release();
}

// If you derive a class from CMediaSample that has to transport specialised
// member variables and entry points then there are three alternate solutions
// The first is to create a memory buffer larger than actually required by the
// sample and store your information either at the beginning of it or at the
// end, the former being moderately safer allowing for misbehaving transform
// filters. You then adjust the buffer address when you create the base media
// sample. This has the disadvantage of breaking up the memory allocated to
// the samples into separate blocks. The second solution is to implement a
// class derived from CMediaSample and support additional interface(s) that
// convey your private data. This means defining a custom interface. The final
// alternative is to create a class that inherits from CMediaSample and adds
// the private data structures, when you get an IMediaSample in your Receive()
// call check to see if your allocator is being used, and if it is then cast
// the IMediaSample into one of your objects. Additional checks can be made
// to ensure the sample's this pointer is known to be one of your own objects

CImageSample::CImageSample(CBaseAllocator *pAllocator,
                           TCHAR *pName,
                           HRESULT *phr,
                           LPBYTE pBuffer,
                           LONG length) :
    CMediaSample(pName,pAllocator,phr,pBuffer,length),
    m_bInit(FALSE)
{
    ASSERT(pAllocator);
    ASSERT(pBuffer);
}

// Set the shared memory DIB information

void CImageSample::SetDIBData(DIBDATA *pDibData)
{
    ASSERT(pDibData);
    m_DibData = *pDibData;
    m_bInit = TRUE;
}

// Retrieve the shared memory DIB data

DIBDATA *CImageSample::GetDIBData()
{
    ASSERT(m_bInit == TRUE);
    return &m_DibData;
}

// This class handles the creation of a palette. It is fairly specialist and
// is intended to simplify palette management for video renderer filters. It
// is for this reason that the constructor requires three other objects with
// which it interacts, namely a base media filter, a base window and a base
// drawing object although the base window or the draw object may be NULL to
// ignore that part of us. We try not to create and install palettes unless
// absolutely necessary as they typically require WM_PALETTECHANGED messages
// to be sent to every window thread in the system which is very expensive

CImagePalette::CImagePalette(CBaseFilter *pBaseFilter,
                             CBaseWindow *pBaseWindow,
                             CDrawImage *pDrawImage) :
    m_pBaseWindow(pBaseWindow),
    m_pFilter(pBaseFilter),
    m_pDrawImage(pDrawImage),
    m_hPalette(NULL)
{
    ASSERT(m_pFilter);
}

// Destructor

#ifdef DEBUG
CImagePalette::~CImagePalette()
{
    ASSERT(m_hPalette == NULL);
}
#endif

// We allow dynamic format changes of the palette but rather than change the
// palette every time we call this to work out whether an update is required.
// If the original type didn't use a palette and the new one does (or vica
// versa) then we return TRUE. If neither formats use a palette we'll return
// FALSE. If both formats use a palette we compare their colours and return
// FALSE if they match. This therefore short circuits palette creation unless
// absolutely necessary since installing palettes is an expensive operation

BOOL CImagePalette::ShouldUpdate(const VIDEOINFOHEADER *pNewInfo,
                                 const VIDEOINFOHEADER *pOldInfo)
{
    // We may not have a current format yet

    if (pOldInfo == NULL) {
        return TRUE;
    }

    // Do both formats not require a palette

    if (ContainsPalette(pNewInfo) == FALSE) {
        if (ContainsPalette(pOldInfo) == FALSE) {
            return FALSE;
        }
    }

    // Compare the colours to see if they match

    DWORD VideoEntries = pNewInfo->bmiHeader.biClrUsed;
    if (ContainsPalette(pNewInfo) == TRUE)
        if (ContainsPalette(pOldInfo) == TRUE)
            if (pOldInfo->bmiHeader.biClrUsed == VideoEntries)
                if (pOldInfo->bmiHeader.biClrUsed > 0)
                    if (memcmp((PVOID) GetBitmapPalette(pNewInfo),
                               (PVOID) GetBitmapPalette(pOldInfo),
                               VideoEntries * sizeof(RGBQUAD)) == 0) {

                        return FALSE;
                    }
    return TRUE;
}

// This is normally called when the input pin type is set to install a palette
// We will typically be called from two different places. The first is when we
// have negotiated a palettised media type after connection, the other is when
// we receive a new type during processing with an updated palette in which
// case we must remove and release the resources held by the current palette

// We can be passed an optional device name if we wish to prepare a palette
// for a specific monitor on a multi monitor system

HRESULT CImagePalette::PreparePalette(const CMediaType *pmtNew,
                                      const CMediaType *pmtOld,
      LPSTR szDevice)
{
    const VIDEOINFOHEADER *pNewInfo = (VIDEOINFOHEADER *) pmtNew->Format();
    const VIDEOINFOHEADER *pOldInfo = (VIDEOINFOHEADER *) pmtOld->Format();
    ASSERT(pNewInfo);

    // This is an performance optimisation, when we get a media type we check
    // to see if the format requires a palette change. If either we need one
    // when previously we didn't or vica versa then this returns TRUE, if we
    // previously needed a palette and we do now it compares their colours

    if (ShouldUpdate(pNewInfo,pOldInfo) == FALSE) {
        NOTE("No update needed");
        return S_FALSE;
    }

    // We must notify the filter graph that the application may have changed
    // the palette although in practice we don't bother checking to see if it
    // is really different. If it tries to get the palette either the window
    // or renderer lock will ensure it doesn't get in until we are finished

    RemovePalette();
    m_pFilter->NotifyEvent(EC_PALETTE_CHANGED,0,0);

    // Do we need a palette for the new format

    if (ContainsPalette(pNewInfo) == FALSE) {
        NOTE("New has no palette");
        return S_FALSE;
    }

    // If we're changing the palette on the fly then we increment our palette
    // cookie which is compared against the cookie also stored in all of our
    // DIBSECTION media samples. If they don't match when we come to draw it
    // then we know the sample is out of date and we'll update it's palette

    NOTE("Making new colour palette");
    m_hPalette = MakePalette(pNewInfo, szDevice);
    ASSERT(m_hPalette != NULL);

    // The window in which the new palette is to be realised may be a NULL
    // pointer to signal that no window is in use, if so we don't call it
    // Some filters just want to use this object to create/manage palettes

    if (m_pBaseWindow) m_pBaseWindow->SetPalette(m_hPalette);

    // This is the only time where we need access to the draw object to say
    // to it that a new palette will be arriving on a sample real soon. The
    // constructor may take a NULL pointer in which case we don't call this

    if (m_pDrawImage) m_pDrawImage->IncrementPaletteVersion();
    return NOERROR;
}

// Helper function to copy a palette out of any kind of VIDEOINFO (ie it may
// be YUV or true colour) into a palettised VIDEOINFO. We use this changing
// palettes on DirectDraw samples as a source filter can attach a palette to
// any buffer (eg YUV) and hand it back. We make a new palette out of that
// format and then copy the palette colours into the current connection type

HRESULT CImagePalette::CopyPalette(const CMediaType *pSrc,CMediaType *pDest)
{
    // Reset the destination palette before starting

    VIDEOINFOHEADER *pDestInfo = (VIDEOINFOHEADER *) pDest->Format();
    pDestInfo->bmiHeader.biClrUsed = 0;
    pDestInfo->bmiHeader.biClrImportant = 0;

    // Does the destination have a palette

    if (PALETTISED(pDestInfo) == FALSE) {
        NOTE("No destination palette");
        return S_FALSE;
    }

    // Does the source contain a palette

    const VIDEOINFOHEADER *pSrcInfo = (VIDEOINFOHEADER *) pSrc->Format();
    if (ContainsPalette(pSrcInfo) == FALSE) {
        NOTE("No source palette");
        return S_FALSE;
    }

    // The number of colours may be zero filled

    DWORD PaletteEntries = pSrcInfo->bmiHeader.biClrUsed;
    if (PaletteEntries == 0) {
        DWORD Maximum  = (1 << pSrcInfo->bmiHeader.biBitCount);
        NOTE1("Setting maximum colours (%d)",Maximum);
        PaletteEntries = Maximum;
    }

    // Make sure the destination has enough room for the palette

    ASSERT(pSrcInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS);
    ASSERT(pSrcInfo->bmiHeader.biClrImportant <= PaletteEntries);
    ASSERT(COLORS(pDestInfo) == GetBitmapPalette(pDestInfo));
    pDestInfo->bmiHeader.biClrUsed = PaletteEntries;
    pDestInfo->bmiHeader.biClrImportant = pSrcInfo->bmiHeader.biClrImportant;
    ULONG BitmapSize = GetBitmapFormatSize(HEADER(pSrcInfo));

    if (pDest->FormatLength() < BitmapSize) {
        NOTE("Reallocating destination");
        pDest->ReallocFormatBuffer(BitmapSize);
    }

    // Now copy the palette colours across

    CopyMemory((PVOID) COLORS(pDestInfo),
               (PVOID) GetBitmapPalette(pSrcInfo),
               PaletteEntries * sizeof(RGBQUAD));

    return NOERROR;
}

// This is normally called when the palette is changed (typically during a
// dynamic format change) to remove any palette we previously installed. We
// replace it (if necessary) in the video window with a standard VGA palette
// that should always be available even if this is a true colour display

HRESULT CImagePalette::RemovePalette()
{
    // Do we have a palette to remove

    if (m_hPalette == NULL) {
        return NOERROR;
    }

    // Get a standard VGA colour palette

    HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
    ASSERT(hPalette);
    const HPALETTE hPalOurs = m_hPalette;

    // Install the standard palette and delete ours. As in the previous method
    // we may not have been given a window in the constructor to use and if we
    // didn't then don't try to install the stock palette in it. This is used
    // by filters that have to create palettes but who do not draw using GDI

    if (m_pBaseWindow) {
        SelectPalette(m_pBaseWindow->GetWindowHDC(), hPalette, TRUE);
        SelectPalette(m_pBaseWindow->GetMemoryHDC(), hPalette, TRUE);
    }

    EXECUTE_ASSERT(DeleteObject(hPalOurs));
    m_hPalette = NULL;
    return NOERROR;
}

// Called to create a palette for the object, the data structure used by GDI
// to describe a palette is a LOGPALETTE, this includes a variable number of
// PALETTEENTRY fields which are the colours, we have to convert the RGBQUAD
// colour fields we are handed in a BITMAPINFO from the media type into these
// This handles extraction of palettes from true colour and YUV media formats

// We can be passed an optional device name if we wish to prepare a palette
// for a specific monitor on a multi monitor system

HPALETTE CImagePalette::MakePalette(const VIDEOINFOHEADER *pVideoInfo, LPSTR szDevice)
{
    ASSERT(ContainsPalette(pVideoInfo) == TRUE);
    ASSERT(pVideoInfo->bmiHeader.biClrUsed >= 0);
    ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS);
    BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);

    const RGBQUAD *pColours;            // Pointer to the palette
    LOGPALETTE *lp;                     // Used to create a palette
    HPALETTE hPalette;                  // Logical palette object

    lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE];
    if (lp == NULL) {
        return NULL;
    }

    // Unfortunately for some hare brained reason a GDI palette entry (a
    // PALETTEENTRY structure) is different to a palette entry from a DIB
    // format (a RGBQUAD structure) so we have to do the field conversion
    // The VIDEOINFO containing the palette may be a true colour type so
    // we use GetBitmapPalette to skip over any bit fields if they exist

    lp->palVersion = PALVERSION;
    lp->palNumEntries = (USHORT) pHeader->biClrUsed;
    if (lp->palNumEntries == 0) lp->palNumEntries = (1 << pHeader->biBitCount);
    pColours = GetBitmapPalette(pVideoInfo);

    for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) {
        lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed;
        lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen;
        lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue;
        lp->palPalEntry[dwCount].peFlags = 0;
    }

    MakeIdentityPalette(lp->palPalEntry, lp->palNumEntries, szDevice);

    // Create a logical palette

    hPalette = CreatePalette(lp);
    ASSERT(hPalette != NULL);
    delete[] lp;
    return hPalette;
}

// GDI does a fair job of compressing the palette entries you give it, so for
// example if you have five entries with an RGB colour (0,0,0) it will remove
// all but one of them. When you subsequently draw an image it will map from
// your logical palette to the compressed device palette. This function looks
// to see if it is trying to be an identity palette and if so sets the flags
// field in the PALETTEENTRYs so they remain expanded to boost performance

// We can be passed an optional device name if we wish to prepare a palette
// for a specific monitor on a multi monitor system

HRESULT CImagePalette::MakeIdentityPalette(PALETTEENTRY *pEntry,INT iColours, LPSTR szDevice)
{
    PALETTEENTRY SystemEntries[10];         // System palette entries
    BOOL bIdentityPalette = TRUE;           // Is an identity palette
    ASSERT(iColours <= iPALETTE_COLORS);    // Should have a palette
    const int PalLoCount = 10;              // First ten reserved colours
    const int PalHiStart = 246;             // Last VGA palette entries

    // Does this have the full colour range

    if (iColours < 10) {
        return S_FALSE;
    }

    // Apparently some displays have odd numbers of system colours

    // Get a DC on the right monitor - it's ugly, but this is the way you have
    // to do it
    HDC hdc;
    if (szDevice == NULL || lstrcmpiA(szDevice, "DISPLAY") == 0)
        hdc = CreateDCA("DISPLAY", NULL, NULL, NULL);
    else
        hdc = CreateDCA(NULL, szDevice, NULL, NULL);
    ASSERT(hdc);
    INT Reserved = GetDeviceCaps(hdc,NUMRESERVED);
    if (Reserved != 20) {
        DeleteDC(hdc);
        return S_FALSE;
    }

    // Compare our palette against the first ten system entries. The reason I
    // don't do a memory compare between our two arrays of colours is because
    // I am not sure what will be in the flags fields for the system entries

    UINT Result = GetSystemPaletteEntries(hdc,0,PalLoCount,SystemEntries);
    for (UINT Count = 0;Count < Result;Count++) {
        if (SystemEntries[Count].peRed != pEntry[Count].peRed ||
                SystemEntries[Count].peGreen != pEntry[Count].peGreen ||
                    SystemEntries[Count].peBlue != pEntry[Count].peBlue) {
                        bIdentityPalette = FALSE;
        }
    }

    // And likewise compare against the last ten entries

    Result = GetSystemPaletteEntries(hdc,PalHiStart,PalLoCount,SystemEntries);
    for (Count = 0;Count < Result;Count++) {
        if (INT(Count) + PalHiStart < iColours) {
            if (SystemEntries[Count].peRed != pEntry[PalHiStart + Count].peRed ||
                    SystemEntries[Count].peGreen != pEntry[PalHiStart + Count].peGreen ||
                        SystemEntries[Count].peBlue != pEntry[PalHiStart + Count].peBlue) {
                            bIdentityPalette = FALSE;
            }
        }
    }

    // If not an identity palette then return S_FALSE

    DeleteDC(hdc);
    if (bIdentityPalette == FALSE) {
        return S_FALSE;
    }

    // Set the non VGA entries so that GDI doesn't map them

    for (Count = PalLoCount;INT(Count) < min(PalHiStart,iColours);Count++) {
        pEntry[Count].peFlags = PC_NOCOLLAPSE;
    }
    return NOERROR;
}

// Constructor initialises the VIDEOINFO we keep storing the current display
// format. The format can be changed at any time, to reset the format held
// by us call the RefreshDisplayType directly (it's a public method). Since
// more than one thread will typically call us (ie window threads resetting
// the type and source threads in the type checking methods) we have a lock

CImageDisplay::CImageDisplay()
{
    RefreshDisplayType(NULL);
}

// This initialises the format we hold which contains the display device type
// We do a conversion on the display device type in here so that when we start
// type checking input formats we can assume that certain fields have been set
// correctly, an example is when we make the 16 bit mask fields explicit. This
// is normally called when we receive WM_DEVMODECHANGED device change messages

// The optional szDeviceName parameter tells us which monitor we are interested
// in for a multi monitor system

HRESULT CImageDisplay::RefreshDisplayType(LPSTR szDeviceName)
{
    CAutoLock cDisplayLock(this);

    // Set the preferred format type

    ZeroMemory((PVOID)&m_Display,sizeof(VIDEOINFOHEADER)+sizeof(TRUECOLORINFO));
    m_Display.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    m_Display.bmiHeader.biBitCount = FALSE;

    // Get the bit depth of a device compatible bitmap

    // get caps of whichever monitor they are interested in (multi monitor)
    HDC hdcDisplay;
    // it's ugly, but this is the way you have to do it
    if (szDeviceName == NULL || lstrcmpiA(szDeviceName, "DISPLAY") == 0)
        hdcDisplay = CreateDCA("DISPLAY", NULL, NULL, NULL);
    else
        hdcDisplay = CreateDCA(NULL, szDeviceName, NULL, NULL);
    if (hdcDisplay == NULL) {
ASSERT(FALSE);
DbgLog((LOG_ERROR,1,TEXT("ACK! Can't get a DC for %s"),
szDeviceName ? szDeviceName : "<NULL>"));
return E_FAIL;
    } else {
DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"),
szDeviceName ? szDeviceName : "<NULL>"));
    }
    HBITMAP hbm = CreateCompatibleBitmap(hdcDisplay,1,1);
    GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS);

    // This call will get the colour table or the proper bitfields
    GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS);
    DeleteObject(hbm);
    DeleteDC(hdcDisplay);

    // Complete the display type initialisation

    ASSERT(CheckHeaderValidity(&m_Display));
    UpdateFormat(&m_Display);
    DbgLog((LOG_TRACE,3,TEXT("New DISPLAY bit depth =%d"),
m_Display.bmiHeader.biBitCount));
    return NOERROR;
}

// We assume throughout this code that any bitfields masks are allowed no
// more than eight bits to store a colour component. This checks that the
// bit count assumption is enforced and also makes sure that all the bits
// set are contiguous. We return a boolean TRUE if the field checks out ok

BOOL CImageDisplay::CheckBitFields(const VIDEOINFO *pInput)
{
    DWORD *pBitFields = (DWORD *) BITMASKS(pInput);

    for (INT iColour = iRED;iColour <= iBLUE;iColour++) {

        // First of all work out how many bits are set

        DWORD SetBits = CountSetBits(pBitFields[iColour]);
        if (SetBits > iMAXBITS || SetBits == 0) {
            NOTE1("Bit fields for component %d invalid",iColour);
            return FALSE;
        }

        // Next work out the number of zero bits prefix
        DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]);

        // This is going to see if all the bits set are contiguous (as they
        // should be). We know how much to shift them right by from the
        // count of prefix bits. The number of bits set defines a mask, we
        // invert this (ones complement) and AND it with the shifted bit
        // fields. If the result is NON zero then there are bit(s) sticking
        // out the left hand end which means they are not contiguous

        DWORD TestField = pBitFields[iColour] >> PrefixBits;
        DWORD Mask = ULONG_MAX << SetBits;
        if (TestField & Mask) {
            NOTE1("Bit fields for component %d not contiguous",iColour);
            return FALSE;
        }
    }
    return TRUE;
}

// This counts the number of bits set in the input field

DWORD CImageDisplay::CountSetBits(DWORD Field)
{
    // This is a relatively well known bit counting algorithm

    DWORD Count = 0;
    DWORD init = Field;

    // Until the input is exhausted, count the number of bits

    while (init) {
        init = init & (init - 1);  // Turn off the bottommost bit
        Count++;
    }
    return Count;
}

// This counts the number of zero bits upto the first one set NOTE the input
// field should have been previously checked to ensure there is at least one
// set although if we don't find one set we return the impossible value 32

DWORD CImageDisplay::CountPrefixBits(DWORD Field)
{
    DWORD Mask = 1;
    DWORD Count = 0;

    while (TRUE) {
        if (Field & Mask) {
            return Count;
        }
        Count++;

        ASSERT(Mask != 0x80000000);
        if (Mask == 0x80000000) {
            return Count;
        }
        Mask <<= 1;
    }
}

// This is called to check the BITMAPINFOHEADER for the input type. There are
// many implicit dependancies between the fields in a header structure which
// if we validate now make for easier manipulation in subsequent handling. We
// also check that the BITMAPINFOHEADER matches it's specification such that
// fields likes the number of planes is one, that it's structure size is set
// correctly and that the bitmap dimensions have not been set as negative

BOOL CImageDisplay::CheckHeaderValidity(const VIDEOINFO *pInput)
{
    // Check the bitmap width and height are not negative.

    if (pInput->bmiHeader.biWidth <= 0 ||
pInput->bmiHeader.biHeight <= 0) {
        NOTE("Invalid bitmap dimensions");
        return FALSE;
    }

    // Check the compression is either BI_RGB or BI_BITFIELDS

    if (pInput->bmiHeader.biCompression != BI_RGB) {
        if (pInput->bmiHeader.biCompression != BI_BITFIELDS) {
            NOTE("Invalid compression format");
            return FALSE;
        }
    }

    // If BI_BITFIELDS compression format check the colour depth

    if (pInput->bmiHeader.biCompression == BI_BITFIELDS) {
        if (pInput->bmiHeader.biBitCount != 16) {
            if (pInput->bmiHeader.biBitCount != 32) {
                NOTE("BI_BITFIELDS not 16/32 bit depth");
                return FALSE;
            }
        }
    }

    // Check the assumptions about the layout of the bit fields

    if (pInput->bmiHeader.biCompression == BI_BITFIELDS) {
        if (CheckBitFields(pInput) == FALSE) {
            NOTE("Bit fields are not valid");
            return FALSE;
        }
    }

    // Are the number of planes equal to one

    if (pInput->bmiHeader.biPlanes != 1) {
        NOTE("Number of planes not one");
        return FALSE;
    }

    // Check the image size is consistent (it can be zero)

    if (pInput->bmiHeader.biSizeImage != GetBitmapSize(&pInput->bmiHeader)) {
        if (pInput->bmiHeader.biSizeImage) {
            NOTE("Image size incorrectly set");
            return FALSE;
        }
    }

    // Check the size of the structure

    if (pInput->bmiHeader.biSize != sizeof(BITMAPINFOHEADER)) {
        NOTE("Size of BITMAPINFOHEADER wrong");
        return FALSE;
    }
    return CheckPaletteHeader(pInput);
}

// This runs a few simple tests against the palette fields in the input to
// see if it looks vaguely correct. The tests look at the number of palette
// colours present, the number considered important and the biCompression
// field which should always be BI_RGB as no other formats are meaningful

BOOL CImageDisplay::CheckPaletteHeader(const VIDEOINFO *pInput)
{
    // The checks here are for palettised videos only

    if (PALETTISED(pInput) == FALSE) {
        if (pInput->bmiHeader.biClrUsed) {
            NOTE("Invalid palette entries");
            return FALSE;
        }
        return TRUE;
    }

    // Compression type of BI_BITFIELDS is meaningless for palette video

    if (pInput->bmiHeader.biCompression != BI_RGB) {
        NOTE("Palettised video must be BI_RGB");
        return FALSE;
    }

    // Check the number of palette colours is correct

    if (pInput->bmiHeader.biClrUsed > PALETTE_ENTRIES(pInput)) {
        NOTE("Too many colours in palette");
        return FALSE;
    }

    // The number of important colours shouldn't exceed the number used

    if (pInput->bmiHeader.biClrImportant > pInput->bmiHeader.biClrUsed) {
        NOTE("Too many important colours");
        return FALSE;
    }
    return TRUE;
}

// Return the format of the video display

const VIDEOINFO *CImageDisplay::GetDisplayFormat()
{
    return &m_Display;
}

// Return TRUE if the display uses a palette

BOOL CImageDisplay::IsPalettised()
{
    return PALETTISED(&m_Display);
}

// Return the bit depth of the current display setting

WORD CImageDisplay::GetDisplayDepth()
{
    return m_Display.bmiHeader.biBitCount;
}

// Initialise the optional fields in a VIDEOINFO. These are mainly to do with
// the source and destination rectangles and palette information such as the
// number of colours present. It simplifies our code just a little if we don't
// have to keep checking for all the different valid permutations in a header
// every time we want to do anything with it (an example would be creating a
// palette). We set the base class media type before calling this function so
// that the media types between the pins match after a connection is made

HRESULT CImageDisplay::UpdateFormat(VIDEOINFO *pVideoInfo)
{
    ASSERT(pVideoInfo);

    BITMAPINFOHEADER *pbmi = HEADER(pVideoInfo);
    SetRectEmpty(&pVideoInfo->rcSource);
    SetRectEmpty(&pVideoInfo->rcTarget);

    // Set the number of colours explicitly

    if (PALETTISED(pVideoInfo)) {
        if (pVideoInfo->bmiHeader.biClrUsed == 0) {
            pVideoInfo->bmiHeader.biClrUsed = PALETTE_ENTRIES(pVideoInfo);
        }
    }

    // The number of important colours shouldn't exceed the number used, on
    // some displays the number of important colours is not initialised when
    // retrieving the display type so we set the colours used correctly

    if (pVideoInfo->bmiHeader.biClrImportant > pVideoInfo->bmiHeader.biClrUsed) {
        pVideoInfo->bmiHeader.biClrImportant = PALETTE_ENTRIES(pVideoInfo);
    }

    // Change the image size field to be explicit

    if (pVideoInfo->bmiHeader.biSizeImage == 0) {
        pVideoInfo->bmiHeader.biSizeImage = GetBitmapSize(&pVideoInfo->bmiHeader);
    }
    return NOERROR;
}

// Lots of video rendering filters want code to check proposed formats are ok
// This checks the VIDEOINFO we are passed as a media type. If the media type
// is a valid media type then we return NOERROR otherwise E_INVALIDARG. Note
// however we only accept formats that can be easily displayed in the display
// so if we are on a 16 bit device we will not accept 24 bit images. The one
// complexity is that most displays draw 8 bit palettised images efficiently
// Also if the input format is less colour bits per pixel then we also accept

HRESULT CImageDisplay::CheckVideoType(const VIDEOINFO *pInput)
{
    // First of all check the VIDEOINFOHEADER looks correct

    if (CheckHeaderValidity(pInput) == FALSE) {
        return E_INVALIDARG;
    }

    // Virtually all devices support palettised images efficiently

    if (m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount) {
        if (PALETTISED(pInput) == TRUE) {
            ASSERT(PALETTISED(&m_Display) == TRUE);
            NOTE("(Video) Type connection ACCEPTED");
            return NOERROR;
        }
    }

    // Is the display depth greater than the input format

    if (m_Display.bmiHeader.biBitCount > pInput->bmiHeader.biBitCount) {
        NOTE("(Video) Mismatch agreed");
        return NOERROR;
    }

    // Is the display depth less than the input format

    if (m_Display.bmiHeader.biBitCount < pInput->bmiHeader.biBitCount) {
        NOTE("(Video) Format mismatch");
        return E_INVALIDARG;
    }

    // Both input and display formats are either BI_RGB or BI_BITFIELDS

    ASSERT(m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount);
    ASSERT(PALETTISED(pInput) == FALSE);
    ASSERT(PALETTISED(&m_Display) == FALSE);

    // BI_RGB 16 bit representation is implicitly RGB555, and likewise BI_RGB
    // 24 bit representation is RGB888. So we initialise a pointer to the bit
    // fields they really mean and check against the display device format
    // This is only going to be called when both formats are equal bits pixel

    const DWORD *pInputMask = GetBitMasks(pInput);
    const DWORD *pDisplayMask = GetBitMasks((VIDEOINFO *)&m_Display);

    if (pInputMask[iRED] != pDisplayMask[iRED] ||
            pInputMask[iGREEN] != pDisplayMask[iGREEN] ||
                pInputMask[iBLUE] != pDisplayMask[iBLUE]) {

        NOTE("(Video) Bit field mismatch");
        return E_INVALIDARG;
    }

    NOTE("(Video) Type connection ACCEPTED");
    return NOERROR;
}

// Return the bit masks for the true colour VIDEOINFO provided

const DWORD *CImageDisplay::GetBitMasks(const VIDEOINFO *pVideoInfo)
{
    static const DWORD FailMasks[] = {0,0,0};

    if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) {
        return BITMASKS(pVideoInfo);
    }

    ASSERT(pVideoInfo->bmiHeader.biCompression == BI_RGB);

    switch (pVideoInfo->bmiHeader.biBitCount) {
        case 16: return bits555;
        case 24: return bits888;
        case 32: return bits888;
        default: return FailMasks;
    }
}

// Check to see if we can support media type pmtIn as proposed by the output
// pin - We first check that the major media type is video and also identify
// the media sub type. Then we thoroughly check the VIDEOINFO type provided
// As well as the contained VIDEOINFO being correct the major type must be
// video, the subtype a recognised video format and the type GUID correct

HRESULT CImageDisplay::CheckMediaType(const CMediaType *pmtIn)
{
    // Does this have a VIDEOINFOHEADER format block

    const GUID *pFormatType = pmtIn->FormatType();
    if (*pFormatType != FORMAT_VideoInfo) {
        NOTE("Format GUID not a VIDEOINFOHEADER");
        return E_INVALIDARG;
    }
    ASSERT(pmtIn->Format());

    // Check the format looks reasonably ok

    ULONG Length = pmtIn->FormatLength();
    if (Length < SIZE_VIDEOHEADER) {
        NOTE("Format smaller than a VIDEOHEADER");
        return E_FAIL;
    }

    VIDEOINFO *pInput = (VIDEOINFO *) pmtIn->Format();

    // Check the major type is MEDIATYPE_Video

    const GUID *pMajorType = pmtIn->Type();
    if (*pMajorType != MEDIATYPE_Video) {
        NOTE("Major type not MEDIATYPE_Video");
        return E_INVALIDARG;
    }

    // Check we can identify the media subtype

    const GUID *pSubType = pmtIn->Subtype();
    if (GetBitCount(pSubType) == USHRT_MAX) {
        NOTE("Invalid video media subtype");
        return E_INVALIDARG;
    }
    return CheckVideoType(pInput);
}

// Given a video format described by a VIDEOINFO structure we return the mask
// that is used to obtain the range of acceptable colours for this type, for
// example, the mask for a 24 bit true colour format is 0xFF in all cases. A
// 16 bit 5:6:5 display format uses 0xF8, 0xFC and 0xF8, therefore given any
// RGB triplets we can AND them with these fields to find one that is valid

BOOL CImageDisplay::GetColourMask(DWORD *pMaskRed,
                                  DWORD *pMaskGreen,
                                  DWORD *pMaskBlue)
{
    CAutoLock cDisplayLock(this);
    *pMaskRed = 0xFF;
    *pMaskGreen = 0xFF;
    *pMaskBlue = 0xFF;

    // If this format is palettised then it doesn't have bit fields

    if (m_Display.bmiHeader.biBitCount < 16) {
        return FALSE;
    }

    // If this is a 24 bit true colour display then it can handle all the
    // possible colour component ranges described by a byte. It is never
    // allowed for a 24 bit colour depth image to have BI_BITFIELDS set

    if (m_Display.bmiHeader.biBitCount == 24) {
        ASSERT(m_Display.bmiHeader.biCompression == BI_RGB);
        return TRUE;
    }

    // Calculate the mask based on the format's bit fields

    const DWORD *pBitFields = (DWORD *) GetBitMasks((VIDEOINFO *)&m_Display);
    DWORD *pOutputMask[] = { pMaskRed, pMaskGreen, pMaskBlue };

    // We know from earlier testing that there are no more than iMAXBITS
    // bits set in the mask and that they are all contiguous. All that
    // therefore remains is to shift them into the correct position

    for (INT iColour = iRED;iColour <= iBLUE;iColour++) {

        // This works out how many bits there are and where they live

        DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]);
        DWORD SetBits = CountSetBits(pBitFields[iColour]);

        // The first shift moves the bit field so that it is right justified
        // in the DWORD, after which we then shift it back left which then
        // puts the leading bit in the bytes most significant bit position

        *(pOutputMask[iColour]) = pBitFields[iColour] >> PrefixBits;
        *(pOutputMask[iColour]) <<= (iMAXBITS - SetBits);
    }
    return TRUE;
}


WINUTIL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Generic window handler base classes, December 1995

// Make sure that you call PrepareWindow to initialise the window after
// the object has been constructed. It is a separate method so that
// derived classes can override useful methods like MessageLoop. Also
// any derived class must call DoneWithWindow in its destructor. If it
// doesn't a message may be retrieved and call a derived class member
// function while a thread is executing the base class destructor code

#ifndef __WINUTIL__
#define __WINUTIL__

const int DEFWIDTH = 320;                    // Initial window width
const int DEFHEIGHT = 240;                   // Initial window height
const int CAPTION = 256;                     // Maximum length of caption
const int TIMELENGTH = 50;                   // Maximum length of times
const int PROFILESTR = 128;                  // Normal profile string
const WORD PALVERSION = 0x300;               // GDI palette version
const LONG PALETTE_VERSION = (LONG) 1;       // Initial palette version
const COLORREF VIDEO_COLOUR = 0;             // Defaults to black background
const HANDLE hMEMORY = (HANDLE) 0xFFFFFFFF;  // Says to open as memory file

#define WIDTH(x) ((*(x)).right - (*(x)).left)
#define HEIGHT(x) ((*(x)).bottom - (*(x)).top)
#define SHOWSTAGE TEXT("WM_SHOWSTAGE")
#define SHOWSTAGETOP TEXT("WM_SHOWSTAGETOP")
#define REALIZEPALETTE TEXT("WM_REALIZEPALETTE")

class AM_NOVTABLE CBaseWindow
{
protected:

    HINSTANCE m_hInstance;          // Global module instance handle
    HWND m_hwnd;                    // Handle for our window
    HDC m_hdc;                      // Device context for the window
    LONG m_Width;                   // Client window width
    LONG m_Height;                  // Client window height
    BOOL m_bActivated;              // Has the window been activated
    LPTSTR m_pClassName;            // Static string holding class name
    DWORD m_ClassStyles;            // Passed in to our constructor
    DWORD m_WindowStyles;           // Likewise the initial window styles
    DWORD m_WindowStylesEx;         // And the extended window styles
    UINT m_ShowStageMessage;        // Have the window shown with focus
    UINT m_ShowStageTop;            // Makes the window WS_EX_TOPMOST
    UINT m_RealizePalette;          // Makes us realize our new palette
    HDC m_MemoryDC;                 // Used for fast BitBlt operations
    HPALETTE m_hPalette;            // Handle to any palette we may have
    BYTE m_bNoRealize;              // Don't realize palette now
    BYTE m_bBackground;             // Should we realise in background
    BYTE m_bRealizing;              // already realizing the palette
    CCritSec m_WindowLock;          // Serialise window object access
    BOOL m_bDoGetDC;                // Should this window get a DC

    // Maps windows message procedure into C++ methods
    friend LRESULT CALLBACK WndProc(HWND hwnd,      // Window handle
                                    UINT uMsg,      // Message ID
                                    WPARAM wParam,  // First parameter
                                    LPARAM lParam); // Other parameter

    virtual LRESULT OnPaletteChange(HWND hwnd, UINT Message);

public:

    CBaseWindow(BOOL bDoGetDC = TRUE);

#ifdef DEBUG
    virtual ~CBaseWindow();
#endif

    virtual HRESULT DoneWithWindow();
    virtual HRESULT PrepareWindow();
    virtual HRESULT InactivateWindow();
    virtual HRESULT ActivateWindow();
    virtual BOOL OnSize(LONG Width, LONG Height);
    virtual BOOL OnClose();
    virtual RECT GetDefaultRect();
    virtual HRESULT UninitialiseWindow();
    virtual HRESULT InitialiseWindow(HWND hwnd);

    HRESULT DoCreateWindow();

    HRESULT PerformanceAlignWindow();
    HRESULT DoShowWindow(LONG ShowCmd);
    void PaintWindow(BOOL bErase);
    void DoSetWindowForeground(BOOL bFocus);
    virtual HRESULT SetPalette(HPALETTE hPalette);
    void SetRealize(BOOL bRealize)
    {
        m_bNoRealize = !bRealize;
    }

    //  Jump over to the window thread to set the current palette
    HRESULT SetPalette();

    virtual HRESULT DoRealisePalette(BOOL bForceBackground = FALSE);

    virtual BOOL PossiblyEatMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
    { return FALSE; };

    // Access our window information

    LONG GetWindowWidth();
    LONG GetWindowHeight();
    HWND GetWindowHWND();
    HDC GetMemoryHDC();
    HDC GetWindowHDC();

    // This is the window procedure the derived object should override

    virtual LRESULT OnReceiveMessage(HWND hwnd,          // Window handle
                                     UINT uMsg,          // Message ID
                                     WPARAM wParam,      // First parameter
                                     LPARAM lParam);     // Other parameter

    // Must be overriden to return class and window styles

    virtual LPTSTR GetClassWindowStyles(
                            DWORD *pClassStyles,          // Class styles
                            DWORD *pWindowStyles,         // Window styles
                            DWORD *pWindowStylesEx) PURE; // Extended styles
};

// This helper class is entirely subservient to the owning CBaseWindow object
// All this object does is to split out the actual drawing operation from the
// main object (because it was becoming too large). We have a number of entry
// points to set things like the draw device contexts, to implement the actual
// drawing and to set the destination rectangle in the client window. We have
// no critical section locking in this class because we are used exclusively
// by the owning window object which looks after serialising calls into us

// If you want to use this class make sure you call NotifyAllocator once the
// allocate has been agreed, also call NotifyMediaType with a pointer to a
// NON stack based CMediaType once that has been set (we keep a pointer to
// the original rather than taking a copy). When the palette changes call
// IncrementPaletteVersion (easiest thing to do is to also call this method
// in the SetMediaType method most filters implement). Finally before you
// start rendering anything call SetDrawContext so that we can get the HDCs
// for drawing from the CBaseWindow object we are given during construction

class CDrawImage
{
protected:

    CBaseWindow *m_pBaseWindow;     // Owning video window object
    CRefTime m_StartSample;         // Start time for the current sample
    CRefTime m_EndSample;           // And likewise it's end sample time
    HDC m_hdc;                      // Main window device context
    HDC m_MemoryDC;                 // Offscreen draw device context
    RECT m_TargetRect;              // Target destination rectangle
    RECT m_SourceRect;              // Source image rectangle
    BOOL m_bStretch;                // Do we have to stretch the images
    BOOL m_bUsingImageAllocator;    // Are the samples shared DIBSECTIONs
    CMediaType *m_pMediaType;       // Pointer to the current format
    int m_perfidRenderTime;         // Time taken to render an image
    LONG m_PaletteVersion;          // Current palette version cookie

    // Draw the video images in the window

    void SlowRender(IMediaSample *pMediaSample);
    void FastRender(IMediaSample *pMediaSample);
    void DisplaySampleTimes(IMediaSample *pSample);
    void UpdateColourTable(HDC hdc,BITMAPINFOHEADER *pbmi);
    void SetStretchMode();

public:

    // Used to control the image drawing

    CDrawImage(CBaseWindow *pBaseWindow);
    BOOL DrawImage(IMediaSample *pMediaSample);
    void SetDrawContext();
    void SetTargetRect(RECT *pTargetRect);
    void SetSourceRect(RECT *pSourceRect);
    void GetTargetRect(RECT *pTargetRect);
    void GetSourceRect(RECT *pSourceRect);
    virtual RECT ScaleSourceRect(const RECT *pSource);

    // Handle updating palettes as they change

    LONG GetPaletteVersion();
    void ResetPaletteVersion();
    void IncrementPaletteVersion();

    // Tell us media types and allocator assignments

    void NotifyAllocator(BOOL bUsingImageAllocator);
    void NotifyMediaType(CMediaType *pMediaType);
    BOOL UsingImageAllocator();

    // Called when we are about to draw an image

    void NotifyStartDraw() {
        MSR_START(m_perfidRenderTime);
    };

    // Called when we complete an image rendering

    void NotifyEndDraw() {
        MSR_STOP(m_perfidRenderTime);
    };
};

// This is the structure used to keep information about each GDI DIB. All the
// samples we create from our allocator will have a DIBSECTION allocated to
// them. When we receive the sample we know we can BitBlt straight to an HDC

typedef struct tagDIBDATA {

    LONG        PaletteVersion;     // Current palette version in use
    DIBSECTION  DibSection;         // Details of DIB section allocated
    HBITMAP     hBitmap;            // Handle to bitmap for drawing
    HANDLE      hMapping;           // Handle to shared memory block
    BYTE        *pBase;             // Pointer to base memory address

} DIBDATA;

// This class inherits from CMediaSample and uses all of it's methods but it
// overrides the constructor to initialise itself with the DIBDATA structure
// When we come to render an IMediaSample we will know if we are using our own
// allocator, and if we are, we can cast the IMediaSample to a pointer to one
// of these are retrieve the DIB section information and hence the HBITMAP

class CImageSample : public CMediaSample
{
protected:

    DIBDATA m_DibData;      // Information about the DIBSECTION
    BOOL m_bInit;           // Is the DIB information setup

public:

    // Constructor

    CImageSample(CBaseAllocator *pAllocator,
                 TCHAR *pName,
                 HRESULT *phr,
                 LPBYTE pBuffer,
                 LONG length);

    // Maintain the DIB/DirectDraw state

    void SetDIBData(DIBDATA *pDibData);
    DIBDATA *GetDIBData();
};

// This is an allocator based on the abstract CBaseAllocator base class that
// allocates sample buffers in shared memory. The number and size of these
// are determined when the output pin calls Prepare on us. The shared memory
// blocks are used in subsequent calls to GDI CreateDIBSection, once that
// has been done the output pin can fill the buffers with data which will
// then be handed to GDI through BitBlt calls and thereby remove one copy

class CImageAllocator : public CBaseAllocator
{
protected:

    CBaseFilter *m_pFilter;   // Delegate reference counts to
    CMediaType *m_pMediaType;           // Pointer to the current format

    // Used to create and delete samples

    HRESULT Alloc();
    void Free();

    // Manage the shared DIBSECTION and DCI/DirectDraw buffers

    HRESULT CreateDIB(LONG InSize,DIBDATA &DibData);
    STDMETHODIMP CheckSizes(ALLOCATOR_PROPERTIES *pRequest);
    virtual CImageSample *CreateImageSample(LPBYTE pData,LONG Length);

public:

    // Constructor and destructor

    CImageAllocator(CBaseFilter *pFilter,TCHAR *pName,HRESULT *phr);
#ifdef DEBUG
    ~CImageAllocator();
#endif

    STDMETHODIMP_(ULONG) NonDelegatingAddRef();
    STDMETHODIMP_(ULONG) NonDelegatingRelease();
    void NotifyMediaType(CMediaType *pMediaType);

    // Agree the number of buffers to be used and their size

    STDMETHODIMP SetProperties(
        ALLOCATOR_PROPERTIES *pRequest,
        ALLOCATOR_PROPERTIES *pActual);
};

// This class is a fairly specialised helper class for image renderers that
// have to create and manage palettes. The CBaseWindow class looks after
// realising palettes once they have been installed. This class can be used
// to create the palette handles from a media format (which must contain a
// VIDEOINFO structure in the format block). We try to make the palette an
// identity palette to maximise performance and also only change palettes
// if actually required to (we compare palette colours before updating).
// All the methods are virtual so that they can be overriden if so required

class CImagePalette
{
protected:

    CBaseWindow *m_pBaseWindow;             // Window to realise palette in
    CBaseFilter *m_pFilter;       // Media filter to send events
    CDrawImage *m_pDrawImage;               // Object who will be drawing
    HPALETTE m_hPalette;                    // The palette handle we own

public:

    CImagePalette(CBaseFilter *pBaseFilter,
                  CBaseWindow *pBaseWindow,
                  CDrawImage *pDrawImage);

#ifdef DEBUG
    virtual ~CImagePalette();
#endif

    static HPALETTE MakePalette(const VIDEOINFOHEADER *pVideoInfo, LPSTR szDevice);
    HRESULT RemovePalette();
    static HRESULT MakeIdentityPalette(PALETTEENTRY *pEntry,INT iColours, LPSTR szDevice);
    HRESULT CopyPalette(const CMediaType *pSrc,CMediaType *pDest);
    BOOL ShouldUpdate(const VIDEOINFOHEADER *pNewInfo,const VIDEOINFOHEADER *pOldInfo);
    HRESULT PreparePalette(const CMediaType *pmtNew,const CMediaType *pmtOld,LPSTR szDevice);
};

// Another helper class really for video based renderers. Most such renderers
// need to know what the display format is to some degree or another. This
// class initialises itself with the display format. The format can be asked
// for through GetDisplayFormat and various other accessor functions. If a
// filter detects a display format change (perhaps it gets a WM_DEVMODECHANGE
// message then it can call RefreshDisplayType to reset that format). Also
// many video renderers will want to check formats as they are proposed by
// source filters. This class provides methods to check formats and only
// accept those video formats that can be efficiently drawn using GDI calls

class CImageDisplay : public CCritSec
{
protected:

    // This holds the display format; biSize should not be too big, so we can
    // safely use the VIDEOINFO structure
    VIDEOINFO m_Display;

    static DWORD CountSetBits(const DWORD Field);
    static DWORD CountPrefixBits(const DWORD Field);
    static BOOL CheckBitFields(const VIDEOINFO *pInput);

public:

    // Constructor and destructor

    CImageDisplay();

    // Used to manage BITMAPINFOHEADERs and the display format

    const VIDEOINFO *GetDisplayFormat();
    HRESULT RefreshDisplayType(LPSTR szDeviceName);
    static BOOL CheckHeaderValidity(const VIDEOINFO *pInput);
    static BOOL CheckPaletteHeader(const VIDEOINFO *pInput);
    BOOL IsPalettised();
    WORD GetDisplayDepth();

    // Provide simple video format type checking

    HRESULT CheckMediaType(const CMediaType *pmtIn);
    HRESULT CheckVideoType(const VIDEOINFO *pInput);
    HRESULT UpdateFormat(VIDEOINFO *pVideoInfo);
    const DWORD *GetBitMasks(const VIDEOINFO *pVideoInfo);

    BOOL GetColourMask(DWORD *pMaskRed,
                       DWORD *pMaskGreen,
                       DWORD *pMaskBlue);
};

#endif // __WINUTIL__


WXDEBUG.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// ActiveX system debugging facilities

#define _WINDLL

#include <streams.h>
#include <stdarg.h>
#include <stdio.h>

#ifdef DEBUG
#ifdef UNICODE
#ifndef _UNICODE
#define _UNICODE
#endif // _UNICODE
#endif // UNICODE
#endif // DEBUG

#include <tchar.h>

#ifdef DEBUG

const INT iDEBUGINFO = 512;                 // Used to format strings
const INT iMAXLEVELS = 5;                   // Maximum debug categories

HINSTANCE m_hInst;                          // Module instance handle
TCHAR m_ModuleName[iDEBUGINFO];             // Cut down module name
DWORD m_Levels[iMAXLEVELS];                 // Debug level per category
CRITICAL_SECTION m_CSDebug;                 // Controls access to list
DWORD m_dwNextCookie;                       // Next active object ID
ObjectDesc *pListHead = NULL;               // First active object
DWORD m_dwObjectCount;                      // Active object count
BOOL m_bInit = FALSE;                       // Have we been initialised
HANDLE m_hOutput = INVALID_HANDLE_VALUE;    // Optional output written here
DWORD dwWaitTimeout = INFINITE;             // Default timeout value
DWORD dwTimeOffset;    // Time of first DbgLog call

const TCHAR *pBaseKey = TEXT("SOFTWARE\\Debug");
const TCHAR *pGlobalKey = TEXT("GLOBAL");
static TCHAR *pUnknownName = TEXT("UNKNOWN");

/* For every module and executable we store a debugging level for each of
   the five categories (eg LOG_ERROR and LOG_TIMING). This makes it easy
   to isolate and debug individual modules without seeing everybody elses
   spurious debug output. The keys are stored in the registry under the
   HKEY_LOCAL_MACHINE\SOFTWARE\Debug\<Module Name>\<KeyName> key values
   NOTE these must be in the same order as their enumeration definition */

TCHAR *pKeyNames[] = {
    TEXT("TIMING"),      // Timing and performance measurements
    TEXT("TRACE"),       // General step point call tracing
    TEXT("MEMORY"),      // Memory and object allocation/destruction
    TEXT("LOCKING"),     // Locking/unlocking of critical sections
    TEXT("ERROR")        // Debug error notification
    };

TCHAR *TimeoutName = TEXT("TIMEOUT");

/* This sets the instance handle that the debug library uses to find
   the module's file name from the Win32 GetModuleFileName function */

void WINAPI DbgInitialise(HINSTANCE hInst)
{
    InitializeCriticalSection(&m_CSDebug);
    m_bInit = TRUE;

    m_hInst = hInst;
    DbgInitModuleName();
    if (GetProfileInt(m_ModuleName, TEXT("BreakOnLoad"), 0))
       DebugBreak();
    DbgInitModuleSettings();
    DbgInitGlobalSettings();
    dwTimeOffset = timeGetTime();
}

/* This is called to clear up any resources the debug library uses - at the
   moment we delete our critical section and the object list. The values we
   retrieve from the registry are all done during initialisation but we don't
   go looking for update notifications while we are running, if the values
   are changed then the application has to be restarted to pick them up */

void WINAPI DbgTerminate()
{
    if (m_hOutput != INVALID_HANDLE_VALUE) {
       EXECUTE_ASSERT(CloseHandle(m_hOutput));
       m_hOutput = INVALID_HANDLE_VALUE;
    }
    DeleteCriticalSection(&m_CSDebug);
    m_bInit = FALSE;
}

/* This is called by DbgInitLogLevels to read the debug settings
   for each logging category for this module from the registry */

void WINAPI DbgInitKeyLevels(HKEY hKey)
{
    LONG lReturn;               // Create key return value
    LONG lKeyPos;               // Current key category
    DWORD dwKeySize;            // Size of the key value
    DWORD dwKeyType;            // Receives it's type
    DWORD dwKeyValue;           // This fields value

    /* Try and read a value for each key position in turn */
    for (lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) {

        dwKeySize = sizeof(DWORD);
        lReturn = RegQueryValueEx(
            hKey,                       // Handle to an open key
            pKeyNames[lKeyPos],         // Subkey name derivation
            NULL,                       // Reserved field
            &dwKeyType,                 // Returns the field type
            (LPBYTE) &dwKeyValue,       // Returns the field's value
            &dwKeySize );               // Number of bytes transferred

        /* If either the key was not available or it was not a DWORD value
           then we ensure only the high priority debug logging is output
           but we try and update the field to a zero filled DWORD value */

        if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD)  {

            dwKeyValue = 0;
            lReturn = RegSetValueEx(
                hKey,                   // Handle of an open key
                pKeyNames[lKeyPos],     // Address of subkey name
                (DWORD) 0,              // Reserved field
                REG_DWORD,              // Type of the key field
                (PBYTE) &dwKeyValue,    // Value for the field
                sizeof(DWORD));         // Size of the field buffer

            if (lReturn != ERROR_SUCCESS) {
                DbgLog((LOG_ERROR,0,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos]));
                dwKeyValue = 0;
            }
        }
        m_Levels[lKeyPos] = max(dwKeyValue,m_Levels[lKeyPos]);
    }

    /*  Read the timeout value for catching hangs */
    dwKeySize = sizeof(DWORD);
    lReturn = RegQueryValueEx(
        hKey,                       // Handle to an open key
        TimeoutName,                // Subkey name derivation
        NULL,                       // Reserved field
        &dwKeyType,                 // Returns the field type
        (LPBYTE) &dwWaitTimeout,    // Returns the field's value
        &dwKeySize );               // Number of bytes transferred

    /* If either the key was not available or it was not a DWORD value
       then we ensure only the high priority debug logging is output
       but we try and update the field to a zero filled DWORD value */

    if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD)  {

        dwWaitTimeout = INFINITE;
        lReturn = RegSetValueEx(
            hKey,                   // Handle of an open key
            TimeoutName,            // Address of subkey name
            (DWORD) 0,              // Reserved field
            REG_DWORD,              // Type of the key field
            (PBYTE) &dwWaitTimeout, // Value for the field
            sizeof(DWORD));         // Size of the field buffer

        if (lReturn != ERROR_SUCCESS) {
            DbgLog((LOG_ERROR,0,TEXT("Could not create subkey %s"),pKeyNames[lKeyPos]));
            dwWaitTimeout = INFINITE;
        }
    }
}

void WINAPI DbgOutString(LPCTSTR psz)
{
    if (m_hOutput != INVALID_HANDLE_VALUE) {
        UINT  cb = lstrlen(psz);
        DWORD dw;
        WriteFile (m_hOutput, psz, cb, &dw, NULL);
    } else {
        OutputDebugString (psz);
    }
}

/* Called by DbgInitGlobalSettings to setup alternate logging destinations
 */

void WINAPI DbgInitLogTo (
    HKEY hKey)
{
    LONG  lReturn;
    DWORD dwKeyType;
    DWORD dwKeySize;
    TCHAR szFile[MAX_PATH] = {0};
    static const TCHAR cszKey[] = TEXT("LogToFile");

    dwKeySize = MAX_PATH;
    lReturn = RegQueryValueEx(
        hKey,                       // Handle to an open key
        cszKey,                     // Subkey name derivation
        NULL,                       // Reserved field
        &dwKeyType,                 // Returns the field type
        (LPBYTE) szFile,            // Returns the field's value
        &dwKeySize);                // Number of bytes transferred

    // create an empty key if it does not already exist
    //
    if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ)
       {
       dwKeySize = 1;
       lReturn = RegSetValueEx(
            hKey,                   // Handle of an open key
            cszKey,                 // Address of subkey name
            (DWORD) 0,              // Reserved field
            REG_SZ,                 // Type of the key field
            (PBYTE)szFile,          // Value for the field
            dwKeySize);            // Size of the field buffer
       }

    // if an output-to was specified.  try to open it.
    //
    if (m_hOutput != INVALID_HANDLE_VALUE) {
       EXECUTE_ASSERT(CloseHandle (m_hOutput));
       m_hOutput = INVALID_HANDLE_VALUE;
    }
    if (szFile[0] != 0)
       {
       if (!lstrcmpi(szFile, TEXT("Console"))) {
          m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
          if (m_hOutput == INVALID_HANDLE_VALUE) {
             AllocConsole ();
             m_hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
          }
          SetConsoleTitle (TEXT("ActiveX Debug Output"));
       } else if (szFile[0] &&
                lstrcmpi(szFile, TEXT("Debug")) &&
                lstrcmpi(szFile, TEXT("Debugger")) &&
                lstrcmpi(szFile, TEXT("Deb")))
          {
          m_hOutput = CreateFile(szFile, GENERIC_WRITE,
                                 FILE_SHARE_READ,
                                 NULL, OPEN_ALWAYS,
                                 FILE_ATTRIBUTE_NORMAL,
                                 NULL);
          if (INVALID_HANDLE_VALUE != m_hOutput)
              {
              static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n");
              SetFilePointer (m_hOutput, 0, NULL, FILE_END);
              DbgOutString (cszBar);
              }
          }
       }
}

/* This is called by DbgInitLogLevels to read the global debug settings for
   each logging category for this module from the registry. Normally each
   module has it's own values set for it's different debug categories but
   setting the global SOFTWARE\Debug\Global applies them to ALL modules */

void WINAPI DbgInitGlobalSettings()
{
    LONG lReturn;               // Create key return value
    TCHAR szInfo[iDEBUGINFO];   // Constructs key names
    HKEY hGlobalKey;            // Global override key

    /* Construct the global base key name */
    wsprintf(szInfo,TEXT("%s\\%s"),pBaseKey,pGlobalKey);

    /* Create or open the key for this module */
    lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE,   // Handle of an open key
                             szInfo,               // Address of subkey name
                             (DWORD) 0,            // Reserved value
                             NULL,                 // Address of class name
                             (DWORD) 0,            // Special options flags
                             KEY_ALL_ACCESS,       // Desired security access
                             NULL,                 // Key security descriptor
                             &hGlobalKey,          // Opened handle buffer
                             NULL);                // What really happened

    if (lReturn != ERROR_SUCCESS) {
        DbgLog((LOG_ERROR,0,TEXT("Could not access GLOBAL module key")));
        return;
    }

    DbgInitKeyLevels(hGlobalKey);
    RegCloseKey(hGlobalKey);
}

/* This sets the debugging log levels for the different categories. We start
   by opening (or creating if not already available) the SOFTWARE\Debug key
   that all these settings live under. We then look at the global values
   set under SOFTWARE\Debug\Global which apply on top of the individual
   module settings. We then load the individual module registry settings */

void WINAPI DbgInitModuleSettings()
{
    LONG lReturn;               // Create key return value
    TCHAR szInfo[iDEBUGINFO];   // Constructs key names
    HKEY hModuleKey;            // Module key handle

    /* Construct the base key name */
    wsprintf(szInfo,TEXT("%s\\%s"),pBaseKey,m_ModuleName);

    /* Create or open the key for this module */
    lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE,   // Handle of an open key
                             szInfo,               // Address of subkey name
                             (DWORD) 0,            // Reserved value
                             NULL,                 // Address of class name
                             (DWORD) 0,            // Special options flags
                             KEY_ALL_ACCESS,       // Desired security access
                             NULL,                 // Key security descriptor
                             &hModuleKey,          // Opened handle buffer
                             NULL);                // What really happened

    if (lReturn != ERROR_SUCCESS) {
        DbgLog((LOG_ERROR,0,TEXT("Could not access module key")));
        return;
    }

    DbgInitLogTo(hModuleKey);
    DbgInitKeyLevels(hModuleKey);
    RegCloseKey(hModuleKey);
}

/* Initialise the module file name */

void WINAPI DbgInitModuleName()
{
    TCHAR FullName[iDEBUGINFO];     // Load the full path and module name
    TCHAR *pName;                   // Searches from the end for a backslash

    GetModuleFileName(m_hInst,FullName,iDEBUGINFO);
    pName = _tcsrchr(FullName,'\\');
    if (pName == NULL) {
        pName = FullName;
    } else {
        pName++;
    }
    lstrcpy(m_ModuleName,pName);
}

/* Displays a message box if the condition evaluated to FALSE */

void WINAPI DbgAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine)
{
    TCHAR szInfo[iDEBUGINFO];

    wsprintf(szInfo, TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"),
             pCondition, iLine, pFileName);

    INT MsgId = MessageBox(NULL,szInfo,TEXT("ASSERT Failed"),
                           MB_SYSTEMMODAL |
                           MB_ICONHAND |
                           MB_YESNOCANCEL |
                           MB_SETFOREGROUND);
    switch (MsgId)
    {
        case IDNO:              /* Kill the application */

            FatalAppExit(FALSE, TEXT("Application terminated"));
            break;

        case IDCANCEL:          /* Break into the debugger */

            DebugBreak();
            break;

        case IDYES:             /* Ignore assertion continue execution */
            break;
    }
}

/* Displays a message box at a break point */

void WINAPI DbgBreakPoint(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine)
{
    TCHAR szInfo[iDEBUGINFO];

    wsprintf(szInfo, TEXT("%s \nAt line %d of %s\nContinue? (Cancel to debug)"),
             pCondition, iLine, pFileName);

    INT MsgId = MessageBox(NULL,szInfo,TEXT("Hard coded break point"),
                           MB_SYSTEMMODAL |
                           MB_ICONHAND |
                           MB_YESNOCANCEL |
                           MB_SETFOREGROUND);
    switch (MsgId)
    {
        case IDNO:              /* Kill the application */

            FatalAppExit(FALSE, TEXT("Application terminated"));
            break;

        case IDCANCEL:          /* Break into the debugger */

            DebugBreak();
            break;

        case IDYES:             /* Ignore break point continue execution */
            break;
    }
}

/* When we initialised the library we stored in the m_Levels array the current
   debug output level for this module for each of the five categories. When
   some debug logging is sent to us it can be sent with a combination of the
   categories (if it is applicable to many for example) in which case we map
   the type's categories into their current debug levels and see if any of
   them can be accepted. The function looks at each bit position in turn from
   the input type field and then compares it's debug level with the modules.

   A level of 0 means that output is always sent to the debugger.  This is
   due to producing output if the input level is <= m_Levels.
*/

BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level)
{
    DWORD Mask = 0x01;

    // If no valid bits are set return FALSE
    if ((Type & ((1<<iMAXLEVELS)-1))) {

// speed up unconditional output.
if (0==Level)
    return(TRUE);

        for (LONG lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) {
            if (Type & Mask) {
                if (Level <= m_Levels[lKeyPos]) {
                    return TRUE;
                }
            }
            Mask <<= 1;
        }
    }
    return FALSE;
}

/* Set debug levels to a given value */

void WINAPI DbgSetModuleLevel(DWORD Type, DWORD Level)
{
    DWORD Mask = 0x01;

    for (LONG lKeyPos = 0;lKeyPos < iMAXLEVELS;lKeyPos++) {
        if (Type & Mask) {
            m_Levels[lKeyPos] = Level;
        }
        Mask <<= 1;
    }
}

/* Print a formatted string to the debugger prefixed with this module's name
   Because the COMBASE classes are linked statically every module loaded will
   have their own copy of this code. It therefore helps if the module name is
   included on the output so that the offending code can be easily found */

void WINAPI DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...)
{
    /* Check the current level for this type combination */

    BOOL bAccept = DbgCheckModuleLevel(Type,Level);
    if (bAccept == FALSE) {
        return;
    }

    TCHAR szInfo[2000];

    /* Format the variable length parameter list */

    va_list va;
    va_start(va, pFormat);

    lstrcpy(szInfo,m_ModuleName);
    wsprintf(szInfo + lstrlen(szInfo),
             TEXT("(tid %x) %8d : "),
             GetCurrentThreadId(), timeGetTime() - dwTimeOffset);

    wvsprintf(szInfo + lstrlen(szInfo), pFormat, va);
    lstrcat(szInfo, TEXT("\r\n"));
    DbgOutString(szInfo);

    va_end(va);
}

/* If we are executing as a pure kernel filter we cannot display message
   boxes to the user, this provides an alternative which puts the error
   condition on the debugger output with a suitable eye catching message */

void WINAPI DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine)
{
    DbgLog((LOG_ERROR,0,TEXT("Assertion FAILED (%s) at line %d in file %s"),
           pCondition, iLine, pFileName));
    DebugBreak();
}

/* Each time we create an object derived from CBaseObject the constructor will
   call us to register the creation of the new object. We are passed a string
   description which we store away. We return a cookie that the constructor
   uses to identify the object when it is destroyed later on. We update the
   total number of active objects in the DLL mainly for debugging purposes */

DWORD WINAPI DbgRegisterObjectCreation(const TCHAR *pObjectName)
{
    /* If this fires you have a mixed DEBUG/RETAIL build */

    ASSERT(pObjectName);

    /* Create a place holder for this object description */

    ObjectDesc *pObject = new ObjectDesc;
    ASSERT(pObject);

    /* It is valid to pass a NULL object name */
    if (pObject == NULL) {
        return FALSE;
    }

    /* Check we have been initialised - we may not be initialised when we are
       being pulled in from an executable which has globally defined objects
       as they are created by the C++ run time before WinMain is called */

    if (m_bInit == FALSE) {
        DbgInitialise(GetModuleHandle(NULL));
    }

    /* Grab the list critical section */
    EnterCriticalSection(&m_CSDebug);

    /* If no name then default to UNKNOWN */
    if (pObjectName == NULL) {
        pObjectName = pUnknownName;
    }

    /* Put the new description at the head of the list */

    pObject->m_pName = pObjectName;
    pObject->m_dwCookie = ++m_dwNextCookie;
    pObject->m_pNext = pListHead;

    pListHead = pObject;
    m_dwObjectCount++;

    DWORD ObjectCookie = pObject->m_dwCookie;
    ASSERT(ObjectCookie);

    DbgLog((LOG_MEMORY,2,TEXT("Object created   %d (%s) %d Active"),
            pObject->m_dwCookie, pObjectName, m_dwObjectCount));

    LeaveCriticalSection(&m_CSDebug);
    return ObjectCookie;
}

/* This is called by the CBaseObject destructor when an object is about to be
   destroyed, we are passed the cookie we returned during construction that
   identifies this object. We scan the object list for a matching cookie and
   remove the object if successful. We also update the active object count */

BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie)
{
    /* Grab the list critical section */
    EnterCriticalSection(&m_CSDebug);

    ObjectDesc *pObject = pListHead;
    ObjectDesc *pPrevious = NULL;

    /* Scan the object list looking for a cookie match */

    while (pObject) {
        if (pObject->m_dwCookie == dwCookie) {
            break;
        }
        pPrevious = pObject;
        pObject = pObject->m_pNext;
    }

    if (pObject == NULL) {
        DbgBreak("Apparently destroying a bogus object");
        LeaveCriticalSection(&m_CSDebug);
        return FALSE;
    }

    /* Is the object at the head of the list */

    if (pPrevious == NULL) {
        pListHead = pObject->m_pNext;
    } else {
        pPrevious->m_pNext = pObject->m_pNext;
    }

    /* Delete the object and update the housekeeping information */

    m_dwObjectCount--;
    DbgLog((LOG_MEMORY,2,TEXT("Object destroyed %d (%s) %d Active"),
            pObject->m_dwCookie, pObject->m_pName, m_dwObjectCount));

    delete pObject;
    LeaveCriticalSection(&m_CSDebug);
    return TRUE;
}

/* This runs through the active object list displaying their details */

void WINAPI DbgDumpObjectRegister()
{
    TCHAR szInfo[iDEBUGINFO];

    /* Grab the list critical section */

    EnterCriticalSection(&m_CSDebug);
    ObjectDesc *pObject = pListHead;

    /* Scan the object list displaying the name and cookie */

    DbgLog((LOG_MEMORY,2,TEXT("")));
    DbgLog((LOG_MEMORY,2,TEXT("   ID             Object Description")));
    DbgLog((LOG_MEMORY,2,TEXT("")));

    while (pObject) {
        wsprintf(szInfo,TEXT("%5d (%8x) %30s"),pObject->m_dwCookie, &pObject, pObject->m_pName);
        DbgLog((LOG_MEMORY,2,szInfo));
        pObject = pObject->m_pNext;
    }

    wsprintf(szInfo,TEXT("Total object count %5d"),m_dwObjectCount);
    DbgLog((LOG_MEMORY,2,TEXT("")));
    DbgLog((LOG_MEMORY,1,szInfo));
    LeaveCriticalSection(&m_CSDebug);
}

/*  Debug infinite wait stuff */
DWORD WINAPI DbgWaitForSingleObject(HANDLE h)
{
    DWORD dwWaitResult;
    do {
        dwWaitResult = WaitForSingleObject(h, dwWaitTimeout);
        ASSERT(dwWaitResult == WAIT_OBJECT_0);
    } while (dwWaitResult == WAIT_TIMEOUT);
    return dwWaitResult;
}
DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount,
                                CONST HANDLE *lpHandles,
                                BOOL bWaitAll)
{
    DWORD dwWaitResult;
    do {
        dwWaitResult = WaitForMultipleObjects(nCount,
                                              lpHandles,
                                              bWaitAll,
                                              dwWaitTimeout);
        ASSERT((DWORD)(dwWaitResult - WAIT_OBJECT_0) < MAXIMUM_WAIT_OBJECTS);
    } while (dwWaitResult == WAIT_TIMEOUT);
    return dwWaitResult;
}

void WINAPI DbgSetWaitTimeout(DWORD dwTimeout)
{
    dwWaitTimeout = dwTimeout;
}

#endif /* DEBUG */

#ifdef _OBJBASE_H_

    /*  Stuff for printing out our GUID names */

    GUID_STRING_ENTRY g_GuidNames[] = {
    #define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
    { TEXT(#name), { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } } },
        #include <uuids.h>
    };

    CGuidNameList GuidNames;
    int g_cGuidNames = sizeof(g_GuidNames) / sizeof(g_GuidNames[0]);

    TCHAR *CGuidNameList::operator [] (const GUID &guid)
    {
        for (int i = 0; i < g_cGuidNames; i++) {
            if (g_GuidNames[i].guid == guid) {
                return g_GuidNames[i].szName;
            }
        }
        if (guid == GUID_NULL) {
            return TEXT("GUID_NULL");
        }

// !!! add something to print FOURCC guids?

// shouldn't this print the hex CLSID?
        return TEXT("Unknown GUID Name");
    }

#endif /* _OBJBASE_H_ */

/*  CDisp class - display our data types */

// clashes with REFERENCE_TIME
CDisp::CDisp(LONGLONG ll, int Format)
{
    // note: this could be combined with CDisp(LONGLONG) by
    // introducing a default format of CDISP_REFTIME
    LARGE_INTEGER li;
    li.QuadPart = ll;
    switch (Format) {
case CDISP_DEC:
{
    TCHAR  temp[20];
    int pos=20;
    temp[--pos] = 0;
    int digit;
    // always output at least one digit
    do {
// Get the rightmost digit - we only need the low word
        digit = li.LowPart % 10;
li.QuadPart /= 10;
temp[--pos] = (TCHAR) digit+L'0';
    } while (li.QuadPart);
    wsprintf(m_String, TEXT("%s"), temp+pos);
    break;
}
case CDISP_HEX:
default:
    wsprintf(m_String, TEXT("0x%X%8.8X"), li.HighPart, li.LowPart);
    }
};

CDisp::CDisp(REFCLSID clsid)
{
    WCHAR strClass[CHARS_IN_GUID+1];
    StringFromGUID2(clsid, strClass, sizeof(strClass) / sizeof(strClass[0]));
    ASSERT(sizeof(m_String)/sizeof(m_String[0]) >= CHARS_IN_GUID+1);
    wsprintf(m_String, TEXT("%ls"), strClass);
};

/*  Display stuff */
CDisp::CDisp(CRefTime llTime)
{
    LPTSTR lpsz = m_String;
    LONGLONG llDiv;
    if (llTime < 0) {
        llTime = -llTime;
        lpsz += wsprintf(lpsz, TEXT("-"));
    }
    llDiv = (LONGLONG)24 * 3600 * 10000000;
    if (llTime >= llDiv) {
        lpsz += wsprintf(lpsz, TEXT("%d days "), (LONG)(llTime / llDiv));
        llTime = llTime % llDiv;
    }
    llDiv = (LONGLONG)3600 * 10000000;
    if (llTime >= llDiv) {
        lpsz += wsprintf(lpsz, TEXT("%d hrs "), (LONG)(llTime / llDiv));
        llTime = llTime % llDiv;
    }
    llDiv = (LONGLONG)60 * 10000000;
    if (llTime >= llDiv) {
        lpsz += wsprintf(lpsz, TEXT("%d mins "), (LONG)(llTime / llDiv));
        llTime = llTime % llDiv;
    }
    wsprintf(lpsz, TEXT("%d.%3.3d sec"),
             (LONG)llTime / 10000000,
             (LONG)((llTime % 10000000) / 10000));
};

/*  Display pin */
CDisp::CDisp(IPin *pPin)
{
    PIN_INFO pi;
    TCHAR str[MAX_PIN_NAME];
    if (pPin) {
       pPin->QueryPinInfo(&pi);
       QueryPinInfoReleaseFilter(pi);
      #ifndef UNICODE
       WideCharToMultiByte(GetACP(), 0, pi.achName, lstrlenW(pi.achName) + 1,
                           str, MAX_PIN_NAME, NULL, NULL);
      #else
       lstrcpy(str, pi.achName);
      #endif
    } else {
       lstrcpy(str, TEXT("NULL IPin"));
    }

    m_pString = (PTCHAR) new TCHAR[lstrlen(str)+64];
    if (!m_pString) {
return;
    }

    CLSID clsid;
    pi.pFilter->GetClassID(&clsid);
    wsprintf(m_pString, TEXT("%s(%s)"), GuidNames[clsid], str);
}

CDisp::~CDisp()
{
}

CDispBasic::~CDispBasic()
{
    if (m_pString != m_String) {
delete [] m_pString;
    }
}

CDisp::CDisp(double d)
{
#ifdef DEBUG
    _stprintf(m_String, TEXT("%.16g"), d);
#else
    wsprintf(m_String, TEXT("%d.%03d"), (int) d, (int) ((d - (int) d) * 1000));
#endif
}

/* If built for debug this will display the media type details. We convert the
   major and subtypes into strings and also ask the base classes for a string
   description of the subtype, so MEDIASUBTYPE_RGB565 becomes RGB 565 16 bit
   We also display the fields in the BITMAPINFOHEADER structure, this should
   succeed as we do not accept input types unless the format is big enough */

#ifdef DEBUG
void WINAPI DisplayType(LPSTR label, const AM_MEDIA_TYPE *pmtIn)
{

    /* Dump the GUID types and a short description */

    DbgLog((LOG_TRACE,5,TEXT("")));
    DbgLog((LOG_TRACE,2,TEXT("%hs  M type %s  S type %s"), label,
    GuidNames[pmtIn->majortype],
    GuidNames[pmtIn->subtype]));
    DbgLog((LOG_TRACE,5,TEXT("Subtype description %s"),GetSubtypeName(&pmtIn->subtype)));

    /* Dump the generic media types */

    if (pmtIn->bTemporalCompression) {
DbgLog((LOG_TRACE,5,TEXT("Temporally compressed")));
    } else {
DbgLog((LOG_TRACE,5,TEXT("Not temporally compressed")));
    }

    if (pmtIn->bFixedSizeSamples) {
DbgLog((LOG_TRACE,5,TEXT("Sample size %d"),pmtIn->lSampleSize));
    } else {
DbgLog((LOG_TRACE,5,TEXT("Variable size samples")));
    }

    if (pmtIn->formattype == FORMAT_VideoInfo) {
/* Dump the contents of the BITMAPINFOHEADER structure */

BITMAPINFOHEADER *pbmi = HEADER(pmtIn->pbFormat);
VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *)pmtIn->pbFormat;

DbgLog((LOG_TRACE,5,TEXT("Source rectangle (Left %d Top %d Right %d Bottom %d)"),
       pVideoInfo->rcSource.left,
       pVideoInfo->rcSource.top,
       pVideoInfo->rcSource.right,
       pVideoInfo->rcSource.bottom));

DbgLog((LOG_TRACE,5,TEXT("Target rectangle (Left %d Top %d Right %d Bottom %d)"),
       pVideoInfo->rcTarget.left,
       pVideoInfo->rcTarget.top,
       pVideoInfo->rcTarget.right,
       pVideoInfo->rcTarget.bottom));

DbgLog((LOG_TRACE,5,TEXT("Size of BITMAPINFO structure %d"),pbmi->biSize));
if (pbmi->biCompression < 256) {
    DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit  (%d)"),
    pbmi->biWidth, pbmi->biHeight,
    pbmi->biBitCount, pbmi->biCompression));
} else {
    DbgLog((LOG_TRACE,2,TEXT("%dx%dx%d bit '%4.4hs'"),
    pbmi->biWidth, pbmi->biHeight,
    pbmi->biBitCount, &pbmi->biCompression));
}

DbgLog((LOG_TRACE,2,TEXT("Image size %d"),pbmi->biSizeImage));
DbgLog((LOG_TRACE,5,TEXT("Planes %d"),pbmi->biPlanes));
DbgLog((LOG_TRACE,5,TEXT("X Pels per metre %d"),pbmi->biXPelsPerMeter));
DbgLog((LOG_TRACE,5,TEXT("Y Pels per metre %d"),pbmi->biYPelsPerMeter));
DbgLog((LOG_TRACE,5,TEXT("Colours used %d"),pbmi->biClrUsed));

    } else if (pmtIn->majortype == MEDIATYPE_Audio) {
DbgLog((LOG_TRACE,2,TEXT("     Format type %s"),
    GuidNames[pmtIn->formattype]));
DbgLog((LOG_TRACE,2,TEXT("     Subtype %s"),
    GuidNames[pmtIn->subtype]));

if ((pmtIn->subtype != MEDIASUBTYPE_MPEG1Packet)
  && (pmtIn->cbFormat >= sizeof(PCMWAVEFORMAT)))
{
    /* Dump the contents of the WAVEFORMATEX type-specific format structure */

    WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmtIn->pbFormat;
            DbgLog((LOG_TRACE,2,TEXT("wFormatTag %u"), pwfx->wFormatTag));
            DbgLog((LOG_TRACE,2,TEXT("nChannels %u"), pwfx->nChannels));
            DbgLog((LOG_TRACE,2,TEXT("nSamplesPerSec %lu"), pwfx->nSamplesPerSec));
            DbgLog((LOG_TRACE,2,TEXT("nAvgBytesPerSec %lu"), pwfx->nAvgBytesPerSec));
            DbgLog((LOG_TRACE,2,TEXT("nBlockAlign %u"), pwfx->nBlockAlign));
            DbgLog((LOG_TRACE,2,TEXT("wBitsPerSample %u"), pwfx->wBitsPerSample));

            /* PCM uses a WAVEFORMAT and does not have the extra size field */

            if (pmtIn->cbFormat >= sizeof(WAVEFORMATEX)) {
                DbgLog((LOG_TRACE,2,TEXT("cbSize %u"), pwfx->cbSize));
            }
} else {
}

    } else {
DbgLog((LOG_TRACE,2,TEXT("     Format type %s"),
    GuidNames[pmtIn->formattype]));
// !!!! should add code to dump wave format, others
    }
}

void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel)
{
    IEnumFilters *pFilters;

    DbgLog((LOG_TRACE,dwLevel,TEXT("DumpGraph [%x]"), pGraph));

    if (FAILED(pGraph->EnumFilters(&pFilters))) {
DbgLog((LOG_TRACE,dwLevel,TEXT("EnumFilters failed!")));
    }

    IBaseFilter *pFilter;
    ULONGn;
    while (pFilters->Next(1, &pFilter, &n) == S_OK) {
FILTER_INFOinfo;

if (FAILED(pFilter->QueryFilterInfo(&info))) {
    DbgLog((LOG_TRACE,dwLevel,TEXT("    Filter [%x]  -- failed QueryFilterInfo"), pFilter));
} else {
    QueryFilterInfoReleaseGraph(info);

    // !!! should QueryVendorInfo here!

    DbgLog((LOG_TRACE,dwLevel,TEXT("    Filter [%x]  '%ls'"), pFilter, info.achName));

    IEnumPins *pins;

    if (FAILED(pFilter->EnumPins(&pins))) {
DbgLog((LOG_TRACE,dwLevel,TEXT("EnumPins failed!")));
    } else {

IPin *pPin;
while (pins->Next(1, &pPin, &n) == S_OK) {
    PIN_INFOinfo;

    if (FAILED(pPin->QueryPinInfo(&info))) {
DbgLog((LOG_TRACE,dwLevel,TEXT("          Pin [%x]  -- failed QueryPinInfo"), pPin));
    } else {
QueryPinInfoReleaseFilter(info);

IPin *pPinConnected = NULL;

HRESULT hr = pPin->ConnectedTo(&pPinConnected);

if (pPinConnected) {
    DbgLog((LOG_TRACE,dwLevel,TEXT("          Pin [%x]  '%ls' [%sput]")
   TEXT("  Connected to pin [%x]"),
    pPin, info.achName,
    info.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out"),
    pPinConnected));

    pPinConnected->Release();

    // perhaps we should really dump the type both ways as a sanity
    // check?
    if (info.dir == PINDIR_OUTPUT) {
AM_MEDIA_TYPE mt;

hr = pPin->ConnectionMediaType(&mt);

if (SUCCEEDED(hr)) {
    DisplayType("Connection type", &mt);

    FreeMediaType(mt);
}
    }
} else {
    DbgLog((LOG_TRACE,dwLevel,
    TEXT("          Pin [%x]  '%ls' [%sput]"),
    pPin, info.achName,
    info.dir == PINDIR_INPUT ? TEXT("In") : TEXT("Out")));

}
    }

    pPin->Release();

}

pins->Release();
    }

}

pFilter->Release();
    }

    pFilters->Release();

}

#endif


WXDEBUG.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Debugging facilities, January 1995

#ifndef __WXDEBUG__
#define __WXDEBUG__

// This library provides fairly straight forward debugging functionality, this
// is split into two main sections. The first is assertion handling, there are
// three types of assertions provided here. The most commonly used one is the
// ASSERT(condition) macro which will pop up a message box including the file
// and line number if the condition evaluates to FALSE. Then there is the
// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will
// still be executed in NON debug builds. The final type of assertion is the
// KASSERT macro which is more suitable for pure (perhaps kernel) filters as
// the condition is printed onto the debugger rather than in a message box.
//
// The other part of the debug module facilties is general purpose logging.
// This is accessed by calling DbgLog(). The function takes a type and level
// field which define the type of informational string you are presenting and
// it's relative importance. The type field can be a combination (one or more)
// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level
// is a DWORD value where zero defines highest important. Use of zero as the
// debug logging level is to be encouraged ONLY for major errors or events as
// they will ALWAYS be displayed on the debugger. Other debug output has it's
// level matched against the current debug output level stored in the registry
// for this module and if less than the current setting it will be displayed.
//
// Each module or executable has it's own debug output level for each of the
// five types. These are read in when the DbgInitialise function is called
// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL
// is loaded, executables must call it explicitely with the module instance
// handle given to them through the WINMAIN entry point. An executable must
// also call DbgTerminate when they have finished to clean up the resources
// the debug library uses, once again this is done automatically for DLLs

// These are the five different categories of logging information

enum {  LOG_TIMING = 0x01,    // Timing and performance measurements
        LOG_TRACE = 0x02,     // General step point call tracing
        LOG_MEMORY =  0x04,   // Memory and object allocation/destruction
        LOG_LOCKING = 0x08,   // Locking/unlocking of critical sections
        LOG_ERROR = 0x10 };   // Debug error notification

enum {  CDISP_HEX = 0x01,
        CDISP_DEC = 0x02};

// For each object created derived from CBaseObject (in debug builds) we
// create a descriptor that holds it's name (statically allocated memory)
// and a cookie we assign it. We keep a list of all the active objects
// we have registered so that we can dump a list of remaining objects

typedef struct tag_ObjectDesc {
    const TCHAR *m_pName;
    DWORD m_dwCookie;
    tag_ObjectDesc *m_pNext;
} ObjectDesc;

#define DLLIMPORT __declspec(dllimport)
#define DLLEXPORT __declspec(dllexport)

#ifdef DEBUG

    #define NAME(x) TEXT(x)

    // These are used internally by the debug library (PRIVATE)

    void WINAPI DbgInitKeyLevels(HKEY hKey);
    void WINAPI DbgInitGlobalSettings();
    void WINAPI DbgInitModuleSettings();
    void WINAPI DbgInitModuleName();
    DWORD WINAPI DbgRegisterObjectCreation(const TCHAR *pObjectName);
    BOOL WINAPI DbgRegisterObjectDestruction(DWORD dwCookie);

    // These are the PUBLIC entry points

    BOOL WINAPI DbgCheckModuleLevel(DWORD Type,DWORD Level);
    void WINAPI DbgSetModuleLevel(DWORD Type,DWORD Level);

    // Initialise the library with the module handle

    void WINAPI DbgInitialise(HINSTANCE hInst);
    void WINAPI DbgTerminate();

    void WINAPI DbgDumpObjectRegister();

    // Display error and logging to the user

    void WINAPI DbgAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine);
    void WINAPI DbgBreakPoint(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine);
    void WINAPI DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine);
    void WINAPI DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...);
    void WINAPI DbgOutString(LPCTSTR psz);

    //  Debug infinite wait stuff
    DWORD WINAPI DbgWaitForSingleObject(HANDLE h);
    DWORD WINAPI DbgWaitForMultipleObjects(DWORD nCount,
                                    CONST HANDLE *lpHandles,
                                    BOOL bWaitAll);
    void WINAPI DbgSetWaitTimeout(DWORD dwTimeout);

#ifdef __strmif_h__
    // Display a media type: Terse at level 2, verbose at level 5
    void WINAPI DisplayType(LPSTR label, const AM_MEDIA_TYPE *pmtIn);

    // Dump lots of information about a filter graph
    void WINAPI DumpGraph(IFilterGraph *pGraph, DWORD dwLevel);
#endif

    #define KASSERT(_x_) if (!(_x_))         \
        DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__)

    //  Break on the debugger without putting up a message box
    //  message goes to debugger instead

    #define KDbgBreak(_x_)                   \
        DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__)

    // We chose a common name for our ASSERT macro, MFC also uses this name
    // So long as the implementation evaluates the condition and handles it
    // then we will be ok. Rather than override the behaviour expected we
    // will leave whatever first defines ASSERT as the handler (i.e. MFC)
    #ifndef ASSERT
        #define ASSERT(_x_) if (!(_x_))         \
            DbgAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__)
    #endif

    //  Put up a message box informing the user of a halt
    //  condition in the program

    #define DbgBreak(_x_)                   \
        DbgBreakPoint(TEXT(#_x_),TEXT(__FILE__),__LINE__)

    #define EXECUTE_ASSERT(_x_) ASSERT(_x_)
    #define DbgLog(_x_) DbgLogInfo _x_

    // MFC style trace macros

    #define NOTE(_x_)             DbgLog((LOG_TRACE,5,TEXT(_x_)));
    #define NOTE1(_x_,a)          DbgLog((LOG_TRACE,5,TEXT(_x_),a));
    #define NOTE2(_x_,a,b)        DbgLog((LOG_TRACE,5,TEXT(_x_),a,b));
    #define NOTE3(_x_,a,b,c)      DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c));
    #define NOTE4(_x_,a,b,c,d)    DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d));
    #define NOTE5(_x_,a,b,c,d,e)  DbgLog((LOG_TRACE,5,TEXT(_x_),a,b,c,d,e));

#else

    // Retail builds make public debug functions inert  - WARNING the source
    // files do not define or build any of the entry points in debug builds
    // (public entry points compile to nothing) so if you go trying to call
    // any of the private entry points in your source they won't compile

    #define NAME(_x_) NULL

    #define DbgInitialise(hInst)
    #define DbgTerminate()
    #define DbgLog(_x_)
    #define DbgOutString(psz)

    #define DbgRegisterObjectCreation(pObjectName)
    #define DbgRegisterObjectDestruction(dwCookie)
    #define DbgDumpObjectRegister()

    #define DbgCheckModuleLevel(Type,Level)
    #define DbgSetModuleLevel(Type,Level)

    #define DbgWaitForSingleObject(h)  WaitForSingleObject(h, INFINITE)
    #define DbgWaitForMultipleObjects(nCount, lpHandles, bWaitAll)     \
               WaitForMultipleObjects(nCount, lpHandles, bWaitAll, INFINITE)
    #define DbgSetWaitTimeout(dwTimeout)

    #define KDbgBreak(_x_)
    #define DbgBreak(_x_)

    #define KASSERT(_x_)
    #ifndef ASSERT
#define ASSERT(_x_)
    #endif
    #define EXECUTE_ASSERT(_x_) _x_

    // MFC style trace macros

    #define NOTE(_x_)
    #define NOTE1(_x_,a)
    #define NOTE2(_x_,a,b)
    #define NOTE3(_x_,a,b,c)
    #define NOTE4(_x_,a,b,c,d)
    #define NOTE5(_x_,a,b,c,d,e)

    #define DisplayType(label, pmtIn)
    #define DumpGraph(pGraph, label)
#endif

// Checks a pointer which should be non NULL - can be used as follows.

#define CheckPointer(p,ret) {if((p)==NULL) return (ret);}

//   HRESULT Foo(VOID *pBar)
//   {
//       CheckPointer(pBar,E_INVALIDARG)
//   }
//
//   Or if the function returns a boolean
//
//   BOOL Foo(VOID *pBar)
//   {
//       CheckPointer(pBar,FALSE)
//   }

// These validate pointers when symbol VFWROBUST is defined
// This will normally be defined in debug not retail builds

#ifdef DEBUG
    #define VFWROBUST
#endif

#ifdef VFWROBUST

    #define ValidateReadPtr(p,cb) \
        {if(IsBadReadPtr((PVOID)p,cb) == TRUE) \
            DbgBreak("Invalid read pointer");}

    #define ValidateWritePtr(p,cb) \
        {if(IsBadWritePtr((PVOID)p,cb) == TRUE) \
            DbgBreak("Invalid write pointer");}

    #define ValidateReadWritePtr(p,cb) \
        {ValidateReadPtr(p,cb) ValidateWritePtr(p,cb)}

    #define ValidateStringPtr(p) \
        {if(IsBadStringPtr((LPCTSTR)p,INFINITE) == TRUE) \
            DbgBreak("Invalid string pointer");}

    #define ValidateStringPtrA(p) \
        {if(IsBadStringPtrA((LPCSTR)p,INFINITE) == TRUE) \
            DbgBreak("Invalid ANSII string pointer");}

    #define ValidateStringPtrW(p) \
        {if(IsBadStringPtrW((LPCWSTR)p,INFINITE) == TRUE) \
            DbgBreak("Invalid UNICODE string pointer");}

#else
    #define ValidateReadPtr(p,cb)
    #define ValidateWritePtr(p,cb)
    #define ValidateReadWritePtr(p,cb)
    #define ValidateStringPtr(p)
    #define ValidateStringPtrA(p)
    #define ValidateStringPtrW(p)
#endif

#ifdef _OBJBASE_H_

    //  Outputting GUID names.  If you want to include the name
    //  associated with a GUID (eg CLSID_...) then
    //
    //      GuidNames[yourGUID]
    //
    //  Returns the name defined in uuids.h as a string

    typedef struct {
        TCHAR   *szName;
        GUID    guid;
    } GUID_STRING_ENTRY;

    class CGuidNameList {
    public:
        TCHAR *operator [] (const GUID& guid);
    };

    extern CGuidNameList GuidNames;

#endif

#ifndef REMIND
    //  REMIND macro - generates warning as reminder to complete coding
    //  (eg) usage:
    //
    //  #pragma message (REMIND("Add automation support"))

    #define QUOTE(x) #x
    #define QQUOTE(y) QUOTE(y)
    #define REMIND(str) __FILE__ "(" QQUOTE(__LINE__) ") :  " str
#endif

//  Hack to display objects in a useful format
//
//  eg If you want to display a LONGLONG ll in a debug string do (eg)
//
//  DbgLog((LOG_TRACE, n, TEXT("Value is %s"), (LPCTSTR)CDisp(ll, CDISP_HEX)));

class CDispBasic
{
public:
    CDispBasic() { m_pString = m_String; };
    ~CDispBasic();
protected:
    PTCHAR m_pString;  // normally points to m_String... unless too much data
    TCHAR m_String[50];
};
class CDisp : public CDispBasic
{
public:
    CDisp(LONGLONG ll, int Format = CDISP_HEX); // Display a LONGLONG in CDISP_HEX or CDISP_DEC form
    CDisp(REFCLSID clsid);      // Display a GUID
    CDisp(double d);            // Display a floating point number
#ifdef __strmif_h__
#ifdef __STREAMS__
    CDisp(CRefTime t);          // Display a Reference Time
#endif
    CDisp(IPin *pPin);          // Display a pin as {filter clsid}(pin name)
#endif // __strmif_h__
    ~CDisp();

    //  Implement cast to (LPCTSTR) as parameter to logger
    operator LPCTSTR()
    {
        return (LPCTSTR)m_pString;
    };
};

#endif // __WXDEBUG__


WXLIST.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Non MFC based generic list template class, December 1994

/* A generic list of pointers to objects.
   Objectives: avoid using MFC libraries in ndm kernel mode and
   provide a really useful list type.

   The class is thread safe in that separate threads may add and
   delete items in the list concurrently although the application
   must ensure that constructor and destructor access is suitably
   synchronised.

   The list name must not conflict with MFC classes as an
   application may use both

   The nodes form a doubly linked, NULL terminated chain with an anchor
   block (the list object per se) holding pointers to the first and last
   nodes and a count of the nodes.
   There is a node cache to reduce the allocation and freeing overhead.
   It optionally (determined at construction time) has an Event which is
   set whenever the list becomes non-empty and reset whenever it becomes
   empty.
   It optionally (determined at construction time) has a Critical Section
   which is entered during the important part of each operation.  (About
   all you can do outside it is some parameter checking).

   The node cache is a repository of nodes that are NOT in the list to speed
   up storage allocation.  Each list has its own cache to reduce locking and
   serialising.  The list accesses are serialised anyway for a given list - a
   common cache would mean that we would have to separately serialise access
   of all lists within the cache.  Because the cache only stores nodes that are
   not in the list, releasing the cache does not release any list nodes.  This
   means that list nodes can be copied or rechained from one list to another
   without danger of creating a dangling reference if the original cache goes
   away.

   Questionable design decisions:
   1. Retaining the warts for compatibility
   2. Keeping an element count -i.e. counting whenever we do anything
      instead of only when we want the count.
   3. Making the chain pointers NULL terminated.  If the list object
      itself looks just like a node and the list is kept as a ring then
      it reduces the number of special cases.  All inserts look the same.
*/

#include <streams.h>

/* set cursor to the position of each element of list in turn  */
#define INTERNALTRAVERSELIST(list, cursor)               \
for ( cursor = (list).GetHeadPositionI()           \
    ; cursor!=NULL                               \
    ; cursor = (list).Next(cursor)                \
    )

/* set cursor to the position of each element of list in turn
   in reverse order
*/
#define INTERNALREVERSETRAVERSELIST(list, cursor)        \
for ( cursor = (list).GetTailPositionI()           \
    ; cursor!=NULL                               \
    ; cursor = (list).Prev(cursor)                \
    )

/* Constructor calls a separate initialisation function that
   creates a node cache, optionally creates a lock object
   and optionally creates a signaling object.

   By default we create a locking object, a DEFAULTCACHE sized
   cache but no event object so the list cannot be used in calls
   to WaitForSingleObject
*/
CBaseList::CBaseList(TCHAR *pName,    // Descriptive list name
                     INT iItems) :    // Node cache size
#ifdef DEBUG
    CBaseObject(pName),
#endif
    m_pFirst(NULL),
    m_pLast(NULL),
    m_Count(0),
    m_Cache(iItems)
{
} // constructor

CBaseList::CBaseList(TCHAR *pName) :  // Descriptive list name
#ifdef DEBUG
    CBaseObject(pName),
#endif
    m_pFirst(NULL),
    m_pLast(NULL),
    m_Count(0),
    m_Cache(DEFAULTCACHE)
{
} // constructor

/* The destructor enumerates all the node objects in the list and
   in the cache deleting each in turn. We do not do any processing
   on the objects that the list holds (i.e. points to) so if they
   represent interfaces for example the creator of the list should
   ensure that each of them is released before deleting us
*/
CBaseList::~CBaseList()
{
    /* Delete all our list nodes */

    RemoveAll();

} // destructor

/* Remove all the nodes from the list but don't do anything
   with the objects that each node looks after (this is the
   responsibility of the creator).
   Aa a last act we reset the signalling event
   (if available) to indicate to clients that the list
   does not have any entries in it.
*/
void CBaseList::RemoveAll()
{
    /* Free up all the CNode objects NOTE we don't bother putting the
       deleted nodes into the cache as this method is only really called
       in serious times of change such as when we are being deleted at
       which point the cache will be deleted anway */

    CNode *pn = m_pFirst;
    while (pn) {
        CNode *op = pn;
        pn = pn->Next();
        delete op;
    }

    /* Reset the object count and the list pointers */

    m_Count = 0;
    m_pFirst = m_pLast = NULL;

} // RemoveAll

/* Return a position enumerator for the entire list.
   A position enumerator is a pointer to a node object cast to a
   transparent type so all we do is return the head/tail node
   pointer in the list.
   WARNING because the position is a pointer to a node there is
   an implicit assumption for users a the list class that after
   deleting an object from the list that any other position
   enumerators that you have may be invalid (since the node
   may be gone).
*/
POSITION CBaseList::GetHeadPositionI() const
{
    return (POSITION) m_pFirst;
} // GetHeadPosition

POSITION CBaseList::GetTailPositionI() const
{
    return (POSITION) m_pLast;
} // GetTailPosition

/* Get the number of objects in the list,
   Get the lock before accessing the count.
   Locking may not be entirely necessary but it has the side effect
   of making sure that all operations are complete before we get it.
   So for example if a list is being added to this list then that
   will have completed in full before we continue rather than seeing
   an intermediate albeit valid state
*/
int CBaseList::GetCountI() const
{
    return m_Count;
} // GetCount

/* Return the object at rp, update rp to the next object from
   the list or NULL if you have moved over the last object.
   You may still call this function once we return NULL but
   we will continue to return a NULL position value
*/
void *CBaseList::GetNextI(POSITION& rp) const
{
    /* have we reached the end of the list */

    if (rp == NULL) {
        return NULL;
    }

    /* Lock the object before continuing */

    void *pObject;

    /* Copy the original position then step on */

    CNode *pn = (CNode *) rp;
    ASSERT(pn != NULL);
    rp = (POSITION) pn->Next();

    /* Get the object at the original position from the list */

    pObject = pn->GetData();
    // ASSERT(pObject != NULL);    // NULL pointers in the list are allowed.
    return pObject;
} //GetNext

/* Return the object at p.
   Asking for the object at NULL ASSERTs then returns NULL
   The object is NOT locked.  The list is not being changed
   in any way.  If another thread is busy deleting the object
   then locking would only result in a change from one bad
   behaviour to another.
*/
void *CBaseList::GetI(POSITION p) const
{
    if (p == NULL) {
        return NULL;
    }

    CNode * pn = (CNode *) p;
    void *pObject = pn->GetData();
    // ASSERT(pObject != NULL);    // NULL pointers in the list are allowed.
    return pObject;
} //Get

/* Return the first position in the list which holds the given pointer.
   Return NULL if it's not found.
*/
POSITION CBaseList::FindI( void * pObj) const
{
    POSITION pn;
    INTERNALTRAVERSELIST(*this, pn){
        if (GetI(pn)==pObj) {
            return pn;
        }
    }
    return NULL;
} // Find

/* Remove the first node in the list (deletes the pointer to its object
   from the list, does not free the object itself).
   Return the pointer to its object or NULL if empty
*/
void *CBaseList::RemoveHeadI()
{
    /* All we do is get the head position and ask for that to be deleted.
       We could special case this since some of the code path checking
       in Remove() is redundant as we know there is no previous
       node for example but it seems to gain little over the
       added complexity
    */

    return RemoveI((POSITION)m_pFirst);
} // RemoveHead

/* Remove the last node in the list (deletes the pointer to its object
   from the list, does not free the object itself).
   Return the pointer to its object or NULL if empty
*/
void *CBaseList::RemoveTailI()
{
    /* All we do is get the tail position and ask for that to be deleted.
       We could special case this since some of the code path checking
       in Remove() is redundant as we know there is no previous
       node for example but it seems to gain little over the
       added complexity
    */

    return RemoveI((POSITION)m_pLast);
} // RemoveTail

/* Remove the pointer to the object in this position from the list.
   Deal with all the chain pointers
   Return a pointer to the object removed from the list.
   The node object that is freed as a result
   of this operation is added to the node cache where
   it can be used again.
   Remove(NULL) is a harmless no-op - but probably is a wart.
*/
void *CBaseList::RemoveI(POSITION pos)
{
    /* Lock the critical section before continuing */

    // ASSERT (pos!=NULL);     // Removing NULL is to be harmless!
    if (pos==NULL) return NULL;

    CNode *pCurrent = (CNode *) pos;
    ASSERT(pCurrent != NULL);

    /* Update the previous node */

    CNode *pNode = pCurrent->Prev();
    if (pNode == NULL) {
        m_pFirst = pCurrent->Next();
    } else {
        pNode->SetNext(pCurrent->Next());
    }

    /* Update the following node */

    pNode = pCurrent->Next();
    if (pNode == NULL) {
        m_pLast = pCurrent->Prev();
    } else {
        pNode->SetPrev(pCurrent->Prev());
    }

    /* Get the object this node was looking after */

    void *pObject = pCurrent->GetData();

    // ASSERT(pObject != NULL);    // NULL pointers in the list are allowed.

    /* Try and add the node object to the cache -
       a NULL return code from the cache means we ran out of room.
       The cache size is fixed by a constructor argument when the
       list is created and defaults to DEFAULTCACHE.
       This means that the cache will have room for this many
       node objects. So if you have a list of media samples
       and you know there will never be more than five active at
       any given time of them for example then override the default
       constructor
    */

    m_Cache.AddToCache(pCurrent);

    /* If the list is empty then reset the list event */

    --m_Count;
    ASSERT(m_Count >= 0);
    return pObject;
} // Remove

/* Add this object to the tail end of our list
   Return the new tail position.
*/

POSITION CBaseList::AddTailI(void *pObject)
{
    /* Lock the critical section before continuing */

    CNode *pNode;
    // ASSERT(pObject);   // NULL pointers in the list are allowed.

    /* If there is a node objects in the cache then use
       that otherwise we will have to create a new one */

    pNode = (CNode *) m_Cache.RemoveFromCache();
    if (pNode == NULL) {
        pNode = new CNode;
    }

    /* Check we have a valid object */

    ASSERT(pNode != NULL);
    if (pNode == NULL) {
        return NULL;
    }

    /* Initialise all the CNode object
       just in case it came from the cache
    */

    pNode->SetData(pObject);
    pNode->SetNext(NULL);
    pNode->SetPrev(m_pLast);

    if (m_pLast == NULL) {
        m_pFirst = pNode;
    } else {
        m_pLast->SetNext(pNode);
    }

    /* Set the new last node pointer and also increment the number
       of list entries, the critical section is unlocked when we
       exit the function
    */

    m_pLast = pNode;
    ++m_Count;

    return (POSITION) pNode;
} // AddTail(object)

/* Add this object to the head end of our list
   Return the new head position.
*/
POSITION CBaseList::AddHeadI(void *pObject)
{
    CNode *pNode;
    // ASSERT(pObject);  // NULL pointers in the list are allowed.

    /* If there is a node objects in the cache then use
       that otherwise we will have to create a new one */

    pNode = (CNode *) m_Cache.RemoveFromCache();
    if (pNode == NULL) {
        pNode = new CNode;
    }

    /* Check we have a valid object */

    ASSERT(pNode != NULL);
    if (pNode == NULL) {
        return NULL;
    }

    /* Initialise all the CNode object
       just in case it came from the cache
    */

    pNode->SetData(pObject);

    /* chain it in (set four pointers) */
    pNode->SetPrev(NULL);
    pNode->SetNext(m_pFirst);

    if (m_pFirst == NULL) {
        m_pLast = pNode;
    } else {
        m_pFirst->SetPrev(pNode);
    }
    m_pFirst = pNode;

    ++m_Count;

    return (POSITION) pNode;
} // AddHead(object)

/* Add all the elements in *pList to the tail of this list.
   Return TRUE if it all worked, FALSE if it didn't.
   If it fails some elements may have been added.
*/
BOOL CBaseList::AddTail(CBaseList *pList)
{
    /* lock the object before starting then enumerate
       each entry in the source list and add them one by one to
       our list (while still holding the object lock)
       Lock the other list too.
    */
    POSITION pos = pList->GetHeadPositionI();

    while (pos) {
       if (NULL == AddTailI(pList->GetNextI(pos))) {
           return FALSE;
       }
    }
    return TRUE;
} // AddTail(list)

/* Add all the elements in *pList to the head of this list.
   Return TRUE if it all worked, FALSE if it didn't.
   If it fails some elements may have been added.
*/
BOOL CBaseList::AddHead(CBaseList *pList)
{
    /* lock the object before starting then enumerate
       each entry in the source list and add them one by one to
       our list (while still holding the object lock)
       Lock the other list too.

       To avoid reversing the list, traverse it backwards.
    */

    POSITION pos;

    INTERNALREVERSETRAVERSELIST(*pList, pos) {
        if (NULL== AddHeadI(pList->GetI(pos))){
            return FALSE;
        }
    }
    return TRUE;
} // AddHead(list)

/* Add the object after position p
   p is still valid after the operation.
   AddAfter(NULL,x) adds x to the start - same as AddHead
   Return the position of the new object, NULL if it failed
*/
POSITION  CBaseList::AddAfterI(POSITION pos, void * pObj)
{
    if (pos==NULL)
        return AddHeadI(pObj);

    /* As someone else might be furkling with the list -
       Lock the critical section before continuing
    */
    CNode *pAfter = (CNode *) pos;
    ASSERT(pAfter != NULL);
    if (pAfter==m_pLast)
        return AddTailI(pObj);

    /* set pnode to point to a new node, preferably from the cache */

    CNode *pNode = (CNode *) m_Cache.RemoveFromCache();
    if (pNode == NULL) {
        pNode = new CNode;
    }

    /* Check we have a valid object */

    ASSERT(pNode != NULL);
    if (pNode == NULL) {
        return NULL;
    }

    /* Initialise all the CNode object
       just in case it came from the cache
    */

    pNode->SetData(pObj);

    /* It is to be added to the middle of the list - there is a before
       and after node.  Chain it after pAfter, before pBefore.
    */
    CNode * pBefore = pAfter->Next();
    ASSERT(pBefore != NULL);

    /* chain it in (set four pointers) */
    pNode->SetPrev(pAfter);
    pNode->SetNext(pBefore);
    pBefore->SetPrev(pNode);
    pAfter->SetNext(pNode);

    ++m_Count;

    return (POSITION) pNode;

} // AddAfter(object)

BOOL CBaseList::AddAfter(POSITION p, CBaseList *pList)
{
    POSITION pos;
    INTERNALTRAVERSELIST(*pList, pos) {
        /* p follows along the elements being added */
        p = AddAfterI(p, pList->GetI(pos));
        if (p==NULL) return FALSE;
    }
    return TRUE;
} // AddAfter(list)

/* Mirror images:
   Add the element or list after position p.
   p is still valid after the operation.
   AddBefore(NULL,x) adds x to the end - same as AddTail
*/
POSITION CBaseList::AddBeforeI(POSITION pos, void * pObj)
{
    if (pos==NULL)
        return AddTailI(pObj);

    /* set pnode to point to a new node, preferably from the cache */

    CNode *pBefore = (CNode *) pos;
    ASSERT(pBefore != NULL);
    if (pBefore==m_pFirst)
        return AddHeadI(pObj);

    CNode * pNode = (CNode *) m_Cache.RemoveFromCache();
    if (pNode == NULL) {
        pNode = new CNode;
    }

    /* Check we have a valid object */

    ASSERT(pNode != NULL);
    if (pNode == NULL) {
        return NULL;
    }

    /* Initialise all the CNode object
       just in case it came from the cache
    */

    pNode->SetData(pObj);

    /* It is to be added to the middle of the list - there is a before
       and after node.  Chain it after pAfter, before pBefore.
    */

    CNode * pAfter = pBefore->Prev();
    ASSERT(pAfter != NULL);

    /* chain it in (set four pointers) */
    pNode->SetPrev(pAfter);
    pNode->SetNext(pBefore);
    pBefore->SetPrev(pNode);
    pAfter->SetNext(pNode);

    ++m_Count;

    return (POSITION) pNode;

} // Addbefore(object)

BOOL CBaseList::AddBefore(POSITION p, CBaseList *pList)
{
    POSITION pos;
    INTERNALREVERSETRAVERSELIST(*pList, pos) {
        /* p follows along the elements being added */
        p = AddBeforeI(p, pList->GetI(pos));
        if (p==NULL) return FALSE;
    }
    return TRUE;
} // AddBefore(list)

/* Split *this after position p in *this
   Retain as *this the tail portion of the original *this
   Add the head portion to the tail end of *pList
   Return TRUE if it all worked, FALSE if it didn't.

   e.g.
      foo->MoveToTail(foo->GetHeadPosition(), bar);
          moves one element from the head of foo to the tail of bar
      foo->MoveToTail(NULL, bar);
          is a no-op
      foo->MoveToTail(foo->GetTailPosition, bar);
          concatenates foo onto the end of bar and empties foo.

   A better, except excessively long name might be
       MoveElementsFromHeadThroughPositionToOtherTail
*/
BOOL CBaseList::MoveToTail
        (POSITION pos, CBaseList *pList)
{
    /* Algorithm:
       Note that the elements (including their order) in the concatenation
       of *pList to the head of *this is invariant.
       1. Count elements to be moved
       2. Join *pList onto the head of this to make one long chain
       3. Set first/Last pointers in *this and *pList
       4. Break the chain at the new place
       5. Adjust counts
       6. Set/Reset any events
    */

    if (pos==NULL) return TRUE;  // no-op.  Eliminates special cases later.

    /* Make cMove the number of nodes to move */
    CNode * p = (CNode *)pos;
    int cMove = 0;            // number of nodes to move
    while(p!=NULL) {
       p = p->Prev();
       ++cMove;
    }

    /* Join the two chains together */
    if (pList->m_pLast!=NULL)
        pList->m_pLast->SetNext(m_pFirst);
    if (m_pFirst!=NULL)
        m_pFirst->SetPrev(pList->m_pLast);

    /* set first and last pointers */
    p = (CNode *)pos;

    if (pList->m_pFirst==NULL)
        pList->m_pFirst = m_pFirst;
    m_pFirst = p->Next();
    if (m_pFirst==NULL)
        m_pLast = NULL;
    pList->m_pLast = p;

    /* Break the chain after p to create the new pieces */
    if (m_pFirst!=NULL)
        m_pFirst->SetPrev(NULL);
    p->SetNext(NULL);

    /* Adjust the counts */
    m_Count -= cMove;
    pList->m_Count += cMove;

    return TRUE;

} // MoveToTail

/* Mirror image of MoveToTail:
   Split *this before position p in *this.
   Retain in *this the head portion of the original *this
   Add the tail portion to the start (i.e. head) of *pList
   Return TRUE if it all worked, FALSE if it didn't.

   e.g.
      foo->MoveToHead(foo->GetTailPosition(), bar);
          moves one element from the tail of foo to the head of bar
      foo->MoveToHead(NULL, bar);
          is a no-op
      foo->MoveToHead(foo->GetHeadPosition, bar);
          concatenates foo onto the start of bar and empties foo.
*/
BOOL CBaseList::MoveToHead
        (POSITION pos, CBaseList *pList)
{

    /* See the comments on the algorithm in MoveToTail */

    if (pos==NULL) return TRUE;  // no-op.  Eliminates special cases later.

    /* Make cMove the number of nodes to move */
    CNode * p = (CNode *)pos;
    int cMove = 0;            // number of nodes to move
    while(p!=NULL) {
       p = p->Next();
       ++cMove;
    }

    /* Join the two chains together */
    if (pList->m_pFirst!=NULL)
        pList->m_pFirst->SetPrev(m_pLast);
    if (m_pLast!=NULL)
        m_pLast->SetNext(pList->m_pFirst);

    /* set first and last pointers */
    p = (CNode *)pos;

    if (pList->m_pLast==NULL)
        pList->m_pLast = m_pLast;

    m_pLast = p->Prev();
    if (m_pLast==NULL)
        m_pFirst = NULL;
    pList->m_pFirst = p;

    /* Break the chain after p to create the new pieces */
    if (m_pLast!=NULL)
        m_pLast->SetNext(NULL);
    p->SetPrev(NULL);

    /* Adjust the counts */
    m_Count -= cMove;
    pList->m_Count += cMove;

    return TRUE;

} // MoveToHead

/* Reverse the order of the [pointers to] objects in *this
*/
void CBaseList::Reverse()
{
    /* algorithm:
       The obvious booby trap is that you flip pointers around and lose
       addressability to the node that you are going to process next.
       The easy way to avoid this is do do one chain at a time.

       Run along the forward chain,
       For each node, set the reverse pointer to the one ahead of us.
       The reverse chain is now a copy of the old forward chain, including
       the NULL termination.

       Run along the reverse chain (i.e. old forward chain again)
       For each node set the forward pointer of the node ahead to point back
       to the one we're standing on.
       The first node needs special treatment,
       it's new forward pointer is NULL.
       Finally set the First/Last pointers

    */
    CNode * p;

    // Yes we COULD use a traverse, but it would look funny!
    p = m_pFirst;
    while (p!=NULL) {
        CNode * q;
        q = p->Next();
        p->SetNext(p->Prev());
        p->SetPrev(q);
        p = q;
    }

    p = m_pFirst;
    m_pFirst = m_pLast;
    m_pLast = p;

#if 0     // old version

    if (m_pFirst==NULL) return;          // empty list
    if (m_pFirst->Next()==NULL) return;  // single node list

    /* run along forward chain */
    for ( p = m_pFirst
        ; p!=NULL
        ; p = p->Next()
        ){
        p->SetPrev(p->Next());
    }

    /* special case first element */
    m_pFirst->SetNext(NULL);     // fix the old first element

    /* run along new reverse chain i.e. old forward chain again */
    for ( p = m_pFirst           // start at the old first element
        ; p->Prev()!=NULL        // while there's a node still to be set
        ; p = p->Prev()          // work in the same direction as before
        ){
        p->Prev()->SetNext(p);
    }

    /* fix forward and reverse pointers
       - the triple XOR swap would work but all the casts look hideous */
    p = m_pFirst;
    m_pFirst = m_pLast;
    m_pLast = p;
#endif

} // Reverse


WXLIST.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Non MFC based generic template list class, December 1994

/* A generic list of pointers to objects.
   No storage management or copying is done on the objects pointed to.
   Objectives: avoid using MFC libraries in ndm kernel mode and
   provide a really useful list type.

   The class is thread safe in that separate threads may add and
   delete items in the list concurrently although the application
   must ensure that constructor and destructor access is suitably
   synchronised. An application can cause deadlock with operations
   which use two lists by simultaneously calling
   list1->Operation(list2) and list2->Operation(list1).  So don't!

   The names must not conflict with MFC classes as an application
   may use both.
   */

#ifndef __WXLIST__
#define __WXLIST__

   /* A POSITION represents (in some fashion that's opaque) a cursor
      on the list that can be set to identify any element.  NULL is
      a valid value and several operations regard NULL as the position
      "one step off the end of the list".  (In an n element list there
      are n+1 places to insert and NULL is that "n+1-th" value).
      The POSITION of an element in the list is only invalidated if
      that element is deleted.  Move operations may mean that what
      was a valid POSITION in one list is now a valid POSITION in
      a different list.

      Some operations which at first sight are illegal are allowed as
      harmless no-ops.  For instance RemoveHead is legal on an empty
      list and it returns NULL.  This allows an atomic way to test if
      there is an element there, and if so, get it.  The two operations
      AddTail and RemoveHead thus implement a MONITOR (See Hoare's paper).

      Single element operations return POSITIONs, non-NULL means it worked.
      whole list operations return a BOOL.  TRUE means it all worked.

      This definition is the same as the POSITION type for MFCs, so we must
      avoid defining it twice.
   */
#ifndef __AFX_H__
struct __POSITION { int unused; };
typedef __POSITION* POSITION;
#endif

const int DEFAULTCACHE = 10;    /* Default node object cache size */

/* A class representing one node in a list.
   Each node knows a pointer to it's adjacent nodes and also a pointer
   to the object that it looks after.
   All of these pointers can be retrieved or set through member functions.
*/
class CBaseList
#ifdef DEBUG
    : public CBaseObject
#endif
{
    /* Making these classes inherit from CBaseObject does nothing
       functionally but it allows us to check there are no memory
       leaks in debug builds.
    */

public:

#ifdef DEBUG
    class CNode : public CBaseObject {
#else
    class CNode {
#endif

        CNode *m_pPrev;         /* Previous node in the list */
        CNode *m_pNext;         /* Next node in the list */
        void *m_pObject;      /* Pointer to the object */

    public:

        /* Constructor - initialise the object's pointers */
        CNode()
#ifdef DEBUG
            : CBaseObject(NAME("List node"))
#endif
        {
        };

        /* Return the previous node before this one */
        CNode *Prev() const { return m_pPrev; };

        /* Return the next node after this one */
        CNode *Next() const { return m_pNext; };

        /* Set the previous node before this one */
        void SetPrev(CNode *p) { m_pPrev = p; };

        /* Set the next node after this one */
        void SetNext(CNode *p) { m_pNext = p; };

        /* Get the pointer to the object for this node */
        void *GetData() const { return m_pObject; };

        /* Set the pointer to the object for this node */
        void SetData(void *p) { m_pObject = p; };
    };

    class CNodeCache
    {
    public:
        CNodeCache(INT iCacheSize) : m_iCacheSize(iCacheSize),
                                     m_pHead(NULL),
                                     m_iUsed(0)
                                     {};
        ~CNodeCache() {
            CNode *pNode = m_pHead;
            while (pNode) {
                CNode *pCurrent = pNode;
                pNode = pNode->Next();
                delete pCurrent;
            }
        };
        void AddToCache(CNode *pNode)
        {
            if (m_iUsed < m_iCacheSize) {
                pNode->SetNext(m_pHead);
                m_pHead = pNode;
                m_iUsed++;
            } else {
                delete pNode;
            }
        };
        CNode *RemoveFromCache()
        {
            CNode *pNode = m_pHead;
            if (pNode != NULL) {
                m_pHead = pNode->Next();
                m_iUsed--;
                ASSERT(m_iUsed >= 0);
            } else {
                ASSERT(m_iUsed == 0);
            }
            return pNode;
        };
    private:
        INT m_iCacheSize;
        INT m_iUsed;
        CNode *m_pHead;
    };

protected:

    CNode* m_pFirst;    /* Pointer to first node in the list */
    CNode* m_pLast;     /* Pointer to the last node in the list */
    LONG m_Count;       /* Number of nodes currently in the list */

private:

    CNodeCache m_Cache; /* Cache of unused node pointers */

private:

    /* These override the default copy constructor and assignment
       operator for all list classes. They are in the private class
       declaration section so that anybody trying to pass a list
       object by value will generate a compile time error of
       "cannot access the private member function". If these were
       not here then the compiler will create default constructors
       and assignment operators which when executed first take a
       copy of all member variables and then during destruction
       delete them all. This must not be done for any heap
       allocated data.
    */
    CBaseList(const CBaseList &refList);
    CBaseList &operator=(const CBaseList &refList);

public:

    CBaseList(TCHAR *pName,
              INT iItems);

    CBaseList(TCHAR *pName);

    ~CBaseList();

    /* Remove all the nodes from *this i.e. make the list empty */
    void RemoveAll();

    /* Return a cursor which identifies the first element of *this */
    POSITION GetHeadPositionI() const;

    /* Return a cursor which identifies the last element of *this */
    POSITION GetTailPositionI() const;

    /* Return the number of objects in *this */
    int GetCountI() const;

protected:
    /* Return the pointer to the object at rp,
       Update rp to the next node in *this
       but make it NULL if it was at the end of *this.
       This is a wart retained for backwards compatibility.
       GetPrev is not implemented.
       Use Next, Prev and Get separately.
    */
    void *GetNextI(POSITION& rp) const;

    /* Return a pointer to the object at p
       Asking for the object at NULL will return NULL harmlessly.
    */
    void *GetI(POSITION p) const;

public:
    /* return the next / prev position in *this
       return NULL when going past the end/start.
       Next(NULL) is same as GetHeadPosition()
       Prev(NULL) is same as GetTailPosition()
       An n element list therefore behaves like a n+1 element
       cycle with NULL at the start/end.

       !!WARNING!! - This handling of NULL is DIFFERENT from GetNext.

       Some reasons are:
       1. For a list of n items there are n+1 positions to insert
          These are conveniently encoded as the n POSITIONs and NULL.
       2. If you are keeping a list sorted (fairly common) and you
          search forward for an element to insert before and don't
          find it you finish up with NULL as the element before which
          to insert.  You then want that NULL to be a valid POSITION
          so that you can insert before it and you want that insertion
          point to mean the (n+1)-th one that doesn't have a POSITION.
          (symmetrically if you are working backwards through the list).
       3. It simplifies the algebra which the methods generate.
          e.g. AddBefore(p,x) is identical to AddAfter(Prev(p),x)
          in ALL cases.  All the other arguments probably are reflections
          of the algebraic point.
    */
    POSITION Next(POSITION pos) const
    {
        if (pos == NULL) {
            return (POSITION) m_pFirst;
        }
        CNode *pn = (CNode *) pos;
        return (POSITION) pn->Next();
    } //Next

    // See Next
    POSITION Prev(POSITION pos) const
    {
        if (pos == NULL) {
            return (POSITION) m_pLast;
        }
        CNode *pn = (CNode *) pos;
        return (POSITION) pn->Prev();
    } //Prev

    /* Return the first position in *this which holds the given
       pointer.  Return NULL if the pointer was not not found.
    */
protected:
    POSITION FindI( void * pObj) const;

    // ??? Should there be (or even should there be only)
    // ??? POSITION FindNextAfter(void * pObj, POSITION p)
    // ??? And of course FindPrevBefore too.
    // ??? List.Find(&Obj) then becomes List.FindNextAfter(&Obj, NULL)

    /* Remove the first node in *this (deletes the pointer to its
       object from the list, does not free the object itself).
       Return the pointer to its object.
       If *this was already empty it will harmlessly return NULL.
    */
    void *RemoveHeadI();

    /* Remove the last node in *this (deletes the pointer to its
       object from the list, does not free the object itself).
       Return the pointer to its object.
       If *this was already empty it will harmlessly return NULL.
    */
    void *RemoveTailI();

    /* Remove the node identified by p from the list (deletes the pointer
       to its object from the list, does not free the object itself).
       Asking to Remove the object at NULL will harmlessly return NULL.
       Return the pointer to the object removed.
    */
    void *RemoveI(POSITION p);

    /* Add single object *pObj to become a new last element of the list.
       Return the new tail position, NULL if it fails.
       If you are adding a COM objects, you might want AddRef it first.
       Other existing POSITIONs in *this are still valid
    */
    POSITION AddTailI(void * pObj);
public:

    /* Add all the elements in *pList to the tail of *this.
       This duplicates all the nodes in *pList (i.e. duplicates
       all its pointers to objects).  It does not duplicate the objects.
       If you are adding a list of pointers to a COM object into the list
       it's a good idea to AddRef them all  it when you AddTail it.
       Return TRUE if it all worked, FALSE if it didn't.
       If it fails some elements may have been added.
       Existing POSITIONs in *this are still valid

       If you actually want to MOVE the elements, use MoveToTail instead.
    */
    BOOL AddTail(CBaseList *pList);

    /* Mirror images of AddHead: */

    /* Add single object to become a new first element of the list.
       Return the new head position, NULL if it fails.
       Existing POSITIONs in *this are still valid
    */
protected:
    POSITION AddHeadI(void * pObj);
public:

    /* Add all the elements in *pList to the head of *this.
       Same warnings apply as for AddTail.
       Return TRUE if it all worked, FALSE if it didn't.
       If it fails some of the objects may have been added.

       If you actually want to MOVE the elements, use MoveToHead instead.
    */
    BOOL AddHead(CBaseList *pList);

    /* Add the object *pObj to *this after position p in *this.
       AddAfter(NULL,x) adds x to the start - equivalent to AddHead
       Return the position of the object added, NULL if it failed.
       Existing POSITIONs in *this are undisturbed, including p.
    */
protected:
    POSITION AddAfterI(POSITION p, void * pObj);
public:

    /* Add the list *pList to *this after position p in *this
       AddAfter(NULL,x) adds x to the start - equivalent to AddHead
       Return TRUE if it all worked, FALSE if it didn't.
       If it fails, some of the objects may be added
       Existing POSITIONs in *this are undisturbed, including p.
    */
    BOOL AddAfter(POSITION p, CBaseList *pList);

    /* Mirror images:
       Add the object *pObj to this-List after position p in *this.
       AddBefore(NULL,x) adds x to the end - equivalent to AddTail
       Return the position of the new object, NULL if it fails
       Existing POSITIONs in *this are undisturbed, including p.
    */
    protected:
    POSITION AddBeforeI(POSITION p, void * pObj);
    public:

    /* Add the list *pList to *this before position p in *this
       AddAfter(NULL,x) adds x to the start - equivalent to AddHead
       Return TRUE if it all worked, FALSE if it didn't.
       If it fails, some of the objects may be added
       Existing POSITIONs in *this are undisturbed, including p.
    */
    BOOL AddBefore(POSITION p, CBaseList *pList);

    /* Note that AddAfter(p,x) is equivalent to AddBefore(Next(p),x)
       even in cases where p is NULL or Next(p) is NULL.
       Similarly for mirror images etc.
       This may make it easier to argue about programs.
    */

    /* The following operations do not copy any elements.
       They move existing blocks of elements around by switching pointers.
       They are fairly efficient for long lists as for short lists.
       (Alas, the Count slows things down).

       They split the list into two parts.
       One part remains as the original list, the other part
       is appended to the second list.  There are eight possible
       variations:
       Split the list {after/before} a given element
       keep the {head/tail} portion in the original list
       append the rest to the {head/tail} of the new list.

       Since After is strictly equivalent to Before Next
       we are not in serious need of the Before/After variants.
       That leaves only four.

       If you are processing a list left to right and dumping
       the bits that you have processed into another list as
       you go, the Tail/Tail variant gives the most natural result.
       If you are processing in reverse order, Head/Head is best.

       By using NULL positions and empty lists judiciously either
       of the other two can be built up in two operations.

       The definition of NULL (see Next/Prev etc) means that
       degenerate cases include
          "move all elements to new list"
          "Split a list into two lists"
          "Concatenate two lists"
          (and quite a few no-ops)

       !!WARNING!! The type checking won't buy you much if you get list
       positions muddled up - e.g. use a POSITION that's in a different
       list and see what a mess you get!
    */

    /* Split *this after position p in *this
       Retain as *this the tail portion of the original *this
       Add the head portion to the tail end of *pList
       Return TRUE if it all worked, FALSE if it didn't.

       e.g.
          foo->MoveToTail(foo->GetHeadPosition(), bar);
              moves one element from the head of foo to the tail of bar
          foo->MoveToTail(NULL, bar);
              is a no-op, returns NULL
          foo->MoveToTail(foo->GetTailPosition, bar);
              concatenates foo onto the end of bar and empties foo.

       A better, except excessively long name might be
           MoveElementsFromHeadThroughPositionToOtherTail
    */
    BOOL MoveToTail(POSITION pos, CBaseList *pList);

    /* Mirror image:
       Split *this before position p in *this.
       Retain in *this the head portion of the original *this
       Add the tail portion to the start (i.e. head) of *pList

       e.g.
          foo->MoveToHead(foo->GetTailPosition(), bar);
              moves one element from the tail of foo to the head of bar
          foo->MoveToHead(NULL, bar);
              is a no-op, returns NULL
          foo->MoveToHead(foo->GetHeadPosition, bar);
              concatenates foo onto the start of bar and empties foo.
    */
    BOOL MoveToHead(POSITION pos, CBaseList *pList);

    /* Reverse the order of the [pointers to] objects in *this
    */
    void Reverse();

    /* set cursor to the position of each element of list in turn  */
    #define TRAVERSELIST(list, cursor)               \
    for ( cursor = (list).GetHeadPosition()           \
        ; cursor!=NULL                               \
        ; cursor = (list).Next(cursor)                \
        )

    /* set cursor to the position of each element of list in turn
       in reverse order
    */
    #define REVERSETRAVERSELIST(list, cursor)        \
    for ( cursor = (list).GetTailPosition()           \
        ; cursor!=NULL                               \
        ; cursor = (list).Prev(cursor)                \
        )

}; // end of class declaration

template<class OBJECT> class CGenericList : public CBaseList
{
public:
    CGenericList(TCHAR *pName,
                 INT iItems,
                 BOOL bLock = TRUE,
                 BOOL bAlert = FALSE) :
                     CBaseList(pName, iItems) {
        UNREFERENCED_PARAMETER(bAlert);
        UNREFERENCED_PARAMETER(bLock);
    };
    CGenericList(TCHAR *pName) :
                     CBaseList(pName) {
    };

    POSITION GetHeadPosition() const { return (POSITION)m_pFirst; }
    POSITION GetTailPosition() const { return (POSITION)m_pLast; }
    int GetCount() const { return m_Count; }

    OBJECT *GetNext(POSITION& rp) const { return (OBJECT *) GetNextI(rp); }

    OBJECT *Get(POSITION p) const { return (OBJECT *) GetI(p); }
    OBJECT *GetHead() const  { return Get(GetHeadPosition()); }

    OBJECT *RemoveHead() { return (OBJECT *) RemoveHeadI(); }

    OBJECT *RemoveTail() { return (OBJECT *) RemoveTailI(); }

    OBJECT *Remove(POSITION p) { return (OBJECT *) RemoveI(p); }
    POSITION AddBefore(POSITION p, OBJECT * pObj) { return AddBeforeI(p, pObj); }
    POSITION AddAfter(POSITION p, OBJECT * pObj)  { return AddAfterI(p, pObj); }
    POSITION AddHead(OBJECT * pObj) { return AddHeadI(pObj); }
    POSITION AddTail(OBJECT * pObj)  { return AddTailI(pObj); }
    BOOL AddTail(CGenericList<OBJECT> *pList)
            { return CBaseList::AddTail((CBaseList *) pList); }
    BOOL AddHead(CGenericList<OBJECT> *pList)
            { return CBaseList::AddHead((CBaseList *) pList); }
    BOOL AddAfter(POSITION p, CGenericList<OBJECT> *pList)
            { return CBaseList::AddAfter(p, (CBaseList *) pList); };
    BOOL AddBefore(POSITION p, CGenericList<OBJECT> *pList)
            { return CBaseList::AddBefore(p, (CBaseList *) pList); };
    POSITION Find( OBJECT * pObj) const { return FindI(pObj); }
}; // end of class declaration

/* These define the standard list types */

typedef CGenericList<CBaseObject> CBaseObjectList;
typedef CGenericList<IUnknown> CBaseInterfaceList;

#endif /* __WXLIST__ */


WXUTIL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// helper classes for building multimedia filters
//

#include <streams.h>

//
//  Declare function from largeint.h we need so that PPC can build
//

//
// Enlarged integer divide - 64-bits / 32-bits > 32-bits
//

#ifndef _X86_

#define LLtoU64(x) (*(unsigned __int64*)(void*)(&(x)))

__inline
ULONG
WINAPI
EnlargedUnsignedDivide (
    IN ULARGE_INTEGER Dividend,
    IN ULONG Divisor,
    IN PULONG Remainder
    )
{
        // return remainder if necessary
        if (Remainder != NULL)
                *Remainder = (ULONG)(LLtoU64(Dividend) % Divisor);
        return (ULONG)(LLtoU64(Dividend) / Divisor);
}

#else
__inline
ULONG
WINAPI
EnlargedUnsignedDivide (
    IN ULARGE_INTEGER Dividend,
    IN ULONG Divisor,
    IN PULONG Remainder
    )
{
    ULONG ulResult;
    _asm {
        mov eax,Dividend.LowPart
        mov edx,Dividend.HighPart
        mov ecx,Remainder
        div Divisor
        or  ecx,ecx
        jz  short label
        mov [ecx],edx
label:
        mov ulResult,eax
    }
    return ulResult;
}
#endif

// --- CAMEvent -----------------------
CAMEvent::CAMEvent(BOOL fManualReset)
{
    m_hEvent = CreateEvent(NULL, fManualReset, FALSE, NULL);
    ASSERT(m_hEvent);
}

CAMEvent::~CAMEvent()
{
    if (m_hEvent) {
EXECUTE_ASSERT(CloseHandle(m_hEvent));
    }
}

// --- CAMMsgEvent -----------------------
// One routine.  The rest is handled in CAMEvent

BOOL CAMMsgEvent::WaitMsg(DWORD dwTimeout)
{
    // wait for the event to be signalled, or for the
    // timeout (in MS) to expire.  allow SENT messages
    // to be processed while we wait
    DWORD dwWait;
    DWORD dwStartTime;

    // set the waiting period.
    DWORD dwWaitTime = dwTimeout;

    // the timeout will eventually run down as we iterate
    // processing messages.  grab the start time so that
    // we can calculate elapsed times.
    if (dwWaitTime != INFINITE) {
        dwStartTime = timeGetTime();
    }

    do {
        dwWait = MsgWaitForMultipleObjects(1,&m_hEvent,FALSE, dwWaitTime, QS_SENDMESSAGE);
        if (dwWait == WAIT_OBJECT_0 + 1) {
    MSG Message;
            PeekMessage(&Message,NULL,0,0,PM_NOREMOVE);

    // If we have an explicit length of time to wait calculate
    // the next wake up point - which might be now.
    // If dwTimeout is INFINITE, it stays INFINITE
    if (dwWaitTime != INFINITE) {

DWORD dwElapsed = timeGetTime()-dwStartTime;

dwWaitTime =
    (dwElapsed >= dwTimeout)
? 0  // wake up with WAIT_TIMEOUT
: dwTimeout-dwElapsed;
    }
        }
    } while (dwWait == WAIT_OBJECT_0 + 1);

    // return TRUE if we woke on the event handle,
    //        FALSE if we timed out.
    return (dwWait == WAIT_OBJECT_0);
}

// --- CAMThread ----------------------

CAMThread::CAMThread()
    : m_EventSend(TRUE)     // must be manual-reset for CheckRequest()
{
    m_hThread = NULL;
}

CAMThread::~CAMThread() {
    Close();
}

// when the thread starts, it calls this function. We unwrap the 'this'
//pointer and call ThreadProc.
DWORD WINAPI
CAMThread::InitialThreadProc(LPVOID pv)
{
    CAMThread * pThread = (CAMThread *) pv;

    return pThread->ThreadProc();
}

BOOL
CAMThread::Create()
{
    DWORD threadid;

    CAutoLock lock(&m_AccessLock);

    if (ThreadExists()) {
return FALSE;
    }

    m_hThread = CreateThread(
    NULL,
    0,
    CAMThread::InitialThreadProc,
    this,
    0,
    &threadid);

    if (!m_hThread) {
return FALSE;
    }

    return TRUE;
}

DWORD
CAMThread::CallWorker(DWORD dwParam)
{
    // lock access to the worker thread for scope of this object
    CAutoLock lock(&m_AccessLock);

    if (!ThreadExists()) {
return (DWORD) E_FAIL;
    }

    // set the parameter
    m_dwParam = dwParam;

    // signal the worker thread
    m_EventSend.Set();

    // wait for the completion to be signalled
    m_EventComplete.Wait();

    // done - this is the thread's return value
    return m_dwReturnVal;
}

// Wait for a request from the client
DWORD
CAMThread::GetRequest()
{
    m_EventSend.Wait();
    return m_dwParam;
}

// is there a request?
BOOL
CAMThread::CheckRequest(DWORD * pParam)
{
    if (!m_EventSend.Check()) {
return FALSE;
    } else {
if (pParam) {
    *pParam = m_dwParam;
}
return TRUE;
    }
}

// reply to the request
void
CAMThread::Reply(DWORD dw)
{
    m_dwReturnVal = dw;

    // The request is now complete so CheckRequest should fail from
    // now on
    //
    // This event should be reset BEFORE we signal the client or
    // the client may Set it before we reset it and we'll then
    // reset it (!)

    m_EventSend.Reset();

    // Tell the client we're finished

    m_EventComplete.Set();
}

// destructor for CMsgThread  - cleans up any messages left in the
// queue when the thread exited
CMsgThread::~CMsgThread()
{
    if (m_hThread != NULL) {
        WaitForSingleObject(m_hThread, INFINITE);
        EXECUTE_ASSERT(CloseHandle(m_hThread));
    }

    POSITION pos = m_ThreadQueue.GetHeadPosition();
    while (pos) {
        CMsg * pMsg = m_ThreadQueue.GetNext(pos);
        delete pMsg;
    }
    m_ThreadQueue.RemoveAll();

    if (m_hSem != NULL) {
        EXECUTE_ASSERT(CloseHandle(m_hSem));
    }
}

BOOL
CMsgThread::CreateThread(
    )
{
    m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
    if (m_hSem == NULL) {
        return FALSE;
    }

    m_hThread = ::CreateThread(NULL, 0, DefaultThreadProc,
       (LPVOID)this, 0, &m_ThreadId);
    return m_hThread != NULL;
}

// This is the threads message pump.  Here we get and dispatch messages to
// clients thread proc until the client refuses to process a message.
// The client returns a non-zero value to stop the message pump, this
// value becomes the threads exit code.

DWORD WINAPI
CMsgThread::DefaultThreadProc(
    LPVOID lpParam
    )
{
    CMsgThread *lpThis = (CMsgThread *)lpParam;
    CMsg msg;
    LRESULT lResult;

    // !!!
    CoInitialize(NULL);

    // allow a derived class to handle thread startup
    lpThis->OnThreadInit();

    do {
lpThis->GetThreadMsg(&msg);
lResult = lpThis->ThreadMessageProc(msg.uMsg,msg.dwFlags,
    msg.lpParam, msg.pEvent);
    } while (lResult == 0L);

    // !!!
    CoUninitialize();

    return (DWORD)lResult;
}

// Block until the next message is placed on the list m_ThreadQueue.
// copies the message to the message pointed to by *pmsg
void
CMsgThread::GetThreadMsg(CMsg *msg)
{
    CMsg * pmsg = NULL;

    // keep trying until a message appears
    while (TRUE) {
        {
            CAutoLock lck(&m_Lock);
            pmsg = m_ThreadQueue.RemoveHead();
            if (pmsg == NULL) {
                m_lWaiting++;
            } else {
                break;
            }
        }
        // the semaphore will be signalled when it is non-empty
        WaitForSingleObject(m_hSem, INFINITE);
    }
    // copy fields to caller's CMsg
    *msg = *pmsg;

    // this CMsg was allocated by the 'new' in PutThreadMsg
    delete pmsg;

}

#ifndef UNICODE
// NOTE: as we need to use the same binaries on Win95 as on NT this code should
// be compiled WITHOUT unicode being defined.  Otherwise we will not pick up
// these internal routines and the binary will not run on Win95.

// Windows 95 doesn't implement this, so we provide an implementation.
LPWSTR
WINAPI
lstrcpyWInternal(
    LPWSTR lpString1,
    LPCWSTR lpString2
    )
{
    LPWSTR  lpReturn = lpString1;
    while (*lpString1++ = *lpString2++);

    return lpReturn;
}

// Windows 95 doesn't implement this, so we provide an implementation.
LPWSTR
WINAPI
lstrcpynWInternal(
    LPWSTR lpString1,
    LPCWSTR lpString2,
    int     iMaxLength
    )
{
    ASSERT(iMaxLength);
    LPWSTR  lpReturn = lpString1;
    if (iMaxLength) {
        while (--iMaxLength && (*lpString1++ = *lpString2++));

        // If we ran out of room (which will be the case if
        // iMaxLength is now 0) we still need to terminate the
        // string.
        if (!iMaxLength) *lpString1 = L'\0';
    }
    return lpReturn;
}

int
WINAPI
lstrcmpWInternal(
    LPCWSTR lpString1,
    LPCWSTR lpString2
    )
{
    do {
WCHAR c1 = *lpString1;
WCHAR c2 = *lpString2;
if (c1 != c2)
    return (int) c1 - (int) c2;
    } while (*lpString1++ && *lpString2++);
    return 0;
}

int
WINAPI
lstrcmpiWInternal(
    LPCWSTR lpString1,
    LPCWSTR lpString2
    )
{
    do {
WCHAR c1 = *lpString1;
WCHAR c2 = *lpString2;
if (c1 >= L'A' && c1 <= L'Z')
    c1 -= (WCHAR) (L'A' - L'a');
if (c2 >= L'A' && c2 <= L'Z')
    c2 -= (WCHAR) (L'A' - L'a');

if (c1 != c2)
    return (int) c1 - (int) c2;
    } while (*lpString1++ && *lpString2++);

    return 0;
}

int
WINAPI
lstrlenWInternal(
    LPCWSTR lpString
    )
{
    int i = -1;
    while (*(lpString+(++i)))
        ;
    return i;
}

#endif // NOT UNICODE

#ifndef UNICODE
int WINAPIV wsprintfWInternal(LPWSTR wszOut, LPCWSTR pszFmt, ...)
{
    char fmt[256]; // !!!
    char ach[256]; // !!!
    int i;

    va_list va;
    va_start(va, pszFmt);
    WideCharToMultiByte(GetACP(), 0, pszFmt, -1, fmt, 256, NULL, NULL);
    i = wvsprintf(ach, fmt, va);
    va_end(va);

    MultiByteToWideChar(CP_ACP, 0, ach, -1, wszOut, i+1);

    return i;
}
#endif

// Helper function - convert int to WSTR
void WINAPI IntToWstr(int i, LPWSTR wstr)
{
#ifdef UNICODE
    wsprintf(wstr, L"%d", i);
#else
    TCHAR temp[32];
    wsprintf(temp, "%d", i);
    MultiByteToWideChar(CP_ACP, 0, temp, -1, wstr, 32);
#endif
} // IntToWstr

#if 0
void * memchrInternal(const void *pv, int c, size_t sz)
{
    BYTE *pb = (BYTE *) pv;
    while (sz--) {
if (*pb == c)
    return (void *) pb;
pb++;
    }
    return NULL;
}
#endif

#define MEMORY_ALIGNMENT        4
#define MEMORY_ALIGNMENT_LOG2   2
#define MEMORY_ALIGNMENT_MASK   MEMORY_ALIGNMENT - 1

void * __stdcall memmoveInternal(void * dst, const void * src, size_t count)
{
    void * ret = dst;

#ifdef _X86_
    if (dst <= src || (char *)dst >= ((char *)src + count)) {

        /*
         * Non-Overlapping Buffers
         * copy from lower addresses to higher addresses
         */
        _asm {
            mov     esi,src
            mov     edi,dst
            mov     ecx,count
            cld
            mov     edx,ecx
            and     edx,MEMORY_ALIGNMENT_MASK
            shr     ecx,MEMORY_ALIGNMENT_LOG2
            rep     movsd
            or      ecx,edx
            jz      memmove_done
            rep     movsb
memmove_done:
        }
    }
    else {

        /*
         * Overlapping Buffers
         * copy from higher addresses to lower addresses
         */
        _asm {
            mov     esi,src
            mov     edi,dst
            mov     ecx,count
            std
            add     esi,ecx
            add     edi,ecx
            dec     esi
            dec     edi
            rep     movsb
            cld
        }
    }
#else
    MoveMemory(dst, src, count);
#endif

    return ret;
}

/*  Arithmetic functions to help with time format conversions
*/

/*   Compute (a * b + d) / c */
LONGLONG WINAPI llMulDiv(LONGLONG a, LONGLONG b, LONGLONG c, LONGLONG d)
{
    /*  Compute the absolute values to avoid signed arithmetic problems */
    ULARGE_INTEGER ua, ub;
    DWORDLONG uc;

    ua.QuadPart = (DWORDLONG)(a >= 0 ? a : -a);
    ub.QuadPart = (DWORDLONG)(b >= 0 ? b : -b);
    uc          = (DWORDLONG)(c >= 0 ? c : -c);
    BOOL bSign = (a < 0) ^ (b < 0);

    /*  Do long multiplication */
    ULARGE_INTEGER p[2];
    p[0].QuadPart  = UInt32x32To64(ua.LowPart, ub.LowPart);

    /*  This next computation cannot overflow into p[1].HighPart because
        the max number we can compute here is:

                 (2 ** 32 - 1) * (2 ** 32 - 1) +  // ua.LowPart * ub.LowPart
    (2 ** 32) *  (2 ** 31) * (2 ** 32 - 1) * 2    // x.LowPart * y.HighPart * 2

    == 2 ** 96 - 2 ** 64 + (2 ** 64 - 2 ** 33 + 1)
    == 2 ** 96 - 2 ** 33 + 1
    < 2 ** 96
    */

    ULARGE_INTEGER x;
    x.QuadPart     = UInt32x32To64(ua.LowPart, ub.HighPart) +
                     UInt32x32To64(ua.HighPart, ub.LowPart) +
                     p[0].HighPart;
    p[0].HighPart  = x.LowPart;
    p[1].QuadPart  = UInt32x32To64(ua.HighPart, ub.HighPart) + x.HighPart;

    if (d != 0) {
        ULARGE_INTEGER ud[2];
        if (bSign) {
            ud[0].QuadPart = (DWORDLONG)(-d);
            if (d > 0) {
                /*  -d < 0 */
                ud[1].QuadPart = (DWORDLONG)(LONGLONG)-1;
            } else {
                ud[1].QuadPart = (DWORDLONG)0;
            }
        } else {
            ud[0].QuadPart = (DWORDLONG)d;
            if (d < 0) {
                ud[1].QuadPart = (DWORDLONG)(LONGLONG)-1;
            } else {
                ud[1].QuadPart = (DWORDLONG)0;
            }
        }
        /*  Now do extended addition */
        ULARGE_INTEGER uliTotal;

        /*  Add ls DWORDs */
        uliTotal.QuadPart  = (DWORDLONG)ud[0].LowPart + p[0].LowPart;
        p[0].LowPart       = uliTotal.LowPart;

        /*  Propagate carry */
        uliTotal.LowPart   = uliTotal.HighPart;
        uliTotal.HighPart  = 0;

        /*  Add 2nd most ls DWORDs */
        uliTotal.QuadPart += (DWORDLONG)ud[0].HighPart + p[0].HighPart;
        p[0].HighPart      = uliTotal.LowPart;

        /*  Propagate carry */
        uliTotal.LowPart   = uliTotal.HighPart;
        uliTotal.HighPart  = 0;

        /*  Add MS DWORDLONGs - no carry expected */
        p[1].QuadPart     += ud[1].QuadPart + uliTotal.QuadPart;

        /*  Now see if we got a sign change from the addition */
        if ((LONG)p[1].HighPart < 0) {
            bSign = !bSign;

            /*  Negate the current value (ugh!) */
            p[0].QuadPart  = ~p[0].QuadPart;
            p[1].QuadPart  = ~p[1].QuadPart;
            p[0].QuadPart += 1;
            p[1].QuadPart += (p[0].QuadPart == 0);
        }
    }

    /*  Now for the division */
    if (c < 0) {
        bSign = !bSign;
    }

    /*  This will catch c == 0 and overflow */
    if (uc <= p[1].QuadPart) {
        return bSign ? (LONGLONG)0x8000000000000000 :
                       (LONGLONG)0x7FFFFFFFFFFFFFFF;
    }

    DWORDLONG ullResult;

    /*  Do the division */
    /*  If the dividend is a DWORD_LONG use the compiler */
    if (p[1].QuadPart == 0) {
        ullResult = p[0].QuadPart / uc;
        return bSign ? -(LONGLONG)ullResult : (LONGLONG)ullResult;
    }

    /*  If the divisor is a DWORD then its simpler */
    ULARGE_INTEGER ulic;
    ulic.QuadPart = uc;
    if (ulic.HighPart == 0) {
        ULARGE_INTEGER uliDividend;
        ULARGE_INTEGER uliResult;
        DWORD dwDivisor = (DWORD)uc;
        // ASSERT(p[1].HighPart == 0 && p[1].LowPart < dwDivisor);
        uliDividend.HighPart = p[1].LowPart;
        uliDividend.LowPart = p[0].HighPart;
#ifndef USE_LARGEINT
        uliResult.HighPart = (DWORD)(uliDividend.QuadPart / dwDivisor);
        p[0].HighPart = (DWORD)(uliDividend.QuadPart % dwDivisor);
        uliResult.LowPart = 0;
        uliResult.QuadPart = p[0].QuadPart / dwDivisor + uliResult.QuadPart;
#else
        /*  NOTE - this routine will take exceptions if
            the result does not fit in a DWORD
        */
        if (uliDividend.QuadPart >= (DWORDLONG)dwDivisor) {
            uliResult.HighPart = EnlargedUnsignedDivide(
                                     uliDividend,
                                     dwDivisor,
                                     &p[0].HighPart);
        } else {
            uliResult.HighPart = 0;
        }
        uliResult.LowPart = EnlargedUnsignedDivide(
                                 p[0],
                                 dwDivisor,
                                 NULL);
#endif
        return bSign ? -(LONGLONG)uliResult.QuadPart :
                        (LONGLONG)uliResult.QuadPart;
    }

    ullResult = 0;

    /*  OK - do long division */
    for (int i = 0; i < 64; i++) {
        ullResult <<= 1;

        /*  Shift 128 bit p left 1 */
        p[1].QuadPart <<= 1;
        if ((p[0].HighPart & 0x80000000) != 0) {
            p[1].LowPart++;
        }
        p[0].QuadPart <<= 1;

        /*  Compare */
        if (uc <= p[1].QuadPart) {
            p[1].QuadPart -= uc;
            ullResult += 1;
        }
    }

    return bSign ? - (LONGLONG)ullResult : (LONGLONG)ullResult;
}

LONGLONG WINAPI Int64x32Div32(LONGLONG a, LONG b, LONG c, LONG d)
{
    ULARGE_INTEGER ua;
    DWORD ub;
    DWORD uc;

    /*  Compute the absolute values to avoid signed arithmetic problems */
    ua.QuadPart = (DWORDLONG)(a >= 0 ? a : -a);
    ub = (DWORD)(b >= 0 ? b : -b);
    uc = (DWORD)(c >= 0 ? c : -c);
    BOOL bSign = (a < 0) ^ (b < 0);

    /*  Do long multiplication */
    ULARGE_INTEGER p0;
    DWORD p1;
    p0.QuadPart  = UInt32x32To64(ua.LowPart, ub);

    if (ua.HighPart != 0) {
        ULARGE_INTEGER x;
        x.QuadPart     = UInt32x32To64(ua.HighPart, ub) + p0.HighPart;
        p0.HighPart  = x.LowPart;
        p1   = x.HighPart;
    } else {
        p1 = 0;
    }

    if (d != 0) {
        ULARGE_INTEGER ud0;
        DWORD ud1;

        if (bSign) {
            //
            //  Cast d to LONGLONG first otherwise -0x80000000 sign extends
            //  incorrectly
            //
            ud0.QuadPart = (DWORDLONG)(-(LONGLONG)d);
            if (d > 0) {
                /*  -d < 0 */
                ud1 = (DWORD)-1;
            } else {
                ud1 = (DWORD)0;
            }
        } else {
            ud0.QuadPart = (DWORDLONG)d;
            if (d < 0) {
                ud1 = (DWORD)-1;
            } else {
                ud1 = (DWORD)0;
            }
        }
        /*  Now do extended addition */
        ULARGE_INTEGER uliTotal;

        /*  Add ls DWORDs */
        uliTotal.QuadPart  = (DWORDLONG)ud0.LowPart + p0.LowPart;
        p0.LowPart       = uliTotal.LowPart;

        /*  Propagate carry */
        uliTotal.LowPart   = uliTotal.HighPart;
        uliTotal.HighPart  = 0;

        /*  Add 2nd most ls DWORDs */
        uliTotal.QuadPart += (DWORDLONG)ud0.HighPart + p0.HighPart;
        p0.HighPart      = uliTotal.LowPart;

        /*  Add MS DWORDLONGs - no carry expected */
        p1 += ud1 + uliTotal.HighPart;

        /*  Now see if we got a sign change from the addition */
        if ((LONG)p1 < 0) {
            bSign = !bSign;

            /*  Negate the current value (ugh!) */
            p0.QuadPart  = ~p0.QuadPart;
            p1 = ~p1;
            p0.QuadPart += 1;
            p1 += (p0.QuadPart == 0);
        }
    }

    /*  Now for the division */
    if (c < 0) {
        bSign = !bSign;
    }

    /*  This will catch c == 0 and overflow */
    if (uc <= p1) {
        return bSign ? (LONGLONG)0x8000000000000000 :
                       (LONGLONG)0x7FFFFFFFFFFFFFFF;
    }

    /*  Do the division */

    /*  If the divisor is a DWORD then its simpler */
    ULARGE_INTEGER uliDividend;
    ULARGE_INTEGER uliResult;
    DWORD dwDivisor = uc;
    uliDividend.HighPart = p1;
    uliDividend.LowPart = p0.HighPart;
    /*  NOTE - this routine will take exceptions if
        the result does not fit in a DWORD
    */
    if (uliDividend.QuadPart >= (DWORDLONG)dwDivisor) {
        uliResult.HighPart = EnlargedUnsignedDivide(
                                 uliDividend,
                                 dwDivisor,
                                 &p0.HighPart);
    } else {
        uliResult.HighPart = 0;
    }
    uliResult.LowPart = EnlargedUnsignedDivide(
                             p0,
                             dwDivisor,
                             NULL);
    return bSign ? -(LONGLONG)uliResult.QuadPart :
                    (LONGLONG)uliResult.QuadPart;
}

#ifdef DEBUG
/******************************Public*Routine******************************\
* Debug CCritSec helpers
*
* We provide debug versions of the Constructor, destructor, Lock and Unlock
* routines.  The debug code tracks who owns each critical section by
* maintaining a depth count.
*
* History:
*
\**************************************************************************/

CCritSec::CCritSec()
{
    InitializeCriticalSection(&m_CritSec);
    m_currentOwner = m_lockCount = 0;
    m_fTrace = FALSE;
}

CCritSec::~CCritSec()
{
    DeleteCriticalSection(&m_CritSec);
}

void CCritSec::Lock()
{
    UINT tracelevel=3;
    DWORD us = GetCurrentThreadId();
    DWORD currentOwner = m_currentOwner;
    if (currentOwner && (currentOwner != us)) {
        // already owned, but not by us
        if (m_fTrace) {
            DbgLog((LOG_LOCKING, 2, TEXT("Thread %d about to wait for lock %x owned by %d"),
                GetCurrentThreadId(), &m_CritSec, currentOwner));
            tracelevel=2;
    // if we saw the message about waiting for the critical
    // section we ensure we see the message when we get the
    // critical section
        }
    }
    EnterCriticalSection(&m_CritSec);
    if (0 == m_lockCount++) {
        // we now own it for the first time.  Set owner information
        m_currentOwner = us;
//ASSERT(((PRTL_CRITICAL_SECTION)&m_CritSec)->OwningThread == (HANDLE)m_currentOwner);
// only valid on NT
        if (m_fTrace) {
            DbgLog((LOG_LOCKING, tracelevel, TEXT("Thread %d now owns lock %x"), m_currentOwner, &m_CritSec));
        }
    }
}

void CCritSec::Unlock() {
    if (0 == --m_lockCount) {
        // about to be unowned
        if (m_fTrace) {
            DbgLog((LOG_LOCKING, 3, TEXT("Thread %d releasing lock %x"), m_currentOwner, &m_CritSec));
    //ASSERT(((PRTL_CRITICAL_SECTION)&m_CritSec)->OwningThread == (HANDLE)m_currentOwner);
    // only valid on NT
        }
        m_currentOwner = 0;
    }
    LeaveCriticalSection(&m_CritSec);
}

void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace)
{
    pcCrit->m_fTrace = fTrace;
}

BOOL WINAPI CritCheckIn(CCritSec * pcCrit)
{
    return (GetCurrentThreadId() == pcCrit->m_currentOwner);
}

BOOL WINAPI CritCheckOut(CCritSec * pcCrit)
{
    return (GetCurrentThreadId() != pcCrit->m_currentOwner);
}
#endif

typedef BSTR (STDAPICALLTYPE *LPSYSALLOCSTRING)(const OLECHAR FAR *);

// Dyna-link to SysAllocString to copy BSTR strings

STDAPI WriteBSTR(BSTR *pstrDest, LPCWSTR szSrc)
{
    LPSYSALLOCSTRINGlpfnSysAllocString;
    HINSTANCEhInst;
    static const charszSysAllocString[]  = "SysAllocString";

    //
    // Try to get the Ole32Aut.dll module handle.
    //

    hInst = LoadOLEAut32();
    if (hInst == NULL) {
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
    }
    lpfnSysAllocString = (LPSYSALLOCSTRING)GetProcAddress(hInst,
  szSysAllocString);
    if (lpfnSysAllocString == NULL) {
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
    }

    *pstrDest = (*lpfnSysAllocString)(szSrc);

    return S_OK;
}

// Free an OLE BSTR through the task allocator

STDAPI FreeBSTR(BSTR* pstr)
{
    if (*pstr != NULL) {
        // get pointer to string
        DWORD* p = (DWORD*) (*pstr);

        // back up to point at DWORD length
        p--;

        // set pointer to null
        *pstr = NULL;

        // and free this
        CoTaskMemFree(p);

        return S_OK;
    } else {
        return S_FALSE;
    }
}

// Return a wide string - allocating memory for it
// Returns:
//    S_OK          - no error
//    E_POINTER     - ppszReturn == NULL
//    E_OUTOFMEMORY - can't allocate memory for returned string
STDAPI AMGetWideString(LPCWSTR psz, LPWSTR *ppszReturn)
{
    CheckPointer(ppszReturn, E_POINTER);
    ValidateReadWritePtr(ppszReturn, sizeof(LPWSTR));
    DWORD nameLen = sizeof(WCHAR) * (lstrlenW(psz)+1);
    *ppszReturn = (LPWSTR)CoTaskMemAlloc(nameLen);
    if (*ppszReturn == NULL) {
       return E_OUTOFMEMORY;
    }
    CopyMemory(*ppszReturn, psz, nameLen);
    return NOERROR;
}

// Waits for the HANDLE hObject.  While waiting messages sent
// to windows on our thread by SendMessage will be processed.
// Using this function to do waits and mutual exclusion
// avoids some deadlocks in objects with windows.
// Return codes are the same as for WaitForSingleObject
DWORD WINAPI WaitDispatchingMessages(HANDLE hObject, DWORD dwWait, HWND hwnd, UINT uMsg)
{
    BOOL bPeeked = FALSE;
    DWORD dwResult;
    DWORD dwStart;
    DWORD dwThreadPriority;
    if (dwWait != INFINITE && dwWait != 0) {
        dwStart = GetTickCount();
    }
    for (; ; ) {
        DWORD dwTimeOut = dwWait;
        if (dwTimeOut > 10) {
            dwTimeOut = 10;
        }
        dwResult = MsgWaitForMultipleObjects(
                             1,
                             &hObject,
                             FALSE,
                             dwTimeOut,
                             hwnd == NULL ? QS_SENDMESSAGE :
                                            QS_SENDMESSAGE + QS_POSTMESSAGE);
        if (dwResult == WAIT_OBJECT_0 + 1 ||
            dwResult == WAIT_TIMEOUT && dwTimeOut != dwWait) {
            MSG msg;
            if (hwnd != NULL) {
                while (PeekMessage(&msg, hwnd, uMsg, uMsg, PM_REMOVE)) {
                    DispatchMessage(&msg);
                }
            }
            // Do this anyway - the previous peek doesn't flush out the
            // messages
            PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);

            if (dwWait != INFINITE && dwWait != 0) {
                DWORD dwNow = GetTickCount();

                // Working with differences handles wrap-around
                DWORD dwDiff = dwNow - dwStart;
                if (dwDiff > dwWait) {
                    dwWait = 0;
                } else {
                    dwWait -= dwDiff;
                }
                dwStart = dwNow;
            }
            if (!bPeeked) {
                //  Raise our priority to prevent our message queue
                //  building up
                dwThreadPriority = GetThreadPriority(GetCurrentThread());
                if (dwThreadPriority < THREAD_PRIORITY_HIGHEST) {
                    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
                }
                bPeeked = TRUE;
            }
        } else {
            break;
        }
    }
    if (bPeeked) {
        SetThreadPriority(GetCurrentThread(), dwThreadPriority);
        if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
            //  Send dummy message
            PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
        }
    }
    return dwResult;
}


WXUTIL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// helper classes and functions for building multimedia filters
//

#ifndef __WXUTIL__
#define __WXUTIL__

// eliminate spurious "statement has no effect" warnings.
#pragma warning(disable: 4705)

// wrapper for whatever critical section we have
class CCritSec {

    // make copy constructor and assignment operator inaccessible

    CCritSec(const CCritSec &refCritSec);
    CCritSec &operator=(const CCritSec &refCritSec);

    CRITICAL_SECTION m_CritSec;

#ifdef DEBUG
public:
    DWORD   m_currentOwner;
    DWORD   m_lockCount;
    BOOL    m_fTrace;        // Trace this one
public:
    CCritSec();
    ~CCritSec();
    void Lock();
    void Unlock();
#else

public:
    CCritSec() {
InitializeCriticalSection(&m_CritSec);
    };

    ~CCritSec() {
DeleteCriticalSection(&m_CritSec);
    };

    void Lock() {
EnterCriticalSection(&m_CritSec);
    };

    void Unlock() {
LeaveCriticalSection(&m_CritSec);
    };
#endif
};

//
// To make deadlocks easier to track it is useful to insert in the
// code an assertion that says whether we own a critical section or
// not.  We make the routines that do the checking globals to avoid
// having different numbers of member functions in the debug and
// retail class implementations of CCritSec.  In addition we provide
// a routine that allows usage of specific critical sections to be
// traced.  This is NOT on by default - there are far too many.
//

#ifdef DEBUG
    BOOL WINAPI CritCheckIn(CCritSec * pcCrit);
    BOOL WINAPI CritCheckOut(CCritSec * pcCrit);
    void WINAPI DbgLockTrace(BOOL fTrace);
#else
    #define CritCheckIn(x) TRUE
    #define CritCheckOut(x) TRUE
    #define DbgLockTrace(pc, fT)
#endif

// locks a critical section, and unlocks it automatically
// when the lock goes out of scope
class CAutoLock {

    // make copy constructor and assignment operator inaccessible

    CAutoLock(const CAutoLock &refAutoLock);
    CAutoLock &operator=(const CAutoLock &refAutoLock);

protected:
    CCritSec * m_pLock;

public:
    CAutoLock(CCritSec * plock)
    {
        m_pLock = plock;
        m_pLock->Lock();
    };

    ~CAutoLock() {
        m_pLock->Unlock();
    };
};

// wrapper for event objects
class CAMEvent
{

    // make copy constructor and assignment operator inaccessible

    CAMEvent(const CAMEvent &refEvent);
    CAMEvent &operator=(const CAMEvent &refEvent);

protected:
    HANDLE m_hEvent;
public:
    CAMEvent(BOOL fManualReset = FALSE);
    ~CAMEvent();

    // Cast to HANDLE - we don't support this as an lvalue
    operator HANDLE () const { return m_hEvent; };

    void Set() { SetEvent(m_hEvent); };
    BOOL Wait(DWORD dwTimeout = INFINITE) {
return (WaitForSingleObject(m_hEvent, dwTimeout) == WAIT_OBJECT_0);
    };
    void Reset() { ResetEvent(m_hEvent); };
    BOOL Check() { return Wait(0); };
};

// wrapper for event objects that do message processing
// This adds ONE method to the CAMEvent object to allow sent
// messages to be processed while waiting

class CAMMsgEvent : public CAMEvent
{

public:

    // Allow SEND messages to be processed while waiting
    BOOL WaitMsg(DWORD dwTimeout = INFINITE);
};

// old name supported for the time being
#define CTimeoutEvent CAMEvent

// support for a worker thread

// simple thread class supports creation of worker thread, synchronization
// and communication. Can be derived to simplify parameter passing
class AM_NOVTABLE CAMThread {

    // make copy constructor and assignment operator inaccessible

    CAMThread(const CAMThread &refThread);
    CAMThread &operator=(const CAMThread &refThread);

    CAMEvent m_EventSend;
    CAMEvent m_EventComplete;

    DWORD m_dwParam;
    DWORD m_dwReturnVal;

protected:
    HANDLE m_hThread;

    // thread will run this function on startup
    // must be supplied by derived class
    virtual DWORD ThreadProc() = 0;

public:
    CAMThread();
    ~CAMThread();

    CCritSec m_AccessLock;// locks access by client threads
    CCritSec m_WorkerLock;// locks access to shared objects

    // thread initially runs this. param is actually 'this'. function
    // just gets this and calls ThreadProc
    static DWORD WINAPI InitialThreadProc(LPVOID pv);

    // start thread running  - error if already running
    BOOL Create();

    // signal the thread, and block for a response
    //
    DWORD CallWorker(DWORD);

    // accessor thread calls this when done with thread (having told thread
    // to exit)
    void Close() {
        HANDLE hThread = (HANDLE)InterlockedExchange((LONG *)&m_hThread, 0);
        if (hThread) {
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }
    };

    // ThreadExists
    // Return TRUE if the thread exists. FALSE otherwise
    BOOL ThreadExists(void) const
    {
        if (m_hThread == 0) {
            return FALSE;
        } else {
            return TRUE;
        }
    }

    // wait for the next request
    DWORD GetRequest();

    // is there a request?
    BOOL CheckRequest(DWORD * pParam);

    // reply to the request
    void Reply(DWORD);

    // If you want to do WaitForMultipleObjects you'll need to include
    // this handle in your wait list or you won't be responsive
    HANDLE GetRequestHandle() const { return m_EventSend; };

    // Find out what the request was
    DWORD GetRequestParam() const { return m_dwParam; };
};

// CQueue
//
// Implements a simple Queue ADT.  The queue contains a finite number of
// objects, access to which is controlled by a semaphore.  The semaphore
// is created with an initial count (N).  Each time an object is added
// a call to WaitForSingleObject is made on the semaphore's handle.  When
// this function returns a slot has been reserved in the queue for the new
// object.  If no slots are available the function blocks until one becomes
// available.  Each time an object is removed from the queue ReleaseSemaphore
// is called on the semaphore's handle, thus freeing a slot in the queue.
// If no objects are present in the queue the function blocks until an
// object has been added.

#define DEFAULT_QUEUESIZE   2

template <class T> class CQueue {
private:
    HANDLE          hSemPut;        // Semaphore controlling queue "putting"
    HANDLE          hSemGet;        // Semaphore controlling queue "getting"
    CRITICAL_SECTION CritSect;      // Thread seriallization
    int             nMax;           // Max objects allowed in queue
    int             iNextPut;       // Array index of next "PutMsg"
    int             iNextGet;       // Array index of next "GetMsg"
    T              *QueueObjects;   // Array of objects (ptr's to void)

    void Initialize(int n) {
        iNextPut = iNextGet = 0;
        nMax = n;
        InitializeCriticalSection(&CritSect);
        hSemPut = CreateSemaphore(NULL, n, n, NULL);
        hSemGet = CreateSemaphore(NULL, 0, n, NULL);
        QueueObjects = new T[n];
    }

public:
    CQueue(int n) {
        Initialize(n);
    }

    CQueue() {
        Initialize(DEFAULT_QUEUESIZE);
    }

    ~CQueue() {
        delete [] QueueObjects;
        DeleteCriticalSection(&CritSect);
        CloseHandle(hSemPut);
        CloseHandle(hSemGet);
    }

    T GetQueueObject() {
        int iSlot;
        T Object;
        LONG lPrevious;

        // Wait for someone to put something on our queue, returns straight
        // away is there is already an object on the queue.
        //
        WaitForSingleObject(hSemGet, INFINITE);

        EnterCriticalSection(&CritSect);
        iSlot = iNextGet++ % nMax;
        Object = QueueObjects[iSlot];
        LeaveCriticalSection(&CritSect);

        // Release anyone waiting to put an object onto our queue as there
        // is now space available in the queue.
        //
        ReleaseSemaphore(hSemPut, 1L, &lPrevious);
        return Object;
    }

    void PutQueueObject(T Object) {
        int iSlot;
        LONG lPrevious;

        // Wait for someone to get something from our queue, returns straight
        // away is there is already an empty slot on the queue.
        //
        WaitForSingleObject(hSemPut, INFINITE);

        EnterCriticalSection(&CritSect);
        iSlot = iNextPut++ % nMax;
        QueueObjects[iSlot] = Object;
        LeaveCriticalSection(&CritSect);

        // Release anyone waiting to remove an object from our queue as there
        // is now an object available to be removed.
        //
        ReleaseSemaphore(hSemGet, 1L, &lPrevious);
    }
};

// miscellaneous string conversion functions
// NOTE: as we need to use the same binaries on Win95 as on NT this code should
// be compiled WITHOUT unicode being defined.  Otherwise we will not pick up
// these internal routines and the binary will not run on Win95.

#ifndef UNICODE
#define wsprintfW wsprintfWInternal
int WINAPIV wsprintfWInternal(LPWSTR, LPCWSTR, ...);

#define lstrcpyW lstrcpyWInternal
LPWSTR
WINAPI
lstrcpyWInternal(
    LPWSTR lpString1,
    LPCWSTR lpString2
    );
#define lstrcpynW lstrcpynWInternal
LPWSTR
WINAPI
lstrcpynWInternal(
    LPWSTR lpString1,
    LPCWSTR lpString2,
    int     iMaxLength
    );
#define lstrcmpW lstrcmpWInternal
int
WINAPI
lstrcmpWInternal(
    LPCWSTR lpString1,
    LPCWSTR lpString2
    );
#define lstrcmpiW lstrcmpiWInternal
int
WINAPI
lstrcmpiWInternal(
    LPCWSTR lpString1,
    LPCWSTR lpString2
    );
#define lstrlenW lstrlenWInternal
int
WINAPI
lstrlenWInternal(
    LPCWSTR lpString
    );
#endif

extern "C"
void * __stdcall memmoveInternal(void *, const void *, size_t);

inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt)
{
#ifdef _X86_
    void *pRet = NULL;

    _asm {
        cld                 // make sure we get the direction right
        mov     ecx, cnt    // num of bytes to scan
        mov     edi, buf    // pointer byte stream
        mov     eax, chr    // byte to scan for
        repne   scasb       // look for the byte in the byte stream
        jnz     exit_memchr // Z flag set if byte found
        dec     edi         // scasb always increments edi even when it
                            // finds the required byte
        mov     pRet, edi
exit_memchr:
    }
    return pRet;

#else
    while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) {
        buf = (unsigned char *)buf + 1;
        cnt--;
    }

    return(cnt ? (void *)buf : NULL);
#endif
}

void WINAPI IntToWstr(int i, LPWSTR wstr);

#define WstrToInt(sz) atoiW(sz)

inline int atoiW(const WCHAR *sz)
{
    int i = 0;

    while (*sz && *sz >= L'0' && *sz <= L'9')
    i = i*10 + *sz++ - L'0';

    return i;
}

inline int WINAPI atoiA(const CHAR *sz)
{
    int i = 0;

    while (*sz && *sz >= '0' && *sz <= '9')
    i = i*10 + *sz++ - '0';

    return i;
}

#ifdef UNICODE
#define atoi    atoiW
#else
#define atoi    atoiA
#endif

// These are available to help managing bitmap VIDEOINFOHEADER media structures

extern const DWORD bits555[3];
extern const DWORD bits565[3];
extern const DWORD bits888[3];

// These help convert between VIDEOINFOHEADER and BITMAPINFO structures

STDAPI_(const GUID) GetTrueColorType(const BITMAPINFOHEADER *pbmiHeader);
STDAPI_(const GUID) GetBitmapSubtype(const BITMAPINFOHEADER *pbmiHeader);
STDAPI_(WORD) GetBitCount(const GUID *pSubtype);
STDAPI_(TCHAR *) GetSubtypeName(const GUID *pSubtype);
STDAPI_(LONG) GetBitmapFormatSize(const BITMAPINFOHEADER *pHeader);
STDAPI_(DWORD) GetBitmapSize(const BITMAPINFOHEADER *pHeader);
STDAPI_(BOOL) ContainsPalette(const VIDEOINFOHEADER *pVideoInfo);
STDAPI_(const RGBQUAD *) GetBitmapPalette(const VIDEOINFOHEADER *pVideoInfo);

// Compares two interfaces and returns TRUE if they are on the same object
BOOL WINAPI IsEqualObject(IUnknown *pFirst, IUnknown *pSecond);

// This is for comparing pins
#define EqualPins(pPin1, pPin2) IsEqualObject(pPin1, pPin2)

// Arithmetic helper functions

// Compute (a * b + rnd) / c
LONGLONG WINAPI llMulDiv(LONGLONG a, LONGLONG b, LONGLONG c, LONGLONG rnd);
LONGLONG WINAPI Int64x32Div32(LONGLONG a, LONG b, LONG c, LONG rnd);

// Avoids us dyna-linking to SysAllocString to copy BSTR strings
STDAPI WriteBSTR(BSTR * pstrDest, LPCWSTR szSrc);
STDAPI FreeBSTR(BSTR* pstr);

// Return a wide string - allocating memory for it
// Returns:
//    S_OK          - no error
//    E_POINTER     - ppszReturn == NULL
//    E_OUTOFMEMORY - can't allocate memory for returned string
STDAPI AMGetWideString(LPCWSTR pszString, LPWSTR *ppszReturn);

// Special wait for objects owning windows
DWORD WINAPI WaitDispatchingMessages(HANDLE hObject, DWORD dwWait, HWND hwnd = NULL, UINT uMsg = 0);
#endif /* __WXUTIL__ */


CONTPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// This class implements the property page for the contrast filter

#include <streams.h>
#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include "resource.h"
#include "contuids.h"
#include "icontrst.h"
#include "contrast.h"
#include "contprop.h"

//
// CreateInstance
//
// This goes in the factory template table to create new filter instances
//
CUnknown * WINAPI CContrastProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    CUnknown *punk = new CContrastProperties(lpunk, phr);
    if (punk == NULL) {
*phr = E_OUTOFMEMORY;
    }
    return punk;

} // CreateInstance

//
// Constructor
//
CContrastProperties::CContrastProperties(LPUNKNOWN pUnk, HRESULT *phr) :
    CBasePropertyPage(NAME("Contrast Property Page"),pUnk,
                      IDD_CONTRASTPROP,
                      IDS_TITLE),
    m_pContrast(NULL)
{
    InitCommonControls();

} // (Constructor)

//
// SetDirty
//
// Sets m_bDirty and notifies the property page site of the change
//
void CContrastProperties::SetDirty()
{
    m_bDirty = TRUE;
    if (m_pPageSite) {
        m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
    }

} // SetDirty

//
// OnReceiveMessage
//
// Virtual method called by base class with Window messages
//
BOOL CContrastProperties::OnReceiveMessage(HWND hwnd,
                                           UINT uMsg,
                                           WPARAM wParam,
                                           LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
    m_hwndSlider = CreateSlider(hwnd);
    ASSERT(m_hwndSlider);
            return (LRESULT) 1;
        }
        case WM_VSCROLL:
        {
    ASSERT(m_hwndSlider);
    OnSliderNotification(wParam);
            return (LRESULT) 1;
        }

        case WM_COMMAND:
        {
    if (LOWORD(wParam) == IDB_DEFAULT)
            {
        pIContrast()->put_DefaultContrastLevel();
        SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, 0L);
                SetDirty();
    }
            return (LRESULT) 1;
        }

        case WM_DESTROY:
        {
    DestroyWindow(m_hwndSlider);
            return (LRESULT) 1;
        }

    }
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);

} // OnReceiveMessage

//
// OnConnect
//
// Called when the property page connects to a filter
//
HRESULT CContrastProperties::OnConnect(IUnknown *pUnknown)
{
    ASSERT(m_pContrast == NULL);

    HRESULT hr = pUnknown->QueryInterface(IID_IContrast, (void **) &m_pContrast);
    if (FAILED(hr)) {
        return E_NOINTERFACE;
    }

    ASSERT(m_pContrast);

    // Get the initial contrast value
    m_pContrast->get_ContrastLevel(&m_cContrastLevel);
    m_cContrastOnExit = m_cContrastLevel;
    return NOERROR;

} // OnConnect

//
// OnDisconnect
//
// Called when we're disconnected from a filter
//
HRESULT CContrastProperties::OnDisconnect()
{
    // Release of Interface after setting the appropriate contrast value

    if (m_pContrast == NULL) {
        return E_UNEXPECTED;
    }

    m_pContrast->put_ContrastLevel(m_cContrastOnExit, 0);
    m_pContrast->Release();
    m_pContrast = NULL;
    return NOERROR;

} // OnDisconnect

//
// OnDeactivate
//
// We are being deactivated
//
HRESULT CContrastProperties::OnDeactivate(void)
{
    // Remember the present contrast level for the next activate

    pIContrast()->get_ContrastLevel(&m_cContrastLevel);
    return NOERROR;

} // OnDeactivate

//
// OnApplyChanges
//
// Changes made should be kept. Change the m_cContrastOnExit variable
//
HRESULT CContrastProperties::OnApplyChanges()
{
    pIContrast()->get_ContrastLevel(&m_cContrastOnExit);
    m_bDirty = FALSE;
    return(NOERROR);

} // OnApplyChanges

//
// CreateSlider
//
// Create the slider (common control) to allow the user to adjust contrast
//
HWND CContrastProperties::CreateSlider(HWND hwndParent)
{
    ULONG Styles = WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_VERT | TBS_BOTH | WS_GROUP;
    HWND hwndSlider = CreateWindow( TRACKBAR_CLASS,
    TEXT(""),
    Styles,
    62, 35,
    39, 100,
    hwndParent,
    NULL,
    g_hInst,
    NULL);
    if (hwndSlider == NULL) {
DWORD dwErr = GetLastError();
return NULL;
    }

    // Set the initial range for the slider
    SendMessage(hwndSlider, TBM_SETRANGE, TRUE, MAKELONG(MinContrastLevel, MaxContrastLevel) );

    // Set a tick at zero
    SendMessage(hwndSlider, TBM_SETTIC, 0, 0L);

    // Set the initial slider position
    SendMessage(hwndSlider, TBM_SETPOS, TRUE, m_cContrastLevel);

    return hwndSlider;

} // CreateSlider

//
// OnSliderNotification
//
// Handle the notification messages from the slider control
//
void CContrastProperties::OnSliderNotification(WPARAM wParam)
{
    switch (wParam) {
        case TB_BOTTOM:
            SetDirty();
    SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, (LPARAM) MinContrastLevel);
    break;

        case TB_TOP:
            SetDirty();
        SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, (LPARAM) MaxContrastLevel);
            break;

        case TB_PAGEDOWN:
        case TB_PAGEUP:
            break;

        case TB_THUMBPOSITION:
        case TB_ENDTRACK:
        {
                SetDirty();
                signed char Level = (signed char) SendMessage(m_hwndSlider, TBM_GETPOS, 0, 0L);
        pIContrast()->put_ContrastLevel(Level, 0);
        }
break;

        // Default handling of these messages is ok
        case TB_THUMBTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
    break;
    }

} // OnSliderNotification


CONTPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// This class implements the property page for the contrast filter

class CContrastProperties : public CBasePropertyPage
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

private:

    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnDeactivate();
    HRESULT OnApplyChanges();

    void SetDirty();

    HWNDCreateSlider(HWND hwndParent);
    voidOnSliderNotification(WPARAM wParam);
    HWNDm_hwndSlider;

    CContrastProperties(LPUNKNOWN lpunk, HRESULT *phr);
    signed char m_cContrastOnExit; // Remember contrast level for CANCEL
    signed char m_cContrastLevel;  // And likewise for next activate

    IContrast *m_pContrast;

    IContrast *pIContrast() {
        ASSERT(m_pContrast);
        return m_pContrast;
    };

}; // CContrastProperties


CONTRAST.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <windows.h>
#include <streams.h>
#include <initguid.h>
#include <olectl.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#endif
#include "contuids.h"
#include "icontrst.h"
#include "contprop.h"
#include "contrast.h"

//
//
// What this sample illustrates
//
// A simple transform filter that adjusts contrast on a video stream. We use
// eight bit palettised images exclusively and can increase or decrease the
// contrast. The contrast is adjusted through a custom interface we support.
// We have a property page that uses this interface to respond to user action
//
// Summary
//
// This is a sample transform filter - we have a single input pin and also a
// single output pin. The input pin accepts only palettised eight bit video
// formats and our output pin also provides an eight bit palettised format.
// We will normally be put between a video decoder (AVI or MPEG) and a video
// renderer. We adjust the contrast of the video images sent through us, by
// increasing contrast the colours become more suturated and likewise less
// contrast reduces the colour into some grey shades and finally to all grey
//
//
// Implementation
//
// We adjust contrast using a neat trick with palettes, the colour palette of
// an image effectively determines how the image is interpreted. So in short
// how the value 23 (for example) is turned into an RGB triplet for display.
// By changing the palette we can reduce and increase contrast without doing
// anything to the actual image pixels themselves (which makes us go faster).
// So by adjusting the palette in band we can adjust the pixel interpretion.
//
// The runtime video renderer supports palette changes in band, when we want
// to change palettes we pick the new colours and attach a VIDEOINFO to the
// next image we send to the renderer. It will then take that palette out of
// the sample and realise a new palette in the window for us. The trigger to
// change the palette is through a custom interface this filter provides.
//
// The custom interface we implement is called IContrast, this allows someone
// to increase and decrease contrast levels for a given video. The normal way
// for the interface to be used is to right click on the contrast filter in
// GRAPHEDT and bring up our property page. This has a slider control on it
// that can be used to adjust the contrast level. Each time the slider moves
// it will ask for a contrast change by calling IContrast. To expose property
// pages a filter implements the ISpecifyPropertyPages interface (it has only
// one method called GetPages which returns the CLSIDs for the property pages)
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Video Renderer"
// Disconnect the renderer from its source filter and insert a contrast in
// between them (the contrast is added to the graph using the Graph Menu and
// selecting Insert Filters, the filter is in the dialog box list). Once the
// contrast filter is inserted run the graph. Right click on the contrast
// box and select properties which brings up a property page that contains a
// slider. The slider can be dragged up and down to adjust the contrast level
//
//
// Files
//
// contprop.cpp         Property page implementation with a slider control
// contprop.h           Class definition for the property page
// contprop.rc          Dialog box template for the property page
// contrast.cpp         Main contrast filter class implementation
// contrast.def         What APIs we import and export from this DLL
// contrast.h           Class definition for the contrast filter
// contrast.reg         What goes in the registry to make us work
// contuids.h           The transform filter CLSIDs
// icontrst.h           Defines the custom contrast filter interface
// makefile             How to build it...
// resource.h           Microsoft Visual C++ generated resource file
//
//
// Base classes we use
//
// CTransformFilter     A generic transform filter that has a single input
//                      and a single output pin. It has some PURE virtual
//                      methods we must override (such as the Transform
//                      method that does the real work). We also use it's
//                      CUnknown base class to implement IUnknown for the
//                      IContrast and ISpecifyPropertyPages we also support
//
//

// setup data

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Video,       // Major type
    &MEDIASUBTYPE_NULL      // Minor type
};

const AMOVIESETUP_PIN psudPins[] =
{
    {
        L"Input",           // String pin name
        FALSE,              // Is it rendered
        FALSE,              // Is it an output
        FALSE,              // Allowed none
        FALSE,              // Allowed many
        &CLSID_NULL,        // Connects to filter
        L"Output",          // Connects to pin
        1,                  // Number of types
        &sudPinTypes },     // The pin details
      { L"Output",          // String pin name
        FALSE,              // Is it rendered
        TRUE,               // Is it an output
        FALSE,              // Allowed none
        FALSE,              // Allowed many
        &CLSID_NULL,        // Connects to filter
        L"Input",           // Connects to pin
        1,                  // Number of types
        &sudPinTypes        // The pin details
    }
};

const AMOVIESETUP_FILTER sudContrast =
{
    &CLSID_Contrast,        // Filter CLSID
    L"Video Contrast",      // Filter name
    MERIT_DO_NOT_USE,       // Its merit
    2,                      // Number of pins
    psudPins                // Pin details
};

// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance

CFactoryTemplate g_Templates[2] = {

    { L"Video Contrast"
    , &CLSID_Contrast
    , CContrast::CreateInstance
    , NULL
    , &sudContrast }
  ,
    { L"Video Contrast Property Page"
    , &CLSID_ContrastPropertyPage
    , CContrastProperties::CreateInstance }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// Constructor
//
CContrast::CContrast(TCHAR *tszName,LPUNKNOWN punk,HRESULT *phr) :
    CTransformFilter(tszName, punk, CLSID_Contrast),
    m_DefaultContrastLevel(0),
    m_ContrastLevel(m_DefaultContrastLevel),
    m_PrevLevel(m_ContrastLevel),
    m_lBufferRequest(1)
{
    ASSERT(tszName);
    ASSERT(phr);

} // Contrast

//
// CreateInstance
//
// Provide the way for COM to create a CContrast object
//
CUnknown * WINAPI CContrast::CreateInstance(LPUNKNOWN punk, HRESULT *phr) {

    CContrast *pNewObject = new CContrast(NAME("Contrast"), punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return pNewObject;

} // CreateInstance

//
// NonDelegatingQueryInterface
//
// Reveals IContrast and ISpecifyPropertyPages
//
STDMETHODIMP CContrast::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);

    if (riid == IID_IContrast) {
        return GetInterface((IContrast *) this, ppv);
    } else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else {
        return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
    }

} // NonDelegatingQueryInterface

//
// Transform
//
// Copy the input sample into the output sample
// Then transform the output sample 'in place'
//
HRESULT CContrast::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
    HRESULT hr = Copy(pIn, pOut);
    if (FAILED(hr)) {
        return hr;
    }
    return Transform(pOut);

} // Transform

//
// Copy
//
// Make destination an identical copy of source
//
HRESULT CContrast::Copy(IMediaSample *pSource, IMediaSample *pDest) const
{
    // Copy the sample data

    BYTE *pSourceBuffer, *pDestBuffer;
    long lSourceSize = pSource->GetActualDataLength();
    long lDestSize= pDest->GetSize();

    ASSERT(lDestSize >= lSourceSize);

    pSource->GetPointer(&pSourceBuffer);
    pDest->GetPointer(&pDestBuffer);

    CopyMemory( (PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize);

    // Copy the sample times

    REFERENCE_TIME TimeStart, TimeEnd;
    if (NOERROR == pSource->GetTime(&TimeStart, &TimeEnd)) {
        pDest->SetTime(&TimeStart, &TimeEnd);
    }

    LONGLONG MediaStart, MediaEnd;
    if (pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) {
        pDest->SetMediaTime(&MediaStart,&MediaEnd);
    }

    // Copy the Sync point property

    HRESULT hr = pSource->IsSyncPoint();
    if (hr == S_OK) {
        pDest->SetSyncPoint(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetSyncPoint(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the media type

    AM_MEDIA_TYPE *pMediaType;
    pSource->GetMediaType(&pMediaType);
    pDest->SetMediaType(pMediaType);
    DeleteMediaType(pMediaType);

    // Copy the preroll property

    hr = pSource->IsPreroll();
    if (hr == S_OK) {
        pDest->SetPreroll(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetPreroll(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the discontinuity property

    hr = pSource->IsDiscontinuity();
    if (hr == S_OK) {
pDest->SetDiscontinuity(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetDiscontinuity(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the actual data length

    long lDataLength = pSource->GetActualDataLength();
    pDest->SetActualDataLength(lDataLength);
    return NOERROR;

} // Copy

//
// Transform
//
// 'In place' adjust the contrast of this sample
//
HRESULT CContrast::Transform(IMediaSample *pMediaSample)
{
    signed char ContrastLevel;

    // Take a copy, so we dont hold the lock
    // for the whole transform. Also ensures
    // we use the same level throughout this!
    {
        CAutoLock cAutoLock(&m_ContrastLock);
ContrastLevel = m_ContrastLevel;
    }

    AM_MEDIA_TYPE *pAdjustedType = NULL;

    pMediaSample->GetMediaType(&pAdjustedType);
    if (pAdjustedType != NULL) {
        if (CheckInputType(&CMediaType(*pAdjustedType)) == S_OK) {
            m_pInput->CurrentMediaType() = *pAdjustedType;
    CoTaskMemFree(pAdjustedType);
} else {
    CoTaskMemFree(pAdjustedType);
    return E_FAIL;
}
    }

    // Pass on format changes to downstream filters

    if ((pAdjustedType != NULL) || (m_PrevLevel != ContrastLevel)) {
CMediaType AdjustedType((AM_MEDIA_TYPE) m_pInput->CurrentMediaType());
        HRESULT hr = Transform(&AdjustedType, ContrastLevel);
        if (hr == S_OK) {
            pMediaSample->SetMediaType(&AdjustedType);
    m_PrevLevel = ContrastLevel;
        } else {
            return hr;
        }
    }
    return NOERROR;

} // Transform

//
// CheckInputType
//
// Check the input type is OK, return an error otherwise
//
HRESULT CContrast::CheckInputType(const CMediaType *mtIn)
{
    // Check this is a VIDEOINFO type

    if (*mtIn->FormatType() != FORMAT_VideoInfo) {
        return E_INVALIDARG;
    }

    // Is this a palettised format

    if (CanChangeContrastLevel(mtIn)) {
    return NOERROR;
    }
    return E_FAIL;

} // CheckInputType

//
// CheckTransform
//
// To be able to transform the formats must be identical
//
HRESULT CContrast::CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut)
{
    HRESULT hr;
    if (FAILED(hr = CheckInputType(mtIn))) {
return hr;
    }

    // format must be a VIDEOINFOHEADER
    if (*mtOut->FormatType() != FORMAT_VideoInfo) {
return E_INVALIDARG;
    }

    // formats must be big enough
    if (mtIn->FormatLength() < sizeof(VIDEOINFOHEADER) ||
mtOut->FormatLength() < sizeof(VIDEOINFOHEADER))
return E_INVALIDARG;

    VIDEOINFO *pInput = (VIDEOINFO *) mtIn->Format();
    VIDEOINFO *pOutput = (VIDEOINFO *) mtOut->Format();
    if (memcmp(&pInput->bmiHeader,&pOutput->bmiHeader,sizeof(BITMAPINFOHEADER)) == 0) {
return NOERROR;
    }

    return E_INVALIDARG;
} // CheckTransform

//
// DecideBufferSize
//
// Tell the output pin's allocator what size buffers we
// require. Can only do this when the input is connected
//
HRESULT CContrast::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
    // Is the input pin connected

    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;
    pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize();

    ASSERT(pProperties->cbBuffer);

    // If we don't have fixed sized samples we must guess some size

    if (!m_pInput->CurrentMediaType().bFixedSizeSamples) {
        if (pProperties->cbBuffer < 100000) {
            // nothing more than a guess!!
            pProperties->cbBuffer = 100000;
        }
    }

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT( Actual.cBuffers == 1 );

    if (pProperties->cBuffers > Actual.cBuffers ||
            pProperties->cbBuffer > Actual.cbBuffer) {
                return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

//
// GetMediaType
//
// I support one type, namely the type of the input pin
// We must be connected to support the single output type
//
HRESULT CContrast::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    // Is the input pin connected

    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    // This should never happen

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    // Do we have more items to offer

    if (iPosition > 0) {
        return VFW_S_NO_MORE_ITEMS;
    }

    *pMediaType = m_pInput->CurrentMediaType();
    return NOERROR;

} // GetMediaType

//
// get_ContrastLevel
//
// Return the current contrast level
//
STDMETHODIMP CContrast::get_ContrastLevel(signed char *ContrastLevel)
{
    CAutoLock cAutoLock(&m_ContrastLock);
    *ContrastLevel = m_ContrastLevel;
    return NOERROR;

} // get_ContrastLevel

//
// put_ContrastLevel
//
// Sets the contrast level for the filter
//
STDMETHODIMP CContrast::put_ContrastLevel(signed char ContrastLevel, unsigned long ChangeTime)
{
    CAutoLock cAutoLock(&m_ContrastLock);
    m_ContrastLevel = ContrastLevel;
    return NOERROR;

} // put_ContrastLevel

//
// put_DefaultContrastLevel
//
// Sets the default contrast level for the filter
//
STDMETHODIMP CContrast::put_DefaultContrastLevel()
{
    CAutoLock cAutoLock(&m_ContrastLock);
    m_ContrastLevel = m_DefaultContrastLevel;
    return NOERROR;

} // put_DefaultContrastLevel

//
// GetPages
//
// This is the sole member of ISpecifyPropertyPages
// Returns the clsid's of the property pages we support
//
STDMETHODIMP CContrast::GetPages(CAUUID *pPages)
{
    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_ContrastPropertyPage;
    return NOERROR;

} // GetPages

//
// CanChangeContrastLevel
//
// Check if this is a paletised format
//
BOOL CContrast::CanChangeContrastLevel(const CMediaType *pMediaType) const
{
    if ((IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video))
         && (IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB8))) {

        // I think I can process this format (8 bit palettised)
// So do a quick sanity check on the palette information

VIDEOINFO *pvi = (VIDEOINFO *) pMediaType->Format();
return (pvi->bmiHeader.biBitCount == 8);

    } else {
        return FALSE;
    }

} // CanChangeContrastLevel

//
// Transform
//
// Adjust the palette entries of pType to reflect the specified contrast level
//
HRESULT CContrast::Transform(AM_MEDIA_TYPE *pType, const signed char ContrastLevel) const
{
    VIDEOINFO *pvi = (VIDEOINFO *) pType->pbFormat;

    if (ContrastLevel >= 0) {
        int Low= 0 + m_ContrastLevel;
        int High= 255 - m_ContrastLevel;
float Grad= ((float)(High - Low)) / 255;

        for (UINT i = 0; i < pvi->bmiHeader.biClrUsed; i++) {

            IncreaseContrast(&pvi->bmiColors[i], Low, High, Grad);
        }
    } else {
        float Grad = 255 / (255 + (float) ContrastLevel + (float) ContrastLevel);

        for (UINT i = 0; i < pvi->bmiHeader.biClrUsed; i++) {
            DecreaseContrast(&pvi->bmiColors[i], ContrastLevel, Grad);
        }
    }
    return NOERROR;

} // Transform

//
// IncreaseContrast
//
// Adjust the contrast of this palette element
//
inline
void CContrast::IncreaseContrast(RGBQUAD *pElem, const int Low, const int High, const float Grad) const
{
    IncreaseContrast(&pElem->rgbRed, Low, High, Grad);
    IncreaseContrast(&pElem->rgbGreen, Low, High, Grad);
    IncreaseContrast(&pElem->rgbBlue, Low, High, Grad);

} // IncreaseContrast

//
// IncreaseContrast
//
// Change this byte's contrast
//
inline
void CContrast::IncreaseContrast(BYTE *pByte, const int Low, const int High, const float Grad) const
{
    if (*pByte <= Low) {
        *pByte = 0;
    }
    else if ((Low < *pByte) && (*pByte <  High)) {
*pByte = (BYTE)( (*pByte - Low) / Grad);
    }
    else {// pElem->rgbGreen >= High
        *pByte = 255;
    }

} // IncreaseContrast

//
// DecreaseContrast
//
// Adjust the contrast of this palette element
//
inline
void CContrast::DecreaseContrast(RGBQUAD *pElem, const int Level, const float Grad) const
{
    DecreaseContrast(&pElem->rgbRed, Level, Grad);
    DecreaseContrast(&pElem->rgbGreen, Level, Grad);
    DecreaseContrast(&pElem->rgbBlue, Level, Grad);

} // DecreaseContrast

//
// DecreaseContrast
//
// Use different maths to calculate the 'decreasing contrast' line
//
inline
void CContrast::DecreaseContrast(BYTE *pByte, const int Level, const float Grad) const
{
    ASSERT(pByte);
    ASSERT(Grad != 0.0);
    *pByte = (BYTE) ( ((int) (*pByte / Grad)) - Level);

} // DecreaseContrast

//
// DllRegisterServer
//
// Handle registration of this filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer


CONTRAST.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

const signed char MaxContrastLevel = 127;
const signed char MinContrastLevel = -127;

class CContrast : public CTransformFilter,
  public IContrast,
  public ISpecifyPropertyPages
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    // Reveals IContrast & ISpecifyPropertyPages
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    DECLARE_IUNKNOWN;

    HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
    HRESULT CheckInputType(const CMediaType *mtIn);
    HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT DecideBufferSize(IMemAllocator *pAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    // IContrast methods

    STDMETHODIMP get_ContrastLevel(signed char *ContrastLevel);
    STDMETHODIMP put_ContrastLevel(signed char ContrastLevel, unsigned long ChangeTime);
    STDMETHODIMP put_DefaultContrastLevel(void);

    // ISpecifyPropertyPages method

    STDMETHODIMP GetPages(CAUUID *pPages);

private:

    // Constructor
    CContrast(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);

    // Look after the transformation of media samples
    BOOL CanChangeContrastLevel(const CMediaType *pMediaType) const;
    HRESULT Copy(IMediaSample *pSource, IMediaSample *pDest) const;
    HRESULT Transform(IMediaSample *pMediaSample);
    HRESULT Transform(AM_MEDIA_TYPE *pType, const signed char ContrastLevel) const;

    // Handle increasing and decreasing contrast
    void IncreaseContrast(RGBQUAD *pElem, const int Low, const int High, const float Grad) const;
    void IncreaseContrast(BYTE *pByte, const int Low, const int High, const float Grad) const;
    void DecreaseContrast(RGBQUAD *pElem, const int Level, const float Grad) const;
    void DecreaseContrast(BYTE *pByte, const int Level, const float Grad) const;

    // The default (no-change) level
    const signed char m_DefaultContrastLevel;

    // The current level to set the palette to
    signed charm_ContrastLevel;

    // The level the last frame's palette was set at
    signed charm_PrevLevel;

    // The number of buffers to request on the output allocator
    const long m_lBufferRequest;

    // Non interface locking critical section
    CCritSec m_ContrastLock;

}; // CContrast


CONTUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// The CLSID's used by the contrast filter

//
// Contrast Filter Object
//
// { fd501043-8ebe-11ce-8183-00aa00577da1 }
DEFINE_GUID(CLSID_Contrast,
0xfd501043, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);

//
// Contrast Property Page
//
// { fd501044-8ebe-11ce-8183-00aa00577da1 }
DEFINE_GUID(CLSID_ContrastPropertyPage,
0xfd501044, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);


ICONTRST.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// A custom interface to allow the user to adjust the contrast level.

#ifndef __ICONTRAST__
#define __ICONTRAST__

#ifdef __cplusplus
extern "C" {
#endif

    // {fd5010a2-8ebe-11ce-8183-00aa00577da1}
    DEFINE_GUID(IID_IContrast,
    0xfd5010a2, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);

    DECLARE_INTERFACE_(IContrast, IUnknown)
    {
        STDMETHOD(get_ContrastLevel) (THIS_
            signed char *ContrastLevel      // The current contrast level
        ) PURE;

        STDMETHOD(put_ContrastLevel) (THIS_
            signed char ContrastLevel,      // Change to the contrast level
            unsigned long ChangeTime        // Time from now to do the change
        ) PURE;

        STDMETHOD(put_DefaultContrastLevel) (THIS) PURE;
    };

#ifdef __cplusplus
}
#endif

#endif // __ICONTRAST__


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
// Microsoft Visual C++ generated include file.
// Used by contprop.rc
//
#define IDS_STATIC                      -1
#define IDD_CONTRASTPROP                101
#define IDC_CONTRAST                    1000
#define IDB_DEFAULT1001
#define IDS_TITLE                       1003

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1002
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


CLIPS.TXT

;comments have leading semicolons
video.avi,1,2
video.avi,5,10


CLTEXT.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include <streams.h>
  #include <strmif.h>
  #include <cutlist.h>
  #include <stdio.h>
  #include "cltext.h"

  ICutListGraphBuilder  *pCLGraphBuilder  = NULL;
  IStandardCutList      *pVideoCL         = NULL;
  IStandardCutList      *pAudioCL         = NULL;
  IGraphBuilder         *pigb             = NULL;
  IMediaControl         *pimc             = NULL;
  IMediaEventEx         *pimex            = NULL;
  IVideoWindow          *pivw             = NULL;
  IMediaSeeking         *pims             = NULL;

  IFileClip             *pVidFileClip[MAX_CLIPS];
  IFileClip             *pAudFileClip[MAX_CLIPS];
  IAMCutListElement       *pVidCLElem[MAX_CLIPS];
  IAMCutListElement       *pAudCLElem[MAX_CLIPS];

  HRESULT hr;

  int nVidElems, nAudElems;

  void CutlistFromTextfile ()

    { // CutlistFromTextfile //

      WCHAR wFile[MAX_PATH];  // file name

      // Initialize video and audio file clips and elements to NULL
      // so we can easily free objects later.
      for (int x = 0; x < MAX_CLIPS; ++x)

        {
          pVidFileClip[x] = NULL;
          pAudFileClip[x] = NULL;
            pVidCLElem[x] = NULL;
            pAudCLElem[x] = NULL;
        };

      // Create cutlist graph builder object
      hr = CoCreateInstance(CLSID_CutListGraphBuilder, NULL,
                            CLSCTX_INPROC, IID_ICutListGraphBuilder,
                            (void**)&pCLGraphBuilder);

      if (FAILED(hr))
        { // CoCreateInstance of CutListGraphBuiler failed
          MBOX("CoCreateInstance of CutListGraphBuiler failed");
          TearDownTheGraph();
          return;
        } // CoCreateInstance of CutListGraphBuiler failed

      // Create simple (standard) cutlist object for video
      hr = CoCreateInstance(CLSID_SimpleCutList, NULL,
                            CLSCTX_INPROC, IID_IStandardCutList,
                            (void**)&pVideoCL);
      if (FAILED(hr))
        { // CoCreateInstance of video CutlistFromTextfile failed
          MBOX("CoCreateInstance of video CLSID_SimpleCutList failed");
          TearDownTheGraph();
          return;
        } // CoCreateInstance of video CLSID_SimpleCutList failed

      // Create simple (standard) cutlist object for audio
      hr = CoCreateInstance(CLSID_SimpleCutList, NULL,
                            CLSCTX_INPROC, IID_IStandardCutList,
                            (void**)&pAudioCL);

      if (FAILED(hr))
        { // CoCreateInstance of audio CutlistFromTextfile failed
          MBOX("CoCreateInstance of audio CLSID_SimpleCutList failed");
          TearDownTheGraph();
          return;
        } // CoCreateInstance of audio CutlistFromTextfile failed

      // Create the individual clips and add them to the cutlist
      nVidElems = nAudElems = 0;
      for (x = 0; x < gTheSet.nNumClips; ++x)

        { // Individual clips

          MultiByteToWideChar(CP_ACP, 0,
                              gTheSet.List[x].szFilename,
                              -1, wFile, MAX_PATH );

          // Create a video clip object and give it the file and stream
          // to read from.
          // SetFileAndStream will fail if we call it from a video clip
          // object and the clip is not a video clip.
          hr = CoCreateInstance(CLSID_VideoFileClip, NULL,
                                CLSCTX_INPROC, IID_IFileClip,
                                (void**)&pVidFileClip[nVidElems]);

          hr = pVidFileClip[nVidElems]->SetFileAndStream(wFile, 0);

          if (SUCCEEDED(hr))

            { // Create video cut and add the clip (element) to the cutlist

              hr = pVidFileClip[nVidElems]->CreateCut(&pVidCLElem[nVidElems],
                      gTheSet.List[x].start*SCALE,
                      gTheSet.List[x].stop*SCALE,
                      0,
                      (gTheSet.List[x].stop-gTheSet.List[x].start)*SCALE,
                      0);

              if (SUCCEEDED(hr))

                { // Add the element to the cutlist

                  hr = pVideoCL->AddElement(pVidCLElem[nVidElems], CL_DEFAULT_TIME, CL_DEFAULT_TIME);

                  if (SUCCEEDED(hr))
                    ++nVidElems;

                  else

                    { // AddElement failed so release associated objects

                      HELPER_RELEASE(pVidCLElem[nVidElems]);
                      HELPER_RELEASE(pVidFileClip[nVidElems]);
                      MBOX("AddElement (video) failed!");

                    } // AddElement failed so release associated objects

                } // Add the element to the cutlist

              else MBOX("CreateCut (video) failed!");

            } // Create video cut

          else

            { // Problems creating video stream

              HELPER_RELEASE(pVidFileClip[nVidElems]);
              MBOX("SetFileAndStream (video) failed!");

            } // Problems creating video stream

          // Create an audio clip object and give it the file and stream
          // to read from.
          // SetFileAndStream will fail if we call it from an audio clip
          // object and the clip is not an audio clip
          hr = CoCreateInstance(CLSID_AudioFileClip, NULL,
                                CLSCTX_INPROC, IID_IFileClip,
                                (void**)&pAudFileClip[nAudElems]);

          hr = pAudFileClip[nAudElems]->SetFileAndStream(wFile, 0);

          if (SUCCEEDED(hr))

            { // Create audio cut and add the clip (element) to the cutlist

            hr = pAudFileClip[nAudElems]->CreateCut(&pAudCLElem[nAudElems],
                      gTheSet.List[x].start*SCALE,
                      gTheSet.List[x].stop*SCALE,
                      0,
                      (gTheSet.List[x].stop-gTheSet.List[x].start)*SCALE,
                      0);

              if (SUCCEEDED(hr))

                { // Add the element to the cutlist

                  hr = pAudioCL->AddElement(pAudCLElem[nAudElems],
                                            CL_DEFAULT_TIME,
                                            CL_DEFAULT_TIME);

                  if (SUCCEEDED(hr))
                    ++nAudElems;

                  else

                    { // AddElement failed so release associated objects

                      HELPER_RELEASE(pAudCLElem[nAudElems]);
                      HELPER_RELEASE(pAudFileClip[nAudElems]);
                      MBOX("AddElement (audio) failed!");

                    } // AddElement failed so release associated objects

                } // Add the element to the cutlist

              else MBOX("CreateCut (audio) failed!");

            } // Create audio cut

          else

            { // Problems creating audio stream

              HELPER_RELEASE(pAudFileClip[nAudElems]);
              MBOX("SetFileAndStream (audio) failed!");

            } // Problems creating audio stream

        } // Individual clips

      // Add the video cutlist to the filter graph
      hr = pCLGraphBuilder->AddCutList(pVideoCL, NULL);

      if (FAILED(hr)) // AddCutList (video) failed
          MBOX("AddCutList (video) failed");

      // Add the audio cutlist to the filter graph
      hr = pCLGraphBuilder->AddCutList(pAudioCL, NULL);

      if (FAILED(hr)) // AddCutList (audio) failed
          MBOX("AddCutList (audio) failed");

      if ((!gTheSet.nNumClips) || (!pVideoCL) && (!pAudioCL))

        { // Clean up

          TearDownTheGraph();
          return;

        } // Clean up

      // Let the filter graph manager construct the the appropriate graph
      // automatically
      hr = pCLGraphBuilder->Render();

      if (FAILED(hr))
        { // Problems rendering the graph
          if (!AMGetErrorText(hr, gszScratch, 2048))
            MBOX("Problems rendering the graph!");
          else
            MBOX(gszScratch);
          TearDownTheGraph();
          return;
        } // Problems rendering the graph

      // Retrieve the filter graph and useful interfaces
      hr = pCLGraphBuilder->GetFilterGraph(&pigb);

      if (FAILED(hr))
        { // Problems retrieving the graph pointer
          if (!AMGetErrorText(hr, gszScratch, 2048))
            MBOX("Problems retrieving the graph pointer!");
          else
            MBOX(gszScratch);
          TearDownTheGraph();
          return;
        } // Problems retrieving the graph pointer

      // QueryInterface for some basic interfaces
      pigb->QueryInterface(IID_IMediaControl, (void **)&pimc);
      pigb->QueryInterface(IID_IMediaEventEx, (void **)&pimex);
      pigb->QueryInterface(IID_IVideoWindow, (void **)&pivw);
      pigb->QueryInterface(IID_IMediaSeeking, (void **)&pims);

      // Decrement the ref count on the filter graph
      pigb->Release();

      // Prepare to play in the main application window's client area

      RECT rc;
      GetClientRect(ghApp, &rc);
      hr = pivw->put_Owner((OAHWND)ghApp);
      hr = pivw->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS);
      hr = pivw->SetWindowPosition(rc.left, rc.top, rc.right, rc.bottom);

      hr = pims->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
      hr = pims->GetDuration(&glTotalLength);

      SetScrollRange(ghApp, SB_HORZ, 0, 100, TRUE);

      //Ready to rumble...
      pimc->Run();

    } // CutlistFromTextfile //

  void TearDownTheGraph (void)

      { // TearDownTheGraph //

        if (gTimerNum)
          timeKillEvent(gTimerNum);

        if (pimc)
          pimc->Stop();

        if (pivw)

          { // Hide the playback window first thing

            pivw->put_Visible(OAFALSE);
            pivw->put_Owner(NULL);

          } //

        HELPER_RELEASE(pimex);
        HELPER_RELEASE(pimc);
        HELPER_RELEASE(pivw);
        HELPER_RELEASE(pims);

        // Remove the video cutlist from the filter graph to free resources
        if (pCLGraphBuilder && pVideoCL)
          pCLGraphBuilder->RemoveCutList(pVideoCL);

        // Remove the audio cutlist from the filter graph to free resources
        if (pCLGraphBuilder && pAudioCL)
          pCLGraphBuilder->RemoveCutList(pAudioCL);

        for (int x = 0; x < nAudElems; ++x)

          { // Release audio objects

            HELPER_RELEASE(pAudCLElem[x]);
            HELPER_RELEASE(pAudFileClip[x]);

          } // Release audio objects

        for (x = 0; x < nVidElems; ++x)

          { // Release video objects

            HELPER_RELEASE(pVidCLElem[x]);
            HELPER_RELEASE(pVidFileClip[x]);

          } // Release video objects

        HELPER_RELEASE(pVideoCL);
        HELPER_RELEASE(pAudioCL);
        HELPER_RELEASE(pCLGraphBuilder);

        gTheSet.nNumClips = 0;

      } // TearDownTheGraph //

  BOOL GetCliplistTxtFilename (LPSTR szName)

    {   // GetCliplistTxtFilename //

        OPENFILENAME ofn;

        ofn.lStructSize       = sizeof(OPENFILENAME);
        ofn.hwndOwner         = ghApp;
        ofn.lpstrFilter       = NULL;
        ofn.lpstrFilter       = "Text Files (clip list) (*.txt)\0*.txt\0\0\0";
        ofn.lpstrCustomFilter = NULL;
        ofn.nFilterIndex      = 1;
        *szName               = 0;
        ofn.lpstrFile         = szName;
        ofn.nMaxFile          = MAX_PATH;
        ofn.lpstrInitialDir   = NULL;
        ofn.lpstrTitle        = NULL;
        ofn.lpstrFileTitle    = NULL;
        ofn.lpstrDefExt       = "TXT";
        ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

        return GetOpenFileName((LPOPENFILENAME)&ofn);

    }   // GetCliplistTxtFilename //

  LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    { // WndMainProc //

      LONGLONG l;

      switch(message)

        { // Window msgs handling

          case WM_COMMAND:

            switch(wParam)

              {  // Program menu option

                case ID_FILE_EXIT:
                  if (gTheSet.nNumClips)
                    TearDownTheGraph();
                  PostQuitMessage(0);
                  break;

                case ID_CUTLIST_PLAY:
                  l = 0;
                  hr = pimc->Stop();
                  hr = pims->SetPositions(&l, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning);
                  hr = pimc->Run();
                  break;

              }  // Program menu option

            break;

          case WM_HSCROLL:

            if (SB_THUMBPOSITION == LOWORD(wParam))

              { // Seeking via horiz. scroll

                l = (glTotalLength * (LONGLONG)HIWORD(wParam))/100;
                pimc->Stop();
                hr = pims->SetPositions(&l, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning);
                pimc->Run();

              } // Seeking via horiz. scroll

          break;

          case WM_EXITSIZEMOVE:

            RECT rc;
            GetClientRect(ghApp, &rc);
            if (pivw)
              pivw->SetWindowPosition(rc.left, rc.top, rc.right, rc.bottom);
            break;

          case WM_SIZE:
            if ((SIZE_RESTORED == wParam) || (SIZE_MAXIMIZED == wParam))
              PostMessage(ghApp, WM_EXITSIZEMOVE, 0, 0);
            break;

          case WM_DESTROY:
            if (gTheSet.nNumClips)
              TearDownTheGraph();
            PostQuitMessage(0);
            break;

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

        } // Window msgs handling

      return FALSE;

      }  // WndMainProc //

  void ReadTextFile (LPCSTR cFilename)

    { // ReadTextFile //

      FILE  *fp;
      TCHAR cString[MAX_PATH];

      if (!(fp = fopen(cFilename, "rt")))
        return; // File not found!

      while (!feof(fp) && (gTheSet.nNumClips < MAX_CLIPS))

        { // Parse individual lines

          ZeroMemory(cString, MAX_PATH);
          fgets(cString, MAX_PATH, fp);

          if (lstrlen(cString) && (cString[0] != TCHAR(';')))

            { // Decipher filename, start, stop times

              int i = lstrlen(cString);

              while (i && (cString[i] != TCHAR(','))) --i;
              gTheSet.List[gTheSet.nNumClips].stop = atol(&cString[i+1]); --i;
              while (i && (cString[i] != TCHAR(','))) --i;
              gTheSet.List[gTheSet.nNumClips].start = atol(&cString[i+1]);
              cString[i] = TCHAR('\0');
              lstrcpy(gTheSet.List[gTheSet.nNumClips].szFilename, cString);
              gTheSet.nNumClips += 1;

            } // Deciphering

        } // Parse individual lines

      fclose(fp);

    } // ReadTextFile //

  void CALLBACK TimerProc (UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)

    { // TimerProc //

      LONGLONG lPos;

      if (pims)
        pims->GetCurrentPosition(&lPos);
      lPos = (lPos * 100)/glTotalLength;
      SetScrollPos(ghApp, SB_HORZ, (int)lPos, TRUE);

    } // TimerProc //

  int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

    { // WinMain //

      MSG msg;
      WNDCLASS wc;

      // OLE subsystem requires applications to initialize things first!
      CoInitialize(NULL);

      ZeroMemory(&wc, sizeof wc);
      wc.lpfnWndProc = WndMainProc;
      ghInst = wc.hInstance = hInstC;
      wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      wc.lpszClassName = CLASSNAME;
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_CUTLIST));
      RegisterClass(&wc);

      RECT rc;

      GetWindowRect(GetDesktopWindow(), &rc);
      rc.right >>= 1;
      rc.bottom >>= 1;

      ghApp = CreateWindow(CLASSNAME,
      APPLICATIONNAME,
      WS_OVERLAPPEDWINDOW|WS_HSCROLL,
      rc.right-200,
      rc.bottom-200,
      400,
      400,
      0,
      0,
      ghInst,
      0);

      SetMenu(ghApp, ghMenu = LoadMenu(hInstC, MAKEINTRESOURCE(IDR_MENU1)));

      ShowWindow(ghApp, SW_NORMAL);
      UpdateWindow(ghApp);

      ZeroMemory(&gTheSet, sizeof gTheSet);

      // Text-file

      TCHAR szFilename[MAX_PATH];

      if (lstrlen(lpCmdLine))
        ReadTextFile(lpCmdLine);
      else
        if (GetCliplistTxtFilename(szFilename))
          ReadTextFile(szFilename);
        else
          return FALSE;

      if (gTheSet.nNumClips)
        CutlistFromTextfile();

      // Establish a callback-based tracking timer
      gTimerNum = 0;
      if (nVidElems || nAudElems)
        gTimerNum = timeSetEvent(125, 0, TimerProc, 0, TIME_PERIODIC);

      while (GetMessage(&msg,NULL,0,0))

      {  // Message loop

         TranslateMessage(&msg);
         DispatchMessage(&msg);

      }  // Message loop

      DestroyMenu(ghMenu);

      // Finished with OLE subsystem
      CoUninitialize();

      return msg.wParam;

  } // WinMain //


CLTEXT.H

#define IDR_MENU1                       101
#define IDI_CUTLIST                     102
#define ID_FILE_EXIT                    40001
#define ID_CUTLIST_PLAY                 40002
#define IDC_STATIC                -1

#define MAX_CLIPS 150
#define SCALE 10000000   // scale for 1 second of reference time

// Forward prototyping
void TearDownTheGraph (void);

// clip (element) details
struct ClipDetails
  {
    TCHAR szFilename[MAX_PATH];   // name of file containing clip
    REFERENCE_TIME start;         // Start (Trim In) position of clip within file
    REFERENCE_TIME stop;          // Stop (Trim Out) position of clip within file
  };

// cutlist is a collection of clips (elements)
struct ClipCollection
  {
    int nNumClips;
    ClipDetails List[MAX_CLIPS];
  };

#define HELPER_RELEASE(x) { if (x) x->Release(); x = NULL; }

#define APPLICATIONNAME "CLText"
#define CLASSNAME "CLText"

HWND            ghApp;
HMENU           ghMenu;
WORD            wDlgRes;
HINSTANCE       ghInst;
ClipCollection  gTheSet;            // Cutlist
TCHAR           gszScratch[2048];   // General, scratch string (e.g. error msgs)
LONGLONG        glTotalLength;
UINT            gTimerNum;

#define MBOX(a) MessageBox(ghApp, a, APPLICATIONNAME, MB_OK)


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by cltext.rc
//

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SIMPLECL.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include <streams.h>
  #include <strmif.h>
  #include <cutlist.h>
  #include "simplecl.h"

  ICutListGraphBuilder  *pCLGraphBuilder  = NULL;
  IStandardCutList      *pVideoCL         = NULL;
  IStandardCutList      *pAudioCL         = NULL;
  IGraphBuilder         *pigb             = NULL;
  IMediaControl         *pimc             = NULL;
  IMediaEventEx         *pimex            = NULL;
  IVideoWindow          *pivw             = NULL;

  IFileClip             *pVidFileClip[MAX_CLIPS];
  IFileClip             *pAudFileClip[MAX_CLIPS];
  IAMCutListElement       *pVidCLElem[MAX_CLIPS];
  IAMCutListElement       *pAudCLElem[MAX_CLIPS];

  HRESULT hr;

  int nVidElems, nAudElems;

  void SimpleCutList ()

    { // SimpleCutList //

      WCHAR wFile[MAX_PATH];  // file name

      // Initialize video and audio file clips and elements to NULL
      // so we can easily free objects later.
      for (int x = 0; x < MAX_CLIPS; ++x)

        {
          pVidFileClip[x] = NULL;
          pAudFileClip[x] = NULL;
            pVidCLElem[x] = NULL;
            pAudCLElem[x] = NULL;
        };

      // Create cutlist graph builder object
      hr = CoCreateInstance(CLSID_CutListGraphBuilder, NULL,
                            CLSCTX_INPROC, IID_ICutListGraphBuilder,
                            (void**)&pCLGraphBuilder);

      if (FAILED(hr))
        { // CoCreateInstance of CutListGraphBuiler failed
          MessageBox(ghApp,
                     "CoCreateInstance of CutListGraphBuiler failed",
                     APPLICATIONNAME, MB_OK);
          TearDownTheGraph();
          return;
        } // CoCreateInstance of CutListGraphBuiler failed

      // Create simple (standard) cutlist object for video
      hr = CoCreateInstance(CLSID_SimpleCutList, NULL,
                            CLSCTX_INPROC, IID_IStandardCutList,
                            (void**)&pVideoCL);
      if (FAILED(hr))
        { // CoCreateInstance of video SimpleCutList failed
          MessageBox(ghApp,
                     "CoCreateInstance of video SimpleCutList failed",
                     APPLICATIONNAME, MB_OK);
          TearDownTheGraph();
          return;
        } // CoCreateInstance of video SimpleCutList failed

      // Create simple (standard) cutlist object for audio
      hr = CoCreateInstance(CLSID_SimpleCutList, NULL,
                            CLSCTX_INPROC, IID_IStandardCutList,
                            (void**)&pAudioCL);

      if (FAILED(hr))
        { // CoCreateInstance of audio SimpleCutList failed
          MessageBox(ghApp,
                     "CoCreateInstance of audio SimpleCutList failed",
                     APPLICATIONNAME, MB_OK);
          TearDownTheGraph();
          return;
        } // CoCreateInstance of audio SimpleCutList failed

      // Create the individual clips and add them to the cutlist
      nVidElems = nAudElems = 0;
      for (x = 0; x < gTheSet.nNumClips; ++x)

        { // Individual clips

          MultiByteToWideChar(CP_ACP, 0,
                              gTheSet.List[x].szFilename,
                              -1, wFile, MAX_PATH );

          // Create a video clip object and give it the file and stream
          // to read from.
          // SetFileAndStream will fail if we call it from a video clip
          // object and the clip is not a video clip.
          hr = CoCreateInstance(CLSID_VideoFileClip, NULL,
                                CLSCTX_INPROC, IID_IFileClip,
                                (void**)&pVidFileClip[nVidElems]);

          hr = pVidFileClip[nVidElems]->SetFileAndStream(wFile, 0);

          if (SUCCEEDED(hr))

            { // Create video cut and add the clip (element) to the cutlist

              hr = pVidFileClip[nVidElems]->CreateCut(&pVidCLElem[nVidElems],
                      gTheSet.List[x].start*SCALE,
                      gTheSet.List[x].stop*SCALE,
                      0,
                      (gTheSet.List[x].stop-gTheSet.List[x].start)*SCALE,
                      0);

              if (SUCCEEDED(hr))

                { // Add the element to the cutlist

                  hr = pVideoCL->AddElement(pVidCLElem[nVidElems], CL_DEFAULT_TIME, CL_DEFAULT_TIME);

                  if (SUCCEEDED(hr))
                    ++nVidElems;

                  else

                    { // AddElement failed so release associated objects

                      HELPER_RELEASE(pVidCLElem[nVidElems]);
                      HELPER_RELEASE(pVidFileClip[nVidElems]);
                      MessageBox(ghApp, "AddElement (video) failed!", APPLICATIONNAME, MB_OK);

                    } // AddElement failed so release associated objects

                } // Add the element to the cutlist

              else MessageBox(ghApp, "CreateCut (video) failed!", APPLICATIONNAME, MB_OK);

            } // Create video cut

          else

            { // Problems creating video stream

              HELPER_RELEASE(pVidFileClip[nVidElems]);
              MessageBox(ghApp, "SetFileAndStream (video) failed!", APPLICATIONNAME, MB_OK);

            } // Problems creating video stream

          // Create an audio clip object and give it the file and stream
          // to read from.
          // SetFileAndStream will fail if we call it from an audio clip
          // object and the clip is not an audio clip
          hr = CoCreateInstance(CLSID_AudioFileClip, NULL,
                                CLSCTX_INPROC, IID_IFileClip,
                                (void**)&pAudFileClip[nAudElems]);

          hr = pAudFileClip[nAudElems]->SetFileAndStream(wFile, 0);

          if (SUCCEEDED(hr))

            { // Create audio cut and add the clip (element) to the cutlist

            hr = pAudFileClip[nAudElems]->CreateCut(&pAudCLElem[nAudElems],
                      gTheSet.List[x].start*SCALE,
                      gTheSet.List[x].stop*SCALE,
                      0,
                      (gTheSet.List[x].stop-gTheSet.List[x].start)*SCALE,
                      0);

              if (SUCCEEDED(hr))

                { // Add the element to the cutlist

                  hr = pAudioCL->AddElement(pAudCLElem[nAudElems],
                                            CL_DEFAULT_TIME,
                                            CL_DEFAULT_TIME);

                  if (SUCCEEDED(hr))
                    ++nAudElems;

                  else

                    { // AddElement failed so release associated objects

                      HELPER_RELEASE(pAudCLElem[nAudElems]);
                      HELPER_RELEASE(pAudFileClip[nAudElems]);
                      MessageBox(ghApp, "AddElement (audio) failed!", APPLICATIONNAME, MB_OK);

                    } // AddElement failed so release associated objects

                } // Add the element to the cutlist

              else MessageBox(ghApp, "CreateCut (audio) failed!", APPLICATIONNAME, MB_OK);

            } // Create audio cut

          else

            { // Problems creating audio stream

              HELPER_RELEASE(pAudFileClip[nAudElems]);
              MessageBox(ghApp, "SetFileAndStream (audio) failed!", APPLICATIONNAME, MB_OK);

            } // Problems creating audio stream

        } // Individual clips

      // Add the video cutlist to the filter graph
      hr = pCLGraphBuilder->AddCutList(pVideoCL, NULL);

      if (FAILED(hr)) // AddCutList (video) failed
          MessageBox(ghApp, "AddCutList (video) failed", APPLICATIONNAME, MB_OK);

      // Add the audio cutlist to the filter graph
      hr = pCLGraphBuilder->AddCutList(pAudioCL, NULL);

      if (FAILED(hr)) // AddCutList (audio) failed
          MessageBox(ghApp, "AddCutList (audio) failed", APPLICATIONNAME, MB_OK);

      if ((!pVideoCL) && (!pAudioCL))

        { // Clean up

          TearDownTheGraph();
          return;

        } // Clean up

      // Let the filter graph manager construct the the appropriate graph
      // automatically
      hr = pCLGraphBuilder->Render();

      if (FAILED(hr))
        { // Problems rendering the graph
          if (!AMGetErrorText(hr, gszScratch, 2048))
            MessageBox(ghApp, "Problems rendering the graph!", APPLICATIONNAME, MB_OK);
          else
            MessageBox(ghApp, gszScratch, APPLICATIONNAME, MB_OK);
          TearDownTheGraph();
          return;
        } // Problems rendering the graph

      // Retrieve the filter graph and useful interfaces
      hr = pCLGraphBuilder->GetFilterGraph(&pigb);

      if (FAILED(hr))
        { // Problems retrieving the graph pointer
          if (!AMGetErrorText(hr, gszScratch, 2048))
            MessageBox(ghApp, "Problems retrieving the graph pointer!", APPLICATIONNAME, MB_OK);
          else
            MessageBox(ghApp, gszScratch, APPLICATIONNAME, MB_OK);
          TearDownTheGraph();
          return;
        } // Problems retrieving the graph pointer

      // QueryInterface for some basic interfaces
      pigb->QueryInterface(IID_IMediaControl, (void **)&pimc);
      pigb->QueryInterface(IID_IMediaEventEx, (void **)&pimex);
      pigb->QueryInterface(IID_IVideoWindow, (void **)&pivw);

      // Decrement the ref count on the filter graph
      pigb->Release();

      // Prepare to play in the main application window's client area

      RECT rc;
      GetClientRect(ghApp, &rc);
      hr = pivw->put_Owner((OAHWND)ghApp);
      hr = pivw->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS);
      hr = pivw->SetWindowPosition(rc.left, rc.top, rc.right, rc.bottom);

      // Have the graph signal event via window callbacks for performance
      pimex->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0);

      // Run the graph if RenderFile succeeded
      pimc->Run();

    } // SimpleCutList //

 void TearDownTheGraph (void)

    { // TearDownTheGraph //

      if (pimc)
        pimc->Stop();

      if (pivw)

        { // Hide the playback window first thing

          pivw->put_Visible(OAFALSE);
          pivw->put_Owner(NULL);

        } //

      HELPER_RELEASE(pimex);
      HELPER_RELEASE(pimc);
      HELPER_RELEASE(pivw);

      // Remove the video cutlist from the filter graph to free resources
      if (pCLGraphBuilder && pVideoCL)
        pCLGraphBuilder->RemoveCutList(pVideoCL);

      // Remove the audio cutlist from the filter graph to free resources
      if (pCLGraphBuilder && pAudioCL)
        pCLGraphBuilder->RemoveCutList(pAudioCL);

      for (int x = 0; x < nAudElems; ++x)

        { // Release audio objects

          HELPER_RELEASE(pAudCLElem[x]);
          HELPER_RELEASE(pAudFileClip[x]);

        } // Release audio objects

      for (x = 0; x < nVidElems; ++x)

        { // Release video objects

          HELPER_RELEASE(pVidCLElem[x]);
          HELPER_RELEASE(pVidFileClip[x]);

        } // Release video objects

      HELPER_RELEASE(pVideoCL);
      HELPER_RELEASE(pAudioCL);
      HELPER_RELEASE(pCLGraphBuilder);

    } // TearDownTheGraph //

  BOOL CALLBACK DialogProc (HWND h, UINT m, WPARAM w, LPARAM l)

    { // DialogProc //

      switch(m)

        { // Msg handling

          case WM_INITDIALOG:
            if (wDlgRes == IDD_MEDIATIMES)
              SetFocus(GetDlgItem(h, IDC_TRIMIN2));
            else return TRUE;
            break;

          case WM_COMMAND:

            switch(w)

              { // Command msg handling

                case IDOKTIMES:

                  gTheSet.List[gTheSet.nNumClips].start = GetDlgItemInt(h, IDC_TRIMIN2, NULL, FALSE);
                  gTheSet.List[gTheSet.nNumClips].stop = GetDlgItemInt(h, IDC_TRIMOUT2, NULL, FALSE);

                  EndDialog(h,1);
                  break;

                case IDOK:
                  EndDialog(h,1);
                  break;

                case IDCANCEL:
                  EndDialog(h,0);
                  break;

              } // Command msg handling

            break;

        } // Msg handling

      return 0;

    } // DialogProc //

  BOOL GetClipFileName (LPSTR szName)

    {   // GetClipFileName //

        OPENFILENAME ofn;

        ofn.lStructSize       = sizeof(OPENFILENAME);
        ofn.hwndOwner         = ghApp;
        ofn.lpstrFilter       = NULL;
        ofn.lpstrFilter       = "AVI/WAV (*.avi; *.wav)\0*.avi;*.wav\0\0\0";
        ofn.lpstrCustomFilter = NULL;
        ofn.nFilterIndex      = 1;
        *szName               = 0;
        ofn.lpstrFile         = szName;
        ofn.nMaxFile          = MAX_PATH;
        ofn.lpstrInitialDir   = NULL;
        ofn.lpstrTitle        = NULL;
        ofn.lpstrFileTitle    = NULL;
        ofn.lpstrDefExt       = "AVI;WAV";
        ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

        return GetOpenFileName((LPOPENFILENAME)&ofn);

    }   // GetClipFileName //

  LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    { // WndMainProc //

      LONG evCode;
      LONG evParam1;
      LONG evParam2;

      switch(message)

        { // Window msgs handling

          case WM_COMMAND:

            switch(wParam)

              {  // Program menu option

                case IDM_ADDFILE:

                  if (GetClipFileName(gTheSet.List[gTheSet.nNumClips].szFilename))

                    { // Add file

                      TCHAR szTitleBar[200];

                      DialogBox(ghInst, MAKEINTRESOURCE(wDlgRes = IDD_MEDIATIMES), ghApp, (DLGPROC)DialogProc);
                      gTheSet.nNumClips = gTheSet.nNumClips + 1;
                      wsprintf(szTitleBar, "SimpleCutList - %d clips(s) added.", gTheSet.nNumClips);
                      SetWindowText(ghApp, szTitleBar);

                    } // Add file

                  break;

                case IDM_RUN:
                  if (gTheSet.nNumClips > 1)
                    SimpleCutList();
                  else
                    DialogBox(ghInst, MAKEINTRESOURCE(wDlgRes = IDD_LESSTHAN2), ghApp, (DLGPROC)DialogProc);
                  break;

                case ID_FILE_ABOUT:
                  DialogBox(ghInst, MAKEINTRESOURCE(wDlgRes = IDD_ABOUT), ghApp, (DLGPROC)DialogProc);
                  break;

                case ID_FILE_EXIT:
                  if (gTheSet.nNumClips)
                    TearDownTheGraph();
                  PostQuitMessage(0);
                  break;

              }  // Program menu option

            break;

          case WM_GRAPHNOTIFY:

            if (!pimex)
              break;

            while (SUCCEEDED(pimex->GetEvent(&evCode, &evParam1, &evParam2, 0)))

              { // Spin through the events

                hr = pimex->FreeEventParams(evCode, evParam1, evParam2);

                if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode))

                  { // Finished

                    TearDownTheGraph();

                    if (EC_USERABORT == evCode)
                      gTheSet.nNumClips = 0;

                    break;

                  } // Finished

              } // Spin through the events

            break;

          case WM_DESTROY:
            PostQuitMessage(0);
            break;

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

        } // Window msgs handling

      return FALSE;

      }  // WndMainProc //

  int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

    { // WinMain //

      MSG msg;
      WNDCLASS wc;

      // OLE subsystem requires applications to initialize things first!
      CoInitialize(NULL);

      ZeroMemory(&wc, sizeof wc);
      wc.lpfnWndProc = WndMainProc;
      ghInst = wc.hInstance = hInstC;
      wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      wc.lpszClassName = CLASSNAME;
      wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_CUTLIST));
      RegisterClass(&wc);

      RECT rc;

      GetWindowRect(GetDesktopWindow(), &rc);
      rc.right >>= 1;
      rc.bottom >>= 1;

      ghApp = CreateWindow(CLASSNAME,
      APPLICATIONNAME,
      WS_OVERLAPPEDWINDOW,
      rc.right-200,
      rc.bottom-200,
      400,
      400,
      0,
      0,
      ghInst,
      0);

      ShowWindow(ghApp, SW_NORMAL);
      UpdateWindow(ghApp);

      ZeroMemory(&gTheSet, sizeof gTheSet);

      while (GetMessage(&msg,NULL,0,0))

      {  // Message loop

         TranslateMessage(&msg);
         DispatchMessage(&msg);

      }  // Message loop

      // Finished with OLE subsystem
      CoUninitialize();

      return msg.wParam;

  } // WinMain //


SIMPLECL.H

#define IDR_MENU1                       101
#define IDI_CUTLIST                     102
#define IDD_MEDIATIMES                  103
#define IDOKTIMES                       104
#define IDD_ABOUT                       105
#define IDD_LESSTHAN2                   106
#define IDC_TRIMIN                      1001
#define IDC_TRIMOUT                     1002
#define IDC_TRIMIN2                     1003
#define IDC_TRIMOUT2                    1004
#define ID_FILE_EXIT                    40001
#define IDM_ADDFILE                     40002
#define IDM_RUN                         40003
#define ID_FILE_ABOUT                   40004
#define IDC_STATIC                -1

#define MAX_CLIPS 150
#define SCALE 10000000   // scale for 1 second of reference time

// Forward prototyping
void TearDownTheGraph (void);

// clip (element) details
struct ClipDetails
  {
    TCHAR szFilename[MAX_PATH];   // name of file containing clip
    REFERENCE_TIME start;         // Start (Trim In) position of clip within file
    REFERENCE_TIME stop;          // Stop (Trim Out) position of clip within file
  };

// cutlist is a collection of clips (elements)
struct ClipCollection
  {
    int nNumClips;
    ClipDetails List[MAX_CLIPS];
  };

#define WM_GRAPHNOTIFY  WM_USER+13

#define HELPER_RELEASE(x) { if (x) x->Release(); x = NULL; }

#define APPLICATIONNAME "Simple Cutlist"
#define CLASSNAME "SimpleCutList"

HWND            ghApp;
WORD            wDlgRes;
HINSTANCE       ghInst;
ClipCollection  gTheSet;            // Cutlist
TCHAR           gszScratch[2048];   // General, scratch string (e.g. error msgs)


DUMP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//
// What this sample illustrates
//
// A renderer that dumps the samples it gets into a text file
//
//
// Summary
//
// We are a generic renderer that can be attached to any data stream that
// uses IMemInputPin data transport. For each sample we receive we write
// it's contents including it's properties into a dump file. The file we
// will write into is specified when the dump filter is created. Graphedt
// creates a file open dialog automatically when it sees a filter being
// created that supports the ActiveMovie defined IFileSinkFilter interface
//
//
// Implementation
//
// Pretty straightforward really, we have our own input pin class so that
// we can override Receive, all that does is to write the properties and
// data into a raw data file (using the Write function). We don't keep
// the file open when we are stopped so the flags to the open function
// ensure that we open a file if already there otherwise we create it.
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Video Renderer"
// This is the filter we will be replacing with the dump renderer. Then click
// on the box and hit DELETE. After that go to the Graph menu and select the
// "Insert Filters", from the dialog box find and select the "Dump Filter".
//
// You will be asked to supply a filename where you would like to have the
// data dumped, the data we receive in this filter is dumped in text form.
// Then dismiss the dialog. Back in the graph layout find the output pin of
// the filter that used to be connected to the input of the video renderer
// you just deleted, right click and do "Render". You should see it being
// connected to the input pin of the dump filter you just inserted.
//
// Click Pause and Run and then a little later stop on the GRAPHEDT frame and
// the data being passed to the renderer will be dumped into a file. Stop the
// graph and dump the filename that you entered when inserting the filter into
// the graph, the data supplied to the renderer will be displayed as raw data
//
//
// Files
//
// dump.cpp             Main implementation of the dump renderer
// dump.def             What APIs the DLL will import and export
// dump.h               Class definition of the derived renderer
// dump.rc              Version information for the sample DLL
// dump.reg             What goes in the registry to make us work
// dumpuids.h           CLSID for the dump filter
// makefile             How to build it...
//
//
// Base classes used
//
// CBaseFilter          Base filter class supporting IMediaFilter
// CRenderedInputPin    An input pin attached to a renderer
// CUnknown             Handle IUnknown for our IFileSinkFilter
// CPosPassThru         Passes seeking interfaces upstream
// CCritSec             Helper class that wraps a critical section
//
//

#include <windows.h>
#include <commdlg.h>
#include <streams.h>
#include <initguid.h>
#include "dumpuids.h"
#include "dump.h"

// Setup data

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_NULL,            // Major type
    &MEDIASUBTYPE_NULL          // Minor type
};

const AMOVIESETUP_PIN sudPins =
{
    L"Input",                   // Pin string name
    FALSE,                      // Is it rendered
    FALSE,                      // Is it an output
    FALSE,                      // Allowed none
    FALSE,                      // Likewise many
    &CLSID_NULL,                // Connects to filter
    L"Output",                  // Connects to pin
    1,                          // Number of types
    &sudPinTypes                // Pin information
};

const AMOVIESETUP_FILTER sudDump =
{
    &CLSID_Dump,                // Filter CLSID
    L"Dump",                    // String name
    MERIT_DO_NOT_USE,           // Filter merit
    1,                          // Number pins
    &sudPins                    // Pin details
};

//
//  Object creation stuff
//
CFactoryTemplate g_Templates[]= {
    L"Dump", &CLSID_Dump, CDump::CreateInstance, NULL, &sudDump
};
int g_cTemplates = 1;

// Constructor

CDumpFilter::CDumpFilter(CDump *pDump,
                         LPUNKNOWN pUnk,
                         CCritSec *pLock,
                         HRESULT *phr) :
    CBaseFilter(NAME("CDumpFilter"), pUnk, pLock, CLSID_Dump),
    m_pDump(pDump)
{
}

//
// GetPin
//
CBasePin * CDumpFilter::GetPin(int n)
{
    if (n == 0) {
        return m_pDump->m_pPin;
    } else {
        return NULL;
    }
}

//
// GetPinCount
//
int CDumpFilter::GetPinCount()
{
    return 1;
}

//
// Stop
//
// Overriden to close the dump file
//
STDMETHODIMP CDumpFilter::Stop()
{
    CAutoLock cObjectLock(m_pLock);
    m_pDump->CloseFile();
    return CBaseFilter::Stop();
}

//
// Pause
//
// Overriden to open the dump file
//
STDMETHODIMP CDumpFilter::Pause()
{
    CAutoLock cObjectLock(m_pLock);
    m_pDump->OpenFile();
    return CBaseFilter::Pause();
}

//
// Run
//
// Overriden to open the dump file
//
STDMETHODIMP CDumpFilter::Run(REFERENCE_TIME tStart)
{
    CAutoLock cObjectLock(m_pLock);
    m_pDump->OpenFile();
    return CBaseFilter::Run(tStart);
}

//
//  Definition of CDumpInputPin
//
CDumpInputPin::CDumpInputPin(CDump *pDump,
                             LPUNKNOWN pUnk,
                             CBaseFilter *pFilter,
                             CCritSec *pLock,
                             CCritSec *pReceiveLock,
                             HRESULT *phr) :

    CRenderedInputPin(NAME("CDumpInputPin"),
                  pFilter,                   // Filter
                  pLock,                     // Locking
                  phr,                       // Return code
                  L"Input"),                 // Pin name
    m_pReceiveLock(pReceiveLock),
    m_pDump(pDump),
    m_tLast(0)
{
}

//
// CheckMediaType
//
// Check if the pin can support this specific proposed type and format
//
HRESULT CDumpInputPin::CheckMediaType(const CMediaType *)
{
    return S_OK;
}

//
// BreakConnect
//
// Break a connection
//
HRESULT CDumpInputPin::BreakConnect()
{
    if (m_pDump->m_pPosition != NULL) {
        m_pDump->m_pPosition->ForceRefresh();
    }
    return CRenderedInputPin::BreakConnect();
}

//
// ReceiveCanBlock
//
// We don't hold up source threads on Receive
//
STDMETHODIMP CDumpInputPin::ReceiveCanBlock()
{
    return S_FALSE;
}

//
// Receive
//
// Do something with this media sample
//
STDMETHODIMP CDumpInputPin::Receive(IMediaSample *pSample)
{
    CAutoLock lock(m_pReceiveLock);
    PBYTE pbData;

    // Has the filter been stopped yet
    if (m_pDump->m_hFile == INVALID_HANDLE_VALUE) {
        return NOERROR;
    }

    REFERENCE_TIME tStart, tStop;
    pSample->GetTime(&tStart, &tStop);
    DbgLog((LOG_TRACE, 1, TEXT("tStart(%s), tStop(%s), Diff(%d ms), Bytes(%d)"),
           (LPCTSTR) CDisp(tStart),
           (LPCTSTR) CDisp(tStop),
           (LONG)((tStart - m_tLast) / 10000),
           pSample->GetActualDataLength()));

    m_tLast = tStart;

    // Copy the data to the file

    HRESULT hr = pSample->GetPointer(&pbData);
    if (FAILED(hr)) {
        return hr;
    }
    return m_pDump->Write(pbData,pSample->GetActualDataLength());
}

//
// DumpStringInfo
//
// Write to the file as text form
//
HRESULT CDumpInputPin::WriteStringInfo(IMediaSample *pSample)
{
    TCHAR TempString[256],FileString[256];
    PBYTE pbData;

    // Retrieve the time stamps from this sample

    REFERENCE_TIME tStart, tStop;
    pSample->GetTime(&tStart, &tStop);
    m_tLast = tStart;

    // Write the sample time stamps out

    wsprintf(FileString,"\r\nRenderer received sample (%dms)",timeGetTime());
    m_pDump->WriteString(FileString);
    wsprintf(FileString,"   Start time (%s)",CDisp(tStart));
    m_pDump->WriteString(FileString);
    wsprintf(FileString,"   End time (%s)",CDisp(tStop));
    m_pDump->WriteString(FileString);

    // Display the media times for this sample

    HRESULT hr = pSample->GetMediaTime(&tStart, &tStop);
    if (hr == NOERROR) {
        wsprintf(FileString,"   Start media time (%s)",CDisp(tStart));
        m_pDump->WriteString(FileString);
        wsprintf(FileString,"   End media time (%s)",CDisp(tStop));
        m_pDump->WriteString(FileString);
    }

    // Is this a sync point sample

    hr = pSample->IsSyncPoint();
    wsprintf(FileString,"   Sync point (%d)",(hr == S_OK));
    m_pDump->WriteString(FileString);

    // Is this a preroll sample

    hr = pSample->IsPreroll();
    wsprintf(FileString,"   Preroll (%d)",(hr == S_OK));
    m_pDump->WriteString(FileString);

    // Is this a discontinuity sample

    hr = pSample->IsDiscontinuity();
    wsprintf(FileString,"   Discontinuity (%d)",(hr == S_OK));
    m_pDump->WriteString(FileString);

    // Write the actual data length

    LONG DataLength = pSample->GetActualDataLength();
    wsprintf(FileString,"   Actual data length (%d)",DataLength);
    m_pDump->WriteString(FileString);

    // Does the sample have a type change aboard

    AM_MEDIA_TYPE *pMediaType;
    pSample->GetMediaType(&pMediaType);
    wsprintf(FileString,"   Type changed (%d)",
        (pMediaType ? TRUE : FALSE));
    m_pDump->WriteString(FileString);
    DeleteMediaType(pMediaType);

    // Copy the data to the file

    hr = pSample->GetPointer(&pbData);
    if (FAILED(hr)) {
        return hr;
    }

    // Write each complete line out in BYTES_PER_LINES groups

    for (int Loop = 0;Loop < (DataLength / BYTES_PER_LINE);Loop++) {
        wsprintf(FileString,FIRST_HALF_LINE,pbData[0],pbData[1],pbData[2],
                 pbData[3],pbData[4],pbData[5],pbData[6],
                    pbData[7],pbData[8],pbData[9]);
        wsprintf(TempString,SECOND_HALF_LINE,pbData[10],pbData[11],pbData[12],
                 pbData[13],pbData[14],pbData[15],pbData[16],
                    pbData[17],pbData[18],pbData[19]);
        lstrcat(FileString,TempString);
        m_pDump->WriteString(FileString);
        pbData += BYTES_PER_LINE;
    }

    // Write the last few bytes out afterwards

    wsprintf(FileString,"   ");
    for (Loop = 0;Loop < (DataLength % BYTES_PER_LINE);Loop++) {
        wsprintf(TempString,"%x ",pbData[Loop]);
        lstrcat(FileString,TempString);
    }
    m_pDump->WriteString(FileString);
    return NOERROR;
}

//
// EndOfStream
//
STDMETHODIMP CDumpInputPin::EndOfStream(void)
{
    CAutoLock lock(m_pReceiveLock);
    return CRenderedInputPin::EndOfStream();

} // EndOfStream

//
// NewSegment
//
// Called when we are seeked
//
STDMETHODIMP CDumpInputPin::NewSegment(REFERENCE_TIME tStart,
                                       REFERENCE_TIME tStop,
                                       double dRate)
{
    m_tLast = 0;
    return S_OK;

} // NewSegment

//
//  CDump class
//
CDump::CDump(LPUNKNOWN pUnk, HRESULT *phr) :
    CUnknown(NAME("CDump"), pUnk),
    m_pFilter(NULL),
    m_pPin(NULL),
    m_pPosition(NULL),
    m_hFile(INVALID_HANDLE_VALUE),
    m_pFileName(0)
{
    m_pFilter = new CDumpFilter(this, GetOwner(), &m_Lock, phr);
    if (m_pFilter == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    m_pPin = new CDumpInputPin(this,GetOwner(),
                               m_pFilter,
                               &m_Lock,
                               &m_ReceiveLock,
                               phr);
    if (m_pPin == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }
}

//
// SetFileName
//
// Implemented for IFileSinkFilter support
//
STDMETHODIMP CDump::SetFileName(LPCOLESTR pszFileName,const AM_MEDIA_TYPE *pmt)
{
    // Is this a valid filename supplied

    CheckPointer(pszFileName,E_POINTER);
    if(wcslen(pszFileName) > MAX_PATH)
        return ERROR_FILENAME_EXCED_RANGE;

    // Take a copy of the filename

    m_pFileName = new WCHAR[1+lstrlenW(pszFileName)];
    if (m_pFileName == 0)
        return E_OUTOFMEMORY;
    lstrcpyW(m_pFileName,pszFileName);

    // Create the file then close it

    HRESULT hr = OpenFile();
    CloseFile();
    return hr;

} // SetFileName

//
// GetCurFile
//
// Implemented for IFileSinkFilter support
//
STDMETHODIMP CDump::GetCurFile(LPOLESTR * ppszFileName,AM_MEDIA_TYPE *pmt)
{
    CheckPointer(ppszFileName, E_POINTER);
    *ppszFileName = NULL;
    if (m_pFileName != NULL) {
        *ppszFileName = (LPOLESTR)
        QzTaskMemAlloc(sizeof(WCHAR) * (1+lstrlenW(m_pFileName)));
        if (*ppszFileName != NULL) {
            lstrcpyW(*ppszFileName, m_pFileName);
        }
    }

    if(pmt) {
        ZeroMemory(pmt, sizeof(*pmt));
        pmt->majortype = MEDIATYPE_NULL;
        pmt->subtype = MEDIASUBTYPE_NULL;
    }
    return S_OK;

} // GetCurFile

// Destructor

CDump::~CDump()
{
    CloseFile();
    delete m_pPin;
    delete m_pFilter;
    delete m_pPosition;
    delete m_pFileName;
}

//
// CreateInstance
//
// Provide the way for COM to create a dump filter
//
CUnknown * WINAPI CDump::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{
    CDump *pNewObject = new CDump(punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return pNewObject;

} // CreateInstance

//
// NonDelegatingQueryInterface
//
// Override this to say what interfaces we support where
//
STDMETHODIMP CDump::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    CheckPointer(ppv,E_POINTER);
    CAutoLock lock(&m_Lock);

    // Do we have this interface

    if (riid == IID_IFileSinkFilter) {
        return GetInterface((IFileSinkFilter *) this, ppv);
    } else if (riid == IID_IBaseFilter || riid == IID_IMediaFilter || riid == IID_IPersist) {
return m_pFilter->NonDelegatingQueryInterface(riid, ppv);
    } else if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
        if (m_pPosition == NULL) {

            HRESULT hr = S_OK;
            m_pPosition = new CPosPassThru(NAME("Dump Pass Through"),
                                           (IUnknown *) GetOwner(),
                                           (HRESULT *) &hr, m_pPin);
            if (m_pPosition == NULL) {
                return E_OUTOFMEMORY;
            }

            if (FAILED(hr)) {
                delete m_pPosition;
                m_pPosition = NULL;
                return hr;
            }
        }
        return m_pPosition->NonDelegatingQueryInterface(riid, ppv);
    } else {
return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }

} // NonDelegatingQueryInterface

//
// OpenFile
//
// Opens the file ready for dumping
//
HRESULT CDump::OpenFile()
{
    TCHAR *pFileName = NULL;

    // Is the file already opened
    if (m_hFile != INVALID_HANDLE_VALUE) {
        return NOERROR;
    }

    // Has a filename been set yet
    if (m_pFileName == NULL) {
        return ERROR_INVALID_NAME;
    }

    // Convert the UNICODE filename if necessary

#if defined(WIN32) && !defined(UNICODE)
    char convert[MAX_PATH];
    if(!WideCharToMultiByte(CP_ACP,0,m_pFileName,-1,convert,MAX_PATH,0,0))
        return ERROR_INVALID_NAME;
    pFileName = convert;
#else
    pFileName = m_pFileName;
#endif

    // Try to open the file

    m_hFile = CreateFile((LPCTSTR) pFileName,   // The filename
                         GENERIC_WRITE,         // File access
                         (DWORD) 0,             // Share access
                         NULL,                  // Security
                         CREATE_ALWAYS,         // Open flags
                         (DWORD) 0,             // More flags
                         NULL);                 // Template

    if (m_hFile == INVALID_HANDLE_VALUE) {
        DWORD dwErr = GetLastError();
        return HRESULT_FROM_WIN32(dwErr);
    }
    return S_OK;

} // Open

//
// CloseFile
//
// Closes any dump file we have opened
//
HRESULT CDump::CloseFile()
{
    if (m_hFile == INVALID_HANDLE_VALUE) {
        return NOERROR;
    }

    CloseHandle(m_hFile);
    m_hFile = INVALID_HANDLE_VALUE;
    return NOERROR;

} // Open

//
// Write
//
// Write stuff to the file
//
HRESULT CDump::Write(PBYTE pbData,LONG lData)
{
    DWORD dwWritten;

    if (!WriteFile(m_hFile,(PVOID)pbData,(DWORD)lData,&dwWritten,NULL)) {
        DWORD dwErr = GetLastError();
        return HRESULT_FROM_WIN32(dwErr);
    }
    return S_OK;
}

//
// WriteString
//
// Writes the given string into the file
//
void CDump::WriteString(TCHAR *pString)
{
    DWORD dwWritten = lstrlen(pString);
    const TCHAR *pEndOfLine = "\r\n";

    WriteFile((HANDLE) m_hFile,
              (PVOID) pString,
              (DWORD) dwWritten,
              &dwWritten, NULL);

    dwWritten = lstrlen(pEndOfLine);
    WriteFile((HANDLE) m_hFile,
              (PVOID) pEndOfLine,
              (DWORD) dwWritten,
              &dwWritten, NULL);

} // WriteString

//
// DllRegisterSever
//
// Handle the registration of this filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer


DUMP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

class CDumpInputPin;
class CDump;
class CDumpFilter;

#define BYTES_PER_LINE 20
#define FIRST_HALF_LINE "   %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x"
#define SECOND_HALF_LINE " %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x"

// Main filter object

class CDumpFilter : public CBaseFilter
{
    CDump * const m_pDump;

public:

    // Constructor
    CDumpFilter(CDump *pDump,
                LPUNKNOWN pUnk,
                CCritSec *pLock,
                HRESULT *phr);

    // Pin enumeration
    CBasePin * GetPin(int n);
    int GetPinCount();

    // Open and close the file as necessary
    STDMETHODIMP Run(REFERENCE_TIME tStart);
    STDMETHODIMP Pause();
    STDMETHODIMP Stop();
};

//  Pin object

class CDumpInputPin : public CRenderedInputPin
{
    CDump    * const m_pDump;           // Main renderer object
    CCritSec * const m_pReceiveLock;    // Sample critical section
    REFERENCE_TIME m_tLast;             // Last sample receive time

public:

    CDumpInputPin(CDump *pDump,
                  LPUNKNOWN pUnk,
                  CBaseFilter *pFilter,
                  CCritSec *pLock,
                  CCritSec *pReceiveLock,
                  HRESULT *phr);

    // Do something with this media sample
    STDMETHODIMP Receive(IMediaSample *pSample);
    STDMETHODIMP EndOfStream(void);
    STDMETHODIMP ReceiveCanBlock();
    HRESULT WriteStringInfo(IMediaSample *pSample);

    // Check if the pin can support this specific proposed type and format
    HRESULT CheckMediaType(const CMediaType *);

    // Break connection
    HRESULT BreakConnect();

    // Track NewSegment
    STDMETHODIMP NewSegment(REFERENCE_TIME tStart,
                            REFERENCE_TIME tStop,
                            double dRate);
};

//  CDump object which has filter and pin members

class CDump : public CUnknown, public IFileSinkFilter
{
    friend class CDumpFilter;
    friend class CDumpInputPin;

    CDumpFilter *m_pFilter;         // Methods for filter interfaces
    CDumpInputPin *m_pPin;          // A simple rendered input pin
    CCritSec m_Lock;                // Main renderer critical section
    CCritSec m_ReceiveLock;         // Sublock for received samples
    CPosPassThru *m_pPosition;      // Renderer position controls
    HANDLE m_hFile;                 // Handle to file for dumping
    LPOLESTR m_pFileName;           // The filename where we dump to

public:

    DECLARE_IUNKNOWN

    CDump(LPUNKNOWN pUnk, HRESULT *phr);
    ~CDump();

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    // Write data streams to a file
    void WriteString(TCHAR *pString);
    HRESULT Write(PBYTE pbData,LONG lData);

    // Implements the IFileSinkFilter interface
    STDMETHODIMP SetFileName(LPCOLESTR pszFileName,const AM_MEDIA_TYPE *pmt);
    STDMETHODIMP GetCurFile(LPOLESTR * ppszFileName,AM_MEDIA_TYPE *pmt);

private:

    // Overriden to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // Open and write to the file
    HRESULT OpenFile();
    HRESULT CloseFile();
};


DUMPUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// The CLSID used by the dump filter

// { 36a5f770-fe4c-11ce-a8ed-00aa002feab5 }
DEFINE_GUID(CLSID_Dump,
0x36a5f770, 0xfe4c, 0x11ce, 0xa8, 0xed, 0x00, 0xaa, 0x00, 0x2f, 0xea, 0xb5);


DVDSAMPL.CPP

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

//
// DVDSampl.cpp: DvdGraphBuilder test/sample app
//

#include <streams.h>
#include <windows.h>
#include <IL21Dec.h>
#include <dvdevcod.h>

#include "DVDSampl.h"

#define APPNAME  TEXT("DVDSampl")

// -------------------------------------------------------------------------
//                .... Windows App Requirement starts ....
// -------------------------------------------------------------------------

//
// Forward declaration of functions
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ;
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) ;
LRESULT CALLBACK SelectLang(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) ;
LRESULT CALLBACK SelectViewLevel(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) ;
LRESULT CALLBACK MenuProc(HWND hWnd, WPARAM wParam, LPARAM lParam) ;
LRESULT CALLBACK KeyProc(HWND hWnd, WPARAM wParam, LPARAM lParam) ;

CSampleDVDPlay  Player ;  // global player object

int APIENTRY WinMain(HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPSTR     lpCmdLine,
     int       nCmdShow)
{
    MSG             msg ;
    HACCEL          hAccelTable ;

    DbgInitialise(hInstance);
    Player.SetAppValues(hInstance, APPNAME, IDS_APP_TITLE) ;

    if (! Player.InitApplication() )
    {
DbgTerminate();
return (FALSE) ;
    }

    // Perform application initialization:
    if (! Player.InitInstance(nCmdShow) )
    {
DbgTerminate();
return (FALSE) ;
    }

    hAccelTable = LoadAccelerators(hInstance, Player.GetAppName()) ;

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
if (! TranslateAccelerator(msg.hwnd, hAccelTable, &msg) )
{
    TranslateMessage(&msg) ;
    DispatchMessage(&msg) ;
}
    }

    DbgTerminate();
    return (msg.wParam) ;
}

// A helper function to convert render flags to corresponding menu ids
int FlagToMenuID(DWORD dwFlag)
{
    dwFlag = (dwFlag & (~AM_DVD_NOVPE)) ;  // strip off VPE flag
    switch (dwFlag)
    {
case AM_DVD_HWDEC_PREFER:
    return (IDM_FLAGBASE + 1) ;

case AM_DVD_HWDEC_ONLY:
    return (IDM_FLAGBASE + 2) ;

case AM_DVD_SWDEC_PREFER:
    return (IDM_FLAGBASE + 3) ;

case AM_DVD_SWDEC_ONLY:
    return (IDM_FLAGBASE + 4) ;

default:  // huh!!!
    return 0 ;
    }
}

LRESULT CALLBACK MenuProc(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    HMENU hMenu = GetMenu(hWnd) ;
    int   wmId    = LOWORD(wParam);
    int   wmEvent = HIWORD(wParam);

    //Parse the menu selections:
    switch (wmId) {

    case IDM_SELECT:
        Player.FileSelect() ;  // if volume has been changed, playback stopped.
        break;

    case IDM_ABOUT:
        DialogBox(Player.GetInstance(), TEXT("AboutBox"), Player.GetWindow(),
                  (DLGPROC) About);
        break;

    case IDM_EXIT:
        DestroyWindow(Player.GetWindow());
        break;

    case IDM_HWMAX:
        if (0 == (Player.GetRenderFlag() & AM_DVD_HWDEC_PREFER))
        {
            CheckMenuItem(hMenu, FlagToMenuID(Player.GetRenderFlag()),
                          MF_UNCHECKED) ;
            Player.SetRenderFlag(AM_DVD_HWDEC_PREFER) ;
            CheckMenuItem(hMenu, IDM_HWMAX, MF_CHECKED) ;
        }
        break;

    case IDM_HWONLY:
        if (0 == (Player.GetRenderFlag() & AM_DVD_HWDEC_ONLY))
        {
            CheckMenuItem(hMenu, FlagToMenuID(Player.GetRenderFlag()),
                          MF_UNCHECKED) ;
            Player.SetRenderFlag(AM_DVD_HWDEC_ONLY) ;
            CheckMenuItem(hMenu, IDM_HWONLY, MF_CHECKED) ;
        }
        break;

    case IDM_SWMAX:
        if (0 == (Player.GetRenderFlag() & AM_DVD_SWDEC_PREFER))
        {
            CheckMenuItem(hMenu, FlagToMenuID(Player.GetRenderFlag()),
                          MF_UNCHECKED) ;
            Player.SetRenderFlag(AM_DVD_SWDEC_PREFER) ;
            CheckMenuItem(hMenu, IDM_SWMAX, MF_CHECKED) ;
        }
        break;

    case IDM_SWONLY:
        if (0 == (Player.GetRenderFlag() & AM_DVD_SWDEC_ONLY))
        {
            CheckMenuItem(hMenu, FlagToMenuID(Player.GetRenderFlag()),
                          MF_UNCHECKED) ;
            Player.SetRenderFlag(AM_DVD_SWDEC_ONLY) ;
            CheckMenuItem(hMenu, IDM_SWONLY, MF_CHECKED) ;
        }
        break;

    case IDM_NOVPE:
        Player.SetRenderFlag(AM_DVD_NOVPE) ;
        CheckMenuItem(hMenu, IDM_NOVPE,
             (Player.GetRenderFlag() & AM_DVD_NOVPE) ? MF_CHECKED : MF_UNCHECKED) ;
        break;

    case IDM_BUILDGRAPH:
        Player.BuildGraph() ;
        break ;

    case IDM_PLAY:
        Player.Play() ;
        break;

    case IDM_STOP:
        Player.Stop() ;
        break;

    case IDM_PAUSE:
        Player.Pause() ;
        break;

    case IDM_FASTFWD:
        Player.FastForward() ;
        break;

    case IDM_VERYFASTFWD:
        Player.VeryFastForward() ;
        break;

    case IDM_FASTRWND:
        Player.FastRewind() ;
        break;

    case IDM_VERYFASTRWND:
        Player.VeryFastRewind() ;
        break;

    case IDM_MENU:
        Player.ShowMenu() ;
        break;

    case IDM_USER_MENUSTATUS:
        //
        // This is a custom user message that informs the app to update the
        // menu according to the state.
        //
        ModifyMenu(hMenu, IDM_MENU, MF_STRING, IDM_MENU,
                   lParam ? TEXT("Resume") : TEXT("Menu")) ;
        break ;

    case IDM_LANG:
        if (! Player.IsLangKnown() )
        {
            MessageBox(Player.GetWindow(),
                    TEXT("Can't specify audio/subpicture language until playback starts."),
                    TEXT("Error"), MB_OK | MB_ICONINFORMATION) ;
            break ;
        }
        DialogBox(Player.GetInstance(), MAKEINTRESOURCE(IDD_LANGUAGE), Player.GetWindow(),
                  (DLGPROC) SelectLang);
        break;

    case IDM_VIEWLEVEL:
        if (Playing == Player.GetState())
        {
            MessageBox(Player.GetWindow(),
                    TEXT("Can't change parental control level during playback. Please stop first."),
                    TEXT("Error"), MB_OK | MB_ICONINFORMATION) ;
            break ;
        }
        DialogBox(Player.GetInstance(), MAKEINTRESOURCE(IDD_VIEWLEVEL), Player.GetWindow(),
                (DLGPROC) SelectViewLevel);
        break;

    case IDM_CC:
        if (Player.ClosedCaption())  // CC turned on
            CheckMenuItem(hMenu, IDM_CC, MF_CHECKED) ;
        else  // CC turned off
            CheckMenuItem(hMenu, IDM_CC, MF_UNCHECKED) ;
        break ;

    case IDM_FULLSCRN:
        if (Player.ShowFullScreen())  // if really in fullscreen mode
            CheckMenuItem(hMenu, IDM_FULLSCRN, MF_CHECKED) ;
        else  // full screen turned off
            CheckMenuItem(hMenu, IDM_FULLSCRN, MF_UNCHECKED) ;
        break;

    default:
        break ;
    }

    return 0 ;
}

LRESULT CALLBACK KeyProc(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    switch (wParam)
    {
case VK_ESCAPE:
    {
    MessageBeep(MB_OK) ;
    HMENU hMenu = GetMenu(hWnd) ;
    Player.StopFullScreen() ;
    CheckMenuItem(hMenu, IDM_FULLSCRN, MF_UNCHECKED) ;  // no fullscrn anymore
    }
    break ;

case VK_UP:
    Player.CursorMove(Cursor_Up) ;
    break ;

case VK_DOWN:
    Player.CursorMove(Cursor_Down) ;
    break ;

case VK_LEFT:
    Player.CursorMove(Cursor_Left) ;
    break ;

case VK_RIGHT:
    Player.CursorMove(Cursor_Right) ;
    break ;

case VK_RETURN:
    Player.CursorSelect() ;
    break ;

default:
    break ;
    }

    return 0 ;
}

//
// One function to update the menu state based on the player object state.
// Used by WM_INITMENUPOPUP message handling.
//
void UpdateMenuState(void)
{
    HMENU  hMenu = GetMenu(Player.GetWindow()) ;

    switch (Player.GetState())
    {
    case Unknown:
        EnableMenuItem(hMenu, IDM_BUILDGRAPH, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_PAUSE, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_PLAY, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_STOP, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_SLOWPLAY, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_FASTFWD, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_VERYFASTFWD, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_FASTRWND, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_VERYFASTRWND, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_MENU, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_LANG, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_VIEWLEVEL, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_CC, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_FULLSCRN, MF_GRAYED) ;
        break ;

    case Stopped:
        EnableMenuItem(hMenu, IDM_BUILDGRAPH, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_STOP, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_PAUSE, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_PLAY, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_SLOWPLAY, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTRWND, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTRWND, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_MENU, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_FULLSCRN, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_LANG, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_VIEWLEVEL, MF_ENABLED) ;
    // CC option has been turned on/off above based on Line21Dec presence
        break ;

    case Paused:
        EnableMenuItem(hMenu, IDM_BUILDGRAPH, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_PAUSE, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_STOP, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_PLAY, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_SLOWPLAY, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTRWND, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTRWND, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_MENU, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_FULLSCRN, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_LANG, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_VIEWLEVEL, MF_ENABLED) ;
        break ;

    case Playing:
        EnableMenuItem(hMenu, IDM_BUILDGRAPH, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_PLAY, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_PAUSE, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_STOP, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_SLOWPLAY, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTFWD, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_FASTRWND, MF_ENABLED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTRWND, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_MENU, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_FULLSCRN, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_LANG, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_VIEWLEVEL, MF_ENABLED) ;
        break ;

    case Scanning:
        EnableMenuItem(hMenu, IDM_BUILDGRAPH, MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_SLOWPLAY, MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_FASTFWD, MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTFWD, MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_FASTRWND, MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_VERYFASTRWND, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_MENU, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_FULLSCRN, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_LANG, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_VIEWLEVEL, MF_GRAYED) ;
        EnableMenuItem(hMenu, IDM_STOP, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_PAUSE, MF_ENABLED) ;
        EnableMenuItem(hMenu, IDM_PLAY, MF_ENABLED) ;
        break ;

    default:  // Huh!!!
        break ;
    }
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HDC  hDC ;
    static PAINTSTRUCT ps ;

    switch (message) {

    case WM_INITMENUPOPUP:
        UpdateMenuState() ;
        break ;

case WM_PAINT:
        hDC = BeginPaint(hWnd, &ps) ;
        ASSERT(hDC) ;
        Player.DrawStatus(hDC) ;
        EndPaint(hWnd, &ps) ;
        break ;

case WM_DVDPLAY_EVENT:
        Player.OnDVDPlayEvent(wParam, lParam) ;
        break ;

case WM_KEYUP:
        KeyProc(hWnd, wParam, lParam) ;
        break ;

case WM_COMMAND:
        MenuProc(hWnd, wParam, lParam) ;
        break;

case WM_DESTROY:
        PostQuitMessage(0);
        break;

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

    return 0 ;
}

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
case WM_INITDIALOG:
        return TRUE;

case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, TRUE);
            return TRUE;
    }
        break;

default:
        break ;
    }

    return FALSE;
}

LRESULT CALLBACK SelectLang(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
case WM_INITDIALOG:
    {
        Player.MakeAudioStreamList(hDlg, IDD_LANG_AUDIOLANG) ;
        Player.MakeSPStreamList(hDlg, IDD_LANG_SPLANG) ;
        BOOL bSPOn = IsDlgButtonChecked(hDlg, IDD_LANG_SHOWSUBPIC) ;
        EnableWindow(GetDlgItem(hDlg, IDD_LANG_SPLANGTEXT), bSPOn) ;
        EnableWindow(GetDlgItem(hDlg, IDD_LANG_SPLANG), bSPOn) ;
    }
    return TRUE;

case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDD_LANG_SHOWSUBPIC:
        {
            BOOL bSPOn = IsDlgButtonChecked(hDlg, IDD_LANG_SHOWSUBPIC) ;
            EnableWindow(GetDlgItem(hDlg, IDD_LANG_SPLANGTEXT), bSPOn) ;
            EnableWindow(GetDlgItem(hDlg, IDD_LANG_SPLANG), bSPOn) ;
        }
        break ;

        case IDOK:
        {
            LONG   lStream ;
            BOOL   bRes ;

            // Set the SP choice value & option specified by the user
            BOOL bSPOn = IsDlgButtonChecked(hDlg, IDD_LANG_SHOWSUBPIC) ;
            if (bSPOn)
            {
                lStream = SendDlgItemMessage(hDlg, IDD_LANG_SPLANG, CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0) ;
                if (CB_ERR == lStream)
                    DbgLog((LOG_ERROR, 1,
                        TEXT("WARNING: Couldn't get selected SP stream id (Error %d)"), lStream)) ;
                else
                {
                    bRes = Player.SetSPState(lStream, bSPOn) ; // TRUE
                    ASSERT(bRes) ;
                }
            }
            else
            {
                bRes = Player.SetSPState(SPSTREAM_NOCHANGE, bSPOn) ; // FALSE
                ASSERT(bRes) ;
            }

            // Set the audio choice value specified by the user
            lStream = SendDlgItemMessage(hDlg, IDD_LANG_AUDIOLANG, CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0) ;
            if (CB_ERR == lStream)
                DbgLog((LOG_ERROR, 1, TEXT("WARNING: Couldn't get selected audio stream id (Error %d)"), lStream)) ;
            else
            {
                bRes = Player.SetAudioState(lStream) ;
                ASSERT(bRes) ;
            }
        }

        // Now fall through to just end the dialog

        case IDCANCEL:
            EndDialog(hDlg, TRUE);
            return TRUE;
        }
        break;

    default:
        break ;
    }

    return FALSE;
}

LRESULT CALLBACK SelectViewLevel(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_INITDIALOG:
        Player.MakeParentalLevelList(hDlg, IDD_VIEW_LEVELLIST) ;
        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
        {
            LONG  lLevel ;
            lLevel = SendDlgItemMessage(hDlg, IDD_VIEW_LEVELLIST, LB_GETCURSEL, (WPARAM) 0, (LPARAM) 0) ;
            if (CB_ERR == lLevel)
                DbgLog((LOG_ERROR, 1, TEXT("WARNING: Couldn't get selected parental control level (Error %d)"), lLevel)) ;
            else
                Player.SetParentalLevel(lLevel) ;
        }

        // Now fall through to just end the dialog

        case IDCANCEL:
            EndDialog(hDlg, TRUE);
            return TRUE;
        }
        break;

    default:
        break ;
    }

    return FALSE;
}

// -------------------------------------------------------------------------
//                .... Windows App Requirement ends ....
// -------------------------------------------------------------------------

// -------------------------------------------------------------------------
//           ....  CSampleDVDPlay class implementation  ....
// -------------------------------------------------------------------------

CSampleDVDPlay::CSampleDVDPlay(void)
{
    CoInitialize(NULL) ;

    // The app stuff
    m_dwRenderFlag = 0 ;
    m_bMenuOn = FALSE ;
    m_bCCOn = FALSE ;
    m_bFullScrnOn = FALSE ;
    m_ulSPStream = 0 ;
    m_bSPOn = TRUE ;
    m_ulAudioStream = 0 ;
    m_bStillOn = FALSE ;
    m_bLangKnown = FALSE ;
    m_eState = Unknown ;
    m_ulParentCtrlLevel = 3 ;  // 3 is the index to level 6 which is for "R"

    m_pDvdGB = NULL ;
    m_pGraph = NULL ;
    m_pDvdC = NULL ;
    m_pDvdI = NULL ;
    m_pVW = NULL ;
    m_pMC = NULL ;
    m_pME = NULL ;

    ZeroMemory(m_achFileName, sizeof(m_achFileName)) ;
    ZeroMemory(m_achStillText, sizeof(m_achStillText)) ;
    ZeroMemory(m_achTitleText, sizeof(m_achTitleText)) ;
    ZeroMemory(m_achChapterText, sizeof(m_achChapterText)) ;
    ZeroMemory(m_achTimeText, sizeof(m_achTimeText)) ;
}

CSampleDVDPlay::~CSampleDVDPlay(void)
{
    // Release the DS interfaces we have got
    ReleaseInterfaces() ;

    if (m_pDvdGB)
        m_pDvdGB->Release() ;

    CoUninitialize() ;
    DbgLog((LOG_TRACE, 0, TEXT("CSampleDVDPlay d-tor exiting..."))) ;
}

void CSampleDVDPlay::ReleaseInterfaces(void)
{
    if (m_pDvdC) {
        m_pDvdC->Release() ;
        m_pDvdC = NULL ;
    }
    if (m_pDvdI) {
        m_pDvdI->Release() ;
        m_pDvdI = NULL ;
    }
    if (m_pVW) {
        m_pVW->Release() ;
        m_pVW = NULL ;
    }
    if (m_pMC) {
        m_pMC->Release() ;
        m_pMC = NULL ;
    }
    if (m_pME) {
        // clear any already set notification arrangement
        m_pME->SetNotifyWindow(NULL, WM_DVDPLAY_EVENT, (ULONG)(LPVOID)m_pME) ;
        m_pME->Release() ;
        m_pME = NULL ;
    }
    if (m_pGraph) {
        m_pGraph->Release() ;
        m_pGraph = NULL ;
    }
}

void CSampleDVDPlay::SetAppValues(HINSTANCE hInst, LPTSTR szAppName,
  int iAppTitleResId)
{
    // The Windows stuff
    m_hInstance = hInst ;
    lstrcpy(m_szAppName, APPNAME) ;
    LoadString(m_hInstance, IDS_APP_TITLE, m_szTitle, 100) ;
}

BOOL CSampleDVDPlay::InitApplication(void)
{
    WNDCLASSEX  wc ;

    // Win32 will always set hPrevInstance to NULL, so lets check
    // things a little closer. This is because we only want a single
    // version of this app to run at a time
    m_hWnd = FindWindow (m_szAppName, m_szTitle) ;
    if (m_hWnd) {
        // We found another version of ourself. Lets defer to it:
        if (IsIconic(m_hWnd)) {
            ShowWindow(m_hWnd, SW_RESTORE);
        }
        SetForegroundWindow(m_hWnd);

        // If this app actually had any functionality, we would
        // also want to communicate any action that our 'twin'
        // should now perform based on how the user tried to
        // execute us.
        return FALSE;
    }

    // Register the app main window class
    wc.cbSize        = sizeof(wc) ;
    wc.style         = CS_HREDRAW | CS_VREDRAW ;
    wc.lpfnWndProc   = (WNDPROC) WndProc ;
    wc.cbClsExtra    = 0 ;
    wc.cbWndExtra    = 0 ;
    wc.hInstance     = m_hInstance ;
    wc.hIcon         = LoadIcon(m_hInstance, m_szAppName) ;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) ;
    wc.lpszMenuName  = m_szAppName ;
    wc.lpszClassName = m_szAppName ;
    wc.hIconSm       = NULL ;
    if (0 == RegisterClassEx(&wc))
    {
        DbgLog((LOG_ERROR, 0,
        TEXT("ERROR: RegisterClassEx() for app class failed (Error %ld)"),
        GetLastError())) ;
        return FALSE ;
    }

    return TRUE ;
}

BOOL CSampleDVDPlay::InitInstance(int nCmdShow)
{
    m_hWnd = CreateWindowEx(0, m_szAppName, m_szTitle, WS_OVERLAPPEDWINDOW,
                        200, 400, 400, 200,
                        NULL, NULL, m_hInstance, NULL);
    if (!m_hWnd) {
        return FALSE ;
    }

    ShowWindow(m_hWnd, nCmdShow);
    UpdateWindow(m_hWnd) ;

    //
    // The DirectShow stuff:
    //     Now instantiate the DVD Graph Builder object and start working
    //
    HRESULT hr = CoCreateInstance(CLSID_DvdGraphBuilder, NULL, CLSCTX_INPROC,
IID_IDvdGraphBuilder, (LPVOID *)&m_pDvdGB) ;
    if (FAILED(hr) || NULL == m_pDvdGB)
    {
        MessageBox(m_hWnd,
                TEXT("DirectShow DVD software not installed properly.\nPress OK to end the app."),
                TEXT("Error"), MB_OK | MB_ICONSTOP) ;
        return FALSE ;
    }

    //
    // By default we use HW decoding as preferred mode. Set menu option.
    // Also we don't turn on CC by default.
    m_dwRenderFlag = AM_DVD_HWDEC_PREFER ;
    CheckMenuItem(GetMenu(m_hWnd), IDM_HWMAX, MF_CHECKED) ;
    CheckMenuItem(GetMenu(m_hWnd), IDM_CC, MF_UNCHECKED) ;
    CheckMenuItem(GetMenu(m_hWnd), IDM_FULLSCRN, MF_UNCHECKED) ;

    return TRUE;
}

void CSampleDVDPlay::BuildGraph(void)
{
    // First release any existing interface pointer(s)
    ReleaseInterfaces() ;

    // Check if a DVD-Video volume name has been specified; if so, use that
    WCHAR    achwFileName[MAX_PATH] ;
    LPCWSTR  lpszwFileName = NULL ;  // by default
    if (lstrlen(m_achFileName) > 0)  // if something was specified before
    {
#ifdef UNICODE
        lstrcpy(achwFileName, m_achFileName) ;
#else
        MultiByteToWideChar(CP_ACP, 0, m_achFileName, -1, achwFileName, MAX_PATH) ;
#endif // UNICODE

        lpszwFileName = achwFileName ;
    }

    // Build the graph
    AM_DVD_RENDERSTATUS   Status ;
    HRESULT hr = m_pDvdGB->RenderDvdVideoVolume(lpszwFileName,
                                    m_dwRenderFlag, &Status) ;
    if (FAILED(hr))
    {
        AMGetErrorText(hr, m_achBuffer, sizeof(m_achBuffer)) ;
        MessageBox(m_hWnd, m_achBuffer, m_szAppName, MB_OK) ;
        return ;
    }
    if (S_FALSE == hr)  // if partial success
    {
        TCHAR    achStatusText[1000] ;
        if (0 == GetStatusText(&Status, achStatusText, sizeof(achStatusText)))
        {
            lstrcpy(achStatusText, TEXT("An unknown error has occurred")) ;
        }
        lstrcat(achStatusText, TEXT("\n\nDo you still want to continue?")) ;
        if (IDNO == MessageBox(m_hWnd, achStatusText, TEXT("Warning"), MB_YESNO))
        {
            return ;
        }
    }

    m_eState = Stopped ;  // graph has been built, however bad it may be

    // Now get all the interfaces to playback the DVD-Video volume
    hr = m_pDvdGB->GetFiltergraph(&m_pGraph) ;
    ASSERT(SUCCEEDED(hr) && m_pGraph) ;

    hr = m_pGraph->QueryInterface(IID_IMediaControl, (LPVOID *)&m_pMC) ;
    ASSERT(SUCCEEDED(hr) && m_pMC) ;

    hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (LPVOID *)&m_pME) ;
    ASSERT(SUCCEEDED(hr) && m_pME) ;

    //
    // Also set up the event notification so that the main window gets
    // informed about all that we care about during playback.
    //
    hr = m_pME->SetNotifyWindow((OAHWND) m_hWnd, WM_DVDPLAY_EVENT,
(ULONG)(LPVOID)m_pME) ;
    ASSERT(SUCCEEDED(hr)) ;

    hr = m_pDvdGB->GetDvdInterface(IID_IDvdControl, (LPVOID *)&m_pDvdC) ;
    ASSERT(SUCCEEDED(hr) && m_pDvdC) ;

    hr = m_pDvdGB->GetDvdInterface(IID_IDvdInfo, (LPVOID *)&m_pDvdI) ;
    ASSERT(SUCCEEDED(hr) && m_pDvdI) ;

    IAMLine21Decoder  *pL21Dec ;
    hr = m_pDvdGB->GetDvdInterface(IID_IAMLine21Decoder, (LPVOID *)&pL21Dec) ;
    if (pL21Dec)
    {
    EnableMenuItem(GetMenu(m_hWnd), IDM_CC, MF_ENABLED) ;
    pL21Dec->SetServiceState(m_bCCOn ? AM_L21_CCSTATE_On : AM_L21_CCSTATE_Off) ;
    pL21Dec->Release() ;
    }
    else
    {
        EnableMenuItem(GetMenu(m_hWnd), IDM_CC, MF_GRAYED) ;
        DbgLog((LOG_TRACE, 2, TEXT("Line21 Decoder interface not found. Can't CC"))) ;
    }

    // Now change the title of the playback window
    hr = m_pDvdGB->GetDvdInterface(IID_IVideoWindow, (LPVOID *)&m_pVW) ;
    if (SUCCEEDED(hr) && m_pVW)
    {
        TCHAR   achTitle[40] ;
#ifdef UNICODE
        LoadString(m_hInstance, IDS_WINDOW_TITLE, achwTitle, sizeof(achwTitle)) ;
#else
        WCHAR   achwTitle[40] ;
        LoadString(m_hInstance, IDS_WINDOW_TITLE, achTitle, sizeof(achTitle)) ;
        MultiByteToWideChar(CP_ACP, 0, achTitle, -1, achwTitle, sizeof(achwTitle)) ;
#endif // UNICODE
        m_pVW->put_Caption((BSTR)achwTitle) ;
    }
    else
        DbgLog((LOG_ERROR, 1, TEXT("IVideoWindow interface not found. No window title change."))) ;

    return ;
}

BOOL CSampleDVDPlay::Play(void)
{
    HRESULT  hr ;

    //
    // If somehow the still image is On, ask Nav to get just out of that mode
    //
    if (m_eState != Playing  &&  m_bStillOn  &&  m_pDvdC)
    {
        if (SUCCEEDED(hr = m_pDvdC->StillOff()))
       {
            m_bStillOn = FALSE ;
            m_eState = Playing ;
            return TRUE ;    // we are done!!!
        }
        else
            DbgLog((LOG_TRACE, 3, TEXT("IDvdControl::StillOff() failed (Error 0x%lx)"), hr)) ;
    }

    //
    // If we are scanning now, just set the scanning speed to normal, i.e, play
    //
    PLAYER_STATE  State = GetState() ;
    if (Scanning == State)
    {
        hr = m_pDvdC->ForwardScan(1.0) ;
        ASSERT(SUCCEEDED(hr)) ;
    }
    //
    // Parental level needs to be set every time we run, because the Nav flushes its
    // settings everytime the graph is "stopped". So we better apply it on transition
    // to "play".
    // Use the actual value of viewing level rather than list box index
    //
    else if (Stopped == State)  // if stopped, set parental level again
    {
        hr = m_pDvdC->ParentalLevelSelect(m_ViewLevels.GetValue(m_ulParentCtrlLevel)) ;
        ASSERT(SUCCEEDED(hr)) ;
    }

    //
    // We need to do some work to play...
    //
    if (NULL == m_pMC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    hr = m_pMC->Run() ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IMediaControl::Run() failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    // Some state changes now
    m_eState = Playing ;
    m_bStillOn = FALSE ;  // should be, right?

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::Stop(void)
{
    if (NULL == m_pMC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pMC->Stop() ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IMediaControl::Stop() failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Stopped ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::Pause(void)
{
    if (NULL == m_pMC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pMC->Pause() ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IMediaControl::Pause() failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Paused ;

    return TRUE ;  // success

}

BOOL CSampleDVDPlay::FastForward(void)
{
    if (NULL == m_pDvdC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pDvdC->ForwardScan(2.0) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdControl::ForwardScan(2.0) failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Scanning ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::VeryFastForward(void)
{
    if (NULL == m_pDvdC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pDvdC->ForwardScan(8.0) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdControl::ForwardScan(8.0) failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Scanning ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::FastRewind(void)
{
    if (NULL == m_pDvdC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pDvdC->BackwardScan(2.0) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdControl::BackwardScan(2.0) failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Scanning ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::VeryFastRewind(void)
{
    if (NULL == m_pDvdC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pDvdC->BackwardScan(8.0) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdControl::BackwardScan(8.0) failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Scanning ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::SlowPlay(void)
{
    if (NULL == m_pDvdC)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }
    HRESULT hr = m_pDvdC->ForwardScan(0.5) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdControl::ForwardScan(0.5) failed (Error 0x%lx)"), hr)) ;
        return FALSE ;
    }

    m_eState = Scanning ;

    return TRUE ;  // success
}

BOOL CSampleDVDPlay::SetSPState(LONG lSPStream, BOOL bState)
{
    HRESULT hr = m_pDvdC->SubpictureStreamChange(lSPStream, bState) ;
    if (SUCCEEDED(hr))
    {
        m_bSPOn = bState ;
        m_ulSPStream = lSPStream ;  // store new SP stream id
        return TRUE ;  // success
    }

    DbgLog((LOG_ERROR, 0,
            TEXT("WARNING: IDvdControl::SubpictureStreamChange(%ld, %s) failed (Error 0x%lx)"),
            lSPStream, bState ? TEXT("TRUE") : TEXT("FALSE"), hr)) ;
    return FALSE ;  // failure
}

//
// On Windows 95 (and OSR2), the locale information is not retrieved properly.  To get
// the audio and subpicture language names properly, we have to do a bit of work around.
// This should be fixed in Windows 98 making the extra work unnecessary.
//
int CSampleDVDPlay::MakeSPStreamList(HWND hDlg, int iListID)
{
    if (NULL == m_pDvdI)
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdInfo interface not available"))) ;
        return FALSE ;
    }

    ULONG  ulStream ;
    LCID   lcid ;
    TCHAR  achLang[MAX_PATH] ;
    int    iRes ;
    ULONG  ulNumLang ;
    ULONG  ulCurrLang ;
    BOOL   bSPOn ;

    // First clear the list box of all SP stream names
    SendDlgItemMessage(hDlg, iListID, CB_RESETCONTENT, (WPARAM) 0, (LPARAM) 0) ;

    // Now add all other available SP streams
    HRESULT hr = m_pDvdI->GetCurrentSubpicture(&ulNumLang, &ulCurrLang, &bSPOn) ;
    if (FAILED(hr))
    {
        MessageBox(m_hWnd, TEXT("Not ready to find language information"), TEXT("Warning"), MB_OK) ;
        return 0 ;
    }

    for (ulStream = 0 ; ulStream < ulNumLang ; ulStream++)
    {
        if (SUCCEEDED(m_pDvdI->GetSubpictureLanguage(ulStream, &lcid))  &&
            0 != lcid)
        {
            iRes = GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, achLang, MAX_PATH) ;
            if (0 == iRes)
            {
                if (! GetSPLanguage(ulStream, bSPOn, achLang, MAX_PATH) )  // Pre-Win98
                    continue ;
            }
        }
        else  // SP language enum failed
        {
            if (! GetSPLanguage(ulStream, bSPOn, achLang, MAX_PATH) )  // Pre-Win98
                continue ;
        }

        // Add to the listbox now
        iRes = SendDlgItemMessage(hDlg, iListID, CB_ADDSTRING, (WPARAM) 0, (LPARAM)(LPVOID) achLang) ;
        if (CB_ERR == iRes || CB_ERRSPACE == iRes)
        {
            DbgLog((LOG_ERROR, 1, TEXT("Error (%d) adding SP language '%s' to list"), iRes, achLang)) ;
        }
    }

    //
    // GetSPLanguage() method might have changed the currently set SP stream and/or state.
    // Let's set it back to what it was before.
    //
    hr = m_pDvdC->SubpictureStreamChange(m_ulSPStream, m_bSPOn) ;
    ASSERT(SUCCEEDED(hr)) ;

    if (ulStream > 0)
    {
        iRes = SendDlgItemMessage(hDlg, IDD_LANG_SPLANG, CB_SETCURSEL,
                        (WPARAM) m_ulSPStream, (LPARAM) 0) ;
        if (CB_ERR == iRes)
            DbgLog((LOG_ERROR, 1,
                TEXT("WARNING: Couldn't set %ld as selected SP stream id (Error %d)"),
                m_ulSPStream, iRes)) ;
    }
    CheckDlgButton(hDlg, IDD_LANG_SHOWSUBPIC, m_bSPOn) ;

    return ulStream ;
}

void CSampleDVDPlay::GetSPLangCode(DVD_SubpictureATR *pSPATR, LPTSTR lpszCode)
{
    lpszCode[0] = (((LANGINFO *)pSPATR)->wLang & 0xFF) | 0x20 ;        // get byte & lower case
    lpszCode[1] = ((((LANGINFO *)pSPATR)->wLang >> 8) & 0xFF) | 0x20 ; // get byte & lower case
    lpszCode[2] = 0 ;
}

BOOL CSampleDVDPlay::GetSPLanguage(ULONG ulStream, BOOL bSPOn, LPTSTR lpszLang, int iMaxLang)
{
    HRESULT hr = m_pDvdC->SubpictureStreamChange(ulStream, bSPOn) ;
    if (FAILED(hr))
        return FALSE ;  // stream disabled?

    DVD_SubpictureATR SPATR ;
    hr = m_pDvdI->GetCurrentSubpictureAttributes(&SPATR) ;
    ASSERT(SUCCEEDED(hr)) ;
    TCHAR  achCode[3] ;
    GetSPLangCode(&SPATR, achCode) ;
    BOOL bRes = m_Langs.GetLangString(achCode, lpszLang) ;
    ASSERT((bRes && 0 != lstrcmp(TEXT("Unknown"), lpszLang)) ||
           (0 == lstrcmp(TEXT("Unknown"), lpszLang))) ;
    return TRUE ;  // success
}

BOOL CSampleDVDPlay::SetAudioState(LONG lAudioStream)
{
    HRESULT hr = m_pDvdC->AudioStreamChange(lAudioStream) ;
    if (SUCCEEDED(hr))
    {
        m_ulAudioStream = lAudioStream ;  // store new audio stream id
        return TRUE ;  // success
    }

    DbgLog((LOG_ERROR, 0,
        TEXT("WARNING: IDvdControl::AudioStreamChange(%ld) failed (Error 0x%lx)"),
        lAudioStream, hr)) ;
    return FALSE ;  // failure
}

int CSampleDVDPlay::MakeAudioStreamList(HWND hDlg, int iListID)
{
    if (NULL == m_pDvdI)
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdInfo interface not available"))) ;
        return FALSE ;
    }

    ULONG  ulStream ;
    LCID   lcid ;
    TCHAR  achLang[MAX_PATH] ;
    int    iRes ;
    ULONG  ulNumLang ;
    ULONG  ulCurrLang ;

    // First clear the list box of all SP stream names
    SendDlgItemMessage(hDlg, iListID, CB_RESETCONTENT, (WPARAM) 0, (LPARAM) 0) ;

    // Now add all available audio streams
    HRESULT hr = m_pDvdI->GetCurrentAudio(&ulNumLang, &ulCurrLang) ;
    if (FAILED(hr))
    {
        MessageBox(m_hWnd, TEXT("Not ready to find language information"), TEXT("Warning"), MB_OK) ;
        return 0 ;
    }

    for (ulStream = 0 ; ulStream < ulNumLang ; ulStream++)
    {
        if (SUCCEEDED(m_pDvdI->GetAudioLanguage(ulStream, &lcid))  &&
            0 != lcid)
        {
            iRes = GetLocaleInfo(lcid, LOCALE_SENGLANGUAGE, achLang, MAX_PATH) ;
            if (0 == iRes)
            {
                if (! GetAudioLanguage(ulStream, achLang, MAX_PATH) )  // Pre-Win98
                    continue ;
            }
        }
        else
        {
            if (! GetAudioLanguage(ulStream, achLang, MAX_PATH) )  // Pre-Win98
                continue ;
        }

        // Add to the listbox now
        iRes = SendDlgItemMessage(hDlg, iListID, CB_ADDSTRING, (WPARAM) 0, (LPARAM)(LPVOID) achLang) ;
        if (CB_ERR == iRes || CB_ERRSPACE == iRes)
        {
            DbgLog((LOG_ERROR, 1, TEXT("Error (%d) adding audio language '%s' to list"), iRes, achLang)) ;
        }
    }

    //
    // GetAudioLanguage() method might have changed the currently set audio stream.
    // Let's set it back to what it was before.
    //
    hr = m_pDvdC->AudioStreamChange(m_ulAudioStream) ;
    ASSERT(SUCCEEDED(hr)) ;

    if (ulStream > 0)
    {
        iRes = SendDlgItemMessage(hDlg, iListID, CB_SETCURSEL,
                    (WPARAM) m_ulAudioStream, (LPARAM) 0) ;
        if (CB_ERR == iRes)
            DbgLog((LOG_ERROR, 1,
                TEXT("WARNING: Couldn't set %ld as selected audio stream id (Error %d)"),
                m_ulAudioStream, iRes)) ;
    }

    return ulStream ;
}

void CSampleDVDPlay::GetAudioLangCode(DVD_AudioATR *pAudATR, LPTSTR lpszCode)
{
    lpszCode[0] = (((LANGINFO *)pAudATR)->wLang & 0xFF) | 0x20 ;        // get byte & lower case
    lpszCode[1] = ((((LANGINFO *)pAudATR)->wLang >> 8) & 0xFF) | 0x20 ; // get byte & lower case
    lpszCode[2] = 0 ;
}

BOOL CSampleDVDPlay::GetAudioLanguage(ULONG ulStream, LPTSTR lpszLang, int iMaxLang)
{
    HRESULT hr = m_pDvdC->AudioStreamChange(ulStream) ;
    if (FAILED(hr))
        return FALSE ;  // stream disabled?

    DVD_AudioATR AudATR ;
    hr = m_pDvdI->GetCurrentAudioAttributes(&AudATR) ;
    ASSERT(SUCCEEDED(hr)) ;
    TCHAR  achCode[3] ;
    GetAudioLangCode(&AudATR, achCode) ;
    BOOL bRes = m_Langs.GetLangString(achCode, lpszLang) ;
    ASSERT((bRes && 0 != lstrcmp(TEXT("Unknown"), lpszLang)) ||
           (0 == lstrcmp(TEXT("Unknown"), lpszLang))) ;
    return TRUE ;  // success
}

int CSampleDVDPlay::MakeParentalLevelList(HWND hDlg, int iListID)
{
    if (NULL == m_pDvdI)
    {
        DbgLog((LOG_ERROR, 0, TEXT("WARNING: IDvdInfo interface not available"))) ;
        return FALSE ;
    }

    int      iLevels = m_ViewLevels.GetCount() ;
    int      iRes ;

    // First clear the list box of all SP stream names
    SendDlgItemMessage(hDlg, iListID, LB_RESETCONTENT, (WPARAM) 0, (LPARAM) 0) ;

    // Now add all other available SP streams
    for (int i = 0 ; i < iLevels ; i++)
    {
        // Add to the listbox now
        iRes = SendDlgItemMessage(hDlg, iListID, LB_ADDSTRING, (WPARAM) 0,
                                  (LPARAM)(LPVOID) m_ViewLevels.GetName(i)) ;
        if (LB_ERR == iRes || LB_ERRSPACE == iRes)
        {
            DbgLog((LOG_ERROR, 1,
                TEXT("Error (%d) adding parental level '%s'(%d) to list"),
                iRes, m_ViewLevels.GetName(i), i)) ;
        }
    }

    if (iLevels > 0)
    {
        iRes = SendDlgItemMessage(hDlg, iListID, LB_SETCURSEL,
                        (WPARAM) m_ulParentCtrlLevel, (LPARAM) 0) ;
    if (LB_ERR == iRes)
    {
            DbgLog((LOG_ERROR, 1,
                TEXT("WARNING: Couldn't set %ld as selected parent level (Error %d)"),
                m_ulParentCtrlLevel, iRes)) ;
    }
    }
    return iLevels ;
}

ULONG CSampleDVDPlay::SetParentalLevel(LONG lLevel)
{
m_ulParentCtrlLevel = lLevel ;
    return m_ulParentCtrlLevel ;
}

BOOL CSampleDVDPlay::IsLangKnown(void)
{
    if (NULL == m_pDvdI)       // most probably graph hasn't been built yet
        return FALSE ;

    // Otherwise see if we can find out our current SP stream id; that indicates it
    ULONG   ulNumLang ;
    ULONG   ulSPLang ;
    BOOL    bSPOn ;
    HRESULT hr = m_pDvdI->GetCurrentSubpicture(&ulNumLang, &ulSPLang, &bSPOn) ;
    m_bLangKnown = SUCCEEDED(hr) ;

    return m_bLangKnown ;
}

BOOL CSampleDVDPlay::ShowMenu(void)
{
    if (NULL == m_pDvdC || NULL == m_pDvdI)
    {
        MessageBox(m_hWnd, TEXT("DVD-Video playback graph hasn't been built yet"), TEXT("Error"), MB_OK) ;
        return FALSE ;
    }

    DVD_DOMAIN   Domain ;
    HRESULT hr = m_pDvdI->GetCurrentDomain(&Domain) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_TRACE, 0, TEXT("IDvdInfo::GetCurentDomain() failed (0x%lx)"), hr)) ;
        return FALSE ;
    }

    //
    // Decide what the menu state flag should be
    //
    switch (Domain)
    {
        case DVD_DOMAIN_FirstPlay:          // lEvent = 1
        case DVD_DOMAIN_Stop:               // lEvent = 5
            break ;

        case DVD_DOMAIN_VideoManagerMenu:   // lEvent = 2
        case DVD_DOMAIN_VideoTitleSetMenu:  // lEvent = 3
            m_bMenuOn = TRUE ;
            break ;

        case DVD_DOMAIN_Title:              // lEvent = 4
            m_bMenuOn = FALSE ;
            break ;
    }

    if (m_bMenuOn)  // if ON now, turn it OFF
    {
        m_pDvdC->Resume() ;
        m_bMenuOn = FALSE ;
        // Inform the app to update the menu option to show "Menu" again
        PostMessage(m_hWnd, WM_COMMAND, IDM_USER_MENUSTATUS, (LPARAM) 0) ;
        return m_bMenuOn ;
    }

    // Turn root menu ON
    hr = m_pDvdC->MenuCall(DVD_MENU_Root) ;
    if (SUCCEEDED(hr))
    {
        m_bMenuOn = TRUE ;
        // Inform the app to update the menu option to show "Resume" now
        PostMessage(m_hWnd, WM_COMMAND, IDM_USER_MENUSTATUS, (LPARAM) 1) ;
        return m_bMenuOn ;
    }

    return m_bMenuOn ;
}

BOOL CSampleDVDPlay::ClosedCaption(void)
{
    IAMLine21Decoder  *pL21Dec ;
    m_pDvdGB->GetDvdInterface(IID_IAMLine21Decoder, (LPVOID *)&pL21Dec) ;
    if (NULL == pL21Dec)  // we should have not come here at all!!
        MessageBox(m_hWnd, TEXT("Line21 Decoder not found.  Can't show Closed Caption"), TEXT("Error"), MB_OK) ;
    else
    {
        m_bCCOn = !m_bCCOn ;
        pL21Dec->SetServiceState(m_bCCOn ?
                        AM_L21_CCSTATE_On : AM_L21_CCSTATE_Off) ;
        pL21Dec->Release() ;
    }

    return m_bCCOn ;
}

BOOL CSampleDVDPlay::ShowFullScreen(void)
{
    if (NULL == m_pVW)
        MessageBox(m_hWnd, TEXT("Video interface not found.  Can't show in full screen mode."), TEXT("Error"), MB_OK) ;
    else
    {
        if (!m_bFullScrnOn)
            StartFullScreen() ;
        else
            StopFullScreen() ;
    }

    return m_bFullScrnOn ;
}

void CSampleDVDPlay::SetRenderFlag(DWORD dwFlag)
{
    if (AM_DVD_NOVPE == dwFlag)  // VPE related flag
        m_dwRenderFlag ^= dwFlag ;
    else                         // HW/SW Dec related flag
        m_dwRenderFlag = (dwFlag | (AM_DVD_NOVPE & m_dwRenderFlag)) ;
}

DWORD CSampleDVDPlay::GetStatusText(AM_DVD_RENDERSTATUS *pStatus,
    LPTSTR lpszStatusText,
    DWORD dwMaxText)
{
    TCHAR    achBuffer[1000] ;

    if (IsBadWritePtr(lpszStatusText, sizeof(*lpszStatusText) * dwMaxText))
    {
        DbgLog((LOG_ERROR, 0, TEXT("GetStatusText(): bad text buffer param"))) ;
        return 0 ;
    }

    int    iChars ;
    LPTSTR lpszBuff = achBuffer ;
    ZeroMemory(achBuffer, sizeof(TCHAR) * 1000) ;
    if (pStatus->iNumStreamsFailed > 0)
    {
        iChars = wsprintf(lpszBuff,
                        TEXT("* %d out of %d DVD-Video streams failed to render properly\n"),
                        pStatus->iNumStreamsFailed, pStatus->iNumStreams) ;
        lpszBuff += iChars ;

        if (pStatus->dwFailedStreamsFlag & AM_DVD_STREAM_VIDEO)
        {
            iChars = wsprintf(lpszBuff, TEXT("    - video stream\n")) ;
            lpszBuff += iChars ;
        }
        if (pStatus->dwFailedStreamsFlag & AM_DVD_STREAM_AUDIO)
        {
            iChars = wsprintf(lpszBuff, TEXT("    - audio stream\n")) ;
            lpszBuff += iChars ;
        }
        if (pStatus->dwFailedStreamsFlag & AM_DVD_STREAM_SUBPIC)
        {
            iChars = wsprintf(lpszBuff, TEXT("    - subpicture stream\n")) ;
            lpszBuff += iChars ;
        }
    }

    if (FAILED(pStatus->hrVPEStatus))
    {
        lstrcat(lpszBuff, "* ") ;
        lpszBuff += lstrlen("* ") ;
        iChars = AMGetErrorText(pStatus->hrVPEStatus, lpszBuff, 200) ;
        lpszBuff += iChars ;
        lstrcat(lpszBuff, "\n") ;
        lpszBuff += lstrlen("\n") ;
    }

    if (pStatus->bDvdVolInvalid)
    {
        iChars = wsprintf(lpszBuff, TEXT("* Specified DVD-Video volume was invalid\n")) ;
        lpszBuff += iChars ;
    }
    else if (pStatus->bDvdVolUnknown)
    {
        iChars = wsprintf(lpszBuff, TEXT("* No valid DVD-Video volume could be located\n")) ;
        lpszBuff += iChars ;
    }

    if (pStatus->bNoLine21In)
    {
        iChars = wsprintf(lpszBuff, TEXT("* The video decoder doesn't produce closed caption data\n")) ;
        lpszBuff += iChars ;
    }
    if (pStatus->bNoLine21Out)
    {
        iChars = wsprintf(lpszBuff, TEXT("* Decoded closed caption data not rendered properly\n")) ;
        lpszBuff += iChars ;
    }

    DWORD dwLength = (lpszBuff - achBuffer) * sizeof(*lpszBuff) ;
    dwLength = min(dwLength, dwMaxText) ;
    lstrcpyn(lpszStatusText, achBuffer, dwLength) ;

    return dwLength ;
}

BOOL CSampleDVDPlay::FileSelect(void)
{
    OPENFILENAME  ofn ;
    TCHAR         achFileName[MAX_PATH] ;

    // Init the filename buffer with either a filename or *.ifo
    if (lstrlen(m_achFileName) > 0)
lstrcpy(achFileName, m_achFileName) ;
    else
lstrcpy(achFileName, TEXT("*.ifo")) ;

    ZeroMemory(&ofn, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = m_hWnd ;
    ofn.lpstrFilter = TEXT("IFO Files\0*.ifo\0All Files\0*.*\0") ;
    ofn.nFilterIndex = 1 ;
    ofn.lpstrFile = achFileName ;
    ofn.nMaxFile = sizeof(achFileName) ;
    ofn.lpstrFileTitle = NULL ;
    ofn.lpstrTitle = TEXT("Select DVD-Video Volume") ;
    ofn.nMaxFileTitle = 0 ;
    ofn.lpstrInitialDir = NULL ;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY ;

    if (GetOpenFileName(&ofn)) // user specified a file
    {
        if (m_pDvdC)  // Graph has already been built; set it now
        {
            Stop() ;  // first ask player to stop the playback.

            // Convert TCHAR filename to WCHAR for COM
            WCHAR   achwFileName[MAX_PATH] ;
#ifdef UNICODE
            lstrcpy(achwFileName, achFileName) ;
#else
            MultiByteToWideChar(CP_ACP, 0, achFileName, -1, achwFileName, MAX_PATH) ;
#endif // UNICODE
            HRESULT hr = m_pDvdC->SetRoot(achwFileName) ;
            if (SUCCEEDED(hr))  // if the new file name is valid DVD-video volume
            {
                lstrcpy(m_achFileName, achFileName) ;  // store it
            }
            else
                DbgLog((LOG_ERROR, 2, TEXT("WARNING: SetRoot(%s) failed (Error 0x%lx)"), achFileName, hr)) ;
        }
        else   // graph not yet built; store name and use in graph building
        {
            lstrcpy(m_achFileName, achFileName) ;
        }

        return TRUE ;  // DVD-Video volume changed
    }

    // Either failed or user hit Esc.
    DWORD dw = CommDlgExtendedError() ;
    DbgLog((LOG_TRACE, 3, TEXT("GetOpenFileName() cancelled/failed with error %lu"), dw)) ;
    return FALSE ; // DVD-Video volume not changed
}

LPTSTR CSampleDVDPlay::GetStringRes(int id)
{
    LoadString(GetModuleHandle(NULL), id, m_achBuffer, 100) ;
    return m_achBuffer ;
}

HRESULT CSampleDVDPlay::OnDVDPlayEvent(WPARAM wParam, LPARAM lParam)
{
    ASSERT(lParam == (LONG)(LPVOID) m_pME) ;

    LONG     lEvent ;
    LONG     lParam1, lParam2 ;

    //
    //  Because the message mode for IMediaEvent may not be set before
    //  we get the first event it's important to read all the events
    //  pending when we get a window message to say there are events pending.
    //  GetEvent() returns E_ABORT when no more event is left.
    //
    while (SUCCEEDED(m_pME->GetEvent(&lEvent, &lParam1, &lParam2, 0))) // no wait
    {
        switch (lEvent)
        {
            //
            // First the DVD related events
            //
            case EC_DVD_STILL_ON:
        if (1 == lParam1 && 0xFFFFFFFF == lParam2)
                {
                    wsprintf(m_achStillText, TEXT("Still On")) ;
                    InvalidateRect(m_hWnd, NULL, TRUE) ;
                }
                break ;

            case EC_DVD_STILL_OFF:
                wsprintf(m_achStillText, TEXT("Still Off")) ;
                InvalidateRect(m_hWnd, NULL, TRUE) ;
                break ;

            case EC_DVD_DOMAIN_CHANGE:
                switch (lParam1)
                {
                    case DVD_DOMAIN_FirstPlay:  // = 1
                    case DVD_DOMAIN_Stop:       // = 5
                    break ;

                    case DVD_DOMAIN_VideoManagerMenu:  // = 2
                    case DVD_DOMAIN_VideoTitleSetMenu: // = 3
                        // Inform the app to update the menu option to show "Resume" now
                        PostMessage(m_hWnd, WM_COMMAND, IDM_USER_MENUSTATUS, (LPARAM) 1) ;
                        m_bMenuOn = TRUE ;  // now menu is "On"
                        break ;

                    case DVD_DOMAIN_Title:      // = 4
                        // Inform the app to update the menu option to show "Menu" again
                        PostMessage(m_hWnd, WM_COMMAND, IDM_USER_MENUSTATUS, (LPARAM) 0) ;
                        m_bMenuOn = FALSE ; // now menu is "Off"
                        break ;

                    default: // hmmmm...
                        break ;
                }
                break ;

            case EC_DVD_BUTTON_CHANGE:
                DbgLog((LOG_TRACE, 5, TEXT("DVD Event: Button Changed to %d out of %d"),
                        lParam2, lParam1));
                break;

            case EC_DVD_TITLE_CHANGE:
                wsprintf(m_achTitleText, TEXT("Title %ld"), lParam1) ;
                InvalidateRect(m_hWnd, NULL, TRUE) ;
                break ;

            case EC_DVD_CHAPTER_START:
                wsprintf(m_achChapterText, TEXT("Chapter %ld"), lParam1) ;
                InvalidateRect(m_hWnd, NULL, TRUE) ;
                break ;

            case EC_DVD_CURRENT_TIME:
            {
                DVD_TIMECODE *pTime = (DVD_TIMECODE *) &lParam1 ;
                wsprintf(m_achTimeText, TEXT("Current Time is  %d%d:%d%d:%d%d"),
                        pTime->Hours10, pTime->Hours1,
                        pTime->Minutes10, pTime->Minutes1,
                        pTime->Seconds10, pTime->Seconds1) ;
                InvalidateRect(m_hWnd, NULL, TRUE) ;
            }
            break ;

            //
            // Then the general DirectShow related events
            //
            case EC_COMPLETE:
                DbgLog((LOG_TRACE, 5, TEXT("DVD Event: Playback complete"))) ;
                m_pMC->Stop() ;  // DShow doesn't stop on end; we should do that
                // fall through now...

            case EC_USERABORT:
            case EC_ERRORABORT:
            case EC_FULLSCREEN_LOST:
                DbgLog((LOG_TRACE, 5, TEXT("DVD Event: 0x%lx"), lEvent)) ;
                StopFullScreen() ;  // we must get out of fullscreen mode now
                break ;

            default:
                DbgLog((LOG_TRACE, 5, TEXT("Unknown DVD Event: 0x%lx"), lEvent)) ;
                break ;
        }

        //
        // Remember to free the event params
        //
        m_pME->FreeEventParams(lEvent, lParam1, lParam2) ;

    }  // end of while (GetEvent()) loop

    return 0 ;
}

void CSampleDVDPlay::DrawStatus(HDC hDC)
{
    TextOut(hDC,  10, 10, m_achStillText,   lstrlen(m_achStillText)) ;
    TextOut(hDC,  10, 25, m_achTitleText,   lstrlen(m_achTitleText)) ;
    TextOut(hDC, 110, 25, m_achChapterText, lstrlen(m_achChapterText)) ;
    TextOut(hDC,  10, 40, m_achTimeText,    lstrlen(m_achTimeText)) ;
}

void CSampleDVDPlay::CursorMove(CURSOR_DIR Dir)
{
    if (! m_bMenuOn )  // if menu is not ON, we can't select!!
        return ;

    switch (Dir)
    {
        case Cursor_Up:
            m_pDvdC->UpperButtonSelect() ;
            break ;

        case Cursor_Down:
            m_pDvdC->LowerButtonSelect() ;
            break ;

        case Cursor_Left:
            m_pDvdC->LeftButtonSelect() ;
            break ;

        case Cursor_Right:
            m_pDvdC->RightButtonSelect() ;
            break ;

        default:  // huh!!!
            break ;
    }
}

void CSampleDVDPlay::CursorSelect(void)
{
    if (m_bMenuOn)
        m_pDvdC->ButtonActivate() ;
}

BOOL CSampleDVDPlay::StartFullScreen()
{
    DbgLog((LOG_TRACE, 5, TEXT("CSampleDVDPlay::StartFullScreen()"))) ;

    if (NULL == m_pVW)
    {
        DbgLog((LOG_ERROR, 0,
                TEXT("CSampleDVDPlay::StartFullScreen() -- no IVideoWindow pointer yet"))) ;
        return m_bFullScrnOn ;
    }

    HRESULT   hr ;
    hr = m_pVW->put_MessageDrain((OAHWND) m_hWnd) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0,
                TEXT("CSampleDVDPlay::StartFullScreen() -- IVideoWindow::put_MessageDrain() failed (0x%lx)"),
                hr)) ;
        return m_bFullScrnOn ;
    }
    hr = m_pVW->put_FullScreenMode(OATRUE) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0,
            TEXT("CSampleDVDPlay::StartFullScreen() -- IVideoWindow::put_FullScreenMode() failed (0x%lx)"),
            hr)) ;
        return m_bFullScrnOn ;
    }

    m_bFullScrnOn = TRUE ;  // we are really in full screen mode

    return m_bFullScrnOn ;
}

BOOL CSampleDVDPlay::StopFullScreen()
{
    DbgLog((LOG_TRACE, 5, TEXT("CSampleDVDPlay::StopFullScreen()"))) ;

    HRESULT   hr ;

    if (NULL == m_pVW)
    {
        DbgLog((LOG_ERROR, 0,
                TEXT("CSampleDVDPlay::StopFullScreen() -- no IVideoWindow pointer yet"))) ;
        return m_bFullScrnOn ;
    }

    hr = m_pVW->put_FullScreenMode(OAFALSE) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0,
                TEXT("CSampleDVDPlay::StopFullScreen() -- IVideoWindow::put_FullScreenMode() failed (0x%lx)"),
                hr)) ;
        return m_bFullScrnOn ;
    }
    hr = m_pVW->put_MessageDrain((OAHWND) NULL) ;
    if (FAILED(hr))
    {
        DbgLog((LOG_ERROR, 0,
                TEXT("CSampleDVDPlay::StopFullScreen() -- IVideoWindow::put_MessageDrain() failed (0x%lx)"),
                hr)) ;
        return m_bFullScrnOn ;
    }

    m_bFullScrnOn = FALSE ;  // we are no more in full screen mode

    return m_bFullScrnOn ;
}

CViewLevels::CViewLevels()
{
    m_iCount = 6 ;
    m_alpszNames[0] = TEXT(" G ") ;       m_aiValues[0] = 1 ;
    m_alpszNames[1] = TEXT(" PG ") ;      m_aiValues[1] = 3 ;
    m_alpszNames[2] = TEXT(" PG-13 ") ;   m_aiValues[2] = 4 ;
    m_alpszNames[3] = TEXT(" R ") ;       m_aiValues[3] = 6 ;
    m_alpszNames[4] = TEXT(" NC-17 ") ;   m_aiValues[4] = 7 ;
    m_alpszNames[5] = TEXT(" Adult ") ;   m_aiValues[5] = 8 ;
}

//
// Only 10 languages have been used here as a sample. The list can be extended to
// include any language listed in ISO 639.
//
CDVDLanguages::CDVDLanguages()
{
    m_alpszCodes[0] = TEXT("de") ;    m_alpszLangNames[0] = TEXT("German") ;
    m_alpszCodes[1] = TEXT("en") ;    m_alpszLangNames[1] = TEXT("English") ;
    m_alpszCodes[2] = TEXT("es") ;    m_alpszLangNames[2] = TEXT("Spanish") ;
    m_alpszCodes[3] = TEXT("fr") ;    m_alpszLangNames[3] = TEXT("French") ;
    m_alpszCodes[4] = TEXT("ja") ;    m_alpszLangNames[4] = TEXT("Japanese") ;
    m_alpszCodes[5] = TEXT("ko") ;    m_alpszLangNames[5] = TEXT("Korean") ;
    m_alpszCodes[6] = TEXT("ni") ;    m_alpszLangNames[6] = TEXT("Dutch") ;
    m_alpszCodes[7] = TEXT("pt") ;    m_alpszLangNames[7] = TEXT("Portuguese") ;
    m_alpszCodes[8] = TEXT("sv") ;    m_alpszLangNames[8] = TEXT("Swedish") ;
    m_alpszCodes[9] = TEXT("zh") ;    m_alpszLangNames[9] = TEXT("Chinese") ;
}

BOOL CDVDLanguages::GetLangString(LPTSTR lpszCode, LPTSTR lpszLang)
{
    for (int i = 0 ; i < 10 ; i++)
    {
        if (0 == lstrcmp(lpszCode, m_alpszCodes[i]))  // match!!
        {
            lstrcpy(lpszLang, m_alpszLangNames[i]) ;

return TRUE ;  // got a match
        }
    }
    lstrcpy(lpszLang, TEXT("Unknown")) ;
    return FALSE ;  // didn't get a match
}


DVDSAMPL.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (c) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

//
// DVDSampl.h: DVDGraphBuilder sample app header file
//

//
// App menu ids
//
#define IDM_SELECT           101
#define IDM_ABOUT            102
#define IDM_EXIT             103

#define IDM_FLAGBASE         110
#define IDM_HWMAX            111
#define IDM_HWONLY           112
#define IDM_SWMAX            113
#define IDM_SWONLY           114
#define IDM_NOVPE            115
#define IDM_BUILDGRAPH       121
#define IDM_PLAY             122
#define IDM_PAUSE            123
#define IDM_STOP             124
#define IDM_SLOWPLAY         125
#define IDM_FASTFWD          126
#define IDM_VERYFASTFWD      127
#define IDM_FASTRWND         128
#define IDM_VERYFASTRWND     129
#define IDM_MENU             131
#define IDM_CC               132
#define IDM_LANG             133
#define IDM_VIEWLEVEL        134
#define IDM_FULLSCRN         135

// A custom menu message by which the app will be informed of the current
// menu state.
#define IDM_USER_MENUSTATUS   200

//
// Special consts for the app
//
#define SPSTREAM_NOCHANGE    32
#define SPSTREAM_NONE        32
#define MAX_SPSTREAMID       32

//
// Version info related constant ids
//
#define DLG_VERFIRST        400
#define IDC_COMPANY         DLG_VERFIRST
#define IDC_FILEDESC        DLG_VERFIRST+1
#define IDC_PRODVER         DLG_VERFIRST+2
#define IDC_COPYRIGHT       DLG_VERFIRST+3
#define IDC_OSVERSION       DLG_VERFIRST+4
#define IDC_TRADEMARK       DLG_VERFIRST+5
#define DLG_VERLAST         DLG_VERFIRST+5

#define IDC_LABEL           DLG_VERLAST+1

//
// App string resource ids
//
#define IDS_APP_TITLE       500
#define IDS_WINDOW_TITLE    501
#define IDS_VER_INFO_LANG   502
#define IDS_VERSION_ERROR   503

//
// Playback Settings dialog ids
//
#define IDD_LANGUAGE                600
#define IDD_LANG_AUDIOLANG          601
#define IDD_LANG_SHOWSUBPIC         602
#define IDD_LANG_SPLANG             603
#define IDD_LANG_SPLANGTEXT         604
#define IDD_LANG_AUDIOLANGTEXT      605

#define IDD_VIEWLEVEL               700
#define IDD_VIEW_LEVELTEXT          701
#define IDD_VIEW_LEVELLIST          702

//
// Special user message used by the app for event notification
//
#define WM_DVDPLAY_EVENT    (WM_USER+100)

//
// Some enumerated type definitions...
//

// Cursor movement directions
typedef enum {
    Cursor_Up = 0, Cursor_Down, Cursor_Left, Cursor_Right
} CURSOR_DIR ;

// Player state
typedef enum {
    Unknown = 0, Stopped, Paused, Playing, Scanning
} PLAYER_STATE ;

//
// A class to wrap the viewing level number and names
//
class CViewLevels {
public:
    CViewLevels() ;

    int     GetCount(void)   { return m_iCount ; } ;
    LPCTSTR GetName(int i)   { return m_alpszNames[i] ; } ;
    int     GetValue(int i)  { return m_aiValues[i] ; } ;

private:
    LPCTSTR   m_alpszNames[6] ;
    int       m_aiValues[6] ;
    int       m_iCount ;
} ;

//
// A class to wrap a few language names and the corresponding 2-char codes
//
// We have only 10 languages from ISO 639 as a sample. It can be extended to
// include any other language in ISO 639.
//
class CDVDLanguages {
public:
    CDVDLanguages() ;

    BOOL GetLangString(LPTSTR lpszCode, LPTSTR lpszLang) ;

private:
    LPCTSTR  m_alpszCodes[10] ;
    LPCTSTR  m_alpszLangNames[10] ;
} ;

//
// An easy way of getting the lang code from the DVD_Subpicture/AudioATR data
//
typedef struct _LangInfo {
    WORD  wRes1 ;  // don't care, just skip 2 bytes
    WORD  wLang ;  // lang code as a WORD value
    WORD  wRes2 ;  // don't care, another 2 bytes
} LANGINFO, *PLANGINFO ;

//
// Sample DVD Playback class
//
class CSampleDVDPlay {
public:   // public methods for Windows structure to call
    CSampleDVDPlay(void) ;
    ~CSampleDVDPlay(void) ;

    void    SetAppValues(HINSTANCE hInst, LPTSTR szAppName,
                         int iAppTitleResId) ;
    BOOL    InitInstance(int nCmdShow) ;
    BOOL    InitApplication(void) ;
    LPTSTR  GetAppName() { return m_szAppName ; } ;
    HINSTANCE GetInstance() { return m_hInstance ; } ;
    HWND    GetWindow() { return m_hWnd ; } ;

    void    BuildGraph(void) ;
    BOOL    Play(void) ;
    BOOL    Pause(void) ;
    BOOL    Stop(void) ;
    BOOL    FastForward(void) ;
    BOOL    VeryFastForward(void) ;
    BOOL    FastRewind(void) ;
    BOOL    VeryFastRewind(void) ;
    BOOL    SlowPlay(void) ;
    int     MakeSPStreamList(HWND hDlg, int iListID) ;
    ULONG   GetSPStream(void)   { return m_ulSPStream ; } ;
    BOOL    SetSPState(LONG lStream, BOOL bState) ;
    void    GetSPLangCode(DVD_SubpictureATR *pSPATR, LPTSTR lpszCode) ;
    BOOL    GetSPLanguage(ULONG ulStream, BOOL bSPOn, LPTSTR lpszLang, int iMaxLang) ;
    int     MakeAudioStreamList(HWND hDlg, int iListID) ;
    ULONG   GetAudioStream(void)   { return m_ulAudioStream ; } ;
    BOOL    SetAudioState(LONG lStream) ;
    BOOL    GetAudioLanguage(ULONG ulStream, LPTSTR lpszLang, int iMaxLang) ;
    void    GetAudioLangCode(DVD_AudioATR *pAudATR, LPTSTR lpszCode) ;
    int     MakeParentalLevelList(HWND hDlg, int iListID) ;
    ULONG   SetParentalLevel(LONG lLevel) ;
    BOOL    ShowMenu(void) ;
    BOOL    ClosedCaption(void) ;
    BOOL    ShowFullScreen(void) ;
    BOOL    StartFullScreen(void) ;
    BOOL    StopFullScreen(void) ;
    void    CursorMove(CURSOR_DIR Dir) ;
    void    CursorSelect(void) ;
    void    SetRenderFlag(DWORD dwFlag) ;
    DWORD   GetRenderFlag(void)  { return m_dwRenderFlag ; } ;
    BOOL    IsStillOn(void)  { return m_bStillOn ; } ;
    BOOL    IsLangKnown(void) ;
    BOOL    IsInitialized(void)  { return m_eState != Unknown ; } ;
    BOOL    CanChangeSpeed(void)  { return Playing == m_eState || Scanning == m_eState ; } ;
    PLAYER_STATE GetState(void)  { return m_eState ; } ;

    BOOL    FileSelect(void) ;
    DWORD   GetStatusText(AM_DVD_RENDERSTATUS *pStatus,
                          LPTSTR lpszStatusText,
                          DWORD dwMaxText) ;
    HRESULT OnDVDPlayEvent(WPARAM wParam, LPARAM lParam) ;
    void    DrawStatus(HDC hDC) ;

private:  // private helper methods for the class' own use
    LPTSTR  GetStringRes (int id) ;
    void    ReleaseInterfaces(void) ;

private:  // internal state info
    HINSTANCE     m_hInstance ;       // current instance
    HWND          m_hWnd ;            // app window handle
    TCHAR         m_szAppName[50] ;   // name of the app
    TCHAR         m_szTitle[50] ;     // title bar text
    TCHAR         m_achBuffer[100] ;  // app's internal buffer for res strings etc.

    CViewLevels   m_ViewLevels ;      // viewing level data (static)
    CDVDLanguages m_Langs ;           // language list data (static)
    BOOL          m_bSPOn ;           // Is Subpicture On?
    ULONG         m_ulSPStream ;      // SubPicture stream number
    ULONG         m_ulAudioStream ;   // Audio stream number
    BOOL          m_bMenuOn ;         // is DVD menu being shown now?
    BOOL          m_bCCOn ;           // is CC being shown?
    BOOL          m_bFullScrnOn ;     // is showing in full screen mode?
    PLAYER_STATE  m_eState ;          // player state (run/pause/stop/...)
    BOOL          m_bStillOn ;        // is a still image on now?
    BOOL          m_bLangKnown ;      // can ask for audio/subpic lang etc?
    ULONG         m_ulParentCtrlLevel ;// parental control level of viewing
    DWORD         m_dwRenderFlag ;    // flags to use for building graph
    TCHAR         m_achFileName[MAX_PATH] ; // current root file name
    TCHAR         m_achStillText[20] ;   // still status text for display
    TCHAR         m_achTitleText[20] ;   // current title info for display
    TCHAR         m_achChapterText[20] ; // current chapter for display
    TCHAR         m_achTimeText[50] ;    // current time for display

    IDvdGraphBuilder *m_pDvdGB ;      // IDvdGraphBuilder interface
    IVideoWindow     *m_pVW ;         // IVideoWindow interface
    IGraphBuilder    *m_pGraph ;      // IGraphBuilder interface
    IDvdInfo         *m_pDvdI ;       // IDvdInfo interface
    IDvdControl      *m_pDvdC ;       // IDvdControl interface
    IMediaControl    *m_pMC ;         // IMediaControl interface
    IMediaEventEx    *m_pME ;         // IMediaEventEx interface
    IAMLine21Decoder *m_pL21Dec ;     // IAMLine21Decoder interface
} ;


EZPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <windows.h>
#include <windowsx.h>
#include <streams.h>
#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>
#include "resource.h"
#include "EZuids.h"
#include "iEZ.h"
#include "EZrgb24.h"
#include "EZprop.h"

//
// CreateInstance
//
// Used by the ActiveMovie base classes to create instances
//
CUnknown *CEZrgb24Properties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    CUnknown *punk = new CEZrgb24Properties(lpunk, phr);
    if (punk == NULL) {
*phr = E_OUTOFMEMORY;
    }
    return punk;

} // CreateInstance

//
// Constructor
//
CEZrgb24Properties::CEZrgb24Properties(LPUNKNOWN pUnk, HRESULT *phr) :
    CBasePropertyPage(NAME("Special Effects Property Page"),
                      pUnk,IDD_EZrgb24PROP,IDS_TITLE),
    m_pIPEffect(NULL),
    m_bIsInitialized(FALSE)
{
    ASSERT(phr);

} // (Constructor)

//
// OnReceiveMessage
//
// Handles the messages for our property window
//
BOOL CEZrgb24Properties::OnReceiveMessage(HWND hwnd,
                                          UINT uMsg,
                                          WPARAM wParam,
                                          LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_COMMAND:
        {
            if (m_bIsInitialized)
            {
                m_bDirty = TRUE;
                if (m_pPageSite)
                {
                    m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
                }
            }
            return (LRESULT) 1;
        }

    }
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);

} // OnReceiveMessage

//
// OnConnect
//
// Called when we connect to a transform filter
//
HRESULT CEZrgb24Properties::OnConnect(IUnknown *pUnknown)
{
    ASSERT(m_pIPEffect == NULL);

    HRESULT hr = pUnknown->QueryInterface(IID_IIPEffect, (void **) &m_pIPEffect);
    if (FAILED(hr)) {
        return E_NOINTERFACE;
    }

    ASSERT(m_pIPEffect);

    // Get the initial image FX property
    m_pIPEffect->get_IPEffect(&m_effect, &m_start, &m_length);
    m_bIsInitialized = FALSE ;
    return NOERROR;

} // OnConnect

//
// OnDisconnect
//
// Likewise called when we disconnect from a filter
//
HRESULT CEZrgb24Properties::OnDisconnect()
{
    // Release of Interface after setting the appropriate old effect value

    if (m_pIPEffect == NULL) {
        return E_UNEXPECTED;
    }

    m_pIPEffect->Release();
    m_pIPEffect = NULL;
    return NOERROR;

} // OnDisconnect

//
// OnActivate
//
// We are being activated
//
HRESULT CEZrgb24Properties::OnActivate()
{
    TCHAR   sz[60];

    _stprintf(sz, TEXT("%f"), m_length);
    Edit_SetText(GetDlgItem(m_Dlg, IDC_LENGTH), sz);
    _stprintf(sz, TEXT("%f"), m_start);
    Edit_SetText(GetDlgItem(m_Dlg, IDC_START), sz);

    CheckRadioButton(m_Dlg, IDC_EMBOSS, IDC_NONE, m_effect);
    m_bIsInitialized = TRUE;
    return NOERROR;

} // OnActivate

//
// OnDeactivate
//
// We are being deactivated
//
HRESULT CEZrgb24Properties::OnDeactivate(void)
{
    ASSERT(m_pIPEffect);
    m_bIsInitialized = FALSE;
    GetControlValues();
    return NOERROR;

} // OnDeactivate

//
// OnApplyChanges
//
// Apply any changes so far made
//
HRESULT CEZrgb24Properties::OnApplyChanges()
{
    GetControlValues();
    m_pIPEffect->put_IPEffect(m_effect, m_start, m_length);

    return NOERROR;
} // OnApplyChanges

void CEZrgb24Properties::GetControlValues()
{
    ASSERT(m_pIPEffect);
    TCHAR sz[STR_MAX_LENGTH];
    REFTIME tmp1, tmp2 ;

    // Get the start and effect times

    Edit_GetText(GetDlgItem(m_Dlg, IDC_LENGTH), sz, STR_MAX_LENGTH);
    tmp2 = COARefTime(atof(sz));

    Edit_GetText(GetDlgItem(m_Dlg, IDC_START), sz, STR_MAX_LENGTH);
    tmp1 = COARefTime(atof(sz));

    // Quick validatation of the fields

    if (tmp1 >= 0 && tmp2 >= 0) {
        m_start = tmp1;
        m_length = tmp2;
    }

    // Find which special effect we have selected

    for (int i = IDC_EMBOSS; i <= IDC_NONE; i++) {
        if (IsDlgButtonChecked(m_Dlg, i)) {
            m_effect = i;
            break;
        }
    }
}


EZPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

class CEZrgb24Properties : public CBasePropertyPage
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

private:

    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate();
    HRESULT OnDeactivate();
    HRESULT OnApplyChanges();

    void    GetControlValues();

    CEZrgb24Properties(LPUNKNOWN lpunk, HRESULT *phr);

    BOOL m_bIsInitialized;      // Used to ignore startup messages
    int m_effect;               // Which effect are we processing
    REFTIME m_start;            // When the effect will begin
    REFTIME m_length;           // And how long it will last for
    IIPEffect *m_pIPEffect;     // The custom interface on the filter

}; // EZrgb24Properties


EZRGB24.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//
// What this sample illustrates
//
// A special effects filter that does image filtering and colour changes. We
// work on decompressed true colour images and apply a range of effects. The
// effect we do and when it is applied to the video stream can be configured
// through a custom interface we implement and which our property page uses.
// We can also save/load our property settings by supporting IPersistStream
//
//
// Summary
//
// This is a sample special effects filter - it works only with RGB24 formats
// We can be inserted between video decoders and renderers and apply a number
// of different effects to the decompressed video stream. For example we can
// apply red, green or blue filters to the images. We also have posterise and
// greying effects amongst others. The effect can be applied to the stream at
// a given start and end media time, this is defined by using the IIPEffect
// interface and can be viewed by using the property page this filter supports
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the video renderer box and disconnect its input pin from the filter it is
// connected to. Go to the Graph menu and select Insert Filters, from the
// dialog select the Special Effects filter and dismiss the dialog. back in
// the tool workspace connect the output of the decoder to the input of the
// sample filter and then connect the output of the sample to the input of
// the video renderer (GRAPHEDT may put a colour space convertor in between)
// Then press play and then right click on the sample to select a transform
// by default a transform will be applied from 2 to 7 seconds into the movie
//
//
// Files
//
// ezprop.cpp           A property page to control the video effects
// ezprop.h             Class definition for the property page object
// ezprop.rc            Dialog box template for the property page
// ezrgb24.cpp          Main filter code that does the special effects
// ezrgb24.def          What APIs we import and export from this DLL
// ezrgb24.h            Class definition for the special effects filter
// ezrgb24.reg          What goes in the registry to make us work
// ezuids.h             Header file containing the filter CLSIDs
// iez.h                Defines the special effects custom interface
// makefile             How we build it...
// resource.h           Microsoft Visual C++ generated resource file
//
//
// Base classes used
//
// CTransformFilter     A transform filter with one input and output pin
// CPersistStream       Handles the grunge of supporting IPersistStream
//
//

#include <windows.h>
#include <streams.h>
#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif
#include "EZuids.h"
#include "iEZ.h"
#include "EZprop.h"
#include "EZrgb24.h"
#include "resource.h"

// Setup information

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Video,       // Major type
    &MEDIASUBTYPE_NULL      // Minor type
};

const AMOVIESETUP_PIN sudpPins[] =
{
    { L"Input",             // Pins string name
      FALSE,                // Is it rendered
      FALSE,                // Is it an output
      FALSE,                // Are we allowed none
      FALSE,                // And allowed many
      &CLSID_NULL,          // Connects to filter
      NULL,                 // Connects to pin
      1,                    // Number of types
      &sudPinTypes          // Pin information
    },
    { L"Output",            // Pins string name
      FALSE,                // Is it rendered
      TRUE,                 // Is it an output
      FALSE,                // Are we allowed none
      FALSE,                // And allowed many
      &CLSID_NULL,          // Connects to filter
      NULL,                 // Connects to pin
      1,                    // Number of types
      &sudPinTypes          // Pin information
    }
};

const AMOVIESETUP_FILTER sudEZrgb24 =
{
    &CLSID_EZrgb24,         // Filter CLSID
    L"Image Effects",       // String name
    MERIT_DO_NOT_USE,       // Filter merit
    2,                      // Number of pins
    sudpPins                // Pin information
};

// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance

CFactoryTemplate g_Templates[] = {
    { L"Image Effects"
    , &CLSID_EZrgb24
    , CEZrgb24::CreateInstance
    , NULL
    , &sudEZrgb24 }
  ,
    { L"Special Effects"
    , &CLSID_EZrgb24PropertyPage
    , CEZrgb24Properties::CreateInstance }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// DllRegisterServer
//
// Handles sample registry and unregistry
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer

//
// Constructor
//
CEZrgb24::CEZrgb24(TCHAR *tszName,
                   LPUNKNOWN punk,
                   HRESULT *phr) :
    CTransformFilter(tszName, punk, CLSID_EZrgb24),
    m_effect(IDC_RED),
    m_lBufferRequest(1),
    CPersistStream(punk, phr)
{
    char sz[60];
    GetProfileStringA("Quartz", "EffectStart", "2.0", sz, 60);
    m_effectStartTime = COARefTime(atof(sz));
    GetProfileStringA("Quartz", "EffectLength", "5.0", sz, 60);
    m_effectTime = COARefTime(atof(sz));

} // (Constructor)

//
// CreateInstance
//
// Provide the way for COM to create a EZrgb24 object
//
CUnknown *CEZrgb24::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{
    CEZrgb24 *pNewObject = new CEZrgb24(NAME("Image Effects"), punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return pNewObject;

} // CreateInstance

//
// NonDelegatingQueryInterface
//
// Reveals IIPEffect and ISpecifyPropertyPages
//
STDMETHODIMP CEZrgb24::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);

    if (riid == IID_IIPEffect) {
        return GetInterface((IIPEffect *) this, ppv);
    } else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else {
        return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
    }

} // NonDelegatingQueryInterface

//
// Transform
//
// Copy the input sample into the output sample - then transform the output
// sample 'in place'. If we have all keyframes, then we shouldn't do a copy
// If we have cinepak or indeo and are decompressing frame N it needs frame
// decompressed frame N-1 available to calculate it, unless we are at a
// keyframe. So with keyframed codecs, you can't get away with applying the
// transform to change the frames in place, because you'll screw up the next
// frames decompression. The runtime MPEG decoder does not have keyframes in
// the same way so it can be done in place. We know if a sample is key frame
// as we transform because the sync point property will be set on the sample
//
HRESULT CEZrgb24::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
    // Copy the properties across

    HRESULT hr = Copy(pIn, pOut);
    if (FAILED(hr)) {
        return hr;
    }

    // Check to see if it is time to do the sample

    CRefTime tStart, tStop ;
    pIn->GetTime((REFERENCE_TIME *) &tStart, (REFERENCE_TIME *) &tStop);

    if (tStart >= m_effectStartTime) {
        if (tStop <= (m_effectStartTime + m_effectTime)) {
            return Transform(pOut);
        }
    }
    return NOERROR;

} // Transform

//
// Copy
//
// Make destination an identical copy of source
//
HRESULT CEZrgb24::Copy(IMediaSample *pSource, IMediaSample *pDest) const
{
    // Copy the sample data

    BYTE *pSourceBuffer, *pDestBuffer;
    long lSourceSize = pSource->GetActualDataLength();
    long lDestSize= pDest->GetSize();

    ASSERT(lDestSize >= lSourceSize);

    pSource->GetPointer(&pSourceBuffer);
    pDest->GetPointer(&pDestBuffer);

    CopyMemory( (PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize);

    // Copy the sample times

    REFERENCE_TIME TimeStart, TimeEnd;
    if (NOERROR == pSource->GetTime(&TimeStart, &TimeEnd)) {
        pDest->SetTime(&TimeStart, &TimeEnd);
    }

    LONGLONG MediaStart, MediaEnd;
    if (pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) {
        pDest->SetMediaTime(&MediaStart,&MediaEnd);
    }

    // Copy the Sync point property

    HRESULT hr = pSource->IsSyncPoint();
    if (hr == S_OK) {
        pDest->SetSyncPoint(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetSyncPoint(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the media type

    AM_MEDIA_TYPE *pMediaType;
    pSource->GetMediaType(&pMediaType);
    pDest->SetMediaType(pMediaType);
    DeleteMediaType(pMediaType);

    // Copy the preroll property

    hr = pSource->IsPreroll();
    if (hr == S_OK) {
        pDest->SetPreroll(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetPreroll(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the discontinuity property

    hr = pSource->IsDiscontinuity();
    if (hr == S_OK) {
pDest->SetDiscontinuity(TRUE);
    }
    else if (hr == S_FALSE) {
        pDest->SetDiscontinuity(FALSE);
    }
    else {  // an unexpected error has occured...
        return E_UNEXPECTED;
    }

    // Copy the actual data length

    long lDataLength = pSource->GetActualDataLength();
    pDest->SetActualDataLength(lDataLength);

    return NOERROR;

} // Copy

//
// Transform (in place)
//
// 'In place' apply the image effect to this sample
//
HRESULT CEZrgb24::Transform(IMediaSample *pMediaSample)
{
    AM_MEDIA_TYPE* pType = &m_pInput->CurrentMediaType();
    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pType->pbFormat;

    BYTE *pData;                // Pointer to the actual image buffer
    long lDataLen;              // Holds length of any given sample
    unsigned int grey,grey2;    // Used when applying greying effects
    int iPixel;                 // Used to loop through the image pixels
    int temp,x,y;               // General loop counters for transforms
    RGBTRIPLE *prgb;            // Holds a pointer to the current pixel

    pMediaSample->GetPointer(&pData);
    lDataLen = pMediaSample->GetSize();

    // Get the image properties from the BITMAPINFOHEADER

    int iPixelSize = pvi->bmiHeader.biBitCount / 8;
    int cxImage    = pvi->bmiHeader.biWidth;
    int cyImage    = pvi->bmiHeader.biHeight;
    int cbImage    = cyImage * cxImage * iPixelSize;
    int numPixels  = cxImage * cyImage;

    switch (m_effect)
    {
        case IDC_NONE: break;

        // Zero out the green and blue components to leave only the red
        // so acting as a filter - for better visual results, compute a
        // greyscale value for the pixel and make that the red component

        case IDC_RED:

            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
        prgb->rgbtGreen = 0;
                prgb->rgbtBlue = 0;
            }
            break;

        case IDC_GREEN:

            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtRed = 0;
                prgb->rgbtBlue = 0;
            }
            break;

        case IDC_BLUE:
            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtRed = 0;
                prgb->rgbtGreen = 0;
            }
            break;

        // Bitwise shift each component to the right by 1
        // this results in the image getting much darker

case IDC_DARKEN:

            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtRed = prgb->rgbtRed >> 1;
                prgb->rgbtGreen = prgb->rgbtGreen >> 1;
                prgb->rgbtBlue = prgb->rgbtBlue >> 1;
            }
            break;

        // Toggle each bit - this gives a sort of X-ray effect

        case IDC_XOR:
            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtRed = prgb->rgbtRed ^ 0xff;
                prgb->rgbtGreen = prgb->rgbtGreen ^ 0xff;
                prgb->rgbtBlue = prgb->rgbtBlue ^ 0xff;
            }
            break;

        // Zero out the five LSB per each component

        case IDC_POSTERIZE:
            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtRed = prgb->rgbtRed & 0xe0;
                prgb->rgbtGreen = prgb->rgbtGreen & 0xe0;
                prgb->rgbtBlue = prgb->rgbtBlue & 0xe0;
            }
            break;

        // Take pixel and its neighbor two pixels to the right and average
        // then out - this blurs them and produces a subtle motion effect

        case IDC_BLUR:
            prgb = (RGBTRIPLE*) pData;
            for (y = 0 ; y < pvi->bmiHeader.biHeight; y++) {
                for (x = 2 ; x < pvi->bmiHeader.biWidth; x++,prgb++) {
                    prgb->rgbtRed = (prgb->rgbtRed + prgb[2].rgbtRed) >> 1;
                    prgb->rgbtGreen = (prgb->rgbtGreen + prgb[2].rgbtGreen) >> 1;
                    prgb->rgbtBlue = (prgb->rgbtBlue + prgb[2].rgbtBlue) >> 1;
}
                prgb +=2;
            }
            break;

        // An excellent greyscale calculation is:
        //      grey = (30 * red + 59 * green + 11 * blue) / 100
        // This is a bit too slow so a faster calculation is:
        //      grey = (red + green) / 2

        case IDC_GREY:
            prgb = (RGBTRIPLE*) pData;
            for (iPixel=0; iPixel < numPixels ; iPixel++, prgb++) {
                grey = (prgb->rgbtRed + prgb->rgbtGreen) >> 1;
        prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) grey;
            }
            break;

        // Really sleazy emboss - rather than using a nice 3x3 convulution
        // matrix, we compare the greyscale values of two neighbours. If
        // they are not different, then a mid grey (128, 128, 128) is
        // supplied.  Large differences get father away from the mid grey

        case IDC_EMBOSS:
            prgb = (RGBTRIPLE*) pData;
            for (y = 0 ; y < pvi->bmiHeader.biHeight; y++) {
                grey2 = (prgb->rgbtRed + prgb->rgbtGreen) >> 1;
                prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) 128;
                prgb++;
for (x = 1 ; x < pvi->bmiHeader.biWidth; x++) {
                    grey = (prgb->rgbtRed + prgb->rgbtGreen) >> 1;
                    temp = grey - grey2;
                    if (temp > 127) temp = 127;
                    if (temp < -127) temp = -127;
                    temp += 128;
                    prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) temp;
                    grey2 = grey;
                    prgb++;
        }
            }
            break;
    }
    return NOERROR;

} // Transform (in place)

// Check the input type is OK - return an error otherwise

HRESULT CEZrgb24::CheckInputType(const CMediaType *mtIn)
{
    // check this is a VIDEOINFOHEADER type

    if (*mtIn->FormatType() != FORMAT_VideoInfo) {
        return E_INVALIDARG;
    }

    // Can we transform this type

    if (CanPerformEZrgb24(mtIn)) {
    return NOERROR;
    }
    return E_FAIL;
}

//
// Checktransform
//
// Check a transform can be done between these formats
//
HRESULT CEZrgb24::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)
{
    if (CanPerformEZrgb24(mtIn)) {
        if (*mtIn == *mtOut) {
            return NOERROR;
        }
    }
    return E_FAIL;

} // CheckTransform

//
// DecideBufferSize
//
// Tell the output pin's allocator what size buffers we
// require. Can only do this when the input is connected
//
HRESULT CEZrgb24::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
    // Is the input pin connected

    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;
    pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize();
    ASSERT(pProperties->cbBuffer);

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT( Actual.cBuffers == 1 );

    if (pProperties->cBuffers > Actual.cBuffers ||
            pProperties->cbBuffer > Actual.cbBuffer) {
                return E_FAIL;
    }
    return NOERROR;

} // DecideBufferSize

//
// GetMediaType
//
// I support one type, namely the type of the input pin
// This type is only available if my input is connected
//
HRESULT CEZrgb24::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    // Is the input pin connected

    if (m_pInput->IsConnected() == FALSE) {
        return E_UNEXPECTED;
    }

    // This should never happen

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    // Do we have more items to offer

    if (iPosition > 0) {
        return VFW_S_NO_MORE_ITEMS;
    }

    *pMediaType = m_pInput->CurrentMediaType();
    return NOERROR;

} // GetMediaType

//
// CanPerformEZrgb24
//
// Check if this is a RGB24 true colour format
//
BOOL CEZrgb24::CanPerformEZrgb24(const CMediaType *pMediaType) const
{
    if (IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) {
        if (IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB24)) {
            VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pMediaType->Format();
            return (pvi->bmiHeader.biBitCount == 24);
        }
    }
    return FALSE;

} // CanPerformEZrgb24

#define WRITEOUT(var)  hr = pStream->Write(&var, sizeof(var), NULL); \
       if (FAILED(hr)) return hr;

#define READIN(var)    hr = pStream->Read(&var, sizeof(var), NULL); \
       if (FAILED(hr)) return hr;

//
// GetClassID
//
// This is the only method of IPersist
//
STDMETHODIMP CEZrgb24::GetClassID(CLSID *pClsid)
{
    return CBaseFilter::GetClassID(pClsid);
} // GetClassID

//
// ScribbleToStream
//
// Overriden to write our state into a stream
//
HRESULT CEZrgb24::ScribbleToStream(IStream *pStream)
{
    HRESULT hr;
    WRITEOUT(m_effect);
    WRITEOUT(m_effectStartTime);
    WRITEOUT(m_effectTime);
    return NOERROR;

} // ScribbleToStream

//
// ReadFromStream
//
// Likewise overriden to restore our state from a stream
//
HRESULT CEZrgb24::ReadFromStream(IStream *pStream)
{
    HRESULT hr;
    READIN(m_effect);
    READIN(m_effectStartTime);
    READIN(m_effectTime);
    return NOERROR;

} // ReadFromStream

//
// GetPages
//
// Returns the clsid's of the property pages we support
//
STDMETHODIMP CEZrgb24::GetPages(CAUUID *pPages)
{
    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_EZrgb24PropertyPage;
    return NOERROR;

} // GetPages

//
// get_IPEffect
//
// Return the current effect selected
//
STDMETHODIMP CEZrgb24::get_IPEffect(int *IPEffect,REFTIME *start,REFTIME *length)
{
    CAutoLock cAutolock(&m_EZrgb24Lock);
    CheckPointer(IPEffect,E_POINTER);
    CheckPointer(start,E_POINTER);
    CheckPointer(length,E_POINTER);

    *IPEffect = m_effect;
    *start = COARefTime(m_effectStartTime);
    *length = COARefTime(m_effectTime);

    return NOERROR;

} // get_IPEffect

//
// put_IPEffect
//
// Set the required video effect
//
STDMETHODIMP CEZrgb24::put_IPEffect(int IPEffect,REFTIME start,REFTIME length)
{
    CAutoLock cAutolock(&m_EZrgb24Lock);

    m_effect = IPEffect;
    m_effectStartTime = COARefTime(start);
    m_effectTime = COARefTime(length);

    SetDirty(TRUE);
    return NOERROR;

} // put_IPEffect


EZRGB24.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

class CEZrgb24 : public CTransformFilter,
 public IIPEffect,
 public ISpecifyPropertyPages,
 public CPersistStream
{

public:

    DECLARE_IUNKNOWN;
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    // Reveals IEZrgb24 and ISpecifyPropertyPages
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // CPersistStream stuff
    HRESULT ScribbleToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);

    // Overrriden from CTransformFilter base class

    HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
    HRESULT CheckInputType(const CMediaType *mtIn);
    HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
    HRESULT DecideBufferSize(IMemAllocator *pAlloc,
     ALLOCATOR_PROPERTIES *pProperties);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);

    // These implement the custom IIPEffect interface

    STDMETHODIMP get_IPEffect(int *IPEffect, REFTIME *StartTime, REFTIME *Length);
    STDMETHODIMP put_IPEffect(int IPEffect, REFTIME StartTime, REFTIME Length);

    // ISpecifyPropertyPages interface
    STDMETHODIMP GetPages(CAUUID *pPages);

    // CPersistStream override
    STDMETHODIMP GetClassID(CLSID *pClsid);

private:

    // Constructor
    CEZrgb24(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);

    // Look after doing the special effect
    BOOL CanPerformEZrgb24(const CMediaType *pMediaType) const;
    HRESULT Copy(IMediaSample *pSource, IMediaSample *pDest) const;
    HRESULT Transform(IMediaSample *pMediaSample);

    CCritSecm_EZrgb24Lock;          // Private play critical section
    int         m_effect;               // Which effect are we processing
    CRefTimem_effectStartTime;      // When the effect will begin
    CRefTimem_effectTime;           // And how long it will last for
    const long m_lBufferRequest;// The number of buffers to use

}; // EZrgb24


EZUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Special effects filter CLSID

// { 8B498501-1218-11cf-ADC4-00A0D100041B }
DEFINE_GUID(CLSID_EZrgb24,
0x8b498501, 0x1218, 0x11cf, 0xad, 0xc4, 0x0, 0xa0, 0xd1, 0x0, 0x4, 0x1b);

// And the property page we support

// { 8B498502-1218-11cf-ADC4-00A0D100041B }
DEFINE_GUID(CLSID_EZrgb24PropertyPage,
0x8b498502, 0x1218, 0x11cf, 0xad, 0xc4, 0x0, 0xa0, 0xd1, 0x0, 0x4, 0x1b);


IEZ.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// A custom interface to allow the user to perform image special effects

#ifndef __IEZ__
#define __IEZ__

#ifdef __cplusplus
extern "C" {
#endif

    // { fd5010a3-8ebe-11ce-8183-00aa00577da1 }
    DEFINE_GUID(IID_IIPEffect,
    0xfd5010a3, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);

    DECLARE_INTERFACE_(IIPEffect, IUnknown)
    {
        STDMETHOD(get_IPEffect) (THIS_
                    int *effectNum,        // The current effect
                    REFTIME *StartTime,     // Start time of effect
                    REFTIME *Length         // length of effect
                 ) PURE;

        STDMETHOD(put_IPEffect) (THIS_
                    int effectNum,        // Change to this effect
                    REFTIME StartTime,      // Start time of effect
                    REFTIME Length          // Length of effect
                 ) PURE;
    };

#ifdef __cplusplus
}
#endif

#endif // __IEZ__


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by EZprop.rc
//
#define IDD_EZrgb24PROP                 101
#define IDC_EZrgb24                     1000
#define IDB_DEFAULT                     1001
#define IDC_EMBOSS                      1002
#define IDC_GREY                        1003
#define IDC_BLUR                        1004
#define IDC_POSTERIZE                   1005
#define IDC_XOR                         1006
#define IDC_DARKEN                      1007
#define IDC_BLUE                        1008
#define IDC_GREEN                       1009
#define IDC_RED                         1010
#define IDC_NONE                        1011
#define IDC_START                       1012
#define IDC_LENGTH                      1014
#define IDS_STATIC                      -1
#define IDS_TITLE                       1015

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1012
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


GARGLE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Gargle Filter - A Transform filter that turns humans into daleks!!!
//
//     What This Sample Illustrates
//
// An in-place transform filter.
// Saving and restoring properties in a saved graph.
// Handling decompressed sound.
// Use of debug macros ASSERT, DbgBreak, DbgLog, DbgBreakPoint
//
//
//     Summary
//
// A simple, in-place transform, audio effect which modifies the data
// in the samples that pass through it.  The effect is an amplitude
// modulation with a synthesised secondary wave function.
// The secondary wave can be a triangular or square wave.  A properties
// sheet allows the shape and frequency of the secondary wave to be chosen.
//
// At low modulation frequencies it sounds like a tremolo, at higher
// modulation frequencies it sounds like a distortion, adding extra
// frequencies above and below the original unmodulated sound.
//
//
//     Demonstration instructions
//
// First build the sample and get it registered (see gargle.reg)
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop into
// the tool any WAV file or any MPEG, AVI or MOV file which has a sound track.
// A graph will be built to render the file.  From the Graph menu select
// Insert filters and insert Gargle.  (If it is not on the list then you
// failed to register it properly.  If it fails to load then you either
// didn't build it properly or the registration does not correctly point to
// the path where gargle.ax is now found.)
// In the graph displayed, find the audio renderer.  Disconnect it (click on
// the incoming arrow and then press the Delete key).
// Create a connection between the same two filters via gargle.  (Drag from
// previos arrow tail pin to gargle input pin.  Drag from Gargle output pin to
// audio renderer input pin).
// Right click on the Gargle box to bring up the property page.  Click on
// the GraphEdt Play button and experiment with different properties.
//
//
//     Implementation
//
// This filter has one input pin, one output pin and
// does its transform in-place (i.e. without copying the data)
// on the push thread (i.e. it is called with a buffer, which it
// transforms and gives to the next filter downstream.  It is
// then blocked until that filter returns.  It then returns
// to its own caller.)
//
// The filter modulates sound by multiplying the value of each sample
// by the value of triangular or square waveform.  Depending on
// the frequency of the modulation it will sound like recurent fading,
// like a tremolo or like a sort of distortion.
//
// It has a properties page which allows control of two properties
// (the frequency and shape of the modulating waveform).  It exports a
// private interface (IGargle) which the properties page uses to get or
// set the frequency and/or shape.
//
// As far as possible the properties page code has been separated and
// is implemented in the files GargProp.* whereas the basic filter
// code is in this file and its header gargle.h.
//
// The word "sample" is used in two senses.  It means either a sound sample
// which is 8 or 16 bits of data representing the instantaneous sound pressure
// or else it means an ActiveMovie sample which is the unit of data that is
// passed between filters, i.e. a buffer full of sound samples.
//
//
//    Known problems ("features NOT illustrated by this sample"):
//
// 1. The properties sheet does NOT give real-time control.
//    Moving the knob immediately changes the modulation being applied
//    to the samples being processed by the filter, but there can then
//    be a long latency before those data are rendered.  (To avoid audio
//    break-up the audio renderer typically keeps a sizeable queue of
//    samples ready to be played).
// 2. Because it operates by alterating the data in the samples
//    it cannot work on a read-only stream.  As defined here
//    it refuses such a connection (e.g. it cannot connect directly
//    to the output of the "infinite tee" filter).
//    In such a case the intelligent graph-building code will often
//    make the connection by inserting an "ACM wrapper" filter.
//    This works but is rather inefficient.
//
//
//      Files
//
// gargle.cpp    This file - main implementation
// gargprop.h    Class definition of properties class (used in gargle.cpp)
// gargprop.cpp  Implementation of the properties sheet
// gargprop.rc   Defines the property page dialog
// resource.h    constants shared between gargprop.rc and gargprop.cpp
// igargle.h     Interface between gargle and gargprop
// garguids.h    The public class ids (only referred to in gargle.cpp)
// gargle.reg    What goes in the registry
// gargle.def    Imports and exports
// makefile      How to build it
//
//
//     Base classes used (refer to docs for diagram of what they inherit):
//
// CTransInPlaceFilter
// CPersistStream
// CBasePropertyPage

//=============================================================================
//=============================================================================

#include <streams.h>              // ActiveMovie, includes windows

// Eliminate two expected level 4 warnings from the Microsoft compiler.
// The class does not have an assignment or copy operator, and so cannot
// be passed by value.  This is normal.  This file compiles clean at the
// highest (most picky) warning level (-W4).
#pragma warning(disable: 4511 4512)

#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif
#include "garguids.h"             // Our own uuids
#include "igargle.h"              // IGargle (properties)
#include "gargprop.h"             // CGargleProperties

//------------------------------------------------------------------------
// CGargle - the gargle filter class
//------------------------------------------------------------------------

class CGargle
    // Inherited classes
    : public CTransInPlaceFilter       // Main ActiveMovie interfaces

    , public ISpecifyPropertyPages     // Needed for properties only

    , public IGargle                   // Needed for properties only.
                                       // Without this the PURE virtual
                                       // functions in IGargle will not
                                       // be implemented.  (If they ever
                                       // got called the entire app would
                                       // silently ExitProcess!!).

    , public CPersistStream            // Implements IPersistStream
                                       // to alow saving of properties
                                       // in a saved graph.
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    DECLARE_IUNKNOWN;

    //
    // --- CTransInPlaceFilter Overrides --
    //

    HRESULT CheckInputType(const CMediaType *mtIn);

    // Basic COM - used here to reveal our property interface.
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // CPersistStream overrides
    HRESULT WriteToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);
    int SizeMax();
    STDMETHODIMP GetClassID(CLSID *pClsid);

    // --- ISpecifyPropertyPages ---

    // return our property pages
    STDMETHODIMP GetPages(CAUUID * pPages);

    // IGargle - private interface to put/set properties
    STDMETHODIMP get_GargleRate(int *GargleRate);
    STDMETHODIMP put_GargleRate(int GargleRate);
    STDMETHODIMP put_DefaultGargleRate(void);
    STDMETHODIMP put_GargleShape(int iGargleShape);
    STDMETHODIMP get_GargleShape(int *GargleShape);

private:

    // Constructor
    CGargle(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);

    // Overrides the PURE virtual Transform of CTransInPlaceFilter base class
    // This is where the "real work" is done.
    HRESULT Transform(IMediaSample *pSample);

    // This is where the real work is really done (called from Transform)
    void MessItAbout(PBYTE pb, int cb);

    // Overrides a CTransformInPlace function.  Called as part of connecting.
    virtual HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);

    // If there are multiple instances of this filter active, it's
    // useful for debug messages etc. to know which one this is.
    // This variable has no other purpose.
    static m_nInstanceCount;                   // total instances
    int m_nThisInstance;

    const int           m_DefaultGargleRate;   // The default rate (Hz)
    int                 m_GargleRate;          // The current rate (Hz)
    int                 m_Shape;               // 0==triangle, 1==square
    int                 m_SamplesPerSec;       // Current sample format
    int                 m_BytesPerSample;      // Current sample format
    int                 m_Channels;            // Current sample format
    int                 m_Phase;               // See MessItAbout in gargle.cpp
    CCritSec            m_GargleLock;          // To serialise access.

}; // class CGargle

//------------------------------------------------------------------------
// Implementation
//------------------------------------------------------------------------

// Put out the name of a function and instance on the debugger.
// Invoke this at the start of functions to allow a trace.
#define DbgFunc(a) DbgLog(( LOG_TRACE                        \
                          , 2                                \
                          , TEXT("CGargle(Instance %d)::%s") \
                          , m_nThisInstance                  \
                          , TEXT(a)                          \
                         ));

// Self-registration data structures

const AMOVIESETUP_MEDIATYPE
sudPinTypes =   { &MEDIATYPE_Audio        // clsMajorType
                , &MEDIASUBTYPE_NULL };   // clsMinorType

const AMOVIESETUP_PIN
psudPins[] = { { L"Input"            // strName
               , FALSE               // bRendered
               , FALSE               // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Output"           // strConnectsToPin
               , 1                   // nTypes
               , &sudPinTypes        // lpTypes
               }
             , { L"Output"           // strName
               , FALSE               // bRendered
               , TRUE                // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Input"            // strConnectsToPin
               , 1                   // nTypes
               , &sudPinTypes        // lpTypes
               }
             };

const AMOVIESETUP_FILTER
sudGargle = { &CLSID_Gargle                   // class id
            , L"Gargle"                       // strName
            , MERIT_DO_NOT_USE                // dwMerit
            , 2                               // nPins
            , psudPins                        // lpPin
            };

// Needed for the CreateInstance mechanism
CFactoryTemplate g_Templates[2]= { { L"Gargle"
                                   , &CLSID_Gargle
                                   , CGargle::CreateInstance
                                   , NULL
                                   , &sudGargle
                                   }
                                 , { L"Gargle Property Page"
                                   , &CLSID_GargProp
                                   , CGargleProperties::CreateInstance
                                   }
                                 };

int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

// initialise the static instance count.
int CGargle::m_nInstanceCount = 0;

//
// CGargle::Constructor
//
// Construct a CGargle object.
//
CGargle::CGargle(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
    : CTransInPlaceFilter (tszName, punk, CLSID_Gargle, phr)
    , CPersistStream(punk, phr)
    , m_DefaultGargleRate (DefaultGargleRate)
    , m_GargleRate        (DefaultGargleRate)
    , m_SamplesPerSec     (0)
    , m_BytesPerSample    (0)
    , m_Channels          (0)
    , m_Phase             (0)
    , m_Shape             (0)
{
    m_nThisInstance = ++m_nInstanceCount; // Useful for debug, no other purpose

    DbgFunc("CGargle");

} // (CGargle constructor)

//
// CreateInstance
//
// Override CClassFactory method.
// Provide the way for COM to create a CGargle object.
//
CUnknown * WINAPI CGargle::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{

    CGargle *pNewObject = new CGargle(NAME("Gargle Filter"), punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return pNewObject;

} // CreateInstance

//
// NonDelegatingQueryInterface
//
// Override CUnknown method.
// Reveal our persistent stream, property pages and IGargle interfaces.
// Anyone can call our private interface so long as they know the secret UUID.
//
STDMETHODIMP CGargle::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);

    if (riid == IID_IGargle) {
        return GetInterface((IGargle *) this, ppv);
    } else if (riid == IID_ISpecifyPropertyPages) {

        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else if (riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *) this, ppv);
    } else {
        // Pass the buck
        return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv);
    }

} // NonDelegatingQueryInterface

// GetClassID
//
// Override CBaseMediaFilter method for interface IPersist
// Part of the persistent file support.  We must supply our class id
// which can be saved in a graph file and used on loading a graph with
// a gargle in it to instantiate this filter via CoCreateInstance.
//
STDMETHODIMP CGargle::GetClassID(CLSID *pClsid)
{
    if (pClsid==NULL) {
        return E_POINTER;
    }
    *pClsid = CLSID_Gargle;
    return NOERROR;

} // GetClassID

//
// SizeMax
//
// Override CPersistStream method.
// State the maximum number of bytes we would ever write in a file
// to save our properties.
//
int CGargle::SizeMax()
{
    // When an int is expanded as characters it takes at most 12 characters
    // including a trailing delimiter.
    // Wide chars doubles this and we want two ints.
    //
    return 48;

}  // SizeMax

//
// WriteToStream
//
// Override CPersistStream method.
// Write our properties to the stream.
//
HRESULT CGargle::WriteToStream(IStream *pStream)
{
    HRESULT hr;
    hr = WriteInt(pStream, m_GargleRate);
    if (FAILED(hr)) return hr;
    hr = WriteInt(pStream, m_Shape);
    if (FAILED(hr)) return hr;
    return NOERROR;
} // WriteToStream

//
// ReadFromStream
//
// Override CPersistStream method.
// Read our properties from the stream.
//
HRESULT CGargle::ReadFromStream(IStream *pStream)
{
    HRESULT hr;
    m_GargleRate = ReadInt(pStream, hr);
    if (FAILED(hr)) return hr;
    m_Shape = ReadInt(pStream, hr);
    if (FAILED(hr)) return hr;
    return NOERROR;
} // ReadFromStream

//
// MessItabout
//
// Mess the sound about by modulating it with a waveform.
// We know the frequency of the modulation (from the slider setting
// which we were told through our internal interface, IGargle, and
// which we stored in m_GargleRate).  At the end of the call we
// record what part of the waveform we finished at in m_Phase and
// we resume at that point next time.
// Uses and updates m_Phase
// Uses m_SamplesPerSec, m_Channels, m_GargleRate, m_Shape
//
void CGargle::MessItAbout(PBYTE pb, int cb)
{
    CAutoLock foo(&m_GargleLock);

    // We know how many samples per sec and how
    // many channels so we can calculate the modulation period in samples.
    //
    int Period = (m_SamplesPerSec * m_Channels) / m_GargleRate;

    while (cb>0) {
       --cb;

       // If m_Shape is 0 (triangle) then we multiply by a triangular waveform
       // that runs 0..Period/2..0..Period/2..0... else by a square one that
       // is either 0 or Period/2 (same maximum as the triangle) or zero.
       //
       {
           // m_Phase is the number of samples from the start of the period.
           // We keep this running from one call to the next,
           // but if the period changes so as to make this more
           // than Period then we reset to 0 with a bang.  This may cause
           // an audible click or pop (but, hey! it's only a sample!)
           //
           ++m_Phase;
           if (m_Phase>Period) m_Phase = 0;

           int M = m_Phase;      // m is what we modulate with

           if (m_Shape ==0 ) {   // Triangle
               if (M>Period/2) M = Period-M;  // handle downslope
           } else {             // Square wave
               if (M<=Period/2) M = Period/2; else M = 0;
           }

           if (m_BytesPerSample==1) {

               // 8 bit sound uses 0..255 representing -128..127
               // Any overflow, even by 1, would sound very bad.
               // so we clip paranoically after modulating.
               // I think it should never clip by more than 1
               //
               int i = *pb-128;               // sound sample, zero based
               i = (i*M*2)/Period;            // modulate
               if (i>127) i = 127;            // clip
               if (i<-128) i = -128;

               *pb = (unsigned char)(i+128);  // reset zero offset to 128

           } else if (m_BytesPerSample==2) {

               // 16 bit sound uses 16 bits properly (0 means 0)
               // We still clip paranoically
               //
               short int *psi = (short int *)pb;
               int i = *psi;                  // in a register, we might hope
               i = (i*M*2)/Period;            // modulate
               if (i>32767) i = 32767;        // clip
               if (i<-32768) i = -32768;
               *psi = (short)i;
               ++pb;  // nudge it on another 8 bits here to get a 16 bit step
               --cb;  // and nudge the count too.

           } else {

               DbgBreak("Too many bytes per sample");
               // just leave it alone!

           }
       }
       ++pb;   // move on 8 bits to next sound sample
    }
} // MessItAbout

//
// Transform
//
// Override CTransInPlaceFilter method.
// Convert the input ActiveMovie sample into the output ActiveMovie sample.
//
HRESULT CGargle::Transform(IMediaSample *pSample)
{

    DbgFunc("Transform");

    // Get the details of the data (address, length)
    //
    BYTE *pSampleBuffer;
    int iSize = pSample->GetActualDataLength();
    pSample->GetPointer(&pSampleBuffer);

    // Actually transform the data
    //
    MessItAbout(pSampleBuffer, iSize );

    return NOERROR;

} // Transform

//
// CheckInputType
//
// Override CTransformFilter method.
// Part of the Connect process.
// Ensure that we do not get connected to formats that we can't handle.
// We only work for wave audio, 8 or 16 bit, uncompressed.
//
HRESULT CGargle::CheckInputType(const CMediaType *pmt)
{

    DisplayType("CheckInputType", pmt);

    WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmt->pbFormat;

    // Reject non-Audio types.
    //
    if (pmt->majortype != MEDIATYPE_Audio) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Reject invalid format blocks
    //
    if (pmt->formattype != FORMAT_WaveFormatEx)
        return VFW_E_TYPE_NOT_ACCEPTED;

    // Reject compressed audio
    //
    if (pwfx->wFormatTag != WAVE_FORMAT_PCM) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Accept only 8 or 16 bit
    //
    if (pwfx->wBitsPerSample!=8 && pwfx->wBitsPerSample!=16) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    return NOERROR;

} // CheckInputType

//
// SetMediaType
//
// Override CTransformFilter method.
// Called when a connection attempt has succeeded. If the output pin
// is being connected and the input pin's media type does not agree then we
// reconnect the input (thus allowing its media type to change,) and vice versa.
//
HRESULT CGargle::SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt)
{

    DbgFunc("SetMediaType");

    // Record what we need for doing the actual transform

    WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmt->Format();
    m_Channels =  pwfx->nChannels;
    m_SamplesPerSec = pwfx->nSamplesPerSec;
    // Ignored: pwfx->nAvgBytesPerSec;
    // Ignored: pwfx->nBlockAlign;
    m_BytesPerSample = pwfx->wBitsPerSample/8;

    // Call the base class to do its thing
    CTransInPlaceFilter::SetMediaType(direction, pmt);

    // Reconnect where necessary.
    if( m_pInput->IsConnected() && m_pOutput->IsConnected() ){
        FILTER_INFO fInfo;

        QueryFilterInfo( &fInfo );

        if (direction == PINDIR_OUTPUT && *pmt != m_pInput->CurrentMediaType() )
            fInfo.pGraph->Reconnect( m_pInput );

        QueryFilterInfoReleaseGraph( fInfo );

        ASSERT(!(direction == PINDIR_INPUT && *pmt != m_pOutput->CurrentMediaType()));
    }

    return NOERROR;

} // SetMediaType

// ==============Implementation of the private IGargle interface ==========
// ==================== needed to support the property page ===============

//
// get_GargleRate
//
// Set *GargleRate to our current rate (Hz)
//
STDMETHODIMP CGargle::get_GargleRate(int *GargleRate)
{

    CAutoLock foo(&m_GargleLock);

    *GargleRate = m_GargleRate;

    DbgLog((LOG_TRACE, 1, TEXT("get_GargleRate: %d"), *GargleRate));

    return NOERROR;

} // get_GargleRate

//
// put_GargleRate
//
// Set the current rate from GargleRate.
//
STDMETHODIMP CGargle::put_GargleRate(int GargleRate)
{

    CAutoLock foo(&m_GargleLock);

    m_GargleRate = GargleRate;
    CPersistStream::SetDirty(TRUE);             // Need to scribble

    DbgLog((LOG_TRACE, 1, TEXT("put_GargleRate: %x"), m_GargleRate));

    return NOERROR;

} // put_GargleRate

//
// put_DefaultGargleRate
//
// Set the current gargle rate to the default
//
STDMETHODIMP CGargle::put_DefaultGargleRate(void)
{

    CAutoLock foo(&m_GargleLock);

    DbgLog((LOG_TRACE, 1, TEXT("put_DefaultGargleRate")));

    m_GargleRate = m_DefaultGargleRate;
    CPersistStream::SetDirty(TRUE);                     // Need to scribble

    return NOERROR;

} // put_DefaultGargleRate

//
// put_GargleShape
//
// Alter the waveform between triangle and square
//
STDMETHODIMP CGargle::put_GargleShape(int iGargleShape)
{
    if (iGargleShape<0 || iGargleShape>1)
        return E_INVALIDARG;
    m_Shape = iGargleShape;
    CPersistStream::SetDirty(TRUE);                     // Need to scribble
    return NOERROR;
} // put_GargleShape

//
// get_GargleShape
//
// Return 0 if the current shape is triangle, 1 if it's square
//
STDMETHODIMP CGargle::get_GargleShape(int *GargleShape)
{

    *GargleShape = m_Shape;
    return NOERROR;

} // get_GargleShape

// ==============Implementation of the IPropertypages Interface ===========

//
// GetPages
//
STDMETHODIMP CGargle::GetPages(CAUUID * pPages)
{

    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_GargProp;

    return NOERROR;

} // GetPages

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
\**************************************************************************/

STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}

#pragma warning(disable: 4514) // "unreferenced inline function has been removed"


GARGPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// GargProp.cpp
//

#include <streams.h>

// Eliminate two expected level 4 warnings from the Microsoft compiler.
// The class does not have an assignment or copy operator, and so cannot
// be passed by value.  This is normal.  This file compiles clean at the
// highest (most picky) warning level (-W4).
#pragma warning(disable: 4511 4512)

#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include <math.h>

#include "resource.h"
#include "igargle.h"
#include "gargprop.h"

//
// CreateInstance
//
// Override CClassFactory method.
// Set lpUnk to point to an IUnknown interface on a new CGargleProperties object
//
CUnknown * WINAPI CGargleProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{

    CUnknown *punk = new CGargleProperties(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return punk;
} // CreateInstance

//
// CGargleProperties constructor
//
// initialise a CGargleProperties object.
//
CGargleProperties::CGargleProperties(LPUNKNOWN lpunk, HRESULT *phr)
    : CBasePropertyPage( NAME("Gargle Property Page")
                       , lpunk, IDD_GARGPROP, IDS_NAME)
    , m_pGargle(NULL)
{

} // (constructor)

//
// SetButtonText
//
// Utility function to set the text of the button to reflect the curent
// waveform being used
//
void SetButtonText(HWND hwnd, int iShape)
{
    if (iShape==0) {
        SetDlgItemText(hwnd, IDB_SQUARE_TRIANGLE, "Triangle wave->square");
    } else {
        SetDlgItemText(hwnd, IDB_SQUARE_TRIANGLE, "Square wave->triangle");
    }

} // SetButtonText

//
// ConvertToPosition
//
// Convert a frequency f which is the gargle rate to a slider position.
// f runs from 1 to 1000.
// p runs from 0 to 300.
// f = 10**(p/100)
// p = 100*log10(f)
//
int ConvertToPosition(int f)
{
    // protect against stupidity - should not occur
    if (f<1) f = 1;
    if (f>1000) f = 1000;

    double x = f;
    x = 100.0*log10(x);

    // protect against rounding at the ends
    int p = (int)x;
    if (p<0) p = 0;
    if (p>300) p = 300;

    return p;

} // ConvertToPosition

//
// ConvertToFrequency
//
// Convert a slider position p to a frequency (gargle rate).
// f runs from 1 to 1000.
// p runs from 0 to 300.
// f = 10**(p/100)
// p = 100*log10(f)
//
int ConvertToFrequency(int p)
{
    // protect against stupidity - should not occur
    if (p<0) p = 0;
    if (p>300) p = 300;

    double x = p;

    x = pow(10.0, x/100.0);

    // protect against rounding at the ends
    int f = (int)x;
    if (f<1) f = 1;
    if (f>1000) f = 1000;

    return f;

} // ConvertToFrequency

//
// OnReceiveMessage
//
// Override CBasePropertyPage method.
// Handle windows messages for the dialog of the property sheet.
//
BOOL CGargleProperties::OnReceiveMessage
                            (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (uMsg) {

        case WM_INITDIALOG:

            m_hwndSlider = CreateSlider(hwnd);
            SetButtonText(hwnd, m_iGargleShape);
            ASSERT(m_hwndSlider);
            return TRUE;                // I don't call setfocus...

        case WM_VSCROLL:

            ASSERT(m_hwndSlider);
            OnSliderNotification(wParam);
            return TRUE;

        case WM_COMMAND:
            if (LOWORD(wParam) == IDB_SQUARE_TRIANGLE) {

                // Cycle through shapes.  See note below.
                //
                int iShape;
                m_pGargle->get_GargleShape(&iShape);  // find what we now have
                iShape = 1-iShape;                            // change shapes
                m_pGargle->put_GargleShape(iShape);             // put it back
                SetButtonText(hwnd, iShape);              // reflect in dialog

            } else if (LOWORD(wParam) == IDB_DEFAULT) {

                // Restore the default settings.

                // Set the filter.
                //
                m_pGargle->put_DefaultGargleRate();
                m_pGargle->put_GargleShape(0);

                // Set the slider position on the screen to match.
                //
                int iPos;
                m_pGargle->get_GargleRate(&iPos);
                iPos = ConvertToPosition(iPos);
                SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, iPos);

                // Set the button text to match.
                //
                int iShape;
                m_pGargle->get_GargleShape(&iShape);  // find out what we now have
                SetButtonText(hwnd, iShape);

            } else {

                // Should not occur - debug!
                //
                char Buff[100];
                wsprintf(Buff, "wParam=%08x", wParam);

                #ifdef DEBUG
                    DbgBreakPoint( Buff, TEXT(__FILE__), __LINE__ );
                #else
                    ASSERT(!"Should not occur - debug!");
                #endif

            }

            return TRUE;

        case WM_DESTROY:

            DestroyWindow(m_hwndSlider);
            return TRUE;

        default:

            return FALSE;

    } // switch

} // OnReceiveMessage

//
// OnConnect
//
// Override CBasePropertyPage method.
// Get the interface to the filter.
// Set the member variables m_iGargleRate and m_iGargleShape.
//
HRESULT CGargleProperties::OnConnect(IUnknown * punk)
{
    // Get IGargle interface
    //
    if (punk == NULL) {
        DbgBreak("You can't call me with a NULL pointer!!");
        return(E_POINTER);
    }

    ASSERT(m_pGargle == NULL);

    HRESULT hr = punk->QueryInterface(IID_IGargle, (void **) &m_pGargle);
    if (FAILED(hr)) {
        DbgBreak("Can't get IGargle interface.");
        return E_NOINTERFACE;
    }

    ASSERT(m_pGargle);

    m_pGargle->get_GargleRate(&m_iGargleRate);
    m_pGargle->get_GargleShape(&m_iGargleShape);
    return NOERROR;

} // OnConnect

//
// OnDisconnect
//
// Override CBasePropertyPage method.
// Release the private interface.
//
HRESULT CGargleProperties::OnDisconnect()
{
    //
    // Release the interface
    //
    if (m_pGargle == NULL) {
        return( E_UNEXPECTED);
    }

    m_pGargle->Release();
    m_pGargle = NULL;

    return(NOERROR);

} // OnDisconnect

//
// OnDeactivate
//
// Destroy the dialog.
//
HRESULT CGargleProperties::OnDeactivate(void)
{

    //
    // Remember the Gargle Rate and shape for the next Activate() call
    //
    m_pGargle->get_GargleRate(&m_iGargleRate);
    m_pGargle->get_GargleShape(&m_iGargleShape);

    return NOERROR;

} // OnDeactivate

//
// CreateSlider
//
// Create the slider (common control) to allow the user to
// adjust the gargling rate.
//
HWND CGargleProperties::CreateSlider(HWND hwndParent)
{

    // Find how to convert dialog units to screen units
    //
    LONG XUnit = GetDialogBaseUnits();
    LONG YUnit = XUnit>>16;
    XUnit = XUnit & 0x0000ffff;

    // Create the slider child window at a position which fits the dialog
    //
    HWND hwndSlider = CreateWindow( TRACKBAR_CLASS
                                  , TEXT("")
                                  , WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_BOTH
                                  , 15*XUnit          // x
                                  , 0                 // y
                                  , 5*XUnit           // width
                                  , 5*YUnit           // Height
                                  , hwndParent
                                  , NULL              // menu
                                  , g_hInst
                                  , NULL              // CLIENTCREATESTRUCT
                                  );
    if (hwndSlider == NULL) {
        DWORD dwErr = GetLastError();
        DbgLog((LOG_ERROR, 1
               , TEXT("Could not create window.  error code: 0x%x"), dwErr));
        return NULL;
    }

    // Set the range to 0..300 which is converted into Hz as 10**(p/100)
    // where p is the slider position.  SeeConvertToFrequency() above.
    //
    SendMessage(hwndSlider, TBM_SETRANGE, TRUE, MAKELONG(0, 300) );

    // Set a tick at the default of 10Hz which is 100 on the log scale.
    // Put another one at 100Hz which corresponds to 200 on the log scale.
    //
    SendMessage(hwndSlider, TBM_SETTIC, 0, 100L);
    SendMessage(hwndSlider, TBM_SETTIC, 0, 200L);

    // Set the slider position according to the value we obtain from
    // initialisation or from the last Deactivate() call.
    //
    int iPos = ConvertToPosition(m_iGargleRate);
    SendMessage(hwndSlider, TBM_SETPOS, TRUE, iPos);

    return hwndSlider;

}  // CreateSlider

//
// OnSliderNotification
//
// Handle the notification meesages from the slider control
//
void CGargleProperties::OnSliderNotification(WPARAM wParam)
{
    int iPos;
    switch (wParam) {
        case TB_BOTTOM:

            iPos = ConvertToPosition(MinGargleRate);
            SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, (LPARAM) iPos);
            break;

        case TB_TOP:

            iPos = ConvertToPosition(MaxGargleRate);
            SendMessage(m_hwndSlider, TBM_SETPOS, TRUE, (LPARAM) iPos);
            break;

        case TB_PAGEDOWN:
        case TB_PAGEUP:

            break;

        case TB_THUMBPOSITION:
        case TB_ENDTRACK:

            {
                int iRate = (int) SendMessage(m_hwndSlider, TBM_GETPOS, 0, 0L);
                iRate = ConvertToFrequency(iRate);
                m_pGargle->put_GargleRate(iRate);
            }
            break;

        case TB_THUMBTRACK: // default handling of these messages is ok.
        case TB_LINEDOWN:
        case TB_LINEUP:

            break;
    }

} // OnSliderNotification

#pragma warning(disable: 4514) // "unreferenced inline function has been removed"


GARGPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// GargProp.h
//
// This file is entirely concerned with the implementation of the
// properties page.  It uses the property page base class to minimise
// the implementation effort.

#ifndef __GARGPROP__
#define __GARGPROP__

#ifdef __cplusplus
extern "C" {
#endif

const int MaxGargleRate = 1000;    // 1000Hz max rate
const int MinGargleRate = 1;       // 1Hz min rate
const int DefaultGargleRate = 10;  // 10 Hz default

class CGargleProperties : public CBasePropertyPage
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

    // Overrides from CBasePropertyPage
    HRESULT OnConnect(IUnknown * punk);
    HRESULT OnDisconnect(void);

    HRESULT OnDeactivate(void);

    CGargleProperties(LPUNKNOWN lpunk, HRESULT *phr);

private:

    BOOL OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    HWND        CreateSlider(HWND hwndParent);
    void        OnSliderNotification(WPARAM wParam);

    HWND        m_hwndSlider;   // handle of slider

    IGargle   *m_pGargle;       // pointer to the IGargle interface of the
                                // gargle filter.  Set up in OnConnect.

    int        m_iGargleRate;   // Remember gargle rate between
                                // Deactivate / Activate calls.
    int        m_iGargleShape;  // 0 = triangle (default), 1 = square wave.

};  // class CGargleProperties

#ifdef __cplusplus
}
#endif

#endif // __GARGPROP__


GARGUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// garguids.h
//

// The public CLSID's used by the gargle filter.
// These same numbers occur in gargle.reg which allows them to be added to the
// registry to give access to these objects by other software components
// such as the ActiveMovie Graph Builder, GraphEdt and so on.

#ifndef __GARGUIDS__
#define __GARGUIDS__

#ifdef __cplusplus
extern "C" {
#endif

//
// Gargle filter object
// {d616f350-d622-11ce-aac5-0020af0b99a3}
DEFINE_GUID(CLSID_Gargle,
0xd616f350, 0xd622, 0x11ce, 0xaa, 0xc5, 0x00, 0x20, 0xaf, 0x0b, 0x99, 0xa3);

//
// Gargle filter property page
// {d616f351-d622-11ce-aac5-0020af0b99a3}
DEFINE_GUID(CLSID_GargProp,
0xd616f351, 0xd622, 0x11ce, 0xaa, 0xc5, 0x00, 0x20, 0xaf, 0x0b, 0x99, 0xa3);

//
//  Note: IGargle's uuid is defined with the interface, see igargle.h
//  IGargle is a private interface created by the filter.
//  The filter object and the property page defined here are public interfaces
//  that can be called by an application or another filter.

#ifdef __cplusplus
}
#endif

#endif // __GARGUIDS__


IGARGLE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// igargle.h
//

// A custom interface to allow the user to adjust the modulation rate.
// It defines the interface between the user interface component (the
// property sheet) and the filter itself.
// This interface is exported by the code in gargle.cpp and is used
// by the code in gargprop.cpp.

#ifndef __IGARGLE__
#define __IGARGLE__

#ifdef __cplusplus
extern "C" {
#endif

//
// IGargle's GUID
//
// {d616f352-d622-11ce-aac5-0020af0b99a3}
DEFINE_GUID(IID_IGargle,
0xd616f352, 0xd622, 0x11ce, 0xaa, 0xc5, 0x00, 0x20, 0xaf, 0x0b, 0x99, 0xa3);

//
// IGargle
//
DECLARE_INTERFACE_(IGargle, IUnknown) {

    // Compare these with the functions in class CGargle in gargle.h
    STDMETHOD(get_GargleRate)
        ( THIS_
          int *GargleRate  // [out] the current gargle level
        ) PURE;

    STDMETHOD(put_GargleRate)
        ( THIS_
          int GargleRate   // [in] Change to the level of gargle
        ) PURE;

    STDMETHOD(put_DefaultGargleRate)
        ( THIS
        ) PURE;

    STDMETHOD(put_GargleShape)
        ( THIS_
          int GargleShape // [in] 0=triangle wave, 1 = square wave
        ) PURE;

    STDMETHOD(get_GargleShape)
        ( THIS_
          int *GargleShape  // [out] the current shape, 0=triangle, 1=square
        ) PURE;

};

#ifdef __cplusplus
}
#endif

#endif // __IGARGLE__


RESOURCE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Definitions of symbolic constants used in GargProp.rc and gargprop.cpp.
//

#ifndef __GARGLERESOURCE__
#define __GARGLERESOURCE__

#ifdef __cplusplus
extern "C" {
#endif

#define IDD_GARGPROP                    101
#define IDS_STATIC                      -1
#define IDB_DEFAULT                     1001   // pushbutton id
#define IDB_SQUARE_TRIANGLE             1002   // pushbutton id
#define IDS_NAME                        102

#ifdef __cplusplus
}
#endif

#endif // __GARGLERESOURCE__


README.TXT

The inftee filter is now installed and registered as part of the DShow
reditributable runtime. The code for the filter continues to be part
of the SDK for bacwards compatibility.


INFTEE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __INFTEE__
#define __INFTEE__

// define a GUID for infinite tee filters

// { 022B8142-0946-11cf-BCB1-444553540000 }
DEFINE_GUID(CLSID_Tee,
0x22b8142, 0x946, 0x11cf, 0xbc, 0xb1, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0);

class CTee;
class CTeeOutputPin;

// class for the Tee filter's Input pin

class CTeeInputPin : public CBaseInputPin
{
    friend class CTeeOutputPin;
    CTee *m_pTee;                  // Main filter object
    BOOL m_bInsideCheckMediaType;  // Re-entrancy control

public:

    // Constructor and destructor
    CTeeInputPin(TCHAR *pObjName,
                 CTee *pTee,
                 HRESULT *phr,
                 LPCWSTR pPinName);

#ifdef DEBUG
    ~CTeeInputPin();
#endif

    // Used to check the input pin connection
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT SetMediaType(const CMediaType *pmt);
    HRESULT BreakConnect();

    // Reconnect outputs if necessary at end of completion
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    STDMETHODIMP NotifyAllocator(IMemAllocator *pAllocator, BOOL bReadOnly);

    // Pass through calls downstream
    STDMETHODIMP EndOfStream();
    STDMETHODIMP BeginFlush();
    STDMETHODIMP EndFlush();
    STDMETHODIMP NewSegment(
                    REFERENCE_TIME tStart,
                    REFERENCE_TIME tStop,
                    double dRate);

    // Handles the next block of data from the stream
    STDMETHODIMP Receive(IMediaSample *pSample);

};

// Class for the Tee filter's Output pins.

class CTeeOutputPin : public CBaseOutputPin
{
    friend class CTeeInputPin;
    friend class CTee;

    CTee *m_pTee;                  // Main filter object pointer
    IUnknown    *m_pPosition;      // Pass seek calls upstream
    BOOL m_bHoldsSeek;             // Is this the one seekable stream
    COutputQueue *m_pOutputQueue;  // Streams data to the peer pin
    BOOL m_bInsideCheckMediaType;  // Re-entrancy control
    LONG m_cOurRef;                // We maintain reference counting

public:

    // Constructor and destructor

    CTeeOutputPin(TCHAR *pObjName,
                   CTee *pTee,
                   HRESULT *phr,
                   LPCWSTR pPinName,
                   INT PinNumber);

#ifdef DEBUG
    ~CTeeOutputPin();
#endif

    // Override to expose IMediaPosition
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppvoid);

    // Override since the life time of pins and filters are not the same
    STDMETHODIMP_(ULONG) NonDelegatingAddRef();
    STDMETHODIMP_(ULONG) NonDelegatingRelease();

    // Override to enumerate media types
    STDMETHODIMP EnumMediaTypes(IEnumMediaTypes **ppEnum);

    // Check that we can support an output type
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT SetMediaType(const CMediaType *pmt);

    // Negotiation to use our input pins allocator
    HRESULT DecideAllocator(IMemInputPin *pPin, IMemAllocator **ppAlloc);
    HRESULT DecideBufferSize(IMemAllocator *pMemAllocator,
                              ALLOCATOR_PROPERTIES * ppropInputRequest);

    // Used to create output queue objects
    HRESULT Active();
    HRESULT Inactive();

    // Overriden to create and destroy output pins
    HRESULT CompleteConnect(IPin *pReceivePin);

    // Overriden to pass data to the output queues
    HRESULT Deliver(IMediaSample *pMediaSample);
    HRESULT DeliverEndOfStream();
    HRESULT DeliverBeginFlush();
    HRESULT DeliverEndFlush();
    HRESULT DeliverNewSegment(
                    REFERENCE_TIME tStart,
                    REFERENCE_TIME tStop,
                    double dRate);

    // Overriden to handle quality messages
    STDMETHODIMP Notify(IBaseFilter *pSender, Quality q);
};

// Class for the Tee filter

class CTee: public CCritSec, public CBaseFilter
{
    // Let the pins access our internal state
    friend class CTeeInputPin;
    friend class CTeeOutputPin;
    typedef CGenericList <CTeeOutputPin> COutputList;

    // Declare an input pin.
    CTeeInputPin m_Input;

    INT m_NumOutputPins;            // Current output pin count
    COutputList m_OutputPinsList;   // List of the output pins
    INT m_NextOutputPinNumber;      // Increases monotonically.
    LONG m_lCanSeek;                // Seekable output pin
    IMemAllocator *m_pAllocator;    // Allocator from our input pin

public:

    CTee(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *hr);
    ~CTee();

    CBasePin *GetPin(int n);
    int GetPinCount();

    // Function needed for the class factory
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);

    // Send EndOfStream if no input connection
    STDMETHODIMP Run(REFERENCE_TIME tStart);
    STDMETHODIMP Pause();
    STDMETHODIMP Stop();

protected:

    // The following manage the list of output pins

    void InitOutputPinsList();
    CTeeOutputPin *GetPinNFromList(int n);
    CTeeOutputPin *CreateNextOutputPin(CTee *pTee);
    void DeleteOutputPin(CTeeOutputPin *pPin);
    int GetNumFreePins();
};

#endif // __INFTEE__


INFTEE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <initguid.h>
#include <inftee.h>
#include <tchar.h>
#include <stdio.h>

//
//
// What this sample illustrates
//
// A pass through filter splits a data stream into many output channels
//
// Summary
//
// This is a sample ActiveMovie pass through filter. We have a single input
// pin and can have many output pins. We start with one output pin and each
// time we connect an output pin we spawn another, although we could keep
// doing this ad infinitum we have a top level maximum of INFTEE_MAX_PINS.
// Any data samples our input pin receives will be sent down each output
// pin in turn. Each output pin has a separate thread if necessary (see
// the output queue class in the SDK) to avoid delivery blocking our thread
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Video Renderer"
// Then click on the box and hit DELETE. After that go to the Graph menu and
// select "Insert Filters", from the dialog box find and select the "Infinite
// Tee Filter" and then dismiss the dialog. Back in the graph layout find the
// output pin of the filter that was connected to the input of the video
// renderer you just deleted, right click and select "Render". You should
// see it being connected to the input pin of the filter you just inserted
//
// The infinite tee filter will have one output pin connected and will have
// spawned another, right click on this and select Render. A new renderer
// will pop up fo the stream. Do this once or twice more and then click on
// the Pause and Run on the GRAPHEDT frame and you will see the video...
//      .. many times over in different windows
//
// Files
//
// inftee.cpp           Main implementation of the infinite tee
// inftee.def           What APIs the DLL will import and export
// inftee.h             Class definition of the infinite tee
// inftee.rc            Not much, just our version information
// inftee.reg           What goes in the registry to make us work
// makefile             How to build it...
//
//
// Base classes used
//
// CBaseInputPin        Basic IMemInputPin based input pin
// CBaseOutputPin       Used for basic connection stuff
// CBaseFilter          Well we need a filter don't we
// CCritSec             Controls access to output pin list
// COutputQueue         Delivers data on a separate thread
//
//

#define INFTEE_MAX_PINS 1000

// Using this pointer in constructor
#pragma warning(disable:4355)

// Setup data

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_NULL,         // Major CLSID
    &MEDIASUBTYPE_NULL       // Minor type
};

const AMOVIESETUP_PIN psudPins[] =
{
    { L"Input",             // Pin's string name
      FALSE,                // Is it rendered
      FALSE,                // Is it an output
      FALSE,                // Allowed none
      FALSE,                // Allowed many
      &CLSID_NULL,          // Connects to filter
      L"Output",            // Connects to pin
      1,                    // Number of types
      &sudPinTypes },       // Pin information
    { L"Output",            // Pin's string name
      FALSE,                // Is it rendered
      TRUE,                 // Is it an output
      FALSE,                // Allowed none
      FALSE,                // Allowed many
      &CLSID_NULL,          // Connects to filter
      L"Input",             // Connects to pin
      1,                    // Number of types
      &sudPinTypes }        // Pin information
};

const AMOVIESETUP_FILTER sudInfTee =
{
    &CLSID_Tee,             // CLSID of filter
    L"Infinite Pin Tee",    // Filter's name
    MERIT_DO_NOT_USE,       // Filter merit
    2,                      // Number of pins
    psudPins                // Pin information
};

CFactoryTemplate g_Templates [1] = {
    { L"Infinite Pin Tee"
    , &CLSID_Tee
    , CTee::CreateInstance
    , NULL
    , &sudInfTee }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// CreateInstance
//
// Creator function for the class ID
//
CUnknown * WINAPI CTee::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    return new CTee(NAME("Infinite Tee Filter"), pUnk, phr);
}

//
// Constructor
//
CTee::CTee(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) :
    m_OutputPinsList(NAME("Tee Output Pins list")),
    m_lCanSeek(TRUE),
    m_pAllocator(NULL),
    m_NumOutputPins(0),
    m_NextOutputPinNumber(0),
    m_Input(NAME("Input Pin"), this, phr, L"Input"),
    CBaseFilter(NAME("Tee filter"), pUnk, this, CLSID_Tee)
{
    ASSERT(phr);

    // Create a single output pin at this time
    InitOutputPinsList();

    CTeeOutputPin *pOutputPin = CreateNextOutputPin(this);

    if (pOutputPin != NULL )
    {
        m_NumOutputPins++;
        m_OutputPinsList.AddTail(pOutputPin);
    }
}

//
// Destructor
//
CTee::~CTee()
{
    InitOutputPinsList();
}

//
// GetPinCount
//
int CTee::GetPinCount()
{
    return (1 + m_NumOutputPins);
}

//
// GetPin
//
CBasePin *CTee::GetPin(int n)
{
    if (n < 0)
        return NULL ;

    // Pin zero is the one and only input pin
    if (n == 0)
        return &m_Input;

    // return the output pin at position(n - 1) (zero based)
    return GetPinNFromList(n - 1);
}

//
// InitOutputPinsList
//
void CTee::InitOutputPinsList()
{
    POSITION pos = m_OutputPinsList.GetHeadPosition();
    while(pos)
    {
        CTeeOutputPin *pOutputPin = m_OutputPinsList.GetNext(pos);
        ASSERT(pOutputPin->m_pOutputQueue == NULL);
        pOutputPin->Release();
    }
    m_NumOutputPins = 0;
    m_OutputPinsList.RemoveAll();

} // InitOutputPinsList

//
// CreateNextOutputPin
//
CTeeOutputPin *CTee::CreateNextOutputPin(CTee *pTee)
{
    WCHAR szbuf[20];             // Temporary scratch buffer
    m_NextOutputPinNumber++;     // Next number to use for pin
    HRESULT hr = NOERROR;

    swprintf(szbuf, L"Output%d", m_NextOutputPinNumber);

    CTeeOutputPin *pPin = new CTeeOutputPin(NAME("Tee Output"), pTee,
    &hr, szbuf,
    m_NextOutputPinNumber);

    if (FAILED(hr) || pPin == NULL) {
        delete pPin;
        return NULL;
    }

    pPin->AddRef();
    return pPin;

} // CreateNextOutputPin

//
// DeleteOutputPin
//
void CTee::DeleteOutputPin(CTeeOutputPin *pPin)
{
    POSITION pos = m_OutputPinsList.GetHeadPosition();
    while(pos) {
        POSITION posold = pos;         // Remember this position
        CTeeOutputPin *pOutputPin = m_OutputPinsList.GetNext(pos);
        if (pOutputPin == pPin) {
            // If this pin holds the seek interface release it
            if (pPin->m_bHoldsSeek) {
                InterlockedExchange(&m_lCanSeek, FALSE);
                pPin->m_bHoldsSeek = FALSE;
                pPin->m_pPosition->Release();
            }

            m_OutputPinsList.Remove(posold);
            ASSERT(pOutputPin->m_pOutputQueue == NULL);
            delete pPin;
            m_NumOutputPins--;
    IncrementPinVersion();
            break;
        }
    }

} // DeleteOutputPin

//
// GetNumFreePins
//
int CTee::GetNumFreePins()
{
    int n = 0;
    POSITION pos = m_OutputPinsList.GetHeadPosition();
    while(pos) {
        CTeeOutputPin *pOutputPin = m_OutputPinsList.GetNext(pos);
        if (pOutputPin->m_Connected == NULL)
            n++;
    }
    return n;

} // GetNumFreePins

//
// GetPinNFromList
//
CTeeOutputPin *CTee::GetPinNFromList(int n)
{
    // Validate the position being asked for
    if (n >= m_NumOutputPins)
        return NULL;

    // Get the head of the list
    POSITION pos = m_OutputPinsList.GetHeadPosition();

    n++;       // Make the number 1 based

    CTeeOutputPin *pOutputPin;
    while(n) {
        pOutputPin = m_OutputPinsList.GetNext(pos);
        n--;
    }
    return pOutputPin;

} // GetPinNFromList

//
// Stop
//
// Overriden to handle no input connections
//
STDMETHODIMP CTee::Stop()
{
    CBaseFilter::Stop();
    m_State = State_Stopped;
    return NOERROR;
}

//
// Pause
//
// Overriden to handle no input connections
//
STDMETHODIMP CTee::Pause()
{
    CAutoLock cObjectLock(m_pLock);
    HRESULT hr = CBaseFilter::Pause();
    if (m_Input.IsConnected() == FALSE) {
        m_Input.EndOfStream();
    }
    return hr;
}

//
// Run
//
// Overriden to handle no input connections
//
STDMETHODIMP CTee::Run(REFERENCE_TIME tStart)
{
    CAutoLock cObjectLock(m_pLock);
    HRESULT hr = CBaseFilter::Run(tStart);
    if (m_Input.IsConnected() == FALSE) {
        m_Input.EndOfStream();
    }
    return hr;
}

//
// CTeeInputPin constructor
//
CTeeInputPin::CTeeInputPin(TCHAR *pName,
                           CTee *pTee,
                           HRESULT *phr,
                           LPCWSTR pPinName) :
    CBaseInputPin(pName, pTee, pTee, phr, pPinName),
    m_pTee(pTee),
    m_bInsideCheckMediaType(FALSE)
{
    ASSERT(pTee);
}

#ifdef DEBUG
//
// CTeeInputPin destructor
//
CTeeInputPin::~CTeeInputPin()
{
    DbgLog((LOG_TRACE,2,TEXT("CTeeInputPin destructor")));
    ASSERT(m_pTee->m_pAllocator == NULL);
}
#endif

#ifdef DEBUG
//
// DisplayMediaType -- (DEBUG ONLY)
//
void DisplayMediaType(TCHAR *pDescription,const CMediaType *pmt)
{

    // Dump the GUID types and a short description

    DbgLog((LOG_TRACE,2,TEXT("")));
    DbgLog((LOG_TRACE,2,TEXT("%s"),pDescription));
    DbgLog((LOG_TRACE,2,TEXT("")));
    DbgLog((LOG_TRACE,2,TEXT("Media Type Description")));
    DbgLog((LOG_TRACE,2,TEXT("Major type %s"),GuidNames[*pmt->Type()]));
    DbgLog((LOG_TRACE,2,TEXT("Subtype %s"),GuidNames[*pmt->Subtype()]));
    DbgLog((LOG_TRACE,2,TEXT("Subtype description %s"),GetSubtypeName(pmt->Subtype())));
    DbgLog((LOG_TRACE,2,TEXT("Format size %d"),pmt->cbFormat));

    // Dump the generic media types */

    DbgLog((LOG_TRACE,2,TEXT("Fixed size sample %d"),pmt->IsFixedSize()));
    DbgLog((LOG_TRACE,2,TEXT("Temporal compression %d"),pmt->IsTemporalCompressed()));
    DbgLog((LOG_TRACE,2,TEXT("Sample size %d"),pmt->GetSampleSize()));

} // DisplayMediaType
#endif

//
// CheckMediaType
//
HRESULT CTeeInputPin::CheckMediaType(const CMediaType *pmt)
{
    CAutoLock lock_it(m_pLock);

    // If we are already inside checkmedia type for this pin, return NOERROR
    // It is possble to hookup two of the tee filters and some other filter
    // like the video effects sample to get into this situation. If we don't
    // detect this situation, we will carry on looping till we blow the stack

    if (m_bInsideCheckMediaType == TRUE)
        return NOERROR;

    m_bInsideCheckMediaType = TRUE;
    HRESULT hr = NOERROR;

#ifdef DEBUG
    // Display the type of the media for debugging perposes
    DisplayMediaType(TEXT("Input Pin Checking"), pmt);
#endif

    // The media types that we can support are entirely dependent on the
    // downstream connections. If we have downstream connections, we should
    // check with them - walk through the list calling each output pin

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();

    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            if (pOutputPin->m_Connected != NULL) {
                // The pin is connected, check its peer
                hr = pOutputPin->m_Connected->QueryAccept(pmt);
                if (hr != NOERROR) {
                    m_bInsideCheckMediaType = FALSE;
                    return VFW_E_TYPE_NOT_ACCEPTED;
                }
            }
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }

    // Either all the downstream pins have accepted or there are none.
    m_bInsideCheckMediaType = FALSE;
    return NOERROR;

} // CheckMediaType

//
// SetMediaType
//
HRESULT CTeeInputPin::SetMediaType(const CMediaType *pmt)
{
    CAutoLock lock_it(m_pLock);
    HRESULT hr = NOERROR;

    // Make sure that the base class likes it
    hr = CBaseInputPin::SetMediaType(pmt);
    if (FAILED(hr))
        return hr;

    ASSERT(m_Connected != NULL);
    return NOERROR;

} // SetMediaType

//
// BreakConnect
//
HRESULT CTeeInputPin::BreakConnect()
{
    // Release any allocator that we are holding
    if (m_pTee->m_pAllocator)
    {
        m_pTee->m_pAllocator->Release();
        m_pTee->m_pAllocator = NULL;
    }
    return NOERROR;

} // BreakConnect

//
// NotifyAllocator
//
STDMETHODIMP
CTeeInputPin::NotifyAllocator(IMemAllocator *pAllocator, BOOL bReadOnly)
{
    CAutoLock lock_it(m_pLock);
    if (pAllocator == NULL)
        return E_FAIL;

    // Free the old allocator if any
    if (m_pTee->m_pAllocator)
        m_pTee->m_pAllocator->Release();

    // Store away the new allocator
    pAllocator->AddRef();
    m_pTee->m_pAllocator = pAllocator;

    // Notify the base class about the allocator
    return CBaseInputPin::NotifyAllocator(pAllocator,bReadOnly);

} // NotifyAllocator

//
// EndOfStream
//
HRESULT CTeeInputPin::EndOfStream()
{
    CAutoLock lock_it(m_pLock);
    ASSERT(m_pTee->m_NumOutputPins);
    HRESULT hr = NOERROR;

    // Walk through the output pins list, sending the message downstream

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            hr = pOutputPin->DeliverEndOfStream();
            if (FAILED(hr))
                return hr;
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return(NOERROR);

} // EndOfStream

//
// BeginFlush
//
HRESULT CTeeInputPin::BeginFlush()
{
    CAutoLock lock_it(m_pLock);
    ASSERT(m_pTee->m_NumOutputPins);
    HRESULT hr = NOERROR;

    // Walk through the output pins list, sending the message downstream

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            hr = pOutputPin->DeliverBeginFlush();
            if (FAILED(hr))
                return hr;
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return CBaseInputPin::BeginFlush();

} // BeginFlush

//
// EndFlush
//
HRESULT CTeeInputPin::EndFlush()
{
    CAutoLock lock_it(m_pLock);
    ASSERT(m_pTee->m_NumOutputPins);
    HRESULT hr = NOERROR;

    // Walk through the output pins list, sending the message downstream

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            hr = pOutputPin->DeliverEndFlush();
            if (FAILED(hr))
                return hr;
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return CBaseInputPin::EndFlush();

} // EndFlush

//
// NewSegment
//

HRESULT CTeeInputPin::NewSegment(REFERENCE_TIME tStart,
                                 REFERENCE_TIME tStop,
                                 double dRate)
{
    CAutoLock lock_it(m_pLock);
    ASSERT(m_pTee->m_NumOutputPins);
    HRESULT hr = NOERROR;

    // Walk through the output pins list, sending the message downstream

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            hr = pOutputPin->DeliverNewSegment(tStart, tStop, dRate);
            if (FAILED(hr))
                return hr;
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return CBaseInputPin::NewSegment(tStart, tStop, dRate);

} // NewSegment

//
// Receive
//
HRESULT CTeeInputPin::Receive(IMediaSample *pSample)
{
    CAutoLock lock_it(m_pLock);

    // Check that all is well with the base class
    HRESULT hr = NOERROR;
    hr = CBaseInputPin::Receive(pSample);
    if (hr != NOERROR)
        return hr;

    // Walk through the output pins list, delivering to each in turn

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            hr = pOutputPin->Deliver(pSample);
            if (hr != NOERROR)
                return hr;
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return NOERROR;

} // Receive

//
// Completed a connection to a pin
//
HRESULT CTeeInputPin::CompleteConnect(IPin *pReceivePin)
{
    HRESULT hr = CBaseInputPin::CompleteConnect(pReceivePin);
    if (FAILED(hr)) {
        return hr;
    }

    // Force any output pins to use our type

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();

    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL) {
            // Check with downstream pin
            if (pOutputPin->m_Connected != NULL) {
                if (m_mt != pOutputPin->m_mt)
                    m_pTee->ReconnectPin(pOutputPin, &m_mt);
            }
        } else {
            // We should have as many pins as the count says we have
            ASSERT(FALSE);
        }
        n--;
    }
    return S_OK;
}

//
// CTeeOutputPin constructor
//
CTeeOutputPin::CTeeOutputPin(TCHAR *pName,
                             CTee *pTee,
                             HRESULT *phr,
                             LPCWSTR pPinName,
                             int PinNumber) :
    CBaseOutputPin(pName, pTee, pTee, phr, pPinName) ,
    m_pOutputQueue(NULL),
    m_bHoldsSeek(FALSE),
    m_pPosition(NULL),
    m_pTee(pTee),
    m_cOurRef(0),
    m_bInsideCheckMediaType(FALSE)
{
    ASSERT(pTee);
}

#ifdef DEBUG
//
// CTeeOutputPin destructor
//
CTeeOutputPin::~CTeeOutputPin()
{
    ASSERT(m_pOutputQueue == NULL);
}
#endif

//
// NonDelegatingQueryInterface
//
// This function is overwritten to expose IMediaPosition and IMediaSelection
// Note that only one output stream can be allowed to expose this to avoid
// conflicts, the other pins will just return E_NOINTERFACE and therefore
// appear as non seekable streams. We have a LONG value that if exchanged to
// produce a TRUE means that we have the honor. If it exchanges to FALSE then
// someone is already in. If we do get it and error occurs then we reset it
// to TRUE so someone else can get it.
//
STDMETHODIMP
CTeeOutputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    ASSERT(ppv);
    *ppv = NULL;
    HRESULT hr = NOERROR;

    // See what interface the caller is interested in.
    if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
        if (m_pPosition) {
            if (m_bHoldsSeek == FALSE)
                return E_NOINTERFACE;
            return m_pPosition->QueryInterface(riid, ppv);
        }
    } else
        return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);

    CAutoLock lock_it(m_pLock);
    ASSERT(m_pPosition == NULL);
    IUnknown *pMediaPosition = NULL;

    // Try to create a seeking implementation
    if (InterlockedExchange(&m_pTee->m_lCanSeek, FALSE) == FALSE)
        return E_NOINTERFACE;

    // Create implementation of this dynamically as sometimes we may never
    // try and seek. The helper object implements IMediaPosition and also
    // the IMediaSelection control interface and simply takes the calls
    // normally from the downstream filter and passes them upstream

    hr = CreatePosPassThru(
                   GetOwner(),
                   FALSE,
                   (IPin *)&m_pTee->m_Input,
                   &pMediaPosition);

    if (pMediaPosition == NULL) {
        InterlockedExchange(&m_pTee->m_lCanSeek, TRUE);
        return E_OUTOFMEMORY;
    }

    if (FAILED(hr)) {
        InterlockedExchange(&m_pTee->m_lCanSeek, TRUE);
        pMediaPosition->Release ();
        return hr;
    }

    m_pPosition = pMediaPosition;
    m_bHoldsSeek = TRUE;
    return NonDelegatingQueryInterface(riid, ppv);

} // NonDelegatingQueryInterface

//
// NonDelegatingAddRef
//
// We need override this method so that we can do proper reference counting
// on our output pin. The base class CBasePin does not do any reference
// counting on the pin in RETAIL.
//
// Please refer to the comments for the NonDelegatingRelease method for more
// info on why we need to do this.
//
STDMETHODIMP_(ULONG) CTeeOutputPin::NonDelegatingAddRef()
{
    CAutoLock lock_it(m_pLock);

#ifdef DEBUG
    // Update the debug only variable maintained by the base class
    m_cRef++;
    ASSERT(m_cRef > 0);
#endif

    // Now update our reference count
    m_cOurRef++;
    ASSERT(m_cOurRef > 0);
    return m_cOurRef;

} // NonDelegatingAddRef

//
// NonDelegatingRelease
//
// CTeeOutputPin overrides this class so that we can take the pin out of our
// output pins list and delete it when its reference count drops to 1 and there
// is atleast two free pins.
//
// Note that CreateNextOutputPin holds a reference count on the pin so that
// when the count drops to 1, we know that no one else has the pin.
//
// Moreover, the pin that we are about to delete must be a free pin(or else
// the reference would not have dropped to 1, and we must have atleast one
// other free pin(as the filter always wants to have one more free pin)
//
// Also, since CBasePin::NonDelegatingAddRef passes the call to the owning
// filter, we will have to call Release on the owning filter as well.
//
// Also, note that we maintain our own reference count m_cOurRef as the m_cRef
// variable maintained by CBasePin is debug only.
//
STDMETHODIMP_(ULONG) CTeeOutputPin::NonDelegatingRelease()
{
    CAutoLock lock_it(m_pLock);

#ifdef DEBUG
    // Update the debug only variable in CBasePin
    m_cRef--;
    ASSERT(m_cRef >= 0);
#endif

    // Now update our reference count
    m_cOurRef--;
    ASSERT(m_cOurRef >= 0);

    // if the reference count on the object has gone to one, remove
    // the pin from our output pins list and physically delete it
    // provided there are atealst two free pins in the list(including
    // this one)

    // Also, when the ref count drops to 0, it really means that our
    // filter that is holding one ref count has released it so we
    // should delete the pin as well.

    if (m_cOurRef <= 1) {
        int n = 2;                     // default forces pin deletion
        if (m_cOurRef == 1) {
            // Walk the list of pins, looking for count of free pins
            n = m_pTee->GetNumFreePins();
        }

        // If there are two free pins, delete this one.
        // NOTE: normall
        if (n >= 2 ) {
            m_cOurRef = 0;
#ifdef DEBUG
            m_cRef = 0;
#endif
            m_pTee->DeleteOutputPin(this);
            return(ULONG) 0;
        }
    }
    return(ULONG) m_cOurRef;

} // NonDelegatingRelease

//
// DecideBufferSize
//
// This has to be present to override the PURE virtual class base function
//
HRESULT CTeeOutputPin::DecideBufferSize(IMemAllocator *pMemAllocator,
                                        ALLOCATOR_PROPERTIES * ppropInputRequest)
{
    return NOERROR;

} // DecideBufferSize

//
// DecideAllocator
//
HRESULT CTeeOutputPin::DecideAllocator(IMemInputPin *pPin, IMemAllocator **ppAlloc)
{
    ASSERT(m_pTee->m_pAllocator != NULL);
    *ppAlloc = NULL;

    // Tell the pin about our allocator, set by the input pin.
    HRESULT hr = NOERROR;
    hr = pPin->NotifyAllocator(m_pTee->m_pAllocator,TRUE);
    if (FAILED(hr))
        return hr;

    // Return the allocator
    *ppAlloc = m_pTee->m_pAllocator;
    m_pTee->m_pAllocator->AddRef();
    return NOERROR;

} // DecideAllocator

//
// CheckMediaType
//
HRESULT CTeeOutputPin::CheckMediaType(const CMediaType *pmt)
{
    CAutoLock lock_it(m_pLock);

    // If we are already inside checkmedia type for this pin, return NOERROR
    // It is possble to hookup two of the tee filters and some other filter
    // like the video effects sample to get into this situation. If we
    // do not detect this, we will loop till we blow the stack

    if (m_bInsideCheckMediaType == TRUE)
        return NOERROR;

    m_bInsideCheckMediaType = TRUE;
    HRESULT hr = NOERROR;

#ifdef DEBUG
    // Display the type of the media for debugging purposes
    DisplayMediaType(TEXT("Output Pin Checking"), pmt);
#endif

    // The input needs to have been conneced first
    if (m_pTee->m_Input.m_Connected == NULL) {
        m_bInsideCheckMediaType = FALSE;
        return VFW_E_NOT_CONNECTED;
    }

    // Make sure that our input pin peer is happy with this
    hr = m_pTee->m_Input.m_Connected->QueryAccept(pmt);
    if (hr != NOERROR) {
        m_bInsideCheckMediaType = FALSE;
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Check the format with the other outpin pins

    int n = m_pTee->m_NumOutputPins;
    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();

    while(n) {
        CTeeOutputPin *pOutputPin = m_pTee->m_OutputPinsList.GetNext(pos);
        if (pOutputPin != NULL && pOutputPin != this) {
            if (pOutputPin->m_Connected != NULL) {
                // The pin is connected, check its peer
                hr = pOutputPin->m_Connected->QueryAccept(pmt);
                if (hr != NOERROR) {
                    m_bInsideCheckMediaType = FALSE;
                    return VFW_E_TYPE_NOT_ACCEPTED;
                }
            }
        }
        n--;
    }
    m_bInsideCheckMediaType = FALSE;
    return NOERROR;

} // CheckMediaType

//
// EnumMediaTypes
//
STDMETHODIMP CTeeOutputPin::EnumMediaTypes(IEnumMediaTypes **ppEnum)
{
    CAutoLock lock_it(m_pLock);
    ASSERT(ppEnum);

    // Make sure that we are connected
    if (m_pTee->m_Input.m_Connected == NULL)
        return VFW_E_NOT_CONNECTED;

    // We will simply return the enumerator of our input pin's peer
    return m_pTee->m_Input.m_Connected->EnumMediaTypes(ppEnum);

} // EnumMediaTypes

//
// SetMediaType
//
HRESULT CTeeOutputPin::SetMediaType(const CMediaType *pmt)
{
    CAutoLock lock_it(m_pLock);

#ifdef DEBUG
    // Display the format of the media for debugging purposes
    DisplayMediaType(TEXT("Output pin type agreed"), pmt);
#endif

    // Make sure that we have an input connected
    if (m_pTee->m_Input.m_Connected == NULL)
        return VFW_E_NOT_CONNECTED;

    // Make sure that the base class likes it
    HRESULT hr = NOERROR;
    hr = CBaseOutputPin::SetMediaType(pmt);
    if (FAILED(hr))
        return hr;

    return NOERROR;

} // SetMediaType

//
// CompleteConnect
//
HRESULT CTeeOutputPin::CompleteConnect(IPin *pReceivePin)
{
    CAutoLock lock_it(m_pLock);
    ASSERT(m_Connected == pReceivePin);
    HRESULT hr = NOERROR;

    hr = CBaseOutputPin::CompleteConnect(pReceivePin);
    if (FAILED(hr))
        return hr;

    // If the type is not the same as that stored for the input
    // pin then force the input pins peer to be reconnected

    if (m_mt != m_pTee->m_Input.m_mt)
    {
        hr = m_pTee->ReconnectPin(m_pTee->m_Input.m_Connected, &m_mt);
        if(FAILED(hr)) {
            return hr;
        }
    }

    // Since this pin has been connected up, create another output pin. We
    // will do this only if there are no unconnected pins on us. However
    // CompleteConnect will get called for the same pin during reconnection

    int n = m_pTee->GetNumFreePins();
    ASSERT(n <= 1);
    if (n == 1 || m_pTee->m_NumOutputPins == INFTEE_MAX_PINS)
        return NOERROR;

    // No unconnected pins left so spawn a new one

    CTeeOutputPin *pOutputPin = m_pTee->CreateNextOutputPin(m_pTee);
    if (pOutputPin != NULL )
    {
        m_pTee->m_NumOutputPins++;
        m_pTee->m_OutputPinsList.AddTail(pOutputPin);
m_pTee->IncrementPinVersion();
    }

    // At this point we should be able to send some
    // notification that we have sprung a new pin

    return NOERROR;

} // CompleteConnect

//
// Active
//
// This is called when we start running or go paused. We create the
// output queue object to send data to our associated peer pin
//
HRESULT CTeeOutputPin::Active()
{
    CAutoLock lock_it(m_pLock);
    HRESULT hr = NOERROR;

    // Make sure that the pin is connected
    if (m_Connected == NULL)
        return NOERROR;

    // Create the output queue if we have to
    if (m_pOutputQueue == NULL)
    {
        m_pOutputQueue = new COutputQueue(m_Connected, &hr, TRUE, FALSE);
        if (m_pOutputQueue == NULL)
            return E_OUTOFMEMORY;

        // Make sure that the constructor did not return any error
        if (FAILED(hr))
        {
            delete m_pOutputQueue;
            m_pOutputQueue = NULL;
            return hr;
        }
    }

    // Pass the call on to the base class
    CBaseOutputPin::Active();
    return NOERROR;

} // Active

//
// Inactive
//
// This is called when we stop streaming
// We delete the output queue at this time
//
HRESULT CTeeOutputPin::Inactive()
{
    CAutoLock lock_it(m_pLock);

    // Delete the output queus associated with the pin.
    if (m_pOutputQueue)
    {
        delete m_pOutputQueue;
        m_pOutputQueue = NULL;
    }

    CBaseOutputPin::Inactive();
    return NOERROR;

} // Inactive

//
// Deliver
//
HRESULT CTeeOutputPin::Deliver(IMediaSample *pMediaSample)
{
    // Make sure that we have an output queue
    if (m_pOutputQueue == NULL)
        return NOERROR;

    pMediaSample->AddRef();
    return m_pOutputQueue->Receive(pMediaSample);

} // Deliver

//
// DeliverEndOfStream
//
HRESULT CTeeOutputPin::DeliverEndOfStream()
{
    // Make sure that we have an output queue
    if (m_pOutputQueue == NULL)
        return NOERROR;

    m_pOutputQueue->EOS();
    return NOERROR;

} // DeliverEndOfStream

//
// DeliverBeginFlush
//
HRESULT CTeeOutputPin::DeliverBeginFlush()
{
    // Make sure that we have an output queue
    if (m_pOutputQueue == NULL)
        return NOERROR;

    m_pOutputQueue->BeginFlush();
    return NOERROR;

} // DeliverBeginFlush

//
// DeliverEndFlush
//
HRESULT CTeeOutputPin::DeliverEndFlush()
{
    // Make sure that we have an output queue
    if (m_pOutputQueue == NULL)
        return NOERROR;

    m_pOutputQueue->EndFlush();
    return NOERROR;

} // DeliverEndFlish

//
// DeliverNewSegment
//
HRESULT CTeeOutputPin::DeliverNewSegment(REFERENCE_TIME tStart,
                                         REFERENCE_TIME tStop,
                                         double dRate)
{
    // Make sure that we have an output queue
    if (m_pOutputQueue == NULL)
        return NOERROR;

    m_pOutputQueue->NewSegment(tStart, tStop, dRate);
    return NOERROR;

} // DeliverNewSegment

//
// Notify
//
STDMETHODIMP CTeeOutputPin::Notify(IBaseFilter *pSender, Quality q)
{
    // We pass the message on, which means that we find the quality sink
    // for our input pin and send it there

    POSITION pos = m_pTee->m_OutputPinsList.GetHeadPosition();
    CTeeOutputPin *pFirstOutput = m_pTee->m_OutputPinsList.GetNext(pos);

    if (this == pFirstOutput) {
if (m_pTee->m_Input.m_pQSink!=NULL) {
    return m_pTee->m_Input.m_pQSink->Notify(m_pTee, q);
} else {

    // No sink set, so pass it upstream
    HRESULT hr;
    IQualityControl * pIQC;

    hr = VFW_E_NOT_FOUND;
    if (m_pTee->m_Input.m_Connected) {
m_pTee->m_Input.m_Connected->QueryInterface(IID_IQualityControl,(void**)&pIQC);

if (pIQC!=NULL) {
    hr = pIQC->Notify(m_pTee, q);
    pIQC->Release();
}
    }
    return hr;
}
    }

    // Quality management is too hard to do
    return NOERROR;

} // Notify

//
// DllRegisterServer
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );
}

//
// DllUnregisterServer
//
STDAPI
DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );
}


SAMPIOS.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*
See SampIOS.h for descriptions of these functions

This file is built into SampIOS.lib, which may be linked into your
executables to supply the various output functions.
*/

#include <streams.h>
#include <stdio.h>
#include <iostream.h>
#include "SampIOS.h"

ostream & operator <<( ostream & out, LPCWSTR pwStr )
{
    const int WideStrLen = lstrlenW(pwStr);
    char *const buffer = new char[WideStrLen+1];
    const int bytes_used =
        WideCharToMultiByte ( CP_ACP, 0, pwStr, WideStrLen
                            , buffer, WideStrLen+1, 0, 0);
    ASSERT( bytes_used != FALSE );
    buffer[bytes_used] = '\0';
    out << buffer;
    delete [] buffer;
    return out;
}

ostream & operator <<( ostream & out, const GUID & clsid )
{
    OLECHAR sz_clsid[CHARS_IN_GUID] = L"{Unknown}";
    StringFromGUID2( clsid, sz_clsid, sizeof(sz_clsid)/sizeof(sz_clsid[0]) );
    out << sz_clsid;
    return out;
}

ostream & operator <<( ostream & out, const IBaseFilter *const pIFilter )
{
    HRESULT hr;
    {
        FILTER_INFO filter_info;
        filter_info.pGraph = 0;
        hr = const_cast<IBaseFilter *>(pIFilter)->QueryFilterInfo( &filter_info );
        ASSERT( SUCCEEDED(hr) );
        out << '"' << filter_info.achName << '"';
if (filter_info.pGraph) {
            filter_info.pGraph->Release();
}
    }
    {
        CLSID clsid;
        hr = const_cast<IBaseFilter *>(pIFilter)->GetClassID( &clsid );
        ASSERT( SUCCEEDED(hr) );
        out << clsid;
    }
    return out;
}

ostream & operator <<( ostream & out, const IEnumFilters *const pIEnumFilters )
{
    IBaseFilter * pIFilter;
    ULONG count;

    while ( SUCCEEDED(const_cast<IEnumFilters*>(pIEnumFilters)->Next( 1, &pIFilter, &count )) && (count > 0))
    {
        out << pIFilter << '\n';
        pIFilter->Release();
    }
    return out;
}

ostream & operator <<( ostream & out, const IPin *const pIPin )
{
    LPWSTR      pin_id;
    PIN_INFO    pin_info;
    const IPin *p_connected_to;
    pin_info.pFilter = 0;

    const_cast<IPin*>(pIPin)->QueryPinInfo( &pin_info );
    const_cast<IPin*>(pIPin)->QueryId( &pin_id );
    out << pin_info.pFilter << ':' << pin_id << '(' << pin_info.achName << ')';
    if (pin_info.pFilter) {
        pin_info.pFilter->Release();
pin_info.pFilter = 0;
    }
    CoTaskMemFree( pin_id );

    const_cast<IPin*>(pIPin)->ConnectedTo( const_cast<IPin**>(&p_connected_to) );
    if ( p_connected_to )
    {
        out << (pin_info.dir == PINDIR_OUTPUT ? "-->" : "<--");
        const_cast<IPin*>(p_connected_to)->QueryPinInfo( &pin_info );
        const_cast<IPin*>(p_connected_to)->QueryId( &pin_id );
        out << pin_info.pFilter << ':' << pin_id << '(' << pin_info.achName << ')';
        CoTaskMemFree( pin_id );
        if (pin_info.pFilter) {
            pin_info.pFilter->Release();
        }
        const_cast<IPin*>(p_connected_to)->Release();
    }
    return out;
}

ostream & operator <<( ostream & out, const IEnumPins *const pIEnumPins )
{
    IPin * pIPin;
    ULONG count;

    while ( SUCCEEDED(const_cast<IEnumPins*>(pIEnumPins)->Next( 1, &pIPin, &count )) && (count > 0))
    {
        out << pIPin << '\n';
        pIPin->Release();
    }
    return out;
}

ostream & WriteAllPins( ostream & out, const IBaseFilter *const piFilter )
{
    HRESULT hr;
    IEnumPins * pIEnumPins;
    hr = const_cast<IBaseFilter*>(piFilter)->EnumPins( &pIEnumPins );
    ASSERT( SUCCEEDED(hr) );
    out << pIEnumPins << flush;
    pIEnumPins->Release();
    return out;
}

ostream & operator <<( ostream & out, const IFilterGraph *const piFilterGraph )
{
    IEnumFilters * pIEnumFilters;
    HRESULT hr = const_cast<IFilterGraph*>(piFilterGraph)->EnumFilters( &pIEnumFilters );
    ASSERT( SUCCEEDED(hr) );

    IBaseFilter * pIFilter;
    ULONG count;

    while ( SUCCEEDED(const_cast<IEnumFilters*>(pIEnumFilters)->Next( 1, &pIFilter, &count )) && (count > 0))
    {
        out << pIFilter << '\n';
        WriteAllPins( out, pIFilter );
        pIFilter->Release();
    }
    pIEnumFilters->Release();

    return out << endl;
}

extern "C" void DumpFilterInfo( const IBaseFilter * pIFilter )
{
    cout << pIFilter << endl;
}

extern "C" void DumpAllFilters( const IFilterGraph * piFilterGraph )
{
    HRESULT hr;
    IEnumFilters * pIEnumFilters;
    hr = const_cast<IFilterGraph*>(piFilterGraph)->EnumFilters( &pIEnumFilters );
    ASSERT( SUCCEEDED(hr) );
    cout << pIEnumFilters << flush;
    pIEnumFilters->Release();
}

extern "C" void DumpPinInfo( const IPin * pIPin )
{
    cout << pIPin << endl;
}

extern "C" void DumpAllPins( const IBaseFilter * piFilter )
{
    WriteAllPins( cout, piFilter );
}

extern "C" void DumpFilterGraph( const IFilterGraph * piFilterGraph )
{
    cout << piFilterGraph << flush;
}


SAMPIOS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*
This header file provides function prototypes for the functions
exported from AM_ios.lib.  These functions are designed to enable
developers to get information in text format regarding objects in
the filter graph.

The 'C' functions, DumpXXX, require "cout" to be valid in the environment
in which you are working.
*/

#ifndef _SampIOS_h
#define _SampIOS_h

#ifdef __cplusplus

    // Just so you can write:   cout << piFilterGraph << endl;  etc.

    // Converts wide string to ANSI string and outputs it
    ostream & operator <<( ostream & out, LPCWSTR pwStr );
    // Builds and outputs a textual version of a GUID
    ostream & operator <<( ostream & out, const GUID & clsid );
    // Output filter information
    ostream & operator <<( ostream & out, const IBaseFilter *const pIFilter );
    // Output filter informations for all filters left in the enumerator
    ostream & operator <<( ostream & out, const IEnumFilters *const pIEnumFilters );
    // Output filter and connection details
    ostream & operator <<( ostream & out, const IFilterGraph *const piFilterGraph );
    // Output pin details (including connection info)
    ostream & operator <<( ostream & out, const IPin *const pIPin );
    // As above, for all pins left in the enumerator
    ostream & operator <<( ostream & out, const IEnumPins *const pIEnumPins );
    // Output the pin info for all pins on supplied filter
    ostream & WriteAllPins( ostream & out, const IBaseFilter *const pIFilter );

    extern "C" {
#endif

void DumpFilterInfo( const IBaseFilter * pIFilter );
void DumpPinInfo( const IPin * pIPin );
void DumpAllPins( const IBaseFilter * piFilter );
void DumpAllFilters( const IFilterGraph * piFilterGraph );
void DumpFilterGraph( const IFilterGraph * piFilterGraph );

#ifdef __cplusplus
    }
#endif

#endif /* _SampIOS_h */


README.TXT

Iplay Sample Application

Version: 1,0,0,3Date:  Dec 20, 1996

Table of Contents
-----------------
1.  Overview
2.  System Requirements
3.  Application Structure
4.  How To Run
5.  Known Limitations
6.  References

1. Overview
-----------
Iplay is a sample ActiveMovie video player.   The purpose of Iplay is to
demonstrate the Indeo Video Interactive API in the ActiveMovie
environment.

2.  System Requirements
-----------------------
Windows 95 or Windows NT
DirectShow 5.1 beta
MSVC++ 4.2 (or later)
ir41_32.ax placed in the windows system directory and registered
  (   drive:\msdev\bin\regsvr32   drive:\windows\system\ir41_32.ax   )

3.  Application Structure
-------------------------
Iplay is written in C++ using the Microsoft Foundation Class (MFC)
library.  It follows the standard structure of an MFC application:

FileClassDescription
------------------------------------------------------------------
Iplay.cppCIPlayAppThe application.
Iplay.h

Mainfrm.cppCMainFrameThe main frame window.
Mainfrm.h

Indeo.cppCindeoThe form view containing
Indeo.hthe Indeo-specific controls.

IPlayDoc.cppCIPlayDocThe "document", which in this
IPlayDoc.hcase is a filter graph.

There are also include files and resource files that make up Iplay.  The
resource files are located in the .\res subdirectory of the Iplay source
directory.

FileDescription
------------------------------------------------------------------
Ax_spec.hThe Indeo Video Interactive interface
definitions for ActiveMovie.

Iplay.rcThe Iplay resource definitions.

Bitmap1.bmpThe custom toolbar bitmap.

Indeo.icoThe Indeo icon.

Vfw_spec.hIndeo-specific data structures.

All ActiveMovie applications must include the following ActiveMovie
header files.

FileDescription
------------------------------------------------------------------
control.hInterface to type library: QuartzTypeLib.

evcode.hStandard Quartz event codes.

strmif.hCOM interface definitions.

uuids.hThe GUIDs for the MediaType type and class
ids for well-known components.

5.  How To Run
--------------

The Iplay application is a standard Windows 95/NT application.  It can
be started by double-clicking its icon, or by using the Run command.
Iplay also supports drag-and-drop; you can drag a movie file (.avi) onto
the Iplay application to open the file in Iplay. If you are working in
Microsoft Developer Studio, from the Build menu select Execute
Iplay.exe.

Iplay can play any movie file, not just Indeo Video Interactive (IVI)
format movies.  If a movie is not IVI format, the Indeo-specific
controls will not be enabled.

When the application starts, the initial view is just the menu and the
toolbar.  Click the Indeo icon on the toolbar to show/hide the extended
view which contains the Indeo-specific controls.

Using The Indeo-Specific Controls

Indeo Video Interactive provides some special settings that enhance
standard playback.  The settings are of two types: sequence options and
frame options.  The sequence options affect a playback sequence and must
be set prior to playing the movie; once the movie is playing the
sequence options cannot be changed.  The frame options affect each frame
of the movie and can be changed at any time while the movie is stopped
or playing.

The sequence options consist of Scalability, Access Key, Alt-Line and
Transparency Fill.  Turning Scalability on allows the Indeo codec to
scale playback performance by dropping bands instead of dropping frames
to maintain frame rate.  A movie must be encoded with Scalability in
order for this option to have an effect on playback.  The Access Key is
an encrypted integer that provides password protection to a movie.  The
Access Key is set at encode time, and at playback the Access Key option
must be turned on and the correct access key value provided in order to
play the movie. If a movie was not encoded with an Access Key, this
option has no effect.  Turning Alt-Line on affects playback when the
movie is resized to exactly double its original size (zoom by two).
When the movie is "zoomed" and Alt-Line is enabled, the codec stretches
the image by filling alternate lines of the output buffer with black
lines.  The black lines are written only once (if the window is not
moved around on the screen), resulting in some performance savings
because less information has to be written to the output buffer for each
frame.  Turning Transparency Fill on or off only affects movies that
were encoded with transparency.  When Transparency Fill is on, the codec
writes the "transparent" pixels to the output buffer as a solid color.
When Transparency Fill is off, the codec does not write the transparent
pixels to the output buffer.

The frame options consist of Brightness, Saturation, Contrast, Decode
Time, Decode Rect and View Rect.  Brightness, Saturation, and Contrast
control the appearance of the movie s colors.  They can range in value
from -128 to 128, with 0 being normal.  Decode Time is the time limit in
milliseconds for the codec to decode each frame.  A setting of 0 means
the codec should use the default time limit based on the encoded frame
rate of the movie.  For example, a 15 frame-per-second (fps) movie has a
default decode time limit of 1/15 x 100 = 66.7 ms.  The Decode Rect is
the portion of the source image that is actually decoded.  The Decode
Rect cannot exceed the boundaries of the source image.  A Decode Rect
with with 0 width and 0 height located at 0,0 defaults to the entire
source image.  The View Rect is the portion of the decoded image that is
actually written to the output buffer.  The View Rect cannot exceed the
boundaries of the Decode Rect.  A View Rect of 0 width and 0 height
located at 0,0 defaults to the entire Decode Rect.

6.  References
--------------

Indeo Video Interactive Features and Capabilities
(http://www.intel.com/pc-supp/multimed/indeo)


CPYRIGHT.H

/************************************************************************
*                                                                       *
*               INTEL CORPORATION PROPRIETARY INFORMATION               *
*                                                                       *
*    This listing is supplied under the terms of a license agreement    *
*      with INTEL Corporation and may not be copied nor disclosed       *
*        except in accordance with the terms of that agreement.         *
*                                                                       *
*************************************************************************
*                                                                       *
*               Copyright (c) 1994-1995 Intel Corporation.              *
*                         All Rights Reserved.                          *
*                                                                       *
************************************************************************/


DATATYPE.H

/************************************************************************
*                                                                       *
*               INTEL CORPORATION PROPRIETARY INFORMATION               *
*                                                                       *
*    This listing is supplied under the terms of a license agreement    *
*      with INTEL Corporation and may not be copied nor disclosed       *
*        except in accordance with the terms of that agreement.         *
*                                                                       *
*************************************************************************
*                                                                       *
*Copyright (c) 1994-1997 Intel Corporation.*
*                         All Rights Reserved.                          *
*                                                                       *
************************************************************************/

#ifdef INCLUDE_NESTING_CHECK
#ifdef _OLE_DATATYPE_H__
#pragma message("***** src\\encode\\ version of DATATYPE.H included multiple times")
#else
#define _OLE_DATATYPE_H__
#pragma message ("src\\encode\\ version of DATATYPE.H being included first time")
#endif
#endif

#ifndef __DATATYPE_H__
#define __DATATYPE_H__

#ifdef INCLUDE_NESTING_CHECK
#pragma message ("src\\encode\\ version of DATATYPE.H actually being used")
#endif

#include <stddef.h>
/* These are the non platform specific data types
 * for which all data and return types are defined
 */

/* These types of data must allways be the same
 * number of bits regardless of the platform.
 * Therefore, these generic names are defined
 * to be:
 *
 * U8      8 bits,  unsigned 0..255
 * I8      8 bits,  signed -128..127
 * U16     16 bits, unsigned 0..65535
 * I16     16 bits, signed -32768..32767
 * U32     32 bits, unsigned 0..4294967295
 * I32     32 bits, signed -2147483648..2147483647
 * Sngl    32 bit floating point (4 bytes)
 * Dbl     64 bit floating point (8 bytes)
 *
 * These pointers are naturally platform specific,
 * but what they point to is allways the same size
 *
 *               These are huge pointers
 * PU8
 * PI8
 * PU16
 * PI16
 * PU32
 * PI32
 * PSngl
 * PDbl
 *               These are far pointers
 * FPU8
 * FPI8
 * FPU16
 * FPI16
 * FPU32
 * FPI32
 * FPSngl
 * FPDbl
 */

#if defined HUGE
#undef HUGE
#endif
#if defined FAR
#undef FAR
#endif
#if defined NEAR
#undef NEAR
#endif

#if defined WINDOWS
#if defined WIN32
/* WINDOWS NT data types, Only X86 support at this time */
#define NEAR
#define FAR
#define HUGE

// TCHAR should be appropriately defined in std header files - ??
// #ifndef _TCHAR_DEFINED
// typedef wchar_t TCHAR; /* unicode for strings */
// #endif

typedef unsigned char  U8;
typedef signed char    I8;
typedef unsigned short U16;
typedef signed short   I16;
typedef unsigned long  U32;
typedef signed long    I32;
typedef float          Sngl;
typedef double         Dbl;

#else /* WIN32 not defined */
/* WINDOWS 3.x under DOS data types */
#define NEAR _near
#define FAR  _far
#define HUGE _huge

#ifndef _INC_TCHAR
typedef char far *LPTSTR;
typedef const char far *LPCTSTR;
typedef charTCHAR;
#define TEXT(quote)quote
#endif
#ifndef BOOLEAN
typedef char BOOLEAN;
#endif
#ifndef INT
typedef int INT;
#endif

typedef unsigned char  U8;
typedef signed char    I8;
typedef unsigned short U16;
typedef signed short   I16;
typedef unsigned long  U32;
typedef signed long    I32;
typedef float          Sngl;
typedef double         Dbl;

#endif /* WIN32 */
#else /* WINDOWS not defined */
/* UNIX data types */
#define NEAR
#define FAR
#define HUGE
#define CDECL
#define BSENC 1
#define HGLOBAL PI16
#define LRESULT I32
#define HINSTANCE PU8

typedef char TCHAR; /* for compatability with win/win32 datatypes for strings */

typedef unsigned char    U8;
typedef signed char      I8;
typedef unsigned short   U16;
typedef signed short int I16;
typedef unsigned long    U32;
typedef signed long      I32;
typedef float            Sngl;
typedef double           Dbl;

/* move WIN32 UNICODE data types to UNIX data types */
typedef char * LPTSTR;
typedef const char * LPCTSTR;

#ifndef TEXT
#define TEXT(quote) quote
#endif /* TEXT */
#endif /* WINDOWS */

typedef char Chr;        /* character string */
typedef char Qchar;  /* quoted character string */

/* pointers */
typedef Chr  NEAR * PChr;       /* near pointer to Character string */
typedef U8   NEAR * PU8;        /* near pointer to unsigned char */
typedef I8   NEAR * PI8;        /* near pointer to signed char */
typedef U16  NEAR * PU16;       /* near pointer to unsigned 16 bit value */
typedef I16  NEAR * PI16;       /* near pointer to signed 16 bit value */
typedef U32  NEAR * PU32;       /* near pointer to unsigned 32 bit value */
typedef I32  NEAR * PI32;       /* near pointer to signed 32 bit value */
typedef Sngl NEAR * PSngl;      /* near pointer to single precision float */
typedef Dbl  NEAR * PDbl;       /* near pointer to double precision float */

typedef Chr  FAR * FPChr;       /* far pointer to Character string */
typedef U8   FAR * FPU8;        /* far pointer to unsigned char */
typedef I8   FAR * FPI8;        /* far pointer to signed char */
typedef U16  FAR * FPU16;       /* far pointer to unsigned 16 bit value */
typedef I16  FAR * FPI16;       /* far pointer to signed 16 bit value */
typedef U32  FAR * FPU32;       /* far pointer to unsigned 32 bit value */
typedef I32  FAR * FPI32;       /* far pointer to signed 32 bit value */
typedef Sngl FAR * FPSngl;      /* far pointer to single precision float */
typedef Dbl  FAR * FPDbl;       /* far pointer to double precision float */

typedef Chr  HUGE * HPChr;      /* huge pointer to Character string */
typedef U8   HUGE * HPU8;       /* huge pointer to unsigned char */
typedef I8   HUGE * HPI8;       /* huge pointer to signed char */
typedef U16  HUGE * HPU16;      /* huge pointer to unsigned 16 bit value */
typedef I16  HUGE * HPI16;      /* huge pointer to signed 16 bit value */
typedef U32  HUGE * HPU32;      /* huge pointer to unsigned 32 bit value */
typedef I32  HUGE * HPI32;      /* huge pointer to signed 32 bit value */
typedef Sngl HUGE * HPSngl;     /* huge pointer to single precision float */
typedef Dbl  HUGE * HPDbl;      /* huge pointer to double precision float */

typedef PU8  NEAR * PPU8;        /* near handle to unsigned char */
typedef PI8  NEAR * PPI8;        /* near handle to unsigned char */
typedef PU16  NEAR * PPU16;        /* near handle to unsigned char */
typedef PI16  NEAR * PPI16;        /* near handle to unsigned char */
typedef PU32  NEAR * PPU32;        /* near handle to unsigned char */
typedef PI32  NEAR * PPI32;        /* near handle to unsigned char */

/* These types of data do not need to be the
 * same from platform to platform. The only
 * concern is that the values represented by
 * these types fit into the smallest bit size
 * of any platform we port to. As new platforms
 * are added, please update this table:
 *
 *                  WIN3.x UNIX  WINNT (win32)
 * NaturalInt:      16     32    32
 * NaturalUnsigned: 16     32    32
 *
 * These pointers are naturally platform specific,
 * and what they point to is allowed to be
 * platform specific also
 *
 *               These are huge pointers
 * PNaturalInt
 * PNaturalUnsigned
 *               These are far pointers
 * FPNaturalInt
 * FPNaturalUnsigned
 *
 * For those who complain about long identifiers,
 * these data types are spelled out specifically
 * to avoid confusion with the fixed length data
 * types and to let the reader who is unfamiliar
 * with our coding practices know that these data
 * types are different on each platform. The intent
 * is to be obvious and obnoxious.
 *
 * to facilitate testing, define TEST_16_BITS
 * which will force all natural data types to 16 bits
 */

#if defined TEST_16_BITS
typedef I16 NaturalInt;
typedef U16 NaturalUnsigned;
#else
#if defined TEST_32_BITS
typedef I32 NaturalInt;
typedef U32 NaturalUnsigned;
#else
typedef signed int NaturalInt;
typedef unsigned int NaturalUnsigned;
#endif /* TEST_32_BITS */
#endif /* TEST_16_BITS */

/* pointers */
typedef NaturalInt HUGE *      PNaturalInt;
typedef NaturalUnsigned HUGE * PNaturalUnsigned;

typedef NaturalInt FAR *       FPNaturalInt;
typedef NaturalUnsigned FAR *  FPNaturalUnsigned;

/* logical types */
typedef NaturalIntBoo;    /* was U32 */
typedef BooBoolean;
typedef Boo*pBoo;

/* BGR_ENTRY
 * The BGR_ENTRY structure is used to define one color.
 * Formerly in PIA_MAIN.H
 */
typedef struct {
U8 u8B;
U8 u8G;
U8 u8R;
U8 u8Reserved;
} BGR_ENTRY;
typedef BGR_ENTRY FAR * PTR_BGR_ENTRY;

/* ASM interface */
#if defined WIN32
#define ASM_CALLTYPE _stdcall
#else
#define ASM_CALLTYPE
#endif

typedef void NEAR * LOCAL_HANDLE;
typedef void NEAR * GLOBAL_HANDLE;
typedef void NEAR * VOID_LOCAL_PTR;
typedef void FAR * VOID_GLOBAL_PTR;
typedef void HUGE * VOID_HUGE_PTR;
typedef void NEAR * MUTEX_HANDLE;
typedef void NEAR * VOID_INSTANCE_PTR;

#define PChrPU8
#define TRUE  1
#define FALSE 0
// #define NULL 0  // Currently defined elsewhere

/* define useful macros */

// TO DO: Maybe these macros should go somewhere else (not in datatypes.h)

#define ARSHIFT(a, b) ((a) >> (b))

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MIN_MAX(a, l, h) ((a) < (l) ? (l) : ((a) > (h) ? (h) : (a)))
#define ABS(a) ((a) < 0 ? (-(a)) : (a))
#define ROUND(f) (I32)((f)>=0? f + .4999999 : f - .4999999 )
#define DIV2(x)     ((x)>0?(x)>>1:-(-(x))>>1)
#define DIV_ROUND(x,d)     ((x)>0?((x)+(d)/2)/(d):-(((-(x))+(d)/2)/(d)))
#define ROUND_SHIFT(a,shift) ( (a)>0?((a)+(1<<((shift)-1)))>>(shift):-((-(a)+(1<<((shift)-1)))>>(shift)))

/*
 * The following are the matrix and rect datatypes, formerly located
 * in the simulator matrix toolkit code.
 *
 */

typedef struct {
I32 NumRows;/* dimensions of matrix (data - cf pitch) */
I32 NumCols;
I32 Pitch;/* spacing between rows; >= NumCols */
PI16 pi16;
} MatrixSt, *PMatrixSt;

typedef const MatrixSt *PCMatrixSt;

typedef struct {
I32 r;/* row, column of upper left-hand corner */
I32 c;
I32 h;/* height, width */
I32 w;
} RectSt, *PRectSt, *pRectSt;

typedef struct _Point {
U32 r, c;
} PointSt, *pPointSt;

typedef const RectSt *PCRectSt;

#endif /* __DATATYPE_H__ */


INDEO.CPP

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// Indeo.cpp : implementation file
//

#include "stdafx.h"
#include "IPlay.h"
#include "Indeo.h"
#include "IPlayDoc.h"
#include "Ax_Spec.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

const int BCS_MAX = 128;// used by trackbar controls
const int BCS_TICS = 16;

/////////////////////////////////////////////////////////////////////////////
// CIndeo

IMPLEMENT_DYNCREATE(CIndeo, CFormView)

CIndeo::CIndeo()
: CFormView(CIndeo::IDD)
{
//{{AFX_DATA_INIT(CIndeo)
m_bAccessKey = FALSE;
m_dwDecodeHeight = 0;
m_dwDecodeWidth = 0;
m_dwDecodeX = 0;
m_dwDecodeY = 0;
m_dwKeyValue = 0;
m_dwViewHeight = 0;
m_dwViewWidth = 0;
m_dwViewX = 0;
m_dwViewY = 0;
m_intBrightness = 0;
m_intContrast = 0;
m_intSaturation = 0;
m_dwDecodeTime = 0;
m_bAltLine = FALSE;
m_intGreen = 0;
m_intRed = 0;
m_intBlue = 0;
m_bDontDropFrames = FALSE;
m_bDontDropQuality = FALSE;
m_bViewOrigin = -1;
m_bTransFill = -1;
//}}AFX_DATA_INIT

m_dwInitTransRGBVal = 0;
m_bViewOrigin = 0;
}

CIndeo::~CIndeo()
{
}

void CIndeo::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CIndeo)
DDX_Control(pDX, IDC_CUSTOM_COLORS, m_btnCustomColors);
DDX_Control(pDX, IDC_DONT_DROP_QUALITY, m_checkDontDropQuality);
DDX_Control(pDX, IDC_DONT_DROP_FRAMES, m_checkDontDropFrames);
DDX_Control(pDX, IDC_SATURATION_TEXT, m_editSaturation);
DDX_Control(pDX, IDC_CONTRAST_TEXT, m_editContrast);
DDX_Control(pDX, IDC_BRIGHTNESS_TEXT, m_editBrightness);
DDX_Control(pDX, IDC_GREEN_EDIT, m_editTransFillGreen);
DDX_Control(pDX, IDC_BLUE_EDIT, m_editTransFillBlue);
DDX_Control(pDX, IDC_RED_EDIT, m_editTransFillRed);
DDX_Control(pDX, IDC_ALTLINE, m_checkAltLine);
DDX_Control(pDX, IDC_DECODE_TIME, m_editDecodeTime);
DDX_Control(pDX, IDC_VIEW_WIDTH, m_editViewWidth);
DDX_Control(pDX, IDC_VIEW_Y, m_editViewY);
DDX_Control(pDX, IDC_VIEW_X, m_editViewX);
DDX_Control(pDX, IDC_VIEW_HEIGHT, m_editViewHeight);
DDX_Control(pDX, IDC_KEY_VALUE, m_editKeyValue);
DDX_Control(pDX, IDC_DECODE_Y, m_editDecodeY);
DDX_Control(pDX, IDC_DECODE_X, m_editDecodeX);
DDX_Control(pDX, IDC_DECODE_WIDTH, m_editDecodeWidth);
DDX_Control(pDX, IDC_ACCESSKEY, m_checkAccessKey);
DDX_Control(pDX, ID_INDEO_DEFAULTS, m_btnDefaults);
DDX_Control(pDX, ID_INDEO_APPLY, m_btnApply);
DDX_Control(pDX, IDC_DECODE_HEIGHT, m_editDecodeHeight);
DDX_Control(pDX, IDC_TB_COLORCONTROL_SATURATION, m_tbSaturation);
DDX_Control(pDX, IDC_TB_COLORCONTROL_CONTRAST, m_tbContrast);
DDX_Control(pDX, IDC_TB_COLORCONTROL_BRIGHTNESS, m_tbBrightness);
DDX_Check(pDX, IDC_ACCESSKEY, m_bAccessKey);
DDX_Text(pDX, IDC_DECODE_HEIGHT, m_dwDecodeHeight);
DDX_Text(pDX, IDC_DECODE_WIDTH, m_dwDecodeWidth);
DDX_Text(pDX, IDC_DECODE_X, m_dwDecodeX);
DDX_Text(pDX, IDC_DECODE_Y, m_dwDecodeY);
DDX_Text(pDX, IDC_KEY_VALUE, m_dwKeyValue);
DDX_Text(pDX, IDC_VIEW_HEIGHT, m_dwViewHeight);
DDX_Text(pDX, IDC_VIEW_WIDTH, m_dwViewWidth);
DDX_Text(pDX, IDC_VIEW_X, m_dwViewX);
DDX_Text(pDX, IDC_VIEW_Y, m_dwViewY);
DDX_Text(pDX, IDC_BRIGHTNESS_TEXT, m_intBrightness);
DDV_MinMaxInt(pDX, m_intBrightness, -128, 128);
DDX_Text(pDX, IDC_CONTRAST_TEXT, m_intContrast);
DDV_MinMaxInt(pDX, m_intContrast, -128, 128);
DDX_Text(pDX, IDC_SATURATION_TEXT, m_intSaturation);
DDV_MinMaxInt(pDX, m_intSaturation, -128, 128);
DDX_Text(pDX, IDC_DECODE_TIME, m_dwDecodeTime);
DDX_Control(pDX, IDC_TRANS_FILL, m_radTransFill);
DDX_Control(pDX, IDC_TRANS_NOFILL, m_radTransNoFill);
DDX_Check(pDX, IDC_ALTLINE, m_bAltLine);
DDX_Text(pDX, IDC_GREEN_EDIT, m_intGreen);
DDV_MinMaxInt(pDX, m_intGreen, 0, 255);
DDX_Text(pDX, IDC_RED_EDIT, m_intRed);
DDV_MinMaxInt(pDX, m_intRed, 0, 255);
DDX_Text(pDX, IDC_BLUE_EDIT, m_intBlue);
DDV_MinMaxInt(pDX, m_intBlue, 0, 255);
DDX_Check(pDX, IDC_DONT_DROP_FRAMES, m_bDontDropFrames);
DDX_Check(pDX, IDC_DONT_DROP_QUALITY, m_bDontDropQuality);
DDX_Control(pDX, IDC_FRAME_ORIGIN, m_radFrameOrigin);
DDX_Control(pDX, IDC_VIEW_ORIGIN, m_radViewOrigin);
DDX_Radio(pDX, IDC_FRAME_ORIGIN, m_bViewOrigin);
DDX_Radio(pDX, IDC_TRANS_NOFILL, m_bTransFill);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CIndeo, CFormView)
//{{AFX_MSG_MAP(CIndeo)
ON_BN_CLICKED(ID_INDEO_APPLY, OnIndeoApply)
ON_BN_CLICKED(ID_INDEO_DEFAULTS, OnIndeoDefaults)
ON_EN_CHANGE(IDC_DECODE_HEIGHT, OnChangeDecodeHeight)
ON_EN_CHANGE(IDC_DECODE_WIDTH, OnChangeDecodeWidth)
ON_EN_CHANGE(IDC_DECODE_X, OnChangeDecodeX)
ON_EN_CHANGE(IDC_DECODE_Y, OnChangeDecodeY)
ON_EN_CHANGE(IDC_VIEW_HEIGHT, OnChangeViewHeight)
ON_EN_CHANGE(IDC_VIEW_WIDTH, OnChangeViewWidth)
ON_EN_CHANGE(IDC_VIEW_X, OnChangeViewX)
ON_EN_CHANGE(IDC_VIEW_Y, OnChangeViewY)
ON_BN_CLICKED(IDC_ACCESSKEY, OnAccesskey)
ON_EN_CHANGE(IDC_DECODE_TIME, OnChangeDecodeTime)
ON_EN_CHANGE(IDC_KEY_VALUE, OnChangeKeyValue)
ON_BN_CLICKED(IDC_TRANS_FILL, OnTransFill)
ON_BN_CLICKED(IDC_TRANS_NOFILL, OnTransNofill)
ON_BN_CLICKED(IDC_ALTLINE, OnAltline)
ON_WM_HSCROLL()
ON_EN_CHANGE(IDC_GREEN_EDIT, OnChangeGreenEdit)
ON_EN_CHANGE(IDC_RED_EDIT, OnChangeRedEdit)
ON_EN_CHANGE(IDC_BLUE_EDIT, OnChangeBlueEdit)
ON_BN_CLICKED(IDC_DONT_DROP_FRAMES, OnDontDropFrames)
ON_BN_CLICKED(IDC_DONT_DROP_QUALITY, OnDontDropQuality)
ON_BN_CLICKED(IDC_VIEW_ORIGIN, OnViewOrigin)
ON_BN_CLICKED(IDC_FRAME_ORIGIN, OnFrameOrigin)
ON_BN_CLICKED(IDC_CUSTOM_COLORS, OnCustomColors)
ON_EN_UPDATE(IDC_BRIGHTNESS_TEXT, OnUpdateBSCText)
ON_WM_TIMER()
ON_EN_UPDATE(IDC_CONTRAST_TEXT, OnUpdateBSCText)
ON_EN_UPDATE(IDC_SATURATION_TEXT, OnUpdateBSCText)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CIndeo diagnostics

#ifdef _DEBUG
void CIndeo::AssertValid() const
{
CFormView::AssertValid();
}

void CIndeo::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CIndeo message handlers

void CIndeo::OnInitialUpdate()
{
CIPlayDoc *pDoc;

CFormView::OnInitialUpdate();

// Initialize trackbars
m_tbBrightness.SetRange(-BCS_MAX, BCS_MAX);
m_tbBrightness.SetTicFreq(BCS_TICS);

m_tbContrast.SetRange(-BCS_MAX, BCS_MAX);
m_tbContrast.SetTicFreq(BCS_TICS);

m_tbSaturation.SetRange(-BCS_MAX, BCS_MAX);
m_tbSaturation.SetTicFreq(BCS_TICS);

// Is the movie an Indeo movie?
pDoc = (CIPlayDoc *)GetDocument();
if ( pDoc->IsIndeo() ){
EnableAllOptions();

pDoc->GetBCS(m_intBrightness, m_intContrast, m_intSaturation);
m_tbBrightness.SetPos(m_intBrightness);
m_tbContrast.SetPos(m_intContrast);
m_tbSaturation.SetPos(m_intSaturation);

pDoc->GetTransFillRGB(m_intRed, m_intGreen, m_intBlue);

m_dwInitTransRGBVal = RGB(m_intRed, m_intGreen, m_intBlue);

pDoc->GetDecodeTime(m_dwDecodeTime);
pDoc->GetDecodeRect(m_dwDecodeX, m_dwDecodeY, m_dwDecodeWidth,
m_dwDecodeHeight);
pDoc->GetViewRect(m_dwViewX, m_dwViewY, m_dwViewWidth, m_dwViewHeight);

pDoc->GetSequenceOptions(m_bAltLine, m_bDontDropFrames, m_bDontDropQuality, m_bTransFill, m_bAccessKey,
m_dwKeyValue);

// Update dialog controls from member variables
UpdateData(FALSE);
}
else {
if (pDoc->IsInitialized())
MessageBox("This is not an IV41 movie so Indeo options will not be available.",
"Not an Indeo Movie", MB_OK);
DisableAllOptions();
}

}

void CIndeo::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
CIPlayDoc *pDoc;

pDoc = (CIPlayDoc *)GetDocument();

// Sequence options can only be changed when the movie
// is stopped (they can actually be changed any time, but
// will not take effect until the movie is stoppped and
// restarted).
if (pDoc->IsIndeo()) {
if (pDoc->m_State == pDoc->Stopped)
EnableSeqOptions();
else
DisableSeqOptions();
}
}

void CIndeo::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
int origBrightness = m_intBrightness;
int origContrast = m_intContrast;
int origSaturation = m_intSaturation;

if ( ! pScrollBar){
CFormView::OnHScroll( nSBCode, nPos, pScrollBar );
return;
}

switch (nSBCode) {
case TB_LINEUP:

 if (pScrollBar->m_hWnd == m_tbBrightness.m_hWnd) {
if (m_intBrightness > -128)
m_intBrightness--;
}
else if (pScrollBar->m_hWnd == m_tbContrast.m_hWnd) {
if (m_intContrast > -128)
m_intContrast--;
}
else if (pScrollBar->m_hWnd == m_tbSaturation.m_hWnd) {
if (m_intSaturation > -128)
m_intSaturation--;
}
break;

case TB_LINEDOWN:

 if (pScrollBar->m_hWnd == m_tbBrightness.m_hWnd) {
if (m_intBrightness < 128)
m_intBrightness++;
}
else if (pScrollBar->m_hWnd == m_tbContrast.m_hWnd){
if (m_intContrast < 128)
m_intContrast++;
}
else if (pScrollBar->m_hWnd == m_tbSaturation.m_hWnd) {
if (m_intSaturation < 128)
m_intSaturation++;
}
break;

case TB_THUMBPOSITION:
case TB_THUMBTRACK:

if (pScrollBar->m_hWnd == m_tbBrightness.m_hWnd)
m_intBrightness = (int)nPos;

else if (pScrollBar->m_hWnd == m_tbContrast.m_hWnd)
m_intContrast = (int)nPos;

else if (pScrollBar->m_hWnd == m_tbSaturation.m_hWnd)
m_intSaturation = (int)nPos;
break;

case TB_PAGEUP:
case TB_PAGEDOWN:

if (pScrollBar->m_hWnd == m_tbBrightness.m_hWnd)
m_intBrightness = (int)m_tbBrightness.GetPos();

else if (pScrollBar->m_hWnd == m_tbContrast.m_hWnd)
m_intContrast = (int)m_tbContrast.GetPos();

else if (pScrollBar->m_hWnd == m_tbSaturation.m_hWnd)
m_intSaturation = (int)m_tbSaturation.GetPos();
break;
}
if ((origBrightness != m_intBrightness)
|| (origContrast != m_intContrast)
|| (origSaturation != m_intSaturation))
   m_btnApply.EnableWindow(TRUE);

UpdateData(FALSE);

return;

}

void CIndeo::OnIndeoApply()
{
CIPlayDoc *pDoc;
CRect rd, rv;

LONG lScalTemp;

// Update member variables from dialog controls
UpdateData(TRUE);

pDoc = (CIPlayDoc *)GetDocument();

//Make sure the decode rect is within the video rect
if ( m_dwDecodeX + m_dwDecodeWidth > pDoc->VideoWidth() ) {
AfxMessageBox(IDS_DECODERECT_TOO_BIG, MB_ICONEXCLAMATION | MB_OK);
m_editDecodeWidth.SetFocus();
m_editDecodeWidth.SetSel((DWORD)((-1 << 16) & 0xFFFF0000));
return;
}

if ( m_dwDecodeY + m_dwDecodeHeight > pDoc->VideoHeight() ) {
AfxMessageBox(IDS_DECODERECT_TOO_BIG, MB_ICONEXCLAMATION | MB_OK);
m_editDecodeHeight.SetFocus();
m_editDecodeHeight.SetSel((DWORD)((-1 << 16) & 0xFFFF0000));
return;
}

// Make sure view rect is in decode rect and video rect; if not, return
rd = CRect(m_dwDecodeX,
          m_dwDecodeY,
          m_dwDecodeX + m_dwDecodeWidth,
          m_dwDecodeY + m_dwDecodeHeight);

rv = CRect(m_dwViewX,
           m_dwViewY,
   m_dwViewX + m_dwViewWidth,
   m_dwViewY + m_dwViewHeight);

if ( rv != CRect(0,0,0,0) ) {

if ( rd == CRect(0,0,0,0) ) {
if ( (m_dwViewX + m_dwViewWidth > pDoc->VideoWidth()) ||
         (m_dwViewY + m_dwViewHeight > pDoc->VideoHeight()) ) {
AfxMessageBox(IDS_VIEWRECT_TOO_BIG, MB_ICONEXCLAMATION | MB_OK);
m_editViewX.SetFocus();
m_editViewX.SetSel((DWORD)((-1 << 16) & 0xFFFF0000));
return;
}
}
/*else if ( rd & rv != rv ) {
AfxMessageBox(IDS_VIEWRECT_TOO_BIG, MB_ICONEXCLAMATION | MB_OK);
m_editViewX.SetFocus();
m_editViewX.SetSel((DWORD)((-1 << 16) & 0xFFFF0000));
return;
} */

}

pDoc->SetTransFillRGB( RGB(m_intBlue, m_intGreen, m_intRed) );

pDoc->SetDecodeTime(m_dwDecodeTime);

pDoc->SetDecodeRect(m_dwDecodeX, m_dwDecodeY, m_dwDecodeWidth,
            m_dwDecodeHeight);

pDoc->SetViewRect(m_bViewOrigin, m_dwViewX, m_dwViewY, m_dwViewWidth, m_dwViewHeight);

pDoc->SetBCS(m_intBrightness, m_intContrast, m_intSaturation);

if (!pDoc->CanStop()) // movie is Stopped
{
if (m_bDontDropFrames && ! m_bDontDropQuality)
lScalTemp = SC_DONT_DROP_FRAMES;
else if (m_bDontDropQuality && ! m_bDontDropFrames)
lScalTemp = SC_DONT_DROP_QUALITY;
else if (m_bDontDropFrames && m_bDontDropQuality)
lScalTemp = SC_OFF;
else
lScalTemp = SC_ON;

pDoc->SetSequenceOptions(m_bAltLine, lScalTemp, m_bTransFill, m_bAccessKey,
m_dwKeyValue);
}

// Disable Apply button until there is something to apply
   m_btnApply.EnableWindow(FALSE);
}

void CIndeo::OnIndeoDefaults()
{
CIPlayDoc *pDoc;

pDoc = (CIPlayDoc *)GetDocument();

pDoc->GetFrameDefaults(m_intBrightness, m_intContrast, m_intSaturation, m_dwDecodeTime, m_dwDecodeX,
   m_dwDecodeY, m_dwDecodeWidth, m_dwDecodeHeight, m_dwViewX, m_dwViewY, m_dwViewWidth,
   m_dwViewHeight);

//Get the Initial color of the Transparency Fill Color
m_intRed   = (m_dwInitTransRGBVal&0x000000FF);
m_intGreen = (m_dwInitTransRGBVal&0x0000FF00)>>8;
m_intBlue  = (m_dwInitTransRGBVal&0x00FF0000)>>16;

m_bViewOrigin = FALSE;

if (!pDoc->CanStop())  // movie is Stopped
pDoc->GetSeqDefaults(m_bAltLine, m_bDontDropFrames, m_bDontDropQuality, m_bTransFill, m_bAccessKey,
m_dwKeyValue);

// Update trackbars
pDoc->SetBCS(m_intBrightness, m_intContrast, m_intSaturation);
m_tbBrightness.SetPos(m_intBrightness);
m_tbContrast.SetPos(m_intContrast);
m_tbSaturation.SetPos(m_intSaturation);

// Update visiblity of Access Key value
if (m_bAccessKey)
m_editKeyValue.ShowWindow(SW_SHOW);
else
m_editKeyValue.ShowWindow(SW_HIDE);

// Update dialog controls from member variables
UpdateData(FALSE);

OnIndeoApply();
}

void CIndeo::OnAccesskey()
{

UpdateData(TRUE);

if (m_bAccessKey) {
m_editKeyValue.ShowWindow(SW_SHOW);
m_editKeyValue.SetFocus();
m_editKeyValue.SetSel((DWORD)((-1 << 16) & 0xFFFF0000));
}
else
m_editKeyValue.ShowWindow(SW_HIDE);

m_btnApply.EnableWindow(TRUE);

}

/////////////////////////////////////////////////////////////////////////////
// CIndeo helper functions

void CIndeo::EnableAllOptions()
{
m_editBrightness.EnableWindow(TRUE);
m_editContrast.EnableWindow(TRUE);
m_editSaturation.EnableWindow(TRUE);

m_tbBrightness.EnableWindow(TRUE);
m_tbContrast.EnableWindow(TRUE);
m_tbSaturation.EnableWindow(TRUE);

m_editDecodeTime.EnableWindow(TRUE);
m_editDecodeX.EnableWindow(TRUE);
m_editDecodeY.EnableWindow(TRUE);
m_editDecodeHeight.EnableWindow(TRUE);
m_editDecodeWidth.EnableWindow(TRUE);

m_editViewX.EnableWindow(TRUE);
m_editViewY.EnableWindow(TRUE);
m_editViewHeight.EnableWindow(TRUE);
m_editViewWidth.EnableWindow(TRUE);

m_editTransFillRed.EnableWindow(TRUE);
m_editTransFillGreen.EnableWindow(TRUE);
m_editTransFillBlue.EnableWindow(TRUE);
m_btnCustomColors.EnableWindow(TRUE);

m_radFrameOrigin.EnableWindow(TRUE);
m_radViewOrigin.EnableWindow(TRUE);

m_btnDefaults.EnableWindow(TRUE);

// Apply button will not be enabled until something changes

EnableSeqOptions();
}

void CIndeo::DisableAllOptions()
{

m_editBrightness.EnableWindow(FALSE);
m_editContrast.EnableWindow(FALSE);
m_editSaturation.EnableWindow(FALSE);

m_tbBrightness.EnableWindow(FALSE);
m_tbContrast.EnableWindow(FALSE);
m_tbSaturation.EnableWindow(FALSE);

m_editDecodeTime.EnableWindow(FALSE);
m_editDecodeX.EnableWindow(FALSE);
m_editDecodeY.EnableWindow(FALSE);
m_editDecodeHeight.EnableWindow(FALSE);
m_editDecodeWidth.EnableWindow(FALSE);

m_editViewX.EnableWindow(FALSE);
m_editViewY.EnableWindow(FALSE);
m_editViewHeight.EnableWindow(FALSE);
m_editViewWidth.EnableWindow(FALSE);

m_editTransFillRed.EnableWindow(FALSE);
m_editTransFillGreen.EnableWindow(FALSE);
m_editTransFillBlue.EnableWindow(FALSE);
m_btnCustomColors.EnableWindow(FALSE);

m_radFrameOrigin.EnableWindow(FALSE);
m_radViewOrigin.EnableWindow(FALSE);

m_btnDefaults.EnableWindow(FALSE);
m_btnApply.EnableWindow(FALSE);

DisableSeqOptions();
}

void CIndeo::EnableSeqOptions()
{
m_checkAltLine.EnableWindow(TRUE);

m_checkDontDropFrames.EnableWindow(TRUE);
m_checkDontDropQuality.EnableWindow(TRUE);

m_checkAccessKey.EnableWindow(TRUE);
m_editKeyValue.EnableWindow(TRUE);

m_radTransFill.EnableWindow(TRUE);
m_radTransNoFill.EnableWindow(TRUE);

if (m_bAccessKey)
m_editKeyValue.ShowWindow(SW_SHOW);
else
m_editKeyValue.ShowWindow(SW_HIDE);
}

void CIndeo::DisableSeqOptions()
{
m_checkAltLine.EnableWindow(FALSE);

m_checkDontDropFrames.EnableWindow(FALSE);
m_checkDontDropQuality.EnableWindow(FALSE);

m_checkAccessKey.EnableWindow(FALSE);
m_editKeyValue.EnableWindow(FALSE);
m_radTransFill.EnableWindow(FALSE);
m_radTransNoFill.EnableWindow(FALSE);
m_editKeyValue.EnableWindow(FALSE);

if (m_bAccessKey)
m_editKeyValue.ShowWindow(SW_SHOW);
else
m_editKeyValue.ShowWindow(SW_HIDE);
}

/////////////////////////////////////////////////////////////////////
// The following message handlers simply enable the Apply button when
// something changes.

void CIndeo::OnChangeDecodeHeight()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeDecodeWidth()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeDecodeX()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeDecodeY()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeViewHeight()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeViewWidth()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeViewX()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeViewY()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeDecodeTime()
{
   m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeKeyValue()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnUpdateBSCText()
{
UpdateData(TRUE);
m_btnApply.EnableWindow(TRUE);
m_tbBrightness.SetPos(m_intBrightness);
m_tbContrast.SetPos(m_intContrast);
m_tbSaturation.SetPos(m_intSaturation);
}

void CIndeo::OnTransFill()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnTransNofill()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnAltline()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeRedEdit()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeGreenEdit()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnChangeBlueEdit()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnDontDropFrames()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnDontDropQuality()
{
m_btnApply.EnableWindow(TRUE);
}

void CIndeo::OnViewOrigin()
{
m_btnApply.EnableWindow(TRUE);
OnIndeoApply();
}

void CIndeo::OnFrameOrigin()
{
m_btnApply.EnableWindow(TRUE);
OnIndeoApply();
}

void CIndeo::OnCustomColors()
{

CHOOSECOLOR TransCC;     // common dialog box structure

TransCC.lStructSize = sizeof(CHOOSECOLOR);
TransCC.hwndOwner = NULL;
TransCC.lpCustColors = (LPDWORD) m_clrefUserCustClrs;
TransCC.rgbResult = RGB(m_intRed, m_intGreen, m_intBlue);
TransCC.Flags = CC_RGBINIT | CC_FULLOPEN;

if (ChooseColor(&TransCC)) // pointer to structure with initialization data
{
SetDlgItemInt( IDC_RED_EDIT, (TransCC.rgbResult&0x000000FF), FALSE );
SetDlgItemInt( IDC_GREEN_EDIT, (TransCC.rgbResult&0x0000FF00)>>8, FALSE );
SetDlgItemInt( IDC_BLUE_EDIT, (TransCC.rgbResult&0x00FF0000)>>16, FALSE );

OnIndeoApply();
}

}


INDEO.H

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// indeo.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CIndeo form view

#ifndef __AFXEXT_H__
#include <afxext.h>
#endif

#include <afxcmn.h>  // for CSliderCtrl

class CIndeo : public CFormView
{
protected:
CIndeo();           // protected constructor used by dynamic creation
DECLARE_DYNCREATE(CIndeo)

DWORDm_dwInitTransRGBVal;
COLORREF m_clrefUserCustClrs[16];

// Form Data
public:
//{{AFX_DATA(CIndeo)
enum { IDD = IDD_INDEO };
CButtonm_btnCustomColors;
CButtonm_checkDontDropQuality;
CButtonm_checkDontDropFrames;
CEditm_editSaturation;
CEditm_editContrast;
CEditm_editBrightness;
CEditm_editTransFillGreen;
CEditm_editTransFillBlue;
CEditm_editTransFillRed;
CButtonm_checkAltLine;
CEditm_editDecodeTime;
CEditm_editViewWidth;
CEditm_editViewY;
CEditm_editViewX;
CEditm_editViewHeight;
CEditm_editKeyValue;
CEditm_editDecodeY;
CEditm_editDecodeX;
CEditm_editDecodeWidth;
CButtonm_checkAccessKey;
CButtonm_btnDefaults;
CButtonm_btnApply;
CEditm_editDecodeHeight;
CSliderCtrlm_tbSaturation;
CSliderCtrlm_tbContrast;
CSliderCtrlm_tbBrightness;
BOOLm_bAccessKey;
DWORDm_dwDecodeHeight;
DWORDm_dwDecodeWidth;
DWORDm_dwDecodeX;
DWORDm_dwDecodeY;
DWORDm_dwKeyValue;
DWORDm_dwViewHeight;
DWORDm_dwViewWidth;
DWORDm_dwViewX;
DWORDm_dwViewY;
intm_intBrightness;
intm_intContrast;
intm_intSaturation;
DWORDm_dwDecodeTime;
CButton m_radTransFill;
CButton m_radTransNoFill;
BOOLm_bAltLine;
intm_intGreen;
intm_intRed;
intm_intBlue;
BOOLm_bDontDropFrames;
BOOLm_bDontDropQuality;
CButton m_radFrameOrigin;
CButton m_radViewOrigin;
intm_bViewOrigin;
intm_bTransFill;
//}}AFX_DATA

// Attributes
public:

// Operations
public:
   void EnableSeqOptions();
void DisableSeqOptions();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CIndeo)
public:
virtual void OnInitialUpdate();
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
//}}AFX_VIRTUAL

// Implementation
protected:
virtual ~CIndeo();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

// Helper functions
void EnableAllOptions();
void DisableAllOptions();

// Generated message map functions
//{{AFX_MSG(CIndeo)
afx_msg void OnIndeoApply();
afx_msg void OnIndeoDefaults();
afx_msg void OnChangeDecodeHeight();
afx_msg void OnChangeDecodeWidth();
afx_msg void OnChangeDecodeX();
afx_msg void OnChangeDecodeY();
afx_msg void OnChangeViewHeight();
afx_msg void OnChangeViewWidth();
afx_msg void OnChangeViewX();
afx_msg void OnChangeViewY();
afx_msg void OnAccesskey();
afx_msg void OnChangeDecodeTime();
afx_msg void OnChangeKeyValue();
afx_msg void OnTransFill();
afx_msg void OnTransNofill();
afx_msg void OnAltline();
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnChangeGreenEdit();
afx_msg void OnChangeRedEdit();
afx_msg void OnChangeBlueEdit();
afx_msg void OnDontDropFrames();
afx_msg void OnDontDropQuality();
afx_msg void OnViewOrigin();
afx_msg void OnFrameOrigin();
afx_msg void OnCustomColors();
afx_msg void OnUpdateBSCText();
afx_msg void OnClickBSCColorcontrol(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


IPLAY.CPP

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// IPlay.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "IPlay.h"

#include "mainfrm.h"
#include "IPlaydoc.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CIPlayApp

BEGIN_MESSAGE_MAP(CIPlayApp, CWinApp)
//{{AFX_MSG_MAP(CIPlayApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CIPlayApp construction

CIPlayApp::CIPlayApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CIPlayApp object

CIPlayApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CIPlayApp initialization

BOOL CIPlayApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

Enable3dControls();

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

// Initialize the quartz library
    CoInitialize(NULL);

// Register the application's document templates.  Document templates
//  serve as the connection between documents, frame windows and views.

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CIPlayDoc),
RUNTIME_CLASS(CMainFrame),       // main SDI frame window
RUNTIME_CLASS(CIndeo));
AddDocTemplate(pDocTemplate);

// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes();

// simple command line parsing
if (m_lpCmdLine[0] == '\0')
{
// create a new (empty) document
OnFileNew();
}
else
{
// open an existing document
OpenDocumentFile(m_lpCmdLine);
}

// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();

return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// Implementation
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CIPlayApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

void CIPlayApp::OnDocumentCreated( CIPlayDoc *pIPlayDoc )
{
    // Single documents only
    // If you want to convert to an MDI you will need to hold a list
    // of all created documents and change CPlayerApp::Run to build
    // up a list of event handles to wait for.
    ASSERT( m_pIPlayDoc == NULL );
    m_pIPlayDoc = pIPlayDoc;

}

void CIPlayApp::OnDocumentDestroyed( CIPlayDoc *pIPlayDoc )
{
    // Single documents only
    ASSERT( m_pIPlayDoc == pIPlayDoc );
    m_pIPlayDoc = NULL;

}

int CIPlayApp::Run()
{   // Overridden to check for Graph events as well as messages

if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CPlayerApp::Run - quitting application.\n");
AfxPostQuitMessage(0);
}

    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
    HANDLE  ahObjects[1];               // handles that need to be waited on
    const int cObjects = 1;             // no of objects that we are waiting on

    // message loop lasts until we get a WM_QUIT message
    // upon which we shall return from the function
    while (TRUE) {

        // If we don't have an event handle then process idle
        // routines until a message arrives or until the idle routines
        // stop (when we block until a message arrives). The graph event
        // handle can only be created in response to a message
        if( (ahObjects[ 0 ] = m_pIPlayDoc->GetGraphEventHandle()) == NULL ){
    while (    bIdle
            && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
    {
    // call OnIdle while in bIdle state
    if (!OnIdle(lIdleCount++)){
    bIdle = FALSE;
                    WaitMessage();
                }
    }
        } else {
            // wait for any message sent or posted to this queue
            // or for a graph notification. If there is no message or event
            // and we are idling then we process the idle time routines
            DWORD result;

            result = MsgWaitForMultipleObjects( cObjects
                                              , ahObjects
                                              , FALSE
                                              , (bIdle ? 0 : INFINITE)
                                              , QS_ALLINPUT
                                              );
            if( result != (WAIT_OBJECT_0 + cObjects) ){
                // not a message...

                if( result == WAIT_OBJECT_0 )
                    m_pIPlayDoc->OnGraphNotify();

                else if( result == WAIT_TIMEOUT )
                    if(!OnIdle(lIdleCount++))
                        bIdle = FALSE;

                continue;
            }
        }

        // When here, we either have a message or no event handle
        // has been created yet.

        // read all of the messages in this next loop
        // removing each message as we read it
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();

// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}

} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));

    } // end of the always while-loop

}

/////////////////////////////////////////////////////////////////////////////
// CIPlayApp commands


IPLAY.H

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// IPlay.h : main header file for the IPLAY application
//

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CIPlayApp:
// See IPlay.cpp for the implementation of this class
//

class CIPlayDoc;  // defined in IPlayDoc.h

class CIPlayApp : public CWinApp
{
public:
CIPlayApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CIPlayApp)
public:
virtual BOOL InitInstance();
virtual int Run();
//}}AFX_VIRTUAL

// Notify when document created / destroyed
public:
    void OnDocumentCreated( CIPlayDoc *pIPlayDoc );
    void OnDocumentDestroyed( CIPlayDoc *pIPlayDoc );

// Point to our single document
protected:
    CIPlayDoc *m_pIPlayDoc;

// Implementation

//{{AFX_MSG(CIPlayApp)
afx_msg void OnAppAbout();
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


IPLAYDOC.CPP

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// IPlaydoc.cpp : implementation of the CIPlayDoc class
//

#include "stdafx.h"
#include "IPlay.h"
#include "IPlaydoc.h"

#include <initguid.h>       // OLE (Quartz) initialization

#include "ax_spec.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc

IMPLEMENT_DYNCREATE(CIPlayDoc, CDocument)

#define new DEBUG_NEW //Checking for mem leaks in debug mode

BEGIN_MESSAGE_MAP(CIPlayDoc, CDocument)
//{{AFX_MSG_MAP(CIPlayDoc)
ON_COMMAND(ID_MEDIA_LOOP, OnMediaLoop)
ON_COMMAND(ID_MEDIA_PAUSE, OnMediaPause)
ON_COMMAND(ID_MEDIA_ZOOMX2, OnMediaZoomx2)
ON_UPDATE_COMMAND_UI(ID_MEDIA_PAUSE, OnUpdateMediaPause)
ON_UPDATE_COMMAND_UI(ID_MEDIA_PLAY, OnUpdateMediaPlay)
ON_UPDATE_COMMAND_UI(ID_MEDIA_STOP, OnUpdateMediaStop)
ON_UPDATE_COMMAND_UI(ID_MEDIA_ZOOMX2, OnUpdateMediaZoomx2)
ON_UPDATE_COMMAND_UI(ID_MEDIA_LOOP, OnUpdateMediaLoop)
ON_COMMAND(ID_MEDIA_PLAY, OnMediaPlay)
ON_COMMAND(ID_MEDIA_STOP, OnMediaStop)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

R4_DEC_SEQ_DATA   m_r4SeqData;  // Indeo data structures
R4_DEC_FRAME_DATA m_r4FrameData;

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc construction/destruction

CIPlayDoc::CIPlayDoc()
{
m_pGraph      = NULL;
m_hGraphEvent = NULL;
m_pIndeo      = NULL;
m_lpstrPath[0] = '\0';
m_bLoop       = FALSE;
m_bZoom  = FALSE;
m_lWidth  = 0;
m_lHeight  = 0;
    m_State       = Uninitialized;

// Initialize the headers for the Indeo data structures
// This stuff never changes (except for dwFlags)
m_r4FrameData.dwSize = sizeof(R4_DEC_FRAME_DATA);
m_r4FrameData.dwFourCC = FOURCC_IV41;
m_r4FrameData.dwVersion = SPECIFIC_INTERFACE_VERSION;
m_r4FrameData.oeEnvironment = OE_32;
m_r4FrameData.dwFlags = 0;

m_r4SeqData.dwSize = sizeof(R4_DEC_SEQ_DATA);
m_r4SeqData.dwFourCC = FOURCC_IV41;
m_r4SeqData.dwVersion = SPECIFIC_INTERFACE_VERSION;
m_r4SeqData.oeEnvironment = OE_32;
m_r4SeqData.dwFlags = 0;

    ((CIPlayApp *) AfxGetApp())->OnDocumentCreated( this );
}

CIPlayDoc::~CIPlayDoc()
{
DeleteContents();

((CIPlayApp *) AfxGetApp())->OnDocumentDestroyed( this );

}

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc diagnostics

#ifdef _DEBUG
void CIPlayDoc::AssertValid() const
{
CDocument::AssertValid();
}

void CIPlayDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc Message Handlers

BOOL CIPlayDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
    WCHAR     wPath[MAX_PATH];

char szDrive[ _MAX_DRIVE ] ;
char szDir[ _MAX_DIR ];
char szFname[ _MAX_FNAME ];
char szExt[ _MAX_EXT ];

IBaseFilter  *pFilter;  // EnumFilters vars
IEnumFilters *pEnum;
ULONG        pcFetched;
HRESULT      hr;
//HWND        hVidWin;

void *pIF;  // generic interface to graph object

// Get rid of any previously opened graph.
    DeleteContents();

// Create a new filter graph object.
    if ( !CreateFilterGraph() ) {
        AfxMessageBox(IDS_CANT_INIT_QUARTZ);
        return FALSE;
    }

// Render the file using the new filter graph.
// This will add the necessary filters to the
// graph (which up til now was empty).
strcpy(m_lpstrPath, lpszPathName);
    MultiByteToWideChar( CP_ACP, 0, lpszPathName,
                         -1, wPath, MAX_PATH );

    hr = m_pGraph->QueryInterface(IID_IGraphBuilder, &pIF);

    if (FAILED( ((IGraphBuilder *)pIF)->RenderFile(wPath, NULL) )) {
        AfxMessageBox(IDS_CANT_RENDER_FILE);
        return FALSE;
    }
((IGraphBuilder *)pIF)->Release();

// Position and set the title of the video window.
_splitpath( lpszPathName, szDrive, szDir, szFname, szExt );
MultiByteToWideChar( CP_ACP, 0, CString(szFname) + szExt,-1,
 wPath, _MAX_FNAME + _MAX_EXT);

SetWindow((BSTR)wPath);

// Get and save the native video size -- used for
// zoom by 2.
hr = m_pGraph->QueryInterface(IID_IBasicVideo, &pIF);

    if( SUCCEEDED(hr) ){
((IBasicVideo *)pIF)->GetVideoSize(&m_lWidth, &m_lHeight);
((IBasicVideo *)pIF)->Release();
}

// Call the stop function to queue up the first
// frame of the clip.  Have to set the state first
// to Paused so we can transistion to Stopped.
m_State = Paused;
OnMediaStop();

// See if the Indeo codec filter is in the graph.
hr = m_pGraph->EnumFilters(&pEnum);

if (FAILED(hr))
return TRUE;

while ((hr = pEnum->Next(1, &pFilter, &pcFetched)) == S_OK)
{
hr = pFilter->QueryInterface(IID_IIndeoDecode, &m_pIndeo);
pFilter->Release();
if (hr == S_OK)
break;

}
// This interface needs to be released, even though
// it was not obtained in the usual way with QueryInterface.
pEnum->Release();

// Someday, the following line can replace the above section of code
// that enumerates the filters in the graph to find the
// IIndeoDecode interface, but apparently that someday is not today.

//hr = m_pGraph->QueryInterface(IID_IIndeoDecode, &m_pIndeo);

return TRUE;
}

void CIPlayDoc::OnGraphNotify(void) {
    IMediaEvent *pME;
    long lEventCode, lParam1, lParam2;

    ASSERT( m_hGraphEvent != NULL );

    if( SUCCEEDED(m_pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME))){
        if( SUCCEEDED(pME->GetEvent(&lEventCode, &lParam1, &lParam2, 0)) )  {

switch (lEventCode) {

            case EC_COMPLETE:
            OnMediaStop();
if (m_bLoop) OnMediaPlay();
break;
            case EC_USERABORT:
            case EC_ERRORABORT:
                OnMediaStop();
        }
}

    pME->Release();
}

    // Send a message so toolbar gets updated
    PostMessage( AfxGetMainWnd()->m_hWnd, WM_USER, 0, 0 );

}

void CIPlayDoc::OnMediaLoop()
{
m_bLoop = !m_bLoop;

}

void CIPlayDoc::OnMediaPause()
{
if( CanPause() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to pause and release the interface
            hr = pMC->Pause();
            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State = Paused;
                return;
            }
        }
}

}

void CIPlayDoc::OnMediaPlay()
{
    if( CanPlay() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to play and release the interface
            hr = pMC->Run();
            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State=Playing;
// Let Indeo view object know the state changed
UpdateAllViews(NULL);
                return;
            }
        }
}

}

void CIPlayDoc::OnMediaStop()
{
if( CanStop() ){
        HRESULThr;
        IMediaControl *pMC;
IMediaPosition *pMP;
OAFilterState fstate;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to stop and release the interface

// put graph in stopped state
            hr = pMC->Stop();

// Rewind the stream
hr = m_pGraph->QueryInterface(IID_IMediaPosition, (void **) &pMP);
hr = pMP->put_CurrentPosition(0);
hr = pMP->Release();

// Flush the graph
hr = pMC->Pause();

// Wait for state transition to complete
hr = pMC->GetState(5000, &fstate);

// Now back to stopped state
hr = pMC->Stop();

            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State = Stopped;
// Let Indeo view object know the state changed
UpdateAllViews(NULL);
                return;
            }
        }
}

}

void CIPlayDoc::OnMediaZoomx2()
{
// This function doubles the width & height of the original video
// size -- depending on the hardware, this may result in a
// better quality video than a random stretch of the video window.
// Subsequent calls toggle the video size between the original size
// and the "x2" size, ignoring the fact that the user may have
// resized the window using the window frame.  The function attempts to
// keep the video window's center in place (zooms/unzooms from top,
// bottom, right and left).

HRESULT hr;
void *pIF;
long x,y,width,height,width2,height2;

m_bZoom = !m_bZoom;

hr = m_pGraph->QueryInterface(IID_IVideoWindow, &pIF);

    if( SUCCEEDED(hr) ){
if (m_bZoom) {
// Set the width & height to twice the native video size
// Allow for the window frame -- we really want to just
// double the size of the client area.
width2 = (m_lWidth * 2) + (m_lWinWidth - m_lWidth);
height2 = (m_lHeight * 2) + (m_lWinHeight - m_lHeight);
((IVideoWindow *)pIF)->GetWindowPosition(&x, &y, &width, &height);
((IVideoWindow *)pIF)->SetWindowPosition(x + (width - width2)/2,
y + (height - height2)/2, width2, height2);
}
else {
// Restore original size
((IVideoWindow *)pIF)->GetWindowPosition(&x, &y, &width, &height);
((IVideoWindow *)pIF)->SetWindowPosition(x +
(width - m_lWinWidth)/2, y + (height - m_lWinHeight)/2,
m_lWinWidth, m_lWinHeight);
}

((IVideoWindow *)pIF)->Release();

}
}

void CIPlayDoc::OnUpdateMediaPause(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanPause() );

}

void CIPlayDoc::OnUpdateMediaPlay(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanPlay() );

}

void CIPlayDoc::OnUpdateMediaStop(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanStop() );

}

void CIPlayDoc::OnUpdateMediaLoop(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( IsInitialized() );
pCmdUI->SetCheck( m_bLoop );

}

void CIPlayDoc::OnUpdateMediaZoomx2(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( IsInitialized() );
pCmdUI->SetCheck( m_bZoom );

}

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc Commands

BOOL CIPlayDoc::IsIndeo()
{
return (m_pIndeo != NULL);
}

void CIPlayDoc::GetTransFillRGB(int& red, int& green, int& blue)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;

m_r4FrameData.dwFlags = DECFRAME_FILL_COLOR |
DECFRAME_VALID;

m_r4FrameData.dwFillColor = DECFRAME_FILL_UNDEFINED;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_FRAME_DATA);
return;
}

if (m_r4FrameData.dwFillColor & DECFRAME_FILL_UNDEFINED) {
red   = 0;
green =0;
blue  =0;
} else { // Fill color is valid
red   = (m_r4FrameData.dwFillColor&0x00FF0000)>>16;
green =(m_r4FrameData.dwFillColor&0x0000FF00)>>8;
blue  =(m_r4FrameData.dwFillColor&0x000000FF);
}

return;
}

void CIPlayDoc::GetBCS(int& b, int& c, int& s)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_BRIGHTNESS |
DECFRAME_CONTRAST |
DECFRAME_SATURATION |
DECFRAME_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_FRAME_DATA);
return;
}

b = LOWORD(m_r4FrameData.lBrightness);
c = LOWORD(m_r4FrameData.lContrast);
s = LOWORD(m_r4FrameData.lSaturation);

return;
}

void CIPlayDoc::GetDecodeTime(DWORD& time)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_TIME_LIMIT | DECFRAME_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);
return;
}

time = m_r4FrameData.dwTimeLimit;

return;
}

void CIPlayDoc::GetDecodeRect(DWORD& x, DWORD& y, DWORD& width, DWORD& height)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_DECODE_RECT | DECFRAME_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_FRAME_DATA);
return;
}

x = m_r4FrameData.rDecodeRect.dwX;
y = m_r4FrameData.rDecodeRect.dwY;
width = m_r4FrameData.rDecodeRect.dwWidth;
height = m_r4FrameData.rDecodeRect.dwHeight;

return;
}

void CIPlayDoc::GetViewRect(DWORD& x, DWORD& y, DWORD& width, DWORD& height)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_VIEW_RECT | DECFRAME_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_FRAME_DATA);
return;
}

x = m_r4FrameData.rViewRect.dwX;
y = m_r4FrameData.rViewRect.dwY;
width = m_r4FrameData.rViewRect.dwWidth;
height = m_r4FrameData.rViewRect.dwHeight;

return;
}

void CIPlayDoc::GetSequenceOptions(BOOL& altline, BOOL& ddframes, BOOL& ddquality, BOOL& trans, BOOL& usekey,
DWORD& key)
{
HRESULT hr;

m_r4SeqData.mtType = MT_DECODE_SEQ_VALUE;
m_r4SeqData.dwFlags =DECSEQ_KEY |
DECSEQ_SCALABILITY |
DECSEQ_FILL_TRANSPARENT |
DECSEQ_ALT_LINE |
DECSEQ_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeSequence(&m_r4SeqData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_SEQ_DATA);
return;
}

altline = m_r4SeqData.fAltLine;

switch (m_r4SeqData.eScalability)
{
case SC_ON:
ddframes = ddquality = FALSE;
break;
case SC_OFF:
ddframes = ddquality = TRUE;
break;
case SC_DONT_DROP_FRAMES:
ddframes = TRUE;
ddquality = FALSE;
break;
case SC_DONT_DROP_QUALITY:
ddframes = FALSE;
ddquality = TRUE;
break;
}

trans = m_r4SeqData.fFillTransparentPixels;
usekey = m_r4SeqData.fEnabledKey;
key = m_r4SeqData.dwKey;

return;
}

void CIPlayDoc::SetTransFillRGB(DWORD rgb)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_VALID | DECFRAME_FILL_COLOR;

m_r4FrameData.dwFillColor = rgb;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeFrame(&m_r4FrameData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);

return;
}

void CIPlayDoc::SetBCS(int b, int c, int s)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_BRIGHTNESS |
DECFRAME_CONTRAST |
DECFRAME_SATURATION |
DECFRAME_VALID;

m_r4FrameData.lBrightness = b;
m_r4FrameData.lContrast = c;
m_r4FrameData.lSaturation = s;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeFrame(&m_r4FrameData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);

return;
}

void CIPlayDoc::SetDecodeTime(DWORD time)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_TIME_LIMIT | DECFRAME_VALID;

m_r4FrameData.dwTimeLimit = time;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeFrame(&m_r4FrameData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);

return;
}

void CIPlayDoc::SetDecodeRect(DWORD x, DWORD y, DWORD width, DWORD height)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;

m_r4FrameData.dwFlags = DECFRAME_DECODE_RECT |
DECFRAME_VALID;

m_r4FrameData.rDecodeRect.dwX = x;
m_r4FrameData.rDecodeRect.dwY = y;
m_r4FrameData.rDecodeRect.dwWidth = width;
m_r4FrameData.rDecodeRect.dwHeight = height;

m_r4FrameData.rDecodeRect.dwX = x;
m_r4FrameData.rDecodeRect.dwY = y;
m_r4FrameData.rDecodeRect.dwWidth = width;
m_r4FrameData.rDecodeRect.dwHeight = height;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeFrame(&m_r4FrameData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);

return;
}

void CIPlayDoc::SetViewRect(BOOL view ,DWORD x, DWORD y, DWORD width, DWORD height)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_VALUE;
m_r4FrameData.dwFlags = DECFRAME_VIEW_RECT |
DECFRAME_VIEW_RECT_ORIGIN |
DECFRAME_VALID;

m_r4FrameData.fViewRectOrigin = view;

m_r4FrameData.rViewRect.dwX = x;
m_r4FrameData.rViewRect.dwY = y;
m_r4FrameData.rViewRect.dwWidth = width;
m_r4FrameData.rViewRect.dwHeight = height;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeFrame(&m_r4FrameData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_FRAME_DATA);

return;
}

void CIPlayDoc::SetSequenceOptions(BOOL altline, LONG scal, BOOL fill, BOOL usekey, DWORD key)
{
HRESULT hr;

m_r4SeqData.mtType = MT_DECODE_SEQ_VALUE;
m_r4SeqData.dwFlags =DECSEQ_KEY |
DECSEQ_SCALABILITY |
DECSEQ_FILL_TRANSPARENT |
DECSEQ_ALT_LINE |
DECSEQ_VALID;

m_r4SeqData.fAltLine = altline;
m_r4SeqData.eScalability = scal;
m_r4SeqData.fFillTransparentPixels = fill;
m_r4SeqData.fEnabledKey = usekey;
m_r4SeqData.dwKey = key;

hr = ((IIndeoDecode *)m_pIndeo)->set_DecodeSequence(&m_r4SeqData);

if (FAILED( hr ))
        AfxMessageBox(IDS_CANT_SET_SEQ_DATA);

return;
}

void CIPlayDoc::GetFrameDefaults(int& b, int& c, int& s, DWORD& time,
  DWORD& dx, DWORD& dy, DWORD& dWidth, DWORD& dHeight,
  DWORD& vx, DWORD& vy, DWORD& vWidth, DWORD& vHeight)
{
HRESULT hr;

m_r4FrameData.mtType = MT_DECODE_FRAME_DEFAULT;
m_r4FrameData.dwFlags = DECFRAME_BRIGHTNESS |
DECFRAME_CONTRAST |
DECFRAME_SATURATION |
DECFRAME_TIME_LIMIT |
DECFRAME_DECODE_RECT |
DECFRAME_VIEW_RECT |
DECFRAME_VIEW_RECT_ORIGIN |
DECFRAME_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeFrame(&m_r4FrameData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_FRAME_DATA);
return;
}

b = LOWORD(m_r4FrameData.lBrightness);
c = LOWORD(m_r4FrameData.lContrast);
s = LOWORD(m_r4FrameData.lSaturation);

time = m_r4FrameData.dwTimeLimit;

dx = m_r4FrameData.rDecodeRect.dwX;
dy = m_r4FrameData.rDecodeRect.dwY;
dWidth = m_r4FrameData.rDecodeRect.dwWidth;
dHeight = m_r4FrameData.rDecodeRect.dwHeight;

vx = m_r4FrameData.rViewRect.dwX;
vy = m_r4FrameData.rViewRect.dwY;
vWidth = m_r4FrameData.rViewRect.dwWidth;
vHeight = m_r4FrameData.rViewRect.dwHeight;

return;
}

void CIPlayDoc::GetSeqDefaults(BOOL& altline, BOOL& ddframes, BOOL& ddquality, BOOL& trans, BOOL& usekey,
DWORD& key)
{
HRESULT hr;

m_r4SeqData.mtType = MT_DECODE_SEQ_DEFAULT;
m_r4SeqData.dwFlags =DECSEQ_KEY |
DECSEQ_SCALABILITY |
DECSEQ_FILL_TRANSPARENT |
DECSEQ_ALT_LINE |
DECSEQ_VALID;

hr = ((IIndeoDecode *)m_pIndeo)->get_DecodeSequence(&m_r4SeqData);

if (FAILED( hr )) {
        AfxMessageBox(IDS_CANT_GET_SEQ_DATA);
return;
}

altline = m_r4SeqData.fAltLine;

switch (m_r4SeqData.eScalability)
{
case SC_ON:
ddframes = ddquality = FALSE;
break;
case SC_OFF:
ddframes = ddquality = TRUE;
break;
case SC_DONT_DROP_FRAMES:
ddframes = TRUE;
ddquality = FALSE;
break;
case SC_DONT_DROP_QUALITY:
ddframes = FALSE;
ddquality = TRUE;
break;
}

trans = m_r4SeqData.fFillTransparentPixels;
usekey = m_r4SeqData.fEnabledKey;
key = m_r4SeqData.dwKey;
}

ULONG CIPlayDoc::VideoWidth()
{
HRESULT hr;
IBasicVideo *pIF;
long width;

    hr = m_pGraph->QueryInterface(IID_IBasicVideo, (void **)&pIF);

    if( FAILED(hr) ) {
AfxMessageBox(IDS_CANT_GET_VIDEO_INTERFACE);
return 0;
}

pIF->get_DestinationWidth(&width);
pIF->Release();

return width;

}

ULONG CIPlayDoc::VideoHeight()
{
HRESULT hr;
IBasicVideo *pIF;
long height;

    hr = m_pGraph->QueryInterface(IID_IBasicVideo, (void **)&pIF);

    if( FAILED(hr) ) {
AfxMessageBox(IDS_CANT_GET_VIDEO_INTERFACE);
return 0;
}

pIF->get_DestinationHeight(&height);
pIF->Release();

return height;

}

/////////////////////////////////////////////////////////////////////////////
// CIPlayDoc protected funtions

BOOL CIPlayDoc::CreateFilterGraph(void) {
    HRESULT hr;// return code

    ASSERT(m_pGraph == NULL);

// Create this document's filter graph object
    hr = CoCreateInstance(CLSID_FilterGraph,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IFilterGraph,
                          (void **) &m_pGraph);

    if (FAILED(hr)){
    m_pGraph = NULL;
        return FALSE;
    }

    // get media event handle
    IMediaEvent *pME;
    hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME);
    if (FAILED(hr)) {
        DeleteContents();
        return FALSE;
    }

    hr = pME->GetEventHandle((OAEVENT*) &m_hGraphEvent);

    pME->Release();

    if (FAILED(hr)) {
        DeleteContents();
        return FALSE;
    }
    return TRUE;
}

void CIPlayDoc::DeleteContents()
{
// Release all interfaces to graph
// and filters.

if (m_pIndeo != NULL) {
 ((IIndeoDecode *)m_pIndeo)->Release();
m_pIndeo = NULL;
}

    if (m_pGraph != NULL) {
        m_pGraph->Release();
        m_pGraph = NULL;
}

m_hGraphEvent = NULL;

    m_State = Uninitialized;
}

void CIPlayDoc::SetWindow(BSTR wPath)
{
HRESULT hr;
IVideoWindow *pVidWin;
RECT rDeskTop;
int x, y;

    hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin);

    if( FAILED(hr) ) {
AfxMessageBox(IDS_CANT_GET_WINDOW_INTERFACE);
return;
}

// Set the caption of the video window
pVidWin->put_Caption(wPath);

// Save the original size for zoom x 2
pVidWin->get_Width(&m_lWinWidth);
pVidWin->get_Height(&m_lWinHeight);

// Center the window on the screen
   GetWindowRect(GetDesktopWindow(), &rDeskTop);

    if (  m_lWinWidth < (rDeskTop.right - rDeskTop.left) )
   x = ((rDeskTop.right - rDeskTop.left) - m_lWinWidth) / 2;
else
x = rDeskTop.left;

if ( m_lWinHeight < (rDeskTop.bottom - rDeskTop.top) )
y = ((rDeskTop.bottom - rDeskTop.top) - m_lWinHeight) / 2;
else
y = rDeskTop.top;

pVidWin->SetWindowPosition(x, y, m_lWinWidth, m_lWinHeight);

pVidWin->Release();
}


IPLAYDOC.H

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 -1997 Intel corporation.All rights reserved.

// IPlaydoc.h : interface of the CIPlayDoc class
//
/////////////////////////////////////////////////////////////////////////////
class CIPlayDoc : public CDocument
{
protected:
CIPlayDoc();
DECLARE_DYNCREATE(CIPlayDoc)
void DeleteContents();

// Attributes
protected:
HANDLE       m_hGraphEvent;
long m_lWinWidth;   // original window size
long m_lWinHeight;
long         m_lWidth;      // encoded video size
long         m_lHeight;
BOOL         m_bLoop;
BOOL         m_bZoom;
void         *m_pIndeo;    // pointer to Indeo interface

public:
IFilterGraph *m_pGraph;
CHAR     m_lpstrPath[MAX_PATH];

    HANDLE GetGraphEventHandle( ) { return m_hGraphEvent; };
void OnGraphNotify();

    enum {Uninitialized, Stopped, Paused, Playing } m_State;
    BOOL CanPlay(){ return m_State==Stopped || m_State==Paused; };
    BOOL CanStop(){ return m_State==Playing || m_State==Paused; };
    BOOL CanPause(){return m_State==Playing; };
    BOOL IsInitialized(){ return m_State!=Uninitialized; };
ULONG VideoWidth();
ULONG VideoHeight();

// Operations
public:
BOOL IsIndeo();
void GetBCS(int&, int&, int& );
void GetTransFillRGB(int&, int&, int&);
void GetDecodeTime(DWORD& );
void GetDecodeRect(DWORD&, DWORD&, DWORD&, DWORD& );
void GetViewRect(DWORD&, DWORD&, DWORD&, DWORD& );
void GetSequenceOptions(BOOL&, BOOL&, BOOL&, BOOL&, BOOL&, DWORD& );
void SetBCS(int, int, int);
void SetTransFillRGB(DWORD);
void SetDecodeTime(DWORD);
void SetDecodeRect(DWORD, DWORD, DWORD, DWORD);
void SetViewRect(BOOL, DWORD, DWORD, DWORD, DWORD);
void SetSequenceOptions(BOOL, LONG, BOOL, BOOL, DWORD);
void GetFrameDefaults(int&, int&, int&, DWORD&,
 DWORD&, DWORD&, DWORD&, DWORD&,
 DWORD&, DWORD&, DWORD&, DWORD& );
void GetSeqDefaults(BOOL&, BOOL&, BOOL&, BOOL&, BOOL&, DWORD&);

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CIPlayDoc)
public:
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CIPlayDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
protected:
//{{AFX_MSG(CIPlayDoc)
afx_msg void OnMediaLoop();
afx_msg void OnMediaPause();
afx_msg void OnMediaZoomx2();
afx_msg void OnUpdateMediaPause(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaPlay(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaStop(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaZoomx2(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaLoop(CCmdUI* pCmdUI);
afx_msg void OnMediaPlay();
afx_msg void OnMediaStop();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

private:
void SetWindow(BSTR);
    BOOL CreateFilterGraph( void );

};


MAINFRM.CPP

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// mainfrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "IPlay.h"
#include "IPlayDoc.h"

#include "mainfrm.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(ID_MEDIA_INDEO, OnMediaIndeo)
ON_UPDATE_COMMAND_UI(ID_MEDIA_INDEO, OnUpdateMediaIndeo)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// arrays of IDs used to initialize control bars

// toolbar buttons - IDs are command buttons
static UINT BASED_CODE buttons[] =
{
// same order as in the bitmap 'toolbar.bmp'
ID_FILE_OPEN,
ID_SEPARATOR,
ID_MEDIA_PLAY,
ID_MEDIA_PAUSE,
ID_MEDIA_STOP,
ID_SEPARATOR,
ID_MEDIA_LOOP,
ID_MEDIA_ZOOMX2,
ID_SEPARATOR,
ID_SEPARATOR,
ID_SEPARATOR,
ID_MEDIA_INDEO
};

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
    EnableDocking(CBRS_FLOAT_MULTI);
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadBitmap(IDR_MAINFRAME) ||
!m_wndToolBar.SetButtons(buttons,
  sizeof(buttons)/sizeof(UINT)))
{
TRACE0("Failed to create toolbar\n");
return -1;      // fail to create
}

    m_wndToolBar.SetSizes( CSize( 40, 40 ), CSize( 32, 32 ) );
m_wndToolBar.EnableDocking(CBRS_ALIGN_TOP);

// Enable tool tips
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY);

// Position window in upper, left corner
    SetWindowPos( NULL, 0, 0, 424, 96, SWP_NOZORDER );
m_bIndeo = FALSE;
return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// Set optimum size for view
cs.cx = 422;
cs.cy = 524;

cs.style = WS_OVERLAPPED | WS_CAPTION |
           WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX;

return CFrameWnd::PreCreateWindow(cs);
}

void CMainFrame::OnMediaIndeo()
{
m_bIndeo = !m_bIndeo;
if (m_bIndeo)
SetWindowPos(NULL, 0, 0, 422, 524, SWP_NOMOVE | SWP_NOZORDER );
else
SetWindowPos(NULL, 0, 0, 422, 96, SWP_NOMOVE | SWP_NOZORDER );

}

void CMainFrame::OnUpdateMediaIndeo(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck( m_bIndeo );

}


MAINFRM.H

// This code and information is provided "as is" without warranty of
// any kind, either expressed or implied, including but not limited to
// the implied warranties of merchantability and/or fitness for a
// particular purpose.

// Copyright (C) 1996 - 1997 Intel corporation.  All rights reserved.

// mainfrm.h : interface of the CMainFrame class
//
/////////////////////////////////////////////////////////////////////////////
#include "indeo.h"
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
CToolBar    m_wndToolBar;
BOOLm_bIndeo;

// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnMediaIndeo();
afx_msg void OnUpdateMediaIndeo(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


AX_SPEC.H

/************************************************************************
*                                                                       *
*               INTEL CORPORATION PROPRIETARY INFORMATION               *
*                                                                       *
*    This listing is supplied under the terms of a license agreement    *
*      with INTEL Corporation and may not be copied nor disclosed       *
*        except in accordance with the terms of that agreement.         *
*                                                                       *
*************************************************************************
*                                                                       *
*Copyright (c) 1994-1997 Intel Corporation.*
*                         All Rights Reserved.                          *
*                                                                       *
************************************************************************/

/*
 *  Tabs set to 4
 *
 *  ax_spec.h
 *
 *  DESCRIPTION:
 *  The Indeo(R) Video Interactive codec provides access to new features
 *  using the ICM_SETCODECSTATE and ICM_GETCODECSTATE messages for VfW.
 *This header file defines the data structures used in these messages.
 */

/* $Header:   I:\proj\src\common\vcs\ax_spec.h_v   1.2 16 Dec 1996 14:54:04tschwart  $
 */

/*  Custom interface for Indeo(r) codecs
 */

#ifndef __AX_SPEC_H__
#define __AX_SPEC_H__

#ifdef __cplusplus
extern "C" {
#endif

#ifndef FOURCC_IV41

#ifndef mmioFOURCC
#define mmioFOURCC( ch0, ch1, ch2, ch3 )\
( (DWORD)(BYTE)(ch0) | ( (DWORD)(BYTE)(ch1) << 8 ) |\
( (DWORD)(BYTE)(ch2) << 16 ) | ( (DWORD)(BYTE)(ch3) << 24 ) )
#endif

#define FOURCC_IV41 mmioFOURCC('I','V','4','1')
#endif

#include "vfw_spec.h"

/* Temporary Encode parameters used to set up the compression information
 */

typedef struct {
DWORD dwFrameRate;
DWORD dwKeyFrameInterval;
DWORD dwDataRate;
DWORD dwQuality;
R4_FLAG fPadding;
} R4_ENC_CMP_DATA, FAR * PTR_R4_ENC_CMP_DATA;

/*AX_SPEC's GUID's
 *
 *{CCDA9131-CE8A-11CE-82DD-0800095A5B55}for Encode
 *{CCDA9132-CE8A-11CE-82DD-0800095A5B55}  for Decode
 *
 */

#define EC_KEY_FRAME (EC_USER + 1)
#define EC_DECODE_RECT_CHANGED (EC_USER + 2)

DEFINE_GUID(IID_IIndeoEncode,
0xCCDA9131, 0xCE8A, 0x11CE, 0x82, 0xDD, 0x08, 0x00, 0x09, 0x5A, 0x5B, 0x55);

/*AX_SPECinterface definition
*/

#undefINTERFACE
#defineINTERFACE IIndeoEncode

DECLARE_INTERFACE_(IIndeoEncode, IUnknown)
{

/*
 *IUnknown methods
 */

STDMETHOD(QueryInterface)
(
THIS_
REFIID riid,
LPVOID FAR* ppvObj
) PURE;

STDMETHOD_(ULONG,AddRef)
(
THIS
) PURE;

STDMETHOD_(ULONG,Release)
(
THIS
) PURE;

/*
 *Query methods
 */

    STDMETHOD(query_EncodeSequence)
(
THIS_
DWORD *pdwFlags /* [out] */ // get data pointer
) PURE;

STDMETHOD(query_ImageDimensions)
(
THIS_
DWORD *pdwWidth,/* [out] */ // get data pointer
DWORD *pdwHeight/* [out] */ // get data pointer
) PURE;

/*
 *Get methods
 */

    STDMETHOD(get_EncodeSequence)
(
THIS_
PTR_R4_ENC_SEQ_DATA pData/* [out] */ // get data pointer
) PURE;

    STDMETHOD(get_EncodePersistent)
(
THIS_
PTR_R4_ENC_SEQ_DATA pData/* [out] */ // get data pointer
) PURE;

    STDMETHOD(get_EncodeCompression)
(
THIS_
PTR_R4_ENC_CMP_DATA pData/* [out] */ // get data pointer
) PURE;

    STDMETHOD(get_EncodeCompressionDefault)
(
THIS_
PTR_R4_ENC_CMP_DATA pData/* [out] */ // get data pointer
) PURE;

/*
 *Set methods
 */

    STDMETHOD(set_EncodeSequence)
(
THIS_
PTR_R4_ENC_SEQ_DATA pData/* [in] */ // set data pointer
) PURE;

    STDMETHOD(set_EncodePersistent)
(
THIS_
PTR_R4_ENC_SEQ_DATA pData/* [in] */ // set data pointer
) PURE;

    STDMETHOD(set_EncodeCompression)
(
THIS_
PTR_R4_ENC_CMP_DATA pData/* [in] */ // set data pointer
) PURE;
};

DEFINE_GUID(IID_IIndeoDecode,
0xCCDA9132, 0xCE8A, 0x11CE, 0x82, 0xDD, 0x08, 0x00, 0x09, 0x5A, 0x5B, 0x55);

#undefINTERFACE
#defineINTERFACE IIndeoDecode

DECLARE_INTERFACE_(IIndeoDecode, IUnknown)
{

/*
 *IUnknown methods
 */

STDMETHOD(QueryInterface)
(
THIS_
REFIID riid,
LPVOID FAR* ppvObj
) PURE;

STDMETHOD_(ULONG,AddRef)
(
THIS
) PURE;

STDMETHOD_(ULONG,Release)
(
THIS
) PURE;

/*
 *Query methods
 */

    STDMETHOD(query_DecodeSequence)
(
THIS_
DWORD *pdwFlags/* [out] */ // query data pointer
) PURE;

    STDMETHOD(query_DecodeFrame)
(
THIS_
DWORD *pdwFlags/* [out] */ // query data pointer
) PURE;

STDMETHOD(query_ImageDimensions)
(
THIS_
DWORD *pdwWidth,/* [out] */ // get data pointer
DWORD *pdwHeight/* [out] */ // get data pointer
) PURE;

/*
 *Get methods
 */

    STDMETHOD(get_DecodeSequence)
(
THIS_
PTR_R4_DEC_SEQ_DATA pData /* [out] */ // get data pointer
) PURE;

    STDMETHOD(get_DecodeFrame)
(
THIS_
PTR_R4_DEC_FRAME_DATA pData/* [out] */ // get data pointer
) PURE;

/*
 *Set methods
 */

    STDMETHOD(set_DecodeSequence)
(
THIS_
PTR_R4_DEC_SEQ_DATA pData/* [in] */ // set data pointer
) PURE;

    STDMETHOD(set_DecodeFrame)
(
THIS_
PTR_R4_DEC_FRAME_DATA pData/* [in] */ // set data pointer
) PURE;

STDMETHOD(set_NotifyKeyFrame)
(
THIS_
DWORD bState/* [in] */ // Set notify on/off
) PURE;
};

#ifdef __cplusplus
}
#endif

#endif /* __AX_SPEC_H__ */


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by IPlay.rc
//
#define IDDEFAULT                       3
#define IDDEFAULT2                      7
#define IDD_ABOUTBOX                    100
#define IDD_COLORCONTROL                104
#define IDD_INDEO                       104
#define IDR_MAINFRAME                   128
#define IDR_IPLAYTYPE                   129
#define IDD_INFO                        134
#define IDC_CHECK1                      1000
#define IDC_SCALABILITY                 1000
#define IDC_DONT_DROP_FRAMES            1000
#define IDC_DECODE_X                    1001
#define IDC_TB_COLORCONTROL_BRIGHTNESS  1002
#define IDC_DECODE_Y                    1003
#define IDC_DECODE_WIDTH                1004
#define IDC_DECODE_HEIGHT               1005
#define IDC_TRANSPARENCY                1006
#define IDC_ALTLINE                     1006
#define IDC_ACCESSKEY                   1007
#define IDC_TB_COLORCONTROL_SATURATION  1008
#define IDC_TB_COLORCONTROL_CONTRAST    1009
#define ID_INDEO_APPLY                  1010
#define IDC_VIEW_X                      1011
#define IDC_VIEW_Y                      1012
#define IDC_VIEW_WIDTH                  1013
#define IDC_VIEW_HEIGHT                 1014
#define IDC_EDIT_KEY                    1015
#define IDC_DECODE_TIME                 1015
#define IDC_KEY_VALUE                   1016
#define IDC_FILENAME                    1016
#define ID_INDEO_DEFAULTS               1017
#define IDC_VIDSIZE                     1017
#define IDC_DURATION                    1018
#define IDC_TRANS_FILL                  1019
#define IDC_TEXT_COLORCONTROL_BRIGHTNESS 1020
#define IDC_BRIGHTNESS_TEXT             1020
#define IDC_TEXT_COLORCONTROL_SATURATION 1021
#define IDC_SATURATION_TEXT             1021
#define IDC_TEXT_COLORCONTROL_CONTRAST  1022
#define IDC_CONTRAST_TEXT               1022
#define IDC_TRANS_NOFILL                1023
#define IDC_TB_MSPERFRAME               1026
#define IDC_CB_PROCESSOR                1027
#define IDC_TEXT_MSPERFRAME             1028
#define IDC_RADIO_PROCESSOR             1029
#define IDC_RADIO_MSPERFRAME            1030
#define IDC_FRAME_ORIGIN                1030
#define IDC_TEXT_TITLE_PROCESSOR        1031
#define IDC_VIEW_ORIGIN                 1031
#define IDC_TEXT_TITLE_MSPERFRAME       1032
#define IDC_RED_EDIT                    1032
#define IDC_GREEN_EDIT                  1033
#define IDC_TEXT_MS00                   1034
#define IDC_BLUE_EDIT                   1034
#define IDC_TEXT_MS10                   1035
#define IDC_TEXT_MS01                   1036
#define IDC_TEXT_MS02                   1037
#define IDC_TEXT_MS03                   1038
#define IDC_TEXT_MS04                   1039
#define IDC_TEXT_MS05                   1040
#define IDC_TEXT_MS11                   1041
#define IDC_DONT_DROP_QUALITY           1041
#define IDC_TEXT_MS12                   1042
#define IDC_TEXT_MS13                   1043
#define IDC_CUSTOM_COLORS               1043
#define IDC_EDIT_PROCESSOR              1044
#define ID_MEDIA_PLAY                   32771
#define ID_MEDIA_PAUSE                  32772
#define ID_MEDIA_STOP                   32773
#define ID_MEDIA_LOOP                   32774
#define ID_MEDIA_ZOOMX2                 32775
#define ID_MEDIA_INFO                   32776
#define ID_INDEO_OPTIONS                32777
#define ID_MEDIA_INDEO                  32778
#define IDS_CANT_INIT_QUARTZ            61204
#define IDS_CANT_RENDER_FILE            61205
#define IDS_CANT_GET_FRAME_DATA         61206
#define IDS_CANT_GET_SEQ_DATA           61207
#define IDS_CANT_SET_FRAME_DATA         61208
#define IDS_CANT_SET_SEQ_DATA           61209
#define IDS_VIEWRECT_NOT_IN_DECODERECT  61210
#define IDS_VIEWRECT_TOO_BIG            61210
#define IDS_DECODERECT_TOO_BIG          61211
#define IDS_CANT_CREATE_TRANSP          61212
#define IDS_CANT_ADD_TRANSP             61213
#define IDS_CANT_GET_WINDOW_INTERFACE   61214
#define IDS_CANT_GET_VIDEO_INTERFACE    61215

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        136
#define _APS_NEXT_COMMAND_VALUE         32779
#define _APS_NEXT_CONTROL_VALUE         1044
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//IPlay.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"


STDAFX.H

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//
#define _AFX_NO_BSTR_SUPPORT
#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxcmn.h>         // MFC common controls

#include <objbase.h>// Common Object Model

#include <strmif.h>// Quartz
#include <control.h>
#include <evcode.h>
#include <uuids.h>


VFW_SPEC.H

/************************************************************************
*  vfw_spec.h                                                           *
*                                                                       *
* This code and information is provided "as is" without warranty of     *
* any kind, either expressed or implied, including but not limited to*
* the implied warranties of merchantability and/or fitness for a        *
* particular purpose.                                                   *
*                                                                       *
*Copyright (c) 1994-1997 Intel Corporation.*
*                         All Rights Reserved.                          *
*                                                                       *
************************************************************************/
/*
 *
 *  DESCRIPTION:
 *  The Indeo(R) Video Interactive codec provides access to new features
 *  using the ICM_SETCODECSTATE and ICM_GETCODECSTATE messages. This header
 *  file defines the data structures used in these messages.
 */

/* $Revision:   1.29  $
 */
/* Remove these three lines (23-25) before distributing publicly
 * $Header:   I:\proj\src\common\vcs\vfw_spec.h_v   1.29   16 Dec 1996 14:53:46   tschwart  $
 */

#ifndef __VFW_SPEC_H__
#define __VFW_SPEC_H__

/* The specific interface version is defined as follows:
 */
#define INTERFACE_MAJOR_NUMBER(v)((v) >> 16)
#define INTERFACE_MINOR_NUMBER(v)((v) & 0xffff)
#define CREATE_INTERFACE_VERSION(maj,min)((((DWORD)maj)<<16) | (((DWORD)min) & 0xffff))
#define SPECIFIC_INTERFACE_VERSION CREATE_INTERFACE_VERSION(1,3)

/* The codec specific information is handled by the general state
 * handler.
 */
#define ICM_GETCODECSTATEICM_GETSTATE
#define ICM_SETCODECSTATEICM_SETSTATE
#defineEXTENDED_INTERFACE_SIZE1

/* OS Environments - Within the Microsoft Windows domain (Win 3.1, Win 95,
 * and Win NT), both applications and codecs can be compiled in either 16 or
 * 32-bit formats.  Any combination is allowed with the knowledge that data
 * pointers will not be thunked.
 */
typedef enum {
OE_UNDEFINED,
OE_16,
OE_32,
} R4_OS_ENVIRONMENT;

/* The type field identifies the kind of operation that should be done as part
 * of the ICM_GETCODECSTATE and ICM_SETCODECSTATE messages.
 *
 * The MT_UNDEFINED value is not a supported type - it should only be used
 * to initialize variables to an "undefined" state.
 */
typedef enum {
MT_UNDEFINED,
MT_DECODE_FRAME_DEFAULT,
MT_DECODE_FRAME_VALUE,
MT_DECODE_SEQ_DEFAULT,
MT_DECODE_SEQ_VALUE,
MT_ENCODE_SEQ_DEFAULT,
MT_ENCODE_SEQ_VALUE,
MT_DECODE_INIT_DATA
} R4_MESSAGE_TYPE;

/*
 * MT_QUEUE is for 16-bit applications that call 32-bit codecs, only.  It
 * is necessary to pre-set ICM_GETCODECSTATE messages by first issuing an
 * ICM_SETCODECSTATE with the MT_QUEUE flag or'ed with the desired
 * message type to get.  The ICM_GETCODECSTATE message which follows
 * must set its dwFourCC field to 0.
 */
#defineMT_QUEUE  (1UL<<31)

/* UCHAR is defined in Win32 but not Win16 MS* Windows headers.
 */
#if !defined UCHAR
typedef unsigned char UCHAR;
#endif

/* A flag is false if it is zero, otherwise it is true.
 */
typedef DWORD R4_FLAG;

/* Pointer to a Flag - A pointer to a flag is used by the codec to write
 * status information at a later time.
 */
typedef R4_FLAG FAR * PTR_R4_FLAG;

/* Rectangle - The following structure is used to describe a rectangle.
 */
typedef struct {
DWORD dwX;
DWORD dwY;
DWORD dwWidth;
DWORD dwHeight;
} R4_RECT, FAR * PTR_R4_RECT;

/* Transparency Bitmask - A transparency bitmask contains one bit for each
 * pixel in the source image. If a bit is zero the data is transparent.
 * The first bit in each byte is identified by the mask 0x80.  The last
 * bit in each byte is identified by the mask 0x01.
 *
 * To allow faster access each row must begin on a DWORD boundary and the
 * total number of bytes must be rounded-up to a multipe of four.
 */
typedef BYTE FAR * PTR_R4_BITMASK;

/* Playback Platform - The codec supports several playback options.  This is
 * not hardware specific.  Instead it rates the relative capabilities of the
 * hardware.
 */
typedef enum {
PP_LOW,
PP_MEDIUM,
PP_HIGH,
} R4_PLAYBACK_PLATFORM;

/* Transparency Method - The codec supports a number of tranparency encoding
 * methods. The enumeration specifies the transparency method.
 */
typedef enum {
TM_UNDEFINED,
TM_NONE,
TM_RANGE,
TM_BITMASK,
TM_FRAME,
TM_ALPHA_CHANNEL/* XRGB color formats only */
} R4_TRANS_METHOD;

/* Transparency Range - A transparency range is specified using minimum
 * and maximum values for R, G, and B. Any pixel with values within this range
 * (inclusive) is considered transparent.
 */
typedef struct {
WORDu16Reserved;
UCHARu8BlueLow;
UCHARu8BlueHigh;
UCHARu8GreenLow;
UCHARu8GreenHigh;
UCHARu8RedLow;
UCHARu8RedHigh;
} R4_RANGE, FAR * PTR_R4_RANGE;

/* The structure header is a set fields that are common,
 * to each of the data structures that follows.
 */
typedef struct {
DWORDdwSize;
DWORDdwFourCC;
DWORDdwVersion;
LONGmtType;
LONGoeEnvironment;
DWORDdwFlags;
} R4_HEADER, FAR * PTR_R4_HEADER;
/* The structure header size is enough room for dwSize, dwFourCC,
 * dwVersion, dwType, dwEnvironment, and dwFlags
 */
#define R4_STRUCTURE_HEADER_SIZE (sizeof(R4_HEADER))

typedef enum {
OFF,
LOW,
MEDIUM,
HIGH
} R4_DITHER;

/* Some decode parameters can be changed prior to either an ICM_DECOMPRESS or
 * ICM_DECOMPRESSEX mesage. These are found in the  R4_DEC_FRAME_DATA
 * structure.  See CUSTOM_INTERFACE_ENCODER_DATA for structure size constraints.
 */
#define DECFRAME_TIME_LIMIT(1UL<<0)
#define DECFRAME_DECODE_RECT(1UL<<1)
#define DECFRAME_VIEW_RECT(1UL<<2)
#define DECFRAME_BOUNDING_RECT(1UL<<3)
#define DECFRAME_TRANS_BITMASK(1UL<<4)
#define DECFRAME_BRIGHTNESS(1UL<<5)
#define DECFRAME_SATURATION(1UL<<6)
#define DECFRAME_CONTRAST(1UL<<7)
#define DECFRAME_FILL_COLOR(1UL<<9)
#define DECFRAME_8BIT_CONFIG_PALETTE(1UL<<10)
#define DECFRAME_8BIT_DITHER(1UL<<11)
#define DECFRAME_VIEW_RECT_ORIGIN(1UL<<12)
#define DECFRAME_INPUT_TRANS_MASK(1UL<<13)
#define DECFRAME_ACCESS_KEY_VIOLATION(1UL<<14)
#define DECFRAME_VALID(1UL<<31)

/* Signals that the fill color provided is not a defined value */
#define DECFRAME_FILL_UNDEFINED 0xFF000000

typedef struct {
DWORDdwSize;
DWORDdwFourCC;
DWORDdwVersion;
LONGmtType;
LONGoeEnvironment;
DWORDdwFlags;

DWORDdwTimeLimit;/* version 1.0 */
R4_RECTrDecodeRect;/* version 1.0 */
R4_RECTrViewRect;/* version 1.0 */
PTR_R4_RECT prBoundingRect;/* version 1.0 */
PTR_R4_BITMASK pbmTransparencyBitmask;/* version 1.0 */
PTR_R4_FLAG pfUpdatedTransparencyBitmask;/* version 1.0 */
LONGlBrightness;/* version 1.0 */
LONGlSaturation;/* version 1.0 */
LONGlContrast;/* version 1.0 */

DWORDdwFillColor;/* version 1.2 */
DWORDdwFirstPalIndex;/* version 1.2 */
DWORDdwLastPalIndex;/* version 1.2 */
LPRGBQUADprgbPalette;/* version 1.2 */
R4_DITHEReDither;/* version 1.2 */
R4_FLAGfViewRectOrigin;/* version 1.2 */
PTR_R4_BITMASK pbmInputTransparencyBitmask;/* version 1.2 */

BOOLbAccessKeyViolation;/* version 1.3 */
} R4_DEC_FRAME_DATA, FAR * PTR_R4_DEC_FRAME_DATA;

/* Turning scalability off completely was accomplished in versions 1.0 and 1.1
 * of the interface by decoding in STEPPING mode.  Setting the scalability
 * flag to false meant not to drop the quality of frames, but frames could
 * still be dropped.  For this reason, if SC_OFF is used, it will map to
 * stepping mode, and SC_DONT_DROP_QUALITY occupies the position of "false."
 */
typedef enum {
SC_DONT_DROP_QUALITY,
SC_ON,
SC_DONT_DROP_FRAMES,
SC_OFF,
} R4_SCALABILITY;

/* Other decode parameters can only be changed prior to either an
 * ICM_DECOMPRESS_BEGIN or an ICM_DECOMPRESSEX_BEGIN message.  These
 * are found in the R4_DEC_SEQ_DATA structure.
 * See CUSTOM_INTERFACE_ENCODER_DATA for structure size constraints.
 */
#define DECSEQ_KEY(1UL<<0)
#define DECSEQ_SCALABILITY(1UL<<1)
#define DECSEQ_FILL_TRANSPARENT(1UL<<2)
#define DECSEQ_ALT_LINE(1UL<<3)
#define DECSEQ_VALID (1UL<<31)

typedef struct {
DWORDdwSize;
DWORDdwFourCC;
DWORDdwVersion;
LONGmtType;
LONGoeEnvironment;
DWORDdwFlags;

DWORDdwKey;/* version 1.0 */
R4_FLAGfEnabledKey;/* version 1.0 */
LONGeScalability;/* version 1.2; 1.0 fScalability */
R4_FLAGfFillTransparentPixels;/* version 1.0 */
R4_FLAGfAltLine;/* version 1.1 */
} R4_DEC_SEQ_DATA, FAR * PTR_R4_DEC_SEQ_DATA;

/* Decode persistent data parameters can only be sent prior to the
 * first ICM_DECOMPRESS_BEGIN message. These are found in the
 * R4_DECODE_INIT_DATA structure.
 */
#define DECINIT_VALID(3UL<<30)/* valid bit + one other bit */

typedef struct {
DWORDdwSize;
DWORDdwFourCC;
DWORDdwVersion;
LONGmtType;
LONGoeEnvironment;
DWORDdwFlags;

R4_FLAGfBidir;/* version 1.2 */
R4_FLAGfDeltaFrames;/* version 1.2 */
R4_FLAGfTransparency;/* version 1.2 */
DWORDdwNLevels;/* version 1.2 */
DWORDdwTileWidth;/* version 1.2 */
DWORDdwTileHeight;/* version 1.2 */

BOOLbAccessKeyInBS;/* version 1.3 */
} R4_DECODE_INIT_DATA, FAR * PTR_R4_DECODE_INIT_DATA;

/* Encode parameters can only be changed prior to an ICM_COMPRESS_BEGIN
 * messsage.  These are found in the R4_ENC_SEQ_DATA structure (these
 * include the parameters that can be set in the super dialog box).
 * See CUSTOM_INTERFACE_ENCODER_DATA for structure size constraints.
 */
#define ENCSEQ_KEY(1UL<<0)
#define ENCSEQ_MINIMUM_VIEWPORT(1UL<<1)
#define ENCSEQ_TRANSPARENCY(1UL<<2)
#define ENCSEQ_SCALABILITY(1UL<<3)
#define ENCSEQ_BI_DIR_PREDICTION(1UL<<4)
#define ENCSEQ_PLAYBACK_PLATFORM(1UL<<5)
#define ENCSEQ_RANDOM_KEY_AND_BIDIR (1UL<<6)
#define ENCSEQ_FRAME_INFO(1UL<<7)
#define ENCSEQ_QUICK_COMPRESS(1UL<<8)
#define ENCSEQ_VALID(1UL<<31)

typedef struct {
DWORDdwSize;
DWORDdwFourCC;
DWORDdwVersion;
LONGmtType;
LONGoeEnvironment;
DWORDdwFlags;

DWORDdwKey;/* version 1.0 */
DWORDdwMinViewportWidth;/* version 1.0 */
DWORDdwMinViewportHeight;/* version 1.0 */
R4_RANGErngTransparencyRange;/* version 1.0 */
PTR_R4_BITMASK pbmTransparencyBitmask;/* version 1.0 */
LONGtmTransparencyMethod;/* version 1.0 */
R4_FLAGfEnabledKey;/* version 1.0 */
R4_FLAGfScalability;/* version 1.0 */
R4_FLAGfBiDirPrediction;/* version 1.0 */
R4_FLAGfRandomKeyAndBiDir;/* version 1.0 */
DWORDdwPlaybackPlatform;/* version 1.0 */
WORDwFrameLatency;/* version 1.0 */
WORDwDeadFrames;/* version 1.0 */
R4_FLAGfQuickCompress;/* version 1.1 */
} R4_ENC_SEQ_DATA, FAR * PTR_R4_ENC_SEQ_DATA;

#endif /* __VFW_SPEC_H__ */


DECODER.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: Decoder.cpp
*
* Implements a prototype Mpeg Audio Software codec.  It just consumes
* the passed in audio frames.
*
*
\**************************************************************************/
#include "MpgAudio.h"

/* -------------------------------------------------------------------------
** CAudioDecoder
** -------------------------------------------------------------------------
*/

/******************************Public*Routine******************************\
* CAudioDecoder
*
* Constructor and destructor
*
*
\**************************************************************************/
CAudioDecoder::CAudioDecoder(CMpegAudioCodec *pMpegCodec)
    : m_pCodec(pMpegCodec) {}

/******************************Public*Routine******************************\
* ResetAudio
*
*
\**************************************************************************/
DWORD
CAudioDecoder::ResetAudio()
{
    CAutoLock lck(this);
    return DECODE_SUCCESS;
}

/******************************Public*Routine******************************\
* DecodeFrame
*
*
\**************************************************************************/
DWORD
CAudioDecoder::DecodeAudioFrame(
    AudioCtrl *pCtrl
    )
{
    CAutoLock   lck(this);
    DWORD       rc = DECODE_SUCCESS;

    m_pCtrl = pCtrl;

    for (DWORD i = 0; i < m_pCtrl->dwNumFrames; i++) {

        // Skip the sync word
        m_pCtrl->pCmprRead += 2;

        // skip the rest of the frame
        if (!SkipFrame()) {
            m_pCtrl->dwOutBuffUsed = 0L;
            return DECODE_ERR_DATA;
        }
    }

    ZeroMemory(m_pCtrl->pOutBuffer, m_pCtrl->dwOutBuffSize);
    m_pCtrl->dwOutBuffUsed = m_pCtrl->dwOutBuffSize;

    return rc;
}

/*****************************Private*Routine******************************\
* SkipFrame
*
*
\**************************************************************************/
BOOL
CAudioDecoder::SkipFrame()
{
    LPBYTE  lpCurr = m_pCtrl->pCmprRead;
    LPBYTE  lpEnd = m_pCtrl->pCmprWrite;
    int sm = 0;

    while (lpCurr < lpEnd && sm < 2)  {

        switch (sm) {
        case 0:
            sm = (*lpCurr == 0xff);
            break;

        case 1:
            if ((*lpCurr & 0xf0) == 0xf0) sm = 2; /* sync found */
            else sm = (*lpCurr == 0xff);
            break;
        }
        lpCurr++;
    }

    //
    // When we get here we have either run out of buffer or found the first
    // "sm" bytes of a valid sync word.
    //
    // Don't forget to put back the sync word bytes that we have just
    // read otherwise they would be lost forever.
    //
    lpCurr -= sm;
    m_pCtrl->pCmprRead = lpCurr;

    if (sm < 2) {
        return FALSE;   // sync not found.
    }

    return TRUE;
}


DECODER.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: Decoder.h
*
*
\**************************************************************************/

#ifndef _INC_DECODER_H
#define _INC_DECODER_H

// -------------------------------------------------------------------------
// Forward declaration to resolve circular reference.
// -------------------------------------------------------------------------
//
class CMpegAudioCodec;

// -------------------------------------------------------------------------
// Audio decoder class
//
// This class encapsulates a low-level mpeg video decoder.
// -------------------------------------------------------------------------
//

// Decode control option
enum {
    DECODE_MONO       = 0x00000001L,
    DECODE_STEREO     = 0x00000002L,
    DECODE_QUARTER    = 0x00000800L,
    DECODE_HALF       = 0x00001000L,
    DECODE_FULL       = 0x00002000L,
    DECODE_HALF_HIQ   = 0x00004000L,
    DECODE_HALF_FULLQ = 0x00008000L,
    DECODE_16BIT      = 0x00010000L,
    DECODE_8BIT       = 0x00020000L,
    DECODE_QSOUND     = 0x00040000L,
    DECODE_QUART_INT  = 0x00080000L
};

// Function Return Values
enum {
    DECODE_SUCCESS    = 0x0000L,
    DECODE_ERR_MEMORY = 0x0001L,
    DECODE_ERR_DATA   = 0x0002L,
    DECODE_ERR_PARAM  = 0x0004L,
    DECODE_ERR_QUARTZ = 0x0008L     // error from Quartz callback
};

struct AudioCtrl {

    //
    // Output Frame Buffer
    //
    DWORD   dwOutBuffUsed;
    DWORD   dwOutBuffSize;
    DWORD   dwMpegError;

    //
    // Frame decoder control
    //
    DWORD   dwCtrl;

    //
    // Input buffer fields
    //
    DWORD   dwNumFrames;
    LPBYTE  pCmprRead;
    LPBYTE  pCmprWrite;
    LPBYTE  pOutBuffer;
};

class CAudioDecoder
    : public CCritSec {

private:
    //
    // Back pointer to the object that created us.
    //
    CMpegAudioCodec *m_pCodec;
    AudioCtrl       *m_pCtrl;

    BOOL    SkipFrame();

public:
    CAudioDecoder(CMpegAudioCodec *pMpegCodec);

    DWORD   ResetAudio();
    DWORD   DecodeAudioFrame(AudioCtrl *pCtrl);
};
#endif


MPEGUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: mpeguids.h
*
* Universal ID's for the mpeg audio framework decoder
*
*
\**************************************************************************/

// {51653990-57B3-11cf-8EB1-02608C9BABA2} MPEG Framework audio decoder
DEFINE_GUID(CLSID_CMpegFrameworkAudioCodec,
0x51653990, 0x57b3, 0x11cf, 0x8e, 0xb1, 0x2, 0x60, 0x8c, 0x9b, 0xab, 0xa2);


MPGAUDIO.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: MpgAudio.cpp
*
* Implements a prototype Mpeg Audio Software codec.  It just consume
* the passed in packets.
*
*
\**************************************************************************/

#include "MpgAudio.h"

// define the GUIDs for streams and my CLSID in this file
#include <initguid.h>

#include "MpegUids.h"

// setup data

const AMOVIESETUP_MEDIATYPE psudIpPinTypes[] =
{ { &MEDIATYPE_Audio                // clsMajorType
  , &MEDIASUBTYPE_MPEG1Packet  }    // clsMinorType
, { &MEDIATYPE_Audio                // clsMajorType
  , &MEDIASUBTYPE_MPEG1AudioPayload }    // clsMinorType
, { &MEDIATYPE_Stream               // clsMajorType
  , &MEDIASUBTYPE_MPEG1Audio   } }; // clsMinorType

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{ &MEDIATYPE_Audio      // clsMajorType
, &MEDIASUBTYPE_NULL }; // clsMinorType

const AMOVIESETUP_PIN psudPins[] =
{ { L"Input"            // strName
  , FALSE               // bRendered
  , FALSE               // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_NULL         // clsConnectsToFilter
  , L"Output"           // strConnectsToPin
  , 3                   // nTypes
  , psudIpPinTypes }    // lpTypes
, { L"Output"           // strName
  , FALSE               // bRendered
  , TRUE                // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_NULL         // clsConnectsToFilter
  , L"Input"            // strConnectsToPin
  , 1                   // nTypes
  , &sudOpPinTypes } }; // lpTypes

const AMOVIESETUP_FILTER
sudMPEGAudio = { &CLSID_CMpegFrameworkAudioCodec // clsID
               , L"MPEG Framework Audio Codec"   // strName
               , 0x00680000                      // dwMerit
               , 2                               // nPins
               , psudPins };                     // lpPin

/* -------------------------------------------------------------------------
** list of class ids and creator functions for class factory
** -------------------------------------------------------------------------
*/
CFactoryTemplate g_Templates[] = {
    { L"MPEG Framework Audio Codec"
    , &CLSID_CMpegFrameworkAudioCodec
    , CMpegAudioCodec::CreateInstance
    , NULL
    , &sudMPEGAudio }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

/* -------------------------------------------------------------------------
** Decoder strings
** -------------------------------------------------------------------------
*/
const TCHAR chAudioChannels[]      = TEXT("AudioChannels");
const TCHAR chAudioFreqDivider[]   = TEXT("AudioFreqDivider");
const TCHAR chAudioQuality[]       = TEXT("AudioQuality");
const TCHAR chAudioQuarterInt[]    = TEXT("AudioQuarterInt");
const TCHAR chAudioBits[]          = TEXT("AudioBits");
const TCHAR chRegistryKey[] =
    TEXT("Software\\Microsoft\\Multimedia\\ActiveMovie Filters\\Sample MPEG Audio Decoder");

// --- CMpegAudioCodec ----------------------------------------
CMpegAudioCodec::CMpegAudioCodec(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr)
    : CTransformFilter(pName, pUnk, CLSID_CMpegAudioCodec),
    m_pAudioDecoder(NULL)
{
    m_FreqDiv  = GetDecoderInteger(chAudioFreqDivider, 4);
    m_PrefChan = GetDecoderInteger(chAudioChannels, 1);
    m_Quality  = GetDecoderInteger(chAudioQuality, 0);
    m_QuarterInt = GetDecoderInteger(chAudioQuarterInt, 0);
    m_WordSize = GetDecoderInteger(chAudioBits, 16);
    if (m_QuarterInt) {
        m_FreqDiv = 4;
    }
}

CMpegAudioCodec::~CMpegAudioCodec()
{
    delete m_pAudioDecoder;
    m_pAudioDecoder = NULL;
}

/******************************Public*Routine******************************\
* CreateInstance
*
* this goes in the factory template table to create new instances
*
*
\**************************************************************************/
CUnknown *
CMpegAudioCodec::CreateInstance(
    LPUNKNOWN pUnk,
    HRESULT *phr
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::CreateInstance")));
    return new CMpegAudioCodec(TEXT("MPEG Audio Codec Filter"), pUnk, phr);
}

/******************************Public*Routine******************************\
* NonDelegatingQueryInterface
*
* Reveals ISpecifyPropertyPages
*
*
\**************************************************************************/
STDMETHODIMP
CMpegAudioCodec::NonDelegatingQueryInterface(
    REFIID riid,
    void ** ppv
    )
{
    return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
}

/******************************Public*Routine******************************\
* EndOfStream
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::EndOfStream()
{
    DbgLog((LOG_TRACE, 2, TEXT("End of stream called")));
    CAutoLock lck(&m_csReceive);

    //
    // Here we would normally decode the remainder of the buffer.  However,
    // the audio codec is designed such that its buffer never contains
    // any full frames of coded data, so all we have to do is reset the
    // audio codec.
    //

    if (m_pAudioDecoder == NULL) {
        return VFW_E_WRONG_STATE;
    }

    ResetAudioDecoder();
    return CTransformFilter::EndOfStream();
}

/******************************Public*Routine******************************\
* EndFlush
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::EndFlush()
{
    DbgLog((LOG_TRACE, 2, TEXT("End flush called")));
    CAutoLock lck(&m_csReceive);

    ResetAudioDecoder();
    return CTransformFilter::EndFlush();
}

/*****************************Private*Routine******************************\
* ProcessDiscontiuity
*
*
\**************************************************************************/
void
CMpegAudioCodec::ProcessDiscontiuity(
    IMediaSample *pSample
    )
{
    ResetAudioDecoder();

    //
    //  Find out what the current stop time is
    //

    //
    //  Get logical duration from upstream
    //
    REFTIME dStart, dStop;
    IMediaPosition *pPosition;

    if (SUCCEEDED(m_pInput->GetConnected()->QueryInterface(IID_IMediaPosition,
                     (void **)&pPosition))
     && SUCCEEDED(pPosition->get_CurrentPosition(&dStart))
     && SUCCEEDED(pPosition->get_StopTime(&dStop)))
    {
        m_tStop = (CRefTime)(COARefTime)dStop - (CRefTime)(COARefTime)dStart;
    }
    else {
        m_tStop = 0x7FFFFFFFFFFFFFFF;
    }

    if (pPosition != NULL) {
        pPosition->Release();
    }

    DbgLog((LOG_TRACE, 2,
           TEXT("Receive() : Discontinuity - setting stop time to %s"),
           (LPCTSTR)CDisp(m_tStop)));
}

/*****************************Private*Routine******************************\
* ProcessSyncPoint
*
*
\**************************************************************************/
void
CMpegAudioCodec::ProcessSyncPoint(
    IMediaSample *pSample,
    BYTE *pSrc
    )
{
    CRefTime  tStart, tStop;

    pSample->GetTime((REFERENCE_TIME*)&tStart,
                     (REFERENCE_TIME*)&tStop);

    m_TimeAtLastSyncPoint = tStart;
    m_TimeSinceLastSyncPoint = 0;

    //
    // If our buffer contains a partial audio frame adjust the
    // sync point time by one frame.  Note that LookForSyncWord advances
    // the m_lpCurr buffer pointer, if there is no sync word found
    // m_lpCurr should equal m_lpEnd, if the last byte in our audio buffer
    // is the first byte of the audio sync word m_lpCurr will equal
    // m_lpEnd - 1.
    // This can fail because sync words can be found other than at
    // frame start!
    //
    if (LookForSyncWord()) {
        m_TimeAtLastSyncPoint -= m_TimePerFrame;
    }
    else {

        ASSERT(m_lpCurr <= m_lpEnd);

        //
        // Now check that there was not a pertial sync word left in our
        // buffer.
        //
        if (m_lpCurr < m_lpEnd) {
            if ((*m_lpCurr == 0xFF) && (pSrc[0] & 0xF0) == 0xF0) {
                m_TimeAtLastSyncPoint -= m_TimePerFrame;
            }
        }
    }
}

/*****************************Private*Routine******************************\
* DeliverSample
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::DeliverSample(
    IMediaSample *pOutSample,
    CRefTime &TimeDecoded,
    int nActBytesWritten
    )
{
    CRefTime tStart, tStop;

    //
    // decompressed frames are always key
    //
    pOutSample->SetSyncPoint(TRUE);
    pOutSample->SetActualDataLength(nActBytesWritten);

    //
    // Extrapolate the correct time stamp.
    //
    tStart = m_TimeAtLastSyncPoint + m_TimeSinceLastSyncPoint;
    if (tStart < (LONG)0) {

        /*  Only play from the start */
    }

    tStop = tStart + TimeDecoded;
    m_TimeSinceLastSyncPoint += TimeDecoded;
    pOutSample->SetTime((REFERENCE_TIME*)&tStart,
                        (REFERENCE_TIME*)&tStop);

    DbgLog((LOG_TRACE, 3, TEXT("Writing %ld bytes with time stamp %s"),
            nActBytesWritten, (LPCTSTR)CDisp(tStart) ));

    return m_pOutput->Deliver(pOutSample);
}

/******************************Public*Routine******************************\
* Receive
*
* Copy the input sample into our buffer.  Loop while the buffer size is
* greater than or equal to the size required to decode a frame of audio data.
* Decode the audio frame and pass it along to the output pin for rendering.
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::Receive(
    IMediaSample *pSample
    )
{
    //  Make sure the pin doesn't go inactive on us
    CAutoLock lck(&m_csReceive);
    if (m_pAudioDecoder == NULL) {
        return E_UNEXPECTED;
    }

    IMediaSample    *pOutSample;
    BYTE            *pDst;
    BYTE            *pSrc;
    BYTE            *lpPacket;
    HRESULT         hr;
    long            LenLeftInBuffer = 0L;
    long            LenLeftInPacket;

    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::Receive")));

    //
    // Check for a discontinuity, if one is found just reset the decoder
    // and then continue processing the current sample.  We do not have to
    // decode any frames left in the audio buffer because the audio codec
    // always consumes all the complete audio data frames copied to
    // the buffer.
    //
    if (pSample->IsDiscontinuity() == S_OK) {
        ProcessDiscontiuity(pSample);
    }

    hr = pSample->GetPointer(&pSrc);
    if (FAILED(hr)) {
        return hr;
    }

    //
    // Get a pointer to the actual mpeg data and determine the
    // length of data supplied.
    //
    if (m_bPayloadOnly) {
        lpPacket = pSrc;
        LenLeftInPacket = pSample->GetActualDataLength();
    }
    else {
        lpPacket = SkipToPacketData(pSrc, LenLeftInPacket);
        if (lpPacket == NULL) {
            NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, E_INVALIDARG, 0);
            return S_OK;
        }
    }
    DbgLog((LOG_TRACE, 2, TEXT("Left in Packet %ld"), LenLeftInPacket ));

    //
    // If this sample is a sync point we need to update our clock and
    // reset the count of samples received since the last sync point.
    // If our buffer contains a partial audio frame we need to make a suitable
    // adjustment to the sync point time as the time refers to the first audio
    // frame in the current sample which would not necessarly be the first
    // sample decoded.
    //

    if (pSample->IsSyncPoint() == S_OK) {
        ProcessSyncPoint(pSample, pSrc);
    }

    //
    // Now, decode the sample data passed to us.
    //

    do {

        LPBYTE      lpDstEnd;
        CRefTime    TimeDecoded;
        DWORD       rc;
        int         nActBytesWritten = 0;

        GetNextPacketChunk(lpPacket, LenLeftInBuffer, LenLeftInPacket);

        DbgLog((LOG_TRACE, 3, TEXT("Left in Buffer %ld"), LenLeftInBuffer ));
        DbgLog((LOG_TRACE, 3, TEXT("Left in Packet %ld"), LenLeftInPacket ));

        if (LenLeftInBuffer < m_FrameSize && LenLeftInPacket == 0L) {
             break;
        }

        //
        // this may block for an indeterminate amount of time
        //
        hr = m_pOutput->GetDeliveryBuffer(&pOutSample,NULL,NULL,0);
        if (FAILED(hr)) {
            break;
        }
        ASSERT(pOutSample);

        hr = pOutSample->GetPointer(&pDst);
        if (FAILED(hr)) {
            break;
        }
        ASSERT(pDst);

        //
        // Initialize the audio control structure
        //
        m_AudioControl.dwNumFrames   = 1;
        m_AudioControl.dwOutBuffSize = m_pOutput->CurrentMediaType().GetSampleSize();
        m_AudioControl.dwOutBuffUsed = 0;
        m_AudioControl.pCmprRead  = m_lpStart;
        m_AudioControl.pCmprWrite = m_lpEnd;

        //
        // Determine the end of the output buffer so that we don't try to
        // decode data beyond it.  m_BytesPerFrame is the number of bytes
        // one frame of Mpeg audio data will expand to after it has been
        // decoded.
        //
        lpDstEnd = pDst + m_AudioControl.dwOutBuffSize - m_FrameSizeOutput;
        TimeDecoded = 0;

        //
        // While there is an audio frame in the buffer and the frame is
        // complete and there is space in the output buffer to store the
        // decompressed frame, decompress it !!
        //

        while (LookForSyncWord()
           && (m_lpCurr + m_FrameSize + Padding() < m_lpEnd)
           && (pDst <= lpDstEnd)) {

            m_AudioControl.pOutBuffer  = pDst;
            m_AudioControl.pCmprRead   = m_lpCurr;
            m_AudioControl.dwMpegError = 0;

            rc = m_pAudioDecoder->DecodeAudioFrame(&m_AudioControl);
            switch (rc) {

            case 0:
                //
                // We have successfully decoded an audio frame.
                //
                if (m_FrameSize == 0L) {
                    m_FrameSize = (LPBYTE)m_AudioControl.pCmprRead -
                                          m_lpCurr - Padding();
                }
                m_lpCurr = (LPBYTE)m_AudioControl.pCmprRead;
                pDst += m_AudioControl.dwOutBuffUsed;
                nActBytesWritten += m_AudioControl.dwOutBuffUsed;
                m_AudioControl.dwOutBuffSize -= m_AudioControl.dwOutBuffUsed;
                TimeDecoded += m_TimePerFrame;
                break;

            case 2:
                //
                // We did not have enough data available to decode the
                // current audio frame.  This is buffer underflow.
                //
                DbgLog((LOG_ERROR, 1, TEXT("Buffer underflow !!")));

            default:
                //
                // Some sort of error occurred, throw the remainder of the
                // buffer away and skip this packet.
                //
                DbgLog((LOG_ERROR, 1, TEXT("Bad return code from audio codec!")));
                m_lpCurr = m_lpEnd;
                NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, E_INVALIDARG, 0);
                hr = S_OK;
                break;
            }
        }

        //
        // Have we decoded any data ?  If so we need to deliver the data to
        // to the filter that receives our output, usually the audio
        // rendering filter.
        //

        if (TimeDecoded > (LONG)0) {

            hr = DeliverSample(pOutSample, TimeDecoded, nActBytesWritten);
            if (FAILED(hr)) {
                DbgLog((LOG_ERROR, 2,
                        TEXT("CMpegAudioCodec::Deliver failed 0x%8.8X"), hr));
                pOutSample->Release();
                break;
            }
        }

        //
        // Release the output buffer. If the connected pin still needs it,
        // it will have addrefed it itself.
        //
        pOutSample->Release();

        //
        // Stop decoding when we have consumed all the data in the input
        // media sample.
        //

    } while (LenLeftInPacket != 0L);

    //
    //  We notify the filter graph of problems but return S_FALSE
    //  back to the caller to notify end of stream
    //

    // if (hr != S_OK) {
    //     NotifyEvent(hr == S_FALSE ? EC_COMPLETE : EC_ERRORABORT, 0, 0);
    // }
    return hr;
}

/*****************************Private*Routine******************************\
* ResetAudioDecoder
*
*
\**************************************************************************/
void
CMpegAudioCodec::ResetAudioDecoder()
{
    m_pAudioDecoder->ResetAudio();
    m_lpStart = m_lpCurr = m_lpEnd = &m_Buffer[0];
    m_FrameSize = 0L;
}

/******************************Public*Routine******************************\
* GetNextPacketChunk
*
*
\**************************************************************************/
void
CMpegAudioCodec::GetNextPacketChunk(
    LPBYTE  &lpPacket,
    long    &LenLeftInBuffer,
    long    &LenLeftInPacket
    )
{
    long AmountToCopy;

    LenLeftInBuffer = m_lpEnd - m_lpCurr;

    //
    // Move what remains in the audio data buffer to the top of
    // the buffer and append the new audio data to it.
    //
    AmountToCopy = min( (AUDIO_BUFF_SIZE - LenLeftInBuffer), LenLeftInPacket);

    MoveMemory(m_lpStart, m_lpCurr, LenLeftInBuffer);
    CopyMemory(m_lpStart + LenLeftInBuffer, lpPacket,  AmountToCopy);

    LenLeftInPacket -= AmountToCopy;
    lpPacket += AmountToCopy;

    //
    // Adjust the buffer pointers so that m_lpCurr points to
    // the start of the buffer and m_lpEnd points to the last
    // valid audio byte in buffer.
    //
    m_lpCurr = m_lpStart;
    m_lpEnd = m_lpStart + LenLeftInBuffer + AmountToCopy;
    LenLeftInBuffer = m_lpEnd - m_lpCurr;
}

/******************************Public*Routine******************************\
* LookForSyncWord
*
*
\**************************************************************************/
BOOL
CMpegAudioCodec::LookForSyncWord()
{
    /* now look for sync */
    int sm = 0;

    while (m_lpCurr < m_lpEnd && sm < 2)  {

        switch (sm) {
        case 0:
            sm = (*m_lpCurr == 0xff);
            break;

        case 1:
            if ((*m_lpCurr & 0xf0) == 0xf0) sm = 2; /* sync found */
            else sm = (*m_lpCurr == 0xff);
            break;
        }
        m_lpCurr++;
    }

    //
    // When we get here we have either run out of buffer or found the first
    // "sm" bytes of a valid sync word.
    //
    // Don't forget to put back the sync word bytes that we have just
    // read otherwise they would be lost forever.
    //
    m_lpCurr -= sm;

    if (sm < 2) {
        return FALSE;   // sync not found.
    }

    return TRUE;
}

/******************************Public*Routine******************************\
* Padding
*
* Returns 1 if the padding bit is set, zero otherwise
*
*
\**************************************************************************/
int
CMpegAudioCodec::Padding()
{
    DWORD dw1 = *(UNALIGNED DWORD *)m_lpCurr;

    //
    // Determine the audio layer for the given frame
    //
    switch (dw1 & 0x600) {

    case 0x200:     // Layer 1
        return (dw1 & 0x20000) ? 4 : 0;

    case 0x400:
    case 0x600:     // Layer 2 or Layer 3
        return (dw1 & 0x20000) ? 1 : 0;

    default:        // Error !!
        return 0;
    }
}

/******************************Public*Routine******************************\
* CheckInputType
*
*
* check if you can support mtIn
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::CheckInputType(
    const CMediaType* pmtIn
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::CheckInputType")));

    //  Check for native streams
    if (*pmtIn->Type() == MEDIATYPE_Stream &&
        *pmtIn->Subtype() == MEDIASUBTYPE_MPEG1Audio) {

        /*  This will be checked a bit more in Connect() */
        return S_OK;
    }

    // check this is an MPEG audio format type
    if (*pmtIn->FormatType() != FORMAT_WaveFormatEx) {
        DbgLog((LOG_ERROR, 2, TEXT("\tFormat not FORMAT_WaveFormatEx")));
        return E_INVALIDARG;
    }

    // we only support MEDIATYPE_Audio
    if (*pmtIn->Type() != MEDIATYPE_Audio) {
        DbgLog((LOG_ERROR, 2, TEXT("\tNot MEDIATYPE_Audio")));
        return E_INVALIDARG;
    }

    if (*pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Packet &&
        *pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Payload &&
        *pmtIn->Subtype() != MEDIASUBTYPE_MPEG1AudioPayload ) {
        DbgLog((LOG_ERROR, 2, TEXT("\tNot MEDIASUBTYPE_MPEG1Packet or Payload")));
        return E_INVALIDARG;
    }

    //
    // Make sure that we have not been given layer III data, we don't know
    // how to decode layer III.
    //
    if (((LPMPEG1WAVEFORMAT)pmtIn->pbFormat)->fwHeadLayer == ACM_MPEG_LAYER3) {
        DbgLog((LOG_ERROR, 2, TEXT("\tCan't decode layer III data")));
        return E_INVALIDARG;
    }

    CopyMemory(&m_Format, pmtIn->pbFormat, sizeof(MPEG1WAVEFORMAT));
    return S_OK;
}

/******************************Public*Routine******************************\
* CheckTransform
*
* check if you can support the transform from this input to this output
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::CheckTransform(
    const CMediaType* pmtIn,
    const CMediaType* pmtOut
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::CheckTransform")));

    // we only output audio
    if (*pmtOut->Type() != MEDIATYPE_Audio) {
        return S_FALSE;
    }

    return S_OK;
}

/******************************Public*Routine******************************\
* SetMediaType
*
* overriden to know when the media type is actually set
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::SetMediaType(
    PIN_DIRECTION direction,
    const CMediaType *pmt
    )
{
    if (direction == PINDIR_INPUT) {

        //
        // Get the data type
        //
        if (*pmt->Subtype() == MEDIASUBTYPE_MPEG1Packet) {
            m_bPayloadOnly = FALSE;
        }
        else {
            ASSERT(*pmt->Subtype() == MEDIASUBTYPE_MPEG1Payload ||
                   *pmt->Subtype() == MEDIASUBTYPE_MPEG1Audio ||
                   *pmt->Subtype() == MEDIASUBTYPE_MPEG1AudioPayload);
            m_bPayloadOnly = TRUE;
        }
    }
    else {

        LPWAVEFORMATEX lpWf = (LPWAVEFORMATEX)pmt->pbFormat;
        int SamplesPerFrame;

        if (m_QuarterInt) {
            m_dwCtrl = DECODE_QUART_INT | DECODE_QUARTER;
        }
        else {
            switch (m_FreqDiv) {
            case 1:
                m_dwCtrl = DECODE_FULL;
                break;

            case 2:
                m_dwCtrl = DECODE_HALF;
                break;

            case 4:
                m_dwCtrl = DECODE_QUARTER;
                break;
            }
        }

        switch (lpWf->wBitsPerSample) {
        case 16:
            m_dwCtrl |= DECODE_16BIT;
            break;

        case 8:
            m_dwCtrl |= DECODE_8BIT;
            break;
        }

        switch (m_Quality) {
        case DECODE_HALF_FULLQ:
            m_dwCtrl |= DECODE_HALF_FULLQ;
            break;

        case DECODE_HALF_HIQ:
            m_dwCtrl |= DECODE_HALF_HIQ;
            break;
        }

        switch (lpWf->nChannels) {
        case 2:
            m_dwCtrl |= DECODE_STEREO;
            break;

        case 1:
            m_dwCtrl |= DECODE_MONO;
            break;
        }

        if (m_Format.fwHeadLayer == ACM_MPEG_LAYER1) {
            SamplesPerFrame = 384;
        }
        else {
            SamplesPerFrame = 1152;
        }

        m_TimePerFrame = MulDiv(SamplesPerFrame, 10000000,
                                m_Format.wfx.nSamplesPerSec);

        m_FrameSizeOutput = MulDiv(MulDiv(SamplesPerFrame,
                                          lpWf->wBitsPerSample, 8),
                                   lpWf->nChannels, m_FreqDiv);
    }
    return S_OK;
}

/******************************Public*Routine******************************\
* GetMediaType
*
* Return our preferred output media types (in order)
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::GetMediaType(
    int iPosition,
    CMediaType *pmt
    )
{
    LPWAVEFORMATEX      lpWf;
    LPWAVEFORMATEX      lpWfIn;
    LPMPEG1WAVEFORMAT   lpWfInMpeg;
    CMediaType          cmt;
    int                 SamplesPerFrame;
    WORD                nChannels;
    WORD                nBitsPerSample;

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    //
    // If the requested output word size is 8 bits do not reveal the 16 bits
    // per sample option.
    //
    if (m_WordSize == 8) {
        if (iPosition > 0) {
            return VFW_S_NO_MORE_ITEMS;
        }
        iPosition = 1;
    }

    switch (iPosition) {
    case 0:
        nBitsPerSample = 16;
        break;

    case 1:
        nBitsPerSample = 8;
        break;

    default:
        return VFW_S_NO_MORE_ITEMS;
    }

    lpWf = (LPWAVEFORMATEX)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
    if (lpWf == NULL) {
        return E_OUTOFMEMORY;
    }
    if (*m_pInput->CurrentMediaType().Type() == MEDIATYPE_Stream) {
        lpWfIn = &m_Format.wfx;
        lpWfInMpeg = &m_Format;
    } else {
        lpWfIn = (LPWAVEFORMATEX)m_pInput->CurrentMediaType().pbFormat;
        lpWfInMpeg = (LPMPEG1WAVEFORMAT)m_pInput->CurrentMediaType().pbFormat;
    }
    ASSERT(lpWfIn != NULL);

    //
    // Dump the input format
    //
    DbgLog((LOG_TRACE, 2, TEXT("nSamplesPerSec = %ld"), lpWfIn->nSamplesPerSec));
    DbgLog((LOG_TRACE, 2, TEXT("nChannels      = %hd"), lpWfIn->nChannels));
    DbgLog((LOG_TRACE, 2, TEXT("Layer          = %hd"), lpWfInMpeg->fwHeadLayer));

    //
    // If the number of channels available matches the number preferred
    // we decode all the channels available.  Otherwise we decode the
    // minimum of the chanels available and the channels preferred.
    //
    if (lpWfIn->nChannels == m_PrefChan) {
        nChannels = m_PrefChan;
    }
    else {
        nChannels = min(m_PrefChan, lpWfIn->nChannels);
    }

    lpWf->wFormatTag       = WAVE_FORMAT_PCM;
    lpWf->nChannels        = nChannels;
    lpWf->nSamplesPerSec   = lpWfIn->nSamplesPerSec / m_FreqDiv;
    lpWf->nBlockAlign      = (nBitsPerSample * nChannels) / 8;
    lpWf->nAvgBytesPerSec  = lpWf->nSamplesPerSec * lpWf->nBlockAlign;
    lpWf->wBitsPerSample   = nBitsPerSample;
    lpWf->cbSize           = 0;

    //
    // Dump the ouput format
    //
    DbgLog((LOG_TRACE, 2, TEXT("!!nSamplesPerSec = %ld"), lpWf->nSamplesPerSec));
    DbgLog((LOG_TRACE, 2, TEXT("!!nChannels      = %hd"), lpWf->nChannels));
    DbgLog((LOG_TRACE, 2, TEXT("!!nBlockAlign    = %hd"), lpWf->nBlockAlign));
    DbgLog((LOG_TRACE, 2, TEXT("!!wBitsPerSample = %hd"), lpWf->wBitsPerSample));
    DbgLog((LOG_TRACE, 2, TEXT("!!nAvgBytesPerSec= %ld\n"), lpWf->nAvgBytesPerSec));

    //
    // we assume the output format is uncompressed
    //
    pmt->SetType(&MEDIATYPE_Audio);
    pmt->SetSubtype(&GUID_NULL);
    pmt->SetFormatType(&FORMAT_WaveFormatEx);
    pmt->SetTemporalCompression(FALSE);

    //
    // The time per frame and output sample size depend on the layer
    // of mpeg audio data being compressed.
    //
    if (lpWfInMpeg->fwHeadLayer == ACM_MPEG_LAYER1) {
        SamplesPerFrame = 384;
    }
    else {
        SamplesPerFrame = 1152;

}

    m_TimePerFrame = MulDiv(SamplesPerFrame, 10000000, lpWfIn->nSamplesPerSec);
    m_FrameSizeOutput = MulDiv(MulDiv(SamplesPerFrame, nBitsPerSample, 8),
                               nChannels, m_FreqDiv);

    pmt->SetSampleSize(m_FrameSizeOutput * MAX_FRAMES_PER_OUTPUT_SAMPLE);
    return S_OK;
}

/******************************Public*Routine******************************\
* DecideBufferSize
*
*
* Called from CBaseOutputPin to prepare the allocator's count
* of buffers and sizes
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::DecideBufferSize(
    IMemAllocator *pAllocator,
    ALLOCATOR_PROPERTIES * pProperties
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::DecideBufferSize")));

    ASSERT(pAllocator);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cBuffers = 8;
    pProperties->cbBuffer = m_pOutput->CurrentMediaType().GetSampleSize();

    ASSERT(pProperties->cbBuffer);
    DbgLog((LOG_TRACE, 2, TEXT("Sample size = %ld\n"), pProperties->cbBuffer));

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAllocator->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT(Actual.cbAlign == 1);
    ASSERT(Actual.cbPrefix == 0);

    if (Actual.cbBuffer < pProperties->cbBuffer ||
        Actual.cBuffers < pProperties->cBuffers) {

            // can't use this allocator
            return E_INVALIDARG;
    }
    return S_OK;
}

/******************************Public*Routine******************************\
* StartStreaming
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::StartStreaming()
{
    CAutoLock       lock(&m_csFilter);

    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::StartStreaming")));

    m_pAudioDecoder = new CAudioDecoder(this);
    if (m_pAudioDecoder == NULL) {
        return E_OUTOFMEMORY;
    }

    ResetAudioDecoder();
    return S_OK;
}

/******************************Public*Routine******************************\
* StopStreaming
*
*
\**************************************************************************/
HRESULT
CMpegAudioCodec::StopStreaming()
{
    CAutoLock lock(&m_csFilter);
    CAutoLock lck(&m_csReceive);
    DbgLog((LOG_TRACE, 2, TEXT("CMpegAudioCodec::StopStreaming")));

    ASSERT(m_pAudioDecoder != NULL);

    delete m_pAudioDecoder;
    m_pAudioDecoder = NULL;

    return S_OK;
}

/******************************Public*Routine******************************\
* GetDecoderInteger
*
*
\**************************************************************************/
int
GetDecoderInteger(
    const TCHAR *pKey,
    int iDefault
    )
{
    HKEY hKey;
    LONG lRet;
    int  iRet = iDefault;

    lRet = RegOpenKey(HKEY_CURRENT_USER, chRegistryKey, &hKey);
    if (lRet == ERROR_SUCCESS) {

        DWORD   dwType, dwLen;

        dwLen = sizeof(iRet);
        if (ERROR_SUCCESS != RegQueryValueEx(hKey, pKey, 0L, &dwType,
                                             (LPBYTE)&iRet, &dwLen)) {
            iRet = iDefault;
        }
        RegCloseKey(hKey);
    }
    return iRet;
}

/******************************Public*Routine******************************\
* SkipToPacketData
*
*
\**************************************************************************/
LPBYTE
SkipToPacketData(
    LPBYTE pSrc,
    long &LenLeftInPacket
    )
{
    LPBYTE  lpPacketStart;
    DWORD   bData;
    long    Length;

    //
    // Skip the stream ID and extract the packet length
    //
    pSrc += 4;
    bData = *pSrc++;
    Length = (long)((bData << 8) + *pSrc++);
    DbgLog((LOG_TRACE, 3, TEXT("Packet length %ld"), Length ));

    //
    // Record position of first byte after packet length
    //
    lpPacketStart = pSrc;

    //
    // Remove stuffing bytes
    //
    for (; ; ) {
        bData = *pSrc++;
        if (!(bData & 0x80)) {
            break;
        }
    }

    if ((bData & 0xC0) == 0x40) {
        pSrc++;
        bData = *pSrc++;
    }

    switch (bData & 0xF1) {

    case 0x21:
        pSrc += 4;
        break;

    case 0x31:
        pSrc += 9;
        break;

    default:
        if (bData != 0x0F) {
            DbgLog((LOG_TRACE, 2, TEXT("Invalid packet - 0x%2.2X\n"), bData));
            return NULL;
        }
    }

    //
    // The length left in the packet is the original length of the packet
    // less those bytes that we have just skipped over.
    //
    LenLeftInPacket = Length - (pSrc - lpPacketStart);
    return pSrc;
}

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}


MPGAUDIO.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/******************************Module*Header*******************************\
* Module Name: MpgAudio.h
*
* Prototype Mpeg Audio codec
*
*
\**************************************************************************/
#ifndef _INC_MPGAUDIO_H
#define _INC_MPGAUDIO_H
#include <streams.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <stddef.h>
#include <string.h>

#include "decoder.h"

class CMpegAudioCodec : public CTransformFilter {
public:
    //
    // --- Com stuff ---
    //
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
    DECLARE_IUNKNOWN;

    //
    // --- CTransform overrides ---
    //
    HRESULT Receive(IMediaSample *pSample);
    HRESULT CheckInputType(const CMediaType* mtIn);
    HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut);
    HRESULT DecideBufferSize(IMemAllocator * pAllocator,
                             ALLOCATOR_PROPERTIES * pProperties);
    HRESULT StartStreaming();
    HRESULT StopStreaming();
    HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT EndOfStream(void);
    HRESULT EndFlush(void);

    CMpegAudioCodec(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *pHr);
    ~CMpegAudioCodec();

private:
    //  Serialize access to the output pin
    long            m_FrameSize;        // Frame input size (bytes)
    long            m_FrameSizeOutput;  // Frame output size (bytes)

    LPBYTE          m_lpStart;
    LPBYTE          m_lpCurr;
    LPBYTE          m_lpEnd;
    BOOL            m_bPayloadOnly;

    enum            {MAX_FRAMES_PER_OUTPUT_SAMPLE = 4};
    enum            {AUDIO_BUFF_SIZE = (1024 * 8)};

    DWORD           m_dwCtrl;
    AudioCtrl       m_AudioControl;
    CAudioDecoder   *m_pAudioDecoder;

    CRefTime        m_TimePerFrame;
    CRefTime        m_TimeAtLastSyncPoint;
    CRefTime        m_TimeSinceLastSyncPoint;

    int             m_FreqDiv;
    int             m_PrefChan;
    int             m_Quality;
    int             m_QuarterInt;
    int             m_WordSize;

    BYTE            m_Buffer[AUDIO_BUFF_SIZE];

    void    ProcessDiscontiuity(IMediaSample *pSample);
    void    ProcessSyncPoint(IMediaSample *pSample, BYTE *pSrc);
    HRESULT DeliverSample(IMediaSample *pOutSample, CRefTime &TimeDecoded,
                          int iSampleSize);

    void    ResetAudioDecoder();
    BOOL    LookForSyncWord();
    int     Padding();
    void    GetNextPacketChunk(LPBYTE &lpPacket,
                               long &LenLeftInBuffer, long &LenLeftInPacket);

    CRefTime        m_tStop;
    MPEG1WAVEFORMAT m_Format;

};

// -------------------------------------------------------------------------
// Helper functions that can be used by audio and video codecs.
// -------------------------------------------------------------------------
//

/******************************Public*Routine******************************\
* ByteSwap
*
* Converts dwX from little endian to big endian and vice-versa.
*
*
\**************************************************************************/
__inline DWORD
ByteSwap(
    DWORD dwX
    )
{
#ifdef _X86_
    _asm    mov     eax, dwX
    _asm    bswap   eax
    _asm    mov     dwX, eax

    return dwX;
#else
    return _lrotl(((dwX & 0xFF00FF00) >> 8) | ((dwX & 0x00FF00FF) << 8), 16);
#endif
}

LPBYTE SkipToPacketData(LPBYTE pSrc, long &LenLeftInPacket);
int GetDecoderInteger(const TCHAR *pKey,int iDefault);

#endif


ALLOC.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  Sequential allocator methods */
#include <streams.h>
#include <alloc.h>

CSequentialAllocator::CSequentialAllocator(
    LPUNKNOWN  pUnk,
    HRESULT   *phr
) : CMemAllocator(TEXT("CSequential Allocator"), pUnk, phr),
    m_pbNext(m_pBuffer),
    m_parSamples(NULL),
    m_lValid(0),
    m_pbStartValid(NULL)
{
}

CSequentialAllocator::~CSequentialAllocator()
{
    if (m_parSamples != NULL) {
        delete [] m_parSamples;
    }
}

STDMETHODIMP CSequentialAllocator::GetBuffer(
    IMediaSample **ppBuffer,
    REFERENCE_TIME * pStartTime,
    REFERENCE_TIME * pEndTime,
    DWORD dwFlags
)
{
    /*  Like the normal version except we will only allocate the NEXT
        buffer
    */

    UNREFERENCED_PARAMETER(pStartTime);
    UNREFERENCED_PARAMETER(pEndTime);
    UNREFERENCED_PARAMETER(dwFlags);
    CMediaSample *pSample = NULL;

    *ppBuffer = NULL;
    for (;;)
    {
{  // scope for lock
    CAutoLock cObjectLock(this);

    /* Check we are committed */
    if (!m_bCommitted) {
return VFW_E_NOT_COMMITTED;
    }
            /* Check if the one we want is there */
            CMediaSample *pSearch = m_lFree.Head();
            while (pSearch) {
                PBYTE pbBuffer;
                pSearch->GetPointer(&pbBuffer);
                if (pbBuffer == m_pbNext) {
                    m_lFree.Remove(pSearch);
                    pSample = pSearch;
                    ASSERT(m_lSize == pSample->GetSize());
                    m_pbNext += m_lSize;
                    if (m_pbNext == m_pBuffer + m_lSize * m_lCount) {
                        m_pbNext = m_pBuffer;
                    }
                    break;
                } else {
                    pSearch = m_lFree.Next(pSearch);
                }
            }
            if (pSample == NULL) {
                /*  If there were some samples but just not ours someone
                    else may be waiting
                */
                if (m_lFree.GetCount() != 0) {
                    NotifySample();
                }
                SetWaiting();
            }
}

/* If we didn't get a sample then wait for the list to signal */

if (pSample) {
    break;
}
        ASSERT(m_hSem != NULL);
        DbgLog((LOG_TRACE, 4, TEXT("Waiting - %d buffers available"),
                m_lFree.GetCount()));
WaitForSingleObject(m_hSem, INFINITE);
    }

#ifdef VFW_S_CANT_CUE
    /* Addref the buffer up to one. On release
       back to zero instead of being deleted, it will requeue itself by
       calling the ReleaseBuffer member function. NOTE the owner of a
       media sample must always be derived from CBaseAllocator */

    ASSERT(pSample->m_cRef == 0);
    pSample->m_cRef = 1;
    *ppBuffer = pSample;
#else
    /* This QueryInterface should addref the buffer up to one. On release
       back to zero instead of being deleted, it will requeue itself by
       calling the ReleaseBuffer member function. NOTE the owner of a
       media sample must always be derived from CBaseAllocator */

    HRESULT hr = pSample->QueryInterface(IID_IMediaSample, (void **)ppBuffer);

    /* For each sample outstanding, we need to AddRef ourselves on his behalf
       he cannot do it, as there is no correct ordering of his release and his
       call to ReleaseBuffer as both could destroy him. We release this count
       in ReleaseBuffer, called when the sample's count drops to zero */

    AddRef();
#endif
    return NOERROR;
}

HRESULT CSequentialAllocator::Alloc()
{
    CAutoLock lck(this);
    if (m_parSamples != NULL) {
        delete [] m_parSamples;
    }
    m_parSamples = new LPCMEDIASAMPLE[m_lCount];
    if (m_parSamples == NULL) {
        return E_OUTOFMEMORY;
    }
    HRESULT hr = CMemAllocator::Alloc();

    if (S_OK == hr) {
        ASSERT(m_lCount == m_lFree.GetCount());

        /* Find the smallest */
        CMediaSample *pSample = m_lFree.Head();

        m_pBuffer = (PBYTE)(DWORD)-1;
        for (; pSample != NULL; pSample = m_lFree.Next(pSample)) {
            PBYTE pbTemp;
            pSample->GetPointer(&pbTemp);
            if (m_pBuffer > pbTemp) {
                m_pBuffer = pbTemp;
            }
        }

        pSample = m_lFree.Head();
        for ( ;pSample != NULL; pSample = m_lFree.Next(pSample)) {
            PBYTE pbTemp;
            pSample->GetPointer(&pbTemp);
            m_parSamples[BufferIndex(pbTemp)] = pSample;
        }
    }
    m_pbStartValid = m_pBuffer;
    m_pbNext       = m_pBuffer;
    ASSERT(m_lValid == 0);
    return hr;
}

/*  Get buffer index */
int CSequentialAllocator::BufferIndex(PBYTE pbBuffer)
{
    int iPos = (pbBuffer - m_pBuffer) / m_lSize;
    ASSERT(iPos < m_lCount);
    return iPos;
}
/*  Given an address get the IMediaSample pointer -
    NB needs optimizing
*/
CMediaSample *CSequentialAllocator::SampleFromBuffer(PBYTE pBuffer)
{
    return m_parSamples[BufferIndex(pBuffer)];
}

/*  Add a buffer to the valid list */
void CSequentialAllocator::AddBuffer(CMediaSample *pSample)
{
    /*  Don't get fooled by 0 length buffers ! */
    if (pSample->GetActualDataLength() == 0) {
        return;
    }
    pSample->AddRef();
#ifdef DEBUG
    PBYTE pbBuffer;
    pSample->GetPointer(&pbBuffer);
    ASSERT(m_pbStartValid + m_lValid == pbBuffer);
#endif
    m_lValid += pSample->GetActualDataLength();
}

/*  Step through valid data */
HRESULT CSequentialAllocator::Advance(LONG lAdvance)
{
    /*  For every sample boundary we step over we should Release()
        a buffer
    */
    int iStart = BufferIndex(m_pbStartValid);
    int iEnd = BufferIndex(m_pbStartValid + lAdvance);
    m_lValid -= lAdvance;
    m_pbStartValid += lAdvance;

    /*  If we're at the end and the last buffer wasn't full move on
        to the next
    */
    if (m_lValid == 0 &&
        (m_pbStartValid - m_pBuffer) % m_lSize != 0) {
        iEnd++;
        m_pbStartValid = m_pBuffer + m_lSize * iEnd;
    }
    while (iStart != iEnd) {
        m_parSamples[iStart]->Release();

        iStart++;
    }

    ASSERT(m_lValid <= (m_lCount * m_lSize) / 2);

    /*
        If we're already into the last buffer and about to wrap
        NOTE m_Valid CAN be >  m_lSize - hit it with a huge
        PROGRAM_STREAM_DIRECTORY packet (can be almost 64K)
    */
    if (m_pbStartValid + m_lValid == m_pBuffer + m_lSize * m_lCount) {
        return Wrap();
    } else {
        return S_OK;
    }
}

/*  Get the valid part */
PBYTE CSequentialAllocator::GetValid(LONG *plValid)
{
    *plValid = m_lValid;
    return m_pbStartValid;
}

/*  Wrap end to go back to start */
HRESULT CSequentialAllocator::Wrap(void)
{
    if (m_lValid != 0) {

        /*  Make sure the copy will work */
        ASSERT(m_lValid <= (m_lSize * m_lCount) / 2);
        IMediaSample *pSample;

        /*  These samples will be AddRef'd already */
        int nBuffers = (m_lValid + m_lSize - 1) / m_lSize;

        ASSERT(nBuffers <= m_lCount / 2);
        for (int i = 0; i < nBuffers; i++) {
            HRESULT hr = GetBuffer(&pSample, NULL, NULL, 0);
            if (FAILED(hr)) {
                return hr;
            }
            ASSERT(pSample == m_parSamples[i]);
        }

        /*  Now copy the data back to the start */
        CopyMemory((PVOID)(m_pBuffer + m_lSize * nBuffers - m_lValid),
                   (PVOID)m_pbStartValid,
                   m_lValid);
        m_pbStartValid = m_pBuffer + m_lSize * nBuffers - m_lValid;

        /*  Release the last buffers since we've effectively
            transferred the ref count to the first one
        */
        for ( ; nBuffers > 0; nBuffers--) {
            m_parSamples[m_lCount - nBuffers]->Release();
        }
    } else {
        m_pbStartValid = m_pBuffer;
    }
    return S_OK;
}

/*  Flush the allocator - just discard all the data in it */
void CSequentialAllocator::Flush()
{
    Advance(m_lValid);
}


SPLITTER.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <pullpin.h>
#include <alloc.h>
#include <splitter.h>

/*  Ignore warnings about this pointers being used in member initialization
    lists
*/
#pragma warning(disable:4355)

/*  Splitter output pin methds */

/*  -- Constructor -- */
CSplitterOutputPin::CSplitterOutputPin(
        CBaseSplitterFilter *pFilter,
        HRESULT *phr,
        LPCWSTR pName) :
    CBaseOutputPin(
        NAME("CSplitterOutputPin"),
        pFilter,
        &pFilter->m_csFilter,
        phr,
        pName),
    m_Notify(this),
    m_bDiscontinuity(FALSE),
    m_pOutputQueue(NULL),
    m_lTypes(NAME("CSplitterFilter::m_lTypes"))
{
}

CSplitterOutputPin::~CSplitterOutputPin()
{
    while (m_lTypes.GetCount() != 0) {
        delete m_lTypes.RemoveHead();
    }
}

/* Override revert to normal ref counting
   These pins cannot be finally Release()'d while the input pin is
   connected */

STDMETHODIMP_(ULONG)
CSplitterOutputPin::NonDelegatingAddRef()
{
    return CUnknown::NonDelegatingAddRef();
}

/* Override to do normal ref counting */

STDMETHODIMP_(ULONG)
CSplitterOutputPin::NonDelegatingRelease()
{
    return CUnknown::NonDelegatingRelease();
}

// override this to set the buffer size and count. Return an error
// if the size/count is not to your liking.
HRESULT CSplitterOutputPin::DecideBufferSize(
    IMemAllocator * pAlloc,
    ALLOCATOR_PROPERTIES * pProp
)
{
    pProp->cBuffers = 100;
    pProp->cbBuffer = 65536;            /* Don't care about size */
    pProp->cbAlign = 1;
    pProp->cbPrefix = 0;
    ALLOCATOR_PROPERTIES propActual;
    return pAlloc->SetProperties(pProp, &propActual);
}

//
//  Override DecideAllocator because we insist on our own allocator since
//  it's 0 cost in terms of bytes
//
HRESULT CSplitterOutputPin::DecideAllocator(
    IMemInputPin *pPin,
    IMemAllocator **ppAlloc
)
{
    HRESULT hr = InitAllocator(ppAlloc);
    if (SUCCEEDED(hr)) {
        ALLOCATOR_PROPERTIES propRequest;
        ZeroMemory(&propRequest, sizeof(propRequest));
        hr = DecideBufferSize(*ppAlloc, &propRequest);
        if (SUCCEEDED(hr)) {
            // tell downstream pins that modification
            // in-place is not permitted
            hr = pPin->NotifyAllocator(*ppAlloc, TRUE);
            if (SUCCEEDED(hr)) {
                return NOERROR;
            }
        }
    }

    /* Likewise we may not have an interface to release */

    if (*ppAlloc) {
        (*ppAlloc)->Release();
        *ppAlloc = NULL;
    }
    return hr;
}

// override this to control the connection
// We use the subsample allocator derived from the input pin's allocator
HRESULT CSplitterOutputPin::InitAllocator(IMemAllocator **ppAlloc)
{
    ASSERT(m_pAllocator == NULL);
    HRESULT hr = NOERROR;

    *ppAlloc = new CSubAllocator(NULL, &hr, Filter()->Allocator());
    if (*ppAlloc == NULL) {
        return E_OUTOFMEMORY;
    }

    if (FAILED(hr)) {
        delete *ppAlloc;
        *ppAlloc = NULL;
        return hr;
    }
    /* Get a reference counted IID_IMemAllocator interface */
    (*ppAlloc)->AddRef();
    return NOERROR;
}

//  Check if we accept a media type - only accept 1 for now
HRESULT CSplitterOutputPin::CheckMediaType(const CMediaType *pmt)
{
    POSITION pos = m_lTypes.GetHeadPosition();
    while (pos) {
        CMediaType *pmtList = m_lTypes.GetNext(pos);
        if (*pmtList == *pmt) {
            return S_OK;
        }
    }
    return S_FALSE;
}

// returns the preferred formats for a pin
HRESULT CSplitterOutputPin::GetMediaType(
    int iPosition,
    CMediaType *pMediaType
)
{
    POSITION pos = m_lTypes.GetHeadPosition();
    while (pos) {
        CMediaType *pmtList = m_lTypes.GetNext(pos);
        if (iPosition-- == 0) {
            *pMediaType = *pmtList;
            return S_OK;
        }
    }
    return VFW_S_NO_MORE_ITEMS;
}

/*  Add a media type that's supported */
HRESULT CSplitterOutputPin::AddMediaType(
    CMediaType const *pmt
)
{
    CMediaType *pmtNew = new CMediaType(*pmt);
    if (pmtNew) {
        if (m_lTypes.AddTail(pmtNew)) {
            return S_OK;
        }
    }
    delete pmtNew;
    return E_OUTOFMEMORY;
}

/*  Output pin - Deliver a sample */
HRESULT CSplitterOutputPin::SendSample(
    const BYTE * pbData,
    LONG lData,
    REFERENCE_TIME rtStart,
    BOOL bSync
)
{
    /*  Get a sample from the allocator */
    if (!IsConnected()) {
        return S_FALSE;
    }
    CMediaSample *pSample = Allocator()->GetSample((PBYTE)pbData, lData);
    ASSERT(pSample != NULL);
    if (bSync) {
        REFERENCE_TIME rtStop = rtStart + 1;
        pSample->SetTime(&rtStart, &rtStop);

        /*  Allow for some decoders that only take not of sync points */
        pSample->SetSyncPoint(TRUE);
    }

    /*  First send newsegment etc if discontinuity */
    if (m_bDiscontinuity) {
        m_bDiscontinuity = FALSE;
        pSample->SetDiscontinuity(TRUE);

        /*  HACK - fix this for seeking */
        m_pOutputQueue->NewSegment(0, 0x7F00000000000000, 1.0);
    }

    /*  Output queue will release the sample */
    return m_pOutputQueue->Receive(pSample);
}

/*  Base splitter class methods */

/* -- Constructor -- */

CBaseSplitterFilter::CBaseSplitterFilter(
    TCHAR *pName,
    LPUNKNOWN pUnk,
    REFCLSID rclsid,
    HRESULT *phr) :

    CBaseFilter(pName, pUnk, &m_csFilter, rclsid, phr),
    m_OutputPins(NAME("CBaseSplitterFilter::m_OutputPins")),
    m_Notify(this),
    m_pInput(NULL),
    m_pParser(NULL)
{
}

/* -- Destructor -- */

CBaseSplitterFilter::~CBaseSplitterFilter()
{
    ASSERT(m_State == State_Stopped);

    /* -- Destroy all pins */
    DestroyInputPin();
    DestroyOutputPins();
}

//
//  Override Pause() so we can prevent the input pin from starting
//  the puller before we're ready (ie have exited stopped state)
//
//  Starting the puller in Active() caused a hole where the first
//  samples could be rejected becase we seemed to be in 'stopped'
//  state
//
STDMETHODIMP
CBaseSplitterFilter::Pause()
{
    CAutoLock lockFilter(&m_csFilter);
    if (m_State == State_Stopped) {
        // and do the normal inactive processing
        POSITION pos = m_OutputPins.GetHeadPosition();
        while (pos) {
            CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
            if (pPin->IsConnected()) {
                pPin->Active();
            }
        }

        CAutoLock lockStreaming(&m_csStream);

        //  Activate our input pin only if we're connected
        if (m_pInput->IsConnected()) {
            m_pInput->Active();
        }
        m_State = State_Paused;

        //  Initialize our state
        ResetAllocatorAndParser();
    } else {
        m_State = State_Paused;
    }
    return S_OK;
}

/*  Stop the filter

    We override this for efficiency and so that we can manage the
    locking correctly
*/
STDMETHODIMP CBaseSplitterFilter::Stop()
{
    // must get this one first - it serializes state changes
    CAutoLock lockFilter(&m_csFilter);
    if (m_State == State_Stopped) {
        return NOERROR;
    }

    // decommit the input pin or we can deadlock
    if (m_pInput != NULL) {
        m_pInput->Inactive();
    }

    // now hold the Receive critsec to prevent further Receive and EOS calls,
    CAutoLock lockStreaming(&m_csStream);

    // and do the normal inactive processing
    POSITION pos = m_OutputPins.GetHeadPosition();
    while (pos) {
        CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
        if (pPin->IsConnected()) {
            pPin->Inactive();
        }
    }
    ResetAllocatorAndParser();
    m_State = State_Stopped;
    return S_OK;
}

/*  -- Return number of pins */
int CBaseSplitterFilter::GetPinCount()
{
    CAutoLock lck(&m_csPins);
    return (m_pInput != NULL ? 1 : 0) +
           m_OutputPins.GetCount();
}

/*  -- Return a given pin -- */
CBasePin *CBaseSplitterFilter::GetPin(int iPin)
{
    CAutoLock lck(&m_csPins);
    if (iPin == 0 && m_pInput != NULL) {
        return m_pInput;
    }
    if (m_pInput != NULL) {
        iPin--;
    }
    if (iPin < 0 || iPin >= m_OutputPins.GetCount()) {
        return NULL;
    }
    POSITION pos = m_OutputPins.GetHeadPosition();
    while (iPin-- > 0) {
        m_OutputPins.GetNext(pos);
    }
    return m_OutputPins.GetNext(pos);
}

/*  -- Add an output pin -- */
BOOL CBaseSplitterFilter::AddOutputPin(CSplitterOutputPin *pOutputPin)
{
    CAutoLock lckFilter(&m_csFilter);
    CAutoLock lckPins(&m_csPins);
    ASSERT(m_State == State_Stopped);
    IncrementPinVersion();
    pOutputPin->AddRef();
    if (m_OutputPins.AddTail(pOutputPin) == NULL) {
        delete pOutputPin;
        return FALSE;
    }
    return TRUE;
}

/*  -- Destroy the input pin -- */
void CBaseSplitterFilter::DestroyInputPin()
{
    delete m_pInput;
    m_pInput = NULL;
}

/*  -- Delete the output pins -- */
void CBaseSplitterFilter::DestroyOutputPins()
{
    CAutoLock lckFilter(&m_csFilter);
    CAutoLock lckPins(&m_csPins);
    ASSERT(m_State == State_Stopped);
    for (;;) {
        CSplitterOutputPin *pPin = m_OutputPins.RemoveHead();
        if (pPin == NULL) {
            break;
        }
        IncrementPinVersion();

        //  Disconnect if necessary
        IPin *pPeer = pPin->GetConnected();
        if (pPeer != NULL) {
            pPeer->Disconnect();
            pPin->Disconnect();
        }
        pPin->Release();
    }
}

/*  Notify to create a new stream
    Called back through CParseNotify
*/
HRESULT CBaseSplitterFilter::CreateStream(
    LPCWSTR         pszName,
    CStreamNotify **ppStreamNotify
)
{
    HRESULT hr = S_OK;
    CSplitterOutputPin *pPin =
        new CSplitterOutputPin(this, &hr, pszName);
    if (FAILED(hr)) {
        delete pPin;
        return hr;
    }
    if (pPin == NULL) {
        return E_OUTOFMEMORY;
    }

    if (!AddOutputPin(pPin)) {
        delete pPin;
        return E_OUTOFMEMORY;
    }
    *ppStreamNotify = pPin->GetNotify();
    return S_OK;
}

HRESULT CBaseSplitterFilter::CompleteConnect(IPin *pReceivePin)
{
    m_pParser = CreateParser(&m_Notify, InputPin()->MediaType());
    if (m_pParser == NULL) {
        return E_OUTOFMEMORY;
    }
    /*  Create a reader to initialize the parser */
    CParseReaderFromAsync rdr(InputPin()->Reader());

    HRESULT hr = m_pParser->Init(&rdr);
    if (SUCCEEDED(hr)) {
        ALLOCATOR_PROPERTIES Props, Actual;

        /*  Our allocator doesn't accumulate alignment correctly
            so use the alignment currently set
        */
        Allocator()->GetProperties(&Props);
        m_pParser->GetSizeAndCount(&Props.cbBuffer, &Props.cBuffers);
        ((IMemAllocator *)Allocator())->SetProperties(&Props, &Actual);
    }
    return hr;
}

/*  Process some data */
HRESULT CBaseSplitterFilter::Receive(IMediaSample *pSample)
{
    /*  Notify our allocator about how much data we've got,
        Call the parser to eat the data,
        eat the data that was parsed
    */

    Allocator()->AddBuffer((CMediaSample *)pSample);

    /*  Now eat the data */
    LONG lValid;
    PBYTE pbValid = Allocator()->GetValid(&lValid);
    LONG lProcessed;
    HRESULT hr = m_pParser->Process(pbValid, lValid, &lProcessed);
    if (S_OK != hr) {
        /*  If something goes wrong do error processing */
        return hr;
    }

    Allocator()->Advance(lProcessed);

    /*  Flush the output queues */
    POSITION pos = m_OutputPins.GetHeadPosition();
    while (pos) {
        CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
        pPin->SendAnyway();
    }
    return S_OK;
}

/*  Send EndOfStream */
void CBaseSplitterFilter::EndOfStream()
{
    ResetAllocatorAndParser();
    POSITION pos = m_OutputPins.GetHeadPosition();
    while (pos) {
        CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
        if (pPin->IsConnected()) {
            pPin->DeliverEndOfStream();
        }
    }
}

/*  Send BeginFlush() */
HRESULT CBaseSplitterFilter::BeginFlush()
{
    POSITION pos = m_OutputPins.GetHeadPosition();

    while (pos) {
        CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
        if (pPin->IsConnected()) {
            pPin->DeliverBeginFlush();
        }
    }
    return S_OK;
}

/*  Send EndFlush() */
HRESULT CBaseSplitterFilter::EndFlush()
{
    ResetAllocatorAndParser();
    POSITION pos = m_OutputPins.GetHeadPosition();
    while (pos) {
        CSplitterOutputPin *pPin = m_OutputPins.GetNext(pos);
        if (pPin->IsConnected()) {
            pPin->DeliverEndFlush();
        }
    }
    return S_OK;
}

/*  Break connection on the input pin */
void CBaseSplitterFilter::BreakConnect()
{
    delete m_pParser;
    m_pParser = NULL;
    DestroyOutputPins();
}

/*  -- Input pin methods -- */

/*  Constructor */
CSplitterInputPin::CSplitterInputPin(
    CBaseSplitterFilter *pFilter,
    HRESULT *phr
) : CBaseInputPin(NAME("CSplitterInputPin"),
             pFilter,
             &pFilter->m_csFilter,
             phr,
             L"Input"),
    m_puller(this)
{
}

/*  Connection stuff */
HRESULT CSplitterInputPin::CheckConnect(IPin *pPin)
{
    HRESULT hr = CBaseInputPin::CheckConnect(pPin);
    if (FAILED(hr)) {
        return hr;
    }

    /*  Create our allocator */
    ASSERT(m_pAllocator == NULL);

    m_pAllocator = new CSequentialAllocator(
                           NULL, // No owner - allocators are separate objects
                           &hr);
    if (m_pAllocator == NULL) {
        return E_OUTOFMEMORY;
    }
    if (FAILED(hr)) {
        return hr;
    }

    /*  The base classes expect the allocator to be AddRef'd */
    m_pAllocator->AddRef();

    // BUGBUG CPullPin might change the allocator and not tell us!
    hr = m_puller.Connect(pPin, m_pAllocator, TRUE);
    if (FAILED(hr)) {
        return hr;
    }
    return hr;
}

/*  Break connection - filter already locked by caller */
HRESULT CSplitterInputPin::BreakConnect()
{
    Filter()->BreakConnect();
    m_puller.Disconnect();
    if (m_pAllocator) {
        m_pAllocator->Release();
        m_pAllocator = NULL;
    }
    return CBaseInputPin::BreakConnect();
}

/*  End of stream */
STDMETHODIMP CSplitterInputPin::EndOfStream()
{
    CAutoLock lck(&Filter()->m_csStream);
    HRESULT hr = CheckStreaming();
    if (FAILED(hr)) {
        return hr;
    }

    /*  Forward to the output pins */
    Filter()->EndOfStream();
    return S_OK;
}

/*  Forward BeginFlush() and EndFlush() to filter */
STDMETHODIMP CSplitterInputPin::BeginFlush()
{
    CAutoLock lck(m_pLock);
    CBaseInputPin::BeginFlush();
    Filter()->BeginFlush();
    return S_OK;
}
STDMETHODIMP CSplitterInputPin::EndFlush()
{
    CAutoLock lck(&Filter()->m_csStream);
    CBaseInputPin::EndFlush();
    Filter()->EndFlush();
    return S_OK;
}

/*  Receive - forward to filter */
STDMETHODIMP CSplitterInputPin::Receive(IMediaSample *pSample)
{
    CAutoLock lck(&Filter()->m_csStream);
    HRESULT hr = CheckStreaming();
    if (FAILED(hr)) {
        return hr;
    }
    hr = Filter()->Receive(pSample);
    if (S_OK != hr) {
        NotifyError(hr);
    }
    return hr;
}

HRESULT CSplitterInputPin::Active()
{
    /*  If we're not seekable then just seek to start for now */
    REFERENCE_TIME rtDuration;
    m_puller.Duration(&rtDuration);
    m_puller.Seek(0, rtDuration);
    HRESULT hr = m_puller.Active();
    if (FAILED(hr)) {
        return hr;
    }
    return CBaseInputPin::Active();
}

HRESULT CSplitterInputPin::Inactive()
{
    m_puller.Inactive();
    return CBaseInputPin::Inactive();
}

/*  forward connection stuff to filter */
HRESULT CSplitterInputPin::CheckMediaType(const CMediaType *pmt)
{
    return Filter()->CheckInputType(pmt);
}
HRESULT CSplitterInputPin::CompleteConnect(IPin *pReceivePin)
{
    return Filter()->CompleteConnect(pReceivePin);
}


ALLOC.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  Allocator for sequential buffers
    Like CBaseAllocator BUT always allocates the next buffer
*/

typedef CMediaSample *LPCMEDIASAMPLE;

class CSequentialAllocator : public CMemAllocator
{
public:
    CSequentialAllocator(
        LPUNKNOWN  pUnk,
        HRESULT   *phr
    );

    ~CSequentialAllocator();

    STDMETHODIMP GetBuffer(
        IMediaSample **ppBuffer,
        REFERENCE_TIME * pStartTime,
        REFERENCE_TIME * pEndTime,
        DWORD dwFlags);

    HRESULT Alloc();

    /*  Get buffer index */
    int BufferIndex(PBYTE pbBuffer);

    /*  Given an address get the IMediaSample pointer -
        NB needs optimizing
    */
    CMediaSample *SampleFromBuffer(PBYTE pBuffer);

    /*  Add a buffer to the valid list */
    void AddBuffer(CMediaSample *pSample);

    /*  Step through valid data */
    HRESULT Advance(LONG lAdvance);

    /*  Get the valid part */
    PBYTE GetValid(LONG *plValid);

    /*  Wrap end to go back to start */
    HRESULT Wrap(void);

    /*  Flush the allocator - just discard all the data in it */
    void Flush();

private:
    PBYTE           m_pbNext;
    LPCMEDIASAMPLE *m_parSamples;

    /*  Simple wrap around buffer stuff */
    LONG            m_lValid;
    PBYTE           m_pbStartValid;
    PBYTE           m_pBuffer;  /* Copy of CMemAllocator's which is private */
};

/*  Allocator for subsamples */
class CSubAllocator : public CBaseAllocator
{
public:
    CSubAllocator(
        LPUNKNOWN  pUnk,
        HRESULT   *phr,
        CSequentialAllocator *pAlloc
    ) : CBaseAllocator(NAME("CSubAllocator"), pUnk, phr),
        m_pAlloc(pAlloc)
    {
    }

    CMediaSample *GetSample(PBYTE pbData, DWORD dwLen)
    {
        HRESULT hr = S_OK;
        CMediaSample *pSample = new CMediaSample(
                                        NAME("CMediaSample"),
                                        this,
                                        &hr,
                                        pbData,
                                        dwLen);
        if (pSample != NULL) {

            /*  We only need to lock the first buffer because
                the super allocator allocates samples sequentially
                so it can't allocate subsequent samples until
                the first one has been freed
            */
            m_pAlloc->SampleFromBuffer(pbData)->AddRef();

            /*  AddRef() ourselves too to conform to the rules */
            pSample->AddRef();

            /*  Make sure WE don't go away too ! */
            AddRef();
        }
        return pSample;
    }

    STDMETHODIMP ReleaseBuffer(IMediaSample * pSample)
    {
        /*  Free the superallocator's buffer */
        CMediaSample *pMediaSample = (CMediaSample *)pSample;
        PBYTE pBuffer;
        pSample->GetPointer(&pBuffer);
        m_pAlloc->SampleFromBuffer(pBuffer)->Release();
        delete pMediaSample;

        Release();
        return NOERROR;
    }

    /*  Must override Free() */
    void Free() {}

private:
    CSequentialAllocator *const m_pAlloc;
};

/*  Track samples in buffer */


MPEG2IDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//
//  MPEG2IDS.H
//

//  GUIDs for use with MPEG-2, AC3, DVD and related projects
//  To reserve a GUID just annotate its usage to the right of its value

//  Major types and subtypes for media types

DEFINE_GUID(MEDIATYPE_MPEG2_PES,
    0xe06d8020, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIATYPE_CONTROL,
    0xe06d8021, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIASUBTYPE_MPEG2_PROGRAM, 0xe06d8022, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIASUBTYPE_MPEG2_TRANSPORT,
    0xe06d8023, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIASUBTYPE_DVD                           ,
    0xe06d8024, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
#if 0 // UNUSED
DEFINE_GUID(, 0xe06d8025, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
#endif // UNUSED
DEFINE_GUID(MEDIASUBTYPE_MPEG2_VIDEO,
    0xe06d8026, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
#if 0 // UNUSED
DEFINE_GUID(, 0xe06d8027, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8028, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8029, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d802a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
#endif // UNUSED
DEFINE_GUID(MEDIASUBTYPE_MPEG2_AUDIO,
    0xe06d802b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIASUBTYPE_DOLBY_AC3,
    0xe06d802c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(MEDIASUBTYPE_SUBPICTURE ,
    0xe06d802d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);

#if 0
DEFINE_GUID(, 0xe06d802e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d802f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8030, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8031, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8032, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8033, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8034, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8035, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8036, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8037, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8038, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8039, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d803f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8040, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8041, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8042, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8043, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8044, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8045, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8046, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8047, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8048, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8049, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d804f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8050, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8051, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8052, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8053, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8054, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8055, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8056, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8057, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8058, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8059, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d805f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8060, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8061, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8062, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8063, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8064, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8065, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8066, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8067, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8068, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8069, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d806f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8070, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8071, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8072, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8073, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8074, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8075, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8076, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8077, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8078, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8079, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d807f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8080, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8081, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8082, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8083, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8084, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8085, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8086, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8087, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8088, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8089, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d808f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8090, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8091, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8092, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8093, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8094, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8095, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8096, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8097, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8098, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8099, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d809f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80a9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80aa, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ab, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ac, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ad, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ae, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80af, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80b9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ba, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80bb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80bc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80bd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80be, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80bf, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80c9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ca, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80cb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80cc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80cd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ce, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80cf, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80d9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80da, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80db, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80dc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80dd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80de, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80df, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);

//
//  Format blocks
//

#endif // UNUSED
DEFINE_GUID(FORMAT_MPEG2Video,
0xe06d80e3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);

#if 0 // UNUSED
DEFINE_GUID(, 0xe06d80e4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80e9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ea, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80eb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ec, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ed, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ee, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ef, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80f9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80fa, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80fb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80fc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80fd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80fe, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d80ff, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8100, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8101, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8102, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8103, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8104, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8105, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8106, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8107, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8108, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8109, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d810f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8110, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8111, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8112, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8113, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8114, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8115, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8116, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8117, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8118, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8119, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d811f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8120, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8121, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8122, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8123, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8124, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8125, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8126, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8127, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8128, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8129, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d812f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8130, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8131, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8132, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8133, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8134, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8135, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8136, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8137, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8138, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8139, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d813f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8140, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8141, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8142, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8143, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8144, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8145, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8146, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8147, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8148, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8149, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d814f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8150, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8151, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8152, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8153, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8154, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8155, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8156, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8157, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8158, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8159, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d815f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8160, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);

DEFINE_GUID(, 0xe06d8161, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8162, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8163, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8164, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8165, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8166, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8167, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8168, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8169, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d816f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8170, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8171, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8172, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8173, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8174, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8175, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8176, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8177, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8178, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8179, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d817f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8180, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8181, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8182, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8183, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8184, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8185, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8186, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8187, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8188, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8189, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d818f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8190, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8191, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8192, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8193, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8194, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8195, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8196, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8197, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8198, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8199, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d819f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81a9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81aa, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ab, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ac, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ad, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ae, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81af, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81b9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ba, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81bb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81bc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81bd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81be, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81bf, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81c9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ca, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81cb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81cc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81cd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ce, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81cf, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81d9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81da, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81db, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81dc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81dd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81de, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81df, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81e9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ea, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81eb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ec, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ed, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ee, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ef, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f0, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f1, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f2, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f3, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f4, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f5, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f6, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f7, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f8, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81f9, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81fa, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81fb, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81fc, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81fd, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81fe, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d81ff, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8200, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8201, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8202, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8203, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8204, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8205, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8206, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8207, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8208, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8209, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d820f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8210, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8211, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8212, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8213, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8214, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8215, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8216, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8217, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8218, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d8219, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821a, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821b, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821c, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821d, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821e, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
DEFINE_GUID(, 0xe06d821f, 0xdb46, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x05f, 0x6c, 0xbb, 0xea);
#endif


MPEG2TYP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*  Define video type for MPEG-2 */
#ifndef __MPEG2TYP__
#define __MPEG2TYP__

#ifdef _WIN32
#include <pshpack1.h>
#else
#ifndef RC_INVOKED
#pragma pack(1)
#endif
#endif

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/*  MPEG-2 stuff */

#define MPEG2VIDEOINFO_PROFILE_SIMPLE             5
#define MPEG2VIDEOINFO_PROFILE_MAIN               4
#define MPEG2VIDEOINFO_PROFILE_SNR_SCALABLE       3
#define MPEG2VIDEOINFO_PROFILE_SPATIALLY_SCALABLE 2
#define MPEG2VIDEOINFO_PROFILE_HIGH               1

#define MPEG2VIDEINFO_LEVEL_LOW                   10
#define MPEG2VIDEINFO_LEVEL_MAIN                  8
#define MPEG2VIDEINFO_LEVEL_HIGH_1440             6
#define MPEG2VIDEINFO_LEVEL_HIGH                  4

typedef struct tagMPEG2VIDEOINFO
{
    /* -- Matches VIDEOINFOHEADER -- */

    RECT            rcSource;          // The bit we really want to use
    RECT            rcTarget;          // Where the video should go
    DWORD           dwBitRate;         // Approximate bit data rate
    DWORD           dwBitErrorRate;    // Bit error rate for this stream
    REFERENCE_TIME  AvgTimePerFrame;   // Average time per frame (100ns units)

    BITMAPINFOHEADER bmiHeader;

    /* ----------------------------- */

    DWORD           dwProfile;              // Which profile
    DWORD           dwLevel;                // What level
    DWORD           dwStartTimeCode;        // 25-bit Group of pictures time code
                                            // at start of data
    DWORD           cbSequenceHeader;       // Length in bytes of bSequenceHeader
    BYTE            bSequenceHeader[1];     // Sequence header including
                                            // quantization matrices if any
                                            // and extension

} MPEG2VIDEOINFO;

/*  AC3 audio

    wFormatTag          WAVE_FORMAT_DOLBY_AC3
    nChannels           1 -6 channels valid
    nSamplesPerSec      48000, 44100, 32000
    nAvgByesPerSec      4000 to 80000
    nBlockAlign         128 - 3840
    wBitsPerSample      Up to 24 bits - (in the original)

*/

#define WAVE_FORMAT_DOLBY_AC3 0x2000

typedef struct tagDOLBYAC3WAVEFORMAT
{
    WAVEFORMATEX     wfx;
    BYTE             bBigEndian;       /* TRUE = Big Endian, FALSE little endian */
    BYTE             bsid;
    BYTE             lfeon;
    BYTE             copyrightb;
    BYTE             nAuxBitsCode;  /*  Aux bits per frame */
} DOLBYAC3WAVEFORMAT;

#ifdef __cplusplus
}
#endif // __cplusplus

#ifdef _WIN32
#include <poppack.h>
#else
#ifndef RC_INVOKED
#pragma pack()
#endif
#endif
#endif // __AMVIDEO__


MPEGDEF.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//

/*
    mpegdef.h

    This file defines the externals for interfacing with MPEG
    components
*/

/*

    MPEG constants

*/

#define MPEG_TIME_DIVISOR (90000)
#define MPEG_MAX_TIME ((LONGLONG)0x200000000)

#define PICTURE_START_CODE       0x00000100
#define USER_DATA_START_CODE     0x000001B2
#define SEQUENCE_HEADER_CODE     0x000001B3
#define SEQUENCE_ERROR_CODE      0x000001B4
#define EXTENSION_START_CODE     0x000001B5
#define SEQUENCE_END_CODE        0x000001B7
#define GROUP_START_CODE         0x000001B8

#define ISO_11172_END_CODE       0x000001B9
#define PACK_START_CODE          0x000001BA
#define SYSTEM_HEADER_START_CODE 0x000001BB
#define PADDING_START_CODE       0x000001BE
#define PACKET_START_CODE_MIN    0x000001BC
#define PACKET_START_CODE_MAX    0x000001FF

#define AUDIO_GLOBAL             0xB8
#define VIDEO_GLOBAL             0xB9
#define RESERVED_STREAM          0xBC
#define PRIVATE_STREAM_1         0xBD
#define PADDING_STREAM           0xBE
#define PRIVATE_STREAM_2         0xBF
#define AUDIO_STREAM             0xC0
#define AUDIO_STREAM_MASK        0xE0
#define VIDEO_STREAM             0xE0
#define VIDEO_STREAM_MASK        0xF0
#define DATA_STREAM              0xF0
#define DATA_STREAM_MASK         0xF0

/*  MPEG-2 stuff */
#define PROGRAM_STREAM_DIRECTORY 0xFF
#define PROGRAM_STREAM_MAP       0xBC
#define ANCILLIARY_STREAM        0xF9
#define ECM_STREAM               0xF0
#define EMM_STREAM               0xF1

#define VALID_PACKET(data)      (((data) >= PACKET_START_CODE_MIN)  \
                              && ((data) <= PACKET_START_CODE_MAX))

#define VALID_SYSTEM_START_CODE(data)     \
       (VALID_PACKET(data)                \
    ||  (data) == SYSTEM_HEADER_START_CODE\
    ||  (data) == PACK_START_CODE         \
    ||  (data) == ISO_11172_END_CODE)

/*  Types of stream */
inline BOOL IsVideoStreamId(BYTE StreamId)
{
    return (StreamId & 0xF0) == 0xE0;
} ;
inline BOOL IsAudioStreamId(BYTE StreamId)
{
    return (StreamId & 0xE0) == 0xC0;
} ;

#define MAX_MPEG_PACKET_SIZE (65535+6)

/*  Lengths of the various structures */
#define PACK_HEADER_LENGTH 12
#define SYSTEM_HEADER_BASIC_LENGTH 12

inline DWORD DWORD_SWAP(DWORD x)
{
    return
     ((DWORD)( ((x) << 24) | ((x) >> 24) |
               (((x) & 0xFF00) << 8) | (((x) & 0xFF0000) >> 8)));
}

/*  Video definitions */

/*  Frame types as defined in a picture header */
#define I_Frame 1
#define D_Frame 4
#define P_Frame 2
#define B_Frame 3


MPGUTIL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  MPEG-2 utility functions */

/*  Packet header info */
typedef struct tag_MPEG_PACKET_DATA {
    DWORD    dwHeaderLen;
    DWORD    dwPacketLen;
    BOOL     bHasPts;
    LONGLONG llPts;
} MPEG_PACKET_DATA;

/*  Read an normal MPEG-1 style clock */
BOOL GetClock(const BYTE * pData, LONGLONG *Clock);

/*  Read an extended clock */
BOOL GetExtendedClock(const BYTE * pbData, LONGLONG *pllClock);

inline GetStartCode(const BYTE * pbData)
{
    return DWORD_SWAP(*(UNALIGNED DWORD *)pbData);
}

/*  Since dwCode is normally a constant just swap that instead */
inline BOOL IsStartCode(const BYTE * pbData, DWORD dwCode)
{
    return DWORD_SWAP(dwCode) == *(UNALIGNED DWORD *)pbData;
}

/*  Parse pack header

    Parameters :
        pbData - pointer to data containing the system header
        cbData - length of data

    Returns :
        Number of bytes processed (0 if the header doesn't fit
        in the data or 4 if the header is invalid)
        We will return 0 if we can't see the next start code to
        check whether it's a system header start code
*/

DWORD ParseMPEG2PackHeader(const BYTE * pbData, DWORD cbData);

/*  Length of pack header - returns 0 if < cbData */
inline DWORD MPEG2PackHeaderLength(const BYTE * pbData, DWORD cbData)
{
    /*  Need more data */
    if (cbData < 14) {
        return 0;
    } else {
        DWORD dwLen = 14 + (pbData[13] & 0x07);
        if (dwLen > cbData) {
            return 0;
        } else {
            return dwLen;
        }
    }
}

/*  Parse system header

    Parameters :
        pbData - pointer to data containing the system header
        cbData - length of data

    Returns :
        Number of bytes processed (0 if the header doesn't fit
        in the data or 4 if the header is invalid)
*/

LONG ParseSystemHeader(const BYTE * pbData, DWORD cbData);

/*  Parse an MPEG2 packet and extract information */
DWORD ParseMPEG2Packet(
    const BYTE * pbData,
    DWORD cbData,
    MPEG_PACKET_DATA *pPacketData
);

/*  Parse an MPEG1 packet and extract information */
DWORD ParseMPEG1Packet(
    const BYTE * pbData,
    DWORD cbData,
    MPEG_PACKET_DATA *pPacketData
);

BOOL ParseMPEG2PacketHeader(
    const BYTE * pbData,
    DWORD dwLen,
    MPEG_PACKET_DATA *pPacketData
);

/*  Inlines to get PTSs from MPEG2 packets */
inline BOOL MPEG2PacketHasPTS(const BYTE * pbPacket)
{
    /*  Just check if the PTS_DTS_flags are 10 or 11 (ie the first
        bit is one
    */
    return 0 != (pbPacket[7] & 0x80);
}

/*  Get the PTS from an MPEG2 packet */
LONGLONG MPEG2PacketPTS(const BYTE * pbPacket);

typedef struct {
    DWORD          dwProfile;          //  Profile - MPEG2 only
    DWORD          dwLevel;            //  Level - MPEG2 only
    LONG           lWidth;             //  Native Width in pixels
    LONG           lHeight;            //  Native Height in pixels
    LONG           lvbv;               //  vbv
    REFERENCE_TIME  tPictureTime;      //  Time per picture in 100ns units
    float          fPictureRate;       //  In frames per second
    LONG           lTimePerFrame;      //  Time per picture in MPEG units
    LONG           dwBitRate;          //  Bits per second
    LONG           lXPelsPerMeter;     //  Pel aspect ratio
    LONG           lYPelsPerMeter;     //  Pel aspect ratio
    DWORD          dwStartTimeCode;    //  First GOP time code (or -1)
    LONG           lActualHeaderLen;   //  Length of valid bytes in raw seq hdr
    BYTE           RawHeader[140];     //  The real sequence header
} SEQHDR_INFO;

/*  MPEG2 stuff */
typedef struct {
    BOOL           bExtensionPresent;
    BOOL           bDisplayExtensionPresent;
    BOOL           bScalableExtensionPresent;
    SEQHDR_INFO    seqhdrInfo;

} MPEG2_SEQHDR_INFO;

/*  Helper */
int inline SequenceHeaderSize(const BYTE *pb)
{
    /*  No quantization matrices ? */
    if ((pb[11] & 0x03) == 0x00) {
        return 12;
    }
    /*  Just non-intra quantization matrix ? */
    if ((pb[11] & 0x03) == 0x01) {
        return 12 + 64;
    }
    /*  Intra found - is there a non-intra ? */
    if (pb[11 + 64] & 0x01) {
        return 12 + 64 + 64;
    } else {
        return 12 + 64;
    }
}

/*  Extract info from video sequence header

    Returns FALSE if the sequence header is invalid
*/

BOOL ParseSequenceHeader(const BYTE *pbData, LONG lData, SEQHDR_INFO *hdrInfo);

BOOL ParseAudioHeader(const BYTE * pbData, MPEG1WAVEFORMAT *pFormat);

/*  Construct a media type from the video info */
HRESULT GetVideoMediaType(CMediaType *cmt, BOOL bPayload, const SEQHDR_INFO *pInfo);

/*  Find the next start code */
BOOL NextStartCode(const BYTE * *ppbData, DWORD *pdwLeft);

/*  Parse AC3 header */
BOOL ParseAC3Header(const BYTE * pbData, DOLBYAC3WAVEFORMAT *pwf);


SPLITTER.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  Parser class - this class defines the object that actually splits
    out the data
*/

/*  CBaseParser class

    This is the object that determines the nature of the data
    and actually splits the stream
*/

/*  Simple stream reader class */
class CParseReader
{
public:
    /*  Set the position */
    virtual HRESULT Length(LONGLONG *pLength) = 0;
    virtual HRESULT SetPointer(LONGLONG) = 0;
    virtual HRESULT Read(PBYTE pbData, DWORD cbData) = 0;
};

/*  Parsing reader from CAsyncReader */
class CParseReaderFromAsync : public CParseReader
{
public:
    CParseReaderFromAsync(IAsyncReader *pRdr) :
        m_pReader(pRdr), m_llPos(0) {};
    HRESULT Length(LONGLONG *pLength)
    {
        LONGLONG llAvailable;
        return m_pReader->Length(pLength, &llAvailable);
    }
    HRESULT SetPointer(LONGLONG llPos)
    {
        m_llPos = 0;
        return S_OK;
    }
    HRESULT Read(PBYTE pbData, DWORD cbData)
    {
        HRESULT hr = m_pReader->SyncRead(m_llPos, (LONG)cbData, pbData);
        if (S_OK == hr) {
            m_llPos += cbData;
        }
        return hr;
    }

private:
    IAsyncReader *m_pReader;
    LONGLONG      m_llPos;
};
class CParserNotify;
class CBaseParser
{
public:
    /*  Instantiate a parser

        pNotify - to call back the calling object to create streams etc
    */
    CBaseParser(CParserNotify *pNotify,
                HRESULT *phr) : m_pNotify(pNotify) {};

    /*  Initialize a parser

        pmt     - type of stream if known - can be NULL
        pRdr    - way to read the source medium - can be NULL
    */
    virtual HRESULT Init(
        CParseReader *pRdr
    ) = 0;

    /*  Get the size and count of buffers preferred based on the
        actual content
    */
    virtual void GetSizeAndCount(LONG *plSize, LONG *plCount) = 0;

    /*  Call this to reinitialize for a new stream */
    virtual void StreamReset() = 0;

    /*  Call this to pass new stream data :

        pbData        - pointer to data
        lData         - length of data
        plProcessed   - Amount of data consumed
    */
    virtual HRESULT Process(
        const BYTE *pbData,
        LONG lData,
        LONG *plProcessed
    ) = 0;
protected:
    CParserNotify * const m_pNotify;
};

/*  Parser calls back to create streams and spit out
    buffers
*/
class CStreamNotify;
class CParserNotify
{
public:

    /*  Create an output stream with type *pmt, notifications
        to this stream passed to the **pStreamNotify object
    */
    virtual HRESULT CreateStream(
        LPCWSTR pszName,
        CStreamNotify **pStreamNotify) = 0;

};

class CStreamNotify
{
public:
    virtual HRESULT SendSample(
        const BYTE *pbData,
        LONG lData,
        REFERENCE_TIME rtStart,
        BOOL bSync
    ) = 0;

    /*  Add a media type that's supported */
    virtual HRESULT AddMediaType(CMediaType const *pmt) = 0;

    /*  Return the current type */
    virtual void CurrentMediaType(AM_MEDIA_TYPE *pmt) = 0;
};

/*  Splitter filter base classes */

/*  Design :

    A splitter filter will have 1 input pin and multiple output pins

    CBaseSplitterFilter defines a base filter with 1 input pin and
    a list of output pins.

    This base class provides for pin enumeration, correct distribution
    of EndOfStream and handling of errors.

    The object structure is :

                            IsA

    CBaseSplitterFilter  <--------  CBaseFilter

    Allocators:

        This class relies on use CSequentialAllocator

        The input pin is designed around CPullPin which hooks up
        to IAsyncReader on the upstream output pin
*/

class CBaseSplitterFilter;
class CBaseParser;

/*  Input pin stuff

    The input pin deletes all the output pins when it's disconnected

    On connection the output pins are created based on the media type
    and possibly on the file content

    The base class handles things like flushing and end of stream
*/

/*  Special output pin type to handle lifetime */
class CSplitterOutputPin : public CBaseOutputPin
{

public:

    //  Constructor
    CSplitterOutputPin(
        CBaseSplitterFilter *pFilter,
        HRESULT *phr,
        LPCWSTR pName);

    ~CSplitterOutputPin();

    //  CUnknown methods
    STDMETHODIMP_(ULONG) NonDelegatingAddRef();
    STDMETHODIMP_(ULONG) NonDelegatingRelease();

    //  CBaseOutputPin methods - we just override these to do
    //  our own hack allocator

    // override this to set the buffer size and count. Return an error
    // if the size/count is not to your liking
    virtual HRESULT DecideBufferSize(
                        IMemAllocator * pAlloc,
                        ALLOCATOR_PROPERTIES * pProp);

    // negotiate the allocator and its buffer size/count
    // calls DecideBufferSize to call SetCountAndSize
    virtual HRESULT DecideAllocator(IMemInputPin * pPin, IMemAllocator ** pAlloc);

    // override this to control the connection
    virtual HRESULT InitAllocator(IMemAllocator **ppAlloc);

    // Check the media type proposed
    HRESULT CheckMediaType(const CMediaType *);

    // returns the preferred formats for a pin
    HRESULT GetMediaType(int iPosition,CMediaType *pMediaType);

    // Send sample generated by parser
    HRESULT SendSample(
        const BYTE *pbData,
        LONG lData,
        REFERENCE_TIME rtStart,
        BOOL bSync
    );

    /*  Add a media type */
    HRESULT AddMediaType(
        CMediaType const *pmt
    );

    HRESULT DeliverEndOfStream()
    {
        m_pOutputQueue->EOS();
        return S_OK;
    }

    //  Delete our output queue on inactive
    HRESULT Inactive()
    {
        HRESULT hr = CBaseOutputPin::Inactive();
        if (FAILED(hr)) {
            return hr;
        }

        delete m_pOutputQueue;
        m_pOutputQueue = NULL;
        return S_OK;
    }

    /*  Wrapper to call output queue to flush samples */
    void SendAnyway()
    {
        if (NULL != m_pOutputQueue) {
            m_pOutputQueue->SendAnyway();
        }
    }

    //  Override Active and EndFlush to set the discontinuity flag
    HRESULT Active()
    {
        m_bDiscontinuity = TRUE;
        /*  If we're not connected we don't participate so it's OK */
        if (!IsConnected()) {
            return S_OK;
        }

        HRESULT hr = CBaseOutputPin::Active();
        if (FAILED(hr)) {
            return hr;
        }

        /*  Create our batch list */
        ASSERT(m_pOutputQueue == NULL);

        hr = S_OK;
        m_pOutputQueue = new COutputQueue(GetConnected(), // input pin
                                          &hr,            // return code
                                          FALSE,          // Auto detect
                                          TRUE,           // ignored
                                          50,             // batch size
                                          TRUE,           // exact batch
                                          50);            // queue size
        if (m_pOutputQueue == NULL) {
            return E_OUTOFMEMORY;
        }
        if (FAILED(hr)) {
            delete m_pOutputQueue;
            m_pOutputQueue = NULL;
        }
        return hr;
    }
    HRESULT DeliverBeginFlush()
    {
        /*  We're already locked via the input pin */
        m_pOutputQueue->BeginFlush();
        return S_OK;
    }
    HRESULT DeliverEndFlush()
    {
        /*  We're already locked via the input pin */
        m_bDiscontinuity = TRUE;
        m_pOutputQueue->EndFlush();
        return S_OK;
    }

    //  Get our notify object
    CStreamNotify *GetNotify()
    {
        return &m_Notify;
    }

protected:
    //  Get a pointer to our allocator
    CSubAllocator *Allocator()
    {
        return (CSubAllocator *)m_pAllocator;
    }

    //  Get a properly cast pointer to our filter
    CBaseSplitterFilter *Filter()
    {
        return (CBaseSplitterFilter *)m_pFilter;
    }

protected:
    //  Stream notify stuff
    class CImplStreamNotify : public CStreamNotify
    {
    public:
        CImplStreamNotify(CSplitterOutputPin *pPin) : m_pPin(pPin) {}
        HRESULT SendSample(
            const BYTE *pbData,
            LONG lData,
            REFERENCE_TIME rtStart,
            BOOL bSync
        )
        {
            return m_pPin->SendSample(pbData, lData, rtStart, bSync);
        }

        HRESULT AddMediaType(CMediaType const *pmt)
        {
            return m_pPin->AddMediaType(pmt);
        }
        void CurrentMediaType(AM_MEDIA_TYPE *pmt)
        {
            m_pPin->ConnectionMediaType(pmt);
        }

    private:
        CSplitterOutputPin * const m_pPin;
    };

    CImplStreamNotify  m_Notify;

    //  Remember when to send NewSegment and discontinuity */
    BOOL               m_bDiscontinuity;

    //  Output queue
    COutputQueue      *m_pOutputQueue;

    //  List of media types we support
    CGenericList<CMediaType> m_lTypes;
};

/*  Base CSplitterInputPin on CPullPin
*/
class CSplitterInputPin : public CBaseInputPin
{
public:
    CSplitterInputPin(
        CBaseSplitterFilter *pFilter,
        HRESULT *phr);

    /*  NonDelegating IUnknown methods - we don't support IMemInputPin */
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv)
    {
        if (riid == IID_IMemInputPin) {
            return E_NOINTERFACE;
        }
        return CBaseInputPin::NonDelegatingQueryInterface(riid, ppv);
    }

    /*  IPin methods */
    STDMETHODIMP EndOfStream();
    STDMETHODIMP BeginFlush();
    STDMETHODIMP EndFlush();
    STDMETHODIMP Receive(IMediaSample *pSample);

    /*  IMemInputPin methods */

    /*  Where we're told which allocator we are using */
    STDMETHODIMP NotifyAllocator(IMemAllocator *pAllocator)
    {
        if (pAllocator != (IMemAllocator *)m_pAllocator) {
            return E_FAIL;
        } else {
            return S_OK;
        }
    }

    /*  Say if we're blocking */
    STDMETHODIMP ReceiveCanBlock()
    {
        return S_FALSE;
    }

    /*  CBasePin methods */
    HRESULT BreakConnect();  //  Override to release puller
    HRESULT CheckConnect(IPin *pPin);  //  Override to connect to puller
    HRESULT Active();
    HRESULT Inactive();
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT CompleteConnect(IPin *pReceivePin);

    /*  Report filter from reader */
    void NotifyError(HRESULT hr)
    {
        if (FAILED(hr)) {
            m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0);
        }
        EndOfStream();
    };

    /*  Convenient way to get the allocator */
    CSequentialAllocator *Allocator()
    {
        return (CSequentialAllocator *)m_pAllocator;
    }

    /*  Point to our media type */
    CMediaType *MediaType()
    {
        return &m_mt;
    }

    /*  Return our async reader */
    IAsyncReader *Reader()
    {
        IAsyncReader *pReader = m_puller.GetReader();
        pReader->Release();
        return pReader;
    }

private:

    //  Get a properly case pointer to our filter
    CBaseSplitterFilter *Filter()
    {
        return (CBaseSplitterFilter *)m_pFilter;
    }

    // class to pull data from IAsyncReader if we detect that interface
    // on the output pin
    class CImplPullPin : public CPullPin
    {
        // forward everything to containing pin
        CSplitterInputPin * const m_pPin;

    public:
        CImplPullPin(CSplitterInputPin* pPin)
          : m_pPin(pPin)
        {
        };

        // forward this to the pin's IMemInputPin::Receive
        HRESULT Receive(IMediaSample* pSample) {
            return m_pPin->Receive(pSample);
        };

        // override this to handle end-of-stream
        HRESULT EndOfStream(void) {
            return m_pPin->EndOfStream();
        };

        // these errors have already been reported to the filtergraph
        // by the upstream filter so ignore them
        void OnError(HRESULT hr) {
            // ignore VFW_E_WRONG_STATE since this happens normally
            // during stopping and seeking
            if (hr != VFW_E_WRONG_STATE) {
                m_pPin->NotifyError(hr);
            }
        };

        // flush the pin and all downstream
        HRESULT BeginFlush() {
            return m_pPin->BeginFlush();
        };

        // Tell the next guy we've finished flushing
        HRESULT EndFlush() {
            return m_pPin->EndFlush();
        };

    };
    CImplPullPin m_puller;
};

class CBaseSplitterFilter : public CBaseFilter
{
friend class CSplitterOutputPin;
friend class CSplitterInputPin;

public:
    //  Constructor and destructor
    CBaseSplitterFilter(
       TCHAR *pName,
       LPUNKNOWN pUnk,
       REFCLSID rclsid,
       HRESULT *phr);
    ~CBaseSplitterFilter();

    //  IMediaFilter methods - override these to manage locking
    STDMETHODIMP Stop();
    STDMETHODIMP Pause();

    /*  Stream control stuff */
    virtual HRESULT BeginFlush();
    virtual HRESULT EndFlush();
    virtual void    EndOfStream();
    virtual HRESULT Receive(IMediaSample *pSample);
    virtual HRESULT CheckInputType(const CMediaType *pmt) = 0;
    virtual HRESULT CompleteConnect(IPin *pReceivePin);

    //  Create the parser
    virtual CBaseParser *CreateParser(
        CParserNotify *pNotify,
        CMediaType *pType) = 0;

    //  CBaseFilter methods
    int GetPinCount();     // 0 pins
    CBasePin *GetPin(int iPin);

    /*  Add a new output pin */
    BOOL AddOutputPin(CSplitterOutputPin *pOutputPin);

    /*  Destroy the output pins */
    void DestroyOutputPins();

    /*  Destroy the input pin */
    void DestroyInputPin();

    /*  Notify to create a new stream
        Called back through CParseNotify
    */
    HRESULT CreateStream(
        LPCWSTR         pszName,
        CStreamNotify **ppStreamNotify
    );

    /*  Called when BreakConnect called on the input pin */
    virtual void BreakConnect();

protected:

    /*  Utility to get the allocator */
    CSequentialAllocator *Allocator()
    {
        return InputPin()->Allocator();
    }

    /*  Reset the states of the allocator and parser
        ready to receive a new stream
    */
    void ResetAllocatorAndParser()
    {
        Allocator()->Flush();
        m_pParser->StreamReset();
    }

    CSplitterInputPin *InputPin()
    {
        return (CSplitterInputPin *)m_pInput;
    }

protected:

    //  Our input pin - created by derived classes
    CBaseInputPin           *m_pInput;

    //  Our output pin list - Derived classes append to this
    CGenericList<CSplitterOutputPin> m_OutputPins;

    //  Filter locking
    CCritSec                 m_csFilter;

    //  Streaming lock
    CCritSec                 m_csStream;

    //  Pin database lock
    CCritSec                 m_csPins;

    //  Parser - created when we're connected
    CBaseParser             *m_pParser;

    //  Parser notify - use member variable to avoid mulitple inheritance
    class CImplParserNotify : public CParserNotify
    {
    public:
        CImplParserNotify(CBaseSplitterFilter *pFilter) : m_pFilter(pFilter) {};
        HRESULT CreateStream(
            LPCWSTR         pszName,
            CStreamNotify **ppStreamNotify
        )
        {
            return m_pFilter->CreateStream(pszName, ppStreamNotify);
        }
    private:
        CBaseSplitterFilter * const m_pFilter;

    };
    CImplParserNotify        m_Notify;
};


MPEG1.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <pullpin.h>
#include <mmreg.h>
#include <mpeg2typ.h>
#include <alloc.h>
#include <splitter.h>
#include <mpegdef.h>
#include <mpgutil.h>
#include <mpeg1.h>
#include <initguid.h>
//  f28300a0-f0cc-11cf-93d5-0080c795857f
DEFINE_GUID(CLSID_MPEG1Sample,
    0xf28300a0, 0xf0cc, 0x11cf, 0x93, 0xd5, 0x00, 0x80, 0xc7, 0x95, 0x85, 0x7f);

/*  Stuff to make this into a filter */
/* List of class IDs and creator functions for the class factory. This
   provides the link between the OLE entry point in the DLL and an object
   being created. The class factory will call the static CreateInstance
   function when it is asked to create a CLSID_MPEG1Splitter object */

CFactoryTemplate g_Templates[1] = {
    {L"MPEG1 Sample", &CLSID_MPEG1Sample, CMPEG1SplitterFilter::CreateInstance}
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

/* This goes in the factory template table to create new instances */

CUnknown *CMPEG1SplitterFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    CUnknown *pUnkRet = new CMPEG1SplitterFilter(pUnk, phr);
    return pUnkRet;
}

/*  Registration setup stuff */
//  Setup data

AMOVIESETUP_MEDIATYPE sudMpgInputType[] =
{
    { &MEDIATYPE_Stream, &MEDIASUBTYPE_MPEG1System }
};
AMOVIESETUP_MEDIATYPE sudMpgAudioOutputType[] =
{
    { &MEDIATYPE_Audio, &MEDIASUBTYPE_MPEG1AudioPayload }
};
AMOVIESETUP_MEDIATYPE sudMpgVideoOutputType[] =
{
    { &MEDIATYPE_Video, &MEDIASUBTYPE_MPEG1Payload }
};

AMOVIESETUP_PIN sudMpgPins[3] =
{
    { L"Input",
      FALSE,                               // bRendered
      FALSE,                               // bOutput
      FALSE,                               // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgInputType),            // Number of media types
      sudMpgInputType
    },
    { L"Audio Output",
      FALSE,                               // bRendered
      TRUE,                                // bOutput
      TRUE,                                // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgAudioOutputType),      // Number of media types
      sudMpgAudioOutputType
    },
    { L"Video Output",
      FALSE,                               // bRendered
      TRUE,                                // bOutput
      TRUE,                                // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgVideoOutputType),      // Number of media types
      sudMpgVideoOutputType
    }
};

AMOVIESETUP_FILTER sudMpgsplit =
{
    &CLSID_MPEG1Sample,
    L"MPEG1 Splitter Sample",
    0,                                     // Don't use us for real!
    NUMELMS(sudMpgPins),                   // 3 pins
    sudMpgPins
};

//
// DllRegisterSever
//
// Handle the registration of this filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer();

} // DllRegisterServer

//  Constructor and destructor
CMPEG1SplitterFilter::CMPEG1SplitterFilter(
   LPUNKNOWN pUnk,
   HRESULT *phr) :
   CBaseSplitterFilter(
       TEXT("CMPEG1SplitterFilter"),
       pUnk,
       CLSID_MPEG1Sample,
       phr)
{
    //  Create our input pin
    m_pInput = new CSplitterInputPin(this, phr);
}
//  Override type checking
HRESULT CMPEG1SplitterFilter::CheckInputType(const CMediaType *pmt)
{
    /*  We'll accept our preferred type or a wild card for the subtype */

    if (pmt->majortype != MEDIATYPE_Stream ||
        pmt->subtype != MEDIASUBTYPE_MPEG1System &&
        pmt->subtype != MEDIASUBTYPE_NULL) {
        return S_FALSE;
    } else {
        return S_OK;
    }
}

LPAMOVIESETUP_FILTER CMPEG1SplitterFilter::GetSetupData()
{
    return &sudMpgsplit;
}

/*  Complete connection and instantiate parser
    This involves:

    Instatiate the parser with for the type and have it check the format
*/

CBaseParser *CMPEG1SplitterFilter::CreateParser(
    CParserNotify *pNotify,
    CMediaType *pType
)
{
    HRESULT hr = S_OK;
    return new CMPEG1Parser(pNotify, &hr);
}

/*  Cheap'n nasty parser - DON'T do yours like this! */
/*  Initialize a parser

    pmt     - type of stream if known - can be NULL
    pRdr    - way to read the source medium - can be NULL
*/
HRESULT CMPEG1Parser::Init(CParseReader *pRdr)
{
    const DWORD dwLen = 65536;
    /*  Just read 32K and look for interesting stuff */
    PBYTE pbData = new BYTE[dwLen];
    if (pbData == NULL) {
        return E_OUTOFMEMORY;
    }
    HRESULT hr = pRdr->Read(pbData, dwLen);
    if (S_OK != hr) {
        delete [] pbData;
        return hr;
    }

    /*  Now just loop looking for start codes */
    DWORD dwLeft = dwLen;
    PBYTE pbCurrent = pbData;
    while (dwLeft >= 140) {
        DWORD dwCode = *(UNALIGNED DWORD *)pbCurrent;

        /*  Check if it's a valid start code */
        if ((dwCode & 0x00FFFFFF) == 0x00010000) {
            dwCode = DWORD_SWAP(dwCode);
            if (VALID_PACKET(dwCode)){
                MPEG_PACKET_DATA Info;
                DWORD dwPacketLen = ParseMPEG1Packet(
                                        pbCurrent,
                                        dwLeft,
                                        &Info);
                if (dwPacketLen > 4) {
                    if (!m_bGotFirstPts && Info.bHasPts) {
                        m_llFirstPts = Info.llPts;
                        m_bGotFirstPts = TRUE;
                    }
                    if (IsVideoStreamId((BYTE)(dwCode & 0xFF))) {
                        m_Video.m_uStreamId = (BYTE)(dwCode & 0xFF);
                        const BYTE * pbHeader = pbCurrent + Info.dwHeaderLen;
                        SEQHDR_INFO seqInfo;

                        /*  Create our format block */
                        if (DWORD_SWAP(SEQUENCE_HEADER_CODE) ==
                            *(UNALIGNED DWORD *)pbHeader &&
                            ParseSequenceHeader(pbHeader, dwLeft, &seqInfo)) {
                            /*  Create the media type from the stream
                                only support Payload streams for now
                            */
                            CMediaType cmt;
                            GetVideoMediaType(&cmt, TRUE, &seqInfo);

                            /*  Create our video stream */
                            m_pNotify->CreateStream(L"Video", &m_Video.m_pNotify);
                            m_Video.m_pNotify->AddMediaType(&cmt);
                        } else {
                        }
                    } else if (IsAudioStreamId((BYTE)(dwCode & 0xFF))) {
                        m_Audio.m_uStreamId = (BYTE)(dwCode & 0xFF);

                        /*  Now let's hope the first 2 bytes are a frame
                            start !
                        */
                        MPEG1WAVEFORMAT wf;
                        if (ParseAudioHeader(pbCurrent + Info.dwHeaderLen,
                                             &wf)) {
                            CMediaType cmt(&MEDIATYPE_Audio);
                            cmt.subtype = MEDIASUBTYPE_MPEG1AudioPayload;
                            cmt.SetFormat((PBYTE)&wf, sizeof(wf));
                            cmt.SetFormatType(&FORMAT_WaveFormatEx);
                            m_Audio.m_uStreamId = (BYTE)(dwCode & 0xFF);
                            m_pNotify->CreateStream(
                                L"Audio",
                                &m_Audio.m_pNotify);
                            m_Audio.m_pNotify->AddMediaType(&cmt);
                        }
                    }
                }
                ASSERT(dwLeft >= dwPacketLen);
                pbCurrent += dwPacketLen;
                dwLeft -= dwPacketLen;
                continue;
            }
        }

        /*  Hideously inefficient! */
        pbCurrent++;
        dwLeft--;
    }
    delete [] pbData;
    if (!m_bGotFirstPts || (!m_Audio.Initialized() && !m_Video.Initialized())) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    } else {
        return S_OK;
    }
}

/*  Get the size and count of buffers preferred based on the
    actual content
*/
void CMPEG1Parser::GetSizeAndCount(LONG *plSize, LONG *plCount)
{
    *plSize = 32768;
    *plCount = 4;
}

/*  Call this to reinitialize for a new stream */
void CMPEG1Parser::StreamReset()
{
}

/*  Call this to pass new stream data :

    pbData        - pointer to data
    lData         - length of data
    plProcessed   - Amount of data consumed
*/
HRESULT CMPEG1Parser::Process(
    const BYTE * pbData,
    LONG lData,
    LONG *plProcessed
)
{
    /*  Just loop processing packets until we run out of data
        We should do a lot more to sync up than just eat a start
        code !
    */

    DWORD dwLeft = lData;
    const BYTE * pbCurrent = pbData;

    while (dwLeft > 4) {
        /*  Find a start code */
        DWORD dwCode = DWORD_SWAP(*(UNALIGNED DWORD *)pbCurrent);
        MPEG_PACKET_DATA Info;
        if (VALID_PACKET(dwCode)) {
            DWORD dwPacketLen = ParseMPEG1Packet(pbCurrent, dwLeft, &Info);
            if (dwPacketLen == 0) {
                break;
            }
            if (dwPacketLen == 4) {
                dwLeft -= 4;
                pbCurrent += 4;
                continue;
            }
            /*  If it's a packet we're interested in send it on */
            CStream *pStream = NULL;
            if (((BYTE)(dwCode & 0xFF)) == m_Audio.m_uStreamId) {
                pStream = &m_Audio;
            } else
            if (((BYTE)(dwCode & 0xFF)) == m_Video.m_uStreamId) {
                pStream = &m_Video;
            }
            if (pStream != NULL) {
                DbgLog((LOG_TRACE, 3, TEXT("Send sample for stream %2.2X"),
                        pStream->m_uStreamId));
                pStream->m_pNotify->SendSample(
                    pbCurrent + Info.dwHeaderLen,
                    Info.dwPacketLen - Info.dwHeaderLen,
                    Info.bHasPts ? TimeStamp(Info.llPts) : 0,
                    Info.bHasPts);
            }
            dwLeft -= Info.dwPacketLen;
            pbCurrent += Info.dwPacketLen;
        } else {
            dwLeft--;
            pbCurrent++;
        }
    }
    *plProcessed = lData - dwLeft;
    return S_OK;
}


MPEG1.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  Structures and definitions for MPEG1 filter stuff */

/*  Implement type checking and CompleteConnect */
class CMPEG1SplitterFilter : public CBaseSplitterFilter
{
public:
    /* This goes in the factory template table to create new instances */
    static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *);

    //  Constructor and destructor
    CMPEG1SplitterFilter(
       LPUNKNOWN pUnk,
       HRESULT *phr);

    //  Support self-registration
    LPAMOVIESETUP_FILTER GetSetupData();

    //  Override type checking
    HRESULT CheckInputType(const CMediaType *pmt);

    //  Create specific parser
    CBaseParser *CreateParser(CParserNotify *pNotify, CMediaType *pType);
};

/*  Parser */
class CMPEG1Parser : public CBaseParser
{
public:
    CMPEG1Parser(CParserNotify *pNotify, HRESULT *phr) :
        CBaseParser(pNotify, phr),
        m_bGotFirstPts(FALSE),
        m_llFirstPts(0)
    {
    }

    /*  Initialize a parser

        pmt     - type of stream if known - can be NULL
        pRdr    - way to read the source medium - can be NULL
    */
    HRESULT Init(CParseReader *pRdr);

    /*  Get the size and count of buffers preferred based on the
        actual content
    */
    void GetSizeAndCount(LONG *plSize, LONG *plCount);

    /*  Call this to reinitialize for a new stream */
    void StreamReset();

    /*  Call this to pass new stream data :

        pbData        - pointer to data
        lData         - length of data
        plProcessed   - Amount of data consumed
    */
    HRESULT Process(
        const BYTE * pbData,
        LONG lData,
        LONG *plProcessed
    );

private:
    /*  Helper to set timestamps */
    REFERENCE_TIME TimeStamp(LONGLONG llPts)
    {
        ASSERT(m_bGotFirstPts);
        LARGE_INTEGER liPtsOffset;
        liPtsOffset.QuadPart = llPts - m_llFirstPts;
        liPtsOffset.HighPart &= 1;
        liPtsOffset.HighPart = -liPtsOffset.HighPart;
        return llMulDiv(liPtsOffset.QuadPart,
                        UNITS,
                        MPEG_TIME_DIVISOR,
                        0);
    }
private:
    /*  Parsing structures */
    class CStream
    {
    public:
        CStream() : m_uStreamId(0),
                    m_pNotify(NULL) {}
        BOOL Initialized()
        {
            return m_pNotify != NULL && m_uStreamId != 0xFF;
        }
        BYTE           m_uStreamId;
        CStreamNotify *m_pNotify;
    };
    CStream  m_Audio;
    CStream  m_Video;
    LONGLONG m_llFirstPts;
    BOOL     m_bGotFirstPts;
};


MPEG2.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <pullpin.h>
#include <mmreg.h>
#include <mpeg2typ.h>
#include <alloc.h>
#include <splitter.h>
#include <mpegdef.h>
#include <mpgutil.h>
#include <initguid.h>
#include <mpeg2ids.h>
#include <mpeg2.h>
//  59b41160-f483-11cf-a66b-00aa00bf96aa
DEFINE_GUID(CLSID_MPEG2Sample,
    0x59b41160, 0xf483, 0x11cf, 0xa6, 0x6b, 0x00, 0xaa, 0x00, 0xbf, 0x96, 0xaa);

/*  Stuff to make this into a filter */
/* List of class IDs and creator functions for the class factory. This
   provides the link between the OLE entry point in the DLL and an object
   being created. The class factory will call the static CreateInstance
   function when it is asked to create a CLSID_MPEG2Splitter object */

CFactoryTemplate g_Templates[1] = {
    {L"MPEG2 Sample", &CLSID_MPEG2Sample, CMPEG2SplitterFilter::CreateInstance}
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

/* This goes in the factory template table to create new instances */

CUnknown *CMPEG2SplitterFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    CUnknown *pUnkRet = new CMPEG2SplitterFilter(pUnk, phr);
    return pUnkRet;
}

/*  Registration setup stuff */
//  Setup data

AMOVIESETUP_MEDIATYPE sudMpgInputType[] =
{
    { &MEDIATYPE_Stream, &MEDIASUBTYPE_MPEG2_PROGRAM }
};
AMOVIESETUP_MEDIATYPE sudMpgAudioOutputType[] =
{
    { &MEDIATYPE_Audio, &MEDIASUBTYPE_MPEG1AudioPayload }
};
AMOVIESETUP_MEDIATYPE sudMpgVideoOutputType[] =
{
    { &MEDIATYPE_Video, &MEDIASUBTYPE_MPEG2_VIDEO }
};

AMOVIESETUP_PIN sudMpgPins[3] =
{
    { L"Input",
      FALSE,                               // bRendered
      FALSE,                               // bOutput
      FALSE,                               // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgInputType),            // Number of media types
      sudMpgInputType
    },
    { L"Audio Output",
      FALSE,                               // bRendered
      TRUE,                                // bOutput
      TRUE,                                // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgAudioOutputType),      // Number of media types
      sudMpgAudioOutputType
    },
    { L"Video Output",
      FALSE,                               // bRendered
      TRUE,                                // bOutput
      TRUE,                                // bZero
      FALSE,                               // bMany
      &CLSID_NULL,                         // clsConnectsToFilter
      NULL,                                // ConnectsToPin
      NUMELMS(sudMpgVideoOutputType),      // Number of media types
      sudMpgVideoOutputType
    }
};

AMOVIESETUP_FILTER sudMpgsplit =
{
    &CLSID_MPEG2Sample,
    L"MPEG2 Splitter Sample",
    0,                                     // Don't use us for real!
    NUMELMS(sudMpgPins),                   // 3 pins
    sudMpgPins
};

//
// DllRegisterSever
//
// Handle the registration of this filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer();

} // DllRegisterServer

//  Constructor and destructor
CMPEG2SplitterFilter::CMPEG2SplitterFilter(
   LPUNKNOWN pUnk,
   HRESULT *phr) :
   CBaseSplitterFilter(
       TEXT("CMPEG2SplitterFilter"),
       pUnk,
       CLSID_MPEG2Sample,
       phr)
{
    //  Create our input pin
    m_pInput = new CSplitterInputPin(this, phr);
}

//  Override type checking and connection completion
HRESULT CMPEG2SplitterFilter::CheckInputType(const CMediaType *pmt)
{
    if (pmt->majortype != MEDIATYPE_Stream ||
        pmt->subtype != MEDIASUBTYPE_MPEG2_PROGRAM &&
        pmt->subtype != MEDIASUBTYPE_NULL) {
        return S_FALSE;
    } else {
        return S_OK;
    }
}

LPAMOVIESETUP_FILTER CMPEG2SplitterFilter::GetSetupData()
{
    return &sudMpgsplit;
}

/*  Complete connection and instantiate parser
    This involves:

    Instatiate the parser with for the type and have it check the format
*/

CBaseParser *CMPEG2SplitterFilter::CreateParser(
    CParserNotify *pNotify,
    CMediaType *pType
)
{
    HRESULT hr = S_OK;
    return new CMPEG2Parser(pNotify, &hr);
}

/*  Cheap'n nasty parser - DON'T do yours like this! */
/*  Initialize a parser

    pmt     - type of stream if known - can be NULL
    pRdr    - way to read the source medium - can be NULL
*/
HRESULT CMPEG2Parser::Init(CParseReader *pRdr)
{
    const DWORD dwLen = 65536 * 4;
    /*  Just read 32K and look for interesting stuff */
    PBYTE pbData = new BYTE[dwLen];
    if (pbData == NULL) {
        return E_OUTOFMEMORY;
    }
    HRESULT hr = pRdr->Read(pbData, dwLen);
    if (S_OK != hr) {
        delete [] pbData;
        return hr;
    }

    /*  Now just loop looking for start codes */
    DWORD dwLeft = dwLen;
    const BYTE * pbCurrent = pbData;
    while (dwLeft >= 140) {
        DWORD dwCode = *(UNALIGNED DWORD *)pbCurrent;

        /*  Check if it's a valid start code */
        if ((dwCode & 0x00FFFFFF) == 0x00010000) {
            dwCode = DWORD_SWAP(dwCode);
            /*  Look for a system header */
            if (dwCode == PACK_START_CODE) {
                DWORD dwLen = MPEG2PackHeaderLength(pbCurrent, dwLeft);
                if (dwLen == 0) {

                    /*  Need more data */
                    break;
                } else {

                    /*  Skip this item */
                    dwLeft -= dwLen;
                    pbCurrent += dwLen;
                    continue;
                }
            }
            if (dwCode == SYSTEM_HEADER_START_CODE) {
                /*  Parse the MPEG-2 system header */
                DWORD dwLen = ParseSystemHeader(pbCurrent, dwLeft);
                if (dwLen > 4) {
                    /*  Pull out the stream info */
                    BYTE bStreams = pbCurrent[SYSTEM_HEADER_BASIC_LENGTH];
                    DWORD cbStreams = dwLen - SYSTEM_HEADER_BASIC_LENGTH;
                    ASSERT(cbStreams % 3 == 0);
                    const BYTE *pbStreams = pbCurrent + SYSTEM_HEADER_BASIC_LENGTH;
                    for ( ;cbStreams > 0 ; pbStreams += 3, cbStreams -= 3) {
                        if (pbStreams[0] == AUDIO_GLOBAL) {
                            m_bHasAudio = TRUE;
                        } else
                        if (pbStreams[0] == VIDEO_GLOBAL) {
                            m_bHasVideo = TRUE;
                        } else {
                            if (IsAudioStreamId(pbStreams[0])) {
                                m_bHasAudio = TRUE;
                                if (m_uAudioStreamId == 0) {
                                    m_uAudioStreamId = pbStreams[0];
                                }
                            }
                            if (IsVideoStreamId(pbStreams[0])) {
                                m_bHasVideo = TRUE;
                                if (m_uVideoStreamId == 0) {
                                    m_uVideoStreamId = pbStreams[0];
                                }
                            }
                            StreamInfo(pbStreams[0])->m_bExists = TRUE;
                        }

                    }
                    pbCurrent += dwLen;
                    dwLeft -= dwLen;
                    continue;
                } else {

                    /*  Need more data */
                    if (dwLen == 0) {
                        break;
                    }
                }
            } else
            if (VALID_PACKET(dwCode)){
                MPEG_PACKET_DATA Info;
                DWORD dwPacketLen = ParseMPEG2Packet(
                                        pbCurrent,
                                        dwLeft,
                                        &Info);
                if (dwPacketLen > 4) {
                    if (!m_bGotFirstPts && Info.bHasPts) {
                        m_llFirstPts = Info.llPts;
                        m_bGotFirstPts = TRUE;
                    }
                    InitStream((UCHAR)(dwCode & 0xFF),
                               pbCurrent + Info.dwHeaderLen,
                               Info.dwPacketLen - Info.dwHeaderLen
                              );

                    if (IsVideoStreamId((BYTE)(dwCode & 0xFF))) {
                        if (m_uVideoStreamId == 0) {
                            m_uVideoStreamId = (BYTE)(dwCode & 0xFF);
                        }
                    } else if (IsAudioStreamId((BYTE)(dwCode & 0xFF))) {
                        if (m_uAudioStreamId == 0) {
                            m_uAudioStreamId = (BYTE)(dwCode & 0xFF);
                        }
                    }
                    ASSERT(dwLeft >= dwPacketLen);
                    pbCurrent += dwPacketLen;
                    dwLeft -= dwPacketLen;
                    continue;
                } else {
                    if (dwPacketLen == 0) {
                        break;
                    }
                }
            }
        }

        /*  Hideously inefficient but then for program stream this shouldn't
            happen very often! */
        pbCurrent++;
        dwLeft--;
    }
    delete [] pbData;
    if (!m_bGotFirstPts || (m_uAudioStreamId == 0 && m_uVideoStreamId == 0)) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    } else {
        return S_OK;
    }
}

/*  Get the size and count of buffers preferred based on the
    actual content
*/
void CMPEG2Parser::GetSizeAndCount(LONG *plSize, LONG *plCount)
{
    /*  HACK HACK - fix this depending on the stream */
    *plSize = 65536;
    *plCount = 8;
}

/*  Call this to reinitialize for a new stream */
void CMPEG2Parser::StreamReset()
{
    /*  Find the type of each stream */
    for (int i = PROGRAM_STREAM_MAP; i <= PROGRAM_STREAM_DIRECTORY; i++) {
        CStream *pStream = m_Streams[i - PROGRAM_STREAM_MAP].m_pStream;
        for (CStream *pSearch = pStream;
             pSearch != NULL;
             pSearch = pSearch->m_pNext) {
            AM_MEDIA_TYPE mt;
            pSearch->m_pNotify->CurrentMediaType(&mt);
            pSearch->m_bDoPES = mt.majortype == MEDIATYPE_MPEG2_PES;
            FreeMediaType(mt);
        }
    }
}

/*  Call this to pass new stream data :

    pbData        - pointer to data
    lData         - length of data
    plProcessed   - Amount of data consumed
*/
HRESULT CMPEG2Parser::Process(
    const BYTE * pbData,
    LONG lData,
    LONG *plProcessed
)
{
    /*  Just loop processing packets until we run out of data
        We should do a lot more to sync up than just eat a start
        code !
    */

    DWORD dwLeft = lData;
    const BYTE * pbCurrent = pbData;

    while (dwLeft > 4) {
        /*  Find a start code */
        DWORD dwCode = DWORD_SWAP(*(UNALIGNED DWORD *)pbCurrent);
        MPEG_PACKET_DATA Info;
        if (VALID_PACKET(dwCode)) {
            DWORD dwPacketLen = ParseMPEG2Packet(pbCurrent, dwLeft, &Info);
            if (dwPacketLen == 0) {
                break;
            }
            if (dwPacketLen == 4) {
                dwLeft -= 4;
                pbCurrent += 4;
                continue;
            }
            /*  If it's a packet we're interested in send it on */
            BYTE uStreamId = (BYTE)(dwCode & 0xFF);
            CStream *pStream = StreamInfo(uStreamId)->FindStream(
                                   pbCurrent[Info.dwHeaderLen]);
            if (pStream != NULL) {
                DbgLog((LOG_TRACE, 3, TEXT("Send sample for stream %2.2X"),
                        pStream->m_uStreamId));
                if (pStream->m_bDoPES) {
                    pStream->m_pNotify->SendSample(
                        pbCurrent,
                        Info.dwPacketLen,
                        Info.bHasPts ? TimeStamp(Info.llPts) : 0,
                        Info.bHasPts);
                } else {
                    pStream->m_pNotify->SendSample(
                        pbCurrent + Info.dwHeaderLen + pStream->m_dwSkip,
                        Info.dwPacketLen - Info.dwHeaderLen - pStream->m_dwSkip,
                        Info.bHasPts ? TimeStamp(Info.llPts) : 0,
                        Info.bHasPts);
                }
            }
            dwLeft -= Info.dwPacketLen;
            pbCurrent += Info.dwPacketLen;
        } else {
            dwLeft--;
            pbCurrent++;
        }
    }
    *plProcessed = lData - dwLeft;
    return S_OK;
}

/*  Initialize a stream */
HRESULT CMPEG2Parser::InitStream(
    UCHAR uStreamId,
    const BYTE * pbFirstPacket,
    DWORD dwLen
)
{
    /*  Do nothing if we've already seen this one */
    if (NULL != StreamInfo(uStreamId)->FindStream(pbFirstPacket[0])) {
        return S_OK;
    }

    /*  This 'base' implementation just finds the audio and video
        streams
    */
    if (IsAudioStreamId(uStreamId)) {
        return InitAudioStream(uStreamId, pbFirstPacket, dwLen);
    }
    if (IsVideoStreamId(uStreamId)) {
        return InitVideoStream(uStreamId, pbFirstPacket, dwLen);
    }
    if (uStreamId == PRIVATE_STREAM_1) {
        return InitPrivateStream1(uStreamId, pbFirstPacket, dwLen);
    }
    return S_FALSE;
}

/*  Initialize an audio stream */
HRESULT CMPEG2Parser::InitAudioStream(
    UCHAR uStreamId,
    const BYTE * pbFirstPacket,
    DWORD dwLen
)
{
    /*  Just assume it's MPEG1 for now (!) */
    MPEG1WAVEFORMAT wf;
    if (ParseAudioHeader(pbFirstPacket, &wf)) {
        CMediaType cmt(&MEDIATYPE_Audio);
        cmt.subtype = MEDIASUBTYPE_MPEG1AudioPayload;
        cmt.SetFormat((PBYTE)&wf, sizeof(wf));
        cmt.SetFormatType(&FORMAT_WaveFormatEx);
        return CreateDefaultStream(
            uStreamId,
            L"Audio",
            &cmt,
            FALSE);
    } else {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }
}

/*  Initialize a video stream */
HRESULT CMPEG2Parser::InitVideoStream(
    UCHAR        uStreamId,
    const BYTE * pbFirstPacket,
    DWORD        dwLen
)
{
    /*  Parse the sequence header and extension */

    /*  Find the next start code */

    const BYTE * pbData = pbFirstPacket;
    const BYTE * pbStartSequenceHeader;
    if (!NextStartCode(&pbData, &dwLen)) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }
    if (!IsStartCode(pbData, SEQUENCE_HEADER_CODE)) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }
    if (dwLen < 200) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    /*  Now handle the sequence header the extension follows right
        on from the end and must be there, otherwise we're MPEG1
    */
    pbStartSequenceHeader = pbData;
    SEQHDR_INFO Info;
    if (!ParseSequenceHeader(pbData, dwLen, &Info)) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    /*  Parse the extension (soft of) */

    pbData += Info.lActualHeaderLen;
    dwLen -= Info.lActualHeaderLen;
    if (!NextStartCode(&pbData, &dwLen)) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }
    if (!IsStartCode(pbData, EXTENSION_START_CODE)) {
        /*  !!  Do MPEG 1 */
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    if (dwLen < 14) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    /*  Get the extension info - note that the various extension start
        codes can be identified by the 4-bit ID following them
        So we can just keep eating extension start codes until we get
        bored an looking for the id - it's up to the authors to
        deliver them according to the spec order
    */
    /*  Check the sequence extension id */
    if ((pbData[4] >> 4) != 0x01) {
        DbgLog((LOG_ERROR, 1, TEXT("Bad extension start code id")));
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    /*  The escape bit seems not to be used */

    Info.dwProfile = pbData[4] & 0x07;
    Info.dwLevel   = pbData[5] >> 4;
    DbgLog((LOG_TRACE, 3, TEXT("%s : %s"),
           Info.dwProfile == 5 ? TEXT("Simple Profile") :
           Info.dwProfile == 4 ? TEXT("Main Profile")   :
           Info.dwProfile == 3 ? TEXT("SNR Scalable Profile") :
           Info.dwProfile == 2 ? TEXT("Spatially Scalable Profile") :
           Info.dwProfile == 1 ? TEXT("High Profile") :
                                 TEXT("Unrecognized Profile"),
           Info.dwLevel  == 10 ? TEXT("Low Level") :
           Info.dwLevel  == 8  ? TEXT("Main Level") :
           Info.dwLevel  == 6  ? TEXT("High 1440 Level") :
           Info.dwLevel  == 4  ? TEXT("High Level") :
                                 TEXT("Unrecognized Level")));
    Info.lWidth += ((pbData[5] & 1) << 13) + (pbData[6] & 0x80) << 5;
    Info.lHeight += (pbData[6] & 0x60) << 7;
    Info.dwBitRate += 400 * (((pbData[6] & 0x1F) << (18 + 7)) +
                             ((pbData[7] & 0xFE) << (18 - 1)));

    /*  Find out the total length and save it somewhere (!) */
    dwLen -= 4;
    pbData += 4;
    while (NextStartCode(&pbData, &dwLen)) {
        if (!IsStartCode(pbData, EXTENSION_START_CODE)) {
            /*  We're there although we may have gone past - too bad */
            DWORD dwHeaderLen = pbData - pbStartSequenceHeader;
            break;
        }
        dwLen -= 4;
        pbData += 4;
    }

    /*  Hack for now - just give them the MPEG1 stuff */
    CMediaType cmt;
    HRESULT hr = GetVideoMediaType(&cmt, TRUE, &Info);
    if (FAILED(hr)) {
        return hr;
    }
    cmt.subtype = MEDIASUBTYPE_MPEG2_VIDEO;
    cmt.formattype = FORMAT_MPEG2Video;

    /*  Create our stream */
    return CreateDefaultStream(uStreamId, L"Video", &cmt);
}

/*  Initialize a stream */
HRESULT CMPEG2Parser::InitPrivateStream1(
    UCHAR uStreamId,
    const BYTE * pbFirstPacket,
    DWORD dwLen
)
{
    DbgLog((LOG_TRACE, 0, TEXT("Substream 0x%2.2X"), pbFirstPacket[0]));
    HRESULT hr = InitAC3(uStreamId, pbFirstPacket, dwLen);
    if (FAILED(hr)) {
        hr = InitSubPicture(uStreamId, pbFirstPacket, dwLen);
    }
    return hr;
}
/*  Initialize a stream */
HRESULT CMPEG2Parser::InitSubPicture(
    UCHAR uStreamId,
    const BYTE * pbFirstPacket,
    DWORD dwLen
)
{
    /*  Don't bother with typs and stuff for now - lucky this test
        can't be mistaken for AC3!
    */

    if ((pbFirstPacket[0] & 0xE0) != 0x20) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    CMediaType cmt(&MEDIATYPE_Video);
    cmt.SetSubtype(&MEDIASUBTYPE_SUBPICTURE);

    return CreateDefaultStream(
               uStreamId,
               L"Subpicture",
               &cmt,
               TRUE,
               TRUE,
               pbFirstPacket[0]);
}

/*  Initialize ac3 stream */
HRESULT CMPEG2Parser::InitAC3(
    UCHAR uStreamId,
    const BYTE * pbFirstPacket,
    DWORD dwLen
)
{
    /*  Do AC3 - there are 3(4?) types :

        DVD - substream id 8 (big endian?)
        non-DVD - no substream (but should be in program map)
        --  Big endian
        --  Little endian (laser disk) - sync word 0x77 0x0B
        --  Big endian (normal) - sync word 0x0B 0x77

    */

    BOOL bDVD = FALSE;
    BOOL bBigEndian = FALSE;

    const BYTE *pbData = pbFirstPacket;
    if ((pbFirstPacket[0] & 0xF8) == 0x80) {
        bDVD = TRUE;
        pbData+= 4;
    }

    if ((pbData[0] == 0x0B && pbData[1] == 0x77)) {
        bBigEndian = TRUE;
    } else
    if ((pbData[1] == 0x0B && pbData[0] == 0x77)) {
    } else return VFW_E_UNKNOWN_FILE_TYPE;

    /*  Parse the rest of the info */
    DOLBYAC3WAVEFORMAT wf;
    if (!ParseAC3Header(pbData, &wf)) {
        return VFW_E_UNKNOWN_FILE_TYPE;
    }

    /*  Create our media type */
    CMediaType cmt(&MEDIATYPE_Audio);
    cmt.SetSubtype(&MEDIASUBTYPE_DOLBY_AC3);
    cmt.SetFormatType(&FORMAT_WaveFormatEx);
    cmt.SetFormat((PBYTE)&wf, sizeof(wf));

    /*  Create out stream */
    HRESULT hr = CreateDefaultStream(
        uStreamId,
        L"AC3",
        &cmt,
        TRUE,
        bDVD,
        bDVD ? pbFirstPacket[0] : 0);

    if (S_OK != hr) {
        return hr;
    }

    /*  Set up the stream */
    if (bDVD) {
        /*  First 4 bytes skipped for DVD */
        StreamInfo(uStreamId)->m_pStream->m_dwSkip = 4;
    }
    return hr;
}

HRESULT CMPEG2Parser::CreateDefaultStream(
    UCHAR   uStreamId,
    LPCWSTR lpszName,
    CMediaType *pmt,
    BOOL bDoPes,
    BOOL bHasSubId,
    BOOL uSubId
)
{
    CStreamInfo *pStreamInfo = StreamInfo(uStreamId);

    /*  Check this one doesn't already exist */
    if (NULL != pStreamInfo->FindStream(uSubId)) {
        return S_FALSE;
    }

    CStream *pStream = new CStream(uStreamId, bHasSubId, uSubId);
    if (pStream == NULL) {
        return E_OUTOFMEMORY;
    }
    ASSERT(bHasSubId || pStreamInfo->m_pStream == NULL);

    pStream->m_pNext = pStreamInfo->m_pStream;
    pStreamInfo->m_pStream = pStream;
    m_pNotify->CreateStream(lpszName, &pStream->m_pNotify);
    pStream->m_pNotify->AddMediaType(pmt);

    /*  Support PES format too */
    if (bDoPes) {
        pmt->SetType(&MEDIATYPE_MPEG2_PES);
        pStream->m_pNotify->AddMediaType(pmt);
    }

    return S_OK;
}


MPEG2.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  Structures and definitions for MPEG2 filter stuff */

/*  Implement type checking and CompleteConnect */
class CMPEG2SplitterFilter : public CBaseSplitterFilter
{
public:
    /* This goes in the factory template table to create new instances */
    static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *);

    //  Constructor and destructor
    CMPEG2SplitterFilter(
       LPUNKNOWN pUnk,
       HRESULT *phr);

    //  Support self-registration
    LPAMOVIESETUP_FILTER GetSetupData();

    //  Override type checking
    HRESULT CheckInputType(const CMediaType *pmt);

    //  Create the parser for this filter
    CBaseParser *CreateParser(CParserNotify *pNotify, CMediaType *pType);
};

/*  MPEG-2 parsing

    We decide which streams we have by a 2-stage process:

        We find a system header.

        If this lists a program stream map we use that for finding
        stream information.

        Otherwise we use the list in the system stream header.

*/

/*  Parser */
class CMPEG2Parser : public CBaseParser
{
public:
    CMPEG2Parser(CParserNotify *pNotify, HRESULT *phr) :
        CBaseParser(pNotify, phr),
        m_bGotFirstPts(FALSE),
        m_llFirstPts(0),
        m_uCurrentAudioStream(0),
        m_uCurrentVideoStream(0),
        m_bHasAudio(FALSE),
        m_bHasVideo(FALSE),
        m_uAudioStreamId(0),
        m_uVideoStreamId(0)
    {
    }

    /*  Initialize a parser

        pmt     - type of stream if known - can be NULL
        pRdr    - way to read the source medium - can be NULL
    */
    HRESULT Init(CParseReader *pRdr);

    /*  Initialize a stream - make this virtual so we can override
        some streams in a derived class
    */
    virtual HRESULT InitStream(
        UCHAR uStreamId,
        const BYTE * pbFirstPacket,
        DWORD dwLen
    );

    /*  Initialize a stream */
    HRESULT InitAudioStream(
        UCHAR uStreamId,
        const BYTE * pbFirstPacket,
        DWORD dwLen
    );

    /*  Initialize a stream */
    HRESULT InitVideoStream(
        UCHAR uStreamId,
        const BYTE *pbFirstPacket,
        DWORD dwLen
    );

    /*  Initialize a stream */
    HRESULT InitPrivateStream1(
        UCHAR uStreamId,
        const BYTE * pbFirstPacket,
        DWORD dwLen
    );

    /*  Initialize AC3 */
    HRESULT InitAC3(
        UCHAR uStreamId,
        const BYTE * pbFirstPacket,
        DWORD dwLen
    );

    /*  Initialize subpicture */
    HRESULT InitSubPicture(
        UCHAR uStreamId,
        const BYTE * pbFirstPacket,
        DWORD dwLen
    );

    /*  Get the size and count of buffers preferred based on the
        actual content
    */
    void GetSizeAndCount(LONG *plSize, LONG *plCount);

    /*  Call this to reinitialize for a new stream */
    void StreamReset();

    /*  Call this to pass new stream data :

        pbData        - pointer to data
        lData         - length of data
        plProcessed   - Amount of data consumed
    */
    HRESULT Process(
        const BYTE * pbData,
        LONG lData,
        LONG *plProcessed
    );

private:
    /*  Helper to set timestamps */
    REFERENCE_TIME TimeStamp(LONGLONG llPts)
    {
        ASSERT(m_bGotFirstPts);
        LARGE_INTEGER liPtsOffset;
        liPtsOffset.QuadPart = llPts - m_llFirstPts;
        liPtsOffset.HighPart &= 1;
        liPtsOffset.HighPart = -liPtsOffset.HighPart;
        return llMulDiv(liPtsOffset.QuadPart,
                        UNITS,
                        MPEG_TIME_DIVISOR,
                        0);
    }
private:
    /*  Parsing structures */
    class CStream
    {
    public:
        CStream(BYTE uStreamId, BOOL bHasSubId, BYTE uSubId) :
                    m_uStreamId(uStreamId),
                    m_uSubId(uSubId),
                    m_bHasSubId(bHasSubId),
                    m_pNotify(NULL),
                    m_dwSkip(0),
                    m_pNext(NULL) {}
        virtual ~CStream() {}          // So we can delete as a CStream
        BOOL Initialized()
        {
            return m_pNotify != NULL && m_uStreamId != 0xFF;
        }
        BYTE           m_uStreamId;
        BYTE           m_uSubId;
        BOOL           m_bHasSubId;
        BOOL           m_bDoPES;
        DWORD          m_dwSkip;
        CStreamNotify *m_pNotify;
        CStream       *m_pNext;
    };

    HRESULT CreateDefaultStream(
        UCHAR   uStreamId,
        LPCWSTR lpszName,
        CMediaType *pmt,
        BOOL  bDoPES = TRUE,
        BOOL  bHasSubId = FALSE,
        BOOL  buSubId = 0
    );

    /*  Select audio and video streams - there could be multiple of
        each
    */
    BOOL     m_bHasAudio;
    UCHAR    m_uCurrentAudioStream;
    BOOL     m_bHasVideo;
    UCHAR    m_uCurrentVideoStream;

    /*  One stream per possible stream */
    class CStreamInfo {
    public:
        CStreamInfo() : m_pStream(NULL), m_bExists(FALSE) {}
        CStream  *m_pStream;
        BOOL      m_bExists;

        ~CStreamInfo()
        {
            while (m_pStream != NULL) {
                CStream *pStream = m_pStream;
                m_pStream = pStream->m_pNext;
                delete pStream;
            }
        }
        CStream *FindStream(UCHAR uSubId)
        {
            for (CStream *pSearch = m_pStream;
                 pSearch != NULL;
                 pSearch = pSearch->m_pNext) {
                if (!pSearch->m_bHasSubId || pSearch->m_uSubId == uSubId) {
                    /*  Done this one */
                    return pSearch;
                }
            }
            return NULL;
        }
    };

    /*  Get a stream corresponding to an id */
    CStreamInfo *StreamInfo(UCHAR id)
    {
        ASSERT(id >= PROGRAM_STREAM_MAP &&
               id <= PROGRAM_STREAM_DIRECTORY);
        return &m_Streams[id - PROGRAM_STREAM_MAP];
    }

    CStreamInfo m_Streams[PROGRAM_STREAM_DIRECTORY - PROGRAM_STREAM_MAP + 1];
    LONGLONG    m_llFirstPts;
    BOOL        m_bGotFirstPts;

    BYTE        m_uAudioStreamId;
    BYTE        m_uVideoStreamId;
};


MPGUTIL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
/*  MPEG 1 & 2 parsing routines */

#include <streams.h>
#include <mpegdef.h>
#include <mmreg.h>
#include <mpeg2typ.h>
#include <mpgutil.h>

#ifdef DEBUG
LPCTSTR PictureTypes[8]   = { TEXT("forbidden frame type"),
                              TEXT("I-Frame"),
                              TEXT("P-Frame"),
                              TEXT("B-Frame"),
                              TEXT("D-Frame"),
                              TEXT("Reserved frame type"),
                              TEXT("Reserved frame type"),
                              TEXT("Reserved frame type")
                            };
LPCTSTR PelAspectRatios[16] = { TEXT("Forbidden"),
                                TEXT("1.0000 - VGA etc"),
                                TEXT("0.6735"),
                                TEXT("0.7031 - 16:9, 625 line"),
                                TEXT("0.7615"),
                                TEXT("0.8055"),
                                TEXT("0.8437 - 16:9, 525 line"),
                                TEXT("0.8935"),
                                TEXT("0.9375 - CCIR601, 625 line"),
                                TEXT("0.9815"),
                                TEXT("1.0255"),
                                TEXT("1.0695"),
                                TEXT("1.1250 - CCIR601, 525 line"),
                                TEXT("1.1575"),
                                TEXT("1.2015"),
                                TEXT("Reserved") };
LPCTSTR PictureRates[16] = { TEXT("Forbidden"),
                             TEXT("23.976"),
                             TEXT("24"),
                             TEXT("25"),
                             TEXT("29.97"),
                             TEXT("30"),
                             TEXT("50"),
                             TEXT("59.94"),
                             TEXT("60"),
                             TEXT("Reserved"),
                             TEXT("Reserved"),
                             TEXT("Reserved"),
                             TEXT("Reserved"),
                             TEXT("Reserved"),
                             TEXT("Reserved"),
                             TEXT("Reserved") };
#endif // DBG

const LONG PictureTimes[16] = { 0,
                                (LONG)((double)10000000 / 23.976),
                                (LONG)((double)10000000 / 24),
                                (LONG)((double)10000000 / 25),
                                (LONG)((double)10000000 / 29.97),
                                (LONG)((double)10000000 / 30),
                                (LONG)((double)10000000 / 50),
                                (LONG)((double)10000000 / 59.94),
                                (LONG)((double)10000000 / 60)
                              };

const float fPictureRates[] =
   {
     (float)0,
     (float)23.976,
     (float)24,
     (float)25,
     (float)29.97,
     (float)30,
     (float)50,
     (float)59.94,
     (float)60.0
   };

const LONG AspectRatios[16] = { 0,
                                393700,
                                (LONG)(393700.0 * 0.6735),
                                (LONG)(393700.0 * 0.7031),
                                (LONG)(393700.0 * 0.7615),
                                (LONG)(393700.0 * 0.8055),
                                (LONG)(393700.0 * 0.8437),
                                (LONG)(393700.0 * 0.8935),
                                (LONG)(393700.0 * 0.9375),
                                (LONG)(393700.0 * 0.9815),
                                (LONG)(393700.0 * 1.0255),
                                (LONG)(393700.0 * 1.0695),
                                (LONG)(393700.0 * 1.1250),
                                (LONG)(393700.0 * 1.1575),
                                (LONG)(393700.0 * 1.2015),
                                0
                              };

/*  Bit rate tables */
const WORD BitRates[3][16] =
{{  0, 32,  64,  96,  128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 },
 {  0, 32,  48,  56,   64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384, 0 },
 {  0, 32,  40,  48,   56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 0 }
};

BOOL inline TESTBIT(const BYTE * pbData, int n)
{
    return 0 != (pbData[n >> 3] & (0x80 >> (n & 7)));
}

/*  Read an extended clock */
BOOL GetExtendedClock(const BYTE * pbData, LONGLONG *pllClock)
{
    /*  Check marker bits
        These occur at bit positions 5, 21, 37, 47 */
    if (!TESTBIT(pbData, 5) ||
        !TESTBIT(pbData, 21) ||
        !TESTBIT(pbData, 37) ||
        !TESTBIT(pbData, 47)) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid extended clock marker bits")));
        return FALSE;
    }

    /*  Compute the clock value

        * = ignore
        M = Marker
        x = value

            0        1       2        3        4
        **xxxMxx xxxxxxxx xxxxxMxx xxxxxxxx xxxxxM**
    */
    LARGE_INTEGER liClock;
    liClock.HighPart = (pbData[0] & 0x20) != 0;
    liClock.LowPart = ((pbData[0] & 0x18) << 27) +
                      ((pbData[0] & 0x03) << 28) +
                      ( pbData[1]         << 20) +
                      ((pbData[2] & 0xF8) << 12) +
                      ((pbData[2] & 0x03) << 13) +
                      ( pbData[3]         << 5) +
                      ( pbData[4]         >> 3);

    /*  Now compute the residual */
    LONG lCRE = ((pbData[4] & 3) << 7) + (pbData[5] >> 1);
    if (lCRE >= 300) {
        DbgLog((LOG_ERROR, 1, TEXT("Clock reference extension >= 300")));
        return FALSE;
    }

    *pllClock = liClock.QuadPart * 300 + lCRE;
    return TRUE;
}

/*  Parse pack header

    Parameters :
        pbData - pointer to data containing the system header
        cbData - length of data

    Returns :
        Number of bytes processed (0 if the header doesn't fit
        in the data or 4 if the header is invalid)
        We will return 0 if we can't see the next start code to
        check whether it's a system header start code
*/

DWORD ParseMPEG2PackHeader(const BYTE * pbData, DWORD cbData)
{
    ASSERT(cbData >= 4);
    ASSERT(*(UNALIGNED DWORD *)pbData == DWORD_SWAP(PACK_START_CODE));

    /*  Is there enough for the basic length ? */
    if (cbData < 18) {
        return 0;
    }
    DWORD dwLen = 14 + (pbData[13] & 0x07);
    if (cbData < dwLen + 4) {
        return 0;
    }

    /*  Check check bits '01' */
    if (pbData[4] & 0xC0 != 0x40) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid bits at start of pack")));
        return 4;
    }
    /*  Get SCR */
    LONGLONG llSCR;
    if (!GetExtendedClock(pbData + 4, &llSCR)) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid pack header SCR field")));
        return 4;
    }

    /*  Get the mux rate */
    DWORD dwMuxRate = (pbData[10] << (22 - 8)) +
                      (pbData[11] << (22 - 16)) +
                      (pbData[12] >> (24 - 22));
    /*  Check the marker bits */
    if ((pbData[12] & 0x03) != 0x03) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid marker bits in pack header")));
        return 4;
    }

    /*  Check the stuffing bytes */
    for (DWORD i = 14; i < dwLen; i++ ) {
        if (pbData[i] != 0xFF) {
            DbgLog((LOG_ERROR, 1, TEXT("Invalid pack stuffing byte 0x%2.2X"),
                    pbData[i]));
            return 4;
        }
    }
    DbgLog((LOG_TRACE, 4, TEXT("Decoded pack - ESCR %s, Mux Rate %d bytes/sec"),
            (LPCTSTR)CDisp(llSCR, CDISP_DEC),
            dwMuxRate * 50));

    return dwLen;
}

/*  Parse system header

    Parameters :
        pbData - pointer to data containing the system header
        cbData - length of data

    Returns :
        Number of bytes processed (0 if the header doesn't fit
        in the data or 4 if the header is invalid)
*/

LONG ParseSystemHeader(const BYTE * pbData, DWORD cbData)
{
    BOOL bHasAudio = FALSE;
    BOOL bHasVideo = FALSE;

    ASSERT(cbData >= 4);
    ASSERT(*(UNALIGNED DWORD *)pbData == DWORD_SWAP(SYSTEM_HEADER_START_CODE));

    /*  Checkt the length */
    if (cbData < 6) {
        return 0;
    }

    DWORD dwLen = 6 + pbData[5] + (pbData[4] << 8);
    if (dwLen < SYSTEM_HEADER_BASIC_LENGTH) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid System Header length")));
        return 4;
    }

    if (cbData < dwLen) {
        return 0;
    }
    /*  Check the marker bits */
    if (0 == (pbData[6] & 0x80) ||
        0 == (pbData[8] & 0x01) ||
        0 == (pbData[10] & 0x20)) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid System Header Marker bits")));
        return 4;
    }

    /*  Parse the 'buffer bounds and scale' list */
    const BYTE * pbNext = pbData + SYSTEM_HEADER_BASIC_LENGTH;
    DWORD cbLeft = dwLen - SYSTEM_HEADER_BASIC_LENGTH;
    for ( ; cbLeft >= 3; cbLeft -= 3, pbNext += 3) {
        if (pbNext[0] == AUDIO_GLOBAL) {
            bHasAudio == TRUE;
        } else
        if (pbNext[0] == VIDEO_GLOBAL) {
            bHasVideo = TRUE;
        } else {
            if (pbNext[0] < PROGRAM_STREAM_MAP) {
                DbgLog((LOG_ERROR, 1, TEXT("Invalid stream id in system header")));
                return 4;
            }
            if (IsVideoStreamId(pbNext[0])) {
                bHasVideo = TRUE;
            } else
            if (IsAudioStreamId(pbNext[0])) {
                bHasAudio = TRUE;
            }
        }
    }
    if (cbLeft != 0) {
        DbgLog((LOG_ERROR, 1, TEXT("Invalid system header length")));
        return 4;
    }
    DbgLog((LOG_TRACE, 4, TEXT("System Header %s, %s"),
            bHasAudio ? TEXT("audio") : TEXT("No audio"),
            bHasVideo ? TEXT("video") : TEXT("No video")));
    return dwLen;
}

/*  Parse an MPEG-1 packet */
DWORD ParseMPEG1Packet(
    const BYTE * pbData,
    DWORD cbData,
    MPEG_PACKET_DATA *pPacketData
)
{
    ZeroMemory((PVOID)pPacketData, sizeof(*pPacketData));
    DWORD dwStartCode = DWORD_SWAP(*(UNALIGNED DWORD *)pbData);
    DbgLog((LOG_TRACE, 4, TEXT("Parse packet %d bytes"), cbData));
    /*  Send it to the right stream */
    if (cbData < 6) {
        return 0;
    }

    /*  Find the length */
    DWORD dwLen = ((LONG)pbData[4] << 8) + (LONG)pbData[5] + 6;
    DbgLog((LOG_TRACE, 4, TEXT("Packet length %d bytes"), dwLen));
    if (dwLen > cbData) {
        return 0;
    }
    pPacketData->dwPacketLen = dwLen;

    /*  Pull out PTS if any */
    DWORD dwHeaderSize = 6;

    if (dwStartCode != PRIVATE_STREAM_2) {
        DWORD dwPts = 6;
        for (;;) {
            if (dwPts >= dwLen) {
                return 4;
            }

            if (pbData[dwPts] & 0x80) {
                /*  Stuffing byte */
                if (pbData[dwPts] != 0xFF) {
                    return 4;
                }
                dwPts++;
                continue;
            }

            /*  Check for STD (nextbits == '01') -
                we know the next bit is 0 so check the next one after that
            */
            if (pbData[dwPts] & 0x40) { // STD stuff
                dwPts += 2;
                continue;
            }

            /*  No PTS - normal case */
            if (pbData[dwPts] == 0x0F) {
                dwHeaderSize = dwPts + 1;
                break;
            }

            if ((pbData[dwPts] & 0xF0) == 0x20 ||
                (pbData[dwPts] & 0xF0) == 0x30) {

                /*  PTS or PTS and DTS */
                dwHeaderSize = (pbData[dwPts] & 0xF0) == 0x20 ? dwPts + 5 :
                                                             dwPts + 10;
                if (dwHeaderSize > dwLen) {
                    return 4;
                }
                if (!GetClock(pbData + dwPts, &pPacketData->llPts)) {
                    return 4;
                }
                pPacketData->bHasPts = TRUE;
                break;
            } else {
                return 4;
                break;
            }
        }
    }
    pPacketData->dwHeaderLen = dwHeaderSize;
    return dwLen;
}

/*  Parse an MPEG-2 packet */
DWORD ParseMPEG2Packet(
    const BYTE * pbData,
    DWORD cbData,
    MPEG_PACKET_DATA *pPacketData)
{
    ASSERT(cbData >= 4);
    ASSERT(pbData[3] >= PROGRAM_STREAM_MAP);

    ZeroMemory((PVOID)pPacketData, sizeof(*pPacketData));

    if (cbData < 6) {
        return 0;
    }
    DWORD dwLen = 6 + pbData[5] + (pbData[4] << 8);
    if (cbData < dwLen + 4) {
        return 0;
    }

    /*  Check for normal */
    switch (pbData[3]) {
    case PROGRAM_STREAM_MAP:
    case PRIVATE_STREAM_2:
    case ECM_STREAM:
    case EMM_STREAM:
    case PROGRAM_STREAM_DIRECTORY:
    case 0xF2: // DSMCC_Stream
    case 0xF8: // H.222 Type E
    case PADDING_STREAM:
        /*  Just the bytes */
        pPacketData->dwHeaderLen = 6;
        pPacketData->dwPacketLen = dwLen;
        pPacketData->bHasPts = FALSE;
        pPacketData->llPts = 0;
        break;

    /*  PES header */
    default:
        {
            if (!ParseMPEG2PacketHeader(pbData, dwLen, pPacketData)) {
                return 4;
            }
        }
        break;

    }
    return dwLen;
}

/*  Parse a packet header when present */
BOOL ParseMPEG2PacketHeader(
    const BYTE * pbData,
    DWORD dwLen,
    MPEG_PACKET_DATA *pPacketData
)
{
    /*  Compute the length */
    if (dwLen < 6 + 3) {
        return FALSE;
    }

    pPacketData->dwHeaderLen = 6 + 3 + pbData[8];
    pPacketData->dwPacketLen = dwLen;

    /*  Extract the PTS */
    if (MPEG2PacketHasPTS(pbData)) {
        pPacketData->bHasPts = TRUE;
        pPacketData->llPts = MPEG2PacketPTS(pbData);
    } else {
        pPacketData->bHasPts = FALSE;
        pPacketData->llPts = 0;
    }

    return TRUE;
}

BOOL ParseSequenceHeader(const BYTE *pbData, LONG lData, SEQHDR_INFO *pInfo)
{
    ASSERT(*(UNALIGNED DWORD *)pbData == DWORD_SWAP(SEQUENCE_HEADER_CODE));

    /*  Check random marker bit */
    if (!(pbData[10] & 0x20)) {
        DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid marker bit")));
        return FALSE;
    }

    DWORD dwWidthAndHeight = ((DWORD)pbData[4] << 16) +
                             ((DWORD)pbData[5] << 8) +
                             ((DWORD)pbData[6]);

    pInfo->lWidth = dwWidthAndHeight >> 12;
    pInfo->lHeight = dwWidthAndHeight & 0xFFF;
    DbgLog((LOG_TRACE, 2, TEXT("Width = %d, Height = %d"),
        pInfo->lWidth,
        pInfo->lHeight));

    /* the '8' bit is the scramble flag used by sigma designs - ignore */
    BYTE PelAspectRatioAndPictureRate = pbData[7];
    if ((PelAspectRatioAndPictureRate & 0x0F) > 8) {
        PelAspectRatioAndPictureRate &= 0xF7;
    }
    DbgLog((LOG_TRACE, 2, TEXT("Pel Aspect Ratio = %s"),
        PelAspectRatios[PelAspectRatioAndPictureRate >> 4]));
    DbgLog((LOG_TRACE, 2, TEXT("Picture Rate = %s"),
        PictureRates[PelAspectRatioAndPictureRate & 0x0F]));

    if ((PelAspectRatioAndPictureRate & 0xF0) == 0 ||
        (PelAspectRatioAndPictureRate & 0x0F) == 0) {
        DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid ratio/rate")));
        return FALSE;
    }

    pInfo->tPictureTime = (LONGLONG)PictureTimes[PelAspectRatioAndPictureRate & 0x0F];
    pInfo->fPictureRate = fPictureRates[PelAspectRatioAndPictureRate & 0x0F];
    pInfo->lTimePerFrame = MulDiv((LONG)pInfo->tPictureTime, 9, 1000);

    /*  Pull out the bit rate and aspect ratio for the type */
    pInfo->dwBitRate = ((((DWORD)pbData[8] << 16) +
                   ((DWORD)pbData[9] << 8) +
                   (DWORD)pbData[10]) >> 6);
    if (pInfo->dwBitRate == 0x3FFFF) {
        DbgLog((LOG_TRACE, 2, TEXT("Variable video bit rate")));
        pInfo->dwBitRate = 0;
    } else {
        pInfo->dwBitRate *= 400;
        DbgLog((LOG_TRACE, 2, TEXT("Video bit rate is %d bits per second"),
               pInfo->dwBitRate));
    }

#if 0
#pragma message (REMIND("Get pel aspect ratio right don't call GDI - it will create a thread!"))
    /*  Get a DC */
    HDC hdc = GetDC(GetDesktopWindow());

    ASSERT(hdc != NULL);
    /*  Guess (randomly) 39.37 inches per meter */
    LONG lNotionalPelsPerMeter = MulDiv((LONG)GetDeviceCaps(hdc, LOGICALPELSX),
                                        3937, 100);
#else
    LONG lNotionalPelsPerMeter = 2000;
#endif

    pInfo->lXPelsPerMeter = lNotionalPelsPerMeter;

    pInfo->lYPelsPerMeter = MulDiv(
                              lNotionalPelsPerMeter,
                              AspectRatios[PelAspectRatioAndPictureRate >> 4],
                              10000);
    /*  Pull out the vbv */
    pInfo->lvbv = ((((LONG)pbData[10] & 0x1F) << 5) |
             ((LONG)pbData[11] >> 3)) * 2048;

    DbgLog((LOG_TRACE, 2, TEXT("vbv size is %d bytes"), pInfo->lvbv));

    /*  Check constrained parameter stuff */
    if (pbData[11] & 0x04) {
        DbgLog((LOG_TRACE, 2, TEXT("Constrained parameter video stream")));

        if (pInfo->lvbv > 40960) {
            DbgLog((LOG_ERROR, 1, TEXT("Invalid vbv (%d) for Constrained stream"),
                    pInfo->lvbv));

            /*  Have to let this through too!  bisp.mpg has this */
            /*  But constrain it since it might be random        */
            pInfo->lvbv = 40960;
        }
    } else {
        DbgLog((LOG_TRACE, 2, TEXT("Non-Constrained parameter video stream")));
    }

    /*  tp_orig has a vbv of 2048 (!) */
    if (pInfo->lvbv < 20000) {
        DbgLog((LOG_TRACE, 2, TEXT("Small vbv (%d) - setting to 40960"),
               pInfo->lvbv));
        pInfo->lvbv = 40960;
    }

    pInfo->lActualHeaderLen = SequenceHeaderSize(pbData);
    CopyMemory((PVOID)pInfo->RawHeader, (PVOID)pbData, pInfo->lActualHeaderLen);
    return TRUE;
}

HRESULT GetVideoMediaType(CMediaType *cmt, BOOL bPayload, const SEQHDR_INFO *pInfo)
{
    cmt->majortype = MEDIATYPE_Video;
    cmt->subtype = bPayload ? MEDIASUBTYPE_MPEG1Payload :
                              MEDIASUBTYPE_MPEG1Packet;
    VIDEOINFO *videoInfo =
        (VIDEOINFO *)cmt->AllocFormatBuffer(FIELD_OFFSET(MPEG1VIDEOINFO, bSequenceHeader[pInfo->lActualHeaderLen]));
    if (videoInfo == NULL) {
        return E_OUTOFMEMORY;
    }
    RESET_HEADER(videoInfo);

    videoInfo->dwBitRate          = pInfo->dwBitRate;
    videoInfo->rcSource.right     = pInfo->lWidth;
    videoInfo->bmiHeader.biWidth  = pInfo->lWidth;
    videoInfo->rcSource.bottom    = pInfo->lHeight;
    videoInfo->bmiHeader.biHeight = pInfo->lHeight;
    videoInfo->bmiHeader.biXPelsPerMeter = pInfo->lXPelsPerMeter;
    videoInfo->bmiHeader.biYPelsPerMeter = pInfo->lYPelsPerMeter;
    videoInfo->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);

    videoInfo->AvgTimePerFrame = pInfo->tPictureTime;
    MPEG1VIDEOINFO *mpgvideoInfo = (MPEG1VIDEOINFO *)videoInfo;
    mpgvideoInfo->cbSequenceHeader = pInfo->lActualHeaderLen;
    CopyMemory((PVOID)mpgvideoInfo->bSequenceHeader,
               (PVOID)pInfo->RawHeader,
               pInfo->lActualHeaderLen);
    mpgvideoInfo->dwStartTimeCode = pInfo->dwStartTimeCode;

    cmt->SetFormatType(&FORMAT_MPEGVideo);
    return S_OK;
}

BOOL CheckAudioHeader(const BYTE * pbData)
{
    /*  Just check it's valid */
#pragma message (REMIND("Check audio header"))
    if ((pbData[2] & 0x0C) == 0x0C) {
        DbgLog((LOG_ERROR, 2, TEXT("Invalid audio sampling frequency")));
        return FALSE;
    }
    if ((pbData[1] & 0x08) != 0x08) {
        DbgLog((LOG_ERROR, 2, TEXT("Invalid audio ID bit = 0")));
        return FALSE;
    }
    if (((pbData[1] >> 1) & 3) == 0x00) {
        DbgLog((LOG_ERROR, 2, TEXT("Invalid audio Layer")));
        return FALSE;
    }

    if (((pbData[2] >> 2) & 3) == 3) {
        DbgLog((LOG_ERROR, 2, TEXT("Invalid sample rate")));
        return FALSE;
    }
    if ((pbData[2] >> 4) == 0x0F) {
        DbgLog((LOG_ERROR, 2, TEXT("Invalid bit rate")));
        return FALSE;
    }

    return TRUE;
}

LONG SampleRate(const BYTE * pbData)
{
    switch ((pbData[2] >> 2) & 3) {
        case 0:
            return 44100;

        case 1:
            return 48000;

        case 2:
            return 32000;

        default:
            DbgBreak("Unexpected Sample Rate");
            return 44100;
    }
}

BOOL ParseAudioHeader(const BYTE * pbData, MPEG1WAVEFORMAT *pFormat)
{
    if (!CheckAudioHeader(pbData)) {
        return FALSE;
    }
    pFormat->wfx.wFormatTag = WAVE_FORMAT_MPEG;

    /*  Get number of channels from Mode */
    switch (pbData[3] >> 6) {
    case 0x00:
        pFormat->fwHeadMode = ACM_MPEG_STEREO;
        break;
    case 0x01:
        pFormat->fwHeadMode = ACM_MPEG_JOINTSTEREO;
        break;
    case 0x02:
        pFormat->fwHeadMode = ACM_MPEG_DUALCHANNEL;
        break;
    case 0x03:
        pFormat->fwHeadMode = ACM_MPEG_SINGLECHANNEL;
        break;
    }
    pFormat->wfx.nChannels =
        (WORD)(pFormat->fwHeadMode == ACM_MPEG_SINGLECHANNEL ? 1 : 2);
    pFormat->fwHeadModeExt = (WORD)(1 << (pbData[3] >> 4));
    pFormat->wHeadEmphasis = (WORD)((pbData[3] & 0x03) + 1);
    pFormat->fwHeadFlags   = (WORD)(((pbData[2] & 1) ? ACM_MPEG_PRIVATEBIT : 0) +
                           ((pbData[3] & 8) ? ACM_MPEG_COPYRIGHT : 0) +
                           ((pbData[3] & 4) ? ACM_MPEG_ORIGINALHOME : 0) +
                           ((pbData[1] & 1) ? ACM_MPEG_PROTECTIONBIT : 0) +
                           ((pbData[1] & 0x08) ? ACM_MPEG_ID_MPEG1 : 0));

    int Layer;

    /*  Get the layer so we can work out the bit rate */
    switch ((pbData[1] >> 1) & 3) {
        case 3:
            pFormat->fwHeadLayer = ACM_MPEG_LAYER1;
            Layer = 1;
            break;
        case 2:
            pFormat->fwHeadLayer = ACM_MPEG_LAYER2;
            Layer = 2;
            break;
        case 1:
            pFormat->fwHeadLayer = ACM_MPEG_LAYER3;
            Layer = 3;
            break;
        case 0:
            return (FALSE);
    }

    /*  Get samples per second from sampling frequency */
    pFormat->wfx.nSamplesPerSec = SampleRate(pbData);
    pFormat->dwHeadBitrate =
        (DWORD)BitRates[Layer - 1][pbData[2] >> 4] * 1000;
    pFormat->wfx.nAvgBytesPerSec = pFormat->dwHeadBitrate / 8;

    /*  Deal with free format (!) */
#pragma message (REMIND("Handle variable bit rate (index 0)"))

    if (pFormat->wfx.nSamplesPerSec != 44100 &&
        /*  Layer 3 can sometimes switch bitrates */
        !(Layer == 3 && /* !m_pStreamList->AudioLock() && */
            (pbData[2] >> 4) == 0)) {

        if (Layer == 1) {
            pFormat->wfx.nBlockAlign = (WORD)
                (4 * ((pFormat->dwHeadBitrate * 12) / pFormat->wfx.nSamplesPerSec));
        } else {
            pFormat->wfx.nBlockAlign = (WORD)
                ((144 * pFormat->dwHeadBitrate) / pFormat->wfx.nSamplesPerSec);
        }
    } else {
        pFormat->wfx.nBlockAlign = 1;
    }

    pFormat->wfx.wBitsPerSample = 0;
    pFormat->wfx.cbSize = sizeof(MPEG1WAVEFORMAT) - sizeof(WAVEFORMATEX);

    pFormat->dwPTSLow  = 0;
    pFormat->dwPTSHigh = 0;

    return TRUE;
}

BOOL GetClock(const BYTE * pData, LONGLONG *Clock)
{
    BYTE  Byte1 = pData[0];
    DWORD Word2 = ((DWORD)pData[1] << 8) + (DWORD)pData[2];
    DWORD Word3 = ((DWORD)pData[3] << 8) + (DWORD)pData[4];

    /*  Do checks */
    if ((Byte1 & 0xE0) != 0x20 ||
        (Word2 & 1) != 1 ||
        (Word3 & 1) != 1) {
        DbgLog((LOG_TRACE, 2, TEXT("Invalid clock field - 0x%2.2X 0x%4.4X 0x%4.4X"),
            Byte1, Word2, Word3));
        return FALSE;
    }

    LARGE_INTEGER liClock;
    liClock.HighPart = (Byte1 & 8) != 0;
    liClock.LowPart  = (DWORD)((((DWORD)Byte1 & 0x6) << 29) +
                       (((DWORD)Word2 & 0xFFFE) << 14) +
                       ((DWORD)Word3 >> 1));

    *Clock = liClock.QuadPart;

    return TRUE;
}

/*  Find the next start code */
BOOL NextStartCode(const BYTE * *ppbData, DWORD *pdwLeft)
{
    const BYTE * pbData = *ppbData;
    DWORD dwLeft = *pdwLeft;

    while (dwLeft > 4 &&
           (*(UNALIGNED DWORD *)pbData & 0x00FFFFFF) != 0x00010000) {
        dwLeft--;
        pbData++;
    }
    *ppbData = pbData;
    *pdwLeft = dwLeft;
    return dwLeft >= 4;
}

/*  Parse AC3 header */
BOOL ParseAC3Header(const BYTE * pbData, DOLBYAC3WAVEFORMAT *pwf)
{
    ZeroMemory((PVOID)pwf, sizeof(*pwf));
    pwf->wfx.cbSize = sizeof(DOLBYAC3WAVEFORMAT) - sizeof(WAVEFORMATEX);

    /*  First check it IS AC3 */
    if (pbData[0] == 0x77 && pbData[1] == 0x0B) {
    } else
    if (pbData[0] == 0x0B && pbData[1] == 0x77) {
        pwf->bBigEndian = TRUE;
    } else {
        return FALSE;
    }

    /*  Get the sampling rate */
    BYTE bData = pwf->bBigEndian ? pbData[4] : pbData[5];
    int SampleRateCode = bData >> 6;
    int BitRateCode = bData & 0x3F;
    switch (SampleRateCode) {
    case 1:
        pwf->wfx.nSamplesPerSec = 44100;
        break;
    case 2:
        pwf->wfx.nSamplesPerSec = 32000;
        break;

    default:  // Isn't one illegal?
        pwf->wfx.nSamplesPerSec = 48000;
        break;
    }

    if (BitRateCode >= 38) {
        return FALSE;
    }

    /*  Get the bit rate */
    int BitRates[] =
    {
      32,  32,  40,  40,  48,  48,  56,  56,  64,  64,
      80,  80,  96,  96, 112, 112, 128, 128, 160, 160,
      192, 192, 224, 224, 256, 256, 320, 320, 384, 384,
      448, 448, 512, 512, 576, 576, 640, 640
    };
    pwf->wfx.nAvgBytesPerSec = BitRates[BitRateCode] * (1000 / 8);

    /*  Get the frame size
        Since every frame contains 1536 samples we can compute it as

        Bypes per frame  = Samples per frame (1536)  * Bytes per second
                           --------------------------------------------
                                      Samples per second
    */
    pwf->wfx.nBlockAlign = MulDiv(1536,
                              pwf->wfx.nAvgBytesPerSec,
                              pwf->wfx.nSamplesPerSec);

    pwf->wfx.nChannels = 6; // Hack!
    pwf->wfx.wFormatTag = WAVE_FORMAT_DOLBY_AC3;

    DbgLog((LOG_TRACE, 3, TEXT("Wave Format Dolby AC3")));
    DbgLog((LOG_TRACE, 3, TEXT("%d bytes per sec, %d samples per sec, align %d"),
           pwf->wfx.nAvgBytesPerSec,
           pwf->wfx.nSamplesPerSec,
           pwf->wfx.nBlockAlign));

    return TRUE;
}

/*  Return the PTS clock value as a 33 bit number
    Returns LONGLONG(-1) if the clock is invalid
*/
LONGLONG MPEG2PacketPTS(const BYTE * pbPacket)
{
    ASSERT(MPEG2PacketHasPTS(pbPacket));
    LONGLONG llClock;
    if (!GetClock(pbPacket + 9, &llClock)) {
        return (LONGLONG)-1;
    }
    return llClock;
}


DECODER.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: Decoder.cpp
*
* Implements a prototype Mpeg Video Software codec.  It just consume
* the passed in packets.
*
\**************************************************************************/
#include "MpgVideo.h"

/* -------------------------------------------------------------------------
** CVideoDecoder
** -------------------------------------------------------------------------
*/

/******************************Public*Routine******************************\
* CVideoDecoder
*
* Constructor and destructor
*
\**************************************************************************/
CVideoDecoder::CVideoDecoder(CMpegVideoCodec *pMpegCodec)
    : m_pCodec(pMpegCodec) {}

/******************************Public*Routine******************************\
* ResetVideo
*
*
*
\**************************************************************************/
DWORD
CVideoDecoder::ResetVideo()
{
    CAutoLock lck(this);
    return DECODE_SUCCESS;
}

/******************************Public*Routine******************************\
* DecodeFrame
*
*
*
\**************************************************************************/
DWORD
CVideoDecoder::DecodeFrame(
    VideoCtrl *pCtrl
    )
{
    CAutoLock   lck(this);
    BOOL        Done = FALSE;
    DWORD       rc = DECODE_SUCCESS;

    m_pCtrl = pCtrl;

    if ((m_pCtrl->dwCtrl & 0xffff) == DECODE_DIS) {

        LPBYTE lpOutputBuffer = m_pCodec->GetDecodeBufferAndFormat();

        if (lpOutputBuffer == NULL) {
            rc = DECODE_ERR_QUARTZ;
        }
        return rc;
    }

    m_pCtrl->dwSkipFlag = 1;
    while (!Done) {

        DWORD sc = NextStartCode();

        switch (sc) {

        case PICTURE_START_CODE:
            DbgLog((LOG_TRACE, 2, TEXT("PICTURE_START_CODE")));
            m_pCtrl->pFrameStartPos = m_pCtrl->pCmprRead;
            rc = SkipFrame();
            Done = TRUE;
            break;

        case SEQUENCE_HEADER_CODE:
            DbgLog((LOG_TRACE, 2, TEXT("SEQUENCE_HEADER_CODE")));
            Discard32Bits();
            break;

        case SEQUENCE_END_CODE:
            DbgLog((LOG_TRACE, 2, TEXT("SEQUENCE_END_CODE")));
            Discard32Bits();
            Done = TRUE;
            break;

        case GROUP_START_CODE:
            DbgLog((LOG_TRACE, 2, TEXT("GROUP_START_CODE")));
            Discard32Bits();
            break;

        case (DWORD)-1:
            //
            // We didn't find a valid start code.
            //
            rc = DECODE_ERR_DATA;
            Done = TRUE;
            break;

        default:
            DbgLog((LOG_TRACE, 2, TEXT("Unexpected start code %#X"), sc));
            Discard32Bits();
            break;
        }
    }

    if (!m_pCtrl->dwSkipFlag) {

        LPBYTE lpOutputBuffer = m_pCodec->GetDecodeBufferAndFormat();

        if (lpOutputBuffer == NULL) {
            rc = DECODE_ERR_QUARTZ;
        }
    }

    return rc;
}

/*****************************Private*Routine******************************\
* NextStartCode
*
* Returns the next start code of -1 if none found within the buffer limits.
* Advances pCmprRead to point to begining of the start code.
*
\**************************************************************************/
DWORD
CVideoDecoder::NextStartCode()
{
    LPBYTE  lpCurr = m_pCtrl->pCmprRead;
    LPBYTE  lpEnd = m_pCtrl->pCmprWrite;
    int     sm = 0;
    DWORD   StartCode = 0x00000100L;

    while ((lpCurr < lpEnd) && sm < 3) {

        switch (sm) {
        case 0:
            lpCurr = (LPBYTE)memchr(lpCurr, 0, lpEnd - lpCurr);
            if (lpCurr != NULL) lpCurr++, sm = 1;
            else lpCurr = lpEnd;
            break;

        case 1:
            if (*lpCurr++ == 0) sm = 2;
            else sm = 0;
            break;

        case 2:
            if (*lpCurr == 1) sm = 3;
            else if (*lpCurr == 0) sm = 2;
            else sm = 0;
            lpCurr++;
            break;
        }
    }

    //
    // When we get here we have either run out of buffer or found the first
    // 3 bytes of a valid start code.
    //
    // If we have the first three bytes of the start code.  The next
    // byte completes the start code.
    //
    // Don't forget to put back the start code bytes that we have just
    // read otherwise they would be lost forever.
    //
    if (lpCurr >= lpEnd) {
        StartCode = (DWORD)-1;
    }
    else {
        StartCode += *lpCurr;
    }
    lpCurr -= sm;

    m_pCtrl->pCmprRead = lpCurr;
    return StartCode;
}

/*****************************Private*Routine******************************\
* Discard32Bits
*
* Throw away the next 32 bits
*
\**************************************************************************/
void
CVideoDecoder::Discard32Bits()
{
    m_pCtrl->pCmprRead += 4;
}

/*****************************Private*Routine******************************\
* SkipFrame
*
*
*
\**************************************************************************/
DWORD
CVideoDecoder::SkipFrame()
{
    DWORD   dwX;

    //
    // If there isn't enough room for the information required then
    // then return 0L
    //
    if (m_pCtrl->pCmprRead > (m_pCtrl->pCmprWrite - 8)) {
        return DECODE_ERR_DATA;
    }

    Discard32Bits();    // Eat the picyure start code

    dwX = ByteSwap(*(UNALIGNED DWORD *)m_pCtrl->pCmprRead);
    m_pCtrl->pCmprRead += 4;

    m_pCtrl->dwTemporalReference  = (dwX & 0xFFC00000) >> 22;
    m_pCtrl->dwFrameType = (dwX & 0x00380000) >> 19;

    switch (m_pCtrl->dwCtrl & 0xffff) {
    case DECODE_NULL:
        m_pCtrl->dwSkipFlag = 1;
        break;

    case DECODE_I:
        m_pCtrl->dwSkipFlag = (m_pCtrl->dwFrameType != FTYPE_I);
        break;

    case DECODE_IP:
        m_pCtrl->dwSkipFlag = (m_pCtrl->dwFrameType == FTYPE_B);
        break;

    case DECODE_IPB:
        m_pCtrl->dwSkipFlag = 0;
        break;
    }

    for (; ; ) {

        dwX = NextStartCode();

        if (dwX == (DWORD)-1) {
            return DECODE_ERR_DATA;
        }
        else {

            BYTE Code = (BYTE)(dwX & 0xFF);

            if (Code == 0x00 || Code > 0xAF) {
                return DECODE_SUCCESS;
            }
            else {
                Discard32Bits();
            }
        }
    }
}


DECODER.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: Decoder.h
*
*
*
*
\**************************************************************************/

#ifndef _INC_DECODER_H
#define _INC_DECODER_H

// -------------------------------------------------------------------------
// Forward declaration to resolve circular reference.
// -------------------------------------------------------------------------
//
class CMpegVideoCodec;

enum {
    SEQUENCE_HEADER_CODE = 0x000001B3,
    SEQUENCE_END_CODE    = 0x000001B7,
    GROUP_START_CODE     = 0x000001B8,
    PICTURE_START_CODE   = 0x00000100
};

// -------------------------------------------------------------------------
// Video decoder class
//
// This class encapsulates a low-level mpeg video decoder.
// -------------------------------------------------------------------------
//

// Decode control option
enum {
    DECODE_NULL = 0x0000L,  // Do not Decode any frame. Scan the stream
    DECODE_I    = 0x0001L,  // I frames only
    DECODE_IP   = 0x0003L,  // I and P frames
    DECODE_IPB  = 0x0007L,  // Decode all the frames
    DECODE_DIS  = 0x0040L   // No Decode, Convert only - force out last frame.
};

// Frame Types
enum {
    FTYPE_I     = 0x0001L,
    FTYPE_P     = 0x0002L,
    FTYPE_B     = 0x0003L
};

// Function Return Values
enum {
    DECODE_SUCCESS    = 0x0000L,
    DECODE_ERR_MEMORY = 0x0001L,
    DECODE_ERR_DATA   = 0x0002L,
    DECODE_ERR_PARAM  = 0x0004L,
    DECODE_ERR_QUARTZ = 0x0008L     // error from Quartz callback
};

struct VideoCtrl {
    //
    // Decoder Output Fields
    //
    DWORD   dwGopTimeStamp; // only valid after a GOP, -1 otherwise.
    DWORD   dwFrameType;
    LPBYTE  pFrameStartPos;
    DWORD   dwSkipFlag;     // 1 if the frame was skipped
    DWORD   dwTemporalReference;

    //
    // Output Frame Buffer
    //
    DWORD   dwOutputFormat;
    DWORD   dwOutputBuffNumber;
    LPBYTE  pFrameBuff;
    DWORD   dwOutStride;
    DWORD   dwOutOffset;
    DWORD   dwYStride;
    DWORD   dwYLines;

    //
    // Frame decoder control
    //
    DWORD   dwCtrl;

    //
    // Input buffer fields
    //
    LPBYTE  pCmprRead;
    LPBYTE  pCmprWrite;
};

class CVideoDecoder
    : public CCritSec {

private:
    //
    // Back pointer to the object that created us.
    //
    CMpegVideoCodec *m_pCodec;
    VideoCtrl       *m_pCtrl;

    DWORD   NextStartCode();
    DWORD   SkipFrame();
    void    Discard32Bits();

public:
    CVideoDecoder(CMpegVideoCodec *pMpegCodec);

    DWORD   ResetVideo();
    DWORD   DecodeFrame(VideoCtrl *pCtrl);
};
#endif


MPEGUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: mpeguids.h
*
* Universal ID's for the mpeg video framework decoder
*
*
\**************************************************************************/

// f50b3f10-19c4-11cf-aa9a-02608c9baba2 MPEG Framework video decoder
DEFINE_GUID(CLSID_CMpegFrameworkVideoCodec,
0xf50b3f10, 0x19c4, 0x11cf, 0xaa, 0x9a, 0x02, 0x60, 0x8c, 0x9b, 0xab, 0xa2);


MPGVIDEO.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: MpgVideo.cpp
*
* Implements a prototype Mpeg Video Software codec.  It just consumes
* the passed in packets.
*
\**************************************************************************/
#include "MpgVideo.h"

// define the GUIDs for streams and my CLSID in this file
#include <initguid.h>
#include "mpegUids.h"
#include "palette.h"

// setup data

const AMOVIESETUP_MEDIATYPE
psudIpPinTypes[] = { { &MEDIATYPE_Video                // clsMajorType
                     , &MEDIASUBTYPE_MPEG1Packet  }    // clsMinorType
                   , { &MEDIATYPE_Video                // clsMajorType
                     , &MEDIASUBTYPE_MPEG1Payload } }; // clsMinorType

const AMOVIESETUP_MEDIATYPE
sudOpPinTypes = { &MEDIATYPE_Video      // clsMajorType
                , &MEDIASUBTYPE_NULL }; // clsMinorType

const AMOVIESETUP_PIN
psudPins[] = { { L"Input"            // strName
               , FALSE               // bRendered
               , FALSE               // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Output"           // strConnectsToPin
               , 2                   // nTypes
               , psudIpPinTypes }    // lpTypes
             , { L"Output"           // strName
               , FALSE               // bRendered
               , TRUE                // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Input"            // strConnectsToPin
               , 1                   // nTypes
               , &sudOpPinTypes } }; // lpTypes

const AMOVIESETUP_FILTER
sudMPEGVideo = { &CLSID_CMpegFrameworkVideoCodec // clsID
               , L"MPEG Framework Audio Codec"   // strName
               , 0x00600000                      // dwMerit
               , 2                               // nPins
               , psudPins };                     // lpPin

/* -------------------------------------------------------------------------
** list of class ids and creator functions for class factory
** -------------------------------------------------------------------------
*/
CFactoryTemplate g_Templates[] =
{
    {L"MPEG Framework Video Codec"
    , &CLSID_CMpegFrameworkVideoCodec
    , CMpegVideoCodec::CreateInstance
    , NULL
    , &sudMPEGVideo }
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

const TCHAR chRegistryKey[] =
    TEXT("Software\\Microsoft\\Multimedia\\ActiveMovie Filters\\Sample MPEG Video Decoder");

/******************************Public*Routine******************************\
* GetDecoderInt
*
*
*
* History:
*
*
\**************************************************************************/
int
GetDecoderInt(
    const TCHAR *pKey,
    int iDefault
    )
{
    HKEY hKey;
    LONG lRet;
    int  iRet = iDefault;

    lRet = RegOpenKey(HKEY_CURRENT_USER, chRegistryKey, &hKey);
    if (lRet == ERROR_SUCCESS) {

        DWORD   dwType, dwLen;

        dwLen = sizeof(iRet);
        if (ERROR_SUCCESS != RegQueryValueEx(hKey, pKey, 0L, &dwType,
                                             (LPBYTE)&iRet, &dwLen)) {
            iRet = iDefault;
        }
        RegCloseKey(hKey);
    }
    return iRet;
}

/* -------------------------------------------------------------------------
** CMpegVideoCodec
** -------------------------------------------------------------------------
*/
CMpegVideoCodec::CMpegVideoCodec(
    TCHAR *pName,
    LPUNKNOWN pUnk,
    HRESULT *phr
    )
    : CTransformFilter(pName, pUnk, CLSID_CMpegVideoCodec),
      m_pVideoDecoder(NULL),
      m_PtsQueue(NAME("Pts queue"), 30, FALSE, FALSE),
      m_pFrameBuff(NULL),
      m_Buffer(NULL),
      m_pOutSample(NULL)
{
    //
    // Pick up any user preferences - this should get moved to the register
    //
    m_IgnoreQualityMessage = GetDecoderInt(TEXT("IgnoreQMessages"), FALSE);

    m_dwCtrlDefault = GetDecoderInt(TEXT("VideoFramesDecoded"), DECODE_IPB);
    m_dwCtrlDefault &= 0x3F;

    m_dwQualDefault = GetDecoderInt(TEXT("VideoQuality"), 0);
    m_dwQualDefault &= 0x30000000;
    m_dwCtrl = (m_dwCtrlDefault | m_dwQualDefault);

    if (GetDecoderInt(TEXT("GreyScale"), 0)) {
        m_PaletteType = GREY_PALETTE;
        m_dwOutputFormatDib = MM_Y_DIB;
        m_dwOutputFormatDdb = MM_Y_DDB;
    }
    else {
        m_PaletteType = COLOUR_PALETTE;
        m_dwOutputFormatDib = MM_RGB8_DIB;
        m_dwOutputFormatDdb = MM_RGB8_DDB;
    }

    //
    // Reset frame stats
    //
    ZeroMemory(m_dwFramesSkipped, sizeof(m_dwFramesSkipped));
    ZeroMemory(m_dwFramesDecoded, sizeof(m_dwFramesDecoded));

    m_PerfDecode = MSR_REGISTER(TEXT("Decode Time  - Start/Stop"));
    m_QualMsg = MSR_REGISTER(TEXT("Quality Message"));
    m_FrameDrawn = MSR_REGISTER(TEXT("Frame Drawn"));
    m_FrameType = MSR_REGISTER(TEXT("Frame Type"));
}

CMpegVideoCodec::~CMpegVideoCodec(
    )
{
    CTimePosition   *pTimePos;

    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::~CMpegVideoCodec")));

    delete m_pVideoDecoder;
    m_pVideoDecoder = NULL;

    delete [] m_pFrameBuff;
    m_pFrameBuff = NULL;

    delete [] m_Buffer;
    m_Buffer = NULL;

    //
    // Purge the PTS queue.
    //
    while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) {
        delete pTimePos;
    }

    //
    // This should have been deleted in BreakConnect
    //
    ASSERT(m_Buffer == NULL);
}

/******************************Public*Routine******************************\
* CreateInstance
*
* This goes in the factory template table to create new instances
*
\**************************************************************************/
CUnknown *
CMpegVideoCodec::CreateInstance(
    LPUNKNOWN pUnk,
    HRESULT * phr
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CreateInstance")));
    return new CMpegVideoCodec(TEXT("MPEG Video codec filter"), pUnk, phr);
}

/******************************Public*Routine******************************\
* NonDelegatingQueryInterface
*
* Here we would reveal ISpecifyPropertyPages and IMpegVideoDecoder if
* the framework had a property page.
*
\**************************************************************************/
STDMETHODIMP
CMpegVideoCodec::NonDelegatingQueryInterface(
    REFIID riid,
    void ** ppv
    )
{
    return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
}

/******************************Public*Routine******************************\
* EndOfStream
*
*
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::EndOfStream()
{
    DbgLog((LOG_TRACE, 2, TEXT("End of stream called")));
    CAutoLock lck(&m_csReceive);

    if (m_pVideoDecoder == NULL) {
        return VFW_E_WRONG_STATE;
    }

    DecodeUntilBufferEmpty();
    ResetVideoDecoder();
    return CTransformFilter::EndOfStream();
}

/******************************Public*Routine******************************\
* EndFlush
*
*
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::EndFlush()
{
    DbgLog((LOG_TRACE, 2, TEXT("End flush called")));
    CAutoLock lck(&m_csReceive);
    ResetVideoDecoder();
    return CTransformFilter::EndFlush();
}

/******************************Public*Routine******************************\
* Receive
*
* Copy the input sample into our buffer.  Loop while the buffer size is
* greater than or equal to the size given in the Vbv for the next picture
* decode the picture and pass it along to the output pin for rendering.
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::Receive(
    IMediaSample *pSample
    )
{
    //
    //  Make sure the pin doesn't go inactive on us
    //

    HRESULT   hr;
    CAutoLock lck(&m_csReceive);

    if (m_pVideoDecoder == NULL || m_pFrameBuff == NULL) {
        return E_UNEXPECTED;
    }

    //
    // Check for a discontinuity, if one is found decode and display any
    // frames left in the circular buffer, reset the decoder and then continue
    // processing the current sample.
    //
    if (pSample->IsDiscontinuity() == S_OK) {

        DecodeUntilBufferEmpty();
        ResetVideoDecoder();

        //
        //  Find out what the current stop time is...
        //  ... get logical duration from upstream
        //
        HRESULT hrPos;
        REFTIME dStart, dStop;
        IMediaPosition *pPosition = NULL;

        hrPos = m_pInput->GetConnected()->QueryInterface(IID_IMediaPosition,
                                                         (void **)&pPosition);

        if ( SUCCEEDED(hrPos)
          && pPosition != NULL
          && SUCCEEDED(pPosition->get_CurrentPosition(&dStart))
          && SUCCEEDED(pPosition->get_StopTime(&dStop)))
        {
            m_tStop =
                (CRefTime)(COARefTime)dStop - (CRefTime)(COARefTime)dStart;
        }
        else {
            m_tStop = 0x7FFFFFFFFFFFFFFF;
        }

        if (pPosition != NULL) {
            pPosition->Release();
        }

        DbgLog((LOG_TRACE, 2,
                TEXT("Receive() : Discontinuity - setting stop time to %s"),
                (LPCTSTR)CDisp(m_tStop)));
    }

    m_pSample = NULL;
    m_LenLeftInPacket = 0L;

    //
    // If this sample is a sync point we need to
    // update our clock and reset the count of
    // samples received since the last sync point.
    //

    if (pSample->IsSyncPoint() == S_OK) {

        CRefTime        tStart, tStop;
        CTimePosition   *pTimePos;

        pSample->GetTime((REFERENCE_TIME*)&tStart,
                         (REFERENCE_TIME*)&tStop);

        pTimePos = new CTimePosition(&tStart,
                                     m_BufferCurr + m_BufferFullness >= m_BufferStart + m_VBlockSize ?
                                     m_BufferCurr + m_BufferFullness - m_VBlockSize : m_BufferCurr + m_BufferFullness);

        DbgLog(( LOG_TRACE, 4, TEXT("PTS: %s  Buffer pos: %ld"),
                 (LPCTSTR)CDisp(tStart), m_BufferCurr + m_BufferFullness ));
        m_PtsQueue.AddTail(pTimePos);

    }

    //
    // Decode and if necessary display as many frames as
    // possible until we exhaust our circular buffer.
    //

    do {

        hr = CopySampleToBuffer(pSample);

        while (SUCCEEDED(hr) && (m_BufferFullness >= m_seqInfo.lvbv)) {

            hr = DecodeNextPicture();
        }

    } while (S_OK == hr && (m_LenLeftInPacket != 0L));

    return hr;
}

/*****************************Private*Routine******************************\
* ResetVideoDecoder
*
*
*
\**************************************************************************/
void
CMpegVideoCodec::ResetVideoDecoder()
{
    //
    // Make him eat our sequence header
    //
    // m_BufferStart = m_BufferCurr = m_Buffer->GetPointer();
    m_BufferStart = m_BufferCurr = m_Buffer;
    m_BufferFullness = m_seqInfo.lActualHeaderLen;

    CopyMemory(m_BufferStart, m_seqInfo.RawHeader, m_seqInfo.lActualHeaderLen);

    m_TimeSinceLastSyncPoint = 0L;
    m_pOutSample = NULL;

    //
    // Don't play anything until we get a sync point
    // but don't make it too negative or we'll overflow!
    //
    m_TimeAtLastSyncPoint = (LONGLONG)-0x7FFFFFFFFFFFFF;
    m_tStopPrev = m_TimeAtLastSyncPoint;

    m_pVideoDecoder->ResetVideo();

    //
    // Purge the PTS queue.
    //
    CTimePosition *pTimePos;
    while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) {
        delete pTimePos;
    }

    //
    // Reset IP tracking
    //
    m_NextIP.Reset();

}

/*****************************Private*Routine******************************\
* DecodeUntilBufferEmpty
*
*
*
\**************************************************************************/
void
CMpegVideoCodec::DecodeUntilBufferEmpty()
{
    HRESULT hr = S_OK;

    while (SUCCEEDED(hr) && (m_BufferFullness > 0)) {

        LPBYTE pCurrentCurr = m_BufferCurr;

        DbgLog((LOG_TRACE, 3, TEXT("Forcing out a frame")));
        hr = DecodeNextPicture();

        if (S_OK != hr || m_BufferCurr == pCurrentCurr) {

            //
            // We're not making any progress!
            //
            DbgLog((LOG_ERROR, 2,
                    TEXT("CMpegVideoCodec::DecodeUntilBufferEmpty() ")
                    TEXT("- Stuck with %d bytes in buffer"),
                    m_BufferFullness));
            break;
        }
    }

    //
    // Now see if there's a frame decoded but not presented
    //
    if (m_NextIP.TimeToDraw()) {

        DbgLog((LOG_TRACE, 2, TEXT("Trying to get last frame")));

        DWORD dwCtrl = m_dwCtrl;
        m_dwCtrl &= 0xFFFF0000;
        m_dwCtrl |= DECODE_DIS;

        if (S_OK != DecodeNextPicture()) {
            DbgLog((LOG_ERROR, 2, TEXT("Failed to display last frame")));
        }

        m_dwCtrl = dwCtrl; // Restore
    }
}

/*****************************Private*Routine******************************\
* GetDecodeBufferAndFormat
*
* This function is called by the mpeg decoder when it is time to determine
* the colour conversion format and the destination buffer pointer for the
* frame.
*
\**************************************************************************/
LPBYTE
CMpegVideoCodec::GetDecodeBufferAndFormat()
{
    unsigned char   *pDst;
    AM_MEDIA_TYPE      *pmt;
    CRefTime        tStartTime(m_tStopPrev);
    CRefTime        tStopTime;

    // Don't ask for a buffer with a negative start time.
    if (tStartTime < (LONGLONG)0) {
        tStartTime = (LONGLONG)0;
    }

    //
    // this may block for an indeterminate amount of time
    //
    tStopTime = tStartTime + m_seqInfo.tPictureTime;
    hrCallback = m_pOutput->GetDeliveryBuffer(&m_pOutSample,
                                              (REFERENCE_TIME*)&tStartTime,
                                              (REFERENCE_TIME*)&tStopTime,
                                              0 // Must set proper flag
                                                // if prev frame skipped
                                              );
    if (FAILED(hrCallback)) {
        return NULL;
    }
    ASSERT(m_pOutSample);

    hrCallback = m_pOutSample->GetPointer(&pDst);
    if (FAILED(hrCallback)) {
        return NULL;
    }
    ASSERT(pDst);

    //
    // If the media type has changed then pmt is NOT NULL
    //
    m_pOutSample->GetMediaType(&pmt);
    if (pmt != NULL) {
        CMediaType cmt(*pmt);
        DeleteMediaType(pmt);
        SetOutputPinMediaType(&cmt);
    }
    return pDst;
}

/*****************************Private*Routine******************************\
* DecodeNextPicture
*
* Decodes the next picture stored in the circular buffer.  If the picture
* is not "skipped" it is passed to the output pin.  Updates m_BufferStart,
* m_BufferCurr and m_BufferFullness to reflect any emptying of the buffer that
* has taken place.
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::DecodeNextPicture()
{
    DWORD   rc;
    HRESULT hr = S_OK;

    //
    // ParthaSr.
    // trying to recover from the absence of quality messages
    // when I do a skip - so I am predictively skipping
    // frames.  - Snap back to default if I have already
    // skipped enough frames
    //
    if (m_dwLateBy < 750000L) {
        m_dwCtrl = m_dwCtrlDefault | m_dwQualDefault;
    }

    m_VideoControl.dwCtrl = m_dwCtrl;

    //
    // If we're within 1 second of the end do all IP
    //
    if (m_tStop - m_tStopPrev < CRefTime(1000L)) {

        //
        // But are we almost at the end? in which case we need to decode
        // everything
        //
        if (m_tStop - m_tStopPrev < CRefTime(m_seqInfo.tPictureTime) +
                                    CRefTime(m_seqInfo.tPictureTime)) {

            if ((m_dwCtrl & 0xFFFF) != DECODE_DIS) {
                m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IPB;
            }
        }
        else {

            if ((m_dwCtrl & 0xFFFF) < DECODE_IP) {
                m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IP;
            }
        }
    }
    else {

        //
        // Only do IP if we're before the start
        //
        if ((m_dwCtrl & 0x3F) > DECODE_IP) {

            if (m_tStopPrev
              + CRefTime(m_seqInfo.tPictureTime)
              + CRefTime(m_seqInfo.tPictureTime) < CRefTime(0L)) {

                m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IP;
            }

        }
    }
    DbgLog((LOG_TRACE, 4, TEXT("Decode flags = %X"), m_VideoControl.dwCtrl));

    //
    // Initialize the video codec with the new video data buffer.  I am
    // assuming that there is at least one video frame in the data
    // buffer.  When this function gets called to force out the frames
    // left in the buffer it needs to guarded with a try except block.
    //
    m_VideoControl.pCmprWrite  = m_BufferCurr + m_BufferFullness;
    m_VideoControl.pCmprRead   = m_BufferCurr;

    //
    // This is where the action starts, call the codec and let it do it stuff.
    //

    //  Don't die if Windows NT takes away the DCI surface
    try {

        MSR_START(m_PerfDecode);
        rc = m_pVideoDecoder->DecodeFrame(&m_VideoControl);
        MSR_STOP(m_PerfDecode);
    }
    except(EXCEPTION_EXECUTE_HANDLER) {

        DbgLog((LOG_ERROR, 1, TEXT("Exception in decoder!")));
        rc = DECODE_ERR_DATA;
    }

    //
    // Did the frame decode OK
    //

    if (rc == DECODE_SUCCESS) {

#ifdef DEBUG
        static char *ft[4] = { "Dummy", "I", "P", "B" };
#endif

        //
        // Record frames stats
        //
        MSR_INTEGER(m_FrameType, m_VideoControl.dwFrameType);
        if (m_VideoControl.dwSkipFlag) {

            DbgLog((LOG_TRACE, 2, TEXT("%hs Frame skipped"),
                    ft[m_VideoControl.dwFrameType]));

            m_dwFramesSkipped[m_VideoControl.dwFrameType]++;

            LARGE_INTEGER li;
            li.QuadPart = m_seqInfo.tPictureTime;
            if (m_dwLateBy > li.LowPart) {
                m_dwLateBy -= li.LowPart;
            }
        }
        else {
            m_dwFramesDecoded[m_VideoControl.dwFrameType]++;
        }

        if (m_VideoControl.dwFrameType == FTYPE_I ||
            m_VideoControl.dwFrameType == FTYPE_P) {

            //
            //  Do I/P - we're actually going to draw the I/P we
            //  decoded last time we got one, not the present one
            //

            if (m_NextIP.GetTime((REFERENCE_TIME *)&m_TimeAtLastSyncPoint)) {
                m_TimeSinceLastSyncPoint = 0;
            }

            REFERENCE_TIME t;
            BOOL bIFrame = m_VideoControl.dwFrameType == FTYPE_I;

            if (UpdateTimeSyncPoint(m_VideoControl.pFrameStartPos, &t)) {
                m_NextIP.Set(m_VideoControl.dwSkipFlag, bIFrame,
                             TRUE, t, m_VideoControl.dwTemporalReference);
            }
            else {
                m_NextIP.Set(m_VideoControl.dwSkipFlag, bIFrame,
                             FALSE, t, m_VideoControl.dwTemporalReference);
            }
        }
        else {

            //
            //  Do B
            //
            m_NextIP.NextRef(m_VideoControl.dwTemporalReference);
            if ( UpdateTimeSyncPoint(m_VideoControl.pFrameStartPos,
                                     (REFERENCE_TIME*)&m_TimeAtLastSyncPoint) ) {
                m_TimeSinceLastSyncPoint = 0;
            }
        }

        CRefTime tStop(m_TimeAtLastSyncPoint + m_TimeSinceLastSyncPoint +
                       m_seqInfo.tPictureTime);

        if (!m_VideoControl.dwSkipFlag) {

            LPBITMAPINFOHEADER  lpbiDst = HEADER(m_pOutput->CurrentMediaType().Format());

            //
            // if the time is < 0, then this is preroll to get from keyframe to
            // the current frame. We decompress it into the output buffer but
            // don't deliver it.
            //
            // In order that there are no gaps we actually start the next
            // frame where we predicted it would start, rather than the
            // actual synch point
            //

            CRefTime tStart(m_tStopPrev);

            DbgLog((LOG_TRACE, 2,
                    TEXT("%hs Frame decoded - tStart = %s, tStop = %s"),
                    ft[m_VideoControl.dwFrameType],
                    (LPCTSTR)CDisp(tStart), (LPCTSTR)CDisp(tStop)));

            #pragma message (REMIND("Do Preroll right"))
            if (tStop > 0L && tStart <= m_tStop) {

                // decompressed frames are always key
                m_pOutSample->SetSyncPoint(TRUE);
                m_pOutSample->SetActualDataLength(lpbiDst->biSizeImage);
                m_pOutSample->SetTime((REFERENCE_TIME*)&tStart,
                                      (REFERENCE_TIME*)&tStop);

                DbgLog((LOG_TRACE, 2, TEXT("%hs Frame sent to next filter"),
                        ft[m_VideoControl.dwFrameType]));

                hr = m_pOutput->Deliver(m_pOutSample);
                MSR_NOTE(m_FrameDrawn);
            }
        }

        //
        // We have successfully decoded a frame.
        // Set the new position
        //
        m_tStopPrev = tStop;
        UpdateBufferPosition(m_VideoControl.pCmprRead);
        m_TimeSinceLastSyncPoint += m_seqInfo.tPictureTime;
    }
    else if (rc == DECODE_ERR_QUARTZ) {

        DbgLog((LOG_ERROR, 2,
                TEXT("Could not get buffer from down stream filter")));
        hr = hrCallback;
    }
    else if (rc == DECODE_ERR_DATA) {

        //
        // We did not have enough data available to decode the
        // current frame, so save the data for next time.
        //
        // Since this can only happen at a discontinuity, end of
        // stream or undecipherable data we'll just throw the data
        // away if it can't be eaten
        DbgLog((LOG_ERROR, 2, TEXT("Buffer underflow") ));

        UpdateBufferPosition(m_VideoControl.pCmprRead);

        //
        //  Notify the filter graph of stream errors
        //
        NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, hr, 0);
    }
    else {

        //
        // Some sort of error occurred, throw the remainder of the
        // buffer away and skip this packet.
        //
        DbgLog((LOG_ERROR, 2,
                TEXT("Bad return code %d from MediaMatics video codec!"), rc ));
        UpdateBufferPosition(m_VideoControl.pCmprRead);

        //
        //  Notify the filter graph of stream errors
        //
        NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, hr, 0);
    }

    //
    // release the output buffer. If the connected pin still needs it,
    // it will have addrefed it itself.
    //
    if (m_pOutSample != NULL) {
        m_pOutSample->Release();
        m_pOutSample = NULL;
    }

    return hr;
}

/*****************************Private*Routine******************************\
* CopySampleToBuffer
*
* Copies the sample to the input buffer and returns the number of bytes
* present in the buffer.  Updates m_BufferStart, m_BufferCurr and
* m_BufferFullness to reflect any wrapping in the buffer
* that may have occurred.
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::CopySampleToBuffer(
    IMediaSample *pSample
    )
{
    BYTE    *pSrc;
    long    LenLeftInBuffer;
    long    AmountToCopy;
    HRESULT hr;

    if (m_pSample == NULL) {

        hr = pSample->GetPointer(&pSrc);
        if (FAILED(hr)) {
            return hr;
        }
        ASSERT(pSrc);

        // Skip pass the stream header and extract the packet length.
        //
        if (m_bPayloadOnly) {
            m_pSample = pSrc;
            m_LenLeftInPacket = pSample->GetActualDataLength();
        }
        else {
            m_pSample = SkipToPacketData(pSrc, m_LenLeftInPacket);
            if (m_pSample == NULL) {
                return E_INVALIDARG;
            }
        }
    }

    //
    // Move what remains in the video data buffer to the top of the buffer
    // and append the new video data to it.
    // Don't ever let it fill up completely or we'll have to worry about
    // full vs empty
    //
    LenLeftInBuffer = m_VBlockSize - m_BufferFullness;

    AmountToCopy = min(LenLeftInBuffer, m_LenLeftInPacket);
    CopyMemory(m_BufferCurr + m_BufferFullness, m_pSample, AmountToCopy);

    m_LenLeftInPacket -= AmountToCopy;
    m_pSample         += AmountToCopy;
    m_BufferFullness  += AmountToCopy;

    return S_OK;
}

/*****************************Private*Routine******************************\
* UpdateBufferPosition
*
* Updates m_BufferCurr from the new position passed in.
* Checks if m_BufferCurr is in the 'virtual' space at the end of the buffer
* and if it is adjusts m_BufferCurrent and m_BufferFullness down by the real
* buffer size
*
\**************************************************************************/
void
CMpegVideoCodec::UpdateBufferPosition(
    LPBYTE lpNewCurrent
    )
{
    ASSERT(lpNewCurrent >= m_BufferCurr);

    //
    // Sometimes the position gets reported as 1 too many (!)
    //
    if (lpNewCurrent > m_BufferCurr + m_BufferFullness) {
        lpNewCurrent = m_BufferCurr + m_BufferFullness;
    }
    m_BufferFullness -= lpNewCurrent - m_BufferCurr;
    if (lpNewCurrent >= (m_BufferStart + m_VBlockSize)) {
        lpNewCurrent -= m_VBlockSize;
    }

    //
    // Fix up the time code list
    //
    while (TRUE) {

        POSITION pos = m_PtsQueue.GetHeadPosition();
        if (pos == NULL) {
            break;
        }

        CTimePosition *pTimePos = m_PtsQueue.Get(pos);

        if (BuffOffset(pTimePos->m_BufferPosition) < BuffOffset(lpNewCurrent)) {
            delete pTimePos;
            m_PtsQueue.RemoveHead();
        }
        else {
            break;
        }
    }

    //
    //  Advance to the new position
    //
    // m_BufferCurr = lpNewCurrent;
    MoveMemory(m_BufferCurr, lpNewCurrent, m_BufferFullness);
}

/*****************************Private*Routine******************************\
* UpdateTimeSyncPoint
*
* Each time we get a media sample with a Pts
* time stamp we add a TIMEPOSITION entry to a queue of time positions.
* Each time we decode an I frame we record the starting and ending position
* of the I frame picture within the input buffer, this information is
* then used to find a suitable time stamp to associate with the frame.  If
* a suitable time stamp cannot be found or the frame is not an I frame we
* calculate a suitable time code by extrapolation.
*
\**************************************************************************/
BOOL
CMpegVideoCodec::UpdateTimeSyncPoint(
    LPBYTE lpPicStart,
    REFERENCE_TIME *Time
    )
{
    POSITION  pos = m_PtsQueue.GetHeadPosition();
    BOOL      bFound;

    for (bFound = FALSE; pos != NULL; ) {
        CTimePosition *pTimePos = m_PtsQueue.GetNext(pos);

        if (BuffOffset(pTimePos->m_BufferPosition) <= BuffOffset(lpPicStart)) {

            //  Buffer start time stamp could be for us (but keep looking
            //  in case there's a better one)
            //
            //  NOTE this ASSUMES there are no packets with time stamps but no
            //  start code so in fact this one should be ours

            bFound = TRUE;
            *Time  = pTimePos->m_PtsTimeStamp;

delete pTimePos;
            m_PtsQueue.RemoveHead();
        }
        else {
            break;
        }
    }

    if (bFound) {
        DbgLog((LOG_TRACE, 3,
                TEXT("CMpegVideoCodec::UpdateTimeSyncPoint() : Found time %s"),
                (LPCTSTR)CDisp(*Time) ));
    }

    return bFound;
}

/*****************************Private*Routine******************************\
* BuffOffset
*
* Adjusts the supplied offset so that is based upon m_BufferCurr
*
\**************************************************************************/
inline ptrdiff_t
CMpegVideoCodec::BuffOffset(
    LPBYTE Offset
    )
{
    ptrdiff_t x = Offset - m_BufferCurr;
    if (x < 0) {
        x += m_VBlockSize;
    }
    return x;
}

/******************************Public*Routine******************************\

* CheckInputType
*
* Check if you can support mtIn
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::CheckInputType(
    const CMediaType* pmtIn
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CheckInputType")));

    //
    //  Check for native streams
    //

    if (*pmtIn->Type() == MEDIATYPE_Stream &&
        *pmtIn->Subtype() == MEDIASUBTYPE_MPEG1Video) {

        //
        //  If there's no format block we'll read the stream during
        //  CompleteConnect()
        //
        if (pmtIn->cbFormat == 0) {
            return S_OK;
        }
    }
    else {
        //
        // check this is an MPEG video format type
        //
        if (*pmtIn->FormatType() != FORMAT_MPEGVideo) {
            return E_INVALIDARG;
        }

        //
        // we only support MEDIATYPE_Video
        //
        if (*pmtIn->Type() != MEDIATYPE_Video) {
            return E_INVALIDARG;
        }

        if (*pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Packet &&
            *pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Payload) {
            return E_INVALIDARG;
        }
    }

    if (pmtIn->cbFormat < SIZE_VIDEOHEADER + sizeof(DWORD) ||
        pmtIn->cbFormat < SIZE_MPEG1VIDEOINFO((MPEG1VIDEOINFO *)pmtIn->pbFormat)) {
        return E_INVALIDARG;
    }

    //
    // Check the sequence header and save the info
    //

    MPEG1VIDEOINFO* videoInfo = (MPEG1VIDEOINFO *)pmtIn->pbFormat;
    if (!ParseSequenceHeader(videoInfo->bSequenceHeader,
                             videoInfo->cbSequenceHeader, &m_seqInfo)) {
        return E_INVALIDARG;
    }

    return S_OK;
}

/******************************Public*Routine******************************\
* CheckTransform
*
* Check if you can support the transform from this input to this output
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::CheckTransform(
    const CMediaType* pmtIn,
    const CMediaType* pmtOut
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CheckTransform")));

    // we only output video
    if (*pmtOut->Type() != MEDIATYPE_Video) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    // Check there is a format block
    if (*pmtOut->FormatType() != FORMAT_VideoInfo) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    //
    // See if we can use dci/direct draw.
    // First check that there is a non empty target rectangle.
    //

    VIDEOINFO *videoInfo = (VIDEOINFO *)pmtOut->pbFormat;
    if (!IsRectEmpty(&videoInfo->rcTarget)) {

        //
        // Next, check that the source rectangle is the entire movie.
        //

        if ( videoInfo->rcSource.left   == 0
          && videoInfo->rcSource.top    == 0
          && videoInfo->rcSource.right  == m_seqInfo.lWidth
          && videoInfo->rcSource.bottom == m_seqInfo.lHeight) {

            //
            // Now check that the target rectangles size is the same as
            // the movies, that is there is no stretching or shrinking.
            //

            if ( (videoInfo->rcTarget.right - videoInfo->rcTarget.left)
                    == m_seqInfo.lWidth
              && (videoInfo->rcTarget.bottom - videoInfo->rcTarget.top)
                    == m_seqInfo.lHeight) {
#ifndef _X86_
                // On Risc machines make sure we are DWORD aligned
                if ((videoInfo->rcTarget.left & 0x03) == 0x00)
#endif
                {
                    DbgLog((LOG_TRACE, 2, TEXT("Using DCI")));
                    return S_OK;
                }
            }
        }
        DbgLog((LOG_TRACE, 2, TEXT("NOT Using DCI")));
        return E_FAIL;
    }

    return S_OK;
}

/******************************Public*Routine******************************\
* SetMediaType
*
* Overriden to know when the media type is actually set
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::SetMediaType(
    PIN_DIRECTION direction,
    const CMediaType *pmt
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::SetMediaType")));

    if (direction == PINDIR_INPUT) {

        //
        // Get the data type
        //
        if (*pmt->Subtype() == MEDIASUBTYPE_MPEG1Packet) {
            m_bPayloadOnly = FALSE;
        }
        else {
            ASSERT(*pmt->Subtype() == MEDIASUBTYPE_MPEG1Payload ||
                   *pmt->Subtype() == MEDIASUBTYPE_MPEG1Video);
            m_bPayloadOnly = TRUE;
        }
    }
    else {
        SetOutputPinMediaType(pmt);
    }
    return S_OK;
}

/*****************************Private*Routine******************************\
* SetOutputPinMediaType
*
* This function is a static member function of CMpegVideoCodec, this is so that
* it can be called from SetMediaType (above) and from the static member
* function GetDecodeBufferAndFormat.  Note that we pass in the "this" pointer
* explicitly.
*
\**************************************************************************/
void
CMpegVideoCodec::SetOutputPinMediaType(
    const CMediaType *pmt
    )
{
    VIDEOINFO   *pvi;
    LONG        lStride;
    LONG        lOffset;

    if (*pmt->Subtype() == MEDIASUBTYPE_Y41P) {
        m_dwOutputFormatDib = MM_411PK;
        m_dwOutputFormatDdb = MM_411PK;
    }
    else if (*pmt->Subtype() == MEDIASUBTYPE_YUY2) {
        m_dwOutputFormatDib = MM_422PK;
        m_dwOutputFormatDdb = MM_422PK;
    }
    else if (*pmt->Subtype() == MEDIASUBTYPE_UYVY) {
        m_dwOutputFormatDib = MM_422SPK;
        m_dwOutputFormatDdb = MM_422SPK;
    }
    else if (*pmt->Subtype() == MEDIASUBTYPE_RGB24) {
        m_dwOutputFormatDib = MM_RGB24_DIB;
        m_dwOutputFormatDdb = MM_RGB24_DDB;
    }
    else if (*pmt->Subtype() == MEDIASUBTYPE_RGB565) {
        m_dwOutputFormatDib = MM_RGB565_DIB;
        m_dwOutputFormatDdb = MM_RGB565_DDB;
    }
    else if (*pmt->Subtype() == MEDIASUBTYPE_RGB555) {
        m_dwOutputFormatDib = MM_RGB555_DIB;
        m_dwOutputFormatDdb = MM_RGB555_DDB;
    }
    else {
        ASSERT(*pmt->Subtype() == MEDIASUBTYPE_RGB8);
        if (m_PaletteType == COLOUR_PALETTE) {
            m_dwOutputFormatDib = MM_RGB8_DIB;
            m_dwOutputFormatDdb = MM_RGB8_DDB;
        }
        else {
            m_dwOutputFormatDib = MM_Y_DIB;
            m_dwOutputFormatDdb = MM_Y_DDB;
        }
    }

    //
    // lStride is the distance between in bytes between a pel on the
    // screen and the pel directly underneath it.
    //

    pvi = (VIDEOINFO *)pmt->pbFormat;
    lStride = ((pvi->bmiHeader.biWidth * pvi->bmiHeader.biBitCount) + 7) / 8;
    lStride = (lStride + 3) & ~3;

    //
    // lOffset is the distance in bytes from the top corner of the
    // target bitmap to the top corner of the video image.  When we are
    // using DIBs this value allways be zero.
    //
    // When we are using DCI/DirectDraw this value will only be zero if
    // we are drawing the video image at the top left hand corner of the
    // display.
    //

    lOffset = (((pvi->rcTarget.left * pvi->bmiHeader.biBitCount) + 7) / 8) +
                (pvi->rcTarget.top * lStride);

    m_VideoControl.dwOutStride = lStride;
    m_VideoControl.dwOutOffset = lOffset;

    //
    // See what orientation we need to use when colour converting
    // the frame.
    //

    if (pvi->bmiHeader.biHeight > 0) {
        m_VideoControl.dwOutputFormat = m_dwOutputFormatDib;
    }
    else {
        m_VideoControl.dwOutputFormat = m_dwOutputFormatDdb;
    }
}

/******************************Public*Routine******************************\
* GetMediaType
*
* Return our preferred output media types (in order)
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::GetMediaType(
    int iPosition,
    CMediaType *pmt
    )
{
    VIDEOINFO   *pVideoInfo;
    CMediaType  cmt;

    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::GetMediaType")));

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    //
    // Quick hack to enable greyscale for Robin
    //
    if (m_PaletteType == GREY_PALETTE) {

        if (iPosition > 0) {
            return VFW_S_NO_MORE_ITEMS;
        }

        iPosition = MT_RGB8;
    }

    //
    // We copy the proposed output format so that we can play around with
    // it all we like and still leave the original preferred format
    // untouched.  We try each of the known BITMAPINFO types in turn
    // starting off with the best quality moving through to the worst
    // (palettised) format
    //

    cmt = m_pInput->CurrentMediaType();

    if (*cmt.Type() != MEDIATYPE_Video) {
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_PREHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        pVideoInfo->rcSource.top = 0;
        pVideoInfo->rcSource.left = 0;
        pVideoInfo->rcSource.right = m_seqInfo.lWidth;
        pVideoInfo->rcSource.bottom = m_seqInfo.lHeight;
        pVideoInfo->AvgTimePerFrame = m_seqInfo.tPictureTime;
        pVideoInfo->rcTarget = pVideoInfo->rcSource;
    }
    else {
        pVideoInfo = (VIDEOINFO *) cmt.Format();
        ASSERT(pVideoInfo != NULL);
    }

    //
    // Fill in the output format according to requested position, see the
    // Media Type enum in mpgvideo.h for the list of supported types and
    // their positions.
    //
    switch (iPosition) {
    case MT_Y41P:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('Y','4','1','P'), 12);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_Y41P);
        break;

    case MT_YUY2:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('Y','U','Y','2'), 16);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_YUY2);
        break;

    case MT_UYVY:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('U','Y','V','Y'), 16);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_UYVY);
        break;

    case MT_RGB24:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, BI_RGB, 24);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_RGB24);
        break;

    case MT_RGB565:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER +
                                                          SIZE_MASKS);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }

        InitDestinationVideoInfo(pVideoInfo, BI_BITFIELDS, 16);

        DWORD *pdw;
        pdw = (DWORD *)(HEADER(pVideoInfo) + 1);
        pdw[iRED]   = bits565[iRED];
        pdw[iGREEN] = bits565[iGREEN];
        pdw[iBLUE]  = bits565[iBLUE];

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_RGB565);
        break;

    case MT_RGB555:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, BI_RGB, 16);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_RGB555);
        break;

    case MT_RGB8:
        pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(
                                           SIZE_VIDEOHEADER + SIZE_PALETTE);
        if (pVideoInfo == NULL) {
            return E_OUTOFMEMORY;
        }
        InitDestinationVideoInfo(pVideoInfo, BI_RGB, 8);
        InitDestinationPalette(pVideoInfo);

        *pmt = cmt;
        pmt->SetSubtype(&MEDIASUBTYPE_RGB8);
        break;

    default:
        return VFW_S_NO_MORE_ITEMS;

    }

    //
    // This block assumes that lpbi has been set up to point to a valid
    // bitmapinfoheader and that cmt has been copied into *pmt.
    // This is taken care of in the switch statement above.  This should
    // kept in mind when new formats are added.
    //
    pmt->SetType(&MEDIATYPE_Video);
    pmt->SetFormatType(&FORMAT_VideoInfo);

    //
    // we assume the output format is uncompressed
    //
    pmt->SetTemporalCompression(FALSE);
    pmt->SetSampleSize(HEADER(pVideoInfo)->biSizeImage);
    return S_OK;
}

/*****************************Private*Routine******************************\
* InitDestinationVideoInfo
*
* Fills in common video and bitmap info header fields
*
\**************************************************************************/
void
CMpegVideoCodec::InitDestinationVideoInfo(
    VIDEOINFO *pVideoInfo,
    DWORD dwComppression,
    int nBitCount
    )
{
    LPBITMAPINFOHEADER lpbi = HEADER(pVideoInfo);
    lpbi->biSize          = sizeof(BITMAPINFOHEADER);
    lpbi->biWidth         = m_seqInfo.lWidth;
    lpbi->biHeight        = m_seqInfo.lHeight;
    lpbi->biPlanes        = 1;
    lpbi->biBitCount      = nBitCount;
    lpbi->biXPelsPerMeter = 0;
    lpbi->biYPelsPerMeter = 0;
    lpbi->biCompression   = dwComppression;
    lpbi->biSizeImage     = GetBitmapSize(lpbi);

    //
    // The "bit" rate is image size in bytes times 8 (to convert to bits)
    // divided by the AvgTimePerFrame.  This result is in bits per 100 nSec,
    // so we multiply by 10000000 to convert to bits per second, this multiply
    // is combined with "times" 8 above so the calculations becomes:
    //
    // BitRate = (biSizeImage * 80000000) / AvgTimePerFrame
    //
    LARGE_INTEGER li;
    li.QuadPart = pVideoInfo->AvgTimePerFrame;
    pVideoInfo->dwBitRate = MulDiv(lpbi->biSizeImage, 80000000, li.LowPart);
    pVideoInfo->dwBitErrorRate = 0L;

}

/*****************************Private*Routine******************************\
* InitDestinationPalette
*
* Creates a standard colour palette or gray scale palette as determined
* by the PaletteType parameter.
*
\**************************************************************************/
void
CMpegVideoCodec::InitDestinationPalette(
    VIDEOINFO *pVideoInfo
    )
{
    int             i;
    int             iPalLowEnd;
    int             iPalHiStart;
    PALETTEENTRY    pal[24];
    HDC             hDC;

    if (m_PaletteType == COLOUR_PALETTE) {
        for ( i = 0; i < 256; i++ ) {
            pVideoInfo->bmiColors[i].rgbRed      = PaletteData[i].r;
            pVideoInfo->bmiColors[i].rgbBlue     = PaletteData[i].b;
            pVideoInfo->bmiColors[i].rgbGreen    = PaletteData[i].g;
            pVideoInfo->bmiColors[i].rgbReserved = 0;
        }
        iPalLowEnd  = 16;
        iPalHiStart = 232;
    }
    else {
        for ( i = 0; i < 256; i++ ) {
            pVideoInfo->bmiColors[i].rgbRed      = i;
            pVideoInfo->bmiColors[i].rgbBlue     = i;
            pVideoInfo->bmiColors[i].rgbGreen    = i;
            pVideoInfo->bmiColors[i].rgbReserved = 0;
        }
        iPalLowEnd  = 10;
        iPalHiStart = 246;
    }

    hDC = GetDC(GetDesktopWindow());
    GetSystemPaletteEntries(hDC, 0, 16, &pal[0] );
    for ( i = 0; i < iPalLowEnd; i++ ) {

        pVideoInfo->bmiColors[i].rgbRed   = pal[i].peRed;
        pVideoInfo->bmiColors[i].rgbGreen = pal[i].peGreen;
        pVideoInfo->bmiColors[i].rgbBlue  = pal[i].peBlue;
    }

    GetSystemPaletteEntries(hDC, iPalHiStart, 256 - iPalHiStart, &pal[0] );
    for ( i = iPalHiStart; i < 256; i++ ) {

        pVideoInfo->bmiColors[i].rgbRed   = pal[i - iPalHiStart].peRed;
        pVideoInfo->bmiColors[i].rgbGreen = pal[i - iPalHiStart].peGreen;
        pVideoInfo->bmiColors[i].rgbBlue  = pal[i - iPalHiStart].peBlue;
    }
    ReleaseDC(GetDesktopWindow(), hDC);

    HEADER(pVideoInfo)->biClrUsed = 256;
    HEADER(pVideoInfo)->biClrImportant = 0;
}

/******************************Public*Routine******************************\
* DecideBufferSize
*
* Called from CBaseOutputPin to prepare the allocator's count
* of buffers and sizes
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::DecideBufferSize(
    IMemAllocator * pAllocator,
    ALLOCATOR_PROPERTIES * pProperties
    )
{
    DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::DecideBufferSize")));

    ASSERT(pAllocator);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;
    pProperties->cbBuffer = m_pOutput->CurrentMediaType().GetSampleSize();

    ASSERT(pProperties->cbBuffer);
    DbgLog((LOG_TRACE, 2, TEXT("Sample size = %ld\n"), pProperties->cbBuffer));

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAllocator->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT(Actual.cbAlign == 1);
    ASSERT(Actual.cbPrefix == 0);

    if (Actual.cbBuffer < pProperties->cbBuffer ||
        Actual.cBuffers < pProperties->cBuffers) {

            // can't use this allocator
            return E_INVALIDARG;
    }
    return S_OK;
}

/******************************Public*Routine******************************\
* StartStreaming
*
*
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::StartStreaming(
    void
    )
{
    CAutoLock   lock(&m_csFilter);
    long        Size;
    // HRESULT     hr;

    ASSERT(MEDIASUBTYPE_RGB8   == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_RGB555 == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_RGB565 == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_Y41P   == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_YUY2   == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_UYVY   == *m_pOutput->CurrentMediaType().Subtype() ||
           MEDIASUBTYPE_RGB24  == *m_pOutput->CurrentMediaType().Subtype());

    ASSERT(m_pFrameBuff == NULL);
    ASSERT(m_pVideoDecoder == NULL);
    ASSERT(m_Buffer == NULL);

    //
    // Create the video codec - if we fail to open the codec it is
    // probably because the codec was unable to allocate memory for its
    // decoding tables.
    //
    m_pVideoDecoder = new CVideoDecoder(this);
    if (m_pVideoDecoder == NULL) {
        return E_OUTOFMEMORY;
    }

    //
    // Allocate some space for the codecs I and P frame buffer store
    //
    Size = m_seqInfo.lWidth * m_seqInfo.lHeight;
    m_pFrameBuff = new BYTE[ 3 * (Size + (2 * (Size / 4)))];
    if (m_pFrameBuff == NULL) {

        delete m_pVideoDecoder;
        m_pVideoDecoder = NULL;

        return E_OUTOFMEMORY;
    }

    m_VBlockSize = m_seqInfo.lvbv;
    m_VBlockSize +=  ((1 << 16) - 1);
    m_VBlockSize &= ~((1 << 16) - 1);

    // m_Buffer = new CCircularBuffer(m_VBlockSize, m_VBlockSize, hr);
    m_Buffer = new BYTE[m_VBlockSize];
    if (m_Buffer == NULL) {

        delete [] m_pFrameBuff;
        m_pFrameBuff = NULL;

        delete m_pVideoDecoder;
        m_pVideoDecoder = NULL;

        return E_OUTOFMEMORY;
    }

    //
    // Initialize the mpeg video codec
    //
    m_dwLateBy = 0;
    m_dwCtrl = (m_dwCtrlDefault | m_dwQualDefault);
    m_VideoControl.dwCtrl = m_dwCtrl;
    m_VideoControl.dwYStride = m_seqInfo.lWidth;
    m_VideoControl.dwYLines = m_seqInfo.lHeight;

    ZeroMemory(m_pFrameBuff, Size);
    m_VideoControl.pFrameBuff = m_pFrameBuff;

    //
    // Now reset the mpeg video decoder
    //
    ResetVideoDecoder();

    //
    // Reset frame stats
    //
    ZeroMemory(m_dwFramesSkipped, sizeof(m_dwFramesSkipped));
    ZeroMemory(m_dwFramesDecoded, sizeof(m_dwFramesDecoded));

    return S_OK;
}

/******************************Public*Routine******************************\
* StopStreaming
*
*
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::StopStreaming(
    void
    )
{
    CAutoLock       lock(&m_csFilter);
    CAutoLock       lck(&m_csReceive);

    CTimePosition   *pTimePos;

    ASSERT(m_pFrameBuff != NULL);
    ASSERT(m_pVideoDecoder != NULL);
    ASSERT(m_Buffer != NULL);

    delete m_pVideoDecoder;
    m_pVideoDecoder = NULL;

    delete [] m_pFrameBuff;
    m_pFrameBuff = NULL;

    delete [] m_Buffer;
    m_Buffer = NULL;

    //
    // Purge the PTS queue.
    //
    DbgLog((LOG_TRACE, 2,
            TEXT("Freeing %d time entries"), m_PtsQueue.GetCount()));

    while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) {
        delete pTimePos;
    }

    return S_OK;
}

/******************************Public*Routine******************************\
* Notify
*
* Handle quality control notifications sent to us
*
\**************************************************************************/
HRESULT
CMpegVideoCodec::AlterQuality(
    Quality q
    )
{
    DWORD dwNewCtrl;
    LARGE_INTEGER li;
    li.QuadPart = q.Late;
    DWORD dwTimeLate = li.LowPart;

    MSR_INTEGER(m_QualMsg, dwTimeLate);
    li.QuadPart = m_seqInfo.tPictureTime;

    DbgLog((LOG_TRACE, 2,
            TEXT("Q = %s Prop = %4.4ld Late = %8.8ld Fram = %8.8ld"),
            q.Type == Famine ? TEXT("Famine") : TEXT("Flood "),
            q.Proportion, dwTimeLate,
            li.LowPart));

    //
    // See if the user has overidden quality messages
    //
    if (m_IgnoreQualityMessage) {
        return S_OK;
    }

    //
    // Turn off the old option and then bring in the new.
    //

    m_dwLateBy = dwTimeLate;

    if (dwTimeLate <= 750000L) {
        DbgLog((LOG_TRACE, 2, TEXT("Default QL")));
        dwNewCtrl = m_dwCtrlDefault;
    }
    else if (dwTimeLate <= (7L * 330000L)) {
        DbgLog((LOG_TRACE, 2, TEXT("IP")));
        dwNewCtrl = DECODE_IP;
    }
    else {
        DbgLog((LOG_TRACE, 2, TEXT("I")));
        dwNewCtrl = DECODE_I;
    }

    m_dwCtrl = (m_dwQualDefault | dwNewCtrl);

    return S_OK;
}

// -------------------------------------------------------------------------
// IP Tracking class
// -------------------------------------------------------------------------
//
inline CMpegVideoCodec::CNextIP::CNextIP()
{
    Reset();
}

inline void
CMpegVideoCodec::CNextIP::Set(
    DWORD dwSkipFlag,
    BOOL bIFrame,
    BOOL bTimeSet,
    REFERENCE_TIME t,
    DWORD dwTemporalReference
    )
{
    if (m_bGotFirst) {

        m_bTimeToDraw = FALSE;
        NextRef(dwTemporalReference); // Might already be time if no Bs
    }
    else {
        if (bIFrame) {
            m_bGotFirst   = TRUE;
            m_bTimeToDraw = TRUE;
        }
    }
    m_dwSkipFlag          = dwSkipFlag;
    m_bTimeSet            = bTimeSet;
    m_t                   = t;
    m_dwTemporalReference = dwTemporalReference;

}

inline BOOL
CMpegVideoCodec::CNextIP::GotFirst()
{
    return m_bGotFirst;
}

inline void
CMpegVideoCodec::CNextIP::NextRef(
    DWORD dwTemporalReference
    )
{
    //  See if we're next after this one
    //  Don't set m_bTimeToDraw = FALSE here because in the case
    //  where we're stepping through undrawable B-frames before the
    //  first I-Frame we've already set m_bTimeToDraw = TRUE
    if (((dwTemporalReference + 1) & 1023) == m_dwTemporalReference) {
        m_bTimeToDraw = TRUE;
    }
    DbgLog((LOG_TRACE, 3,
            TEXT("New Temporal Reference %d, NextIP = %d, TimeToDraw = %d"),
            dwTemporalReference, m_dwTemporalReference,
            m_bTimeToDraw));
}

inline BOOL
CMpegVideoCodec::CNextIP::GetTime(
    REFERENCE_TIME *pt
    )
{
    if (m_bTimeSet) {
        *pt = m_t;
    }
    return m_bTimeSet;
}

inline BOOL
CMpegVideoCodec::CNextIP::TimeToDraw()  const
{
    return !m_dwSkipFlag && m_bTimeToDraw;
}

inline void
CMpegVideoCodec::CNextIP::Reset()
{
    m_bGotFirst = FALSE;
    m_bTimeSet = FALSE;
    m_dwTemporalReference = 0;
    m_bTimeToDraw = FALSE;
}

/******************************Public*Routine******************************\
* SkipToPacketData
*
*
*
\**************************************************************************/
LPBYTE
SkipToPacketData(
    LPBYTE pSrc,
    long &LenLeftInPacket
    )
{
    LPBYTE  lpPacketStart;
    DWORD   bData;
    long    Length;

    //
    // Skip the stream ID and extract the packet length
    //
    pSrc += 4;
    bData = *pSrc++;
    Length = (long)((bData << 8) + *pSrc++);
    DbgLog((LOG_TRACE, 3, TEXT("Packet length %ld"), Length ));

    //
    // Record position of first byte after packet length
    //
    lpPacketStart = pSrc;

    //
    // Remove stuffing bytes
    //
    for (; ; ) {
        bData = *pSrc++;
        if (!(bData & 0x80)) {
            break;
        }
    }

    if ((bData & 0xC0) == 0x40) {
        pSrc++;
        bData = *pSrc++;
    }

    switch (bData & 0xF1) {

    case 0x21:
        pSrc += 4;
        break;

    case 0x31:
        pSrc += 9;
        break;

    default:
        if (bData != 0x0F) {
            DbgLog((LOG_TRACE, 2, TEXT("Invalid packet - 0x%2.2X\n"), bData));
            return NULL;
        }
    }

    //
    // The length left in the packet is the original length of the packet
    // less those bytes that we have just skipped over.
    //
    LenLeftInPacket = Length - (pSrc - lpPacketStart);
    return pSrc;
}

#ifdef DEBUG
LPCTSTR PictureTypes[8] = {
    TEXT("forbidden frame type"),
    TEXT("I-Frame"),
    TEXT("P-Frame"),
    TEXT("B-Frame"),
    TEXT("D-Frame"),
    TEXT("Reserved frame type"),
    TEXT("Reserved frame type"),
    TEXT("Reserved frame type")
};

LPCTSTR PelAspectRatios[16] = {
    TEXT("Forbidden"),
    TEXT("1.0000 - VGA etc"),
    TEXT("0.6735"),
    TEXT("0.7031 - 16:9, 625 line"),
    TEXT("0.7615"),
    TEXT("0.8055"),
    TEXT("0.8437 - 16:9, 525 line"),
    TEXT("0.8935"),
    TEXT("0.9375 - CCIR601, 625 line"),
    TEXT("0.9815"),
    TEXT("1.0255"),
    TEXT("1.0695"),
    TEXT("1.1250 - CCIR601, 525 line"),
    TEXT("1.1575"),
    TEXT("1.2015"),
    TEXT("Reserved")
};

LPCTSTR PictureRates[16] = {
    TEXT("Forbidden"),
    TEXT("23.976"),
    TEXT("24"),
    TEXT("25"),
    TEXT("29.97"),
    TEXT("30"),
    TEXT("50"),
    TEXT("59.94"),
    TEXT("60"),
    TEXT("Reserved"),
    TEXT("Reserved"),
    TEXT("Reserved"),
    TEXT("Reserved"),
    TEXT("Reserved"),
    TEXT("Reserved"),
    TEXT("Reserved")
};
#endif // DEBUG

const LONG PictureTimes[16] = {
    0,
    (LONG)((double)10000000 / 23.976),
    (LONG)((double)10000000 / 24),
    (LONG)((double)10000000 / 25),
    (LONG)((double)10000000 / 29.97),

(LONG)((double)10000000 / 30),
    (LONG)((double)10000000 / 50),
    (LONG)((double)10000000 / 59.94),
    (LONG)((double)10000000 / 60)
};

const LONG AspectRatios[16] = {
    0,
    393700,
    (LONG)(393700.0 * 0.6735),
    (LONG)(393700.0 * 0.7031),
    (LONG)(393700.0 * 0.7615),
    (LONG)(393700.0 * 0.8055),
    (LONG)(393700.0 * 0.8437),
    (LONG)(393700.0 * 0.8935),
    (LONG)(393700.0 * 0.9375),
    (LONG)(393700.0 * 0.9815),
    (LONG)(393700.0 * 1.0255),
    (LONG)(393700.0 * 1.0695),
    (LONG)(393700.0 * 1.1250),
    (LONG)(393700.0 * 1.1575),
    (LONG)(393700.0 * 1.2015),
    0
};

/******************************Public*Routine******************************\
* ParseSequenceHeader
*
*
*
\**************************************************************************/
BOOL
CMpegVideoCodec::ParseSequenceHeader(
    const BYTE *pbData,
    LONG lData,
    SEQHDR_INFO *pInfo
    )
{
    ASSERT(*(UNALIGNED DWORD *)pbData == ByteSwap(SEQUENCE_HEADER_CODE));

    //
    // Check random marker bit
    //
    if (!(pbData[10] & 0x20)) {
        DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid marker bit")));
        return FALSE;
    }

    DWORD dwWidthAndHeight = ((DWORD)pbData[4] << 16) + ((DWORD)pbData[5] << 8) +
                             ((DWORD)pbData[6]);

    pInfo->lWidth = dwWidthAndHeight >> 12;
    pInfo->lHeight = dwWidthAndHeight & 0xFFF;

    DbgLog((LOG_TRACE, 2, TEXT("Width = %d, Height = %d"),
            pInfo->lWidth, pInfo->lHeight));

    //
    // the '8' bit is the scramble flag used by sigma designs - ignore
    //
    BYTE PelAspectRatioAndPictureRate = pbData[7];

    if ((PelAspectRatioAndPictureRate & 0x0F) > 8) {
        PelAspectRatioAndPictureRate &= 0xF7;
    }

    DbgLog((LOG_TRACE, 2, TEXT("Pel Aspect Ratio = %s"),
        PelAspectRatios[PelAspectRatioAndPictureRate >> 4]));
    DbgLog((LOG_TRACE, 2, TEXT("Picture Rate = %s"),
        PictureRates[PelAspectRatioAndPictureRate & 0x0F]));

    if ((PelAspectRatioAndPictureRate & 0xF0) == 0 ||
        (PelAspectRatioAndPictureRate & 0x0F) == 0) {

        DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid ratio/rate")));
        return FALSE;
    }

    pInfo->tPictureTime =
            (LONGLONG)PictureTimes[PelAspectRatioAndPictureRate & 0x0F];

    pInfo->lTimePerFrame =
            MulDiv((LONG)pInfo->tPictureTime, 9, 1000);

    /*  Pull out the bit rate and aspect ratio for the type */
    pInfo->dwBitRate = ((((DWORD)pbData[8] << 16) + ((DWORD)pbData[9] << 8) +
                          (DWORD)pbData[10]) >> 6);

    if (pInfo->dwBitRate == 0x3FFFF) {

        DbgLog((LOG_TRACE, 2, TEXT("Variable video bit rate")));
        pInfo->dwBitRate = 0;
    }
    else {

        pInfo->dwBitRate *= 400;
        DbgLog((LOG_TRACE, 2, TEXT("Video bit rate is %d bits per second"),
               pInfo->dwBitRate));
    }

    //
    // Get a DC
    //
    HDC hdc = GetDC(GetDesktopWindow());
    ASSERT(hdc != NULL);

    //
    //  Guess (randomly) 39.37 inches per meter
    //
    LONG lNotionalPelsPerMeter =
            MulDiv((LONG)GetDeviceCaps(hdc, LOGPIXELSX), 3937, 100);

    pInfo->lXPelsPerMeter = lNotionalPelsPerMeter;

    pInfo->lYPelsPerMeter = MulDiv(
                              lNotionalPelsPerMeter,
                              AspectRatios[PelAspectRatioAndPictureRate >> 4],
                              10000);
    //
    // Pull out the vbv
    //
    pInfo->lvbv = ((((LONG)pbData[10] & 0x1F) << 5) |
                    ((LONG)pbData[11] >> 3)) * 2048;

    DbgLog((LOG_TRACE, 2, TEXT("vbv size is %d bytes"), pInfo->lvbv));

    //
    // Check constrained parameter stuff
    //
    if (pbData[11] & 0x04) {

        DbgLog((LOG_TRACE, 2, TEXT("Constrained parameter video stream")));

        if (pInfo->lvbv > 40960) {

            DbgLog((LOG_ERROR, 1,
                    TEXT("Invalid vbv (%d) for Constrained stream"),
                    pInfo->lvbv));

            //
            //  Have to let this through too!  bisp.mpg has this
            //  But constrain it since it might be random
            //
            pInfo->lvbv = 40960;

        }

    }
    else {
        DbgLog((LOG_TRACE, 2, TEXT("Non-Constrained parameter video stream")));
    }

    pInfo->lActualHeaderLen = lData;
    CopyMemory((PVOID)pInfo->RawHeader, (PVOID)pbData, pInfo->lActualHeaderLen);
    return TRUE;
}
/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}


MPGVIDEO.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
/******************************Module*Header*******************************\
* Module Name: MpgVideo.h
*
* Prototype Mpeg Video codec
*
\**************************************************************************/
#ifndef _INC_MPGVIDEO_H
#define _INC_MPGVIDEO_H
#include <streams.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <stddef.h>
#include <string.h>

#include "Decoder.h"

// -------------------------------------------------------------------------
// Helper functions that can be used by audio and video codecs.
// -------------------------------------------------------------------------
//

/******************************Public*Routine******************************\
* ByteSwap
*
* Converts dwX from little endian to big endian and vice-versa.
*
\**************************************************************************/
__inline DWORD
ByteSwap(
    DWORD dwX
    )
{
#ifdef _X86_
    _asm    mov     eax, dwX
    _asm    bswap   eax
    _asm    mov     dwX, eax

    return dwX;
#else
    return _lrotl(((dwX & 0xFF00FF00) >> 8) | ((dwX & 0x00FF00FF) << 8), 16);
#endif
}

LPBYTE SkipToPacketData(LPBYTE pSrc, long &LenLeftInPacket);

// -------------------------------------------------------------------------
// This structure is used to associate Pts time stamps with positions
// within the circular buffer.  Each time we get a media sample with a Pts
// time stamp we add a TIMEPOSITION entry to a queue of time positions.
// Each time we decode an I frame we record the starting and ending position
// of the I frame picture within the circular buffer, this information is
// then used to find a suitable time stamp to associate with the frame.  If
// a suitable time stamp cannot be found or the frame is not an I frame we
// calculate a suitable time code by extrapolation.
// -------------------------------------------------------------------------
//
class CTimePosition {
public:
    CRefTime    m_PtsTimeStamp;
    LPBYTE      m_BufferPosition;

    CTimePosition(CRefTime *TimeStamp, LPBYTE lpPos)
        : m_PtsTimeStamp(*TimeStamp), m_BufferPosition(lpPos) {}
};

// -------------------------------------------------------------------------
// Structure to hold the contents of an Mpeg 1 sequence header.
// -------------------------------------------------------------------------
struct SEQHDR_INFO {
    LONG           lWidth;             //  Native Width in pixels
    LONG           lHeight;            //  Native Height in pixels
    LONG           lvbv;               //  vbv
    REFERENCE_TIME tPictureTime;       //  Time per picture in 100ns units
    LONG           lTimePerFrame;      //  Time per picture in MPEG units
    LONG           dwBitRate;          //  Bits per second
    LONG           lXPelsPerMeter;     //  Pel aspect ratio
    LONG           lYPelsPerMeter;     //  Pel aspect ratio
    DWORD          dwStartTimeCode;    //  First GOP time code (or -1)
    LONG           lActualHeaderLen;   //  Length of valid bytes in raw seq hdr
    BYTE           RawHeader[140];     //  The real sequence header
};

// -------------------------------------------------------------------------
// Quartz Mpeg video codec framework class
//
// -------------------------------------------------------------------------
//
class CMpegVideoOutputPin;
class CMpegVideoInpuPin;

class CMpegVideoCodec
    : public CTransformFilter {

public:

    //
    // --- Com stuff ---
    //
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
    DECLARE_IUNKNOWN;

    //
    // --- CTransform overrides ---
    //
    HRESULT Receive(IMediaSample *pSample);
    HRESULT CheckInputType(const CMediaType* mtIn);
    HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut);
    HRESULT DecideBufferSize(IMemAllocator * pAllocator,
                             ALLOCATOR_PROPERTIES * pProperties);
    HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT StartStreaming(void);
    HRESULT StopStreaming(void);
    HRESULT EndOfStream(void);
    HRESULT EndFlush(void);
    HRESULT AlterQuality(Quality q);

    CMpegVideoCodec(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *pHr);
    ~CMpegVideoCodec();

    //
    // Callback function from low-level deocoder to get an output
    // buffer to draw into.
    //
    LPBYTE GetDecodeBufferAndFormat();

private:
    LPBYTE          m_pSample;
    LONG            m_LenLeftInPacket;

    //
    // Input buffer
    //
    int             m_VBlockSize;
    LPBYTE          m_Buffer;
    LPBYTE          m_BufferStart;
    LPBYTE          m_BufferCurr;
    LONG            m_BufferFullness;
    void            UpdateBufferPosition(LPBYTE lpNewCurrent);

    //
    // Output buffer, for decode I and P frames.
    //
    LPBYTE          m_pFrameBuff;

    //
    // Sequence header of the video stream beging played.
    //
    SEQHDR_INFO     m_seqInfo;

    //
    // Decode statistics, number of I, P, B and skipped frames.
    //
    DWORD           m_dwFramesDecoded[4];
    DWORD           m_dwFramesSkipped[4];
    int             m_PerfDecode;
    int             m_QualMsg;
    int             m_FrameDrawn;
    int             m_FrameType;

    //
    // This is the low level mpeg video decoder that all the work
    //
    DWORD           m_dwLateBy;
    DWORD           m_dwCtrl;
    DWORD           m_dwCtrlDefault;
    DWORD           m_dwQualDefault;
    CVideoDecoder   *m_pVideoDecoder;
    VideoCtrl       m_VideoControl;

    //
    // The decoder calls back to get the colour conversion format
    // and a buffer to decode into.  The callback is static because it
    // does not have a "this" pointer.  hrCallback is used to keep a record
    // of any error that may have occurred during the callback.
    //
    void            SetOutputPinMediaType(const CMediaType *pmt);
    IMediaSample    *m_pOutSample;
    HRESULT         hrCallback;

    //
    // If the input sample contains a sync point then its
    // sample time is correct.  I will use this time to resync my
    // internal clock and reset the count of frames decoded
    // since the previous sync point.  Otherwise, if the sample
    // is not a sync point I use the average time per frame and
    // the count of frames since the last sync point to interpolate
    // a suitable time for the newly decoded frame.
    //
    CRefTime        m_TimeAtLastSyncPoint;
    CRefTime        m_TimeSinceLastSyncPoint;
    CRefTime        m_tStopPrev;     // Previous stop time
    CGenericList<CTimePosition> m_PtsQueue;

    //
    // IP Frame tracking class
    //
    class CNextIP
    {
    public:
        CNextIP();
        BOOL GotFirst();
        void NextRef(DWORD dwTemporalReference);
        BOOL GetTime(REFERENCE_TIME *pt);
        BOOL TimeToDraw() const;
        void Reset();
        void Set(DWORD dwSkipFlag, BOOL bIFrame,
                 BOOL bTimeSet, REFERENCE_TIME t, DWORD dwTemporalReference);

    private:
        DWORD           m_dwSkipFlag;
        BOOL            m_bGotFirst;
        BOOL            m_bTimeSet;
        BOOL            m_bTimeToDraw;
        REFERENCE_TIME  m_t;
        DWORD           m_dwTemporalReference;
    };

    CNextIP         m_NextIP;

    //
    // Remember whether the media type specified packets or payload
    //
    BOOL            m_bPayloadOnly;

    //
    // On 8 bit displays the user can select a gray scale palette
    // alteranative to the default colour palette, this bypasses the
    // colour space conversion code.
    //
    enum MEDIA_FORMATS {MM_411PK, MM_422PK, MM_422SPK, MM_RGB24_DIB,
                        MM_RGB24_DDB, MM_RGB565_DIB, MM_RGB565_DDB,
                        MM_RGB555_DIB, MM_RGB555_DDB, MM_RGB8_DIB,
                        MM_RGB8_DDB, MM_Y_DIB, MM_Y_DDB};

    enum MEDIA_TYPES   {MT_Y41P = 0, MT_YUY2, MT_UYVY, MT_RGB24, MT_RGB565,
                        MT_RGB555, MT_RGB8, MT_COUNT_OF_TYPES = MT_RGB8};

    enum PALETTE_TYPE {COLOUR_PALETTE, GREY_PALETTE};

    PALETTE_TYPE    m_PaletteType;
    BOOL            m_IgnoreQualityMessage;
    DWORD           m_dwOutputFormatDib;
    DWORD           m_dwOutputFormatDdb;

    HRESULT         CopySampleToBuffer(IMediaSample *pSample);
    BOOL            UpdateTimeSyncPoint(LPBYTE lpPicStart, REFERENCE_TIME *Time);
    ptrdiff_t       BuffOffset(LPBYTE Offset);

    HRESULT         DecodeNextPicture();
    void            ResetVideoDecoder();
    void            DecodeUntilBufferEmpty();

    void            InitDestinationVideoInfo(VIDEOINFO *pVI, DWORD Comp, int n);
    void            InitDestinationPalette(VIDEOINFO *pVideoInfo);

    BOOL            ParseSequenceHeader(const BYTE *pbData, LONG lData,
                                        SEQHDR_INFO *pInfo);

    //
    //  Stop time
    //
    CRefTime        m_tStop;
};
#endif


NOPINS.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <initguid.h>
#include "nopins.h"

// setup data

const AMOVIESETUP_FILTER sudNopins = { &CLSID_NoPins       // clsID
                                     , L"NoPins filter"    // strName
                                     , MERIT_DO_NOT_USE    // dwMerit
                                     , 0                   // nPins
                                     , NULL };             // lpPin

CFactoryTemplate g_Templates[]= { L"NoPins filter"
                                , &CLSID_NoPins
                                , CNoPins::CreateInstance
                                , NULL
                                , &sudNopins };
int g_cTemplates = 1;

/*
 * Null Filter - A filter that does nothing and has no pins !!!
 */

CNoPins::CNoPins(LPUNKNOWN pUnk, HRESULT *phr) :
    CBaseFilter(NAME("NoPins"), pUnk, &m_Lock, CLSID_NoPins),
    m_bComplete(FALSE)
{
}

/*
   CreateInstance
*/
CUnknown * WINAPI CNoPins::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{
    CNoPins *pNewObject = new CNoPins(punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return pNewObject;
}

/*  Pin enumeration */
int CNoPins::GetPinCount()
{
    return 0;
}

CBasePin *CNoPins::GetPin(int i)
{
    UNREFERENCED_PARAMETER(i);
    return NULL;
}

STDMETHODIMP CNoPins::Run(REFERENCE_TIME tStart)
{
#if 0 // No need for this if we don't support IMediaPosition
    CAutoLock lck(&m_Lock);
    if (!m_bComplete) {
        NotifyEvent(EC_COMPLETE, 0, 0);
        m_bComplete = TRUE;
    }
#endif
    return CBaseFilter::Run(tStart);
}

STDMETHODIMP CNoPins::Stop()
{
    CAutoLock lck(&m_Lock);
    m_bComplete = FALSE;
    return CBaseFilter::Stop();
}

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}


NOPINS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*
 * Null Filter - A filter that does nothing and has no pins !!!
 */

class CNoPins : public CBaseFilter
{
public:

    //  Make one of these
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    //  Constructor
    CNoPins(LPUNKNOWN pUnk, HRESULT *phr);

    //  Interface methods
    STDMETHODIMP Run(REFERENCE_TIME tStart);
    STDMETHODIMP Stop();

    //  CBaseFilter methods
    int CNoPins::GetPinCount();     // 0 pins
    CBasePin *CNoPins::GetPin(int iPin);

private:
    //  Locking
    CCritSec m_Lock;

    //  Don't send EC_COMPLETE twice
    BOOL     m_bComplete;
};

/*  Our class id */
// NoPins filter object
// a8fbb9c0-92e1-11cf-b4d1-00805f6cbbea
DEFINE_GUID(CLSID_NoPins,
0xa8fbb9c0, 0x92e1, 0x11cf, 0xb4, 0xd1, 0x00, 0x80, 0x5f, 0x6c, 0xbb, 0xea);


INULL.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//----------------------------------------------------------------------------
// iNull.h
//----------------------------------------------------------------------------

// A custom interface to allow the user select media types

#ifndef __INULLIP__
#define __INULLIP__

#ifdef __cplusplus
extern "C" {
#endif

//----------------------------------------------------------------------------
// INullIP's GUID
//
// {43D849C0-2FE8-11cf-BCB1-444553540000}
DEFINE_GUID(IID_INullIPP,
0x43d849c0, 0x2fe8, 0x11cf, 0xbc, 0xb1, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// INullIPP
//----------------------------------------------------------------------------
DECLARE_INTERFACE_(INullIPP, IUnknown)
{

    STDMETHOD(put_MediaType) (THIS_
      CMediaType *pmt       /* [in] */// the media type selected
 ) PURE;

    STDMETHOD(get_MediaType) (THIS_
      CMediaType **pmt      /* [out] */// the media type selected
 ) PURE;

    STDMETHOD(get_IPin) (THIS_
      IPin **pPin          /* [out] */// the source pin
 ) PURE;

    STDMETHOD(get_State) (THIS_
      FILTER_STATE *state  /* [out] */// the filter state
 ) PURE;

};
//----------------------------------------------------------------------------

#ifdef __cplusplus
}
#endif

#endif // __INULLIP__


NULLIP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// In-Place Null Transform
//
//     What This Sample Illustrates
//
// An in-place transform filter.
// Media types and the connection process
// Performance logging using Measure.dll
// Use of CAutoLock class to automatically release critical sections
//
//
//     Summary
//
// A simple in-place transform with one input pin and one output pin.
// The filter operates in-place which means that it has no media type
// enumerators of its own.  It obtains enumerators from the filters either
// on either side and passes types so obtained across for approval by the
// one on the other side.  On the way through it sees if it likes the
// type and rejects any that it doesn't.  The property sheet displays the
// list of types obtained from the upstream filter and allows one or
// all to be selected.  Any type not selected will be rejected by the
// connection process.  Almost all the work is done by the base classes.
//
//
//     Demonstration instructions
//
// First build the sample and get it registered (see nullip.reg)
// Start GRAPHEDT (available in the ActiveMovie SDK tools).
// Select Graph then InsertFilters and insert FileSource (async)
// Select Angry.AVI as the source file.
// Insert Null-In-Place.  (If it is not on the list then you
// failed to register it properly.  If it fails to load then you either
// didn't build it properly or the registration does not correctly point to
// the path where nullip.ax is now found.)
// Right click on the output pin of the source filter. Select Render.
// In the resulting graph you will see the Null-In-Place filter inserted between
// the AVI decompressor and the video renderer.
// Right click on the null-in-place filter to bring up its properties page.
// Select Major Type: MEDIATYPE_Video - Sub Type:MEDIASUBTYPE_RGB24 <etc>
// Click on Apply.  It wil tell you that it can't do it.
// Delete the output connection (Null-In-Place to Video Renderer)
// Try to Apply the media type again.  It succeeds.
// Connect the Mull-In-Place transform to the Video Renderer again.
// It will (probably) insert a Colour Space Transform to make the connection.
//
//
//     Implementation
//
// This filter has one input pin, one output pin and
// does its "transform" in-place (i.e. without copying the data)
// on the push thread (i.e. it is called with a buffer, which it
// "transforms" and gives to the next filter downstream.  It is
// then blocked until that filter returns.  It then returns
// to its own caller.)
//
// In this case the transform does nothing.  It just passes the data on.
//
// It has a properties page which allows control of what media types
// are allowed on connection.

// Note about Receive:
//
// NOTE: Please note that we shoud NOT override the Receive method to Deliver
// the input sample directly. We may have a case where we could not negotiate
// the same allocator for the input and output pins. (For example, if the
// upstream filter insists on its own allocator and provides a read-only
// allocator, that will be the case). In this case samples need to be
// copied from input to output allocator and then transformed. The base
// class Receive method does this. Transform () will get called on the copied
// over buffer and in our case, that function is a NOP and does nothing.
//
//
//      Files
//
// nullip.cpp    This file - main implementation
// nullprop.cpp  Implementation of the properties sheet
// nullprop.h    Class definition of properties class (used in nullip.cpp)
// nullprop.rc   Defines the property page dialog
// inull.h       Interface between nullip and nullprop
// nulluids.h    The public class ids (only referred to in nullip)
// resource.h    constants shared between nullip.rc and nullip.cpp
// texttype.cpp  implementation of class used to display media types as text
// texttype.h    definition of class used to display media types as text
// nullip.def    Imports and exports
// nullip.reg    What goes in the registry
// makefile      How to build it
//
//
//     Base classes used (refer to docs for diagram of what they inherit):
//
// CTransInPlaceFilter
// CBasePropertyPage
// CMediaType
// CTransInPlaceInputPin
// CTransInPlaceOutputPin

#include <streams.h>          // quartz, includes windows

// Eliminate two expected level 4 warnings from the Microsoft compiler.
// The class does not have an assignment or copy operator, and so cannot
// be passed by value.  This is normal.  This file compiles clean at the
// highest (most picky) warning level (-W4).
#pragma warning(disable: 4511 4512)

#include <measure.h>          // performance measurement (MSR_)

#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif

#include "nullUids.h"         // our own public guids

#include "inull.h"            // interface between filter and property sheet
#include "nullprop.h"         // property sheet implementatino class

// ----------------------------------------------------------------------------
// Class definitions of input pin, output pin and filter
// ----------------------------------------------------------------------------

class CNullInPlaceInputPin : public CTransInPlaceInputPin
{
    public:
        CNullInPlaceInputPin( TCHAR *pObjectName
                            , CTransInPlaceFilter *pTransInPlaceFilter
                            , HRESULT * phr
                            , LPCWSTR pName
                            )
                              : CTransInPlaceInputPin( pObjectName
                                                     , pTransInPlaceFilter
                                                     , phr
                                                     , pName
                                                     )
        {
        }

        HRESULT CheckMediaType(const CMediaType* pmt);
};

class CNullInPlaceOutputPin : public CTransInPlaceOutputPin
{
    public:
        CNullInPlaceOutputPin( TCHAR *pObjectName
                             , CTransInPlaceFilter *pTransInPlaceFilter
                             , HRESULT * phr
                             , LPCWSTR pName
                             )
                               : CTransInPlaceOutputPin( pObjectName
                                                       , pTransInPlaceFilter
                                                       , phr
                                                       , pName
                                                       )
        {
        }

        HRESULT CheckMediaType(const CMediaType* pmt);
};

// CNullInPlace
//
class CNullInPlace : public CTransInPlaceFilter
                   , public INullIPP
                   , public ISpecifyPropertyPages
{

        friend class CNullInPlaceInputPin;
        friend class CNullInPlaceOutputPin;

    public:

        static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

        DECLARE_IUNKNOWN;

        //
        // --- CTransInPlaceFilter Overrides --
        //

        virtual CBasePin *GetPin( int n );

        HRESULT CheckInputType(const CMediaType* mtIn)
            { UNREFERENCED_PARAMETER(mtIn);  return S_OK; }

        // Basic COM - used here to reveal our property interface.
        STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

        STDMETHODIMP get_IPin (IPin **ppPin) ;
        STDMETHODIMP put_MediaType(CMediaType *pmt);
        STDMETHODIMP get_MediaType(CMediaType **pmt);
        STDMETHODIMP get_State(FILTER_STATE *state);

        //
        // --- ISpecifyPropertyPages ---
        //

        STDMETHODIMP GetPages(CAUUID *pPages);

    private:

        CNullInPlace(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);

        //
        // Overrides the PURE virtual Transform of CTransInPlaceFilter base class
        // This is where the "real work" is done.
        //
        HRESULT Transform(IMediaSample *pSample)
            { UNREFERENCED_PARAMETER(pSample); return NOERROR; }

        //
        // If there are multiple instances of this filter active, it's
        // useful for debug messages etc. to know which one this is.
        //
        static m_nInstanceCount;   // total instances
        int m_nThisInstance;

        CMediaType m_mtPreferred;  // Media type chosen from property sheet

        CCritSec m_NullIPLock;     // To serialise access.
};

// ----------------------------------------------------------------------------
// Implementation of pins and filter
// ----------------------------------------------------------------------------

//
// DbgFunc
//
// Put out the name of the function and instance on the debugger.
// Call this at the start of interesting functions to help debug
//
#define DbgFunc(a) DbgLog(( LOG_TRACE                        \
                          , 2                                \
                          , TEXT("CNullInPlace(Instance %d)::%s") \
                          , m_nThisInstance                  \
                          , TEXT(a)                          \
                         ));

//
// setup data
//

const AMOVIESETUP_MEDIATYPE
sudPinTypes =   { &MEDIATYPE_NULL                // clsMajorType
                , &MEDIASUBTYPE_NULL }  ;       // clsMinorType

const AMOVIESETUP_PIN
psudPins[] = { { L"Input"            // strName
               , FALSE               // bRendered
               , FALSE               // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Output"           // strConnectsToPin
               , 1                   // nTypes
               , &sudPinTypes }      // lpTypes
             , { L"Output"           // strName
               , FALSE               // bRendered
               , TRUE                // bOutput
               , FALSE               // bZero
               , FALSE               // bMany
               , &CLSID_NULL         // clsConnectsToFilter
               , L"Input"            // strConnectsToPin
               , 1                   // nTypes
               , &sudPinTypes } };   // lpTypes

const AMOVIESETUP_FILTER
sudNullIP = { &CLSID_NullInPlace                 // clsID
            , L"Null In Place"                // strName
            , MERIT_DO_NOT_USE                // dwMerit
            , 2                               // nPins
            , psudPins };                     // lpPin

//
// Needed for the CreateInstance mechanism
//
CFactoryTemplate g_Templates[]=
    {   {L"Null-In-Place"
        , &CLSID_NullInPlace
        ,   CNullInPlace::CreateInstance
        , NULL
        , &sudNullIP }
    ,
        { L"Null IP Property Page"
        , &CLSID_NullIPPropertyPage
        , NullIPProperties::CreateInstance }

    };
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

//
// initialise the static instance count.
//
int CNullInPlace::m_nInstanceCount = 0;

// ----------------------------------------------------------------------------
//            Input pin implementation
// ----------------------------------------------------------------------------

// CheckMediaType
//
// Override CTransInPlaceInputPin method.
// If we have been given a preferred media type from the property sheet
// then only accept a type that is exactly that.
// else if there is nothing downstream, then accept anything
// else if there is a downstream connection then first check to see if
// the subtype (and implicitly the major type) are different from the downstream
// connection and if they are different, fail them
// else ask the downstream input pin if the type (i.e. all details of it)
// are acceptable and take that as our answer.
//
HRESULT CNullInPlaceInputPin::CheckMediaType( const CMediaType *pmt )
{   CNullInPlace *pNull = (CNullInPlace *) m_pTIPFilter;

#ifdef DEBUG
    DisplayType(TEXT("Input type proposed"),pmt);
#endif

    if (pNull->m_mtPreferred.IsValid() == FALSE)
    {
        if( pNull->m_pOutput->IsConnected() ) {

            //  We used to check here if the subtype of the proposed type
            //  matched the subtype of the type on the output pin
            //  but this broke as follows:
            //
            //  Renderering the output pin of a CODEC we picked up
            //  2 NULLIPs already in the graph:
            //
            //  Subtypes      Y41P       Y41P       RGB565
            //  Filters  CODEC---->NULLIP---->NULLIP------>RENDERER
            //
            //  Each NULLIP has scheduled a reconnect at this point
            //  and the reconnect on the first connection happens
            //  first:
            //
            //  Subtypes                 Y41P       RGB565
            //  Filters  CODEC     NULLIP---->NULLIP------>RENDERER
            //
            //  In trying to (re)connect the CODEC to the first NULLIP
            //  we first propose (say) Y41P and the first NULLIP
            //  checks that Y41P is the same as its output type
            //  so the call gets passed to the QueryAccept of
            //  the second NULLIP.  The second NULLIP rejected the
            //  call because the subtype on its output pin is not
            //  RGB565.  In a similar way the first NULLIP
            //  rejected Y41P.
            //
            //  By removing this optimization (checking the
            //  subtype before passing the call on) we avoided
            //  the problem.

            return pNull->m_pOutput->GetConnected()->QueryAccept( pmt );
        }
        return S_OK;
    }
    else
        if (*pmt == pNull->m_mtPreferred)
            return S_OK  ;
        else
            return VFW_E_TYPE_NOT_ACCEPTED;

}

// ----------------------------------------------------------------------------
//            Input pin implementation
// ----------------------------------------------------------------------------

// CheckMediaType
//
// Override CTransInPlaceOutputPin method.
// If we have ben given a media type from the property sheet, then insist on
// exactly that, else pass the request up to the base class implementation.
//
HRESULT CNullInPlaceOutputPin::CheckMediaType( const CMediaType *pmt )
{   CNullInPlace *pNull = (CNullInPlace *) m_pTIPFilter;

    if (pNull->m_mtPreferred.IsValid() == FALSE)
    {
        return CTransInPlaceOutputPin::CheckMediaType (pmt) ;
    }
    else
        if (*pmt == pNull->m_mtPreferred)
            return S_OK  ;
        else
            return VFW_E_TYPE_NOT_ACCEPTED;

}

// ----------------------------------------------------------------------------
//            Filter implementation
// ----------------------------------------------------------------------------

//
// CNullInPlace::Constructor
//
CNullInPlace::CNullInPlace(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
    : CTransInPlaceFilter (tszName, punk, CLSID_NullInPlace, phr)
{
    m_nThisInstance = ++m_nInstanceCount;
    m_mtPreferred.InitMediaType () ;
    DbgFunc("CNullInPlace");

} // (CNullInPlace constructor)

//
// CreateInstance
//
// Override CClassFactory method.
// Provide the way for COM to create a CNullInPlace object
//
CUnknown * WINAPI CNullInPlace::CreateInstance(LPUNKNOWN punk, HRESULT *phr) {

    CNullInPlace *pNewObject = new CNullInPlace(NAME("Null-In-Place Filter"), punk, phr );
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return pNewObject;

} // CreateInstance

//
// GetPin
//
// Override CBaseFilter method.
// return a non-addrefed CBasePin * for the user to addref if he holds onto it
// for longer than his pointer to us.  This is part of the implementation of
// EnumMediaTypes.  All attempts to refer to our pins from the outside have
// to come through here, so it's a valid place to create them.
//
CBasePin *CNullInPlace::GetPin(int n)
{
    // Create the single input pin and the single output pin
    // If anything fails, fail the whole lot and clean up.

    if (m_pInput == NULL || m_pOutput == NULL) {

        HRESULT hr = S_OK;

        m_pInput = new CNullInPlaceInputPin(NAME("Null input pin"),
                                          this,              // Owner filter
                                          &hr,               // Result code
                                          L"Input");         // Pin name

        // a failed return code should delete the object

        if (FAILED(hr) || m_pInput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
            return NULL;
        }

        m_pOutput = new CNullInPlaceOutputPin(NAME("Null output pin"),
                                            this,            // Owner filter
                                            &hr,             // Result code
                                            L"Output");      // Pin name

        // failed return codes cause both objects to be deleted

        if (FAILED(hr) || m_pOutput == NULL) {
            delete m_pInput;
            m_pInput = NULL;
            delete m_pOutput;
            m_pOutput = NULL;
            return NULL;
        }
    }

    /* Find which pin is required */

    switch(n) {
        case 0:
            return m_pInput;
        case 1:
           return m_pOutput;
    }
    return NULL;

} // GetPin

//
// NonDelegatingQueryInterface
//
// Override CUnknown method.
// Part of the basic COM (Compound Object Model) mechanism.
// This is how we expose our interfaces.
//
STDMETHODIMP CNullInPlace::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CheckPointer(ppv,E_POINTER);

    if (riid == IID_INullIPP) {
        return GetInterface((INullIPP *) this, ppv);
    }
    else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    }
    else {
        return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
    }

} // NonDelegatingQueryInterface

// get_IPin
//
// INull method.
// Set *ppPin to the upstream output pin which supplies us
// or to NULL if there is no upstream pin connected to us.
//
STDMETHODIMP CNullInPlace::get_IPin (IPin **ppPin)
{
    CAutoLock l(&m_NullIPLock);
    if (!m_pInput)
    {
        *ppPin = NULL ;
        return NOERROR ;
    }
    if( !m_pInput->IsConnected() )
        *ppPin = NULL ;
    else
    {
        *ppPin = m_pInput->GetConnected () ;
        (*ppPin)->AddRef() ;
    }
    return NOERROR ;

} // get_IPin

//
// put_MediaType
//
// INull method.
//
STDMETHODIMP CNullInPlace::put_MediaType(CMediaType *pmt)
{
    CAutoLock l(&m_NullIPLock);

    //
    // if the state of the graph is running, fail the call.
    //
    if (m_State == State_Running)
        return E_UNEXPECTED ;

    //
    // check the source and sink filters like this media type
    //
    if (pmt == NULL)
        m_mtPreferred.InitMediaType () ;
    else {
        IPin *pPin= m_pInput->GetConnected();
        if (pPin) {
            if (pPin->QueryAccept(pmt) != NOERROR) {
                MessageBox(NULL,TEXT("Upstream filter cannot provide this type"),
                           TEXT("Format Selection"),
                           MB_OK | MB_ICONEXCLAMATION);
                return VFW_E_TYPE_NOT_ACCEPTED;
            }
        }
        pPin= m_pOutput->GetConnected();
        if (pPin) {
            if (pPin->QueryAccept(pmt) != NOERROR) {
                MessageBox(NULL,TEXT("Downstream filter cannot accept this type"),
                           TEXT("Format Selection"),
                           MB_OK | MB_ICONEXCLAMATION);
                return VFW_E_TYPE_NOT_ACCEPTED;
            }
        }
        m_mtPreferred = *pmt ;
    }

    //
    // force reconnect of input if the media type of connection does not match.
    //
    if( m_pInput->IsConnected() )
    {
        if (m_pInput->CurrentMediaType()!= m_mtPreferred)
            m_pGraph->Reconnect(m_pInput);
    }
    return NOERROR ;

} // put_MediaType

//
// get_MediaType
//
// INull method.
// Set *pmt to the current preferred media type.
//
STDMETHODIMP CNullInPlace::get_MediaType(CMediaType **pmt)
{
    CAutoLock l(&m_NullIPLock);

    *pmt = &m_mtPreferred ;
    return NOERROR ;

} // get_MediaType

//
// get_State
//
// INull method
// Set *state to the current state of the filter (State_Stopped etc)
//
STDMETHODIMP CNullInPlace::get_State(FILTER_STATE *state)
{
    CAutoLock l(&m_NullIPLock);
    *state = m_State ;
    return NOERROR;

} // get_State

//-----------------------------------------------------------------------------
//                  ISpecifyPropertyPages implementation
//-----------------------------------------------------------------------------

//
// GetPages
//
// Returns the clsid's of the property pages we support
//
STDMETHODIMP CNullInPlace::GetPages(CAUUID *pPages) {

    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_NullIPPropertyPage;

    return NOERROR;

} // GetPages

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}

// Microsoft C Compiler will give hundreds of warnings about
// unused inline functions in header files.  Try to disable them.
#pragma warning( disable:4514)


NULLPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//

#include <streams.h>

// Eliminate two expected level 4 warnings from the Microsoft compiler.
// The class does not have an assignment or copy operator, and so cannot
// be passed by value.  This is normal.  This file compiles clean at the
// highest (most picky) warning level (-W4).
#pragma warning(disable: 4511 4512)

#include <windowsx.h>
#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

#include "resource.h"    // ids used in the dialog
#include "nulluids.h"    // public guids
#include "inull.h"       // private interface between property sheet and filter
#include "nullprop.h"    // our own class
#include "texttype.h"    // class to express media types as text

//
// CreateInstance
//
// Override CClassFactory method.
// Set lpUnk to point to an IUnknown interface on a new NullIPProperties object
// Part of the COM object instantiation mechanism
//
CUnknown * WINAPI NullIPProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{

    CUnknown *punk = new NullIPProperties(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return punk;
}

//
// NullIPProperties::Constructor
//
// Constructs and initialises a NullIPProperties object
//
NullIPProperties::NullIPProperties(LPUNKNOWN pUnk, HRESULT *phr)
    : CBasePropertyPage(NAME("NullIP Property Page"),pUnk,
        IDD_DIALOG1, IDS_TITLE)
    , m_pPin(NULL)
    , m_nIndex(0)
    , m_pINullIPP(NULL)
{
    ASSERT(phr);

} // (constructor) NullIPProperties

//
// SetDirty
//
// Sets m_hrDirtyFlag and notifies the property page site of the change
//
void NullIPProperties::SetDirty()
{
    m_bDirty = TRUE;
    if (m_pPageSite)
    {
        m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
    }

} // SetDirty

//
// OnReceiveMessage
//
// Override CBasePropertyPage method.
// Handles the messages for our property window
//
BOOL NullIPProperties::OnReceiveMessage(HWND hwnd,
                                        UINT uMsg,
                                        WPARAM wParam,
                                        LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            // get the hWnd of the list box
            m_hwndLB = GetDlgItem (hwnd, IDC_MEDIALIST) ;
            FillListBox();
            return (LRESULT) 1;
        }

        case WM_COMMAND:
        {
                if (HIWORD(wParam) == LBN_SELCHANGE)
            {
                SetDirty();
            }
            return (LRESULT) 1;
        }

    }
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);

} // OnReceiveMessage

//
// OnConnect
//
// Override CBasePropertyPage method.
// Notification of which object this property page should display.
// We query the object for the INullIPP interface.
//
// If cObjects == 0 then we must release the interface.
// Set the member variable m_pPin to the upstream output pin connected
// to our input pin (or NULL if not connected).
//
HRESULT NullIPProperties::OnConnect(IUnknown *pUnknown)
{
    ASSERT(m_pINullIPP == NULL);

    HRESULT hr = pUnknown->QueryInterface(IID_INullIPP, (void **) &m_pINullIPP);
    if (FAILED(hr))
    {
        return E_NOINTERFACE;
    }

    ASSERT(m_pINullIPP);

    ASSERT(!m_pPin);
    m_pINullIPP->get_IPin(&m_pPin);

    return NOERROR;

} // OnConnect

//
// OnDisconnect
//
// Override CBasePropertyPage method.
// Release the private interface, release the upstream pin.
//
HRESULT NullIPProperties::OnDisconnect()
{
    // Release of Interface

    if (m_pINullIPP == NULL)
        return E_UNEXPECTED;
    m_pINullIPP->Release();
    m_pINullIPP = NULL;

    //
    // Release the pin interface that we are holding.
    //
    if (m_pPin)
    {
        m_pPin->Release() ;
        m_pPin = NULL ;
    }
    return NOERROR;

} // OnDisconnect

//
// Activate
//
// We are being activated
//
HRESULT NullIPProperties::OnActivate()
{
    SendMessage (m_hwndLB, LB_SETCURSEL, m_nIndex, 0) ;
    return NOERROR;

} // Activate

//
// OnApplyChanges
//
// Changes made should be kept.
//
HRESULT NullIPProperties::OnApplyChanges()
{

    ASSERT(m_pINullIPP);

    //
    // get the current selection of the media type
    //
    int iIndex = SendMessage (m_hwndLB, LB_GETCURSEL, 0, 0) ;
    if (iIndex <= 0)
        iIndex = 0 ;

    m_bDirty = FALSE;            // the page is now clean
    if (iIndex == 0)
    {
        m_pINullIPP->put_MediaType (NULL) ;
        m_nIndex = 0;
        return NOERROR;
    }

    IEnumMediaTypes * pMTEnum;
    AM_MEDIA_TYPE *pMediaType;
    ULONG count;

    //
    // get the type enumerator from the pin again
    //
    HRESULT hr = m_pPin->EnumMediaTypes(&pMTEnum);
    if (FAILED(hr))
    {
        return hr;
    }

    //
    // skip to the appropriate type and read it
    //
    pMTEnum->Skip(iIndex - 1);
    pMTEnum->Next(1, &pMediaType, &count);
    if (count != 1) {
        pMTEnum->Release();
        return E_UNEXPECTED;
    }

    //
    // change the type by calling the filter
    //
    CMediaType cmt(*pMediaType);
    DeleteMediaType(pMediaType);
    hr = m_pINullIPP->put_MediaType(&cmt) ;
    if (FAILED(hr))
    {
        pMTEnum->Release();
        return NOERROR;
    }

    //
    // update the current selection and release the enumerator
    //
    m_nIndex = iIndex;
    pMTEnum->Release();
    return NOERROR;

} // OnApplyChanges

//
// FillListBox
//
// Fill the list box with an enumeration of the media type that our
//
void NullIPProperties::FillListBox()
{
    IEnumMediaTypes * pMTEnum;
    AM_MEDIA_TYPE *pMediaType;
    ULONG count;
    TCHAR szBuffer[200];
    int Loop = 0, wextent = 0 ;
    SIZE extent ;
    FILTER_STATE state ;
    CMediaType *pmtCurrent ;

    //
    // get the current media type
    //
    ASSERT(m_pINullIPP);
    m_pINullIPP->get_MediaType (&pmtCurrent);
    m_nIndex = 0 ;

    //
    // Fill the first entry of the list box with a choice to select any media.
    //
    LoadString(g_hInst, IDS_ANYTYPE, szBuffer, 200);
    SendMessage (m_hwndLB, LB_ADDSTRING, 0, (LPARAM)szBuffer) ;

    //
    // if the filter is not connected on the input, nothing more to fill
    // also return if we haven't gotten any pin interface
    //
    if (m_pPin == NULL)
        return ;

    //
    // get a dc for the control. need this to get the extent of the strings
    // that we put in the list box (so that we can get a horiz scroll bar).
    //
    HDC hdc = GetDC (m_hwndLB) ;

    //
    // now enumerate the media types from our pin.
    //
    HRESULT hr = m_pPin->EnumMediaTypes(&pMTEnum);

    if (SUCCEEDED(hr))
    {
        ASSERT(pMTEnum);
        pMTEnum->Next(1, &pMediaType, &count);
        while (count == 1)                    // While a media type retrieved OK
        {
            //
            // Count it - used to set m_nIndex.
            //
            Loop++;

            //
            // convert the media type to string.
            //
            CTextMediaType(*pMediaType).AsText(szBuffer, 200, TEXT(" - "), TEXT(" - "), TEXT("\0"));

            //
            // if the mediatype matches the current one, set m_nIndex to cur index
            //
            if (*pmtCurrent == *pMediaType) {
                m_nIndex = Loop;
            }

            //
            // get the extent of the string and save the max extent
            //
            GetTextExtentPoint (hdc, szBuffer, _tcslen(szBuffer), &extent) ;
            if (extent.cx > wextent)
                wextent = extent.cx ;

            //
            // add the string to the list box.
            //
            SendMessage (m_hwndLB, LB_ADDSTRING, 0, (LPARAM)szBuffer) ;

            //
            // enumerate the next media type and loop.
            //
            DeleteMediaType(pMediaType);
            pMTEnum->Next(1, &pMediaType, &count);
        }
        pMTEnum->Release();
        SendMessage (m_hwndLB, LB_SETHORIZONTALEXTENT, wextent, 0) ;
        SendMessage (m_hwndLB, LB_SETCURSEL, m_nIndex, 0) ;
    }
    ASSERT(m_pINullIPP);

    //
    // if the filter is in a running state, disable the list box and allow
    // no input.
    //
    m_pINullIPP->get_State(&state);
    EnableWindow (m_hwndLB, state != State_Running) ;

} // FillListBox


NULLPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//----------------------------------------------------------------------------
// nullprop.h
//----------------------------------------------------------------------------

class NullIPProperties : public CBasePropertyPage
{

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);
    DECLARE_IUNKNOWN;

private:

    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate();
    HRESULT OnApplyChanges();

    void SetDirty();
    void FillListBox();

    NullIPProperties(LPUNKNOWN lpunk, HRESULT *phr);

    HWND        m_hwndLB ;       // Handle of the list box
    int         m_nIndex ;       // Index of the selected media type
    IPin        *m_pPin ;        // The upstream output pin connected to us
    INullIPP    *m_pINullIPP;    // Null In Place property interface

};  // class NullIPProperties


NULLUIDS.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// nulluids.h
//

// The CLSID's used by the Null-In-Place filter

//
// Null-InPlace Filter Object
// {52b63860-dc93-11ce-a099-00aa00479a58}
DEFINE_GUID(CLSID_NullInPlace,
0x52b63860, 0xdc93, 0x11ce, 0xa0, 0x99, 0x00, 0xaa, 0x00, 0x47, 0x9a, 0x58);

// Null-InPlace Filters propertypage
// {8928AD20-2FEE-11cf-BCB1-444553540000}
DEFINE_GUID(CLSID_NullIPPropertyPage,
0x8928ad20, 0x2fee, 0x11cf, 0xbc, 0xb1, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0);


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by nullprop.rc
//
#define IDD_DIALOG1                     101
#define IDC_MEDIALIST                   1001

#define IDS_PREFORMAT                   301
#define IDS_PREMAJOR                    302
#define IDS_PRESUB                      303
#define IDS_NOTYPE                      304
#define IDS_ANYTYPE                     305
#define IDS_TITLE                       306

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1002
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


TEXTTYPE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// texttype.cpp
//

// CTextMediaType

#include <windows.h>
#include <mmsystem.h>
#include <streams.h>

#include <string.h>
#include <tchar.h>

#include <stdio.h>
#include <wchar.h>

#include "resource.h"
#include "texttype.h"

//
// AsText
//
// Return the media type as a text string. Will place szAfterMajor after
// the text string for the major type and szAfterOthers after all other
// string apart from the last one.
//
void CTextMediaType::AsText(LPTSTR szType,
                            unsigned int iLen,
                            LPTSTR szAfterMajor,
                            LPTSTR szAfterOthers,
                            LPTSTR szAtEnd)
{
    ASSERT(szType);

    //
    // Convert Majortype to string
    //
    TCHAR szMajorType[100];
    UINT  iMajorType = 100;

    CLSID2String(szMajorType, iMajorType, &majortype);

    //
    // Convert Subtype to string
    //
    TCHAR szSubType[100];
    UINT  iSubType = 100;
    CLSID2String(szSubType, iSubType, &subtype);

    //
    // Convert Format to string
    //
    TCHAR szFormat[300];
    UINT  iFormat = 300;
    Format2String(szFormat, iFormat, FormatType(), Format(), FormatLength());

    //
    // Obtain the strings preceeding the Major Type, Sub Type and Format.
    //
    TCHAR szPreMajor[50];
    TCHAR szPreSub[50];
    TCHAR szPreFormat[50];

    LoadString(g_hInst, IDS_PREMAJOR, szPreMajor, 50);
    LoadString(g_hInst, IDS_PRESUB, szPreSub, 50);
    LoadString(g_hInst, IDS_PREFORMAT, szPreFormat, 50);

    _sntprintf(szType, iLen, TEXT("%s%s%s%s%s%s%s%s%s"),
                szPreMajor,  szMajorType, szAfterMajor,
                szPreSub,    szSubType, szAfterOthers,
                szPreFormat, szFormat, szAtEnd);
}

//
// CLSID2String
//
// Find a string description for a given GUID
//
void CTextMediaType::CLSID2String(LPTSTR szBuffer,
                                  UINT iLength,
                                  const GUID* pGuid)
{
    TCHAR *pGuidName = GuidNames[*pGuid];
    UINT strLength = lstrlen(pGuidName) + 1;
    lstrcpyn(szBuffer,pGuidName,min(strLength,iLength));
}

//
// Format2String
//
// Converts a format block to a string
//
void CTextMediaType::Format2String(LPTSTR szBuffer,
                                   UINT iLength,
                                   const GUID* pFormatType,
                                   BYTE* pFormat,
                                   ULONG lFormatLength)
{
    UNREFERENCED_PARAMETER(lFormatLength);

    //
    // Get the name of the format
    //
    TCHAR szName[50];
    UINT iName = 50;
    CLSID2String(szName, iName, pFormatType);

    //
    // Video Format
    //
    if (IsEqualGUID(*pFormatType, FORMAT_VideoInfo) ||
        IsEqualGUID(*pFormatType, FORMAT_MPEGVideo)) {

        VIDEOINFOHEADER * pVideoFormat = (VIDEOINFOHEADER *) pFormat;

        _sntprintf(szBuffer, iLength, TEXT("%4.4hs %dx%d, %d bits")
                   , (pVideoFormat->bmiHeader.biCompression == 0) ? "RGB" :
                         ((pVideoFormat->bmiHeader.biCompression == BI_BITFIELDS) ? "BITF" :
                         (LPTSTR) &pVideoFormat->bmiHeader.biCompression )
                   , pVideoFormat->bmiHeader.biWidth
                   , pVideoFormat->bmiHeader.biHeight
                   , pVideoFormat->bmiHeader.biBitCount);

         return;
    }

    //
    // Audio Format
    //
    if (IsEqualGUID(*pFormatType, FORMAT_WaveFormatEx)) {
        WAVEFORMATEX *pWaveFormat = (WAVEFORMATEX *) pFormat;

        // !!! use ACM to get format type name?
        _sntprintf(szBuffer, iLength, TEXT("%s: %.3f KHz %d bit %s ")
                   , szName
                   , (double) pWaveFormat->nSamplesPerSec / 1000.0
                   , pWaveFormat->wBitsPerSample
                   , pWaveFormat->nChannels == 1 ? TEXT("mono") : TEXT("stereo")
                  );

        return;
    }
    _sntprintf(szBuffer, iLength, TEXT("%s"), szName);
}


TEXTTYPE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// texttype.h
//

// A CMediaType that can return itself as text.

class CTextMediaType : public CMediaType {

public:

    CTextMediaType(AM_MEDIA_TYPE mt):CMediaType(mt) {}

    void AsText(LPTSTR szType,
                unsigned int iLen,
                LPTSTR szAfterMajor,
                LPTSTR szAfterOthers,
                LPTSTR szAtEnd);

private:

    // Provide a string description for this format block

    void Format2String(LPTSTR szBuffer,
                       UINT iLength,
                       const GUID* pGuid,
                       BYTE* pFormat,
                       ULONG lFormatLength);

    // Convert this CLSID into a meaningful string

    void CLSID2String(LPTSTR szBuffer,
                       UINT iLength,
                       const GUID* pGuid);
};


NULLNULL.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//     What This Sample Illustrates
//
// What is the minimum necessary in a filter.
//
//     Summary
//
// A filter which does nothing except pass the data through.
//
//     Demonstration instructions
//
// First build the sample and get it registered (see nullnull.reg)
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop into
// the tool any WAV file or any MPEG, AVI or MOV file.
// A graph will be built to render the file.  From the Graph menu select
// Insert filters and insert NullNull.  (If it is not on the list then you
// failed to register it properly.  If it fails to load then you either
// didn't build it properly or the registration does not correctly point to
// the path where nullnull.ax is now found.)
// In the graph displayed, pick on a connection.  Disconnect it (click on
// the incoming arrow and then press the Delete key).
// Create a connection between the same two filters.  (Drag the mouse along
// the same path as the arrow follwed from tail to head).
// If the nullnull filter is not now included, try it with a different
// connection.  (Connections between source filters and parsers normally
// do not work, others normally do.  Why is another story).
//
//
//     Implementation
//
// Does everything possible with the base classes.  That which cannot be so
// done (e.g. creating this filter) is done here.
// The filter has one input pin, one output pin and does its transform
// in-place (i.e. without copying the data) on the push thread (i.e. it
// is called with a buffer, which it "transforms" and gives to the next
// filter downstream.  It is then blocked until that filter returns.
// It then returns to its own caller).  This filter in fact does the null
// transform it merely passes the data on unchanged.
//
//
//    Known problems ("features NOT illustrated by this sample"):
//
// Almost everything!  This is a truly minimal filter.
//
//
//      Files
//
// nullnull.cpp  The entire implementation
// nullnull.def  Lists exports and imports (part of build process)
// nullnull.rc   Contains some "boiler-plate"
// nullnull.reg  Allows REGEDIT to register this filter.  (Not strictly necessary).
//
//     Base classes used (refer to docs for diagram of what they inherit):
//
// CTransInPlaceFilter

#include <streams.h>     // Active Movie (includes windows.h)
#include <initguid.h>    // declares DEFINE_GUID to declare an EXTERN_C const.

// The CLSID used by the minimal, in-place, null filter
// DO NOT REUSE IT.  Run uuidgen.exe to create a new one.
// {08af6540-4f21-11cf-aacb-0020af0b99a3}
DEFINE_GUID(CLSID_NullNull,
0x08af6540, 0x4f21, 0x11cf, 0xaa, 0xcb, 0x00, 0x20, 0xaf, 0x0b, 0x99, 0xa3);

// setup data - allows the self-registration to work.

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{ &MEDIATYPE_NULL        // clsMajorType
, &MEDIASUBTYPE_NULL };  // clsMinorType

const AMOVIESETUP_PIN psudPins[] =
{ { L"Input"            // strName
  , FALSE               // bRendered
  , FALSE               // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_NULL         // clsConnectsToFilter
  , L""                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes        // lpTypes
  }
, { L"Output"           // strName
  , FALSE               // bRendered
  , TRUE                // bOutput
  , FALSE               // bZero
  , FALSE               // bMany
  , &CLSID_NULL         // clsConnectsToFilter
  , L""                 // strConnectsToPin
  , 1                   // nTypes
  , &sudPinTypes        // lpTypes
  }
};

const AMOVIESETUP_FILTER sudNullNull =
{ &CLSID_NullNull                  // clsID
, L"Minimal Null"                 // strName
, MERIT_DO_NOT_USE                // dwMerit
, 2                               // nPins
, psudPins };                     // lpPin

// CNullNull
//
class CNullNull
    : public CTransInPlaceFilter
{

public:

    static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);

    DECLARE_IUNKNOWN;

private:

    // Constructor - just calls the base class constructor
    CNullNull(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
        : CTransInPlaceFilter (tszName, punk, CLSID_NullNull, phr)
    { }

    // Overrides the PURE virtual Transform of CTransInPlaceFilter base class
    // This is where the "real work" is done by altering *pSample.
    // We do the Null transform by leaving it alone.
    HRESULT Transform(IMediaSample *pSample){ return NOERROR; }

    // We accept any input type.  We'd return S_FALSE for any we didn't like.
    HRESULT CheckInputType(const CMediaType* mtIn) { return S_OK; }
};

// Needed for the CreateInstance mechanism
CFactoryTemplate g_Templates[]=
    {   { L"Minimal Null"
        , &CLSID_NullNull
        , CNullNull::CreateInstance
        , NULL
        , &sudNullNull }
    };
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

//
// CreateInstance
//
// Provide the way for COM to create a CNullNull object
CUnknown * WINAPI CNullNull::CreateInstance(LPUNKNOWN punk, HRESULT *phr) {

    CNullNull *pNewObject = new CNullNull(NAME("Minimal, in-place, null filter"), punk, phr );
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return pNewObject;
} // CreateInstance

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}

// Microsoft C Compiler will give hundreds of warnings about
// unused inline functions in header files.  Try to disable them.
#pragma warning( disable:4514)


EXT_ENUM.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Ext_Enum.h
//
//
// Contents:
//

#ifndef _Ext_Enum_h
#define _Ext_Enum_h

#pragma warning( disable : 4514 )

// A helper template & macro so that we only release non-null interface
// pointers and we set the pointer to null afterwards so that we don't
// release it again.
template<class Interface> void SafeRelPtrIPtr( Interface * * ppIUnk )
{
    if ( *ppIUnk )
    {
        (*ppIUnk)->Release();
        (*ppIUnk) = 0;
    }
}

#define SafeRelIPtr( pI ) SafeRelPtrIPtr( &(pI) )

// A template function for cloning.  We always clone an Implementation, but
// supply a pointer to an Interface.  We need both to parameterize this function.
template<class Implementation, class Interface>
static inline HRESULT CloneAny( const Implementation & _this, Interface ** ppI)
{
    HRESULT hr;

    if (ppI)
    {
        *ppI = 0;
        // Relies on Implementation having defined this kind of "copy" constructor.
        Implementation *const p = new Implementation( _this, &hr );
        if (p)
        {
            if SUCCEEDED(hr)    *ppI = p;
            else                delete p;
        }
        else hr = E_OUTOFMEMORY;
    }
    else hr = E_POINTER;
    return hr;
}

// AnyEnum presents a simple concrete class that can cover any enumerator that conforms to
// the standard COM enumerator interface.  You'll probably never instantiate one of these,
// but it gives us a good foundation from which to build derived enumerators with special
// behaviour.

template<class IEnum, class BaseType> class AnyEnum : public IEnum
{
protected:
long    m_cRef;                             // Local ref count
IEnum * m_pIEnum;                           // Pointer to a "real" enumarator that will do most of the work

// Protected default constructor, derived classes relying on this must set m_pIEnum themselves.
    AnyEnum()                                   : m_cRef(1), m_pIEnum(0)  {}

public:
    virtual ~AnyEnum()                          { SafeRelIPtr( m_pIEnum ); }
    AnyEnum( const AnyEnum & copy, HRESULT * phr );

    // IUnknown interface
STDMETHODIMP            QueryInterface( REFIID iid, void ** ppv );
STDMETHODIMP_(ULONG)    AddRef();
STDMETHODIMP_(ULONG)    Release();

    // IEnum interface
    STDMETHODIMP            Next( ULONG cBase, BaseType ** ppBase, ULONG * pcFetched );
    STDMETHODIMP            Skip( ULONG cBase );
STDMETHODIMP            Reset( void );
STDMETHODIMP            Clone( IEnum ** ppEnum );
};

// ConstrictedEnum
//
// An abstract template class extending AnyEnum by adding a Select() method.
// Derived classes must implement this method.  For each object that our inner
// enumerator passes back to us, we ask Select() if it meets its selection
// criteria, if it does, we pass the object back to our caller, otherwise we
// ask our inner enumerator for another candidate.
//
// Next() is enhanced to call Select() for each candidate.  Skip() is re-implemented
// since its skip count must now be in terms of objects that meet Select()'s criteria.
// A new Skip() is implemented where we can explicity get a count of the number of
// candidates skipped.

template<class IEnum, class BaseType> class ConstrictedEnum : public AnyEnum<IEnum,BaseType>
{
protected:
    ConstrictedEnum()                       {}

    // Pure virtual Selector method.
virtual BOOL Select( BaseType ** ppBase ) =0;

public:
    ConstrictedEnum( const ConstrictedEnum & copy, HRESULT * phr )
    : AnyEnum<IEnum,BaseType>( copy, phr )  {}

    STDMETHODIMP            Next( ULONG cBase, BaseType ** ppBase, ULONG * pcFetched );
STDMETHODIMP            Skip( ULONG cBase );
STDMETHODIMP            Skip( ULONG cBase, ULONG * pcCount );
};

// CImpIEnumFilters
//
// This class not only restricts ennumeration to IBaseFilters that support a specific interface,
// it also returns pointers to THAT interface, rather than just IBaseFilter pointers.

class CImpIEnumFilters : public ConstrictedEnum<IEnumFilters, IBaseFilter>
{
private:
REFIIDm_iid;

protected:
CImpIEnumFilters( REFIID iid )                  : m_iid(iid)                {}
    BOOLSelect( IBaseFilter ** ppIFilter );

public:
    CImpIEnumFilters( const CImpIEnumFilters & copy, HRESULT * phr )
    : ConstrictedEnum<IEnumFilters,IBaseFilter>( copy, phr ), m_iid( copy.m_iid )   {}

    CImpIEnumFilters( IFilterGraph * pIFilterGraph, REFIID iid )
: m_iid(iid)
{
HRESULT hr = pIFilterGraph->EnumFilters( &m_pIEnum );
ASSERT( SUCCEEDED(hr) );
}

STDMETHODIMP Clone( IEnumFilters  ** ppEnum );
};

// CImpIEnumUnconnectedFilters
//
// Enumerates filters over a filter graph, but only returns those who have none of their pins connected

class CImpIEnumUnconnectedFilters : public ConstrictedEnum<IEnumFilters, IBaseFilter>
{
protected:
    BOOLSelect( IBaseFilter ** ppIFilter );

public:
    CImpIEnumUnconnectedFilters( const CImpIEnumUnconnectedFilters & copy, HRESULT * phr )
    : ConstrictedEnum<IEnumFilters,IBaseFilter>( copy, phr ) {}

    CImpIEnumUnconnectedFilters( IFilterGraph * pIFilterGraph )
{
HRESULT hr = pIFilterGraph->EnumFilters( &m_pIEnum );
ASSERT( SUCCEEDED(hr) );
}

    STDMETHODIMP Clone( IEnumFilters  ** ppEnum );
};

template<PIN_DIRECTION DIR> class CImpIEnumPins : public ConstrictedEnum<IEnumPins, IPin>
{
protected:
CImpIEnumPins()                                                 {}
    BOOLSelect( IPin ** ppIPin );

public:
CImpIEnumPins( const CImpIEnumPins & copy, HRESULT * phr )
    : ConstrictedEnum<IEnumPins, IPin>( copy, phr )                 {}

    CImpIEnumPins( IBaseFilter * pIFilter )
{
HRESULT hr = pIFilter->EnumPins( &m_pIEnum );
ASSERT ( SUCCEEDED(hr) );
}

STDMETHODIMP Clone( IEnumPins  ** ppEnum );
};

typedef CImpIEnumPins<PINDIR_INPUT>     CImpIEnumPinsIn;
typedef CImpIEnumPins<PINDIR_OUTPUT>    CImpIEnumPinsOut;
typedef AnyEnum<IEnumPins, IPin>        CImpIEnumPinsBoth;

template<class EnumPinClass> class CImpIEnumOverGraph : public EnumPinClass
{
private:
CImpIEnumOverGraph()            {}
protected:
    HRESULT         m_hr;
IEnumFilters* m_pIEnumFilters;
    CCritSec        m_crit_sec;

    HRESULT NextFilter();           // Iterates us through the filters

public:
~CImpIEnumOverGraph()           { SafeRelIPtr(m_pIEnumFilters); }

    CImpIEnumOverGraph( const CImpIEnumOverGraph & copy, HRESULT * phr )
    : EnumPinClass( copy, phr )
    {
        if SUCCEEDED(*phr)
        {
            *phr = copy.m_pIEnumFilters->Clone( &m_pIEnumFilters );
            m_hr = SUCCEEDED(*phr) ? copy.m_hr : *phr;
        }
        else m_pIEnumFilters = 0;
    }

    CImpIEnumOverGraph( IFilterGraph * pIFilterGraph )
{
HRESULT hr;
hr = pIFilterGraph->EnumFilters( &m_pIEnumFilters );
ASSERT( SUCCEEDED(hr) );
hr = NextFilter();
ASSERT( SUCCEEDED(hr) );
}

STDMETHODIMP Next( ULONG cBase, IPin ** ppBase, ULONG * pcFetched );
STDMETHODIMP Reset( void );
STDMETHODIMP Clone( IEnumPins ** ppEnum );
};

typedef CImpIEnumOverGraph<CImpIEnumPinsIn>     CImpIEnumPinsInOverGraph;
typedef CImpIEnumOverGraph<CImpIEnumPinsOut>    CImpIEnumPinsOutOverGraph;
typedef CImpIEnumOverGraph<CImpIEnumPinsBoth>   CImpIEnumPinsOverGraph;

#include "Ext_Enum.inl"

#endif


IAMOVIE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include "Ext_Enum.h"

class CAMovie : public IAMovie, public CUnknown
{
private:
    IGraphBuilder   *   pGB;
    IMediaControl   *   pMC;
    IMediaEvent     *   pME;
    IMediaPosition  *   pMP;

protected:
    ~CAMovie();
    CAMovie(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr);

    // Disconnect all pins.  If a filter has no pins, remove it completely
    // This is to part-destroy a filter graph, leaving only those filters
    // that may be usefult for rendering a file or pin after this function
    // has been called.
    HRESULT DisconnectAllAndRemoveIsolates();

    // For all filters in graph, if the filter has pins, and all are unconnected,
    // then remove the filter from the graph.
    HRESULT RemoveAllUnconnected();

public:
    DECLARE_IUNKNOWN

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);

    STDMETHODIMP NonDelegatingQueryInterface(REFIID iid, void ** ppv);

    // IFilterGraph

    // Add a filter to the graph and name it with *pName.
    // If the name is not unique, The request will fail.
    // The Filter graph will call the JoinFilterGraph
    // member function of the filter to inform it.
    // This must be called before attempting Connect, ConnectDirect or Render
    // for pins of the filter.

    STDMETHODIMP AddFilter
        ( IBaseFilter * pFilter,
          LPCWSTR pName
        );

    // Remove a filter from the graph. The filter graph implementation
    // will inform the filter that it is being removed.

    STDMETHODIMP RemoveFilter
        ( IBaseFilter * pFilter
        );

    // Set *ppEnum to be an enumerator for all filters in the graph.

    STDMETHODIMP EnumFilters
        ( IEnumFilters **ppEnum
        );

    // Set *ppFilter to be the filter which was added with the name *pName
    // Will fail and set *ppFilter to NULL if the name is not in this graph.

    STDMETHODIMP FindFilterByName
        ( LPCWSTR pName,
          IBaseFilter ** ppFilter
        );

    //==========================================================================
    // Low level connection functions
    //==========================================================================

    // Connect these two pins directly (i.e. without intervening filters)
    // the media type is optional, and may be partially specified (that is
    // the subtype and/or format type may be GUID_NULL). See IPin::Connect
    // for details of the media type parameter.
    STDMETHODIMP ConnectDirect
        ( IPin * ppinOut,              // the output pin
          IPin * ppinIn,               // the input pin
          const AM_MEDIA_TYPE* pmt     // optional mediatype
        );

    // On a separate thread (which will not hold any relevant locks)
    // Break the connection that this pin has and reconnect it to the
    // same other pin.

    STDMETHODIMP Reconnect
        ( IPin * ppin        // the pin to disconnect and reconnect
        );

    // Disconnect this pin, if connected.  Successful no-op if not connected.

    STDMETHODIMP Disconnect
        ( IPin * ppin
        );

    //==========================================================================
    // intelligent connectivity - now in IGraphBuilder, axextend.idl
    //==========================================================================

    //==========================================================================
    // Whole graph functions
    //==========================================================================

    // Once a graph is built, it can behave as a (composite) filter.
    // To control this filter, QueryInterface for IMediaFilter.

    // The filtergraph will by default ensure that the graph has a sync source
    // when it is made to Run.  SetSyncSource(NULL) will prevent that and allow
    // all the filters to run unsynchronised until further notice.
    // SetDefaultSyncSource will set the default sync source (the same as would
    // have been set by default on the first call to Run).
    STDMETHODIMP SetDefaultSyncSource(void);

    // IGraphBuilder

    // Connect these two pins directly or indirectly, using transform filters
    // if necessary.

    STDMETHODIMP Connect
    ( IPin * ppinOut,    // the output pin
      IPin * ppinIn      // the input pin
    );

    // Connect this output pin directly or indirectly, using transform filters
    // if necessary to something that will render it.

    STDMETHODIMP Render
    ( IPin * ppinOut     // the output pin
    );

    // This pair of methods are a cross between the IGraphBuilder ones and the IMediaControl ones,
    // unfortunate perhaps, but they do make the nicest C / C++ interfaces .

    // adds and connects filters needed to play the specified file
    STDMETHODIMP RenderFile( LPCWSTR strFilename );

    // adds to the graph the source filter that can read this file,
    // and returns an IFilterInfo object for it
    STDMETHODIMP AddSourceFilter( LPCWSTR strFilename, IBaseFilter ** ppUnk);

    // IMediaControl methods
    STDMETHODIMP Run();
    STDMETHODIMP Pause();
    STDMETHODIMP Stop();

    //returns the state. same semantics as IMediaFilter::GetState
    STDMETHODIMP GetState(
                LONG msTimeout,
                FILTER_STATE* pfs);

    // IMediaEvent

    // get back the event handle. This is manual-reset
    // (don't - it's reset by the event mechanism) and remains set
    // when events are queued, and reset when the queue is empty.
    STDMETHODIMP GetEventHandle(
                    HEVENT * hEvent);

    // remove the next event notification from the head of the queue and
    // return it. Waits up to msTimeout millisecs if there are no events.
    // if a timeout occurs without any events, this method will return
    // E_ABORT, and the value of the event code and other parameters
    // is undefined.
    STDMETHODIMP GetEvent(
                    long * lEventCode,
                    long * lParam1,
                    long * lParam2,
                    long msTimeout
                    );
    // Free any resources associated with the parameters to an event.
    // Event parameters may be LONGs, IUnknown* or BSTR. No action
    // is taken with LONGs. IUnknown are passed addrefed and need a
    // Release call. BSTR are allocated by the task allocator and will be
    // freed by calling the task allocator.
    STDMETHODIMP FreeEventParams(
    long lEvCode,
    long lParam1,
    long lParam2);

    // Calls GetEvent repeatedly discarding events until it finds a
    // completion event (EC_COMPLETE, EC_ERRORABORT, or EC_USERABORT).
    // The completion event is removed from the queue and returned
    // in pEvCode. Note that the object is still in running mode until
    // a Pause or Stop call is made.
    // If the timeout occurs, *pEvCode will be 0 and E_ABORT will be
    // returned.
    STDMETHODIMP WaitForCompletion(
                    long msTimeout,
                    long * pEvCode);

    // cancels any system handling of the specified event code
    // and ensures that the events are passed straight to the application
    // (via GetEvent) and not handled. A good example of this is
    // EC_REPAINT: default handling for this ensures the painting of the
    // window and does not get posted to the app.
    STDMETHODIMP CancelDefaultHandling(
                    long lEvCode);

    // restore the normal system default handling that may have been
    // cancelled by CancelDefaultHandling().
    STDMETHODIMP RestoreDefaultHandling( long lEvCode);

    // IMediaPosition
    // properties
    STDMETHODIMP get_Duration(
                REFTIME* plength);

    STDMETHODIMP put_CurrentPosition(
                REFTIME llTime);

    STDMETHODIMP get_CurrentPosition(
                REFTIME* pllTime);

    STDMETHODIMP get_StopTime(
                REFTIME* pllTime);

    STDMETHODIMP put_StopTime(
                REFTIME llTime);

    STDMETHODIMP get_PrerollTime(
                REFTIME* pllTime);
    STDMETHODIMP put_PrerollTime(
                REFTIME llTime);

    STDMETHODIMP put_Rate(
                double dRate);
    STDMETHODIMP get_Rate(
                double * pdRate);

/* New methods */

    // Remove all filters
    STDMETHODIMP  RemoveAllFilters();

    // If the filter graph can be Run to completion, run it and wait for it to complete
STDMETHODIMP  Play();
    // As above, but RenderNewFile first
    STDMETHODIMP  PlayFile( LPCWSTR strFilename );

    // Render all unconnected output pins
    STDMETHODIMP  RenderAll();
    // Render supplied files "as if" the filter graoh were first emptied
    STDMETHODIMP  RenderNewFile( LPCWSTR strFilename );

    STDMETHODIMP  EnumFiltersByInterface( REFIID iid, IEnumFilters ** pEnumFilters );
    // Enum pins over the entire graph
    STDMETHODIMP  EnumPins( IEnumPins ** ppEnum );
    STDMETHODIMP  EnumPinsIn( IEnumPins ** ppEnum );
    STDMETHODIMP  EnumPinsOut( IEnumPins ** ppEnum );
};

CFactoryTemplate g_Templates[]=
{   {L"ActiveMovie Sample PID", &CLSID_AMovie,   CAMovie::CreateInstance},
};
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

CAMovie::~CAMovie()
{ /* nothing to do */ }

CAMovie::CAMovie(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr)
: CUnknown( pName, pUnk )
, pGB(0), pMC(0), pME(0), pMP(0)
{
    if (!pUnk) *phr = VFW_E_NEED_OWNER;
    else if SUCCEEDED(*phr)
    {
        *phr = pUnk->QueryInterface( IID_IGraphBuilder, reinterpret_cast<void**>(&pGB) );
        if SUCCEEDED(*phr)
        {
            pGB->Release();
            *phr = pUnk->QueryInterface( IID_IMediaControl, reinterpret_cast<void**>(&pMC) );
            if SUCCEEDED(*phr)
            {
                pMC->Release();
                *phr = pUnk->QueryInterface( IID_IMediaEvent, reinterpret_cast<void**>(&pME) );
                if SUCCEEDED(*phr)
                {
                    pME->Release();
                    *phr = pUnk->QueryInterface( IID_IMediaPosition, reinterpret_cast<void**>(&pMP) );
                    if SUCCEEDED(*phr) pMP->Release();
                }
            }
        }
    }
}

CUnknown * WINAPI CAMovie::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    CUnknown * result = 0;
    result = new CAMovie( NAME("IAMovie Class"), pUnk, phr );
    if ( !result ) *phr = E_OUTOFMEMORY;
    return result;
}

STDMETHODIMP CAMovie::NonDelegatingQueryInterface(REFIID iid, void ** ppv)
{
    CheckPointer(ppv,E_POINTER);

    if ( iid == IID_IAMovie )
    {
        return GetInterface(static_cast<IAMovie *>(this), ppv );
    }
    else
    {
return CUnknown::NonDelegatingQueryInterface(iid, ppv);
    }
}

// IFilterGraph (pGB)
STDMETHODIMP CAMovie::AddFilter( IBaseFilter * pFilter, LPCWSTR pName )
{ return pGB->AddFilter( pFilter, pName ); }
STDMETHODIMP CAMovie::RemoveFilter( IBaseFilter * pFilter )
{ return pGB->RemoveFilter( pFilter ); }
STDMETHODIMP CAMovie::EnumFilters( IEnumFilters **ppEnum )
{ return pGB->EnumFilters( ppEnum ); }
STDMETHODIMP CAMovie::FindFilterByName( LPCWSTR pName, IBaseFilter ** ppFilter )
{ return pGB->FindFilterByName( pName, ppFilter ); }
STDMETHODIMP CAMovie::ConnectDirect( IPin * ppinOut, IPin * ppinIn, const AM_MEDIA_TYPE* pmt )
{ return pGB->ConnectDirect( ppinOut, ppinIn, pmt ); }
STDMETHODIMP CAMovie::Reconnect( IPin * ppin )
{ return pGB->Reconnect( ppin ); }
STDMETHODIMP CAMovie::Disconnect( IPin * ppin )
{ return pGB->Disconnect( ppin ); }
STDMETHODIMP CAMovie::SetDefaultSyncSource( )
{ return pGB->SetDefaultSyncSource( ); }

// IGraphBuilder (pGB)
STDMETHODIMP CAMovie::Connect( IPin * ppinOut, IPin * ppinIn )
{ return pGB->Connect( ppinOut, ppinIn ); }
STDMETHODIMP CAMovie::Render( IPin * ppinOut )
{ return pGB->Render( ppinOut ); }

// This pair of methods are a cross between the IGraphBuilder ones and the IMediaControl ones,
// unfortunate perhaps, but they do make the nicest C / C++ interfaces .
STDMETHODIMP CAMovie::AddSourceFilter( LPCWSTR strFilename, IBaseFilter ** ppUnk )
{
    const LPWSTR fn = const_cast<LPWSTR>(strFilename);
    return pGB->AddSourceFilter( fn, fn, ppUnk );
}

STDMETHODIMP CAMovie::RenderFile( LPCWSTR strFilename )
{
    const LPWSTR fn = const_cast<LPWSTR>(strFilename);
    return pGB->RenderFile( fn, fn );
}

// IMediaControl (pMC)
STDMETHODIMP CAMovie::Run( )
{ return pMC->Run( ); }
STDMETHODIMP CAMovie::Pause( )
{ return pMC->Pause( ); }
STDMETHODIMP CAMovie::Stop( )
{ return pMC->Stop( ); }
STDMETHODIMP CAMovie::GetState( LONG msTimeout, FILTER_STATE* pfs )
{ return pMC->GetState( msTimeout, (long *)pfs ); }

// IMediaEvent (pME)
STDMETHODIMP CAMovie::GetEventHandle( HEVENT * hEvent )
{ return pME->GetEventHandle( (long *)hEvent ); }
STDMETHODIMP CAMovie::GetEvent( long * lEventCode, long * lParam1, long * lParam2, long msTimeout )
{ return pME->GetEvent( lEventCode, lParam1, lParam2, msTimeout ); }
STDMETHODIMP CAMovie::FreeEventParams( long lEventCode, long lParam1, long lParam2 )
{ return pME->FreeEventParams( lEventCode, lParam1, lParam2 ); }
STDMETHODIMP CAMovie::WaitForCompletion( long msTimeout, long * pEvCode )
{ return pME->WaitForCompletion( msTimeout, pEvCode ); }
STDMETHODIMP CAMovie::CancelDefaultHandling( long lEvCode )
{ return pME->CancelDefaultHandling( lEvCode ); }
STDMETHODIMP CAMovie::RestoreDefaultHandling( long lEvCode )
{ return pME->RestoreDefaultHandling( lEvCode ); }

// IMediaPosition (pMP )
STDMETHODIMP CAMovie::get_Duration( REFTIME* plength )
{ return pMP->get_Duration( plength ); }
STDMETHODIMP CAMovie::put_CurrentPosition( REFTIME llTime )
{ return pMP->put_CurrentPosition( llTime ); }
STDMETHODIMP CAMovie::get_CurrentPosition( REFTIME* pllTime )
{ return pMP->get_CurrentPosition( pllTime ); }
STDMETHODIMP CAMovie::get_StopTime( REFTIME* pllTime )
{ return pMP->get_StopTime( pllTime ); }
STDMETHODIMP CAMovie::put_StopTime( REFTIME llTime )
{ return pMP->put_StopTime( llTime ); }
STDMETHODIMP CAMovie::get_PrerollTime( REFTIME* pllTime )
{ return pMP->get_PrerollTime( pllTime ); }
STDMETHODIMP CAMovie::put_PrerollTime( REFTIME llTime )
{ return pMP->put_PrerollTime( llTime ); }
STDMETHODIMP CAMovie::put_Rate( double dRate )
{ return pMP->put_Rate( dRate ); }
STDMETHODIMP CAMovie::get_Rate( double * pdRate )
{ return pMP->get_Rate( pdRate ); }

// New methods

// Remove all filters
STDMETHODIMP CAMovie::RemoveAllFilters()
{
    HRESULT  hr;

    IEnumFilters * piEnumFilters;
    hr = EnumFilters( &piEnumFilters );
    if SUCCEEDED(hr)
    {
        CGenericList<IBaseFilter> FilterList("List of filters to be removed");

        IBaseFilter * piFilter;
        while ( piEnumFilters->Next(1, &piFilter, 0) == NOERROR )
        {
            FilterList.AddTail( piFilter );
        }
        piEnumFilters->Release();
        while ( piFilter = FilterList.RemoveHead() )
        {
            hr = RemoveFilter( piFilter );
            piFilter->Release();
        }
    }
    return hr;
}

// If the filter graph can be Run to completion, run it and wait for it to complete
STDMETHODIMP  CAMovie::Play()
{
    HRESULT hr;
    REFTIME duration;
    hr = get_Duration( &duration );
    if ( SUCCEEDED( hr ) && duration > 0 )
    {
        hr = Run();
        if SUCCEEDED(hr)
        {
            long evCode;
            WaitForCompletion( INFINITE, &evCode );
        }
        Stop();
    }
    return hr;
}

STDMETHODIMP  CAMovie::PlayFile( LPCWSTR strFilename )
{
    HRESULT hr;
    hr = RenderNewFile( strFilename );
    if SUCCEEDED(hr)
    {
        hr = Play();
    }
    return hr;
}

STDMETHODIMP CAMovie::RenderAll( )
{
    HRESULT hr = NOERROR;
    CImpIEnumPinsOutOverGraph EnumOutPins( this, &hr );
    if SUCCEEDED(hr)
    {
        IPin * piPin;

        while ( EnumOutPins.Next( 1, &piPin, 0 ) == NOERROR )
        {
            Render( piPin );
        }
    }
    return hr;
}

STDMETHODIMP CAMovie::RenderNewFile( LPCWSTR strFilename )
{
    HRESULT hr;
    DisconnectAllAndRemoveIsolates();
    hr = RenderFile( strFilename );
    RemoveAllUnconnected();
    return hr;
}

STDMETHODIMP CAMovie::EnumFiltersByInterface( REFIID iid, IEnumFilters ** ppEnum )
{
    HRESULT hr = NOERROR;
    *ppEnum = new CImpIEnumFilters( pGB, iid );
    if (!*ppEnum) hr = E_OUTOFMEMORY;
    else if FAILED(hr)
    {
        delete *ppEnum;
        *ppEnum = 0;
    }
    return hr;
}

STDMETHODIMP CAMovie::EnumPins( IEnumPins ** ppEnum )
{
    HRESULT hr = NOERROR;
    *ppEnum = new CImpIEnumPinsOverGraph( pGB, &hr );
    if (!*ppEnum) hr = E_OUTOFMEMORY;
    else if FAILED(hr)
    {
        delete *ppEnum;
        *ppEnum = 0;
    }
    return hr;
}

STDMETHODIMP CAMovie::EnumPinsIn( IEnumPins ** ppEnum )
{
    HRESULT hr = NOERROR;
    *ppEnum = new CImpIEnumPinsInOverGraph( pGB, &hr );
    if (!*ppEnum) hr = E_OUTOFMEMORY;
    else if FAILED(hr)
    {
        delete *ppEnum;
        *ppEnum = 0;
    }
    return hr;
}

STDMETHODIMP CAMovie::EnumPinsOut( IEnumPins ** ppEnum )
{
    HRESULT hr = NOERROR;
    *ppEnum = new CImpIEnumPinsOutOverGraph( pGB, &hr );
    if (!*ppEnum) hr = E_OUTOFMEMORY;
    else if FAILED(hr)
    {
        delete *ppEnum;
        *ppEnum = 0;
    }
    return hr;
}

// Protected methods

// #include <iostream.h>
// #include <SampIOS.h>

// Disconnect all pins.  If a filter has no pins, remove it completely
// This is to part-destroy a filter graph, leaving only those filters
// that may be usefult for rendering a file or pin after this function
// has been called.
HRESULT CAMovie::DisconnectAllAndRemoveIsolates()
{
    // cout << "Pre DisconnectAllAndRemoveIsolates()\n" << pGB << endl;
    HRESULT  hr;

    IEnumFilters * piEnumFilters;
    hr = EnumFilters( &piEnumFilters );
    if SUCCEEDED(hr)
    {
        CGenericList<IPin>        PinList("List of pins to be disconnected");
        CGenericList<IBaseFilter> FilterList("List of filters to be removed");
        IBaseFilter * piFilter;
        IPin * piPin;
        while ( piEnumFilters->Next(1, &piFilter, 0) == NOERROR )
        {
            BOOL connected = FALSE, connectable = FALSE;
            IEnumPins * piEnumPins;
            hr = piFilter->EnumPins( &piEnumPins );
            if SUCCEEDED(hr)
            {
                while ( piEnumPins->Next(1, &piPin, 0) == NOERROR )
                {
                    connectable = TRUE;
                    IPin * piPinConnectedTo;
                    piPin->ConnectedTo( &piPinConnectedTo );
                    if (piPinConnectedTo)
                    {
                        PinList.AddTail( piPin );
                        piPinConnectedTo->Release();
                    }
                    else piPin->Release();
                }
                piEnumPins->Release();
            }
            if (!connectable)   FilterList.AddTail( piFilter );
            else                piFilter->Release();
        }
        piEnumFilters->Release();
        while ( piPin = PinList.RemoveHead() )
        {
            hr = Disconnect( piPin );
            piPin->Release();
        }
        while ( piFilter = FilterList.RemoveHead() )
        {
            hr = RemoveFilter( piFilter );
            piFilter->Release();
        }
    }
    // cout << "Post DisconnectAllAndRemoveIsolates()\n" << pGB << endl;
    return hr;
}

// For all filters in graph, if the filter has pins, and all are unconnected,
// then remove the filter from the graph.
HRESULT CAMovie::RemoveAllUnconnected()
{
    // cout << "Pre RemoveAllUnconnected()\n" << pGB << endl;
    HRESULT  hr;

    IEnumFilters * piEnumFilters;
    hr = EnumFilters( &piEnumFilters );
    if SUCCEEDED(hr)
    {
        CGenericList<IBaseFilter> FilterList("List of filters to be removed");
        IBaseFilter * piFilter;
        while ( piEnumFilters->Next(1, &piFilter, 0) == NOERROR )
        {
            BOOL connected = FALSE, connectable = FALSE;
            IEnumPins * piEnumPins;
            hr = piFilter->EnumPins( &piEnumPins );
            if SUCCEEDED(hr)
            {
                IPin * piPin;
                while ( !connected && piEnumPins->Next(1, &piPin, 0) == NOERROR )
                {
                    connectable = TRUE;
                    IPin * piPinConnectedTo;
                    piPin->ConnectedTo( &piPinConnectedTo );
                    if (piPinConnectedTo)
                    {
                        connected = TRUE;
                        piPinConnectedTo->Release();
                    }
                    piPin->Release();
                }
                piEnumPins->Release();
            }
            if (connectable && !connected)  FilterList.AddTail( piFilter );
            else                            piFilter->Release();
        }
        piEnumFilters->Release();
        while ( piFilter = FilterList.RemoveHead() )
        {
            hr = RemoveFilter( piFilter );
            piFilter->Release();
        }
    }
    // cout << "Post RemoveAllUnconnected()\n" << pGB << endl;
    return hr;
}

//---------------------------------------------------------------------------
//
//  AMovieRegisterInterface()
//
//
//
//---------------------------------------------------------------------------

HRESULT
AMovieRegisterInterface( CLSID  ClsidInterface
                       , LPTSTR strName
                       , CLSID  ClsidDistributor )
{
  TCHAR   achKey[MAX_PATH];
  TCHAR   achClsidInterface[CHARS_IN_GUID];
  TCHAR   achClsidDistributor[CHARS_IN_GUID];
  OLECHAR achTemp[CHARS_IN_GUID];
  HKEY    hkey, hsubkey;
  LONG    lReturn;
  HRESULT hr;

  hr = StringFromGUID2( ClsidInterface
                      , achTemp
                      , CHARS_IN_GUID );
  if( SUCCEEDED(hr) )
  {
    wsprintf( achClsidInterface, "%ls", achTemp );

    hr = StringFromGUID2( ClsidDistributor
                        , achTemp
                        , CHARS_IN_GUID );
  }

  if( FAILED(hr) ) return hr;

  wsprintf( achClsidDistributor, "%ls", achTemp );

  wsprintf( achKey, "Interface\\%s", achClsidInterface );
  lReturn = RegCreateKey( HKEY_CLASSES_ROOT
                        , achKey
                        , &hkey );
  if( ERROR_SUCCESS == lReturn )
  {
    if( NULL != strName )
    {
      RegSetValue( hkey
                 , NULL
                 , REG_SZ
                 , strName
                 , lstrlen(strName)+1 );
    }

    lReturn = RegCreateKey( hkey
                          , "Distributor"
                          , &hsubkey );
    if( ERROR_SUCCESS == lReturn )
    {

      RegSetValue( hsubkey
                 , NULL
                 , REG_SZ
                 , achClsidDistributor
                 , lstrlen(achClsidDistributor)+1 );

      RegCloseKey( hsubkey );
    }

    RegCloseKey( hkey );
  }

  return HRESULT_FROM_WIN32(lReturn);

}

//---------------------------------------------------------------------------
//
// AMovieUnregisterInterface()
//
//
//
//---------------------------------------------------------------------------

HRESULT
AMovieUnregisterInterface( REFCLSID ClsidInterface )
{
  HKEY    hkey;
  LONG    lReturn;
  HRESULT hr;
  TCHAR   achClsidInterface[CHARS_IN_GUID];
  OLECHAR achTemp[CHARS_IN_GUID];

  // comvert CLSID to OLE string
  //
  hr  = StringFromGUID2( ClsidInterface, achTemp, CHARS_IN_GUID );
  if( FAILED(hr) ) return hr;

  // convert OLE string to ANSI (as that's
  // all that WIN95 Reg* calls can handle
  //
  wsprintf( achClsidInterface, "%ls", achTemp );

  // open Interface key
  //
  lReturn = RegOpenKeyEx( HKEY_CLASSES_ROOT
                        , TEXT("Interface")
                        , 0
                        , MAXIMUM_ALLOWED
                        , &hkey );
  if( ERROR_SUCCESS == lReturn )
  {
    // if successful, eliminate subkey and close
    // Interface key
    //
    EliminateSubKey( hkey, achClsidInterface );
    RegCloseKey( hkey );
  }

  return HRESULT_FROM_WIN32( lReturn );
}

//
// exported entry points for registration and
// unregistration.
//
// In this case they (un)register out one and
// only interface and then call through to the
// default implementation.
//
// UUID Interface   {359ace10-7688-11cf-8b23-00805f6cef60}
// Interface name   IAMovie
// UUID Distributor {5f2759c0-7685-11cf-8b23-00805f6cef60}
//
STDAPI DllRegisterServer()
{
  HRESULT hr = NOERROR;

  // Register interface so ActiveMovie can
  // find me from my IID
  //
  hr = AMovieRegisterInterface( IID_IAMovie
                              , TEXT("IAMovie")
                              , CLSID_AMovie  );

  if( SUCCEEDED(hr) )
    hr = AMovieDllRegisterServer2( TRUE );

  return hr;
}

STDAPI DllUnregisterServer()
{
  HRESULT hr = NOERROR;

  hr = AMovieUnregisterInterface( IID_IAMovie );

  if( SUCCEEDED(hr) )
    hr = AMovieDllRegisterServer2( FALSE );

  return hr;
}


README.TXT

Player - Sample ActiveMovie applications
========================================

mfcplay
    Programmed using C++ and the Microsoft Foundation Classes

cplay
    Programmed using plain vanilla C


ABOUT.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Simply handles the Help..About dialog box

#include "stdwin.h"
#include "about.h"
#include "resource.h"

//
// AboutDlgProc
//
BOOL FAR PASCAL AboutDlgProc( HWND hwnd, UINT message, UINT wParam, LONG lParam )
{
    switch( message )
    {
        case WM_INITDIALOG:
           return (LRESULT) 1;

        case WM_COMMAND:
            if( wParam==IDOK || wParam==IDCANCEL ){
                EndDialog( hwnd, 0);
                return (LRESULT) 1;
            }
    }
    return (LRESULT) 0;

} // AboutDlgProc

//
// DoAboutDialog
//
void DoAboutDialog( HINSTANCE hInstance, HANDLE hwnd )
{
    DialogBox( hInstance, MAKEINTRESOURCE( IDD_ABOUTBOX ), hwnd, AboutDlgProc );

} // DoAboutDialog


ABOUT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

void DoAboutDialog( HINSTANCE hInstance, HANDLE hwnd );


ASSERT.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include "stdwin.h"

//
// DbgAssert
//
// Displays a message box if the condition evaluated to FALSE
//
void DbgAssert(const char *pCondition, const char *pFileName, int iLine)
{
    int MsgId;
    char szInfo[1024];

    wsprintf(szInfo, TEXT("%s \nAt line %d of %s"),pCondition, iLine, pFileName);
    MsgId = MessageBox(NULL, szInfo, TEXT("ASSERT Failed"),
                           MB_SYSTEMMODAL |
                           MB_ICONHAND |
                           MB_ABORTRETRYIGNORE);
    switch (MsgId)
    {
        case IDABORT:           // Kill the application

            FatalAppExit(FALSE, TEXT("Application terminated"));
            break;

        case IDRETRY:           // Break into the debugger
            DebugBreak();
            break;

        case IDIGNORE:          // Ignore assertion continue executing
            break;
    }
}


CPLAY.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include "stdwin.h"
#include "cplay.h"
#include "about.h"
#include "file.h"
#include "media.h"
#include "toolbar.h"
#include "resource.h"

//
//
// What this sample illustrates
//
// A simple ActiveMovie application written in C.
//
//
// Summary
//
// This is an ActiveMovie application that is written in C. We create a window
// that has a simple menu allowing files to be opened and closed. The window
// also has three buttons for stopping, pausing and running the media. We are
// written to call the standard filtergraph interfaces. The only complication
// we have added is two UI alternatives. The filter can either be compiled to
// rewind to the start of the media when it is stopped, or alternatively it
// can leave the current position (and therefore frame) wherever it is stopped
//
// Files
//
// about.c          Run of the mill about box
// about.h          Header file for about box
// assert.c         Display a message box
// cplay.c          Main application code
// cplay.h          And its function protoypes
// file.c           Looks after file open dialogs
// file.h           And its two function prototypes
// makefile         How we build it
// media.c          Functions that call the graph
// media.h          And their prototypes
// player.rc        Visual C++ resource file
// resource.h       Header file for resources
// stdwin.c         Precompiled header file
// stdwin.h         Precompiled header file
// toolbar.c        Looks after the app toolbar
// toolbar.h        Public function prototypes
//
//

#define UNTITLED_STRING " - Untitled"
#define nMaxResourceStrSize 128

AppVars appVars;

//
// PlayerMessageBox
//
// Load and display an error message
//
void PlayerMessageBox( UINT nResource )
{
    char szStr[ nMaxResourceStrSize ];

    LoadString( appVars.hInstance, nResource, szStr, nMaxResourceStrSize );
    MessageBox( appVars.hwndMainFrame, szStr, appVars.szAppName,
                MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION );

} // PlayerMessageBox

//
// ProcessCommand
//
// Process a WM_COMMAND message to the main window
//
long ProcessCommand( HWND hwnd, UINT wParam, LONG lParam )
{
    switch( wParam ){
        case ID_FILE_EXIT:
            PostQuitMessage( 0 );
            break;

        case ID_HELP_ABOUT:
            DoAboutDialog( appVars.hInstance,hwnd );
            break;

        case ID_FILE_OPEN:
            OpenMediaFile( hwnd, NULL );
            break;

        case ID_MEDIA_PLAY:
            OnMediaPlay( );
            break;

        case ID_MEDIA_PAUSE:
            OnMediaPause( );
            break;

        case ID_MEDIA_STOP:
            OnMediaStop( );
            break;

        default:
            return DefWindowProc( hwnd, WM_COMMAND, wParam, lParam );
    }
    return (LRESULT) 0;

} // ProcessCommand

//
// OnGetMinMaxInfo
//
// Sets the minimum size of the main window and the maximum height
//
void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
    RECT rectRequired;
    SIZE sizeToolbar;

    // Our client area is going to be the toolbar, so find our its size
    CalcRequiredSize( &sizeToolbar );

    rectRequired.left = rectRequired.top = 0;
    rectRequired.right = sizeToolbar.cx;
    rectRequired.bottom = sizeToolbar.cy;

    // Take into account the menu, caption and thick frame
    AdjustWindowRect( &rectRequired, WS_CAPTION|WS_THICKFRAME, TRUE );

    // Set the min/max sizes
    lpMMI->ptMinTrackSize.x = rectRequired.right - rectRequired.left;
    lpMMI->ptMinTrackSize.y = lpMMI->ptMaxTrackSize.y =
            rectRequired.bottom - rectRequired.top;

} // OnGetMinMaxInfo

//
// MainFrameProc
//
// Handles the message sent to the main window
//
long FAR PASCAL MainFrameProc( HWND hwnd, UINT message, UINT wParam, LONG lParam)
{
    switch( message ){
        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        case WM_GETMINMAXINFO:
            OnGetMinMaxInfo( (MINMAXINFO FAR *) lParam );
            break;

        case WM_DRAWITEM:
            DrawButton( appVars.hInstance, (DRAWITEMSTRUCT FAR *) lParam );
            break;

        case WM_INITMENUPOPUP:
            if( lParam == 1 ){          // Media popup menu
                EnableMenuItem( (HMENU) wParam, ID_MEDIA_PLAY,  CanPlay()  ? MF_ENABLED : MF_GRAYED );
                EnableMenuItem( (HMENU) wParam, ID_MEDIA_PAUSE, CanPause() ? MF_ENABLED : MF_GRAYED );
                EnableMenuItem( (HMENU) wParam, ID_MEDIA_STOP,  CanStop()  ? MF_ENABLED : MF_GRAYED );
            } else
                return DefWindowProc( hwnd, message, wParam, lParam );
            break;

        case WM_COMMAND:
            return ProcessCommand( hwnd, wParam, lParam );

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

} // MainFrameProc

//
// InitApplication
//
BOOL InitApplication()
{
    strcpy( appVars.szAppName, APP_NAME );

    // Filter interface initialize?
    if( SUCCEEDED( CoInitialize( NULL )))
return TRUE;

    return FALSE;
}

//
// UnInitApplication
//
void UnInitApplication()
{
    CoUninitialize( );
}

//
// InitInstance
//
// Set the specific instance data and register our main
// window class if it has not been registered already
//
BOOL InitInstance( HANDLE hInstance, HANDLE hPrevInstance )
{
    appVars.hInstance = hInstance;

    if(!hPrevInstance){
        WNDCLASS wndClass;

        wndClass.style          = CS_HREDRAW | CS_VREDRAW;
        wndClass.lpfnWndProc    = MainFrameProc;
        wndClass.cbClsExtra     = 0;
        wndClass.cbWndExtra     = 0;
        wndClass.hInstance      = appVars.hInstance;
        wndClass.hIcon          = LoadIcon( appVars.hInstance, MAKEINTRESOURCE( IDR_MAINFRAME ));
        wndClass.hCursor        = LoadCursor( NULL, IDC_ARROW );
        wndClass.hbrBackground  = GetStockObject( LTGRAY_BRUSH );
        wndClass.lpszMenuName   = MAKEINTRESOURCE( IDR_MAINFRAME );
        wndClass.lpszClassName  = appVars.szAppName;

        RegisterClass( &wndClass );
    }

    return TRUE;

} // InitInstance

//
// InitMainFrame
//
// Create our main window
//
BOOL InitMainFrame( int nCmdShow )
{
    const DWORD Styles = WS_OVERLAPPEDWINDOW &~ WS_MAXIMIZEBOX;
    char szTitle[ 30 ];

    strcpy( szTitle, APP_NAME );
    strcat( szTitle, UNTITLED_STRING );

    appVars.hwndMainFrame =
        CreateWindow( appVars.szAppName,    // Our class name
                      szTitle,              // Window title
                      Styles,               // It's styles
                      CW_USEDEFAULT,        // No x position
                      CW_USEDEFAULT,        // And no y either
                      0, 65,                // Initial sizes
                      NULL,                 // No parent window
                      NULL,                 // And no menu
                      appVars.hInstance,    // App instance
                      NULL);                // Creation data

    ShowWindow( appVars.hwndMainFrame, nCmdShow );
    UpdateWindow( appVars.hwndMainFrame );
    return TRUE;

} // InitMainFrame

//
// DoMainLoop
//
// Main message loop
//
UINT DoMainLoop()
{
    MSG msg;
    HANDLE  ahObjects[1];       // Handles that need to be waited on
    const int cObjects = 1;     // Number of objects that we have

    // Message loop lasts until we get a WM_QUIT message
    // Upon which we shall return from the function

    while (TRUE) {
        if( (ahObjects[ 0 ] = GetGraphEventHandle()) == NULL ) {
            WaitMessage();
        } else {

            // Wait for any message or a graph notification

            DWORD Result = MsgWaitForMultipleObjects( cObjects,
                                                      ahObjects,
                                                      FALSE,
                                                      INFINITE,
                                                      QS_ALLINPUT);

            // Have we received an event notification

            if( Result != (WAIT_OBJECT_0 + cObjects) )
            {
                if( Result == WAIT_OBJECT_0 ) {
                    OnGraphNotify();
                }
                continue;
            }
        }

        // Read all of the messages in this next loop
        // removing each message as we read it

        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                return msg.wParam;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

} // DoMainLoop

//
// WinMain
//
// Application entry point
//
int PASCAL WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdParam,
                    int nCmdShow )
{
    UINT nReturn;

    // Initialise COM and the application
    if ( InitApplication() == FALSE ) return 0;

    if( InitInstance( hInstance, hPrevInstance ) &&
        InitMainFrame( nCmdShow ) &&
        InitToolbar( hInstance, appVars.hwndMainFrame ) &&
        InitMedia( ) &&
        InitFileOpenDialog( appVars.hwndMainFrame ))
    {
        if( lpszCmdParam[0] != '\0' ) {
    OpenMediaFile( appVars.hwndMainFrame, lpszCmdParam );
        }

        nReturn = DoMainLoop();

// Stop the graph if we can
if( CanStop() )
            OnMediaStop();

        // Release the filter graph
DeleteContents();
    }

    UnInitApplication();
    return nReturn;

} // WinMain


CPLAY.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#define APP_NAME "Player"

typedef struct
{
    char szAppName[ 32 ];
    HANDLE hInstance;
    HWND hwndMainFrame;
    BOOL bFatalError;
} AppVars;

void PlayerMessageBox( UINT nResource );


FILE.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// This handles the file..open dialog box

#include "stdwin.h"
#include "file.h"

static OPENFILENAME ofn;

//
// InitFileOpenDialog
//
BOOL InitFileOpenDialog( HWND hwnd )
{
    ofn.lStructSize         = sizeof( OPENFILENAME );
    ofn.hwndOwner           = hwnd;
    ofn.hInstance           = NULL;
    ofn.lpstrFilter         = "Media files\0*.MPG;*.AVI;*.MOV;*.WAV\0"
                              "MPEG files\0*.MPG\0"
                              "AVI files\0*.AVI\0"
                              "Quick Time files\0*.MOV\0"
                              "Wave audio files\0*.WAV\0"
                              "Filter graph files\0*.GRF\0"
                              "All Files\0*.*\0\0";
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0;
    ofn.nFilterIndex        = 0;
    ofn.lpstrFile           = NULL;
    ofn.nMaxFile            = _MAX_PATH;
    ofn.lpstrFileTitle      = NULL;
    ofn.nMaxFileTitle       = _MAX_FNAME + _MAX_EXT;
    ofn.lpstrInitialDir     = NULL;
    ofn.lpstrTitle          = NULL;
    ofn.Flags               = OFN_FILEMUSTEXIST;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = "mpg";
    ofn.lCustData           = 0;
    ofn.lpfnHook            = NULL;
    ofn.lpTemplateName      = NULL;

    return TRUE;

} // InitFileOpenDialog

//
// DoFileOpenDialog
//
BOOL DoFileOpenDialog( HWND hwnd, LPSTR lpstrFileName, LPSTR lpstrTitleName )
{
    // Called to display the open file dialog
    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = lpstrFileName;
    ofn.lpstrFileTitle = lpstrTitleName;

    return GetOpenFileName( &ofn );

} // DoFileOpenDialog


FILE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

BOOL InitFileOpenDialog( HWND hwnd );
BOOL DoFileOpenDialog( HWND hwnd, LPSTR lpstrFileName, LPSTR lpstrTitleName );


MEDIA.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include "stdwin.h"
#include <evcode.h>
#include "cplay.h"
#include "file.h"
#include "media.h"
#include "toolbar.h"
#include "resource.h"

// Current multimedia variables
static Media media;

//
// CanPlay
//
// Return true if we can go to a playing state from our current state
//
BOOL CanPlay()
{
    return (media.state == Stopped || media.state == Paused);
}

//
// CanStop
//
// Return true if we can go to a stopped state from our current state
//
BOOL CanStop()
{
    return (media.state == Playing || media.state == Paused);
}

//
// CanPause
//
// Return true if we can go to a paused state from our current state
//
BOOL CanPause()
{
    return (media.state == Playing || media.state == Stopped);
}

//
// IsInitialized
//
// Return true if we have loaded and initialized a multimedia file
//
BOOL IsInitialized()
{
    return (media.state != Uninitialized);
}

//
// ChangeStateTo
//
void ChangeStateTo( State newState )
{
    media.state = newState;

    // update the toolbar
    UpdateToolbar();
}

//
// InitMedia
//
// Initialization
//
BOOL InitMedia()
{
    ChangeStateTo( Uninitialized );

    media.hGraphNotifyEvent = NULL;
    media.pGraph = NULL;

    return TRUE;
}

//
// CreateFilterGraph
//
BOOL CreateFilterGraph()
{
    IMediaEvent *pME;
    HRESULT hr;

    ASSERT(media.pGraph == NULL);

    hr = CoCreateInstance(&CLSID_FilterGraph,           // CLSID of object
  NULL,                         // Outer unknown
  CLSCTX_INPROC_SERVER,         // Type of server
  &IID_IGraphBuilder,           // Interface wanted
  (void **) &media.pGraph);     // Returned object
    if (FAILED(hr)){
media.pGraph = NULL;
return FALSE;
    }

    // We use this to find out events sent by the filtergraph

    hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaEvent, (void **) &pME);
    if (FAILED(hr)) {
DeleteContents();
return FALSE;
    }

    hr = pME->lpVtbl->GetEventHandle(pME, (OAEVENT*) &media.hGraphNotifyEvent);
    pME->lpVtbl->Release( pME );

    if (FAILED(hr)) {
DeleteContents();
return FALSE;
    }

    return TRUE;

} // CreateFilterGraph

// Destruction
//
// DeleteContents
//
void DeleteContents()
{
    if (media.pGraph != NULL) {
media.pGraph->lpVtbl->Release( media.pGraph );
media.pGraph = NULL;
    }

    // this event is owned by the filter graph and is thus invalid
    media.hGraphNotifyEvent = NULL;

    ChangeStateTo( Uninitialized );
}

//
// RenderFile
//
BOOL RenderFile( LPSTR szFileName )
{
    HRESULT hr;
    WCHAR wPath[MAX_PATH];

    DeleteContents();

    if ( !CreateFilterGraph() ) {
PlayerMessageBox( IDS_CANT_INIT_QUARTZ );
return FALSE;
    }

    MultiByteToWideChar( CP_ACP, 0, szFileName, -1, wPath, MAX_PATH );

    SetCursor( LoadCursor( NULL, IDC_WAIT ) );
    hr = media.pGraph->lpVtbl->RenderFile(media.pGraph, wPath, NULL);
    SetCursor( LoadCursor( NULL, IDC_ARROW ) );

    if (FAILED( hr )) {
PlayerMessageBox( IDS_CANT_RENDER_FILE );
return FALSE;
    }
    return TRUE;

} // RenderFile

//
// SetTitle
//
// Update the title of the video renderer to "Player - szFile"
//
void SetTitle( HWND hwnd, char *szFile )
{
    char szNewTitle[ _MAX_FNAME + _MAX_EXT  + 20 ];

    strcpy( szNewTitle, APP_NAME );
    strcat( szNewTitle, " - " );
    strcat( szNewTitle, szFile );

    // Update the window's title
    SetWindowText( hwnd, szNewTitle );

} // SetTitle

//
// OpenMediaFile
//
// File..Open has been selected
//
void OpenMediaFile( HWND hwnd, LPSTR szFile )
{
    static char szFileName[ _MAX_PATH ];
    static char szTitleName[ _MAX_FNAME + _MAX_EXT ];

    if( szFile != NULL && RenderFile( szFile ))
    {
LPSTR szTitle;

// Work out the full path name and the file name from the
// specified file
GetFullPathName( szFile, _MAX_PATH, szFileName, &szTitle );
strncpy( szTitleName, szTitle, _MAX_FNAME + _MAX_EXT );
szTitleName[ _MAX_FNAME + _MAX_EXT -1 ] = '\0';

// Set the main window title and update the state
SetTitle( hwnd, szTitleName );
ChangeStateTo( Stopped );

    } else if( DoFileOpenDialog( hwnd, szFileName, szTitleName )
       && RenderFile( szFileName ) ){

// Set the main window title and update the state
SetTitle( hwnd, szTitleName );
ChangeStateTo( Stopped );
    }

} // OpenMediaFile

//
// OnMediaPlay
//
// There are two possible UI strategies for an application: you either play
// from the start each time you stop, or you play from the current position,
// in which case you have to explicitly rewind at the end. If you want the
// play from current and rewind at end, enable this code, if you want the
// other option, then enable FROM_START in both OnMediaStop and OnAbortStop.

#undef REWIND
#define FROM_START

void OnMediaPlay()
{
    if( CanPlay() ){
HRESULT hr;
IMediaControl *pMC;

// Obtain the interface to our filter graph
hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaControl, (void **) &pMC);

if( SUCCEEDED(hr) ){
#ifdef REWIND
    IMediaPosition * pMP;
    hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph,
                                      &IID_IMediaPosition,
                                      (void**) &pMP);
    if (SUCCEEDED(hr)) {
// start from last position, but rewind if near the end
REFTIME tCurrent, tLength;
hr = pMP->lpVtbl->get_Duration(pMP, &tLength);
if (SUCCEEDED(hr)) {
    hr = pMP->lpVtbl->get_CurrentPosition(pMP, &tCurrent);
    if (SUCCEEDED(hr)) {
// within 1sec of end? (or past end?)
if ((tLength - tCurrent) < 1) {
    pMP->lpVtbl->put_CurrentPosition(pMP, 0);
}
    }
}
pMP->lpVtbl->Release(pMP);
    }
#endif
    // Ask the filter graph to play and release the interface
    hr = pMC->lpVtbl->Run( pMC );
    pMC->lpVtbl->Release( pMC );

    if( SUCCEEDED(hr) ){
ChangeStateTo( Playing );
return;
    }
}

// Inform the user that an error occurred
PlayerMessageBox( IDS_CANT_PLAY );

    }

} // OnMediaPlay

//
// OnMediaPause
//
void OnMediaPause()
{
    if( CanPause() ){
HRESULT hr;
IMediaControl *pMC;

// Obtain the interface to our filter graph
hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaControl, (void **) &pMC);

if( SUCCEEDED(hr) ){
    // Ask the filter graph to pause and release the interface
    hr = pMC->lpVtbl->Pause( pMC );
    pMC->lpVtbl->Release( pMC );

    if( SUCCEEDED(hr) ){
ChangeStateTo( Paused );
return;
    }
}

// Inform the user that an error occurred
PlayerMessageBox( IDS_CANT_PAUSE );
    }

} // OnMediaPause

//
// OnMediaAbortStop
//
// Called when we get an EC_USER_ABORT or EC_ERROR_ABORT event
//
void OnMediaAbortStop(void)
{
    if(CanStop())
    {
HRESULT hr;
IMediaControl *pMC;

// Obtain the interface to our filter graph
hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaControl, (void **) &pMC);

if( SUCCEEDED(hr) ){
#ifdef FROM_START
    IMediaPosition * pMP;
#endif
    // Ask the filter graph to stop and release the interface
    hr = pMC->lpVtbl->Stop( pMC );
    pMC->lpVtbl->Release( pMC );

#ifdef FROM_START

    // if we want always to play from the beginning
    // then we should seek back to the start here
    // (on app or user stop request, and also after EC_COMPLETE).
    hr = media.pGraph->lpVtbl->QueryInterface(
media.pGraph,
&IID_IMediaPosition,
(void**) &pMP);
    if (SUCCEEDED(hr)) {
pMP->lpVtbl->put_CurrentPosition(pMP, 0);
pMP->lpVtbl->Release(pMP);
    }

    // no visible rewind or we will re-show the window!

#endif

    if( SUCCEEDED(hr) ){
ChangeStateTo( Stopped );
return;
    }
}
// Inform the user that an error occurred
PlayerMessageBox( IDS_CANT_STOP );
    }

} // OnMediaAbortStop

//
// OnMediaStop
//
// There are two different ways to stop a graph. We can stop and then when we
// are later paused or run continue from the same position. Alternatively the
// graph can be set back to the start of the media when it is stopped to have
// a more CDPLAYER style interface. These are both offered here conditionally
// compiled using the FROM_START definition. The main difference is that in
// the latter case we put the current position to zero while we change states
//
void OnMediaStop()
{
    if( CanStop() ){
HRESULT hr;
IMediaControl *pMC;

// Obtain the interface to our filter graph
hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaControl, (void **) &pMC);
if( SUCCEEDED(hr) ){

#ifdef FROM_START
    IMediaPosition * pMP;
    OAFilterState state;

    // Ask the filter graph to pause
    hr = pMC->lpVtbl->Pause( pMC );

    // if we want always to play from the beginning
    // then we should seek back to the start here
    // (on app or user stop request, and also after EC_COMPLETE).
    hr = media.pGraph->lpVtbl->QueryInterface(media.pGraph,
      &IID_IMediaPosition,
      (void**) &pMP);
    if (SUCCEEDED(hr)) {
pMP->lpVtbl->put_CurrentPosition(pMP, 0);
pMP->lpVtbl->Release(pMP);
    }

    // wait for pause to complete
    pMC->lpVtbl->GetState(pMC, INFINITE, &state);
#endif

    // now really do the stop
    pMC->lpVtbl->Stop( pMC );
    pMC->lpVtbl->Release( pMC );
    ChangeStateTo( Stopped );
    return;
}

// Inform the user that an error occurred
PlayerMessageBox( IDS_CANT_STOP );
    }

} // OnMediaStop

//
// GetGraphEventHandle
//
// We use this to check for graph events
//
HANDLE GetGraphEventHandle()
{
    return media.hGraphNotifyEvent;

} // GetGraphEventHandle

//
// OnGraphNotify
//
// If the event handle is valid, then ask the graph if
// anything has happened (eg the graph has stopped...)
//
void OnGraphNotify()
{
    IMediaEvent *pME;
    long lEventCode, lParam1, lParam2;

    ASSERT( media.hGraphNotifyEvent != NULL );

    if( SUCCEEDED(media.pGraph->lpVtbl->QueryInterface(media.pGraph, &IID_IMediaEvent, (void **) &pME))){
if( SUCCEEDED(pME->lpVtbl->GetEvent(pME, &lEventCode, &lParam1, &lParam2, 0))) {
    if (lEventCode == EC_COMPLETE) {
OnMediaStop();
    } else if ((lEventCode == EC_USERABORT) ||
       (lEventCode == EC_ERRORABORT)) {
OnMediaAbortStop();
    }
}
pME->lpVtbl->Release( pME );
    }
}


MEDIA.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

typedef enum tagState {Uninitialized, Stopped, Paused, Playing } State;

typedef struct tagMedia
{
    State state;
    IGraphBuilder *pGraph;
    HANDLE hGraphNotifyEvent;
} Media;

BOOL InitMedia();
void OpenMediaFile( HWND hwnd, LPSTR szFile );
HANDLE GetGraphEventHandle( void );

BOOL CanPlay();
BOOL CanStop();
BOOL CanPause();
BOOL IsInitialized();
void DeleteContents();

// Event handlers

void OnMediaPlay();
void OnMediaPause();
void OnMediaStop();
void OnMediaAbortStop();
void OnGraphNotify();


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by player.rc
//
#define IDD_ABOUTBOX                    100
#define IDR_MAINFRAME                   128
#define IDR_DISABLED_MAINFRAME          129
#define ID_MEDIA_PLAY                   32771
#define ID_MEDIA_PAUSE                  32772
#define ID_MEDIA_STOP                   32773
#define ID_FILE_OPEN                    0xE101
#define ID_FILE_MRU_FILE1               0xE110
#define ID_FILE_ABOUT                   0xE140
#define ID_HELP_ABOUT                   0xE140
#define ID_FILE_EXIT                    0xE141
#define IDS_CANT_INIT_QUARTZ            61204
#define IDS_CANT_RENDER_FILE            61205
#define IDS_CANT_PLAY                   61206
#define IDS_CANT_PAUSE                  61207
#define IDS_CANT_STOP                   61208
#define IDC_STATIC                      65535

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDWIN.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Precomplied header file
#include "stdwin.h"


STDWIN.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Allows Visual C++ to precompile the standard header files

#ifdef NDEBUG
  #define ASSERT( exp ) ((void)0)
#else
  void DbgAssert(const char *pCondition, const char *pFileName, int iLine);
  #define ASSERT(exp) if(!(exp)) DbgAssert( TEXT(#exp), TEXT(__FILE__), TEXT(__LINE__) );
#endif

#include <windows.h>
#include <commdlg.h>
#include <string.h>

#include <objbase.h>
#include <strmif.h>
#include <control.h>
#include <uuids.h>


TOOLBAR.C

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// In the C++ versions of Player the MFC class CToolbar handles all of the
// toolbar implementation, including creating a 'disabled' bitmap from the
// toolbar bitmap. In this app, the pure C version, of Player we insist on
// a seperate disabled bitmap being supplied by the programmer. This
// drastically reduces the amount of code required to maintain the toolbar.
// The buttons in this app are controls with the owner draw attribute set.

#include "stdwin.h"
#include "media.h"
#include "resource.h"

// Constants for the bitmap
const int nButtonImageWidth = 32;
const int nButtonImageHeight = 32;

// Constants for the toolbar implementation
const int nButtonBorder = 8;
const int nSeperatorGap = 6;
const int nToolbarBorderWidth = 5;
const int nToolbarBorderHeight = 3;

// The window for each button
struct{
    HWND hwndPlayButton;
    HWND hwndPauseButton;
    HWND hwndStopButton;
} toolbar;

//
// CalcRequiredSize
//
void CalcRequiredSize( SIZE *pSize )
{
    // Calculate the area required for this toolbar

    // size for 3 buttons, 2 borders and a seperator
    // ...but we'll add on a some extra seperators for good measure
    pSize->cx = (nButtonImageWidth + nButtonBorder) * 3
                    + nToolbarBorderWidth * 2 + nSeperatorGap*5;

    // size for 1 button and 2 borders
    pSize->cy = nButtonImageHeight + nButtonBorder
                    + nToolbarBorderHeight * 2;

} // CalcRequiredSize

//
// UpdateToolbar
//
// Maintains the enabled/disabled state of the buttons - we should be
// called periodically and/or whenever there is a change of graph state
//
void UpdateToolbar()
{
    EnableWindow( toolbar.hwndPlayButton, CanPlay() );
    EnableWindow( toolbar.hwndPauseButton, CanPause() );
    EnableWindow( toolbar.hwndStopButton, CanStop() );

} // UpdateToolbar

//
// InitToolbar
//
// Create the controls for the buttons
//
BOOL InitToolbar( HINSTANCE hInstance, HWND hwnd )
{
    int x;      // Position of the next button

    x = nToolbarBorderWidth;

    // The 'Play' button
    toolbar.hwndPlayButton = CreateWindow( "BUTTON",
                                           NULL,
                                           BS_OWNERDRAW | WS_VISIBLE | WS_CHILD,
                                           x,
                                           nToolbarBorderHeight,
                                           nButtonImageWidth + nButtonBorder,
                                           nButtonImageHeight + nButtonBorder,
                                           hwnd,
                                           (HMENU) ID_MEDIA_PLAY,
                                           hInstance,
                                           NULL);

    x += nButtonImageWidth + nButtonBorder;

    // The 'Pause' button
    toolbar.hwndPauseButton = CreateWindow( "BUTTON",
                                            NULL,
                                            BS_OWNERDRAW | WS_VISIBLE | WS_CHILD,
                                            x,
                                            nToolbarBorderHeight,
                                            nButtonImageWidth + nButtonBorder,
                                            nButtonImageHeight + nButtonBorder,
                                            hwnd,
                                            (HMENU) ID_MEDIA_PAUSE,
                                            hInstance,
                                            NULL);

    x += nButtonImageWidth + nButtonBorder + nSeperatorGap;

    // The 'Stop' button
    toolbar.hwndStopButton = CreateWindow( "BUTTON",
                                           NULL,
                                           BS_OWNERDRAW | WS_VISIBLE | WS_CHILD,
                                           x,
                                           nToolbarBorderHeight,
                                           nButtonImageWidth + nButtonBorder,
                                           nButtonImageHeight + nButtonBorder,
                                           hwnd,
                                           (HMENU) ID_MEDIA_STOP,
                                           hInstance,
                                           NULL);

    // We don't call UpdateToolbar to set the button states as
    // the multimedia variables may not have been initialized yet
    return TRUE;

} // InitToolbar

//
// DrawRect
//
// Draws a filled rectangle using the given stock object.
// The rectangle starts at left,top and includes right,bottom
//
void DrawRect( HDC hDC, int left, int top, int right, int bottom, UINT nStockObject )
{
    RECT rect;

    rect.left = left;
    rect.top = top;
    rect.bottom = bottom+1;
    rect.right = right+1;

    FillRect( hDC, &rect, GetStockObject( nStockObject ) );

} // DrawRect

//
// DrawButton
//
// Called by the main window whenever a button needs to be redrawn
//
void DrawButton( HINSTANCE hInstance, DRAWITEMSTRUCT FAR * lpDrawItem )
{
    HDC hSourceDC = CreateCompatibleDC( NULL );
    HGDIOBJ hgdiOldBitmap;
    int nIndex;
    UINT nUpperBrush, nLowerBrush;

    int lFrame = lpDrawItem->rcItem.left;
    int tFrame = lpDrawItem->rcItem.top;
    int rFrame = lpDrawItem->rcItem.right -1;
    int bFrame = lpDrawItem->rcItem.bottom - 1;

    // Draw a black, rounded frame around the bottom top and bottom lines
    DrawRect( lpDrawItem->hDC, lFrame+1, tFrame, rFrame-1, tFrame, BLACK_BRUSH );
    DrawRect( lpDrawItem->hDC, lFrame+1, bFrame, rFrame-1, bFrame, BLACK_BRUSH );

    // left and right lines
    DrawRect( lpDrawItem->hDC, lFrame, tFrame+1, lFrame, bFrame-1, BLACK_BRUSH );
    DrawRect( lpDrawItem->hDC, rFrame, tFrame+1, rFrame, bFrame-1, BLACK_BRUSH );

    // Adjust the pointers to point to the inside of the rectangle
    lFrame++; rFrame--;
    tFrame++; bFrame--;

    // The left and top will be highlighted and the right and bottom will
    // be in shadow if the button is raised (ie, not selected).
    // Otherwise the left and top will be in shadow and the right and bottom
    // will be their normal light gray

    if( lpDrawItem->itemState & ODS_SELECTED ){
        nUpperBrush = GRAY_BRUSH;
        nLowerBrush = LTGRAY_BRUSH;
    } else {
        nUpperBrush = WHITE_BRUSH;
        nLowerBrush = GRAY_BRUSH;
    }

    // Draw top & left border highlight/shadow
    DrawRect( lpDrawItem->hDC, lFrame, tFrame, rFrame, tFrame, nUpperBrush );
    DrawRect( lpDrawItem->hDC, lFrame, tFrame, lFrame, bFrame, nUpperBrush );

    // Draw bottom & right border highlight/shadow
    DrawRect( lpDrawItem->hDC, rFrame, tFrame+1, rFrame, bFrame, nLowerBrush );
    DrawRect( lpDrawItem->hDC, lFrame+1, bFrame, rFrame, bFrame, nLowerBrush );

    // Load the IDR_DISABLED_MAINFRAME bitmap into our source hDC if the
    // button is disabled otherwise load the IDR_MAINFRAME bitmap
    if( lpDrawItem->itemState & ODS_DISABLED )
        hgdiOldBitmap = SelectObject( hSourceDC,
            (HGDIOBJ) LoadBitmap( hInstance, MAKEINTRESOURCE( IDR_DISABLED_MAINFRAME ) ));
    else
        hgdiOldBitmap =
        hgdiOldBitmap = SelectObject( hSourceDC,
            (HGDIOBJ) LoadBitmap( hInstance, MAKEINTRESOURCE( IDR_MAINFRAME ) ));

    // Decide which button to blit to the display
    switch( lpDrawItem->CtlID ){
        case ID_MEDIA_PLAY:
            nIndex = 0;
            break;

        case ID_MEDIA_PAUSE:
            nIndex = 1;
            break;

        case ID_MEDIA_STOP:
            nIndex = 2;
            break;
    }

    // ..and blit it
    BitBlt( lpDrawItem->hDC,
            lpDrawItem->rcItem.left + nButtonBorder/2,
            lpDrawItem->rcItem.top  + nButtonBorder/2,
            nButtonImageWidth,
            nButtonImageHeight,
            hSourceDC,
            nIndex * nButtonImageWidth,
            0,
            SRCCOPY
          );

    // Restore the original bitmap
    SelectObject( hSourceDC, hgdiOldBitmap );

} // DrawButton


TOOLBAR.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

void UpdateToolbar();
void DrawButton( HINSTANCE hInstance, DRAWITEMSTRUCT FAR * lpDrawItem );
void CalcRequiredSize( SIZE *pSize );
BOOL InitToolbar( HINSTANCE hInstance, HWND hwnd );


INWINDOW.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include <mmsystem.h>
  #include <streams.h>
  #include "inwindow.h"

  #define APPLICATIONNAME "PlayFile"
  #define CLASSNAME "PlayFile"

  #define WM_GRAPHNOTIFY  WM_USER+13

  #define HELPER_RELEASE(x) { if (x) x->Release(); x = NULL; }

  HWND      ghApp;
  HINSTANCE ghInst;

  HRESULT   hr;
  LONG      evCode;
  LONG      evParam1;
  LONG      evParam2;
  RECT      grc;

  // Collection of interfaces
  IBaseFilter   *pif   = NULL;
  IGraphBuilder *pigb  = NULL;
  IMediaControl *pimc  = NULL;
  IMediaEventEx *pimex = NULL;
  IVideoWindow  *pivw  = NULL;

  void PlayMovieInWindow (LPSTR szFile)

    { // PlayMovieInWindow //

      WCHAR wFile[MAX_PATH];
      MultiByteToWideChar( CP_ACP, 0, szFile, -1, wFile, MAX_PATH );

      hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pigb);

      if (SUCCEEDED(hr))

      { // Graphbuilder instance

        // QueryInterface for some basic interfaces
        pigb->QueryInterface(IID_IMediaControl, (void **)&pimc);
        pigb->QueryInterface(IID_IMediaEventEx, (void **)&pimex);
        pigb->QueryInterface(IID_IVideoWindow, (void **)&pivw);

        // Have the graph construct its the appropriate graph automatically
        hr = pigb->RenderFile(wFile, NULL);

        pivw->put_Owner((OAHWND)ghApp);
        pivw->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN);

        // Have the graph signal event via window callbacks for performance
        pimex->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0);

        GetClientRect(ghApp, &grc);
        pivw->SetWindowPosition(grc.left, grc.top, grc.right, grc.bottom);

        // Run the graph if RenderFile succeeded
        if (SUCCEEDED(hr))
          pimc->Run();

      } // Graphbuilder instance

    } // PlayMovieInWindow //

  // ---------------------------------------------------------------------------
  // End of multimedia specific code.
  // ---------------------------------------------------------------------------

  BOOL GetClipFileName(LPSTR szName)

    { // GetClipFileName //

      OPENFILENAME ofn;

      ofn.lStructSize       = sizeof(OPENFILENAME);
      ofn.hwndOwner         = ghApp;
      ofn.lpstrFilter       = NULL;
      ofn.lpstrFilter       = "Video files (*.mpg; *.mpeg; *.avi; *.mov; *.qt)\0*.mpg; *.mpeg; *.avi; *.mov; *.qt\0\0";
      ofn.lpstrCustomFilter = NULL;
      ofn.nFilterIndex      = 1;
      *szName = 0;
      ofn.lpstrFile         = szName;
      ofn.nMaxFile          = MAX_PATH;
      ofn.lpstrInitialDir   = NULL;
      ofn.lpstrTitle        = NULL;
      ofn.lpstrFileTitle    = NULL;
      ofn.lpstrDefExt       = "MPG";
      ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

      return GetOpenFileName((LPOPENFILENAME)&ofn);

    } // GetClipFileName //

  LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    { // WndMainProc //

      switch(message)

        { // Window msgs handling

          case WM_EXITSIZEMOVE:

            GetClientRect(ghApp, &grc);
            pivw->SetWindowPosition(grc.left, grc.top, grc.right, grc.bottom);
            break;

          case WM_COMMAND:

            switch(wParam)

              { // Menus

                case ID_FILE_OPENCLIP:

                  TCHAR szFilename[MAX_PATH];

                  if (GetClipFileName(szFilename))
                    PlayMovieInWindow(szFilename);

                  break;

                case ID_FILE_EXIT:

                  if (pimc)
                    pimc->Stop();

                  if (pivw)

                    { // Relinquish ownership (IMPORTANT!) after hiding

                      pivw->put_Visible(OAFALSE);
                      pivw->put_Owner(NULL);
                      HELPER_RELEASE(pivw);

                    } // Relinquish ownership (IMPORTANT!) after hiding

                  HELPER_RELEASE(pif);
                  HELPER_RELEASE(pigb);
                  HELPER_RELEASE(pimc);
                  HELPER_RELEASE(pimex);

                  PostQuitMessage(0);

                  break;

              } // Menus

            break;

          case WM_GRAPHNOTIFY:

            while (SUCCEEDED(pimex->GetEvent(&evCode, &evParam1, &evParam2, 0)))

              { // Spin through the events

                hr = pimex->FreeEventParams(evCode, evParam1, evParam2);

                if (EC_COMPLETE == evCode)

                  { // Finished

                    if (pivw)

                      { // Relinquish ownership (IMPORTANT!) after hiding

                        pivw->put_Visible(OAFALSE);
                        pivw->put_Owner(NULL);
                        HELPER_RELEASE(pivw);

                      } // Relinquish ownership (IMPORTANT!) after hiding

                    HELPER_RELEASE(pif);
                    HELPER_RELEASE(pigb);
                    HELPER_RELEASE(pimc);
                    HELPER_RELEASE(pimex);

                    break;

                  } // Finished

              } // Spin through the events

            break;

          case WM_DESTROY:
            PostQuitMessage(0);
            break;

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

      } // Window msgs handling

      return FALSE;

    } // WndMainProc //

  int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

    { // WinMain //

      MSG msg;
      WNDCLASS wc;

      // OLE subsystem requires applications to initialize things first!
      CoInitialize(NULL);

      ZeroMemory(&wc, sizeof wc);
      wc.lpfnWndProc = WndMainProc;
      ghInst = wc.hInstance = hInstC;
      wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
      wc.lpszClassName = CLASSNAME;
      wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_INWINDOW));
      RegisterClass(&wc);

      ghApp = CreateWindow(CLASSNAME,
        APPLICATIONNAME,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        0,
        0,
        ghInst,
        0);

      ShowWindow(ghApp, SW_NORMAL);
      UpdateWindow(ghApp);

      while (GetMessage(&msg,NULL,0,0))

        {  // Message loop

           TranslateMessage(&msg);
           DispatchMessage(&msg);

        }  // Message loop

      // Finished with OLE subsystem
      CoUninitialize();

      return msg.wParam;

    } // WinMain //


INWINDOW.H

#define IDR_MENU                        101
#define ID_FILE_OPENCLIP                40001
#define ID_FILE_EXIT                    40002
#define IDI_INWINDOW                    100


MAINFRM.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// mainfrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "mfcplay.h"

#include "mainfrm.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_GETMINMAXINFO()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// arrays of IDs used to initialize control bars

// toolbar buttons - IDs are command buttons
static UINT BASED_CODE buttons[] =
{
// same order as in the bitmap 'toolbar.bmp'
ID_MEDIA_PLAY,
ID_MEDIA_PAUSE,
ID_SEPARATOR,
ID_MEDIA_STOP,
};

const int nButtons = sizeof(buttons)/sizeof(UINT);
const int nButtonImageWidth = 32;
const int nButtonImageHeight = 32;
const int nButtonBorder = 8;

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
    sizeImage = CSize( nButtonImageWidth, nButtonImageHeight );
    sizeButton = sizeImage + CSize( nButtonBorder, nButtonBorder );

}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadBitmap(IDR_MAINFRAME) ||
!m_wndToolBar.SetButtons(buttons, nButtons))
{
TRACE0("Failed to create toolbar\n");
return -1;      // fail to create
}

    m_wndToolBar.SetSizes( sizeButton, sizeImage );

    SetWindowPos( NULL, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE );

// TODO: Remove this if you don't want tool tips
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY);

return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style = WS_OVERLAPPED | WS_CAPTION | FWS_ADDTOTITLE
| WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX;

return CFrameWnd::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers

void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
    // Find out the default sizes
CFrameWnd::OnGetMinMaxInfo(lpMMI);

    // Our client area is going to be the toolbar
    CRect rectClient =
        CRect( CPoint(0, 0), CSize(sizeButton.cx * nButtons, sizeButton.cy) );

    // Adjust for a border around the buttons
    rectClient.BottomRight().y += nButtonBorder;

    // The windows API AdjustWindowRect (and thus Cwnd::CalcWindowRect) gets its
    // sums wrong when you have a WS_OVERLAPPED style. The following line gets
    // around this.
    AdjustWindowRect( &rectClient, WS_CAPTION|WS_THICKFRAME, TRUE );

    CSize sizeRequired = rectClient.BottomRight() - rectClient.TopLeft();

    lpMMI->ptMinTrackSize = CPoint( sizeRequired );
    lpMMI->ptMaxTrackSize.y = sizeRequired.cy;
}


MAINFRM.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// mainfrm.h : interface of the CMainFrame class
//
/////////////////////////////////////////////////////////////////////////////

class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)

// Attributes
private:
    CSize sizeButton, sizeImage;

public:

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMainFrame)
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
CToolBar    m_wndToolBar;

// Generated message map functions
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


MFCDOC.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// playdoc.cpp : implementation of the CPlayerDoc class
//

#include "stdafx.h"
#include <reftime.h>
#include "mfcplay.h"

#include "mfcdoc.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPlayerDoc

IMPLEMENT_DYNCREATE(CPlayerDoc, CDocument)

BEGIN_MESSAGE_MAP(CPlayerDoc, CDocument)
//{{AFX_MSG_MAP(CPlayerDoc)
ON_UPDATE_COMMAND_UI(ID_MEDIA_PLAY, OnUpdateMediaPlay)
ON_UPDATE_COMMAND_UI(ID_MEDIA_PAUSE, OnUpdateMediaPause)
ON_UPDATE_COMMAND_UI(ID_MEDIA_STOP, OnUpdateMediaStop)
ON_COMMAND(ID_MEDIA_PLAY, OnMediaPlay)
ON_COMMAND(ID_MEDIA_PAUSE, OnMediaPause)
ON_COMMAND(ID_MEDIA_STOP, OnMediaStop)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPlayerDoc construction/destruction

CPlayerDoc::CPlayerDoc()
{
// TODO: add one-time construction code here
    m_pGraph = NULL;
    m_State = Uninitialized;
    m_hGraphNotifyEvent = NULL;

    ((CPlayerApp *) AfxGetApp())->OnDocumentCreated( this );
}

BOOL CPlayerDoc::CreateFilterGraph(void) {
    HRESULT hr;// return code

    ASSERT(m_pGraph == NULL);

    hr = CoCreateInstance(CLSID_FilterGraph, // get this documents graph object
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IGraphBuilder,
                          (void **) &m_pGraph);
    if (FAILED(hr)){
    m_pGraph = NULL;
        return FALSE;
    }

    // get hold of the event notification handle so we can wait for
    // completion
    IMediaEvent *pME;
    hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME);
    if (FAILED(hr)) {
        DeleteContents();
        return FALSE;
    }

    hr = pME->GetEventHandle((OAEVENT*) &m_hGraphNotifyEvent);

    pME->Release();

    if (FAILED(hr)) {
        DeleteContents();
        return FALSE;
    }

    return TRUE;
}

void CPlayerDoc::DeleteContents( void ){

    if (m_pGraph != NULL) {
        m_pGraph->Release();
        m_pGraph = NULL;
    }

    // this event is owned by the filtergraph and thus is no longer valid
    m_hGraphNotifyEvent = NULL;

    m_State = Uninitialized;
}

CPlayerDoc::~CPlayerDoc()
{
    DeleteContents();

    ((CPlayerApp *) AfxGetApp())->OnDocumentDestroyed( this );
}

/////////////////////////////////////////////////////////////////////////////
// CPlayerDoc diagnostics

#ifdef _DEBUG
void CPlayerDoc::AssertValid() const
{
CDocument::AssertValid();
}

void CPlayerDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CPlayerDoc commands

void CPlayerDoc::OnUpdateMediaPlay(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanPlay() );

}

void CPlayerDoc::OnUpdateMediaPause(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanPause() );

}

void CPlayerDoc::OnUpdateMediaStop(CCmdUI* pCmdUI)
{
    pCmdUI->Enable( CanStop() );

}

void CPlayerDoc::OnMediaPlay()
{
    if( CanPlay() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to play and release the interface

            // default behaviour is to carry on from where we stopped last
            // time.
            //
            // if you want it to do this, but rewind at the end then
            // define REWIND.
            // Otherwise you probably want to always start from the
            // beginning -> define FROM_START (in OnMediaStop)
#undef REWIND
#define FROM_START

#ifdef REWIND
            IMediaPosition * pMP;
            hr = m_pGraph->QueryInterface(IID_IMediaPosition, (void**) &pMP);
            if (SUCCEEDED(hr)) {
                // start from last position, but rewind if near the
                // end
                REFTIME tCurrent, tLength;
                hr = pMP->get_Duration(&tLength);
                if (SUCCEEDED(hr)) {
                    hr = pMP->get_CurrentPosition(&tCurrent);
                    if (SUCCEEDED(hr)) {
                        // within 1sec of end? (or past end?)
                        if ((tLength - tCurrent) < 1) {
                            pMP->put_CurrentPosition(0);
                        }
                    }
                }
                pMP->Release();
            }
#endif

            hr = pMC->Run();
            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State=Playing;
                return;
            }
        }

        // Inform the user that an error occurred
        AfxMessageBox(IDS_CANT_PLAY);

    }
}

void CPlayerDoc::OnMediaPause()
{
if( CanPause() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to pause and release the interface
            hr = pMC->Pause();
            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State = Paused;
                return;
            }
        }

        // Inform the user that an error occurred
        AfxMessageBox(IDS_CANT_PAUSE);
    }

}

// stop the graph without rewinding
void CPlayerDoc::OnAbortStop()
{
if( CanStop() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);

        if( SUCCEEDED(hr) ){
            // Ask the filter graph to stop and release the interface
            hr = pMC->Stop();
#ifdef FROM_START
            // if we want always to play from the beginning
            // then we should seek back to the start here
            // (on app or user stop request, and also after EC_COMPLETE).
            IMediaPosition * pMP;
            hr = m_pGraph->QueryInterface(IID_IMediaPosition, (void**) &pMP);
            if (SUCCEEDED(hr)) {
                pMP->put_CurrentPosition(0);
                pMP->Release();
            }

            // no visible rewind or we will re-show the window!

#endif

            pMC->Release();

            if( SUCCEEDED(hr) ){
                m_State = Stopped;
                return;
            }
        }

        // Inform the user that an error occurred
        AfxMessageBox(IDS_CANT_STOP);
}
}

// There are two different ways to stop a graph. We can stop and then when we
// are later paused or run continue from the same position. Alternatively the
// graph can be set back to the start of the media when it is stopped to have
// a more CDPLAYER style interface. These are both offered here conditionally
// compiled using the FROM_START definition. The main difference is that in
// the latter case we put the current position to zero while we change states

void CPlayerDoc::OnMediaStop()
{
    if( CanStop() ){
        HRESULThr;
        IMediaControl *pMC;

        // Obtain the interface to our filter graph
        hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);
        if( SUCCEEDED(hr) ){

#ifdef FROM_START
            IMediaPosition * pMP;
            OAFilterState state;

            // Ask the filter graph to pause
            hr = pMC->Pause();

            // if we want always to play from the beginning
            // then we should seek back to the start here
            // (on app or user stop request, and also after EC_COMPLETE).
            hr = m_pGraph->QueryInterface(IID_IMediaPosition,
                                          (void**) &pMP);
            if (SUCCEEDED(hr)) {
                pMP->put_CurrentPosition(0);
                pMP->Release();
            }

            // wait for pause to complete
            pMC->GetState(INFINITE, &state);
#endif
            // now really do the stop
            pMC->Stop();
            pMC->Release();
            m_State = Stopped;
            return;
        }
        // Inform the user that an error occurred
        AfxMessageBox(IDS_CANT_STOP);
    }
}

BOOL CPlayerDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
    WCHARwPath[MAX_PATH];

    DeleteContents();

    if ( !CreateFilterGraph() ) {
        AfxMessageBox(IDS_CANT_INIT_QUARTZ);
        return FALSE;
    }

    MultiByteToWideChar( CP_ACP, 0, lpszPathName,
                         -1, wPath, MAX_PATH );

    if (FAILED( m_pGraph->RenderFile(wPath, NULL) )) {
        AfxMessageBox(IDS_CANT_RENDER_FILE);
        return FALSE;
    }

    m_State = Stopped;

    return TRUE;
}

//
// If the event handle is valid, ask the graph
// if anything has happened. eg the graph has stopped...
void CPlayerDoc::OnGraphNotify(void) {
    IMediaEvent *pME;
    long lEventCode, lParam1, lParam2;

    ASSERT( m_hGraphNotifyEvent != NULL );

    if( SUCCEEDED(m_pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME))) {
        if( SUCCEEDED(pME->GetEvent(&lEventCode, &lParam1, &lParam2, 0))) {

            // if this is a normal stop, then we do a rewind as
            // we would if the user pressed stop. For an abort,
            // we don't touch this.
            if (lEventCode == EC_COMPLETE) {
                OnMediaStop();
            } else if ((lEventCode == EC_ERRORABORT) ||
                       (lEventCode == EC_USERABORT)) {

                // put the graph into stop mode but don't mess with it.
                OnAbortStop();
            }
        }
    pME->Release();
}

    // The toolbar will only be updated after a message has been received,
    // so send a dummy message
    PostMessage( AfxGetMainWnd()->m_hWnd, WM_WAKEUP, 0, 0 );

}


MFCDOC.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// playdoc.h : interface of the CPlayerDoc class
//
/////////////////////////////////////////////////////////////////////////////

class CPlayerDoc : public CDocument
{
protected: // create from serialization only
CPlayerDoc();
DECLARE_DYNCREATE(CPlayerDoc)

    BOOL CreateFilterGraph( void );
    void DeleteContents( void );

// Event handles
protected:
    HANDLE m_hGraphNotifyEvent;
public:
    HANDLE GetGraphEventHandle( ) { return m_hGraphNotifyEvent; };
    void OnGraphNotify();

// Attributes
public:
    enum {Uninitialized, Stopped, Paused, Playing } m_State;

    IGraphBuilder *m_pGraph;

    BOOL CanPlay(){ return m_State==Stopped || m_State==Paused; };
    BOOL CanStop(){ return m_State==Playing || m_State==Paused; };
    BOOL CanPause(){return m_State==Playing || m_State==Stopped; };
    BOOL IsInitialized(){ return m_State!=Uninitialized; }

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPlayerDoc)
public:
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CPlayerDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:

        // just stop the graph (if aborting). Normal stop will
        // rewind
        void OnAbortStop();

//{{AFX_MSG(CPlayerDoc)
afx_msg void OnUpdateMediaPlay(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaPause(CCmdUI* pCmdUI);
afx_msg void OnUpdateMediaStop(CCmdUI* pCmdUI);
afx_msg void OnMediaPlay();
afx_msg void OnMediaPause();
afx_msg void OnMediaStop();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////


MFCPLAY.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// player.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "mfcplay.h"

#include "mainfrm.h"
#include "mfcdoc.h"
#include "mfcvw.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPlayerApp

BEGIN_MESSAGE_MAP(CPlayerApp, CWinApp)
//{{AFX_MSG_MAP(CPlayerApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPlayerApp construction

CPlayerApp::CPlayerApp()
{
// Place all significant initialization in InitInstance

    m_pPlayerDoc = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CPlayerApp object

CPlayerApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CPlayerApp initialization

BOOL CPlayerApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

Enable3dControls();

LoadStdProfileSettings( 0 );  // Load standard INI file options (including MRU)

    // Initialize the quartz library
    CoInitialize(NULL);

// Register the application's document templates.  Document templates
//  serve as the connection between documents, frame windows and views.

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CPlayerDoc),
RUNTIME_CLASS(CMainFrame),       // main SDI frame window
RUNTIME_CLASS(CPlayerView));
AddDocTemplate(pDocTemplate);

// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes();

// simple command line parsing
if (m_lpCmdLine[0] == '\0')
{
// create a new (empty) document
OnFileNew();
}
else
{
// open an existing document
if(!OpenDocumentFile(m_lpCmdLine))
                    return FALSE;
}

// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();

return TRUE;
}

int CPlayerApp::ExitInstance( )
{
    CoUninitialize();

    return CWinApp::ExitInstance();
}

void CPlayerApp::OnDocumentCreated( CPlayerDoc *pPlayerDoc )
{
    // Single documents only
    // If you want to convert to an MDI you will need to hold a list
    // of all created documents and change CPlayerApp::Run to build
    // up a list of event handles to wait for.
    ASSERT( m_pPlayerDoc == NULL );
    m_pPlayerDoc = pPlayerDoc;

}

void CPlayerApp::OnDocumentDestroyed( CPlayerDoc *pPlayerDoc )
{
    // Single documents only
    ASSERT( m_pPlayerDoc == pPlayerDoc );
    m_pPlayerDoc = NULL;

}

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// Implementation
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CPlayerApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}

int CPlayerApp::Run()
{   // Overridden to check for Graph events as well as messages

if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CPlayerApp::Run - quitting application.\n");
AfxPostQuitMessage(0);
}

    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
    HANDLE  ahObjects[1];               // handles that need to be waited on
    const int cObjects = 1;             // no of objects that we are waiting on

    // message loop lasts until we get a WM_QUIT message
    // upon which we shall return from the function
    while (TRUE) {

        // If we don't have an event handle then process idle
        // routines until a message arrives or until the idle routines
        // stop (when we block until a message arrives). The graph event
        // handle can only be created in response to a message
        if( (ahObjects[ 0 ] = m_pPlayerDoc->GetGraphEventHandle()) == NULL ){
    while (    bIdle
            && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
    {
    // call OnIdle while in bIdle state
    if (!OnIdle(lIdleCount++)){
    bIdle = FALSE;
                    WaitMessage();
                }
    }
        } else {
            // wait for any message sent or posted to this queue
            // or for a graph notification. If there is no message or event
            // and we are idling then we process the idle time routines
            DWORD result;

            result = MsgWaitForMultipleObjects( cObjects
                                              , ahObjects
                                              , FALSE
                                              , (bIdle ? 0 : INFINITE)
                                              , QS_ALLINPUT
                                              );
            if( result != (WAIT_OBJECT_0 + cObjects) ){
                // not a message...

                if( result == WAIT_OBJECT_0 )
                    m_pPlayerDoc->OnGraphNotify();

                else if( result == WAIT_TIMEOUT )
                    if(!OnIdle(lIdleCount++))
                        bIdle = FALSE;

                continue;
            }
        }

        // When here, we either have a message or no event handle
        // has been created yet.

        // read all of the messages in this next loop
        // removing each message as we read it
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();

// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}

} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));

    } // end of the always while-loop

}
/////////////////////////////////////////////////////////////////////////////
// CPlayerApp commands


MFCPLAY.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// player.h : main header file for the PLAYER application
//

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols

// Message to wake up our application
#define WM_WAKEUP WM_USER

/////////////////////////////////////////////////////////////////////////////
// CPlayerApp:
// See player.cpp for the implementation of this class
//

class CPlayerDoc;

class CPlayerApp : public CWinApp
{
public:
CPlayerApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPlayerApp)
public:
virtual BOOL InitInstance();
        virtual int ExitInstance();
    virtual int Run();
//}}AFX_VIRTUAL

// Notify when document created / destroyed
public:
    void OnDocumentCreated( CPlayerDoc *pPlayerDoc );
    void OnDocumentDestroyed( CPlayerDoc *pPlayerDoc );

// Point to our single document
protected:
    CPlayerDoc *m_pPlayerDoc;

public:
// Implementation

//{{AFX_MSG(CPlayerApp)
afx_msg void OnAppAbout();
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


MFCVW.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// playvw.cpp : implementation of the CPlayerView class
//

#include "stdafx.h"
#include "mfcplay.h"

#include "mfcdoc.h"
#include "mfcvw.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPlayerView

IMPLEMENT_DYNCREATE(CPlayerView, CView)

BEGIN_MESSAGE_MAP(CPlayerView, CView)
//{{AFX_MSG_MAP(CPlayerView)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPlayerView construction/destruction

CPlayerView::CPlayerView()
{
// TODO: add construction code here

}

CPlayerView::~CPlayerView()
{
}

/////////////////////////////////////////////////////////////////////////////
// CPlayerView drawing

void CPlayerView::OnDraw(CDC* pDC)
{
CPlayerDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

// TODO: add draw code for native data here
}

/////////////////////////////////////////////////////////////////////////////
// CPlayerView diagnostics

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

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

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

/////////////////////////////////////////////////////////////////////////////
// CPlayerView message handlers


MFCVW.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// playvw.h : interface of the CPlayerView class
//
/////////////////////////////////////////////////////////////////////////////

class CPlayerView : public CView
{
protected: // create from serialization only
CPlayerView();
DECLARE_DYNCREATE(CPlayerView)

// Attributes
public:
CPlayerDoc* GetDocument();

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CPlayerView)
public:
virtual void OnDraw(CDC* pDC);  // overridden to draw this view
protected:
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CPlayerView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
//{{AFX_MSG(CPlayerView)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in playvw.cpp
inline CPlayerDoc* CPlayerView::GetDocument()
   { return (CPlayerDoc*)m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by player.rc
//
#define IDD_ABOUTBOX                    100
#define IDR_MAINFRAME                   128
#define IDR_PLAYERTYPE                  129
#define ID_MEDIA_PLAY                   32771
#define ID_MEDIA_PAUSE                  32772
#define ID_MEDIA_STOP                   32773
#define IDS_CANT_INIT_QUARTZ            61204
#define IDS_CANT_RENDER_FILE            61205
#define IDS_CANT_PLAY                   61206
#define IDS_CANT_PAUSE                  61207
#define IDS_CANT_STOP                   61208

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        131
#define _APS_NEXT_COMMAND_VALUE         32774
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STDAFX.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// stdafx.cpp : source file that includes just the standard includes
//player.pch will be the pre-compiled header
//stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"


STDAFX.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#define _AFX_NO_BSTR_SUPPORT
#include <afxwin.h>// MFC core and standard components
#include <afxext.h> // MFC extensions (including VB)
#include <objbase.h>
#include <afxtempl.h>
#include <strmif.h>
#include <control.h>
#include <evcode.h>
#include <uuids.h>


MPEGPROP.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include <mmsystem.h>
  #include <streams.h>
  #include "mpegprop.h"

  #define APPLICATIONNAME "MPEGProp"
  #define CLASSNAME "MPEGProp"

  #define WM_GRAPHNOTIFY  WM_USER+13

  #define HELPER_RELEASE(x) { if (x) x->Release(); x = NULL; }

  HWND      ghApp;
  HINSTANCE ghInst;

  HRESULT   hr;
  LONG      evCode;
  LONG      evParam1;
  LONG      evParam2;

  // Collection of interfaces
  IBaseFilter   *pif   = NULL;
  IGraphBuilder *pigb  = NULL;
  IMediaControl *pimc  = NULL;
  IMediaEventEx *pimex = NULL;
  IVideoWindow  *pivw  = NULL;
  IFilterGraph  *pifg  = NULL;

  void MPEGProp (LPSTR szFile)

    { // MPEGProp //

      WCHAR wFile[MAX_PATH];
      MultiByteToWideChar( CP_ACP, 0, szFile, -1, wFile, MAX_PATH );

      hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pigb);

      if (SUCCEEDED(hr))

      { // Graphbuilder instance

        // QueryInterface for some basic interfaces
        pigb->QueryInterface(IID_IMediaControl, (void **)&pimc);
        pigb->QueryInterface(IID_IMediaEventEx, (void **)&pimex);
        pigb->QueryInterface(IID_IVideoWindow, (void **)&pivw);

        // Have the graph construct its the appropriate graph automatically
        hr = pigb->RenderFile(wFile, NULL);

        // Have the graph signal event via window callbacks for performance
        pimex->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0);

        pigb->QueryInterface(IID_IFilterGraph, (void **)&pifg);

        // The easiest way to find the single MPEG video codec
        // in the graph is via IBaseFilter::FindFilterByName.
        // Multiple instances of the same filter make things
        // a bit more challenging...
        IBaseFilter           *pifPP = NULL;
        ISpecifyPropertyPages *pispp = NULL;

        if (SUCCEEDED(pifg->FindFilterByName(L"MPEG Video Decoder", &pifPP)))

          { // Found MPEG video decoder.

            pifPP->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pispp);

            // Declare the counted array of GUIDs for the property page
            CAUUID caGUID;

            pispp->GetPages(&caGUID);
            HELPER_RELEASE(pispp);

            // Display the default MPEG Video Decoder filter's property page
            OleCreatePropertyFrame(NULL,
                0,
                0,
                L"Filter",
                1,
                (IUnknown **)&pifPP,
                0,
                NULL,
                0,
                0,
                NULL);

          } // Found MPEG video decoder.

        else MessageBox(ghApp, "MPEG video decoder not found in the graph!", APPLICATIONNAME, MB_ICONHAND);

        HELPER_RELEASE(pifPP);

        // Run the graph if RenderFile succeeded
        if (SUCCEEDED(hr))
          pimc->Run();

      } // Graphbuilder instance

    } // MPEGProp //

  // ---------------------------------------------------------------------------
  // End of multimedia specific code.
  // ---------------------------------------------------------------------------

  BOOL GetClipFileName(LPSTR szName)

    { // GetClipFileName //

      OPENFILENAME ofn;

      ofn.lStructSize       = sizeof(OPENFILENAME);
      ofn.hwndOwner         = ghApp;
      ofn.lpstrFilter       = NULL;
      ofn.lpstrFilter       = "MPEG files (*.mpg; *.mpeg; *.mpe)\0*.mpg; *.mpeg; *.mpe\0\0";
      ofn.lpstrCustomFilter = NULL;
      ofn.nFilterIndex      = 1;
      *szName = 0;
      ofn.lpstrFile         = szName;
      ofn.nMaxFile          = MAX_PATH;
      ofn.lpstrInitialDir   = NULL;
      ofn.lpstrTitle        = NULL;
      ofn.lpstrFileTitle    = NULL;
      ofn.lpstrDefExt       = "MPG";
      ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

      return GetOpenFileName((LPOPENFILENAME)&ofn);

    } // GetClipFileName //

  LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    { // WndMainProc //

      switch(message)

        { // Window msgs handling

          case WM_COMMAND:

            switch(wParam)

              { // Menus

                case ID_FILE_OPENCLIP:

                  TCHAR szFilename[MAX_PATH];

                  if (GetClipFileName(szFilename))
                    MPEGProp(szFilename);

                  break;

                case ID_FILE_EXIT:

                  if (pimc)
                    pimc->Stop();

                  if (pivw)
                    pivw->put_Visible(OAFALSE);

                  HELPER_RELEASE(pivw);
                  HELPER_RELEASE(pif);
                  HELPER_RELEASE(pigb);
                  HELPER_RELEASE(pimc);
                  HELPER_RELEASE(pimex);

                  PostQuitMessage(0);

                  break;

              } // Menus

            break;

          case WM_GRAPHNOTIFY:

            if (NULL == pimex)  // Trap potential NULL pointer fault
              break;

            while (SUCCEEDED(pimex->GetEvent(&evCode, &evParam1, &evParam2, 0)))

              { // Spin through the events

                hr = pimex->FreeEventParams(evCode, evParam1, evParam2);

                if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode))

                  { // Finished

                    pimc->Stop();
                    pivw->put_Visible(OAFALSE);

                    HELPER_RELEASE(pivw);
                    HELPER_RELEASE(pif);
                    HELPER_RELEASE(pigb);
                    HELPER_RELEASE(pimc);
                    HELPER_RELEASE(pimex);

                    break;

                  } // Finished

              } // Spin through the events

            break;

          case WM_DESTROY:
            PostQuitMessage(0);
            break;

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

      } // Window msgs handling

      return FALSE;

    } // WndMainProc //

  int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

    { // WinMain //

      MSG msg;
      WNDCLASS wc;

      // OLE subsystem requires applications to initialize things first!
      CoInitialize(NULL);

      ZeroMemory(&wc, sizeof wc);
      wc.lpfnWndProc = WndMainProc;
      ghInst = wc.hInstance = hInstC;
      wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
      wc.lpszClassName = CLASSNAME;
      wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_MPEGPROP));
      RegisterClass(&wc);

      ghApp = CreateWindow(CLASSNAME,
        APPLICATIONNAME,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        0,
        0,
        ghInst,
        0);

      ShowWindow(ghApp, SW_NORMAL);
      UpdateWindow(ghApp);

      while (GetMessage(&msg,NULL,0,0))

        {  // Message loop

           TranslateMessage(&msg);
           DispatchMessage(&msg);

        }  // Message loop

      // Finished with OLE subsystem
      CoUninitialize();

      return msg.wParam;

    } // WinMain //


MPEGPROP.H

#define IDR_MENU                        101
#define ID_FILE_OPENCLIP                40001
#define ID_FILE_EXIT                    40002
#define IDI_MPEGPROP                    100


PLAYFILE.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include <mmsystem.h>
  #include <streams.h>
  #include "playfile.h"

  #define APPLICATIONNAME "PlayFile"
  #define CLASSNAME "PlayFile"

  #define WM_GRAPHNOTIFY  WM_USER+13

  #define HELPER_RELEASE(x) { if (x) x->Release(); x = NULL; }

  HWND      ghApp;
  HINSTANCE ghInst;

  HRESULT   hr;
  LONG      evCode;
  LONG      evParam1;
  LONG      evParam2;

  // Collection of interfaces
  IBaseFilter   *pif   = NULL;
  IGraphBuilder *pigb  = NULL;
  IMediaControl *pimc  = NULL;
  IMediaEventEx *pimex = NULL;
  IVideoWindow  *pivw  = NULL;

  void PlayFile (LPSTR szFile)

    { // PlayFile //

      WCHAR wFile[MAX_PATH];
      MultiByteToWideChar( CP_ACP, 0, szFile, -1, wFile, MAX_PATH );

      hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pigb);

      if (SUCCEEDED(hr))

      { // Graphbuilder instance

        // QueryInterface for some basic interfaces
        pigb->QueryInterface(IID_IMediaControl, (void **)&pimc);
        pigb->QueryInterface(IID_IMediaEventEx, (void **)&pimex);
        pigb->QueryInterface(IID_IVideoWindow, (void **)&pivw);

        // Have the graph construct its the appropriate graph automatically
        hr = pigb->RenderFile(wFile, NULL);

        // Have the graph signal event via window callbacks for performance
        pimex->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0);

        // Run the graph if RenderFile succeeded
        if (SUCCEEDED(hr))
          pimc->Run();

      } // Graphbuilder instance

    } // PlayFile //

  // ---------------------------------------------------------------------------
  // End of multimedia specific code.
  // ---------------------------------------------------------------------------

  BOOL GetClipFileName(LPSTR szName)

    { // GetClipFileName //

      OPENFILENAME ofn;

      ofn.lStructSize       = sizeof(OPENFILENAME);
      ofn.hwndOwner         = ghApp;
      ofn.lpstrFilter       = NULL;
      ofn.lpstrFilter       = "Video files (*.mpg; *.mpeg; *.avi; *.mov; *.qt)\0*.mpg; *.mpeg; *.avi; *.mov; *.qt\0\0";
      ofn.lpstrCustomFilter = NULL;
      ofn.nFilterIndex      = 1;
      *szName = 0;
      ofn.lpstrFile         = szName;
      ofn.nMaxFile          = MAX_PATH;
      ofn.lpstrInitialDir   = NULL;
      ofn.lpstrTitle        = NULL;
      ofn.lpstrFileTitle    = NULL;
      ofn.lpstrDefExt       = "MPG";
      ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

      return GetOpenFileName((LPOPENFILENAME)&ofn);

    } // GetClipFileName //

  LRESULT CALLBACK WndMainProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    { // WndMainProc //

      switch(message)

        { // Window msgs handling

          case WM_COMMAND:

            switch(wParam)

              { // Menus

                case ID_FILE_OPENCLIP:

                  TCHAR szFilename[MAX_PATH];

                  if (GetClipFileName(szFilename))
                    PlayFile(szFilename);

                  break;

                case ID_FILE_EXIT:

                  if (pimc)
                    pimc->Stop();

                  if (pivw)
                    pivw->put_Visible(OAFALSE);

                  HELPER_RELEASE(pivw);
                  HELPER_RELEASE(pif);
                  HELPER_RELEASE(pigb);
                  HELPER_RELEASE(pimc);
                  HELPER_RELEASE(pimex);

                  PostQuitMessage(0);

                  break;

              } // Menus

            break;

          case WM_GRAPHNOTIFY:

            while (SUCCEEDED(pimex->GetEvent(&evCode, &evParam1, &evParam2, 0)))

              { // Spin through the events

                hr = pimex->FreeEventParams(evCode, evParam1, evParam2);

                if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode))

                  { // Finished

                    pivw->put_Visible(OAFALSE);

                    HELPER_RELEASE(pivw);
                    HELPER_RELEASE(pif);
                    HELPER_RELEASE(pigb);
                    HELPER_RELEASE(pimc);
                    HELPER_RELEASE(pimex);

                    break;

                  } // Finished

              } // Spin through the events

            break;

          case WM_DESTROY:
            PostQuitMessage(0);
            break;

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

      } // Window msgs handling

      return FALSE;

    } // WndMainProc //

  int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)

    { // WinMain //

      MSG msg;
      WNDCLASS wc;

      // OLE subsystem requires applications to initialize things first!
      CoInitialize(NULL);

      ZeroMemory(&wc, sizeof wc);
      wc.lpfnWndProc = WndMainProc;
      ghInst = wc.hInstance = hInstC;
      wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
      wc.lpszClassName = CLASSNAME;
      wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_PLAYFILE));
      RegisterClass(&wc);

      ghApp = CreateWindow(CLASSNAME,
        APPLICATIONNAME,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        0,
        0,
        ghInst,
        0);

      ShowWindow(ghApp, SW_NORMAL);
      UpdateWindow(ghApp);

      while (GetMessage(&msg,NULL,0,0))

        {  // Message loop

           TranslateMessage(&msg);
           DispatchMessage(&msg);

        }  // Message loop

      // Finished with OLE subsystem
      CoUninitialize();

      return msg.wParam;

    } // WinMain //


PLAYFILE.H

#define IDR_MENU                        101
#define ID_FILE_OPENCLIP                40001
#define ID_FILE_EXIT                    40002
#define IDI_PLAYFILE                    100


PLAYIT.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//   Trivial player console application
//
//   Function:
//       Plays a movie file with options for
//       --  Playing full screen
//       --  Playing on top of normal windows
//       --  Delaying start time
//       Exits when movie playback is complete
//       NO user interface
//
//   Usage: playit [-f] [-t] [-delay nsecs] foo.bar\n

#include <windows.h>
#include <strmif.h>
#include <uuids.h>
#include <control.h>
#include <stdio.h>

BOOL bMax = FALSE;
BOOL bOnTop = FALSE;
DWORD dwDelay = 0;

HRESULT PlayMovie(LPSTR lpszMovie)
{
    int iLen = MultiByteToWideChar(
                      CP_ACP,
                      MB_PRECOMPOSED,
                      lpszMovie,
                      -1,
                      NULL,
                      0);
    LPWSTR lpwszMovie = new WCHAR[iLen + 1];
    MultiByteToWideChar(
        CP_ACP,
        MB_PRECOMPOSED,
        lpszMovie,
        -1,
        lpwszMovie,
        iLen + 1);

    HRESULT hr = S_OK;
    IMediaControl *pMC = NULL;
    hr = CoCreateInstance(CLSID_FilterGraph,
                          NULL,
                          CLSCTX_INPROC,
                          IID_IMediaControl,
                          (void **)&pMC);

    IVideoWindow *pVW = NULL;
    IGraphBuilder *pGB = NULL;
    IMediaEventEx *pME;
    if (SUCCEEDED(hr)) {
        hr = pMC->QueryInterface(IID_IVideoWindow, (void **)&pVW);
    }

    if (SUCCEEDED(hr)) {
        hr = pMC->QueryInterface(IID_IGraphBuilder, (void **)&pGB);
    }
    if (SUCCEEDED(hr)) {
        hr = pMC->QueryInterface(IID_IMediaEventEx, (void **)&pME);
    }

    if (SUCCEEDED(hr)) {
        pVW->put_Visible(0);
        pVW->put_AutoShow(0);
        hr = pGB->RenderFile(lpwszMovie, NULL);
        Sleep(dwDelay * 1000);
    }

    if (bMax) {
        if (SUCCEEDED(hr)) {
            pVW->put_FullScreenMode(-1);
        }
    }

    if (bOnTop) {
        long ExStyle;
        if (SUCCEEDED(pVW->get_WindowStyleEx(&ExStyle))) {
            pVW->put_WindowStyleEx(ExStyle | WS_EX_TOPMOST);
        }
    }

    if (SUCCEEDED(hr)) {
        pMC->Pause();
        pVW->put_Visible(-1);
        hr = pMC->Run();
    }

    if (SUCCEEDED(hr)) {
        long evCode;
        hr = pME->WaitForCompletion(INFINITE, &evCode);
    }

    if (pMC) {
        pMC->Release();
    }
    if (pVW) {
        pVW->Release();
    }
    if (pGB) {
        pGB->Release();
    }
    if (pME) {
        pME->Release();
    }
    return hr;
}

int _cdecl main(int argc, char *argv[])
{
    argv++;
    while (argc > 2) {
        if (0 == lstrcmpi(argv[0], "-t")) {
            bOnTop = TRUE;
            argc--;
            argv++;
        } else
        if (0 == lstrcmpi(argv[0], "-f")) {
            bMax = TRUE;
            argc--;
            argv++;
        } else
        if (argc > 3 && 0 == lstrcmpi(argv[0], "-delay")) {
            dwDelay = atoi(argv[1]);
            argc -= 2;
            argv += 2;
        } else {
            break;
        }
    }
    if (argc != 2) {
        printf("Usage playit [-f] [-t] [-delay nsecs] foo.bar\n");
        exit(0);
    }
    CoInitialize(NULL);
    PlayMovie(argv[0]);
    CoUninitialize();
    return 1;
}


SAMPVID.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <vidprop.h>
#include <initguid.h>
#include <sampvid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif

//
//
// What this sample illustrates
//
// A simple video renderer that draws video into a text shaped window on
// Windows NT and a simple popup window on Windows 95. Shows to how use
// the base video renderer classes from the ActiveMovie SDK. Implements
// a property page to allow users to find out quality management details.
//
//
// Summary
//
// This is a sample ActiveMovie video renderer - the filter is based on the
// CBaseVideoRenderer base class. The base class handles all the seeking,
// synchronisation and quality management necessary for video renderers. In
// particular we override the DoRenderSample and PrepareRender methods so
// that we can draw images and realize palettes as necessary in Windows.
//
//
// Implementation
//
// The original idea was that the renderer would create a custom window that
// spelt out ActiveX, in the letters the video would be displayed. To create
// a window with a non rectangular clip region like this meant using paths.
// Unfortunately these are only truly supported on WindowsNT so for Win95 we
// create a simple popup window (ie no system menu nor accellerator boxes).
//
// The renderer supports both IBasicVideo and IVideoWindow that is achieved
// fairly simply by inheriting our renderer from the CBaseControlVideo and
// CBaseControlWindow base classes. To fully implement these interfaces we
// must then override and implement some more PURE virtual methods such as
// GetVideoFormat and Get/SetSourceRect (which all live in VIDEOTXT.CPP).
//
// Because we are either a simple popup window or a text shaped window we may
// not have a title bar for the user to grab to move the window around. So we
// handle WM_NCHITTEST messages (with HTCLIENT) to effectively enable window
// dragging by clicking on the video client area.
//
// We make heavy use of other base classes, notably the CImageAllocator which
// provides buffers that are really DIBSECTIONs. This enables faster drawing
// of video (and is the same trick pulled by the real runtime renderer). We
// also use CImageDisplay to match up optimal drawing formats and for video
// type checking, CImagePalette for general palette creation and handling and
// last but not least CDrawImage that can be used for general video drawing.
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Video Renderer"
// This is the filter we will be replacing with the sample video renderer.
// Then click on the box and hit DELETE. After that go to the Graph menu and
// select "Insert Filters", from the dialog box find and select the "Sample
// Renderer" and then dismiss the dialog. Back in the graph layout find the
// output pin of the filter that was connected to the input of the video
// renderer you just deleted, right click and select "Render". You should
// see it being connected to the input pin of the renderer you just inserted
//
// Click Pause and Run on the GRAPHEDT frame and you will see the video...
//
//
// Files
//
// sampvid.cpp          Main implementation of the video renderer
// sampvid.def          What APIs the DLL will import and export
// sampvid.h            Class definition of the derived renderer
// sampvid.rc           Dialog box template for our property page
// sampvid.reg          What goes in the registry to make us work
// videotxt.cpp         The code to look after a video window
// vidprop.cpp          The implementation of the property page
// vidprop.h            The class definition for the property page
// makefile             How to build it...
//
//
// Base classes used
//
// CImageAllocator      A DIBSECTION video image allocator
// CVideoInputPin       IPin and IMemInputPin interfaces
// CImageDisplay        Manages the video display type
// CMediaType           Source connection media type
// CVideoText           Does the actual video rendering
// CImagePalette        Looks after managing a palette
// CDrawImage           Does the actual image drawing
//
//

// Setup data

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Video,           // Major type
    &MEDIASUBTYPE_NULL          // Minor type
};

const AMOVIESETUP_PIN sudPins =
{
    L"Input",                   // Name of the pin
    FALSE,                      // Is pin rendered
    FALSE,                      // Is an output pin
    FALSE,                      // Ok for no pins
    FALSE,                      // Allowed many
    &CLSID_NULL,                // Connects to filter
    L"Output",                  // Connects to pin
    1,                          // Number of pin types
    &sudPinTypes                // Details for pins
};

const AMOVIESETUP_FILTER sudSampVid =
{
    &CLSID_SampleRenderer,      // Filter CLSID
    L"Sample Video Renderer",   // Filter name
    MERIT_DO_NOT_USE,           // Filter merit
    1,                          // Number pins
    &sudPins                    // Pin details
};

// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance

CFactoryTemplate g_Templates[] = {
    { L"Sample Video Renderer"
    , &CLSID_SampleRenderer
    , CVideoRenderer::CreateInstance
    , NULL
    , &sudSampVid }
  ,
    { L"Quality Property Page"
    , &CLSID_SampleQuality
    , CQualityProperties::CreateInstance }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// CreateInstance
//
// This goes in the factory template table to create new filter instances
//
CUnknown * WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    return new CVideoRenderer(NAME("Sample Video Renderer"),pUnk,phr);

} // CreateInstance

#pragma warning(disable:4355)

//
// Constructor
//
CVideoRenderer::CVideoRenderer(TCHAR *pName,
                               LPUNKNOWN pUnk,
                               HRESULT *phr) :

    CBaseVideoRenderer(CLSID_SampleRenderer,pName,pUnk,phr),
    m_InputPin(NAME("Video Pin"),this,&m_InterfaceLock,phr,L"Input"),
    m_ImageAllocator(this,NAME("Sample video allocator"),phr),
    m_VideoText(NAME("Video properties"),GetOwner(),phr,&m_InterfaceLock,this),
    m_ImagePalette(this,&m_VideoText,&m_DrawImage),
    m_DrawImage(&m_VideoText)
{
    // Store the video input pin
    m_pInputPin = &m_InputPin;

    // Reset the current video size

    m_VideoSize.cx = 0;
    m_VideoSize.cy = 0;

    // Initialise the window and control interfaces

    m_VideoText.SetControlVideoPin(&m_InputPin);
    m_VideoText.SetControlWindowPin(&m_InputPin);

} // (Constructor)

//
// Destructor
//
CVideoRenderer::~CVideoRenderer()
{
    m_pInputPin = NULL;

} // (Destructor)

//
// CheckMediaType
//
// Check the proposed video media type
//
HRESULT CVideoRenderer::CheckMediaType(const CMediaType *pmtIn)
{
    return m_Display.CheckMediaType(pmtIn);

} // CheckMediaType

//
// GetPin
//
// We only support one input pin and it is numbered zero
//
CBasePin *CVideoRenderer::GetPin(int n)
{
    ASSERT(n == 0);
    if (n != 0) {
        return NULL;
    }

    // Assign the input pin if not already done so

    if (m_pInputPin == NULL) {
        m_pInputPin = &m_InputPin;
    }
    return m_pInputPin;

} // GetPin

//
// NonDelegatingQueryInterface
//
// Overriden to say what interfaces we support and where
//
STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *)this, ppv);
    } else if (riid == IID_IVideoWindow) {
        return m_VideoText.NonDelegatingQueryInterface(riid,ppv);
    } else if (riid == IID_IBasicVideo) {
        return m_VideoText.NonDelegatingQueryInterface(riid,ppv);
    }
    return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv);

} // NonDelegatingQueryInterface

//
// GetPages
//
// Return the CLSIDs for the property pages we support
//
STDMETHODIMP CVideoRenderer::GetPages(CAUUID *pPages)
{
    CheckPointer(pPages,E_POINTER);

    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }

    pPages->pElems[0] = CLSID_SampleQuality;
    return NOERROR;

} // GetPages

//
// DoRenderSample
//
// Have the drawing object render the current image
//
HRESULT CVideoRenderer::DoRenderSample(IMediaSample *pMediaSample)
{
    return m_DrawImage.DrawImage(pMediaSample);

} // DoRenderSample

//
// PrepareRender
//
// Overriden to realise the palette before drawing. We used to have to realise
// the palette on every frame because we could never be sure of receiving top
// level messages like WM_PALETTECHANGED. However the plug in distributor will
// now make sure we get these but we still have to do this because otherwise
// we may not find the palette being realised before the thread comes to draw

void CVideoRenderer::PrepareRender()
{
    // Realise the palette on this thread
    m_VideoText.DoRealisePalette();
} // PrepareRender

//
// Active
//
// The auto show flag is used to have the window shown automatically when we
// change state. We do this only when moving to paused or running, when there
// is no outstanding EC_USERABORT set and when the window is not already up
// This can be changed through the IVideoWindow interface AutoShow property.
// If the window is not currently visible then we are showing it because of
// a state change to paused or running, in which case there is no point in
// the video window sending an EC_REPAINT as we're getting an image anyway
//
HRESULT CVideoRenderer::Active()
{
    HWND hwnd = m_VideoText.GetWindowHWND();
    NOTE("AutoShowWindow");

    if (m_VideoText.IsAutoShowEnabled() == TRUE) {
        if (m_bAbort == FALSE) {
            if (IsWindowVisible(hwnd) == FALSE) {
                NOTE("Executing AutoShowWindow");
                SetRepaintStatus(FALSE);
                m_VideoText.PerformanceAlignWindow();
                m_VideoText.DoShowWindow(SW_SHOWNORMAL);
                m_VideoText.DoSetWindowForeground(TRUE);
            }
        }
    }
    return CBaseVideoRenderer::Active();

} // Active

//
// SetMediaType
//
// We store a copy of the media type used for the connection in the renderer
// because it is required by many different parts of the running renderer
// This can be called when we come to draw a media sample that has a format
// change with it. We normally delay type changes until they are really due
// for rendering otherwise we will change types too early if the source has
// allocated a queue of samples. In our case this isn't a problem because we
// only ever receive one sample at a time so it's safe to change immediately
//
HRESULT CVideoRenderer::SetMediaType(const CMediaType *pmt)
{
    CAutoLock cInterfaceLock(&m_InterfaceLock);
    CMediaType StoreFormat(m_mtIn);
    HRESULT hr = NOERROR;

    // Fill out the optional fields in the VIDEOINFOHEADER

    m_mtIn = *pmt;
    VIDEOINFO *pVideoInfo = (VIDEOINFO *) m_mtIn.Format();
    m_Display.UpdateFormat(pVideoInfo);

    // We set the new palette before completing so that the method can look
    // at the old RGB colours we used and compare them with the new set, if
    // they're all identical colours we don't need to change the palette

    hr = m_ImagePalette.PreparePalette(&m_mtIn,&StoreFormat,NULL);
    if (FAILED(hr)) {
        return hr;
    }

    // Complete the initialisation

    m_DrawImage.NotifyMediaType(&m_mtIn);
    m_ImageAllocator.NotifyMediaType(&m_mtIn);
    return NOERROR;

} // SetMediaType

//
// BreakConnect
//
// This is called when a connection or an attempted connection is terminated
// and lets us to reset the connection flag held by the base class renderer
// The filter object may be hanging onto an image to use for refreshing the
// video window so that must be freed (the allocator decommit may be waiting
// for that image to return before completing) then we must also uninstall
// any palette we were using, reset anything set with the control interfaces
// then set our overall state back to disconnected ready for the next time

HRESULT CVideoRenderer::BreakConnect()
{
    CAutoLock cInterfaceLock(&m_InterfaceLock);

    // Check we are in a valid state

    HRESULT hr = CBaseVideoRenderer::BreakConnect();
    if (FAILED(hr)) {
        return hr;
    }

    // The window is not used when disconnected
    IPin *pPin = m_InputPin.GetConnected();
    if (pPin) SendNotifyWindow(pPin,NULL);

    // The base class break connect disables us from sending any EC_REPAINT
    // events which is important otherwise when we come down here to remove
    // our palette we end up painting the window again - which in turn sees
    // there is no image to draw and would otherwise send a redundant event

    m_ImagePalette.RemovePalette();
    m_mtIn.ResetFormatBuffer();
    return NOERROR;

} // BreakConnect

//
// CompleteConnect
//
// When we complete connection we need to see if the video has changed sizes
// If it has then we activate the window and reset the source and destination
// rectangles. If the video is the same size then we bomb out early. By doing
// this we make sure that temporary disconnections such as when we go into a
// fullscreen mode do not cause unnecessary property changes. The basic ethos
// is that all properties should be persistent across connections if possible
//
HRESULT CVideoRenderer::CompleteConnect(IPin *pReceivePin)
{
    CAutoLock cInterfaceLock(&m_InterfaceLock);
    CBaseVideoRenderer::CompleteConnect(pReceivePin);
    m_DrawImage.ResetPaletteVersion();

    // Has the video size changed between connections

    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_mtIn.Format();
    if (pVideoInfo->bmiHeader.biWidth == m_VideoSize.cx) {
        if (pVideoInfo->bmiHeader.biHeight == m_VideoSize.cy) {
            return NOERROR;
        }
    }

    // Pass the video window handle upstream
    HWND hwnd = m_VideoText.GetWindowHWND();
    NOTE1("Sending EC_NOTIFY_WINDOW %x",hwnd);
    SendNotifyWindow(pReceivePin,hwnd);

    // Set them for the current video dimensions

    m_DrawImage.SetDrawContext();
    m_VideoSize.cx = pVideoInfo->bmiHeader.biWidth;
    m_VideoSize.cy = pVideoInfo->bmiHeader.biHeight;
    m_VideoText.SetDefaultSourceRect();
    m_VideoText.SetDefaultTargetRect();
    m_VideoText.OnVideoSizeChange();
    m_VideoText.ActivateWindow();

    return NOERROR;

} // CompleteConnect

//
// OnReceiveFirstSample
//
// Use the image just delivered to display a poster frame
//
void CVideoRenderer::OnReceiveFirstSample(IMediaSample *pMediaSample)
{
    DoRenderSample(pMediaSample);

} // OnReceiveFirstSample

// Constructor

CVideoInputPin::CVideoInputPin(TCHAR *pObjectName,
                               CVideoRenderer *pRenderer,
                               CCritSec *pInterfaceLock,
                               HRESULT *phr,
                               LPCWSTR pPinName) :

    CRendererInputPin(pRenderer,phr,pPinName),
    m_pRenderer(pRenderer),
    m_pInterfaceLock(pInterfaceLock)
{
    ASSERT(m_pRenderer);
    ASSERT(pInterfaceLock);

} // (Constructor)

//
// GetAllocator
//
// This overrides the CBaseInputPin virtual method to return our allocator
// we create to pass shared memory DIB buffers that GDI can directly access
// When NotifyAllocator is called it sets the current allocator in the base
// input pin class (m_pAllocator), this is what GetAllocator should return
// unless it is NULL in which case we return the allocator we would like
//
STDMETHODIMP CVideoInputPin::GetAllocator(IMemAllocator **ppAllocator)
{
    CAutoLock cInterfaceLock(m_pInterfaceLock);
    CheckPointer(ppAllocator,E_POINTER);

    // Has an allocator been set yet in the base class

    if (m_pAllocator == NULL) {
        m_pAllocator = &m_pRenderer->m_ImageAllocator;
        m_pAllocator->AddRef();
    }

    m_pAllocator->AddRef();
    *ppAllocator = m_pAllocator;
    return NOERROR;

} // GetAllocator

//
// NotifyAllocator
//
// The COM specification says any two IUnknown pointers to the same object
// should always match which provides a way for us to see if they are using
// our DIB allocator or not. Since we are only really interested in equality
// and our object always hands out the same IMemAllocator interface we can
// just see if the pointers match. If they are we set a flag in the main
// renderer as the window needs to know whether it can do fast rendering
//
STDMETHODIMP
CVideoInputPin::NotifyAllocator(IMemAllocator *pAllocator,BOOL bReadOnly)
{
    CAutoLock cInterfaceLock(m_pInterfaceLock);

    // Make sure the base class gets a look

    HRESULT hr = CBaseInputPin::NotifyAllocator(pAllocator,bReadOnly);
    if (FAILED(hr)) {
        return hr;
    }

    // Whose allocator is the source going to use

    m_pRenderer->m_DrawImage.NotifyAllocator(FALSE);
    if (pAllocator == &m_pRenderer->m_ImageAllocator) {
        m_pRenderer->m_DrawImage.NotifyAllocator(TRUE);
    }
    return NOERROR;

} // NotifyAllocator

//
// DllRegisterSever
//
// Handle the registration of this filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer


SAMPVID.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __SAMPVID__
#define __SAMPVID__

DEFINE_GUID(CLSID_SampleRenderer,
0x4d4b1600, 0x33ac, 0x11cf, 0xbf, 0x30, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a);

DEFINE_GUID(CLSID_SampleQuality,
0xdb76d7f0, 0x97cc, 0x11cf, 0xa0, 0x96, 0x00, 0x80, 0x5f, 0x6c, 0xab, 0x82);

// Forward declarations

class CVideoRenderer;
class CVideoInputPin;
class CControlVideo;
class CVideoText;

// This is the video renderer window it supports IBasicVideo and IVideoWindow
// by inheriting from the CBaseControlWindow and CbaseControlVideo classes.
// Those classes leave a few PURE virtual methods that we have to override to
// complete their implementation such as the handling of source and target
// rectangles. The class also looks after creating the window with a custom
// clip region in the shape of the word ActiveX (only applies to Windows/NT)

class CVideoText : public CBaseControlWindow, public CBaseControlVideo
{
protected:

    CVideoRenderer *m_pRenderer;        // Owning sample renderer object
    SIZE m_Size;                        // Size of the masking bitmap

public:

    CVideoText(TCHAR *pName,                 // Object description
               LPUNKNOWN pUnk,               // Normal COM ownership
               HRESULT *phr,                 // OLE failure code
               CCritSec *pInterfaceLock,     // Main critical section
               CVideoRenderer *pRenderer);   // Delegates locking to

    virtual ~CVideoText();
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid,void **ppv);

    // Pure virtual methods for the IBasicVideo interface

    HRESULT IsDefaultTargetRect();
    HRESULT SetDefaultTargetRect();
    HRESULT SetTargetRect(RECT *pTargetRect);
    HRESULT GetTargetRect(RECT *pTargetRect);
    HRESULT IsDefaultSourceRect();
    HRESULT SetDefaultSourceRect();
    HRESULT SetSourceRect(RECT *pSourceRect);
    HRESULT GetSourceRect(RECT *pSourceRect);
    HRESULT GetStaticImage(long *pBufferSize,long *pDIBImage);

    // Prepare the window with a text region

    void InitRenderer(TCHAR *pStringName);
    HRESULT InitWindowRegion(TCHAR *pStringName);
    HFONT CreateVideoFont();
    RECT GetDefaultRect();
    VIDEOINFOHEADER *GetVideoFormat();

    // Overriden from CBaseWindow return our window and class styles

    LPTSTR GetClassWindowStyles(DWORD *pClassStyles,
                                DWORD *pWindowStyles,
                                DWORD *pWindowStylesEx);

    // Method that gets all the window messages

    LRESULT OnReceiveMessage(HWND hwnd,          // Window handle
                             UINT uMsg,          // Message ID
                             WPARAM wParam,      // First parameter
                             LPARAM lParam);     // Other parameter
}; // CVideoText

// This class supports the renderer input pin. We have to override the base
// class input pin because we provide our own special allocator which hands
// out buffers based on GDI DIBSECTIONs. We have an extra limitation which
// is that we only connect to filters that agree to use our allocator. This
// stops us from connecting to the tee for example. The extra work required
// to use someone elses allocator and select the buffer into a bitmap and
// that into the HDC is not great but would only really confuse this sample

class CVideoInputPin : public CRendererInputPin
{
    CVideoRenderer *m_pRenderer;        // The renderer that owns us
    CCritSec *m_pInterfaceLock;         // Main filter critical section

public:

    // Constructor

    CVideoInputPin(
        TCHAR *pObjectName,             // Object string description
        CVideoRenderer *pRenderer,      // Used to delegate locking
        CCritSec *pInterfaceLock,       // Main critical section
        HRESULT *phr,                   // OLE failure return code
        LPCWSTR pPinName);              // This pins identification

    // Manage our DIBSECTION video allocator
    STDMETHODIMP GetAllocator(IMemAllocator **ppAllocator);
    STDMETHODIMP NotifyAllocator(IMemAllocator *pAllocator,BOOL bReadOnly);

}; // CVideoInputPin

// This is the COM object that represents a simple rendering filter. It
// supports IBaseFilter and IMediaFilter and a single input stream (pin)
// The classes that support these interfaces have nested scope NOTE the
// nested class objects are passed a pointer to their owning renderer
// when they are created but they should not use it during construction

class CVideoRenderer : public ISpecifyPropertyPages, public CBaseVideoRenderer
{
public:

    // Constructor and destructor

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
    CVideoRenderer(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr);
    ~CVideoRenderer();

    // Implement the ISpecifyPropertyPages interface

    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **);
    STDMETHODIMP GetPages(CAUUID *pPages);

    CBasePin *GetPin(int n);

    // Override these from the filter and renderer classes

    void PrepareRender();
    HRESULT Active();
    HRESULT BreakConnect();
    HRESULT CompleteConnect(IPin *pReceivePin);
    HRESULT SetMediaType(const CMediaType *pmt);
    HRESULT CheckMediaType(const CMediaType *pmtIn);
    HRESULT CheckVideoType(const VIDEOINFO *pDisplay,const VIDEOINFO *pInput);
    HRESULT UpdateFormat(VIDEOINFO *pVideoInfo);
    HRESULT DoRenderSample(IMediaSample *pMediaSample);
    void OnReceiveFirstSample(IMediaSample *pMediaSample);

public:

    CImageAllocator m_ImageAllocator;   // Our DIBSECTION allocator
    CVideoInputPin m_InputPin;          // IPin based interfaces
    CImageDisplay m_Display;            // Manages the video display type
    CMediaType m_mtIn;                  // Source connection media type
    CVideoText m_VideoText;             // Does the actual video rendering
    CImagePalette m_ImagePalette;       // Looks after managing a palette
    CDrawImage m_DrawImage;             // Does the actual image drawing
    SIZE m_VideoSize;                   // Size of the current video stream

}; // CVideoRenderer

#endif // __SAMPVID__


VIDEOTXT.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Sample ActiveMovie video renderer

#include <streams.h>
#include <sampvid.h>
#include <vidprop.h>
#include <tchar.h>

// This is a sample renderer that displays video in a window. What is special
// about that is that the window is non rectangular, the region it has is a
// word (whatever VideoString is defined to be as shown below). Using various
// GDI methods we create a region from the word and select it into our window
// Windows will then look after clipping and mouse interactions, all we have
// to do is handle the painting and drawing. This sample can work if it uses
// either its own allocator (the preferred approach) or a source filter's
// (like when it connects to the tee filter). It has separate code paths to
// handle the drawing depending whose allocator it's using on this connection

TCHAR VideoString[] = TEXT("ActiveX");

//
// Constructor
//
CVideoText::CVideoText(TCHAR *pName,                 // Object description
                       LPUNKNOWN pUnk,               // Normal COM ownership
                       HRESULT *phr,                 // OLE failure code
                       CCritSec *pInterfaceLock,     // Main critical section
                       CVideoRenderer *pRenderer) :  // Delegates locking to

    CBaseControlVideo(pRenderer,pInterfaceLock,pName,pUnk,phr),
    CBaseControlWindow(pRenderer,pInterfaceLock,pName,pUnk,phr),
    m_pRenderer(pRenderer)
{
    PrepareWindow();
    InitWindowRegion(VideoString);

} // (Constructor)

//
// Destructor
//
CVideoText::~CVideoText()
{
    InactivateWindow();
    DoneWithWindow();

} // (Destructor)

//
// NonDelegatingQueryInterface
//
// Overriden to say what interfaces we support and where
//
STDMETHODIMP CVideoText::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    if (riid == IID_IVideoWindow) {
        return CBaseVideoWindow::NonDelegatingQueryInterface(riid,ppv);
    } else {
        ASSERT(riid == IID_IBasicVideo);
        return CBaseBasicVideo::NonDelegatingQueryInterface(riid,ppv);
    }
}

//
// CreateVideoFont
//
// This is called when we initialise the mask bitmap used when drawing. We
// create and install a larger font into the device context. The font used
// is 72 point Arial. It doesn't matter that the width of the letters are
// bigger than the video as we can stretch the video during the draw. This
// makes us go slower but it looks good and the quality management in the
// base renderer class should take care of reducing the source frame rate
//
HFONT CVideoText::CreateVideoFont()
{
    LOGFONT lf;

    int iLogPelsY = GetDeviceCaps(m_hdc,LOGPIXELSY);
    ZeroMemory(&lf,sizeof(LOGFONT));

    lf.lfHeight = (-72 * iLogPelsY) / 72;
    lf.lfWeight = 700;
    lf.lfCharSet = ANSI_CHARSET;
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf.lfQuality = PROOF_QUALITY;
    lf.lfPitchAndFamily = DEFAULT_PITCH | FF_SWISS;
    _tcscpy(lf.lfFaceName,TEXT("Arial"));

    return CreateFontIndirect(&lf);

} // CreateVideoFont

//
// InitWindowRegion
//
// We display the video in a window that has a region selected into it that
// matches the word we are passed in. By doing this we let Windows manage
// all the clipping and mouse technology. The trick is in creating a region
// that matches the word, this is done by using paths. We create a path for
// a temporary HDC, draw the word and then end the path. After which we can
// then ask Windows for a region that describes that path. That gives us a
// region for the outside of the word so we not it to get the word region
//
HRESULT CVideoText::InitWindowRegion(TCHAR *pStringName)
{
    OSVERSIONINFO VersionInfo;
    VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    EXECUTE_ASSERT(GetVersionEx(&VersionInfo));

    // Set a window region according to the OS capabilities

    if ((VersionInfo.dwPlatformId & VER_PLATFORM_WIN32_NT) == 0) {
        m_Size.cx = 320;
        m_Size.cy = 240;
        return NOERROR;
    }

    // Get the text extents the word passed in will require based on the
    // font and bitmap selected in the current device context. For it to
    // be displayed in a different font it must be selected into the HDC

    HDC hdc = CreateCompatibleDC(m_hdc);
    HFONT hFont = CreateVideoFont();
    SelectObject(hdc,hFont);

    GetTextExtentPoint32((HDC) hdc,             // The output device context
                         pStringName,           // The string we'll be using
                         lstrlen(pStringName),  // Number of characters in it
                         (LPSIZE) &m_Size);     // Filled in with the extents

    // Create a bitmap that matches the current format

    HBITMAP hMaskBitmap = CreateCompatibleBitmap(hdc,m_Size.cx,m_Size.cy);
    if (hMaskBitmap == NULL) {
        ASSERT(hMaskBitmap);
        return E_UNEXPECTED;
    }

    // Select the monochrome bitmap into the device context

    HBITMAP hBitmap = (HBITMAP) SelectObject(hdc,hMaskBitmap);
    EXECUTE_ASSERT(BeginPath(hdc));

    // Draw the string into the monochrome bitmap

    ExtTextOut((HDC) hdc,               // Target device context
               (int) 0,                 // x coordinate reference
               (int) 0,                 // Likewise y coordinate
               (DWORD) 0,               // No special flags to set
               NULL,                    // No clipping rectangle
               pStringName,             // Pointer to text words
               lstrlen(pStringName),    // Number of characters
               NULL);                   // Intercharacter spacing

    EXECUTE_ASSERT(EndPath(hdc));
    HRGN hOutside = PathToRegion(hdc);
    HRGN hFullWindow = CreateRectRgn(0,0,m_Size.cx,m_Size.cy);
    HRGN hWordRegion = CreateRectRgn(0,0,1,1);
    CombineRgn(hWordRegion,hFullWindow,hOutside,RGN_DIFF);
    SetWindowRgn(m_hwnd,hWordRegion,TRUE);

    // Clear up the regions we created

    DeleteObject(hWordRegion);
    DeleteObject(hOutside);
    DeleteObject(hFullWindow);

    // Delete the HDC and text bitmap

    SelectObject(hdc,hBitmap);
    HFONT hDefault = (HFONT) GetStockObject(SYSTEM_FONT);
    SelectObject(hdc,hDefault);
    DeleteObject(hFont);
    DeleteObject(hMaskBitmap);
    DeleteDC(hdc);

    return NOERROR;

} // InitWindowRegion

//
// GetClassWindowStyles
//
// When we call PrepareWindow in our constructor it will call this method as
// it is going to create the window to get our window and class styles. The
// return code is the class name and must be allocated in static storage. We
// specify a normal window during creation although the window styles as well
// as the extended styles may be changed by the application via IVideoWindow
//
LPTSTR CVideoText::GetClassWindowStyles(DWORD *pClassStyles,
                                        DWORD *pWindowStyles,
                                        DWORD *pWindowStylesEx)
{
    OSVERSIONINFO VersionInfo;
    VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    EXECUTE_ASSERT(GetVersionEx(&VersionInfo));

    // Default styles for Windows NT systems

    *pClassStyles = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT;
    *pWindowStyles = WS_POPUP | WS_CLIPCHILDREN;
    *pWindowStylesEx = (DWORD) 0;

    // Make a normal window on Win95 systems

    if ((VersionInfo.dwPlatformId & VER_PLATFORM_WIN32_NT) == 0) {
        *pWindowStyles = WS_POPUP | WS_CAPTION | WS_CLIPCHILDREN;
    }
    return TEXT("VideoTextRenderer");

} // GetClassWindowStyles

//
// GetDefaultRect
//
// Return the default window rectangle
//
RECT CVideoText::GetDefaultRect()
{
    RECT DefaultRect = {0,0,m_Size.cx,m_Size.cy};
    ASSERT(m_hwnd);
    ASSERT(m_hdc);
    return DefaultRect;

} // GetDefaultRect

//
// OnReceiveMessage
//
// This is the derived class window message handler methods
//
LRESULT CVideoText::OnReceiveMessage(HWND hwnd,          // Window handle
                                     UINT uMsg,          // Message ID
                                     WPARAM wParam,      // First parameter
                                     LPARAM lParam)      // Other parameter
{
    IBaseFilter *pFilter = NULL;
    RECT ClientRect;

    // Blank out the window background

    if (uMsg == WM_ERASEBKGND) {
        EXECUTE_ASSERT(GetClientRect(m_hwnd,&ClientRect));
        HBRUSH hBrush = CreateSolidBrush(RGB(0,0,0));
        EXECUTE_ASSERT(FillRect(m_hdc,&ClientRect,hBrush));
        EXECUTE_ASSERT(DeleteObject(hBrush));
        return (LRESULT) 0;
    }

    // Handle WM_CLOSE by aborting the playback

    if (uMsg == WM_CLOSE) {
        m_pRenderer->NotifyEvent(EC_USERABORT,0,0);
        DoShowWindow(SW_HIDE);
        return CBaseWindow::OnClose();
    }

    // We pass on WM_ACTIVATEAPP messages to the filtergraph so that the
    // IVideoWindow plug in distributor can switch us out of fullscreen
    // mode where appropriate. These messages may also be used by the
    // resource manager to keep track of which renderer has the focus

    if (uMsg == WM_ACTIVATEAPP) {
        NOTE1("Notification of EC_ACTIVATE (%d)",(BOOL) wParam);
        m_pRenderer->QueryInterface(IID_IBaseFilter,(void **) &pFilter);
        m_pRenderer->NotifyEvent(EC_ACTIVATE,wParam,(LPARAM) pFilter);
        pFilter->Release();
        return (LRESULT) 0;
    }

    // Treat clicks on text as requests to move window

    if (uMsg == WM_NCHITTEST) {
        LRESULT Result = DefWindowProc(hwnd,uMsg,wParam,lParam);
        if (Result == HTCLIENT) {
            Result = HTCAPTION;
        }
        return Result;
    }

    // The base class that implements IVideoWindow looks after a flag
    // that says whether or not the cursor should be hidden. If so we
    // hide the cursor and return (LRESULT) 1. Otherwise we pass to
    // the DefWindowProc to show the cursor as normal. This is used
    // when our window is made fullscreen to imitate the Modex filter

    if (uMsg == WM_SETCURSOR) {
        if (IsCursorHidden() == TRUE) {
            SetCursor(NULL);
            return (LRESULT) 1;
        }
    }

    // When we detect a display change we send an EC_DISPLAY_CHANGED
    // message along with our input pin. The filtergraph will stop
    // everyone and reconnect our input pin. When being reconnected
    // we can then accept the media type that matches the new display
    // mode since we may no longer be able to draw the current format

    if (uMsg == WM_DISPLAYCHANGE) {
        m_pRenderer->m_Display.RefreshDisplayType(NULL);
        m_pRenderer->OnDisplayChange();
        NOTE("Sent EC_DISPLAY_CHANGED event");
        return (LRESULT) 0;
    }
    return CBaseWindow::OnReceiveMessage(hwnd,uMsg,wParam,lParam);

} // OnReceiveMessage

//
// SetDefaultTargetRect
//
// This is called when we reset the default target rectangle
//
HRESULT CVideoText::SetDefaultTargetRect()
{
    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pRenderer->m_mtIn.Format();
    BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);
    RECT TargetRect = {0,0,m_Size.cx,m_Size.cy};
    m_pRenderer->m_DrawImage.SetTargetRect(&TargetRect);
    return NOERROR;

} // SetDefaultTargetRect

//
// IsDefaultTargetRect
//
// Return S_OK if using the default target otherwise S_FALSE
//
HRESULT CVideoText::IsDefaultTargetRect()
{
    RECT TargetRect;

    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pRenderer->m_mtIn.Format();
    BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);
    m_pRenderer->m_DrawImage.GetTargetRect(&TargetRect);

    // Check the destination matches the initial client area

    if (TargetRect.left != 0 || TargetRect.top != 0 ||
            TargetRect.right != m_Size.cx ||
                TargetRect.bottom != m_Size.cy) {
                    return S_FALSE;
    }
    return S_OK;

} // IsDefaultTargetRect

//
// SetTargetRect
//
// This is called to set the target rectangle in the video window, it will be
// called whenever a WM_SIZE message is retrieved from the message queue. We
// simply store the rectangle and use it later when we do the drawing calls
//
HRESULT CVideoText::SetTargetRect(RECT *pTargetRect)
{
    m_pRenderer->m_DrawImage.SetTargetRect(pTargetRect);
    return NOERROR;

} // SetTargetRect

//
// GetTargetRect
//
// Return the current destination rectangle
//
HRESULT CVideoText::GetTargetRect(RECT *pTargetRect)
{
    ASSERT(pTargetRect);
    m_pRenderer->m_DrawImage.GetTargetRect(pTargetRect);
    return NOERROR;

} // GetTargetRect

//
// SetDefaultSourceRect
//
// This is called when we reset the default source rectangle
//
HRESULT CVideoText::SetDefaultSourceRect()
{
    SIZE VideoSize = m_pRenderer->m_VideoSize;
    RECT SourceRect = {0,0,VideoSize.cx,VideoSize.cy};
    m_pRenderer->m_DrawImage.SetSourceRect(&SourceRect);
    return NOERROR;

} // SetDefaultSourceRect

//
// IsDefaultSourceRect
//
// Return S_OK if using the default source otherwise S_FALSE
//
HRESULT CVideoText::IsDefaultSourceRect()
{
    RECT SourceRect;

    // Does the source match the native video size

    SIZE VideoSize = m_pRenderer->m_VideoSize;
    CAutoLock cWindowLock(&m_WindowLock);
    m_pRenderer->m_DrawImage.GetSourceRect(&SourceRect);

    // Check the coordinates match the video dimensions

    if (SourceRect.right == VideoSize.cx) {
        if (SourceRect.bottom == VideoSize.cy) {
            if (SourceRect.left == 0) {
                if (SourceRect.top == 0) {
                    return S_OK;
                }
            }
        }
    }
    return S_FALSE;

} // IsDefaultSourceRect

//
// SetSourceRect
//
// This is called when we want to change the section of the image to draw. We
// use this information in the drawing operation calls later on. We must also
// see if the source and destination rectangles have the same dimensions. If
// not we must stretch during the rendering rather than a direct pixel copy
//
HRESULT CVideoText::SetSourceRect(RECT *pSourceRect)
{
    m_pRenderer->m_DrawImage.SetSourceRect(pSourceRect);
    return NOERROR;

} // SetSourceRect

//
// GetSourceRect
//
// Return the current source rectangle
//
HRESULT CVideoText::GetSourceRect(RECT *pSourceRect)
{
    ASSERT(pSourceRect);
    m_pRenderer->m_DrawImage.GetSourceRect(pSourceRect);
    return NOERROR;

} // GetSourceRect

//
// GetStaticImage
//
// Return a copy of the current image in the video renderer
//
HRESULT CVideoText::GetStaticImage(long *pBufferSize,long *pDIBImage)
{
    NOTE("Entering GetStaticImage");
    IMediaSample *pMediaSample;
    pMediaSample = m_pRenderer->GetCurrentSample();
    RECT SourceRect;

    // Is there an image available

    if (pMediaSample == NULL) {
        return E_UNEXPECTED;
    }

    // Find a scaled source rectangle for the current bitmap

    m_pRenderer->m_DrawImage.GetSourceRect(&SourceRect);
    SourceRect = m_pRenderer->m_DrawImage.ScaleSourceRect(&SourceRect);
    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pRenderer->m_mtIn.Format();

    // Call the base class helper method to do the work

    HRESULT hr = CopyImage(pMediaSample,        // Buffer containing image
                           pVideoInfo,          // Type representing bitmap
                           pBufferSize,         // Size of buffer for DIB
                           (BYTE*) pDIBImage,   // Data buffer for output
                           &SourceRect);        // Current source position

    pMediaSample->Release();
    return hr;

} // GetStaticImage

//
// GetVideoFormat
//
// Derived classes must override this to return a VIDEOINFOHEADER representing
// the video format. We cannot call IPin ConnectionMediaType to get this
// format because various filters dynamically change the type when using
// DirectDraw such that the format shows the position of the logical
// bitmap in a frame buffer surface, so the size might be returned as
// 1024x768 pixels instead of 320x240 which are the real video dimensions
//
VIDEOINFOHEADER *CVideoText::GetVideoFormat()
{
    return (VIDEOINFOHEADER *)m_pRenderer->m_mtIn.Format();
} // GetVideoFormat


VIDPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <sampvid.h>
#include <vidprop.h>

// This class implements a property page dialog for the video renderer. We
// expose certain statistics from the quality management implementation. In
// particular we have two edit fields that show the number of frames we have
// actually drawn and the number of frames that we dropped. The number of
// frames we dropped does NOT represent the total number dropped in any play
// back sequence (as expressed through MCI status frames skipped) since the
// quality management protocol may have negotiated with the source filter for
// it to send fewer frames in the first place. Dropping frames in the source
// filter is nearly always a more efficient mechanism when we are flooded

//
// Constructor
//
CQualityProperties::CQualityProperties(LPUNKNOWN pUnk,HRESULT *phr) :
    CBasePropertyPage(NAME("Quality Page"),pUnk,IDD_QUALITY,IDS_NAME),
    m_pQualProp(NULL)
{
    ASSERT(phr);

} // (Constructor)

//
// CreateInstance
//
// Create a quality properties object
//
CUnknown * WINAPI CQualityProperties::CreateInstance(LPUNKNOWN lpUnk, HRESULT *phr)
{
    return new CQualityProperties(lpUnk, phr);

} // CreateInstance

//
// OnConnect
//
// Give us the filter to communicate with
//
HRESULT CQualityProperties::OnConnect(IUnknown *pUnknown)
{
    ASSERT(m_pQualProp == NULL);

    // Ask the renderer for it's IQualProp interface

    HRESULT hr = pUnknown->QueryInterface(IID_IQualProp,(void **)&m_pQualProp);
    if (FAILED(hr)) {
        return E_NOINTERFACE;
    }

    ASSERT(m_pQualProp);

    // Get quality data for our page

    m_pQualProp->get_FramesDroppedInRenderer(&m_iDropped);
    m_pQualProp->get_FramesDrawn(&m_iDrawn);
    m_pQualProp->get_AvgFrameRate(&m_iFrameRate);
    m_pQualProp->get_Jitter(&m_iFrameJitter);
    m_pQualProp->get_AvgSyncOffset(&m_iSyncAvg);
    m_pQualProp->get_DevSyncOffset(&m_iSyncDev);
    return NOERROR;

}  // OnConnect

//
// OnDisconnect
//
// Release any IQualProp interface we have
//
HRESULT CQualityProperties::OnDisconnect()
{
    // Release the interface

    if (m_pQualProp == NULL) {
        return E_UNEXPECTED;
    }

    m_pQualProp->Release();
    m_pQualProp = NULL;
    return NOERROR;

}  // OnDisconnect

//
// OnActivate
//
// Set the text fields in the property page
//
HRESULT CQualityProperties::OnActivate()
{
    SetEditFieldData();
    return NOERROR;

} // OnActivate

//
// SetEditFieldData
//
// Initialise the property page fields
//
void CQualityProperties::SetEditFieldData()
{
    ASSERT(m_pQualProp);
    TCHAR buffer[50];

    wsprintf(buffer,"%d", m_iDropped);
    SendDlgItemMessage(m_Dlg, IDD_QDROPPED, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
    wsprintf(buffer,"%d", m_iDrawn);
    SendDlgItemMessage(m_Dlg, IDD_QDRAWN, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
    wsprintf(buffer,"%d.%02d", m_iFrameRate/100, m_iFrameRate%100);
    SendDlgItemMessage(m_Dlg, IDD_QAVGFRM, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
    wsprintf(buffer,"%d", m_iFrameJitter);
    SendDlgItemMessage(m_Dlg, IDD_QJITTER, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
    wsprintf(buffer,"%d", m_iSyncAvg);
    SendDlgItemMessage(m_Dlg, IDD_QSYNCAVG, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
    wsprintf(buffer,"%d", m_iSyncDev);
    SendDlgItemMessage(m_Dlg, IDD_QSYNCDEV, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);

} // SetEditFieldData


VIDPROP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#ifndef __VIDPROP__
#define __VIDPROP__

#define IDD_QUALITY             150     // Dialog resource
#define IDD_Q1                  151     // Frames played
#define IDD_Q2                  152     // Frames dropped
#define IDD_Q4                  154     // Frame rate
#define IDD_Q5                  155     // Frame jitter
#define IDD_Q6                  156     // Sync offset
#define IDD_Q7                  157     // Sync deviation
#define FIRST_Q_BUTTON          171     // First button
#define LAST_Q_BUTTON           177     // Last button
#define IDD_QDRAWN              171     // Frames played
#define IDD_QDROPPED            172     // Frames dropped
#define IDD_QAVGFRM             174     // Average frame rate achieved
#define IDD_QJITTER             175     // Average frame jitter
#define IDD_QSYNCAVG            176     // Average sync offset
#define IDD_QSYNCDEV            177     // Std dev sync offset
#define IDS_NAME                178     // Quality dialog name

// Property page built on top of a renderer IQualProp interface

class CQualityProperties : public CBasePropertyPage
{
    IQualProp *m_pQualProp;         // Interface held on the renderer
    int m_iDropped;                 // Number of frames dropped
    int m_iDrawn;                   // Count of images drawn
    int m_iSyncAvg;                 // Average sync value
    int m_iSyncDev;                 // And standard deviation
    int m_iFrameRate;               // Total frame rate average
    int m_iFrameJitter;             // Measure of frame jitter

    void SetEditFieldData();
    void DisplayStatistics(void);

public:

    CQualityProperties(LPUNKNOWN lpUnk, HRESULT *phr);
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);

    // Overriden from CBasePropertyPage base class

    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate();

}; // CQualityProperties

#endif // __VIDPROP__


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by scope.rc
//
#define IDD_SCOPEDIALOG                 101
#define IDI_ICON1                       103
#define IDC_L_GAIN                      1001
#define IDC_L_GAIN_TEXT                 1002
#define IDC_L_OFFSET                    1003
#define IDC_L_TITLE                     1004
#define IDC_TIMEBASE_TEXT               1004
#define IDC_L_AUTO                      1005
#define IDC_R_GAIN                      1006
#define IDC_R_GAIN_TEXT                 1007
#define IDC_R_OFFSET                    1008
#define IDC_R_TITLE                     1009
#define IDC_L_OFFSET_TEXT               1009
#define IDC_R_AUTO                      1010
#define IDC_TS_LAST                     1011
#define IDC_TB_START                    1012
#define IDC_TB_SCROLL                   1012
#define IDC_FREEZE                      1013
#define IDC_TIMEBASE                    1014
#define IDC_R_OFFSET_TEXT               1015
#define IDC_TS_START                    1016
#define IDC_TS_DELTA                    1017
#define IDC_SCOPEWINDOW                 1019
#define IDC_USER1                       1021
#define IDC_TRIGGER                     1022
#define IDC_FORMAT                      1023
#define IDC_BUFSIZE                     1024
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1024
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SCOPE.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <commctrl.h>
#include <mmsystem.h>
#include <initguid.h>
#include <wxdebug.h>
#include "scope.h"
#include "resource.h"

//
//
// What this sample illustrates
//
// An audio oscilloscope that shows the waveform graphically as the audio is
// received by the filter. The filter is a renderer that can put where ever
// the normal runtime renderer goes. We have a single input pin that accepts
// a number of difference audio formats and renders the data as appropriate.
//
//
// Summary
//
// This is an audio oscilloscope renderer - we are basicly an audio renderer
// When we are created we also create a class to look after the scope window
// whose constructor creates a worker thread, when it is destroyed it will
// also terminate the worker thread. On that worker thread a window is looked
// after that shows the audio waveform for data sent to us. The data is kept
// in a circular buffer that loops when sufficient data has been received. We
// support a number of different audio formats such as 8bit mode and stereo.
//
//
// Demonstration Instructions
//
// (To really sure of this demonstration the machine must have a sound card)
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any
// MPEG, AVI or MOV file into the tool and it will be rendered. Then go to
// the filters in the graph and find the filter (box) titled "Audio Renderer"
// This is the filter we will be replacing with this oscilloscope renderer.
// Then click on the box and hit DELETE. After that go to the Graph menu and
// select "Insert Filters", from the dialog box that pops up find and select
// "Oscilloscope", then dismiss the dialog. Back in the graph layout find the
// output pin of the filter that was connected to the input of the audio
// renderer you just deleted, right click and select "Render". You should
// see it being connected to the input pin of the oscilloscope you inserted
//
// Click Run on GRAPHEDT and you'll see a waveform for the audio soundtrack...
//
//
// Files
//
// icon1.ico            The icon for the oscilloscope window
// makefile             How we build it...
// resource.h           Microsoft Visual C++ generated file
// scope.cpp            The main filter and window implementations
// scope.def            What APIs the DLL imports and exports
// scope.h              Window and filter class definitions
// scope.mak            Visual C++ generated makefile
// scope.rc             Dialog box template for our window
// scope.reg            What goes in the registry to make us work
//
//
// Base classes we use
//
// CBaseInputPin        A generic input pin we use for the filter
// CCritSec             A wrapper class around a critical section
// CBaseFilter          The generic ActiveMovie filter object
//
//

// Setup data

const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Audio,           // Major type
    &MEDIASUBTYPE_NULL          // Minor type
};

const AMOVIESETUP_PIN sudPins  =
{
    L"Input",                   // Pin string name
    FALSE,                      // Is it rendered
    FALSE,                      // Is it an output
    FALSE,                      // Allowed zero pins
    FALSE,                      // Allowed many
    &CLSID_NULL,                // Connects to filter
    L"Output",                  // Connects to pin
    1,                          // Number of pins types
    &sudPinTypes } ;            // Pin information

const AMOVIESETUP_FILTER sudScope =
{
    &CLSID_Scope,               // Filter CLSID
    L"Oscilloscope",            // String name
    MERIT_DO_NOT_USE,           // Filter merit
    1,                          // Number pins
    &sudPins                    // Pin details
};

// List of class IDs and creator functions for class factory

CFactoryTemplate g_Templates []  = {
    { L"Oscilloscope"
    , &CLSID_Scope
    , CScopeFilter::CreateInstance
    , NULL
    , &sudScope }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// CreateInstance
//
// This goes in the factory template table to create new instances
//
CUnknown * WINAPI CScopeFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    return new CScopeFilter(pUnk, phr);

} // CreateInstance

//
// Constructor
//
// Create the filter, scope window, and input pin
//
#pragma warning(disable:4355)
//
CScopeFilter::CScopeFilter(LPUNKNOWN pUnk,HRESULT *phr) :
    CBaseFilter(NAME("Oscilloscope"), pUnk, (CCritSec *) this, CLSID_Scope),
    m_Window(NAME("Oscilloscope"), this, phr)
{
    // Create the single input pin

    m_pInputPin = new CScopeInputPin(this,phr,L"Scope Input Pin");
    if (m_pInputPin == NULL) {
        *phr = E_OUTOFMEMORY;
    }

} // (Constructor)

//
// Destructor
//
CScopeFilter::~CScopeFilter()
{
    // Delete the contained interfaces

    ASSERT(m_pInputPin);
    delete m_pInputPin;
    m_pInputPin = NULL;

} // (Destructor)

//
// GetPinCount
//
// Return the number of input pins we support
//
int CScopeFilter::GetPinCount()
{
    return 1;

} // GetPinCount

//
// GetPin
//
// Return our single input pin - not addrefed
//
CBasePin *CScopeFilter::GetPin(int n)
{
    // We only support one input pin and it is numbered zero

    ASSERT(n == 0);
    if (n != 0) {
        return NULL;
    }
    return m_pInputPin;

} // GetPin

//
// JoinFilterGraph
//
// Show our window when we join a filter graph
//   - and hide it when we are annexed from it
//
STDMETHODIMP CScopeFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName)
{
    HRESULT hr = CBaseFilter::JoinFilterGraph(pGraph, pName);
    if (FAILED(hr)) {
        return hr;
    }

    // Hide or show the scope as appropriate

    if (pGraph == NULL) {
        m_Window.InactivateWindow();
    } else {
        m_Window.ActivateWindow();
    }
    return hr;

} // JoinFilterGraph

//
// Stop
//
// Switch the filter into stopped mode.
//
STDMETHODIMP CScopeFilter::Stop()
{
    CAutoLock lock(this);

    if (m_State != State_Stopped) {

        // Pause the device if we were running
        if (m_State == State_Running) {
            HRESULT hr = Pause();
            if (FAILED(hr)) {
                return hr;
            }
        }

        DbgLog((LOG_TRACE,1,TEXT("Stopping....")));

        // Base class changes state and tells pin to go to inactive
        // the pin Inactive method will decommit our allocator which
        // we need to do before closing the device

        HRESULT hr = CBaseFilter::Stop();
        if (FAILED(hr)) {
            return hr;
        }
    }
    return NOERROR;

} // Stop

//
// Pause
//
// Override Pause to stop the window streaming
//
STDMETHODIMP CScopeFilter::Pause()
{
    CAutoLock lock(this);

    // Check we can PAUSE given our current state

    if (m_State == State_Running) {
        m_Window.StopStreaming();
    }

    // tell the pin to go inactive and change state
    return CBaseFilter::Pause();

} // Pause

//
// Run
//
// Overriden to start the window streaming
//
STDMETHODIMP CScopeFilter::Run(REFERENCE_TIME tStart)
{
    CAutoLock lock(this);
    HRESULT hr = NOERROR;
    FILTER_STATE fsOld = m_State;

    // This will call Pause if currently stopped

    hr = CBaseFilter::Run(tStart);
    if (FAILED(hr)) {
        return hr;
    }

    m_Window.ActivateWindow();

    if (fsOld != State_Running) {
        m_Window.StartStreaming();
    }
    return NOERROR;

} // Run

//
// Constructor
//
CScopeInputPin::CScopeInputPin(CScopeFilter *pFilter,
                               HRESULT *phr,
                               LPCWSTR pPinName) :
    CBaseInputPin(NAME("Scope Input Pin"), pFilter, pFilter, phr, pPinName)
{
    m_pFilter = pFilter;

} // (Constructor)

//
// Destructor does nothing
//
CScopeInputPin::~CScopeInputPin()
{
} // (Destructor)

//
// BreakConnect
//
// This is called when a connection or an attempted connection is terminated
// and allows us to reset the connection media type to be invalid so that
// we can always use that to determine whether we are connected or not. We
// leave the format block alone as it will be reallocated if we get another
// connection or alternatively be deleted if the filter is finally released
//
HRESULT CScopeInputPin::BreakConnect()
{
    // Check we have a valid connection

    if (m_mt.IsValid() == FALSE) {
        return E_FAIL;
    }

    m_pFilter->Stop();

    // Reset the CLSIDs of the connected media type

    m_mt.SetType(&GUID_NULL);
    m_mt.SetSubtype(&GUID_NULL);
    return CBaseInputPin::BreakConnect();

} // BreakConnect

//
// CheckMediaType
//
// Check that we can support a given proposed type
//
HRESULT CScopeInputPin::CheckMediaType(const CMediaType *pmt)
{
    WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmt->Format();

    if (pwfx == NULL)
        return E_INVALIDARG;

    // Reject non-PCM Audio type

    if (pmt->majortype != MEDIATYPE_Audio) {
        return E_INVALIDARG;
    }

    if (pmt->formattype != FORMAT_WaveFormatEx) {
        return E_INVALIDARG;
    }

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM) {
        return E_INVALIDARG;
    }
    return NOERROR;

} // CheckMediaType

//
// SetMediaType
//
// Actually set the format of the input pin
//
HRESULT CScopeInputPin::SetMediaType(const CMediaType *pmt)
{
    CAutoLock lock(m_pFilter);

    // Pass the call up to my base class

    HRESULT hr = CBaseInputPin::SetMediaType(pmt);
    if (SUCCEEDED(hr)) {

        WAVEFORMATEX *pwf = (WAVEFORMATEX *) pmt->Format();

        m_pFilter->m_Window.m_nChannels = pwf->nChannels;
        m_pFilter->m_Window.m_nSamplesPerSec = pwf->nSamplesPerSec;
        m_pFilter->m_Window.m_nBitsPerSample = pwf->wBitsPerSample;
        m_pFilter->m_Window.m_nBlockAlign = pwf->nBlockAlign;

        m_pFilter->m_Window.m_MaxValue = 128;
        m_pFilter->m_Window.m_nIndex = 0;

        if (!m_pFilter->m_Window.AllocWaveBuffers ())
            return E_FAIL;

        // Reset the horizontal scroll bar
        m_pFilter->m_Window.SetHorizScrollRange(m_pFilter->m_Window.m_hwndDlg);
    }
    return hr;

} // SetMediaType

//
// Active
//
// Implements the remaining IMemInputPin virtual methods
//
HRESULT CScopeInputPin::Active(void)
{
    return NOERROR;

} // Active

//
// Inactive
//
// Called when the filter is stopped
//
HRESULT CScopeInputPin::Inactive(void)
{
    return NOERROR;

} // Inactive

//
// Receive
//
// Here's the next block of data from the stream
//
HRESULT CScopeInputPin::Receive(IMediaSample * pSample)
{
    // Lock this with the filter-wide lock
    CAutoLock lock(m_pFilter);

    // If we're stopped, then reject this call
    // (the filter graph may be in mid-change)
    if (m_pFilter->m_State == State_Stopped) {
        return E_FAIL;
    }

    // Check all is well with the base class
    HRESULT hr = CBaseInputPin::Receive(pSample);
    if (FAILED(hr)) {
        return hr;
    }

    // Send the sample to the video window object for rendering
    return m_pFilter->m_Window.Receive(pSample);

} // Receive

//
// CScopeWindow Constructor
//
CScopeWindow::CScopeWindow(TCHAR *pName, CScopeFilter *pRenderer,HRESULT *phr) :
    m_hInstance(g_hInst),
    m_pRenderer(pRenderer),
    m_hThread(INVALID_HANDLE_VALUE),
    m_ThreadID(0),
    m_hwndDlg(NULL),
    m_hwnd(NULL),
    m_pPoints1(NULL),
    m_pPoints2(NULL),
    m_nPoints(0),
    m_bStreaming(FALSE),
    m_bActivated(FALSE),
    m_LastMediaSampleSize(0)
{
    // Create a thread to look after the window

    ASSERT(m_pRenderer);
    m_hThread = CreateThread(NULL,                  // Security attributes
                             (DWORD) 0,             // Initial stack size
                             WindowMessageLoop,     // Thread start address
                             (LPVOID) this,         // Thread parameter
                             (DWORD) 0,             // Creation flags
                             &m_ThreadID);          // Thread identifier

    // If we couldn't create a thread the whole thing's off

    ASSERT(m_hThread);
    if (m_hThread == NULL) {
        *phr = E_FAIL;
        return;
    }

    // Wait until the window has been initialised
    m_SyncWorker.Wait();

} // (Constructor)

//
// Destructor
//
CScopeWindow::~CScopeWindow()
{
    // Ensure we stop streaming and release any samples

    StopStreaming();
    InactivateWindow();

    // Tell the thread to destroy the window
    SendMessage(m_hwndDlg, WM_GOODBYE, (WPARAM)0, (LPARAM)0);

    // Make sure it has finished

    ASSERT(m_hThread != NULL);
    WaitForSingleObject(m_hThread,INFINITE);
    CloseHandle(m_hThread);

    if (m_pPoints1 != NULL) delete [] m_pPoints1;
    if (m_pPoints2 != NULL) delete [] m_pPoints2;

} // (Destructor)

//
// ResetStreamingTimes
//
// This resets the latest sample stream times
//
HRESULT CScopeWindow::ResetStreamingTimes()
{
    m_StartSample = 0;
    m_EndSample = 0;
    return NOERROR;

} // ResetStreamingTimes

//
// StartStreaming
//
// This is called when we start running state
//
HRESULT CScopeWindow::StartStreaming()
{
    CAutoLock cAutoLock(this);

    // Are we already streaming

    if (m_bStreaming == TRUE) {
        return NOERROR;
    }

    m_bStreaming = TRUE;
    return NOERROR;

} // StartStreaming

//
// StopStreaming
//
// This is called when we stop streaming
//
HRESULT CScopeWindow::StopStreaming()
{
    CAutoLock cAutoLock(this);

    // Have we been stopped already

    if (m_bStreaming == FALSE) {
        return NOERROR;
    }

    m_bStreaming = FALSE;
    return NOERROR;

} // StopStreaming

//
// InactivateWindow
//
// Called at the end to put the window in an inactive state
//
HRESULT CScopeWindow::InactivateWindow()
{
    // Has the window been activated
    if (m_bActivated == FALSE) {
        return S_FALSE;
    }

    // Now hide the scope window

    ShowWindow(m_hwndDlg,SW_HIDE);
    m_bActivated = FALSE;
    return NOERROR;

} // InactivateWindow

//
// ActivateWindow
//
// Show the scope window
//
HRESULT CScopeWindow::ActivateWindow()
{
    // Has the window been activated
    if (m_bActivated == TRUE) {
        return S_FALSE;
    }

    m_bActivated = TRUE;
    ASSERT(m_bStreaming == FALSE);

    ShowWindow(m_hwndDlg,SW_SHOWNORMAL);
    return NOERROR;

} // ActivateWindow

//
// OnClose
//
// This function handles the WM_CLOSE message
//
BOOL CScopeWindow::OnClose()
{
    InactivateWindow();
    return TRUE;

} // OnClose

typedef struct GainEntry_tag {
    double GainValue;
    TCHAR GainText[8];
} GainEntry;

GainEntry GainEntries[] =
{
    128.,  TEXT ("*128"),
    64.,   TEXT ("*64"),
    32.,   TEXT ("*32"),
    16.,   TEXT ("*16"),
    8.,    TEXT ("*8"),
    4.,    TEXT ("*4"),
    2.,    TEXT ("*2"),
    1.,    TEXT ("*1"),
    1./2,  TEXT ("/2"),
    1./4,  TEXT ("/4"),
    1./8,  TEXT ("/8"),
    1./16, TEXT ("/16"),
    1./32, TEXT ("/32"),
    1./64, TEXT ("/64"),
    1./128,TEXT ("/128"),
    1./256,TEXT ("/256"),
};

#define N_GAINENTRIES (sizeof(GainEntries) / sizeof (GainEntries[0]))
#define GAIN_DEFAULT_INDEX 7

typedef struct TBEntry_tag {
    int TBDivisor;
    TCHAR TBText[16];
} TBEntry;

TBEntry Timebases[] =
{
    10000,  TEXT ("10 uS/Div"),
     5000,  TEXT ("20 uS/Div"),
     2000,  TEXT ("50 uS/Div"),
     1000,  TEXT ("100 uS/Div"),
      500,  TEXT ("200 uS/Div"),
      200,  TEXT ("500 uS/Div"),
      100,  TEXT ("1 mS/Div"),
       50,  TEXT ("2 mS/Div"),
       20,  TEXT ("5 mS/Div"),
       10,  TEXT ("10 mS/Div"),
        5,  TEXT ("20 mS/Div"),
        2,  TEXT ("50 mS/Div"),
        1,  TEXT ("100 mS/Div")
};

#define N_TIMEBASES (sizeof(Timebases) / sizeof (Timebases[0]))
#define TIMEBASE_DEFAULT_INDEX 9

//
// SetControlRanges
//
// Set the scroll ranges for all of the vertical trackbars
//
void CScopeWindow::SetControlRanges(HWND hDlg)
{
    SendMessage(m_hwndLGain, TBM_SETRANGE, TRUE, MAKELONG(0, N_GAINENTRIES - 1) );
    SendMessage(m_hwndLGain, TBM_SETPOS, TRUE, (LPARAM) GAIN_DEFAULT_INDEX);
    SetDlgItemText (hDlg, IDC_L_GAIN_TEXT, GainEntries[m_LGain].GainText);

    SendMessage(m_hwndLOffset, TBM_SETRANGE, TRUE, MAKELONG(0, m_Height - 1));
    SendMessage(m_hwndLOffset, TBM_SETPOS, TRUE, (LPARAM) m_Height / 2);
    SetDlgItemInt (hDlg, IDC_L_OFFSET_TEXT, -m_LOffset, TRUE);

    SendMessage(m_hwndRGain, TBM_SETRANGE, TRUE, MAKELONG(0, N_GAINENTRIES - 1) );
    SendMessage(m_hwndRGain, TBM_SETPOS, TRUE, (LPARAM) GAIN_DEFAULT_INDEX);
    SetDlgItemText (hDlg, IDC_R_GAIN_TEXT, GainEntries[m_RGain].GainText);

    SendMessage(m_hwndROffset, TBM_SETRANGE, TRUE, MAKELONG(0, m_Height - 1) );
    SendMessage(m_hwndROffset, TBM_SETPOS, TRUE, (LPARAM) m_Height / 2);
    SetDlgItemInt (hDlg, IDC_R_OFFSET_TEXT, -m_ROffset, TRUE);

    SendMessage(m_hwndTimebase, TBM_SETRANGE, TRUE, MAKELONG(0, N_TIMEBASES - 1) );
    SendMessage(m_hwndTimebase, TBM_SETPOS, TRUE, (LPARAM) m_nTimebase);
    SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText);

} // SetControlRanges

//
// SetHorizScrollRange
//
// The horizontal scrollbar handles scrolling through the 1 second circular buffer
//
void CScopeWindow::SetHorizScrollRange(HWND hDlg)
{
    SendMessage(m_hwndTBScroll, TBM_SETRANGE, TRUE, MAKELONG(0, (m_nPoints - 1) / 2) );
    SendMessage(m_hwndTBScroll, TBM_SETPOS, TRUE, (LPARAM) (m_nPoints - 1) / 2);

    m_TBScroll = m_nPoints - 1;

    TCHAR szFormat[80];

    switch (m_nBitsPerSample + m_nChannels) {
        case 9:
            // Mono, 8-bit
            lstrcpy (szFormat, TEXT ("M-8-"));
            break;

        case 10:
            // Stereo, 8-bit
            lstrcpy (szFormat, TEXT ("S-8-"));
            break;

        case 17:
            // Mono, 16-bit
            lstrcpy (szFormat, TEXT ("M-16-"));
            break;

        case 18:
            // Stereo, 16-bit
            lstrcpy (szFormat, TEXT ("S-16-"));
            break;

        default:
            lstrcpy (szFormat, TEXT (" "));
            SetDlgItemText (hDlg, IDC_FORMAT, szFormat);
            return;

    } // End of format switch

    TCHAR szSamplingFreq[80];
    wsprintf (szSamplingFreq, "%d", m_nSamplesPerSec);
    lstrcat (szFormat, szSamplingFreq);
    SetDlgItemText (hDlg, IDC_FORMAT, szFormat);

} // SetHorizScrollRange

//
// ProcessHorizScrollCommands
//
// Called when we get a horizontal scroll bar message
//
void CScopeWindow::ProcessHorizScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    int pos;
    int command = LOWORD (wParam);

    if (command != TB_ENDTRACK &&
        command != TB_THUMBTRACK &&
        command != TB_LINEDOWN &&
        command != TB_LINEUP &&
        command != TB_PAGEUP &&
        command != TB_PAGEDOWN)
            return;

    ASSERT (IsWindow ((HWND) lParam));

    pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L);

    if ((HWND) lParam == m_hwndTBScroll) {
        m_TBScroll = ((m_nPoints - 1) / 2 - pos) * 2;
    }
    OnPaint();

} // ProcessHorizScrollCommands

//
// ProcessVertScrollCommands
//
// Called when we get a vertical scroll bar message
//
void CScopeWindow::ProcessVertScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
    int pos;
    int command = LOWORD (wParam);

    if (command != TB_ENDTRACK &&
        command != TB_THUMBTRACK &&
        command != TB_LINEDOWN &&
        command != TB_LINEUP &&
        command != TB_PAGEUP &&
        command != TB_PAGEDOWN)
            return;

    ASSERT (IsWindow ((HWND) lParam));

    pos = (int) SendMessage((HWND) lParam, TBM_GETPOS, 0, 0L);

    if ((HWND) lParam == m_hwndLGain) {
        m_LGain = pos;
        SetDlgItemText (hDlg, IDC_L_GAIN_TEXT, GainEntries[m_LGain].GainText);
    } else if ((HWND) lParam == m_hwndLOffset) {
        m_LOffset = pos - m_Height / 2;
        SetDlgItemInt (hDlg, IDC_L_OFFSET_TEXT, -m_LOffset, TRUE);
    } else if ((HWND) lParam == m_hwndRGain) {
        m_RGain = pos;
        SetDlgItemText (hDlg, IDC_R_GAIN_TEXT, GainEntries[m_RGain].GainText);
    } else if ((HWND) lParam == m_hwndROffset) {
        m_ROffset = pos - m_Height / 2;
        SetDlgItemInt (hDlg, IDC_R_OFFSET_TEXT, -m_ROffset, TRUE);
    } else if ((HWND) lParam == m_hwndTimebase) {
        m_nTimebase = pos ;
        SetDlgItemText (hDlg, IDC_TIMEBASE_TEXT, Timebases[m_nTimebase].TBText);
    }
    OnPaint();

} // ProcessVertScrollCommands

//
// InitialiseWindow
//
// This is called by the worker window thread after it has created the main
// window and it wants to initialise the rest of the owner objects window
// variables such as the device contexts. We execute this function with the
// critical section still locked.
//
HRESULT CScopeWindow::InitialiseWindow(HWND hDlg)
{
    RECT rR;

    // Initialise the window variables
    m_hwnd = GetDlgItem (hDlg, IDC_SCOPEWINDOW);

    // Quick sanity check
    ASSERT(m_hwnd != NULL);

    m_nTimebase = TIMEBASE_DEFAULT_INDEX;
    m_fTriggerPosZeroCrossing = 1;
    m_fFreeze = 0;

    m_LGain = GAIN_DEFAULT_INDEX;
    m_RGain = GAIN_DEFAULT_INDEX;
    m_LOffset = 0;
    m_ROffset = 0;

    m_TBScroll = 0;

    GetWindowRect (m_hwnd, &rR);
    m_Width = rR.right - rR.left;
    m_Height = rR.bottom - rR.top;

    m_hwndLGain =       GetDlgItem (hDlg, IDC_L_GAIN);
    m_hwndLOffset =     GetDlgItem (hDlg, IDC_L_OFFSET);
    m_hwndLGainText =   GetDlgItem (hDlg, IDC_L_GAIN_TEXT);
    m_hwndLTitle =      GetDlgItem (hDlg, IDC_L_TITLE);

    m_hwndRGain =       GetDlgItem (hDlg, IDC_R_GAIN);
    m_hwndROffset =     GetDlgItem (hDlg, IDC_R_OFFSET);
    m_hwndRGainText =   GetDlgItem (hDlg, IDC_R_GAIN_TEXT);
    m_hwndRTitle =      GetDlgItem (hDlg, IDC_R_TITLE);

    m_hwndTimebase =    GetDlgItem (hDlg, IDC_TIMEBASE);
    m_hwndFreeze =      GetDlgItem (hDlg, IDC_FREEZE);
    m_hwndTBStart =     GetDlgItem (hDlg, IDC_TS_START);
    m_hwndTBEnd   =     GetDlgItem (hDlg, IDC_TS_LAST);
    m_hwndTBDelta =     GetDlgItem (hDlg, IDC_TS_DELTA);
    m_hwndTBScroll =    GetDlgItem (hDlg, IDC_TB_SCROLL);

    SetControlRanges(hDlg);
    SetHorizScrollRange(hDlg);

    CheckDlgButton(
            hDlg,                // handle of dialog box
            IDC_FREEZE,                // button-control identifier
            m_fFreeze);                // check state

     CheckDlgButton(
            hDlg,                // handle of dialog box
            IDC_TRIGGER,        // button-control identifier
            m_fTriggerPosZeroCrossing);// check state

    m_hPen1 = CreatePen (PS_SOLID, 0, RGB (0, 0xff, 0));
    m_hPen2 = CreatePen (PS_SOLID, 0, RGB (0x40, 0x40, 0xff));
    m_hPenTicks = CreatePen (PS_SOLID, 0, RGB (0x80, 0x80, 0x80));
    m_hBrushBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);

    HDC hdc = GetDC (NULL);
    m_hBitmap = CreateCompatibleBitmap (hdc, m_Width, m_Height);
    ReleaseDC (NULL, hdc);
    return NOERROR;

} // InitialiseWindow

//
// UninitialiseWindow
//
// This is called by the worker window thread when it receives a WM_GOODBYE
// message from the window object destructor to delete all the resources we
// allocated during initialisation
//
HRESULT CScopeWindow::UninitialiseWindow()
{
    // Reset the window variables
    DeleteObject (m_hPen1);
    DeleteObject (m_hPen2);
    DeleteObject (m_hPenTicks);
    DeleteObject (m_hBitmap);

    m_hwnd = NULL;
    return NOERROR;

} // UninitialiseWindow

//
// ScopeDlgProc
//
// The Scope window is actually a dialog box, and this is its window proc.
// The only thing tricky about this is that the "this" pointer to the
// CScopeWindow is passed during the WM_INITDIALOG message and is stored
// in the window user data. This lets us access methods in the class
// from within the dialog.
//
BOOL CALLBACK ScopeDlgProc(HWND hDlg,        // Handle of dialog box
                           UINT uMsg,        // Message identifier
                           WPARAM wParam,// First message parameter
                           LPARAM lParam)// Second message parameter
{
    CScopeWindow *pScopeWindow;      // Pointer to the owning object

    // Get the window long that holds our owner pointer
    pScopeWindow = (CScopeWindow *) GetWindowLong(hDlg, GWL_USERDATA);

    switch (uMsg) {
        case WM_INITDIALOG:
            pScopeWindow = (CScopeWindow *) lParam;
            SetWindowLong(hDlg, (DWORD) GWL_USERDATA, (LONG) pScopeWindow);
            return TRUE;

        case WM_COMMAND:
            switch (wParam) {
                case IDOK:
                case IDCANCEL:
                    EndDialog (hDlg, 0);
                    return TRUE;

                case IDC_FREEZE:
                    pScopeWindow->m_fFreeze =
                        (BOOL) IsDlgButtonChecked(hDlg,IDC_FREEZE);
                    pScopeWindow->DrawWaveform();
                    break;

                case IDC_TRIGGER:
                    pScopeWindow->m_fTriggerPosZeroCrossing =
                        (BOOL) IsDlgButtonChecked(hDlg,IDC_TRIGGER);
                    pScopeWindow->DrawWaveform();
                    break;

                default:
                    break;
            }

        case WM_VSCROLL:
            pScopeWindow->ProcessVertScrollCommands(hDlg, wParam, lParam);
            break;

        case WM_HSCROLL:
            pScopeWindow->ProcessHorizScrollCommands(hDlg, wParam, lParam);
            break;

        case WM_PAINT:
            ASSERT(pScopeWindow != NULL);
            pScopeWindow->OnPaint();
            break;

        // We stop WM_CLOSE messages going any further by intercepting them
        // and then setting an abort signal flag in the owning renderer so
        // that it knows the user wants to quit. The renderer can then
        // go about deleting it's interfaces and the window helper object
        // which will eventually cause a WM_DESTROY message to arrive. To
        // make it look as though the window has been immediately closed
        // we hide it and then wait for the renderer to catch us up

        case WM_CLOSE:
            ASSERT(pScopeWindow != NULL);
            pScopeWindow->OnClose();
            return (LRESULT) 0;

        // We receive a WM_GOODBYE window message (synchronously) from the
        // window object destructor in which case we do actually destroy
        // the window and complete the process in the WM_DESTROY message

        case WM_GOODBYE:
            ASSERT(pScopeWindow != NULL);
            pScopeWindow->UninitialiseWindow();
            PostQuitMessage(FALSE);
            EndDialog (hDlg, 0);
            return (LRESULT) 0;

        default:
            break;
    }
    return (LRESULT) 0;

} // ScopeDlgProc

//
// MessageLoop
//
// This is the standard windows message loop for our worker thread. It sits
// in a normal processing loop dispatching messages until it receives a quit
// message, which may be generated through the owning object's destructor
//
HRESULT CScopeWindow::MessageLoop()
{
    MSG Message;        // Windows message structure
    DWORD dwResult;     // Wait return code value

    HANDLE hWait[] = { (HANDLE) m_RenderEvent };

    // Enter the modified message loop

    while (TRUE) {

        // We use this to wait for two different kinds of events, the first

// are the normal windows messages, the other is an event that will
        // be signaled when a sample is ready

        dwResult = MsgWaitForMultipleObjects((DWORD) 1,     // Number events
                                             hWait,         // Event handle
                                             FALSE,         // Wait for either
                                             INFINITE,      // No timeout
                                             QS_ALLINPUT);  // All messages

        // Has a sample become ready to render
        if (dwResult == WAIT_OBJECT_0) {
            DrawWaveform();
        }

        // Process the thread's window message

        while (PeekMessage(&Message,NULL,(UINT) 0,(UINT) 0,PM_REMOVE)) {

            // Check for the WM_QUIT message

            if (Message.message == WM_QUIT) {
                return NOERROR;
            }

            // Send the message to the window procedure

            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
    }

} // MessageLoop

//
// WindowMessageLoop
//
// This creates a window and processes it's messages on a separate thread
//
DWORD __stdcall CScopeWindow::WindowMessageLoop(LPVOID lpvThreadParm)
{
    CScopeWindow *pScopeWindow;     // The owner renderer object

    // Cast the thread parameter to be our owner object
    pScopeWindow = (CScopeWindow *) lpvThreadParm;

    pScopeWindow->m_hwndDlg =
        CreateDialogParam(
            pScopeWindow->m_hInstance,        // Handle of app instance
            MAKEINTRESOURCE (IDD_SCOPEDIALOG),// Dialog box template
            NULL,                        // Handle of owner window
            (DLGPROC) ScopeDlgProc,        // Address of dialog procedure
            (LONG) pScopeWindow                 // Initialization value
        );

    if (pScopeWindow->m_hwndDlg != NULL)
    {
        // Initialise the window, then signal the constructor that it can
        // continue and then unlock the object's critical section and
        // process messages

        pScopeWindow->InitialiseWindow(pScopeWindow->m_hwndDlg);
    }

    pScopeWindow->m_SyncWorker.Set();

    if (pScopeWindow->m_hwndDlg != NULL)
    {
        pScopeWindow->MessageLoop();
    }

    ExitThread(TRUE);
    return TRUE;

} // WindowMessageLoop

//
// OnPaint
//
// WM_PAINT message
//
BOOL CScopeWindow::OnPaint()
{
    DrawWaveform();
    return TRUE;

} // OnPaint

//
// ClearWindow
//
// Clear the scope to black and draw the center tickmarks
//
void CScopeWindow::ClearWindow(HDC hdc)
{
    int y = m_Height / 2;

    SetMapMode (hdc, MM_TEXT);
    SetWindowOrgEx (hdc, 0, 0, NULL);
    SetViewportOrgEx (hdc, 0, 0, NULL);

    // Paint the entire window black
    PatBlt(hdc,            // Handle of device context
           (INT) 0,        // x-coord of upper-left corner
           (INT) 0,        // y-coord of upper-left corner
           m_Width,        // Width of rectangle to be filled
           m_Height,       // Height of rectangle to be filled
           BLACKNESS);     // Raster operation code

    // Draw the horizontal line
    HPEN hPenOld = (HPEN) SelectObject (hdc, m_hPenTicks);
    MoveToEx (hdc, 0, y, NULL);
    LineTo (hdc, m_Width, y);

    // Draw the tickmarks
    float inc = (float) m_Width / 10;
    int pos, j;
    int TickPoint;
    for (j = 0; j <= 10; j++) {
        if (j == 0 || j == 5 || j == 10)
            TickPoint =  m_Height / 15;
        else
            TickPoint = m_Height / 30;
        pos = (int) (j * inc);
        MoveToEx (hdc, pos, y + TickPoint, NULL);
        LineTo (hdc, pos, y - TickPoint);
    }
    SelectObject (hdc, hPenOld);

} // ClearWindow

//
// DrawPartialWaveform
//
// Draw a part of the Oscilloscope waveform - IndexStart and IndexEnd
// are pointers into the m_pPoints array (in LOGICAL COORDINATES)
// while ViewpointStart and ViewpointEnd are in SCREEN COORDINATES
//
void CScopeWindow::DrawPartialWaveform(HDC hdc,
                                       int IndexStart,
                                       int IndexEnd,
                                       int ViewportStart,
                                       int ViewportEnd)
{
    int nPoints = IndexEnd - IndexStart;
    int nViewportWidth = ViewportEnd - ViewportStart;
    ASSERT (IndexStart + nPoints < m_nPoints);

    // Origin at lower left, x increases up, y increases to right
    SetMapMode (hdc, MM_ANISOTROPIC);

    SetWindowOrgEx (hdc, IndexStart, 0, NULL);
    SetWindowExtEx (hdc, nPoints, (int) (m_MaxValue / GainEntries[m_LGain].GainValue), NULL);
    SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL);
    SetViewportOrgEx (hdc, ViewportStart, m_LOffset + m_Height / 2, NULL);

    HPEN OldPen = (HPEN) SelectObject (hdc, m_hPen1);
    Polyline (hdc, m_pPoints1 + IndexStart, nPoints + 1);
    SelectObject (hdc, OldPen);

    if (m_pPoints2) {
        SetWindowOrgEx (hdc, IndexStart, 0, NULL);
        SetWindowExtEx (hdc, nPoints, (int) (m_MaxValue / GainEntries[m_RGain].GainValue), NULL);
        SetViewportExtEx (hdc, nViewportWidth, -m_Height / 2, NULL);
        SetViewportOrgEx (hdc, ViewportStart, m_ROffset + m_Height / 2, NULL);

        HPEN OldPen = (HPEN) SelectObject (hdc, m_hPen2);
        Polyline (hdc, m_pPoints2 + IndexStart, nPoints + 1);
        SelectObject (hdc, OldPen);
    }

} // DrawPartialWaveform

//
// DrawWaveform
//
// Draw the full Oscilloscope waveform
//
void CScopeWindow::DrawWaveform(void)
{
    CAutoLock lock(m_pRenderer);
    TCHAR szT[40];

    if (m_pPoints1 == NULL)
        return;

    HDC hdc = GetWindowDC (m_hwnd);  // WindowDC has clipping region
    HDC hdcT = CreateCompatibleDC (hdc);
    HBITMAP hBitmapOld = (HBITMAP) SelectObject (hdcT, m_hBitmap);

    ClearWindow (hdcT);

    int StartOffset;
    int IndexEdge;
    int IndexStart1, IndexEnd1;
    int IndexStart2, IndexEnd2;
    int PointsToDisplay, PointsToDisplay1, PointsToDisplay2;
    int ViewportBreak;
    int OffsetTimeMS;
    BOOL fWraps;                // If segment to display wraps around 0

    PointsToDisplay = m_nPoints / Timebases [m_nTimebase].TBDivisor;

    StartOffset = (m_nIndex - 1) - m_TBScroll;
    if (StartOffset < 0)
        StartOffset += m_nPoints;

    if (m_fTriggerPosZeroCrossing) {
        SearchForPosZeroCrossing(StartOffset, &IndexEdge);
        IndexEnd2 = IndexEdge;
    } else {
        IndexEnd2 = StartOffset;
    }
    IndexStart2 = IndexEnd2 - PointsToDisplay;    // can be negative

    if (IndexEnd2 > m_nIndex)
        OffsetTimeMS = (m_nIndex + (m_nPoints - IndexEnd2)) * 1000 / m_nSamplesPerSec;
    else
        OffsetTimeMS = (m_nIndex - IndexEnd2) * 1000 / m_nSamplesPerSec;

    if (fWraps = (IndexStart2 < 0)) {
        IndexStart1 = IndexStart2 + m_nPoints;
        IndexEnd1 = m_nPoints - 1;
        IndexStart2 = 0;

        PointsToDisplay1 = IndexEnd1 - IndexStart1;
    }

    PointsToDisplay2 = IndexEnd2 - IndexStart2;

    if (fWraps) {
        ViewportBreak = (int) (m_Width * (float) PointsToDisplay1 / PointsToDisplay);

        // Draw the first section (from the end of the POINT array)
        DrawPartialWaveform(hdcT,
            IndexStart1, IndexEnd1,     // Index start, Index end
            0, ViewportBreak);          // Window start, Window end

        // Draw the second section (from the beginning of the POINT array)
        DrawPartialWaveform(hdcT,
            IndexStart2, IndexEnd2,     // Index start, Index end
            ViewportBreak, m_Width);    // Window start, Window end
    } else {
        DrawPartialWaveform(hdcT,
            IndexStart2, IndexEnd2,     // Index start, Index end
            0, m_Width);                // Window start, Window end
    }

    SetMapMode (hdcT, MM_TEXT);
    SetWindowOrgEx (hdcT, 0, 0, NULL);
    SetViewportOrgEx (hdcT, 0, 0, NULL);

    BitBlt(hdc,        // Handle of destination device context
            0,        // x-coordinate of upper-left corner
            0,         // y-coordinate of upper-left corner
            m_Width,// Wwidth of destination rectangle
            m_Height,// Height of destination rectangle
            hdcT,// Handle of source device context
            0,          // x-coordinate of source rectangle
            0,          // y-coordinate of source rectangle
            SRCCOPY); // Raster operation code

    SelectObject (hdcT, hBitmapOld);
    DeleteDC (hdcT);
    GdiFlush();
    ReleaseDC (m_hwnd, hdc);

    // Show the size of the last buffer received
    wsprintf (szT, "%d", m_LastMediaSampleSize);
    SetDlgItemText (m_hwndDlg, IDC_BUFSIZE, szT);

    // Show the timestamps
    LONG mSStart;
    LONG mSEnd = m_EndSample.Millisecs();

    CRefTime rt;
    m_pRenderer->StreamTime (rt);

    // Delta is the difference between the last sample received and
    // the current sample playing according to the StreamTime
    LONG mSDelta = mSEnd - rt.Millisecs();
    wsprintf (szT, "%d.%d", mSDelta / 1000, abs (mSDelta) % 1000);
    SetDlgItemText (m_hwndDlg, IDC_TS_DELTA, szT);

    // Show the Delta point on the horizontal trackbar as the selection
    if (mSDelta < 1000) {
        int SelectStart = m_nPoints - (m_nPoints * mSDelta / 1000);
        SelectStart /= 2;
        int SelectEnd = SelectStart + m_nPoints / 100;
        SendMessage(m_hwndTBScroll, TBM_SETSEL, TRUE, MAKELONG (SelectStart, SelectEnd));
    }
    else
        SendMessage(m_hwndTBScroll, TBM_SETSEL, TRUE, 0L); // hide the selection

    // Display the begin and end times of the sweep
    mSEnd -= OffsetTimeMS;
    mSStart = mSEnd - PointsToDisplay * 1000 / m_nSamplesPerSec;

    wsprintf (szT, "%d.%d", mSStart / 1000, abs (mSStart) % 1000);
    SetDlgItemText (m_hwndDlg, IDC_TS_START, szT);

    wsprintf (szT, "%d.%d", mSEnd / 1000, abs (mSEnd) % 1000);
    SetDlgItemText (m_hwndDlg, IDC_TS_LAST, szT);

} // DrawWaveform

//
// AllocWaveBuffers
//
// Allocate a 1 second buffer for each channel
// This is only called when the format changes
// Return TRUE if allocations succeed
//
BOOL CScopeWindow::AllocWaveBuffers()
{
    int j;

    if (m_pPoints1) delete [] m_pPoints1;
    if (m_pPoints2) delete [] m_pPoints2;

    m_pPoints1 = NULL;
    m_pPoints2 = NULL;
    m_nPoints = 0;

    m_nPoints = m_nSamplesPerSec;

    if (m_pPoints1 = new POINT [m_nSamplesPerSec]) {
        m_nPoints = m_nSamplesPerSec;
        for (j = 0; j < m_nSamplesPerSec; j++)
            m_pPoints1[j].x = j;
    }

    if (m_nChannels == 2) {
        if (m_pPoints2 = new POINT [m_nSamplesPerSec])
            for (j = 0; j < m_nSamplesPerSec; j++)
                m_pPoints2[j].x = j;
    }

    // Return TRUE if allocations succeeded
    ASSERT ((m_pPoints1 != NULL) && ((m_nChannels == 2) ? (m_pPoints2 != NULL) : TRUE));
    return ((m_pPoints1 != NULL) && ((m_nChannels == 2) ? (m_pPoints2 != NULL) : TRUE));

} // AllocWaveBuffers

//
// SearchForPosZeroCrossing
//
// Searches backward for a positive going zero crossing in the waveform
//
void CScopeWindow::SearchForPosZeroCrossing(int StartPoint, int * IndexEdge)
{
    if (StartPoint < 0)
        StartPoint = 0;

    int cur, last, j;

    *IndexEdge = StartPoint;

    last = m_pPoints1[StartPoint].y;

    for (j = m_nPoints; j > 0; j--) {
        if (--StartPoint < 0)
            StartPoint = m_nPoints - 1;
        cur = m_pPoints1[StartPoint].y;
        if (cur < 0 && last >= 0) {
            *IndexEdge = StartPoint;
            break;
        }
        last = cur;
    }

} // SearchForPosZeroCrossing

//
// CopyWaveform
//
// Copy the current MediaSample into a POINT array so we can use GDI
// to paint the waveform.  The POINT array contains a 1 second history
// of the past waveform.  The "Y" values are normalized to a range of
// +128 to -127 within the POINT array.
//
void CScopeWindow::CopyWaveform(IMediaSample *pMediaSample)
{
    BYTE *pWave;                // Pointer to image data
    int  nBytes;
    int  nSamplesPerChan;

    pMediaSample->GetPointer(&pWave);
    ASSERT(pWave != NULL);

    nBytes = pMediaSample->GetActualDataLength();
    nSamplesPerChan = nBytes / (m_nChannels * m_nBlockAlign);

    switch (m_nBitsPerSample + m_nChannels) {
        BYTE * pb;
        WORD * pw;

        case 9:
        {   // Mono, 8-bit
            pb = pWave;
            while (nSamplesPerChan--) {
                m_pPoints1[m_nIndex].y = (int)*pb++ - 127;  // Make zero centered
                if (++m_nIndex == m_nSamplesPerSec)
                    m_nIndex = 0;
            }
        }
        break;

        case 10:
        {   // Stereo, 8-bit
            pb = pWave;
            while (nSamplesPerChan--) {
                m_pPoints1[m_nIndex].y = (int)*pb++ - 127; // Make zero centered
                m_pPoints2[m_nIndex].y = (int)*pb++ - 127;
                if (++m_nIndex == m_nSamplesPerSec)
                    m_nIndex = 0;
            }
        }
        break;

        case 17:
        { // Mono, 16-bit
            pw = (WORD *) pWave;
            while (nSamplesPerChan--) {
                m_pPoints1[m_nIndex].y = (int) ((short) *pw++) / 256;
                if (++m_nIndex == m_nSamplesPerSec)
                    m_nIndex = 0;
            }
        }
        break;

        case 18:
        { // Stereo, 16-bit
            pw = (WORD *)pWave;
            while (nSamplesPerChan--) {
                m_pPoints1[m_nIndex].y = (int) ((short) *pw++) / 256;
                m_pPoints2[m_nIndex].y = (int) ((short) *pw++) / 256;
                if (++m_nIndex == m_nSamplesPerSec)
                    m_nIndex = 0;
            }
        }
        break;

        default:
            ASSERT(0);
            break;

    } // End of format switch

} // CopyWaveform

//
// Receive
//
// Called when the input pin receives another sample.
// Copy the waveform to our circular 1 second buffer
//
HRESULT CScopeWindow::Receive(IMediaSample *pSample)
{
    CAutoLock cAutoLock(this);
    ASSERT(pSample != NULL);

    // Has our UI been frozen

    if (m_fFreeze) {
        return NOERROR;
    }

    REFERENCE_TIME tStart, tStop;
    pSample->GetTime (&tStart,&tStop);
    m_StartSample = tStart;
    m_EndSample = tStop;

    // Ignore zero-length samples
    if ((m_LastMediaSampleSize = pSample->GetActualDataLength()) == 0)
        return NOERROR;

    if (m_bStreaming == TRUE) {
        CopyWaveform (pSample);     // Copy data to our circular buffer
        SetEvent(m_RenderEvent);    // Set an event to display the
                                    // new data on another thread
        return NOERROR;
    }
    return NOERROR;

} // Receive

//
// DllRegisterServer
//
// Handles DLL registry
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer


SCOPE.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Oscilloscope filter

// { 35919F40-E904-11ce-8A03-00AA006ECB65 }
DEFINE_GUID(CLSID_Scope,
0x35919f40, 0xe904, 0x11ce, 0x8a, 0x3, 0x0, 0xaa, 0x0, 0x6e, 0xcb, 0x65);

class CScopeFilter;
class CScopeWindow;

// Class supporting the scope input pin

class CScopeInputPin : public CBaseInputPin
{
    friend class CScopeFilter;
    friend class CScopeWindow;

private:

    CScopeFilter *m_pFilter;         // The filter that owns us

public:

    CScopeInputPin(CScopeFilter *pTextOutFilter,
                   HRESULT *phr,
                   LPCWSTR pPinName);
    ~CScopeInputPin();

    // Lets us know where a connection ends
    HRESULT BreakConnect();

    // Check that we can support this input type
    HRESULT CheckMediaType(const CMediaType *pmt);

    // Actually set the current format
    HRESULT SetMediaType(const CMediaType *pmt);

    // IMemInputPin virtual methods

    // Override so we can show and hide the window
    HRESULT Active(void);
    HRESULT Inactive(void);

    // Here's the next block of data from the stream.
    // AddRef it if you are going to hold onto it
    STDMETHODIMP Receive(IMediaSample *pSample);

}; // CScopeInputPin

// This class looks after the management of a window. When the class gets
// instantiated the constructor spawns off a worker thread that does all
// the window work. The original thread waits until it is signaled to
// continue. The worker thread first registers the window class if it
// is not already done. Then it creates a window and sets it's size to
// a default iWidth by iHeight dimensions. The worker thread MUST be the
// one who creates the window as it is the one who calls GetMessage. When
// it has done all this it signals the original thread which lets it
// continue, this ensures a window is created and valid before the
// constructor returns. The thread start address is the WindowMessageLoop
// function. This takes as it's initialisation parameter a pointer to the
// CVideoWindow object that created it, the function also initialises it's
// window related member variables such as the handle and device contexts

// These are the video window styles

const DWORD dwTEXTSTYLES = (WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN);
const DWORD dwCLASSSTYLES = (CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT | CS_OWNDC);
const LPTSTR RENDERCLASS = TEXT("OscilloscopeWindowClass");
const LPTSTR TITLE = TEXT("Oscilloscope");

const int iWIDTH = 320;             // Initial window width
const int iHEIGHT = 240;            // Initial window height
const int WM_GOODBYE (WM_USER + 2); // Sent to close the window

class CScopeWindow : public CCritSec
{
    friend class CScopeInputPin;
    friend class CScopeFilter;

private:

    HINSTANCE m_hInstance;          // Global module instance handle
    CScopeFilter *m_pRenderer;      // The owning renderer object
    HWND m_hwndDlg;                 // Handle for our dialog
    HWND m_hwnd;                    // Handle for the graph window
    HBRUSH m_hBrushBackground;      // Used to paint background
    HPEN m_hPen1;                   // We use two pens for drawing
    HPEN m_hPen2;                   //  the waveforms in the window
    HPEN m_hPenTicks;               // Used to draw ticks at bottom
    HBITMAP m_hBitmap;              // Draw all waveforms into here
    HANDLE m_hThread;               // Our worker thread
    DWORD m_ThreadID;               // Worker thread ID
    CAMEvent m_SyncWorker;          // Synchronise with worker thread
    CAMEvent m_RenderEvent;         // Signals sample to render
    LONG m_Width;                   // Client window width
    LONG m_Height;                  // Client window height
    BOOL m_bActivated;              // Has the window been activated
    CRefTime m_StartSample;         // Most recent sample start time
    CRefTime m_EndSample;           // And it's associated end time
    BOOL m_bStreaming;              // Are we currently streaming
    POINT *m_pPoints1;              // Array of points to graph Channel1
    POINT *m_pPoints2;              // Array of points to graph Channel2
    int m_nPoints;                  // Size of m_pPoints[1|2]
    int m_nIndex;                   // Index of last sample written
    int m_LastMediaSampleSize;      // Size of last MediaSample

    int m_nChannels;                // number of active channels
    int m_nSamplesPerSec;           // Samples per second
    int m_nBitsPerSample;           // Number bits per sample
    int m_nBlockAlign;              // Alignment on the samples
    int m_MaxValue;                 // Max Value of the POINTS array

    int m_LGain;                    // Left channel control settings
    int m_LOffset;                  //  And likewise its offset
    int m_RGain;                    // Right channel control settings
    int m_ROffset;                  //  And likewise its offset
    int m_nTimebase;                // Timebase settings
    BOOL m_fFreeze;                 // Flag toi signal we're UI frozen
    int m_TBScroll;                 // Holds position in scroll range

    // Hold window handles to controls

    HWND m_hwndLGain;
    HWND m_hwndLOffset;
    HWND m_hwndLGainText;
    HWND m_hwndLTitle;
    HWND m_hwndRGain;
    HWND m_hwndROffset;
    HWND m_hwndRGainText;
    HWND m_hwndRTitle;
    HWND m_hwndTimebase;
    HWND m_hwndFreeze;
    HWND m_hwndTBScroll;
    HWND m_hwndTBStart;
    HWND m_hwndTBEnd;
    HWND m_hwndTBDelta;

    BOOL m_fTriggerPosZeroCrossing;

    // These create and manage a video window on a separate thread

    HRESULT UninitialiseWindow();
    HRESULT InitialiseWindow(HWND hwnd);
    HRESULT MessageLoop();

    static DWORD __stdcall WindowMessageLoop(LPVOID lpvThreadParm);

    // Maps windows message loop into C++ virtual methods
    friend LRESULT CALLBACK WndProc(HWND hwnd,      // Window handle
                                    UINT uMsg,      // Message ID
                                    WPARAM wParam,  // First parameter
                                    LPARAM lParam); // Other parameter

    // Called when we start and stop streaming
    HRESULT ResetStreamingTimes();

    // Window message handlers
    BOOL OnClose();
    BOOL OnPaint();

    // Draw the waveform
    void ClearWindow(HDC hdc);
    BOOL AllocWaveBuffers(void);
    void SearchForPosZeroCrossing(int StartPoint, int * IndexEdge);
    void CopyWaveform(IMediaSample *pMediaSample);

    void DrawPartialWaveform(HDC hdc,
                             int IndexStart,
                             int IndexEnd,
                             int ViewportStart,
                             int ViewportEnd);

    void DrawWaveform(void);
    void SetControlRanges(HWND hDlg);
    void SetHorizScrollRange(HWND hDlg);
    void ProcessVertScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam);
    void ProcessHorizScrollCommands(HWND hDlg, WPARAM wParam, LPARAM lParam);

    friend BOOL CALLBACK ScopeDlgProc(HWND hwnd,        // Window handle
                                    UINT uMsg,          // Message ID
                                    WPARAM wParam,      // First parameter
                                    LPARAM lParam);     // Other parameter

public:

    // Constructors and destructors

    CScopeWindow(TCHAR *pName, CScopeFilter *pRenderer, HRESULT *phr);
    virtual ~CScopeWindow();

    HRESULT StartStreaming();
    HRESULT StopStreaming();
    HRESULT InactivateWindow();
    HRESULT ActivateWindow();

    // Called when the input pin receives a sample
    HRESULT Receive(IMediaSample * pIn);

}; // CScopeWindow

// This is the COM object that represents the oscilloscope filter

class CScopeFilter : public CBaseFilter, public CCritSec
{

public:
    // Implements the IBaseFilter and IMediaFilter interfaces

    DECLARE_IUNKNOWN

    STDMETHODIMP Stop();
    STDMETHODIMP Pause();
    STDMETHODIMP Run(REFERENCE_TIME tStart);

public:

    CScopeFilter(LPUNKNOWN pUnk,HRESULT *phr);
    virtual ~CScopeFilter();

    // Return the pins that we support
    int GetPinCount();
    CBasePin *GetPin(int n);

    // This goes in the factory template table to create new instances
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);

    STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);

private:

    // The nested classes may access our private state
    friend class CScopeInputPin;
    friend class CScopeWindow;

    CScopeInputPin *m_pInputPin;   // Handles pin interfaces
    CScopeWindow m_Window;         // Looks after the window

}; // CScopeFilter


SETUP.CPP

  //=========================================================================
  //
  //  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  //  PURPOSE.
  //
  // Copyright (c) 1996 - 1997  Microsoft Corporation.  All Rights Reserved.
  //
  //=========================================================================

  #include <windows.h>
  #include "setup.h"

  // This sample demonstrates how a 3rd party package can set up the DirectXMedia
  // redist. bits from their setup program. This sample assumes the DXMEDIA.EXE
  // is sitting in the same directory as the compiled version of this code.

  #define TITLE "Sample DXMedia Install Package"

  // Forward prototyping
  BOOL CALLBACK DialogProc (HWND, UINT, WPARAM, LPARAM);

  DWORD ExecuteSynchCmdLine (LPTSTR szProgExe, LPTSTR szCmdLine)

    { // ExecuteSynchCmdLine //

      DWORD dwTimeout = 30;
      DWORD dwRC      = WAIT_TIMEOUT;
      MSG   msg;
      DWORD dw;
      STARTUPINFO StartupInfo;
      PROCESS_INFORMATION ProcessInformation;

      ZeroMemory(&StartupInfo, sizeof STARTUPINFO);
      StartupInfo.cb = sizeof STARTUPINFO;
      StartupInfo.wShowWindow = SW_NORMAL;
      ZeroMemory(&ProcessInformation, sizeof PROCESS_INFORMATION);

      TCHAR cPad[MAX_PATH];
      lstrcpy(cPad, " ");
      lstrcat(cPad, szCmdLine);

      if (CreateProcess(szProgExe, cPad, NULL, NULL, NULL, NULL, NULL, NULL, &StartupInfo, &ProcessInformation))

        { // Spin off child process

          while (dwRC == WAIT_TIMEOUT)

            { // Poll status every 'dwTimeout' milliseconds

              dwRC = WaitForSingleObject(ProcessInformation.hProcess, dwTimeout);

              while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                {
                   TranslateMessage(&msg);
                   DispatchMessage(&msg);
                }

            } // Poll status every 'dwTimeout' milliseconds

        } // Spin off child process

    GetExitCodeProcess(ProcessInformation.hProcess, &dw);

    return dw;

    } // ExecuteSynchCmdLine //

  int WINAPI WinMain (HINSTANCE a, HINSTANCE b, LPSTR c, int d)

  {// WinMain //

      HRESULT hr = NOERROR;

  // Prompt to install the package
      if (!DialogBox(a, MAKEINTRESOURCE(IDD_DIALOG1), GetDesktopWindow(), (DLGPROC)DialogProc))
        return FALSE; // Selected CANCEL

      // DXMedia.exe redist. package needs to be in same (current) directory!
      hr = ExecuteSynchCmdLine("dxmedia.exe","-id:ExampleIdentifier");

      switch(hr)

        { // Most common return codes

          case ERROR_SUCCESS:
            MessageBox(GetDesktopWindow(), "Redist. package installed successfully; no reboot required.", TITLE, MB_OK);
            break;

          case ERROR_SUCCESS_REBOOT_REQUIRED:
            MessageBox(GetDesktopWindow(), "Redist. package installed successfully; reboot required!", TITLE, MB_ICONHAND);
            break;

          case E_FAIL:
            MessageBox(GetDesktopWindow(), "Redist. package failed to install! Is dxmedia.exe in the same directory?", TITLE, MB_ICONHAND);
            break;

          default:

            TCHAR cDbgMsg[255];
            wsprintf(cDbgMsg, "Redist. package returned an unexpected error code (hr=0x%08X)", hr);
            MessageBox(GetDesktopWindow(), cDbgMsg, TITLE, MB_ICONHAND);
            break;

        } // Most common return codes

      return FALSE;

    }// WinMain //

  BOOL CALLBACK DialogProc (HWND hWnd, UINT mMsg, WPARAM wParam, LPARAM lParam)

    { // DialogProc //

      switch(mMsg)

        { // Msg handling

          case WM_COMMAND:

            switch(wParam)

              { // Command msg handling

                case IDOK:
                  EndDialog(hWnd,1);
                  break;

                case IDCANCEL:
                  EndDialog(hWnd,0);
                  break;

              } // Command msg handling

            break;

        } // Msg handling

      return 0;

    } // DialogProc //


SETUP.H

#define IDD_DIALOG1                     101
#define IDC_STATIC                      -1
#define IDI_SETUP                       0


SHOWSTRM.CPP

#include <windows.h>
#include <stdio.h>
#include "ddraw.h"
#include "mmstream.h"
#include "amstream.h"
#include "ddstream.h"

#define RELEASE(x) if (x) (x)->Release();
#define CHECK_ERROR(x)     \
   if (FAILED(hr = (x))) { \
       printf(#x "  failed with HRESULT(0x%8.8X)\n", hr); \
       goto Exit;          \
   }

HRESULT OpenMMStream(const char * pszFileName, IDirectDraw *pDD, IMultiMediaStream **ppMMStream)
{
    *ppMMStream = NULL;
    IAMMultiMediaStream *pAMStream;
    HRESULT hr;

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
 IID_IAMMultiMediaStream, (void **)&pAMStream));
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_READ, 0, NULL));
    CHECK_ERROR(pAMStream->AddMediaStream(pDD, &MSPID_PrimaryVideo, 0, NULL));
    CHECK_ERROR(pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL));

    WCHAR       wPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, pszFileName, -1, wPath, sizeof(wPath)/sizeof(wPath[0]));

    CHECK_ERROR(pAMStream->OpenFile(wPath, 0));

    *ppMMStream = pAMStream;
    pAMStream->AddRef();

Exit:
    if (pAMStream == NULL) {
printf("Could not create a CLSID_MultiMediaStream object\n"
       "Check you have run regsvr32 amstream.dll\n");
    }
    RELEASE(pAMStream);
    return hr;
}

HRESULT RenderStreamToSurface(IDirectDraw *pDD, IDirectDrawSurface *pPrimary,
      IMultiMediaStream *pMMStream)
{

    HRESULT hr;
    IMediaStream *pPrimaryVidStream = NULL;
    IDirectDrawMediaStream *pDDStream = NULL;
    IDirectDrawSurface *pSurface = NULL;
    IDirectDrawStreamSample *pSample = NULL;

    RECT rect;

    CHECK_ERROR(pMMStream->GetMediaStream(MSPID_PrimaryVideo, &pPrimaryVidStream));
    CHECK_ERROR(pPrimaryVidStream->QueryInterface(IID_IDirectDrawMediaStream, (void **)&pDDStream));

    CHECK_ERROR(pDDStream->CreateSample(NULL, NULL, 0, &pSample));
    CHECK_ERROR(pSample->GetSurface(&pSurface, &rect));

    CHECK_ERROR(pMMStream->SetState(STREAMSTATE_RUN));

    while (true) {
if (pSample->Update(0, NULL, NULL, 0) != S_OK) {
    break;
}
pPrimary->Blt(&rect, pSurface, &rect, DDBLT_WAIT, NULL);
    }

Exit:
    RELEASE(pPrimaryVidStream);
    RELEASE(pDDStream);
    RELEASE(pSample);
    RELEASE(pSurface);

    return hr;
}

int _cdecl
main(
    int argc,
    char *argv[]
    )
{
    if (argc < 2) {
printf("Usage : showstrm movie.ext\n");
exit(0);
    }
    CoInitialize(NULL);
    IDirectDraw *pDD;

    HRESULT hr = DirectDrawCreate(NULL, &pDD, NULL);
    if (SUCCEEDED(hr)) {
DDSURFACEDESC ddsd;
IDirectDrawSurface *pPrimarySurface;

pDD->SetCooperativeLevel(GetDesktopWindow(), DDSCL_NORMAL);

ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
hr = pDD->CreateSurface(&ddsd, &pPrimarySurface, NULL);
if (SUCCEEDED(hr)) {
    IMultiMediaStream *pMMStream;
    hr = OpenMMStream(argv[1], pDD, &pMMStream);
    if (SUCCEEDED(hr)) {
RenderStreamToSurface(pDD, pPrimarySurface, pMMStream);
pMMStream->Release();
    }
    pPrimarySurface->Release();
}
pDD->Release();
    } else {
printf("Could not open DirectDraw - check it is installed\n");
    }
    CoUninitialize();
    return 0;
}


ISYNTH.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// isynth.h
//
// A custom interface to allow the user to adjust the frequency

#ifndef __ISYNTH__
#define __ISYNTH__

#ifdef __cplusplus
extern "C" {
#endif

//
// ISynth's GUID
//
// {FFC08882-CDAC-11ce-8A03-00AA006ECB65}
DEFINE_GUID(IID_ISynth,
0xffc08882, 0xcdac, 0x11ce, 0x8a, 0x3, 0x0, 0xaa, 0x0, 0x6e, 0xcb, 0x65);

//
// ISynth
//
DECLARE_INTERFACE_(ISynth, IUnknown) {

    STDMETHOD(get_Frequency) (THIS_
                int *Frequency          /* [out] */    // the current frequency
             ) PURE;

    STDMETHOD(put_Frequency) (THIS_
                int    Frequency        /* [in] */    // Change to this frequency
             ) PURE;

    STDMETHOD(get_Waveform) (THIS_
                int *Waveform           /* [out] */    // the current Waveform
             ) PURE;

    STDMETHOD(put_Waveform) (THIS_
                int    Waveform         /* [in] */    // Change to this Waveform
             ) PURE;

    STDMETHOD(get_Channels) (THIS_
                int *Channels           /* [out] */    // the current Channels
             ) PURE;

    STDMETHOD(put_Channels) (THIS_
                int    Channels         /* [in] */    // Change to this Channels
             ) PURE;

    STDMETHOD(get_BitsPerSample) (THIS_
                int *BitsPerSample      /* [out] */    // the current BitsPerSample
             ) PURE;

    STDMETHOD(put_BitsPerSample) (THIS_
                int    BitsPerSample    /* [in] */    // Change to this BitsPerSample
             ) PURE;

    STDMETHOD(get_SamplesPerSec) (THIS_
                 int *SamplesPerSec     /* [out] */    // the current SamplesPerSec
             ) PURE;

    STDMETHOD(put_SamplesPerSec) (THIS_
                  int    SamplesPerSec  /* [in] */    // Change to this SamplesPerSec
             ) PURE;

    STDMETHOD(get_Amplitude) (THIS_
                  int *Amplitude        /* [out] */    // the current Amplitude
             ) PURE;

    STDMETHOD(put_Amplitude) (THIS_
                  int    Amplitude      /* [in] */    // Change to this Amplitude
              ) PURE;

    STDMETHOD(get_SweepRange) (THIS_
                  int *SweepStart,      /* [out] */
                  int *SweepEnd         /* [out] */
             ) PURE;

    STDMETHOD(put_SweepRange) (THIS_
                  int    SweepStart,    /* [in] */
                  int    SweepEnd       /* [in] */
             ) PURE;

};

#ifdef __cplusplus
}
#endif

#endif // __ISYNTH__


RESOURCE.H

//{{NO_DEPENDENCIES}}
//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// Microsoft Visual C++ generated include file.
// Used by synth.rc
//
#define IDD_BALLPROP                    101
#define IDD_SYNTHPROP1                  101
#define IDC_FREQUENCYTEXT               1002
#define IDC_FREQUENCY                   1003
#define IDC_AMPLITUDETEXT               1003
#define IDC_FREQTRACKBAR                1004
#define IDC_FREQUENCYGROUP              1005
#define IDC_WAVEFORMGROUP               1006
#define IDC_WAVESINE                    1007
#define IDC_WAVESQUARE                  1008
#define IDC_WAVESAWTOOTH                1009
#define IDC_WAVESWEEP                   1010
#define IDC_AMPLITUDETRACKBAR           1011
#define IDC_CHANNELS1                   1012
#define IDC_CHANNELS2                   1013
#define IDC_BITSPERSAMPLEGROUP          1014
#define IDC_BITSPERSAMPLE8              1015
#define IDC_BITSPERSAMPLE16             1016
#define IDC_SAMPLINGFREQUENCYGROUP      1017
#define IDC_SAMPLINGFREQ11              1018
#define IDC_SAMPLINGFREQ22              1019
#define IDC_SAMPLINGFREQ44              1020
#define IDC_AMPLITUDEGROUP              1021
#define IDC_SWEEP                       1023
#define IDS_SYNTHPROPNAME               1024
#define IDS_STATIC                      -1
#define IDC_CHANNELSGROUP               -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1025
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SYNTH.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// synth.cpp
//
// Audio Signal Generator Source Filter

#include <windows.h>
#include <streams.h>
#include <math.h>

#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#endif

#define _AUDIOSYNTH_IMPLEMENTATION_

#include "isynth.h"
#include "synth.h"
#include "synthprp.h"

// setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{ &MEDIATYPE_Audio      // clsMajorType
, &MEDIASUBTYPE_NULL }; // clsMinorType

const AMOVIESETUP_PIN sudOpPin =
{ L"Output"          // strName
, FALSE              // bRendered
, TRUE               // bOutput
, FALSE              // bZero
, FALSE              // bMany
, &CLSID_NULL        // clsConnectsToFilter
, L"Input"           // strConnectsToPin
, 1                  // nTypes
, &sudOpPinTypes };  // lpTypes

const AMOVIESETUP_FILTER sudSynth =
{ &CLSID_SynthFilter     // clsID
, L"Audio Synthesizer" // strName
, MERIT_UNLIKELY       // dwMerit
, 1                    // nPins
, &sudOpPin };         // lpPin

// -------------------------------------------------------------------------
// g_Templates
// -------------------------------------------------------------------------
// COM global table of objects in this dll

CFactoryTemplate g_Templates[] = {

    { L"Audio Synthesizer"
    , &CLSID_SynthFilter
    , CSynthFilter::CreateInstance
    , NULL
    , &sudSynth }
  ,
    { L"Audio Synthesizer Property Page"
    , &CLSID_SynthPropertyPage
    , CSynthProperties::CreateInstance }

};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

// -------------------------------------------------------------------------
// CSynthFilter, the main filter object
// -------------------------------------------------------------------------
//
// CreateInstance
//
// The only allowed way to create Synthesizers

CUnknown * WINAPI CSynthFilter::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr) {

    CUnknown *punk = new CSynthFilter(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return punk;
}

//
// CSynthFilter::Constructor
//
// initialise a CSynthStream object so that we have a pin.

CSynthFilter::CSynthFilter(LPUNKNOWN lpunk, HRESULT *phr)
    : CSource(NAME("Audio Synthesizer Filter"),lpunk, CLSID_SynthFilter)
    , CPersistStream(lpunk, phr)
{
    CAutoLock l(&m_cStateLock);

    m_paStreams    = (CSourceStream **) new CSynthStream*[1];
    if (m_paStreams == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    m_paStreams[0] = new CSynthStream(phr, this, L"Audio Synth Stream");
    if (m_paStreams[0] == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    if (SUCCEEDED(*phr)) {
ASSERT(m_Synth);
m_Channels = 1;
m_SamplesPerSec = 11025;
m_BitsPerSample = 8;
m_Synth->put_SynthFormat(m_Channels, m_BitsPerSample, m_SamplesPerSec);
    }
}

//
// CSynthFilter::Destructor
//
CSynthFilter::~CSynthFilter(void) {

    //
    //  Base class will free our pins
    //
}

//
// NonDelegatingQueryInterface
//
// Reveal our property page, persistance, and control interfaces

STDMETHODIMP CSynthFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
    CAutoLock l(&m_cStateLock);

    if (riid == IID_ISynth) {
        return GetInterface((ISynth *) this, ppv);
    }
    else if (riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *) this, ppv);
    }
    else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else {
        return CSource::NonDelegatingQueryInterface(riid, ppv);
    }
}

//
// GetPages
//
STDMETHODIMP CSynthFilter::GetPages(CAUUID * pPages) {

    CAutoLock l(&m_cStateLock);

    pPages->cElems = 1;
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }
    *(pPages->pElems) = CLSID_SynthPropertyPage;

    return NOERROR;

}

// -------------------------------------------------------------------------
// --- IPersistStream ---
// -------------------------------------------------------------------------

#define WRITEOUT(var)   hr = pStream->Write(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

#define READIN(var)     hr = pStream->Read(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

STDMETHODIMP CSynthFilter::GetClassID(CLSID *pClsid)
{
    return CBaseFilter::GetClassID(pClsid);
}

int CSynthFilter::SizeMax ()
{
    return sizeof (int) * 8;
}

HRESULT CSynthFilter::WriteToStream(IStream *pStream)
{
    HRESULT hr;
    int i, k;

    get_Frequency (&i);  // don't we wish we'd used a structure, now?
    WRITEOUT(i);
    get_Waveform (&i);
    WRITEOUT(i);
    get_Channels (&i);
    WRITEOUT(i);
    get_BitsPerSample (&i);
    WRITEOUT(i);
    get_SamplesPerSec (&i);
    WRITEOUT(i);
    get_Amplitude (&i);
    WRITEOUT(i);
    get_SweepRange (&i, &k);
    WRITEOUT(i);
    WRITEOUT(k);

    return hr;
}

HRESULT CSynthFilter::ReadFromStream(IStream *pStream)
{
    HRESULT hr;
    int i, k;

    READIN(i);
    put_Frequency(i);
    READIN(i);
    put_Waveform (i);
    READIN(i);
    put_Channels (i);
    READIN(i);
    put_BitsPerSample (i);
    READIN(i);
    put_SamplesPerSec (i);
    READIN(i);
    put_Amplitude (i);
    READIN(i);
    READIN(k);
    put_SweepRange (i, k);

    return hr;
}

// -------------------------------------------------------------------------
// ISynth, the control interface for the synthesizer
// -------------------------------------------------------------------------

//
// get_Frequency
//
STDMETHODIMP CSynthFilter::get_Frequency(int *Frequency) {

    m_Synth->get_Frequency(Frequency);

    DbgLog((LOG_TRACE, 3, TEXT("get_Frequency: %d"), *Frequency));

    return NOERROR;
}

//
// put_Frequency
//
STDMETHODIMP CSynthFilter::put_Frequency(int Frequency) {

    m_Synth->put_Frequency (Frequency);

    DbgLog((LOG_TRACE, 3, TEXT("put_Frequency: %d"), Frequency));

    return NOERROR;
}

//
// get_Waveform
//
STDMETHODIMP CSynthFilter::get_Waveform(int *Waveform) {

    m_Synth->get_Waveform (Waveform);

    DbgLog((LOG_TRACE, 3, TEXT("get_Waveform: %d"), *Waveform));

    return NOERROR;
}

//
// put_Waveform
//
STDMETHODIMP CSynthFilter::put_Waveform(int Waveform) {

    m_Synth->put_Waveform (Waveform);

    DbgLog((LOG_TRACE, 3, TEXT("put_Waveform: %d"), Waveform));

    return NOERROR;
}

//
// get_Channels
//
STDMETHODIMP CSynthFilter::get_Channels(int *Channels) {

    *Channels = m_Channels;

    DbgLog((LOG_TRACE, 3, TEXT("get_Channels: %d"), *Channels));

    return NOERROR;
}

//
// If the format changes, we need to reconnect
//
void CSynthFilter::ReconnectWithNewFormat(void) {

//    CAutoLock l(&m_SynthLock);

    HRESULT hr;
    FILTER_STATE  State;

    CBasePin *pPin = GetPin(0);

    // Get the state and confirm that the graph is stopped
    GetState (0, &State);
    if (State != State_Stopped && pPin->GetConnected()) {
// let's attempt a dynamic connection
CMediaType mtNew;

// !!! better way to get back to a CSynthStream???
CSynthStream * pStream = (CSynthStream *) pPin;
pStream->GetMediaType(&mtNew);

// !!! does this really mean they'll accept a dynamic format change?
hr = pPin->GetConnected()->QueryAccept(&mtNew);

DbgLog((LOG_TRACE,2,TEXT("Attempting format change: queryAccept returned %x"), hr));

if (hr == S_OK) {
    // actually change what's being pushed
    m_Synth->put_SynthFormat(m_Channels, m_BitsPerSample, m_SamplesPerSec);
} else {
    // !!! couldn't change right now, we should really schedule a reconnect
    // for the next time the graph is stopped.
}

        return;
    }

    if (!m_pGraph)
        return;

    hr = GetFilterGraph()->Reconnect (pPin);       // Renegotiate the format
    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, 1, TEXT("Reconnect failed, err=%x"), hr));
        return;
    }
}

//
// put_Channels
//
STDMETHODIMP CSynthFilter::put_Channels(int Channels) {

    m_Channels = Channels;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_Channels: %d"), Channels));

    return NOERROR;
}

//
// get_BitsPerSample
//
STDMETHODIMP CSynthFilter::get_BitsPerSample(int *BitsPerSample) {

    *BitsPerSample = m_BitsPerSample;

    DbgLog((LOG_TRACE, 3, TEXT("get_BitsPerSample: %d"), *BitsPerSample));

    return NOERROR;
}

//
// put_BitsPerSample
//
STDMETHODIMP CSynthFilter::put_BitsPerSample(int BitsPerSample) {

    m_BitsPerSample = BitsPerSample;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_BitsPerSample: %d"), BitsPerSample));

    return NOERROR;
}

//
// get_SamplesPerSec
//
STDMETHODIMP CSynthFilter::get_SamplesPerSec(int *SamplesPerSec) {

    *SamplesPerSec = m_SamplesPerSec;

    DbgLog((LOG_TRACE, 3, TEXT("get_SamplesPerSec: %d"), *SamplesPerSec));

    return NOERROR;
}

//
// put_SamplesPerSec
//
STDMETHODIMP CSynthFilter::put_SamplesPerSec(int SamplesPerSec) {

    m_SamplesPerSec = SamplesPerSec;

    ReconnectWithNewFormat ();

    DbgLog((LOG_TRACE, 3, TEXT("put_SamplesPerSec: %d"), SamplesPerSec));

    return NOERROR;
}

//
// get_Amplitude
//
STDMETHODIMP CSynthFilter::get_Amplitude(int *Amplitude) {

    m_Synth->get_Amplitude (Amplitude);

    DbgLog((LOG_TRACE, 3, TEXT("get_Amplitude: %d"), *Amplitude));

    return NOERROR;
}

//
// put_Amplitude
//
STDMETHODIMP CSynthFilter::put_Amplitude(int Amplitude) {

    m_Synth->put_Amplitude (Amplitude);

    DbgLog((LOG_TRACE, 3, TEXT("put_Amplitude: %d"), Amplitude));

    return NOERROR;
}

//
// get_SweepRange
//
STDMETHODIMP CSynthFilter::get_SweepRange(int *SweepStart, int *SweepEnd) {

    m_Synth->get_SweepRange (SweepStart, SweepEnd);

    DbgLog((LOG_TRACE, 3, TEXT("get_SweepStart: %d %d"), *SweepStart, *SweepEnd));

    return NOERROR;
}

//
// put_SweepRange
//
STDMETHODIMP CSynthFilter::put_SweepRange(int SweepStart, int SweepEnd) {

    m_Synth->put_SweepRange (SweepStart, SweepEnd);

    DbgLog((LOG_TRACE, 3, TEXT("put_SweepRange: %d %d"), SweepStart, SweepEnd));

    return NOERROR;
}

// -------------------------------------------------------------------------
// CSynthStream, the output pin
// -------------------------------------------------------------------------

//
// CSynthStream::Constructor
//

CSynthStream::CSynthStream(HRESULT *phr, CSynthFilter *pParent, LPCWSTR pName)
    : CSourceStream(NAME("Audio Synth output pin"),phr, pParent, pName) {

    CAutoLock l(m_pFilter->pStateLock());

    {
        CAutoLock l(&m_cSharedState);

        m_Synth = new CAudioSynth( );
        pParent->m_Synth = m_Synth;
        if (m_Synth == NULL) {
            *phr = E_OUTOFMEMORY;
            return;
        }
m_pParent = pParent;
    }
}

//
// CSynthStream::Destructor
//
CSynthStream::~CSynthStream(void) {

    CAutoLock l(&m_cSharedState);

    delete m_Synth;
}

//
// FillBuffer
//
// Stuffs the buffer with data
HRESULT CSynthStream::FillBuffer(IMediaSample *pms) {

    BYTE *pData;
    long lDataLen;
    int nSamplesPerSec;
    int nBitsPerSample;
    int nChannels;
    BOOL fNewFormat = FALSE;

    pms->GetPointer(&pData);
    lDataLen = pms->GetSize();

    CAutoLock lShared(&m_cSharedState);
    m_Synth->FillAudioBuffer (pData, lDataLen, &fNewFormat);

    if (fNewFormat) {
CMediaType mtNew;
GetMediaType(&mtNew);

pms->SetMediaType(&mtNew);

DbgLog((LOG_TRACE,2,TEXT("Sending buffer with new media type")));
    }

    CRefTime rtStart  = m_rtSampleTime;  // the current time is the sample's start

    m_Synth->get_SamplesPerSec (&nSamplesPerSec);
    m_Synth->get_BitsPerSample (&nBitsPerSample);
    m_Synth->get_Channels (&nChannels);

    m_rtSampleTime += (UNITS * lDataLen /
            (nSamplesPerSec * nChannels * nBitsPerSample / 8));

    pms->SetTime((REFERENCE_TIME*)&rtStart,
                 (REFERENCE_TIME*)&m_rtSampleTime);

    return NOERROR;
}

//
// Format Support
//

//
// GetMediaType
//
HRESULT CSynthStream::GetMediaType(CMediaType *pmt) {

    CAutoLock l(m_pFilter->pStateLock());

    WAVEFORMATEX *pwf = (WAVEFORMATEX *) pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));

    pwf->wFormatTag = WAVE_FORMAT_PCM;
    pwf->nChannels = (WORD) m_pParent->m_Channels;
    pwf->nSamplesPerSec = (DWORD) m_pParent->m_SamplesPerSec;
    pwf->wBitsPerSample = (WORD) m_pParent->m_BitsPerSample;
    pwf->nBlockAlign = pwf->wBitsPerSample * pwf->nChannels / 8;
    pwf->nAvgBytesPerSec = (int) ((DWORD) pwf->nBlockAlign *
                           pwf->nSamplesPerSec);
    pwf->cbSize = 0;

    return CreateAudioMediaType(pwf, pmt, FALSE);
}

//
// CheckMediaType
//
// Returns E_INVALIDARG if the mediatype is not acceptable, S_OK if it is
HRESULT CSynthStream::CheckMediaType(const CMediaType *pMediaType) {

    CAutoLock l(m_pFilter->pStateLock());

    //  Check that's Audio and that the format block
    //  has the WAVEFORMATEX structure (indicated by a format type
    //  GUID of FORMAT_WaveFormatEx)

    if ((*pMediaType->Type() != MEDIATYPE_Audio) ||
        (*pMediaType->FormatType() != FORMAT_WaveFormatEx))
        return E_INVALIDARG;

    WAVEFORMATEX * pwfx  = (WAVEFORMATEX *)pMediaType->Format();

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
return E_INVALIDARG;

    // !!! check 8/16, 1/2 channel

    // Check for the subtypes we support

    // Get the format area of the media type

    // !!! if we're going to allow arbitrary media types here, we have to actually
    // look at SetMediaType to see what we've agreed on!

    return S_OK;  // This format is acceptable.
}

//
// DecideBufferSize
//
// This will always be called after the format has been sucessfully
// negotiated. So we have a look at m_mt to see what format we agreed to.
// Then we can ask for buffers of the correct size to contain them.
HRESULT CSynthStream::DecideBufferSize(IMemAllocator *pAlloc,
                                       ALLOCATOR_PROPERTIES *pProperties)
{
    CAutoLock l(m_pFilter->pStateLock());
    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    pProperties->cbBuffer = WaveBufferSize;

    int nBitsPerSample;
    int nSamplesPerSec;
    int nChannels;

    m_Synth->get_SamplesPerSec (&nSamplesPerSec);
    m_Synth->get_BitsPerSample (&nBitsPerSample);
    m_Synth->get_Channels (&nChannels);

    pProperties->cBuffers = nChannels * (nSamplesPerSec / pProperties->cbBuffer) * (nBitsPerSample / 8);
    // Get 1/2 second worth of buffers
    pProperties->cBuffers /= 2;
    if (pProperties->cBuffers < 1)
        pProperties->cBuffers = 1 ;

    // Ask the allocator to reserve us the memory

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (FAILED(hr)) {
        return hr;
    }

    // Is this allocator unsuitable

    if (Actual.cbBuffer < pProperties->cbBuffer) {
        return E_FAIL;
    }
    return NOERROR;
}

//
// SetMediaType
//
// Overriden from CBasePin.
HRESULT CSynthStream::SetMediaType(const CMediaType *pMediaType) {

    CAutoLock l(m_pFilter->pStateLock());

    HRESULT hr;         // return code from base class calls

    // Pass the call up to my base class
    hr = CSourceStream::SetMediaType(pMediaType);
    if (SUCCEEDED(hr))
        return NOERROR;
    else
        return hr;

}

//
// OnThreadCreate
//
// as we go active reset the stream time to zero
HRESULT CSynthStream::OnThreadCreate(void) {

    CAutoLock lShared(&m_cSharedState);

    m_rtSampleTime = 0;

    return NOERROR;
}

//
// Active
//
// Send a message to the property page telling it to disable
// buttons which change the format when the graph starts running
HRESULT CSynthStream::Active  (void) {
    m_Synth->AllocWaveCache();

    return CSourceStream::Active();
}

//
// Inactive
//
// Send a message to the property page telling it to enable
// buttons which change the format when the graph stops running
HRESULT CSynthStream::Inactive  (void) {
    return CSourceStream::Inactive();
}

// -------------------------------------------------------------------------
// CAudioSynth
// -------------------------------------------------------------------------
// Object that knows nothing about ActiveMovie, but just synthesizes
// waveforms

CAudioSynth::CAudioSynth(
                int Frequency,
                int Waveform,
                int iBitsPerSample,
                int iChannels,
                int iSamplesPerSec,
                int iAmplitude
                )
    : m_bWaveCache(NULL),
      m_wWaveCache(NULL)
{

    ASSERT(Waveform >= WAVE_SINE);
    ASSERT(Waveform <  WAVE_LAST);

    m_iFrequency = Frequency;
    m_iWaveform = Waveform;
    m_iAmplitude = iAmplitude;
    m_iSweepStart = DefaultSweepStart;
    m_iSweepEnd = DefaultSweepEnd;

    // init our WAVEFORMATEX structure
    wfex.wFormatTag = WAVE_FORMAT_PCM;
    wfex.wBitsPerSample = iBitsPerSample;
    wfex.nChannels = iChannels;
    wfex.nSamplesPerSec = iSamplesPerSec;
    wfex.nBlockAlign = wfex.wBitsPerSample * wfex.nChannels / 8;
    wfex.nAvgBytesPerSec = ((DWORD) wfex.nBlockAlign *
                           wfex.nSamplesPerSec);
    wfex.cbSize = 0;
}

CAudioSynth::~CAudioSynth()
{
    if (m_bWaveCache) {
        delete[] m_bWaveCache;
    }

    if (m_wWaveCache) {
        delete[] m_wWaveCache;
    }
}

//
// AllocWaveCache
//
//
void CAudioSynth::AllocWaveCache (void) {
    wfexLast = wfex;

    m_iWaveCacheCycles = m_iFrequency;
    m_iWaveCacheSize = (int) wfex.nSamplesPerSec;

    m_iFrequencyLast = 0;// force cache contents invalid

    if (m_bWaveCache) {
        delete[] m_bWaveCache;
        m_bWaveCache = NULL;
    }
    if (m_wWaveCache) {
        delete[] m_wWaveCache;
        m_wWaveCache = NULL;
    }

    if (wfex.wBitsPerSample == 8)
        m_bWaveCache = new BYTE [m_iWaveCacheSize];
    else
        m_wWaveCache = new WORD [m_iWaveCacheSize];
}

//
// FillAudioBuffer
//
//
//
void CAudioSynth::FillAudioBuffer (BYTE pBuf[], int iSize, BOOL *pfNewFormat) {
    BOOL fCalcCache = FALSE;

    CAutoLock l(&m_SynthLock);

    // Only realloc the cache if the format has changed !

    if ((wfex.nChannels != wfexLast.nChannels) ||
        (wfex.wBitsPerSample != wfexLast.wBitsPerSample) ||
        (wfex.nSamplesPerSec != wfexLast.nSamplesPerSec)) {
    *pfNewFormat = TRUE;
            fCalcCache = TRUE;
    AllocWaveCache();
    }

    if (m_iFrequency != m_iFrequencyLast) {
fCalcCache = TRUE;
m_iFrequencyLast = m_iFrequency;
    }
    if (m_iWaveform != m_iWaveformLast) {
fCalcCache = TRUE;
m_iWaveformLast = m_iWaveform;
    }
    if (m_iAmplitude != m_iAmplitudeLast) {
fCalcCache = TRUE;
m_iAmplitudeLast = m_iAmplitude;
    }

    if (fCalcCache) {
        switch (m_iWaveform) {

        case WAVE_SINE:
                CalcCacheSine ();
                break;

        case WAVE_SQUARE:
                CalcCacheSquare ();
                break;

        case WAVE_SAWTOOTH:
                CalcCacheSawtooth ();
                break;

        case WAVE_SINESWEEP:
                CalcCacheSweep ();
                break;

        }
    }

    // Copy cache to output buffers
    if (wfex.wBitsPerSample == 8 && wfex.nChannels == 1) {
        while (iSize--) {
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 8 && wfex.nChannels == 2) {
        iSize /= 2;
        while (iSize--) {
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex];
            *pBuf++ = m_bWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 16 && wfex.nChannels == 1) {
        WORD * pW = (WORD *) pBuf;
        iSize /= 2;
        while (iSize--) {
            *pW++ = m_wWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }
    else if (wfex.wBitsPerSample == 16 && wfex.nChannels == 2) {
        WORD * pW = (WORD *) pBuf;
        iSize /= 4;
        while (iSize--) {
            *pW++ = m_wWaveCache[m_iWaveCacheIndex];
            *pW++ = m_wWaveCache[m_iWaveCacheIndex++];
            if (m_iWaveCacheIndex >= m_iWaveCacheSize)
                m_iWaveCacheIndex = 0;
        }
    }

}

//
// CalcCacheSine
//
//
void CAudioSynth::CalcCacheSine (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;

    amplitude = ((wfex.wBitsPerSample == 8) ? 127 : 32767 )
                    * m_iAmplitude / 100;

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pB++ = (BYTE) (sin (d) * amplitude) + 128;
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pW++ = (WORD) (sin (d) * amplitude);
        }
    }

}

//
// CalcCacheSquare
//
//
void CAudioSynth::CalcCacheSquare (void) {

    int i;
    double d;
    double FTwoPIDivSpS;
    BYTE b0, b1;
    WORD w0, w1;

    b0 = (BYTE) 128 - (127 * m_iAmplitude / 100);
    b1 = (BYTE) 128 + (127 * m_iAmplitude / 100);
    w0 = (WORD) (32767. * m_iAmplitude / 100);
    w1 = (WORD) - (32767. * m_iAmplitude / 100);

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pB++ = (BYTE) ((sin (d) >= 0) ? b1 : b0);
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            d = FTwoPIDivSpS * i;
            *pW++ = (WORD) ((sin (d) >= 0) ? w1 : w0);
        }
    }
}

//
// CalcCacheSawtooth
//
void CAudioSynth::CalcCacheSawtooth (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;
    double step;
    double curstep;
    BOOL fLastWasNeg = TRUE;
    BOOL fPositive;

    amplitude = ((wfex.wBitsPerSample == 8) ? 255 : 65535 )
                    * m_iAmplitude / 100;

    FTwoPIDivSpS = m_iFrequency * TWOPI / wfex.nSamplesPerSec;
    step = amplitude * m_iFrequency / wfex.nSamplesPerSec;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    BYTE * pB = m_bWaveCache;
    PWORD pW = (PWORD) m_wWaveCache;

    for (i = 0; i < m_iWaveCacheSize; i++) {
        d = FTwoPIDivSpS * i;

        // OneShot triggered on positive zero crossing
        fPositive = (sin (d) >= 0);

        if (fLastWasNeg && fPositive) {
            if (wfex.wBitsPerSample == 8)
                curstep = 128 - amplitude / 2;
            else
                curstep = 32768 - amplitude / 2;
        }
        fLastWasNeg = !fPositive;

        if (wfex.wBitsPerSample == 8)
            *pB++ = (BYTE) curstep;
        else
            *pW++ = (WORD) (-32767 + curstep);

        curstep += step;
    }
}

//
// CalcCacheSweep
//
void CAudioSynth::CalcCacheSweep (void) {

    int i;
    double d;
    double amplitude;
    double FTwoPIDivSpS;
    double CurrentFreq;
    double DeltaFreq;

    amplitude = ((wfex.wBitsPerSample == 8) ? 127 : 32767 )
                    * m_iAmplitude / 100;

    DeltaFreq = ((double) m_iSweepEnd - m_iSweepStart) / m_iWaveCacheSize;
    CurrentFreq = m_iSweepStart;

    m_iWaveCacheIndex = 0;
    m_iCurrentSample = 0;

    if (wfex.wBitsPerSample == 8) {
        BYTE * pB = m_bWaveCache;
        d = 0.0;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            FTwoPIDivSpS = (int) CurrentFreq * TWOPI / wfex.nSamplesPerSec;
            CurrentFreq += DeltaFreq;
            d += FTwoPIDivSpS;
            *pB++ = (BYTE) (sin (d) * amplitude) + 128;
        }
    }
    else {
        PWORD pW = (PWORD) m_wWaveCache;
        d = 0.0;

        for (i = 0; i < m_iWaveCacheSize; i++) {
            FTwoPIDivSpS = (int) CurrentFreq * TWOPI / wfex.nSamplesPerSec;
            CurrentFreq += DeltaFreq;
            d += FTwoPIDivSpS;
            *pW++ = (WORD) (sin (d) * amplitude);
        }
    }
}

//
// get_Frequency
//
STDMETHODIMP CAudioSynth::get_Frequency(int *Frequency) {

    *Frequency = m_iFrequency;

    DbgLog((LOG_TRACE, 3, TEXT("get_Frequency: %d"), *Frequency));

    return NOERROR;
}

//
// put_Frequency
//
STDMETHODIMP CAudioSynth::put_Frequency(int Frequency) {

    CAutoLock l(&m_SynthLock);

    m_iFrequency = Frequency;

    DbgLog((LOG_TRACE, 3, TEXT("put_Frequency: %d"), Frequency));

    return NOERROR;
}

//
// get_Waveform
//
STDMETHODIMP CAudioSynth::get_Waveform(int *Waveform) {

    *Waveform = m_iWaveform;

    DbgLog((LOG_TRACE, 3, TEXT("get_Waveform: %d"), *Waveform));

    return NOERROR;
}

//
// put_Waveform
//
STDMETHODIMP CAudioSynth::put_Waveform(int Waveform) {

    CAutoLock l(&m_SynthLock);

    m_iWaveform = Waveform;

    DbgLog((LOG_TRACE, 3, TEXT("put_Waveform: %d"), Waveform));

    return NOERROR;
}

//
// get_Channels
//
STDMETHODIMP CAudioSynth::get_Channels(int *Channels) {

    *Channels = wfex.nChannels;

    DbgLog((LOG_TRACE, 3, TEXT("get_Channels: %d"), *Channels));

    return NOERROR;
}

//
// get_BitsPerSample
//
STDMETHODIMP CAudioSynth::get_BitsPerSample(int *BitsPerSample) {

    *BitsPerSample = wfex.wBitsPerSample;

    DbgLog((LOG_TRACE, 3, TEXT("get_BitsPerSample: %d"), *BitsPerSample));

    return NOERROR;
}

//
// get_SamplesPerSec
//
STDMETHODIMP CAudioSynth::get_SamplesPerSec(int *SamplesPerSec) {

    *SamplesPerSec = wfex.nSamplesPerSec;

    DbgLog((LOG_TRACE, 3, TEXT("get_SamplesPerSec: %d"), *SamplesPerSec));

    return NOERROR;
}

//
// put_SynthFormat
//
STDMETHODIMP CAudioSynth::put_SynthFormat(int Channels, int BitsPerSample,
  int SamplesPerSec) {

    CAutoLock l(&m_SynthLock);

    wfex.nChannels = Channels;

    wfex.wBitsPerSample = BitsPerSample;

    wfex.nSamplesPerSec = SamplesPerSec;

    DbgLog((LOG_TRACE, 1, TEXT("put_SynthFormat: %d-bit %d-channel %dHz"),
    BitsPerSample, Channels, SamplesPerSec));

    return NOERROR;
}

//
// get_Amplitude
//
STDMETHODIMP CAudioSynth::get_Amplitude(int *Amplitude) {

    *Amplitude =  m_iAmplitude;

    DbgLog((LOG_TRACE, 3, TEXT("get_Amplitude: %d"), *Amplitude));

    return NOERROR;
}

//
// put_Amplitude

//
STDMETHODIMP CAudioSynth::put_Amplitude(int Amplitude) {

    CAutoLock l(&m_SynthLock);

    if (Amplitude > MaxAmplitude || Amplitude < MinAmplitude)
        return E_INVALIDARG;

    m_iAmplitude = Amplitude;

    DbgLog((LOG_TRACE, 3, TEXT("put_Amplitude: %d"), Amplitude));

    return NOERROR;
}

//
// get_SweepRange
//
STDMETHODIMP CAudioSynth::get_SweepRange(int *SweepStart, int *SweepEnd) {

    *SweepStart = m_iSweepStart;
    *SweepEnd = m_iSweepEnd;

    DbgLog((LOG_TRACE, 3, TEXT("get_SweepStart: %d %d"), *SweepStart, *SweepEnd));

    return NOERROR;
}

//
// put_SweepRange
//
STDMETHODIMP CAudioSynth::put_SweepRange(int SweepStart, int SweepEnd) {

    CAutoLock l(&m_SynthLock);

    m_iSweepStart = SweepStart;
    m_iSweepEnd = SweepEnd;

    DbgLog((LOG_TRACE, 3, TEXT("put_SweepRange: %d %d"), SweepStart, SweepEnd));

    return NOERROR;
}

/******************************Public*Routine******************************\
* exported entry points for registration and
* unregistration (in this case they only call
* through to default implmentations).
*
*
*
* History:
*
\**************************************************************************/
STDAPI
DllRegisterServer()
{
  return AMovieDllRegisterServer2( TRUE );
}

STDAPI
DllUnregisterServer()
{
  return AMovieDllRegisterServer2( FALSE );
}


SYNTH.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  synth.h
//
//  Audio Signal Generator

#ifndef __AUDIOSYNTH__
#define __AUDIOSYNTH__

//CLSID_SynthFilter
//{79A98DE0-BC00-11ce-AC2E-444553540000}
DEFINE_GUID(CLSID_SynthFilter,
0x79a98de0, 0xbc00, 0x11ce, 0xac, 0x2e, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0);

//CLSID_SynthFilterPropertyPage
//{79A98DE1-BC00-11ce-AC2E-444553540000}
DEFINE_GUID(CLSID_SynthPropertyPage,
0x79a98de1, 0xbc00, 0x11ce, 0xac, 0x2e, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0);

const double TWOPI = 6.283185308;
const int MaxFrequency = 20000;
const int MinFrequency = 0;
const int DefaultFrequency = 440;       // A-440
const int MaxAmplitude = 100;
const int MinAmplitude = 0;
const int DefaultSweepStart = DefaultFrequency;
const int DefaultSweepEnd = 5000;
const int WaveBufferSize = 16*1024;     // Size of each allocated buffer
                                        // Originally used to be 2K, but at
                                        // 44khz/16bit/stereo you would get
                                        // audio breaks with a transform in the
                                        // middle.

enum Waveforms {
    WAVE_SINE = 0,
    WAVE_SQUARE,
    WAVE_SAWTOOTH,
    WAVE_SINESWEEP,
    WAVE_LAST           // Always keep this guy last
};

#define WM_PROPERTYPAGE_ENABLE  (WM_USER + 100)

// below stuff is implementation-only....
#ifdef _AUDIOSYNTH_IMPLEMENTATION_

class CSynthStream;

// -------------------------------------------------------------------------
// CAudioSynth
// -------------------------------------------------------------------------

class CAudioSynth {

public:

    CAudioSynth(
                int Frequency = DefaultFrequency,
                int Waveform = WAVE_SINE,
                int iBitsPerSample = 8,
                int iChannels = 1,
                int iSamplesPerSec = 11025,
                int iAmplitude = 100
                );

    ~CAudioSynth();

    // Load the buffer with the current waveform
    void FillAudioBuffer (BYTE pBuf[], int iSize, BOOL * fNewFormat);

    // Set the "current" format and allocate temporary memory
    void AllocWaveCache    (void) ;

    STDMETHODIMP get_Frequency(int *Frequency);
    STDMETHODIMP put_Frequency(int  Frequency);
    STDMETHODIMP get_Waveform(int *Waveform);
    STDMETHODIMP put_Waveform(int  Waveform);
    STDMETHODIMP get_Channels(int *Channels);
    STDMETHODIMP get_BitsPerSample(int *BitsPerSample);
    STDMETHODIMP get_SamplesPerSec(int *SamplesPerSec);
    STDMETHODIMP put_SynthFormat(int Channels, int BitsPerSample, int SamplesPerSec);
    STDMETHODIMP get_Amplitude(int *Amplitude);
    STDMETHODIMP put_Amplitude(int  Amplitude);
    STDMETHODIMP get_SweepRange(int *SweepStart, int *SweepEnd);
    STDMETHODIMP put_SweepRange(int  SweepStart, int  SweepEnd);

private:
    CCritSec m_SynthLock;

    WAVEFORMATEX wfex;          // the current format
    WAVEFORMATEX wfexLast;      // last known waveformat

    int m_iWaveform;            // WAVE_SINE ...
    int m_iFrequency;           // if not using sweep, this is the frequency
    int m_iAmplitude;           // 0 to 100

    int m_iWaveformLast;        // keep track of the last known format
    int m_iFrequencyLast;       // so we can flush the cache if necessary
    int m_iAmplitudeLast;

    int m_iCurrentSample;       // 0 to iSamplesPerSec-1
    BYTE * m_bWaveCache;        // Wave Cache as BYTEs
    WORD * m_wWaveCache;        // Wave Cache as WORDs
    int m_iWaveCacheSize;       // how big is the cache?
    int m_iWaveCacheCycles;     // how many cycles are in the cache
    int m_iWaveCacheIndex;

    int m_iSweepStart;           // start of sweep
    int m_iSweepEnd;             // end of sweep
//    double m_SweepDuration;     // how long the sweep lasts

    void CalcCacheSine     (void) ;
    void CalcCacheSquare   (void) ;
    void CalcCacheSawtooth (void) ;
    void CalcCacheSweep    (void) ;

};

// -------------------------------------------------------------------------
// CSynthFilter
// -------------------------------------------------------------------------
// CSynthFilter manages filter level stuff

class CSynthFilter :    public ISynth,
                        public CPersistStream,
                        public ISpecifyPropertyPages,
                        public CSource {

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);
    ~CSynthFilter();

    DECLARE_IUNKNOWN;

    // override this to reveal our property interface
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // --- ISpecifyPropertyPages ---

    // return our property pages
    STDMETHODIMP GetPages(CAUUID * pPages);

    // --- IPersistStream Interface

    STDMETHODIMP GetClassID(CLSID *pClsid);
    int SizeMax();
    HRESULT WriteToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);

    //
    // --- ISynth ---
    //

    STDMETHODIMP get_Frequency(int *Frequency);
    STDMETHODIMP put_Frequency(int Frequency);
    STDMETHODIMP get_Waveform(int *Waveform);
    STDMETHODIMP put_Waveform(int Waveform);
    STDMETHODIMP get_Channels(int *Channels);
    STDMETHODIMP get_BitsPerSample(int *BitsPerSample);
    STDMETHODIMP get_SamplesPerSec(int *SamplesPerSec);
    STDMETHODIMP put_Channels(int Channels);
    STDMETHODIMP put_BitsPerSample(int BitsPersample);
    STDMETHODIMP put_SamplesPerSec(int SamplesPerSec);
    STDMETHODIMP get_Amplitude(int *Amplitude);
    STDMETHODIMP put_Amplitude(int Amplitude);
    STDMETHODIMP get_SweepRange(int *SweepStart, int *SweepEnd);
    STDMETHODIMP put_SweepRange(int  SweepStart, int  SweepEnd);

    CAudioSynth *m_Synth;           // the current synthesizer
    intm_Channels;
    intm_BitsPerSample;
    intm_SamplesPerSec;

private:

    // it is only allowed to to create these objects with CreateInstance
    CSynthFilter(LPUNKNOWN lpunk, HRESULT *phr);

    // When the format changes, reconnect...
    void CSynthFilter::ReconnectWithNewFormat(void);

};

// -------------------------------------------------------------------------
// CSynthStream
// -------------------------------------------------------------------------
// CSynthStream manages the data flow from the output pin.

class CSynthStream : public CSourceStream {

public:

    CSynthStream(HRESULT *phr, CSynthFilter *pParent, LPCWSTR pPinName);
    ~CSynthStream();

    BOOL ReadyToStop(void) {return FALSE;}

    // stuff an audio buffer with the current format
    HRESULT FillBuffer(IMediaSample *pms);

    // ask for buffers of the size appropriate to the agreed media type.
    HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    // verify we can handle this format
    HRESULT CheckMediaType(const CMediaType *pMediaType);

    // set the agreed media type
    HRESULT SetMediaType(const CMediaType *pMediaType);

    HRESULT GetMediaType(CMediaType *pmt);

    // resets the stream time to zero.
    HRESULT OnThreadCreate(void);

    HRESULT Active   (void);
    HRESULT Inactive (void);

private:

    // Access to this state information should be serialized with the filters
    // critical section (m_pFilter->pStateLock())

    CCritSec    m_cSharedState;     // use this to lock access to m_rtSampleTime and m_Synth
                                    // which are shared with the worker thread.

    CRefTime     m_rtSampleTime;    // The time to be stamped on each sample
    CAudioSynth *m_Synth;           // the current synthesizer

    CSynthFilter *m_pParent;
};

#endif // _AUDIOSYNTH_IMPLEMENTATION_ implementation only....

#endif /* __AUDIOSYNTH__ */


SYNTHPRP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// synthprop.cpp - Synthesizer Property Page
//

#include <windows.h>
#include <streams.h>
#include <commctrl.h>
#include <memory.h>
#include <olectl.h>

#include "isynth.h"
#include "synth.h"
#include "synthprp.h"
#include "resource.h"

// -------------------------------------------------------------------------
// CSynthProperties
// -------------------------------------------------------------------------

//
// CreateInstance
//

CUnknown * WINAPI CSynthProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    CUnknown *punk = new CSynthProperties(lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return punk;
}

//
// Constructor
//
// Creaete a Property page object for the synthesizer

CSynthProperties::CSynthProperties(LPUNKNOWN lpunk, HRESULT *phr)
    : CBasePropertyPage(NAME("Synth Property Page"), lpunk,
        IDD_SYNTHPROP1,IDS_SYNTHPROPNAME)
    , m_pSynth(NULL)
    , m_iSweepStart(DefaultSweepStart)
    , m_iSweepEnd(DefaultSweepEnd)
, m_fWindowInActive(TRUE)
{
    ASSERT(phr);

    InitCommonControls();
}

//
// OnConnect
//
// Give us the filter to communicate with

HRESULT CSynthProperties::OnConnect(IUnknown *pUnknown)
{
    ASSERT(m_pSynth == NULL);

    // Ask the filter for it's control interface

    HRESULT hr = pUnknown->QueryInterface(IID_ISynth,(void **)&m_pSynth);
    if (FAILED(hr)) {
        return E_NOINTERFACE;
    }

    ASSERT(m_pSynth);

    // Get current filter state
    m_pSynth->get_BitsPerSample(&m_iBitsPerSampleOriginal);
    m_pSynth->get_Waveform(&m_iWaveformOriginal);
    m_pSynth->get_Frequency(&m_iFrequencyOriginal);
    m_pSynth->get_Channels(&m_iChannelsOriginal);
    m_pSynth->get_SamplesPerSec(&m_iSamplesPerSecOriginal);
    m_pSynth->get_Amplitude(&m_iAmplitudeOriginal);

    return NOERROR;
}

//
// OnDisconnect
//
// Release the interface

HRESULT CSynthProperties::OnDisconnect()
{
    // Release the interface

    if (m_pSynth == NULL) {
        return E_UNEXPECTED;
    }

    m_pSynth->put_Waveform(m_iWaveformOriginal);
    m_pSynth->put_Frequency(m_iFrequencyOriginal);
    m_pSynth->put_Amplitude(m_iAmplitudeOriginal);

    m_pSynth->put_Channels(m_iChannelsOriginal);
    m_pSynth->put_BitsPerSample(m_iBitsPerSampleOriginal);
    m_pSynth->put_SamplesPerSec(m_iSamplesPerSecOriginal);

    m_pSynth->Release();
    m_pSynth = NULL;
    return NOERROR;
}

//
// OnActivate
//
// Called on dialog creation

HRESULT CSynthProperties::OnActivate(void)
{
    InitPropertiesDialog(m_hwnd);

    ASSERT(m_hwndFreqSlider);

m_fWindowInActive = FALSE;
    return NOERROR;
}

//
// OnDeactivate
//
// Called on dialog destruction

HRESULT
CSynthProperties::OnDeactivate(void)
{
m_fWindowInActive = TRUE;
    return NOERROR;
}

//
// OnApplyChanges
//
// User pressed the Apply button, remember the current settings

HRESULT CSynthProperties::OnApplyChanges(void)
{
    m_pSynth->get_BitsPerSample(&m_iBitsPerSampleOriginal);
    m_pSynth->get_Waveform(&m_iWaveformOriginal);
    m_pSynth->get_Frequency(&m_iFrequencyOriginal);
    m_pSynth->get_Channels(&m_iChannelsOriginal);
    m_pSynth->get_SamplesPerSec(&m_iSamplesPerSecOriginal);
    m_pSynth->get_Amplitude(&m_iAmplitudeOriginal);

    return NOERROR;
}

//
// OnReceiveMessages
//
// Handles the messages for our property window

BOOL CSynthProperties::OnReceiveMessage( HWND hwnd
                                , UINT uMsg
                                , WPARAM wParam
                                , LPARAM lParam)
{

if(m_fWindowInActive)
return FALSE;

    switch (uMsg) {

    case WM_PROPERTYPAGE_ENABLE:
        // Our private message that our owning filter sends us when changing to a Run / Stop / Pause
        // state.  if lParam, then enable the controls which affect the format; if not lParam, then
        // disable the controls that affect the format.

        EnableWindow (GetDlgItem (hwnd, IDC_SAMPLINGFREQ11), (BOOL) lParam);
        EnableWindow (GetDlgItem (hwnd, IDC_SAMPLINGFREQ22), (BOOL) lParam);
        EnableWindow (GetDlgItem (hwnd, IDC_SAMPLINGFREQ44), (BOOL) lParam);

        EnableWindow (GetDlgItem (hwnd, IDC_BITSPERSAMPLE8),  (BOOL) lParam);
        EnableWindow (GetDlgItem (hwnd, IDC_BITSPERSAMPLE16), (BOOL) lParam);

        EnableWindow (GetDlgItem (hwnd, IDC_CHANNELS1), (BOOL) lParam);
        EnableWindow (GetDlgItem (hwnd, IDC_CHANNELS2), (BOOL) lParam);
        break;

    case WM_VSCROLL:
        if ((HWND) lParam == m_hwndFreqSlider)
            OnFreqSliderNotification(LOWORD (wParam), HIWORD (wParam));
        else if ((HWND) lParam == m_hwndAmplitudeSlider)
            OnAmpSliderNotification(LOWORD (wParam), HIWORD (wParam));
        SetDirty();
        return TRUE;

    case WM_COMMAND:

        switch (LOWORD(wParam)) {

        case IDC_FREQUENCYTEXT:
        {
            int iNotify = HIWORD (wParam);

            if (iNotify == EN_KILLFOCUS) {
                BOOL fOK;

                int iPos = GetDlgItemInt (hwnd, IDC_FREQUENCYTEXT, &fOK, FALSE);
                int iMaxFreq;

                m_pSynth->get_SamplesPerSec(&iMaxFreq);
                iMaxFreq /= 2;

                if (!fOK || (iPos > iMaxFreq || iPos < 0)) {
                    int iCurrentFreq;
                    m_pSynth->get_Frequency(&iCurrentFreq);
                    SetDlgItemInt (hwnd, IDC_FREQUENCYTEXT, iCurrentFreq, FALSE);
                    break;
                }

   SendMessage(m_hwndFreqSlider, TBM_SETPOS, TRUE, (LPARAM) iMaxFreq - iPos);
                m_pSynth->put_Frequency(iPos);
                SetDirty();
            }
        }
        break;

        case IDC_AMPLITUDETEXT:
        {
            int iNotify = HIWORD (wParam);

            if (iNotify == EN_KILLFOCUS) {
                BOOL fOK;

                int iPos = GetDlgItemInt (hwnd, IDC_AMPLITUDETEXT, &fOK, FALSE);

                if (!fOK || (iPos > MaxAmplitude || iPos < 0)) {
                    int iCurrentAmplitude;

                    m_pSynth->get_Amplitude(&iCurrentAmplitude);
                    SetDlgItemInt (hwnd, IDC_AMPLITUDETEXT, iCurrentAmplitude, FALSE);
                    break;
                }

    SendMessage(m_hwndAmplitudeSlider, TBM_SETPOS, TRUE, (LPARAM) MaxAmplitude - iPos);
                m_pSynth->put_Amplitude(iPos);
                SetDirty();
            }
        }
        break;

        case IDC_SAMPLINGFREQ11:
            m_pSynth->put_SamplesPerSec(11025);
            RecalcFreqSlider();
            SetDirty();
            break;
        case IDC_SAMPLINGFREQ22:
            m_pSynth->put_SamplesPerSec(22050);
            RecalcFreqSlider();
            SetDirty();
            break;
        case IDC_SAMPLINGFREQ44:
            m_pSynth->put_SamplesPerSec(44100);
            RecalcFreqSlider();
            SetDirty();
            break;

        case IDC_BITSPERSAMPLE8:
            m_pSynth->put_BitsPerSample(8);
            SetDirty();
            break;
        case IDC_BITSPERSAMPLE16:
            m_pSynth->put_BitsPerSample(16);
            SetDirty();
            break;

        case IDC_CHANNELS1:
            m_pSynth->put_Channels(1);
            SetDirty();
            break;
        case IDC_CHANNELS2:
            m_pSynth->put_Channels(2);
            SetDirty();
            break;

        case IDC_WAVESINE:
            m_pSynth->put_Waveform(WAVE_SINE);
            SetDirty();
            break;
        case IDC_WAVESQUARE:
            m_pSynth->put_Waveform(WAVE_SQUARE);
            SetDirty();
            break;
        case IDC_WAVESAWTOOTH:
            m_pSynth->put_Waveform(WAVE_SAWTOOTH);
            SetDirty();
            break;
        case IDC_WAVESWEEP:
            m_pSynth->put_Waveform(WAVE_SINESWEEP);
            SetDirty();
            break;

        default:
            break;

        }
        return TRUE;

    case WM_DESTROY:
        return TRUE;

    default:
        return FALSE;

    }
    return TRUE;
}

//
// InitPropertiesDialog
//

void
CSynthProperties::InitPropertiesDialog(HWND hwndParent)
{
    m_hwndFreqSlider = GetDlgItem (hwndParent, IDC_FREQTRACKBAR);
    m_hwndFreqText  = GetDlgItem (hwndParent, IDC_FREQUENCYTEXT);
    m_hwndAmplitudeSlider = GetDlgItem (hwndParent, IDC_AMPLITUDETRACKBAR);
    m_hwndAmplitudeText  = GetDlgItem (hwndParent, IDC_AMPLITUDETEXT);

    // Sampling Frequency
    int i;
    switch (m_iSamplesPerSecOriginal) {
    case 11025: i = IDC_SAMPLINGFREQ11; break;
    case 22050: i = IDC_SAMPLINGFREQ22; break;
    case 44100: i = IDC_SAMPLINGFREQ44; break;
    default:
        ASSERT(0);
    }
    CheckRadioButton(hwndParent,
        IDC_SAMPLINGFREQ11,
        IDC_SAMPLINGFREQ44,
        i);

    // BitsPerSample
    CheckRadioButton(hwndParent,
                IDC_BITSPERSAMPLE8,
                IDC_BITSPERSAMPLE16,
                IDC_BITSPERSAMPLE8 + m_iBitsPerSampleOriginal / 8 - 1);

    // Waveform 0 == sine, 1 == square, ...
    CheckRadioButton(hwndParent,
                IDC_WAVESINE,
                IDC_WAVESWEEP,
                IDC_WAVESINE + m_iWaveformOriginal);

    // Channels
    CheckRadioButton(hwndParent,
                IDC_CHANNELS1,
                IDC_CHANNELS2,
                IDC_CHANNELS1 + m_iChannelsOriginal - 1);

    //
    // Frequency trackbar
    //

    RecalcFreqSlider();

    //
    //  Amplitude trackbar
    //

    SendMessage(m_hwndAmplitudeSlider, TBM_SETRANGE, TRUE,
        MAKELONG(MinAmplitude, MaxAmplitude) );

    SendMessage(m_hwndAmplitudeSlider, TBM_SETTIC, 0,
        ((MinAmplitude + MaxAmplitude) / 2));

    SendMessage(m_hwndAmplitudeSlider, TBM_SETPOS, TRUE,
        (LPARAM) (MaxAmplitude - m_iAmplitudeOriginal));

    SetDlgItemInt (hwndParent, IDC_AMPLITUDETEXT,
        m_iAmplitudeOriginal, TRUE);
}

//
// RecalcFreqSlider
//
// Set the range, current settings for the Freq scrollbar

void
CSynthProperties::RecalcFreqSlider(void)
{
    int iPos, iMaxFreq;

    // Limit the frequency to one half the sampling frequency

    m_pSynth->get_SamplesPerSec(&iMaxFreq);
    iMaxFreq /= 2;
    m_pSynth->get_Frequency(&iPos);
    if (iPos > iMaxFreq)
        iPos = iMaxFreq;

    SendMessage(m_hwndFreqSlider, TBM_SETRANGE, TRUE,
        MAKELONG(0, iMaxFreq));

    SendMessage(m_hwndFreqSlider, TBM_SETTIC, 0,
        iMaxFreq / 2);

    SendMessage(m_hwndFreqSlider, TBM_SETPOS, TRUE,
        (LPARAM) (iMaxFreq - iPos));

    SendMessage(m_hwndFreqSlider, TBM_SETPAGESIZE, 0, 10);

    SendMessage(m_hwndFreqSlider, TBM_SETSEL, TRUE,
        MAKELONG (iMaxFreq - m_iSweepEnd, iMaxFreq - m_iSweepStart));

    SetDlgItemInt (m_hwnd, IDC_FREQUENCYTEXT,
        iPos, TRUE);

}

//
// OnFreqSliderNotification
//
// Handle the notification meesages from the slider control

void
CSynthProperties::OnFreqSliderNotification(WPARAM wParam, WORD wPosition)
{
    int MaxFreq;
    int Freq;
    int SliderPos;

    switch (wParam) {

    case TB_ENDTRACK:
    case TB_THUMBTRACK:
    case TB_LINEDOWN:
    case TB_LINEUP: {
        // max frequency of slider is half the sampling frequency
        m_pSynth->get_SamplesPerSec (&MaxFreq);
        MaxFreq /= 2;
        SliderPos = (int) SendMessage(m_hwndFreqSlider, TBM_GETPOS, 0, 0L);
        Freq = MaxFreq - SliderPos;
        m_pSynth->put_Frequency (Freq);

        // Set the end of sweep to the current slider pos
        if (!(GetKeyState (VK_SHIFT) & 0x8000)) {
            m_iSweepEnd = Freq;
        }

        // Set the start of the sweep range if SHIFT key is pressed
        if (GetKeyState (VK_SHIFT) & 0x8000) {
            m_iSweepStart = Freq;
        }
        m_pSynth->put_SweepRange (m_iSweepStart, m_iSweepEnd);

        if (m_iSweepEnd > m_iSweepStart)
            SendMessage(m_hwndFreqSlider, TBM_SETSEL, TRUE,
                MAKELONG (MaxFreq - m_iSweepEnd, MaxFreq - m_iSweepStart));
        else
            SendMessage(m_hwndFreqSlider, TBM_SETSEL, TRUE,
                MAKELONG (MaxFreq - m_iSweepStart, MaxFreq - m_iSweepEnd));

        SetDlgItemInt (m_hwnd, IDC_FREQUENCYTEXT, Freq, TRUE);

    }
    break;

    }
}

//
// OnAmpSliderNotification
//
// Handle the notification meesages from the slider control

void
CSynthProperties::OnAmpSliderNotification(WPARAM wParam, WORD wPosition)
{
    switch (wParam) {

    case TB_ENDTRACK:
    case TB_THUMBTRACK:
    case TB_LINEDOWN:
    case TB_LINEUP: {
        int Level = (int) SendMessage(m_hwndAmplitudeSlider, TBM_GETPOS, 0, 0L);
        m_pSynth->put_Amplitude (MaxAmplitude - Level);
        SetDlgItemInt (m_hwnd, IDC_AMPLITUDETEXT, MaxAmplitude - Level, TRUE);
    }
    break;

    }
}

//
// SetDirty
//
// notifies the property page site of changes

void
CSynthProperties::SetDirty()
{
    m_bDirty = TRUE;
    if (m_pPageSite)
        m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
}


SYNTHPRP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//  synthprp.h
//
//  CSynthProperties

class CSynthProperties : public CBasePropertyPage {

public:

    CSynthProperties(LPUNKNOWN lpUnk, HRESULT *phr);
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate();
    HRESULT OnDeactivate();
    HRESULT OnApplyChanges();
    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

private:

    static BOOL CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    void    InitPropertiesDialog(HWND hwndParent);
    void    OnFreqSliderNotification(WPARAM wParam, WORD wPosition);
    void    OnAmpSliderNotification(WPARAM wParam, WORD wPosition);
    void    RecalcFreqSlider(void );
    void    SetDirty(void );

    HWND    m_hwndFreqSlider;           // handle of slider
    HWND    m_hwndFreqText;             // Handle of frequency text window
    HWND    m_hwndAmplitudeSlider;      // handle of slider
    HWND    m_hwndAmplitudeText;        // Handle of amplitude text window

    int     m_iWaveformOriginal;        // WAVE_SINE ...
    int     m_iFrequencyOriginal;       // if not using sweep, this is the frequency
    int     m_iBitsPerSampleOriginal;   // 8 or 16
    int     m_iChannelsOriginal;        // 1 or 2
    int     m_iSamplesPerSecOriginal;   // 8000, 11025, ...
    int     m_iAmplitudeOriginal;       // 0 to 100
    int     m_iSweepStart;              // Sweep range on freq slider
    int     m_iSweepEnd;

BOOLm_fWindowInActive;// TRUE ==> dialog is in the process of being destroyed

    ISynth  *m_pSynth;                  // Interface to the synthsizer
};


TEXTOUT.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//==========================================================================;

#include <streams.h>
#include <initguid.h>
#include "textout.h"

//
//
// What this sample illustates
//
// A simple text renderer - we draw samples from a text stream into a window
// We use the CBaseRenderer class to manage a filter with a single input pin
// and CBaseWindow to look after the construction, management and destruction
// of a window. The window object creates itself a separate worker thread.
//
//
// Summary
//
// This is a text renderer - we have authored a specially AVI file (called
// CLOCKTXT.AVI) which has an additional stream containing buffers of null
// termimated text. The AVI parser reads these and sends us them packaged
// up as ActiveMovie media samples. We get the buffer from these and cast
// them to strings ready to be passed to ExtTextOut (from DoRenderSample).
//
//
// Implementation
//
// We create a simple window (the default size of 320x240) which is displayed
// each time the filter is paused. Into this window we will ExtTextOut each
// strings that are sent to us. The renderer is based on the CBaseRenderer
// SDK base class which handles all the synchronisation and seeking although
// it doesn't have any quality management implementation (to get the quality
// management we would inherit from CBaseVideoRenderer base class instead).
//
// From the base renderer class we override the virtual OnReceiveFirstSample
// method so that when we are paused we will show a poster frame. The filter
// also supports IVideoWindow which is done fairly simply by inheriting a
// class from CBaseControlWindow, this base class implements most of that
// interface although we also override OnClose, OnReceiveMessage (which is a
// generic message handler) and GetClassWindowStyles. GetClassWindowStyles
// is called by the base window class (CBaseWindow) during window creation
// when it wants to know what window and class styles it should register.
//
//
// Demonstration instructions
//
// Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop the
// CLOCKTXT.AVI movie into the tool and it will be rendered. Click on Pause
// and then Run on the GRAPHEDT frame and you will see the video in one
// window on the desktop and another video with text being drawn into it...
// The words being displayed are "One", "Two". "Three" etc and the point at
// which they are displayed should match with the images in the other window
//
//
// Files
//
// textout.cpp          The filter and window class implementations
// textout.def          What we import and export from this DLL
// textout.h            Window and filter class definitions
// textout.rc           Version and title resource information
// textout.reg          What goes in the registry to make us work
// makefile             How we build it...
//
//
// Base classes we use
//
// CBaseWindow          Manages a window on a separate worker thread
// CBaseRenderer        The simple renderer filter base class
//
//

// Setup data

const AMOVIESETUP_MEDIATYPE sudIpPinTypes =
{
    &MEDIATYPE_Text,              // MajorType
    &MEDIASUBTYPE_NULL            // MinorType
};

const AMOVIESETUP_PIN sudIpPin =
{
    L"Input",                     // The Pins name
    FALSE,                        // Is rendered
    FALSE,                        // Is an output pin
    FALSE,                        // Allowed none
    FALSE,                        // Allowed many
    &CLSID_NULL,                  // Connects to filter
    NULL,                         // Connects to pin
    1,                            // Number of types
    &sudIpPinTypes                // Pin details
};

const AMOVIESETUP_FILTER sudTextoutAx =
{
    &CLSID_TextRender,            // Filter CLSID
    L"Text Display",              // String name
    MERIT_NORMAL,                 // Filter merit
    1,                            // Number of pins
    &sudIpPin                     // Pin details
};

// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance
// function when it is asked to create a CLSID_VideoRenderer object

CFactoryTemplate g_Templates[] = {
    { L"Text Display"
    , &CLSID_TextRender
    , CTextOutFilter::CreateInstance
    , NULL
    , &sudTextoutAx },
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

//
// DllRegisterServer
//
// Used to register and unregister the filter
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer

//
// Constructor
//
// Constructor for the text out renderer filter. After initialising the base
// renderer class and our nested window handling class we have to pass our
// input pin we have to the window class. The base class uses this to check
// that the filter has a valid pin connection before allowing IVideoWindow
// methods to be called (this is a stipulation of the interface set mainly
// because most filters can't do anything before they know what data they
// will be dealing with - an example being video renderers who can't really
// support IVideoWindow fully until they know the size/format of the video)
//
#pragma warning(disable:4355)
//
CTextOutFilter::CTextOutFilter(LPUNKNOWN pUnk,HRESULT *phr) :
    CBaseRenderer(CLSID_TextRender, NAME("TextOut Filter"), pUnk, phr),
    m_TextWindow(NAME("Text properties"),GetOwner(),phr,&m_InterfaceLock,this)
{
    m_TextWindow.SetControlWindowPin( GetPin(0) );

} // (Constructor)

//
// Destructor
//
CTextOutFilter::~CTextOutFilter()
{
}

//
// CreateInstance
//
// This goes in the factory template table to create new instances
//
CUnknown * WINAPI CTextOutFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    CTextOutFilter *pTextOutFilter = new CTextOutFilter(pUnk,phr);
    if (pTextOutFilter == NULL) {
        return NULL;
    }
    return (CBaseMediaFilter *) pTextOutFilter;

} // CreateInstance

//
// NonDelegatingQueryInterface
//
// Overriden to say what interfaces we support and where
//
STDMETHODIMP
CTextOutFilter::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
    CheckPointer(ppv,E_POINTER);
    if (riid == IID_IVideoWindow) {
        return m_TextWindow.NonDelegatingQueryInterface(riid,ppv);
    }
    return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);

} // NonDelegatingQueryInterface

//
// Pause
//
// Overriden to show the text renderer window
//
STDMETHODIMP CTextOutFilter::Pause()
{
    BOOL fStopToPause = (m_State == State_Stopped);

    HRESULT hr = CBaseRenderer::Pause();
    if(FAILED(hr)) {
        return hr;
    }

    if(fStopToPause)
    {
        m_TextWindow.ActivateWindow();
        m_TextWindow.DoShowWindow(SW_SHOWNORMAL);
    }
    return hr;

} // Pause

//
// BreakConnect
//
// Deactivate the text out rendering window
//
HRESULT CTextOutFilter::BreakConnect()
{
    m_TextWindow.InactivateWindow();
    m_TextWindow.DoShowWindow(SW_HIDE);
    return NOERROR;

} // BreakConnect

//
// CheckMediaType
//
// Check that we can support a given proposed type
//
HRESULT CTextOutFilter::CheckMediaType(const CMediaType *pmt)
{
    // Reject non-Text type

    if (pmt->majortype != MEDIATYPE_Text) {
return E_INVALIDARG;
    }
    return NOERROR;

} // CheckMediaType

//
// OnPaint
//
// This is called when the window thread receives a WM_PAINT message
//
BOOL CTextOutFilter::OnPaint(COLORREF WindowColour)
{
    CAutoLock cAutoLock(&m_RendererLock);
    RECT ClientRect;
    PAINTSTRUCT ps;

    BeginPaint(m_TextWindow.GetWindowHWND(),&ps);
    EndPaint(m_TextWindow.GetWindowHWND(),&ps);

    // Display the text if we have a sample

    if (m_pMediaSample) {
        DrawText(m_pMediaSample);
        return TRUE;
    }

    // Create a coloured brush to paint the window

    HBRUSH hBrush = CreateSolidBrush(WindowColour);
    EXECUTE_ASSERT(GetClientRect(m_TextWindow.GetWindowHWND(),&ClientRect));
    EXECUTE_ASSERT(FillRect(m_TextWindow.GetWindowHDC(),&ClientRect,hBrush));
    EXECUTE_ASSERT(DeleteObject(hBrush));
    return TRUE;

} // OnPaint

//
// DoRenderSample
//
// This is called when a sample is ready for rendering
//
HRESULT CTextOutFilter::DoRenderSample(IMediaSample *pMediaSample)
{
    ASSERT(pMediaSample);
    DrawText(pMediaSample);
    return NOERROR;

} // DoRenderSample

//
// OnReceiveFirstSample
//
// Display an image if not streaming
//
void CTextOutFilter::OnReceiveFirstSample(IMediaSample *pMediaSample)
{
    if(IsStreaming() == FALSE)
    {
        ASSERT(pMediaSample);
        DrawText(pMediaSample);
    }

} // OnReceiveFirstSample

//
// DrawText
//
// This is called with an IMediaSample interface on the image to be drawn. We
// are called from two separate code paths. The first is when we're signalled
// that an image has become due for rendering, the second is when we need to
// refresh a static window image. NOTE it is safe to check the type of buffer
// allocator as to change it we must be inactive, which by definition means
// we cannot have any samples available to render so we cannot be here
//
void CTextOutFilter::DrawText(IMediaSample *pMediaSample)
{
    BYTE *pText;        // Pointer to image data
    RECT rcClip;        // window rectangle

    SetRect(&rcClip, (LONG) 0, (LONG) 0,
            m_TextWindow.GetWindowWidth(),
            m_TextWindow.GetWindowHeight());

    pMediaSample->GetPointer(&pText);
    ASSERT(pText != NULL);

    // Ignore zero length samples

    if (pMediaSample->GetActualDataLength() == 0) {
        return;
    }

    // Remove trailing NULL from the text data

    ExtTextOut(m_TextWindow.GetWindowHDC(),
       0, 0,
       ETO_OPAQUE | ETO_CLIPPED,
       &rcClip,
       (char *) pText,
       pMediaSample->GetActualDataLength() - 1,
       NULL);

    GdiFlush();

} // DrawText

//
// Constructor
//
// Derived class handling window interactions. We did have the main renderer
// object inheriting from CBaseControlWindow so that we didn't have to have
// a separate class but that means there are two many classes derived from
// CUnknown, so when in the final text out filter class you call something
// like GetOwner it gets really confusing to know who is actually going to
// be called. So in the end we made it a separate class for the window. We
// have to specialise the base class to provide the PURE virtual method that
// returns the class and window information (GetClassWindowStyles). We are
// also interested in certain window messages like WM_PAINT and WM_NCHITTEST
//
CTextOutWindow::CTextOutWindow(TCHAR *pName,                // Object string
                               LPUNKNOWN pUnk,              // COM ownership
                               HRESULT *phr,                // OLE code
                               CCritSec *pLock,             // Interface lock
                               CTextOutFilter *pRenderer) : // Main filter

    CBaseControlWindow(pRenderer,pLock,pName,pUnk,phr),
    m_pRenderer(pRenderer)
{
    PrepareWindow();

} // Constructor

//
// Destructor
//
CTextOutWindow::~CTextOutWindow()
{
    DoneWithWindow();

} // Destructor

//
// OnReceiveMessage
//
// This is a virtual method that does our derived class message handling
// We should process the messages we are interested in and then call the
// base class as appropriate - some messages we may not pass forward
//
LRESULT CTextOutWindow::OnReceiveMessage(HWND hwnd,         // Window handle
                                         UINT uMsg,         // Message ID
                                         WPARAM wParam,     // First parameter
                                         LPARAM lParam)     // Other parameter
{
    switch (uMsg) {

        // This tells us some of the window's client area has become exposed
        // If our connected filter is doing overlay work then we repaint the
        // background so that it will pick up the window clipping changes

        case WM_PAINT:

            m_pRenderer->OnPaint(RGB(0,0,0));
            return (LRESULT) 1;

    }
    return CBaseControlWindow::OnReceiveMessage(hwnd,uMsg,wParam,lParam);

} // OnReceiveMessage

//
// OnClose
//
// Overriding the WM_CLOSE handling to also signal EC_USERABORT
//
BOOL CTextOutWindow::OnClose()
{
    CBaseControlWindow::OnClose();
    m_pRenderer->NotifyEvent(EC_USERABORT,0,0);
    return TRUE;

} // OnClose

//
// GetClassWindowStyles
//
// When we call PrepareWindow in our constructor it will call this method as
// it is going to create the window to get our window and class styles. The
// return code is the class name and must be allocated in static storage. We
// specify a normal window during creation although the window styles as well
// as the extended styles may be changed by the application via IVideoWindow
//
LPTSTR CTextOutWindow::GetClassWindowStyles(DWORD *pClassStyles,
                                            DWORD *pWindowStyles,
                                            DWORD *pWindowStylesEx)
{
    *pClassStyles = TextClassStyles;
    *pWindowStyles = TextWindowStyles;
    *pWindowStylesEx = (DWORD) 0;
    return TextClassName;

} // GetClassWindowStyles


TEXTOUT.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

// You MUST change this when adapting this sample!
// Run uuidgen.exe from any SDK to get a new GUID

DEFINE_GUID(CLSID_PlainText,
0x48025242, 0x2d39, 0x11ce, 0x87, 0x5d, 0x0, 0x60, 0x8c, 0xb7, 0x80, 0x66);

class CTextOutFilter;
class CTextOutWindow;

// These are our video window styles

const LPTSTR TextClassName = TEXT("TextRendererBaseClass");
const DWORD TextClassStyles = (CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT);
const DWORD TextWindowStyles = (WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);

// Derived class handling window interactions. We did have the main renderer
// object inheriting from CBaseControlWindow so that we didn't have to have
// a separate class but that means there are two many classes derived from
// CUnknown, so when in the final text out filter class you call something
// like GetOwner it gets really confusing to know who is actually going to
// be called. So in the end we made it a separate class for the window. We
// have to specialise the base class to provide the PURE virtual method that
// returns the class and window information (GetClassWindowStyles). We are
// also interested in certain window messages like WM_PAINT and WM_NCHITTEST

class CTextOutWindow : public CBaseControlWindow
{
    CTextOutFilter *m_pRenderer;

public:

    CTextOutWindow(TCHAR *pName,                 // Object description
                   LPUNKNOWN pUnk,               // Normal COM ownership
                   HRESULT *phr,                 // OLE failure code
                   CCritSec *pLock,              // Our interface Lock
                   CTextOutFilter *pRenderer);   // Delegates locking to

    ~CTextOutWindow();
    BOOL OnClose();

    LRESULT OnReceiveMessage(HWND hwnd,             // Window handle
                             UINT uMsg,             // Message ID
                             WPARAM wParam,         // First parameter
                             LPARAM lParam);        // Other parameter

    LPTSTR GetClassWindowStyles(DWORD *pClassStyles,
                                DWORD *pWindowStyles,
                                DWORD *pWindowStylesEx);

}; // CTextOutWindow

// Overall filter object for the text renderer. We have to provide our own
// version of NonDelegatingQueryInterface so that we can expose not only the
// interfaces supported by the base renderer but also pass on queries for
// IVideoWindow to our window handling class (m_TextWindow). The rest of the
// methods we override are pretty dull, dealing with type checking and so on

class CTextOutFilter : public CBaseRenderer
{
    CTextOutWindow m_TextWindow;

public:

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);

    CTextOutFilter(LPUNKNOWN pUnk,HRESULT *phr);
    ~CTextOutFilter();
    STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **);

    STDMETHODIMP Pause();
    HRESULT BreakConnect();
    HRESULT CheckMediaType(const CMediaType *pmt);
    BOOL OnPaint(COLORREF WindowColour);
    HRESULT DoRenderSample(IMediaSample *pMediaSample);
    void OnReceiveFirstSample(IMediaSample *pMediaSample);
    void DrawText(IMediaSample *pMediaSample);

}; // CTextOutFilter


BUILDER.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Begin VB.Form frmMain
   Caption         =   "Filter and Pin Viewer"
   ClientHeight    =   6060
   ClientLeft      =   1245
   ClientTop       =   1935
   ClientWidth     =   7230
   LinkTopic       =   "frmMain"
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   6060
   ScaleWidth      =   7230
   Begin VB.Frame frameFilter
      Caption         =   "Filter Graph"
      Height          =   2652
      Left            =   120
      TabIndex        =   12
      Top             =   120
      Width           =   6972
      Begin VB.CommandButton cmdSource
         Caption         =   "Add &Source Filter..."
         Height          =   372
         Left            =   4560
         TabIndex        =   14
         Top             =   2160
         Width           =   1572
      End
      Begin VB.ListBox listFilters
         Height          =   1425
         Left            =   3960
         TabIndex        =   4
         Top             =   480
         Width           =   2775
      End
      Begin VB.CommandButton cmdAddRegFilter
         Caption         =   "&Add ->"
         Height          =   372
         Left            =   3120
         TabIndex        =   13
         Top             =   600
         Width           =   732
      End
      Begin VB.ListBox listRegFilters
         Height          =   1425
         Left            =   240
         TabIndex        =   2
         Top             =   480
         Width           =   2775
      End
      Begin VB.Label lblFilters
         Caption         =   "Filters in current filter &graph"
         Height          =   252
         Left            =   3960
         TabIndex        =   3
         Top             =   240
         Width           =   2052
      End
      Begin VB.Label lblRegFilters
         Caption         =   "&Registered filters"
         Height          =   252
         Left            =   240
         TabIndex        =   1
         Top             =   240
         Width           =   2052
      End
   End
   Begin VB.Frame framePinInfo
      Caption         =   "Filter"
      Height          =   3132
      Left            =   120
      TabIndex        =   0
      Top             =   2760
      Width           =   6972
      Begin VB.TextBox txtPinInfo
         Height          =   1812
         Left            =   3720
         MultiLine       =   -1  'True
         ScrollBars      =   2  'Vertical
         TabIndex        =   17
         Text            =   "builder.frx":0000
         Top             =   1200
         Width           =   3012
      End
      Begin VB.CommandButton cmdConnect
         Caption         =   "Co&nnect One Pin..."
         Height          =   372
         Left            =   1920
         TabIndex        =   8
         Top             =   2640
         Width           =   1692
      End
      Begin VB.CommandButton cmdRender
         Caption         =   "&Connect Downstream"
         Height          =   372
         Left            =   120
         TabIndex        =   7
         Top             =   2640
         Width           =   1692
      End
      Begin VB.ListBox listPins
         Height          =   1230
         Left            =   240
         TabIndex        =   6
         Top             =   1200
         Width           =   3255
      End
      Begin VB.Label lblFilterName
         Height          =   255
         Left            =   1440
         TabIndex        =   16
         Top             =   240
         Width           =   4815
      End
      Begin VB.Label lblFilter
         Caption         =   "Filter name:"
         Height          =   255
         Left            =   240
         TabIndex        =   15
         Top             =   240
         Width           =   975
      End
      Begin VB.Label lblVendor
         Caption         =   "Vendor: "
         Height          =   255
         Left            =   240
         TabIndex        =   11
         Top             =   480
         Width           =   735
      End
      Begin VB.Label lblVendorInfo
         Height          =   255
         Left            =   1440
         TabIndex        =   10
         Top             =   480
         Width           =   4935
      End
      Begin VB.Label lblPinListbox
         Caption         =   "&Pins in selected filter"
         Height          =   255
         Left            =   240
         TabIndex        =   5
         Top             =   840
         Width           =   2055
      End
      Begin VB.Label lblPinInfo
         Caption         =   "Information for selected pin"
         Height          =   252
         Left            =   3720
         TabIndex        =   9
         Top             =   840
         Width           =   2172
      End
   End
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   8760
      Top             =   -120
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
      Flags           =   4096
   End
   Begin VB.Menu mnuFilterGraph
      Caption         =   "&FilterGraph"
      Begin VB.Menu mnu_FilterGraphNew
         Caption         =   "&New (empty)"
      End
      Begin VB.Menu mnu_FilterGraphGenerate
         Caption         =   "&Generate from input file..."
      End
      Begin VB.Menu mnu_Separator1
         Caption         =   "-"
      End
      Begin VB.Menu mnu_FilterGraphRun
         Caption         =   "&Run"
      End
      Begin VB.Menu mnu_FilterGraphPause
         Caption         =   "&Pause"
      End
      Begin VB.Menu mnu_FilterGraphStop
         Caption         =   "&Stop"
      End
      Begin VB.Menu mnu_Separator2
         Caption         =   "-"
      End
      Begin VB.Menu mnu_FilterGraphExit
         Caption         =   "E&xit"
      End
   End
   Begin VB.Menu mnu_Options
      Caption         =   "&Options"
      Begin VB.Menu mnu_BuildCustomGraph
         Caption         =   "&Build custom graph"
      End
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' global variables
' use the prefix "g_obj" to indicate a global object
Dim g_objMC As IMediaControl ' object  'IMediaControl
Dim g_objSelFilter As Object 'IFilterInfo
Dim g_objRegFilters As Object ' IFilterInfo collection
Dim g_objLocalRegFilter As Object ' IFilterInfo
Dim g_objSelPin As Object  ' IPinInfo; pin selected from listbox
Dim g_fRunning As Boolean  ' indicates whether the video is running or not

Private Sub RefreshRegFilters()
' update the listbox of registered filters
' using the global variable g_objRegFilters
    Dim filter As IRegFilterInfo
    listRegFilters.Clear
    If Not g_objRegFilters Is Nothing Then
        For Each filter In g_objRegFilters
            listRegFilters.AddItem filter.Name
        Next filter
    End If
    If listRegFilters.ListCount > 0 Then
        listRegFilters.ListIndex = 0  ' select first in list
    End If
End Sub

Private Sub AddFilter(FName As String, f As IFilterInfo)
' call IRegFilterInfo::filter
    Dim LocalRegFilters As Object

    Set LocalRegFilters = g_objMC.RegFilterCollection
    Dim filter As IRegFilterInfo
    For i = 0 To (LocalRegFilters.Count - 1) Step 1
        LocalRegFilters.Item i, filter
        If filter.Name = FName Then
            filter.filter f
            Exit For
        End If
    Next i
    RefreshFilters

End Sub

Private Sub cmdAddRegFilter_Click()
' IMediaControl property RegFilterCollection is an
' an IAMCollection interface
    Dim filter As IRegFilterInfo
    For Each filter In g_objRegFilters ' listRegFilters
        If filter.Name = listRegFilters.Text Then
            Dim f As IFilterInfo
            filter.filter f
            If f.IsFileSource Then
               CommonDialog1.ShowOpen
               ' handle user cancel
               f.filename = CommonDialog1.filename
            End If
            Exit For
        End If
    Next filter

    ' Set frmRegFilters = g_objMC.RegFilterCollection
    RefreshFilters
End Sub

Private Sub cmdConnect_Click()
' connects the current selected pin, indicated by
' g_objSelPin , g_objMC, and g_objSelFilter
    On Error Resume Next ' if already connected, connect fails
    frmSelectPin.OtherDir = g_objSelPin.Direction
    Set frmSelectPin.g_objMC = g_objMC
    Set frmSelectPin.g_objFI = g_objSelFilter
    frmSelectPin.RefreshFilters
    frmSelectPin.Show 1
    If frmSelectPin.bOK Then
        Dim objPI As IPinInfo
        Set objPI = frmSelectPin.g_objPI
        g_objSelPin.Connect objPI
        RefreshFilters  ' sub in this VB app that updates display
    End If
End Sub

Private Sub Form_Load()
' initialize the display
    listFilters.Clear
    listPins.Clear
    txtPinInfo.Text = ""
' create the global object used throughout the app
    Set g_objMC = New FilgraphManager
' update the display for registered filters
    Set g_objRegFilters = g_objMC.RegFilterCollection
    RefreshRegFilters
End Sub

Private Sub cmdRender_Click()
' call IPinInfo::Render
' complete the graph downstream from this pin
    On Error Resume Next  ' if already connected, this fails
    g_objSelPin.Render
    RefreshFilters
End Sub

Private Sub cmdSource_Click()
' calls IMediaControl::AddSourceFilter
' adds to the graph the source filter that can read the given filename
' Returns an IFilterInfo object.

    Dim objFilter As Object  ' tmp object for valid syntax; not used here

    On Error GoTo cmdsourceclick_cancelerror
    CommonDialog1.CancelError = True
    CommonDialog1.filter = "ActiveMovie files (*.mpg;*.avi;*.mov)|*.mpg;*.avi;*.mov|"
    CommonDialog1.ShowOpen  ' open the source filter
    g_objMC.AddSourceFilter CommonDialog1.filename, objFilter

    ' we don't actually use the returned IFilterInfo object here...

    RefreshFilters  ' update all info displayed by this VB app
    ' the app displays filters, pins, and detailed pin info
    Exit Sub

cmdsourceclick_cancelerror:
    ' user canceled out of the Open File dialog; just exit
    Exit Sub
End Sub

Public Sub RefreshFilters()
' update the contents of the "Filters" combo box
' using the current IMediaControl.FilterCollection
    Dim objFI As IFilterInfo

    listFilters.Clear
    For Each objFI In g_objMC.FilterCollection
        listFilters.AddItem objFI.Name
    Next objFI

    If listFilters.ListCount > 0 Then
        listFilters.ListIndex = 0  ' select first in list
    End If
End Sub

Private Sub listFilters_Click()
' user clicked on a filter in the filters combo box
' or entry 0 was selected by default when filling the
' "listFilters" listbox
' update the pins listbox to show all of its pins
    Dim objFI As IFilterInfo
    For Each objFI In g_objMC.FilterCollection
        If objFI.Name = listFilters.Text Then
            Set g_objSelFilter = objFI
            lblFilterName.Caption = objFI.Name
            lblVendorInfo.Caption = objFI.VendorInfo
            ' add all of this filter's pins to the combo box
            listPins.Clear
            Dim objPI As IPinInfo
            For Each objPI In objFI.Pins
                listPins.AddItem objPI.Name
            Next objPI
        End If
    Next objFI   ' next filter info
    listPins.ListIndex = 0  ' select the first in the combobox

End Sub

Private Sub listPins_Click()
' Add detailed pin information to the listbox on the right
' when the user clicks on a pin in the listbox on the left
  Dim strTemp As String
  On Error Resume Next
  Dim objPin As IPinInfo
  For Each objPin In g_objSelFilter.Pins
    If objPin.Name = listPins.Text Then ' selected in listbox?
      Set g_objSelPin = objPin  'yes, get all info
      strTemp = ""
      Dim objPinOther As IPinInfo
      Set objPinOther = objPin.ConnectedTo
      If Err.Number = 0 Then
        strTemp = "Connected to pin: " + objPinOther.Name + " "
        Dim objPeer As IFilterInfo
        Set objPeer = objPinOther.FilterInfo
        strTemp = strTemp + " on filter: " + objPeer.Name + " "
        Dim objMTI As IMediaTypeInfo
        Set objMTI = objPin.ConnectionMediaType
        strTemp = strTemp + vbCrLf + "Media Type: " + objMTI.Type
      End If
      If objPin.Direction = 0 Then
        strTemp = strTemp + " " + vbCrLf + "Direction: Input"
      Else
        strTemp = strTemp + " " + vbCrLf + "Direction: Output"
      End If
    txtPinInfo.Text = strTemp
    End If
  Next objPin
  Exit Sub

End Sub

Private Sub listRegFilters_DblClick()
    cmdAddRegFilter_Click
End Sub

Private Sub mnu_BuildCustomGraph_Click()

' This routine demonstrates a likely common use
' of these methods in Visual Basic applications:
' Directly creating the filter graph needed
' for a specific multimedia file.

' The graph has the following filters: AVI Source, AVI Decompressor,
' Video Renderer, AVI Splitter, and Audio Renderer.
' Note that these filters can be connected by reusing just
' two pin object variables, but for clarity of the example,
' all are defined using names that reflect their position
' in the filter graph.

' The filters are declared with their pins, as follows:

Dim pSourceFilter As IFilterInfo  ' AVI source filter; has two pins
Dim SourceOutputPin As IPinInfo  'Source Filter output pin

Dim pAVISplitter As IFilterInfo ' AVI splitter
Dim SplitterInPin As IPinInfo   ' AVI splitter pin "Input"
Dim SplitterOut00Pin As IPinInfo  ' AVI splitter pin "Stream 00"
Dim SplitterOut01Pin As IPinInfo  ' AVI splitter pin "Stream 01"

Dim pDECFilter As IFilterInfo  ' AVI Decompressor; has two pins
Dim DECInPin As IPinInfo   'AVI Decompressor pin "XForm In"
Dim DECOutPin As IPinInfo   ' AVI Decompressor pin "XForm Out"

Dim pVidRenderer As IFilterInfo ' Video renderer, has one pin
Dim VidRendInPin As IPinInfo  ' Video Renderer pin "Input"

Dim pAudioRenderer As IFilterInfo 'Audio renderer, has one pin
Dim AudioRendInPin As IPinInfo ' Audio Renderer pin "Input"

Dim pPin As IPinInfo
    Set SVideoPin = Nothing
    Set SAudioPin = Nothing
    Set DECInPin = Nothing
    Set DECOutPin = Nothing
    Set VidRendInPin = Nothing
    Set AudioRendInPin = Nothing
' reinitialize all global variables
    Set g_objRegFilters = Nothing
    Set g_objSelFilter = Nothing
    Set g_objSelPin = Nothing
    Set g_objMC = Nothing
' create a new IMediaControl object
    Set g_objMC = New FilgraphManager
    ' reset the listRegFilters again
    Set g_objRegFilters = g_objMC.RegFilterCollection
    RefreshRegFilters
' reinitialize the display
    listFilters.Clear
    lblFilterName.Caption = ""
    lblVendorInfo.Caption = ""
    listPins.Clear
    txtPinInfo.Text = ""
    g_fRunning = False

'Add source filter for an AVI file
    On Error GoTo err_CustomGraph_Cancel
    CommonDialog1.CancelError = True
    CommonDialog1.filter = "AVI files (*.avi)|*.avi"
    CommonDialog1.ShowOpen  ' get the name of the source or filter graph file
    g_objMC.AddSourceFilter CommonDialog1.filename, pSourceFilter

    On Error GoTo err_CustomGraph  ' handle other errors
    ' Get the pins we need to connect
    For Each pPin In pSourceFilter.Pins
      Debug.Print pPin.Name
      If pPin.Name = "Output" Then
          Set SourceOutputPin = pPin
      End If
    Next pPin

    'Add DEC filter
    AddFilter "AVI Decompressor", pDECFilter
    'Print out list of pins on decompressor filter
    For Each pPin In pDECFilter.Pins
      Debug.Print pPin.Name
      ' save specific pins to connect them
      If pPin.Name = "XForm In" Then
          Set DECInPin = pPin
      End If
      If pPin.Name = "XForm Out" Then
          Set DECOutPin = pPin
      End If
    Next pPin

    'Add AVI Splitter
    AddFilter "AVI Splitter", pAVISplitter
    'Print out list of pins on decompressor filter
    For Each pPin In pAVISplitter.Pins
      Debug.Print pPin.Name
      ' save specific pins to connect them
      ' pin 0, pin 1
      If pPin.Name = "input pin" Then
          Set SplitterInPin = pPin
      ElseIf pPin.Name = "Stream 00" Then
          Set SplitterOut00Pin = pPin
      ElseIf pPin.Name = "Stream 01" Then
          Set SplitterOut01Pin = pPin
      End If
    Next pPin

    'Connect Source video output pin to AVI splitter input pin
    If Not SourceOutputPin Is Nothing And Not SplitterInPin Is Nothing Then
        SourceOutputPin.Connect SplitterInPin
    End If

    ' Splitter now knows how many output pins it needs
    For Each pPin In pAVISplitter.Pins
      Debug.Print pPin.Name
      ' save specific pins to connect them
      ' pin 0, pin 1
      If pPin.Name = "Stream 00" Then
          Set SplitterOut00Pin = pPin
      ElseIf pPin.Name = "Stream 01" Then
          Set SplitterOut01Pin = pPin
      End If
    Next pPin

    'Add Video Renderer filter and set its pin variables
    AddFilter "Video Renderer", pVidRenderer
    'Print out list of pins on video renderer filter
    For Each pPin In pVidRenderer.Pins
      Debug.Print pPin.Name
      If pPin.Name = "Input" Then
          Set VidRendInPin = pPin
      End If
    Next pPin

    'Add Audio Renderer filter and set its pin variables
    AddFilter "Audio Renderer", pAudioRenderer
    'Print out list of pins on audioo renderer filter
    For Each pPin In pAudioRenderer.Pins
      Debug.Print pPin.Name
      If InStr(pPin.Name, "Input") Then
          Set AudioRendInPin = pPin
      End If
    Next pPin

    ' Connect AVI splitter stream 01 to AVI decompressor
    If Not DECInPin Is Nothing And Not SplitterOut00Pin Is Nothing Then
        SplitterOut00Pin.Connect DECInPin
    End If

    'Connect DEC filter output pin to Video Renderer input pin
    If Not DECOutPin Is Nothing And Not VidRendInPin Is Nothing Then
        DECOutPin.Connect VidRendInPin
    End If

    ' Connect AVI splitter stream 01 to audio renderer
    ' continue if there is no audio connection for the source AVI file
    On Error Resume Next
    If Not AudioRendInPin Is Nothing And Not SplitterOut01Pin Is Nothing Then
        SplitterOut01Pin.Connect AudioRendInPin
    End If

    RefreshFilters
    Exit Sub
err_CustomGraph:
    MsgBox "Could not create the custom filter graph. Please select an .AVI file that uses the AVI splitter and AVI decompressor filters."
    Exit Sub
err_CustomGraph_Cancel:
    ' user cancelled out of File Open dialog; just exit
    Exit Sub
End Sub

Private Sub mnu_FilterGraphExit_Click()
    Set g_objLocalRegFilter = Nothing
    Set g_objRegFilters = Nothing
    Set g_objSelFilter = Nothing
    Set g_objSelPin = Nothing
    Set g_objMC = Nothing
    End
End Sub

Private Sub mnu_FilterGraphGenerate_Click()
' User is initializing the filter graph based on a source file
' Create a new filter graph and then get all filters, connections

' reset the application's global objects
    Set g_objRegFilters = Nothing
    Set g_objSelFilter = Nothing
    Set g_objSelPin = Nothing
    Set g_objMC = Nothing
' initialize the display
    listFilters.Clear
    listPins.Clear
    txtPinInfo.Text = ""
' create a new IMediaControl object
    Set g_objMC = New FilgraphManager
' refresh the display for registered filters
    Set g_objRegFilters = g_objMC.RegFilterCollection
    RefreshRegFilters

    ' use the common dialog to let the user select the input file
    On Error GoTo filtergraphgenerate_cancelerror
    CommonDialog1.CancelError = True
    CommonDialog1.filter = "ActiveMovie files (*.mpg;*.avi;*.mov;*.wav)|*.mpg;*.avi;*.mov;*.wav|"
    CommonDialog1.ShowOpen

    ' call IMediaControl::RenderFile to add all filters and connect all pins
    g_objMC.RenderFile CommonDialog1.filename
    ' update and display the list of registered filters
    ' update and display all active filters for this filter graph
    RefreshFilters  ' local subroutine to update the listbox
    Exit Sub
filtergraphgenerate_cancelerror:
    ' user cancelled out of the File Open dialog
    Exit Sub
End Sub

Private Sub mnu_FilterGraphNew_Click()
' user wants to start with a fresh filter graph
' reset the global objects
    Set g_objRegFilters = Nothing
    Set g_objSelFilter = Nothing
    Set g_objSelPin = Nothing
    Set g_objMC = Nothing
' create a new IMediaControl object
    Set g_objMC = New FilgraphManager
    ' reset the listRegFilters again
    Set g_objRegFilters = g_objMC.RegFilterCollection
    RefreshRegFilters
' clear the contents of the listboxes, textboxes, and labels
    listFilters.Clear
    lblFilterName.Caption = ""
    lblVendorInfo.Caption = ""
    listPins.Clear
    txtPinInfo.Text = ""
' set the current playback state to stopped
    g_fRunning = False
End Sub

Private Sub mnu_FilterGraphPause_Click()
' sets the playback state to pause
    g_objMC.Pause
End Sub

Private Sub mnu_FilterGraphRun_Click()
' sets the playback state to run and starts the filter graph if it wasn't running
    If Not g_fRunning Then
      Dim pPosition As IMediaPosition
      Set pPosition = g_objMC
      If Err.Number = 0 Then
        On Error Resume Next
        pPosition.CurrentPosition = 0
      End If
    End If
    g_objMC.Run
    g_fRunning = True
End Sub

Private Sub mnu_FilterGraphStop_Click()
' stops the filter graph playback
    g_objMC.Stop
    g_fRunning = False
End Sub


SELPIN.FRM

VERSION 5.00
Begin VB.Form frmSelectPin
   Caption         =   "Connect to Pin"
   ClientHeight    =   3210
   ClientLeft      =   4890
   ClientTop       =   4920
   ClientWidth     =   6270
   LinkTopic       =   "Form2"
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   3210
   ScaleWidth      =   6270
   Begin VB.ListBox listPins
      Height          =   1425
      Left            =   3360
      TabIndex        =   7
      Top             =   360
      Width           =   2655
   End
   Begin VB.ListBox listFilters
      Height          =   1425
      Left            =   240
      TabIndex        =   5
      Top             =   360
      Width           =   2655
   End
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   3360
      TabIndex        =   4
      Top             =   2640
      Width           =   975
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   1920
      TabIndex        =   3
      Top             =   2640
      Width           =   975
   End
   Begin VB.Label Label2
      Caption         =   "Pins"
      Height          =   252
      Left            =   3240
      TabIndex        =   6
      Top             =   120
      Width           =   492
   End
   Begin VB.Label VendorInfoLabel
      Caption         =   "Vendor Info:"
      Height          =   252
      Left            =   120
      TabIndex        =   2
      Top             =   2160
      Width           =   972
   End
   Begin VB.Label VendorInfo
      Height          =   252
      Left            =   1320
      TabIndex        =   1
      Top             =   2160
      Visible         =   0   'False
      Width           =   2772
   End
   Begin VB.Label Label1
      Caption         =   "Filters"
      Height          =   255
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   495
   End
End
Attribute VB_Name = "frmSelectPin"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' create filter components to connect filter pins
Public OtherDir As Long
Public g_objFI As IFilterInfo
Public g_objPI As IPinInfo
Public g_objMC As IMediaControl
Public bOK As Boolean

Private Sub Cancel_Click()
' cancel was clicked, so no pin connection is made
    bOK = False
    Hide
End Sub

Private Sub listFilters_Click()
' when the user clicks on a specific filter in the filter graph, this creates the
' list of pins for that filter in the pin listbox
    Dim pfilter As IFilterInfo
    For Each pfilter In g_objMC.FilterCollection
        If pfilter.Name = listFilters.Text Then
            ' display the information and pins for the selected filter
            Set g_objFI = pfilter  ' global FilterInfo object
            VendorInfo.Caption = pfilter.VendorInfo
            listPins.Clear
            Dim pin As IPinInfo
            For Each pin In pfilter.Pins
                Dim pinOther As IPinInfo
                On Error Resume Next
                Set pinOther = pin.ConnectedTo
                If Err.Number <> 0 Then
                    If pin.Direction <> OtherDir Then
                        listPins.AddItem pin.Name
                    End If
                End If
            Next pin
        End If
    Next pfilter
    listPins.ListIndex = 0
End Sub

Private Sub Form_Load()
' fill the filters listbox with all filters in the current filter graph
    RefreshFilters
End Sub

Public Sub RefreshFilters()
' fill the filters listbox with all filters in the current filter graph
    listFilters.Clear
    Dim filter As IFilterInfo
    For Each filter In g_objMC.FilterCollection
        Dim pin As IPinInfo
        Dim pinOther As IPinInfo
        For Each pin In filter.Pins
            On Error Resume Next
            Set pinOther = pin.ConnectedTo
            If Err.Number <> 0 Then
                If pin.Direction <> OtherDir Then
                    listFilters.AddItem filter.Name
                    Exit For
                End If
            End If
        Next pin
    Next filter
    listFilters.ListIndex = 0
End Sub

Private Sub OK_Click()
' connect the selected pins, if possible. if no connection is possible, the pin
' selection box closes and the program continues normally.
    Dim pin As IPinInfo
    For Each pin In g_objFI.Pins
        If pin.Name = listPins.Text Then
            Set g_objPI = pin
            bOK = True
            Exit For
        End If
    Next pin
    Hide
End Sub

Private Sub listPins_Click()
' when a pin is selected, store it in the global pin object
  Dim pPin As IPinInfo
  For Each pPin In g_objFI.Pins
    If pPin.Name = listPins.Text Then
      Set SelPin = pPin
    End If
  Next pPin
End Sub


OCXVB01.BAS

Attribute VB_Name = "AMSample"
' ActiveMovie OCX Sample Code
' Copyright (c) 1996 - 1997 Microsoft Corporation
' All Rights Reserved

Option Explicit

' Counters for Timer, PositionChange, and StateChange events.
Global g_cTimer As Long
Global g_cPositionChange As Long
Global g_cStateChange As Long

' Tracks file name extension and whether or not a file is currently open.
Global g_FileExtension As String
Global g_FileOpened As Boolean

Sub Main()

    ' Main entry point to ActiveMovie OCX Sample application.

    ' Load main form, position and show.
    Load frmMain
    With frmMain
        .Top = Screen.Height * 0.05
        .Left = Screen.Width * 0.05
        .Visible = True
    End With

    ' Load viewer form but don't show yet.
    Load frmViewer
    With frmViewer
        .Visible = False
        .Left = frmMain.Left + frmMain.Width
        .Top = frmMain.Top
    End With

    ' Initialize global variables.
    g_FileOpened = False
    g_FileExtension = ""

End Sub

Sub ResizeViewer()

    ' Resize form to dimensions of ActiveMovie control + nonclient region.
    With frmViewer
        .Visible = False
        .Height = .ActiveMovie1.Height + (.Height - .ScaleHeight)
        .Width = .ActiveMovie1.Width + (.Width - .ScaleWidth)
        .Visible = True
    End With

End Sub

Sub UpdateStatusBar()

    ' Update the main form status bar to show the current number of events.

    With frmMain.StatusBar1
         .Panels(1).Text = "Timer Events: " & g_cTimer
         .Panels(2).Text = "State Changes: " & g_cStateChange
         .Panels(3).Text = "Position Changes: " & g_cPositionChange
    End With

End Sub


OCXVB01.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Object = "{6B7E6392-850A-101B-AFC0-4210102A8DA7}#1.1#0"; "COMCTL32.OCX"
Begin VB.Form frmMain
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "ActiveMovie OCX Sample"
   ClientHeight    =   2910
   ClientLeft      =   3435
   ClientTop       =   2295
   ClientWidth     =   5370
   Icon            =   "ocxvb01.frx":0000
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   2910
   ScaleWidth      =   5370
   ShowInTaskbar   =   0   'False
   Begin VB.Frame frmImageSize
      Caption         =   "Change Image Size"
      Enabled         =   0   'False
      Height          =   2292
      Left            =   2880
      TabIndex        =   9
      Top             =   120
      Width           =   2292
      Begin VB.OptionButton obOneHalfScreen
         Caption         =   "One Half Screen"
         Height          =   255
         Left            =   240
         TabIndex        =   14
         Top             =   1800
         Width           =   1935
      End
      Begin VB.OptionButton obOneFourthScreen
         Caption         =   "One Fourth Screen"
         Height          =   255
         Left            =   240
         TabIndex        =   13
         Top             =   1440
         Width           =   1935
      End
      Begin VB.OptionButton obOneSixteenthScreen
         Caption         =   "One Sixteenth Screen"
         Height          =   255
         Left            =   240
         TabIndex        =   12
         Top             =   1080
         Width           =   1935
      End
      Begin VB.OptionButton obDoubleOriginal
         Caption         =   "Double Original Size"
         Height          =   255
         Left            =   240
         TabIndex        =   11
         Top             =   720
         Width           =   1935
      End
      Begin VB.OptionButton obOriginal
         Caption         =   "Original Size"
         Height          =   255
         Left            =   240
         TabIndex        =   10
         Top             =   360
         Width           =   1815
      End
   End
   Begin VB.Frame frmPlaybackRate
      Caption         =   "Change Playback Rate"
      Height          =   1212
      Left            =   240
      TabIndex        =   4
      Top             =   1200
      Width           =   2412
      Begin ComctlLib.Slider Slider1
         Height          =   492
         Left            =   240
         TabIndex        =   5
         Top             =   240
         Width           =   1932
         _ExtentX        =   3413
         _ExtentY        =   873
         _Version        =   327680
         Enabled         =   -1  'True
         LargeChange     =   1
         TickStyle       =   2
      End
      Begin VB.Label Label3
         Alignment       =   2  'Center
         Caption         =   "1.5"
         Height          =   252
         Left            =   1860
         TabIndex        =   8
         Top             =   840
         Width           =   252
      End
      Begin VB.Label Label2
         Alignment       =   2  'Center
         Caption         =   "1.0"
         Height          =   252
         Left            =   1020
         TabIndex        =   7
         Top             =   840
         Width           =   372
      End
      Begin VB.Label Label1
         Alignment       =   2  'Center
         Caption         =   "0.5"
         Height          =   252
         Left            =   240
         TabIndex        =   6
         Top             =   840
         Width           =   372
      End
   End
   Begin VB.Frame frmChangeFrames
      Caption         =   "Change Current Position"
      Height          =   972
      Left            =   240
      TabIndex        =   1
      Top             =   120
      Width           =   2412
      Begin VB.CommandButton cmdNextFrame
         Caption         =   "&>>"
         Enabled         =   0   'False
         Height          =   372
         Left            =   1320
         TabIndex        =   3
         Top             =   360
         Width           =   852
      End
      Begin VB.CommandButton cmdPreviousFrame
         Caption         =   "&<<"
         Enabled         =   0   'False
         Height          =   372
         Left            =   240
         TabIndex        =   2
         Top             =   360
         Width           =   852
      End
   End
   Begin ComctlLib.StatusBar StatusBar1
      Align           =   2  'Align Bottom
      Height          =   408
      Left            =   0
      TabIndex        =   0
      Top             =   2508
      Width           =   5376
      _ExtentX        =   9472
      _ExtentY        =   714
      SimpleText      =   ""
      _Version        =   327680
      BeginProperty Panels {0713E89E-850A-101B-AFC0-4210102A8DA7}
         NumPanels       =   3
         BeginProperty Panel1 {0713E89F-850A-101B-AFC0-4210102A8DA7}
            AutoSize        =   1
            Object.Width           =   3122
            TextSave        =   ""
            Object.Tag             =   ""
         EndProperty
         BeginProperty Panel2 {0713E89F-850A-101B-AFC0-4210102A8DA7}
            AutoSize        =   1
            Object.Width           =   3122
            TextSave        =   ""
            Object.Tag             =   ""
         EndProperty
         BeginProperty Panel3 {0713E89F-850A-101B-AFC0-4210102A8DA7}
            AutoSize        =   1
            Object.Width           =   3122
            TextSave        =   ""
            Object.Tag             =   ""
         EndProperty
      EndProperty
   End
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   240
      Top             =   5880
      _ExtentX        =   688
      _ExtentY        =   688
      _Version        =   327680
   End
   Begin VB.Menu mnu_File
      Caption         =   "&File"
      Begin VB.Menu mnu_File_Open
         Caption         =   "&Open"
      End
      Begin VB.Menu sep1
         Caption         =   "-"
      End
      Begin VB.Menu mnu_File_Run
         Caption         =   "&Run"
      End
      Begin VB.Menu mnu_File_Pause
         Caption         =   "&Pause"
      End
      Begin VB.Menu mnu_File_Stop
         Caption         =   "&Stop"
      End
      Begin VB.Menu sep2
         Caption         =   "-"
      End
      Begin VB.Menu mnu_File_Exit
         Caption         =   "E&xit"
      End
   End
   Begin VB.Menu mnu_View
      Caption         =   "&View"
      Begin VB.Menu mnu_View_DisplayPanel
         Caption         =   "&Display Panel"
         Checked         =   -1  'True
      End
      Begin VB.Menu sep4
         Caption         =   "-"
      End
      Begin VB.Menu mnu_View_ControlPanel
         Caption         =   "&Control Panel"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_View_PositionControls
         Caption         =   "&Position Controls"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_View_SelectionControls
         Caption         =   "&Selection Controls"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_View_Tracker
         Caption         =   "&Tracker"
         Checked         =   -1  'True
      End
   End
   Begin VB.Menu mnu_Enable
      Caption         =   "E&nable"
      Begin VB.Menu mnu_Enable_PositionControls
         Caption         =   "&Position Controls"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_Enable_SelectionControls
         Caption         =   "&Selection Controls"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_Enable_Tracker
         Caption         =   "&Tracker"
         Checked         =   -1  'True
      End
   End
   Begin VB.Menu mnu_Options
      Caption         =   "&Options"
      Begin VB.Menu mnu_Options_AutoRewind
         Caption         =   "Auto &Rewind"
         Checked         =   -1  'True
      End
      Begin VB.Menu mnu_Options_DisplayTime
         Caption         =   "Display &Time"
         Checked         =   -1  'True
      End
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' ActiveMovie Control Sample
' Copyright (c) 1996 - 1997 Microsoft Corporation
' All Rights Reserved.
'

Private Sub cmdNextFrame_Click()
    If ((frmViewer.ActiveMovie1.CurrentPosition + 0.1) <= frmViewer.ActiveMovie1.Duration) Then
        frmViewer.ActiveMovie1.CurrentPosition = frmViewer.ActiveMovie1.CurrentPosition + 0.1
    End If
End Sub

Private Sub cmdPreviousFrame_Click()
    If ((frmViewer.ActiveMovie1.CurrentPosition - 0.1) >= 0) Then
        frmViewer.ActiveMovie1.CurrentPosition = frmViewer.ActiveMovie1.CurrentPosition - 0.1
    End If

End Sub

Private Sub Form_Load()

    ' ActiveMovie Control Sample Code
    ' Copyright (c) 1996 - 1997 Microsoft Corporation
    ' All Rights Reserved

    ' This sample demonstrates the ActiveMovie Control.
    ' It contains the following controls:
    '
    '   The ActiveMovie control (in its own form).
    '   A status bar control.
    '   The common dialogs control.
    '   Two buttons that allow you to step through the stream.
    '   Option buttons that let you select the stream size.
    '   A slider that lets you change the playback rate.

    ' The main form, frmMain, contains several menus that
    ' control various ActiveMovie controls.
    ' When events are enabled, a global counter is used to
    ' keep a running account of each ActiveMovie control event,
    ' and the value is displayed in the status control.

    ' Initialize control properties and set menu items to match.

    Slider1.Value = 5   ' Right in the middle.
    ' Default value for ActiveMovie1.Rate = 1.0
    ' Use default value.
    obOriginal.Value = True
    With frmViewer.ActiveMovie1

'***
' The DisplayMode property does not function with the most recent build
' (IE3.0) of ActiveMovie
        ' Display current position in time, rather than frames.
        '.DisplayMode = amvTime
        'mnu_Options_DisplayTime.Checked = True
'***

        ' Show the display panel.
        .ShowDisplay = True
        mnu_View_DisplayPanel.Checked = True

        ' Show the control panel and all other controls.
        .ShowControls = True
        mnu_View_ControlPanel.Checked = True
        .ShowPositionControls = False
        mnu_View_PositionControls.Checked = False
        .ShowSelectionControls = False
        mnu_View_SelectionControls.Checked = False
        .ShowTracker = True
        mnu_View_Tracker.Checked = True

        ' Enable the sub-controls on the control panel.
        .EnablePositionControls = False
        mnu_Enable_PositionControls.Checked = False
        .EnableSelectionControls = False
        mnu_Enable_SelectionControls.Checked = False
        .EnableTracker = False
        mnu_Enable_Tracker.Checked = False

        ' Set AutoRewind.
        .AutoRewind = True
        mnu_Options_AutoRewind = True
    End With

End Sub

Private Sub Form_Unload(Cancel As Integer)

    End ' end program

End Sub

Private Sub mnu_Enable_PositionControls_Click()

    ' Let the user change the prop value from the menu.

    If (mnu_Enable_PositionControls.Checked) Then
        mnu_Enable_PositionControls.Checked = False
        frmViewer.ActiveMovie1.EnablePositionControls = False
    Else
        mnu_Enable_PositionControls.Checked = True
        frmViewer.ActiveMovie1.EnablePositionControls = True
    End If

End Sub

Private Sub mnu_Enable_SelectionControls_Click()

    ' Let the user change the prop value from the menu.

    If (mnu_Enable_SelectionControls.Checked) Then
        mnu_Enable_SelectionControls.Checked = False
        frmViewer.ActiveMovie1.EnableSelectionControls = False
    Else
        mnu_Enable_SelectionControls.Checked = True
        frmViewer.ActiveMovie1.EnableSelectionControls = True
    End If

End Sub

Private Sub mnu_Enable_Tracker_Click()

    ' Let the user change the prop value from the menu.

    If (mnu_Enable_Tracker.Checked) Then
        mnu_Enable_Tracker.Checked = False
        frmViewer.ActiveMovie1.EnableTracker = False
    Else
        mnu_Enable_Tracker.Checked = True
        frmViewer.ActiveMovie1.EnableTracker = True
    End If

End Sub

Private Sub mnu_File_Exit_Click()

    End ' End program.

End Sub

Private Sub mnu_File_Open_Click()

    ' Opens an ActiveMovie file.
    ' Displays Viewer form if successful.

    On Error GoTo err_FileOpen

    CommonDialog1.Filter = "All files (*.*)|*.*|ActiveMovie files (*.mpg;*.mpa;*.mpv;*.mov;*.mpeg;*.enc;*.m1v;*.mp2)|*.mpg;*.mpa;*.mpv;*.mov;*.mpeg;*.enc;*.m1v;*.mp2|Audio files (.wav)|*.wav|Video for Windows files (.avi)|*.avi"
    CommonDialog1.Flags = 4 'Hide read-only check box
    CommonDialog1.ShowOpen

    ' New file resets playback speed to 1.0
    Slider1.Value = 5   ' right in the middle
    ' Default value for ActiveMovie1.Rate = 1.0
    ' If did not escape...
    If CommonDialog1.filename <> "" Then
        frmViewer.ActiveMovie1.filename = CommonDialog1.filename
        g_FileOpened = True
        g_FileExtension = Right$(CommonDialog1.filename, Len(CommonDialog1.filename) - InStr(CommonDialog1.filename, "."))
    Else
        GoTo err_FileOpen
    End If

    ' Reset controls and menu settings for new file.
    With frmViewer.ActiveMovie1
        .ShowPositionControls = False
        mnu_View_PositionControls.Checked = False
        .ShowSelectionControls = False
        mnu_View_SelectionControls.Checked = False
        .ShowTracker = True
        mnu_View_Tracker.Checked = True

        ' Also, enable the sub-controls on the control panel
        .EnablePositionControls = False
        mnu_Enable_PositionControls.Checked = False
        .EnableSelectionControls = False
        mnu_Enable_SelectionControls.Checked = False
        .EnableTracker = False
        mnu_Enable_Tracker.Checked = False

        ' Enable change position buttons.
        ' Can't use buttons for .asf files.
        If g_FileExtension = "asf" Then
            cmdPreviousFrame.Enabled = False
            cmdNextFrame.Enabled = False
        Else
            cmdPreviousFrame.Enabled = True
            cmdNextFrame.Enabled = True
            Slider1.Enabled = True
        End If

        ' Enable frame image size panel.
        frmImageSize.Enabled = True

    End With

    Exit Sub

err_FileOpen:

    MsgBox "File open error.", 0, "ActiveMovie OCX Sample"

    Exit Sub

End Sub

Private Sub mnu_File_Pause_Click()

    ' Pauses the current file.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.Pause
    End If

End Sub

Private Sub mnu_File_Run_Click()

    ' Starts playing the current file.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.Run
        frmViewer.ZOrder 0
    End If

End Sub

Private Sub mnu_File_Stop_Click()

    ' Stops file play.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.Stop
    End If

End Sub

Private Sub mnu_Options_AutoRewind_Click()

    ' Toggles the AutoRewind property.

    If (mnu_Options_AutoRewind.Checked) Then
        frmViewer.ActiveMovie1.AutoRewind = False
        mnu_Options_AutoRewind.Checked = False
    Else
        frmViewer.ActiveMovie1.AutoRewind = True
        mnu_Options_AutoRewind.Checked = True
    End If

End Sub

Private Sub mnu_Options_DisplayTime_Click()
'***
' The DisplayMode property does not function with the most recent build
' (IE3.0) of ActiveMovie.

    ' Toggles the DisplayMode between time and frames.

'    With frmViewer.ActiveMovie1
'        If (.DisplayMode = amvTime) Then
'            .DisplayMode = amvFrames
'            mnu_Options_DisplayTime.Checked = False
'        Else
'            .DisplayMode = amvTime
'            mnu_Options_DisplayTime.Checked = True
'        End If
'    End With
'***

End Sub

Private Sub mnu_View_ControlPanel_Click()

    ' Toggles the ShowControls property.

    With frmViewer.ActiveMovie1
        If (.AllowHideControls) Then
            If (.ShowControls) Then
                .ShowControls = False
            Else
                .ShowControls = True
            End If
            ' Toggle menu check mark.
            mnu_View_ControlPanel.Checked = Not mnu_View_ControlPanel.Checked
        End If
    End With

    ResizeViewer

End Sub

Private Sub mnu_View_DisplayPanel_Click()

    ' Toggles ShowDisplay property.

    With frmViewer.ActiveMovie1
        If (.AllowHideDisplay) Then
            If (.ShowDisplay) Then
                .ShowDisplay = False
            Else
                .ShowDisplay = True
            End If
            ' Toggle menu check mark.
            mnu_View_DisplayPanel.Checked = Not mnu_View_DisplayPanel.Checked
        End If
    End With

    ResizeViewer

End Sub

Private Sub mnu_View_PositionControls_Click()

    ' Toggles ShowPositionControls property.

    frmViewer.ActiveMovie1.ShowPositionControls = Not frmViewer.ActiveMovie1.ShowPositionControls
    mnu_View_PositionControls.Checked = Not mnu_View_PositionControls.Checked

    ResizeViewer

End Sub

Private Sub mnu_View_SelectionControls_Click()

    ' Toggles ShowSelectionControls property.

    frmViewer.ActiveMovie1.ShowSelectionControls = Not frmViewer.ActiveMovie1.ShowSelectionControls
    mnu_View_SelectionControls.Checked = Not mnu_View_SelectionControls.Checked

    ResizeViewer

End Sub

Private Sub mnu_View_Tracker_Click()

    ' Toggles ShowTracker property.

    frmViewer.ActiveMovie1.ShowTracker = Not frmViewer.ActiveMovie1.ShowTracker
    mnu_View_Tracker.Checked = Not mnu_View_Tracker.Checked

    ResizeViewer

End Sub

Private Sub obDoubleOriginal_Click()
  ' Set image size to double original size.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.MovieWindowSize = amvDoubleOriginalSize
        Call ResizeViewer
    End If
End Sub

Private Sub obOneFourthScreen_Click()
  ' Set image size to one fourth screen.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.MovieWindowSize = amvOneFourthScreen
        Call ResizeViewer
    End If
End Sub

Private Sub obOneHalfScreen_Click()
  ' Set image size to one half screen.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.MovieWindowSize = amvOneHalfScreen
        Call ResizeViewer
    End If
End Sub

Private Sub obOneSixteenthScreen_Click()
  ' Set image size to one sixteenth screen.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.MovieWindowSize = amvOneSixteenthScreen
        Call ResizeViewer
    End If
End Sub

Private Sub obOriginal_Click()
    ' Set image size to original.

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.MovieWindowSize = amvOriginalSize
        Call ResizeViewer
    End If

End Sub

Private Sub Slider1_Change()

    ' Set playback rate.
    ' 10 settings, each setting = 0.1

    If g_FileOpened = True Then
        frmViewer.ActiveMovie1.Rate = 0.5 + (0.1 * Slider1.Value)
    End If

End Sub


VIEWER.FRM

VERSION 5.00
Object = "{05589FA0-C356-11CE-BF01-00AA0055595A}#2.0#0"; "amovie.ocx"
Begin VB.Form frmViewer
   Caption         =   "ActiveMovie OCX Viewer"
   ClientHeight    =   1305
   ClientLeft      =   9840
   ClientTop       =   5475
   ClientWidth     =   4065
   ControlBox      =   0   'False
   Icon            =   "viewer.frx":0000
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   1305
   ScaleWidth      =   4065
   Begin AMovieCtl.ActiveMovie ActiveMovie1
      Height          =   1200
      Left            =   0
      TabIndex        =   0
      Top             =   0
      Width           =   3990
      _ExtentX        =   7038
      _ExtentY        =   2117
   End
End
Attribute VB_Name = "frmViewer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False

Private Sub ActiveMovie1_PositionChange(ByVal oldPosition As Double, ByVal newPosition As Double)

    ' Tracks PositionChange events.

    g_cPositionChange = g_cPositionChange + 1
    UpdateStatusBar

End Sub

Private Sub ActiveMovie1_StateChange(ByVal oldState As Long, ByVal newState As Long)

    ' Tracks StateChange events.
    g_cStateChange = g_cStateChange + 1
    UpdateStatusBar

End Sub

Private Sub ActiveMovie1_ReadyStateChange(ReadyState As ReadyStateConstants)
If ReadyState = amvComplete Then
 ResizeViewer
End If
End Sub

Private Sub ActiveMovie1_Timer()

    ' Tracks Timer events.

    g_cTimer = g_cTimer + 1
    UpdateStatusBar

End Sub

Private Sub Form_Load()

    ' ActiveMovie OCX Sample Code
    ' Copyright (c) 1996 - 1997 Microsoft Corporation
    ' All Rights Reserved

    ' See the ActiveMovie1 object for events.

End Sub

Private Sub sdmen_Click()
MsgBox Str$(ActiveMovie1.Width) + Str$(ActiveMovie1.Height)
End Sub


PLAYER.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Object = "{6B7E6392-850A-101B-AFC0-4210102A8DA7}#1.1#0"; "COMCTL32.OCX"
Begin VB.Form Form1
   Caption         =   "Form1"
   ClientHeight    =   1545
   ClientLeft      =   3915
   ClientTop       =   1530
   ClientWidth     =   4515
   LinkTopic       =   "Form1"
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   1545
   ScaleWidth      =   4515
   Begin VB.Timer Timer1
      Interval        =   200
      Left            =   3720
      Top             =   1200
   End
   Begin VB.CommandButton StopButton
      Caption         =   "&Stop"
      Height          =   495
      Left            =   2160
      TabIndex        =   3
      Top             =   120
      Width           =   735
   End
   Begin VB.CommandButton PlayButton
      Caption         =   "&Play"
      Height          =   495
      Left            =   1200
      TabIndex        =   2
      Top             =   120
      Width           =   735
   End
   Begin VB.CommandButton OpenButton
      Caption         =   "&Open"
      Height          =   495
      Left            =   240
      TabIndex        =   0
      Top             =   120
      Width           =   735
   End
   Begin ComctlLib.Slider Slider1
      Height          =   375
      Left            =   240
      TabIndex        =   1
      Top             =   840
      Width           =   3735
      _ExtentX        =   6588
      _ExtentY        =   661
      _Version        =   327680
   End
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   3240
      Top             =   1200
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Dim pMC As Object
Dim bOpen As Boolean
Dim bPlay As Boolean
Dim bSeeking As Boolean

Private Sub Form_Load()

Set pMC = Nothing
bOpen = False
bPlay = False
bSeeking = False

End Sub

Private Sub OpenButton_Click()
    If bPlay Then pMC.Stop
    If bOpen Then Set pMC = Nothing
    bOpen = False

    Set pMC = New FilgraphManager

    CommonDialog1.ShowOpen
    pMC.RenderFile CommonDialog1.filename

    Rem make window appear
    Dim pVW As IVideoWindow
    Set pVW = pMC
    pVW.Visible = True
    Set pVW = Nothing

    bOpen = True

End Sub

Private Sub PlayButton_Click()
    If Not bOpen Then Exit Sub
    If bPlay Then Exit Sub

    pMC.Run
    bPlay = True
    OpenButton.Enabled = False
    PlayButton.Enabled = False

End Sub

Private Sub Slider1_Change()

bSeeking = False
If Not bPlay Then
pMC.Stop
End If

End Sub

Private Sub Slider1_Scroll()

If Not bOpen Then Exit Sub

bSeeking = True
pMC.Pause

Dim pMP As IMediaPosition
Set pMP = pMC

Dim v As Double
v = Slider1.Value - Slider1.Min
v = v / Slider1.Max * pMP.Duration
pMP.CurrentPosition = v
Set pMP = Nothing

End Sub

Private Sub StopButton_Click()
    If Not bOpen Then Exit Sub
    pMC.Stop
    bPlay = False
    OpenButton.Enabled = True
    PlayButton.Enabled = True

End Sub

Private Sub Timer1_Timer()

    If Not bOpen Then Exit Sub

    If bSeeking Then Exit Sub

    Dim pMP As IMediaPosition
    Set pMP = pMC

    Dim curpos As Double
    Dim length As Double
    Dim mark As Long

    curpos = pMP.CurrentPosition
    length = pMP.Duration
    mark = (curpos / length) * 10

    If Slider1.Value <> mark Then
        Slider1.Value = mark
    End If

    Set pMP = Nothing

    If Not bPlay Then Exit Sub

    Dim pME As IMediaEvent
    Dim EventCode As Long
    Set pME = pMC

    On Error Resume Next
    pME.WaitForCompletion 0, EventCode

    Set pME = Nothing

    If EventCode = 0 Then Exit Sub

    pMC.Stop
    bPlay = False
    OpenButton.Enabled = True
    PlayButton.Enabled = True

End Sub


VBDEMO.FRM

VERSION 5.00
Object = "{6B7E6392-850A-101B-AFC0-4210102A8DA7}#1.1#0"; "COMCTL32.OCX"
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Begin VB.Form frmMain
   Caption         =   "ActiveMovie VB Sample"
   ClientHeight    =   6465
   ClientLeft      =   1830
   ClientTop       =   2880
   ClientWidth     =   5850
   ClipControls    =   0   'False
   LinkTopic       =   "frmMain"
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   6465
   ScaleWidth      =   5850
   Begin VB.Frame frameBalance
      Caption         =   "Balance"
      Height          =   1215
      Left            =   2880
      TabIndex        =   13
      Top             =   1560
      Width           =   2295
      Begin ComctlLib.Slider slBalance
         Height          =   504
         Left            =   120
         TabIndex        =   14
         Top             =   240
         Width           =   2052
         _ExtentX        =   3625
         _ExtentY        =   900
         _Version        =   327680
         Min             =   -10000
         Max             =   10000
         TickStyle       =   2
         TickFrequency   =   2000
      End
      Begin VB.Label lblRight
         Caption         =   "Right"
         Height          =   255
         Left            =   1680
         TabIndex        =   16
         Top             =   840
         Width           =   495
      End
      Begin VB.Label lblLeft
         Caption         =   "Left"
         Height          =   255
         Left            =   120
         TabIndex        =   15
         Top             =   840
         Width           =   495
      End
   End
   Begin VB.Frame frameVideoControls
      Caption         =   "Video"
      Height          =   1335
      Left            =   240
      TabIndex        =   10
      Top             =   0
      Width           =   2295
      Begin ComctlLib.Toolbar Toolbar1
         Height          =   600
         Left            =   240
         TabIndex        =   12
         Top             =   360
         Width           =   1935
         _ExtentX        =   3413
         _ExtentY        =   1058
         ButtonWidth     =   979
         ButtonHeight    =   953
         ImageList       =   "ImageList1"
         _Version        =   327680
         BeginProperty Buttons {0713E452-850A-101B-AFC0-4210102A8DA7}
            NumButtons      =   5
            BeginProperty Button1 {0713F354-850A-101B-AFC0-4210102A8DA7}
               Caption         =   "Play"
               Object.Tag             =   ""
               ImageIndex      =   1
            EndProperty
            BeginProperty Button2 {0713F354-850A-101B-AFC0-4210102A8DA7}
               Object.Tag             =   ""
               Style           =   3
            EndProperty
            BeginProperty Button3 {0713F354-850A-101B-AFC0-4210102A8DA7}
               Caption         =   "Pause"
               Object.Tag             =   ""
               ImageIndex      =   2
            EndProperty
            BeginProperty Button4 {0713F354-850A-101B-AFC0-4210102A8DA7}
               Object.Tag             =   ""
               Style           =   3
            EndProperty
            BeginProperty Button5 {0713F354-850A-101B-AFC0-4210102A8DA7}
               Caption         =   "Stop"
               Object.Tag             =   ""
               ImageIndex      =   3
            EndProperty
         EndProperty
      End
   End
   Begin VB.TextBox txtRate
      BackColor       =   &H00000000&
      BeginProperty Font
         Name            =   "Terminal"
         Size            =   9
         Charset         =   255
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H00FFFFFF&
      Height          =   270
      Left            =   4440
      TabIndex        =   7
      Top             =   1200
      Width           =   855
   End
   Begin VB.TextBox txtStart
      BackColor       =   &H00000000&
      BeginProperty Font
         Name            =   "Terminal"
         Size            =   9
         Charset         =   255
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H00FFFFFF&
      Height          =   270
      Left            =   4440
      TabIndex        =   6
      Top             =   840
      Width           =   855
   End
   Begin VB.TextBox txtElapsed
      Alignment       =   2  'Center
      BackColor       =   &H00000000&
      BeginProperty Font
         Name            =   "Terminal"
         Size            =   9
         Charset         =   255
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H00FFFFFF&
      Height          =   270
      Left            =   4440
      TabIndex        =   4
      Top             =   480
      Width           =   855
   End
   Begin VB.Timer Timer1
      Left            =   5160
      Top             =   4680
   End
   Begin VB.TextBox txtDuration
      Alignment       =   2  'Center
      BackColor       =   &H00000000&
      BeginProperty Font
         Name            =   "Terminal"
         Size            =   9
         Charset         =   255
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H00FFFFFF&
      Height          =   270
      Left            =   4440
      TabIndex        =   3
      Top             =   120
      Width           =   855
   End
   Begin VB.Frame frameVolume
      Caption         =   "Volume"
      Height          =   1215
      Left            =   360
      TabIndex        =   0
      Top             =   1560
      Width           =   2295
      Begin ComctlLib.Slider slVolume
         Height          =   504
         Left            =   120
         TabIndex        =   11
         Top             =   240
         Width           =   2052
         _ExtentX        =   3625
         _ExtentY        =   900
         _Version        =   327680
         Min             =   -10000
         Max             =   0
         TickStyle       =   2
         TickFrequency   =   1000
      End
      Begin VB.Label lblMax
         Caption         =   "Max"
         Height          =   255
         Left            =   1800
         TabIndex        =   2
         Top             =   840
         Width           =   375
      End
      Begin VB.Label lblMin
         Caption         =   "Min"
         Height          =   255
         Left            =   120
         TabIndex        =   1
         Top             =   840
         Width           =   495
      End
   End
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   4920
      Top             =   3120
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
   End
   Begin VB.Label lblDuration
      Caption         =   "Length (Sec):"
      Height          =   255
      Left            =   2760
      TabIndex        =   17
      Top             =   120
      Width           =   1455
   End
   Begin ComctlLib.ImageList ImageList1
      Left            =   5040
      Top             =   3840
      _ExtentX        =   1005
      _ExtentY        =   1005
      BackColor       =   -2147483643
      ImageWidth      =   16
      ImageHeight     =   16
      MaskColor       =   12632256
      _Version        =   327680
      BeginProperty Images {0713E8C2-850A-101B-AFC0-4210102A8DA7}
         NumListImages   =   3
         BeginProperty ListImage1 {0713E8C3-850A-101B-AFC0-4210102A8DA7}
            Picture         =   "vbdemo.frx":0000
            Key             =   ""
         EndProperty
         BeginProperty ListImage2 {0713E8C3-850A-101B-AFC0-4210102A8DA7}
            Picture         =   "vbdemo.frx":0112
            Key             =   ""
         EndProperty
         BeginProperty ListImage3 {0713E8C3-850A-101B-AFC0-4210102A8DA7}
            Picture         =   "vbdemo.frx":0224
            Key             =   ""
         EndProperty
      EndProperty
   End
   Begin VB.Label lblRate
      Caption         =   "Playback speed:"
      Height          =   255
      Left            =   2760
      TabIndex        =   9
      Top             =   1200
      Width           =   1335
   End
   Begin VB.Label lblStart
      Caption         =   "Start position (Sec):"
      Height          =   255
      Left            =   2760
      TabIndex        =   8
      Top             =   840
      Width           =   1455
   End
   Begin VB.Label lblElapsed
      Caption         =   "Elapsed Time (Sec):"
      Height          =   252
      Left            =   2760
      TabIndex        =   5
      Top             =   480
      Width           =   1572
   End
   Begin VB.Shape Shape1
      BackStyle       =   1  'Opaque
      BorderColor     =   &H00808080&
      BorderWidth     =   4
      Height          =   3135
      Left            =   240
      Top             =   3000
      Width           =   4095
   End
   Begin VB.Menu mnu_File
      Caption         =   "&File"
      Begin VB.Menu mnu_FileOpen
         Caption         =   "&Open"
      End
      Begin VB.Menu mnu_FileExit
         Caption         =   "E&xit"
      End
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' ActiveMovie SDK - VB4 interface sample
' Copyright 1996 - 1997 Microsoft Corporation
' This sample demonstrates Visual Basic 4.0 use of the
' ActiveMovie interfaces that are in FilgraphManager.

' This sample defines the following variable prefixes:
'  g_dbl - global double;
'  g_f   - global flag (Boolean);
'  g_obj - object;
'  g_str - string

Dim g_objVideoWindow As IVideoWindow       'VideoWindow Object
Dim g_objMediaControl As IMediaControl     'MediaControl Object
Dim g_objMediaPosition As IMediaPosition   'MediaPosition Object
Dim g_objBasicAudio  As IBasicAudio        'Basic Audio Object
Dim g_objBasicVideo As IBasicVideo         'Basic Video Object

Dim g_strFileName As String      'Name retrieved with FileOpenDlg
Dim g_dblRunLength As Double     'Global duration in seconds
Dim g_dblStartPosition As Double 'Global start position in seconds
Dim g_dblRate As Double          'Global rate
Dim g_fVideoRun As Boolean       'Flag used to trigger clock

Private Sub Form_Load()
    'Alter the coordinate system so that we work
    'in pixels (instead of the default twips)

    frmMain.ScaleMode = 3   ' pixels

    'Set the granularity for the timer control
    'so that we can display the duration for
    'given video sequence.

    Timer1.Interval = 1000 '1 second intervals

End Sub

Private Sub Form_Unload(Cancel As Integer)
    mnu_FileExit_Click
End Sub

Private Sub mnu_FileExit_Click()
    cleanup
    End
End Sub

Private Sub mnu_FileOpen_Click()
' Use the common file dialog to select an ActiveMovie file
' (has the extension .AVI or .MPG.)
' Initialize global variables based on the
' contents of the file:
'   g_strFileName - name of file name selected by the user
'   g_dblRunLength = length of the file; duration
'   g_dblStartPosition - point at which to start playing clip
'   g_objMediaControl, g_objMediaEvent, g_objMediaPosition,
'   g_objBasicAudio, g_objVideoWindow - programmable objects

    'clean up memory (in case a file is already open)
    cleanup

    'Retrieve the name of an .avi or an .mpg
    'file that the user wishes to view.
    CommonDialog1.Filter = "ActiveMovie files (*.mpg;*.avi;*.mov)|*.mpg;*.avi;*.mov"
    CommonDialog1.ShowOpen
    g_strFileName = CommonDialog1.filename

    'Verify that correct filename was selected

    If (InStr(1, g_strFileName, ".avi", 1)) Or (InStr(1, g_strFileName, ".mpg", 1) Or (InStr(1, g_strFileName, ".mov", 1))) Then
    Else
        MsgBox "Invalid Filename Selected!", , "ActiveMovie sample"
        Exit Sub
    End If

    'Instantiate a filter graph for the requested
    'file format.

    Set g_objMediaControl = New FilgraphManager
    If g_objMediaControl Is Nothing Then
        MsgBox "Cannot create the IMediaControl object"
        Exit Sub
    End If

    g_objMediaControl.RenderFile (g_strFileName)

    'Setup the IBasicAudio object (this
    'is equivalent to calling QueryInterface()
    'on IFilterGraphManager). Initialize the volume
    'to the maximum value.

    ' Some filter graphs don't render audio
    ' In this sample, skip setting volume property
    Set g_objBasicAudio = g_objMediaControl
    On Error Resume Next
    g_objBasicAudio.Volume = slVolume.Value

    'Setup the IVideoWindow object. Remove the
    'caption, border, dialog frame, and scrollbars
    'from the default window. Position the window.
    'Set the parent to the app's form.

    Set g_objVideoWindow = g_objMediaControl
    g_objVideoWindow.WindowStyle = CLng(&H6000000)
    g_objVideoWindow.Left = CLng(Shape1.Left)
    g_objVideoWindow.Top = CLng(Shape1.Top)
    Shape1.Width = g_objVideoWindow.Width
    Shape1.Height = g_objVideoWindow.Height
    g_objVideoWindow.Owner = frmMain.hWnd

    'Setup the IMediaEvent object for the
    'sample toolbar (run, pause, play).

    Set g_objMediaEvent = g_objMediaControl

    'Setup the IMediaPosition object so that we
    'can display the duration of the selected
    'video as well as the elapsed time.

    Set g_objMediaPosition = g_objMediaControl
    g_dblRunLength = g_objMediaPosition.Duration
    txtDuration.Text = CStr(g_dblRunLength)

    ' reset start position to 0
    g_dblStartPosition = 0#
    txtStart.Text = CDbl(g_dblStartPosition)
    ' use user-established playback rate
    g_dblRate = g_objMediaPosition.Rate
    txtRate.Text = CStr(g_dblRate)

End Sub

Private Sub slVolume_Change()

    'Set the volume on the slider
    If Not g_objMediaControl Is Nothing Then
        g_objBasicAudio.Volume = slVolume.Value
    End If

End Sub

Private Sub slBalance_Change()

    'Set the balance using the slider
    If Not g_objMediaControl Is Nothing Then
        g_objBasicAudio.Balance = (slBalance.Value * -1)
    End If

End Sub

Private Sub txtStart_KeyDown(KeyCode As Integer, Shift As Integer)

    ' handle user input to change the start position
    If KeyCode = vbKeyReturn Then
        If g_objMediaPosition Is Nothing Then
            Exit Sub
        ElseIf CDbl(txtStart.Text) > g_dblRunLength Then
            MsgBox "Specified position invalid: re-enter new position."
        ElseIf CDbl(txtStart.Text) < 0 Then
            MsgBox "Specified position invalid: re-enter new position."
        ElseIf CStr(txtStart.Text) <> "" Then
            g_dblStartPosition = CDbl(txtStart.Text)
            g_objMediaPosition.CurrentPosition = g_dblStartPosition
        End If
    End If

End Sub

Private Sub txtRate_KeyDown(KeyCode As Integer, Shift As Integer)

    ' ActiveMovie VB sample
    ' handle user updates to the Rate value
    If KeyCode = vbKeyReturn Then
        If g_objMediaPosition Is Nothing Then
            Exit Sub
        ElseIf CDbl(txtRate.Text) < 0# Then
            MsgBox "Negative values invalid: re-enter value between 0 and 2.0"
        ElseIf CStr(txtRate.Text) <> "" Then
            g_dblRate = CDbl(txtRate.Text)
            g_objMediaPosition.Rate = g_dblRate
        End If
    End If

End Sub

Private Sub Timer1_Timer()
    'Retrieve the Elapsed Time and
    'display it in the corresponding
    'textbox.
    Dim Dbl As Double

    If g_fVideoRun = True Then
        Dbl = g_objMediaPosition.CurrentPosition
        If Dbl < g_dblRunLength Then
            txtElapsed.Text = CStr(Dbl)
        Else
            txtElapsed.Text = CStr(g_dblRunLength)
        End If
    End If

End Sub

Private Sub Toolbar1_ButtonClick(ByVal Button As Button)
' handle buttons on the toolbar
' buttons 1, 3 and 5 are defined; 2 and 4 are separators
' all ActiveMovie objects are defined only if the user
' has already selected a filename and initialized the objects

    ' if the objects aren't defined, avoid errors
    If g_objMediaControl Is Nothing Then
        Exit Sub
    End If

    If Button.Index = 1 Then 'PLAY
        'Invoke the MediaControl Run() method
        'and play the video through the predefined
        'filter graph.

        ' Use specified starting position
        If g_objMediaPosition.CurrentPosition < g_dblStartPosition Then
            g_objMediaPosition.CurrentPosition = g_dblStartPosition
        End If

        g_objMediaControl.Run
        g_fVideoRun = True

    ElseIf Button.Index = 3 Then  'PAUSE
        'Invoke the MediaControl Pause() method
        'and pause the video that is being
        'displayed through the predefined
        'filter graph.

        g_objMediaControl.Pause
        g_fVideoRun = False

    ElseIf Button.Index = 5 Then  'STOP

        'Invoke the MediaControl Stop() method
        'and stop the video that is being
        'displayed through the predefined
        'filter graph.

        g_objMediaControl.Stop
        g_fVideoRun = False
        ' reset to the beginning of the video
        g_objMediaPosition.CurrentPosition = 0
        txtElapsed.Text = "0.0"

    End If
End Sub

Private Sub cleanup()

    ' clean up memory
    If Not g_objMediaControl Is Nothing Then
        g_objMediaControl.Stop
        Debug.Print "Setting owner to NULL" + Chr(13)
        g_objVideoWindow.Owner = 0          'sets the Owner to NULL
    End If
    Set g_objVideoWindow = Nothing
    Set g_objMediaControl = Nothing
    Set g_objMediaPosition = Nothing
    Set g_objBasicAudio = Nothing
    Set g_objBasicVideo = Nothing

End Sub


CDEVCOM.CPP

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//   filename: cdevcom.c
//
//     External Device Communication Functions
//
//---------------------------------------------------------
//
//NOTES:
// 1. Code is un-optimized for clarity
// 2. Minimal error checking.
// 3. Protocol is tested with SVO-5800 (RS-422) and SVBK-10 (RS-232) machines
// 4. Timecode reading is supported, but dealing with multiple
//calling threads is nasty, so be sure you know what you're
//doing before you change anything.
//
//
//---------------------------------------------------------

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <streams.h>
#include <mmsystem.h>

#include "ctimecod.h"
#include "cdevcom.h"

//----------------------------------------------------------
// structures

// SVO-5800/SVBK-1 Command Structure
typedef struct tagSV_CMD{
BYTE bCmd1;// Command byte 1 - lo nybble has data byte
//  count
BYTE bCmd2;// Command byte 2
BYTE bCmd3;// Data if required by command, or checksum if none
BYTE bCmd4;// Checksum if 1 data byte
BYTE bByteCnt;// How many bytes in the command (including checksum)
int iRespCnt;// How many bytes in the response
} SV_CMD;

// SVO Commands - this is the command table.
// ***IT MUST BE IN THE SAME ORDER AS THE COMMAND LIST IN CDEVCOM.H!!!!!
static SV_CMD SvoCmdTbl[] = {
{0x20,1,0x21,0,3,3},// Play
{0x20,0,0x20,0,3,3},// Stop
{0x61,0x0c,0x11,0x7e,4,11},// Request LTC
{0x41,0x36,1,0x78,4,3},// Timer Mode Select: LTC
{0x20,0x20,0x40,0,3,3},   // Rewind
{0x20,0x6b,0x8b,0,3,3},// Freeze on
{0x20,0x6a,0x8a,0,3,3},// Freeze off
{0x20,0x04,0x24,0,3,3},// Standby off
{0x20,0x05,0x25,0,3,3},// Standby on
{0x00,0x11,0x11,0,3,5},// Device Type Request
{0x61,0x20,0x08,0x89,4,11},// Status Request - 8 bytes
{0x20,0x0f,0x2f,0,3,3},// Eject
{0x00,0x0c,0x0c,0,3,3},// Local On
{0x00,0x1d,0x1d,0,3,3},// Local Off
{0x20,0x10,0x30,0,3,3},// Fast Forward
{0x20,0x02,0x22,0,3,3},// Record
{0xFF,0,0,0,0,0}// Device Clear
};

// device type request table
typedef struct tagSVO_DEVTYPE{
BYTE bData1;// 1st data byte of response
BYTE bData2;// 2nd data byte of response
} SVO_DEVTYPE;

static SVO_DEVTYPE SvoDevTbl[] = {
{0x80, 0x0c},
{0x81, 0x0c},
{0x80, 0x08},
{0x80, 0x08},
{0x81, 0x08},
{0x10, 0x2c},
{0x11, 0x2c},
{0x10, 0x28},
{0x11, 0x28},
{0x10, 0x20},
{0x11, 0x20},
{0x10, 0x21},
{0x11, 0x21},
{0x20, 0x51}
};

const int DevTypeCnt = sizeof(SvoDevTbl);

const TCHAR *strSvoDevID[] = {
"EVO-9850",
"EVO-9850P",
"EVO-9800",
"EVO-9800A",
"EVO-9800P",
"SVO-9600",
"SVO-9620",
"SVP-9000",
"SVP-9020",
"SVO-5800",
"SVO-5800P",
"SVO-5600",
"SVO-5600P",
"UVW-1800",
};
const TCHAR *strUnknownDevID =
{"Unknown Device"};

// SVBK Command Table
// ***IT MUST BE IN THE SAME ORDER AS THE COMMAND LIST IN CDEVCOM.H!!!!!
static SV_CMD SvbkCmdTbl[] = {
{0x3A,0,0,0,1,1},// Play
{0x3F,0,0,0,1,1},// Stop
{0xA0,0,0,0,1,9},// Request LTC
{0xFF,0,0,0,0,0},// Timer Mode Select: LTC
{0xAC,0,0,0,1,1},   // Rewind
{0x4F,0,0,0,1,1},// Freeze on
{0x3A,0,0,0,1,1},// Freeze off
{0xFF,0,0,0,0,0},// Standby off
{0xFF,0,0,0,0,0},// Standby on
{0x8F,0,0,0,1,1},// Device Type Request
{0xD7,0,0,0,1,5},// Status Request - 5 bytes
{0x2A,0,0,0,1,2},// Eject
{0xFF,0,0,0,0,0},// Local On
{0xFF,0,0,0,0,0},// Local Off
{0xAB,0,0,0,1,1},// Fast Forward
{0xFA,0xFA,0xCA,0,3,1},// Record
{0x56,0,0,0,1,1}// Device Clear
};

const TCHAR *strSvbkDevID =
{"SVBK-1"};
const int SvbkDevType = 0x80;

// which machine's command table are we using?
SV_CMD *pVcrCmdTable = SvbkCmdTbl;
int VcrType;// 0 for SVO, 1 for SVBK
static TCHAR SvbkBuf[16];// for multi-byte commands

// for simulation mode
static BYTE SvoAck[] = {
0x10,0x01,0x11};
static DWORD SvoAckCount = 3L;
static BYTE SvbkAck[] = {
0x0A};
static DWORD SvbkAckCount = 1L;
static TCHAR strDevID[] = "Pretend Machine";

static DWORD SimulatedFramecount = 0L;
static long lNDFTable[] = {1080000L,108000L,18000L,1800L,300L,30L,10L,1L};

// COM port stuff
#define COMM_TIMEOUT_MS = 60;

static TCHAR DevPortCom1[] = "COM1";
static TCHAR DevPortCom2[] = "COM2";
static TCHAR DevPortCom3[] = "COM3";
static TCHAR DevPortCom4[] = "COM4";
static TCHAR buf[20];

// init static members
HANDLE CDevCom::m_hDevPort = 0;
TCHAR * CDevCom::m_pcDevPort = NULL;
BYTE CDevCom::m_bLastCmd = 0;
BYTE CDevCom::m_LastMotionCmd = 0;
int CDevCom::m_CurCmdCookie = 1;
int CDevCom::m_LastSentCmdCookie = -1;
DCB CDevCom::m_dcb = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0};

int CDevCom::m_PendingResponseCnt = 0;
BOOL CDevCom::m_bSimulateHardware = FALSE;
COMMTIMEOUTS CDevCom::m_ctmoTimeout = {
0,0,0,0,0};
COMSTAT CDevCom::m_csCommStat = {
0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,};
// Command Queue stuff to deal with multi-threading
#define COMMANDLISTSIZE 10
#define RESPONSE_SLEEP_TIME 5// in milliseconds
#define RESPONSE_RETRY_MAX20// should never have to wait for more than this
// numer of sleep times
// Use Generic list template class to build command queue. Use of
// such a queue will allow the communications class to be thread safe.
static CGenericList<COMMANDOBJECT> CommandList("List of Pending Commands");

//intialize an empty array of command objects
COMMANDOBJECT CommandObjects[] = {
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff},
{-1,-1, 0xffff}
};

// initialize an empty array of response objects - we don't create a linked
// list of these because we don't expect to every have more that two or three
// buffered responses
RESPONSEOBJECT ResponseObject[] = {
{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
#define MAX_RESPONSE_OBJECTS 3

//---------------------------------------------------------
//
// CDevCom constructor
//
//---------------------------------------------------------
CDevCom::CDevCom(CBaseFilter *pFilter, HRESULT *phr)
{
m_pFilter = pFilter;
// need some help with timecode
m_pTimecode = new CTimecode();
if (m_pTimecode == NULL) {
        *phr = E_OUTOFMEMORY;
       return;
}
*phr = S_OK;
return;

}
//---------------------------------------------------------
//
// Destructor
//
//---------------------------------------------------------
CDevCom::~CDevCom()
{

}

//---------------------------------------------------------
//
//OpenDevPort
//
//Tries to open the requested port.  If port is not available,
//enters "simulation" mode
//
//---------------------------------------------------------
DWORD
CDevCom::OpenDevPort( int Port )
{
TCHAR resp[20];
DWORD LastError;
DWORD dwCommError;
int Cookie;

timeBeginPeriod(1);// setup 1ms timer for timestamping
m_bSimulateHardware = FALSE;// this may have been set TRUE previously
// get the port
switch (Port){
case DEV_PORT_COM1:
m_pcDevPort = DevPortCom1;
break;
case DEV_PORT_COM2:
m_pcDevPort = DevPortCom2;
break;
case DEV_PORT_COM3:
m_pcDevPort = DevPortCom3;
break;
case DEV_PORT_COM4:
m_pcDevPort = DevPortCom4;
break;
case DEV_PORT_SIM:
case DEV_PORT_DIAQ:
case DEV_PORT_ARTI:
default:
if (m_hDevPort)
CloseDevPort();
m_bSimulateHardware = TRUE;
return 0L;
}
// open the port
if ( (m_hDevPort = CreateFile( m_pcDevPort,GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
    NULL )) == (HANDLE) -1 ){
// we're in simulation mode
DWORD dwTemp = GetLastError();
m_bSimulateHardware = TRUE;
return 1L;
}

// get the Device Control Block
if ( !GetCommState( m_hDevPort, &m_dcb ) ) {
CloseDevPort();
m_bSimulateHardware = TRUE;
   return 1L;
}

// put in our values
m_dcb.BaudRate = 38400;
    m_dcb.fDtrControl = DTR_CONTROL_ENABLE;
    m_dcb.fDsrSensitivity = FALSE;
    m_dcb.fOutX = FALSE;
    m_dcb.fInX = FALSE;
    m_dcb.fNull = FALSE;
    m_dcb.fRtsControl = RTS_CONTROL_ENABLE;
    m_dcb.fAbortOnError = TRUE;
  m_dcb.ByteSize = 8;
    m_dcb.Parity = ODDPARITY;
    m_dcb.StopBits = ONESTOPBIT;

if ( !SetCommState(m_hDevPort, &m_dcb) ){
// failure forces simulation mode
m_bSimulateHardware = TRUE;
return 1L;
}

// set the comm timeout
if ( !GetCommTimeouts(m_hDevPort, &m_ctmoTimeout) ){
CloseDevPort();
m_bSimulateHardware = TRUE;
   return 1L;
}
// timeout after COMM_TIMEOUT_MS milliseconds
m_ctmoTimeout.ReadIntervalTimeout = 15L;
m_ctmoTimeout.ReadTotalTimeoutMultiplier = 10L;
m_ctmoTimeout.ReadTotalTimeoutConstant = 60L;
m_ctmoTimeout.WriteTotalTimeoutMultiplier = 1L;
m_ctmoTimeout.WriteTotalTimeoutConstant = 60L;
if ( !SetCommTimeouts(m_hDevPort, &m_ctmoTimeout) ){
CloseDevPort();
m_bSimulateHardware = TRUE;
   return 1L;
}

// now let's see if there is some hardware there
// try an SVO type machine first
pVcrCmdTable = SvoCmdTbl;
VcrType = 0;
if (SendDevCmd(DEVICE_TYPE_REQUEST, &Cookie) != 0L){
GetDevResponse(resp, Cookie);//cleanup
CloseDevPort();
m_bSimulateHardware = TRUE;
m_PendingResponseCnt = 0;
   return 1L;
}

if ( GetDevResponse(resp, Cookie) != 0L){
// try the SVBK now
pVcrCmdTable = SvbkCmdTbl;
VcrType = 1;
FlushDevResponseBuffer();// clean out the UART
ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
// need to change the baud rate among other things
m_dcb.BaudRate = 9600;
m_dcb.Parity = NOPARITY;
    if ( !SetCommState(m_hDevPort, &m_dcb) ){
// set simulation mode
LastError = GetLastError();
m_bSimulateHardware = TRUE;
return 1L;
}
Sleep(30L);// give the machine time to recover
SendDevCmd(DEVICE_CLEAR, &Cookie);
GetDevResponse(resp, Cookie);
// DEVICE_CLEAR puts the machine in pause, so put it in stop
SendDevCmd(STOP, &Cookie);
GetDevResponse(resp, Cookie);
Sleep(30L);// give the machine time to recover
FlushDevResponseBuffer();// clean out the UART
ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
if (SendDevCmd(DEVICE_TYPE_REQUEST, &Cookie) != 0L){
GetDevResponse(resp, Cookie);//cleanup
CloseDevPort();
m_bSimulateHardware = TRUE;
m_PendingResponseCnt = 0;
   return 1L;
}
// we don't actually check the response value here because
// it is not always consistant.
if ( GetDevResponse(resp, Cookie) != 0L){
CloseDevPort();
m_bSimulateHardware = TRUE;
return 1L;
}
// cleanup
ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
FlushDevResponseBuffer();// clean out the UART
return 0L;
}
 return 0L;
}

//---------------------------------------------------------
//
//CloseDevPort
//
//Shuts things down
//
//---------------------------------------------------------
DWORD
CDevCom::CloseDevPort( void )
{
DWORD LastError;

timeEndPeriod(1);// restore timer period

if ( m_hDevPort && !m_bSimulateHardware )
{// close the port
if (DeleteFile(m_pcDevPort)) {
LastError = GetLastError();
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
m_pcDevPort = NULL;
if ( !CloseHandle( m_hDevPort ) ) {
LastError = GetLastError();
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
m_hDevPort = 0;
return 0L;
}
m_bSimulateHardware = FALSE;
return 0L;
}

//---------------------------------------------------------
//
//FlushDevResponseBuffer
//
//A cleanup method
//
//---------------------------------------------------------
DWORD
CDevCom::FlushDevResponseBuffer( void )
{
DWORD LastError;
DWORD dwRetByteCnt;
DWORD dwCommError;
char buf[16];

if (m_bSimulateHardware)
return 0L;
m_PendingResponseCnt = 0;
if (PurgeComm(m_hDevPort, PURGE_RXCLEAR)) {
LastError = GetLastError();
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
if (m_csCommStat.cbInQue) {
if ( !ReadFile( m_hDevPort, buf, (DWORD)m_csCommStat.cbInQue,
 (LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
LastError = GetLastError();
// could stand a bit better error handling here
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
}

return 0L;
}

//---------------------------------------------------------
//
//SendDevCmd
//
//Sends command to device (or queues it up for xmission).
//Returned a Cookie so the caller can find the
//correct response
//
//---------------------------------------------------------
DWORD
CDevCom::SendDevCmd( int Cmd, int *pCookie)
{
CAutoLock lock(this);
PCOMMANDOBJECT pCmdObj;
DWORD dwret;

// return immediately if command is not supported
if (pVcrCmdTable[Cmd].bCmd1 == 0xFF)
return DEV_COMM_ERR_COMMAND_NOT_SUPPORTED;

// get a new cookie
if (m_CurCmdCookie++ == 0)
m_CurCmdCookie++;
// queue the command if the response has not yet been
// pulled for the previously sent command
if (m_PendingResponseCnt){
// some extra instructions here for debugging
pCmdObj = AddCommand(Cmd, m_CurCmdCookie);
if (pCmdObj == NULL){
ASSERT(pCmdObj);
return DEV_COMM_ERR_QUEUE_OVERFLOW;
}
*pCookie = m_CurCmdCookie;
return 0L;
}
dwret = ReallySendDevCmd( Cmd, m_CurCmdCookie);
*pCookie = m_CurCmdCookie;
return dwret;
}

//---------------------------------------------------------
//
//ReallySendDevCmd
//
//This does the actual sending of queued commands
//
//---------------------------------------------------------
DWORD
CDevCom::ReallySendDevCmd( int Cmd, int Cookie )
{
SV_CMD *svCommand;
BYTE bCmdSequenceCnt;
DWORD dwBytesWritten;
DWORD LastError;
DWORD dwret = 0L;
TCHAR *pCmd;
int i;

m_bLastCmd = Cmd;
if ( m_isMotionCmd(Cmd) )// do we still need to know this?
m_LastMotionCmd = Cmd;

if (!m_bSimulateHardware) {
// get the command from the table and shoot it out
svCommand = &pVcrCmdTable[Cmd];
// we handle the two machine types differently because the SVBK
// machine might have multiple command/response pair sequences
if (VcrType == 0) {// SVO has one sequence and can send multiple bytes
i = (int)svCommand->bByteCnt;
bCmdSequenceCnt = 1;
}
else {// SVBK only sends out one byte per sequence
i = 1;
bCmdSequenceCnt = svCommand->bByteCnt;
}
pCmd = (TCHAR *)svCommand;
while (bCmdSequenceCnt) {// do this loop for each sequence
// send out the command
if ( !WriteFile( m_hDevPort, (TCHAR *)pCmd, i,
(LPDWORD)&dwBytesWritten, NULL ) ) {
LastError = GetLastError();
dwret = DEV_COMM_ERR_COMMUNICATION_ERROR;
}
if (VcrType == 1 && bCmdSequenceCnt > 1) {
// must pick up response byte if another sequence is pending
m_PendingResponseCnt++;
ReallyGetDevResponse(SvbkBuf,1);
// point to the next command byte in the sequence
pCmd++;
// special handling for SVBK's Eject command
if (svCommand->bCmd1 == 0x2a)
Sleep(1000);// wait while EJECT completes
}
bCmdSequenceCnt--;
}
}
m_LastSentCmdCookie = Cookie;
m_PendingResponseCnt++;
return dwret;
}

//---------------------------------------------------------
//
//GetDevResponse
//
//Returns device response to command.  If command
//hasn't really been sent yet (still in queue), error
//is returned in the DWORD
//
//---------------------------------------------------------
DWORD
CDevCom::GetDevResponse( TCHAR *buf, int Cookie )
{
CAutoLock lock(this);

DWORD dwret = 0L;
int temp;
PCOMMANDOBJECT ptemp;
POSITION posNextCommand;
BOOL bNeedCommand = FALSE;

// is this command's response buffered?
if ( IsResponseBuffered(Cookie, &temp) ) {
GetBufferedResponse( buf, Cookie );
// don't send another command out since
// a response wasn't cleared out of the COMM port
return dwret;
}

// was this the last command sent?
if (Cookie == m_LastSentCmdCookie) {
// get the response
dwret = ReallyGetDevResponse( buf, pVcrCmdTable[m_bLastCmd].iRespCnt );
if (dwret)
return dwret;
}
else {
// since it wasn't, is a response pending?
if (m_PendingResponseCnt) {
dwret = BufferResponse();
if (dwret)
return dwret;
}
bNeedCommand = TRUE;
}

// since the command hasn't been sent it must be in the queue,
// so send something
temp = CommandList.GetCount();
if (temp) {
posNextCommand = CommandList.GetHeadPosition();
ptemp = CommandList.Get(posNextCommand);
dwret = ReallySendDevCmd( ptemp->Command, ptemp->Cookie );
ptemp = CommandList.RemoveHead();
RemoveCommand(ptemp);
return DEV_COMM_ERR_COMMAND_NOT_SENT_YET;
}
else
if (bNeedCommand)
return DEV_COMM_ERR_COMMAND_MISSING;

return dwret;
}
//---------------------------------------------------------
//
//ReallyGetDevResponse
//
//Actually handles communications to get device response
//
//---------------------------------------------------------
DWORD
CDevCom::ReallyGetDevResponse( TCHAR *buf, int bytecnt )
{
DWORD dwRetByteCnt;
DWORD LastError;

m_PendingResponseCnt--;
if (m_bSimulateHardware) {
(GetSimulatedResponse(buf));
}
else {
if ( !ReadFile( m_hDevPort, buf, (DWORD)bytecnt,
 (LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
LastError = GetLastError();
// could stand a bit better error handling here
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
if ( dwRetByteCnt != (DWORD)bytecnt ) {
return DEV_COMM_ERR_RESPONSE_MISMATCH;
}
// SVBK doesn't use checksums and its timecode reading
// is a bit unpredictable
if (VcrType == 0) {
if ( TestResponse(buf, dwRetByteCnt) )
return DEV_COMM_ERR_RESPONSE_CHECKSUM;
}
else {
// sometimes an extra wierd character is sent
// during timecode reading, so we need to watch for it
if (bytecnt == pVcrCmdTable[READ_TC].iRespCnt) {
// this was a timecode response, so check for multiple
// 0xA1's at the beginning of the buffer.  If we have
// more than one, we need to get another byte out of
// the receive buffer
if (buf[1] == (TCHAR)0xA1) {
if ( !ReadFile( m_hDevPort,
&buf[pVcrCmdTable[READ_TC].iRespCnt],
1L,(LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
LastError = GetLastError();
// could stand a bit better error handling here
return DEV_COMM_ERR_COMMUNICATION_ERROR;
}
if ( dwRetByteCnt != 1L )
return DEV_COMM_ERR_RESPONSE_MISMATCH;
}
}
}
}
return S_OK;
}
//---------------------------------------------------------
//
//BufferResponse
//
//Pulls a response out of the COMM device for the most
//recently send command
//
//---------------------------------------------------------
DWORD
CDevCom::BufferResponse(void)
{
int i;
// get an available slot in the array
for (i=0; i<MAX_RESPONSE_OBJECTS; i++) {
if (ResponseObject[i].Cookie == -1)
break;
}
if (i>=MAX_RESPONSE_OBJECTS)
return DEV_COMM_ERR_RESPONSE_OVERFLOW;
// put in the cookie and response byte count
ResponseObject[i].Cookie = m_LastSentCmdCookie;
ResponseObject[i].ResponseByteCount = pVcrCmdTable[m_bLastCmd].iRespCnt;
// get the response
return ReallyGetDevResponse( ResponseObject[i].buf,
ResponseObject[i].ResponseByteCount );
}
//---------------------------------------------------------
//
//GetBufferedResponse
//
//Pulls a response out of the response array
//
//---------------------------------------------------------
DWORD
CDevCom::GetBufferedResponse( TCHAR *buf, int Cookie)
{
int i;

if ( !IsResponseBuffered( Cookie, &i ) )
return DEV_COMM_ERR_RESPONSE_MISSING;

// copy the response into the return buffer
memcpy( buf, ResponseObject[i].buf, ResponseObject[i].ResponseByteCount);

// Clear the entry
ResponseObject[i].Cookie = -1;
ResponseObject[i].ResponseByteCount = -1;
// get the response
return S_OK;
}

//---------------------------------------------------------
//
//IsResponseBuffered
//
//Searches the response array for the given cookie
//
//---------------------------------------------------------
BOOL
CDevCom::IsResponseBuffered( int Cookie, int *index)
{
int i;
// find the response in the array
for (i=0; i<MAX_RESPONSE_OBJECTS; i++) {
if (ResponseObject[i].Cookie == Cookie) {
*index = i;
return TRUE;
}
}
return FALSE;
}

//---------------------------------------------------------
//
//Wait4DevResponse
//
//Wrapper method that handles retries
//
//---------------------------------------------------------
DWORD
CDevCom::Wait4DevResponse( TCHAR *buf, int Cookie )
{
DWORD dwret;
int i = 0;
do {
if (i > RESPONSE_RETRY_MAX)
return DEV_COMM_ERR_RESPONSE_TIMEOUT;
dwret = GetDevResponse(buf, Cookie);
if (dwret != 0L && dwret != DEV_COMM_ERR_COMMAND_NOT_SENT_YET)
return dwret;
if (dwret)
Sleep(RESPONSE_SLEEP_TIME);
}while (dwret);
return dwret;
}
//---------------------------------------------------------
//
//GetSimulatedResponse
//
//Used for simulation mode
//
//---------------------------------------------------------
DWORD
CDevCom::GetSimulatedResponse( TCHAR *buf )
{
switch (m_bLastCmd){
case READ_TC:
if (m_LastMotionCmd == PLAY || m_LastMotionCmd == FREEZE_OFF) {
SimulatedFramecount++;
if ( SimulatedFramecount > 2591999L )
SimulatedFramecount = 0;
}
else if (m_LastMotionCmd == REWIND){
if (SimulatedFramecount >2591999L)
SimulatedFramecount = 2591999L;
else
SimulatedFramecount-=10;
}
GetSimulatedTimecode(buf, SimulatedFramecount);
return 0L;

case DEVICE_TYPE_REQUEST:
strcpy(buf, strDevID);
return 0L;

case PLAY:
 case STOP:
case TIMER_MODE_SELECT_LTC:
case REWIND:
case FREEZE_ON:
case FREEZE_OFF:
case STANDBY_OFF:
default:
if (VcrType == 0)
strncpy(buf, (TCHAR *)SvoAck, SvoAckCount);
else
strncpy(buf, (TCHAR *)SvbkAck, SvbkAckCount);
return 0L;
}
return 0L;
}
//---------------------------------------------------------
//
//m_isMotionCmd
//
//private helper method to differentiate Motion commands
//from things like Timecode requests (sometimes timecode
//requests need to be handled differently)
//
//---------------------------------------------------------
BOOL
CDevCom::m_isMotionCmd( BYTE Cmd ) {
 switch (Cmd) {
case READ_TC:
case TIMER_MODE_SELECT_LTC:
return FALSE;
case PLAY:
case STOP:
case REWIND:
case FFWD:
case RECORD:
case FREEZE_ON:
case FREEZE_OFF:
case STANDBY_OFF:
case EJECT:
return TRUE;
default:
return FALSE;
}
}
//---------------------------------------------------------
//
//ProcessDeviceTypeResponse
//
// Convert response to device type request to string if possible
//
//---------------------------------------------------------
void
CDevCom::ProcessDeviceTypeResponse( TCHAR *bufin, TCHAR *bufout)
{
int i;
if (m_bSimulateHardware) {
strcpy( bufout, bufin );
return;
}

// handle the two machine types differently
if (VcrType == 0) {
// compare the 2 data bytes of the response to the list of known
// machine types.  If we find a match, return the corresponding
// device type string
for (i=0; i<DevTypeCnt; i++) {
if ( bufin[2] != SvoDevTbl[i].bData1 )
continue;
if ( bufin[3] != SvoDevTbl[i].bData2 )
continue;
strcpy( bufout, strSvoDevID[i] );
return;
}
// unknown machine
strcpy( bufout, strUnknownDevID );
}
else
if (bufin[0] & (TCHAR)SvbkDevType)
strcpy( bufout, strSvbkDevID );
else
// unknown machine
strcpy( bufout, strUnknownDevID );
return;
}

//---------------------------------------------------------
//
//ProcessDeviceStatusResponse
//
// Stuff status structure with response to status request
//returns FALSE if SVBK returns a NAK
//
//---------------------------------------------------------
BOOL
CDevCom::ProcessDeviceStatusResponse( TCHAR *bufin, PVCRSTATUS pStatus )
{
if (m_bSimulateHardware) {
pStatus->bCassetteOut = OAFALSE;
pStatus->bLocal = OAFALSE;
return FALSE;
}

// we only process two bits of information right now, and
// it's hardcoded for clarity and simplicity
if (VcrType == 0) {// SVO
if (bufin[2] & 0x20)
pStatus->bCassetteOut = OATRUE;
else
pStatus->bCassetteOut = OAFALSE;

if (bufin[2] & 0x01)
pStatus->bLocal = OATRUE;
else
pStatus->bLocal = OAFALSE;
}
else { // SVBK
if (bufin[0] == 0x0b)// machine can send a NAK if not ready
return TRUE;
if (bufin[0] & 0x08)
pStatus->bCassetteOut = OATRUE;
else
pStatus->bCassetteOut = OAFALSE;
// dummy response for remote/local because that
// info is not available
pStatus->bLocal = OAFALSE;
}
return FALSE;
}
//---------------------------------------------------------
//
//AddCommand
//
// Insert pending command in empty space in array
//
//---------------------------------------------------------
PCOMMANDOBJECT
CDevCom::AddCommand( int Cmd, int Cookie ){

int i;
POSITION pos;

for (i=0; i<COMMANDLISTSIZE; i++) {
if (CommandObjects[i].Command == -1) {
CommandObjects[i].Command = Cmd;
CommandObjects[i].Cookie = Cookie;
CommandObjects[i].Timestamp = timeGetTime();
pos = CommandList.AddTail( &CommandObjects[i] );
return &CommandObjects[i];
}
}
return NULL; //Null means no more space
}
//---------------------------------------------------------
//
//RemoveCommand
//
// Clear entry in command array
//
//---------------------------------------------------------
void
CDevCom::RemoveCommand( PCOMMANDOBJECT pCmdObj )
{
pCmdObj->Command = -1;
pCmdObj->Cookie = -1;
return;
}
//---------------------------------------------------------
//
//TestResponse
//
// Calculates checksum on response and compares it to embedded
// checksum.  Returns FALSE if OK, TRUE if not
//
//---------------------------------------------------------
BOOL
CDevCom::TestResponse( TCHAR *buf, int Count )
{
BYTE CalcCheckSum = 0;
int i;

for ( i=0; i<(Count-1); i++) {
CalcCheckSum += (BYTE)buf[i];
}
if ( CalcCheckSum != (BYTE)buf[(Count-1)] )
return TRUE;
return FALSE;
}
//---------------------------------------------------------
//
//ProcessVcrTC - converts the timecode response to a binary
//framecount.  Handles both SVO and SVBK timecode formats
//
//---------------------------------------------------------
DWORD
CDevCom::ProcessVcrTC( TCHAR *inbuf, DWORD *dwFCM, long *ptimecode,
  long *puserbits)
{
BYTE btemp;
WORD wtemp;
static TCHAR outbuf[12];
static TIMECODE_SAMPLE tcsTimecode;
int i=0;
TCHAR *ptemp;

// look at first byte to determine SVO or SVBK
if ((inbuf[0] & 0x70) != 0x70 ) { // SVO's command value
// SVBK timecode format is 8 byte ASCII, Hx10 first
// hours.  Can't tell whether it's drop or nondrop,
// so we'll stay nondrop SVBK also has an unpredictable
// number of 0xA1's at the head of the buffer, so we must correct.
while ( inbuf[i] == (TCHAR)0xA1 || inbuf[i] == (TCHAR)0xA2 ) {
i++;
}
ptemp =&inbuf[i];

wtemp = AM_TIMECODE_30NONDROP;
// hours
outbuf[0] = ptemp[0];
outbuf[1] = ptemp[1];
outbuf[2] = ':';

// minutes
outbuf[3] = ptemp[2];
outbuf[4] = ptemp[3];
outbuf[5] = ':';

// seconds
outbuf[6] = ptemp[4];
outbuf[7] = ptemp[5];
outbuf[8] = ':';

// frames
outbuf[9] = ptemp[6];
outbuf[10] = ptemp[7];

// terminate

outbuf[11] = 0;

// convert to binary
tcsTimecode.timecode.wFrameRate = wtemp;
m_pTimecode->ConvertStringToTimecode(outbuf, &tcsTimecode);
*ptimecode = tcsTimecode.timecode.dwFrames;
*dwFCM = (DWORD)wtemp;
*puserbits = (DWORD)0L; // forget userbits
}
else { // SVO
// SVO t/c format is this:
// BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
//DATA1 x  DF 10F   | 1F
//DATA2 x 10S   | 1S
//DATA3 x 10M   | 1M
//DATA4 x  x 10H   | 1H
//
// It lives in bytes 2 thru 5 in the response from the machine, and
// userbits live in bytes 6 thru 9

// just yank em out and put em in the output buffer
// get the dropframe flag
if ( inbuf[2] & 0x40 )
wtemp = AM_TIMECODE_30DROP;
else
wtemp = AM_TIMECODE_30NONDROP;

// hours
btemp = inbuf[5] & 0x30;
btemp = btemp>>(4*sizeof(TCHAR));
btemp += '0';
outbuf[0] = btemp;
outbuf[1] = (inbuf[5] & 0x0f) + '0';
outbuf[2] = ':';

// minutes
btemp = inbuf[4] & 0x70;
btemp = btemp>>(4*sizeof(TCHAR));
outbuf[3] = btemp + '0';
outbuf[4] = (inbuf[4] & 0x0f) + '0';
outbuf[5] = ':';

// seconds
btemp = inbuf[3] & 0x70;
btemp = btemp>>(4*sizeof(TCHAR));
outbuf[6] = btemp + '0';
outbuf[7] = (inbuf[3] & 0x0f) + '0';
if ( AM_TIMECODE_30DROP == wtemp )
outbuf[8] = ';';
else
outbuf[8] = ':';

// frames
btemp = inbuf[2] & 0x30;
btemp = btemp>>(4*sizeof(TCHAR));
outbuf[9] = btemp + '0';
outbuf[10] = (inbuf[2] & 0x0f) + '0';

// terminate
outbuf[11] = 0;

// convert to binary
tcsTimecode.timecode.wFrameRate = wtemp;
m_pTimecode->ConvertStringToTimecode(outbuf, &tcsTimecode);
*ptimecode = tcsTimecode.timecode.dwFrames;
*dwFCM = (DWORD)wtemp;
*puserbits = (DWORD)inbuf[6];
}
return 0;
}

//---------------------------------------------------------
//
//For no-hardware mode
//
//---------------------------------------------------------
void
CDevCom::GetSimulatedTimecode( TCHAR * timecode, long framecount )
{
long temp;
int i;

if (VcrType == 0) {
// SVO t/c format is this:
// BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
//DATA1 x  DF 10F   | 1F
//DATA2 x 10S   | 1S
//DATA3 x 10M   | 1M
//DATA4 x  x 10H   | 1H
//
// It lives in bytes 2 thru 5 in the response from the machine
// Bytes 6 thru 9 are user bits, and 10 is the checksum

// put in the command
timecode[0] = 0x74;
timecode[1] = 0x04;

// convert the frame count to Svo t/c format
temp = framecount / lNDFTable[0];// hours
framecount -= temp * lNDFTable[0];
timecode[5] = (TCHAR)temp << (4*sizeof(TCHAR));
temp = framecount / lNDFTable[1];
framecount -= temp * lNDFTable[1];
timecode[5] |= temp;

temp = framecount / lNDFTable[2];// minutes
framecount -= temp * lNDFTable[2];
timecode[4] = (TCHAR)temp << (4*sizeof(TCHAR));
temp = framecount / lNDFTable[3];
framecount -= temp * lNDFTable[3];
timecode[4] |= temp;

temp = framecount / lNDFTable[4];// seconds
framecount -= temp * lNDFTable[4];
timecode[3] = (TCHAR)temp << (4*sizeof(TCHAR));
temp = framecount / lNDFTable[5];
framecount -= temp * lNDFTable[5];
timecode[3] |= temp;

temp = framecount / lNDFTable[6];// frames
framecount -= temp * lNDFTable[6];
timecode[2] = (TCHAR)temp << (4*sizeof(TCHAR));
timecode[2] |= framecount;

// do the checksum
timecode[10] = 0;
for (i=0; i<11; i++)
{
timecode[10] +=timecode[i];
}
}
else {
// SVBK timecode format is 8 byte ASCII, Hx10 first
// hours.  Can't tell whether it's drop or nondrop, so we'll stay
// nondrop
temp = framecount / lNDFTable[0];// hours
framecount -= temp * lNDFTable[0];
timecode[0] = (TCHAR)temp | 0x30;
temp = framecount / lNDFTable[1];
framecount -= temp * lNDFTable[1];
timecode[1] = temp | 0x30;

temp = framecount / lNDFTable[2];// minutes
framecount -= temp * lNDFTable[2];
timecode[2] = (TCHAR)temp | 0x30;
temp = framecount / lNDFTable[3];
framecount -= temp * lNDFTable[3];
timecode[3] = temp | 0x30;

temp = framecount / lNDFTable[4];// seconds
framecount -= temp * lNDFTable[4];
timecode[4] = (TCHAR)temp | 0x30;
temp = framecount / lNDFTable[5];
framecount -= temp * lNDFTable[5];
timecode[5] = temp | 0x30;

temp = framecount / lNDFTable[6];// frames
framecount -= temp * lNDFTable[6];
timecode[6] = (TCHAR)temp | 0x30;
timecode[7] = framecount | 0x30;
}
return;
}

// eof cdevcom.cpp


CDEVCOM.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//filename: cdevcom.h
//
//header for devcom.cpp - External Device Communications Class
//for an SVO-type RS-422 Controlled Tape Machine and
//SVBK-type RS-232 Controlled machines
//
//
#ifndef CDEVCOM_H
#defineCDEVCOM_H1

//----------------------------------------------------------
//
// Structures and definitions for this implementation
//
// communications and low-level protocol defines
// device ports
#define DEV_PORT_SIM1
#define DEV_PORT_COM12// standard serial ports
#define DEV_PORT_COM23
#define DEV_PORT_COM34
#define DEV_PORT_COM45
#define DEV_PORT_DIAQ6// Diaquest driver
#define DEV_PORT_ARTI7// ARTI driver
#define DEV_PORT_13948// IEEE 1394 Bus
#define DEV_PORT_USB9// Universal Serial Bus
#define DEV_PORT_MINDEV_PORT_SIM
#define DEV_PORT_MAXDEV_PORT_USB

// machine commands - ****The order of these must match the order
// of the commands in the command list in devcom.cpp!!!
#define PLAY0
#define STOP1
#define READ_TC2
#define TIMER_MODE_SELECT_LTC 3
#define REWIND4
#define FREEZE_ON 5
#define FREEZE_OFF 6
#define STANDBY_OFF 7
#define STANDBY_ON 8
#define DEVICE_TYPE_REQUEST 9
#define REQUEST_STATUS 10
#define EJECT 11
#define LOCAL_ON 12
#define LOCAL_OFF 13
#define FFWD 14
#define RECORD 15
#define DEVICE_CLEAR 16

// error values
#define DEV_COMM_ERR_COMMAND_NOT_SENT_YET1L
#define DEV_COMM_ERR_QUEUE_OVERFLOW2L
#define DEV_COMM_ERR_COMMUNICATION_ERROR3L
#define DEV_COMM_ERR_RESPONSE_MISMATCH4L
#define DEV_COMM_ERR_RESPONSE_TIMEOUT5L
#define DEV_COMM_ERR_RESPONSE_CHECKSUM6L
#define DEV_COMM_ERR_COMMAND_MISSING7L
#define DEV_COMM_ERR_RESPONSE_OVERFLOW8L
#define DEV_COMM_ERR_RESPONSE_MISSING9L
#define DEV_COMM_ERR_COMMAND_NOT_SUPPORTED10L

// low level machine status structure filled in after
// REQUEST_STATUS command from above.  This structure would
// grow in a full implementation
typedef struct tagVCRSTATUS{
BOOL bCassetteOut;// OATRUE means no cassette
BOOL bLocal;// OATRUE means front panel switch in local
} VCRSTATUS;

typedef VCRSTATUS far *PVCRSTATUS;

typedef struct tagCOMMANDOBJECT {
int Command;
int Cookie;
DWORD Timestamp;
} COMMANDOBJECT;
typedef COMMANDOBJECT far *PCOMMANDOBJECT;

typedef struct tagRESPONSEOBJECT {
int Cookie;
int ResponseByteCount;
TCHAR buf[16];
} RESPONSEOBJECT;
typedef RESPONSEOBJECT far *PRESPONSEOBJECT;

//----------------------------------------------------------
//
//The communications object
//
//----------------------------------------------------------
class CDevCom : public CCritSec{
public:
// constructor
CDevCom(CBaseFilter *pFilter, HRESULT *phr);

~CDevCom();

// all of these methods return 0 for success
// Initialize Device Port accepts the following:
//DEV_PORT_COM1 | DEV_PORT_COM2 |DEV_PORT_COM3 |
//DEV_PORT_COM3 | DEV_PORT_DIAQ | DEV_PORT_ARTI etc.
DWORD OpenDevPort(int port);

// Close Device Port
DWORD CloseDevPort(void);

// Flush Device Response Buffer
DWORD FlushDevResponseBuffer(void);

// Send command to device.
DWORD SendDevCmd(int, int *);

// Read response from device - accepts pointer to response buffer
// Must pass cookie in to get proper command.  If command hasn't
// actually been sent, method returns with and error value
DWORD GetDevResponse(TCHAR *, int);
// this one sits around (by sleeping) until either the proper
// response comes back, a timeout occurs, or an error happens.
DWORD Wait4DevResponse(TCHAR *, int);

// convert response to device type request to a string
void ProcessDeviceTypeResponse( TCHAR *bufin, TCHAR *bufout);

// convert status request response to something meaningful
BOOL ProcessDeviceStatusResponse( TCHAR *bufin, PVCRSTATUS pStatus );

// convert raw machine timecode to something usable
DWORD ProcessVcrTC(TCHAR * RawTC, DWORD * pdwFCM, long * ptimecode,
long * puserbits);

protected:
CCritSec  *m_pCritSec;// Object we use for locking

private:
CTimecode *m_pTimecode;
CBaseFilter *m_pFilter;// our filter

static HANDLEm_hDevPort;// handle of opened comm port
static TCHAR *m_pcDevPort;// name of opened comm port
static BYTEm_bLastCmd;
static BYTEm_LastMotionCmd;// need this for effective
//  simulation
static DCB m_dcb;// initialize this with
//   GetCommState
static int m_PendingResponseCnt;// for synchronizing responses
static BOOLm_bSimulateHardware;// TRUE if no VCR connected
static COMMTIMEOUTS m_ctmoTimeout;// communications timeout structure
static COMSTATm_csCommStat;// communications status structure
static intm_CurCmdCookie;// used for command queueing
static intm_LastSentCmdCookie;// so we know immediately which
//   command was just sent

DWORD GetSimulatedResponse( TCHAR *buf );// for the big fake out
BOOL m_isMotionCmd( BYTE Cmd );
PCOMMANDOBJECT AddCommand(int Cmd, int Cookie);
void RemoveCommand(PCOMMANDOBJECT pCmd);
DWORD ReallySendDevCmd(int, int);
DWORD ReallyGetDevResponse( TCHAR *buf, int bytecnt );
BOOL TestResponse( TCHAR *buf, int Count );
DWORD BufferResponse(void);
DWORD GetBufferedResponse( TCHAR *buf, int Cookie);
BOOL IsResponseBuffered( int Cookie, int *index);
void GetSimulatedTimecode(TCHAR * timecode, long framecount);
};

#endif// #ifndef CDEVCOM_H
// eof cdevcom.h


CTIMECOD.CPP

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//   filename: ctimecod.cpp
//
//     SMPTE Timecode support class
//
//---------------------------------------------------------
//
//This code is provided for reference purposes only
//
//NOTES:
// 1. Code is un-optimized for clarity
// 2. Minimal error checking.
// 3. Negative t/c values not supported
// 4. File logging functions are here for illustrative
//purposes but not used.
//
//---------------------------------------------------------

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <vfw.h>
#include <streams.h>
#include "ctimecod.h"

// timecode conversion tables
#define TC_LENGTH 11*sizeof(TCHAR)// length of a t/c string
static long lNDFTable[] = {1080000L,108000L,18000L,1800L,300L,30L,10L,1L};
static long lDFTable[] = {1078920L,107892L,17982L,1798L,300L,30L,10L,1L};
static long l25Table[] = {900000L,90000L,15000L,1500L,250L,25L,10L,1L};
static long l24Table[] = {864000L,86400L,14400L,1440L,240L,24L,10L,1L};

//---------------------------------------------------------
//
//ConvertStringToTimecode
//
// Timecode sample passed in contains desired framerate for conversion
//
//---------------------------------------------------------
DWORD
CTimecode::ConvertStringToTimecode(TCHAR *lpszTimecode,
   PTIMECODE_SAMPLE pTimecodeSample)
{
int i, j;
long *table;
TCHAR temp;

// select conversion table
switch (pTimecodeSample->timecode.wFrameRate)
{
case AM_TIMECODE_30DROP:
table = lDFTable;
break;
case AM_TIMECODE_30NONDROP:
table = lNDFTable;
break;
case AM_TIMECODE_25:
table = l25Table;
break;
case AM_TIMECODE_24:
table = l24Table;
break;
default:
table = lNDFTable;
}
pTimecodeSample->timecode.dwFrames = 0L;
j = 0;
for ( i = 0; lpszTimecode[i]!= 0; i++ )
{
if ( (lpszTimecode[i] > '9') || (lpszTimecode[i] < '0') )
continue;
temp = (lpszTimecode[i] - '0');
pTimecodeSample->timecode.dwFrames += (DWORD)temp * table[j];
j++;
}
return 0L;
}
//---------------------------------------------------------
//
//ConvertTimecodeToString
//
//---------------------------------------------------------
DWORD
CTimecode::ConvertTimecodeToString(PTIMECODE_SAMPLE pTimecodeSample,
   TCHAR *lpszTimecode)
{
int i, j, temp;
long timecode;
long *table;
WORD wFCM;
int flag = 0;

// make local copies
wFCM = pTimecodeSample->timecode.wFrameRate;
timecode = pTimecodeSample->timecode.dwFrames;

// select conversion table
switch (wFCM)
{
case AM_TIMECODE_30DROP:
table = lDFTable;
break;
case AM_TIMECODE_30NONDROP:
table = lNDFTable;
break;
case AM_TIMECODE_25:
table = l25Table;
break;
case AM_TIMECODE_24:
table = l24Table;
break;
default:
table = lNDFTable;
}
for ( i=0, j=0; j<(TC_LENGTH); i++, j++ )
{
temp = (int)( timecode / table[i]);
lpszTimecode[j] = temp + '0';// ASCII conversion
timecode -= temp * table[i];
// if minutesx10, do the drop frame test
if ( i == 2 ) {
if ( timecode < 2)
flag = 1;// this means we are on a 10 minute
// boundary and 2 frames should be
// added later
else
timecode-=2L;
}
if ( i == 3 ) {
// this is where the 2 frames get added in
if ( !flag )
timecode+=2L;
}
// add punctuation
if ( 1 == j || 4 == j || 7 == j )
{
j++;
if ( 8 == j && wFCM == AM_TIMECODE_30DROP )
lpszTimecode[j] = ';';
else
lpszTimecode[j] = ':';
}
}
lpszTimecode[j] = 0;// null terminate
return 0L;
}

// eof ctimecod.c


CTIMECOD.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//filename: ctimecod.h
//
//header for ctimecod.cpp - basic timecode support class
//
//
//
#ifndef CTIMECOD_H
#defineCTIMECOD_H1

#include <vfw.h>

// Timecode framerate definitions
#define AM_TIMECODE_30DROP0// 30 fps, dropframe
#define AM_TIMECODE_30NONDROP30// 30 fps, non-dropframe
#define AM_TIMECODE_2525// 25 fps (PAL)
#define AM_TIMECODE_2424// 24 fps

// Timecode flag masks:
#defineAM_TIMECODE_FLAG_FCM0x00000001// Frame code mode:
//  0=nondrop, 1=drop
#defineAM_TIMECODE_FLAG_CF0x00000002// color frame flag
#defineAM_TIMECODE_FLAG_FIELD0x00000004// field flag
#defineAM_TIMECODE_FLAG_DF0x00000008// drop frame flag -
//  from flags in actual
//  timecode
#defineAM_TIMECODE_COLORFRAME0x000000f0// which frame in color
//  sequence
#defineAM_TIMECODE_COLORSEQUENCE0x00000f00// duration in frames of
//  complete sequence
#defineAM_TIMECODE_FILMSEQUENCE_TYPE0x0000f000// One of FILM_SEQUENCE_XXX
//  defines
// upper 16 bits are reserved for future use - set to 0

// for informational purposes:
#define FILM_SEQUENCE_NONE       0
#define FILM_SEQUENCE_AABBBCCDDD 1
#define FILM_SEQUENCE_AAABBCCCDD 2

//
//The timecode support class
//
class CTimecode {
public:

// convert HH:MM:SS:FFs to binary.  Args are pointer to null-terminated
//"HH:MM:SS:FF' string and frame code mode:MC_FORMAT_SMPTE_30 ||
//MC_FORMAT_SMPTE_30DROP
DWORD ConvertStringToTimecode(TCHAR *lpszTimecode,
PTIMECODE_SAMPLE pTimecodeSample);

// convert binary frames to HH:MM:SS:FFs.  NOTE:
//DF algorithm does not account for mathematicaly-generated
//values, i.e., assumes all binary values came from Ascii-converted
//DF values.
DWORD ConvertTimecodeToString(PTIMECODE_SAMPLE pTimecodeSample,
TCHAR *lpszTimecode);
};

#endif// #ifndef CTIMECOD_H
// eof ctimecod.h


CVCRUTIL.CPP

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//filename: cvcrutil.cpp
//
// Base classes implementing external device control (VCR) interfaces.
// Derive from these and implement just the custom method and property
// methods.
//

#include <windows.h>
#include <streams.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <vfw.h>

#include "vcruids.h"
#include <initguid.h>
#include <olectl.h>

#include "ctimecod.h"
#include "cdevcom.h"
#include "cvcrutil.h"

#define DbgFunc(a) DbgLog(( LOG_TRACE, \
                            2, \
                            TEXT("VCRTC::%16hu"), \
                            TEXT(a) \
                         ));

const long ExtVidConnType[] = {
PhysConn_Video_Tuner,
PhysConn_Video_Composite,
PhysConn_Video_SVideo,
PhysConn_Video_RGB,
PhysConn_Video_YRYBY,
PhysConn_Video_SerialDigital,
PhysConn_Video_ParallelDigital,
PhysConn_Video_SCSI,
PhysConn_Video_AUX,
PhysConn_Video_1394,
PhysConn_Video_USB
};
const int ExtVidConnCnt = sizeof(ExtVidConnType);

const TCHAR *strExtVidConnType[] = {
TEXT("Tuner"),
TEXT("Composite"),
TEXT("S-Video"),
TEXT("RGB"),
TEXT("Y//R-Y//B-Y"),
TEXT("Serial Digital"),
TEXT("Parallel Digital"),
TEXT("SCSI"),
TEXT("AUX"),
TEXT("1394"),
TEXT("USB")
};

const long ExtAudConnType[] = {
PhysConn_Audio_Tuner,
PhysConn_Audio_Line,
PhysConn_Audio_Mic,
PhysConn_Audio_AESDigital,
PhysConn_Audio_SPDIFDigital,
PhysConn_Audio_SCSI,
PhysConn_Audio_AUX,
PhysConn_Audio_1394,
PhysConn_Audio_USB
};
const int ExtAudConnCnt = sizeof(ExtAudConnType);

const TCHAR *strExtAudConnType[] = {
TEXT("Tuner"),
TEXT("Line"),
TEXT("Mic"),
TEXT("AES Digital"),
TEXT("SPDIF Digital"),
TEXT("SCSI"),
TEXT("AUX"),
TEXT("1394"),
TEXT("USB")
};
static TCHAR buf[32];
static TCHAR buf1[32];
const TCHAR strDummyDeviceVersion[] = TEXT("1.0");

//------------------------------------------
//
//CAMPhysicalPinInfo Implementation
//
//------------------------------------------
CAMPhysicalPinInfo::CAMPhysicalPinInfo(HRESULT * phr, TCHAR *tszName) :
CUnknown( tszName, NULL )
{

}

//------------------------------------------
//
//Destructor
//
//------------------------------------------
CAMPhysicalPinInfo::~CAMPhysicalPinInfo()
{

}

//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our Interface
//------------------------------------------
STDMETHODIMP
CAMPhysicalPinInfo::NonDelegatingQueryInterface(REFIID riid, void **ppv) {

CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_IAMPhysicalPinInfo)
        return GetInterface((IAMPhysicalPinInfo *) this, ppv);
    else
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

//------------------------------------------
//
// CAMExtDevice implementation
//
//------------------------------------------
CAMExtDevice::CAMExtDevice(HRESULT * phr, TCHAR *tszName) :
CUnknown( tszName, NULL ),
m_CurDevicePort(NULL)
{
HRESULT Result;
// must have a way to talk to a machine
m_devcom = new CDevCom((CBaseFilter *)this, &Result);
    if (m_devcom == NULL || Result) {
        *phr = E_OUTOFMEMORY;
        return;
}

// initialize the capabilities structures - we default to a test VCR,
// in this case a simple SVHS recorder such as the SVO-5800 (RS-422)
// or other machine with an SVBK-10 RS-232 interface
m_DevCaps.CanRecord = OATRUE;
m_DevCaps.CanRecordStrobe = OAFALSE;
m_DevCaps.HasAudio = OATRUE;
m_DevCaps.HasVideo = OATRUE;
m_DevCaps.UsesFiles = OAFALSE;
m_DevCaps.CanSave = OAFALSE;
m_DevCaps.DeviceType = ED_DEVTYPE_VCR;
m_DevCaps.TCRead = OATRUE;
m_DevCaps.TCWrite = OAFALSE;
m_DevCaps.CTLRead = OATRUE;
m_DevCaps.IndexRead = OAFALSE;
m_DevCaps.Preroll = 150L;
m_DevCaps.Postroll = 30L;;
m_DevCaps.SyncAcc = ED_SYNCACC_PRECISE;
m_DevCaps.NormRate = ED_RATE_2997;
m_DevCaps.CanPreview = OATRUE;
m_DevCaps.CanMonitorSrc = OATRUE;
m_DevCaps.CanTest = OATRUE;
m_DevCaps.VideoIn = OATRUE;
m_DevCaps.AudioIn = OATRUE;
m_DevCaps.Calibrate = OAFALSE;
m_DevCaps.SeekType = ED_SEEK_SLOW;

// default comm port and power state
put_DevicePort(DEV_PORT_COM2);
put_DevicePower(ED_POWER_ON);
}

//------------------------------------------
//
//Destructor
//
//------------------------------------------
CAMExtDevice::~CAMExtDevice()
{
 m_devcom->CloseDevPort();
}

//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our Interface
//------------------------------------------
STDMETHODIMP
CAMExtDevice::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_IAMExtDevice)
        return GetInterface((IAMExtDevice *) this, ppv);
    else
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

//------------------------------------------
//
//GetCapability
//
// General device capabilities property
//------------------------------------------
HRESULT
CAMExtDevice::GetCapability(long Capability, long *pValue, double *pdblValue )
{
HRESULT result = S_OK;

switch (Capability){

case ED_DEVCAP_CAN_RECORD:
*pValue = m_DevCaps.CanRecord;
break;
case ED_DEVCAP_CAN_RECORD_STROBE:
*pValue = m_DevCaps.CanRecordStrobe;
break;
case ED_DEVCAP_HAS_AUDIO:
*pValue = m_DevCaps.HasAudio;
break;
case ED_DEVCAP_HAS_VIDEO:
*pValue = m_DevCaps.HasVideo;
break;
case ED_DEVCAP_USES_FILES:
*pValue = m_DevCaps.UsesFiles;
break;
case ED_DEVCAP_CAN_SAVE:
*pValue = m_DevCaps.CanSave;
break;
case ED_DEVCAP_DEVICE_TYPE:
*pValue = m_DevCaps.DeviceType;
break;
case ED_DEVCAP_TIMECODE_READ:
*pValue = m_DevCaps.TCRead;
break;
case ED_DEVCAP_TIMECODE_WRITE:
*pValue = m_DevCaps.TCWrite;
break;
case ED_DEVCAP_CTLTRK_READ:
*pValue = m_DevCaps.CTLRead;
break;
case ED_DEVCAP_INDEX_READ:
*pValue = m_DevCaps.IndexRead;
break;
case ED_DEVCAP_PREROLL:
*pValue = m_DevCaps.Preroll;
break;
case ED_DEVCAP_POSTROLL:
*pValue = m_DevCaps.Postroll;
break;
case ED_DEVCAP_SYNC_ACCURACY:
*pValue = m_DevCaps.SyncAcc;
break;
case ED_DEVCAP_NORMAL_RATE:
*pValue = m_DevCaps.NormRate;
break;
case ED_DEVCAP_CAN_PREVIEW:
*pValue = m_DevCaps.CanPreview;
break;
case ED_DEVCAP_CAN_MONITOR_SOURCES:
*pValue = m_DevCaps.CanMonitorSrc;
break;
case ED_DEVCAP_CAN_TEST:
*pValue = m_DevCaps.CanTest;
break;
case ED_DEVCAP_VIDEO_INPUTS:
*pValue = m_DevCaps.VideoIn;
break;
case ED_DEVCAP_AUDIO_INPUTS:
*pValue = m_DevCaps.AudioIn;
break;
case ED_DEVCAP_NEEDS_CALIBRATING:
*pValue = m_DevCaps.Calibrate;
break;
case ED_DEVCAP_SEEK_TYPE:
*pValue = m_DevCaps.SeekType;
break;
default:
result = VFW_E_NOT_FOUND;
}
return S_OK;
}

//------------------------------------------
//
//get_ExternalDeviceID
//
// Get external device identification string
//------------------------------------------
HRESULT
CAMExtDevice::get_ExternalDeviceID(LPOLESTR * ppszData)
{
int Cookie;

CheckPointer(ppszData, E_POINTER);
*ppszData = NULL;
// query the device
if (m_devcom->SendDevCmd(DEVICE_TYPE_REQUEST, &Cookie) )
return E_FAIL;
if (m_devcom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_devcom->ProcessDeviceTypeResponse(buf, buf1);
if (buf1 !=NULL) {
*ppszData = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * (1+lstrlenW((LPOLESTR)buf1)));
if (*ppszData !=NULL)
lstrcpyW(*ppszData, (LPOLESTR)buf1);
}
return S_OK;
}

//------------------------------------------
//
//get_ExternalDeviceVersion
//
//------------------------------------------
HRESULT
CAMExtDevice::get_ExternalDeviceVersion(LPOLESTR * ppszData)
{
// The sample machine doesn't support this, so send
// a dummy response
CheckPointer(ppszData, E_POINTER);
*ppszData = NULL;
// query the device
*ppszData = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * \
(1+lstrlenW((LPOLESTR)strDummyDeviceVersion)));
if (*ppszData !=NULL)
lstrcpyW(*ppszData, (LPOLESTR)strDummyDeviceVersion);

return S_OK;
}

//------------------------------------------
//
//put_DevicePower
//
// Controls the external device's power mode.
// Unfortunately, the sample machine doesn't support this
//------------------------------------------
HRESULT
CAMExtDevice::put_DevicePower(long PowerMode)
{
TESTFLAG(PowerMode, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//get_DevicePower
//
//------------------------------------------
HRESULT
CAMExtDevice::get_DevicePower(long *pPowerMode)
{
// if the device port is being simulated, assume power off
if (m_CurDevicePort == DEV_PORT_SIM)
m_PowerMode = ED_POWER_OFF;
else
m_PowerMode = ED_POWER_ON;
*pPowerMode = m_PowerMode;
return S_OK;
}

//------------------------------------------
//
//Calibrate
//
//------------------------------------------
HRESULT
CAMExtDevice::Calibrate(HEVENT hEvent, long Mode, long *pStatus)
{
TESTFLAG( Mode , OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//get_DevicePort
//
//------------------------------------------
HRESULT
CAMExtDevice::get_DevicePort(long *pDevicePort)
{
*pDevicePort = m_CurDevicePort;
return S_OK;
}

//------------------------------------------
//
//put_DevicePort
//
//------------------------------------------
HRESULT
CAMExtDevice::put_DevicePort(long DevicePort)
{
TESTFLAG( DevicePort, OATRUE);

if ( DevicePort != m_CurDevicePort ) {
// range check
if ((DevicePort < DEV_PORT_MIN) ||
(DevicePort > DEV_PORT_MAX))
return VFW_E_NOT_FOUND;// find correct error code
if ( (m_CurDevicePort != NULL) & (m_CurDevicePort !=DEV_PORT_SIM) )
m_devcom->CloseDevPort();
if (m_devcom->OpenDevPort(DevicePort))
m_CurDevicePort = DEV_PORT_SIM;
else
m_CurDevicePort = DevicePort;
}
return S_OK;
}

//------------------------------------------
//
// CAMExtTransport implementation
//
//------------------------------------------
CAMExtTransport::CAMExtTransport(HRESULT * phr, TCHAR *tszName) :
CUnknown( tszName, NULL )
{
// initialize the video parms
m_TranVidParms.OutputMode = ED_PLAYBACK;
m_TranVidParms.Input = 0;// use the first (zeroth) input as the default

m_TranAudParms.EnableOutput = ED_AUDIO_ALL;
m_TranAudParms.EnableRecord = 0L;
m_TranAudParms.Input = 0;
m_TranAudParms.MonitorSource = 0;

m_TranStatus.Mode = ED_MODE_STOP;
m_TranStatus.LastError = 0L;
m_TranStatus.RecordInhibit = OAFALSE;
m_TranStatus.ServoLock = OAFALSE;
m_TranStatus.MediaPresent = OAFALSE;
m_TranStatus.MediaLength =
m_TranStatus.MediaSize =
m_TranStatus.MediaTrackCount =
m_TranStatus.MediaTrackLength =
m_TranStatus.MediaTrackSide =
m_TranStatus.MediaType = 0L;
m_TranStatus.LinkMode = OATRUE;// default to "linked"

m_localcontrol = FALSE;
}

//------------------------------------------
//
//Destructor
//
//------------------------------------------
CAMExtTransport::~CAMExtTransport()
{

}

//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our Interface
//------------------------------------------
STDMETHODIMP
CAMExtTransport::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_IAMExtTransport)
        return GetInterface((IAMExtTransport *) this, ppv);
    else
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

//------------------------------------------
//
//SetCommunicationObject
//
//------------------------------------------
HRESULT
CAMExtTransport::SetCommunicationObject(CDevCom *pCDevCom)
{
m_pTDevCom = pCDevCom;
return S_OK;
}

//------------------------------------------
//
//GetCapability
//
// Transport capabilities property
//------------------------------------------
HRESULT
CAMExtTransport::GetCapability(long Capability, long *pValue,
   double *pdblValue )
{
return E_NOTIMPL;
}

//------------------------------------------
//
//put_MediaState
//
//------------------------------------------
HRESULT
CAMExtTransport::put_MediaState(long State)
{
TESTFLAG(State, OATRUE);
int Cookie;
// the only thing we'll do here is eject - all others imply
// a bigger state change (like STOP->FREEZE) that we'd like to
// do here
if (State == ED_MEDIA_UNLOAD) {
if (m_TranStatus.Mode != ED_MODE_STOP)
CAMExtTransport::put_Mode(ED_MODE_STOP);
if (m_pTDevCom->SendDevCmd(EJECT, &Cookie))
return E_FAIL;
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
Sleep(4000);// wait a bit until the machine completes
m_mediastate = State;
return S_OK;
}
return E_NOTIMPL;
}

//------------------------------------------
//
//get_MediaState
//
//------------------------------------------
HRESULT
CAMExtTransport::get_MediaState(long FAR* pState)
{
int Cookie;
BOOL bStatusRet;

// This might be different for a device other than a VCR, but:
// ED_MEDIA_SPIN_UP: media inserted and not stopped
// ED_MEDIA_SPIN_DOWN: media inserted and stopped
// ED_MEDIA_UNLOAD: media ejected
if (m_TranStatus.Mode == ED_MODE_STOP) {
// check device for presence of media
do {
if (m_pTDevCom->SendDevCmd(REQUEST_STATUS, &Cookie))
return E_FAIL;
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
bStatusRet = m_pTDevCom->ProcessDeviceStatusResponse(buf,
&m_VcrStatus);
} while (bStatusRet);
if (m_VcrStatus.bCassetteOut == OATRUE)
*pState = ED_MEDIA_UNLOAD;
else
*pState = ED_MEDIA_SPIN_DOWN;
}
else
*pState = ED_MEDIA_SPIN_UP;

m_mediastate = *pState;
return S_OK;
}

//------------------------------------------
//
//put_LocalControl
//
//------------------------------------------
HRESULT
CAMExtTransport::put_LocalControl(long State)
{
TESTFLAG(State, OATRUE);
long temp;
int Cookie;
DWORD dwResult;

if (State == OATRUE)
temp = LOCAL_ON;
else
temp = LOCAL_OFF;
dwResult = m_pTDevCom->SendDevCmd(temp, &Cookie);
if (dwResult == DEV_COMM_ERR_COMMAND_NOT_SUPPORTED)
return E_NOTIMPL;
if (dwResult)
return E_FAIL;
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
return S_OK;
}

//------------------------------------------
//
//get_LocalControl
//
//------------------------------------------
HRESULT
CAMExtTransport::get_LocalControl(long FAR* pState)
{
int Cookie;

if (m_pTDevCom->SendDevCmd(REQUEST_STATUS, &Cookie))
return E_FAIL;
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_pTDevCom->ProcessDeviceStatusResponse(buf, &m_VcrStatus);
*pState = m_VcrStatus.bLocal;
return S_OK;
}

//------------------------------------------
//
//GetStatus
//
//------------------------------------------
HRESULT
CAMExtTransport::GetStatus(long StatusItem, long FAR* pValue)
{
switch (StatusItem) {
case ED_LINK_MODE:
*pValue = m_TranStatus.LinkMode;
return S_OK;
default:
return E_NOTIMPL;
}
}

//------------------------------------------
//
//GetTransportBasicParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::GetTransportBasicParameters(long Param, long FAR* pValue,
 LPOLESTR * ppszData)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetTransportBasicParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::SetTransportBasicParameters(long Param, long Value,
 LPCOLESTR pszData)
{
// we test false because this is not implemented
TESTFLAG(Param, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//GetTransportVideoParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::GetTransportVideoParameters(long Param, long FAR* pValue)
{
HRESULT hr = S_OK;

switch (Param) {
case ED_TRANSVIDEO_SET_OUTPUT:
*pValue = m_TranVidParms.OutputMode;
break;
case ED_TRANSVIDEO_SET_SOURCE:
*pValue = m_TranVidParms.Input;
break;
default:
hr = VFW_E_NOT_FOUND;
}
return hr;
}

//------------------------------------------
//
//SetTransportVideoParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::SetTransportVideoParameters(long Param, long Value)
{
// we test false because this is not implemented
TESTFLAG(Param, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//GetTransportAudioParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::GetTransportAudioParameters(long Param, long FAR* pValue)
{
HRESULT hr = S_OK;

switch (Param) {
case ED_TRANSAUDIO_ENABLE_OUTPUT:
*pValue = m_TranAudParms.EnableOutput;
break;
case ED_TRANSAUDIO_ENABLE_RECORD:
*pValue = m_TranAudParms.EnableRecord;
break;
case ED_TRANSAUDIO_SET_SOURCE:
*pValue = m_TranAudParms.Input;
break;
case ED_TRANSAUDIO_SET_MONITOR:
*pValue = m_TranAudParms.MonitorSource;
break;
default:
hr = VFW_E_NOT_FOUND;
}
return hr;
}

//------------------------------------------
//
//SetTransportAudioParameters
//
//------------------------------------------
HRESULT
CAMExtTransport::SetTransportAudioParameters(long Param, long Value)
{
// we test false because this is not implemented
TESTFLAG(Param, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//put_Mode
//
//------------------------------------------
HRESULT
CAMExtTransport::put_Mode(long Mode)
{
HRESULT result = S_OK;
DWORD cmdresult;
static TCHAR buf[32];
int Cookie;

TESTFLAG(Mode, OATRUE);
if ( Mode == m_TranStatus.Mode )
return S_OK;// don't repeat commands
if ( m_localcontrol == TRUE )
return ED_ERR_DEVICE_NOT_READY;

switch (Mode) {
case ED_MODE_PLAY:
cmdresult = m_pTDevCom->SendDevCmd(PLAY, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_PLAY;
break;
case ED_MODE_STOP:
cmdresult = m_pTDevCom->SendDevCmd(STOP, &Cookie);
ASSERT(cmdresult == 0L);
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
cmdresult = m_pTDevCom->SendDevCmd(STANDBY_OFF, &Cookie);
// we know that some machines don't support STANDBY_OFF
if (cmdresult != 0 &&
cmdresult != DEV_COMM_ERR_COMMAND_NOT_SUPPORTED )
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_STOP;
break;
case ED_MODE_FREEZE:
if (m_TranStatus.Mode == ED_MODE_STOP) {
return result;
}
cmdresult = m_pTDevCom->SendDevCmd(FREEZE_ON, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_FREEZE;
break;
case ED_MODE_THAW:
cmdresult = m_pTDevCom->SendDevCmd(FREEZE_OFF, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_TranStatus.Mode = m_lastmode;
break;

case ED_MODE_FF:
cmdresult = m_pTDevCom->SendDevCmd(FFWD, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_FF;
break;
case ED_MODE_REW:
cmdresult = m_pTDevCom->SendDevCmd(REWIND, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_REW;
break;
case ED_MODE_RECORD:
cmdresult = m_pTDevCom->SendDevCmd(RECORD, &Cookie);
ASSERT(cmdresult == 0L);
if (cmdresult == 0L)
if (m_pTDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
m_lastmode = m_TranStatus.Mode;
m_TranStatus.Mode = ED_MODE_RECORD;
break;
case ED_MODE_RECORD_STROBE:
result = E_NOTIMPL;
break;
case ED_MODE_STEP:
result = E_NOTIMPL;
break;
case ED_MODE_SHUTTLE:
result = E_NOTIMPL;
break;
case ED_MODE_LINK_ON:
m_TranStatus.LinkMode = OATRUE;
break;
case ED_MODE_LINK_OFF:
m_TranStatus.LinkMode = OAFALSE;
break;
default:
result = E_NOTIMPL;
}
// clean up if things went well
return result;
}

//------------------------------------------
//
//get_Mode
//
//------------------------------------------
HRESULT
CAMExtTransport::get_Mode(long FAR* pMode)
{
*pMode = m_TranStatus.Mode;
return S_OK;
}

//------------------------------------------
//
//put_Rate
//
//------------------------------------------
HRESULT
CAMExtTransport::put_Rate(double dblRate)
{
// add Test flag support
return E_NOTIMPL;
}

//------------------------------------------
//
//get_Rate
//
//------------------------------------------
HRESULT
CAMExtTransport::get_Rate(double FAR* pdblRate)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//GetChase
//
//------------------------------------------
HRESULT
CAMExtTransport::GetChase(long FAR* pEnabled, long FAR* pOffset,
  HEVENT FAR* phEvent)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetChase
//
//------------------------------------------
HRESULT
CAMExtTransport::SetChase(long Enable, long Offset, HEVENT hEvent)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//GetBump
//
//------------------------------------------
HRESULT
CAMExtTransport::GetBump(long FAR* pSpeed, long FAR* pDuration)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetBump
//
//------------------------------------------
HRESULT
CAMExtTransport::SetBump(long Speed, long Duration)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//get_AntiClogControl
//
//------------------------------------------
HRESULT
CAMExtTransport::get_AntiClogControl(long FAR* pEnabled)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//put_AntiClogControl
//
//------------------------------------------
HRESULT
CAMExtTransport::put_AntiClogControl(long Enable)
{
TESTFLAG(Enable, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//GetEditPropertySet
//
//------------------------------------------
HRESULT
CAMExtTransport::GetEditPropertySet(long EditID, long FAR* pState)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetEditPropertySet
//
//------------------------------------------
HRESULT
CAMExtTransport::SetEditPropertySet(long FAR* pEditID, long State)
{
TESTFLAG(State, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
//GetEditProperty
//
//------------------------------------------
HRESULT
CAMExtTransport::GetEditProperty(long EditID, long Param, long FAR* pValue)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetEditProperty
//
//------------------------------------------
HRESULT
CAMExtTransport::SetEditProperty(long EditID, long Param, long Value)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//get_EditStart
//
//------------------------------------------
HRESULT
CAMExtTransport::get_EditStart(long FAR* pValue)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//put_EditStart
//
//------------------------------------------
HRESULT
CAMExtTransport::put_EditStart(long Value)
{
TESTFLAG(Value, OAFALSE);
return E_NOTIMPL;
}

//------------------------------------------
//
// CAMTcr implementation
//
//------------------------------------------
CAMTcr::CAMTcr(HRESULT * phr, TCHAR *tszName) :
CUnknown( tszName, NULL )
{
// need some help with timecode
m_timecode = new CTimecode();
}

//------------------------------------------
//
//Destructor
//
//------------------------------------------
CAMTcr::~CAMTcr()
{

}
//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our Interface
//------------------------------------------
STDMETHODIMP
CAMTcr::NonDelegatingQueryInterface(REFIID riid, void **ppv) {

CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_IAMTimecodeReader)
        return GetInterface((IAMTimecodeReader *) this, ppv);
    else
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
}

//------------------------------------------
//
//SetCommunicationObject
//
//------------------------------------------
HRESULT
CAMTcr::SetCommunicationObject(CDevCom *pCDevCom)
{
m_pTCDevCom = pCDevCom;
return S_OK;
}

//------------------------------------------
//
//GetTCRMode
//
//------------------------------------------
HRESULT
CAMTcr::GetTCRMode(long Param, long FAR* pValue)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//get_VITCLine
//
//------------------------------------------
HRESULT
CAMTcr::get_VITCLine(long * pLine)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//put_VITCLine
//
//------------------------------------------
HRESULT
CAMTcr::put_VITCLine(long Line)
{
return E_NOTIMPL;
}

//------------------------------------------
//
//SetTCRMode
//
//------------------------------------------
HRESULT
CAMTcr::SetTCRMode(long Param, long Value)
{
TCHAR buf[20];
int Cookie;
DWORD dwResult;

TESTFLAG(Param, OATRUE);
// the real implementation will use a table instead
//of a switch.  This seriously partial implementation
//is just to get basic timecode reading right
switch (Param)
{
case ED_TCR_SOURCE:
if ( Value == ED_TCR_LTC ) {
dwResult = m_pTCDevCom->SendDevCmd(TIMER_MODE_SELECT_LTC,
&Cookie);
// we know that some machines don't support this command
// and don't even ask for an answer
if ( dwResult == DEV_COMM_ERR_COMMAND_NOT_SUPPORTED)
return E_NOTIMPL;
if ( dwResult )// fail on all other result codes
return E_FAIL;
if (m_pTCDevCom->Wait4DevResponse(buf, Cookie))
return E_FAIL;
}
return S_OK;
default:
return E_NOTIMPL;
}
return E_NOTIMPL;
}

//------------------------------------------
//
//GetTimecode
//
//------------------------------------------
HRESULT
CAMTcr::GetTimecode( PTIMECODE_SAMPLE pTimecodeSample)
{
DWORD cmdresult;
static TCHAR buf[32];
DWORD fcm;
int Cookie;
       LONG lFrame, lUser ;

cmdresult = m_pTCDevCom->SendDevCmd(READ_TC, &Cookie);
if ( cmdresult == 0L )
{// commands might get refused
if ( (cmdresult = m_pTCDevCom->Wait4DevResponse(buf, Cookie)) )

return cmdresult;//E_FAIL;
DbgLog((LOG_TRACE, 1, buf));
m_pTCDevCom->ProcessVcrTC(buf, &fcm, &lFrame, &lUser);
pTimecodeSample->timecode.wFrameRate = (WORD)fcm;
pTimecodeSample->timecode.dwFrames = (DWORD) lFrame ;
pTimecodeSample->dwUser = (DWORD) lUser ;
pTimecodeSample->dwFlags = 0L;
return S_OK;
}
// leave the reference time alone for now
// (to be filled in by calling filter)
return S_FALSE;
}

//eof  cvcrutil.cpp


CVCRUTIL.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
// filename: cvcrutil.h
//
// base abstract classes implementing the device control interfaces
// derive your filter (or an embedded class) from one of these and implement
// the pure virtual property and method functions
//

#ifndef __CVCRUTIL__
#define __CVCRUTIL__

// a macro to screen for the "Test" flag
#define TESTFLAG(a,b) if( a & 0x8000 ) return b

//---------------------------------------------------------
//
// Structures for this implementation
//
//---------------------------------------------------------

// someplace to put our properties
typedef struct tagDEVCAPS{
long CanRecord;
long CanRecordStrobe;
long HasAudio;
long HasVideo;
long UsesFiles;
long CanSave;
long DeviceType;
long TCRead;
long TCWrite;
long CTLRead;
long IndexRead;
long Preroll;
long Postroll;
long SyncAcc;
long NormRate;
long CanPreview;
long CanMonitorSrc;
long CanTest;
long VideoIn;
long AudioIn;
long Calibrate;
long SeekType;
long SimulatedHardware;// private
} DEVCAPS;

typedef DEVCAPS far *PDEVCAPS;

// transport status
typedef struct tagTRANSPORTSTATUS{
long Mode;
long LastError;
long RecordInhibit;
long ServoLock;
long MediaPresent;
long MediaLength;
long MediaSize;
long MediaTrackCount;
long MediaTrackLength;
long MediaTrackSide;
long MediaType;
long LinkMode;
} TRANSPORTSTATUS;
typedef TRANSPORTSTATUS far *PTRANSPORTSTATUS;

// transport basic parameters
typedef struct tagTRANSPORTBASICPARMS{
long TimeFormat;
long TimeReference;
long Superimpose;
long EndStopAction;
long RecordFormat;
long StepFrames;
long SetpField;
long Preroll;
long RecPreroll;
long Postroll;
long EditDelay;
long PlayTCDelay;
long RecTCDelay;
long EditField;
long FrameServo;
long ColorFrameServo;
long ServoRef;
long WarnGenlock;
long SetTracking;
TCHAR VolumeName[40];
long Ballistic[20];
long Speed;
long CounterFormat;
long TunerChannel;
long TunerNumber;
long TimerEvent;
long TimerStartDay;
long TimerStartTime;
long TimerStopDay;
long TimerStopTime;
} TRANSPORTBASICPARMS;
typedef TRANSPORTBASICPARMS far *PTRANSPORTBASICPARMS;

// transport video parameters
typedef struct tagTRANSPORTVIDEOPARMS{
long OutputMode;
long Input;
} TRANSPORTVIDEOPARMS;
typedef TRANSPORTVIDEOPARMS far *PTRANSPORTVIDEOPARMS;

// transport audio parameters
typedef struct tagTRANSPORTAUDIOPARMS{
long EnableOutput;
long EnableRecord;
long EnableSelsync;
long Input;
long MonitorSource;
} TRANSPORTAUDIOPARMS;
typedef TRANSPORTAUDIOPARMS far *PTRANSPORTAUDIOPARMS;

//---------------------------------------------------------
//
// Here's the temporary CAMPhysicalPinInfo class to be used with the input pins
//
//---------------------------------------------------------
class CAMPhysicalPinInfo : public CUnknown, public IAMPhysicalPinInfo
{
public:

CAMPhysicalPinInfo(HRESULT * phr, TCHAR *tszName);
    virtual ~CAMPhysicalPinInfo();

DECLARE_IUNKNOWN
// override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

/* IAMPhysicalPinInfo method  - to be implemented by derived class */
virtual STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType) PURE;

protected:
CCritSec m_StateLock;// Lock this to serialize function accesses to the
//  filter state

private:
CCritSec  *m_pCritSec;// Object we use for locking

};

//---------------------------------------------------------
//
//IAMExtDevice Implementation
//
//---------------------------------------------------------

class CAMExtDevice : public CUnknown, public IAMExtDevice
{
public:

CAMExtDevice(HRESULT * phr, TCHAR *tszName);
    virtual ~CAMExtDevice();

DECLARE_IUNKNOWN
// override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

/* IAMExtDevice methods */
STDMETHODIMP GetCapability (long Capability, long FAR* pValue,
double FAR* pdblValue);
    STDMETHODIMP get_ExternalDeviceID(LPOLESTR * ppszData);
    STDMETHODIMP get_ExternalDeviceVersion(LPOLESTR * ppszData);
    STDMETHODIMP put_DevicePower(long PowerMode);
    STDMETHODIMP get_DevicePower(long FAR* pPowerMode);
    STDMETHODIMP Calibrate(HEVENT hEvent, long Mode, long FAR* pStatus);
STDMETHODIMP get_DevicePort(long FAR * pDevicePort);
STDMETHODIMP put_DevicePort(long DevicePort);

protected:
CDevCom *m_devcom;// how to talk to the machine
CCritSec m_StateLock;// Lock this to serialize function accesses
//  to the filter state

private:
CCritSec  *m_pCritSec;// Object we use for locking
DEVCAPS m_DevCaps;// external device capabilities

int m_CurDevicePort;// how we are talking to the machine
int m_PowerMode;// on, off or standby
};

//---------------------------------------------------------
//
// IAMExtTransport implementation
//
// Transport Class - VCRs, ATRs, DDRs, etc.
//
//---------------------------------------------------------
class CAMExtTransport : public CUnknown, public IAMExtTransport
{
public:

CAMExtTransport(HRESULT * phr, TCHAR *tszName);
    virtual ~CAMExtTransport();

   DECLARE_IUNKNOWN
    // override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

/* IAMExtTransport methods */
STDMETHODIMP GetCapability (long Capability, long FAR* pValue,
double FAR* pdblValue);
    STDMETHODIMP put_MediaState(long State);
    STDMETHODIMP get_MediaState(long FAR* pState);
    STDMETHODIMP put_LocalControl(long State);
    STDMETHODIMP get_LocalControl(long FAR* pState);
    STDMETHODIMP GetStatus(long StatusItem, long FAR* pValue);
    STDMETHODIMP GetTransportBasicParameters(long Param, long FAR* pValue,
LPOLESTR * ppszData);
    STDMETHODIMP SetTransportBasicParameters(long Param, long Value,
LPCOLESTR pszData);
    STDMETHODIMP GetTransportVideoParameters(long Param, long FAR* pValue);
    STDMETHODIMP SetTransportVideoParameters(long Param, long Value);
    STDMETHODIMP GetTransportAudioParameters(long Param, long FAR* pValue);
    STDMETHODIMP SetTransportAudioParameters(long Param, long Value);
    STDMETHODIMP put_Mode(long Mode);
    STDMETHODIMP get_Mode(long FAR* pMode);
    STDMETHODIMP put_Rate(double dblRate);
    STDMETHODIMP get_Rate(double FAR* pdblRate);
    STDMETHODIMP GetChase(long FAR* pEnabled, long FAR* pOffset,
HEVENT FAR* phEvent);
    STDMETHODIMP SetChase(long Enable, long Offset, HEVENT hEvent);
    STDMETHODIMP GetBump(long FAR* pSpeed, long FAR* pDuration);
    STDMETHODIMP SetBump(long Speed, long Duration);
    STDMETHODIMP get_AntiClogControl(long FAR* pEnabled);
    STDMETHODIMP put_AntiClogControl(long Enable);
    STDMETHODIMP GetEditPropertySet(long EditID, long FAR* pState);
    STDMETHODIMP SetEditPropertySet(long FAR* pEditID, long State);
    STDMETHODIMP GetEditProperty(long EditID, long Param, long FAR* pValue);
    STDMETHODIMP SetEditProperty(long EditID, long Param, long Value);
    STDMETHODIMP get_EditStart(long FAR* pValue);
    STDMETHODIMP put_EditStart(long Value);

// The communications object can change depending on the low-level
// protocol and/or hardware connection
HRESULT SetCommunicationObject(CDevCom *);

protected:
CCritSec m_StateLock;// Lock this to serialize function accesses
//  to the filter state

private:
CDevCom *m_pTDevCom;// how to talk to the machine
TRANSPORTSTATUSm_TranStatus;// current status
TRANSPORTVIDEOPARMS m_TranVidParms;// keep all capabilities, etc. here
TRANSPORTAUDIOPARMS m_TranAudParms;
TRANSPORTBASICPARMS m_TranBasicParms;

// a bunch of properties we want to remember
long m_lastmode;
long m_mediastate;
long m_localcontrol;
VCRSTATUS m_VcrStatus;// raw status from VCR

};

//---------------------------------------------------------
//
// IAMTimecodeReader implementation
//
//---------------------------------------------------------

// Timecode Reader class
class CAMTcr : public CUnknown, public IAMTimecodeReader
{
public:

CAMTcr(HRESULT * phr, TCHAR *tszName);
    virtual ~CAMTcr();

   DECLARE_IUNKNOWN
// override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    /* IAMTimecodeReader methods */
    STDMETHODIMP GetTCRMode( long Param, long FAR* pValue);
    STDMETHODIMP SetTCRMode( long Param, long Value);
    STDMETHODIMP put_VITCLine( long Line);
    STDMETHODIMP get_VITCLine( long FAR* pLine);
    STDMETHODIMP GetTimecode( PTIMECODE_SAMPLE pTimecodeSample);

// helper to connect to the communications object.  This
// is an implementation-specific method
HRESULT SetCommunicationObject(CDevCom *);

protected:
CCritSec m_StateLock;// Lock this to serialize function accesses
//  to the filter state

private:
CDevCom *m_pTCDevCom;// how to talk to the device
CTimecode *m_timecode;
};

#endif // __CVCRUTIL__

// eof cvcrutil.h


FVCRCTRL.CPP

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//filename: fvcrctrl.cpp
//
// Sample VCR Control Filter
//    This filter is CBaseFilter derived.
//
// Responds to run/pause/stop.  An app would probably want to override the
// default operation of this filter and "disconnect" the PLAY, STOP and
// PAUSE mode commands from the filter's ::Run(), ::Stop() and ::Pause()
// methods using the "link" property.
//
//

#include <windows.h>
#include <streams.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <initguid.h>
#include <olectl.h>
#include <winbase.h>

#include "vcruids.h"
#include "ctimecod.h"
#include "cdevcom.h"
#include "cvcrutil.h"
#include "fvcrctrl.h"
#include "vcrprop.h"
#include "trprop.h"

#define DbgFunc(a) DbgLog(( LOG_TRACE, \
                            2, \
                            TEXT("CVcrFilter::%s"), \
                            TEXT(a) \
                         ));

// IPersistStream macros
#define WRITEOUT(var)   hr = pStream->Write(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

#define READIN(var)     hr = pStream->Read(&var, sizeof(var), NULL); \
                        if (FAILED(hr)) return hr;

// some local storage
LONGLONG CVcrTCOut::m_llLastTime = 0L;

const int textbufsize = 12*sizeof(TCHAR);
static TIMECODE_SAMPLE tcsTimecode = {
0L,{0,0,0L},0L,0L
};
// we will ask for timecode samples this often
const REFERENCE_TIME rtPeriodTime = 3333 * (UNITS / (100*MILLISECONDS));

// Physical Pin data
const TCHAR *strSVO = {"SVO-5800"};
const TCHAR *strPhysConn_Video_Composite = {"Composite"};
const TCHAR *strPhysConn_Video_SVideo = {"S-Video"};
const TCHAR *strPhysConn_Video_Black = {"Black"};
const TCHAR *strPhysConn_Audio_Mic = {"Microphone"};
const TCHAR *strPhysConn_Audio_Line = {"Line"};

// Setup data
const AMOVIESETUP_MEDIATYPE sudTcOpPinTypes[] =
{
    {&MEDIATYPE_Text,       // Major type
    &MEDIASUBTYPE_NULL},    // Minor type
{&MEDIATYPE_Timecode,// Major type
    &MEDIASUBTYPE_NULL}// Minor type
};

const AMOVIESETUP_MEDIATYPE sudAudOpPinType[] =
{
    &MEDIATYPE_AnalogAudio,// Major type
    &MEDIASUBTYPE_NULL// Minor type
};

const AMOVIESETUP_MEDIATYPE sudVidIpPinType[] =
{
    &MEDIATYPE_AnalogVideo,// Major type
    &MEDIASUBTYPE_AnalogVideo_NTSC_M,// Minor type
&MEDIATYPE_AnalogVideo,// Major type
    &MEDIASUBTYPE_AnalogVideo_NTSC_M// Minor type
};

const AMOVIESETUP_MEDIATYPE sudAudIpPinType[] =
{
    &MEDIATYPE_AnalogAudio,// Major type
    &MEDIASUBTYPE_NULL,// Minor type
&MEDIATYPE_AnalogAudio,// Major type
    &MEDIASUBTYPE_NULL// Minor type
};

const AMOVIESETUP_PIN sudPins[] =
{
   {L"Timecode Out",        // Pin string name
    FALSE,                  // Is it rendered
    TRUE,                   // Is it an output
    FALSE,                  // Can we have none
    FALSE,                  // Can we have many
    &CLSID_NULL,            // Connects to filter
    NULL,                   // Connects to pin
    2,                      // Number of types
    sudTcOpPinTypes},       // Pin details
   {L"Analog Composite Video In",     // Pin string name
    FALSE,                  // Is it rendered
    FALSE,                  // Is it an output
    FALSE,                  // Can we have none
    FALSE,                  // Can we have many
    &CLSID_NULL,            // Connects to filter
    NULL,                   // Connects to pin
    1,                      // Number of types
    sudVidIpPinType} // Pin details
};

const AMOVIESETUP_FILTER sudVcrctrlax =
{
    &CLSID_VCRControlFilter, // Filter CLSID
    L"VCR Control Filter",   // String name
    MERIT_DO_NOT_USE,    // Filter merit
    1,                      // Number of pins (that we know about)
    sudPins// Pin details
};

CFactoryTemplate g_Templates[]= {
{L"VCR Control Filter",
&CLSID_VCRControlFilter,
CVcrFilter::CreateInstance,
NULL,
&sudVcrctrlax},
{L"VCR Control General Property Page",
&CLSID_VCRControlPropertyPage,
CVcrProperties::CreateInstance,
NULL,
NULL},
{L"VCR Control Transport Property Page",
&CLSID_VCRTransPropertyPage,
CExtTransProperties::CreateInstance,
NULL,
NULL}
};
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);

//------------------------------------------
//
// exported entry points for registration and
// unregistration (in this case they only call
// through to default implmentations).
//
//------------------------------------------

//------------------------------------------
//
// DllRegisterServer
//
// Exported entry points for registration and unregistration
//
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer

//------------------------------------------
//
// DllUnregisterServer
//
//------------------------------------------
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer
//------------------------------------------

//------------------------------------------
//
// CVcrFilter::Constructor
//
//------------------------------------------
CVcrFilter::CVcrFilter(TCHAR *tszName, LPUNKNOWN punk, CLSID clsid,
HRESULT *phr)
    : CBaseFilter(tszName, punk, &m_StateLock, clsid, phr),
CAMExtDevice(phr, tszName),
CAMExtTransport(phr, tszName),
CAMTcr(phr, tszName),
CPersistStream(punk, phr),
m_iPins(0),
m_paStreams(NULL)
{
    DbgFunc("CVcrFilter");

CAutoLock cObjectLock(&m_StateLock);
LPOLESTR pName = NULL;

// we use a single CDevCom object to make the filter thread-safe
CAMTcr::SetCommunicationObject(m_devcom);
CAMExtTransport::SetCommunicationObject(m_devcom);

// create the pins - reserve space for 6, but we might only make 5
    m_paStreams    = (CBasePin **) new COutStream*[6];
    if (m_paStreams == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

    // our only output pin
m_paStreams[0] = new CVcrTCOut(phr, this, L"Timecode Out");
    if (m_paStreams[0] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

// Create the input pins
m_paStreams[1] = new CVcrAudioLineInputPin(NAME("VCR Audio Line"),this,
phr, L"Audio Line In");
    if (m_paStreams[1] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

m_paStreams[2] = new CVcrAudioMicInputPin(NAME("VCR Audio Line"),this,
phr, L"Audio Mic In");
    if (m_paStreams[2] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

m_paStreams[3] = new CVcrVideoCompInputPin(NAME("VCR Video Input"),this,
phr, L"Composite Video In");
    if (m_paStreams[3] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

    m_paStreams[4] = new CVcrVideoSInputPin(NAME("VCR Video Input"),this,
phr, L"S-Video In");
    if (m_paStreams[4] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
    }

// if this is an SVO-5800, then it has an additional input pin
get_ExternalDeviceID( &pName );
if ( strcmp(strSVO, (const char *)(LPSTR)pName) == 0 ) {
m_paStreams[5] = new CVcrVideoBlackInputPin(NAME("VCR Video Input"),this,
phr, L"Video Black");
if (m_paStreams[5] == NULL) {
        *phr = E_OUTOFMEMORY;
return;
}
}
QzTaskMemFree(pName);// must release the memory
pName = NULL;

put_Mode( ED_MODE_STOP );

// configure tc reader
SetTCRMode(ED_TCR_SOURCE, ED_TCR_LTC);
CPersistStream::SetDirty(TRUE);// always write out the state
}

//------------------------------------------
//
// CreateInstance
//
// Provide the way for COM to create a CVcrFilter object.  Also
// initialize a CVcrTCOut object so that we have a pin.
//------------------------------------------
CUnknown *
CVcrFilter::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{
    CVcrFilter *pNewObject = new CVcrFilter(NAME("VCR Control Filter"),
punk, CLSID_VCRControlFilter, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return (CSource *)pNewObject;
}

//------------------------------------------
//
// The Destructor
//
//------------------------------------------
CVcrFilter::~CVcrFilter( void )
{
//  Free our pins and pin array
    while (m_iPins != 0) {
// deleting the pins causes them to be removed from the array...
delete m_paStreams[m_iPins - 1];
    }

    ASSERT(m_paStreams == NULL);
}

//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our property pages and IAMExtDev/
//  IAMExtTransport/etc. interfaces
//------------------------------------------
STDMETHODIMP
CVcrFilter::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else if (riid == IID_IAMExtDevice) {
        return GetInterface((IAMExtDevice *) this, ppv);
    } else if (riid == IID_IAMExtTransport) {
        return GetInterface((IAMExtTransport *) this, ppv);
} else if (riid == IID_IAMTimecodeReader) {
        return GetInterface((IAMTimecodeReader *) this, ppv);
} else if (riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *) this, ppv);
    }else {
        return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);
}
}

//------------------------------------------
//
// GetClassID
//
// Override CBaseMediaFilter method for interface IPersist
// Part of the persistent file support.  We must supply our class id
// which can be saved in a graph file and used on loading a graph with
// an external device in it to instantiate this filter via CoCreateInstance.
//
//------------------------------------------
STDMETHODIMP
CVcrFilter::GetClassID(CLSID *pClsid)
{
    if (pClsid==NULL) {
        return E_POINTER;
    }
    *pClsid = CLSID_VCRControlFilter;
    return NOERROR;
}

//------------------------------------------
//
// SizeMax
//
// Override CPersistStream method.
// State the maximum number of bytes we would ever write in a file
// to save our properties.
//
//------------------------------------------
int
CVcrFilter::SizeMax()
{
    // When an int is expanded as characters it takes at most 12 characters
    // including a trailing delimiter.
    // Wide chars doubles this and we want 16 ints.
    //
    return 384;
}

//------------------------------------------
//
// WriteToStream
//
// Override CPersistStream method.
// Write our properties to the stream.
//
//We only write a few of the important properties
//to demonstrate the process.
//
//------------------------------------------
HRESULT
CVcrFilter::WriteToStream(IStream *pStream)
{
long temp;
    HRESULT hr;

GetTransportVideoParameters(ED_TRANSVIDEO_SET_SOURCE, &temp);
    hr = WRITEOUT(temp);
    if (FAILED(hr)) return hr;

GetTransportAudioParameters(ED_TRANSAUDIO_SET_SOURCE, &temp);
    hr = WRITEOUT(temp);
    if (FAILED(hr)) return hr;

GetStatus(ED_LINK_MODE, &temp);
    hr = WRITEOUT(temp);
    if (FAILED(hr)) return hr;

get_DevicePort(&temp);
    hr = WRITEOUT(temp);
    if (FAILED(hr)) return hr;

    return NOERROR;
}

//------------------------------------------
//
// ReadFromStream
//
// Override CPersistStream method.
// Read our properties from the stream.
//
//------------------------------------------
HRESULT
CVcrFilter::ReadFromStream(IStream *pStream)
{
long temp;
    HRESULT hr;

hr = READIN(temp);
SetTransportVideoParameters(ED_TRANSVIDEO_SET_SOURCE, temp);
    if (FAILED(hr)) return hr;

hr = READIN(temp);
SetTransportAudioParameters(ED_TRANSAUDIO_SET_SOURCE, temp);
    if (FAILED(hr)) return hr;

hr = READIN(temp);
put_Mode(temp);// this will set the link mode
    if (FAILED(hr)) return hr;

hr = READIN(temp);
put_DevicePort(temp);// this will set the active communications
    if (FAILED(hr)) return hr;// port

    return NOERROR;
}

//------------------------------------------
//
//  Add a new pin
//
//------------------------------------------
HRESULT
CVcrFilter::AddPin(CBasePin *pPin)
{
    CAutoLock lock(&m_StateLock);

    /*  Allocate space for this pin and the old ones */
    CBasePin **paStreams = new CBasePin *[m_iPins + 1];
    if (paStreams == NULL) {
        return E_OUTOFMEMORY;
    }
    if (m_paStreams != NULL) {
        CopyMemory((PVOID)paStreams, (PVOID)m_paStreams,
                   m_iPins * sizeof(m_paStreams[0]));
        paStreams[m_iPins] = pPin;
        delete [] m_paStreams;
    }
    m_paStreams = (CBasePin **)paStreams;
    m_paStreams[m_iPins] = pPin;
    m_iPins++;
return S_OK;
}

//------------------------------------------
//
//  Remove a pin - pStream is NOT deleted
//
//------------------------------------------
HRESULT
CVcrFilter::RemovePin(CBasePin *pStream)
{
    int i;
    for (i = 0; i < m_iPins; i++) {
        if (m_paStreams[i] == pStream) {
            if (m_iPins == 1) {
                delete [] m_paStreams;
                m_paStreams = NULL;
            } else {
                /*  no need to reallocate */
while (++i < m_iPins)
m_paStreams[i - 1] = m_paStreams[i];
            }
            m_iPins--;
return S_OK;
        }
    }
    return S_FALSE;
}

//------------------------------------------
//
// GetPinCount
//
// Returns the number of pins this filter has
//
//------------------------------------------
int
CVcrFilter::GetPinCount(void)
{
    CAutoLock lock(&m_StateLock);
    return m_iPins;
}

//------------------------------------------
//
// GetPin
//
// Return a non-addref'd pointer to pin n
// needed by CBaseFilter
//
//------------------------------------------
CBasePin *
CVcrFilter::GetPin(int n)
{
    CAutoLock lock(&m_StateLock);

    if ((m_iPins > 0) && (n < m_iPins)) {

        ASSERT(m_paStreams[n]);
return m_paStreams[n];
    }
    return NULL;
}

//------------------------------------------
//
// IMediaControl Overrides
//
//------------------------------------------
HRESULT
CVcrFilter::Run(REFERENCE_TIME tStart)
{
long Mode;
long LinkMode;

DbgFunc("Run");
GetStatus(ED_LINK_MODE, &LinkMode);
if (LinkMode == OATRUE) {
get_Mode( &Mode );
if (Mode == ED_MODE_FREEZE)
put_Mode(ED_MODE_THAW);
else
put_Mode(ED_MODE_PLAY);
}

// use the base Run method for the state transition
CBaseFilter::Run(tStart);
return NOERROR;
}

HRESULT
CVcrFilter::Pause() {
long LinkMode;

DbgFunc("Pause");
GetStatus(ED_LINK_MODE, &LinkMode);
if (LinkMode == OATRUE)
put_Mode(ED_MODE_FREEZE);

// use the base Run method for the state transition
CBaseFilter::Pause();
return NOERROR;
}

HRESULT
CVcrFilter::Stop()
{
long LinkMode;

DbgFunc("Stop");
GetStatus(ED_LINK_MODE, &LinkMode);
if (LinkMode == OATRUE)
put_Mode(ED_MODE_STOP);

// use the base Run method for the state transition
CBaseFilter::Stop();
return NOERROR;
}

// ==============Implementation of the IPropertypages Interface ===========
//------------------------------------------
//
// GetPages - return the CLSIDs for the property pages we support
//
//------------------------------------------
STDMETHODIMP
CVcrFilter::GetPages(CAUUID * pPages)
{
CAutoLock l(&m_StateLock);

CheckPointer(pPages,E_POINTER);

pPages->cElems = 2;
    pPages->pElems = (GUID *) QzTaskMemAlloc(2 * sizeof(GUID));
    if (pPages->pElems == NULL) {
        return E_OUTOFMEMORY;
    }

    pPages->pElems[0] = CLSID_VCRControlPropertyPage;
    pPages->pElems[1] = CLSID_VCRTransPropertyPage;

    return NOERROR;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * --- COutStream ----
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------
//
//------------------------------------------
//
// COutStream::Constructor
//
// increments the number of pins present on the filter
//
//------------------------------------------
COutStream::COutStream(
    TCHAR *pObjectName,
    HRESULT *phr,
    CVcrFilter *pParent,
    LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, pParent, pParent->pLock(), phr, pPinName),
    m_pFilter(pParent),
m_pRefClock(NULL),
m_hAdviseEvent(NULL)
{
     *phr = m_pFilter->AddPin(this);
}

//------------------------------------------
//
// COutStream::Destructor
//
// Decrements the number of pins on this filter
//------------------------------------------
COutStream::~COutStream(void)
{
m_pFilter->RemovePin(this);
}
//------------------------------------------
//
// Active
//
// The pin is active - start up the worker thread
//
//------------------------------------------
HRESULT
COutStream::Active(void)
{
    CAutoLock lock(m_pFilter->pLock());

    HRESULT hr;

    if (m_pFilter->IsActive()) {
return S_FALSE;// succeeded, but did not allocate resources
//  (they already exist...)
    }

    // do nothing if not connected - its ok not to connect to
    // all pins of an external device filter
    if (!IsConnected()) {
        return NOERROR;
    }

    hr = CBaseOutputPin::Active();
    if (FAILED(hr)) {
        return hr;
    }

    ASSERT(!ThreadExists());

    // start the thread
    if (!Create()) {
        return E_FAIL;
    }

    // Tell thread to initialize. If OnThreadCreate Fails, so does this.
    hr = Init();
    if (FAILED(hr))
return hr;

    return Pause();
}

//------------------------------------------
//
// Inactive
//
// Pin is inactive - shut down the worker thread
// Waits for the worker to exit before returning.
//
//------------------------------------------
HRESULT
COutStream::Inactive(void)
{
    CAutoLock lock(m_pFilter->pLock());

    HRESULT hr;

    // do nothing if not connected - its ok not to connect to
    // all pins of an external device filter
    if (!IsConnected())
        return NOERROR;

    // !!! need to do this before trying to stop the thread, because
    // we may be stuck waiting for our own allocator!!!

    hr = CBaseOutputPin::Inactive();// call this first to decommit
//  the allocator
    if (FAILED(hr))
return hr;

    if (ThreadExists()) {
hr = Stop();
if (FAILED(hr)) {
return hr;
}
hr = Exit();
if (FAILED(hr)) {
return hr;
}
Close();// Wait for the thread to exit, then tidy up.
    }
    return NOERROR;
}

//------------------------------------------
//
// ThreadProc
//
// When this returns the thread exits
// Return codes > 0 indicate an error occured
//
//------------------------------------------
DWORD
COutStream::ThreadProc(void)
{
    HRESULT hr;  // the return code from calls
    Command com;
REFERENCE_TIME rtNow;
DWORD    dwAdviseToken;

    do {
com = GetRequest();
if (com != CMD_INIT) {
DbgLog((LOG_ERROR, 1, TEXT("Thread expected init command")));
Reply(E_UNEXPECTED);
}
    } while (com != CMD_INIT);

    DbgLog((LOG_TRACE, 1, TEXT("COutStream worker thread initializing")));

    hr = OnThreadCreate(); // perform set up tasks
    if (FAILED(hr)) {
        DbgLog((LOG_ERROR, 1,
TEXT("COutStream::OnThreadCreate failed. Aborting thread.")));
        OnThreadDestroy();
Reply(hr);// send failed return code from OnThreadCreate
        return 1;
    }

    // Initialisation suceeded
    Reply(NOERROR);

    Command cmd;
    do {
cmd = GetRequest();

switch (cmd) {

case CMD_EXIT:
Reply(NOERROR);
break;

case CMD_RUN:
DbgLog((LOG_ERROR, 1,
TEXT("CMD_RUN received before a CMD_PAUSE???")));
// !!! fall through???
case CMD_PAUSE:
Reply(NOERROR);
// we pick up the reference clock here
if ( m_pFilter->GetSyncSource(&m_pRefClock) )
return (DWORD) VFW_E_NO_CLOCK;
ASSERT(m_pRefClock);
// and set the tick to 33.33ms (an NTSC video frame).  We use a
// "one-shot" event instead of a periodic notification because
// we don't want events to stack up due to system latencies.
m_hAdviseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
m_pRefClock->GetTime(&rtNow);
m_pRefClock->AdviseTime(m_tStart, rtNow+rtPeriodTime,
(unsigned long)m_hAdviseEvent, &dwAdviseToken);

DoBufferProcessingLoop();
break;

case CMD_STOP:
// release our reference clock
m_pRefClock->Release();
m_pRefClock = NULL;
m_hAdviseEvent = NULL;
Reply(NOERROR);
break;

default:
DbgLog((LOG_ERROR, 1, TEXT("Unknown command %d received!"), cmd));
Reply(E_NOTIMPL);
break;
}
    } while (cmd != CMD_EXIT);

    hr = OnThreadDestroy();// tidy up.
    if (FAILED(hr)) {
        DbgLog((LOG_ERROR, 1,
TEXT("COutStream::OnThreadDestroy failed. Exiting thread.")));
        return 1;
    }

    DbgLog((LOG_TRACE, 1, TEXT("COutStream worker thread exiting")));
    return 0;
}

//------------------------------------------
//
// DoBufferProcessingLoop
//
// Grabs a buffer and waits for a frame time to pass
//
//------------------------------------------
HRESULT
COutStream::DoBufferProcessingLoop(void)
{
    Command com;
REFERENCE_TIME rtNow = 0L;
REFERENCE_TIME rtAdvise = 0L;
DWORD    dwAdviseToken;

    OnThreadStartPlay();

    do {
while (!CheckRequest(&com)) {

    IMediaSample *pSample;

    HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,FALSE);
    if (FAILED(hr)) {
continue;// go round again. Perhaps the error will go away
    // or the allocator is decommited & we will be asked to
    // exit soon.
    }

    // Virtual function user will override.
    hr = FillBuffer(pSample);
    // !!! shouldn't we check for errors here???

    if (hr == S_OK) {
Deliver(pSample);
    } else if (hr == S_FALSE) {
pSample->Release();
DeliverEndOfStream();
return S_OK;
    } else {
DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr));
    }

    pSample->Release();
// and block here for a frame -
// but only do this if an event has been set
if (m_hAdviseEvent) {
WaitForSingleObject(m_hAdviseEvent, 1000L);
// woke up - show the time
m_pRefClock->GetTime(&rtNow);
rtAdvise = rtNow + rtPeriodTime;
// reset the clock
m_pRefClock->AdviseTime(m_tStart, rtAdvise,
(unsigned long)m_hAdviseEvent, &dwAdviseToken);
}
}

if (com == CMD_RUN || com == CMD_PAUSE)
    com = GetRequest(); // throw command away
else if (com != CMD_STOP) {
    DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!")));
}
    } while (com != CMD_STOP);

    return S_FALSE;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrTCOut
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrTCOut::Constructor
//
// Create a default stream
//
//------------------------------------------
CVcrTCOut::CVcrTCOut(HRESULT *phr, CVcrFilter *pParent, LPCWSTR pName)
    : COutStream(NAME("VCR Timecode output pin"),phr, pParent, pName),
    m_pFilter(pParent)
{
    CAutoLock l(m_pFilter->pLock());

m_ctc = new CTimecode();
}

//------------------------------------------
//
// CVcrTCOut::Destructor
//
//------------------------------------------
CVcrTCOut::~CVcrTCOut(void)
{
    CAutoLock l(m_pFilter->pLock());

    delete m_ctc;

}

//------------------------------------------
//
// FillBuffer
//
// Stuffs the buffer with data
//
//------------------------------------------
HRESULT
CVcrTCOut::FillBuffer(IMediaSample *pms)
{
    BYTE*pData;
    long mode;
DWORDdwresult;

    TIMECODE_SAMPLE tcsTimecode ;

    pms->GetPointer(&pData);

//what time is it?
CRefTime rtStart  = m_rtSampleTime;// the current time is
//  the sample's start
    m_rtSampleTime += (LONG)(1000L*1001L)/30000L;// 29.97 fps for
//  now in 100 ns units
    pms->SetTime((REFERENCE_TIME*)&rtStart,
                 (REFERENCE_TIME*)&m_rtSampleTime);

if ( *(m_mt.Type()) == MEDIATYPE_Text)
            pms->SetActualDataLength(textbufsize);
else
            pms->SetActualDataLength(sizeof(TIMECODE_SAMPLE));

// should we update the display (only when in PLAY)?
m_pFilter->get_Mode(&mode);
if ( mode == ED_MODE_PLAY ) {
// get a new timecode
if ( (dwresult = m_pFilter->GetTimecode(&tcsTimecode)) )
tcsTimecode.timecode.dwFrames = -1;
if ( m_mtype == 0) {
// we need to output text - make the timecode displayable
m_ctc->ConvertTimecodeToString(&tcsTimecode, (TCHAR *)pData);
}
else {
// dummy out unused members
tcsTimecode.qwTick = m_rtSampleTime;
tcsTimecode.timecode.wFrameFract = 0;
tcsTimecode.dwUser = 0L;
tcsTimecode.dwFlags = 0x0000;// make it non-drop for now
memcpy((TCHAR *)pData, &tcsTimecode, sizeof(TIMECODE_SAMPLE));
}
}
    return NOERROR;
}

//------------------------------------------
//
// GetMediaType
//
//------------------------------------------
HRESULT
CVcrTCOut::GetMediaType(int iPosition, CMediaType *pmt)
{
    CAutoLock l(m_pFilter->pLock());

    if (iPosition<0) {
        return E_INVALIDARG;
    }
// we support 2 media types now
if (iPosition == 0) {
pmt->SetType(&MEDIATYPE_Text);
pmt->SetSubtype(&GUID_NULL);
}
else if (iPosition == 1) {
pmt->SetType(&MEDIATYPE_Timecode);
pmt->SetSubtype(&MEDIASUBTYPE_NULL);
}
else if (iPosition>1) {
        return VFW_S_NO_MORE_ITEMS;
    }

    // no temporal compression in both cases
    pmt->SetTemporalCompression(FALSE);

    return NOERROR;
}

//------------------------------------------
//
// CheckMediaType
//
// Returns E_INVALIDARG if the mediatype is not
//acceptable, S_OK if it is
//
//------------------------------------------
HRESULT
CVcrTCOut::CheckMediaType(const CMediaType *pMediaType)
{
    CAutoLock l(m_pFilter->pLock());

    if (*(pMediaType->Type()) != MEDIATYPE_Text &&
*(pMediaType->Type()) != MEDIATYPE_Timecode)//Text or timecode only
       return E_INVALIDARG;

    return S_OK;  // This format is acceptable.
}

//------------------------------------------
//
// DecideBufferSize
//
// This will always be called after the format has been sucessfully
// negotiated. So we have a look at m_mt to see what size media sample
// we agreed. Then we can ask for buffers of the correct size to contain them.
//
//------------------------------------------
HRESULT
CVcrTCOut::DecideBufferSize(IMemAllocator *pAlloc,
ALLOCATOR_PROPERTIES *pRequest)
{
    CAutoLock l(m_pFilter->pLock());

if ( *(m_mt.Type()) == MEDIATYPE_Text)
pRequest->cbBuffer = textbufsize;//the size we will request
else
pRequest->cbBuffer = sizeof(TIMECODE_SAMPLE);//the size we will request

pRequest->cBuffers = 5;           //the number of buffers to use.
    ALLOCATOR_PROPERTIES  Actual;
    pAlloc->SetProperties(pRequest, &Actual);

    if ((Actual.cbBuffer < pRequest->cbBuffer) ) // this allocator is unsuitable
        return E_FAIL;

    return NOERROR;
}

//------------------------------------------
//
// OnThreadCreate
//
// as we go active reset the stream time to zero
//
//------------------------------------------
HRESULT
CVcrTCOut::OnThreadCreate(void)
{
// save a local copy of the currently selected media type

// for this pin: 0 for text, 1 for timecode
if (*(m_mt.Type()) == MEDIATYPE_Text)
m_mtype = 0;
else
m_mtype = 1;

    m_rtSampleTime = 0;
tcsTimecode.timecode.dwFrames = 0L;

    return NOERROR;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrInputPin
// *
//
// Base class for hardware input pins
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrInputPin Constructor
//
//------------------------------------------
CVcrInputPin::CVcrInputPin(TCHAR *pObjectName, CVcrFilter *pFilter,
HRESULT * phr, LPCWSTR pName)
:
CBaseInputPin(pObjectName, pFilter, pFilter->pLock(), phr, pName),
CAMPhysicalPinInfo(phr, (TCHAR *)pName),
m_pFilter(pFilter)
{
   DbgLog((LOG_TRACE,1,TEXT("CVcrInputPin constructor")));
    ASSERT(pFilter);
*phr = m_pFilter->AddPin(this);
}

//------------------------------------------
//
//CVcrInputPin Destructor
//
//------------------------------------------
CVcrInputPin::~CVcrInputPin(void)
{
DbgLog((LOG_TRACE,1,TEXT("*Destroying an input pin")));
m_pFilter->RemovePin(this);
}

//------------------------------------------
//
// NonDelegatingQueryInterface
//
// Reveal our Interfaces
//------------------------------------------
STDMETHODIMP
CVcrInputPin::NonDelegatingQueryInterface(REFIID riid, void **ppv)
{
CAutoLock cObjectLock(&m_StateLock);

    if (riid == IID_IAMPhysicalPinInfo)
        return GetInterface((IAMPhysicalPinInfo *) this, ppv);
    else
        return CBaseInputPin::NonDelegatingQueryInterface(riid, ppv);
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrVideoCompInputPin
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrVideoCompInputPin Constructor
//
//------------------------------------------
CVcrVideoCompInputPin::CVcrVideoCompInputPin(TCHAR *pObjectName,
 CVcrFilter *pFilter,
HRESULT * phr, LPCWSTR pName)
:
CVcrInputPin(pObjectName, pFilter, phr, pName)
{

}

//------------------------------------------
//
//CVcrVideoCompInputPin Destructor
//
//------------------------------------------
CVcrVideoCompInputPin::~CVcrVideoCompInputPin(void)
{
}

//------------------------------------------
//
// GetPhysicalType
//
// Returns physical connection info
//
//------------------------------------------

HRESULT
CVcrVideoCompInputPin::GetPhysicalType( long *pType, LPOLESTR * ppszType)
{
    *pType = PhysConn_Video_Composite;
CheckPointer(ppszType, E_POINTER);
*ppszType = NULL;
// query the device
*ppszType = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * \
(1+lstrlenW((LPOLESTR)strPhysConn_Video_Composite)));
if (*ppszType !=NULL)
lstrcpyW(*ppszType, (LPOLESTR)strPhysConn_Video_Composite);

return S_OK;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrVideoSInputPin
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrVideoSInputPin Constructor
//
//------------------------------------------
CVcrVideoSInputPin::CVcrVideoSInputPin(TCHAR *pObjectName, CVcrFilter *pFilter,
HRESULT * phr, LPCWSTR pName)
:
CVcrInputPin(pObjectName, pFilter, phr, pName)
{

}

//------------------------------------------
//
//CVcrVideoSInputPin Destructor
//
//------------------------------------------
CVcrVideoSInputPin::~CVcrVideoSInputPin(void)
{

}

//------------------------------------------
//
// GetPhysicalType
//
// Returns physical connection info
//
//------------------------------------------

HRESULT
CVcrVideoSInputPin::GetPhysicalType( long *pType, LPOLESTR * ppszType)
{
    *pType = PhysConn_Video_SVideo;
CheckPointer(ppszType, E_POINTER);
*ppszType = NULL;
// query the device
*ppszType = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * \
(1+lstrlenW((LPOLESTR)strPhysConn_Video_SVideo)));
if (*ppszType !=NULL)
lstrcpyW(*ppszType, (LPOLESTR)strPhysConn_Video_SVideo);

return S_OK;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrVideoBlackInputPin
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrVideoBlackInputPin Constructor
//
//------------------------------------------
CVcrVideoBlackInputPin::CVcrVideoBlackInputPin(TCHAR *pObjectName,
   CVcrFilter *pFilter,
HRESULT * phr, LPCWSTR pName)
:
CVcrInputPin(pObjectName, pFilter, phr, pName)
{

}

//------------------------------------------
//
//CVcrVideoBlackInputPin Destructor
//
//------------------------------------------
CVcrVideoBlackInputPin::~CVcrVideoBlackInputPin(void)
{

}

//------------------------------------------
//
// GetPhysicalType
//
// Returns physical connection info
//
//------------------------------------------

HRESULT
CVcrVideoBlackInputPin::GetPhysicalType( long *pType, LPOLESTR * ppszType)
{
*pType = PhysConn_Video_Black;
CheckPointer(ppszType, E_POINTER);
*ppszType = NULL;
// query the device
*ppszType = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * \
(1+lstrlenW((LPOLESTR)strPhysConn_Video_Black)));
if (*ppszType !=NULL)
lstrcpyW(*ppszType, (LPOLESTR)strPhysConn_Video_Black);

    return S_OK;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrAudioLineInputPin
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrAudioLineInputPin Constructor
//
//------------------------------------------
CVcrAudioLineInputPin::CVcrAudioLineInputPin(TCHAR *pObjectName,
 CVcrFilter *pFilter,
HRESULT * phr, LPCWSTR pName)
:
CVcrInputPin(pObjectName, pFilter, phr, pName)
{

}

//------------------------------------------
//
//CVcrAudioLineInputPin Destructor
//
//------------------------------------------
CVcrAudioLineInputPin::~CVcrAudioLineInputPin(void)
{

}

//------------------------------------------
//
// GetPhysicalType
//
// Returns physical connection info
//
//------------------------------------------

HRESULT
CVcrAudioLineInputPin::GetPhysicalType( long *pType, LPOLESTR * ppszType)
{
*pType = PhysConn_Audio_Line;
CheckPointer(ppszType, E_POINTER);
*ppszType = NULL;
// query the device
*ppszType = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) *
(1+lstrlenW((LPOLESTR)strPhysConn_Audio_Line)));
if (*ppszType !=NULL)
lstrcpyW(*ppszType, (LPOLESTR)strPhysConn_Audio_Line);

return S_OK;
}

//------------------------------------------
//------------------------------------------
//------------------------------------------
// *
// * CVcrAudioMicInputPin
// *
//------------------------------------------
//------------------------------------------
//------------------------------------------

//------------------------------------------
//
// CVcrAudioMicInputPin Constructor
//
//------------------------------------------
CVcrAudioMicInputPin::CVcrAudioMicInputPin(TCHAR *pObjectName,
   CVcrFilter *pFilter,
HRESULT * phr,
LPCWSTR pName)
:
CVcrInputPin(pObjectName, pFilter, phr, pName)
{

}

//------------------------------------------
//
//CVcrAudioMicInputPin Destructor
//
//------------------------------------------
CVcrAudioMicInputPin::~CVcrAudioMicInputPin(void)
{

}

//------------------------------------------
//
// GetPhysicalType
//
// Returns physical connection info
//
//------------------------------------------

HRESULT
CVcrAudioMicInputPin::GetPhysicalType( long *pType, LPOLESTR * ppszType)
{
*pType = PhysConn_Audio_Mic;
CheckPointer(ppszType, E_POINTER);
*ppszType = NULL;
// query the device
*ppszType = (LPOLESTR)
QzTaskMemAlloc(sizeof(WCHAR) * \
(1+lstrlenW((LPOLESTR)strPhysConn_Audio_Mic)));
if (*ppszType !=NULL)
lstrcpyW(*ppszType, (LPOLESTR)strPhysConn_Audio_Mic);

    return S_OK;
}

//eof fvcrctrl.cpp


FVCRCTRL.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
// filename: fvcrctrl.h
//
// VCR Control filter - CBaseFilter Derived
//
// This is a prototype "VCR" filter.  Depending on the detected VCR,
// It has 5 or 6 pins - 2 or 3 Video inputs, 2 Audio inputs
// and one Timecode outout (a timecode input pin would be useless).
// It's a bit different than the standard source, transform,
// and rendering filters because it has both input and output
// pins with fairly unrelated behaviors.
//
// It implements the following External Device interfaces:
//
//IAMExtDevice
//IAMPhysicalPinInfo
//IAMExtTransport
//IAMTimecodeReader
//
// The filter also has a couple of property pages that put a simple
// user interface on many of the methods and properties of
// the supported interfaces.

class COutStream;// The class that will handle the timecode output pin
//*************************************************/
// CVcrFilter
//
class CVcrFilter : public CBaseFilter, public CAMExtDevice,
 public CAMExtTransport, public CAMTcr, public CPersistStream,
 public ISpecifyPropertyPages
{
friend class COutStream;// so they can lock
friend class CVcrTCOut;
public:

    static CUnknown *CreateInstance(LPUNKNOWN punk, HRESULT *phr);

// we replace the DECLARE_IUNKNOWN macro so we can resolve
// ambiguous references due to multiple CUnknowns
STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {
        return CBaseFilter::GetOwner()->QueryInterface(riid,ppv);
    };
    STDMETHODIMP_(ULONG) AddRef() {
        return CBaseFilter::GetOwner()->AddRef();
    };
    STDMETHODIMP_(ULONG) Release() {
        return CBaseFilter::GetOwner()->Release();
    };
HINSTANCELoadOLEAut32();

    //
    // --- CBaseFilter Overrides --
    //

    int GetPinCount();
    CBasePin * GetPin(int n);

// CPersistStream overrides
    HRESULT WriteToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);
    int SizeMax();
    STDMETHODIMP GetClassID(CLSID *pClsid);

// Utilities
HRESULT     AddPin(CBasePin *);
    HRESULT     RemovePin(CBasePin *);
CCritSec*pLock(void) { return &m_StateLock; }// provide our
//  critical section

// override state changes to allow derived Device Control filter
    // to control its own start/stop
    STDMETHODIMP Stop();
    STDMETHODIMP Pause();
    STDMETHODIMP Run(REFERENCE_TIME tStart);

// Basic COM - used here to reveal our property interface.
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

// --- ISpecifyPropertyPages ---
// return our property pages
    STDMETHODIMP GetPages(CAUUID * pPages);

private:

    CVcrFilter(TCHAR *tszName, LPUNKNOWN punk, CLSID clsid, HRESULT *phr);
~CVcrFilter();

int m_iPins;// The number of pins on this filter.
//  Updated by COutStream and CVcrInputPin
//  constructors & destructors.
protected:

    CBasePin **m_paStreams;// the pins on this filter.
CCritSec m_StateLock;// Lock this to serialize function accesses
//  to the filter state
};

//*************************************************/
// COutStream
//
// Use this class to manage a stream of data that comes
// from a derived pin.
// Uses a worker thread to put data on the pin.
//
class COutStream : public CAMThread, public CBaseOutputPin
{
public:

    COutStream(TCHAR *pObjectName,
                  HRESULT *phr,
                  CVcrFilter *pParent,
                  LPCWSTR pName);

    virtual ~COutStream(void);  // virtual destructor ensures derived class
//  destructors are called too.

// thread commands
    virtual enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT};
    HRESULT Init(void) { return CallWorker(CMD_INIT); }
    HRESULT Exit(void) { return CallWorker(CMD_EXIT); }
    HRESULT Run(void) { return CallWorker(CMD_RUN); }
    HRESULT Pause(void) { return CallWorker(CMD_PAUSE); }
    HRESULT Stop(void) { return CallWorker(CMD_STOP); }

protected:

    CVcrFilter *m_pFilter;// The parent of this stream
HANDLE m_hAdviseEvent;// for an advise event
IReferenceClock *m_pRefClock;// this graph's sync source

    // *
    // * Data Source
    // *
    // * The following three functions: FillBuffer, OnThreadCreate/Destroy, are
    // * called from within the ThreadProc. They are used in the creation of
    // * the media samples this pin will provide
    // *

    // Override this to provide the worker thread a means
    // of processing a buffer
    virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE;

    // Called as the thread is created/destroyed - use to perform
    // jobs such as start/stop streaming mode
    // If OnThreadCreate returns an error the thread will exit.
    virtual HRESULT OnThreadCreate(void) {return NOERROR;};
    virtual HRESULT OnThreadDestroy(void) {return NOERROR;};
    virtual HRESULT OnThreadStartPlay(void) {return NOERROR;};

    // *
    // * Worker Thread
    // *

    HRESULT Active(void);    // Starts up the worker thread
    HRESULT Inactive(void);  // Exits the worker thread.

    Command GetRequest(void) { return (Command) CAMThread::GetRequest(); }
    BOOL    CheckRequest(Command *pCom) {
return CAMThread::CheckRequest( (DWORD *) pCom); }

    // override these if you want to add thread commands
// (and the T/C stream will)
    virtual DWORD ThreadProc(void);  // the thread function
    virtual HRESULT DoBufferProcessingLoop(void);   // the loop executed
//  whilst running

    // *
    // * MEDIA_TYPE support
    // *

    // Override this - we support multiple media types
    virtual HRESULT CheckMediaType( const CMediaType *pMediaType) PURE;
};

//*************************************************/
// CVcrTCOut
//
// This filter has an output pin which can output timecode samples
// as either a binary sample or as displayable text.
//
class CVcrTCOut : public COutStream
{
private:

CVcrFilter *m_pFilter;// the external device filter that owns us
    CCritSec    m_cSharedState;     // use this to lock access to
//  m_rtSampleTime and m_pFilter which are
//  shared with the worker thread.
    CRefTime    m_rtSampleTime;// The time to be stamped on each sample
CTimecode*m_ctc;// need this for data conversions
static LONGLONG m_llLastTime;// the last time timecode was updated
int m_mtype;// 0 for text, 1 for Aux Data

public:

CVcrTCOut(
HRESULT *phr,
CVcrFilter *pParent,
LPCWSTR pPinName);

~CVcrTCOut();

BOOL ReadyToStop(void) {return FALSE;}

    // stuff a text buffer with the current format
    HRESULT FillBuffer(IMediaSample *pms);

    // ask for buffers of the size appropriate to the agreed media type.
    HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc,
ALLOCATOR_PROPERTIES *pRequest);

    // verify we can handle this format
    HRESULT CheckMediaType(const CMediaType *pMediaType);

// we handle multiple media types
    HRESULT GetMediaType(int iPosition, CMediaType *pmt);

    // resets the stream time to zero.
    HRESULT OnThreadCreate(void);
};
//*************************************************/
//
//Base Hardware Input Pin Class
//
// It might be desirable to add hardware output pins in a similar manner
// as the following input pins if, for example, an external device
// has switchable outputs.
//
class CVcrInputPin : public CBaseInputPin, public CAMPhysicalPinInfo
{
public:
    CVcrInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrInputPin();

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** pv);

    // we aren't supposed to connect to anybody
    HRESULT CheckMediaType(const CMediaType*) {
return E_FAIL;
    }

// CAMPhysicalPinInfo's method - the derived input pins must implement
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType){
return E_NOTIMPL;}

private:
    CVcrFilter * m_pFilter;// parent
CCritSec m_StateLock;// Lock this to serialize function accesses
};

//*************************************************/
//
//1st of 4 (or 5) input pins
//
class CVcrVideoCompInputPin : public CVcrInputPin
{
public:
    CVcrVideoCompInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrVideoCompInputPin();

// CAMPhysicalPinInfo's method - we must return our physical pin info
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType);
};

//*************************************************/
//
//2nd of 4 (or 5) input pins
//
class CVcrVideoSInputPin : public CVcrInputPin
{
public:
    CVcrVideoSInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrVideoSInputPin();

// CAMPhysicalPinInfo's method - we must return our physical pin info
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType);
};

//*************************************************/
//
//3rd of 4 (or 5) input pins
//
class CVcrAudioLineInputPin : public CVcrInputPin
{
public:
    CVcrAudioLineInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrAudioLineInputPin();

// CAMPhysicalPinInfo's method - we must return our physical pin info
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType);
};

//*************************************************/
//
//4th of 4 (or 5) input pins
//
class CVcrAudioMicInputPin : public CVcrInputPin
{
public:
    CVcrAudioMicInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrAudioMicInputPin();

// CAMPhysicalPinInfo's method - we must return our physical pin info
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType);
};

//*************************************************/
//
//Optional 5th input pin
//
class CVcrVideoBlackInputPin : public CVcrInputPin
{
public:
    CVcrVideoBlackInputPin(
        TCHAR *pObjectName,
        CVcrFilter *pFilter,
    HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVcrVideoBlackInputPin();

// CAMPhysicalPinInfo's method - we must return our physical pin info
STDMETHODIMP GetPhysicalType(long *pType, LPOLESTR * ppszType);
};

// eof fvcrctrl.h


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by vcrprop.rc
//
#define IDS_DEVCAP_CAN_RECORD           1
#define IDS_DEVCAP_CAN_RECORD_STROBE    2
#define IDS_DEVCAP_HAS_AUDIO            3
#define IDS_DEVCAP_HAS_VIDEO            4
#define IDS_DEVCAP_USES_FILES           5
#define IDS_DEVCAP_CAN_SAVE             6
#define IDS_DEVCAP_DEVICE_TYPE          7
#define IDS_DEVCAP_EXTDEV_ID            8
#define IDS_DEVCAP_TIMECODE_READ        9
#define IDS_DEVCAP_TIMECODE_WRITE       10
#define IDS_DEVCAP_CTLTRK_READ          11
#define IDS_DEVCAP_INDEX_READ           12
#define IDS_DEVCAP_PREROLL              13
#define IDS_DEVCAP_POSTROLL             14
#define IDS_DEVCAP_SYNCACC              15
#define IDS_DEVCAP_NORMAL_RATE          16
#define IDS_DEVCAP_CAN_PREVIEW          17
#define IDS_DEVCAP_CAN_MONITOR_SOURCES  18
#define IDS_DEVCAP_CAN_TEST             19
#define IDS_DEVCAP_VIDEO_INPUTS         20
#define IDS_DEVCAP_AUDIO_INPUTS         21
#define IDS_DEVCAP_NEEDS_CALIBRATING    22
#define IDS_DEVCAP_SEEK_TYPE            23
#define IDS_DEVTYPE_VCR                 24
#define IDS_DEVTYPE_LASERDISK           25
#define IDS_DEVTYPE_ATR                 26
#define IDS_DEVTYPE_DDR                 27
#define IDS_DEVTYPE_ROUTER              28
#define IDS_DEVTYPE_KEYER               29
#define IDS_DEVTYPE_MIXER_VIDEO         30
#define IDS_DEVTYPE_DVE                 31
#define IDS_DEVTYPE_WIPEGEN             32
#define IDS_DEVTYPE_MIXER_AUDIO         33
#define IDS_DEVTYPE_CG                  34
#define IDS_DEVTYPE_TBC                 35
#define IDS_DEVTYPE_TCG                 36
#define IDS_DEVTYPE_GPI                 37
#define IDS_DEVTYPE_JOYSTICK            38
#define IDS_DEVTYPE_KEYBOARD            39
#define IDS_SYNCACC_PRECISE             40
#define IDS_SYNCACC_FRAME               41
#define IDS_SYNCACC_ROUGH               42
#define IDS_RATE_24                     43
#define IDS_RATE_25                     44
#define IDS_RATE_2997                   45
#define IDS_RATE_30                     46
#define IDS_SEEK_PERFECT                47
#define IDS_SEEK_FAST                   48
#define IDS_SEEK_SLOW                   49
#define IDS_ON                          50
#define IDS_OFF                         51
#define IDS_STANDBY                     52
#define IDS_ACTIVE                      53
#define IDS_INACTIVE                    54
#define IDS_ALL                         55
#define IDS_EDSTAT_MODE                 56
#define IDS_EDSTAT_ERROR                57
#define IDS_EDSTAT_LOCAL                58
#define IDS_EDSTAT_RECORD_INHIBIT       59
#define IDS_EDSTAT_SERVO_LOCK           60
#define IDS_EDSTAT_MEDIA_PRESENT        61
#define IDS_EDSTAT_MEDIA_LENGTH         62
#define IDS_EDSTAT_MEDIA_SIZE           63
#define IDS_EDSTAT_MEDIA_TRKCNT         64
#define IDS_EDSTAT_MEDIA_TRKLEN         65
#define IDS_EDSTAT_MEDIA_SIDE           66
#define IDS_EDSTAT_MEDIA_TYPE           67
#define IDS_MODE_PLAY                   68
#define IDS_MODE_STOP                   69
#define IDS_MODE_FREEZE                 70
#define IDS_MODE_THAW                   71
#define IDS_MODE_FF                     72
#define IDS_MODE_REW                    73
#define IDS_MODE_RECORD                 74
#define IDS_MODE_RECORD_STROBE          75
#define IDS_MODE_STEP                   76
#define IDS_MODE_SHUTTLE                77
#define IDS_MEDIA_VHS                   78
#define IDS_MEDIA_SVHS                  79
#define IDS_MEDIA_HI8                   80
#define IDS_MEDIA_UMATIC                81
#define IDS_MEDIA_DVC                   82
#define IDS_MEDIA_1_INCH                83
#define IDS_MEDIA_D1                    84
#define IDS_MEDIA_D2                    85
#define IDS_MEDIA_D3                    86
#define IDS_MEDIA_D5                    87
#define IDS_MEDIA_DBETA                 88
#define IDS_MEDIA_BETA                  89
#define IDS_MEDIA_8MM                   90
#define IDS_MEDIA_DDR                   91
#define IDS_MEDIA_OTHER                 92
#define IDS_MEDIA_CLV                   93
#define IDS_MEDIA_CAV                   94
#define IDS_TRBASIC_TIME_FORMAT         95
#define IDS_TRBASIC_TIME_REFERENCE      96
#define IDS_TRBASIC_SUPERIMPOSE         97
#define IDS_TRBASIC_END_STOP_ACTION     98
#define IDS_TRBASIC_RECORD_FORMAT       99
#define IDS_TRBASIC_STEP_COUNT          100
#define IDS_TRBASIC_STEP_UNIT           101
#define IDS_TRBASIC_PREROLL             102
#define IDS_TRBASIC_POSTROLL            103
#define IDS_TRBASIC_EDIT_DELAY          104
#define IDS_TRBASIC_PLAYTC_DELAY        105
#define IDS_TRBASIC_RECTC_DELAY         106
#define IDS_TRBASIC_EDIT_FIELD          107
#define IDS_TRBASIC_FRAME_SERVO         108
#define IDS_TRBASIC_CF_SERVO            109
#define IDS_TRBASIC_SERVO_REF           110
#define IDS_TRBASIC_WARN_GL             111
#define IDS_TRBASIC_SET_TRACKING        112
#define IDS_TRBASIC_SET_FREEZE_TIMEOUT  113
#define IDS_TRBASIC_VOLUME_NAME         114
#define IDS_TRBASIC_BALLISTIC_1         115
#define IDS_TRBASIC_BALLISTIC_2         116
#define IDS_TRBASIC_BALLISTIC_3         117
#define IDS_TRBASIC_BALLISTIC_4         118
#define IDS_TRBASIC_BALLISTIC_5         119
#define IDS_TRBASIC_BALLISTIC_6         120
#define IDS_TRBASIC_BALLISTIC_7         121
#define IDS_TRBASIC_BALLISTIC_8         122
#define IDS_TRBASIC_BALLISTIC_9         123
#define IDS_TRBASIC_BALLISTIC_10        124
#define IDS_TRBASIC_BALLISTIC_11        125
#define IDS_TRBASIC_BALLISTIC_12        126
#define IDS_TRBASIC_BALLISTIC_13        127
#define IDS_TRBASIC_BALLISTIC_14        128
#define IDS_TRBASIC_BALLISTIC_15        129
#define IDS_TRBASIC_BALLISTIC_16        130
#define IDS_TRBASIC_BALLISTIC_17        131
#define IDS_TRBASIC_BALLISTIC_18        132
#define IDS_TRBASIC_BALLISTIC_19        133
#define IDS_TRBASIC_BALLISTIC_20        134
#define IDS_TRBASIC_SETCLOCK            135
#define IDS_TRBASIC_SET_COUNTER_FORMAT  136
#define IDS_TRBASIC_SET_COUNTER_VALUE   137
#define IDS_TRBASIC_SETTUNER_CH_UP      138
#define IDS_TRBASIC_SETTUNER_CH_DN      139
#define IDS_TRBASIC_SETTUNER_SK_UP      140
#define IDS_TRBASIC_SETTUNER_SK_DN      141
#define IDS_TRBASIC_SETTUNER_CH         142
#define IDS_TRBASIC_SETTUNER_NUM        143
#define IDS_TRBASIC_SETTIMER_EVENT      144
#define IDS_TRBASIC_SETTIMER_STARTDAY   145
#define IDS_TRBASIC_SETTIMER_STARTTIME  146
#define IDS_TRBASIC_SETTIMER_STOPDAY    147
#define IDS_TRBASIC_SETTIMER_STOPTIME   148
#define IDS_FORMAT_MILLISECONDS         149
#define IDS_FORMAT_FRAMES               150
#define IDS_FORMAT_SMPTE_30DROP         151
#define IDS_FORMAT_SMPTE_30NONDROP      152
#define IDS_FORMAT_SMPTE_25             153
#define IDS_FORMAT_SMPTE_24             154
#define IDS_FORMAT_HMSF                 155
#define IDS_FORMAT_TMSF                 156
#define IDS_TIMEREF_TIMECODE            157
#define IDS_TIMEREF_CONTROL_TRACK       158
#define IDS_TIMEREF_INDEX               159
#define IDS_RECORD_FORMAT_SP            160
#define IDS_RECORD_FORMAT_LP            161
#define IDS_RECORD_FORMAT_EP            162
#define IDS_STEP_FIELD                  163
#define IDS_STEP_FRAME                  164
#define IDS_STEP_3_2                    165
#define IDS_REF_EXTERNAL                166
#define IDS_REF_INPUT                   167
#define IDS_REF_INTERNAL                168
#define IDS_REF_AUTO                    169
#define IDS_TRACKING_PLUS               170
#define IDS_TRACKING_MINUS              171
#define IDS_TRACKING_RESET              172
#define IDS_TRVIDEO_SET_OUTPUT          173
#define IDS_TRVIDEO_SET_SOURCE          174
#define IDS_E2E                         175
#define IDS_PLAYBACK                    176
#define IDS_TRAUDIO_ENABLE_OUTPUT       178
#define IDS_TRAUDIO_ENABLE_RECORD       179
#define IDS_TRAUDIO_ENABLE_SELSYNC      180
#define IDS_TRAUDIO_SET_SOURCE          181
#define IDS_TRAUDIO_SET_MONITOR         182
#define IDS_AUDIO_ALL                   183
#define IDS_EDTCAP_EJECT                184
#define IDS_EDTCAP_BUMPPLAY             185
#define IDS_EDTCAP_PLAYB                186
#define IDS_EDTCAP_SETEE                187
#define IDS_EDTCAP_SETPB                188
#define IDS_EDTCAP_DELVIDIN             189
#define IDS_EDTCAP_DELVIDOUT            190
#define IDS_EDTCAP_DELAUDIN             191
#define IDS_EDTCAP_DELAUDOUT            192
#define IDS_EDTCAP_FVARMAX              193
#define IDS_EDTCAP_RVARMAX              194
#define IDS_EDTCAP_NUMATRK              195
#define IDS_EDTCAP_LTCTRK               196
#define IDS_EDTCAP_NEEDTBC              197
#define IDS_EDTCAP_NEEDCUE              198
#define IDS_EDTCAP_INSERT               199
#define IDS_EDTCAP_ASSM                 200
#define IDS_EDTCAP_FSTEP                201
#define IDS_EDTCAP_CLOCKINC             202
#define IDS_EDTCAP_DLENGTH              203
#define IDS_EDTCAP_FREEZE               204
#define IDS_EDTCAP_TUNER                205
#define IDS_EDTCAP_TIMER                206
#define IDS_EDTCAP_CLOCK                207
#define IDS_TRUE                        300
#define IDS_FALSE                       301
#define IDS_PROP_TITLE                  302
#define IDS_PROP_GENERAL                303
#define IDS_PROP_TRANS                  304
#define IDS_MODE_EDIT_CUE               305
#define IDD_DIALOG1                     501
#define IDC_DIALOG1                     501
#define IDD_DIALOG2                     502
#define IDD_DIALOG3                     503
#define IDC_DCDBLVAL                    1004
#define IDC_MS_SPINUP                   1005
#define IDC_MS_SD                       1006
#define IDC_MS_UL                       1007
#define IDC_DEVCAPS                     1008
#define IDC_DEVCAPS_TEXT                1009
#define IDC_DCTEXT                      1010
#define IDC_MSTEXT                      1011
#define IDC_EDITTXT                     1012
#define IDC_DIDTEXT                     1013
#define IDC_DCDVTEXT                    1014
#define IDC_DCDV                        1015
#define IDC_DCDPTEXT                    1016
#define IDC_DCPWR_ON                    1017
#define IDC_DCPWR_OFF                   1018
#define IDC_DCPWR_STBY                  1019
#define IDC_TRLOC_ON                    1020
#define IDC_TRLOCAL_OFF                 1021
#define IDC_TRSTATTXT                   1022
#define IDC_LBTRSTAT                    1024
#define IDC_TRBASICTXT                  1026
#define IDC_LBTRBASIC                   1027
#define IDC_TRBASIC_VALUE               1028
#define IDC_TRVIDEOTXT                  1029
#define IDC_LBTRVIDEO                   1030
#define IDC_TRVIDEO_VALUE               1031
#define IDC_LBTRVIDEO_VAL               1031
#define IDC_TRAUDIOTXT                  1032
#define IDC_LBTRAUDIO                   1033
#define IDC_TRAUDIO_VALUE               1034
#define IDC_LBTRAUDIO_VAL               1034
#define IDC_TRMODETXT                   1035
#define IDC_LBTRBASIC_VAL               1035
#define IDC_TRRATETXT                   1037
#define IDC_TRCHASETXT                  1039
#define IDC_TRCHASE_ON                  1040
#define IDC_TRCHASE_OFF                 1041
#define IDC_TRCHASE_VALUE               1042
#define IDC_TRCHOFFTXT                  1043
#define IDC_TRBUMPTXT                   1044
#define IDC_COMTXT                      1044
#define IDC_TRBUMP_ON                   1045
#define IDC_COM1                        1045
#define IDC_TRBUMP_OFF                  1046
#define IDC_COM2                        1046
#define IDC_TRCHASE_VALUE2              1047
#define IDC_COM3                        1047
#define IDC_TRBUMPDURTXT                1048
#define IDC_COM4                        1048
#define IDC_TRCLOG                      1049
#define IDC_COMSIM                      1049
#define IDC_TRCLOG_ON                   1050
#define IDC_TRSTAT_VALUE                1050
#define IDC_TRCLOG_OFF                  1051
#define IDC_TRMODE_VALUE                1052
#define IDC_TRRATE_VALUE                1053
#define IDC_EDVIDIN                     1054
#define IDC_TRCAP                       1054
#define IDC_EDAUDIN                     1055
#define IDC_VIDSEL                      1056
#define IDC_AUDSEL                      1057
#define IDC_TRSTATUS_VAL                1057
#define IDC_EBTRVID_VAL                 1058
#define IDC_EBTRBASIC_VAL               1059
#define IDC_TRCAP_TXT                   1059
#define IDC_TRCAP_VAL                   1060
#define IDC_DCDEVID                     1061
#define IDC_TRLOCALTXT                  1062
#define IDC_LINK                        1063

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        507
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1064
#define _APS_NEXT_SYMED_VALUE           505
#endif
#endif


TRPROP.CPP

//==========================================================================
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------
//
// trprop.cpp - IAMExtTransport Property Page for External Device (VCR) Filter
//

#include <streams.h>

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

#include "ctimecod.h"
#include "cdevcom.h"
#include "cvcrutil.h"
#include "vcruids.h"

#include "resource.h"
#include "fvcrctrl.h"
#include "trprop.h"

// see vcrprop.cpp for a description of how these tables work
typedef struct tagPARMVALUEINFO{
int ValueType;// ED_STR || ED_NUM
int StringID;
int DevCap;
} PARMVALUEINFO;

#define ED_STR 1
#define ED_NUM 2
#define ED_VAL 3
#define ED_DBL 4

static PARMVALUEINFO ModeTable[] = {
{ ED_STR, IDS_EDSTAT_MODE, ED_MODE }
};
const int MaxModeParm = sizeof(ModeTable) / sizeof(ModeTable[0]);

// this table is for matching modes to the list box.  We comment
// out the modes not supported by the sample machine
static long ModeValueTable[][2] = {
//{ IDS_MODE_EDIT_CUE, ED_MODE_EDIT_CUE },
{ IDS_MODE_FF, ED_MODE_FF },
{ IDS_MODE_FREEZE, ED_MODE_FREEZE },
{ IDS_MODE_PLAY, ED_MODE_PLAY },
{ IDS_MODE_RECORD, ED_MODE_RECORD },
//{ IDS_MODE_RECORD_STROBE, ED_MODE_RECORD_STROBE },
{ IDS_MODE_REW, ED_MODE_REW },
//{ IDS_MODE_SHUTTLE, ED_MODE_SHUTTLE },
//{ IDS_MODE_STEP, ED_MODE_STEP },
{ IDS_MODE_STOP, ED_MODE_STOP },
{ IDS_MODE_THAW, ED_MODE_THAW }
};
const int MaxModeValue = sizeof(ModeValueTable)/(sizeof(ModeValueTable[0][0]) +
   sizeof(ModeValueTable[0][1]) );

// *
// * CExtTranProperties - A few selected properties in IAMExtTransport
// *

//---------------------------------------------------------
//
// CreateInstance
//
//---------------------------------------------------------
CUnknown *CExtTransProperties::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
    CUnknown *punk = new CExtTransProperties(lpunk, phr);
    if (punk == NULL) {
*phr = E_OUTOFMEMORY;
    }

    return punk;
}

//---------------------------------------------------------
//
// CExtTransProperties::Constructor
//
//---------------------------------------------------------
CExtTransProperties::CExtTransProperties(LPUNKNOWN lpunk, HRESULT *phr)
    : CBasePropertyPage(NAME("VCR Transport Property Page"),lpunk,
        IDD_DIALOG2, IDS_PROP_TRANS),
m_pExtDevice(NULL),
m_pExtTransport(NULL),
    m_bIsInitialized(FALSE),
m_bSetHourGlass(FALSE)
{

}
//---------------------------------------------------------
//
// Message handler
//
//---------------------------------------------------------
BOOL CExtTransProperties::OnReceiveMessage(HWND hwnd,
                                        UINT uMsg,
                                        WPARAM wParam,
                                        LPARAM lParam)
{
// need a pointer to this object
CExtTransProperties *pThis =
(CExtTransProperties *) GetWindowLong(m_Dlg, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
OnInitDialog();
break;
        }

        case WM_COMMAND:
        {
            if (m_bIsInitialized) {
if (HIWORD(wParam) == LBN_SELCHANGE)
                SetDirty();
  OnCommand( (int) LOWORD( wParam ), (int) HIWORD( wParam ) );
}
break;
        }
}
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}
//---------------------------------------------------------
//
// Property page initialization
//
//---------------------------------------------------------
HRESULT CExtTransProperties::OnConnect(IUnknown *pUnknown)
{
// Get the objects on which properties we want to set
    ASSERT(m_pExtDevice == NULL);

HRESULT hr = pUnknown->QueryInterface( IID_IAMExtDevice,
(void **) &m_pExtDevice );
if( FAILED( hr ) )
    return hr;
ASSERT( m_pExtDevice != NULL );
ASSERT(m_pExtTransport == NULL);
hr = pUnknown->QueryInterface( IID_IAMExtTransport,
(void **) &m_pExtTransport );
if( FAILED( hr ) )
    return hr;
ASSERT( m_pExtTransport != NULL );
    m_bIsInitialized = FALSE ;
    return NOERROR;
}
//---------------------------------------------------------
//
// Property page shutdown
//
//---------------------------------------------------------
HRESULT CExtTransProperties::OnDisconnect()
{
    if (m_pExtDevice == NULL){
        return E_UNEXPECTED;
    }
m_pExtDevice->Release();
m_pExtDevice = NULL;

if (m_pExtTransport == NULL){
        return E_UNEXPECTED;
    }

m_pExtTransport->Release();
m_pExtTransport = NULL;

    return NOERROR;
}
//---------------------------------------------------------
//
// Property page activation
//
//---------------------------------------------------------
HRESULT CExtTransProperties::OnActivate()
{
    m_bIsInitialized = TRUE;
    return NOERROR;
}
//---------------------------------------------------------
//
// The real initialization work is done here
//
//---------------------------------------------------------
BOOL CExtTransProperties::OnInitDialog( void )
{
int i;
TCHAR buf[64];

    InitCommonControls();

// get the window handle
m_hwndModeList = GetDlgItem(m_Dlg, IDC_TRMODE_VALUE);
ASSERT(m_hwndModeList);

SendMessage(m_hwndModeList, WM_SETREDRAW, TRUE, 0L);
// fill the mode list box
for (i = 0; i < MaxModeValue; i++) {
LoadString(g_hInst, ModeValueTable[i][0], (LPSTR)buf, 64 );
    SendMessage(m_hwndModeList, LB_ADDSTRING, 0, (LPARAM)(LPSTR)buf);
    }

// set media state and local control radio buttons, and mode listbox
UpdateControls();

m_hHourGlass = LoadCursor(NULL, IDC_WAIT);
m_hCurrentCursor = GetCursor();
    return (LRESULT) 1;
}
//---------------------------------------------------------
//
// Handle the property page commands
//
//---------------------------------------------------------
BOOL CExtTransProperties::OnCommand( int iButton, int iNotify )
{
CExtTransProperties *pThis =
(CExtTransProperties *) GetWindowLong(m_Dlg, DWL_USER);
    ASSERT(pThis);
    switch( iButton ){
case IDC_MS_SPINUP:
pThis->m_CurMediaState = ED_MEDIA_SPIN_UP;
break;
case IDC_MS_SD:
pThis->m_CurMediaState = ED_MEDIA_SPIN_DOWN;
break;
case IDC_MS_UL:
pThis->m_CurMediaState = ED_MEDIA_UNLOAD;
pThis->m_bSetHourGlass = TRUE;
break;
case IDC_TRLOC_ON:
pThis->m_CurLocalState = OATRUE;
break;
case IDC_TRLOCAL_OFF:
pThis->m_CurLocalState = OAFALSE;
break;
}

    SetDirty();
    return (LRESULT) 1;
}
//---------------------------------------------------------
//
//Apply user-requested changes
//
//---------------------------------------------------------
HRESULT CExtTransProperties::OnApplyChanges()
{
int temp;

// we might want to set an hourglass cursor here

if (m_bSetHourGlass) {
SetCursor(m_hHourGlass);
m_pExtTransport->put_MediaState(m_CurMediaState);
SetCursor(m_hCurrentCursor);
}
m_pExtTransport->put_LocalControl(m_CurLocalState);

temp = SendMessage(m_hwndModeList, LB_GETCURSEL, 0, 0L);
if ( temp != m_CurModeSel ) {
m_CurModeSel = temp;
m_pExtTransport->put_Mode(ModeValueTable[m_CurModeSel][1]);
}

UpdateControls();
return(NOERROR);

}

//---------------------------------------------------------
//
// Sets m_bDirty and notifies the property page site of the change
//
//---------------------------------------------------------
void CExtTransProperties::SetDirty()
{
    m_bDirty = TRUE;
    if (m_pPageSite)
    {
        m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
    }
}

//---------------------------------------------------------
//
// UpdateControls - sets non-listbox controls to current values
//
//---------------------------------------------------------
void CExtTransProperties::UpdateControls()
{
int temp;

m_pExtTransport->get_MediaState(&m_CurMediaState);
switch (m_CurMediaState) {
case ED_MEDIA_SPIN_UP:
temp = IDC_MS_SPINUP;
break;
case ED_MEDIA_SPIN_DOWN:
temp = IDC_MS_SD;
break;
case ED_MEDIA_UNLOAD:
temp = IDC_MS_UL;
break;
};
CheckRadioButton(m_Dlg, IDC_MS_SPINUP, IDC_MS_UL, temp);

m_pExtTransport->get_LocalControl(&m_CurLocalState);
switch (m_CurLocalState) {
case OATRUE:
temp = IDC_TRLOC_ON;
break;
case OAFALSE:
temp = IDC_TRLOCAL_OFF;
break;
}
CheckRadioButton(m_Dlg, IDC_TRLOC_ON, IDC_TRLOCAL_OFF, temp);

m_pExtTransport->get_Mode(&m_CurMode);
// convert to listbox index
m_CurModeSel = FindModeIndex(m_CurMode);
SendMessage(m_hwndModeList, LB_SETCURSEL, m_CurModeSel, 0L);

return;
}

//---------------------------------------------------------
//
// FindModeIndex - given a mode, get its index into the
// string/mode table
//
//---------------------------------------------------------
intCExtTransProperties::FindModeIndex(long Value){

int i;

for (i = 0; i <MaxModeValue; i++) {
if ( ModeValueTable[i][1] == Value )
return i;// found it
}
return -1;
}

// eof trprop.cpp


TRPROP.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
// filename: trprop.h
//
//IAMExtTransport Property Page for External Device (VCR)Filter
//

#ifndef __TRPROP__
#define __TRPROP__

class CExtTransProperties : public CBasePropertyPage
{
public:

    static CUnknown *CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

protected:

// CBasePropertyPage overrides
    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate () ;
    HRESULT OnApplyChanges();

private:

    CExtTransProperties(LPUNKNOWN lpunk, HRESULT *phr);

    void    InitPropertiesDialog(HWND hwndParent);
intFindModeIndex(long Value);
voidSetDirty();

    BOOLOnInitDialog( void );
    BOOLOnCommand( int iButton, int iNotify );
voidUpdateControls(void);

HWNDm_hwndModeList;// Handle of transport mode value listbox
longm_CurMediaState;// what's the media doing?
longm_CurLocalState;// online or offline?
longm_CurModeSel;// current transport mode listbox selection
longm_CurMode;// current transport mode
BOOLm_bIsInitialized;// Will be false while we set init
//  values in Dlg
BOOLm_bSetHourGlass;// TRUE if an EJECT will happen
HCURSORm_hHourGlass;// Handle of the hourglass cursor
HCURSORm_hCurrentCursor;// Handle of the non-hourglass cursor

IAMExtTransport*m_pExtTransport;// pointers to the IAMExtTransport and
IAMExtDevice*m_pExtDevice;// IAMExtDevice interfaces
};

#endif// __TRPROP__
// eof trprop.h


VCRPROP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
// vcrprop.cpp - IAMExtDevice Property Page for VCR Control Filter
//

#include <streams.h>

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <olectl.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

#include "ctimecod.h"
#include "cdevcom.h"
#include "cvcrutil.h"
#include "vcruids.h"

#include "resource.h"
#include "fvcrctrl.h"
#include "vcrprop.h"

// initialize some data for device capabilities.  Each entry has 3 data items
//the first item describes whether the capability value is text or numerical,
//  (we know they are all returned in "Value" parm of the capability method).
//The 2nd is the string ID, and the last is the parameter itself
typedef struct tagDEVCAPINFO{
int ValueType;// ED_STR || ED_NUM
int StringID;
int DevCap;
} DEVCAPINFO;

#define ED_STR 1
#define ED_NUM 2
#define ED_VAL 3
#define ED_DBL 4

static DEVCAPINFO DevCapTable[] = {
{ ED_STR, IDS_DEVCAP_CAN_RECORD, ED_DEVCAP_CAN_RECORD },
{ ED_STR, IDS_DEVCAP_CAN_RECORD_STROBE, ED_DEVCAP_CAN_RECORD_STROBE },
{ ED_STR, IDS_DEVCAP_HAS_AUDIO, ED_DEVCAP_HAS_AUDIO },
{ ED_STR, IDS_DEVCAP_HAS_VIDEO, ED_DEVCAP_HAS_VIDEO },
{ ED_STR, IDS_DEVCAP_USES_FILES, ED_DEVCAP_USES_FILES },
{ ED_STR, IDS_DEVCAP_CAN_SAVE, ED_DEVCAP_CAN_SAVE },
{ ED_STR, IDS_DEVCAP_DEVICE_TYPE, ED_DEVCAP_DEVICE_TYPE },
{ ED_STR, IDS_DEVCAP_TIMECODE_READ, ED_DEVCAP_TIMECODE_READ },
{ ED_STR, IDS_DEVCAP_TIMECODE_WRITE, ED_DEVCAP_TIMECODE_WRITE },
{ ED_STR, IDS_DEVCAP_CTLTRK_READ, ED_DEVCAP_CTLTRK_READ },
{ ED_STR, IDS_DEVCAP_INDEX_READ, ED_DEVCAP_INDEX_READ },
{ ED_NUM, IDS_DEVCAP_PREROLL, ED_DEVCAP_PREROLL },
{ ED_NUM, IDS_DEVCAP_POSTROLL, ED_DEVCAP_POSTROLL },
{ ED_STR, IDS_DEVCAP_SYNCACC, ED_DEVCAP_SYNC_ACCURACY },
{ ED_STR, IDS_DEVCAP_NORMAL_RATE, ED_DEVCAP_NORMAL_RATE },
{ ED_STR, IDS_DEVCAP_CAN_PREVIEW, ED_DEVCAP_CAN_PREVIEW },
{ ED_STR, IDS_DEVCAP_CAN_MONITOR_SOURCES, ED_DEVCAP_CAN_MONITOR_SOURCES },
{ ED_STR, IDS_DEVCAP_CAN_TEST, ED_DEVCAP_CAN_TEST },
{ ED_STR, IDS_DEVCAP_VIDEO_INPUTS, ED_DEVCAP_VIDEO_INPUTS },
{ ED_STR, IDS_DEVCAP_AUDIO_INPUTS, ED_DEVCAP_AUDIO_INPUTS },
{ ED_STR, IDS_DEVCAP_NEEDS_CALIBRATING, ED_DEVCAP_NEEDS_CALIBRATING },
{ ED_STR, IDS_DEVCAP_SEEK_TYPE, ED_DEVCAP_SEEK_TYPE },
};
const int MaxDevCaps = sizeof(DevCapTable) / sizeof(DevCapTable[0]);

// this table is for getting the string ID's of the responses
static long DevValueTable[][2] = {
{ IDS_DEVTYPE_VCR,         ED_DEVTYPE_VCR },
{ IDS_DEVTYPE_LASERDISK,   ED_DEVTYPE_LASERDISK },
{ IDS_DEVTYPE_ATR,         ED_DEVTYPE_ATR },
{ IDS_DEVTYPE_DDR,         ED_DEVTYPE_DDR },
{ IDS_DEVTYPE_ROUTER,      ED_DEVTYPE_ROUTER },
{ IDS_DEVTYPE_KEYER,       ED_DEVTYPE_KEYER },
{ IDS_DEVTYPE_MIXER_VIDEO, ED_DEVTYPE_MIXER_VIDEO },
{ IDS_DEVTYPE_DVE,         ED_DEVTYPE_DVE },
{ IDS_DEVTYPE_WIPEGEN,     ED_DEVTYPE_WIPEGEN },
{ IDS_DEVTYPE_MIXER_AUDIO, ED_DEVTYPE_MIXER_AUDIO },
{ IDS_DEVTYPE_CG,          ED_DEVTYPE_CG },
{ IDS_DEVTYPE_TBC,         ED_DEVTYPE_TBC },
{ IDS_DEVTYPE_TCG,         ED_DEVTYPE_TCG },
{ IDS_DEVTYPE_GPI,         ED_DEVTYPE_GPI },
{ IDS_DEVTYPE_JOYSTICK,    ED_DEVTYPE_JOYSTICK },
{ IDS_DEVTYPE_KEYBOARD,    ED_DEVTYPE_KEYBOARD},
{ IDS_SYNCACC_PRECISE, ED_SYNCACC_PRECISE},
{ IDS_SYNCACC_FRAME,   ED_SYNCACC_FRAME },
{ IDS_SYNCACC_ROUGH,   ED_SYNCACC_ROUGH },
{ IDS_RATE_24,         ED_RATE_24 },
{ IDS_RATE_25,         ED_RATE_25 },
{ IDS_RATE_2997,       ED_RATE_2997 },
{ IDS_RATE_30,         ED_RATE_30 },
{ IDS_SEEK_PERFECT,    ED_SEEK_PERFECT},
{ IDS_SEEK_FAST,       ED_SEEK_FAST },
{ IDS_SEEK_SLOW,       ED_SEEK_SLOW },
{ IDS_ON,              ED_POWER_ON},
{ IDS_OFF,             ED_POWER_OFF },
{ IDS_STANDBY,         ED_POWER_STANDBY },
{ IDS_ACTIVE,          ED_ACTIVE },
{ IDS_INACTIVE,        ED_INACTIVE},
{ IDS_ALL,             ED_ALL },
{ IDS_TRUE,OATRUE },
{ IDS_FALSE,OAFALSE }
};
const int MaxDevValue = sizeof(DevValueTable) / (sizeof(DevValueTable[0][0]) +
 sizeof(DevValueTable[0][1]));

static UINT CBasePropertyPages = 1;
static const CLSID *pPropertyPageClsids[] =
{
    &CLSID_VCRControlPropertyPage
};
static const dwTimeout = 1000L;

// *
// * CVcrProperties - Properties in IAMExtDevice
// *

//---------------------------------------------------------
//
// CreateInstance
//
//---------------------------------------------------------
CUnknown *CVcrProperties::CreateInstance( LPUNKNOWN punk, HRESULT *phr )
{
    CVcrProperties *pNewObject
        = new CVcrProperties( punk, phr);

    if( pNewObject == NULL )
        *phr = E_OUTOFMEMORY;

    return pNewObject;
}
//---------------------------------------------------------
//
// CVcrProperties::Constructor
//
//---------------------------------------------------------
CVcrProperties::CVcrProperties( LPUNKNOWN pUnk, HRESULT *phr)
    : CBasePropertyPage(NAME("VCR Property Page"),pUnk,
        IDD_DIALOG1, IDS_PROP_GENERAL),
m_pExtDevice(NULL),
m_pExtTransport(NULL),
    m_bIsInitialized(FALSE),
m_hwndCapList(NULL),
m_hwndCapValue(NULL),
m_hwndVidInList(NULL),
m_hwndAudInList(NULL),
m_hPortButton1(NULL),
m_hPortButton2(NULL),
m_hPortButton3(NULL),
m_hPortButton4(NULL),
m_hPortButton5(NULL)
{

}

//---------------------------------------------------------
//
// Message handler
//
//---------------------------------------------------------
BOOL CVcrProperties::OnReceiveMessage(HWND hwnd,
                                        UINT uMsg,
                                        WPARAM wParam,
                                        LPARAM lParam)
{
HRESULT hr;
FILTER_STATE fsState;

// need a pointer to this object
CVcrProperties *pThis = (CVcrProperties *) GetWindowLong(m_Dlg, DWL_USER);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
OnInitDialog();
break;
        }

        case WM_COMMAND:
        {
if (m_bIsInitialized) {
OnCommand( (int) LOWORD( wParam ), (int) HIWORD( wParam ) );
break;
}
        }
default:
if (m_bIsInitialized) {
OnCapListNotification();
// test graph state and enable/disable port selection
//controls as appropriate
hr = m_pFilter->GetState(dwTimeout, &fsState);
if ( fsState != State_Stopped)
EnablePortSelection(FALSE);
else
EnablePortSelection(TRUE);
}
    }
    return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}

//---------------------------------------------------------
//
// Property page initialization
//
//---------------------------------------------------------
HRESULT CVcrProperties::OnConnect(IUnknown *pUnknown)
{
// Get the objects on which properties we want to set
    ASSERT(m_pExtDevice == NULL);

HRESULT hr = pUnknown->QueryInterface( IID_IAMExtDevice,
(void **) &m_pExtDevice );
if( FAILED( hr ) )
    return hr;
ASSERT( m_pExtDevice != NULL );
ASSERT(m_pExtTransport == NULL);

hr = pUnknown->QueryInterface( IID_IAMExtTransport,
(void **) &m_pExtTransport );
if( FAILED( hr ) )
    return hr;
ASSERT( m_pExtTransport != NULL );

hr = pUnknown->QueryInterface( IID_IBaseFilter, (void **) &m_pFilter );
if( FAILED( hr ) )
    return hr;
ASSERT( m_pFilter != NULL );

m_bIsInitialized = FALSE ;
    return NOERROR;
}
//---------------------------------------------------------
//
// Property page shutdown
//
//---------------------------------------------------------
HRESULT CVcrProperties::OnDisconnect()
{
    if (m_pExtDevice == NULL)
return E_UNEXPECTED;

m_pExtDevice->Release();
m_pExtDevice = NULL;

if (m_pExtTransport == NULL)
        return E_UNEXPECTED;

m_pExtTransport->Release();
m_pExtTransport = NULL;

if (m_pFilter == NULL)
        return E_UNEXPECTED;

m_pFilter->Release();
m_pFilter = NULL;
    return NOERROR;
}
//---------------------------------------------------------
//
// Property page activation
//
//---------------------------------------------------------
HRESULT CVcrProperties::OnActivate()
{
    m_bIsInitialized = TRUE;
    return NOERROR;
}
//---------------------------------------------------------
//
// The real initialization work is done here
//
//---------------------------------------------------------
BOOL CVcrProperties::OnInitDialog( void )
{
int i;
TCHAR buf[64];
int temp;
IPin *pPin;
IAMPhysicalPinInfo *pIAMPhys;
IEnumPins *pEnumPins;
ULONG cFetched;
LPOLESTR pName = NULL;
PIN_INFO PinInfo;
long Type;
HRESULT hr;
DWORD dwTimeout;
FILTER_STATE fsState;

    InitCommonControls();

// get the window and control handles
m_hwndCapList = GetDlgItem(m_Dlg, IDC_DEVCAPS);
ASSERT(m_hwndCapList);
m_hwndCapValue = GetDlgItem(m_Dlg, IDC_DCTEXT);
ASSERT(m_hwndCapValue);
m_hwndVidInList = GetDlgItem(m_Dlg, IDC_VIDSEL);
ASSERT(m_hwndVidInList);
m_hwndAudInList = GetDlgItem(m_Dlg, IDC_AUDSEL);
ASSERT(m_hwndAudInList);
m_hPortButton1 = GetDlgItem(m_Dlg, IDC_COM1);
ASSERT(m_hPortButton1);
m_hPortButton2 = GetDlgItem(m_Dlg, IDC_COM2);
ASSERT(m_hPortButton2);
m_hPortButton3 = GetDlgItem(m_Dlg, IDC_COM3);
ASSERT(m_hPortButton3);
m_hPortButton4 = GetDlgItem(m_Dlg, IDC_COM4);
ASSERT(m_hPortButton4);
m_hPortButton5 = GetDlgItem(m_Dlg, IDC_COMSIM);
ASSERT(m_hPortButton5);

// Decide if Device Port selection should be enabled or not
dwTimeout = 1000L;
hr = m_pFilter->GetState(dwTimeout, &fsState);
if ( fsState != State_Stopped)
EnablePortSelection(FALSE);
else
EnablePortSelection(TRUE);

temp = SendMessage(m_hwndCapList, WM_SETREDRAW, TRUE, 0L);
// fill the device cap list box
for (i = 0; i < MaxDevCaps; i++) {
temp = LoadString(g_hInst, DevCapTable[i].StringID, (LPSTR)buf, 64 );
    temp = SendMessage(m_hwndCapList, LB_ADDSTRING, 0, (LPARAM)(LPSTR)buf);
    }

// put the displayed capability value up
m_CurCapSel = SendMessage(m_hwndCapList, LB_GETTOPINDEX, 0, 0L);
ShowCapabilityValue(m_CurCapSel, m_hwndCapValue);

//put up the device ID
m_pExtDevice->get_ExternalDeviceID( &pName );
temp = SetDlgItemText(m_Dlg, IDC_DCDEVID, (LPCTSTR)pName);
QzTaskMemFree(pName);
pName = NULL;

//put up the device version
m_pExtDevice->get_ExternalDeviceVersion( &pName );
temp = SetDlgItemText(m_Dlg, IDC_DCDV, (LPCTSTR)pName);
pName = NULL;

// are we linked to the graph's controls or not?
m_pExtTransport->GetStatus(ED_LINK_MODE, &m_bLink);

// set power status, comm port radio buttons, etc.
UpdateControls();

// do the video input list
temp = SendMessage(m_hwndVidInList, WM_SETREDRAW, TRUE, 0L);
temp = SendMessage(m_hwndAudInList, WM_SETREDRAW, TRUE, 0L);
// use the filter's pin enumeration interface for this

m_pFilter->EnumPins( &pEnumPins );

do {// do 1 pin at a time
pEnumPins->Next( 1, &pPin, &cFetched );
if ( cFetched > 0 ) {
// we only deal with input pins here
pPin->QueryPinInfo( &PinInfo );
if ( PinInfo.dir == PINDIR_INPUT ) {
// get our IAMPhysicalPinInfo
hr = pPin->QueryInterface( IID_IAMPhysicalPinInfo,
(void **) &pIAMPhys );
if( FAILED( hr ) )
return 0;
ASSERT( pIAMPhys != NULL );
// is it video pin?
pIAMPhys->GetPhysicalType( &Type, &pName );
if (Type < PhysConn_Audio_Tuner )
// yup - put up the name in the video input list box
temp = SendMessage(m_hwndVidInList, LB_ADDSTRING, 0,
(LPARAM)(LPSTR)pName);
else
temp = SendMessage(m_hwndAudInList, LB_ADDSTRING, 0,
(LPARAM)(LPSTR)pName);
QzTaskMemFree(pName);
pName = NULL;
}
// release the pin whose info we just requested and the
// pin we just enumerated
QueryPinInfoReleaseFilter(PinInfo);
pPin->Release();
}
else// no more pins
break;
} while (TRUE);

// lose the enumerator
pEnumPins->Release();

    return (LRESULT) 1;
}
//---------------------------------------------------------
//
// Handle the property page commands
//
//---------------------------------------------------------
BOOL CVcrProperties::OnCommand( int iButton, int iNotify )
{
CVcrProperties *pThis = (CVcrProperties *) GetWindowLong(m_Dlg, DWL_USER);
    ASSERT(pThis);
    switch( iButton ){
case IDC_COM1:
pThis->m_CurDevPort = DEV_PORT_COM1;
break;
case IDC_COM2:
pThis->m_CurDevPort = DEV_PORT_COM2;
break;
case IDC_COM3:
pThis->m_CurDevPort = DEV_PORT_COM3;
break;
case IDC_COM4:
pThis->m_CurDevPort = DEV_PORT_COM4;
break;
case IDC_COMSIM:
pThis->m_CurDevPort = DEV_PORT_SIM;
break;
case IDC_DCPWR_ON:
pThis->m_CurPowerMode = ED_POWER_ON;
break;
case IDC_DCPWR_OFF:
pThis->m_CurPowerMode = ED_POWER_OFF;
break;
case IDC_DCPWR_STBY:
pThis->m_CurPowerMode = ED_POWER_STANDBY;
break;
case IDC_LINK:
if (IsDlgButtonChecked(m_Dlg, IDC_LINK))
pThis->m_bLink = OATRUE;
else
pThis->m_bLink = OAFALSE;
break;
}

    SetDirty();
    return (LRESULT) 1;
}
//---------------------------------------------------------
//
//Apply user-requested changes
//
//---------------------------------------------------------
HRESULT CVcrProperties::OnApplyChanges()
{
if (m_bDevPortEnabled)
m_pExtDevice->put_DevicePort(m_CurDevPort);
m_pExtDevice->put_DevicePower(m_CurPowerMode);
m_pExtTransport->SetTransportVideoParameters(ED_TRANSVIDEO_SET_SOURCE,
m_CurVidInSel);
    m_pExtTransport->SetTransportAudioParameters(ED_TRANSAUDIO_SET_SOURCE,
m_CurAudInSel);
UpdateControls();
return(NOERROR);

}
//---------------------------------------------------------
//
//
// Sets m_bDirty and notifies the property page site of the change
//
//---------------------------------------------------------
void CVcrProperties::SetDirty()
{
    m_bDirty = TRUE;
    if (m_pPageSite)
        m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
}

//---------------------------------------------------------
//
// UpdateControls - sets non-listbox controls to current values
//
//---------------------------------------------------------
void CVcrProperties::UpdateControls()
{
int temp;
LPOLESTR pName = NULL;

m_pExtDevice->get_DevicePower(&m_CurPowerMode);
switch (m_CurPowerMode) {
case ED_POWER_ON:
temp = IDC_DCPWR_ON;
break;
case ED_POWER_OFF:
temp = IDC_DCPWR_OFF;
break;
case ED_POWER_STANDBY:
temp = IDC_DCPWR_STBY;
break;
};
CheckRadioButton(m_Dlg, IDC_DCPWR_ON, IDC_DCPWR_STBY, temp);
m_pExtDevice->get_DevicePort(&m_CurDevPort);
switch (m_CurDevPort) {
case DEV_PORT_COM1:
temp = IDC_COM1;
break;
case DEV_PORT_COM2:
temp = IDC_COM2;
break;
case DEV_PORT_COM3:
temp = IDC_COM3;
break;
case DEV_PORT_COM4:
temp = IDC_COM4;
break;
case DEV_PORT_SIM:
temp = IDC_COMSIM;
break;
default:
// can add support for ARTI or VLAN here
temp = NULL;
}
CheckRadioButton(m_Dlg, IDC_COM1, IDC_COMSIM, temp);

if (m_bLink == OATRUE) {
m_pExtTransport->put_Mode(ED_MODE_LINK_ON);
CheckDlgButton(m_Dlg, IDC_LINK, TRUE);
}
else {
m_pExtTransport->put_Mode(ED_MODE_LINK_OFF);
CheckDlgButton(m_Dlg, IDC_LINK, FALSE);
}

//put up the device ID
m_pExtDevice->get_ExternalDeviceID( &pName );
temp = SetDlgItemText(m_Dlg, IDC_DCDEVID, (LPCTSTR)pName);
QzTaskMemFree(pName);
pName = NULL;

}
//---------------------------------------------------------
//
// OnCapListNotification
//
// Handle the notification messages from the Capabilities control
//
//---------------------------------------------------------
void CVcrProperties::OnCapListNotification()
{
long temp;
// fake-out because can't get scroll messages from listbox
// update the value control if the selection changed
temp = SendMessage(m_hwndCapList, LB_GETTOPINDEX, 0, 0L);
if ( temp != m_CurCapSel ) {
m_CurCapSel = temp;
ShowCapabilityValue(m_CurCapSel, m_hwndCapValue);
}
}
//---------------------------------------------------------
//
// FindStringID - given a Capability Value that is known to be a
//string, pluck the StringID from the local table
//
//---------------------------------------------------------
intCVcrProperties::FindStringID(long Value)
{
int i;

for (i = 0; i <MaxDevValue; i++) {
if ( DevValueTable[i][1] == Value )
return DevValueTable[i][0];// found it
}
return -1;
}
//---------------------------------------------------------
//
// ShowCapabilityValue - uses index into DevCapTable to put this up
//
//---------------------------------------------------------
void CVcrProperties::ShowCapabilityValue(int index, HWND hwndValue)
{
long Value;
double dblValue;
int temp;
int StringID;
TCHAR buf[64];

m_pExtDevice->GetCapability(DevCapTable[index].DevCap, &Value, &dblValue);
// figure out how to display it
if ( DevCapTable[index].ValueType == ED_STR ) {
StringID = FindStringID(Value);
temp = LoadString(g_hInst, StringID, (LPSTR)buf, 64 );
}
else // it's a number
_ultoa( (unsigned long) Value, (LPSTR)buf, 10 );
// put it up
temp = SetWindowText( hwndValue,(LPSTR)buf);
}
//---------------------------------------------------------
//
// EnablePortSelection - enables/disables the radio buttons to
//keep the machine from blowing up
//
//---------------------------------------------------------
void CVcrProperties::EnablePortSelection(BOOL bEnable)
{
if (bEnable) {
EnableWindow(m_hPortButton1, TRUE);
EnableWindow(m_hPortButton2, TRUE);
EnableWindow(m_hPortButton3, TRUE);
EnableWindow(m_hPortButton4, TRUE);
EnableWindow(m_hPortButton5, TRUE);
m_bDevPortEnabled = TRUE;
}
else {
EnableWindow(m_hPortButton1, FALSE);
EnableWindow(m_hPortButton2, FALSE);
EnableWindow(m_hPortButton3, FALSE);
EnableWindow(m_hPortButton4, FALSE);
EnableWindow(m_hPortButton5, FALSE);
m_bDevPortEnabled = FALSE;
}
}
// eof vcrprop.cpp


VCRPROP.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
// filename: vcrprop.h
//
// IAMExtDevice Property Page for VCR Control Filter
//

#ifndef __VCRPROP__
#define __VCRPROP__

class CVcrProperties : public CBasePropertyPage
{

public:

    static CUnknown *CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

protected:
// CBasePropertyPage overrides
    BOOL OnReceiveMessage(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
    HRESULT OnConnect(IUnknown *pUnknown);
    HRESULT OnDisconnect();
    HRESULT OnActivate () ;
    HRESULT OnApplyChanges();

private:

    // work-around to make list boxes work
void    OnCapListNotification(void);

// you can't always change the communications port
voidEnablePortSelection(BOOL);

intFindStringID(long Value);
voidShowCapabilityValue(int index, HWND hwndValue);
voidSetDirty();

    BOOLOnInitDialog( void );
    BOOLOnCommand( int iButton, int iNotify );
voidUpdateControls(void);

CVcrProperties(LPUNKNOWN lpunk, HRESULT *phr);

HWNDm_hwndCapList;// Handle of capability listbox
HWNDm_hwndCapValue;// Handle of capability value control
HWNDm_hwndVidInList;// Handle of video input listbox
HWNDm_hwndAudInList;// Handle of audio input listbox
HWNDm_hPortButton1;// Handle of COM1 radio button
HWNDm_hPortButton2;// Handle of COM2 radio button
HWNDm_hPortButton3;// Handle of COM3 radio button
HWNDm_hPortButton4;// Handle of COM4 radio button
HWNDm_hPortButton5;// Handle of SIM radio button

longm_CurCapSel;// current capabilites selection
longm_CurVidInSel;// current video input selection
longm_CurAudInSel;// current audio input selection
longm_CurDevPort;// how we talk to device
longm_CurPowerMode;// on or what?
long m_bLink;// should VCR run follow graph?

IBaseFilter*m_pFilter;// pointer to IBaseFilter on our object
IAMExtDevice*m_pExtDevice;// pointers to the IAMExtDevice
//  and IAMExtTransport
IAMExtTransport *m_pExtTransport;//  interfaces

BOOL m_bIsInitialized;// Will be false while we set init values in Dlg
BOOL m_bDevPortEnabled;// Selection only allowed in STOP
};

#endif // __VCRPROP__
// eof vcrprop.h


VCRUIDS.H

//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
// filename: exdevuid.h
//

// The CLSID's used by the VCR Control filter

#ifndef __EXDEVUID__
#define __EXDEVUID__

//
// VCR Control Filter Object
// {c8a3b330-0f2a-11cf-8c09-204c4f4f5020}
DEFINE_GUID(CLSID_VCRControlFilter,
0xc8a3b330, 0x0f2a, 0x11cf, 0x8c, 0x09, 0x20, 0x4c, 0x4f, 0x4f, 0x50, 0x20);

// VCR Property Page - General Props
// {c8a3b331-0f2a-11cf-8c09-204c4f4f5020}
DEFINE_GUID(CLSID_VCRControlPropertyPage,
0xc8a3b331, 0x0f2a, 0x11cf, 0x8c, 0x09, 0x20, 0x4c, 0x4f, 0x4f, 0x50, 0x20);

// VCR Property Page - Transport Props
// {c8a3b332-0f2a-11cf-8c09-204c4f4f5020}
DEFINE_GUID(CLSID_VCRTransPropertyPage,
0xc8a3b332, 0x0f2a, 0x11cf, 0x8c, 0x09, 0x20, 0x4c, 0x4f, 0x4f, 0x50, 0x20);

#endif __EXDEVUID__// #ifndef __EXDEVUID__

// eof exdevuid.h


README.TXT

Vidcap, the VFW video capture filter sample, is not registered with the SDK
installation. To register it, run the command

  regsvr32 c:\dxmedia\bin\vidcap.ax

Vidcap.ax registers one entry per VFW capture device in the Video Capture
Sources category. See the function DllRegisterServer in vidcap.cpp for
details.


OVERLAY.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*

    Methods for CVidOverlay, CVidOverlayNotify

    This is a preview pin, which will use the hardware overlay capabilities
    of the driver to make it draw into the window of the video renderer that
    this pin is connected to.  We connect to the renderer through IOverlay,
    meaning that the renderer tells us where the window is at all times, and
    it's up to us to draw in it.

    When we get RUN, if the capture pin is connected, it will have made
    a capture window already, and we must use that capture window, since
    there can be only one (it takes the hardware).  If the capture pin
    isn't connected, we create the capture window.  In any case, we make it
    a child of the video renderer's window and always make it fill up the
    video renderer's window's rectangle, and then turn overlay on, and
    VOILA!  we have preview.  When we stop streaming, we destroy the
    capture window (if we made it) so the capture pin get make one again
    if it needs to.

    Reading this code is very useful for figuring out how to make a pin
    that connects to the renderer through IOverlay.

*/

#include <streams.h>
#include <mmsystem.h>
#include <vfw.h>
#include "vidcap.h"

// CVidOverlay constructor
//
CVidOverlay::CVidOverlay(TCHAR *pObjectName, CVidCap *pCapture,
        HRESULT * phr, LPCWSTR pName)
   :
   CBaseOutputPin(pObjectName, pCapture, pCapture->pStateLock(), phr, pName),
   m_OverlayNotify(NAME("Overlay notification interface"), pCapture, NULL, phr),
   m_pCap(pCapture),
   m_fRunning(FALSE),
   m_hwndCap(NULL)
{
   DbgLog((LOG_TRACE,1,TEXT("CVidOverlay constructor")));
   ASSERT(pCapture);
}

CVidOverlay::~CVidOverlay()
{
    DbgLog((LOG_TRACE,1,TEXT("*Destroying the Overlay pin")));
};

STDMETHODIMP CVidOverlay::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    DbgLog((LOG_TRACE,99,TEXT("CVidOverlay::QueryInterface")));
    if (ppv)
*ppv = NULL;

    /* Do we have this interface */

    if (riid == IID_IKsPropertySet) {
        return GetInterface((LPUNKNOWN) (IKsPropertySet *) this, ppv);
    } else {
        return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
    }
}

// Overridden to connect with IOverlay instead of IMemInputPin
// Say if we're prepared to connect to a given input pin from
// this output pin
//
STDMETHODIMP CVidOverlay::Connect(IPin *pReceivePin,
                                        const AM_MEDIA_TYPE *pmt)
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlay::Connect")));

    /*  Call the base class to make sure the directions match! */
    HRESULT hr = CBaseOutputPin::Connect(pReceivePin,pmt);
    if (FAILED(hr)) {
        return hr;
    }
    /*  We're happy if we can get an IOverlay interface */

    hr = pReceivePin->QueryInterface(IID_IOverlay,
                                     (void **)&m_pOverlay);

    // we were promised this would work
    ASSERT(SUCCEEDED(hr));

    /*  Because we're not going to get called again - except to
        propose a media type - we set up a callback here.

        There's only one overlay pin so we don't need any context.
    */

    hr = m_pOverlay->Advise(&m_OverlayNotify,
                            ADVISE_CLIPPING | ADVISE_POSITION);

    /*
        We don't need to hold on to the IOverlay pointer
        because BreakConnect will be called before the receiving
        pin goes away.
    */

    if (FAILED(hr)) {
// !!! Shouldn't happen, but this isn't quite right
        Disconnect();
pReceivePin->Disconnect();
        return hr;
    } else {
        m_bAdvise = TRUE;
    }

    return hr;
}

// Overridden to connect with IOverlay instead of IMemInputPin
//
HRESULT CVidOverlay::BreakConnect()
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlay::BreakConnect")));

    // we must get rid of our overlay notify advise now
    if (m_pOverlay != NULL) {
        if (m_bAdvise) {
            m_pOverlay->Unadvise();
            m_bAdvise = FALSE;
        }
        m_pOverlay->Release();
        m_pOverlay = NULL;
    }

    return CBaseOutputPin::BreakConnect();
}

// Overridden to connect with IOverlay instead of IMemInputPin
// Override this because we don't want any allocator!
//
HRESULT CVidOverlay::DecideAllocator(IMemInputPin * pPin,
                        IMemAllocator ** pAlloc) {
    /*  We just don't want one so everything's OK as it is */
    return S_OK;
}

// Overridden to connect with IOverlay instead of IMemInputPin
HRESULT CVidOverlay::GetMediaType(int iPosition, CMediaType *pmt)
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlay::GetMediaType #%d"), iPosition));

    if (pmt == NULL) {
        DbgLog((LOG_TRACE,3,TEXT("NULL format, no can do")));
return E_INVALIDARG;
    }

    if (iPosition < 0) {
        return E_INVALIDARG;
    }

    if (iPosition > 0) {
        return VFW_S_NO_MORE_ITEMS;
    }

    // We provide a media type of OVERLAY with an 8 bit format (the
    // renderer won't accept it if we don't set up an 8 bit format)

    BYTE aFormat[sizeof(VIDEOINFOHEADER) + SIZE_PALETTE];
    VIDEOINFOHEADER *pFormat = (VIDEOINFOHEADER *)aFormat;
    ZeroMemory(pFormat, sizeof(VIDEOINFOHEADER) + SIZE_PALETTE);

    // make the overlay window the same size as what's being captured
    if (m_pCap->m_pCapturePin->m_mt.IsValid()) {
        pFormat->bmiHeader.biWidth =
HEADER(m_pCap->m_pCapturePin->m_mt.Format())->biWidth;
        pFormat->bmiHeader.biHeight =
HEADER(m_pCap->m_pCapturePin->m_mt.Format())->biHeight;
    } else {
        pFormat->bmiHeader.biWidth = 320;
        pFormat->bmiHeader.biHeight = 240;
    }

    pFormat->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
    pFormat->bmiHeader.biPlanes = 1;
    pFormat->bmiHeader.biBitCount = 8;

    pmt->SetFormat((PBYTE)pFormat, sizeof(VIDEOINFOHEADER) + SIZE_PALETTE);
    pmt->SetFormatType(&FORMAT_VideoInfo);

    if (pmt->pbFormat == NULL) {
        return E_OUTOFMEMORY;
    }

    pmt->majortype = MEDIATYPE_Video;
    pmt->subtype   = MEDIASUBTYPE_Overlay;
    pmt->bFixedSizeSamples    = FALSE;
    pmt->bTemporalCompression = FALSE;
    pmt->lSampleSize          = 0;

    return NOERROR;
}

// Overridden to connect with IOverlay instead of IMemInputPin
// We accept overlay connections only
//
HRESULT CVidOverlay::CheckMediaType(const CMediaType *pMediaType)
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlay::CheckMediaType")));
    if (pMediaType->subtype == MEDIASUBTYPE_Overlay)
        return NOERROR;
    else
return E_FAIL;
}

// Overridden to connect with IOverlay instead of IMemInputPin
//
HRESULT CVidOverlay::CheckConnect(IPin *pPin)
{
    // we don't connect to anyone who doesn't support IOverlay.
    // after all, we're an overlay pin
    HRESULT hr = pPin->QueryInterface(IID_IOverlay, (void **)&m_pOverlay);

    if (FAILED(hr)) {
        return E_NOINTERFACE;
    } else {
m_pOverlay->Release();
m_pOverlay = NULL;
    }

    return CBasePin::CheckConnect(pPin);
}

// Overridden to connect with IOverlay instead of IMemInputPin
//
HRESULT CVidOverlay::Active()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidOverlay Stop->Pause")));
    // don't let the base class Active() get called for non-IMemInput pins
    return NOERROR;
}

// Overridden to connect with IOverlay instead of IMemInputPin
//
HRESULT CVidOverlay::Inactive()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidOverlay Pause->Stop")));

    // we made our own capture window since the capture pin doesn't have one
    if (m_pCap->m_pCapturePin->m_hwCapCapturing == NULL && m_hwndCap)
DestroyCaptureWindow(m_hwndCap);

    // don't let the base class Inactive() get called for non-IMemInput pins
    return NOERROR;
}

// we want to know when we start running... that's when we are supposed
// to start previewing... NOT when we start streaming like normal filters
// (when paused)
//
HRESULT CVidOverlay::ActiveRun(REFERENCE_TIME tStart)
{
    DbgLog((LOG_TRACE,2,TEXT("CVidOverlay Pause->Run")));

    ASSERT(m_pCap->m_pOverlayPin->IsConnected());

    m_fRunning = TRUE;

    // something's wrong...
    if (m_pOverlay == NULL)
return NOERROR;

    m_hwndCap = m_pCap->m_pCapturePin->m_hwCapCapturing;

    HWND hwndRender;
    m_pOverlay->GetWindowHandle(&hwndRender);
    if (hwndRender == NULL)
return NOERROR;

    // it appears the capture pin is not connected up.  We'll need to make
    // our own capture window.  We'll destroy it when we stop, so we never
    // get confused if somebody connects the capture pin up after that.
    if (m_hwndCap == NULL) {
        DbgLog((LOG_TRACE,2,TEXT("OVERLAY creating its own capwin")));
        // this will make the cap win a child of the renderer
m_hwndCap = CreateCaptureWindow(hwndRender);
if (m_hwndCap == NULL)
   return NOERROR;
    } else {
// !!! THIS HANGS IN SEND MESSAGE in SetWindowLong!
        // make the capture window a child of the renderer so it moves with
        // the renderer window and overlay will appear in the renderer window
        LONG lStyle = GetWindowLong(m_hwndCap, GWL_STYLE);
        lStyle |= WS_CHILD;
        SetWindowLong(m_hwndCap, GWL_STYLE, lStyle);
        SetParent(m_hwndCap, hwndRender);
    }

    // now turn overlay ON
    DbgLog((LOG_TRACE,2,TEXT("Turning OVERLAY ON")));
    capOverlay(m_hwndCap, TRUE);

    return NOERROR;
}

// we need to stop overlay when we transition from RUN to PAUSE... not
// from PAUSE to STOP like normal filters -- that's the rule for capture
// filters
//
HRESULT CVidOverlay::ActivePause()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidOverlay Run->Pause")));

    m_fRunning = FALSE;

    // turn overlay off and stop being a child of the renderer
    if (m_hwndCap) {
        DbgLog((LOG_TRACE,2,TEXT("Turning OVERLAY off")));
        capOverlay(m_hwndCap, FALSE);
        LONG lStyle = GetWindowLong(m_hwndCap, GWL_STYLE);
        lStyle &= ~WS_CHILD;
        SetWindowLong(m_hwndCap, GWL_STYLE, lStyle);
        SetParent(m_hwndCap, NULL);
    }

    return NOERROR;
}

// make a capture window for our overlay pin, and make it a child of the
// video renderer window passed in
//
HWND CVidOverlay::CreateCaptureWindow(HWND hwndP)
{
    BOOL bErr;

    HWND hwndCapture;   // The window to return

    // make the capture window a child of the renderer window passed in
    hwndCapture = capCreateCaptureWindow(NULL, WS_CHILD | WS_VISIBLE,
                                         0, 0, 150, 150,
                                         hwndP, 261 /* ID */);

    if (!hwndCapture) {
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("CAP Window could not be created") ));
        return NULL;
    }
    //DbgLog((LOG_TRACE,2,TEXT("OVERLAY created capture window")));

    // connect to the hardware device we're supposed to use
    bErr = capDriverConnect(hwndCapture,
m_pCap->m_pCapturePin->m_uiDriverIndex);
    if (!bErr) {
        DestroyWindow(hwndCapture);
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Driver failed to connect") ) );
        return NULL;
    }

    return hwndCapture;
}

// Disconnect the driver before destroying the window.
//
BOOL CVidOverlay::DestroyCaptureWindow(HWND hwnd)
{

    ASSERT(hwnd != NULL);

    // !!! why is this failing?
    BOOL bDriverDisconnected = capDriverDisconnect(hwnd);
    //DbgLog((LOG_TRACE,2,TEXT("Driver disconnect: %x"), bDriverDisconnected));

    BOOL bWindowGone = DestroyWindow(hwnd);
    //DbgLog((LOG_TRACE,2,TEXT("Window destroy: %x"), bWindowGone));

    return (bDriverDisconnected && bWindowGone);
}

//
// PIN CATEGORIES - let the world know that we are a PREVIEW pin
//

HRESULT CVidOverlay::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in.  To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CVidOverlay::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

    if (pcbReturned)
*pcbReturned = sizeof(GUID);

    if (pPropData == NULL)
return S_OK;

    if (cbPropData < sizeof(GUID))
return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_PREVIEW;
    return S_OK;
}

// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CVidOverlay::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pTypeSupport)
*pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
}

//=========================================================================//
//***I N T E R M I S S I O N***//
//=========================================================================//

/*
        IOverlayNotify - how the renderer tells us where the window is
 and when it needs repainting
*/

CVidOverlayNotify::CVidOverlayNotify(TCHAR              * pName,
                               CVidCap   * pFilter,
                               LPUNKNOWN            pUnk,
                               HRESULT            * phr) :
    CUnknown(pName, pUnk)
{
    DbgLog((LOG_TRACE,1,TEXT("*Instantiating CVidOverlayNotify")));
    m_pFilter = pFilter;
    m_rcClient.top = -1;// something unusual
}

CVidOverlayNotify::~CVidOverlayNotify()
{
    DbgLog((LOG_TRACE,1,TEXT("*Destroying CVidOverlayNotify")));
}

STDMETHODIMP CVidOverlayNotify::NonDelegatingQueryInterface(REFIID riid,
                                                         void ** ppv)
{
    DbgLog((LOG_TRACE,99,TEXT("CVidOverlayNotify::QueryInterface")));
    if (ppv)
*ppv = NULL;

    /* Do we have this interface */

    if (riid == IID_IOverlayNotify) {
        return GetInterface((LPUNKNOWN) (IOverlayNotify *) this, ppv);
    } else {
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }
}

STDMETHODIMP_(ULONG) CVidOverlayNotify::NonDelegatingRelease()
{
    return m_pFilter->Release();
}

STDMETHODIMP_(ULONG) CVidOverlayNotify::NonDelegatingAddRef()
{
    return m_pFilter->AddRef();
}

STDMETHODIMP CVidOverlayNotify::OnColorKeyChange(
    const COLORKEY *pColorKey)          // Defines new colour key
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlayNotify::OnColorKeyChange")));

// We expect the hardware to handle colour key stuff, so I'm really
// hoping that the renderer will never draw the colour key itself.

    return NOERROR;
}

// The calls to OnClipChange happen in sync with the window. So it's called
// with an empty clip list before the window moves to freeze the video, and
// then when the window has stabilised it is called again with the new clip
// list. The OnPositionChange callback is for overlay cards that don't want
// the expense of synchronous clipping updates and just want to know when
// the source or destination video positions change. They will NOT be called
// in sync with the window but at some point after the window has changed
// (basicly in time with WM_SIZE etc messages received). This is therefore
// suitable for overlay cards that don't inlay their data to the framebuffer

STDMETHODIMP CVidOverlayNotify::OnClipChange(
    const RECT    * pSourceRect,         // Area of source video to use
    const RECT    * pDestinationRect,    // screen co-ords of window
    const RGNDATA * pRegionData)         // Header describing clipping
{

    // we get a LOT of these messages.  Try to save time by ignore many
    // of them

    if (!m_pFilter->m_pOverlayPin)
return NOERROR;

    if (!m_pFilter->m_pOverlayPin->IsConnected())
return NOERROR;

    if (IsRectEmpty(pSourceRect) && IsRectEmpty(pDestinationRect))
return NOERROR;

    HWND hwnd = NULL;
    RECT rcC;
    if (m_pFilter->m_pOverlayPin->m_pOverlay)
        m_pFilter->m_pOverlayPin->m_pOverlay->GetWindowHandle(&hwnd);

    if (hwnd == NULL || !IsWindowVisible(hwnd))
return NOERROR;

    // It's up to us to keep garbage out of the window by painting it if
    // we're not running, and the hardware has nothing to draw
    if (!m_pFilter->m_pOverlayPin->m_fRunning) {
        HDC  hdc;
        if (hwnd)
    hdc = GetDC(hwnd);
        if (hdc == NULL)
    return NOERROR;
GetClientRect(hwnd, &rcC);
HBRUSH hbrOld = (HBRUSH)SelectObject(hdc, GetStockObject(BLACK_BRUSH));
PatBlt(hdc, 0, 0, rcC.right, rcC.bottom, PATCOPY);
SelectObject(hdc, hbrOld);
        ReleaseDC(hwnd, hdc);
return NOERROR;
    }

    DbgLog((LOG_TRACE,3,TEXT("OnClip/PositionChange (%d,%d) (%d,%d)"),
        pSourceRect->right - pSourceRect->left,
        pSourceRect->bottom - pSourceRect->top,
        pDestinationRect->right - pDestinationRect->left,
        pDestinationRect->bottom - pDestinationRect->top));

    // make the capture window fill up the renderer window
    if (m_pFilter->m_pOverlayPin->m_hwndCap) {
        GetClientRect(hwnd, &rcC);
        if (rcC.left != m_rcClient.left || rcC.top != m_rcClient.top ||
        rcC.right != m_rcClient.right ||
rcC.bottom != m_rcClient.bottom) {
    SetWindowPos(m_pFilter->m_pOverlayPin->m_hwndCap, NULL,
rcC.left, rcC.top, rcC.right, rcC.bottom, SWP_NOZORDER);
            DbgLog((LOG_TRACE,3,TEXT("Moving CAPWIN to (%d,%d,%d,%d)"),
rcC.left, rcC.top, rcC.right, rcC.bottom));
    m_rcClient = rcC;
}
    }

    return NOERROR;
}

STDMETHODIMP CVidOverlayNotify::OnPaletteChange(
    DWORD dwColors,                     // Number of colours present
    const PALETTEENTRY *pPalette)       // Array of palette colours
{
    DbgLog((LOG_TRACE,3,TEXT("CVidOverlayNotify::OnPaletteChange")));

    return NOERROR;
}

STDMETHODIMP CVidOverlayNotify::OnPositionChange(
    const RECT *pSourceRect,            // Area of video to play with
    const RECT *pDestinationRect)       // Area video goes
{

    return OnClipChange(pSourceRect, pDestinationRect, NULL);
}


PREVIEW.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

/*

    Methods for CVidPreview - the preview pin that doesn't use overlay

The basic premise here is that the driver only provides one
stream... we are FAKING this preview stream by only accepting
the same media type as our capture pin, and every once in a while
when the capture pin has some extra time it will send us a copy
of a frame to use as a preview frame.  If we had hardware that
can do a h/w overlay, we would have used the overlay type of
preview pin.  If we had hardware that can capture two streams
at once, we wouldn't need to fake a preview pin by duplicating
data going out the capture pin, but alas, we are not that lucky.

 This is tricky to do.  If the capture pin is streaming and using
 a capture window to capture with, we can't make our own capture
window or make any calls to the capture pin's capture window (there
can be only one around at a time).  So in this case, the capture pin
will send us a frame every once in a while for us to copy and deliver.
If the capture pin is NOT active, and does not have the capture
device open, then we need to create our own capture window and use
it to capture frames and deliver them.  We will be able to switch
back and forth.

This pin supports CBaseStreamControl (and thus IAMStreamControl)
for turning preview on and off while the graph is running
*/

#include <streams.h>
#include <mmsystem.h>
#include <vfw.h>
#include "vidcap.h"

// CVidPreview constructor
//
CVidPreview::CVidPreview(TCHAR *pObjectName, CVidCap *pCapture,
        HRESULT * phr, LPCWSTR pName)
   :
   CBaseOutputPin(pObjectName, pCapture, pCapture->pStateLock(), phr, pName),
   m_pCap(pCapture),
   m_fRunning(FALSE),
   m_hThread(NULL),
   m_tid(0),
   m_hEventRun(NULL),
   m_dwAdvise(0),
   m_fCapturing(FALSE),
   m_hEventActiveChanged(NULL),
   m_hEventFrameValid(NULL),
   m_lpFrame(NULL),
   m_iFrameSize(0),
   m_fLastSampleDiscarded(FALSE),
   m_fFrameValid(FALSE),
   m_hwndCap(NULL)
{
   DbgLog((LOG_TRACE,1,TEXT("CVidPreview constructor")));
   ASSERT(pCapture);
}

CVidPreview::~CVidPreview()
{
    DbgLog((LOG_TRACE,1,TEXT("*Destroying the Preview pin")));
    // we left one of these hanging around
    if (m_hwndCap)
DestroyCaptureWindow(m_hwndCap);
};

STDMETHODIMP CVidPreview::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    if (riid == IID_IAMStreamControl) {
return GetInterface((LPUNKNOWN)(IAMStreamControl *)this, ppv);
    } else if (riid == IID_IKsPropertySet) {
return GetInterface((LPUNKNOWN)(IKsPropertySet *)this, ppv);
    }

   return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv);
}

// we can only allow being connected with the same media type as the
// capture pin.  It may be giving us the frames to deliver.
//
HRESULT CVidPreview::GetMediaType(int iPosition, CMediaType *pmt)
{
    DbgLog((LOG_TRACE,3,TEXT("CVidPreview::GetMediaType #%d"), iPosition));

    if (iPosition < 0)
return E_INVALIDARG;
    if (iPosition > 0)
return VFW_S_NO_MORE_ITEMS;

    // we preview the same format as we capture
    return m_pCap->m_pCapturePin->GetMediaType(pmt);
}

// We only accept what the capture pin would accept.
//
HRESULT CVidPreview::CheckMediaType(const CMediaType *pMediaType)
{
    DbgLog((LOG_TRACE,3,TEXT("CVidPreview::CheckMediaType")));

    // Only accept what our capture pin is providing.  I will not switch
    // our capture pin over to a new format just because somebody changes
    // the preview pin.
    return m_pCap->m_pCapturePin->CheckMediaType(pMediaType);
}

// we are being RUN.  Time to start previewing
//
HRESULT CVidPreview::ActiveRun(REFERENCE_TIME tStart)
{
    DbgLog((LOG_TRACE,2,TEXT("CVidPreview Pause->Run")));

    ASSERT(IsConnected());

    m_fRunning = TRUE;
    m_rtRun = tStart;

    // tell our thread to start previewing
    SetEvent(m_hEventRun);

    return NOERROR;
}

// we have stopped running.  Our thread will stop previewing
//
HRESULT CVidPreview::ActivePause()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidPreview Run->Pause")));

    m_fRunning = FALSE;

    return NOERROR;
}

// we are starting to stream.  Time to create a thread to do preview
//
HRESULT CVidPreview::Active()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidPreview Stop->Pause")));

    ASSERT(IsConnected());

    // This event tells the thread when we are RUN or STOPPED
    m_hEventRun = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!m_hEventRun) {
        DbgLog((LOG_ERROR,1,TEXT("Can't create Run event")));
        return E_OUTOFMEMORY;
    }

    // This event is set by the thread when it notices the capture pin
    // going from inactive->active or vv.  We delay returning from
    // CapturePinActive() until the thread notices, because the capture
    // pin will actually go active after CapturePinActive returns and
    // our thread has to have closed the capture window by then.
    m_hEventActiveChanged = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!m_hEventActiveChanged) {
        DbgLog((LOG_ERROR,1,TEXT("Can't create ActiveChanged event")));
        return E_OUTOFMEMORY;
    }

    // This event tells the thread that a frame is ready to deliver.  It
    // might have come from the capture pin, or we might have captured it
    // ourself.
    m_hEventFrameValid = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!m_hEventFrameValid) {
        DbgLog((LOG_ERROR,1,TEXT("Can't create FrameValid event")));
        return E_OUTOFMEMORY;
    }

    m_EventAdvise.Reset();
    m_fFrameValid = FALSE;

    m_hThread = CreateThread(NULL, 0, CVidPreview::ThreadProcInit, this,
0, &m_tid);
    if (!m_hThread) {
        DbgLog((LOG_ERROR,1,TEXT("Can't create Preview thread")));
       return E_OUTOFMEMORY;
    }

    // now that we're streaming, we need a capture window to use, unless
    // of course, our capture pin is active too, in which case we'll use
    // its capture window since there can be only one.
    if (!m_fCapturing && !m_hwndCap) {
m_hwndCap = CreateCaptureWindow();
ASSERT(m_hwndCap);
    }

    return CBaseOutputPin::Active();
}

// our pin has stopped streaming.  Time to kill the thread.
//
HRESULT CVidPreview::Inactive()
{
    DbgLog((LOG_TRACE,2,TEXT("CVidPreview Pause->Stop")));

    ASSERT(IsConnected());

    // tell our thread to give up and die
    SetEvent(m_hEventRun);
    SetEvent(m_hEventFrameValid);
    SetEvent(m_hEventActiveChanged);

    // If our thread is stuck in GetDeliveryBuffer, only this will release it
    HRESULT hr = CBaseOutputPin::Inactive();

    // We're waiting for an advise that will now never come
    if (m_pCap->m_pClock && m_dwAdvise) {
m_pCap->m_pClock->Unadvise(m_dwAdvise);
m_EventAdvise.Set();
    }

    CloseHandle(m_hEventRun);
    CloseHandle(m_hEventActiveChanged);
    CloseHandle(m_hEventFrameValid);
    m_hEventRun = NULL;
    m_hEventActiveChanged = NULL;
    m_hEventFrameValid = NULL;

    // when our main thread shuts down the capture thread, the capture
    // thread will destroy the capture window it made, and this will cause
    // USER to send messages to our main thread, and if we're blocked
    // waiting for the capture thread to go away, we will deadlock
    // preventing user from sending us the messages, and thus our thread
    // will never go away.  We need to dispatch messages while waiting
    // !!! It's more efficient to use MsgWaitForMultipleObjects, I know.
    while (WaitForSingleObject(m_hThread, 50) == WAIT_TIMEOUT) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
 }
    }
    CloseHandle(m_hThread);
    m_tid = 0;
    m_hThread = NULL;

    // now that we've stopped streaming, the capture pin may need to open
    // the device to connect, etc, so we better let it go
    if (m_hwndCap)
DestroyCaptureWindow(m_hwndCap);
    m_hwndCap = NULL;

    return hr;
}

// we want 1 buffer big enough to hold a frame of the type that the capture
// pin is capturing.  We do some fancy math to align the buffers if the
// pin we are connected to requires it.
//
HRESULT CVidPreview::DecideBufferSize(IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pProperties)
{
   DbgLog((LOG_TRACE,2,TEXT("CVidPreview DecideBufferSize")));

   ASSERT(pAllocator);
   ASSERT(pProperties);

   // !!! more preview buffers?
   if (pProperties->cBuffers < 1)
       pProperties->cBuffers = 1;

   if (pProperties->cbAlign == 0)
pProperties->cbAlign = 1;

   // This is how big we need each buffer to be
   CMediaType cmt;
   HRESULT hr = GetMediaType(0, &cmt);
   if (hr != S_OK)
return hr;
   pProperties->cbBuffer = max(pProperties->cbBuffer,
(long)(HEADER(cmt.Format())->biSizeImage));

   // Make the prefix + buffer size meet the alignment restriction
   pProperties->cbBuffer = (long)ALIGNUP(pProperties->cbBuffer +
pProperties->cbPrefix, pProperties->cbAlign) -
pProperties->cbPrefix;

   ASSERT(pProperties->cbBuffer);

   DbgLog((LOG_TRACE,2,TEXT("Preview: %d buffers, prefix %d size %d align %d"),
pProperties->cBuffers, pProperties->cbPrefix,
pProperties->cbBuffer,
pProperties->cbAlign));

   ALLOCATOR_PROPERTIES Actual;
   return pAllocator->SetProperties(pProperties,&Actual);

   // !!! Are we sure we'll be happy with this?

}

// I know my preview sucks.  Stop telling me.  :-)
HRESULT CVidPreview::Notify(IBaseFilter *pFilter, Quality q)
{
    return NOERROR;
}

// The capture pin is going active ==> We must shut down our capture window
// The capture pin is inactive ==> We need our own capture window now
//
HRESULT CVidPreview::CapturePinActive(BOOL fActive)
{
    DbgLog((LOG_TRACE,2,TEXT("Capture pin says Active=%d"), fActive));

    if (fActive == m_fCapturing)
return S_OK;
    m_fCapturing = fActive;

    // the capture pin will want to use the h/w.  Close our capture window
    if (fActive && m_hwndCap) {
DestroyCaptureWindow(m_hwndCap);
m_hwndCap = NULL;
    }

    // don't make a capture window if we're stopped and don't need it.  We'll
    // make one when we're activated
    if (!fActive && !m_hwndCap && m_hThread) {
m_hwndCap = CreateCaptureWindow();
        ASSERT(m_hwndCap);
    }

    // stop thread from waiting for us to send a valid frame - no more to come
    // it will hang waiting forever if we don't do this
    SetEvent(m_hEventFrameValid);

    // wait until our worker thread notices the difference
    if (m_fRunning)
        WaitForSingleObject(m_hEventActiveChanged, INFINITE);
    ResetEvent(m_hEventActiveChanged);

    return S_OK;
}

// The capture pin is sending us a frame to preview
//
HRESULT CVidPreview::ReceivePreviewFrame(LPVOID lpFrame, int iSize)
{
    // I'm not the least bit interested in previewing right now, or
    // we haven't used the last one yet, or we don't have a place to put it
    if (!m_fRunning || m_fFrameValid || m_lpFrame == NULL) {
        //DbgLog((LOG_TRACE,4,TEXT("Not interested")));
return S_OK;
    }

    DbgLog((LOG_TRACE,4,TEXT("Capture pin is giving us a preview frame")));

    // !!! can't avoid mem copy without using our own allocator
    // !!! we do this copy memory even if preview pin is OFF (IAMStreamControl)
    // because we can't risk blocking this call by calling CheckStreamState
    CopyMemory(m_lpFrame, lpFrame, iSize);
    m_iFrameSize = iSize;
    m_fFrameValid = TRUE;
    SetEvent(m_hEventFrameValid);
    return S_OK;
}

// make a capture window for our preview pin
//
HWND CVidPreview::CreateCaptureWindow()
{
    BOOL bErr;

    DbgLog((LOG_TRACE,2,TEXT("PREVIEW creating a capture window")));

    HWND hwndCapture;   // The window to return

    hwndCapture = capCreateCaptureWindow(NULL, 0,
                                         0, 0, 150, 150,
                                         NULL, 0 /* ID */);

    if (!hwndCapture) {
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("CAP Window could not be created") ));
        return NULL;
    }

    bErr = capDriverConnect(hwndCapture,
m_pCap->m_pCapturePin->m_uiDriverIndex);
    if (!bErr) {
        DestroyWindow(hwndCapture);
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Driver failed to connect") ) );
        return NULL;
    }

    // we will be grabbing single frames
    capSetCallbackOnFrame(hwndCapture, &VideoCallback);

    SetWindowLong(hwndCapture, GWL_USERDATA, (LONG) this);

    return hwndCapture;
}

// Disconnect the driver before destroying the window.
//
BOOL CVidPreview::DestroyCaptureWindow(HWND hwnd)
{

    ASSERT(hwnd != NULL);

    DbgLog((LOG_TRACE,2,TEXT("PREVIEW destroying the capture window")));

    // !!! why is this failing?
    BOOL bDriverDisconnected = capDriverDisconnect(hwnd);
    //DbgLog((LOG_TRACE,2,TEXT("Driver disconnect: %x"), bDriverDisconnected));

    BOOL bWindowGone = DestroyWindow(hwnd);
    //DbgLog((LOG_TRACE,2,TEXT("Window destroy: %x"), bWindowGone));

    return (bDriverDisconnected && bWindowGone);
}

//
// VideoCallback
//
// The AVICap Video callback. Keep a copy of the buffer we are given
// May be called after the worker thread, or even the pin has gone away,
// depending on AVICap's internal timing. Therefore be very careful with the
// pointers we use.
//
LRESULT CALLBACK CVidPreview::VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr)
{

    CVidPreview *pThis = (CVidPreview *)GetWindowLong(hwnd, GWL_USERDATA);
    ASSERT(pThis);

    // I'm not the least bit interested in previewing right now, or
    // we haven't used the last one yet, or we don't have a place to put it
    if (!pThis->m_fRunning || pThis->m_fFrameValid ||
pThis->m_lpFrame == NULL) {
        //DbgLog((LOG_TRACE,4,TEXT("Not interested")));
return S_OK;
    }

    DbgLog((LOG_TRACE,4,TEXT("capGrabFrame callback got a preview frame")));

    // !!! can't avoid mem copy without using our own allocator
    // !!! we do this copy memory even if preview pin is OFF (IAMStreamControl)
    // because we can't risk blocking this call by calling CheckStreamState
    CopyMemory(pThis->m_lpFrame, lpVHdr->lpData, lpVHdr->dwBufferLength);
    pThis->m_iFrameSize = lpVHdr->dwBufferLength;
    pThis->m_fFrameValid = TRUE;
    SetEvent(pThis->m_hEventFrameValid);
    return S_OK;
}

DWORD WINAPI CVidPreview::ThreadProcInit(void *pv)
{
    CVidPreview *pThis = (CVidPreview *)pv;
    return pThis->ThreadProc();
}

DWORD CVidPreview::ThreadProc()
{
    IMediaSample *pSample;
    CRefTime rtStart, rtEnd;
    HRESULT hr;
    BOOL fCaptureActive = m_fCapturing;
    int iWait;
    HANDLE hWait[2] = {m_hEventFrameValid, m_hEventRun};

    DbgLog((LOG_TRACE,2,TEXT("CVidPreview ThreadProc")));

    // Send preview frames as long as we're running.  Die when not streaming
    while (1) {

// only preview while running
if (!m_fRunning) {
           DbgLog((LOG_TRACE,3,TEXT("Preview thread waiting for RUN")));
    WaitForSingleObject(m_hEventRun, INFINITE);
           DbgLog((LOG_TRACE,3,TEXT("Preview thread got RUN")));
 }
ResetEvent(m_hEventRun);

// if we stopped instead of ran
if (!m_fRunning)
    break;

while (m_fRunning) {
    hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
    if (FAILED(hr))
break;
    hr = pSample->GetPointer((LPBYTE *)&m_lpFrame);
    if (FAILED(hr))
break;

    // try to notice if the capture pin starts/stops streaming
    if (m_fCapturing != fCaptureActive) {
               DbgLog((LOG_TRACE,3,TEXT("Preview thread noticed Active=%d"),
 m_fCapturing));
SetEvent(m_hEventActiveChanged);
fCaptureActive = m_fCapturing;
    }

    // the capture pin will send us a frame
    if (fCaptureActive) {
               DbgLog((LOG_TRACE,4,TEXT("PREVIEW using capture picture")));

    // m_hEventFrameValid, m_hEventRun
iWait = WaitForMultipleObjects(2, hWait, FALSE, INFINITE);

// time for our thread to die - don't reset the event because
// we may need it to fire when we break out of this loop
if (iWait != WAIT_OBJECT_0) {
                   DbgLog((LOG_TRACE,2,TEXT("Wait for streaming pic abort1")));
    m_lpFrame = NULL;// please don't write here anymore
        pSample->Release();
    continue;
}

// the streaming pin stopped being active... switch again
if (!m_fFrameValid) {
                   DbgLog((LOG_TRACE,2,TEXT("Wait for streaming pic abort2")));
    ResetEvent(m_hEventFrameValid);
    m_lpFrame = NULL;// please don't write here anymore
        pSample->Release();
    continue;
}

ResetEvent(m_hEventFrameValid);
        pSample->SetActualDataLength(m_iFrameSize);

    // we have our own capture window and are getting our own frames
    } else {
               DbgLog((LOG_TRACE,4,TEXT("PREVIEW using capGrabFrame")));

// get a frame, and give it to our callback
capGrabFrame(m_hwndCap);

    // m_hEventFrameValid, m_hEventRun
// wait for our callback to get the frame
iWait = WaitForMultipleObjects(2, hWait, FALSE, INFINITE);

// time for our thread to die - don't reset the event because
// we may need it to fire when we break out of this loop
if (iWait != WAIT_OBJECT_0) {
                   DbgLog((LOG_TRACE,2,TEXT("Wait for capGrabFrame abort1")));
    m_lpFrame = NULL;// please don't write here anymore
        pSample->Release();
    continue;
}

// the streaming pin stopped being active... switch again
if (!m_fFrameValid) {
                   DbgLog((LOG_TRACE,2,TEXT("Wait for capGrabFrame abort2")));
    ResetEvent(m_hEventFrameValid);
    m_lpFrame = NULL;// please don't write here anymore
        pSample->Release();
    continue;
}

ResetEvent(m_hEventFrameValid);
        pSample->SetActualDataLength(m_iFrameSize);
    }

    // time stamp the sample
    if (m_pCap->m_pClock) {
        m_pCap->m_pClock->GetTime((REFERENCE_TIME *)&rtStart);
              rtStart = rtStart - m_pCap->m_tStart;
if (m_mt.IsValid() &&
((VIDEOINFOHEADER *)m_mt.Format())->AvgTimePerFrame)
                  rtEnd = rtStart +
((VIDEOINFOHEADER *)m_mt.Format())->AvgTimePerFrame;
else
    rtEnd = rtStart + 666666;// assume 15fps

// !!! NO TIME STAMPS for preview unless we know the latency
// of the graph... we could drop every frame needlessly since
// they'll all arrive at the renderer late if we're software
// decoding these frames.
//
// We only send another preview frame when this one is done,
// so we won't get a backup if decoding is slow.
//
// Actually, adding a latency time would still be broken
// if the latency was > 1 frame length, because the renderer
// would hold on to the sample until past the time for the
// next frame, and we wouldn't send out the next preview frame
// as soon as we should, and our preview frame rate would suffer
//
// But besides all that, we really need time stamps for
// the stream control stuff to work, so we'll have to live
// with preview frame rates being inferior if we have an
// oustanding stream control request.

AM_STREAM_INFO am;
GetInfo(&am);
if (am.dwFlags & AM_STREAM_INFO_START_DEFINED ||
am.dwFlags & AM_STREAM_INFO_STOP_DEFINED) {
                    //DbgLog((LOG_TRACE,0,TEXT("TIME STAMPING ANYWAY")));
                    pSample->SetTime((REFERENCE_TIME *)&rtStart,
(REFERENCE_TIME *)&rtEnd);
}
          }

    // This is important so ReceivePreviewFrame doesn't screw up
    m_lpFrame = NULL;// please don't write here anymore
    m_fFrameValid = FALSE;// next time m_lpFrame is set, Ok to write

    int iStreamState = CheckStreamState(pSample);
            pSample->SetDiscontinuity(FALSE);
            if (iStreamState == STREAM_FLOWING) {
                DbgLog((LOG_TRACE,4,TEXT("*PREV Sending frame at %d"), (int)rtStart));
        if (m_fLastSampleDiscarded)
                    pSample->SetDiscontinuity(TRUE);
            } else {
                DbgLog((LOG_TRACE,4,TEXT("*PREVIEW Discarding frame at %d"),
(int)rtStart));
        m_fLastSampleDiscarded = TRUE;
            }
    if (iStreamState == STREAM_FLOWING) {
        pSample->SetSyncPoint(TRUE);// !!! not necessarily
        pSample->SetPreroll(FALSE);
               DbgLog((LOG_TRACE,4,TEXT("*Delivering a preview frame")));
        Deliver(pSample);
    }
    pSample->Release();

    // if previewing ourself, wait for time till next frame
    // !!! capture pin may wait on this to become active
    if (!fCaptureActive && m_pCap->m_pClock) {
                hr = m_pCap->m_pClock->AdviseTime(m_rtRun, rtEnd,
            (HEVENT)(HANDLE) m_EventAdvise, &m_dwAdvise);
            if (SUCCEEDED(hr)) {
            m_EventAdvise.Wait();
            }
m_dwAdvise = 0;
            }
}
// just in case
SetEvent(m_hEventActiveChanged);
    }

    DbgLog((LOG_TRACE,2,TEXT("CVidPreview ThreadProc is dead")));
    return 0;
}

//
// PIN CATEGORIES - let the world know that we are a PREVIEW pin
//

HRESULT CVidPreview::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in.  To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CVidPreview::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

    if (pcbReturned)
*pcbReturned = sizeof(GUID);

    if (pPropData == NULL)
return S_OK;

    if (cbPropData < sizeof(GUID))
return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_PREVIEW;
    return S_OK;
}

// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CVidPreview::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pTypeSupport)
*pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
}


VIDCAP.CPP

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//  Video capture stream source filter
//

// Uses the AVICap window to capture video data to pass downstream.
// By using the video callback it avoids AVICap sending data to a file
// - AVICap is merely capturing buffers for us to pass on.
//

// Caveats
//
// ** Should reject going active when the user has format dialogs up.

#include <streams.h>
#include <initguid.h>
#include <olectl.h>
#include <mmsystem.h>
#include <vfw.h>
#include <string.h>
#include <stddef.h>     // for offsetof macro

#include "vidcap.h"

// setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{ &MEDIATYPE_Video       // clsMajorType
, &MEDIASUBTYPE_NULL };  // clsMinorType

const AMOVIESETUP_PIN sudOpPin =
{ L"Output"          // strName
, FALSE              // bRendered
, TRUE               // bOutput
, FALSE              // bZero
, FALSE              // bMany
, &CLSID_NULL        // clsConnectsToFilter
, NULL               // strConnectsToPin
, 1                  // nMediaTypes
, &sudOpPinTypes };  // lpMediaType

const AMOVIESETUP_FILTER sudVidCapax =
{ &CLSID_VidCap              // clsID
, L"Video Capture (AVICap)"  // strName
, MERIT_UNLIKELY             // dwMerit
, 1                          // nPins
, &sudOpPin };               // lpPin

// COM global table of objects available in this dll
CFactoryTemplate g_Templates[1] = {

    { L"Video Capture (AVICap)"
    , &CLSID_VidCap
    , CVidCap::CreateInstance
    , NULL
    , NULL }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

// exported entry points for registration and
// unregistration (in this case they only call
// through to default implmentations).
//
STDAPI DllRegisterServer()
{
  HRESULT hr = AMovieDllRegisterServer2( TRUE );
  if(SUCCEEDED(hr))
  {

      // locate each installed VFW capture driver and register an
      // entry in the Video Capture entry that maps to this filter. We
      // expect other capture filters to register just once
      //
      IFilterMapper2 *pFm2;
      HRESULT hr = CoCreateInstance(
          CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
          IID_IFilterMapper2, (void **)&pFm2);
      if(SUCCEEDED(hr))
      {

          for(UINT i = 0; i < 10; i++)
          {
              TCHAR szName[100], szDesc[100];
              if(capGetDriverDescription(
                  i, szName, sizeof(szName), szDesc, sizeof(szDesc)))
              {
                  // generate unique instance value (avicap - name). This
                  // should be something that doesn't change from machine
                  // to machine as it's used to persist this particular
                  // device (see documentation for
                  // IMoniker::GetDisplayName). Behind the scenes, this is
                  // the name of the registry key for this device. We'll
                  // use this name for both the instance name and the
                  // friendly-name
                  TCHAR szUniq[200];
                  wsprintf(szUniq, "avicap SDK Sample - %s", szName);

                  WCHAR wszUniq[200];
                  MultiByteToWideChar(CP_ACP, 0, szUniq, -1, wszUniq, NUMELMS(wszUniq));

                  // RegisterFilter returns a moniker here.
                  IMoniker *pMoniker = 0;

                  REGFILTER2 rf2;
                  rf2.dwVersion = 1;
                  rf2.dwMerit = MERIT_DO_NOT_USE;
                  rf2.cPins = 0;
                  rf2.rgPins = 0;

                  hr = pFm2->RegisterFilter(
                      CLSID_VidCap,
                      wszUniq,
                      &pMoniker,
                      &CLSID_VideoInputDeviceCategory,
                      wszUniq,
                      &rf2);

                  if(SUCCEEDED(hr))
                  {
                      // write out the device number. when the device
                      // is picked this filter needs to know which
                      // device to open. It does that by reading the
                      // AviCapIndex value.
                      IPropertyBag *pPropBag;
                      hr = pMoniker->BindToStorage(
                          0, 0, IID_IPropertyBag, (void **)&pPropBag);
                      if(SUCCEEDED(hr))
                      {
                          VARIANT var;
                          var.vt = VT_I4;
                          var.lVal = i;
                          hr = pPropBag->Write(L"AviCapIndex", &var);

                          pPropBag->Release();
                      }
                      pMoniker->Release();
                  }

              } //capGetDriverDescription

              if(FAILED(hr))
                  break;

          } // for loop

          pFm2->Release();
      } // CoCreateInstance

  } // AMovieDllRegisterServer2

  return hr;
}

STDAPI DllUnregisterServer()
{
    HRESULT hr = AMovieDllRegisterServer2( FALSE );
    if(SUCCEEDED(hr))
    {

        // remove each entry

        IFilterMapper2 *pFm2;
        HRESULT hr = CoCreateInstance(
            CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
            IID_IFilterMapper2, (void **)&pFm2);
        if(SUCCEEDED(hr))
        {

            for(UINT i = 0; i < 10; i++)
            {
                TCHAR szName[100], szDesc[100];
                if(capGetDriverDescription(
                    i, szName, sizeof(szName), szDesc, sizeof(szDesc)))
                {
                    // generate unique instance value (avicap - name). This
                    // should be something that doesn't change from machine
                    // to machine as it's used to persist this particular
                    // device (see documentation for
                    // IMoniker::GetDisplayName). Behind the scenes, this is
                    // the name of the registry key for this device. We'll
                    // use this name for both the instance name and the
                    // friendly-name
                    TCHAR szUniq[200];
                    wsprintf(szUniq, "avicap SDK Sample - %s", szName);

                    WCHAR wszUniq[200];
                    MultiByteToWideChar(CP_ACP, 0, szUniq, -1, wszUniq, NUMELMS(wszUniq));

                    hr = pFm2->UnregisterFilter(
                        &CLSID_VideoInputDeviceCategory,
                        wszUniq,
                        CLSID_VidCap);

                } //capGetDriverDescription

            } // for loop

            pFm2->Release();
        } // CoCreateInstance

    } // AMovieDllRegisterServer2

    return hr;
}

//
// CVidCap::Constructor
//
// don't create any pins yet, until we are told which device to use
// (through IPersistPropertyBag or CPersistStream)
CVidCap::CVidCap(TCHAR *pName, LPUNKNOWN lpunk, HRESULT *phr)
    : CSource(pName, lpunk, CLSID_VidCap),
      CPersistStream(lpunk, phr),
      m_pCapturePin(NULL),
      m_pOverlayPin(NULL),
      m_pPreviewPin(NULL),
      m_iVideoId(-1)
{

    CAutoLock l(&m_cStateLock);

    DbgLog((LOG_TRACE, 1, TEXT("CVidCap filter created")));
}

//
// CVidCap::Destructor
//
CVidCap::~CVidCap(void)
{
    DbgLog((LOG_TRACE, 1, TEXT("CVidCap filter destroyed")) );

    if (m_pCapturePin)
        delete m_pCapturePin;
    if (m_pOverlayPin)
        delete m_pOverlayPin;
    if (m_pPreviewPin)
        delete m_pPreviewPin;
}

//
// CreateInstance
//
// Called by CoCreateInstance to create a vidcap filter.
CUnknown * WINAPI CVidCap::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{

    CUnknown *punk = new CVidCap(TEXT("Video capture filter"), lpunk, phr);
    if (punk == NULL) {
        *phr = E_OUTOFMEMORY;
    }
    return punk;
}

// give out our interfaces
STDMETHODIMP CVidCap::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    if (riid == IID_IAMVfwCaptureDialogs) {
        return GetInterface((LPUNKNOWN)(IAMVfwCaptureDialogs *)this, ppv);
    } else if (riid == IID_IPersistPropertyBag) {
        return GetInterface((IPersistPropertyBag *)this, ppv);
    } else if(riid == IID_IPersistStream) {
        return GetInterface((IPersistStream *)this, ppv);
    }

   return CSource::NonDelegatingQueryInterface(riid, ppv);
}

// how many pins do we have? maybe 2, maybe 1, maybe 0
//
int CVidCap::GetPinCount()
{
   DbgLog((LOG_TRACE,5,TEXT("CVidCap::GetPinCount")));

   // we have a preview pin (one or the other)
   if (m_pOverlayPin || m_pPreviewPin)
return 2;
   else if (m_pCapturePin)
return 1;
   else
        return 0;
}

// Give out pointers to our pins.  We might have an overlay preview pin
// or a non-overlay preview pin
//
CBasePin * CVidCap::GetPin(int ii)
{
   DbgLog((LOG_TRACE,5,TEXT("CVidCap::GetPin")));

   if (ii == 0 && m_pCapturePin)
      return m_pCapturePin;
   if (ii == 1 && m_pOverlayPin)
      return m_pOverlayPin;
   if (ii == 1 && m_pPreviewPin)
      return m_pPreviewPin;
   return NULL;
}

// IPersistPropertyBag stuff
//
// Load is called to tell us what device to use.  There may be several
// capture cards on the system that we could use
STDMETHODIMP CVidCap::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog)
{
    HRESULT hr;
    CAutoLock l(pStateLock());

    DbgLog((LOG_TRACE,1,TEXT("Load...")));

    // We already have some pins, thank you
    if (m_pCapturePin)
return E_UNEXPECTED;

    // Default to capture device #0
    if (pPropBag == NULL) {
        m_iVideoId = 0;
        DbgLog((LOG_TRACE,1,TEXT("Using default device ID=%d"), m_iVideoId));
        CreatePins(&hr);
return hr;
    }

    // find out what device to use
    // different filters look in different places to find this info
    VARIANT var;
    var.vt = VT_I4;
    hr = pPropBag->Read(L"AviCapIndex", &var, 0);
    if(SUCCEEDED(hr))
    {
        hr = S_OK;
        m_iVideoId = var.lVal;
        DbgLog((LOG_TRACE,1,TEXT("Using device ID=%d"), m_iVideoId));
        CreatePins(&hr);
    }
    return hr;
}

STDMETHODIMP CVidCap::Save(
    LPPROPERTYBAG pPropBag, BOOL fClearDirty,
    BOOL fSaveAllProperties)
{
    // E_NOTIMPL is not really a valid return code as any object implementing
    // this interface must support the entire functionality of the
    // interface.
    return E_NOTIMPL;
}

// have we been initialized yet?  (Has somebody called Load)
STDMETHODIMP CVidCap::InitNew()
{
   if(m_pCapturePin)
   {
       ASSERT(m_iVideoId != -1);
       return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
   }
   else
   {
       return S_OK;
   }
}

// CPersistStream stuff
//
// what is our class ID?
STDMETHODIMP CVidCap::GetClassID(CLSID *pClsid)
{
    CheckPointer(pClsid, E_POINTER);
    *pClsid = CLSID_VidCap;
    return S_OK;
}

// CSource expects all its pins to derive from CSourceStream. Since
// this sample doesn't do this, we implement QueryId and FindPin to
// provide matching implementations

STDMETHODIMP CVidCap::FindPin(
    LPCWSTR Id, IPin ** ppPin)
{
    return CBaseFilter::FindPin(Id, ppPin);
}

HRESULT CVidCap::WriteToStream(IStream *pStream)
{
    ASSERT(m_iVideoId >= -1 && m_iVideoId < 10);
    return pStream->Write(&m_iVideoId, sizeof(LONG), 0);
}

// what device should we use?  Used to re-create a .GRF file that we
// are in
HRESULT CVidCap::ReadFromStream(IStream *pStream)
{
    if(m_pCapturePin)
    {
        ASSERT(m_iVideoId != -1);
        return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
    }

    ASSERT(m_iVideoId == -1);

    LONG iVideoId;
    HRESULT hr = pStream->Read(&iVideoId, sizeof(LONG), 0);
    if(FAILED(hr))
        return hr;

    m_iVideoId = iVideoId;
    DbgLog((LOG_TRACE,1,TEXT("Using device ID=%d"), m_iVideoId));

    hr = S_OK;
    CreatePins(&hr);
    return hr;
}

// How long is our data?  Just a long int (m_iVideoId)
int CVidCap::SizeMax()
{
    return sizeof(LONG);
}

// Now we can create our output pins, after a device is chosen
//
void CVidCap::CreatePins(HRESULT *phr)
{
    if (FAILED(*phr))
        return;

    CAutoLock l(pStateLock());

    if (m_pCapturePin)
        *phr = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);

    ASSERT(m_iVideoId != -1);// no device chosen yet?!

    // Our capture pin MUST be called L"Capture"
    m_pCapturePin = new CVidStream(NAME("Video capture stream"),
phr, this, m_iVideoId, L"Capture");
    if (m_pCapturePin == NULL) {
        *phr = E_OUTOFMEMORY;
        return;
    }

    if (FAILED(*phr)) {
delete m_pCapturePin;
m_pCapturePin = NULL;
        return;
    }

    // We can do overlay, so let's make a preview pin that does overlay
    // Otherwise, do a preview pin that will fake up a preview
    if (m_pCapturePin->m_HasOverlay)
m_pOverlayPin = CreateOverlayPin(phr);
    else
m_pPreviewPin = CreatePreviewPin(phr);
}

// tell CBaseStreamControl what clock to use
//
STDMETHODIMP CVidCap::SetSyncSource(IReferenceClock *pClock)
{
    if (m_pCapturePin)
m_pCapturePin->SetSyncSource(pClock);
    if (m_pPreviewPin)
m_pPreviewPin->SetSyncSource(pClock);
    return CSource::SetSyncSource(pClock);
}

// tell CBaseStreamControl what sink to use
//
STDMETHODIMP CVidCap::JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName)
{
    HRESULT hr = CSource::JoinFilterGraph(pGraph, pName);
    if (hr == S_OK && m_pCapturePin)
m_pCapturePin->SetFilterGraph(m_pSink);
    if (hr == S_OK && m_pPreviewPin)
m_pPreviewPin->SetFilterGraph(m_pSink);
    return hr;
}

// we don't send any data during PAUSE, so to avoid hanging renderers, we
// need to return VFW_S_CANT_CUE when paused
//
STDMETHODIMP CVidCap::GetState(DWORD dwMSecs, FILTER_STATE *State)
{
    UNREFERENCED_PARAMETER(dwMSecs);
    CheckPointer(State,E_POINTER);
    ValidateReadWritePtr(State,sizeof(FILTER_STATE));

    *State = m_State;
    if (m_State == State_Paused)
return VFW_S_CANT_CUE;
    else
        return S_OK;
}

// Run
//
// Activate the pin, letting it know that we are moving to State_Running
//
STDMETHODIMP CVidCap::Run(REFERENCE_TIME tStart) {
    CAutoLock l(pStateLock());

    DbgLog((LOG_TRACE,2,TEXT("::Run")));

    HRESULT hr;

    m_tStart = tStart;  // remember the stream time offset

    hr = CSource::Run(tStart);

    // Tell CBaseStreamControl what's going on
    m_pCapturePin->NotifyFilterState(State_Running, tStart);
    if (m_pPreviewPin)
        m_pPreviewPin->NotifyFilterState(State_Running, tStart);

    if (SUCCEEDED(hr)) {
// start us running
m_pCapturePin->Run();
// overlay pin wants to know too
if (m_pOverlayPin && m_pOverlayPin->IsConnected())
    m_pOverlayPin->ActiveRun(tStart);
// preview pin wants to know too
if (m_pPreviewPin && m_pPreviewPin->IsConnected())
    m_pPreviewPin->ActiveRun(tStart);
    }

    return S_OK;
}

//
// Pause
//
// Activate the pin, letting it know that Paused will
// be the next state
STDMETHODIMP CVidCap::Pause(void) {
    CAutoLock l(pStateLock());

    BOOL fWasStopped = FALSE;

    if (m_State == State_Paused) {
        return S_OK;
    }

    DbgLog((LOG_TRACE,2,TEXT("::Pause")));

    // The video renderer will start blocking Deliver() when it goes from
    // run to pause (or any filter could potentially do that) making us
    // hang, so we need to tell it to release the sample by flushing
    // it before we tell the thread to pause, or the thread will be hung
    // and never get our message
    if (m_State == State_Running) {
        m_pCapturePin->DeliverBeginFlush();
        m_pCapturePin->DeliverEndFlush();

// our overlay pin wants to know when we stop running
if (m_pOverlayPin)
    m_pOverlayPin->ActivePause();
// our preview pin wants to know when we stop running
if (m_pPreviewPin)
    m_pPreviewPin->ActivePause();
    }

    // we're streaming the graph now, so we better close our temporary
    // window, so we can open it for real later, with the real # of buffers
    if (m_State == State_Stopped && m_pCapturePin->m_hwCapCapturing) {
        m_pCapturePin->DestroyCaptureWindow(m_pCapturePin->m_hwCapCapturing);
        m_pCapturePin->m_hwCapCapturing = NULL;
    }

    // the capture pin is in fact going to start streaming... tell the
    // preview pin to release the hardware
    if (m_State == State_Stopped && m_pCapturePin->IsConnected()) {
fWasStopped = TRUE;
if (m_pPreviewPin)
    m_pPreviewPin->CapturePinActive(TRUE);
    }

    // need to change state before sending the pause request
    // or the thread will not be created when we try to signal it.
    HRESULT hr = CSource::Pause();
    if (FAILED(hr)) {
// error, never mind
if (fWasStopped && m_pPreviewPin)
    m_pPreviewPin->CapturePinActive(FALSE);
        return hr;
    }

    // the source stream base class seems to freak out if we pause it when not
    // connected
    if (m_pCapturePin->IsConnected()) {
        // Tell CBaseStreamControl what's going on
        m_pCapturePin->NotifyFilterState(State_Paused, 0);
if (m_pPreviewPin)
            m_pPreviewPin->NotifyFilterState(State_Paused, 0);

        hr = m_pCapturePin->Pause();
// error, never mind
if (FAILED(hr) && fWasStopped && m_pPreviewPin)
    m_pPreviewPin->CapturePinActive(FALSE);
return hr;
    }

    return NOERROR;
}

//
// Stop
//
// Pass the current state to the pins Inactive method
STDMETHODIMP CVidCap::Stop(void) {
    HRESULT hr;
    CAutoLock l(pStateLock());

    DbgLog((LOG_TRACE,2,TEXT("::Stop")));

    // Shame on the base classes, they don't take care of this, and it's very
    // important for us that we go through pause on our way to stop
    if (m_State == State_Running) {
hr = Pause();
if (FAILED(hr))
    return hr;
    }

    // Tell stream control we're stopped, so he will stop blocking the thread
    // that captures the video (if we're in discarding mode).  Calling
    // CSource::Stop below will hang if the AVICAP capture thread is blocked,
    // because it's going to destroy that thread.
    m_pCapturePin->NotifyFilterState(State_Stopped, 0);
    if (m_pPreviewPin)
        m_pPreviewPin->NotifyFilterState(State_Stopped, 0);

    hr = CSource::Stop();

    // tell our preview pin that it can have the h/w if it wants it.
    // we're through
    if (m_pPreviewPin)
m_pPreviewPin->CapturePinActive(FALSE);

    m_tStart = CRefTime(0L);

    return hr;
}

// create the preview pin we use if we have overlay hardware
//
CVidOverlay * CVidCap::CreateOverlayPin(HRESULT * phr)
{
   DbgLog((LOG_TRACE,2,TEXT("CreateOverlayPin")));

   WCHAR wszPinName[16];
   lstrcpyW(wszPinName, L"Preview");

   CVidOverlay * pOverlay = new CVidOverlay(NAME("Video Overlay Stream"),
this, phr, wszPinName);
   if (!pOverlay)
      *phr = E_OUTOFMEMORY;

   // if initialization failed, delete the stream array
   // and return the error
   //
   if (FAILED(*phr) && pOverlay)
      delete pOverlay, pOverlay = NULL;

   return pOverlay;
}

// create the preview pin we use if we DO NOT have overlay hardware
//
CVidPreview * CVidCap::CreatePreviewPin(HRESULT * phr)
{
   DbgLog((LOG_TRACE,2,TEXT("CreatePreviewPin")));

   WCHAR wszPinName[16];
   lstrcpyW(wszPinName, L"Preview");

   CVidPreview * pPreview = new CVidPreview(NAME("Video Preview Stream"),
this, phr, wszPinName);
   if (!pPreview)
      *phr = E_OUTOFMEMORY;

   // if initialization failed, delete the stream array
   // and return the error
   //
   if (FAILED(*phr) && pPreview)
      delete pPreview, pPreview = NULL;

   return pPreview;
}

//
// IAMVfwCaptureDialogs  implementation
//

// Does this driver support a particular dialog box?
//
HRESULT CVidCap::HasDialog(int iDialog)
{
    if (iDialog == VfwCaptureDialog_Source)
return (m_pCapturePin->m_SupportsVideoSourceDialog ? S_OK : S_FALSE);
    else if (iDialog == VfwCaptureDialog_Format)
return (m_pCapturePin->m_SupportsVideoFormatDialog ? S_OK : S_FALSE);
    else if (iDialog == VfwCaptureDialog_Display)
return (m_pCapturePin->m_SupportsVideoDisplayDialog ? S_OK : S_FALSE);
    else
return E_INVALIDARG;
}

// Show a particular dialog box of the driver
//
HRESULT CVidCap::ShowDialog(int iDialog, HWND hwnd)
{
    HRESULT hr;

    // we can't hold any critical sections while the dialog box is up

    // before bringing up a dialog that could change our format, make
    // sure we're not streaming
    if (m_State != State_Stopped) {
return E_UNEXPECTED;
    }

    // !!! If the filter starts streaming while the dialog is up, this
    // could cause problems! We aren't protecting against this

    // open the device temporarily - if we don't put the hwnd in our
    // m_hwCapCapturing variable, GetMediaType below won't get the
    // format set by the dialog box
    if (m_pCapturePin->m_hwCapCapturing == NULL)
        m_pCapturePin->m_hwCapCapturing = m_pCapturePin->CreateCaptureWindow(0);
    if (m_pCapturePin->m_hwCapCapturing == NULL)
        return E_FAIL;

    if (iDialog == VfwCaptureDialog_Source)
hr = capDlgVideoSource(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
    else if (iDialog == VfwCaptureDialog_Format)
hr = capDlgVideoFormat(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
    else if (iDialog == VfwCaptureDialog_Display)
hr = capDlgVideoDisplay(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
    else {
hr = E_INVALIDARG;
    }

    // bringing up the Format Dialog can change the format we are capturing
    // with.
    if (hr == NOERROR && iDialog == VfwCaptureDialog_Format) {
        DbgLog((LOG_TRACE,1,TEXT("Dialog changed our output format")));

// now get the new format chosen in the dialog.  First of all,
 // forget any remembered type in m_mt, so that GetMediaType will
// quiz the driver for the format set by the dialog
m_pCapturePin->m_mt.SetType(&GUID_NULL);
CMediaType cmt;
m_pCapturePin->GetMediaType(&cmt);
// now put the media type back in case this new type doesn't take
if (m_pCapturePin->IsConnected())
    m_pCapturePin->m_mt.SetType(&MEDIATYPE_Video);

        // If we are connected to somebody, make sure they like it
        if (m_pCapturePin->IsConnected())
    hr = m_pCapturePin->GetConnected()->QueryAccept(&cmt);

if (hr == NOERROR) {
    hr = m_pCapturePin->SetMediaType(&cmt);
            // Now reconnect us so the graph starts using the new format
    if (hr == NOERROR)
                m_pCapturePin->Reconnect(TRUE);
}
    }

    // leave the driver open so we can connect quickly.  We'll close the driver
    // before we start streaming

    return hr;
}

// used to send secret messages to a capture driver. Use at your own risk
//
HRESULT CVidCap::SendDriverMessage(int iDialog, int uMsg, long dw1, long dw2)
{
return E_NOTIMPL;// too scary
}

// *
// * Implements CVidStream - manages the output pin
// *

//
// CVidStream::Constructor
//
// keep the driver index to open.
// Well behaved filters are supposed to only hold resources (like opening
// the capture driver) when they are streaming, so we will open the
// capture driver temporarily only (until we are streaming), and then close
// it again right away.  Drivers usually have no memory across opens, so
// we'll have to remember what format we want to capture with and when we
// finally open it for real, send that format to the driver, as it will
// have long forgotten.
// !!! If we really cared about performance, we wouldn't open and close
// the driver so much.  That takes forever.  Also, we do extra memory copies
// that slow things down.  But this is only a sample driver
//
CVidStream::CVidStream(TCHAR            *pObjectName
                      , HRESULT         *phr
                      , CVidCap         *pParentFilter
                      , unsigned int    uiDriverIndex
                      , LPCWSTR          pPinName
                      )
    :CSourceStream(pObjectName, phr, pParentFilter, pPinName),
     m_uiDriverIndex(uiDriverIndex),
     m_plFilled(NULL),
     m_hwCapCapturing(NULL),
     m_fSetFormatCalled(FALSE),
     m_dwMicroSecPerFrame(66667),      // default to 15 fps
     m_uiFramesCaptured(0),
     m_uiFramesSkipped(0),
     m_llTotalFrameSize(0),
     m_uiFramesDelivered(0)
    {

    CAutoLock lock(m_pFilter->pStateLock());

    // for IAMBufferNegotiation - no suggestions so far
    m_propSuggested.cBuffers = -1;
    m_propSuggested.cbBuffer = -1;
    m_propSuggested.cbAlign = -1;
    m_propSuggested.cbPrefix = -1;

    // open the driver temporarily
    m_hwCapCapturing = CreateCaptureWindow(0);
    if (m_hwCapCapturing == NULL) {
        *phr = E_FAIL;
        return;
    }

    // get the name and version of the driver

#ifdef UNICODE
    capDriverGetName(m_hwCapCapturing, m_szName, sizeof(m_szName));
    capDriverGetVersion(m_hwCapCapturing, m_szVersion, sizeof(m_szVersion));
#else
    char sz[giDriverNameStrLen];
    capDriverGetName(m_hwCapCapturing, sz, sizeof(sz));
    MultiByteToWideChar(CP_ACP, 0,
                            sz, -1,
                            m_szName, giDriverNameStrLen);

    capDriverGetVersion(m_hwCapCapturing, sz, sizeof(sz));
    MultiByteToWideChar(CP_ACP, 0,
                            sz, -1,
                            m_szVersion, giDriverVerStrLen);
#endif
    // Establish what dialogs this driver can display.

    CAPDRIVERCAPS DriverCaps;
    capDriverGetCaps(m_hwCapCapturing, &DriverCaps, sizeof(DriverCaps) );
    m_SupportsVideoSourceDialog  = DriverCaps.fHasDlgVideoSource;
    m_SupportsVideoDisplayDialog = DriverCaps.fHasDlgVideoDisplay;
    m_SupportsVideoFormatDialog  = DriverCaps.fHasDlgVideoFormat;
    m_HasOverlay  = DriverCaps.fHasOverlay;
#if 0
    m_SuppliesPalettes = DriverCaps.fDriverSuppliesPalettes;
#endif

    // leave the driver open so we can connect quickly.  We'll close the driver
    // before we start streaming

    DbgLog( (LOG_TRACE, 1, TEXT("CVidStream created") ) );
}

//
// CVidStream::Destructor
//
// we should be inactive before this is called.
CVidStream::~CVidStream(void) {

    CAutoLock lock(m_pFilter->pStateLock());

    ASSERT(!m_pFilter->IsActive());

    if (m_hwCapCapturing)
DestroyCaptureWindow(m_hwCapCapturing);

    DbgLog( (LOG_TRACE, 1, TEXT("CVidStream destroyed") ) );

}

// set the new media type
//
HRESULT CVidStream::SetMediaType(const CMediaType* pmt)
{
    DbgLog((LOG_TRACE,2,TEXT("SetMediaType %x %dbit %dx%d"),
HEADER(pmt->Format())->biCompression,
HEADER(pmt->Format())->biBitCount,
HEADER(pmt->Format())->biWidth,
HEADER(pmt->Format())->biHeight));

    ASSERT(((CVidCap *)m_pFilter)->m_State == State_Stopped);

    // We are being told the frame rate to use.  It is in the VIDEOINFOHEADER
    if (((VIDEOINFOHEADER *)(pmt->pbFormat))->AvgTimePerFrame) {
m_dwMicroSecPerFrame = (DWORD)(((VIDEOINFOHEADER *)(pmt->pbFormat))->
AvgTimePerFrame / 10);
        DbgLog((LOG_TRACE,2,TEXT("SetMediaType: New frame rate is %d us per frame"),
m_dwMicroSecPerFrame));
    }

    // !!! The bit rate to use is in the VIDEOINFOHEADER too, but we can't
    // obey it... we have no programmatic way of setting it, only through
    // a dialog box

    // now reconnect our preview pin to use the same format as us
    Reconnect(FALSE);

    // this will remember the media type in m_mt, and when we open the
    // driver for real, we'll send it this format to use
    return CSourceStream::SetMediaType(pmt);
}

// stop remembering what media type we are supposed to use once we start
// streaming - all bets are off until we connect again... unless somebody
// called SetFormat... we will always use that format from now on
//
HRESULT CVidStream::BreakConnect()
{
    if (m_fSetFormatCalled == FALSE)
        m_mt.SetType(&GUID_NULL);

return CSourceStream::BreakConnect();
}

//
// CheckMediaType
//
// Queries the video driver to see if the format is acceptable
// The only way to query if we support a given format is to set the driver
// to use that format and see if it succeeds or fails.  So we better only
// accept queries until we start streaming, because then we'd actually
// affect the capture!
//
HRESULT CVidStream::CheckMediaType(const CMediaType *pmt) {

    DbgLog((LOG_TRACE,3,TEXT("CheckMediaType")));

    CAutoLock l(&m_cSharedState);
    CAutoLock lock(m_pFilter->pStateLock());

    // bad idea to set the capture format while capturing...
    if (((CVidCap *)m_pFilter)->m_State != State_Stopped)
return E_UNEXPECTED;

    if (pmt == NULL || pmt->Format() == NULL) {
        DbgLog((LOG_TRACE,3,TEXT("Rejecting: type/format is NULL")));
return E_INVALIDARG;
    }

    // we only support MEDIATYPE_Video
    if (*pmt->Type() != MEDIATYPE_Video) {
        DbgLog((LOG_TRACE,3,TEXT("Rejecting: not VIDEO")));
return E_INVALIDARG;
    }

    // check this is a VIDEOINFOHEADER type
    if (*pmt->FormatType() != FORMAT_VideoInfo) {
        DbgLog((LOG_TRACE,3,TEXT("Rejecting: format not VIDINFO")));
        return E_INVALIDARG;
    }

    RECT rcS = ((VIDEOINFOHEADER *)pmt->Format())->rcSource;
    RECT rcT = ((VIDEOINFOHEADER *)pmt->Format())->rcTarget;
    if (!IsRectEmpty(&rcT) && (rcT.left != 0 || rcT.top != 0 ||
HEADER(pmt->Format())->biWidth != rcT.right ||
HEADER(pmt->Format())->biHeight != rcT.bottom)) {
        DbgLog((LOG_TRACE,3,TEXT("Rejecting: can't use funky rcTarget")));
        return VFW_E_INVALIDMEDIATYPE;
    }
    // We don't know what this would be relative to... reject everything
    if (!IsRectEmpty(&rcS)) {
        DbgLog((LOG_TRACE,3,TEXT("Rejecting: can't use funky rcSource")));
        return VFW_E_INVALIDMEDIATYPE;
    }

    // open the driver temporarily
    if (m_hwCapCapturing == NULL)
        m_hwCapCapturing = CreateCaptureWindow(0);
    if (m_hwCapCapturing == NULL)
        return E_FAIL;

    // the only way we can see if the driver supports a given format is to
    // try and set it to use that format.
    LPBITMAPINFOHEADER lpbiCheck = HEADER(pmt->Format());
    DWORD dw = capSetVideoFormat(m_hwCapCapturing, lpbiCheck,
lpbiCheck->biSize +
((lpbiCheck->biBitCount > 8 || lpbiCheck->biClrUsed) ?
(lpbiCheck->biClrUsed * sizeof(PALETTEENTRY)) :
2 ^ lpbiCheck->biBitCount * sizeof(PALETTEENTRY)));

    // leave the driver open so we can connect quickly.  We'll close the driver
    // before we start streaming

    return (dw == TRUE ? NOERROR : VFW_E_INVALIDMEDIATYPE);
}

//
// GetMediaType
//
// Queries the video driver and places an appropriate media type in *pmt
// If we have remembered a type that we are supposed to be using,
// return that one
//
HRESULT CVidStream::GetMediaType(CMediaType *pmt) {

    CAutoLock l(&m_cSharedState);

    // We've been told by somebody to use a particular media type.
    // That's what we'll return
    if (m_mt.IsValid()) {
*pmt = m_mt;
return NOERROR;
    }

    // We may or may not be streaming and have the driver open already
    if (m_hwCapCapturing == NULL)
        m_hwCapCapturing = CreateCaptureWindow(0);
    if (m_hwCapCapturing == NULL)
        return E_FAIL;

    pmt->SetType(&MEDIATYPE_Video);
    pmt->SetFormatType(&FORMAT_VideoInfo);

    DWORD dwFormatSize;
    VIDEOINFOHEADER *pvi;

    dwFormatSize = capGetVideoFormatSize(m_hwCapCapturing);

    ASSERT(dwFormatSize > 0);

    // Find out how big we need to allocate the buffer
#define AllocBufferSize (max(sizeof(VIDEOINFOHEADER) + sizeof(TRUECOLORINFO), \
dwFormatSize+offsetof(VIDEOINFOHEADER,bmiHeader)))

    // Set up the format section of the mediatype to be the right size
    pvi = (VIDEOINFOHEADER *) pmt->AllocFormatBuffer(AllocBufferSize);
#undef AllocBufferSize
    if (pvi == NULL) {
        return E_OUTOFMEMORY;
    }

    // make sure all fields are initially zero
    ZeroMemory((void *)pvi, sizeof(VIDEOINFOHEADER));

    // make a note of the current fps we're doing
    pvi->AvgTimePerFrame = m_dwMicroSecPerFrame * 10;

    // grab the BITMAPINFOHEADER straight in
    // will leave the memory after the last palette entry as zeros.
    capGetVideoFormat(m_hwCapCapturing, &(pvi->bmiHeader), dwFormatSize);

    const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
    pmt->SetSubtype(&SubTypeGUID);
    pmt->SetSampleSize(GetSampleSize(&pvi->bmiHeader));
    pmt->SetTemporalCompression(FALSE);

    // leave the driver open so we can connect quickly.  We'll close the driver
    // before we start streaming

    return NOERROR;
}

//
// OnThreadCreate
//
// Start streaming & reset time samples are stamped with.
HRESULT CVidStream::OnThreadCreate(void) {

    CAutoLock l(&m_cSharedState);

    m_ThreadState = Stopped;

    // we are starting to stream now.  Open the capture driver FOR REAL!
    // Use however many buffers we're supposed to use
    ASSERT(m_hwCapCapturing == NULL);
    m_hwCapCapturing = CreateCaptureWindow(m_propActual.cBuffers);
    if (m_hwCapCapturing == NULL) {
            return E_FAIL;
    }

    // m_mt is the format we connected with.  Tell the driver to use
    // that format as its capture format
    LPBITMAPINFOHEADER lpbi = HEADER(m_mt.Format());
    DWORD dwSize = lpbi->biSize +
((lpbi->biBitCount > 8 || lpbi->biClrUsed) ?
(lpbi->biClrUsed * sizeof(PALETTEENTRY)) :
(2 ^ lpbi->biBitCount * sizeof(PALETTEENTRY)));
    DWORD dw = capSetVideoFormat(m_hwCapCapturing, lpbi, dwSize);
    ASSERT(dw == TRUE);// we were promised this would work!

    m_plFilled = new CVideoBufferList( m_mt.lSampleSize
                                     , m_dwMicroSecPerFrame
                                     , (CVidCap *)m_pFilter
     , m_propActual.cBuffers
                                     );
    if (m_plFilled == NULL) {
        return E_OUTOFMEMORY;
    }

    // IAMDroppedFrames:  every time you start streaming, reset your stats
    m_uiFramesCaptured = 0;
    m_uiFramesSkipped = 0;
    m_llTotalFrameSize = 0;
    m_uiFramesDelivered = 0;

    return NOERROR;
}

// Inactive - overridden to replace the call to CAMThread::Close with
// different code that will dispatch messages waiting for the thread to
// die.  (explained below)
//
HRESULT CVidStream::Inactive(void) {

    CAutoLock lock(m_pFilter->pStateLock());

    HRESULT hr;

    // do nothing if not connected - its ok not to connect to
    // all pins of a source filter
    if (!IsConnected()) {
        return NOERROR;
    }

    // !!! need to do this before trying to stop the thread, because
    // we may be stuck waiting for our own allocator!!!

    hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator
    if (FAILED(hr)) {
return hr;
    }

    if (ThreadExists()) {
hr = Stop();

if (FAILED(hr)) {
    return hr;
}

hr = Exit();
if (FAILED(hr)) {
    return hr;
}

        // when our main thread shuts down the capture thread, the capture
        // thread will destroy the capture window it made, and this will cause
// USER to send messages to our main thread, and if we're blocked
// waiting for the capture thread to go away, we will deadlock
// preventing user from sending us the messages, and thus our thread
// will never go away.  We need to dispatch messages while waiting
        if (m_hThread) {
    // !!! It's more efficient to use MsgWaitForMultipleObjects, I know.
            while (WaitForSingleObject(m_hThread, 50) == WAIT_TIMEOUT) {
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
        }
    }
            CloseHandle(m_hThread);
            m_hThread = 0;
        }
    }

    // hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator
    //if (FAILED(hr)) {
    //return hr;
    //}

    return NOERROR;
}

//
// OnThreadDestroy
//
// Free the list of completed buffers and stop streaming
// Attempts to stop streaming and destroy the window, even in error
// cases.
HRESULT CVidStream::OnThreadDestroy(void) {

    CAutoLock l(&m_cSharedState);

    ASSERT(m_ThreadState == Stopped);

    // no longer streaming.  close the driver
    BOOL fWindowGone = DestroyCaptureWindow(m_hwCapCapturing);
    m_hwCapCapturing = NULL;

    delete m_plFilled, m_plFilled = NULL;

    DbgLog((LOG_TRACE, 1, TEXT("Frames Captured: %d"), m_uiFramesCaptured));
    DbgLog((LOG_TRACE, 1, TEXT("Frames Skipped: %d"), m_uiFramesSkipped));
    DbgLog((LOG_TRACE, 1, TEXT("Frames Delivered: %d"), m_uiFramesDelivered));

    if (!fWindowGone) {
        return E_UNEXPECTED;
    } else {
        return NOERROR;
    }
}

//
// DecideBufferSize
//
// Get an allocator that we have been asked to get (through
// IAMBufferNegotiation) or that we will be happy with
// Check that the allocator can give us appropriately sized buffers
// Always called after format negotiation.
//
HRESULT CVidStream::DecideBufferSize(IMemAllocator *pAlloc,
                                     ALLOCATOR_PROPERTIES *pProperties)
{
    CAutoLock lock(m_pFilter->pStateLock());
    CAutoLock l(&m_cSharedState);
    ASSERT(pAlloc);
    ASSERT(pProperties);
    HRESULT hr = NOERROR;

    // use the app's numbers, if we were given some through IAMBufferNegotiation
    // otherwise, use some default

    if (m_propSuggested.cBuffers > 0)
        pProperties->cBuffers = m_propSuggested.cBuffers;
    else
        pProperties->cBuffers = 32;// !!!or whatever

    if (m_propSuggested.cbBuffer > 0)
        pProperties->cbBuffer = m_propSuggested.cbBuffer;
    else
        pProperties->cbBuffer = m_mt.lSampleSize;

    if (m_propSuggested.cbAlign > 0)
        pProperties->cbAlign = m_propSuggested.cbAlign;

    if (m_propSuggested.cbPrefix > 0)
        pProperties->cbPrefix = m_propSuggested.cbPrefix;

    ASSERT(pProperties->cbBuffer);

    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted

    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProperties,&Actual);
    if (SUCCEEDED(hr)) {

        // Is this allocator unsuitable
        if (Actual.cbBuffer < pProperties->cbBuffer) {
            hr = E_FAIL;
        }

// remember what properties the allocator is using... somebody
// might ask later
m_propActual = Actual;
    }
    return hr;
}

//
// GetSampleSize
//
// Given a BITMAPINFOHEADER, calculates the sample size needed.
long CVidStream::GetSampleSize(LPBITMAPINFOHEADER pbmi) {

    long lSize;

    if (pbmi->biSizeImage > 0) {

        lSize = pbmi->biSizeImage;

    }
    else {      // biSizeImage is allowed to be zero for uncompressed formats,
                // so do the maths ourselves...

        lSize = (pbmi->biWidth *
                 pbmi->biHeight *
                 pbmi->biBitCount) / 8 + 1;
        if (lSize < 0) { // biHeight was negative
            lSize *= -1;
        }
    }

    // round up to nearest DWORD
    int rem = lSize % sizeof(DWORD);
    if (rem)
        lSize += sizeof(DWORD) - rem;

    return lSize;
}

//
// CreateCaptureWindow
//
// Create a hidden AVICap window, and make sure it is configured appropriately
// Returns NULL on failure.
// successful calls should be balanced with calls to DestroyCaptureWindow()
// Creates the AVICap window with (up to) lBufferCount no. of buffers.
// use lBufferCount = 0, when you only wish to interrgoate the the driver, or
// specify a number of buffers, if you actually want to capture.
//
HWND CVidStream::CreateCaptureWindow(long lBufferCount)
{

    CAutoLock lock(&m_cSharedState);

    BOOL bErr;  //return code of capXXX calls

    HWND hwndCapture;   // The window to return

    hwndCapture = capCreateCaptureWindow(NULL,       // No name
                                         0,          // no style.
                                                     // defaults to invisible
                                         0, 0, 150, 150, // an arbitrary size
                                         0,          // no parent
                                         0);         // don't care about the id

    if (!hwndCapture) {
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Window could not be created") ));
        return NULL;
    }

    bErr = capDriverConnect(hwndCapture, m_uiDriverIndex);
    if (!bErr) {
        DestroyWindow(hwndCapture);
        DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Driver failed to connect") ) );
        return NULL;
    }
    DbgLog((LOG_TRACE, 2, TEXT("Driver Connected") ));

    CAPTUREPARMS cp;
    capCaptureGetSetup(hwndCapture, &cp, sizeof(cp) ); // get the current defaults

    cp.dwRequestMicroSecPerFrame = m_dwMicroSecPerFrame; // Set desired frame rate
    cp.fMakeUserHitOKToCapture   = FALSE;
    cp.fYield                    = TRUE;                 // we want capture on a
                                                         // background thread.
    cp.wNumVideoRequested        = (WORD) lBufferCount;  // we may get less than
                                                         // this - no problem
    cp.fCaptureAudio             = FALSE;
    cp.vKeyAbort                 = 0;                    // If no key is provided,
                                                         // it won't stop...
    cp.fAbortLeftMouse           = FALSE;
    cp.fAbortRightMouse          = FALSE;
    cp.fLimitEnabled             = FALSE;                // we want to stop
    cp.fMCIControl               = FALSE;

    capCaptureSetSetup(hwndCapture, &cp, sizeof(cp) );

    capSetCallbackOnVideoStream(hwndCapture, &VideoCallback);
    capSetCallbackOnFrame(hwndCapture, &VideoCallback);  // also use for single
                                                         // frame capture

#if 0
    CAPSTATUS    cs;
    ZeroMemory(&cs, sizeof(cs));
    capGetStatus(hwndCapture, &cs, sizeof(cs));

    // try to see if the driver uses palettes
    if (((cs.hPalCurrent != NULL) || (cs.fUsingDefaultPalette))) {
m_UsesPalettes = TRUE;
    } else {
        m_UsesPalettes = FALSE;
    }
    if (m_UsesPalettes && m_SuppliesPalettes) {
capPaletteAuto(hwndCapture, 10, 236);
    }
#endif

    SetWindowLong(hwndCapture, GWL_USERDATA, (LONG) this);

    return hwndCapture;
}

//
// DestroyCaptureWindow()
//
// Disconnect the driver before destroying the window.
BOOL CVidStream::DestroyCaptureWindow(HWND hwnd)
{

    ASSERT(hwnd != NULL);

    // !!! why is this failing?
    BOOL bDriverDisconnected = capDriverDisconnect(hwnd);
    DbgLog(( LOG_ERROR|LOG_TRACE, 2
           , TEXT("Driver disconnect: %x"), bDriverDisconnected) );

    BOOL bWindowGone = DestroyWindow(hwnd);
    DbgLog((LOG_ERROR|LOG_TRACE, 2, TEXT("Window destroy: %x"), bWindowGone) );

    return (bDriverDisconnected && bWindowGone);
}

//
// VideoCallback
//
// The AVICap Video callback. Keep a copy of the buffer we are given
// May be called after the worker thread, or even the pin has gone away,
// depending on AVICap's internal timing. Therefore be very careful with the
// pointers we use.
//
LRESULT CALLBACK CVidStream::VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr)
{

    CVidStream *pThis = (CVidStream *) GetWindowLong(hwnd, GWL_USERDATA);

    ASSERT(pThis);

    if (pThis->m_plFilled == NULL) {    // The filled list has gone away.
                                        // ignore this buffer
        return (LRESULT) TRUE;
    }
    else {
        pThis->m_plFilled->Add(lpVHdr);
    }

    //DbgLog((LOG_TRACE,5,TEXT("Got a buffer back!")));

    return (LRESULT) TRUE;
}

// Override to handle quality messages
STDMETHODIMP CVidStream::Notify(IBaseFilter * pSender, Quality q)
{
    // if q.Late.RefTime.QuadPart >0 then skip ahead that much.
    // thereafter adjust the time per frame by a factor of
    // 1000/q.Proportion (watch for truncation of fractions
    // do the multiply first!

    // Not Yet Implemented :-)

    return NOERROR;
}

// CSource expects all its pins to derive from CSourceStream. Since
// this sample doesn't do this, we implment QueryId here and FindPin
// on the filter to provide matching implementations

STDMETHODIMP CVidStream::QueryId(
    LPWSTR * Id)
{
    return CBaseOutputPin::QueryId(Id);
}

//
// DoBufferProcessingLoop
//
// Replace the loop in CSourceStream with something of my own, so that I can
// wait for buffers & commands.
HRESULT CVidStream::DoBufferProcessingLoop(void) {

    DbgLog((LOG_TRACE,2,TEXT("*** Entering DoBufferProcessingLoop")));

    HANDLE haWaitObjects[2];
    {
        CAutoLock l(&m_cSharedState);

        haWaitObjects[0] = GetRequestHandle();  // command handle first so that
                                                // it has priority over buffers
        haWaitObjects[1] = m_plFilled->GetWaitHandle();
    }

    for (;;) {

        // wait for commands or buffers.  This thread created the capture
// window, so we have to dispatch messages frequently on this thread
// or any other thread sending any message to the capture window will
// hang
// !!! It's more efficient to use MsgWaitForMultipleObjects, I know
        DWORD dwWaitObject
            = WaitForMultipleObjects(2, haWaitObjects, FALSE, 50);

if (dwWaitObject == WAIT_TIMEOUT) {
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }
} else if (dwWaitObject == WAIT_OBJECT_0) {    // thread command request

    Command com;

            EXECUTE_ASSERT(CheckRequest(&com));
            switch (com) {
            case CMD_RUN:

com = GetRequest();

                if (m_ThreadState != Running) {
                    DbgLog((LOG_TRACE,1,TEXT("*** STARTING CAPTURE")));
    // !!! This function may take a while to initialize
    // capture, so we may not start seeing frames for 1/3 of
    // a second or so.  Unfortunately, unless we want a dialog
    // box to pop up saying "press OK to start" there's no good
    // way of starting capture at any known point in time
                    capCaptureSequenceNoFile(m_hwCapCapturing);
                }
                m_ThreadState = Running;
                Reply(NOERROR);
                break;

            case CMD_PAUSE:

                DbgLog((LOG_TRACE,1,TEXT("CMD_PAUSE")));

com = GetRequest();

                switch (m_ThreadState) {
                case Stopped:
    // first thing sent will be a discontinuity
    m_plFilled->m_fLastSampleDiscarded = TRUE;

    // init our time stamping variables
    m_plFilled->m_rtLastStartTime = -1;
    m_plFilled->m_llLastFrame = -1;
    m_plFilled->m_llFrameOffset = 0;
    m_plFilled->m_fReRun = FALSE;

    m_ThreadState = Paused; // mark that we are paused
                    break;
                case Running:
                    DbgLog((LOG_TRACE,1,TEXT("*** STOPPING CAPTURE")));
    // we just went from RUN to PAUSE.  If we go to
    // RUN again without STOPping first, we will be in
    // a situation where the graph was run->pause->run
    m_plFilled->m_fReRun = TRUE;
                    capCaptureStop(m_hwCapCapturing);
                    break;
                default:
                    // null op
                    break;
                }
                m_ThreadState = Paused;
                Reply(NOERROR);
                break;

            case CMD_STOP:

                if (m_ThreadState == Running) {
                    DbgLog((LOG_TRACE,1,TEXT("*** STOPPING CAPTURE")));
                    capCaptureStop(m_hwCapCapturing);
                }
                m_ThreadState = Stopped;
                //DbgLog((LOG_TRACE, 1, TEXT("Seen Stop command")));
                // don't reply here as that is done by CSourceStream
                return NOERROR;

            default:

                ASSERT(!"Unexpected thread command");
com = GetRequest();
                break;
            }

        } else if (dwWaitObject == (WAIT_OBJECT_0 + 1)) { // m_plFilled

            // Process the buffer we've just been signalled on
            IMediaSample *pSample;

            // get a buffer to put this video data in
            HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0);
            if (FAILED(hr)) {
                continue;
            }

            hr = FillBuffer(pSample);

    // !!! If this fails, we're supposed to stop delivering
            Deliver(pSample);

            pSample->Release();

        } else {  // get out - otherwise we will infinite loop...

            DbgLog((LOG_TRACE, 1
                   , TEXT("Unexpected buffer/command wait return: %d")
                   , dwWaitObject));
            return E_UNEXPECTED;
        }
    }

    ASSERT(m_ThreadState == Stopped);

    return NOERROR;
}

//
// FillBuffer
//
// Take the buffer from the head of the video buffer list.
// We will only be called when there is such a buffer
//
HRESULT CVidStream::FillBuffer(IMediaSample *pSample) {

    CAutoLock l(&m_cSharedState);

    HRESULT hr = m_plFilled->RemoveHeadIntoSample(pSample);

    return hr;
}

//
// NonDelegatingQueryInterface
//
// Expose all of our interfaces on the pin
//
STDMETHODIMP CVidStream::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
    CheckPointer(ppv,E_POINTER);

    if (riid == IID_IAMStreamControl) {
return GetInterface((LPUNKNOWN)(IAMStreamControl *)this, ppv);
    } else if (riid == IID_IAMStreamConfig) {
return GetInterface((LPUNKNOWN)(IAMStreamConfig *)this, ppv);
    } else if (riid == IID_IAMVideoCompression) {
return GetInterface((LPUNKNOWN)(IAMVideoCompression *)this, ppv);
    } else if (riid == IID_IAMDroppedFrames) {
return GetInterface((LPUNKNOWN)(IAMDroppedFrames *)this, ppv);
    } else if (riid == IID_IAMBufferNegotiation) {
return GetInterface((LPUNKNOWN)(IAMBufferNegotiation *)this, ppv);
    } else if (riid == IID_IKsPropertySet) {
return GetInterface((LPUNKNOWN)(IKsPropertySet *)this, ppv);
    }

    return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}

// *
// * CVideoBufferList
// *

//
// CVideoBufferList::Constructor
//
CVideoBufferList::CVideoBufferList( int iBufferSize
                                  , DWORD dwMicroSecPerFrame
                                  , CVidCap *pFilter
                                  , int iBuffers
                                  )
    :m_dwMicroSecPerFrame(dwMicroSecPerFrame),
     m_FirstBuffer(TRUE),
     m_pFilter(pFilter),
     m_evList(TRUE),
     m_iPreviewCount(0),
     m_lFilled(NAME("Pending, full, buffers"),
               DEFAULTCACHE),   // default cache
     m_lFree(NAME("Empty buffers"),
               DEFAULTCACHE)    // default cache
{
    for (int i=0; i < iBuffers; i++) {

        CBuffer *pBuffer = new CBuffer(iBufferSize);
        if (pBuffer == NULL) {
            return;
        }
        m_lFree.AddTail(pBuffer);

    }
}

//
// CVideoBufferList::Destructor
//
CVideoBufferList::~CVideoBufferList() {

    while (m_lFree.GetCount() > 0) {            // free buffers on the free list...

        CBuffer *pBuff = m_lFree.RemoveHead();
        delete pBuff;
    }

    DbgLog((LOG_TRACE, 1
           , TEXT("Filled frames not sent before stop issued: %d")
           , m_lFilled.GetCount()));

    while (m_lFilled.GetCount() > 0) { //... then free buffers on filled list
                                       // - we don't care about the data they hold.

        CBuffer *pBuff = m_lFilled.RemoveHead();
        delete pBuff;
    }
}

//
// Add
//
// Add a video buffer to this list. gets a free buffer from m_lFree, copies
// the video data into it and then puts it on m_lFilled.
// if m_lFree is empty, fail silently, effectively skipping the buffer.
HRESULT CVideoBufferList::Add(LPVIDEOHDR lpVHdr) {

    CAutoLock lck(&m_ListCrit);
    CRefTime rt;

    //DbgLog((LOG_TRACE,4,TEXT("driver time: %dms"), lpVHdr->dwTimeCaptured));

    // Time stamp this sample with the graph's clock time when this sample
    // is captured. (no clock? use the driver time)
    // !!! hopefully the driver latency isn't too bad! It should really be
    // compensated for
    if (FAILED(m_pFilter->StreamTime(rt)))// current time
        rt = CRefTime((LONG)lpVHdr->dwTimeCaptured);    // init with ms

    // now ask IAMStreamControl if we need to bother delivering this sample
    // I don't have a sample around right now, so I'll make up one.  All it
    // needs is the time stamp.
    // !!! this is kinda hacky
    CMemAllocator all(TEXT("Test"), NULL, NULL);
    CMediaSample Sample(NULL, &all, NULL);
    CRefTime rtEnd = rt + CRefTime((LONGLONG)m_dwMicroSecPerFrame * 10);
    Sample.SetTime((REFERENCE_TIME *)&rt, (REFERENCE_TIME *)&rtEnd);
    int iStreamState = m_pFilter->m_pCapturePin->CheckStreamState(&Sample);
    if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING) {
  // DbgLog((LOG_TRACE,3,TEXT("*VIDCAP ON")));
    } else {
  // DbgLog((LOG_TRACE,3,TEXT("*VIDCAP OFF")));
  m_fLastSampleDiscarded = TRUE;// next one is discontinuity
    }

    // we are faking up a preview pin and supposed to send it a frame to
    // give out every once in a while when we have free time and copying
    // the data for preview will NOT HURT CAPTURE PERFORMANCE.  Well, if
    // our capture pin is currently off, we have nothing to lose.  If
    // we have no frames on the filled list pending delivery, we'll assume
    // we have spare time and can afford to send a preview frame.  In any
    // case, send a preview frame at least once every 30 frames. (!!!)
    // !!! Ideally, we don't necessarily want to send this frame as a preview,
    // it may be many seconds old if we have lots of buffering, we want to
    // send the most recently captured frame.  I don't do that here.
    if (m_pFilter->m_pPreviewPin) {
if (iStreamState == m_pFilter->m_pCapturePin->STREAM_DISCARDING ||
m_lFilled.GetCount() == 0 || m_iPreviewCount++ == 30) {
    m_iPreviewCount = 0;// reset
    m_pFilter->m_pPreviewPin->ReceivePreviewFrame(lpVHdr->lpData,
lpVHdr->dwBufferLength);
}
    }

    // what frame number is this (based on the time captured)?  Round
    // such that if frames 1 and 2 are expected at 33 and 66ms, anything
    // from 17 to 49 will be considered frame 1.
    //
    // frame = (us + 1/2(us per frame)) / (us / frame)
    //
    // then we add an offset if we so desire
    //
    LONGLONG llFrame = ((lpVHdr->dwTimeCaptured * 1000 +
m_dwMicroSecPerFrame / 2) / m_dwMicroSecPerFrame)
+ m_llFrameOffset;

    // NOTE:  If the graph is RUN->PAUSE->RUN again we need to
    // continue sending frame numbers (in the MediaTime) where we
    // left off before pausing.  BUT... the driver has been stopped
    // and started and is numbering the frames back at zero again!
    // So we need to notice that the graph has been paused and re-run
    // without stopping in between, and then add as an offset to all
    // future frame numbers, the last frame number sent before the
    // pause.
    if (m_fReRun && llFrame < m_llLastFrame) {
m_llFrameOffset = m_llLastFrame;
llFrame += m_llFrameOffset;
m_fReRun = FALSE;
    }

    // the time stamps we get from the drivers are not always accurate,
    // and we may think we see frame 0, 2, 2, 3, 5, 5, 6, 7, but if
    // we just pretended the first 2 was frame 1 and the first 5 was
    // frame 4, we wouldn't skip any frames or send the same frame
    // twice
    if (llFrame == m_llLastFrame + 2)
llFrame -= 1;

    // we're supposed to deliver this frame, and we can
    if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING &&
m_lFree.GetCount() > 0) {

// Never send something backwards in time from the last thing
// sent.  This can happen with live sources if you run, then
// pause, then run the graph again.  The first time stamp after
// the second run might have a time less than the last time stamp
// delivered before the pause.  Also, because the graph is run
// 50ms or so before time 0, it's possible for a frame captured
// right away to have a time stamp less than zero.  We don't want
// to deliver such samples, but we don't want to consider them
// dropped frames either.  We just pretend they never existed.

if (rt >= 0 && rt >= m_rtLastStartTime) {

    // Just like above, where we didn't deliver or skip any frame
    // whose time stampes seemed to go backwards in time, we can't
    // deliver a frame whose MediaTime stamps don't increase
    // monotonically.  We don't want to deliver a duplicate media time
    // that we already sent, but we don't want to consider them
    // dropped frames either.  We just pretend they never existed.
    if (llFrame > m_llLastFrame) {

// oops, this frame number is more than one frame later than
// the last frame; we've skipped some frames, probably due
// to starving the driver who couldn't capture in time
if (llFrame > m_llLastFrame + 1) {
            m_pFilter->m_pCapturePin->m_uiFramesSkipped +=
(DWORD)(llFrame - m_llLastFrame - 1);
}

                CBuffer *pBuff = m_lFree.RemoveHead();
                pBuff->CopyBuffer(lpVHdr, rt, llFrame);

        // remember how to set the sample flags later
        pBuff->m_fSyncPoint = lpVHdr->dwFlags & VHDR_KEYFRAME;
        // !!! Technically speaking, you should set a discontinuity
        // after you drop (skip) a frame, too, but I'm probably dealing
        // with all keyframes and it doesn't matter so much if a frame
        // is skipped
        pBuff->m_fDiscontinuity = m_fLastSampleDiscarded;

                m_lFilled.AddTail(pBuff);
                if (m_lFilled.GetCount() == 1) {
                    m_evList.Set();
                }
                m_pFilter->m_pCapturePin->m_uiFramesCaptured++;
         m_fLastSampleDiscarded = FALSE;

        // remember the last Time and MediaTime we used
        m_rtLastStartTime = rt;
        m_llLastFrame = llFrame;
    }
}

    // we're supposed to deliver this frame, but we can't... it's skipped
    } else if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING) {

        m_pFilter->m_pCapturePin->m_uiFramesSkipped++;

    // we are discarding this frame.  Don't count it as captured or skipped
    // We must make a note that we've seen this frame, so we don't think that
    // we skipped all the frames we didn't send because stream control was off
    } else {
m_llLastFrame = llFrame;
    }

    return NOERROR;
}

//
// RemoveHeadIntoSample
//
// Copy the head of the filled list into the supplied IMediaSample.
// Fail with E_UNEXPECTED if called on an empty m_lFilled;
// return S_FALSE if you don't want the sample delivered
//
HRESULT CVideoBufferList::RemoveHeadIntoSample(IMediaSample *pSample)
{
    HRESULT hr;
    CAutoLock lck(&m_ListCrit);

    if (m_lFilled.GetCount() < 1) {
        hr = E_UNEXPECTED;
    } else {

        CBuffer *pBuff = m_lFilled.RemoveHead();
        if (m_lFilled.GetCount() == 0) {
            m_evList.Reset();
        }

        BYTE *pSampleBuffer;
        hr = pSample->GetPointer(&pSampleBuffer);
        if (SUCCEEDED(hr)) {

    LONG lSampleSize = pBuff->GetSize();
            ASSERT(pBuff->GetSize() <= pSample->GetSize());

    // Copy the captured data
            CopyMemory((void *)pSampleBuffer, pBuff->GetPointer(), lSampleSize);

            // set all the sample flags
    pSample->SetActualDataLength(lSampleSize);
          pSample->SetSyncPoint(pBuff->m_fSyncPoint);
          pSample->SetDiscontinuity(pBuff->m_fDiscontinuity);
          pSample->SetPreroll(FALSE);

            FILTER_STATE State;
            m_pFilter->GetState(0, &State);

            CRefTime rtStart;

    // set the time stamp of this sample
     rtStart = pBuff->GetCaptureTime();
            CRefTime rtEnd = rtStart + CRefTime((LONGLONG)m_dwMicroSecPerFrame *
10);
            DbgLog((LOG_TRACE,4,TEXT("Sample Time: %dms %dms"),
rtStart.Millisecs(), rtEnd.Millisecs()));
            ASSERT(rtStart <= rtEnd);
            pSample->SetTime((REFERENCE_TIME*)&rtStart,
                             (REFERENCE_TIME*)&rtEnd);

    // also set the media time... which is supposed to be the
    // frame number
     LONGLONG llFrame = pBuff->GetCaptureFrame();
    LONGLONG llFrameE = llFrame + 1;
            pSample->SetMediaTime(&llFrame, &llFrameE);
            DbgLog((LOG_TRACE,4,TEXT("Media Time: %d"),llFrame));

            m_pFilter->m_pCapturePin->m_uiFramesDelivered++;
            m_pFilter->m_pCapturePin->m_llTotalFrameSize += lSampleSize;
            m_lFree.AddTail(pBuff);
        }
    }

    return hr;
}

//
// CBuffer::Constructor
//
//Get a new buffer of the maximum size we will handle
CVideoBufferList::CBuffer::CBuffer(int iBufferSize) {

    m_pData = new BYTE[iBufferSize];

    // Set the two length fields to the maximum size
    m_iCaptureDataLength = m_iDataLength = iBufferSize;

}

//
// CVideoDataBuffer::Destructor
//
CVideoBufferList::CBuffer::~CBuffer() {

    delete m_pData;
}

//
// CopyBuffer
//
// Copy the supplied data in lpVHdr->lpData to this Buffer
// rt is the time stamp
void CVideoBufferList::CBuffer::CopyBuffer(LPVIDEOHDR lpVHdr, CRefTime& rt, LONGLONG llFrame) {

    ASSERT((DWORD) m_iDataLength >= lpVHdr->dwBufferLength);

    m_rt = rt;// time stamp
    m_llFrame = llFrame;// frame number

    // Copy the captured video buffer, and remember its length
    CopyMemory(m_pData, lpVHdr->lpData, (m_iCaptureDataLength = lpVHdr->dwBufferLength));

}

// Reconnect our pin.  We would like to change our output format
// Sometimes extra filters have to be placed in between, like when you
// switch from 8 bit to 16 bit RGB and you have to put a colour converter
// between you and the renderer you were connected to.  In this case,
// reconnect won't work, you have to explicitly intelligently connect
// them yourself.
//
void CVidStream::Reconnect(BOOL fCapturePinToo)
{
    if (IsConnected() && fCapturePinToo) {
        DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our streaming pin")));
        CMediaType cmt;
GetMediaType(&cmt);
if (S_OK == GetConnected()->QueryAccept(&cmt)) {
            ((CVidCap *)m_pFilter)->m_pGraph->Reconnect(this);
} else {
            // This will fail if we switch from 8 bit to 16 bit RGB connected
            // to a renderer that needs a colour converter inserted to do 16 bit
    // Oh boy.  We're going to have to get clever and insert some
    // filters between us to help us reconnect
            DbgLog((LOG_TRACE,1,TEXT("Whoa! We *really* need to reconnect!")));
    IPin *pCon = GetConnected();
    pCon->AddRef();// or it will go away in Disconnect
    ((CVidCap *)m_pFilter)->m_pGraph->Disconnect(GetConnected());
    ((CVidCap *)m_pFilter)->m_pGraph->Disconnect(this);
    IGraphBuilder *pFG;
    HRESULT hr = ((CVidCap *)m_pFilter)->m_pGraph->QueryInterface(
IID_IGraphBuilder, (void **)&pFG);
    if (hr == NOERROR) {
        hr = pFG->Connect(this, pCon);
pFG->Release();
    }
    pCon->Release();
    if (hr != NOERROR)
                DbgLog((LOG_ERROR,1,TEXT("*** RECONNECT FAILED! ***")));
    // !!! We need to notify application that graph is different
 }
 // when this pin gets reconnected it will call us again to do the
 // other two pins
 return;
    }

    // Now reconnect the overlay pin
    CVidOverlay *pOverlayPin = ((CVidCap *)m_pFilter)->m_pOverlayPin;
    if (pOverlayPin && pOverlayPin->IsConnected()) {
        DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our overlay pin")));
        CMediaType cmt;
pOverlayPin->GetMediaType(0, &cmt);
if (S_OK == pOverlayPin->GetConnected()->QueryAccept(&cmt)) {
    ((CVidCap *)m_pFilter)->m_pGraph->Reconnect(pOverlayPin);
} else {
    // Huh?
    ASSERT(FALSE);
}
    }

    // Now reconnect the non-overlay preview pin
    CVidPreview *pPreviewPin = ((CVidCap *)m_pFilter)->m_pPreviewPin;
    if (pPreviewPin && pPreviewPin->IsConnected()) {
        DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our preview pin")));
        CMediaType cmt;
pPreviewPin->GetMediaType(0, &cmt);
if (S_OK == pPreviewPin->GetConnected()->QueryAccept(&cmt)) {
    ((CVidCap *)m_pFilter)->m_pGraph->Reconnect(pPreviewPin);
} else {
    // Oh boy.  We're going to have to get clever and insert some
    // filters between us to help us reconnect
            DbgLog((LOG_TRACE,1,TEXT("Whoa! We *really* need to reconnect!")));
    IPin *pCon = pPreviewPin->GetConnected();
    pCon->AddRef();// or it will go away in Disconnect
    ((CVidCap *)m_pFilter)->m_pGraph->Disconnect(
pPreviewPin->GetConnected());
    ((CVidCap *)m_pFilter)->m_pGraph->Disconnect(pPreviewPin);
    IGraphBuilder *pFG;
    HRESULT hr = ((CVidCap *)m_pFilter)->m_pGraph->QueryInterface(
IID_IGraphBuilder, (void **)&pFG);
    if (hr == NOERROR) {
        hr = pFG->Connect(pPreviewPin, pCon);
pFG->Release();
    }
    pCon->Release();
    if (hr != NOERROR)
                DbgLog((LOG_ERROR,1,TEXT("*** RECONNECT FAILED! ***")));
    // !!! We need to notify application that graph is different
}
    }
}

//=============================================================================

//
// IAMStreamConfig stuff
//

// Tell the capture card to capture a specific format.  If it isn't connected,
// then it will use that format to connect when it does.  If already connected,
// then it will reconnect with the new format.
//
HRESULT CVidStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
    HRESULT hr;

    if (pmt == NULL)
return E_POINTER;

    // To make sure we're not in the middle of start/stop streaming
    CAutoLock lock(m_pFilter->pStateLock());
    CAutoLock l(&m_cSharedState);

    DbgLog((LOG_TRACE,2,TEXT("IAMStreamConfig::SetFormat %x %dbit %dx%d"),
HEADER(pmt->pbFormat)->biCompression,
HEADER(pmt->pbFormat)->biBitCount,
HEADER(pmt->pbFormat)->biWidth,
HEADER(pmt->pbFormat)->biHeight));

    if (((CVidCap *)m_pFilter)->m_State != State_Stopped)
return E_UNEXPECTED;

    // If this is the same format as we already are using, don't bother
    CMediaType mt;
    GetMediaType(&mt);
    if (mt == *pmt) {
return NOERROR;
    }

    // see if we like this type
    if ((hr = CheckMediaType((CMediaType *)pmt)) != NOERROR) {
DbgLog((LOG_TRACE,2,TEXT("SetFormat rejected")));
return hr;
    }

    // If we are connected to somebody, make sure they like it
    // !!! A video renderer might reject going to 16 bit from 8 bit, but
    // reconnecting would still work, by pulling in a colour converter
    if (IsConnected()) {
hr = GetConnected()->QueryAccept(pmt);
if (hr != NOERROR) {
    DbgLog((LOG_TRACE,2,TEXT("SetFormat rejected by the other pin")));
    return E_INVALIDARG;
}
    }

    // OK, we're using it
    hr = SetMediaType((CMediaType *)pmt);

    // Changing the format means reconnecting if necessary
    if (hr == NOERROR) {
m_fSetFormatCalled = TRUE;// from now on, this is the format
// we must use
        Reconnect(TRUE);
    }

    return hr;
}

// What format is the capture card capturing right now?
// The caller must free it with DeleteMediaType(*ppmt)
//
HRESULT CVidStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
    DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetFormat")));

    if (ppmt == NULL)
return E_POINTER;

    *ppmt = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
    if (*ppmt == NULL)
return E_OUTOFMEMORY;
    ZeroMemory(*ppmt, sizeof(AM_MEDIA_TYPE));
    HRESULT hr = GetMediaType((CMediaType *)*ppmt);
    if (hr != NOERROR) {
CoTaskMemFree(*ppmt);
*ppmt = NULL;
return hr;
    }
    return NOERROR;
}

//
//
HRESULT CVidStream::GetNumberOfCapabilities(int *piCount, int *piSize)
{
    DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetNumberOfCapabilities")));

    if (piCount == NULL || piSize == NULL)
return E_POINTER;

    *piCount = 0;
    *piSize = 0;

    return NOERROR;
}

// find out some capabilities of this capture device
//
HRESULT CVidStream::GetStreamCaps(int i, AM_MEDIA_TYPE **ppmt, LPBYTE pSCC)
{
    DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetStreamCaps")));

    // sorry, I have no clue what to say
    return E_NOTIMPL;
}

//=============================================================================

// IAMVideoCompression stuff

// Get some information about the driver
//
HRESULT CVidStream::GetInfo(LPWSTR pszVersion, int *pcbVersion, LPWSTR pszDescription, int *pcbDescription, long *pDefaultKeyFrameRate, long *pDefaultPFramesPerKey, double *pDefaultQuality, long *pCapabilities)
{
    DbgLog((LOG_TRACE,3,TEXT("IAMVideoCompression::GetInfo")));

    // we can't do anything programmatically
    if (pCapabilities)
        *pCapabilities = 0;
    if (pDefaultKeyFrameRate)
        *pDefaultKeyFrameRate = 0;
    if (pDefaultPFramesPerKey)
        *pDefaultPFramesPerKey = 0;
    if (pDefaultQuality)
        *pDefaultQuality = 0;

    // we can give them a driver name and version
    if (pszVersion && pcbVersion)
lstrcpynW(pszVersion, m_szVersion, *pcbVersion / 2);
    if (pszDescription && pcbDescription)
lstrcpynW(pszDescription, m_szName, *pcbDescription / 2);

    // return the number of bytes this unicode string is, incl. NULL char
    if (pcbVersion)
*pcbVersion = lstrlenW(m_szVersion) * 2 + 2;
    if (pcbDescription)
*pcbDescription = lstrlenW(m_szName) * 2 + 2;

    return NOERROR;
}

//=============================================================================

/* IAMDroppedFrames stuff */

// How many frames did we drop?
//
HRESULT CVidStream::GetNumDropped(long *plDropped)
{
    DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetNumDropped - %d dropped"),
(int)m_uiFramesSkipped));

    if (plDropped == NULL)
return E_POINTER;

    *plDropped = (long)m_uiFramesSkipped;
    return NOERROR;
}

// How many frames did we not drop?
//
HRESULT CVidStream::GetNumNotDropped(long *plNotDropped)
{
    DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetNumNotDropped - %d not dropped"),
(int)m_uiFramesDelivered));

    if (plNotDropped == NULL)
return E_POINTER;

    *plNotDropped = (long)m_uiFramesDelivered;
    return NOERROR;
}

// Which frames did we drop (give me up to lSize of them - we got lNumCopied)
//
HRESULT CVidStream::GetDroppedInfo(long lSize, long *plArray, long *plNumCopied)
{
    DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetDroppedInfo")));

    return E_NOTIMPL;// !!! Do this someday?

#if 0
    if (lSize <= 0)
return E_INVALIDARG;
    if (plArray == NULL || plNumCopied == NULL)
return E_POINTER;

    *plNumCopied = min(lSize, NUM_DROPPED);
    *plNumCopied = (long)min(*plNumCopied, m_capstats.dwlNumDropped);

    LONG l;
    for (l = 0; l < *plNumCopied; l++) {
plArray[l] = (long)m_capstats.dwlDropped[l];
    }

    return NOERROR;
#endif
}

HRESULT CVidStream::GetAverageFrameSize(long *plAverageSize)
{
    DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetAverageFrameSize - %d"),
m_uiFramesDelivered ?
(long)(m_llTotalFrameSize / m_uiFramesDelivered)
: 0));

    if (plAverageSize == NULL)
return E_POINTER;

    *plAverageSize = m_uiFramesDelivered ?
(long)(m_llTotalFrameSize / m_uiFramesDelivered)
: 0;

    return NOERROR;
}

///////////////////////////////
// IAMBufferNegotiation methods
///////////////////////////////

// Somebody wants us to use allocator properties like these when we
// connect
//
HRESULT CVidStream::SuggestAllocatorProperties(const ALLOCATOR_PROPERTIES *pprop)
{
    DbgLog((LOG_TRACE,2,TEXT("SuggestAllocatorProperties")));

    // to make sure we're not in the middle of connecting
    CAutoLock lock(m_pFilter->pStateLock());
    CAutoLock l(&m_cSharedState);

    // sorry, too late, we've made up our mind already
    if (IsConnected())
return E_UNEXPECTED;

    m_propSuggested = *pprop;

    DbgLog((LOG_TRACE,2,TEXT("cBuffers-%d  cbBuffer-%d  cbAlign-%d  cbPrefix-%d"),
pprop->cBuffers, pprop->cbBuffer, pprop->cbAlign, pprop->cbPrefix));

    return NOERROR;
}

// what properties is the allocator using right now?
//
HRESULT CVidStream::GetAllocatorProperties(ALLOCATOR_PROPERTIES *pprop)
{
    DbgLog((LOG_TRACE,2,TEXT("GetAllocatorProperties")));

    // to make sure we're not in the middle of connecting
    CAutoLock lock(m_pFilter->pStateLock());
    CAutoLock l(&m_cSharedState);

    // we are not connected... we have no allocator!
    if (!IsConnected())
return E_UNEXPECTED;

    if (pprop == NULL)
return E_POINTER;

    // tell them what they've won, Johnny
    *pprop = m_propActual;

    return NOERROR;
}

//
// PIN CATEGORIES - let the world know that we are a CAPTURE pin
//

HRESULT CVidStream::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in.  To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CVidStream::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

    if (pcbReturned)
*pcbReturned = sizeof(GUID);

    if (pPropData == NULL)
return S_OK;

    if (cbPropData < sizeof(GUID))
return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
    return S_OK;
}

// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CVidStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

    if (pTypeSupport)
*pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
}


VIDCAP.H

//==========================================================================;
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
//  Video capture stream source filter
//

// - Uses the CSource/CSourceStream classes to create a video capture filter.
// - Uses the AVICap capXXX macros to capture from the first driver it finds.

// CLSID_VidCap
// {fd501040-8ebe-11ce-8183-00aa00577da1}
DEFINE_GUID(CLSID_VidCap,
0xfd501040, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);

// align a dw to an arbitrary size
#define ALIGNUP(dw,align) ((long)(((long)(dw)+(align)-1) / (align)) * (align))

// forward declarations
class CVidOverlay;
class CVidPreview;

// The buffer sizes required for driver name and version strings
const int giDriverNameStrLen = 80;
const int giDriverVerStrLen  = 40;

class CVidStream;       // manages the output stream & pin
class CVideoBufferList; // A place to store filled buffers before we can send
                        // them downstream

//
// CVidCap
//
// The VidCap filter object. Provides the IBaseFilter/IMediaFilter interfaces.
// IPersistPropertyBag must be supported to find out what device to use
// (this filter works with many video cards)
// CPersistStream must be supported so a .GRF file with this filter in it
// can be re-created with the right device
// IAMVfwCaptureDialogs is supported to bring up the VfW capture driver
// dialog boxes.  A filter that does not work with the VfW capture drivers
// will NOT support this interface
class CVidCap : public CSource, public IPersistPropertyBag,
  public IAMVfwCaptureDialogs, public CPersistStream
{

public:

    // Construct a VidCap filter
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

    CCritSec m_cStateLock;      // Lock this when a function accesses
                                // the filter state.
                                // Generally _all_ functions, since access to
                                // this filter will be by multiple threads.

    DECLARE_IUNKNOWN

    // reveals our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    int GetPinCount();
    CBasePin * GetPin(int ix);

    // overide the Run & Pause methods so that I can notify the pin
    // of the state it should move to - also needed for CBaseStreamControl
    STDMETHODIMP Run(REFERENCE_TIME tStart);
    STDMETHODIMP Pause(void);
    STDMETHODIMP Stop(void);

    // live source - override GetState to return VFW_S_CANT_CUE when pausing
    // since we won't be sending any data when paused
    STDMETHODIMP GetState(DWORD dwMSecs, FILTER_STATE *State);

    // IAMVfwCaptureDialogs stuff
    STDMETHODIMP HasDialog(int iDialog);
    STDMETHODIMP ShowDialog(int iDialog, HWND hwnd);
    STDMETHODIMP SendDriverMessage(int iDialog, int uMsg, long dw1, long dw2);

    // for IAMStreamControl
    STDMETHODIMP SetSyncSource(IReferenceClock *pClock);
    STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);

    // IPersistPropertyBag methods
    STDMETHODIMP InitNew();
    STDMETHODIMP Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog);
    STDMETHODIMP Save(LPPROPERTYBAG pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties);

    // CPersistStream
    HRESULT WriteToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);
    int SizeMax();
    STDMETHODIMP GetClassID(CLSID *pClsid);

    STDMETHODIMP FindPin(
        LPCWSTR Id,
        IPin ** ppPin
    );
private:

    // During construction we create the single CVidStream object that provides
    // the capture output pin.
    CVidCap(TCHAR *, LPUNKNOWN, HRESULT *);
    ~CVidCap();
    void CreatePins(HRESULT *phr);

    CVidOverlay *CreateOverlayPin(HRESULT *phr);
    CVidPreview *CreatePreviewPin(HRESULT *phr);

    CVidStream  *m_pCapturePin;// our capture pin
    CVidOverlay *m_pOverlayPin;// our h/w overlay preview pin
    CVidPreview *m_pPreviewPin;// our non h/w overlay preview pin

    int m_iVideoId;// which device to use

    friend class CVidStream;
    friend class CVideoBufferList;
    friend class CVidOverlay;
    friend class CVidPreview;
    friend class CVidOverlayNotify;
};

//
// CVidStream
//
// Manages the output pin and the video capture device.
// Video capture pins are supposed to support all of these interfaces
class CVidStream : public CSourceStream, public CBaseStreamControl,
   public IAMVideoCompression, public IAMStreamConfig,
   public IAMDroppedFrames, public IAMBufferNegotiation,
   public IKsPropertySet
{

public:

    CVidStream( TCHAR           *pObjectName
              , HRESULT         *phr
              , CVidCap         *pParentFilter
              , unsigned int    uiDriverIndex
              , LPCWSTR         pPinName
              );

    ~CVidStream();

    //
    //  --- CSourceStream implementation ---
    //
public:

    HRESULT DecideBufferSize(IMemAllocator *pAlloc,
                             ALLOCATOR_PROPERTIES *pProperties);

    // helper reconnect function.  We can reconnect everything, or just
    // the preview pin because the capture pin changed
    void Reconnect(BOOL fCapturePinToo);

    // IKsPropertySet stuff - to tell the world we are a "capture" type pin
    STDMETHODIMP Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData);
    STDMETHODIMP Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData,
DWORD *pcbReturned);
    STDMETHODIMP QuerySupported(REFGUID guidPropSet, DWORD dwPropID,
DWORD *pTypeSupport);

    // IAMStreamConfig stuff
    STDMETHODIMP SetFormat(AM_MEDIA_TYPE *pmt);
    STDMETHODIMP GetFormat(AM_MEDIA_TYPE **ppmt);
    STDMETHODIMP GetNumberOfCapabilities(int *piCount, int *piSize);
    STDMETHODIMP GetStreamCaps(int i, AM_MEDIA_TYPE **ppmt, LPBYTE pSCC);

    /* IAMVideoCompression methods */
    STDMETHODIMP put_KeyFrameRate(long KeyFrameRate) {return E_NOTIMPL;};
    STDMETHODIMP get_KeyFrameRate(long *pKeyFrameRate) {return E_NOTIMPL;};
    STDMETHODIMP put_WindowSize(DWORDLONG WindowSize) {return E_NOTIMPL;};
    STDMETHODIMP get_WindowSize(DWORDLONG *pWindowSize) {return E_NOTIMPL;};
    STDMETHODIMP put_PFramesPerKeyFrame(long PFramesPerKeyFrame)
{return E_NOTIMPL;};
    STDMETHODIMP get_PFramesPerKeyFrame(long *pPFramesPerKeyFrame)
{return E_NOTIMPL;};
    STDMETHODIMP put_Quality(double Quality) {return E_NOTIMPL;};
    STDMETHODIMP get_Quality(double *pQuality) {return E_NOTIMPL;};
    STDMETHODIMP OverrideKeyFrame(long FrameNumber) {return E_NOTIMPL;};
    STDMETHODIMP OverrideFrameSize(long FrameNumber, long Size)
{return E_NOTIMPL;};
    STDMETHODIMP GetInfo(LPWSTR pstrVersion,
int *pcbVersion,
LPWSTR pstrDescription,
int *pcbDescription,
long *pDefaultKeyFrameRate,
long *pDefaultPFramesPerKey,
double *pDefaultQuality,
long *pCapabilities);

    /* IAMBufferNegotiation methods */
    STDMETHODIMP SuggestAllocatorProperties(const ALLOCATOR_PROPERTIES *pprop);
    STDMETHODIMP GetAllocatorProperties(ALLOCATOR_PROPERTIES *pprop);

    /* IAMDroppedFrames methods */
    STDMETHODIMP GetNumDropped(long *plDropped);
    STDMETHODIMP GetNumNotDropped(long *plNotDropped);
    STDMETHODIMP GetDroppedInfo(long lSize, long *plArray,
long *plNumCopied);
    STDMETHODIMP GetAverageFrameSize(long *plAverageSize);

    //
    // --- Worker Thread fn's ---
    //
    HRESULT OnThreadCreate(void);
    HRESULT OnThreadDestroy(void);

    HRESULT DoBufferProcessingLoop(void);
    HRESULT FillBuffer(IMediaSample *pSamp);

    HRESULT Inactive(void);

    // Override to handle quality messages
    STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);

    STDMETHODIMP QueryId(
        LPWSTR * Id
    );

public:

    DECLARE_IUNKNOWN;

    // reveals our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

private:        // State shared between worker & client
    CCritSec            m_cSharedState;     // Lock this to access this state,
                                            // shared with the worker thread

    unsigned int        m_uiDriverIndex;    // the device to open when active
    HWND                m_hwCapCapturing;   // The AVI capture window,
                                            // used whilst active
    CVideoBufferList    *m_plFilled;        // The buffers AVICap has filled,
                                            // but that haven't been sent yet.
                                            // NB This has its own critical
                                            // section, so m_cSharedState does
                                            // not need to be locked for access

    DWORD               m_dwMicroSecPerFrame;   // The current number of
                                            // microseconds between each frame
    ALLOCATOR_PROPERTIES m_propSuggested;   // IAMBufferNegotiation
    ALLOCATOR_PROPERTIES m_propActual;      // what the allocator is using
    BOOLm_fSetFormatCalled; // restricted to using this?

private:        // thread state. No peeking/writing by anyone else!

    enum ThreadState { Stopped,         // will exit soon, or just started
                       Paused,          // generate a poster frame if entered
                                        // from stop
                       Running          // streaming data downstream
                     };
    ThreadState m_ThreadState;

private:        // Capture Support

    HRESULT GetMediaType(CMediaType *pmt);
    HRESULT CheckMediaType(const CMediaType *pmt);
    HRESULT SetMediaType(const CMediaType *pmt);
    HRESULT BreakConnect();

    long    GetSampleSize(LPBITMAPINFOHEADER pbmi);   // Calculate the sample
                                                      // size needed.

    HWND CreateCaptureWindow(long lBufferCount);// Create the AVICap window
                                            // invisibly. Balance with
                                            // calls to DestroyCaptureWindow.
                                            // This is the function that grabs
                                            // resources. The filter holds no
                                            // resources (the capture driver)
                                            // until it is called
    BOOL DestroyCaptureWindow(HWND hwnd);   // Destroy an AVICap window,
                                            // release resources

    // put the buffer we are given onto the filled list for delivery downstream
    static LRESULT CALLBACK VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr);

private:

    WCHAR m_szName[giDriverNameStrLen];         // The driver's name
    WCHAR m_szVersion[giDriverVerStrLen];       // The driver's version

    BOOL m_SupportsVideoSourceDialog;           // The dialogs this driver supports
    BOOL m_SupportsVideoDisplayDialog;          //
    BOOL m_SupportsVideoFormatDialog;           //
    BOOL m_HasOverlay;           // device does h/w overlay
#if 0
    BOOL m_UsesPalettes;// does the driver use a palette?
    BOOL m_SuppliesPalettes;// can the driver give us a palette?
#endif

    // for IAMDroppedFrames
    unsigned int                m_uiFramesCaptured;
    LONGLONG                    m_llTotalFrameSize;
    unsigned int                m_uiFramesSkipped;
    unsigned int                m_uiFramesDelivered;

    friend class CVidCap;
    friend class CVideoBufferList;
    friend class CVidOverlay;
    friend class CVidPreview;
    friend class CVidOverlayNotify;
};

//
// CVideoBufferList
//
// This list is a place to store a buffer that AVIcap gives to us, before we can
// pass them on to the next filter downstream.
// Constructs a list of free buffers on construction.
class CVideoBufferList {

public:

    CVideoBufferList( int iBufferSize
                    , DWORD dwMicroSecPerFrame
                    , CVidCap *pFilter
                    , int iBuffers
                    );
    ~CVideoBufferList();

    HRESULT Add(LPVIDEOHDR lpVHdr);
    HRESULT RemoveHeadIntoSample(IMediaSample *pSample);

    HANDLE GetWaitHandle() {
        return (HANDLE)m_evList;
    }

    // *
    // * CBuffer
    // *

    // This is a class to store the data given to us by AVICap.
    // This must copy the data it is given, as we don't know what AVICap
    // does to its buffers.
    class CBuffer {

    public:

        CBuffer(int iBufferSize);       // prepares an iBufferSize'd memory buffer
        ~CBuffer();                     // frees the data buffer

        BYTE            *GetPointer() const {return m_pData;}
        void            CopyBuffer(LPVIDEOHDR lpVHdr, CRefTime& rt, LONGLONG llTime);
        int             GetSize() const {return m_iCaptureDataLength;}
        // The time for this sample to be presented
        CRefTime        GetCaptureTime() const {return m_rt;}
// The frame number
        LONGLONG        GetCaptureFrame() const {return m_llFrame;}

    private:

        BYTE            *m_pData;       // The data stored in this buffer
        int             m_iDataLength;  // m_pData's length
int             m_iCaptureDataLength;  // length returned from device

        CRefTime        m_rt;           // The stream time for this sample.
        LONGLONG        m_llFrame;      // The frame number
BOOLm_fSyncPoint;// for setting sample flags
BOOLm_fDiscontinuity;// for setting sample flags

friend class CVideoBufferList;
    };

private:

    CCritSec                    m_ListCrit;     // Serialize lists
    CAMEvent                      m_evList;       // New element on list
    CGenericList<CBuffer>       m_lFilled;
    CGenericList<CBuffer>       m_lFree;

    CVidCap                     *m_pFilter;

    BOOL                        m_FirstBuffer;

    DWORD                       m_dwMicroSecPerFrame;
    CRefTime                    m_rtStartTime;
    BOOLm_fLastSampleDiscarded;

    // the Time and MediaTime of the last frame delivered
    REFERENCE_TIMEm_rtLastStartTime;
    LONGLONGm_llLastFrame;

    // Did somebody do Run->Pause->Run to the graph?
    BOOLm_fReRun;

    // frame offset to add to the MediaTime - explained in the code
    LONGLONGm_llFrameOffset;

    int m_iPreviewCount;// send a preview frame every 30th frame

    friend class CVidStream;
};

// CVidOverlayNotify
// where the video renderer informs us of window moves/clips so we can fix
// the overlay
//
class CVidOverlayNotify : public CUnknown, public IOverlayNotify
{
    public:
        /* Constructor and destructor */
        CVidOverlayNotify(TCHAR              *pName,
                       CVidCap  *pFilter,
                       LPUNKNOWN           pUnk,
                       HRESULT            *phr);
        ~CVidOverlayNotify();

        /* Unknown methods */

        DECLARE_IUNKNOWN

        STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
        STDMETHODIMP_(ULONG) NonDelegatingRelease();
        STDMETHODIMP_(ULONG) NonDelegatingAddRef();

        /* IOverlayNotify methods */

        STDMETHODIMP OnColorKeyChange(
            const COLORKEY *pColorKey);         // Defines new colour key

        STDMETHODIMP OnClipChange(
            const RECT *pSourceRect,            // Area of video to play
            const RECT *pDestinationRect,       // Area of video to play
            const RGNDATA *pRegionData);        // Header describing clipping

        STDMETHODIMP OnPaletteChange(
            DWORD dwColors,                     // Number of colours present
            const PALETTEENTRY *pPalette);      // Array of palette colours

        STDMETHODIMP OnPositionChange(
            const RECT *pSourceRect,            // Area of video to play with
            const RECT *pDestinationRect);      // Area video goes

    private:
        CVidCap *m_pFilter;
        RECT     m_rcClient;// how big the preview window is

} ;

// CVidOverlay
// If the capture card supports hardware overlay, this kind of pin will
// be made for a preview pin and connect to the renderer using IOverlay
// Hardware overlay preview is free... there's no point ever turning it
// off, so we don't support CBaseStreamControl (IAMStreamControl)
//
class CVidOverlay : public CBaseOutputPin, public IKsPropertySet
{
public:
    CVidOverlay(
        TCHAR *pObjectName,
        CVidCap *pCapture,
        HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVidOverlay();

    DECLARE_IUNKNOWN

    // reveals our interfaces
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // IKsPropertySet stuff - to tell the world we are a "preview" type pin
    STDMETHODIMP Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData);
    STDMETHODIMP Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData,
DWORD *pcbReturned);
    STDMETHODIMP QuerySupported(REFGUID guidPropSet, DWORD dwPropID,
DWORD *pTypeSupport);

    HRESULT GetMediaType(int iPosition, CMediaType* pt);

    // check if the pin can support this specific proposed type&format
    HRESULT CheckMediaType(const CMediaType*);

    // override this to not do anything with allocators
    HRESULT DecideAllocator(IMemInputPin *pPin,
                           IMemAllocator **ppAlloc);

    // override these to use IOverlay, not IMemInputPin
    STDMETHODIMP Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt);
    HRESULT BreakConnect();
    HRESULT CheckConnect(IPin *pPin);

    HRESULT Active();// Stop-->Pause
    HRESULT Inactive();// Pause-->Stop
    HRESULT ActiveRun(REFERENCE_TIME tStart);// Pause-->Run
    HRESULT ActivePause();// Run-->Pause

    // say how big our buffers should be and how many we want
    HRESULT DecideBufferSize(IMemAllocator * pAllocator,
                            ALLOCATOR_PROPERTIES *pProperties)
    {
return NOERROR;
    };

private:
    HWND CreateCaptureWindow(HWND hwnd);
    BOOL DestroyCaptureWindow(HWND hwnd);

    CVidCap     * m_pCap;     // parent
    IOverlay    * m_pOverlay; // Overlay window on output pin
    CVidOverlayNotify m_OverlayNotify; // Notify object
    BOOL         m_bAdvise;   // Advise id
    BOOLm_fRunning;  // am I running?
    HWNDm_hwndCap;   // the capture window to use

    friend class CVidCap;
    friend class CVidOverlayNotify;
};

// CVidPreview
// for devices that do NOT do hardware overlay, we will be nice and provide
// a preview ourselves, even though the device doesn't support it.  We'll do
// it by noticing when we have free time, and copying some frames and sending
// them out the preview pin.  We'll make sure we never hurt capture
// performance by doing so.
//
class CVidPreview : public CBaseOutputPin, public CBaseStreamControl,
    public IKsPropertySet
{
public:
    CVidPreview(
        TCHAR *pObjectName,
        CVidCap *pCapture,
        HRESULT * phr,
        LPCWSTR pName);

    virtual ~CVidPreview();

    DECLARE_IUNKNOWN

    // override this to say what interfaces we support where
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);

    // IKsPropertySet stuff - to tell the world we are a "preview" type pin
    STDMETHODIMP Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData);
    STDMETHODIMP Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData,
DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData,
DWORD *pcbReturned);
    STDMETHODIMP QuerySupported(REFGUID guidPropSet, DWORD dwPropID,
DWORD *pTypeSupport);

    HRESULT GetMediaType(int iPosition, CMediaType* pt);

    // check if the pin can support this specific proposed type&format
    HRESULT CheckMediaType(const CMediaType*);

    HRESULT ActiveRun(REFERENCE_TIME tStart);// Pause-->Run
    HRESULT ActivePause();// Run-->Pause
    HRESULT Active();// Stop-->Pause
    HRESULT Inactive();// Pause-->Stop

    STDMETHODIMP Notify(IBaseFilter *pFilter, Quality q);

    // say how big our buffers should be and how many we want
    HRESULT DecideBufferSize(IMemAllocator * pAllocator,
                            ALLOCATOR_PROPERTIES *pProperties);

private:
    static DWORD WINAPI ThreadProcInit(void *pv);
    DWORD ThreadProc();
    HRESULT CapturePinActive(BOOL fActive);
    HRESULT ReceivePreviewFrame(LPVOID lpFrame, int iSize);
    HWND CreateCaptureWindow();
    BOOL DestroyCaptureWindow(HWND hwnd);

    CVidCap     *m_pCap;  // parent
    REFERENCE_TIME m_rtRun;
    HANDLEm_hThread;
    DWORDm_tid;
    HANDLEm_hEventRun;
    HANDLEm_hEventActiveChanged;
    CAMEvent    m_EventAdvise;
    DWORDm_dwAdvise;
    BOOLm_fCapturing;// is the streaming pin active?
    BOOLm_fLastSampleDiscarded;// for IAMStreamControl
    HWNDm_hwndCap;

// !!! these have to be public so the video callback can get at them
public:
    LPVOIDm_lpFrame;
    BOOLm_fRunning; // am I streaming?
    intm_iFrameSize;
    BOOLm_fFrameValid;
    HANDLEm_hEventFrameValid;

    // capGrabFrame callback
    static LRESULT CALLBACK VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr);

    friend class CVidCap;
    friend class CVidStream;
    friend class CVideoBufferList;
};


DOCUMENT.CPP

// Document.cpp: implementation of the CDocument class.
//
//////////////////////////////////////////////////////////////////////

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include "vcproj.h"
extern TCHAR g_szStart[];
extern TCHAR g_szEnd[];
extern TCHAR g_szAll[];
extern char szAppName[];
LPTSTR   GetStringRes (int id);

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CDocument::~CDocument()
{

}

BOOL CDocument::Initialize(HWND hwndDoc)
{
    m_hWnd = hwndDoc;
    return m_ClipList.Initialize(hwndDoc);
}

void inline WriteLong(IStream *pStream, long val)
{
    pStream->Write(&val, sizeof(val), NULL);
}

long inline ReadLong(IStream *pStream)
{
    long val;
    pStream->Read(&val, sizeof(val), NULL);
    return val;
}

void CDocument::ResetContents(void)
{
    m_TargetFileName.Empty();
    m_VideoCodecDisplayName.Empty();
    m_AudioCodecDisplayName.Empty();
    m_ClipList.ResetContents();
}

BOOL CDocument::ReadFromStream(IStream * pStream)
{
    ResetContents();

    m_TargetFileName.ReadFromStream(pStream);
    m_VideoCodecDisplayName.ReadFromStream(pStream);
    m_AudioCodecDisplayName.ReadFromStream(pStream);
    m_Height = ReadLong(pStream);
    m_Width = ReadLong(pStream);
    m_PixelDepth = ReadLong(pStream);

    m_ClipList.ReadFromStream(pStream);
    return TRUE;
}

BOOL CDocument::WriteToStream(IStream * pStream)
{
    m_TargetFileName.WriteToStream(pStream);
    m_VideoCodecDisplayName.WriteToStream(pStream);
    m_AudioCodecDisplayName.WriteToStream(pStream);
    WriteLong(pStream, m_Height);
    WriteLong(pStream, m_Width);
    WriteLong(pStream, m_PixelDepth);
    m_ClipList.WriteToStream(pStream);
    return TRUE;
}

BOOL CDocument::NewClip()
{
    CClip *pClip;
    int i = m_ClipList.CurSelClipIndex();
    if (i < 0) {    // Nothing selected = -1;  Add to end.
        i = m_ClipList.NumClips();
    }
    m_ClipList.AddClip(i, &pClip);
    if (pClip->DoSettingsDialog(m_hWnd)) {
        m_ClipList.UpdateClipView(i);
        MarkDirty();
    } else {
        m_ClipList.DeleteClip(i);
    }
    return TRUE;
}

BOOL CDocument::DeleteClip()
{
    int i = m_ClipList.CurSelClipIndex();
    if (i >= 0) {
        m_ClipList.DeleteClip(i);
        MarkDirty();
    }
    return TRUE;
}

BOOL CDocument::EditClip()
{
    int i = m_ClipList.CurSelClipIndex();
    if (i >= 0) {
        CClip *pClip = m_ClipList.GetClip(i);
        if (pClip->DoSettingsDialog(m_hWnd)) {
            m_ClipList.UpdateClipView(i);
            MarkDirty();
        }
    }
    return TRUE;
}

extern HINSTANCE hInst;

void InitOpenFileName(HWND hWnd, OPENFILENAME& OpenFileName, TCHAR * pszFileName)
{
    OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = hWnd;
    OpenFileName.hInstance         = hInst;
    OpenFileName.lpstrFilter       = NULL;
    OpenFileName.lpstrCustomFilter = NULL;
    OpenFileName.nMaxCustFilter    = 0;
    OpenFileName.nFilterIndex      = 0;
    OpenFileName.lpstrFile         = pszFileName;
    OpenFileName.nMaxFile          = MAX_PATH;
    OpenFileName.lpstrFileTitle    = NULL;
    OpenFileName.nMaxFileTitle     = 0;
    OpenFileName.lpstrInitialDir   = NULL;
    OpenFileName.lpstrTitle        = NULL;
    OpenFileName.nFileOffset       = 0;
    OpenFileName.nFileExtension    = 0;
    OpenFileName.lpstrDefExt       = NULL;
    OpenFileName.lCustData         = 0;
    OpenFileName.lpfnHook    = NULL;
    OpenFileName.lpTemplateName    = NULL;
    OpenFileName.Flags             = OFN_SHOWHELP;
}

BOOL CDocument::OpenFile()
{
    USES_CONVERSION;
    BOOL bWorked = FALSE;
    TCHAR szFile[MAX_PATH];

    szFile[0] = 0;
    OPENFILENAME OpenFileName;
    InitOpenFileName(m_hWnd, OpenFileName, szFile);

    if (GetOpenFileName(&OpenFileName)) {
        CComBSTR szTryThisName(T2BSTR(szFile));
        CComPtr<IStorage> pStorage;
        HRESULT hr = StgOpenStorage(szTryThisName, NULL,
                                    STGM_READ | STGM_SHARE_EXCLUSIVE, NULL, 0,
                                    &pStorage);
        if (SUCCEEDED(hr)) {
            CComPtr <IStream> pStream;
            hr = pStorage->OpenStream(L"VidClip", NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream);
            if (SUCCEEDED(hr)) {
                bWorked = ReadFromStream(pStream);
                if (bWorked) {
                    m_DocumentFileName = szTryThisName;
                }
            }
        }
    }
    return bWorked;
}

BOOL CDocument::SaveAsFile(bool bShowDialog)
{
    USES_CONVERSION;
    BOOL bWorked = FALSE;
    TCHAR szFile[MAX_PATH];

    if (!m_DocumentFileName || bShowDialog) {
        OPENFILENAME OpenFileName;
        InitOpenFileName(m_hWnd, OpenFileName, szFile);
        if (m_DocumentFileName) {
            lstrcpy(szFile, OLE2T(m_DocumentFileName));
        } else {
            szFile[0] = 0;
        }
        if (!GetSaveFileName(&OpenFileName)) {
            return FALSE;
        }
        m_DocumentFileName = T2OLE(szFile);
    }

    CComPtr<IStorage> pStorage;
    HRESULT hr = StgCreateDocfile(m_DocumentFileName,
                 STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0,
                 &pStorage);
    if (SUCCEEDED(hr)) {
        CComPtr<IStream> pStream;
        hr = pStorage->CreateStream(L"VidClip", STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream);
        if (SUCCEEDED(hr)) {
            bWorked = WriteToStream(pStream);
        }
    }
    return bWorked;
}

//////////////////// CLIPS

void inline WriteStreamTime(IStream *pStream, STREAM_TIME val)
{
    pStream->Write(&val, sizeof(val), NULL);
}

STREAM_TIME inline ReadStreamTime(IStream *pStream)
{
    STREAM_TIME val;
    pStream->Read(&val, sizeof(val), NULL);
    return val;
}

BOOL CClip::WriteToStream(IStream *pStream)
{
    m_FileName.WriteToStream(pStream);
    WriteStreamTime(pStream, m_stStart);
    WriteStreamTime(pStream, m_stEnd);
    return TRUE;
}

BOOL CClip::ReadFromStream(IStream *pStream)
{
    m_FileName.Empty();
    m_FileName.ReadFromStream(pStream);
    m_stStart = ReadStreamTime(pStream);
    m_stEnd = ReadStreamTime(pStream);
    return TRUE;
}

//////////////////// CLIP LIST

void CClipList::InsertCol(int iColNum, TCHAR * pszColHeader, int Width)
{
    LV_COLUMN col = {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT, LVCFMT_LEFT, Width, pszColHeader, 0, 0};
    ListView_InsertColumn(m_hLV, iColNum, &col);
}

BOOL CClipList::Initialize(HWND hwndParent)
{
    m_hLV = CreateWindowEx(0, WC_LISTVIEW, "ClipList", WS_VISIBLE | WS_CHILDWINDOW | WS_CLIPSIBLINGS | LVS_REPORT | LVS_SINGLESEL,
                           0, 0, 0, 0, hwndParent, (HMENU)1, hInst, NULL);

    InsertCol(0, "File Name", 400);
    InsertCol(1, "Start", 100);
    InsertCol(2, "Stop", 100);
    return TRUE;
}

int CClipList::NumClips(void)
{
    return ListView_GetItemCount(m_hLV);
}

int CClipList::CurSelClipIndex(void)
{
    return ListView_GetNextItem(m_hLV, -1, LVNI_ALL | LVNI_SELECTED);
}

CClip * CClipList::GetClip(int i)
{
    LV_ITEM lvi = {LVIF_PARAM, i, 0};
    ListView_GetItem(m_hLV, &lvi);
    return (CClip *)(lvi.lParam);
}

BOOL CClipList::AddClip(int i, CClip **ppClip)
{
    CClip *pNewClip = new CClip();
    if (!pNewClip) {
        return FALSE;
    }
    LV_ITEM lvi = {LVIF_PARAM | LVIF_TEXT, i, 0};
    lvi.pszText = "<New Clip>";
    lvi.lParam = (LPARAM)pNewClip;
    ListView_InsertItem(m_hLV, &lvi);
    lvi.pszText = "----";
    lvi.mask = LVIF_TEXT;
    lvi.iSubItem = 1;
    ListView_SetItem(m_hLV, &lvi);
    lvi.iSubItem = 2;
    ListView_SetItem(m_hLV, &lvi);
    *ppClip = pNewClip;

    return TRUE;
}

void CClipList::DeleteClip(int i)
{
    delete GetClip(i);
    ListView_DeleteItem(m_hLV, i);
}

void CClipList::UpdateClipView(int i)
{
    USES_CONVERSION;
    CClip *pClip = GetClip(i);

    LV_ITEM lvi = {LVIF_TEXT, i, 0};
    lvi.pszText = OLE2T(pClip->m_FileName);
    ListView_SetItem(m_hLV, &lvi);

    if (pClip->m_stStart == 0 && pClip->m_stEnd == 0) {
        lvi.iSubItem = 1;
        lvi.pszText = g_szAll;
        ListView_SetItem(m_hLV, &lvi);
        lvi.iSubItem = 2;
        lvi.pszText = _T("");
        ListView_SetItem(m_hLV, &lvi);
    } else {
        TCHAR s[20];
        long Second = (long)(pClip->m_stStart / (STREAM_TIME)10000000);
        wsprintf(s, _T("%li"), Second);
        lvi.iSubItem = 1;
        lvi.pszText = pClip->m_stStart == 0 ? g_szStart : s;
        ListView_SetItem(m_hLV, &lvi);

        Second = (long)(pClip->m_stEnd / (STREAM_TIME)10000000);
        wsprintf(s, _T("%li"), Second);
        lvi.iSubItem = 2;
        lvi.pszText = pClip->m_stEnd == 0 ? g_szEnd : s;
        ListView_SetItem(m_hLV, &lvi);
    }
}

BOOL CClipList::WriteToStream(IStream *pStream)
{
    int lNumClips = NumClips();
    WriteLong(pStream, lNumClips);
    for (int i = 0; i < lNumClips; i++) {
        GetClip(i)->WriteToStream(pStream);
    }
    return TRUE;

}

void CClipList::ResetContents()
{
    int lNumClips = NumClips();
    for (int i = 0; i < lNumClips; i++) {
        delete GetClip(i);
    }
    ListView_DeleteAllItems(m_hLV);
}

BOOL CClipList::ReadFromStream(IStream *pStream)
{
    ResetContents();

    int lNumClips = ReadLong(pStream);
    for (int i = 0; i < lNumClips; i++) {
        CClip *pClip;
        AddClip(i, &pClip);
        pClip->ReadFromStream(pStream);
        UpdateClipView(i);
    }
    return TRUE;
}

void CClipList::SetSize(int Height, int Width)
{
    MoveWindow(m_hLV, 0, 0, Height, Width, TRUE);
}

LRESULT CALLBACK ClipSettingsDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

BOOL CClip::DoSettingsDialog(HWND hwndParent)
{
    return DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_CLIP), hwndParent, (DLGPROC)ClipSettingsDlg, (LPARAM)this);
}

void inline SetEditSeconds(HWND hDlg, int ID, STREAM_TIME st)
{
    long Second = (long)(st / (STREAM_TIME)10000000);
    TCHAR s[20];
    wsprintf(s, _T("%li"), Second);
    SendDlgItemMessage(hDlg, ID, WM_SETTEXT, 0, (LPARAM)s);
}

void CClip::InitDialog(HWND hDlg)
{
    USES_CONVERSION;
    CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
    SendDlgItemMessage(hDlg, IDC_EDIT_FILENAME, WM_SETTEXT, 0, (LPARAM)W2T(m_FileName));
    SetEditSeconds(hDlg, IDC_EDIT_START, m_stStart);
    SetEditSeconds(hDlg, IDC_EDIT_END, m_stEnd);
}

bool CClip::CleanUpDialog(HWND hDlg, bool bSaveSettings)
{
    bool bWorked = true;
    if (bSaveSettings) {
        TCHAR szEditContents[MAX_PATH];
        TCHAR *pUnused;
        STREAM_TIME stStart, stEnd;

        SendDlgItemMessage(hDlg, IDC_EDIT_START, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);
        stStart = (STREAM_TIME)strtol(szEditContents, &pUnused, 10) * (STREAM_TIME)10000000;

        SendDlgItemMessage(hDlg, IDC_EDIT_END, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);
        stEnd = (STREAM_TIME)strtol(szEditContents, &pUnused, 10) * (STREAM_TIME)10000000;

        SendDlgItemMessage(hDlg, IDC_EDIT_FILENAME, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);

        if (stStart <= stEnd || stEnd == 0) {
            HANDLE hTest = CreateFile(szEditContents, GENERIC_READ, FILE_SHARE_READ, NULL,
                                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
            if (hTest == INVALID_HANDLE_VALUE) {
                bWorked = false;
                MessageBox(hDlg, GetStringRes(IDS_FILE_DOES_NOT_EXIST), szAppName, MB_OK);
            } else {
                CloseHandle(hTest);
                m_FileName = szEditContents;
                m_stStart = stStart;
                m_stEnd = stEnd;
            }
        } else {
            bWorked = false;
            MessageBox(hDlg, GetStringRes(IDS_INVALID_TIMES), szAppName, MB_OK);
        }
    }
    return bWorked;
}

//
//  FUNCTION: ClipSettings(HWND, unsigned, WORD, LONG)
//
//
//  MESSAGES:
//
// WM_INITDIALOG - initialize dialog box
// WM_COMMAND    - Input received
//
//

LRESULT CALLBACK ClipSettingsDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    CClip *pClip = (CClip *)GetWindowLong(hDlg, DWL_USER);
    switch (message) {
        case WM_INITDIALOG:
            pClip = (CClip *)lParam;
            SetWindowLong(hDlg, DWL_USER, lParam);
            pClip->InitDialog(hDlg);
            return TRUE;

        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
                if (pClip->CleanUpDialog(hDlg, LOWORD(wParam) == IDOK)) {
                    EndDialog(hDlg, LOWORD(wParam) == IDOK);
                }
                return TRUE;
            }
            if (LOWORD(wParam) == IDC_BUTTON_BROWSE) {
                TCHAR szFile[MAX_PATH];
                szFile[0] = 0;
                OPENFILENAME OpenFileName;
                InitOpenFileName(hDlg, OpenFileName, szFile);
                if (GetOpenFileName(&OpenFileName)) {
                    SendDlgItemMessage(hDlg, IDC_EDIT_FILENAME, WM_SETTEXT, 0, (LPARAM)szFile);
                }
                return TRUE;
            }
            break;
   }

   return FALSE;
}


DOCUMENT.H

// Document.h: interface for the CDocument class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_DOCUMENT_H__26A093E9_DD1C_11D0_8FFE_00C04FD9189D__INCLUDED_)
#define AFX_DOCUMENT_H__26A093E9_DD1C_11D0_8FFE_00C04FD9189D__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

class CClip
{
public:
    CClip() : m_stStart(0), m_stEnd(0) {};
    CComBSTR    m_FileName;
    STREAM_TIME m_stStart;
    STREAM_TIME m_stEnd;
    BOOL WriteToStream(IStream *pStream);
    BOOL ReadFromStream(IStream *pStream);
    BOOL DoSettingsDialog(HWND hwndParent);
    void InitDialog(HWND);
    bool CleanUpDialog(HWND, bool);
};

class CClipList
{
public:
    BOOL Initialize(HWND hwndParent);
    BOOL WriteToStream(IStream *pStream);
    BOOL ReadFromStream(IStream *pStream);
    void ResetContents(void);
    int NumClips();
    int CurSelClipIndex();
    CClip * GetClip(int i);
    BOOL AddClip(int i, CClip **ppClip);
    void DeleteClip(int i);
    void UpdateClipView(int i);
    void SetSize(int, int);

private:
    void InsertCol(int iColNum, TCHAR * pszColHeader, int Width);

public:
    HWND m_hLV;
};

class CDocument
{
public:
        CDocument() : m_bDirty(false), m_Height(240), m_Width(320), m_PixelDepth(0) {};
        BOOL Initialize(HWND hDocWindow);
        void MarkDirty() { m_bDirty = true; };
bool m_bModified;
BOOL WriteToStream(IStream *pStream);
BOOL ReadFromStream(IStream *pStream);
        void ResetContents(void);
virtual ~CDocument();
        BOOL OpenFile();
        BOOL SaveAsFile(bool bShowDialog);
        BOOL NewClip();
        BOOL DeleteClip();
        BOOL EditClip();

public:
        HWND    m_hWnd;
        CClipList   m_ClipList;
long m_PixelDepth;
long m_Width;
long m_Height;
        CComBSTR    m_DocumentFileName;
        CComBSTR    m_TargetFileName;
        CComBSTR    m_VideoCodecDisplayName;
        CComBSTR    m_AudioCodecDisplayName;
        bool        m_bDirty;
};

#endif // !defined(AFX_DOCUMENT_H__26A093E9_DD1C_11D0_8FFE_00C04FD9189D__INCLUDED_)


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by vidclip.Rc
//
#define IDD_SETTINGS                    101
#define IDD_CLIP                        102
#define IDS_ALL704
#define IDS_START                       505
#define IDS_END                         506
#define IDS_INVALID_TIMES               507
#define IDS_FILE_DOES_NOT_EXIST         508
#define IDS_NOFILENAME                  509
#define IDS_NOCLIPS                     510
#define IDS_UNABLETOINITREAD            511
#define IDS_UNABLETOOPENREAD            512
#define IDS_NOCOMPRESSOR                513
#define IDS_INTERNALERROR               514
#define IDS_CANTCOMPRESS                515
#define IDS_CANTCONNECTTOMUX            516
#define IDS_STRING517                   517
#define IDS_UNABLETOINITWRITE           518
#define IDS_CANTSETFORMAT               519
#define IDS_UNABLETOSETOUTPUTNAME       520
#define IDS_LIMITEDFUNCTION             521
#define IDC_HEIGHT                      1000
#define IDC_EDIT_START                  1001
#define IDC_WIDTH                       1002
#define IDC_EDIT_END                    1002
#define IDC_RADIO8BIT                   1003
#define IDC_BUTTON_BROWSE               1003
#define IDC_RADIO16BIT                  1004
#define IDC_RADIO24BIT                  1005
#define IDC_EDIT_FILENAME               1006
#define IDC_COMBO_VIDCOMP               1007
#define IDC_COMBO_AUDIOCOMP             1008
#define IDM_SETTINGS                    40001
#define IDM_VIDEO_ADDCLIP               40003
#define IDM_VIDEO_DELETECLIP            40004
#define IDM_VIDEO_MAKEMOVIE             40005
#define IDM_VIDEO_EDITCLIP              40006

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40007
#define _APS_NEXT_CONTROL_VALUE         1004
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SETTINGS.CPP

#include "vcproj.h"

// Local Header Files
#include "vidclip.h"
#include "resource.h"
#include "atlbase.h"
extern CComModule _Module;
#include "atlcom.h"
#include "strmif.h"
#include "uuids.h"
#include "document.h"

class CSettings
{
public:
    CSettings(CDocument *pDoc) : m_pDocument(pDoc) {};
    void Init(HWND hDlg);
    void FillCompressorList(HWND hWndCombo, REFGUID CatGuid, ICreateDevEnum *pCreateDevEnum, CComBSTR& szDisplayName);
    void CleanCompressorList(HWND hWndCombo, LPOLESTR * ppDisplayName);
    void CleanUp(HWND hDlg, bool bSave);

    CDocument * m_pDocument;
};

void GetMonikerName(IMoniker *pM, LPOLESTR *ppDisplayName)
{
    CComPtr <IBindCtx> pBindCtx;
    CreateBindCtx(0, &pBindCtx);
    pM->GetDisplayName(pBindCtx, NULL, ppDisplayName);
}

void CSettings::FillCompressorList(
    HWND hWndCombo,
    REFGUID CatGuid,
    ICreateDevEnum *pCreateDevEnum,
    CComBSTR &szDisplayName)
{
    CComPtr <IMalloc> pMalloc;
    CoGetMalloc(1, &pMalloc);
    bool bFoundPrevSelection = false;
    IEnumMoniker *pEm;
    HRESULT hr = pCreateDevEnum->CreateClassEnumerator(
        CatGuid,
        &pEm,
        0);
    if(hr == S_OK)
    {
        // enumerator returns S_FALSE if the category is empty
        ULONG cFetched;
        IMoniker *pM;
        while(hr = pEm->Next(1, &pM, &cFetched),
              hr == S_OK)
        {
            IPropertyBag *pPropBag;
            hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
            if(SUCCEEDED(hr))
            {
                VARIANT var;
                var.vt = VT_BSTR;
                hr = pPropBag->Read(L"FriendlyName", &var, 0);
                if(SUCCEEDED(hr))
                {
                    TCHAR szString[MAX_PATH];
                    WideCharToMultiByte(
                        CP_ACP, 0, var.bstrVal, -1,
                        szString, sizeof(szString), 0, 0);

                    int ID = SendMessage(hWndCombo, CB_ADDSTRING, 0, (LPARAM)&szString);
                    SendMessage(hWndCombo, CB_SETITEMDATA, ID, (LPARAM)pM);
                    SysFreeString(var.bstrVal);
                    if (!bFoundPrevSelection && szDisplayName) {
                        LPOLESTR pThisDevName;
                        GetMonikerName(pM, &pThisDevName);
                        if (pThisDevName) {
                            if (wcscmp(szDisplayName, pThisDevName) == 0) {
                                bFoundPrevSelection = true;
                                SendMessage(hWndCombo, CB_SETCURSEL, ID, 0);
                            }
                            pMalloc->Free(pThisDevName);
                        }
                    }
                }
                pPropBag->Release();
            }

            /// pM->Release(); -- Will do this later!
        }
        pEm->Release();
    }
    static TCHAR szNone[] = "<None>";
    int ID = SendMessage(hWndCombo, CB_ADDSTRING, 0, (LPARAM)&szNone);
    if (!bFoundPrevSelection) {
        SendMessage(hWndCombo, CB_SETCURSEL, ID, 0);
    }
}

void inline SetEditLong(HWND hDlg, int ID, long val)
{
    TCHAR s[20];
    wsprintf(s, _T("%li"), val);
    SendDlgItemMessage(hDlg, ID, WM_SETTEXT, 0, (LPARAM)s);
}

void CSettings::Init(HWND hDlg)
{
    USES_CONVERSION;
    CComPtr <ICreateDevEnum> pCreateDevEnum;
    HRESULT hr = CoCreateInstance(
        CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
        IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if(SUCCEEDED(hr)) {
        FillCompressorList(GetDlgItem(hDlg, IDC_COMBO_VIDCOMP),
                           CLSID_VideoCompressorCategory,
                           pCreateDevEnum,
                           m_pDocument->m_VideoCodecDisplayName);
        FillCompressorList(GetDlgItem(hDlg, IDC_COMBO_AUDIOCOMP),
                           CLSID_AudioCompressorCategory,
                           pCreateDevEnum,
                           m_pDocument->m_AudioCodecDisplayName);
    }
    CheckRadioButton(hDlg, IDC_RADIO16BIT, IDC_RADIO24BIT, IDC_RADIO16BIT + m_pDocument->m_PixelDepth);
    SendDlgItemMessage(hDlg, IDC_EDIT_FILENAME, WM_SETTEXT, 0, (LPARAM)W2T(m_pDocument->m_TargetFileName));
    SetEditLong(hDlg, IDC_HEIGHT, m_pDocument->m_Height);
    SetEditLong(hDlg, IDC_WIDTH, m_pDocument->m_Width);
}

void CSettings::CleanCompressorList(HWND hWndCombo, LPOLESTR * ppDisplayName)
{
    *ppDisplayName = NULL;
    int IDSelected = SendMessage(hWndCombo, CB_GETCURSEL, 0, 0);
    if (IDSelected != CB_ERR) {
        IMoniker *pMoniker = (IMoniker *)SendMessage(hWndCombo, CB_GETITEMDATA, IDSelected, 0);
        if (pMoniker) {
            GetMonikerName(pMoniker, ppDisplayName);
        }
    }
    int NumItems = SendMessage(hWndCombo, CB_GETCOUNT, 0, 0);
    for (int i = 0; i < NumItems; i++) {
        IMoniker *pMoniker = (IMoniker *)SendMessage(hWndCombo, CB_GETITEMDATA, i, 0);
        if (pMoniker) {
            pMoniker->Release();
        }
    }
}

void CSettings::CleanUp(HWND hDlg, bool bSave)
{
    USES_CONVERSION;
    WCHAR *pVideoName = NULL;
    WCHAR *pAudioName = NULL;
    CleanCompressorList(GetDlgItem(hDlg, IDC_COMBO_VIDCOMP), &pVideoName);
    CleanCompressorList(GetDlgItem(hDlg, IDC_COMBO_AUDIOCOMP), &pAudioName);
    if (bSave) {
        m_pDocument->m_VideoCodecDisplayName = pVideoName;
        m_pDocument->m_AudioCodecDisplayName = pAudioName;
    }
    CComPtr <IMalloc> pMalloc;
    CoGetMalloc(1, &pMalloc);
    pMalloc->Free(pVideoName);
    pMalloc->Free(pAudioName);
    if (bSave) {
        TCHAR szEditContents[MAX_PATH];
        TCHAR *pUnused;
        m_pDocument->MarkDirty();
        SendDlgItemMessage(hDlg, IDC_EDIT_FILENAME, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);
        m_pDocument->m_TargetFileName = szEditContents;

        SendDlgItemMessage(hDlg, IDC_HEIGHT, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);
        m_pDocument->m_Height = strtol(szEditContents, &pUnused, 10);
        SendDlgItemMessage(hDlg, IDC_WIDTH, WM_GETTEXT, sizeof(szEditContents) / sizeof(TCHAR), (LPARAM)szEditContents);
        m_pDocument->m_Width = strtol(szEditContents, &pUnused, 10);

        m_pDocument->m_PixelDepth = IsDlgButtonChecked(hDlg, IDC_RADIO16BIT) ? 0 : 1;

    }
}

void DoSettingsDialog(HINSTANCE hInst, HWND hWnd, CDocument *pDoc)
{
    CSettings Settings(pDoc);
    DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SETTINGS), hWnd, (DLGPROC)SettingsDlg, (LPARAM)&Settings);
}

//
//  FUNCTION: Settings(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for "About" dialog box
//       This version allows greater flexibility over the contents of the 'About' box,
//       by pulling out values from the 'Version' resource.
//
//  MESSAGES:
//
// WM_INITDIALOG - initialize dialog box
// WM_COMMAND    - Input received
//
//

LRESULT CALLBACK SettingsDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    CSettings *pSettings = (CSettings *)GetWindowLong(hDlg, DWL_USER);
    switch (message) {
        case WM_INITDIALOG:
            pSettings = (CSettings *)lParam;
            SetWindowLong(hDlg, DWL_USER, lParam);
            pSettings->Init(hDlg);
            return TRUE;

        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
                pSettings->CleanUp(hDlg, LOWORD(wParam) == IDOK);
                EndDialog(hDlg, TRUE);
                return TRUE;
            }
            break;
   }

   return FALSE;
}


VCPROJ.H

//
//  Common header file for entire VidClip project.
//
#ifndef __VCPROJ_H__
#define __VCPROJ_H__

// Windows Header Files:
#include <windows.h>
#include <commctrl.h>

// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>

// Multi-Media stream header files
#include <amstream.h>

//
//  Required for ATL stuff
//
#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include "atlbase.h"
extern CComModule _Module;
#include "atlcom.h"

// Local Header Files
#include "vidclip.h"
#include "document.h"
#include "resource.h"

#endif  // #ifndef __VCPROJ_H__


VIDCLIP.CPP

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1996  Microsoft Corporation.  All Rights Reserved.
//
// PROGRAM: VidClip.c - Stolen from Generic.C
//
// PURPOSE: Illustrates the 'minimum' functionality of a well-behaved Win32 application..
//
// PLATFORMS:  Windows 95, Windows NT 4.0 and up
//
// FUNCTIONS:
//    WinMain() - calls initialization function, processes message loop
//    InitApplication() - Initializes window data nd registers window
//    InitInstance() -saves instance handle and creates main window
//    WindProc() Processes messages
//    About() - Process menssages for "About" dialog box
//    CenterWindow() -  Centers one window over another
//
// SPECIAL INSTRUCTIONS: N/A
//
#include <vcproj.h>

#define APPNAME "VidClip"

#include <strmif.h>
#include <uuids.h>
#include <amstream.h>
#include <initguid.h>
#include <ddrawex.h>

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include "atlbase.h"
CComModule _Module;
#include "atlcom.h"

#include <atlimpl.cpp>

// Global Variables:

HINSTANCE hInst;      // current instance
char szAppName[100];  // Name of the app
char szTitle[100];    // The title bar text
IDirectDraw *g_pDD = NULL;

TCHAR g_szStart[100];
TCHAR g_szEnd[100];
TCHAR g_szAll[100];

const DDPIXELFORMAT g_aPixelFormats[] =
{
///    {sizeof(DDPIXELFORMAT), DDPF_RGB | DDPF_PALETTEINDEXED8, 0, 8, 0, 0, 0, 0},
    {sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 16, 0x00007C00, 0x000003E0, 0x0000001F, 0},
    {sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0},
    {sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0}
};
CDocument g_Document;

// Foward declarations of functions included in this code module:

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
LPTSTR   GetStringRes (int id);
HRESULT CreateWriterStream(LPOLESTR szOutputFileName,
                           LPOLESTR szVideoCodec,
                           DDSURFACEDESC& ddsdVideoFormat,
                           LPOLESTR szAudioCodec,
                           WAVEFORMATEX& wfexAudioFormat,
                           IMultiMediaStream **ppMMStream);
void DoMakeMovie(HINSTANCE, HWND, CDocument *);

//
//  FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
//
//  PURPOSE: Entry point for the application.
//
//  COMMENTS:
//
// This function initializes the application and processes the
// message loop.
//
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   MSG msg;
   HACCEL hAccelTable;

   CoInitialize(NULL);
   // Initialize global strings
   lstrcpy (szAppName, APPNAME);
   LoadString (hInstance, IDS_APP_TITLE, szTitle, 100);
   LoadString (hInstance, IDS_START, g_szStart, sizeof(g_szStart));
   LoadString (hInstance, IDS_END,   g_szEnd, sizeof(g_szEnd));
   LoadString (hInstance, IDS_ALL,   g_szAll, sizeof(g_szAll));

   if (!hPrevInstance) {
      // Perform instance initialization:
      if (!InitApplication(hInstance)) {
         return (FALSE);
      }
   }

   // Perform application initialization:
   if (!InitInstance(hInstance, nCmdShow)) {
      return (FALSE);
   }

   hAccelTable = LoadAccelerators (hInstance, szAppName);

   // Main message loop:
   while (GetMessage(&msg, NULL, 0, 0)) {
      if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg)) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }

   CoUninitialize();
   return (msg.wParam);

   lpCmdLine; // This will prevent 'unused formal parameter' warnings
}

//
//  FUNCTION: InitApplication(HANDLE)
//
//  PURPOSE: Initializes window data and registers window class
//
//  COMMENTS:
//
//       In this function, we initialize a window class by filling out a data
//       structure of type WNDCLASS.
//
BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASSEX  wcex;
    HWND        hwnd;

    // Win32 will always set hPrevInstance to NULL, so lets check
    // things a little closer. This is because we only want a single
    // version of this app to run at a time
    hwnd = FindWindow (szAppName, szTitle);
    if (hwnd) {
        // We found another version of ourself. Lets defer to it:
        if (IsIconic(hwnd)) {
            ShowWindow(hwnd, SW_RESTORE);
        }
        SetForegroundWindow (hwnd);

        // If this app actually had any functionality, we would
        // also want to communicate any action that our 'twin'
        // should now perform based on how the user tried to
        // execute us.
        return FALSE;
    } else {
        // Fill in window class structure with parameters that describe
        // the main window.

         // Added elements for Windows 95:
        wcex.cbSize = sizeof(WNDCLASSEX);

        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = (WNDPROC)WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = 0;
        wcex.hInstance      = hInstance;
        wcex.hIcon          = LoadIcon (hInstance, szAppName);
        wcex.hIconSm        = LoadIcon(hInstance, "SMALL");
        wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);

        wcex.lpszMenuName  = szAppName;
        wcex.lpszClassName = szAppName;

        // Register the window class and return success/failure code.
        return RegisterClassEx(&wcex);
    }
}

BOOL AddListViewItem(HWND hLV, TCHAR * pszItem, void * pThingie)
{
    LV_ITEM lvi;
    lvi.mask= LVIF_TEXT | LVIF_PARAM;
    lvi.iItem= 0x7FFF;
    lvi.iSubItem= 0;
    lvi.pszText= pszItem;
    lvi.lParam= (LPARAM)pThingie;
    lvi.cchTextMax  = 0;
    ListView_InsertItem(hLV, &lvi);
    return TRUE;
}

//
//   FUNCTION: InitInstance(HANDLE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szAppName, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
      NULL, NULL, hInstance, NULL);

   if (!hWnd) {
      return (FALSE);
   }

   InitCommonControls();
   /*
   HWND hListView = CreateWindowEx(0, WC_LISTVIEW, "Fred", WS_CHILDWINDOW | WS_CLIPSIBLINGS | LVS_REPORT,
                        0, 0, 0, 0, hWnd, (HMENU)1, hInstance, NULL);

   InitListView(hListView);
   AddListViewItem(hListView, "This is a test", NULL);
   AddListViewItem(hListView, "And another test", NULL);
   AddListViewItem(hListView, "And yet another test", NULL);
   */
   g_Document.Initialize(hWnd);

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   MessageBox (GetFocus(), GetStringRes(IDS_LIMITEDFUNCTION), szAppName, MB_OK);

   //ShowWindow(hListView, nCmdShow);
   //UpdateWindow(hListView);

   // BUGBUG -- There is still a bug in ddstream with 8bpp palettes & ddreaw
   // Make sure to make this work with a NULL g_pDD!!!
   CComPtr <IDirectDrawFactory> pFactory;
   HRESULT hr = CoCreateInstance(CLSID_DirectDrawFactory, NULL, CLSCTX_INPROC_SERVER,
                                IID_IDirectDrawFactory, (void **)&pFactory);
   if (SUCCEEDED(hr)) {
        hr = pFactory->CreateDirectDraw(NULL, NULL, DDSCL_NORMAL,0, NULL, &g_pDD);
   }
   return (TRUE);
}

void UpdateMenus(HWND hWnd, CDocument * pDoc)
{
    BOOL bEnableSave = FALSE;
    BOOL bEnableEdit = FALSE;
    if (pDoc->m_ClipList.NumClips() > 0) {
        bEnableSave = (pDoc->m_TargetFileName != NULL);
        bEnableEdit = (pDoc->m_ClipList.CurSelClipIndex() >= 0);
    }
    HMENU hMenu = GetMenu(hWnd);
    UINT uEditSetting = bEnableEdit ? MF_ENABLED : MF_GRAYED;
    UINT uSaveSetting = bEnableSave ? MF_ENABLED : MF_GRAYED;
    EnableMenuItem(hMenu, IDM_VIDEO_EDITCLIP, uEditSetting);
    EnableMenuItem(hMenu, IDM_VIDEO_DELETECLIP, uEditSetting);
    EnableMenuItem(hMenu, IDM_VIDEO_MAKEMOVIE, uSaveSetting);
    EnableMenuItem(hMenu, IDM_SAVE, uSaveSetting);
    EnableMenuItem(hMenu, IDM_SAVEAS, uSaveSetting);
}

//
//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for the main window.
//
//  MESSAGES:
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//    WM_DISPLAYCHANGE - message sent to Plug & Play systems when the display changes
//    WM_RBUTTONDOWN - Right mouse click -- put up context menu here if appropriate
//    WM_NCRBUTTONUP - User has clicked the right button on the application's system menu
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   int wmId, wmEvent;
   PAINTSTRUCT ps;
   HDC hdc;
   POINT pnt;
   HMENU hMenu;
   BOOL bGotHelp;
   CDocument *pDoc = &g_Document;

   switch (message) {

      case WM_COMMAND:
         wmId    = LOWORD(wParam); // Remember, these are...
         wmEvent = HIWORD(wParam); // ...different for Win32!

         //Parse the menu selections:
         switch (wmId) {

            case IDM_ABOUT:
               DialogBox(hInst, "AboutBox", hWnd, (DLGPROC)About);
               break;

            case IDM_SETTINGS:
                DoSettingsDialog(hInst, hWnd, pDoc);
                UpdateMenus(hWnd, pDoc);
                break;

            case IDM_EXIT:
               DestroyWindow (hWnd);
               break;

            case IDM_HELPTOPICS: // Only called in Windows 95
               bGotHelp = WinHelp (hWnd, APPNAME".HLP", HELP_FINDER,(DWORD)0);
               if (!bGotHelp)
               {
                  MessageBox (GetFocus(), GetStringRes(IDS_NO_HELP),
                              szAppName, MB_OK|MB_ICONHAND);
               }
               break;

            case IDM_HELPCONTENTS: // Not called in Windows 95
               bGotHelp = WinHelp (hWnd, APPNAME".HLP", HELP_CONTENTS,(DWORD)0);
               if (!bGotHelp)
               {
                  MessageBox (GetFocus(), GetStringRes(IDS_NO_HELP),
                              szAppName, MB_OK|MB_ICONHAND);
               }
               break;

            case IDM_HELPSEARCH: // Not called in Windows 95
               if (!WinHelp(hWnd, APPNAME".HLP", HELP_PARTIALKEY,
                           (DWORD)(LPSTR)""))
               {
                  MessageBox (GetFocus(), GetStringRes(IDS_NO_HELP),
                              szAppName, MB_OK|MB_ICONHAND);
               }
               break;

            case IDM_HELPHELP: // Not called in Windows 95
               if(!WinHelp(hWnd, (LPSTR)NULL, HELP_HELPONHELP, 0))
               {
                  MessageBox (GetFocus(), GetStringRes(IDS_NO_HELP),
                              szAppName, MB_OK|MB_ICONHAND);
               }
               break;

            case IDM_VIDEO_ADDCLIP:
                pDoc->NewClip();
                break;

            case IDM_VIDEO_EDITCLIP:
                pDoc->EditClip();
                break;

            case IDM_VIDEO_DELETECLIP:
                pDoc->DeleteClip();
                break;

            case IDM_VIDEO_MAKEMOVIE:
                DoMakeMovie(hInst, hWnd, pDoc);
                break;

            // Here are all the other possible menu options,
            // all of these are currently disabled:
            case IDM_NEW:
                pDoc->ResetContents();
                break;

            case IDM_OPEN:
                return pDoc->OpenFile();

            case IDM_SAVE:
            case IDM_SAVEAS:
                return pDoc->SaveAsFile(wmId == IDM_SAVEAS);

            case IDM_UNDO:
            case IDM_CUT:
            case IDM_COPY:
            case IDM_PASTE:
            case IDM_LINK:
            case IDM_LINKS:

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

      case WM_NCRBUTTONUP: // RightClick on windows non-client area...
         if (SendMessage(hWnd, WM_NCHITTEST, 0, lParam) == HTSYSMENU)
         {
            // The user has clicked the right button on the applications
            // 'System Menu'. Here is where you would alter the default
            // system menu to reflect your application. Notice how the
            // explorer deals with this. For this app, we aren't doing
            // anything
            return (DefWindowProc(hWnd, message, wParam, lParam));
         } else {
            // Nothing we are interested in, allow default handling...
            return (DefWindowProc(hWnd, message, wParam, lParam));
         }
            break;

        case WM_RBUTTONDOWN: // RightClick in windows client area...
            pnt.x = LOWORD(lParam);
            pnt.y = HIWORD(lParam);
            ClientToScreen(hWnd, (LPPOINT) &pnt);
      // This is where you would determine the appropriate 'context'
      // menu to bring up. Since this app has no real functionality,
      // we will just bring up the 'Help' menu:
            hMenu = GetSubMenu (GetMenu (hWnd), 2);
            if (hMenu) {
                TrackPopupMenu (hMenu, 0, pnt.x, pnt.y, 0, hWnd, NULL);
            } else {
            // Couldn't find the menu...
                MessageBeep(0);
            }
            break;

      case WM_PAINT:
         hdc = BeginPaint (hWnd, &ps);
         // Add any drawing code here...
         EndPaint (hWnd, &ps);
         break;

      case WM_DESTROY:
         // Tell WinHelp we don't need it any more...
               WinHelp (hWnd, APPNAME".HLP", HELP_QUIT,(DWORD)0);
         PostQuitMessage(0);
         break;

      case WM_SIZE:
          pDoc->m_ClipList.SetSize(LOWORD(lParam), HIWORD(lParam));
          break;

      case WM_NOTIFY: {
          LPNMHDR pnmhdr = (LPNMHDR)lParam;
          if (pnmhdr->hwndFrom == pDoc->m_ClipList.m_hLV) {
              if (pnmhdr->code == NM_DBLCLK) {
                pDoc->EditClip();
              }
              UpdateMenus(hWnd, pDoc);
          }
          }
          break;

      case WM_ACTIVATE:
          UpdateMenus(hWnd, pDoc);
          // Fall through...

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

//
//  FUNCTION: About(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for "About" dialog box
//       This version allows greater flexibility over the contents of the 'About' box,
//       by pulling out values from the 'Version' resource.
//
//  MESSAGES:
//
// WM_INITDIALOG - initialize dialog box
// WM_COMMAND    - Input received
//
//
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
   static  HFONT hfontDlg;    // Font for dialog text
   static   HFONT hFinePrint; // Font for 'fine print' in dialog
   DWORD   dwVerInfoSize;     // Size of version information block
   LPSTR   lpVersion;         // String pointer to 'version' text
   DWORD   dwVerHnd=0;        // An 'ignored' parameter, always '0'
   UINT    uVersionLen;
   WORD    wRootLen;
   BOOL    bRetCode;
   int     i;
   char    szFullPath[256];
   char    szResult[256];
   char    szGetName[256];
   DWORD dwVersion;
   char  szVersion[40];
   DWORD dwResult;

   switch (message) {
        case WM_INITDIALOG:
         ShowWindow (hDlg, SW_HIDE);

         if (PRIMARYLANGID(GetUserDefaultLangID()) == LANG_JAPANESE)
         {
            hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, SHIFTJIS_CHARSET, 0, 0, 0,
                                  VARIABLE_PITCH | FF_DONTCARE, "");
            hFinePrint = CreateFont(11, 0, 0, 0, 0, 0, 0, 0, SHIFTJIS_CHARSET, 0, 0, 0,
                                    VARIABLE_PITCH | FF_DONTCARE, "");
         }
         else
         {
            hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                  VARIABLE_PITCH | FF_SWISS, "");
            hFinePrint = CreateFont(11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                    VARIABLE_PITCH | FF_SWISS, "");
         }

         CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
         GetModuleFileName (hInst, szFullPath, sizeof(szFullPath));

         // Now lets dive in and pull out the version information:
         dwVerInfoSize = GetFileVersionInfoSize(szFullPath, &dwVerHnd);
         if (dwVerInfoSize) {
            LPSTR   lpstrVffInfo;
            HANDLE  hMem;
            hMem = GlobalAlloc(GMEM_MOVEABLE, dwVerInfoSize);
            lpstrVffInfo  = (char *)GlobalLock(hMem);
            GetFileVersionInfo(szFullPath, dwVerHnd, dwVerInfoSize, lpstrVffInfo);
            // The below 'hex' value looks a little confusing, but
            // essentially what it is, is the hexidecimal representation
            // of a couple different values that represent the language
            // and character set that we are wanting string values for.
            // 040904E4 is a very common one, because it means:
            //   US English, Windows MultiLingual characterset
            // Or to pull it all apart:
            // 04------        = SUBLANG_ENGLISH_USA
            // --09----        = LANG_ENGLISH
            // --11----        = LANG_JAPANESE
            // ----04E4 = 1252 = Codepage for Windows:Multilingual

            lstrcpy(szGetName, GetStringRes(IDS_VER_INFO_LANG));

            wRootLen = lstrlen(szGetName); // Save this position

            // Set the title of the dialog:
            lstrcat (szGetName, "ProductName");
            bRetCode = VerQueryValue((LPVOID)lpstrVffInfo,
               (LPSTR)szGetName,
               (LPVOID *)&lpVersion,
               (UINT *)&uVersionLen);

            // Notice order of version and string...
            if (PRIMARYLANGID(GetUserDefaultLangID()) == LANG_JAPANESE)
            {
               lstrcpy(szResult, lpVersion);
               lstrcat(szResult, "    o [ W        ");
            }
            else
            {
               lstrcpy(szResult, "About ");
               lstrcat(szResult, lpVersion);
            }

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

            SetWindowText (hDlg, szResult);

            // Walk through the dialog items that we want to replace:
            for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++) {
               GetDlgItemText(hDlg, i, szResult, sizeof(szResult));
               szGetName[wRootLen] = (char)0;
               lstrcat (szGetName, szResult);
               uVersionLen   = 0;
               lpVersion     = NULL;
               bRetCode      =  VerQueryValue((LPVOID)lpstrVffInfo,
                  (LPSTR)szGetName,
                  (LPVOID *)&lpVersion,
                  (UINT *)&uVersionLen);

               if ( bRetCode && uVersionLen && lpVersion) {
               // Replace dialog item text with version info
                  lstrcpy(szResult, lpVersion);
                  SetDlgItemText(hDlg, i, szResult);
               }
               else
               {
                  dwResult = GetLastError();

                  wsprintf(szResult, GetStringRes(IDS_VERSION_ERROR), dwResult);
                  SetDlgItemText (hDlg, i, szResult);
               }
               SendMessage (GetDlgItem (hDlg, i), WM_SETFONT,
                  (UINT)((i==DLG_VERLAST)?hFinePrint:hfontDlg),
                  TRUE);
            } // for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++)

            GlobalUnlock(hMem);
            GlobalFree(hMem);

         } else {
            // No version information available.
         } // if (dwVerInfoSize)

            SendMessage (GetDlgItem (hDlg, IDC_LABEL), WM_SETFONT,
            (WPARAM)hfontDlg,(LPARAM)TRUE);

         // We are  using GetVersion rather then GetVersionEx
         // because earlier versions of Windows NT and Win32s
         // didn't include GetVersionEx:
         dwVersion = GetVersion();

         if (dwVersion < 0x80000000) {
            // Windows NT
            wsprintf (szVersion, "Microsoft Windows NT %u.%u (Build: %u)",
               (DWORD)(LOBYTE(LOWORD(dwVersion))),
               (DWORD)(HIBYTE(LOWORD(dwVersion))),
                    (DWORD)(HIWORD(dwVersion)) );
         } else if (LOBYTE(LOWORD(dwVersion))<4) {
            // Win32s
                wsprintf (szVersion, "Microsoft Win32s %u.%u (Build: %u)",
               (DWORD)(LOBYTE(LOWORD(dwVersion))),
               (DWORD)(HIBYTE(LOWORD(dwVersion))),
                    (DWORD)(HIWORD(dwVersion) & ~0x8000) );
         } else {
            // Windows 95
                wsprintf (szVersion, "Microsoft Windows 95 %u.%u",
                    (DWORD)(LOBYTE(LOWORD(dwVersion))),
                    (DWORD)(HIBYTE(LOWORD(dwVersion))) );
         }

          SetWindowText (GetDlgItem(hDlg, IDC_OSVERSION), szVersion);
         ShowWindow (hDlg, SW_SHOW);
         return (TRUE);

      case WM_COMMAND:
         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
            EndDialog(hDlg, TRUE);
            DeleteObject (hfontDlg);
            DeleteObject (hFinePrint);
            return (TRUE);
         }
         break;
   }

    return FALSE;
}

//
//   FUNCTION: CenterWindow(HWND, HWND)
//
//   PURPOSE: Centers one window over another.
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
//       This functionwill center one window over another ensuring that
//    the placement of the window is within the 'working area', meaning
//    that it is both within the display limits of the screen, and not
//    obscured by the tray or other framing elements of the desktop.
BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
   RECT    rChild, rParent, rWorkArea;
   int     wChild, hChild, wParent, hParent;
   int     xNew, yNew;
   BOOL  bResult;

   // Get the Height and Width of the child window
   GetWindowRect (hwndChild, &rChild);
   wChild = rChild.right - rChild.left;
   hChild = rChild.bottom - rChild.top;

   // Get the Height and Width of the parent window
   GetWindowRect (hwndParent, &rParent);
   wParent = rParent.right - rParent.left;
   hParent = rParent.bottom - rParent.top;

   // Get the limits of the 'workarea'
   bResult = SystemParametersInfo(
      SPI_GETWORKAREA,  // system parameter to query or set
      sizeof(RECT),
      &rWorkArea,
      0);
   if (!bResult) {
      rWorkArea.left = rWorkArea.top = 0;
      rWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
      rWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
   }

   // Calculate new X position, then adjust for workarea
   xNew = rParent.left + ((wParent - wChild) /2);
   if (xNew < rWorkArea.left) {
      xNew = rWorkArea.left;
   } else if ((xNew+wChild) > rWorkArea.right) {
      xNew = rWorkArea.right - wChild;
   }

   // Calculate new Y position, then adjust for workarea
   yNew = rParent.top  + ((hParent - hChild) /2);
   if (yNew < rWorkArea.top) {
      yNew = rWorkArea.top;
   } else if ((yNew+hChild) > rWorkArea.bottom) {
      yNew = rWorkArea.bottom - hChild;
   }

   // Set it, and return
   return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

//---------------------------------------------------------------------------
//
// FUNCTION:    GetStringRes (int id INPUT ONLY)
//
// COMMENTS:    Load the resource string with the ID given, and return a
//              pointer to it.  Notice that the buffer is common memory so
//              the string must be used before this call is made a second time.
//
//---------------------------------------------------------------------------

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}

////////////////////////////////////////////////////////////////////

typedef HRESULT (STDAPICALLTYPE * PFNSAMPLECALLBACK) (IStreamSample *pSource,
                                                      IStreamSample *pDest,
                                                      void * pvContext);

#define MAX_COPY_STREAMS 5

class CopyPair {
public:
    CComPtr<IStreamSample> pSource;
    CComPtr<IStreamSample> pDest;
    MSPID PurposeId;
    PFNSAMPLECALLBACK pCallback;
    void * pCallbackContext;
    HRESULT hrLastStatus;
    bool    bReading;
    bool    bGotFirstSample;
    STREAM_TIME stActualStart;
    STREAM_TIME stStartBias;
    STREAM_TIME stLastEndTime;
};

class CCopyEngine
{
public:
    CCopyEngine(IMultiMediaStream *pDestMMStream);
    ~CCopyEngine();

    HRESULT InitStream(REFMSPID PurposeId,
                       PFNSAMPLECALLBACK pCallback = NULL,
                       void * pContext = NULL,
                       bool bSharedFormat = false);
    HRESULT CopyStreamData(IMultiMediaStream *pSourceMMStream,
                           STREAM_TIME stStart, STREAM_TIME stEnd);

private:
    CComPtr<IMultiMediaStream>  m_pDestMMStream;
    CopyPair        m_aPair[MAX_COPY_STREAMS];
    HANDLE          m_aEvent[MAX_COPY_STREAMS];
    int             m_cNumPairs;
    bool            m_bStarted;
};

///////

CCopyEngine::CCopyEngine(IMultiMediaStream *pDestMMStream) :
    m_cNumPairs(0),
    m_pDestMMStream(pDestMMStream),
    m_bStarted(false)
    {};

HRESULT CCopyEngine::InitStream(REFMSPID PurposeId,
                                PFNSAMPLECALLBACK pCallback, void * pContext,
                                bool bSharedFormat)
{
    HRESULT hr = E_FAIL;    // Assume it won't work.
    if (m_cNumPairs < MAX_COPY_STREAMS) {
        CComPtr <IMediaStream> pDest;
        if (m_pDestMMStream->GetMediaStream(PurposeId, &pDest) == NOERROR) {
            CComPtr<IStreamSample> pDestSample;
            hr = pDest->AllocateSample(0, &pDestSample);
            if (SUCCEEDED(hr)) {
                m_aEvent[m_cNumPairs] = CreateEvent(NULL, TRUE, FALSE, NULL);
                if (!m_aEvent[m_cNumPairs]) {
                    hr = E_OUTOFMEMORY;
                } else {
                    m_aPair[m_cNumPairs].pDest = pDestSample;
                    m_aPair[m_cNumPairs].pCallback = pCallback;
                    m_aPair[m_cNumPairs].pCallbackContext = pContext;
                    m_aPair[m_cNumPairs].PurposeId = PurposeId;
                    m_aPair[m_cNumPairs].stStartBias = 0;
                    m_cNumPairs++;
                }
            }
        }
    }
    return hr;
}

HRESULT CCopyEngine::CopyStreamData(IMultiMediaStream *pSourceMMStream,
                                    STREAM_TIME stClipStart, STREAM_TIME stClipEnd)
{
    HRESULT hr;
    if (m_cNumPairs == 0) {
        return S_FALSE;
    }
    int i;
    for (i = 0; i < m_cNumPairs; i++) {
        m_aPair[i].bGotFirstSample = false;
        CComPtr <IMediaStream> pSource;
        if (pSourceMMStream->GetMediaStream(m_aPair[i].PurposeId, &pSource) == NOERROR) {
            m_aPair[i].pSource = NULL;
            hr = pSource->AllocateSample(0, &m_aPair[i].pSource);
        } else {
            hr = E_FAIL;
        }
        if (FAILED(hr)) {
            return hr;
        }
    }
    pSourceMMStream->SetState(STREAMSTATE_RUN);
    if (stClipStart) {
        pSourceMMStream->Seek(stClipStart);
    }
    for (i = 0; i < m_cNumPairs; i++) {
        m_aPair[i].hrLastStatus = NOERROR;
        m_aPair[i].bReading = true;
        m_aPair[i].pSource->Update(0, m_aEvent[i], NULL, 0);
    }
    if (!m_bStarted) {
        m_pDestMMStream->SetState(STREAMSTATE_RUN);
        m_bStarted = true;
    }
    int NumRunning = m_cNumPairs;
    while (NumRunning > 0) {
        DWORD dwWaitRet = WaitForMultipleObjects(m_cNumPairs, m_aEvent, FALSE, INFINITE);
        if (dwWaitRet >= WAIT_OBJECT_0 && dwWaitRet < WAIT_OBJECT_0 + m_cNumPairs) {
            int iCompleted = dwWaitRet - WAIT_OBJECT_0;
            CopyPair *pPair = &m_aPair[iCompleted];
            IStreamSample *pDone = pPair->bReading ? pPair->pSource : pPair->pDest;
            pPair->hrLastStatus = pDone->CompletionStatus(0, 0);

if (pPair->hrLastStatus == NOERROR) {
                if (pPair->bReading) {
                    STREAM_TIME stStart, stStop;
                    pPair->pSource->GetSampleTimes(&stStart, &stStop, NULL);
                    if (stClipEnd > 0 && stStart > stClipEnd) {
                        if (pPair->bGotFirstSample) {
                            pPair->stStartBias += pPair->stLastEndTime - pPair->stActualStart;
                        }
                        NumRunning--;
                        ResetEvent(m_aEvent[iCompleted]);
                    } else {
                        if (pPair->pCallback) {
                            pPair->pCallback(pPair->pSource, pPair->pDest, pPair->pCallbackContext);
                        }
                        if (!pPair->bGotFirstSample) {
                            pPair->stActualStart = stStart;
                            pPair->bGotFirstSample = true;
                        }
                        pPair->stLastEndTime = stStop;
                        stStart += pPair->stStartBias - pPair->stActualStart;
                        stStop += pPair->stStartBias - pPair->stActualStart;
                        pPair->pDest->SetSampleTimes(&stStart, &stStop);
                        pPair->bReading = false;
                        pPair->pDest->Update(0, m_aEvent[iCompleted], NULL, 0);
                    }
                } else {
                    pPair->pSource->Update(0, m_aEvent[iCompleted], NULL, 0);
                    pPair->bReading = true;
                }
            } else {
                if (pPair->bGotFirstSample) {
                    pPair->stStartBias += pPair->stLastEndTime - pPair->stActualStart;
                }
                ResetEvent(m_aEvent[iCompleted]);
                NumRunning--;
            }
        }
    }
    pSourceMMStream->SetState(STREAMSTATE_STOP);
    for (i = 0; i < m_cNumPairs; i++) {
        m_aPair[i].pSource = NULL;  // Release the source sample
    }
    return NOERROR;
}

CCopyEngine::~CCopyEngine()
{
    int i;
    for (i = 0; i < m_cNumPairs; i++) {
        CloseHandle(m_aEvent[i]);
        if (m_bStarted && m_aPair[i].pDest) {
            CComPtr<IMediaStream> pMS;
            m_aPair[i].pDest->GetMediaStream(&pMS);
            pMS->SendEndOfStream(0);
        }
    }
    if (m_bStarted) {
        m_pDestMMStream->SetState(STREAMSTATE_STOP);
    }

}

///////////////////////////////////////////////////////////////////

void ErrorMessage(int StringId)
{
    MessageBox (GetFocus(), GetStringRes(StringId), szAppName, MB_OK|MB_ICONHAND);
}

#define CHECK_ERROR(x, idFailMsg) if (FAILED(hr = (x))) { if (idFailMsg) ErrorMessage(idFailMsg); goto Exit; }

#define RECTHEIGHT(r) ((r).bottom - (r).top)
#define RECTWIDTH(r)  ((r).right - (r).left)

HRESULT STDAPICALLTYPE VideoCallback(IStreamSample *pSource, IStreamSample *pDest,
                                     void * pvhWnd)
{
    CComQIPtr <IDirectDrawStreamSample, &IID_IDirectDrawStreamSample> pSrcSample(pSource);
    CComPtr <IDirectDrawSurface> pSrcSurface;
    RECT rectSrc;
    if (SUCCEEDED(pSrcSample->GetSurface(&pSrcSurface, &rectSrc))) {
        HDC hdcSurface;
        if (SUCCEEDED(pSrcSurface->GetDC(&hdcSurface))) {
            CComQIPtr <IDirectDrawStreamSample, &IID_IDirectDrawStreamSample> pDestSample(pDest);
            CComPtr <IDirectDrawSurface> pDestSurface;
            RECT rectDest;
            HRESULT hr = pDestSample->GetSurface(&pDestSurface, &rectDest);

            HDC hdcDest = GetDC((HWND)pvhWnd);
            BOOL fred = StretchBlt(hdcDest, 0, 0, rectDest.right-rectDest.left, rectDest.bottom-rectDest.top,
                       hdcSurface, rectSrc.left, rectSrc.top, RECTWIDTH(rectSrc), RECTHEIGHT(rectSrc), SRCCOPY);
            ReleaseDC((HWND)pvhWnd, hdcDest);

            hr = pDestSurface->GetDC(&hdcDest);
            StretchBlt(hdcDest, 0, 0, rectDest.right-rectDest.left, rectDest.bottom-rectDest.top,
                       hdcSurface, rectSrc.left, rectSrc.top, RECTWIDTH(rectSrc), RECTHEIGHT(rectSrc), SRCCOPY);

            TextOut(hdcDest, 20, 20, "Easy to do effects!", 19);

            pDestSurface->ReleaseDC(hdcDest);

        }

        //  Release this ALWAYS to bypass NT4.0 DDraw bug
        pSrcSurface->ReleaseDC(hdcSurface);
    }
    return NOERROR;
}

HRESULT OpenReadMMStream(LPOLESTR pszFileName, DDSURFACEDESC & ddsd, IMultiMediaStream **ppMMStream)
{
    *ppMMStream = NULL;
    CComPtr <IAMMultiMediaStream> pAMStream;
    CComPtr <IMediaStream> pVideoStream;
    CComQIPtr<IDirectDrawMediaStream, &IID_IDirectDrawMediaStream> pDDStream;
    HRESULT hr;

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
 IID_IAMMultiMediaStream, (void **)&pAMStream), IDS_UNABLETOINITREAD);
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_READ, 0, NULL), IDS_UNABLETOINITREAD);
    CHECK_ERROR(pAMStream->AddMediaStream(g_pDD, &MSPID_PrimaryVideo, 0, &pVideoStream), IDS_UNABLETOINITREAD);
    pDDStream = pVideoStream;

    CHECK_ERROR(pDDStream->SetFormat(&ddsd, NULL), IDS_UNABLETOINITREAD);
 //   CHECK_ERROR(pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, 0, NULL));

    CHECK_ERROR(pAMStream->OpenFile(pszFileName, AMMSF_NOCLOCK), IDS_UNABLETOOPENREAD);

    *ppMMStream = pAMStream;
    pAMStream->AddRef();

Exit:
    return hr;
}

void DoMakeMovie(HINSTANCE hInst, HWND hWndPreview, CDocument * pDocument)
{
    if (!pDocument->m_TargetFileName) {
        ErrorMessage(IDS_NOFILENAME);
        return;
    }
    if (pDocument->m_ClipList.NumClips() == 0) {
        ErrorMessage(IDS_NOCLIPS);
        return;
    }

    DDSURFACEDESC ddsd;
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.dwHeight = pDocument->m_Height;
    ddsd.dwWidth  = pDocument->m_Width;
    memcpy(&ddsd.ddpfPixelFormat, &g_aPixelFormats[pDocument->m_PixelDepth], sizeof(ddsd.ddpfPixelFormat));

    WAVEFORMATEX wfex;

    ShowWindow(pDocument->m_ClipList.m_hLV, SW_HIDE);

    CComPtr <IMultiMediaStream> pWriterStream;
    if (SUCCEEDED(CreateWriterStream(pDocument->m_TargetFileName,   // If this fails, it will display an appropriate message
                       pDocument->m_VideoCodecDisplayName,
                       ddsd,
                       pDocument->m_AudioCodecDisplayName,
                       wfex,
                       &pWriterStream))) {
        CCopyEngine Engine(pWriterStream);
        Engine.InitStream(MSPID_PrimaryVideo, VideoCallback, hWndPreview, false);
        int NumClips = pDocument->m_ClipList.NumClips();
        for (int i = 0; i < NumClips; i++) {
            CClip *pClip = pDocument->m_ClipList.GetClip(i);
            CComPtr <IMultiMediaStream> pReadStream;
            ddsd.dwFlags = DDSD_PIXELFORMAT;    // Only set pixel format on source streams
            if (SUCCEEDED(OpenReadMMStream(pClip->m_FileName, ddsd, &pReadStream))) {
                Engine.CopyStreamData(pReadStream, pClip->m_stStart, pClip->m_stEnd);
            }
        }
    }

    ShowWindow(pDocument->m_ClipList.m_hLV, SW_SHOW);
}

HRESULT AddAndRenderCompressor(LPOLESTR pszCodec,
                               LPOLESTR pszFilterName,
                               ICaptureGraphBuilder *pBuilder,
                               IMediaStream *pStream,
                               IBaseFilter *pMuxFilter)
{
    HRESULT hr;
    CComPtr <IMoniker> pDeviceMoniker;
    CComPtr <IBindCtx> pBindCtx;
    CComPtr <IBaseFilter> pCodecFilter;
    CComPtr <IGraphBuilder> pFilterGraph;
    if (pszCodec) {
        unsigned long ccEaten;
        CHECK_ERROR(CreateBindCtx(0, &pBindCtx), IDS_NOCOMPRESSOR);
        CHECK_ERROR(MkParseDisplayName(pBindCtx, pszCodec, &ccEaten, &pDeviceMoniker), IDS_NOCOMPRESSOR);
        CHECK_ERROR(pDeviceMoniker->BindToObject(pBindCtx, NULL, IID_IBaseFilter, (void **)&pCodecFilter), IDS_NOCOMPRESSOR);
        CHECK_ERROR(pBuilder->GetFiltergraph(&pFilterGraph), IDS_INTERNALERROR);
        CHECK_ERROR(pFilterGraph->AddFilter(pCodecFilter, pszFilterName), IDS_INTERNALERROR);
        hr = pBuilder->RenderStream(NULL, pStream, pCodecFilter, pMuxFilter);
        if (FAILED(hr)) {
            ErrorMessage(IDS_CANTCOMPRESS);
        }
    } else {
        hr = pBuilder->RenderStream(NULL, pStream, NULL, pMuxFilter);
        if (FAILED(hr)) {
            ErrorMessage(IDS_CANTCONNECTTOMUX);
        }
    }
Exit:
    return hr;
}

HRESULT CreateWriterStream(LPOLESTR szOutputFileName,
                           LPOLESTR szVideoCodec,
                           DDSURFACEDESC& ddsdVideoFormat,
                           LPOLESTR szAudioCodec,
                           WAVEFORMATEX& wfexAudioFormat,
                           IMultiMediaStream **ppMMStream)
{
    *ppMMStream = NULL;
    CComPtr <IAMMultiMediaStream> pAMStream;
    CComPtr <IMediaStream> pVideoStream;
    CComPtr <IMediaStream> pAudioStream;
    CComPtr <ICaptureGraphBuilder> pBuilder;
    CComPtr <IGraphBuilder> pFilterGraph;
    CComPtr <IFileSinkFilter> pFileSinkWriter;
    CComPtr <IBaseFilter> pMuxFilter;
    CComQIPtr <IDirectDrawMediaStream, &IID_IDirectDrawMediaStream> pDDStream;
    CComQIPtr <IAudioMediaStream, &IID_IAudioMediaStream> pAudioSpecificStream;
    HRESULT hr;

    CHECK_ERROR(CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER,
 IID_IAMMultiMediaStream, (void **)&pAMStream), IDS_UNABLETOINITWRITE);
    CHECK_ERROR(pAMStream->Initialize(STREAMTYPE_WRITE, 0, NULL), IDS_UNABLETOINITWRITE);

    CHECK_ERROR(pAMStream->AddMediaStream(g_pDD, &MSPID_PrimaryVideo, 0, &pVideoStream), IDS_UNABLETOINITWRITE);
  //  CHECK_ERROR(pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, 0, &pAudioStream));

    pDDStream = pVideoStream;
   // pAudioSpecificStream = pAudioStream;

    CHECK_ERROR(pDDStream->SetFormat(&ddsdVideoFormat, NULL), IDS_CANTSETFORMAT);
 /// BUGBUG!!!   CHECK_ERROR(pAudioSpecificStream->SetFormat(&wfexAudioFormat));

    CHECK_ERROR(CoCreateInstance(CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC_SERVER,
                                 IID_ICaptureGraphBuilder, (void **)&pBuilder), IDS_UNABLETOINITWRITE);

    CHECK_ERROR(pAMStream->GetFilterGraph(&pFilterGraph), IDS_INTERNALERROR);
    CHECK_ERROR(pBuilder->SetFiltergraph(pFilterGraph), IDS_INTERNALERROR);

    CHECK_ERROR(pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, szOutputFileName, &pMuxFilter, &pFileSinkWriter), IDS_UNABLETOSETOUTPUTNAME);

    CHECK_ERROR(AddAndRenderCompressor(szVideoCodec, L"Video compressor", pBuilder, pVideoStream, pMuxFilter), 0); // This function displays its own message
///    CHECK_ERROR(AddAndRenderCompressor(szAudioCodec, L"Audio compressor", pBuilder, pAudioStream, pMuxFilter));

    *ppMMStream = pAMStream;
    (*ppMMStream)->AddRef();

Exit:
    return hr;
}


VIDCLIP.H

#define IDM_NEW            100
#define IDM_OPEN           101
#define IDM_SAVE           102
#define IDM_SAVEAS         103
#define IDM_PRINT          104
#define IDM_PRINTSETUP     105
#define IDM_EXIT           106
#define IDM_UNDO           200
#define IDM_CUT            201
#define IDM_COPY           202
//---------------------------------------------------------------------------
//  GENERIC.H
//---------------------------------------------------------------------------

#define IDM_PASTE          203
#define IDM_LINK           204
#define IDM_LINKS          205
#define IDM_HELPCONTENTS   300
#define IDM_HELPSEARCH     301
#define IDM_HELPHELP       302
#define IDM_ABOUT          303
#define IDM_HELPTOPICS     304

#define IDC_STATIC -1

#define DLG_VERFIRST        400
#define IDC_COMPANY        DLG_VERFIRST
#define IDC_FILEDESC          DLG_VERFIRST+1
#define IDC_PRODVER         DLG_VERFIRST+2
#define IDC_COPYRIGHT       DLG_VERFIRST+3
#define IDC_OSVERSION       DLG_VERFIRST+4
#define IDC_TRADEMARK       DLG_VERFIRST+5
#define DLG_VERLAST         DLG_VERFIRST+5

#define IDC_LABEL           DLG_VERLAST+1

#define IDS_APP_TITLE       500
#define IDS_DISPLAYCHANGED  501
#define IDS_VER_INFO_LANG   502
#define IDS_VERSION_ERROR   503
#define IDS_NO_HELP         504

//
//  External functions
//
class CDocument;
void DoSettingsDialog(HINSTANCE, HWND, CDocument *);
LRESULT CALLBACK SettingsDlg(HWND, UINT, WPARAM, LPARAM);
BOOL CenterWindow (HWND, HWND);


README.TXT

DirectX 5 Release Notes for the Platform SDK
----------------------------------------------

Setting up your DirectX build environment
-----------------------------------------
To setup your DirectX build environment, edit the
<sdkdir>\samples\graphics\directx\dxsetenv.bat
file to point to the location of your compiler, assembler, and the location
of the Platform SDK. Run the dxsetenv.bat batch file.

Windows 95-specific libs (VC and Watcom) are installed to <sdkdir>\lib.
Windows 95-specific libs (Borland) are installed to <sdkdir>\lib\borland.

If you have problems building the samples when running under a DOS Command
box, try using a NT DOS CMD box instead. Start\Run, Type "CMD", hit enter".

If you encounter problems building samples using the default build
environment, you may need to edit the DXSETENV.BAT file to use WINDEF.H
released with Microsoft Visual C++ version 5.0.

SDK Samples
-----------

To ensure compatability with all editors, all tabs in sample source code files
have been replaced with spaces.

All SDK samples are designed to be built in the directory they are in.
Once your Win32 development environment is set up, you
can go to any sample directory and do a make.

There is a main make file in each sample directory:
MAKEFILE        - for use with Microsoft VC++ 2.0 or higher (NMAKE)

There are 2 ways to build each sample, debug or retail.   To build a
sample as retail with VC++, just type:

nmake nodebug=1

To build a sample as debug with VC++, just type:

nmake

There is also a master make file in the SAMPLES directory that will compile
all the samples at once:
MAKEFILE        - for use with Microsoft VC++ 2.0 or higher (NMAKE)

Creating a setup program for your game
---------------------------------------
We have included the source code to a sample setup program for a game
which uses the DirectX runtimes. The source for this program is located in
<sdkdir>\samples\Graphics\DirectX\setup. Note that to build
your own custom setup program, all you need to do is edit the copy_list
at the start of DINSTALL.C to be your list of files, search for
"fox", and then for "bear", and change things appropriately.

Next, create a game directory that includes your files and the setup program
you have built. Xcopy /s the <sdkdir>\redist\DirectX5\win95\directx directory to the
root of your game directory, i.e.:

XCOPY /S c:\mssdk\redist\DirectX5\win95\directx\*.* D:\FUNGAME

If you are building an autorun CD title, you can copy the AUTORUN.INF at
<sdkdir>\samples\Graphics\DirectX\setup to the root of your game directory.
If your installation program is called SETUP.EXE, you will not have to make
any changes to this file. If it is called something else, then you can edit
AUTORUN.INF appropriately.

Notes for users of Visual C++ 4.2
---------------------------------
Visual C++ 4.2 includes the DX2 header files and libraries.  If you are
getting errors compiling the samples, make sure that the DX5 include
and lib paths come before the MSVC++ 4.2 include and libs.

Notes for users of Watcom C/C++
-------------------------------
Watcom C/C++ v10.6 is required to compile the DXSDK samples.
v10.0 is not sufficient. The Microsoft Windows Platform SDK is also required
and is expected to be in \MSTOOLS on the same drive as your DXSDK
sample files. If the Platform SDK is elsewhere, you can set the MSTOOLS
environment variable to point to its root directory (e.g. set
MSTOOLS=\MSTOOLS).

Change lines 408-410 of SAMPLES\WIN32.MAK from:
        libc = libc.lib oldnames.lib
        libcmt = libcmt.lib oldnames.lib
        libcdll = msvcrt.lib oldnames.lib
to:
        libc =
        libcmt =
        libcdll =

The Watcom makefiles expect the WATCOM environment variable
to be set, as it should have been by the Watcom installation procedure.

Only a subset of the samples have Watcom makefiles.

Watcom and Watcom C/C++ are trademarks of Powersoft, Watcom Division.


README.TXT

BELLHOP.EXE
-----------

Bellhop is a DirectPlay sample that demonstrates how to write a lobby client
application using the DirectPlay API.

Note: MSVC may include older versions of the DirectX header files and
libraries.  This sample requires DirectX 5.  In order to avoid compile
errors, make sure the path to the DirectX 5 header files and libraries are
listed BEFORE the MSVC header files and libraries through the
Tools -> Options -> Directories menu.

To use the Bellhop application, a DirectPlay lobby server must be running on
a computer on the network.

Lobby Server Setup
------------------
The lobby server application is called LSERVER.EXE and can be found in the
\sdk\bin folder.  Double click on this application to start it.

To initialize the lobby server in its default mode, click on the "Server"
menu. Choose "Start" to start the lobby server.  The lobby server will create
several default top-level groups to start with. Users will only be allowed
to create sub-groups within the top-level groups, staging areas, or shortcuts
to staging areas.

By choosing "Test Mode" in the "Server" menu before clicking "Start", the
lobby server will be in test mode.  No default top-level groups will be
created. Users are free to create any group hierarchy they want to any depth.

By choosing "Secure Mode" in the "Server" menu before clicking "Start", the
lobby will require clients to login to the lobby server but only if the lobby
server is being run on Windows NT 4.0 as described in the DirectPlay readme.

The lobby server will then ask which service provider to use - all clients
connecting to the server will need to select this same service provider.
Provide a name for the lobby server session.

Bellhop
-------
Bellhop is used by people trying to find other people to play games with. By
launching Bellhop and connecting to the lobby server, you can navigate
through the "rooms" (represented by a door icon) on the lobby server to
find other people whom you can chat with.  You can create or join "staging
areas" (represented by the triangle icon) to launch DirectPlay lobby aware
games.

Starting Bellhop:

After launching bellhop, you will be asked to select a "lobby provider".  A
lobby provider is a DLL installed on your system that is used by DirectPlay
to communicate with a particular lobby server.  A sample lobby provider is
installed as part of the SDK.  Other lobby server developers will need to
create and install their own lobby providers in order for Bellhop to work
with them.  Choose "Lobby Connection for DirectPlay"

You will then be presented with a dialog asking you to select a service
provider. The sample lobby server and lobby provider are capable of operating
over any network supported by DirectPlay which is why you are presented with
this choice.  Select an appropriate service provider (like TCP/IP) and click
OK.

As usual with TCP/IP, you will be prompted for the address of the lobby
server.  You may leave it blank to try to find it on your local network or
you can enter the computer name or its IP address.  When the lobby server
name is displayed, join it.

Using Bellhop:

To move your player from doorway to doorway, double-click on the doorway you
wish to enter.  You can also drag and drop the icon representing your player
on top of the doorway.

There is optional support for allowing players in multiple rooms
simultaneously. To enable it, uncomment the code in the CreatePlayer section
of CGroupTree and rebuild.  For every player created, a player is added to
the root of the tree control. You can enter simultaneous rooms by dragging the
player icon from the top level onto a doorway.

You can only see the contents of rooms that you are a member of.  Once you
leave a room, all the doorways and players in that room will disappear.

Context menus are enabled throughout the tree control. Right clicking on a
player or group will present you with the options available for that specific
item. If you right click on the background of the tree control, you can
select refresh to re-enumerate all the groups and players your player is
aware of.

To create a staging area, right click on the folder which you want to create
it in.  Select "CreateGroupInGroup".  In this dialog, you can create either a
sub-folder or a staging area. If you choose to create a staging area, you
will need to select which game to play from the listbox (only registered
lobby-aware applications are listed) and which connection to launch the game
on. Optionally, you can specify a password for the session and the maximum
number of players.

If you want to join a staging area, you can right click on the icon and
select "GetGroupConnectionSettings" which shows you properties of the staging
area. You can modify these settings using "SetGroupConnectionSettings".

To start a game from a staging area, right click on the staging area icon and
select "StartSession".  This will launch the application on the machines of
all the people in the staging area and connect them together in a session.

If you move into the staging area after the session has started, you may
select "StartSession" to join the session in progress. If the session is in
progress, the icon will change from a green triangle to a red and black
triangle.

You can create a shortcut to a room by drag dropping one doorway on top of
another.  The icon will appear as a doorway surrounded by a magic portal.

You can change the name of local groups or players by selecting
SetGroupName\SetPlayerName from the appropriate context menu or by single
clicking on the text of the item within the tree control.

Spectators are essentially the same as a regular player within the lobby.


BELLHOP.H

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       bellhop.h
 *  Content:Header file for bellhop.cpp
 *
 ***************************************************************************/

#define IDIRECTPLAY2_OR_GREATER
#include <windows.h>
#include <windowsx.h>
#include <dplay.h>
#include <dplobby.h>
#include "CGRpTree.h"

// constants
const DWORD MAXPLAYERS = 10;// max no. players in the session
const DWORD MAXSTRLEN = 200;// max. size of a string

// structure used to store DirectPlay information
typedef struct {
LPDIRECTPLAY3AlpDirectPlay3A;// IDirectPlay3A interface pointer
LPDIRECTPLAYLOBBY2AlpDirectPlayLobby2A;// IDirectPlayLobby2A interface pointer
HANDLEhPlayerEvent;// player event to use
DPIDdpidPlayer;// ID of player created
BOOLbIsHost;// TRUE if we are hosting the session
BOOLbSecureSession;// TRUE if the session is secure.
DWORDdwPlayerFlags;
CGroupTree*lpGroupTree;
intxPaneSplit;
intxHalfSplitWidth;
intySpacing;
intxSpacing;
BOOLbSplitMove;
charszSecureName[256];
charszSecurePassword[256];
charszSecureDomain[256];
} DPLAYINFO, *LPDPLAYINFO;

typedef struct {
HWNDhWnd;
intidCombo;
} ENUMCONNSTRUCT, *LPENUMCONNSTRUCT;

typedef struct {
GUIDguidSP;
BYTEConnection[1];
} CONNECTIONINFO, *LPCONNECTIONINFO;

typedef struct {
GUIDguidInstance;
DWORDdwFlags;
} SESSIONINFO, *LPSESSIONINFO;

typedef struct {
LPDPLAYINFOlpDPInfo;
DPIDdpidRoom;
} LOBBYGROUPCONTEXT, *LPLOBBYGROUPCONTEXT;

typedef struct {
GUIDguidApplication;
CHARszAppName[MAXSTRLEN];
} APPNAMECONTEXT, *LPAPPNAMECONTEXT;

// guid for this application
// {4BF5D540-BDA5-11d0-9C4F-00A0C905425E}
DEFINE_GUID(BELLHOP_GUID,
0x4bf5d540, 0xbda5, 0x11d0, 0x9c, 0x4f, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e);

// prototypes
extern HRESULTCreateDirectPlayInterface(LPDIRECTPLAY3A *lplpDirectPlay3A );
extern HRESULTCreateDirectPlayLobbyInterface(LPDIRECTPLAYLOBBY2A *lplpDirectPlayLobby2A );
extern HRESULTConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
extern voidErrorBox(LPSTR lpszErrorStr, HRESULT hr);

// prototypes BELLHOP.CPP
BOOL CALLBACKLobbyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACKConnectionSettingsDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HRESULTSetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
HRESULTShutdownConnection(LPDPLAYINFO lpDPInfo);
DWORD WINAPIReceiveThread(LPVOID lpThreadParameter);
HRESULTReceiveMessage(LPDPLAYINFO lpDPInfo);
voidHandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo);
voidHandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
DPID idFrom, DPID idTo);
HRESULTGetPlayerName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPDPNAME *lplpName);
HRESULTGetGroupName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidGroup,
 LPDPNAME *lplpName);
HRESULTNewChatString(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPSTR lpszMsg, LPSTR *lplpszStr);
HRESULTDoCreateRoom(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoDeleteRoom(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoCreateTable(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoDeleteTable(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoDeletePlayerFromGroup(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoLaunch(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTDoGroupConnectionSettings(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTInitializeLobby(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTUpdateLobbyDisplay(HWND hWnd, LPDPLAYINFO lpDPInfo);
voidUpdateButtons(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTSendChatMessage(HWND hWnd, LPDPLAYINFO lpDPInfo);

extern voidEnableDlgButton(HWND hWnd, int nIDDlgItem, BOOL bEnable);
voidHandleStartSession(LPDPLCONNECTION lpConn, LPDPLAYINFO lpDPInfo );
BOOL FAR PASCALEnumApp(LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags);
HRESULTGetComboBoxGuid(HWND hWnd, LONG iDialogItem, LPGUID lpguidReturn);
HRESULTGetLocalAppName(LPDIRECTPLAYLOBBY lpIDPL,
LPGUID lpguidApplication, LPSTR lpszAppName);
voidLogString(LPSTR lpszDisplayFormat, LPSTR lpszDataStr);
voidOnSize(HWND hWnd, LPDPLAYINFO lpDPInfo);

// Globals
extern HINSTANCE ghInstance;


CGRPTREE.CPP

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       cGrpTree.cpp
 *  Content:An abstracted Tree control that knows how to handle
 *              DirectPlay system messages and enumerations.
 *
 ***************************************************************************/

#include "cgrptree.h"

WNDPROCgwpOrigEditProc;

///////////////////////////////////////////////////////////////////////////////////////
long FAR PASCAL EditCtrlSubProc(HWND hWnd, WORD wMessage,WORD wParam,LONG lParam)
{

    switch (wMessage)
     {

          case WM_GETDLGCODE:
            return (DLGC_WANTALLKEYS |
                    CallWindowProc( gwpOrigEditProc, hWnd, wMessage,
                                   wParam, lParam));

          case WM_CHAR:
          //Process this message to avoid message beeps.
if ((wParam == VK_RETURN) || (wParam == VK_TAB))
return 0;
break ;

       default:
            break;

     } /* end switch */

return (CallWindowProc( gwpOrigEditProc, hWnd, wMessage,
                                   wParam, lParam));

}

///////////////////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL TV_EnumPlayersCallback(DPID dpId,
DWORD dwPlayerType,
LPCDPNAME lpName,
DWORD dwFlags,
LPVOID lpContext)
{
LPENUMTREESTRUCT lp = (LPENUMTREESTRUCT) lpContext;

if (DPPLAYERTYPE_GROUP == dwPlayerType)
{
if (DPENUMGROUPS_SHORTCUT & dwFlags)
{
lp->lpTree->AddGroupToGroup( lp->dpidParent, dpId, dwFlags );
}
else
{
HRESULThr;
ENUMTREESTRUCTets;

ets.dpidParent = dpId;
ets.lpTree = lp->lpTree;
ets.bRecursive = lp->bRecursive;

if (NULL == lp->dpidParent)
{
lp->lpTree->CreateGroup( dpId, lpName->lpszShortNameA, dwFlags );
}
else
{
lp->lpTree->CreateGroupInGroup( lp->dpidParent, dpId, lpName->lpszShortNameA, dwFlags );
}

if (ets.bRecursive)
{
hr = lp->lpTree->m_lpDP3A->EnumGroupsInGroup(dpId, NULL,
TV_EnumPlayersCallback,
&ets, DPENUMPLAYERS_ALL );

hr = lp->lpTree->m_lpDP3A->EnumGroupPlayers(dpId, NULL,
TV_EnumPlayersCallback,
&ets, DPENUMPLAYERS_ALL );
}

}
}
else
{
if (lp->dpidParent)
{
lp->lpTree->AddPlayerToGroup( lp->dpidParent, dpId, dwFlags );
}
else
{
lp->lpTree->CreatePlayer( dpId, lpName->lpszShortNameA, dwFlags );
}
}

return TRUE;
}

///////////////////////////////////////////////////////////////////////////////////////
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
LPBRANCHSTRUCTlp1 = (LPBRANCHSTRUCT) lParam1,
lp2 = (LPBRANCHSTRUCT) lParam2;

if ( lp1->btType < lp2->btType )
{
return -1;
}

if (lp1->btType > lp2->btType )
{
return 1;
}

//if we got here, they are of the same type
//so sort alphabetically

LPDPNAMElpdpn1 = NULL,
lpdpn2 = NULL;
intiRes = 0;
HRESULThr = DPERR_GENERIC;
CGroupTree *lpgt = (CGroupTree *) lParamSort;

if ((lp1->btType == BT_PLAYER ) || (lp1->btType == BT_PLAYER_IN_GROUP ))
{
hr = lpgt->GetPlayerName( lp1->dpid, &lpdpn1 );
}
else
{
hr = lpgt->GetGroupName( lp1->dpid, &lpdpn1 );
}

if FAILED(hr)
{
goto FAILURE;
}

if ((lp2->btType == BT_PLAYER ) || (lp2->btType == BT_PLAYER_IN_GROUP ))
{
hr = lpgt->GetPlayerName( lp2->dpid, &lpdpn2 );
}
else
{
hr = lpgt->GetGroupName( lp2->dpid, &lpdpn2 );
}

if FAILED(hr)
{
goto FAILURE;
}

iRes = strcmp( lpdpn1->lpszShortNameA, lpdpn2->lpszShortNameA );

if ( 0 == iRes )
{
//The groups have the same name.
if ( lp1->dpid <  lp2->dpid )
{
iRes = -1;
}
else if ( lp1->dpid >  lp2->dpid )
{
iRes = 1;
}
else
{
iRes = -1;
}
}

FAILURE:
if (lpdpn1)
LocalFree(lpdpn1);

if (lpdpn2)
LocalFree(lpdpn2);

return iRes;
}

///////////////////////////////////////////////////////////////////////////////////////
CGroupTree::CGroupTree()
{

m_hInst = GetModuleHandle( NULL );
m_hwndTreeView = NULL;
m_hwndParent = NULL;
m_lpDP3A = NULL;
m_fDragging = FALSE;
m_dpidPlayer = 0;
m_dpidLastGroup = NULL;

// Prepare popup menus
m_hMenu = LoadMenu( m_hInst, MAKEINTRESOURCE(IDM_MENU) );
m_hRootMenu = GetSubMenu( m_hMenu, 0 );
m_hGroupMenu = GetSubMenu( m_hMenu, 1 );
m_hPlayerMenu = GetSubMenu( m_hMenu, 2 );
m_hShortcutMenu = GetSubMenu( m_hMenu, 3 );
m_hPlayerInGroupMenu= GetSubMenu( m_hMenu, 4 );

// Prepare tree icons
m_hImageList = ImageList_Create(32, 32, 0, 8, 8);
ImageList_SetBkColor(m_hImageList, GetSysColor(COLOR_WINDOW));
m_nGroupImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_CLOSEDDOOR)));
m_nInGroupImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_OPENDOOR)));
m_nPlayerImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_PLAYER)));
m_nShortcutInGroupImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_SHORTCUT)));
m_nStagingAreaImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_STAGINGAREA)));
m_nSpectatorImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_SPECTATOR)));
m_nSessionInProgressImg = ImageList_AddIcon(m_hImageList, LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_SESSIONINPROGRESS)));

ZeroMemory( &m_bsDragging, sizeof( BRANCHSTRUCT ) );

}

///////////////////////////////////////////////////////////////////////////////////////
CGroupTree::~CGroupTree()
{
if (m_hImageList)
{
ImageList_Destroy( m_hImageList );
m_hImageList = NULL;
}

DestroyMenu( m_hPlayerInGroupMenu );
DestroyMenu( m_hShortcutMenu );
DestroyMenu( m_hPlayerMenu );
DestroyMenu( m_hGroupMenu );
DestroyMenu( m_hRootMenu );
DestroyMenu( m_hMenu );
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CGroupTree::Init( HWND hWnd, LPDIRECTPLAY3A lpDP3A, DPID dpidPlayer )
{
if ( (hWnd) && (lpDP3A) && (dpidPlayer) )
{
m_hwndTreeView = hWnd;
m_hwndParent = GetParent( m_hwndTreeView );
m_lpDP3A = lpDP3A;
m_dpidPlayer = dpidPlayer;

TreeView_SetImageList(hWnd, m_hImageList, TVSIL_NORMAL);
return TRUE;
}
else
return FALSE;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::GetPlayerName( DPID dpidPlayer, LPDPNAME * lplpn)
{
HRESULThr= DPERR_GENERIC;
LPVOIDlpData= NULL;
DWORDdwSize= 0;

hr = m_lpDP3A->GetPlayerName( dpidPlayer, NULL, &dwSize );

if (DPERR_BUFFERTOOSMALL == hr )
{
lpData = LocalAlloc( LPTR, dwSize );

if ( NULL != lpData )
{
hr = m_lpDP3A->GetPlayerName( dpidPlayer, lpData, &dwSize );

if (FAILED(hr))
{
LocalFree( lpData );
lpData = NULL;
}
}
else
{
hr = DPERR_OUTOFMEMORY;
}
}

*lplpn = (LPDPNAME) lpData;

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::GetGroupName( DPID dpidGroup, LPDPNAME * lplpn)
{
HRESULThr= DPERR_GENERIC;
LPVOIDlpData = NULL;

DWORDdwSize= 0;

hr = m_lpDP3A->GetGroupName( dpidGroup, lpData, &dwSize );

if (DPERR_BUFFERTOOSMALL == hr )
{
lpData = LocalAlloc( LPTR,  dwSize );

if ( NULL != lpData )
{
hr = m_lpDP3A->GetGroupName( dpidGroup, lpData, &dwSize );

if (FAILED(hr))
{
LocalFree( lpData );
lpData = NULL;
}
}
else
{
hr = DPERR_OUTOFMEMORY;
}
}

*lplpn = (LPDPNAME) lpData;

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HTREEITEM CGroupTree::FindItem( HTREEITEM htiSearchRoot,
DPID dpidTarget,
BRANCH_TYPE bt,
DWORD dwSearch )
{
    TV_ITEM tvi;
HTREEITEMhItem,
htiSubSearch;
LPBRANCHSTRUCT lpbs = NULL;

    if(TVI_ROOT == htiSearchRoot)
    {
        hItem = TreeView_GetRoot(m_hwndTreeView);
    }
else
{
hItem = TreeView_GetChild( m_hwndTreeView, htiSearchRoot );
}

    while(hItem)
    {
        ZeroMemory( &tvi, sizeof(TV_ITEM));

        tvi.mask            = TVIF_PARAM;
        tvi.hItem           = hItem;

        TreeView_GetItem(m_hwndTreeView, &tvi);
lpbs = (LPBRANCHSTRUCT) tvi.lParam;

        if ( (lpbs->dpid == dpidTarget) && ( bt == lpbs->btType))
        {
return tvi.hItem;
        }

        if ( (lpbs->btType == BT_GROUP) && (ST_SEARCH_SUBGROUPS & dwSearch) )
{
htiSubSearch = FindItem( hItem, dpidTarget, bt, dwSearch );
if (htiSubSearch)
return (htiSubSearch);
}

hItem = TreeView_GetNextSibling(m_hwndTreeView, hItem);
    }

    return NULL;
}

///////////////////////////////////////////////////////////////////////////////////////
HTREEITEM CGroupTree::Insert( HTREEITEM htiParent, DPID dpID, LPSTR lpszShortNameA, BRANCH_TYPE bt, DWORD dwFlags )
{
    LPBRANCHSTRUCTlpbs= NULL;
HTREEITEMhtrItem= NULL;

if ( lpbs = (LPBRANCHSTRUCT) LocalAlloc( LPTR,  sizeof(BRANCHSTRUCT) ) )
{
TV_INSERTSTRUCTtvi;
TV_SORTCBtvscb;

ZeroMemory(lpbs, sizeof( BRANCHSTRUCT ) );
ZeroMemory(&tvscb, sizeof(tvscb));
ZeroMemory(&tvi, sizeof(tvi));
tvi.hParent= htiParent;
tvi.hInsertAfter= TVI_LAST;

tvi.item.mask= TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE | TVIF_TEXT;
tvi.item.pszText= lpszShortNameA;
tvi.item.cchTextMax= strlen(lpszShortNameA)+1;

switch( bt)
{
case BT_PLAYER:
case BT_PLAYER_IN_GROUP:
tvi.item.iImage = (DPPLAYER_SPECTATOR & dwFlags) ?  m_nSpectatorImg: m_nPlayerImg;
tvi.item.iSelectedImage= tvi.item.iImage;
break;

case BT_GROUP:
if (DPGROUP_STAGINGAREA & dwFlags)
{
HRESULT hr;
LPDPLCONNECTION lp = NULL;
DWORDdwSize = 0;
BOOLbSessionInProgress = FALSE;

// if it is a staging area, check to see if the session is in progress.
hr = IDirectPlay3_GetGroupConnectionSettings( m_lpDP3A, 0, dpID, NULL, &dwSize );
if (DPERR_BUFFERTOOSMALL == hr )
{
lp = (LPDPLCONNECTION) GlobalAllocPtr( GHND, dwSize );

if (lp)
{
hr = IDirectPlay3_GetGroupConnectionSettings( m_lpDP3A, 0, dpID, lp, &dwSize );

if (!IsEqualGUID(lp->lpSessionDesc->guidInstance, GUID_NULL))
{
// If the server has assigned an instance guid
// to our connection structure, the session
// has already started.

bSessionInProgress = TRUE;
}
GlobalFreePtr(lp);
}
}

tvi.item.iImage = (bSessionInProgress)?m_nSessionInProgressImg:m_nStagingAreaImg;
tvi.item.iSelectedImage = tvi.item.iImage;
}
else
{
tvi.item.iImage = m_nGroupImg;
tvi.item.iSelectedImage = m_nInGroupImg;
}
break;

case BT_SHORTCUT_IN_GROUP:
tvi.item.iImage = m_nShortcutInGroupImg;
tvi.item.iSelectedImage= tvi.item.iImage;
break;

default:
//Invalid BranchType
break;
}
tvi.item.lParam= (LPARAM) lpbs;

lpbs->dpid= dpID;
lpbs->btType= bt;
lpbs->dwFlags= dwFlags;

htrItem = TreeView_InsertItem( m_hwndTreeView, &tvi );

tvscb.hParent = htiParent;
tvscb.lpfnCompare = CompareFunc;
tvscb.lParam = (LPARAM) this;

TreeView_SortChildrenCB( m_hwndTreeView, &tvscb, 0 );

if(TVI_ROOT != htiParent)
{
TreeView_Expand(m_hwndTreeView, htiParent, TVE_EXPAND);
}

Redraw();
}

return htrItem;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::CreatePlayer( DPID dpidPlayer, LPSTR lpszShortNameA, DWORD dwFlags )
{
/*
// We currently support only one local player. This section of code would create a
// new player at the root for each create player message received.

LPDPNAME lpn = NULL;

if (NULL == lpszShortNameA)
{
GetPlayerName(dpidPlayer, &lpn );
lpszShortNameA = lpn->lpszShortNameA;
}

if (lpszShortNameA)
{
Insert( TVI_ROOT, dpidPlayer, lpszShortNameA, BT_PLAYER, dwFlags );
}

if (lpn)
LocalFree(lpn);
*/

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::CreateGroup( DPID dpidGroup, LPSTR lpszShortNameA, DWORD dwFlags  )
{
LPDPNAME lpn = NULL;

if (NULL == lpszShortNameA)
{
GetGroupName(dpidGroup, &lpn );
lpszShortNameA = lpn->lpszShortNameA;
}

if (lpszShortNameA)
{
Insert( TVI_ROOT, dpidGroup, lpszShortNameA, BT_GROUP, dwFlags );
}

if (lpn)
LocalFree(lpn);

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::CreateGroupInGroup( DPID dpidParentGroup, DPID dpidChildGroup, LPSTR lpszShortNameA, DWORD dwFlags   )
{
HTREEITEM hParentItem = NULL;

if ( hParentItem = FindItem( TVI_ROOT, dpidParentGroup, BT_GROUP, ST_SEARCH_SUBGROUPS ) )
{
LPDPNAME lpn = NULL;

if (NULL == lpszShortNameA)
{
GetGroupName(dpidChildGroup, &lpn );
lpszShortNameA = lpn->lpszShortNameA;
}

if (lpszShortNameA)
{
Insert( hParentItem, dpidChildGroup, lpszShortNameA, BT_GROUP, dwFlags );
}

if (lpn)
LocalFree(lpn);

}

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::AddPlayerToGroup( DPID dpidGroup, DPID dpidPlayer, DWORD dwFlags )
{
HRESULThr = DPERR_GENERIC;
LPDPNAME lpPlayerName = NULL;

hr = GetPlayerName( dpidPlayer, &lpPlayerName );

if (SUCCEEDED(hr))
{
HTREEITEM hParentItem = NULL;

if ( hParentItem = FindItem( TVI_ROOT, dpidGroup, BT_GROUP, ST_SEARCH_SUBGROUPS ) )
{
Insert( hParentItem, dpidPlayer, lpPlayerName->lpszShortNameA, BT_PLAYER_IN_GROUP, dwFlags );
}
}

if (lpPlayerName)
LocalFree( lpPlayerName);

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::AddGroupToGroup( DPID dpidParentGroup, DPID dpidShortcut, DWORD dwFlags )
{
HRESULThr = DPERR_GENERIC;
LPDPNAME lpGroupName = NULL;

hr = GetGroupName( dpidShortcut, &lpGroupName );

if (SUCCEEDED(hr))
{
HTREEITEM hParentItem = NULL;

if ( hParentItem = FindItem( TVI_ROOT, dpidParentGroup, BT_GROUP, ST_SEARCH_SUBGROUPS ) )
{
Insert( hParentItem, dpidShortcut, lpGroupName->lpszShortNameA, BT_SHORTCUT_IN_GROUP, dwFlags );
}
}

if (lpGroupName)
LocalFree( lpGroupName);

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::DestroyPlayer(DPID dpidPlayer)
{
    /*

// We are not currently adding additional players to the tree view. The
// Code below would add a player to the root of the tree control. If you
// want that functionality.

HTREEITEMhtiPlayer = FindItem( TVI_ROOT, dpidPlayer, BT_PLAYER, ST_NO_SUBGROUPS );

if ( htiPlayer )
    {
TreeView_DeleteItem(m_hwndTreeView, htiPlayer);
Redraw();
    }
*/

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::DestroyGroup(DPID dpidGroup)
{
    HTREEITEMhtiGroup = FindItem( TVI_ROOT, dpidGroup, BT_GROUP, ST_SEARCH_SUBGROUPS );

if ( htiGroup )
    {
TreeView_DeleteItem(m_hwndTreeView, htiGroup);
Redraw();
}

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::DeletePlayerFromGroup( DPID dpidGroup, DPID dpidPlayer )
{
HRESULThr = DPERR_GENERIC;
HTREEITEMhtiParent = NULL,
htiPlayer = NULL;

if ( htiParent = FindItem( TVI_ROOT, dpidGroup, BT_GROUP, ST_SEARCH_SUBGROUPS ) )
{
if ( htiPlayer = FindItem( htiParent, dpidPlayer, BT_PLAYER_IN_GROUP, ST_NO_SUBGROUPS ) )
{
TreeView_DeleteItem(m_hwndTreeView, htiPlayer);
hr = DP_OK;

Redraw();
}
}

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::DeleteGroupFromGroup( DPID dpidParentGroup, DPID dpidShortcut )
{
HRESULThr = DPERR_GENERIC;
HTREEITEMhtiParent = NULL,
htiShortcut = NULL;

if ( htiParent = FindItem( TVI_ROOT, dpidParentGroup, BT_GROUP, ST_SEARCH_SUBGROUPS ) )
{
if ( htiShortcut = FindItem( htiParent, dpidShortcut, BT_SHORTCUT_IN_GROUP, ST_NO_SUBGROUPS ) )
{
TreeView_DeleteItem(m_hwndTreeView, htiShortcut );
hr = DP_OK;

Redraw();
}
}
return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::SetPlayerName( DPID dpidPlayer, LPSTR lpszShortName )
{
HRESULThr= DPERR_GENERIC;

hr = RecursiveRename( TVI_ROOT, dpidPlayer, lpszShortName );

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::SetGroupName( DPID dpidPlayer, LPSTR lpszShortName )
{
HRESULThr= DPERR_GENERIC;

hr = RecursiveRename( TVI_ROOT, dpidPlayer, lpszShortName );

return hr;

}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::RecursiveRename(HTREEITEM htiSearchRoot,
DPID dpidTarget,
LPSTR lpszName )
{
    TV_ITEM tvi;
HTREEITEMhItem;
LPBRANCHSTRUCT lpbs = NULL;

    if(TVI_ROOT == htiSearchRoot)
    {
        hItem = TreeView_GetRoot(m_hwndTreeView);
    }
else
{
hItem = TreeView_GetChild( m_hwndTreeView, htiSearchRoot );
}

    while(hItem)
    {
        ZeroMemory( &tvi, sizeof(TV_ITEM));

        tvi.mask            = TVIF_PARAM;
        tvi.hItem           = hItem;

        TreeView_GetItem(m_hwndTreeView, &tvi);
lpbs = (LPBRANCHSTRUCT) tvi.lParam;

        if (lpbs->dpid == dpidTarget)
        {
        ZeroMemory( &tvi, sizeof(TV_ITEM));

            tvi.hItem = hItem;
tvi.mask = TVIF_TEXT;
tvi.pszText = lpszName;
tvi.cchTextMax = strlen(lpszName)+1;

TreeView_SetItem( m_hwndTreeView, &tvi );
        }

        if (lpbs->btType == BT_GROUP)
{
RecursiveRename( hItem, dpidTarget, lpszName );
}

hItem = TreeView_GetNextSibling(m_hwndTreeView, hItem);
    }

    return DP_OK;

}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::Refresh( BOOL bRecursive )
{
HRESULThr = DPERR_GENERIC;
ENUMTREESTRUCTets;

ets.dpidParent = NULL;
ets.lpTree   = this;
ets.bRecursive = bRecursive;

TreeView_DeleteAllItems( m_hwndTreeView );

if (m_lpDP3A)
{

//We don't see other root level players in the lobby world.

//hr = m_lpDP3A->EnumPlayers( NULL, TV_EnumPlayersCallback, &ets, DPENUMPLAYERS_ALL );

//if (SUCCEEDED( hr ))
//{
hr = m_lpDP3A->EnumGroups( NULL, TV_EnumPlayersCallback, &ets, DPENUMPLAYERS_ALL );
//}
}

return hr;
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CGroupTree::Update(LPVOID lpvMsg)
{

DWORDdwMsgType= ((LPDPMSG_GENERIC)lpvMsg)->dwType;
LPSTRlpszShortName= NULL;
BOOLbReturn= TRUE;

// Draws the tree based strictly on system messages.
// If you place a call to this method in the area where you process
// your system messages, it should give you a good representation
// of the DirectPlay group structure as it is created.

    switch(dwMsgType)
    {
case DPSYS_CREATEPLAYERORGROUP:
{
LPDPMSG_CREATEPLAYERORGROUP lp = (LPDPMSG_CREATEPLAYERORGROUP)lpvMsg;

lpszShortName = lp->dpnName.lpszShortNameA;
if (DPPLAYERTYPE_PLAYER == lp->dwPlayerType)
{
CreatePlayer(lp->dpId, lpszShortName, lp->dwFlags );
}
else
{
if (NULL == lp->dpIdParent)
{
CreateGroup(lp->dpId, lpszShortName, lp->dwFlags );
}
else
{
CreateGroupInGroup(lp->dpIdParent, lp->dpId, lpszShortName, lp->dwFlags );
}

}
        }
break;

        case DPSYS_ADDPLAYERTOGROUP:
{
LPDPMSG_ADDPLAYERTOGROUP lp = (LPDPMSG_ADDPLAYERTOGROUP)lpvMsg;
DWORDdwFlags;
HRESULT hr = IDirectPlay3_GetPlayerFlags( m_lpDP3A, lp->dpIdPlayer, &dwFlags );

if (SUCCEEDED(hr))
{
// If I can't get his flags, he must have deleted himself
// by the time I got this message.
AddPlayerToGroup(lp->dpIdGroup, lp->dpIdPlayer,dwFlags );
}

}
break;

        case DPSYS_ADDGROUPTOGROUP:
{
LPDPMSG_ADDGROUPTOGROUP lp = (LPDPMSG_ADDGROUPTOGROUP)lpvMsg;
DWORDdwFlags;
HRESULT hr = IDirectPlay3_GetGroupFlags( m_lpDP3A, lp->dpIdGroup, &dwFlags );

if (SUCCEEDED(hr))
{
// If I can't get his flags, he must have deleted himself
// by the time I got this message.
AddGroupToGroup(lp->dpIdParentGroup, lp->dpIdGroup, dwFlags );
}
}
break;

        case DPSYS_DESTROYPLAYERORGROUP:
{
LPDPMSG_DESTROYPLAYERORGROUP lp = (LPDPMSG_DESTROYPLAYERORGROUP) lpvMsg;

if ( DPPLAYERTYPE_PLAYER == lp->dwPlayerType )
{
DestroyPlayer( lp->dpId );
}
else
{
DestroyGroup( lp->dpId );
}

        }
break;

        case DPSYS_DELETEGROUPFROMGROUP:
{
LPDPMSG_DELETEGROUPFROMGROUP lp =(LPDPMSG_DELETEGROUPFROMGROUP)lpvMsg;

DeleteGroupFromGroup( lp->dpIdParentGroup, lp->dpIdGroup );
}
        break;

        case DPSYS_DELETEPLAYERFROMGROUP:
{
LPDPMSG_DELETEPLAYERFROMGROUP lp =(LPDPMSG_DELETEPLAYERFROMGROUP)lpvMsg;

DeletePlayerFromGroup( lp->dpIdGroup, lp->dpIdPlayer );
}
        break;

        case DPSYS_SETPLAYERORGROUPDATA:
//Nothing for right now.
break;

        case DPSYS_SETPLAYERORGROUPNAME:
{
LPDPMSG_SETPLAYERORGROUPNAME lp = (LPDPMSG_SETPLAYERORGROUPNAME)lpvMsg;

    lpszShortName = lp->dpnName.lpszShortNameA;

if ( DPPLAYERTYPE_PLAYER == lp->dwPlayerType )
{
SetPlayerName(lp->dpId, lpszShortName );
}
else
{
SetGroupName(lp->dpId, lpszShortName );
}

}
        break;

default:
//Code a new message...
bReturn = FALSE;
break;
    }

    return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////
DPID CGroupTree::GetDPIDOfCurrentSelection( LPBRANCHSTRUCT lpbt)
{

HTREEITEMhtItem;
TV_ITEMtvi;

ZeroMemory( &tvi, sizeof( TV_ITEM ) );

htItem = TreeView_GetSelection(m_hwndTreeView);

if (htItem)
{

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htItem;

TreeView_GetItem(m_hwndTreeView, &tvi);

if (lpbt)
{
lpbt->dpid = ((LPBRANCHSTRUCT)(tvi.lParam))->dpid;
lpbt->btType = ((LPBRANCHSTRUCT)(tvi.lParam))->btType;
}

return ((LPBRANCHSTRUCT) (tvi.lParam))->dpid;
}

return 0;

}

///////////////////////////////////////////////////////////////////////////////////////
DPID CGroupTree::GetDPIDOfCurrentSelectionParent( LPBRANCHSTRUCT lpbt)
{

HTREEITEMhtChildItem, htItem;
TV_ITEMtvi;

ZeroMemory( &tvi, sizeof( TV_ITEM ) );

htChildItem = TreeView_GetSelection(m_hwndTreeView);

htItem = TreeView_GetParent( m_hwndTreeView, htChildItem );

if (htItem)
{

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htItem;

TreeView_GetItem(m_hwndTreeView, &tvi);

if (lpbt)
{
lpbt->dpid = ((LPBRANCHSTRUCT)(tvi.lParam))->dpid;
lpbt->btType = ((LPBRANCHSTRUCT)(tvi.lParam))->btType;
}

return ((LPBRANCHSTRUCT) (tvi.lParam))->dpid;
}

return 0;

}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::OnBeginDrag(NM_TREEVIEW *lpnmtv)
{
    HIMAGELIST himl;    // handle of image list
LPBRANCHSTRUCTlp = NULL;
int level = 0;
UINTxIndent = 0;
HTREEITEM htItem = NULL;

 lp = (LPBRANCHSTRUCT)lpnmtv->itemNew.lParam;
m_bsDragging = *((LPBRANCHSTRUCT)(lpnmtv->itemNew.lParam));
GetBranchStructOfParent( lpnmtv->itemNew.hItem, &m_bsParentOfDragging );

   // Tell the tree-view control to create an image to use
    // for dragging.
    himl = TreeView_CreateDragImage(m_hwndTreeView, lpnmtv->itemNew.hItem);

    // Start the drag operation.
RECT rcItem;

TreeView_GetItemRect(m_hwndTreeView, lpnmtv->itemNew.hItem, &rcItem, FALSE);

htItem = lpnmtv->itemNew.hItem;

do
{
htItem = TreeView_GetParent( m_hwndTreeView, htItem );
level++;
}
while (htItem);

xIndent = TreeView_GetIndent( m_hwndTreeView ) * level;

    BOOL b = ImageList_BeginDrag(himl, 0,
lpnmtv->ptDrag.x-rcItem.left - xIndent,
lpnmtv->ptDrag.y-rcItem.top);

// Hide the mouse cursor, and direct mouse input to the
    // parent window.

    ShowCursor( FALSE );
SetCapture(m_hwndParent);
    m_fDragging = TRUE;

ImageList_DragEnter( m_hwndTreeView,
lpnmtv->ptDrag.x-rcItem.left - xIndent,
lpnmtv->ptDrag.y);
    return;
}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::OnMouseMove( LONG xCur, LONG yCur)
{
    HTREEITEM htiTarget;  // handle of target item
    TV_HITTESTINFO tvht;  // hit test information

TV_ITEMtvi;

ZeroMemory(&tvi, sizeof(TV_ITEM ) );

    if (m_fDragging)
{

        // Drag the item to the current position of the mouse cursor.
RECT rcTree, rcParent;

GetWindowRect( m_hwndParent, &rcParent );
GetWindowRect( m_hwndTreeView, &rcTree );
ImageList_DragMove(xCur, yCur);

        // Find out if the cursor is on the item. If it is, highlight
        // the item as a drop target.
        tvht.pt.x = xCur;
        tvht.pt.y = yCur;
        if ((htiTarget = TreeView_HitTest(m_hwndTreeView, &tvht)) != NULL)
{
  ImageList_DragLeave( m_hwndTreeView );
TreeView_SelectDropTarget(m_hwndTreeView, htiTarget);
ImageList_DragEnter( m_hwndTreeView, xCur, yCur);

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htiTarget;

TreeView_GetItem(m_hwndTreeView, &tvi);

  memcpy( &m_bsDropTarget, (LPBRANCHSTRUCT)(tvi.lParam), sizeof(BRANCHSTRUCT) );

}
else
{
ZeroMemory( &m_bsDropTarget, sizeof(BRANCHSTRUCT) );
}
    }
    return;
}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::OnLButtonUp(void)
{
    if (m_fDragging)
{
        ImageList_EndDrag();
        ReleaseCapture();
    ShowCursor( TRUE );
        m_fDragging = FALSE;
  ImageList_DragLeave( m_hwndTreeView );

if (m_bsDropTarget.dpid)
{
switch (m_bsDragging.btType )
{
case BT_PLAYER:
m_lpDP3A->AddPlayerToGroup(m_bsDropTarget.dpid, m_bsDragging.dpid );
break;

case BT_GROUP:
m_lpDP3A->AddGroupToGroup(m_bsDropTarget.dpid, m_bsDragging.dpid );
break;

case BT_PLAYER_IN_GROUP:
if (m_bsDropTarget.dpid != m_bsParentOfDragging.dpid)
{
m_lpDP3A->AddPlayerToGroup(m_bsDropTarget.dpid, m_bsDragging.dpid );
m_lpDP3A->DeletePlayerFromGroup(m_bsParentOfDragging.dpid, m_bsDragging.dpid );
}
break;

case BT_SHORTCUT_IN_GROUP:
if (m_bsDropTarget.dpid != m_bsParentOfDragging.dpid)
{
m_lpDP3A->AddGroupToGroup(m_bsDropTarget.dpid, m_bsDragging.dpid );
m_lpDP3A->DeleteGroupFromGroup(m_bsParentOfDragging.dpid, m_bsDragging.dpid );
}
break;
}
}

TreeView_SelectDropTarget( m_hwndTreeView, (HTREEITEM) NULL );
    }
    return;
}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::Redraw(void)
{
RECT r;

GetWindowRect(m_hwndTreeView, &r );
InvalidateRect(m_hwndTreeView, &r, TRUE);
UpdateWindow(m_hwndParent);

}

///////////////////////////////////////////////////////////////////////////////////////

void CGroupTree::OnRButtonDown( LONG xCur, LONG yCur)
{
    HTREEITEM htiTarget;  // handle of target item
    TV_HITTESTINFO tvht;  // hit test information
LPBRANCHSTRUCT lpbs = NULL;
HMENU hMenu = NULL;

TV_ITEMtvi;

ZeroMemory(&tvi, sizeof(TV_ITEM ) );
RECT rc;

GetWindowRect( m_hwndTreeView, &rc );

    // Find out if the cursor is on the item. If it is, highlight
    // the item as a drop target.
    tvht.pt.x = xCur - rc.left;
    tvht.pt.y = yCur - rc.top;
    if ((htiTarget = TreeView_HitTest(m_hwndTreeView, &tvht)) != NULL)
{
TreeView_SelectItem(m_hwndTreeView, htiTarget);

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htiTarget;

TreeView_GetItem(m_hwndTreeView, &tvi);

if (lpbs = (LPBRANCHSTRUCT)tvi.lParam)
{
m_dpidMenuTarget = lpbs->dpid;

switch (lpbs->btType)
{
case BT_GROUP:
hMenu = m_hGroupMenu;
EnableMenuItem(hMenu,ID_GROUP_STARTSESSION,
(DPGROUP_STAGINGAREA & lpbs->dwFlags)?MF_ENABLED:MF_GRAYED );
EnableMenuItem(hMenu,ID_GROUP_CONNECTIONSETTINGS,
(DPGROUP_STAGINGAREA & lpbs->dwFlags)?MF_ENABLED:MF_GRAYED );
break;

case BT_PLAYER:
hMenu = m_hPlayerMenu;
break;

case BT_SHORTCUT_IN_GROUP:
hMenu = m_hShortcutMenu;
break;

case BT_PLAYER_IN_GROUP:
hMenu = m_hPlayerInGroupMenu;
break;

default:
hMenu = m_hRootMenu;
break;
}
}
}
else
{
hMenu = m_hRootMenu;
}

TrackPopupMenuEx(hMenu,
TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
xCur, yCur, m_hwndParent, NULL );
}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::OnDblClk( LONG xCur, LONG yCur)
{
    HTREEITEM htiTarget;  // handle of target item
    TV_HITTESTINFO tvht;  // hit test information
LPBRANCHSTRUCT lpbs = NULL;
HMENU hMenu = NULL;

TV_ITEMtvi;

ZeroMemory(&tvi, sizeof(TV_ITEM ) );
RECT rc;

GetWindowRect( m_hwndTreeView, &rc );

    // Find out if the cursor is on the item. If it is, highlight
    // the item as a drop target.
    tvht.pt.x = xCur - rc.left;
    tvht.pt.y = yCur - rc.top;
    if ((htiTarget = TreeView_HitTest(m_hwndTreeView, &tvht)) != NULL)
{
TreeView_SelectItem(m_hwndTreeView, htiTarget);

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htiTarget;

TreeView_GetItem(m_hwndTreeView, &tvi);

if (lpbs = (LPBRANCHSTRUCT)tvi.lParam)
{
m_dpidMenuTarget = lpbs->dpid;

switch (lpbs->btType)
{
case BT_SHORTCUT_IN_GROUP:
case BT_GROUP:
if (m_dpidLastGroup != lpbs->dpid)
{
m_lpDP3A->AddPlayerToGroup(lpbs->dpid, m_dpidPlayer );

m_lpDP3A->DeletePlayerFromGroup(m_dpidLastGroup, m_dpidPlayer );
m_dpidLastGroup = lpbs->dpid;
}
break;

default:
break;
}
}
}
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CGroupTree::BeginLabelEdit()
{
// Workaround for Windows tree control/dialog odd behavior
// Retrieve the handle of the edit control.
m_hwndEditCtrl = TreeView_GetEditControl( m_hwndTreeView );

// Subclass the edit control.
gwpOrigEditProc = (WNDPROC) SetWindowLong(m_hwndEditCtrl, GWL_WNDPROC, (LONG) EditCtrlSubProc );

return DP_OK;
}

///////////////////////////////////////////////////////////////////////////////////////
void CGroupTree::EndLabelEdit( TV_DISPINFO FAR * lpTVDisp )
{
HRESULT hr;

// Subclass the edit control.
SetWindowLong(m_hwndEditCtrl, GWL_WNDPROC, (LONG) gwpOrigEditProc);

if (lpTVDisp->item.pszText)
{
LPBRANCHSTRUCT lpbs = (LPBRANCHSTRUCT) lpTVDisp->item.lParam;

hr = CheckAccessRights(lpTVDisp);

if (SUCCEEDED(hr))
{
DPNAME dpn;
dpn.dwSize = sizeof(DPNAME);
dpn.lpszShortNameA = lpTVDisp->item.pszText;
dpn.lpszLongNameA = lpTVDisp->item.pszText;

switch (lpbs->btType)
{
case  BT_GROUP:
m_lpDP3A->SetGroupName(lpbs->dpid, &dpn, DPSET_REMOTE );
break;

case BT_PLAYER_IN_GROUP:
case BT_PLAYER:
m_lpDP3A->SetPlayerName(lpbs->dpid, &dpn,  DPSET_REMOTE );
break;
}
}
else
{
MessageBox( m_hwndParent, "Cannot change the name of remote players or groups", "Error", MB_OK);
return;
}
}
}

///////////////////////////////////////////////////////////////////////////////////////
DPID CGroupTree::GetBranchStructOfParent( HTREEITEM htChildItem, LPBRANCHSTRUCT lpbt)
{

HTREEITEMhtItem;
TV_ITEMtvi;

ZeroMemory( &tvi, sizeof( TV_ITEM ) );
ZeroMemory( lpbt, sizeof( BRANCHSTRUCT ) );

htItem = TreeView_GetParent( m_hwndTreeView, htChildItem );

if (htItem)
{

tvi.mask            = TVIF_PARAM;
tvi.hItem           = htItem;

TreeView_GetItem(m_hwndTreeView, &tvi);

if (lpbt)
{
*lpbt = *((LPBRANCHSTRUCT)(tvi.lParam));
}

return ((LPBRANCHSTRUCT) (tvi.lParam))->dpid;
}

return 0;

}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CGroupTree::OnWM_NOTIFY( WPARAM wParam, LPARAM lParam )
{
int idCtrl = (int) wParam;
LPNMHDR pnmh = (LPNMHDR)lParam;
if (NULL == pnmh)
return FALSE;
  NM_TREEVIEW*nmtv;
TV_ITEM*pItemNew = NULL;
TV_ITEM*pItemOld = NULL;
LPBRANCHSTRUCTlpBranch = NULL;
HRESULT hr;

switch(pnmh->code)
{
case NM_RCLICK:
{
POINT p;
GetCursorPos(&p);
OnRButtonDown(p.x, p.y );
}
break;

case NM_DBLCLK:
{
POINT p;
GetCursorPos(&p);
OnDblClk(p.x, p.y );
}
break;

case TVN_BEGINLABELEDIT:
hr = BeginLabelEdit();

if (FAILED(hr))
return TRUE;
break;

case TVN_ENDLABELEDIT:
EndLabelEdit((TV_DISPINFO FAR *) lParam);
break;

case TVN_SELCHANGING:
break;

case TVN_DELETEITEM:
nmtv= (NM_TREEVIEW *)lParam;
pItemOld= &nmtv->itemOld;
LocalFree( (LPVOID) pItemOld->lParam );
break;

case TVN_BEGINDRAG:
OnBeginDrag((NM_TREEVIEW *) lParam);
break;

}

return 0;
}

HRESULTCGroupTree::EditLabel()
{
HRESULT hr; // Initialized by call to CheckAccessRights()
HTREEITEMhtItem;
TV_ITEMtvi;

ZeroMemory( &tvi, sizeof( TV_ITEM ) );

htItem = TreeView_GetSelection(m_hwndTreeView);

if (htItem)
{
HWND hwnd = NULL;

hwnd = TreeView_EditLabel( m_hwndTreeView, htItem );
hr = (hwnd ? DP_OK : DPERR_ACCESSDENIED);
}
else
{
hr = DPERR_ACCESSDENIED;
}

return hr;
}

HRESULT CGroupTree::CheckAccessRights(TV_DISPINFO FAR * lpTVDisp)
{
HRESULT hr = DPERR_GENERIC;
DWORDdwFlags = 0;
BRANCHSTRUCT bsCurSel;

if (NULL == lpTVDisp)
{
GetDPIDOfCurrentSelection( &bsCurSel );
}
else
{
bsCurSel = *((LPBRANCHSTRUCT) lpTVDisp->item.lParam);
}

switch ( bsCurSel.btType )
{
case BT_PLAYER:
case BT_PLAYER_IN_GROUP:
hr = m_lpDP3A->GetPlayerFlags( bsCurSel.dpid, &dwFlags );
break;

case BT_GROUP:
hr = m_lpDP3A->GetGroupFlags( bsCurSel.dpid, &dwFlags );
break;

default:
break;
}

if (FAILED(hr))
return hr;

if (dwFlags & DPPLAYER_LOCAL )
{
hr = DP_OK;
}
else
{
hr = DPERR_ACCESSDENIED;
}

return hr;
}


CGRPTREE.H

#ifndef __CGRPTREE__
#define __CGRPTREE__

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <dplay.h>
#include <dplobby.h>
#include "resource.h"

//#DEFINES
#define ST_NO_SUBGROUPS0
#define ST_SEARCH_SUBGROUPS1

class CGroupTree;

typedef enum {  BT_INVALID = 0,
BT_GROUP,
BT_SHORTCUT_IN_GROUP,
BT_PLAYER_IN_GROUP,
BT_PLAYER,
 } BRANCH_TYPE;

typedef struct tagBRANCHSTRUCT
{
    DPIDdpid;
    BRANCH_TYPEbtType;
DWORDdwFlags;
} BRANCHSTRUCT, *LPBRANCHSTRUCT;

typedef struct tagENUMTREESTRUCT
{
    DPIDdpidParent;
CGroupTree *lpTree;
BOOLbRecursive;
} ENUMTREESTRUCT, *LPENUMTREESTRUCT;

class CGroupTree{
private:
HINSTANCEm_hInst;
HWNDm_hwndTreeView;
HWNDm_hwndParent;
HWNDm_hwndEditCtrl;
HIMAGELISTm_hImageList;
intm_nStagingAreaImg;
intm_nGroupImg;
intm_nInGroupImg;
intm_nPlayerImg;
intm_nShortcutInGroupImg;
intm_nSpectatorImg;
intm_nSessionInProgressImg;
BOOLm_fDragging;
BRANCHSTRUCTm_bsParentOfDragging;
BRANCHSTRUCTm_bsDragging;
BRANCHSTRUCTm_bsDropTarget;
HMENUm_hMenu,
m_hRootMenu,
m_hGroupMenu,
m_hPlayerMenu,
m_hShortcutMenu,
m_hPlayerInGroupMenu;
DPIDm_dpidMenuTarget;
DPIDm_dpidPlayer;
DPIDm_dpidLastGroup;

private:
HTREEITEM FindItem( HTREEITEM htiSearchRoot,
DPID dpidTarget,
BRANCH_TYPE bt,
DWORD dwSearch );
HTREEITEM Insert(HTREEITEM htiParent,
DPID dpID,
LPSTR lpShortNameA,
BRANCH_TYPE bt, DWORD dwFlags );
HRESULTRecursiveRename(HTREEITEM htiSearchRoot,
DPID dpidTarget,
LPSTR lpszName );
DPIDGetBranchStructOfParent( HTREEITEM htChildItem, LPBRANCHSTRUCT lpbt);

public:
LPDIRECTPLAY3Am_lpDP3A;

public:
CGroupTree();
~CGroupTree();

BOOLInit( HWND hWnd, LPDIRECTPLAY3A lpDP3A, DPID dpidPlayer );
HRESULT CreateGroup( DPID dpidGroup,  LPSTR lpszShortNameA, DWORD dwFlags );
HRESULT CreatePlayer( DPID dpidPlayer,  LPSTR lpszShortNameA, DWORD dwFlags );
HRESULT DestroyGroup( DPID dpidGroup );
HRESULT DestroyPlayer( DPID dpidPlayer );
HRESULT CreateGroupInGroup( DPID dpidParentGroup, DPID dpidChildGroup, LPSTR lpszShortNameA, DWORD dwFlags );
HRESULT AddPlayerToGroup(DPID dpidGroup, DPID dpidPlayer, DWORD dwFlags);
HRESULT AddGroupToGroup( DPID dpidParentGroup, DPID dpidShortcut, DWORD dwFlags );
HRESULT DeletePlayerFromGroup(DPID dpidGroup, DPID dpidPlayer );
HRESULT DeleteGroupFromGroup( DPID dpidParentGroup, DPID dpidShortcut );
HRESULT GetPlayerName( DPID dpidPlayerName, LPDPNAME * lplpName );
HRESULT GetGroupName( DPID dpidGroupName, LPDPNAME * lplpName );
DPIDGetDPIDOfCurrentSelection(LPBRANCHSTRUCT lpbt = NULL);
DPIDGetDPIDOfCurrentSelectionParent( LPBRANCHSTRUCT lpbt = NULL);
HRESULTSetPlayerName( DPID dpidPlayer, LPSTR lpszShortName );
HRESULT SetGroupName( DPID dpidPlayer, LPSTR lpszShortName );
HRESULT Refresh( BOOL bRecursive = TRUE );
BOOLUpdate(LPVOID lpvMsg);
voidOnBeginDrag(NM_TREEVIEW *lpnmtv);
voidOnMouseMove( LONG xCur, LONG yCur);
voidOnLButtonUp(void);
voidOnRButtonDown( LONG xCur, LONG yCur);
voidOnDblClk( LONG xCur, LONG yCur);
voidRedraw();
HRESULTBeginLabelEdit();
voidEndLabelEdit(TV_DISPINFO FAR * lpTVDisp );
BOOLOnWM_NOTIFY( WPARAM wParam, LPARAM lParam );
HRESULTEditLabel();
HRESULT CheckAccessRights(TV_DISPINFO FAR * lpTVDisp = NULL);

};

#endif


CONNSET.CPP

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       ConnSet.cpp
 *  Content:A dialog to set and get Group connection settings for a lobby
 *
 ***************************************************************************/

#include "Bellhop.h"

HRESULTGetConnectionSPGuid(HWND hWnd, int idCombo, GUID *lpGuidSP);
HRESULTGetComboBoxGuid(HWND hWnd, LONG iDialogItem, LPGUID lpguidReturn);
HRESULT SetGroupConnection(HWND hWnd, LPLOBBYGROUPCONTEXT lpContext);
BOOL CALLBACK LobbyGroupWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL InitConnectionSettingsDialog(  HWND hWnd, LPLOBBYGROUPCONTEXT lpContext);
BOOL FAR PASCALEnumApp(LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags);
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(
LPCGUIDlpguidSP,
LPVOIDlpConnection,
DWORDdwSize,
LPCDPNAMElpName,
DWORDdwFlags,
LPVOIDlpContext);

///////////////////////////////////////////////////////////////////////////////////////
BOOL InitConnectionSettingsDialog( HWND hWnd, LPLOBBYGROUPCONTEXT lpContext)
{
BOOLbRet= FALSE,
bFound;
intiNum= 0,
i,
lParam;
LPCONNECTIONINFOlpci= NULL;
LPGUIDlpGuid= NULL;
LPDPLCONNECTIONlpdpconn = NULL;
HRESULThr;
ENUMCONNSTRUCTenStruct;
DWORDdwSize;

HWNDhSPComboBox = GetDlgItem( hWnd, IDC_GROUPCONNECTIONSPCOMBO );
HWNDhAppComboBox = GetDlgItem( hWnd, IDC_APPCOMBO );

// put all the DirectPlay applications in a combo box
lpContext->lpDPInfo->lpDirectPlayLobby2A->EnumLocalApplications(EnumApp, hWnd, 0);

// put all the available connections in a combo box
enStruct.hWnd = hWnd;
enStruct.idCombo = IDC_GROUPCONNECTIONSPCOMBO;

hr = IDirectPlay3_EnumConnections(lpContext->lpDPInfo->lpDirectPlay3A,
&BELLHOP_GUID,
DirectPlayEnumConnectionsCallback,
&enStruct, DPCONNECTION_DIRECTPLAY );
if (FAILED(hr))
goto FAILURE;

dwSize = 0;
hr = IDirectPlay3_GetGroupConnectionSettings( lpContext->lpDPInfo->lpDirectPlay3A,
0, lpContext->dpidRoom, lpdpconn, &dwSize );

if (DPERR_BUFFERTOOSMALL != hr)
goto FAILURE;

lpdpconn = (LPDPLCONNECTION) GlobalAllocPtr( GHND, dwSize );
if (NULL == lpdpconn)
goto FAILURE;

hr = IDirectPlay3_GetGroupConnectionSettings( lpContext->lpDPInfo->lpDirectPlay3A,
0, lpContext->dpidRoom, lpdpconn, &dwSize );

if (FAILED(hr))
goto FAILURE;

iNum = ComboBox_GetCount( hSPComboBox );

bFound = FALSE;
for (i=0; i<iNum; i++)
{
lParam =  ComboBox_GetItemData( hSPComboBox, i );

if ((lParam) && (CB_ERR != lParam))
{
lpci = (LPCONNECTIONINFO) lParam;
if (IsEqualGUID( lpci->guidSP, lpdpconn->guidSP ))
{
bFound = TRUE;
ComboBox_SetCurSel( hSPComboBox, i );
break;
}
lpci = NULL;
}

lParam = NULL;
}

if (FALSE == bFound)
{
//No match.
ComboBox_AddString( hSPComboBox, "<Unknown Service Provider>");
ComboBox_SetItemData( hSPComboBox, 0, 0);
ComboBox_SetCurSel( hSPComboBox, 0 );
}

iNum = ComboBox_GetCount( hAppComboBox );

bFound = FALSE;
for (i=0; i<iNum; i++)
{
lParam = ComboBox_GetItemData( hAppComboBox, i );

if ((lParam) && (CB_ERR != lParam))
{
lpGuid = (LPGUID) lParam;
if (IsEqualGUID( *lpGuid, lpdpconn->lpSessionDesc->guidApplication ))
{
bFound = TRUE;
ComboBox_SetCurSel( hAppComboBox, i );
break;
}
lpGuid = NULL;
}

lParam = NULL;

}

if (FALSE == bFound)
{
//No match.
ComboBox_AddString( hAppComboBox, "<Unknown Application>");
ComboBox_SetItemData( hAppComboBox, 0, 0);
ComboBox_SetCurSel( hAppComboBox, 0 );
}

bRet = TRUE;

// initialize max players
SetDlgItemInt(hWnd, IDC_MAXPLAYERSEDIT, 0, FALSE);
SetDlgItemText( hWnd, IDC_PASSWORDEDIT,lpdpconn->lpSessionDesc->lpszPasswordA );

FAILURE:
return bRet;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT SetGroupConnection(HWND hWnd, LPLOBBYGROUPCONTEXT lpContext)
{
CHARszPassword[MAXSTRLEN];
DWORDdwMaxPlayers;
LPDPLCONNECTIONlp = NULL;
HRESULThr;
GUIDguidApplication,
guidSP;
DWORDdwSize = 0;

//Pull the info from the dialog
GetDlgItemText(hWnd, IDC_PASSWORDEDIT, szPassword, MAXSTRLEN);
dwMaxPlayers = GetDlgItemInt(hWnd, IDC_MAXPLAYERSEDIT, NULL, FALSE);
hr = GetComboBoxGuid(hWnd, IDC_APPCOMBO, &guidApplication);

if (FAILED( hr))
{
ErrorBox( "Please select a different application.", hr );
return hr;
}

GetConnectionSPGuid(hWnd, IDC_GROUPCONNECTIONSPCOMBO, &guidSP);

if (FAILED( hr))
{
ErrorBox( "Please select a different service provider.", hr );
return hr;
}

//Get the old connection settings.
hr = IDirectPlay3_GetGroupConnectionSettings(lpContext->lpDPInfo->lpDirectPlay3A,
 0, lpContext->dpidRoom, NULL, &dwSize );

lp = (LPDPLCONNECTION) GlobalAllocPtr( GHND, dwSize );

if (lp)
{
hr = IDirectPlay3_GetGroupConnectionSettings(lpContext->lpDPInfo->lpDirectPlay3A,
 0, lpContext->dpidRoom, (LPVOID) lp, &dwSize );

lp->lpSessionDesc->dwMaxPlayers = dwMaxPlayers;
lp->lpSessionDesc->lpszPasswordA = szPassword;
lp->lpSessionDesc->guidApplication = guidApplication;
lp->guidSP = guidSP;

hr = IDirectPlay3_SetGroupConnectionSettings(lpContext->lpDPInfo->lpDirectPlay3A,
 0, lpContext->dpidRoom, lp );
}
else
{
hr = DPERR_OUTOFMEMORY;
}

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK ConnectionSettingsDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPLOBBYGROUPCONTEXTlpContext = (LPLOBBYGROUPCONTEXT) GetWindowLong(hWnd, DWL_USER);
HRESULThr;

    switch(uMsg)
    {
        case WM_INITDIALOG:

// context passed in lParam
lpContext = (LPLOBBYGROUPCONTEXT) lParam;

            // save the globals with the window
SetWindowLong(hWnd, DWL_USER, (LONG) lpContext);
InitConnectionSettingsDialog( hWnd, lpContext );
            break;

        case WM_DESTROY:
{
WPARAMindex;
LRESULTlpData;

// destroy the GUID's stored with each app name
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}

// destroy the connection info in the combo box.
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_GROUPCONNECTIONSPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}
}
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDOK:
// save changes they made
hr = SetGroupConnection( hWnd, lpContext);

if (SUCCEEDED(hr))
{
// Return success
EndDialog(hWnd, TRUE);
}
                    break;

case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, FALSE);

                    break;

case IDC_STAGINGAREA:
{
int i = SendDlgItemMessage( hWnd, IDC_STAGINGAREA, BM_GETCHECK, 0, 0 );
EnableWindow( GetDlgItem( hWnd, IDC_PASSWORDEDIT ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_APPCOMBO ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_MAXPLAYERSEDIT ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_GROUPCONNECTIONSPCOMBO ), (BST_CHECKED==i));
}
break;

            }

            break;
    }

    // Allow for default processing
    return FALSE;
}


DIALOG.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dialog.cpp
 *  Content:Creates a dialog to query the user for connection settings
 *and establish a connection.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <cguid.h>

#include "bellhop.h"
#include "resource.h"

// constants
const DWORD MAXNAMELEN= 200;// max size of a session or player name
const UINTTIMERID= 1;// timer ID to use
const UINTTIMERINTERVAL= 1000;// timer interval

// prototypes
BOOL CALLBACKConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACKSecurityCredentialsWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HRESULTCreateDirectPlayInterface(LPDIRECTPLAY3A *lplpDirectPlay3A );
HRESULTCreateDirectPlayLobbyInterface(LPDIRECTPLAYLOBBY2A *lplpDirectPlayLobby2A );
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(LPCGUID lpguidSP, LPVOID lpConnection, DWORD dwSize, LPCDPNAME lpName,
  DWORD dwFlags, LPVOID lpContext);
HRESULTDestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);
HRESULTDestroyDirectPlayLobbyInterface(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDirectPlayLobby2A);
HRESULT JoinSession(HWND hWnd,
LPDIRECTPLAY3A lpDirectPlay3A,
LPDIRECTPLAYLOBBY2A lpDirectPlayLobby2A,
LPGUID lpguidSessionInstance,
DWORDdwSessionFlags,
LPSTR lpszPlayerName,
DWORDdwPlayerFlags,
LPDPLAYINFO lpDPInfo);

HRESULTEnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);

HRESULTGetConnection(HWND hWnd,  int idCombo, LPVOID *lplpConnection);
HRESULTGetConnectionSPGuid(HWND hWnd, int idCombo, GUID *lpGuidSP);
voidDeleteConnectionList(HWND hWnd);
HRESULTGetSessionInfo(HWND hWnd, LPGUID lpguidSessionInstance, LPDWORD lpdwFlags);
voidSelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance);
voidDeleteSessionInstanceList(HWND hWnd);
voidEnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable);

///////////////////////////////////////////////////////////////////////////////////////
HRESULT ConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
// ask user for connection settings
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CONNECTDIALOG),
   NULL, (DLGPROC) ConnectWndProc, (LPARAM) lpDPInfo))
{
return (DP_OK);
}
else
{
return (DPERR_USERCANCEL);
}
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK ConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
static LPDIRECTPLAY3AlpDirectPlay3A;
static LPDIRECTPLAYLOBBY2AlpDirectPlayLobby2A;
static UINTidTimer;
GUIDguidSessionInstance;
charszPlayerName[MAXNAMELEN];
DWORDdwNameSize;
HRESULThr;
LPVOIDlpConnection = NULL;
ENUMCONNSTRUCTenStruct;
DWORDdwSessionFlags;
DWORDdwPlayerFlags = NULL;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;
lpDirectPlay3A = NULL;
lpDirectPlayLobby2A = NULL;

// Create an IDirectPlay3 interface
hr = CreateDirectPlayInterface(&lpDirectPlay3A);
if FAILED(hr)
goto SETUP_FAILURE;

// Create an IDirectLobby2 interface
hr = CreateDirectPlayLobbyInterface(&lpDirectPlayLobby2A);
if FAILED(hr)
goto SETUP_FAILURE;

// set first item in the connections combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, (WPARAM) 0, (LPARAM) "<Select a lobby provider>");
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) 0, (LPARAM) 0);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);

// put all the available connections in a combo box
enStruct.hWnd = hWnd;
enStruct.idCombo = IDC_SPCOMBO;

IDirectPlay3_EnumConnections(lpDirectPlay3A, &BELLHOP_GUID, DirectPlayEnumConnectionsCallback,
&enStruct, DPCONNECTION_DIRECTPLAYLOBBY);

// setup initial button state
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_SPECTATORBUTTON, FALSE);
break;

SETUP_FAILURE:
ErrorBox("Could not create DirectPlay object because of error 0x%08X", hr);
EndDialog(hWnd, FALSE);
break;

case WM_DESTROY:
// delete information stored along with the lists
DeleteConnectionList(hWnd);
DeleteSessionInstanceList(hWnd);
        break;

case WM_TIMER:
// refresh the session list
hr = EnumSessions(hWnd, lpDirectPlay3A);
break;

    case WM_COMMAND:

      switch(LOWORD(wParam))
        {
case IDC_SPCOMBO:
switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
// service provider changed, so rebuild display and
// delete any existing DirectPlay interface
KillTimer(hWnd, idTimer );
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

// get pointer to the selected connection
hr = GetConnection(hWnd, IDC_SPCOMBO, &lpConnection);
if FAILED(hr)
goto SP_FAILURE;

if (lpConnection)
{
  // Create a new DPlay interface.
hr = CreateDirectPlayInterface(&lpDirectPlay3A);

if ((FAILED(hr)) || (NULL == lpDirectPlay3A))
goto SP_FAILURE;

// initialize the connection
hr = IDirectPlay3_InitializeConnection(lpDirectPlay3A, lpConnection, 0);
if FAILED(hr)
goto SP_FAILURE;

// start enumerating the sessions
hr = EnumSessions(hWnd, lpDirectPlay3A);
if FAILED(hr)
goto SP_FAILURE;

// set a timer to refresh the session list
idTimer = SetTimer(hWnd, TIMERID, TIMERINTERVAL, NULL);
}
else
{
// They've selected the generic option "<Select a service provider>"
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_SPECTATORBUTTON, FALSE);
}
break;
}
break;

SP_FAILURE:
if (hr != DPERR_USERCANCEL)
ErrorBox("Could not select service provider because of error 0x%08X", hr);
break;

        case IDC_SPECTATORBUTTON:
// Joining as a spectator is the same as a regular join
// just with different flags.
dwPlayerFlags = DPPLAYER_SPECTATOR;
// Fall through to case IDC_JOINBUTTON:
        case IDC_JOINBUTTON:

// should have an interface by now
if (lpDirectPlay3A == NULL)
break;

    KillTimer(hWnd, idTimer );
// get guid of selected session instance

hr = GetSessionInfo(hWnd, &guidSessionInstance, &dwSessionFlags);
if FAILED(hr)
goto JOIN_FAILURE;

// use computer name for player name
dwNameSize = MAXNAMELEN;
if (!GetComputerName(szPlayerName, &dwNameSize))
lstrcpy(szPlayerName, "unknown");
_strlwr(szPlayerName);

// join this session
hr = JoinSession(hWnd,
lpDirectPlay3A,
 lpDirectPlayLobby2A,
 &guidSessionInstance,
 dwSessionFlags,
 szPlayerName,
 dwPlayerFlags,
 lpDPInfo);

if FAILED(hr)
goto JOIN_FAILURE;

// dismiss dialog if we succeeded in joining
EndDialog(hWnd, TRUE);
            break;

JOIN_FAILURE:
ErrorBox("Could not join session because of error 0x%08X", hr);
break;

        case IDCANCEL:
// delete any interface created if cancelling
KillTimer(hWnd, idTimer );
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

hr = DestroyDirectPlayLobbyInterface(hWnd, lpDirectPlayLobby2A);
lpDirectPlayLobby2A = NULL;

EndDialog(hWnd, FALSE);
            break;
        }

        break;
    }

    // Allow for default processing
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(
LPCGUIDlpguidSP,
LPVOIDlpConnection,
DWORDdwSize,
LPCDPNAMElpName,
DWORDdwFlags,
LPVOIDlpContext)
{
LPENUMCONNSTRUCTlp = (LPENUMCONNSTRUCT) lpContext;
    LRESULTiIndex;
LPCONNECTIONINFOlpConnectionBuffer = NULL;

// store service provider name in combo box
iIndex = SendDlgItemMessage(lp->hWnd, lp->idCombo, CB_ADDSTRING, 0,
(LPARAM) lpName->lpszShortNameA);
if (iIndex == CB_ERR)
goto FAILURE;

// make space for Connection Shortcut
lpConnectionBuffer = (LPCONNECTIONINFO) GlobalAllocPtr(GHND, dwSize+sizeof(CONNECTIONINFO));
if (lpConnectionBuffer == NULL)
goto FAILURE;

// store pointer to GUID in combo box
memcpy(lpConnectionBuffer->Connection, lpConnection, dwSize);
lpConnectionBuffer->guidSP = *lpguidSP;
SendDlgItemMessage(lp->hWnd, lp->idCombo, CB_SETITEMDATA, (WPARAM) iIndex,
(LPARAM) lpConnectionBuffer);

FAILURE:
    return (TRUE);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CreateDirectPlayInterface( LPDIRECTPLAY3A *lplpDirectPlay3A )
{
HRESULThr;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;

// Create an IDirectPlay3 interface
hr = CoCreateInstance(CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectPlay3A, (LPVOID*)&lpDirectPlay3A);

// return interface created
*lplpDirectPlay3A = lpDirectPlay3A;

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CreateDirectPlayLobbyInterface( LPDIRECTPLAYLOBBY2A *lplpDirectPlayLobby2A )
{
HRESULThr;
LPDIRECTPLAYLOBBY2AlpDirectPlayLobby2A = NULL;

// Create an IDirectPlay3 interface
hr = CoCreateInstance(CLSID_DirectPlayLobby, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectPlayLobby2A, (LPVOID*)&lpDirectPlayLobby2A);

// return interface created
*lplpDirectPlayLobby2A = lpDirectPlayLobby2A;

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
HRESULThr = DP_OK;

if (lpDirectPlay3A)
{
DeleteSessionInstanceList(hWnd);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_SPECTATORBUTTON, FALSE);

hr = IDirectPlay3_Release(lpDirectPlay3A);
}

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DestroyDirectPlayLobbyInterface(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDirectPlayLobby2A)
{
HRESULThr = DP_OK;

if (lpDirectPlayLobby2A)
{
hr = lpDirectPlayLobby2A->Release();
}

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT JoinSession(HWND hWnd,
LPDIRECTPLAY3A lpDirectPlay3A,
LPDIRECTPLAYLOBBY2A lpDirectPlayLobby2A,
LPGUID lpguidSessionInstance,
DWORDdwSessionFlags,
LPSTR lpszPlayerName,
DWORDdwPlayerFlags,
LPDPLAYINFO lpDPInfo)
{
DPIDdpidPlayer;
DPNAMEdpName;
DPSESSIONDESC2sessionDesc;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// Spectator or regular player
lpDPInfo->dwPlayerFlags = dwPlayerFlags;

// prepare a session description
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidInstance = *lpguidSessionInstance;
sessionDesc.dwFlags = dwSessionFlags;

if (DPSESSION_SECURESERVER & dwSessionFlags )
{
hr = IDirectPlay3_SecureOpen(lpDirectPlay3A,
&sessionDesc,
DPOPEN_JOIN,
NULL,
NULL );

if ( DPERR_LOGONDENIED == hr )
{

// we need to collect security credentials
// and try again.

if (DialogBoxParam( ghInstance,
MAKEINTRESOURCE(IDD_SECURITYCREDENTIALSDIALOG),
hWnd,
(DLGPROC) SecurityCredentialsWndProc,
(LPARAM) &lpDPInfo))
{

DPCREDENTIALS dpcr;
dpcr.dwSize = sizeof(DPCREDENTIALS);
dpcr.dwFlags = 0;
dpcr.lpszUsernameA = lpDPInfo->szSecureName;
dpcr.lpszPasswordA = lpDPInfo->szSecurePassword;
dpcr.lpszDomainA = lpDPInfo->szSecureDomain;

hr = IDirectPlay3_SecureOpen(lpDirectPlay3A,
&sessionDesc,
DPOPEN_JOIN,
NULL,
&dpcr );
if (FAILED(hr))
{
// Conceivably, we could cycle back and try to get credentials again
// but in this sample, we'll just drop out on the error.
goto OPEN_FAILURE;
}

lpDPInfo->bSecureSession = TRUE;
}
else
{
// abort. user clicked cancel.
goto OPEN_FAILURE;
}
}
}
else
{
// Session does not require security
hr = IDirectPlay3_Open(lpDirectPlay3A, &sessionDesc, DPOPEN_JOIN);
if FAILED(hr)
goto OPEN_FAILURE;
}

// fill out name structure
ZeroMemory(&dpName, sizeof(DPNAME));
dpName.dwSize = sizeof(DPNAME);
dpName.lpszShortNameA = lpszPlayerName;
dpName.lpszLongNameA = NULL;

// create a player with this name
hr = IDirectPlay3_CreatePlayer(lpDirectPlay3A, &dpidPlayer, &dpName,
lpDPInfo->hPlayerEvent,
NULL, 0, dwPlayerFlags );
if FAILED(hr)
goto CREATEPLAYER_FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->lpDirectPlayLobby2A = lpDirectPlayLobby2A;

lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = FALSE;

return (DP_OK);

CREATEPLAYER_FAILURE:
OPEN_FAILURE:
IDirectPlay3_Close(lpDirectPlay3A);
return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL EnumSessionsCallback(
LPCDPSESSIONDESC2lpSessionDesc,
LPDWORDlpdwTimeOut,
DWORDdwFlags,
LPVOIDlpContext)
{
HWNDhWnd = (HWND) lpContext;
LONGiIndex;
charszBuffer[256];
LPSESSIONINFOlpSessionInfo = NULL;

// see if last session has been enumerated
    if (dwFlags & DPESC_TIMEDOUT)
return (FALSE);

wsprintf(szBuffer,
(DPSESSION_SECURESERVER & lpSessionDesc->dwFlags ? "%s (SECURE)" : "%s" ),
lpSessionDesc->lpszSessionNameA );

// store session name in list
iIndex = SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_ADDSTRING,
(WPARAM) 0, (LPARAM) szBuffer);

if (iIndex == LB_ERR)
goto FAILURE;

// make space for session instance guid
lpSessionInfo = (LPSESSIONINFO) GlobalAllocPtr( GHND, sizeof(SESSIONINFO) );
if (lpSessionInfo == NULL)
goto FAILURE;

// Extract the data we need from the session description
lpSessionInfo->guidInstance = lpSessionDesc->guidInstance;
lpSessionInfo->dwFlags = lpSessionDesc->dwFlags;

// store pointer to guid in list
SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpSessionInfo);

FAILURE:
    return (TRUE);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT EnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
DPSESSIONDESC2sessionDesc;
GUIDguidSessionInstance;
DWORDdwFlags;
LONGiIndex;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// get guid of currently selected session
guidSessionInstance = GUID_NULL;
hr = GetSessionInfo(hWnd, &guidSessionInstance, &dwFlags);

// delete existing session list
DeleteSessionInstanceList(hWnd);

// add sessions to session list
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidApplication = BELLHOP_GUID;

hr = IDirectPlay3_EnumSessions(lpDirectPlay3A, &sessionDesc, 0, EnumSessionsCallback,
  hWnd, DPENUMSESSIONS_AVAILABLE | DPENUMSESSIONS_ASYNC);

// select the session that was previously selected
SelectSessionInstance(hWnd, &guidSessionInstance);

// hilite "Join" button only if there are sessions to join
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCOUNT,
   (WPARAM) 0, (LPARAM) 0);

EnableDlgButton(hWnd, IDC_JOINBUTTON, (iIndex > 0) ? TRUE : FALSE);
EnableDlgButton(hWnd, IDC_SPECTATORBUTTON, (iIndex > 0) ? TRUE : FALSE);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetConnection(HWND hWnd, int idCombo, LPVOID *lplpConnection)
{
LONGiIndex;

// get index of the item currently selected in the combobox
iIndex = SendDlgItemMessage(hWnd,  idCombo, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get the pointer to the connection shortcut associated with
// the item
iIndex = SendDlgItemMessage(hWnd, idCombo, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);

if ((CB_ERR == iIndex) || ( NULL == iIndex ))
return (DPERR_GENERIC);

*lplpConnection = &((LPCONNECTIONINFO) iIndex)->Connection;

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetConnectionSPGuid(HWND hWnd, int idCombo, GUID *lpGuidSP)
{
LONGiIndex;

// get index of the item currently selected in the combobox
iIndex = SendDlgItemMessage(hWnd,  idCombo, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get the pointer to the connection shortcut associated with
// the item
iIndex = SendDlgItemMessage(hWnd, idCombo, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);

if ((iIndex == CB_ERR) || (iIndex == NULL ))
return (DPERR_GENERIC);

*lpGuidSP = ((LPCONNECTIONINFO) iIndex)->guidSP;

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
void DeleteConnectionList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each service provider name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData != 0)// no data to delete
GlobalFreePtr((LPVOID) lpData);

i += 1;
}

// delete all items in combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetSessionInfo(HWND hWnd, LPGUID lpguidSessionInstance, LPDWORD lpdwFlags)
{
LONGiIndex;
LPSESSIONINFOlp;

// get guid for session
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == LB_ERR)
return (DPERR_GENERIC);

iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == LB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

lp = (LPSESSIONINFO) iIndex;
*lpguidSessionInstance = lp->guidInstance;
*lpdwFlags = lp->dwFlags;

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
void DeleteSessionInstanceList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each session name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to delete
continue;

GlobalFreePtr((LPVOID) lpData);
i += 1;
}

// delete all items in list
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

///////////////////////////////////////////////////////////////////////////////////////
void SelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance)
{
WPARAMi, iIndex;
LONGlpData;

// loop over the GUID's stored with each session name
// to find the one that matches what was passed in
i = 0;
iIndex = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to compare to
continue;

// guid matches
if (IsEqualGUID(*lpguidSessionInstance, *((LPGUID) lpData)))
{
iIndex = i;// store index of this string
break;
}

i += 1;
}

// select this item
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_SETCURSEL, (WPARAM) iIndex, (LPARAM) 0);
}

///////////////////////////////////////////////////////////////////////////////////////
void EnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable)
{
EnableWindow(GetDlgItem(hDlg, nIDDlgItem), bEnable);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK SecurityCredentialsWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
HWNDhwndName= NULL,
hwndPassword= NULL,
hwndDomain= NULL;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        lpDPInfo = (LPDPLAYINFO) lParam;
break;

    case WM_COMMAND:

      switch(LOWORD(wParam))
        {
case IDOK:
hwndName = GetDlgItem( hWnd, IDC_SECURENAME );
hwndPassword = GetDlgItem( hWnd, IDC_SECUREPASSWORD );
hwndDomain = GetDlgItem( hWnd, IDC_SECUREDOMAIN );

Edit_GetText(hwndName, lpDPInfo->szSecureName, 256);
Edit_GetText(hwndPassword, lpDPInfo->szSecurePassword, 256);
Edit_GetText(hwndDomain, lpDPInfo->szSecureDomain, 256);
EndDialog(hWnd, TRUE);
break;

        case IDCANCEL:
EndDialog(hWnd, FALSE);
            break;
        }

        break;
    }

    // Allow for default processing
    return FALSE;
}


LOBBY.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       lobby.cpp
 *  Content:Uses information from the lobby to establish a connection.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <dplobby.h>

#include "bellhop.h"

HRESULT ConnectUsingLobby(LPDPLAYINFO lpDPInfo)
{
LPDIRECTPLAY2AlpDirectPlay2A = NULL;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;
LPDIRECTPLAYLOBBY2AlpDirectPlayLobby2A = NULL;
LPDPLCONNECTIONlpConnectionSettings = NULL;
DPIDdpidPlayer;
DWORDdwSize;
HRESULThr;

// get an ANSI DirectPlay lobby interface
hr = CreateDirectPlayLobbyInterface(&lpDirectPlayLobby2A );
if FAILED(hr)
goto FAILURE;

    // get connection settings from the lobby
// if this routine returns DPERR_NOTLOBBIED, then a lobby did not
// launch this application and the user needs to configure the connection.

// pass in a NULL pointer to just get the size of the connection setttings
hr = lpDirectPlayLobby2A->GetConnectionSettings(0, NULL, &dwSize);
if (DPERR_BUFFERTOOSMALL != hr)
goto FAILURE;

// allocate memory for the connection setttings
lpConnectionSettings = (LPDPLCONNECTION) GlobalAllocPtr(GHND, dwSize);
if (NULL == lpConnectionSettings)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the connection settings
hr = lpDirectPlayLobby2A->GetConnectionSettings(0, lpConnectionSettings, &dwSize);
if FAILED(hr)
goto FAILURE;

// before connecting, the game should configure the session description
// with any settings it needs

// set flags and max players used by the game
    lpConnectionSettings->lpSessionDesc->dwFlags = DPSESSION_MIGRATEHOST |
   DPSESSION_KEEPALIVE;
    lpConnectionSettings->lpSessionDesc->dwMaxPlayers = MAXPLAYERS;

    // store the updated connection settings
    hr = lpDirectPlayLobby2A->SetConnectionSettings(0, 0, lpConnectionSettings);
if FAILED(hr)
goto FAILURE;

// connect to the session - returns an ANSI IDirectPlay2A interface
hr = lpDirectPlayLobby2A->Connect(0, &lpDirectPlay2A, NULL);
if FAILED(hr)
goto FAILURE;

// Obtain an IDirectPlay3A interface, the IDirectPlay2A interface will
// be released at the end of the function
hr = IDirectPlay2_QueryInterface(lpDirectPlay2A, IID_IDirectPlay3A, (LPVOID *) &lpDirectPlay3A);
if FAILED(hr)
goto FAILURE;

// create a player with the name returned in the connection settings
hr = IDirectPlay3_CreatePlayer(lpDirectPlay3A, &dpidPlayer,
lpConnectionSettings->lpPlayerName,
lpDPInfo->hPlayerEvent, NULL, 0, 0);
if FAILED(hr)
goto FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->lpDirectPlayLobby2A = lpDirectPlayLobby2A;

lpDPInfo->dpidPlayer = dpidPlayer;
if (lpConnectionSettings->dwFlags & DPLCONNECTION_CREATESESSION)
lpDPInfo->bIsHost = TRUE;
else
lpDPInfo->bIsHost = FALSE;

// set our interfaces to NULL here
// so they won't be released below

lpDirectPlay3A = NULL;
lpDirectPlayLobby2A = NULL;

FAILURE:
if (lpDirectPlay2A)
IDirectPlay2_Release(lpDirectPlay2A);

if (lpDirectPlay3A)
IDirectPlay3_Release(lpDirectPlay3A);

if (lpDirectPlayLobby2A)
IDirectPlayLobby_Release(lpDirectPlayLobby2A);

if (lpConnectionSettings)
GlobalFreePtr(lpConnectionSettings);

return (hr);
}


BELLHOP.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       bellhop.cpp
 *  Content:Simple lobby program using DirectPlay.
 *
 ***************************************************************************/

#define INITGUID
#include "bellhop.h"
#include "resource.h"

enum {
WM_USER_ADDSTRING= WM_USER+257,// window message to add string to chat string list
WM_USER_UPDATE// window message to update lobby display
};

// globals
HANDLEghReceiveThread = NULL;// handle of receive thread
DWORDgidReceiveThread = 0;// id of receive thread
HANDLEghKillReceiveEvent = NULL;// event used to kill receive thread
HWNDghMainWnd = NULL;// main window
HINSTANCEghInstance;// application instance

//EXTERNs
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(LPCGUID lpguidSP, LPVOID lpConnection,
DWORD dwSize, LPCDPNAME lpName,
DWORD dwFlags, LPVOID lpContext);
HRESULTGetConnection(HWND hWnd,  int idCombo, LPVOID *lplpConnection);
HRESULTGetConnectionSPGuid(HWND hWnd, int idCombo, GUID *lpGuidSP);

///////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow )
{
DPLAYINFODPInfo;
intiResult = 0;
HRESULThr;

ghInstance = hInstance;

// Initialize COM library
hr = CoInitialize(NULL);
if FAILED(hr)
goto FAILURE;

// setup the connection
hr = SetupConnection(hInstance, &DPInfo);
if FAILED(hr)
goto FAILURE;

// show the chat window
iResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_LOBBYDIALOG), NULL, (DLGPROC) LobbyWndProc, (LPARAM) &DPInfo);

FAILURE:
// shut down the connection
hr = ShutdownConnection(&DPInfo);

// Uninitialize the COM library
CoUninitialize();

return (iResult);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK LobbyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
DWORDdwTextLen;

    switch(uMsg)
    {

    case WM_INITDIALOG:
{
HDC hDC;
int xPixelsPerInch,
yPixelsPerInch;
RECT rect;

// Save the connection info pointer
lpDPInfo = (LPDPLAYINFO) lParam;

// Get the dialog spacing info
hDC = GetDC(hWnd);
xPixelsPerInch = GetDeviceCaps(hDC, LOGPIXELSX);
yPixelsPerInch = GetDeviceCaps(hDC, LOGPIXELSY);
ReleaseDC(hWnd, hDC);
lpDPInfo->xSpacing = xPixelsPerInch / 8;
lpDPInfo->ySpacing = yPixelsPerInch / 8;
lpDPInfo->xHalfSplitWidth = xPixelsPerInch / 12;

GetClientRect(hWnd, &rect);
            lpDPInfo->xPaneSplit = ((rect.right - rect.left) - (2 * lpDPInfo->xSpacing )) / 3
- lpDPInfo->xHalfSplitWidth;

// Create the tree control
lpDPInfo->lpGroupTree = new CGroupTree;
lpDPInfo->lpGroupTree->Init(GetDlgItem(hWnd, IDT_MESSAGEVIEW),
lpDPInfo->lpDirectPlay3A,
lpDPInfo->dpidPlayer);
lpDPInfo->lpGroupTree->Refresh();
lpDPInfo->lpGroupTree->CreatePlayer( lpDPInfo->dpidPlayer, NULL, lpDPInfo->dwPlayerFlags );

// store global window
ghMainWnd = hWnd;

// initiallize lobby
InitializeLobby(hWnd, lpDPInfo);

OnSize( hWnd, lpDPInfo );
}
        break;

    case WM_DESTROY:
ghMainWnd = NULL;
        break;

    case WM_SIZE:
        OnSize(hWnd, lpDPInfo);
        break;

    case WM_LBUTTONDOWN:
        lpDPInfo->bSplitMove = TRUE;
        SetCapture(hWnd);
        break;

    case WM_LBUTTONUP:
if (lpDPInfo->bSplitMove)
{
lpDPInfo->bSplitMove = FALSE;
ReleaseCapture();
}
else
{
lpDPInfo->lpGroupTree->OnLButtonUp();
}
        break;

    case WM_MOUSEMOVE:
        if(lpDPInfo->bSplitMove)
        {
// track mouse movement while adjusting the divider
            RECT    rect;
            // change the value from unsigned to signed
            int     x = (int)(short)LOWORD(lParam);

            GetClientRect(hWnd, &rect);
            if (rect.left > x)
            {
                x = rect.left;
            }
            else if (rect.right < x)
            {
                x = rect.right;
            }
            lpDPInfo->xPaneSplit = (x - lpDPInfo->xHalfSplitWidth - (lpDPInfo->xSpacing * 2));
            OnSize(hWnd, lpDPInfo);
        }
else
{
lpDPInfo->lpGroupTree->OnMouseMove(LOWORD(lParam), HIWORD(lParam) );
}
        break;

// this is a user-defined message used to add strings to the log window
case WM_USER_ADDSTRING:
// get length of text in log window
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, WM_GETTEXTLENGTH,
   (WPARAM) 0, (LPARAM) 0);

// put selection at end
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_SETSEL,
   (WPARAM) dwTextLen, (LPARAM) dwTextLen);

// add string in lParam to log window
SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_REPLACESEL,
(WPARAM) FALSE, (LPARAM) lParam);
GlobalFreePtr((LPVOID) lParam);
break;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case ID_ROOT_CREATEGROUP:
DoCreateRoom(hWnd, lpDPInfo);
break;

        case ID_GROUP_CONNECTIONSETTINGS:
DoGroupConnectionSettings(hWnd, lpDPInfo);
break;

        case ID_DESTROYGROUP:
DoDeleteRoom(hWnd, lpDPInfo);
break;

        case ID_PLAYERINGROUP_DELETEPLAYERFROMGROUP:
DoDeletePlayerFromGroup(hWnd, lpDPInfo);
break;

        case ID_CREATEGROUPINGROUP:
DoCreateTable(hWnd, lpDPInfo);
break;

        case ID_SHORTCUT_DELETEGROUPFROMGROUP:
DoDeleteTable(hWnd, lpDPInfo);
break;

        case ID_GROUP_STARTSESSION:
DoLaunch(hWnd, lpDPInfo);
break;

        case ID_PLAYER_SETPLAYERNAME:
case ID_PLAYERINGROUP_SETPLAYERNAME:
        case ID_GROUP_SETGROUPNAME:
lpDPInfo->lpGroupTree->EditLabel();
break;

        case IDC_SENDBUTTON:
SendChatMessage(hWnd, lpDPInfo);
break;

        case IDCANCEL:
EndDialog(hWnd, FALSE);
            break;

        case ID_ROOT_ENUMRECURSIVE:
lpDPInfo->lpGroupTree->Refresh( );
 lpDPInfo->lpGroupTree->CreatePlayer( lpDPInfo->dpidPlayer, NULL, lpDPInfo->dwPlayerFlags );
           break;

case ID_ROOT_ENUMGROUPS:
lpDPInfo->lpGroupTree->Refresh( FALSE );
 lpDPInfo->lpGroupTree->CreatePlayer( lpDPInfo->dpidPlayer, NULL, lpDPInfo->dwPlayerFlags );
           break;

}
break;

case WM_NOTIFY :
 return lpDPInfo->lpGroupTree->OnWM_NOTIFY( wParam, lParam );
break;

case WM_MENUSELECT:
return FALSE;
break;
}

    // Allow for default processing
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT SetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
HRESULThr;

ZeroMemory(lpDPInfo, sizeof(DPLAYINFO));

// create event used by DirectPlay to signal a message has arrived
lpDPInfo->hPlayerEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (lpDPInfo->hPlayerEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create event used to signal that the receive thread should exit
ghKillReceiveEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (ghKillReceiveEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create thread to receive player messages
ghReceiveThread = CreateThread(NULL,// default security
   0,// default stack size
   ReceiveThread,// pointer to thread routine
   lpDPInfo,// argument for thread
   0,// start it right away
   &gidReceiveThread);
if (ghReceiveThread == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// if there is no lobby connection, ask the user for settings
hr = ConnectUsingDialog(hInstance, lpDPInfo);
if FAILED(hr)
goto FAILURE;

return (DP_OK);

FAILURE:
ShutdownConnection(lpDPInfo);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT ShutdownConnection(LPDPLAYINFO lpDPInfo)
{
if (ghReceiveThread)
{
// wake up receive thread and wait for it to quit
SetEvent(ghKillReceiveEvent);
WaitForSingleObject(ghReceiveThread, INFINITE);

CloseHandle(ghReceiveThread);
ghReceiveThread = NULL;
}

if (ghKillReceiveEvent)
{
CloseHandle(ghKillReceiveEvent);
ghKillReceiveEvent = NULL;
}

if (lpDPInfo->lpDirectPlay3A)
{
if (lpDPInfo->dpidPlayer)
{
IDirectPlay3_DestroyPlayer(lpDPInfo->lpDirectPlay3A, lpDPInfo->dpidPlayer);
lpDPInfo->dpidPlayer = 0;
}
IDirectPlay3_Close(lpDPInfo->lpDirectPlay3A);
IDirectPlay3_Release(lpDPInfo->lpDirectPlay3A);
lpDPInfo->lpDirectPlay3A = NULL;

IDirectPlayLobby_Release(lpDPInfo->lpDirectPlayLobby2A);
lpDPInfo->lpDirectPlayLobby2A = NULL;

}

if (lpDPInfo->hPlayerEvent)
{
CloseHandle(lpDPInfo->hPlayerEvent);
lpDPInfo->hPlayerEvent = NULL;
}

if (lpDPInfo->lpGroupTree)
{
delete lpDPInfo->lpGroupTree;
lpDPInfo->lpGroupTree = NULL;
}
return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ReceiveThread(LPVOID lpThreadParameter)
{
    LPDPLAYINFOlpDPInfo = (LPDPLAYINFO) lpThreadParameter;
HANDLEeventHandles[2];

eventHandles[0] = lpDPInfo->hPlayerEvent;
eventHandles[1] = ghKillReceiveEvent;

// loop waiting for player events. If the kill event is signaled
// the thread will exit
while (WaitForMultipleObjects(2, eventHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
{
// receive any messages in the queue
ReceiveMessage(lpDPInfo);
}

ExitThread(0);

return (0);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT ReceiveMessage(LPDPLAYINFO lpDPInfo)
{
DPIDidFrom, idTo;
LPVOIDlpvMsgBuffer;
DWORDdwMsgBufferSize;
HRESULThr;

lpvMsgBuffer = NULL;
dwMsgBufferSize = 0;

// loop to read all messages in queue
do
{
// loop until a single message is successfully read
do
{
// read messages from any player, including system player
idFrom = 0;
idTo = 0;

hr = IDirectPlay3_Receive(lpDPInfo->lpDirectPlay3A, &idFrom, &idTo, DPRECEIVE_ALL,
  lpvMsgBuffer, &dwMsgBufferSize);

// not enough room, so resize buffer
if (hr == DPERR_BUFFERTOOSMALL)
{
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);

lpvMsgBuffer = GlobalAllocPtr(GHND, dwMsgBufferSize);
if (lpvMsgBuffer == NULL)
hr = DPERR_OUTOFMEMORY;
}
} while (hr == DPERR_BUFFERTOOSMALL);

if ((SUCCEEDED(hr)) &&// successfully read a message
(dwMsgBufferSize >= sizeof(DPMSG_GENERIC)))// and it is big enough
{
// update the tree view UI.
lpDPInfo->lpGroupTree->Update( lpvMsgBuffer );

// check for system message
if (idFrom == DPID_SYSMSG)
{
HandleSystemMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
dwMsgBufferSize, idFrom, idTo);
}
else
{
HandleApplicationMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
 dwMsgBufferSize, idFrom, idTo);
}
}
} while (SUCCEEDED(hr));

// free any memory we created
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
void HandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
  DPID idFrom, DPID idTo)
{
// Any messages received from the lobby server would be handled here.
// In this case, we don't have any
}

///////////////////////////////////////////////////////////////////////////////////////
void HandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo)
{
LPSTRlpszStr = NULL;

    // The body of each case is there so you can set a breakpoint and examine
    // the contents of the message received.
switch (lpMsg->dwType)
{
case DPSYS_STARTSESSION:
{
LPDPMSG_STARTSESSIONlp = (LPDPMSG_STARTSESSION) lpMsg;

HandleStartSession(lp->lpConn, lpDPInfo);
}
break;

case DPSYS_CREATEPLAYERORGROUP:
        {
            LPDPMSG_CREATEPLAYERORGROUPlp = (LPDPMSG_CREATEPLAYERORGROUP) lpMsg;
LPSTRlpszName, lpszDisplayFormat;

if (lp->dwPlayerType == DPPLAYERTYPE_PLAYER)
lpszDisplayFormat = "Player \"%s\" created\r\n";
else if (lp->dwPlayerType == DPPLAYERTYPE_GROUP)
lpszDisplayFormat = "Group \"%s\" created\r\n";
else
lpszDisplayFormat = "Unknown object \"%s\" created\r\n";

// get pointer to player name
if (lp->dpnName.lpszShortNameA)
lpszName = lp->dpnName.lpszShortNameA;
else
lpszName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(lpszDisplayFormat) +
   lstrlen(lpszName) + 1);
if (lpszStr == NULL)
break;

// build string
wsprintf(lpszStr, lpszDisplayFormat, lpszName);
        }
break;

case DPSYS_DESTROYPLAYERORGROUP:
        {
            LPDPMSG_DESTROYPLAYERORGROUPlp = (LPDPMSG_DESTROYPLAYERORGROUP)lpMsg;
LPSTRlpszName, lpszDisplayFormat;

if (lp->dwPlayerType == DPPLAYERTYPE_PLAYER)
lpszDisplayFormat = "Player \"%s\" destroyed\r\n";
else if (lp->dwPlayerType == DPPLAYERTYPE_GROUP)
lpszDisplayFormat = "Group \"%s\" destroyed\r\n";
else
lpszDisplayFormat = "Unknown object \"%s\" destroyed\r\n";

// get pointer to player name
if (lp->dpnName.lpszShortNameA)
lpszName = lp->dpnName.lpszShortNameA;
else
lpszName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(lpszDisplayFormat) +
   lstrlen(lpszName) + 1);
if (lpszStr == NULL)
break;

// build string
wsprintf(lpszStr, lpszDisplayFormat, lpszName);
        }
break;

case DPSYS_ADDPLAYERTOGROUP:
case DPSYS_DELETEPLAYERFROMGROUP:
        {
            LPDPMSG_ADDPLAYERTOGROUPlpAddMsg = (LPDPMSG_ADDPLAYERTOGROUP)lpMsg;
            LPDPMSG_DELETEPLAYERFROMGROUPlpDeleteMsg = (LPDPMSG_DELETEPLAYERFROMGROUP)lpMsg;
DPIDdpidPlayer, dpidGroup;
LPSTRlpszPlayerName, lpszGroupName;
LPDPNAMElpPlayerName, lpGroupName;
LPSTRlpszDisplayFormat;
            HRESULThr;

if (lpMsg->dwType == DPSYS_ADDPLAYERTOGROUP)
{
dpidPlayer = lpAddMsg->dpIdPlayer;
dpidGroup = lpAddMsg->dpIdGroup;
lpszDisplayFormat = "Player \"%s\" added to group \"%s\"\r\n";
}
else
{
dpidPlayer = lpDeleteMsg->dpIdPlayer;
dpidGroup = lpDeleteMsg->dpIdGroup;
lpszDisplayFormat = "Player \"%s\" removed from group \"%s\"\r\n";
}

// get pointer to player name
hr = GetPlayerName(lpDPInfo->lpDirectPlay3A, dpidPlayer, &lpPlayerName);
if FAILED(hr)
{
// A failure may mean that the player has been deleted
// since we began processing this message
lpPlayerName = NULL;
}

if ((lpPlayerName) && (lpPlayerName->lpszShortNameA))
lpszPlayerName = lpPlayerName->lpszShortNameA;
else
lpszPlayerName = "unknown";

// get pointer to group name
hr = GetGroupName(lpDPInfo->lpDirectPlay3A, dpidGroup, &lpGroupName);
if FAILED(hr)
{
// A failure may mean that the group has been deleted
// since we began processing this message
lpGroupName = NULL;
}

if ((lpGroupName) && (lpGroupName->lpszShortNameA))
lpszGroupName = lpGroupName->lpszShortNameA;
else
lpszGroupName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(lpszDisplayFormat) +
   lstrlen(lpszPlayerName) +
   lstrlen(lpszGroupName) + 1);
// build string
if (lpszStr)
wsprintf(lpszStr, lpszDisplayFormat, lpszPlayerName, lpszGroupName);

// free data we allocated
if (lpPlayerName)
GlobalFreePtr(lpPlayerName);
if (lpGroupName)
GlobalFreePtr(lpGroupName);
        }
break;

case DPSYS_ADDGROUPTOGROUP:
case DPSYS_DELETEGROUPFROMGROUP:
        {
            LPDPMSG_ADDGROUPTOGROUPlpAddMsg = (LPDPMSG_ADDGROUPTOGROUP)lpMsg;
            LPDPMSG_DELETEGROUPFROMGROUPlpDeleteMsg = (LPDPMSG_DELETEGROUPFROMGROUP)lpMsg;
DPIDdpidParentGroup, dpidGroup;
LPSTRlpszParentGroupName, lpszGroupName;
LPDPNAMElpParentGroupName, lpGroupName;
LPSTRlpszDisplayFormat;
            HRESULThr;

if (lpMsg->dwType == DPSYS_ADDGROUPTOGROUP)
{
dpidGroup = lpAddMsg->dpIdGroup;
dpidParentGroup = lpAddMsg->dpIdParentGroup;
lpszDisplayFormat = "Group \"%s\" added to group \"%s\"\r\n";
}
else
{
dpidParentGroup = lpDeleteMsg->dpIdParentGroup;
dpidGroup = lpDeleteMsg->dpIdGroup;
lpszDisplayFormat = "Group \"%s\" removed from group \"%s\"\r\n";
}

// get pointer to player name
hr = GetGroupName(lpDPInfo->lpDirectPlay3A, dpidParentGroup, &lpParentGroupName);
if FAILED(hr)
lpParentGroupName = NULL;

if ((lpParentGroupName) && (lpParentGroupName->lpszShortNameA))
lpszParentGroupName = lpParentGroupName->lpszShortNameA;
else
lpszParentGroupName = "unknown";

// get pointer to group name
hr = GetGroupName(lpDPInfo->lpDirectPlay3A, dpidGroup, &lpGroupName);
if FAILED(hr)
lpGroupName = NULL;

if ((lpGroupName) && (lpGroupName->lpszShortNameA))
lpszGroupName = lpGroupName->lpszShortNameA;
else
lpszGroupName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(lpszDisplayFormat) +
   lstrlen(lpszParentGroupName) +
   lstrlen(lpszGroupName) + 1);
// build string
if (lpszStr)
wsprintf(lpszStr, lpszDisplayFormat, lpszGroupName, lpszParentGroupName );

// free data we allocated
if (lpParentGroupName)
GlobalFreePtr(lpParentGroupName);
if (lpGroupName)
GlobalFreePtr(lpGroupName);
        }
break;

case DPSYS_SESSIONLOST:
        {
            LPDPMSG_SESSIONLOST lp = (LPDPMSG_SESSIONLOST)lpMsg;
LPSTRszDisplayFormat = "Session lost.\r\n";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) + 1);
if (lpszStr == NULL)
break;

// build string
lstrcpy(lpszStr, szDisplayFormat);
        }
break;

case DPSYS_HOST:
        {
            LPDPMSG_HOSTlp = (LPDPMSG_HOST)lpMsg;
LPSTRszDisplayFormat = "You have become the host\r\n";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) + 1);
if (lpszStr == NULL)
break;

// build string
lstrcpy(lpszStr, szDisplayFormat);

// we are now the host
lpDPInfo->bIsHost = TRUE;
        }
break;

case DPSYS_SETPLAYERORGROUPDATA:
        {
            LPDPMSG_SETPLAYERORGROUPDATA lp = (LPDPMSG_SETPLAYERORGROUPDATA)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPNAME:
        {
            LPDPMSG_SETPLAYERORGROUPNAME lp = (LPDPMSG_SETPLAYERORGROUPNAME)lpMsg;
        }
break;

case DPSYS_CHAT:
        {
            LPDPMSG_CHATlp = (LPDPMSG_CHAT)lpMsg;
DWORDdwSize = lstrlen( lp->lpChat->lpszMessageA ) + 12;
//Allow extra room for "{whisper} ", in case this message
//was sent just to me.

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr( GHND, dwSize );

if (lpszStr == NULL)
break;

if (NULL == lp->idToGroup)
{
//This message was sent just to me, not to a whole group
lstrcpy( lpszStr, "{whisper} " );
lstrcat( lpszStr, lp->lpChat->lpszMessageA );
}
else
{
// build string
lstrcpy(lpszStr, lp->lpChat->lpszMessageA);
}
        }
break;

}

// post string to chat window
if (lpszStr)
{
// make sure window is still valid
if (ghMainWnd)
PostMessage(ghMainWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
else
GlobalFreePtr(lpszStr);
}

}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetPlayerName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPDPNAME *lplpName)
{
LPDPNAMElpName = NULL;
DWORDdwNameSize;
HRESULThr;

// get size of player name data
hr = IDirectPlay3_GetPlayerName(lpDirectPlay3A, dpidPlayer, NULL, &dwNameSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpName = (LPDPNAME) GlobalAllocPtr(GHND, dwNameSize);
if (lpName == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get player name data
hr = IDirectPlay3_GetPlayerName(lpDirectPlay3A, dpidPlayer, lpName, &dwNameSize);
if FAILED(hr)
goto FAILURE;

// return pointer to name structure
*lplpName = lpName;

return (DP_OK);

FAILURE:
if (lpName)
GlobalFreePtr(lpName);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetGroupName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidGroup,
 LPDPNAME *lplpName)
{
LPDPNAMElpName = NULL;
DWORDdwNameSize;
HRESULThr;

// get size of player name data
hr = IDirectPlay3_GetGroupName(lpDirectPlay3A, dpidGroup, NULL, &dwNameSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpName = (LPDPNAME) GlobalAllocPtr(GHND, dwNameSize);
if (lpName == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get player name data
hr = IDirectPlay3_GetGroupName(lpDirectPlay3A, dpidGroup, lpName, &dwNameSize);
if FAILED(hr)
goto FAILURE;

// return pointer to name structure
*lplpName = lpName;

return (DP_OK);

FAILURE:
if (lpName)
GlobalFreePtr(lpName);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
void InitializeLobbyGroupWindow(HWND hWnd, LPLOBBYGROUPCONTEXT lpContext)
{
HRESULThr = DPERR_GENERIC;
ENUMCONNSTRUCTenStruct;

if (lpContext->dpidRoom == 0)
SetWindowText(hWnd, "Create Room");
else
SetWindowText(hWnd, "Create Table");

// initialize max players
SetDlgItemInt(hWnd, IDC_MAXPLAYERSEDIT, 0, FALSE);

// put all the DirectPlay applications in a combo box
lpContext->lpDPInfo->lpDirectPlayLobby2A->EnumLocalApplications(EnumApp, hWnd, 0);
SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);

// put all the available connections in a combo box
enStruct.hWnd = hWnd;
enStruct.idCombo = IDC_GROUPCONNECTIONSPCOMBO;

hr = IDirectPlay3_EnumConnections(lpContext->lpDPInfo->lpDirectPlay3A,
&BELLHOP_GUID,
DirectPlayEnumConnectionsCallback,
&enStruct, DPCONNECTION_DIRECTPLAY );

SendDlgItemMessage(hWnd, IDC_GROUPCONNECTIONSPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);
return;

}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT CreateLobbyGroup(HWND hWnd, LPLOBBYGROUPCONTEXT lpContext)
{
CHARszShortName[MAXSTRLEN],
szLongName[MAXSTRLEN],
szPassword[MAXSTRLEN];
DPNAMEdpName;
DWORDdwMaxPlayers;
DPIDdpID;
DPLCONNECTIONdplconn;
DPSESSIONDESC2dpsess;
HRESULThr;
DWORDdwFlags = 0;

// get strings from dialog
GetDlgItemText(hWnd, IDC_SHORTNAMEEDIT, szShortName, MAXSTRLEN);
GetDlgItemText(hWnd, IDC_LONGNAMEEDIT, szLongName, MAXSTRLEN);

if (BST_CHECKED == SendDlgItemMessage( hWnd, IDC_STAGINGAREA, BM_GETCHECK, 0, 0 ))
{
GetDlgItemText(hWnd, IDC_PASSWORDEDIT, szPassword, MAXSTRLEN);
dwMaxPlayers = GetDlgItemInt(hWnd, IDC_MAXPLAYERSEDIT, NULL, FALSE);
dwFlags = DPGROUP_STAGINGAREA;
}

// build a dpname structure
ZeroMemory(&dpName, sizeof(DPNAME));
dpName.dwSize = sizeof(DPNAME);
dpName.lpszShortNameA = szShortName;
dpName.lpszLongNameA = szLongName;

// create a root group
if (lpContext->dpidRoom == 0)
{
hr = IDirectPlay3_CreateGroup(lpContext->lpDPInfo->lpDirectPlay3A,
&dpID, &dpName, NULL, 0, dwFlags);
}

// create the table group
else
{
hr = IDirectPlay3_CreateGroupInGroup(lpContext->lpDPInfo->lpDirectPlay3A,
lpContext->dpidRoom,
&dpID, &dpName,
NULL, 0, dwFlags);
}

if FAILED(hr)
goto FAILURE;

if ( DPGROUP_STAGINGAREA & dwFlags )
{

// Fill out the DPSESSIONDESC2 structure
ZeroMemory( &dpsess, sizeof(DPSESSIONDESC2) );
dpsess.dwSize = sizeof( DPSESSIONDESC2 );
dpsess.dwFlags = DPSESSION_KEEPALIVE | DPSESSION_MIGRATEHOST;

CoCreateGuid(&(dpsess.guidInstance));

GetComboBoxGuid(hWnd, IDC_APPCOMBO, &dpsess.guidApplication);

dpsess.dwMaxPlayers = dwMaxPlayers;
dpsess.dwCurrentPlayers = 0;
dpsess.lpszSessionNameA = dpName.lpszShortNameA;
if (lstrlen(szPassword))
dpsess.lpszPasswordA = szPassword;

// Fill out the DPLCONNECTION structure
ZeroMemory( &dplconn, sizeof(DPLCONNECTION) );
dplconn.dwSize = sizeof( DPLCONNECTION );
dplconn.lpSessionDesc = &dpsess;

GetConnectionSPGuid(hWnd, IDC_GROUPCONNECTIONSPCOMBO, &dplconn.guidSP);

// The rest of the DPLCONNECTION structure gets
// filled in by the lobby

hr = IDirectPlay3_SetGroupConnectionSettings(lpContext->lpDPInfo->lpDirectPlay3A,
 0, dpID, &dplconn );

if FAILED(hr)
goto FAILURE;
}

FAILURE:
return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK LobbyGroupWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPLOBBYGROUPCONTEXTlpContext = (LPLOBBYGROUPCONTEXT) GetWindowLong(hWnd, DWL_USER);

    switch(uMsg)
    {
      case WM_INITDIALOG:

// context passed in lParam
lpContext = (LPLOBBYGROUPCONTEXT) lParam;

            // save the globals with the window
SetWindowLong(hWnd, DWL_USER, (LONG) lpContext);

// Initialize dialog with appropriate information
InitializeLobbyGroupWindow(hWnd, lpContext);
            break;

        case WM_DESTROY:
{
WPARAMindex;
LRESULTlpData;

// destroy the GUID's stored with each app name
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}

// destroy the connection info in the combo box.
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_GROUPCONNECTIONSPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}
}
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDOK:
// save changes they made
CreateLobbyGroup(hWnd, lpContext);
                    // Return success
                    EndDialog(hWnd, TRUE);

                    break;

case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, FALSE);

                    break;

case IDC_STAGINGAREA:
{
int i = SendDlgItemMessage( hWnd, IDC_STAGINGAREA, BM_GETCHECK, 0, 0 );
EnableWindow( GetDlgItem( hWnd, IDC_PASSWORDEDIT ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_APPCOMBO ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_MAXPLAYERSEDIT ), (BST_CHECKED==i));
EnableWindow( GetDlgItem( hWnd, IDC_GROUPCONNECTIONSPCOMBO ), (BST_CHECKED==i));
}
break;

            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoGroupConnectionSettings(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
LOBBYGROUPCONTEXTcontext;

context.lpDPInfo = lpDPInfo;
context.dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

if (DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_CONNECTIONSETTINGSDIALOG), hWnd,
  (DLGPROC) ConnectionSettingsDialogProc, (LPARAM) &context))
{
// something changed
// We could update a status bar here.
}

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoCreateRoom(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
LOBBYGROUPCONTEXTcontext;

context.lpDPInfo = lpDPInfo;
context.dpidRoom = (DPID) 0;

if (DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_LOBBYGROUPDIALOG), hWnd,
  (DLGPROC) LobbyGroupWndProc, (LPARAM) &context))
{
// something changed
// We could update a status bar here.
}

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoDeleteRoom(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
DPIDdpidRoom;
HRESULThr = DP_OK;

dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

// delete the group
hr = IDirectPlay3_DestroyGroup(lpDPInfo->lpDirectPlay3A, dpidRoom);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoDeletePlayerFromGroup(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
DPIDdpidRoom,
dpidPlayer;
HRESULThr = DP_OK;

dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelectionParent();
dpidPlayer = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

// delete the player from the group
hr = IDirectPlay3_DeletePlayerFromGroup(lpDPInfo->lpDirectPlay3A, dpidRoom, dpidPlayer );

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoCreateTable(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
LOBBYGROUPCONTEXTcontext;

context.lpDPInfo = lpDPInfo;
context.dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_LOBBYGROUPDIALOG), hWnd,
  (DLGPROC) LobbyGroupWndProc, (LPARAM) &context);
return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoDeleteTable(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
DPIDdpidRoom,
dpidShortcut;
HRESULThr = DP_OK;

dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelectionParent();
dpidShortcut = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

// delete the player from the group
hr = IDirectPlay3_DeleteGroupFromGroup(lpDPInfo->lpDirectPlay3A, dpidRoom, dpidShortcut );
return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT DoLaunch(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
DPIDdpidRoom;
HRESULThr = DPERR_GENERIC;

dpidRoom = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection();

hr = IDirectPlay3_StartSession(lpDPInfo->lpDirectPlay3A, 0, dpidRoom);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT InitializeLobby(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
DPNAMEname;
DPIDdpID;
HRESULThr = DP_OK;

if (lpDPInfo->bIsHost)
{
// add some groups to start with
ZeroMemory(&name, sizeof(DPNAME));
name.dwSize = sizeof(DPNAME);

name.lpszShortNameA = "Golf Shack";
hr = IDirectPlay3_CreateGroup(lpDPInfo->lpDirectPlay3A, &dpID, &name, NULL, 0, 0);
if FAILED(hr)
goto FAILURE;

name.lpszShortNameA = "Monster Truck Rally";
hr = IDirectPlay3_CreateGroup(lpDPInfo->lpDirectPlay3A, &dpID, &name, NULL, 0, 0);
if FAILED(hr)
goto FAILURE;

name.lpszShortNameA = "Club Hellbender";
hr = IDirectPlay3_CreateGroup(lpDPInfo->lpDirectPlay3A, &dpID, &name, NULL, 0, 0);
if FAILED(hr)
goto FAILURE;
}

FAILURE:
return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
void LogString(LPSTR lpszDisplayFormat, LPSTR lpszDataStr)
{
LPSTRlpszStr;

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(lpszDisplayFormat) +
   lstrlen(lpszDataStr) + 1);
if (lpszStr == NULL)
return;

// build string
wsprintf(lpszStr, lpszDisplayFormat, lpszDataStr);

// post string to chat window
// make sure window is still valid
if (ghMainWnd)
PostMessage(ghMainWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
else
GlobalFreePtr(lpszStr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT SendChatMessage(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
LPSTRlpszChatStr = NULL;
LPSTRlpszStr = NULL;
LONGlStrLen;
HRESULThr;
DPCHATdpc;
DPIDdpidTarget;

// get length of item text
lStrLen = SendDlgItemMessage(hWnd, IDC_SENDEDIT, WM_GETTEXTLENGTH,
(WPARAM) 0, (LPARAM) 0);

// make room for it
lpszChatStr = (LPSTR) GlobalAllocPtr(GHND, lStrLen + 1);
if (lpszChatStr == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get item text
lStrLen = GetDlgItemText(hWnd, IDC_SENDEDIT, lpszChatStr, lStrLen + 1);

// create string to display this text
hr = NewChatString(lpDPInfo->lpDirectPlay3A, lpDPInfo->dpidPlayer, lpszChatStr, &lpszStr);
if FAILED(hr)
goto FAILURE;

// setup the DPCHAT struct
memset(&dpc, 0, sizeof(DPCHAT));
dpc.dwSize = sizeof(DPCHAT);
dpc.lpszMessageA = lpszStr;
BRANCHSTRUCTbs;

dpidTarget = lpDPInfo->lpGroupTree->GetDPIDOfCurrentSelection( &bs );

hr = IDirectPlay3_SendChatMessage(lpDPInfo->lpDirectPlay3A, lpDPInfo->dpidPlayer,
dpidTarget, DPSEND_GUARANTEED, &dpc);

if FAILED(hr)
goto FAILURE;

// display this string
PostMessage(hWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
lpszStr = NULL;// set to NULL so we don't delete it below

FAILURE:
if (lpszChatStr)
GlobalFreePtr(lpszChatStr);

if (lpszStr)
GlobalFreePtr(lpszStr);

SetDlgItemText(hWnd, IDC_SENDEDIT, "");

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT NewChatString(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPSTR lpszMsg, LPSTR *lplpszStr)
{
LPDPNAMElpName = NULL;
LPSTRlpszStr = NULL;
LPSTRlpszPlayerName;
LPSTRszDisplayFormat = "%s>\t%s\r\n";
HRESULThr;

// get name of player
hr = GetPlayerName(lpDirectPlay3A, dpidPlayer, &lpName);
if FAILED(hr)
goto FAILURE;

if (lpName->lpszShortNameA)
lpszPlayerName = lpName->lpszShortNameA;
else
lpszPlayerName = "unknown";

// allocate space for display string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) +
   lstrlen(lpszPlayerName) +
   lstrlen(lpszMsg) + 1);
if (lpszStr == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// build string
wsprintf(lpszStr, szDisplayFormat, lpszPlayerName, lpszMsg);

*lplpszStr = lpszStr;
lpszStr = NULL;

FAILURE:
if (lpszStr)
GlobalFreePtr(lpszStr);

if (lpName)
GlobalFreePtr(lpName);

return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT WaitForRunMsg( DWORD dwAppId, HANDLE hReceiveEvent, LPDWORD lpdwStatus, LPDPLAYINFO lpDPInfo )
{
LPVOID              lpvMsg          = NULL;
    DWORD               dwMsgFlags;
DWORDdwMsgSize= 0;
BOOLbContinue= TRUE;
BOOLbNotFinished= TRUE;
    HRESULT             hr= E_FAIL;

// For this function, we could have spun a seperate thread or integrated
// the app/lobby client messaging into the main receive thread.
// For simplicity sake, the app/lobby client messaging loop is contained below.

// There are seven possible states that get us out of this function:
//1. receive DPLSYS_DPLAYCONNECTSUCCEEDED
//  2. receive DPLSYS_DPLAYCONNECTFAILED
//  3. (Option) timeout waiting for hReceiveEvent
//  4. DPERR_OUTOFMEMORY
//  5. We receive an unexpected player-to-player msg.
//  6. The app terminated.
//  7. We get a system message we don't know about.
//  8. We get an error calling receive

*lpdwStatus = 0;

while (bNotFinished)
{
if (hReceiveEvent)
{
if (WAIT_TIMEOUT == WaitForSingleObject( hReceiveEvent, 20000 ))
{
hr = DPERR_TIMEOUT;
bNotFinished = FALSE;
break;
}
}

do
{
hr =IDirectPlayLobby_ReceiveLobbyMessage(lpDPInfo->lpDirectPlayLobby2A,
0, dwAppId, &dwMsgFlags,
lpvMsg, &dwMsgSize);

switch( hr )
{
case DPERR_BUFFERTOOSMALL:
{
if (lpvMsg)
{
GlobalFreePtr( lpvMsg );
lpvMsg = NULL;
}

lpvMsg = GlobalAllocPtr(GHND, dwMsgSize);
if(!lpvMsg)
{hr = DPERR_OUTOFMEMORY;
goto end;
}
}
break;

case DP_OK:
{
// This better be a system message
if(!(dwMsgFlags & DPLAD_SYSTEM))
{
LPDPMSG_GENERIC lp = (LPDPMSG_GENERIC) lpvMsg;
hr = DPERR_GENERIC;
goto end;
}

if ( lpvMsg )
{
// Switch on the system message type
*lpdwStatus = *(LPDWORD)lpvMsg;

switch(*lpdwStatus)
{
case DPLSYS_APPTERMINATED:
// App shut down
bNotFinished = FALSE;
bContinue = FALSE;
break;

case DPLSYS_CONNECTIONSETTINGSREAD:
break;

case DPLSYS_DPLAYCONNECTSUCCEEDED:
case DPLSYS_DPLAYCONNECTFAILED:
bNotFinished = FALSE;
break;

default:
// RUNDPMSGLOG: Unexpected system message type
hr = DPERR_GENERIC;
bContinue = FALSE;
bNotFinished = FALSE;
break;
}
}
}
break;

case DPERR_NOMESSAGES:
// There are no messages left.
// We need to stop and wait for another
bContinue = FALSE;
break;

default:
goto end;
break;
}

} while( bContinue );
}

end:

// Free resources
    GlobalFreePtr( lpvMsg );

    // Return
    return (DPERR_NOMESSAGES == hr ? DP_OK : hr );
}

///////////////////////////////////////////////////////////////////////////////////////
void HandleStartSession(LPDPLCONNECTION lpConn, LPDPLAYINFO lpDPInfo )
{
DWORDdwAppID;
CHARszStr[MAXSTRLEN];
HRESULThr;
HANDLEhEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
DWORDdwStatus = 0;

if (hEvent)
{
// display name of application to launch
hr = GetLocalAppName(lpDPInfo->lpDirectPlayLobby2A,
&lpConn->lpSessionDesc->guidApplication, szStr);

if FAILED(hr)
lstrcpy(szStr, "unknown");

LogString("Launching \"%s\"...\r\n", szStr);

// Call RunApplication
hr = IDirectPlayLobby_RunApplication(lpDPInfo->lpDirectPlayLobby2A, 0, &dwAppID, lpConn, hEvent );

if FAILED(hr)
{
ErrorBox("Could not launch application because of error 0x%08X", hr);
}
else
{
// Wait for the app to launch
hr = WaitForRunMsg( dwAppID, hEvent, &dwStatus, lpDPInfo );
}

CloseHandle( hEvent );
}

} // HandleStartSession

///////////////////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL EnumApp(LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags)
{
    HWNDhWnd = (HWND)lpContext;
    LRESULTiIndex;
LPGUIDlpGuid;

// store application name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_ADDSTRING, 0, (LPARAM) lpAppInfo->lpszAppNameA);
if (iIndex == LB_ERR)
goto Failure;

// make space for application GUID
lpGuid = (LPGUID) GlobalAllocPtr(GHND, sizeof(GUID));
if (lpGuid == NULL)
goto Failure;

// store pointer to GUID in combo box
*lpGuid = lpAppInfo->guidApplication;
SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

Failure:
    return (TRUE);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetComboBoxGuid(HWND hWnd, LONG iDialogItem, LPGUID lpguidReturn)
{
LONGiIndex;

// get index of selected item
iIndex = SendDlgItemMessage(hWnd, iDialogItem, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get data associated with this item
iIndex = SendDlgItemMessage(hWnd, iDialogItem, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == CB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

// data is a pointer to a guid
*lpguidReturn = *((LPGUID) iIndex);

return (DP_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL GetLocalAppNameCallback(LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags)
{
    LPAPPNAMECONTEXTlpAppNameContext = (LPAPPNAMECONTEXT) lpContext;

if (IsEqualGUID(lpAppInfo->guidApplication, lpAppNameContext->guidApplication))
{
lstrcpy(lpAppNameContext->szAppName, lpAppInfo->lpszAppNameA);
return (FALSE);
}

    return (TRUE);
}

///////////////////////////////////////////////////////////////////////////////////////
HRESULT GetLocalAppName(LPDIRECTPLAYLOBBY lpIDPL,
LPGUID lpguidApplication, LPSTR lpszAppName)
{
HRESULThr;
APPNAMECONTEXTAppNameContext;

ZeroMemory(&AppNameContext, sizeof(APPNAMECONTEXT));
AppNameContext.guidApplication = *lpguidApplication;

// search local apps for matching guid
hr = IDirectPlayLobby_EnumLocalApplications(lpIDPL, GetLocalAppNameCallback, &AppNameContext, 0);
if FAILED(hr)
goto FAILURE;

// no local app found matching this guid
if (lstrlen(AppNameContext.szAppName) == 0)
{
hr = DPERR_GENERIC;
goto FAILURE;
}

// return name
lstrcpy(lpszAppName, AppNameContext.szAppName);

FAILURE:
return (hr);
}

///////////////////////////////////////////////////////////////////////////////////////
void ErrorBox(LPSTR lpszErrorStr, HRESULT hr)
{
charszStr[MAXSTRLEN];

wsprintf(szStr, lpszErrorStr, hr);

MessageBox(NULL, szStr, "Error", MB_OK);
}

///////////////////////////////////////////////////////////////////////////////////////
void OnSize(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
    HDWP hDWP;
    RECT ClientRect;
    int Height;
int xSpacing = lpDPInfo->xSpacing;
int ySpacing = lpDPInfo->ySpacing;
    HWND hKeyTreeWnd;
    HWND hValueListWnd;
HWND hMsgEditWnd;
HWND hSendButtonWnd;
    int x;
    int dx;
RECT SendButtonRect;
int sendbuttonwidth;
int sendbuttonheight;

    if (IsIconic(hWnd))
        return;

    if ((hDWP = BeginDeferWindowPos(4)) != NULL)
    {
        //  Data structure used when calling GetEffectiveClientRect (which takes into
        //  account space taken up by the toolbars/status bars).  First half of the
        //  pair is zero when at the end of the list, second half is the control id.
        int s_EffectiveClientRectData[] = {
            1, 0,                               //  For the menu bar, but is unused
            0, 0                                //  First zero marks end of data
        };

        GetEffectiveClientRect(hWnd, &ClientRect, s_EffectiveClientRectData);
        Height = ClientRect.bottom - ClientRect.top - (ySpacing * 5);

// Resize the tree control
hKeyTreeWnd = GetDlgItem( hWnd, IDT_MESSAGEVIEW );

        DeferWindowPos(hDWP, hKeyTreeWnd, NULL,
xSpacing, ClientRect.top + ySpacing,
lpDPInfo->xPaneSplit, Height,
SWP_NOZORDER | SWP_NOACTIVATE);

        x = lpDPInfo->xPaneSplit + lpDPInfo->xHalfSplitWidth * 2;
        dx = ClientRect.right - ClientRect.left - x - xSpacing;

        // Resize the logging window
hValueListWnd = GetDlgItem( hWnd, IDC_LOGEDIT );

        DeferWindowPos(hDWP, hValueListWnd, NULL,
x, ClientRect.top+ySpacing,
dx, Height,
            SWP_NOZORDER | SWP_NOACTIVATE);

        // move the Send button, its size is constant
hSendButtonWnd = GetDlgItem( hWnd, IDC_SENDBUTTON);

GetWindowRect( hSendButtonWnd, &SendButtonRect );
sendbuttonwidth = SendButtonRect.right - SendButtonRect.left;
sendbuttonheight = SendButtonRect.bottom - SendButtonRect.top;

        DeferWindowPos(hDWP, hSendButtonWnd, NULL,
ClientRect.right - ( sendbuttonwidth + xSpacing ),
ClientRect.bottom - ( sendbuttonheight + ySpacing ),
sendbuttonwidth,
sendbuttonheight,
            SWP_NOZORDER | SWP_NOACTIVATE);

        // Resize and move the message edit control
hMsgEditWnd = GetDlgItem( hWnd, IDC_SENDEDIT );

        DeferWindowPos(hDWP, hMsgEditWnd, NULL,
ClientRect.left + xSpacing,
ClientRect.bottom - ( sendbuttonheight + ySpacing ),
ClientRect.right - ClientRect.left - (sendbuttonwidth + xSpacing * 3),
sendbuttonheight,
            SWP_NOZORDER | SWP_NOACTIVATE);

EndDeferWindowPos(hDWP);
    }
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by bellhop.rc
//
#define IDD_LOBBYDIALOG                 101
#define IDD_CONNECTDIALOG               102
#define IDD_CREATETABLEDIALOG           103
#define IDD_LOBBYGROUPDIALOG            103
#define IDI_BELLHOP                     104
#define IDD_CONNECTIONSETTINGSDIALOG    104
#define IDI_PLAYER                      105
#define IDI_SHORTCUT                    106
#define IDM_MENU                        109
#define IDI_STAGINGAREA                 112
#define IDI_GROUP                       113
#define IDI_OPENDOOR                    114
#define IDI_CLOSEDDOOR                  115
#define IDD_SECURITYCREDENTIALSDIALOG   116
#define IDI_SESSIONINPROGRESS           117
#define IDI_SPECTATOR                   118
#define IDC_LOGEDIT                     1000
#define IDC_SENDEDIT                    1001
#define IDC_SENDBUTTON                  1002
#define IDC_SPCOMBO                     1003
#define IDC_SESSIONLIST                 1004
#define IDC_HOSTBUTTON                  1005
#define IDC_JOINBUTTON                  1006
#define IDC_SPECTATORBUTTON             1007
#define IDC_ROOMLIST                    1012
#define IDC_TABLELIST                   1013
#define IDC_PLAYERLIST                  1014
#define IDC_LEAVEBUTTON                 1016
#define IDC_LEAVETABLEBUTTON            1016
#define IDC_CREATEBUTTON                1017
#define IDC_CREATETABLEBUTTON           1017
#define IDC_DELETEBUTTON                1018
#define IDC_DELETETABLEBUTTON           1018
#define IDC_LAUNCHBUTTON                1019
#define IDC_MAXPLAYERSEDIT              1020
#define IDC_CREATEROOMBUTTON            1020
#define IDC_DELETEROOMBUTTON            1021
#define IDC_TABLENAMEEDIT               1022
#define IDC_SHORTNAMEEDIT               1022
#define IDC_PASSWORDEDIT                1023
#define IDC_APPCOMBO                    1025
#define IDT_MESSAGEVIEW                 1026
#define IDC_TVDELETEBUTTON              1028
#define IDC_REFRESH                     1029
#define IDC_STAGINGAREA                 1030
#define IDC_LONGNAMEEDIT                1031
#define IDC_GROUPCONNECTIONSPCOMBO      1032
#define IDC_SECURENAME                  1033
#define IDC_SECUREPASSWORD              1034
#define IDC_SECUREDOMAIN                1035
#define ID_CREATEGROUPINGROUP           40001
#define ID_SETGROUPNAME                 40002
#define ID_DESTROYGROUP                 40003
#define ID_SETGROUPDATA                 40004
#define ID_GETGROUPDATA                 40005
#define ID_GETGROUPNAME                 40015
#define ID_ROOT_CREATEPLAYER            40016
#define ID_ROOT_CREATEGROUP             40017
#define ID_PLAYER_DESTROYPLAYER         40018
#define ID_PLAYER_SETPLAYERDATA         40019
#define ID_PLAYER_GETPLAYERDATA         40020
#define ID_PLAYER_SETPLAYERNAME         40021
#define ID_PLAYER_GETPLAYERNAME         40022
#define ID_SHORTCUT_DELETEGROUPFROMGROUP 40023
#define ID_SHORTCUT_GETGROUPDATA        40024
#define ID_SHORTCUT_GETGROUPNAME        40025
#define ID_SHORTCUT_SETGROUPDATA        40026
#define ID_SHORTCUT_SETGROUPNAME        40027
#define ID_PLAYERINGROUP_DELETEPLAYERFROMGROUP 40028
#define ID_ROOT_ENUMPLAYERS             40030
#define ID_ROOT_ENUMGROUPS              40031
#define ID_ROOT_ENUMROOTGROUPS          40031
#define ID_GROUP_ENUMPLAYERSINGROUP     40032
#define ID_ROOT_ENUMRECURSIVE           40034
#define ID_GROUP_ENUMGROUPSINGROUP_ONELEVEL 40035
#define ID_GROUP_ENUMGROUPSINGROUP_RECURSIVE 40036
#define ID_GROUP_STARTSESSION           40037
#define ID_GROUP_CONNECTIONSETTINGS     40038
#define ID_ROOT_STANDARDMESSAGE         40039
#define ID_PLAYERINGROUP_SETPLAYERNAME  40040
#define ID_GROUP_SETGROUPNAME           40041

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        119
#define _APS_NEXT_COMMAND_VALUE         40042
#define _APS_NEXT_CONTROL_VALUE         1035
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


BOIDS.CPP

/*
**----------------------------------------------------------------------------
**
**  File:       boids.cpp
**  Purpose:
**  Notes:
**
**Copyright (C) 1995 - 1997 Microsoft Corporation. All Rights Reserved.
**----------------------------------------------------------------------------
*/

/*
**----------------------------------------------------------------------------
** Includes
**----------------------------------------------------------------------------
*/

#include <math.h>
#include <time.h>

#include "D3DScene.h"
#include "Debug.h"
#include "D3DWin.h"
#include "d3dutils.h"
#include "d3dtex.h"
#include "boids.h"

/*
**----------------------------------------------------------------------------
** Defines
**----------------------------------------------------------------------------
*/

#define NUM_BOIDS13

// ground pattern
D3DVECTOR       grid_color(0.0f, 0.3f, 0.5f);
D3DLVERTEXpattern1[24];
D3DLVERTEXpattern2[8];
WORDpat1_indices[25] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0 };
WORDpat2_indices[9] = { 0, 1, 2, 3, 4, 5, 6, 7, 0 };

#define MESH_SIZE8
#define SPHERE_VERTICES(2+MESH_SIZE*MESH_SIZE*2)
#define SPHERE_INDICES((MESH_SIZE*4 + MESH_SIZE*4*(MESH_SIZE-1))*3)

/*
**----------------------------------------------------------------------------
** Local Variables
**----------------------------------------------------------------------------
*/

D3DVERTEXsphere[SPHERE_VERTICES];
WORDsphere_indices[SPHERE_INDICES];

Flockflock;

D3DVERTEXboid_vertices[16];
WORDboid_indices[30];

D3DMATRIXproj, view, world;

Light*lpLight1,
*lpLight2;

Material*lpBackgroundMat,
*lpGridMat,
*lpSphereMat,
*lpBoidMat;

D3DTextureSphereTex;

/*
**----------------------------------------------------------------------------
** Function definitions
**----------------------------------------------------------------------------
*/

/*
**----------------------------------------------------------------------------
** Name:        DrawBoid
** Purpose:
**----------------------------------------------------------------------------
*/

HRESULT
Drawboid(LPDIRECT3DDEVICE2 lpDev)
{
HRESULT hResult;

        hResult = lpDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, D3DVT_VERTEX, (LPVOID)boid_vertices, 16, boid_indices, 30, D3DDP_WAIT);

return hResult;
} // End DrawBoid

/*
**----------------------------------------------------------------------------
** Name:        DrawSphere
** Purpose:
**----------------------------------------------------------------------------
*/

HRESULT
DrawSphere(LPDIRECT3DDEVICE2 lpDev)
{
HRESULT hResult;

        hResult = lpDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, D3DVT_VERTEX, (LPVOID)sphere, SPHERE_VERTICES, sphere_indices, SPHERE_INDICES, D3DDP_WAIT);
if (hResult != D3D_OK)
return hResult;

return D3D_OK;
} // End DrawSphere

HRESULT
DrawPattern(LPDIRECT3DDEVICE2 lpDev)
{
HRESULT hResult;

for (int dx= -2; dx<3; dx++) {
for (int dz= -2; dz<3; dz++) {
D3DVECTORoffset(dx*80.0f, 0.0f, dz*80.0f);

world = TranslateMatrix(offset);
hResult = lpDev->SetTransform(D3DTRANSFORMSTATE_WORLD, &world);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}
                        hResult = lpDev->DrawIndexedPrimitive(D3DPT_LINESTRIP, D3DVT_LVERTEX, (LPVOID)pattern1, 24, pat1_indices, 25, D3DDP_WAIT);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}
                        hResult = lpDev->DrawIndexedPrimitive(D3DPT_LINESTRIP, D3DVT_LVERTEX, (LPVOID)pattern2, 8, pat2_indices, 9, D3DDP_WAIT);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}
}
}

return D3D_OK;
}// end of DrawPattern()

/*
**----------------------------------------------------------------------------
** D3DScene Methods
**----------------------------------------------------------------------------
*/

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::D3DScene
** Purpose:Default Constructor
**----------------------------------------------------------------------------
*/

D3DScene::D3DScene(void)
{
lpd3dWindow = NULL;
} // End D3DScene::D3DScene

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::~D3DScene
** Purpose:Default Destructor
**----------------------------------------------------------------------------
*/

D3DScene::~D3DScene(void)
{
Fini();
lpd3dWindow = NULL;
} // End D3DScene::~D3DScene

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::Init
** Purpose:Do all static intialization here
** Notes:This means all Scene data that isn't dependent on the
**D3D interface, viewport, or D3D device in some manner
**----------------------------------------------------------------------------
*/

HRESULT
D3DScene::Init(LPD3DWindow lpd3dWin)
{
HRESULT hResult;
int i, j;

// Check Parameters
if (! lpd3dWin){
// Need a valid D3D Window object
hResult = APPERR_INVALIDPARAMS;
REPORTERR(hResult);
return hResult;
}

// Save assocation with D3D Window
lpd3dWindow = lpd3dWin;

// generate the boid data

// top
boid_vertices[ 0] = D3DVERTEX(D3DVECTOR(0.0f, 0.0f, 10.0f), Normalize(D3DVECTOR(0.2f, 1.0f, 0.0f)), 0.0f, 0.5f);
boid_vertices[ 1] = D3DVERTEX(D3DVECTOR(10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(0.1f, 1.0f, 0.0f)), 0.5f, 1.0f);
boid_vertices[ 2] = D3DVERTEX(D3DVECTOR(3.0f, 3.0f, -7.0f), Normalize(D3DVECTOR(0.0f, 1.0f, 0.0f)), 0.425f, 0.575f);
boid_vertices[ 3] = D3DVERTEX(D3DVECTOR(-3.0f, 3.0f, -7.0f), Normalize(D3DVECTOR(-0.1f, 1.0f, 0.0f)), 0.425f, 0.425f);
boid_vertices[ 4] = D3DVERTEX(D3DVECTOR(-10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(-0.2f, 1.0f, 0.0f)), 0.5f, 0.0f);

//bottom
boid_vertices[ 5] = D3DVERTEX(D3DVECTOR(0.0f, 0.0f, 10.0f), Normalize(D3DVECTOR(0.2f, -1.0f, 0.0f)), 1.0f, 0.5f);
boid_vertices[ 6] = D3DVERTEX(D3DVECTOR(10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(0.1f, -1.0f, 0.0f)), 0.5f, 1.0f);
boid_vertices[ 7] = D3DVERTEX(D3DVECTOR(3.0f, -3.0f, -7.0f), Normalize(D3DVECTOR(0.0f, -1.0f, 0.0f)), 0.575f, 0.575f);
boid_vertices[ 8] = D3DVERTEX(D3DVECTOR(-3.0f, -3.0f, -7.0f), Normalize(D3DVECTOR(-0.1f, -1.0f, 0.0f)), 0.575f, 0.425f);
boid_vertices[ 9] = D3DVERTEX(D3DVECTOR(-10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(-0.2f, -1.0f, 0.0f)), 0.5f, 0.0f);

// rear
boid_vertices[10] = D3DVERTEX(D3DVECTOR(10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(-0.4f, 0.0f, -1.0f)), 0.5f, 1.0f);
boid_vertices[11] = D3DVERTEX(D3DVECTOR(3.0f, 3.0f, -7.0f), Normalize(D3DVECTOR(-0.2f, 0.0f, -1.0f)), 0.425f, 0.575f);
boid_vertices[12] = D3DVERTEX(D3DVECTOR(-3.0f, 3.0f, -7.0f), Normalize(D3DVECTOR(0.2f, 0.0f, -1.0f)), 0.425f, 0.425f);
boid_vertices[13] = D3DVERTEX(D3DVECTOR(-10.0f, 0.0f, -10.0f), Normalize(D3DVECTOR(0.4f, 0.0f, -1.0f)), 0.5f, 0.0f);
boid_vertices[14] = D3DVERTEX(D3DVECTOR(-3.0f, -3.0f, -7.0f), Normalize(D3DVECTOR(0.2f, 0.0f, -1.0f)), 0.575f, 0.425f);
boid_vertices[15] = D3DVERTEX(D3DVECTOR(3.0f, -3.0f, -7.0f), Normalize(D3DVECTOR(-0.2f, 0.0f, -1.0f)), 0.575f, 0.575f);

// top
boid_indices[ 0] = 0;
boid_indices[ 1] = 1;
boid_indices[ 2] = 2;
boid_indices[ 3] = 0;
boid_indices[ 4] = 2;
boid_indices[ 5] = 3;
boid_indices[ 6] = 0;
boid_indices[ 7] = 3;
boid_indices[ 8] = 4;

// bottom
boid_indices[ 9] = 5;
boid_indices[10] = 7;
boid_indices[11] = 6;
boid_indices[12] = 5;
boid_indices[13] = 8;
boid_indices[14] = 7;
boid_indices[15] = 5;
boid_indices[16] = 9;
boid_indices[17] = 8;

// rear
boid_indices[18] = 10;
boid_indices[19] = 15;
boid_indices[20] = 11;
boid_indices[21] = 11;
boid_indices[22] = 15;
boid_indices[23] = 12;
boid_indices[24] = 12;
boid_indices[25] = 15;
boid_indices[26] = 14;
boid_indices[27] = 12;
boid_indices[28] = 14;
boid_indices[29] = 13;

// scale the boid to be unit length
for (i=0; i<16; i++) {
boid_vertices[i].x /= 20.0f;
boid_vertices[i].y /= 20.0f;
boid_vertices[i].z /= 20.0f;
}

// seed the random number generator
srand(time(NULL));

// allocate the flock
if (!(flock.boids = (Boid *)malloc(NUM_BOIDS * sizeof(Boid)))) {
return FALSE;
}
if (!(flock.dist = (float **)malloc(NUM_BOIDS * sizeof(float *)))) {
return FALSE;
}

flock.num_boids = NUM_BOIDS;
flock.goal = D3DVECTOR(0.0f, 0.0f, 0.0f);

for (i=0; i<flock.num_boids; i++) {
flock.boids[i].loc = D3DVECTOR(100.0f*(rnd()-rnd()), 10.0f*rnd(), 100.0f*(rnd()-rnd()));
flock.boids[i].dir = Normalize(D3DVECTOR(rnd()-rnd(), rnd()-rnd(), rnd()-rnd()));
flock.boids[i].yaw = flock.boids[i].pitch = flock.boids[i].roll = flock.boids[i].dyaw = 0.0f;
flock.boids[i].speed = 0.1f;
flock.boids[i].color = D3DVECTOR(rnd(), rnd(), rnd());
flock.boids[i].color -= D3DVECTOR(Min(flock.boids[i].color));
flock.boids[i].color /= Max(flock.boids[i].color);
if (!(flock.dist[i] = (float *)malloc(NUM_BOIDS * sizeof(float)))) {
return FALSE;
}
}

flock.num_obs = 4;
if (!(flock.obs = (Obstacle *)malloc(flock.num_obs * sizeof(Obstacle)))) {
return FALSE;
}
    flock.obs[0].loc = D3DVECTOR(100.0f, 10.0f, 0.0f);
    flock.obs[1].loc = D3DVECTOR(-100.0f, 10.0f, 0.0f);
    flock.obs[2].loc = D3DVECTOR(0.0f, 10.0f, 100.0f);
    flock.obs[3].loc = D3DVECTOR(0.0f, 10.0f, -100.0f);
flock.obs[0].radius = 3.0f;
flock.obs[1].radius = 3.0f;
flock.obs[2].radius = 3.0f;
flock.obs[3].radius = 3.0f;

D3DCOLORdiffuse = D3DRGB(grid_color[0], grid_color[1], grid_color[2]),
specular = D3DRGB(0.0, 0.0, 0.0);

pattern1[ 0] = D3DLVERTEX(D3DVECTOR(-25.0f, 0.0f, 35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 1] = D3DLVERTEX(D3DVECTOR(-15.0f, 0.0f, 35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 2] = D3DLVERTEX(D3DVECTOR(-5.0f, 0.0f, 25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 3] = D3DLVERTEX(D3DVECTOR(5.0f, 0.0f, 25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 4] = D3DLVERTEX(D3DVECTOR(15.0f, 0.0f, 35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 5] = D3DLVERTEX(D3DVECTOR(25.0f, 0.0f, 35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 6] = D3DLVERTEX(D3DVECTOR(35.0f, 0.0f, 25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 7] = D3DLVERTEX(D3DVECTOR(35.0f, 0.0f, 15.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 8] = D3DLVERTEX(D3DVECTOR(25.0f, 0.0f, 5.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[ 9] = D3DLVERTEX(D3DVECTOR(25.0f, 0.0f, -5.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[10] = D3DLVERTEX(D3DVECTOR(35.0f, 0.0f, -15.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[11] = D3DLVERTEX(D3DVECTOR(35.0f, 0.0f, -25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[12] = D3DLVERTEX(D3DVECTOR(25.0f, 0.0f, -35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[13] = D3DLVERTEX(D3DVECTOR(15.0f, 0.0f,-35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[14] = D3DLVERTEX(D3DVECTOR(5.0f, 0.0f, -25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[15] = D3DLVERTEX(D3DVECTOR(-5.0f, 0.0f, -25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[16] = D3DLVERTEX(D3DVECTOR(-15.0f, 0.0f,-35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[17] = D3DLVERTEX(D3DVECTOR(-25.0f, 0.0f,-35.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[18] = D3DLVERTEX(D3DVECTOR(-35.0f, 0.0f, -25.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[19] = D3DLVERTEX(D3DVECTOR(-35.0f, 0.0f, -15.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[20] = D3DLVERTEX(D3DVECTOR(-25.0f, 0.0f, -5.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[21] = D3DLVERTEX(D3DVECTOR(-25.0f, 0.0f, 5.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[22] = D3DLVERTEX(D3DVECTOR(-35.0f, 0.0f, 15.0f), diffuse, specular, 0.0f, 0.0f);
pattern1[23] = D3DLVERTEX(D3DVECTOR(-35.0f, 0.0f, 25.0f), diffuse, specular, 0.0f, 0.0f);

pattern2[ 0] = D3DLVERTEX(D3DVECTOR(-5.0f, 0.0f, 15.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 1] = D3DLVERTEX(D3DVECTOR(5.0f, 0.0f, 15.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 2] = D3DLVERTEX(D3DVECTOR(15.0f, 0.0f, 5.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 3] = D3DLVERTEX(D3DVECTOR(15.0f, 0.0f, -5.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 4] = D3DLVERTEX(D3DVECTOR(5.0f, 0.0f, -15.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 5] = D3DLVERTEX(D3DVECTOR(-5.0f, 0.0f, -15.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 6] = D3DLVERTEX(D3DVECTOR(-15.0f, 0.0f, -5.0f), diffuse, specular, 0.0f, 0.0f);
pattern2[ 7] = D3DLVERTEX(D3DVECTOR(-15.0f, 0.0f, 5.0f), diffuse, specular, 0.0f, 0.0f);

float   dj = pi/(MESH_SIZE+1.0f);
floatdi = pi/MESH_SIZE;

// generate the sphere data, note the random texture coords

// vertices 0 and 1 are the north and south poles
sphere[0] = D3DVERTEX(D3DVECTOR(0.0f, 1.0f, 0.0f), Normalize(D3DVECTOR(0.0f, 1.0f, 0.0f)), 0.0f, 0.0f);
sphere[1] = D3DVERTEX(D3DVECTOR(0.0f, -1.0f, 0.0f), Normalize(D3DVECTOR(0.0f, -1.0f, 0.0f)), 1.0f, 1.0f);

for (j=0; j<MESH_SIZE; j++) {
for (i=0; i<MESH_SIZE*2; i++) {
D3DVECTORp;
floatu, v;

p.y = (float) cos((j+1) * dj);
p.x = (float) sin(i * di) * (float) sin((j+1) * dj);
p.z = (float) cos(i * di) * (float) sin((j+1) * dj);
u = (float)i/MESH_SIZE;
if (u>1.0f)
u -= 1.0f;
u = 1.0f - u;// flip so texture is not mirrored
v = (float)j/MESH_SIZE;
sphere[2+i+j*MESH_SIZE*2] = D3DVERTEX(p, p, u, v);
}
}

// now generate the traingle indices
// strip around north pole first
for (i=0; i<MESH_SIZE*2; i++) {
sphere_indices[3*i] = 0;
sphere_indices[3*i+1] = i+2;
sphere_indices[3*i+2] = i+3;
if (i==MESH_SIZE*2-1)
sphere_indices[3*i+2] = 2;
}

// now all the middle strips
intv;// vertex offset
int ind;// indices offset
for (j=0; j<MESH_SIZE-1; j++) {
v = 2+j*MESH_SIZE*2;
ind = 3*MESH_SIZE*2 + j*6*MESH_SIZE*2;
for (i=0; i<MESH_SIZE*2; i++) {
sphere_indices[6*i+ind] = v+i;
sphere_indices[6*i+2+ind] = v+i+1;
sphere_indices[6*i+1+ind] = v+i+MESH_SIZE*2;

sphere_indices[6*i+ind+3] = v+i+MESH_SIZE*2;
sphere_indices[6*i+2+ind+3] = v+i+1;
sphere_indices[6*i+1+ind+3] = v+i+MESH_SIZE*2+1;
if (i==MESH_SIZE*2-1) {
sphere_indices[6*i+2+ind] = v+i+1-2*MESH_SIZE;
sphere_indices[6*i+2+ind+3] = v+i+1-2*MESH_SIZE;
sphere_indices[6*i+1+ind+3] = v+i+MESH_SIZE*2+1-2*MESH_SIZE;
}
}
}

// finally strip around south pole
v = SPHERE_VERTICES-MESH_SIZE*2;
ind = SPHERE_INDICES-3*MESH_SIZE*2;
for (i=0; i<MESH_SIZE*2; i++) {
sphere_indices[3*i+ind] = 1;
sphere_indices[3*i+1+ind] = v+i+1;
sphere_indices[3*i+2+ind] = v+i;
if (i==MESH_SIZE*2-1)
sphere_indices[3*i+1+ind] = v;
}

// Success
    return D3D_OK;
} // End D3DScene::Init

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::Fini
**  Purpose:Cleanup scene objects
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::Fini(void)
{
Detach();
lpd3dWindow = NULL;

// Success
return D3D_OK;
} // End D3DScene::Fini

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::Attach
** Purpose:Attaching to a new D3DWindow object
** Notes:Need to create and attach all Scene objects dependent upon
**the D3D interface, viewport, and D3D device here.
**For Example:  Textures, Materials, Lights, etc.
**----------------------------------------------------------------------------
*/

HRESULT D3DScene::Attach(void)
{
HRESULT hResult;

// Check Initialization
if ((! lpd3dWindow) || (! lpd3dWindow->isValid())){
// Error, not properly initialized
hResult = APPERR_NOTINITIALIZED;
REPORTERR(hResult);
return hResult;
}

hResult = AttachViewport();
if (hResult != D3D_OK) {
return hResult;
}

// Success
return D3D_OK;
} // End D3DScene::Attach

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::Detach
**  Purpose:Cleanup all scene objects dependent upon the
**D3D Interface, viewport, or D3D device
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::Detach(void)
{
// Cleanup Viewport
DetachViewport();

// Success
return D3D_OK;
} // End D3DScene::Fini

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::Render
** Purpose:
**----------------------------------------------------------------------------
*/

HRESULT D3DScene::Render(void)
{
LPDIRECT3DDEVICE2lpDev;
LPDIRECT3DVIEWPORT2 lpView;
RECTrSrc;
LPD3DRECTlpExtent = NULL;
inti;
HRESULThResult;
D3DCLIPSTATUSstatus;
D3DRECTd3dRect;
D3DVECTORoffset;
static D3DVECTORfrom(0.0f, 30.0f, 100.0f);
static D3DVECTORat(0.0f, 0.0f, 50.0f);
static D3DVECTORup(0.0f, 1.0f, 0.0f);
static floattic = -200.0f * rnd();

// Check Initialization
if ((! lpd3dWindow) || (! lpd3dWindow->isValid ())){
// Error, not properly initialized
hResult = APPERR_NOTINITIALIZED;
REPORTERR(hResult);
return hResult;
}

lpDev= lpd3dWindow->lpd3dDevice;
lpView  = lpd3dWindow->lpd3dViewport;
lpd3dWindow->GetSurfaceRect(rSrc);

// Double Check
#ifdef DEBUG
if ((! lpDev) || (! lpView))
{
// Error, not initialized properly
hResult = APPERR_NOTINITIALIZED;
REPORTERR(hResult);
return hResult;
}
#endif

//
    // Clear both back and z-buffer.
    //
    // NOTE: Its safe to specify the z-buffer clear flag even if we
    // don't have an attached z-buffer. Direct3D will simply discard
    // the flag if no z-buffer is being used.
    //
    // NOTE: For maximum efficiency we only want to clear those
    // regions of the device surface and z-buffer which we actually
    // rendered to in the last frame. This is the purpose of the
    // array of rectangles and count passed to this function. It is
    // possible to query Direct3D for the regions of the device
    // surface that were rendered to by that execute. The application
    // can then accumulate those rectangles and clear only those
    // regions. However this is a very simple sample and so, for
    // simplicity, we will just clear the entire device surface and
    // z-buffer. Probably not something you wan't to do in a real
    // application.
    ///
d3dRect.lX1 = rSrc.left;
d3dRect.lX2 = rSrc.right;
d3dRect.lY1 = rSrc.top;
d3dRect.lY2 = rSrc.bottom;
hResult = lpView->Clear(1UL, &d3dRect,
 D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}

if (lpExtent) {
// Calculate exclude region
status.dwFlags = D3DCLIPSTATUS_EXTENTS2;
status.dwStatus = 0;
status.minx = (float)lpExtent->x1;
status.maxx = (float)lpExtent->x2;
status.miny = (float)lpExtent->y1;
status.maxy = (float)lpExtent->y2;
status.minz = 0.0f;
status.maxz = 0.0f;

hResult = lpDev->SetClipStatus(&status);
if (hResult != D3D_OK) {
REPORTERR(hResult);
goto lblCLEANUP;
}
}

// Begin Scene
// Note:  This is complicated by the need to
//  check for lost surfaces and restore
hResult = lpDev->BeginScene();
if (hResult != D3D_OK) {
while (hResult == DDERR_SURFACELOST) {
// Restore surface
while (hResult == DDERR_SURFACELOST) {
hResult = lpd3dWindow->Restore();
}

// Try BeginScene again
hResult = lpDev->BeginScene();
}

if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}
}

    // Turn off specular highlights
        hResult = lpDev->SetRenderState(D3DRENDERSTATE_SPECULARENABLE, FALSE);
if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

    // Turn on Z-buffering
hResult = lpDev->SetRenderState(D3DRENDERSTATE_ZENABLE, 1);
if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

// null out the texture handle
    hResult = lpDev->SetRenderState(D3DRENDERSTATE_TEXTUREHANDLE, 0);
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

// turn on dithering
    hResult = lpDev->SetRenderState(D3DRENDERSTATE_DITHERENABLE, TRUE);
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

// turn on some ambient light
    hResult = lpDev->SetLightState(D3DLIGHTSTATE_AMBIENT, RGBA_MAKE(10, 10, 10, 10));
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

// set the view and projection matrices

tic += 0.01f;

view = ViewMatrix(from, at, up);
proj = ProjectionMatrix(1.0f, 500.0f, pi/4.0f);

hResult = lpDev->SetTransform(D3DTRANSFORMSTATE_VIEW, &view);
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

hResult = lpDev->SetTransform(D3DTRANSFORMSTATE_PROJECTION, &proj);
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

// draw ground grid
lpGridMat->SetAsCurrent(lpDev);
hResult = DrawPattern(lpDev);
    if (hResult != D3D_OK) {
        REPORTERR(hResult);
goto lblCLEANUP;
}

UpdateFlock(flock);

at = D3DVECTOR(0.0f);
// draw the boids
for (i=0; i<flock.num_boids; i++) {
D3DVECTORstep;

// build the world matrix for the boid
world = MatrixMult(MatrixMult(RotateYMatrix(flock.boids[i].yaw), RotateXMatrix(flock.boids[i].pitch)), RotateZMatrix(flock.boids[i].roll));

// first translate into place, then set orientation, then scale (if needed)
world = TranslateMatrix(flock.boids[i].loc);
world = MatrixMult(world, MatrixMult(MatrixMult(RotateYMatrix(flock.boids[i].yaw),
RotateXMatrix(flock.boids[i].pitch)), RotateZMatrix(flock.boids[i].roll)));

// apply the world matrix
hResult = lpDev->SetTransform(D3DTRANSFORMSTATE_WORLD, &world);
if (hResult != D3D_OK) {
REPORTERR(hResult);
goto lblCLEANUP;
}

// display the boid
lpBoidMat->SetDiffuse(flock.boids[i].color);
lpBoidMat->SetAsCurrent(lpDev);
hResult = Drawboid(lpDev);
if (hResult != D3D_OK) {
REPORTERR(hResult);
goto lblCLEANUP;
}

flock.boids[i].dir[0] = world(2, 0);
flock.boids[i].dir[1] = world(2, 1);
flock.boids[i].dir[2] = world(2, 2);

flock.boids[i].loc += flock.boids[i].dir * flock.boids[i].speed;

at += flock.boids[i].loc;
}
// find the center of the flock and look there for the next frame
at /= (float)(flock.num_boids);
from = at + D3DVECTOR(20.0f * (float)sin (tic* 0.223f),
                   16.0f + 15.0f * (float)sin (tic * 0.33f),
   20.0f * (float)cos (tic * 0.31f));

flock.goal = D3DVECTOR(105.0f * (float)sin (tic * 0.1f),
10.0f,
105.0f * (float)cos (tic * 0.1f));

// Finally, draw obstacles last since they're transparent.
// Set renderstates to support blending such that the texture
// color is added over the background color.  Also turn Z-write
// off so when two spheres line up you can always see them both.
    if (lpDev->SetRenderState(D3DRENDERSTATE_BLENDENABLE, TRUE) != D3D_OK)
        return FALSE;
   if (lpDev->SetRenderState(D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCCOLOR) != D3D_OK)
        return FALSE;
    if (lpDev->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE) != D3D_OK)
return FALSE;
    if (lpDev->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, FALSE) != D3D_OK)
return FALSE;
// make sure the texture wraps correctly
    if (lpDev->SetRenderState(D3DRENDERSTATE_WRAPU, TRUE) != D3D_OK)
return FALSE;

lpSphereMat->SetAsCurrent(lpDev);
for (i=0; i<flock.num_obs; i++) {
world = MatrixMult(TranslateMatrix(flock.obs[i].loc), MatrixMult(RotateYMatrix(-tic*pi), ScaleMatrix (flock.obs[i].radius)));

hResult = lpDev->SetTransform(D3DTRANSFORMSTATE_WORLD, &world);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}

hResult = DrawSphere(lpDev);
if (hResult != D3D_OK) {
REPORTERR(hResult);
goto lblCLEANUP;
}
}

// set renderstates back to default
    if (lpDev->SetRenderState(D3DRENDERSTATE_BLENDENABLE, FALSE) != D3D_OK)
        return FALSE;
    if (lpDev->SetRenderState(D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE) != D3D_OK)
        return FALSE;
    if (lpDev->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_ZERO) != D3D_OK)
return FALSE;
    if (lpDev->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, TRUE) != D3D_OK)
return FALSE;

lblCLEANUP:
// End Scene
// Note:  This is complicated by the need to restore lost surfaces
    hResult = lpDev->EndScene();
if (hResult != D3D_OK) {
while (hResult == DDERR_SURFACELOST) {
// Restore surface
while (hResult == DDERR_SURFACELOST) {
hResult = lpd3dWindow->Restore();
}

// Try EndScene again
hResult = lpDev->EndScene();
}

if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}
}

if (lpExtent) {
hResult = lpDev->GetClipStatus(&status);
if (hResult != D3D_OK) {
REPORTERR(hResult);
return hResult;
}

if (status.dwFlags & D3DCLIPSTATUS_EXTENTS2) {
lpExtent->x1 = (long) floor((double)status.minx);
lpExtent->x2 = (long) ceil((double)status.maxx);
lpExtent->y1 = (long) floor((double)status.miny);
lpExtent->y2 = (long) ceil((double)status.maxy);
}
}

return hResult;
} // End D3DScene::Render

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::Restore
** Purpose:     Restore any scene specific surfaces that might have been
**lost on a DDERR_LOSTSURFACE message
**----------------------------------------------------------------------------
*/

HRESULT D3DScene::Restore(void)
{
// Nothing to do for now

// Success
return D3D_OK;
} // End D3DScene::Restore

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::AttachViewport
** Purpose:
**----------------------------------------------------------------------------
*/

HRESULT
D3DScene::AttachViewport(void)
{
LPDIRECT3D2lpD3D;
LPDIRECT3DDEVICE2lpDev;
LPDIRECT3DVIEWPORT2   lpView;

// Check Initialization
if ((! lpd3dWindow) || (! lpd3dWindow->isValid())) {
// Error,
REPORTERR(DDERR_GENERIC);
return DDERR_GENERIC;
}

lpD3D  = lpd3dWindow->lpD3D;
lpDev  = lpd3dWindow->lpd3dDevice;
lpView = lpd3dWindow->lpd3dViewport;

if ((! lpD3D) || (! lpDev) || (! lpView)) {
REPORTERR(DDERR_GENERIC);
return DDERR_GENERIC;
}

// Create and set up the background material
lpBackgroundMat = new Material(lpD3D, lpDev);
lpBackgroundMat->SetDiffuse(D3DVECTOR(0.0f, 0.05f, 0.1f));
lpBackgroundMat->SetAsBackground(lpView);

// Create and set up the grid material, since we're using LVertices we want
// to make sure that ramp mode can actually get the right colors so we set
// the emissive value to the color we want and choose a small ramp size
lpGridMat = new Material(lpD3D, lpDev);
lpGridMat->SetEmissive(grid_color);
lpGridMat->SetRampSize(2);

// Create and set up the sphere material
lpSphereMat = new Material(lpD3D, lpDev);
lpSphereMat->SetDiffuse(D3DVECTOR(1.0f));

// Create the sphere texture and attatch it to the material
SphereTex.Load(lpDev, "DX5_logo");
lpSphereMat->SetTextureHandle(SphereTex.GetHandle());

// Create and set up the boid material
// note that we'll just change the color for each boid we render
lpBoidMat = new Material(lpD3D, lpDev);
lpBoidMat->SetDiffuse(D3DVECTOR(1.0f));

// set up transform matrices
D3DVECTORfrom(0.0f, 0.0f, -100.0f);
D3DVECTORat(0.0f, 0.0f, 0.0f);
D3DVECTORup(0.0f, 1.0f, 0.0f);

view = ViewMatrix(from, at, up);
proj = ProjectionMatrix(1.0f, 400.0f, pi/4.0f);
world = IdentityMatrix();

// create 2 lights
D3DVECTORcolor(1.0f, 1.0f, 1.0f);
D3DVECTORdirection(-0.5f, -1.0f, -0.3f);

lpLight1 = new DirectionalLight(lpD3D, color, Normalize(direction));
if (lpLight1) {
lpLight1->AddToViewport(lpView);
}

lpLight2 = new DirectionalLight(lpD3D, color/2.0f, -Normalize(direction));
if (lpLight2) {
lpLight2->AddToViewport(lpView);
}

// Success
    return D3D_OK;
} // End D3DScene::AttachViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::DetachViewport
**  Purpose:Cleanup Viewport
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::DetachViewport(void)
{
LPDIRECT3DVIEWPORT2 lpViewport = NULL;

if (lpd3dWindow) {
lpViewport = lpd3dWindow->GetViewport();
}

// Cleanup lights
if (lpLight1) {
if (lpViewport) {
lpLight1->RemoveFromViewport(lpViewport);
}

    delete lpLight1;
lpLight1 = NULL;
}

if (lpLight2) {
if (lpViewport) {
lpLight2->RemoveFromViewport(lpViewport);
}

delete lpLight2;
lpLight2 = NULL;
}

// Cleanup Materials
delete lpBackgroundMat;
delete lpGridMat;
delete lpSphereMat;
delete lpBoidMat;

lpBackgroundMat = NULL;
lpGridMat = NULL;
lpSphereMat = NULL;
lpBoidMat = NULL;

SphereTex.Release();

// Success
return D3D_OK;
} // End D3DScene::DetachViewport

/*
**----------------------------------------------------------------------------
** End of File
**----------------------------------------------------------------------------
*/


BOIDS.H

#ifndef BOIDS_H
#define BOIDS_H
/*
**-----------------------------------------------------------------------------
**  File:       Boids.h
**  Purpose:    Sample showing DrawPrimitive functionality
**
**Copyright (C) 1995 - 1997 Microsoft Corporation. All Rights Reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**Typedefs
**-----------------------------------------------------------------------------
*/

typedef struct t_boid {
D3DVECTORloc;
D3DVECTORdir;// cur direction
D3DVECTORdelta_pos;// change in position from flock centering
D3DVECTORdelta_dir;// change in direction
intdelta_cnt;// number of boids that influence this delta_dir
floatspeed;
floatyaw, pitch, roll, dyaw;
D3DVECTORcolor;
} Boid;

typedef struct t_obstacle {
D3DVECTORloc;
floatradius;
} Obstacle;

typedef struct t_flock {
intnum_boids;
Boid*boids;
intnum_obs;
Obstacle*obs;
float**dist;// 2-d array of boid distances, yuk what a waste
D3DVECTORgoal;
} Flock;

/*
**-----------------------------------------------------------------------------
**Function Prototypes
**-----------------------------------------------------------------------------
*/

// Boids Functions
void UpdateFlock (Flock flock);

/*
**----------------------------------------------------------------------------
** End of File
**----------------------------------------------------------------------------
*/
#endif // BOIDS_H


COMMON.H

#ifndef COMMON_H
#define COMMON_H
/*
**-----------------------------------------------------------------------------
**  File:       Common.h
**  Purpose:    Common definitions and include files
**
**  Microsoft Copyright (c) 1995 - 1997, All rights reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <tchar.h>

#include <ddraw.h>

// Note:  Must Define D3D_OVERLOADS to get C++ version of D3DMATRIX
#define D3D_OVERLOADS
#include <d3d.h>
#include <d3dtypes.h>

#include "resource.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/
#ifndef FAR
    #define FAR
#endif

#ifndef NEAR
    #define NEAR
#endif

#ifndef CONST
    #define CONST const
#endif

#ifndef MAX_PATH
    #define MAX_PATH 260
#endif

#ifndef MAX_STRING
    #define MAX_STRING 260
#endif

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

#ifndef FALSE
    #define FALSE 0
#endif

#ifndef TRUE
    #define TRUE 1
#endif

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Macros
**-----------------------------------------------------------------------------
*/
#ifndef max
    #define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
    #define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#ifndef max3
    #define max3(a,b,c)         (((a) > (b)) ? (((a) > (c)) ? (a) : (c)) : (((b) > (c)) ? (b) : (c)))
#endif

#ifndef min3
    #define min3(a,b,c)         (((a) < (b)) ? (((a) < (c)) ? (a) : (c)) : (((b) < (c)) ? (b) : (c)))
#endif

#ifndef clamp
    #define clamp(L,V,U)        (((L) > (V)) ? (L) : (((U) < (V)) ? (U) : (V)))
#endif

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // COMMON_H


D3DSCENE.H

#ifndef D3DSCENE_H
#define D3DSCENE_H
/*
**-----------------------------------------------------------------------------
**  File:       D3DScene.h
**  Purpose:    Sample D3D Scene Management code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "D3DWin.h"

/*
**-----------------------------------------------------------------------------
**  Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:D3DScene
**  Purpose:
**-----------------------------------------------------------------------------
*/

class D3DScene {
private:
protected:
// Data
LPD3DWindow lpd3dWindow;// Associated D3D Window

public:

D3DScene ();
~D3DScene ();

HRESULT Init (LPD3DWindow lpd3dWindow);
HRESULT Fini (void);

HRESULT Attach (void);
HRESULT Detach (void);

HRESULT AttachViewport (void);
HRESULT DetachViewport (void);

HRESULT Render (void);
HRESULT Restore (void);

}; // End class D3DScene

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End D3DSCENE_H


D3DTEX.CPP

/**************************************************************************
 D3DTexture class

 **************************************************************************/
#define D3D_OVERLOADS

#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <d3d.h>

#include "d3dtex.h"

/**************************************************************************
 DEBUG junk
 **************************************************************************/
#if defined(DEBUG) || defined(_DEBUG)
static void DPF(char *fmt, ...)
{
    char ach[128];
    va_list va;
    va_start( va, fmt );
    wvsprintf( ach, fmt, va );
    va_end( va );
    OutputDebugString("TEXTURE: ");
    OutputDebugString(ach);
    OutputDebugString("\r\n");
}
#else
#define DPF if (0)
#endif

/**************************************************************************
 ChooseTextureFormat
 **************************************************************************/

typedef struct {
    DWORD           bpp;        // we want a texture format of this bpp
    DDPIXELFORMAT   ddpf;       // place the format here
}   FindTextureData;

HRESULT CALLBACK FindTextureCallback(DDSURFACEDESC *DeviceFmt, LPVOID lParam)
{
    FindTextureData * FindData = (FindTextureData *)lParam;
    DDPIXELFORMAT ddpf = DeviceFmt->ddpfPixelFormat;

    DPF("FindTexture: %d %s%s%s %08X %08X %08X %08X",
ddpf.dwRGBBitCount,
        (ddpf.dwFlags & (DDPF_ALPHA|DDPF_ALPHAPIXELS)) ? "ALPHA " : "",
        (ddpf.dwFlags &(DDPF_RGB)) ? "RGB " : "",
        (ddpf.dwFlags &(DDPF_PALETTEINDEXED8 | DDPF_PALETTEINDEXED4)) ? "PAL " : "",
ddpf.dwRBitMask,
ddpf.dwGBitMask,
ddpf.dwBBitMask,
ddpf.dwRGBAlphaBitMask);

    //
    // we use GetDC/BitBlt to init textures so we only
    // want to use formats that GetDC will support.
    //
    if (ddpf.dwFlags & (DDPF_ALPHA|DDPF_ALPHAPIXELS))
        return DDENUMRET_OK;

    if (ddpf.dwRGBBitCount <= 8 &&
        !(ddpf.dwFlags & (DDPF_PALETTEINDEXED8 | DDPF_PALETTEINDEXED4)))
        return DDENUMRET_OK;

    if (ddpf.dwRGBBitCount > 8 && !(ddpf.dwFlags & DDPF_RGB))
        return DDENUMRET_OK;

    //
    // BUGBUG GetDC does not work for 1 or 4bpp YET!
    //
    if (ddpf.dwRGBBitCount < 8)
        return DDENUMRET_OK;

    //
    // keep the texture format that is nearest to the bitmap we have
    //
    if (FindData->ddpf.dwRGBBitCount == 0 ||
       (ddpf.dwRGBBitCount >= FindData->bpp &&
       (UINT)(ddpf.dwRGBBitCount - FindData->bpp) < (UINT)(FindData->ddpf.dwRGBBitCount - FindData->bpp)))
    {
        FindData->ddpf = ddpf;
    }

    return DDENUMRET_OK;
}

void ChooseTextureFormat(IDirect3DDevice2 *Device, DWORD bpp, DDPIXELFORMAT *pddpf)
{
    FindTextureData FindData;
    ZeroMemory(&FindData, sizeof(FindData));
    FindData.bpp = bpp;
    Device->EnumTextureFormats(FindTextureCallback, (LPVOID)&FindData);
    *pddpf = FindData.ddpf;

    DPF("ChooseTexture: %d %s%s%s %08X %08X %08X %08X",
pddpf->dwRGBBitCount,
        (pddpf->dwFlags & (DDPF_ALPHA|DDPF_ALPHAPIXELS)) ? "ALPHA " : "",
        (pddpf->dwFlags &(DDPF_RGB)) ? "RGB " : "",
        (pddpf->dwFlags &(DDPF_PALETTEINDEXED8 | DDPF_PALETTEINDEXED4)) ? "PAL " : "",
pddpf->dwRBitMask,
pddpf->dwGBitMask,
pddpf->dwBBitMask,
pddpf->dwRGBAlphaBitMask);
}

/**************************************************************************
 PaletteFromBitmap
 **************************************************************************/

static IDirectDrawPalette * PaletteFromBitmap(IDirectDraw *DirectDraw, HBITMAP Bitmap)
{
    IDirectDrawPalette *    Palette = NULL;
    HDC                     BitmapDC;
    DWORD                   adw[256];
    int                     colors, i;

    //
    // get the color table from the DIBSection
    //
    BitmapDC = CreateCompatibleDC(NULL);
    SelectObject(BitmapDC, Bitmap);
    colors = GetDIBColorTable(BitmapDC, 0, 256, (RGBQUAD *)adw);
    DeleteDC(BitmapDC);

    if (colors != 0)
    {
        //
        // convert BGR to RGB (stupid IBM!)
        //
        for (i=0; i<colors; i++)
            adw[i] = RGB(GetBValue(adw[i]),GetGValue(adw[i]),GetRValue(adw[i]));

        //
        // create a DirectDraw palette for the texture.
        //
        DirectDraw->CreatePalette(colors <= 16 ? DDPCAPS_4BIT : DDPCAPS_8BIT,
            (PALETTEENTRY *)adw, &Palette, NULL);
    }

    return Palette;
}

/**************************************************************************
 GetDD

 get the IDirectDraw from a IDirect3DDevice, we need the DD
 to create surfaces.

 this function does not do a AddRef on the DirectDraw object
 (ie you dont need to Release)

 NOTE if your app has this around as a global you can use
 the global instead of this code.

 **************************************************************************/
static IDirectDraw * GetDD(IDirect3DDevice2 *Device)
{
    IDirectDraw *       DirectDraw;
    IDirectDrawSurface *Target;
    IDirectDrawSurface2*Target2;

    //
    // get the render target (we need it to get the IDirectDraw)
    //
    if (Device==NULL || Device->GetRenderTarget(&Target) != DD_OK)
        return NULL;

    //
    // get the DirectDraw object, but first we need a IDirectDrawSurface2
    //
    if (Target->QueryInterface(IID_IDirectDrawSurface2, (void**)&Target2) != DD_OK)
        return NULL;
    Target->Release();
    Target2->GetDDInterface((void**)&DirectDraw);
    Target2->Release();
    DirectDraw->Release();  // dont up ref count

    return DirectDraw;
}

/**************************************************************************
 D3DTexture::Load

 load a bitmap from the resource, or bmp file and create a
 D3D texture map

 **************************************************************************/

BOOL D3DTexture::Load(IDirect3DDevice2 *Device, char *BitmapName)
{
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    HBITMAP             Bitmap;
    IDirectDraw *       DirectDraw;

    DPF("D3DTexture::Load(%s)", BitmapName);

    //
    // we need a IDirectDraw so we can create a surface.
    //
    if ((DirectDraw = GetDD(Device)) == NULL)
        return FALSE;

    //
    // load the bitmap from a resource or file.
    //
    Bitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), BitmapName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (Bitmap == NULL)
        Bitmap = (HBITMAP)LoadImage(NULL, BitmapName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (Bitmap == NULL)
        return FALSE;

    // free any existing texture.
    Release();

    GetObject(Bitmap, sizeof(bm), &bm);      // get size of bitmap

    //
    // find the best texture format to use.
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ChooseTextureFormat(Device, bm.bmBitsPixel, &ddsd.ddpfPixelFormat);
    ddsd.dwFlags |= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;

    //
    // create a video memory texture
    //
    // if we are dealing with a HAL create in video memory, else
    // create in system memory.
    //
    D3DDEVICEDESC hal, hel;
    ZeroMemory(&hal, sizeof(hal));
    hal.dwSize = sizeof(hal);
    ZeroMemory(&hel, sizeof(hel));
    hel.dwSize = sizeof(hel);
    Device->GetCaps(&hal, &hel);

    // BUGBUG should we check for texture caps?
    if (hal.dcmColorModel)
        ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_TEXTURE | DDSCAPS_ALLOCONLOAD;
    else
        ddsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY| DDSCAPS_TEXTURE;

    if (DirectDraw->CreateSurface(&ddsd, &DeviceSurface, NULL) != DD_OK)
        goto error;

    //
    // create a system memory surface for the texture.
    //
    // we use this system memory surface for the ::Load call
    // and this surface does not get lost.
    //
    if (hal.dcmColorModel)
    {
        ddsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_TEXTURE;

        if (DirectDraw->CreateSurface(&ddsd, &MemorySurface, NULL) != DD_OK)
            goto error;
    }
    else
    {
        //
        // when dealing with a SW rasterizer we dont need to make two
        // surfaces.
        //
        MemorySurface = DeviceSurface;
        DeviceSurface->AddRef();
    }

    //
    // create a palette for the texture
    //
    if (ddsd.ddpfPixelFormat.dwRGBBitCount <= 8)
    {
        Palette = PaletteFromBitmap(DirectDraw, Bitmap);

        //
        // now we have a palette, attach the palette to the Surface
        // and the texture
        //
        MemorySurface->SetPalette(Palette);
        DeviceSurface->SetPalette(Palette);
    }

    //
    // copy the bitmap to our surface
    //
    if (!Copy(Bitmap))
        goto error;

    //
    // get the texture handle
    //
    IDirect3DTexture2 *Texture;
    DeviceSurface->QueryInterface(IID_IDirect3DTexture2, (void**)&Texture);
    Texture->GetHandle(Device, &Handle);
    Texture->Release();

    //
    // we are done, delete the bitmap (we made a copy) and return.
    //
    DeleteObject(Bitmap);
    return TRUE;

error:
    if (Bitmap)
        DeleteObject(Bitmap);
    Release();
    return FALSE;
}

/**************************************************************************
 D3DTexture::Copy

 init the system memory surface from a GDI Bitmap

 **************************************************************************/

BOOL D3DTexture::Copy(HBITMAP Bitmap)
{
    BITMAP  bm;
    HDC     BitmapDC;
    HDC     SurfaceDC;
    BOOL    result = FALSE;

    DPF("D3DTexture::Copy");

    GetObject(Bitmap, sizeof(bm), &bm);    // get size of bitmap

    BitmapDC = CreateCompatibleDC(NULL);
    SelectObject(BitmapDC, Bitmap);

    if (MemorySurface->GetDC(&SurfaceDC) == DD_OK)
    {
        BitBlt(SurfaceDC, 0, 0, bm.bmWidth, bm.bmHeight, BitmapDC, 0, 0, SRCCOPY);
        MemorySurface->ReleaseDC(SurfaceDC);
        result = Restore();
    }
    DeleteDC(BitmapDC);
    return result;
}

/**************************************************************************
 D3DTexture::Restore

 restore the texture image

 **************************************************************************/

BOOL D3DTexture::Restore()
{
    HRESULT             err;
    IDirect3DTexture2  *MemoryTexture;
    IDirect3DTexture2  *DeviceTexture;

    DPF("D3DTexture::Restore");

    if (DeviceSurface == NULL || MemorySurface == NULL)
        return FALSE;

    //
    // we dont need to do this step for system memory surfaces.
    //
    if (DeviceSurface == MemorySurface)
        return TRUE;

    //
    // restore the video memory texture.
    //
    if (DeviceSurface->Restore() != DD_OK)
        return FALSE;

    //
    // call IDirect3DTexture::Load() to copy the texture to the device.
    //
    DeviceSurface->QueryInterface(IID_IDirect3DTexture2, (void**)&DeviceTexture);
    MemorySurface->QueryInterface(IID_IDirect3DTexture2, (void**)&MemoryTexture);

    err = DeviceTexture->Load(MemoryTexture);

    DeviceTexture->Release();
    MemoryTexture->Release();

    return err == DD_OK;
}

/**************************************************************************
 D3DTexture::Release

 free the texture, free all objects associated with this texture

 NOTE we cant do this in the destructor because DirectDraw
 will clean up all the surfaces when the main direct draw object
 is destroyed, before a static destructor will be called.

 **************************************************************************/

void D3DTexture::Release()
{
    DPF("D3DTexture::Release");

    if (MemorySurface)
       MemorySurface->Release();
    MemorySurface = 0;

    if (DeviceSurface)
       DeviceSurface->Release();
    DeviceSurface = 0;

    if (Palette)
       Palette->Release();
    Palette = 0;

    Handle = 0;
}


D3DTEX.H

/**************************************************************************
 **************************************************************************/
class D3DTexture {
    private:
        IDirectDrawSurface *MemorySurface;  // system memory surface
        IDirectDrawSurface *DeviceSurface;  // video memory texture
IDirectDrawPalette *Palette;
D3DTEXTUREHANDLE    Handle;

    public:
        D3DTexture()
        {
    MemorySurface = 0;
    DeviceSurface = 0;
            Palette = 0;
    Handle = 0;
        }

        D3DTEXTUREHANDLE    GetHandle()    {return Handle;}
        IDirectDrawSurface* GetSurface()   {return MemorySurface;}
        IDirectDrawPalette* GetPalette()   {return Palette;}

        BOOL Load(IDirect3DDevice2 *Device, char *BitmapName);
        BOOL Copy(HBITMAP Bitmap);
        void Release(void);
        BOOL Restore(void);
};


D3DUTILS.CPP

/*
**-----------------------------------------------------------------------------
** Name:    D3DUtils.cpp
** Purpose: Various D3D utility functions
** Notes:
**
** Copyright (c) 1995 - 1997 by Microsoft, all rights reserved.
**-----------------------------------------------------------------------------
*/

// Note:  Must Define D3D_OVERLOADS to get C++ version of D3DMATRIX
#define D3D_OVERLOADS
#include <float.h>
#include <math.h>

#include "d3dutils.h"

const float pi = 3.141592654f;

/*
**-----------------------------------------------------------------------------
** Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       ZeroMatrix
**  Purpose:sets D3D matrix to all 0's
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ZeroMatrix(void)
{
    D3DMATRIX ret;
    for (int i=0; i<4; i++) {
        for (int j=0; j<4; j++) {
            ret(i, j) = 0.0f;
}
}
    return ret;
} // end ZeroMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       IdentityMatrix
**  Purpose:sets D3D matrix to Identiy (1's on diagonal, zero's elsewhere)
**-----------------------------------------------------------------------------
*/

D3DMATRIX
IdentityMatrix(void)
{
    D3DMATRIX ret;
    for (int i=0; i<4; i++) {
        for (int j=0; j<4; j++){
            ret(i, j) = 0.0f;
}
ret(i,i) = 1.0f;
}
    return ret;
} // end IdentityMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       ProjectionMatrix
**  Purpose:sets Projection matrix from fov, near and far planes
**  Notes:
**1. fov is in radians.
**2. See Blinn, "A Trip Down the Graphics Pipeline" pg 188 for details.
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ProjectionMatrix(const float near_plane,
 const float far_plane,
 const float fov)
{
floatc, s, Q;

c = (float) cos(fov*0.5);
s = (float) sin(fov*0.5);
Q = s/(1.0f - near_plane/far_plane);

    D3DMATRIX ret = ZeroMatrix();
    ret(0, 0) = c;
    ret(1, 1) = c;
ret(2, 2) = Q;
ret(3, 2) = -Q*near_plane;
    ret(2, 3) = s;
    return ret;
}// end ProjectionMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       ViewMatrix
**  Purpose:Controls where the camara is.
**  Notes:
**1. Note the roll parameter is in radians and rools the viewpoint
**around the viewing direction
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ViewMatrix(const D3DVECTOR& from,
   const D3DVECTOR& at,
   const D3DVECTOR& world_up,
   const float roll)
{
    D3DMATRIX view = IdentityMatrix();
    D3DVECTOR up, right, view_dir;

    view_dir = Normalize(at - from);
right = CrossProduct(world_up, view_dir);
up = CrossProduct(view_dir, right);

right = Normalize(right);
up = Normalize(up);

    view(0, 0) = right.x;
    view(1, 0) = right.y;
    view(2, 0) = right.z;
    view(0, 1) = up.x;
    view(1, 1) = up.y;
    view(2, 1) = up.z;
    view(0, 2) = view_dir.x;
    view(1, 2) = view_dir.y;
    view(2, 2) = view_dir.z;

    view(3, 0) = -DotProduct(right, from);
    view(3, 1) = -DotProduct(up, from);
    view(3, 2) = -DotProduct(view_dir, from);

// Set roll
if (roll != 0.0f) {
view = MatrixMult(RotateZMatrix(-roll), view);
}

    return view;
} // end ViewMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       RotateXMatrix
**  Purpose:Rotate matrix about X axis
**-----------------------------------------------------------------------------
*/

D3DMATRIX
RotateXMatrix(const float rads)
{
floatcosine, sine;

cosine = (float) cos(rads);
sine = (float) sin(rads);
    D3DMATRIX ret = IdentityMatrix();
    ret(1,1) = cosine;
ret(2,2) = cosine;
ret(1,2) = -sine;
ret(2,1) = sine;
    return ret;
} // end RotateXMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       RotateYMatrix
**  Purpose:Rotate matrix about Y axis
**-----------------------------------------------------------------------------
*/

D3DMATRIX
RotateYMatrix(const float rads)
{
floatcosine, sine;

cosine = (float) cos(rads);
sine = (float) sin(rads);
    D3DMATRIX ret = IdentityMatrix();
    ret(0,0) = cosine;
ret(2,2) = cosine;
ret(0,2) = sine;
ret(2,0) = -sine;
    return ret;
} // end RotateY

/*
**-----------------------------------------------------------------------------
**  Name:       RotateZMatrix
**  Purpose:Rotate matrix about Z axis
**-----------------------------------------------------------------------------
*/

D3DMATRIX
RotateZMatrix(const float rads)
{
floatcosine, sine;

cosine = (float) cos(rads);
sine = (float) sin(rads);
    D3DMATRIX ret = IdentityMatrix();
    ret(0,0) = cosine;
ret(1,1) = cosine;
ret(0,1) = -sine;
ret(1,0) = sine;
    return ret;
} // end RotateZMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       TranslateMatrix
**  Purpose:    Returns matrix to translate by (dx, dy, dz)
**-----------------------------------------------------------------------------
*/

D3DMATRIX
TranslateMatrix(const float dx, const float dy, const float dz)
{
    D3DMATRIX ret = IdentityMatrix();
ret(3, 0) = dx;
ret(3, 1) = dy;
ret(3, 2) = dz;
return ret;
} // end TranslateMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       TranslateMatrix
**  Purpose:    Returns matrix to translate by v
**-----------------------------------------------------------------------------
*/

D3DMATRIX
TranslateMatrix(const D3DVECTOR& v)
{
    D3DMATRIX ret = IdentityMatrix();
ret(3, 0) = v[0];
ret(3, 1) = v[1];
ret(3, 2) = v[2];
return ret;
} // end TranslateMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       ScaleMatrix
**  Purpose:    scale matrix (uniform)
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ScaleMatrix(const float size)
{
    D3DMATRIX ret = IdentityMatrix();
ret(0, 0) = size;
ret(1, 1) = size;
ret(2, 2) = size;
return ret;
} // end ScaleMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       ScaleMatrix
**  Purpose:scale matrix
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ScaleMatrix(const float a, const float b, const float c)
{
    D3DMATRIX ret = IdentityMatrix();
ret(0, 0) = a;
ret(1, 1) = b;
ret(2, 2) = c;
return ret;
} // end ScaleMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       ScaleMatrix
**  Purpose:scale matrix
**-----------------------------------------------------------------------------
*/

D3DMATRIX
ScaleMatrix(const D3DVECTOR& v)
{
    D3DMATRIX ret = IdentityMatrix();
ret(0, 0) = v.x;
ret(1, 1) = v.y;
ret(2, 2) = v.z;
return ret;
} // end ScaleMatrix

/*
**-----------------------------------------------------------------------------
**  Name:       MatrixMult
**  Purpose:[C] = [A] * [B]
**-----------------------------------------------------------------------------
*/

D3DMATRIX
MatrixMult(const D3DMATRIX & a, const D3DMATRIX & b)
{
D3DMATRIX ret = ZeroMatrix();

for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
for (int k=0; k<4; k++) {
ret(i, j) += a(k, j) * b(i, k);
}
}
}
return ret;
} // end MatrixMult

/*
**-----------------------------------------------------------------------------
**  Name:       TransformVector
**  Purpose:V' = V * [M]
**-----------------------------------------------------------------------------
*/

D3DVECTOR
TransformVector(const D3DVECTOR& v, const D3DMATRIX & m)
{
floathvec[4];

for (int i=0; i<4; i++) {
hvec[i] = 0.0f;
for (int j=0; j<4; j++) {
if (j==3) {
hvec[i] += m(j, i);
} else {
hvec[i] += v[j] * m(j, i);
}
}
}
D3DVECTOR ret(hvec[0]/hvec[3], hvec[1]/hvec[3], hvec[2]/hvec[3]);

return ret;
} // end TransformVector

/*
**-----------------------------------------------------------------------------
**  Name:       TransformNormal
**  Purpose:N' = N * [M]
**-----------------------------------------------------------------------------
*/

D3DVECTOR
TransformNormal(const D3DVECTOR& v, const D3DMATRIX & mat)
{
D3DMATRIXm;

m = MatrixInverse(mat);
m = MatrixTranspose(m);
return TransformVector(v, m);
}  // end TransformNormal

/*
**-----------------------------------------------------------------------------
**  Name:       MatrixInverse
**  Purpose:Creates the inverse of a 4x4 matrix
**-----------------------------------------------------------------------------
*/

static voidlubksb(D3DMATRIX & a, int *indx, float *b);
static void ludcmp(D3DMATRIX & a, int *indx, float *d);

D3DMATRIX
MatrixInverse(const D3DMATRIX & m)
{
D3DMATRIXn, y;
inti, j, indx[4];
floatd, col[4];

n = m;
ludcmp(n, indx, &d);

for (j=0; j<4; j++) {
for (i=0; i<4; i++) {
col[i] = 0.0f;
}
col[j] = 1.0f;
lubksb(n, indx, col);
for (i=0; i<4; i++) {
y(i, j) = col[i];
}
}
return y;
} // end MatrixInverse

/*
**-----------------------------------------------------------------------------
**  Name:       lubksb
**  Purpose:backward subsitution
**-----------------------------------------------------------------------------
*/

static void
lubksb(D3DMATRIX & a, int *indx, float *b)
{
inti, j, ii=-1, ip;
floatsum;

for (i=0; i<4; i++) {
ip = indx[i];
sum = b[ip];
b[ip] = b[i];
if (ii>=0) {
for (j=ii; j<=i-1; j++) {
sum -= a(i, j) * b[j];
}
} else if (sum != 0.0) {
ii = i;
}
b[i] = sum;
}
for (i=3; i>=0; i--) {
sum = b[i];
for (j=i+1; j<4; j++) {
sum -= a(i, j) * b[j];
}
b[i] = sum/a(i, i);
}
} // end lubksb

/*
**-----------------------------------------------------------------------------
**  Name:       ludcmp
**  Purpose:LU decomposition
**-----------------------------------------------------------------------------
*/

static void
ludcmp(D3DMATRIX & a, int *indx, float *d)
{
floatvv[4];               /* implicit scale for each row */
floatbig, dum, sum, tmp;
inti, imax, j, k;

*d = 1.0f;
for (i=0; i<4; i++) {
big = 0.0f;
for (j=0; j<4; j++) {
if ((tmp = (float) fabs(a(i, j))) > big) {
big = tmp;
}
}
/*
if (big == 0.0f) {
printf("ludcmp(): singular matrix found...\n");
exit(1);
}
*/
vv[i] = 1.0f/big;
}
for (j=0; j<4; j++) {
for (i=0; i<j; i++) {
sum = a(i, j);
for (k=0; k<i; k++) {
sum -= a(i, k) * a(k, j);
}
a(i, j) = sum;
}
big = 0.0f;
for (i=j; i<4; i++) {
sum = a(i, j);
for (k=0; k<j; k++) {
sum -= a(i, k)*a(k, j);
}
a(i, j) = sum;
if ((dum = vv[i] * (float)fabs(sum)) >= big) {
big = dum;
imax = i;
}
}
if (j != imax) {
for (k=0; k<4; k++) {
dum = a(imax, k);
a(imax, k) = a(j, k);
a(j, k) = dum;
}
*d = -(*d);
vv[imax] = vv[j];
}
indx[j] = imax;
if (a(j, j) == 0.0f) {
a(j, j) = 1.0e-20f;      /* can be 0.0 also... */
}
if (j != 3) {
dum = 1.0f/a(j, j);
for (i=j+1; i<4; i++) {
a(i, j) *= dum;
}
}
}
} // end ludcmp

/*
**-----------------------------------------------------------------------------
**  Name:       Matrix Transpose
**  Purpose:[M] = [M]'
**-----------------------------------------------------------------------------
*/

D3DMATRIX
MatrixTranspose(const D3DMATRIX & m)
{
D3DMATRIXret;
inti, j;

for (i=0; i<4; i++) {
for (j=0; j<4; j++) {
ret(i, j) = m(j, i);
}
}

return ret;
} // end MatrixTranspose

/*
Class Methods
*/

/*
**-----------------------------------------------------------------------------
**  Name:       Light::Light
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

Light::Light(LPDIRECT3D2 lpD3D)
{
    memset(&light, 0, sizeof(D3DLIGHT2));
light.dwSize = sizeof(D3DLIGHT2);
    lpD3D->CreateLight(&lpD3DLight, NULL);
changed = 1;

// default to no attenuation with distance
    light.dvAttenuation0 = 1.0f;
    light.dvAttenuation1 = 0.0f;
    light.dvAttenuation2 = 0.0f;
// default to no max distance
light.dvRange = D3DLIGHT_RANGE_MAX;
// default to linear ramp from inner to outer cone
light.dvFalloff = 1.0f;
// default to on
light.dwFlags = D3DLIGHT_ACTIVE;
} // end of Light::Light

/*
**-----------------------------------------------------------------------------
**  Name:       Light::~Light
**  Purpose:Destructor
**-----------------------------------------------------------------------------
*/

Light::~Light()
{
if (lpD3DLight) {
lpD3DLight->Release();
lpD3DLight = NULL;
}
} // end Light::~Light

/*
**-----------------------------------------------------------------------------
**  Name:       PointLight::PointLight
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

PointLight::PointLight(LPDIRECT3D2lpD3D,
   const D3DVECTOR&color,
   const D3DVECTOR&position) : Light(lpD3D)
{
    light.dltType = D3DLIGHT_POINT;
    light.dcvColor.r = color[0];
    light.dcvColor.g = color[1];
    light.dcvColor.b = color[2];
    light.dvPosition.x = position[0];
    light.dvPosition.y = position[1];
    light.dvPosition.z = position[2];

changed = 1;
} // end PointLight::PointLight

/*
**-----------------------------------------------------------------------------
**  Name:       SpotLight::SpotLight
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

SpotLight::SpotLight(LPDIRECT3D2lpD3D,
 const D3DVECTOR&color,
 const D3DVECTOR&position,
 const D3DVECTOR&direction,
 const floatumbra_angle,
 const floatpenumbra_angle) : Light(lpD3D)
{
    light.dltType = D3DLIGHT_SPOT;
    light.dcvColor.r = color[0];
    light.dcvColor.g = color[1];
    light.dcvColor.b = color[2];
    light.dvPosition.x = position[0];
    light.dvPosition.y = position[1];
    light.dvPosition.z = position[2];
    light.dvDirection.x = direction[0];
    light.dvDirection.y = direction[1];
    light.dvDirection.z = direction[2];
light.dvTheta = umbra_angle;
light.dvPhi = penumbra_angle;

changed = 1;
} // end of SpotLight::SpotLight

/*
**-----------------------------------------------------------------------------
**  Name:       DirectionalLight::DirectionalLight
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

DirectionalLight::DirectionalLight(LPDIRECT3D2lpD3D,
   const D3DVECTOR&color,
   const D3DVECTOR&direction) : Light(lpD3D)
{
    light.dltType = D3DLIGHT_DIRECTIONAL;
    light.dcvColor.r = color[0];
    light.dcvColor.g = color[1];
    light.dcvColor.b = color[2];
    light.dvDirection.x = direction[0];
    light.dvDirection.y = direction[1];
    light.dvDirection.z = direction[2];

changed = 1;
}// end of DirectionalLight::DirectionalLight

/*
**-----------------------------------------------------------------------------
**  Name:       ParallelLight::ParallelLight
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

ParallelPointLight::ParallelPointLight(LPDIRECT3D2lpD3D,
   const D3DVECTOR&color,
   const D3DVECTOR&position) : Light(lpD3D)
{
    light.dltType = D3DLIGHT_PARALLELPOINT;
    light.dcvColor.r = color[0];
    light.dcvColor.g = color[1];
    light.dcvColor.b = color[2];
    light.dvPosition.x = position[0];
    light.dvPosition.y = position[1];
    light.dvPosition.z = position[2];

changed = 1;
} // end of ParallelPointLight::ParallelPointLight constructor

/*
**-----------------------------------------------------------------------------
**  Name:       Material::Material
**  Purpose:Constructor
**-----------------------------------------------------------------------------
*/

Material::Material(LPDIRECT3D2 lpD3D, LPDIRECT3DDEVICE2 lpDev)
{
    memset(&Mat, 0, sizeof(D3DMATERIAL));
Mat.dwSize = sizeof(D3DMATERIAL);

// leave the default material black

    Mat.dwRampSize = 16;
Mat.diffuse.a = 1.0f;

lpD3D->CreateMaterial(&lpMat, NULL);
    lpMat->SetMaterial(&Mat);
    lpMat->GetHandle(lpDev, &hMat);

changed = 1;
Set();
} // end of Material::Material

/*
**-----------------------------------------------------------------------------
**  Name:       Material::~Material
**  Purpose:Destructor
**-----------------------------------------------------------------------------
*/

Material::~Material()
{
if (lpMat) {
lpMat->Release();
lpMat = NULL;
}
} // end of Material::~Material

/*
**-----------------------------------------------------------------------------
**  Name:       rnd
**  Purpose:Returns a random number (float) in the range [0.0 - 1.0]
**-----------------------------------------------------------------------------
*/

float
rnd(void)
{
return float(rand())/RAND_MAX;
}// end of rnd()

/*
**-----------------------------------------------------------------------------
**  end of File
**-----------------------------------------------------------------------------
*/


D3DUTILS.H

#ifndef D3DUTILS_H
#define D3DUTILS_H
/*
**-----------------------------------------------------------------------------
** Name:    D3DUtils.h
** Purpose: Various D3D utility functions
** Notes:
**
** Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

#define D3D_OVERLOADS
#include <d3d.h>
#include <d3dtypes.h>

extern const float pi;

/*
**-----------------------------------------------------------------------------
** Function Prototypes
**-----------------------------------------------------------------------------
*/

// generic simple matrix routines
extern D3DMATRIX ZeroMatrix(void);
extern D3DMATRIX IdentityMatrix(void);

extern D3DMATRIX ProjectionMatrix(const float near_plane, const float far_plane, const float fov);
extern D3DMATRIX ViewMatrix(const D3DVECTOR & from, const D3DVECTOR & at,
const D3DVECTOR & up, const float roll=0.0f);

extern D3DMATRIX RotateXMatrix(const float rads);
extern D3DMATRIX RotateYMatrix(const float rads);
extern D3DMATRIX RotateZMatrix(const float rads);
extern D3DMATRIX TranslateMatrix(const float dx, const float dy, const float dz);
extern D3DMATRIX TranslateMatrix(const D3DVECTOR & v);
extern D3DMATRIX ScaleMatrix(const float size);
extern D3DMATRIX ScaleMatrix(const float a, const float b, const float c);
extern D3DMATRIX ScaleMatrix(const D3DVECTOR & v);

extern D3DMATRIX MatrixMult(const D3DMATRIX & a, const D3DMATRIX & b);
extern D3DMATRIX MatrixInverse(const D3DMATRIX & m);
extern D3DMATRIX MatrixTranspose(const D3DMATRIX & m);

extern D3DVECTOR TransformVector(const D3DVECTOR & v, const D3DMATRIX & m);
extern D3DVECTOR TransformNormal(const D3DVECTOR & v, const D3DMATRIX & m);

// Other stuff
extern float rnd(void);

/*
**-----------------------------------------------------------------------------
** Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Class:Light
**Purpose:
**-----------------------------------------------------------------------------
*/

class Light {
protected:
public:
D3DLIGHT2light;// structure defining the light
LPDIRECT3DLIGHTlpD3DLight;// object pointer for the light
intchanged;
public:
Light(LPDIRECT3D2 lpD3D);
~Light();

HRESULTAddToViewport(LPDIRECT3DVIEWPORT2 lpView);
HRESULTRemoveFromViewport(LPDIRECT3DVIEWPORT2 lpView);
HRESULTSet(void);

voidSetColor(const D3DVECTOR& color);
D3DVECTORGetColor(void) const;
voidSetPosition(const D3DVECTOR& position);
D3DVECTORGetPosition(void) const;
voidSetDirection(const D3DVECTOR& direction);
D3DVECTORGetDirection(void) const;
voidSetAttenuation(const D3DVECTOR& attenuation);
D3DVECTORGetAttenuation(void) const;
voidSetRange(const float range);
floatGetRange(void) const;
voidSetFalloff(const float falloff);
floatGetFalloff(void) const;
voidSetUmbra(const float falloff);
floatGetUmbra(void) const;
voidSetPenumbra(const float falloff);
floatGetPenumbra(void) const;
voidSetFlags(const DWORD flags);
DWORDGetFlags(void) const;
}; // End Light

/*
**-----------------------------------------------------------------------------
**Class:PointLight
**Purpose:
**-----------------------------------------------------------------------------
*/

class PointLight : public Light {
public:
PointLight(LPDIRECT3D2 lpD3D,
const D3DVECTOR & color,
const D3DVECTOR & position);
}; // End PointLight

/*
**-----------------------------------------------------------------------------
**Class:SpotLight
**Purpose:
**-----------------------------------------------------------------------------
*/

class SpotLight : public Light {
public:
SpotLight(LPDIRECT3D2 lpD3D,
const D3DVECTOR& color,
const D3DVECTOR& position,
const D3DVECTOR& direction,
const float umbra_angle,
const float penumbra_angle);
}; // End SpotLight

/*
**-----------------------------------------------------------------------------
**Class:DirectionalLight
**Purpose:
**-----------------------------------------------------------------------------
*/

class DirectionalLight : public Light {
public:
DirectionalLight(LPDIRECT3D2 lpD3D,
const D3DVECTOR& color, const D3DVECTOR& direction);
};

/*
**-----------------------------------------------------------------------------
**Class:ParallelPointLight
**Purpose:
**-----------------------------------------------------------------------------
*/

class ParallelPointLight : public Light {
public:
ParallelPointLight(LPDIRECT3D2 lpD3D, const D3DVECTOR& color, const D3DVECTOR& position);
}; // End ParallelPointLight

/*
**-----------------------------------------------------------------------------
**Class:Material
**Purpose:
**-----------------------------------------------------------------------------
*/

class Material
{
protected:
D3DMATERIALMat;
    D3DMATERIALHANDLEhMat;
LPDIRECT3DMATERIAL2 lpMat;
intchanged;
public:
Material(LPDIRECT3D2 lpD3D, LPDIRECT3DDEVICE2 lpDev);
~Material();

HRESULTSetAsCurrent(LPDIRECT3DDEVICE2 lpDev);
HRESULTSetAsBackground(LPDIRECT3DVIEWPORT2 lpView);
HRESULTSet(void);

voidSetDiffuse(const D3DVECTOR& color);
D3DVECTORGetDiffuse(void) const;
voidSetAlpha(const float& alpha);
floatGetAlpha(void) const;
voidSetAmbient(const D3DVECTOR& color);
D3DVECTORGetAmbient(void) const;
voidSetEmissive(const D3DVECTOR& color);
D3DVECTORGetEmissive(void) const;
voidSetSpecular(const D3DVECTOR& color);
D3DVECTORGetSpecular(void) const;
voidSetPower(const D3DVALUE& power);
D3DVALUEGetPower(void) const;
voidSetRampSize(const DWORD& ramp);
DWORDGetRampSize(void) const;
voidSetTextureHandle(const D3DTEXTUREHANDLE& hTexture);
D3DTEXTUREHANDLEGetTextureHandle(void);
}; // End Material

//
// include the inline functions for the classes
//
#include "d3dutils.inl"

/*
**-----------------------------------------------------------------------------
**End of File
**-----------------------------------------------------------------------------
*/
#endif // D3DUTILS_H


D3DWIN.CPP

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWin.cpp
**  Purpose:
**
**Basic Initialization proceeds as follows:
**
**1.  Enumerate all Driver, modes, D3D devices (see DrvMgr.cpp for details)
**2.  Choose a starting driver, mode, D3D device
**- default driver = primary display driver (lpGuidDD = NULL)
**- default mode   = current desktop
**- default device = D3D device compatible with desktop mode
**3.  Validate driver, mode, D3D device
**4.  Create interfaces (from DD driver)
**  5.  Set window (from associated window handle)
**  6.  Create DD/D3D interfaces (lpDD, lpDD2, lpD3D)
**  7.  Create Primary surface (primary palette, if necessary)
**- Attach a clipper to primary surface
**  8.  Create Render surface
**- Render surface (and associated Z-buffer)
**- D3D Device
**- D3D Viewport
**
**  After initialization is complete, we have the
**following objects necessary for rendering:
**
**lpDD2 - DirectDraw interface, used for creating texture surfaces
**lpD3D - Direct3D interface, used for creating materials, lights, viewports
**lpD3DDevice - D3D device (current material, current viewport, etc.)
**lpViewport - current viewport
**lpPrimary  - front buffer
**lpRender   - render target
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Includes
**-----------------------------------------------------------------------------
*/

#include "D3DWin.h"
#include "WinProc.h"
#include "D3DScene.h"
#include "Debug.h"

/*
**-----------------------------------------------------------------------------
**D3DWindow Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::D3DWindow
** Purpose: Default Constructor
**-----------------------------------------------------------------------------
*/

D3DWindow::D3DWindow (void)
{
    ZeroMemory (this, sizeof(D3DWindow));
    this->dwSize = sizeof(D3DWindow);

// Default to creating a z-buffer
createZBufferOn ();
} // End D3DWindow::D3DWindow ()

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::~D3DWindow
** Purpose: Destructor
**-----------------------------------------------------------------------------
*/

D3DWindow::~D3DWindow (void)
{
// Destroy all objects
    Fini ();

// Mark all other pointers as invalid
// In case user tries to reuse this object
lpCurrDriver = NULL;
lpCurrMode = NULL;
lpCurrDevice = NULL;
hWindow = NULL;
lpd3dScene = NULL;

} // End D3DWindow::~D3DWindow

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Create
** Purpose: Creates a D3DWindow
**
** Basic Algorithm:
**- Validate parameters
**- Choose (and validate choices) for driver, mode, device
**- Create Interfaces
**- Set Window
**- Set Mode
**- Create Primary surface (and palette)
**- Create Render surface (and D3D device)
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Create (
HWND   hWnd,/* In:  Window */
LPGUID lpGuidDD,/* In:  Requested DirectDraw Device */
DWORD  dwW,/* In:Requested Mode */
DWORD  dwH,
DWORD  dwBPP,
DWORD  dwRefresh,
LPGUID lpGuidD3D,/* In:  Requested D3D device */
BOOL   fUseZBuffer) /* In:  Create Z-Buffer */
{
    HRESULT         hResult;

    // Check parameters
if ((! hWnd) || (! IsWindow (hWnd)))
{
// Error, Invalid parameters
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

// Set Current Window
hWindow = hWnd;

    // Set Use Z-Buffer On/Off
    if (fUseZBuffer)
        createZBufferOn ();
    else
        createZBufferOff ();

    // Choose Default Driver, Mode, device
    hResult = ChooseDriverDefaults (lpGuidDD,
dwW, dwH, dwBPP, dwRefresh,
lpGuidD3D,
TRUE,
&lpCurrDriver,
&lpCurrMode,
&lpCurrDevice);
    if (FAILED (hResult))
        return hResult;

    // Create DD/D3D Interface objects
    hResult = InitInterfaces ();
    if (FAILED (hResult))
        return hResult;

// Attach window to DD interface
hResult = InitWindow ();
if (FAILED (hResult))
goto lblCLEANUP;

// Set Fullscreen Mode
hResult = InitFullscreenMode ();
if (FAILED (hResult))
goto lblCLEANUP;

    // Create Primary Surface (and palette)
    hResult = InitPrimary ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Create the Render Surface (and D3D Device)
    hResult = InitRender ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Notify the window of a successful initialization
SendMessage (hWindow, D3DWIN_INIT, 0, (LPARAM)(void *)this);

    // Success
    return DD_OK;

lblCLEANUP:
// Failure

    // Cleanup
    Fini ();

    return hResult;
} // End D3DWindow::Create

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Init
** Purpose:
**
** Basic Algorithm:
**- Validate driver, mode, device
**- Create Interfaces
**- Attach Window
**- Set Fullscreen Mode
**- Create Primary surface (and palette)
**- Create Render surface (and D3D device)
**
** Notes:
**1.  Assumes that a valid window handle has already
**been associated with this D3DWindow
**2.  Assumes that driver, mode, device already choosen
**- however if not, reasonable defaults will be choosen
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Init (void)
{
    HRESULT         hResult;

    // Check parameters
if ((! hWindow) || (! IsWindow (hWindow)))
{
// Error, Invalid Initialization
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

    // Validate Curr Driver, mode, device
hResult = ValidateDefaults ();
if (FAILED (hResult))
return hResult;

    // Create DD/D3D Interface objects
    hResult = InitInterfaces ();
    if (FAILED (hResult))
goto lblCLEANUP;

// Attach the window to the DD interface
hResult = InitWindow ();
if (FAILED (hResult))
goto lblCLEANUP;

// Set the Mode
hResult = InitFullscreenMode ();
if (FAILED (hResult))
goto lblCLEANUP;

    // Create Primary Surface (and palette)
    hResult = InitPrimary ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Create Render surface (and D3D device)
    hResult = InitRender ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Notify the window of a successful initialization
SendMessage (hWindow, D3DWIN_INIT, 0, (LPARAM)(void *)this);

    // Success
    return DD_OK;

lblCLEANUP:
// Failure

    // Cleanup
    Fini ();
    return hResult;
} // End D3DWindow::Init

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Fini
** Purpose: Destroys a D3DWindow
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Fini (void)
{
// Notify the window that we are cleaning up
SendMessage (hWindow, D3DWIN_FINI, 0, (LPARAM)(void *)this);

// Cleanup
    FiniRender ();
    FiniPrimary ();
FiniFullscreenMode ();
//FiniWindow ();
    FiniInterfaces ();

// Success
return DD_OK;
} // End D3DWindow::Fini

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ValidateDefaults
** Purpose: Verify's current driver, mode, and device
** Notes:
**
**1.  Rather than fail completely, this will pick new defaults
**      if the current defaults don't work.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ValidateDefaults (void)
{
LPGUIDlpGuidDD, lpGuidD3D;
HRESULThResult;
    LPDDDrvInfolpDrvNew;
LPDDModeInfolpModeNew;
LPD3DDevInfolpDevNew;

// Initialize Driver Manager, if necessary
if (! DDDrvMgr::isInitialized ())
{
hResult = DDDrvMgr::Init ();
if (FAILED (hResult))
return hResult;
}

    // Get DD Guid
if (lpCurrDriver)
lpGuidDD = lpCurrDriver->GetGuid ();
else
lpGuidDD = NULL;

// Get D3D Guid
if (lpCurrDevice)
lpGuidD3D = &(lpCurrDevice->guid);
else
lpGuidD3D = NULL;

// Get Driver corresponding to DD Guid
    lpDrvNew = ValidateDriver (lpGuidDD);
if (! lpDrvNew)
    {
        // Error, invalid DD Guid
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
        return hResult;
    }

DWORD w, h, bpp, refresh;

// Get Current mode info
if (lpCurrMode)
lpCurrMode->GetMode (w, h, bpp, refresh);
else
{
w = h = bpp = refresh = 0;
}

// Get Fullscreen D3D device and compatible mode
if (! GetFullscreenMode (lpDrvNew, lpGuidD3D,
 w, h, bpp, refresh,
 &lpModeNew, &lpDevNew))
{
// Couldn't find a valid mode and/or device
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return APPERR_GENERIC;
}

// Note:  Instead of complaining let's go ahead
//  and use the new defaults
// Save new defaults
lpCurrDriver = lpDrvNew;
lpCurrMode = lpModeNew;
lpCurrDevice = lpDevNew;

    // Success
    return DD_OK;
} // End D3DWindow::ValidateDefaults

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::CreateInterfaces
** Purpose: Creates DD/D3D interfaces from specified Guid
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::CreateInterfaces (LPGUID lpDDGuid)
{
LPDDDrvInfo lpDrvNew;
HRESULThResult;

// Verify Guid
lpDrvNew = ValidateDriver (lpDDGuid);
if (! lpDrvNew)
{
// Invalid Params
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

lpCurrDriver = lpDrvNew;

hResult = D3DWindow::InitInterfaces ();
if (FAILED (hResult))
return hResult;

// Success
return DD_OK;
} // End D3DWindow::CreateInterfaces

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitInterfaces
** Purpose: Creates DD/D3D interfaces
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitInterfaces (void)
{
    HRESULT         hResult;
    LPGUID          lpGuid;

    // Do we have a current DD Driver
    if (! lpCurrDriver)
    {
// So, Grab the Primary DD driver
lpCurrDriver = ValidateDriver (NULL);
if (! lpCurrDriver)
{
// Error, No current Driver
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}
    }

    // Get DD Guid
    lpGuid = lpCurrDriver->GetGuid ();

    // Create DD interface
    hResult = DirectDrawCreate (lpGuid, &lpDD, NULL);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);
goto lblCLEANUP;
    }

    // Get DD2 interface
    hResult = lpDD->QueryInterface ((REFIID)IID_IDirectDraw2, (void **)&lpDD2);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);

// Inform User that they Need DX 5.0 installed
        MessageBox (hWindow, TEXT ("This app requires DirectX 5.0"), TEXT("Error"), MB_OK);
        goto lblCLEANUP;
    }

    // Get D3D interface
    hResult = lpDD2->QueryInterface ((REFIID)IID_IDirect3D2, (void **)&lpD3D);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);
        goto lblCLEANUP;
    }

// Mark this stage as done
turnValidInterfaceOn ();

    // Success
    return DD_OK;

lblCLEANUP:
    // Failure
    FiniInterfaces ();

    return hResult;
} // End InitInterfaces

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniInterfaces
** Purpose: Destroys DD/D3D interfaces
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniInterfaces (void)
{
// Mark this stage as invalid
turnValidInterfaceOff ();

    // Release Direct3D Interface
    if (lpD3D)
    {
        lpD3D->Release ();
        lpD3D = NULL;
    }

    // Release DirectDraw2 Interface
    if (lpDD2)
    {
        lpDD2->Release ();
        lpDD2 = NULL;
    }

    // Release DirectDraw Interface
    if (lpDD)
    {
        lpDD->Release ();
        lpDD = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniInterfaces

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitWindow
** Purpose: Attaches Window to Direct Draw Interface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitWindow (void)
{
HRESULT hResult;
DWORDdwFlags;

// Check Initialization
if ((! hWindow) || (! IsWindow (hWindow)))
{
// Error, we need a valid window to continue
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

    // Get Cooperative Flags
    dwFlags = DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN;

    // Set Cooperative Level
    hResult = lpDD2->SetCooperativeLevel (hWindow, dwFlags);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
return hResult;
    }

// Success
return DD_OK;
} // End D3DWindow::InitWindow

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniWindow
** Purpose: Cleanups window
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniWindow (void)
{
// Currently does nothing

// Success
return DD_OK;
} // End D3DWindow::FiniWindow

/*
**-----------------------------------------------------------------------------
**Name:D3DWindow::InitFullscreenMode
**Purpose:Switches to requested fullscreen mode
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitFullscreenMode (void)
{
HRESULT hResult;
DWORD   dwFlags = 0;

// Check Initialization
if ((! lpCurrMode) || (! lpDD2))
{
// Error, we need a valid mode and DirectDraw 2 interface to proceed
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

// Calculate Mode info
DWORD w, h, bpp, refresh;
lpCurrMode->GetMode (w, h, bpp, refresh);

// Special check for mode 320 x 200 x 8
if ((w == 320) && (h == 200) && (bpp == 8))
    {
// Make sure we use Mode 13 instead of Mode X
dwFlags = DDSDM_STANDARDVGAMODE;
    }

    // Set Requested Fullscreen mode
    hResult = lpDD2->SetDisplayMode (w, h, bpp, refresh, dwFlags);
    if (SUCCEEDED (hResult))
{
// Save Surface Rectangle
rSurf.left   = 0;
rSurf.top    = 0;
rSurf.right  = w;
rSurf.bottom = h;

// Success
turnValidFullscreenOn ();
return hResult;
}

DPF (DEBUG_ERROR, "SetDisplayMode failed (%d x %d x %d), trying (640 x 480 x %d)", w, h, bpp, bpp);

// Don't give up!
// Try 640 x 480 x bpp mode instead
if ((w != 640 || h != 480))
    {
w = 640;
h = 480;

lpCurrMode = ValidateMode (lpCurrDriver, w, h, bpp, 0, lpCurrDevice);
if (lpCurrMode)
{
hResult = lpDD2->SetDisplayMode (w, h, bpp, 0, 0);
if (SUCCEEDED (hResult))
{
// Save Surface Rectangle
rSurf.left   = 0;
rSurf.top    = 0;
rSurf.right  = w;
rSurf.bottom = h;

// Success
turnValidFullscreenOn ();
return hResult;
}
}
}

// Keep trying
// Try 640 x 480 x 16 mode instead
if (bpp != 16)
    {
DPF (DEBUG_ERROR, "SetDisplayMode failed (640 x 480 x %d), trying (640 x 480 x 16)", bpp);
bpp = 16;

lpCurrMode = ValidateMode (lpCurrDriver, w, h, bpp, 0, lpCurrDevice);
if (lpCurrMode)
{
hResult = lpDD2->SetDisplayMode (w, h, bpp, 0, 0);
if (SUCCEEDED (hResult))
{
// Save Surface Rectangle
rSurf.left   = 0;
rSurf.top    = 0;
rSurf.right  = w;
rSurf.bottom = h;

// Success
turnValidFullscreenOn ();
return hResult;
}
}
}

// Failure
REPORTERR (hResult);
return hResult;
} // End D3DWindow::InitFullscreenMode

/*
**-----------------------------------------------------------------------------
**Name:D3DWindow::FiniFullscreenMode
**Purpose:Restores mode to original desktop
**Notes:This does nothing if we are windowed
**I.E. the user already is restored
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniFullscreenMode (void)
{
turnValidFullscreenOff ();

// Restore original desktop mode
if (lpDD2)
lpDD2->RestoreDisplayMode();

// Success
return DD_OK;
} // End D3DWindow::FiniFullscreenMode

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitPrimary
** Purpose: Creates a primary surface (desktop surface)
** Notes:   Also creates and attaches palette, if necessary
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitPrimary (void)
{
    HRESULThResult;
    DDSURFACEDESCddsd;

    // Check Initialization
    if ((! lpCurrMode) || (! lpDD2))
    {
        // Error, Need a valid mode and DD interface to proceed
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
    }

// Note:  There is no need to fill in width, height, bpp, etc.
//        This was taken care of in the SetDisplayMode call.

// Setup Surfaces caps for a front buffer and back buffer
    ddsd.dwSize= sizeof(ddsd);
    ddsd.dwFlags= DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps= DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX |
  DDSCAPS_3DDEVICE;// Create a D3D compatible surface
    ddsd.dwBackBufferCount= 1;

// Create Primary surface
    hResult = lpDD2->CreateSurface (&ddsd, &lpddsPrimary, NULL);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        return hResult;
    }

// Create and attach palette, if necessary
hResult = InitPalette ();
if (FAILED (hResult))
return hResult;

// Mark as Valid
turnValidPrimaryOn ();

    // Success
    return DD_OK;
} // End D3DWindow::InitPrimary

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniPrimary
** Purpose: Destroys the Primary Surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniPrimary (void)
{
// Mark as Invalid
turnValidPrimaryOff ();

// Cleanup palette
FiniPalette ();

    // Release Primary Surface Object
    if (lpddsPrimary)
    {
        lpddsPrimary->Release ();
        lpddsPrimary = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniPrimary

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitPalette
** Purpose: Creates a primary palette if necessary
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitPalette ()
{
    HRESULT             hResult;
    HDC                 hdc;
    DWORD               ii;
    DWORD               cbSize;
    DWORD               dwFlags;
    DDSURFACEDESC       ddsd;

    // Destroy old palette
    FiniPalette ();

    // Make sure we are properly intialized
    // for this to work
    if ((! lpDD2) || (! lpddsPrimary))
    {
        // Error, need a valid DD interfac and a primary surface to continue
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
    }

    // Get primary surface caps
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hResult = lpddsPrimary->GetSurfaceDesc(&ddsd);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        return hResult;
    }

    // Make sure it is a palettized surface
    if (! isPalettized (&(ddsd.ddpfPixelFormat)))
    {
        // Success, primary isn't palettized
// So we don't need to create a palette
        return DD_OK;
    }

    // Create and save System palette
    hdc = GetDC (NULL);
    cPalette = GetDeviceCaps (hdc, SIZEPALETTE);
    if (cPalette)
    {
        if (cPalette > 256)
            cPalette = 256;

        // Get memory for system palette
        lppeSystem = new PALETTEENTRY[cPalette];
        if (! lppeSystem)
        {
ReleaseDC (NULL, hdc);

            // Error, not enough memory
hResult = APPERR_OUTOFMEMORY;
REPORTERR (hResult);
goto lblCLEANUP;
        }

// Get Memory for current palette
lppeCurr = new PALETTEENTRY[cPalette];
if (! lppeCurr)
{
ReleaseDC (NULL, hdc);

            // Error, not enough memory
hResult = APPERR_OUTOFMEMORY;
REPORTERR (hResult);
goto lblCLEANUP;

}

        // Save system palette
        GetSystemPaletteEntries (hdc, 0, cPalette, lppeSystem);

        // Copy system palette to temporary values
        cbSize = cPalette * sizeof (PALETTEENTRY);
        CopyMemory (lppeCurr, lppeSystem, cbSize);
    }
ReleaseDC (NULL, hdc);

    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED1)
    {
        dwFlags = DDPCAPS_1BIT;

        // Only 2 palette entries, we need them all
        for (ii = 0; ii < 2; ii++)
            lppeCurr[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED2)
    {
        // Only 4 palette entries, we need them all
        for (ii = 0; ii < 4; ii++)
            lppeCurr[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_2BIT;
    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4)
    {
        // Only 16 palette entries, we will save black and white
        // and keep the rest for ourselves.

        lppeCurr[0].peFlags = D3DPAL_READONLY;
        lppeCurr[15].peFlags = D3DPAL_READONLY;

        for (ii = 1; ii < 15; ii++)
            lppeCurr[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_4BIT;
    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
    {
        // 256 palette entries, we can afford to be nice
        // and save the first 10 and last 10 palette entries
        // for system use
        for (ii = 0; ii < 10; ii++)
        {
            lppeCurr[ii].peFlags = D3DPAL_READONLY;
            lppeCurr[246+ii].peFlags = D3DPAL_READONLY;
        }

        for (ii = 10; ii < 246; ii++)
            lppeCurr[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_8BIT;
    }
    else
    {
        // Error, programming (unknown palette type)
hResult = APPERR_GENERIC;
REPORTERR (hResult);
goto lblCLEANUP;
    }

    // Create Primary Palette
    hResult = lpDD2->CreatePalette (dwFlags,
                                    lppeCurr,
                                    &lpddpPalette,
                                    NULL);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Attach palette to primary surface
    hResult = lpddsPrimary->SetPalette (lpddpPalette);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Success
    return DD_OK;

lblCLEANUP:
// Failure

// Cleanup
FiniPalette ();
return hResult;
} // D3DWindow::InitPalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniPalette
** Purpose: Destroys primary palette
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniPalette (void)
{
    // Note:  Should we Detach Palette object from surfaces
    // No way to do this that I know of...

    // Cleanup up DD Palette object
    if (lpddpPalette)
    {
        lpddpPalette->Release ();
        lpddpPalette = NULL;
    }

// Cleanup Current Palette
if (lppeCurr)
{
delete [] lppeCurr;
lppeCurr = NULL;
}

    // Cleanup System Palette
    if (lppeSystem)
    {
        // Note:  Should we try and restore system palette here ?!?

        // Destroy system palette
        delete [] lppeSystem;
        lppeSystem = NULL;
    }

cPalette = 0;

// Success
return DD_OK;
} // End FiniPalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::CreateRender
** Purpose: Creates the rendering surface and D3D device
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::CreateRender (LPGUID lpD3DGuid)
{
HRESULT hResult;
LPD3DDevInfo lpDevNew;
LPDDModeInfo lpModeNew;

// Check Initialization
if ((! lpCurrDriver) || (! lpCurrMode))
{
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

// Validate D3D Device
lpDevNew = ValidateDevice (lpCurrDriver, lpD3DGuid, NULL);
if (! lpDevNew)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

DWORD w, h, bpp, refresh;
lpCurrMode->GetMode (w, h, bpp, refresh);

// Validate Mode with this D3D Device
lpModeNew = ValidateMode (lpCurrDriver, w, h, bpp, 0, lpDevNew);
if (! lpModeNew)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

// Do we need to Change the Mode as well
// to stay in synch with the new D3D device ?!?
if (lpModeNew != lpCurrMode)
{
// Save old mode
LPDDModeInfo lpModeOld = lpCurrMode;

// Try to switch modes
lpCurrMode = lpModeNew;
hResult = InitPrimary ();
if (FAILED (hResult))
{
// Try to restore old mode and exit with error
lpCurrMode = lpModeOld;
InitPrimary ();
return hResult;
}

// Successfully switched modes
}

// Save new D3D device
lpCurrDevice = lpDevNew;

// Create new Render surface (using new Device)
hResult = InitRender ();
if (FAILED (hResult))
return hResult;

// Success
return DD_OK;
}

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitRender
** Purpose: Creates the rendering surface and D3D device
** Notes:
**
**1.  Catch 22:  To creating a D3D device you need it's render surface
**First.  But in order to create a render surface properly you need
**      to know the D3D device desc characteristics first.   Fortunately we
**already have this information.  Because we store it awy in the
**Driver Manager.
**
**  2.  This same catch 22 prevented us from enumerating Texture formats
**      easily in the Driver Manager.  So, we do it here once we have a
**valid D3D device.
**
** Basic Algorithm:
**1. Get pointer to back buffer and use as render surface
**  2. Create Z-buffer (optional)
**3. Create D3D Device (enumerate Texture Formats)
**4. Create Viewport
**5. InitScene (optional)
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitRender (void)
{
    HRESULT         hResult;
    DWORD           dwMemType;
    LPD3DDEVICEDESC lpDeviceDesc;
    DDSURFACEDESC   ddsd;
    DDSCAPSddscaps;
    DWORD           dwWidth, dwHeight, dwBPP;

// Check Initialization
    if ((! hWindow) || (! IsWindow (hWindow)) ||
(! lpCurrDevice) || (! lpCurrMode) ||
(! lpDD2) || (! lpD3D) || (! lpddsPrimary))
    {
        // Error, Not initialized properly before calling this method
hResult = APPERR_NOTINITIALIZED;

REPORTERR (hResult);
        return hResult;
    }

//
// Step 1.  Grab the Render surface (back buffer)
//from the Primary surface (front buffer)
//
   ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
hResult = lpddsPrimary->GetAttachedSurface (&ddscaps, &lpddsRender);
   if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

//
// Step 2.Create and attach Z-buffer (optional)
//

// Get D3D Device description
if (lpCurrDevice->isHardware ())
{
// Hardware device
// Z-buffer has to be in Video memory
lpDeviceDesc = &(lpCurrDevice->d3dHalDesc);
dwMemType = DDSCAPS_VIDEOMEMORY;
}
else
{
// Software device
// Z-Buffer has to be in System memory
lpDeviceDesc = &(lpCurrDevice->d3dHelDesc);
dwMemType = DDSCAPS_SYSTEMMEMORY;
}

// Should we create a Z-buffer ?!?
    if ((isCreateZBuffer ()) && (lpDeviceDesc) &&
        (0L != lpDeviceDesc->dwDeviceZBufferBitDepth))
    {
// Get Z-buffer surface info (w, h, bpp, video vs. system memory)
ddsd.dwSize = sizeof(ddsd);
hResult = lpddsPrimary->GetSurfaceDesc (&ddsd);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}
dwWidth   = ddsd.dwWidth;
dwHeight  = ddsd.dwHeight;
dwBPP  = FlagsToBitDepth (lpDeviceDesc->dwDeviceZBufferBitDepth);

        // Create the z-buffer.
        ZeroMemory (&ddsd, sizeof(ddsd));
        ddsd.dwSize            = sizeof(ddsd);
        ddsd.dwFlags           = DDSD_CAPS   |
                                 DDSD_WIDTH  |
                                 DDSD_HEIGHT |
                                 DDSD_ZBUFFERBITDEPTH;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_ZBUFFER | dwMemType;
        ddsd.dwWidth           = dwWidth;
        ddsd.dwHeight          = dwHeight;
        ddsd.dwZBufferBitDepth = dwBPP;
        hResult = lpDD2->CreateSurface (&ddsd, &lpddsZBuff, NULL);
        if (FAILED(hResult))
        {
            REPORTERR (hResult);

            // Note: we may be able to continue without a z buffer
// So don't exit
        }
else
{
// Attach Z-buffer to rendering surface
hResult = lpddsRender->AddAttachedSurface (lpddsZBuff);
if (FAILED (hResult))
{
REPORTERR (hResult);

if (lpddsZBuff)
{
lpddsZBuff->Release ();
lpddsZBuff = NULL;
}

// Note: we may be able to continue without a z buffer
// So don't exit
}
//else
//{
// Note:  We could actually release the reference to the
//        Z-Buffer here, if we don't need a pointer to it anymore.
//        The back-buffer has an attachment to it now and it
//        won't go away until the Back-buffer does.
// lpddsZBuff->Release ();
// lpddsZBuff = NULL;
//}
}
    }

    //
    // Step 4.  Create the D3D device interface
//
hResult = lpD3D->CreateDevice (lpCurrDevice->guid,
    lpddsRender,
    &lpd3dDevice);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Enumerate all Texture formats associated with this D3D device
hResult = lpCurrDevice->LoadFormats (lpd3dDevice);
if (FAILED (hResult))
{
// Error, no texture formats
// Hope we can run OK without textures
}

// Mark as valid
turnValidRenderOn ();

//
// Step 5.  Create the viewport
//
hResult = InitViewport ();
if (FAILED (hResult))
return hResult;

//
// Step 6.Allow Scene to create all objects dependent on us
//
if (lpd3dScene)
hResult = lpd3dScene->Attach ();

    // Success
    return DD_OK;
} // End D3DWindow::InitRender

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniRender
** Purpose: Destroys the Rendering surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniRender (void)
{
// Allow Scene to cleanup all objects dependent on us
if (lpd3dScene)
lpd3dScene->Detach ();

// Cleanup viewport
FiniViewport ();

// Mark as invalid
turnValidRenderOff ();

    // Release D3D Device
    if (lpd3dDevice)
    {
        lpd3dDevice->Release ();
        lpd3dDevice = NULL;
    }

    // Release Z Buffer
    if (lpddsZBuff)
    {
// Detach Z-Buffer from back buffer
if (lpddsRender)
lpddsRender->DeleteAttachedSurface (0L, lpddsZBuff);

// Release Z-Buffer
        lpddsZBuff->Release ();
        lpddsZBuff = NULL;
    }

    // Release rendering surface
    if (lpddsRender)
    {
        lpddsRender->Release ();
        lpddsRender = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniRender

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::InitViewport
**  Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitViewport (void)
{
HRESULT hResult;

// Check Initialization
if ((! lpD3D) || (! lpd3dDevice))
{
// Error, Not properly initialized before calling this method
hResult = APPERR_NOTINITIALIZED;
        REPORTERR (hResult);
        return hResult;
}

// Create Viewport
    hResult = lpD3D->CreateViewport (&lpd3dViewport, NULL);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Attach viewport to D3D device
    hResult = lpd3dDevice->AddViewport (lpd3dViewport);
    if (FAILED (hResult))
    {
lpd3dViewport->Release ();
lpd3dViewport = NULL;

        REPORTERR (hResult);
        return hResult;
    }

// Set up Initial Viewport parameters
hResult = UpdateViewport ();
    if (FAILED (hResult))
{
lpd3dDevice->DeleteViewport (lpd3dViewport);
lpd3dViewport->Release ();
lpd3dViewport = NULL;

        return hResult;
}

// Mark as valid
turnValidViewportOn ();

/// Success
return DD_OK;
} // End D3DWindow::InitViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::FiniViewport
**  Purpose:    Cleanup viewport
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniViewport (void)
{
// Mark as invalid
turnValidViewportOn ();

// Release D3D viewport
    if (lpd3dViewport)
    {
lpd3dDevice->DeleteViewport (lpd3dViewport);
        lpd3dViewport->Release ();
        lpd3dViewport = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::UpdateViewport
**  Purpose:    Keeps viewport updated with current window size
**  Notes:
**
**1. The viewport construction here assumes that you are rendering
**Triangles using the D3DVERTEX and D3DIM is doing Transform,
**lighting, and rasterization for you.
**
**2. If you are rendering triangles using D3DTLVERTEX and doing your
**   own transform and lighting then you need to setup the viewport
**   differently.   As follows:
**
**      // Replace the following values below:
**dvClipX= 0.0f;
**dvClipY= 0.0f;
**dvClipWidth= dwSurfW;
**dvClipHeight= dvSurfH;
**
**  3. This perserves the aspect ratio.  If you don't need or want to
**     perserve the aspect ratio then set inv_aspect = 1.0 below and
**     work this constant through the rest of the viewport setup.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::UpdateViewport (void)
{
    HRESULThResult;
    D3DVIEWPORT2d3dViewport;
DWORDdwSurfW, dwSurfH;

    // Check Parameters
if ((! lpd3dDevice) || (! lpd3dViewport))
{
// Not properly initialized before calling this method
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

// Get Surface Width and Height
dwSurfW = abs (rSurf.right - rSurf.left);
dwSurfH = abs (rSurf.bottom - rSurf.top);

float inv_aspect;

if (dwSurfW)
inv_aspect = (float)dwSurfH/(float)dwSurfW;
else
                inv_aspect = 1.0f;

    // Update Viewport
    ZeroMemory (&d3dViewport, sizeof(d3dViewport));
    d3dViewport.dwSize= sizeof(d3dViewport);     // Always set size of structure!!!
    d3dViewport.dwX= 0UL;
    d3dViewport.dwY= 0UL;
    d3dViewport.dwWidth= dwSurfW;
    d3dViewport.dwHeight= dwSurfH;
    d3dViewport.dvClipX= -1.0f;
    d3dViewport.dvClipY= inv_aspect;
    d3dViewport.dvClipWidth= 2.0f;
    d3dViewport.dvClipHeight = 2.0f * inv_aspect;
    d3dViewport.dvMinZ= 0.0f;
    d3dViewport.dvMaxZ= 1.0f;

// Update Viewport
    hResult = lpd3dViewport->SetViewport2 (&d3dViewport);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Update D3D device to use this viewport
    hResult = lpd3dDevice->SetCurrentViewport (lpd3dViewport);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

    // Success
    return DD_OK;
} // End D3DWindow::UpdateViewport

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::DrawFrame
** Purpose: Paints current surface to window
** Notes:
**
**1. Full screen mode - we render to the back buffer
**   and then flip the front and back buffers to make the
**   changes visible
**
**  2. Windowed mode - We blt the render surface onto the primary surface
**
**  3. This routine is not written for optimal performance
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::DrawFrame (void)
{
    HRESULT hResult = DD_OK;

// Check Initialization
    if (! isValid ())
    {
        // Error, not properly initialized
hResult = APPERR_NOTINITIALIZED;
//REPORTERR (hResult);
        return hResult;
    }

    if (isPaused ())
    {
        // Don't draw, if paused
        return DD_OK;
    }

    // Paint until we truly succeed or error out
    while (TRUE)
    {
// Render D3D Scene
if (lpd3dScene)
hResult = lpd3dScene->Render ();

if (SUCCEEDED (hResult))
{
// Flip Front and Back buffers (Primary, Render)
    hResult =lpddsPrimary->Flip (lpddsRender, 1);

// Did it work ?!?
if (SUCCEEDED (hResult))
{
// Success, exit
return hResult;
}
}

        // Check if busy or drawing
        if ((DDERR_SURFACEBUSY == hResult) ||
            (DDERR_WASSTILLDRAWING == hResult))
        {
            // Try again
            continue;
        }

        // Check for lost surfaces
        while (DDERR_SURFACELOST == hResult)
        {
            // Restore surfaces
            hResult = Restore ();
        }

        // Check for real error
        if (FAILED (hResult))
        {
            // Error,
            REPORTERR (hResult);
            return hResult;
        }
    }

    // Success
    return DD_OK;
} // End D3DWindow::DrawFrame

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Move
** Purpose: Moving full-screen windows not supported
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Move (long x, long y)
{
// Do nothing

    // Success
    return DD_OK;
} // D3DWindow::Move

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Resize
** Purpose: Resizes window
** Notes:Resizing Fullscreen windows not supported.
**I.E. The Back buffer needs to stay the same size as the front
**Buffer.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Resize (DWORD dwWidth, DWORD dwHeight)
{
// Do nothing

// Success
return DD_OK;
} // End D3DWindow::Resize

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::RealizePalette
**  Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::RealizePalette (void)
{
HRESULT hResult;

//
    // Realizing the palette using DirectDraw is quite different
    // from GDI. To realize the palette we call SetPalette()
    // each time our application is activated.
    //
    // NOTE: DirectDraw spots the fact that the new palette is the
    // same as the old one and so does not increase the reference
    // count of the palette.
    //

if ((lpddsPrimary) && (lpddpPalette))
{
hResult = lpddsPrimary->SetPalette (lpddpPalette);
if (FAILED (hResult))
        {
            REPORTERR (hResult);
            return hResult;
        }
}

// Success
return DD_OK;
} // End D3DWindow::RealizePalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::toGDI
** Purpose: Setups for drawing to GDI surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::toGDI (void)
{
HRESULT hResult;

// Step 1. Restore system palette (optional)
if (lpddpPalette)
{
// Save the current palette
hResult = lpddpPalette->GetEntries (0, 0, cPalette, lppeCurr);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

// Restore the system palette into our device
hResult = lpddpPalette->SetEntries (0, 0, cPalette, lppeSystem);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}
}

// Step 2. Flip to GDI Surface
if (lpDD2)
{
hResult = lpDD2->FlipToGDISurface ();
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}
}

// Step 3.  Force window to redraw itself (on GDI surface)
if ((hWindow) && (IsWindow (hWindow)))
{
DrawMenuBar (hWindow);
RedrawWindow (hWindow, NULL, NULL, RDW_FRAME);
}

// Success
return DD_OK;
} // End D3DWindow::toGDI

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::fromGDI
** Purpose: Restores from GDI
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::fromGDI (void)
{
HRESULT hResult;

// Restore current palette
if (lpddpPalette)
{
hResult = lpddpPalette->SetEntries (0, 0, cPalette, lppeCurr);
if (FAILED (hResult))
return hResult;
}

// Success
return DD_OK;
} // End D3DWindow::fromGDI

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Pause
** Purpose: Pause any work on DD/D3D resources
** Notes:
**
** For Fullscreen apps we need to setup the
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Pause (BOOL fPause)
{
HRESULT hResult;

    // Turning pausing on/off ?!?
    if (fPause)
    {
// Increment pause semaphore
        dwPaused++;

// Are we pausing for the first time
        if (dwPaused == 1L)
        {
// Flip to GDI surface
hResult = toGDI ();
if (FAILED (hResult))
return hResult;
        }

    }
    else
    {
        if (dwPaused == 0L)
        {
            // Programmer Error, already unpaused
hResult = APPERR_GENERIC;
REPORTERR (hResult);
            return hResult;
        }

// Are we unpausing for the last time ?!?
        if (dwPaused == 1L)
        {
// Restore from GDI surface
hResult = fromGDI ();
if (FAILED (hResult))
return hResult;
        }

        // Decrement pause semaphore
        dwPaused--;
    }

    // Success
    return DD_OK;
} // D3DWindow::Pause

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::AttachScene
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::AttachScene (LPD3DScene lpNewScene)
{
// Check parameters
if (! lpNewScene)
{
return DDERR_INVALIDPARAMS;
}

// Save Scene pointer
lpd3dScene = lpNewScene;

// Inform scene of our existence
lpd3dScene->Init (this);

// Allow scene to create any objects dependent on us
if (isValid ())
{
lpd3dScene->Attach ();
}

// Success
return DD_OK;
} // End D3DWindow::AttachScene

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::DetachScene
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::DetachScene (void)
{
// Cleanup Scene
if (lpd3dScene)
{
lpd3dScene->Detach ();
lpd3dScene->Fini ();
lpd3dScene = NULL;
}

// Success
return DD_OK;
} // End D3DWindow::DetachScene

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Restore
** Purpose: Restores lost surfaces
** Note:    Eventually we should inform the user somehow that
**          they need to redraw the surface but for now punt
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Restore (void)
{
    HRESULT hResult;

// Check Initialization
    if (! isValid ())
{
// Error, not properly initialized before calling this method
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
}

    // Restore Primary Surface
    if (lpddsPrimary)
    {
hResult = lpddsPrimary->IsLost ();
if (hResult != DD_OK)
{
hResult = lpddsPrimary->Restore ();
if (FAILED (hResult))
return hResult;
}
    }

    // Restore Z Buffer
    if (lpddsZBuff)
    {
hResult = lpddsZBuff->IsLost ();
if (hResult != DD_OK)
{
        hResult = lpddsZBuff->Restore ();
    if (FAILED (hResult))
    return hResult;
}
    }

    // Restore Rendering surface
    if (lpddsRender)
    {
hResult = lpddsRender->IsLost ();
if (hResult != DD_OK)
{
        hResult = lpddsRender->Restore ();
    if (FAILED (hResult))
    return hResult;
}
    }

// Allow D3D Scene to restore any surfaces
if (lpd3dScene)
{
hResult = lpd3dScene->Restore ();
if (FAILED (hResult))
return hResult;
}

    // Success
    return DD_OK;
} // End D3DWindow::Restore

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetSurfaceRect
** Purpose: Get bounding rectangle of surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetSurfaceRect (RECT & rSurface)
{
HRESULT hResult;

    if (! isValid ())
    {
// Error, not properly initialized before calling this method
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
    }

    // Return Surface rectangle
    rSurface = rSurf;

    // Success
    return DD_OK;
} // D3DWindow::GetSurfaceRect

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetPrimaryRect
** Purpose: Get bounding rectangle of primary surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetPrimaryRect (RECT & rPrimary)
{
HRESULT hResult;

    if (! isValid ())
    {
// Error, not properly initialized before calling this method
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
    }

    // Return Primary rectangle
    rPrimary = rPrim;

    // Success
    return DD_OK;
} // GetPrimaryRect

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::ChangeDesktop
**  Purpose:    The Primary Desktop has changed Modes
**Notes:
**
**1.  Since we are a fullscreen app, this shouldn't effect us
**until we restore the desktop mode.  Since DirectDraw should
**be tracking this anyway.  We can just ignore doing anything
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDesktop (void)
{
// Do nothing

// Success
return DD_OK;
} // D3DInfo::ChangeDesktop

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ChangeDriver
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDriver (
LPGUID lpGuidDD,
LPD3DDevInfo lpDevHint,
LPDDModeInfo lpModeHint)
{
HRESULT hResult;
LPGUID lpGuidD3D;
LPDDDrvInfo  lpDrvNew,  lpDrvOld;
LPDDModeInfo lpModeNew, lpModeOld;
LPD3DDevInfo lpDevNew, lpDevOld;
DWORD w, h, bpp, refresh;

// Get New Driver
lpDrvNew = ValidateDriver (lpGuidDD);
if (! lpDrvNew)
{
// Error, invalid DD Guid
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
        return hResult;
}

// Get requested D3D device
if (lpDevHint)
lpGuidD3D = &(lpDevHint->guid);
else if (lpCurrDevice)
lpGuidD3D = &(lpCurrDevice->guid);
else
lpGuidD3D = NULL;

// Get requested mode
if (lpModeHint)
lpModeHint->GetMode (w, h, bpp, refresh);
else
{
// Default to 640 x 480 x 16
w= 640;
h= 480;
bpp= 16;
refresh = 0;
}

// Get new device and mode compatible with this driver
if (! GetFullscreenMode (lpDrvNew, lpGuidD3D, w, h, bpp, refresh,
 &lpModeNew, &lpDevNew))
{
// Error, unable to find a valid D3D Device
// and Mode that work with this driver.
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}

// Save old defaults
lpDrvOld= lpCurrDriver;
lpModeOld= lpCurrMode;
lpDevOld= lpCurrDevice;

// Destroy almost everything
Fini ();

// Set new defaults
lpCurrDriver = lpDrvNew;
lpCurrMode   = lpModeNew;
lpCurrDevice = lpDevNew;

// Re-create almost everything based on new driver, device, and mode
hResult = Init ();
if (FAILED (hResult))
{
// Try to restore old defaults
Fini ();

lpCurrDriver = lpDrvOld;
lpCurrMode   = lpModeOld;
lpCurrDevice = lpDevOld;

Init ();
return hResult;
}

// Notify the window of a successful change in Driver
SendMessage (hWindow, D3DWIN_CHANGED_DRIVER, 0, 0);

// Success
    return DD_OK;
} // End D3DWindow::ChangeDriver

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ChangeMode
** Purpose: Change the mode
**
**Basic Algorithm:
**
**  1.  Validate new mode request
**  2.  Validate that the D3D device is compatible
**  3.  Destroy old mode
**  4.  Create new mode
**
**- This can be complicated by the fact that the new mode is not
**    compatible with the current D3D device, in which case we need
**    to choose a new D3D device that will work with this mode.
**
**  - We don't normally allow mode changes for windowed mode.
**- However the desktop mode could have changed underneath us,
**  So make sure the desktop mode and our current mode match.
**  If not, then change to the current desktop mode.
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeMode (
DWORD w,// Mode Width
DWORD h,// Mode Height
DWORD bpp,// Mode Bits Per Pixel
DWORD refresh)// Optional:  Mode refresh rate
{
HRESULT hResult;
LPDDDrvInfo  lpOldDrv;
LPDDModeInfo lpOldMode, lpNewMode;
LPD3DDevInfo lpOldDev, lpNewDev;

// Check Initialization
if ((! hWindow) || (! IsWindow (hWindow)))
{
// Error, Not properly initialized
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
return hResult;
}

lpOldDrv  = lpCurrDriver;
lpOldMode = lpCurrMode;
lpOldDev  = lpCurrDevice;

//
// Step 1. Get New Mode
//

// Find new mode corresponding to w, h, bpp
lpNewMode = lpOldDrv->FindMode (w, h, bpp, 0, NULL);
if (! lpNewMode)
{
// Error, Invalid Mode parameters
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

//
// Step 2.   Check if Device needs to be changed as well
//
if (lpNewMode->ModeSupported (lpOldDev))
{
lpNewDev = NULL;
}
else
{
LPD3DDevInfo lpNextBest;
lpNewDev = lpOldDrv->FindDeviceSupportsMode (&lpOldDev->guid,
 lpNewMode,
 &lpNextBest);
if (! lpNewDev)
{
if (! lpNextBest)
{
// No D3D device is compatible with this new mode
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}
lpNewDev = lpNextBest;
}
}

//
// Step 3.Destroy current Mode
//
FiniRender ();
FiniPrimary ();
//  FiniFullscreenMode ();// Don't do this => unnecessary mode switch

//
// Step 4.  Create new mode
//
lpCurrMode = lpNewMode;
if (lpNewDev)
lpCurrDevice = lpNewDev;

// Change Mode
hResult = InitFullscreenMode ();
if (FAILED (hResult))
return hResult;

// Create Primary Surface
hResult = InitPrimary ();
if (FAILED (hResult))
{
// Try to restore old mode
lpCurrMode = lpOldMode;
lpCurrDevice = lpOldDev;

InitFullscreenMode ();
InitPrimary ();
InitRender ();

return hResult;
}

// Create Render surface
hResult = InitRender ();
if (FAILED (hResult))
{
FiniPrimary ();
//  FiniFullscreenMode ();// Unnecessary mode switch

// Try to restore old mode
lpCurrMode = lpOldMode;
lpCurrDevice = lpOldDev;

InitFullscreenMode ();
InitPrimary ();
InitRender ();

return hResult;
}

// Notify the window of a successful change in Mode
SendMessage (hWindow, D3DWIN_CHANGED_MODE, 0, 0);

// Success
    return DD_OK;
} // End D3DWindow::ChangeMode

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ChangeDevice
** Purpose: Change to a new D3D device (RAMP, RGB, Hardware, etc.)
** Notes:
**
**  Algorithm:
**- Destroy the current D3D Device (and associated surfaces)
**- Recreate a new D3D device from the new GUID
**
**  1.The new D3D Device may not be supported by the current DD Device.
**  2.  The new D3D Device may not be compatible with the current Mode
**- Since we are fullscreen, just pick a new mode that is compatible
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDevice (
LPGUID lpD3DGuid,
LPDDModeInfo lpModeHint)
{
    HRESULT         hResult;
LPDDDrvInfolpDrvOld;
LPDDModeInfo    lpModeNew, lpModeOld;
LPD3DDevInfolpDevNew, lpDevOld;

// Check Parameters
if (! lpD3DGuid)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
        return hResult;
}

// Check Initialization
    if (! isValid () || (! lpddsRender))
{
hResult = APPERR_NOTINITIALIZED;
REPORTERR (hResult);
        return hResult;
}

// Save Original State
lpDrvOld= lpCurrDriver;
lpModeOld   = lpCurrMode;
lpDevOld= lpCurrDevice;

// Verify new D3D device belongs to current DD driver
lpDevNew = lpDrvOld->FindDevice (lpD3DGuid, NULL);
if (! lpDevNew)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
        return hResult;
}

//
//Step 1. Verify new D3D device is supported with current mode
//
if (lpModeHint)
lpModeNew = lpModeHint;
else
lpModeNew = lpModeOld;
if (! lpModeNew->ModeSupported (lpDevNew))
{
// We are a full screen app, so we can do what we want
// Pick a new mode that is compatible with this device
LPDDModeInfo lpNextBest;
DWORD w, h, bpp, refresh;

lpModeNew->GetMode (w, h, bpp, refresh);

lpModeNew = lpDrvOld->FindModeSupportsDevice (w, h, bpp, 0,
  lpDevNew,
  &lpNextBest);

if (! lpModeNew)
{
if (! lpNextBest)
{
// Error , no compatible mode found!!!
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}
lpModeNew = lpNextBest;
}
}
if (lpModeNew == lpModeOld)
lpModeNew = NULL;

//
//Step 2.  Destroy Old D3D Device (and mode)
//
FiniRender ();
if (lpModeNew)
{
FiniPrimary ();
// FiniFullscreenMode ();// Unnecessary mode switch
}

//
//Step 3. Create new D3D Device (new mode optional)
//

// Set new D3D device (and mode)
if (lpModeNew)
lpCurrMode = lpModeNew;
lpCurrDevice = lpDevNew;

// Create new mode, if necessary
if (lpModeNew)
{
// Change Mode
hResult = InitFullscreenMode ();
if (FAILED (hResult))
{
// Try to restore original mode and device
lpCurrDevice = lpDevOld;
lpCurrMode   = lpModeOld;

InitFullscreenMode ();
InitPrimary ();
InitRender ();

return hResult;
}

// Create Primary
hResult = InitPrimary ();
if (FAILED (hResult))
{
// Try to restore original mode and device
lpCurrDevice = lpDevOld;
lpCurrMode   = lpModeOld;

InitFullscreenMode ();
InitPrimary ();
InitRender ();

return hResult;
}
}

// Create new D3D Device
hResult = InitRender ();
if (FAILED (hResult))
{
// Try to restore original mode and device
if (lpModeNew)
lpCurrMode = lpModeOld;
lpCurrDevice = lpDevOld;

if (lpModeNew)
{
FiniPrimary ();
InitFullscreenMode ();
InitPrimary ();
}

InitRender ();

// Return Error
REPORTERR (hResult);
return hResult;
}

// Notify the window of a successful change in device
SendMessage (hWindow, D3DWIN_CHANGED_DEVICE, 0, 0);

    // Success
    return DD_OK;
} // End D3DWindow::ChangeDevice

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


D3DWIN.H

#ifndef D3DWIN_H
#define D3DWIN_H
/*
**-----------------------------------------------------------------------------
**  Name:       D3DWin.h
**  Purpose:    Sample D3D framework
**
**Copyright (C) 1995 - 1997 Microsoft Corporation. All Rights Reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Includes
**-----------------------------------------------------------------------------
*/
#include "Common.h"
#include "DrvMgr.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

// Windows messages
#define D3DWIN_GET_VALID    (WM_USER+1000)
#define D3DWIN_GET_POINTER  (WM_USER+1001)
#define D3DWIN_GET_SURFACE  (WM_USER+1002)

#define D3DWIN_INIT  (WM_USER+2000)
#define D3DWIN_FINI  (WM_USER+2001)
#define D3DWIN_CHANGED_DRIVER  (WM_USER+2002)
#define D3DWIN_CHANGED_MODE  (WM_USER+2003)
#define D3DWIN_CHANGED_DEVICE  (WM_USER+2004)

#define D3DWIN_MIN_SIZE     64

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

class D3DScene;// Prevent having to include D3DScene.h
typedef D3DScene * LPD3DScene;

class D3DWindow;                    // Forward declaration
typedef D3DWindow * LPD3DWindow;

/*
**-----------------------------------------------------------------------------
**  Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow
**  Purpose:    Encapsulates D3D rendering info for a window
**-----------------------------------------------------------------------------
*/

class D3DWindow {
protected:
//
    // Flags
//
    enum {
DWF_FULLSCREEN= 0x00000001,
        DWF_VISIBLE= 0x00000002,
        DWF_ZBUFFER= 0x00000004,
DWF_ACTIVE= 0x00000008,
    } Attributes;

    enum {
        DWF_VALID_INTERFACE = 0x00000001,
DWF_VALID_FULLSCREEN= 0x00000002,
DWF_VALID_PRIMARY= 0x00000004,
DWF_VALID_RENDER    = 0x00000008,
DWF_VALID_VIEWPORT  = 0x00000010,
DWF_VALID_SCENE= 0x00000020,

DWF_VALID    = 0x0000001F, // INTERFACE | FULLSCREEN | PRIMARY | RENDER | VIEWPORT
    } Validates;

public:

//
    // Creation Methods
//
    D3DWindow (void);
    ~D3DWindow (void);

HRESULT D3DWindow::Create (
HWND   hWnd,
LPGUID lpDDGuid= NULL,
DWORD  dwW= 0L,
DWORD  dwH= 0L,
DWORD  dwBPP= 0L,
DWORD  dwRefresh= 0L,
LPGUID lpGuidD3D= NULL,
BOOL   fUseZBuffer= TRUE);
    HRESULT Init (void);
HRESULT Fini (void);

//
    // Window Methods
    //
    HRESULT DrawFrame (void);

    HRESULT Move (long x, long y);
    HRESULT Resize (DWORD w, DWORD h);

HRESULT RealizePalette (void);

HRESULT toGDI (void);
HRESULT fromGDI (void);

    BOOL    isPaused (void)             { return (dwPaused != 0); }
    HRESULT Pause (BOOL fOn);

BOOL    isFullscreen (void){ return ((fValid & DWF_VALID_FULLSCREEN) ? TRUE : FALSE); }

    HRESULT Restore (void );

// Window State
BOOL    isActive (void){ return ((fAttribs & DWF_ACTIVE) ? TRUE : FALSE); }
    voidturnActiveOn (void){ fAttribs |= DWF_ACTIVE; }
    voidturnActiveOff (void){ fAttribs &= ~DWF_ACTIVE; }

//
// Scene Methods
//
HRESULT   AttachScene (LPD3DScene lpNewScene);
HRESULT    DetachScene (void);

//
    // Driver Methods
    //
HRESULT ChangeDesktop (void);
    HRESULT ChangeDriver (LPGUID lpGuid, LPD3DDevInfo lpDevHint = NULL,
  LPDDModeInfo lpModeHint = NULL);
    HRESULT ChangeDevice (LPGUID lpGuid, LPDDModeInfo lpModeHint = NULL);
    HRESULT ChangeMode (DWORD w, DWORD h, DWORD bpp, DWORD refresh);

//
// Misc. Methods
//
BOOL isValid (void)         { return (((fValid & DWF_VALID) == DWF_VALID) ? TRUE : FALSE); }

    BOOL isCreateZBuffer (void) { return (fAttribs & DWF_ZBUFFER); }
    void createZBufferOn (void) { fAttribs |= DWF_ZBUFFER; }
    void createZBufferOff (void){ fAttribs &= ~DWF_ZBUFFER; }

//
// Member methods
//
HWND  GetWindow (void){ return hWindow; }
HCURSOR  GetCursor (void){ return hCursorOld; }
void  SetCursor (HCURSOR hNew){ hCursorOld = hNew; }

LPDDDrvInfo  GetDriverInfo (void){ return lpCurrDriver; }
LPDDModeInfo  GetModeInfo (void){ return lpCurrMode; }
LPD3DDevInfo  GetDeviceInfo (void){ return lpCurrDevice; }

LPGUID  GetDDGuid (void){ return ((lpCurrDriver) ? lpCurrDriver->GetGuid () : NULL); }
BOOL  GetModeInfo (DWORD & w, DWORD & h,
   DWORD & bpp, DWORD & refresh)
{
if (! lpCurrMode)
return FALSE;

lpCurrMode->GetMode (w, h , bpp, refresh);
return TRUE;
}
LPGUID  GetD3DGuid (void){ return ((lpCurrDevice) ? &(lpCurrDevice->guid) : NULL); }

LPDIRECTDRAW  GetDD (void){ return lpDD; }
LPDIRECTDRAW2 GetDD2 (void){ return lpDD2; }
LPDIRECT3D2   GetD3D (void){ return lpD3D; }

LPDIRECTDRAWSURFACE GetPrimary (void){ return lpddsPrimary; }
LPDIRECTDRAWSURFACE GetFrontBuffer (void){ return lpddsPrimary; }
LPDIRECTDRAWSURFACE GetBackBuffer (void){ return lpddsRender; }
LPDIRECTDRAWPALETTEGetPalette (void){ return lpddpPalette; }

LPDIRECTDRAWSURFACE GetRender (void){ return lpddsRender; }
LPDIRECTDRAWSURFACE GetZBuffer (void){ return lpddsZBuff; }
LPDIRECT3DDEVICE2   GetD3DDevice (void){ return lpd3dDevice; }
LPDIRECT3DVIEWPORT2GetViewport (void){ return lpd3dViewport; }

HRESULT GetSurfaceRect (RECT & rSurface);
    HRESULT GetPrimaryRect (RECT & rPrimary);

LPD3DScene GetScene (void){ return lpd3dScene; }

protected:

//
// Protected Methods
//

    HRESULT ValidateDefaults (void);

HRESULT CreateInterfaces (LPGUID lpGuidDD);
    HRESULT InitInterfaces (void);
    HRESULT FiniInterfaces (void);

HRESULT InitWindow (void);
HRESULT FiniWindow (void);

HRESULT InitFullscreenMode (void);
HRESULT FiniFullscreenMode (void);

HRESULT InitPrimary (void);
    HRESULT FiniPrimary (void);

    HRESULT InitPalette (void);
    HRESULT FiniPalette (void);

    HRESULT CreateRender (LPGUID lpD3DGuid);
HRESULT InitRender (void);
    HRESULT FiniRender (void);

HRESULT InitViewport (void);
HRESULT FiniViewport (void);
HRESULT UpdateViewport (void);

// Track initialization process
    void turnValidInterfaceOn (void){ fValid |= DWF_VALID_INTERFACE; }
    void turnValidInterfaceOff(void){ fValid &= ~DWF_VALID_INTERFACE; }

    void turnValidPrimaryOn (void){ fValid |= DWF_VALID_PRIMARY; }
    void turnValidPrimaryOff (void){ fValid &= ~DWF_VALID_PRIMARY; }

    void turnValidFullscreenOn (void){ fValid |= DWF_VALID_FULLSCREEN; }
    void turnValidFullscreenOff (void){ fValid &= ~DWF_VALID_FULLSCREEN; }

    void turnValidRenderOn (void){ fValid |= DWF_VALID_RENDER; }
    void turnValidRenderOff(void){ fValid &= ~DWF_VALID_RENDER; }

    void turnValidViewportOn (void){ fValid |= DWF_VALID_VIEWPORT; }
    void turnValidViewportOff(void){ fValid &= ~DWF_VALID_VIEWPORT; }

void turnValidSceneOn (void){ fValid |= DWF_VALID_SCENE; }
void turnValidSceneOff (void){ fValid &= ~DWF_VALID_SCENE;}

    // Flags
    BOOL isValidDefaults (void){ return ((lpCurrDriver && lpCurrMode && lpCurrDevice) ? TRUE : FALSE); }
    BOOL isValidInterface (void){ return ((fValid & DWF_VALID_INTERFACE) ? TRUE : FALSE); }
    BOOL isValidFullscreen (void){ return ((fValid & DWF_VALID_FULLSCREEN) ? TRUE : FALSE); }
    BOOL isValidPrimary (void){ return ((fValid & DWF_VALID_PRIMARY) ? TRUE : FALSE); }
    BOOL isValidRender (void){ return ((fValid & DWF_VALID_RENDER) ? TRUE : FALSE); }
BOOL isValidViewport (void){ return ((fValid & DWF_VALID_VIEWPORT) ? TRUE : FALSE); }
BOOL isValidScene (void){ return ((fValid & DWF_VALID_SCENE) ? TRUE : FALSE); }

    HRESULT CalcRects (void);

//
// Protected Data
//
    DWORD           dwSize;             // Structure Size
    DWORD           fAttribs;           // Attribute Flags
    DWORD           fChecks;            // Programming flags
DWORDfValid;// Validation flags

    // Window Data
HWND            hWindow;            // Window handle
    DWORD           dwPaused;           // Paused Semaphore
HCURSORhCursorOld;// Old Cursor

    // Driver Defaults
LPDDDrvInfolpCurrDriver;// Current Driver
LPDDModeInfolpCurrMode;// Current Mode
LPD3DDevInfolpCurrDevice;// Current Device

// Interfaces
    LPDIRECTDRAW    lpDD;               // DirectDraw Interface
    LPDIRECTDRAW2   lpDD2;              // DirectDraw2 Interface
    LPDIRECT3D2     lpD3D;              // Direct3D2 Interface

    // Primary Surface
    LPDIRECTDRAWSURFACE  lpddsPrimary;  // Primary Surface
    LPDIRECTDRAWPALETTE  lpddpPalette;  // Primary Palette
DWORD cPalette;// Count of palette entries
    PALETTEENTRY *       lppeSystem;    // Saved System palette entries
PALETTEENTRY *       lppeCurr;// Current palette entries

    // Render Surface
    LPDIRECTDRAWSURFACE  lpddsRender;   // Rendering surface
    LPDIRECTDRAWSURFACE  lpddsZBuff;    // Z-Buffer surface
    LPDIRECT3DDEVICE2    lpd3dDevice;// D3D Device
LPDIRECT3DVIEWPORT2  lpd3dViewport;// D3D Viewport

    // Misc.
    RECT                rPrim;          // Current primary pos,size
    RECT                rSurf;          // Current surface pos,size

// D3D Scene
LPD3DScenelpd3dScene;// D3D Scene

// Friends
friendclass D3DScene;

}; // End D3DWindow

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // D3DWIN_H


DEBUG.CPP

/*
**-----------------------------------------------------------------------------
** Name:    Debug.cpp
** Purpose: Example debug code for D3D sample
** Notes:
**
** Copyright (C) 199 - 1997 Microsoft Corporation. All Rights Reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Includes
**-----------------------------------------------------------------------------
*/

#include "Debug.h"
#include "WinMain.h"
#include "WinProc.h"

/*
**-----------------------------------------------------------------------------
** Global variables
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
    DWORD   g_dwDebugLevel = DEBUG_ALWAYS;
    BOOL    g_fDebug = TRUE;
#else
    BOOL    g_fDebug = FALSE;
#endif

/*
**-----------------------------------------------------------------------------
** Local prototypes
**-----------------------------------------------------------------------------
*/

BOOL GetDDErrorString (HRESULT hResult, LPTSTR lpszError, DWORD cchError);

/*
**-----------------------------------------------------------------------------
** Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       dprintf
**  Purpose:Printf to debug output
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
void __cdecl dprintf (DWORD dwDebugLevel, LPCTSTR szFormat, ...)
{
    TCHAR   szBuffer[MAX_STRING];
    va_list va;

// Check if current debug level
    if (dwDebugLevel <= g_dwDebugLevel)
    return;

    lstrcpy (szBuffer, START_STR);
    va_start(va, szFormat);
    wvsprintf (szBuffer+lstrlen (szBuffer), szFormat, va);
    va_end(va);
    lstrcat (szBuffer, END_STR);
    OutputDebugString (szBuffer);
}
#endif // DEBUG

/*
**-----------------------------------------------------------------------------
** Name:    ReportDDError
** Purpose:
**-----------------------------------------------------------------------------
*/

void _cdecl ReportDDError (HRESULT hResult, LPCTSTR szFormat, ...)
{
    TCHAR   szMsg[MAX_STRING];
    TCHAR   szErr[MAX_STRING];
    TCHAR   szUnknown[] = TEXT("Unknown");
    DWORD   cchErr = sizeof(szErr)/sizeof(TCHAR);
    va_list va;

    // Check for Success Error code
    if (hResult == DD_OK)
        return;

    // Get DD/D3D error string
    szErr[0] = 0;
    GetDDErrorString (hResult, szErr, cchErr);
    wsprintf (szMsg, TEXT("DD/D3D Error = %s\r\n"), szErr);

    // Append the rest of the error message
    va_start( va, szFormat );
    wvsprintf (szMsg + lstrlen(szMsg), szFormat, va);
    va_end( va );

    // Dump to debugger
    DPF (DEBUG_ERROR, TEXT("%s"), szMsg);

//
// Define the following if you want an in your face Messagebox on errors
//
#ifdef DEBUG_PROMPTME
    HWND    hWindow;

    // Pause App
if (g_hMainWindow)
OnPause (g_hMainWindow, TRUE);

    // Display Error Message to user
    if (g_hMainWindow)
        hWindow = g_hMainWindow;
    else
        hWindow = NULL;

    MessageBox (hWindow, szMsg, g_szMainTitle, MB_OK | MB_APPLMODAL);

    // Unpause app
if (g_hMainWindow)
OnPause (g_hMainWindow, FALSE);
#endif

} // ReportDDError

/*
**-----------------------------------------------------------------------------
** Name:    GetDDErrorString
** Purpose: outputs a debug string to debugger
**-----------------------------------------------------------------------------
*/

BOOL GetDDErrorString (HRESULT hResult, LPTSTR lpszErrorBuff, DWORD cchError)
{
    DWORD  cLen;
    LPTSTR lpszError;
    TCHAR  szMsg[MAX_STRING];

    // Check parameters
    if (!lpszErrorBuff || !cchError)
    {
        // Error, invalid parameters
        return FALSE;
    }

    switch (hResult)
    {
    case DD_OK:
        // The request completed successfully.
        lpszError = TEXT("DD_OK");
        break;

    case DDERR_ALREADYINITIALIZED:
        // The object has already been initialized.
        lpszError = TEXT("DDERR_ALREADYINITIALIZED");
        break;

    case DDERR_BLTFASTCANTCLIP:
        // A DirectDrawClipper object is attached to a source surface
        // that has passed into a call to the IDirectDrawSurface2::BltFast method.
        lpszError = TEXT("DDERR_BLTFASTCANTCLIP");
        break;

    case DDERR_CANNOTATTACHSURFACE:
        // A surface cannot be attached to another requested surface.
        lpszError = TEXT("DDERR_CANNOTATTACHSURFACE");
        break;

    case DDERR_CANNOTDETACHSURFACE:
        // A surface cannot be detached from another requested surface.
        lpszError = TEXT("DDERR_CANNOTDETACHSURFACE");
        break;

    case DDERR_CANTCREATEDC:
        // Windows cannot create any more device contexts (DCs).
        lpszError = TEXT("DDERR_CANTCREATEDC");
        break;

    case DDERR_CANTDUPLICATE:
        // Primary and 3D surfaces, or surfaces that are
        // implicitly created, cannot be duplicated.
        lpszError = TEXT("DDERR_CANTDUPLICATE");
        break;

    case DDERR_CANTLOCKSURFACE:
        // Access to this surface is refused because an
        // attempt was made to lock the primary surface without DCI support.
        lpszError = TEXT("DDERR_CANTLOCKSURFACE");
        break;

    case DDERR_CANTPAGELOCK:
        // An attempt to page lock a surface failed.
        // Page lock will not work on a display-memory
        // surface or an emulated primary surface.
        lpszError = TEXT("DDERR_CANTPAGELOCK");
        break;

    case DDERR_CANTPAGEUNLOCK:
        // An attempt to page unlock a surface failed.
        // Page unlock will not work on a display-memory
        // surface or an emulated primary surface.
        lpszError = TEXT("DDERR_CANTPAGEUNLOCK");
        break;

    case DDERR_CLIPPERISUSINGHWND:
        // An attempt was made to set a clip list for a DirectDrawClipper
        // object that is already monitoring a window handle.
        lpszError = TEXT("DDERR_CLIPPERISUSINGHWND");
        break;

    case DDERR_COLORKEYNOTSET:
        // No source color key is specified for this operation
        lpszError = TEXT("DDERR_COLORKEYNOTSET");
        break;

    case DDERR_CURRENTLYNOTAVAIL:
        // No support is currently available.
        lpszError = TEXT("DDERR_CURRENTLYNOTAVAIL");
        break;

    case DDERR_DCALREADYCREATED:
        // A device context (DC) has already been returned for this surface.
        // Only one DC can be retrieved for each surface.
        lpszError = TEXT("DDERR_DCALREADYCREATED");
        break;

    case DDERR_DIRECTDRAWALREADYCREATED:
        // A DirectDraw object representing this driver
        // has already been created for this process.
        lpszError = TEXT("DDERR_DIRECTDRAWALREADYCREATED");
        break;

    case DDERR_EXCEPTION:
        // An exception was encountered while
        // performing the requested operation.
        lpszError = TEXT("DDERR_EXCEPTION");
        break;

    case DDERR_EXCLUSIVEMODEALREADYSET:
        // An attempt was made to set the cooperative
        // level when it was already set to exclusive.
        lpszError = TEXT("DDERR_EXCLUSIVEMODEALREADYSET");
        break;

    case DDERR_GENERIC:
        // There is an undefined error condition.
        lpszError = TEXT("DDERR_GENERIC");
        break;

    case DDERR_HEIGHTALIGN:
        // The height of the provided rectangle
        // is not a multiple of the required alignment.
        lpszError = TEXT("DDERR_HEIGHTALIGN");
        break;

    case DDERR_HWNDALREADYSET:
        // The DirectDraw cooperative level window
        // handle has already been set. It cannot
        // be reset while the process has surfaces or palettes created.
        lpszError = TEXT("DDERR_HWNDALREADYSET");
        break;

    case DDERR_HWNDSUBCLASSED:
        // DirectDraw is prevented from restoring state because the
        // DirectDraw cooperative level window handle has been subclassed.
        lpszError = TEXT("DDERR_HWNDSUBCLASSED");
        break;

    case DDERR_IMPLICITLYCREATED:
        // The surface cannot be restored because
        // it is an implicitly created surface.
        lpszError = TEXT("DDERR_IMPLICITLYCREATED");
        break;

    case DDERR_INCOMPATIBLEPRIMARY:
        // The primary surface creation request
        // does not match with the existing primary surface.
        lpszError = TEXT("DDERR_INCOMPATIBLEPRIMARY");
        break;

    case DDERR_INVALIDCAPS:
        // One or more of the capability bits
        // passed to the callback function are incorrect.
        lpszError = TEXT("DDERR_INVALIDCAPS");
        break;

    case DDERR_INVALIDCLIPLIST:
        // DirectDraw does not support the provided clip list.
        lpszError = TEXT("DDERR_INVALIDCLIPLIST");
        break;

    case DDERR_INVALIDDIRECTDRAWGUID:
        // The globally unique identifier (GUID) passed to the
        // DirectDrawCreate function is not a valid DirectDraw driver identifier.
        lpszError = TEXT("DDERR_INVALIDDIRECTDRAWGUID");
        break;

    case DDERR_INVALIDMODE:
        // DirectDraw does not support the requested mode.
        lpszError = TEXT("DDERR_INVALIDMODE");
        break;

    case DDERR_INVALIDOBJECT:
        // DirectDraw received a pointer that was an invalid DirectDraw object.
        lpszError = TEXT("DDERR_INVALIDOBJECT");
        break;

    case DDERR_INVALIDPARAMS:
        // One or more of the parameters passed to the method are incorrect.
        lpszError = TEXT("DDERR_INVALIDPARAMS");
        break;

    case DDERR_INVALIDPIXELFORMAT:
        // The pixel format was invalid as specified.
        lpszError = TEXT("DDERR_INVALIDPIXELFORMAT");
        break;

    case DDERR_INVALIDPOSITION:
        // The position of the overlay on the destination is no longer legal.
        lpszError = TEXT("DDERR_INVALIDPOSITION");
        break;

    case DDERR_INVALIDRECT:
        // The provided rectangle was invalid.
        lpszError = TEXT("DDERR_INVALIDRECT");
        break;

    case DDERR_INVALIDSURFACETYPE:
        // The requested operation could not be performed
        // because the surface was of the wrong type.
        lpszError = TEXT("DDERR_INVALIDSURFACETYPE");
        break;

    case DDERR_LOCKEDSURFACES:
        // One or more surfaces are locked,
        // causing the failure of the requested operation.
        lpszError = TEXT("DDERR_LOCKEDSURFACES");
        break;

    case DDERR_MOREDATA:
        // There is more data available than the specified
        // buffer size could hold.
        lpszError = TEXT("DDERR_MOREDATA");
        break;

    case DDERR_NO3D:
        // No 3D hardware or emulation is present.
        lpszError = TEXT("DDERR_NO3D");
        break;

    case DDERR_NOALPHAHW:
        // No alpha acceleration hardware is present or available,
        // causing the failure of the requested operation.
        lpszError = TEXT("DDERR_NOALPHAHW");
        break;

    case DDERR_NOBLTHW:
        // No blitter hardware is present.
        lpszError = TEXT("DDERR_NOBLTHW");
        break;

    case DDERR_NOCLIPLIST:
        // No clip list is available.
        lpszError = TEXT("DDERR_NOCLIPLIST");
        break;

    case DDERR_NOCLIPPERATTACHED:
        // No DirectDrawClipper object is attached to the surface object.
        lpszError = TEXT("DDERR_NOCLIPPERATTACHED");
        break;

    case DDERR_NOCOLORCONVHW:
        // The operation cannot be carried out because
        // no color-conversion hardware is present or available.
        lpszError = TEXT("DDERR_NOCOLORCONVHW");
        break;

    case DDERR_NOCOLORKEY:
        // The surface does not currently have a color key.
        lpszError = TEXT("DDERR_NOCOLORKEY");
        break;

    case DDERR_NOCOLORKEYHW:
        // The operation cannot be carried out because there
        // is no hardware support for the destination color key.
        lpszError = TEXT("DDERR_NOCOLORKEYHW");
        break;

    case DDERR_NOCOOPERATIVELEVELSET:
        // A create function is called without the
        // IDirectDraw2::SetCooperativeLevel method being called.
        lpszError = TEXT("DDERR_NOCOOPERATIVELEVELSET");
        break;

    case DDERR_NODC:
        // No DC has ever been created for this surface.
        lpszError = TEXT("DDERR_NODC");
        break;

    case DDERR_NODDROPSHW:
        // No DirectDraw raster operation (ROP) hardware is available.
        lpszError = TEXT("DDERR_NODDROPSHW");
        break;

    case DDERR_NODIRECTDRAWHW:
        // Hardware-only DirectDraw object creation is not possible;
        // the driver does not support any hardware.
        lpszError = TEXT("DDERR_NODIRECTDRAWHW");
        break;

    case DDERR_NODIRECTDRAWSUPPORT:
        // DirectDraw support is not possible with the current display driver.
        lpszError = TEXT("DDERR_NODIRECTDRAWSUPPORT");
        break;

    case DDERR_NOEMULATION:
        // Software emulation is not available.
        lpszError = TEXT("DDERR_NOEMULATION");
        break;

    case DDERR_NOEXCLUSIVEMODE:
        // The operation requires the application to have
        // exclusive mode, but the application does not have exclusive mode.
        lpszError = TEXT("DDERR_NOEXCLUSIVEMODE");
        break;

    case DDERR_NOFLIPHW:
        // Flipping visible surfaces is not supported.
        lpszError = TEXT("DDERR_NOFLIPHW");
        break;

    case DDERR_NOGDI:
        // No GDI is present.
        lpszError = TEXT("DDERR_NOGDI");
        break;

    case DDERR_NOHWND:
        // Clipper notification requires a window handle,
        // or no window handle has been previously set
        // as the cooperative level window handle.
        lpszError = TEXT("DDERR_NOHWND");
        break;

    case DDERR_NOMIPMAPHW:
        // The operation cannot be carried out because no
        // mipmap texture mapping hardware is present or available.
        lpszError = TEXT("DDERR_NOMIPMAPHW");
        break;

    case DDERR_NOMIRRORHW:
        // The operation cannot be carried out because
        // no mirroring hardware is present or available.
        lpszError = TEXT("DDERR_NOMIRRORHW");
        break;

    case DDERR_NONONLOCALVIDMEM:
        // An attempt was made to allocate non-local video memory
        // from a device that does not support non-local video memory.
        lpszError = TEXT("DDERR_NONONLOCALVIDMEM");
        break;

    case DDERR_NOOVERLAYDEST:
        // The IDirectDrawSurface2::GetOverlayPosition method
        // is called on an overlay that the IDirectDrawSurface2::UpdateOverlay
        // method has not been called on to establish a destination.
        lpszError = TEXT("DDERR_NOOVERLAYDEST");
        break;

    case DDERR_NOOVERLAYHW:
        // The operation cannot be carried out because
        // no overlay hardware is present or available.
        lpszError = TEXT("DDERR_NOOVERLAYHW");
        break;

    case DDERR_NOPALETTEATTACHED:
        // No palette object is attached to this surface.
        lpszError = TEXT("DDERR_NOPALETTEATTACHED");
        break;

    case DDERR_NOPALETTEHW:
        // There is no hardware support for 16- or 256-color palettes.
        lpszError = TEXT("DDERR_NOPALETTEHW");
        break;

    case DDERR_NORASTEROPHW:
        // The operation cannot be carried out because
        // no appropriate raster operation hardware is present or available.
        lpszError = TEXT("DDERR_NORASTEROPHW");
        break;

    case DDERR_NOROTATIONHW:
        // The operation cannot be carried out because
        // no rotation hardware is present or available.
        lpszError = TEXT("DDERR_NOROTATIONHW");
        break;

    case DDERR_NOSTRETCHHW:
        // The operation cannot be carried out because
        // there is no hardware support for stretching.
        lpszError = TEXT("DDERR_NOSTRETCHHW");
        break;

    case DDERR_NOT4BITCOLOR:
        // The DirectDrawSurface object is not using a
        // 4-bit color palette and the requested operation
        // requires a 4-bit color palette.
        lpszError = TEXT("DDERR_NOT4BITCOLOR");
        break;

    case DDERR_NOT4BITCOLORINDEX:
        // The DirectDrawSurface object is not using a 4-bit
        // color index palette and the requested operation
        // requires a 4-bit color index palette.
        lpszError = TEXT("DDERR_NOT4BITCOLORINDEX");
        break;

    case DDERR_NOT8BITCOLOR:
        // The DirectDrawSurface object is not using an 8-bit
        // color palette and the requested operation requires
        // an 8-bit color palette.
        lpszError = TEXT("DDERR_NOT8BITCOLOR");
        break;

    case DDERR_NOTAOVERLAYSURFACE:
        // An overlay component is called for a non-overlay surface.
        lpszError = TEXT("DDERR_NOTAOVERLAYSURFACE");
        break;

    case DDERR_NOTEXTUREHW:
        // The operation cannot be carried out because no
        // texture-mapping hardware is present or available.
        lpszError = TEXT("DDERR_NOTEXTUREHW");
        break;

    case DDERR_NOTFLIPPABLE:
        // An attempt has been made to flip a surface that cannot be flipped.
        lpszError = TEXT("DDERR_NOTFLIPPABLE");
        break;

    case DDERR_NOTFOUND:
        // The requested item was not found.
        lpszError = TEXT("DDERR_NOTFOUND");
        break;

    case DDERR_NOTINITIALIZED:
        // An attempt was made to call an interface method of a DirectDraw object
        // created by CoCreateInstance before the object was initialized.
        lpszError = TEXT("DDERR_NOTINITIALIZED");
        break;

    case DDERR_NOTLOCKED:
        // An attempt is made to unlock a surface that was not locked.
        lpszError = TEXT("DDERR_NOTLOCKED");
        break;

    case DDERR_NOTPAGELOCKED:
        // An attempt is made to page unlock a surface
        // with no outstanding page locks.
        lpszError = TEXT("DDERR_NOTPAGELOCKED");
        break;

    case DDERR_NOTPALETTIZED:
        // The surface being used is not a palette-based surface.
        lpszError = TEXT("DDERR_NOTPALETTIZED");
        break;

    case DDERR_NOVSYNCHW:
        // The operation cannot be carried out because
        // there is no hardware support for vertical blank synchronized operations.
        lpszError = TEXT("DDERR_NOVSYNCHW");
        break;

    case DDERR_NOZBUFFERHW:
        // The operation to create a z-buffer in display memory
        // or to perform a blit using a z-buffer cannot be carried
        // out because there is no hardware support for z-buffers.
        lpszError = TEXT("DDERR_NOZBUFFERHW");
        break;

    case DDERR_NOZOVERLAYHW:
        // The overlay surfaces cannot be z-layered based
        // on the z-order because the hardware does not
        // support z-ordering of overlays.
        lpszError = TEXT("DDERR_NOZOVERLAYHW");
        break;

    case DDERR_OUTOFCAPS:
        // The hardware needed for the requested operation has already been allocated.
        lpszError = TEXT("DDERR_OUTOFCAPS");
        break;

    case DDERR_OUTOFMEMORY:
        // DirectDraw does not have enough memory to perform the operation.
        lpszError = TEXT("DDERR_OUTOFMEMORY");
        break;

    case DDERR_OUTOFVIDEOMEMORY:
        // DirectDraw does not have enough display memory to perform the operation.
        lpszError = TEXT("DDERR_OUTOFVIDEOMEMORY");
        break;

    case DDERR_OVERLAYCANTCLIP:
        // The hardware does not support clipped overlays.
        lpszError = TEXT("DDERR_OVERLAYCANTCLIP");
        break;

    case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
        // An attempt was made to have more than one color key active on an overlay.
        lpszError = TEXT("DDERR_OVERLAYCOLORKEYONLYONEACTIVE");
        break;

    case DDERR_OVERLAYNOTVISIBLE:
        // The IDirectDrawSurface2::GetOverlayPosition method is called on a hidden overlay.
        lpszError = TEXT("DDERR_OVERLAYNOTVISIBLE");
        break;

    case DDERR_PALETTEBUSY:
        // Access to this palette is refused
        // because the palette is locked by another thread.
        lpszError = TEXT("DDERR_PALETTEBUSY");
        break;

    case DDERR_PRIMARYSURFACEALREADYEXISTS:
        // This process has already created a primary surface.
        lpszError = TEXT("DDERR_PRIMARYSURFACEALREADYEXISTS");
        break;

    case DDERR_REGIONTOOSMALL:
        // The region passed to the
        // IDirectDrawClipper::GetClipList method is too small.
        lpszError = TEXT("DDERR_REGIONTOOSMALL");
        break;

    case DDERR_SURFACEALREADYATTACHED:
        // An attempt was made to attach a surface to
        // another surface to which it is already attached.
        lpszError = TEXT("DDERR_SURFACEALREADYATTACHED");
        break;

    case DDERR_SURFACEALREADYDEPENDENT:
        // An attempt was made to make a surface a dependency
        // of another surface to which it is already dependent.
        lpszError = TEXT("DDERR_SURFACEALREADYDEPENDENT");
        break;

    case DDERR_SURFACEBUSY:
        // Access to the surface is refused because the
        // surface is locked by another thread.
        lpszError = TEXT("DDERR_SURFACEBUSY");
        break;

    case DDERR_SURFACEISOBSCURED:
        // Access to the surface is refused
        // because the surface is obscured.
        lpszError = TEXT("DDERR_SURFACEISOBSCURED");
        break;

    case DDERR_SURFACELOST:
        // Access to the surface is refused because the
        // surface memory is gone. The DirectDrawSurface
        // object representing this surface should have
        // the IDirectDrawSurface2::Restore method called on it.
        lpszError = TEXT("DDERR_SURFACELOST");
        break;

    case DDERR_SURFACENOTATTACHED:
        // The requested surface is not attached.
        lpszError = TEXT("DDERR_SURFACENOTATTACHED");
        break;

    case DDERR_TOOBIGHEIGHT:
        // The height requested by DirectDraw is too large.
        lpszError = TEXT("DDERR_TOOBIGHEIGHT");
        break;

    case DDERR_TOOBIGSIZE:
        // The size requested by DirectDraw is too large.
        // However, the individual height and width are OK.
        lpszError = TEXT("DDERR_TOOBIGSIZE");
        break;

    case DDERR_TOOBIGWIDTH:
        // The width requested by DirectDraw is too large.
        lpszError = TEXT("DDERR_TOOBIGWIDTH");
        break;

    case DDERR_UNSUPPORTED:
        // The operation is not supported.
        lpszError = TEXT("DDERR_UNSUPPORTED");
        break;

    case DDERR_UNSUPPORTEDFORMAT:
        // The FourCC format requested is not supported by DirectDraw.
        lpszError = TEXT("DDERR_UNSUPPORTEDFORMAT");
        break;

    case DDERR_UNSUPPORTEDMASK:
        // The bitmask in the pixel format requested is not supported by DirectDraw.
        lpszError = TEXT("DDERR_UNSUPPORTEDMASK");
        break;

    case DDERR_UNSUPPORTEDMODE:
        // The display is currently in an unsupported mode.
        lpszError = TEXT("DDERR_UNSUPPORTEDMODE");
        break;

    case DDERR_VERTICALBLANKINPROGRESS:
        // A vertical blank is in progress.
        lpszError = TEXT("DDERR_VERTICALBLANKINPROGRESS");
        break;

    case DDERR_WASSTILLDRAWING:
        // The previous blit operation that is transferring
        // information to or from this surface is incomplete.
        lpszError = TEXT("DDERR_WASSTILLDRAWING");
        break;

    case DDERR_WRONGMODE:
        // This surface cannot be restored because it was created in a different mode.
        lpszError = TEXT("DDERR_WRONGMODE");
        break;

    case DDERR_XALIGN:
        // The provided rectangle was not horizontally aligned on a required boundary.
        lpszError = TEXT("DDERR_XALIGN");
        break;

    case DDERR_VIDEONOTACTIVE:
        // The video port is not active
        lpszError = TEXT("DDERR_VIDEONOTACTIVE");
        break;

        //
        // D3D Immediate Mode Errors
        //

    case D3DERR_BADMAJORVERSION:
        // ???
        lpszError = TEXT("D3DERR_BADMAJORVERSION");
        break;

    case D3DERR_BADMINORVERSION:
        // ???
        lpszError = TEXT("D3DERR_BADMINORVERSION");
        break;

    case D3DERR_INVALID_DEVICE:
        // ???
        lpszError = TEXT("D3DERR_INVALID_DEVICE");
        break;

    case D3DERR_INITFAILED:
        // ???
        lpszError = TEXT("D3DERR_INITFAILED");
        break;

    case D3DERR_EXECUTE_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_CREATE_FAILED");
        break;

    case D3DERR_EXECUTE_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_DESTROY_FAILED");
        break;

    case D3DERR_EXECUTE_LOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_LOCK_FAILED");
        break;

    case D3DERR_EXECUTE_UNLOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_UNLOCK_FAILED");
        break;

    case D3DERR_EXECUTE_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_LOCKED");
        break;

    case D3DERR_EXECUTE_NOT_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_NOT_LOCKED");
        break;

    case D3DERR_EXECUTE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_FAILED");
        break;

    case D3DERR_EXECUTE_CLIPPED_FAILED:
        // ???
        lpszError = TEXT("D3DERR_CLIPPED_FAILED");
        break;

    case D3DERR_TEXTURE_NO_SUPPORT:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_NO_SUPPORT");
        break;

    case D3DERR_TEXTURE_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_CREATE_FAILED");
        break;

    case D3DERR_TEXTURE_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_DESTROY_FAILED");
        break;

    case D3DERR_TEXTURE_LOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_LOCK_FAILED");
        break;

    case D3DERR_TEXTURE_UNLOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_UNLOCK_FAILED");
        break;

    case D3DERR_TEXTURE_LOAD_FAILED:
        // ???
        lpszError = TEXT("D3DERR_LOAD_FAILED");
        break;

    case D3DERR_TEXTURE_SWAP_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SWAP_FAILED");
        break;

    case D3DERR_TEXTURE_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_LOCKED");
        break;

    case D3DERR_TEXTURE_NOT_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_NOT_LOCKED");
        break;

    case D3DERR_TEXTURE_GETSURF_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_GETSURF_FAILED");
        break;

    case D3DERR_MATRIX_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_CREATE_FAILED");
        break;

    case D3DERR_MATRIX_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_DESTROY_FAILED");
        break;

    case D3DERR_MATRIX_SETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_SETDATA_FAILED");
        break;

    case D3DERR_MATRIX_GETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MAXTRIX_GETDATA_FAILED");
        break;

    case D3DERR_SETVIEWPORTDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SETVIEWPORTDATA_FAILED");
        break;

    case D3DERR_INVALIDCURRENTVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_INVALIDCURRENTVIEWPORT");
        break;

    case D3DERR_INVALIDPRIMITIVETYPE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDPRIMITIVETYPE");
        break;

    case D3DERR_INVALIDVERTEXTYPE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDVERTEXTYPE");
        break;

    case D3DERR_TEXTURE_BADSIZE:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_BADSIZE");
        break;

    case D3DERR_MATERIAL_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_CREATE_FAILED");
        break;

    case D3DERR_MATERIAL_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_DESTROY_FAILED");

break;

    case D3DERR_MATERIAL_SETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_SETDATA_FAILED");
        break;

    case D3DERR_MATERIAL_GETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_GETDATA_FAILED");
        break;

    case D3DERR_INVALIDPALETTE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDPALETTE");
        break;

    case D3DERR_ZBUFF_NEEDS_SYSTEMMEMORY:
        // ???
        lpszError = TEXT("D3DERR_ZBUFF_NEEDS_SYSTEMMEMORY");
        break;

    case D3DERR_ZBUFF_NEEDS_VIDEOMEMORY:
        // ???
        lpszError = TEXT("D3DERR_ZBUFF_NEEDS_VIDEOMEMORY");
        break;

    case D3DERR_SURFACENOTINVIDMEM:
        // ???
        lpszError = TEXT("D3DERR_SURFACENOTINVIDMEM");
        break;

    case D3DERR_LIGHT_SET_FAILED:
        // ???
        lpszError = TEXT("D3DERR_LIGHT_SET_FAILED");
        break;

    case D3DERR_LIGHTHASVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_LIGHTHASVIEWPORT");
        break;

    case D3DERR_LIGHTNOTINTHISVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_LIGHTNOTINTHISVIEWPORT");
        break;

    case D3DERR_SCENE_IN_SCENE:
        // ???
        lpszError = TEXT("D3DERR_SCENE_IN_SCENE");
        break;

    case D3DERR_SCENE_NOT_IN_SCENE:
        // ???
        lpszError = TEXT("D3DERR_SCENE_NOT_IN_SCENE");
        break;

    case D3DERR_SCENE_BEGIN_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SCENE_BEGIN_FAILED");
        break;

    case D3DERR_SCENE_END_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SCENE_END_FAILED");
        break;

    case D3DERR_INBEGIN:
        // ???
        lpszError = TEXT("D3DERR_INBEGIN");
        break;

    case D3DERR_NOTINBEGIN:
        // ???
        lpszError = TEXT("D3DERR_NOTINBEGIN");
        break;

    case D3DERR_NOVIEWPORTS:
        // ???
        lpszError = TEXT("D3DERR_NOVIEWPORTS");
        break;

    case D3DERR_VIEWPORTDATANOTSET:
        // ???
        lpszError = TEXT("D3DERR_VIEWPORTDATANOTSET");
        break;

    case D3DERR_VIEWPORTHASNODEVICE:
        // ???
        lpszError = TEXT("D3DERR_VIEWPORTHASNODEVICE");
        break;

        //
        // D3D Retained Mode Errors
        //
#if 0
    case D3DRMERR_BADOBJECT:
        // ???
        lpszError = TEXT("D3DRMERR_BADOBJECT");
        break;

    case D3DRMERR_BADTYPE:
        // ???
        lpszError = TEXT("D3DRMERR_BADTYPE");
        break;

    case D3DRMERR_BADALLOC:
        // ???
        lpszError = TEXT("D3DRMERR_BADALLOC");
        break;

    case D3DRMERR_FACEUSED:
        // ???
        lpszError = TEXT("D3DRMERR_FACEUSED");
        break;

    case D3DRMERR_NOTFOUND:
        // ???
        lpszError = TEXT("D3DRMERR_NOTFOUND");
        break;

    case D3DRMERR_NOTDONEYET:
        // ???
        lpszError = TEXT("D3DRMERR_NOTDONEYET");
        break;

    case D3DRMERR_FILENOTFOUND:
        // ???
        lpszError = TEXT("D3DRMERR_FILENOTFOUND");
        break;

    case D3DRMERR_BADFILE:
        // ???
        lpszError = TEXT("D3DRMERR_BADFILE");
        break;

    case D3DRMERR_BADDEVICE:
        // ???
        lpszError = TEXT("D3DRMERR_BADDEVICE");
        break;

    case D3DRMERR_BADVALUE:
        // ???
        lpszError = TEXT("D3DRMERR_BADVALUE");
        break;

    case D3DRMERR_BADMAJORVERSION:
        // ???
        lpszError = TEXT("D3DRMERR_BADMAJORVERSION");
        break;

    case D3DRMERR_BADMINORVERSION:
        // ???
        lpszError = TEXT("D3DRMERR_BADMINORVERSION");
        break;

    case D3DRMERR_UNABLETOEXECUTE:
        // ???
        lpszError = TEXT("D3DRMERR_BADMINORVERSION");
        break;
#endif // D3DRM_ERRORS

//
// Application defined errors
//
case APPERR_GENERIC:
        // Generic Error
        lpszError = TEXT("APPERR_GENERIC");
        break;

case APPERR_INVALIDPARAMS:
        // Invalid Parameters passed into function
        lpszError = TEXT("APPERR_INVALIDPARAMS");
        break;

case APPERR_NOTINITIALIZED:
// Programmer error, called function without proper initialization
lpszError = TEXT("APPERR_NOT_INITIALIZED");
break;

    default:
        // Unknown DD/D3D/App Error
        wsprintf (szMsg, "Unknown Error #%ld", (DWORD)(hResult & 0x0000FFFFL));
        lpszError = szMsg;
        break;
    }

    // Copy DD/D3D Error string to buff
    cLen = strlen (lpszError);
    if (cLen >= cchError)
    {
        cLen = cchError - 1;
    }

    if (cLen)
    {
        strncpy (lpszErrorBuff, lpszError, cLen);
        lpszErrorBuff[cLen] = 0;
    }

    return TRUE;
} // End GetDDErrorString

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


DEBUG.H

#ifndef DEBUG_H
#define DEBUG_H
/*
**-----------------------------------------------------------------------------
**  File:       Debug.h
**  Purpose:    Sample Debug code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
// Note:  Define DEBUG_PROMPTME if you want MessageBox Error prompting
//  This can get annoying quickly...
// #define DEBUG_PROMPTME

// Pre and Post debug string info
#define START_STRTEXT ("BOIDS: ")
#define END_STRTEXT ("\r\n")
#endif // DEBUG

// Debug Levels
#define DEBUG_ALWAYS0L
#define DEBUG_CRITICAL1L
#define DEBUG_ERROR2L
#define DEBUG_MINOR3L
#define DEBUG_WARN4L
#define DEBUG_DETAILS5L

// Sample Errors
#define APPERR_GENERICMAKE_DDHRESULT (10001)
#defineAPPERR_INVALIDPARAMSMAKE_DDHRESULT (10002)
#define APPERR_NOTINITIALIZEDMAKE_DDHRESULT (10003)
#define APPERR_OUTOFMEMORYMAKE_DDHRESULT (10004)
#define APPERR_NOTFOUNDMAKE_DDHRESULT (10005)

/*
**-----------------------------------------------------------------------------
**  Macros
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
    #define DPF dprintf
    #define ASSERT(x) \
        if (! (x)) \
        { \
            DPF (DEBUG_ALWAYS, TEXT("Assertion violated: %s, File = %s, Line = #%ld\n"), \
                 TEXT(#x), TEXT(__FILE__), (DWORD)__LINE__ ); \
            abort (); \
        }

   #define REPORTERR(x) \
       ReportDDError ((x), TEXT("File = %s, Line = #%ld\n"), \
                      TEXT(__FILE__), (DWORD)__LINE__ );

   #define FATALERR(x) \
       ReportDDError ((x), TEXT("File = %s, Line = #%ld\n"), \
                      TEXT(__FILE__), (DWORD)__LINE__ ); \
       OnPause (TRUE); \
       DestroyWindow (g_hMainWindow);
#else
   #define REPORTERR(x)
   #define DPF 1 ? (void)0 : (void)
   #define ASSERT(x)
   #define FATALERR(x) \
       OnPause (TRUE); \
       DestroyWindow (g_hMainWindow);
#endif // DEBUG

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

// Debug Variables
#ifdef DEBUG
extern DWORD g_dwDebugLevel;
#endif

extern BOOL  g_fDebug;

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Debug Routines
#ifdef DEBUG
void __cdecl dprintf (DWORD dwDebugLevel, LPCTSTR szFormat, ...);
#endif //DEBUG

void _cdecl ReportDDError (HRESULT hResult, LPCTSTR szFormat, ...);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End DEBUG_H


DRVMGR.CPP

/*
**-----------------------------------------------------------------------------
**  Name:       DrvMgr.cpp
**  Purpose:    Implements DD/D3D Driver/Device enumeration
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Includes
**-----------------------------------------------------------------------------
*/

#include "DrvMgr.h"
#include "Debug.h"

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

// Used with Enumeration Callbacks
typedef struct tagDD_CB_INFO
{
    BOOL  fResult;    // Success/Failure
    DWORD  cCount;       // Current count
    void *    lpExtra;// Current Driver/Device/Etc.
} DD_CB_INFO;
typedef DD_CB_INFO * LPDD_CB_INFO;

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

// Global DDDrvMgr data
DWORD       DDDrvMgr::g_fFlags= 0L;
DWORDDDDrvMgr::g_cDrivers= 0L;
LPDDDrvInfo DDDrvMgr::g_lpDriverRoot= NULL;
LPDDDrvInfo DDDrvMgr::g_lpDriverTail= NULL;

/*
**-----------------------------------------------------------------------------
**  Local Prototypes
**-----------------------------------------------------------------------------
*/

BOOL WINAPI DriverEnumCallback (LPGUID lpGuid, LPTSTR lpDesc,
                                LPTSTR lpName, LPVOID lpExtra);

HRESULT WINAPI ModeEnumCallback (LPDDSURFACEDESC lpDDSurfDesc,
                                 LPVOID lpExtra);

HRESULT WINAPI TextureFormatEnumCallback (LPDDSURFACEDESC lpTexFormatDesc,
  LPVOID lpExtra);

HRESULT WINAPI DeviceEnumCallback (LPGUID lpGuid,
   LPTSTR lpName,
   LPTSTR lpDesc,
                                   LPD3DDEVICEDESC lpHalDevice,
                                   LPD3DDEVICEDESC lpHelDevice,
                                   LPVOID lpExtra);

/*
**-----------------------------------------------------------------------------
**  Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:    ValidateDriver
** Purpose: Find DD driver matching user choice
**or returns the primary driver on failure
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo ValidateDriver (LPGUID lpDDGuid)
{
LPDDDrvInfo lpDrvNew, lpDrvNext;

    // Find Driver matching specified GUID
    lpDrvNew = DDDrvMgr::FindDriver (lpDDGuid, &lpDrvNext);
    if (lpDrvNew)
{
// Exact match
return lpDrvNew;
}

// Return next best match (or failure)
return lpDrvNext;
} // End ValidateDriver

/*
**-----------------------------------------------------------------------------
** Name:    ValidateMode
** Purpose: Find DD mode matching user choice (w,h,bpp)
** Notes:filters modes against device, if specified.
**-----------------------------------------------------------------------------
*/

LPDDModeInfo ValidateMode (
LPDDDrvInfo lpDriver,  /* In:  DD Driver */
DWORD w,/* In:  width, height, bpp */
DWORD h,
DWORD bpp,
DWORD refresh,
LPD3DDevInfo lpFilter)/* In:Device used as filter */
{
LPDDModeInfo lpModeNew, lpModeNext;

// Check Parameters
if (! lpDriver)
return FALSE;

if (! lpFilter)
lpModeNew = lpDriver->FindMode (w, h, bpp, refresh, &lpModeNext);
else
{
// Filter mode against D3D device compatiblity
lpModeNew = lpDriver->FindModeSupportsDevice (w, h, bpp, refresh,
  lpFilter,
  &lpModeNext);
}

    if (lpModeNew)
{
// Exact match
return lpModeNew;
}

// Return next best match (or failure)
return lpModeNext;
} // End ValidateMode

/*
**-----------------------------------------------------------------------------
** Name:    ValidateDevice
** Purpose: Find D3D device matching user choice
** Notes:Filters devices against mode, if specified.
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo ValidateDevice(
LPDDDrvInfo lpDriver,
LPGUID lpD3DGuid,
LPDDModeInfo lpFilter)
{
LPD3DDevInfo lpDevNew, lpDevNext;

// Check Parameters
if (! lpDriver)
return FALSE;

if (! lpFilter)
{
lpDevNew = lpDriver->FindDevice (lpD3DGuid, &lpDevNext);
}
else
{
// Filter device against mode
lpDevNew = lpDriver->FindDeviceSupportsMode (lpD3DGuid,
       lpFilter,
 &lpDevNext);
}

    if (lpDevNew)
{
// Exact match
return lpDevNew;
}

// Return next best match (or failure)
return lpDevNext;
} // End ValidateDevice

/*
**-----------------------------------------------------------------------------
** Name:    ValidateFormat
** Purpose: Find texture format matching user choice (ddpfPixelformat)
** Notes:filters modes against device, if specified.
**-----------------------------------------------------------------------------
*/

LPDDModeInfo ValidateFormat (
LPD3DDevInfo lpDevice,  /* In:  D3D Device */
DWORD bpp)
{
LPDDModeInfo lpFormatNew, lpFormatNext;

// Check Parameters
if (! lpDevice)
return FALSE;

lpFormatNew = lpDevice->FindFormat (bpp, &lpFormatNext);

    if (lpFormatNew)
{
// Exact match
return lpFormatNew;
}

// Return next best match (or failure)
return lpFormatNext;
} // End ValidateFormat

/*
**-----------------------------------------------------------------------------
** Name:    ValidateFormat
** Purpose: Find texture format matching user choice (ddpfPixelformat)
** Notes:filters modes against device, if specified.
**-----------------------------------------------------------------------------
*/

LPDDModeInfo ValidateFormat (
LPD3DDevInfo lpDevice,  /* In:  D3D Device */
LPDDPIXELFORMAT lpddpf)
{
LPDDModeInfo lpFormatNew, lpFormatNext;

// Check Parameters
if (! lpDevice)
return FALSE;

lpFormatNew = lpDevice->FindFormat (lpddpf, &lpFormatNext);

    if (lpFormatNew)
{
// Exact match
return lpFormatNew;
}

// Return next best match (or failure)
return lpFormatNext;
} // End ValidateFormat

/*
**-----------------------------------------------------------------------------
** Name:    GetDesktopMode
** Purpose: Find DD mode corresponding to desktop
**and a compatible D3D device.
** Notes:
**
**1.  We have no choice, we are stuck with the current desktop mode.
**  2.  Since we can't change the mode, we must choose a D3D device
**that is compatible with the current mode.
**
**-----------------------------------------------------------------------------
*/

BOOL GetDesktopMode (
LPDDDrvInfo lpDriver,/* In:   Driver */
LPGUID lpD3DGuid, /* In:   Requested D3D device guid */
LPDDModeInfo * lpMode,/* Out:  Desktop Mode */
LPD3DDevInfo * lpDev)/* Out:  D3D device compatible with mode */
{
HWND hDesktop;
HDC hdc;
DWORD w, h, bpp;
LPDDModeInfo lpModeNew;
LPD3DDevInfo lpDevNew, lpDevNext;

// Check Parameters
if ((! lpDriver) || (! lpMode) || (! lpDev))
return FALSE;

// Get Desktop Mode info
hDesktop = GetDesktopWindow ();
hdc = GetDC (hDesktop);

w= GetDeviceCaps (hdc, HORZRES);
h= GetDeviceCaps (hdc, VERTRES);
bpp= GetDeviceCaps(hdc, PLANES) * GetDeviceCaps (hdc, BITSPIXEL);

ReleaseDC (hDesktop, hdc);

// Get Mode
lpModeNew = lpDriver->FindMode (w, h, bpp, 0, NULL);
if (! lpModeNew)
return FALSE;

// Get Compatible Device
lpDevNew = lpDriver->FindDeviceSupportsMode (lpD3DGuid,
          lpModeNew,
 &lpDevNext);
if (! lpDevNew)
{
if (! lpDevNext)
return FALSE;
lpDevNew = lpDevNext;
}

// Save results
*lpMode = lpModeNew;
*lpDev  = lpDevNew;

// Success
return TRUE;
} // End GetDesktopMode

/*
**-----------------------------------------------------------------------------
** Name:    GetFullscreenMode
** Purpose: Find D3D Device and a compatible mode
** Notes:
**
**  1. Pick the D3D device first
**  2. Pick a DD mode that is compatible with our D3D device choice.
**-----------------------------------------------------------------------------
*/

BOOL GetFullscreenMode (
LPDDDrvInfo lpDriver,/* In:   Driver */
LPGUID lpD3DGuid, /* In:   requested D3D device guid */
DWORD w,/* In: requested mode */
DWORD h,
DWORD bpp,
DWORD        refresh,
LPDDModeInfo * lpMode,/* Out:  Valid Desktop Mode compatible with device */
LPD3DDevInfo * lpDev)/* Out:  Valid D3D device */
{
LPDDModeInfo lpModeNew, lpModeNext;
LPD3DDevInfo lpDevNew, lpDevNext;

// Check Parameters
if ((! lpDriver) || (! lpMode) || (! lpDev))
return FALSE;

// Get D3D Device
lpDevNew = lpDriver->FindDevice (lpD3DGuid, &lpDevNext);
if (! lpDevNew)
{
if (! lpDevNext)
return FALSE;
lpDevNew = lpDevNext;
}

// Double check requested mode parameters
if ((w == 0) || (h == 0) || (bpp == 0))
{
// Pick a reasonable full screen default
// Most Hardware devices support 16 bpp,
// many don't support 8 bpp, so pick 16
w   = 640;
h= 480;
bpp = 16;
}

// Get Compatible Mode
lpModeNew = lpDriver->FindModeSupportsDevice (w, h, bpp, refresh,
      lpDevNew,
  &lpModeNext);
if (! lpModeNew)
{
if (! lpModeNext)
return FALSE;
lpModeNew = lpModeNext;
}

// Save results
*lpMode = lpModeNew;
*lpDev  = lpDevNew;

// Success
return TRUE;
} // End GetFullscreenMode

/*
**-----------------------------------------------------------------------------
** Name:    ChooseDriverDefaults
** Purpose: Chooses default driver, mode, and device
** Notes:
**
**Windowed:The mode comes from the desktop and can't be changed
**so derive the device from the mode.
**  FullScreen:Any mode is fine.  Hardware is more interesting than
**software, so derive the mode from the device
**
**-----------------------------------------------------------------------------
*/

HRESULT ChooseDriverDefaults (
    LPGUID lpGuidDD,// In:Requested DD Guid
    DWORD  dwW,// In:Requested Mode (w,h,bpp,refresh)
DWORD  dwH,
DWORD  dwBPP,
DWORD  dwRefresh,
    LPGUID lpGuidD3D,// In:Requested D3D Guid
BOOL   fFullScreen,// In:  Fullscreen or Windowed mode
LPDDDrvInfo * lpDriver,// Out: Valid Driver
LPDDModeInfo * lpMode,// Out: Valid Mode
LPD3DDevInfo * lpDevice)// Out: Valid Device
{
HRESULThResult;
    LPDDDrvInfolpDrvNew;
LPDDModeInfolpModeNew;
LPD3DDevInfolpDevNew;

// Check Parameters
if ((! lpDriver) || (! lpMode) || (! lpDevice))
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

// Initialize Driver Manager, if necessary
if (! DDDrvMgr::isInitialized ())
{
hResult = DDDrvMgr::Init ();
if (FAILED (hResult))
return hResult;
}

    // Find Driver matching specified GUID
    lpDrvNew = ValidateDriver (lpGuidDD);
if (! lpDrvNew)
    {
        // Error, invalid DD Guid
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
        return hResult;
    }

if (fFullScreen)
{
// Get D3D device and compatible mode
    if (! GetFullscreenMode (lpDrvNew, lpGuidD3D,
 dwW, dwH, dwBPP, dwRefresh,
 &lpModeNew, &lpDevNew))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}
}
else
{
// Get Desktop mode and compatible D3D device
if (! GetDesktopMode (lpDrvNew, lpGuidD3D, &lpModeNew, &lpDevNew))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}
}

// Return results
*lpDriver = lpDrvNew;
*lpMode   = lpModeNew;
*lpDevice = lpDevNew;

    // Success
    return DD_OK;
} // End ChooseDriverDefaults

/*
**-----------------------------------------------------------------------------
**  Name:       FlagsToBitDepth
**  Purpose:    Gets Bit Depth from DDPF Flags
**-----------------------------------------------------------------------------
*/

DWORD FlagsToBitDepth (DWORD dwFlags)
{
    if (dwFlags & DDBD_1)
        return 1L;
    else if (dwFlags & DDBD_2)
        return 2L;
    else if (dwFlags & DDBD_4)
        return 4L;
    else if (dwFlags & DDBD_8)
        return 8L;
    else if (dwFlags & DDBD_16)
        return 16L;
    else if (dwFlags & DDBD_24)
        return 24L;
    else if (dwFlags & DDBD_32)
        return 32L;
    else
        return 0L;
} // End FlagsToBitDepth

/*
**-----------------------------------------------------------------------------
**  Name:       BitDepthToFlags
**  Purpose:    Converts BPP to corresponding DDPF flag
**-----------------------------------------------------------------------------
*/

DWORD BitDepthToFlags (DWORD dwBPP)
{
    switch (dwBPP)
{
case 1:
    return DDBD_1;
case 2:
    return DDBD_2;
case 4:
    return DDBD_4;
case 8:
    return DDBD_8;
case 16:
    return DDBD_16;
case 24:
    return DDBD_24;
case 32:
    return DDBD_32;
default:
// Error
    return (DWORD)0L;
    }
} // End BitDepthToFlags

/*
**-----------------------------------------------------------------------------
** Name:    isPalettized
**-----------------------------------------------------------------------------
*/

BOOL isPalettized (LPDDPIXELFORMAT lpddpf)
{
    if (! lpddpf)
    {
        // Error,
        return FALSE;
    }

    if (lpddpf->dwFlags & DDPF_PALETTEINDEXED1)
        return TRUE;

    if (lpddpf->dwFlags & DDPF_PALETTEINDEXED2)
        return TRUE;

    if (lpddpf->dwFlags & DDPF_PALETTEINDEXED4)
        return TRUE;

    if (lpddpf->dwFlags & DDPF_PALETTEINDEXED8)
        return TRUE;

    // Not palettized
    return FALSE;
} // End IsPalettized

/*
**-----------------------------------------------------------------------------
**  Local Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DriverEnumCallback
**  Purpose:    Add this DD driver to global driver list
**-----------------------------------------------------------------------------
*/

BOOL WINAPI DriverEnumCallback(
    GUID FAR *  lpGuid,
    LPTSTR      lpDesc,
    LPTSTR      lpName,
    LPVOID      lpExtra)
{
    HRESULT         hResult;
    LPDD_CB_INFO lpInfo;
    LPDDDrvInfo     lpDriver;
    DWORD           dwIndex;

    if (! lpExtra)
    {
        // Programming error, invalid pointer
        return DDENUMRET_OK;
    }

    lpInfo = (LPDD_CB_INFO)lpExtra;
    dwIndex = lpInfo->cCount;

    // Get Pointer to driver info
    lpDriver = new DDDrvInfo;
    if (! lpDriver)
    {
        // Error, Not enough memory to create driver
        return DDENUMRET_OK;
    }

    // Initialize driver
// Enumerate modes, D3D devices
    hResult = lpDriver->Create (lpGuid, lpName, lpDesc);
    if (FAILED (hResult))
    {
        // Error, Driver Create failed
        return DDENUMRET_OK;
    }

// Add To Global Driver List
hResult = DDDrvMgr::AddDriver (lpDriver);
if (FAILED (hResult))
{
// Error, Driver Create Failed
return DDENUMRET_OK;
}

    // Increment driver count
    lpInfo->cCount++;

    // Success
    return DDENUMRET_OK;
} // End DriverEnumCallback

/*
**-----------------------------------------------------------------------------
**  Name:       ModeEnumCallback
**  Purpose:    Add this mode to the driver
**-----------------------------------------------------------------------------
*/

HRESULT WINAPI ModeEnumCallback (
    LPDDSURFACEDESC lpDDSurfDesc,
    LPVOID          lpExtra)
{
HRESULThResult;
LPDDModeInfolpMode;
    LPDD_CB_INFO lpInfo;
    LPDDDrvInfolpDriver;
    DWORD           dwIndex;

    if (! lpExtra)
    {
        // Programming error, invalid pointer
        return DDENUMRET_OK;
    }

    lpInfo   = (LPDD_CB_INFO)lpExtra;
    lpDriver = (LPDDDrvInfo) lpInfo->lpExtra;
    dwIndex  = lpInfo->cCount;

    if (! lpDriver)
    {
        // Programming Error, invalid pointer
        return DDENUMRET_OK;
    }

    if (! lpDDSurfDesc)
    {
        // Error, invalid pointer
        return DDENUMRET_CANCEL;
    }

    // Double check structure size
    if (lpDDSurfDesc->dwSize != sizeof(DDSURFACEDESC))
    {
        // Error, structure is wrong size
        return DDENUMRET_CANCEL;
    }

// Create Mode node
lpMode = new DDModeInfo;
if (! lpMode)
{
        // Error, not enough memory to store mode info
        return DDENUMRET_OK;
}

    // Copy surface description
    lpMode->ddSurfDesc = *lpDDSurfDesc;

// Add Mode to Driver Mode List
hResult = lpDriver->AddMode (lpMode);
if (FAILED (hResult))
{
        // Error, not enough memory to store mode info
        return DDENUMRET_OK;
}

    // Update mode count
    lpInfo->cCount++;

    return DDENUMRET_OK;
} // End ModeEnumCallback

/*
**-----------------------------------------------------------------------------
**  Name:       TextureFormatEnumCallback
**  Purpose:    Add this mode to the driver
**-----------------------------------------------------------------------------
*/

HRESULT WINAPI TextureFormatEnumCallback (
    LPDDSURFACEDESC lpTextureFormat,
    LPVOID          lpExtra)
{
HRESULThResult;
LPDDModeInfolpFormat;
    LPDD_CB_INFO lpInfo;
    LPD3DDevInfolpDevice;
    DWORD           dwIndex;

    if (! lpExtra)
    {
        // Programming error, invalid pointer
        return DDENUMRET_OK;
    }

    lpInfo   = (LPDD_CB_INFO)lpExtra;
    lpDevice = (LPD3DDevInfo)lpInfo->lpExtra;
    dwIndex  = lpInfo->cCount;

    if (! lpDevice)
    {
        // Programming error, invalid pointer
        return DDENUMRET_OK;
    }

    if (! lpTextureFormat)
    {
        // Error, invalid pointer
        return DDENUMRET_CANCEL;
    }

    // Double check structure size
    if (lpTextureFormat->dwSize != sizeof(DDSURFACEDESC))
    {
        // Error, structure is wrong size
        return DDENUMRET_CANCEL;
    }

// Create format node
lpFormat = new DDModeInfo;
if (! lpFormat)
{
        // Error, not enough memory to store format info
        return DDENUMRET_OK;
}

    // Copy texture format description
    lpFormat->ddSurfDesc = *lpTextureFormat;

// Add format to D3D device format list
hResult = lpDevice->AddFormat (lpFormat);
if (FAILED (hResult))
{
        // Error, not enough memory to store mode info
        return DDENUMRET_OK;
}

    // Update format count
    lpInfo->cCount++;

    return DDENUMRET_OK;
} // End TextureFormatEnumCallback

/*
**-----------------------------------------------------------------------------
**  Name:       DeviceEnumCallback
**  Purpose:    Add this D3D Device Driver info to the driver
**-----------------------------------------------------------------------------
*/

HRESULT WINAPI DeviceEnumCallback (
    LPGUID          lpGuid,
    LPTSTR          lpName,
    LPTSTR          lpDesc,
    LPD3DDEVICEDESC lpHalDevice,
    LPD3DDEVICEDESC lpHelDevice,
    LPVOID          lpExtra)
{
    HRESULT         hResult;
    LPDD_CB_INFOlpInfo;
    LPDDDrvInfo     lpDriver;
LPD3DDevInfolpDevice;
    DWORD           dwIndex;

    if (! lpExtra)
    {
        // Programming error, invalid pointer
        return DDENUMRET_OK;
    }

    lpInfo   = (LPDD_CB_INFO) lpExtra;
    lpDriver = (LPDDDrvInfo) lpInfo->lpExtra;
    dwIndex  = lpInfo->cCount;

    if (! lpDriver)
    {
        // Programming Error, invalid pointer
        return DDENUMRET_OK;
    }

// Create D3D Device node
lpDevice = new D3DDevInfo;
if (! lpDevice)
{
        // Not Enough memory to create D3D device node
        return DDENUMRET_OK;
}

    // Initialize D3D Device info
    hResult = lpDevice->Create (lpGuid, lpName, lpDesc,
                                lpHalDevice, lpHelDevice);
    if (FAILED (hResult))
    {
        // Error
        return DDENUMRET_OK;
    }

// Add to Driver D3D Device list
hResult = lpDriver->AddDevice (lpDevice);
if (FAILED (hResult))
{
        // Error
        return DDENUMRET_OK;
}

    // Update D3D device Driver count
    lpInfo->cCount++;

    return DDENUMRET_OK;
} // End DeviceEnumCallback

/*
**-----------------------------------------------------------------------------
**  DDModeInfo Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::GetWidth
**  Purpose:    Gets the Width for this mode
**-----------------------------------------------------------------------------
*/

DWORD DDModeInfo::GetWidth (void)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return 0L;

// Check that Pixel format is valid
if (! (ddSurfDesc.dwFlags & DDSD_WIDTH))
return 0L;

// Get Bits Per Pixel
return ddSurfDesc.dwWidth;
} // DDModeInfo::GetWidth

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::GetHeight
**  Purpose:    Gets the Height for this mode
**-----------------------------------------------------------------------------
*/

DWORD DDModeInfo::GetHeight (void)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return 0L;

// Check that Pixel format is valid
if (! (ddSurfDesc.dwFlags & DDSD_HEIGHT))
return 0L;

// Get Bits Per Pixel
return ddSurfDesc.dwHeight;
} // DDModeInfo::GetHeight

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::GetBPP
**  Purpose:    Gets the Bits per pixel for this mode
**-----------------------------------------------------------------------------
*/

DWORD DDModeInfo::GetBPP (void)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return 0L;

// Check that Pixel format is valid
if (! (ddSurfDesc.dwFlags & DDSD_PIXELFORMAT))
return 0L;
if (ddSurfDesc.ddpfPixelFormat.dwSize != sizeof(DDPIXELFORMAT))
return 0L;

// Assume it is RGB
return ddSurfDesc.ddpfPixelFormat.dwRGBBitCount;
} // DDModeInfo::GetBPP

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::GetMode
**  Purpose:    Gets the Mode info (w,h,bpp) for this mode
**-----------------------------------------------------------------------------
*/

HRESULT DDModeInfo::GetMode (
DWORD & dwW,
DWORD & dwH,
DWORD & dwBPP,
DWORD & dwRefresh)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return DDERR_GENERIC;

// Check that width is valid
if (! (ddSurfDesc.dwFlags & DDSD_WIDTH))
return DDERR_GENERIC;

// Check that height is valid
if (! (ddSurfDesc.dwFlags & DDSD_HEIGHT))
return DDERR_GENERIC;

// Check that Pixel format is valid
if (! (ddSurfDesc.dwFlags & DDSD_PIXELFORMAT))
return DDERR_GENERIC;
if (ddSurfDesc.ddpfPixelFormat.dwSize != sizeof(DDPIXELFORMAT))
return DDERR_GENERIC;

// Get Width, height, BPP
dwW= ddSurfDesc.dwWidth;
dwH= ddSurfDesc.dwHeight;
dwBPP= ddSurfDesc.ddpfPixelFormat.dwRGBBitCount;
dwRefresh= 0L;

// Success
return DD_OK;
} // DDModeInfo::GetMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::ModeSupportedByDevice
**  Purpose:
**-----------------------------------------------------------------------------
*/

BOOL DDModeInfo::ModeSupported (LPD3DDevInfo lpDevice)
{
// Check Parameters
if (! lpDevice)
return FALSE;

// Make sure D3D device supports this mode
DWORD dwBPP    = GetBPP ();
DWORD dwFlag   = BitDepthToFlags (dwBPP);
DWORD dwDepths = 0L;

// Get Supported Bit Depths for this D3D device
if (lpDevice->isHardware ())
dwDepths = lpDevice->d3dHalDesc.dwDeviceRenderBitDepth;
else
dwDepths = lpDevice->d3dHelDesc.dwDeviceRenderBitDepth;

if (dwDepths & dwFlag)
{
// Supported !!!
return TRUE;
}

// Not Supported !!!
return FALSE;
} // End DDModeInfo::ModeSupported

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::Match
**  Purpose:    Checks if this mode matches (w,h,bpp)
**-----------------------------------------------------------------------------
*/

BOOL DDModeInfo::Match (DWORD dwW, DWORD dwH, DWORD dwBPP)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return FALSE;

// Check for Match
    if ((ddSurfDesc.dwWidth  == dwW) &&
        (ddSurfDesc.dwHeight == dwH))
{
if (ddSurfDesc.ddpfPixelFormat.dwRGBBitCount == dwBPP)
return TRUE;
    }

    return FALSE;
} // DDModeInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Match
**  Purpose:    Checks if this mode matches with this surface desc
**-----------------------------------------------------------------------------
*/

BOOL DDModeInfo::Match (const DDSURFACEDESC & ddsd)
{
    // Check parameters
    if (ddsd.dwSize != sizeof (DDSURFACEDESC))
        return FALSE;

    // Check Height
    if (ddsd.dwFlags & DDSD_HEIGHT)
    {
        if (ddsd.dwHeight != ddSurfDesc.dwHeight)
            return FALSE;
    }

    // Check Width
    if (ddsd.dwFlags & DDSD_WIDTH)
    {
        if (ddsd.dwWidth != ddSurfDesc.dwWidth)
            return FALSE;
    }

    // Check Pitch
    if (ddsd.dwFlags & DDSD_PITCH)
    {
        if (ddsd.lPitch != ddSurfDesc.lPitch)
            return FALSE;
    }

    // Check Back Buffer count
    if (ddsd.dwFlags & DDSD_BACKBUFFERCOUNT)
    {
        if (ddsd.dwBackBufferCount != ddSurfDesc.dwBackBufferCount)
            return FALSE;
    }

    // Check MipMap count
    if (ddsd.dwFlags & DDSD_MIPMAPCOUNT)
    {
        if (ddsd.dwMipMapCount != ddSurfDesc.dwMipMapCount)
            return FALSE;
    }

    // Check ZBufferBitDepth
    if (ddsd.dwFlags & DDSD_ZBUFFERBITDEPTH)
    {
        if (ddsd.dwZBufferBitDepth != ddSurfDesc.dwZBufferBitDepth)
            return FALSE;
    }

    // Check Refresh Rate
    if (ddsd.dwFlags & DDSD_REFRESHRATE)
    {
        if (ddsd.dwRefreshRate != ddSurfDesc.dwRefreshRate)
            return FALSE;
    }

    // Check Alpha Bit Depth
    if (ddsd.dwFlags & DDSD_ALPHABITDEPTH)
    {
        if (ddsd.dwAlphaBitDepth != ddSurfDesc.dwAlphaBitDepth)
            return FALSE;
    }

    // Check ColorKey Dest Blt
    if (ddsd.dwFlags & DDSD_CKDESTBLT)
    {

        if (ddsd.ddckCKDestBlt.dwColorSpaceLowValue !=
            ddSurfDesc.ddckCKDestBlt.dwColorSpaceLowValue)
            return FALSE;

        if (ddsd.ddckCKDestBlt.dwColorSpaceHighValue !=
            ddSurfDesc.ddckCKDestBlt.dwColorSpaceHighValue)
            return FALSE;
    }

    // Check ColorKey Dest Overlay
    if (ddsd.dwFlags & DDSD_CKDESTBLT)
    {

        if (ddsd.ddckCKDestOverlay.dwColorSpaceLowValue !=
            ddSurfDesc.ddckCKDestOverlay.dwColorSpaceLowValue)
            return FALSE;

        if (ddsd.ddckCKDestOverlay.dwColorSpaceHighValue !=
            ddSurfDesc.ddckCKDestOverlay.dwColorSpaceHighValue)
            return FALSE;
    }

    // Check ColorKey Src Blt

if (ddsd.dwFlags & DDSD_CKSRCBLT)
    {

        if (ddsd.ddckCKSrcBlt.dwColorSpaceLowValue !=
            ddSurfDesc.ddckCKSrcBlt.dwColorSpaceLowValue)
            return FALSE;

        if (ddsd.ddckCKSrcBlt.dwColorSpaceHighValue !=
            ddSurfDesc.ddckCKSrcBlt.dwColorSpaceHighValue)
            return FALSE;
    }

    // Check ColorKey Src Overlay
    if (ddsd.dwFlags & DDSD_CKSRCOVERLAY)
    {

        if (ddsd.ddckCKSrcOverlay.dwColorSpaceLowValue !=
            ddSurfDesc.ddckCKSrcOverlay.dwColorSpaceLowValue)
            return FALSE;

        if (ddsd.ddckCKSrcOverlay.dwColorSpaceHighValue !=
            ddSurfDesc.ddckCKSrcOverlay.dwColorSpaceHighValue)
            return FALSE;
    }

    // Check Pixel Format
    if (ddsd.dwFlags & DDSD_PIXELFORMAT)
    {
if (! Match (ddsd.ddpfPixelFormat))
return FALSE;
    }

    // Check Caps
    if (ddsd.dwFlags & DDSD_CAPS)
    {
        // Superset is OK
        if ((ddsd.ddsCaps.dwCaps & ddSurfDesc.ddsCaps.dwCaps) !=
            ddsd.ddsCaps.dwCaps)
            return FALSE;
    }

    // Success, we have a match
    return TRUE;
} // DDModeInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo::Match
**  Purpose:    Checks if this mode matches (bpp)
**-----------------------------------------------------------------------------
*/

BOOL DDModeInfo::Match (DWORD dwBPP)
{
    // Check parameters
    if (ddSurfDesc.dwSize != sizeof (DDSURFACEDESC))
        return FALSE;

// Check for Match
if (ddSurfDesc.ddpfPixelFormat.dwRGBBitCount == dwBPP)
return TRUE;

    return FALSE;
} // DDModeInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Match
**  Purpose:    Checks if this mode matches the requested pixel format
**-----------------------------------------------------------------------------
*/

BOOL DDModeInfo::Match (const DDPIXELFORMAT & ddpf)
{
DWORD dwCheck;

    // Check parameters
if (ddpf.dwSize != sizeof (DDPIXELFORMAT))
return FALSE;

// Check Alpha-Only flag
    if (ddpf.dwFlags & DDPF_ALPHA)
    {
// Is this mode alpha only too ?!?
        if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ALPHA))
            return FALSE;

// Do Alpha bit depths match ?!?
if (ddpf.dwAlphaBitDepth != ddSurfDesc.ddpfPixelFormat.dwAlphaBitDepth)
return FALSE;
    }

// Check ZBuffer-Only flag
    if (ddpf.dwFlags & DDPF_ZBUFFER)
    {
// Is this mode Z-buffer only too ?!?
        if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ZBUFFER))
            return FALSE;

// Do Z-depths match ?!?
if (ddpf.dwZBufferBitDepth != ddSurfDesc.ddpfPixelFormat.dwZBufferBitDepth)
return FALSE;
    }

// Check Compressed flag
if (ddpf.dwFlags & DDPF_COMPRESSED)
{
// Is this mode compressed too ?!?
        if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_COMPRESSED))
            return FALSE;
}

// Check FourCC flag
if (ddpf.dwFlags & DDPF_FOURCC)
{
// Is this mode a fourCC code too ?!?
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_FOURCC))
return FALSE;

// Do fourCC codes match ?!?
if (ddSurfDesc.ddpfPixelFormat.dwFourCC != ddpf.dwFourCC)
return FALSE;
}

    // Check Palette flags
dwCheck = DDPF_PALETTEINDEXED1 | DDPF_PALETTEINDEXED2 |
      DDPF_PALETTEINDEXED4 | DDPF_PALETTEINDEXED8 |
  DDPF_PALETTEINDEXEDTO8;
if (ddpf.dwFlags & dwCheck)
{
DWORD dwOne = ddpf.dwFlags & dwCheck;
DWORD dwTwo = ddSurfDesc.ddpfPixelFormat.dwFlags & dwCheck;

// Do palette formats match ?!?
if (dwOne != dwTwo)
return FALSE;
}

// Check RGB flags
if (ddpf.dwFlags & DDPF_RGB)
{
// Does this mode support RGB too ???
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_RGB))
return FALSE;

// Check RGBTOYUV flag
if (ddpf.dwFlags & DDPF_RGBTOYUV)
{
// Does this mode support RGBTOYUV too???
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_RGBTOYUV))
return FALSE;
}

// Do BPP's match?
if (ddpf.dwRGBBitCount != ddSurfDesc.ddpfPixelFormat.dwRGBBitCount)
return FALSE;

// Do Masks match
if (ddpf.dwRBitMask != ddSurfDesc.ddpfPixelFormat.dwRBitMask)
return FALSE;

if (ddpf.dwGBitMask != ddSurfDesc.ddpfPixelFormat.dwGBitMask)
return FALSE;

if (ddpf.dwBBitMask != ddSurfDesc.ddpfPixelFormat.dwBBitMask)
return FALSE;

// Check Alpha Channel
if (ddpf.dwFlags & DDPF_ALPHAPIXELS)
{
// Does this mode support Alphachannel as well ?!?
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS))
return FALSE;

// Do Alpha channel masks match
if (ddpf.dwRGBAlphaBitMask != ddSurfDesc.ddpfPixelFormat.dwRGBAlphaBitMask)
return FALSE;
}

// Check Z channel
if (ddpf.dwFlags & DDPF_ZPIXELS)
{
// Does this mode support Z channel as well ?!?
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ZPIXELS))
return FALSE;

// Do Z channel masks match
if (ddpf.dwRGBZBitMask != ddSurfDesc.ddpfPixelFormat.dwRGBZBitMask)
return FALSE;
}
}

// Check YUV flags
if (ddpf.dwFlags & DDPF_YUV)
{
// Does this mode support YUV too ???
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_YUV))
return FALSE;

// Do BPP's match?
if (ddpf.dwYUVBitCount != ddSurfDesc.ddpfPixelFormat.dwYUVBitCount)
return FALSE;

// Do Masks match
if (ddpf.dwYBitMask != ddSurfDesc.ddpfPixelFormat.dwYBitMask)
return FALSE;

if (ddpf.dwUBitMask != ddSurfDesc.ddpfPixelFormat.dwUBitMask)
return FALSE;

if (ddpf.dwVBitMask != ddSurfDesc.ddpfPixelFormat.dwVBitMask)
return FALSE;

// Check Alpha channel
if (ddpf.dwFlags & DDPF_ALPHAPIXELS)
{
// Does this mode support Alphachannel as well ?!?
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS))
return FALSE;

// Do Alpha channel masks match
if (ddpf.dwYUVAlphaBitMask != ddSurfDesc.ddpfPixelFormat.dwYUVAlphaBitMask)
return FALSE;
}

// Check Z channel
if (ddpf.dwFlags & DDPF_ZPIXELS)
{
// Does this mode support Z channel as well ?!?
if (! (ddSurfDesc.ddpfPixelFormat.dwFlags & DDPF_ZPIXELS))
return FALSE;

// Do Z channel masks match
if (ddpf.dwRGBZBitMask != ddSurfDesc.ddpfPixelFormat.dwRGBZBitMask)
return FALSE;
}
}

    // Success, we have a match
    return TRUE;
} // DDModeInfo::Match

/*
**-----------------------------------------------------------------------------
**  D3DDevInfo Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::isHardware
**  Purpose:    Checks if this D3D Device is a hardware driver
**-----------------------------------------------------------------------------
*/

BOOL D3DDevInfo::isHardware (void)
{
    // No correct way of doing this,
    // but if the hardware caps don't specify a shading
    // model then it probably isn't hardware accelerated
    DWORD dwColorModel = d3dHalDesc.dcmColorModel;
    if (dwColorModel)
        return TRUE;
    return FALSE;
} // End D3DDevInfo::isHardware

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Match
**  Purpose:    Checks if this D3D Device matches this guid
**-----------------------------------------------------------------------------
*/

BOOL D3DDevInfo::Match (LPGUID lpGuid)
{
    if (lpGuid == NULL)
        return FALSE;

    if (! isValid ())
        return FALSE;

    if (guid != *lpGuid)
        return FALSE;

    // Success
    return TRUE;
} // End D3DDevInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::Match
**  Purpose:    Checks if this D3D Device matches the specified
**              hardware and/or software description
**-----------------------------------------------------------------------------
*/

BOOL D3DDevInfo::Match (LPD3DDEVICEDESC lpHal, LPD3DDEVICEDESC lpHel)
{
    if (! isValid ())
        return FALSE;

    // Check Parameters
    if ((! lpHal) && (! lpHel))
        return FALSE;

    //
    // Compare Hal description's
    //
    if (lpHal)
    {
        if (lpHal->dwSize != sizeof (D3DDEVICEDESC))
            return FALSE;

        // Check ColorModel
        if (lpHal->dwFlags & D3DDD_COLORMODEL)
        {
            // If it does more than we are interested in that's OK
            if ((lpHal->dcmColorModel & d3dHalDesc.dcmColorModel) !=
                lpHal->dcmColorModel)
                return FALSE;
        }

        // Check Device Caps
        if (lpHal->dwFlags & D3DDD_DEVCAPS)
        {
            // If it does more than we are asking, that's OK
            if ((lpHal->dwDevCaps & d3dHalDesc.dwDevCaps) !=
                lpHal->dwDevCaps)
                return FALSE;
        }

        // Check transform caps
        if (lpHal->dwFlags & D3DDD_TRANSFORMCAPS)
        {
            // Note: Flesh this out later
        }

        // Check Clipping
        if (lpHal->dwFlags & D3DDD_BCLIPPING)
        {
            if (lpHal->bClipping != d3dHalDesc.bClipping)
                return FALSE;
        }

        // Check lighting caps
        if (lpHal->dwFlags & D3DDD_LIGHTINGCAPS)
        {
            // Note: Flesh this out later
        }

        // Check line caps
        if (lpHal->dwFlags & D3DDD_LINECAPS)
        {
            // Note: Flesh this out later
        }

        // Check triangle caps
        if (lpHal->dwFlags & D3DDD_TRICAPS)
        {
            // Note: Flesh this out later
        }

        // Check Render Surface bit depth
        if (lpHal->dwDeviceRenderBitDepth)
        {
             if (lpHal->dwDeviceRenderBitDepth != d3dHalDesc.dwDeviceRenderBitDepth)
                return FALSE;
        }

        // Check Z-Buffer surface bit depth
        if (lpHal->dwDeviceZBufferBitDepth)
        {
            if (lpHal->dwDeviceZBufferBitDepth != d3dHalDesc.dwDeviceZBufferBitDepth)
                return FALSE;
        }

        // Check Max buffer size
        if (lpHal->dwFlags & D3DDD_MAXBUFFERSIZE)
        {
            // Only worry, if it is smaller than what we want
            if (lpHal->dwMaxBufferSize > d3dHalDesc.dwMaxBufferSize)
                return FALSE;
        }

        // Check Max Vertex count
        if (lpHal->dwFlags & D3DDD_MAXVERTEXCOUNT)
        {
            // Only worry, if it is smaller than what we want
            if (lpHal->dwMaxVertexCount > d3dHalDesc.dwMaxVertexCount)
                return FALSE;
        }
    }

    //
    // Compare Hel description's
    //
    if (lpHel)
    {
        if (lpHel->dwSize != sizeof (D3DDEVICEDESC))
            return FALSE;

        // Check ColorModel
        if (lpHel->dwFlags & D3DDD_COLORMODEL)
        {
            // If it does more than we are interested in that's OK
            if ((lpHel->dcmColorModel & d3dHelDesc.dcmColorModel) !=
                lpHel->dcmColorModel)
                return FALSE;
        }

        // Check Device Caps
        if (lpHel->dwFlags & D3DDD_DEVCAPS)
        {
            // If it does more than we are asking, that's OK
            if ((lpHel->dwDevCaps & d3dHelDesc.dwDevCaps) !=
                lpHel->dwDevCaps)
                return FALSE;
        }

        // Check transform caps
        if (lpHel->dwFlags & D3DDD_TRANSFORMCAPS)
        {
            // Note: Flesh this out later
        }

        // Check Clipping
        if (lpHel->dwFlags & D3DDD_BCLIPPING)
        {
            if (lpHel->bClipping != d3dHelDesc.bClipping)
                return FALSE;
        }

        // Check lighting caps
        if (lpHel->dwFlags & D3DDD_LIGHTINGCAPS)
        {
            // Note: Flesh this out later
        }

        // Check line caps
        if (lpHel->dwFlags & D3DDD_LINECAPS)
        {
            // Note: Flesh this out later
        }

        // Check triangle caps
        if (lpHel->dwFlags & D3DDD_TRICAPS)
        {
            // Note: Flesh this out later
        }

        // Check Render Surface bit depth
        if (lpHel->dwDeviceRenderBitDepth)
        {
             if (lpHel->dwDeviceRenderBitDepth != d3dHelDesc.dwDeviceRenderBitDepth)
                return FALSE;
        }

        // Check Z-Buffer surface bit depth
        if (lpHel->dwDeviceZBufferBitDepth)
        {
            if (lpHel->dwDeviceZBufferBitDepth != d3dHelDesc.dwDeviceZBufferBitDepth)
                return FALSE;
        }

        // Check Max buffer size
        if (lpHel->dwFlags & D3DDD_MAXBUFFERSIZE)
        {
            // Only worry, if it is smaller than what we want
            if (lpHel->dwMaxBufferSize > d3dHelDesc.dwMaxBufferSize)
                return FALSE;
        }

        // Check Max Vertex count
        if (lpHel->dwFlags & D3DDD_MAXVERTEXCOUNT)
        {
            // Only worry, if it is smaller than what we want
            if (lpHel->dwMaxVertexCount > d3dHelDesc.dwMaxVertexCount)
                return FALSE;
        }
    }

    // Success
    return TRUE;
} // End D3DDevInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::Create
**  Purpose:    Creates a new D3D Device
**-----------------------------------------------------------------------------
*/

HRESULT D3DDevInfo::Create (
    LPGUID          lpD3DGuid,
    LPTSTR          lpD3DName,
    LPTSTR          lpD3DDesc,
    LPD3DDEVICEDESC lpD3DHal,
    LPD3DDEVICEDESC lpD3DHel)
{
    // Copy GUID
    if (! lpD3DGuid)
    {
        // Error, Invalid device
        return DDERR_INVALIDPARAMS;
    }
    guid = *lpD3DGuid;

// Copy Name
LPTSTR szTemp;
    if (! lpD3DName)
    {
szTemp = TEXT("UNKNOWN");
    }
    else
    {
        szTemp = lpD3DName;
    }

    DWORD cLen = _tcslen (szTemp);
DWORD cbSize = (cLen + 1) * sizeof(TCHAR);
szName = (LPTSTR) malloc (cbSize);
if (szName)
{
_tcsncpy (szName, szTemp, cLen);
szName[cLen] = 0;
}

    // Copy Description
    if (! lpD3DDesc)
    {
szTemp = TEXT("UNKNOWN");
    }
    else
    {
        szTemp = lpD3DDesc;
    }

    cLen = _tcslen (szTemp);
cbSize = (cLen + 1) * sizeof(TCHAR);
szDesc = (LPTSTR) malloc (cbSize);
if (szDesc)
{
_tcsncpy (szDesc, szTemp, cLen);
szDesc[cLen] = 0;
}

    // Copy D3D info
    if (lpD3DHal)
    {
        d3dHalDesc = *lpD3DHal;
    }

    if (lpD3DHel)
    {
        d3dHelDesc = *lpD3DHel;
    }

// Mark Texture format list as not loaded
cFormats = 0L;
turnFormatsLoadedOff ();

// Mark as valid
    validOn ();

    return DD_OK;
} // End D3DDevInfo::Create

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::Destroy
**  Purpose:    Cleanup any memory or interfaces
**-----------------------------------------------------------------------------
*/

void D3DDevInfo::Destroy (void)
{
// Destroy Texture Formats
DestroyFormats ();

// Clean up strings
if (szDesc)
{
free (szDesc);
szDesc = NULL;
}

if (szName)
{
free (szName);
szName = NULL;
}

lpPrev = NULL;
lpNext = NULL;
} // End D3DDevInfo::Destroy

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::LoadFormats
**  Purpose:    Loads texture formats
**  Notes:
**
**In order to load texture formats, we need to have a valid D3D Device
**Getting a D3D Device directly complicates the Driver Manager code.
**
**So we will defer loading texture formats until we have created a valid
**  D3D device in D3DWindow::InitRender and then call this function to
**  Load all the texture formats.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DDevInfo::LoadFormats (LPDIRECT3DDEVICE2 lpD3DDevice)
{
// Have we already loaded the texture formats
if (! formatsLoaded ())
{
HRESULT hResult;
    DD_CB_INFOcbInfo;

// Check Parameters
if (! lpD3DDevice)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

// Enumerate all Texture Formats for this device
cbInfo.fResult  = TRUE;
cbInfo.lpExtra  = (void *)this;
cbInfo.cCount   = 0L;

hResult = lpD3DDevice->EnumTextureFormats (TextureFormatEnumCallback,
   (void *)&cbInfo);
if (FAILED(hResult))
{
// Error
REPORTERR (hResult);
return hResult;
}

// Double check count
if ((! cbInfo.fResult) || (cbInfo.cCount == 0) || (cFormats != cbInfo.cCount))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}

// Mark texture formats as loaded
turnFormatsLoadedOn ();
}

// Success
return DD_OK;
} // D3DDevInfo::LoadFormats

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::DestroyFormats
**  Purpose:    Destroys texture formats
**-----------------------------------------------------------------------------
*/

HRESULT D3DDevInfo::DestroyFormats (void)
{
if (formatsLoaded ())
{
LPDDModeInfo lpCurr, lpNext, lpPrev;

lpCurr = lpFormatRoot;

// Walk linked list and destroy all Format nodes
while (lpCurr)
{
lpNext = lpCurr->lpNext;
lpPrev = lpCurr->lpPrev;

// Remove node from List
if (lpPrev)
lpPrev->lpNext = lpNext;
else
lpFormatRoot = lpNext;

if (lpNext)
lpNext->lpPrev = lpPrev;
else
lpFormatTail = lpPrev;

// Destroy this node
lpCurr->lpNext = NULL;
lpCurr->lpPrev = NULL;
delete lpCurr;

// Move to next node in list
lpCurr = lpNext;
}

cFormats = 0L;
lpFormatRoot = NULL;
lpFormatTail = NULL;

// Mark as unloaded
turnFormatsLoadedOff ();
}

// Success
return DD_OK;
} // End D3DDevInfo::DestroyFormats

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::AddFormat
**  Purpose:    add new texture format to format list
**-----------------------------------------------------------------------------
*/

HRESULT D3DDevInfo::AddFormat (LPDDModeInfo lpFormatNew)
{
// Check Parameters
if (! lpFormatNew)
{
// Error, Invalid parameters
return DDERR_INVALIDPARAMS;
}

// Add to tail of Format List
lpFormatNew->lpPrev = lpFormatTail;
lpFormatNew->lpNext = NULL;

// Update tail
if (lpFormatTail)
lpFormatTail->lpNext = lpFormatNew;
lpFormatTail = lpFormatNew;

// Update Root
if (! lpFormatRoot)
lpFormatRoot = lpFormatNew;

// Update count
cFormats++;

// Success
return DD_OK;
} // End D3DDevInfo::AddFormat

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::DelFormat
**  Purpose:    removes texture format from format list
**-----------------------------------------------------------------------------
*/

HRESULT D3DDevInfo::DelFormat (LPDDModeInfo lpFormatDel)
{
// Check parameters
if (lpFormatDel)
{
// Error
return DDERR_INVALIDPARAMS;
}

// Remove this Mode From mode list
    LPDDModeInfo lpPrevMode = lpFormatDel->lpPrev;
    LPDDModeInfo lpNextMode = lpFormatDel->lpNext;

    if (lpPrevMode)
        lpPrevMode->lpNext = lpNextMode;
    else
        lpFormatRoot = lpNextMode;

    if (lpNextMode)
lpNextMode->lpPrev = lpPrevMode;
else
lpFormatTail = lpPrevMode;

    // Destroy format node
lpFormatDel->lpPrev = NULL;
    lpFormatDel->lpNext = NULL;
    delete lpFormatDel;

// Update count
cFormats--;

// Success
return DD_OK;
} // End D3DDevInfo::DelFormat

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::FindFormat
**  Purpose:    finds specified texture format
**-----------------------------------------------------------------------------
*/

LPDDModeInfo D3DDevInfo::FindFormat (
LPDDPIXELFORMAT lpddpf,/* In:Texture Format Desc */
LPDDModeInfo *  lpNextBest,/* Out:Second best match */
LPDDModeInfo    lpStart)/* In:start from this node */
{
LPDDModeInfolpCurrFormat, lpNextFormat;

// Get Starting node
if (! lpStart)
lpCurrFormat = lpFormatRoot;
else
lpCurrFormat = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrFormat;

// Search format list for best match
    while (lpCurrFormat)
{
lpNextFormat = lpCurrFormat->lpNext;
        if (lpCurrFormat->Match (*lpddpf))
        {
            return lpCurrFormat;
        }

lpCurrFormat = lpNextFormat;
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End D3DDevInfo::FindFormat

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::FindFormat
**  Purpose:    finds specified texture format from requested BPP
**-----------------------------------------------------------------------------
*/

LPDDModeInfo D3DDevInfo::FindFormat (
DWORDbpp,/* In:requested BPP */
LPDDModeInfo *  lpNextBest,/* Out:Second best match */
LPDDModeInfo    lpStart)/* In:start from this node */
{
LPDDModeInfolpCurrFormat, lpNextFormat;

// Get Starting node
if (! lpStart)
lpCurrFormat = lpFormatRoot;
else
lpCurrFormat = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrFormat;

// Search format list for best match
    while (lpCurrFormat)
{
lpNextFormat = lpCurrFormat->lpNext;
        if (lpCurrFormat->Match (bpp))
        {
            return lpCurrFormat;
        }

lpCurrFormat = lpNextFormat;
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End D3DDevInfo::FindFormat

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo::EnumFormats
**  Purpose:    Enumerate Formats in this Device
**-----------------------------------------------------------------------------
*/

DWORD D3DDevInfo::EnumFormats (const D3DDEV_ENUMINFO & eiInfo)
{
    DWORD           dwResult = ENUM_FAILURE;
LPDDModeInfolpCurrFormat, lpNextFormat;

    // Check Match Callback function
    if (! eiInfo.fpcbEnum)
    {
        // Error, invalid callback
        return ENUM_ERROR;
    }

// Get Starting node
if (eiInfo.lpStart)
lpCurrFormat = (LPDDModeInfo)(eiInfo.lpStart);
else
lpCurrFormat = lpFormatRoot;

// Do callback on each node in list
while (lpCurrFormat)
{
lpNextFormat = lpCurrFormat->lpNext;

        // Call Enum Mode Callback
        dwResult = (*eiInfo.fpcbEnum)((LPVOID)this, lpCurrFormat, eiInfo.dwExtra);

        // Check for early exit
        if (dwResult & ENUM_STOP)
        {
            // Return result code
            return (dwResult & ~ENUM_STOP);
        }
   }

    // Failure
    return (dwResult & ~ENUM_STOP);
} // End D3DDevInfo::EnumFormats

/*
**-----------------------------------------------------------------------------
**  DDDrvInfo Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Create
**  Purpose:    Create a new Driver description
**  Notes:
**1.  If the driver doesn't support D3D we treat it as an invalid driver.
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::Create (
GUID FAR *      lpGuid,
    LPTSTR          lpDriverName,
    LPTSTR          lpDriverDesc)
{
    HRESULT         hResult;
    LPDIRECTDRAW    lpDD  = NULL;
    LPDIRECTDRAW2   lpDD2 = NULL;
    LPDIRECT3D2     lpD3D = NULL;

    if (isValid ())
    {
        // Programmer Error, already valid, call Fini to cleanup
        return FALSE;
    }

    // Copy GUID
    if (! lpGuid)
    {
        primaryOn ();
    }
    else
    {
        guid = *lpGuid;
    }

    // Copy Name
LPTSTRszTemp;
    if (! lpDriverName)
{
szTemp = TEXT("UNKNOWN");
}
    else
    {
szTemp = lpDriverName;
    }

DWORD cLen = _tcslen (szTemp);
DWORD cbSize = (cLen + 1) * sizeof(TCHAR);
szName = (LPTSTR) malloc (cbSize);
if (szName)
{
_tcsncpy (szName, szTemp, cLen);
szName[cLen] = 0;
}

    // Copy Desc
    if (! lpDriverDesc)
{
szTemp = TEXT("UNKNOWN");
}
    else
    {
szTemp = lpDriverDesc;
    }

cLen = _tcslen (szTemp);
cbSize = (cLen + 1) * sizeof(TCHAR);
szDesc = (LPTSTR) malloc (cbSize);
if (szDesc)
{
_tcsncpy (szDesc, szTemp, cLen);
szDesc[cLen] = 0;
}

    // Create DirectDraw Object
    hResult = DirectDrawCreate (lpGuid, &lpDD, NULL);
    if FAILED (hResult)
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Get The DirectDraw2 Interface
    hResult = lpDD->QueryInterface ((REFIID)IID_IDirectDraw2, (void **)&lpDD2);
    if (FAILED(hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Get The Direct3D Interface
    hResult = lpDD->QueryInterface ((REFIID)IID_IDirect3D2, (void **)&lpD3D);
    if (FAILED(hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Get The Driver Caps
    ddHalCaps.dwSize = sizeof(DDCAPS);
    ddHelCaps.dwSize = sizeof(DDCAPS);
    hResult = lpDD2->GetCaps (&ddHalCaps, &ddHelCaps);
    if (FAILED(hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

// Enumerate all Modes for this DD Driver
cModes = 0L;
turnModesLoadedOff ();

hResult = LoadModes (lpDD2);
if (FAILED (hResult))
goto lblCLEANUP;

// Enumerate all D3D Devices for this driver
cDevices = 0L;
turnDevicesLoadedOff ();

hResult = LoadDevices (lpD3D);
if (FAILED (hResult))
goto lblCLEANUP;

    // Mark as Valid Driver
    validOn ();

    // Success
    hResult = DD_OK;

lblCLEANUP:
    // Cleanup the Interfaces before leaving
    if (lpD3D)
    {
        lpD3D->Release ();
        lpD3D = NULL;
    }

    if (lpDD2)
    {
        lpDD2->Release ();
        lpDD2 = NULL;
    }

    if (lpDD)
    {
        lpDD->Release ();
        lpDD = NULL;
    }

    return hResult;
} // End DDDrvInfo::Create

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Destroy
**  Purpose:    Cleanup any memory or interfaces
**-----------------------------------------------------------------------------
*/

void DDDrvInfo::Destroy (void)
{
// Destroy all Modes and Devices
DestroyDevices ();
DestroyModes ();

// Clean up strings
if (szDesc)
{
free (szDesc);
szDesc = NULL;
}

if (szName)
{
free (szName);
szName = NULL;
}

lpPrev = NULL;
lpNext = NULL;

    // Mark as an invalid driver
    validOff ();
} // End DDDrvInfo::Destroy

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Match
**  Purpose:    checks for match with specified guid
**-----------------------------------------------------------------------------
*/

BOOL DDDrvInfo::Match (LPGUID lpGuid)
{
    if (! isValid())
        return FALSE;

    if (! lpGuid)
    {
        if (isPrimary())
            return TRUE;
    }
    else
    {
        if (*lpGuid == guid)
            return TRUE;

}

    return FALSE;
} // End DDDrvInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::Match
**  Purpose:    checks for match with specified hal,hel caps
**-----------------------------------------------------------------------------
*/

BOOL DDDrvInfo::Match (LPDDCAPS lpHal, LPDDCAPS lpHel)
{
    if (! isValid())
        return FALSE;

    if ((! lpHal) && (! lpHel))
        return FALSE;

    // Check for match with hal caps
    if (lpHal)
    {
        // Flesh out later
    }

    if (lpHal)
    {
        // Flesh out later
    }

    // Success
    return TRUE;
} // End DDDrvInfo::Match

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::LoadModes
**  Purpose:    Load all modes associated with this DD Driver
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::LoadModes (LPDIRECTDRAW2 lpDD2)
{
// Have we already loaded the modes
if (! modesLoaded ())
{
HRESULT hResult;
    DD_CB_INFOcbInfo;

// Check Parameters
if (! lpDD2)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

    // Enumerate all modes for this driver
cbInfo.fResult  = TRUE;
cbInfo.lpExtra  = (void *)this;
cbInfo.cCount   = 0L;

hResult = lpDD2->EnumDisplayModes (0L, NULL, &cbInfo,
                                           ModeEnumCallback);
if (FAILED(hResult))
{
// Error
REPORTERR (hResult);
return hResult;
}

// Double check count
if ((! cbInfo.fResult) || (cbInfo.cCount == 0) || (cModes != cbInfo.cCount))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}

// Mark Modes as loaded
turnModesLoadedOn ();
}

// Success
return DD_OK;
} // D3DDevInfo::LoadModes

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::DestroyModes
**  Purpose:    Destroys all Modes in Mode List
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::DestroyModes (void)
{
LPDDModeInfo lpCurr, lpNext, lpPrev;

lpCurr = lpModeRoot;

// Walk linked list and destroy all Mode nodes
while (lpCurr)
{
lpNext = lpCurr->lpNext;
lpPrev = lpCurr->lpPrev;

// Remove node from List
if (lpPrev)
lpPrev->lpNext = lpNext;
else
lpModeRoot = lpNext;

if (lpNext)
lpNext->lpPrev = lpPrev;
else
lpModeTail = lpPrev;

// Destroy this node
lpCurr->lpNext = NULL;
lpCurr->lpPrev = NULL;
delete lpCurr;

// Move to next node in list
lpCurr = lpNext;
}

cModes   = 0L;
lpModeRoot = NULL;
lpModeTail = NULL;

turnModesLoadedOff ();

// Success
return DD_OK;
} // End DDDrvInfo::DestroyModes

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::AddMode
**  Purpose:    add new mode to mode list
**  Notes:1
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::AddMode (LPDDModeInfo lpModeNew)
{
// Check Parameters
if (! lpModeNew)
{
// Error, Invalid parameters
return DDERR_INVALIDPARAMS;
}

// Add to tail of Mode List
lpModeNew->lpPrev = lpModeTail;
lpModeNew->lpNext = NULL;

// Update tail
if (lpModeTail)
lpModeTail->lpNext = lpModeNew;
lpModeTail = lpModeNew;

// Update Root
if (! lpModeRoot)
lpModeRoot = lpModeNew;

// Update count
cModes++;

// Success
return DD_OK;
} // End DDDrvInfo::AddMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::DelMode
**  Purpose:    removes mode from mode list
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::DelMode (LPDDModeInfo lpModeDel)
{
// Check parameters
if (lpModeDel)
{
// Error
return DDERR_INVALIDPARAMS;
}

// Remove this Mode From mode list
    LPDDModeInfo lpPrevMode = lpModeDel->lpPrev;
    LPDDModeInfo lpNextMode = lpModeDel->lpNext;

    if (lpPrevMode)
        lpPrevMode->lpNext = lpNextMode;
    else
        lpModeRoot = lpNextMode;

    if (lpNextMode)
lpNextMode->lpPrev = lpPrevMode;
else
lpModeTail = lpPrevMode;

lpModeDel->lpPrev = NULL;
    lpModeDel->lpNext = NULL;

    // Destroy mode node
    delete lpModeDel;

// Update count
cModes--;

// Success
return DD_OK;
} // End DDDrvInfo::DelMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindMode
**  Purpose:    finds specified mode
**-----------------------------------------------------------------------------
*/

LPDDModeInfo DDDrvInfo::FindMode (
DWORDdwW,/* In:  Match this width */
DWORDdwH,/* In:  Match this Height */
DWORDdwBPP,/* In:  Match this Bits Per Pixel */
DWORD           dwRefresh,/* In:  Not supported yet... */
LPDDModeInfo *lpNextBest,/* Out: Return next best match */
LPDDModeInfo    lpStart) /* In:  Start search from this mode */
{
LPDDModeInfolpCurrMode, lpNextMode;

// Get Starting node
if (! lpStart)
lpCurrMode = lpModeRoot;
else
lpCurrMode = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrMode;

// Search mode list for best match
    while (lpCurrMode)
{
lpNextMode = lpCurrMode->lpNext;
        if (lpCurrMode->Match (dwW, dwH, dwBPP))
        {
return lpCurrMode;
        }
        else if (lpCurrMode->Match (640, 480, 8))
        {
if (lpNextBest)
*lpNextBest = lpCurrMode;
        }
lpCurrMode = lpNextMode;
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End DDDrvInfo::FindMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindMode
**  Purpose:    finds specified mode
**-----------------------------------------------------------------------------
*/

LPDDModeInfo DDDrvInfo::FindMode (
LPDDSURFACEDESC lpddsd,/* In:Mode Desc */
LPDDModeInfo *  lpNextBest,/* Out:Second best match */
LPDDModeInfo    lpStart)/* In:start from this node */
{
LPDDModeInfolpCurrMode, lpNextMode;

// Get Starting node
if (! lpStart)
lpCurrMode = lpModeRoot;
else
lpCurrMode = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrMode;

// Search mode list for best match
    while (lpCurrMode)
{
lpNextMode = lpCurrMode->lpNext;
        if (lpCurrMode->Match (*lpddsd))
        {
            return lpCurrMode;
        }
        else if (lpCurrMode->Match (640, 480, 8))
        {
if (lpNextBest)
*lpNextBest = lpCurrMode;
        }
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End DDDrvInfo::FindMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::EnumModes
**  Purpose:    Enumerate modes in this Driver
**-----------------------------------------------------------------------------
*/

DWORD DDDrvInfo::EnumModes (const DDDRV_ENUMINFO & eiInfo)
{
    DWORD           dwResult = ENUM_FAILURE;
LPDDModeInfolpCurrMode, lpNextMode;

    // Check Match Callback function
    if (! eiInfo.fpcbEnum)
    {
        // Error, invalid callback
        return ENUM_ERROR;
    }

// Get Starting node
if (eiInfo.lpStart)
lpCurrMode = (LPDDModeInfo)(eiInfo.lpStart);
else
lpCurrMode = lpModeRoot;

// Do callback on each node in list
while (lpCurrMode)
{
lpNextMode = lpCurrMode->lpNext;

        // Call Enum Mode Callback
        dwResult = (*eiInfo.fpcbEnum)((LPVOID)this, lpCurrMode, eiInfo.dwExtra);

        // Check for early exit
        if (dwResult & ENUM_STOP)
        {
            // Return result code
            return (dwResult & ~ENUM_STOP);
        }
   }

    // Failure
    return (dwResult & ~ENUM_STOP);
} // End DDDrvInfo::EnumModes

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::LoadDevices
**  Purpose:    Load all Devices associated with this DD Driver
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::LoadDevices (LPDIRECT3D2 lpD3D2)
{
// Have we already loaded the D3D Devices for this driver
if (! devicesLoaded ())
{
HRESULT hResult;
    DD_CB_INFOcbInfo;

// Check Parameters
if (! lpD3D2)
{
hResult = APPERR_INVALIDPARAMS;
REPORTERR (hResult);
return hResult;
}

    cbInfo.fResult  = TRUE;
    cbInfo.lpExtra  = (void *)this;
cbInfo.cCount   = 0L;

hResult = lpD3D2->EnumDevices (DeviceEnumCallback, &cbInfo);
if (FAILED(hResult))
{
REPORTERR (hResult);
return hResult;
}

// Double check count
if ((! cbInfo.fResult) || (cbInfo.cCount == 0) || (cDevices != cbInfo.cCount))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}

// Mark Devices as loaded
turnDevicesLoadedOn ();
}

// Success
return DD_OK;
} // DDDrvInfo::LoadDevices

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::DestroyDevices
**  Purpose:    Destroys all Devices in Device List
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::DestroyDevices (void)
{
LPD3DDevInfo lpCurr, lpNext, lpPrev;

lpCurr = lpDeviceRoot;

// Walk linked list and destroy all D3D Device nodes
while (lpCurr)
{
lpNext = lpCurr->lpNext;
lpPrev = lpCurr->lpPrev;

// Remove node from List
if (lpPrev)
lpPrev->lpNext = lpNext;
else
lpDeviceRoot = lpNext;

if (lpNext)
lpNext->lpPrev = lpPrev;
else
lpDeviceTail = lpPrev;

// Destroy this node
lpCurr->lpNext = NULL;
lpCurr->lpPrev = NULL;
delete lpCurr;

// Move to next node in list
lpCurr = lpNext;
}

cDevices   = 0L;
lpDeviceRoot = NULL;
lpDeviceTail = NULL;

turnDevicesLoadedOff ();

// Success
return DD_OK;
} // End DDDrvInfo::DestroyDevices

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::AddDevice
**  Purpose:    add new D3D device to device list
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::AddDevice (LPD3DDevInfo lpDevNew)
{
// Check Parameters
if (! lpDevNew)
{
// Error, Invalid parameters
return DDERR_INVALIDPARAMS;
}

// Add to tail of Mode List
lpDevNew->lpPrev = lpDeviceTail;
lpDevNew->lpNext = NULL;

// Update tail
if (lpDeviceTail)
lpDeviceTail->lpNext = lpDevNew;
lpDeviceTail = lpDevNew;

// Update Root
if (! lpDeviceRoot)
lpDeviceRoot = lpDevNew;

// Update count
cDevices++;

// Success
return DD_OK;
} // End DDDrvInfo::AddDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::DelDevice
**  Purpose:    removes D3D device from Device list
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvInfo::DelDevice (LPD3DDevInfo lpDevDel)
{
// Check parameters
if (lpDevDel)
{
// Error
return DDERR_INVALIDPARAMS;
}

// Remove this Mode From mode list
    LPD3DDevInfo lpPrevDev = lpDevDel->lpPrev;
    LPD3DDevInfo lpNextDev = lpDevDel->lpNext;

    if (lpPrevDev)
        lpPrevDev->lpNext = lpNextDev;
    else
        lpDeviceRoot = lpNextDev;

    if (lpNextDev)
lpNextDev->lpPrev = lpPrevDev;
else
lpDeviceTail = lpPrevDev;

lpDevDel->lpPrev = NULL;
    lpDevDel->lpNext = NULL;

    // Destroy mode node
    delete lpDevDel;

// Update count
cDevices--;

// Success
return DD_OK;
} // End DDDrvInfo::DelDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindDevice
**  Purpose:    finds specified D3D Device
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo DDDrvInfo::FindDevice (
LPGUIDlpGuid,
LPD3DDevInfo *  lpNextBest,
LPD3DDevInfolpStart)
{
LPD3DDevInfolpCurrDev, lpNextDev;
LPD3DDevInfo    lpHardware = NULL;
LPD3DDevInfolpMMX = NULL;
LPD3DDevInfo    lpRGB = NULL;
LPD3DDevInfo    lpFirst = NULL;

if (lpNextBest)
*lpNextBest = NULL;

// Get Root
if (! lpStart)
lpCurrDev = lpDeviceRoot;
else
lpCurrDev = lpStart;

lpFirst = lpCurrDev;

// Search mode list for best match
    while (lpCurrDev)
{
lpNextDev = lpCurrDev->lpNext;
        if (lpCurrDev->Match (lpGuid))
        {
return lpCurrDev;
        }

if (lpCurrDev->isHardware ())
        {
if (! lpHardware)
lpHardware = lpCurrDev;
        }

if (lpCurrDev->guid == IID_IDirect3DRGBDevice)
{
if (! lpRGB)
lpRGB = lpCurrDev;
}

if (lpCurrDev->guid == IID_IDirect3DMMXDevice)
{
if (! lpMMX)
lpMMX = lpCurrDev;
}

lpCurrDev = lpNextDev;
    }

if (lpNextBest)
{
if (lpHardware)
*lpNextBest = lpHardware;
else if (lpRGB)
*lpNextBest = lpRGB;
else if (lpMMX)
*lpNextBest = lpMMX;
else if (lpFirst)
*lpNextBest = lpFirst;
}

    // Failure, user may use lpNextBest instead
    return NULL;
} // End DDDrvInfo::FindDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindDevice
**  Purpose:    finds specified D3D Device
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo DDDrvInfo::FindDevice (
    LPD3DDEVICEDESC lpHal,
    LPD3DDEVICEDESC lpHel,
    LPD3DDevInfo *lpNextBest,
LPD3DDevInfolpStart)
{
LPD3DDevInfolpCurrDev, lpNextDev;
LPD3DDevInfo    lpHardware = NULL;
LPD3DDevInfolpMMX = NULL;
LPD3DDevInfo    lpRGB = NULL;
LPD3DDevInfo    lpFirst = NULL;

if (lpNextBest)
*lpNextBest = NULL;

// Get Root
if (! lpStart)
lpCurrDev = lpDeviceRoot;
else
lpCurrDev = lpStart;

lpFirst = lpCurrDev;

// Search mode list for best match
    while (lpCurrDev)
{
lpNextDev = lpCurrDev->lpNext;
        if (lpCurrDev->Match (lpHal, lpHel))
        {
return lpCurrDev;
        }

if (lpCurrDev->isHardware ())
        {
if (! lpHardware)
lpHardware = lpCurrDev;
        }

if (lpCurrDev->guid == IID_IDirect3DRGBDevice)
{
if (! lpRGB)
lpRGB = lpCurrDev;
}

if (lpCurrDev->guid == IID_IDirect3DMMXDevice)
{
if (! lpMMX)
lpMMX = lpCurrDev;
}

lpCurrDev = lpNextDev;
    }

if (lpNextBest)
{
if (lpHardware)
*lpNextBest = lpHardware;
else if (lpRGB)
*lpNextBest = lpRGB;
else if (lpMMX)
*lpNextBest = lpMMX;
else if (lpFirst)
*lpNextBest = lpFirst;
}

    // Failure
    return NULL;
} // End DDDrvInfo::FindDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindDeviceSupportsMode
**  Purpose:    finds specified D3D Device that is compatible
**with specified mode
**  Notes;
**
**1.  First looks for an exact match that is compatible
**  2.  Looks for a hardware device that is compatible
**3.  Looks for any device that is compatible
**
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo DDDrvInfo::FindDeviceSupportsMode (
LPGUIDlpGuid,
    LPDDModeInfolpMode,
LPD3DDevInfo *  lpNextBest,
LPD3DDevInfolpStart)
{
LPD3DDevInfolpCurrDev, lpNextDev;

// Check parameters
if (! lpMode)
{
// Error, Invalid parameters
if (lpNextBest)
*lpNextBest = NULL;
return NULL;
}

// Get Root
if (! lpStart)
lpCurrDev = lpDeviceRoot;
else
lpCurrDev = lpStart;

if (lpNextBest)
{
if (lpMode->ModeSupported (lpCurrDev))
*lpNextBest = lpCurrDev;
}

// Search mode list for best match
    while (lpCurrDev)
{
lpNextDev = lpCurrDev->lpNext;
        if (lpCurrDev->Match (lpGuid))
        {
if (lpMode->ModeSupported (lpCurrDev))
return lpCurrDev;
        }
        else if (lpCurrDev->isHardware ())
        {
if (lpNextBest)
{
if (lpMode->ModeSupported (lpCurrDev))
*lpNextBest = lpCurrDev;
}
        }
else if (lpMode->ModeSupported (lpCurrDev))
{
if (lpNextBest)
*lpNextBest = lpCurrDev;
}
lpCurrDev = lpNextDev;
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End DDDrvInfo::FindDeviceSupportsMode

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::FindModeSupportsDevice
**  Purpose:    finds specified mode that is compatible
**with specified D3D device
**
**1.  First trys for an exact mode, if it is compatible
**2.  Then looks for 640, 480, 16, if it is compatible
**  3.  Then looks for any mode that is compatible
**
**-----------------------------------------------------------------------------
*/

LPDDModeInfo DDDrvInfo::FindModeSupportsDevice (
DWORDdwW,
DWORDdwH,
DWORDdwBPP,
DWORDdwRefresh,/* In: Not supported yet ... */
LPD3DDevInfolpDevice,
LPDDModeInfo *lpNextBest,
LPDDModeInfolpStart)
{
LPDDModeInfolpCurrMode, lpNextMode;

// Check parameters
if (! lpDevice)
{
// Error, Invalid parameters
if (lpNextBest)
*lpNextBest = NULL;
return NULL;
}

// Get Root
if (! lpStart)
lpCurrMode = lpModeRoot;
else
lpCurrMode = lpStart;

if (lpNextBest)
{
if (lpCurrMode->ModeSupported (lpDevice))
*lpNextBest = lpCurrMode;
}

// Search mode list for best match
    while (lpCurrMode)
{
lpNextMode = lpCurrMode->lpNext;
        if (lpCurrMode->Match (dwW, dwH, dwBPP))
        {
if (lpCurrMode->ModeSupported (lpDevice))
return lpCurrMode;
        }
        else if (lpCurrMode->Match (640, 480, 16))
        {
if (lpNextBest)
{
if (lpCurrMode->ModeSupported (lpDevice))
*lpNextBest = lpCurrMode;
}
        }
else if (lpCurrMode->ModeSupported (lpDevice))
{
if (lpNextBest)
*lpNextBest = lpCurrMode;
}
lpCurrMode = lpNextMode;
    }

    // Failure, user may use lpNextBest instead
    return NULL;
} // End DDDrvInfo::FindModeSupportsDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::EnumDevices
**  Purpose:    Enumerate D3D devices in driver
**-----------------------------------------------------------------------------
*/

DWORD DDDrvInfo::EnumDevices (const DDDRV_ENUMINFO & eiInfo)
{
    DWORD           dwResult = ENUM_FAILURE;
LPD3DDevInfolpCurrDev, lpNextDev;

    // Check Match Callback function
    if (! eiInfo.fpcbEnum)
    {
        // Error, invalid callback
        return ENUM_ERROR;
    }

// Get Starting node
if (eiInfo.lpStart)
lpCurrDev = (LPD3DDevInfo)(eiInfo.lpStart);
else
lpCurrDev = lpDeviceRoot;

// Do callback on each node in list
while (lpCurrDev)
{
lpNextDev = lpCurrDev->lpNext;

dwResult = (*eiInfo.fpcbEnum)((LPVOID)this, lpCurrDev, eiInfo.dwExtra);

        // Check for early exit
        if (dwResult & ENUM_STOP)
{
            // Return result code
            return (dwResult & ~ENUM_STOP);
}

lpCurrDev = lpNextDev;
}

    // Failure
    return (dwResult & ~ENUM_STOP);
} // End DDDrvInfo::EnumDevices

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo::GetGuid
**Purpose:
**-----------------------------------------------------------------------------
*/

LPGUID DDDrvInfo::GetGuid (void)
{
if (isPrimary ())
return NULL;
else
return &guid;
} // End DDDrvInfo::GetGuid

/*
**-----------------------------------------------------------------------------
**  DDDrvMgr Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::Init
**  Purpose:    Grovel all DD driver info in system
**              and store in global DDInfo structures
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::Init (void)
{
    if (! isInitialized ())
    {
        HRESULT         hResult;

g_cDrivers = 0L;

// Load all drivers in system
hResult = LoadDrivers ();
if (FAILED (hResult))
return hResult;

        // Mark as initialized
        initOn ();
    }

// Success
    return DD_OK;
} // End DDDrvMgr::Init

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::Fini
**  Purpose:    Cleanup all global DDInfo structures
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::Fini (void)
{
    if (isInitialized ())
    {
DestroyDrivers ();

        // Mark as not initialized
        initOff ();
    }

// Success
    return DD_OK;
} // End DDDrvMgr::Fini

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::LoadDrivers
**  Purpose:    Load all DD Drivers in system
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::LoadDrivers (void)
{
HRESULT hResult;
    DD_CB_INFOcbInfo;

    // Initialize all valid drivers in system
    cbInfo.fResult  = TRUE;
    cbInfo.cCount   = 0L;
    cbInfo.lpExtra  = (void *)NULL;

    hResult = DirectDrawEnumerate (DriverEnumCallback, &cbInfo);
if (FAILED(hResult))
{
REPORTERR (hResult);
return hResult;
}

// Double check count
if ((! cbInfo.fResult) || (cbInfo.cCount == 0) || (g_cDrivers != cbInfo.cCount))
{
hResult = APPERR_GENERIC;
REPORTERR (hResult);
return hResult;
}

// Success
return DD_OK;
} // DDDrvMgr::LoadDrivers

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::DestroyDrivers
**  Purpose:    Destroys all Drivers in Driver List
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::DestroyDrivers (void)
{
LPDDDrvInfo lpCurr, lpNext, lpPrev;

lpCurr = g_lpDriverRoot;

// Walk linked list and destroy all D3D Device nodes
while (lpCurr)
{
lpNext = lpCurr->lpNext;
lpPrev = lpCurr->lpPrev;

// Remove node from List
if (lpPrev)
lpPrev->lpNext = lpNext;
else
g_lpDriverRoot = lpNext;

if (lpNext)
lpNext->lpPrev = lpPrev;
else
g_lpDriverTail = lpPrev;

// Destroy this node
lpCurr->lpNext = NULL;
lpCurr->lpPrev = NULL;
delete lpCurr;

// Move to next node in list
lpCurr = lpNext;
}

g_cDrivers = 0L;
g_lpDriverRoot = NULL;
g_lpDriverTail = NULL;

// Success
return DD_OK;
} // End DDDrvMgr::DestroyDrivers

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::DDDrvMgr
**  Purpose:    Default constructor
**-----------------------------------------------------------------------------
*/

DDDrvMgr::DDDrvMgr (void)
{
    lpCurrDriver    = NULL;
    lpCurrMode= NULL;
    lpCurrDevice= NULL;
} // End DDDrvMgr::DDDrvMgr

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::~DDDrvMgr
**-----------------------------------------------------------------------------
*/

DDDrvMgr::~DDDrvMgr (void)
{
} // End DDDrvMgr::~DDDrvMgr

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::AddDriver
**  Purpose:    add new driver to driver list
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::AddDriver (LPDDDrvInfo lpDrvNew)
{
// Check Parameters
if (! lpDrvNew)
{
// Error, Invalid parameters
return DDERR_INVALIDPARAMS;
}

// Add to tail of Mode List
lpDrvNew->lpPrev = DDDrvMgr::g_lpDriverTail;
lpDrvNew->lpNext = NULL;

// Update tail
if (DDDrvMgr::g_lpDriverTail)
DDDrvMgr::g_lpDriverTail->lpNext = lpDrvNew;
DDDrvMgr::g_lpDriverTail = lpDrvNew;

// Update Root
if (! DDDrvMgr::g_lpDriverRoot)
DDDrvMgr::g_lpDriverRoot = lpDrvNew;

// Update count
g_cDrivers++;

// Success
return DD_OK;
} // End DDDrvMgr::AddDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::DelDriver
**  Purpose:    removes driver from driver list
**-----------------------------------------------------------------------------
*/

HRESULT DDDrvMgr::DelDriver (LPDDDrvInfo lpDrvDel)
{
// Check parameters
if (lpDrvDel)
{
// Error
return DDERR_INVALIDPARAMS;
}

// Remove this Mode From mode list
    LPDDDrvInfo lpPrevDrv = lpDrvDel->lpPrev;
    LPDDDrvInfo lpNextDrv = lpDrvDel->lpNext;

    if (lpPrevDrv)
        lpPrevDrv->lpNext = lpNextDrv;
    else
DDDrvMgr::g_lpDriverRoot = lpNextDrv;

    if (lpNextDrv)
lpNextDrv->lpPrev = lpPrevDrv;
else
DDDrvMgr::g_lpDriverTail = lpPrevDrv;

lpDrvDel->lpPrev = NULL;
    lpDrvDel->lpNext = NULL;

    // Destroy mode node
    delete lpDrvDel;

// Update count
g_cDrivers--;

// Success
return DD_OK;
} // End DDDrvMgr::DelDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::FindDriver
**  Purpose:    Finds driver corresponding to guid
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo DDDrvMgr::FindDriver (
LPGUIDlpGuid,
LPDDDrvInfo *lpNextBest,
LPDDDrvInfolpStart)
{
    LPDDDrvInfo lpCurrDrv, lpNextDrv;

#ifdef DEBUG
    if (! isInitialized ())
    {
        // Error, not initialized
        return NULL;
    }
#endif

    // Get Start node
if (! lpStart)
lpCurrDrv = DDDrvMgr::g_lpDriverRoot;
else
lpCurrDrv = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrDrv;

while (lpCurrDrv)
{
lpNextDrv = lpCurrDrv->lpNext;
if (lpCurrDrv->Match (lpGuid))
{
// Success
return lpCurrDrv;
}
else if (lpCurrDrv->isPrimary ())
{
if (lpNextBest)
*lpNextBest = lpCurrDrv;
}

lpCurrDrv = lpNextDrv;
}

    // Failure, user could use next best instead
    return NULL;
} // End DDDrvMgr::FindDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::FindDriver
**  Purpose:    Finds driver corresponding to specified device caps
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo DDDrvMgr::FindDriver (
    LPDDCAPS  lpHal,
    LPDDCAPS  lpHel,
    LPDDDrvInfo * lpNextBest,
LPDDDrvInfo   lpStart)
{
    LPDDDrvInfo lpCurrDrv, lpNextDrv;

    // Get Start node
if (! lpStart)
lpCurrDrv = DDDrvMgr::g_lpDriverRoot;
else
lpCurrDrv = lpStart;

if (lpNextBest)
*lpNextBest = lpCurrDrv;

while (lpCurrDrv)
{
lpNextDrv = lpCurrDrv->lpNext;

if (lpCurrDrv->Match (lpHal, lpHel))
{
// Success
return lpCurrDrv;
}

else if (lpCurrDrv->isPrimary ())
{
if (lpNextBest)
*lpNextBest = lpCurrDrv;
}

lpCurrDrv = lpNextDrv;
}

    // Failure, user could use next best instead
    return NULL;
} // End DDDrvMgr::FindDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr::EnumDrivers
**  Purpose:    Enumerate drivers
**-----------------------------------------------------------------------------
*/

DWORD DDDrvMgr::EnumDrivers (const DDDRV_ENUMINFO & eiInfo)
{
    DWORDdwResult = ENUM_FAILURE;
LPDDDrvInfo lpCurrDrv, lpNextDrv;

    // Check Match Callback function
    if (! eiInfo.fpcbEnum)
    {
        // Error, invalid callback
        return ENUM_ERROR;
    }

// Get starting node
    if (eiInfo.lpStart)
lpCurrDrv = (LPDDDrvInfo)(eiInfo.lpStart);
else
lpCurrDrv = DDDrvMgr::g_lpDriverRoot;

    // for each driver, call user defined match callback function
while (lpCurrDrv)
{
        lpNextDrv = lpCurrDrv->lpNext;

        // Call Enum Drivers Callback
        dwResult = (*eiInfo.fpcbEnum)((LPVOID)lpCurrDrv, NULL, eiInfo.dwExtra);

        // Check for early exit
        if (dwResult & ENUM_STOP)
        {
            // Return result code
            return (dwResult & ~ENUM_STOP);
        }
   }

    // Failure
    return (dwResult & ~ENUM_STOP);
} // End DDDrvMgr::EnumDrivers

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/


DRVMGR.H

#ifndef DRVMGR_H
#define DRVMGR_H
/*
**-----------------------------------------------------------------------------
**  Name:       DrvMgr.h
**  Purpose:    Creates and manages DD/D3D drivers static info
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Includes
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

// DD Driver Flags
#define DDDRV_INIT0x00000001L

#define DDDRV_VALID0x00000001L
#define DDDRV_PRIMARY0x00000002L
#define DDDRV_D3D0x00000004L

#define DDDRV_MODES_LOADED0x00000008L
#define DDDRV_DEVICES_LOADED0x00000010L

// D3D Device Flags
#define D3DDEV_VALID0x00000001L
#define D3DDEV_FORMATS_LOADED0x00000002L

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

// Forward declarations
class DDModeInfo;
typedef DDModeInfo * LPDDModeInfo;

class D3DDevInfo;
typedef D3DDevInfo * LPD3DDevInfo;

class DDDrvInfo;
typedef DDDrvInfo * LPDDDrvInfo;

class DDDrvMgr;
typedef DDDrvMgr * LPDDDrvMgr;

// Enumeration Callback Info
#define ENUM_STOP  0x80000000L

typedef enum {
    ENUM_ERROR    = 0,
    ENUM_FAILURE  = 1,
    ENUM_SUCCESS  = 2
};

// Callback function prototypes
typedef DWORD (*ENUMDDDRVCALLBACK)(LPVOID lpDriver, LPVOID lpData, DWORD dwExtra);
typedef DWORD (*ENUMD3DDEVCALLBACK)(LPVOID lpDevice, LPVOID lpData, DWORD dwExtra);

// Enum Driver Callback Info
typedef struct tagDDDRV_ENUMINFO
{
    void *              lpStart;        // Starting Node
    ENUMDDDRVCALLBACK   fpcbEnum;       // Enumeration Callback function
    DWORD               dwExtra;        // User defined extra data for callback
} DDDRV_ENUMINFO;
typedef DDDRV_ENUMINFO * LPDDDRV_ENUMINFO;

// Enum D3D Device Callback Info
typedef struct tagD3DDEV_ENUMINFO
{
    void *              lpStart;        // Starting Node
    ENUMD3DDEVCALLBACK  fpcbEnum;       // Enumeration Callback function
    DWORD               dwExtra;        // User defined extra data for callback
} D3DDEV_ENUMINFO;
typedef D3DDEV_ENUMINFO * LPD3DDEV_ENUMINFO;

/*
**-----------------------------------------------------------------------------
**  Functions
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo ValidateDriver (LPGUID lpDDGuid);
LPDDModeInfo ValidateMode (LPDDDrvInfo lpDriver, DWORD w, DWORD h,
   DWORD bpp, DWORD refresh, LPD3DDevInfo lpFilter);
LPD3DDevInfo ValidateDevice (LPDDDrvInfo lpDriver, LPGUID lpD3DGuid,
     LPDDModeInfo lpFilter);

BOOL GetDesktopMode (LPDDDrvInfo lpDriver, LPGUID lpD3DGuid,
 LPDDModeInfo * lpMode,LPD3DDevInfo * lpDev);

BOOL GetFullscreenMode (LPDDDrvInfo lpDriver,  LPGUID lpD3DGuid,
DWORD w, DWORD h, DWORD bpp, DWORD refresh,
    LPDDModeInfo * lpMode,LPD3DDevInfo * lpDev);

HRESULT ChooseDriverDefaults (LPGUID lpGuidDD, DWORD w, DWORD h,
  DWORD bpp, DWORD refresh, LPGUID lpGuidD3D,
  BOOL fFullScreen, LPDDDrvInfo * lpDriver,
  LPDDModeInfo * lpMode, LPD3DDevInfo * lpDevice);

DWORD FlagsToBitDepth (DWORD dwFlags);
DWORD BitDepthToFlags (DWORD dwBPP);
BOOL  isPalettized (LPDDPIXELFORMAT lpddpf);

/*
**-----------------------------------------------------------------------------
**  Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DDModeInfo
**  Purpose:    DirectDraw Mode info
**-----------------------------------------------------------------------------
*/

class DDModeInfo
{
private:
protected:
public:
    // Data
    DDSURFACEDESC   ddSurfDesc;     // Complete Surface Description
LPDDModeInfolpPrev;// Prev Node in List
LPDDModeInfo    lpNext;// Next Node In List

    // Constructors
    DDModeInfo (void)
        {
            ZeroMemory (&ddSurfDesc, sizeof (ddSurfDesc));
            ddSurfDesc.dwSize = sizeof (DDSURFACEDESC);

lpPrev = NULL;
lpNext = NULL;
        }

    DDModeInfo (const DDSURFACEDESC & ddDesc)
        {
            CopyMemory (&ddSurfDesc, (const void *)&ddDesc, sizeof (ddSurfDesc));
            ddSurfDesc.dwSize = sizeof (DDSURFACEDESC);

lpPrev = NULL;
lpNext = NULL;
        }
    ~DDModeInfo (void)
{
lpPrev = NULL;
lpNext = NULL;
}

    // Methods
DWORD GetWidth (void);
DWORD GetHeight (void);
DWORD GetBPP (void);
HRESULT GetMode (DWORD & dwWidth, DWORD & dwHeight,
 DWORD & dwBPP, DWORD & dwRefresh);

BOOL ModeSupported (LPD3DDevInfo lpDevice);

    BOOL Match (DWORD dwW, DWORD dwH, DWORD dwBPP);
    BOOL Match (const DDSURFACEDESC & ddsd);

BOOL Match (DWORD dwBPP);
BOOL Match (const DDPIXELFORMAT & ddpf);
}; // DDModeInfo

/*
**-----------------------------------------------------------------------------
**  Name:       D3DDevInfo
**  Purpose:    D3D Device/driver info for D3D Driver Manager
**Notes:
**
**- The Texture Format is a Mode (both are DDSURFACEDESC wrappers)
**
**-----------------------------------------------------------------------------
*/

class D3DDevInfo
{
private:
protected:
public:
    // D3D Device Driver info
    DWORD           fFlags;                 // Flags

    // D3D Info
    GUID            guid;                   // GUID
    LPTSTR          szName;// Driver Name
    LPTSTR          szDesc;// Driver Description
    D3DDEVICEDESC   d3dHalDesc;             // HAL info
    D3DDEVICEDESC   d3dHelDesc;             // HEL info

    // Texture Formats
DWORDcFormats;// Count of Texture Formats
    LPDDModeInfo    lpFormatRoot;// List of Texture Formats (root)
LPDDModeInfolpFormatTail;// List of Texture Formats (tail)

// Node Info
LPD3DDevInfolpPrev;// Prev Node
LPD3DDevInfolpNext;// Next Node

    // Flags
    BOOL isValid (void)     { return (fFlags & DDDRV_VALID); }
    void validOn (void)     { fFlags |= DDDRV_VALID; }
    void validOff (void)    { fFlags &= ~DDDRV_VALID; }

    // Methods
    HRESULT Create (LPGUID lpGuid, LPTSTR lpName, LPTSTR lpDesc,
                    LPD3DDEVICEDESC lpHalDevice,
                    LPD3DDEVICEDESC lpHelDevice);
    void Destroy (void);

    // Constructors
    D3DDevInfo (void)
        {
validOff ();
turnFormatsLoadedOff ();

szName = NULL;
szDesc = NULL;

        ZeroMemory (&d3dHalDesc, sizeof(D3DDEVICEDESC));
        d3dHalDesc.dwSize = sizeof (D3DDEVICEDESC);

        ZeroMemory (&d3dHelDesc, sizeof(D3DDEVICEDESC));
        d3dHelDesc.dwSize = sizeof (D3DDEVICEDESC);

cFormats = 0L;
lpFormatRoot = NULL;
lpFormatTail = NULL;

lpPrev = NULL;
lpNext = NULL;
        }

    ~D3DDevInfo (void)
{
Destroy ();
}

// Texure Format Methods
BOOL formatsLoaded (void){ return ((fFlags & D3DDEV_FORMATS_LOADED) ? TRUE : FALSE); }
void turnFormatsLoadedOn (void){ fFlags |= D3DDEV_FORMATS_LOADED; }
void turnFormatsLoadedOff (void){ fFlags &= ~D3DDEV_FORMATS_LOADED; }

HRESULT LoadFormats (LPDIRECT3DDEVICE2 lpd3dDevice);
HRESULT DestroyFormats (void);

HRESULT AddFormat (LPDDModeInfo lpFormatNew);
HRESULT DelFormat (LPDDModeInfo lpFormatDel);
DWORD   countFormats (void) { return cFormats; }

    LPDDModeInfo FindFormat (DWORD dwBPP,
     LPDDModeInfo * lpNextBest,
     LPDDModeInfo lpStartFormat = NULL);
    LPDDModeInfo FindFormat (LPDDPIXELFORMAT lpddsd,
     LPDDModeInfo * lpNextBest,
     LPDDModeInfo lpStartFormat = NULL);

    DWORD EnumFormats (const D3DDEV_ENUMINFO & eiInfo);

    // Methods
    BOOL isHardware (void);
    BOOL Match (LPGUID lpGuid);
    BOOL Match (LPD3DDEVICEDESC lpHalDesc, LPD3DDEVICEDESC lpHelDesc);

}; // End D3DDevInfo

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvInfo
**  Purpose:    Encapsulates DD/D3D driver info
**-----------------------------------------------------------------------------
*/

class DDDrvInfo
{
private:
protected:
public:

    // Structure info
    DWORD           fFlags;                         // D3D Driver flags

    // Driver Info
    GUID            guid;                           // guid, if any
    LPTSTR          szName;// name
    LPTSTR          szDesc;// description

    // Driver Caps
    DDCAPS          ddHalCaps;                      // Hardware caps
    DDCAPS          ddHelCaps;                      // Emulation caps

    // Mode Info
DWORDcModes;// Count of Modes
    LPDDModeInfo    lpModeRoot;// List of Modes (root)
LPDDModeInfolpModeTail;// List of Modes (tail)

    // D3D Info
DWORDcDevices;// Count of D3D devices
    LPD3DDevInfo    lpDeviceRoot;// List of D3D Devices (root)
LPD3DDevInfolpDeviceTail;// List of D3D Devices (tail)

// Node Info
LPDDDrvInfolpPrev;// Previous node
LPDDDrvInfolpNext;// Next node

    //
    // Flag Methods
    //
    BOOL isValid (void)     { return (fFlags & DDDRV_VALID); }
    void validOn (void)     { fFlags |= DDDRV_VALID; }
    void validOff (void)    { fFlags &= ~DDDRV_VALID; }

    BOOL isPrimary (void)   { return (fFlags & DDDRV_PRIMARY); }
    void primaryOn (void)   { fFlags |= DDDRV_PRIMARY; }
    void primaryOff (void)  { fFlags &= ~DDDRV_PRIMARY; }

    BOOL isD3D (void)     { return (fFlags & DDDRV_D3D); }
    void d3dOn (void)     { fFlags |= DDDRV_D3D; }
    void d3dOff (void)    { fFlags &= ~DDDRV_D3D; }

    // Methods
    HRESULT Create (LPGUID lpGuid, LPTSTR lpszName, LPTSTR lpszDesc);
    void    Destroy (void);

    // Constructors
    DDDrvInfo (void)
        {
validOff ();

ZeroMemory (&ddHalCaps, sizeof (DDCAPS));
ddHalCaps.dwSize = sizeof (DDCAPS);

ZeroMemory (&ddHelCaps, sizeof (DDCAPS));
ddHelCaps.dwSize = sizeof (DDCAPS);

cModes  = 0L;
lpModeRoot = NULL;
lpModeTail = NULL;

cDevices = 0L;
lpDeviceRoot = NULL;
lpDeviceTail = NULL;

lpPrev = NULL;
lpNext = NULL;
        }

    ~DDDrvInfo (void)
{
Destroy ();
}

// Mode Methods
BOOL modesLoaded (void){ return ((fFlags & DDDRV_MODES_LOADED) ? TRUE : FALSE); }
void turnModesLoadedOn (void){ fFlags |= DDDRV_MODES_LOADED; }
void turnModesLoadedOff (void){ fFlags &= ~DDDRV_MODES_LOADED; }

HRESULT LoadModes (LPDIRECTDRAW2 lpDD2);
HRESULT DestroyModes (void);

HRESULT AddMode (LPDDModeInfo lpModeNew);
HRESULT DelMode (LPDDModeInfo lpModeDel);
DWORD countModes (void) { return cModes; }

    LPDDModeInfo FindMode (DWORD dwW, DWORD dwH,
   DWORD dwBPP, DWORD dwRefresh,
   LPDDModeInfo * lpNextBest,
   LPDDModeInfo lpStartMode = NULL);
    LPDDModeInfo FindMode (LPDDSURFACEDESC lpddsd,
   LPDDModeInfo * lpNextBest,
   LPDDModeInfo lpStartMode = NULL);

    DWORD EnumModes (const DDDRV_ENUMINFO & eiInfo);

// D3D Device Methods
BOOL devicesLoaded (void){ return ((fFlags & DDDRV_DEVICES_LOADED) ? TRUE : FALSE); }
void turnDevicesLoadedOn (void){ fFlags |= DDDRV_DEVICES_LOADED; }
void turnDevicesLoadedOff (void){ fFlags &= ~DDDRV_DEVICES_LOADED; }

HRESULT LoadDevices (LPDIRECT3D2 lpD3D2);
HRESULT DestroyDevices (void);

HRESULT AddDevice (LPD3DDevInfo lpModeNew);
HRESULT DelDevice (LPD3DDevInfo lpModeDel);
DWORD countDevices (void) { return cDevices; }

    LPD3DDevInfo FindDevice (LPGUID lpGuid, LPD3DDevInfo * lpNextBest,
 LPD3DDevInfo lpStart = NULL);
    LPD3DDevInfo FindDevice (LPD3DDEVICEDESC lpHal, LPD3DDEVICEDESC lpHel,
 LPD3DDevInfo * lpNextBest,
 LPD3DDevInfo lpStart = NULL);

    DWORD EnumDevices (const DDDRV_ENUMINFO & eiInfo);

// Mode <=> Device compatible find functions
    LPD3DDevInfo FindDeviceSupportsMode (LPGUID lpGuid,
 LPDDModeInfo lpMode,
     LPD3DDevInfo * lpNextBest,
     LPD3DDevInfo lpStart = NULL);
LPDDModeInfo FindModeSupportsDevice (DWORD dwW, DWORD dwH,
 DWORD dwBPP, DWORD dwRefresh,
 LPD3DDevInfo lpDevice,
 LPDDModeInfo * lpNextBest,
 LPDDModeInfo lpStart = NULL);

    // Methods
    BOOL  Match (LPGUID lpGuid);
    BOOL  Match (LPDDCAPS lpHal, LPDDCAPS lpHel);

LPGUID GetGuid (void);

}; // End class DDDrvInfo

/*
**-----------------------------------------------------------------------------
**  Name:       DDDrvMgr
**  Purpose:    Creates and Manages DD/D3D driver info
**  Notes:
**          1.  Modes and D3D Drivers may be incompatible with each other
**              code needs to be added to detect and mark this
**          2.  Need to add code for finding exact and nearest mode matches
**              from requested width, height, pixel format, and refresh rate
**          3.  Need to add code for finding D3D devices from
**-----------------------------------------------------------------------------
*/

class DDDrvMgr
{
private:
protected:
public:
    // Static Data
    static DWORD         g_fFlags;                  // Global flags
static DWORD g_cDrivers;// Count of DD Drivers
    static LPDDDrvInfo g_lpDriverRoot;// Global list of DD Device Drivers (root)
    static LPDDDrvInfo g_lpDriverTail;// Global list of DD Device Drivers (tail)

    // Data
    LPDDDrvInfolpCurrDriver;// Pointer to current DD Device driver
    LPDDModeInfolpCurrMode;// Pointer to current mode in DD driver
    LPD3DDevInfolpCurrDevice;// Pointer to current D3D device
LPDDModeInfolpCurrTextureFormat;// Pointer to current Texture Format

    // Static Methods
    static HRESULT Init (void);
    static HRESULT Fini (void);

    static BOOL isInitialized (void)    { return (g_fFlags & DDDRV_INIT); }
    static void initOn (void)           { g_fFlags |= DDDRV_INIT; }
    static void initOff (void)          { g_fFlags &= ~DDDRV_INIT; }

// Driver Methods
static HRESULT LoadDrivers (void);
static HRESULT DestroyDrivers (void);

static HRESULT AddDriver (LPDDDrvInfo lpDrvNew);
static HRESULT DelDriver (LPDDDrvInfo lpDrvDel);
static DWORD countDrivers (void) { return g_cDrivers; }

    static DWORD EnumDrivers (const DDDRV_ENUMINFO & eiInfo);

    static LPDDDrvInfo FindDriver (LPGUID lpGuidDD, LPDDDrvInfo * dwNextBest,
   LPDDDrvInfo lpStart = NULL);
    static LPDDDrvInfo FindDriver (LPDDCAPS lpHalCaps, LPDDCAPS lpHelCaps,
   LPDDDrvInfo * dwNextBest,
   LPDDDrvInfo lpStart = NULL);

    // Constructors
    DDDrvMgr (void);
    ~DDDrvMgr (void);

// DD info methods
    LPDDDrvInfo GetCurrDriver (void)
{
return lpCurrDriver;
}

    // Mode Methods
    DWORD countModes (void)
{
if (lpCurrDriver)
return lpCurrDriver->countModes ();
return 0L;
}

    LPDDModeInfo GetCurrMode (void)
{
return lpCurrMode;
}

    // D3D info methods
    DWORD countDevices (void)
{
if (lpCurrDriver)
return lpCurrDriver->countDevices ();
return 0L;
}

    LPD3DDevInfo GetCurrDevice (void)
{
return lpCurrDevice;
}

// Texture Format methods
DWORD countFormats (void)
{
if (lpCurrDevice)
return lpCurrDevice->countFormats ();
return 0L;
}

LPDDModeInfo GetCurrTextureFormat (void)
{
return lpCurrTextureFormat;
}

}; // End class DDDrvMgr

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // DRVMGR_H


FLOCK.CPP

/*
**-----------------------------------------------------------------------------
**File:flock.cpp
**Purpose:-- where the boids are
**Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Include Files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "Debug.h"
#include "d3dutils.h"
#include "boids.h"

/*
**-----------------------------------------------------------------------------
** Constants
**-----------------------------------------------------------------------------
*/

const float InfluenceRadius = 20.0f;
const float InfluenceRadiusSquared = InfluenceRadius * InfluenceRadius;
const float CollisionFraction = 0.8f;
const float InvCollisionFraction = 1.0f/(1.0f-CollisionFraction);

const float NormalSpeed = 0.1f;
const floatAngleTweak = 0.02f;
const float PitchToSpeedRatio = 0.002f;

/*
**-----------------------------------------------------------------------------
** Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:UpdateFlock
** Purpose:Update posiiton of each boid in flock
**-----------------------------------------------------------------------------
*/

void
UpdateFlock (Flock flock)
{
inti, j;
floatdist;

// first update the dist array 0.0..1.0 with 0.0 being furthest away
for (i=0; i<flock.num_boids; i++) {
for (j=i+1; j<flock.num_boids; j++) {
dist = SquareMagnitude(flock.boids[i].loc - flock.boids[j].loc);
dist = InfluenceRadiusSquared - dist;
if (dist < 0.0f) {
dist = 0.0f;
} else {
dist /= InfluenceRadiusSquared;
}
flock.dist[i][j] = flock.dist[j][i] = dist;
}
flock.dist[i][i] = 0.0f;
flock.boids[i].delta_dir = D3DVECTOR(0.0f);
flock.boids[i].delta_pos = D3DVECTOR(0.0f);
flock.boids[i].delta_cnt = 0;
}

for (i=0; i<flock.num_boids; i++) {
for (j=i+1; j<flock.num_boids; j++) {
// if i is near j have them influence each other
if (flock.dist[i][j] > 0.0) {
D3DVECTORdiff = Normalize(flock.boids[i].loc - flock.boids[j].loc);
D3DVECTORdelta;
floatcol = 0.0f;// collision weighting

// only do collision testing against the nearest ones
if (flock.dist[i][j] - CollisionFraction > 0.0f) {
col = (flock.dist[i][j] - CollisionFraction) * InvCollisionFraction;
}

// add in a little flock centering
if (flock.dist[i][j] - (1.0-CollisionFraction) > 0.0f) {
col -= flock.dist[i][j] * (1.0f-col);
}

delta = col * diff;

// add in the collision avoidance
flock.boids[i].delta_pos += delta;
flock.boids[j].delta_pos -= delta;

// add in the velocity influences
flock.boids[i].delta_dir += flock.boids[j].dir * flock.dist[i][j];
flock.boids[j].delta_dir += flock.boids[i].dir * flock.dist[i][j];
flock.boids[i].delta_cnt++;
flock.boids[j].delta_cnt++;
}
}
}

// update the boids
for (i=0; i<flock.num_boids; i++) {
if (flock.boids[i].delta_cnt) {
flock.boids[i].delta_dir /= (float)flock.boids[i].delta_cnt;
flock.boids[i].delta_dir -= flock.boids[i].dir;
flock.boids[i].delta_dir *= 1.5f;
}
D3DVECTORdelta = flock.boids[i].delta_dir + flock.boids[i].delta_pos; // + 0.1f * D3DVECTOR(rnd()-rnd(), rnd()-rnd(), rnd()-rnd());
D3DVECTORoffset;

//delta = Normalize(delta);

// add in the influence of the global goal
D3DVECTORgoal = 0.5f * Normalize(flock.goal-flock.boids[i].loc);
delta += goal;

// add in any obstacles
for (j=0; j<flock.num_obs; j++) {
D3DVECTORob = flock.boids[i].loc - flock.obs[j].loc;
floatradius = flock.obs[j].radius;
floatdist = Magnitude(ob);

if (dist > 2*radius)
continue;

ob /= dist;// normalize
dist = 1.0f - dist/(2.0f*radius);
delta += dist * ob * 5.0f;
}

// first deal with pitch changes
if (delta.y > 0.01) {// we're too low
flock.boids[i].pitch += AngleTweak;
if (flock.boids[i].pitch > 0.8f)
flock.boids[i].pitch = 0.8f;
} else if (delta.y < -0.01) {// we're too high
flock.boids[i].pitch -= AngleTweak;
if (flock.boids[i].pitch < -0.8f)
flock.boids[i].pitch = -0.8f;
} else {
// add damping
flock.boids[i].pitch *= 0.98f;
}

// speed up or slow down depending on angle of attack
flock.boids[i].speed -= flock.boids[i].pitch * PitchToSpeedRatio;
// damp back to normal
flock.boids[i].speed = (flock.boids[i].speed-NormalSpeed)*0.99f + NormalSpeed;

if (flock.boids[i].speed < NormalSpeed/2) {
flock.boids[i].speed = NormalSpeed/2;
}
if (flock.boids[i].speed > NormalSpeed*5) {
flock.boids[i].speed = NormalSpeed*5;
}

// now figure out yaw changes
offset = delta;
offset.y = 0.0f;
delta = flock.boids[i].dir;
offset = Normalize(offset);
floatdot = DotProduct(offset, delta);
// speed up slightly if not turning much
if (dot > 0.7f) {
dot -= 0.7f;
flock.boids[i].speed += dot * 0.005f;
}
offset = CrossProduct(offset, delta);
dot = (1.0f-dot)/2.0f * 0.07f;
if (offset.y > 0.05f) {
flock.boids[i].dyaw = (flock.boids[i].dyaw*19.0f + dot) * 0.05f;
} else if (offset.y < -0.05f) {
flock.boids[i].dyaw = (flock.boids[i].dyaw*19.0f - dot) * 0.05f;
} else {
flock.boids[i].dyaw *= 0.98f;// damp it
}
flock.boids[i].yaw += flock.boids[i].dyaw;
flock.boids[i].roll = -flock.boids[i].dyaw * 20.0f;

}

}// end of UpdateFlock()

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


GETDXVER.CPP

/**************************************************************************

    GetDXVer.cpp

    Demonstrates how applications can detect what version of DirectX
    is installed.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <dinput.h>

/****************************************************************************
 *
 *      GetDXVersion
 *
 *  This function returns two arguments:
 *dwDXVersion:
 *    0    No DirectX installed
 *    0x100   DirectX version 1 installed
 *    0x200   DirectX 2 installed
 *    0x300   DirectX 3 installed
 *    0x500   At least DirectX 5 installed.
 *    0x501   At least DirectX 5a installed.
 *dwDXPlatform:
 *    0                            Unknown (This is a failure case. Should never happen)
 *    VER_PLATFORM_WIN32_WINDOWS    Windows 9X platform
 *    VER_PLATFORM_WIN32_NT    Windows NT platform
 *
 ****************************************************************************/

typedef HRESULT (WINAPI *DIRECTDRAWCREATE)(GUID *, LPDIRECTDRAW *, IUnknown *);
typedef HRESULT (WINAPI *DIRECTINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUT *, IUnknown *);

void GetDXVersion (LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform)
{
    HRESULT    hr;
    HINSTANCE    DDHinst = 0;
    HINSTANCE    DIHinst = 0;
    LPDIRECTDRAW    pDDraw = 0;
    LPDIRECTDRAW2    pDDraw2 = 0;
    DIRECTDRAWCREATE    DirectDrawCreate = 0;
    DIRECTINPUTCREATE    DirectInputCreate = 0;
    OSVERSIONINFO    osVer;
    LPDIRECTDRAWSURFACE    pSurf = 0;
    LPDIRECTDRAWSURFACE3    pSurf3 = 0;

    /*
     * First get the windows platform
     */
    osVer.dwOSVersionInfoSize = sizeof(osVer);
    if (!GetVersionEx(&osVer))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
return;
    }

    if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
*pdwDXPlatform = VER_PLATFORM_WIN32_NT;
/*
 * NT is easy... NT 4.0 is DX2, 4.0 SP3 is DX3, 5.0 is DX5
 * and no DX on earlier versions.
 */
if (osVer.dwMajorVersion < 4)
{
    *pdwDXPlatform = 0;//No DX on NT3.51 or earlier
    return;
}
if (osVer.dwMajorVersion == 4)
{
    /*
     * NT4 up to SP2 is DX2, and SP3 onwards is DX3, so we are at least DX2
     */
    *pdwDXVersion = 0x200;

            /*
             * We're not supposed to be able to tell which SP we're on, so check for dinput
             */
            DIHinst = LoadLibrary("DINPUT.DLL");
            if (DIHinst == 0)
            {
                /*
                 * No DInput... must be DX2 on NT 4 pre-SP3
                 */
                OutputDebugString("Couldn't LoadLibrary DInput\r\n");
        return;
            }

            DirectInputCreate = (DIRECTINPUTCREATE)
                                    GetProcAddress(DIHinst, "DirectInputCreateA");
            FreeLibrary(DIHinst);

            if (DirectInputCreate == 0)
            {
                /*
                 * No DInput... must be pre-SP3 DX2
                 */
                OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
        return;
            }

    /*
     * It must be NT4, DX2
     */
    *pdwDXVersion = 0x300; //DX3 on NT4 SP3 or higher
    return;
}
/*
 * Else it's NT5 or higher, and it's DX5a or higher:
 */
*pdwDXVersion = 0x501; //DX5a on NT5
return;
    }

    /*
     * Not NT... must be Win9x
     */
    *pdwDXPlatform = VER_PLATFORM_WIN32_WINDOWS;

    /*
     * If we are on Memphis or higher, then we are at least DX5a
     */
    if ( (osVer.dwBuildNumber & 0xffff) > 1353) //Check for higher than developer release
    {
*pdwDXVersion = 0x501; //DX5a on Memphis or higher
return;
    }

    /*
     * Now we know we are in Windows 9x (or maybe 3.1), so anything's possible.
     * First see if DDRAW.DLL even exists.
     */
    DDHinst = LoadLibrary("DDRAW.DLL");
    if (DDHinst == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
return;
    }

    /*
     *  See if we can create the DirectDraw object.
     */
    DirectDrawCreate = (DIRECTDRAWCREATE)
                            GetProcAddress(DDHinst, "DirectDrawCreate");
    if (DirectDrawCreate == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't LoadLibrary DDraw\r\n");
return;
    }

    hr = DirectDrawCreate(NULL, &pDDraw, NULL);
    if (FAILED(hr))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't create DDraw\r\n");
return;
    }

    /*
     *  So DirectDraw exists.  We are at least DX1.
     */
    *pdwDXVersion = 0x100;

    /*
     *  Let's see if IID_IDirectDraw2 exists.
     */
    hr = pDDraw->QueryInterface(IID_IDirectDraw2, (LPVOID *)&pDDraw2);
    if (FAILED(hr))
    {
/*
 * No IDirectDraw2 exists... must be DX1
 */
pDDraw->Release();
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't QI DDraw2\r\n");
return;
    }
    /*
     * IDirectDraw2 exists. We must be at least DX2
     */
    pDDraw2->Release();
    *pdwDXVersion = 0x200;

    /*
     *  See if we can create the DirectInput object.
     */
    DIHinst = LoadLibrary("DINPUT.DLL");
    if (DIHinst == 0)
    {
        /*
         * No DInput... must be DX2
         */
        OutputDebugString("Couldn't LoadLibrary DInput\r\n");
pDDraw->Release();
FreeLibrary(DDHinst);
return;
    }

    DirectInputCreate = (DIRECTINPUTCREATE)
                            GetProcAddress(DIHinst, "DirectInputCreateA");
    FreeLibrary(DIHinst);

    if (DirectInputCreate == 0)
    {
        /*
         * No DInput... must be DX2
         */
FreeLibrary(DDHinst);
pDDraw->Release();
        OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
return;
    }

    /*
     * DirectInputCreate exists. That's enough to tell us that we are at least DX3
     */
    *pdwDXVersion = 0x300;

    /*
     * Checks for 3a vs 3b?
     */

    /*
     * We can tell if DX5 is present by checking for the existence of IDirectDrawSurface3.
     * First we need a surface to QI off of.
     */
    DDSURFACEDESC desc;

    ZeroMemory(&desc, sizeof(desc));
    desc.dwSize = sizeof(desc);
    desc.dwFlags = DDSD_CAPS;
    desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    hr = pDDraw->SetCooperativeLevel(NULL,DDSCL_NORMAL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't Set coop level\r\n");
return;
    }

    hr = pDDraw->CreateSurface(&desc, &pSurf, NULL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't CreateSurface\r\n");
return;
    }

    /*
     * Try for the IDirectDrawSurface3 interface. If it works, we're on DX5 at least
     */
    if ( FAILED(pSurf->QueryInterface(IID_IDirectDrawSurface3,(LPVOID*)&pSurf3)) )
    {
        pDDraw->Release();
        FreeLibrary(DDHinst);

        return;
    }

    /*
     * QI for IDirectDrawSurface3 succeeded. We must be at least DX5
     */
    *pdwDXVersion = 0x500;

    pSurf->Release();
    pDDraw->Release();
    FreeLibrary(DDHinst);

    return;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by boids2.rc
//
#define IDI_MAIN_ICON                   101
#define IDR_MAIN_MENU                   102
#define IDR_MAIN_ACCEL                  113
#define IDD_ABOUT                       114
#define IDD_CHANGE_MODE                 115
#define IDD_CHANGE_DRIVER               116
#define IDD_CHANGE_DEVICE               117
#define IDC_MODES                       1001
#define IDC_DRIVERS                     1003
#define IDC_DEVICES                     1004
#define IDM_EXIT                        40001
#define IDM_ABOUT                       40002
#define ID_HELP_ABOUT                   40003
#define IDM_CHANGE_MODES                40004
#define IDM_CHANGE_DRIVERS              40005
#define IDM_CHANGE_DEVICES              40009
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        119
#define _APS_NEXT_COMMAND_VALUE         40010
#define _APS_NEXT_CONTROL_VALUE         1008
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


WINMAIN.CPP

/*
**-----------------------------------------------------------------------------
**  File:       WinMain.cpp
**  Purpose:    D3D sample showing DrawPrimitive functionality
**  Notes:
**
**There are 3 main C++ classes that make this sample work.
**
**DDDrvMgr:Enumerates all drivers, modes, devices, etc.
**D3DWindow:D3D Frameworks
**D3DScene:Sample Scene Renderer
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

// Note:  Define INITGUID in one and only one file!!!
#define  INITGUID
#include "Common.h"
#include "Debug.h"
#include "WinMain.h"
#include "WinProc.h"
#include "DrvMgr.h"
#include "D3DWin.h"
#include "D3DScene.h"
#include "boids.h"

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

// Application variables
HINSTANCEg_hMainInstance= NULL;
HWND        g_hMainWindow       = NULL;
HACCEL      g_hMainAccel        = NULL;
HCURSORg_hMainCursor= NULL;

LPCTSTR     g_szMainName        = TEXT ("Boids2");
LPCTSTR     g_szMainClass= TEXT ("BoidsClass");
LPCTSTR     g_szMainTitle= TEXT ("Boids2 Sample");
LPCTSTR     g_szPaused          = TEXT ("Paused");

INT         g_nExitCode         = 0L;

//
// D3D Variables
//

// Driver Management (DD Drivers, modes, D3D devices)
DDDrvMgrg_DrvMgr;

//D3D framework (DD/D3D interfaces, surfaces, etc.)
LPD3DWindowg_lpd3dWin;

//  The Sample Framework (Materials, lights, textures, rendering, etc.)
LPD3DSceneg_lpd3dScene;

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

HRESULT CreateD3DWindow (
DWORDdwStyleEx,/* In:  Standard CreateWindowEx parameters */
LPCTSTRlpszClass,
    LPCTSTRlpszName,
DWORDdwStyle,
intx,
inty,
intnWidth,
intnHeight,
HWNDhParent,
HMENUhMenu,
HINSTANCEhInstance,
LPVOIDlpParam,

BOOL        fUseZBuffer,/* In:  Use Z-buffer */
HWND *lphWnd,/* Out:Window handle */
LPD3DWindow * lpd3dWnd);/* Out: Created D3DWindow pointer */

HRESULT DestroyD3DWindow (void);

extern void GetDXVersion (LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform);

/*
**-----------------------------------------------------------------------------
**  Function Definitions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       InitMain
**  Purpose:Initialize Application
**-----------------------------------------------------------------------------
*/

BOOL InitMain (void)
{
    DWORD dwDxVersion;
    DWORD dwDxPlatform;

    DPF (DEBUG_DETAILS, TEXT("InitApp"));

    // Get hInstance handle
    g_hMainInstance = (HINSTANCE) GetModuleHandle (NULL);
    if (NULL == g_hMainInstance)
    {
// Error - GetModule Handle

DPF (DEBUG_CRITICAL, TEXT("InitApp - GetModuleHandle() failed."));
        return FALSE;
    }

    // Check for DX5.0 or greater
    GetDXVersion (&dwDxVersion, &dwDxPlatform);
    if (dwDxVersion < 0x500)
    {
        MessageBox (NULL, TEXT("This App requires DX 5.0 or greater\r\nin order to run."), TEXT("Error"), MB_OK);
        return FALSE;
    }

    // Check for Previous instance
    if (! CheckPreviousApp ())
    {
        return FALSE;
    }

    // Init main window
    if (! InitMainWindow ())
    {
        return FALSE;
    }

    DPF (DEBUG_DETAILS, TEXT("InitApp - Success"));

    // Success
    return TRUE;
} // End InitMain

/*
**-----------------------------------------------------------------------------
**  Name:       FiniMain
**  Purpose:Cleanup Application
**-----------------------------------------------------------------------------
*/

void FiniMain (void)
{
DestroyD3DWindow ();

    UnregisterClass (g_szMainClass, g_hMainInstance);
} // End FiniMain

/*
**-----------------------------------------------------------------------------
**  Name:       CheckPreviousApp
**  Purpose:Check for previous instance of this application
**-----------------------------------------------------------------------------
*/

BOOL CheckPreviousApp (void)
{
    HWND hwndFind;
    HWND hwndLast;
    HWND hwndForeGround;
    DWORD dwFindID;
    DWORD dwForeGroundID;

    // Check if application is already running
    hwndFind = FindWindow (g_szMainClass, g_szMainTitle);
    if (hwndFind)
    {
        // Bring previously running application's main
        // window to the user's attention
        hwndForeGround = GetForegroundWindow ();
        dwForeGroundID = GetWindowThreadProcessId (hwndForeGround, NULL);
        dwFindID = GetWindowThreadProcessId (hwndFind, NULL);

        // Don't do anything if window is already in foreground
        // Unless it is iconized.
        if ((dwFindID != dwForeGroundID) || (IsIconic (hwndFind)))
        {
            hwndLast = GetLastActivePopup (hwndFind);
            if (IsIconic (hwndLast))
            {
                ShowWindow (hwndLast, SW_RESTORE);
            }

            BringWindowToTop (hwndLast);
            SetForegroundWindow (hwndLast);
        }

        // Prevent additional instance's of this application
        // from running

        DPF (DEBUG_WARN, TEXT("CheckPrevious App - Another instance of this app is already running."));
        return FALSE;
    }

    return TRUE;
} // End CheckPreviousApp

/*
**-----------------------------------------------------------------------------
**  Name:       RunMain
**  Purpose:    Main Message Pump
**-----------------------------------------------------------------------------
*/

void RunMain (void)
{
    MSG msg;

    DPF (DEBUG_DETAILS, TEXT("RunMain - Enter"));

    if ((! g_hMainInstance) || (! g_hMainWindow))
    {
        return;
    }

    g_hMainAccel = LoadAccelerators (g_hMainInstance, MAKEINTRESOURCE (IDR_MAIN_ACCEL));

    //
    //  The Main Message loop
    //
    //  Note:  In order to catch Idle behavior we use PeekMessage instead of GetMessage
    //
    while (TRUE)
    {
        while (TRUE)
        {

            if (! PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
            {
                // App is Idle
                break;
            }

            // Exit App ?!?
            if (msg.message == WM_QUIT)
            {
                DPF (DEBUG_DETAILS, TEXT("RunMain - Exit"));
                g_nExitCode = msg.wParam;
                return;
            }

            if (g_hMainAccel)
            {
                if (! TranslateAccelerator (g_hMainWindow, g_hMainAccel, &msg))
                {
                    TranslateMessage (&msg);
                    DispatchMessage (&msg);
                }
            }
            else
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }

        // Do some Idle processing
        OnIdle (g_hMainWindow);
    } // End While

} // End RunMain

/*
**-----------------------------------------------------------------------------
**  Name:       WinMain
**  Purpose:    Application EntryPoint
**-----------------------------------------------------------------------------
*/

INT WINAPI WinMain(
    HINSTANCE   hInstance,    // handle to current instance
    HINSTANCE   hPrevInstance,// handle to previous instance
    LPSTR       lpCmdLine,    // pointer to command line
    INT         nShowCmd)     // show state of window
{
    g_nExitCode = 0;

    DPF (DEBUG_DETAILS, TEXT("WinMain - Enter"));

    //  Initialize App
    if (! InitMain ())
        return g_nExitCode;

    //  Main Message Loop
    RunMain ();

    //  Cleanup App
    FiniMain ();

    //  Success
    DPF (DEBUG_DETAILS, TEXT("WinMain - Exit, status = %08lX"), (DWORD)g_nExitCode);
    return g_nExitCode;
} // End WinMain

 /*
**-----------------------------------------------------------------------------
**  Name:       InitMainWindow
**  Purpose:
**-----------------------------------------------------------------------------
*/

BOOL InitMainWindow (void)
{
HRESULThResult;
    WNDCLASS    wc;
    HWND        hWnd;
DWORDdwStyle, dwStyleEx;

    DPF (DEBUG_DETAILS, TEXT("InitMainWindow - Enter"));

g_hMainCursor = LoadCursor ((HINSTANCE)NULL, IDC_ARROW);

    // Register Main Window Class
    wc.style            = CS_DBLCLKS;
    wc.lpfnWndProc      = (WNDPROC)D3DWindowProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 4;  // Reserve space for lpd3dWindow pointer
    wc.hInstance        = g_hMainInstance;
    wc.hIcon            = LoadIcon (g_hMainInstance, MAKEINTRESOURCE (IDI_MAIN_ICON));
    wc.hCursor          = g_hMainCursor;
    wc.hbrBackground    = (HBRUSH)GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName     = MAKEINTRESOURCE (IDR_MAIN_MENU);
    wc.lpszClassName    = g_szMainClass;

    if (! RegisterClass (&wc))
    {
        // Error - Register class failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - RegisterClass failed."));
        return FALSE;
    }

// We want a popup window
dwStyleEx = WS_EX_APPWINDOW;
dwStyle   = WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX;

//
    // Create Main Window
//
hResult = CreateD3DWindow (dwStyleEx,
   g_szMainClass,
   g_szMainTitle,
   dwStyle,
   0, 0, 640, 480,
   NULL,
   NULL,
   g_hMainInstance,
   NULL,
   TRUE,// Use Z-Buffer
   &hWnd,
   &g_lpd3dWin);
if (FAILED (hResult))
    {
        // Error - CreateWindow failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - CreateWindow failed."));
        return FALSE;
    }

//
// Create our D3D scene
//

g_lpd3dScene = new D3DScene;
if (! g_lpd3dScene)
{
        // Error - Construct D3D Scene failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - Not enough memory for D3D scene."));
        return FALSE;
}

// Attach our D3DScene to the Window
hResult = g_lpd3dWin->AttachScene (g_lpd3dScene);
if (FAILED (hResult))
{
        // Error - Attaching our D3D Scene to our D3D Window failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - AttachScene failed."));
        return FALSE;
}

    g_hMainWindow = hWnd;

    // Success
    DPF (DEBUG_DETAILS, TEXT("InitMainWindow - Success"));
    return TRUE;
} // End InitMainWindow

/*
**-----------------------------------------------------------------------------
** Name:    CreateD3DWindow
** Purpose: Creates a simple D3D window
** Notes:
**-----------------------------------------------------------------------------
*/

HRESULT CreateD3DWindow (
DWORDdwStyleEx,/* In:  CreateWindowEx parameters */
LPCTSTRlpszClass,
    LPCTSTRlpszName,
DWORDdwStyle,
intx,
inty,
intnWidth,
intnHeight,
HWNDhParent,
HMENUhMenu,
HINSTANCEhInstance,
LPVOIDlpParam,

BOOL        fUseZBuffer,/* In:  Use Z-buffer */
HWND *lphWnd,/* Out:Window handle */
LPD3DWindow * lpd3dWnd)/* Out: Created D3DWindow pointer */
{
    HRESULT             hResult;
    HWND                hWnd;
    LPD3DWindow         lpWnd;

// Check Parameters
if (! lpszClass)
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

// Fill in defaults, if missing
if (! hInstance)
hInstance = GetModuleHandle (NULL);

if (! hParent)
hParent = GetDesktopWindow ();

    // Create D3D Window
    lpWnd = new D3DWindow ();
    if (! lpWnd)
    {
// Error, not enough memory
REPORTERR (DDERR_OUTOFMEMORY);
        return DDERR_OUTOFMEMORY;
    }

    // Create Extended Window
    hWnd = CreateWindowEx (
                dwStyleEx,// extended window style
                lpszClass,// window class
                lpszName,    // window name (title)
                dwStyle,// window style
                x,// horizontal position of window
                y,// vertical position of window
                nWidth,// window width
                nHeight,            // window height
                hParent,// handle to parent window
                hMenu,                  // handle to menu
                hInstance,              // handle to app instance
                lpParam);               // Extra Data
    if (! hWnd)
    {
        // Error, unable to create window
        delete lpWnd;
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
    }

    //
    //  Gotcha:  You must Show the window before you change to Fullscreen
    //           Otherwise the following can happen.
    //   1. SetCooperativeLevel (DDSCL_EXCLUSIVE) works
    //             2. SetDisplayMode (w,h,bpp) loses the exclusive mode state
    //                because the window is not fully initialized.
    //                (DDraw gets confused by the responses it gets from the window)
    //             3. CreateSurface (PRIMARY | FLIP) fails with DDERR_NOEXCLUSIVEMODE.
    //                As the exlusive mode state has been lost
    //

    // Show the Window and Paint it's contents
    ShowWindow (hWnd, SW_SHOWDEFAULT);
    UpdateWindow (hWnd);

    // Create D3DWindow from hWnd
    hResult = lpWnd->Create (hWnd);
    if (FAILED (hResult))
    {
        // Error, unable to create window
        delete lpWnd;
        DestroyWindow (hWnd);
        return hResult;
    }

    // Return results
    if (lphWnd)
        *lphWnd = hWnd;
    if (lpd3dWnd)
        *lpd3dWnd = lpWnd;

    // Success
    return DD_OK;
} // End CreateD3DWindow

/*
**-----------------------------------------------------------------------------
** Name:    DestroyD3DWindow
** Purpose: Destroys a D3DWindow (window and D3DWindow object)
**-----------------------------------------------------------------------------
*/

HRESULT DestroyD3DWindow (void)
{
// Check Parameters
    if ((g_hMainWindow) && (IsWindow (g_hMainWindow)))
{
DestroyWindow (g_hMainWindow);
g_hMainWindow = NULL;
}

    // Free memory associated with D3DWindow Object
    if (g_lpd3dWin)
{
        delete g_lpd3dWin;
g_lpd3dWin = NULL;
}

// Sucess
    return DD_OK;
} // End DestroyD3DWindow

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/


WINMAIN.H

#ifndef WINMAIN_H
#define WINMAIN_H
/*
**-----------------------------------------------------------------------------
**  File:       WinMain.h
**  Purpose:    Sample Window code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

#define MIN_WIN_SIZE_X128
#define MIN_WIN_SIZE_Y  128
#define DEF_WIN_SIZE_X256
#define DEF_WIN_SIZE_Y  256

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

extern HINSTANCE    g_hMainInstance;
extern HWND         g_hMainWindow;
extern HACCEL       g_hMainAccel;
extern HCURSOR      g_hMainCursor;

extern LPCTSTR      g_szMainName;
extern LPCTSTR      g_szMainClass;
extern LPCTSTR      g_szMainTitle;
extern LPCTSTR      g_szPaused;

extern INT          g_nExitCode;

extern BOOL         g_fActive;
extern INT          g_nPaused;

extern INT          g_nWinWidth;
extern INT          g_nWinHeight;

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Windows routines
BOOL InitMain (void);
void FiniMain (void);

BOOL CheckPreviousApp (void);
void RunMain (void);
INT  WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, INT nShowCmd);
BOOL InitMainWindow (void);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End WINMAIN_H


WINPROC.CPP

/*
**-----------------------------------------------------------------------------
**  File:       WinProc.cpp
**  Purpose:    A sample windows proc for supporting D3D.
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Include Files
**-----------------------------------------------------------------------------
*/

#include "WinProc.h"
#include "WinMain.h"
#include "Debug.h"

/*
**-----------------------------------------------------------------------------
**Local Typedefs
**-----------------------------------------------------------------------------
*/

typedef struct tagChangeDDInfo {
DDDrvInfo  * lpDrvOrig;// Original Driver
DDDrvInfo  * lpDrvNew;// New Driver (user choice)

D3DDevInfo * lpDevOrig;// Original D3D device
D3DDevInfo * lpDevNew;// New D3D device (user choice)

DDModeInfo * lpModeOrig;// Orignal Mode
DDModeInfo * lpModeNew;// New Mode (user choice)
} ChangeDDInfo;
typedef ChangeDDInfo * LPChangeDDInfo;

/*
**-----------------------------------------------------------------------------
**Local Variables
**-----------------------------------------------------------------------------
*/

BOOL g_fMinimized = FALSE;
BOOL g_fMaximized = FALSE;

/*
**-----------------------------------------------------------------------------
**Local function prototypes
**-----------------------------------------------------------------------------
*/

// About Message proc
BOOL FAR PASCAL AboutBoxProc (HWND hWnd, UINT uiMsg,
  WPARAM wParam, LPARAM lParam);

// Change Dialog
BOOL DlgDriversInit (HWND hDlg);
BOOL DlgDevicesInit (HWND hDlg);
BOOL DlgModesInit (HWND hDlg);

int _cdecl CompareModes (const void* element1, const void* element2);

LPDDDrvInfo  DlgGetDriver (HWND hDlg);
LPD3DDevInfo DlgGetDevice (HWND hDlg);
LPDDModeInfo DlgGetMode (HWND hDlg);

BOOL FAR PASCAL ChangeDriverProc (HWND hWnd, UINT uiMsg,
      WPARAM wParam, LPARAM lParam);
void OnChangeDriver (HWND hWindow, int idDialog);

/*
**-----------------------------------------------------------------------------
**Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindowProc
**  Purpose:    handles windows messages for D3DWindow's
**-----------------------------------------------------------------------------
*/

LRESULT CALLBACK D3DWindowProc (
    HWND    hWindow,
    UINT    uiMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
LONG lResult;

    //
    // Windows messages
    //
    switch (uiMsg)
    {
case WM_ACTIVATE:
return OnActivate (hWindow, wParam, lParam);

    case WM_ACTIVATEAPP:
        return OnActivateApp (hWindow, wParam, lParam);

case WM_CLOSE:
return OnClose (hWindow);

    case WM_COMMAND:
        return OnCommand (hWindow, wParam, lParam);

    case WM_CREATE:
return OnCreate (hWindow);

    case WM_DESTROY:
return OnDestroy (hWindow);

case WM_DISPLAYCHANGE:
return OnDisplayChange (hWindow);

    case WM_ERASEBKGND:
return OnEraseBackground (hWindow, wParam, lParam);

case WM_ENTERMENULOOP:
return OnEnterMenuLoop (hWindow, wParam, lParam);

case WM_EXITMENULOOP:
return OnExitMenuLoop (hWindow, wParam, lParam);

    case WM_GETMINMAXINFO:
return OnGetMinMaxInfo (hWindow, wParam, lParam);

    case WM_MOVE:
        return OnMove (hWindow, wParam, lParam);

    case WM_NCPAINT:
return OnNCPaint (hWindow, wParam, lParam);

    case WM_PAINT:
    {
        HDC         hdc;
        PAINTSTRUCT ps;

        hdc = BeginPaint (hWindow, &ps);
        lResult = OnPaint (hWindow, hdc, &ps);
        EndPaint (hWindow, &ps);
    }
        return lResult;

case WM_PALETTECHANGED:
// Note:  We need to support this
break;

case WM_QUERYNEWPALETTE:
// Note:  We need to support this
break;

    case WM_SETCURSOR:
return OnSetCursor (hWindow, wParam, lParam);

    case WM_SIZE:
        return OnSize (hWindow, wParam, lParam);

case WM_SYSCOMMAND:
return OnSysCommand (hWindow, wParam, lParam);

case WM_WINDOWPOSCHANGING:
return OnWindowPosChanging (hWindow, wParam, lParam);

 //
     // D3D Window Query/Notification messages
     //
    case D3DWIN_GET_VALID:       // Get valid size
{
LPD3DWindow lpd3dWindow;

//Get D3DWindow pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
        if (! lpd3dWindow)
            return FALSE;
        return lpd3dWindow->isValid();
}

    case D3DWIN_GET_POINTER:
//Get D3DWindow pointer
return (LRESULT)GetWindowLong (hWindow, GWL_USERDATA);

    case D3DWIN_GET_SURFACE:
{
LPD3DWindow lpd3dWindow;

//Get D3DWindow pointer
lpd3dWindow =  (LPD3DWindow)(void *)GetWindowLong (hWindow, GWL_USERDATA);
        if (! lpd3dWindow)
            return NULL;
        return (LPARAM)(lpd3dWindow->GetRender ());
}

case D3DWIN_INIT:
return OnD3DInit (hWindow, (LPD3DWindow) lParam);

case D3DWIN_FINI:
return OnD3DFini (hWindow, (LPD3DWindow) lParam);

case D3DWIN_CHANGED_DRIVER:
return OnD3DChangeDriver (hWindow);

case D3DWIN_CHANGED_MODE:
return OnD3DChangeMode (hWindow);

case D3DWIN_CHANGED_DEVICE:
return OnD3DChangeDevice (hWindow);

    } // End Switch

   // Do Default window behavior
   return DefWindowProc (hWindow, uiMsg, wParam, lParam);
} // End D3DWindowProc

/*
**-----------------------------------------------------------------------------
**  Name:       AboutBoxProc
**  Purpose:    handles messages for About Dialog box
**-----------------------------------------------------------------------------
*/
BOOL
FAR PASCAL AboutBoxProc (HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
switch (uiMsg)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
EndDialog (hWnd, TRUE);
break;

    case WM_INITDIALOG:
return TRUE;
}
return FALSE;
} // End AboutBoxProc

/*
**-----------------------------------------------------------------------------
**  Name:       OnAbout
**  Purpose:    Display About Dialog
**-----------------------------------------------------------------------------
*/

LRESULT OnAbout (HWND hWindow)
{
HINSTANCE hInstance;

if ((! hWindow) || (! IsWindow (hWindow)))
return 0L;

hInstance = (HINSTANCE) GetWindowLong (hWindow, GWL_HINSTANCE);
if (! hInstance)
return 0L;

    // Pause App
    OnPause (hWindow, TRUE);

    // Do About Dialog here
    DialogBox (hInstance, MAKEINTRESOURCE (IDD_ABOUT), hWindow, (DLGPROC)AboutBoxProc);

    // UnPause app
    OnPause (hWindow, FALSE);

    return 0L;
} // End OnAbout

/*
**-----------------------------------------------------------------------------
**  Name:       OnActivate
**  Purpose:    handles WM_ACTIVATE message
**-----------------------------------------------------------------------------
*/

LRESULT OnActivate (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
BOOL fActive= (BOOL) LOWORD(wParam); // activation flag
BOOL fMinimized= (BOOL) HIWORD(wParam); // minimized flag
HWND hwndPrevious= (HWND) lParam; // window handl

// Do Default Behavior
return DefWindowProc (hWindow, WM_ACTIVATE, wParam, lParam);
} // End OnActivate

/*
**-----------------------------------------------------------------------------
**  Name:       OnActivateApp
**  Purpose:    handles WM_ACTIVATEAPP message
**-----------------------------------------------------------------------------
*/

LRESULT OnActivateApp (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
BOOLfActive= (BOOL) wParam;// Activate or deactivate
DWORDdwThreadID= (DWORD) lParam;// thread identifier

// Get D3D Window pointer
LPD3DWindow lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);

    if (fActive)
    {
// Reactivating - Realize current palette
if (lpd3dWindow)
{
lpd3dWindow->turnActiveOn ();
lpd3dWindow->Restore ();
lpd3dWindow->RealizePalette ();
}
    }
    else
    {
        //
        //  If we have been deactivated invalidate
        //  to show the paused display.
        //
if (lpd3dWindow)
{
lpd3dWindow->turnActiveOff ();
        InvalidateRect (hWindow, NULL, FALSE);
}

    }

    // Do default behavior
return DefWindowProc (hWindow, WM_ACTIVATEAPP, wParam, lParam);
} // End OnActivateApp

/*
**-----------------------------------------------------------------------------
**  Name:       OnClose
**  Purpose:    handles WM_CLOSE message
**-----------------------------------------------------------------------------
*/

LRESULT OnClose (HWND hWindow)
{
// Close down window
    DestroyWindow (hWindow);
    return 0L;
} // End OnClose

/*
**-----------------------------------------------------------------------------
**  Name:       OnCommand
**  Purpose:    handles WM_COMMAND messages
**-----------------------------------------------------------------------------
*/

LRESULT OnCommand (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
HWND  hControl= (HWND)lParam;
    DWORD dwNotify= (DWORD)HIWORD(wParam);
    DWORD dwID      = (DWORD)LOWORD(wParam);

    if (hControl)
    {
//
    // Handle messages from controls here
        //
    }
    else
{
//
// Handle Menu messages here
//
switch (dwID)
{
case IDM_EXIT:
// Close down app
SendMessage (hWindow, WM_CLOSE, 0, 0);
return 0;

case IDM_ABOUT:
// Bring up the "About" dialog
OnAbout (hWindow);
break;

case IDM_CHANGE_DRIVERS:
// Bring up the "Change Driver" dialog
OnChangeDriver (hWindow, IDD_CHANGE_DRIVER);
break;

case IDM_CHANGE_DEVICES:
// Bring up the "Change Device" dialog
OnChangeDriver (hWindow, IDD_CHANGE_DEVICE);
break;

case IDM_CHANGE_MODES:
// Bring up the "Change Mode" dialog
OnChangeDriver (hWindow, IDD_CHANGE_MODE);
break;

default:
// Unknown Menu Command
break;
}
}

    // Let Default Window Proc handle it for us.
    return DefWindowProc (hWindow, WM_COMMAND, wParam, lParam);
} // End OnCommand

/*
**-----------------------------------------------------------------------------
**  Name:       OnCreate
**  Purpose:    Handles WM_CREATE message
**-----------------------------------------------------------------------------
*/

LRESULT OnCreate (HWND hWindow)
{
    // Success
    return 0L;
} // End OnCreate

/*
**-----------------------------------------------------------------------------
**  Name:       OnDestroy
**  Purpose:    Handles WM_DESTROY message
**-----------------------------------------------------------------------------
*/

LRESULT OnDestroy (HWND hWindow)
{
    LPD3DWindow lpd3dWindow;

    //Delete associated D3DWindow from this window
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, 0L);
if (lpd3dWindow)
lpd3dWindow->Fini ();
SetWindowLong (hWindow, GWL_USERDATA, 0L);

    // Tell windows to QUIT!
    PostQuitMessage (0);
    return 0L;
} // End OnDestroy

/*
**-----------------------------------------------------------------------------
**  Name:       OnDisplayChange
**  Purpose:    Handles WM_DISPLAYCHANGE message
**  Notes:This means the desktop mode has changed underneath us
**-----------------------------------------------------------------------------
*/

LRESULT OnDisplayChange (HWND hWindow)
{
HRESULThResult;
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

// Change Primary surface to new desktop
hResult = lpd3dWindow->ChangeDesktop ();
if (FAILED (hResult))
return 0L;

// Success
    return 0L;
} // End OnDestroy

/*
**-----------------------------------------------------------------------------
**  Name:       OnEraseBackground
**  Purpose:    Handles WM_ERASEBKGND message
**-----------------------------------------------------------------------------
*/

LRESULT OnEraseBackground (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if ((lpd3dWindow) &&
    (lpd3dWindow->isValid ()) &&
(lpd3dWindow->isActive ()) &&
(! lpd3dWindow->isPaused ()))
{
// Tell system we erased background, even though we didn't
// It will soon get covered up by a frame from RenderScene
return 1L;
}

// Do the Default behavior (erase the background)
return DefWindowProc (hWindow, WM_ERASEBKGND, wParam, lParam);
} // End OnEraseBackground

/*
**-----------------------------------------------------------------------------
**  Name:       OnEnterMenuLoop
**  Purpose:    Handles WM_ENTERMENULOOP messages
**-----------------------------------------------------------------------------
*/

LRESULT OnEnterMenuLoop (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if ((lpd3dWindow) && (lpd3dWindow->isValid ()))
{
// pause app (go into GDI drawing mode)
lpd3dWindow->Pause (TRUE);

// Restore cursor
SetCursor (g_hMainCursor);
}

// Do the Default behavior
return DefWindowProc (hWindow, WM_ENTERMENULOOP, wParam, lParam);
} // End OnEnterMenuLoop

/*
**-----------------------------------------------------------------------------
**  Name:       OnExitMenuLoop
**  Purpose:    Handles WM_EXITMENULOOP messages
**-----------------------------------------------------------------------------
*/

LRESULT OnExitMenuLoop (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if ((lpd3dWindow) && (lpd3dWindow->isValid ()))
{
// Get Rid of cursor again
SetCursor (NULL);

// Unpause app (return from GDI drawing)
lpd3dWindow->Pause (FALSE);
}

// Do the Default behavior
return DefWindowProc (hWindow, WM_EXITMENULOOP, wParam, lParam);
} // End OnExitMenuLoop

/*
**-----------------------------------------------------------------------------
**  Name:       OnGetMinMaxInfo
**  Purpose:    Handles WM_GETMINMAXINFO message
**-----------------------------------------------------------------------------
*/

LRESULT OnGetMinMaxInfo (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
LPMINMAXINFOlpmmi;
LPD3DWindowlpd3dWindow;
RECTrSurf;
DWORDdwWidth;
DWORDdwHeight;

lpmmi = (LPMINMAXINFO)lParam;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if ((lpd3dWindow) &&
(lpd3dWindow->isFullscreen ()) &&
(! g_fMinimized))
{
// Get Width and height of current surface
lpd3dWindow->GetSurfaceRect (rSurf);

dwWidth  = abs (rSurf.right - rSurf.left);
dwHeight = abs (rSurf.bottom - rSurf.top);

// Prevent changes in size
lpmmi->ptMaxTrackSize.x = dwWidth;
lpmmi->ptMaxTrackSize.y = dwHeight;
lpmmi->ptMinTrackSize.x = dwWidth;
lpmmi->ptMinTrackSize.y = dwHeight;

return 0L;
}

return DefWindowProc (hWindow, WM_GETMINMAXINFO, wParam, lParam);
} // End OnGetMinMaxInfo

/*
**-----------------------------------------------------------------------------
**  Name:       OnIdle
**  Purpose:    Animate while system is idle
**-----------------------------------------------------------------------------
*/

void OnIdle (HWND hWindow)
{
    HRESULT hResult;
    LPD3DWindow  lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

    //
    // Only animate if we are the foreground app, we aren't suspended
    // and we have completed initialization and we aren't minimized
    //
if ((! lpd3dWindow->isActive ()) ||
(lpd3dWindow->isPaused ()) ||
(! lpd3dWindow->isValid ()) ||
(g_fMinimized))
return;

    hResult = lpd3dWindow->DrawFrame ();
    if (FAILED (hResult))
return;
} // End OnIdle

/*
**-----------------------------------------------------------------------------
**  Name:       OnMove
**  Purpose:    handles WM_MOVE message
**-----------------------------------------------------------------------------
*/

LRESULT OnMove (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
// We don't support moving Full-screen windows

    // Do default behavior
    return DefWindowProc (hWindow, WM_MOVE, wParam, lParam);
} // End OnMove

/*
**-----------------------------------------------------------------------------
**  Name:       OnNCPaint
**  Purpose:    Handles WM_NCPaint message
**-----------------------------------------------------------------------------
*/

LRESULT OnNCPaint (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if ((lpd3dWindow) &&
    (lpd3dWindow->isValid ()) &&
(lpd3dWindow->isActive ()) &&
(! lpd3dWindow->isPaused ()) &&
(! g_fMinimized))
{
// Prevent the system from drawing the NC frame
return 0L;
}

// Do the Default behavior (Draw the NC Frame)
return DefWindowProc (hWindow, WM_NCPAINT, wParam, lParam);
} // End OnNCPaint

/*
**-----------------------------------------------------------------------------
**  Name:       OnPaint
**  Purpose:Handles WM_PAINT messages
**-----------------------------------------------------------------------------
*/

LRESULT OnPaint (HWND hWindow, HDC hdc, LPPAINTSTRUCT lpps)
{
    HRESULThResult;
LPD3DWindow lpd3dWindow;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);

    if (lpd3dWindow &&
lpd3dWindow->isActive () &&
!lpd3dWindow->isPaused () &&
lpd3dWindow->isValid() &&
! g_fMinimized)
    {
        //
        // NOTE: DrawFrame() re-renders the scene as well as blitting the
        // result to the primary. As all we really want to do here is
        // repaint the client area we don't really need to re-render -
        // just re-blit. For this simple sample this inefficiency
        // doesn't matter but for real applications not re-rendering
        // may be a useful optimization.
        //
        hResult = lpd3dWindow->DrawFrame ();
        if (FAILED (hResult))
            return 0L;
    }
    else
    {
        //
        // Show the suspended image if the D3Dwinow is not yet created,
// created, the window is inactive, or paused.
        //
        PaintPaused (hWindow, hdc);
    }

    return 0L;
} // End OnPaint

/*
**-----------------------------------------------------------------------------
**  Name:OnPause
**  Purpose:Pause/Unpause app
**-----------------------------------------------------------------------------
*/

void OnPause (HWND hWindow, BOOL fPause)
{
LPD3DWindow lpd3dWindow;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

lpd3dWindow->Pause (fPause);
} // End OnPause

/*
**-----------------------------------------------------------------------------
**  Name:       OnSetCursor
**  Purpose:Handles WM_SETCURSOR messages
**-----------------------------------------------------------------------------
*/

LRESULT OnSetCursor (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow  lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);

// Don't show cursor while in fullscreen
if ((lpd3dWindow) && (lpd3dWindow->isFullscreen ()))
{
if ((lpd3dWindow->isPaused ()) ||
(! lpd3dWindow->isActive ()))
{
// Restore cursor
SetCursor (g_hMainCursor);
}
else
{
// Get rid of cursor
SetCursor (NULL);
}
return TRUE;
}

// Do Default behavior
return DefWindowProc (hWindow, WM_SETCURSOR, wParam, lParam);
} // End OnSetCursor

/*
**-----------------------------------------------------------------------------
**  Name:       OnSize
**  Purpose:Handles WM_SIZE messages
**-----------------------------------------------------------------------------
*/

LRESULT OnSize (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
DWORDfwSizeType= (DWORD)wParam;
    LPD3DWindowlpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);

switch (fwSizeType)
{
case SIZE_MAXHIDE:
// Message is sent to all pop-up windows when some other window is maximized.
break;

case SIZE_MAXIMIZED:
// Window has been maximized.
g_fMaximized = TRUE;
g_fMinimized = FALSE;
break;

case SIZE_MAXSHOW:
// Message is sent to all pop-up windows when some other window has been restored to its former size.
break;

case SIZE_MINIMIZED:
// Window has been minimized.
g_fMinimized = TRUE;
g_fMaximized = FALSE;
break;

case SIZE_RESTORED:
// Window has been resized but neither minimized nor maximized
if (g_fMinimized)
{
// Restore surfaces
if (lpd3dWindow)
lpd3dWindow->Restore ();

g_fMaximized = FALSE;
g_fMinimized = FALSE;
}
return 0L;
}

// Do Default Behavior
    return DefWindowProc (hWindow, WM_SIZE, wParam, lParam);
} // End OnSize

/*
**-----------------------------------------------------------------------------
**  Name:       OnSysCommand
**  Purpose:Handles WM_SYSCOMMAND messages
**-----------------------------------------------------------------------------
*/

LRESULT OnSysCommand (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
// Get D3D window pointer
    LPD3DWindowlpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
DWORDuCmdType = wParam;        // Type of system command requested
intxPos = LOWORD(lParam);    // horizontal postion, in screen coordinates
intyPos = HIWORD(lParam);    // vertical postion, in screen coordinates

switch (uCmdType)
{
case SC_MAXIMIZE:
// Maximizes the window.
g_fMaximized = TRUE;
g_fMinimized = FALSE;

// Not supported
return 0L;

case SC_MINIMIZE:
// Minimizes the window.
g_fMinimized = TRUE;
g_fMaximized = FALSE;
break;

case SC_MOVE:
// Moves the window

// Not supported
return 0L;

case SC_RESTORE:
//Restores the window to its normal position and size.
if (g_fMinimized)
{
// Restore app
if (lpd3dWindow)
lpd3dWindow->Restore ();
}

g_fMinimized = FALSE;
g_fMaximized = FALSE;
break;

case SC_SCREENSAVE:
// Executes the screen saver application specified in the [boot] section of the SYSTEM.INI file.
break;

case SC_SIZE:
// Sizes the window.

// Not supported
return 0L;
}

// Do Default Behavior
    return DefWindowProc (hWindow, WM_SYSCOMMAND, wParam, lParam);
} // End OnSysCommand

/*
**-----------------------------------------------------------------------------
**  Name:       OnWindowPosChanging
**  Purpose:    handles WM_WINDOWPOSCHANGING message
**  Notes:
**
**1. This is where we protect ourselves against unwanted moving
**   and resizing of the window.  (We are fullscreen after all)
**
**  2. First gotcha:  Changing the Display mode causes the window
**   to change size and we want to let that happen.  So we will
**   compare the current window size to the current surface size.
**   If they don't match we will resize the window to match the surface.
**
**  3. Second gotcha.  DirectDraw hooks the window procedure and on
**   a deactivate app call trys to minimize the window on our behalf.
**   We should allow this to happen!!!
**   It can do this by either calling ShowWindow or SetWindowPos
**
**-----------------------------------------------------------------------------
*/

LRESULT OnWindowPosChanging (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
// We don't support moving Full-screen windows
// Get D3D Window pointer
LPD3DWindow lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (lpd3dWindow &&
(lpd3dWindow->isFullscreen ()) &&
(! g_fMinimized))
{
LPWINDOWPOS lpwp = (LPWINDOWPOS) lParam;

if (lpwp)
{
// Let minimize commands fall through
// Check for minimize by seeing if window is iconic
// this state should already be set by the time
// we get this window message
if (IsIconic (hWindow))
{
// Looks like we are getting minimized
g_fMinimized = TRUE;
}
else
{
RECTrWin, rSurf;
DWORDdwWinW, dwWinH;
DWORDdwSurfW, dwSurfH;

GetWindowRect (hWindow, &rWin);
dwWinW = abs (rWin.right - rWin.left);
dwWinH = abs (rWin.bottom - rWin.top);

lpd3dWindow->GetSurfaceRect (rSurf);
dwSurfW = abs (rSurf.right - rSurf.left);
dwSurfH = abs (rSurf.bottom - rSurf.top);

// Make sure the surface is some reasonable size
if (dwSurfW < 320)
dwSurfW = 320;
if (dwSurfH < 200)
dwSurfH = 200;

// Is our window the same size as our surface ?!?
if ((dwSurfW != dwWinW) || (dwSurfH != dwWinH))
{
// No, then resize it
lpwp->x = rSurf.left;
lpwp->y = rSurf.top;
lpwp->cx = dwSurfW;
lpwp->cy = dwSurfH;

// Force resize
lpwp->flags &= ~(SWP_NOMOVE | SWP_NOSIZE);
}
else
{
// Prevent resizing and moving
lpwp->flags |= (SWP_NOMOVE | SWP_NOSIZE);
}

// Fall through to default window proc
}
}
}

    // Do Default Behavior
    return DefWindowProc (hWindow, WM_WINDOWPOSCHANGING, wParam, lParam);
} // End OnWindowPosChanging

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DInit
**  Purpose:Notification that associated D3D Window object
**has successfully initialized
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DInit (HWND hWindow, LPD3DWindow lpd3dWindow)
{
if ((! lpd3dWindow) || (! lpd3dWindow->isValid ()))
{
// Error, something went wrong
return 0L;
}

// Save the pointer to the D3D Window object
SetWindowLong (hWindow, GWL_USERDATA, (long)(void *)lpd3dWindow);

// Make sure we are properly marked as active/inactive
HWND hActive = GetActiveWindow ();
if (hActive == hWindow)
lpd3dWindow->turnActiveOn ();
else
lpd3dWindow->turnActiveOff ();

// Success
return 0L;
} // End OnD3DInit

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DFini
**  Purpose:Notification that the D3D window object
**is cleaning up.
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DFini (HWND hWindow, LPD3DWindow lpd3dWindow)
{
// Set our window pointer to NULL
// so we don't attempt to misuse it later.
SetWindowLong (hWindow, GWL_USERDATA, 0L);

// Success
return 0L;
} // End OnD3DFini

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeDriver
**  Purpose:Notification that our associated D3D Window object
**has changed it's DD Driver.
**  Notes:This is a good time to update our Menus
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DChangeDriver (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeDriver

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeMode
**  Purpose:Notification that our associated D3D Window object
**has changed it's mode.
**  Notes:This is a good time to update our Menus
**-----------------------------------------------------------------------------

*/

LRESULT OnD3DChangeMode (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeMode

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeDevice
**  Purpose:Notification that our associated D3D Window object
**has changed it's D3D device.
**  Notes:This is a good time to update our Menus
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DChangeDevice (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeDevice

/*
**-----------------------------------------------------------------------------
**  Name:       PaintPaused
**  Purpose:Paint the window as Paused
**  Notes:
**
**  1.  We show the paused state by drawing a text string
**      in the client area of the window.
**
**-----------------------------------------------------------------------------
*/

void PaintPaused (HWND hWindow, HDC hdc)
{
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    COLORREF crOldTextColor;
    INT      oldMode;
    INT      x;
    INT      y;
    SIZE     size;
    RECT     rect;
    INT      nStrLen;

    //
    // Black background.
    //
    hOldPen   = (HPEN)SelectObject (hdc, GetStockObject (NULL_PEN));
    hOldBrush = (HBRUSH)SelectObject (hdc, GetStockObject (BLACK_BRUSH));

    //
    // White text.
    //
    oldMode = SetBkMode (hdc, TRANSPARENT);
    crOldTextColor = SetTextColor (hdc, RGB(255, 255, 255));

    //
    GetClientRect (hWindow, &rect);

    //
    // Clear the client area.
    //
    Rectangle (hdc, rect.left, rect.top, rect.right + 1, rect.bottom + 1);

    //
    // Draw the string centered in the client area.
    //
    nStrLen = strlen (g_szPaused);
    GetTextExtentPoint32 (hdc, g_szPaused, nStrLen, &size);
    x = (rect.right  - size.cx) / 2;
    y = (rect.bottom - size.cy) / 2;
    TextOut (hdc, x, y, g_szPaused, nStrLen);

    // Cleanup
    SetTextColor (hdc, crOldTextColor);
    SetBkMode (hdc, oldMode);

    SelectObject (hdc, hOldBrush);
    SelectObject (hdc, hOldPen);
} // End PaintPaused

/*
**-----------------------------------------------------------------------------
**Dialog Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DlgDriversInit
**  Purpose:    Set up Drivers for dialog
**  Notes:Shows list of current Drivers
**-----------------------------------------------------------------------------
*/

BOOL DlgDriversInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;
LPDDDrvInfolpCurr, lpNext;
DWORDdwIndex;
LPGUIDlpGuidDD;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver = lpChange->lpDrvNew;

// Validate Driver
if (! lpDriver)
lpGuidDD = NULL;
else
lpGuidDD = lpDriver->GetGuid ();
lpDriver = ValidateDriver (lpGuidDD);
if (! lpDriver)
return FALSE;

// Dump Driver list to Combo Box
dwIndex = 0;
lpCurr = DDDrvMgr::g_lpDriverRoot;
while (lpCurr)
{
TCHAR szBuff[80];
DWORD dwNew;

lpNext = lpCurr->lpNext;

if (lpCurr->isPrimary ())
wsprintf (szBuff, TEXT("%s (Primary)"), lpCurr->szName);
else
wsprintf (szBuff, TEXT("%s"), lpCurr->szName);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to driver for this item
SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpCurr);

// Is it the current Driver
if (lpDriver == lpCurr)
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}

lpCurr = lpNext;
}

// Success
return TRUE;
} // End DlgDriversInit

/*
**-----------------------------------------------------------------------------
**  Name:       DlgDevicesInit
**  Purpose:    Set up Devices for dialog
**  Notes:Shows list of current Devices
**-----------------------------------------------------------------------------
*/

BOOL DlgDevicesInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;
LPD3DDevInfolpDevice;
LPD3DDevInfolpCurr, lpNext;
DWORDdwIndex;
LPGUIDlpGuidD3D;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver = lpChange->lpDrvNew;
if (! lpDriver)
return FALSE;

lpDevice = lpChange->lpDevNew;

// Validate Device
if (! lpDevice)
lpGuidD3D = NULL;
else
lpGuidD3D = &(lpDevice->guid);
lpDevice = ValidateDevice (lpDriver, lpGuidD3D, NULL);
if (! lpDevice)
return FALSE;

// Dump Device list to Combo Box
dwIndex = 0;
lpCurr = lpDriver->lpDeviceRoot;
while (lpCurr)
{
TCHAR szBuff[80];
DWORD dwNew;

lpNext = lpCurr->lpNext;

// Get Device String
wsprintf (szBuff, TEXT("%s"), lpCurr->szName);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_DEVICES, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to device for this item
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpCurr);

// Is it the current device
if (lpDevice == lpCurr)
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}

lpCurr = lpNext;
}

// Success
return TRUE;
} // End DlgDevicesInit

/*
**-----------------------------------------------------------------------------
**  Name:       CompareModes
**  Purpose:    Comparision function used with Quick sort
**to get sorted list of modes
**-----------------------------------------------------------------------------
*/

int _cdecl CompareModes (const void * element1, const void * element2)
{
LPDDModeInfo lpMode1;
LPDDModeInfo lpMode2;
DWORD w1, h1, bpp1, refresh1;
DWORD w2, h2, bpp2, refresh2;
int   iCompare = 0;

    lpMode1 = *((LPDDModeInfo *)element1);
    lpMode2 = *((LPDDModeInfo *)element2);

// Handle Null pointers
if ((! lpMode1) && (! lpMode2))
return 0;
if ((lpMode1) && (! lpMode2))
return -1;
if ((! lpMode2) && (lpMode2))
return 1;

// Get Mode Data
lpMode1->GetMode (w1, h1, bpp1, refresh1);
lpMode2->GetMode (w2, h2, bpp2, refresh2);

// Sort first on BPP then width then height
    if (bpp1 < bpp2)
iCompare = -1;
    else if (bpp1 > bpp2)
iCompare = 1;
    else if (w1 < w2)
iCompare = -1;
    else if (w1 > w2)
iCompare = 1;
    else if (h1 < h2)
iCompare = -1;
    else if (h1 > h2)
iCompare = 1;

// Equality
    return iCompare;
} // End CompareModes

/*
**-----------------------------------------------------------------------------
**  Name:       DlgModesInit
**  Purpose:    Set up Modes for dialog
**  Notes:Shows list of current modes, compatible with the current
**device.  This list is displayed in sorted order (bpp, w, h)
**-----------------------------------------------------------------------------
*/

BOOL DlgModesInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;
LPD3DDevInfolpDevice;
LPDDModeInfolpModeCurr, lpModeRoot, lpCurr, lpNext;
DWORDcModes;
DWORDcbSize;
DWORDdwIndex;
LPDDModeInfo *lpModes;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver   = lpChange->lpDrvNew;
lpDevice   = lpChange->lpDevNew;
lpModeCurr = lpChange->lpModeNew;

if (! lpDriver)
return FALSE;

if (! lpDevice)
{
lpDevice = ValidateDevice (lpDriver, NULL, NULL);
if (! lpDevice)
return FALSE;
}

lpModeRoot = lpDriver->lpModeRoot;
if (! lpModeRoot)
return FALSE;

cModes = lpDriver->countModes ();
if (! cModes)
return FALSE;

if (! lpModeCurr)
{
lpModeCurr = ValidateMode (lpDriver, 640, 480, 16, 0, lpDevice);
}

// Get memory for Mode list
cbSize = cModes * sizeof (LPDDModeInfo);
lpModes = (LPDDModeInfo *) malloc (cbSize);
if (! lpModes)
return FALSE;

// Create Mode List
dwIndex = 0;
lpCurr = lpModeRoot;
while (lpCurr)
{
lpNext = lpCurr->lpNext;

lpModes[dwIndex] = lpCurr;

dwIndex++;

lpCurr = lpNext;
}

// Sort Mode list
    qsort ((void *)lpModes, (size_t)cModes, sizeof(LPDDModeInfo), CompareModes);

// Dump Mode list to Combo Box
for (dwIndex = 0; dwIndex < cModes; dwIndex++)
{
// Make sure mode is supported by D3D device
if ((lpModes[dwIndex]) &&
(lpModes[dwIndex]->ModeSupported (lpDevice)))
{
TCHAR szBuff[80];
DWORD w, h, bpp, refresh;
DWORD dwNew;

lpModes[dwIndex]->GetMode (w, h, bpp, refresh);

// Set up Mode String
if (refresh)
wsprintf (szBuff, TEXT("%4d x %4d x %4d (%4d Hz)"),
      w, h, bpp, refresh);
else
wsprintf (szBuff, TEXT("%4d x %4d x %4d"),
      w, h, bpp);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_MODES, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to Mode Info for this item
SendDlgItemMessage (hDlg, IDC_MODES, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpModes[dwIndex]);

// Is it the current Mode
if (lpModeCurr == lpModes[dwIndex])
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_MODES, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}
}
}

// Cleanup Memory
free (lpModes);
lpModes = NULL;

// Success
return TRUE;
} // End DlgModesInit

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetDriver
**  Purpose:    Get current driver selection
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo DlgGetDriver (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to driver
LPDDDrvInfo lpDriver = (LPDDDrvInfo) SendDlgItemMessage (hDlg,
       IDC_DRIVERS,
 CB_GETITEMDATA,
 (WPARAM)dwIndex,
 (LPARAM)0);
return lpDriver;
}

// Failure
return NULL;
} // End DlgGetDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetDevice
**  Purpose:    Get current device selection
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo DlgGetDevice (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_DEVICES, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to device
LPD3DDevInfo lpDevice = (LPD3DDevInfo) SendDlgItemMessage (hDlg,
         IDC_DEVICES,
   CB_GETITEMDATA,
   (WPARAM)dwIndex,
   (LPARAM)0);
return lpDevice;
}

// Failure
return NULL;
} // End DlgGetDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetMode
**  Purpose:    Get current mode selection
**-----------------------------------------------------------------------------
*/

LPDDModeInfo DlgGetMode (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_MODES, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to device
LPDDModeInfo lpMode = (LPDDModeInfo) SendDlgItemMessage (hDlg,
         IDC_MODES,
 CB_GETITEMDATA,
 (WPARAM)dwIndex,
 (LPARAM)0);
return lpMode;
}

// Failure
return NULL;
} // End DlgGetMode

/*
**-----------------------------------------------------------------------------
**  Name:       OnChangeDriver
**  Purpose:    Allows user to choose a new Driver from dialog
**Notes:User can also choose a new device and mode
**-----------------------------------------------------------------------------
*/

void OnChangeDriver (HWND hWindow, int idDialog)
{
HINSTANCEhInstance;
intfResult;
ChangeDDInfochangeInfo;
LPD3DWindowlpd3dWindow;

// Get application instance
hInstance = (HINSTANCE) GetWindowLong (hWindow, GWL_HINSTANCE);
if (! hInstance)
return;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

// Setup Change info
changeInfo.lpDrvOrig= lpd3dWindow->GetDriverInfo ();
changeInfo.lpDrvNew= changeInfo.lpDrvOrig;

changeInfo.lpDevOrig= lpd3dWindow->GetDeviceInfo ();
changeInfo.lpDevNew= changeInfo.lpDevOrig;

changeInfo.lpModeOrig= lpd3dWindow->GetModeInfo ();
changeInfo.lpModeNew= changeInfo.lpModeOrig;

    // Pause App
    OnPause (hWindow, TRUE);

    // Do Change Mode Dialog here
    fResult = DialogBoxParam (hInstance, MAKEINTRESOURCE (idDialog),
  hWindow, (DLGPROC)ChangeDriverProc,
  (LPARAM)(void *)&changeInfo);

    // UnPause app
    OnPause (hWindow, FALSE);

// Did user request a change ?!?
if (fResult == TRUE)
{
LPGUID lpGuidDD, lpGuidD3D;
DWORD w, h, bpp, refresh;

if (changeInfo.lpDrvOrig != changeInfo.lpDrvNew)
{
if (changeInfo.lpDrvNew)
lpGuidDD = changeInfo.lpDrvNew->GetGuid ();
else
lpGuidDD = NULL;

lpd3dWindow->ChangeDriver (lpGuidDD,
   changeInfo.lpDevNew,
   changeInfo.lpModeNew);
}
else if (changeInfo.lpDevOrig != changeInfo.lpDevNew)
{
if (changeInfo.lpDevNew)
lpGuidD3D = &(changeInfo.lpDevNew->guid);
else
lpGuidD3D = NULL;

lpd3dWindow->ChangeDevice (lpGuidD3D, changeInfo.lpModeNew);
}
else if (changeInfo.lpModeOrig != changeInfo.lpModeNew)
{
changeInfo.lpModeNew->GetMode (w, h, bpp, refresh);
lpd3dWindow->ChangeMode (w, h, bpp, refresh);
}
}
} // End OnChangeDriver

/*
**-----------------------------------------------------------------------------
**  Name:       ChangeDriverProc
**  Purpose:    handles messages for Change Driver,
**D3D device, and mode dialog boxes
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK ChangeDriverProc(
    HWND hDlg,// handle to dialog box
    UINT uiMsg,// message
    WPARAM wParam,// first message parameter
    LPARAM lParam) // second message parameter
{
BOOLfChanged;
LPChangeDDInfo  lpChange;
LPDDDrvInfolpDriver;
LPDDModeInfolpMode;
LPD3DDevInfolpDevice;

switch (uiMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
// Get Change Info
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
EndDialog (hDlg, FALSE);

fChanged = FALSE;

// Get New Driver
lpDriver = DlgGetDriver (hDlg);
if ((lpDriver) && (lpDriver != lpChange->lpDrvOrig))
{
fChanged = TRUE;
lpChange->lpDrvNew = lpDriver;
}
else
lpChange->lpDrvNew = lpChange->lpDrvOrig;

// Get New Device
lpDevice = DlgGetDevice (hDlg);
if ((lpDevice) && (lpDevice != lpChange->lpDevOrig))
{
fChanged = TRUE;
lpChange->lpDevNew = lpDevice;
}
else
lpChange->lpDevNew = lpChange->lpDevOrig;

// Get New Mode
lpMode = DlgGetMode (hDlg);
if ((lpMode) && (lpMode != lpChange->lpModeOrig))
{
fChanged = TRUE;
lpChange->lpModeNew = lpMode;
}
else
lpChange->lpModeNew = lpChange->lpModeOrig;

// Return success/failure
EndDialog (hDlg, fChanged);
break;

case IDCANCEL:
EndDialog (hDlg, FALSE);
break;

case IDC_DRIVERS:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current driver
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the Driver
lpDriver = DlgGetDriver (hDlg);
if ((lpDriver) && (lpDriver != lpChange->lpDrvNew))
{
lpChange->lpDrvNew = lpDriver;
lpChange->lpDevNew = NULL;// Pick a new device
lpChange->lpModeNew = NULL;// Pick a new mode

// Update the Device list
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_RESETCONTENT, 0, 0);
DlgDevicesInit (hDlg);

// Update the Mode list
SendDlgItemMessage (hDlg, IDC_MODES, CB_RESETCONTENT, 0, 0);
DlgModesInit (hDlg);
}
break;
}
break;

case IDC_DEVICES:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current device
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the device
lpDevice = DlgGetDevice (hDlg);
if ((lpDevice) && (lpDevice != lpChange->lpDevNew))
{
lpChange->lpDevNew = lpDevice;

// Update the Mode list
SendDlgItemMessage (hDlg, IDC_MODES, CB_RESETCONTENT, 0, 0);
DlgModesInit (hDlg);
}
break;
}
break;

case IDC_MODES:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current mode
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the Mode
lpMode = DlgGetMode (hDlg);
if ((lpMode) && (lpMode != lpChange->lpModeNew))
{
lpChange->lpModeNew = lpMode;
}
break;
}
break;
}
break;

    case WM_INITDIALOG:
// Save pointer to ChangeInfo
SetWindowLong (hDlg, DWL_USER, (long)lParam);

// Set up the current driver, device, and mode lists
if (GetDlgItem (hDlg, IDC_DRIVERS))
if (! DlgDriversInit (hDlg))
return FALSE;

if (GetDlgItem (hDlg, IDC_DEVICES))
if (! DlgDevicesInit (hDlg))
return FALSE;

if (GetDlgItem (hDlg, IDC_MODES))
if (! DlgModesInit (hDlg))
return FALSE;

// Successful init
return TRUE;
}
return FALSE;
} // End ChangeDriverProc

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/


WINPROC.H

#ifndef WINPROC_H
#define WINPROC_H
/*
**-----------------------------------------------------------------------------
**  File:       WinProc.h
**  Purpose:    Sample Window Procedure code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "D3DWin.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

#define MENU_DRIVER0
#define MENU_DEVICE1
#define MENU_MODE2
#define MENU_TEXTURE3
#define MENU_HELP4

#define MENU_FIRST_DRIVER1500
#define MENU_FIRST_DEVICE1600
#define MENU_FIRST_MODE1700
#define MENU_FIRST_TEXTURE1800
#define MENU_FIRST_TEXFORMAT1900
#define MENU_LAST_DYNAMIC2000

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Windows routines
LRESULT CALLBACK D3DWindowProc (HWND hWindow, UINT uiMessage,
                                WPARAM wParam, LPARAM lParam);

// Window Message routines
LRESULT OnAbout (HWND hWindow);
LRESULT OnActivate (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnActivateApp (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnClose (HWND hWindow);
LRESULT OnCommand (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnCreate (HWND hWindow);
LRESULT OnDestroy (HWND hWindow);
LRESULT OnDisplayChange (HWND hWindow);
LRESULT OnEraseBackground (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnEnterMenuLoop (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnExitMenuLoop (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnGetMinMaxInfo (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnMove (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnNCPaint (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnPaint (HWND hWindow, HDC hdc, LPPAINTSTRUCT lpps);
LRESULT OnSetCursor (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnSize (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnSysCommand (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnWindowPosChanging (HWND hWindow, WPARAM wParam, LPARAM lParam);

// Special Non Message routines
void    OnIdle (HWND hWindow);
void    OnPause (HWND hWindow, BOOL fPause);

// D3D Notification routines
LRESULTOnD3DInit (HWND hWindow, LPD3DWindow lpd3dWindow);
LRESULTOnD3DFini (HWND hWindow, LPD3DWindow lpd3dWindow);
LRESULTOnD3DChangeDriver (HWND hWindow);
LRESULTOnD3DChangeMode (HWND hWindow);
LRESULTOnD3DChangeDevice (HWND hWindow);
LRESULTOnD3DChangeFullscreen (HWND hWindow);

// Misc Routines
void    PaintPaused (HWND hWindow, HDC hdc);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End WINPROC_H


README.TXT

Readme for D3DEx1 Sample
========================

This sample displays a spinning triangle in a window.
It uses Direct3D Immediate mode and execute buffers.

This application is a good place to look for a good way to determine
what Direct3D device to use.

Further pointers:
The D3dEx2 sample application shows how just a few changes are needed to
work well on a multi-monitor system.

3/5/97 Known limitations:

On MMX machines, RGB is faster than RAMP. Currently it chooses RAMP.

Resizing of the window is not supported. (Because of the varying amounts
of video-card memory, a resize may force the application to have to stop
using the h/w and resort to emulation.)


NOWARN.H

/*
 * Shut that bloody bouzouki up!
 * Disable the benign /W4 warnings generate when including system
 * header files from .c source files.
 *
 * W4201 = nameless struct/union.
 * W4214 = nonstandard extension used : bit field types other than int
 * W4209 = nonstandard extension used : benign typedef redefinition
 * W4001 = nonstandard extension used : 'single line comment' was used
 * W4115 = named type definition in parantheses
 * W4699 = Note: Creating precomplied header
 * W4514 = Note: Creating new precomplied header
 * W4127 = conditional expression is constant
 */
#pragma warning(disable:4201)
#pragma warning(disable:4214)
#pragma warning(disable:4209)
#pragma warning(disable:4001)
#pragma warning(disable:4115)
#pragma warning(disable:4699)
#pragma warning(disable:4514)
#pragma warning(disable:4127)


D3DEX1.C

/***********************************************************************
 *
 * File :       d3dex1.c
 *
 * Abstract :   A very simple Direct3D example which simply draws a
 *              single, rotating, Gouraud shaded triangle in a fixed
 *              size window.
 *
 *              For code clarity a number of issues have not been
 *              addressed in this sample. For example, full screen
 *              operation, resizing the window, texture mapping are
 *              not included. Furthermore, certain optimizations have
 *              not been included where they would obfuscate the code.
 *              Every attempt has been made to highlight these areas
 *              will extensive comments.
 *
 * Author :     Colin D. C. McCartney
 *
 * Date :       09/04/96
 *
 * Version :    V1.0
 *
 * To do:
 * - Handle DDERR_WRONGMODE
 *
 * Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 ***********************************************************************/

/***********************************************************************
 *
 * Include files
 *
 ***********************************************************************/

#define  INITGUID
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <ddraw.h>
#include <d3d.h>

#include "nowarn.h"
#include "resource.h"

#ifdef DEBUG
// Make it possible to get reasonable symbols for poor debuggers
#define static
#endif

 /***********************************************************************
 *
 * Constants
 *
 ***********************************************************************/

/*
 * Class name for this application's window class.
 */
#define WINDOW_CLASSNAME      "D3DSample1Class"

/*
 * Title for the application's window.
 */
#define WINDOW_TITLE          "D3D Sample 1"

/*
 * String to be displayed when the application is paused.
 */
#define PAUSED_STRING         "Paused"

/*
 * Half height of the view window.
 */
#define HALF_HEIGHT           D3DVAL(0.5)

/*
 * Front and back clipping planes.
 */
#define FRONT_CLIP            D3DVAL(1.0)
#define BACK_CLIP             D3DVAL(1000.0)

/*
 * Fixed window size.
 */
#define WINDOW_WIDTH          320
#define WINDOW_HEIGHT         200

/*
 * Maximum length of the chosen device name and description of the
 * chosen Direct3D device.
 */
#define MAX_DEVICE_NAME       256
#define MAX_DEVICE_DESC       256

/*
 * Amount to rotate per frame.
 */
#define M_PI                  3.14159265359
#define M_2PI                 6.28318530718
#define ROTATE_ANGLE_DELTA    (M_2PI / 300.0)

/*
 * Execute buffer contents
 */
#define NUM_VERTICES          3UL
#define NUM_INSTRUCTIONS      6UL
#define NUM_STATES            8UL
#define NUM_PROCESSVERTICES   1UL
#define NUM_TRIANGLES         1UL

/***********************************************************************
 *
 * Macro funtions.
 *
 ***********************************************************************/

/*
 * Extract the error code from an HRESULT
 */
#define CODEFROMHRESULT(hRes) ((hRes) & 0x0000FFFFUL)

/***********************************************************************
 *
 * Global store
 *
 ***********************************************************************/

/*
 * Application instance handle (set in WinMain).
 */
static HINSTANCE               hAppInstance              = NULL;

/*
 * Running in debug mode?
 */
static BOOL                    fDebug                    = FALSE;

/*
 * Is the app. active?
 */
static BOOL                    fActive                   = TRUE;

/*
 * Has the app. been suspended?
 */
static BOOL                    fSuspended                = FALSE;

/*
 * DirectDraw interfaces
 */
static LPDIRECTDRAW            lpdd                      = NULL;
static LPDIRECTDRAWSURFACE     lpddPrimary               = NULL;
static LPDIRECTDRAWSURFACE     lpddDevice                = NULL;
static LPDIRECTDRAWSURFACE     lpddZBuffer               = NULL;
static LPDIRECTDRAWPALETTE     lpddPalette               = NULL;

/*
 * Direct3D interfaces
 */
static LPDIRECT3D              lpd3d                     = NULL;
static LPDIRECT3DDEVICE        lpd3dDevice               = NULL;
static LPDIRECT3DMATERIAL      lpd3dMaterial             = NULL;
static LPDIRECT3DMATERIAL      lpd3dBackgroundMaterial   = NULL;
static LPDIRECT3DVIEWPORT      lpd3dViewport             = NULL;
static LPDIRECT3DLIGHT         lpd3dLight                = NULL;
static LPDIRECT3DEXECUTEBUFFER lpd3dExecuteBuffer        = NULL;

/*
 * Direct3D handles
 */
static D3DMATRIXHANDLE         hd3dWorldMatrix           = 0UL;
static D3DMATRIXHANDLE         hd3dViewMatrix            = 0UL;
static D3DMATRIXHANDLE         hd3dProjMatrix            = 0UL;
static D3DMATERIALHANDLE       hd3dSurfaceMaterial       = 0UL;
static D3DMATERIALHANDLE       hd3dBackgroundMaterial    = 0UL;

/*
 * Globals used for selecting the Direct3D device. They are
 * globals as it makes it easy for the enumeration callback
 * to read and write from them.
 */
static BOOL                    fDeviceFound              = FALSE;
static DWORD                   dwDeviceBitDepth          = 0UL;
static GUID                    guidDevice;
static char                    szDeviceName[MAX_DEVICE_NAME];
static char                    szDeviceDesc[MAX_DEVICE_DESC];
static D3DDEVICEDESC           d3dHWDeviceDesc;
static D3DDEVICEDESC           d3dSWDeviceDesc;

/*
 * The screen coordinates of the client area of the window. This
 * rectangle defines the destination into which we blit to update
 * the client area of the window with the results of the 3D rendering.
 */
static RECT                    rDstRect;

/*
 * This rectangle defines the portion of the rendering target surface
 * into which we render. The top left coordinates of this rectangle
 * are always zero and the right and bottom give the size of the
 * viewport.
 */
static RECT                    rSrcRect;

/*
 * Angle of rotation of the world matrix.
 */
static double                  dAngleOfRotation          = 0.0;

/*
 * Predefined transformations.
 */
static D3DMATRIX d3dWorldMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dViewMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 5.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dProjMatrix =
{
    D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 1.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL(-1.0), D3DVAL( 0.0)
};

/***********************************************************************
 *
 * Function prototypes
 *
 ***********************************************************************/

static void           ReportError(HWND hwnd, int nMessage, HRESULT hRes);
static void           FatalError(HWND hwnd, int nMessage, HRESULT hRes);

static DWORD          BitDepthToFlags(DWORD dwBitDepth);
static DWORD          FlagsToBitDepth(DWORD dwFlags);

static void           SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
                                               double      dHalfHeight,
                                               double      dFrontClipping,
                                               double      dBackClipping);
static void           SetRotationAboutY(LPD3DMATRIX lpd3dMatrix,
                                        double      dAngleOfRotation);

static HRESULT        CreateDirect3D(HWND hwnd);
static HRESULT        ReleaseDirect3D(void);

static HRESULT        CreatePrimary(HWND hwnd);
static HRESULT        RestorePrimary(void);
static HRESULT        ReleasePrimary(void);

static HRESULT WINAPI EnumDeviceCallback(LPGUID          lpGUID,
                                         LPSTR           lpszDeviceDesc,
                                         LPSTR           lpszDeviceName,
                                         LPD3DDEVICEDESC lpd3dHWDeviceDesc,
                                         LPD3DDEVICEDESC lpd3dSWDeviceDesc,
                                         LPVOID          lpUserArg);
static HRESULT        ChooseDevice(void);

static HRESULT        CreateDevice(DWORD dwWidth, DWORD dwHeight);
static HRESULT        RestoreDevice(void);
static HRESULT        ReleaseDevice(void);

static LRESULT        RestoreSurfaces(void);

static HRESULT        FillExecuteBuffer(void);
static HRESULT        CreateScene(void);
static HRESULT        ReleaseScene(void);
static HRESULT        AnimateScene(void);

static HRESULT        UpdateViewport(void);

static HRESULT        RenderScene(void);
static HRESULT        DoFrame(HWND hwnd);
static void           PaintSuspended(HWND hwnd, HDC hdc);

static LRESULT        OnMove(HWND hwnd, int x, int y);
static LRESULT        OnSize(HWND hwnd, int w, int h);
static LRESULT        OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps);
static LRESULT        OnIdle(HWND hwnd);

LRESULT CALLBACK      WndProc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam);
int PASCAL            WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                              LPSTR lpszCommandLine, int cmdShow);

/***********************************************************************
 *
 * Macro functions
 *
 ***********************************************************************/

/***********************************************************************/

#ifdef _DEBUG
#define ASSERT(x)       assert(x)
#else
#define ASSERT(x)
#endif

/***********************************************************************/

/*
 * Used to keep the compiler happy about any unused parameters.
 */
#define USE_PARAM(x)    (x) = (x)

/***********************************************************************/

/***********************************************************************
 *
 * Functions
 *
 ***********************************************************************/

/***********************************************************************/

/*
 * Report the given error by display a message box.
 */
static void
ReportError(HWND hwnd, int nMessage, HRESULT hRes)
{
    HDC  hdc;
    char szBuffer[256];
    char szMessage[128];
    char szError[128];
    int  nStrID;

    /*
     * Turn the animation loop off.
     */
    fSuspended = TRUE;

    /*
     * Get the high level error message.
     */
    LoadString(hAppInstance, nMessage, szMessage, sizeof(szMessage));

    /*
     * We issue sensible error messages for common run time errors. For
     * errors which are internal or coding errors we simply issue an
     * error number (they should never occur).
     */
    switch (hRes)
    {
        case DDERR_EXCEPTION:        nStrID = IDS_ERR_EXCEPTION;        break;
        case DDERR_GENERIC:          nStrID = IDS_ERR_GENERIC;          break;
        case DDERR_OUTOFMEMORY:      nStrID = IDS_ERR_OUTOFMEMORY;      break;
        case DDERR_OUTOFVIDEOMEMORY: nStrID = IDS_ERR_OUTOFVIDEOMEMORY; break;
        case DDERR_SURFACEBUSY:      nStrID = IDS_ERR_SURFACEBUSY;      break;
        case DDERR_SURFACELOST:      nStrID = IDS_ERR_SURFACELOST;      break;
        case DDERR_WRONGMODE:        nStrID = IDS_ERR_WRONGMODE;        break;
        default:                     nStrID = IDS_ERR_INTERNALERROR;    break;
    }
    LoadString(hAppInstance, nStrID, szError, sizeof(szError));

    /*
     * Show the "paused" display.
     */
    hdc = GetDC(hwnd);
    PaintSuspended(hwnd, hdc);
    ReleaseDC(hwnd, hdc);

    /*
     * Convert the error code into a string (not very informative but
     * it keeps the code simple).
     */
    wsprintf(szBuffer, "%s\n%s (Error #%d)", szMessage, szError, CODEFROMHRESULT(hRes));
    MessageBox(hwnd, szBuffer, WINDOW_TITLE, MB_OK | MB_APPLMODAL);
    fSuspended = FALSE;
}

/***********************************************************************/

/*
 * Handle a fatal error. Displays the error message via a message box
 * and then destroys the window.
 */
static void
FatalError(HWND hwnd, int nMessage, HRESULT hRes)
{
    /*
     * Report the error.
     */
    ReportError(hwnd, nMessage, hRes);
    fSuspended = TRUE;

    /*
     * And shut down.
     *
     * NOTE: We don't attempt to clean up. That will be done
     * when WM_DESTROY happens.
     */
    DestroyWindow(hwnd);
}

/***********************************************************************/

/*
 * Converts a bit depth into the appropriate DirectDraw bit depth flag.
 */
static DWORD
BitDepthToFlags(DWORD dwBitDepth)
{
    switch (dwBitDepth)
    {
        case  1UL: return DDBD_1;
        case  2UL: return DDBD_2;
        case  4UL: return DDBD_4;
        case  8UL: return DDBD_8;
        case 16UL: return DDBD_16;
        case 24UL: return DDBD_24;
        case 32UL: return DDBD_32;
        default:   return 0UL;     /* Oh, please... */
    }
}

/***********************************************************************/

/*
 * Convert bit depth flags to an acutal bit count. Selects the smallest
 * bit count in the mask if more than one flag is present.
 */
static DWORD
FlagsToBitDepth(DWORD dwFlags)
{
    if (dwFlags & DDBD_1)
        return 1UL;
    else if (dwFlags & DDBD_2)
        return 2UL;
    else if (dwFlags & DDBD_4)
        return 4UL;
    else if (dwFlags & DDBD_8)
        return 8UL;
    else if (dwFlags & DDBD_16)
        return 16UL;
    else if (dwFlags & DDBD_24)
        return 24UL;
    else if (dwFlags & DDBD_32)
        return 32UL;
    else
        return 0UL; /* Oh, please... */
}

/***********************************************************************/

/*
 * Set the given matrix to a perspective transform for the given half
 * height and front and back clipping planes.
 */
static void
SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
                         double      dHalfHeight,
                         double      dFrontClipping,
                         double      dBackClipping)
{
    double dTmp1;
    double dTmp2;

    ASSERT(NULL != lpd3dMatrix);

    dTmp1 = dHalfHeight / dFrontClipping;
    dTmp2 = dBackClipping / (dBackClipping - dFrontClipping);

    lpd3dMatrix->_11 =  D3DVAL(2.0);
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 =  D3DVAL(0.0);
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(2.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  D3DVAL(0.0);
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  D3DVAL(dTmp1 * dTmp2);
    lpd3dMatrix->_34 =  D3DVAL(dTmp1);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(-dHalfHeight * dTmp2);
    lpd3dMatrix->_44 =  D3DVAL(0.0);
}

/***********************************************************************/

/*
 * Set the given matrix to a rotation about Y transform of the given
 * number of radians.
 */
static void
SetRotationAboutY(LPD3DMATRIX lpd3dMatrix, double dAngleOfRotation)
{
    D3DVALUE dvCos;
    D3DVALUE dvSin;

    ASSERT(NULL != lpd3dMatrix);

    dvCos = D3DVAL(cos(dAngleOfRotation));
    dvSin = D3DVAL(sin(dAngleOfRotation));

    lpd3dMatrix->_11 =  dvCos;
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 = -dvSin;
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(1.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  dvSin;
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  dvCos;
    lpd3dMatrix->_34 =  D3DVAL(0.0);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(0.0);
    lpd3dMatrix->_44 =  D3DVAL(1.0);
}

/***********************************************************************/

/*
 * Create the DirectDraw/3D driver object and get DirectDraw and Direct3D
 * interfaces for communicating with that object.
 */
static HRESULT
CreateDirect3D(HWND hwnd)
{
    HRESULT hRes;

    ASSERT(NULL == lpdd);
    ASSERT(NULL == lpd3d);

    /*
     * Create the DirectDraw/3D driver object and get the DirectDraw
     * interface to that object.
     */
    hRes = DirectDrawCreate(NULL, &lpdd, NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * As we are running in a window set the cooperative level to
     * normal. Also, to ensure that the palette is realized correctly
     * we need to pass the hwnd of the main window.
     */
    hRes = lpdd->lpVtbl->SetCooperativeLevel(lpdd, hwnd, DDSCL_NORMAL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Get the Direct3D interface to the DirectDraw/3D driver object.
     */
    hRes = lpdd->lpVtbl->QueryInterface(lpdd, &IID_IDirect3D, &lpd3d);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Release the DirectDraw/3D driver object.
 */
static HRESULT
ReleaseDirect3D(void)
{
    if (NULL != lpd3d)
    {
        lpd3d->lpVtbl->Release(lpd3d);
        lpd3d = NULL;
    }
    if (NULL != lpdd)
    {
        lpdd->lpVtbl->Release(lpdd);
        lpdd = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Create the primary surface (representing the desktop) and create and
 * attach a clipper and, if necessary, a palette.
 */
static HRESULT
CreatePrimary(HWND hwnd)
{
    HRESULT             hRes;
    DDSURFACEDESC       ddsd;
    LPDIRECTDRAWCLIPPER lpddClipper;
    HDC                 hdc;
    int                 i;
    PALETTEENTRY        peColorTable[256];

    ASSERT(NULL != hwnd);
    ASSERT(NULL != lpdd);
    ASSERT(NULL == lpddPrimary);
    ASSERT(NULL == lpddPalette);

    /*
     * Create the primary surface.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddPrimary, NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the clipper. We bind the application's window to the
     * clipper and attach it to the primary. This ensures then when we
     * blit from the rendering surface to the primary we don't write
     * outside the visible region of the window.
     */
    hRes = DirectDrawCreateClipper(0UL, &lpddClipper, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpddClipper->lpVtbl->SetHWnd(lpddClipper, 0UL, hwnd);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }
    hRes = lpddPrimary->lpVtbl->SetClipper(lpddPrimary, lpddClipper);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }

    /*
     * We release the clipper interface after attaching it to the surface
     * as we don't need to use it again. The surface holds a reference to
     * the clipper when its been attached. The clipper will therefore be
     * released when the surface is released.
     */
    lpddClipper->lpVtbl->Release(lpddClipper);

    /*
     * If the primary is palettized then so will the device (the device
     * surface must have the same pixel format as the current primary if
     * we want to double buffer with DirectDraw). Hence, if the primary
     * is palettized we need to create a palette and attach it to the
     * primary (and to the device surface when we create it).
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;
    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
    {
        /*
         * Initializing the palette correctly is essential. We are
         * running in a window so we need to be a good windows app
         * and not mess with the top ten and bottom ten static
         * colors. Therefore, we copy them from the system palette
         * and mark them as read only (D3DPAL_READONLY). The middle
         * 236 entries are free for use by Direct3D so we mark them
         * free (D3DPAL_FREE).
         *
         * NOTE: In order that the palette entries are correctly
         * allocated it is essential that the free entries are
         * also marked reserved to GDI (PC_RESERVED).
         *
         * NOTE: We don't need to specify the palette caps flag
         * DDPCAPS_INITIALIZE. This flag is obsolete. CreatePalette
         * must be given a valid palette entry array and always
         * initializes from it.
         */
        hdc = GetDC(NULL);
        GetSystemPaletteEntries(hdc, 0, 256, peColorTable);
        ReleaseDC(NULL, hdc);

        for (i = 0; i < 10; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        for (i = 10; i < 246; i++)
            peColorTable[i].peFlags = D3DPAL_FREE | PC_RESERVED;
        for (i = 246; i < 256; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        hRes = lpdd->lpVtbl->CreatePalette(lpdd,
                                           DDPCAPS_8BIT,
                                           peColorTable,
                                           &lpddPalette,
                                           NULL);
        if (FAILED(hRes))
            return hRes;

        hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary, lpddPalette);
            return hRes;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Attempt to restore the video memory allocated for the primary. This
 * function will be invoked by a DirectX function returning
 * DDERR_SURFACELOST due to a mode switch or fullscreen DOS box
 * invalidating video memory.
 */
static HRESULT
RestorePrimary(void)
{
    ASSERT(NULL != lpddPrimary);

    return lpddPrimary->lpVtbl->Restore(lpddPrimary);
}

/***********************************************************************/

/*
 * Release the primary surface and its attached clipper and palette.
 */
static HRESULT
ReleasePrimary(void)
{
    if (NULL != lpddPalette)
    {
        lpddPalette->lpVtbl->Release(lpddPalette);
        lpddPalette = NULL;
    }
    if (NULL != lpddPrimary)
    {
        lpddPrimary->lpVtbl->Release(lpddPrimary);
        lpddPrimary = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * This callback is invoked for each Direct3D device installed on the
 * system. For each device we get its identifying GUID, a name and
 * description, a description of its hardware and software capabilities
 * and a user argument (which we don't use).
 */
static HRESULT WINAPI
EnumDeviceCallback(LPGUID          lpGUID,
                   LPSTR           lpszDeviceDesc,
                   LPSTR           lpszDeviceName,
                   LPD3DDEVICEDESC lpd3dHWDeviceDesc,
                   LPD3DDEVICEDESC lpd3dSWDeviceDesc,
                   LPVOID          lpUserArg)
{
    BOOL            fIsHardware;
    LPD3DDEVICEDESC lpd3dDeviceDesc;

    /*
     * We don't use the user argument so just keep the compiler happy.
     */
    USE_PARAM(lpUserArg);

    /*
     * If there is no hardware support then the color model is zero.
     */
    fIsHardware     = (0UL != lpd3dHWDeviceDesc->dcmColorModel);
    lpd3dDeviceDesc = (fIsHardware ? lpd3dHWDeviceDesc : lpd3dSWDeviceDesc);

    /*
     * If we are in debug mode and this is a hardware device skip it.
     */
    if (fDebug && fIsHardware)
        return D3DENUMRET_OK;

    /*
     * Does the device render at the depth we want?
     */
    if (0UL == (lpd3dDeviceDesc->dwDeviceRenderBitDepth & dwDeviceBitDepth))
    {
        /*
         * No skip this device.
         */
        return D3DENUMRET_OK;
    }

    /*
     * The device must support gouraud shaded triangles.
     */
    if (D3DCOLOR_MONO == lpd3dDeviceDesc->dcmColorModel)
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORGOURAUDMONO))
        {
            /*
             * No gouraud shading. Skip this device.
             */
            return D3DENUMRET_OK;
        }
    }
    else
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORGOURAUDRGB))
        {
            /*
             * No gouraud shading. Skip this device.
             */
            return D3DENUMRET_OK;
        }
    }

    if (!fIsHardware && fDeviceFound && (D3DCOLOR_RGB == lpd3dDeviceDesc->dcmColorModel))
    {
        /*
         * If this is software RGB and we already have found a software
         * mono already then we are not interested. Skip it.
         */
        return D3DENUMRET_OK;
    }

    /*
     * This is a device we are interested in - cache the details away.
     */
    fDeviceFound = TRUE;
    CopyMemory(&guidDevice, lpGUID, sizeof(GUID));
    strcpy(szDeviceDesc, lpszDeviceDesc);
    strcpy(szDeviceName, lpszDeviceName);
    CopyMemory(&d3dHWDeviceDesc, lpd3dHWDeviceDesc, sizeof(D3DDEVICEDESC));
    CopyMemory(&d3dSWDeviceDesc, lpd3dSWDeviceDesc, sizeof(D3DDEVICEDESC));

    /*
     * If this is a hardware device we have found what we are looking
     * for.
     */
    if (fIsHardware)
        return D3DENUMRET_CANCEL;

    /*
     * Keep looking...
     */
    return D3DENUMRET_OK;
}

/***********************************************************************/

/*
 * Choose an appropriate Direct3D using the following mechanism:
 *
 * 1) Discard any devices which don't match the current display depth.
 * 2) Discard any devices which can't do gouraud shaded triangles.
 * 3) If a hardware device is found which matches 1) and 2) use it.
 *    However, if we are running in debug mode we will skip hardware.
 * 4) Otherwise favour Mono/Ramp mode software renderers over RGB ones
 *    as, at least until MMX is widespread, Mono will be faster.
 *
 * The actual implementation of this mechanism is in the callback
 * function above.
 */
static HRESULT
ChooseDevice(void)
{
    DDSURFACEDESC ddsd;
    HRESULT       hRes;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);

    /*
     * As we are running in a window we will not be changing the screen
     * depth and hence the pixel format of the rendering target must match
     * the pixel format of the current primary. Therefore, we need to
     * determine the pixel format of the primary.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;

    dwDeviceBitDepth = BitDepthToFlags(ddsd.ddpfPixelFormat.dwRGBBitCount);

    /*
     * Enumerate the devices and pick one.
     */
    fDeviceFound = FALSE;
    hRes = lpd3d->lpVtbl->EnumDevices(lpd3d, EnumDeviceCallback, &fDeviceFound);
    if (FAILED(hRes))
        return hRes;

    if (!fDeviceFound)
    {
        /*
         * No suitable device was found. We have no alternative but to
         * fail creation entirely.
         */
        return DDERR_NOTFOUND;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Create an instance of the Direct3D device we choose earlier with the
 * given width and height.
 *
 * This function handles all aspects of the device creation including
 * choosing surface memory type, create the device surface, the z-buffer
 * (if necessary) and attaching the palette (if required).
 */
static HRESULT
CreateDevice(DWORD dwWidth, DWORD dwHeight)
{
    LPD3DDEVICEDESC lpd3dDeviceDesc;
    DWORD           dwDeviceMemType;
    DWORD           dwZBufferMemType;
    DDSURFACEDESC   ddsd;
    HRESULT         hRes;
    DWORD           dwZBufferBitDepth;

    ASSERT(NULL != lpdd);
    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);
    ASSERT(NULL == lpddDevice);

ASSERT(NULL == lpd3dDevice);

    /*
     * The first step is to determine the kind of memory (system or
     * video) from which the device surface should be allocated.
     */
    if (0UL != d3dHWDeviceDesc.dcmColorModel)
    {
        lpd3dDeviceDesc = &d3dHWDeviceDesc;

        /*
         * Device has a hardware rasterizer. Currently this means that
         * the device surface must be in video memory.
         */
        dwDeviceMemType  = DDSCAPS_VIDEOMEMORY;
        dwZBufferMemType = DDSCAPS_VIDEOMEMORY;
    }
    else
    {
        lpd3dDeviceDesc = &d3dSWDeviceDesc;

        /*
         * Device has a software rasterizer. We will let DirectDraw
         * decide where the device surface resides unless we are
         * running in debug mode in which case we will force it into
         * system memory. For a software rasterizer the z-buffer should
         * always go into system memory. A z-buffer in video memory will
         * kill performance.
         */
        dwDeviceMemType  = (fDebug ? DDSCAPS_SYSTEMMEMORY : 0UL);
        dwZBufferMemType = DDSCAPS_SYSTEMMEMORY;
    }

    /*
     * Create the device surface. The pixel format will be identical
     * to the primary so we don't have to explicitly specify it. We do
     * need to explicity specify the size, memory type and capabilities
     * of the surface.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.dwWidth        = dwWidth;
    ddsd.dwHeight       = dwHeight;
    ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_OFFSCREENPLAIN | dwDeviceMemType;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddDevice, NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * If we have created a palette then we have already determined that
     * the primary (and hence the device surface) is palettized so
     * attach the palette to the device surface (its already attached to
     * the primary).
     */
    if (NULL != lpddPalette)
    {
        hRes = lpddDevice->lpVtbl->SetPalette(lpddDevice, lpddPalette);
        if (FAILED(hRes))
            return hRes;
    }

    /*
     * We now determine whether we need a z-buffer or not and if so
     * its bit depth.
     */
    if (0UL != lpd3dDeviceDesc->dwDeviceZBufferBitDepth)
    {
        /*
         * The device supports z-buffering. Determine the depth. We
         * select the lowest supported z-buffer depth to save memory.
         * Accuracy is not too important for this sample.
         */
        dwZBufferBitDepth = FlagsToBitDepth(lpd3dDeviceDesc->dwDeviceZBufferBitDepth);

        /*
         * Create the z-buffer.
         */
        ZeroMemory(&ddsd, sizeof(ddsd));
        ddsd.dwSize            = sizeof(ddsd);
        ddsd.dwFlags           = DDSD_CAPS   |
                                 DDSD_WIDTH  |
                                 DDSD_HEIGHT |
                                 DDSD_ZBUFFERBITDEPTH;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_ZBUFFER | dwZBufferMemType;
        ddsd.dwWidth           = dwWidth;
        ddsd.dwHeight          = dwHeight;
        ddsd.dwZBufferBitDepth = dwZBufferBitDepth;
        hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddZBuffer, NULL);
        if (FAILED(hRes))
            return hRes;

        /*
         * Attach it to the rendering target.
         */
        hRes = lpddDevice->lpVtbl->AddAttachedSurface(lpddDevice, lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    /*
     * Now all the elements are in place (device surface in correct
     * memory type, attached z-buffer of correct depth and memory
     * type, and palette if necessary) we can actually query for the
     * Direct3D we choose earlier.
     */
    hRes = lpddDevice->lpVtbl->QueryInterface(lpddDevice,
                                              &guidDevice,
                                              &lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Restore the video memory for the device surface and z-buffer if it
 * has been lost.
 */
static HRESULT
RestoreDevice(void)
{
    HRESULT hRes;

    if (NULL != lpddZBuffer)
    {
        hRes = lpddZBuffer->lpVtbl->Restore(lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    if (NULL != lpddDevice)
    {
        hRes = lpddDevice->lpVtbl->Restore(lpddDevice);
        if (FAILED(hRes))
            return hRes;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Release the Direct3D device and its associated surfaces.
 */
static HRESULT
ReleaseDevice(void)
{
    if (NULL != lpd3dDevice)
    {
        lpd3dDevice->lpVtbl->Release(lpd3dDevice);
        lpd3dDevice = NULL;
    }
    if (NULL != lpddZBuffer)
    {
        lpddZBuffer->lpVtbl->Release(lpddZBuffer);
        lpddZBuffer = NULL;
    }
    if (NULL != lpddDevice)
    {
        lpddDevice->lpVtbl->Release(lpddDevice);
        lpddDevice = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Attempt to restore all the surfaces used by the application.
 */
static LRESULT
RestoreSurfaces(void)
{
    HRESULT hRes;

    hRes = RestorePrimary();
    if (FAILED(hRes))
        return hRes;

    hRes = RestoreDevice();
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Fill the single execute buffer used in this sample with all the
 * vertices, transform, light and render state and drawing primitives
 * necessary to draw our triangle.
 *
 * NOTE: This is not the most efficient way of organizing the execute
 * buffer. For best performance you want to minimize state changes. In
 * this sample we submit the execute buffer for each frame in the
 * animation loop and no state in the buffer is modified. The only
 * thing we modify is the world matrix (its contents - not its handle).
 * Therefore, it would be more efficient to extract all the static
 * state instructions into a separate execute buffer which we issue
 * once only at startup and, from then on, simply execute a second
 * execute buffer with vertices and triangles.
 * However, this sample is not exactly performance critical so we will
 * just use one execute buffer and resubmit it its entirety for each
 * frame.
 */
static HRESULT
FillExecuteBuffer(void)
{
    HRESULT              hRes;
    D3DEXECUTEBUFFERDESC d3dExeBufDesc;
    LPD3DVERTEX          lpVertex;
    LPD3DINSTRUCTION     lpInstruction;
    LPD3DPROCESSVERTICES lpProcessVertices;
    LPD3DTRIANGLE        lpTriangle;
    LPD3DSTATE           lpState;

    ASSERT(NULL != lpd3dExecuteBuffer);
    ASSERT(0UL  != hd3dSurfaceMaterial);
    ASSERT(0UL  != hd3dWorldMatrix);
    ASSERT(0UL  != hd3dViewMatrix);
    ASSERT(0UL  != hd3dProjMatrix);

    /*
     * Lock the execute buffer.
     */
    ZeroMemory(&d3dExeBufDesc, sizeof(d3dExeBufDesc));
    d3dExeBufDesc.dwSize = sizeof(d3dExeBufDesc);
    hRes = lpd3dExecuteBuffer->lpVtbl->Lock(lpd3dExecuteBuffer, &d3dExeBufDesc);
    if (FAILED(hRes))
        return hRes;

    /*
     * For explanatory purposes we fill the execute buffer by casting
     * a pointer to the execute buffer to the appropriate data structures.
     *
     * !!! NOTE: Issue - alignment.
     */
    lpVertex = (LPD3DVERTEX)d3dExeBufDesc.lpData;

    /*
     * First vertex.
     */
    lpVertex->dvX  = D3DVAL( 0.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL( 1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 0.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    /*
     * Second vertex.
     */
    lpVertex->dvX  = D3DVAL( 1.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    /*
     * Third vertex.
     */
    lpVertex->dvX  = D3DVAL(-1.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 0.0);
    lpVertex++;

    /*
     * Transform state - world, view and projection.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpVertex;
    lpInstruction->bOpcode = D3DOP_STATETRANSFORM;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_WORLD;
    lpState->dwArg[0] = hd3dWorldMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_VIEW;
    lpState->dwArg[0] = hd3dViewMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_PROJECTION;
    lpState->dwArg[0] = hd3dProjMatrix;
    lpState++;

    /*
     * Lighting state.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATELIGHT;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 2U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dlstLightStateType = D3DLIGHTSTATE_MATERIAL;
    lpState->dwArg[0] = hd3dSurfaceMaterial;
    lpState++;
    lpState->dlstLightStateType = D3DLIGHTSTATE_AMBIENT;
    lpState->dwArg[0] = RGBA_MAKE(128, 128, 128, 128);
    lpState++;

    /*
     * Render state.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATERENDER;
    lpInstruction->bSize = sizeof(D3DSTATE);
    lpInstruction->wCount = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->drstRenderStateType = D3DRENDERSTATE_FILLMODE;
    lpState->dwArg[0] = D3DFILL_SOLID;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_SHADEMODE;
    lpState->dwArg[0] = D3DSHADE_GOURAUD;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_DITHERENABLE;
    lpState->dwArg[0] = TRUE;
    lpState++;

    /*
     * The process vertices instruction tells the driver what to
     * do with the vertices in the buffer. In this sample we want
     * Direct3D to perform the entire pipeline on our behalf so
     * the instruction is D3DPROCESSVERTICES_TRANSFORMLIGHT.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_PROCESSVERTICES;
    lpInstruction->bSize   = sizeof(D3DPROCESSVERTICES);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpProcessVertices = (LPD3DPROCESSVERTICES)lpInstruction;
    lpProcessVertices->dwFlags    = D3DPROCESSVERTICES_TRANSFORMLIGHT;
    lpProcessVertices->wStart     = 0U;           /* First source vertex */
    lpProcessVertices->wDest      = 0U;
    lpProcessVertices->dwCount    = NUM_VERTICES; /* Number of vertices  */
    lpProcessVertices->dwReserved = 0UL;
    lpProcessVertices++;

    /*
     * Draw the triangle.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpProcessVertices;
    lpInstruction->bOpcode = D3DOP_TRIANGLE;
    lpInstruction->bSize   = sizeof(D3DTRIANGLE);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpTriangle = (LPD3DTRIANGLE)lpInstruction;
    lpTriangle->wV1    = 0U;
    lpTriangle->wV2    = 1U;
    lpTriangle->wV3    = 2U;
    lpTriangle->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
    lpTriangle++;

    /*
     * Stop execution of the buffer.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpTriangle;
    lpInstruction->bOpcode = D3DOP_EXIT;
    lpInstruction->bSize   = 0UL;
    lpInstruction->wCount  = 0U;

    /*
     * Unlock the execute buffer.
     */
    lpd3dExecuteBuffer->lpVtbl->Unlock(lpd3dExecuteBuffer);

    return DD_OK;
}

/***********************************************************************/

/*
 * Create the elements making up the 3D scene.
 *
 * In this sample the scene consists of the single light, the viewport,
 * the background and surface material, the three transformation matrices
 * and the execute buffer holding the state changes and drawing primitives.
 */
static HRESULT
CreateScene(void)
{
    HRESULT              hRes;
    D3DMATERIAL          d3dMaterial;
    D3DLIGHT             d3dLight;
    DWORD                dwVertexSize;
    DWORD                dwInstructionSize;
    DWORD                dwExecuteBufferSize;
    D3DEXECUTEBUFFERDESC d3dExecuteBufferDesc;
    D3DEXECUTEDATA       d3dExecuteData;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL == lpd3dViewport);
    ASSERT(NULL == lpd3dMaterial);
    ASSERT(NULL == lpd3dBackgroundMaterial);
    ASSERT(NULL == lpd3dExecuteBuffer);
    ASSERT(NULL == lpd3dLight);
    ASSERT(0UL  == hd3dWorldMatrix);
    ASSERT(0UL  == hd3dViewMatrix);
    ASSERT(0UL  == hd3dProjMatrix);

    /*
     * Create the light.
     */
    hRes = lpd3d->lpVtbl->CreateLight(lpd3d, &lpd3dLight, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dLight, sizeof(d3dLight));
    d3dLight.dwSize = sizeof(d3dLight);
    d3dLight.dltType = D3DLIGHT_POINT;
    d3dLight.dcvColor.dvR    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvG    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvB    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvA    = D3DVAL( 1.0);
    d3dLight.dvPosition.dvX  = D3DVAL( 1.0);
    d3dLight.dvPosition.dvY  = D3DVAL(-1.0);
    d3dLight.dvPosition.dvZ  = D3DVAL(-1.0);
    d3dLight.dvAttenuation0  = D3DVAL( 1.0);
    d3dLight.dvAttenuation1  = D3DVAL( 0.1);
    d3dLight.dvAttenuation2  = D3DVAL( 0.0);
    hRes = lpd3dLight->lpVtbl->SetLight(lpd3dLight, &d3dLight);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the background material.
     */
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, &lpd3dBackgroundMaterial, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(0.0);
    d3dMaterial.dvPower       = D3DVAL(0.0);

    /*
     * As this is the background material we don't want a ramp allocated (we
     * are not going to be smooth shading the background).
     */
    d3dMaterial.dwRampSize    = 1UL;

    hRes = lpd3dBackgroundMaterial->lpVtbl->SetMaterial(lpd3dBackgroundMaterial,
                                                        &d3dMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dBackgroundMaterial->lpVtbl->GetHandle(lpd3dBackgroundMaterial,
                                                      lpd3dDevice,
                                                      &hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the viewport.
     *
     * The actual viewport parameter are set in the function UpdateViewport
     * which is called in response to WM_SIZE.
     */
    hRes = lpd3d->lpVtbl->CreateViewport(lpd3d, &lpd3dViewport, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->AddViewport(lpd3dDevice, lpd3dViewport);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->SetBackground(lpd3dViewport, hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->AddLight(lpd3dViewport, lpd3dLight);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the matrices.
     */
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dWorldMatrix, &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dViewMatrix, &d3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dProjMatrix);
    if (FAILED(hRes))
        return hRes;
    SetPerspectiveProjection(&d3dProjMatrix, HALF_HEIGHT, FRONT_CLIP, BACK_CLIP);
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dProjMatrix, &d3dProjMatrix);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the surface material.
     */
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, &lpd3dMaterial, NULL);
    if (FAILED(hRes))
        return hRes;
    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);

    /*
     * Base green with white specular.
     */
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(1.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.4);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(1.0);
    d3dMaterial.dvPower       = D3DVAL(20.0);
    d3dMaterial.dwRampSize    = 16UL;

    hRes = lpd3dMaterial->lpVtbl->SetMaterial(lpd3dMaterial, &d3dMaterial);
    if (FAILED(hRes))
        return hRes;

    hRes = lpd3dMaterial->lpVtbl->GetHandle(lpd3dMaterial, lpd3dDevice, &hd3dSurfaceMaterial);
    if (FAILED(hRes))
        return hRes;

    /*
     * Build the execute buffer.
     */
    dwVertexSize        = (NUM_VERTICES        * sizeof(D3DVERTEX));
    dwInstructionSize   = (NUM_INSTRUCTIONS    * sizeof(D3DINSTRUCTION))     +
                          (NUM_STATES          * sizeof(D3DSTATE))           +
                          (NUM_PROCESSVERTICES * sizeof(D3DPROCESSVERTICES)) +
                          (NUM_TRIANGLES       * sizeof(D3DTRIANGLE));
    dwExecuteBufferSize = dwVertexSize + dwInstructionSize;
    ZeroMemory(&d3dExecuteBufferDesc, sizeof(d3dExecuteBufferDesc));
    d3dExecuteBufferDesc.dwSize       = sizeof(d3dExecuteBufferDesc);
    d3dExecuteBufferDesc.dwFlags      = D3DDEB_BUFSIZE;
    d3dExecuteBufferDesc.dwBufferSize = dwExecuteBufferSize;
    hRes = lpd3dDevice->lpVtbl->CreateExecuteBuffer(lpd3dDevice,
                                                    &d3dExecuteBufferDesc,
                                                    &lpd3dExecuteBuffer,
                                                    NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Fill the execute buffer with the required vertices, state
     * instructions and drawing primitives.
     */
    hRes = FillExecuteBuffer();
    if (FAILED(hRes))
        return hRes;

    /*
     * Set the execute data so Direct3D knows how many vertices are in the
     * buffer and where the instructions start.
     */
    ZeroMemory(&d3dExecuteData, sizeof(d3dExecuteData));
    d3dExecuteData.dwSize = sizeof(d3dExecuteData);
    d3dExecuteData.dwVertexCount       = NUM_VERTICES;
    d3dExecuteData.dwInstructionOffset = dwVertexSize;
    d3dExecuteData.dwInstructionLength = dwInstructionSize;
    hRes = lpd3dExecuteBuffer->lpVtbl->SetExecuteData(lpd3dExecuteBuffer, &d3dExecuteData);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Release all the objects comprising the 3D scene.
 */
static HRESULT
ReleaseScene(void)
{
    if (NULL != lpd3dExecuteBuffer)
    {
        lpd3dExecuteBuffer->lpVtbl->Release(lpd3dExecuteBuffer);
        lpd3dExecuteBuffer = NULL;
    }
    if (NULL != lpd3dBackgroundMaterial)
    {
        lpd3dBackgroundMaterial->lpVtbl->Release(lpd3dBackgroundMaterial);
        lpd3dBackgroundMaterial = NULL;
    }
    if (NULL != lpd3dMaterial)
    {
        lpd3dMaterial->lpVtbl->Release(lpd3dMaterial);
        lpd3dMaterial = NULL;
    }
    if (0UL != hd3dWorldMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dWorldMatrix);
        hd3dWorldMatrix = 0UL;
    }
    if (0UL != hd3dViewMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dViewMatrix);
        hd3dViewMatrix = 0UL;
    }
    if (0UL != hd3dProjMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dProjMatrix);
        hd3dProjMatrix = 0UL;
    }
    if (NULL != lpd3dLight)
    {
        lpd3dLight->lpVtbl->Release(lpd3dLight);
        lpd3dLight = NULL;
    }
    if (NULL != lpd3dViewport)
    {
        lpd3dViewport->lpVtbl->Release(lpd3dViewport);
        lpd3dViewport = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Animate the scene.
 *
 * The animation in this sample is simply a rotation about the Y axis.
 * So all we need to do is build a rotation matrix and set the world
 * matrix to that new rotation matrix.
 *
 * Note, we don't need to modify the execute buffer in any way to peform
 * this rotation. We simply set the matrix and resubmit the execute
 * buffer.
 */
static HRESULT
AnimateScene(void)
{
    HRESULT hRes;

    ASSERT(NULL != lpd3dDevice);
    ASSERT(0UL  != hd3dWorldMatrix);

    /*
     * We rotate the triangle by setting thr world transform to a
     * rotation matrix.
     */
    SetRotationAboutY(&d3dWorldMatrix, dAngleOfRotation);
    dAngleOfRotation += ROTATE_ANGLE_DELTA;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice,
                                          hd3dWorldMatrix,
                                          &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Update the viewport in response to a change in window size. This
 * ensures that we render at a resolution which matches the client
 * area of the target window.
 */
static HRESULT
UpdateViewport(void)
{
    D3DVIEWPORT d3dViewport;

    ASSERT(NULL != lpd3dViewport);

    ZeroMemory(&d3dViewport, sizeof(d3dViewport));
    d3dViewport.dwSize   = sizeof(d3dViewport);
    d3dViewport.dwX      = 0UL;
    d3dViewport.dwY      = 0UL;
    d3dViewport.dwWidth  = (DWORD)rSrcRect.right;
    d3dViewport.dwHeight = (DWORD)rSrcRect.bottom;
    d3dViewport.dvScaleX = D3DVAL((float)d3dViewport.dwWidth / 2.0);
    d3dViewport.dvScaleY = D3DVAL((float)d3dViewport.dwHeight / 2.0);
    d3dViewport.dvMaxX   = D3DVAL(1.0);
    d3dViewport.dvMaxY   = D3DVAL(1.0);
    return lpd3dViewport->lpVtbl->SetViewport(lpd3dViewport, &d3dViewport);
}

/***********************************************************************/

/*
 * Render the 3D scene.
 *
 * Fundamentally this involved submitting our single execute buffer.
 * However, we also need to clear the back and z-buffers and demark
 * the start and end of the scene (which in this case is a single
 * execute).
 */
static HRESULT
RenderScene(void)
{
    HRESULT hRes;
    D3DRECT d3dRect;

    ASSERT(NULL != lpd3dViewport);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL != lpd3dExecuteBuffer);

    /*
     * Clear both back and z-buffer.
     *
     * NOTE: Its safe to specify the z-buffer clear flag even if we
     * don't have an attached z-buffer. Direct3D will simply discard
     * the flag if no z-buffer is being used.
     *
     * NOTE: For maximum efficiency we only want to clear those
     * regions of the device surface and z-buffer which we actually
     * rendered to in the last frame. This is the purpose of the
     * array of rectangles and count passed to this function. It is
     * possible to query Direct3D for the regions of the device
     * surface that were rendered to by that execute. The application
     * can then accumulate those rectangles and clear only those
     * regions. However this is a very simple sample and so, for
     * simplicity, we will just clear the entire device surface and
     * z-buffer. Probably not something you want to do in a real
     * application.
     */
    d3dRect.lX1 = rSrcRect.left;
    d3dRect.lX2 = rSrcRect.right;
    d3dRect.lY1 = rSrcRect.top;
    d3dRect.lY2 = rSrcRect.bottom;
    hRes = lpd3dViewport->lpVtbl->Clear(lpd3dViewport,
                                        1UL,
                                        &d3dRect,
                                        D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
    if (FAILED(hRes))
        return hRes;

    /*
     * Start the scene.
     *
     * This function must be called once and once only for every frame
     * of animation. If you have multiple execute buffers comprising a
     * single frame you must have one call to BeginScene() before
     * submitting those execute buffers.
     *
     * NOTE: If you have more than one device being rendered in a
     * single frame, say a rear view mirror in a racing game, call
     * BeginScene() and EndScene() once for each device.
     */
    hRes = lpd3dDevice->lpVtbl->BeginScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    /*
     * Submit the execute buffer.
     *
     * We want Direct3D to clip the data on our behalf so we specify
     * D3DEXECUTE_CLIPPED.
     */
    hRes = lpd3dDevice->lpVtbl->Execute(lpd3dDevice,
                                        lpd3dExecuteBuffer,
                                        lpd3dViewport,
                                        D3DEXECUTE_CLIPPED);
    if (FAILED(hRes))
    {
        lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
        return hRes;
    }

    /*
     * End the scene.
     */
    hRes = lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    /*
     * At this point the scene will have been rendered and the device
     * surface will hold the contents of the rendering.
     */

    return DD_OK;
}

/***********************************************************************/

/*
 * Render and show a single frame.
 *
 * This involves rendering the scene and blitting the result to client
 * area of the application window on the primary surface.
 *
 * NOTE: This function handles lost surfaces by attempting to restore
 * the applications surfaces and then retrying the rendering.
 */
static HRESULT
DoFrame(HWND hwnd)
{
    HRESULT hRes;

    /*
     * We keeping trying until we succeed or we fail for a reason
     * other than DDERR_SURFACELOST.
     */
    while (TRUE)
    {
        hRes = RenderScene();
        if (SUCCEEDED(hRes))
        {
    POINT pt;
    RECT rTmp;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd, &pt );

    rTmp = rDstRect;
    OffsetRect(&rTmp, pt.x, pt.y);

            hRes = lpddPrimary->lpVtbl->Blt(lpddPrimary,
                                            &rTmp,
                                            lpddDevice,
                                            &rSrcRect,
                                            DDBLT_WAIT,
                                            NULL);
            if (SUCCEEDED(hRes))
                /*
                 * It worked. Bail.
                 */
                return hRes;
        }
        while (DDERR_SURFACELOST == hRes)
            /*
             * The surfaces are lost. Restore them.
             */
            hRes = RestoreSurfaces();
        if (FAILED(hRes))
            /*
             * Something went wrong and it wasn't DDERR_SURFACELOST.
             */
            return hRes;
    }
}

/***********************************************************************/

/*
 * The application suspends when in the background or when handling and
 * error. We signal this fact by drawing a notification string in the
 * client area of the window.
 */
static void

PaintSuspended(HWND hwnd, HDC hdc)
{
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    COLORREF crOldTextColor;
    int      oldMode;
    int      x;
    int      y;
    SIZE     size;
    RECT     rect;
    int      nStrLen;

    /*
     * Black background.
     */
    hOldPen   = SelectObject(hdc, GetStockObject(NULL_PEN));
    hOldBrush = SelectObject(hdc, GetStockObject(BLACK_BRUSH));

    /*
     * White text.
     */
    oldMode = SetBkMode(hdc, TRANSPARENT);
    crOldTextColor = SetTextColor(hdc, RGB(255, 255, 255));

    GetClientRect(hwnd, &rect);

    /*
     * Clear the client area.
     */
    Rectangle(hdc, rect.left, rect.top, rect.right + 1, rect.bottom + 1);

    /*
     * Draw the string centered in the client area.
     */
    nStrLen = strlen(PAUSED_STRING);
    GetTextExtentPoint32(hdc, PAUSED_STRING, nStrLen, &size);
    x = (rect.right  - size.cx) / 2;
    y = (rect.bottom - size.cy) / 2;
    TextOut(hdc, x, y, PAUSED_STRING, nStrLen);

    SetTextColor(hdc, crOldTextColor);
    SetBkMode(hdc, oldMode);

    SelectObject(hdc, hOldBrush);
    SelectObject(hdc, hOldPen);
}

/***********************************************************************/

static LRESULT
OnMove(HWND hwnd, int x, int y)
{
    HRESULT hRes;

    /*
     * No action if the device has not yet been created or if we are
     * suspended.
     */
    if ((NULL != lpd3dDevice) && !fSuspended)
    {
        /*
         * Repaint the client area.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/

static LRESULT
OnSize(HWND hwnd, int w, int h)
{
    HRESULT       hRes;
    DDSURFACEDESC ddsd;

    /*
     * Nothing to do if we are suspended.
     */
    if (!fSuspended)
    {
        /*
         * Update the source and destination rectangles (used by the
         * blit which shows the rendering in the client area).
         */
        rDstRect.right  = rDstRect.left + w;
        rDstRect.bottom = rDstRect.top  + h;
        rSrcRect.right  = w;
        rSrcRect.bottom = h;

        if (NULL != lpd3dDevice)
        {
            /*
             * We already have a device. But is it big enough for the the
             * new window client size?
             *
             * NOTE: As this window is fixed size we should not ever be
             * end up being resized. But just in case we will handle it.
             * This will be useful when we make the application resizable.
             */
            ZeroMemory(&ddsd, sizeof(ddsd));
            ddsd.dwSize = sizeof(ddsd);
            hRes = lpddDevice->lpVtbl->GetSurfaceDesc(lpddDevice, &ddsd);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_DEVICESIZE, hRes);
                return 0L;
            }

            if ((w > (int)ddsd.dwWidth) || (h > (int)ddsd.dwHeight))
            {
                /*
                 * Nope, the device is too small. We need to shut it down
                 * and rebuild it.
                 */

                /*
                 * Execute buffers are bound to devices so when we release
                 * the device we must release the execute buffer.
                 */
                ReleaseScene();
                ReleaseDevice();
            }
        }

        if (NULL == lpd3dDevice)
        {
            /*
             * No Direct3D device yet. This is either because this is the
             * first time through the loop or because we discarded the
             * existing device because it was not big enough for the new
             * window client size.
             */
            hRes = CreateDevice((DWORD)w, (DWORD)h);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
                return 0L;
            }
            hRes = CreateScene();
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_BUILDSCENE, hRes);
                return 0L;
            }
        }

        hRes = UpdateViewport();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_UPDATEVIEWPORT, hRes);
            return 0L;
        }

        /*
         * Render at the new size and show the results in the window's
         * client area.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/

static LRESULT
OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps)
{
    HRESULT hRes;

    USE_PARAM(lpps);

    if (fActive && !fSuspended && (NULL != lpd3dDevice))
    {
        /*
         * NOTE: DoFrame() re-renders the scene as well as blitting the
         * result to the primary. As all we really want to do here is
         * repaint the client area we don't really need to re-render -
         * just re-blit. For this simple sample this inefficiency
         * doesn't matter but for real applications not re-rendering
         * may be a useful optimization.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }
    else
    {
        /*
         * Show the suspended image if we are not active, or suspended or
         * if we have not yet created the device.
         */
        PaintSuspended(hwnd, hdc);
    }

    return 0L;
}

/***********************************************************************/

static LRESULT
OnIdle(HWND hwnd)
{
    HRESULT hRes;

    /*
     * Only animate if we are the foreground app, we aren't suspended
     * and we have completed initialization.
     */
    if (fActive && !fSuspended && (NULL != lpd3dDevice))
    {
        hRes = AnimateScene();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_ANIMATESCENE, hRes);
            return 0L;
        }

        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/

LRESULT CALLBACK
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    LRESULT     lResult;
    HRESULT     hRes;
    char        szBuffer[128];

    switch (msg)
    {
        case WM_CREATE:
            hRes = CreateDirect3D(hwnd);
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
                ReleaseDirect3D();
                return -1L;
            }

            hRes = CreatePrimary(hwnd);
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_INITSCREEN, hRes);
                ReleasePrimary();
                ReleaseDirect3D();
                return -1L;
            }

            hRes = ChooseDevice();
            if (FAILED(hRes))
            {
                ReportError(hwnd, IDS_ERRMSG_NODEVICE, hRes);
                ReleasePrimary();
                ReleaseDirect3D();
                return -1L;
            }

            /*
             * Update the title to show the name of the chosen device.
             */
            wsprintf(szBuffer, "%s: %s", WINDOW_TITLE, szDeviceName);
            SetWindowText(hwnd, szBuffer);

            return 0L;

        case WM_MOVE:
            return OnMove(hwnd, (int)(signed short)LOWORD(lParam), (int)(signed short)HIWORD(lParam));

        case WM_SIZE:
            return OnSize(hwnd, (int)LOWORD(lParam), (int)HIWORD(lParam));

        case WM_ERASEBKGND:
            /*
             * Our rendering fills the entire viewport so we won't bother
             * erasing the background.
             */
            return 1L;

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

            lResult = OnPaint(hwnd, hdc, &ps);

            EndPaint(hwnd, &ps);
            return lResult;

        case WM_ACTIVATEAPP:
            fActive = (BOOL)wParam;
            if (fActive && !fSuspended && (NULL != lpddPalette))
            {
                /*
                 * Realizing the palette using DirectDraw is quite different
                 * from GDI. To realize the palette we call SetPalette()
                 * each time our application is activated.
                 *
                 * NOTE: DirectDraw spots the fact that the new palette is the
                 * same as the old one and so does not increase the reference
                 * count of the palette.
                 */
                hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary, lpddPalette);
                if (FAILED(hRes))
                {
                    FatalError(hwnd, IDS_ERRMSG_REALIZEPALETTE, hRes);
                    return 0L;
                }

            }
            else
            {
                /*
                 * If we have been deactived invalidate to show the suspended
                 * display.
                 */
                InvalidateRect(hwnd, NULL, FALSE);
            }
            return 0L;

        case WM_KEYUP:
            /*
             * We use the escape key as a quick way of getting out of the
             * application.
             */
            if (VK_ESCAPE == (int)wParam)
            {
                DestroyWindow(hwnd);
                return 0L;
            }
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0L;

        case WM_DESTROY:
            /*
             * All cleanup is done here when terminating normally or
             * shutting down due to an error.
             */
            ReleaseScene();
            ReleaseDevice();
            ReleasePrimary();
            ReleaseDirect3D();

            PostQuitMessage(0);
            return 0L;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/***********************************************************************/

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCommandLine,
        int       cmdShow)
{
    WNDCLASS wndClass;
    HWND     hwnd;
    MSG      msg;

    USE_PARAM(hPrevInstance);

    /*
     * Record the instance handle.
     */
    hAppInstance = hInstance;

    /*
     * Very, very primitive command line processing. We only have one
     * option, debug so we will just assume that if anything was
     * specified on the command line that means debug mode (no hardware
     * all surfaces explicitly in system memory).
     */
    if (0 != *lpszCommandLine)
        fDebug = TRUE;

    /*
     * Register the window class.
     */
    wndClass.style         = 0;
    wndClass.lpfnWndProc   = WndProc;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = 0;
    wndClass.hInstance     = hInstance;
    wndClass.hIcon         = LoadIcon(hAppInstance, MAKEINTRESOURCE(IDI_APPICON));
    wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = WINDOW_CLASSNAME;

    RegisterClass(&wndClass);

    /*
     * Create the main window of the instance.
     */
    hwnd = CreateWindow(WINDOW_CLASSNAME,
                        WINDOW_TITLE,
                        WS_OVERLAPPED | WS_SYSMENU,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        WINDOW_WIDTH, WINDOW_HEIGHT,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ShowWindow(hwnd, cmdShow);
    UpdateWindow(hwnd);

    /*
     * The main message dispatch loop.
     *
     * NOTE: For simplicity we handle the message loop with a
     * simple PeekMessage scheme. This might not be the best
     * mechanism for a real application (a separate render worker
     * thread might be better).
     */
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
        {
            /*
             * Message pending. If its QUIT then exit the message
             * loop. Otherwise, process the message.
             */
            if (WM_QUIT == msg.message)
            {
                break;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            /*
             * Animate the scene.
             */
            OnIdle(hwnd);
        }
    }

    return msg.wParam;
}

/***********************************************************************/

/***********************************************************************
 *
 * End of file
 *
 ***********************************************************************/


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by d3dex1.rc
//
#define IDS_ERR_OUTOFMEMORY             1
#define IDS_ERR_EXCEPTION               2
#define IDS_ERR_GENERIC                 3
#define IDS_ERR_OUTOFVIDEOMEMORY        4
#define IDS_ERR_SURFACEBUSY             5
#define IDS_ERR_SURFACELOST             6
#define IDS_ERR_WRONGMODE               7
#define IDS_ERR_INTERNALERROR           8
#define IDS_ERRMSG_RENDERSCENE          9
#define IDS_ERRMSG_ANIMATESCENE         10
#define IDS_ERRMSG_INITDIRECT3D         11
#define IDS_ERRMSG_NODEVICE             12
#define IDS_ERRMSG_BUILDSCENE           13
#define IDS_ERRMSG_DEVICESIZE           14
#define IDS_ERRMSG_CREATEDEVICE         15
#define IDS_ERRMSG_UPDATEVIEWPORT       16
#define IDS_ERRMSG_INITSCREEN           17
#define IDS_ERRMSG_REALIZEPALETTE       18
#define IDI_APPICON                     103

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

this is a version of d3dex1 that demonstrates how a windowed direct draw
app can/should handle multiple monitors.

files:
    d3dex2.cpp      - about the same as the original d3dex1

                      look at how this app uses the routines
                      in ddmm.cpp

                      look at how this app handles WM_MOVE to
                      detect when the window moves monitors.

                      look at how it converts from a window
                      client cordinate to a device cordinate

    ddmm.cpp        - generic directdraw multimonitor helper routines.

                      DirectDrawCreateFromDevice
                      DirectDrawCreateFromWindow
                      DirectDrawDeviceFromWindow

    multimon.h      - stub multimon functions that use GetProcAddress
                      used by ddmm.cpp, so it can still run on
                      Win95 or NT4


D3DEX2.C

/***********************************************************************
 *
 * File :       d3dex1.c
 *
 * Abstract :   A very simple Direct3D example which simply draws a
 *              single, rotating, Gouraud shaded triangle in a fixed
 *              size window.
 *
 *              For code clarity a number of issues have not been
 *              addressed in this sample. For example, full screen
 *              operation, resizing the window, texture mapping are
 *              not included. Furthermore, certain optimizations have
 *              not been included where they would obfuscate the code.
 *              Every attempt has been made to highlight these areas
 *              will extensive comments.
 *
 * Author :     Colin D. C. McCartney
 *
 * Date :       09/04/96
 *
 * Version :    V1.0
 *
 * To do:
 * - Handle DDERR_WRONGMODE
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 ***********************************************************************/

/***********************************************************************
 *
 * Include files
 *
 ***********************************************************************/

#define  INITGUID
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <ddraw.h>
#include <d3d.h>

#include "nowarn.h"
#include "resource.h"

// Multi-monitor support
// Include this header file which has helper functions
// which help us use get the right DirectDraw object for
// the monitor(s) that we want to display on.
#include "ddmm.h"

#ifdef DEBUG
// Make it possible to get reasonable symbols for crummy debuggers
#define static
#endif

 /***********************************************************************
 *
 * Constants
 *
 ***********************************************************************/

/*
 * Class name for this application's window class.
 */
#define WINDOW_CLASSNAME      "D3DSample1Class"

/*
 * Title for the application's window.
 */
#define WINDOW_TITLE          "D3D Sample 1"

/*
 * String to be displayed when the application is paused.
 */
#define PAUSED_STRING         "Paused"

/*
 * Half height of the view window.
 */
#define HALF_HEIGHT           D3DVAL(0.5)

/*
 * Front and back clipping planes.
 */
#define FRONT_CLIP            D3DVAL(1.0)
#define BACK_CLIP             D3DVAL(1000.0)

/*
 * Fixed window size.
 */
#define WINDOW_WIDTH          320
#define WINDOW_HEIGHT         200

/*
 * Maximum length of the chosen device name and description of the
 * chosen Direct3D device.
 */
#define MAX_DEVICE_NAME       256
#define MAX_DEVICE_DESC       256

/*
 * Amount to rotate per frame.
 */
#define M_PI                  3.14159265359
#define M_2PI                 6.28318530718
#define ROTATE_ANGLE_DELTA    (M_2PI / 300.0)

/*
 * Execute buffer contents
 */
#define NUM_VERTICES          3UL
#define NUM_INSTRUCTIONS      6UL
#define NUM_STATES            8UL
#define NUM_PROCESSVERTICES   1UL
#define NUM_TRIANGLES         1UL

/***********************************************************************
 *
 * Macro funtions.
 *
 ***********************************************************************/

/*
 * Extract the error code from an HRESULT
 */
#define CODEFROMHRESULT(hRes) ((hRes) & 0x0000FFFFUL)

/***********************************************************************
 *
 * Global store
 *
 ***********************************************************************/

/*
 * Application instance handle (set in WinMain).
 */
static HINSTANCE               hAppInstance              = NULL;

/*
 * Running in debug mode?
 */
static BOOL                    fDebug                    = FALSE;

/*
 * Is the app. active?
 */
static BOOL                    fActive                   = TRUE;

/*
 * Has the app. been suspended?
 */
static BOOL                    fSuspended                = FALSE;

/*
 * DirectDraw interfaces
 */
static LPDIRECTDRAW            lpdd                      = NULL;
static LPDIRECTDRAWSURFACE     lpddPrimary               = NULL;
static LPDIRECTDRAWSURFACE     lpddDevice                = NULL;
static LPDIRECTDRAWSURFACE     lpddZBuffer               = NULL;
static LPDIRECTDRAWPALETTE     lpddPalette               = NULL;

/*
 * Direct3D interfaces
 */
static LPDIRECT3D              lpd3d                     = NULL;
static LPDIRECT3DDEVICE        lpd3dDevice               = NULL;
static LPDIRECT3DMATERIAL      lpd3dMaterial             = NULL;
static LPDIRECT3DMATERIAL      lpd3dBackgroundMaterial   = NULL;
static LPDIRECT3DVIEWPORT      lpd3dViewport             = NULL;
static LPDIRECT3DLIGHT         lpd3dLight                = NULL;
static LPDIRECT3DEXECUTEBUFFER lpd3dExecuteBuffer        = NULL;

/*
 * Direct3D handles
 */
static D3DMATRIXHANDLE         hd3dWorldMatrix           = 0UL;
static D3DMATRIXHANDLE         hd3dViewMatrix            = 0UL;
static D3DMATRIXHANDLE         hd3dProjMatrix            = 0UL;
static D3DMATERIALHANDLE       hd3dSurfaceMaterial       = 0UL;
static D3DMATERIALHANDLE       hd3dBackgroundMaterial    = 0UL;

/*
 * Globals used for selecting the Direct3D device. They are
 * globals as it makes it easy for the enumeration callback
 * to read and write from them.
 */
static BOOL                    fDeviceFound              = FALSE;
static DWORD                   dwDeviceBitDepth          = 0UL;
static GUID                    guidDevice;
static char                    szDeviceName[MAX_DEVICE_NAME];
static char                    szDeviceDesc[MAX_DEVICE_DESC];
static D3DDEVICEDESC           d3dHWDeviceDesc;
static D3DDEVICEDESC           d3dSWDeviceDesc;

/*
 * The screen coordinates of the client area of the window. This
 * rectangle defines the destination into which we blit to update
 * the client area of the window with the results of the 3D rendering.
 */
static RECT                    rDstRect;

/*
 * This rectangle defines the portion of the rendering target surface
 * into which we render. The top left coordinates of this rectangle
 * are always zero and the right and bottom give the size of the
 * viewport.
 */
static RECT                    rSrcRect = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};

/*
 * Angle of rotation of the world matrix.
 */
static double                  dAngleOfRotation          = 0.0;

/*
 * Predefined transformations.
 */
static D3DMATRIX d3dWorldMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dViewMatrix =
{
    D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 5.0), D3DVAL( 1.0)
};

static D3DMATRIX d3dProjMatrix =
{
    D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 2.0), D3DVAL( 0.0), D3DVAL( 0.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL( 1.0), D3DVAL( 1.0),
    D3DVAL( 0.0), D3DVAL( 0.0), D3DVAL(-1.0), D3DVAL( 0.0)
};

// Multimonitor globals
int     hMonitor;
char    szMonitor[128];
RECT    rectMonitor;

/***********************************************************************
 *
 * Function prototypes
 *
 ***********************************************************************/

static void           ReportError(HWND hwnd, int nMessage, HRESULT hRes);
static void           FatalError(HWND hwnd, int nMessage, HRESULT hRes);

static DWORD          BitDepthToFlags(DWORD dwBitDepth);
static DWORD          FlagsToBitDepth(DWORD dwFlags);

static void           SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
                                               double      dHalfHeight,
                                               double      dFrontClipping,
                                               double      dBackClipping);
static void           SetRotationAboutY(LPD3DMATRIX lpd3dMatrix,
                                        double      dAngleOfRotation);

static HRESULT        CreateDirect3D(HWND hwnd);
static HRESULT        ReleaseDirect3D(void);

static HRESULT        CreatePrimary(HWND hwnd);
static HRESULT        RestorePrimary(void);
static HRESULT        ReleasePrimary(void);

static HRESULT WINAPI EnumDeviceCallback(LPGUID          lpGUID,
                                         LPSTR           lpszDeviceDesc,
                                         LPSTR           lpszDeviceName,
                                         LPD3DDEVICEDESC lpd3dHWDeviceDesc,
                                         LPD3DDEVICEDESC lpd3dSWDeviceDesc,
                                         LPVOID          lpUserArg);
static HRESULT        ChooseDevice(void);

static HRESULT        CreateDevice(DWORD dwWidth, DWORD dwHeight);
static HRESULT        RestoreDevice(void);
static HRESULT        ReleaseDevice(void);

static LRESULT        RestoreSurfaces(void);

static HRESULT        FillExecuteBuffer(void);
static HRESULT        CreateScene(void);
static HRESULT        ReleaseScene(void);
static HRESULT        AnimateScene(void);

static HRESULT        UpdateViewport(void);

static HRESULT        RenderScene(void);
static HRESULT        DoFrame(HWND hwnd);
static void           PaintSuspended(HWND hwnd, HDC hdc);

static LRESULT        OnMove(HWND hwnd, int x, int y);
static LRESULT        OnSize(HWND hwnd, int w, int h);
static LRESULT        OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps);
static LRESULT        OnIdle(HWND hwnd);

// Multimonitor
static HRESULT      CheckMonitors(HWND hwnd, BOOL fReset);

LRESULT CALLBACK      WndProc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam);
int PASCAL            WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                              LPSTR lpszCommandLine, int cmdShow);

/***********************************************************************
 *
 * Macro functions
 *
 ***********************************************************************/

/***********************************************************************/

#ifdef _DEBUG
#define ASSERT(x)       assert(x)
#else
#define ASSERT(x)
#endif

/***********************************************************************/

/*
 * Used to keep the compiler happy about any unused parameters.
 */
#define USE_PARAM(x)    (x) = (x)

/***********************************************************************/

/***********************************************************************
 *
 * Functions
 *
 ***********************************************************************/

/***********************************************************************/

/*
 * Report the given error by display a message box.
 */
static void
ReportError(HWND hwnd, int nMessage, HRESULT hRes)
{
    HDC  hdc;
    char szBuffer[256];
    char szMessage[128];
    char szError[128];
    int  nStrID;

    /*
     * Turn the animation loop off.
     */
    fSuspended = TRUE;

    /*
     * Get the high level error message.
     */
    LoadString(hAppInstance, nMessage, szMessage, sizeof(szMessage));

    /*
     * We issue sensible error messages for common run time errors. For
     * errors which are internal or coding errors we simply issue an
     * error number (they should never occur).
     */
    switch (hRes)
    {
        case DDERR_EXCEPTION:        nStrID = IDS_ERR_EXCEPTION;        break;
        case DDERR_GENERIC:          nStrID = IDS_ERR_GENERIC;          break;
        case DDERR_OUTOFMEMORY:      nStrID = IDS_ERR_OUTOFMEMORY;      break;
        case DDERR_OUTOFVIDEOMEMORY: nStrID = IDS_ERR_OUTOFVIDEOMEMORY; break;
        case DDERR_SURFACEBUSY:      nStrID = IDS_ERR_SURFACEBUSY;      break;
        case DDERR_SURFACELOST:      nStrID = IDS_ERR_SURFACELOST;      break;
        case DDERR_WRONGMODE:        nStrID = IDS_ERR_WRONGMODE;        break;
        default:                     nStrID = IDS_ERR_INTERNALERROR;    break;
    }
    LoadString(hAppInstance, nStrID, szError, sizeof(szError));

    /*
     * Show the "paused" display.
     */
    hdc = GetDC(hwnd);
    PaintSuspended(hwnd, hdc);
    ReleaseDC(hwnd, hdc);

    /*
     * Convert the error code into a string (not very informative but
     * it keeps the code simple).
     */
    wsprintf(szBuffer, "%s\n%s (Error #%d)", szMessage, szError, CODEFROMHRESULT(hRes));
    MessageBox(hwnd, szBuffer, WINDOW_TITLE, MB_OK | MB_APPLMODAL);
    fSuspended = FALSE;
}

/***********************************************************************/

/*
 * Handle a fatal error. Displays the error message via a message box
 * and then destroys the window.
 */
static void
FatalError(HWND hwnd, int nMessage, HRESULT hRes)
{
    /*
     * Report the error.
     */
    ReportError(hwnd, nMessage, hRes);
    fSuspended = TRUE;

    /*
     * And shut down.
     *
     * NOTE: We don't attempt to clean up. That will be done
     * when WM_DESTROY happens.
     */
    DestroyWindow(hwnd);
}

/***********************************************************************/

/*
 * Converts a bit depth into the appropriate DirectDraw bit depth flag.
 */
static DWORD
BitDepthToFlags(DWORD dwBitDepth)
{
    switch (dwBitDepth)
    {
        case  1UL: return DDBD_1;
        case  2UL: return DDBD_2;
        case  4UL: return DDBD_4;
        case  8UL: return DDBD_8;
        case 16UL: return DDBD_16;
        case 24UL: return DDBD_24;
        case 32UL: return DDBD_32;
        default:   return 0UL;     /* Oh, please... */
    }
}

/***********************************************************************/

/*
 * Convert bit depth flags to an acutal bit count. Selects the smallest
 * bit count in the mask if more than one flag is present.
 */
static DWORD
FlagsToBitDepth(DWORD dwFlags)
{
    if (dwFlags & DDBD_1)
        return 1UL;
    else if (dwFlags & DDBD_2)
        return 2UL;
    else if (dwFlags & DDBD_4)
        return 4UL;
    else if (dwFlags & DDBD_8)
        return 8UL;
    else if (dwFlags & DDBD_16)
        return 16UL;
    else if (dwFlags & DDBD_24)
        return 24UL;
    else if (dwFlags & DDBD_32)
        return 32UL;
    else
        return 0UL; /* Oh, please... */
}

/***********************************************************************/

/*
 * Set the given matrix to a perspective transform for the given half
 * height and front and back clipping planes.
 */
static void
SetPerspectiveProjection(LPD3DMATRIX lpd3dMatrix,
                         double      dHalfHeight,
                         double      dFrontClipping,
                         double      dBackClipping)
{
    double dTmp1;
    double dTmp2;

    ASSERT(NULL != lpd3dMatrix);

    dTmp1 = dHalfHeight / dFrontClipping;
    dTmp2 = dBackClipping / (dBackClipping - dFrontClipping);

    lpd3dMatrix->_11 =  D3DVAL(2.0);
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 =  D3DVAL(0.0);
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(2.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  D3DVAL(0.0);
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  D3DVAL(dTmp1 * dTmp2);
    lpd3dMatrix->_34 =  D3DVAL(dTmp1);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(-dHalfHeight * dTmp2);
    lpd3dMatrix->_44 =  D3DVAL(0.0);
}

/***********************************************************************/

/*
 * Set the given matrix to a rotation about Y transform of the given
 * number of radians.
 */
static void
SetRotationAboutY(LPD3DMATRIX lpd3dMatrix, double dAngleOfRotation)
{
    D3DVALUE dvCos;
    D3DVALUE dvSin;

    ASSERT(NULL != lpd3dMatrix);

    dvCos = D3DVAL(cos(dAngleOfRotation));
    dvSin = D3DVAL(sin(dAngleOfRotation));

    lpd3dMatrix->_11 =  dvCos;
    lpd3dMatrix->_12 =  D3DVAL(0.0);
    lpd3dMatrix->_13 = -dvSin;
    lpd3dMatrix->_14 =  D3DVAL(0.0);
    lpd3dMatrix->_21 =  D3DVAL(0.0);
    lpd3dMatrix->_22 =  D3DVAL(1.0);
    lpd3dMatrix->_23 =  D3DVAL(0.0);
    lpd3dMatrix->_24 =  D3DVAL(0.0);
    lpd3dMatrix->_31 =  dvSin;
    lpd3dMatrix->_32 =  D3DVAL(0.0);
    lpd3dMatrix->_33 =  dvCos;
    lpd3dMatrix->_34 =  D3DVAL(0.0);
    lpd3dMatrix->_41 =  D3DVAL(0.0);
    lpd3dMatrix->_42 =  D3DVAL(0.0);
    lpd3dMatrix->_43 =  D3DVAL(0.0);
    lpd3dMatrix->_44 =  D3DVAL(1.0);
}

/***********************************************************************/

/*
 * Create the DirectDraw/3D driver object and get DirectDraw and Direct3D
 * interfaces for communicating with that object.
 */
static HRESULT
CreateDirect3D(HWND hwnd)
{
    HRESULT hRes;

    ASSERT(NULL == lpdd);
    ASSERT(NULL == lpd3d);

    /*
     * Create the DirectDraw/3D driver object and get the DirectDraw
     * interface to that object.
     */
    lpdd = DirectDrawCreateFromWindow(hwnd);
    if (lpdd == NULL)
        return E_FAIL;

    /*
     * As we are running in a window set the cooperative level to
     * normal. Also, to ensure that the palette is realized correctly
     * we need to pass the hwnd of the main window.
     */
    hRes = lpdd->lpVtbl->SetCooperativeLevel(lpdd, hwnd, DDSCL_NORMAL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Get the Direct3D interface to the DirectDraw/3D driver object.
     */
    hRes = lpdd->lpVtbl->QueryInterface(lpdd, &IID_IDirect3D, &lpd3d);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Release the DirectDraw/3D driver object.
 */
static HRESULT
ReleaseDirect3D(void)
{
    if (NULL != lpd3d)
    {
        lpd3d->lpVtbl->Release(lpd3d);
        lpd3d = NULL;
    }
    if (NULL != lpdd)
    {
        lpdd->lpVtbl->Release(lpdd);
        lpdd = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Create the primary surface (representing the desktop) and create and
 * attach a clipper and, if necessary, a palette.
 */
static HRESULT
CreatePrimary(HWND hwnd)
{
    HRESULT             hRes;
    DDSURFACEDESC       ddsd;
    LPDIRECTDRAWCLIPPER lpddClipper;
    HDC                 hdc;
    int                 i;
    PALETTEENTRY        peColorTable[256];

    ASSERT(NULL != hwnd);
    ASSERT(NULL != lpdd);
    ASSERT(NULL == lpddPrimary);
    ASSERT(NULL == lpddPalette);

    /*
     * Create the primary surface.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddPrimary, NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the clipper. We bind the application's window to the
     * clipper and attach it to the primary. This ensures then when we
     * blit from the rendering surface to the primary we don't write
     * outside the visible region of the window.
     */
    hRes = DirectDrawCreateClipper(0UL, &lpddClipper, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpddClipper->lpVtbl->SetHWnd(lpddClipper, 0UL, hwnd);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }
    hRes = lpddPrimary->lpVtbl->SetClipper(lpddPrimary, lpddClipper);
    if (FAILED(hRes))
    {
        lpddClipper->lpVtbl->Release(lpddClipper);
        return hRes;
    }

    /*
     * We release the clipper interface after attaching it to the surface
     * as we don't need to use it again. The surface holds a reference to
     * the clipper when its been attached. The clipper will therefore be
     * released when the surface is released.
     */
    lpddClipper->lpVtbl->Release(lpddClipper);

    /*
     * If the primary is palettized then so will the device (the device
     * surface must have the same pixel format as the current primary if
     * we want to double buffer with DirectDraw). Hence, if the primary
     * is palettized we need to create a palette and attach it to the
     * primary (and to the device surface when we create it).
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;
    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
    {
        /*
         * Initializing the palette correctly is essential. We are
         * running in a window so we need to be a good windows app
         * and not mess with the top ten and bottom ten static
         * colors. Therefore, we copy them from the system palette
         * and mark them as read only (D3DPAL_READONLY). The middle
         * 236 entries are free for use by Direct3D so we mark them
         * free (D3DPAL_FREE).
         *
         * NOTE: In order that the palette entries are correctly
         * allocated it is essential that the free entries are
         * also marked reserved to GDI (PC_RESERVED).
         *
         * NOTE: We don't need to specify the palette caps flag
         * DDPCAPS_INITIALIZE. This flag is obsolete. CreatePalette
         * must be given a valid palette entry array and always
         * initializes from it.
         */
        hdc = GetDC(NULL);
        GetSystemPaletteEntries(hdc, 0, 256, peColorTable);
        ReleaseDC(NULL, hdc);

        for (i = 0; i < 10; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        for (i = 10; i < 246; i++)
            peColorTable[i].peFlags = D3DPAL_FREE | PC_RESERVED;
        for (i = 246; i < 256; i++)
            peColorTable[i].peFlags = D3DPAL_READONLY;
        hRes = lpdd->lpVtbl->CreatePalette(lpdd,
                                           DDPCAPS_8BIT,
                                           peColorTable,
                                           &lpddPalette,
                                           NULL);
        if (FAILED(hRes))
            return hRes;

        hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary, lpddPalette);
            return hRes;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Attempt to restore the video memory allocated for the primary. This
 * function will be invoked by a DirectX function returning
 * DDERR_SURFACELOST due to a mode switch or fullscreen DOS box
 * invalidating video memory.
 */
static HRESULT
RestorePrimary(void)
{
    ASSERT(NULL != lpddPrimary);

    return lpddPrimary->lpVtbl->Restore(lpddPrimary);
}

/***********************************************************************/

/*
 * Release the primary surface and its attached clipper and palette.
 */
static HRESULT
ReleasePrimary(void)
{
    if (NULL != lpddPalette)
    {
        lpddPalette->lpVtbl->Release(lpddPalette);
        lpddPalette = NULL;
    }
    if (NULL != lpddPrimary)
    {
        lpddPrimary->lpVtbl->Release(lpddPrimary);
        lpddPrimary = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * This callback is invoked for each Direct3D device installed on the
 * system. For each device we get its identifying GUID, a name and
 * description, a description of its hardware and software capabilities
 * and a user argument (which we don't use).
 */
static HRESULT WINAPI
EnumDeviceCallback(LPGUID          lpGUID,
                   LPSTR           lpszDeviceDesc,
                   LPSTR           lpszDeviceName,
                   LPD3DDEVICEDESC lpd3dHWDeviceDesc,
                   LPD3DDEVICEDESC lpd3dSWDeviceDesc,
                   LPVOID          lpUserArg)
{
    BOOL            fIsHardware;
    LPD3DDEVICEDESC lpd3dDeviceDesc;

    /*
     * We don't use the user argument so just keep the compiler happy.
     */
    USE_PARAM(lpUserArg);

    /*
     * If there is no hardware support then the color model is zero.
     */
    fIsHardware     = (0UL != lpd3dHWDeviceDesc->dcmColorModel);
    lpd3dDeviceDesc = (fIsHardware ? lpd3dHWDeviceDesc : lpd3dSWDeviceDesc);

    /*
     * If we are in debug mode and this is a hardware device skip it.
     */
    if (fDebug && fIsHardware)
        return D3DENUMRET_OK;

    /*
     * Does the device render at the depth we want?
     */
    if (0UL == (lpd3dDeviceDesc->dwDeviceRenderBitDepth & dwDeviceBitDepth))
    {
        /*
         * No skip this device.
         */
        return D3DENUMRET_OK;
    }

    /*
     * The device must support gouraud shaded triangles.
     */
    if (D3DCOLOR_MONO == lpd3dDeviceDesc->dcmColorModel)
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORGOURAUDMONO))
        {
            /*
             * No gouraud shading. Skip this device.
             */
            return D3DENUMRET_OK;
        }
    }
    else
    {
        if (!(lpd3dDeviceDesc->dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORGOURAUDRGB))
        {
            /*
             * No gouraud shading. Skip this device.
             */
            return D3DENUMRET_OK;
        }
    }

    if (!fIsHardware && fDeviceFound && (D3DCOLOR_RGB == lpd3dDeviceDesc->dcmColorModel))
    {
        /*
         * If this is software RGB and we already have found a software
         * mono already then we are not interested. Skip it.
         */
        return D3DENUMRET_OK;
    }

    /*
     * This is a device we are interested in - cache the details away.
     */
    fDeviceFound = TRUE;
    CopyMemory(&guidDevice, lpGUID, sizeof(GUID));
    strcpy(szDeviceDesc, lpszDeviceDesc);
    strcpy(szDeviceName, lpszDeviceName);
    CopyMemory(&d3dHWDeviceDesc, lpd3dHWDeviceDesc, sizeof(D3DDEVICEDESC));
    CopyMemory(&d3dSWDeviceDesc, lpd3dSWDeviceDesc, sizeof(D3DDEVICEDESC));

    /*
     * If this is a hardware device we have found what we are looking
     * for.
     */
    if (fIsHardware)
        return D3DENUMRET_CANCEL;

    /*
     * Keep looking...
     */
    return D3DENUMRET_OK;
}

/***********************************************************************/

/*
 * Choose an appropriate Direct3D using the following mechanism:
 *
 * 1) Discard any devices which don't match the current display depth.
 * 2) Discard any devices which can't do gouraud shaded triangles.
 * 3) If a hardware device is found which matches 1) and 2) use it.
 *    However, if we are running in debug mode we will skip hardware.
 * 4) Otherwise favour Mono/Ramp mode software renderers over RGB ones
 *    as, at least until MMX is widespread, Mono will be faster.
 *
 * The actual implementation of this mechanism is in the callback
 * function above.
 */
static HRESULT
ChooseDevice(void)
{
    DDSURFACEDESC ddsd;
    HRESULT       hRes;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);

    /*
     * As we are running in a window we will not be changing the screen
     * depth and hence the pixel format of the rendering target must match
     * the pixel format of the current primary. Therefore, we need to
     * determine the pixel format of the primary.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hRes = lpddPrimary->lpVtbl->GetSurfaceDesc(lpddPrimary, &ddsd);
    if (FAILED(hRes))
        return hRes;

    dwDeviceBitDepth = BitDepthToFlags(ddsd.ddpfPixelFormat.dwRGBBitCount);

    /*
     * Enumerate the devices and pick one.
     */
    fDeviceFound = FALSE;
    hRes = lpd3d->lpVtbl->EnumDevices(lpd3d, EnumDeviceCallback, &fDeviceFound);
    if (FAILED(hRes))
        return hRes;

    if (!fDeviceFound)
    {
        /*
         * No suitable device was found. We have no alternative but to
         * fail creation entirely.
         */
        return DDERR_NOTFOUND;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Create an instance of the Direct3D device we choose earlier with the
 * given width and height.
 *
 * This function handles all aspects of the device creation including
 * choosing surface memory type, create the device surface, the z-buffer
 * (if necessary) and attaching the palette (if required).

*/
static HRESULT
CreateDevice(DWORD dwWidth, DWORD dwHeight)
{
    LPD3DDEVICEDESC lpd3dDeviceDesc;
    DWORD           dwDeviceMemType;
    DWORD           dwZBufferMemType;
    DDSURFACEDESC   ddsd;
    HRESULT         hRes;
    DWORD           dwZBufferBitDepth;

    ASSERT(NULL != lpdd);
    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpddPrimary);
    ASSERT(NULL == lpddDevice);
    ASSERT(NULL == lpd3dDevice);

    /*
     * The first step is to determine the kind of memory (system or
     * video) from which the device surface should be allocated.
     */
    if (0UL != d3dHWDeviceDesc.dcmColorModel)
    {
        lpd3dDeviceDesc = &d3dHWDeviceDesc;

        /*
         * Device has a hardware rasterizer. Currently this means that
         * the device surface must be in video memory.
         */
        dwDeviceMemType  = DDSCAPS_VIDEOMEMORY;
        dwZBufferMemType = DDSCAPS_VIDEOMEMORY;
    }
    else
    {
        lpd3dDeviceDesc = &d3dSWDeviceDesc;

        /*
         * Device has a software rasterizer. We will let DirectDraw
         * decide where the device surface resides unless we are
         * running in debug mode in which case we will force it into
         * system memory. For a software rasterizer the z-buffer should
         * always go into system memory. A z-buffer in video memory will
         * kill performance.
         */
        dwDeviceMemType  = (fDebug ? DDSCAPS_SYSTEMMEMORY : 0UL);
        dwZBufferMemType = DDSCAPS_SYSTEMMEMORY;
    }

    /*
     * Create the device surface. The pixel format will be identical
     * to the primary so we don't have to explicitly specify it. We do
     * need to explicity specify the size, memory type and capabilities
     * of the surface.
     */
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.dwWidth        = dwWidth;
    ddsd.dwHeight       = dwHeight;
    ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_OFFSCREENPLAIN | dwDeviceMemType;
    hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddDevice, NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * If we have created a palette then we have already determined that
     * the primary (and hence the device surface) is palettized so
     * attach the palette to the device surface (its already attached to
     * the primary).
     */
    if (NULL != lpddPalette)
    {
        hRes = lpddDevice->lpVtbl->SetPalette(lpddDevice, lpddPalette);
        if (FAILED(hRes))
            return hRes;
    }

    /*
     * We now determine whether we need a z-buffer or not and if so
     * its bit depth.
     */
    if (0UL != lpd3dDeviceDesc->dwDeviceZBufferBitDepth)
    {
        /*
         * The device supports z-buffering. Determine the depth. We
         * select the lowest supported z-buffer depth to save memory.
         * Accuracy is not too important for this sample.
         */
        dwZBufferBitDepth = FlagsToBitDepth(lpd3dDeviceDesc->dwDeviceZBufferBitDepth);

        /*
         * Create the z-buffer.
         */
        ZeroMemory(&ddsd, sizeof(ddsd));
        ddsd.dwSize            = sizeof(ddsd);
        ddsd.dwFlags           = DDSD_CAPS   |
                                 DDSD_WIDTH  |
                                 DDSD_HEIGHT |
                                 DDSD_ZBUFFERBITDEPTH;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_ZBUFFER | dwZBufferMemType;
        ddsd.dwWidth           = dwWidth;
        ddsd.dwHeight          = dwHeight;
        ddsd.dwZBufferBitDepth = dwZBufferBitDepth;
        hRes = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddZBuffer, NULL);
        if (FAILED(hRes))
            return hRes;

        /*
         * Attach it to the rendering target.
         */
        hRes = lpddDevice->lpVtbl->AddAttachedSurface(lpddDevice, lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    /*
     * Now all the elements are in place (device surface in correct
     * memory type, attached z-buffer of correct depth and memory
     * type, and palette if necessary) we can actually query for the
     * Direct3D we choose earlier.
     */
    hRes = lpddDevice->lpVtbl->QueryInterface(lpddDevice,
                                              &guidDevice,
                                              &lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Restore the video memory for the device surface and z-buffer if it
 * has been lost.
 */
static HRESULT
RestoreDevice(void)
{
    HRESULT hRes;

    if (NULL != lpddZBuffer)
    {
        hRes = lpddZBuffer->lpVtbl->Restore(lpddZBuffer);
        if (FAILED(hRes))
            return hRes;
    }

    if (NULL != lpddDevice)
    {
        hRes = lpddDevice->lpVtbl->Restore(lpddDevice);
        if (FAILED(hRes))
            return hRes;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Release the Direct3D device and its associated surfaces.
 */
static HRESULT
ReleaseDevice(void)
{
    if (NULL != lpd3dDevice)
    {
        lpd3dDevice->lpVtbl->Release(lpd3dDevice);
        lpd3dDevice = NULL;
    }
    if (NULL != lpddZBuffer)
    {
        lpddZBuffer->lpVtbl->Release(lpddZBuffer);
        lpddZBuffer = NULL;
    }
    if (NULL != lpddDevice)
    {
        lpddDevice->lpVtbl->Release(lpddDevice);
        lpddDevice = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Attempt to restore all the surfaces used by the application.
 */
static LRESULT
RestoreSurfaces(void)
{
    HRESULT hRes;

    hRes = RestorePrimary();
    if (FAILED(hRes))
        return hRes;

    hRes = RestoreDevice();
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Fill the single execute buffer used in this sample with all the
 * vertices, transform, light and render state and drawing primitives
 * necessary to draw our triangle.
 *
 * NOTE: This is not the most efficient way of organizing the execute
 * buffer. For best performance you want to minimize state changes. In
 * this sample we submit the execute buffer for each frame in the
 * animation loop and no state in the buffer is modified. The only
 * thing we modify is the world matrix (its contents - not its handle).
 * Therefore, it would be more efficient to extract all the static
 * state instructions into a separate execute buffer which we issue
 * once only at startup and, from then on, simply execute a second
 * execute buffer with vertices and triangles.
 * However, this sample is not exactly performance critical so we will
 * just use one execute buffer and resubmit it its entirety for each
 * frame.
 */
static HRESULT
FillExecuteBuffer(void)
{
    HRESULT              hRes;
    D3DEXECUTEBUFFERDESC d3dExeBufDesc;
    LPD3DVERTEX          lpVertex;
    LPD3DINSTRUCTION     lpInstruction;
    LPD3DPROCESSVERTICES lpProcessVertices;
    LPD3DTRIANGLE        lpTriangle;
    LPD3DSTATE           lpState;

    ASSERT(NULL != lpd3dExecuteBuffer);
    ASSERT(0UL  != hd3dSurfaceMaterial);
    ASSERT(0UL  != hd3dWorldMatrix);
    ASSERT(0UL  != hd3dViewMatrix);
    ASSERT(0UL  != hd3dProjMatrix);

    /*
     * Lock the execute buffer.
     */
    ZeroMemory(&d3dExeBufDesc, sizeof(d3dExeBufDesc));
    d3dExeBufDesc.dwSize = sizeof(d3dExeBufDesc);
    hRes = lpd3dExecuteBuffer->lpVtbl->Lock(lpd3dExecuteBuffer, &d3dExeBufDesc);
    if (FAILED(hRes))
        return hRes;

    /*
     * For explanatory purposes we fill the execute buffer by casting
     * a pointer to the execute buffer to the appropriate data structures.
     *
     * !!! NOTE: Issue - alignment.
     */
    lpVertex = (LPD3DVERTEX)d3dExeBufDesc.lpData;

    /*
     * First vertex.
     */
    lpVertex->dvX  = D3DVAL( 0.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL( 1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 0.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    /*
     * Second vertex.
     */
    lpVertex->dvX  = D3DVAL( 1.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 1.0);
    lpVertex++;

    /*
     * Third vertex.
     */
    lpVertex->dvX  = D3DVAL(-1.0); /* Position in model coordinates       */
    lpVertex->dvY  = D3DVAL(-1.0);
    lpVertex->dvZ  = D3DVAL( 0.0);
    lpVertex->dvNX = D3DVAL( 0.0); /* Normalized illumination normal      */
    lpVertex->dvNY = D3DVAL( 0.0);
    lpVertex->dvNZ = D3DVAL(-1.0);
    lpVertex->dvTU = D3DVAL( 1.0); /* Texture coordinates (not used here) */
    lpVertex->dvTV = D3DVAL( 0.0);
    lpVertex++;

    /*
     * Transform state - world, view and projection.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpVertex;
    lpInstruction->bOpcode = D3DOP_STATETRANSFORM;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_WORLD;
    lpState->dwArg[0] = hd3dWorldMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_VIEW;
    lpState->dwArg[0] = hd3dViewMatrix;
    lpState++;
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_PROJECTION;
    lpState->dwArg[0] = hd3dProjMatrix;
    lpState++;

    /*
     * Lighting state.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATELIGHT;
    lpInstruction->bSize   = sizeof(D3DSTATE);
    lpInstruction->wCount  = 2U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->dlstLightStateType = D3DLIGHTSTATE_MATERIAL;
    lpState->dwArg[0] = hd3dSurfaceMaterial;
    lpState++;
    lpState->dlstLightStateType = D3DLIGHTSTATE_AMBIENT;
    lpState->dwArg[0] = RGBA_MAKE(128, 128, 128, 128);
    lpState++;

    /*
     * Render state.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_STATERENDER;
    lpInstruction->bSize = sizeof(D3DSTATE);
    lpInstruction->wCount = 3U;
    lpInstruction++;
    lpState = (LPD3DSTATE)lpInstruction;
    lpState->drstRenderStateType = D3DRENDERSTATE_FILLMODE;
    lpState->dwArg[0] = D3DFILL_SOLID;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_SHADEMODE;
    lpState->dwArg[0] = D3DSHADE_GOURAUD;
    lpState++;
    lpState->drstRenderStateType = D3DRENDERSTATE_DITHERENABLE;
    lpState->dwArg[0] = TRUE;
    lpState++;

    /*
     * The process vertices instruction tells the driver what to
     * do with the vertices in the buffer. In this sample we want
     * Direct3D to perform the entire pipeline on our behalf so
     * the instruction is D3DPROCESSVERTICES_TRANSFORMLIGHT.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpState;
    lpInstruction->bOpcode = D3DOP_PROCESSVERTICES;
    lpInstruction->bSize   = sizeof(D3DPROCESSVERTICES);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpProcessVertices = (LPD3DPROCESSVERTICES)lpInstruction;
    lpProcessVertices->dwFlags    = D3DPROCESSVERTICES_TRANSFORMLIGHT;
    lpProcessVertices->wStart     = 0U;           /* First source vertex */
    lpProcessVertices->wDest      = 0U;
    lpProcessVertices->dwCount    = NUM_VERTICES; /* Number of vertices  */
    lpProcessVertices->dwReserved = 0UL;
    lpProcessVertices++;

    /*
     * Draw the triangle.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpProcessVertices;
    lpInstruction->bOpcode = D3DOP_TRIANGLE;
    lpInstruction->bSize   = sizeof(D3DTRIANGLE);
    lpInstruction->wCount  = 1U;
    lpInstruction++;
    lpTriangle = (LPD3DTRIANGLE)lpInstruction;
    lpTriangle->wV1    = 0U;
    lpTriangle->wV2    = 1U;
    lpTriangle->wV3    = 2U;
    lpTriangle->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
    lpTriangle++;

    /*
     * Stop execution of the buffer.
     */
    lpInstruction = (LPD3DINSTRUCTION)lpTriangle;
    lpInstruction->bOpcode = D3DOP_EXIT;
    lpInstruction->bSize   = 0UL;
    lpInstruction->wCount  = 0U;

    /*
     * Unlock the execute buffer.
     */
    lpd3dExecuteBuffer->lpVtbl->Unlock(lpd3dExecuteBuffer);

    return DD_OK;
}

/***********************************************************************/

/*
 * Create the elements making up the 3D scene.
 *
 * In this sample the scene consists of the single light, the viewport,
 * the background and surface material, the three transformation matrices
 * and the execute buffer holding the state changes and drawing primitives.
 */
static HRESULT
CreateScene(void)
{
    HRESULT              hRes;
    D3DMATERIAL          d3dMaterial;
    D3DLIGHT             d3dLight;
    DWORD                dwVertexSize;
    DWORD                dwInstructionSize;
    DWORD                dwExecuteBufferSize;
    D3DEXECUTEBUFFERDESC d3dExecuteBufferDesc;
    D3DEXECUTEDATA       d3dExecuteData;

    ASSERT(NULL != lpd3d);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL == lpd3dViewport);
    ASSERT(NULL == lpd3dMaterial);
    ASSERT(NULL == lpd3dBackgroundMaterial);
    ASSERT(NULL == lpd3dExecuteBuffer);
    ASSERT(NULL == lpd3dLight);
    ASSERT(0UL  == hd3dWorldMatrix);
    ASSERT(0UL  == hd3dViewMatrix);
    ASSERT(0UL  == hd3dProjMatrix);

    /*
     * Create the light.
     */
    hRes = lpd3d->lpVtbl->CreateLight(lpd3d, &lpd3dLight, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dLight, sizeof(d3dLight));
    d3dLight.dwSize = sizeof(d3dLight);
    d3dLight.dltType = D3DLIGHT_POINT;
    d3dLight.dcvColor.dvR    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvG    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvB    = D3DVAL( 1.0);
    d3dLight.dcvColor.dvA    = D3DVAL( 1.0);
    d3dLight.dvPosition.dvX  = D3DVAL( 1.0);
    d3dLight.dvPosition.dvY  = D3DVAL(-1.0);
    d3dLight.dvPosition.dvZ  = D3DVAL(-1.0);
    d3dLight.dvAttenuation0  = D3DVAL( 1.0);
    d3dLight.dvAttenuation1  = D3DVAL( 0.1);
    d3dLight.dvAttenuation2  = D3DVAL( 0.0);
    hRes = lpd3dLight->lpVtbl->SetLight(lpd3dLight, &d3dLight);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the background material.
     */
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, &lpd3dBackgroundMaterial, NULL);
    if (FAILED(hRes))
        return hRes;

    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(0.0);
    d3dMaterial.dvPower       = D3DVAL(0.0);

    /*
     * As this is the background material we don't want a ramp allocated (we
     * are not going to be smooth shading the background).
     */
    d3dMaterial.dwRampSize    = 1UL;

    hRes = lpd3dBackgroundMaterial->lpVtbl->SetMaterial(lpd3dBackgroundMaterial,
                                                        &d3dMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dBackgroundMaterial->lpVtbl->GetHandle(lpd3dBackgroundMaterial,
                                                      lpd3dDevice,
                                                      &hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the viewport.
     *
     * The actual viewport parameter are set in the function UpdateViewport
     * which is called in response to WM_SIZE.
     */
    hRes = lpd3d->lpVtbl->CreateViewport(lpd3d, &lpd3dViewport, NULL);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->AddViewport(lpd3dDevice, lpd3dViewport);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->SetBackground(lpd3dViewport, hd3dBackgroundMaterial);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dViewport->lpVtbl->AddLight(lpd3dViewport, lpd3dLight);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the matrices.
     */
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dWorldMatrix, &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dViewMatrix, &d3dViewMatrix);
    if (FAILED(hRes))
        return hRes;
    hRes = lpd3dDevice->lpVtbl->CreateMatrix(lpd3dDevice, &hd3dProjMatrix);
    if (FAILED(hRes))
        return hRes;
    SetPerspectiveProjection(&d3dProjMatrix, HALF_HEIGHT, FRONT_CLIP, BACK_CLIP);
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice, hd3dProjMatrix, &d3dProjMatrix);
    if (FAILED(hRes))
        return hRes;

    /*
     * Create the surface material.
     */
    hRes = lpd3d->lpVtbl->CreateMaterial(lpd3d, &lpd3dMaterial, NULL);
    if (FAILED(hRes))
        return hRes;
    ZeroMemory(&d3dMaterial, sizeof(d3dMaterial));
    d3dMaterial.dwSize = sizeof(d3dMaterial);

    /*
     * Base green with white specular.
     */
    d3dMaterial.dcvDiffuse.r  = D3DVAL(0.0);
    d3dMaterial.dcvDiffuse.g  = D3DVAL(1.0);
    d3dMaterial.dcvDiffuse.b  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.r  = D3DVAL(0.0);
    d3dMaterial.dcvAmbient.g  = D3DVAL(0.4);
    d3dMaterial.dcvAmbient.b  = D3DVAL(0.0);
    d3dMaterial.dcvSpecular.r = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.g = D3DVAL(1.0);
    d3dMaterial.dcvSpecular.b = D3DVAL(1.0);
    d3dMaterial.dvPower       = D3DVAL(20.0);
    d3dMaterial.dwRampSize    = 16UL;

    hRes = lpd3dMaterial->lpVtbl->SetMaterial(lpd3dMaterial, &d3dMaterial);
    if (FAILED(hRes))
        return hRes;

    hRes = lpd3dMaterial->lpVtbl->GetHandle(lpd3dMaterial, lpd3dDevice, &hd3dSurfaceMaterial);
    if (FAILED(hRes))
        return hRes;

    /*
     * Build the execute buffer.
     */
    dwVertexSize        = (NUM_VERTICES        * sizeof(D3DVERTEX));
    dwInstructionSize   = (NUM_INSTRUCTIONS    * sizeof(D3DINSTRUCTION))     +
                          (NUM_STATES          * sizeof(D3DSTATE))           +
                          (NUM_PROCESSVERTICES * sizeof(D3DPROCESSVERTICES)) +
                          (NUM_TRIANGLES       * sizeof(D3DTRIANGLE));
    dwExecuteBufferSize = dwVertexSize + dwInstructionSize;
    ZeroMemory(&d3dExecuteBufferDesc, sizeof(d3dExecuteBufferDesc));
    d3dExecuteBufferDesc.dwSize       = sizeof(d3dExecuteBufferDesc);
    d3dExecuteBufferDesc.dwFlags      = D3DDEB_BUFSIZE;
    d3dExecuteBufferDesc.dwBufferSize = dwExecuteBufferSize;
    hRes = lpd3dDevice->lpVtbl->CreateExecuteBuffer(lpd3dDevice,
                                                    &d3dExecuteBufferDesc,
                                                    &lpd3dExecuteBuffer,
                                                    NULL);
    if (FAILED(hRes))
        return hRes;

    /*
     * Fill the execute buffer with the required vertices, state
     * instructions and drawing primitives.
     */
    hRes = FillExecuteBuffer();
    if (FAILED(hRes))
        return hRes;

    /*
     * Set the execute data so Direct3D knows how many vertices are in the
     * buffer and where the instructions start.
     */
    ZeroMemory(&d3dExecuteData, sizeof(d3dExecuteData));
    d3dExecuteData.dwSize = sizeof(d3dExecuteData);
    d3dExecuteData.dwVertexCount       = NUM_VERTICES;
    d3dExecuteData.dwInstructionOffset = dwVertexSize;
    d3dExecuteData.dwInstructionLength = dwInstructionSize;
    hRes = lpd3dExecuteBuffer->lpVtbl->SetExecuteData(lpd3dExecuteBuffer, &d3dExecuteData);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Release all the objects comprising the 3D scene.
 */
static HRESULT
ReleaseScene(void)
{
    if (NULL != lpd3dExecuteBuffer)
    {
        lpd3dExecuteBuffer->lpVtbl->Release(lpd3dExecuteBuffer);
        lpd3dExecuteBuffer = NULL;
    }
    if (NULL != lpd3dBackgroundMaterial)
    {
        lpd3dBackgroundMaterial->lpVtbl->Release(lpd3dBackgroundMaterial);
        lpd3dBackgroundMaterial = NULL;
    }
    if (NULL != lpd3dMaterial)
    {
        lpd3dMaterial->lpVtbl->Release(lpd3dMaterial);
        lpd3dMaterial = NULL;
    }
    if (0UL != hd3dWorldMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dWorldMatrix);
        hd3dWorldMatrix = 0UL;
    }
    if (0UL != hd3dViewMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dViewMatrix);
        hd3dViewMatrix = 0UL;
    }
    if (0UL != hd3dProjMatrix)
    {
        lpd3dDevice->lpVtbl->DeleteMatrix(lpd3dDevice, hd3dProjMatrix);
        hd3dProjMatrix = 0UL;
    }
    if (NULL != lpd3dLight)
    {
        lpd3dLight->lpVtbl->Release(lpd3dLight);
        lpd3dLight = NULL;
    }
    if (NULL != lpd3dViewport)
    {
        lpd3dViewport->lpVtbl->Release(lpd3dViewport);
        lpd3dViewport = NULL;
    }

    return DD_OK;
}

/***********************************************************************/

/*
 * Animate the scene.
 *
 * The animation in this sample is simply a rotation about the Y axis.
 * So all we need to do is build a rotation matrix and set the world
 * matrix to that new rotation matrix.
 *
 * Note, we don't need to modify the execute buffer in any way to peform
 * this rotation. We simply set the matrix and resubmit the execute
 * buffer.
 */
static HRESULT
AnimateScene(void)
{
    HRESULT hRes;

    ASSERT(NULL != lpd3dDevice);
    ASSERT(0UL  != hd3dWorldMatrix);

    /*
     * We rotate the triangle by setting the world transform to a
     * rotation matrix.
     */
    SetRotationAboutY(&d3dWorldMatrix, dAngleOfRotation);
    dAngleOfRotation += ROTATE_ANGLE_DELTA;
    hRes = lpd3dDevice->lpVtbl->SetMatrix(lpd3dDevice,
                                          hd3dWorldMatrix,
                                          &d3dWorldMatrix);
    if (FAILED(hRes))
        return hRes;

    return DD_OK;
}

/***********************************************************************/

/*
 * Update the viewport in response to a change in window size. This
 * ensures that we render at a resolution which matches the client
 * area of the target window.
 */
static HRESULT
UpdateViewport(void)
{
    D3DVIEWPORT d3dViewport;

    ASSERT(NULL != lpd3dViewport);

    ZeroMemory(&d3dViewport, sizeof(d3dViewport));
    d3dViewport.dwSize   = sizeof(d3dViewport);
    d3dViewport.dwX      = 0UL;
    d3dViewport.dwY      = 0UL;
    d3dViewport.dwWidth  = (DWORD)rSrcRect.right;
    d3dViewport.dwHeight = (DWORD)rSrcRect.bottom;
    d3dViewport.dvScaleX = D3DVAL((float)d3dViewport.dwWidth / 2.0);
    d3dViewport.dvScaleY = D3DVAL((float)d3dViewport.dwHeight / 2.0);
    d3dViewport.dvMaxX   = D3DVAL(1.0);
    d3dViewport.dvMaxY   = D3DVAL(1.0);
    return lpd3dViewport->lpVtbl->SetViewport(lpd3dViewport, &d3dViewport);
}

/***********************************************************************/

/*
 * Render the 3D scene.
 *
 * Fundamentally this involved submitting our single execute buffer.
 * However, we also need to clear the back and z-buffers and demark
 * the start and end of the scene (which in this case is a single
 * execute).
 */
static HRESULT
RenderScene(void)
{
    HRESULT hRes;
    D3DRECT d3dRect;

    ASSERT(NULL != lpd3dViewport);
    ASSERT(NULL != lpd3dDevice);
    ASSERT(NULL != lpd3dExecuteBuffer);

    /*
     * Clear both back and z-buffer.
     *
     * NOTE: Its safe to specify the z-buffer clear flag even if we
     * don't have an attached z-buffer. Direct3D will simply discard
     * the flag if no z-buffer is being used.
     *
     * NOTE: For maximum efficiency we only want to clear those
     * regions of the device surface and z-buffer which we actually
     * rendered to in the last frame. This is the purpose of the
     * array of rectangles and count passed to this function. It is
     * possible to query Direct3D for the regions of the device
     * surface that were rendered to by that execute. The application
     * can then accumulate those rectangles and clear only those
     * regions. However this is a very simple sample and so, for
     * simplicity, we will just clear the entire device surface and
     * z-buffer. Probably not something you want to do in a real
     * application.
     */
    d3dRect.lX1 = rSrcRect.left;
    d3dRect.lX2 = rSrcRect.right;
    d3dRect.lY1 = rSrcRect.top;
    d3dRect.lY2 = rSrcRect.bottom;
    hRes = lpd3dViewport->lpVtbl->Clear(lpd3dViewport,
                                        1UL,
                                        &d3dRect,
                                        D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
    if (FAILED(hRes))
        return hRes;

    /*
     * Start the scene.
     *
     * This function must be called once and once only for every frame
     * of animation. If you have multiple execute buffers comprising a
     * single frame you must have one call to BeginScene() before
     * submitting those execute buffers.
     *
     * NOTE: If you have more than one device being rendered in a
     * single frame, say a rear view mirror in a racing game, call
     * BeginScene() and EndScene() once for each device.
     */
    hRes = lpd3dDevice->lpVtbl->BeginScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    /*
     * Submit the execute buffer.
     *
     * We want Direct3D to clip the data on our behalf so we specify
     * D3DEXECUTE_CLIPPED.
     */
    hRes = lpd3dDevice->lpVtbl->Execute(lpd3dDevice,
                                        lpd3dExecuteBuffer,
                                        lpd3dViewport,
                                        D3DEXECUTE_CLIPPED);
    if (FAILED(hRes))
    {
        lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
        return hRes;
    }

    /*
     * End the scene.
     */
    hRes = lpd3dDevice->lpVtbl->EndScene(lpd3dDevice);
    if (FAILED(hRes))
        return hRes;

    /*
     * At this point the scene will have been rendered and the device
     * surface will hold the contents of the rendering.
     */

    return DD_OK;
}

/***********************************************************************/

/*
 * Render and show a single frame.
 *
 * This involves rendering the scene and blitting the result to client
 * area of the application window on the primary surface.
 *
 * NOTE: This function handles lost surfaces by attempting to restore
 * the applications surfaces and then retrying the rendering.
 */
static HRESULT
DoFrame(HWND hwnd)
{
    HRESULT hRes;

    /*
     * We keeping trying until we succeed or we fail for a reason
     * other than DDERR_SURFACELOST.
     */
    while (TRUE)
    {
        hRes = RenderScene();
        if (SUCCEEDED(hRes))
        {
    POINT pt;
    RECT rTmp;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd, &pt );

    rTmp = rDstRect;

    // For multi-monitor systems, we need to translate
    // the Desktop coordinates into Device coordinates.
    pt.x -= rectMonitor.left;
    pt.y -= rectMonitor.top;
    OffsetRect(&rTmp, pt.x, pt.y);

            hRes = lpddPrimary->lpVtbl->Blt(lpddPrimary,
                                            &rTmp,
                                            lpddDevice,
                                            &rSrcRect,
                                            DDBLT_WAIT,
                                            NULL);
            if (SUCCEEDED(hRes))
                /*
                 * It worked. Bail.
                 */
                return hRes;
        }
        while (DDERR_SURFACELOST == hRes)

/*
             * The surfaces are lost. Restore them.
             */
            hRes = RestoreSurfaces();
        if (FAILED(hRes))
            /*
             * Something went wrong and it wasn't DDERR_SURFACELOST.
             */
            return hRes;
    }
}

/***********************************************************************/

/*
 * The application suspends when in the background or when handling and
 * error. We signal this fact by drawing a notification string in the
 * client area of the window.
 */
static void
PaintSuspended(HWND hwnd, HDC hdc)
{
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    COLORREF crOldTextColor;
    int      oldMode;
    int      x;
    int      y;
    SIZE     size;
    RECT     rect;
    int      nStrLen;

    /*
     * Black background.
     */
    hOldPen   = SelectObject(hdc, GetStockObject(NULL_PEN));
    hOldBrush = SelectObject(hdc, GetStockObject(BLACK_BRUSH));

    /*
     * White text.
     */
    oldMode = SetBkMode(hdc, TRANSPARENT);
    crOldTextColor = SetTextColor(hdc, RGB(255, 255, 255));

    GetClientRect(hwnd, &rect);

    /*
     * Clear the client area.
     */
    Rectangle(hdc, rect.left, rect.top, rect.right + 1, rect.bottom + 1);

    /*
     * Draw the string centered in the client area.
     */
    nStrLen = strlen(PAUSED_STRING);
    GetTextExtentPoint32(hdc, PAUSED_STRING, nStrLen, &size);
    x = (rect.right  - size.cx) / 2;
    y = (rect.bottom - size.cy) / 2;
    TextOut(hdc, x, y, PAUSED_STRING, nStrLen);

    SetTextColor(hdc, crOldTextColor);
    SetBkMode(hdc, oldMode);

    SelectObject(hdc, hOldBrush);
    SelectObject(hdc, hOldPen);
}

/***********************************************************************/

static LRESULT
OnMove(HWND hwnd, int x, int y)
{
    HRESULT hRes;

    hRes = CheckMonitors(hwnd, FALSE);
    if (FAILED(hRes))
    {
return 0L;
    }

    /*
     * No action if the device has not yet been created or if we are
     * suspended.
     */
    if ((NULL != lpd3dDevice) && !fSuspended)
    {
        /*
         * Repaint the client area.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/
#define FORCE_RESET TRUE

static HRESULT
CheckMonitors(HWND hwnd, BOOL fReset)
{
    HRESULT     hRes;
    char        szBuffer[128];

    if (!fReset && hMonitor == DirectDrawDeviceFromWindow(hwnd, NULL, NULL))
return S_OK;

    // If we do, then first release what we have so far
    ReleaseScene();
    ReleaseDevice();
    ReleasePrimary();
    ReleaseDirect3D();

    // Update our monitor information
    hMonitor = DirectDrawDeviceFromWindow(hwnd, szMonitor, &rectMonitor);

    // Now start from scratch
    hRes = CreateDirect3D(hwnd);
    if (FAILED(hRes))
    {
        ReportError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
        ReleaseDirect3D();
        return -1L;
    }

    hRes = CreatePrimary(hwnd);
    if (FAILED(hRes))
    {
        ReportError(hwnd, IDS_ERRMSG_INITSCREEN, hRes);
        ReleasePrimary();
        ReleaseDirect3D();
        return -1L;
    }

    hRes = ChooseDevice();
    if (FAILED(hRes))
    {
        ReportError(hwnd, IDS_ERRMSG_NODEVICE, hRes);
        ReleasePrimary();
        ReleaseDirect3D();
        return -1L;
    }

    hRes = CreateDevice((DWORD)rSrcRect.right, (DWORD)rSrcRect.bottom);
    if (FAILED(hRes))
    {
        FatalError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
ReleaseDevice();
        ReleasePrimary();
        ReleaseDirect3D();
        return 0L;
    }
    hRes = CreateScene();
    if (FAILED(hRes))
    {
        FatalError(hwnd, IDS_ERRMSG_BUILDSCENE, hRes);
ReleaseDevice();
        ReleasePrimary();
        ReleaseDirect3D();
        return 0L;
    }

    hRes = UpdateViewport();
    if (FAILED(hRes))
    {
        FatalError(hwnd, IDS_ERRMSG_UPDATEVIEWPORT, hRes);
ReleaseScene();
ReleaseDevice();
        ReleasePrimary();
        ReleaseDirect3D();
        return 0L;
    }

    /*
     * Update the title to show the name of the chosen device.
     */
    wsprintf(szBuffer, "%s: %s", WINDOW_TITLE, szDeviceName);
    SetWindowText(hwnd, szBuffer);

    return 0L;
}

/***********************************************************************/

/***********************************************************************/

static LRESULT
OnSize(HWND hwnd, int w, int h)
{
    HRESULT       hRes;
    DDSURFACEDESC ddsd;

    // Check if we need to update our monitor information
    // This can happen if our window is being sized onto
    // another monitor.
    hRes = CheckMonitors(hwnd, FALSE);
    if (FAILED(hRes))
    {
return 0L;
    }

    /*
     * Nothing to do if we are suspended.
     */
    if (!fSuspended)
    {
        /*
         * Update the source and destination rectangles (used by the
         * blit which shows the rendering in the client area).
         */
        rDstRect.right  = rDstRect.left + w;
        rDstRect.bottom = rDstRect.top  + h;

// If the window is sized to something larger than our initial size
// then just use DDraw stretch. If it is something smaller
// then reduce the complexity for Direct3D since the stretch
// will just drop pixels anyhow.
        rSrcRect.right  = min(w, WINDOW_WIDTH);
        rSrcRect.bottom = min(h, WINDOW_HEIGHT);

        if (NULL != lpd3dDevice)
        {
            /*
             * We already have a device. But is it big enough for the the
             * new window client size?
             *
             * NOTE: As this window is fixed size we should not ever be
             * end up being resized. But just in case we will handle it.
             * This will be useful when we make the application resizable.
             */
            ZeroMemory(&ddsd, sizeof(ddsd));
            ddsd.dwSize = sizeof(ddsd);
            hRes = lpddDevice->lpVtbl->GetSurfaceDesc(lpddDevice, &ddsd);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_DEVICESIZE, hRes);
                return 0L;
            }

            if ((w > (int)ddsd.dwWidth) || (h > (int)ddsd.dwHeight))
            {
                /*
                 * Nope, the device is too small. We need to shut it down
                 * and rebuild it.
                 */

                /*
                 * Execute buffers are bound to devices so when we release
                 * the device we must release the execute buffer.
                 */
                ReleaseScene();
                ReleaseDevice();
            }
        }

        if (NULL == lpd3dDevice)
        {
            /*
             * No Direct3D device yet. This is either because this is the
             * first time through the loop or because we discarded the
             * existing device because it was not big enough for the new
             * window client size.
             */
            hRes = CreateDevice((DWORD)w, (DWORD)h);
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_CREATEDEVICE, hRes);
                return 0L;
            }
            hRes = CreateScene();
            if (FAILED(hRes))
            {
                FatalError(hwnd, IDS_ERRMSG_BUILDSCENE, hRes);
                return 0L;
            }
        }

        hRes = UpdateViewport();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_UPDATEVIEWPORT, hRes);
            return 0L;
        }

        /*
         * Render at the new size and show the results in the window's
         * client area.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/

static LRESULT
OnPaint(HWND hwnd, HDC hdc, LPPAINTSTRUCT lpps)
{
    HRESULT hRes;

    USE_PARAM(lpps);

    if (!fSuspended && (NULL != lpd3dDevice))
    {
        /*
         * NOTE: DoFrame() re-renders the scene as well as blitting the
         * result to the primary. As all we really want to do here is
         * repaint the client area we don't really need to re-render -
         * just re-blit. For this simple sample this inefficiency
         * doesn't matter but for real applications not re-rendering
         * may be a useful optimization.
         */
        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }
    else
    {
        /*
         * Show the suspended image if we are not active, or suspended or
         * if we have not yet created the device.
         */
        PaintSuspended(hwnd, hdc);
    }

    return 0L;
}

/***********************************************************************/

static LRESULT
OnIdle(HWND hwnd)
{
    HRESULT hRes;

    /*
     * Only animate if we aren't suspended
     * and we have completed initialization.
     */
    if ( !fSuspended && (NULL != lpd3dDevice))
    {
        hRes = AnimateScene();
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_ANIMATESCENE, hRes);
            return 0L;
        }

        hRes = DoFrame(hwnd);
        if (FAILED(hRes))
        {
            FatalError(hwnd, IDS_ERRMSG_RENDERSCENE, hRes);
            return 0L;
        }
    }

    return 0L;
}

/***********************************************************************/

LRESULT CALLBACK
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    LRESULT     lResult;
    HRESULT     hRes;

    switch (msg)
    {
        case WM_CREATE:

    hRes = CheckMonitors(hwnd, FORCE_RESET);
    if (FAILED(hRes))
return -1;

            return 0L;

        case WM_MOVE:
            return OnMove(hwnd, (int)(signed short)LOWORD(lParam), (int)(signed short)HIWORD(lParam));

        case WM_SIZE:
            return OnSize(hwnd, (int)LOWORD(lParam), (int)HIWORD(lParam));

        case WM_ERASEBKGND:
            /*
             * Our rendering fills the entire viewport so we won't bother
             * erasing the background.
             */
            return 1L;

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

            lResult = OnPaint(hwnd, hdc, &ps);

            EndPaint(hwnd, &ps);
            return lResult;

        case WM_ACTIVATEAPP:
            fActive = (BOOL)wParam;
            if (fActive && !fSuspended && (NULL != lpddPalette))
            {
                /*
                 * Realizing the palette using DirectDraw is quite different
                 * from GDI. To realize the palette we call SetPalette()
                 * each time our application is activated.
                 *
                 * NOTE: DirectDraw spots the fact that the new palette is the
                 * same as the old one and so does not increase the reference
                 * count of the palette.
                 */
                hRes = lpddPrimary->lpVtbl->SetPalette(lpddPrimary, lpddPalette);
                if (FAILED(hRes))
                {
                    FatalError(hwnd, IDS_ERRMSG_REALIZEPALETTE, hRes);
                    return 0L;
                }

            }
            else
            {
                /*
                 * If we have been deactived invalidate to show the suspended
                 * display.
                 */
                InvalidateRect(hwnd, NULL, FALSE);
            }
            return 0L;

        case WM_KEYUP:
            /*
             * We use the escape key as a quick way of getting out of the
             * application.
             */
            if (VK_ESCAPE == (int)wParam)
            {
                DestroyWindow(hwnd);
                return 0L;
            }
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0L;

        case WM_DESTROY:
            /*
             * All cleanup is done here when terminating normally or
             * shutting down due to an error.
             */
            ReleaseScene();
            ReleaseDevice();
            ReleasePrimary();
            ReleaseDirect3D();

            PostQuitMessage(0);
            return 0L;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/***********************************************************************/

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCommandLine,
        int       cmdShow)
{
    WNDCLASS wndClass;
    HWND     hwnd;
    MSG      msg;

    USE_PARAM(hPrevInstance);

    /*
     * Record the instance handle.
     */
    hAppInstance = hInstance;

    /*
     * Very, very primitive command line processing. We only have one
     * option, debug so we will just assume that if anything was
     * specified on the command line that means debug mode (no hardware
     * all surfaces explicitly in system memory).
     */
    if (0 != *lpszCommandLine)
        fDebug = TRUE;

    /*
     * Register the window class.
     */
    wndClass.style         = 0;
    wndClass.lpfnWndProc   = WndProc;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = 0;
    wndClass.hInstance     = hInstance;
    wndClass.hIcon         = LoadIcon(hAppInstance, MAKEINTRESOURCE(IDI_APPICON));
    wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = WINDOW_CLASSNAME;

    RegisterClass(&wndClass);

    /*
     * Create the main window of the instance.
     */
    hwnd = CreateWindow(WINDOW_CLASSNAME,
                        WINDOW_TITLE,
                        WS_OVERLAPPED | WS_SYSMENU | WS_SIZEBOX,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        WINDOW_WIDTH, WINDOW_HEIGHT,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);

    ShowWindow(hwnd, cmdShow);
    UpdateWindow(hwnd);

    /*
     * The main message dispatch loop.
     *
     * NOTE: For simplicity we handle the message loop with a
     * simple PeekMessage scheme. This might not be the best
     * mechanism for a real application (a separate render worker
     * thread might be better).
     */
    while (TRUE)
    {
        if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
        {
            /*
             * Message pending. If its QUIT then exit the message
             * loop. Otherwise, process the message.
             */
            if (WM_QUIT == msg.message)
            {
                break;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            /*
             * Animate the scene.
             */
            OnIdle(hwnd);
        }
    }

    return msg.wParam;
}

/***********************************************************************/

/***********************************************************************
 *
 * End of file
 *
 ***********************************************************************/


README.TXT

DirectDraw example program 1.

Creates a DirectDraw object and then a primary surface with a back buffer.
Slowly flips between the primary surface and the back buffer.


DDEX1.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddex1.cpp
 *  Content:    Direct Draw example program 1.  Creates a Direct Draw
 *              object and then a primary surface with a back buffer.
 *              Slowly flips between the primary surface and the back
 *              buffer.  Press F12 to terminate the program.
 *
 ***************************************************************************/

#define NAME "DDExample1"
#define TITLE "Direct Draw Example 1"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <stdlib.h>
#include <stdarg.h>
#include "resource.h"

#define TIMER_ID        1
#define TIMER_RATE      500

LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSBack;      // DirectDraw back surface
BOOL                    bActive;        // is application active?

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
lpDD->Release();
lpDD = NULL;
    }
} /* finiObjects */

char szMsg[] = "Page Flipping Test: Press F12 to exit";
char szFrontMsg[] = "Front buffer (F12 to quit)";
char szBackMsg[] = "Back buffer (F12 to quit)";

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    RECT        rc;
    SIZE        size;
    static BYTE phase = 0;
    HDC         hdc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
bActive = wParam;
break;

    case WM_CREATE:
break;

    case WM_SETCURSOR:
SetCursor(NULL);
return TRUE;

    case WM_TIMER:
// Flip surfaces
if( bActive )
{
    if (lpDDSBack->GetDC(&hdc) == DD_OK)
    {
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
if( phase )
{
    TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
    phase = 0;
}
else
{
    TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) );
    phase = 1;
}
lpDDSBack->ReleaseDC(hdc);
    }

    while( 1 )
    {
HRESULT ddrval;
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = lpDDSPrimary->Restore();
    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }
}
break;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
case VK_F12:
    PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_PAINT:
BeginPaint( hWnd, &ps );
GetClientRect(hWnd, &rc);
GetTextExtentPoint( ps.hdc, szMsg, lstrlen(szMsg), &size );
SetBkColor( ps.hdc, RGB( 0, 0, 255 ) );
SetTextColor( ps.hdc, RGB( 255, 255, 0 ) );
TextOut( ps.hdc, (rc.right - size.cx)/2, (rc.bottom - size.cy)/2,
    szMsg, sizeof( szMsg )-1 );
EndPaint( hWnd, &ps );
break;

    case WM_DESTROY:
finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWND                hwnd;
    WNDCLASS            wc;
    DDSURFACEDESC       ddsd;
    DDSCAPS             ddscaps;
    HRESULT             ddrval;
    HDC                 hdc;
    char                buf[256];

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
WS_EX_TOPMOST,
NAME,
TITLE,
WS_POPUP,
0, 0,
GetSystemMetrics( SM_CXSCREEN ),
GetSystemMetrics( SM_CYSCREEN ),
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval == DD_OK )
    {
// Get exclusive mode
ddrval = lpDD->SetCooperativeLevel( hwnd,
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
if(ddrval == DD_OK )
{
    ddrval = lpDD->SetDisplayMode( 640, 480, 8 );
    if( ddrval == DD_OK )
    {
// Create the primary surface with 1 back buffer
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
      DDSCAPS_FLIP |
      DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
if( ddrval == DD_OK )
{
    // Get a pointer to the back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
  &lpDDSBack);
    if( ddrval == DD_OK )
    {
// draw some text.
if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
{
    SetBkColor( hdc, RGB( 0, 0, 255 ) );
    SetTextColor( hdc, RGB( 255, 255, 0 ) );
    TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
    lpDDSPrimary->ReleaseDC(hdc);
}

if (lpDDSBack->GetDC(&hdc) == DD_OK)
{
    SetBkColor( hdc, RGB( 0, 0, 255 ) );
    SetTextColor( hdc, RGB( 255, 255, 0 ) );
    TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) );
    lpDDSBack->ReleaseDC(hdc);
}

// Create a timer to flip the pages
if( SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) )
{
     return TRUE;
}
    }
}
    }
}
    }

    wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval );
    MessageBox( hwnd, buf, "ERROR", MB_OK );
    finiObjects();
    DestroyWindow( hwnd );
    return FALSE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;

    lpCmdLine = lpCmdLine;
    hPrevInstance = hPrevInstance;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while (GetMessage(&msg, NULL, 0, 0))
    {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }

    return msg.wParam;

} /* WinMain */


RESOURCE.H

#define IDR_MENU                        102
#define IDM_EXIT                        40001


README.TXT

DirectDraw example program 2.

Adds functionality to example program 1.
Changes the video mode to 640x480x8. Reads a bitmap file from disk and
copies it into the back buffer and then slowly flips between the primary
surface and the back buffer.


DDEX2.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddex2.cpp
 *  Content:    Direct Draw example program 2.  Adds functionality to
 *              example program 1.  Changes the video mode to 640x480x8.
 *              Reads a bitmap file from disk and copies it into the
 *              back buffer and then slowly flips between the primary
 *              surface and the back buffer.  Press F12 to exit the program.
 *
 ***************************************************************************/

#define NAME "DDExample2"
#define TITLE "Direct Draw Example 2"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <stdlib.h>
#include <stdarg.h>
#include "resource.h"
#include "ddutil.h"

#define TIMER_ID        1
#define TIMER_RATE      250

LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSBack;      // DirectDraw back surface
LPDIRECTDRAWPALETTE     lpDDPal;        // DirectDraw palette
BOOL                    bActive;        // is application active?

char szBackground[] = "BACK";

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }

} /* finiObjects */

char szFrontMsg[] = "Front buffer (F12 to quit)";
char szBackMsg[] = "Back buffer (F12 to quit)";

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    static BYTE phase = 0;
    HDC         hdc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
bActive = wParam;
break;

    case WM_CREATE:
break;

    case WM_TIMER:
// Flip surfaces
if( bActive )
{
    if (lpDDSBack->GetDC(&hdc) == DD_OK)
    {
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
if( phase )
{
    TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
    phase = 0;
}
else
{
    TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) );
    phase = 1;
}
lpDDSBack->ReleaseDC(hdc);
    }
    while( 1 )
    {
HRESULT ddrval;
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = lpDDSPrimary->Restore();

    if( ddrval != DD_OK )
    {
break;
    }

    ddrval = DDReLoadBitmap(lpDDSBack, szBackground);

    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }
}
break;

    case WM_SETCURSOR:
SetCursor(NULL);
return TRUE;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
case VK_F12:
    DestroyWindow( hWnd );
    return 0;
}
break;

    case WM_DESTROY:
finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWND                hwnd;
    WNDCLASS            wc;
    DDSURFACEDESC       ddsd;
    DDSCAPS             ddscaps;
    HRESULT             ddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
WS_EX_TOPMOST,
NAME,
TITLE,
WS_POPUP,
0,
0,
GetSystemMetrics( SM_CXSCREEN ),
GetSystemMetrics( SM_CYSCREEN ),
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
goto error;

    // Get exclusive mode
    ddrval = lpDD->SetCooperativeLevel( hwnd,
    DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if(ddrval != DD_OK )
goto error;

    // Set the video mode to 640x480x8
    ddrval = lpDD->SetDisplayMode( 640, 480, 8);
    if(ddrval != DD_OK)
goto error;

    // Create the primary surface with 1 back buffer
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
goto error;

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);

    if( ddrval != DD_OK )
goto error;

    lpDDPal = DDLoadPalette(lpDD, szBackground);

    if (lpDDPal == NULL)
goto error;

    ddrval = lpDDSPrimary->SetPalette(lpDDPal);

    if( ddrval != DD_OK )
goto error;

    // load a bitmap into the back buffer.
    ddrval = DDReLoadBitmap(lpDDSBack, szBackground);

    if( ddrval != DD_OK )
goto error;

    // Create a timer to flip the pages
    if( !SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) )
goto error;

    return TRUE;

error:
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;

    lpCmdLine = lpCmdLine;
    hPrevInstance = hPrevInstance;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while(GetMessage(&msg, NULL, 0, 0))
    {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }

    return msg.wParam;

} /* WinMain */


README.TXT

DirectDraw example program 3.

Adds functionality to example program 2.  Creates two offscreen surfaces in
addition to the primary surface and back buffer.  Loads a bitmap file into
each offscreen surface.  Uses BltFast to copy the contents of an offscreen
surface to the back buffer and then flips the buffers and copies the next
offscreen surface to the back buffer.

This program requires at least 1.2 Megs of video ram.


DDEX3.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddex3.cpp
 *  Content:    Direct Draw example program 3.  Adds functionality to
 *              example program 2.  Creates two offscreen surfaces in
 *              addition to the primary surface and back buffer.  Loads
 *              a bitmap file into each offscreen surface.  Uses BltFast
 *              to copy the contents of an offscreen surface to the back
 *              buffer and then flips the buffers and copies the next
 *              offscreen surface to the back buffer.  Press F12 to exit
 *              the program.  This program requires at least 1.2 Megs of
 *              video ram.
 *
 ***************************************************************************/

#define NAME "DDExample3"
#define TITLE "Direct Draw Example 3"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <stdlib.h>
#include <stdarg.h>
#include "resource.h"
#include "ddutil.h"         // DDLoadPalette, DDCopyBitmap

// Name of our bitmap resource.
char szBitmap[] = "DDEX3";

BOOL InitSurfaces( void );

#define TIMER_ID        1
#define TIMER_RATE      500

LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSBack;      // DirectDraw back surface
LPDIRECTDRAWSURFACE     lpDDSOne;       // Offscreen surface 1
LPDIRECTDRAWSURFACE     lpDDSTwo;       // Offscreen surface 2
LPDIRECTDRAWPALETTE     lpDDPal;        // DirectDraw palette
BOOL                    bActive;        // is application active?

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
    lpDDSOne->Release();
    lpDDSOne = NULL;
}
if( lpDDSTwo != NULL )
{
    lpDDSTwo->Release();
    lpDDSTwo = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }
} /* finiObjects */

/*
 * restoreAll
 *
 * restore all lost objects
 */
HRESULT restoreAll( void )
{
    HRESULT     ddrval;

    ddrval = lpDDSPrimary->Restore();
    if( ddrval == DD_OK )
    {
ddrval = lpDDSOne->Restore();
if( ddrval == DD_OK )
{
    ddrval = lpDDSTwo->Restore();
    if( ddrval == DD_OK )
    {
InitSurfaces();
    }
}
    }
    return ddrval;

} /* restoreAll */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    static  int phase = 0;
    RECT        rcRect;

    switch( message )
    {
    case WM_ACTIVATEAPP:
bActive = wParam;
break;

    case WM_CREATE:
break;

    case WM_SETCURSOR:
SetCursor(NULL);
return TRUE;

    case WM_TIMER:
if( bActive )
{
    HRESULT             ddrval;
    LPDIRECTDRAWSURFACE pdds;

    rcRect.left = 0;
    rcRect.top = 0;
    rcRect.right = 640;
    rcRect.bottom = 480;
    if(phase)
    {
pdds = lpDDSTwo;
phase = 0;
    }
    else
    {
pdds = lpDDSOne;
phase = 1;
    }
    while( 1 )
    {
ddrval = lpDDSBack->BltFast( 0, 0, pdds, &rcRect, FALSE );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }

    // Flip surfaces
    while( 1 )
    {
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }
}
break;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
case VK_F12:
    PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWND                hwnd;
    WNDCLASS            wc;
    DDSURFACEDESC       ddsd;
    DDSCAPS             ddscaps;
    HRESULT             ddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
0,
NAME,
TITLE,
WS_POPUP,
0,
0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Get exclusive mode
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Set the video mode to 640x480x8
    ddrval = lpDD->SetDisplayMode( 640, 480, 8);
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create the primary surface with 1 back buffer
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create a offscreen bitmap.
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = 480;
    ddsd.dwWidth = 640;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSOne, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create another offscreen bitmap.
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSTwo, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create a Direct Draw Palette and associate it with the front buffer
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    if (lpDDPal)
lpDDSPrimary->SetPalette( lpDDPal );

    if( !InitSurfaces() )
    {
return initFail(hwnd);
    }

    // Create a timer to flip the pages
    if( !SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) )
    {
return initFail(hwnd);
    }

    return TRUE;

} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;

    lpCmdLine = lpCmdLine;
    hPrevInstance = hPrevInstance;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( GetMessage( &msg, NULL, 0, 0 ) )
    {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }

    return msg.wParam;

} /* WinMain */

/*
 * InitSurfaces - This function reads the bitmap file FRNTBACK.BMP
 * and stores half of it in offscreen surface 1 and the other half in
 * offscreen surface 2.
 */
BOOL InitSurfaces( void )
{
    HBITMAP hbm;

    // Load our bitmap resource.
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (hbm == NULL)
return FALSE;

    DDCopyBitmap(lpDDSOne, hbm, 0, 0,   640, 480);
    DDCopyBitmap(lpDDSTwo, hbm, 0, 480, 640, 480);
    DeleteObject(hbm);

    return TRUE;

} /* readBMPIntoSurfaces */


README.TXT

Direct Draw example program 4.

Adds functionality to example program 3.  Creates a flipping surface and
loads a bitmap image into an offscreen surface.  Uses BltFast to copy
portions of the offscreen surface to the back buffer to generate an
animation.

Illustrates watching the return code from BltFast to prevent tearing.

This program requires 1.2 Meg of video ram.


DDEX4.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:ddex4.cpp
 *  Content:Direct Draw example program 4.Adds functionality to
 *example program 3.  Creates a flipping surface and loads
 *a bitmap image into an offscreen surface.  Uses BltFast to
 *copy portions of the offscreen surface to the back buffer
 *to generate an animation.  Illustrates watching return
 *code from BltFast to prevent image tearing.  This program
 *requires 1.2 Meg of video ram.
 *
 ***************************************************************************/

#define NAME "DDExample4"
#define TITLE "Direct Draw Example 4"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil.h"

char szBitmap[] = "ALL";

LPDIRECTDRAWlpDD;// DirectDraw object
LPDIRECTDRAWSURFACElpDDSPrimary;// DirectDraw primary surface
LPDIRECTDRAWSURFACElpDDSBack;// DirectDraw back surface
LPDIRECTDRAWSURFACElpDDSOne;// Offscreen surface 1
LPDIRECTDRAWPALETTElpDDPal;// DirectDraw palette
BOOLbActive;// is application active?

/*
 * restoreAll
 *
 * restore all lost objects
 */
HRESULT restoreAll( void )
{
    HRESULTddrval;

    ddrval = lpDDSPrimary->Restore();
    if( ddrval == DD_OK )
    {
ddrval = lpDDSOne->Restore();
if( ddrval == DD_OK )
{
            DDReLoadBitmap(lpDDSOne, szBitmap);
}
    }
    return ddrval;

} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORDlastTickCount[3] = {0,0,0};
    static intcurrentFrame[3] = {0,0,0};
    DWORDthisTickCount;
    RECTrcRect;
    DWORDdelay[3] = {50, 78, 13};
    inti;
    intxpos[3] = {288, 190, 416};
    intypos[3] = {128, 300, 256};
    HRESULTddrval;

    // Decide which frame will be blitted next
    thisTickCount = GetTickCount();
    for(i=0; i<3; i++)
    {
if((thisTickCount - lastTickCount[i]) > delay[i])
{
    // Move to next frame;
    lastTickCount[i] = thisTickCount;
    currentFrame[i]++;
    if(currentFrame[i] > 59)
    {
currentFrame[i] = 0;
    }
}
    }

    // Blit the stuff for the next frame
    rcRect.left = 0;
    rcRect.top = 0;
    rcRect.right = 640;
    rcRect.bottom = 480;
    while( 1 )
    {
        ddrval = lpDDSBack->BltFast( 0, 0, lpDDSOne,
            &rcRect, DDBLTFAST_NOCOLORKEY );

if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }
    if(ddrval != DD_OK)
    {
return;
    }

    for( i=0; i<3; i++ )
    {
        rcRect.left   = currentFrame[i]%10*64;
        rcRect.top    = currentFrame[i]/10*64 + 480;
        rcRect.right  = currentFrame[i]%10*64 + 64;
        rcRect.bottom = currentFrame[i]/10*64 + 64 + 480;

while( 1 )
{
    ddrval = lpDDSBack->BltFast( xpos[i], ypos[i], lpDDSOne,
                                 &rcRect, DDBLTFAST_SRCCOLORKEY );
    if( ddrval == DD_OK )
    {
break;
    }
    if( ddrval == DDERR_SURFACELOST )
    {
ddrval = restoreAll();
if( ddrval != DD_OK )
{
    return;
}
    }
    if( ddrval != DDERR_WASSTILLDRAWING )
    {
return;
    }
}
    }

    // Flip the surfaces
    while( 1 )
    {
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }

} /* updateFrame */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
    lpDDSOne->Release();
    lpDDSOne = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }
} /* finiObjects */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
    case WM_ACTIVATEAPP:
    bActive = wParam;
break;

    case WM_SETCURSOR:
        SetCursor(NULL);
        return TRUE;

    case WM_CREATE:
break;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
    finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWNDhwnd;
    WNDCLASSwc;
    DDSURFACEDESCddsd;
    DDSCAPSddscaps;
    HRESULTddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockBrush(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
        0,
NAME,
TITLE,
WS_POPUP,
0,
0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Get exclusive mode
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Set the video mode to 640x480x8
    ddrval = lpDD->SetDisplayMode( 640, 480, 8);
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create the primary surface with 1 back buffer
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // create and set the palette
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    if (lpDDPal)
        lpDDSPrimary->SetPalette(lpDDPal);

    // Create the offscreen surface, by loading our bitmap.
    lpDDSOne = DDLoadBitmap(lpDD, szBitmap, 0, 0);

    if( lpDDSOne == NULL )
    {
return initFail(hwnd);
    }

    // Set the color key for this bitmap (black)
    DDSetColorKey(lpDDSOne, RGB(0,0,0));

    return TRUE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSGmsg;

    lpCmdLine = lpCmdLine;
    hPrevInstance = hPrevInstance;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
else if( bActive )
{
    updateFrame();
        }
        else
        {
            // make sure we go to sleep if we have nothing else to do
            WaitMessage();
        }
    }
} /* WinMain */


README.TXT

Direct Draw example program 5.

Adds functionality to example program 4.  Uses GetEntries() to read a palette,
modifies the entries, and then uses SetEntries() to update the palette.

This program requires 1.2 Meg of video ram.


DDEX5.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:ddex5.cpp
 *  Content:Direct Draw example program 5.Adds functionality to
 *example program 4.  Uses GetEntries() to read a palette,
 *modifies the entries, and then uses SetEntries() to update
 *the palette.  This program requires 1.2 Meg of video ram.
 *
 ***************************************************************************/

#define NAME "DDExample5"
#define TITLE "Direct Draw Example 5"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil.h"

char szBitmap[] = "ALL";

LPDIRECTDRAWlpDD;// DirectDraw object
LPDIRECTDRAWSURFACElpDDSPrimary;// DirectDraw primary surface
LPDIRECTDRAWSURFACElpDDSBack;// DirectDraw back surface
LPDIRECTDRAWSURFACElpDDSOne;// Offscreen surface 1
LPDIRECTDRAWPALETTElpDDPal;// The primary surface palette
BOOLbActive;// is application active?

BYTE torusColors[256];// Marks the colors used in the torus

/*
 * restoreAll
 *
 * restore all lost objects
 */
HRESULT restoreAll( void )
{
    HRESULTddrval;
    DDSURFACEDESCddsd;

    ddrval = lpDDSPrimary->Restore();
    if( ddrval == DD_OK )
    {
ddrval = lpDDSOne->Restore();
if( ddrval == DD_OK )
        {
            DDReLoadBitmap(lpDDSOne, szBitmap);
}
    }

    //Loose the old palette
    lpDDPal->Release();
    // create and set the palette (restart cycling from the same place)
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    if (lpDDPal)
        lpDDSPrimary->SetPalette(lpDDPal);
    //
    // Mark the colors used in the torus frames
    //
    int i,x,y;

    // First, set all colors as unused
    for(i=0; i<256; i++)
    {
        torusColors[i] = 0;
    }

    // lock the surface and scan the lower part (the torus area)
    // and remember all the index's we find.
    ddsd.dwSize = sizeof(ddsd);
    while (lpDDSOne->Lock(NULL, &ddsd, 0, NULL) == DDERR_WASSTILLDRAWING)
        ;

    // Now search through the torus frames and mark used colors
    for( y=480; y<480+384; y++ )
    {
        for( x=0; x<640; x++ )
        {
            torusColors[((BYTE *)ddsd.lpSurface)[y*ddsd.lPitch+x]] = 1;
        }
    }

    lpDDSOne->Unlock(NULL);

    return ddrval;

} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORDlastTickCount[4] = {0,0,0,0};
    static intcurrentFrame[3] = {0,0,0};
    DWORDthisTickCount;
    RECTrcRect;
    DWORDdelay[4] = {50, 78, 13, 93};
    inti;
    intxpos[3] = {288, 190, 416};
    intypos[3] = {128, 300, 256};
    PALETTEENTRYpe[256];
    HRESULTddrval;

    // Decide which frame will be blitted next
    thisTickCount = GetTickCount();
    for(i=0; i<3; i++)
    {
if((thisTickCount - lastTickCount[i]) > delay[i])
{
    // Move to next frame;
    lastTickCount[i] = thisTickCount;
    currentFrame[i]++;
    if(currentFrame[i] > 59)
currentFrame[i] = 0;
}
    }

    // Blit the stuff for the next frame
    rcRect.left = 0;
    rcRect.top = 0;
    rcRect.right = 640;
    rcRect.bottom = 480;
    while( 1 )
    {
        ddrval = lpDDSBack->BltFast( 0, 0, lpDDSOne,
            &rcRect, DDBLTFAST_NOCOLORKEY );

if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }
    if(ddrval != DD_OK)
    {
    return;
    }

    for(i=0; i<3; i++)
    {
        rcRect.left   = currentFrame[i]%10*64;
        rcRect.top    = currentFrame[i]/10*64 + 480;
        rcRect.right  = currentFrame[i]%10*64 + 64;
        rcRect.bottom = currentFrame[i]/10*64 + 64 + 480;

while( 1 )
{
    ddrval = lpDDSBack->BltFast( xpos[i], ypos[i], lpDDSOne,
                                 &rcRect, DDBLTFAST_SRCCOLORKEY );

    if( ddrval == DD_OK )
    {
break;
    }
    if( ddrval == DDERR_SURFACELOST )
    {
ddrval = restoreAll();
if( ddrval != DD_OK )
{
    return;
}
    }
    if( ddrval != DDERR_WASSTILLDRAWING )
    {
return;
    }
}
    }

    if( (thisTickCount - lastTickCount[3]) > delay[3] )
    {
// Change the palette
if(lpDDPal->GetEntries( 0, 0, 256, pe ) != DD_OK)
{
    return;
}

for(i=1; i<256; i++)
{
    if(!torusColors[i])
    {
continue;
    }
    pe[i].peRed = (pe[i].peRed+2) % 256;
    pe[i].peGreen = (pe[i].peGreen+1) % 256;
    pe[i].peBlue = (pe[i].peBlue+3) % 256;
}
if(lpDDPal->SetEntries( 0, 0, 256, pe) != DD_OK)
{
    return;
}

lastTickCount[3] = thisTickCount;
    }

    // Flip the surfaces
    while( 1 )
    {
ddrval = lpDDSPrimary->Flip( NULL, 0 );
if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
break;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    break;
}
    }

} /* updateFrame */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
    lpDDSOne->Release();
    lpDDSOne = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }

} /* finiObjects */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
    case WM_ACTIVATEAPP:
    bActive = wParam;
break;

    case WM_SETCURSOR:
        SetCursor(NULL);
        return TRUE;

    case WM_CREATE:
break;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
    finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWNDhwnd;
    WNDCLASSwc;
    DDSURFACEDESCddsd;
    DDSCAPSddscaps;
    HRESULTddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockBrush(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
        0,
NAME,
TITLE,
WS_POPUP,
0,
0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
NULL,
NULL,
hInstance,
        NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );
    SetFocus( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Get exclusive mode
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Set the video mode to 640x480x8
    ddrval = lpDD->SetDisplayMode( 640, 480, 8);
    if(ddrval != DD_OK)
    {
return initFail(hwnd);
    }

    // Create the primary surface with 1 back buffer
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // create and set the palette
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    if (lpDDPal)
        lpDDSPrimary->SetPalette(lpDDPal);

    // Create the offscreen surface, by loading our bitmap.
    lpDDSOne = DDLoadBitmap(lpDD, szBitmap, 0, 0);

    if( lpDDSOne == NULL )
    {
return initFail(hwnd);
    }

    // set the color key to black
    DDSetColorKey(lpDDSOne, RGB(0,0,0));

    //
    // Mark the colors used in the torus frames
    //
    int i,x,y;

    // First, set all colors as unused
    for(i=0; i<256; i++)
    {
        torusColors[i] = 0;
    }

    // lock the surface and scan the lower part (the torus area)
    // and remember all the index's we find.
    ddsd.dwSize = sizeof(ddsd);
    while (lpDDSOne->Lock(NULL, &ddsd, 0, NULL) == DDERR_WASSTILLDRAWING)
        ;

    // Now search through the torus frames and mark used colors
    for( y=480; y<480+384; y++ )
    {
        for( x=0; x<640; x++ )
        {
            torusColors[((BYTE *)ddsd.lpSurface)[y*ddsd.lPitch+x]] = 1;
        }
    }

    lpDDSOne->Unlock(NULL);

    return TRUE;

} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSGmsg;

    lpCmdLine = lpCmdLine;
    hPrevInstance = hPrevInstance;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
    {
return msg.wParam;
    }
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
else if( bActive )
{
    updateFrame();
        }
        else
        {
            // make sure we go to sleep if we have nothing else to do
            WaitMessage();
        }
    }
} /* WinMain */


DIEX1.CPP

/**************************************************************************

    DIEX1.CPP - DirectInput simple sample 1

    Demonstrates an application which receives relative mouse data
    in non-exclusive mode via a game loop.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <dinput.h>

#include "diex1.h"

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

char c_szClassName[] = "DIEX1";

HINSTANCE       g_hinst;                /* My instance handle */
BOOL            g_fPaused = TRUE;       /* Should I be paused? */

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT           g_pdi;
LPDIRECTINPUTDEVICE     g_pMouse;
char                    g_szText[1024]; /* What we display in client area */

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void
Complain(
    HWND hwndOwner,
    HRESULT hr,
    LPCSTR pszMessage
)
{
    MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 *      This entails the following four functions:
 *
 *          DirectInputCreate
 *          IDirectInput::CreateDevice
 *          IDirectInputDevice::SetDataFormat
 *          IDirectInputDevice::SetCooperativeLevel
 *
 ****************************************************************************/

BOOL
DIInit(
    HWND hwnd
)
{
    HRESULT hr;

    /*
     *  Register with the DirectInput subsystem and get a pointer
     *  to a IDirectInput interface we can use.
     *
     *  Parameters:
     *
     *      g_hinst
     *
     *          Instance handle to our application or DLL.
     *
     *      DIRECTINPUT_VERSION
     *
     *          The version of DirectInput we were designed for.
     *          We take the value from the <dinput.h> header file.
     *
     *      &g_pdi
     *
     *          Receives pointer to the IDirectInput interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Obtain an interface to the system mouse device.
     *
     *  Parameters:
     *
     *      GUID_SysMouse
     *
     *          The instance GUID for the device we wish to access.
     *          GUID_SysMouse is a predefined instance GUID that
     *          always refers to the system mouse device.
     *
     *      &g_pMouse
     *
     *          Receives pointer to the IDirectInputDevice interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateDevice");
        return FALSE;
    }

    /*
     *  Set the data format to "mouse format".
     *
     *  A data format specifies which controls on a device we
     *  are interested in, and how they should be reported.
     *
     *  This tells DirectInput that we will be passing a
     *  DIMOUSESTATE structure to IDirectInputDevice::GetDeviceState.
     *
     *  Parameters:
     *
     *      c_dfDIMouse
     *
     *          Predefined data format which describes
     *          a DIMOUSESTATE structure.
     */
    hr = g_pMouse->SetDataFormat(&c_dfDIMouse);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat");
        return FALSE;
    }

    /*
     *  Set the cooperativity level to let DirectInput know how
     *  this device should interact with the system and with other
     *  DirectInput applications.
     *
     *  Parameters:
     *
     *      DISCL_NONEXCLUSIVE
     *
     *          Retrieve mouse data when acquired, not interfering
     *          with any other applications which are reading mouse
     *          data.
     *
     *      DISCL_FOREGROUND
     *
     *          If the user switches away from our application,
     *          automatically release the mouse back to the system.
     *
     */
    hr = g_pMouse->SetCooperativeLevel(hwnd,
                                       DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void
DITerm(void)
{

    /*
     *  Destroy any lingering IDirectInputDevice object.
     */
    if (g_pMouse) {

        /*
         *  Cleanliness is next to godliness.  Unacquire the device
         *  one last time just in case we got really confused and tried
         *  to exit while the device is still acquired.
         */
        g_pMouse->Unacquire();

        g_pMouse->Release();
        g_pMouse = NULL;
    }

    /*
     *  Destroy any lingering IDirectInput object.
     */
    if (g_pdi) {
        g_pdi->Release();
        g_pdi = NULL;
    }

}

/****************************************************************************
 *
 *      Ex_OnPaint
 *
 *      Display the current mouse position and button state.
 *
 ****************************************************************************/

LRESULT
Ex_OnPaint(
    HWND hwnd
)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, g_szText,
                   lstrlen(g_szText), NULL);

        EndPaint(hwnd, &ps);
    }

    return 0;
}

/****************************************************************************
 *
 *      Ex_OneFrame
 *
 *      The game plays here.
 *
 *      Our "game" consists entirely of reading mouse data
 *      and displaying it.
 *
 ****************************************************************************/

void
Ex_OneFrame(HWND hwnd)
{

    if (g_pMouse) {

        DIMOUSESTATE dims;          /* DirectInput mouse state structure */
        HRESULT hr;

    again:;
        hr = g_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), &dims);
        if (hr == DIERR_INPUTLOST) {
            /*
             *  DirectInput is telling us that the input stream has
             *  been interrupted.  We aren't tracking any state
             *  between polls, so we don't have any special reset
             *  that needs to be done.  We just re-acquire and
             *  try again.
             */
            hr = g_pMouse->Acquire();
            if (SUCCEEDED(hr)) {
                goto again;
            }
        }

        if (SUCCEEDED(hr)) {
            char szBuf[1024];

            /*
             *  Build the new status string.
             *
             *  Display mouse coordinates (x, y, z) and buttons.
             */
            wsprintf(szBuf, "(%d, %d, %d) %c %c %c %c",
                     dims.lX, dims.lY, dims.lZ,
                     (dims.rgbButtons[0] & 0x80) ? '0' : ' ',
                     (dims.rgbButtons[1] & 0x80) ? '1' : ' ',
                     (dims.rgbButtons[2] & 0x80) ? '2' : ' ',
                     (dims.rgbButtons[3] & 0x80) ? '3' : ' ');

            /*
             *  Trigger a repaint only if the status string changed.
             *  This avoids flicker.
             */
            if (lstrcmp(g_szText, szBuf)) {
                lstrcpy(g_szText, szBuf);
                InvalidateRect(hwnd, NULL, TRUE);
            }
        }
    }

    /*
     *  Sleep for a few milliseconds to simulate a 30fps frame rate.
     */
    Sleep(1000 / 30);

}

/****************************************************************************
 *
 *      Ex_SyncAcquire
 *
 *      Acquire or unacquire the mouse, depending on the the g_fPaused
 *      flag.  This synchronizes the device with our internal view of
 *      the world.
 *
 ****************************************************************************/

void
Ex_SyncAcquire(HWND hwnd)
{
    if (g_fPaused) {
        if (g_pMouse) g_pMouse->Unacquire();
    } else {
        if (g_pMouse) g_pMouse->Acquire();
    }
}

/****************************************************************************
 *
 *      Ex_WndProc
 *
 *      Window procedure for simple sample.
 *
 ****************************************************************************/

LRESULT CALLBACK
Ex_WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{

    switch (msg) {

    case WM_PAINT:      return Ex_OnPaint(hwnd);

    /*
     *  WM_ACTIVATE
     *
     *      Windows sends this message when the window becomes
     *      the active window or stops being the active window.
     *
     *      wParam = WA_INACTIVE if window is no longer active
     *
     *      wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
     *
     *      If we are losing activation, then pause.
     *
     *      If we are gaining activation, then unpause.
     *
     *      After deciding whether we are paused or unpaused,
     *      tell DirectInput that we don't (paused) or do (unpaused)
     *      want non-exclusive access to the mouse.
     *
     */
    case WM_ACTIVATE:
        g_fPaused = wParam == WA_INACTIVE;
        Ex_SyncAcquire(hwnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND
AppInit(
    HINSTANCE hinst,
    int nCmdShow
)
{

    /*
     *  Save instance handle for future reference.
     */
    g_hinst = hinst;

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szClassName;
    wc.hbrBackground  = 0;
    wc.hInstance      = hinst;
    wc.style          = 0;
    wc.lpfnWndProc    = Ex_WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    HWND hwnd = CreateWindow(
                    c_szClassName,                  // Class name
                    "DIEX1 - Alt+F4 to exit",       // Caption
                    WS_OVERLAPPEDWINDOW,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Position
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Size
                    NULL,                           // No parent
                    NULL,                           // No menu
                    g_hinst,                        // inst handle
                    0                               // no params
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Standard game loop.
         */
        for (;;) {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                /* If it's a quit message, we're outta here */
                if (msg.message == WM_QUIT) {
                    break;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } else if (g_fPaused) {
                WaitMessage();
            } else {
                Ex_OneFrame(hwnd);
            }
        }
    }

    DITerm();

    return msg.wParam;

}


DIEX1.H

/**************************************************************************

    DIEX1.H - DirectInput simple sample 1

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>


DIEX2.CPP

/**************************************************************************

    DIEX2.CPP - DirectInput simple sample 2

    Demonstrates an application which receives relative mouse data
    in exclusive mode via a game loop.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <dinput.h>

#include "diex2.h"

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

char c_szClassName[] = "DIEX2";

HINSTANCE       g_hinst;                /* My instance handle */
BOOL            g_fPaused = TRUE;       /* Should I be paused? */

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT           g_pdi;
LPDIRECTINPUTDEVICE     g_pMouse;
char                    g_szText[1024]; /* What we display in client area */

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void
Complain(
    HWND hwndOwner,
    HRESULT hr,
    LPCSTR pszMessage
)
{
    MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 *      This entails the following four functions:
 *
 *          DirectInputCreate
 *          IDirectInput::CreateDevice
 *          IDirectInputDevice::SetDataFormat
 *          IDirectInputDevice::SetCooperativeLevel
 *
 ****************************************************************************/

BOOL
DIInit(
    HWND hwnd
)
{
    HRESULT hr;

    /*
     *  Register with the DirectInput subsystem and get a pointer
     *  to a IDirectInput interface we can use.
     *
     *  Parameters:
     *
     *      g_hinst
     *
     *          Instance handle to our application or DLL.
     *
     *      DIRECTINPUT_VERSION
     *
     *          The version of DirectInput we were designed for.
     *          We take the value from the <dinput.h> header file.
     *
     *      &g_pdi
     *
     *          Receives pointer to the IDirectInput interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Obtain an interface to the system mouse device.
     *
     *  Parameters:
     *
     *      GUID_SysMouse
     *
     *          The instance GUID for the device we wish to access.
     *          GUID_SysMouse is a predefined instance GUID that
     *          always refers to the system mouse device.
     *
     *      &g_pMouse
     *
     *          Receives pointer to the IDirectInputDevice interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateDevice");
        return FALSE;
    }

    /*
     *  Set the data format to "mouse format".
     *
     *  A data format specifies which controls on a device we
     *  are interested in, and how they should be reported.
     *
     *  This tells DirectInput that we will be passing a
     *  DIMOUSESTATE structure to IDirectInputDevice::GetDeviceState.
     *
     *  Parameters:
     *
     *      c_dfDIMouse
     *
     *          Predefined data format which describes
     *          a DIMOUSESTATE structure.
     */
    hr = g_pMouse->SetDataFormat(&c_dfDIMouse);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat");
        return FALSE;
    }

    /*
     *  Set the cooperativity level to let DirectInput know how
     *  this device should interact with the system and with other
     *  DirectInput applications.
     *
     *  Parameters:
     *
     *      DISCL_EXCLUSIVE
     *
     *          When the mouse is acquired, no other application
     *          will be able to acquire the mouse exclusively.
     *          Furthermore, the Windows mouse cursor will not move.
     *
     *      DISCL_FOREGROUND
     *
     *          If the user switches away from our application,
     *          automatically release the mouse back to the system.
     *
     */
    hr = g_pMouse->SetCooperativeLevel(hwnd,
                                       DISCL_EXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void
DITerm(void)
{

    /*
     *  Destroy any lingering IDirectInputDevice object.
     */
    if (g_pMouse) {

        /*
         *  Cleanliness is next to godliness.  Unacquire the device
         *  one last time just in case we got really confused and tried
         *  to exit while the device is still acquired.
         */
        g_pMouse->Unacquire();

        g_pMouse->Release();
        g_pMouse = NULL;
    }

    /*
     *  Destroy any lingering IDirectInput object.
     */
    if (g_pdi) {
        g_pdi->Release();
        g_pdi = NULL;
    }

}

/****************************************************************************
 *
 *      Ex_OnPaint
 *
 *      Display the current mouse position and button state.
 *
 ****************************************************************************/

LRESULT
Ex_OnPaint(
    HWND hwnd
)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, g_szText,
                   lstrlen(g_szText), NULL);

        EndPaint(hwnd, &ps);
    }

    return 0;
}

/****************************************************************************
 *
 *      Ex_OneFrame
 *
 *      The game plays here.
 *
 *      Our "game" consists entirely of reading mouse data
 *      and displaying it.
 *
 ****************************************************************************/

void
Ex_OneFrame(HWND hwnd)
{

    if (g_pMouse) {

        DIMOUSESTATE dims;          /* DirectInput mouse state structure */
        HRESULT hr;

    again:;
        hr = g_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), &dims);
        if (hr == DIERR_INPUTLOST) {
            /*
             *  DirectInput is telling us that the input stream has
             *  been interrupted.  We aren't tracking any state
             *  between polls, so we don't have any special reset
             *  that needs to be done.  We just re-acquire and
             *  try again.
             */
            hr = g_pMouse->Acquire();
            if (SUCCEEDED(hr)) {
                goto again;
            }
        }

        if (SUCCEEDED(hr)) {
            char szBuf[1024];

            /*
             *  Build the new status string.
             *
             *  Display mouse coordinates (x, y, z) and buttons.
             */
            wsprintf(szBuf, "(%d, %d, %d) %c %c %c %c",
                     dims.lX, dims.lY, dims.lZ,
                     (dims.rgbButtons[0] & 0x80) ? '0' : ' ',
                     (dims.rgbButtons[1] & 0x80) ? '1' : ' ',
                     (dims.rgbButtons[2] & 0x80) ? '2' : ' ',
                     (dims.rgbButtons[3] & 0x80) ? '3' : ' ');

            /*
             *  Trigger a repaint only if the status string changed.
             *  This avoids flicker.
             */
            if (lstrcmp(g_szText, szBuf)) {
                lstrcpy(g_szText, szBuf);
                InvalidateRect(hwnd, NULL, TRUE);
            }
        }
    }

    /*
     *  Sleep for a few milliseconds to simulate a 30fps frame rate.
     */
    Sleep(1000 / 30);

}

/****************************************************************************
 *
 *      Ex_SyncAcquire
 *
 *      Acquire or unacquire the mouse, depending on the the g_fPaused
 *      flag.  This synchronizes the device with our internal view of
 *      the world.
 *
 ****************************************************************************/

void
Ex_SyncAcquire(HWND hwnd)
{
    if (g_fPaused) {
        if (g_pMouse) g_pMouse->Unacquire();
    } else {
        if (g_pMouse) g_pMouse->Acquire();
    }
}

/****************************************************************************
 *
 *      Private messages
 *
 *      WM_SYNCACQUIRE forces us to re-synchronize our acquisition
 *      with the world.  There are some subtleties with the way
 *      Windows manages mouse ownership, so we post this message
 *      to ourselves to say "Okay, things have settled down; now go
 *      synchronize DirectInput with our internal view of the world."
 *
 ****************************************************************************/

#define WM_SYNCACQUIRE      (WM_USER + 0)

/****************************************************************************
 *
 *      Ex_WndProc
 *
 *      Window procedure for simple sample.
 *
 ****************************************************************************/

LRESULT CALLBACK
Ex_WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    LRESULT lRc;

    switch (msg) {

    case WM_PAINT:      return Ex_OnPaint(hwnd);

    /*
     *  WM_ACTIVATE
     *
     *      Windows sends this message when the window becomes
     *      the active window or stops being the active window.
     *
     *      wParam = WA_INACTIVE if window is no longer active
     *
     *      wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
     *
     *      If we are losing activation, then pause.
     *
     *      If we are gaining activation, then unpause.
     *
     *      After deciding whether we are paused or unpaused,
     *      tell DirectInput that we don't (paused) or do (unpaused)
     *      want exclusive access to the mouse.
     *
     */
    case WM_ACTIVATE:
        g_fPaused = wParam == WA_INACTIVE;
        Ex_SyncAcquire(hwnd);
        break;

    /*
     *  Unacquire the mouse if a menu appears, so that the user can
     *  interact with the menu in the normal manner.
     *
     *  From Windows' point of view, we are still the active window
     *  when a menu appears, but we want to act like the menu deactivated
     *  us.
     */
    case WM_ENTERMENULOOP:
    case WM_ENTERSIZEMOVE:
        g_fPaused = TRUE;
        Ex_SyncAcquire(hwnd);
        break;

    /*
     *  Reacquire the devices when the menu goes away.
     *
     *  SUBTLETY 1:  Windows actually sends the WM_EXITMENULOOP message
     *  before all the menu-related stuff is finished, so post ourselves
     *  a private message to reacquire after the menu has been torn
     *  down for real.
     *
     *  SUBTLETY 2:  Don't assume that just because the menu is going
     *  away that you are still the active window.  You might not be.
     *
     *  SUBTLETY 3:  Don't assume that just because you're the active
     *  window that you are restored and ready for action.  You might
     *  just be a taskbar button.
     */
    case WM_EXITMENULOOP:
    case WM_EXITSIZEMOVE:
        g_fPaused = GetActiveWindow() != hwnd || IsIconic(hwnd);
        PostMessage(hwnd, WM_SYNCACQUIRE, 0, 0L);
        break;

    case WM_SYNCACQUIRE:
        Ex_SyncAcquire(hwnd);
        break;

    case WM_SYSCOMMAND:

        lRc = DefWindowProc(hwnd, msg, wParam, lParam);

        /*
         * The WM_SYSCOMMAND might've been a WM_CLOSE, in which case
         * our window no longer exists.  So be careful.
         */
        if (IsWindow(hwnd)) {
            Ex_SyncAcquire(hwnd);
        }
        return lRc;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND
AppInit(
    HINSTANCE hinst,
    int nCmdShow
)
{

    /*
     *  Save instance handle for future reference.
     */
    g_hinst = hinst;

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szClassName;
    wc.hbrBackground  = 0;
    wc.hInstance      = hinst;
    wc.style          = 0;
    wc.lpfnWndProc    = Ex_WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    HWND hwnd = CreateWindow(
                    c_szClassName,                  // Class name
                    "DIEX2 - Alt+F4 to exit",       // Caption
                    WS_OVERLAPPEDWINDOW,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Position
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Size
                    NULL,                           // No parent
                    NULL,                           // No menu
                    g_hinst,                        // inst handle
                    0                               // no params
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Standard game loop.
         */
        for (;;) {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                /* If it's a quit message, we're outta here */
                if (msg.message == WM_QUIT) {
                    break;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } else if (g_fPaused) {
                WaitMessage();
            } else {
                Ex_OneFrame(hwnd);
            }
        }
    }

    DITerm();

    return msg.wParam;

}


DIEX2.H

/**************************************************************************

    DIEX2.H - DirectInput simple sample 2

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>


DIEX3.CPP

/**************************************************************************

    DIEX3.CPP - DirectInput simple sample 3

    Demonstrates an application which receives keyboard data
    in non-exclusive mode via a game loop.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <dinput.h>

#include "diex3.h"

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

char c_szClassName[] = "DIEX3";

HINSTANCE       g_hinst;                /* My instance handle */
BOOL            g_fPaused = TRUE;       /* Should I be paused? */

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT           g_pdi;
LPDIRECTINPUTDEVICE     g_pKeyboard;
char                    g_szText[1024]; /* What we display in client area */

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void
Complain(
    HWND hwndOwner,
    HRESULT hr,
    LPCSTR pszMessage
)
{
    MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 *      This entails the following four functions:
 *
 *          DirectInputCreate
 *          IDirectInput::CreateDevice
 *          IDirectInputDevice::SetDataFormat
 *          IDirectInputDevice::SetCooperativeLevel
 *
 ****************************************************************************/

BOOL
DIInit(
    HWND hwnd
)
{
    HRESULT hr;

    /*
     *  Register with the DirectInput subsystem and get a pointer
     *  to a IDirectInput interface we can use.
     *
     *  Parameters:
     *
     *      g_hinst
     *
     *          Instance handle to our application or DLL.
     *
     *      DIRECTINPUT_VERSION
     *
     *          The version of DirectInput we were designed for.
     *          We take the value from the <dinput.h> header file.
     *
     *      &g_pdi
     *
     *          Receives pointer to the IDirectInput interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Obtain an interface to the system keyboard device.
     *
     *  Parameters:
     *
     *      GUID_SysKeyboard
     *
     *          The instance GUID for the device we wish to access.
     *          GUID_SysKeyboard is a predefined instance GUID that
     *          always refers to the system keyboard device.
     *
     *      &g_pKeyboard
     *
     *          Receives pointer to the IDirectInputDevice interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pdi->CreateDevice(GUID_SysKeyboard, &g_pKeyboard, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateDevice");
        return FALSE;
    }

    /*
     *  Set the data format to "keyboard format".
     *
     *  A data format specifies which controls on a device we
     *  are interested in, and how they should be reported.
     *
     *  This tells DirectInput that we will be passing an array
     *  of 256 bytes to IDirectInputDevice::GetDeviceState.
     *
     *  Parameters:
     *
     *      c_dfDIKeyboard
     *
     *          Predefined data format which describes
     *          an array of 256 bytes, one per scancode.
     */
    hr = g_pKeyboard->SetDataFormat(&c_dfDIKeyboard);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat");
        return FALSE;
    }

    /*
     *  Set the cooperativity level to let DirectInput know how
     *  this device should interact with the system and with other
     *  DirectInput applications.
     *
     *  Parameters:
     *
     *      DISCL_NONEXCLUSIVE
     *
     *          Retrieve keyboard data when acquired, not interfering
     *          with any other applications which are reading keyboard
     *          data.
     *
     *      DISCL_FOREGROUND
     *
     *          If the user switches away from our application,
     *          automatically release the keyboard back to the system.
     *
     */
    hr = g_pKeyboard->SetCooperativeLevel(hwnd,
                                       DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void
DITerm(void)
{

    /*
     *  Destroy any lingering IDirectInputDevice object.
     */
    if (g_pKeyboard) {

        /*
         *  Cleanliness is next to godliness.  Unacquire the device
         *  one last time just in case we got really confused and tried
         *  to exit while the device is still acquired.
         */
        g_pKeyboard->Unacquire();

        g_pKeyboard->Release();
        g_pKeyboard = NULL;
    }

    /*
     *  Destroy any lingering IDirectInput object.
     */
    if (g_pdi) {
        g_pdi->Release();
        g_pdi = NULL;
    }

}

/****************************************************************************
 *
 *      Ex_OnPaint
 *
 *      Display the current keyboard state.
 *
 ****************************************************************************/

LRESULT
Ex_OnPaint(
    HWND hwnd
)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, g_szText,
                   lstrlen(g_szText), NULL);

        EndPaint(hwnd, &ps);
    }

    return 0;
}

/****************************************************************************
 *
 *      Ex_OneFrame
 *
 *      The game plays here.
 *
 *      Our "game" consists entirely of reading keyboard data
 *      and displaying it.
 *
 ****************************************************************************/

void
Ex_OneFrame(HWND hwnd)
{

    if (g_pKeyboard) {

        BYTE diks[256];             /* DirectInput keyboard state buffer */
        HRESULT hr;

    again:;
        hr = g_pKeyboard->GetDeviceState(sizeof(diks), &diks);
        if (hr == DIERR_INPUTLOST) {
            /*
             *  DirectInput is telling us that the input stream has
             *  been interrupted.  We aren't tracking any state
             *  between polls, so we don't have any special reset
             *  that needs to be done.  We just re-acquire and
             *  try again.
             */
            hr = g_pKeyboard->Acquire();
            if (SUCCEEDED(hr)) {
                goto again;
            }
        }

        if (SUCCEEDED(hr)) {
            char szBuf[1024];

            /*
             *  Build the new status string.
             *
             *  Display the scan codes of the keys that are down.
             */
            int i;
            char *psz = szBuf;
            for (i = 0; i < 256; i++) {
                if (diks[i] & 0x80) {
                    psz += wsprintf(psz, "%02x ", i);
                }
            }
            *psz = 0;                   /* Terminate the string */

            /*
             *  Trigger a repaint only if the status string changed.
             *  This avoids flicker.
             */
            if (lstrcmp(g_szText, szBuf)) {
                lstrcpy(g_szText, szBuf);
                InvalidateRect(hwnd, NULL, TRUE);
            }
        }
    }

    /*
     *  Sleep for a few milliseconds to simulate a 30fps frame rate.
     */
    Sleep(1000 / 30);

}

/****************************************************************************
 *
 *      Ex_SyncAcquire
 *
 *      Acquire or unacquire the keyboard, depending on the the g_fPaused
 *      flag.  This synchronizes the device with our internal view of
 *      the world.
 *
 ****************************************************************************/

void
Ex_SyncAcquire(HWND hwnd)
{
    if (g_fPaused) {
        if (g_pKeyboard) g_pKeyboard->Unacquire();
    } else {
        if (g_pKeyboard) g_pKeyboard->Acquire();
    }
}

/****************************************************************************
 *
 *      Ex_WndProc
 *
 *      Window procedure for simple sample.
 *
 ****************************************************************************/

LRESULT CALLBACK
Ex_WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{

    switch (msg) {

    case WM_PAINT:      return Ex_OnPaint(hwnd);

    /*
     *  WM_ACTIVATE
     *
     *      Windows sends this message when the window becomes
     *      the active window or stops being the active window.
     *
     *      wParam = WA_INACTIVE if window is no longer active
     *
     *      wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
     *
     *      If we are losing activation, then pause.
     *
     *      If we are gaining activation, then unpause.
     *
     *      After deciding whether we are paused or unpaused,
     *      tell DirectInput that we don't (paused) or do (unpaused)
     *      want non-exclusive access to the keyboard.
     *
     */
    case WM_ACTIVATE:
        g_fPaused = wParam == WA_INACTIVE;
        Ex_SyncAcquire(hwnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND
AppInit(
    HINSTANCE hinst,
    int nCmdShow
)
{

    /*
     *  Save instance handle for future reference.
     */
    g_hinst = hinst;

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szClassName;
    wc.hbrBackground  = 0;
    wc.hInstance      = hinst;
    wc.style          = 0;
    wc.lpfnWndProc    = Ex_WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    HWND hwnd = CreateWindow(
                    c_szClassName,                  // Class name
                    "DIEX3 - Alt+F4 to exit",       // Caption
                    WS_OVERLAPPEDWINDOW,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Position
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Size
                    NULL,                           // No parent
                    NULL,                           // No menu
                    g_hinst,                        // inst handle
                    0                               // no params
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Standard game loop.
         */
        for (;;) {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                /* If it's a quit message, we're outta here */
                if (msg.message == WM_QUIT) {
                    break;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } else if (g_fPaused) {
                WaitMessage();
            } else {
                Ex_OneFrame(hwnd);
            }
        }
    }

    DITerm();

    return msg.wParam;

}


DIEX3.H

/**************************************************************************

    DIEX3.H - DirectInput simple sample 3

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>


DIEX4.CPP

/**************************************************************************

    DIEX4.CPP - DirectInput simple sample 4

    Demonstrates an application which retrieves buffered keyboard data
    in non-exclusive mode via a game loop.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <dinput.h>

#include "diex4.h"

/****************************************************************************
 *
 *      Global Parameters
 *
 ****************************************************************************/

#define DINPUT_BUFFERSIZE       16      /* Number of buffer elements */

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

char c_szClassName[] = "DIEX4";

HINSTANCE       g_hinst;                /* My instance handle */
BOOL            g_fPaused = TRUE;       /* Should I be paused? */

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT           g_pdi;
LPDIRECTINPUTDEVICE     g_pKeyboard;
char                    g_szText[1024]; /* What we display in client area */

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void
Complain(
    HWND hwndOwner,
    HRESULT hr,
    LPCSTR pszMessage
)
{
    MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 *      This entails the following four functions:
 *
 *          DirectInputCreate
 *          IDirectInput::CreateDevice
 *          IDirectInputDevice::SetDataFormat
 *          IDirectInputDevice::SetCooperativeLevel
 *
 *      Reading buffered data requires another function:
 *
 *          IDirectInputDevice::SetProperty(DIPROP_BUFFERSIZE)
 *
 ****************************************************************************/

BOOL
DIInit(
    HWND hwnd
)
{
    HRESULT hr;

    /*
     *  Register with the DirectInput subsystem and get a pointer
     *  to a IDirectInput interface we can use.
     *
     *  Parameters:
     *
     *      g_hinst
     *
     *          Instance handle to our application or DLL.
     *
     *      DIRECTINPUT_VERSION
     *
     *          The version of DirectInput we were designed for.
     *          We take the value from the <dinput.h> header file.
     *
     *      &g_pdi
     *
     *          Receives pointer to the IDirectInput interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Obtain an interface to the system keyboard device.
     *
     *  Parameters:
     *
     *      GUID_SysKeyboard
     *
     *          The instance GUID for the device we wish to access.
     *          GUID_SysKeyboard is a predefined instance GUID that
     *          always refers to the system keyboard device.
     *
     *      &g_pKeyboard
     *
     *          Receives pointer to the IDirectInputDevice interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pdi->CreateDevice(GUID_SysKeyboard, &g_pKeyboard, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateDevice");
        return FALSE;
    }

    /*
     *  Set the data format to "keyboard format".
     *
     *  A data format specifies which controls on a device we
     *  are interested in, and how they should be reported.
     *
     *  This tells DirectInput that we are interested in all keys
     *  on the device, and they should be reported as DirectInput
     *  DIK_* codes.
     *
     *  Parameters:
     *
     *      c_dfDIKeyboard
     *
     *          Predefined data format which describes
     *          an array of 256 bytes, one per scancode.
     */
    hr = g_pKeyboard->SetDataFormat(&c_dfDIKeyboard);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat");
        return FALSE;
    }

    /*
     *  Set the cooperativity level to let DirectInput know how
     *  this device should interact with the system and with other
     *  DirectInput applications.
     *
     *  Parameters:
     *
     *      DISCL_NONEXCLUSIVE
     *
     *          Retrieve keyboard data when acquired, not interfering
     *          with any other applications which are reading keyboard
     *          data.
     *
     *      DISCL_FOREGROUND
     *
     *          If the user switches away from our application,
     *          automatically release the keyboard back to the system.
     *
     */
    hr = g_pKeyboard->SetCooperativeLevel(hwnd,
                                       DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel");
        return FALSE;
    }

    /*
     *  IMPORTANT STEP IF YOU WANT TO USE BUFFERED DEVICE DATA!
     *
     *  DirectInput uses unbuffered I/O (buffer size = 0) by default.
     *  If you want to read buffered data, you need to set a nonzero
     *  buffer size.
     *
     *  Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements.
     *
     *  The buffer size is a DWORD property associated with the device.
     */
    DIPROPDWORD dipdw =
        {
            {
                sizeof(DIPROPDWORD),        // diph.dwSize
                sizeof(DIPROPHEADER),       // diph.dwHeaderSize
                0,                          // diph.dwObj
                DIPH_DEVICE,                // diph.dwHow
            },
            DINPUT_BUFFERSIZE,              // dwData
        };

    hr = g_pKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "Set buffer size");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void
DITerm(void)
{

    /*
     *  Destroy any lingering IDirectInputDevice object.
     */
    if (g_pKeyboard) {

        /*
         *  Cleanliness is next to godliness.  Unacquire the device
         *  one last time just in case we got really confused and tried
         *  to exit while the device is still acquired.
         */
        g_pKeyboard->Unacquire();

        g_pKeyboard->Release();
        g_pKeyboard = NULL;
    }

    /*
     *  Destroy any lingering IDirectInput object.
     */
    if (g_pdi) {
        g_pdi->Release();
        g_pdi = NULL;
    }

}

/****************************************************************************
 *
 *      Ex_OnPaint
 *
 *      Display the current keyboard state.
 *
 ****************************************************************************/

LRESULT
Ex_OnPaint(
    HWND hwnd
)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, g_szText,
                   lstrlen(g_szText), NULL);

        EndPaint(hwnd, &ps);
    }

    return 0;
}

/****************************************************************************
 *
 *      Ex_OneFrame
 *
 *      The game plays here.
 *
 *      Our "game" consists entirely of reading keyboard data
 *      and displaying it.
 *
 ****************************************************************************/

void
Ex_OneFrame(HWND hwnd)
{

    if (g_pKeyboard) {

        DIDEVICEOBJECTDATA rgod[DINPUT_BUFFERSIZE]; /* Receives buffered data */
        DWORD cod;
        HRESULT hr;

    again:;
        cod = DINPUT_BUFFERSIZE;
        hr = g_pKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
                                        rgod, &cod, 0);
        if (hr != DI_OK) {
            /*
             *  We got an error or we got DI_BUFFEROVERFLOW.
             *
             *  Either way, it means that continuous contact with the
             *  device has been lost, either due to an external
             *  interruption, or because the buffer overflowed
             *  and some events were lost.
             *
             *  Consequently, if a button was pressed at the time
             *  the buffer overflowed or the connection was broken,
             *  the corresponding "up" message might have been lost.
             *
             *  But since our simple sample doesn't actually have
             *  any state associated with button up or down events,
             *  there is no state to reset.  (In a real game, ignoring
             *  the buffer overflow would result in the game thinking
             *  a key was held down when in fact it isn't; it's just
             *  that the "up" event got lost because the buffer
             *  overflowed.)
             *
             *  If we want to be cleverer, we could do a
             *  GetDeviceState() and compare the current state
             *  against the state we think the device is in,
             *  and process all the states that are currently
             *  different from our private state.
             *
             */

            /* << insert recovery code here if you need any >> */

            if (hr == DIERR_INPUTLOST) {
                hr = g_pKeyboard->Acquire();
                if (SUCCEEDED(hr)) {
                    goto again;
                }
            }
        }

        /*
         *  In order for it to be worth our while to parse the
         *  buffer elements, the GetDeviceData must have succeeded,
         *  and we must have received some data at all.
         */
        if (SUCCEEDED(hr) && cod > 0) {
            char szBuf[1024];
            DWORD iod;

            /*
             *  Study each of the buffer elements and process them.
             *
             *  Since we really don't do anything, our "processing"
             *  consists merely of squirting the name into our
             *  local buffer.
             */
            for (iod = 0; iod < cod; iod++) {
                wsprintf(szBuf, "%02x was %s",
                         rgod[iod].dwOfs,
                         (rgod[iod].dwData & 0x80) ? "pressed" : "released");
            }

            /*
             *  Trigger a repaint only if the status string changed.
             *  This avoids flicker.
             */
            if (lstrcmp(g_szText, szBuf)) {
                lstrcpy(g_szText, szBuf);
                InvalidateRect(hwnd, NULL, TRUE);
            }
        }
    }

    /*
     *  Sleep for a few milliseconds to simulate a 30fps frame rate.
     */
    Sleep(1000 / 30);

}

/****************************************************************************
 *
 *      Ex_SyncAcquire
 *
 *      Acquire or unacquire the keyboard, depending on the the g_fPaused
 *      flag.  This synchronizes the device with our internal view of
 *      the world.
 *
 ****************************************************************************/

void
Ex_SyncAcquire(HWND hwnd)
{
    if (g_fPaused) {
        if (g_pKeyboard) g_pKeyboard->Unacquire();
    } else {
        if (g_pKeyboard) g_pKeyboard->Acquire();
    }
}

/****************************************************************************
 *
 *      Ex_WndProc
 *
 *      Window procedure for simple sample.
 *
 ****************************************************************************/

LRESULT CALLBACK
Ex_WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{

    switch (msg) {

    case WM_PAINT:      return Ex_OnPaint(hwnd);

    /*
     *  WM_ACTIVATE
     *
     *      Windows sends this message when the window becomes
     *      the active window or stops being the active window.
     *
     *      wParam = WA_INACTIVE if window is no longer active
     *
     *      wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
     *
     *      If we are losing activation, then pause.
     *
     *      If we are gaining activation, then unpause.
     *
     *      After deciding whether we are paused or unpaused,
     *      tell DirectInput that we don't (paused) or do (unpaused)
     *      want non-exclusive access to the keyboard.
     *
     */
    case WM_ACTIVATE:
        g_fPaused = wParam == WA_INACTIVE;
        Ex_SyncAcquire(hwnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND
AppInit(
    HINSTANCE hinst,
    int nCmdShow
)
{

    /*
     *  Save instance handle for future reference.
     */
    g_hinst = hinst;

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szClassName;
    wc.hbrBackground  = 0;
    wc.hInstance      = hinst;
    wc.style          = 0;
    wc.lpfnWndProc    = Ex_WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    HWND hwnd = CreateWindow(
                    c_szClassName,                  // Class name
                    "DIEX4 - Alt+F4 to exit",       // Caption
                    WS_OVERLAPPEDWINDOW,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Position
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Size
                    NULL,                           // No parent
                    NULL,                           // No menu
                    g_hinst,                        // inst handle
                    0                               // no params
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Standard game loop.
         */
        for (;;) {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                /* If it's a quit message, we're outta here */
                if (msg.message == WM_QUIT) {
                    break;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } else if (g_fPaused) {
                WaitMessage();
            } else {
                Ex_OneFrame(hwnd);
            }
        }
    }

    DITerm();

    return msg.wParam;

}


DIEX4.H

/**************************************************************************

    DIEX4.H - DirectInput simple sample 4

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>


README.TXT

DirectInput Force Feedback Simple Sample 1

This application applies raw forces to a force feedback joystick,
illustrating how a simulator-type application can use force feedback
to generate forces computed by a physics engine.

The center of the program is the joySetForcesXY() function, which
accepts two-dimensional force data and sends it to the joystick.


DIFF1.H

/**************************************************************************

    DIFF1.H - DirectInput force feedback simple sample 1

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#define STRICT
#include <windows.h>
#include <commctrl.h>


DIFF1.CPP

/**************************************************************************

    DIFF1.CPP - DirectInput simple force feedback sample 1

    Demonstrates an application which plays a force on the joystick
    in response to mouse clicks.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include "diff1.h"
#include <windowsx.h>
#include <dinput.h>
#include <math.h>

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

char c_szClassName[] = "DIFF1";

HINSTANCE       g_hinst;                /* My instance handle */
BOOL            g_fPaused = TRUE;       /* Should I be paused? */
int             g_xForce;               /* Coordinates of the active force */
int             g_yForce;
int             g_cxClient;             /* Client window size */
int             g_cyClient;

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT           g_pdi;
LPDIRECTINPUTDEVICE2    g_pJoystick;
LPDIRECTINPUTEFFECT     g_pEffect;

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void
Complain(
    HWND hwndOwner,
    HRESULT hr,
    LPCSTR pszMessage
)
{
    MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}

/****************************************************************************
 *
 *      EnumFFJoysticksCallback
 *
 *      Called once for each enumerated force feedback joystick.
 *
 *      If we find one, create a device interface on it so we can
 *      play with it.
 *
 *      Parameters:
 *
 *          pinst
 *
 *              Pointer to DIDEVICEINSTANCE structure which describes
 *              the device.
 *
 *          lpvContext
 *
 *              The pointer we passed to EnumDevices which we don't
 *              use for anything.
 *
 ****************************************************************************/

BOOL CALLBACK
EnumFFJoysticksCallback(LPCDIDEVICEINSTANCE pinst, LPVOID lpvContext)
{
    HRESULT hr;
    LPDIRECTINPUTDEVICE pdev;
    LPDIRECTINPUTDEVICE2 pdev2;

    /*
     *  Obtain an interface to the enumerated force feedback joystick.
     *
     *  Parameters:
     *
     *      pinst->guidInstance
     *
     *          The instance GUID for the device we wish to access.
     *
     *      &pdev
     *
     *          Receives pointer to the IDirectInputDevice interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pdi->CreateDevice(pinst->guidInstance, &pdev, NULL);

    /*
     *  If it failed, then we can't use this joystick for some
     *  bizarre reason.  (Maybe the user unplugged it while we
     *  were in the middle of enumerating it.)
     *
     *  Continue enumerating; maybe we'll have better luck with the
     *  next one.
     */
    if (FAILED(hr)) {
        return DIENUM_CONTINUE;
    }

    /*
     *  We really want to use IDirectInputDevice2, so move there
     *  once and for all.
     *
     *  Parameters:
     *
     *      IID_IDirectInputDevice2
     *
     *          The interface we are requesting.
     *
     *      &pdev2
     *
     *          Receives a pinter to the new interface.
     */

    hr = pdev->QueryInterface(IID_IDirectInputDevice2,
                              (LPVOID *)&pdev2);

    /*
     *  Whether or not the QueryInterface worked, we are finished
     *  with the old interface.
     */
    pdev->Release();

    /*
     *  If the QueryInterface failed, then something weird happened.
     *  Maybe the currently-installed version of DirectInput doesn't
     *  support force feedback.
     *
     *  Continue enumerating; maybe we'll have better luck with the
     *  next one.
     */
    if (FAILED(hr)) {
        return DIENUM_CONTINUE;
    }

    /*
     *  We successfully created an IDirectInputDevice2.
     *
     *  No point in looking for another one.
     */
    g_pJoystick = pdev2;
    return DIENUM_STOP;

}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 *      This entails the following four functions:
 *
 *          DirectInputCreate
 *          IDirectInput::EnumDevices           (to find a joystick)
 *          IDirectInputDevice2::SetDataFormat
 *          IDirectInputDevice2::SetCooperativeLevel
 *          IDirectInputDevice2::SetProperty    (to disable auto-center)
 *          IDirectInputDevice2::CreateEffect
 *
 ****************************************************************************/

BOOL
DIInit(HWND hwnd)
{
    HRESULT hr;

    /*
     *  Register with the DirectInput subsystem and get a pointer
     *  to a IDirectInput interface we can use.
     *
     *  Parameters:
     *
     *      g_hinst
     *
     *          Instance handle to our application or DLL.
     *
     *      DIRECTINPUT_VERSION
     *
     *          The version of DirectInput we were designed for.
     *          We take the value from the <dinput.h> header file.
     *
     *      &g_pdi
     *
     *          Receives pointer to the IDirectInput interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Look for a force feedback joystick we can use for this
     *  sample program.
     *
     *  Parameters:
     *
     *      DIDEVTYPE_JOYSTICK
     *
     *          Enumerate only joystick devices.
     *
     *      EnumFFJoysticksCallback
     *
     *          Callback function that is called once for
     *          each force feedback joystick found.
     *
     *      NULL
     *
     *          Context which is passed to the callback function.
     *
     *      DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK
     *
     *          Flags that further restrict the enumeration.
     *
     *          We are interested only in attached joysticks
     *          which support force feedback.
     */
    hr = g_pdi->EnumDevices(DIDEVTYPE_JOYSTICK,
                            EnumFFJoysticksCallback,
                            NULL,
                            DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK);

    if (g_pJoystick == NULL) {
        Complain(hwnd, hr, "Couldn't find any force feedback joysticks");
        return FALSE;
    }

    /*
     *  Set the data format to "simple joystick format".
     *
     *  A data format specifies which controls on a device we
     *  are interested in, and how they should be reported.
     *
     *  This tells DirectInput that we will be passing a
     *  DIJOYSTATE structure to IDirectInputDevice2::GetDeviceState.
     *  Even though we won't actually do it in this sample.
     *  But setting the data format is important so that
     *  the DIJOFS_* values work properly.
     *
     *  Parameters:
     *
     *      c_dfDIJoystick
     *
     *          Predefined data format which describes
     *          a DIJOYSTATE structure.
     */
    hr = g_pJoystick->SetDataFormat(&c_dfDIJoystick);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat");
        return FALSE;
    }

    /*
     *  Set the cooperativity level to let DirectInput know how
     *  this device should interact with the system and with other
     *  DirectInput applications.
     *
     *  Parameters:
     *
     *      DISCL_EXCLUSIVE
     *
     *          Exclusive access is required in order to perform
     *          force feedback.
     *
     *      DISCL_FOREGROUND
     *
     *          If the user switches away from our application,
     *          automatically release the joystick back to the system.
     *
     */
    hr = g_pJoystick->SetCooperativeLevel(hwnd,
                                          DISCL_EXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel");
        return FALSE;
    }

    /*
     *  Since we will be playing force feedback effects,
     *  we should disable the auto-centering spring.
     *
     *  DIPROPDWORD::diph.dwSize
     *
     *      Must be sizeof(DIPROPDWORD)
     *
     *  DIPROPDWORD::diph.dwHeaderSize
     *
     *      Must be sizeof(DIPROPHEADER)
     *
     *  DIPROPDWORD::diph.dwObj
     *
     *      Must be zero for device properties.
     *
     *  DIPROPDWORD::diph.dwHow
     *
     *      DIPH_DEVICE for device properties.
     *
     *  DIPROPDWORD::dwData
     *
     *      FALSE to disable auto-centering.
     */
    DIPROPDWORD dipdw;
    dipdw.diph.dwSize = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = FALSE;

    hr = g_pJoystick->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetProperty(Autocenter)");
        return FALSE;
    }

    /*
     *  This application needs only one effect:  Applying raw forces.
     *
     *  DIEFFECT::dwSize
     *
     *          Must be sizeof(DIEFFECT).
     *
     *  DIEFFECT::dwFlags
     *
     *          DIEFF_CARTESIAN because we will be applying X and Y
     *          forces, not angles.
     *
     *          DIEFF_OBJECTOFFSETS because we will be using the
     *          DIJOFS_* macros to specify the axes.
     *
     *  DIEFFECT::dwDuration
     *
     *          INFINITE because we want the force to continue playing
     *          indefinitely until we explicitly change it.
     *
     *  DIEFFECT::dwSamplePeriod
     *
     *          0 means "use default".  Using a custom sample period
     *          is a special effect which we don't particularly care
     *          about.
     *
     *  DIEFFECT::dwGain
     *
     *          DI_FFNOMINALMAX to play all values at exactly the
     *          strength we specify.
     *
     *  DIEFFECT::dwTriggerButton
     *
     *          DIEB_NOTRIGGER because we don't want this effect
     *          to be associated with a trigger.
     *
     *  DIEFFECT::dwTriggerRepeatInterval
     *
     *          0 because there no trigger.
     *
     *  DIEFFECT::cAxes
     *
     *          2 because we have two axes, X and Y.
     *
     *  DIEFFECT::rgdwAxes
     *
     *          Points to an array which identifies the two axes
     *          we want to talk to, namely X and Y.
     *
     *  DIEFFECT::rglDirection
     *
     *          Points to an array which gives the direction in
     *          which the force should be applied.
     *          Nothing yet.
     *
     *  DIEFFECT::lpEnvelope
     *
     *          NULL because we don't want to apply an envelope
     *          to the effect.
     *
     *  DIEFFECT::cbTypeSpecificParameters
     *  DIEFFECT::lpvTypeSpecificParameters
     *
     *          Size of and pointer to type-specific parameters.
     *          For a constant force effect, we need a
     *          DICONSTANTFORCE structure.
     */

    DIEFFECT eff;
    DWORD rgdwAxes[2] = { DIJOFS_X, DIJOFS_Y };
    LONG rglDirection[2] = { 0, 0 };
    DICONSTANTFORCE cf = { 0 };

    eff.dwSize = sizeof(DIEFFECT);
    eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
    eff.dwDuration = INFINITE;
    eff.dwSamplePeriod = 0;
    eff.dwGain = DI_FFNOMINALMAX;
    eff.dwTriggerButton = DIEB_NOTRIGGER;
    eff.dwTriggerRepeatInterval = 0;
    eff.cAxes = 2;
    eff.rgdwAxes = rgdwAxes;
    eff.rglDirection = rglDirection;
    eff.lpEnvelope = 0;
    eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
    eff.lpvTypeSpecificParams = &cf;

    /*
     *  Now create this effect we prepared.
     *
     *  Parameters:
     *
     *      GUID_ConstantForce
     *
     *          We are playing a raw force, plain and simple.
     *
     *      &eff
     *
     *          The DIEFFECT structure that describes it.
     *
     *      &g_pEffect
     *
     *          Receives pointer to the IDirectInputEffect interface
     *          that was created.
     *
     *      NULL
     *
     *          We do not use OLE aggregation, so this parameter
     *          must be NULL.
     *
     */
    hr = g_pJoystick->CreateEffect(GUID_ConstantForce,
                                   &eff,
                                   &g_pEffect,
                                   NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateEffect");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void
DITerm(void)
{

    /*
     *  Destroy any lingering IDirectInputEffect object.
     */
    if (g_pEffect) {

        g_pEffect->Release();
        g_pEffect = NULL;
    }

    /*
     *  Destroy any lingering IDirectInputDevice object.
     */
    if (g_pJoystick) {

        /*
         *  Cleanliness is next to godliness.  Unacquire the device
         *  one last time just in case we got really confused and tried
         *  to exit while the device is still acquired.
         */
        g_pJoystick->Unacquire();

        g_pJoystick->Release();
        g_pJoystick = NULL;
    }

    /*
     *  Destroy any lingering IDirectInput object.
     */
    if (g_pdi) {
        g_pdi->Release();
        g_pdi = NULL;
    }

}

/****************************************************************************
 *
 *      joySetForcesXY
 *
 *      Apply the X and Y forces to the effect we prepared.
 *
 *      Parameters:
 *
 *          peff
 *
 *              Pointer to a constant-force LPDIRECTINPUEFFECT
 *              which we will push in the appropriate direction
 *              with the appropriate amount of force.
 *
 *          x, y
 *
 *              X and Y forces to apply, respectively, in the
 *              range -DI_FFNOMINALMAX to +DI_FFNOMINALMAX.
 *
 ****************************************************************************/

HRESULT
joySetForcesXY(LPDIRECTINPUTEFFECT peff, int x, int y)
{
    HRESULT hres;

    /*
     *  Modifying an effect is basically the same as creating
     *  a new one, except you need only specify the parameters
     *  you are modifying; the rest are left alone.
     *
     *  DIEFFECT::dwSize
     *
     *          Must be sizeof(DIEFFECT).
     *
     *  DIEFFECT::dwFlags
     *
     *          DIEFF_CARTESIAN because we will be applying X and Y
     *          forces, not angles.
     *
     *          DIEFF_OBJECTOFFSETS because we used the
     *          DIJOFS_* macros to specify the axes.
     *
     *  DIEFFECT::cAxes
     *
     *          2 because we have two axes, X and Y.
     *
     *  DIEFFECT::rglDirection
     *
     *          Points to an array which gives the direction in
     *          which the force should be applied.
     *
     *          We use the x and y values directly to specify
     *          the direction.  Note that DirectInput currently
     *          uses these values to specify direction only, not
     *          magnitude.  Magnitude needs to be specified
     *          elsewhere.  (See below.)
     *
     *  DIEFFECT::cbTypeSpecificParameters
     *  DIEFFECT::lpvTypeSpecificParameters
     *
     *          Size of and pointer to type-specific parameters.
     *          For a constant force effect, we need a
     *          DICONSTANTFORCE structure.
     *
     *  DICONSTANTFORCE::lMagnitude
     *
     *          The total magnitude of the force, which is
     *          the hypotenuse of x and y.
     */

    LONG rglDirection[2] = { x, y };

    DICONSTANTFORCE cf;
    cf.lMagnitude = (DWORD)sqrt((double)x * (double)x +
                                (double)y * (double)y);

    DIEFFECT eff;
    eff.dwSize = sizeof(DIEFFECT);
    eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
    eff.cAxes = 2;
    eff.rglDirection = rglDirection;
    eff.lpEnvelope = 0;
    eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
    eff.lpvTypeSpecificParams = &cf;

    /*
     *  Now set the new parameters and start the effect immediately.
     *
     *  Parameters:
     *
     *      &eff
     *
     *          The DIEFFECT structure that describes the new parameters.
     *
     *      DIEP_DIRECTION
     *      DIEP_TYPESPECIFICPARAMS
     *
     *          We want to change the direction and the DICONSTANTFORCE.
     *
     *      DIEP_START
     *
     *          Start the effect as soon as the parameters are downloaded.
     */
    hres = peff->SetParameters(&eff, DIEP_DIRECTION |
                                     DIEP_TYPESPECIFICPARAMS |
                                     DIEP_START);

    return hres;
}

/****************************************************************************
 *
 *      Ex_SyncAcquire
 *
 *      Acquire or unacquire the joystick, depending on the the g_fPaused
 *      flag.  This synchronizes the device with our internal view of
 *      the world.
 *
 ****************************************************************************/

void
Ex_SyncAcquire(HWND hwnd)
{
    if (g_fPaused) {
        if (g_pJoystick) {
            g_pJoystick->Unacquire();
        }
    } else {
        if (g_pJoystick) g_pJoystick->Acquire();
        if (g_pEffect) {
            g_pEffect->Start(1, 0);
        }
    }
}

/****************************************************************************
 *
 *      Ex_OnSize
 *
 *      The window client size changed.  Remember the new size.
 *
 ****************************************************************************/

void
Ex_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    g_cxClient = cx;
    g_cyClient = cy;
}

/****************************************************************************
 *
 *      Ex_OnPaint
 *
 *      Paint our little target area thingie.
 *
 *      We draw a small crosshairs at the center (so the user knows
 *      where the center is)
 *
 ****************************************************************************/

void
Ex_OnPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        HPEN hpenOld, hpenBlack;
        HBRUSH hbrOld, hbrBlack;
        int x, y;

        /*
         *  Everything is scaled to the size of the window.
         */

        hpenBlack = GetStockPen(BLACK_PEN);
        hpenOld = SelectPen(hdc, hpenBlack);

        x = g_cxClient / 2;
        y = g_cyClient / 2;

        MoveToEx(hdc, x, y - 10, NULL);
        LineTo(hdc, x, y + 10 + 1);

        MoveToEx(hdc, x - 10, y, NULL);
        LineTo(hdc, x + 10 + 1, y);

        hbrBlack = GetStockBrush(BLACK_BRUSH);
        hbrOld = SelectBrush(hdc, hbrBlack);

        x = MulDiv(g_cxClient, g_xForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX);
        y = MulDiv(g_cyClient, g_yForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX);

        Ellipse(hdc, x-5, y-5, x+6, y+6);

        SelectBrush(hdc, hbrOld);
        SelectPen(hdc, hpenOld);

        EndPaint(hwnd, &ps);
    }

}

/****************************************************************************
 *
 *      Ex_CoordToForce
 *
 *      Convert a coordinate 0 <= x <= cx to a force value
 *      in the range -DI_FFNOMINALMAX to +DI_FFNOMINALMAX.
 *
 ****************************************************************************/

int
Ex_CoordToForce(int x, int cx)
{
    x = MulDiv(x, 2 * DI_FFNOMINALMAX, cx) - DI_FFNOMINALMAX;
    if (x < -DI_FFNOMINALMAX) {
        x = -DI_FFNOMINALMAX;
    }
    if (x > +DI_FFNOMINALMAX) {
        x = +DI_FFNOMINALMAX;
    }
    return x;
}

/****************************************************************************
 *
 *      Ex_OnMouseMove
 *
 *      If the mouse button is down, then change the direction of
 *      the force to match the new location.
 *
 ****************************************************************************/

void
Ex_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
{
    if (keyFlags & MK_LBUTTON) {
        g_xForce = Ex_CoordToForce(x, g_cxClient);
        g_yForce = Ex_CoordToForce(y, g_cyClient);

        InvalidateRect(hwnd, 0, TRUE);
        UpdateWindow(hwnd);

        joySetForcesXY(g_pEffect, g_xForce, g_yForce);

    }
}

/****************************************************************************
 *
 *      Ex_OnLButtonDown
 *
 *      Capture the mouse so we can follow it, and start updating the
 *      force information.
 *
 ****************************************************************************/

void
Ex_OnLButtonDown(HWND hwnd, BOOL fDblClk, int x, int y, UINT keyFlags)
{
    SetCapture(hwnd);
    Ex_OnMouseMove(hwnd, x, y, MK_LBUTTON);
}

/****************************************************************************
 *
 *      Ex_OnLButtonUp
 *
 *      Stop capturing the mouse when the button goes up.
 *
 ****************************************************************************/

void
Ex_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
{
    ReleaseCapture();
}

/****************************************************************************
 *
 *      Ex_WndProc
 *
 *      Window procedure for simple force feedback sample.
 *
 ****************************************************************************/

LRESULT CALLBACK
Ex_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch (msg) {

    HANDLE_MSG(hwnd, WM_PAINT, Ex_OnPaint);
    HANDLE_MSG(hwnd, WM_MOUSEMOVE, Ex_OnMouseMove);
    HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Ex_OnLButtonDown);
    HANDLE_MSG(hwnd, WM_LBUTTONUP, Ex_OnLButtonUp);
    HANDLE_MSG(hwnd, WM_SIZE, Ex_OnSize);

    /*
     *  WM_ACTIVATE
     *
     *      Windows sends this message when the window becomes
     *      the active window or stops being the active window.
     *
     *      wParam = WA_INACTIVE if window is no longer active
     *
     *      wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
     *
     *      If we are losing activation, then pause.
     *
     *      If we are gaining activation, then unpause.
     *
     *      After deciding whether we are paused or unpaused,
     *      tell DirectInput that we don't (paused) or do (unpaused)
     *      want access to the joystick.
     *
     */
    case WM_ACTIVATE:
        g_fPaused = wParam == WA_INACTIVE;
        Ex_SyncAcquire(hwnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND
AppInit(
    HINSTANCE hinst,
    int nCmdShow
)
{

    /*
     *  Save instance handle for future reference.
     */
    g_hinst = hinst;

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szClassName;
    wc.hbrBackground  = GetStockBrush(WHITE_BRUSH);
    wc.hInstance      = hinst;
    wc.style          = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc    = Ex_WndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    HWND hwnd = CreateWindow(
                    c_szClassName,                  // Class name
                    "DIFF1 - Click in target to change force value", // Caption
                    WS_OVERLAPPEDWINDOW,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Position
                    CW_USEDEFAULT, CW_USEDEFAULT,   // Size
                    NULL,                           // No parent
                    NULL,                           // No menu
                    g_hinst,                        // inst handle
                    0                               // no params
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Standard message loop.
         */
        while (GetMessage(&msg, NULL, 0, 0)) {

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    DITerm();

    return msg.wParam;

}


README.TXT

This program is useful for testing multiple exclusive mode apps interacting
with multiple non-exclusive mode apps.  The program displays a rotating
donut in the upper left corner of the screen.  It may be terminated by
pressing Esc or F12.

Various command line switches can be specified to modify the characteristics
of this program.  Each switch consists of one character and need not be
preceeded with a dash or slash.  The switches are as follows:

0   -   Display in far left position
1   -   Display in middle position
2   -   Display in right position
X   -   Use exclusive mode
A   -   Switch mode to 640x480x8 and use exclusive mode
B   -   Switch mode to 800x600x8 and use exclusive mode
C   -   Switch mode to 1024x768x8 and use exclusive mode
D   -   Switch mode to 1280x1024x8 and use exclusive mode

The switches may be combined.  If two switches are used which contradict,
the last switch specified will be in effect.

If this program is running in non-exclusive mode, it attempts to continue to
run even when it loses focus.  If it is running in exclusive mode, the
program does not attempt to modify the screen when it doesn't have focus.


DONUT.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donut.cpp
 *
 ***************************************************************************/

#define NAME "Donut"
#define TITLE "Donut"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil.h"

char szBitmap[] = "DONUT";

int             gPos = 0;
BOOL            gExclusive = FALSE;
int             gMode = 0;
HWND            hwnd;

LPDIRECTDRAW            lpDD;           // DirectDraw object
LPDIRECTDRAWSURFACE     lpDDSPrimary;   // DirectDraw primary surface
LPDIRECTDRAWSURFACE     lpDDSOne;       // Offscreen surface 1
LPDIRECTDRAWSURFACE     lpDDSTwo;       // Offscreen surface 2
LPDIRECTDRAWPALETTE     lpDDPal;        // DirectDraw palette
BOOL                    bActive;        // is application active?

/*
 * restoreAll
 *
 * restore all lost objects
 */
HRESULT restoreAll( void )
{
    HRESULT     ddrval;

    ddrval = lpDDSPrimary->Restore();
    if( ddrval == DD_OK )
    {
ddrval = lpDDSOne->Restore();
if( ddrval == DD_OK )
{
    ddrval = lpDDSTwo->Restore();
    if( ddrval == DD_OK )
    {
DDReLoadBitmap(lpDDSOne, szBitmap);
    }
}
    }
    return ddrval;

} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORD        lastTickCount = 0;
    static int          currentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORD               thisTickCount;
    RECT                rcRect;
    DWORD               delay = 17;
    HRESULT             ddrval;
    int                 pos;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= delay)
    {
return;
    }

    switch( gPos )
    {
case 0: pos = 0; break;
case 1: pos = 64; break;
case 2: pos = 128; break;
    }
    rcRect.left = 0;
    rcRect.top = 0;
    rcRect.right = 64;
    rcRect.bottom = 64;

    // restore a previously saved patch
    while( haveBackground )
    {
ddrval = lpDDSPrimary->BltFast( pos, 0, lpDDSTwo, &rcRect, FALSE );
if( ddrval == DD_OK )
{
    haveBackground = TRUE;
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }

    rcRect.left = pos;
    rcRect.right = pos+64;
    // Save the current primary surface that we are about to overwrite
    while( 1 )
    {
haveBackground = FALSE;
ddrval = lpDDSTwo->BltFast( 0, 0, lpDDSPrimary,
    &rcRect, DDBLTFAST_NOCOLORKEY);

if( ddrval == DD_OK )
{
    haveBackground = TRUE;
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) > delay)
    {
// Move to next frame;
lastTickCount = thisTickCount;
currentFrame++;
if(currentFrame > 59)
{
    currentFrame = 0;
}
    }

    // Blit the stuff for the next frame
    rcRect.left   = currentFrame%10*64;
    rcRect.top    = currentFrame/10*64;
    rcRect.right  = currentFrame%10*64 + 64;
    rcRect.bottom = currentFrame/10*64 + 64;

    while( 1 )
    {
ddrval = lpDDSPrimary->BltFast( pos, 0, lpDDSOne,
    &rcRect, DDBLTFAST_SRCCOLORKEY);

if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
    if( ddrval != DD_OK )
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }
    if(ddrval != DD_OK)
    {
return;
    }
} /* updateFrame */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
    lpDDSOne->Release();
    lpDDSOne = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }
    // Clean up the screen on exit
    RedrawWindow( NULL, NULL, NULL, RDW_INVALIDATE | RDW_ERASE |
     RDW_ALLCHILDREN );

} /* finiObjects */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    hwnd = hWnd;
    switch( message )
    {
    case WM_ACTIVATEAPP:
bActive = wParam;
break;

    case WM_SETCURSOR:
SetCursor(NULL);
return TRUE;

    case WM_PALETTECHANGED:
if ((HWND)wParam == hWnd)
    break;
// fall through to WM_QUERYNEWPALETTE
    case WM_QUERYNEWPALETTE:
// install our palette here
if (lpDDPal)
{
    lpDDSPrimary->SetPalette(lpDDPal);
}
DDReLoadBitmap(lpDDSOne, szBitmap);
break;

    case WM_CREATE:
break;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
case VK_F12:
    PostMessage(hWnd,WM_CLOSE,0,0);
    break;
}
break;

    case WM_DESTROY:
finiObjects();
PostQuitMessage( 0 );
break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * doInit - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    HWND                hwnd;
    WNDCLASS            wc;
    DDSURFACEDESC       ddsd;
    HRESULT             ddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
0,
NAME,
TITLE,
WS_POPUP,
0,
0,
1,
1,
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Get exclusive mode if requested
    if(gExclusive)
    {
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    }
    else
    {
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );
    }
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Set the video mode to 640x480x8
    switch( gMode )
    {
case 1:  ddrval = lpDD->SetDisplayMode( 640, 480, 8); break;
case 2:  ddrval = lpDD->SetDisplayMode( 800, 600, 8); break;
case 3:  ddrval = lpDD->SetDisplayMode( 1024, 768, 8); break;
case 4:  ddrval = lpDD->SetDisplayMode( 1280, 1024, 8); break;
    }
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    if (lpDDPal)
    {
lpDDSPrimary->SetPalette(lpDDPal);
    }

    lpDDSOne = DDLoadBitmap(lpDD, szBitmap, 0, 0);
    if( lpDDSOne == NULL )
    {
return initFail(hwnd);
    }

    // set color key to black
    DDSetColorKey(lpDDSOne, RGB(0,0,0));

    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = 64;
    ddsd.dwWidth = 64;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSTwo, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    return TRUE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;
    LPSTR       c;

    for(c=lpCmdLine; *c != '\0'; c++)
    {
switch( *c )
{
    case '0': gPos = 0; break;
    case '1': gPos = 1; break;
    case '2': gPos = 2; break;
    case 'X': gExclusive = TRUE; break;
    case 'A': gExclusive = TRUE; gMode = 1; break;
    case 'B': gExclusive = TRUE; gMode = 2; break;
    case 'C': gExclusive = TRUE; gMode = 3; break;
    case 'D': gExclusive = TRUE; gMode = 4; break;
}
    }

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
else if( !gExclusive || bActive )
{
    updateFrame();
}
else
{
    WaitMessage();
}
    }
} /* WinMain */


README.TXT

Space Donuts Sample Game
------------------

This game demonstrates many of the features of DirectDraw.  It will take
advantage of hardware acceleration if it is supported by the driver.

Roids defaults to 640x480 at 256 colors.  You may specify a different
resolution and pixel depth on the command line (roids 800x600x16).

This program requires less than 1 Meg of video ram.  However, all of its
art may not fit in the vram on a 1 Meg rectangular memory card.

The commands which this game recognizes are listed on the opening screen.

    ESC, F12        - Quit
    NUMPAD 4        - Turn left
    NUMPAD 6        - Turn right
    NUMPAD 5        - Stop moving
    NUMPAD 8        - Accelerate forward
    NUMPAD 2        - Accelerate backward
    SPACEBAR        - Fire
    NUMPAD 7        - Shield
    ENTER           - Starts game
    F5              - toggle frame rate display
    F3              - toggle audio
    F1              - toggle cheesy trails effect

Command line switches:

    -e              - Use emulation, not hardware acceleration
    -t              - Test mode.  Runs game for you.
    -x              - Stress mode.  Never halt if you can help it.
    -S              - turn off sound

   These switches may be followed by three option numbers representing:
        X resolution
        Y resolution
        Bits per pixel

Sound code
----------

The sound code in this application is deliberately designed
to be stressful.  For example, each bullet on the screen uses a different
sound buffer.  Over 70 sound buffers are created (including duplicates)
and 20-25 may be playing at any time.  This could be made much more
efficient, but we wanted code to stress our API and mixer.

The sounds are implemented using the helper functions in dsutil.h and
dsutil.c (found in the sdk\samples\misc directory).  These helper
functions may help you to add sound to your application quickly and
easily.


DONUTS.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donuts.c
 *  Content:    Shoot-em-up game
 *
 *
 ***************************************************************************/
#define INITGUID

#include "donuts.h"

LPDIRECTDRAWSURFACE     lpFrontBuffer;
LPDIRECTDRAWSURFACE     lpBackBuffer;
LPDIRECTDRAWSURFACE     lpDonut;
LPDIRECTDRAWSURFACE     lpPyramid;
LPDIRECTDRAWSURFACE     lpCube;
LPDIRECTDRAWSURFACE     lpSphere;
LPDIRECTDRAWSURFACE     lpShip;
LPDIRECTDRAWSURFACE     lpNum;
LPDIRECTDRAW            lpDD;
LPDIRECTDRAWPALETTE     lpArtPalette;
LPDIRECTDRAWPALETTE     lpSplashPalette;
BOOL                    bSoundEnabled = FALSE;
BOOL                    bPlayIdle = FALSE;
BOOL                    bPlayBuzz = FALSE;
BOOL                    bPlayRev = FALSE;
BOOL                    lastThrust = FALSE;
BOOL                    lastShield = FALSE;
int                     showDelay = 0;
HWND                    hWndMain;
HACCEL                  hAccel;
HINSTANCE               hInst;
BOOL                    bShowFrameCount=TRUE;
BOOL                    bIsActive;
BOOL                    bMouseVisible;
DWORD                   dwFrameCount;
DWORD                   dwFrameTime;
DWORD                   dwFrames;
DWORD                   dwFramesLast;
BOOL                    bUseEmulation;
BOOL                    bTest=FALSE;
BOOL                    bStress=FALSE;
DWORD                   dwTransType;
RGBQUAD                 SPalette[256];
DWORD                   lastTickCount;
int                     score;
int                     ProgramState;
int                     level;
int                     restCount;
DWORD                   dwFillColor;
BOOL                    bSpecialEffects = FALSE;
int                     iForceErase = 0;
DWORD                   ShowLevelCount = 3000;
DWORD                   ScreenX;
DWORD                   ScreenY;
DWORD                   ScreenBpp;
BOOL                    bWantSound = TRUE;  //global hack to turn off sound

int getint(char**p, int def);

#ifdef DEBUG
char                    DebugBuf[256];
BOOL                    bHELBlt = FALSE;
#endif

DBLNODE                 DL;             // Display List

#ifdef USE_DSOUND
LPDIRECTSOUND           lpDS;
HSNDOBJ                 hsoBeginLevel     = NULL;
HSNDOBJ                 hsoEngineIdle     = NULL;
HSNDOBJ                 hsoEngineRev      = NULL;
HSNDOBJ                 hsoSkidToStop     = NULL;
HSNDOBJ                 hsoShieldBuzz     = NULL;
HSNDOBJ                 hsoShipExplode    = NULL;
HSNDOBJ                 hsoFireBullet     = NULL;
HSNDOBJ                 hsoShipBounce     = NULL;
HSNDOBJ                 hsoDonutExplode   = NULL;
HSNDOBJ                 hsoPyramidExplode = NULL;
HSNDOBJ                 hsoCubeExplode    = NULL;
HSNDOBJ                 hsoSphereExplode  = NULL;
#endif

void setup_game(void)
{
    restCount = GetTickCount();
    initLevel( ++level );
    // set the palette
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpArtPalette );
}

/*
 *
 *  AppPause
 *
 */
void AppPause(void)
{
    lpDD->lpVtbl->FlipToGDISurface(lpDD);
    DrawMenuBar(hWndMain);
    RedrawWindow(hWndMain, NULL, NULL, RDW_FRAME);

}

/*
 *
 *  AppUnpause
 *
 *  Reset the various time counters so the donuts don't suddenly
 *  jump halfways across the screen and so the frame rate remains accurate.
 *
 */
void AppUnpause(void)
{
    iForceErase = 2;
    lastTickCount = dwFrameTime = timeGetTime();
}

/*
 *  CheckOneMenuItem
 *
 *  Helper function that checks a single item in a menu.
 */
void CheckOneMenuItem(HMENU hmenu, UINT idc, BOOL fCheck)
{
    CheckMenuItem(hmenu, idc,
                  fCheck ? (MF_BYCOMMAND | MF_CHECKED)
                         : (MF_BYCOMMAND | MF_UNCHECKED));
}

/*
 *  CheckMenuItems
 *
 *  Sync the menu checkmarks with our internal variables
 */
void CheckMenuItems(HWND hwnd)
{
    HMENU hmenu = GetMenu(hwnd);

    CheckOneMenuItem(hmenu, IDC_TRAILS, bSpecialEffects);
#ifdef USE_DSOUND
    CheckOneMenuItem(hmenu, IDC_AUDIO, bWantSound && bSoundEnabled);
#endif
    CheckOneMenuItem(hmenu, IDC_FRAMERATE, bShowFrameCount);

}

/*
 * MainWndproc
 *
 * Callback for all Windows messages
 */
long FAR PASCAL MainWndproc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;
    UINT        cmd;

    switch( message )
    {
    case WM_ACTIVATEAPP:
        bIsActive = (BOOL) wParam;
if( bIsActive )
    {
    bMouseVisible = FALSE;
            lastTickCount = GetTickCount();
            bSpecialEffects = FALSE;
       // we are active, need to reacquire the keyboard
            ReacquireInput();
}
else
{
    bMouseVisible = TRUE;
       // DirectInput automatically unacquires for us in FOREGROUND mode
}
        break;

    case WM_CREATE:
        break;

    case WM_SETCURSOR:
if( !bMouseVisible )
{
    SetCursor(NULL);
}
else
{
    SetCursor(LoadCursor( NULL, IDC_ARROW ));
}
        return TRUE;

    case WM_COMMAND:
        cmd = GET_WM_COMMAND_ID(wParam, lParam);

        if (cmd >= IDC_DEVICES && cmd < IDC_DEVICES + 100) {
            PickInputDevice(cmd - IDC_DEVICES);
        } else switch (cmd) {

        case IDC_FRAMERATE:
            bShowFrameCount = !bShowFrameCount;
            if( bShowFrameCount )
            {
                dwFrameCount = 0;
                dwFrameTime = timeGetTime();
            }
            break;

        case IDC_STARTGAME:
            if( ProgramState == PS_SPLASH )
            {
                ProgramState = PS_BEGINREST;
                setup_game();
            }
            break;

        case IDC_QUIT:
            PostMessage( hWnd, WM_CLOSE, 0, 0 );
            return 0;

        case IDC_AUDIO:
#ifdef USE_DSOUND
            if(bWantSound)
            {
        if( bSoundEnabled )
        {
            DestroySound();
        }
        else
        {
            InitializeSound();
        }
                }
#endif
            break;

        case IDC_TRAILS:
            bSpecialEffects = !bSpecialEffects;
            break;
        }
        break;

    case WM_INITMENU:
        CheckMenuItems(hWndMain);
        break;

    case WM_ERASEBKGND:
    return 1;

    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
        EndPaint( hWnd, &ps );
        return 1;

    case WM_DESTROY:
        CleanupInput();
        DestroyGame();
        PostQuitMessage( 0 );
        break;

    case WM_ENTERMENULOOP:
      AppPause();
      break;

    case WM_EXITMENULOOP:
      AppUnpause();
      break;

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

} /* MainWndproc */

/*
 * initApplication
 *
 * Do that Windows initialization stuff...
 */
static BOOL initApplication( HANDLE hInstance, int nCmdShow )
{
    WNDCLASS    wc;
    BOOL        rc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndproc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(DONUTS_ICON));
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName =  MAKEINTRESOURCE(DONUTS_MENU);
    wc.lpszClassName = "DonutsClass";
    rc = RegisterClass( &wc );
    if( !rc )
    {
        return FALSE;
    }

    hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(DONUTS_ACCEL));
    if ( !hAccel )
    {
        return FALSE;
    }

    hWndMain = CreateWindowEx(0,  // WS_EX_TOPMOST,
        "DonutsClass",
        "Donuts",
        WS_VISIBLE | // so we don't have to call ShowWindow
        WS_POPUP |   // non-app window
        WS_CAPTION | // so our menu doesn't look ultra-goofy
        WS_SYSMENU,  // so we get an icon in the tray
        0,
        0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    UpdateWindow( hWndMain );

#ifdef USE_DSOUND
    /*
     *  If sound is globally disabled, then disable the sound menu.
     */
    if (!bWantSound) {
        EnableMenuItem(GetMenu(hWndMain), IDC_AUDIO,
                       MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
    }
#endif

    return TRUE;

} /* initApplication */

/*
 * WinMain
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                        int nCmdShow )
{
    MSG     msg;

    // save off application instance
    hInst = hInstance;

    while( lpCmdLine[0] == '-' )
    {
        lpCmdLine++;

        switch (*lpCmdLine++)
        {
        case 'e':
            bUseEmulation = TRUE;
            break;
        case 't':
            bTest = TRUE;
            break;
        case 'S':
            bWantSound = FALSE;
            break;
        case 'x':
            bStress= TRUE;
            bTest = TRUE;
            break;
        }
        while( IS_SPACE(*lpCmdLine) )
        {
            lpCmdLine++;
        }
    }

    ScreenX = getint(&lpCmdLine, 640);
    ScreenY = getint(&lpCmdLine, 480);
    ScreenBpp = getint(&lpCmdLine, 8);

    if( !initApplication(hInstance, nCmdShow) )
    {
        return FALSE;
    }

    if( !InitializeGame() )
    {
        DestroyWindow( hWndMain );
        return FALSE;
    }

    dwFrameTime = timeGetTime();

    while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( !GetMessage( &msg, NULL, 0, 0 ) )
            {
                return msg.wParam;
            }
            if ( !TranslateAccelerator( hWndMain, hAccel, &msg ) ) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else if ( bIsActive )
        {
            UpdateFrame();
        }
        else
        {
            WaitMessage();
        }
    }

} /* WinMain */

void DestroyGame( void )
{
}

BOOL InitializeGame( void )
{
    DDCAPS          ddcaps;
    HRESULT         ddrval;
    DDSURFACEDESC   ddsd;
    DDSCAPS         ddscaps;
#ifdef NT_HACK
    DDSURFACEDESC DDSurfDesc;
#endif

    score = 0;
    if( bTest )
    ShowLevelCount = 1000;

    if( bUseEmulation )
        ddrval = DirectDrawCreate( (LPVOID) DDCREATE_EMULATIONONLY, &lpDD, NULL );
    else
        ddrval = DirectDrawCreate( NULL, &lpDD, NULL );

    if( ddrval != DD_OK )
        return CleanupAndExit("DirectDrawCreate Failed!");

    ddrval = lpDD->lpVtbl->SetCooperativeLevel( lpDD, hWndMain,
                            DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
        return CleanupAndExit("SetCooperativeLevel Failed");

    #ifdef NT_HACK
DDSurfDesc.dwSize = sizeof(DDSurfDesc);
ddrval = lpDD->lpVtbl->GetDisplayMode(lpDD,&DDSurfDesc);
if(ddrval == DD_OK)
ScreenBpp = DDSurfDesc.ddpfPixelFormat.dwRGBBitCount;
    #endif

    // set the mode
    ddrval = lpDD->lpVtbl->SetDisplayMode( lpDD, ScreenX, ScreenY, ScreenBpp );
    if( ddrval != DD_OK )
        return CleanupAndExit("SetDisplayMode Failed!");

    // check the color key hardware capabilities
    dwTransType = DDBLTFAST_SRCCOLORKEY;
    ddcaps.dwSize = sizeof( ddcaps );

#ifdef DEBUG
    if( GetProfileInt( "Donuts", "force_dest_blt", 0) )
    {
dwTransType = DDBLTFAST_DESTCOLORKEY;
    }
    bHELBlt = GetProfileInt( "Donuts", "force_HEL_blt", bHELBlt );
#endif

    // Create surfaces
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpFrontBuffer, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface FrontBuffer Failed!");

    // get a pointer to the back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpFrontBuffer->lpVtbl->GetAttachedSurface(
                lpFrontBuffer,
                &ddscaps,
                &lpBackBuffer );
    if( ddrval != DD_OK )
        return CleanupAndExit("GetAttachedDurface Failed!");

    ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
#ifdef DEBUG
    if( bHELBlt )
        ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
#endif
    ddsd.dwWidth = 320;
    ddsd.dwHeight = 384;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpDonut, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpDonut Failed!");

    ddsd.dwHeight = 128;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpPyramid, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpPyramid Failed!");

    ddsd.dwHeight = 32;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpCube, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpCube Failed!");

    ddsd.dwHeight = 32;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpSphere, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpSphere Failed!");
    // Set the background color fill color

    ddsd.dwHeight = 256;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpShip, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpShip Failed!");

    ddsd.dwHeight = 16;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpNum, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpNum Failed!");

    if( !RestoreSurfaces() )
        return CleanupAndExit("RestoreSurfaces Failed!");

    DL.next = DL.prev = &DL;            // null display list
    DL.type = OBJ_SHIP;
    DL.surf = lpShip;
    lastTickCount = GetTickCount();

#ifdef USE_DSOUND
    if(bWantSound)
    {
        InitializeSound();
    }
#endif

   // initialize DirectInput
   if(!InitInput(hInst, hWndMain))
      return CleanupAndExit("DirectInput initialization Failed!");

    if(bTest)
    {
    ProgramState = PS_ACTIVE;
setup_game();
    }
    else
    {
        ProgramState = PS_SPLASH;
    }
    return TRUE;
}

BOOL CleanupAndExit( char *err)
{
#ifdef DEBUG
    wsprintf(DebugBuf, "___CleanupAndExit  err = %s\n", err );
    OutputDebugString( DebugBuf );
#endif

    // make the cursor visible
    SetCursor(LoadCursor( NULL, IDC_ARROW ));
    bMouseVisible = TRUE;

    if( lpDonut != NULL )
        lpDonut->lpVtbl->Release( lpDonut );

    if( lpPyramid != NULL )
        lpPyramid->lpVtbl->Release( lpPyramid );

    if( lpCube != NULL )
        lpCube->lpVtbl->Release( lpCube );

    if( lpSphere != NULL )
        lpSphere->lpVtbl->Release( lpSphere );

    if( lpShip != NULL )
        lpShip->lpVtbl->Release( lpShip );

    if( lpNum != NULL )
        lpNum->lpVtbl->Release( lpNum );

    if( lpFrontBuffer != NULL )
        lpFrontBuffer->lpVtbl->Release( lpFrontBuffer );

    if( lpArtPalette != NULL )
        lpArtPalette->lpVtbl->Release( lpArtPalette );

    if( lpSplashPalette != NULL )
        lpSplashPalette->lpVtbl->Release( lpSplashPalette );

    if( lpDD != NULL )
        lpDD->lpVtbl->Release( lpDD );

     // clean up DirectInput objects
     CleanupInput();

    //
    // warn user if there is one
    //

    if( !bStress )
    {
        MessageBox( hWndMain, err, "ERROR", MB_OK );
    }
    return FALSE;
}

void bltSplash( void )
{
    HRESULT     ddrval;
    HBITMAP     hbm;

    // set the palette before loading the splash screen
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpSplashPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), "SPLASH", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if ( NULL == hbm )
return;

    // if the surface is lost, DDCopyBitmap will fail and the surface will
    // be restored in FlipScreen.
    ddrval = DDCopyBitmap( lpBackBuffer, hbm, 0, 0, 0, 0 );

    DeleteObject( hbm );

    FlipScreen();
}

#ifdef USE_DSOUND
//
// play a sound, but first set the panning according to where the
// object is on the screen.  fake 3D sound.
//
void playPanned(HSNDOBJ hSO, DBLNODE *object)
{
    IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(hSO);

if(!bWantSound)
return;   // No sound our Work is done

    if (pDSB)
    {
switch(ScreenX)
{
case 320:
    IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
((object->dst.right + object->dst.left) / 2) / 320.0) - 10000.0));
    break;
case 640:
    IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
((object->dst.right + object->dst.left) / 2) / 640.0) - 10000.0));
    break;
case 1024:
    IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
((object->dst.right + object->dst.left) / 2) / 1024.0) - 10000.0));
    break;
case 1280:
    IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
((object->dst.right + object->dst.left) / 2) / 1280.0) - 10000.0));
    break;
}

        IDirectSoundBuffer_Play(pDSB, 0, 0, 0);
    }
}
#endif

void UpdateFrame( void )
{
    switch( ProgramState )
    {
        case PS_SPLASH:
            // display the splash screen
            bltSplash();
            return;
        case PS_ACTIVE:
            UpdateDisplayList();
            CheckForHits();
            DrawDisplayList();
            if ( isDisplayListEmpty() )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjStop(hsoEngineIdle);
                    SndObjStop(hsoEngineRev);
                }
#endif
                bPlayIdle = FALSE;
                bPlayRev = FALSE;
                lastThrust = lastShield = FALSE;
                ProgramState = PS_BEGINREST;
                restCount = GetTickCount();
                initLevel( ++level );
            }
            return;
        case PS_BEGINREST:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjPlay(hsoBeginLevel, 0);
            }
#endif
            ProgramState = PS_REST;
            //
            // FALLTHRU
            //
        case PS_REST:
            if( ( GetTickCount() - restCount ) > ShowLevelCount )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);
                }
#endif
                bPlayIdle = TRUE;
                lastTickCount = GetTickCount();
                ProgramState = PS_ACTIVE;
            }
            else
            {
                DisplayLevel();
            }
            return;
    }
}

void DisplayLevel( void )
{
    char buf[10];

    EraseScreen();
    buf[0] = 10 + '0';
    buf[1] = 11 + '0';
    buf[2] = 12 + '0';
    buf[3] = 11 + '0';
    buf[4] = 10 + '0';
    buf[5] = '\0';
    bltScore( buf, ScreenX/2-64, ScreenY/2-8 );
    buf[0] = level / 100 + '0';
    buf[1] = level / 10 + '0';
    buf[2] = level % 10 + '0';
    buf[3] = '\0';
    bltScore( buf, ScreenX/2+22, ScreenY/2-8 );
    FlipScreen();
}

void bltScore( char *num, int x, int y )
{
    char *c;
    RECT    src;
    int     i;
    HRESULT ddrval;

    for(c=num; *c != '\0'; c++)
    {
        while( 1 )
        {
            i = *c - '0';
            src.left = i*16;
            src.top = 0;
            src.right = src.left + 16;
            src.bottom = src.top + 16;
            ddrval = lpBackBuffer->lpVtbl->BltFast( lpBackBuffer, x, y, lpNum, &src, dwTransType );
            if( ddrval == DD_OK )
            {
                break;
            }
            if( ddrval == DDERR_SURFACELOST )
            {
                if( !RestoreSurfaces() )
                    return;
            }
            if( ddrval != DDERR_WASSTILLDRAWING )
            {
                return;
            }
        }
        x += 16;
    }
}

void CheckForHits( void )
{
    LPDBLNODE   bullet, target, save;
    int         frame, x, y, l, t;
    BOOL        hit;

    // update screen rects
    target = &DL;
    do
    {
        frame = (DWORD)target->frame;
        switch( target->type )
        {
            case OBJ_DONUT:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 64;
                target->dst.bottom = target->dst.top + 64;
                target->src.left = 64 * (frame % 5);
                target->src.top = 64 * (frame /5);
                target->src.right = target->src.left + 64;
                target->src.bottom = target->src.top + 64;
                break;
            case OBJ_PYRAMID:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 32;
                target->dst.bottom = target->dst.top + 32;
                target->src.left = 32 * (frame % 10);
                target->src.top = 32 * (frame /10);
                target->src.right = target->src.left + 32;
                target->src.bottom = target->src.top + 32;
                break;
            case OBJ_SPHERE:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 16;
                target->dst.bottom = target->dst.top + 16;
                target->src.left = 16 * (frame % 20);
                target->src.top = 16 * (frame /20);
                target->src.right = target->src.left + 16;
                target->src.bottom = target->src.top + 16;
                break;
            case OBJ_CUBE:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 16;
target->dst.bottom = target->dst.top + 16;
                target->src.left = 16 * (frame % 20);
target->src.top = 16 * (frame /20);
                target->src.right = target->src.left + 16;
                target->src.bottom = target->src.top + 16;
                break;
            case OBJ_SHIP:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 32;
                target->dst.bottom = target->dst.top + 32;
if( lastShield )
    target->src.top = 32 * (frame / 10) + 128;
else
                    target->src.top = 32 * (frame /10);
                target->src.left = 32 * (frame % 10);
                target->src.right = target->src.left + 32;
                target->src.bottom = target->src.top + 32;
                break;
    case OBJ_BULLET:
        frame = (DWORD)target->frame/20 % 4;
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 3;
                target->dst.bottom = target->dst.top + 3;
                target->src.left = BULLET_X + frame*4;
                target->src.top = BULLET_Y;
                target->src.right = target->src.left + 3;
                target->src.bottom = target->src.top + 3;
                break;
        }
        target = target->next;
    }
    while( target != &DL );

    bullet=&DL;
    do
    {
        hit = FALSE;
        if((bullet->type != OBJ_BULLET) && (bullet != &DL))
        {
            bullet = bullet->next;
            continue;
        }

        x = (bullet->dst.left + bullet->dst.right) / 2;
        y = (bullet->dst.top + bullet->dst.bottom) / 2;
        for(target=DL.next; target != &DL; target = target->next)
        {
            if( ( target->type != OBJ_DONUT ) &&
                ( target->type != OBJ_PYRAMID ) &&
                ( target->type != OBJ_SPHERE ) &&
                ( target->type != OBJ_CUBE ) )
                continue;

            if( (x >= target->dst.left) &&
                (x <  target->dst.right) &&
                (y >= target->dst.top) &&
                (y <  target->dst.bottom) )
            {
                if ((bullet != &DL) || !lastShield)
                {
                    // the bullet hit the target
                    switch( target->type )
                    {
                    case OBJ_DONUT:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                        playPanned(hsoDonutExplode, target);
}
#endif
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        score += 10;
                        break;
                    case OBJ_PYRAMID:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                            playPanned(hsoPyramidExplode, target);
}
#endif
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_CUBE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_CUBE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        score += 20;
                        break;
                    case OBJ_CUBE:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                            playPanned(hsoCubeExplode, target);
}
#endif
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        break;
                        score += 40;
                    case OBJ_SPHERE:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                            playPanned(hsoSphereExplode, target);
}
#endif
                        score += 20;
                    }

                    l = target->dst.left;
                    t = target->dst.top;
                    DeleteFromList( target );
                }

                hit = TRUE;
            }

            if( hit )
            {
                if( bullet == &DL )
                {
                    hit = FALSE;
                    if (!lastShield && !showDelay && !bTest)
                    {
#ifdef USE_DSOUND
            if(bWantSound)
            {
                            playPanned(hsoShipExplode, bullet);
}
#endif
                        score -= 150;
                        if (score < 0)
                            score = 0;

                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );

addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
initShip(TRUE);
                    }
                }

                break;
            }
        }

        if( hit )
        {
            save = bullet;
            bullet = bullet->next;

            DeleteFromList( save );
        }
        else
        {
            bullet = bullet->next;
        }

    } while (bullet != &DL);
}

void EraseScreen( void )
{
    DDBLTFX     ddbltfx;
    HRESULT     ddrval;

    // iForceErase forces us to erase so we can get of stray GDI pixels
    if (iForceErase)
    {
        iForceErase--;
    }
    else if( bSpecialEffects )   // cool looking screen with no colorfill
    {
        return;
    }

    // Erase the background
    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = dwFillColor;
    while( 1 )
    {
        ddrval = lpBackBuffer->lpVtbl->Blt( lpBackBuffer, NULL, NULL,
                 NULL, DDBLT_COLORFILL, &ddbltfx );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if( !RestoreSurfaces() )
                return;
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }
}

void FlipScreen( void )
{
    HRESULT     ddrval;

    // Flip the surfaces
    while( 1 )
    {
        ddrval = lpFrontBuffer->lpVtbl->Flip( lpFrontBuffer, NULL, 0 );
        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if( !RestoreSurfaces() )
            {
                return;
            }
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            break;
        }
    }
}

void DrawDisplayList( void )
{
    LPDBLNODE   this;
    LPDBLNODE   last;
    HRESULT     ddrval;
    char        scorebuf[11];
    int         rem;

    // blt everything in reverse order if we are doing destination transparency
    // calculate score string
    scorebuf[0] = score/10000000 + '0';
    rem = score % 10000000;
    scorebuf[1] = rem/1000000 + '0';
    rem = score % 1000000;
    scorebuf[2] = rem/100000 + '0';
    rem = score % 100000;
    scorebuf[3] = rem/10000 + '0';
    rem = score % 10000;
    scorebuf[4] = rem/1000 + '0';
    rem = score % 1000;
    scorebuf[5] = rem/100 + '0';
    rem = score % 100;
    scorebuf[6] = rem/10 + '0';
    rem = score % 10;
    scorebuf[7] = rem + '0';
#ifdef USE_DSOUND
    if( bSoundEnabled )
    {
    scorebuf[8] = 14 + '0';
    scorebuf[9] = 13 + '0';
    scorebuf[10] = '\0';
    }
    else
#endif
    {
        scorebuf[8] = '\0';
    }

    EraseScreen();
    if( dwTransType == DDBLTFAST_DESTCOLORKEY )
    {
        bltScore(scorebuf, 10, ScreenY-26);

        if( bShowFrameCount )
    DisplayFrameRate();

this = DL.next; // start with the topmost bitmap
last = DL.next; // don't blt it twice

        if (showDelay)
            last = &DL;
    }
    else
    {
this = &DL;// start with the bottommost bitmap (the ship)
last = &DL;// don't blt it twice

        if (showDelay)
    this = this->prev;
    }

    do
    {
        while( 1 )
{
    ddrval = lpBackBuffer->lpVtbl->BltFast( lpBackBuffer, this->dst.left, this->dst.top, this->surf, &(this->src), dwTransType );
            if( ddrval == DD_OK )
            {
                break;
            }
            if( ddrval == DDERR_SURFACELOST )
            {
                if( !RestoreSurfaces() )
                    return;
            }
            if( ddrval != DDERR_WASSTILLDRAWING )
            {
                return;
            }
}
if( dwTransType != DDBLTFAST_DESTCOLORKEY )
{
    this = this->prev;
}
else
{
    this = this->next;
}
    }
    while( this != last );

    if( dwTransType != DDBLTFAST_DESTCOLORKEY )
    {
        bltScore(scorebuf, 10, ScreenY-26);

        if( bShowFrameCount )
    DisplayFrameRate();
    }

    FlipScreen();
}

void DisplayFrameRate( void )
{
    DWORD               time2;
    char                buff[256];

    dwFrameCount++;
    time2 = timeGetTime() - dwFrameTime;
    if( time2 > 1000 )
    {
        dwFrames = (dwFrameCount*1000)/time2;
        dwFrameTime = timeGetTime();
        dwFrameCount = 0;
    }
    if( dwFrames == 0 )
    {
        return;
    }

    if (dwFrames != dwFramesLast)
    {
        dwFramesLast = dwFrames;
    }

    if( dwFrames > 99 )
    {
dwFrames = 99;
    }
    buff[0] = (char)((dwFrames / 10) + '0');
    buff[1] = (char)((dwFrames % 10) + '0');
    buff[2] = '\0';
    bltScore(buff, ScreenX/2-25, 10);
}

void DeleteFromList( LPDBLNODE this )
{
    this->next->prev = this->prev;
    this->prev->next = this->next;
    LocalFree( this );
}

void UpdateDisplayList( void )
{
    LPDBLNODE   this;
    LPDBLNODE   save;
    DWORD       thisTickCount = GetTickCount();
    DWORD       tickDiff = thisTickCount - lastTickCount;
    double      maxx, maxy;
    double      maxframe;
    DWORD       input;
    BOOL        event = FALSE;

    if( bTest )
    {
        input |= (KEY_RIGHT | KEY_FIRE);
    }
    lastTickCount = thisTickCount;

    input = ReadGameInput();

    if (showDelay)
    {
        showDelay -= (int)tickDiff;
        if (showDelay < 0)
        {
            showDelay = 0;
            lastShield = FALSE;
    initShip( FALSE );
        }
    }

    // update the ship
    if( !showDelay )
    {
        DL.posx += DL.velx * (double)tickDiff;
        DL.posy += DL.vely * (double)tickDiff;
    }
    if( DL.posx > MAX_SHIP_X )
    {
        DL.posx = MAX_SHIP_X;
        DL.velx = -DL.velx;
        event = TRUE;
    }
    else if ( DL.posx < 0 )
    {
        DL.posx =0;
        DL.velx = -DL.velx;
        event = TRUE;
    }
    if( DL.posy > MAX_SHIP_Y )
    {
        DL.posy = MAX_SHIP_Y;
        DL.vely = -DL.vely;
        event = TRUE;
    }
    else if ( DL.posy < 0 )
    {
        DL.posy =0;
        DL.vely = -DL.vely;
        event = TRUE;
    }
    if (event)
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
            playPanned(hsoShipBounce, &DL);
}
#endif
        event = FALSE;
    }

    if ((event = (showDelay || ((input & KEY_SHIELD) == KEY_SHIELD))) !=
        lastShield)
    {
        if (event && !showDelay)
{
#ifdef USE_DSOUND
        if(bWantSound)
        {
            SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);
}
#endif
    bPlayBuzz = TRUE;
}
        else
{
#ifdef USE_DSOUND
        if(bWantSound)
        {
                SndObjStop(hsoShieldBuzz);
}
#endif
        bPlayBuzz = FALSE;
    }
        lastShield = event;
    }
    if (event)
    {
        input &= ~(KEY_FIRE);
    }

    if (input & KEY_FIRE)
    {
if( !showDelay )
{
            // add a bullet to the scene
            score--;
            if(score < 0)
                score = 0;

#ifdef USE_DSOUND
        if(bWantSound)
        {
                SndObjPlay(hsoFireBullet, 0);
}
#endif
            addObject( OBJ_BULLET, Dirx[(int)DL.frame]*6.0 + 16.0 + DL.posx,
                                   Diry[(int)DL.frame]*6.0 + 16.0 + DL.posy,
                                   Dirx[(int)DL.frame]*500.0/1000.0,
   Diry[(int)DL.frame]*500.0/1000.0 );
        }
    }

    event = FALSE;
    if( input & KEY_LEFT )
    {
            DL.frame -= 1.0;
            if( DL.frame < 0.0 )
                DL.frame += MAX_SHIP_FRAME;
    }
    if( input & KEY_RIGHT )
    {
            DL.frame += 1.0;
            if( DL.frame >= MAX_SHIP_FRAME)
                DL.frame -= MAX_SHIP_FRAME;
    }
    if( input & KEY_UP )
    {
            DL.velx += Dirx[(int)DL.frame] * 10.0/1000.0;
            DL.vely += Diry[(int)DL.frame] * 10.0/1000.0;
            event = TRUE;
    }
    if( input & KEY_DOWN )
    {
            DL.velx -= Dirx[(int)DL.frame] * 10.0/1000.0;
            DL.vely -= Diry[(int)DL.frame] * 10.0/1000.0;
            event = TRUE;
    }

    if (event != lastThrust)
    {
        if (event)
        {
            input &= ~KEY_STOP;
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjStop(hsoSkidToStop);
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);
}
#endif
    bPlayRev = TRUE;
        }
        else
        {
#ifdef USE_DSOUND
        if(bWantSound)
        {
        SndObjStop(hsoEngineRev);
}
#endif
    bPlayRev = FALSE;
        }

        lastThrust = event;
    }

    if( input & KEY_STOP )
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
        if (DL.velx || DL.vely)
            playPanned(hsoSkidToStop, &DL);
}
#endif

        DL.velx = 0;
        DL.vely = 0;
    }

    this = DL.next;
    do
    {
        this->posx += this->velx * (double)tickDiff;
        this->posy += this->vely * (double)tickDiff;
        this->frame += this->delay * (double)tickDiff;
        switch( this->type )
        {
            case OBJ_DONUT:
                maxx = (double)MAX_DONUT_X;
                maxy = (double)MAX_DONUT_Y;
                maxframe = (double)MAX_DONUT_FRAME;
                break;
            case OBJ_PYRAMID:
                maxx = (double)MAX_PYRAMID_X;
                maxy = (double)MAX_PYRAMID_Y;
                maxframe = (double)MAX_PYRAMID_FRAME;
                break;
            case OBJ_SPHERE:
                maxx = (double)MAX_SPHERE_X;
                maxy = (double)MAX_SPHERE_Y;
                maxframe = (double)MAX_SPHERE_FRAME;
                break;
            case OBJ_CUBE:
                maxx = (double)MAX_CUBE_X;
                maxy = (double)MAX_CUBE_Y;
                maxframe = (double)MAX_CUBE_FRAME;
                break;
            case OBJ_BULLET:
                maxx = (double)MAX_BULLET_X;
                maxy = (double)MAX_BULLET_Y;
                maxframe = (double)MAX_BULLET_FRAME;
                if( this->frame >= (double)MAX_BULLET_FRAME )
                {
                    save = this;
                    this = this->next;
                    DeleteFromList( save );
    continue;
                }
                break;
        }
        if( this != &DL )
        {
            if( this->posx > maxx )
            {
                this->posx = maxx;
                this->velx = -this->velx;
            }
            else if ( this->posx < 0 )
            {
                this->posx =0;
                this->velx = -this->velx;
            }
            if( this->posy > maxy )
            {
                this->posy = maxy;
                this->vely = -this->vely;
            }
            else if ( this->posy < 0 )
            {
                this->posy =0;
                this->vely = -this->vely;
            }
            if( this->frame >= maxframe )
            {
                this->frame -= maxframe;
            }
            this = this->next;
        }
    }
    while( this != &DL );
}

BOOL isDisplayListEmpty( void )
{
    LPDBLNODE ptr;

    for(ptr=DL.next; ptr != &DL; ptr = ptr->next)
    {
        if(ptr->type != OBJ_BULLET)
            return FALSE;
    }
    return TRUE;
}

void initShip( BOOL delay )
{
    DL.posx = (double)(ScreenX/2-16);    // center the ship
    DL.posy = (double)(ScreenY/2-16);
    DL.frame = 0.0;
    if( bTest )
    {
        DL.velx = 0.25;
DL.vely = 0.5;
    }
    else
    {
        DL.velx = DL.vely = 0.0;// not moving
    }
    if( !bTest && delay )
    showDelay = DEF_SHOW_DELAY;
}

void initLevel( int level )
{
    int     i;

    // clear any stray bullets out of the display list
    while( DL.next != &DL )
    {
        DeleteFromList( DL.next );
    }
    for(i=0; i<(2*level-1); i++)
    {
        addObject( OBJ_DONUT, -1.0, -1.0, -1.0, -1.0 );
    }
    initShip(TRUE);
}

void addObject( SHORT type, double x, double y, double vx, double vy )
{
    LPDBLNODE   new;

    new = (LPDBLNODE) LocalAlloc( LPTR, sizeof( DBLNODE ) );
    if( new == NULL)
        return;

    new->type = type;
    switch( type )
    {
        case OBJ_DONUT:
            if( x < 0.0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_DONUT_X );
                new->posy = randDouble( 0.0, (double)MAX_DONUT_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 30.0*randDouble( 0.1, 0.4 )/1000.0;
    new->surf = lpDonut;
    linkObject( new );
            break;
        case OBJ_PYRAMID:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_PYRAMID_X );
                new->posy = randDouble( 0.0, (double)MAX_PYRAMID_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 0.3, 1.0 )/1000.0;
    new->surf = lpPyramid;
            linkObject( new );
            break;
        case OBJ_SPHERE:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_SPHERE_X );
                new->posy = randDouble( 0.0, (double)MAX_SPHERE_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 1.5, 2.0 )/1000.0;
    new->surf = lpSphere;
            linkObject( new );
            break;
        case OBJ_CUBE:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_CUBE_X );
                new->posy = randDouble( 0.0, (double)MAX_CUBE_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 0.8, 2.0 )/1000.0;
    new->surf = lpCube;
            linkObject( new );
            break;
        case OBJ_BULLET:
            new->posx = x;
            new->posy = y;
            new->velx = vx;
            new->vely = vy;
            new->frame = 0.0;
            new->delay = 1.0;
    new->surf = lpNum;
            linkObject( new );
            break;
    }
}

void linkObject( LPDBLNODE new )
{
    new->next = DL.next;
    new->prev = &DL;
    DL.next->prev = new;
    DL.next = new;
}

void linkLastObject( LPDBLNODE new )
{
    new->prev = DL.prev;
    new->next = &DL;
    DL.prev->next = new;
    DL.prev = new;
}

BOOL RestoreSurfaces( void )
{
    HRESULTddrval;
    HBITMAP     hbm;

    ddrval = lpFrontBuffer->lpVtbl->Restore(lpFrontBuffer);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpDonut->lpVtbl->Restore(lpDonut);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpPyramid->lpVtbl->Restore(lpPyramid);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpCube->lpVtbl->Restore(lpCube);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpSphere->lpVtbl->Restore(lpSphere);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpShip->lpVtbl->Restore(lpShip);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpNum->lpVtbl->Restore(lpNum);
    if( ddrval != DD_OK )
        return FALSE;

    // Create and set the palette for the splash bitmap
    lpSplashPalette = DDLoadPalette( lpDD, "SPLASH" );
    if( NULL == lpSplashPalette )
        return CleanupAndExit("DDLoadPalette SPLASH");

    // Create and set the palette for the art bitmap
    lpArtPalette = DDLoadPalette( lpDD, "DONUTS8" );
    if( NULL == lpArtPalette )
        return CleanupAndExit("DDLoadPalette DONUTS");

    // set the palette before loading the art
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpArtPalette );

    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

    if( NULL == hbm )
        return FALSE;

    ddrval = DDCopyBitmap( lpDonut, hbm, 0, 0, 320, 384 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    // NOTE: Why are we calling LoadImage again?  StretchBlt (which is
    // called in DDCopyBitmap) does not work properly when performing
    // an 8-bpp to 24- or 32-bpp blt multiple times from the same
    // bitmap.  The workaround is to call LoadImage before each
    // StretchBlt because the first StretchBlt after a LoadImage will
    // work.
    if(ScreenBpp >= 24)
    {
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( NULL == hbm )
    return FALSE;
    }

    ddrval = DDCopyBitmap( lpPyramid, hbm, 0, 384, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( NULL == hbm )
    return FALSE;
    }

    ddrval = DDCopyBitmap( lpSphere, hbm, 0, 512, 320, 32 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( NULL == hbm )
    return FALSE;
    }

    ddrval = DDCopyBitmap( lpCube, hbm, 0, 544, 320, 32 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( NULL == hbm )
    return FALSE;
    }

    ddrval = DDCopyBitmap( lpShip, hbm, 0, 576, 320, 256 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( NULL == hbm )
    return FALSE;
    }

    ddrval = DDCopyBitmap( lpNum, hbm, 0, 832, 320, 16 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    DeleteObject( hbm );

    // set colorfill colors and color keys according to bitmap contents
    dwFillColor = DDColorMatch( lpDonut, CLR_INVALID );

    DDSetColorKey( lpDonut, CLR_INVALID );
    DDSetColorKey( lpPyramid, CLR_INVALID );
    DDSetColorKey( lpCube, CLR_INVALID );
    DDSetColorKey( lpSphere, CLR_INVALID );
    DDSetColorKey( lpShip, CLR_INVALID );
    DDSetColorKey( lpNum, CLR_INVALID );

    return TRUE;
}

int randInt( int low, int high )
{
    int range = high - low;
    int num = rand() % range;
    return( num + low );
}

double randDouble( double low, double high )
{
    double range = high - low;
    double num = range * (double)rand()/(double)RAND_MAX;
    return( num + low );
}

#ifdef USE_DSOUND
void InitializeSound( void )
{
if(!bWantSound)
return; // out of here
    bSoundEnabled = FALSE;
    if (SUCCEEDED(DirectSoundCreate(NULL, &lpDS, NULL)))
    {
        if (SUCCEEDED(lpDS->lpVtbl->SetCooperativeLevel(lpDS, hWndMain,
            DSSCL_NORMAL)))
        {
            hsoBeginLevel     = SndObjCreate(lpDS, "BeginLevel",      1);
            hsoEngineIdle     = SndObjCreate(lpDS, "EngineIdle",      1);
            hsoEngineRev      = SndObjCreate(lpDS, "EngineRev",       1);
            hsoSkidToStop     = SndObjCreate(lpDS, "SkidToStop",      1);
            hsoShieldBuzz     = SndObjCreate(lpDS, "ShieldBuzz",      1);
            hsoShipExplode    = SndObjCreate(lpDS, "ShipExplode",     1);
            hsoFireBullet     = SndObjCreate(lpDS, "Gunfire",        25);
            hsoShipBounce     = SndObjCreate(lpDS, "ShipBounce",      4);
            hsoDonutExplode   = SndObjCreate(lpDS, "DonutExplode",   10);
            hsoPyramidExplode = SndObjCreate(lpDS, "PyramidExplode", 12);
            hsoCubeExplode    = SndObjCreate(lpDS, "CubeExplode",    15);
            hsoSphereExplode  = SndObjCreate(lpDS, "SphereExplode",  10);
            bSoundEnabled = TRUE;

//#ifdef USE_DSOUND   this should be dead code Josephc
            if( bPlayIdle )
                SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);

            if( bPlayBuzz )
                SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);

            if( bPlayRev )
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);
//#endif
        }
        else
        {
            lpDS->lpVtbl->Release(lpDS);
            lpDS = NULL;
        }
    }
}

void DestroySound( void )
{
if(!bWantSound)
return; //No work to be done
    bSoundEnabled = FALSE;
    if (lpDS)
    {
        SndObjDestroy(hsoBeginLevel);
        hsoBeginLevel = NULL;
        SndObjDestroy(hsoEngineIdle);
        hsoEngineIdle = NULL;
        SndObjDestroy(hsoEngineRev);
        hsoEngineRev = NULL;
        SndObjDestroy(hsoSkidToStop);
        hsoSkidToStop = NULL;
        SndObjDestroy(hsoShieldBuzz);
        hsoShieldBuzz = NULL;
        SndObjDestroy(hsoShipExplode);
        hsoShipExplode = NULL;
        SndObjDestroy(hsoFireBullet);
        hsoFireBullet = NULL;
        SndObjDestroy(hsoShipBounce);
        hsoShipBounce = NULL;
        SndObjDestroy(hsoDonutExplode);
        hsoDonutExplode = NULL;
        SndObjDestroy(hsoPyramidExplode);
        hsoPyramidExplode = NULL;
        SndObjDestroy(hsoCubeExplode);
        hsoCubeExplode = NULL;
        SndObjDestroy(hsoSphereExplode);
        hsoSphereExplode = NULL;

        lpDS->lpVtbl->Release(lpDS);
        lpDS = NULL;
    }
}
#endif

int getint(char**p, int def)
{
    int i=0;

    while (IS_SPACE(**p))
        (*p)++;

    if (!IS_NUM(**p))
        return def;

    while (IS_NUM(**p))
        i = i*10 + *(*p)++ - '0';

    while (IS_SPACE(**p))
        (*p)++;

    return i;
}


DONUTS.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donuts.h
 *  Content:    main include file
 *
 *
 ***************************************************************************/

#ifndef DONUTS_INCLUDED
#define DONUTS_INCLUDED

#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#ifdef USE_DSOUND
#include <dsound.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include "resource.h"
#include "ddutil.h"
#ifdef USE_DSOUND
#include "dsutil.h"
#endif
#include "input.h"

#define DEF_SHOW_DELAY     (2000)

#define IS_NUM(c)     ((c) >= '0' && (c) <= '9')
#define IS_SPACE(c)   ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == 'x')

enum
{
    OBJ_DONUT = 0,
    OBJ_PYRAMID,
    OBJ_CUBE,
    OBJ_SPHERE,
    OBJ_SHIP,
    OBJ_BULLET
};

// program states
enum
{
    PS_SPLASH,
    PS_ACTIVE,
    PS_BEGINREST,
    PS_REST
};

#define     MAX_SCREEN_X    (ScreenX-1)
#define     MAX_SCREEN_Y    (ScreenY-1)
#define     MAX_DONUT_X     MAX_SCREEN_X - 64
#define     MAX_DONUT_Y     MAX_SCREEN_Y - 64
#define     MAX_DONUT_FRAME 30
#define     MAX_PYRAMID_X     MAX_SCREEN_X - 32
#define     MAX_PYRAMID_Y     MAX_SCREEN_Y - 32
#define     MAX_PYRAMID_FRAME 40
#define     MAX_SPHERE_X     MAX_SCREEN_X - 16
#define     MAX_SPHERE_Y     MAX_SCREEN_Y - 16
#define     MAX_SPHERE_FRAME 40
#define     MAX_CUBE_X     MAX_SCREEN_X - 16
#define     MAX_CUBE_Y     MAX_SCREEN_Y - 16
#define     MAX_CUBE_FRAME 40
#define     MAX_SHIP_X     MAX_SCREEN_X - 32
#define     MAX_SHIP_Y     MAX_SCREEN_Y - 32
#define     MAX_SHIP_FRAME 40
#define     MAX_BULLET_X    MAX_SCREEN_X - 3;
#define     MAX_BULLET_Y    MAX_SCREEN_Y - 3;
#define     MAX_BULLET_FRAME 400

// Offsets for the bullet bitmap
#define     BULLET_X    304
#define     BULLET_Y    0

/*
 * structures
 */

/*
 * DBLNODE - a node in a generic doubly-linked list
 */
typedef struct _DBLNODE
{
    struct  _DBLNODE    FAR *next;  // link to next node
    struct  _DBLNODE    FAR *prev;  // link to previous node
    SHORT               type;       // type of object
    double              posx, posy; // actual x and y position
    double              velx, vely; // x and y velocity (pixels/millisecond)
    double              frame;      // current frame
    double              delay;      // frame/millisecond
    RECT                src, dst;   // source and destination rects
    LPDIRECTDRAWSURFACE surf;       // surface containing bitmap
} DBLNODE;
typedef DBLNODE FAR *LPDBLNODE;

double      Dirx[40] =
{
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891007,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688,
    -1.000000,
    -0.987688,
    -0.951056,
    -0.891006,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434
};

double      Diry[40] =
{
    -1.000000,
    -0.987688,
    -0.951057,
    -0.891007,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434,
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891006,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688
};

/*
 * fn prototypes
 */
void    DestroyGame( void );
BOOL    InitializeGame( void );
void    makeFontStuff( void );
void    UpdateFrame( void );
BOOL    CleanupAndExit( char *err );
BOOL    RestoreSurfaces( void );
BOOL    isDisplayListEmpty( void );
void    initShip( BOOL delay );
void    initLevel( int level );
void    addObject( SHORT type, double x, double y, double vx, double vy );
void    linkObject( LPDBLNODE new );
void    linkLastObject( LPDBLNODE new );
void    UpdateDisplayList( void );
void    DrawDisplayList( void );
int     randInt( int low, int high );
double  randDouble( double low, double high );
void    DeleteFromList( LPDBLNODE this );
void    CheckForHits( void );
void    bltScore( char *num, int x, int y );
void    DisplayFrameRate( void );
void    bltSplash( void );
void    EraseScreen( void );
void    FlipScreen( void );
void    DisplayLevel( void );
void    InitializeSound( void );
void    DestroySound( void );

#endif


INPUT.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.c
 *  Content:    Input routines for Space Donuts game
 *
 *
 ***************************************************************************/

#include <dinput.h>
#include "input.h"
#include "resource.h"

extern HWND hWndMain;
extern DWORD ReadKeyboardInput(void);
extern DWORD ReadJoystickInput(void);

// allocate external variables
DWORD (*ReadGameInput)(void) = ReadKeyboardInput;

/*
 *  We'll use up to the first ten input devices.
 *
 *  c_cpdiFound is the number of found devices.
 *  g_rgpdiFound[0] is the array of found devices.
 *  g_pdevCurrent is the device that we are using for input.
 */
#define MAX_DINPUT_DEVICES 10
int g_cpdevFound;
LPDIRECTINPUTDEVICE2 g_rgpdevFound[MAX_DINPUT_DEVICES];
LPDIRECTINPUTDEVICE2 g_pdevCurrent;

/*--------------------------------------------------------------------------
| AddInputDevice
|
| Records an input device in the array of found devices.
|
| In addition to stashing it in the array, we also add it to the device
| menu so the user can pick it.
|
*-------------------------------------------------------------------------*/

void AddInputDevice(LPDIRECTINPUTDEVICE pdev, LPCDIDEVICEINSTANCE pdi)
{

    if (g_cpdevFound < MAX_DINPUT_DEVICES) {

        HRESULT hRes;

        /*
         *  Convert it to a Device2 so we can Poll() it.
         */

        hRes = pdev->lpVtbl->QueryInterface(
                    pdev, &IID_IDirectInputDevice2,
                    (LPVOID *)&g_rgpdevFound[g_cpdevFound]);

        if (SUCCEEDED(hRes)) {

            HMENU hmenu;

            /*
             *  Add its description to the menu.
             */
            hmenu = GetSubMenu(GetMenu(hWndMain), 0);

            InsertMenu(hmenu, g_cpdevFound, MF_BYPOSITION | MF_STRING,
                       IDC_DEVICES + g_cpdevFound,
                       pdi->tszInstanceName);

            g_cpdevFound++;
        }
    }
}

/*--------------------------------------------------------------------------
|
| SetDIDwordProperty
|
| Set a DWORD property on a DirectInputDevice.
|
*-------------------------------------------------------------------------*/

HRESULT
SetDIDwordProperty(LPDIRECTINPUTDEVICE pdev, REFGUID guidProperty,
                   DWORD dwObject, DWORD dwHow, DWORD dwValue)
{
   DIPROPDWORD dipdw;

   dipdw.diph.dwSize       = sizeof(dipdw);
   dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
   dipdw.diph.dwObj        = dwObject;
   dipdw.diph.dwHow        = dwHow;
   dipdw.dwData            = dwValue;

   return pdev->lpVtbl->SetProperty(pdev, guidProperty, &dipdw.diph);

}

/*--------------------------------------------------------------------------
| InitKeyboardInput
|
| Initializes DirectInput for the keyboard.  Creates a keyboard device,
| sets the data format to our custom format, sets the cooperative level and
| adds it to the menu.
|
*-------------------------------------------------------------------------*/

BOOL InitKeyboardInput(LPDIRECTINPUT pdi)
{
   LPDIRECTINPUTDEVICE pdev;
   DIDEVICEINSTANCE di;

   // create the DirectInput keyboard device
   if(pdi->lpVtbl->CreateDevice(pdi, &GUID_SysKeyboard, &pdev, NULL) != DI_OK)
   {
      OutputDebugString("IDirectInput::CreateDevice FAILED\n");
      return FALSE;
   }

   // set keyboard data format
   if(pdev->lpVtbl->SetDataFormat(pdev, &c_dfDIKeyboard) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetDataFormat FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   // set the cooperative level
   if(pdev->lpVtbl->SetCooperativeLevel(pdev, hWndMain,
      DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetCooperativeLevel FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   // set buffer size
   if (SetDIDwordProperty(pdev, DIPROP_BUFFERSIZE, 0, DIPH_DEVICE, KEYBUFSIZE) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEVICE) FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   //
   // Get the name of the primary keyboard so we can add it to the menu.
   //
   di.dwSize = sizeof(di);
   if (pdev->lpVtbl->GetDeviceInfo(pdev, &di) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::GetDeviceInfo FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   //
   // Add it to our list of devices.  If AddInputDevice succeeds,
   // he will do an AddRef.
   //
   AddInputDevice(pdev, &di);
   pdev->lpVtbl->Release(pdev);

   return TRUE;
}

/*--------------------------------------------------------------------------
| InitJoystickInput
|
| Initializes DirectInput for an enumerated joystick device.
| Creates the device, device, sets the standard joystick data format,
| sets the cooperative level and adds it to the menu.
|
| If any step fails, just skip the device and go on to the next one.
|
*-------------------------------------------------------------------------*/

BOOL FAR PASCAL InitJoystickInput(LPCDIDEVICEINSTANCE pdinst, LPVOID pvRef)
{
   LPDIRECTINPUT pdi = pvRef;
   LPDIRECTINPUTDEVICE pdev;
   DIPROPRANGE diprg;

   // create the DirectInput joystick device
   if(pdi->lpVtbl->CreateDevice(pdi, &pdinst->guidInstance, &pdev, NULL) != DI_OK)
   {
      OutputDebugString("IDirectInput::CreateDevice FAILED\n");
      return DIENUM_CONTINUE;
   }

   // set joystick data format
   if(pdev->lpVtbl->SetDataFormat(pdev, &c_dfDIJoystick) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetDataFormat FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return DIENUM_CONTINUE;
   }

   // set the cooperative level
   if(pdev->lpVtbl->SetCooperativeLevel(pdev, hWndMain,
      DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetCooperativeLevel FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return DIENUM_CONTINUE;
   }

   // set X-axis range to (-1000 ... +1000)
   // This lets us test against 0 to see which way the stick is pointed.

   diprg.diph.dwSize       = sizeof(diprg);
   diprg.diph.dwHeaderSize = sizeof(diprg.diph);
   diprg.diph.dwObj        = DIJOFS_X;
   diprg.diph.dwHow        = DIPH_BYOFFSET;
   diprg.lMin              = -1000;
   diprg.lMax              = +1000;

   if(pdev->lpVtbl->SetProperty(pdev, DIPROP_RANGE, &diprg.diph) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_RANGE) FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   //
   // And again for Y-axis range
   //
   diprg.diph.dwObj        = DIJOFS_Y;

   if(pdev->lpVtbl->SetProperty(pdev, DIPROP_RANGE, &diprg.diph) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_RANGE) FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   // set X axis dead zone to 50% (to avoid accidental turning)
   // Units are ten thousandths, so 50% = 5000/10000.
   if (SetDIDwordProperty(pdev, DIPROP_DEADZONE, DIJOFS_X, DIPH_BYOFFSET, 5000) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEADZONE) FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   // set Y axis dead zone to 50% (to avoid accidental thrust)
   // Units are ten thousandths, so 50% = 5000/10000.
   if (SetDIDwordProperty(pdev, DIPROP_DEADZONE, DIJOFS_Y, DIPH_BYOFFSET, 5000) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEADZONE) FAILED\n");
      pdev->lpVtbl->Release(pdev);
      return FALSE;
   }

   //
   // Add it to our list of devices.  If AddInputDevice succeeds,
   // he will do an AddRef.
   //
   AddInputDevice(pdev, pdinst);
   pdev->lpVtbl->Release(pdev);

   return DIENUM_CONTINUE;
}

/*--------------------------------------------------------------------------
| InitInput
|
| Initializes DirectInput for the keyboard and all joysticks.
|
| For each input device, add it to the menu.
|
*-------------------------------------------------------------------------*/

BOOL InitInput(HINSTANCE hInst, HWND hWnd)
{
   LPDIRECTINPUT pdi;
   BOOL fRc;

   // Note: Joystick support is a DirectX 5.0 feature.
   // Since we also want to run on DirectX 3.0, we will start out
   // with DirectX 3.0 to make sure that at least we get the keyboard.

   // create the DirectInput interface object
   if(DirectInputCreate(hInst, 0x0300, &pdi, NULL) != DI_OK)
   {
      OutputDebugString("DirectInputCreate 3.0 FAILED\n");
      return FALSE;
   }

   fRc = InitKeyboardInput(pdi);
   pdi->lpVtbl->Release(pdi);       // Finished with DX 3.0

   if (!fRc) {
      return FALSE;
   }

   // create the DirectInput 5.0 interface object
   if(DirectInputCreate(hInst, DIRECTINPUT_VERSION, &pdi, NULL) == DI_OK)
   {

      //
      // Enumerate the joystick devices.  If it doesn't work, oh well,
      // at least we got the keyboard.
      //

      pdi->lpVtbl->EnumDevices(pdi, DIDEVTYPE_JOYSTICK,
                               InitJoystickInput, pdi, DIEDFL_ATTACHEDONLY);

      pdi->lpVtbl->Release(pdi);    // Finished with DX 5.0.

   } else {
      OutputDebugString("DirectInputCreate 5.0 FAILED - no joystick support\n");
   }

   // Default device is the keyboard
   PickInputDevice(0);

   // if we get here, we were successful
   return TRUE;
}

/*--------------------------------------------------------------------------
| CleanupInput
|
| Cleans up all DirectInput objects.
*-------------------------------------------------------------------------*/
void CleanupInput(void)
{
   int idev;

   // make sure the device is unacquired
   // it doesn't harm to unacquire a device that isn't acquired

   if (g_pdevCurrent)
   {
      IDirectInputDevice_Unacquire(g_pdevCurrent);
   }

   // release all the devices we created
   for (idev = 0; idev < g_cpdevFound; idev++)
   {
      if (g_rgpdevFound[idev]) {
         IDirectInputDevice_Release(g_rgpdevFound[idev]);
         g_rgpdevFound[idev] = 0;
      }
   }

}

/*--------------------------------------------------------------------------
| ReacquireInput
|
| Reacquires the current input device.  If Acquire() returns S_FALSE,
| that means
| that we are already acquired and that DirectInput did nothing.
*-------------------------------------------------------------------------*/
BOOL ReacquireInput(void)
{
    HRESULT hRes;

    // if we have a current device
    if(g_pdevCurrent)
    {
       // acquire the device
       hRes = IDirectInputDevice_Acquire(g_pdevCurrent);
       if(SUCCEEDED(hRes))
       {
          // acquisition successful
          return TRUE;
       }
       else
       {
          // acquisition failed
          return FALSE;
       }
    }
    else
    {
       // we don't have a current device
       return FALSE;
    }

}

/*--------------------------------------------------------------------------
| ReadKeyboardInput
|
| Requests keyboard data and performs any needed processing.
*-------------------------------------------------------------------------*/
DWORD ReadKeyboardInput(void)
{
   DIDEVICEOBJECTDATA      rgKeyData[KEYBUFSIZE];
   DWORD                   dwEvents;
   DWORD                   dw;
   static DWORD            dwKeyState = 0;
   HRESULT                 hRes;

   // get data from the keyboard
   dwEvents = KEYBUFSIZE;
   hRes = IDirectInputDevice_GetDeviceData(g_pdevCurrent,
                                           sizeof(DIDEVICEOBJECTDATA),
                                           rgKeyData, &dwEvents, 0);

   if(hRes != DI_OK)
   {
      // did the read fail because we lost input for some reason?
      // if so, then attempt to reacquire.  If the second acquire
      // fails, then the error from GetDeviceData will be
      // DIERR_NOTACQUIRED, so we won't get stuck an infinite loop.
      if(hRes == DIERR_INPUTLOST)
         ReacquireInput();

      // return the fact that we did not read any data
      return 0;
   }

      // process the data
      for(dw = 0; dw < dwEvents; dw++)
      {
         switch(rgKeyData[dw].dwOfs)
         {
         // fire
         case DIK_SPACE:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_FIRE;
            else
               dwKeyState &= (DWORD)~KEY_FIRE;
            break;

         // stop
         case DIK_NUMPAD5:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_STOP;
            else
               dwKeyState &= ~KEY_STOP;
            break;

         // shield
         case DIK_NUMPAD7:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_SHIELD;
            else
               dwKeyState &= ~KEY_SHIELD;
            break;

         // thrust
         case DIK_UP:
         case DIK_NUMPAD8:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_UP;
            else
               dwKeyState &= ~KEY_UP;
            break;

         // reverse thrust
         case DIK_DOWN:
         case DIK_NUMPAD2:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_DOWN;
            else
               dwKeyState &= ~KEY_DOWN;
            break;

         // rotate left
         case DIK_LEFT:
         case DIK_NUMPAD4:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_LEFT;
            else
               dwKeyState &= ~KEY_LEFT;
            break;

         // rotate right
         case DIK_RIGHT:
         case DIK_NUMPAD6:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_RIGHT;
            else
               dwKeyState &= ~KEY_RIGHT;
            break;
         }

   }

   // return the state of the keys to the caller
   return dwKeyState;

}

/*--------------------------------------------------------------------------
| ReadJoystickInput
|
| Requests joystick data and performs any needed processing.
|
*-------------------------------------------------------------------------*/
DWORD ReadJoystickInput(void)
{
   DWORD                   dwKeyState;
   HRESULT                 hRes;
   DIJOYSTATE              js;

   // poll the joystick to read the current state
   hRes = IDirectInputDevice2_Poll(g_pdevCurrent);

   // get data from the joystick
   hRes = IDirectInputDevice_GetDeviceState(g_pdevCurrent,
                                            sizeof(DIJOYSTATE), &js);

   if(hRes != DI_OK)
   {
      // did the read fail because we lost input for some reason?
      // if so, then attempt to reacquire.  If the second acquire
      // fails, then the error from GetDeviceData will be
      // DIERR_NOTACQUIRED, so we won't get stuck an infinite loop.
      if(hRes == DIERR_INPUTLOST)
         ReacquireInput();

      // return the fact that we did not read any data
      return 0;
   }

   //
   // Now study the position of the stick and the buttons.
   //

   dwKeyState = 0;

   if (js.lX < 0) {
      dwKeyState |= KEY_LEFT;
   } else if (js.lX > 0) {
      dwKeyState |= KEY_RIGHT;
   }

   if (js.lY < 0) {
      dwKeyState |= KEY_UP;
   } else if (js.lY > 0) {
      dwKeyState |= KEY_DOWN;
   }

   if (js.rgbButtons[0] & 0x80) {
      dwKeyState |= KEY_FIRE;
   }

   if (js.rgbButtons[1] & 0x80) {
      dwKeyState |= KEY_SHIELD;
   }

   if (js.rgbButtons[2] & 0x80) {
      dwKeyState |= KEY_STOP;
   }

   return dwKeyState;

}

/*--------------------------------------------------------------------------
| PickInputDevice
|
| Make the n'th input device the one that we will use for game play.
|
*-------------------------------------------------------------------------*/

BOOL PickInputDevice(int n)
{
    if (n < g_cpdevFound) {

        /*
         *  Unacquire the old device.
         */
        if (g_pdevCurrent) {
            IDirectInputDevice_Unacquire(g_pdevCurrent);
        }

        /*
         *  Move to the new device.
         */
        g_pdevCurrent = g_rgpdevFound[n];

        /*
         *  Set ReadGameInput to the appropriate handler.
         */
        if (n == 0) {
            ReadGameInput = ReadKeyboardInput;
        } else {
            ReadGameInput = ReadJoystickInput;
        }

        CheckMenuRadioItem(GetSubMenu(GetMenu(hWndMain), 0),
                           IDC_DEVICES, IDC_DEVICES + g_cpdevFound - 1,
                           IDC_DEVICES + n, MF_BYCOMMAND);

        ReacquireInput();

        return TRUE;
    } else {
        return FALSE;
    }
}


INPUT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.h
 *
 *
 ***************************************************************************/

#ifndef _INPUT_H
#define _INPUT_H

#include <dinput.h>

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

// keyboard buffer size
#define KEYBUFSIZE 32

/*
 * keyboard commands
 */
#define KEY_STOP   0x00000001l
#define KEY_DOWN   0x00000002l
#define KEY_LEFT   0x00000004l
#define KEY_RIGHT  0x00000008l
#define KEY_UP     0x00000010l
#define KEY_FIRE   0x00000020l
#define KEY_THROW  0x00000040l
#define KEY_SHIELD 0x00000080l

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

// external variables
extern BOOL bKeyboardAcquired;

extern DWORD (*ReadGameInput)(void);

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

// prototypes
BOOL InitInput(HINSTANCE hInst, HWND hWnd);
void CleanupInput(void);
BOOL ReacquireInput(void);
BOOL PickInputDevice(int);

//--------------------------------------------------------------------------
#endif // _INPUT_H


RESOURCE.H

#define DONUTS_ICON101

#define DONUTS_MENU     1

#define DONUTS_ACCEL    1

#define IDC_STARTGAME   100
#define IDC_QUIT        101
#define IDC_TRAILS      102
#define IDC_AUDIO       103
#define IDC_FRAMERATE   104

#define IDC_DEVICES     200     // 200 through 299


README.TXT

MultiMonitor Space Donuts (Donuts2)
-----------------------------------

This sample game demonstrates multimonitor DirectDraw programming.
Multiple display monitors are supported in Windows98 and Windows NT 5.0
and versions of DirectX beginning with DX5A.

Donuts2 will only run on multimonitor capable systems - that is, it requires
that your system have installed the software listed above. If this is
the case, Donuts2 will run on any number of monitors from one (single
display) to a maximum of nine (monster display).

The play area in Donuts2 is determined by the monitor configuration of
your Windows desktop, which may be adjusted in the Display/Settings tab of
the Control Panel. The amount of video RAM you have on each video card
will determine the maximum resolutions at which you will experience a
reasonable frame rate.

The commands which this game recognizes are listed on the opening screen.

    ESC, F12        - Quit
    NUMPAD 4        - Turn left
    NUMPAD 6        - Turn right
    NUMPAD 5        - Stop moving
    NUMPAD 8        - Accelerate forward
    NUMPAD 2        - Accelerate backward
    SPACEBAR        - Fire
    NUMPAD 7        - Shield
    ENTER           - Starts game
    F5              - toggle frame rate display
    F3              - toggle audio
    F1              - toggle cheesy trails effect

Command line switches:

    -S              - turn off sound

Sound code
----------

The sound code in this application is deliberately designed
to be stressful.  For example, each bullet on the screen uses a different
sound buffer.  Over 70 sound buffers are created (including duplicates)
and 20-25 may be playing at any time.  This could be made much more
efficient, but we wanted code to stress our API and mixer.

Make files
----------

Two makefiles are provided: MAKEFILE builds Donuts2 without sound support,
and MAKEWSND builds Donuts2 with sound support. Specify nodebug=1 on the
NMAKE command line to build a release version.


DONUTS.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donuts.h
 *  Content:    main include file
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By      Reason
 *   ====       ==      ======
 *   12-jul-95  kylej   Initial Creation
 *   15-sep-97  t-craigs Multimon
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#ifndef DONUTS_INCLUDED
#define DONUTS_INCLUDED

//#define USE_DSOUND

#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <mmsystem.h>
#include <ddraw.h>

#ifdef USE_DSOUND
#include <dsound.h>
#endif

#include "resource.h"
#include "ddutil.h"
#include "input.h"
#include "util.h"
#include "monitor.h"

#ifdef USE_DSOUND
#include "dsutil.h"
#endif

#define DEF_SHOW_DELAY     (2000)

// program states
enum
{
    PS_SPLASH,
    PS_ACTIVE,
    PS_BEGINREST,
    PS_REST
};

class CObject
{
public:
CObject( int nMaxFrame, int nSize );

int  GetMonitorX();
int  GetMonitorY();
void UpdateRect();
void Blt();
BOOL CheckPosition( DWORD tickDiff );
void DeleteFromList();

// virtual fcts
virtual void Init( double x, double y );
virtual BOOL CheckFrame();
virtual void UpdateFrame( DWORD tickDiff );
virtual BOOL CanBeBullet() { return FALSE; };
virtual BOOL CanBeTarget() { return FALSE; };

// pure virtual - must be implemented by derived classes
virtual void UpdateSrcRect() = 0;
virtual int  Hit( double l, double t ) = 0;

virtual LPDIRECTDRAWSURFACE GetSurface() = 0;

// constants
int km_iMaxFrame;
int km_iSize;

// data
CObject*next;
CObject*prev;
    double              posx, posy; // actual x and y position
    double              velx, vely; // x and y velocity (pixels/millisecond)
    double              frame;      // current frame
    double              delay;
    RECT                src, dst;
intiMonitor;
};

class CDonut : public CObject
{
public:
CDonut();

virtual void UpdateSrcRect();
virtual LPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init( double x, double y );
virtual BOOL CanBeTarget() { return TRUE; };
};

class CPyramid : public CObject
{
public:
CPyramid();

virtual void UpdateSrcRect();
virtual LPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init( double x, double y );
virtual BOOL CanBeTarget() { return TRUE; };
};

class CCube : public CObject
{
public:
CCube();

virtual void UpdateSrcRect();
virtual LPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init( double x, double y );
virtual BOOL CanBeTarget() { return TRUE; };
};

class CSphere : public CObject
{
public:
CSphere();

virtual void UpdateSrcRect();
virtualLPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init( double x, double y );
virtual BOOL CanBeTarget() { return TRUE; };
};

class CShip : public CObject
{
public:
CShip();

virtual void UpdateSrcRect();
virtual LPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init();
virtual void UpdateFrame( DWORD tickDiff );
virtual BOOL CanBeBullet() { return TRUE; };

BOOLlastShield;
};

class CBullet : public CObject
{
enum {
BULLET_X = 304,
BULLET_Y = 0
};
public:
CBullet();

virtual void UpdateSrcRect();
virtual LPDIRECTDRAWSURFACE GetSurface();
virtual int  Hit( double l, double t );

virtual void Init( double x, double y );
virtual BOOL CheckFrame();
virtual BOOL CanBeBullet() { return TRUE; };
};

/*
 * fn prototypes
 */
voidAppPause(void);
voidAppUnpause(void);
voidCheckOneMenuItem(HMENU hmenu, UINT idc, BOOL fCheck);
voidCheckMenuItems(HWND hwnd);

BOOLInitApplication( HANDLE hInstance, int nCmdShow );
BOOLInitializeGame( void );
voidInitLevel( int level );
voidSetupGame();

voidUpdateFrame( void );
voidUpdateDisplayList( void );
voidCheckForHits( void );
voidDrawDisplayList( void );

voidDisplayFrameRate( void );
voidDisplayLevel( void );
voidBltScore( char *num, int x, int y );
voidBltSplash( void );
BOOLEraseScreen( void );
BOOL FlipScreen( void );
BOOLCleanupAndExit( char *err );
BOOLCleanupAndExit( char *err, HRESULT ddrval );

#ifdef USE_DSOUND
voidPlayPanned(HSNDOBJ hSO, double posx);
voidInitializeSound( void );
voidDestroySound( void );
#endif

#endif


DSUTIL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>

typedef struct
{
    BYTE *pbWaveData;               // pointer into wave resource (for restore)
    DWORD cbWaveSize;               // size of wave data (for restore)
    int iAlloc;                     // number of buffers.
    int iCurrent;                   // current buffer
    IDirectSoundBuffer* Buffers[1]; // list of buffers

} SNDOBJ, *HSNDOBJ;

#define _HSNDOBJ_DEFINED
#include "dsutil.h"

static const char c_szWAV[] = "WAV";

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName)
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};
    BYTE *pbWaveData;

    if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
    {
        dsBD.dwSize = sizeof(dsBD);
        dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2;

        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName)
{
    BOOL result=FALSE;
    BYTE *pbWaveData;
    DWORD cbWaveSize;

    if (DSGetWaveResource(NULL, lpName, NULL, &pbWaveData, &cbWaveSize))
    {
        if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)) &&
            DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
        {
            result = TRUE;
        }
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pcbWaveSize)
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    void *pvRes;

    if (((hResInfo = FindResource(hModule, lpName, c_szWAV)) != NULL) &&
        ((hResData = LoadResource(hModule, hResInfo)) != NULL) &&
        ((pvRes = LockResource(hResData)) != NULL) &&
        DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize))
    {
        return TRUE;
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// SndObj fns
///////////////////////////////////////////////////////////////////////////////

SNDOBJ *SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent)
{
    SNDOBJ *pSO = NULL;
    LPWAVEFORMATEX pWaveHeader;
    BYTE *pbData;
    ULONG cbData;

    if (DSGetWaveResource(NULL, lpName, &pWaveHeader, &pbData, &cbData))
    {
        if (iConcurrent < 1)
            iConcurrent = 1;

        if ((pSO = (SNDOBJ *)LocalAlloc(LPTR, sizeof(SNDOBJ) +
            (iConcurrent-1) * sizeof(IDirectSoundBuffer *))) != NULL)
        {
            int i;

            pSO->iAlloc = iConcurrent;
            pSO->pbWaveData = pbData;
            pSO->cbWaveSize = cbData;
            pSO->Buffers[0] = DSLoadSoundBuffer(pDS, lpName);

            for (i=1; i<pSO->iAlloc; i++)
            {
                if (FAILED(IDirectSound_DuplicateSoundBuffer(pDS,
                    pSO->Buffers[0], &pSO->Buffers[i])))
                {
                    pSO->Buffers[i] = DSLoadSoundBuffer(pDS, lpName);
                    if (!pSO->Buffers[i]) {
                        SndObjDestroy(pSO);
                        pSO = NULL;
                        break;
                    }
                }
            }
        }
    }

    return pSO;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void SndObjDestroy(SNDOBJ *pSO)
{
    if (pSO)
    {
        int i;

        for (i=0; i<pSO->iAlloc; i++)
        {
            if (pSO->Buffers[i])
            {
                IDirectSoundBuffer_Release(pSO->Buffers[i]);
                pSO->Buffers[i] = NULL;
            }
        }
        LocalFree((HANDLE)pSO);
    }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *SndObjGetFreeBuffer(SNDOBJ *pSO)
{
    IDirectSoundBuffer *pDSB;

    if (pSO == NULL)
        return NULL;

    if (pDSB = pSO->Buffers[pSO->iCurrent])
    {
        HRESULT hres;
        DWORD dwStatus;

        hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

        if (FAILED(hres))
            dwStatus = 0;

        if ((dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
        {
            if (pSO->iAlloc > 1)
            {
                if (++pSO->iCurrent >= pSO->iAlloc)
                    pSO->iCurrent = 0;

                pDSB = pSO->Buffers[pSO->iCurrent];
                hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

                if (SUCCEEDED(hres) && (dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
                {
                    IDirectSoundBuffer_Stop(pDSB);
                    IDirectSoundBuffer_SetCurrentPosition(pDSB, 0);
                }
            }
            else
            {
                pDSB = NULL;
            }
        }

        if (pDSB && (dwStatus & DSBSTATUS_BUFFERLOST))
        {
            if (FAILED(IDirectSoundBuffer_Restore(pDSB)) ||
                !DSFillSoundBuffer(pDSB, pSO->pbWaveData, pSO->cbWaveSize))
            {
                pDSB = NULL;
            }
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjPlay(SNDOBJ *pSO, DWORD dwPlayFlags)
{
    BOOL result = FALSE;

    if (pSO == NULL)
        return FALSE;

    if ((!(dwPlayFlags & DSBPLAY_LOOPING) || (pSO->iAlloc == 1)))
    {
        IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(pSO);
        if (pDSB != NULL) {
            result = SUCCEEDED(IDirectSoundBuffer_Play(pDSB, 0, 0, dwPlayFlags));
        }
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjStop(SNDOBJ *pSO)
{
    int i;

    if (pSO == NULL)
        return FALSE;

    for (i=0; i<pSO->iAlloc; i++)
    {
        IDirectSoundBuffer_Stop(pSO->Buffers[i]);
        IDirectSoundBuffer_SetCurrentPosition(pSO->Buffers[i], 0);
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize)
{
    if (pDSB && pbWaveData && cbWaveSize)
    {
        LPVOID pMem1, pMem2;
        DWORD dwSize1, dwSize2;

        if (SUCCEEDED(IDirectSoundBuffer_Lock(pDSB, 0, cbWaveSize,
            &pMem1, &dwSize1, &pMem2, &dwSize2, 0)))
        {
            CopyMemory(pMem1, pbWaveData, dwSize1);

            if ( 0 != dwSize2 )
                CopyMemory(pMem2, pbWaveData+dwSize1, dwSize2);

            IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
            return TRUE;
        }
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,DWORD *pcbWaveSize)
{
    DWORD *pdw;
    DWORD *pdwEnd;
    DWORD dwRiff;
    DWORD dwType;
    DWORD dwLength;

    if (ppWaveHeader)
        *ppWaveHeader = NULL;

    if (ppbWaveData)
        *ppbWaveData = NULL;

    if (pcbWaveSize)
        *pcbWaveSize = 0;

    pdw = (DWORD *)pvRes;
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
        goto exit;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
        goto exit;      // not a WAV

    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);

    while (pdw < pdwEnd)
    {
        dwType = *pdw++;
        dwLength = *pdw++;

        switch (dwType)
        {
        case mmioFOURCC('f', 'm', 't', ' '):
            if (ppWaveHeader && !*ppWaveHeader)
            {
                if (dwLength < sizeof(WAVEFORMAT))
                    goto exit;      // not a WAV

                *ppWaveHeader = (WAVEFORMATEX *)pdw;

                if ((!ppbWaveData || *ppbWaveData) &&
                    (!pcbWaveSize || *pcbWaveSize))
                {
                    return TRUE;
                }
            }
            break;

        case mmioFOURCC('d', 'a', 't', 'a'):
            if ((ppbWaveData && !*ppbWaveData) ||
                (pcbWaveSize && !*pcbWaveSize))
            {
                if (ppbWaveData)
                    *ppbWaveData = (LPBYTE)pdw;

                if (pcbWaveSize)
                    *pcbWaveSize = dwLength;

                if (!ppWaveHeader || *ppWaveHeader)
                    return TRUE;
            }
            break;
        }

        pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
    }

exit:
    return FALSE;
}


DSUTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer    Loads an IDirectSoundBuffer from a Win32 resource in
//                      the current application.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the buffer.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns an IDirectSoundBuffer containing the wave data or NULL on error.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//      IDirectSoundBuffer *pDSB = DSLoadSoundBuffer(pDS, "Turtle");
//
//      if (pDSB)
//      {
//          IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//          /* ... */
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer  Reloads an IDirectSoundBuffer from a Win32 resource in
//                      the current application. normally used to handle
//                      a DSERR_BUFFERLOST error.
// Params:
//  pDSB        -- Pointer to an IDirectSoundBuffer to be reloaded.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns a BOOL indicating whether the buffer was successfully reloaded.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//  TryAgain:
//      HRESULT hres = IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//
//      if (FAILED(hres))
//      {
//          if ((hres == DSERR_BUFFERLOST) &&
//              DSReloadSoundBuffer(pDSB, "Turtle"))
//          {
//              goto TryAgain;
//          }
//          /* deal with other errors... */
//      }
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource    Finds a WAV resource in a Win32 module.
//
// Params:
//  hModule     -- Win32 module handle of module containing WAV resource.
//                 Pass NULL to indicate current application.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  ppWaveHeader-- Optional pointer to WAVEFORMATEX * to receive a pointer to
//                 the WAVEFORMATEX header in the specified WAV resource.
//                 Pass NULL if not required.
//
//  ppbWaveData -- Optional pointer to BYTE * to receive a pointer to the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
//  pdwWaveSize -- Optional pointer to DWORD to receive the size of the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
// Returns a BOOL indicating whether a valid WAV resource was found.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

///////////////////////////////////////////////////////////////////////////////
//
// HSNDOBJ             Handle to a SNDOBJ object.
//
//  SNDOBJs are implemented in dsutil as an example layer built on top
//      of DirectSound.
//
//      A SNDOBJ is generally used to manage individual
//      sounds which need to be played multiple times concurrently.  A
//      SNDOBJ represents a queue of IDirectSoundBuffer objects which
//      all refer to the same buffer memory.
//
//      A SNDOBJ also automatically reloads the sound resource when
//      DirectSound returns a DSERR_BUFFERLOST
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _HSNDOBJ_DEFINED
DECLARE_HANDLE32(HSNDOBJ);
#endif

///////////////////////////////////////////////////////////////////////////////
//
// SndObjCreate     Loads a SNDOBJ from a Win32 resource in
//                  the current application.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the SNDOBJ.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  iConcurrent -- Integer representing the number of concurrent playbacks of
//                 to plan for.  Attempts to play more than this number will
//                 succeed but will restart the least recently played buffer
//                 even if it is not finished playing yet.
//
// Returns an HSNDOBJ or NULL on error.
//
// NOTES:
//      SNDOBJs automatically restore and reload themselves as required.
//
///////////////////////////////////////////////////////////////////////////////
HSNDOBJ SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjDestroy  Frees a SNDOBJ and releases all of its buffers.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to free.
//
///////////////////////////////////////////////////////////////////////////////
void SndObjDestroy(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjPlay   Plays a buffer in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
//  dwPlayFlags -- Flags to pass to IDirectSoundBuffer::Play.  It is not
//                 legal to play an SndObj which has more than one buffer
//                 with the DSBPLAY_LOOPING flag.  Pass 0 to stop playback.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjPlay(HSNDOBJ hSO, DWORD dwPlayFlags);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjStop   Stops one or more buffers in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjStop(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjGetFreeBuffer      returns one of the cloned buffers that is
//                          not currently playing
//
// Params:
//  hSO         -- Handle to a SNDOBJ
//
// NOTES:
//  This function is provided so that callers can set things like pan etc
//  before playing the buffer.
//
// EXAMPLE:
//  ...
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *SndObjGetFreeBuffer(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// helper routines
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD dwWaveSize);
BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

#ifdef __cplusplus
}
#endif


INPUT.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.cpp
 *  Content:    Input routines for Space Donuts game
 *
 * @@BEGIN_MSINTERNAL
 * History:
 *  DateByReason
 *  ============
 *  22-aug-96davidklinitial (input.c)
 *  15-sep-07t-craigsconverted to cpp
 *
 * @@END_MSINTERNAL
 *
 ***************************************************************************/

#include <dinput.h>
#include "input.h"
#include "resource.h"

extern HWND  hWndMain;
extern DWORD ReadKeyboardInput(void);
extern DWORD ReadJoystickInput(void);

// allocate external variables
DWORD (*ReadGameInput)(void) = ReadKeyboardInput;

/*
 *  We'll use up to the first ten input devices.
 *
 *  c_cpdiFound is the number of found devices.
 *  g_rgpdiFound[0] is the array of found devices.
 *  g_pdevCurrent is the device that we are using for input.
 */
#define MAX_DINPUT_DEVICES 10
int g_cpdevFound;
LPDIRECTINPUTDEVICE g_rgpdevFound[MAX_DINPUT_DEVICES];
LPDIRECTINPUTDEVICE g_pdevCurrent;

// @@BEGIN_MSINTERNAL
#ifdef USE_CUSTOM_DATA
/*--------------------------------------------------------------------------
| Create custom keyboard data format:
|
| This is included to provide an example of how to create and use a custom
| keyboard data format with DirectInput.  The following format tells
| DirectInput to provide data on the "game-play" keys only, and to ignore
| the remaining keys.
|
| This is not truely needed for this game, rather it is included as an
| example for those who may need this support in an application.
*-------------------------------------------------------------------------*/

#pragma BEGIN_CONST_DATA

   // create an array of keys...
   static DIOBJECTDATAFORMAT c_rgodfSpaceDonutsKeys[] =
{
{ &GUID_Key, 0, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_SPACE) | 0x80000000, },
{ &GUID_Key, 1, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_UP) | 0x80000000, },
{ &GUID_Key, 2, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_DOWN) | 0x80000000, },
{ &GUID_Key, 3, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_LEFT) | 0x80000000, },
{ &GUID_Key, 4, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_RIGHT) | 0x80000000, },
{ &GUID_Key, 5, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD2) | 0x80000000, },
{ &GUID_Key, 6, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD4) | 0x80000000, },
{ &GUID_Key, 7, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD5) | 0x80000000, },
{ &GUID_Key, 8, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD6) | 0x80000000, },
{ &GUID_Key, 9, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD7) | 0x80000000, },
{ &GUID_Key, 10, DIDFT_BUTTON | DIDFT_MAKEINSTANCE(DIK_NUMPAD8) | 0x80000000, },
};

   // the following creates the data format based on the array above
   // (note: the size of the format MUST be a multiple of 4!)
   const DIDATAFORMAT c_dfSpaceDonutsKeys =
{
sizeof(DIDATAFORMAT),
sizeof(DIOBJECTDATAFORMAT),
DIDF_RELAXIS,
12,
sizeof(c_rgodfSpaceDonutsKeys) / sizeof(c_rgodfSpaceDonutsKeys[0]),
c_rgodfSpaceDonutsKeys,
};
#pragma END_CONST_DATA

/*--------------------------------------------------------------------------
| Below are symbolic constant definitions for use with our custom data
| format.  They only have meaning in the context of this application.  If
| you are not planning on using a custom keyboard data format, you will
| use the DIK_* constants defined in DINPUT.H.
*-------------------------------------------------------------------------*/
#define SD_SPACE     0x00
#define SD_UP        0x01
#define SD_DOWN      0x02
#define SD_LEFT      0x03
#define SD_RIGHT     0x04
#define SD_NUMPAD2   0x05
#define SD_NUMPAD4   0x06
#define SD_NUMPAD5   0x07
#define SD_NUMPAD6   0x08
#define SD_NUMPAD7   0x09
#define SD_NUMPAD8   0x0A

#endif // USE_CUSTOM_DATA
// @@END_MSINTERNAL

/*--------------------------------------------------------------------------
| AddInputDevice
|
| Records an input device in the array of found devices.
|
| In addition to stashing it in the array, we also add it to the device
| menu so the user can pick it.
|
*-------------------------------------------------------------------------*/

void AddInputDevice(LPDIRECTINPUTDEVICE pdev, LPCDIDEVICEINSTANCE pdi)
{

    if (g_cpdevFound < MAX_DINPUT_DEVICES) {

        HMENU hmenu;

        /*
         *  Add its description to the menu.
         */
        hmenu = GetSubMenu(GetMenu(hWndMain), 0);

        InsertMenu(hmenu, g_cpdevFound, MF_BYPOSITION | MF_STRING,
                   IDC_DEVICES + g_cpdevFound,
                   pdi->tszInstanceName);

        /*
         *  Save it in the array and AddRef it so it stays valid.
         */
        g_rgpdevFound[g_cpdevFound++] = pdev;
        pdev->AddRef();

    }
}

/*--------------------------------------------------------------------------
|
| SetDIDwordProperty
|
| Set a DWORD property on a DirectInputDevice.
|
*-------------------------------------------------------------------------*/

HRESULT
SetDIDwordProperty(LPDIRECTINPUTDEVICE pdev, REFGUID guidProperty,
                   DWORD dwObject, DWORD dwHow, DWORD dwValue)
{
   DIPROPDWORD dipdw;

   dipdw.diph.dwSize       = sizeof(dipdw);
   dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
   dipdw.diph.dwObj        = dwObject;
   dipdw.diph.dwHow        = dwHow;
   dipdw.dwData            = dwValue;

   return pdev->SetProperty( guidProperty, &dipdw.diph);

}

/*--------------------------------------------------------------------------
| InitKeyboardInput
|
| Initializes DirectInput for the keyboard.  Creates a keyboard device,
| sets the data format to our custom format, sets the cooperative level and
| adds it to the menu.
|
*-------------------------------------------------------------------------*/

BOOL InitKeyboardInput(LPDIRECTINPUT pdi)
{
   LPDIRECTINPUTDEVICE pdev;
   DIDEVICEINSTANCE di;

   // create the DirectInput keyboard device
   if(pdi->CreateDevice( GUID_SysKeyboard, &pdev, NULL) != DI_OK)
   {
      OutputDebugString("IDirectInput::CreateDevice FAILED\n");
      return FALSE;
   }

// @@BEGIN_MSINTERNAL
#ifdef USE_CUSTOM_DATA
   // tell DirectInput to use our custom format
   if(pdev->lpVtbl->SetDataFormat(pdev, &c_dfSpaceDonutsKeys) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetDataFormat FAILED\n");
      pdev->Release();
      return FALSE;
   }
#else
// @@END_MSINTERNAL
   // set keyboard data format
   if(pdev->SetDataFormat( &c_dfDIKeyboard) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetDataFormat FAILED\n");
      pdev->Release();
      return FALSE;
   }
// @@BEGIN_MSINTERNAL
#endif // USE_CUSTOM_DATA
// @@END_MSINTERNAL

   // set the cooperative level
   if(pdev->SetCooperativeLevel( hWndMain, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetCooperativeLevel FAILED\n");
      pdev->Release();
      return FALSE;
   }

   // set buffer size
   if (SetDIDwordProperty(pdev, DIPROP_BUFFERSIZE, 0, DIPH_DEVICE, KEYBUFSIZE) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEVICE) FAILED\n");
      pdev->Release();
      return FALSE;
   }

   //
   // Get the name of the primary keyboard so we can add it to the menu.
   //
   di.dwSize = sizeof(di);
// @@BEGIN_MSINTERNAL

/*   di.dwSize = sizeof(DIDEVICEINSTANCE30);*/

// @@END_MSINTERNAL
   if (pdev->GetDeviceInfo( &di) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::GetDeviceInfo FAILED\n");
      pdev->Release();
      return FALSE;
   }

   //
   // Add it to our list of devices.  If AddInputDevice succeeds,
   // he will do an AddRef.
   //
   AddInputDevice(pdev, &di);
   pdev->Release();

   return TRUE;
}

/*--------------------------------------------------------------------------
| InitJoystickInput
|
| Initializes DirectInput for an enumerated joystick device.
| Creates the device, device, sets the standard joystick data format,
| sets the cooperative level and adds it to the menu.
|
| If any step fails, just skip the device and go on to the next one.
|
*-------------------------------------------------------------------------*/

BOOL FAR PASCAL InitJoystickInput(LPCDIDEVICEINSTANCE pdinst, LPVOID pvRef)
{
   LPDIRECTINPUT pdi = (LPDIRECTINPUT) pvRef;
   LPDIRECTINPUTDEVICE pdev;
   DIPROPRANGE diprg;

   // create the DirectInput joystick device
   if(pdi->CreateDevice( pdinst->guidInstance, &pdev, NULL) != DI_OK)
   {
      OutputDebugString("IDirectInput::CreateDevice FAILED\n");
      return DIENUM_CONTINUE;
   }

   // set joystick data format
   if(pdev->SetDataFormat( &c_dfDIJoystick) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetDataFormat FAILED\n");
      pdev->Release();
      return DIENUM_CONTINUE;
   }

   // set the cooperative level
   if(pdev->SetCooperativeLevel( hWndMain, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetCooperativeLevel FAILED\n");
      pdev->Release();
      return DIENUM_CONTINUE;
   }

   // set X-axis range to (-1000 ... +1000)
   // This lets us test against 0 to see which way the stick is pointed.

   diprg.diph.dwSize       = sizeof(diprg);
   diprg.diph.dwHeaderSize = sizeof(diprg.diph);
   diprg.diph.dwObj        = DIJOFS_X;
   diprg.diph.dwHow        = DIPH_BYOFFSET;
   diprg.lMin              = -1000;
   diprg.lMax              = +1000;

   if(pdev->SetProperty( DIPROP_RANGE, &diprg.diph) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_RANGE) FAILED\n");
      pdev->Release();
      return FALSE;
   }

   //
   // And again for Y-axis range
   //
   diprg.diph.dwObj        = DIJOFS_Y;

   if(pdev->SetProperty( DIPROP_RANGE, &diprg.diph) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_RANGE) FAILED\n");
      pdev->Release();
      return FALSE;
   }

   // set X axis dead zone to 50% (to avoid accidental turning)
   if (SetDIDwordProperty(pdev, DIPROP_DEADZONE, DIJOFS_X, DIPH_BYOFFSET, 50) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEADZONE) FAILED\n");
      pdev->Release();
      return FALSE;
   }

   // set Y axis dead zone to 50% (to avoid accidental thrust)
   if (SetDIDwordProperty(pdev, DIPROP_DEADZONE, DIJOFS_Y, DIPH_BYOFFSET, 50) != DI_OK)
   {
      OutputDebugString("IDirectInputDevice::SetProperty(DIPH_DEADZONE) FAILED\n");
      pdev->Release();
      return FALSE;
   }

   //
   // Add it to our list of devices.  If AddInputDevice succeeds,
   // he will do an AddRef.
   //
   AddInputDevice(pdev, pdinst);
   pdev->Release();

   return DIENUM_CONTINUE;
}

/*--------------------------------------------------------------------------
| InitInput
|
| Initializes DirectInput for the keyboard and all joysticks.
|
| For each input device, add it to the menu.
|
*-------------------------------------------------------------------------*/

BOOL InitInput(HINSTANCE hInst, HWND hWnd)
{
   LPDIRECTINPUT pdi;
   BOOL fRc;

   // Note: Joystick support is a DirectX 5.0 feature.
   // Since we also want to run on DirectX 3.0, we will start out
   // with DirectX 3.0 to make sure that at least we get the keyboard.

   // create the DirectInput interface object
   if(DirectInputCreate(hInst, 0x0300, &pdi, NULL) != DI_OK)
   {
      OutputDebugString("DirectInputCreate 3.0 FAILED\n");
      return FALSE;
   }

   fRc = InitKeyboardInput(pdi);
   pdi->Release();       // Finished with DX 3.0

   if (!fRc) {
      return FALSE;
   }

   // create the DirectInput 5.0 interface object
   if(DirectInputCreate(hInst, DIRECTINPUT_VERSION, &pdi, NULL) == DI_OK)
   {

      //
      // Enumerate the joystick devices.  If it doesn't work, oh well,
      // at least we got the keyboard.
      //

      pdi->EnumDevices( DIDEVTYPE_JOYSTICK, InitJoystickInput, pdi, DIEDFL_ATTACHEDONLY);

      pdi->Release();    // Finished with DX 5.0.

   } else {
      OutputDebugString("DirectInputCreate 5.0 FAILED - no joystick support\n");
   }

   // Default device is the keyboard
   PickInputDevice(0);

   // if we get here, we were successful
   return TRUE;
}

/*--------------------------------------------------------------------------
| CleanupInput
|
| Cleans up all DirectInput objects.
*-------------------------------------------------------------------------*/
void CleanupInput(void)
{
   int idev;

   // make sure the device is unacquired
   // it doesn't harm to unacquire a device that isn't acquired

   if (g_pdevCurrent)
   {
      IDirectInputDevice_Unacquire(g_pdevCurrent);
   }

   // release all the devices we created
   for (idev = 0; idev < g_cpdevFound; idev++)
   {
      if (g_rgpdevFound[idev]) {
         IDirectInputDevice_Release(g_rgpdevFound[idev]);
         g_rgpdevFound[idev] = 0;
      }
   }

}

/*--------------------------------------------------------------------------
| ReacquireInput
|
| Reacquires the current input device.  If Acquire() returns S_FALSE,
| that means
| that we are already acquired and that DirectInput did nothing.
*-------------------------------------------------------------------------*/
BOOL ReacquireInput(void)
{
    HRESULT hRes;

    // if we have a current device
    if(g_pdevCurrent)
    {
       // acquire the device
       hRes = IDirectInputDevice_Acquire(g_pdevCurrent);
       if(SUCCEEDED(hRes))
       {
          // acquisition successful
          return TRUE;
       }
       else
       {
          // acquisition failed
          return FALSE;
       }
    }
    else
    {
       // we don't have a current device
       return FALSE;
    }

}

/*--------------------------------------------------------------------------
| ReadKeyboardInput
|
| Requests keyboard data and performs any needed processing.
@@BEGIN_MSINTERNAL
| NOTE: this
| function uses the custom data format and peforms some app specific
| translations, it does not use the standard DirectInput keyboard data
| format!
@@END_MSINTERNAL
*-------------------------------------------------------------------------*/
DWORD ReadKeyboardInput(void)
{
   DIDEVICEOBJECTDATA      rgKeyData[KEYBUFSIZE];
   DWORD                   dwEvents;
   DWORD                   dw;
   static DWORD            dwKeyState = 0;
   HRESULT                 hRes;

   // get data from the keyboard
   dwEvents = KEYBUFSIZE;
   hRes = IDirectInputDevice_GetDeviceData(g_pdevCurrent,
                                           sizeof(DIDEVICEOBJECTDATA),
                                           rgKeyData, &dwEvents, 0);

   if(hRes != DI_OK)
   {
      // did the read fail because we lost input for some reason?
      // if so, then attempt to reacquire.  If the second acquire
      // fails, then the error from GetDeviceData will be
      // DIERR_NOTACQUIRED, so we won't get stuck an infinite loop.
      if(hRes == DIERR_INPUTLOST)
         ReacquireInput();

      // return the fact that we did not read any data
      return 0;
   }

// @@BEGIN_MSINTERNAL
#ifdef USE_CUSTOM_DATA
   // process the data
   for(dw = 0; dw < dwEvents; dw++)
   {
      switch(rgKeyData[dw].dwOfs)
      {
      // fire
      case SD_SPACE:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_FIRE;
         else
            dwKeyState &= (DWORD)~KEY_FIRE;
         break;

      // stop
      case SD_NUMPAD5:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_STOP;
         else
            dwKeyState &= ~KEY_STOP;
         break;

      // shield
      case SD_NUMPAD7:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_SHIELD;
         else
            dwKeyState &= ~KEY_SHIELD;
         break;

      // thrust
      case SD_UP:
      case SD_NUMPAD8:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_UP;
         else
            dwKeyState &= ~KEY_UP;
         break;

      // reverse thrust
      case SD_DOWN:
      case SD_NUMPAD2:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_DOWN;
         else
            dwKeyState &= ~KEY_DOWN;
         break;

      // rotate left
      case SD_LEFT:
      case SD_NUMPAD4:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_LEFT;
         else
            dwKeyState &= ~KEY_LEFT;
         break;

      // rotate right
      case SD_RIGHT:
      case SD_NUMPAD6:
         if(rgKeyData[dw].dwData & 0x80)
            dwKeyState |= KEY_RIGHT;
         else
            dwKeyState &= ~KEY_RIGHT;
         break;
      }
#else
// @@END_MSINTERNAL
      // process the data
      for(dw = 0; dw < dwEvents; dw++)
      {
         switch(rgKeyData[dw].dwOfs)
         {
         // fire
         case DIK_SPACE:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_FIRE;
            else
               dwKeyState &= (DWORD)~KEY_FIRE;
            break;

         // stop
         case DIK_NUMPAD5:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_STOP;
            else
               dwKeyState &= ~KEY_STOP;
            break;

         // shield
         case DIK_NUMPAD7:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_SHIELD;
            else
               dwKeyState &= ~KEY_SHIELD;
            break;

         // thrust
         case DIK_UP:
         case DIK_NUMPAD8:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_UP;
            else
               dwKeyState &= ~KEY_UP;
            break;

         // reverse thrust
         case DIK_DOWN:
         case DIK_NUMPAD2:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_DOWN;
            else
               dwKeyState &= ~KEY_DOWN;
            break;

         // rotate left
         case DIK_LEFT:
         case DIK_NUMPAD4:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_LEFT;
            else
               dwKeyState &= ~KEY_LEFT;
            break;

         // rotate right
         case DIK_RIGHT:
         case DIK_NUMPAD6:
            if(rgKeyData[dw].dwData & 0x80)
               dwKeyState |= KEY_RIGHT;
            else
               dwKeyState &= ~KEY_RIGHT;
            break;
         }
// @@BEGIN_MSINTERNAL
#endif // USE_CUSTOM_DATA
// @@END_MSINTERNAL

   }

   // return the state of the keys to the caller
   return dwKeyState;

}

/*--------------------------------------------------------------------------
| ReadJoystickInput
|
| Requests joystick data and performs any needed processing.
|
*-------------------------------------------------------------------------*/
DWORD ReadJoystickInput(void)
{
   DWORD                   dwKeyState;
   HRESULT                 hRes;
   DIJOYSTATE              js;

   // get data from the joystick
   hRes = IDirectInputDevice_GetDeviceState(g_pdevCurrent,
                                            sizeof(DIJOYSTATE), &js);

   if(hRes != DI_OK)
   {
      // did the read fail because we lost input for some reason?
      // if so, then attempt to reacquire.  If the second acquire
      // fails, then the error from GetDeviceData will be
      // DIERR_NOTACQUIRED, so we won't get stuck an infinite loop.
      if(hRes == DIERR_INPUTLOST)
         ReacquireInput();

      // return the fact that we did not read any data
      return 0;
   }

   //
   // Now study the position of the stick and the buttons.
   //

   dwKeyState = 0;

   if (js.lX < 0) {
      dwKeyState |= KEY_LEFT;
   } else if (js.lX > 0) {
      dwKeyState |= KEY_RIGHT;
   }

   if (js.lY < 0) {
      dwKeyState |= KEY_UP;
   } else if (js.lY > 0) {
      dwKeyState |= KEY_DOWN;
   }

   if (js.rgbButtons[0] & 0x80) {
      dwKeyState |= KEY_FIRE;
   }

   if (js.rgbButtons[1] & 0x80) {
      dwKeyState |= KEY_SHIELD;
   }

   if (js.rgbButtons[2] & 0x80) {
      dwKeyState |= KEY_STOP;
   }

   return dwKeyState;

}

/*--------------------------------------------------------------------------
| PickInputDevice
|
| Make the n'th input device the one that we will use for game play.
|
*-------------------------------------------------------------------------*/

BOOL PickInputDevice(int n)
{
    if (n < g_cpdevFound) {

        /*
         *  Unacquire the old device.
         */
        if (g_pdevCurrent) {
            IDirectInputDevice_Unacquire(g_pdevCurrent);
        }

        /*
         *  Move to the new device.
         */
        g_pdevCurrent = g_rgpdevFound[n];

        /*
         *  Set ReadGameInput to the appropriate handler.
         */
        if (n == 0) {
            ReadGameInput = ReadKeyboardInput;
        } else {
            ReadGameInput = ReadJoystickInput;
        }

        CheckMenuRadioItem(GetSubMenu(GetMenu(hWndMain), 0),
                           IDC_DEVICES, IDC_DEVICES + g_cpdevFound - 1,
                           IDC_DEVICES + n, MF_BYCOMMAND);

        ReacquireInput();

        return TRUE;
    }
else
{
        return FALSE;
    }
}


INPUT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.h
 *
 * @@BEGIN_MSINTERNAL
 * History:
 *  DateByReason
 *  ============
 *  22-aug-96davidklinitial
 *
 * @@END_MSINTERNAL
 *
 ***************************************************************************/

#ifndef _INPUT_H
#define _INPUT_H

#include <dinput.h>

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

// @@BEGIN_MSINTERNAL
#define BEGIN_CONST_DATA data_seg(".text", "CODE")
#define END_CONST_DATA data_seg(".data", "DATA")
// @@END_MSINTERNAL

// keyboard buffer size
#define KEYBUFSIZE 32

/*
 * keyboard commands
 */
#define KEY_STOP   0x00000001l
#define KEY_DOWN   0x00000002l
#define KEY_LEFT   0x00000004l
#define KEY_RIGHT  0x00000008l
#define KEY_UP     0x00000010l
#define KEY_FIRE   0x00000020l
#define KEY_THROW  0x00000040l
#define KEY_SHIELD 0x00000080l

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

// external variables
extern BOOL bKeyboardAcquired;

extern DWORD (*ReadGameInput)(void);

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

// prototypes
BOOL InitInput(HINSTANCE hInst, HWND hWnd);
void CleanupInput(void);
BOOL ReacquireInput(void);
BOOL PickInputDevice(int);

//--------------------------------------------------------------------------
#endif // _INPUT_H


MAIN.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       main.cpp
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#include "donuts.h"

// globals defined elsewhere
externCMyMonitorMonitor[9];
externCShipDL;
externdoubleDirx[40];
externdoubleDiry[40];

// globals
#ifdef DEBUG
char                    DebugBuf[256];
#endif

BOOL                    bSoundEnabled = FALSE;
BOOL                    bPlayIdle = FALSE;
BOOL                    bPlayBuzz = FALSE;
BOOL                    bPlayRev = FALSE;
#ifdef USE_DSOUND
LPDIRECTSOUND           lpDS;
HSNDOBJ                 hsoBeginLevel     = NULL;
HSNDOBJ                 hsoEngineIdle     = NULL;
HSNDOBJ                 hsoEngineRev      = NULL;
HSNDOBJ                 hsoSkidToStop     = NULL;
HSNDOBJ                 hsoShieldBuzz     = NULL;
HSNDOBJ                 hsoShipExplode    = NULL;
HSNDOBJ                 hsoFireBullet     = NULL;
HSNDOBJ                 hsoShipBounce     = NULL;
HSNDOBJ                 hsoDonutExplode   = NULL;
HSNDOBJ                 hsoPyramidExplode = NULL;
HSNDOBJ                 hsoCubeExplode    = NULL;
HSNDOBJ                 hsoSphereExplode  = NULL;
#endif

// global data
HWND            hWndMain;
HACCEL                  hAccel;
HINSTANCE               hInst;
BOOL                    bIsActive;

BOOL                    bSpecialEffects = FALSE;
BOOL                    bWantSound = TRUE;
BOOL                    bShowFrameCount=TRUE;

BOOL                    lastThrust = FALSE;
int                     showDelay = 0;
BOOL                    bMouseVisible;
DWORD                   dwFrameCount;
DWORD                   dwFrameTime;
DWORD                   dwFrames;
DWORD                   dwFramesLast;
DWORD                   lastTickCount;
int                     score;
int                     ProgramState;
int                     level;
int                     restCount;
DWORD                   ShowLevelCount = 3000;
intiForceErase = 0;
RECTrcVirtualDesktop;

//
// FUNCTION:WinMain
//
// DESCRIPTION:Contains main program loop
//
// NOTES:- program starts here!
//
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                        int nCmdShow )
{
    MSG     msg;

// save application instance
    hInst = hInstance;

    while( lpCmdLine[0] == '-' )
    {
        lpCmdLine++;

        switch (*lpCmdLine++)
        {
        case 'S': case 's':
            bWantSound = FALSE;
            break;
        }
        while( IS_SPACE(*lpCmdLine) )
        {
            lpCmdLine++;
        }
    }

// initialize display system
if ( CMyMonitor::Initialize() != DD_OK )
{
CleanupAndExit("Couldn't initialize multimonitor system");
return -1;
}

rcVirtualDesktop.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
rcVirtualDesktop.right = rcVirtualDesktop.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
rcVirtualDesktop.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
rcVirtualDesktop.bottom = rcVirtualDesktop.left + GetSystemMetrics(SM_CYVIRTUALSCREEN);

if( !InitApplication(hInstance, nCmdShow) )
    {
DestroyWindow( hWndMain );
        return FALSE;
    }

    if( !InitializeGame() )
    {
        DestroyWindow( hWndMain );
        return FALSE;
    }

    dwFrameTime = timeGetTime();

while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( !GetMessage( &msg, NULL, 0, 0 ) )
            {
                return msg.wParam;
            }
            if ( !TranslateAccelerator( hWndMain, hAccel, &msg ) )
{
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else if ( bIsActive )
        {
UpdateFrame();
        }
        else
        {
            WaitMessage();
        }
    }
} /* WinMain */

//
// FUNCTION:MainWndproc
//
// DESCRIPTION:Window procedure for message handling
//
// NOTES:
//
long FAR PASCAL MainWndproc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    UINT        cmd;
//PAINTSTRUCT ps;

    switch( message )
    {
    case WM_ACTIVATEAPP:
        bIsActive = (BOOL) wParam;
if( bIsActive )
    {
bMouseVisible = FALSE;
            lastTickCount = GetTickCount();
            bSpecialEffects = FALSE;
// we are active, need to reacquire the keyboard
            ReacquireInput();
}
else
{
bMouseVisible = TRUE;
   // DirectInput automatically unacquires for us in FOREGROUND mode
}
        break;

    case WM_CREATE:
        break;

    case WM_SETCURSOR:
if( !bMouseVisible )
{
SetCursor(NULL);
}
else
{
SetCursor(LoadCursor( NULL, IDC_ARROW ));
}
        return TRUE;

    case WM_COMMAND:
cmd = GET_WM_COMMAND_ID(wParam, lParam);

        if (cmd >= IDC_DEVICES && cmd < IDC_DEVICES + 100)
{
            PickInputDevice(cmd - IDC_DEVICES);
        }
else
{
switch (cmd)
{
case IDC_FRAMERATE:
bShowFrameCount = !bShowFrameCount;
if( bShowFrameCount )
{
dwFrameCount = 0;
dwFrameTime = timeGetTime();
}
break;

case IDC_STARTGAME:
if( ProgramState == PS_SPLASH )
{
ProgramState = PS_BEGINREST;
SetupGame();
}
break;

case IDC_QUIT:
PostMessage( hWnd, WM_CLOSE, 0, 0 );
return 0;

case IDC_AUDIO:
#ifdef USE_DSOUND
if(bWantSound)
{
    if( bSoundEnabled )
    {
    DestroySound();
    }
    else
    {
    InitializeSound();
    }
}
#endif
break;

case IDC_TRAILS:
bSpecialEffects = !bSpecialEffects;
break;
}
break;
}

    case WM_INITMENU:
        CheckMenuItems(hWndMain);
        break;

    case WM_ERASEBKGND:
    return 1;
/*
    case WM_PAINT:
        BeginPaint( hWnd, &ps );
        EndPaint( hWnd, &ps );
        return 1;
*/
    case WM_DESTROY:
        CleanupAndExit(NULL);
        PostQuitMessage( 0 );
        break;

    case WM_ENTERMENULOOP:
AppPause();
break;

    case WM_EXITMENULOOP:
        AppUnpause();
        break;

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

} /* MainWndproc */

//
// FUNCTION:InitApplication
//
// DESCRIPTION:Perform window creation
//
// NOTES:
//
BOOL InitApplication( HANDLE hInstance, int nCmdShow )
{
    WNDCLASS    wc;
    BOOL        rc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndproc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(DONUTS_ICON));
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName =  MAKEINTRESOURCE(DONUTS_MENU);
    wc.lpszClassName = "DonutsClass";
    rc = RegisterClass( &wc );
    if( !rc )
    {
        return FALSE;
    }

    hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(DONUTS_ACCEL));
    if ( !hAccel )
    {
        return FALSE;
    }

    hWndMain = CreateWindowEx(
0,
        "DonutsClass",
        "Donuts",
        WS_VISIBLE | // so we don't have to call ShowWindow
        WS_POPUP |   // non-app window
        WS_CAPTION | // so our menu doesn't look ultra-goofy
        WS_SYSMENU,  // so we get an icon in the tray
        0,
        0,
        Monitor[0].dwWidth,
        Monitor[0].dwHeight,
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    UpdateWindow( hWndMain );

#ifdef USE_DSOUND
    /*
     *  If sound is globally disabled, then disable the sound menu.
     */
    if (!bWantSound)
{
        EnableMenuItem(GetMenu(hWndMain), IDC_AUDIO, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
    }
#endif
    return TRUE;

} /* InitApplication */

//
// FUNCTION:MainWndproc
//
// DESCRIPTION:Initialize DirectX et al
//
// NOTES:
//
BOOL InitializeGame( void )
{
HRESULT ddrval;
for (int i = 0; i < CMonitor::iNumberOfMonitors; i++)
{
ddrval = Monitor[i].DDInit( hWndMain, TRUE );
if ( ddrval != DD_OK )
{
return CleanupAndExit( Monitor[i].szErrMsg, ddrval );
}
ddrval = Monitor[i].RestoreSurfaces();
if ( ddrval != DD_OK )
{
return CleanupAndExit("CMyMonitor::RestoreSurfaces failed", ddrval );
}
}

#ifdef USE_DSOUND
    if(bWantSound)
    {
        InitializeSound();
    }
#endif

// initialize DirectInput
if( !InitInput(hInst, hWndMain) )
{
return CleanupAndExit("DirectInput initialization failed");
}

    score = 0;

lastTickCount = GetTickCount();

    ProgramState = PS_SPLASH;

return TRUE;
}

//
// FUNCTION:AppPause
//
// DESCRIPTION:Draw menu bar
//
// NOTES:
//
void AppPause(void)
{
    DrawMenuBar(hWndMain);
    RedrawWindow(hWndMain, NULL, NULL, RDW_FRAME);
Monitor[0].lpDD->FlipToGDISurface();
}

//
// FUNCTION:AppUnpause
//
// DESCRIPTION:Reset the various time counters so the donuts don't suddenly
//jump halfway across the screen and so the frame rate remains accurate.
//
// NOTES:
//
void AppUnpause(void)
{
iForceErase = 2;
lastTickCount = dwFrameTime = timeGetTime();
}

//
// FUNCTION:CheckOneMenuItem
//
// DESCRIPTION:Checks a single item in a menu
//
// NOTES:
//
void CheckOneMenuItem(HMENU hmenu, UINT idc, BOOL fCheck)
{
    CheckMenuItem(hmenu, idc,
                  fCheck ? (MF_BYCOMMAND | MF_CHECKED)
                         : (MF_BYCOMMAND | MF_UNCHECKED));
}

//
// FUNCTION:AppUnpause
//
// DESCRIPTION:Sync menu checkmarks with internal variables
//
// NOTES:
//
void CheckMenuItems(HWND hwnd)
{
    HMENU hmenu = GetMenu(hwnd);

    CheckOneMenuItem(hmenu, IDC_TRAILS, bSpecialEffects);

#ifdef USE_DSOUND
    CheckOneMenuItem(hmenu, IDC_AUDIO, bWantSound && bSoundEnabled);
#endif

    CheckOneMenuItem(hmenu, IDC_FRAMERATE, bShowFrameCount);

}

//
// FUNCTION:DisplayLevel
//
// DESCRIPTION:Blit "Level ###"
//
// NOTES:
//
void DisplayLevel( void )
{
    char buf[10];
int left, top;

    EraseScreen();
    buf[0] = 10 + '0';
    buf[1] = 11 + '0';
    buf[2] = 12 + '0';
    buf[3] = 11 + '0';
    buf[4] = 10 + '0';
    buf[5] = '\0';

top = Monitor[DL.iMonitor].dwHeight / 2 - 8;
left = (Monitor[DL.iMonitor].dwWidth - 16 * 9) / 2;
    BltScore( buf, left, top );

    buf[0] = level / 100 + '0';
    buf[1] = level / 10 + '0';
    buf[2] = level % 10 + '0';
    buf[3] = '\0';

    left += 16 * 6;
BltScore( buf, left, top );
    FlipScreen();
}

//
// FUNCTION:DisplayFrameRate
//
// DESCRIPTION:Blit the frame rate
//
// NOTES:
//
void DisplayFrameRate( void )
{
    DWORDtime2;
    char    buff[256];

    dwFrameCount++;
    time2 = timeGetTime() - dwFrameTime;
    if( time2 > 1000 )
    {
        dwFrames = (dwFrameCount*1000)/time2;
        dwFrameTime = timeGetTime();
        dwFrameCount = 0;
    }
    if( dwFrames == 0 )
    {
        return;
    }

    if (dwFrames != dwFramesLast)
    {
        dwFramesLast = dwFrames;
    }

    if( dwFrames > 99 )
    {
dwFrames = 99;
    }
    buff[0] = (char)((dwFrames / 10) + '0');
    buff[1] = (char)((dwFrames % 10) + '0');
    buff[2] = '\0';
    BltScore(buff, (int)(0.02 * (double) Monitor[DL.iMonitor].dwWidth), (int)(0.02 * (double) Monitor[DL.iMonitor].dwHeight) );
}

//
// FUNCTION:BltScore
//
// DESCRIPTION:Performs letter & number blitting for frame rate, score, and level ###
//
// NOTES:
//
void BltScore( char *num, int x, int y )
{
    int     i;
    HRESULT ddrval;
DDBLTFX ddbltfx;
DWORDdwFlag;
RECT    src;
RECTdst = { x, y, x + 16, y + 16 };

ddbltfx.dwSize = sizeof( ddbltfx );
dwFlag = DDBLT_KEYSRC;

    for(char* c=num; *c != '\0'; c++)
    {
        while( 1 )
        {
            i = *c - '0';
            src.left = i*16;
            src.top = 0;
            src.right = src.left + 16;
            src.bottom = src.top + 16;

ddrval = Monitor[DL.iMonitor].lpBackBuffer->Blt( &dst, Monitor[DL.iMonitor].lpNum, &src, dwFlag, &ddbltfx );
            if( ddrval == DD_OK )
            {
                break;
            }
            if( ddrval == DDERR_SURFACELOST )
            {
                ddrval = Monitor[DL.iMonitor].RestoreSurfaces();
if ( ddrval != DD_OK )
{
                    CleanupAndExit("BltScore (CMyMonitor::RestoreSurfaces) failed", ddrval );
return;
}
            }
            if( ddrval != DDERR_WASSTILLDRAWING )
            {
                return;
            }
        }
dst.left += 16;
dst.right += 16;
    }
}

//
// FUNCTION:BltSplash
//
// DESCRIPTION:Display intro screen on the primary monitor
//
// NOTES:
//
void BltSplash( void )
{
    HRESULT     ddrval;
    HBITMAP     hbm;
intiMonitor;

    // set the palette
    Monitor[0].lpFrontBuffer->SetPalette( Monitor[0].lpSplashPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), "SPLASH", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if ( hbm == NULL )
return;

    // if the surface is lost, DDCopyBitmap will fail and the surface will
    // be restored in FlipScreen.
    ddrval = DDCopyBitmap( Monitor[0].lpBackBuffer, hbm, 0, 0, 0, 0 );

// blank out other monitors
for (iMonitor = 1; iMonitor < CMonitor::iNumberOfMonitors; iMonitor++)
{
ddrval = Monitor[iMonitor].Blank();
if ( ddrval != DD_OK )
{
CleanupAndExit( "BltSplash (CMyMonitor::Blank) failed", ddrval );
return;
}
}

    DeleteObject( hbm );

    FlipScreen();
}

//
// FUNCTION:SetupGame
//
// DESCRIPTION:Perform start time initialization
//
// NOTES:
//
void SetupGame(void)
{
    restCount = GetTickCount();
    InitLevel( ++level );

    for(int i=0; i< CMonitor::iNumberOfMonitors; i++)
    {
        Monitor[i].lpFrontBuffer->SetPalette( Monitor[i].lpArtPalette );
    };
}

//
// FUNCTION:EraseScreen
//
// DESCRIPTION:Fill back buffers with background color and draws a border
//
// NOTES:
//
BOOL EraseScreen( void )
{
    if ( iForceErase > 0 )
{
--iForceErase;
}
else if( bSpecialEffects )
{
        return TRUE;
}

HRESULT ddrval;
for(int iMonitor=0;iMonitor<CMonitor::iNumberOfMonitors;iMonitor++)
    {
ddrval = Monitor[iMonitor].Blank();
if ( ddrval != DD_OK )
{
return CleanupAndExit("EraseScreen (CMyMonitor::Blank) failed", ddrval );
}

        ddrval = Monitor[iMonitor].DrawBorder();
        if ( ddrval != DD_OK )
{
return CleanupAndExit("EraseScreen (CMyMonitor::DrawBorder) failed", ddrval );
}
}
return TRUE;
}

//
// FUNCTION:FlipScreen
//
// DESCRIPTION:Flip back buffers to front buffers
//
// NOTES:
//
BOOL FlipScreen( void )
{
HRESULT ddrval;
    for(int iMonitor=0;iMonitor<CMonitor::iNumberOfMonitors;iMonitor++)
    {
ddrval = Monitor[iMonitor].Flip();
if ( ddrval != DD_OK )
{
return CleanupAndExit("FlipScreen (CMyMonitor::Flip) failed", ddrval );
}
    }
return TRUE;
}

//
// FUNCTION:InitLevel
//
// DESCRIPTION:Perform new level initialization
//
// NOTES:
//
void InitLevel( int level )
{
    int     i;
doublestartx, starty;

    // clear any stray bullets out of the display list
    while( DL.next != &DL )
    {
        DL.next->DeleteFromList();
    }

// add some donuts
    for(i=0; i<(2*level-1); i++)
    {
        CObject* pObj = new CDonut;
startx = randDouble( Monitor[DL.iMonitor].lpMonitorRect->left, Monitor[DL.iMonitor].lpMonitorRect->right - pObj->km_iSize );
        starty = randDouble( Monitor[DL.iMonitor].lpMonitorRect->top, Monitor[DL.iMonitor].lpMonitorRect->bottom - pObj->km_iSize );

// virtual fct call
pObj->Init( startx, starty );
    }
    DL.Init();
   showDelay = DEF_SHOW_DELAY;
}

//
// FUNCTION:UpdateFrame
//
// DESCRIPTION:Draw the next frame
//
// NOTES:- depends on program state
//- called from main program loop when no messages to process
//
void UpdateFrame( void )
{
    switch( ProgramState )
    {
        case PS_SPLASH:
            // display the splash screen
            BltSplash();
            return;

        case PS_ACTIVE:
UpdateDisplayList();
CheckForHits();
DrawDisplayList();

// check if display list is empty
if ( DL.next == &DL && DL.prev == &DL )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjStop(hsoEngineIdle);
                    SndObjStop(hsoEngineRev);
                }
#endif
                bPlayIdle = FALSE;
                bPlayRev = FALSE;
                lastThrust = DL.lastShield = FALSE;
                ProgramState = PS_BEGINREST;
                restCount = GetTickCount();
                InitLevel( ++level );
            }
            return;

        case PS_BEGINREST:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjPlay(hsoBeginLevel, 0);
            }
#endif
            ProgramState = PS_REST;
            //
            // FALLTHRU
            //
        case PS_REST:
            if( ( GetTickCount() - restCount ) > ShowLevelCount )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);
                }
#endif
                bPlayIdle = TRUE;
                lastTickCount = GetTickCount();
                ProgramState = PS_ACTIVE;
            }
            else
            {
DisplayLevel();
            }
            return;
    }
}

//
// FUNCTION:UpdateDisplayList
//
// DESCRIPTION:Add and remove objects from the display list
//
// NOTES:- reads user input
//- adjust position, speed, and animation frame of objects
//
void UpdateDisplayList( void )
{
    CObject*cur;
CObject*save;
    DWORD       input;
DWORD       thisTickCount = GetTickCount();
    DWORD       tickDiff = thisTickCount - lastTickCount;
    BOOL        event = FALSE;

    lastTickCount = thisTickCount;

input = ReadGameInput();

    if (showDelay)
    {
        showDelay -= (int)tickDiff;
        if (showDelay < 0)
        {
            showDelay = 0;
            DL.lastShield = FALSE;
DL.Init();
        }
    }

event = DL.CheckPosition( tickDiff );

if (event)
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
            PlayPanned(hsoShipBounce, DL.posx);
}
#endif
        event = FALSE;
    }

    if ((event = (showDelay || ((input & KEY_SHIELD) == KEY_SHIELD))) != DL.lastShield)
    {
        if (event && !showDelay)
{
#ifdef USE_DSOUND
        if(bWantSound)
        {
            SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);
}
#endif
    bPlayBuzz = TRUE;
}
        else
{
#ifdef USE_DSOUND
        if(bWantSound)
        {
                SndObjStop(hsoShieldBuzz);
}
#endif
        bPlayBuzz = FALSE;
    }
        DL.lastShield = event;
    }
    if (event)
    {
        input &= ~(KEY_FIRE);
    }

    if (input & KEY_FIRE)
    {
if( !showDelay )
{
            // add a bullet to the scene
            --score;
            if(score < 0)
                score = 0;

#ifdef USE_DSOUND
        if(bWantSound)
        {
                SndObjPlay(hsoFireBullet, 0);
}
#endif
            CObject* pObj = new CBullet;
// virtual fct. call
pObj->Init( Dirx[(int)DL.frame]*6.0 + 16.0 + DL.posx, Diry[(int)DL.frame]*6.0 + 16.0 + DL.posy );
pObj->velx = Dirx[(int)DL.frame]*500.0/1000.0;
pObj->vely = Diry[(int)DL.frame]*500.0/1000.0;

        }
    }

    event = FALSE;
    if( input & KEY_LEFT )
    {
        DL.frame -= 1.0;
        if( DL.frame < 0.0 )
            DL.frame += DL.km_iMaxFrame;
    }
    if( input & KEY_RIGHT )
    {
        DL.frame += 1.0;
        if( DL.frame >= DL.km_iMaxFrame )
            DL.frame -= DL.km_iMaxFrame;
    }
    if( input & KEY_UP )
    {
        DL.velx += Dirx[(int)DL.frame] * 10.0/1000.0;
        DL.vely += Diry[(int)DL.frame] * 10.0/1000.0;
    event = TRUE;
    }
    if( input & KEY_DOWN )
    {
        DL.velx -= Dirx[(int)DL.frame] * 10.0/1000.0;
        DL.vely -= Diry[(int)DL.frame] * 10.0/1000.0;
        event = TRUE;
    }

    if (event != lastThrust)
    {
        if (event)
        {
            input &= ~KEY_STOP;
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjStop(hsoSkidToStop);
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);
}
#endif
bPlayRev = TRUE;
        }
        else
        {
#ifdef USE_DSOUND
if(bWantSound)
{
    SndObjStop(hsoEngineRev);
}
#endif
    bPlayRev = FALSE;
        }
        lastThrust = event;
    }

    if( input & KEY_STOP )
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
        if (DL.velx || DL.vely)
            PlayPanned(hsoSkidToStop, DL.posx);
}
#endif
        DL.velx = 0;
        DL.vely = 0;
    }

    cur = DL.next;
    do
    {
cur->CheckPosition( tickDiff );
cur = cur->next;
    }
    while( cur != &DL );

cur = DL.next;
do
{
// virtual fct. call
if ( cur->CheckFrame() )
{
save = cur;
cur = cur->next;
save->DeleteFromList();
}
else
{
cur = cur->next;
}
}
while( cur != &DL );
}

//
// FUNCTION:CheckForHits
//
// DESCRIPTION:Handle object collisions
//
// NOTES:- updates object bounding boxes
//- look for intersections of bullet-like objects with target-like objects
//
void CheckForHits( void )
{
    CObject*bullet, *target, *save;
    int         x, y, l ,t;
    BOOL        hit;

    // update screen rects
    target = &DL;
    do
    {
target->UpdateRect();
        target = target->next;
    }
    while( target != &DL );

// look for intersections of bullet-like objects with target-like objects
    bullet=&DL;
    do
    {
        hit = FALSE;
        if( !bullet->CanBeBullet() )
        {
            bullet = bullet->next;
            continue;
        }

        x = (bullet->dst.left + bullet->dst.right) / 2;
        y = (bullet->dst.top + bullet->dst.bottom) / 2;

        for( target = DL.next; target != &DL; target = target->next)
        {
            if( !target->CanBeTarget() )
                continue;

            if( (x >= target->dst.left) &&
                (x <  target->dst.right) &&
                (y >= target->dst.top) &&
                (y <  target->dst.bottom) )
            {
// do we have a bullet or the ship with shields on
                if ((bullet != &DL) || !DL.lastShield)
                {
                    // the bullet hit the target
l = target->dst.left;
t = target->dst.top;

// virtual fct call
                    score += target->Hit( l, t );

                    target->DeleteFromList();
                }
                hit = TRUE;
            }

            if( hit )
            {
                if( bullet == &DL )
                {
                    hit = FALSE;
                    if ( !DL.lastShield && !showDelay )
                    {
score += DL.Hit( l ,t );
        if (score < 0)
                            score = 0;
showDelay = DEF_SHOW_DELAY;
                    }
                }
                break;
            }
        } //for

        if( hit )
        {
// get rid of the bullet
            save = bullet;
            bullet = bullet->next;

            save->DeleteFromList();
}
        else
        {
            bullet = bullet->next;
        }
    } while (bullet != &DL);
}

//
// FUNCTION:DrawDisplayList
//
// DESCRIPTION:Blits everything the the screen
//
// NOTES:
//
void DrawDisplayList( void )
{
    CObject*cur;
CObject*last;
    char        scorebuf[11];
    int         rem;

    // calculate score string
    scorebuf[0] = score/10000000 + '0';
    rem = score % 10000000;
    scorebuf[1] = rem/1000000 + '0';
    rem = score % 1000000;
    scorebuf[2] = rem/100000 + '0';
    rem = score % 100000;
    scorebuf[3] = rem/10000 + '0';
    rem = score % 10000;
    scorebuf[4] = rem/1000 + '0';
    rem = score % 1000;
    scorebuf[5] = rem/100 + '0';
    rem = score % 100;
    scorebuf[6] = rem/10 + '0';
    rem = score % 10;
    scorebuf[7] = rem + '0';
#ifdef USE_DSOUND
    if( bSoundEnabled )
    {
    scorebuf[8] = 14 + '0';
    scorebuf[9] = 13 + '0';
    scorebuf[10] = '\0';
    }
    else
#endif
    {
        scorebuf[8] = '\0';
    }

    EraseScreen();

cur = &DL;// start with the bottommost bitmap (the ship)
last = &DL;// don't blt it twice

    if (showDelay)
    cur = cur->prev;

    do
    {
cur->Blt();
cur = cur->prev;
    }
    while( cur != last );

BltScore(scorebuf, (int)(0.05 * Monitor[DL.iMonitor].dwWidth), (int)(0.95 * Monitor[DL.iMonitor].dwHeight));

    if( bShowFrameCount )
DisplayFrameRate();

    FlipScreen();
}

//
// FUNCTION:CleanupAndExit
//
// DESCRIPTION:Shut everything down and display a box if an error occured
//
// NOTES:
//
BOOL CleanupAndExit( char *err )
{

#ifdef DEBUG
    wsprintf(DebugBuf, "CleanupAndExit: %s\n", err );
    OutputDebugString( DebugBuf );
#endif

    // make the cursor visible
    SetCursor(LoadCursor( NULL, IDC_ARROW ));
    bMouseVisible = TRUE;

// release monitors
CMyMonitor::Uninit();

    // clean up DirectInput objects
    CleanupInput();

    // warn user
    if( err )
    {
        MessageBox( hWndMain, err, "ERROR", MB_OK );
    }
    return FALSE;
}

BOOL CleanupAndExit( char* err, HRESULT ddrval )
{
char* szText;
BOOL  rc;

if ( !err )
{
szText = new char[256];
wsprintf( szText, "Err: ddrval = %x", ddrval );
}
else
{
szText = new char[strlen(err) + 256];
wsprintf( szText, "Err: %s (ddrval = %x)", err, ddrval );
}
rc = CleanupAndExit( szText );

delete [] szText;
return rc;
}

#ifdef USE_DSOUND

//
// FUNCTION:PlayPanned
//
// DESCRIPTION:Play a sound with left-right relative volume adjusted depending on location
//within the virtual desktop.
//
// NOTES:
//
void PlayPanned(HSNDOBJ hSO, double posx )
{
static int nMiddle = (rcVirtualDesktop.left + rcVirtualDesktop.right) / 2;
static int nWidth = rcVirtualDesktop.right - rcVirtualDesktop.left;

    if(!bWantSound)
return;

IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(hSO);

    if (pDSB)
    {
LONG pos = (LONG)((posx - nMiddle) / (double) nWidth * 20000.0);
pDSB->SetPan(pos);

        pDSB->Play( 0, 0, 0);
    }
}

//
// FUNCTION:InitializeSound
//
// DESCRIPTION:Initialize DirectSound objects
//
// NOTES:
//
void InitializeSound( void )
{
if(!bWantSound)
return;

    bSoundEnabled = FALSE;

    if (SUCCEEDED(DirectSoundCreate(NULL, &lpDS, NULL)))
    {
        if (SUCCEEDED(lpDS->SetCooperativeLevel(hWndMain, DSSCL_NORMAL)))

{
            hsoBeginLevel     = SndObjCreate(lpDS, "BeginLevel",      1);
            hsoEngineIdle     = SndObjCreate(lpDS, "EngineIdle",      1);
            hsoEngineRev      = SndObjCreate(lpDS, "EngineRev",       1);
            hsoSkidToStop     = SndObjCreate(lpDS, "SkidToStop",      1);
            hsoShieldBuzz     = SndObjCreate(lpDS, "ShieldBuzz",      1);
            hsoShipExplode    = SndObjCreate(lpDS, "ShipExplode",     1);
            hsoFireBullet     = SndObjCreate(lpDS, "Gunfire",        25);
            hsoShipBounce     = SndObjCreate(lpDS, "ShipBounce",      4);
            hsoDonutExplode   = SndObjCreate(lpDS, "DonutExplode",   10);
            hsoPyramidExplode = SndObjCreate(lpDS, "PyramidExplode", 12);
            hsoCubeExplode    = SndObjCreate(lpDS, "CubeExplode",    15);
            hsoSphereExplode  = SndObjCreate(lpDS, "SphereExplode",  10);
            bSoundEnabled = TRUE;

//#ifdef USE_DSOUND   this should be dead code Josephc
            if( bPlayIdle )
                SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);

            if( bPlayBuzz )
                SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);

            if( bPlayRev )
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);

//#endif
        }
        else
        {
            lpDS->Release();
            lpDS = NULL;
        }
    }
}

//
// FUNCTION:DestroySound
//
// DESCRIPTION:Release DirectSound objects
//
// NOTES:
//
void DestroySound( void )
{
if(!bWantSound)
return;

    bSoundEnabled = FALSE;
    if (lpDS)
    {
        SndObjDestroy(hsoBeginLevel);
        hsoBeginLevel = NULL;
        SndObjDestroy(hsoEngineIdle);
        hsoEngineIdle = NULL;
        SndObjDestroy(hsoEngineRev);
        hsoEngineRev = NULL;
        SndObjDestroy(hsoSkidToStop);
        hsoSkidToStop = NULL;
        SndObjDestroy(hsoShieldBuzz);
        hsoShieldBuzz = NULL;
        SndObjDestroy(hsoShipExplode);
        hsoShipExplode = NULL;
        SndObjDestroy(hsoFireBullet);
        hsoFireBullet = NULL;
        SndObjDestroy(hsoShipBounce);
        hsoShipBounce = NULL;
        SndObjDestroy(hsoDonutExplode);
        hsoDonutExplode = NULL;
        SndObjDestroy(hsoPyramidExplode);
        hsoPyramidExplode = NULL;
        SndObjDestroy(hsoCubeExplode);
        hsoCubeExplode = NULL;
        SndObjDestroy(hsoSphereExplode);
        hsoSphereExplode = NULL;

        lpDS->Release();
        lpDS = NULL;
    }
}
#endif


MONITOR.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       monitor.cpp
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#define COMPILE_MULTIMON_STUBS
#include "monitor.h"
#include "util.h"

// globals defined here
CMyMonitor Monitor[9];

// globals defined elsewhere
#ifdef DEBUG
externchar    DebugBuf[256];
#endif

/*
 *
 * CMonitor
 *
 */

// static class member initialization
intCMonitor::iNumberOfMonitors = 0;

// constructor
CMonitor::CMonitor() :
lpMonitorRect( NULL ),
hMonitor( NULL ),
dwWidth(0),
dwHeight(0),
ScreenBpp(0),
lpDD(NULL),
lpFrontBuffer(NULL),
lpBackBuffer(NULL)
{
szErrMsg[0] = '\0';
}

CMonitor::~CMonitor()
{
Release();
delete lpGUID;
}

//
// FUNCTION:EnumDeviceCallback
//
// DESCRIPTION:Initializes common data of consecutive elements in the global Monitor array
//
// NOTES:- callback fct for DirectDrawEnumerateEx
//
BOOL CALLBACK EnumDeviceCallback( GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam, HMONITOR hMonitor )
{
if ( hMonitor != NULL && CMonitor::iNumberOfMonitors < 9)
{
CMonitor& curMonitor = Monitor[CMonitor::iNumberOfMonitors];

curMonitor.hMonitor = hMonitor;

if ( lpGUID == NULL )
{
curMonitor.lpGUID = NULL;
}
else
{
curMonitor.lpGUID = new GUID;
memcpy( curMonitor.lpGUID, lpGUID, sizeof(GUID) );
}

curMonitor.MonitorInfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo( hMonitor, (LPMONITORINFOEX)(&curMonitor.MonitorInfo) );

curMonitor.lpMonitorRect = &curMonitor.MonitorInfo.rcMonitor;

curMonitor.dwWidth = curMonitor.lpMonitorRect->right - curMonitor.lpMonitorRect->left;
curMonitor.dwHeight = curMonitor.lpMonitorRect->bottom - curMonitor.lpMonitorRect->top;

++CMonitor::iNumberOfMonitors;

#ifdef DEBUG
wsprintf( DebugBuf, "monitor %d: left=%d top=%d right=%d bottom=%d\n",
CMonitor::iNumberOfMonitors,
curMonitor.lpMonitorRect->left,
curMonitor.lpMonitorRect->top,
curMonitor.lpMonitorRect->right,
curMonitor.lpMonitorRect->bottom );
OutputDebugString( DebugBuf );
#endif
}

return TRUE;
}

//
// CLASS:CMonitor
//
// FUNCTION:Initialize
//
// DESCRIPTION:Initializes the common data of global Monitor array
//
// NOTES:- all the work is done in EnumMonitorCallback
//- static function
//
HRESULT CMonitor::Initialize()
{
HMODULE hModule;
LPDIRECTDRAWENUMERATEEX lpDDEnum;

hModule = GetModuleHandle( "ddraw.dll" );
lpDDEnum = (LPDIRECTDRAWENUMERATEEX) GetProcAddress( hModule, "DirectDrawEnumerateExA" );

if ( lpDDEnum == NULL )
{
return DDERR_GENERIC;
}
else
{
lpDDEnum( EnumDeviceCallback, (LPVOID) NULL, DDENUM_ATTACHEDSECONDARYDEVICES );
}
return DD_OK;
}

//
// CLASS:CMonitor
//
// FUNCTION:DDInit
//
// DESCRIPTION:Basic DirectDraw initialization for this monitor
//
// NOTES:- creates DirectDraw object and a primary surface with one back buffer
//- uses new SetCooperativeLevel flags for multimon fullscreen exclusive
//
HRESULT CMonitor::DDInit( HWND hWnd, BOOL bEnableMultiMon )
{
DDCAPS          ddcaps;
    HRESULT         ddrval;
    DDSCAPS         ddscaps;
DDSURFACEDESC   ddsd;

DirectDrawCreate( lpGUID, &lpDD, NULL);
if( lpDD == NULL)
    {
wsprintf( szErrMsg, "DirectDrawCreate failed");
    return DDERR_GENERIC;
    }

// SetCooperativeLevel
if ( bEnableMultiMon )
{
if ( this == &Monitor[0] )
{
// primary monitor, use focus window as device window
ddrval = lpDD->SetCooperativeLevel( hWnd, DDSCL_SETFOCUSWINDOW );
if ( ddrval != DD_OK )
{
wsprintf( szErrMsg, "SetCooperativeLevel failed");
return ddrval;
}

ddrval = lpDD->SetCooperativeLevel( hWnd,
DDSCL_SETDEVICEWINDOW | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
}
else
{
ddrval = lpDD->SetCooperativeLevel( hWnd,
            DDSCL_SETFOCUSWINDOW |
DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN |
DDSCL_CREATEDEVICEWINDOW);
}
}
else
{
ddrval = lpDD->SetCooperativeLevel( hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
}
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "SetCooperativeLevel failed");
    return ddrval;
}

ddcaps.dwSize = sizeof( ddcaps );

// Create surfaces
memset( &ddsd, 0, sizeof( ddsd ) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;

ddrval = lpDD->CreateSurface( &ddsd, &lpFrontBuffer, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface FrontBuffer failed");
    return ddrval;
}

// get a pointer to the back buffer
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpFrontBuffer->GetAttachedSurface( &ddscaps, &lpBackBuffer );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "GetAttachedSurface failed");
    return ddrval;
}

return DD_OK;
}

//
// CLASS:CMonitor
//
// FUNCTION:RestoreSurfaces
//
// DESCRIPTION:Restores the primary surface
//
// NOTES:
//
HRESULT CMonitor::RestoreSurfaces()
{
return lpFrontBuffer->Restore();
}

//
// CLASS:CMonitor
//
// FUNCTION:Blank
//
// DESCRIPTION:Fills the back buffer with the specified color
//
// NOTES:
//
HRESULT CMonitor::Blank( DWORD dwFillColor )
{
HRESULT ddrval;
DDBLTFX ddbltfx;

ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = dwFillColor;

while( 1 )
{
ddrval = lpBackBuffer->Blt( NULL, NULL, NULL, DDBLT_COLORFILL, &ddbltfx );

if( ddrval == DD_OK )
{
break;
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
return ddrval;
}
}
return DD_OK;
}

//
// CLASS:CMonitor
//
// FUNCTION:Flip
//
// DESCRIPTION:Flips the back buffer to the front buffer
//
// NOTES:
//
HRESULT CMonitor::Flip()
{
HRESULT ddrval;

while( 1 )
{
    ddrval = lpFrontBuffer->Flip( NULL, 0 );
    if( ddrval == DD_OK )
    {
        break;
    }
    if( ddrval == DDERR_SURFACELOST )
    {
        // virtual fct call
ddrval = RestoreSurfaces();
if ( ddrval != DD_OK )
        {
            return ddrval;
        }
    }
    if( ddrval != DDERR_WASSTILLDRAWING )
    {
        return ddrval;
    }
}
return DD_OK;
}

//
// CLASS:CMonitor
//
// FUNCTION:Uninit
//
// DESCRIPTION:Uninitializes all monitors in the global Monitor array
//
// NOTES:- calls CMonitor::Release
//- static function
//
void CMonitor::Uninit()
{
for (int i = 0; i < iNumberOfMonitors; i++)
{
// virtual fct call
Monitor[i].Release();
}
}

//
// CLASS:CMonitor
//
// FUNCTION:Release
//
// DESCRIPTION:Releases the primary surface and DirectDraw object
//
// NOTES:
//
void CMonitor::Release()
{
if( lpFrontBuffer != NULL )
{
    lpFrontBuffer->Release();
lpFrontBuffer = NULL;
}

if( lpDD != NULL )
{
    lpDD->Release();
lpDD = NULL;
}
}

/*
 *
 * CMyMonitor
 *
 */

// static class member initialization
DWORD CMyMonitor::dwFillColor = 0;

// constructor
CMyMonitor::CMyMonitor() :
iNumLines(0),
lpDonut(NULL),
lpPyramid(NULL),
lpCube(NULL),
lpSphere(NULL),
lpShip(NULL),
lpNum(NULL),
lpArtPalette(NULL),
lpSplashPalette(NULL),
lpDDClipper(NULL)
{}

//
// CLASS:CMyMonitor
//
// FUNCTION:Initialize
//
// DESCRIPTION:Initializes the global Monitor array
//
// NOTES:- calls CMonitor::Initialize to set up common data for all monitors
//- then calculates where monitors touch for border drawing
//
HRESULT CMyMonitor::Initialize()
{
HRESULT ddrval;
ddrval = CMonitor::Initialize();
if ( ddrval != DD_OK )
{
return ddrval;
}

// figure out where monitors touch
for(int iMonitor=0; iMonitor< iNumberOfMonitors; iMonitor++)
{
CMyMonitor* pMyMon = &Monitor[iMonitor];

for (int i = 0; i < iNumberOfMonitors; i++)
{
BOOL hit = FALSE;

if (i == iMonitor)
continue;

if (pMyMon->lpMonitorRect->top == Monitor[i].lpMonitorRect->bottom)
{
                if ( pMyMon->lpMonitorRect->left >= Monitor[i].lpMonitorRect->right ||
                     pMyMon->lpMonitorRect->right <= Monitor[i].lpMonitorRect->left )
                {
                        continue;
                }

pMyMon->Line[pMyMon->iNumLines].top = pMyMon->lpMonitorRect->top;
pMyMon->Line[pMyMon->iNumLines].bottom = pMyMon->Line[pMyMon->iNumLines].top + 1;

if ( pMyMon->lpMonitorRect->left > Monitor[i].lpMonitorRect->left )
{
pMyMon->Line[pMyMon->iNumLines].left = pMyMon->lpMonitorRect->left;
}
else
{
pMyMon->Line[pMyMon->iNumLines].left = Monitor[i].lpMonitorRect->left;
}
if ( pMyMon->lpMonitorRect->right <= Monitor[i].lpMonitorRect->right )
{
pMyMon->Line[pMyMon->iNumLines].right = pMyMon->lpMonitorRect->right - 1;
}
else
{
pMyMon->Line[pMyMon->iNumLines].right = Monitor[i].lpMonitorRect->right;
}
hit = TRUE;
}

if (pMyMon->lpMonitorRect->bottom == Monitor[i].lpMonitorRect->top)
{
                if ( pMyMon->lpMonitorRect->left >= Monitor[i].lpMonitorRect->right ||
                     pMyMon->lpMonitorRect->right <= Monitor[i].lpMonitorRect->left )
                {
                        continue;
                }

pMyMon->Line[pMyMon->iNumLines].bottom = pMyMon->lpMonitorRect->bottom - 1;
pMyMon->Line[pMyMon->iNumLines].top = pMyMon->Line[pMyMon->iNumLines].bottom - 1;

if ( pMyMon->lpMonitorRect->left > Monitor[i].lpMonitorRect->left )
{
pMyMon->Line[pMyMon->iNumLines].left = pMyMon->lpMonitorRect->left;
}
else
{
pMyMon->Line[pMyMon->iNumLines].left = Monitor[i].lpMonitorRect->left;
}
if ( pMyMon->lpMonitorRect->right <= Monitor[i].lpMonitorRect->right )
{
pMyMon->Line[pMyMon->iNumLines].right = pMyMon->lpMonitorRect->right - 1;
}
else
{
pMyMon->Line[pMyMon->iNumLines].right = Monitor[i].lpMonitorRect->right;
}
hit = TRUE;
}

if (pMyMon->lpMonitorRect->left == Monitor[i].lpMonitorRect->right)
{
                if ( pMyMon->lpMonitorRect->top >= Monitor[i].lpMonitorRect->bottom ||
                     pMyMon->lpMonitorRect->bottom <= Monitor[i].lpMonitorRect->top )
                {
                        continue;
                }

pMyMon->Line[pMyMon->iNumLines].left = pMyMon->lpMonitorRect->left;
pMyMon->Line[pMyMon->iNumLines].right = pMyMon->Line[pMyMon->iNumLines].left + 1;

if ( pMyMon->lpMonitorRect->bottom <= Monitor[i].lpMonitorRect->bottom )
{
pMyMon->Line[pMyMon->iNumLines].bottom = pMyMon->lpMonitorRect->bottom - 1;
}
else
{
pMyMon->Line[pMyMon->iNumLines].bottom = Monitor[i].lpMonitorRect->bottom;
}
if ( pMyMon->lpMonitorRect->top > Monitor[i].lpMonitorRect->top )
{
pMyMon->Line[pMyMon->iNumLines].top = pMyMon->lpMonitorRect->top;
}
else
{
pMyMon->Line[pMyMon->iNumLines].top = Monitor[i].lpMonitorRect->top;
}
hit = TRUE;
}

if (pMyMon->lpMonitorRect->right == Monitor[i].lpMonitorRect->left)
{
                if ( pMyMon->lpMonitorRect->top >= Monitor[i].lpMonitorRect->bottom ||
                     pMyMon->lpMonitorRect->bottom <= Monitor[i].lpMonitorRect->top )
                {
                        continue;
                }

pMyMon->Line[pMyMon->iNumLines].right = pMyMon->lpMonitorRect->right - 1;
pMyMon->Line[pMyMon->iNumLines].left = pMyMon->Line[pMyMon->iNumLines].right - 1;

if ( pMyMon->lpMonitorRect->bottom <= Monitor[i].lpMonitorRect->bottom )
{
pMyMon->Line[pMyMon->iNumLines].bottom = pMyMon->lpMonitorRect->bottom - 1;
}
else
{
pMyMon->Line[pMyMon->iNumLines].bottom = Monitor[i].lpMonitorRect->bottom;
}
if ( pMyMon->lpMonitorRect->top > Monitor[i].lpMonitorRect->top )
{
pMyMon->Line[pMyMon->iNumLines].top = pMyMon->lpMonitorRect->top;
}
else
{
pMyMon->Line[pMyMon->iNumLines].top = Monitor[i].lpMonitorRect->top;
}
hit = TRUE;
}
if ( hit )
{
pMyMon->Line[pMyMon->iNumLines].left -= pMyMon->lpMonitorRect->left;
pMyMon->Line[pMyMon->iNumLines].top -= pMyMon->lpMonitorRect->top;
pMyMon->Line[pMyMon->iNumLines].right -= pMyMon->lpMonitorRect->left;
pMyMon->Line[pMyMon->iNumLines].bottom -= pMyMon->lpMonitorRect->top;
++pMyMon->iNumLines;
}
}
}
return DD_OK;
}

//
// CLASS:CMyMonitor
//
// FUNCTION:Uninit
//
// DESCRIPTION:Uninitializes the global Monitor array
//
// NOTES:- calls CMonitor::Uninit
//
void CMyMonitor::Uninit()
{
CMonitor::Uninit();
}

//
// CLASS:CMyMonitor
//
// FUNCTION:DDInit
//
// DESCRIPTION:Initializes DirectDraw objects
//
// NOTES:- calls CMonitor::DDInit for common init
//- creates surfaces for game graphics
//- creates a clipper
//
HRESULT CMyMonitor::DDInit( HWND hwnd, BOOL bEnableMultiMon )
{
DDSURFACEDESC   ddsd;
HRESULTddrval;
    RECTrc;

// common init
ddrval = CMonitor::DDInit( hwnd, bEnableMultiMon );
if ( ddrval != DD_OK )
{
return ddrval;
}

// create surfaces for game graphics
memset( &ddsd, 0, sizeof( ddsd ) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

ddsd.dwWidth = 320;
ddsd.dwHeight = 384;

ddrval = lpDD->CreateSurface( &ddsd, &lpDonut, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpDonut failed");
    return ddrval;
}

ddsd.dwHeight = 128;
ddrval = lpDD->CreateSurface( &ddsd, &lpPyramid, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpPyramid failed");
    return ddrval;
}

ddsd.dwHeight = 32;
ddrval = lpDD->CreateSurface( &ddsd, &lpCube, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpCube failed");
    return ddrval;
}

ddsd.dwHeight = 32;
ddrval = lpDD->CreateSurface( &ddsd, &lpSphere, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpShere failed");
    return ddrval;
}

ddsd.dwHeight = 256;
ddrval = lpDD->CreateSurface( &ddsd, &lpShip, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpShip failed");
    return ddrval;
}

ddsd.dwHeight = 16;
ddrval = lpDD->CreateSurface( &ddsd, &lpNum, NULL );
if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "CreateSurface lpNum failed");
    return ddrval;
}

//create a clipper
ddrval = lpDD->CreateClipper((DWORD)0,&lpDDClipper,NULL);
    if(ddrval != DD_OK)
{
wsprintf( szErrMsg, "CreateClipper failed");
return ddrval;
}

rc.left = rc.top = 0;
rc.right = dwWidth;
rc.bottom = dwHeight;

    ClipList.hdr.dwSize=sizeof(RGNDATAHEADER);
    ClipList.hdr.iType=RDH_RECTANGLES;
    ClipList.hdr.nCount=1;
    ClipList.hdr.nRgnSize=0;
    memcpy(&ClipList.hdr.rcBound, &rc, sizeof(RECT));
    memcpy(&ClipList.rgndata, &rc, sizeof(RECT));

    ddrval = lpDDClipper->SetClipList((LPRGNDATA)&ClipList,0);
    if(ddrval != DD_OK)
{
wsprintf( szErrMsg, "SetClipList failed");
return ddrval;
}

    ddrval = lpBackBuffer->SetClipper(lpDDClipper);
    if( ddrval != DD_OK )
{
wsprintf( szErrMsg, "SetClipper failed");
        return ddrval;
}

return DD_OK;
}

//
// CLASS:CMyMonitor
//
// FUNCTION:RestoreSurfaces
//
// DESCRIPTION:Initializes DirectDraw objects
//
// NOTES:- calls CMonitor::RestoreSurfaces
//- loads game graphics
//
HRESULT CMyMonitor::RestoreSurfaces()
{
HRESULT ddrval;
HBITMAP hbm;

ddrval = CMonitor::RestoreSurfaces();
if ( ddrval != DD_OK )
{
return ddrval;
}

ddrval = lpDonut->Restore();
if( ddrval != DD_OK )
{
    return ddrval;
}

ddrval = lpPyramid->Restore();
if( ddrval != DD_OK )
{
    return ddrval;
}

ddrval = lpCube->Restore();
if( ddrval != DD_OK )
{
    return ddrval;
}

ddrval = lpSphere->Restore();
if( ddrval != DD_OK )
{
return ddrval;
}

ddrval = lpShip->Restore();
if( ddrval != DD_OK )
{
return ddrval;
}

ddrval = lpNum->Restore();
if( ddrval != DD_OK )
{
    return ddrval;
}

// Create and set the palette for the splash bitmap
lpSplashPalette = DDLoadPalette( lpDD, "SPLASH" );
if( lpSplashPalette == NULL )
{
wsprintf( szErrMsg, "DDLoadPalette SPLASH failed");
    return DDERR_GENERIC;
}

// Create and set the palette for the art bitmap
lpArtPalette = DDLoadPalette( lpDD, "DONUTS8" );
if( lpArtPalette == NULL )
{
wsprintf( szErrMsg, "DDLoadPalette DONUTS8 failed");
    return DDERR_GENERIC;
}

// set the palette before loading the art
lpFrontBuffer->SetPalette( lpArtPalette );

hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
    return DDERR_GENERIC;
}

ddrval = DDCopyBitmap( lpDonut, hbm, 0, 0, 320, 384 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpDonut failed");
    return DDERR_GENERIC;
}

// NOTE: Why are we calling LoadImage again?  StretchBlt (which is
// called in DDCopyBitmap) does not work properly when performing
// an 8-bpp to 24- or 32-bpp blt multiple times from the same
// bitmap.  The workaround is to call LoadImage before each
// StretchBlt because the first StretchBlt after a LoadImage will
// work.
if(ScreenBpp >= 24)
{
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
return DDERR_GENERIC;
}
}

ddrval = DDCopyBitmap( lpPyramid, hbm, 0, 384, 320, 128 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpPyramid failed");
    return DDERR_GENERIC;
}

if(ScreenBpp >= 24)
{
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
return DDERR_GENERIC;
}
}

ddrval = DDCopyBitmap( lpSphere, hbm, 0, 512, 320, 32 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpSphere failed");
    return DDERR_GENERIC;
}

if(ScreenBpp >= 24)
{
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
return DDERR_GENERIC;
}
}

ddrval = DDCopyBitmap( lpCube, hbm, 0, 544, 320, 32 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpCube failed");
    return DDERR_GENERIC;
}

if(ScreenBpp >= 24)
{
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
return DDERR_GENERIC;
}
}

ddrval = DDCopyBitmap( lpShip, hbm, 0, 576, 320, 256 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpShip failed");
    return DDERR_GENERIC;
}

if(ScreenBpp >= 24)
{
DeleteObject( hbm );
hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

if( hbm == NULL )
{
wsprintf( szErrMsg, "LoadImage DONUTS8 failed");
return DDERR_GENERIC;
}

}

ddrval = DDCopyBitmap( lpNum, hbm, 0, 832, 320, 16 );
if( ddrval != DD_OK )
{
    DeleteObject( hbm );
wsprintf( szErrMsg, "DDCopyBitmap lpNum failed");
    return DDERR_GENERIC;
}

DeleteObject( hbm );

// set colorfill colors and color keys according to bitmap contents
dwFillColor = DDColorMatch( lpDonut, CLR_INVALID );

DDSetColorKey( lpDonut, CLR_INVALID );
DDSetColorKey( lpPyramid, CLR_INVALID );
DDSetColorKey( lpCube, CLR_INVALID );
DDSetColorKey( lpSphere, CLR_INVALID );
DDSetColorKey( lpShip, CLR_INVALID );
DDSetColorKey( lpNum, CLR_INVALID );

return DD_OK;
}

//
// CLASS:CMyMonitor
//
// FUNCTION:Release
//
// DESCRIPTION:Releases all DirectDraw objects
//
// NOTES:- releases off-screen surfaces
//- calls CMonitor::Release
//
void CMyMonitor::Release()
{
    if( lpDonut != NULL )
{
    lpDonut->Release();
lpDonut = NULL;
}

if( lpPyramid != NULL )
{
    lpPyramid->Release();
lpPyramid = NULL;
}

if( lpCube != NULL )
{
    lpCube->Release();
lpCube = NULL;
}

if( lpSphere != NULL )
{
    lpSphere->Release();
lpSphere = NULL;
}

if( lpShip != NULL )
{
    lpShip->Release();
lpShip = NULL;
}

if( lpNum != NULL )
{
    lpNum->Release();
lpNum = NULL;
}

if( lpArtPalette != NULL )
{
lpArtPalette->Release();
lpArtPalette = NULL;
}

if( lpSplashPalette != NULL )
{
    lpSplashPalette->Release();
lpSplashPalette = NULL;
}

if( lpDDClipper != NULL )
{
lpDDClipper->Release();
lpDDClipper = NULL;
}

CMonitor::Release();
}

//
// CLASS:CMyMonitor
//
// FUNCTION:DrawBorder
//
// DESCRIPTION:Draws a white border around the screen except where another monitor touches
//
// NOTES:
//
HRESULT CMyMonitor::DrawBorder()
{
HRESULTddrval;
RECTrect;
DDBLTFXddbltfx;

LPDIRECTDRAWSURFACE lpsurf = lpBackBuffer;

ddbltfx.dwSize = sizeof( ddbltfx );
ddbltfx.dwFillColor = RGB(255,255,255);

for (int i = 0; i < 4; i++)
{
switch (i)
{
case 0:
rect.left = 0;
rect.top = 0;
rect.right = dwWidth - 1;
rect.bottom = 1;
break;
case 1:
rect.left = dwWidth - 2;
rect.top = 0;
rect.right = dwWidth - 1;
rect.bottom = dwHeight - 1;
break;
case 2:
rect.left = 0;
rect.top = dwHeight - 2;
rect.right = dwWidth - 1;
rect.bottom = dwHeight - 1;
break;
case 3:
rect.left = 0;
rect.top = 0;
rect.right = 1;
rect.bottom = dwHeight - 1;
break;
}

while (1)
{
ddrval = lpsurf->Blt( &rect, NULL, NULL, DDBLT_COLORFILL, &ddbltfx );

if( ddrval == DD_OK )
{
break;
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
return ddrval;
}
}
}

ddbltfx.dwFillColor = dwFillColor;
for (i = 0; i < iNumLines; i++)
    {
while (1)
{
ddrval = lpsurf->Blt( &Line[i], NULL, NULL, DDBLT_COLORFILL, &ddbltfx );

if( ddrval == DD_OK )
{
break;
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
#ifdef DEBUG
wsprintf( DebugBuf, "Undraw line: left = %d top = %d right = %d bottom = %d\n",
Line[i].left, Line[i].top, Line[i].right, Line[i].bottom );
OutputDebugString( DebugBuf );
#endif
return ddrval;
}
}
}
return ddrval;
}

//
// CLASS:CMyMonitor
//
// FUNCTION:Blank
//
// DESCRIPTION:Fills the screen with background color
//
// NOTES:- calls CMonitor::Blank
//
HRESULT CMyMonitor::Blank()
{
return CMonitor::Blank( dwFillColor );
}


MONITOR.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       monitor.h
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#ifndef MONITOR_H
#define MONITOR_H

#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>

#include "multimon.h"// make sure to include this before ddraw.h for multimon
#include <ddraw.h>

#include "resource.h"
#include "ddutil.h"

//
// The CMonitor class encapsulates basic functionality
// for programming graphical display output.
// It is multimonitor aware and DirectDraw enabled.
//
// The static Initialize routine assumes the existance
// of a global array of CMonitor objects, and employs the new
// DirectDrawEnumerateEx fct. to set up descriptive data structures.
//
// CMonitor also supports a bare-bones DirectDraw framework:
// an IDirectDraw interface and an IDirectDrawSurface primary
// surface with one back buffer. The three surface-related
// functions (DDInit, RestoreSurfaces, and Release) are virtual
// to allow a derived class to support any number of
// additional surfaces while remaining compatible with the
// generic Blank and Flip routines.
//
class CMonitor
{
public:
CMonitor();
~CMonitor();

static intiNumberOfMonitors;

// Initializes the common data of global Monitor array
static HRESULTInitialize();

// Uninitializes all monitors in the global Monitor array
static voidUninit();

// Basic DirectDraw initialization
virtual HRESULTDDInit( HWND hWnd, BOOL bEnableMultiMon );

// Restores the primary surface
virtual HRESULT RestoreSurfaces();

// Releases the primary surface and DirectDraw object
virtual voidRelease();

// Fills the back buffer with the specified color
HRESULTBlank( DWORD dwFillColor );

// Flips the back buffer to the front buffer
HRESULTFlip();

// descriptive data structures
    HMONITORhMonitor;
    GUID*lpGUID;
    MONITORINFOEXMonitorInfo;
LPRECTlpMonitorRect;
DWORDdwWidth;
DWORDdwHeight;
DWORDScreenBpp;

// use this to return detailed error messages
charszErrMsg[256];

// DDraw interface pointers
LPDIRECTDRAW            lpDD;
LPDIRECTDRAWSURFACE     lpFrontBuffer;
LPDIRECTDRAWSURFACE     lpBackBuffer;
};

typedef struct {
    RGNDATAHEADER hdr;
    RECT rgndata[4];
} CLIPLIST, *LPCLIPLIST;

//
// The CMyMonitor derives from CMonitor and builds
// upon that foundation to implement additional graphical
// display functionality specific to this application (Donuts2).
//
// The Initialize routine now calculates the monitor boundary
// locations where there is monitor adjacency as defined in
// the Display/Settings tab of the Control Panel, and DrawBorder
// displays a white line around the monitor edges except at
// these points of adjacency.
//
// DDInit, RestoreSurfaces, and Release have been extended to
// support all the off-screen sprite bitmaps.
//
class CMyMonitor : public CMonitor
{
public:
CMyMonitor();

static DWORD    dwFillColor;

static HRESULT  Initialize();
static voidUninit();

virtual HRESULTDDInit( HWND hwnd, BOOL bEnableMultiMon );
virtual HRESULT RestoreSurfaces();
virtual voidRelease();

HRESULTBlank();
HRESULTDrawBorder();

// data
intiNumLines;
RECTLine[9];

// more DDraw objects
CLIPLISTClipList;
LPDIRECTDRAWSURFACE     lpDonut;
LPDIRECTDRAWSURFACE     lpPyramid;
LPDIRECTDRAWSURFACE     lpCube;
LPDIRECTDRAWSURFACE     lpSphere;
LPDIRECTDRAWSURFACE     lpShip;
LPDIRECTDRAWSURFACE     lpNum;
LPDIRECTDRAWPALETTE     lpArtPalette;
LPDIRECTDRAWPALETTE     lpSplashPalette;
LPDIRECTDRAWCLIPPER     lpDDClipper;
LPDIRECTDRAWSURFACElpBackground;
};

#endif


DONUTS.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       donuts.cpp
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#include "donuts.h"

// globals defined here
CShipDL; // display list

// globals defined elsewhere
externCMyMonitorMonitor[9];
externBOOLbWantSound;

#ifdef DEBUG
externchar        DebugBuf[256];
#endif

#ifdef USE_DSOUND
extern HSNDOBJhsoDonutExplode;
extern HSNDOBJhsoPyramidExplode;
extern HSNDOBJhsoCubeExplode;
extern HSNDOBJhsoSphereExplode;
extern HSNDOBJhsoShipExplode;
#endif

// constructor
CObject::CObject( int nMaxFrame, int nSize )
{
// constants
km_iMaxFrame = nMaxFrame;
km_iSize = nSize;

// defaults
next = prev = this;
posx = posy = velx = vely = frame = delay = 0.0;
iMonitor = 0;
}

//
// CLASS:CObject
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes object data, links it to the display list
//
// NOTES:
//
void CObject::Init( double x, double y )
{
    iMonitor = DL.iMonitor;
posx = x;
    posy = y;
frame = randDouble( 0, 30 );

// link the object to the display list
    next = DL.next;
    prev = &DL;
    DL.next->prev = this;
    DL.next = this;
}

//
// CLASS:CObject
//
// FUNCTION:DeleteFromList
//
// DESCRIPTION:Removes this object from the display list
//
// NOTES:
//
void CObject::DeleteFromList()
{
    next->prev = prev;
    prev->next = next;
    delete this;
}

//
// CLASS:CObject
//
// FUNCTION:UpdateRect
//
// DESCRIPTION:Computes new dst and src rects
//
// NOTES:- src rect calculation performed by pure virtual fct UpdateSrcRect
//
void CObject::UpdateRect()
{
dst.left = (DWORD)posx;
    dst.top = (DWORD)posy;
    dst.right = dst.left + km_iSize;
    dst.bottom = dst.top + km_iSize;

// pure virtual fct call
UpdateSrcRect();
}

//
// CLASS:CObject
//
// FUNCTION:Blt
//
// DESCRIPTION:Blts the object's bitmap to the back buffer
//
// NOTES:- surface bitmap determined by pure virtual fct GetSurface()
//
void CObject::Blt()
{
RECTrc;
DWORDdwFlag;
DDBLTFX ddbltfx;
HRESULTddrval;

rc.top = dst.top - Monitor[iMonitor].lpMonitorRect->top;
rc.left = dst.left - Monitor[iMonitor].lpMonitorRect->left;
rc.bottom = rc.top + (src.bottom - src.top);
rc.right = rc.left + (src.right - src.left);

ddbltfx.dwSize = sizeof( ddbltfx );
dwFlag = DDBLT_KEYSRC;

while (1)
{
ddrval = Monitor[iMonitor].lpBackBuffer->Blt( &rc, GetSurface(), &src, dwFlag, &ddbltfx );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            ddrval = Monitor[iMonitor].RestoreSurfaces();
if ( ddrval != DD_OK )
{
                CleanupAndExit("CObject::Blt (CMyMonitor::RestoreSurfaces failed", ddrval );
return;
}
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
}
}

//
// CLASS:CObject
//
// FUNCTION:UpdateFrame
//
// DESCRIPTION:Updates position and frame
//
// NOTES:- virtual fct
//
void CObject::UpdateFrame( DWORD tickDiff )
{
posx += velx * (double)tickDiff;
    posy += vely * (double)tickDiff;
    frame += delay * (double)tickDiff;
}

//
// CLASS:CObject
//
// FUNCTION:CheckPosition
//
// DESCRIPTION:Identifies location on virtual desktop (which monitor)
//
// NOTES:- returns TRUE if the object has bounced off a wall
//
BOOL CObject::CheckPosition( DWORD tickDiff )
{
intiNewMonitor;
doublemaxx, minx, maxy, miny, maxframe;
BOOLevent;

// virtual fct call
UpdateFrame( tickDiff );

maxx = (double) Monitor[iMonitor].lpMonitorRect->right - km_iSize;
minx = (double) Monitor[iMonitor].lpMonitorRect->left;
maxy = (double) Monitor[iMonitor].lpMonitorRect->bottom - km_iSize;
miny = (double) Monitor[iMonitor].lpMonitorRect->top;

    maxframe = (double)km_iMaxFrame;

    if( velx > 0 && posx > maxx )
{
iNewMonitor = GetMonitorX();
if ( iNewMonitor == iMonitor )
{
// bounce off the walls
posx = maxx;
velx = -velx;
event = TRUE;
}
else
{
iMonitor = iNewMonitor;
}
}
else if ( velx < 0 && posx < minx )
{
iNewMonitor = GetMonitorX();
if ( iNewMonitor == iMonitor )
{
posx = minx;
velx = -velx;
event = TRUE;
}
else
{
iMonitor = iNewMonitor;
}
}
else if( vely > 0 && posy > maxy )
{
iNewMonitor = GetMonitorY();
if ( iNewMonitor == iMonitor )
{
posy = maxy;
vely = -vely;
event = TRUE;
}
else
{
iMonitor = iNewMonitor;
}
}
else if ( vely < 0 && posy < miny )
{
iNewMonitor = GetMonitorY();
if ( iNewMonitor == iMonitor )
{
posy = miny;
vely = -vely;
event = TRUE;}
else
{
iMonitor = iNewMonitor;
}
}

return event;
}

//
// CLASS:CObject
//
// FUNCTION:CheckFrame
//
// DESCRIPTION:Checks for frame overflow
//
// NOTES:- returns TRUE if the object is to be removed from the display list
//- in this implementation, FALSE is always returned
//
BOOL CObject::CheckFrame()
{
if( frame >= km_iMaxFrame )
{
frame -= km_iMaxFrame;
}
return FALSE;
}

//
// CLASS:CObject
//
// FUNCTION:GetMonitorX
//
// DESCRIPTION:Checks for object proximity to monitor x boundary
//
// NOTES:- returns the monitor on which the object is now located
//
int CObject::GetMonitorX()
{
POINT pt;

for (int i = 0; i < CMonitor::iNumberOfMonitors; i++)
{
// don't check the monitor we're on
if ( i == iMonitor )
continue;

pt.y = (long) posy;
pt.x = (long) posx - km_iSize - 1;

if ( PtInRect( Monitor[i].lpMonitorRect, pt ))
{
return i;
}
pt.x = (long) posx + km_iSize + 1;
if ( PtInRect(Monitor[i].lpMonitorRect, pt ))
{
return i;
}
}
// if we get here, not close to any monitors
return iMonitor;
}

//
// CLASS:CObject
//
// FUNCTION:GetMonitorY
//
// DESCRIPTION:Checks for object proximity to monitor y boundary
//
// NOTES:- returns the monitor on which the object is now located
//
int CObject::GetMonitorY()
{
int i;
POINT pt;

for (i = 0; i < CMonitor::iNumberOfMonitors; i++)
{
// don't check the monitor we're on
if ( i == iMonitor )
continue;

pt.x = (long) posx;
pt.y = (long) posy - km_iSize - 1;

if ( PtInRect(Monitor[i].lpMonitorRect, pt ))
{
return i;
}
pt.y = (long) posy + km_iSize + 1;
if ( PtInRect(Monitor[i].lpMonitorRect, pt ))
{
return i;
}
}
// if we get here, not close to any monitors
return iMonitor;
}

/*
 *
 * CDonut
 *
 */

// constructor
CDonut::CDonut() :
CObject( 30, 64 )
{
}

//
// CLASS:CDonut
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes donut data
//
// NOTES:- calls CObject::Init
//
void CDonut::Init( double x, double y )
{
CObject::Init( x, y );
velx = randDouble( -50.0/1000.0, 50.0/1000.0 );
    vely = randDouble( -50.0/1000.0, 50.0/1000.0 );
    delay = 30.0*randDouble( 0.1, 0.4 )/1000.0;
}

//
// CLASS:CDonut
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the donut src rect
//
// NOTES:
//
void CDonut::UpdateSrcRect()
{
    DWORD dwframe = (DWORD) frame;
src.left = km_iSize * (dwframe % 5);
    src.top = km_iSize * (dwframe /5);
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CDonut
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CDonut::GetSurface()
{
return Monitor[iMonitor].lpDonut;
}

//
// CLASS:CDonut
//
// FUNCTION:Hit
//
// DESCRIPTION:Adds objects to the display list and returns the score bonus
//
// NOTES:
//
int CDonut::Hit( double l, double t )
{
#ifdef USE_DSOUND
if(bWantSound)
{
    PlayPanned(hsoDonutExplode, posx);
}
#endif
CObject* pObj;

pObj = new CPyramid;
pObj->Init(dst.left, dst.top);

pObj = new CPyramid;
pObj->Init(dst.left, dst.top);

pObj = new CPyramid;
pObj->Init(dst.left, dst.top);

return 10;
}

/*
 *
 * CPyramid
 *
 */

// constructor
CPyramid::CPyramid() :
CObject( 40, 32 )
{
}

//
// CLASS:CPyramid
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes pyramid data
//
// NOTES:- calls CObject::Init
//
void CPyramid::Init( double x, double y )
{
CObject::Init( x, y );
velx = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
    vely = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
    delay = 40.0*randDouble( 0.3, 1.0 )/1000.0;
}

//
// CLASS:CPyramid
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the pyramid src rect
//
// NOTES:
//
void CPyramid::UpdateSrcRect()
{
    DWORD dwFrame = (DWORD) frame;
src.left = km_iSize * (dwFrame % 10);
    src.top = km_iSize * (dwFrame /10);
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CPyramid
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CPyramid::GetSurface()
{
return Monitor[iMonitor].lpPyramid;
}

//
// CLASS:CPyramid
//
// FUNCTION:Hit
//
// DESCRIPTION:Adds objects to the display list and returns the score bonus
//
// NOTES:
//
int CPyramid::Hit( double l, double t)
{
#ifdef USE_DSOUND
if(bWantSound)
{
        PlayPanned(hsoPyramidExplode, posx);
}
#endif
CObject* pObj = new CSphere;
pObj->Init( dst.left, dst.top );

pObj = new CCube;
pObj->Init( dst.left, dst.top );

pObj = new CCube;
pObj->Init( dst.left, dst.top );

return 20;
}

/*
 *
 * CCube
 *
 */

// constructor
CCube::CCube() :
CObject( 40, 16 )
{
}

//
// CLASS:CCube
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes cube data
//
// NOTES:- calls CObject::Init
//
void CCube::Init( double x, double y )
{
CObject::Init( x, y );
    velx = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
    vely = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
    delay = 40.0*randDouble( 0.8, 2.0 )/1000.0;
}

//
// CLASS:CCube
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the cube src rect
//
// NOTES:
//
void CCube::UpdateSrcRect()
{
DWORD dwFrame = (DWORD) frame;
    src.left = km_iSize * (dwFrame % 20);
    src.top = km_iSize * (dwFrame /20);
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CCube
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CCube::GetSurface()
{
return Monitor[iMonitor].lpCube;
}

//
// CLASS:CCube
//
// FUNCTION:Hit
//
// DESCRIPTION:Adds objects to the display list and returns the score bonus
//
// NOTES:
//
int CCube::Hit( double l, double t )
{
#ifdef USE_DSOUND
if(bWantSound)
{
        PlayPanned(hsoCubeExplode, posx);
}
#endif
CObject* pObj = new CSphere;
pObj->Init( dst.left, dst.top );

pObj = new CSphere;
pObj->Init( dst.left, dst.top );

return 40;
}

/*
 *
 * CSphere
 *
 */

// constructor
CSphere::CSphere() :
CObject( 40, 16 )
{
}

//
// CLASS:CSphere
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes sphere data
//
// NOTES:- calls CObject::Init
//
void CSphere::Init( double x, double y )
{
CObject::Init( x, y );
    velx = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
    vely = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
    delay = 40.0*randDouble( 1.5, 2.0 )/1000.0;
}

//
// CLASS:CSphere
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the sphere src rect
//
// NOTES:
//
void CSphere::UpdateSrcRect()
{
DWORD dwFrame = (DWORD) frame;
    src.left = km_iSize * (dwFrame % 20);
    src.top = km_iSize * (dwFrame /20);
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CSphere
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CSphere::GetSurface()
{
return Monitor[iMonitor].lpSphere;
}

//
// CLASS:CSphere
//
// FUNCTION:Hit
//
// DESCRIPTION:Adds objects to the display list and returns the score bonus
//
// NOTES:
//
int CSphere::Hit( double l, double t)
{
#ifdef USE_DSOUND
if(bWantSound)
{
        PlayPanned(hsoSphereExplode, posx);
}
#endif
    return 20;
}

/*
 *
 * CShip
 *
 */

// constructor
CShip::CShip() :
CObject( 40, 32 )
{
}

//
// CLASS:CShip
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes ship data
//
// NOTES:
//
void CShip::Init()
{
LPRECTlprect = Monitor[iMonitor].lpMonitorRect;

posx = lprect->left + (double)(Monitor[iMonitor].dwWidth - km_iSize) /2;    // center the ship
    posy = lprect->top  + (double)(Monitor[iMonitor].dwHeight - km_iSize) /2;
    frame = 0.0;
    velx = vely = 0.0;// not moving
}

//
// CLASS:CShip
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the ship src rect
//
// NOTES:
//
void CShip::UpdateSrcRect()
{
DWORD dwFrame = (DWORD) frame;
if( lastShield )
src.top = km_iSize * (dwFrame / 10) + 128;
else
        src.top = km_iSize * (dwFrame /10);
    src.left = km_iSize * (dwFrame % 10);
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CShip
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CShip::GetSurface()
{
return Monitor[iMonitor].lpShip;
}

//
// CLASS:CShip
//
// FUNCTION:Hit
//
// DESCRIPTION:Adds objects to the display list and returns the score bonus
//
// NOTES:
//
int CShip::Hit( double l, double t )
{
#ifdef USE_DSOUND
if(bWantSound)
{
        PlayPanned(hsoShipExplode, posx);
}
#endif

CObject* pObj;

for (int i = 0; i < 4; i++)
{
pObj = new CSphere;
pObj->Init( l, t );
}

for (i = 0; i < 10; i++)
{
pObj = new CBullet;
pObj->Init( l, t );
pObj->velx = randDouble( -0.5, 0.5 );
pObj->vely = randDouble( -0.5, 0.5 );
}

Init();

return -150;
}

//
// CLASS:CShip
//
// FUNCTION:UpdateFrame
//
// DESCRIPTION:Updates ship position
//
// NOTES:- unlike other objects, frame is not time-dependent
//
void CShip::UpdateFrame( DWORD tickDiff )
{
posx += velx * (double)tickDiff;
    posy += vely * (double)tickDiff;
// don't change the frame based on time
}

/*
 *
 * CBullet
 *
 */

// constructor
CBullet::CBullet() :
CObject( 400, 3 )
{
}

//
// CLASS:CBullet
//
// FUNCTION:Init
//
// DESCRIPTION:Initializes bullet data
//
// NOTES:
//
void CBullet::Init( double x, double y )
{
CObject::Init( x, y );
    frame = 0.0;
    delay = 1.0;
}

//
// CLASS:CBullet
//
// FUNCTION:UpdateSrcRect
//
// DESCRIPTION:Updates the bullet src rect
//
// NOTES:
//
void CBullet::UpdateSrcRect()
{
DWORD dwFrame = (DWORD)frame/20 % 4;
    src.left = BULLET_X + dwFrame*4;
    src.top = BULLET_Y;
    src.right = src.left + km_iSize;
    src.bottom = src.top + km_iSize;
}

//
// CLASS:CBullet
//
// FUNCTION:GetSurface
//
// DESCRIPTION:Returns the DDraw surface to be used for blitting
//
// NOTES:
//
LPDIRECTDRAWSURFACE CBullet::GetSurface()
{
return Monitor[iMonitor].lpNum;
}

//
// CLASS:CBullet
//
// FUNCTION:Hit
//
// DESCRIPTION:Never called, bullets can't be hit
//
// NOTES:- an implementation must be provided because Hit is declared
//  pure virtual in the base class
//
int CBullet::Hit( double l, double t )
{
return 0;
}

//
// CLASS:CBullet
//
// FUNCTION:CheckFrame
//
// DESCRIPTION:Checks for frame overflow
//
// NOTES:- returns TRUE if the bullet has expired
//
BOOL CBullet::CheckFrame()
{
return ( frame >= km_iMaxFrame);
}


UTIL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       util.cpp
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#include "util.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// global declaration
double      Dirx[40] =
{
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891007,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688,
    -1.000000,
    -0.987688,
    -0.951056,
    -0.891006,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434
};

double      Diry[40] =
{
    -1.000000,
    -0.987688,
    -0.951057,
    -0.891007,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434,
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891006,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688
};

int randInt( int low, int high )
{
    int range = high - low;
    int num = rand() % range;
    return( num + low );
}

double randDouble( double low, double high )
{
    double range = high - low;
    double num = range * (double)rand()/(double)RAND_MAX;
    return( num + low );
}

int getint(char**p, int def)
{
    int i=0;

    while (IS_SPACE(**p))
        (*p)++;

    if (!IS_NUM(**p))
        return def;

    while (IS_NUM(**p))
        i = i*10 + *(*p)++ - '0';

    while (IS_SPACE(**p))
        (*p)++;

    return i;
}


UTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       util.h
 *
 *@@BEGIN_MSINTERNAL
 *  History:
 *   Date       By        Reason
 *   ====       ==        ======
 *   15-sep-97  t-craigs  Created
 *@@END_MSINTERNAL
 *
 ***************************************************************************/

#define IS_NUM(c)     ((c) >= '0' && (c) <= '9')
#define IS_SPACE(c)   ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == 'x')

intgetint(char**p, int def);
int     randInt( int low, int high );
double  randDouble( double low, double high );


README.TXT

DPCHAT ReadMe

This is a simple chat program to demonstrate usage of the DirectPlay APIs.

Note: MSVC may include older versions of the DirectX header files and
libraries.  This sample requires DirectX 5.  In order to avoid compile errors,
make sure the path to the DirectX 5 header files and libraries are listed
BEFORE the MSVC header files and libraries through the
Tools -> Options -> Directories menu.

Note: The DPLAUNCH or BELLHOP sample that is part of the DirectX 5 SDK can be
used to exercise the lobby functionality of DPCHAT.

dpchat.mak - MSVC workspace to compile DPCHAT.

lobby.cpp - sample code for enabling a DirectPlay application to be launched
            and connected to a session by a lobby.

dialog.cpp - sample code for establishing a session connection by asking
            the user to supply information and make choices

dpchat.cpp - the core chat application

dpchat.reg - Adds entries to the registry to make DPCHAT visible to lobby
             applications.


DPCHAT.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dpchat.cpp
 *  Content:Simple chat program using DirectPlay.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>

#include "dpchat.h"
#include "resource.h"

// constants
const DWORD APPMSG_CHATSTRING= 0;// message type for chat string
const UINTWM_USER_ADDSTRING= WM_USER+257;// window message to add string to chat string list
const DWORDMAXSTRLEN= 200;// max size of a temporary string

// structures

// message structure used to send a chat string to another player
typedef struct {
DWORDdwType;// message type (APPMSG_CHATSTRING)
charszMsg[1];// message string (variable length)
} MSG_CHATSTRING, *LPMSG_CHATSTRING;

// globals
HANDLEghReceiveThread = NULL;// handle of receive thread
DWORDgidReceiveThread = 0;// id of receive thread
HANDLEghKillReceiveEvent = NULL;// event used to kill receive thread
HWNDghChatWnd = NULL;// main chat window

// prototypes
BOOL CALLBACKChatWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULTSetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
HRESULTShutdownConnection(LPDPLAYINFO lpDPInfo);
DWORD WINAPIReceiveThread(LPVOID lpThreadParameter);
HRESULTReceiveMessage(LPDPLAYINFO lpDPInfo);
voidHandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo);
voidHandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
DPID idFrom, DPID idTo);
HRESULTGetChatPlayerName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPDPNAME *lplpName);
HRESULTSendChatMessage(HWND hWnd, LPDPLAYINFO lpDPInfo);
HRESULTNewChatString(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPSTR lpszMsg, LPSTR *lplpszStr);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow )
{
DPLAYINFODPInfo;
intiResult = 0;
HRESULThr;

// Initialize COM library
hr = CoInitialize(NULL);
if ( FAILED(hr) )
goto FAILURE;

// setup the connection
hr = SetupConnection(hInstance, &DPInfo);
if FAILED(hr)
goto FAILURE;

// show the chat window
iResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHATDIALOG), NULL, (DLGPROC) ChatWndProc, (LPARAM) &DPInfo);

FAILURE:
// shut down the connection
hr = ShutdownConnection(&DPInfo);

// Uninitialize the COM library
CoUninitialize();

return (iResult);
}

BOOL CALLBACK ChatWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
DWORDdwTextLen;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // Save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;

// store global window
ghChatWnd = hWnd;
        break;

    case WM_DESTROY:
ghChatWnd = NULL;
        break;

// this is a user-defined message used to add strings to the log window
case WM_USER_ADDSTRING:
// get length of text in log window
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, WM_GETTEXTLENGTH,
   (WPARAM) 0, (LPARAM) 0);

// put selection at end
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_SETSEL,
   (WPARAM) dwTextLen, (LPARAM) dwTextLen);

// add string in lParam to log window
SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_REPLACESEL,
(WPARAM) FALSE, (LPARAM) lParam);
GlobalFreePtr((LPVOID) lParam);
break;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDC_SENDBUTTON:
SendChatMessage(hWnd, lpDPInfo);
break;

        case IDCANCEL:
EndDialog(hWnd, FALSE);
            break;
}
break;
}

    // Allow for default processing
    return FALSE;
}

HRESULT SetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
HRESULThr;

ZeroMemory(lpDPInfo, sizeof(DPLAYINFO));

// create event used by DirectPlay to signal a message has arrived
lpDPInfo->hPlayerEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (lpDPInfo->hPlayerEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create event used to signal that the receive thread should exit
ghKillReceiveEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (ghKillReceiveEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create thread to receive player messages
ghReceiveThread = CreateThread(NULL,// default security
   0,// default stack size
   ReceiveThread,// pointer to thread routine
   lpDPInfo,// argument for thread
   0,// start it right away
   &gidReceiveThread);
if (ghReceiveThread == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// try to connect using the lobby
hr = ConnectUsingLobby(lpDPInfo);
if FAILED(hr)
{
// if the error returned is DPERR_NOTLOBBIED, that means we
// were not launched by a lobby and we should ask the user for
// connection settings. If any other error is returned it means
// we were launched by a lobby but there was an error making the
// connection.

if (hr != DPERR_NOTLOBBIED)
ErrorBox("Could not connect using lobby because of error 0x%08X", hr);

// if there is no lobby connection, ask the user for settings
hr = ConnectUsingDialog(hInstance, lpDPInfo);
if FAILED(hr)
goto FAILURE;
}

return (DP_OK);

FAILURE:
ShutdownConnection(lpDPInfo);

return (hr);
}

HRESULT ShutdownConnection(LPDPLAYINFO lpDPInfo)
{
if (ghReceiveThread)
{
// wake up receive thread and wait for it to quit
SetEvent(ghKillReceiveEvent);
WaitForSingleObject(ghReceiveThread, INFINITE);

CloseHandle(ghReceiveThread);
ghReceiveThread = NULL;
}

if (ghKillReceiveEvent)
{
CloseHandle(ghKillReceiveEvent);
ghKillReceiveEvent = NULL;
}

if (lpDPInfo->lpDirectPlay3A)
{
if (lpDPInfo->dpidPlayer)
{
lpDPInfo->lpDirectPlay3A->DestroyPlayer(lpDPInfo->dpidPlayer);
lpDPInfo->dpidPlayer = 0;
}
lpDPInfo->lpDirectPlay3A->Close();
lpDPInfo->lpDirectPlay3A->Release();
lpDPInfo->lpDirectPlay3A = NULL;
}

if (lpDPInfo->hPlayerEvent)
{
CloseHandle(lpDPInfo->hPlayerEvent);
lpDPInfo->hPlayerEvent = NULL;
}

return (DP_OK);
}

DWORD WINAPI ReceiveThread(LPVOID lpThreadParameter)
{
    LPDPLAYINFOlpDPInfo = (LPDPLAYINFO) lpThreadParameter;
HANDLEeventHandles[2];

eventHandles[0] = lpDPInfo->hPlayerEvent;
eventHandles[1] = ghKillReceiveEvent;

// loop waiting for player events. If the kill event is signaled
// the thread will exit
while (WaitForMultipleObjects(2, eventHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
{
// receive any messages in the queue
ReceiveMessage(lpDPInfo);
}

ExitThread(0);

return (0);
}

HRESULT ReceiveMessage(LPDPLAYINFO lpDPInfo)
{
DPIDidFrom, idTo;
LPVOIDlpvMsgBuffer;
DWORDdwMsgBufferSize;
HRESULThr;

lpvMsgBuffer = NULL;
dwMsgBufferSize = 0;

// loop to read all messages in queue
do
{
// loop until a single message is successfully read
do
{
// read messages from any player, including system player
idFrom = 0;
idTo = 0;

hr = lpDPInfo->lpDirectPlay3A->Receive(&idFrom, &idTo, DPRECEIVE_ALL,
   lpvMsgBuffer, &dwMsgBufferSize);

// not enough room, so resize buffer
if (hr == DPERR_BUFFERTOOSMALL)
{
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);
lpvMsgBuffer = GlobalAllocPtr(GHND, dwMsgBufferSize);
if (lpvMsgBuffer == NULL)
hr = DPERR_OUTOFMEMORY;
}
} while (hr == DPERR_BUFFERTOOSMALL);

if ((SUCCEEDED(hr)) &&// successfully read a message
(dwMsgBufferSize >= sizeof(DPMSG_GENERIC)))// and it is big enough
{
// check for system message
if (idFrom == DPID_SYSMSG)
{
HandleSystemMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
dwMsgBufferSize, idFrom, idTo);
}
else
{
HandleApplicationMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
 dwMsgBufferSize, idFrom, idTo);
}
}
} while (SUCCEEDED(hr));

// free any memory we created
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);

return (DP_OK);
}

void HandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
  DPID idFrom, DPID idTo)
{
LPSTRlpszStr = NULL;
HRESULThr;

switch (lpMsg->dwType)
{
case APPMSG_CHATSTRING:
        {
            LPMSG_CHATSTRING   lp = (LPMSG_CHATSTRING) lpMsg;

// create string to display
hr = NewChatString(lpDPInfo->lpDirectPlay3A, idFrom, lp->szMsg, &lpszStr);
if FAILED(hr)
break;
        }
break;
}

// post string to chat window
if (lpszStr)
{
// make sure window is still valid
if (ghChatWnd)
PostMessage(ghChatWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
else
GlobalFreePtr(lpszStr);
}
}

void HandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo)
{
LPSTRlpszStr = NULL;

    // The body of each case is there so you can set a breakpoint and examine
    // the contents of the message received.
switch (lpMsg->dwType)
{
case DPSYS_CREATEPLAYERORGROUP:
        {
            LPDPMSG_CREATEPLAYERORGROUPlp = (LPDPMSG_CREATEPLAYERORGROUP) lpMsg;
LPSTRlpszPlayerName;
LPSTRszDisplayFormat = "\"%s\" has joined\r\n";

// get pointer to player name
if (lp->dpnName.lpszShortNameA)
lpszPlayerName = lp->dpnName.lpszShortNameA;
else
lpszPlayerName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) +
   lstrlen(lpszPlayerName) + 1);
if (lpszStr == NULL)
break;

// build string
wsprintf(lpszStr, szDisplayFormat, lpszPlayerName);
        }
break;

case DPSYS_DESTROYPLAYERORGROUP:
        {
            LPDPMSG_DESTROYPLAYERORGROUPlp = (LPDPMSG_DESTROYPLAYERORGROUP)lpMsg;
LPSTRlpszPlayerName;
LPSTRszDisplayFormat = "\"%s\" has left\r\n";

// get pointer to player name
if (lp->dpnName.lpszShortNameA)
lpszPlayerName = lp->dpnName.lpszShortNameA;
else
lpszPlayerName = "unknown";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) +
   lstrlen(lpszPlayerName) + 1);
if (lpszStr == NULL)
break;

// build string
wsprintf(lpszStr, szDisplayFormat, lpszPlayerName);
        }
break;

case DPSYS_ADDPLAYERTOGROUP:
        {
            LPDPMSG_ADDPLAYERTOGROUP lp = (LPDPMSG_ADDPLAYERTOGROUP)lpMsg;
        }
break;

case DPSYS_DELETEPLAYERFROMGROUP:
        {
            LPDPMSG_DELETEPLAYERFROMGROUP lp = (LPDPMSG_DELETEPLAYERFROMGROUP)lpMsg;
        }
break;

case DPSYS_SESSIONLOST:
        {
            LPDPMSG_SESSIONLOST lp = (LPDPMSG_SESSIONLOST)lpMsg;
        }
break;

case DPSYS_HOST:
        {
            LPDPMSG_HOSTlp = (LPDPMSG_HOST)lpMsg;
LPSTRszDisplayFormat = "You have become the host\r\n";

// allocate space for string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) + 1);
if (lpszStr == NULL)
break;

// build string
lstrcpy(lpszStr, szDisplayFormat);

// we are now the host
lpDPInfo->bIsHost = TRUE;
        }
break;

case DPSYS_SETPLAYERORGROUPDATA:
        {
            LPDPMSG_SETPLAYERORGROUPDATA lp = (LPDPMSG_SETPLAYERORGROUPDATA)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPNAME:
        {
            LPDPMSG_SETPLAYERORGROUPNAME lp = (LPDPMSG_SETPLAYERORGROUPNAME)lpMsg;
        }
break;
}

// post string to chat window
if (lpszStr)
{
// make sure window is still valid
if (ghChatWnd)
PostMessage(ghChatWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
else
GlobalFreePtr(lpszStr);
}
}

HRESULT GetChatPlayerName(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPDPNAME *lplpName)
{
LPDPNAMElpName = NULL;
DWORDdwNameSize;
HRESULThr;

// get size of player name data
hr = lpDirectPlay3A->GetPlayerName(dpidPlayer, NULL, &dwNameSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpName = (LPDPNAME) GlobalAllocPtr(GHND, dwNameSize);
if (lpName == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get player name data
hr = lpDirectPlay3A->GetPlayerName(dpidPlayer, lpName, &dwNameSize);
if FAILED(hr)
goto FAILURE;

// return pointer to name structure
*lplpName = lpName;

return (DP_OK);

FAILURE:
if (lpName)
GlobalFreePtr(lpName);

return (hr);
}

HRESULT SendChatMessage(HWND hWnd, LPDPLAYINFO lpDPInfo)
{
LPSTRlpszChatStr = NULL;
LPSTRlpszStr = NULL;
LPMSG_CHATSTRINGlpChatMessage = NULL;
DWORDdwChatMessageSize;
LONGlStrLen;
HRESULThr;

// get length of item text
lStrLen = SendDlgItemMessage(hWnd, IDC_SENDEDIT, WM_GETTEXTLENGTH,
(WPARAM) 0, (LPARAM) 0);

// make room for it
lpszChatStr = (LPSTR) GlobalAllocPtr(GHND, lStrLen + 1);
if (lpszChatStr == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get item text
lStrLen = GetDlgItemText(hWnd, IDC_SENDEDIT, lpszChatStr, lStrLen + 1);

// create string to display this text
hr = NewChatString(lpDPInfo->lpDirectPlay3A, lpDPInfo->dpidPlayer, lpszChatStr, &lpszStr);
if FAILED(hr)
goto FAILURE;

// display this string
PostMessage(hWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
lpszStr = NULL;// set to NULL so we don't delete it below

// create space for message plus string (string length included in message header)
dwChatMessageSize = sizeof(MSG_CHATSTRING) + lstrlen(lpszChatStr);
lpChatMessage = (LPMSG_CHATSTRING) GlobalAllocPtr(GHND, dwChatMessageSize);
if (lpChatMessage == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// build message
lpChatMessage->dwType = APPMSG_CHATSTRING;
lstrcpy(lpChatMessage->szMsg, lpszChatStr);

// send this string to all other players
hr = lpDPInfo->lpDirectPlay3A->Send(lpDPInfo->dpidPlayer, DPID_ALLPLAYERS,
DPSEND_GUARANTEED, lpChatMessage, dwChatMessageSize);
if FAILED(hr)
goto FAILURE;

FAILURE:
if (lpszChatStr)
GlobalFreePtr(lpszChatStr);

if (lpszStr)
GlobalFreePtr(lpszStr);

if (lpChatMessage)
GlobalFreePtr(lpChatMessage);

SetDlgItemText(hWnd, IDC_SENDEDIT, "");

return (hr);
}

HRESULT NewChatString(LPDIRECTPLAY3A lpDirectPlay3A, DPID dpidPlayer,
  LPSTR lpszMsg, LPSTR *lplpszStr)
{
LPDPNAMElpName = NULL;
LPSTRlpszStr = NULL;
LPSTRlpszPlayerName;
LPSTRszDisplayFormat = "%s>\t%s\r\n";
HRESULThr;

// get name of player
hr = GetChatPlayerName(lpDirectPlay3A, dpidPlayer, &lpName);
if FAILED(hr)
goto FAILURE;

if (lpName->lpszShortNameA)
lpszPlayerName = lpName->lpszShortNameA;
else
lpszPlayerName = "unknown";

// allocate space for display string
lpszStr = (LPSTR) GlobalAllocPtr(GHND, lstrlen(szDisplayFormat) +
   lstrlen(lpszPlayerName) +
   lstrlen(lpszMsg) + 1);
if (lpszStr == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// build string
wsprintf(lpszStr, szDisplayFormat, lpszPlayerName, lpszMsg);

*lplpszStr = lpszStr;
lpszStr = NULL;

FAILURE:
if (lpszStr)
GlobalFreePtr(lpszStr);

if (lpName)
GlobalFreePtr(lpName);

return (hr);
}

void ErrorBox(LPSTR lpszErrorStr, HRESULT hr)
{
charszStr[MAXSTRLEN];

wsprintf(szStr, lpszErrorStr, hr);

MessageBox(NULL, szStr, "Error", MB_OK);
}


DPCHAT.H

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dpchat.h
 *  Content:Header file for dpchat.cpp
 *
 ***************************************************************************/

#define IDIRECTPLAY2_OR_GREATER
#include <dplay.h>

// constants
const DWORD MAXPLAYERS = 10;// max no. players in the session

// structure used to store DirectPlay information
typedef struct {
LPDIRECTPLAY3AlpDirectPlay3A;// IDirectPlay3A interface pointer
HANDLEhPlayerEvent;// player event to use
DPIDdpidPlayer;// ID of player created
BOOLbIsHost;// TRUE if we are hosting the session
} DPLAYINFO, *LPDPLAYINFO;

// guid for this application
// {5BFDB060-06A4-11d0-9C4F-00A0C905425E}
DEFINE_GUID(DPCHAT_GUID,
0x5bfdb060, 0x6a4, 0x11d0, 0x9c, 0x4f, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e);

// prototypes
extern HRESULTConnectUsingLobby(LPDPLAYINFO lpDPInfo);
extern HRESULTConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
extern voidErrorBox(LPSTR lpszErrorStr, HRESULT hr);


LOBBY.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       lobby.cpp
 *  Content:Uses information from the lobby to establish a connection.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <dplobby.h>

#include "dpchat.h"

HRESULT ConnectUsingLobby(LPDPLAYINFO lpDPInfo)
{
LPDIRECTPLAY2AlpDirectPlay2A = NULL;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;
LPDIRECTPLAYLOBBYAlpDirectPlayLobbyA = NULL;
LPDPLCONNECTIONlpConnectionSettings = NULL;
DPIDdpidPlayer;
DWORDdwSize;
HRESULThr;

// get an ANSI DirectPlay lobby interface
hr = DirectPlayLobbyCreate(NULL, &lpDirectPlayLobbyA, NULL, NULL, 0);
if FAILED(hr)
goto FAILURE;

    // get connection settings from the lobby
// if this routine returns DPERR_NOTLOBBIED, then a lobby did not
// launch this application and the user needs to configure the connection.

// pass in a NULL pointer to just get the size of the connection setttings
hr = lpDirectPlayLobbyA->GetConnectionSettings(0, NULL, &dwSize);
if (DPERR_BUFFERTOOSMALL != hr)
goto FAILURE;

// allocate memory for the connection setttings
lpConnectionSettings = (LPDPLCONNECTION) GlobalAllocPtr(GHND, dwSize);
if (NULL == lpConnectionSettings)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the connection settings
hr = lpDirectPlayLobbyA->GetConnectionSettings(0, lpConnectionSettings, &dwSize);
if FAILED(hr)
goto FAILURE;

// before connecting, the game should configure the session description
// with any settings it needs

// set flags and max players used by the game
    lpConnectionSettings->lpSessionDesc->dwFlags = DPSESSION_MIGRATEHOST |
   DPSESSION_KEEPALIVE;
    lpConnectionSettings->lpSessionDesc->dwMaxPlayers = MAXPLAYERS;

    // store the updated connection settings
    hr = lpDirectPlayLobbyA->SetConnectionSettings(0, 0, lpConnectionSettings);
if FAILED(hr)
goto FAILURE;

// connect to the session - returns an ANSI IDirectPlay2A interface
hr = lpDirectPlayLobbyA->Connect(0, &lpDirectPlay2A, NULL);
if FAILED(hr)
goto FAILURE;

// Obtain an IDirectPlay3A interface, the IDirectPlay2A interface will
// be released at the end of the function
hr = lpDirectPlay2A->QueryInterface(IID_IDirectPlay3A, (LPVOID *) &lpDirectPlay3A);
if FAILED(hr)
goto FAILURE;

// create a player with the name returned in the connection settings
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer,
lpConnectionSettings->lpPlayerName,
lpDPInfo->hPlayerEvent, NULL, 0, 0);
if FAILED(hr)
goto FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
if (lpConnectionSettings->dwFlags & DPLCONNECTION_CREATESESSION)
lpDPInfo->bIsHost = TRUE;
else
lpDPInfo->bIsHost = FALSE;

lpDirectPlay3A = NULL;// set to NULL here so it won't release below

FAILURE:
if (lpDirectPlay2A)
lpDirectPlay2A->Release();

if (lpDirectPlay3A)
lpDirectPlay3A->Release();

if (lpDirectPlayLobbyA)
lpDirectPlayLobbyA->Release();

if (lpConnectionSettings)
GlobalFreePtr(lpConnectionSettings);

return (hr);
}


DIALOG.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dialog.cpp
 *  Content:Creates a dialog to query the user for connection settings
 *and establish a connection.
 *
 ***************************************************************************/

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <cguid.h>

#include "dpchat.h"
#include "resource.h"

// constants
const DWORD MAXNAMELEN= 200;// max size of a session or player name
const UINTTIMERID= 1;// timer ID to use
const UINTTIMERINTERVAL= 1000;// timer interval

// prototypes
BOOL CALLBACKConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HRESULTCreateDirectPlayInterface(LPDIRECTPLAY3A *lplpDirectPlay3A );
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(LPCGUID lpguidSP,
LPVOID lpConnection, DWORD dwConnectionSize,
LPCDPNAME lpName, DWORD dwFlags, LPVOID lpContext);
HRESULTDestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);
HRESULTHostSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPSTR lpszSessionName, LPSTR lpszPlayerName,
LPDPLAYINFO lpDPInfo);
HRESULTJoinSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPGUID lpguidSessionInstance, LPSTR lpszPlayerName,
LPDPLAYINFO lpDPInfo);
HRESULTEnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);

HRESULTGetConnection(HWND hWnd, LPVOID *lplpConnection);
voidDeleteConnectionList(HWND hWnd);
HRESULTGetSessionInstanceGuid(HWND hWnd, LPGUID lpguidSessionInstance);
voidSelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance);
voidDeleteSessionInstanceList(HWND hWnd);
voidEnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable);

HRESULT ConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
// ask user for connection settings
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CONNECTDIALOG),
   NULL, (DLGPROC) ConnectWndProc, (LPARAM) lpDPInfo))
{
return (DP_OK);
}
else
{
return (DPERR_USERCANCEL);
}
}

BOOL CALLBACK ConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
static LPDIRECTPLAY3AlpDirectPlay3A;
static UINTidTimer;
GUIDguidSessionInstance;
charszSessionName[MAXNAMELEN];
charszPlayerName[MAXNAMELEN];
DWORDdwNameSize;
HRESULThr;
LPVOIDlpConnection = NULL;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;
lpDirectPlay3A = NULL;

// Create an IDirectPlay3 interface
hr = CreateDirectPlayInterface(&lpDirectPlay3A);
if (FAILED(hr))
goto SETUP_FAILURE;

// set first item in the connections combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, (WPARAM) 0, (LPARAM) "<Select a service provider>");
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) 0, (LPARAM) 0);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);

// put all the available connections in a combo box
lpDirectPlay3A->EnumConnections(&DPCHAT_GUID, DirectPlayEnumConnectionsCallback, hWnd, 0);

// setup initial button state
EnableDlgButton(hWnd, IDC_HOSTBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
break;

SETUP_FAILURE:
MessageBox(NULL, "This application requires DirectX 5 or later.", NULL, MB_OK);
EndDialog(hWnd, FALSE);
break;

    case WM_DESTROY:
// delete information stored along with the lists
DeleteConnectionList(hWnd);
DeleteSessionInstanceList(hWnd);
        break;

case WM_TIMER:
// refresh the session list
hr = EnumSessions(hWnd, lpDirectPlay3A);
break;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
case IDC_SPCOMBO:
switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
// service provider changed, so rebuild display and
// delete any existing DirectPlay interface
KillTimer(hWnd, idTimer );
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

// get pointer to the selected connection
hr = GetConnection(hWnd, &lpConnection);
if FAILED(hr)
goto SP_FAILURE;

if (lpConnection)
{
/*
 * Create a new DPlay interface.
 */

hr = CreateDirectPlayInterface( &lpDirectPlay3A );

if ((FAILED(hr)) || (NULL == lpDirectPlay3A))
goto SP_FAILURE;

// initialize the connection
hr = lpDirectPlay3A->InitializeConnection(lpConnection, 0);
if FAILED(hr)
goto SP_FAILURE;

// OK to host now
EnableDlgButton(hWnd, IDC_HOSTBUTTON, TRUE);

// start enumerating the sessions
hr = EnumSessions(hWnd, lpDirectPlay3A);
if FAILED(hr)
goto SP_FAILURE;

// set a timer to refresh the session list
idTimer = SetTimer(hWnd, TIMERID, TIMERINTERVAL, NULL);
}
else
{
// They've selected the generic option "<Select a service provider>"
EnableDlgButton(hWnd, IDC_HOSTBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
}
break;
}
break;

SP_FAILURE:
if (hr != DPERR_USERCANCEL)
ErrorBox("Could not select service provider because of error 0x%08X", hr);
break;

case IDC_HOSTBUTTON:
// should have an interface by now
if (lpDirectPlay3A == NULL)
break;

KillTimer(hWnd, idTimer );
// use computer name for session name
dwNameSize = MAXNAMELEN;
if (!GetComputerName(szSessionName, &dwNameSize))
lstrcpy(szSessionName, "Session");

// use user name for player name
dwNameSize = MAXNAMELEN;
if (!GetUserName(szPlayerName, &dwNameSize))
lstrcpy(szPlayerName, "unknown");

// host a new session on this service provider
hr = HostSession(lpDirectPlay3A, szSessionName, szPlayerName, lpDPInfo);
if FAILED(hr)
goto HOST_FAILURE;

// dismiss dialog if we succeeded in hosting
EndDialog(hWnd, TRUE);
break;

HOST_FAILURE:
ErrorBox("Could not host session because of error 0x%08X", hr);
break;

        case IDC_JOINBUTTON:

// should have an interface by now
if (lpDirectPlay3A == NULL)
break;

KillTimer(hWnd, idTimer );
// get guid of selected session instance
hr = GetSessionInstanceGuid(hWnd, &guidSessionInstance);
if FAILED(hr)
goto JOIN_FAILURE;

// use user name for player name
dwNameSize = MAXNAMELEN;
if (!GetUserName(szPlayerName, &dwNameSize))
lstrcpy(szPlayerName, "unknown");

// join this session
hr = JoinSession(lpDirectPlay3A, &guidSessionInstance, szPlayerName, lpDPInfo);

if FAILED(hr)
goto JOIN_FAILURE;

// dismiss dialog if we succeeded in joining
EndDialog(hWnd, TRUE);
            break;

JOIN_FAILURE:
ErrorBox("Could not join session because of error 0x%08X", hr);
break;

        case IDCANCEL:
KillTimer(hWnd, idTimer );
// delete any interface created if cancelling
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

EndDialog(hWnd, FALSE);
            break;
        }

        break;
    }

    // Allow for default processing
    return FALSE;
}

BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(
LPCGUID     lpguidSP,
LPVOIDlpConnection,
DWORDdwConnectionSize,
LPCDPNAME   lpName,
DWORD dwFlags,
LPVOID lpContext)
{

    HWNDhWnd = (HWND) lpContext;
    LRESULTiIndex;
LPVOIDlpConnectionBuffer;

// store service provider name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, 0,
(LPARAM) lpName->lpszShortNameA);
if (iIndex == CB_ERR)
goto FAILURE;

// make space for connection shortcut
lpConnectionBuffer = GlobalAllocPtr(GHND, dwConnectionSize);
if (lpConnectionBuffer == NULL)
goto FAILURE;

// store pointer to connection shortcut in combo box
memcpy(lpConnectionBuffer, lpConnection, dwConnectionSize);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex,
(LPARAM) lpConnectionBuffer);

FAILURE:
    return (TRUE);
}

HRESULT CreateDirectPlayInterface( LPDIRECTPLAY3A *lplpDirectPlay3A )
{
HRESULThr;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;

// Create an IDirectPlay3 interface
hr = CoCreateInstance(CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectPlay3A, (LPVOID*)&lpDirectPlay3A);

// return interface created
*lplpDirectPlay3A = lpDirectPlay3A;

return (hr);
}

HRESULT DestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
HRESULThr = DP_OK;

if (lpDirectPlay3A)
{
DeleteSessionInstanceList(hWnd);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);

hr = lpDirectPlay3A->Release();
}

return (hr);
}

HRESULT HostSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPSTR lpszSessionName, LPSTR lpszPlayerName,
LPDPLAYINFO lpDPInfo)
{
DPIDdpidPlayer;
DPNAMEdpName;
DPSESSIONDESC2sessionDesc;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// host a new session
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.dwFlags = DPSESSION_MIGRATEHOST | DPSESSION_KEEPALIVE;
    sessionDesc.guidApplication = DPCHAT_GUID;
    sessionDesc.dwMaxPlayers = MAXPLAYERS;
sessionDesc.lpszSessionNameA = lpszSessionName;

hr = lpDirectPlay3A->Open(&sessionDesc, DPOPEN_CREATE);
if FAILED(hr)
goto OPEN_FAILURE;

// fill out name structure
ZeroMemory(&dpName, sizeof(DPNAME));
dpName.dwSize = sizeof(DPNAME);
dpName.lpszShortNameA = lpszPlayerName;
dpName.lpszLongNameA = NULL;

// create a player with this name
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer, &dpName,
lpDPInfo->hPlayerEvent, NULL, 0, 0);
if FAILED(hr)
goto CREATEPLAYER_FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = TRUE;

return (DP_OK);

CREATEPLAYER_FAILURE:
OPEN_FAILURE:
lpDirectPlay3A->Close();
return (hr);
}

HRESULT JoinSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPGUID lpguidSessionInstance, LPSTR lpszPlayerName,
LPDPLAYINFO lpDPInfo)
{
DPIDdpidPlayer;
DPNAMEdpName;
DPSESSIONDESC2sessionDesc;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// join existing session
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidInstance = *lpguidSessionInstance;

hr = lpDirectPlay3A->Open(&sessionDesc, DPOPEN_JOIN);
if FAILED(hr)
goto OPEN_FAILURE;

// fill out name structure
ZeroMemory(&dpName, sizeof(DPNAME));
dpName.dwSize = sizeof(DPNAME);
dpName.lpszShortNameA = lpszPlayerName;
dpName.lpszLongNameA = NULL;

// create a player with this name
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer, &dpName,
lpDPInfo->hPlayerEvent, NULL, 0, 0);
if FAILED(hr)
goto CREATEPLAYER_FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = FALSE;

return (DP_OK);

CREATEPLAYER_FAILURE:
OPEN_FAILURE:
lpDirectPlay3A->Close();
return (hr);
}

BOOL FAR PASCAL EnumSessionsCallback(
LPCDPSESSIONDESC2lpSessionDesc,
LPDWORDlpdwTimeOut,
DWORDdwFlags,
LPVOIDlpContext)
{
HWNDhWnd = (HWND) lpContext;
LPGUIDlpGuid;
LONGiIndex;

// see if last session has been enumerated
    if (dwFlags & DPESC_TIMEDOUT)
return (FALSE);

// store session name in list
iIndex = SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_ADDSTRING,
(WPARAM) 0, (LPARAM) lpSessionDesc->lpszSessionNameA);

if (iIndex == LB_ERR)
goto FAILURE;

// make space for session instance guid
lpGuid = (LPGUID) GlobalAllocPtr( GHND, sizeof(GUID) );
if (lpGuid == NULL)
goto FAILURE;

// store pointer to guid in list
*lpGuid = lpSessionDesc->guidInstance;
SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

FAILURE:
    return (TRUE);
}

HRESULT EnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
DPSESSIONDESC2sessionDesc;
GUIDguidSessionInstance;
LONGiIndex;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// get guid of currently selected session
guidSessionInstance = GUID_NULL;
hr = GetSessionInstanceGuid(hWnd, &guidSessionInstance);

// delete existing session list
DeleteSessionInstanceList(hWnd);

// add sessions to session list
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidApplication = DPCHAT_GUID;

hr = lpDirectPlay3A->EnumSessions(&sessionDesc, 0, EnumSessionsCallback,
  hWnd, DPENUMSESSIONS_AVAILABLE | DPENUMSESSIONS_ASYNC);

// select the session that was previously selected
SelectSessionInstance(hWnd, &guidSessionInstance);

// hilite "Join" button only if there are sessions to join
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCOUNT,
   (WPARAM) 0, (LPARAM) 0);

EnableDlgButton(hWnd, IDC_JOINBUTTON, (iIndex > 0) ? TRUE : FALSE);

return (hr);
}

HRESULT GetConnection(HWND hWnd, LPVOID *lplpConnection)
{
LONGiIndex;

// get index of the item currently selected in the combobox
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get the pointer to the connection shortcut associated with
// the item
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

*lplpConnection = (LPVOID) iIndex;

return (DP_OK);
}

void DeleteConnectionList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each service provider name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData != 0)// no data to delete
GlobalFreePtr((LPVOID) lpData);

i += 1;
}

// delete all items in combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

HRESULT GetSessionInstanceGuid(HWND hWnd, LPGUID lpguidSessionInstance)
{
LONGiIndex;

// get guid for session
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == LB_ERR)
return (DPERR_GENERIC);

iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == LB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

*lpguidSessionInstance = *((LPGUID) iIndex);

return (DP_OK);
}

void DeleteSessionInstanceList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each session name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to delete
continue;

GlobalFreePtr((LPVOID) lpData);
i += 1;
}

// delete all items in list
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

void SelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance)
{
WPARAMi, iIndex;
LONGlpData;

// loop over the GUID's stored with each session name
// to find the one that matches what was passed in
i = 0;
iIndex = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to compare to
continue;

// guid matches
if (IsEqualGUID(*lpguidSessionInstance, *((LPGUID) lpData)))
{
iIndex = i;// store index of this string
break;
}

i += 1;
}

// select this item
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_SETCURSEL, (WPARAM) iIndex, (LPARAM) 0);
}

void EnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable)
{
EnableWindow(GetDlgItem(hDlg, nIDDlgItem), bEnable);
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dpchat.rc
//
#define IDD_DIALOG1                     101
#define IDD_CHATDIALOG                  101
#define IDD_CONNECTDIALOG               102
#define IDC_LOGEDIT                     1000
#define IDC_SENDEDIT                    1001
#define IDC_SENDBUTTON                  1002
#define IDC_SPCOMBO                     1003
#define IDC_SESSIONLIST                 1004
#define IDC_HOSTBUTTON                  1005
#define IDC_JOINBUTTON                  1006

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1012
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

DPLAUNCH
--------

Sample application to demonstrate how to launch a DirectPlay application
using the IDirectPlayLobby::RunApplication function. You can also use the
BELLHOP sample lobby client application and the sample lobby server to do
this.

Note: MSVC may include older versions of the DirectX header files and
libraries.  This sample requires DirectX 5.  In order to avoid compile
errors, make sure the path to the DirectX 5 header files and libraries are
listed BEFORE the MSVC header files and libraries through the
Tools -> Options -> Directories menu.

Instructions:

1) Choose which application to launch. This dropdown listbox uses
   IDirectPlayLobby::EnumLocalApplications to see what applications
   are available.
2) Choose which service provider to use.
3) Choose the player name for this machine
4) Choose either "Host Session" and supply a name for the session
        - or -
   Choose "Join Session" and supply the network address of the machine
   that is hosting the session.  IPX requires no network address,
   Internet TCP/IP requires an IP address or machine name, modem requires
   a phone number to call and a modem.
5) Click on "Run Application"

Notes:

- Make sure you use DPLAUNCH to launch an application on one machine
  with the "Host Session" option first.  Then launch the application
  on other machines (using DPLAUNCH) with the same service provider using
  the "Join Session" option and the network address of the host (if needed)

- DPLAUNCH will only join sessions that were created using DPLAUNCH. The
  reason is that the session instance GUID is hard coded in DPLAUNCH.
  Normally, DirectPlay generates one (if the application was not lobbied)
  or a lobby server generates one and passes it to the lobby clients.

- A real lobby client would be an application similar to DPLAUNCH but it
  would be communicating with some lobby server to obtain all the
  parameters for the IDirectPlayLobby::RunApplication call.


DPLAUNCH.C

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dplaunch.c
 *  Content:Implementation of a DirectPlay launching utility
 *
 ***************************************************************************/

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <objbase.h>
#include <cguid.h>

#include "dplay.h"
#include "dplobby.h"

#include "resource.h"

// constants
#define NAMEMAX200// maximum size of a string name
#define ADDRESSTYPEMAX10// maximum no. address types

// GUID for sessions this application creates
// {D559FC00-DC12-11cf-9C4E-00A0C905425E}
DEFINE_GUID(MY_SESSION_GUID,
0xd559fc00, 0xdc12, 0x11cf, 0x9c, 0x4e, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e);

// structures

// list of address types
typedef struct {
DWORDdwCount;
GUIDguidAddressTypes[ADDRESSTYPEMAX];
} ADDRESSTYPELIST, *LPADDRESSTYPELIST;

// prototypes
BOOL CALLBACKLauncherWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULTInitializeLauncherWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A *lplpDPlayLobby);
HRESULTUpdateAddressInfo(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby);
voidDestroyLauncherWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby);
voidLaunchDirectPlayApplication(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby);
HRESULTGetComboBoxGuid(HWND hWnd, LONG iDialogItem, LPGUID lpguidServiceProvider);
HRESULTFillModemComboBox(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby, LPGUID lpguidServiceProvider);

// ---------------------------------------------------------------------------
// WinMain
// ---------------------------------------------------------------------------
// Description:             Main windows entry point.
// Arguments:
//  HINSTANCE               [in] Standard windows stuff
//  HINSTANCE               [in]
//  LPSTR                   [in]
//  int                     [in]
// Returns:
//  int
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow )
{
    return DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_LAUNCHERDIALOG), NULL, LauncherWndProc, (LPARAM) hInstance);
}

// ---------------------------------------------------------------------------
// LauncherWndProc
// ---------------------------------------------------------------------------
// Description:             Message callback function for Launcher dialog.
// Arguments:
//  HWND                    [in] Dialog window handle.
//  UINT                    [in] Window message identifier.
//  WPARAM                  [in] Depends on message.
//  LPARAM                  [in] Depends on message.
// Returns:
//  BOOL                    TRUE if message was processed internally.
BOOL CALLBACK LauncherWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCEhInst;
static LPDIRECTPLAYLOBBY2AlpDPlayLobby;
HRESULThr;

    switch(uMsg)
    {
        case WM_INITDIALOG:
            // Save the instance handle
            hInst = (HINSTANCE)lParam;

// Initialize dialog with launcher information
lpDPlayLobby = NULL;
hr = InitializeLauncherWindow(hWnd, &lpDPlayLobby);
            break;

        case WM_DESTROY:
// Destroy launcher information in dialog
DestroyLauncherWindow(hWnd, lpDPlayLobby);

            // Return failure
            EndDialog(hWnd, FALSE);

            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDC_SPCOMBO:

switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
// update the address info display
UpdateAddressInfo(hWnd, lpDPlayLobby);
break;
}
break;

                case IDC_RUNAPPBUTTON:
// get settings and launch application
                    LaunchDirectPlayApplication(hWnd, lpDPlayLobby);

                    break;

                case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, TRUE);

                    break;
            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

// ---------------------------------------------------------------------------
// EnumApp
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlay.
//Enumerates the applications registered with DirectPlay.
// Arguments:
//  LPDPLAPPINFO            [in] information about the application
//  LPVOID    [in] user-defined context
//  DWORD[in] flags
// Returns:
//  BOOLTRUE to continue enumerating
BOOL FAR PASCAL EnumApp(LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags)
{
    HWNDhWnd = lpContext;
    LRESULTiIndex;
LPGUIDlpGuid;

// store application name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_ADDSTRING, 0, (LPARAM) lpAppInfo->lpszAppNameA);
if (iIndex == LB_ERR)
goto Failure;

// make space for application GUID
lpGuid = (LPGUID) GlobalAllocPtr(GHND, sizeof(GUID));
if (lpGuid == NULL)
goto Failure;

// store pointer to GUID in combo box
*lpGuid = lpAppInfo->guidApplication;
SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

Failure:
    return (TRUE);
}

// ---------------------------------------------------------------------------
// EnumSP
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlay.
//Enumerates service providers registered with DirectPlay.
// Arguments:
//  LPGUID[in] GUID of service provider
//  LPTSTR[in] name of service provider
//  DWORD[in] major version of DirectPlay
//  DWORD[in] minor version of DirectPlay
//  LPVOID    [in] user-defined context
// Returns:
//  BOOLTRUE to continue enumerating
BOOL FAR PASCAL EnumSP(LPGUID lpSPGuid, LPTSTR lptszDesc, DWORD dwMajorVersion,
       DWORD dwMinorVersion, LPVOID lpContext)
{
    HWNDhWnd = lpContext;
    LRESULTiIndex;
LPGUIDlpGuid;

// store service provider name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, 0, (LPARAM) lptszDesc);
if (iIndex == LB_ERR)
goto Failure;

// make space for service provider GUID
lpGuid = (LPGUID) GlobalAllocPtr(GHND, sizeof(GUID));
if (lpGuid == NULL)
goto Failure;

// store pointer to GUID in combo box
*lpGuid = *lpSPGuid;
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

Failure:
    return (TRUE);
}

// ---------------------------------------------------------------------------
// InitializeLauncherWindow
// ---------------------------------------------------------------------------
// Description:             Initializes the window for the Launcher.
// Arguments:
//  HWND                    [in] Window handle.
//  LPDIRECTPLAYLOBBY2A[out] IDirectPlayLobby interface.
// Returns:
//  HRESULTany errors initializing the window
HRESULT InitializeLauncherWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A *lplpDPlayLobby)
{
LPDIRECTPLAYLOBBYAlpDPlayLobbyA = NULL;
LPDIRECTPLAYLOBBY2AlpDPlayLobby2A = NULL;
HRESULThr;

// get ANSI DirectPlayLobby interface
hr = DirectPlayLobbyCreate(NULL, &lpDPlayLobbyA, NULL, NULL, 0);
if FAILED(hr)
goto Failure;

// get ANSI DirectPlayLobby2 interface
hr = lpDPlayLobbyA->lpVtbl->QueryInterface(lpDPlayLobbyA,
&IID_IDirectPlayLobby2A, (LPVOID *) &lpDPlayLobby2A);
if FAILED(hr)
goto Failure;

// don't need DirectPlayLobby interface anymore
lpDPlayLobbyA->lpVtbl->Release(lpDPlayLobbyA);
lpDPlayLobbyA = NULL;

// put all the DirectPlay applications in a combo box
lpDPlayLobby2A->lpVtbl->EnumLocalApplications(lpDPlayLobby2A, EnumApp, hWnd, 0);

// put all the service providers in a combo box
DirectPlayEnumerate(EnumSP, hWnd);

// initialize the controls
SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_SETCURSEL, (WPARAM) 0, 0);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETCURSEL, (WPARAM) 0, 0);
SendDlgItemMessage(hWnd, IDC_HOSTRADIO, BM_SETCHECK, (WPARAM) BST_CHECKED, 0);

// update the address info display
hr = UpdateAddressInfo(hWnd, lpDPlayLobby2A);

// return the ANSI lobby interface
*lplpDPlayLobby = lpDPlayLobby2A;

return (DP_OK);

Failure:
if (lpDPlayLobbyA)
lpDPlayLobbyA->lpVtbl->Release(lpDPlayLobbyA);
if (lpDPlayLobby2A)
lpDPlayLobby2A->lpVtbl->Release(lpDPlayLobby2A);

return (hr);
}

// ---------------------------------------------------------------------------
// DestroyLauncherWindow
// ---------------------------------------------------------------------------
// Description:             Destroys the launcher window.
// Arguments:
//  HWND                    [in] Window handle.
//  LPDIRECTPLAYLOBBY2A      [in] DirectPlay Lobby interface to destroy
// Returns:
//  Nothing
void DestroyLauncherWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby)
{
WPARAMindex;
LRESULTlpData;

// destroy the GUID's stored with each app name
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_APPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}

// destroy the GUID's stored with each service provider name
index = 0;
while (TRUE)
{
lpData = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA, (WPARAM) index, 0);
if ((lpData == CB_ERR) || (lpData == 0))
break;

GlobalFreePtr((LPVOID) lpData);
index += 1;
}

// release the lobby interface
if (lpDPlayLobby)
lpDPlayLobby->lpVtbl->Release(lpDPlayLobby);
}

// ---------------------------------------------------------------------------
// EnumAddressTypes
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlayLobby.
//Enumerates the address types supported by the
//given Service Provider and returns them in a list.
// Arguments:
//  REFGUID                 [in] GUID of the address type
//  LPVOID    [in] user-defined context
//  DWORD[in] flags
// Returns:
//  BOOLFALSE to stop enumerating after the first callback
BOOL FAR PASCAL EnumAddressTypes(REFGUID guidAddressType, LPVOID lpContext,
DWORD dwFlags)
{
LPADDRESSTYPELISTlpAddressTypes = (LPADDRESSTYPELIST) lpContext;

// make sure there is room
if (lpAddressTypes->dwCount < ADDRESSTYPEMAX)
{
// save the address type guid in the list
lpAddressTypes->guidAddressTypes[lpAddressTypes->dwCount] = *guidAddressType;
lpAddressTypes->dwCount++;
}

return (TRUE);
}

// ---------------------------------------------------------------------------
// UpdateAddressInfo
// ---------------------------------------------------------------------------
// Description:             Updates address information elements in dialog.
//Calls EnumAddressTypes() to determine what address
//information should be displayed and arranges dialog
//to display and collect the needed information.
// Arguments:
//  HWND[in] window handle
//  LPDIRECTPLAYLOBBY2A[in] DirectPlay Lobby interface to use
// Returns:
//  HRESULTDP_OK if it succeedes, otherwise the error
HRESULT UpdateAddressInfo(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby)
{
GUIDguidServiceProvider, guidAddressType;
ADDRESSTYPELISTaddressTypeList;
DWORDi;
HRESULThr;

// get guid of currently selected service provider
hr = GetComboBoxGuid(hWnd, IDC_SPCOMBO, &guidServiceProvider);
if FAILED(hr)
goto Failure;

// get the list of address types for this service provider
ZeroMemory(&addressTypeList, sizeof(ADDRESSTYPELIST));
hr = lpDPlayLobby->lpVtbl->EnumAddressTypes(lpDPlayLobby,
EnumAddressTypes, &guidServiceProvider, &addressTypeList, 0L);
if FAILED(hr)
goto Failure;

// clear and hide address dialog items
SendDlgItemMessage(hWnd, IDC_ADDRESSCOMBO, CB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSCOMBO), SW_HIDE);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSCOMBOLABEL), SW_HIDE);

SetDlgItemText(hWnd, IDC_ADDRESSEDIT, "");
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDIT), SW_HIDE);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDITLABEL), SW_HIDE);

// loop over the address types
for (i = 0; i < addressTypeList.dwCount; i++)
{
guidAddressType = addressTypeList.guidAddressTypes[i];

// phone number
if (IsEqualGUID(&guidAddressType, &DPAID_Phone))
{
SetDlgItemText(hWnd, IDC_ADDRESSEDITLABEL, "Phone number");
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDIT), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDITLABEL), SW_SHOW);
}

// modem
else if (IsEqualGUID(&guidAddressType, &DPAID_Modem))
{
SetDlgItemText(hWnd, IDC_ADDRESSCOMBOLABEL, "Modem");
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSCOMBO), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSCOMBOLABEL), SW_SHOW);
FillModemComboBox(hWnd, lpDPlayLobby, &guidServiceProvider);
}

// internet address
else if (IsEqualGUID(&guidAddressType, &DPAID_INet))
{
SetDlgItemText(hWnd, IDC_ADDRESSEDITLABEL, "IP address");
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDIT), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_ADDRESSEDITLABEL), SW_SHOW);
}
}

Failure:
return (hr);
}

// ---------------------------------------------------------------------------
// CreateAddress
// ---------------------------------------------------------------------------
// Description:             Creates a DPADDRESS using the address information
//from the dialog.
// Arguments:
//  HWND[in] window handle
//  LPDIRECTPLAYLOBBY2A      [in] DirectPlay Lobby interface to use
//  LPGUID[in] GUID of servicer provider to create address for
//  LPVOID*[out] pointer to return address in
//  LPDWORD[out] pointer to return address size in
// Returns:
//  HRESULTany error creating the address
HRESULT CreateAddress(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby,
  LPGUID lpguidServiceProvider,
  LPVOID *lplpAddress, LPDWORD lpdwAddressSize)
{
GUIDguidAddressType;
ADDRESSTYPELISTaddressTypeList;
DPCOMPOUNDADDRESSELEMENTaddressElements[1 + ADDRESSTYPEMAX];
CHARszPhoneNumberString[NAMEMAX];
CHARszModemString[NAMEMAX];
CHARszIPAddressString[NAMEMAX];
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
DWORDi, dwElementCount;
HRESULThr;

// get the list of address types for this service provider
ZeroMemory(&addressTypeList, sizeof(ADDRESSTYPELIST));
hr = lpDPlayLobby->lpVtbl->EnumAddressTypes(lpDPlayLobby,
EnumAddressTypes, lpguidServiceProvider, &addressTypeList, 0L);
if FAILED(hr)
goto Failure;

dwElementCount = 0;

// all DPADDRESS's must have a service provider chunk
addressElements[dwElementCount].guidDataType = DPAID_ServiceProvider;
addressElements[dwElementCount].dwDataSize = sizeof(GUID);
addressElements[dwElementCount].lpData = lpguidServiceProvider;
dwElementCount++;

// loop over the address types
for (i = 0; i < addressTypeList.dwCount; i++)
{
guidAddressType = addressTypeList.guidAddressTypes[i];

// phone number
if (IsEqualGUID(&guidAddressType, &DPAID_Phone))
{
// add a phone number chunk
GetDlgItemText(hWnd, IDC_ADDRESSEDIT, szPhoneNumberString, NAMEMAX);
addressElements[dwElementCount].guidDataType = DPAID_Phone;
addressElements[dwElementCount].dwDataSize = lstrlen(szPhoneNumberString) + 1;
addressElements[dwElementCount].lpData = szPhoneNumberString;
dwElementCount++;
}

// modem
else if (IsEqualGUID(&guidAddressType, &DPAID_Modem))
{
// add a modem chunk
GetDlgItemText(hWnd, IDC_ADDRESSCOMBO, szModemString, NAMEMAX);
addressElements[dwElementCount].guidDataType = DPAID_Modem;
addressElements[dwElementCount].dwDataSize = lstrlen(szModemString) + 1;
addressElements[dwElementCount].lpData = szModemString;
dwElementCount++;
}

// internet address
else if (IsEqualGUID(&guidAddressType, &DPAID_INet))
{
// add an IP address chunk
GetDlgItemText(hWnd, IDC_ADDRESSEDIT, szIPAddressString, NAMEMAX);
addressElements[dwElementCount].guidDataType = DPAID_INet;
addressElements[dwElementCount].dwDataSize = lstrlen(szIPAddressString) + 1;
addressElements[dwElementCount].lpData = szIPAddressString;
dwElementCount++;
}
}

// bail if no address data is available
if (dwElementCount == 1)
return (DPERR_GENERIC);

// see how much room is needed to store this address
hr = lpDPlayLobby->lpVtbl->CreateCompoundAddress(lpDPlayLobby,
addressElements, dwElementCount,
NULL, &dwAddressSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto Failure;

// allocate space
lpAddress = GlobalAllocPtr(GHND, dwAddressSize);
if (lpAddress == NULL)
{
hr = DPERR_NOMEMORY;
goto Failure;
}

// create the address
hr = lpDPlayLobby->lpVtbl->CreateCompoundAddress(lpDPlayLobby,
addressElements, dwElementCount,
lpAddress, &dwAddressSize);
if FAILED(hr)
goto Failure;

// return the address info
*lplpAddress = lpAddress;
*lpdwAddressSize = dwAddressSize;

return (DP_OK);

Failure:
if (lpAddress)
GlobalFreePtr(lpAddress);

return (hr);
}

// ---------------------------------------------------------------------------
// RunApplication
// ---------------------------------------------------------------------------
// Description:             Wrapper for the IDirectPlayLobby::RunApplication() method.
// Arguments:
//  LPDIRECTPLAYLOBBY2A[in] DirectPlay Lobby interface to use
//  LPGUID[in] GUID of application to launch
//  LPGUID[in] GUID of session to host with
//  LPSTR[in] GUID of service provider to connect with
//  LPVOID[in] service-provider address to connect to
//  DWORD[in] length of address
//  LPSTR[in] name of session to host
//  LPSTR[in] name of our player
//  BOOL[in] TRUE to host session, FALSE to join
// Returns:
//  HRESULTany error running the application
HRESULT RunApplication(LPDIRECTPLAYLOBBY2A lpDPlayLobby,
   LPGUID lpguidApplication,
   LPGUID lpguidInstance,
   LPGUID lpguidServiceProvider,
   LPVOID lpAddress,
   DWORD  dwAddressSize,
   LPSTR  lpszSessionName,
   LPSTR  lpszPlayerName,
   BOOL  bHostSession)
{
DWORDappID;
DPSESSIONDESC2sessionInfo;
DPNAMEplayerName;
DPLCONNECTIONconnectInfo;
HRESULThr;

if (lpDPlayLobby == NULL)
return (DPERR_NOINTERFACE);

// fill out session description
ZeroMemory(&sessionInfo, sizeof(DPSESSIONDESC2));
    sessionInfo.dwSize = sizeof(DPSESSIONDESC2);// Size of structure
sessionInfo.dwFlags = 0;// DPSESSION_xxx flags
    sessionInfo.guidInstance = *lpguidInstance;// ID for the session instance
    sessionInfo.guidApplication = *lpguidApplication;// GUID of the DirectPlay application.
    sessionInfo.dwMaxPlayers = 0;// Maximum # players allowed in session
    sessionInfo.dwCurrentPlayers = 0;// Current # players in session (read only)
sessionInfo.lpszSessionNameA = lpszSessionName;// ANSI name of the session
sessionInfo.lpszPasswordA = NULL;// ANSI password of the session (optional)
sessionInfo.dwReserved1 = 0;// Reserved for future MS use.
    sessionInfo.dwReserved2 = 0;
    sessionInfo.dwUser1 = 0;// For use by the application
    sessionInfo.dwUser2 = 0;
    sessionInfo.dwUser3 = 0;
    sessionInfo.dwUser4 = 0;

// fill out player name
ZeroMemory(&playerName, sizeof(DPNAME));
playerName.dwSize = sizeof(DPNAME);// Size of structure
    playerName.dwFlags = 0;// Not used. Must be zero.
playerName.lpszShortNameA = lpszPlayerName;// ANSI short or friendly name
playerName.lpszLongNameA = lpszPlayerName;// ANSI long or formal name

// fill out connection description
ZeroMemory(&connectInfo, sizeof(DPLCONNECTION));
connectInfo.dwSize = sizeof(DPLCONNECTION);// Size of this structure
if (bHostSession)
connectInfo.dwFlags = DPLCONNECTION_CREATESESSION; // Create a new session
else
connectInfo.dwFlags = DPLCONNECTION_JOINSESSION; // Join existing session
connectInfo.lpSessionDesc = &sessionInfo;// Pointer to session desc to use on connect
connectInfo.lpPlayerName = &playerName;// Pointer to Player name structure
connectInfo.guidSP = *lpguidServiceProvider;// GUID of the DPlay SP to use
connectInfo.lpAddress = lpAddress;// Address for service provider
connectInfo.dwAddressSize = dwAddressSize;// Size of address data

// launch and connect the game
hr = lpDPlayLobby->lpVtbl->RunApplication(lpDPlayLobby,
0,// Flags
&appID,// App ID
&connectInfo,// Connection data
NULL);// Connect event
return (hr);
}

// ---------------------------------------------------------------------------
// LaunchDirectPlayApplication
// ---------------------------------------------------------------------------
// Description:             Gathers information from the dialog and runs the application.
// Arguments:
//  HWND                    [in] Window handle.
//  LPDIRECTPLAYLOBBY2A      [in] DirectPlay Lobby interface to use
// Returns:
//  Nothing
void LaunchDirectPlayApplication(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby)
{
GUIDguidApplication, guidSession, guidServiceProvider;
LPSTRlpPlayerName, lpSessionName;
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
CHARszPlayerName[NAMEMAX], szSessionName[NAMEMAX];
LRESULTiHost;
HRESULThr;

SetDlgItemText(hWnd, IDC_STATUSEDIT, "Launching...");

// get guid of application to launch
hr = GetComboBoxGuid(hWnd, IDC_APPCOMBO, &guidApplication);
if FAILED(hr)
goto Failure;

// get guid of service provider to use
hr = GetComboBoxGuid(hWnd, IDC_SPCOMBO, &guidServiceProvider);
if FAILED(hr)
goto Failure;

// get address to use with this service provider
hr = CreateAddress(hWnd, lpDPlayLobby, &guidServiceProvider,
   &lpAddress, &dwAddressSize);
// ignore the error because lpAddress will just be null

// get guid of session to create.
guidSession = MY_SESSION_GUID;

// get name of our player
GetDlgItemText(hWnd, IDC_PLAYEREDIT, szPlayerName, NAMEMAX);
lpPlayerName = szPlayerName;

// get host vs. join flag
iHost = SendDlgItemMessage(hWnd, IDC_HOSTRADIO, BM_GETCHECK, (WPARAM) 0, 0);
if (iHost == BST_CHECKED)
{
iHost = TRUE;// we are hosting a session

// get name of session
GetDlgItemText(hWnd, IDC_SESSIONEDIT, szSessionName, NAMEMAX);
lpSessionName = szSessionName;
}
else
{
iHost = FALSE;// we are joining an existing session
lpSessionName = NULL;// don't need a session name if we are joining
}

// launch the application
hr = RunApplication(lpDPlayLobby,
&guidApplication,
&guidSession,
&guidServiceProvider,
lpAddress, dwAddressSize,
lpSessionName, lpPlayerName,
iHost);
if FAILED(hr)
goto Failure;

SetDlgItemText(hWnd, IDC_STATUSEDIT, "Launch successful");

if (lpAddress)
GlobalFreePtr(lpAddress);
return;

Failure:
if (lpAddress)
GlobalFreePtr(lpAddress);

SetDlgItemText(hWnd, IDC_STATUSEDIT, "Launch failed");

return;
}

// ---------------------------------------------------------------------------
// GetComboBoxGuid
// ---------------------------------------------------------------------------
// Description:             Returns GUID stored with a combo box item
// Arguments:
//  HWND                    [in]  Window handle.
//  LONG[in]  Dialog box item ID of combo box
//  LPGUID[out] GUID stored with item in combo box
// Returns:
//  HRESULTany error getting the GUID
HRESULT GetComboBoxGuid(HWND hWnd, LONG iDialogItem, LPGUID lpguidReturn)
{
LONGiIndex;

// get index of selected item
iIndex = SendDlgItemMessage(hWnd, iDialogItem, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get data associated with this item
iIndex = SendDlgItemMessage(hWnd, iDialogItem, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == CB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

// data is a pointer to a guid
*lpguidReturn = *((LPGUID) iIndex);

return (DP_OK);
}

// ---------------------------------------------------------------------------
// EnumModemAddress
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlayLobby.
//Enumerates the DirectPlay address chunks. If the
//chunk contains modem strings, add them to the control.
// Arguments:
//  REFGUID                 [in] GUID of the address type
//  DWORD[in] size of chunk
//  LPVOID    [in] pointer to chunk
//  LPVOID    [in] user-defined context
// Returns:
//  BOOLFALSE to stop enumerating after the first callback
BOOL FAR PASCAL EnumModemAddress(REFGUID lpguidDataType, DWORD dwDataSize,
LPCVOID lpData, LPVOID lpContext)
{
HWNDhWnd = (HWND) lpContext;
LPSTRlpszStr = (LPSTR) lpData;

// modem
if (IsEqualGUID(lpguidDataType, &DPAID_Modem))
{
// loop over all strings in list
while (lstrlen(lpszStr))
{
// store modem name in combo box
SendDlgItemMessage(hWnd, IDC_ADDRESSCOMBO, CB_ADDSTRING, 0, (LPARAM) lpszStr);

// skip to next string
lpszStr += lstrlen(lpszStr) + 1;
}
}

return (TRUE);
}

// ---------------------------------------------------------------------------
// FillModemComboBox
// ---------------------------------------------------------------------------
// Description:             Fills combo box with modem names
// Arguments:
//  HWND                    [in]  Window handle.
//  LPDIRECTPLAYLOBBY2A     [in]  DirectPlay Lobby interface to use
//  LPGUID[out] GUID of service provider to use
// Returns:
//  HRESULTany error
HRESULT FillModemComboBox(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby, LPGUID lpguidServiceProvider)
{
LPDIRECTPLAYlpDPlay1 = NULL;
LPDIRECTPLAY3AlpDPlay3A = NULL;
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
HRESULThr;

// get a DirectPlay interface for this service provider
hr = DirectPlayCreate(lpguidServiceProvider, &lpDPlay1, NULL);
if FAILED(hr)
goto Failure;

// query for an ANSI DirectPlay3 interface
hr = lpDPlay1->lpVtbl->QueryInterface(lpDPlay1, &IID_IDirectPlay3A, (LPVOID *) &lpDPlay3A);
if FAILED(hr)
goto Failure;

// get size of player address for player zero
hr = lpDPlay3A->lpVtbl->GetPlayerAddress(lpDPlay3A, DPID_ALLPLAYERS, NULL, &dwAddressSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto Failure;

// make room for it
lpAddress = GlobalAllocPtr(GHND, dwAddressSize);
if (lpAddress == NULL)
{
hr = DPERR_NOMEMORY;
goto Failure;
}

// get the address
hr = lpDPlay3A->lpVtbl->GetPlayerAddress(lpDPlay3A, DPID_ALLPLAYERS, lpAddress, &dwAddressSize);
if FAILED(hr)
goto Failure;

// get modem strings from address and put them in the combo box
hr = lpDPlayLobby->lpVtbl->EnumAddress(lpDPlayLobby, EnumModemAddress,
 lpAddress, dwAddressSize, hWnd);
if FAILED(hr)
goto Failure;

// select first item in list
SendDlgItemMessage(hWnd, IDC_ADDRESSCOMBO, CB_SETCURSEL, (WPARAM) 0, 0);

Failure:
if (lpDPlay1)
lpDPlay1->lpVtbl->Release(lpDPlay1);
if (lpDPlay3A)
lpDPlay3A->lpVtbl->Release(lpDPlay3A);
if (lpAddress)
GlobalFreePtr(lpAddress);

return (hr);
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dplaunch.rc
//
#define IDD_DIALOG1                     101
#define IDD_LAUNCHERDIALOG              102
#define IDC_HOSTRADIO                   1000
#define IDC_JOINRADIO                   1001
#define IDC_APPCOMBO                    1002
#define IDC_SPCOMBO                     1003
#define IDC_ADDRESSEDIT                 1004
#define IDC_PLAYEREDIT                  1005
#define IDC_SESSIONEDIT                 1006
#define IDC_RUNAPPBUTTON                1007
#define IDC_STATUSEDIT                  1008
#define IDC_ADDRESSCOMBO                1009
#define IDC_ADDRESSEDITLABEL            1010
#define IDC_ADDRESSCOMBOLABEL           1011
#define IDC_EDIT1                       1013

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1014
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

DPSLOTS.EXE
------------

Sample application to demonstrate how to build a client/server
application using DirectPlay.

Note: MSVC may include older versions of the DirectX header files and
libraries.  This sample requires DirectX 5.  In order to avoid compile
errors, make sure the path to the DirectX 5 header files and libraries are
listed BEFORE the MSVC header files and libraries through the
Tools -> Options -> Directories menu.

CLIENT/SERVER
-------------

DPSLOTS is really two applications in one. When you host a session, it acts
as a server. The server maintains a simple database of account balances for
every user that is allowed to log in. Clients can request their current
balance from the server and can request that the server spin the wheels and
calculate the amount won or lost.

The server uses a simple text file to record the balance for each user. Each
line of the text file contains the user account ID and a 8-character balance
figure separated by a comma, as in:

Account1,    1000
Account2,     500

You will have to manually add lines to this file for each user.

When you join a session, DPSLOTS acts as a client and sends messages to the
server for all operations. After requesting a spin from the server, it
animates the tumblers and displays the results. The client may be required
to log the user into the server using a name and password.

SECURITY
--------

DPSLOTS can be hosted using security by clicking the "Require Secure Login"
checkbox after choosing to host.  By leaving the "Security Provider" editbox
blank the default NTLM security package will be used.  If you have an
alternate SSPI security package installed, it may be specified here.

Once this is done, the server will host a secure session and clients will
have to securely log in with a user name and a password before being
allowed access to the server. All messages between the client and server
will be digitally signed and encrypted.

When hosting a secure server, the account ID for each user will be defined
by the security package being used. This account ID must be stored in the
database file along with the appropriate balance.

The default security package is NTLM, which uses a domain and a user name
for the account ID. For example, if your domain is "GAMES" and you have two
users "Bob" and "Jane", your database file would look like:

GAMES/Bob,    1000
GAMES/Jane,     500

Currently you can only host a secure session using NTLM on Windows NT
workstation or server. However, you can log into the server from Windows 95
or Windows NT.


DIALOG.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dialog.cpp
 *  Content:Creates a dialog to query the user for connection settings
 *and establish a connection.
 *
 ***************************************************************************/

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <cguid.h>

#include "dpslots.h"
#include "resource.h"

// constants
const UINTTIMERID= 1;// timer ID to use
const UINTTIMERINTERVAL= 1000;// timer interval

// structures

// server configuration information
typedef struct {
CHARszServerName[MAXSTRLEN];
CHARszDatabaseFile[MAXSTRLEN];
CHARszSecurityProvider[MAXSTRLEN];
BOOLbRequireSecureLogin;
} SERVERCONFIG, *LPSERVERCONFIG;

// client login information
typedef struct {
CHARszPlayerName[MAXSTRLEN];
CHARszUserName[MAXSTRLEN];
CHARszPassword[MAXSTRLEN];
CHARszDomain[MAXSTRLEN];
} CLIENTLOGIN, *LPCLIENTLOGIN;

// prototypes
BOOL CALLBACKConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HRESULTCreateDirectPlayInterface(LPDIRECTPLAY3A *lplpDirectPlay3A );
BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(LPCGUID lpguidSP,
LPVOID lpConnection, DWORD dwConnectionSize,
LPCDPNAME lpName, DWORD dwFlags, LPVOID lpContext);
HRESULTDestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);
HRESULTHostSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPSERVERCONFIG lpServerConfig,
LPDPLAYINFO lpDPInfo);
HRESULTJoinSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPGUID lpguidSessionInstance,
LPCLIENTLOGIN lpClientLogin,
LPDPLAYINFO lpDPInfo);
HRESULTEnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A);

HRESULTGetConnection(HWND hWnd, LPVOID *lplpConnection);
voidDeleteConnectionList(HWND hWnd);
HRESULTGetSessionInstanceGuid(HWND hWnd, LPGUID lpguidSessionInstance);
voidSelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance);
voidDeleteSessionInstanceList(HWND hWnd);
BOOLGetServerConfig(HWND hWnd, LPSERVERCONFIG lpServerConfig);
BOOLGetClientLogin(HWND hWnd, LPCLIENTLOGIN lpClientLogin);
HRESULTGetSessionDesc(LPDIRECTPLAY3A lpDirectPlay3A, LPDPSESSIONDESC2 *lplpSessionDesc);

HRESULT ConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
// ask user for connection settings
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CONNECTDIALOG),
   NULL, (DLGPROC) ConnectWndProc, (LPARAM) lpDPInfo))
{
return (DP_OK);
}
else
{
return (DPERR_USERCANCEL);
}
}

BOOL CALLBACK ConnectWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo;
static LPDIRECTPLAY3AlpDirectPlay3A;
static UINTidTimer;
GUIDguidSessionInstance;
SERVERCONFIGserverConfig;
CLIENTLOGINclientLogin;
LPVOIDlpConnection = NULL;
DWORDdwNameSize;
HRESULThr;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;
lpDirectPlay3A = NULL;

// Create an IDirectPlay3 interface
hr = CreateDirectPlayInterface( &lpDirectPlay3A );

if (FAILED(hr))
goto SETUP_FAILURE;

// set first item in the connections combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, (WPARAM) 0, (LPARAM) "<Select a service provider>");
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) 0, (LPARAM) 0);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);

// put all the available connections in a combo box
lpDirectPlay3A->EnumConnections(&DPSLOTS_GUID, DirectPlayEnumConnectionsCallback, hWnd, 0);

// setup initial button state
EnableDlgButton(hWnd, IDC_HOSTBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
break;

SETUP_FAILURE:
ErrorBox("Could not create DirectPlay object because of error 0x%08X", hr);
EndDialog(hWnd, FALSE);
break;

    case WM_DESTROY:
// delete information stored along with the lists
DeleteConnectionList(hWnd);
DeleteSessionInstanceList(hWnd);
        break;

case WM_TIMER:
// refresh the session list
hr = EnumSessions(hWnd, lpDirectPlay3A);
break;

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
case IDC_SPCOMBO:
switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
// service provider changed, so rebuild display and
// delete any existing DirectPlay interface
KillTimer(hWnd, idTimer );
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

// get pointer to the selected connection
hr = GetConnection(hWnd, &lpConnection);
if FAILED(hr)
goto SP_FAILURE;

if (lpConnection)
{
hr = CreateDirectPlayInterface( &lpDirectPlay3A );

if ((FAILED(hr)) || (NULL == lpDirectPlay3A))
goto SP_FAILURE;

// initialize the connection
hr = lpDirectPlay3A->InitializeConnection(lpConnection, 0);
if FAILED(hr)
goto SP_FAILURE;

// OK to host now
EnableDlgButton(hWnd, IDC_HOSTBUTTON, TRUE);

// start enumerating the sessions
hr = EnumSessions(hWnd, lpDirectPlay3A);
if FAILED(hr)
goto SP_FAILURE;

// set a timer to refresh the session list
idTimer = SetTimer(hWnd, TIMERID, TIMERINTERVAL, NULL);
}
else
{
// They've selected the generic option "<Select a service provider>"
EnableDlgButton(hWnd, IDC_HOSTBUTTON, FALSE);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);
}
break;
}
break;

SP_FAILURE:
if (hr != DPERR_USERCANCEL)
ErrorBox("Could not select service provider because of error 0x%08X", hr);
break;

case IDC_HOSTBUTTON:
// should have an interface by now
if (lpDirectPlay3A == NULL)
break;

// get server configuration from user
ZeroMemory(&serverConfig, sizeof(SERVERCONFIG));
if (!GetServerConfig(hWnd, &serverConfig))
break;

// host a new session on this service provider
hr = HostSession(lpDirectPlay3A, &serverConfig, lpDPInfo);
if FAILED(hr)
goto HOST_FAILURE;

// dismiss dialog if we succeeded in hosting
EndDialog(hWnd, TRUE);
break;

HOST_FAILURE:
ErrorBox("Could not host session because of error 0x%08X", hr);
break;

        case IDC_JOINBUTTON:

// should have an interface by now
if (lpDirectPlay3A == NULL)
break;

// get guid of selected session instance
hr = GetSessionInstanceGuid(hWnd, &guidSessionInstance);
if FAILED(hr)
goto JOIN_FAILURE;

// get server configuration from user
ZeroMemory(&clientLogin, sizeof(CLIENTLOGIN));

// use user name for player name
dwNameSize = MAXSTRLEN;
GetUserName(clientLogin.szPlayerName, &dwNameSize);

// join this session
hr = JoinSession(lpDirectPlay3A, &guidSessionInstance, &clientLogin, lpDPInfo);

// need to ask user for credentials
if (hr == DPERR_LOGONDENIED)
{
if (!GetClientLogin(hWnd, &clientLogin))
break;

// try again, this time with credentials
hr = JoinSession(lpDirectPlay3A, &guidSessionInstance, &clientLogin, lpDPInfo);
}

if FAILED(hr)
goto JOIN_FAILURE;

// dismiss dialog if we succeeded in joining
EndDialog(hWnd, TRUE);
            break;

JOIN_FAILURE:
ErrorBox("Could not join session because of error 0x%08X", hr);
break;

        case IDCANCEL:
// delete any interface created if cancelling
hr = DestroyDirectPlayInterface(hWnd, lpDirectPlay3A);
lpDirectPlay3A = NULL;

EndDialog(hWnd, FALSE);
            break;
        }

        break;
    }

    // Allow for default processing
    return FALSE;
}

BOOL FAR PASCAL DirectPlayEnumConnectionsCallback(
LPCGUID     lpguidSP,
LPVOIDlpConnection,
DWORDdwConnectionSize,
LPCDPNAME   lpName,
DWORD dwFlags,
LPVOID lpContext)
{

    HWNDhWnd = (HWND) lpContext;
    LRESULTiIndex;
LPVOIDlpConnectionBuffer;

// store service provider name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, 0,
(LPARAM) lpName->lpszShortNameA);
if (iIndex == CB_ERR)
goto FAILURE;

// make space for Connection Shortcut
lpConnectionBuffer = GlobalAllocPtr(GHND, dwConnectionSize);
if (lpConnectionBuffer == NULL)
goto FAILURE;

// store pointer to GUID in combo box
memcpy(lpConnectionBuffer, lpConnection, dwConnectionSize);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex,
(LPARAM) lpConnectionBuffer);

FAILURE:
    return (TRUE);
}

HRESULT CreateDirectPlayInterface( LPDIRECTPLAY3A *lplpDirectPlay3A )
{
HRESULThr;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;

// Create an IDirectPlay3 interface
hr = CoCreateInstance(CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectPlay3A, (LPVOID*)&lpDirectPlay3A);

// return interface created
*lplpDirectPlay3A = lpDirectPlay3A;

return (hr);
}

HRESULT DestroyDirectPlayInterface(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
HRESULThr = DP_OK;

if (lpDirectPlay3A)
{
DeleteSessionInstanceList(hWnd);
EnableDlgButton(hWnd, IDC_JOINBUTTON, FALSE);

hr = lpDirectPlay3A->Release();
}

return (hr);
}

HRESULT HostSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPSERVERCONFIG lpServerConfig,
LPDPLAYINFO lpDPInfo)
{
DPIDdpidPlayer;
DPSESSIONDESC2sessionDesc;
DPSECURITYDESCsecurityDesc;
LPDPSECURITYDESClpSecurityDesc;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// host a new session
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidApplication = DPSLOTS_GUID;
    sessionDesc.dwMaxPlayers = 0;
sessionDesc.lpszSessionNameA = lpServerConfig->szServerName;
sessionDesc.dwFlags = SESSIONFLAGS(lpServerConfig->bRequireSecureLogin);

// assume no security descriptor will be needed
lpSecurityDesc = NULL;

if (lpServerConfig->bRequireSecureLogin)
{
// a service provider string was entered, so use it
if (lstrlen(lpServerConfig->szSecurityProvider))
{
ZeroMemory(&securityDesc, sizeof(DPSECURITYDESC));
securityDesc.dwSize = sizeof(DPSECURITYDESC);
securityDesc.dwFlags = 0;
securityDesc.lpszSSPIProviderA = lpServerConfig->szSecurityProvider;
lpSecurityDesc = &securityDesc;
}
}

// host the session
hr = lpDirectPlay3A->SecureOpen(&sessionDesc, DPOPEN_CREATE, lpSecurityDesc, NULL);
if FAILED(hr)
goto FAILURE;

// create a server player with no name
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer, NULL,
lpDPInfo->hPlayerEvent, NULL, 0, SERVERPLAYERFLAGS);
if FAILED(hr)
goto FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = TRUE;
lpDPInfo->bIsSecure = lpServerConfig->bRequireSecureLogin;

// save database name in global
lstrcpy(gszDatabaseName, lpServerConfig->szDatabaseFile);

return (DP_OK);

FAILURE:
lpDirectPlay3A->Close();
return (hr);
}

HRESULT JoinSession(LPDIRECTPLAY3A lpDirectPlay3A,
LPGUID lpguidSessionInstance,
LPCLIENTLOGIN lpClientLogin,
LPDPLAYINFO lpDPInfo)
{
    DPID                dpidPlayer;
DPNAMEdpName;
DPSESSIONDESC2sessionDesc;
LPDPSESSIONDESC2lpSessionDesc = NULL;
DPCREDENTIALScredentials;
LPDPCREDENTIALSlpCredentials;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// join existing session
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidInstance = *lpguidSessionInstance;

// assume no credentials are going to be used
lpCredentials = NULL;

// setup credentials
ZeroMemory(&credentials, sizeof(DPCREDENTIALS));
credentials.dwSize = sizeof(DPCREDENTIALS);
credentials.dwFlags = 0;

if (lstrlen(lpClientLogin->szUserName))
{
credentials.lpszUsernameA = lpClientLogin->szUserName;
lpCredentials = &credentials;
}

if (lstrlen(lpClientLogin->szPassword))
{
credentials.lpszPasswordA = lpClientLogin->szPassword;
lpCredentials = &credentials;
}

if (lstrlen(lpClientLogin->szDomain))
{
credentials.lpszDomainA = lpClientLogin->szDomain;
lpCredentials = &credentials;
}

// join the session
hr = lpDirectPlay3A->SecureOpen(&sessionDesc, DPOPEN_JOIN, NULL, lpCredentials);

if FAILED(hr)
goto FAILURE;

// fill out name structure
ZeroMemory(&dpName, sizeof(DPNAME));
dpName.dwSize = sizeof(DPNAME);
dpName.lpszShortNameA = lpClientLogin->szPlayerName;
dpName.lpszLongNameA = NULL;

// create a player with this name
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer, &dpName,
lpDPInfo->hPlayerEvent, NULL, 0, CLIENTPLAYERFLAGS);
if FAILED(hr)
goto FAILURE;

// get the session desc
hr = GetSessionDesc(lpDirectPlay3A, &lpSessionDesc);
if FAILED(hr)
goto FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = FALSE;

if (lpSessionDesc->dwFlags & DPSESSION_SECURESERVER)
lpDPInfo->bIsSecure = TRUE;
else
lpDPInfo->bIsSecure = FALSE;

GlobalFreePtr(lpSessionDesc);

return (DP_OK);

FAILURE:
if (lpSessionDesc)
GlobalFreePtr(lpSessionDesc);

lpDirectPlay3A->Close();
return (hr);
}

BOOL FAR PASCAL EnumSessionsCallback(
LPCDPSESSIONDESC2lpSessionDesc,
LPDWORDlpdwTimeOut,
DWORDdwFlags,
LPVOIDlpContext)
{
HWNDhWnd = (HWND) lpContext;
LPGUIDlpGuid;
LONGiIndex;

// see if last session has been enumerated
    if (dwFlags & DPESC_TIMEDOUT)
return (FALSE);

// store session name in list
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_ADDSTRING,
(WPARAM) 0, (LPARAM) lpSessionDesc->lpszSessionNameA);

if (iIndex == LB_ERR)
goto FAILURE;

// make space for session instance guid
lpGuid = (LPGUID) GlobalAllocPtr(GHND, sizeof(GUID));
if (lpGuid == NULL)
goto FAILURE;

// store pointer to guid in list
*lpGuid = lpSessionDesc->guidInstance;
SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

FAILURE:
    return (TRUE);
}

HRESULT EnumSessions(HWND hWnd, LPDIRECTPLAY3A lpDirectPlay3A)
{
DPSESSIONDESC2sessionDesc;
GUIDguidSessionInstance;
LONGiIndex;
HRESULThr;

// check for valid interface
if (lpDirectPlay3A == NULL)
return (DPERR_INVALIDOBJECT);

// get guid of currently selected session
guidSessionInstance = GUID_NULL;
hr = GetSessionInstanceGuid(hWnd, &guidSessionInstance);

// delete existing session list
DeleteSessionInstanceList(hWnd);

// add sessions to session list
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
    sessionDesc.guidApplication = DPSLOTS_GUID;

hr = lpDirectPlay3A->EnumSessions(&sessionDesc, 0, EnumSessionsCallback,
  hWnd, DPENUMSESSIONS_AVAILABLE | DPENUMSESSIONS_ASYNC);

// select the session that was previously selected
SelectSessionInstance(hWnd, &guidSessionInstance);

// hilite "Join" button only if there are sessions to join
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCOUNT,
   (WPARAM) 0, (LPARAM) 0);

EnableDlgButton(hWnd, IDC_JOINBUTTON, (iIndex > 0) ? TRUE : FALSE);

return (hr);
}

HRESULT GetConnection(HWND hWnd, LPVOID *lplpConnection)
{
LONGiIndex;

// get index of the item currently selected in the combobox
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

// get the pointer to the connection shortcut associated with
// the item
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

*lplpConnection = (LPVOID) iIndex;

return (DP_OK);
}

void DeleteConnectionList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each service provider name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData != 0)// no data to delete
GlobalFreePtr((LPVOID) lpData);

i += 1;
}

// delete all items in combo box
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

HRESULT GetSessionInstanceGuid(HWND hWnd, LPGUID lpguidSessionInstance)
{
LONGiIndex;

// get guid for session
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == LB_ERR)
return (DPERR_GENERIC);

iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == LB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

*lpguidSessionInstance = *((LPGUID) iIndex);

return (DP_OK);
}

void DeleteSessionInstanceList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each session name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to delete
continue;

GlobalFreePtr((LPVOID) lpData);
i += 1;
}

// delete all items in list
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

void SelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance)
{
WPARAMi, iIndex;
LONGlpData;

// loop over the GUID's stored with each session name
// to find the one that matches what was passed in
i = 0;
iIndex = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to compare to
continue;

// guid matches
if (IsEqualGUID(*lpguidSessionInstance, *((LPGUID) lpData)))
{
iIndex = i;// store index of this string
break;
}

i += 1;
}

// select this item
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_SETCURSEL, (WPARAM) iIndex, (LPARAM) 0);
}

HRESULT GetSessionDesc(LPDIRECTPLAY3A lpDirectPlay3A, LPDPSESSIONDESC2 *lplpSessionDesc)
{
LPDPSESSIONDESC2lpSessionDesc = NULL;
DWORDdwSessionDescSize;
HRESULThr;

// get size of session desc
hr = lpDirectPlay3A->GetSessionDesc(NULL, &dwSessionDescSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpSessionDesc = (LPDPSESSIONDESC2) GlobalAllocPtr(GHND, dwSessionDescSize);
if (lpSessionDesc == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the session desc
hr = lpDirectPlay3A->GetSessionDesc(lpSessionDesc, &dwSessionDescSize);
if FAILED(hr)
goto FAILURE;

// return account description
*lplpSessionDesc = lpSessionDesc;
lpSessionDesc = NULL;

FAILURE:
if (lpSessionDesc)
GlobalFreePtr(lpSessionDesc);

return (hr);
}

void InitializeServerConfigWindow(HWND hWnd)
{
charszSessionName[MAXSTRLEN];
DWORDdwNameSize;

// use username for default
dwNameSize = MAXSTRLEN;
if (GetComputerName(szSessionName, &dwNameSize))
SetDlgItemText(hWnd, IDC_SERVERNAMEEDIT, szSessionName);

// use default name
SetDlgItemText(hWnd, IDC_DATABASEFILEEDIT, DEFAULTDATABASE);

// security off by default
CheckDlgItem(hWnd, IDC_SECURECHECK, FALSE);
}

void SaveServerConfig(HWND hWnd, LPSERVERCONFIG lpServerConfig)
{
// get strings from dialog
GetDlgItemText(hWnd, IDC_SERVERNAMEEDIT, lpServerConfig->szServerName, MAXSTRLEN);
GetDlgItemText(hWnd, IDC_DATABASEFILEEDIT, lpServerConfig->szDatabaseFile, MAXSTRLEN);
GetDlgItemText(hWnd, IDC_SECURITYPROVIDEREDIT, lpServerConfig->szSecurityProvider, MAXSTRLEN);

if (DlgItemIsChecked(hWnd, IDC_SECURECHECK))
lpServerConfig->bRequireSecureLogin = TRUE;
else
lpServerConfig->bRequireSecureLogin = FALSE;
}

BOOL CALLBACK ServerConfigWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPSERVERCONFIGlpServerConfig;

    switch(uMsg)
    {
        case WM_INITDIALOG:

// globals are passed in lParam
lpServerConfig = (LPSERVERCONFIG) lParam;

// Initialize dialog with appropriate information
InitializeServerConfigWindow(hWnd);
            break;

        case WM_DESTROY:
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDOK:
// save changes they made
SaveServerConfig(hWnd, lpServerConfig);
                    // Return success
                    EndDialog(hWnd, TRUE);

                    break;

case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, FALSE);

                    break;
            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

BOOL GetServerConfig(HWND hWnd, LPSERVERCONFIG lpServerConfig)
{
// collect server config from dialog
return (DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_SERVERCONFIGDIALOG), hWnd, (DLGPROC) ServerConfigWndProc, (LPARAM) lpServerConfig));
}

void InitializeClientLoginWindow(HWND hWnd)
{
charszPlayerName[MAXSTRLEN];
DWORDdwNameSize;

// use user name for player name
dwNameSize = MAXSTRLEN;
if (GetUserName(szPlayerName, &dwNameSize))
SetDlgItemText(hWnd, IDC_USERNAMEEDIT, szPlayerName);
}

void SaveClientLogin(HWND hWnd, LPCLIENTLOGIN lpClientLogin)
{
// get strings from dialog
GetDlgItemText(hWnd, IDC_USERNAMEEDIT, lpClientLogin->szUserName, MAXSTRLEN);
GetDlgItemText(hWnd, IDC_PASSWORDEDIT, lpClientLogin->szPassword, MAXSTRLEN);
GetDlgItemText(hWnd, IDC_DOMAINEDIT, lpClientLogin->szDomain, MAXSTRLEN);
}

BOOL CALLBACK ClientLoginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPCLIENTLOGINlpClientLogin;

    switch(uMsg)
    {
        case WM_INITDIALOG:

// globals are passed in lParam
lpClientLogin = (LPCLIENTLOGIN) lParam;

// Initialize dialog with appropriate information
InitializeClientLoginWindow(hWnd);
            break;

        case WM_DESTROY:
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDOK:
// save changes they made
SaveClientLogin(hWnd, lpClientLogin);
                    // Return success
                    EndDialog(hWnd, TRUE);

                    break;

case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, FALSE);

                    break;
            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

BOOL GetClientLogin(HWND hWnd, LPCLIENTLOGIN lpClientLogin)
{
// collect server config from dialog
return (DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_CLIENTLOGINDIALOG), hWnd, (DLGPROC) ClientLoginWndProc, (LPARAM) lpClientLogin));
}


DPSLOTS.CPP

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dpslots.cpp
 *  Content:common dpslots code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>

#include "dpslots.h"
#include "resource.h"

// globals
HANDLEghReceiveThread = NULL;// handle of receive thread
DWORDgidReceiveThread = 0;// id of receive thread
HANDLEghKillReceiveEvent = NULL;// event used to kill receive thread
HINSTANCEghInstance = NULL;// application instance
CHARgszDatabaseName[MAXSTRLEN];// database name

// prototypes
HRESULTSetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
HRESULTShutdownConnection(LPDPLAYINFO lpDPInfo);
DWORD WINAPIReceiveThread(LPVOID lpThreadParameter);
HRESULTReceiveMessage(LPDPLAYINFO lpDPInfo);
voidHandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
DPID idFrom, DPID idTo);
voidHandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpCmdLine, int nCmdShow )
{
DPLAYINFODPInfo;
intiResult = 0;
HRESULThr;

ghInstance = hInstance;
srand(GetTickCount());
lstrcpy(gszDatabaseName, DEFAULTDATABASE);

// Initialize COM library
hr = CoInitialize(NULL);
if (FAILED(hr))
goto FAILURE;

// setup the connection
hr = SetupConnection(hInstance, &DPInfo);
if FAILED(hr)
goto FAILURE;

// show the server window
if (DPInfo.bIsHost)
{
iResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVERDIALOG), NULL, (DLGPROC) ServerWndProc, (LPARAM) &DPInfo);
}

// show the client window
else
{
iResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CLIENTDIALOG), NULL, (DLGPROC) ClientWndProc, (LPARAM) &DPInfo);
}

FAILURE:
// shut down the connection
hr = ShutdownConnection(&DPInfo);

// Uninitialize the COM library
CoUninitialize();

return (iResult);
}

HRESULT SetupConnection(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo)
{
HRESULThr;

ZeroMemory(lpDPInfo, sizeof(DPLAYINFO));

// create event used by DirectPlay to signal a message has arrived
lpDPInfo->hPlayerEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (lpDPInfo->hPlayerEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create event used to signal that the receive thread should exit
ghKillReceiveEvent = CreateEvent(NULL,// no security
 FALSE,// auto reset
 FALSE,// initial event reset
 NULL);// no name
if (ghKillReceiveEvent == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create thread to receive player messages
ghReceiveThread = CreateThread(NULL,// default security
   0,// default stack size
   ReceiveThread,// pointer to thread routine
   lpDPInfo,// argument for thread
   0,// start it right away
   &gidReceiveThread);
if (ghReceiveThread == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// try to connect using the lobby
hr = ConnectUsingLobby(lpDPInfo);
if FAILED(hr)
{
// if the error returned is DPERR_NOTLOBBIED, that means we
// were not launched by a lobby and we should ask the user for
// connection settings. If any other error is returned it means
// we were launched by a lobby but there was an error making the
// connection.

if (hr != DPERR_NOTLOBBIED)
ErrorBox("Could not connect using lobby because of error 0x%08X", hr);

// if there is no lobby connection, ask the user for settings
hr = ConnectUsingDialog(hInstance, lpDPInfo);
if FAILED(hr)
goto FAILURE;
}

return (DP_OK);

FAILURE:
ShutdownConnection(lpDPInfo);

return (hr);
}

HRESULT ShutdownConnection(LPDPLAYINFO lpDPInfo)
{
if (ghReceiveThread)
{
// wake up receive thread and wait for it to quit
SetEvent(ghKillReceiveEvent);
WaitForSingleObject(ghReceiveThread, INFINITE);

CloseHandle(ghReceiveThread);
ghReceiveThread = NULL;
}

if (ghKillReceiveEvent)
{
CloseHandle(ghKillReceiveEvent);
ghKillReceiveEvent = NULL;
}

if (lpDPInfo->lpDirectPlay3A)
{
if (lpDPInfo->dpidPlayer)
{
lpDPInfo->lpDirectPlay3A->DestroyPlayer(lpDPInfo->dpidPlayer);
lpDPInfo->dpidPlayer = 0;
}
lpDPInfo->lpDirectPlay3A->Close();
lpDPInfo->lpDirectPlay3A->Release();
lpDPInfo->lpDirectPlay3A = NULL;
}

if (lpDPInfo->hPlayerEvent)
{
CloseHandle(lpDPInfo->hPlayerEvent);
lpDPInfo->hPlayerEvent = NULL;
}

return (DP_OK);
}

DWORD WINAPI ReceiveThread(LPVOID lpThreadParameter)
{
    LPDPLAYINFOlpDPInfo = (LPDPLAYINFO) lpThreadParameter;
HANDLEeventHandles[2];

eventHandles[0] = lpDPInfo->hPlayerEvent;
eventHandles[1] = ghKillReceiveEvent;

// loop waiting for player events. If the kill event is signaled
// the thread will exit
while (WaitForMultipleObjects(2, eventHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
{
// receive any messages in the queue
ReceiveMessage(lpDPInfo);
}

ExitThread(0);

return (0);
}

HRESULT ReceiveMessage(LPDPLAYINFO lpDPInfo)
{
DPIDidFrom, idTo;
LPVOIDlpvMsgBuffer;
DWORDdwMsgBufferSize;
HRESULThr;

lpvMsgBuffer = NULL;
dwMsgBufferSize = 0;

// loop to read all messages in queue
do
{
// loop until a single message is successfully read
do
{
// read messages from any player, including system player
idFrom = 0;
idTo = 0;

hr = lpDPInfo->lpDirectPlay3A->Receive(&idFrom, &idTo, DPRECEIVE_ALL,
   lpvMsgBuffer, &dwMsgBufferSize);

// not enough room, so resize buffer
if (hr == DPERR_BUFFERTOOSMALL)
{
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);
lpvMsgBuffer = GlobalAllocPtr(GHND, dwMsgBufferSize);
if (lpvMsgBuffer == NULL)
hr = DPERR_OUTOFMEMORY;
}
} while (hr == DPERR_BUFFERTOOSMALL);

if ((SUCCEEDED(hr)) &&// successfully read a message
(dwMsgBufferSize >= sizeof(DPMSG_GENERIC)))// and it is big enough
{
// check for system message
if (idFrom == DPID_SYSMSG)
{
HandleSystemMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
dwMsgBufferSize, idFrom, idTo);
}
else
{
HandleApplicationMessage(lpDPInfo, (LPDPMSG_GENERIC) lpvMsgBuffer,
 dwMsgBufferSize, idFrom, idTo);
}
}
} while (SUCCEEDED(hr));

// free any memory we created
if (lpvMsgBuffer)
GlobalFreePtr(lpvMsgBuffer);

return (DP_OK);
}

void HandleSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo)
{
if (lpDPInfo->bIsHost)
{
ServerSystemMessage(lpDPInfo, lpMsg, dwMsgSize, idFrom, idTo);
}
else
{
ClientSystemMessage(lpDPInfo, lpMsg, dwMsgSize, idFrom, idTo);
}
}

void HandleApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
  DPID idFrom, DPID idTo)
{
// When using a secure session we should not get any messages here
// because encrypted messages come through as system messages. Therefore,
// it is a security hole to process messages here.

if (lpDPInfo->bIsSecure)
return;

if (lpDPInfo->bIsHost)
{
ServerApplicationMessage(lpDPInfo, lpMsg, dwMsgSize, idFrom, idTo);
}
else
{
ClientApplicationMessage(lpDPInfo, lpMsg, dwMsgSize, idFrom, idTo);
}
}

void ErrorBox(LPSTR lpszErrorStr, HRESULT hr)
{
CHARszStr[200];

wsprintf(szStr, lpszErrorStr, hr);

MessageBox(NULL, szStr, "Error", MB_OK);
}

void CheckDlgItem(HWND hDlg, int nIDDlgItem, BOOL bCheck)
{
SendDlgItemMessage(hDlg, nIDDlgItem, BM_SETCHECK,
   (WPARAM) ((bCheck) ?  BST_CHECKED : BST_UNCHECKED), (LPARAM) 0);
}

BOOL DlgItemIsChecked(HWND hDlg, int nIDDlgItem)
{
return ((SendDlgItemMessage(hDlg, nIDDlgItem, BM_GETCHECK, (WPARAM) 0, (LPARAM) 0) == BST_CHECKED) ? TRUE : FALSE);
}

void EnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable)
{
EnableWindow(GetDlgItem(hDlg, nIDDlgItem), bEnable);
}


DPSLOTS.H

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dpslots.h
 *  Content:common dpslots headers
 *
 ***************************************************************************/

#define IDIRECTPLAY2_OR_GREATER
#include <dplay.h>
#include <dplobby.h>

// constants
#define NUMWHEELS    3// no. wheels
#define SLOTSPERWHEEL    6// no. slots per wheel
#define MAXSTRLEN200// max size of a string
#define DEFAULTDATABASE"slotsdb.txt" // default name of database file

// flags to use when creating players
#define CLIENTPLAYERFLAGS0
#define SERVERPLAYERFLAGS(DPPLAYER_SERVERPLAYER)

// flags to use when sending messages
#define NONSECURESENDFLAGS(DPSEND_GUARANTEED)
#define SECURESENDFLAGS(DPSEND_GUARANTEED |\
 DPSEND_SIGNED |\
 DPSEND_ENCRYPTED)
#define SENDFLAGS(bIsSecure)((bIsSecure) ? SECURESENDFLAGS : NONSECURESENDFLAGS)

// flags to use when creating sessions
#define NONSECURESESSIONFLAGS(DPSESSION_KEEPALIVE |\
 DPSESSION_CLIENTSERVER)
#define SECURESESSIONFLAGS(DPSESSION_KEEPALIVE |\
 DPSESSION_CLIENTSERVER |\
 DPSESSION_SECURESERVER)
#define SESSIONFLAGS(bIsSecure)((bIsSecure) ? SECURESESSIONFLAGS : NONSECURESESSIONFLAGS)

// client messages
#define BALANCEREQUEST300// request for account balance
#define SPINREQUEST302// request for spin

// server messages
#define BALANCERESPONSE400// account balance reply
#define SPINRESPONSE401// spin reply

// structure used to store DirectPlay information
typedef struct {
LPDIRECTPLAY3AlpDirectPlay3A;// IDirectPlay3A interface pointer
HANDLEhPlayerEvent;// player event to use
DPIDdpidPlayer;// ID of player created
BOOLbIsHost;// TRUE if we are hosting the session
BOOLbIsSecure;// TRUE if session is secure
} DPLAYINFO, *LPDPLAYINFO;

// message structures

// used to request the results of a wager
typedef struct {
    DWORD   dwType;// message type
    DWORD   dwAmountBet;// amount to wager
} MSG_SPINREQUEST, *LPMSG_SPINREQUEST;

// response to wager request
typedef struct {
    DWORD   dwType;// message type
HRESULThr;// result of request
LONGdwAmountWonOrLost;// amount won or lost
    DWORD   dwBalance;// current balance after wager
    DWORD   dwIndex[NUMWHEELS];// slot settings
} MSG_SPINRESPONSE, *LPMSG_SPINRESPONSE;

// used to request current balance
typedef struct {
    DWORDdwType;// message type
} MSG_BALANCEREQUEST, *LPMSG_BALANCEREQUEST;

// response to balance request
typedef struct {
    DWORDdwType;// message type
HRESULThr;// result of request
    DWORDdwBalance;// current balance
} MSG_BALANCERESPONSE, *LPMSG_BALANCERESPONSE;

// guid for this application
// {EC4F7AA0-E1E0-11d0-9C50-00A0C905425E}
DEFINE_GUID(DPSLOTS_GUID,
0xec4f7aa0, 0xe1e0, 0x11d0, 0x9c, 0x50, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e);

// globals
extern HINSTANCEghInstance;// instance of application
extern CHARgszDatabaseName[MAXSTRLEN];

// common prototypes
extern HRESULTConnectUsingLobby(LPDPLAYINFO lpDPInfo);
extern HRESULTConnectUsingDialog(HINSTANCE hInstance, LPDPLAYINFO lpDPInfo);
extern voidErrorBox(LPSTR lpszErrorStr, HRESULT hr);
extern voidCheckDlgItem(HWND hDlg, int nIDDlgItem, BOOL bCheck);
extern BOOLDlgItemIsChecked(HWND hDlg, int nIDDlgItem);
extern voidEnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable);

// client prototypes
extern BOOL CALLBACKClientWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
extern voidClientSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
DPID idFrom, DPID idTo);
extern voidClientApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo);
// server prototypes
extern BOOL CALLBACKServerWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
extern voidServerSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
DPID idFrom, DPID idTo);
extern voidServerApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo);


LOBBY.CPP

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       lobby.cpp
 *  Content:Uses information from the lobby to establish a connection.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <dplobby.h>

#include "dpslots.h"

HRESULT ConnectUsingLobby(LPDPLAYINFO lpDPInfo)
{
LPDIRECTPLAY2AlpDirectPlay2A = NULL;
LPDIRECTPLAY3AlpDirectPlay3A = NULL;
LPDIRECTPLAYLOBBYAlpDirectPlayLobbyA = NULL;
LPDPLCONNECTIONlpConnectionSettings = NULL;
DPIDdpidPlayer;
DWORDdwSize;
BOOLbIsHost;
DWORDdwPlayerFlags;
HRESULThr;

// get an ANSI DirectPlay lobby interface
hr = CoCreateInstance(CLSID_DirectPlayLobby, NULL, CLSCTX_INPROC_SERVER,
  IID_IDirectPlayLobbyA, (LPVOID*)&lpDirectPlayLobbyA);
if FAILED(hr)
goto FAILURE;

    // get connection settings from the lobby
// if this routine returns DPERR_NOTLOBBIED, then a lobby did not
// launch this application and the user needs to configure the connection.

// pass in a NULL pointer to just get the size of the connection setttings
hr = lpDirectPlayLobbyA->GetConnectionSettings(0, NULL, &dwSize);
if (DPERR_BUFFERTOOSMALL != hr)
goto FAILURE;

// allocate memory for the connection setttings
lpConnectionSettings = (LPDPLCONNECTION) GlobalAllocPtr(GHND, dwSize);
if (NULL == lpConnectionSettings)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the connection settings
hr = lpDirectPlayLobbyA->GetConnectionSettings(0, lpConnectionSettings, &dwSize);
if FAILED(hr)
goto FAILURE;

// see if we are the host or not
if (lpConnectionSettings->dwFlags & DPLCONNECTION_CREATESESSION)
{
lpConnectionSettings->lpSessionDesc->dwFlags = NONSECURESESSIONFLAGS;
bIsHost = TRUE;
}
else
{
bIsHost = FALSE;
}

    // store the updated connection settings
    hr = lpDirectPlayLobbyA->SetConnectionSettings(0, 0, lpConnectionSettings);
if FAILED(hr)
goto FAILURE;

// connect to the session - returns an ANSI IDirectPlay2A interface
hr = lpDirectPlayLobbyA->Connect(0, &lpDirectPlay2A, NULL);
if FAILED(hr)
goto FAILURE;

// Obtain an IDirectPlay3A interface, the IDirectPlay2A interface will
// be released at the end of the function
hr = lpDirectPlay2A->QueryInterface(IID_IDirectPlay3A, (LPVOID *) &lpDirectPlay3A);
if FAILED(hr)
goto FAILURE;

// if we are hosting then we need to create a server player
if (bIsHost)
dwPlayerFlags = SERVERPLAYERFLAGS;
else
dwPlayerFlags = CLIENTPLAYERFLAGS;

// create a player with the name returned in the connection settings
hr = lpDirectPlay3A->CreatePlayer(&dpidPlayer,
lpConnectionSettings->lpPlayerName,
lpDPInfo->hPlayerEvent, NULL, 0, dwPlayerFlags);
if FAILED(hr)
goto FAILURE;

// return connection info
lpDPInfo->lpDirectPlay3A = lpDirectPlay3A;
lpDPInfo->dpidPlayer = dpidPlayer;
lpDPInfo->bIsHost = bIsHost;
lpDPInfo->bIsSecure = FALSE;

lpDirectPlay3A = NULL;// set to NULL here so it won't release below

FAILURE:
if (lpDirectPlay2A)
lpDirectPlay2A->Release();

if (lpDirectPlay3A)
lpDirectPlay3A->Release();

if (lpDirectPlayLobbyA)
lpDirectPlayLobbyA->Release();

if (lpConnectionSettings)
GlobalFreePtr(lpConnectionSettings);

return (hr);
}


CLIENT.CPP

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       client.cpp
 *  Content:Slot machine client using DirectPlay.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>

#include "dpslots.h"
#include "resource.h"

// constants
const DWORD SLOTWIDTH= 110;// width of slot
const DWORD SLOTHEIGHT= 119;// height of slot
const DWORD SLOTBORDER= 9;// space between slots
const DWORD REVSPERSECOND= 1;// no. revolutions per second
const DWORD PIXELSPERSLOT= SLOTHEIGHT - SLOTBORDER;// no. vertical pixels per slot
const DWORD PIXELSPERREV= PIXELSPERSLOT * SLOTSPERWHEEL;// no. vertical pixels drawn per revolution
const DWORD PIXELSPERSECOND= PIXELSPERREV * REVSPERSECOND;// no. vertical pixels drawn per second
const UINTTIMERID= 1;// timer ID to use
const UINTTIMERINTERVAL= 50;// timer interval
const UINTMAXSTRING= 200;// max size of a string

// window messages
const UINTWM_USER_UPDATEBALANCE = WM_USER + BALANCERESPONSE;
const UINTWM_USER_STARTSPINNING = WM_USER + SPINRESPONSE;

// structures
typedef struct {
DWORDdwIndex;// index of wheel slot to show
    DWORDdwStartTicks;// time wheel started spinning
DWORDdwDuration;// duration wheel should spin
} WHEELINFO, *LPWHEELINFO;

// globals
HWNDghClientWnd = NULL;// main window

// prototypes
HRESULTSendBalanceRequest(LPDPLAYINFO lpDPInfo);
HRESULTSendSpinRequest(LPDPLAYINFO lpDPInfo, DWORD dwAmountBet);
voidDrawWheels(LPWHEELINFO lpWheels, HBITMAP hWheelBitmap,
   HDC hDC, LPRECT lpBoundsRect);
voidStartSpinning(LPWHEELINFO lpWheels);
BOOLSpinWheels(HWND hWnd, LPWHEELINFO lpWheels, HBITMAP hWheelBitmap);

BOOL CALLBACK ClientWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo = NULL;
static UINTidTimer = 0;
static HBITMAPhWheelBitmap = NULL;
static WHEELINFOWheelInfo[NUMWHEELS];
static MSG_SPINRESPONSESpinResponse;
CHARszStr[MAXSTRING];
DWORDi;
DWORDdwAmountBet;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;

// store global window
ghClientWnd = hWnd;

// get slots bitmap
hWheelBitmap = LoadBitmap(ghInstance, MAKEINTRESOURCE(IDB_SLOTSBITMAP));

// initialize slots
for (i = 0; i < NUMWHEELS; i++)
WheelInfo[i].dwIndex = ((DWORD)rand()) % SLOTSPERWHEEL;

        // get starting balance
SendBalanceRequest(lpDPInfo);
break;

    case WM_DESTROY:
// stop the timer
if (idTimer)
{
KillTimer(hWnd, idTimer);
idTimer = 0;
}

// free the bitmap handle
if (hWheelBitmap)
{
DeleteObject(hWheelBitmap);
hWheelBitmap = NULL;
}
ghClientWnd = NULL;
        break;

case WM_USER_UPDATEBALANCE:

// balance is in lParam
        sprintf(szStr, "$%4d", lParam);

        // display new balance
SetDlgItemText(hWnd, IDC_EDIT_BALANCE, szStr);
break;

case WM_USER_STARTSPINNING:

// copy spin response message from lParam
SpinResponse = *((LPMSG_SPINRESPONSE) lParam);
GlobalFreePtr((LPVOID) lParam);// free memory

// check for valid spin
if FAILED(SpinResponse.hr)
{
SetDlgItemText(hWnd, IDC_RESULTEDIT, "You don't have enough money!");
}
else
{
// copy slot settings specified by server
for (i = 0; i < NUMWHEELS; i++)
WheelInfo[i].dwIndex = SpinResponse.dwIndex[i];

// clear win or lose
SetDlgItemText(hWnd, IDC_RESULTEDIT, "");

// start things spinning
StartSpinning(WheelInfo);
idTimer = SetTimer(hWnd, TIMERID, TIMERINTERVAL, NULL);

// disable spin button while spinning
EnableDlgButton(hWnd, IDC_SPINBUTTON, FALSE);
}
break;

case WM_TIMER:
// readraw any spinning wheels
if (!SpinWheels(hWnd, WheelInfo, hWheelBitmap))
{
KillTimer(hWnd, idTimer);
idTimer = 0;

// display amount won or lost
if (SpinResponse.dwAmountWonOrLost > 0)
{
sprintf(szStr,"You win $%d!", SpinResponse.dwAmountWonOrLost);
PlaySound(MAKEINTRESOURCE(IDR_WINWAVE), ghInstance, SND_ASYNC | SND_RESOURCE);
}
else if (SpinResponse.dwAmountWonOrLost < 0)
{
sprintf(szStr,"You lose $%d!", -SpinResponse.dwAmountWonOrLost);
PlaySound(MAKEINTRESOURCE(IDR_LOSEWAVE), ghInstance, SND_ASYNC | SND_RESOURCE);
}
else
{
strcpy(szStr, "");
}

// display win or loss
SetDlgItemText(hWnd, IDC_RESULTEDIT, szStr);

PostMessage(hWnd, WM_USER_UPDATEBALANCE, (WPARAM) 0, (LPARAM) SpinResponse.dwBalance);

// enable spin button again
EnableDlgButton(hWnd, IDC_SPINBUTTON, TRUE);
}
break;

    case WM_DRAWITEM:
{
DRAWITEMSTRUCT*diInfo;

diInfo = (DRAWITEMSTRUCT *) lParam;

switch (diInfo->CtlID)
{
case IDC_SLOTS:
if (diInfo->itemAction == ODA_DRAWENTIRE)
{
DrawWheels(WheelInfo, hWheelBitmap, diInfo->hDC, &diInfo->rcItem);
}
break;
}
}
break;

case WM_COMMAND:
        switch(LOWORD(wParam))
        {
case IDC_SPINBUTTON:
// find out how much was bet
dwAmountBet = 0;

// one dollar
if (DlgItemIsChecked(hWnd, IDC_BET1CHECK))
dwAmountBet += 1;

// five dollars
if (DlgItemIsChecked(hWnd, IDC_BET2CHECK))
dwAmountBet += 5;

// ten dollars
if (DlgItemIsChecked(hWnd, IDC_BET3CHECK))
dwAmountBet += 10;

// ask server for results of spin using this bet
            SendSpinRequest(lpDPInfo, dwAmountBet);
break;

        case IDCANCEL:
EndDialog(hWnd, FALSE);
            break;
}
break;
}

    // Allow for default processing
    return FALSE;
}

void ClientApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
  DPID idFrom, DPID idTo)
{
switch (lpMsg->dwType)
{
    case BALANCERESPONSE:
        {
            LPMSG_BALANCERESPONSE lpBalance = (LPMSG_BALANCERESPONSE)lpMsg;

 PostMessage(ghClientWnd, WM_USER_UPDATEBALANCE, (WPARAM) 0, (LPARAM) lpBalance->dwBalance);
        }
        break;

    case SPINRESPONSE:
        {
            LPMSG_SPINRESPONSE lpSpin = (LPMSG_SPINRESPONSE)lpMsg;
            LPMSG_SPINRESPONSE lpSpinCopy;

// make a copy of the message so we can pass it to the wndproc
lpSpinCopy = (LPMSG_SPINRESPONSE) GlobalAllocPtr(GHND, sizeof(MSG_SPINRESPONSE));
if (lpSpinCopy == NULL)
break;

*lpSpinCopy = *lpSpin;

            PostMessage(ghClientWnd, WM_USER_STARTSPINNING, (WPARAM) 0, (LPARAM) lpSpinCopy);
        }
        break;

    default:
        break;
}
}

void ClientSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo)
{
    // The body of each case is there so you can set a breakpoint and examine
    // the contents of the message received.
switch (lpMsg->dwType)
{
case DPSYS_CREATEPLAYERORGROUP:
        {
            LPDPMSG_CREATEPLAYERORGROUP lp = (LPDPMSG_CREATEPLAYERORGROUP) lpMsg;
        }
break;

case DPSYS_DESTROYPLAYERORGROUP:
        {
            LPDPMSG_DESTROYPLAYERORGROUP lp = (LPDPMSG_DESTROYPLAYERORGROUP)lpMsg;
        }
break;

case DPSYS_ADDPLAYERTOGROUP:
        {
            LPDPMSG_ADDPLAYERTOGROUP lp = (LPDPMSG_ADDPLAYERTOGROUP)lpMsg;
        }
break;

case DPSYS_DELETEPLAYERFROMGROUP:
        {
            LPDPMSG_DELETEPLAYERFROMGROUP lp = (LPDPMSG_DELETEPLAYERFROMGROUP)lpMsg;
        }
break;

case DPSYS_SESSIONLOST:
        {
            LPDPMSG_SESSIONLOST lp = (LPDPMSG_SESSIONLOST)lpMsg;
        }
break;

case DPSYS_HOST:
        {
            LPDPMSG_HOSTlp = (LPDPMSG_HOST)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPDATA:
        {
            LPDPMSG_SETPLAYERORGROUPDATA lp = (LPDPMSG_SETPLAYERORGROUPDATA)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPNAME:
        {
            LPDPMSG_SETPLAYERORGROUPNAME lp = (LPDPMSG_SETPLAYERORGROUPNAME)lpMsg;
        }
break;

case DPSYS_SECUREMESSAGE:
        {
            LPDPMSG_SECUREMESSAGE lp = (LPDPMSG_SECUREMESSAGE)lpMsg;

ClientApplicationMessage(lpDPInfo, (LPDPMSG_GENERIC) lp->lpData, lp->dwDataSize,
  lp->dpIdFrom, idTo);

        }
break;
}
}

HRESULT SendBalanceRequest(LPDPLAYINFO lpDPInfo)
{
    MSG_BALANCEREQUESTMsg;

    ZeroMemory(&Msg, sizeof(MSG_BALANCEREQUEST));
    Msg.dwType = BALANCEREQUEST;

    return (lpDPInfo->lpDirectPlay3A->Send(lpDPInfo->dpidPlayer,
DPID_SERVERPLAYER, SENDFLAGS(lpDPInfo->bIsSecure),
&Msg, sizeof(MSG_BALANCEREQUEST)));
}

HRESULT SendSpinRequest(LPDPLAYINFO lpDPInfo, DWORD dwAmountBet)
{
    MSG_SPINREQUESTMsg;

    ZeroMemory(&Msg, sizeof(MSG_SPINREQUEST));
    Msg.dwType = SPINREQUEST;
Msg.dwAmountBet = dwAmountBet;

    return (lpDPInfo->lpDirectPlay3A->Send(lpDPInfo->dpidPlayer,
DPID_SERVERPLAYER, SENDFLAGS(lpDPInfo->bIsSecure),
&Msg, sizeof(MSG_SPINREQUEST)));
}

#define RECTWIDTH(lpRect)     ((lpRect)->right - (lpRect)->left)
#define RECTHEIGHT(lpRect)    ((lpRect)->bottom - (lpRect)->top)

BOOL PaintBitmap(HDC hDC, LPRECT lpDCRect, HBITMAP hDDB,  LPRECT lpDDBRect)
{
    HDC         hMemDC;            // Handle to memory DC
    HBITMAP     hOldBitmap;        // Handle to previous bitmap
    BOOL        bSuccess = FALSE;  // Success/fail flag

    // Create a memory DC

    hMemDC = CreateCompatibleDC(hDC);

    // If this failed, return FALSE

    if (!hMemDC)
        return FALSE;

    // Select bitmap into the memory DC

    hOldBitmap = (HBITMAP) SelectObject (hMemDC, hDDB);

    // Make sure to use the stretching mode best for color pictures

    SetStretchBltMode (hDC, COLORONCOLOR);

    // Determine whether to call StretchBlt() or BitBlt()
    if ((RECTWIDTH(lpDCRect)  == RECTWIDTH(lpDDBRect)) &&
            (RECTHEIGHT(lpDCRect) == RECTHEIGHT(lpDDBRect)))
        bSuccess = BitBlt(hDC, lpDCRect->left, lpDCRect->top,
                lpDCRect->right - lpDCRect->left,
                lpDCRect->bottom - lpDCRect->top, hMemDC, lpDDBRect->left,
                lpDDBRect->top, SRCCOPY);
    else
        bSuccess = StretchBlt(hDC, lpDCRect->left,  lpDCRect->top,
                lpDCRect->right - lpDCRect->left,
                lpDCRect->bottom - lpDCRect->top, hMemDC, lpDDBRect->left,
                lpDDBRect->top,  lpDDBRect->right - lpDDBRect->left,
                lpDDBRect->bottom - lpDDBRect->top, SRCCOPY);

    // Clean up

    SelectObject(hMemDC, hOldBitmap);

    DeleteDC(hMemDC);

    // Return with success/fail flag

    return bSuccess;
}

void DrawWheels(LPWHEELINFO lpWheels, HBITMAP hWheelBitmap, HDC hDC, LPRECT lpBoundsRect)
{
RECTrectDC, rectSlot;
DWORDdwWidth, dwHeight, dwXOffset, dwYOffset;
DWORDi;

if (hWheelBitmap == NULL)
return;

dwWidth = lpBoundsRect->right - lpBoundsRect->left;
dwHeight = lpBoundsRect->bottom - lpBoundsRect->top;

dwXOffset = (dwWidth - (SLOTWIDTH * NUMWHEELS)) / (NUMWHEELS + 1);
dwYOffset = (dwHeight - SLOTHEIGHT) / 2;

SetRect(&rectDC, dwXOffset, dwYOffset,
dwXOffset + SLOTWIDTH, dwYOffset + SLOTHEIGHT);

for (i = 0; i < NUMWHEELS; i++)
{
SetRect(&rectSlot, 0, 0, SLOTWIDTH, SLOTHEIGHT);
OffsetRect(&rectSlot, 0, lpWheels[i].dwIndex * PIXELSPERSLOT);

PaintBitmap(hDC, &rectDC, hWheelBitmap, &rectSlot);

OffsetRect(&rectDC, SLOTWIDTH + dwXOffset, 0);
}
}

void StartSpinning(LPWHEELINFO lpWheels)
{
DWORDi;

for (i = 0; i < NUMWHEELS; i++)
{
lpWheels[i].dwStartTicks = GetTickCount();
lpWheels[i].dwStartTicks -= lpWheels[i].dwIndex * PIXELSPERSLOT * 1000 / PIXELSPERREV;
lpWheels[i].dwDuration = 1000 * (i + 1) + 1000;
}
}

BOOL SpinWheels(HWND hWnd, LPWHEELINFO lpWheels, HBITMAP hWheelBitmap)
{
HDChDC;
RECTrectBounds;
LPRECTlpBoundsRect;
RECTrectDC, rectSlot;
DWORDdwWidth, dwHeight, dwXOffset, dwYOffset, dwYStart;
DWORDi, dwTicks, dwStoppedCount;

if (hWheelBitmap == NULL)
return (FALSE);

hDC = GetWindowDC(GetDlgItem(hWnd, IDC_SLOTS));
if (hDC == NULL)
return (FALSE);

if (!GetWindowRect(GetDlgItem(hWnd, IDC_SLOTS), &rectBounds))
return (FALSE);

lpBoundsRect = &rectBounds;

dwWidth = lpBoundsRect->right - lpBoundsRect->left;
dwHeight = lpBoundsRect->bottom - lpBoundsRect->top;

dwXOffset = (dwWidth - (SLOTWIDTH * NUMWHEELS)) / (NUMWHEELS + 1);
dwYOffset = (dwHeight - SLOTHEIGHT) / 2;

SetRect(&rectDC, dwXOffset, dwYOffset,
dwXOffset + SLOTWIDTH, dwYOffset + SLOTHEIGHT);

dwStoppedCount = 0;
for (i = 0; i < NUMWHEELS; i++)
{
if (lpWheels[i].dwDuration == 0)
{
dwStoppedCount++;
}
else
{
dwTicks = GetTickCount() - lpWheels[i].dwStartTicks;
dwYStart = (dwTicks * PIXELSPERSECOND) / 1000;
dwYStart %= PIXELSPERREV;

if (dwTicks >= lpWheels[i].dwDuration)
{
//lpWheels[i].value = ((dwYStart + (PIXELSPERSLOT - 1)) / PIXELSPERSLOT) % SLOTSPERWHEEL;

SetRect(&rectSlot, 0, 0, SLOTWIDTH, SLOTHEIGHT);
OffsetRect(&rectSlot, 0, lpWheels[i].dwIndex * PIXELSPERSLOT);
PaintBitmap(hDC, &rectDC, hWheelBitmap, &rectSlot);

lpWheels[i].dwDuration = 0;
                if (dwStoppedCount == (NUMWHEELS - 1))
                    PlaySound(MAKEINTRESOURCE(IDR_STOPWAVE), ghInstance, SND_RESOURCE);
                else
                    PlaySound(MAKEINTRESOURCE(IDR_STOPWAVE), ghInstance, SND_ASYNC | SND_RESOURCE);
            }
else
{
SetRect(&rectSlot, 0, 0, SLOTWIDTH, SLOTHEIGHT);
OffsetRect(&rectSlot, 0, dwYStart);
if (rectSlot.bottom > PIXELSPERREV)
{
RECTrectSlotTmp, rectDCTmp;
DWORDheight;

// copy from bottom end of bitmap
height = PIXELSPERREV - rectSlot.top;
rectSlotTmp = rectSlot;
rectSlotTmp.bottom = rectSlotTmp.top + height;
rectDCTmp = rectDC;
rectDCTmp.bottom = rectDCTmp.top + height;
PaintBitmap(hDC, &rectDCTmp, hWheelBitmap, &rectSlotTmp);

height = rectSlot.bottom - PIXELSPERREV;
rectSlotTmp = rectSlot;
rectSlotTmp.top = 0;
rectSlotTmp.bottom = height;
rectDCTmp = rectDC;
rectDCTmp.top = rectDCTmp.bottom - height;
PaintBitmap(hDC, &rectDCTmp, hWheelBitmap, &rectSlotTmp);
}
else
PaintBitmap(hDC, &rectDC, hWheelBitmap, &rectSlot);
}
}
OffsetRect(&rectDC, SLOTWIDTH + dwXOffset, 0);
}

return (dwStoppedCount != NUMWHEELS);
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dpslots.rc
//
#define IDD_DIALOG1                     101
#define IDD_CHATDIALOG                  101
#define IDD_CONNECTDIALOG               102
#define IDD_SLOTSDIALOG                 103
#define IDD_CLIENTDIALOG                103
#define IDB_SLOTSBITMAP                 104
#define IDR_STOPWAVE                    105
#define IDR_WINWAVE                     106
#define IDR_LOSEWAVE                    107
#define IDD_SERVERDIALOG                108
#define IDD_SERVERCONFIGDIALOG          109
#define IDD_CLIENTLOGINDIALOG           110
#define IDC_LOGEDIT                     1000
#define IDC_SENDEDIT                    1001
#define IDC_SENDBUTTON                  1002
#define IDC_SPCOMBO                     1003
#define IDC_SESSIONLIST                 1004
#define IDC_HOSTBUTTON                  1005
#define IDC_JOINBUTTON                  1006
#define IDC_SLOTS                       1018
#define IDC_SPINBUTTON                  1019
#define IDC_BETCHECK1                   1023
#define IDC_BET1CHECK                   1023
#define IDC_BETCHECK2                   1024
#define IDC_BET2CHECK                   1024
#define IDC_BETCHECK3                   1025
#define IDC_BET3CHECK                   1025
#define IDC_STATIC_BALANCE              1026
#define IDC_EDIT_BALANCE                1027
#define IDC_RESULTEDIT                  1028
#define IDC_SERVERNAMEEDIT              1030
#define IDC_DATABASEFILEEDIT            1031
#define IDC_SECURITYPROVIDEREDIT        1032
#define IDC_SECURECHECK                 1033
#define IDC_USERNAMEEDIT                1034
#define IDC_PASSWORDEDIT                1035
#define IDC_DOMAINEDIT                  1036

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        111
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1035
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SERVER.CPP

/*==========================================================================
 *
 *  Copyright (C) 1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       server.cpp
 *  Content:Slot machine server using DirectPlay.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>

#include "dpslots.h"
#include "resource.h"

// constants
const UINTMAX_RECORD_LENGTH= 256;// max database record size
const DWORDINDEX_JACKPOT= 3;// index of jackpot image
const DWORDINDEX_SPOILER= 5;// index of spoiler image

// window messages
const UINTWM_USER_ADDSTRING = WM_USER + 257;// add string to log window

// structures
typedef struct {
DWORDdwBalance;// account record from database
} ACCOUNTINFO, *LPACCOUNTINFO;

// globals
HWNDghServerWnd = NULL;// main window
FILE*glpFile = NULL;// database file

// prototypes
HRESULTHandleBalanceRequest(LPDPLAYINFO lpDPInfo, LPMSG_BALANCEREQUEST lpBalance, DPID idTo);
HRESULTHandleSpinRequest(LPDPLAYINFO lpDPInfo, LPMSG_SPINREQUEST lpBalance, DPID idTo);
voidLogRequest(LPSTR lpszFormat, LPDPACCOUNTDESC lpAccountDesc, DWORD dwValue);
LONGGetAmountWonOrLost(DWORD dwAmountBet, DWORD dwIndex[]);
FILE*OpenAccountDB(LPSTR lpszDBName);
voidCloseAccountDB(FILE *lpFile);
BOOLQueryAccount(FILE *lpFile, LPSTR lpszAccountID, LPACCOUNTINFO lpAccountInfo);
BOOLUpdateAccount(FILE *lpFile, LPSTR lpszAccountID, LPACCOUNTINFO lpAccountInfo);
HRESULTGetAccountDesc(LPDPLAYINFO lpDPInfo, DPID idPlayer, LPDPACCOUNTDESC *lplpAccountDesc);

BOOL CALLBACK ServerWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPDPLAYINFOlpDPInfo = NULL;
static UINTidTimer = 0;
static FILE*lpFile = NULL;
DWORDdwTextLen;

    switch(uMsg)
    {
    case WM_INITDIALOG:
        // save the connection info pointer
        lpDPInfo = (LPDPLAYINFO) lParam;

// store global window
ghServerWnd = hWnd;

// open account database
glpFile = OpenAccountDB(gszDatabaseName);
break;

    case WM_DESTROY:
// stop the timer
if (idTimer)
{
KillTimer(hWnd, idTimer);
idTimer = 0;
}

// close account database
if (glpFile)
CloseAccountDB(glpFile);

ghServerWnd = NULL;
        break;

// this is a user-defined message used to add strings to the log window
case WM_USER_ADDSTRING:
// get length of text in log window
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, WM_GETTEXTLENGTH,
   (WPARAM) 0, (LPARAM) 0);

// put selection at end
dwTextLen = SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_SETSEL,
   (WPARAM) dwTextLen, (LPARAM) dwTextLen);

// add string in lParam to log window
SendDlgItemMessage(hWnd, IDC_LOGEDIT, EM_REPLACESEL,
(WPARAM) FALSE, (LPARAM) lParam);
GlobalFreePtr((LPVOID) lParam);
break;

case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDCANCEL:
EndDialog(hWnd, FALSE);
            break;
}
break;
}

    // Allow for default processing
    return FALSE;
}

void ServerApplicationMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
  DPID idFrom, DPID idTo)
{
switch (lpMsg->dwType)
{
    case BALANCEREQUEST:

HandleBalanceRequest(lpDPInfo, (LPMSG_BALANCEREQUEST)lpMsg, idFrom);
        break;

    case SPINREQUEST:

HandleSpinRequest(lpDPInfo, (LPMSG_SPINREQUEST)lpMsg, idFrom);
        break;

    default:
        break;
}
}

void ServerSystemMessage(LPDPLAYINFO lpDPInfo, LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize,
 DPID idFrom, DPID idTo)
{
    // The body of each case is there so you can set a breakpoint and examine
    // the contents of the message received.
switch (lpMsg->dwType)
{
case DPSYS_CREATEPLAYERORGROUP:
        {
            LPDPMSG_CREATEPLAYERORGROUP lp = (LPDPMSG_CREATEPLAYERORGROUP) lpMsg;
        }
break;

case DPSYS_DESTROYPLAYERORGROUP:
        {
            LPDPMSG_DESTROYPLAYERORGROUP lp = (LPDPMSG_DESTROYPLAYERORGROUP)lpMsg;
        }
break;

case DPSYS_ADDPLAYERTOGROUP:
        {
            LPDPMSG_ADDPLAYERTOGROUP lp = (LPDPMSG_ADDPLAYERTOGROUP)lpMsg;
        }
break;

case DPSYS_DELETEPLAYERFROMGROUP:
        {
            LPDPMSG_DELETEPLAYERFROMGROUP lp = (LPDPMSG_DELETEPLAYERFROMGROUP)lpMsg;
        }
break;

case DPSYS_SESSIONLOST:
        {
            LPDPMSG_SESSIONLOST lp = (LPDPMSG_SESSIONLOST)lpMsg;
        }
break;

case DPSYS_HOST:
        {
            LPDPMSG_HOSTlp = (LPDPMSG_HOST)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPDATA:
        {
            LPDPMSG_SETPLAYERORGROUPDATA lp = (LPDPMSG_SETPLAYERORGROUPDATA)lpMsg;
        }
break;

case DPSYS_SETPLAYERORGROUPNAME:
        {
            LPDPMSG_SETPLAYERORGROUPNAME lp = (LPDPMSG_SETPLAYERORGROUPNAME)lpMsg;
        }
break;

case DPSYS_SECUREMESSAGE:
        {
            LPDPMSG_SECUREMESSAGE lp = (LPDPMSG_SECUREMESSAGE)lpMsg;

ServerApplicationMessage(lpDPInfo, (LPDPMSG_GENERIC) lp->lpData, lp->dwDataSize,
  lp->dpIdFrom, idTo);

        }
break;
}
}

HRESULT HandleBalanceRequest(LPDPLAYINFO lpDPInfo, LPMSG_BALANCEREQUEST lpBalance, DPID idTo)
{
    MSG_BALANCERESPONSEMsg;
LPDPACCOUNTDESClpAccountDesc = NULL;
ACCOUNTINFOAccountInfo;
HRESULThr;

// create balance response message
ZeroMemory(&Msg, sizeof(MSG_BALANCERESPONSE));
Msg.dwType = BALANCERESPONSE;

// get account description for this player
hr = GetAccountDesc(lpDPInfo, idTo, &lpAccountDesc);
    if FAILED(hr)
goto FAILURE;

// get account information from database using account ID
if ((glpFile == NULL) ||
(!QueryAccount(glpFile, lpAccountDesc->lpszAccountIDA, &AccountInfo)))
{
hr = DPERR_ACCESSDENIED;
goto FAILURE;
}

// return balance from database
Msg.dwBalance = AccountInfo.dwBalance;

FAILURE:
Msg.hr = hr;

if FAILED(Msg.hr)
LogRequest("Balance request for \"%s\" failed: 0x%08X\r\n", lpAccountDesc, Msg.hr);
else
LogRequest("Balance request for \"%s\" returned $%d\r\n", lpAccountDesc, Msg.dwBalance);

if (lpAccountDesc)
GlobalFreePtr(lpAccountDesc);

// send the message
    return (lpDPInfo->lpDirectPlay3A->Send(lpDPInfo->dpidPlayer,
idTo, SENDFLAGS(lpDPInfo->bIsSecure),
&Msg, sizeof(MSG_BALANCERESPONSE)));
}

HRESULT HandleSpinRequest(LPDPLAYINFO lpDPInfo, LPMSG_SPINREQUEST lpSpin, DPID idTo)
{
    MSG_SPINRESPONSEMsg;
LPDPACCOUNTDESClpAccountDesc = NULL;
ACCOUNTINFOAccountInfo;
DWORDi;
HRESULThr;

// create spin response message
    ZeroMemory(&Msg, sizeof(MSG_SPINRESPONSE));
    Msg.dwType = SPINRESPONSE;

// get account description for this player
hr = GetAccountDesc(lpDPInfo, idTo, &lpAccountDesc);
    if FAILED(hr)
goto FAILURE;

// get account information from database using account ID
if ((glpFile == NULL) ||
(!QueryAccount(glpFile, lpAccountDesc->lpszAccountIDA, &AccountInfo)))
{
hr = DPERR_ACCESSDENIED;
goto FAILURE;
}

// bet exceeds balance in database
if (lpSpin->dwAmountBet > AccountInfo.dwBalance)
{
hr = DPERR_UNAVAILABLE;
goto FAILURE;
}

// generate new slot settings
for (i = 0; i < NUMWHEELS; i++)
Msg.dwIndex[i] = ((DWORD)rand()) % SLOTSPERWHEEL;

// determine amount won or lost
Msg.dwAmountWonOrLost = GetAmountWonOrLost(lpSpin->dwAmountBet, Msg.dwIndex);

// update account info in database for this player
AccountInfo.dwBalance += Msg.dwAmountWonOrLost;

if (!UpdateAccount(glpFile, lpAccountDesc->lpszAccountIDA, &AccountInfo))
{
hr = DPERR_ACCESSDENIED;
goto FAILURE;
}

// send new balance back
    Msg.dwBalance = AccountInfo.dwBalance;

FAILURE:
Msg.hr = hr;

if FAILED(Msg.hr)
LogRequest("Spin request for \"%s\" failed: 0x%08X\r\n", lpAccountDesc, Msg.hr);
else
LogRequest("Spin request for \"%s\" returned $%d\r\n", lpAccountDesc, Msg.dwAmountWonOrLost);

if (lpAccountDesc)
GlobalFreePtr(lpAccountDesc);

// send the message
    return (lpDPInfo->lpDirectPlay3A->Send(lpDPInfo->dpidPlayer,
idTo, SENDFLAGS(lpDPInfo->bIsSecure),
&Msg, sizeof(MSG_SPINRESPONSE)));
}

void LogRequest(LPSTR lpszFormat, LPDPACCOUNTDESC lpAccountDesc, DWORD dwValue)
{
LPSTRlpszStr;
LPSTRlpszAccountID;

// make sure we have an account ID
if (lpAccountDesc == NULL)
lpszAccountID = "unknown";
else
lpszAccountID = lpAccountDesc->lpszAccountIDA;

// create a buffer for the output string, account string and a numeric value
lpszStr = (LPSTR) GlobalAllocPtr(GHND, strlen(lpszFormat) + strlen(lpszAccountID) + 10);
if (lpszStr == NULL)
return;

// format the string to log
wsprintf(lpszStr, lpszFormat, lpszAccountID, dwValue);

// log it - main wnd proc will dispose of the string
PostMessage(ghServerWnd, WM_USER_ADDSTRING, (WPARAM) 0, (LPARAM) lpszStr);
}

LONG GetAmountWonOrLost(DWORD dwAmountBet, DWORD dwIndex[])
{
LONG    nMultiplier;

// check for jackpot
if ((dwIndex[0] == INDEX_JACKPOT) &&
(dwIndex[1] == INDEX_JACKPOT) &&
(dwIndex[2] == INDEX_JACKPOT))
{
nMultiplier = 100;
}

// three in a row
else if ((dwIndex[0] == dwIndex[1]) &&
 (dwIndex[1] == dwIndex[2]))
{
nMultiplier = 25;
}

// first two match
else if (dwIndex[0] == dwIndex[1])
{
nMultiplier = 5;
}

// you lose!
else
nMultiplier = -1;

// any spoiler and you lose
if ((dwIndex[0] == INDEX_SPOILER) ||
(dwIndex[1] == INDEX_SPOILER) ||
(dwIndex[2] == INDEX_SPOILER))
{
nMultiplier = -1;
}

// return amount won or lost
return (dwAmountBet * nMultiplier);
}

FILE* OpenAccountDB(LPSTR lpszDBName)
{
FILE*lpFile;

lpFile = fopen(lpszDBName, "r+b");

return (lpFile);
}

void CloseAccountDB(FILE *lpFile)
{
fclose(lpFile);
}

BOOL GetRecord(FILE *lpFile, LPSTR lpszKey, LPSTR lpszRecord)
{
rewind(lpFile);
while (fgets(lpszRecord, MAX_RECORD_LENGTH, lpFile))
{
_strupr(lpszRecord);
if (!strncmp(lpszRecord, lpszKey, strlen(lpszKey)))
return (TRUE);
}

return (FALSE);
}

BOOL PutRecord(FILE *lpFile, LPSTR lpszKey, LPSTR lpszRecord)
{
CHARszLine[MAX_RECORD_LENGTH];
DWORDdwRecordIndex;

rewind(lpFile);
dwRecordIndex = 0;
while (fgets(szLine, MAX_RECORD_LENGTH, lpFile))
{
_strupr(szLine);
if (!strncmp(szLine, lpszKey, strlen(lpszKey)))
{
fseek(lpFile, dwRecordIndex, SEEK_SET);
fputs(lpszRecord, lpFile);
return (TRUE);
}
dwRecordIndex += strlen(szLine);
}

return (FALSE);
}

BOOL QueryAccount(FILE *lpFile, LPSTR lpszAccountID, LPACCOUNTINFO lpAccountInfo)
{
    CHARszBuffer[MAX_RECORD_LENGTH];
    CHAR*lpToken;

    if (!GetRecord(lpFile, lpszAccountID, szBuffer))
    {
        return FALSE;
    }

    lpToken = strtok(szBuffer, ",");
    if (lpToken)
    {
        lpToken = strtok(NULL, "\n");
        if (lpToken)
        {
            lpAccountInfo->dwBalance = atoi(lpToken);
        }
    }

    return TRUE;
}

BOOL UpdateAccount(FILE *lpFile, LPSTR lpszAccountID, LPACCOUNTINFO lpAccountInfo)
{
    CHARszBuffer[MAX_RECORD_LENGTH];

    if (!GetRecord(lpFile, lpszAccountID, szBuffer))
    {
        return FALSE;
    }

sprintf(szBuffer, "%s,%8d", lpszAccountID, lpAccountInfo->dwBalance);
return (PutRecord(lpFile, lpszAccountID, szBuffer));
}

HRESULT GetSecureAccountDesc(LPDPLAYINFO lpDPInfo, DPID idPlayer, LPDPACCOUNTDESC *lplpAccountDesc)
{
LPDPACCOUNTDESClpAccountDesc = NULL;
DWORDdwAccountDescSize;
HRESULThr;

// get size of account description
hr = lpDPInfo->lpDirectPlay3A->GetPlayerAccount(idPlayer, 0,
NULL, &dwAccountDescSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpAccountDesc = (LPDPACCOUNTDESC) GlobalAllocPtr(GHND, dwAccountDescSize);
if (lpAccountDesc == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the account description for this player
hr = lpDPInfo->lpDirectPlay3A->GetPlayerAccount(idPlayer, 0,
lpAccountDesc, &dwAccountDescSize);
if FAILED(hr)
goto FAILURE;

// return account description
_strupr(lpAccountDesc->lpszAccountIDA);
*lplpAccountDesc = lpAccountDesc;
lpAccountDesc = NULL;

FAILURE:
if (lpAccountDesc)
GlobalFreePtr(lpAccountDesc);

return (hr);
}

HRESULT GetUnsecureAccountDesc(LPDPLAYINFO lpDPInfo, DPID idPlayer, LPDPACCOUNTDESC *lplpAccountDesc)
{
LPDPACCOUNTDESClpAccountDesc = NULL;
DWORDdwAccountDescSize;
LPDPNAMElpName;
HRESULThr;

// get size of player name
hr = lpDPInfo->lpDirectPlay3A->GetPlayerName(idPlayer,
NULL, &dwAccountDescSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpAccountDesc = (LPDPACCOUNTDESC) GlobalAllocPtr(GHND, sizeof(DPACCOUNTDESC) + dwAccountDescSize);
if (lpAccountDesc == NULL)
{
hr = DPERR_OUTOFMEMORY;
goto FAILURE;
}

// get the player name
lpName = (LPDPNAME) (((LPSTR)lpAccountDesc) + sizeof(DPACCOUNTDESC));
hr = lpDPInfo->lpDirectPlay3A->GetPlayerName(idPlayer,
lpName, &dwAccountDescSize);
if FAILED(hr)
goto FAILURE;

// return account description
lpAccountDesc->lpszAccountIDA = lpName->lpszShortNameA;
_strupr(lpAccountDesc->lpszAccountIDA);
*lplpAccountDesc = lpAccountDesc;
lpAccountDesc = NULL;

FAILURE:
if (lpAccountDesc)
GlobalFreePtr(lpAccountDesc);

return (hr);
}

HRESULT GetAccountDesc(LPDPLAYINFO lpDPInfo, DPID idPlayer, LPDPACCOUNTDESC *lplpAccountDesc)
{
HRESULThr;

if (lpDPInfo->bIsSecure)
{
hr = GetSecureAccountDesc(lpDPInfo, idPlayer, lplpAccountDesc);
}
else
{
hr = GetUnsecureAccountDesc(lpDPInfo, idPlayer, lplpAccountDesc);
}

return (hr);
}


README.TXT

Reality Lab / DirectSound 3D Viewer Sample

DS3DVIEW.EXE

This sample is a simple extension of the 3D object viewer sample that shipped
with DirectX 2.  It allows you to attach sounds to objects and move them around
a bit in 3D.

Viewer Notes:
-------------
The initial scene has a light source in the top right. To see this the view
can be moved backwards and forwards using the T and R keys. Additional objects
can be loaded from the file menu, and new light sources can be added from the
lights menu.  Objects can be rotated with the left mouse button and dragged
with the right.

The last object that was rotated or dragged is the current selection.

The device quality can be altered using the Renderer menu. Using this menu the
type of device used can also be switched from ramp to RGB. When using the RGB
device the colour of lights in the scene can be changed in the same way as you
change the colour of objects. In 256 colour mode it is advisable to dither an
RGB device for best results.

Additional keyboard controls

T               Forwards.
R               Back.
Z               Move current selection forwards.
X               Move current selection back.
G               Gouraud shade
F               Flat Shade
D               Dither toggle.
Delete  Delete the current selection.

Sound Notes:
------------

To attach a sound to an object, select the object and then select the "Attach Sound"
command from the Sound menu.  You can then select a WAV file to load.  To hear the
sound, select "Play Sound Once" or "Play Sound Looping."  To hear some neat effects,
try "Orbit" which will orbit the object/sound around the listener, or "Bullet,"
which will shoot the object/sound past the listener's head.

Properties for the sound or the listener can also be modified using the appropriate
menu commands and dialogs.


DSUTIL3D.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil3d.c
 *  Content:    Routines for dealing with sounds from resources -- this
 *                version has been modified and extended somewhat from the
 *                version found in the SAMPLES\MISC directory.
 *
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "dsound.h"

typedef struct
{
    BYTE *pbWaveData;               // pointer into wave resource (for restore)
    DWORD cbWaveSize;               // size of wave data (for restore)
    int iAlloc;                     // number of buffers.
    int iCurrent;                   // current buffer
    IDirectSoundBuffer* Buffers[1]; // list of buffers

} SNDOBJ, *HSNDOBJ;

#define _HSNDOBJ_DEFINED
#include "dsutil3d.h"

static const char c_szWAV[] = "WAV";

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer - Loads a .WAV into a sound buffer and returns the sound buffer
//
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName)
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};
    BYTE *pbWaveData;
    void *pvBase;

    dsBD.dwSize = sizeof(dsBD);
    dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY
                        | DSBCAPS_LOCSOFTWARE;

    if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
    {
        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
    } else if (DSGetWaveFile(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData,
                        &dsBD.dwBufferBytes, &pvBase))
    {
        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
UnmapViewOfFile (pvBase);
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSLoad3DSoundBuffer - Loads a .WAV into a 3D sound buffer and returns the sound buffer
//
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *DSLoad3DSoundBuffer(IDirectSound *pDS, LPCTSTR lpName)
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};
    BYTE *pbWaveData;
    void *pvBase;

    dsBD.dwSize = sizeof(dsBD);
    dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRL3D | DSBCAPS_CTRLVOLUME
                        | DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE
                        | DSBCAPS_STICKYFOCUS;

    if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
    {
        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
    } else if (DSGetWaveFile(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData,
                        &dsBD.dwBufferBytes, &pvBase))
    {
        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
UnmapViewOfFile (pvBase);
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName)
{
    BOOL result=FALSE;
    BYTE *pbWaveData;
    DWORD cbWaveSize;
    void *pvBase;

    if (DSGetWaveResource(NULL, lpName, NULL, &pbWaveData, &cbWaveSize))
    {
        if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)) &&
            DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
{
    result = TRUE;
        }
    } else if( DSGetWaveFile(NULL, lpName, NULL, &pbWaveData, &cbWaveSize, &pvBase))
    {
        if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)) &&
            DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
{
    result = TRUE;
        }
        UnmapViewOfFile (pvBase);
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pcbWaveSize)
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    void * pvRes;

    if (((hResInfo = FindResource(hModule, lpName, c_szWAV)) != NULL) &&
        ((hResData = LoadResource(hModule, hResInfo)) != NULL) &&
        ((pvRes = LockResource(hResData)) != NULL) &&
        DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize))
    {
        return TRUE;
    }

    return FALSE;

}

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveFile
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveFile(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pcbWaveSize,
void **ppvBase)
{
    void *pvRes;
    HANDLE hFile, hMapping;

    *ppvBase = NULL;

    hFile = CreateFile (lpName, GENERIC_READ, FILE_SHARE_READ,
                    NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return FALSE;

    hMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hMapping == INVALID_HANDLE_VALUE)
    {
        CloseHandle (hFile);
        return FALSE;
    }

    CloseHandle (hFile);

    pvRes = MapViewOfFile (hMapping, FILE_MAP_READ, 0, 0, 0);
    if (pvRes == NULL)
    {
        CloseHandle (hMapping);
        return FALSE;
    }

        CloseHandle (hMapping);

    if (DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize) == FALSE)
    {
        UnmapViewOfFile (pvRes);
        return FALSE;
    }

    *ppvBase = pvRes;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// SndObj fns
///////////////////////////////////////////////////////////////////////////////

SNDOBJ *SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent)
{
    SNDOBJ *pSO = NULL;
    LPWAVEFORMATEX pWaveHeader;
    BOOL fFound = FALSE;
    BYTE *pbData;
    UINT cbData;
    void * pvBase;

    if (DSGetWaveResource(NULL, lpName, &pWaveHeader, &pbData, &cbData))
        fFound = TRUE;
    if (DSGetWaveFile(NULL, lpName, &pWaveHeader, &pbData, &cbData, &pvBase))
    {
        fFound = TRUE;
        UnmapViewOfFile( pvBase );
    }
    if( fFound )
    {
        if (iConcurrent < 1)
            iConcurrent = 1;

        if ((pSO = (SNDOBJ *)LocalAlloc(LPTR, sizeof(SNDOBJ) +
    (iConcurrent-1) * sizeof(IDirectSoundBuffer *))) != NULL)
        {
            int i;

            pSO->iAlloc = iConcurrent;
            pSO->pbWaveData = pbData;
            pSO->cbWaveSize = cbData;
            pSO->Buffers[0] = DSLoadSoundBuffer(pDS, lpName);

    for (i=1; i<pSO->iAlloc; i++)
    {
if (FAILED(IDirectSound_DuplicateSoundBuffer(pDS,
                    pSO->Buffers[0], &pSO->Buffers[i])))
                {
    pSO->Buffers[i] = DSLoadSoundBuffer(pDS, lpName);
    if (!pSO->Buffers[i]) {
SndObjDestroy(pSO);
pSO = NULL;
break;
    }
}
            }
        }
    }

    return pSO;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void SndObjDestroy(SNDOBJ *pSO)
{
    if (pSO)
    {
        int i;

        for (i=0; i<pSO->iAlloc; i++)
        {
            if (pSO->Buffers[i])
            {
                IDirectSoundBuffer_Release(pSO->Buffers[i]);
                pSO->Buffers[i] = NULL;
            }
        }
        LocalFree((HANDLE)pSO);
    }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *SndObjGetFreeBuffer(SNDOBJ *pSO)
{
    IDirectSoundBuffer *pDSB;

    if (pSO == NULL)
        return NULL;

    if (pDSB = pSO->Buffers[pSO->iCurrent])
    {
        HRESULT hres;
        DWORD dwStatus;

        hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

        if (FAILED(hres))
            dwStatus = 0;

        if ((dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
        {
            if (pSO->iAlloc > 1)
            {
                if (++pSO->iCurrent >= pSO->iAlloc)
                    pSO->iCurrent = 0;

                pDSB = pSO->Buffers[pSO->iCurrent];
                hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

                if (SUCCEEDED(hres) && (dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
                {
                    IDirectSoundBuffer_Stop(pDSB);
                    IDirectSoundBuffer_SetCurrentPosition(pDSB, 0);
                }
            }
            else
            {
                pDSB = NULL;
            }
        }

        if (pDSB && (dwStatus & DSBSTATUS_BUFFERLOST))
        {
            if (FAILED(IDirectSoundBuffer_Restore(pDSB)) ||
                !DSFillSoundBuffer(pDSB, pSO->pbWaveData, pSO->cbWaveSize))
            {
                pDSB = NULL;
            }
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjPlay(SNDOBJ *pSO, DWORD dwPlayFlags)
{
    BOOL result = FALSE;

    if (pSO == NULL)
        return FALSE;

    if ((!(dwPlayFlags & DSBPLAY_LOOPING) || (pSO->iAlloc == 1)))
    {
IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(pSO);
if (pDSB != NULL) {
    result = SUCCEEDED(IDirectSoundBuffer_Play(pDSB, 0, 0, dwPlayFlags));
}
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjStop(SNDOBJ *pSO)
{
    int i;

    if (pSO == NULL)
        return FALSE;

    for (i=0; i<pSO->iAlloc; i++)
    {
        IDirectSoundBuffer_Stop(pSO->Buffers[i]);
        IDirectSoundBuffer_SetCurrentPosition(pSO->Buffers[i], 0);
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize)
{
    if (pDSB && pbWaveData && cbWaveSize)
    {
        LPVOID pMem1, pMem2;
        DWORD dwSize1, dwSize2;

        if (SUCCEEDED(IDirectSoundBuffer_Lock(pDSB, 0, cbWaveSize,
            &pMem1, &dwSize1, &pMem2, &dwSize2, 0)))
        {
            CopyMemory(pMem1, pbWaveData, dwSize1);

            if ( 0 != dwSize2 )
                CopyMemory(pMem2, pbWaveData+dwSize1, dwSize2);

            IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
            return TRUE;
        }
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,DWORD *pcbWaveSize)
{
    DWORD *pdw;
    DWORD *pdwEnd;
    DWORD dwRiff;
    DWORD dwType;
    DWORD dwLength;

    if (ppWaveHeader)
        *ppWaveHeader = NULL;

    if (ppbWaveData)
        *ppbWaveData = NULL;

    if (pcbWaveSize)
        *pcbWaveSize = 0;

    pdw = (DWORD *)pvRes;
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
        goto exit;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
        goto exit;      // not a WAV

    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);

    while (pdw < pdwEnd)
    {
        dwType = *pdw++;
        dwLength = *pdw++;

        switch (dwType)
        {
        case mmioFOURCC('f', 'm', 't', ' '):
            if (ppWaveHeader && !*ppWaveHeader)
            {
                if (dwLength < sizeof(WAVEFORMAT))
                    goto exit;      // not a WAV

                *ppWaveHeader = (WAVEFORMATEX *)pdw;

                if ((!ppbWaveData || *ppbWaveData) &&
                    (!pcbWaveSize || *pcbWaveSize))
                {
                    return TRUE;
                }
            }
            break;

        case mmioFOURCC('d', 'a', 't', 'a'):
            if ((ppbWaveData && !*ppbWaveData) ||
                (pcbWaveSize && !*pcbWaveSize))
            {
                if (ppbWaveData)
                    *ppbWaveData = (LPBYTE)pdw;

                if (pcbWaveSize)
                    *pcbWaveSize = dwLength;

                if (!ppWaveHeader || *ppWaveHeader)
                    return TRUE;
            }
            break;
        }

        pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
    }

exit:
    return FALSE;
}


DSUTIL3D.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

///////////////////////////////////////////////////////////////////////////////
//
// These Load functions will first look in the Win32 resources, and then they
//   will attempt to find the given name as a file on disk.
//
// DSLoadSoundBuffer    Loads an IDirectSoundBuffer from a Win32 resource in
//                      the current application, or alternately from disk.
//
// DSLoad3DSoundBuffer  Loads an IDirectSoundBuffer from a Win32 resource in
//                      the current application, and asks for DSBCAPS_CTRL3D.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the buffer.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns an IDirectSoundBuffer containing the wave data or NULL on error.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//      IDirectSoundBuffer *pDSB = DSLoadSoundBuffer(pDS, "Turtle");
//
//      if (pDSB)
//      {
//          IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//          /* ... */
//
//  or you can substitute the first line above as:
//     IDirectSoundBuffer *pDSB = DSLoadSoundBuffer(pDS, "turtle.wav");
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName);
IDirectSoundBuffer *DSLoad3DSoundBuffer(IDirectSound *pDS, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer  Reloads an IDirectSoundBuffer from a Win32 resource in
//                      the current application. normally used to handle
//                      a DSERR_BUFFERLOST error. Will also attempt to find
//                      the given name as a file, if there is no resource.
// Params:
//  pDSB        -- Pointer to an IDirectSoundBuffer to be reloaded.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns a BOOL indicating whether the buffer was successfully reloaded.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//  TryAgain:
//      HRESULT hres = IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//
//      if (FAILED(hres))
//      {
//          if ((hres == DSERR_BUFFERLOST) &&
//              DSReloadSoundBuffer(pDSB, "Turtle"))
//          {
//              goto TryAgain;
//          }
//          /* deal with other errors... */
//      }
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource    Finds a WAV resource in a Win32 module.
//
// Params:
//  hModule     -- Win32 module handle of module containing WAV resource.
//                 Pass NULL to indicate current application.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  ppWaveHeader-- Optional pointer to WAVEFORMATEX * to receive a pointer to
//                 the WAVEFORMATEX header in the specified WAV resource.
//                 Pass NULL if not required.
//
//  ppbWaveData -- Optional pointer to BYTE * to receive a pointer to the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
//  pdwWaveSize -- Optional pointer to DWORD to receive the size of the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
// Returns a BOOL indicating whether a valid WAV resource was found.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveFile    Finds a WAV file on disk and uses a memory mapped file to
//                    get a memory image of the file.
//
// Params:
//  hModule     -- Win32 module handle of module containing WAV resource.
//                 Pass NULL to indicate current application.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  ppWaveHeader-- Optional pointer to WAVEFORMATEX * to receive a pointer to
//                 the WAVEFORMATEX header in the specified WAV resource.
//                 Pass NULL if not required.
//
//  ppbWaveData -- Optional pointer to BYTE * to receive a pointer to the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
//  pdwWaveSize -- Optional pointer to DWORD to receive the size of the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
//  ppvBase     -- Required out-parameter which receives the base-address of
//                 the file mapping.  This is needed for the caller to Unmap
//                 the file.
//
// Returns a BOOL indicating whether a valid WAV file was found and mapped.
// The caller is responsible for calling UnmapViewOfFile(*ppvBase) when they
// are done with the "memory" for the file.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveFile(HMODULE hModule, LPCTSTR lpName, WAVEFORMATEX **ppWaveHeader,
     BYTE **ppbWaveData, DWORD *pdwWaveSize, void **ppvBase);

///////////////////////////////////////////////////////////////////////////////
//
// HSNDOBJ             Handle to a SNDOBJ object.
//
//  SNDOBJs are implemented in dsutil as an example layer built on top
//      of DirectSound.
//
//      A SNDOBJ is generally used to manage individual
//      sounds which need to be played multiple times concurrently.  A
//      SNDOBJ represents a queue of IDirectSoundBuffer objects which
//      all refer to the same buffer memory.
//
//      A SNDOBJ also automatically reloads the sound resource when
//      DirectSound returns a DSERR_BUFFERLOST
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _HSNDOBJ_DEFINED
#define _HSNDOBJ_DEFINED
DECLARE_HANDLE32(HSNDOBJ);
#endif

///////////////////////////////////////////////////////////////////////////////
//
// SndObjCreate     Loads a SNDOBJ from a Win32 resource in
//    the current application.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the SNDOBJ.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  iConcurrent -- Integer representing the number of concurrent playbacks of
//                 to plan for.  Attempts to play more than this number will
//                 succeed but will restart the least recently played buffer
//                 even if it is not finished playing yet.
//
// Returns an HSNDOBJ or NULL on error.
//
// NOTES:
//      SNDOBJs automatically restore and reload themselves as required.
//
///////////////////////////////////////////////////////////////////////////////
HSNDOBJ SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjDestroy  Frees a SNDOBJ and releases all of its buffers.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to free.
//
///////////////////////////////////////////////////////////////////////////////
void SndObjDestroy(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjPlayPlays a buffer in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
//  dwPlayFlags -- Flags to pass to IDirectSoundBuffer::Play.  It is not
//                 legal to play an SndObj which has more than one buffer
//                 with the DSBPLAY_LOOPING flag.  Pass 0 to stop playback.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjPlay(HSNDOBJ hSO, DWORD dwPlayFlags);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjStopStops one or more buffers in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjStop(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjGetFreeBuffer    returns one of the cloned buffers that is
//    not currently playing
//
// Params:
//  hSO -- Handle to a SNDOBJ
//
// NOTES:
//  This function is provided so that callers can set things like pan etc
//  before playing the buffer.
//
// EXAMPLE:
//  ...
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *SndObjGetFreeBuffer(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// helper routines
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD dwWaveSize);
BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

#ifdef __cplusplus
}
#endif


FILE.CPP

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
 *
 *  File: file.cpp
 *
 *  DESCRIPTION: Functionality to let users pick files and return their
 *     names to the object viewer (one for XOF, one for WAV)
 *
 ***************************************************************************/

#include "d3drmwin.h"
#include "resource.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>

/*
** Lets the user select a new geometry file to load and returns the full path and filename
*/

char* OpenNewFile( HWND hwnd )
{
    static char file[256];
    static char fileTitle[256];
    static char filter[] = "Geometry files (*.x)\0*.x\0"
                           "All Files (*.*)\0*.*\0";
    OPENFILENAME ofn;

    strcpy( file, "");
    strcpy( fileTitle, "");

// Set up the OPENFILENAME structure
    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hwnd;
#ifdef WIN32
    ofn.hInstance         = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);
#else
    ofn.hInstance         = (HINSTANCE) GetWindowWord(hwnd, GWW_HINSTANCE);
#endif
    ofn.lpstrFilter       = filter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter    = 0L;
    ofn.nFilterIndex      = 1L;
    ofn.lpstrFile         = file;
    ofn.nMaxFile          = sizeof(file);
    ofn.lpstrFileTitle    = fileTitle;
    ofn.nMaxFileTitle     = sizeof(fileTitle);
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrTitle        = "Open a File";
    ofn.nFileOffset       = 0;
    ofn.nFileExtension    = 0;
    ofn.lpstrDefExt       = "*.x";
    ofn.lCustData         = 0;

    ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

// Call windows functionality to make it happen!
    if (GetOpenFileName(&ofn))
        return (char*)ofn.lpstrFile;
    else
        return NULL;
}

/*
** Lets the user select a new .WAV file to load and returns it's full path and filename
*/

char* OpenNewSoundFile( HWND hwnd )
{
    static char file[256];
    static char fileTitle[256];
    static char filter[] = "Sound files (*.wav)\0*.wav\0"
                           "All Files (*.*)\0*.*\0";
    OPENFILENAME ofn;

    strcpy( file, "");
    strcpy( fileTitle, "");

    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hwnd;
#ifdef WIN32
    ofn.hInstance         = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);
#else
    ofn.hInstance         = (HINSTANCE) GetWindowWord(hwnd, GWW_HINSTANCE);
#endif
    ofn.lpstrFilter       = filter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter    = 0L;
    ofn.nFilterIndex      = 1L;
    ofn.lpstrFile         = file;
    ofn.nMaxFile          = sizeof(file);
    ofn.lpstrFileTitle    = fileTitle;
    ofn.nMaxFileTitle     = sizeof(fileTitle);
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrTitle        = "Open a File";
    ofn.nFileOffset       = 0;
    ofn.nFileExtension    = 0;
    ofn.lpstrDefExt       = "*.wav";
    ofn.lCustData         = 0;

    ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    if (GetOpenFileName(&ofn))
        return (char*)ofn.lpstrFile;
    else
        return NULL;
}


FILE.H

#ifndef ___VIEWER_FILE_OPENERS
#define ___VIEWER_FILE_OPENERS

char* OpenNewFile(HWND);
char* OpenNewSoundFile(HWND);

#endif


DS3DVI.H

/************************************************************************************************************

  Direct3DRM and DirectSound 3D interface designed for the Viewer Sample Application

  (c) 1996 Microsoft Corporation

************************************************************************************************************/

#ifndef ___DS3DVIEWER_INTERNAL
#define ___DS3DVIEWER_INTERNAL

// "Internal" structure definitions and other include information used by
// the DS3DVIEW application.  We put this stuff here so other modules can
// access it without great pain.

// Info for the 3D3RM  (RL info)
typedef struct _AppInfo
{
        // The parent frame
        LPDIRECT3DRMFRAME scene;
        // The frame for the camera (a child of the scene)
        LPDIRECT3DRMFRAME camera;

        // Device for Windows
    LPDIRECT3DRMDEVICE dev;
        // Defines how the 3D scene is rendered into the 2D window
    LPDIRECT3DRMVIEWPORT view;

        // Defines which color model (RGB/Ramp) is used for rendering - used when creating a device
        //  from the RL api
    D3DRMCOLORMODEL model;

        // Whether or not the window is minimized (modified by the resizing handler)
        BOOL bMinimized;
} AppInfo;

/*
** Error Checking code
*/
BOOL D3DRM_SUCCEED(HRESULT result, int force_critical = -1, char* info = NULL);
BOOL DS3D_SUCCEED(HRESULT result, int force_critical = -1, char* info = NULL);

/****** DirectSound3D Helper Stuff ************************************/

// Simple thing to copy vectors to each other independant of type
#define RLV_TO_DSV(in,out) out.x = in.x;\
                                                   out.y = in.y;\
                                                   out.z = in.z;

#endif


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by viewer.rc
//
#define MENU_FILE_ABOUT                 1
#define MENU_FILE_OPEN                  2
#define MENU_FILE_EXIT                  3
#define MENU_FILE_NEW_WINDOW            4
#define MENU_EDIT_CUT                   100
#define MENU_EDIT_COPY                  101
#define MENU_EDIT_PASTE                 102
#define MENU_EDIT_DELETE                103
#define MENU_EDIT_COLOUR                104
#define MENU_EDIT_BOXES                 105
#define MENU_QUALITY_LIGHTING           200
#define MENU_QUALITY_POINTS             201
#define MENU_QUALITY_WIREFRAME          202
#define MENU_QUALITY_SOLID              203
#define MENU_QUALITY_FLAT               204
#define MENU_QUALITY_GOURAUD            205
#define MENU_QUALITY_PHONG              206
#define MENU_MODEL_MONO                 207
#define MENU_MODEL_RGB                  208
#define MENU_DITHER                     209
#define MENU_TEXTURE_FILTERING          210
#define MENU_LIGHT_DIRECTIONAL          301
#define MENU_LIGHT_PARALLEL_POINT       302
#define MENU_LIGHT_POINT                303
#define MENU_LIGHT_SPOT                 304
#define MENU_SOUND_ATTACHSOUND          401
#define MENU_SOUND_REMOVESOUND          402
#define MENU_SOUND_REMOVEALLSOUNDS      403
#define MENU_SOUND_PLAYSOUND            404
#define MENU_SOUND_PLAYSOUNDLOOPING     405
#define MENU_SOUND_STOPSOUND            406
#define MENU_SOUND_STOPALLSOUNDS        407
#define MENU_MOTION_ORBITSELECTEDOBJECT 501
#define MENU_MOTION_BULLETSELECTEDOBJECT 502
#define IDC_ORBIT                       1005
#define IDC_BULLET                      1006
#define IDC_EDIT1                       1007
#define IDC_CONEANGLE                   1007
#define IDC_EDIT2                       1008
#define IDC_CONEOUTSIDEVOLUME           1008
#define IDC_EDIT3                       1009
#define IDC_MINIMUMDISTANCE             1009
#define IDC_EDIT4                       1011
#define IDC_MAXIMUMDISTANCE             1011
#define IDC_DISTANCEFACTOR              1012
#define IDC_ROLLOFFFACTOR               1013
#define IDC_DOPPLERFACTOR               1014
#define MENU_SOUND_GLOBALPROPERTIES     40015
#define MENU_SOUND_SELECTEDSOUNDPROPERTIES 40016
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        106
#define _APS_NEXT_COMMAND_VALUE         40019
#define _APS_NEXT_CONTROL_VALUE         1015
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


RLDS3D.CPP

/************************************************************************************************************

  Direct3DRM and DirectSound3D interface designed for the Viewer Sample Application

  (c) 1996 Microsoft Corporation

************************************************************************************************************/

#include "rlds3d.h"
#include "ds3dvi.h"     // "Internal" include stuff, like some structures

// For resource symbols
#include "resource.h"

#include "d3drmwin.h"

#include <windows.h>
#include <windowsx.h>

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <direct.h>

#include <objbase.h>
#include <initguid.h>

#include "rodcone.h"
#include "file.h"

#include <mmsystem.h>
#include "dsound.h"
#include "dsutil3d.h"

/*
** This is an interface between the viewer and the RL/DS3D APIs which provides the
**   functionality required by the viewer in simplified form.
**
** Note: This is not an object-oriented API since there should only be need for one
**   copy at a time and this way C++ to C conversions should be fairly easy
**
** DESCRIPTION: The user interfaces the 3D world and objects therein by selecting an item
**   with the mouse (on the screen) and performing operations on that item.  The user will
**   also be able to perform operations on the camera and a few global operations applied
**   to all objects.
**
** USAGE: Create the interface using RLDS3D_Initialize() (returns FALSE if not created)
**   and remove it using RLDS3D_Deinitialize().  The Render() functionality draws the world
**   on the screen.  Other functionality as described.
*/

/*
***********************************  GLOBALS  *************************************
*/

/*
** VIEWER LPDIRECT3DRMFRAME APPDATA STRUCTURE
**
** Pointers to this class are placed (as necessary) in the appdata fields for LPDIRECT3DRMFRAMEs.  Any
**   necessary data to be attached to frames should be added to this class
*/

class FRAMEAPPDATA {
public:
        // Sound buffer interfaces (the standard one and the 3D interface)
        LPDIRECTSOUNDBUFFER Sound;
        LPDIRECTSOUND3DBUFFER i3DSound;

        // Whether or not this frame is currently in orbit (and thus contained in a temporary frame)
        BOOL bOrbiting;
        // Whether or not this frame is a bullet (and thus has a bullet callback and dialog)
        BOOL bBullet;
        // A window handle associated with the orbiting/bullet state which controls speed, etc.
        HWND hDlg;

        FRAMEAPPDATA() : Sound(NULL), i3DSound(NULL), bOrbiting(FALSE), bBullet(FALSE), hDlg(0) {};
        ~FRAMEAPPDATA() {
                if (i3DSound) i3DSound->Release();
                if (Sound) Sound->Release();
        };
};
typedef FRAMEAPPDATA* LPFRAMEAPPDATA;

// Interface to the D3DRM functions
LPDIRECT3DRM lpD3DRM = NULL;

// Pointer to the Directsound API
LPDIRECTSOUND lpDS = NULL;

// Clipper for the DDraw surface
LPDIRECTDRAWCLIPPER lpDDClipper = NULL;

// Handle to the API's parent window (passed in Initialize)
HWND    hwndParent;

// Handle to our instance
HINSTANCE hinst;

AppInfo* info;

/*
** Globals used for editing what is in the D3DRM world
*/

// Selected frame (contains visual currently selected by user)
LPDIRECT3DRMFRAME sFrame = NULL;

// Whether or not editing box should be shown around the selected object
BOOL showBoxes = FALSE;

// Currently selected visual (if one is selected)
static LPDIRECT3DRMMESHBUILDER sVisual = NULL;

// Currently selected light (if one is selected)
static LPDIRECT3DRMLIGHT sLight = NULL;

// Box/Speaker cone to go around the selected item (if boxes are on and if there IS a speaker)
static LPDIRECT3DRMMESH selectionBox = NULL;
static LPDIRECT3DRMMESH selectionSpeaker = NULL;

// Clipboard frame and visual allowing cutting and pasting
LPDIRECT3DRMFRAME clipboardFrame = NULL;
LPDIRECT3DRMVISUAL clipboardVisual = NULL;

/*
** Globals for DirectSound3D
*/

// Listener information (connected to the camera)
LPDIRECTSOUND3DLISTENER lp3DListenerInfo;

// Primary Buffer (we don't REALLY need a pointer to it but we keep it just in case)
LPDIRECTSOUNDBUFFER lpDSBuff;

/*
************************  Internal function declarations  ****************************
*/

// These are not part of the interface and are just used by the RLDS3D functions
static BOOL CreateDevice(HWND win, AppInfo* info);
HRESULT __cdecl loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex);
char* LSTRRCHR( const char* lpString, int bChar );
static void PlaceMesh(LPDIRECT3DRMMESHBUILDER mesh, AppInfo *info);
static BOOL CreateScene(AppInfo* info);
static BOOL RebuildDevice(HWND win, AppInfo* info, int width, int height);
static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX*);
void SelectVisual(LPDIRECT3DRMMESHBUILDER visual, LPDIRECT3DRMFRAME frame);
int ChooseNewColor(HWND, D3DCOLOR*);
void RemoveSoundRecord(LPDIRECT3DRMFRAME owner);
void releaseSoundCallback(LPDIRECT3DRMFRAME frame);
void StopOrbiting(LPDIRECT3DRMFRAME stopme);
void UpdateConeVisual(void);

/*
***********************  Error checker functions  ************************************
*/

/*
** These are wrapped around DS or D3DRM calls to appropriately catch errors and handle them.
**
** The generalized cases are coded, callers can pass forced reactions to errors by using the force_critical variables
*/

/*
** In this code the general philosophy for when to use the viewer is when requesting anything from something outside the
**   program.  Thus, asking a frame which we know exists to accept an added visual doesn't get checked, but trying to create
**   a new frame (which tries to allocate memory) is checked in case we've run out of free memory.
*/

BOOL D3DRM_SUCCEED(HRESULT result, int force_critical, char* info) {
        if (result == D3DRM_OK) return TRUE;
        // Could be 0 for non-critical, 1 for non-critical but reports it, or 2 for critical (notify and quit)
        int priority = 0;
        char* error_string;
        switch (result) {
                case D3DRMERR_BADALLOC:                 error_string = "Out of memory";                                                         priority = 2; break;
                case D3DRMERR_BADDEVICE:                error_string = "Device is not compatible with renderer";        priority = 2; break;
                case D3DRMERR_BADFILE:                  error_string = "Data file is corrupt";                                          priority = 2; break;
                case D3DRMERR_BADMAJORVERSION:  error_string = "Bad DLL major version";                                         priority = 2; break;
                case D3DRMERR_BADMINORVERSION:  error_string = "Bad DLL minor version";                                         priority = 2; break;
                case D3DRMERR_BADOBJECT:                error_string = "Object expected in argument";                           priority = 2; break;
                case D3DRMERR_BADTYPE:                  error_string = "Bad argument type passed";                                      priority = 1; break;
                case D3DRMERR_BADVALUE:                 error_string = "Bad argument value passed";                                     priority = 1; break;
                case D3DRMERR_FACEUSED:                 error_string = "Face already used in a mesh";                           priority = 1; break;
                case D3DRMERR_FILENOTFOUND:             error_string = "File cannot be opened";                                         priority = 1; break;
                case D3DRMERR_NOTDONEYET:               error_string = "Unimplemented function called";                         priority = 1; break;
                case D3DRMERR_NOTFOUND:                 error_string = "Object not found in specified place";           priority = 1; break;
                case D3DRMERR_UNABLETOEXECUTE:  error_string = "Unable to carry out procedure";                         priority = 2; break;
                default:                                                error_string = "D3DRM Error: Unable to continue";                       priority = 2; break;
        }

        int ret;
        if (force_critical >= 0) priority = force_critical;
        if (priority == 1) {
            ret = MessageBox(hwndParent, error_string, "D3DRM Warning", MB_APPLMODAL|MB_ICONWARNING|MB_OK);
        }
        else if (priority == 2) {
            ret = MessageBox(hwndParent, error_string, "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
                PostMessage(hwndParent, WM_CLOSE,0,0);
        }
        return FALSE;
}

/*
** DS isn't vital for the viewer to run so error messages don't force a quit
*/

BOOL DS3D_SUCCEED(HRESULT result, int force_critical, char* info) {
        if (result == DS_OK) return TRUE;
        // Could be 0 for no message, 1 for non-critical (simply needs to report it), or 2 for critical (notify and quit)
        int priority = 0;
        char* error_string = NULL;
        switch (result) {
                case DSERR_ALLOCATED:                   error_string = "Requested resources already in use";                    priority = 1; break;
                case DSERR_ALREADYINITIALIZED:  error_string = "Object already initialized";                                    priority = 0; break;
                case DSERR_BADFORMAT:                   error_string = "Wave format not supported";                                             priority = 0; break;
                case DSERR_BUFFERLOST:                  error_string = "Buffer lost and must be restored";                              priority = 0; break;
                case DSERR_CONTROLUNAVAIL:              error_string = "Control requested not available";                               priority = 0; break;
                case DSERR_GENERIC:                             error_string = "Undetermined error";                                                    priority = 1; break;
                case DSERR_INVALIDCALL:                 error_string = "Invalid call for object's current state";               priority = 0; break;
                case DSERR_INVALIDPARAM:                error_string = "Invalid parameters passed to object";                   priority = 0; break;
                case DSERR_NOAGGREGATION:               error_string = "Object does not support aggregation";                   priority = 1; break;
                case DSERR_NODRIVER:                    error_string = "No sound driver available";                                             priority = 1; break;
//              case DSERR_NOINTERFACE:                 error_string = "Requested COM interface not available";                 priority = 0; break;
                case DSERR_OUTOFMEMORY:                 error_string = "Out of memory";                                                                 priority = 1; break;
                case DSERR_PRIOLEVELNEEDED:             error_string = "Caller does not have required priority level";  priority = 0; break;
//              case DSERR_UNINITIALIZED:               error_string = "DirectSound not initialized";                                   priority = 1; break;
                case DSERR_UNSUPPORTED:                 error_string = "Unsupported function called";                                   priority = 0; break;
                default:                                                error_string = "Undetermined error";                                                    priority = 1; break;
        }

        int ret;
        if (force_critical >= 0) priority = force_critical;
        if (priority == 1) {
            ret = MessageBox(hwndParent, error_string, "DS3D Warning", MB_APPLMODAL|MB_ICONWARNING|MB_OK);
        }
        else if (priority == 2) {
            ret = MessageBox(hwndParent, error_string, "DS3D Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
                PostMessage(hwndParent, WM_CLOSE,0,0);
        }
        return FALSE;
}

/*
************************  INITIALIZATION/DEINITIALIZATION ****************************
*/

/*
** Initialize will attach itself to the passed window.  Call initialize after
**  getting a handle for your window but before you display it.
**
** Initialize will initialize the D3DRM API and return false if it fails.  It will
**  also attempt to initialize a DirectSound3D API, but failing this does not
**  justify a failed Initialize (since the 3D sound isn't a necessary part of
**  the viewer)
*/

BOOL RLDS3D_Initialize(HWND hwndPW, HINSTANCE this_inst) {

        if (!hwndPW) return FALSE;

        hinst = this_inst;
        hwndParent = hwndPW;

        // D3DRM API INIT - we force non-critical warnings here because the initialization returns a boolean for success

        // Call Direct3DRMCreate to try to make a D3DRM object
        if (!D3DRM_SUCCEED(Direct3DRMCreate(&lpD3DRM), 1)) return FALSE;

if (!D3DRM_SUCCEED(DirectDrawCreateClipper(0, &lpDDClipper, NULL),1)) {
lpD3DRM->Release();
return FALSE;
}

        if (!D3DRM_SUCCEED(lpDDClipper->SetHWnd(0, hwndParent),1)) {
                lpDDClipper->Release();
                lpD3DRM->Release();
        return FALSE;
    }

    // Set up D3DRM information structure
    info = (AppInfo*) malloc(sizeof(AppInfo));
        if (!info) {
                lpDDClipper->Release();
                lpD3DRM->Release();
                return FALSE;
        }

    info->model = D3DCOLOR_MONO;

    // Calls our internal function to create a viewport and device and attach it to the window (CreateDevice in misc. functionality)
        // (also fills in the global information structure "info" for what D3DRM can do and is currently doing)
        if (CreateDevice(hwndParent, info) == FALSE) {
                lpDDClipper->Release();
                lpD3DRM->Release();
                return FALSE;
        }

        // DIRECTSOUND 3D INIT - note, if this fails the entire initialization is still OK.

        // Description for our primary buffer creation
        DSBUFFERDESC dsbd;

    int ret = IDRETRY;

        // Try to create the Directsound objects until we either do it, are told to ignore it, or are told to abort
        while (ret == IDRETRY) {
                // Create the directsound object
                if (DS3D_SUCCEED(DirectSoundCreate(NULL, &lpDS, NULL))) {
                        // Set cooperative level
                        if (DS3D_SUCCEED(lpDS->SetCooperativeLevel(hwndParent, DSSCL_PRIORITY))) {
                                // Create a primary buffer so we can query for a 3D Listener interface
                                memset(&dsbd, 0, sizeof(DSBUFFERDESC));
                                dsbd.dwSize = sizeof(DSBUFFERDESC);
                                dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D;
                                if (DS3D_SUCCEED(lpDS->CreateSoundBuffer(&dsbd, &lpDSBuff, NULL))) {

                                        // Make the primary 44.1 KHz so that it sounds better
                                        WAVEFORMATEX wfx;
                                        wfx.wFormatTag = WAVE_FORMAT_PCM;
                                        wfx.nChannels = 2;
                                        wfx.nSamplesPerSec = 44100;
                                        wfx.nAvgBytesPerSec = 44100*2*2;
                                        wfx.nBlockAlign = 4;
                                        wfx.wBitsPerSample = 16;
                                        wfx.cbSize = 0;
                                        lpDSBuff->SetFormat(&wfx);

                                        // Get the 3D listener information (error currently ignored)
                                        if (DS3D_SUCCEED(lpDSBuff->QueryInterface(IID_IDirectSound3DListener, (void**) &lp3DListenerInfo))) {
                                                lp3DListenerInfo->SetDopplerFactor(D3DVAL(100.0), DS3D_IMMEDIATE);
                                        }
                                        else {
                                                // Failed to get listener info
                                                lpDSBuff->Release();
                                                lpDS->Release();
                                                lpDS = NULL;
                                        }
                                }
                                else {
                                        // Failed to create a primary buffer
                                        lpDS->Release();
                                        lpDS = NULL;
                                }
                        }
                        else {
                                // Failed to set cooperative level
                                lpDS->Release();
                                lpDS = NULL;
                        }
                }

                // Warn that we could create the DirectSound object
                if (!lpDS) {
                        ret = MessageBox(hwndParent, "DirectSound 3D could not initialize", "Warning", MB_APPLMODAL|MB_ICONWARNING|MB_ABORTRETRYIGNORE);
                        if (ret == IDABORT) {
                                lpDDClipper->Release();
                                lpD3DRM->Release();
                                return FALSE;
    }
                }
                else ret = IDOK;
        }
        return TRUE;
}

/*
** Deinitializes
*/

void RLDS3D_Deinitialize() {
        // Releases the DSound interface... this is very important!
        if (lpDS != NULL) {
                lpDSBuff->Release();
                lpDS->Release();
        }
        info->dev->Release();
        lpDDClipper->Release();
        lpD3DRM->Release();
        free(info);
}

/*
********************************************  ADDING/REMOVING/EDITING OBJECTS  *********************************
*/

/*
** Loads XOF file into the RL world (with textures)
*/

void RLDS3D_LoadXOF(char* file) {
    if (!file) return;
LPDIRECT3DRMMESHBUILDER builder = NULL;
if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (builder->Load(file, NULL, D3DRMLOAD_FROMFILE, loadTextures, NULL) != D3DRM_OK) {
            MessageBox(hwndParent, "Unable to load file", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONEXCLAMATION|MB_OK);
builder->Release();
return;
}
PlaceMesh(builder, info);
builder->Release();
}
}

/*
** Sets/Gets whether or not boxes are shown around selected item
*/

BOOL RLDS3D_GetBoxes(void) {
        return showBoxes;
}

void RLDS3D_SetBoxes(BOOL new_val) {
        showBoxes = new_val;
        // Re-selects the currently selected visual so that the box is generated/destroyed around it
        RLDS3D_UpdateSelectionBox();
}

/*
** Updates the bounding box around the selected visual (this could be done using a render callback function to compare the
**   frame's scaling and transform functions instead)
*/

void RLDS3D_UpdateSelectionBox(void) {
        // When we select this visual, we destroy any existing box and create a new one around it if showBoxes is true.
        SelectVisual(sVisual, sFrame);
}

/*
** Deselects the currently selected 3D visual.
*/

void RLDS3D_DeselectVisual()
{
        // Removes the bounding box from around it if it's there
    if (sFrame && selectionBox) {
        sFrame->DeleteVisual(selectionBox);
        sFrame->DeleteVisual(selectionSpeaker);
    }

    sFrame = NULL;
    sLight = NULL;
    sVisual = NULL;
    selectionBox = NULL;
    selectionSpeaker = NULL;
}

/*
** Given coordinates it selects the first visual under those coordinates in the window's viewport
*/

void RLDS3D_FindAndSelectVisual(int x, int y, LPBOOL changed) {
    LPDIRECT3DRMVISUAL visual;
    LPDIRECT3DRMFRAME frame;
    LPDIRECT3DRMPICKEDARRAY picked;
    LPDIRECT3DRMFRAMEARRAY frames;
    LPDIRECT3DRMMESHBUILDER mesh;
    LPDIRECT3DRMVIEWPORT view = info->view;

LPDIRECT3DRMFRAME oldframe = sFrame;

    /*
     * Make sure we don't try to select the selection box of the current
     * selection.
     */
    RLDS3D_DeselectVisual();

    view->Pick(x, y, &picked);
    if (picked)
    {   if (picked->GetSize())
        {
// Get the top-level visual
            picked->GetPick(0, &visual, &frames, 0);
            // The frames that contain the visual are placed into a framearray in heiarchical order, take the
            //   last one (the one most closely associated with the visual)
            frames->GetElement(frames->GetSize() - 1, &frame);
            // We can only select meshes so we query the visual to make sure it is one
            if (D3DRM_SUCCEED(visual->QueryInterface(IID_IDirect3DRMMeshBuilder, (void **) &mesh), 0))
            {
                // If we're clicking on an orbiting frame then we need to stop it.
                StopOrbiting(frame);
                SelectVisual(mesh, frame);
                mesh->Release();
            }
            frame->Release();
            frames->Release();
            visual->Release();
        }
        picked->Release();
    }
if (changed) {
if (sFrame == oldframe) *changed = FALSE; else *changed = TRUE;
}
}

/*
** Cuts the current selection to the clipboard
*/

void RLDS3D_CutVisual()
{
    LPDIRECT3DRMFRAME frame;

    if (clipboardFrame)
    clipboardFrame->Release();

    if (sFrame)
    {   clipboardFrame = sFrame;
        clipboardVisual = sVisual;

        // If a 3D sound is attached, remove it (sounds not carried to clipboard)
RemoveSoundRecord(clipboardFrame);
RLDS3D_DeselectVisual();

        clipboardFrame->AddRef();
        clipboardFrame->GetParent(&frame);
        if (frame) {
            frame->DeleteChild(clipboardFrame);
            frame->Release();
        }
    }
}

/*
** Copies the current selection to the clipboard
*/

void RLDS3D_CopyVisual()
{
    LPDIRECT3DRMFRAME frame;

    if (sFrame)
    {
                // Things could really foul up if the clones aren't created so we make sure they are
        if (!D3DRM_SUCCEED(sFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &clipboardFrame))) {
                        // If we've failed, we must make sure the clipboard is empty!
                        clipboardVisual->Release();
                        clipboardVisual = NULL;
                        return;
                }

        if (!D3DRM_SUCCEED(sVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &clipboardVisual))) {
                        // If we've failed, we must make sure the clipboard is empty!
                        clipboardFrame->Release();
                        clipboardFrame = NULL;
                        return;
                }

                // If a 3D sound is attached, remove it (sounds not carried to clipboard)
                RemoveSoundRecord(clipboardFrame);

        clipboardFrame->AddVisual(clipboardVisual);
                clipboardVisual->Release();

        clipboardFrame->GetParent(&frame);
        if (frame) {
                        frame->DeleteChild(clipboardFrame);
                        frame->Release();
                }
    }
}

/*
** Pastes the current selection to the window
*/

void RLDS3D_PasteVisual()
{
    if (clipboardFrame)
    {
        LPDIRECT3DRMFRAME frame;
        LPDIRECT3DRMVISUAL visual;

        if (!D3DRM_SUCCEED(clipboardFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &frame))) return;
        if (!D3DRM_SUCCEED(clipboardVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &visual))) {
                        frame->Release();
                        return;
                }

        frame->AddVisual(visual);
        info->scene->AddChild(frame);
                visual->Release();
                frame->Release();
    }
}

/*
** Deletes the current selection from the world without copying to the clipboard
*/

void RLDS3D_DeleteVisual()
{
    if (sFrame) {
        LPDIRECT3DRMFRAME parent, frame;
        // Make a copy of the selected frame ('cause deselecting it will make it inaccessible)
        frame = sFrame;
        // If a 3D sound is attached, remove it
        RemoveSoundRecord(frame);

        // Deselect the frame (removes bounding box, etc, also sets sFrame to NULL)
        RLDS3D_DeselectVisual();
        if (frame->GetAppData()) delete (FRAMEAPPDATA*)frame->GetAppData();
frame->GetParent(&parent);
        if (parent) {
                parent->DeleteChild(frame);
                parent->Release();
        }
    }
}

/*
** Add a directional light
*/

void RLDS3D_AddDirectionalLight() {
    LPDIRECT3DRMMESHBUILDER builder;
    LPDIRECT3DRMLIGHT light;
    LPDIRECT3DRMFRAME frame;

    if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
                if (D3DRM_SUCCEED(builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
                        builder->SetQuality(D3DRMRENDER_UNLITFLAT);
                        if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
                                if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
                                        frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
                                        frame->AddVisual(builder);
                                        frame->AddLight(light);
                                        frame->Release();
                                }
                                light->Release();
                        }
                }
                builder->Release();
        }
}

/*
** Add a parallel point light
*/

void RLDS3D_AddParallelPointLight() {
    LPDIRECT3DRMMESHBUILDER builder;
    LPDIRECT3DRMLIGHT light;
    LPDIRECT3DRMFRAME frame;

    if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
            if (D3DRM_SUCCEED(builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
                        builder->SetQuality(D3DRMRENDER_UNLITFLAT);
                        builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2));
                        if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
                                if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
                                        frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
                                        frame->AddVisual(builder);
                                        frame->AddLight(light);
                                        frame->Release();
                                }
                                light->Release();
                        }
                }
                builder->Release();
        }
}

/*
** Add Point Light
*/

void RLDS3D_AddPointLight() {
    LPDIRECT3DRMMESHBUILDER builder;
    LPDIRECT3DRMLIGHT light;
    LPDIRECT3DRMFRAME frame;

    if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
                if (D3DRM_SUCCEED(builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
                        builder->SetQuality(D3DRMRENDER_UNLITFLAT);
                        builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2));
                        if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_POINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
                                if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
                                        frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
                                        frame->AddVisual(builder);
                                        frame->AddLight(light);
                                        frame->Release();
                                }
                                light->Release();
                        }
                }
                builder->Release();
        }
}

/*
** Add a spotlight
*/

void RLDS3D_AddSpotlight() {
    LPDIRECT3DRMMESHBUILDER builder;
    LPDIRECT3DRMLIGHT light;
    LPDIRECT3DRMFRAME frame;

        if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
            if (D3DRM_SUCCEED(builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
                        builder->SetQuality(D3DRMRENDER_UNLITFLAT);
                        if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
                                if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
                                        frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
                                        frame->AddVisual(builder);
                                        frame->AddLight(light);
                                        frame->Release();
                                }

light->Release();
                        }
                }
                builder->Release();
        }
}

/*
***********************************  OBJECT MOTION/SCALING/COLORING  ****************************************
*/

/*
** Chooses a new color and sets the selected object to it... handles the selection of the color, etc.
*/

void RLDS3D_SetSelColour(void) {
    if (!sFrame) return;
    LPDIRECT3DRMMESHBUILDER mesh;

    if (!D3DRM_SUCCEED(sVisual->QueryInterface(IID_IDirect3DRMMeshBuilder, (void**) &mesh),0)) return;

    if (sLight)
    {
        D3DCOLOR c = sLight->GetColor();

        if (ChooseNewColor(hwndParent, &c)) {
            mesh->SetColor(c);
            sLight->SetColor(c);
        }
    } else {
        D3DCOLOR c;

        if (mesh->GetFaceCount()) {
            LPDIRECT3DRMFACEARRAY faces;
            LPDIRECT3DRMFACE face;
            mesh->GetFaces(&faces);
            faces->GetElement(0, &face);
            c = face->GetColor();
            face->Release();
            faces->Release();
        } else
            c = D3DRMCreateColorRGB(D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));

        if (ChooseNewColor(hwndParent, &c))
            mesh->SetColor(c);
    }

    mesh->Release();
}

/*
** Moves the camera relative to itself by providing scalars to multiply against the CAMERA-RELATIVE unit vectors
**   forwards/up/right.
*/

void RLDS3D_SetCamVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right) {
        D3DVECTOR vDir, vUp, vRight;
        info->camera->GetOrientation(info->scene, &vDir, &vUp);
        // Cross the UP vector and the RIGHT vector to get the FORWARDS vector
        D3DRMVectorCrossProduct(&vRight, &vUp, &vDir);
        info->camera->SetVelocity(info->scene, vDir.x*forward + vUp.x*up + vRight.x*right,
                                   vDir.y*forward + vUp.y*up + vRight.y*right,
                                   vDir.z*forward + vUp.z*up + vRight.z*right,
   TRUE);
}

/*
** Rotates the camera around its three axis
**
** forward_axis is roll, up_axis is yaw, right_axis is pitch
*/

void RLDS3D_SetCamRotForward(D3DVALUE forward_axis) {
        info->camera->SetRotation(info->camera, 0.0f, 0.0f, 1.0f, forward_axis);
}

void RLDS3D_SetCamRotUp(D3DVALUE up_axis) {
        info->camera->SetRotation(info->camera, 0.0f, 1.0f, 0.0f, up_axis);
}

void RLDS3D_SetCamRotRight(D3DVALUE right_axis) {
        info->camera->SetRotation(info->camera, 1.0f, 0.0f, 0.0f, right_axis);
}

/*
** Scales the currently selected object by the scale factors...
*/

void RLDS3D_ScaleSelected(D3DVALUE sx, D3DVALUE sy, D3DVALUE sz) {
        if (!sFrame) return;
        if (!sVisual) return;
        RLDS3D_StopOrbitSelected();
        sVisual->Scale(sx, sy, sz);
        RLDS3D_UpdateSelectionBox();
}

/*
** Moves the currently selected object in the 3D world relative to the camera
*/

void RLDS3D_SetSelectedVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right) {
        if (!sFrame) return;
        RLDS3D_StopOrbitSelected();
        D3DVECTOR vDir, vUp, vRight;
        info->camera->GetOrientation(info->scene, &vDir, &vUp);
        D3DRMVectorCrossProduct(&vRight, &vUp, &vDir);
        sFrame->SetVelocity(info->scene, vDir.x*forward + vUp.x*up + vRight.x*right,
   vDir.y*forward + vUp.y*up + vRight.y*right,
   vDir.z*forward + vUp.z*up + vRight.z*right,
   TRUE);
}

/*
** Rotates the currently selected object relative to the camera's frame when passed
**   a vector for the axis and an angle of rotation.
** This is useful because the AXIS which is specified is relative to what appears on the screen with it's origin at the camera
**   (ie: (0,0,1) will be an axis straight into the screen and thus things will spin around the centre of the screen)
*/

void RLDS3D_SetSelectedRotRelToCam(D3DVALUE AxisX, D3DVALUE AxisY, D3DVALUE AxisZ, D3DVALUE angle) {
        if (!sFrame) return;
        RLDS3D_StopOrbitSelected();
        sFrame->SetRotation(info->camera, AxisX, AxisY, AxisZ, angle);
}

/*
** Moves the currently selected object by x/y pixels on the screen from it's relative position.  Allows a user
**   to drag objects around the screen using the mouse.  Assumes that the distance from the frame to the screen
**   will remain constant
*/

void RLDS3D_MoveSelectedPosByScreenCoords(double delta_x, double delta_y) {
        if (!sFrame) return;
        RLDS3D_StopOrbitSelected();
        D3DVECTOR p1;
    D3DRMVECTOR4D p2;
    sFrame->GetPosition(info->scene, &p1);
        info->view->Transform(&p2, &p1);
        // returned value is in homogenous coords so we need to multiply by w to get vector coords
    p2.x += D3DMultiply(D3DVAL((D3DVALUE)delta_x), p2.w);
    p2.y += D3DMultiply(D3DVAL((D3DVALUE)delta_y), p2.w);
    info->view->InverseTransform(&p1, &p2);
    sFrame->SetPosition(info->scene, p1.x, p1.y, p1.z);
}

void RLDS3D_GetDistanceFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetDistanceFactor(temp);
}

void RLDS3D_GetDopplerFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetDopplerFactor(temp);
}

void RLDS3D_GetRolloffFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetRolloffFactor(temp);
}

void RLDS3D_SetDistanceFactor(D3DVALUE temp) {
lp3DListenerInfo->SetDistanceFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetDopplerFactor(D3DVALUE temp) {
lp3DListenerInfo->SetDopplerFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetRolloffFactor(D3DVALUE temp) {
lp3DListenerInfo->SetRolloffFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_CommitDeferredSettings(void) {
lp3DListenerInfo->CommitDeferredSettings();
}

BOOL RLDS3D_SoundSelected(void) {
if (!lpDS) return FALSE;
if (!sFrame) return FALSE;
if (!sFrame->GetAppData()) return FALSE;
if (!((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound) return FALSE;
return TRUE;
}

void RLDS3D_GetSelConeAngles(LPDWORD inner, LPDWORD outer) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetConeAngles(inner, outer);
}

void RLDS3D_GetSelConeOutsideVolume(LPLONG temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetConeOutsideVolume(temp);
}

void RLDS3D_GetSelMinimumDistance(D3DVALUE *temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetMinDistance(temp);
}

void RLDS3D_GetSelMaximumDistance(D3DVALUE *temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetMaxDistance(temp);
}

void RLDS3D_SetSelConeAngles(DWORD inner, DWORD outer) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetConeAngles(inner, outer, DS3D_IMMEDIATE);
UpdateConeVisual();
}

void RLDS3D_SetSelConeOutsideVolume(LONG temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetConeOutsideVolume(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetSelMinimumDistance(D3DVALUE temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetMinDistance(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetSelMaximumDistance(D3DVALUE temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetMaxDistance(temp, DS3D_IMMEDIATE);
}

/*
** Orbit control windows have a pointer to the orbiting frame attached to them.  The frame, in return, has the handle of the window in it's
** frameappdata.  When the window is used (the value modified/Stop Orbit button hit) the window modifies the frame and itself appropriately.
** When the frame is modified (ie: Destroyed) it passes messages to the window appropriately.
*/

/*
** Structure to store info about an orbit
*/

struct ORBITDATA {
        LPDIRECT3DRMFRAME orbit_frame;
        LPDIRECT3DRMFRAME child_frame;
        D3DVECTOR axis; // Axis around which the orbit is going
        D3DVALUE speed; // Speed (angles per second)
};

typedef ORBITDATA* LPORBITDATA;

/*
** Structure to store info about a bullet, used by both the dialog box and the callback function
*/

struct BULLETDATA {
        LPDIRECT3DRMFRAME bullet_frame;
        D3DVECTOR vStartPosition;
        D3DVECTOR vDirection;
        D3DVALUE fSpeed;
        D3DVALUE fTime;
};

typedef BULLETDATA* LPBULLETDATA;

static void CDECL bulletCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta) {
        LPBULLETDATA binfo = (LPBULLETDATA)arg;
        if (binfo->fTime <= D3DVAL(0.0)) {
                binfo->fTime = D3DVAL(10.0); // Ten seconds, 5 for before and 5 for after.
                D3DVALUE mult = D3DDivide(D3DMultiply(binfo->fTime, binfo->fSpeed), D3DVAL(-2.0)); // Calculate the displacement multiplier for calculating starting position
                obj->SetPosition(info->scene, binfo->vStartPosition.x + D3DMultiply(mult, binfo->vDirection.x),
                                                  binfo->vStartPosition.y + D3DMultiply(mult, binfo->vDirection.y),
                                                                          binfo->vStartPosition.z + D3DMultiply(mult, binfo->vDirection.z));
        }
        else binfo->fTime -= delta;
}

/*
** Stops the passed frame from orbiting or from bulleting
*/

void StopOrbiting(LPDIRECT3DRMFRAME stopme) {
        // If it isn't orbiting we don't need to stop it!
        if (!stopme) return;
        if (!stopme->GetAppData()) return;
        FRAMEAPPDATA* data = (FRAMEAPPDATA*)stopme->GetAppData();
        if (data->bOrbiting) {
                if (data->hDlg) {
                        // We explicitly call EndDialog on the orbit's control without sending it any message.  As a result we have
                        //   to grab it's associated orbitdata and delete it ourselves.
                        LPORBITDATA orbit_data = (LPORBITDATA)GetWindowLong(data->hDlg, DWL_USER);
                        if (orbit_data) delete orbit_data;
                        EndDialog(data->hDlg, TRUE);
                }

                // Get its parent
                LPDIRECT3DRMFRAME parent;
                stopme->GetParent(&parent);

                // Put this frame back into the scene frame (this assumes that's where it came from)
                info->scene->AddChild(stopme);

                // The parent frame of an orbiting visual is only around to make the object orbit, so we don't need it
                parent->Release();
                data->bOrbiting = FALSE;
                data->hDlg = 0;
        }
        if (data->bBullet) {
                if (data->hDlg) {
                        LPBULLETDATA bullet_data = (LPBULLETDATA)GetWindowLong(data->hDlg, DWL_USER);
                        stopme->SetPosition(info->scene, bullet_data->vStartPosition.x, bullet_data->vStartPosition.y, bullet_data->vStartPosition.z);
                        if (bullet_data) delete bullet_data;
                        EndDialog(data->hDlg, TRUE);
                        stopme->DeleteMoveCallback(bulletCallback, bullet_data);
                }
                stopme->SetVelocity(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), TRUE);
                data->bBullet = FALSE;
                data->hDlg = 0;
        }
}

void RLDS3D_StopOrbitSelected() {
        StopOrbiting(sFrame);
}

/*
** Windows procedure for the orbit dialog box
*/

BOOL CALLBACK OrbitDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    lparam = lparam;

        LPORBITDATA my_data = (ORBITDATA*)GetWindowLong(win, DWL_USER);
        char lpszTS[100];

    switch (msg)
    {
    case WM_INITDIALOG:
                // We've set it up to pass in the pointer to our case-specific data (through WM_INITDIALOG)
                // so we tuck it away for later use.  This points to the ORBITDATA structure associated with
                // this dialog box
                SetWindowLong(win, DWL_USER, lparam);
                sprintf(lpszTS, "%f", ((LPORBITDATA)lparam)->speed);
                SendDlgItemMessage(win, IDC_ORBIT, WM_SETTEXT, 0, (LPARAM) lpszTS);
                SendDlgItemMessage(win, IDC_ORBIT, EM_SETLIMITTEXT, 100, 0);
        return TRUE;

    case WM_COMMAND:
                {
                        if (wparam == IDOK) {
                                // Stopping the orbit will end the dialog for us.
                                StopOrbiting(my_data->child_frame);
                                return TRUE;
                        }
                        else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_ORBIT) {
                                // Get string length
                                int stringlength = SendDlgItemMessage(win, IDC_ORBIT, EM_LINELENGTH, 0, 0);
                                // Check to make sure the string isn't too long - atof accepts up to 100 chars
                                if (stringlength > 99) return TRUE;
                                lpszTS[0] = (char)stringlength;
                                // Get string
                                SendDlgItemMessage(win, IDC_ORBIT, EM_GETLINE, 0, (LPARAM) lpszTS);
                                lpszTS[stringlength] = 0;
                                // Store speed and set new rotation
                                my_data->speed = D3DVAL(atof(lpszTS));
                                my_data->orbit_frame->SetRotation(info->scene, my_data->axis.x, my_data->axis.y, my_data->axis.z, my_data->speed);
                                return TRUE;
                        }
                        else return FALSE;
                }
        break;
        }
        return FALSE;
}

/*
** Windows proc for the bullet dialog box
*/

BOOL CALLBACK BulletDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    lparam = lparam;

        LPBULLETDATA my_data = (BULLETDATA*)GetWindowLong(win, DWL_USER);
        char lpszTS[100];

    switch (msg)
    {
    case WM_INITDIALOG:
                // We've set it up to pass in the pointer to our case-specific data (through WM_INITDIALOG)
                // so we tuck it away for later use.  This points to the ORBITDATA structure associated with
                // this dialog box
                SetWindowLong(win, DWL_USER, lparam);
                sprintf(lpszTS, "%f", ((LPBULLETDATA)lparam)->fSpeed);
                SendDlgItemMessage(win, IDC_BULLET, WM_SETTEXT, 0, (LPARAM) lpszTS);
                SendDlgItemMessage(win, IDC_BULLET, EM_SETLIMITTEXT, 100, 0);
        return TRUE;

    case WM_COMMAND:
                {
                        if (wparam == IDOK) {
                                // Stopping the orbit will end the dialog for us.
                                StopOrbiting(my_data->bullet_frame);
                                return TRUE;
                        }
                        else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_BULLET) {
                                // Get string length
                                int stringlength = SendDlgItemMessage(win, IDC_BULLET, EM_LINELENGTH, 0, 0);
                                // Check to make sure the string isn't too long - atof accepts up to 100 chars
                                if (stringlength > 99) return TRUE;
                                lpszTS[0] = (char)stringlength;
                                // Get string
                                SendDlgItemMessage(win, IDC_BULLET, EM_GETLINE, 0, (LPARAM) lpszTS);
                                lpszTS[stringlength] = 0;

                                // Store speed and set
                                my_data->fSpeed = D3DVAL(atof(lpszTS));
                                my_data->bullet_frame->SetVelocity(info->scene, D3DMultiply(my_data->vDirection.x, my_data->fSpeed),
                                                                    D3DMultiply(my_data->vDirection.y, my_data->fSpeed),
                                                                    D3DMultiply(my_data->vDirection.z, my_data->fSpeed),
TRUE);
                                return TRUE;
                        }
                        else return FALSE;
                }
        break;
        }
        return FALSE;
}

/*
** Orbits the currently selected object around the camera... creates a frame which is centred at the camera and contains
**   the selected frame, and then rotates that frame around the camera
*/

ULONG orbit_num = 1;

void RLDS3D_OrbitSelected(void) {
        if (!sFrame) return;
        StopOrbiting(sFrame);
        FRAMEAPPDATA* tempdat;

        // Need to create orbit data, if not then can't orbit
        LPORBITDATA orbitdat = new ORBITDATA;
        if (!orbitdat) return;

        if (!sFrame->GetAppData()) {
                tempdat = new FRAMEAPPDATA();
                if (!tempdat) return;
                sFrame->SetAppData((ULONG)(tempdat));
        }
        else {
                tempdat = (LPFRAMEAPPDATA)sFrame->GetAppData();
        }

        // Create a frame.  Centre it around the camera.  Add the selected frame.  Rotate it!
        LPDIRECT3DRMFRAME orbiting_frame;

        if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &orbiting_frame))) return;

        orbiting_frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
        orbiting_frame->SetOrientation(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0));
        orbiting_frame->AddChild(sFrame);

        D3DVECTOR vDir, vUp;
        info->camera->GetOrientation(info->scene, &vDir, &vUp);

        // Store orbit info
        orbitdat->orbit_frame = orbiting_frame;
        orbitdat->child_frame = sFrame;
        orbitdat->speed = D3DVAL(1.0);
        orbitdat->axis.x = vUp.x;
        orbitdat->axis.y = vUp.y;
        orbitdat->axis.z = vUp.z;

        orbiting_frame->SetRotation(info->scene, vUp.x, vUp.y, vUp.z, orbitdat->speed);

        // Set the boolean in the frame's appinfo to tell everyone it's orbiting
        tempdat->bOrbiting = TRUE;

        // Create a dialog box and orbitinfo so that this thing has something to control it
        HWND mydial = CreateDialogParam(hinst, "OrbitBox", hwndParent, OrbitDlgProc, (LONG)orbitdat);
        if (mydial) {
                char name_string[50];
                sprintf(name_string, "Orbit #%i", orbit_num++);
                SetWindowText(mydial, name_string);
                ShowWindow(mydial, SW_SHOW);
                UpdateWindow(mydial);
                tempdat->hDlg = mydial;
        }
}

/*
** Bullet the currently selected object.  Bullets have two properties: Speed and bullet-time.  The bullet passes through it's original
**   position along the path defined by the viewer's original viewpoint (it heads towards the viewer).  It takes 5 seconds to get to the
**   viewer and five seconds once past the viewer, and continues doing thus until the bullet is stopped.  Speed is controlled by the user.
*/

ULONG bullet_num = 1;

void RLDS3D_BulletSelected(void) {
        if (!sFrame) return;
        StopOrbiting(sFrame);

        FRAMEAPPDATA* tempdat;
        if (!sFrame->GetAppData()) {
                tempdat = new FRAMEAPPDATA();
                if (!tempdat) return;
                sFrame->SetAppData((ULONG)(tempdat));
        }
        else {
                tempdat = (LPFRAMEAPPDATA)sFrame->GetAppData();
        }

        // Create a new bullet object and attach our callback to the frame (it will release itself and put itself back
        //   into position when it's done!)

        LPBULLETDATA bs = new BULLETDATA;
        if (!bs) return;

        sFrame->GetPosition(info->scene, &(bs->vStartPosition));

        D3DVECTOR vDir, vUp;
        info->camera->GetOrientation(info->scene, &vDir, &vUp);
        // velocity heading towards camera
        bs->vDirection.x = -vDir.x;
        bs->vDirection.y = -vDir.y;
        bs->vDirection.z = -vDir.z;

        bs->bullet_frame = sFrame;
        bs->fTime = D3DVAL(10.0); // Ten seconds, 5 for before and 5 for after.
        bs->fSpeed = D3DVAL(10.0);

        D3DVALUE mult = D3DDivide(D3DMultiply(bs->fTime, bs->fSpeed), D3DVAL(-2.0)); // Calculate the displacement multiplier for calculating starting position

        sFrame->SetPosition(info->scene, bs->vStartPosition.x + D3DMultiply(mult, bs->vDirection.x),
                                             bs->vStartPosition.y + D3DMultiply(mult, bs->vDirection.y),
                                                                     bs->vStartPosition.z + D3DMultiply(mult, bs->vDirection.z));

        sFrame->SetVelocity(info->scene, D3DMultiply(bs->vDirection.x, bs->fSpeed),
                             D3DMultiply(bs->vDirection.y, bs->fSpeed),
                             D3DMultiply(bs->vDirection.z, bs->fSpeed),
 TRUE);

        sFrame->AddMoveCallback(bulletCallback, (void*)bs);

        // Set the boolean in the frame's appinfo to tell everyone it's orbiting
        tempdat->bBullet = TRUE;

        // Create a dialog box and orbitinfo so that this thing has something to control it
        HWND mydial = CreateDialogParam(hinst, "BulletBox", hwndParent, BulletDlgProc, (LONG)bs);
        if (mydial) {
                char name_string[50];
                sprintf(name_string, "Bullet #%i", bullet_num++);
                SetWindowText(mydial, name_string);
                ShowWindow(mydial, SW_SHOW);
                UpdateWindow(mydial);
                tempdat->hDlg = mydial;
        }
}

/*
**********************************  DIRECTSOUND 3D FUNCTIONS  *****************************************
*/

/*
** The RLDS3D interface allows you to attach sound(s) to the selected object which can then be played and
**   whose 3D audio position will update according to the RL object's position
**
** Some functionality is applied to all sounds, and as a result, the RLDS3D interface keeps a record of
**   existing sounds and which frames they are attached to.  Removal of a frame and/or a sound updates these
**   records
*/

/*
** CALLBACKS - update position as 3D objects move around
*/

// The listener callback is attached to the camera.  This is done in CreateScene()
static void CDECL listenerCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta)
{
        arg = arg;
        if (!lpDS) return;
        D3DVECTOR rlvCameraInfo, rlvCameraUp;

        info->camera->GetPosition(info->scene, &rlvCameraInfo);
        lp3DListenerInfo->SetPosition(rlvCameraInfo.x, rlvCameraInfo.y,
      rlvCameraInfo.z, DS3D_DEFERRED);

        info->camera->GetOrientation(info->scene, &rlvCameraInfo, &rlvCameraUp);
        lp3DListenerInfo->SetOrientation(rlvCameraInfo.x, rlvCameraInfo.y,
 rlvCameraInfo.z, rlvCameraUp.x,
 rlvCameraUp.y, rlvCameraUp.z, DS3D_DEFERRED);

        info->camera->GetVelocity(info->scene, &rlvCameraInfo, TRUE);
        lp3DListenerInfo->SetVelocity(rlvCameraInfo.x, rlvCameraInfo.y,
      rlvCameraInfo.z, DS3D_DEFERRED);

lp3DListenerInfo->CommitDeferredSettings();
}

// Sounds are updated whenever their frame moves using Render Callbacks.  This is the callback function.
static void CDECL soundCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delay)
{
        arg = arg;
        LPDIRECT3DRMFRAME tFrame = (LPDIRECT3DRMFRAME)obj;
        if (!lpDS) return;
        // Get the sound from the frame's app data
        LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)tFrame->GetAppData();
        if (!lpAppDat) return;
        // Get the 3D sound buffer and remove the sound callback if it's NULL since it shouldn't exist
        if (!lpAppDat->Sound || !lpAppDat->i3DSound) {
                releaseSoundCallback(tFrame);
                return;
        }
        D3DVECTOR rlvVisualInfo, rlvVisualUp;

        tFrame->GetPosition(info->scene, &rlvVisualInfo);
        lpAppDat->i3DSound->SetPosition(rlvVisualInfo.x, rlvVisualInfo.y,
rlvVisualInfo.z, DS3D_DEFERRED);

        tFrame->GetOrientation(info->scene, &rlvVisualInfo, &rlvVisualUp);
        lpAppDat->i3DSound->SetConeOrientation(rlvVisualInfo.x, rlvVisualInfo.y,
       rlvVisualInfo.z, DS3D_DEFERRED);

        tFrame->GetVelocity(info->scene, &rlvVisualInfo, TRUE);
        lpAppDat->i3DSound->SetVelocity(rlvVisualInfo.x, rlvVisualInfo.y,
rlvVisualInfo.z, DS3D_DEFERRED);

lp3DListenerInfo->CommitDeferredSettings();
}

// This adds a sound callback function to a frame
void setSoundCallback(LPDIRECT3DRMFRAME frame) {
        frame->AddMoveCallback(soundCallback, NULL);
}

// Removes a sound callback from a frame
void releaseSoundCallback(LPDIRECT3DRMFRAME frame) {
        frame->DeleteMoveCallback(soundCallback, NULL);
}

/*
** Sound records - keeps track of which frames have sounds associated with them and
**   adds/removes the sounds as required.
*/

struct SoundRecord {
        LPDIRECT3DRMFRAME  lpFrame;
        SoundRecord* next;
};

SoundRecord* top = NULL;

BOOL Recurse_Remove(LPDIRECT3DRMFRAME owner, SoundRecord* record) {
        if (!record) return FALSE;
        if (Recurse_Remove(owner, record->next)) {
                SoundRecord* temp = record->next;
                record->next = temp->next;
                delete temp;
        }
        if (owner == record->lpFrame) return TRUE;
        return FALSE;
}

// Removes a frame from the records
void RemoveSoundRecord(LPDIRECT3DRMFRAME owner) {
        if (!owner) return;

        // Don't bother deleting frames without appdata and a sound associated with them
        // because they SHOULDN'T be on the list.
        if (!owner->GetAppData()) return;
        LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(owner->GetAppData());
        if (!data->Sound) return;

        // Release the sound and remove the soundrecord from the list.
        data->i3DSound->Release();
        data->i3DSound = NULL;
        data->Sound->Release();
        data->Sound = NULL;
        releaseSoundCallback(owner);

        if (Recurse_Remove(owner, top)) {
                SoundRecord* temp = top;
                top = top->next;
                delete temp;
        }
}

// Adds a frame to the records
void AddSoundRecord(LPDIRECT3DRMFRAME owner, char* sound_filename) {
        if (!lpDS) return;
        if (!owner) return;
        if (!sound_filename) return;

        // Removes all previous sounds from this frame
        RemoveSoundRecord(owner);

        // Create frame data for this frame if it hasn't already been done
        LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(owner->GetAppData());
        if (!data) {
                data = new FRAMEAPPDATA();
                if (!data) return;
                owner->SetAppData((ULONG)data);
        }

        // Create the sound and attach it to the frame
        data->Sound = DSLoad3DSoundBuffer(lpDS, sound_filename);
        if (!data->Sound) return;

        // Query to get the 3D interface, destroy the sound buffer if it's not available...
        data->Sound->QueryInterface(IID_IDirectSound3DBuffer, (void**)&data->i3DSound);
        if (!data->i3DSound) {
                data->Sound->Release();
                data->Sound = NULL;
                return;
        }

        // Set the minimum distance at which the sound's amplitude should decay.
        data->i3DSound->SetMinDistance((D3DVALUE)10.0, DS3D_IMMEDIATE);

        setSoundCallback(owner);

        SoundRecord* temp = new SoundRecord;
        temp->next = top;
        top = temp;
        top->lpFrame = owner;
}

/*
** Functionality for users
*/

// Stops all the sounds in the world.  (Actually, only in the local RL world.)
// Runs through the records of all the sounds and stops them.
void RLDS3D_StopAllSounds() {
        if (!lpDS) return;
        SoundRecord* temp = top;
        while (temp) {
                LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(temp->lpFrame->GetAppData());
                if (data) {
                        if (data->Sound) data->Sound->Stop();
                }
                temp = temp->next;
        }
}

// Removes all sounds in the world
void RLDS3D_RemoveAllSounds() {
        if (!lpDS) return;
        while (top) RemoveSoundRecord(top->lpFrame);
UpdateConeVisual();
}

// Plays the sound associated with the currently selected object
void RLDS3D_PlaySound(BOOL bIsLooping) {
        if (!sFrame) return;
        if (!lpDS) return;
        LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)sFrame->GetAppData();
        if (lpAppDat) {
                if (lpAppDat->Sound) {
                        lpAppDat->Sound->Stop();
                        lpAppDat->Sound->SetCurrentPosition(0);
                        if (bIsLooping) {
                                lpAppDat->Sound->Play(0,0,DSBPLAY_LOOPING);
                        }
                        else {

lpAppDat->Sound->Play(0,0,0);
                        }
                }
        }
}

// Stops the sound associated with the currently selected object
void RLDS3D_StopSelectedSound() {
        if (!sFrame) return;
        if (!lpDS) return;
        LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)sFrame->GetAppData();
        if (lpAppDat) {
                if (lpAppDat->Sound) {
                        lpAppDat->Sound->Stop();
                }
        }
}

// Removes the sound from the currently selected object
void RLDS3D_RemoveSound() {
        RLDS3D_StopSelectedSound();
        if (!sFrame) return;
        if (!lpDS) return;
        RemoveSoundRecord(sFrame);
UpdateConeVisual();
}

// Attaches a sound (filename provided) to the selected frame
void RLDS3D_AttachSound(char* filename) {
        // Removes the sound attached to the currently selected item (if it exists)
        RLDS3D_RemoveSound();
        if (!sFrame) return;
        if (!lpDS) return;
        AddSoundRecord(sFrame, filename);
UpdateConeVisual();
}

/*
*************************************  MISC. MAINTENANCE FUNCTIONS  **************************************
*/

/*
** Allows external users access to the RL Device to deal with Windows-related issues
**   (See case WM_ACTIVATE: and case WM_PAINT: in viewer source for examples of HandleActivate() and HandlePaint())
** Design note: This was done to save time from the conversion from the old version of the viewer rather than
**   having RLDS3D_HandleActivate(), etc.
*/

LPDIRECT3DRMDEVICE RLDS3D_WinDevice() {
        if (!info) return NULL;
        return info->dev;
}

/*
** Handles activation messages from the window
*/

void RLDS3D_HandleActivate(WPARAM wparam) {
        if (!info || !info->dev) return;
        LPDIRECT3DRMWINDEVICE windev;
        if (D3DRM_SUCCEED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev))) {
                windev->HandleActivate(wparam);
                windev->Release();
        }
}

/*
** Handles paint messages from the window - the paintstruct which BeginPaint has been called on is
**   passed to it
*/

void RLDS3D_HandlePaint(PAINTSTRUCT* ps) {
        if (!info) return;
        LPDIRECT3DRMWINDEVICE windev;
        if (D3DRM_SUCCEED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev))) {
                windev->HandlePaint(ps->hdc);
                windev->Release();
        }
}

/*
** Tells whether or not sound is initialized
*/

BOOL RLDS3D_SoundInitialized() {
        if (lpDS) return TRUE;
        return FALSE;
}

/*
** Tells whether or not a frame is selected
*/

BOOL RLDS3D_FrameSelected() {
        if (sFrame) return TRUE;
        return FALSE;
}

/*
** Render the scene into the viewport.
*/

void RLDS3D_Render(D3DVALUE time_delta)
{
    // When the WM_SIZE message passes 0's as size to the ResizeViewport we know that it's minimized, in which case we don't render it.
    if (info->bMinimized == TRUE) return;
    D3DRM_SUCCEED(info->scene->Move(time_delta));
    D3DRM_SUCCEED(info->view->Clear());
    D3DRM_SUCCEED(info->view->Render(info->scene));
    D3DRM_SUCCEED(info->dev->Update());
}

/*
 * Resize the viewport and device when the window size changes.
 */
void RLDS3D_ResizeViewport(int width, int height)
{
    int view_width = info->view->GetWidth();
    int view_height = info->view->GetHeight();
    int dev_width = info->dev->GetWidth();
    int dev_height = info->dev->GetHeight();

    if (!(width && height)) {
            info->bMinimized = TRUE;
            return;

    }
    else info->bMinimized = FALSE;

    if (view_width == width && view_height == height)
        return;
else info->bMinimized = FALSE;

    if (width <= dev_width && height <= dev_height) {
        info->view->Release();
        D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, width, height, &info->view));
        info->view->SetBack(D3DVAL(400.0));
    }

    int ret;
    if (!RebuildDevice(hwndParent, info, width, height)) {
        ret = MessageBox(hwndParent, "Unable to create Direct3D device", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
        PostMessage(hwndParent, WM_CLOSE,0,0);
    };
}

/*
** Sets/Gets the polygon fill mode
*/

D3DRMFILLMODE RLDS3D_GetPolygonFillMode(void) {
    return (D3DRMFILLMODE)(info->dev->GetQuality() & D3DRMFILL_MASK);
}

void RLDS3D_SetPolygonFillMode(D3DRMFILLMODE quality) {
    D3DRMRENDERQUALITY oldq = info->dev->GetQuality();
    oldq = (oldq & ~D3DRMFILL_MASK) | quality;
    info->dev->SetQuality(oldq);
}

/*
** Sets/Gets the polygon shading mode
*/

D3DRMSHADEMODE RLDS3D_GetPolygonShadeMode(void) {
        return (D3DRMSHADEMODE)(info->dev->GetQuality() & D3DRMSHADE_MASK);
}

void RLDS3D_SetPolygonShadeMode(D3DRMSHADEMODE quality) {
        D3DRMRENDERQUALITY oldq = info->dev->GetQuality();
        oldq = (oldq & ~D3DRMSHADE_MASK) | quality;
        info->dev->SetQuality(oldq);
}

/*
** Sets/Gets the color model for the viewport (RGB or mono (256-color-based))
*/

D3DRMCOLORMODEL RLDS3D_GetColourModel(void) {
        return info->model;
}

void RLDS3D_SetColourModel(D3DRMCOLORMODEL model) {
    info->model = model;
    int ret;
    if (!RebuildDevice(hwndParent, info, info->dev->GetWidth(), info->dev->GetHeight())) {
        ret = MessageBox(hwndParent, "Unable to selected Direct3D device", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
        PostMessage(hwndParent, WM_CLOSE,0,0);
    }
}

/*
** Sets/Gets whether or not lighting is on
*/

BOOL RLDS3D_GetLighting(void) {
    D3DRMLIGHTMODE mode = (D3DRMLIGHTMODE)(info->dev->GetQuality() & D3DRMLIGHT_MASK);
    if (mode == D3DRMLIGHT_ON) return TRUE;
    return FALSE;
}

void RLDS3D_SetLighting(BOOL new_val) {
    D3DRMRENDERQUALITY qual = info->dev->GetQuality() & ~D3DRMLIGHT_MASK;
    if (new_val) qual |= D3DRMLIGHT_ON; else qual |= D3DRMLIGHT_OFF;
    info->dev->SetQuality(qual);
}

/*
** Sets/Gets whether or not dithering is on
*/

BOOL RLDS3D_GetDither(void) {
    return info->dev->GetDither();
}

void RLDS3D_SetDither(BOOL dither) {
    info->dev->SetDither(dither);
}

/*
** Sets/Gets texture quality (only relevant for RGB modes)
*/

D3DRMTEXTUREQUALITY RLDS3D_GetTextureQuality(void) {
    return info->dev->GetTextureQuality();
}

void RLDS3D_SetTextureQuality(D3DRMTEXTUREQUALITY new_quality) {
    info->dev->SetTextureQuality(new_quality);
}

/*
*************************************  INTERNAL FUNCTIONS (Not part of API)  ********************************
*/

/*
** Given a bounding box this generates a visual representation of it using rods and cones
*/

static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX* box)
{
    LPDIRECT3DRMMESHBUILDER mesh;
    static D3DVECTOR zero = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
    static D3DVECTOR dir = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
    D3DVECTOR a, b;

    if (!D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&mesh))) return NULL;

    dir.z = box->max.z + D3DVAL(1.0);
    AddRod(mesh, D3DVAL(0.05), zero, dir);
    a = dir;
    a.z += D3DVAL(0.6);
    AddCone(mesh, D3DVAL(0.2), dir, a);
    a = box->min;
    b = a;
    b.y = box->max.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->max.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->min.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->min.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.z = box->max.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->max.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->max.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->min.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->min.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    b.y = box->max.y; a = b; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b = box->max; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a.y = box->min.y; b = a; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);

    if (!D3DRM_SUCCEED(mesh->SetColor(D3DRMCreateColorRGB(D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))) {
        mesh->Release();
        return NULL;
    }
    return mesh;
}

/*
** Given a box, this creates a mesh in the shape of a 16-sided speaker cone aimed forward with requested angle
*/

#define CONE_POINTS 16
#define pi 3.14159

static LPDIRECT3DRMMESHBUILDER makeSpeaker(D3DRMBOX* box, D3DVALUE in_angle) {
    if (!box) return NULL;

    D3DVALUE angle = in_angle / D3DVAL(2.0);
DWORD* speaker_faces = new DWORD[CONE_POINTS*4+1];
    if (!speaker_faces) return NULL;
    memset(speaker_faces, 0, sizeof(DWORD[CONE_POINTS*4+1]));

    LPDIRECT3DRMMESHBUILDER mesh;
    if (!D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&mesh))) {
        delete speaker_faces;
        return NULL;
    }

    static D3DVECTOR zero = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};

    D3DVECTOR v[CONE_POINTS+1];

    // center of the cone
    v[CONE_POINTS] = zero;

    int looper;

    // Angle along XZ plane which is rotated CONE_POINT times to form cone
    D3DVECTOR base_angle;
    base_angle.z = (box->max.z + D3DVAL(2.0)) * (D3DVALUE)cos(angle * pi / 180.0);
    base_angle.x = (box->max.z + D3DVAL(2.0)) * (D3DVALUE)sin(angle * pi / 180.0);
    base_angle.y = D3DVAL(0.0);

    for (looper=0; looper<CONE_POINTS; looper++) {
        v[looper].z = base_angle.z;
        v[looper].x = base_angle.x * (D3DVALUE)cos((looper*2*pi)/CONE_POINTS);
        v[looper].y = base_angle.x * (D3DVALUE)sin((looper*2*pi)/CONE_POINTS);
        speaker_faces[looper*4] = 3;
        speaker_faces[looper*4+1] = looper % CONE_POINTS;
        speaker_faces[looper*4+2] = (looper + 1) % CONE_POINTS;
        speaker_faces[looper*4+3] = CONE_POINTS;
    }

    v[CONE_POINTS] = zero;

    if (!D3DRM_SUCCEED(mesh->AddFaces(CONE_POINTS+1, v, 0, NULL, speaker_faces, NULL))) {
        delete speaker_faces;
        mesh->Release();
        return NULL;
    }

    for (looper=0; looper<CONE_POINTS; looper++) {
        speaker_faces[looper*4+2] = looper % CONE_POINTS;
        speaker_faces[looper*4+1] = (looper + 1) % CONE_POINTS;
    }

    if (!D3DRM_SUCCEED(mesh->AddFaces(CONE_POINTS+1, v, 0, NULL, speaker_faces, NULL))) {
        delete speaker_faces;
        mesh->Release();
        return NULL;
    }

    delete speaker_faces;

    if (!D3DRM_SUCCEED(mesh->SetColor(D3DRMCreateColorRGB(D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))) {
        mesh->Release();
        return NULL;
    }

    if (!D3DRM_SUCCEED(mesh->SetQuality((mesh->GetQuality() & ~D3DRMSHADE_MASK) | D3DRMSHADE_FLAT))) {
        mesh->Release();
        return NULL;
    }

    return mesh;
}

/*
** Selects the given visual inside the given frame
*/

void UpdateConeVisual(void) {
if (!sFrame) return;
if (!sFrame->GetAppData()) return;
    LPFRAMEAPPDATA fd = (LPFRAMEAPPDATA)sFrame->GetAppData();
if (!fd->i3DSound) return;
if (showBoxes && sVisual)
        {   D3DRMBOX box;
            LPDIRECT3DRMMESHBUILDER builder;
        sFrame->DeleteVisual(selectionSpeaker);
            sVisual->GetBox(&box);
DWORD temp, outer;
fd->i3DSound->GetConeAngles(&temp, &outer);
            builder = makeSpeaker(&box, D3DVAL(temp));
            builder->CreateMesh(&selectionSpeaker);
            sFrame->AddVisual(selectionSpeaker);
            selectionSpeaker->Release();
builder->Release();
}

}

void SelectVisual(LPDIRECT3DRMMESHBUILDER visual, LPDIRECT3DRMFRAME frame) {
    RLDS3D_DeselectVisual();
    sVisual = visual;
    sFrame = frame;

    if (sVisual)
    {   LPDIRECT3DRMLIGHTARRAY lights;

        sLight = 0;
        sFrame->GetLights(&lights);
        if (lights)
        {   if (lights->GetSize())
            {   lights->GetElement(0, &sLight);
                sLight->Release(); /* reinstate reference count */
            }
            lights->Release();
        }

        if (showBoxes && visual)
        {   D3DRMBOX box;
            LPDIRECT3DRMMESHBUILDER builder;

            sVisual->GetBox(&box);
            builder = makeBox(&box);
            builder->CreateMesh(&selectionBox);
            sFrame->AddVisual(selectionBox);
            selectionBox->Release();
builder->Release();
UpdateConeVisual();
        }
    }
}

LPGUID
FindDevice(D3DCOLORMODEL cm)
{
    LPDIRECTDRAW lpDD;
    LPDIRECT3D lpD3D;
    D3DFINDDEVICESEARCH search;
    static D3DFINDDEVICERESULT result;
    HRESULT error;

    if (DirectDrawCreate(NULL, &lpDD, NULL) != DD_OK)
        return NULL;

    if (lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D) != DD_OK) {
        lpDD->Release();
        return NULL;
    }

    memset(&search, 0, sizeof search);
    search.dwSize = sizeof search;
    search.dwFlags = D3DFDS_COLORMODEL;
    search.dcmColorModel = (cm == D3DCOLOR_MONO) ? D3DCOLOR_MONO : D3DCOLOR_RGB;

    memset(&result, 0, sizeof result);
    result.dwSize = sizeof result;

    error = lpD3D->FindDevice(&search, &result);

    lpD3D->Release();
    lpDD->Release();

    if (error != D3D_OK)
        return NULL;
    else
        return &result.guid;
}

/*
 * Create the device and viewport.
 */

static BOOL CreateDevice(HWND win, AppInfo* info)
{
    RECT r;
    int bpp;
    HDC hdc;

    GetClientRect(win, &r);
    if (!D3DRM_SUCCEED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper, NULL, r.right, r.bottom, &info->dev))) return FALSE;

        hdc = GetDC(win);
    bpp = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(win, hdc);
    switch (bpp)
    {
    case 1:
        info->dev->SetShades(4);
        lpD3DRM->SetDefaultTextureShades(4);
        break;
    case 16:
        info->dev->SetShades(32);
        lpD3DRM->SetDefaultTextureColors(64);
        lpD3DRM->SetDefaultTextureShades(32);
        info->dev->SetDither(FALSE);
        break;
    case 24:
        info->dev->SetShades(256);
        lpD3DRM->SetDefaultTextureColors(64);
        lpD3DRM->SetDefaultTextureShades(256);
        info->dev->SetDither(FALSE);
        break;
    default:
        info->dev->SetDither(FALSE);
    }
    if (!CreateScene(info)) {
                info->dev->Release();
                return FALSE;
        }
    if (!D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, info->dev->GetWidth(), info->dev->GetHeight(), &info->view))) {
                info->dev->Release();
                return FALSE;
        }
    info->view->SetBack(D3DVAL(5000.0));
        return TRUE;
}

/*
 * Creates a simple scene and adds it to the main scene
 */

static BOOL CreateScene(AppInfo* info)
{
    LPDIRECT3DRMFRAME light;
    LPDIRECT3DRMLIGHT light1, light2;

    // Note that if something fails, we don't bother freeing up everything we've created... the caller to CreateScene should destroy
    //   the lpD3DRM object and that should happily release everything created with it.
    // Also note that, since we're just the viewer, if there's a critical error we pass a quit message with our error message since
    //   we'd want to quit if the initialize failed anyways...

    if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(NULL, &info->scene))) return FALSE;
    if (!D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light1))) return FALSE;
    if (!D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL(0.1), D3DVAL(0.1), D3DVAL(0.1), &light2))) return FALSE;
    if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &light))) return FALSE;

    light->SetPosition(info->scene, D3DVAL(2.0), D3DVAL(2.0), D3DVAL(5.0));
    light->SetOrientation(info->scene, D3DVAL(-1.0), D3DVAL(-1.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0));
    light->AddLight(light1);
    info->scene->AddLight(light2);

    if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &info->camera))) return FALSE;
    info->camera->SetPosition(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
    // Add a callback to the camera's frame so that the listener is updated with the camera
    info->camera->AddMoveCallback(listenerCallback, NULL);

    light->Release(), light1->Release(), light2->Release();
    return TRUE;
}

/*
 * Regenerate the device if the color model changes or the window size
 * changes.
 */
static BOOL RebuildDevice(HWND win, AppInfo* info, int width, int height)
{
    int old_dither = info->dev->GetDither();
    D3DRMRENDERQUALITY old_quality = info->dev->GetQuality();
    int old_shades = info->dev->GetShades();

    info->view->Release();
    info->dev->Release();

    LPGUID guid = FindDevice(info->model);

    if (!guid) return FALSE;

    if (!D3DRM_SUCCEED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper, guid, width, height, &info->dev))) return FALSE;

    info->dev->SetDither(old_dither);
    info->dev->SetQuality(old_quality);
    info->dev->SetShades(old_shades);
    width = info->dev->GetWidth();
    height = info->dev->GetHeight();
    if (!D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, width, height, &info->view))) return FALSE;
    info->view->SetBack(D3DVAL(400.0));
        return TRUE;
}

/*
 * Place an object in front of the camera.
 */
static void PlaceMesh(LPDIRECT3DRMMESHBUILDER mesh, AppInfo *info)
{
    LPDIRECT3DRMFRAME frame;

    if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) return;
    frame->AddVisual(mesh);
    frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15.0));
    frame->Release();
}

HRESULT __cdecl loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex)
{
    char* ext = LSTRRCHR(name, (int)'.');

    if (ext && !lstrcmpi(ext, ".ppm"))
        if (D3DRM_SUCCEED(lpD3DRM->LoadTexture(name, tex))) return 0;
    return -1;
}

/*
** Finds the last occurance of bChar in a null-terminated string, good for finding a pointer to the extension of a filename
*/
char* LSTRRCHR( const char* lpString, int bChar )
{
    if( lpString != NULL )
    {
        const char*     lpBegin;

        lpBegin = lpString;

        while( *lpString != 0 )
        {
            lpString++;
        }

        while( 1 )
        {
            if( *lpString == bChar )
            {
                return (char*)lpString;
            }

            if( lpString == lpBegin )
            {
                break;
            }

            lpString--;
        }
    }

    return NULL;
} /* LSTRRCHR */

/*
** Strange little function to pick a color from a table using standardized Windows stuff
*/

int ChooseNewColor(HWND win, D3DCOLOR* current)
{
    CHOOSECOLOR cc;
    COLORREF clr;
    COLORREF aclrCust[16];
    int i;

    for (i = 0; i < 16; i++)
        aclrCust[i] = RGB(255, 255, 255);

    clr =
        RGB
        (   (int) (255 * D3DRMColorGetRed(*current)),
            (int) (255 * D3DRMColorGetGreen(*current)),
            (int) (255 * D3DRMColorGetBlue(*current))
        );

    memset(&cc, 0, sizeof(CHOOSECOLOR));
    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = win;
    cc.rgbResult = clr;
    cc.lpCustColors = aclrCust;
    cc.Flags = CC_RGBINIT|CC_FULLOPEN;

    if (ChooseColor(&cc))
    {   *current =
            D3DRMCreateColorRGB
            (   D3DVAL(GetRValue(cc.rgbResult) / D3DVAL(255.0)),
                D3DVAL(GetGValue(cc.rgbResult) / D3DVAL(255.0)),
                D3DVAL(GetBValue(cc.rgbResult) / D3DVAL(255.0))
            );
        return TRUE;
    }
    else return FALSE;
}


RLDS3D.H

#ifndef ___RLDS3D_HEADER_FILE
#define ___RLDS3D_HEADER_FILE

#include "d3drmwin.h"

/*
** This is an interface between the viewer and the RL/DS3D APIs which provides the
**   functionality required by the viewer in simplified form.
**
** Note: This is not an object-oriented API since there should only be need for one
**   copy at a time and this way C++ to C conversions should be fairly easy
**
** DESCRIPTION: The user interfaces the 3D world and objects therein by selecting items
**   with the mouse (on the screen) and performing operations on that item.  The user will
**   also be able to perform operations on the camera and a few global operations applied
**   to all objects.
**
** USAGE: Create the interface using RLDS3D_Initialize() (returns FALSE if not created)
**   and remove it using RLDS3D_Deinitialize().  The RLDS3D_Render() functionality draws the world
**   on the screen.
*/

/*
************************  INITIALIZATION/DEINITIALIZATION ****************************
*/

/*
** Initialize will attach itself to the passed window.  Call initialize after
**  getting a handle for your window but before you display it.
**
** Initialize will initialize the RL API and return false if it fails.  It will
**  also attempt to initialize a DirectSound3D API, but failing this does not
**  justify a failed Initialize (since the 3D sound isn't a necessary part of
**  the viewer)
*/

BOOL RLDS3D_Initialize(HWND hwndPW, HINSTANCE this_inst);

/*
** Deinitializes where necessary (assumes program quits after calling this otherwise it'd free up memory, etc...)
*/

void RLDS3D_Deinitialize();

/*
***************************  ADDING/REMOVING/EDITING OBJECTS  *********************************
*/

/*
** Loads XOF file into the RL world (with textures)
*/

void RLDS3D_LoadXOF(char* file);

/*
** Sets/Gets whether or not boxes are shown around selected item
*/

BOOL RLDS3D_GetBoxes(void);
void RLDS3D_SetBoxes(BOOL new_val);

/*
** Updates the bounding box around the selected visual (this could be done using a render callback function to compare the
**   frame's scaling and transform functions instead)
*/

void RLDS3D_UpdateSelectionBox(void);

/*
** Deselects the currently selected 3D visual.
*/

void RLDS3D_DeselectVisual();

/*
** Given coordinates it selects the first visual under those coordinates in the window's viewport
*/

void RLDS3D_FindAndSelectVisual(int x, int y, LPBOOL changed = NULL);

/*
** Cuts the current selection to the clipboard
*/

void RLDS3D_CutVisual();

/*
** Copies the current selection to the clipboard
*/

void RLDS3D_CopyVisual();

/*
** Pastes the current selection to the window
*/

void RLDS3D_PasteVisual();

/*
** Deletes the current selection from the world without copying to the clipboard
*/

void RLDS3D_DeleteVisual();

/*
** Add a directional light
*/
void RLDS3D_AddDirectionalLight();

/*
** Add a parallel point light
*/
void RLDS3D_AddParallelPointLight();

/*
** Add Point Light
*/
void RLDS3D_AddPointLight();

/*
** Add a spotlight
*/
void RLDS3D_AddSpotlight();

/*
***********************************  OBJECT MOTION/SCALING/COLOURING  ****************************************
*/

/*
** Sets the selected object's colour
*/

void RLDS3D_SetSelColour();

/*
** Moves the camera relative to itself by providing scalars to multiply against the CAMERA-RELATIVE unit vectors
**   forwards/up/right.
*/

void RLDS3D_SetCamVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right);

/*
** Rotates the camera around its three axis
**
** forward_axis is roll, up_axis is yaw, right_axis is pitch
** (Only one affects at a time)
*/

void RLDS3D_SetCamRotForward(D3DVALUE forward_axis);
void RLDS3D_SetCamRotUp(D3DVALUE up_axis);
void RLDS3D_SetCamRotRight(D3DVALUE right_axis);

/*
** Scales the selected object in the x/y/z axis of it's orientation by the specified values
*/

void RLDS3D_ScaleSelected(D3DVALUE sx, D3DVALUE sy, D3DVALUE sz);

/*
** Moves the currently selected object in the 3D world relative to the camera
*/

void RLDS3D_SetSelectedVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right);

/*
** Rotates the currently selected object relative to the camera's frame when passed
**   a vector for the axis and an angle of rotation.
** This is useful because the AXIS which is specified is relative to what appears on the screen with it's origin at the camera
**   (ie: (0,0,1) will be an axis straight into the screen and thus things will spin around the centre of the screen)
*/

void RLDS3D_SetSelectedRotRelToCam(D3DVALUE AxisX, D3DVALUE AxisY, D3DVALUE AxisZ, D3DVALUE angle);

/*
** Moves the currently selected object by x/y pixels on the screen from it's relative position.  Allows a user
**   to drag objects around the screen using the mouse.  Assumes that the distance from the frame to the screen
**   will remain constant
*/

void RLDS3D_MoveSelectedPosByScreenCoords(double delta_x, double delta_y);

/*
** Orbits the selected object around the camera
*/

void RLDS3D_OrbitSelected(void);
void RLDS3D_StopOrbitSelected();

/*
** Bullets it towards the camera
*/

void RLDS3D_BulletSelected(void);

/*
**********************  DIRECTSOUND 3D INTERFACE  ******************************
*/

/*
** Stops all sounds from playing
*/

void RLDS3D_StopAllSounds();

/*
** Removes all of the sounds
*/

void RLDS3D_RemoveAllSounds();

/*
** Plays the sound associated with the currently selected object
*/

void RLDS3D_PlaySound(BOOL bIsLooping);

/*
** Stops the sound associated with the currently selected object
*/

void RLDS3D_StopSelectedSound();

/*
** Removes the sound from the currently selected object
*/

void RLDS3D_RemoveSound();
/*
** Attaches a sound (filename provided) to the selected frame
*/

void RLDS3D_AttachSound(char* filename);

/*
** Global parameter mods
*/
void RLDS3D_GetDistanceFactor(D3DVALUE *temp);
void RLDS3D_GetDopplerFactor(D3DVALUE *temp);
void RLDS3D_GetRolloffFactor(D3DVALUE *temp);
void RLDS3D_SetDistanceFactor(D3DVALUE temp);
void RLDS3D_SetDopplerFactor(D3DVALUE temp);
void RLDS3D_SetRolloffFactor(D3DVALUE temp);

void RLDS3D_CommitDeferredSettings(void);

/*
** Selected sound parameter modifications
*/

BOOL RLDS3D_SoundSelected(void);
void RLDS3D_GetSelConeAngles(LPDWORD inner, LPDWORD outer);
void RLDS3D_GetSelConeOutsideVolume(LPLONG temp);
void RLDS3D_GetSelMinimumDistance(D3DVALUE *temp);
void RLDS3D_GetSelMaximumDistance(D3DVALUE *temp);
void RLDS3D_SetSelConeAngles(DWORD inner, DWORD outer);
void RLDS3D_SetSelConeOutsideVolume(LONG temp);
void RLDS3D_SetSelMinimumDistance(D3DVALUE temp);
void RLDS3D_SetSelMaximumDistance(D3DVALUE temp);

/*
*************************************  MISC. MAINTENANCE FUNCTIONS  **************************************
*/

/*
** Allows external users access to the RL Device to deal with Windows-related issues
**   (See case WM_ACTIVATE: and case WM_PAINT: in viewer source for examples of HandleActivate() and HandlePaint())
** Design note: This was done to save time from the conversion from the old version of the viewer rather than
**   having RLDS3D_HandleActivate(), etc.
*/

LPDIRECT3DRMDEVICE RLDS3D_WinDevice();

// Handles window activation (pass the wparam from the winproc)
void RLDS3D_HandleActivate(WPARAM wparam);

// Handles paint messages from the window.  Pass this one the paintstructure created using BeginPaint
void RLDS3D_HandlePaint(PAINTSTRUCT* ps);

// Tells whether or not something is currently selected

BOOL RLDS3D_FrameSelected();

/*
** Renders the scene's next frame into the viewport.
*/

void RLDS3D_Render(D3DVALUE time_delta);

/*
 * Resize the viewport and device when the window size changes.
 */

void RLDS3D_ResizeViewport(int width, int height);

/*
** Returns whether or not the 3D Sound API was actually initialized properly
*/

BOOL RLDS3D_SoundInitialized();

/*
** Sets/Gets the current polygon fill mode
*/

D3DRMFILLMODE RLDS3D_GetPolygonFillMode(void);
void RLDS3D_SetPolygonFillMode(D3DRMFILLMODE quality);

/*
** Sets/Gets the current polygon shade mode
*/

void RLDS3D_SetPolygonShadeMode(D3DRMSHADEMODE quality);
D3DRMSHADEMODE RLDS3D_GetPolygonShadeMode(void);

/*
** Sets/Gets the colour model for the viewport (RGB or ramp)
*/

D3DRMCOLORMODEL RLDS3D_GetColourModel(void);
void RLDS3D_SetColourModel(D3DRMCOLORMODEL model);

/*
** Sets/Gets whether or not the lights affect the visuals
*/

void RLDS3D_SetLighting(BOOL new_val);
BOOL RLDS3D_GetLighting(void);

/*
** Sets/Gets whether or not dithering is on
*/

BOOL RLDS3D_GetDither(void);
void RLDS3D_SetDither(BOOL dither);

/*
** Sets/Gets texture quality (only relevant for RGB modes)
*/

D3DRMTEXTUREQUALITY RLDS3D_GetTextureQuality(void);
void RLDS3D_SetTextureQuality(D3DRMTEXTUREQUALITY new_quality);

#endif


RODCONE.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rodcone.cpp
 *
 ***************************************************************************/

/*
 * Sample code for building objects out of rods and cones.
 */

#include "d3drmwin.h"
#include "resource.h"
#include "rodcone.h"
#include <math.h>

static unsigned long rod_faces[] =
{   8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, /* end 1 */
    4, 0, 0, 1, 1, 9, 1, 8, 0,  /* side 0 */
    4, 1, 1, 2, 2, 10, 2, 9, 1, /* side 1 */
    4, 2, 2, 3, 3, 11, 3, 10, 2, /* side 2 */
    4, 3, 3, 4, 4, 12, 4, 11, 3, /* side 3 */
    4, 4, 4, 5, 5, 13, 5, 12, 4, /* side 4 */
    4, 5, 5, 6, 6, 14, 6, 13, 5, /* side 5 */
    4, 6, 6, 7, 7, 15, 7, 14, 6, /* side 6 */
    4, 7, 7, 0, 0, 8, 0, 15, 7,         /* side 7 */
    8, 8, 0, 9, 1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7, /* end 2 */
    0,
};

/*
** Error function which is actually defined in rlds3d.cpp
*/

BOOL D3DRM_SUCCEED(HRESULT result, int force_critical = -1, char* info = NULL);

void AddRod(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b)
{
if (!mesh) return;
    D3DVECTOR d, u, r;
    D3DVECTOR v[16];
    D3DVECTOR n[8];
    D3DVALUE f;
    int i;

    /*
     * Find the unit vector along the rod.
     */
    d.x = b.x - a.x;
    d.y = b.y - a.y;
    d.z = b.z - a.z;
    D3DRMVectorNormalise(&d);

    /*
     * Pick a vector normal to d
     */
    if (d.y != D3DVAL(0.0) || d.z != D3DVAL(0.0))
    {   u.x = D3DVAL(0.0);
        if (d.y == D3DVAL(0.0))
        {   u.y = D3DVAL(1.0);
            u.z = D3DVAL(0.0);
        } else
        {   D3DVALUE n_fix =
                D3DVAL(1.0)
            +   D3DDivide(D3DMultiply(d.z, d.z), D3DMultiply(d.y, d.y));
#ifdef FIXED_POINT_API
            double un_val = (double)n_fix / (double)(1<<16);
            u.z = D3DVAL(sqrt(1/un_val));
#else
            u.z = D3DVAL(sqrt(D3DDivide(D3DVAL(1.0), D3DVAL(n_fix))));
#endif
            u.y = -D3DMultiply(u.z, D3DDivide(d.z, d.y));
        }
    } else
    {   u.x = D3DVAL(0.0);
        u.y = D3DVAL(0.0);
        u.z = D3DVAL(1.0);
    }

    /*
     * Now find a vector normal to them both, to give us a coordinate
     * system in the plane normal to the rod.
     */
    D3DRMVectorCrossProduct(&r, &d, &u);

    /*
     * Scale down the coordinates to the radius of the rod.
     */
    u.x = D3DMultiply(u.x, radius);
    u.y = D3DMultiply(u.y, radius);
    u.z = D3DMultiply(u.z, radius);
    r.x = D3DMultiply(r.x, radius);
    r.y = D3DMultiply(r.y, radius);
    r.z = D3DMultiply(r.z, radius);

    /*
     * Calculate the corners of an octagon.
     */
    f = D3DVAL((float)sqrt((double)2) / (2 * (1 + (float)sqrt((double)2) / 2)));
    v[0].x = u.x + D3DMultiply(r.x, f);
    v[0].y = u.y + D3DMultiply(r.y, f);
    v[0].z = u.z + D3DMultiply(r.z, f);

    v[1].x = D3DMultiply(u.x, f) + r.x;
    v[1].y = D3DMultiply(u.y, f) + r.y;
    v[1].z = D3DMultiply(u.z, f) + r.z;

    v[2].x = D3DMultiply(-u.x, f) + r.x;
    v[2].y = D3DMultiply(-u.y, f) + r.y;
    v[2].z = D3DMultiply(-u.z, f) + r.z;

    v[3].x = -u.x + D3DMultiply(r.x, f);
    v[3].y = -u.y + D3DMultiply(r.y, f);
    v[3].z = -u.z + D3DMultiply(r.z, f);

    v[4].x = -u.x - D3DMultiply(r.x, f);
    v[4].y = -u.y - D3DMultiply(r.y, f);
    v[4].z = -u.z - D3DMultiply(r.z, f);

    v[5].x = D3DMultiply(-u.x, f) - r.x;
    v[5].y = D3DMultiply(-u.y, f) - r.y;
    v[5].z = D3DMultiply(-u.z, f) - r.z;

    v[6].x = D3DMultiply(u.x, f) - r.x;
    v[6].y = D3DMultiply(u.y, f) - r.y;
    v[6].z = D3DMultiply(u.z, f) - r.z;

    v[7].x = u.x - D3DMultiply(r.x, f);
    v[7].y = u.y - D3DMultiply(r.y, f);
    v[7].z = u.z - D3DMultiply(r.z, f);

    /*
     * Add the rod endpoints and calculate the vertex normals.
     */
    for (i = 0; i < 8; i++)
    {   n[i] = v[i];
        D3DRMVectorNormalise(&n[i]);
        v[i + 8].x = v[i].x + b.x;
        v[i + 8].y = v[i].y + b.y;
        v[i + 8].z = v[i].z + b.z;
        v[i].x += a.x;
        v[i].y += a.y;
        v[i].z += a.z;
    }

    /*
     * Now add the faces.
     */
D3DRM_SUCCEED(mesh->AddFaces(16, v, 8, n, rod_faces, NULL));
}

static unsigned long cone_faces[] =
{   8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, /* end 1 */
    3, 0, 0, 1, 1, 8, 1,        /* side 0 */
    3, 1, 1, 2, 2, 8, 1,        /* side 1 */
    3, 2, 2, 3, 3, 8, 1, /* side 2 */
    3, 3, 3, 4, 4, 8, 1, /* side 3 */
    3, 4, 4, 5, 5, 8, 1, /* side 4 */
    3, 5, 5, 6, 6, 8, 1, /* side 5 */
    3, 6, 6, 7, 7, 8, 1, /* side 6 */
    3, 7, 7, 0, 0, 8, 1,                /* side 7 */
    0,
};

void AddCone(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b)
{
if (!mesh) return;
    D3DVECTOR d, u, r;
    D3DVECTOR v[16];
    D3DVECTOR n[8];
    D3DVALUE f;
    int i;

    /*
     * Find the unit vector along the rod.
     */
    d.x = b.x - a.x;
    d.y = b.y - a.y;
    d.z = b.z - a.z;
    D3DRMVectorNormalise(&d);

    /*
     * Pick a vector normal to d
     */
    if (d.y != D3DVAL(0.0) || d.z != D3DVAL(0.0))
    {   u.x = D3DVAL(0.0);
        if (d.y == D3DVAL(0.0))
        {   u.y = D3DVAL(1.0);
            u.z = D3DVAL(0.0);
        } else
        {   D3DVALUE n_fix =
                D3DVAL(1.0)
            +   D3DDivide(D3DMultiply(d.z, d.z), D3DMultiply(d.y, d.y));
#ifdef FIXED_POINT_API
            double un_val = (double)n_fix / (double)(1<<16);
            u.z = D3DVAL(sqrt(1 / un_val));
#else
            u.z = D3DVAL(sqrt(D3DVAL(1.0) / D3DVAL(n_fix)));
#endif
            u.y = - D3DDivide(D3DMultiply(u.z, d.z), d.y);
        }
    } else
    {   u.x = D3DVAL(0.0);
        u.y = D3DVAL(0.0);
        u.z = D3DVAL(1.0);
    }

    /*
     * Now find a vector normal to them both, to give us a coordinate
     * system in the plane normal to the rod.
     */
    D3DRMVectorCrossProduct(&r, &d, &u);

    /*
     * Scale down the coordinates to the radius of the rod.
     */
    u.x = D3DMultiply(u.x, radius);
    u.y = D3DMultiply(u.y, radius);
    u.z = D3DMultiply(u.z, radius);
    r.x = D3DMultiply(r.x, radius);
    r.y = D3DMultiply(r.y, radius);
    r.z = D3DMultiply(r.z, radius);

    /*
     * Calculate the corners of an octagon.
     */
    f = D3DVAL((float)sqrt((double)2) / (2 * (1 + (float)sqrt((double)2) / 2)));
    v[0].x = u.x + D3DMultiply(r.x, f);
    v[0].y = u.y + D3DMultiply(r.y, f);
    v[0].z = u.z + D3DMultiply(r.z, f);

    v[1].x = D3DMultiply(u.x, f) + r.x;
    v[1].y = D3DMultiply(u.y, f) + r.y;
    v[1].z = D3DMultiply(u.z, f) + r.z;

    v[2].x = D3DMultiply(-u.x, f) + r.x;
    v[2].y = D3DMultiply(-u.y, f) + r.y;
    v[2].z = D3DMultiply(-u.z, f) + r.z;

    v[3].x = -u.x + D3DMultiply(r.x, f);
    v[3].y = -u.y + D3DMultiply(r.y, f);
    v[3].z = -u.z + D3DMultiply(r.z, f);

    v[4].x = -u.x - D3DMultiply(r.x, f);
    v[4].y = -u.y - D3DMultiply(r.y, f);
    v[4].z = -u.z - D3DMultiply(r.z, f);

    v[5].x = D3DMultiply(-u.x, f) - r.x;
    v[5].y = D3DMultiply(-u.y, f) - r.y;
    v[5].z = D3DMultiply(-u.z, f) - r.z;

    v[6].x = D3DMultiply(u.x, f) - r.x;
    v[6].y = D3DMultiply(u.y, f) - r.y;
    v[6].z = D3DMultiply(u.z, f) - r.z;

    v[7].x = u.x - D3DMultiply(r.x, f);
    v[7].y = u.y - D3DMultiply(r.y, f);
    v[7].z = u.z - D3DMultiply(r.z, f);

    v[8] = b;

    /*
     * Calculate the vertex normals.
     */
    for (i = 0; i < 8; i++)
    {   n[i] = v[i];
        D3DRMVectorNormalise(&n[0]);
        v[i].x += a.x;
        v[i].y += a.y;
        v[i].z += a.z;
    }

    /*
     * Now add the faces.
     */

    D3DRM_SUCCEED(mesh->AddFaces(9, v, 8, n, cone_faces, NULL));
}


RODCONE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rodcone.h
 *
 ***************************************************************************/

void AddRod(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b);
void AddCone(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b);


VIEWER.CPP

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
 *
 *  File: viewer.cpp
 *
 ***************************************************************************/

#define INITGUID
#include "d3drmwin.h"
#include "resource.h"

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <direct.h>

#include "rlds3d.h"
#include "file.h"

static char ViewerClass[32] = "ViewerClass";

static BOOL FirstInstance(HINSTANCE);
static BOOL AnyInstance(HINSTANCE, HWND*);
long FAR PASCAL WindowProc(HWND, UINT, WPARAM, LPARAM);
static void Idle();

BOOL CALLBACK WaitBoxDlgProc(HWND, UINT, WPARAM, LPARAM);

BOOL AboutBoxOn = FALSE;

/*
 * Initialization, message loop
 */

int PASCAL WinMain
    (HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, int cmdshow)
{
    MSG         msg;
    int         idle;
    int         done = FALSE;
    HACCEL      accel;

DWORDprev_tick;
DWORDthis_tick;
D3DVALUEtime_delta;

    prev_inst = prev_inst;
    cmdline = cmdline;

// Register our class if necessary
    if (!prev_inst) {
// Do the stuff for if this is the first instance of the program.
if (!FirstInstance(this_inst)) return FALSE;
}

// Create our window and get it's handle (needs to be done whether we're first or not)
HWND win;
    if (!AnyInstance(this_inst, &win)) return FALSE;

// Initialize the RLDS3D interface
if (!RLDS3D_Initialize(win, this_inst)) {
// If it doesn't initialize then we don't have anything we need to clean up, so we just exit
exit(-1);
}

// Show our window!
    ShowWindow(win, cmdshow);
    UpdateWindow(win);

// Load accelerators
    accel = LoadAccelerators(this_inst, "ViewerAccel");

prev_tick = timeGetTime();

    while (!done)
    {   idle = TRUE;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {   idle = FALSE;
            if (msg.message == WM_QUIT)
            {   done = TRUE;
                break;
            }

            if (!TranslateAccelerator(msg.hwnd, accel, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

        }
if (idle) Idle();
this_tick = timeGetTime();
time_delta = D3DVAL((float)((this_tick - prev_tick)/1000.0));
prev_tick = this_tick;
RLDS3D_Render(time_delta);
    }
RLDS3D_Deinitialize();
return msg.wParam;
}

/*
 * Register window class for the application, and do any other
 * application initialization
 */
static BOOL FirstInstance(HINSTANCE this_inst)
{
    WNDCLASS    wc;
    BOOL        rc;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = this_inst;
    wc.hIcon = LoadIcon(this_inst, "ViewerIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = "ViewerMenu";
    wc.lpszClassName = ViewerClass;
    rc = RegisterClass(&wc);

    return rc;
}

/*
 * Do work required for every instance of the application:
 * create the window, initialize data
 */
static BOOL AnyInstance(HINSTANCE this_inst, HWND* window_handle)
{
    HWND win;
    char szCaption[] = "DirectSound3D Demo";

    /*
     * create main window
     */
    win =
        CreateWindow
        (   ViewerClass,                /* class */
            szCaption,                  /* caption */
            WS_OVERLAPPEDWINDOW,        /* style */
            CW_USEDEFAULT,              /* init. x pos */
            CW_USEDEFAULT,              /* init. y pos */
            400,                        /* init. x size */
            400,                        /* init. y size */
            NULL,                       /* parent window */
            NULL,                       /* menu handle */
            this_inst,                  /* program handle */
            NULL                        /* create parms */
        );

    if (!win) return FALSE;
*window_handle = win;
    return TRUE;
}

/*
 * Processes messages for the about dialog.
 */
BOOL WINAPI AboutDlgProc
    (HWND win, unsigned msg, WPARAM wparam, LPARAM lparam)
{
    lparam = lparam;

    switch (msg)
    {
    case WM_INITDIALOG:
        return TRUE;

    case WM_COMMAND:
        if (wparam == IDOK)
        {
            AboutBoxOn = FALSE;
            EndDialog(win, TRUE);
            return TRUE;
        }
        break;
    case WM_CLOSE:
        {
            AboutBoxOn = FALSE;
            EndDialog(win, TRUE);
            return FALSE;
        }
        break;
    }
    return FALSE;
}

static int
    left_drag = FALSE, right_drag = FALSE,
    last_x, last_y;

static void Idle()
{
// Stop selected item being dragged from rotating/moving
    if (left_drag) {
RLDS3D_SetSelectedRotRelToCam(D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0));
}
    if (right_drag) {
RLDS3D_SetSelectedVelRelToCam(D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
}
}

static int FillModeToMenuItem(D3DRMFILLMODE fm)
{
    switch (fm) {
    case D3DRMFILL_POINTS:
        return 2;

    case D3DRMFILL_WIREFRAME:
        return 3;

    case D3DRMFILL_SOLID:
        return 4;
    }
    return -1;
}

// Sets the polygon fill mode and checks/unchecks the appropriate menu items
static void SetFillMode(HWND win, D3DRMFILLMODE fm)
{
    HMENU menu;
    D3DRMFILLMODE oldfm = RLDS3D_GetPolygonFillMode();
    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, FillModeToMenuItem(oldfm),
                  MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, FillModeToMenuItem(fm),
                  MF_BYPOSITION|MF_CHECKED);
RLDS3D_SetPolygonFillMode(fm);
}

static int ShadeModeToMenuItem(D3DRMSHADEMODE sm)
{
    switch (sm) {
    case D3DRMSHADE_FLAT:
        return 6;

    case D3DRMSHADE_GOURAUD:
        return 7;

    case D3DRMSHADE_PHONG:
        return 8;
    }
    return -1;
}

// Sets the polygon shade mode and checks/unchecks the appropriate menu items
static void SetShadeMode(HWND win, D3DRMSHADEMODE sm)
{
    HMENU menu;
    D3DRMSHADEMODE oldsm = RLDS3D_GetPolygonShadeMode();

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, ShadeModeToMenuItem(oldsm),
                  MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, ShadeModeToMenuItem(sm),
                  MF_BYPOSITION|MF_CHECKED);
RLDS3D_SetPolygonShadeMode(sm);
}

// Sets the rendering model and checks/unchecks the appropriate menu items
static void SetModel(HWND win, D3DRMCOLORMODEL model)
{
    HMENU menu;
    D3DRMCOLORMODEL oldModel = RLDS3D_GetColourModel();

    if (oldModel == model) return;

    RLDS3D_SetColourModel(model);

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, 9 + (int) oldModel, MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, 9 + (int) model, MF_BYPOSITION|MF_CHECKED);
}

// Toggles dithering and checks/unchecks menu item
static void ToggleDither(HWND win)
{
    HMENU menu;
    int dither = RLDS3D_GetDither();
    int checked;
    dither = !dither;
RLDS3D_SetDither(dither);

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);

    if (dither) checked = MF_CHECKED;
    else checked = MF_UNCHECKED;

    CheckMenuItem(menu, 13, MF_BYPOSITION|checked);
}

// Toggles texture filtering and checks/unchecks menu item
static void ToggleTextureFiltering(HWND win)
{
    HMENU menu;
    D3DRMTEXTUREQUALITY tq = RLDS3D_GetTextureQuality();
    int checked;
    if (tq == D3DRMTEXTURE_NEAREST)
        tq = D3DRMTEXTURE_LINEAR;
    else
        tq = D3DRMTEXTURE_NEAREST;

    RLDS3D_SetTextureQuality(tq);
    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);

    if (tq == D3DRMTEXTURE_LINEAR) checked = MF_CHECKED;
    else checked = MF_UNCHECKED;

    CheckMenuItem(menu, 14, MF_BYPOSITION|checked);
}

// Ditto for lighting
static void ToggleLighting(HWND win)
{
    HMENU menu;
    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    if (RLDS3D_GetLighting()) {
    CheckMenuItem(menu, 0, (MF_BYPOSITION | MF_UNCHECKED));
RLDS3D_SetLighting(FALSE);
}
else {
    CheckMenuItem(menu, 0, (MF_BYPOSITION | MF_CHECKED));
RLDS3D_SetLighting(TRUE);
}
}

/*
** Windows procs for the sound param dlg boxes
*/

BOOL CALLBACK WaitBoxDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam) {
return FALSE;
};

BOOL CALLBACK GlobalParamDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    lparam = lparam;

    char lpszTS[100];

    switch (msg)
    {
case WM_INITDIALOG:
{
// Set up our edit fields appropriately
                D3DVALUE temp;
RLDS3D_GetDistanceFactor(&temp);
sprintf(lpszTS, "%f",temp);
SendDlgItemMessage(win, IDC_DISTANCEFACTOR, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_DISTANCEFACTOR, EM_SETLIMITTEXT, 10, 0);
RLDS3D_GetRolloffFactor(&temp);
sprintf(lpszTS, "%f",temp);
SendDlgItemMessage(win, IDC_ROLLOFFFACTOR, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_ROLLOFFFACTOR, EM_SETLIMITTEXT, 10, 0);
RLDS3D_GetDopplerFactor(&temp);
sprintf(lpszTS, "%f",temp);
SendDlgItemMessage(win, IDC_DOPPLERFACTOR, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_DOPPLERFACTOR, EM_SETLIMITTEXT, 10, 0);
return TRUE;
}
case WM_CLOSE:
EndDialog(win, TRUE);
return FALSE;

case WM_COMMAND:
            {
                if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_DOPPLERFACTOR) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_DOPPLERFACTOR, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_DOPPLERFACTOR, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
// No real need for error checking 'cause the edit field is numbers only
RLDS3D_SetDopplerFactor(D3DVAL(atof(lpszTS)));
                    return TRUE;
                }
                else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_ROLLOFFFACTOR) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_ROLLOFFFACTOR, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_ROLLOFFFACTOR, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
RLDS3D_SetRolloffFactor(D3DVAL(atof(lpszTS)));
                    return TRUE;
                }
                else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_DISTANCEFACTOR) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_DISTANCEFACTOR, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_DISTANCEFACTOR, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
RLDS3D_SetDistanceFactor(D3DVAL(atof(lpszTS)));
                    return TRUE;
                }
                else return FALSE;
            }
        break;
    }
    return FALSE;
}

HWND hSelCfgDlg = 0;

BOOL CALLBACK SelectedParamDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    lparam = lparam;

    char lpszTS[100];

    switch (msg)
    {
case WM_INITDIALOG:
{
// Set up our edit fields appropriately
                D3DVALUE temp;
LONG temp2;
DWORD temp3, temp4;
// Ignores the out side cone angle for now, which is fine, because elsewhere we
// set it to equal the inside angle
RLDS3D_GetSelConeAngles(&temp3, &temp4);
sprintf(lpszTS, "%i",temp3);
SendDlgItemMessage(win, IDC_CONEANGLE, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_CONEANGLE, EM_SETLIMITTEXT, 10, 0);
RLDS3D_GetSelConeOutsideVolume(&temp2);
sprintf(lpszTS, "%i",temp2);
SendDlgItemMessage(win, IDC_CONEOUTSIDEVOLUME, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_CONEOUTSIDEVOLUME, EM_SETLIMITTEXT, 10, 0);
RLDS3D_GetSelMaximumDistance(&temp);
sprintf(lpszTS, "%f",temp);
SendDlgItemMessage(win, IDC_MAXIMUMDISTANCE, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_MAXIMUMDISTANCE, EM_SETLIMITTEXT, 10, 0);
RLDS3D_GetSelMinimumDistance(&temp);
sprintf(lpszTS, "%f",temp);
SendDlgItemMessage(win, IDC_MINIMUMDISTANCE, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_MINIMUMDISTANCE, EM_SETLIMITTEXT, 10, 0);
hSelCfgDlg = win;
return TRUE;
}

case WM_CLOSE:
EndDialog(win, TRUE);
hSelCfgDlg = 0;
return FALSE;

case WM_COMMAND:
            {
                if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_CONEANGLE) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_CONEANGLE, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_CONEANGLE, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
// No real need for error checking 'cause the edit field is numbers only
RLDS3D_SetSelConeAngles((DWORD)atol(lpszTS), (DWORD)atol(lpszTS));
                    return TRUE;
                }
                else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_CONEOUTSIDEVOLUME) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_CONEOUTSIDEVOLUME, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_CONEOUTSIDEVOLUME, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
RLDS3D_SetSelConeOutsideVolume(atol(lpszTS));
                    return TRUE;
                }
                else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_MAXIMUMDISTANCE) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_MAXIMUMDISTANCE, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_MAXIMUMDISTANCE, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
RLDS3D_SetSelMaximumDistance(D3DVAL(atof(lpszTS)));
                    return TRUE;
                }
                else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_MINIMUMDISTANCE) {
// Get value from edit box (get length, check lenth, set first char to length, request string, end it with 0)
                    int stringlength = SendDlgItemMessage(win, IDC_MINIMUMDISTANCE, EM_LINELENGTH, 0, 0);
                    if (stringlength > 10) return TRUE;
                    lpszTS[0] = (char)stringlength;
                    SendDlgItemMessage(win, IDC_MINIMUMDISTANCE, EM_GETLINE, 0, (LPARAM) lpszTS);
                    lpszTS[stringlength] = 0;
// Set value in RLDS3D (convert string to long and go crazy with the bad boy)
RLDS3D_SetSelMinimumDistance(D3DVAL(atof(lpszTS)));
                    return TRUE;
                }
                else return FALSE;
            }
        break;
    }
    return FALSE;
}

#define SIGN_EXTEND(w)    ((((int)(w)) << 16) >> 16)

/*
 * Handle messages for the main application window
 */

LONG FAR PASCAL WindowProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    HINSTANCE inst_handle;
    static HCURSOR oldCursor = NULL;

    switch (msg)
    {
    case WM_KEYDOWN:
        {
            switch (wparam)
            {

            case 'A':
   RLDS3D_SetCamVelRelToCam(D3DVAL(5.0), D3DVAL(0.0), D3DVAL(0.0));
                break;

            case 'Z':
RLDS3D_SetCamVelRelToCam(D3DVAL(-5.0), D3DVAL(0.0), D3DVAL(0.0));
                break;

            case VK_RIGHT:
RLDS3D_SetCamRotUp(D3DVAL(1.0));
                break;
            case VK_LEFT:
RLDS3D_SetCamRotUp(D3DVAL(-1.0));
                break;
case VK_UP:
RLDS3D_SetCamRotRight(D3DVAL(1.0));
break;
case VK_DOWN:
RLDS3D_SetCamRotRight(D3DVAL(-1.0));
break;
}
        }
        break;

    case WM_KEYUP:
        switch (wparam)
        {
case VK_LEFT:
case VK_RIGHT:
        case VK_UP:
        case VK_DOWN:
// Stop the camera from rotating completely
RLDS3D_SetCamRotForward(D3DVAL(0.0));
break;

case 'A':
case 'Z':
RLDS3D_SetCamVelRelToCam(D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
            break;
        }
        break;

    case WM_LBUTTONDOWN:
        {   HCURSOR hCur;
            int x = LOWORD(lparam);
            int y = HIWORD(lparam);
            last_x = x;
            last_y = y;
BOOL bChanged = FALSE;
            RLDS3D_FindAndSelectVisual(x, y, &bChanged);
// Get rid of the Selected Object Cfg dialog if we've selected a new object
if (bChanged && hSelCfgDlg) EndDialog(hSelCfgDlg, TRUE);
            left_drag = TRUE;
            SetCapture(win);
            /* change to a groovy cursor */
            hCur = LoadCursor(NULL, IDC_ARROW);
            if (hCur) oldCursor = SetCursor(hCur);
            else oldCursor = NULL;
        }
        break;

    case WM_LBUTTONUP:
        ReleaseCapture();
        left_drag = FALSE;
        if (oldCursor) SetCursor(oldCursor);
        break;

    case WM_RBUTTONDOWN:
        {
            HCURSOR hCur;
            int x = LOWORD(lparam);
            int y = HIWORD(lparam);
            last_x = x;
            last_y = y;
BOOL bChanged = FALSE;
            RLDS3D_FindAndSelectVisual(x, y, &bChanged);
// Get rid of the Selected Object Cfg dialog if we've selected a new object
if (bChanged && hSelCfgDlg) EndDialog(hSelCfgDlg, TRUE);
            right_drag = TRUE;
            SetCapture(win);
            /* change to a groovy cursor */
            hCur = LoadCursor(NULL, IDC_ARROW);
            if (hCur) oldCursor = SetCursor(hCur);
            else oldCursor = NULL;
        }
        break;

    case WM_RBUTTONUP:
        right_drag = FALSE;
        ReleaseCapture();
        if (oldCursor) SetCursor(oldCursor);
        break;

    case WM_MOUSEMOVE:
        if ((wparam & MK_LBUTTON) && left_drag)
        {   double delta_x, delta_y;
            delta_x = SIGN_EXTEND(LOWORD(lparam)) - last_x;
            delta_y = -SIGN_EXTEND((HIWORD(lparam)) - last_y);
            last_x = SIGN_EXTEND(LOWORD(lparam));
            last_y = SIGN_EXTEND(HIWORD(lparam));
            {
                double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y);
                double radius = 50;
                double denom;

                denom = 0.05 * sqrt(radius * radius + delta_r * delta_r);

                if (delta_r == 0 || denom == 0) break;
RLDS3D_SetSelectedRotRelToCam(D3DDivide(D3DVAL((float) delta_y), D3DVAL((float) delta_r)),
  D3DDivide(D3DVAL((float) -delta_x), D3DVAL((float) delta_r)),
  D3DVAL(0.0),
  D3DDivide(D3DVAL((float) delta_r), D3DVAL((float) denom)));
            }
        }
        else if ((wparam & MK_RBUTTON) && right_drag)
        {   double delta_x, delta_y;

            delta_x = SIGN_EXTEND(LOWORD(lparam)) - last_x;
            delta_y = SIGN_EXTEND(HIWORD(lparam)) - last_y;
            last_x = SIGN_EXTEND(LOWORD(lparam));
            last_y = SIGN_EXTEND(HIWORD(lparam));
RLDS3D_MoveSelectedPosByScreenCoords(delta_x, delta_y);
        }
        break;
    case WM_COMMAND:
        switch( wparam & 0xffff )
        {
case MENU_MOTION_ORBITSELECTEDOBJECT:
{
RLDS3D_OrbitSelected();
}
break;
case MENU_MOTION_BULLETSELECTEDOBJECT:
{
RLDS3D_BulletSelected();
}
break;
case MENU_SOUND_ATTACHSOUND:
{
if (RLDS3D_SoundInitialized())
{
if (RLDS3D_FrameSelected()) {
char* file = OpenNewSoundFile(win);
if (file) RLDS3D_AttachSound(file);
}
else MessageBox(win, "No object selected to attach a sound to!", "Doh!", MB_ICONEXCLAMATION);
}
else MessageBox(win, "Sound not initialized!", "Doh!", MB_ICONEXCLAMATION);
}
break;
case MENU_SOUND_REMOVESOUND:
RLDS3D_RemoveSound();
break;
case MENU_SOUND_REMOVEALLSOUNDS:
RLDS3D_RemoveAllSounds();
break;

case MENU_SOUND_PLAYSOUND:
RLDS3D_PlaySound(FALSE);
break;
case MENU_SOUND_PLAYSOUNDLOOPING:
RLDS3D_PlaySound(TRUE);
break;
case MENU_SOUND_STOPSOUND:
RLDS3D_StopSelectedSound();
break;

case MENU_SOUND_STOPALLSOUNDS:
RLDS3D_StopAllSounds();
break;

case MENU_SOUND_SELECTEDSOUNDPROPERTIES:
{
if (RLDS3D_SoundInitialized()) {
if (RLDS3D_SoundSelected()) {
inst_handle = (HINSTANCE) GetWindowLong(win, GWL_HINSTANCE);
HWND mydial = CreateDialog(inst_handle, "SelectedParamBox", win, SelectedParamDlgProc);
if (mydial) {
ShowWindow(mydial, SW_SHOW);
UpdateWindow(mydial);
}
}
else MessageBox(win, "No sound currently selected", "Doh!", MB_ICONEXCLAMATION);
}
else MessageBox(win, "Sound not initialized!", "Doh!", MB_ICONEXCLAMATION);
}
break;
case MENU_SOUND_GLOBALPROPERTIES:
{
if (RLDS3D_SoundInitialized()) {
inst_handle = (HINSTANCE) GetWindowLong(win, GWL_HINSTANCE);
HWND mydial = CreateDialog(inst_handle, "GlobalParamBox", win, GlobalParamDlgProc);
if (mydial) {
ShowWindow(mydial, SW_SHOW);
UpdateWindow(mydial);
}
}
else MessageBox(win, "Sound not initialized!", "Doh!", MB_ICONEXCLAMATION);
}
break;

case MENU_FILE_ABOUT:
{
inst_handle = (HINSTANCE) GetWindowLong(win, GWL_HINSTANCE);
DialogBox(inst_handle,"AboutBox", win, AboutDlgProc);
}
            break;

        case MENU_FILE_OPEN:
            {
                char *file = OpenNewFile(win);
if (file) {
RLDS3D_LoadXOF(file);
                }
            }
            break;

        case MENU_FILE_EXIT:
            PostQuitMessage(0);
            break;

        case MENU_EDIT_CUT:
            RLDS3D_CutVisual();
            break;

        case MENU_EDIT_COPY:
            RLDS3D_CopyVisual();
            break;

        case MENU_EDIT_PASTE:
            RLDS3D_PasteVisual();
            break;

        case MENU_EDIT_DELETE:
            RLDS3D_DeleteVisual();
            break;

        case MENU_EDIT_COLOUR:
// Currently everything is handled there but the selection wheel maybe should be out here?
RLDS3D_SetSelColour();
            break;

        case MENU_EDIT_BOXES:
            {
                HMENU menu;
if (RLDS3D_GetBoxes() == TRUE) RLDS3D_SetBoxes(FALSE); else RLDS3D_SetBoxes(TRUE);
                int checked = (RLDS3D_GetBoxes()==TRUE) ? MF_CHECKED : MF_UNCHECKED;
                menu = GetMenu(win);
                menu = GetSubMenu(menu, 1);
                CheckMenuItem(menu, MENU_EDIT_BOXES, MF_BYCOMMAND|checked);
            }
            break;

case MENU_QUALITY_LIGHTING:
ToggleLighting(win);
break;

// Fill Modes
case MENU_QUALITY_POINTS:
SetFillMode(win, D3DRMFILL_POINTS);
break;

        case MENU_QUALITY_WIREFRAME:
            SetFillMode(win, D3DRMFILL_WIREFRAME);
            break;

        case MENU_QUALITY_SOLID:
            SetFillMode(win, D3DRMFILL_SOLID);
            break;

// Shading modes
        case MENU_QUALITY_FLAT:
            SetShadeMode(win, D3DRMSHADE_FLAT);
            break;

        case MENU_QUALITY_GOURAUD:
            SetShadeMode(win, D3DRMSHADE_GOURAUD);
            break;

        case MENU_QUALITY_PHONG:
            SetShadeMode(win, D3DRMSHADE_PHONG);
            break;

// Color models
        case MENU_MODEL_MONO:
            SetModel(win, D3DCOLOR_MONO);
            break;

        case MENU_MODEL_RGB:
            SetModel(win, D3DCOLOR_RGB);
            break;

        case MENU_DITHER:
            ToggleDither(win);
            break;

        case MENU_TEXTURE_FILTERING:
            ToggleTextureFiltering(win);
            break;

        case MENU_LIGHT_DIRECTIONAL:
RLDS3D_AddDirectionalLight();
break;
        case MENU_LIGHT_PARALLEL_POINT:
RLDS3D_AddParallelPointLight();
break;
        case MENU_LIGHT_POINT:
RLDS3D_AddPointLight();
break;
        case MENU_LIGHT_SPOT:
RLDS3D_AddSpotlight();
break;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage( 0 );
        break;

    case WM_SIZE:
        {
            int width = LOWORD(lparam);
            int height = HIWORD(lparam);
            RLDS3D_ResizeViewport(width, height);
        }
        break;

    case WM_ACTIVATE:
{
RLDS3D_HandleActivate(wparam);
}
        break;

    case WM_PAINT:
if (RLDS3D_WinDevice())
{
RECT r;
            PAINTSTRUCT ps;

            if (GetUpdateRect(win, &r, FALSE))
            {   BeginPaint(win, &ps);
RLDS3D_HandlePaint(&ps);
                EndPaint(win, &ps);
            }
        }
        else return DefWindowProc(win, msg, wparam, lparam);

    default:
        return DefWindowProc(win, msg, wparam, lparam);
    }
    return 0L;
}


README.TXT

DirectSound Mixing Test (dsshow)
--------------------------------

This app allows the user to play with most of the controls exposed by
DirectSoundBuffers, including the mixing of multiple sounds, and changing
frequency, pan, and volume of those sounds.

Open one or more .wav files using the File.Open dialog.  A control bar will
be displayed for each file opened.  Dsshow uses static buffers for its sounds
and is therefore designed for short sounds only.  If you try to open a file
that is very large, you will run out of system memory - beware!

The control bar for each sound contains a bunch of things.  First, the file
name is displayed at the top.  The next line states whether the buffer is
mixed in software (SW) or hardware (HW), and whether the buffer is playing
or stopped.  Dsshow will put buffers in hardware whenever possible.  On the
next two lines, the current play and write cursors are displayed.  They are
updated every half second or so.  Then there are sliders to control the
buffers frequency, pan, and volume settings.  At the bottom, there is a
Play/Stop button (it morphs back and forth, try it!) and a Close button (to
delete the file from the view), and a checkbox for Looping.

The dsshow title bar tells you information about the hardware mixing
capabilities of your sound card.  It displays the number of free hw mixing
channels and hw memory (if you have an ISA card like most people).  If both
of those numbers are 0, your card does not have hardware mixing.

There are also two interesting things you can do from the Options menu.
First, you can set the format of the primary buffer by selecting
Options.Output Type.  This will list the formats supported by the primary
buffer, and let you choose one.  Also, you can toggle Options.Enumerate Drivers
which will show you what objects DirectSoundEnumerate will list for you,
and allow you to select one.  This only happens when you start up Dsshow,
so to see the effect, select this menu item, exit Dsshow, and then run Dsshow
again.


DSENUM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dstrenum.c
 *  Content:   Illustrates enumerating DirectSound drivers
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <memory.h>

#include "dsenum.h"
#include "resource.h"

extern HINSTANCE hInst;
extern HWND      hWndMain;

/****************************************************************************/
/* DoDSoundEnumerate()                                                      */
/*                                                                          */
/*   This function takes care of handling the DirectSound enumeration, which*/
/* simply means creating a dialog box, at this point...                     */
/****************************************************************************/
BOOL DoDSoundEnumerate( LPGUID lpGUID )
    {
    if( DialogBoxParam( hInst, MAKEINTRESOURCE(IDD_DSENUMBOX), hWndMain,
                        DSEnumDlgProc, (LPARAM)lpGUID ))
        return( TRUE );

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumDlgProc()                                                          */
/*                                                                          */
/*   Dialog procedure for the DSound enumeration choice dialog. Allows the  */
/* user to choose from installed drivers.  Returns TRUE on error.           */
/****************************************************************************/
BOOL CALLBACK DSEnumDlgProc( HWND hDlg, UINT msg,
                                WPARAM wParam, LPARAM lParam )
    {
    static HWND   hCombo;
    static LPGUID lpGUID;
    LPGUID        lpTemp;
    int           i;

    switch( msg )
        {
        case WM_INITDIALOG:
            hCombo = GetDlgItem( hDlg, IDC_DSENUM_COMBO );
            lpGUID = (LPGUID)lParam;

            if( DirectSoundEnumerate( (LPDSENUMCALLBACK)DSEnumProc, &hCombo ) != DS_OK )
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            if( ComboBox_GetCount( hCombo ))
                ComboBox_SetCurSel( hCombo, 0 );
            else
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            return( TRUE );

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDOK:
                    for( i = 0; i < ComboBox_GetCount( hCombo ); i++ )
                        {
                        lpTemp = (LPGUID)ComboBox_GetItemData( hCombo, i );
                        if( i == ComboBox_GetCurSel( hCombo ))
                            {
                            if( lpTemp != NULL )
                                memcpy( lpGUID, lpTemp, sizeof(GUID));
    else
        lpGUID = NULL;
    }
                        if( lpTemp )
                            LocalFree( lpTemp );
                        }
                    // If we got the NULL GUID, then we want to open the default
                    // sound driver, so return with an error and the init code
    // will know not to pass in the guID and will send NULL
    // instead.
                    if( lpGUID == NULL )
        EndDialog( hDlg, TRUE );
                    else
                        EndDialog( hDlg, FALSE );
                    return( TRUE );

                case IDCANCEL:
                    // Force a NULL GUID
                    EndDialog( hDlg, TRUE );
                    return( TRUE );
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumProc()                                                             */
/*                                                                          */
/*   This is the Enumeration procedure called by DirectSoundEnumerate with  */
/* the parameters of each DirectSound Object available in the system.       */
/****************************************************************************/
BOOL CALLBACK DSEnumProc( LPGUID lpGUID, LPSTR lpszDesc,
                                LPSTR lpszDrvName, LPVOID lpContext )
    {
    HWND   hCombo = *(HWND *)lpContext;
    LPGUID lpTemp = NULL;

    if( lpGUID != NULL )
        {
        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
    return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));
}

    ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    ComboBox_FindString( hCombo, 0, lpszDesc ),
    lpTemp );
    return( TRUE );
    }


DSENUM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsenum.h
 *  Content:    Direct Sound Enumeration defines
 *
 *
 ***************************************************************************/

#ifndef __DSENUM_INCLUDED__
#define __DSENUM_INCLUDED__

// Function declarations

BOOL DoDSoundEnumerate( LPGUID );

BOOL CALLBACK DSEnumProc( LPGUID, LPSTR, LPSTR, LPVOID );
BOOL CALLBACK DSEnumDlgProc( HWND, UINT, WPARAM, LPARAM );

#endif


DEBUG.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.h
 *  Content:debug header
 *
 ***************************************************************************/
#ifndef __DEBUG_INCLUDED__
#define __DEBUG_INCLUDED__
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "DSSHOW"    // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef _WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);
    void WINAPI _Assert( char * szFile, int iLine );

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
    #define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__)
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
    #define ASSERT(x)
#endif

#ifdef __cplusplus
}
#endif
#endif  // __DEBUG_INCLUDED__


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by main.rc
//
#define ID_APPLY                        3
#define IDR_MAINMENU                    101
#define IDD_OUTPUTBUFFERTYPE            102
#define IDD_ABOUT                       103
#define IDD_DSENUMBOX                   109
#define IDI_ICON1                       104
#define IDI_ICON2                       105
#define IDI_ICON3                       107
#define IDD_CHECKLATENCY                108
#define IDD_FILEOPEN_NEST110
#define IDC_FONEST_STICKY111
#define IDC_FORMATS                     1000
#define ID_OK                           1001
#define ID_CANCEL                       1002
#define ID_DONE                         1004
#define IDC_FILES_LB                    1005
#define ID_PLAY                         1006
#define ID_STOP                         1007
#define IDC_DSENUM_COMBO                1008
#define ID_FILE_EXIT                    40002
#define IDPD_FILE_EXIT                  40002
#define IDPD_FILE_OPEN                  40003
#define IDPD_OPTIONS_OUTPUTTYPE         40004
#define IDPD_HELP_ABOUT                 40005
#define IDPD_CHECKLATENCY               40006
#define IDPD_ENUMDRIVERS                40007
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        112
#define _APS_NEXT_COMMAND_VALUE         40008
#define _APS_NEXT_CONTROL_VALUE         1009
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SHELL.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       shell.c
 *  Content:    Direct Sound show-off.
 *  This app basically uses the direct sound api's and pops up some
 *  controls that the user can play with at runtime to change
 *  the sound frequency, panning, volume, etc.   It has a few
 *  other functions built in.
 *
 *  This app also takes a couple command-line parameters.  The format is:
 *
 *DSShow [/PLAY [/LOOP]] [file] [file] ...
 *
 *    Specifying /PLAY causes any specified files to be played as they're
 * opened.  Adding the /LOOP causes them to loop as well.  /LOOP without
 * /PLAY means nothing.  Everything else is assumed to be one or more file
 * names.  Filenames can be enclosed in quotes.  This also means you can
 * drag and drop files onto the program's icon
 *
 *
 ***************************************************************************/

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>

#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <dsound.h>

#include "wassert.h"
#include "wave.h"

#include "resource.h"
#include "shell.h"
#include "dsenum.h"

// =======================================================================
/* Procedure called when the application is loaded for the first time */
// =======================================================================
BOOL ClassInit( hInstance )
HANDLE hInstance;
{
    WNDCLASS    myClass;

    myClass.hCursor             = LoadCursor( NULL, IDC_ARROW );
    myClass.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_ICON3));
    myClass.lpszMenuName        = MAKEINTRESOURCE(IDR_MAINMENU);
    myClass.lpszClassName       = (LPSTR)szAppName;
    myClass.hbrBackground       = (HBRUSH)(COLOR_WINDOW);
    myClass.hInstance           = hInstance;
    myClass.style               = CS_HREDRAW | CS_VREDRAW;
    myClass.lpfnWndProc         = WndProc;
    myClass.cbClsExtra          = 0;
    myClass.cbWndExtra          = 0;

    if (!RegisterClass( &myClass ) )
       return FALSE;

    return TRUE;        /* Initialization succeeded */
}

// =======================================================================
/* This "hook procedure" is called by the common dialog code for certain
 *   events that may occur during the life of our nested dialog structure.
 * We nest the Explorer style dialog inside our file open dialog so we
 *   can addd a check box for stick buffers.
 */
 // =======================================================================
UINT CALLBACK FileOpenCustomTemplateDlgProc( hDlg, message, wParam, lParam )
HWND hDlg;
UINT message;
WPARAM wParam;
LPARAM lParam;
{
    static LPOPENFILENAME   lpofn = NULL;

    switch( message )
    {
    case WM_INITDIALOG:
        lpofn = (LPOPENFILENAME)lParam;

        /* Set the flag to match the current state of the check box control */
        *((LPBOOL)lpofn->lCustData) = SendDlgItemMessage( hDlg, IDC_FONEST_STICKY,
                                                            BM_GETCHECK, 0, 0 );
        return TRUE;

    case WM_NOTIFY:
        switch(((LPOFNOTIFY)lParam)->hdr.code)
        {
        case CDN_SELCHANGE:
            /* Use this area to process anything that must be updated when the
             * user changes the selection in the Common Dialog Box.
             *   NOTE: Provided only for informational purposes
             */
            return FALSE;

        case CDN_FILEOK:
            /* We can do lots of things in this notification message.  The most
             * important is that we can decide whether the Common Dialog call will
             * go through or whether it will fail.  I decided to handle the checkbox
             * control in this one place versus 4 others... -PRN
             */
            Assert( lpofn != NULL );
            *((LPBOOL)lpofn->lCustData) = SendDlgItemMessage( hDlg, IDC_FONEST_STICKY,
                                                                BM_GETCHECK, 0, 0 );
            /* Returning zero signifies that we "approve" of the OK command,
             * and allows the common dialog to finish.
             */
            return FALSE;
        }
        /* Let the default dialog do/continue processing */
        return FALSE;
    }
    return FALSE;
}

// =======================================================================
// =======================================================================
int PASCAL WinMain( hInstance, hPrevInstance, lpszCmdLine, cmdShow )
HINSTANCE hInstance, hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
    MSG   msg;
    HWND  hWnd;

    // We must call this to ensure the common controls are setup for
    // this application
    InitCommonControls();

    if (!hPrevInstance) {
/* Call initialization procedure if this is the first instance */
    if (!ClassInit( hInstance ))
    return FALSE;
}

    hWnd = CreateWindow((LPSTR)szAppName,
(LPSTR)szMessage,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
DX_MINWINDOW,
DY_MINWINDOW,
(HWND)NULL,
(HMENU)NULL,
(HANDLE)hInstance,
(LPSTR)NULL
);

    if (!hWnd) return (int)msg.wParam;

    // Make a long line across the top.
    CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
0,
0,
8000,
2,
hWnd,
(HMENU)0,
hInst,
NULL);

    /* Save instance handle for DialogBox */
    hInst = hInstance;

    ShowWindow( hWnd, cmdShow );

    if( lpszCmdLine && *lpszCmdLine )
        if( !ParseCommandLine( lpszCmdLine ))
    goto Exit_WinMain;

    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0)) {
TranslateMessage((LPMSG)&msg);
DispatchMessage((LPMSG)&msg);
}

Exit_WinMain:
    DestroyWindow(hWnd);
    UnregisterClass(szAppName, hInstance);
    return (int)msg.wParam;
}

// =======================================================================
/*  This function updates the status window by writing the specified
    string to the window, prepended by a string indicating whether
    the buffer is in hardware or software
*/
// =======================================================================
void UpdateStatus(FILEINFO *pFileInfo, DWORD dwStatus)
{
    TCHAR szStatus[200];
    DWORD dwPlay, dwWrite;
    HRESULT hr;

    lstrcpy(szStatus, pFileInfo->fHardware ? szHW : szSW);
    if (dwStatus & DSBSTATUS_BUFFERLOST)
    {
    lstrcat(szStatus, szLost);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else if (dwStatus & DSBSTATUS_PLAYING)
    {
    lstrcat(szStatus, szPlaying);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else
    {
    lstrcat(szStatus, szStopped);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }

    if (pFileInfo->fSticky)
{
lstrcat(szStatus, szSticky);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
}
    hr = IDirectSoundBuffer_GetCurrentPosition(pFileInfo->pDSB, &dwPlay, &dwWrite);
    if (DS_OK == hr) {
    wsprintf(szStatus, szFmtPlayPosition, dwPlay);
    SendMessage(pFileInfo->hWndPlayPosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    wsprintf(szStatus, szFmtWritePosition, dwWrite);
    SendMessage(pFileInfo->hWndWritePosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }

    return;
}

// =======================================================================
/*  This function updates the main window title to show some
    relevant information about the direct sound object
*/
// =======================================================================
void UpdateMainStatus()
{
    DSCAPS  dsc;
    TCHAR   szTitle[200];

    // Update main window title with some relevant info
    dsc.dwSize = sizeof(dsc);
    IDirectSound_GetCaps(gpds, &dsc);
    wsprintf(szTitle, "%s : free hw memory = %dKb, free hw buffers = %d",
 szMessage, (dsc.dwFreeHwMemBytes+512)/1024,
 dsc.dwFreeHwMixingAllBuffers);
    SendMessage(hWndMain, WM_SETTEXT, 0, (LPARAM)szTitle);
    return;
}

// =======================================================================
/*  This routine will set up everything needed for the app to run.

    Input:
hWnd                - App main window handle

    Output:
None.

*/
// =======================================================================
int AppInit(HWND hWnd)
{
    UINT            cT;
    DSBUFFERDESC    dsbd;
    BOOL    fUseGuid;
    HRESULT         hr;
    DWORD           dw;

    // Set up the global window handle.
    hWndMain = hWnd;

    // Set up the global File...Open dialog's start directory
    GetMediaStartPath();

    // Set up the file info header
    FileInfoFirst.pNext = NULL;
    FileInfoFirst.pwfx = NULL;
    FileInfoFirst.cox = COX_STARTCONTROL;
    FileInfoFirst.coy = COY_STARTCONTROL;

    // Clear the coordinate buffer.  Used to find the next available
    // position to use for a new control.  -1 is the invalid value.
    for (cT=0; cT<MAXCONTROLS; cT++) rgfcoxAvail[cT] = FALSE;

    // Setup the timer...
    if ((dwTimer = SetTimer(hWnd, 1, TIMERPERIOD, NULL)) == 0)
{
MessageBox(hWnd, "Cannot allocate timer, aborting", "DirectSound Demo", MB_OK|MB_ICONSTOP);
return -1;
    }

    // Now set up all the direct sound stuff...

    // Get the largest waveformatex structure.
    if (MMSYSERR_NOERROR != acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dw))
    {
MessageBox(hWnd, "ACM Metrics failed, aborting", "DirectSound Demo",
   MB_OK|MB_ICONSTOP);
return -1;
    }

    // Setup the format, frequency, volume, etc.
    if ((FileInfoFirst.pwfx = GlobalAllocPtr(GPTR, dw)) == NULL)
    {
MessageBox(hWnd, "Out of Memory", "DirectSound Demo",
   MB_OK|MB_ICONSTOP);
return -1;
    }

    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 2;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*2*2;
    FileInfoFirst.pwfx->nBlockAlign = 4;
    FileInfoFirst.pwfx->wBitsPerSample = 16;
    FileInfoFirst.pwfx->cbSize = 0;

#ifdef STARTEIGHTBITS

    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 2;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*1*2;
    FileInfoFirst.pwfx->nBlockAlign = 2;
    FileInfoFirst.pwfx->wBitsPerSample = 8;
    FileInfoFirst.pwfx->cbSize = 0;
#endif

#ifdef STARTMONO
    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 1;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*1*2;
    FileInfoFirst.pwfx->nBlockAlign = 2;
    FileInfoFirst.pwfx->wBitsPerSample = 16;
    FileInfoFirst.pwfx->cbSize = 0;
#endif

    // Optionally enumerate DSOUND devices and allow the user to pick one...

    if (!SUCCEEDED(CoInitialize(NULL))) {
MessageBox(hWnd, "Failed to initialize COM library", "DirectSound Demo", MB_OK | MB_ICONSTOP);
return -1;
    }

    fEnumDrivers = (BOOL)GetProfileInt( "DSSHOW", "EnumDrivers", FALSE );
fUseGuid = (fEnumDrivers && !DoDSoundEnumerate(&guID));

// Create the direct sound object.
    hr = CoCreateInstance(&CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
  &IID_IDirectSound, &gpds);

    if (SUCCEEDED(hr) && (NULL != gpds))
{
hr = IDirectSound_Initialize(gpds, fUseGuid ? &guID : NULL);
if (SUCCEEDED(hr))
{
// Note we need to set the level to be priority to set the
// format of the primary buffer
hr = IDirectSound_SetCooperativeLevel(gpds, hWndMain, DSSCL_PRIORITY);
if (SUCCEEDED(hr))
{

// Set up the primary direct sound buffer.
ZeroMemory(&dsbd, sizeof(dsbd));
dsbd.dwSize = sizeof(dsbd);
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;

hr = IDirectSound_CreateSoundBuffer(gpds, &dsbd, &(FileInfoFirst.pDSB), NULL);
if (SUCCEEDED(hr))
{
hr = IDirectSoundBuffer_Play(FileInfoFirst.pDSB, 0, 0, DSBPLAY_LOOPING);
if (SUCCEEDED(hr))
{
UpdateMainStatus();
}
else
{
MessageBox(hWnd, "Cannot play primary buffer","DirectSound Demo", MB_OK|MB_ICONSTOP);
IDirectSoundBuffer_Release(FileInfoFirst.pDSB);
FileInfoFirst.pDSB = NULL;
    }
}
else
{
MessageBox(hWnd, "Cannot create primary buffer","DirectSound Demo", MB_OK|MB_ICONSTOP);
}
    }
else
{
MessageBox(hWnd, "DirectSound SetCooperativeLevel failed","DirectSound Demo", MB_OK|MB_ICONSTOP);
    }
} //initialize
else
{
    MessageBox(hWnd, "Failed to Initialize DirectSound object", "DirectSound Demo", MB_OK | MB_ICONSTOP);
}

if (!SUCCEEDED(hr))
{
    IDirectSound_Release(gpds);
    gpds = NULL;
}

 }
else
{
MessageBox(hWnd, "Failed to create DirectSound COM object",
   "DirectSound Demo", MB_OK | MB_ICONSTOP);
}

if (SUCCEEDED(hr))
{
return 0;
}
else
{
CoUninitialize();
return -1;
}
}

// =======================================================================
/*  This will destroy all the created objects, allocated memory, etc.  Must be called
    before termination of app.

    Input:
hWnd                - Window handle of main window

    Output:
None.

*/
// =======================================================================
void AppDestroy( HWND hWnd )
{

    HRESULT     hr = 0;

    if (dwTimer != 0)
    {
KillTimer(hWnd, dwTimer);
dwTimer = 0;
    }

    StopAllDSounds(hWnd, &FileInfoFirst);
    FreeAllList(hWnd, &FileInfoFirst);

    // Destroy the direct sound buffer.
    if(FileInfoFirst.pDSB != NULL)
    {
IDirectSoundBuffer_Stop(FileInfoFirst.pDSB);
IDirectSoundBuffer_Release(FileInfoFirst.pDSB);
FileInfoFirst.pDSB = NULL;
    }

    // Destroy the direct sound object.
    if (gpds != NULL)
    {
IDirectSound_Release(gpds);
gpds = NULL;
CoUninitialize();
    }

    if (FileInfoFirst.pwfx != NULL)
    {
GlobalFreePtr(FileInfoFirst.pwfx);
FileInfoFirst.pwfx = NULL;
    }

    if (FileInfoFirst.pbData != NULL)
    {
GlobalFreePtr(FileInfoFirst.pbData);
FileInfoFirst.pbData = NULL;
    }

    WriteProfileString( "DSSHOW", "EnumDrivers", fEnumDrivers ? "1" : "0" );

}

// =======================================================================
/* Procedures which make up the window class. */
// =======================================================================
long FAR PASCAL WndProc( hWnd, message, wParam, lParam )
HWND hWnd;
unsigned message;
WPARAM wParam;
LPARAM lParam;
{

    switch (message)
{

case WM_CREATE:
    if (AppInit(hWnd)) return (-1);
    break;

case WM_TIMER:
    if (!UIMainWindowTimerHandler(hWnd, wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

case WM_HSCROLL:
    if (!UIMainWindowHSBHandler(hWnd, wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));

    break;

case WM_VSCROLL:
    if (!UIMainWindowVSBHandler(hWnd, wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

case WM_INITMENU:
if((HMENU)wParam != GetMenu( hWnd ))
    break;
CheckMenuItem((HMENU)wParam, IDPD_ENUMDRIVERS,
fEnumDrivers ? MF_CHECKED : MF_UNCHECKED );
break;

case WM_COMMAND:
    if (!UIMainWindowCMDHandler(hWnd, wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

    break;

/*case WM_PAINT:
    {

    break;
    }*/

case WM_DESTROY:
    AppDestroy(hWnd);
    PostQuitMessage( 0 );
    break;

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

}

    return(0L);
}

// =======================================================================
/*  This routine will pop up the open file dialog and open a file, and make any internal
    arrangements so we know the file is loaded.

    Input:
hWnd            -   Handle of parent window.

    Output:
None.

*/
// =======================================================================
void PD_FileOpen( HWND hWnd )
{

    char            szFileName[MAX_PATH];
    UINT            cSamples;
    FILEINFO        *pFileInfo                  = NULL;
    int             nFileName;
    BOOL            fSticky;

    if (GetNumControls(&FileInfoFirst) >= MAXCONTROLS)
    {
    MessageBox(hWnd, "No more controls allowed",
   "Hold on a sec...", MB_OK);
    return;
    }

    // Open the file, and check its format, etc.
    if (OpenFileDialog(hWnd, szFileName, &nFileName, &fSticky))
    {

    // Allocate the memory for the structure.
    if ((pFileInfo = GlobalAllocPtr(GPTR, sizeof(FILEINFO))) == NULL)
    {
MessageBox(hWnd, "Cannot add this file",
       "Out of Memory", MB_OK|MB_ICONSTOP);
goto ERROR_DONE_ROUTINE;
    }

    pFileInfo->pbData   = NULL;
    pFileInfo->pwfx     = NULL;
    pFileInfo->pDSB     = NULL;
    pFileInfo->fSticky  = fSticky;
    strcpy(pFileInfo->szFileName, szFileName);

    if (WaveLoadFile(szFileName, &pFileInfo->cbSize,
    &cSamples, &pFileInfo->pwfx, &pFileInfo->pbData) != 0)
    {
MessageBox(hWnd, "Bad wave file or file too big to fit in memory",
"Cannot load wave", MB_OK|MB_ICONSTOP);
goto ERROR_DONE_ROUTINE;
    }

    GetNextControlCoords(&FileInfoFirst,
     &pFileInfo->cox, &pFileInfo->coy);

    if (NewDirectSoundBuffer(pFileInfo) != 0)
    {
MessageBox(hWnd, "Cannot create new buffer",
       "Direct Sound Error", MB_OK|MB_ICONSTOP);
goto ERROR_DONE_ROUTINE;
    }

    Assert(pFileInfo->pbData != NULL);

    // If we fail after this, make sure to update the list!!!
    if (AddToList(&FileInfoFirst, pFileInfo) != 0)
    {
MessageBox(hWnd, "Cannot add file to list",
   "Out of Memory", MB_OK|MB_ICONSTOP);
goto ERROR_DONE_ROUTINE;
    }

    pFileInfo->nFileName = nFileName;
    CreateControl(hWnd, pFileInfo, pFileInfo->pwfx->nSamplesPerSec,
      (MAXPAN_TB-MINPAN_TB)/2, MINVOL_TB );
    ChangeOutputVol(pFileInfo);
    ChangeOutputFreq(pFileInfo);
    ChangeOutputPan(pFileInfo);
    UpdateMainStatus();

    }

    goto DONE_ROUTINE;

ERROR_DONE_ROUTINE:
    if (pFileInfo != NULL)
    {

    ReleaseDirectSoundBuffer(pFileInfo);

    if (pFileInfo->pwfx != NULL)
    {
GlobalFreePtr(pFileInfo->pwfx);

    }
    if (pFileInfo->pbData != NULL)
    {
GlobalFreePtr(pFileInfo->pbData);
    }

    GlobalFreePtr(pFileInfo);
    pFileInfo = NULL;
    }

DONE_ROUTINE:
    return;

}

// =======================================================================
/*  This routine will initialize a new direct sound buffer,
    set the data in the buffer,
    set the rate, format, etc...

    Input:
pFileInfo   -   Pointer to file info with all
nessecary info filled,
like pbData, cbData, etc...

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int NewDirectSoundBuffer(FILEINFO *pFileInfo)
{

    DSBUFFERDESC        dsbd;
    DSBCAPS         dsbc;
    HRESULT         hr;
    BYTE            *pbData         = NULL;
    BYTE            *pbData2        = NULL;
    DWORD           dwLength;
    DWORD           dwLength2;

    // Set up the direct sound buffer.
    memset(&dsbd, 0, sizeof(DSBUFFERDESC));
    dsbd.dwSize                 = sizeof(DSBUFFERDESC);
    dsbd.dwFlags                = 0;
    dsbd.dwFlags                |= DSBCAPS_STATIC;
    // Use new GetCurrentPosition() accuracy (DirectX 2 feature)
    dsbd.dwFlags                |= DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2;
    if (pFileInfo->fSticky)
        dsbd.dwFlags |= DSBCAPS_STICKYFOCUS;
    dsbd.dwBufferBytes               = pFileInfo->cbSize;
    dsbd.lpwfxFormat            = pFileInfo->pwfx;
    if ((hr = gpds->lpVtbl->CreateSoundBuffer(gpds,
  &dsbd,
  &(pFileInfo->pDSB),
  NULL )) != 0)
{
goto ERROR_IN_ROUTINE;
}

    // Ok, lock the sucker down, and copy the memory to it.
    if ((hr = pFileInfo->pDSB->lpVtbl->Lock(pFileInfo->pDSB,
0,
pFileInfo->cbSize,
&pbData,
&dwLength,
&pbData2,
&dwLength2,
    0L)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    Assert(pbData != NULL);
    memcpy(pbData, pFileInfo->pbData, pFileInfo->cbSize);

    // Ok, now unlock the buffer, we don't need it anymore.
    if ((hr = pFileInfo->pDSB->lpVtbl->Unlock(pFileInfo->pDSB,
      pbData, pFileInfo->cbSize,
      NULL, 0)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    pbData = NULL;

    if ((hr = pFileInfo->pDSB->lpVtbl->SetVolume(pFileInfo->pDSB,
    MAXVOL_VAL)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    if ((hr = pFileInfo->pDSB->lpVtbl->SetPan(pFileInfo->pDSB,
    MIDPAN_VAL)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    dsbc.dwSize = sizeof(dsbc);
    if (hr = IDirectSoundBuffer_GetCaps(pFileInfo->pDSB, &dsbc))
    {
    goto ERROR_IN_ROUTINE;
    }

    if (dsbc.dwFlags & DSBCAPS_LOCHARDWARE) {
    pFileInfo->fHardware = TRUE;
    } else {
    pFileInfo->fHardware = FALSE;
    }

    goto DONE_ROUTINE;

ERROR_IN_ROUTINE:
    if (pbData != NULL)
    {
    hr = pFileInfo->pDSB->lpVtbl->Unlock(pFileInfo->pDSB, pbData,
pFileInfo->cbSize, NULL, 0);
    pbData = NULL;
    }

    if (pFileInfo->pDSB != NULL)
    {
    pFileInfo->pDSB->lpVtbl->Release(pFileInfo->pDSB);
    pFileInfo->pDSB = NULL;
    }

DONE_ROUTINE:

    return(hr);

}

// =======================================================================
/*  This routine will release a direct sound buffer,
    freeing up memory, resources,
    whatever.

    Input:
pFileInfo   -   Pointer to the file info,
with the proper stuff set.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int ReleaseDirectSoundBuffer( FILEINFO *pFileInfo )
{

    if (pFileInfo->pDSB != NULL)
    {
    pFileInfo->pDSB->lpVtbl->Release(pFileInfo->pDSB);
    pFileInfo->pDSB = NULL;
    }

    return(0);

}

// =======================================================================
/*  This routine will find the next x and y coordinates to
    write the control to.
    The rgfcoxAvail is an array of booleans.
    If false, then the index can be
    used as an x coordinate.

    Input:
pFileInfoHead - Header of the linked list.
pcox, pcoy    - Filled upon return with next
coordinates to use.

    Output:
Only pcox and pcoy change.

*/
// =======================================================================
void GetNextControlCoords(
    FILEINFO    *pFileInfoHead,
    int         *pcox,
    int         *pcoy
    )
{
    UINT            cT;

    for (cT=0; cT<MAXCONTROLS; cT++)
    {
    if (rgfcoxAvail[cT] == FALSE)
    {
rgfcoxAvail[cT] = TRUE;
break;
    }

    }

    if (cT == MAXCONTROLS)
    {
    Assert(FALSE);
    // Couldn't find a place to put control, shouldn't happen though.
    cT = 666;       // Well, at least put it off screen.
    }

    *pcox = cT*DX_CONTROLSPACING+COX_STARTCONTROL;      //Offsetting the text from the border
    *pcoy = COY_STARTCONTROL;

}

// =======================================================================
/*  CreateControl

    This will create the control used for the window, actually it is a
    bundle of controls put together.  I was thinking of a good way to
    figure out id codes for the controls but found no good way except a
    "funny" way...I'm going to use the x coordinate of the control as the
    id for the first control, then id+1 for the second control.  Since
    all the controls have different x coordinates, this is fine, as long
    as the # of windows in the control is not more than the spacing of
    the controls.

    Input:
hWnd                -   Parent Window.
pFileInfo           -   Pointer to FileInfo structure with the cox and coy filled.
dwFreq, dwPan, dwVol-   Default track bar values.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int CreateControl(HWND hWnd, FILEINFO *pFileInfo, DWORD dwFreq,DWORD dwPan, DWORD dwVol)
{
    intcox, coy;
    int     coxOld, coyOld;
    int     nError = 0;
    DWORDidBase;
    SIZE    Size;
    HDC     hDC = NULL;
DWORD dwMinFreq, dwMaxFreq;

    /* Figure out the values of dwPan and dwVol that the track bars like */

    idBase = pFileInfo->cox;
    Assert(pFileInfo != NULL);
    cox = pFileInfo->cox+DX_TEXTSPACING;
    coy = pFileInfo->coy+DY_TEXTSPACING;        //We may have to shift this

    coxOld = cox;
    coyOld = coy;
coy -= 8;                       //We must adjust to fit the text in the border

    if ((hDC = GetDC(hWnd)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    if (!GetTextExtentPoint32(hDC, pFileInfo->szFileName+pFileInfo->nFileName, strlen(pFileInfo->szFileName+pFileInfo->nFileName), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    //Creates the Filename window
    if ((pFileInfo->hWndFileName_TXT = CreateWindow(
"STATIC",
pFileInfo->szFileName+pFileInfo->nFileName,
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_FILENAME_TXT,
Size.cy,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}
//Create line under Filename
    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;

    if ((pFileInfo->hWndFileName_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    // Now create status if required.

    #ifdef SHOWSTATUS

    if (!GetTextExtentPoint32(hDC, szPlaying, strlen(szPlaying), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    //Creates Status Window
    if ((pFileInfo->hWndStatus_TXT = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_STATUS_TXT,
Size.cy, // + DY_TEXTSPACING,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;

    //Create line under Status
    if ((pFileInfo->hWndStatus_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    //Creates PlayPos Window
    if ((pFileInfo->hWndPlayPosition_TXT = CreateWindow(
"STATIC", "",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_STATUS_TXT,
Size.cy,
hWnd,
NULL,
hInst,
NULL)) == NULL)
    {
nError = -1;
goto DONE_ROUTINE;
    }

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING; //Create line under PlayPos

    if ((pFileInfo->hWndPlayPosition_EDGE = CreateWindow(
"STATIC", "",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
NULL,
hInst,
NULL)) == NULL)
    {
nError = -1;
goto DONE_ROUTINE;
    }

    //Creates WritePos Window

if ((pFileInfo->hWndWritePosition_TXT = CreateWindow(
"STATIC", "",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_STATUS_TXT,
Size.cy,
hWnd,
NULL,
hInst,
NULL)) == NULL)
    {
nError = -1;
goto DONE_ROUTINE;
    }

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;

    //Create line under WritePos
    if ((pFileInfo->hWndWritePosition_EDGE = CreateWindow(
"STATIC", "",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
NULL,
hInst,
NULL)) == NULL)
    {
nError = -1;
goto DONE_ROUTINE;
    }

    #endif

    //Set up the Freq Text
    if (!GetTextExtentPoint32(hDC, szFreq, strlen(szFreq), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    // Make the frequency text there.
    if ((pFileInfo->hWndFreq_TXT = CreateWindow(
"STATIC",
szFreq,
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_FREQ_TXT,
Size.cy,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    coy += Size.cy;

    // Make the frequency trackbar.
    if ((pFileInfo->hWndFreq_TB = CreateWindow(
TRACKBAR_CLASS,
"",
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTH,
cox,
coy,
DX_FREQ_TB,
DY_FREQ_TB,
hWnd,
(HMENU)(idBase+idFreqTB),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

// get the min and max range that the sound card supports.
// If the buffer is in hardware query the card, else use
// our ifdef'd values.
if (pFileInfo->fHardware)
{
DSCAPS dsc;
memset(&dsc, 0, sizeof(DSCAPS));
dsc.dwSize = sizeof(DSCAPS);
    nError = IDirectSound_GetCaps(gpds, &dsc);
Assert(nError == DS_OK);
dwMinFreq = dsc.dwMinSecondarySampleRate;
dwMaxFreq = dsc.dwMaxSecondarySampleRate;
}
else
{
dwMinFreq = DSBFREQUENCY_MIN;
dwMaxFreq = DSBFREQUENCY_MAX;
}

SendMessage(pFileInfo->hWndFreq_TB, TBM_SETRANGEMIN, FALSE, dwMinFreq / FREQFACTOR );
SendMessage(pFileInfo->hWndFreq_TB, TBM_SETRANGEMAX, FALSE, dwMaxFreq / FREQFACTOR );

    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPAGESIZE, 0, FREQPAGE );
    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPOS, TRUE, (dwFreq + FREQADD)/FREQFACTOR);
    pFileInfo->dwFreq = dwFreq;

    coy += DY_FREQ_TB+DY_PANSPACING;

    if ((pFileInfo->hWndFreq_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_PANSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    //Adjusts the relative position of the Text
    coy -= (((DY_PANSPACING+DY_TEXTSPACING)/2)-((DY_LOOPEDSPACING+DY_TEXTSPACING)/2));

    if (!GetTextExtentPoint32(hDC, szPan, strlen(szPan), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    // Make the pan text there.
    if ((pFileInfo->hWndPan_TXT = CreateWindow(
"STATIC",
szPan,
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_PAN_TXT,
Size.cy,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    coy += Size.cy;

    // Make the pan trackbar.
    if ((pFileInfo->hWndPan_TB = CreateWindow(
TRACKBAR_CLASS,
"",
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTH,
cox,
coy,
DX_PAN_TB,
DY_PAN_TB,
hWnd,
(HMENU)(idBase+idPanTB),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    SendMessage(pFileInfo->hWndPan_TB, TBM_SETRANGE, FALSE, MAKELONG(MINPAN_TB, MAXPAN_TB));
    SendMessage(pFileInfo->hWndPan_TB, TBM_SETPOS, TRUE, dwPan);
SendMessage(pFileInfo->hWndPan_TB, TBM_SETPAGESIZE, 0, PANPAGE );
    pFileInfo->dwPan = dwPan;

    coy += DY_PAN_TB + DY_VOLSPACING;

    if ((pFileInfo->hWndPan_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_VOLSPACING+DY_TEXTSPACING)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    //Adjusts the relative position of the Text
    coy -= (((DY_PANSPACING+DY_TEXTSPACING)/2)-((DY_LOOPEDSPACING+DY_TEXTSPACING)/2));

    if (!GetTextExtentPoint32(hDC, szVolume, strlen(szVolume), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    // Make the volume text there.
    if ((pFileInfo->hWndVol_TXT = CreateWindow(
"STATIC",
szVolume,
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox,
coy,
DX_VOL_TXT,
Size.cy,
hWnd,
(HMENU)idBase,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    coy += Size.cy;

    // Make the volume trackbars.
    // Create main volume bar.
    if ((pFileInfo->hWndVolM_TB = CreateWindow(
TRACKBAR_CLASS,
"",
WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_BOTH,
cox,
coy,
DX_VOL_TB,
DY_VOL_TB,
hWnd,
(HMENU)(idBase+idVolMTB),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    SendMessage(pFileInfo->hWndVolM_TB, TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB));
    SendMessage(pFileInfo->hWndVolM_TB, TBM_SETPOS, TRUE, dwVol);
    pFileInfo->dwVol = MAXVOL_TB - dwVol;

    // Now the left volume.
    if ((pFileInfo->hWndVolL_TB = CreateWindow(
TRACKBAR_CLASS,
"",
WS_CHILD | WS_VISIBLE |WS_DISABLED| TBS_VERT | TBS_BOTH,
cox+DX_VOL_TB+DX_VOLSPACING_TB,
coy,
DX_VOL_TB,
DY_VOL_TB,
hWnd,
(HMENU)(idBase+idVolLTB),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB));
    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETPOS, TRUE, MAXVOL_TB);

    if ((pFileInfo->hWndVolL_TXT = CreateWindow(
"STATIC",
"L",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox+DX_VOL_TB*3/2+DX_VOLSPACING_TB/2,
coy+DY_VOL_TB+DY_VOLSPACINGY,
DX_VOLUMECHAR,
Size.cy,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    // And right volume.
    if ((pFileInfo->hWndVolR_TB = CreateWindow(
TRACKBAR_CLASS,
"",
WS_CHILD | WS_VISIBLE | WS_DISABLED | TBS_VERT | TBS_BOTH,
cox+DX_VOL_TB*2+DX_VOLSPACING_TB*2,
coy,
DX_VOL_TB,
DY_VOL_TB,
hWnd,
(HMENU)(idBase+idVolRTB),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    SendMessage(pFileInfo->hWndVolR_TB,
    TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB));
    SendMessage(pFileInfo->hWndVolR_TB,
    TBM_SETPOS, TRUE, MAXVOL_TB);

    if ((pFileInfo->hWndVolR_TXT = CreateWindow(
"STATIC",
"R",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
cox+DX_VOL_TB*5/2+DX_VOLSPACING_TB/2+2,
    // +2 to look nice.
coy+DY_VOL_TB+DY_VOLSPACINGY,
DX_VOLUMECHAR,
Size.cy,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    coy += DY_VOL_TB + DY_BEFOREFIRSTBUTTON;    //Line under L & R

    if ((pFileInfo->hWndVol_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
cox,
coy - (DY_BEFOREFIRSTBUTTON)/2,
DX_LINEEDGE,
DY_LINEEDGE,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    if (!GetTextExtentPoint32(hDC, szPlay, strlen(szPlay), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    //Create Play Button
    if ((pFileInfo->hWndPlay_BN = CreateWindow(
"BUTTON",
szPlay,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
cox,
coy,
DX_BUTTONSPACING,
Size.cy + DY_BUTTONSPACING,
hWnd,
(HMENU)(idBase+idPlayBN),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    //coy += Size.cy + DY_BUTTONSPACING + DY_BETWEENBUTTONS;

    if (!GetTextExtentPoint32(hDC, szPlay, strlen(szPlay), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    //Make Remove button
    if ((pFileInfo->hWndRemove_BN = CreateWindow(
"BUTTON",
szRemove,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
cox + DX_BUTTONSPACING + DY_BETWEENBUTTONS,
coy,
DX_BUTTONSPACING,
Size.cy + DY_BUTTONSPACING,
hWnd,
(HMENU)(idBase+idRemoveBN),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    coy += Size.cy + DY_BUTTONSPACING+ DY_BETWEENBUTTONS;

    //Set up Looped Checkbox
    if (!GetTextExtentPoint32(hDC, szLooped, strlen(szLooped), &Size))
{
nError = -1;
goto DONE_ROUTINE;
}

    //Create Looped Checkbox window
    if ((pFileInfo->hWndLooped_BN = CreateWindow(
"BUTTON",
szLooped,
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
cox,
coy,
DX_LOOPED_TXT,
Size.cy + DY_TEXTSPACING -2,
hWnd,
(HMENU)(idBase+idLoopedBN),
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    // Don't need the between buttons spacing
    //  because there are no more controls.
    coy += Size.cy;// + DY_BUTTONSPACING; //+ DY_BETWEENBUTTONS;

    if ((pFileInfo->hWndWhole_EDGE = CreateWindow(
"STATIC",
"",
WS_CHILD | WS_VISIBLE | SS_ETCHEDFRAME,
coxOld-DX_FRAMEEDGE,
coyOld-DY_FRAMEEDGE,
DX_CONTROLSPACING-DX_FRAMEEDGEINNER,
coy - coyOld + DY_FRAMEEDGE*2,
hWnd,
(HMENU)0,
hInst,
NULL)) == NULL)
{
nError = -1;
goto DONE_ROUTINE;
}

    SetAllText(pFileInfo);
    UpdateLRVolume(pFileInfo);

DONE_ROUTINE:
    if (hDC != NULL)
    {
if (ReleaseDC(hWnd, hDC) == 0)
{
nError = -1;
goto DONE_ROUTINE;
}
    }

    return(nError);

}

// =======================================================================
/*  This will add to the linked list of FileInfo's.
    The FileInfo's keep track of the
    files loaded, and this is done in a linked list format

    Input:
pFileInfoHead   -   Top of linked list.
pFileInfo   -   Pointer to entry to add.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int AddToList(FILEINFO *pFileInfoHead, FILEINFO *pFileInfo)
{
    pFileInfo->pNext = NULL;
    pFileInfo->fPlaying = FALSE;

    while (pFileInfoHead->pNext != NULL)
{
pFileInfoHead = pFileInfoHead->pNext;
}

    pFileInfoHead->pNext = pFileInfo;

    return(0);

}

// =======================================================================
/*  This routine will get the number of controls in the window.
    Can be used to determine new size of window.

    Input:
pFileInfoHead           -   Header of linked list.

    Output:
# of controls.
*/
// =======================================================================
int GetNumControls( FILEINFO *pFileInfoHead )
{

    int cT  = 0;

    while (pFileInfoHead->pNext != NULL)
    {
    pFileInfoHead = pFileInfoHead->pNext;
    cT++;
    }

    return(cT);

}

// =======================================================================
/*  This routine will free the whole linked list in pFileInfoFirst,
    including all the
    memory used by the wave file, waveformatex structure, etc.
*/
// =======================================================================
int FreeAllList(HWND hWnd, FILEINFO *pFileInfoFirst)
{

    FILEINFO        *pFileInfo, *pFileNext;
    UINT        cT;

    Assert(pFileInfoFirst != NULL);
    pFileInfo = pFileInfoFirst->pNext;

    while (pFileInfo != NULL)
    {
    ReleaseDirectSoundBuffer(pFileInfo);
    GlobalFreePtr(pFileInfo->pwfx);
    GlobalFreePtr(pFileInfo->pbData);
    pFileNext = pFileInfo->pNext;
    GlobalFreePtr(pFileInfo);
    pFileInfo = pFileNext;
    }

    for (cT=0; cT<MAXCONTROLS; cT++)
    rgfcoxAvail[cT] = FALSE;

    return(0);

}

// =======================================================================
/*  This routine will remove an entry from the list, i.e. will remove
    pFileInfo and all its allocated memory from the list pointed by the header
    by pFileInfoHead

    Input:
pFileInfo               -   Pointer to entry to remove.
pFileInfoHead           -   Head, first entry.

    Output:
0 if successful, else the error.

*/
// =======================================================================
int RemoveFromList(FILEINFO *pFileInfo, FILEINFO *pFileInfoHead)
{

    FILEINFO        *pFileNext;

    Assert(pFileInfoHead != NULL);

    // This used to be pFileInfoHead != NULL
    while (pFileInfoHead->pNext != NULL)
{
if (pFileInfoHead->pNext == pFileInfo)
    {
    Assert(pFileInfo->cox/DX_CONTROLSPACING < MAXCONTROLS);
    rgfcoxAvail[pFileInfo->cox/DX_CONTROLSPACING] = FALSE;

    DestroyWindow(pFileInfo->hWndFileName_TXT);
    DestroyWindow(pFileInfo->hWndFreq_TB);
    DestroyWindow(pFileInfo->hWndFreq_TXT);
    DestroyWindow(pFileInfo->hWndPan_TB);
    DestroyWindow(pFileInfo->hWndPan_TXT);
    DestroyWindow(pFileInfo->hWndVol_TXT);
    DestroyWindow(pFileInfo->hWndVolL_TB);
    DestroyWindow(pFileInfo->hWndVolR_TB);
    DestroyWindow(pFileInfo->hWndVolM_TB);
    DestroyWindow(pFileInfo->hWndLooped_BN);
    DestroyWindow(pFileInfo->hWndPlay_BN);
    DestroyWindow(pFileInfo->hWndRemove_BN);
    DestroyWindow(pFileInfo->hWndFileName_EDGE);
    DestroyWindow(pFileInfo->hWndLooped_EDGE);
    DestroyWindow(pFileInfo->hWndFreq_EDGE);
    DestroyWindow(pFileInfo->hWndPan_EDGE);
    DestroyWindow(pFileInfo->hWndVol_EDGE);
    DestroyWindow(pFileInfo->hWndWhole_EDGE);
    DestroyWindow(pFileInfo->hWndVolL_TXT);
    DestroyWindow(pFileInfo->hWndVolR_TXT);
    #ifdef SHOWSTATUS
    DestroyWindow(pFileInfo->hWndStatus_TXT);
    DestroyWindow(pFileInfo->hWndStatus_EDGE);
    DestroyWindow(pFileInfo->hWndPlayPosition_TXT);
    DestroyWindow(pFileInfo->hWndPlayPosition_EDGE);
    DestroyWindow(pFileInfo->hWndWritePosition_TXT);
    DestroyWindow(pFileInfo->hWndWritePosition_EDGE);
    #endif

    GlobalFree(pFileInfoHead->pNext->pwfx);
    GlobalFree(pFileInfoHead->pNext->pbData);
    pFileNext = pFileInfoHead->pNext->pNext;
    GlobalFreePtr(pFileInfoHead->pNext);
    pFileInfoHead->pNext = pFileNext;
    break;
    }
pFileInfoHead = pFileInfoHead->pNext;
}

    return(0);
}

// =======================================================================
/*  This will pop up the open file dialog and allow the user to pick one file.

    Input:
hWnd            -   Handle of parent window.
pszFileName         -   String to store filename in, must be at least MAX_PATH long.

    Output:
TRUE if a file was  picked successfully, else FALSE (user didn't pick a file)

 */
// =======================================================================
BOOL OpenFileDialog(HWND hWnd, LPSTR pszFileName, int *nFileName, LPBOOL lpfSticky)
{
    BOOL            fReturn,
    fValid;
    OPENFILENAME    ofn;

    pszFileName[0]          = 0;

    ofn.lStructSize         = sizeof(ofn);
    ofn.hwndOwner           = hWnd;
    ofn.hInstance           = hInst;
    ofn.lpstrFilter         = "Wave Files\0*.wav\0All Files\0*.*\0\0";
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0;
    ofn.nFilterIndex        = 1;
    ofn.lpstrFile           = pszFileName;
    ofn.nMaxFile            = MAX_PATH;
    ofn.lpstrFileTitle      = NULL;
    ofn.nMaxFileTitle       = 0;
    ofn.lpstrInitialDir     = gszCDStartPath;
    ofn.lpstrTitle          = "File Open";
    ofn.Flags               = OFN_FILEMUSTEXIST | OFN_EXPLORER
                                | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_HIDEREADONLY;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = "wav";
    ofn.lCustData           = (LONG)lpfSticky;
    ofn.lpfnHook            = FileOpenCustomTemplateDlgProc;
    ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_FILEOPEN_NEST);

    fValid = FALSE;
    do   {

    if (fReturn = GetOpenFileName(&ofn))
    {
// Set the start path for the next time this dialog is opened
        lstrcpy( gszCDStartPath, pszFileName );
        gszCDStartPath[ofn.nFileOffset] = '\0';

fValid = IsValidWave(pszFileName);
if (!fValid)
{
MessageBox(hWnd, "Wave files must be PCM format!",
       "Invalid Wave File", MB_OK|MB_ICONSTOP);
}
else
*nFileName = ofn.nFileOffset;
    }
    else fValid = TRUE;         // Force break out of loop.

    } while (!fValid);

    return(fReturn);

}

// =======================================================================
/*  This function will determine if the filename passed
    in is a valid wave for this
    app, that is a PCM wave.

    Input:
pszFileName -   FileName to check.

    Output:
FALSE if not a valid wave, TRUE if it is.

*/
// =======================================================================
BOOL IsValidWave(LPSTR pszFileName)
{
    BOOL            fReturn     = FALSE;
    int             nError      = 0;
    HMMIO           hmmio;
    MMCKINFO        mmck;
    WAVEFORMATEX    *pwfx;

    if ((nError = WaveOpenFile(pszFileName, &hmmio, &pwfx, &mmck)) != 0)
{
goto ERROR_IN_ROUTINE;
}

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
{
goto ERROR_IN_ROUTINE;
}

    WaveCloseReadFile(&hmmio, &pwfx);

    fReturn = TRUE;

ERROR_IN_ROUTINE:
    return(fReturn);

}

// =======================================================================
// =======================================================================
BOOL UIMainWindowVSBHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

    pFileInfo = FileInfoFirst.pNext;

    Assert(pFileInfo != NULL);

    while (pFileInfo != NULL)
    {

    if ((HWND)lParam == pFileInfo->hWndVolM_TB)
    {
pFileInfo->dwVol = MAXVOL_TB -
SendMessage(pFileInfo->hWndVolM_TB, TBM_GETPOS, 0, 0);
ChangeOutputVol(pFileInfo);
SetAllText(pFileInfo);
UpdateLRVolume(pFileInfo);
fReturn = TRUE;
    }

    pFileInfo = pFileInfo->pNext;

    }

    return (fReturn);

}

// =======================================================================
/*  This routine will handle all the calls to the WM_HSCROLL
    for the main window, that
    is, all the horizontal scrollbar (and trackbar) messages.

    Input:
Standard parameters (minus the "message" parameter)
for a window callback, though
this is called from the window callback.

    Output:
FALSE if the message isn't processed, else TRUE if it is.
If FALSE, the
return procedure should call the default windows procedure.
*/
// =======================================================================
BOOL UIMainWindowHSBHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

    pFileInfo = FileInfoFirst.pNext;

    Assert(pFileInfo != NULL);

    while (pFileInfo != NULL)
    {

    if ((HWND)lParam == pFileInfo->hWndFreq_TB)
    {
pFileInfo->dwFreq = (SendMessage(pFileInfo->hWndFreq_TB,
TBM_GETPOS, 0, 0) * FREQFACTOR) - FREQADD;
ChangeOutputFreq(pFileInfo);
SetAllText(pFileInfo);
fReturn = TRUE;
    }

    else if ((HWND)lParam == pFileInfo->hWndPan_TB)
    {
pFileInfo->dwPan = SendMessage(pFileInfo->hWndPan_TB,
       TBM_GETPOS, 0, 0);
ChangeOutputPan(pFileInfo);
SetAllText(pFileInfo);
UpdateLRVolume(pFileInfo);
fReturn = TRUE;
    }

    pFileInfo = pFileInfo->pNext;

    }

    return (fReturn);

}

// =======================================================================
/*  This routine will handle all the calls to the WM_COMMAND
    for the main window.

    Input:
    Standard parameters (minus the "message" parameter)
    for a window callback, though
    this is called from the window callback.

    Output:
FALSE if the message isn't processed, else TRUE if it is.
If FALSE, the
return procedure should call the default windows procedure.

*/
// =======================================================================
BOOL UIMainWindowCMDHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    BOOL        fReturn     = FALSE;
    FILEINFO        *pFileInfo;
    FILEINFO        *pFileInfoNext;
    DWORD       dwLooping;

    pFileInfo = FileInfoFirst.pNext;
    while (pFileInfo != NULL)
    {

    pFileInfoNext = pFileInfo->pNext;

    if ((HWND)lParam == pFileInfo->hWndLooped_BN)
    {
pFileInfo->fLooped = SendMessage(pFileInfo->hWndLooped_BN,
 BM_GETCHECK, 0, 0);
// If it is playing then reset the looping to be proper
if( pFileInfo->fPlaying ) {
if( pFileInfo->fLooped ) {
    dwLooping = DSBPLAY_LOOPING;
} else {
    dwLooping = 0;
}
pFileInfo->pDSB->lpVtbl->Play(pFileInfo->pDSB,
0, 0, dwLooping );
}
fReturn = TRUE;
    }
    else if ((HWND)lParam == pFileInfo->hWndPlay_BN)
    {
if (pFileInfo->fPlaying)
{
if (StopDSound(hWnd, pFileInfo) == 0)
{
    SendMessage((HWND)lParam,
WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szPlay);

#ifdef SHOWSTATUS
    UpdateStatus(pFileInfo, 0);
#endif

    fReturn = TRUE;
    break;
}

}
else
{
if (StartDSound(hWnd, pFileInfo) == 0)
{
    SendMessage((HWND)lParam,
WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szStop);
#ifdef SHOWSTATUS
    UpdateStatus(pFileInfo, DSBSTATUS_PLAYING);
#endif

    fReturn = TRUE;
    break;
}

}
fReturn = TRUE;
    }

    else if ((HWND)lParam == pFileInfo->hWndRemove_BN)
    {
ReleaseDirectSoundBuffer(pFileInfo);
RemoveFromList(pFileInfo, &FileInfoFirst);
        UpdateMainStatus();

fReturn = TRUE;
    }

    pFileInfo = pFileInfoNext;

    }

    if (!fReturn)
    {

    switch(wParam)
    {

case IDPD_FILE_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;

case IDPD_FILE_OPEN:
PD_FileOpen(hWnd);
break;

case IDPD_HELP_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUT),
      hWnd, (DLGPROC)DLGHelpAbout);
break;

case IDPD_OPTIONS_OUTPUTTYPE:
DialogBox(hInst, MAKEINTRESOURCE(IDD_OUTPUTBUFFERTYPE),
      hWnd, (DLGPROC)DLGOutputBufferType);
break;

case IDPD_CHECKLATENCY:
StopAllDSounds(hWnd, &FileInfoFirst);
// Now fake that we're on in each voice so the
//timer will update the
// strings in the window.
pFileInfo = FileInfoFirst.pNext;
while (pFileInfo != NULL)
{
    pFileInfo->fPlaying = TRUE;
    pFileInfo = pFileInfo->pNext;
}

DialogBox(hInst, MAKEINTRESOURCE(IDD_CHECKLATENCY),
      hWnd, (DLGPROC)DLGCheckLatency);
break;

    case IDPD_ENUMDRIVERS:
fEnumDrivers = !fEnumDrivers;
if( fEnumDrivers )
    {
    MessageBox( hWnd,
    "Drivers will not be enumerated until DSSHOW is run again.",
    szAppName, MB_OK );
    }
break;

default:
return(FALSE);

    }
    }

    return(TRUE);

}

// =======================================================================
/*  This routine will handle the timer messages.

    Input:
Standard input.

    Output:
TRUE if processed message, otherwise FALSE
*/
// =======================================================================
BOOL UIMainWindowTimerHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    FILEINFO        *pFileInfo;
    BOOL            fReturn             = FALSE;
    DWORD           dwStatus            = 0;

    for (pFileInfo = FileInfoFirst.pNext; pFileInfo != NULL; pFileInfo = pFileInfo->pNext)
    {
    HRESULT hr;

    hr = IDirectSoundBuffer_GetStatus(pFileInfo->pDSB, &dwStatus);
    if (DS_OK != hr) continue;

    if (dwStatus & DSBSTATUS_BUFFERLOST) {
LPBYTE pbData, pbData2;
DWORD  dwLength, dwLength2;

//
//  Restore the buffer, rewrite data, and play
//
hr = IDirectSoundBuffer_Restore(pFileInfo->pDSB);
if (DS_OK == hr) {

hr = IDirectSoundBuffer_Lock(pFileInfo->pDSB, 0,
 pFileInfo->cbSize,
 &pbData, &dwLength,
 &pbData2, &dwLength2,
 0);

if (DS_OK == hr) {

    Assert(pbData != NULL);
    Assert(pFileInfo->pbData != NULL);
    memcpy(pbData, pFileInfo->pbData, pFileInfo->cbSize);

    hr = IDirectSoundBuffer_Unlock(pFileInfo->pDSB,
       pbData, dwLength,
       NULL, 0);

    if (DS_OK == hr) {

    if (pFileInfo->fPlaying) {
if (pFileInfo->fLooped) {
IDirectSoundBuffer_Play( pFileInfo->pDSB, 0, 0,
 DSBPLAY_LOOPING );
} else {
IDirectSoundBuffer_Play( pFileInfo->pDSB, 0, 0,
 0 );
}
    }

    IDirectSoundBuffer_GetStatus(pFileInfo->pDSB, &dwStatus);

    }
}
}
    }

#ifdef SHOWSTATUS
    UpdateStatus(pFileInfo, dwStatus);
#endif

    if (!(dwStatus & DSBSTATUS_BUFFERLOST))
    {
if ((pFileInfo->fPlaying) && (!(dwStatus & DSBSTATUS_PLAYING)) )
{
if (StopDSound(hWnd, pFileInfo) == 0)
{
    SendMessage(pFileInfo->hWndPlay_BN,
WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szPlay);
}
}
    }

    pFileInfo->fLost = dwStatus & DSBSTATUS_BUFFERLOST;

    fReturn = TRUE;
    }

    return (fReturn);

}

// =======================================================================
/*  This routine will start a sound to be played.
    Input:
hWnd        -   Of parent window.
pFileInfo   -   Pointer to file to start,
which is loaded and the
data is filled in the structure,
such as pbData,
etc.

    Output:
0 if successful, else the error code.
*/
// =======================================================================
int StartDSound(HWND hWnd, FILEINFO *pFileInfo)
{
    HRESULT     hr              = 0;
    DWORD           dwLooped;
    DWORD           dwStatus                = 0;

    // Already playing?

    // Start sound here....
    dwLooped = 0;
    if (pFileInfo->fLooped) {
    dwLooped = DSBPLAY_LOOPING;
    }

    if ((hr = pFileInfo->pDSB->lpVtbl->GetStatus(pFileInfo->pDSB,
 &dwStatus)) == 0)
    {
    if ((dwStatus&DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
    {
// Don't bother playing, just restart
if ((hr = pFileInfo->pDSB->lpVtbl->SetCurrentPosition(
    pFileInfo->pDSB, 0)) != 0)
{
MessageBox(hWnd, "Cannot set current position",
       "Direct Sound Error", MB_OK);
}
    }
    // Yes gotos are bad but this is real life not school.
    else goto PLAY_THE_THING;
    }

    else
    {
PLAY_THE_THING:
    if ((hr = pFileInfo->pDSB->lpVtbl->Play(pFileInfo->pDSB,
    0, 0, dwLooped)) != 0)
    {
    MessageBox(hWnd, "Cannot start playing",
   "Direct Sound Error", MB_OK);
    }
    else
    pFileInfo->fPlaying = TRUE;
    }

    return(hr);

}

// =======================================================================
/*  This routine will stop a sound which is playing.

    Input:
hWnd        - Of parent window.
pFileInfo       - Pointer to file to stop playing.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int StopDSound(HWND hWnd, FILEINFO *pFileInfo)
{
    HRESULT     hr          = 0;

    if (!pFileInfo->fPlaying)
    return(0);

    // Stop sound here...
    if ((hr = pFileInfo->pDSB->lpVtbl->Stop(pFileInfo->pDSB)) != 0)
    {
    MessageBox(hWnd, "Cannot stop sound",
   "Direct Sound Error", MB_OK);
    }

else
    pFileInfo->fPlaying = FALSE;

    return(hr);

}

// =======================================================================
/*  This routine will stop all the sounds which are playing.

    Input:
hWnd        - Of parent window.
pFileInfo   - Pointer to file to stop playing.
(i.e. the head)

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int StopAllDSounds(HWND hWnd, FILEINFO *pFileInfo)
{
    while (pFileInfo->pNext != NULL)
{
StopDSound(hWnd, pFileInfo->pNext);
pFileInfo = pFileInfo->pNext;
}

    return(0);

}

// =======================================================================
/*  This routine will set the freq, vol and pan slider text
    according to the value
    passed in.

    Input:
pFileInfo   -   File pointer to set frequency for.

    The dwFreq in the pFileInfo structure must be set.
    This also uses the window handle
    in the pFileInfo structure.

    Output:
None.
*/
// =======================================================================
void SetAllText(FILEINFO    *pFileInfo)
{
    char            szBufT[128];

    sprintf(szBufT, "%s: %lu Hz     ",
szFreq, pFileInfo->dwFreq);
    SetWindowText(pFileInfo->hWndFreq_TXT, szBufT);

    // Change PAN val to show full range
    sprintf(szBufT, "%s: %ld", szPan,
(((LONG)(pFileInfo->dwPan) + SHIFTPAN_TB) * MULTPAN_TB ) );
    SetWindowText(pFileInfo->hWndPan_TXT, szBufT);

    // Change VOLUME val to show full range
    sprintf(szBufT, "%s: %ld", szVolume,
(((LONG)(pFileInfo->dwVol) + SHIFTVOL_TB) * MULTVOL_TB ));
    SetWindowText(pFileInfo->hWndVol_TXT, szBufT);

}

// =======================================================================
/*  This routine will update the left and right
    volume according to main volume
    and pan.

    Input:
pFileInfo   - Pointer to fileinfo to update.

    Output:
Nothing worth using.
*/
// =======================================================================
void UpdateLRVolume(FILEINFO *pFileInfo)
{

    int             volLeft, volRight;

    if (pFileInfo->dwPan < MIDPAN_TB)
    {
    volLeft = pFileInfo->dwVol;
    volRight = (((int)pFileInfo->dwPan)
    *(int)pFileInfo->dwVol)/((int)MIDPAN_TB);
    }
    else
    {
    volLeft = ((((int)pFileInfo->dwPan - MAXPAN_TB)*-1)
   *(int)pFileInfo->dwVol)/((int)MIDPAN_TB);
    volRight = pFileInfo->dwVol;
    }

    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETPOS, TRUE, MAXVOL_TB-volLeft);
    SendMessage(pFileInfo->hWndVolR_TB, TBM_SETPOS, TRUE, MAXVOL_TB-volRight);

}

// =======================================================================
/*  This will change the output panning position for a certain FILEINFO.
    This is
    done by sending messages to the direct sound driver

    Input:
pFileInfo   -   FileInfo to set.  This must contain the
panning value to set.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputPan(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    // Change PAN val  since TB does not go full range
    if ((hr = pFileInfo->pDSB->lpVtbl->SetPan(pFileInfo->pDSB,
(((pFileInfo->dwPan) + SHIFTPAN_TB) * MULTPAN_TB) )) != 0)
    {
goto ERROR_DONE_ROUTINE;
    }

ERROR_DONE_ROUTINE:
    return(hr);

}

// =======================================================================
/*  This will change the output freq for a certain FILEINFO.  This is
    done by sending messages to the direct sound driver

    Input:
pFileInfo                   -   FileInfo to set.  This must contain the
freq value to set.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputFreq(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    if ((hr = pFileInfo->pDSB->lpVtbl->SetFrequency(pFileInfo->pDSB, pFileInfo->dwFreq)) != 0)
{
goto ERROR_DONE_ROUTINE;
}

ERROR_DONE_ROUTINE:
    return(hr);

}

// =======================================================================
/*  This will change the output vol for a certain FILEINFO.  This is
    done by sending messages to the direct sound driver

    Input:
pFileInfo                   -   FileInfo to set.  This must contain the
freq value to set.

    Output:
0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputVol(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    // Shift VOLUME val by 4 bits since TB does not go full range
    if ((hr = pFileInfo->pDSB->lpVtbl->SetVolume(pFileInfo->pDSB,
(((pFileInfo->dwVol) + SHIFTVOL_TB) * MULTVOL_TB) )) != 0)
{
goto ERROR_DONE_ROUTINE;
}

ERROR_DONE_ROUTINE:
    return(hr);

}

// =======================================================================
/*  This is the dialog box handler for the check latency dialog box.

    Input:
Standard dialog box input.

    Output:
Standard dialog box output.

*/
// =======================================================================
long FAR PASCAL DLGCheckLatency(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    static HWND     hWndFiles_LB;
    FILEINFO        *pFileInfo              = NULL;
    int         nSelected;
    int         cT;

    switch(uMsg)
    {
    case WM_INITDIALOG:
hWndFiles_LB = GetDlgItem(hWnd, IDC_FILES_LB);

pFileInfo = FileInfoFirst.pNext;
while (pFileInfo != NULL)
{
SendMessage(hWndFiles_LB,
LB_ADDSTRING,
0,
(LPARAM)(pFileInfo->szFileName
     + pFileInfo->nFileName));
pFileInfo = pFileInfo->pNext;
}

break;

    case WM_COMMAND:
switch(wParam)
{
case ID_DONE:
    PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;

case ID_PLAY:
    if ((nSelected = SendMessage(hWndFiles_LB,
 LB_GETCURSEL, 0, 0))
!= LB_ERR)
    {
    for (cT=0, pFileInfo = FileInfoFirst.pNext;
pFileInfo != NULL;
pFileInfo = pFileInfo->pNext, cT++)
    {
if (cT == nSelected)
{
StartDSound(hWnd, pFileInfo);
break;
}
    }

    }

    break;

case ID_STOP:
    StopAllDSounds(hWnd, &FileInfoFirst);
    break;

default:
    break;

}
break;

    case WM_CLOSE:
StopAllDSounds(hWnd, &FileInfoFirst);
EndDialog(hWnd, 0);
break;

    default:
return(0);
break;

    }

    return(1);

}

// =======================================================================
/*  The help about dialog procedure.

    Input:
Standard windows dialog procedure.

    Output:
Standard windows dialog procedure.

*/
// =======================================================================
long FAR PASCAL DLGHelpAbout(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_INITDIALOG:
break;

    case WM_COMMAND:
switch(wParam)
{
case ID_OK:
    PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;

default:
    break;

}
break;

    case WM_CLOSE:
EndDialog(hWnd, 0);
break;

    default:
return(0);
break;

    }

    return(1);

}

// =======================================================================
/*  The help about dialog procedure.

    Input:
Standard windows dialog procedure.

    Output:
Standard windows dialog procedure.

*/
// =======================================================================
long FAR PASCAL DLGOutputBufferType(HWND hWnd,UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    static HWND     hWndFormats_LB          = NULL;
    int         cT;
    int         nSelection;

    switch(uMsg)
    {
    case WM_INITDIALOG:
// Get the windows we need.
hWndFormats_LB = GetDlgItem(hWnd, IDC_FORMATS);

// Put the strings in the list box.
for (cT=0; cT<C_DROPDOWNPCMFORMATS; cT++)
SendMessage(hWndFormats_LB,
LB_ADDSTRING, 0, (LPARAM)rgszTypes[cT]);

// Get the current format and highlight it in the list box.
if ((nSelection = FormatToIndex(hWnd, &FileInfoFirst)) != LB_ERR)
{
SendMessage(hWndFormats_LB, LB_SETCURSEL, nSelection, 0);
}

break;

    case WM_COMMAND:
    switch(LOWORD(wParam))
{
case IDC_FORMATS:
    if( HIWORD( wParam ) == LBN_DBLCLK )
{
SendMessage( hWnd, WM_COMMAND, MAKEWPARAM( ID_OK, 0 ),
0L );
}
    break;

case ID_OK:
    if ((nSelection = SendMessage(hWndFormats_LB,
LB_GETCURSEL, 0, 0)) != LB_ERR)
    {
    if (IndexToFormat(hWnd, &FileInfoFirst, nSelection)
    == 0)
PostMessage(hWnd, WM_CLOSE, 0, 0);
    }
    break;

case ID_CANCEL:
    PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;

case ID_APPLY:
    if ((nSelection = SendMessage(hWndFormats_LB,
LB_GETCURSEL, 0, 0)) != LB_ERR)
    IndexToFormat(hWnd, &FileInfoFirst, nSelection);

    break;

default:
    break;

}
break;

    case WM_CLOSE:
EndDialog(hWnd, 0);
break;

    default:
return(0);
break;

    }

    return(1);

}

// =======================================================================
/*  This routine will determine the output format in
    terms of an integer from the
    current output rate, type, etc.
    stored in the direct sound routines.   Integer
    values designate the string # in rgszTypes,
    i.e. index 0 is 8000kHz, 8 bit mono,
    etc...

    Input:
hWnd    - Handle of the current window.
pFileInfo   - Pointer to the file info to retrieve the format for.

    Output:
The index of the format, LB_ERR if undetermined.

*/
// =======================================================================
int FormatToIndex(HWND hWnd, FILEINFO *pFileInfo)
{

    WAVEFORMATEX    wfx;
    DWORD       dwWaveStyle;
    DWORD       dwSize;
    int         nError              = 0;

    // Get the format.
    if ((nError = pFileInfo->pDSB->lpVtbl->GetFormat(pFileInfo->pDSB,
    &wfx, sizeof(wfx), &dwSize)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }
    if( dwSize > sizeof( wfx ) ) {
    nError = DSERR_GENERIC;
    goto ERROR_IN_ROUTINE;
    }

    // Change wfx to an integer.
    // Assume theres an error and check all parameters to
    // see if its valid.
    nError = LB_ERR;
    dwWaveStyle = 0;

    if (wfx.wFormatTag != WAVE_FORMAT_PCM)
   goto ERROR_IN_ROUTINE;

    // Check the channels
switch (wfx.nChannels)
{
case 1:
break;

case 2:
    dwWaveStyle |= 1;
break;

    default:
goto ERROR_IN_ROUTINE;
}

    // Check the bits...
switch (wfx.wBitsPerSample)
{
case 8:
break;

case 16:
dwWaveStyle |= 2;
break;

    default:
goto ERROR_IN_ROUTINE;
    }

    // Check the rate.
switch(wfx.nSamplesPerSec)
{
case 8000:
break;

case 11025:
    dwWaveStyle |= 4;
break;

case 22050:
    dwWaveStyle |= 8;
break;

case 44100:
    dwWaveStyle |= 12;
break;

    default:
goto ERROR_IN_ROUTINE;
    }

    nError = (int)dwWaveStyle;

ERROR_IN_ROUTINE:
    return(nError);
}

// =======================================================================
/*  This will convert an index (from a list box for instance)
    to a format by passing
    in the format to direct sound.

    Input:
hWnd        -   Handle to window.
pFileInfo   -   Pointer to current file info.
index       -   Index value to convert to a
    waveformat structure.

    Output:
0 if successful, else the error code.
*/
// =======================================================================
int IndexToFormat(HWND hWnd, FILEINFO *pFileInfo, int index)
{

    int         nError      = 0;

    pFileInfo->pwfx->wFormatTag = WAVE_FORMAT_PCM;

    pFileInfo->pwfx->nChannels = 2;                                     // Assume stereo.
    if ((index%2) == 0)
    pFileInfo->pwfx->nChannels = 1;                                 // Its mono.

    // Assume 16 bit
    pFileInfo->pwfx->nBlockAlign = 2*pFileInfo->pwfx->nChannels;
    pFileInfo->pwfx->wBitsPerSample = 16;
    if ((index%4) < 2) {
    // Its 8 bit.
    pFileInfo->pwfx->nBlockAlign = 1*pFileInfo->pwfx->nChannels;
    pFileInfo->pwfx->wBitsPerSample = 8;
    }

    pFileInfo->pwfx->nSamplesPerSec = 44100;    // Assume 44.1 kHz
    if (index < 4)
pFileInfo->pwfx->nSamplesPerSec = 8000;
    else if (index < 8)
pFileInfo->pwfx->nSamplesPerSec = 11025;
    else if (index < 12)
pFileInfo->pwfx->nSamplesPerSec = 22050;

    pFileInfo->pwfx->nAvgBytesPerSec = pFileInfo->pwfx->nSamplesPerSec *
       pFileInfo->pwfx->nBlockAlign;
    pFileInfo->pwfx->cbSize = 0;

    if ((nError = pFileInfo->pDSB->lpVtbl->SetFormat(pFileInfo->pDSB,
pFileInfo->pwfx)) != DS_OK)         {
MessageBox(hWnd, "Cannot set format buffer",
   "Direct Sound Error", MB_OK);
    goto ERROR_DONE_ROUTINE;

    }

ERROR_DONE_ROUTINE:
    return(nError);

}

// =======================================================================
/* GetMediaStartPath()                                                      */
/*                                                                          */
/*   This helper function attempts to get the media directory for Direct3D, */
/* which is where all the installed DX wave files go. If it can't find that */
/* it settles for the media sub-directory of the Windows directory.         */
// =======================================================================
void GetMediaStartPath( void )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
gszRegKeyDirect3D,
0, KEY_READ, &hReg ))
{
        goto REG_OPEN_FAILED;
}
    else
{
// Query the Registry for the path to the media directory
cbStartPathLen = sizeof( gszCDStartPath );
if( ERROR_SUCCESS != RegQueryValueEx( hReg, gszRegValueD3DPath,
NULL, NULL,
gszCDStartPath, &cbStartPathLen ))
    {
    goto REG_OPEN_FAILED;
    }
RegCloseKey( hReg );
hReg = NULL;
}

    return;

REG_OPEN_FAILED:
    // Start off by getting the Windows directory -- we're trying to build a
    // file path like "C:\WINDOWS\MEDIA", but the WINDOWS directory could be
    // named anything, so we must ask.
    GetWindowsDirectory( gszCDStartPath, sizeof(gszCDStartPath));
    // If there's no trailing backslash, append one
    if( lstrcmp( &gszCDStartPath[lstrlen(gszCDStartPath)], TEXT("\\") ))
lstrcat( gszCDStartPath, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( gszCDStartPath, TEXT("MEDIA"));
    }

// =======================================================================
// fGetToken()
//
//    Parses the command-line string "in place" starting at pszStart.  A ptr
// to the start of the next token and it's length will be the out parameters,
// or NULL and 0 if no token.  Note that *ppszRet will NOT be NULL-terminated
// since the string is part of another string.  That's what then length is for.
//
// Returns: TRUE if a token was retrieved, or FALSE if there was no token.
// =======================================================================
BOOL fGetToken( PSTR pszStart, PSTR *ppszRet, int *pcchRet )
    {
    PSTR  pszCur = pszStart;
    PSTR  pszTokStart;

    if( !pszStart || NULL == ppszRet || NULL == pcchRet )
return FALSE;

    // Skip leading whitespace
    while( *pszCur && (*pszCur == ' ' || *pszCur == '\t'))
pszCur++;

    *ppszRet = NULL;
    *pcchRet = 0;

    if( *pszCur )
{
pszTokStart = pszCur;

while( *pszCur && *pszCur != ' ' && *pszCur != '\t' )
    pszCur++;

*ppszRet = pszTokStart;
*pcchRet = (int)(pszCur - pszTokStart);
}

    if( *pcchRet != 0 )
return TRUE;
    else
return FALSE;
    }

// =======================================================================
// fMatchToken()
//
//    Attempts to match the first cchLen characters of pszDatum to the
// string at pszString.  The comparison is case-insensitive (this function
// is designed for command-line switch matching).
//
// Returns: TRUE if the first cchLen characters are a match, else FALSE.
// =======================================================================
BOOL fMatchToken( PSTR pszString, PSTR pszDatum, int cchLen )
    {
    inti;

    for( i = 0; i < cchLen; i++ )
{
if( CharLower( (LPTSTR)MAKELONG( pszString[i], 0 ))
    != CharLower( (LPTSTR)MAKELONG( pszDatum[i], 0 )))
    return FALSE;
}
    return TRUE;
    }

// =======================================================================
// ParseCommandLine()
//
//    Given a command-line string without the module name, this function will
// parse the command line and takes action on whatever it finds there.
//
// Returns: TRUE if successful, or FALSE if there was an error.
// =======================================================================
BOOL ParseCommandLine(LPSTR lpszCmdLine)
    {
    PSTRpszCur,pszToken;
    PSTRppszFiles[MAXCONTROLS];
    BOOLfStartPlaying = FALSE, fStartLooping = FALSE;
    intcchTokLen = 0, i, nFilesFound;

    pszCur = lpszCmdLine;

    // First get all the command line switches
    while( fGetToken(pszCur, &pszToken, &cchTokLen) &&
   (pszToken[0] == '/' || pszToken[0] == '-' ))
{
pszCur = pszToken + cchTokLen;
pszToken++;

if( fMatchToken( pszToken, "PLAY", 4 ))
    {
    fStartPlaying = TRUE;
    }
else if( fMatchToken( pszToken, "LOOP", 4 ))
    {
    fStartLooping = TRUE;
    }
else
    {
    // We don't recognize this mysterious switch, so eat it and move on
    }
}

    // Anything left on the command-line will be treated as a filename and
    // we'll attempt to open it after we've found them all
    nFilesFound = 0;
    while( fGetToken(pszCur, &pszToken, &cchTokLen) && nFilesFound < MAXCONTROLS )
{
pszCur = pszToken + cchTokLen;
ppszFiles[nFilesFound] = GlobalAllocPtr( GPTR, (cchTokLen+1)*sizeof(char));
// Copy the token out of the command-line string and into our buffer
CopyMemory( ppszFiles[nFilesFound], pszToken, cchTokLen*sizeof(char));
// Append a NULL terminator to what we just copied (to be safe)
*(ppszFiles[nFilesFound] + cchTokLen) = 0;
nFilesFound++;
}
    // This function will take the array of strings we've created and open
    // each string as a file.  It will obey the global fStartPlaying and
    // fStartLooping flags we may have already set above
    if( nFilesFound )
BatchOpenFiles( ppszFiles, nFilesFound, fStartPlaying, fStartLooping );

    // Free the space we allocated
    for( i = 0; i < nFilesFound; i++ )
{
GlobalFreePtr( ppszFiles[i] );
ppszFiles[i] = NULL;
}

    // Returning TRUE means the caller should continue doing what they
    // were doing: we succeeded.
    return TRUE;
    }

// =======================================================================
// BatchOpenFiles()
//
//    Takes an array of string pointers and tries to open each as a file to
// playback.  If fPlay is TRUE, the files will be played as they are being
// opened.  If fLoop is TRUE, they will also be set to loop.
//
// Returns: FALSE in the event of catastrophic failure, otherwise TRUE.
// =======================================================================
BOOL BatchOpenFiles( PSTR *ppszFiles, int nFiles, BOOL fPlay, BOOL fLoop )
    {
    int i;
    FILEINFO *pfi;
    DWORD cSamples;

    // Cap the number of files we can load out of the given set if we'd load
    // too many otherwise
    if( GetNumControls(&FileInfoFirst) + nFiles > MAXCONTROLS )
nFiles = MAXCONTROLS - GetNumControls(&FileInfoFirst);

    for( i = 0; i < nFiles; i++ )
{
if(( pfi = GlobalAllocPtr(GPTR, sizeof(FILEINFO))) == NULL )
    goto BOF_Fail;

ZeroMemory( pfi, sizeof(FILEINFO));
strcpy( pfi->szFileName, ppszFiles[i] );

if( WaveLoadFile( ppszFiles[i], &pfi->cbSize, &cSamples,
  &pfi->pwfx, &pfi->pbData ) != 0 )
    goto BOF_LoopError;

GetNextControlCoords( &FileInfoFirst, &pfi->cox, &pfi->coy );
if( NewDirectSoundBuffer(pfi) != 0)
    goto BOF_LoopError;
Assert( pfi->pbData != NULL );

if( AddToList( &FileInfoFirst, pfi ) != 0 )
    goto BOF_LoopError;

pfi->nFileName = 0;

if( CreateControl( hWndMain, pfi, pfi->pwfx->nSamplesPerSec,
       (MAXPAN_TB-MINPAN_TB)/2, MINVOL_TB ) != 0 )
    {
    ReleaseDirectSoundBuffer(pfi);
    RemoveFromList( pfi, &FileInfoFirst );
    // RemoveFromList will do all the cleanup
    pfi = NULL;
    goto BOF_LoopError;
    }
ChangeOutputVol(pfi);
ChangeOutputFreq(pfi);
ChangeOutputPan(pfi);

// LOOP is only obeyed if PLAY was also specified
if( fPlay )
    {
    if( fLoop )
{
pfi->fLooped = TRUE;
SendMessage( pfi->hWndLooped_BN, BM_SETCHECK, TRUE, 0L );
}
    SendMessage( hWndMain, WM_COMMAND, 0, (LPARAM)pfi->hWndPlay_BN );
    }

// Avoid the in-loop error cleanup by using a continue statement here
// to jump back up to the top
continue;

// Cleanup code in case we fail to open a particular file -- we should
// just ignore this one and continue because we might still be able to
// open other files
    BOF_LoopError:
if( NULL != pfi )
    {
    if( NULL != pfi->pwfx )
{
GlobalFreePtr(pfi->pwfx);
pfi->pwfx = NULL;
}
    if( NULL != pfi->pbData )
{
GlobalFreePtr(pfi->pbData);
pfi->pbData = NULL;
}

    ReleaseDirectSoundBuffer(pfi);
    GlobalFreePtr(pfi);
    pfi = NULL;
    }
}

    UpdateMainStatus();

    return TRUE;

BOF_Fail:
    return FALSE;
    }


SHELL.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       shell.h
 *  Content:    DirectSound shell header
 *
 ***************************************************************************/
#ifndef __SHELL_INCLUDED__
#define __SHELL_INCLUDED__
// If this is defined, then an extra text string under the filename will show
// if the file is stopped or playing.
#define SHOWSTATUS

#define MAXCONTROLS             8

// For setting the ranges on the freq, vol, etc.
// dwFreq*FREQMUL+FREQADD = # to show on screen.

#define FREQFACTOR              (5)
#define FREQADD                 (0)
#define FREQPAGE                (100)
#define PANPAGE                 (5)

#define MINFREQ_TB              0
#define MAXFREQ_TB              100000
#define MINPAN_TB               0
#define MIDPAN_TB               400
#define MAXPAN_TB               800
#define SHIFTPAN_TB             (-400)
#define MULTPAN_TB              (10L)
#define MINPAN_VAL              (-400)
#define MIDPAN_VAL              0
#define MAXPAN_VAL              400
#define MINVOL_TB               0
#define MAXVOL_TB               400
#define SHIFTVOL_TB             (-400)
#define MULTVOL_TB              (10L)
#define MINVOL_VAL              -400
#define MAXVOL_VAL              0

// Id code deltas
#define idFreqTB                1
#define idPanTB                 2
#define idVolLTB                3
#define idVolRTB                4
#define idVolMTB                5
#define idLoopedBN              6
#define idPlayBN                7
#define idRemoveBN              8

// UI stuff.

#define DX_WIN_BORDER           30
#define DY_WIN_BORDER           30
#define DX_MINWINDOW            640
#define DY_MINWINDOW            450// Was 560 Must tweek to keep above the taskbar <= 450

#define DX_CONTROLSPACING       150
#define DY_CONTROLSPACING       0

#define DX_LINEEDGE             (DX_CONTROLSPACING - 20)
#define DY_LINEEDGE             (1)

#define COX_STARTCONTROL        (20)// Defines the left edge of the first file window
#define COY_STARTCONTROL        (10)// Defines the upper edge of the first file window

#define DX_TEXTSPACING          5
#define DY_TEXTSPACING          10

#define DX_LOOPEDSPACING        0  //Relative horizontal offset of top four areas
#define DY_LOOPEDSPACING        -6//Space offset around lines

#define DX_STATUSSPACING        0

#ifdef SHOWSTATUS
#define DY_STATUSSPACING        8
#else
#define DY_STATUSSPACING        0
#endif

#define DX_FREQSPACING          0
#define DY_FREQSPACING          10

#define DY_PANSPACING           10
#define DY_VOLSPACING           10

//Establish the Widths of various windows:
#define DX_FILENAME_TXT         120//Filename window
#define DX_STATUS_TXT           120//Status window
#define DX_FREQ_TXT             120//Frequency window
#define DX_PAN_TXT              100//Pan window
#define DX_VOL_TXT              100//Volume window
#define DX_LOOPED_TXT           100//Checkbox + 'Looped' window

//Establish slider sizes:
#define DX_FREQ_TB              130//Frequency slider width
#define DY_FREQ_TB              40//Frequency slider height
#define DX_PAN_TB130//Pan slider width
#define DY_PAN_TB               40//Pan slider height
#define DX_VOL_TB               40//Master volume slider width
#define DY_VOL_TB               100//Master volume slider height
#define DX_VOLSPACING_TB        5//Distance between master volume and its label

#define DX_VOLUMECHAR           15
#define DY_VOLSPACINGY          -5//Distance between channel mixers and L & R labels

#define DX_BUTTONSPACING        61//Width of the Play and Remove buttons
#define DY_BUTTONSPACING        10
#define DY_BEFOREFIRSTBUTTON    18//Distance above and below Volume->buttons line
#define DY_BETWEENBUTTONS       5//Vertical space between buttons

#define DX_FRAMEEDGE            10//Distance between leftmost control and the window edge
#define DY_FRAMEEDGE            10//Distance between outermost contol and the window edge
#define DX_FRAMEEDGEINNER       4//Distance between file windows

// To check for stopping of sounds, a timer is set...use this for the rate.
#define TIMERPERIOD500// In milliseconds

// In Options/Output Type, there are strings to pick the format...Here are the number of them.
#define C_DROPDOWNPCMFORMATS    16

typedef struct _fileinfo
    {
    BYTE                *pbData;        // Pointer to actual data of file.
    UINT                cbSize;         // Size of data.
    WAVEFORMATEX*pwfx;          // Pointer to waveformatex structure.

    DWORD               dwFreq;         // Frequency.
    DWORD               dwPan;          // Panning info.
    DWORD               dwVol;          // Total volume.
    BOOL                fLooped;        // Looped?

    BOOL                fPlaying;       // Is this one playing?
    BOOL                fLost;          // Is this one lost?
    BOOL                fHardware;      // Is this a hardware buffer?
    BOOL                fSticky;        // Is this a sticky buffer?

    intcox;            // Coordinates of where the structure is
    intcoy;            // printed.

    // HWND's needed.
    HWND                hWndFileName_TXT;   // Filename text string.
    HWND                hWndFreq_TB;        // Trackbar handle.
    HWND                hWndFreq_TXT;       // Text string for freq.
    HWND                hWndPan_TB;         // Trackbar handle.
    HWND                hWndPan_TXT;        // Text string for pan.
    HWND                hWndVol_TXT;        // Text string for volume.
    HWND                hWndVolL_TB;        // Trackbar for volume left.
    HWND                hWndVolR_TB;        // Trackbar for volume right.
    HWND                hWndVolM_TB;        // Main volume.
    HWND                hWndLooped_BN;      // Looped
    HWND                hWndPlay_BN;        // Play
    HWND                hWndRemove_BN;      // Remove.

    HWND                hWndFileName_EDGE;  // The line under filename.
    HWND                hWndLooped_EDGE;    // The line under looped.
    HWND                hWndFreq_EDGE;      // The line under freq.
    HWND                hWndPan_EDGE;       // The line under pan.
    HWND                hWndVol_EDGE;       // The line under volume.
    HWND                hWndWhole_EDGE;     // The whole surrounding edge.
    HWND                hWndVolL_TXT;       // For the L
    HWND                hWndVolR_TXT;       // For the R

    #ifdef SHOWSTATUS
    HWND                hWndStatus_TXT;     // For status.
    HWND                hWndStatus_EDGE;
    HWND                hWndPlayPosition_TXT;
    HWND                hWndPlayPosition_EDGE;
    HWND                hWndWritePosition_TXT;
    HWND                hWndWritePosition_EDGE;
    #endif

    LPDIRECTSOUNDBUFFER pDSB;               // Pointer to direct sound buffer.

    int                 nFileName;          // Index to filename, not including dir.
    char                szFileName[MAX_PATH];
    struct _fileinfo    *pNext;             // Pointer to next file.

    } FILEINFO;

char            szAppName[]     = "Direct Sound Demo";
char            szMessage[]     = "Direct Sound Demo";

char gszCDStartPath[MAX_PATH];        // The path to start the Open dialog in

// Registry Key and Value names that allow us to retrive a path something like
// "C:\DXSDK\SDK\MEDIA", but matching the current install.
const TCHAR gszRegKeyDirect3D[] = TEXT("Software\\Microsoft\\Direct3D");
const TCHAR gszRegValueD3DPath[] = TEXT("D3D Path");

HANDLE          hInst;
HWND            hWndMain        = NULL;
DWORD           dwTimer         = 0;    // Timer handle.
GUID                    guID;
BOOL                    fEnumDrivers = FALSE;

/*  This is the main head of the linked list, but its only used for the
    pNext which will point to the first FILEINFO structure, or NULL if there
    are no files loaded  */
FILEINFO        FileInfoFirst;              // Start of linked list.

char            szFreq[]        = "Freq";
char            szPan[]         = "Pan";
char            szVolume[]      = "Volume";
char            szLooped[]      = "Looped";
char            szPlay[]        = "Play";
char            szStop[]        = "Stop";
char            szRemove[]      = "Close";

#ifdef SHOWSTATUS
char            szFmtPlayPosition[] = "Play %d";
char            szFmtWritePosition[]    = "Write %d";
char            szPlaying[]     = "Playing";
char            szStopped[]     = "Stopped";
char            szSticky[]      = "Sticky";
char                    szLost[]        = "Lost";
char            szHW[]          = "HW-";
char                    szSW[]                  = "SW-";
#endif

char *rgszTypes[C_DROPDOWNPCMFORMATS] =
    {                                       // Index
    "8.000 kHz, 8-Bit, Mono",               // 0
    "8.000 kHz, 8-Bit, Stereo",             // 1
    "8.000 kHz, 16-Bit, Mono",              // 2
    "8.000 kHz, 16-Bit, Stereo",            // 3
    "11.025 kHz, 8-Bit, Mono",              // 4
    "11.025 kHz, 8-Bit, Stereo",            // 5
    "11.025 kHz, 16-Bit, Mono",             // 6
    "11.025 kHz, 16-Bit, Stereo",           // 7
    "22.050 kHz, 8-Bit, Mono",              // 8
    "22.050 kHz, 8-Bit, Stereo",            // 9
    "22.050 kHz, 16-Bit, Mono",             // 10
    "22,050 kHz, 16-Bit, Stereo",           // 11
    "44.100 kHz, 8-Bit, Mono",              // 12
    "44.100 kHz, 8-Bit, Stereo",            // 13
    "44.100 kHz, 16-Bit, Mono",             // 14
    "44.100 kHz, 16-Bit, Stereo"            // 15
    };

BOOL                    rgfcoxAvail[MAXCONTROLS];

LPDIRECTSOUND           gpds            = NULL;

long FAR PASCAL WndProc(HWND, unsigned, WPARAM, LPARAM);
long FAR PASCAL DLGHelpAbout(HWND, UINT, WPARAM, LPARAM);
long FAR PASCAL DLGOutputBufferType(HWND, UINT, WPARAM, LPARAM);
long FAR PASCAL DLGCheckLatency(HWND, UINT, WPARAM, LPARAM);
UINT CALLBACK FileOpenCustomTemplateDlgProc(HWND, UINT, WPARAM, LPARAM);

BOOL ClassInit(HANDLE);
void PD_FileOpen(HWND);
BOOL OpenFileDialog(HWND, LPSTR, int *, LPBOOL);
BOOL IsValidWave(LPSTR);
void GetMediaStartPath(void);

int CreateControl(HWND, FILEINFO *, DWORD, DWORD, DWORD);
void GetNextControlCoords(FILEINFO *, int *, int *);
int AddToList(FILEINFO *, FILEINFO *);
int FreeAllList(HWND, FILEINFO *);
int RemoveFromList(FILEINFO *, FILEINFO *);
int GetNumControls(FILEINFO *);
int StartDSound(HWND, FILEINFO *);
int StopDSound(HWND, FILEINFO *);
int StopAllDSounds(HWND, FILEINFO *);

BOOL UIMainWindowVSBHandler(HWND, WPARAM, LPARAM);
BOOL UIMainWindowHSBHandler(HWND, WPARAM, LPARAM);
BOOL UIMainWindowCMDHandler(HWND, WPARAM, LPARAM);
BOOL UIMainWindowTimerHandler(HWND, WPARAM, LPARAM);
void SetAllText(FILEINFO *);
void UpdateLRVolume(FILEINFO *);

void AppDestroy(HWND);
BOOL AppInit(HWND);
BOOL ParseCommandLine(PSTR);
BOOL BatchOpenFiles(PSTR *,int,BOOL,BOOL);

int NewDirectSoundBuffer(FILEINFO *);
int ReleaseDirectSoundBuffer(FILEINFO *);
int ChangeOutputPan(FILEINFO *);
int ChangeOutputFreq(FILEINFO *);
int ChangeOutputVol(FILEINFO *);

int FormatToIndex(HWND, FILEINFO *);
int IndexToFormat(HWND, FILEINFO *, int);
#endif


WASSERT.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wassert.c
 *  Content:Windows assert handler
 *You must externally define hWndMain and szAppName for this
 *to work.
 *
 ***************************************************************************/

#include <windows.h>

extern HWND hWndMain;
extern char szAppName[];

#include "wassert.h"

#ifdef ASSERT
void AssertFail(char szErr[], char szFileName[], int nLine, char szMessage[])
{
char szT[256];

if (szMessage != NULL)
wsprintf(szT, "Assert(%s);\nFile %s, line %d.  %s", szErr, szFileName, nLine, szMessage);
else
wsprintf(szT, "Assert(%s);\nFile %s, line %d.", szErr, szFileName, nLine);
switch (MessageBox(hWndMain, szT, szAppName, MB_ABORTRETRYIGNORE | MB_ICONSTOP | MB_APPLMODAL))
{
case IDABORT:
SendMessage(hWndMain, WM_CLOSE, 0, 0);
case IDRETRY:
_asm int 3;
// Fall Through //
case IDIGNORE:
break;

} // switch
} // AssertFail

#endif // ASSERT


WASSERT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wassert.h
 *  Content:assert header
 *
 ***************************************************************************/
#ifndef __WASSERT_INCLUDED__
#define __WASSERT_INCLUDED__

#ifdef _DEBUG
#define ASSERT// Enable the Assert() macro
#endif

#ifdef ASSERT

void AssertFail(char [], char [], int, char[]);
#define Assert(f) ((f) ? (void)NULL : (void)AssertFail(#f, __FILE__, __LINE__,NULL))
#define AssertMessage(f, szMessage) ((f) ? (void)NULL : (void)AssertFail(#f, __FILE__, __LINE__, szMessage))

#else
#define Assert(f) (void)NULL// Macro that does nothing
#define AssertMessage(f, szMessage) (void)NULL

#endif // ~ASSERT

#endif


WAVE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:               wave.c
 *  Content:    Wave library routines.
 *      This file is used for loading/saving waves, and reading and
 *      writing waves in smaller blocks.
 *      Uses WaveOpenFile, WaveReadFile and WaveCloseReadFile for
 *      single block access to reading wave files.
 *      Uses WaveCreateFile, WaveWriteFile, WaveCloseWriteFile for
 *      single block access for writing  wave files.
 *      Uses WaveLoadFile to load a whole wave file into memory.
 *      Uses WaveSaveFile to save a whole wave file into memory.
 *
 ***************************************************************************/

/* PROTOTYPES */
#include <windows.h>
#include <mmsystem.h>
#include "wave.h"
#include "wassert.h"
#include "windowsx.h"

/* ROUTINES */
/* -------------------------------------------------------*/

/* This function will open a wave input file and prepare it for reading,
 * so the data can be easily
 * read with WaveReadFile. Returns 0 if successful, the error code if not.
 *      pszFileName - Input filename to load.
 *      phmmioIn    - Pointer to handle which will be used
 *          for further mmio routines.
 *      ppwfxInfo   - Ptr to ptr to WaveFormatEx structure
 *          with all info about the file.
 *
*/
int WaveOpenFile(
char *pszFileName,                              // (IN)
HMMIO *phmmioIn,                                // (OUT)
WAVEFORMATEX **ppwfxInfo,                       // (OUT)
MMCKINFO *pckInRIFF                             // (OUT)
)
{
HMMIO           hmmioIn;
MMCKINFO        ckIn;           // chunk info. for general use.
PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.
WORD            cbExtraAlloc;   // Extra bytes for waveformatex
int             nError;         // Return value.

// Initialization...
*ppwfxInfo = NULL;
nError = 0;
hmmioIn = NULL;

if ((hmmioIn = mmioOpen(pszFileName, NULL, MMIO_ALLOCBUF | MMIO_READ)) == NULL)
{
nError = ER_CANNOTOPEN;
goto ERROR_READING_WAVE;
}

if ((nError = (int)mmioDescend(hmmioIn, pckInRIFF, NULL, 0)) != 0)
{
goto ERROR_READING_WAVE;
}

if ((pckInRIFF->ckid != FOURCC_RIFF) || (pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E')))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Search the input file for for the 'fmt ' chunk.     */
    ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if ((nError = (int)mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

/* Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
    * if there are extra parameters at the end, we'll ignore them */

    if (ckIn.cksize < (long) sizeof(PCMWAVEFORMAT))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Read the 'fmt ' chunk into <pcmWaveFormat>.*/
    if (mmioRead(hmmioIn, (HPSTR) &pcmWaveFormat, (long) sizeof(pcmWaveFormat)) != (long) sizeof(pcmWaveFormat))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

// Ok, allocate the waveformatex, but if its not pcm
// format, read the next word, and thats how many extra
// bytes to allocate.
if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM)
cbExtraAlloc = 0;

else
{
// Read in length of extra bytes.
if (mmioRead(hmmioIn, (LPSTR) &cbExtraAlloc,
(long) sizeof(cbExtraAlloc)) != (long) sizeof(cbExtraAlloc))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

}

// Ok, now allocate that waveformatex structure.
if ((*ppwfxInfo = GlobalAlloc(GMEM_FIXED, sizeof(WAVEFORMATEX)+cbExtraAlloc)) == NULL)
{
nError = ER_MEM;
goto ERROR_READING_WAVE;
}

// Copy the bytes from the pcm structure to the waveformatex structure
memcpy(*ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat));
(*ppwfxInfo)->cbSize = cbExtraAlloc;

// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
if (cbExtraAlloc != 0)
{
if (mmioRead(hmmioIn, (LPSTR) (((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(cbExtraAlloc)),
(long) (cbExtraAlloc)) != (long) (cbExtraAlloc))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}
}

/* Ascend the input file out of the 'fmt ' chunk. */
if ((nError = mmioAscend(hmmioIn, &ckIn, 0)) != 0)
{
goto ERROR_READING_WAVE;

}

goto TEMPCLEANUP;

ERROR_READING_WAVE:
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

TEMPCLEANUP:
*phmmioIn = hmmioIn;

return(nError);

}

/*      This routine has to be called before WaveReadFile as it searchs for the chunk to descend into for
reading, that is, the 'data' chunk.  For simplicity, this used to be in the open routine, but was
taken out and moved to a separate routine so there was more control on the chunks that are before
the data chunk, such as 'fact', etc... */

int WaveStartDataRead(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRIFF
)
{
int                     nError;

nError = 0;

// Do a nice little seek...
if ((nError = mmioSeek(*phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
Assert(FALSE);
}

nError = 0;
//      Search the input file for for the 'data' chunk.
pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a');
if ((nError = mmioDescend(*phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

goto CLEANUP;

ERROR_READING_WAVE:

CLEANUP:
return(nError);
}

/*      This will read wave data from the wave file.  Makre sure we're descended into
the data chunk, else this will fail bigtime!
hmmioIn         - Handle to mmio.
cbRead          - # of bytes to read.
pbDest          - Destination buffer to put bytes.
cbActualRead- # of bytes actually read.

*/

int WaveReadFile(
HMMIO hmmioIn,                          // IN
UINT cbRead,                            // IN
BYTE *pbDest,                           // IN
MMCKINFO *pckIn,                        // IN.
UINT *cbActualRead                      // OUT.

)
{

MMIOINFO    mmioinfoIn;         // current status of <hmmioIn>
int                     nError;
UINT            cT, cbDataIn;

nError = 0;

if (nError = mmioGetInfo(hmmioIn, &mmioinfoIn, 0) != 0)
{
goto ERROR_CANNOT_READ;
}

cbDataIn = cbRead;
if (cbDataIn > pckIn->cksize)
cbDataIn = pckIn->cksize;

pckIn->cksize -= cbDataIn;

for (cT = 0; cT < cbDataIn; cT++)
{
/* Copy the bytes from the io to the buffer. */
if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
    if ((nError = mmioAdvance(hmmioIn, &mmioinfoIn, MMIO_READ)) != 0)
{
goto ERROR_CANNOT_READ;
}
    if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
nError = ER_CORRUPTWAVEFILE;
goto ERROR_CANNOT_READ;
}
}

// Actual copy.
*((BYTE*)pbDest+cT) = *((BYTE*)mmioinfoIn.pchNext)++;
}

if ((nError = mmioSetInfo(hmmioIn, &mmioinfoIn, 0)) != 0)
{
goto ERROR_CANNOT_READ;
}

*cbActualRead = cbDataIn;
goto FINISHED_READING;

ERROR_CANNOT_READ:
*cbActualRead = 0;

FINISHED_READING:
return(nError);

}

/*      This will close the wave file openned with WaveOpenFile.
phmmioIn - Pointer to the handle to input MMIO.
ppwfxSrc - Pointer to pointer to WaveFormatEx structure.

Returns 0 if successful, non-zero if there was a warning.

*/
int WaveCloseReadFile(
HMMIO *phmmio,                                  // IN
WAVEFORMATEX **ppwfxSrc                 // IN
)
{

if (*ppwfxSrc != NULL)
{
GlobalFree(*ppwfxSrc);
*ppwfxSrc = NULL;
}

if (*phmmio != NULL)
{
mmioClose(*phmmio, 0);
*phmmio = NULL;
}

return(0);

}

/*      This routine will create a wave file for writing.  This will automatically overwrite any
existing file with the same name, so be careful and check before hand!!!
pszFileName     - Pointer to filename to write.
phmmioOut               - Pointer to HMMIO handle that is used for further writes
pwfxDest                - Valid waveformatex destination structure.
pckOut                  - Pointer to be set with the MMCKINFO.
pckOutRIFF              - Pointer to be set with the RIFF info.

*/
int WaveCreateFile(
char *pszFileName,                              // (IN)
HMMIO *phmmioOut,                               // (OUT)
WAVEFORMATEX *pwfxDest,                 // (IN)
MMCKINFO *pckOut,                               // (OUT)
MMCKINFO *pckOutRIFF                    // (OUT)

)
{

int             nError;                         // Return value.
DWORD           dwFactChunk;            // Contains the actual fact chunk. Garbage until WaveCloseWriteFile.
MMCKINFO        ckOut1;

dwFactChunk = (DWORD)-1;
nError = 0;

*phmmioOut = mmioOpen(pszFileName, NULL,
MMIO_ALLOCBUF | MMIO_READWRITE|MMIO_CREATE);

    if (*phmmioOut == NULL)
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot save WAVE file
}

    /* Create the output file RIFF chunk of form type 'WAVE'.
     */
    pckOutRIFF->fccType = mmioFOURCC('W', 'A', 'V', 'E');
pckOutRIFF->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOutRIFF, MMIO_CREATERIFF)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* We are now descended into the 'RIFF' chunk we just created.
     * Now create the 'fmt ' chunk. Since we know the size of this chunk,
     * specify it in the MMCKINFO structure so MMIO doesn't have to seek
     * back and set the chunk size after ascending from the chunk.
     */
    pckOut->ckid = mmioFOURCC('f', 'm', 't', ' ');
    pckOut->cksize = sizeof(PCMWAVEFORMAT);   // we know the size of this ck.
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* Write the PCMWAVEFORMAT structure to the 'fmt ' chunk if its that type. */
    if (pwfxDest->wFormatTag == WAVE_FORMAT_PCM)
{
    if (mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(PCMWAVEFORMAT))
!= sizeof(PCMWAVEFORMAT))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}
}

else
{
// Write the variable length size.
if ((UINT)mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(*pwfxDest)+pwfxDest->cbSize)
!= (sizeof(*pwfxDest)+pwfxDest->cbSize))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

}

    /* Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

// Now create the fact chunk, not required for PCM but nice to have.  This is filled
// in when the close routine is called.
ckOut1.ckid = mmioFOURCC('f', 'a', 'c', 't');
ckOut1.cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, &ckOut1, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if (mmioWrite(*phmmioOut, (HPSTR)&dwFactChunk, sizeof(dwFactChunk)) != sizeof(dwFactChunk))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;
}

// Now ascend out of the fact chunk...
    if ((nError = mmioAscend(*phmmioOut, &ckOut1, 0)) != 0)
{
nError = ER_CANNOTWRITE;        // cannot write file, probably
goto ERROR_CANNOT_WRITE;
}

goto DONE_CREATE;

ERROR_CANNOT_WRITE:
// Maybe delete the half-written file?  Ah forget it for now, its good to leave the
// file there for debugging...

DONE_CREATE:
return(nError);

}

/*      This routine has to be called before any data is written to the wave output file, via wavewritefile.  This
sets up the data to write, and creates the data chunk.
*/

int WaveStartDataWrite(
HMMIO *phmmioOut,                               // (IN)
MMCKINFO *pckOut,                               // (IN)
MMIOINFO *pmmioinfoOut                  // (OUT)
)
{

int             nError;

nError = 0;
 /* Create the 'data' chunk that holds the waveform samples.  */
    pckOut->ckid = mmioFOURCC('d', 'a', 't', 'a');
pckOut->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if ((nError = mmioGetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

goto CLEANUP;
ERROR_CANNOT_WRITE:

CLEANUP:
return(nError);
}

/* This routine will write out data to a wave file.
hmmioOut                - Handle to hmmioOut filled by WaveCreateFile
cbWrite                 - # of bytes to write out.
pbSrc                   - Pointer to source.
pckOut                  - pointer to ckOut filled by WaveCreateFile
cbActualWrite   - # of actual bytes written.
pmmioinfoOut    - Pointer to mmioinfoOut filled by WaveCreateFile.

Returns 0 if successful, else the error code.

 */

int WaveWriteFile(
HMMIO hmmioOut,                         // (IN)
UINT cbWrite,                           // (IN)
BYTE *pbSrc,                            // (IN)
MMCKINFO *pckOut,                       // (IN)
UINT *cbActualWrite,            // (OUT)
MMIOINFO *pmmioinfoOut          // (IN)
)
{

int                     nError;
UINT            cT;

nError = 0;

*cbActualWrite = 0;

for (cT=0; cT < cbWrite; cT++)
{
if (pmmioinfoOut->pchNext == pmmioinfoOut->pchEndWrite)
{
pmmioinfoOut->dwFlags |= MMIO_DIRTY;
if ((nError = mmioAdvance(hmmioOut, pmmioinfoOut, MMIO_WRITE)) != 0)
{
    goto ERROR_CANNOT_WRITE;
}
}
*((BYTE*)pmmioinfoOut->pchNext)++ = *((BYTE*)pbSrc+cT);
(*cbActualWrite)++;
}

ERROR_CANNOT_WRITE:
// What to do here?  Well, for now, nothing, just return that error.  (maybe delete the
// file later?

return(nError);

}

/*      This routine will close a wave file used for writing.  Returns 0 if successful, else
the error code.
phmmioOut       - Pointer to mmio handle for saving.
pckOut          - Pointer to the MMCKINFO for saving.
pckOutRiff      - Pointer to the riff MMCKINFO for saving.
pmmioinfoOut- Pointer to mmioinfo for saving.
cSamples        - # of samples saved, for the fact chunk.  For PCM, this isn't used but
  will be written anyway, so this can be zero as long as programs ignore
  this field when they load PCM formats.

*/
int WaveCloseWriteFile(
HMMIO *phmmioOut,               // (IN)
MMCKINFO *pckOut,               // (IN)
MMCKINFO *pckOutRIFF,   // (IN)
MMIOINFO *pmmioinfoOut, // (IN)
DWORD cSamples                  // (IN)
)
{

int                     nError;

nError = 0;

if (*phmmioOut == NULL)
return(0);

pmmioinfoOut->dwFlags |= MMIO_DIRTY;
    if ((nError = mmioSetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
// cannot flush, probably...
goto ERROR_CANNOT_WRITE;
}

 /* Ascend the output file out of the 'data' chunk -- this will cause
     * the chunk size of the 'data' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

// Do this here instead...
if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

nError = mmioSeek(*phmmioOut, 0, SEEK_SET);
if ((nError = (int)mmioDescend(*phmmioOut, pckOutRIFF, NULL, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

nError = 0;
pckOut->ckid = mmioFOURCC('f', 'a', 'c', 't');
if ((nError = mmioDescend(*phmmioOut, pckOut, pckOutRIFF, MMIO_FINDCHUNK)) == 0)
{
// If it didn't fail, write the fact chunk out, if it failed, not critical, just
// assert (below).
    nError = mmioWrite(*phmmioOut, (HPSTR)&cSamples, sizeof(DWORD));
nError = mmioAscend(*phmmioOut, pckOut, 0);
nError = 0;
}
else
{
nError = 0;
Assert(FALSE);
}

// CANTWRITEFACT:
    /* Ascend the output file out of the 'RIFF' chunk -- this will cause
     * the chunk size of the 'RIFF' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

ERROR_CANNOT_WRITE:
if (*phmmioOut != NULL)
{
mmioClose(*phmmioOut, 0);
*phmmioOut = NULL;
}

return(nError);

}

/*      This routine will copy from a source wave file to a destination wave file all those useless chunks
(well, the ones useless to conversions, etc --> apparently people use them!).  The source will be
seeked to the begining, but the destination has to be at a current pointer to put the new chunks.
This will also seek     back to the start of the wave riff header at the end of the routine.

phmmioIn                - Pointer to input mmio file handle.
pckIn                   - Pointer to a nice ckIn to use.
pckInRiff               - Pointer to the main riff.
phmmioOut               - Pointer to output mmio file handle.
pckOut                  - Pointer to nice ckOut to use.
pckOutRiff              - Pointer to the main riff.

Returns 0 if successful, else the error code.  If this routine fails, it still attemps to seek back to
the start of the wave riff header, though this too could be unsuccessful.
*/

int WaveCopyUselessChunks(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRiff,
HMMIO *phmmioOut,
MMCKINFO *pckOut,
MMCKINFO *pckOutRiff)
{

int                             nError;

nError = 0;
// First seek to the stinking start of the file, not including the riff header...
if ((nError = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
nError = ER_CANNOTREAD;
goto ERROR_IN_PROC;
}

nError = 0;

    while (mmioDescend(*phmmioIn, pckIn, pckInRiff, 0) == 0)
    {

//  quickly check for corrupt RIFF file--don't ascend past end!
if ((pckIn->dwDataOffset + pckIn->cksize) > (pckInRiff->dwDataOffset + pckInRiff->cksize))
    goto ERROR_IN_PROC;

switch (pckIn->ckid)
{
    //  explicitly skip these...
    case mmioFOURCC('f', 'm', 't', ' '):
break;

    case mmioFOURCC('d', 'a', 't', 'a'):
break;

    case mmioFOURCC('f', 'a', 'c', 't'):
break;

    case mmioFOURCC('J', 'U', 'N', 'K'):
break;

    case mmioFOURCC('P', 'A', 'D', ' '):
break;

    case mmioFOURCC('c', 'u', 'e', ' '):
break;

    //  copy chunks that are OK to copy
    case mmioFOURCC('p', 'l', 's', 't'):
// although without the 'cue' chunk, it doesn't make much sense
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    case mmioFOURCC('D', 'I', 'S', 'P'):
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    //  don't copy unknown chunks
    default:
break;
}

//  step up to prepare for next chunk..
mmioAscend(*phmmioIn, pckIn, 0);
    }

ERROR_IN_PROC:
{
int nErrorT;
// Seek back to riff header
nErrorT = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET);
}

return(nError);
}

/** BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
 *
 *  DESCRIPTION:
 *
 *
 *  ARGUMENTS:
 *      (LPWAVECONVCB lpwc, LPMMCKINFO lpck)
 *
 *  RETURN (BOOL NEAR PASCAL):
 *
 *
 *  NOTES:
 *
 **  */

BOOL riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
{
    MMCKINFO    ck;
    HPSTR       hpBuf;

    //
    //
    //
    hpBuf = (HPSTR)GlobalAllocPtr(GHND, lpck->cksize);
    if (!hpBuf)
return (FALSE);

    ck.ckid   = lpck->ckid;
    ck.cksize = lpck->cksize;
    if (mmioCreateChunk(hmmioDst, &ck, 0))
goto rscc_Error;

    if (mmioRead(hmmioSrc, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioWrite(hmmioDst, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioAscend(hmmioDst, &ck, 0))
goto rscc_Error;

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (TRUE);

rscc_Error:

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (FALSE);
} /* RIFFSupCopyChunk() */

/*      This routine loads a full wave file into memory.  Be careful, wave files can get
pretty big these days :).
szFileName      -       sz Filename
cbSize          -       Size of loaded wave (returned)
cSamples        -       # of samples loaded.
ppwfxInfo       -       Pointer to pointer to waveformatex structure.  The wfx structure
IS ALLOCATED by this routine!  Make sure to free it!
ppbData         -       Pointer to a byte pointer (globalalloc) which is allocated by this
routine.  Make sure to free it!

Returns 0 if successful, else the error code.
*/

int WaveLoadFile(
char *pszFileName,                      // (IN)
UINT *cbSize,                           // (OUT)
DWORD *pcSamples,                       // (OUT)
WAVEFORMATEX **ppwfxInfo,       // (OUT)
BYTE **ppbData                          // (OUT)
)
{

HMMIO                           hmmioIn;
MMCKINFO                        ckInRiff;
MMCKINFO                        ckIn;
int                                     nError;
UINT                            cbActualRead;

*ppbData = NULL;
*ppwfxInfo = NULL;
*cbSize = 0;

if ((nError = WaveOpenFile(pszFileName, &hmmioIn, ppwfxInfo, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

if ((nError = WaveStartDataRead(&hmmioIn, &ckIn, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

// Ok, size of wave data is in ckIn, allocate that buffer.
if ((*ppbData = (BYTE *)GlobalAlloc(GMEM_FIXED, ckIn.cksize)) == NULL)
{
nError = ER_MEM;
goto ERROR_LOADING;
}

if ((nError = WaveReadFile(hmmioIn, ckIn.cksize, *ppbData, &ckIn, &cbActualRead)) != 0)
{
goto ERROR_LOADING;
}

*cbSize = cbActualRead;
goto DONE_LOADING;

ERROR_LOADING:
if (*ppbData != NULL)
{
GlobalFree(*ppbData);
*ppbData = NULL;
}
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

DONE_LOADING:
// Close the wave file.
if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

return(nError);

}

/*      This routine saves a wave file in currently in memory.
pszFileName -   FileName to save to.  Automatically overwritten, be careful!
cbSize          -       Size in bytes to write.
cSamples        -       # of samples to write, used to make the fact chunk. (if !PCM)
pwfxDest        -       Pointer to waveformatex structure.
pbData          -       Pointer to the data.
*/

int WaveSaveFile(
char *pszFileName,                      // (IN)
UINT cbSize,                            // (IN)
DWORD cSamples,                         // (IN)
WAVEFORMATEX *pwfxDest,         // (IN)
BYTE *pbData                            // (IN)
)
{

HMMIO           hmmioOut;
MMCKINFO        ckOut;
MMCKINFO        ckOutRIFF;
MMIOINFO        mmioinfoOut;
UINT            cbActualWrite;
int                     nError;

if ((nError = WaveCreateFile(pszFileName, &hmmioOut, pwfxDest, &ckOut, &ckOutRIFF)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveStartDataWrite(&hmmioOut, &ckOut, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveWriteFile(hmmioOut, cbSize, pbData, &ckOut, &cbActualWrite, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveCloseWriteFile(&hmmioOut, &ckOut, &ckOutRIFF, &mmioinfoOut, cSamples)) != 0)
{
goto ERROR_SAVING;
}

ERROR_SAVING:

return(nError);

}


WAVE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wave.h
 *  Content:wave header
 *
 ***************************************************************************/
#ifndef __WAVE_INCLUDED__
#define __WAVE_INCLUDED__
#include "windows.h"

#define WAVEVERSION 1

#ifndef ER_MEM
#define ER_MEM 0xe000
#endif

#ifndef ER_CANNOTOPEN
#define ER_CANNOTOPEN 0xe100
#endif

#ifndef ER_NOTWAVEFILE
#define ER_NOTWAVEFILE 0xe101
#endif

#ifndef ER_CANNOTREAD
#define ER_CANNOTREAD 0xe102
#endif

#ifndef ER_CORRUPTWAVEFILE
#define ER_CORRUPTWAVEFILE0xe103
#endif

#ifndef ER_CANNOTWRITE
#define ER_CANNOTWRITE0xe104
#endif

int WaveOpenFile(char *, HMMIO *, WAVEFORMATEX **, MMCKINFO *);
int WaveStartDataRead(HMMIO *, MMCKINFO *, MMCKINFO *);
int WaveReadFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *);
int WaveCloseReadFile(HMMIO *, WAVEFORMATEX **);

int WaveCreateFile(char *, HMMIO *, WAVEFORMATEX *, MMCKINFO *, MMCKINFO *);
int WaveStartDataWrite(HMMIO *, MMCKINFO *, MMIOINFO *);
int WaveWriteFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *, MMIOINFO *);
int WaveCloseWriteFile(HMMIO *, MMCKINFO *, MMCKINFO *, MMIOINFO *, DWORD);

int WaveLoadFile(char *, UINT *, DWORD *, WAVEFORMATEX **, BYTE **);
int WaveSaveFile(char *, UINT, DWORD, WAVEFORMATEX *, BYTE *);

int WaveCopyUselessChunks(HMMIO *, MMCKINFO *, MMCKINFO *, HMMIO *, MMCKINFO *, MMCKINFO *);
BOOL riffCopyChunk(HMMIO, HMMIO, const LPMMCKINFO);
#endif


README.TXT

DSShow3D -- DirectSound3D Sample
================================

This program is an improved version of the DirectSound Show (DSSHOW) sample
that shipped in previous DirectX SDKs.  It features a more compact interface,
support for 3D sounds and new buffer options, the ability to open countless
sounds (i.e. more than 8, which was dsshow's limit), and a new UI that takes
advantage of your entire desktop for placing (or minimizing) windows.


DBLIST.H

#ifndef __DBLIST_H__
#define __DBLIST_H__

#include "debug.h"

#ifndef __cplusplus
#error DBList.h included from a C file!
#endif

//forward declaration of template used as friend class
template <class T> class DbLinkedList;

template<class A>
class DblNode
{
friend class DbLinkedList<A>;

private:
    class DblNode<A>    *pNext;
    class DblNode<A>    *pPrev;

    A       value;
};

template <class T>
class DbLinkedList
{
private:

public:
    DbLinkedList();
    DbLinkedList( T emptyElt );
    ~DbLinkedList();

    inline void SetEmpty( T emptyElt ) { m_emptyT = emptyElt; }

    inline BOOL IsAtTail()  { ASSERT( NULL != m_pCurrent );
                    return (m_pCurrent->pNext == NULL); }

    void Clear( void );
    void RemoveCurrent();
    void Remove( T );
    BOOL InsertAfterCurrent( T val );
    BOOL InsertBeforeCurrent( T val );
    T GetCurrent();
    void Append( T );

    inline void SetAtHead()     { m_pCurrent = m_pHead; }
    inline void SetAtTail()     { m_pCurrent = m_pTail; }

    void AssertValid();

    inline int GetElementCount( void )  { return m_nElementCount; }

    DbLinkedList<T> &operator++()
        { if( m_pCurrent && m_pCurrent->pNext ) m_pCurrent = m_pCurrent->pNext; return *this; }
    DbLinkedList<T> &operator++( int )
        { if( m_pCurrent && m_pCurrent->pNext ) m_pCurrent = m_pCurrent->pNext; return *this; }
    DbLinkedList<T> &operator--()
        { if( m_pCurrent && m_pCurrent->pPrev ) m_pCurrent = m_pCurrent->pPrev; return *this; }
    DbLinkedList<T> &operator--( int )
        { if( m_pCurrent && m_pCurrent->pPrev ) m_pCurrent = m_pCurrent->pPrev; return *this; }

protected:
    DblNode<T>  *m_pHead;
    DblNode<T>  *m_pTail;
    DblNode<T>  *m_pCurrent;

    int m_nElementCount;
    T   m_emptyT;
};

// Because of the way templates work, we must include the code from the
// header file.
#include "DbList.cpp"

#endif


DEBUG.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.c
 *  Content:debug code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include <dsound.h>
#include "debug.h"

#if defined(DEBUG) || defined(_DEBUG)

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifdef _WIN32
    #include <wchar.h>
#else
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
    #define wsprintfA           wsprintf
    #define MessageBoxA         MessageBox
#endif

//
//
//
BOOL    __gfDbgEnabled          = TRUE;         // master enable
UINT    __guDbgLevel            = 0;            // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF
//
//  Description:
//
//
//  Arguments:
//      LPSTR szFormat:
//
//      va_list va:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF
(
    LPSTR                   szFormat,
    va_list                 va
)
{
    char                ach[DEBUG_MAX_LINE_LEN];
    BOOL                fDebugBreak = FALSE;
    BOOL                fPrefix     = TRUE;
    BOOL                fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch (*szFormat)
        {
            case '!':
                fDebugBreak = TRUE;
                szFormat++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFormat++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFormat++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
    {
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");
    }

#ifdef _WIN32
    wvsprintfA(ach + lstrlenA(ach), szFormat, va);
#else
    wvsprintf(ach + lstrlenA(ach), szFormat, (LPSTR)va);
#endif

    if (fCRLF)
    {
        lstrcatA(ach, "\r\n");
    }

    OutputDebugStringA(ach);

    if (fDebugBreak)
    {
        DebugBreak();
    }
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf
//
//  Description:
//      dprintf() is called by the DPF() macro if DEBUG is defined at compile
//      time. It is recommended that you only use the DPF() macro to call
//      this function--so you don't have to put #ifdef DEBUG around all
//      of your code.
//
//  Arguments:
//      UINT uDbgLevel:
//
//      LPSTR szFormat:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf
(
    UINT                    uDbgLevel,
    LPSTR                   szFormat,
    ...
)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFormat);
    DbgVPrintF(szFormat, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (BOOL):
//      Returns the previous debugging state.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable
(
    BOOL                    fEnable
)
{
    BOOL                fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel
//
//  Description:
//
//
//  Arguments:
//      UINT uLevel:
//
//  Return (UINT):
//      Returns the previous debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel
(
    UINT                    uLevel
)
{
    UINT                uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgGetLevel
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (UINT):
//      Returns the current debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgGetLevel
(
    void
)
{
    return (__guDbgLevel);
} // DbgGetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (UINT):
//      Returns the debugging level that was set.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize
(
    BOOL                    fEnable
)
{
    UINT                uLevel;

    uLevel = GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, (UINT)-1);
    if ((UINT)-1 == uLevel)
    {
        //
        //  if the debug key is not present, then force debug output to
        //  be disabled. this way running a debug version of a component
        //  on a non-debugging machine will not generate output unless
        //  the debug key exists.
        //
        uLevel  = 0;
        fEnable = FALSE;
    }

    DbgSetLevel(uLevel);
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

//--------------------------------------------------------------------------;
//
//  void _Assert
//
//  Description:
//      This routine is called if the ASSERT macro (defined in debug.h)
//      tests and expression that evaluates to FALSE.  This routine
//      displays an "assertion failed" message box allowing the user to
//      abort the program, enter the debugger (the "retry" button), or
//      ignore the assertion and continue executing.  The message box
//      displays the file name and line number of the _Assert() call.
//
//  Arguments:
//      char *  szFile: Filename where assertion occurred.
//      int     iLine:  Line number of assertion.
//
//--------------------------------------------------------------------------;

#ifndef _WIN32
#pragma warning(disable:4704)
#endif

void WINAPI _Assert
(
    char *  szFile,
    int     iLine
)
{
    static char     ach[300];       // debug output (avoid stack overflow)
    int            id;
#ifndef _WIN32
    int             iExitCode;
#endif

    wsprintfA(ach, "Assertion failed in file %s, line %d.  [Press RETRY to debug.]", (LPSTR)szFile, iLine);

    id = MessageBoxA(NULL, ach, "Assertion Failed",
            MB_SYSTEMMODAL | MB_ICONHAND | MB_ABORTRETRYIGNORE );

switch (id)
{

case IDABORT:               // Kill the application.
#ifndef _WIN32
        iExitCode = 0;
        _asm
        {
        movah, 4Ch
        moval, BYTE PTR iExitCode
        int     21h
        }
#else
        FatalAppExit(0, TEXT("Good Bye"));
#endif // WIN16
break;

case IDRETRY:               // Break into the debugger.
DebugBreak();
break;

case IDIGNORE:              // Ignore assertion, continue executing.
break;
}
} // _Assert

#ifndef _WIN32
#pragma warning(default:4704)
#endif

#endif // #if defined(DEBUG) || defined(_DEBUG)

// Silly little function gives meaningful error messages from HRESULT's
LPSTR TranslateDSError( HRESULT hr )
    {
    switch( hr )
{
case DSERR_ALLOCATED:
    return "DSERR_ALLOCATED";

case DSERR_CONTROLUNAVAIL:
    return "DSERR_CONTROLUNAVAIL";

case DSERR_INVALIDPARAM:
    return "DSERR_INVALIDPARAM";

case DSERR_INVALIDCALL:
    return "DSERR_INVALIDCALL";

case DSERR_GENERIC:
    return "DSERR_GENERIC";

case DSERR_PRIOLEVELNEEDED:
    return "DSERR_PRIOLEVELNEEDED";

case DSERR_OUTOFMEMORY:
    return "DSERR_OUTOFMEMORY";

case DSERR_BADFORMAT:
    return "DSERR_BADFORMAT";

case DSERR_UNSUPPORTED:
    return "DSERR_UNSUPPORTED";

case DSERR_NODRIVER:
    return "DSERR_NODRIVER";

case DSERR_ALREADYINITIALIZED:
    return "DSERR_ALREADYINITIALIZED";

case DSERR_NOAGGREGATION:
    return "DSERR_NOAGGREGATION";

case DSERR_BUFFERLOST:
    return "DSERR_BUFFERLOST";

case DSERR_OTHERAPPHASPRIO:
    return "DSERR_OTHERAPPHASPRIO";

case DSERR_UNINITIALIZED:
    return "DSERR_UNINITIALIZED";

default:
    return "Unknown HRESULT";
}
    }


DEBUG.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.h
 *  Content:debug header
 *
 ***************************************************************************/
#ifndef __DEBUG_INCLUDED__
#define __DEBUG_INCLUDED__
#ifdef __cplusplus
extern "C"
{
#endif

LPSTR TranslateDSError( HRESULT );

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "DSSHOW3D"    // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef _WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#if defined(DEBUG) || defined(_DEBUG)
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);
    void WINAPI _Assert( char * szFile, int iLine );

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
    #define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__)
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
    #define ASSERT(x)
#endif

#if defined(DEBUG) || defined(_DEBUG)
#define ASSERT_HWND( h )    ASSERT( IsWindow((h)) )
#else
#define ASSERT_HWND( h )    ASSERT( NULL != (h) )
#endif

#ifdef __cplusplus
}
#endif
#endif  // __DEBUG_INCLUDED__


DSENUM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dstrenum.c
 *  Content:   Illustrates enumerating DirectSound drivers
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <memory.h>

#include "dsenum.h"
#include "resource.h"

extern HINSTANCE ghInst;
extern HWND      hWndMain;

/****************************************************************************/
/* DoDSoundEnumerate()                                                      */
/*                                                                          */
/*   This function takes care of handling the DirectSound enumeration, which*/
/* simply means creating a dialog box, at this point...                     */
/****************************************************************************/
BOOL DoDSoundEnumerate( LPGUID lpGUID )
    {
    if( DialogBoxParam( ghInst, MAKEINTRESOURCE(IDD_DSENUMBOX), hWndMain,
                        DSEnumDlgProc, (LPARAM)lpGUID ))
        return( TRUE );

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumDlgProc()                                                          */
/*                                                                          */
/*   Dialog procedure for the DSound enumeration choice dialog. Allows the  */
/* user to choose from installed drivers.  Returns TRUE on error.           */
/****************************************************************************/
BOOL CALLBACK DSEnumDlgProc( HWND hDlg, UINT msg,
                                WPARAM wParam, LPARAM lParam )
    {
    static HWND   hCombo;
    static LPGUID lpGUID;
    LPGUID        lpTemp;
    int           i;

    switch( msg )
        {
        case WM_INITDIALOG:
            hCombo = GetDlgItem( hDlg, IDC_DSENUM_COMBO );
            lpGUID = (LPGUID)lParam;

            if( DirectSoundEnumerate( (LPDSENUMCALLBACK)DSEnumProc, &hCombo ) != DS_OK )
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            if( ComboBox_GetCount( hCombo ))
                ComboBox_SetCurSel( hCombo, 0 );
            else
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            return( TRUE );

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDOK:
                    for( i = 0; i < ComboBox_GetCount( hCombo ); i++ )
                        {
                        lpTemp = (LPGUID)ComboBox_GetItemData( hCombo, i );
                        if( i == ComboBox_GetCurSel( hCombo ))
                            {
                            if( lpTemp != NULL )
                                memcpy( lpGUID, lpTemp, sizeof(GUID));
    else
        lpGUID = NULL;
    }
                        if( lpTemp )
                            LocalFree( lpTemp );
                        }
                    // If we got the NULL GUID, then we want to open the default
                    // sound driver, so return with an error and the init code
    // will know not to pass in the guID and will send NULL
    // instead.
                    if( lpGUID == NULL )
        EndDialog( hDlg, TRUE );
                    else
                        EndDialog( hDlg, FALSE );
                    return( TRUE );

                case IDCANCEL:
                    // Force a NULL GUID
                    EndDialog( hDlg, TRUE );
                    return( TRUE );
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumProc()                                                             */
/*                                                                          */
/*   This is the Enumeration procedure called by DirectSoundEnumerate with  */
/* the parameters of each DirectSound Object available in the system.       */
/****************************************************************************/
BOOL CALLBACK DSEnumProc( LPGUID lpGUID, LPCTSTR lpszDesc,
                                LPCTSTR lpszDrvName, LPVOID lpContext )
    {
    HWND   hCombo = *(HWND *)lpContext;
    LPGUID lpTemp = NULL;

    if( lpGUID != NULL )
        {
        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
    return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));
}

    ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    ComboBox_FindString( hCombo, 0, lpszDesc ),
    lpTemp );
    return( TRUE );
    }


DSENUM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsenum.h
 *  Content:    Direct Sound Enumeration defines
 *
 *
 ***************************************************************************/

#ifndef __DSENUM_INCLUDED__
#define __DSENUM_INCLUDED__

#ifdef __cplusplus
extern "C" {
#endif

// Function declarations

BOOL DoDSoundEnumerate( LPGUID );

BOOL CALLBACK DSEnumProc( LPGUID, LPCTSTR, LPCTSTR, LPVOID );
BOOL CALLBACK DSEnumDlgProc( HWND, UINT, WPARAM, LPARAM );

#ifdef __cplusplus
};
#endif

#endif


DSSHOW3D.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       DSShow3d.c
 *  Content:    Direct Sound show-off, including 3D Sound.
 *
 *
 ***************************************************************************/

//#pragma warning( disable: 4102 4101 )

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <cguid.h>
#include <shlobj.h> // for browseinfo
#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <dsound.h>

#include "DSShow3D.h"
#define INIT_GVARS
#include "GVars.h"

#include "MainWnd.h"
#include "FileInfo.h"

#include "wave.h"
#include "debug.h"

static TCHAR szOpenStartDir[MAX_PATH];
static BOOL gfDisable3D = FALSE;

BOOL IsPentiumOrBetter(void);

// Format codes used to compactly represent each possible output format
//
// The code is: FFCBB where...
//
//    FF -> Frequency 8=8000, 11=11025, 22=22050, 44=44100
//    C  -> # of channels (1 or 2)
//    BB -> Bits 08 is 8-bit, 16 is 16-bit
//
// Use the FormatCodeToWFX() function to set a WAVEFORMATEX structure
// based on a format code, or FormatCodeToText() to get a text string
// representing the format.
//

#define FC_GETFREQCODE(fc)((fc) / 1000)
#define FC_GETBITS(fc)((fc) % 100)
#define FC_GETCHANNELS(fc)(((fc) % 1000) / 100)

// Functions limited in scope to this file

static BOOL InitializeDSound( void );
static void FreeDSound( void );
static BOOL InitInstance( HINSTANCE, LPTSTR, int );
static BOOL InitPrimarySoundBuffer( void );
static void GetMediaPath( LPTSTR, int );
static BOOL LoadRegistrySettings( void );
static BOOL SaveRegistrySettings( void );
static BOOL ParseCommandLine( LPTSTR lpszCmdLine );
static BOOL fMatchToken( PSTR pszString, PSTR pszDatum, int cchLen );
static BOOL fGetToken( PSTR pszStart, PSTR *ppszRet, int *pcchRet );

/* InitializeDSound()
 *
 *    Initialize the DirectSound object and other stuff we'll use like the
 * primary buffer and 3D listener object.
 */
BOOL InitializeDSound( void )
    {
    HRESULT hr;

    hr = CoCreateInstance( CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectSound, (void**)&gpds );

    if (FAILED(hr) || (NULL == gpds))
{
DPF( 0, "Could not create a DSound object (%s)", TranslateDSError(hr));
MessageBox( AppWnd.GetHwnd(), "Unable to get a DirectSound object",
    "COM Failure", MB_OK | MB_ICONSTOP );
goto ID_ExitError;
}
    DPF( 2, "Got an IDirectSound object" );

    if( grs.fDefaultDevice )
{
DPF( 2, "Using default device as first choice" );
hr = gpds->Initialize(NULL );
}
    else
{
if( FAILED( hr = gpds->Initialize( &grs.guPreferredDevice )))
    {
    DPF( 0, "Initialize failed on preferred device, using default" );
    if( IDNO == MessageBox( GetActiveWindow(),
"Unable to use preferred device. Use default instead?",
gszAppName, MB_YESNO ))
{
DPF( 2, "User chose not to use default device instead" );
goto ID_ExitReleaseDS;
}
    else
{
DPF( 2, "Falling back to default device" );
hr = gpds->Initialize( NULL );
}
    }
}
    if (FAILED(hr))
{
DPF( 0, "Failed Initialize() on IDirectSound object (%s)", TranslateDSError(hr));
MessageBox( AppWnd.GetHwnd(), "Could not initialize DirectSound object",
    gszAppName, MB_OK | MB_ICONSTOP );
goto ID_ExitReleaseDS;
}

    if( grs.fUseExclusiveMode )
{
hr = gpds->SetCooperativeLevel( AppWnd.GetHwnd(), DSSCL_EXCLUSIVE);
        DPF( 3, "Received DSSCL_EXCLUSIVE" );
}
    else
{
hr = gpds->SetCooperativeLevel( AppWnd.GetHwnd(), DSSCL_PRIORITY);
        DPF( 3, "Received DSSCL_PRIORITY" );
}
    if (FAILED(hr))
{
DPF( 0, "Couldn't SetCooperativeLevel() (%s)", TranslateDSError(hr));
MessageBox( AppWnd.GetHwnd(), "Could not SetCooperativeLevel()",
    gszAppName, MB_OK | MB_ICONSTOP );
goto ID_ExitReleaseDS;
}

    DPF( 3, "Creating Primary Buffer" );

    if( !InitPrimarySoundBuffer())
{
DPF( 0, "Failed on call to InitPrimarySoundBuffer()" );
goto ID_ExitReleaseDS;
}

    // Return SUCCESS
    return TRUE;

ID_ExitReleaseDS:
    // The InitPrimarySoundBuffer() call should have cleaned up
    // after itself if it failed

    ASSERT( NULL == gp3DListener );
    ASSERT( NULL == gpdsbPrimary );

    if( NULL != gpds )
{
gpds->Release();
gpds = NULL;
}

ID_ExitError:
    return FALSE;
    }

void FreeDSound()
    {
    if( NULL != gpdsbPrimary )
{
gpdsbPrimary->Stop();
gpdsbPrimary->Release();
gpdsbPrimary = NULL;
}
    if( NULL != gp3DListener )
{
gp3DListener->Release();
gp3DListener = NULL;
}
    if( NULL != gpds )
{
gpds->Release();
gpds = NULL;
}
    }

/* InitInstance()
 *
 *    This function is responsible for all initialization that must occur
 * when a new instance of our application is started.
 */
BOOL InitInstance( HINSTANCE hInstance, LPTSTR lpszCommandLine, int cmdShow )
    {
    WNDCLASS    myClass;

    myClass.hCursor             = LoadCursor(NULL, IDC_ARROW);
    myClass.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_SPEAKER_RGB_3D));
    myClass.lpszMenuName        = MAKEINTRESOURCE(IDR_MAINMENU);
    myClass.lpszClassName       = (LPTSTR)gszAppWndClass;
    myClass.hbrBackground       = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    myClass.hInstance           = hInstance;
    myClass.style               = CS_HREDRAW | CS_VREDRAW;
    myClass.lpfnWndProc         = MainWndProc;
    myClass.cbClsExtra          = 0;
    myClass.cbWndExtra          = 0;

    if (!RegisterClass( &myClass ))
       return FALSE;

    // Load the current registry settings
    LoadRegistrySettings();
    gdwOutputFormat = grs.dwPreferredFormat;

    if( !AppWnd.Create())
goto II_ExitError;

    AppWnd.ShowWindow( cmdShow );
    AppWnd.UpdateWindow();

    /* Continue doing other initialization stuff */

    // Setup the timer...
    if ((gdwTimer = SetTimer(AppWnd.GetHwnd(), 1, TIMERPERIOD, NULL)) == 0)
{
MessageBox(AppWnd.GetHwnd(), "Cannot allocate timer, aborting", gszAppCaption,
MB_OK|MB_ICONSTOP);
goto II_ExitError;
}

    // Get the largest waveformatex structure.
    if (MMSYSERR_NOERROR != acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT,
    &gcbMaxWaveFormatSize))
        {
MessageBox(AppWnd.GetHwnd(), "ACM Metrics failed, aborting", gszAppCaption,
   MB_OK|MB_ICONSTOP);
goto II_ExitError;
}

    // Initialize the COM subsystem and create our DirectSound stuff

    if (!SUCCEEDED(CoInitialize(NULL)))
{
MessageBox( AppWnd.GetHwnd(), "Failed to initialize COM library",
gszAppCaption, MB_OK | MB_ICONSTOP);
goto II_ExitError;
}
    else
gfCOMInitialized = TRUE;

    // Initialize the DirectSound global interfaces
    if( !InitializeDSound())
goto II_ExitShutdownCOM;

    if( !ParseCommandLine( lpszCommandLine ))
goto II_ExitFreeDSound;

    return TRUE;    // TRUE indicates success

II_ExitFreeDSound:
    FreeDSound();

II_ExitShutdownCOM:
    if( gfCOMInitialized )
{
DPF( 0, "Calling CoUninitialize from InitInstance error cleanup" );
CoUninitialize();
}

II_ExitError:

    return FALSE;   // FALSE indicates failure on initialization
    }// InitInstance()

/* WinMain()
 *
 *   Main entry point for this program's execution.  Everything starts here.
 */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpszCmdLine, int cmdShow)
    {
    MSG   msg;

    DbgInitialize( TRUE );

    InitCommonControls();

    // Save instance handle
    ghInst = hInstance;

    if (!InitInstance(hInstance, lpszCmdLine, cmdShow))
return 0;

    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0))
{
// Only Translate and Dispatch the message if it's not going
// to one of our many modeless dialog windows
if( !IsDialogMessage( ghwndListener, &msg )
    && (NULL == ghDlgActive || !IsDialogMessage( ghDlgActive, &msg )))
    {
    TranslateMessage((LPMSG)&msg);
    DispatchMessage((LPMSG)&msg);
    }
}

    UnregisterClass(gszAppWndClass, hInstance);
    return (int)msg.wParam;
    }// WinMain()

/* InitPrimarySoundBuffer()
 *
 *    Creates and initializes the primary sound buffer for the application.
 * We need the primary buffer in order to get the 3D listener interface and
 * also to select output format type.
 */
BOOL InitPrimarySoundBuffer( void )
    {
    HRESULT    hr;
    DSBUFFERDESC    dsbd;
    int    nCurFormat;

    ZeroMemory( &dsbd, sizeof(DSBUFFERDESC));

    gpwfxFormat = (LPWAVEFORMATEX)GlobalAlloc(GPTR, sizeof(WAVEFORMATEX));
    if( NULL == gpwfxFormat )
return FALSE;

    ZeroMemory( gpwfxFormat, sizeof(WAVEFORMATEX));

    gpwfxFormat->wFormatTag = WAVE_FORMAT_PCM;

    FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
    DPF( 2, "Initial format code: %lu", gdwOutputFormat );

    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;

    if( FAILED( hr = gpds->CreateSoundBuffer( &dsbd, &gpdsbPrimary, NULL )))
{
DPF( 0, "Couldn't create primary buffer (%s)", TranslateDSError(hr));
goto IPSB_ExitError;
}

    if( FAILED( hr = gpdsbPrimary->SetFormat( gpwfxFormat )))
{
DisableFormatCode( gdwOutputFormat );

// If we couldn't load the desired format, then try everything starting
// at really high quality, and degrading to 8M8.
nCurFormat = 0;
DPF( 2, "Unable to SetFormat() preferred format to %lu", gdwOutputFormat );

while( FAILED( hr ) && nCurFormat < NUM_FORMATENTRIES )
{
gdwOutputFormat = aFormatOrder[nCurFormat];
FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
DPF( 2, "Trying format code: %lu", gdwOutputFormat );

hr = gpdsbPrimary->SetFormat( gpwfxFormat );
if( FAILED(hr))
{
DisableFormatCode( gdwOutputFormat );
DPF( 2, "Return from SetFormat on primary buffer = %s", TranslateDSError(hr));
}
else
{
EnableFormatCode( gdwOutputFormat );
DPF( 2, "Succeeded on SetFormat() for code %lu", gdwOutputFormat );
}

nCurFormat++;
}//while
}

    if( FAILED( hr ))
{
DPF( 0, "Failed SetFormat() on all formats!" );
goto IPSB_ExitError;
}

    if( FAILED( hr = gpdsbPrimary->QueryInterface( IID_IDirectSound3DListener,
(void**)&gp3DListener)))
{
DPF( 0, "Couldn't QI primary buffer for 3D listener interface (%s)", TranslateDSError(hr));
goto IPSB_ExitError;
}
    if( FAILED( hr = gpdsbPrimary->Play( 0, 0, DSBPLAY_LOOPING )))
{
DPF( 0, "Couldn't play primary buffer (%s)", TranslateDSError(hr));
goto IPSB_ExitRelease;
}

    return TRUE;

IPSB_ExitRelease:
    if( gp3DListener )
{
DPF( 0, "Releasing 3D Listener in InitPrimarySoundBuffer() error cleanup" );
gp3DListener->Release();
gp3DListener = NULL;
}
    if( gpdsbPrimary )
{
DPF( 0, "Releasing Primary in InitPrimarySoundBuffer() error cleanup" );
gpdsbPrimary->Stop();
gpdsbPrimary->Release();
gpdsbPrimary = NULL;
}

IPSB_ExitError:
    return FALSE;
    }

/*  This will pop up the open file dialog and allow the user to pick one file.

    Input:
hWnd        -   Handle of parent window.
pszFileName -   String to store filename in, must be >= MAX_PATH long.

    Output:
TRUE if a file was picked successfully or FALSE if the user didn't
pick a file)

 */
BOOL OpenFileDialog( HWND hWnd, LPTSTR pszFileName,
int *nFileName, LPDWORD lpdwFlags )
    {
    BOOL            fReturn,
    fValid;
    OPENFILENAME    ofn;

    pszFileName[0]          = 0;

    ofn.lStructSize         = sizeof(ofn);
    ofn.hwndOwner           = hWnd;
    ofn.hInstance           = ghInst;
    ofn.lpstrFilter         = "Wave Files\0*.wav\0All Files\0*.*\0\0";
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0;
    ofn.nFilterIndex        = 1;
    ofn.lpstrFile           = pszFileName;
    ofn.nMaxFile            = MAX_PATH;
    ofn.lpstrFileTitle      = NULL;
    ofn.nMaxFileTitle       = 0;
    ofn.lpstrInitialDir     = grs.szInitialDir;
    ofn.lpstrTitle          = "File Open";
    ofn.Flags               = OFN_FILEMUSTEXIST | OFN_EXPLORER
                                | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK
| OFN_HIDEREADONLY;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = "wav";
    ofn.lCustData           = (LONG)lpdwFlags;
    ofn.lpfnHook            = FileOpenCustomTemplateDlgProc;
    ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_FILEOPEN_NEST);

    fValid = FALSE;
    do
    {
    if( fReturn = GetOpenFileName( &ofn ))
{
fValid = IsValidWave( pszFileName );
if( !fValid )
MessageBox( hWnd, "Wave files must be PCM format!",
    "Invalid Wave File", MB_OK|MB_ICONSTOP );
else
*nFileName = ofn.nFileOffset;
}
    else
fValid = TRUE;         // Force break out of loop.
    } while( !fValid );

    return fReturn;
    }

/*
 *
 *
This function will determine if the filename passed
    in is a valid wave for this
    app, that is a PCM wave.

    Input:
pszFileName -   FileName to check.

    Output:
FALSE if not a valid wave, TRUE if it is.

*/
BOOL IsValidWave( LPTSTR pszFileName )
    {
    BOOL    fReturn = FALSE;
    int             nError = 0;
    HMMIO           hmmio;
    MMCKINFO        mmck;
    WAVEFORMATEX    *pwfx;

    if ((nError = WaveOpenFile(pszFileName, &hmmio, &pwfx, &mmck)) != 0)
{
goto ERROR_IN_ROUTINE;
}

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
{
goto ERROR_IN_ROUTINE;
}

    WaveCloseReadFile(&hmmio, &pwfx);

    fReturn = TRUE;

ERROR_IN_ROUTINE:
    return fReturn;
    }

/* AboutDlgProc()
 *
 *    Standard dialog procedure for the About box.  As simple as can be.
 */
BOOL CALLBACK AboutDlgProc( HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam )
    {
    switch(uMsg)
{
case WM_INITDIALOG:
    break;

case WM_COMMAND:
    switch(wParam)
    {
    case ID_OK:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;

    default:
break;

    }
    break;

case WM_CLOSE:
    EndDialog(hWnd, 0);
    break;

default:
    return FALSE;
    break;

}

    return TRUE;
}

/* FileOpenCustomTemplateDlgProc()
 *
 * This "hook procedure" is called by the common dialog code for certain
 *   events that may occur during the life of our nested dialog structure.
 * We nest the Explorer style dialog inside our file open dialog so we
 *   can add a check box for stick buffers.
 */
UINT CALLBACK FileOpenCustomTemplateDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    static LPOPENFILENAME   lpofn = NULL;
    static HWND    hStickyRadio, h3DCheck, hLocalRadio, hGlobalRadio;
    static HWND        hGetPosRadio, hGetPos2Radio, hMuteAtMaxDist, hNotifyCheck;

    switch( message )
    {
    case WM_INITDIALOG:
{
        lpofn = (LPOPENFILENAME)lParam;

h3DCheck = GetDlgItem( hDlg, IDC_FONEST_3D );
hLocalRadio = GetDlgItem( hDlg, IDC_FONEST_LOCAL_RADIO );
hStickyRadio = GetDlgItem( hDlg, IDC_FONEST_STICKY_RADIO );
hGlobalRadio = GetDlgItem( hDlg, IDC_FONEST_GLOBAL_RADIO );
hGetPosRadio = GetDlgItem( hDlg, IDC_FONEST_GETPOS_RADIO );
hGetPos2Radio = GetDlgItem( hDlg, IDC_FONEST_GETPOS2_RADIO );
hNotifyCheck = GetDlgItem(hDlg, IDC_FONEST_NOTIFYCHECK);
hMuteAtMaxDist = GetDlgItem(hDlg, IDC_FONEST_MUTEATMAX);

Button_SetCheck( hGetPos2Radio, TRUE );

if( grs.dwDefaultFocusFlag & DSBCAPS_STICKYFOCUS )
Button_SetCheck( hStickyRadio, TRUE );
else if( grs.dwDefaultFocusFlag & DSBCAPS_GLOBALFOCUS )
Button_SetCheck( hGlobalRadio, TRUE );
else
{
ASSERT( grs.dwDefaultFocusFlag == 0 );
Button_SetCheck( hLocalRadio, TRUE );
}

if (gfDisable3D)
{
grs.fOpen3D = FALSE;
}

Static_Enable(h3DCheck, !gfDisable3D);
Button_SetCheck( h3DCheck, grs.fOpen3D );

Static_Enable(hMuteAtMaxDist, Button_GetCheck(h3DCheck));

*((LPDWORD)lpofn->lCustData) = 0;
}
    return TRUE;

case WM_COMMAND:
if(LOWORD(wParam) == IDC_FONEST_3D)
{
Static_Enable(hMuteAtMaxDist, Button_GetCheck(h3DCheck));
}
return TRUE;

    case WM_NOTIFY:
        switch(((LPOFNOTIFY)lParam)->hdr.code)
        {
        case CDN_SELCHANGE:
            /* Use this area to process anything that must be updated when the
             * user changes the selection in the Common Dialog Box.
             *   NOTE: Provided only for informational purposes
             */
            return FALSE;

        case CDN_FILEOK:
            /* We can do lots of things in this notification message.  The most
             * important is that we can decide whether the Common Dialog call
             * will go through or whether it will fail.  I decided to handle
             * the checkbox control in this one place versus 4 others...
             */
            ASSERT( lpofn != NULL );
    /* Set flags to match the current state of the check box controls */

    /* Use normal GetPosition */
    *((LPDWORD)lpofn->lCustData) |=
Button_GetCheck(hGetPosRadio)? OPENFILENAME_F_GETPOS : 0;
    /* Use DSBCAPS_GETCURRENTPOSITION2 */
    *((LPDWORD)lpofn->lCustData) |=
Button_GetCheck(hGetPos2Radio)? OPENFILENAME_F_GETPOS2 : 0;

    /* Local buffer focus */
    *((LPDWORD)lpofn->lCustData) |=
Button_GetCheck(hStickyRadio)? OPENFILENAME_F_LOCALFOCUS : 0;
    /* Sticky buffer focus */
    *((LPDWORD)lpofn->lCustData) |=
Button_GetCheck(hStickyRadio)? OPENFILENAME_F_STICKYFOCUS : 0;
    /* Global sound focus */
    *((LPDWORD)lpofn->lCustData) |=
Button_GetCheck(hGlobalRadio)? OPENFILENAME_F_GLOBALFOCUS : 0;
    /* 3D Buffer */
    *((LPDWORD)lpofn->lCustData) |=
    Button_GetCheck(h3DCheck)? OPENFILENAME_F_3D : 0;

// Are Notifies On
    *((LPDWORD)lpofn->lCustData) |=
    Button_GetCheck(hNotifyCheck)? OPENFILENAME_F_NOTIFY : 0;

        /* Mute at max distance */
        *((LPDWORD)lpofn->lCustData) |=
            Button_GetCheck(hMuteAtMaxDist)? OPENFILENAME_F_MUTEMAX : 0;
           /* Returning zero signifies that we "approve" of the OK command,
             * and allows the common dialog to finish.
             */
            return FALSE;
        }
        /* Let the default dialog do/continue processing */
        return FALSE;
    }
    return FALSE;
}

/* SettingsDlgProc()
 *
 *    DialogBox procedure for the Settings dialog, which sets a bunch of
 * the application-global settings stored in our global REGSETTINGS struct.
 */
BOOL CALLBACK SettingsDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    static BOOL fSettingsSaved;
    static HWNDhInitialDirEdit, hDeviceCombo, hDeviceText, hDefaultCheck;
    static HWND hFormatCombo, hExclusiveCheck, hOpen3DCheck, hFocusCombo;
static BOOL fDefaultDevice, fXMode;
static DWORD dwFormat;
static BOOL fInited = FALSE;
    int    ndx, i, idxLocal, idxSticky, idxGlobal;
    TCHAR   szFormat[32];
    LPGUID  lpguTemp;

    switch( message )
{
case WM_INITDIALOG:
{
WAVEFORMATEX wfx, wfxActual;
HRESULT hr = DS_OK;
ZeroMemory(&wfx, sizeof(wfx));
ZeroMemory(&wfxActual, sizeof(wfxActual));

hInitialDirEdit = GetDlgItem( hDlg, IDC_SETTINGS_INITIALDIR_EDIT );
hDeviceCombo = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEVICE_COMBO );
hFormatCombo = GetDlgItem( hDlg, IDC_SETTINGS_DSD_FORMAT_COMBO );
hFocusCombo = GetDlgItem( hDlg, IDC_SETTINGS_FOCUS_COMBO );
hDeviceText = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEVICE_TEXT );
hDefaultCheck = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEFAULT_CHECK );
hExclusiveCheck = GetDlgItem( hDlg, IDC_SETTINGS_EXCLUSIVE_CHECK );
hOpen3DCheck = GetDlgItem( hDlg, IDC_SETTINGS_OPEN3D_CHECK );

    // We use this flag to determine the return value from DialogBox()
    // FALSE indicates no change, TRUE means a change occurred.
    fSettingsSaved = FALSE;

    // Add all the format strings to the combo box
    for( i = 0; i < NUM_FORMATENTRIES; i ++ )
{
// Only add formats that are available on this card (this will
// ignore any formats we couldn't SetFormat() with at startup,
// and any we have since found to be unusable as well).
if( !fdFormats[i].fEnable )
continue;

wfx.wFormatTag = WAVE_FORMAT_PCM;
if( FormatCodeToText(fdFormats[i].dwCode, szFormat, sizeof(szFormat)))
{
// Do a set and get format to see what the card supports.
FormatCodeToWFX(fdFormats[i].dwCode, &wfx);
hr = gpdsbPrimary->SetFormat(&wfx);
if (hr != DS_OK)
DPF(0, "SettingsDlgProc: SetFormat failed");
hr = gpdsbPrimary->GetFormat(&wfxActual, sizeof(WAVEFORMATEX), NULL);
if (hr != DS_OK)
DPF(0, "SettingsDlgProc: GetFormat failed");
if (!IsSameFormat(&wfx, &wfxActual))
{
char szTemp[2] = "*";
strcat(szFormat, szTemp);
}
ndx = ComboBox_AddString( hFormatCombo, szFormat );
ComboBox_SetItemData( hFormatCombo, ndx, fdFormats[i].dwCode );
}
} //for

// restore the format.
FormatCodeToWFX(gdwOutputFormat, &wfx);
hr = gpdsbPrimary->SetFormat(&wfx);
ASSERT(hr == DS_OK);

    DirectSoundEnumerate( (LPDSENUMCALLBACK)DSEnumProc, (LPVOID)&hDeviceCombo );

    // Add the three focus choices to the listbox, and set the item data
    // for each to the appropriate flag
    idxLocal = ComboBox_AddString( hFocusCombo, "Local" );
    ComboBox_SetItemData( hFocusCombo, idxLocal, 0 );
    idxSticky = ComboBox_AddString( hFocusCombo, "Sticky" );
    ComboBox_SetItemData( hFocusCombo, idxSticky, DSBCAPS_STICKYFOCUS );
    idxGlobal = ComboBox_AddString( hFocusCombo, "Global" );
    ComboBox_SetItemData( hFocusCombo, idxGlobal, DSBCAPS_GLOBALFOCUS );

    // Select the proper Drop List item
    if( grs.dwDefaultFocusFlag == DSBCAPS_STICKYFOCUS )
ComboBox_SetCurSel( hFocusCombo, idxSticky );
    else if( grs.dwDefaultFocusFlag == DSBCAPS_GLOBALFOCUS )
ComboBox_SetCurSel( hFocusCombo, idxGlobal );
    else
ComboBox_SetCurSel( hFocusCombo, idxLocal );

    // Set the states of checkboxes and controls that depend on them
    Button_SetCheck( hDefaultCheck, grs.fDefaultDevice );
    Button_SetCheck( hExclusiveCheck, grs.fUseExclusiveMode );
// is 3D on or off?
if (gfDisable3D)
{
grs.fOpen3D = FALSE;
Static_Enable(hOpen3DCheck, FALSE);
}
    Button_SetCheck( hOpen3DCheck, grs.fOpen3D );
    Static_Enable( hDeviceCombo, !grs.fDefaultDevice );
    Static_Enable( hDeviceText, !grs.fDefaultDevice );

    Edit_LimitText( hInitialDirEdit, sizeof(grs.szInitialDir));
    Edit_SetText( hInitialDirEdit, grs.szInitialDir );

    if (FormatCodeToText(grs.dwPreferredFormat, szFormat, sizeof(szFormat)))
{
ComboBox_SetCurSel( hFormatCombo, ComboBox_FindString( hFormatCombo,
    -1, szFormat ));
}
if (!fInited)
{// remember the values we started with.
fDefaultDevice = grs.fDefaultDevice;
fXMode = grs.fUseExclusiveMode;
dwFormat = grs.dwPreferredFormat;
fInited = TRUE;
}

return TRUE;
}

case WM_COMMAND:
    switch( LOWORD(wParam))
{
case IDC_SETTINGS_BROWSE_INITIALDIR_BUTTON:
    {
BROWSEINFO              bi;
TCHAR                   szDisplayName[MAX_PATH];
LPITEMIDLIST            pidl;
LPMALLOC  pMalloc = NULL;

ZeroMemory(&bi, sizeof(bi));

bi.hwndOwner = GetParent(hDlg);
bi.pszDisplayName = szDisplayName;
bi.lpszTitle = TEXT("Select folder");
bi.ulFlags = BIF_RETURNONLYFSDIRS;

pidl = SHBrowseForFolder(&bi);

if(pidl)
{
SHGetPathFromIDList(pidl, szDisplayName);
// set the directory name.
strcpy(grs.szInitialDir, szDisplayName);
    Edit_SetText( hInitialDirEdit, grs.szInitialDir );
}
// free the pidl using the shell's task allocator.
if (SHGetMalloc(&pMalloc) == NOERROR)
{
// free it.
pMalloc->Free((void *)pidl);
}
    }
    break;

case IDC_SETTINGS_DSD_DEFAULT_CHECK:
{
    grs.fDefaultDevice = !grs.fDefaultDevice;
    Static_Enable( hDeviceCombo, !grs.fDefaultDevice );
    Static_Enable( hDeviceText, !grs.fDefaultDevice );
    // By selecting the first item, we wipe out the state where
    // the user could come in with the box checked, uncheck it,
    // and leave without selecting anything
    ComboBox_SetCurSel( hDeviceCombo, 0 );
}
    break;

case IDOK:
{
BOOL fDeviceChanged, fXModeChanged, fFormatChanged;
fDeviceChanged = fXModeChanged = fFormatChanged = FALSE;

grs.fDefaultDevice = Button_GetCheck( hDefaultCheck );
if( !grs.fDefaultDevice )
{
lpguTemp = (LPGUID)ComboBox_GetItemData( hDeviceCombo,
ComboBox_GetCurSel( hDeviceCombo ));
if( NULL != lpguTemp )
grs.guPreferredDevice = *lpguTemp;
else
grs.guPreferredDevice = GUID_NULL;
}
else
grs.guPreferredDevice = GUID_NULL;

if (!gfDisable3D)
{
grs.fOpen3D = Button_GetCheck( hOpen3DCheck );
}
grs.fUseExclusiveMode = Button_GetCheck( hExclusiveCheck );

grs.dwPreferredFormat = ComboBox_GetItemData( hFormatCombo,ComboBox_GetCurSel( hFormatCombo ));

grs.dwDefaultFocusFlag = ComboBox_GetItemData( hFocusCombo,ComboBox_GetCurSel( hFocusCombo ));
Edit_GetText( hInitialDirEdit, grs.szInitialDir,sizeof(grs.szInitialDir));

if (grs.fDefaultDevice != fDefaultDevice)
{
fDeviceChanged = TRUE;
}

if (grs.fUseExclusiveMode != fXMode)
{
fXModeChanged = TRUE;
}

if (dwFormat != grs.dwPreferredFormat)
{
fFormatChanged = TRUE;
}

if (fDeviceChanged | fXModeChanged | fFormatChanged)
{
char sz[70] = "Settings will take effect after you restart the app.";
MessageBox(hDlg, sz, "", MB_OK|MB_ICONEXCLAMATION);
}

SaveRegistrySettings();
fSettingsSaved = TRUE;
SendMessage( hDlg, WM_CLOSE, 0, 0 );
}
break;

case IDCANCEL:
    fSettingsSaved = FALSE;
    SendMessage( hDlg, WM_CLOSE, 0, 0 );
    break;

default:
    return FALSE;
}
    return TRUE;
    break;

case WM_CLOSE:
    EndDialog( hDlg, fSettingsSaved );
    return TRUE;

case WM_DESTROY:
    while( ComboBox_GetCount( hDeviceCombo ))
{
lpguTemp = (LPGUID)ComboBox_GetItemData( hDeviceCombo, 0 );
if( NULL != lpguTemp )
    GlobalFree(lpguTemp);
ComboBox_DeleteString( hDeviceCombo, 0 );
}
    return TRUE;

default:
    return FALSE;
}
    ASSERT( FALSE );
    return FALSE;
    }

/* LoadRegistrySettings()
 *
 *    Load application global REGSETTINGS structure values from the registry.
 */
BOOL LoadRegistrySettings( void )
    {
    HKEYhReg;
    DWORDdwVal;
    DWORDcbValSize;
    BOOLfRet = TRUE;

    // Load current settings from our registry key
    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_CURRENT_USER, REG_SETTINGS_KEY,
0, KEY_READ, &hReg ))
{
GetMediaPath( grs.szInitialDir, sizeof(grs.szInitialDir));

grs.fDefaultDevice = TRUE;
grs.fUseExclusiveMode = FALSE;
grs.fOpen3D = FALSE;
if(!IsPentiumOrBetter())
{
gfDisable3D = TRUE;// it's false by default otherwise.
}
grs.dwDefaultFocusFlag = 0;
grs.szInitialDir[0] = '\0';
grs.dwPreferredFormat = aFormatOrder[0];
fRet = TRUE;
goto LRS_Return;
}

    // Load the "Use DirectSound Default Device" flag
    cbValSize = sizeof(dwVal);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_DEVICE_DEFAULT,
    NULL, NULL, (LPBYTE)&dwVal,
    &cbValSize ))
{
grs.fDefaultDevice = TRUE;
fRet = FALSE;
}
    else
{
grs.fDefaultDevice = (BOOL)dwVal;

if( !grs.fDefaultDevice )
    {
    // Load the GUID for the preferred device (only if it's not the default)
    cbValSize = sizeof(grs.guPreferredDevice);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_DEVICE_GUID,
    NULL, NULL,
    (LPBYTE)&grs.guPreferredDevice,
    &cbValSize ))
{
// Copy GUID_NULL into the guPreferredDevice (only works in C++)
grs.guPreferredDevice = GUID_NULL;
fRet = FALSE;
}
    }
}

    cbValSize = sizeof(dwVal);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_USE_EXCLUSIVE,
    NULL, NULL, (LPBYTE)&dwVal,

&cbValSize ))
{
grs.fUseExclusiveMode = FALSE;
fRet = FALSE;
}
    else
grs.fUseExclusiveMode = (BOOL)dwVal;

    // Load the flag telling us whether to default to 2D or 3D
if (!IsPentiumOrBetter())
{
gfDisable3D = TRUE;
grs.fOpen3D = FALSE;
}
else
{
gfDisable3D = FALSE;
cbValSize = sizeof(dwVal);
if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_OPEN3D,
NULL, NULL, (LPBYTE)&dwVal,
&cbValSize ))
{
grs.fOpen3D = FALSE;
fRet = FALSE;
}
else
grs.fOpen3D = (BOOL)dwVal;
}

    // Load the coded version of the preferred output format
    cbValSize = sizeof(grs.dwPreferredFormat);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_OUTPUT_FORMAT,
    NULL, NULL,
    (LPBYTE)&grs.dwPreferredFormat,
    &cbValSize ))
{
grs.dwPreferredFormat = aFormatOrder[0];
fRet = FALSE;
}

    // Load the default focus DSBCAPS flags
    cbValSize = sizeof(grs.dwDefaultFocusFlag);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_FOCUS_FLAG,
    NULL, NULL,
    (LPBYTE)&grs.dwDefaultFocusFlag,
    &cbValSize ))
{
grs.dwDefaultFocusFlag = 0;
fRet = FALSE;
}

    // Load the initial directory for WAVE files
    cbValSize = sizeof(grs.szInitialDir);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_INITIAL_DIR,
    NULL, NULL,
    (LPBYTE)&grs.szInitialDir,
    &cbValSize ))
{
GetMediaPath( grs.szInitialDir, sizeof(grs.szInitialDir));
fRet = FALSE;
}

    if( hReg != NULL )
{
RegCloseKey( hReg );
hReg = NULL;
}

LRS_Return:
    return fRet;
    }

/* SaveRegistrySettings()
 *
 *    Write the values in the REGSETTINGS global structure to the registry.
 */
BOOL SaveRegistrySettings( void )
    {
    HKEYhReg;
    DWORDdwVal, dwCreateDisposition;
    BOOLfRet = TRUE;

    // Save current settings to our registry key
    if( ERROR_SUCCESS != RegCreateKeyEx( HKEY_CURRENT_USER, REG_SETTINGS_KEY,
0, NULL, 0, KEY_WRITE, NULL, &hReg,
&dwCreateDisposition ))
{
fRet = FALSE;
goto SRS_Return;
}

    // Save the "Use DirectSound Default Device" flag
    dwVal = (DWORD)grs.fDefaultDevice;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_DEVICE_DEFAULT,
    0, REG_DWORD, (LPBYTE)&dwVal,
    sizeof(DWORD)))
{
fRet = FALSE;
}
    else
{
if( !grs.fDefaultDevice )
    {
    // Save the GUID for the preferred device (only if it's not the default)
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_DEVICE_GUID,
    0, REG_BINARY,
    (LPBYTE)&grs.guPreferredDevice,
    sizeof(GUID)))
{
fRet = FALSE;
}
    }
}

    // Use DSSCL_EXCLUSIVE ??
    dwVal = (DWORD)grs.fUseExclusiveMode;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_USE_EXCLUSIVE,
    0, REG_DWORD, (LPBYTE)&dwVal,
    sizeof(DWORD)))
{
fRet = FALSE;
}

#ifdef DEBUG
if (grs.fOpen3D)
{
ASSERT(gfDisable3D == FALSE);
}
#endif

    dwVal = (DWORD)grs.fOpen3D;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_OPEN3D,
    0, REG_DWORD, (LPBYTE)&dwVal,
    sizeof(DWORD)))
{
fRet = FALSE;
}

    // Save the coded version of the preferred output format
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_OUTPUT_FORMAT,
    0, REG_DWORD,
    (LPBYTE)&grs.dwPreferredFormat,
    sizeof(DWORD)))
{
fRet = FALSE;
}

    // Save the coded version of the preferred output format
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_FOCUS_FLAG,
    0, REG_DWORD,
    (LPBYTE)&grs.dwDefaultFocusFlag,
    sizeof(DWORD)))
{
fRet = FALSE;
}

    // Save the initial directory for WAVE files
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_INITIAL_DIR,
    0, REG_SZ,
    (LPBYTE)&grs.szInitialDir,
    lstrlen(grs.szInitialDir)))
{
fRet = FALSE;
}

    RegCloseKey( hReg );
    hReg = NULL;

SRS_Return:
    return fRet;
    }

/* GetMediaPath()
 *
 *    In the absence of a registry value, this function is called to pick a
 * starting point for the File|Open dialog.  Usually \DXSDK\SDK\MEDIA.
 */
void GetMediaPath( LPTSTR lpszBuf, int nBuf )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
REG_DIRECT3D_KEY,
0, KEY_READ, &hReg ))
{
        goto REG_OPEN_FAILED;
}
    else
{
// Query the Registry for the path to the media directory
cbStartPathLen = sizeof( szOpenStartDir );
if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_D3DPATH_VAL,
NULL, NULL,
(LPBYTE)szOpenStartDir,
&cbStartPathLen ))
    {
    goto REG_OPEN_FAILED;
    }
RegCloseKey( hReg );
hReg = NULL;
}

    return;

REG_OPEN_FAILED:
    // Start off by getting the Windows directory -- we're trying to build a
    // file path like "C:\WINDOWS\MEDIA", but the WINDOWS directory could be
    // named anything, so we must ask.
    GetWindowsDirectory( szOpenStartDir, sizeof(szOpenStartDir));
    // If there's no trailing backslash, append one
    if( lstrcmp( &szOpenStartDir[lstrlen(szOpenStartDir)], TEXT("\\") ))
lstrcat( szOpenStartDir, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( szOpenStartDir, TEXT("MEDIA"));
    }

/* FormatCodeToText()
 *
 *    This function reads format codes and puts out a text string for them.
 * It does not check for invalid codes.  FALSE return means the buffer was
 * invalid in some way, TRUE means success.
 *
 */
BOOL FormatCodeToText( DWORD dwFormat, LPTSTR lpszBuf, int nBufSize )
    {
    DWORD   dwFreq;

    // The longest string we'll ever put in is 21 characters (including NULL)
    if( NULL == lpszBuf || nBufSize < 21 )
return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    dwFreq = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    wsprintf( lpszBuf, "%u Hz, %u-bit %s", dwFreq, FC_GETBITS(dwFormat),
FC_GETCHANNELS(dwFormat) == 1 ? "Mono" : "Stereo" );

    return TRUE;
    }

/* FormatCodeToWFX()
 *
 *    This function reads format codes and fills most of the fields of a
 * WAVEFORMATEX structure based on the values read.  It does not fill the
 * wFormatTag or cbSize members.
 *
 */
BOOL FormatCodeToWFX( DWORD dwFormat, PWAVEFORMATEX pwfx )
    {
    DWORD   dwFreq;

    if( NULL == pwfx )
return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    pwfx->nSamplesPerSec = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    pwfx->wBitsPerSample = (WORD)FC_GETBITS(dwFormat);
    pwfx->nChannels = (WORD)FC_GETCHANNELS(dwFormat);

    // The nBlockAlign calculation below only works for whole-byte samples
    ASSERT( pwfx->wBitsPerSample % 8 == 0 );

    pwfx->nBlockAlign = pwfx->nChannels * (pwfx->wBitsPerSample / 8);
    pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;

    return TRUE;
    }

/* FormatCodeFromCommandID()
 *
 *    Returns the Format Code that matches the given command ID.
 */
DWORD FormatCodeFromCommandID( WORD wID )
    {
    int i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
{
if( fdFormats[i].wCommandID == wID )
    return fdFormats[i].dwCode;
}
    return 0;
    }

/* CommandIDFromFormatCode()
 *
 *    Searchs our FORMATDATA array and returns the Command ID corresponding
 * to the given format code, or 0 if there is no such valid format code.
 */
WORD CommandIDFromFormatCode( DWORD dwCode )
    {
    int i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
{
if( fdFormats[i].dwCode == dwCode )
    return fdFormats[i].wCommandID;
}
    return 0;
    }

void DisableFormatCode( DWORD dwCode )
    {
    inti;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
{
if( fdFormats[i].dwCode == dwCode )
    {
    fdFormats[i].fEnable = FALSE;
    break;
    }
}
    }

void EnableFormatCode( DWORD dwCode )
    {
    inti;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
{
if( fdFormats[i].dwCode == dwCode )
    {
    fdFormats[i].fEnable = TRUE;
    break;
    }
}
    }

/* DSEnumProc()
 *
 *   DirectSoundEnumerate() callback procedure which fills a combo box with
 * the description strings of all devices and attachs a pointer to a GUID,
 * which must be freed later by calling delete.
 */
BOOL CALLBACK DSEnumProc( LPGUID lpguDevice, LPCTSTR lpszDesc,
                                LPCTSTR lpszDrvName, LPVOID lpContext )
    {
    HWND   hCombo = *(HWND *)lpContext;
    LPGUID lpguTemp = NULL;
    int    idx;

    if( NULL != lpguDevice )
        {
        lpguTemp = (LPGUID)GlobalAlloc(GPTR, sizeof(GUID));
// We failed to allocate storage, so continue with next device
if( NULL == lpguTemp )
    return( TRUE );

        CopyMemory( lpguTemp, lpguDevice, sizeof(GUID));
}

    idx = ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    ComboBox_FindString( hCombo, 0, lpszDesc ),
    lpguTemp );

    if( !grs.fDefaultDevice )
{
if( NULL == lpguTemp )
    {
    if( grs.guPreferredDevice == GUID_NULL )
ComboBox_SetCurSel( hCombo, idx );
    }
else if( *lpguTemp == grs.guPreferredDevice )
    ComboBox_SetCurSel( hCombo, idx );
}

    return( TRUE );
    }

/* fGetToken()
 *
 *    Parses the command-line string "in place" starting at pszStart.  A ptr
 * to the start of the next token and it's length will be the out parameters,
 * or NULL and 0 if no token.  Note that *ppszRet will NOT be NULL-terminated
 * since the string is part of another string.  That's what then length is for.
 *
 * Returns: TRUE if a token was retrieved, or FALSE if there was no token.
 */
BOOL fGetToken( PSTR pszStart, PSTR *ppszRet, int *pcchRet )
    {
    PSTR  pszCur = pszStart;
    PSTR  pszTokStart;

    if( !pszStart || NULL == ppszRet || NULL == pcchRet )
return FALSE;

    // Skip leading whitespace
    while( *pszCur && (*pszCur == ' ' || *pszCur == '\t'))
pszCur++;

    *ppszRet = NULL;
    *pcchRet = 0;

    if( *pszCur )
{
pszTokStart = pszCur;

while( *pszCur && *pszCur != ' ' && *pszCur != '\t' )
    pszCur++;

*ppszRet = pszTokStart;
*pcchRet = (int)(pszCur - pszTokStart);
}

    if( *pcchRet != 0 )
return TRUE;
    else
return FALSE;
    }

/* fMatchToken()
 *
 *    Attempts to match the first cchLen characters of pszDatum to the
 * string at pszString.  The comparison is case-insensitive (this function
 * is designed for command-line switch matching).
 *
 * Returns: TRUE if the first cchLen characters are a match, else FALSE.
 */
BOOL fMatchToken( PSTR pszString, PSTR pszDatum, int cchLen )
    {
    inti;

    for( i = 0; i < cchLen; i++ )
{
if( CharLower( (LPTSTR)MAKELONG( pszString[i], 0 ))
    != CharLower( (LPTSTR)MAKELONG( pszDatum[i], 0 )))
    return FALSE;
}
    return TRUE;
    }

/* ParseCommandLine()
 *
 *    Given a command-line string without the module name, this function will
 * parse the command line and takes action on whatever it finds there.
 *
 * Returns: TRUE if successful, or FALSE if there was an error.
 */
BOOL ParseCommandLine(LPTSTR lpszCmdLine)
    {
    PSTRpszCur,pszToken;
    PSTRppszFiles[MAXCONTROLS];
    BOOLfStartPlaying = FALSE, fStartLooping = FALSE;
    intcchTokLen = 0, i, nFilesFound;

    pszCur = lpszCmdLine;

    // First get all the command line switches
    while( fGetToken(pszCur, &pszToken, &cchTokLen) &&
   (pszToken[0] == '/' || pszToken[0] == '-' ))
{
pszCur = pszToken + cchTokLen;
pszToken++;

if( fMatchToken( pszToken, "PLAY", 4 ))
    {
    fStartPlaying = TRUE;
    }
else if( fMatchToken( pszToken, "LOOP", 4 ))
    {
    fStartLooping = TRUE;
    }
else
    {
    // We don't recognize this mysterious switch, so eat it and move on
    }
}

    // Anything left on the command-line will be treated as a filename and
    // we'll attempt to open it after we've found them all
    nFilesFound = 0;
    while( fGetToken(pszCur, &pszToken, &cchTokLen) && nFilesFound < MAXCONTROLS )
{
pszCur = pszToken + cchTokLen;
ppszFiles[nFilesFound] = (LPTSTR)GlobalAlloc(GPTR, (cchTokLen+1) * sizeof(TCHAR));
// Copy the token out of the command-line string and into our buffer
CopyMemory( ppszFiles[nFilesFound], pszToken, cchTokLen*sizeof(TCHAR));
// Append a NULL terminator to what we just copied (to be safe)
*(ppszFiles[nFilesFound] + cchTokLen) = 0;
nFilesFound++;
}
    // This function will take the array of strings we've created and open
    // each string as a file.  It will obey the global fStartPlaying and
    // fStartLooping flags we may have already set above
    if( nFilesFound )
AppWnd.BatchOpenFiles( ppszFiles, nFilesFound, fStartPlaying, fStartLooping );

    // Free the space we allocated
    for( i = 0; i < nFilesFound; i++ )
{
GlobalFree(ppszFiles[i]);
ppszFiles[i] = NULL;
}

    // Returning TRUE means the caller should continue doing what they
    // were doing: we succeeded.
    return TRUE;
    }

// ===============================================================
// IsPentiumOrBetter
//returns FALSE if the machine is less than a Pentium.
// ===============================================================
BOOL IsPentiumOrBetter(void)
{
BOOL fRet = TRUE;
SYSTEM_INFO si;

ZeroMemory(&si, sizeof(SYSTEM_INFO));

GetSystemInfo(&si);

// should work with pentiums and higher.
if (si.dwProcessorType == PROCESSOR_INTEL_386 ||
si.dwProcessorType == PROCESSOR_INTEL_486)
{
fRet = FALSE;
}

return fRet;
}


DSSHOW3D.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       DSShow3D.h
 *  Content:    DirectSound Mixing Test with 3D sound main header file
 *
 ***************************************************************************/
#ifndef __DSSHOW3D_H__
#define __DSSHOW3D_H__

#define QQUOTE(x) #x
#define QUOTE(y) QQUOTE(y)
#define REMIND(str) __FILE__ "(" QUOTE(__LINE__) ") : " str

#include <mmsystem.h>
#include <dsound.h>
#include <d3dtypes.h>

#include "resource.h"
#include "debug.h"

#define MAXCONTROLS             200 // An insanely large number of controls

// To check for stopping of sounds, and updating UI, a timer is set...use this for the rate.
#define TIMERPERIOD200// In milliseconds

#define OPENFILENAME_F_LOCALFOCUS0x00000001
#define OPENFILENAME_F_STICKYFOCUS0x00000002
#define OPENFILENAME_F_GLOBALFOCUS0x00000004

#define OPENFILENAME_F_GETPOS0x00000008
#define OPENFILENAME_F_GETPOS20x00000010

#define OPENFILENAME_F_3D    0x00000020
#define OPENFILENAME_F_MUTEMAX      0x00000040
#define OPENFILENAME_F_NOTIFY0x00000080

#define MAXVOL_VAL0
#define MIDPAN_VAL0

// Registry Keys and Values
#define REG_SETTINGS_KEY    TEXT("Software\\Microsoft\\DSShow3D")
#define REG_SETTING_DEVICE_DEFAULT  TEXT("UseDSoundDefault")
#define REG_SETTING_USE_EXCLUSIVE   TEXT("UseExclusiveMode")
#define REG_SETTING_OUTPUT_FORMAT   TEXT("PreferredOutputFormat")
#define REG_SETTING_FOCUS_FLAG    TEXT("Focus")
#define REG_SETTING_OPEN3D    TEXT("DefaultOpen3D")
#define REG_SETTING_INITIAL_DIR    TEXT("InitialDirectory")
#define REG_SETTING_DEVICE_GUID    TEXT("PreferredDevice")
#define REG_DIRECT3D_KEY    TEXT("Software\\Microsoft\\Direct3D")
#define REG_D3DPATH_VAL    TEXT("D3D Path")

typedef struct tag_rs
{
    BOOL    fDefaultDevice;    // Use the DSound default device?
    BOOL    fOpen3D;    // Default to opening in 3D?
BOOLfDisable3D;// True on systems which are less than a Pentium.
    BOOL    fUseExclusiveMode;    // Use exclusive mode?
    DWORD   dwDefaultFocusFlag;    // The DSBCAPS flag for the default focus
    DWORD   dwPreferredFormat;    // Preferred output format
    TCHAR   szInitialDir[MAX_PATH]; // Initial open directory
    GUID    guPreferredDevice;    // GUID of preferred device, if not default
} REGSETTINGS, *PREGSETTINGS;

typedef struct tag_fd
{
    DWORD   dwCode;
    WORD    wCommandID;
    BOOL    fEnable;
} FORMATDATA, *PFORMATDATA;

///////////////////////////////////////////////////////////////////////
// Function prototypes
//

BOOL CALLBACK DSEnumProc( LPGUID, LPCTSTR, LPCTSTR, LPVOID );
BOOL CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK SettingsDlgProc(HWND, UINT, WPARAM, LPARAM);
UINT CALLBACK FileOpenCustomTemplateDlgProc(HWND, UINT, WPARAM, LPARAM);

BOOL OpenFileDialog(HWND, LPTSTR, int *, LPDWORD);
BOOL IsValidWave(LPTSTR);

BOOL FormatCodeToWFX( DWORD, PWAVEFORMATEX );
BOOL FormatCodeToText( DWORD, LPTSTR, int );

DWORD FormatCodeFromCommandID( WORD );
WORD CommandIDFromFormatCode( DWORD );
void DisableFormatCode( DWORD );
void EnableFormatCode( DWORD );
BOOL IsSameFormat(LPWAVEFORMATEX pwfx1, LPWAVEFORMATEX pwfx2);

#endif// __DSSHOW3D_H__


FILEINFO.CPP

///////////////////////////////////////////////////////////////////////////////
//
// FileInfo.cpp
//
//   Implementation of the FileInfo class, which is the main class used to
// data and user interface elements associated with a sound buffer in DSShow3D.
//
//

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>

#include "DSShow3D.h"
#include "GVars.h"
#include "FileInfo.h"
#include "wave.h"
#include "debug.h"

int FileInfo::m_xNextPos = 0;
int FileInfo::m_yNextPos = 0;

///////////////////////////////////////////////////////////////////////////////
// FileInfo()
//
//    Class constructor -- the pmw parameter defaults to NULL, but it should be
// something useful before the class is really used.
//
FileInfo::FileInfo( MainWnd *pmw )
{
    // create an event to pass to SetNotificationPositions
    m_dsbPosNotify.hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
    if(!IsValidHandle(m_dsbPosNotify.hEventNotify))
    {
        MessageBox(NULL, "CreateEvent Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
        PostQuitMessage(-1);
        return;
    }
    m_dwTimeNotified = 0L;

    m_pmwOwner = pmw;
    m_pbData = NULL;
    m_pwfx = NULL;
    ZeroMemory( m_szFileName, sizeof(m_szFileName));
    m_nFileIndex = 0;
    m_hwndInterface = NULL;
    ZeroMemory( &m_ht, sizeof(HWNDTABLE));
    m_dwInternalFlags = 0;
    m_fPlayButtonSaysPlay = TRUE;

    ZeroMemory( &m_dsbd, sizeof(DSBUFFERDESC));
    m_pDSB = NULL;
}

///////////////////////////////////////////////////////////////////////////////
// ~FileInfo()
//
//    Class destructor.
//
FileInfo::~FileInfo()
{
    m_pmwOwner = NULL;

    if( m_pDSB != NULL )
    {
        m_pDSB->Release();
        m_pDSB = NULL;
    }
    if( m_pwfx )
    {
        GlobalFree( m_pwfx );
        m_pwfx = NULL;
    }
    if( m_pbData )
    {
        GlobalFree( m_pbData );
        m_pbData = NULL;
    }
    if(m_pDSN)
    {
        m_pDSN->Release();
        m_pDSN = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
// SetFileName()
//
//    Set the internal filename data variable and implicitly update the caption
// to reflect the change.
//
void FileInfo::SetFileName( LPTSTR lpsz, int nIndx )
{
    lstrcpy( m_szFileName, lpsz );
    m_nFileIndex = nIndx;

    // If this Assert fails, then we were handed a bad index value
    ASSERT( m_nFileIndex < lstrlen( m_szFileName ));

    UpdateFileName();
}

///////////////////////////////////////////////////////////////////////////////
// LoadWave()
//
//    Given a filename and an index to the partial filename (i.e. an index into
// the possibly full pathname where the actual filename begins), this function
// will do everything needed to load a file into the FileInfo structure.
//
// Returns 0 on success, non-zero on failure.
//
int FileInfo::LoadWave( LPTSTR lpszFile, int nIndx )
{
    SetFileName( lpszFile, nIndx );

    // TODO: Need to add in support for ACM filters here

    // TODO: Need to determine what's "too big" to load static and then
    //       setup something for streaming the buffer instead.
    if( WaveLoadFile( m_szFileName, &m_cbDataSize, &m_pwfx, &m_pbData ) != 0 )
    {
    // There had better be a MainWnd object, or something is really messed
    ASSERT( m_pmwOwner );
    m_pmwOwner->MessageBox( "Bad wave file or file too big to fit in memory",
                            MB_OK|MB_ICONSTOP );
    goto LW_Error;
    }

    if( NewDirectSoundBuffer() != 0 )
    {
    // There had better be a MainWnd object, or something is really messed
    ASSERT( m_pmwOwner );
    m_pmwOwner->MessageBox( "Cannot create new DirectSoundBuffer object",
                            MB_OK|MB_ICONSTOP );
    goto LW_Error;
    }

    m_dwInternalFlags |= FI_INTERNALF_LOADED;

    // If we haven't failed in loading so far, this point will be a valid
    // pointer to the wave's data.
    ASSERT( NULL != m_pbData );

    // Create the ControlPod interface object
    CreateInterface( m_pmwOwner->GetHwnd());

    return 0;

LW_Error:
    return -1;
}

///////////////////////////////////////////////////////////////////////////////
// NewDirectSoundBuffer()
//
//    This function does all the work to create a new DirectSound buffer, and
// gets the interface pointers for both 2D and 3D (if the buffer is 3D).
//
int FileInfo::NewDirectSoundBuffer()
    {
    DSBCAPS         dsbc;
    HRESULT         hr;
    BYTE            *pbWrite1= NULL;
    BYTE            *pbWrite2    = NULL;
    DWORD           cbLen1;
    DWORD           cbLen2;
BOOL fNotify =IsNotifyOn(); // just to avoid making 2 calls to it.

    /* Set up the direct sound buffer. */
    m_dsbd.dwSize       = sizeof(DSBUFFERDESC);

    // We already set the flags to zero in the constructor.  Don't do it again
    // or we might wipe out anything a derived class has setup.
if (fNotify)
{
    m_dsbd.dwFlags    |= DSBCAPS_STATIC | DSBCAPS_CTRLPOSITIONNOTIFY;
}
else
{
m_dsbd.dwFlags    |= DSBCAPS_STATIC;
}

    m_dsbd.dwFlags    |= DSBCAPS_CTRLDEFAULT;// !!! default
    // The derived class will pick its 3D flags
    if( m_dwInternalFlags & IsSticky())
    {
        ASSERT( !IsGlobal());
        m_dsbd.dwFlags |= DSBCAPS_STICKYFOCUS;
    }
    if( m_dwInternalFlags & IsGlobal())
    {
        ASSERT( !IsSticky());
        m_dsbd.dwFlags |= DSBCAPS_GLOBALFOCUS;
    }
    if( m_dwInternalFlags & IsMutedAtMax())
    {
        m_dsbd.dwFlags |= DSBCAPS_MUTE3DATMAXDISTANCE;
    }

    // This flag can only be set if the open dialog detected emulation and
    // allowed the proper radio buttons to be enabled
    if( m_dwInternalFlags & IsUsingGetPos2())
    m_dsbd.dwFlags |= DSBCAPS_GETCURRENTPOSITION2;

    m_dsbd.dwBufferBytes    = m_cbDataSize;
    m_dsbd.lpwfxFormat      = m_pwfx;

    /* Make sure these are NULL before we start */
    m_pDSB = NULL;

    if( FAILED( hr = gpds->CreateSoundBuffer( &m_dsbd, &m_pDSB, NULL )))
    {
    goto ERROR_IN_ROUTINE;
    }

// now set up notification pointer.
if (fNotify)
{
if( FAILED(hr = m_pDSB->QueryInterface(IID_IDirectSoundNotify, (void**)&m_pDSN)))
        {
            MessageBox(NULL, "Failed to create notification object!", "Error!", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
            goto ERROR_IN_ROUTINE;
        }
}

    /* Ok, lock the sucker down, and copy the memory to it. */
    if( FAILED( hr = m_pDSB->Lock( 0, m_cbDataSize, (LPVOID *)&pbWrite1, &cbLen1,
                        (LPVOID *)&pbWrite2, &cbLen2, 0L )))
    {
    goto ERROR_IN_ROUTINE;
    }

    ASSERT( pbWrite1 != NULL );
    ASSERT( cbLen1 == m_cbDataSize );

    CopyMemory( pbWrite1, m_pbData, m_cbDataSize );

    ASSERT( 0 == cbLen2 );
    ASSERT( NULL == pbWrite2 );

    /* Ok, now unlock the buffer, we don't need it anymore. */
    if( FAILED( hr = m_pDSB->Unlock( pbWrite1, m_cbDataSize, pbWrite2, 0 )))
    {
    goto ERROR_IN_ROUTINE;
    }

    pbWrite1 = NULL;

    if (FAILED(hr = m_pDSB->SetVolume( MAXVOL_VAL )))
    {
    goto ERROR_IN_ROUTINE;
    }

    if (!Is3D()) {
        if( FAILED( hr = m_pDSB->SetPan( MIDPAN_VAL )))
        {
        goto ERROR_IN_ROUTINE;
        }
    }

    dsbc.dwSize = sizeof(dsbc);
    if( hr = m_pDSB->GetCaps( &dsbc ))
    {
    goto ERROR_IN_ROUTINE;
    }

    if( dsbc.dwFlags & DSBCAPS_LOCHARDWARE )
    {
    m_dwInternalFlags |= FI_INTERNALF_HARDWARE;
    }
    else
    {
    m_dwInternalFlags &= ~FI_INTERNALF_HARDWARE;
    }

    goto DONE_ROUTINE;

ERROR_IN_ROUTINE:
    if( pbWrite1 != NULL )
    {
    hr = m_pDSB->Unlock( pbWrite1, m_cbDataSize, pbWrite2, 0 );
    pbWrite1 = NULL;
    }

    if( NULL != m_pDSB )
    {
    m_pDSB->Release();
    m_pDSB = NULL;
    }

DONE_ROUTINE:
    return hr;
    }

///////////////////////////////////////////////////////////////////////////////
// SendDestroyRequest()
//
//    Ask the owning window object to destroy us and remove any information
// it may be keeping about our existence.
//
void FileInfo::SendDestroyRequest()
{
    // We must have an owner to send the request to.  We should never
    // have gotten any further than the CreateInterface() without one.
    ASSERT( NULL != m_pmwOwner );

    m_pmwOwner->DestroyFileInfo( this );
}

///////////////////////////////////////////////////////////////////////////////
// SendDestroyRequest()
//
//    Plays a buffer or updates playback flags according to some class state
// variables like our looping flag.
//
void FileInfo::PlayBuffer( void )
{
    if( m_pDSB )
    {
        if( IsLooped())
            m_pDSB->Play( 0, 0, DSBPLAY_LOOPING );
        else
            m_pDSB->Play( 0, 0, 0 );

        m_dwInternalFlags |= FI_INTERNALF_PLAYING;
    }
}

///////////////////////////////////////////////////////////////////////////////
// StopBuffer()
//
//    Stop the buffer and reset it's position to the start.
//
void FileInfo::StopBuffer( void )
{
    if( m_pDSB )
    {
        m_pDSB->Stop();
        m_pDSB->SetCurrentPosition( 0 );

        // Clear our internal state bit
        m_dwInternalFlags &= ~FI_INTERNALF_PLAYING;
    }
}

void FileInfo::Close( void )
{
    SendMessage( m_hwndInterface, WM_COMMAND, MAKELONG(IDCANCEL, 0), 0L );
}

///////////////////////////////////////////////////////////////////////////////
// DuplicateBuffer()
//
//    Initializes this FileInfo object by duplicating the given one.
//
void FileInfo::Duplicate( FileInfo *pfiSource )
{
    if( NULL == pfiSource || NULL == pfiSource->m_pDSB ||
        !(pfiSource->m_dwInternalFlags & FI_INTERNALF_LOADED))
        return;

    m_cbDataSize = pfiSource->m_cbDataSize;
    m_nFileIndex = pfiSource->m_nFileIndex;
    m_dwInternalFlags = pfiSource->m_dwInternalFlags;
    m_pmwOwner = pfiSource->m_pmwOwner;

    m_pwfx = (PWAVEFORMATEX)GlobalAlloc(GPTR, sizeof(WAVEFORMATEX) + pfiSource->m_pwfx->cbSize);
    m_pbData = (LPBYTE)GlobalAlloc(GPTR, pfiSource->m_cbDataSize);

    CopyMemory( m_pbData, pfiSource->m_pbData, pfiSource->m_cbDataSize );
    CopyMemory( m_pwfx, pfiSource->m_pwfx,
                sizeof(WAVEFORMATEX) + pfiSource->m_pwfx->cbSize);
    CopyMemory( &m_dsbd, &pfiSource->m_dsbd, sizeof(m_dsbd));
    CopyMemory( &m_szFileName, pfiSource->m_szFileName, sizeof(m_szFileName));

    ASSERT( NULL != pfiSource->m_pDSB );
    ASSERT( NULL != gpds );

    HRESULThr;

    if( FAILED( hr = gpds->DuplicateSoundBuffer(pfiSource->m_pDSB, &m_pDSB)))
    {
        MessageBox(NULL, "Failed to DuplicateSoundBuffer()", "Error!", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
    }
    else
    {
        // now set up notification pointer.
    if (IsNotifyOn())
    {
    if( FAILED( hr = m_pDSB->QueryInterface(IID_IDirectSoundNotify, (void**)&m_pDSN)))
            {
                MessageBox(NULL, "Failed to create notification object!", "Error!", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
                PostQuitMessage(-1);
                return;
            }
    }

        CreateInterface( m_pmwOwner->GetHwnd());
    }
}

///////////////////////////////////////////////////////////////////////////////
// CreateInterface()
//
//    Creates an interface window. A return of TRUE indicates success.
//
BOOL FileInfo::CreateInterface( HWND hwndOwner )
{
    m_hwndInterface = CreateDialogParam( ghInst, MAKEINTRESOURCE(IDD_BUFFER),
                   hwndOwner, (DLGPROC)FileInfoDlgProc,
                   (LPARAM)this );

    if( NULL == m_hwndInterface )
        goto FICI_Fail;

    UpdateFileName();

    CascadeWindow();

    ShowWindow( m_hwndInterface, SW_SHOW );

    // This flag tells us an interface window was successfully created
    m_dwInternalFlags |= FI_INTERNALF_INTERFACE;
    return TRUE;

FICI_Fail:
    if( m_hwndInterface )
    {
        DestroyWindow( m_hwndInterface );
        m_hwndInterface = NULL;
    }
    // Clear the flag that says we have a good interface window created
    m_dwInternalFlags &= ~FI_INTERNALF_INTERFACE;
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// ResetCascade()
//
//
//
void FileInfo::ResetCascade( void )
{
    POINT   ptParent;

    ptParent.x = ptParent.y = 0;
    ClientToScreen( m_pmwOwner->GetHwnd(), &ptParent );
    m_xNextPos = ptParent.x;
    m_yNextPos = ptParent.y;
}

///////////////////////////////////////////////////////////////////////////////
// MinimizeWindow()
//
//
//
void FileInfo::MinimizeWindow( void )
{
    ShowWindow( m_hwndInterface, SW_MINIMIZE );
}

///////////////////////////////////////////////////////////////////////////////
// RestoreWindow()
//
//
//
void FileInfo::RestoreWindow( void )
{
    SendMessage( m_hwndInterface, WM_SYSCOMMAND, SC_RESTORE, 0L );
}

///////////////////////////////////////////////////////////////////////////////
// CascadeWindow()
//
//
//
void FileInfo::CascadeWindow( void )
{
    RECT    rcWind;
    int     nStep;

    // Don't move minimized windows
    if( IsIconic( m_hwndInterface ))
        return;

    GetWindowRect( m_hwndInterface, &rcWind );

    if( m_xNextPos + (rcWind.right - rcWind.left) > GetSystemMetrics(SM_CXSCREEN))
        ResetCascade();
    else if( m_yNextPos + (rcWind.bottom - rcWind.top) > GetSystemMetrics(SM_CYSCREEN))
        ResetCascade();

    SetWindowPos( m_hwndInterface, NULL, m_xNextPos, m_yNextPos,
                    0, 0, SWP_NOSIZE | SWP_NOZORDER );
    // Move diagonally by the height of the title bar
    nStep = GetSystemMetrics(SM_CYCAPTION);
    m_xNextPos += nStep;
    m_yNextPos += nStep;
}

///////////////////////////////////////////////////////////////////////////////
// FileInfoDlgProc()
//
//    Window message callback function for all Interface DLGs'.  Route messages
// to message handler functions
//
BOOL CALLBACK FileInfoDlgProc( HWND hDlg, UINT message,
                        WPARAM wParam, LPARAM lParam )
{
    FileInfo *pfi;

    switch( message )
    {
        // The first step is to stash our class object pointer in the user data
        // and Initialize all our controls and internal data members.
        case WM_INITDIALOG:
            ASSERT( NULL != lParam );
            pfi = (FileInfo *)lParam;
            SetWindowLong( hDlg, DWL_USER, (LONG)pfi );

            if( !pfi->OnInitDialog( hDlg, wParam ))
            {
                DestroyWindow( hDlg );
            }
            return TRUE;

        // By setting the global variable that tracks the active dialog,
        // we can easily dispatch keyboard messages to the proper dialog
        // through IsDialogMessage() in our main message pump.
        case WM_ACTIVATE:
            if( !wParam )
            ghDlgActive = NULL;
            else
            ghDlgActive = hDlg;
            return TRUE;

        case WM_INITMENU:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            return pfi->OnInitMenu( wParam, lParam );

        case WM_DROPFILES:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            return pfi->m_pmwOwner->SendMessage( WM_DROPFILES, wParam, lParam );

        case WM_COMMAND:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            return pfi->OnCommand( wParam, lParam );

        // Handle this to deal with right-clicks on our controls -- we have a
        // bunch of different context menus that we can popup
        case WM_CONTEXTMENU:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            return pfi->OnContextMenu( hDlg, LOWORD(lParam), HIWORD(lParam));

        // Trackbar slider notifications come through here
        case WM_HSCROLL:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            return pfi->OnHScroll( LOWORD(wParam), (LONG)HIWORD(wParam), (HWND)lParam );

        case WM_DESTROY:
            pfi = (FileInfo*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi );
            pfi->OnDestroy();
            return TRUE;

        default:
            return FALSE;
    }

    ASSERT( FALSE );
}

/* OnInitMenu()
 *
 *    Updates the state of items on the menus in response to a WM_INITMENU
 * message, which means the menu is about to be displayed.
 */
BOOL FileInfo::OnInitMenu( WPARAM wParam, LPARAM lParam )
{
    HMENU   hSys = GetSystemMenu( m_hwndInterface, FALSE );

    EnableMenuItem( hSys, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSys, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );
    return TRUE;
}

/* OnInitDialog()
 *
 *    Handles the initialization of the FilInfo interface window, which is
 * actually a modeless dialog.
 */
BOOL FileInfo::OnInitDialog( HWND hDlg, WPARAM wParam )
{
    TCHAR   szBuf1[64], tszFmt[64];

    // Grab a bunch of handles and store them for easy access later
    m_ht.hPlayButton = GetDlgItem( hDlg, IDC_BUFFERDLG_PLAY_BUTTON );
    m_ht.hLoopedCheck = GetDlgItem( hDlg, IDC_BUFFERDLG_LOOPED_CHECK );
    m_ht.hProgressSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_PROGRESS_SLIDER );
    m_ht.hProgressText = GetDlgItem( hDlg, IDC_BUFFERDLG_PROGRESS_TEXT );
    m_ht.hProgressSpin = GetDlgItem( hDlg, IDC_BUFFERDLG_PROGRESS_SPIN );
    m_ht.hFreqText = GetDlgItem( hDlg, IDC_BUFFERDLG_FREQ_TEXT );
    m_ht.hFreqSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_FREQ_SLIDER );
    m_ht.hVolText = GetDlgItem( hDlg, IDC_BUFFERDLG_VOL_TEXT );
    m_ht.hVolSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_VOL_SLIDER );
    m_ht.hPanText = GetDlgItem( hDlg, IDC_BUFFERDLG_PAN_TEXT );
    m_ht.hPanSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_PAN_SLIDER );
    m_ht.hPlayCursorText = GetDlgItem( hDlg, IDC_BUFFERDLG_PLAYCURSOR_TEXT );
    m_ht.hWriteCursorText = GetDlgItem( hDlg, IDC_BUFFERDLG_WRITECURSOR_TEXT );
    m_ht.hDataFormatText = GetDlgItem( hDlg, IDC_BUFFERDLG_DATAFORMAT_TEXT );
    m_ht.hBufferTypeText = GetDlgItem( hDlg, IDC_BUFFERDLG_BUFFERTYPE_TEXT );
    m_ht.hFocusModeText = GetDlgItem( hDlg, IDC_BUFFERDLG_FOCUS_TEXT );
    m_ht.hGetPosModeText = GetDlgItem( hDlg, IDC_BUFFERDLG_GETPOS_TEXT );
    m_ht.hNotifyText = GetDlgItem( hDlg, IDC_BUFFERDLG_NOTIFY_TEXT );
    m_ht.hNotifySlider = GetDlgItem( hDlg, IDC_BUFFERDLG_NOTIFY_SLIDER );
    m_ht.hNotifyFrame = GetDlgItem( hDlg, IDC_BUFFERDLG_NOTIFY_FRAME );

    // Load, fill in and set the string describing the format of the sound data
    if( m_pwfx->nChannels == 1 )
    LoadString( ghInst, IDS_DATAFORMAT_MONO, tszFmt, sizeof(tszFmt)-1);
    else
    LoadString( ghInst, IDS_DATAFORMAT_STEREO, tszFmt, sizeof(tszFmt)-1);
    wsprintf( szBuf1, tszFmt, m_pwfx->nSamplesPerSec, m_pwfx->wBitsPerSample );

    Static_SetText( m_ht.hDataFormatText, szBuf1 );

    // Set the Buffer Type text to HARDWARE or SOFTWARE
    if( IsHardware())
        LoadString( ghInst, IDS_BUFFERTYPE_HARDWARE, szBuf1, sizeof(szBuf1)-1);
    else
        LoadString( ghInst, IDS_BUFFERTYPE_SOFTWARE, szBuf1, sizeof(szBuf1)-1);
    Static_SetText( m_ht.hBufferTypeText, szBuf1 );

    // Set the focus type to LOCAL, STICKY, or GLOBAL
    if( IsSticky())
        LoadString( ghInst, IDS_FOCUSMODE_STICKY, szBuf1, sizeof(szBuf1)-1);
    else if( IsGlobal())
        LoadString( ghInst, IDS_FOCUSMODE_GLOBAL, szBuf1, sizeof(szBuf1)-1);
    else
        LoadString( ghInst, IDS_FOCUSMODE_LOCAL, szBuf1, sizeof(szBuf1)-1);
    Static_SetText( m_ht.hFocusModeText, szBuf1 );

    if( Is3D())
    {
        Static_Enable( m_ht.hPanText, FALSE );
        Static_Enable( m_ht.hPanSlider, FALSE );
    }

if (IsNotifyOn())
{
Static_Enable(m_ht.hNotifySlider, TRUE);
Static_Enable(m_ht.hNotifyText, TRUE);
Static_Enable(m_ht.hNotifyFrame, TRUE);
}
else
{
Static_Enable(m_ht.hNotifySlider, FALSE);
Static_Enable(m_ht.hNotifyText, FALSE);
Static_Enable(m_ht.hNotifyFrame, FALSE);
}

    // Set the range, page size, etc. of our "slider" (trackbar) controls
    SetSliders();

    // Update the state of the UI elements that change
    UpdateUI();
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// OnContextMenu()
//
//    Handles a right-click in the client area by popping up a menu if we have
// one we'd like to display, or returning otherwise. TRUE indicates the message
// was "handled" and no further processing is needed.  FALSE means the message
// was not handled.
//
BOOL FileInfo::OnContextMenu( HWND hwnd, int x, int y )
{
    HMENU   hm, hSub;
    int     nSubMenu = -1, idFrom = 0;
    POINT   pt = { x, y };
    RECT    rectWind1, rectWind2;

    // Set the sub-menu to the Frequency context menu if we hit the Freq. Slider
    // or text control
    GetWindowRect( m_ht.hFreqSlider, &rectWind1 );
    GetWindowRect( m_ht.hFreqText, &rectWind2 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt ))
        nSubMenu = 0;

    // Set the sub-menu to the Volume context menu if we hit the Vol. Slider
    // or text control
    GetWindowRect( m_ht.hVolSlider, &rectWind1 );
    GetWindowRect( m_ht.hVolText, &rectWind2 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt ))
        nSubMenu = 1;

    // Only have pan on non-3D buffers
    if (!Is3D())
    {
        // Set the sub-menu to the Pan context menu if we hit the Pan Slider
        // or text control
        GetWindowRect( m_ht.hPanSlider, &rectWind1 );
        GetWindowRect( m_ht.hPanText, &rectWind2 );

        if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt ))
        nSubMenu = 2;
        }

    // We didn't detect any "interesting" hotspots, so return as unprocessed.
    if( nSubMenu < 0 )
        return FALSE;

    // If we make it here, we're gonna popup a context menu of some sort

    // Attempt to load our menu.  If we fail, we still handled the message
    // so return TRUE
    if(( hm = LoadMenu( ghInst, MAKEINTRESOURCE(IDM_POPUPS))) == NULL )
        return TRUE;

    hSub = GetSubMenu( hm, nSubMenu );
    TrackPopupMenu( hSub, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
            pt.x, pt.y, 0, m_hwndInterface, NULL );

    DestroyMenu( hm );

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// SetSliders()
//
//    Sets the range, page size, tic frequency, and other parameters of all the
// trackbar controls we use in the interface.
//
void FileInfo::SetSliders( void )
{
    DWORD   dwSampleRateRange, dwMaxFreq, dwMinFreq;
    UDACCEL udAccel;
    DSCAPS dsc;

// If the buffer is in hardware, query the card for samplerates
// else use our default values.
if (IsHardware())
{
int hr = DS_OK;
// Get the min/max frequency range by querying the card.
memset(&dsc, 0, sizeof(DSCAPS));
dsc.dwSize = sizeof(DSCAPS);
hr = gpds->GetCaps(&dsc);
ASSERT(hr == DS_OK);
        dwMinFreq = dsc.dwMinSecondarySampleRate;
        dwMaxFreq = dsc.dwMaxSecondarySampleRate;
    }
else
{
dwMinFreq = DSBFREQUENCY_MIN;
dwMaxFreq = DSBFREQUENCY_MAX;
}

    dwSampleRateRange = dwMaxFreq - dwMinFreq;

    // Scale the sample rate range into an acceptable span and keep
    // track of the multiplying constant we'll have to use later when
    // we want to set positions based on real values
    m_dwFreqSliderFactor = 1;

    while( dwSampleRateRange > 10000 )
    {
        dwSampleRateRange /= 10;
        m_dwFreqSliderFactor *= 10;
    }

    SendMessage( m_ht.hFreqSlider, TBM_SETRANGEMIN,
                 FALSE, (LPARAM)dwMinFreq / m_dwFreqSliderFactor );
    SendMessage( m_ht.hFreqSlider, TBM_SETRANGEMAX,
                 FALSE, (LPARAM)dwMaxFreq / m_dwFreqSliderFactor );
    SendMessage( m_ht.hFreqSlider, TBM_SETPAGESIZE, 0,
                 FREQ_SLIDER_PAGESIZE_HZ / m_dwFreqSliderFactor );

    SendMessage( m_ht.hProgressSpin, UDM_SETBUDDY, (WPARAM)m_ht.hProgressSlider, 0 );
    SendMessage( m_ht.hProgressSpin, UDM_SETRANGE, 0, MAKELONG(PROGRESS_MAX, PROGRESS_MIN));
    SendMessage( m_ht.hProgressSpin, UDM_SETPOS, 0, MAKELONG(0,0));
    udAccel.nSec = 0;
    udAccel.nInc = PROGRESS_MAX / 20;
    SendMessage( m_ht.hProgressSpin, UDM_SETACCEL, 1, (LPARAM)&udAccel );

    SendMessage( m_ht.hProgressSlider, TBM_SETRANGEMIN, FALSE, (LPARAM)PROGRESS_MIN );
    SendMessage( m_ht.hProgressSlider, TBM_SETRANGEMAX, FALSE, (LPARAM)PROGRESS_MAX );
    SendMessage( m_ht.hProgressSlider, TBM_SETPAGESIZE, FALSE, (LPARAM)PROGRESS_MAX / 20 );
    SendMessage( m_ht.hProgressSlider, TBM_SETTICFREQ, (WPARAM)PROGRESS_TIC, 0 );

    SendMessage( m_ht.hNotifySlider, TBM_SETRANGEMIN, FALSE, (LPARAM)0 );
    SendMessage( m_ht.hNotifySlider, TBM_SETRANGEMAX, FALSE, (LPARAM)NOTIFY_MAX );
    SendMessage( m_ht.hNotifySlider, TBM_SETPAGESIZE, FALSE, (LPARAM)NOTIFY_MAX / 20 );

    // Intentionally set the range backwards because a large number means
    // a smaller value
    SendMessage( m_ht.hVolSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(VOL_MIN + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );
    SendMessage( m_ht.hVolSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(VOL_MAX + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );
    SendMessage( m_ht.hVolSlider, TBM_SETPAGESIZE, 0,
                 VOL_SLIDER_PAGE / VOL_SLIDER_FACTOR );
    // NOTE: No TICs on the volume slider

    SendMessage( m_ht.hPanSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(PAN_MIN + PAN_SLIDER_SHIFT) / PAN_SLIDER_FACTOR );
    SendMessage( m_ht.hPanSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(PAN_MAX + PAN_SLIDER_SHIFT) / PAN_SLIDER_FACTOR );
    SendMessage( m_ht.hPanSlider, TBM_SETPAGESIZE, 0,
                 PAN_SLIDER_PAGE / PAN_SLIDER_FACTOR );
    // NOTE: No TICs on the pan slider

    // Update the display from the buffer's current settings
    UpdateFreqUI( 0, TRUE );
    UpdateVolUI( 0, TRUE );
    UpdatePanUI( 0, TRUE );
}

///////////////////////////////////////////////////////////////////////////////
// UpdateUI()
//
//    This function is normally called from the MainWnd object's timer handler
// to refresh the UI elements that are time-dependent.  These are things like
// the play and write cursors and the progress.
//
void FileInfo::UpdateUI( void )
{
    TCHAR   szText[8];
    DWORD   dwStatus, dwPlay, dwWrite;

    if( NULL == m_pDSB )
        return;

    if( FAILED( m_pDSB->GetStatus( &dwStatus )))
        return;

    if( dwStatus & DSBSTATUS_BUFFERLOST )
    {
        LPBYTEpb1, pb2;
        DWORDcb1, cb2;

        if( SUCCEEDED( m_pDSB->Restore()))
        {
            if( SUCCEEDED( m_pDSB->Lock( 0, m_cbDataSize, (LPVOID *)&pb1, &cb1, (LPVOID *)&pb2, &cb2, 0 )))
            {
                ASSERT( m_cbDataSize == cb1 );
                if( NULL != m_pbData )
                    CopyMemory( pb1, m_pbData, m_cbDataSize );
                m_pDSB->Unlock( pb1, m_cbDataSize, pb2, 0 );

                if(IsPlaying())
                    PlayBuffer();
            }
        }
    }
    else
    {
        if( dwStatus & DSBSTATUS_PLAYING )
            SetPlaying( TRUE );
        else
            SetPlaying( FALSE );
    }

    Button_SetCheck( m_ht.hLoopedCheck, IsLooped());

    UpdatePlayButton();

    m_pDSB->GetCurrentPosition( &dwPlay, &dwWrite );
    wsprintf( szText, "%u", dwPlay );
    Static_SetText( m_ht.hPlayCursorText, szText );
    wsprintf( szText, "%u", dwWrite );
    Static_SetText( m_ht.hWriteCursorText, szText );

    UpdateProgressUI( dwPlay );

    // Check to see if the notification event is signaled
    UpdateNotifySignal();

    return;
}

///////////////////////////////////////////////////////////////////////////////
// UpdateProgressUI()
//
//
//
void FileInfo::UpdateProgressUI( DWORD dwPlayPos )
{
    TCHAR   szText[8];
    FLOAT   fPercentage;

    // TODO: Set the progress slider position
    fPercentage = ((FLOAT)dwPlayPos / (FLOAT)(m_cbDataSize-1));

    SendMessage( m_ht.hProgressSlider, TBM_SETPOS, TRUE, (LPARAM)(PROGRESS_MAX * fPercentage));
    SendMessage( m_ht.hProgressSpin, UDM_SETPOS, TRUE, MAKELONG((PROGRESS_MAX * fPercentage), 0));

    wsprintf( szText, "%i%%", (int)(100 * fPercentage));
    Static_SetText( m_ht.hProgressText, szText );

}

///////////////////////////////////////////////////////////////////////////////
// UpdateVolUI()
//
//    Updates the position of the volume slider and text. You can specify that
// it get the volume from the buffer by passing TRUE to fFromBuffer, or you can
// specify a volume by putting it in lForceVol and setting fFromBuffer = FALSE.
//
void FileInfo::UpdateVolUI( LONG lForceVol, BOOL fFromBuffer )
{
    LONG    lVol;
    TCHAR   szText[16];

    if( fFromBuffer )
    {
        if( NULL != m_pDSB )
        {
            m_pDSB->GetVolume( &lVol );
        }
        else
            lVol = 0;
    }
    else
        lVol = lForceVol;

    SendMessage( m_ht.hVolSlider, TBM_SETPOS, (WPARAM)TRUE,
            (LPARAM)(lVol + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );

    // Print volume in decibels
    wsprintf( szText, "%i dB", lVol / 100 );

    Static_SetText( m_ht.hVolText, szText );
}

///////////////////////////////////////////////////////////////////////////////
// UpdatePanUI()
//
//    Updates the position of the panning slider and text. You can specify that
// it get the pan from the buffer by passing TRUE to fFromBuffer, or you can
// specify a pan by putting it in lForcePan and setting fFromBuffer = FALSE.
//
void FileInfo::UpdatePanUI( LONG lForcePan, BOOL fFromBuffer )
{
    LONG    lPan;
    TCHAR   szText[16];

    if ( Is3D())
    {
        lForcePan = 0;
        fFromBuffer = FALSE;
    }

    if( fFromBuffer )
    {
        if( NULL != m_pDSB )
        {
            m_pDSB->GetPan( &lPan );
        }
        else
            lPan = 0;
    }
    else
        lPan = lForcePan;

    SendMessage( m_ht.hPanSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lPan + PAN_SLIDER_SHIFT) / PAN_SLIDER_FACTOR );

    // Print pan in decibels
    wsprintf( szText, "%i dB", lPan / 100 );

    Static_SetText( m_ht.hPanText, szText );
}

///////////////////////////////////////////////////////////////////////////////
// UpdateFreqUI()
//
//    Updates the position of the freq slider and text. You can specify that
// it get the freq from the buffer by passing TRUE to fFromBuffer, or you can
// specify a freq by putting it in dwForceFreq and setting fFromBuffer = FALSE.
//
void FileInfo::UpdateFreqUI( DWORD dwForceFreq, BOOL fFromBuffer )
{
    DWORD   dwFreq;
    TCHAR   szText[16];

    if( fFromBuffer )
    {
        if( NULL != m_pDSB )
            m_pDSB->GetFrequency( &dwFreq );
        else
            dwFreq = 0;
    }
    else
        dwFreq = dwForceFreq;

    SendMessage( m_ht.hFreqSlider, TBM_SETPOS,
                 (WPARAM)TRUE, (LPARAM)dwFreq / m_dwFreqSliderFactor );

    wsprintf( szText, "%u Hz", dwFreq );

    Static_SetText( m_ht.hFreqText, szText );
}

///////////////////////////////////////////////////////////////////////////////
// UpdatePlayButton()
//
//    Uses an internal state flag and the "IsPlaying" flag to determine and set
// the proper text (Play/Stop) for the Play button.
//
void FileInfo::UpdatePlayButton( void )
{
    if( m_fPlayButtonSaysPlay && IsPlaying())
    {
        // Set to "Stop"
        m_fPlayButtonSaysPlay = FALSE;
        Button_SetText( m_ht.hPlayButton, TEXT("Stop"));
    }
    else if( !m_fPlayButtonSaysPlay && !IsPlaying())
    {
        // Set to "Play"
        Button_SetText( m_ht.hPlayButton, TEXT("Play"));
        m_fPlayButtonSaysPlay = TRUE;
    }
    else
        return;
}

///////////////////////////////////////////////////////////////////////////////
// OnHScroll()
//
//    Main message handler for the WM_HSCROLL message.  this function basically
// figures out which horizontal scrolling control is responsible for sending
// the message and passes on handling to an appropriate function for handling.
//
BOOL FileInfo::OnHScroll( WORD wNotification, LONG lPos, HWND hControl )
{
    if( !hControl )
        return FALSE;

    if( hControl == m_ht.hProgressSpin )
    {
        HandleProgressSpinScroll( wNotification, lPos );
        return TRUE;
    }
    else if( hControl == m_ht.hProgressSlider )
    {
        HandleProgressSliderScroll( wNotification, lPos );
        return TRUE;
    }
    else if( hControl == m_ht.hFreqSlider )
    {
        HandleFreqSliderScroll( wNotification, lPos );
        return TRUE;
    }
    else if( hControl == m_ht.hVolSlider )
    {
        HandleVolSliderScroll( wNotification, lPos );
        return TRUE;
    }
    else if( hControl == m_ht.hPanSlider )
    {
        HandlePanSliderScroll( wNotification, lPos );
        return TRUE;
    }
    else if( hControl == m_ht.hNotifySlider )
    {
        HandleNotifySliderScroll( wNotification, lPos );
        return TRUE;
    }
    else
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// HandleFreqSliderScroll()
//
//    Helper function for OnHScroll() which handles the WM_HSCROLL message for
// the Frequency slider.  Figures out the next position, sets it, and updates
// the UI elements that are affected (text).
//
void FileInfo::HandleFreqSliderScroll( WORD wNot, LONG lPos )
{
    DWORD dwFreq;

    switch( wNot )
    {
    case TB_THUMBTRACK:
        if( NULL != m_pDSB )
        {
            if( SUCCEEDED( m_pDSB->SetFrequency( lPos * m_dwFreqSliderFactor )))
            {
                UpdateFreqUI( lPos * m_dwFreqSliderFactor, FALSE );
            }
        }
        break;

    case TB_ENDTRACK:
    case TB_LINEDOWN:
    case TB_LINEUP:
    case TB_PAGEDOWN:
    case TB_PAGEUP:
        lPos = SendMessage( m_ht.hFreqSlider, TBM_GETPOS, 0, 0 );
        if( NULL != m_pDSB )
        {
            if( SUCCEEDED( m_pDSB->SetFrequency( lPos * m_dwFreqSliderFactor )))
            {
                UpdateFreqUI( lPos * m_dwFreqSliderFactor, FALSE );
            }
            else
            {
                if( SUCCEEDED( m_pDSB->GetFrequency( &dwFreq )))
                {
                    SendMessage( m_ht.hFreqSlider, TBM_SETPOS,
                                 TRUE, dwFreq / m_dwFreqSliderFactor );
                }
            }
        }
        break;
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleProgressSliderScroll()
//
//
//
void FileInfo::HandleProgressSliderScroll( WORD wNot, LONG lPos )
{
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            if( IsPlaying())
                fUpdate = TRUE;
            break;

        case TB_ENDTRACK:
            fUpdate = FALSE;
            break;

        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht.hProgressSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB )
    {
        FLOATfPercentage = ((FLOAT)lPos / 10000);

        m_pDSB->SetCurrentPosition( (DWORD)(fPercentage * (m_cbDataSize-1)));
        UpdateUI();
    //UpdateProgressUI(  (DWORD)(fPercentage * (m_cbDataSize-1)));
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleProgressSpinScroll()
//
//
//
void FileInfo::HandleProgressSpinScroll( WORD wNot, LONG lPos )
{
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case SB_THUMBPOSITION:
            lPos = SendMessage( m_ht.hProgressSpin, UDM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB )
    {
        FLOATfPercentage = ((FLOAT)lPos / 10000);

        m_pDSB->SetCurrentPosition( (DWORD)(fPercentage * (m_cbDataSize-1)));
        UpdateUI();
        UpdateProgressUI(  (DWORD)(fPercentage * (m_cbDataSize-1)));
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleVolSliderScroll()
//
//    Helper function for OnHScroll() which handles the WM_HSCROLL message for
// the Volume slider.  Figures out the next position, sets it, and updates
// the UI elements that are affected (text).
//
void FileInfo::HandleVolSliderScroll( WORD wNot, LONG lPos )
{
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht.hVolSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB )
    {
        m_pDSB->SetVolume( (lPos * VOL_SLIDER_FACTOR) - VOL_SLIDER_SHIFT );
        DPF( 1, "SetVolume: %i", (lPos * VOL_SLIDER_FACTOR) - VOL_SLIDER_SHIFT );
        UpdateVolUI( (lPos * VOL_SLIDER_FACTOR) - VOL_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandlePanSliderScroll()
//
//    Helper function for OnHScroll() which handles the WM_HSCROLL message for
// the Pan slider.  Figures out the next position, sets it, and updates the UI
// elements that are affected (text).
//
void FileInfo::HandlePanSliderScroll( WORD wNot, LONG lPos )
{
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht.hPanSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB )
    {
        m_pDSB->SetPan( (lPos * PAN_SLIDER_FACTOR) - PAN_SLIDER_SHIFT );
        UpdatePanUI( (lPos * PAN_SLIDER_FACTOR) - PAN_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleNotifySliderScroll()
//
// function: handles WM_SCROLL messages on behalf of the IDC_BUFFERDLG_NOTIFY_SLIDER
//           control
//
void FileInfo::HandleNotifySliderScroll( WORD wNot, LONG lPos )
{
    BOOL        fUpdate = FALSE;
    HRESULThRes    = DS_OK;
    float       flPosFactor;

    // if notifications are enabled, then pay attention to WM_HSCROLL messages
    if(IsNotifyOn())
    {
        switch( wNot )
        {
            case TB_ENDTRACK:
            case TB_LINEDOWN:
            case TB_LINEUP:
            case TB_PAGEDOWN:
            case TB_PAGEUP:
                fUpdate = TRUE;
                lPos = SendMessage( m_ht.hNotifySlider, TBM_GETPOS, 0, 0 );
                break;

            case TB_THUMBTRACK:
            default:
                fUpdate = FALSE;
        }

        // if we actually changed something, then bother with SetPositionNotify
        if( (fUpdate))
        {
    ASSERT(m_pDSB);
    ASSERT(m_pDSN);

            // the slider position expressed as a fraction
            flPosFactor = (float)lPos / (float)NOTIFY_MAX;

            // ... translated into bytes into the buffer
            m_dsbPosNotify.dwOffset = (DWORD)((float)m_cbDataSize * flPosFactor);

            // you must stop the buffer before you SetNotificationPositions
            if(IsPlaying())
            {
                m_pDSB->Stop();
            }

            // (re)set the notification
            ASSERT(m_pDSN);
            hRes = m_pDSN->SetNotificationPositions(1, &m_dsbPosNotify);

            if(FAILED(hRes))
            {
                MessageBox(NULL, "SetNotificationPositions Failed!", "", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
            }
            else
            {
                if(IsPlaying())
                {
                    if( IsLooped())
                    {
                        m_pDSB->Play( 0, 0, DSBPLAY_LOOPING );
                    }
                    else
                    {
                        m_pDSB->Play( 0, 0, 0 );
                    }
                }
            }
        }
    }
    // otherwise, set the position to 0
    else
    {
        lPos = SendMessage( m_ht.hNotifySlider, TBM_SETPOS, TRUE, 0 );
    }
}

///////////////////////////////////////////////////////////////////////////////
// UpdateNotifySignal()
//
// function: Updates the little box next to the 'notify' slider according to the
//           state of the notification event m_dsbPosNotify.hEventNotify
//
void FileInfo::UpdateNotifySignal(void)
{
    // if we notifications are enabled
    if(IsNotifyOn())
    {
        RECT    rect;
        HDC     hdc;
        HBRUSH  hB, hOB;
        HPEN    hOP;

        OutputDebugString("UpdateNotifySignal\n");

        // dc and rect for the little box
        hdc = GetDC(m_ht.hNotifyFrame);
        GetClientRect(m_ht.hNotifyFrame, &rect);

        // is the notification event signaled?
        if(WAIT_OBJECT_0 == WaitForSingleObject(m_dsbPosNotify.hEventNotify, 0))
        {
            // get time of notification
            m_dwTimeNotified = timeGetTime();
        }

        // if the event was signaled < 1/2 sec ago, make fill the box with red,
        //  otherwise with the panel color
        if((timeGetTime() - m_dwTimeNotified) < 500)
        {
            hB = CreateSolidBrush((COLORREF)0x0000ff);
        }
        else
        {
            hB = GetSysColorBrush(COLOR_3DFACE);
        }

        // fill the box
        hOP = (HPEN)SelectObject(hdc, GetStockObject(NULL_PEN));
        hOB = (HBRUSH)SelectObject(hdc, hB);
        Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);

        // clean up GDI objects
        SelectObject(hdc, hOB);
        SelectObject(hdc, hOP);
        DeleteObject(hB);
        ReleaseDC(m_ht.hNotifyFrame, hdc);
    }
}

///////////////////////////////////////////////////////////////////////////////
// OnDestroy()
//
void FileInfo::OnDestroy()
{
    SendDestroyRequest();
    return;
}

///////////////////////////////////////////////////////////////////////////////
// OnCommand()
//
//    Handles WM_COMMAND messages sent to the dialog interface.
//
BOOL FileInfo::OnCommand( WPARAM wParam, LPARAM lParam )
{
    ASSERT( NULL != m_hwndInterface );

    // These three functions break out the handling of the WM_COMMAND messages
    // that will be sent by various context menus.  There's no real difference
    // between these and other messages, but keeping them seperate emphasizes
    // where they come from and keeps our switch a bit shorter.
    if( HandleFreqContext( wParam ) || HandleVolContext( wParam )
        || HandlePanContext( wParam ))
    {
        return TRUE;
    }

    switch( LOWORD( wParam ))
    {
        case ID_BUFFERDLG_FILE_OPEN:
            // For convenience, the File|Open command is on the dialog's menu.
            // If we see it, we should reflect it to the parent for processing.
            ASSERT( NULL != m_pmwOwner );
            SendMessage( m_pmwOwner->GetHwnd(), WM_COMMAND,
                        MAKEWPARAM( IDC_FILE_OPEN, 0 ), 0L );
            break;

        case ID_BUFFERDLG_DUPLICATE:
            m_pmwOwner->DuplicateBuffer( this );
            break;

        case IDCANCEL:
            // This is what the dialog subsystem will send us when the user
            // clicks the Close button from the caption bar or the System menu
            DestroyWindow( m_hwndInterface );
            break;

            case IDC_BUFFERDLG_PLAY_BUTTON:
            // Handle the Play button depending on whether or not the buffer is
            // playing or stopped.
            if( IsPlaying())
            {
            StopBuffer();
            }
            else
            {
            PlayBuffer();
            }
            UpdatePlayButton();
            break;

        case IDC_BUFFERDLG_LOOPED_CHECK:
            // Toggle the state of the looping flag with an XOR
            m_dwInternalFlags ^= FI_INTERNALF_LOOPED;

            // Calling Play will update the looping state in DSound
            if( IsPlaying())
                PlayBuffer();
            break;

        default:
            // FALSE means we didn't want to deal with the message
            return FALSE;
    }

    // TRUE means we processed the message
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// UpdateFileName()
//
//    Updates the file name which is displayed in the dialog window caption.
//
void FileInfo::UpdateFileName( void )
{
    if( NULL != m_hwndInterface )
    {
        SendMessage( m_hwndInterface, WM_SETTEXT, 0L,
                     (LPARAM)&m_szFileName[m_nFileIndex] );
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandlePanContext()
//
//    Handle WM_COMMAND messages from the Pan context menu.  Returns TRUE if a
// message was handled.
//
BOOL FileInfo::HandlePanContext( WPARAM wParam )
{
    switch( LOWORD( wParam ))
    {
        case ID_PANCONTEXT_CENTER:
            if( m_pDSB )
            {
                m_pDSB->SetPan(0);
                UpdatePanUI( 0, FALSE );
            }
            break;
        case ID_PANCONTEXT_10DB_LEFT:
            if( m_pDSB )
            {
                m_pDSB->SetPan(-1000);
                UpdatePanUI( -1000, FALSE );
            }
            break;
        case ID_PANCONTEXT_10DB_RIGHT:
            if( m_pDSB )
            {
                m_pDSB->SetPan(1000);
                UpdatePanUI( 1000, FALSE );
            }
            break;
        case ID_PANCONTEXT_FULL_LEFT:
            if( m_pDSB )
            {
                m_pDSB->SetPan(-10000);
                UpdatePanUI( -10000, FALSE );
            }
            break;
        case ID_PANCONTEXT_FULL_RIGHT:
            if( m_pDSB )
            {
                m_pDSB->SetPan(10000);
                UpdatePanUI( 10000, FALSE );
            }
            break;

        default:
            return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// HandleVolContext()
//
//    Handle WM_COMMAND messages from the Vol context menu.  Returns TRUE if a
// message was handled.
//
BOOL FileInfo::HandleVolContext( WPARAM wParam )
{
    switch( LOWORD( wParam ))
    {
        case ID_VOLCONTEXT_0DB:
            if( m_pDSB )
            {
                m_pDSB->SetVolume(0);
                UpdateVolUI( 0, FALSE );
            }
            break;
        case ID_VOLCONTEXT_10DB:
            if( m_pDSB )
            {
                m_pDSB->SetVolume(-1000);
                UpdateVolUI( -1000, FALSE );
            }
            break;
        case ID_VOLCONTEXT_20DB:
            if( m_pDSB )
            {
                m_pDSB->SetVolume(-2000);
                UpdateVolUI( -2000, FALSE );
            }
            break;
        case ID_VOLCONTEXT_30DB:
            if( m_pDSB )
            {
                m_pDSB->SetVolume(-3000);
                UpdateVolUI( -3000, FALSE );
            }
            break;
        case ID_VOLCONTEXT_100DB:
            if( m_pDSB )
            {
                m_pDSB->SetVolume(-10000);
                UpdateVolUI( -10000, FALSE );
            }
            break;

        default:
            return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// HandleFreqContext()
//
//    Handle WM_COMMAND messages from the Freq context menu.  Returns TRUE if a
// message was handled.
//
BOOL FileInfo::HandleFreqContext( WPARAM wParam )
{
    switch( LOWORD( wParam ))
    {
        case ID_FREQCONTEXT_FILEDEFAULT:
            if( m_pDSB )
            {
                if( SUCCEEDED( m_pDSB->SetFrequency(m_pwfx->nSamplesPerSec)))
                    UpdateFreqUI( m_pwfx->nSamplesPerSec, FALSE );
            }
            break;

        case ID_FREQCONTEXT_8000HZ:
            if( m_pDSB )
            {
                if( SUCCEEDED( m_pDSB->SetFrequency(8000)))
                    UpdateFreqUI( 8000, FALSE );
            }
            break;

        case ID_FREQCONTEXT_11025HZ:
            if( m_pDSB )
            {
                if( SUCCEEDED( m_pDSB->SetFrequency(11025)))
                    UpdateFreqUI( 11025, FALSE );
            }
            break;

        case ID_FREQCONTEXT_22050HZ:
            if( m_pDSB )
            {
                if( SUCCEEDED( m_pDSB->SetFrequency(22050)))
                    UpdateFreqUI( 22050, FALSE );
            }
            break;

        case ID_FREQCONTEXT_44100HZ:
            if( m_pDSB )
            {
                if( SUCCEEDED( m_pDSB->SetFrequency(44100)))
                    UpdateFreqUI( 44100, FALSE );
            }
            break;

        default:
            return FALSE;
    }
    return TRUE;
}

////////////////////////////////////////////////////////////
BOOL IsValidHandle(HANDLE h)
{
    if((h == NULL) || (h == INVALID_HANDLE_VALUE))
        return FALSE;

    return TRUE;
}

void SafeCloseHandle(HANDLE* pH)
{
    // if handle looks valid, close it
    if((*pH != NULL) && (*pH != INVALID_HANDLE_VALUE))
    {
        CloseHandle(*pH);
    }

    *pH = INVALID_HANDLE_VALUE;
    return;
}


FILEINFO.H

#ifndef __FILEINFO_H__
#define __FILEINFO_H__

#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>

#define FREQ_SLIDER_PAGESIZE_HZ     1000    // Move 1000 Hz per page
#define FREQ_SLIDER_MAX        100000
#define FREQ_SLIDER_MIN        100

#define VOL_SLIDER_FACTOR        100    // Scaling factor
#define VOL_SLIDER_SHIFT        10000   // Offset (guarantees >0 range)
#define VOL_SLIDER_PAGE        500
#define VOL_MIN            -10000
#define VOL_MAX            0

#define PAN_SLIDER_FACTOR        100    // Scaling factor
#define PAN_SLIDER_SHIFT        10000   // Offset (guarantees >0 range)
#define PAN_SLIDER_PAGE        500
#define PAN_MIN            -10000
#define PAN_MAX            10000

#define NOTIFY_MAX                  10000

#define PROGRESS_MIN    0
#define PROGRESS_MAX    10000
#define PROGRESS_TIC    1000

// Internal class state flags
#define FI_INTERNALF_3D    0x00000001
#define FI_INTERNALF_HARDWARE0x00000002
#define FI_INTERNALF_LOOPED    0x00000004
#define FI_INTERNALF_PLAYING0x00000008
#define FI_INTERNALF_LOST    0x00000010
#define FI_INTERNALF_LOADED    0x00000020
#define FI_INTERNALF_STATIC    0x00000040
#define FI_INTERNALF_STREAMING0x00000080
#define FI_INTERNALF_USEGETPOS20x00000100
#define FI_INTERNALF_INTERFACE0x00000200
#define FI_INTERNALF_STICKY    0x00000400
#define FI_INTERNALF_GLOBAL    0x00000800
#define FI_INTERNALF_MUTEMAX0x00001000
#define FI_INTERNALF_NOTIFY0x00002000

typedef struct tag_hwtable
{
    HWND    hLoopedCheck;
    HWND    hProgressSlider, hProgressText, hProgressSpin;
    HWND    hFreqText, hFreqSlider;
    HWND    hVolText, hVolSlider;
    HWND    hPanText, hPanSlider;
    HWND    hDataFormatText;
    HWND    hPlayCursorText, hWriteCursorText;
    HWND    hBufferTypeText, hFocusModeText, hGetPosModeText;
    HWND    hNotifyText, hNotifySlider, hNotifyFrame;
    HWND    hPlayButton;

} HWNDTABLE, *PHWNDTABLE;

class FileInfo
{
friend BOOL CALLBACK FileInfoDlgProc( HWND, UINT, WPARAM, LPARAM );

// Useful protected member functions
protected:
    virtual BOOL OnInitDialog( HWND, WPARAM );
    virtual BOOL OnInitMenu( WPARAM, LPARAM );
    virtual BOOL OnCommand( WPARAM, LPARAM );
    virtual BOOL OnHScroll( WORD, LONG, HWND );
    virtual BOOL OnContextMenu( HWND, int, int );
    virtual void OnDestroy();

    virtual BOOL CreateInterface( HWND );
    virtual void UpdateFileName( void );
    void UpdatePlayButton( void );

    virtual void SetSliders( void );

    void UpdateProgressUI( DWORD );
    void UpdateVolUI( LONG, BOOL );
    void UpdatePanUI( LONG, BOOL );
    void UpdateFreqUI( DWORD, BOOL );

    void HandleFreqSliderScroll( WORD, LONG );
    void HandleVolSliderScroll( WORD, LONG );
    void HandlePanSliderScroll( WORD, LONG );
    void HandleProgressSliderScroll( WORD, LONG );
    void HandleProgressSpinScroll( WORD, LONG );
    void HandleNotifySliderScroll( WORD, LONG );
    void UpdateNotifySignal(void);

    BOOL HandleFreqContext( WPARAM );
    BOOL HandleVolContext( WPARAM );
    BOOL HandlePanContext( WPARAM );

    inline void SetInternalFlag( BOOL fSet, DWORD dwVal )
    { if( fSet ) m_dwInternalFlags |= dwVal;
    else m_dwInternalFlags &= ~dwVal; }

public:
    FileInfo( class MainWnd *pmw = NULL );
    virtual ~FileInfo();

    int LoadWave( LPSTR lpszFile, int nIndx );
    virtual int NewDirectSoundBuffer( void );
    void SetFileName( LPTSTR lpsz, int nIndx );
    void PlayBuffer( void );
    void StopBuffer( void );

    void Close( void );

    virtual void UpdateUI( void );
    virtual void Duplicate( FileInfo * );

    void CascadeWindow( void );
    void ResetCascade( void );
    void MinimizeWindow( void );
    void RestoreWindow( void );

    void SetOwner( class MainWnd *pmw )   { if( pmw ) m_pmwOwner = pmw; }

    inline void Set3D( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_3D ); }

    inline void SetNotify( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_NOTIFY ); }

    inline void SetPlaying( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_PLAYING ); }

    inline void SetSticky( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_STICKY ); }

    inline void SetGlobal( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_GLOBAL ); }

    inline void SetLooped( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_LOOPED ); }

    inline void SetUseGetPos2( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_USEGETPOS2 ); }

    inline void SetMuteAtMax( BOOL fNew )
    { SetInternalFlag( fNew, FI_INTERNALF_MUTEMAX ); }

    inline BOOL Is3D()
    { return (m_dwInternalFlags & FI_INTERNALF_3D); }

    inline BOOL IsNotifyOn()
    { return (m_dwInternalFlags & FI_INTERNALF_NOTIFY); }

    inline BOOL IsPlaying()
    { return (m_dwInternalFlags & FI_INTERNALF_PLAYING ); }

    inline BOOL IsLooped()
    { return (m_dwInternalFlags & FI_INTERNALF_LOOPED ); }

    inline BOOL IsSticky()
    { return (m_dwInternalFlags & FI_INTERNALF_STICKY ); }

    inline BOOL IsGlobal()
    { return (m_dwInternalFlags & FI_INTERNALF_GLOBAL ); }

    inline BOOL IsHardware()
    { return (m_dwInternalFlags & FI_INTERNALF_HARDWARE ); }

    inline BOOL IsUsingGetPos2()
    { return (m_dwInternalFlags & FI_INTERNALF_USEGETPOS2 ); }

    inline BOOL IsMutedAtMax()
    { return (m_dwInternalFlags & FI_INTERNALF_MUTEMAX ); }

    // Send a request to the owner MainWnd object that this object be
    // destroyed, which may involve more than a simple delete (like removal
    // from a list or something).
    void SendDestroyRequest( void );

// Member data
protected:
    LPBYTE          m_pbData;// Pointer to actual data of file.
    UINT            m_cbDataSize;// Size of data.
    LPWAVEFORMATEX  m_pwfx;// Pointer to waveformatex structure.
    DSBUFFERDESC    m_dsbd;

    DWORD        m_dwFreqSliderFactor;   // Scaling factor

    DWORD        m_dwInternalFlags;  // A bit field of flags

    HWND        m_hwndInterface;
    HWNDTABLE    m_ht;// A table of all the control HWND's

    BOOL        m_fPlayButtonSaysPlay;

    TCHAR           m_szFileName[MAX_PATH];
    int             m_nFileIndex;// Index to filename, without dir.

    class MainWnd*  m_pmwOwner;

    LPDIRECTSOUNDBUFFER    m_pDSB;     // Pointer to direct sound buffer.
    LPDIRECTSOUNDNOTIFY     m_pDSN;     // pointer to direct sound notify instance

    DSBPOSITIONNOTIFY       m_dsbPosNotify;
//    BOOL                    m_fNotifying;
    UINT                    m_dwTimeNotified;

    static int    m_xNextPos, m_yNextPos;
};

typedef FileInfo *PFILEINFO;

BOOL CALLBACK FileInfoDlgProc( HWND, UINT, WPARAM, LPARAM );

BOOL IsValidHandle(HANDLE h);
void SafeCloseHandle(HANDLE* pH);

#endif// __FILEINFO_H__


FINFO3D.CPP

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commctrl.h>
#include <dsound.h>
#include <math.h>
#include <stdio.h>

#include "wave.h"
#include "debug.h"
#include "DSShow3D.h"
#include "FInfo3D.h"
#include "lsnrinfo.h"
#include "GVars.h"

///////////////////////////////////////////////////////////////////////////////
// FileInfo3D()
//
//    Class constructor for the 3D interface/information class.
//
FileInfo3D::FileInfo3D( MainWnd * pmw ) : FileInfo( pmw )
{
    ZeroMemory( &m_ht3d, sizeof(HWNDTABLE3D));
    ZeroMemory( &m_vPos, sizeof(D3DVECTOR));
    ZeroMemory( &m_vVelo, sizeof(D3DVECTOR));
    m_flMaxDistance = 50.f;
    m_flMinDistance = 1.f;
    m_pDSB3D = NULL;

    m_dwInternalFlags |= FI_INTERNALF_3D;

    m_dwInnerAngle = FI3D_DEFAULT_INNER_ANGLE;
    m_dwOuterAngle = FI3D_DEFAULT_OUTER_ANGLE;
}

///////////////////////////////////////////////////////////////////////////////
// ~FileInfo3D()
//
//   Virtual destructor for the FileInfo3D class.  Should deallocate any space
// allocated by the constructor.
//
FileInfo3D::~FileInfo3D()
{
    // If we do an allocation of memory specific to the FileInfo3D class,
    // we should free it here.

    // Release the 3D interface
    if( m_pDSB3D != NULL )
    {
        m_pDSB3D->Release();
        m_pDSB3D = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
// NewDirectSoundBuffer()
//
//    Virtual function which handles opening a new sound buFfer by setting some
// flags and calling down to the base class handler, which actually does most
// of the work.  Upon return, this function will do a bit more work to get a 3D
// interface.
//
int FileInfo3D::NewDirectSoundBuffer()
{
    HRESULThr;

    m_dsbd.dwFlags = DSBCAPS_CTRL3D;

    if( !FileInfo::NewDirectSoundBuffer())
    {
        // Try to grab the 3D interface pointer

        if( FAILED( hr = m_pDSB->QueryInterface( IID_IDirectSound3DBuffer,
                                (void **)&m_pDSB3D )))
        {
            DPF( 0, "QI for DS3DBuffer interface: %s", TranslateDSError(hr));
            return -1;
        }
        return 0;
    }
    else
        return -1;
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL FileInfo3D::OnInitDialog( HWND hDlg, WPARAM wParam )
{
    HRESULT hr;
    TCHAR   szTS[6];

    // Grab some 3D interface HWND's and pass along to the base class
    m_ht3d.hXSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_X_SLIDER );
    m_ht3d.hYSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_Y_SLIDER );
    m_ht3d.hZSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_Z_SLIDER );
    m_ht3d.hXText = GetDlgItem( hDlg, IDC_BUFFERDLG_X_TEXT );
    m_ht3d.hYText = GetDlgItem( hDlg, IDC_BUFFERDLG_Y_TEXT );
    m_ht3d.hZText = GetDlgItem( hDlg, IDC_BUFFERDLG_Z_TEXT );

    m_ht3d.hVxSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_VX_SLIDER );
    m_ht3d.hVySlider = GetDlgItem( hDlg, IDC_BUFFERDLG_VY_SLIDER );
    m_ht3d.hVzSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_VZ_SLIDER );
    m_ht3d.hVxText = GetDlgItem( hDlg, IDC_BUFFERDLG_VX_TEXT );
    m_ht3d.hVyText = GetDlgItem( hDlg, IDC_BUFFERDLG_VY_TEXT );
    m_ht3d.hVzText = GetDlgItem( hDlg, IDC_BUFFERDLG_VZ_TEXT );

    m_ht3d.hOuterVolSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_OUTERVOL_SLIDER );
    m_ht3d.hOuterVolText = GetDlgItem( hDlg, IDC_BUFFERDLG_OUTERVOL_TEXT );

    m_ht3d.hInnerAngleEdit = GetDlgItem( hDlg, IDC_BUFFERDLG_INNERANGLE_EDIT );
    m_ht3d.hInnerAngleSpin = GetDlgItem( hDlg, IDC_BUFFERDLG_INNERANGLE_SPIN );
    m_ht3d.hOuterAngleEdit = GetDlgItem( hDlg, IDC_BUFFERDLG_OUTERANGLE_EDIT );
    m_ht3d.hOuterAngleSpin = GetDlgItem( hDlg, IDC_BUFFERDLG_OUTERANGLE_SPIN );

    m_ht3d.hDisable3D = GetDlgItem( hDlg, IDC_BUFFERDLG_DISABLE );

    m_ht3d.hMaxDistSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_MAXMUTE_SLIDER );
    m_ht3d.hMaxDistText = GetDlgItem( hDlg, IDC_BUFFERDLG_MAXMUTE_TEXT );
    m_ht3d.hMinDistSlider = GetDlgItem( hDlg, IDC_BUFFERDLG_MINDIST_SLIDER );
    m_ht3d.hMinDistText = GetDlgItem( hDlg, IDC_BUFFERDLG_MINDIST_TEXT );
    m_ht3d.hCurDistText = GetDlgItem( hDlg, IDC_BUFFERDLG_CURDIST_TEXT );

    // The tab order in the dialog must be such that the spin control is
    // immediately after the edit control it is expected to auto-buddy to.
    // That case is tested here.
    ASSERT( (HWND)SendMessage( m_ht3d.hInnerAngleSpin,
                UDM_GETBUDDY, 0, 0 ) == m_ht3d.hInnerAngleEdit );

    // If this fails, the tab order in the dialog resource is wrong (see above)
    ASSERT( (HWND)SendMessage( m_ht3d.hOuterAngleSpin,
                UDM_GETBUDDY, 0, 0 ) == m_ht3d.hOuterAngleEdit );

    SendMessage( m_ht3d.hInnerAngleSpin, UDM_SETRANGE, 0, MAKELONG( 360, 0 ));
    SendMessage( m_ht3d.hOuterAngleSpin, UDM_SETRANGE, 0, MAKELONG( 360, 0 ));

    SendMessage( m_ht3d.hInnerAngleSpin, UDM_SETPOS,
                 0, MAKELONG(m_dwInnerAngle, 0));
    wsprintf( szTS, "%u", m_dwInnerAngle );
    Edit_SetText( m_ht3d.hInnerAngleEdit, szTS );

    SendMessage( m_ht3d.hOuterAngleSpin, UDM_SETPOS,
                 0, MAKELONG(m_dwOuterAngle, 0));
    wsprintf( szTS, "%u", m_dwOuterAngle );
    Edit_SetText( m_ht3d.hOuterAngleEdit, szTS );

    if( m_pDSB3D )
    {
        if( FAILED( hr = m_pDSB3D->SetConeAngles( m_dwInnerAngle,
                                                  m_dwOuterAngle, DS3D_IMMEDIATE )))
            DPF( 0, "SetConeAngles: %s", TranslateDSError(hr));

        m_pDSB3D->GetPosition( &m_vPos );
        m_pDSB3D->GetVelocity( &m_vVelo );
        m_pDSB3D->SetMaxDistance( m_flMaxDistance, DS3D_IMMEDIATE );
        m_pDSB3D->SetMinDistance( m_flMinDistance, DS3D_IMMEDIATE );
    }

    return FileInfo::OnInitDialog( hDlg, wParam );
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL FileInfo3D::OnCommand( WPARAM wParam, LPARAM lParam )
{
    BOOL fRet = FALSE;

    if( HandleOuterVolContext( wParam )
        || HandlePositionContext( wParam )
        || HandleVelocityContext( wParam ) )
        fRet = TRUE;
    else if( HandleConeInnerEditNotify( wParam, lParam ))
        fRet = TRUE;
    else if( HandleConeOuterEditNotify( wParam, lParam ))
        fRet = TRUE;
    else if( HandleDisableNotify( wParam, lParam ))
        fRet = TRUE;
    else
        fRet = FileInfo::OnCommand( wParam, lParam );

    return fRet;
}

///////////////////////////////////////////////////////////////////////////////
// OnHScroll()
//
//
//
BOOL FileInfo3D::OnHScroll( WORD wNotification, LONG lPos, HWND hControl )
{
    if( !hControl )
        return FALSE;
    if( hControl == m_ht3d.hOuterVolSlider )
        HandleOuterVolSliderScroll( wNotification, lPos );

    else if( hControl == m_ht3d.hXSlider )
        HandleXSliderScroll( wNotification, lPos );
    else if( hControl == m_ht3d.hYSlider )
        HandleYSliderScroll( wNotification, lPos );
    else if( hControl == m_ht3d.hZSlider )
        HandleZSliderScroll( wNotification, lPos );

    else if( hControl == m_ht3d.hVxSlider )
        HandleVxSliderScroll( wNotification, lPos );
    else if( hControl == m_ht3d.hVySlider )
        HandleVySliderScroll( wNotification, lPos );
    else if( hControl == m_ht3d.hVzSlider )
        HandleVzSliderScroll( wNotification, lPos );

    else if( hControl == m_ht3d.hMaxDistSlider )
        HandleMaxDistSliderScroll( wNotification, lPos );
    else if( hControl == m_ht3d.hMinDistSlider )
        HandleMinDistSliderScroll( wNotification, lPos );

    // All messages we don't handle pass through to the base class
    else
        return FileInfo::OnHScroll( wNotification, lPos, hControl );

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleOuterVolSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hOuterVolSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume(( lPos * VOL_SLIDER_FACTOR )
                        - VOL_SLIDER_SHIFT, DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
        UpdateOuterVolUI(( lPos * VOL_SLIDER_FACTOR ) - VOL_SLIDER_SHIFT,
                                        FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleXSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hXSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vPos.x = D3DVAL(lPos * X_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetPosition( m_vPos.x, m_vPos.y,
                            m_vPos.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetPosition: %s", TranslateDSError(hr));
        UpdateXSliderUI((lPos * X_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleYSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hYSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vPos.y = D3DVAL(lPos * Y_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetPosition( m_vPos.x, m_vPos.y,
                            m_vPos.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetPosition: %s", TranslateDSError(hr));
        UpdateYSliderUI((lPos * Y_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleZSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hZSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vPos.z = D3DVAL(lPos * Z_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetPosition( m_vPos.x, m_vPos.y,
                                                m_vPos.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetPosition: %s", TranslateDSError(hr));
        UpdateZSliderUI((lPos * Z_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleVxSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hVxSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vVelo.x = D3DVAL(lPos * X_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetVelocity( m_vVelo.x, m_vVelo.y,
                            m_vVelo.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetVelocity: %s", TranslateDSError(hr));
        UpdateVxSliderUI((lPos * X_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleVySliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hVySlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vVelo.y = D3DVAL(lPos * Y_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetVelocity( m_vVelo.x, m_vVelo.y,
                            m_vVelo.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetVelocity: %s", TranslateDSError(hr));
        UpdateVySliderUI((lPos * Y_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::HandleVzSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hVzSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_vVelo.z = D3DVAL(lPos * Z_SLIDER_FACTOR) - POS_SLIDER_SHIFT;
        if( FAILED( hr = m_pDSB3D->SetVelocity( m_vVelo.x, m_vVelo.y,
                                                m_vVelo.z, DS3D_IMMEDIATE )))
            DPF( 0, "SetVelocity: %s", TranslateDSError(hr));
        UpdateVzSliderUI((lPos * Z_SLIDER_FACTOR) - POS_SLIDER_SHIFT, FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleMaxDistSliderScroll
// function:        handles WM_HSCROLL messages sent on behalf of IDC_BUFFERDLG_MAXMUTE_SLIDER
//
void FileInfo3D::HandleMaxDistSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hMaxDistSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_flMaxDistance = D3DVAL(lPos * MUTEMAX_SLIDER_FACTOR);
        if( FAILED( hr = m_pDSB3D->SetMaxDistance( m_flMaxDistance, DS3D_IMMEDIATE )))
        {
            DPF( 0, "SetPosition: %s", TranslateDSError(hr));
        }

        UpdateMuteMaxSliderUI((lPos * MUTEMAX_SLIDER_FACTOR), FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// HandleMinDistSliderScroll
// function:        handles WM_HSCROLL messages sent on behalf of IDC_BUFFERDLG_MINDIST_SLIDER
//
void FileInfo3D::HandleMinDistSliderScroll( WORD wNot, LONG lPos )
{
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
    {
        case TB_THUMBTRACK:
            break;

        case TB_ENDTRACK:
        case TB_LINEDOWN:
        case TB_LINEUP:
        case TB_PAGEDOWN:
        case TB_PAGEUP:
            lPos = SendMessage( m_ht3d.hMinDistSlider, TBM_GETPOS, 0, 0 );
            break;

        default:
            fUpdate = FALSE;
    }

    if( fUpdate && NULL != m_pDSB3D )
    {
        m_flMinDistance = D3DVAL(lPos * MINDIST_SLIDER_FACTOR);
        if( FAILED( hr = m_pDSB3D->SetMinDistance( m_flMinDistance, DS3D_IMMEDIATE )))
        {
            DPF( 0, "SetPosition: %s", TranslateDSError(hr));
        }

        UpdateMinDistSliderUI((lPos * MINDIST_SLIDER_FACTOR), FALSE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// UpdateOuterVolUI()
//
//
//
//
//
void FileInfo3D::UpdateOuterVolUI( LONG lForceVol, BOOL fFromBuffer )
{
    HRESULT hr;
    LONG    lVol;
    TCHAR   szText[16];

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            if( FAILED( hr = m_pDSB3D->GetConeOutsideVolume( &lVol )))
            DPF( 0, "GetConeOutsideVolume: %s", TranslateDSError(hr));
        }
        else
            lVol = 0;
    }
    else
        lVol = lForceVol;

    SendMessage( m_ht3d.hOuterVolSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lVol + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );

    // Print volume in decibels
    wsprintf( szText, "%i dB", lVol / 100 );

    Static_SetText( m_ht3d.hOuterVolText, szText );
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL FileInfo3D::OnContextMenu( HWND hDlg, int x, int y )
{
    HMENU   hm, hSub;
    int     nSubMenu = -1;
    POINT   pt = { x, y };
    RECT    rectWind1, rectWind2, rectWind3;

    // Set the sub-menu to the Outer Volume context menu if we hit the proper
    // slider or text control
    GetWindowRect( m_ht3d.hOuterVolSlider, &rectWind1 );
    GetWindowRect( m_ht3d.hOuterVolText, &rectWind2 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt ))
        nSubMenu = 3;

    // Set the POS_CONTEXT submenu if there's a click on the position sliders
    // or text controls
    GetWindowRect( m_ht3d.hXSlider, &rectWind1 );
    GetWindowRect( m_ht3d.hYSlider, &rectWind2 );
    GetWindowRect( m_ht3d.hZSlider, &rectWind3 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt )
        || PtInRect( &rectWind3, pt ))
        nSubMenu = 4;

    GetWindowRect( m_ht3d.hXText, &rectWind1 );
    GetWindowRect( m_ht3d.hYText, &rectWind2 );
    GetWindowRect( m_ht3d.hZText, &rectWind3 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt )
        || PtInRect( &rectWind3, pt ))
        nSubMenu = 4;

    // Set the VELO_CONTEXT submenu if there's a click on the position sliders
    // or text controls
    GetWindowRect( m_ht3d.hVxSlider, &rectWind1 );
    GetWindowRect( m_ht3d.hVySlider, &rectWind2 );
    GetWindowRect( m_ht3d.hVzSlider, &rectWind3 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt )
        || PtInRect( &rectWind3, pt ))
        nSubMenu = 5;

    GetWindowRect( m_ht3d.hVxText, &rectWind1 );
    GetWindowRect( m_ht3d.hVyText, &rectWind2 );
    GetWindowRect( m_ht3d.hVzText, &rectWind3 );

    if( PtInRect( &rectWind1, pt ) || PtInRect( &rectWind2, pt )
        || PtInRect( &rectWind3, pt ))
        nSubMenu = 5;

    // We didn't detect any "interesting" hotspots, so pass along to base class
    if( nSubMenu < 0 )
        return FileInfo::OnContextMenu( hDlg, x, y );

    // If we make it here, we're gonna popup a context menu of some sort

    // Attempt to load our menu.  If we fail, we still handled the message
    // so return TRUE
    if(( hm = LoadMenu( ghInst, MAKEINTRESOURCE(IDM_POPUPS))) == NULL )
        return TRUE;

    hSub = GetSubMenu( hm, nSubMenu );
    TrackPopupMenu( hSub, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                    pt.x, pt.y, 0, m_hwndInterface, NULL );

    DestroyMenu( hm );

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::OnDestroy()
{
    FileInfo::OnDestroy();
    return;
}

void FileInfo3D::Duplicate( FileInfo *pfiSource )
{
    HRESULThr;

    FileInfo::Duplicate( pfiSource );

    if( FAILED( hr = m_pDSB->QueryInterface( IID_IDirectSound3DBuffer,
                                             (void **)&m_pDSB3D )))
    {
        DPF( 0, "QI for DS3DBuffer interface: %s", TranslateDSError(hr));
    }
    else
    {
        // Update the UI from the interface we just got
        UpdateOuterVolUI( 0, TRUE );
        UpdateXSliderUI( 0, TRUE );
        UpdateYSliderUI( 0, TRUE );
        UpdateZSliderUI( 0, TRUE );
        UpdateVxSliderUI( 0, TRUE );
        UpdateVySliderUI( 0, TRUE );
        UpdateVzSliderUI( 0, TRUE );

        m_pDSB3D->GetConeAngles( &m_dwInnerAngle, &m_dwOuterAngle );

// update the UI.
UpdateMinDistSliderUI( 0, TRUE );
UpdateMuteMaxSliderUI(0, TRUE);

        DWORDdwMode;
        TCHARszTS[6];

        SendMessage( m_ht3d.hInnerAngleSpin, UDM_SETPOS,
                     0, MAKELONG(m_dwInnerAngle, 0));
        wsprintf( szTS, "%u", m_dwInnerAngle );
        Edit_SetText( m_ht3d.hInnerAngleEdit, szTS );

        SendMessage( m_ht3d.hOuterAngleSpin, UDM_SETPOS,
                     0, MAKELONG(m_dwOuterAngle, 0));
        wsprintf( szTS, "%u", m_dwOuterAngle );
        Edit_SetText( m_ht3d.hOuterAngleEdit, szTS );

        m_pDSB3D->GetMode( &dwMode );
        Button_SetCheck( m_ht3d.hDisable3D, dwMode == DS3DMODE_DISABLE );
    }
}

///////////////////////////////////////////////////////////////////////////////
// CreateInterface()
//
//    Very similar to the interface creation code for 2D objects, but creates a
// different dialog.  (Eventually may do more stuff related to D3D display).
//
BOOL FileInfo3D::CreateInterface( HWND hwndOwner )
{
    m_hwndInterface = CreateDialogParam( ghInst, MAKEINTRESOURCE(IDD_BUFFER3D),
                                         hwndOwner, (DLGPROC)FileInfo3DDlgProc,
                                         (LPARAM)this );

    if( NULL == m_hwndInterface )
        goto FICI_Fail;
    else
        UpdateFileName();

    CascadeWindow();
    ShowWindow( m_hwndInterface, SW_SHOW );

    // This flag tells us an interface window was successfully created
    m_dwInternalFlags |= FI_INTERNALF_INTERFACE;
    return TRUE;

FICI_Fail:
    if( m_hwndInterface )
    {
        DestroyWindow( m_hwndInterface );
        m_hwndInterface = NULL;
    }
    // Clear the flag that says we have a good interface window created
    m_dwInternalFlags &= ~FI_INTERNALF_INTERFACE;
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::SetSliders( void )
{
    // Do 3D controls slider initialization

    // Intentionally set the range backwards because a large number means
    // a smaller value
    SendMessage( m_ht3d.hOuterVolSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(VOL_MIN + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );
    SendMessage( m_ht3d.hOuterVolSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(VOL_MAX + VOL_SLIDER_SHIFT) / VOL_SLIDER_FACTOR );
    SendMessage( m_ht3d.hOuterVolSlider, TBM_SETPAGESIZE, 0,
                 VOL_SLIDER_PAGE / VOL_SLIDER_FACTOR );
    // NOTE: No TICs on the cone volume slider

    // position sliders
    SendMessage( m_ht3d.hXSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    SendMessage( m_ht3d.hXSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    SendMessage( m_ht3d.hXSlider, TBM_SETPAGESIZE, 0, X_SLIDER_FACTOR );

    SendMessage( m_ht3d.hYSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    SendMessage( m_ht3d.hYSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    SendMessage( m_ht3d.hYSlider, TBM_SETPAGESIZE, 0, Y_SLIDER_FACTOR );

    SendMessage( m_ht3d.hZSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    SendMessage( m_ht3d.hZSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    SendMessage( m_ht3d.hZSlider, TBM_SETPAGESIZE, 0, Z_SLIDER_FACTOR );

    // velocity sliders
    SendMessage( m_ht3d.hVxSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVxSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVxSlider, TBM_SETPAGESIZE, 0, X_SLIDER_FACTOR );

    SendMessage( m_ht3d.hVySlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVySlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVySlider, TBM_SETPAGESIZE, 0, Y_SLIDER_FACTOR );

    SendMessage( m_ht3d.hVzSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(POS_SLIDER_MIN + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVzSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(POS_SLIDER_MAX + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    SendMessage( m_ht3d.hVzSlider, TBM_SETPAGESIZE, 0, Z_SLIDER_FACTOR );

    // max distance slider
    SendMessage( m_ht3d.hMaxDistSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(MUTEMAX_SLIDER_MIN) / MUTEMAX_SLIDER_FACTOR );
    SendMessage( m_ht3d.hMaxDistSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(MUTEMAX_SLIDER_MAX) / MUTEMAX_SLIDER_FACTOR );
    SendMessage( m_ht3d.hMaxDistSlider, TBM_SETPAGESIZE, 0, MUTEMAX_SLIDER_FACTOR );

    // Min distance slider
    SendMessage( m_ht3d.hMinDistSlider, TBM_SETRANGEMIN, FALSE,
                 (LPARAM)(MINDIST_SLIDER_MIN) / MINDIST_SLIDER_FACTOR );
    SendMessage( m_ht3d.hMinDistSlider, TBM_SETRANGEMAX, FALSE,
                 (LPARAM)(MINDIST_SLIDER_MAX) / MINDIST_SLIDER_FACTOR );
    SendMessage( m_ht3d.hMinDistSlider, TBM_SETPAGESIZE, 0, MINDIST_SLIDER_FACTOR );

//    SendMessage( m_ht3d.hOuterVolSlider, TBM_SETPAGESIZE, 0,
//            VOL_SLIDER_PAGE / VOL_SLIDER_FACTOR );
    // NOTE: No TICs on the position sliders

    // Update the display from the buffer's current settings
    UpdateOuterVolUI( 0, TRUE );
    UpdateXSliderUI( 0, TRUE );
    UpdateYSliderUI( 0, TRUE );
    UpdateZSliderUI( 0, TRUE );
    UpdateVxSliderUI( 0, TRUE );
    UpdateVySliderUI( 0, TRUE );
    UpdateVzSliderUI( 0, TRUE );
    UpdateMuteMaxSliderUI( 0, TRUE );
    UpdateMinDistSliderUI( 0, TRUE );

    FileInfo::SetSliders();
}

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FileInfo3D::UpdateUI( void )
{
    UpdatePosAndVelo();
    UpdateCurrentDistUI();
    UpdateXSliderUI( 0, TRUE );
    UpdateYSliderUI( 0, TRUE );
    UpdateZSliderUI( 0, TRUE );
    FileInfo::UpdateUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateFileName()
//
//    Updates the file name which is displayed in the dialog window caption.
//
void FileInfo3D::UpdateFileName( void )
{
    TCHAR   szTitle[MAX_PATH + 5];

    if( NULL != m_hwndInterface )
    {
        lstrcpy( szTitle, &m_szFileName[m_nFileIndex] );
        lstrcat( szTitle, TEXT(" (3D)"));
        SendMessage( m_hwndInterface, WM_SETTEXT, 0L, (LPARAM)szTitle );
    }
}

///////////////////////////////////////////////////////////////////////////////
// FileInfo3DDlgProc()
//
//
//
BOOL CALLBACK FileInfo3DDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    FileInfo3D *pfi3d;

    switch( message )
    {
        // The first step is to stash our class object pointer in the user data
        // and Initialize all our controls and internal data members.
        case WM_INITDIALOG:
            ASSERT( NULL != lParam );
            pfi3d = (FileInfo3D *)lParam;
            SetWindowLong( hDlg, DWL_USER, (LONG)pfi3d );

            if( !pfi3d->OnInitDialog( hDlg, wParam ))
            {
            DestroyWindow( hDlg );
            }
            return TRUE;

        case WM_COMMAND:
            pfi3d = (FileInfo3D*)GetWindowLong( hDlg, DWL_USER );

// It's possible to get notification messages from child controls
            // before we have been given a WM_INITDIALOG message.  This is not
            // a good thing for dereferencing the pointer because we won't have
            // class info. Specifically, the spin controls in our dialog force
            // the edit control to send EN_CHANGE and EN_UPDATE messages when
            // they set the text of the edit control they're tied to.
            if( NULL == pfi3d || NULL == pfi3d->m_hwndInterface )
            return FALSE;
            return !pfi3d->OnCommand( wParam, lParam );

        // Handle this to deal with right-clicks on our controls -- we have a
        // bunch of different context menus that we can popup
        case WM_CONTEXTMENU:
            pfi3d = (FileInfo3D*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi3d );
            return pfi3d->OnContextMenu( hDlg, LOWORD(lParam), HIWORD(lParam));

        // Trackbar slider notifications come through here
        case WM_HSCROLL:
            pfi3d = (FileInfo3D*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi3d );
            return pfi3d->OnHScroll( LOWORD(wParam), (LONG)HIWORD(wParam), (HWND)lParam );

        case WM_DESTROY:
            pfi3d = (FileInfo3D*)GetWindowLong( hDlg, DWL_USER );
            ASSERT( NULL != pfi3d );
            pfi3d->OnDestroy();
            return TRUE;

        default:
            return FileInfoDlgProc( hDlg, message, wParam, lParam );
    }

    ASSERT( FALSE );
}

///////////////////////////////////////////////////////////////////////////////
// HandleOuterVolContext()
//
//    Pre-process the parameters to WM_COMMAND messages we get and sees if we
// knows how to handle any of them.  We know how to handle any message that
// comes from the Outer Volume controls' context menu.
//
BOOL FileInfo3D::HandleOuterVolContext( WPARAM wParam )
{
    HRESULT hr;

    switch( wParam )
    {
        case ID_OUTERVOLCONTEXT_0DB:
            if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume( 0,
                                    DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
            UpdateOuterVolUI( 0, TRUE );
            break;

        case ID_OUTERVOLCONTEXT_10DB:
            if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume( -1000,
                                    DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
            UpdateOuterVolUI( -1000, TRUE );
            break;

        case ID_OUTERVOLCONTEXT_20DB:
            if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume( -2000,
                                    DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
            UpdateOuterVolUI( -2000, TRUE );
            break;

        case ID_OUTERVOLCONTEXT_30DB:
            if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume( -3000,
                                    DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
            UpdateOuterVolUI( -3000, TRUE );
            break;

        case ID_OUTERVOLCONTEXT_100DB:
            if( FAILED( hr = m_pDSB3D->SetConeOutsideVolume( -10000,
                                    DS3D_IMMEDIATE )))
            DPF( 0, "SetConeOutsideVolume: %s", TranslateDSError(hr));
            UpdateOuterVolUI( -10000, TRUE );
            break;

        default:
            return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// HandlePositionContext()
//
//    Pre-process the parameters to WM_COMMAND messages we get and sees if we
// knows how to handle any of them.  We know how to handle any message that
// comes from the Position controls' context menu.
//
BOOL FileInfo3D::HandlePositionContext( WPARAM wParam )
{
    D3DVECTORd3dListener;

    switch( wParam )
    {
        case ID_POSCONTEXT_ORIGIN:
            ASSERT( NULL != m_pDSB3D );
            m_pDSB3D->SetPosition( D3DVAL(0), D3DVAL(0), D3DVAL(0), DS3D_IMMEDIATE );
            UpdateXSliderUI( 0, TRUE );
            UpdateYSliderUI( 0, TRUE );
            UpdateZSliderUI( 0, TRUE );
            break;

        case ID_POSCONTEXT_LISTENERPOSITION:
            ASSERT( NULL != m_pDSB3D );
            ASSERT( NULL != gp3DListener );
            gp3DListener->GetPosition( &d3dListener );
            m_pDSB3D->SetPosition( d3dListener.x, d3dListener.y,
                        d3dListener.z, DS3D_IMMEDIATE );
            UpdateXSliderUI( (DWORD)d3dListener.x, TRUE );
            UpdateYSliderUI( (DWORD)d3dListener.y, TRUE );
            UpdateZSliderUI( (DWORD)d3dListener.z, TRUE );
            break;

        default:
            return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// HandleVelocityContext()
//
//    Pre-process the parameters to WM_COMMAND messages we get and sees if we
// knows how to handle any of them.  We know how to handle any message that
// comes from the Position controls' context menu.
//
BOOL FileInfo3D::HandleVelocityContext( WPARAM wParam )
{
    switch( wParam )
    {
        case ID_VELOCONTEXT_ORIGIN:
            ASSERT( NULL != m_pDSB3D );
            m_pDSB3D->SetVelocity( D3DVAL(0), D3DVAL(0), D3DVAL(0), DS3D_IMMEDIATE );
            UpdateVxSliderUI( 0, TRUE );
            UpdateVySliderUI( 0, TRUE );
            UpdateVzSliderUI( 0, TRUE );
            m_vVelo.x = m_vVelo.y = m_vVelo.z = 0.f;
            break;

        default:
            return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// UpdateXSliderUI()
//
//    Update anything that displays the position of the X slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateXSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetPosition( &m_vPos );
            lPos = (LONG)m_vPos.x;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hXSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lPos + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hXText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateYSliderUI()
//
//    Update anything that displays the position of the Y slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateYSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetPosition( &m_vPos );
            lPos = (LONG)m_vPos.y;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hYSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lPos + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hYText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateZSliderUI()
//
//    Update anything that displays the position of the Z slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateZSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHARszText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetPosition( &m_vPos );
            lPos = (LONG)m_vPos.z;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hZSlider, TBM_SETPOS, (WPARAM)TRUE,
            (LPARAM)(lPos + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hZText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateVxSliderUI()
//
//    Update anything that displays the position of the Vx slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateVxSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetVelocity( &m_vVelo );
            lPos = (LONG)m_vVelo.x;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hVxSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lPos + POS_SLIDER_SHIFT) / X_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hVxText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateVySliderUI()
//
//    Update anything that displays the position of the Vy slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateVySliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetVelocity( &m_vVelo );
            lPos = (LONG)m_vVelo.y;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hVySlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)(lPos + POS_SLIDER_SHIFT) / Y_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hVyText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateVzSliderUI()
//
//    Update anything that displays the position of the Vz slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void FileInfo3D::UpdateVzSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHARszText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetVelocity( &m_vVelo );
            lPos = (LONG)m_vVelo.z;
        }
        else
            lPos = 0;
    }
    else
        lPos = lForcePos;

    SendMessage( m_ht3d.hVzSlider, TBM_SETPOS, (WPARAM)TRUE,
            (LPARAM)(lPos + POS_SLIDER_SHIFT) / Z_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hVzText, szText );

    // anything that updates position will change the current distance
    UpdateCurrentDistUI();
}

///////////////////////////////////////////////////////////////////////////////
// UpdateMuteMaxSliderUI()
//
// function:    Update anything that displays the position of the MaxDistance
//              slider in some manner to reflect the new position, either from
//              the buffer or from the parameter.
//
void FileInfo3D::UpdateMuteMaxSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHARszText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetMaxDistance( &m_flMaxDistance );
            lPos = (LONG)m_flMaxDistance;
        }
        else
        {
            lPos = 0;
        }
    }
    else
    {
        lPos = lForcePos;
    }

    SendMessage( m_ht3d.hMaxDistSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)lPos / MUTEMAX_SLIDER_FACTOR );

    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hMaxDistText, szText );
}

///////////////////////////////////////////////////////////////////////////////
// UpdateMinDistSliderUI()
//
// function:    Update anything that displays the position of the MinDistance
//              slider in some manner to reflect the new position, either from
//              the buffer or from the parameter.
//
void FileInfo3D::UpdateMinDistSliderUI( LONG lForcePos, BOOL fFromBuffer )
{
    TCHARszText[8];
    LONG    lPos;

    if( fFromBuffer )
    {
        if( NULL != m_pDSB3D )
        {
            m_pDSB3D->GetMinDistance( &m_flMinDistance );
            lPos = (LONG)m_flMinDistance;
        }
        else
        {
            lPos = 0;
        }
    }
    else
    {
        lPos = lForcePos;
    }

    SendMessage( m_ht3d.hMinDistSlider, TBM_SETPOS, (WPARAM)TRUE,
                 (LPARAM)lPos / MINDIST_SLIDER_FACTOR );

    wsprintf( szText, "%i", lPos );
    Static_SetText( m_ht3d.hMinDistText, szText );
}

///////////////////////////////////////////////////////////////////////////////
// UpdateCurrentDistUI()
//
// function:    Update Current Distance text box in response to change in either
//              buffer or listener position
//
void FileInfo3D::UpdateCurrentDistUI( void )
{
    TCHAR    szText[24];
    D3DVECTOR   d3dListPos;
    D3DVALUE    dx, dy, dz;
    doubledist;
    HRESULT     hr;
    // get current listener position
    if(SUCCEEDED (hr = gp3DListener->GetPosition( &d3dListPos )))
    {
        // find distance from listener to buffer
        dx = d3dListPos.x - m_vPos.x;
        dy = d3dListPos.y - m_vPos.y;
        dz = d3dListPos.z - m_vPos.z;

        dist = sqrt((dx * dx) + (dy * dy) + (dz * dz));

        // update the text box
        sprintf( szText, "%.2f", dist );
        Static_SetText( m_ht3d.hCurDistText, szText );
    }
}

///////////////////////////////////////////////////////////////////////////////
// UpdatePosAndVelo()
//
// function:    Update position as a function of velocity text box in response
//              to timer click
//
UpdatePosAndVelo();
void FileInfo3D::UpdatePosAndVelo( void )
{
    float flTimerPeriod = 100.f / (float)TIMERPERIOD;

    m_vPos.x += m_vVelo.x * flTimerPeriod;
    m_vPos.y += m_vVelo.y * flTimerPeriod;
    m_vPos.z += m_vVelo.z * flTimerPeriod;

    if(m_vPos.x > POS_SLIDER_MAX)
    {
        m_vPos.x = (float)POS_SLIDER_MAX;
        m_vVelo.x = -m_vVelo.x;
    }
    if(m_vPos.x < POS_SLIDER_MIN)
    {
        m_vPos.x = (float)POS_SLIDER_MIN;
        m_vVelo.x = -m_vVelo.x;
    }
    if(m_vPos.y > POS_SLIDER_MAX)
    {
        m_vPos.y = (float)POS_SLIDER_MAX;
        m_vVelo.y = -m_vVelo.y;
    }
    if(m_vPos.y < POS_SLIDER_MIN)
    {
        m_vPos.y = (float)POS_SLIDER_MIN;
        m_vVelo.y = -m_vVelo.y;
    }
    if(m_vPos.z > POS_SLIDER_MAX)
    {
        m_vPos.z = (float)POS_SLIDER_MAX;
        m_vVelo.z = -m_vVelo.z;
    }
    if(m_vPos.z < POS_SLIDER_MIN)
    {
        m_vPos.z = (float)POS_SLIDER_MIN;
        m_vVelo.z = -m_vVelo.z;
    }

    if( NULL != m_pDSB3D )
    {
        m_pDSB3D->SetPosition( m_vPos.x, m_vPos.y, m_vPos.z, DS3D_IMMEDIATE );
        m_pDSB3D->SetVelocity( m_vVelo.x, m_vVelo.y, m_vVelo.z, DS3D_IMMEDIATE );
        UpdateVxSliderUI(0, TRUE);
        UpdateVySliderUI(0, TRUE);
        UpdateVzSliderUI(0, TRUE);
    }
}

///////////////////////////////////////////////////////////////////////////////
BOOL FileInfo3D::HandleConeInnerEditNotify( WPARAM wParam, LPARAM lParam )
{
    HRESULT hr;
    BOOL    fRet = FALSE;
    LONG    lValue;
    TCHAR   szTS[6];

    if( HIWORD( wParam ) == EN_CHANGE )
    {
        if( (HWND)lParam == m_ht3d.hInnerAngleEdit )
        {
            Edit_GetText( m_ht3d.hInnerAngleEdit, szTS, sizeof(szTS));
            lValue = atol(szTS);
            if( lValue < 0 )
            {
                Edit_SetText( m_ht3d.hInnerAngleEdit, "0" );
                return TRUE;
            }
            else
                m_dwInnerAngle = (DWORD)lValue;

            // Make sure we haven't incremented the inner cone larger than
            // the outer cone
            if( m_dwInnerAngle > m_dwOuterAngle )
            {
                m_dwOuterAngle = m_dwInnerAngle;
                SendMessage( m_ht3d.hOuterAngleSpin, UDM_SETPOS,
                            0, MAKELONG(m_dwOuterAngle, 0));
                wsprintf( szTS, "%u", m_dwOuterAngle );
                Edit_SetText( m_ht3d.hOuterAngleEdit, szTS );
            }
            if( m_pDSB3D )
                if( FAILED( hr = m_pDSB3D->SetConeAngles( m_dwInnerAngle,
                            m_dwOuterAngle, DS3D_IMMEDIATE )))
                    DPF( 0, "SetConeAngles: %s", TranslateDSError(hr));
            fRet = TRUE;
        }
    }

    return fRet;
}

BOOL FileInfo3D::HandleConeOuterEditNotify( WPARAM wParam, LPARAM lParam )
{
    HRESULT hr;
    BOOL    fRet = FALSE;
    LONG    lValue;
    TCHAR   szTS[6];

    if( HIWORD( wParam ) == EN_CHANGE )
    {
        if( (HWND)lParam == m_ht3d.hOuterAngleEdit )
        {
            Edit_GetText( m_ht3d.hOuterAngleEdit, szTS, sizeof(szTS));
            lValue = atol(szTS);
            if( lValue < 0 )
            {
                Edit_SetText( m_ht3d.hOuterAngleEdit, "0" );
                return TRUE;
            }
            else
                m_dwOuterAngle = (DWORD)lValue;

            // Make sure we haven't decremented the outer cone smaller than
            // the inner cone
            if( m_dwInnerAngle > m_dwOuterAngle )
            {
                m_dwInnerAngle = m_dwOuterAngle;
                SendMessage( m_ht3d.hInnerAngleSpin, UDM_SETPOS,
                            0, MAKELONG(m_dwInnerAngle, 0));
                wsprintf( szTS, "%u", m_dwInnerAngle );
                Edit_SetText( m_ht3d.hInnerAngleEdit, szTS );
            }
            if( m_pDSB3D )
                if( FAILED( hr = m_pDSB3D->SetConeAngles( m_dwInnerAngle,
                            m_dwOuterAngle, DS3D_IMMEDIATE )))
                    DPF( 0, "SetConeAngles: %s", TranslateDSError(hr));
            fRet = TRUE;
        }
    }

    return fRet;
}

BOOL FileInfo3D::HandleDisableNotify( WPARAM wParam, LPARAM lParam )
{
    HRESULT hr;
    BOOL    fRet = FALSE;

    if( (HWND)lParam == m_ht3d.hDisable3D )
    {
        if( Button_GetCheck( m_ht3d.hDisable3D ))
        {
            if( m_pDSB3D ) {
            if( FAILED( hr = m_pDSB3D->SetMode( DS3DMODE_DISABLE, 0 )))
                DPF( 0, "SetMode: %s", TranslateDSError(hr));
            fRet = TRUE;
            }
        }
        else
        {
            if( m_pDSB3D ) {
            if( FAILED( hr = m_pDSB3D->SetMode( DS3DMODE_NORMAL, 0 )))
                DPF( 0, "SetMode: %s", TranslateDSError(hr));
            fRet = TRUE;
            }
        }
    }

    return fRet;
}


FINFO3D.H

#ifndef __FILEINFO3D_H__
#define __FILEINFO3D_H__

#include "FileInfo.h"

#define CONEVOL_MIN-10000
#define CONEVOL_MAX0
#define CONEVOL_SLIDER_FACTOR100
#define CONEVOL_SLIDER_SHIFT10000
#define CONEVOL_SLIDER_PAGE500

#define X_SLIDER_FACTOR1
#define Y_SLIDER_FACTOR1
#define Z_SLIDER_FACTOR1
#define MUTEMAX_SLIDER_FACTOR1
#define MINDIST_SLIDER_FACTOR1

#define POS_SLIDER_MIN-50// Centimeters
#define POS_SLIDER_MAX50// Centimeters
#define POS_SLIDER_SHIFT50// Shift so min is 0

#define MUTEMAX_SLIDER_MIN0// Centimeters
#define MUTEMAX_SLIDER_MAX100// Centimeters
#define MINDIST_SLIDER_MIN0// Centimeters
#define MINDIST_SLIDER_MAX100// Centimeters

#define FI3D_DEFAULT_INNER_ANGLE    90
#define FI3D_DEFAULT_OUTER_ANGLE    120

typedef struct tag_ht3d
{
    HWND    hXSlider, hYSlider, hZSlider;
    HWND    hVxSlider, hVySlider, hVzSlider;
    HWND    hXText, hYText, hZText;
    HWND    hVxText, hVyText, hVzText;
    HWND    hOuterVolText, hOuterVolSlider;
    HWND    hInnerAngleEdit, hInnerAngleSpin;
    HWND    hOuterAngleEdit, hOuterAngleSpin;
    HWND    hMaxDistSlider, hMaxDistText, hMinDistSlider, hMinDistText, hCurDistText;
    HWND    hDisable3D;
} HWNDTABLE3D;

class FileInfo3D : public FileInfo
{
friend BOOL CALLBACK FileInfo3DDlgProc( HWND, UINT, WPARAM, LPARAM );

public:
    FileInfo3D( MainWnd *pmw = NULL );
    virtual ~FileInfo3D();

    virtual int NewDirectSoundBuffer( void );

    virtual void Duplicate( FileInfo * );

    virtual void UpdateUI( void );
    virtual void UpdateFileName( void );

// Useful protected member functions
protected:
    virtual BOOL OnInitDialog( HWND, WPARAM );
    virtual BOOL OnCommand( WPARAM, LPARAM );
    virtual BOOL OnHScroll( WORD, LONG, HWND );
    virtual BOOL OnContextMenu( HWND, int, int );
    virtual void OnDestroy();

    virtual BOOL CreateInterface( HWND );
    virtual void SetSliders( void );

    void HandleOuterVolSliderScroll( WORD, LONG );
    void HandleXSliderScroll( WORD, LONG );
    void HandleYSliderScroll( WORD, LONG );
    void HandleZSliderScroll( WORD, LONG );
    void HandleVxSliderScroll( WORD, LONG );
    void HandleVySliderScroll( WORD, LONG );
    void HandleVzSliderScroll( WORD, LONG );
    void HandleMaxDistSliderScroll( WORD, LONG );
    void HandleMinDistSliderScroll( WORD, LONG );

    BOOL HandleConeInnerEditNotify( WPARAM, LPARAM );
    BOOL HandleConeOuterEditNotify( WPARAM, LPARAM );

    BOOL HandleDisableNotify( WPARAM, LPARAM );

    BOOL HandleOuterVolContext( WPARAM );
    BOOL HandlePositionContext( WPARAM );
    BOOL HandleVelocityContext( WPARAM );

    void UpdateOuterVolUI( LONG, BOOL );
    void UpdateXSliderUI( LONG, BOOL );
    void UpdateYSliderUI( LONG, BOOL );
    void UpdateZSliderUI( LONG, BOOL );
    void UpdateVxSliderUI( LONG, BOOL );
    void UpdateVySliderUI( LONG, BOOL );
    void UpdateVzSliderUI( LONG, BOOL );
    void UpdateMuteMaxSliderUI( LONG, BOOL );
    void UpdateMinDistSliderUI( LONG, BOOL );
public:
    void UpdateCurrentDistUI( void );
    void UpdatePosAndVelo( void );

protected:
    HWNDTABLE3D    m_ht3d;
    D3DVECTOR    m_vPos;
    D3DVECTOR    m_vVelo;
    D3DVALUE        m_flMaxDistance;
    D3DVALUE        m_flMinDistance;
    DWORD        m_dwInnerAngle, m_dwOuterAngle;

    LPDIRECTSOUND3DBUFFER   m_pDSB3D;// DirectSound 3D buffer interface
};

typedef FileInfo3D *PFILEINFO3D;

BOOL CALLBACK FileInfo3DDlgProc( HWND, UINT, WPARAM, LPARAM );

#endif


GVARS.H

#ifndef __GVARS_H__
#define __GVARS_H__

class FileInfo;
class MainWnd;
class FileInfo3D;
class ListenerInfo;

#include <dsound.h>
#include "DSShow3d.h"
#include "MainWnd.h"

#ifdef __cplusplus
extern "C" {
#endif

// Make sure to change this if you add format code entries to fdFormats[]
#define NUM_FORMATENTRIES   16

#ifdef INIT_GVARS

TCHAR   gszAppName[]        = "DSShow3D";
TCHAR   gszAppCaption[]     = "DirectSound 3D Mixing Test";
TCHAR   gszAppWndClass[]    = "DSShow3D_MainApp_WndClass";

FORMATDATA   fdFormats[] = { { 8108, ID_OPTIONS_FORMAT_8M8, TRUE },
                { 8116, ID_OPTIONS_FORMAT_8M16, TRUE },
                { 8208, ID_OPTIONS_FORMAT_8S8, TRUE },
                { 8216, ID_OPTIONS_FORMAT_8S16, TRUE },
                { 11108, ID_OPTIONS_FORMAT_11M8, TRUE },
                { 11116, ID_OPTIONS_FORMAT_11M16, TRUE },
                { 11208, ID_OPTIONS_FORMAT_11S8, TRUE },
                { 11216, ID_OPTIONS_FORMAT_11S16, TRUE },
                { 22108, ID_OPTIONS_FORMAT_22M8, TRUE },
                { 22116, ID_OPTIONS_FORMAT_22M16, TRUE },
                { 22208, ID_OPTIONS_FORMAT_22S8, TRUE },
                { 22216, ID_OPTIONS_FORMAT_22S16, TRUE },
                { 44108, ID_OPTIONS_FORMAT_44M8, TRUE },
                { 44116, ID_OPTIONS_FORMAT_44M16, TRUE },
                { 44208, ID_OPTIONS_FORMAT_44S8, TRUE },
                { 44216, ID_OPTIONS_FORMAT_44S16, TRUE } };

DWORD aFormatOrder[] = { 44216, 44116, 44208, 44108, 22216, 22116, 22208, 22108,
                11216, 11116, 11208, 11108, 8216, 8116, 8208, 8108 };

HINSTANCE   ghInst;
HWND        hWndMain        = NULL;
HWND        ghDlgActive     = NULL;
DWORD       gdwTimer        = 0;        // Timer handle.
DWORD       gcbMaxWaveFormatSize= 0;
DWORD       gdwOutputFormat = 0;
BOOL        gfCOMInitialized    = FALSE;
REGSETTINGS grs;

PWAVEFORMATEX   gpwfxFormat = NULL;
ListenerInfo *  gpListenerInfo = NULL;
HWND        ghwndListener = NULL;

LPDIRECTSOUND       gpds = NULL;
LPDIRECTSOUNDBUFFER gpdsbPrimary = NULL;
LPDIRECTSOUND3DLISTENER  gp3DListener = NULL;

MainWnd         AppWnd;

#else   // INIT_GVARS

extern TCHAR    gszAppName[];
extern TCHAR    gszAppCaption[];
extern TCHAR    gszAppWndClass[];

extern FORMATDATA   fdFormats[];
extern DWORD        aFormatOrder[];

extern HINSTANCE    ghInst;
extern HWND     hWndMain;
extern HWND     ghDlgActive;
extern DWORD        gdwTimer;           // Timer handle.
extern DWORD        gcbMaxWaveFormatSize;
extern DWORD        gdwOutputFormat;
extern BOOL     gfCOMInitialized;
extern REGSETTINGS  grs;

extern PWAVEFORMATEX    gpwfxFormat;
extern ListenerInfo *   gpListenerInfo;
extern HWND     ghwndListener;

extern LPDIRECTSOUND        gpds;
extern LPDIRECTSOUNDBUFFER  gpdsbPrimary;
extern LPDIRECTSOUND3DLISTENER   gp3DListener;

extern MainWnd          AppWnd;

#endif  // INIT_GVARS

#ifdef __cplusplus
}
#endif

#endif  // __GVARS_H__


LSNRINFO.CPP

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>

#include "DSShow3D.h"
#include "debug.h"
#include "GVars.h"
#include "LsnrInfo.h"
#include "finfo3d.h"

ListenerInfo::ListenerInfo()
    {
    m_hDistanceEdit = m_hDopplerEdit = m_hRolloffEdit = NULL;
    m_hXSlider = m_hYSlider = m_hZSlider = NULL;
    m_hXText = m_hYText = m_hZText = NULL;

    m_vPos.x = m_vPos.y = m_vPos.z = (D3DVALUE)0.0;
    }

ListenerInfo::~ListenerInfo()
    {
    }

BOOL ListenerInfo::OnInitDialog( HWND hDlg, WPARAM wParam )
    {
    m_hDistanceEdit = GetDlgItem( hDlg, IDC_LISTENER_DISTANCEFACTOR_EDIT );
    m_hDopplerEdit = GetDlgItem( hDlg, IDC_LISTENER_DOPPLERFACTOR_EDIT );
    m_hRolloffEdit = GetDlgItem( hDlg, IDC_LISTENER_ROLLOFFFACTOR_EDIT );

    m_hXSlider = GetDlgItem( hDlg, IDC_LISTENER_X_SLIDER );
    m_hYSlider = GetDlgItem( hDlg, IDC_LISTENER_Y_SLIDER );
    m_hZSlider = GetDlgItem( hDlg, IDC_LISTENER_Z_SLIDER );

    m_hXText = GetDlgItem( hDlg, IDC_LISTENER_X_TEXT );
    m_hYText = GetDlgItem( hDlg, IDC_LISTENER_Y_TEXT );
    m_hZText = GetDlgItem( hDlg, IDC_LISTENER_Z_TEXT );

    // Initialize the distance factor to metres
    Edit_SetText( m_hDistanceEdit, "1.0" );
    Edit_SetText( m_hDopplerEdit, "1.0" );
    Edit_SetText( m_hRolloffEdit, "1.0" );

    gp3DListener->SetPosition( D3DVAL(0.0f), D3DVAL(0.0f), D3DVAL(-10.0f), DS3D_IMMEDIATE );
//    gp3DListener->CommitDeferredSettings();

    SetSliders();

    return TRUE;
    }

void ListenerInfo::UpdateUI( void )
    {
    UpdateXSliderUI( 0, TRUE );
    UpdateYSliderUI( 0, TRUE );
    UpdateZSliderUI( 0, TRUE );
    }

BOOL ListenerInfo::OnCommand( WPARAM wParam, LPARAM lParam )
    {
    TCHAR   szEdit[16];

    if( HIWORD(wParam) == EN_CHANGE )
{
if( (HWND)lParam == m_hDistanceEdit )
    {
    Edit_GetText( m_hDistanceEdit, szEdit, sizeof(szEdit));

    ASSERT( NULL != gp3DListener );
    DPF( 3, "Setting Distance Factor: %s", szEdit );
    gp3DListener->SetDistanceFactor(D3DVAL(atof(szEdit)), DS3D_IMMEDIATE );
    }
else if((HWND)lParam == m_hDopplerEdit )
    {
    Edit_GetText( m_hDopplerEdit, szEdit, sizeof(szEdit));

    ASSERT( NULL != gp3DListener );
    DPF( 3, "Setting Doppler Factor: %s", szEdit );
    gp3DListener->SetDopplerFactor(D3DVAL(atof(szEdit)), DS3D_IMMEDIATE );
    }
else if((HWND)lParam == m_hRolloffEdit )
    {
    Edit_GetText( m_hRolloffEdit, szEdit, sizeof(szEdit));

    ASSERT( NULL != gp3DListener );
    DPF( 3, "Setting Rolloff Factor: %s", szEdit );
    gp3DListener->SetRolloffFactor(D3DVAL(atof(szEdit)), DS3D_IMMEDIATE );
    }
}
    return FALSE;
    }

///////////////////////////////////////////////////////////////////////////////
// OnHScroll()
//
//    Main message handler for the WM_HSCROLL message.  this function basically
// figures out which horizontal scrolling control is responsible for sending
// the message and passes on handling to an appropriate function for handling.
//
BOOL ListenerInfo::OnHScroll( WORD wNotification, LONG lPos, HWND hControl )
    {
    if( !hControl )
return FALSE;

    if( hControl == m_hXSlider )
{
HandleXSliderScroll( wNotification, lPos );
return TRUE;
}
    else if( hControl == m_hYSlider )
{
HandleYSliderScroll( wNotification, lPos );
return TRUE;
}
    else if( hControl == m_hZSlider )
{
HandleZSliderScroll( wNotification, lPos );
return TRUE;
}
    else
return FALSE;
    }

void ListenerInfo::OnDestroy( void )
    {
    }

BOOL CALLBACK ListenerInfoDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    PLISTENERINFO   pli;

    switch( message )
{
case WM_INITDIALOG:
    ASSERT( lParam != NULL );
    pli = (PLISTENERINFO)lParam;
    SetWindowLong( hDlg, DWL_USER, (LONG)pli );

    if( !pli->OnInitDialog( hDlg, wParam ))
DestroyWindow( hDlg );
    return TRUE;

case WM_HSCROLL:
    pli = (PLISTENERINFO)GetWindowLong( hDlg, DWL_USER );
    ASSERT( NULL != pli );
    return !pli->OnHScroll( LOWORD(wParam), (LONG)HIWORD(wParam), (HWND)lParam );

case WM_COMMAND:
    pli = (PLISTENERINFO)GetWindowLong( hDlg, DWL_USER );
    // It's possible to get child notifications before the
    // INITDIALOG message, so we'll handle a NULL class item
    // here a less stringently
    if( !pli )
return FALSE;
    return !pli->OnCommand( wParam, lParam );

case WM_DESTROY:
    pli = (PLISTENERINFO)GetWindowLong( hDlg, DWL_USER );
    ASSERT( NULL != pli );
    pli->OnDestroy();
    return TRUE;

default:
    return FALSE;
}
    }

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ListenerInfo::SetSliders( void )
    {
    SendMessage( m_hXSlider, TBM_SETRANGEMIN, FALSE,
(LPARAM)(LISTENER_SLIDER_MIN + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hXSlider, TBM_SETRANGEMAX, FALSE,
(LPARAM)(LISTENER_SLIDER_MAX + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hXSlider, TBM_SETPAGESIZE, 0, LISTENER_SLIDER_FACTOR);

    SendMessage( m_hYSlider, TBM_SETRANGEMIN, FALSE,
(LPARAM)(LISTENER_SLIDER_MIN + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hYSlider, TBM_SETRANGEMAX, FALSE,
(LPARAM)(LISTENER_SLIDER_MAX + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hYSlider, TBM_SETPAGESIZE, 0, LISTENER_SLIDER_FACTOR);

    SendMessage( m_hZSlider, TBM_SETRANGEMIN, FALSE,
(LPARAM)(LISTENER_SLIDER_MIN + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hZSlider, TBM_SETRANGEMAX, FALSE,
(LPARAM)(LISTENER_SLIDER_MAX + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    SendMessage( m_hZSlider, TBM_SETPAGESIZE, 0, LISTENER_SLIDER_FACTOR);

    UpdateUI();
    }

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ListenerInfo::HandleXSliderScroll( WORD wNot, LONG lPos )
    {
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
{
case TB_THUMBTRACK:
    break;

case TB_ENDTRACK:
case TB_LINEDOWN:
case TB_LINEUP:
case TB_PAGEDOWN:
case TB_PAGEUP:
    lPos = SendMessage( m_hXSlider, TBM_GETPOS, 0, 0 );
    break;

default:
    fUpdate = FALSE;
}

    if( fUpdate && NULL != gp3DListener )
{
m_vPos.x = D3DVAL(lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT;
DPF( 3, "Setting listener pos: (%i, %i, %i)", (int)m_vPos.x, (int)m_vPos.y, (int)m_vPos.z );
if( FAILED( hr = gp3DListener->SetPosition( m_vPos.x, m_vPos.y,
m_vPos.z, DS3D_IMMEDIATE )))
    DPF( 0, "IDirectSound3DListener::SetPosition returned : %s", TranslateDSError(hr));
UpdateXSliderUI((lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT, FALSE );
}
    }

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ListenerInfo::HandleYSliderScroll( WORD wNot, LONG lPos )
    {
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
{
case TB_THUMBTRACK:
    break;

case TB_ENDTRACK:
case TB_LINEDOWN:
case TB_LINEUP:
case TB_PAGEDOWN:
case TB_PAGEUP:
    lPos = SendMessage( m_hYSlider, TBM_GETPOS, 0, 0 );
    break;

default:
    fUpdate = FALSE;
}

    if( fUpdate && NULL != gp3DListener )
{
m_vPos.y = D3DVAL(lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT;
DPF( 3, "Setting listener pos: (%i, %i, %i)", (int)m_vPos.x, (int)m_vPos.y, (int)m_vPos.z );
if( FAILED( hr = gp3DListener->SetPosition( m_vPos.x, m_vPos.y,
m_vPos.z, DS3D_IMMEDIATE )))
    DPF( 0, "IDirectSound3DListener::SetPosition returned : %s", TranslateDSError(hr));
UpdateYSliderUI((lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT, FALSE );
}
    }

///////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ListenerInfo::HandleZSliderScroll( WORD wNot, LONG lPos )
    {
    HRESULT hr;
    BOOL fUpdate = TRUE;

    switch( wNot )
{
case TB_THUMBTRACK:
    break;

case TB_ENDTRACK:
case TB_LINEDOWN:
case TB_LINEUP:
case TB_PAGEDOWN:
case TB_PAGEUP:
    lPos = SendMessage( m_hZSlider, TBM_GETPOS, 0, 0 );
    break;

default:
    fUpdate = FALSE;
}

    if( fUpdate && NULL != gp3DListener )
{
m_vPos.z = D3DVAL(lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT;
DPF( 3, "Setting listener pos: (%i, %i, %i)", (int)m_vPos.x, (int)m_vPos.y, (int)m_vPos.z );
if( FAILED( hr = gp3DListener->SetPosition( m_vPos.x, m_vPos.y,
m_vPos.z, DS3D_IMMEDIATE )))
    DPF( 0, "IDirectSound3DListener::SetPosition returned : %s", TranslateDSError(hr));
UpdateZSliderUI((lPos * LISTENER_SLIDER_FACTOR) - LISTENER_SLIDER_SHIFT, FALSE );
}
    }

///////////////////////////////////////////////////////////////////////////////
// UpdateXSliderUI()
//
//    Update anything that displays the position of the X slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void ListenerInfo::UpdateXSliderUI( LONG lForcePos, BOOL fFromInterface )
    {
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromInterface )
        {
if( NULL != gp3DListener )
    {
    gp3DListener->GetPosition( &m_vPos );
    lPos = (LONG)m_vPos.x;
    }
else
    lPos = 0;
}
    else
lPos = lForcePos;

    SendMessage( m_hXSlider, TBM_SETPOS, (WPARAM)TRUE,
    (LPARAM)(lPos + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_hXText, szText );
    }

///////////////////////////////////////////////////////////////////////////////
// UpdateYSliderUI()
//
//    Update anything that displays the position of the Y slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void ListenerInfo::UpdateYSliderUI( LONG lForcePos, BOOL fFromInterface )
    {
    TCHAR   szText[8];
    LONG    lPos;

    if( fFromInterface )
        {
if( NULL != gp3DListener )
    {
    gp3DListener->GetPosition( &m_vPos );
    lPos = (LONG)m_vPos.y;
    }
else
    lPos = 0;
}
    else
lPos = lForcePos;

    SendMessage( m_hYSlider, TBM_SETPOS, (WPARAM)TRUE,
    (LPARAM)(lPos + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_hYText, szText );
    }

///////////////////////////////////////////////////////////////////////////////
// UpdateZSliderUI()
//
//    Update anything that displays the position of the Z slider in some manner
// to reflect the new position, either from the buffer or from the parameter.
//
void ListenerInfo::UpdateZSliderUI( LONG lForcePos, BOOL fFromInterface )
    {
    TCHARszText[8];
    LONGlPos;

    if( fFromInterface )
        {
if( NULL != gp3DListener )
    {
    gp3DListener->GetPosition( &m_vPos );
    lPos = (LONG)m_vPos.z;
    }
else
    lPos = 0;
}
    else
lPos = lForcePos;

    SendMessage( m_hZSlider, TBM_SETPOS, (WPARAM)TRUE,
    (LPARAM)(lPos + LISTENER_SLIDER_SHIFT) / LISTENER_SLIDER_FACTOR );
    wsprintf( szText, "%i", lPos );
    Static_SetText( m_hZText, szText );
    }


LSNRINFO.H

#ifndef __LISTENERINFO_H__
#define __LISTENERINFO_H__

#define LISTENER_SLIDER_MAX(50)
#define LISTENER_SLIDER_MIN(-LISTENER_SLIDER_MAX)
#define LISTENER_SLIDER_FACTOR(1)
#define LISTENER_SLIDER_SHIFT(LISTENER_SLIDER_MAX)

class ListenerInfo
{
friend BOOL CALLBACK ListenerInfoDlgProc( HWND, UINT, WPARAM, LPARAM );

public:
    ListenerInfo();
    ~ListenerInfo();

    void UpdateUI( void );

// Useful protected member functions
protected:
    BOOL OnInitDialog( HWND, WPARAM );
    BOOL OnCommand( WPARAM, LPARAM );
    BOOL OnHScroll( WORD, LONG, HWND );
    void OnDestroy( void );

    void HandleXSliderScroll( WORD, LONG );
    void HandleYSliderScroll( WORD, LONG );
    void HandleZSliderScroll( WORD, LONG );

    void UpdateXSliderUI( LONG, BOOL );
    void UpdateYSliderUI( LONG, BOOL );
    void UpdateZSliderUI( LONG, BOOL );

    void SetSliders( void );

protected:
    HWND    m_hDistanceEdit, m_hDopplerEdit, m_hRolloffEdit;
    HWND    m_hXSlider, m_hYSlider, m_hZSlider;
    HWND    m_hXText, m_hYText, m_hZText;

    D3DVECTORm_vPos;
};

typedef ListenerInfo *PLISTENERINFO;

BOOL CALLBACK ListenerInfoDlgProc( HWND, UINT, WPARAM, LPARAM );

#endif


MAINWND.CPP

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>

#include "resource.h"
#include "DSShow3D.h"
#include "GVars.h"

#include "MainWnd.h"
#include "FInfo3D.h"
#include "FileInfo.h"
#include "LsnrInfo.h"
#include "wave.h"
#include "debug.h"

static HWND hMainWndClient;

MainWnd::MainWnd()
{
    m_fCreated = FALSE;
    m_hwnd = NULL;
    m_n3DBuffers = 0;
    ZeroMemory( &m_dscaps, sizeof(DSCAPS));
    m_dscaps.dwSize = sizeof(m_dscaps);
}

MainWnd::~MainWnd()
{
    // This situation should never really occur, but we should make sure
    // that there are no coding errors by asserting that fact.
    if( m_hwnd )
    {
ASSERT_HWND(m_hwnd);
::DestroyWindow( m_hwnd );
m_hwnd = NULL;
m_fCreated = FALSE;
    }
}

BOOL MainWnd::Create()
{
    if( m_fCreated )
return FALSE;

    ASSERT( NULL == m_hwnd );
    m_hwnd = CreateWindowEx( WS_EX_ACCEPTFILES,
    gszAppWndClass,
    gszAppCaption,
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    200,
    150,
    (HWND)NULL,
    (HMENU)NULL,
    (HINSTANCE)ghInst,
    (LPTSTR)NULL );

    if( !m_hwnd )
return FALSE;

    SetWindowLong( m_hwnd, GWL_USERDATA, (LONG)this );
    m_fCreated = TRUE;

    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar( m_hwnd );

    UpdateStatus();

    return TRUE;
}

int MainWnd::MessageBox( LPCSTR lpcszMessage, UINT uType )
{
    return ::MessageBox( m_hwnd, lpcszMessage, gszAppName, uType );
}

int MainWnd::MessageBox( UINT uResID, UINT uType )
{
    // TODO Make the 512 some defined constant
    TCHARszMessage[512];
    int    nRet;

    LoadString( ghInst, uResID, szMessage, 512 );
    nRet = ::MessageBox( m_hwnd, szMessage, gszAppName, uType );

    return nRet;
}

BOOL MainWnd::OnTimer( WPARAM wParam, LPARAM lParam )
{
    // Move to the Head of the list
    m_dlInfoList.SetAtHead();

    if( NULL != gpListenerInfo )
gpListenerInfo->UpdateUI();

    // While the current element isn't NULL, call OnTimer on each FileInfo
    for( int i = 0; i < m_dlInfoList.GetElementCount(); i++ )
    {
m_dlInfoList.GetCurrent()->UpdateUI();
// Overloaded increment moves to next position in the list
m_dlInfoList++;
    }
    return TRUE;
}

void MainWnd::OnDestroy()
{
    HRESULT     hr = 0;

    if( gdwTimer != 0 )
    {
KillTimer( m_hwnd, gdwTimer );
gdwTimer = 0;
    }

    if( NULL != gpwfxFormat )
    {
GlobalFree( gpwfxFormat );
gpwfxFormat = NULL;
    }

    /* Destroy the direct sound object. */
    if( gp3DListener != NULL )
    {
DPF( 3, "Releasing 3D Listener in MainWnd::OnDestroy()" );
gp3DListener->Release();
gp3DListener = NULL;
    }
    if( gpdsbPrimary )
    {
DPF( 3, "Releasing Primary in MainWnd::OnDestroy()" );
gpdsbPrimary->Stop();
gpdsbPrimary->Release();
gpdsbPrimary = NULL;
    }
    if( gpds != NULL )
    {
DPF( 3, "Releasing DSound object in MainWnd::OnDestroy()" );
gpds->Release();
gpds = NULL;
    }

    if( gfCOMInitialized )
CoUninitialize();

//    WriteProfileString( gszAppName, "EnumDrivers", gfEnumDrivers ? "1" : "0" );

    if( m_hwnd )
m_hwnd = NULL;
    return;
}

/* MainWndProc()
 *
 *    Window message handler function for the main application window.
 * For the most part, this sucker just dispatchs calls to some helper
 * functions in this module which do the actual handling.
 */
LRESULT CALLBACK MainWndProc( HWND hWnd, unsigned message, WPARAM wParam, LPARAM lParam )
{
    if( !hWnd )
return 0L;

    // This will be a valid pointer to the class object for any message
    // after the WM_CREATE
    MainWnd *pmw = (MainWnd *)GetWindowLong( hWnd, GWL_USERDATA );

    switch (message)
{
case WM_CREATE:
    // If we want to do anything with the class here, we'll have
    // to modify the code to pass pmw in LPARAM since there's no
    // other way to get it yet: the code that sets the WindowLong
    // user data has not yet executed
    break;

case WM_ACTIVATE:
    ASSERT( NULL != pmw );
    pmw->UpdateStatus();
    break;

case WM_TIMER:
    ASSERT( NULL != pmw );
    if (!pmw->OnTimer(wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

case WM_DROPFILES:
    ASSERT( NULL != pmw );
    if( !pmw->OnDropFiles( wParam ))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

case WM_PAINT:
    ASSERT( NULL != pmw );
    if( !pmw->OnPaint(wParam, lParam))
return(DefWindowProc(hWnd, message, wParam, lParam));
    break;

case WM_INITMENU:
    ASSERT( NULL != pmw );
    if((HMENU)wParam != GetMenu( hWnd ))
break;
    return pmw->OnInitMenu( wParam );
    break;

case WM_INITMENUPOPUP:
// we only want to deal with the options sub-menu.
    ASSERT( NULL != pmw );
    if((BOOL)HIWORD(lParam) == TRUE) // it's a system menu
break;
else if ((UINT)LOWORD(lParam) != 1) // it's not the options menu.
break;
// else handle it.
    return pmw->OnInitMenuPopup( wParam );
    break;

case WM_COMMAND:
    ASSERT( NULL != pmw );
    if (!pmw->OnCommand(wParam, lParam))
return DefWindowProc(hWnd, message, wParam, lParam);
    break;

case WM_DESTROY:
    ASSERT( NULL != pmw );
    pmw->OnDestroy();
    PostQuitMessage(0);
    break;

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

    return 0L;
}

BOOL MainWnd::OnInitMenuPopup( WPARAM wParam )
{
    int i;
char szFormat[255];
WAVEFORMATEX wfx, wfxActual;
BOOL fRet = FALSE;
DWORD dwFormatID;
char szTemp[25] = "*";
HRESULT hr = DS_OK;
HMENU hSubMenu = (HMENU)wParam;

ZeroMemory(&wfx, sizeof(WAVEFORMATEX));
ZeroMemory(&wfxActual, sizeof(WAVEFORMATEX));

// Get the handle to the format submenu.
hSubMenu = GetSubMenu(hSubMenu,0);
ASSERT(IsMenu(hSubMenu));

for( i = 0; i < NUM_FORMATENTRIES; i++ )
{
dwFormatID = CommandIDFromFormatCode(fdFormats[i].dwCode);
// get the string representing this format.
fRet = FormatCodeToText(fdFormats[i].dwCode, szFormat, sizeof(szFormat));
ASSERT(fRet);
// set this as the format.
FormatCodeToWFX( fdFormats[i].dwCode, &wfx);
wfx.wFormatTag = WAVE_FORMAT_PCM;
hr = gpdsbPrimary->SetFormat(&wfx);
if (hr != DS_OK)
{
DPF( 0, "SetFormat failed (%s)", TranslateDSError(hr));
DisableFormatCode(fdFormats[i].dwCode);
continue;
}
else
{
EnableFormatCode(fdFormats[i].dwCode);
}

// now get what is the actual format.
hr = gpdsbPrimary->GetFormat(&wfxActual, sizeof(WAVEFORMATEX), NULL);
if (hr != DS_OK)
{
DPF(0, "GetFormat failed with hr = %lu", hr);
}
// are these formats the same.
if (!IsSameFormat(&wfx, &wfxActual))
{
strcat(szFormat, szTemp);
fRet = ModifyMenu(hSubMenu, dwFormatID, MF_BYCOMMAND | MF_STRING, dwFormatID, szFormat);
}

if( !fdFormats[i].fEnable )
EnableMenuItem((HMENU)wParam, dwFormatID, MF_BYCOMMAND | MF_GRAYED );
else
EnableMenuItem((HMENU)wParam, dwFormatID, MF_BYCOMMAND | MF_ENABLED );
}// for

CheckMenuItem( (HMENU)wParam,
   CommandIDFromFormatCode( gdwOutputFormat ),
   MF_BYCOMMAND | MF_CHECKED );

// set the format back.
FormatCodeToWFX(gdwOutputFormat, &wfx);
hr = gpdsbPrimary->SetFormat(&wfx);
ASSERT(hr == DS_OK);

return TRUE;
}

BOOL MainWnd::OnInitMenu( WPARAM wParam )
{
    // If there are no buffers open, then disable the buffers menu
    EnableMenuItem((HMENU)wParam, 2, MF_BYPOSITION |
   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    return TRUE;
}

/* OnDropFiles()
 *
 *    Handles the WM_DROPFILES message, which is a message sent when someone
 * drags and drops files onto our application window.
 */
BOOL MainWnd::OnDropFiles( WPARAM wParam )
{
    WORD    wNumFiles = DragQueryFile((HDROP)wParam, (UINT)-1, NULL, (UINT)0 );
    WORD    nFile, nPathLength;
    LPTSTR  lpszFile;

    for( nFile = 0; nFile < wNumFiles; nFile++ )
    {
nPathLength = DragQueryFile((HDROP)wParam, nFile, NULL, 0 ) + 1;
if(( lpszFile = (LPTSTR)GlobalAlloc(GPTR, nPathLength * sizeof(TCHAR))) == NULL )
    continue;
DragQueryFile((HDROP)wParam, nFile, lpszFile, nPathLength );

// Open the file
OnFileOpen( lpszFile );

GlobalFree(lpszFile);
    }

    DragFinish((HDROP)wParam);
    return TRUE;
}

/* OnCommand()
 *
 *    WM_COMMAND message handler for the main window procedure.
 */
BOOL MainWnd::OnCommand( WPARAM wParam, LPARAM lParam )
{
    HRESULT hr;
    DWORD   dwNewFormat;
    int    i = 0;

    if(( dwNewFormat = FormatCodeFromCommandID( LOWORD(wParam))) != 0 )
{
CheckMenuItem( GetMenu( m_hwnd ),
CommandIDFromFormatCode( gdwOutputFormat ),
MF_BYCOMMAND | MF_UNCHECKED );
FormatCodeToWFX( dwNewFormat, gpwfxFormat );
DPF( 2, "Setting output format to code: %lu", dwNewFormat );
hr = gpdsbPrimary->SetFormat( gpwfxFormat );
if( SUCCEEDED( hr ))
    gdwOutputFormat = dwNewFormat;
else
    {
    DisableFormatCode( dwNewFormat );
    EnableMenuItem( GetMenu( m_hwnd ),
    CommandIDFromFormatCode( dwNewFormat ),
    MF_BYCOMMAND | MF_GRAYED );
    FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
    gpdsbPrimary->SetFormat( gpwfxFormat );
    }
return TRUE;
}

    switch( LOWORD(wParam))
    {
case IDC_FILE_EXIT:
    PostMessage(WM_CLOSE, 0, 0);
    break;

case IDC_FILE_OPEN:
    OnFileOpen( NULL );
    UpdateStatus();
    break;

case IDC_OPTIONS_SETTINGS:
    DialogBox(ghInst, MAKEINTRESOURCE(IDD_SETTINGS),
    m_hwnd, (DLGPROC)SettingsDlgProc);
    UpdateStatus();
    break;

case IDC_BUFFERS_MINIMIZEALL:
    m_dlInfoList.SetAtHead();
    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
{
m_dlInfoList.GetCurrent()->MinimizeWindow();
m_dlInfoList++;
}
    break;

case IDC_BUFFERS_RESTOREALL:
    m_dlInfoList.SetAtHead();
    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
{
m_dlInfoList.GetCurrent()->RestoreWindow();
m_dlInfoList++;
}
    break;

case IDC_BUFFERS_CLOSEALL:
    while( m_dlInfoList.GetElementCount())
{
m_dlInfoList.SetAtHead();
m_dlInfoList.GetCurrent()->Close();
// Element is removed from the list by processing elsewhere
}
    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar( m_hwnd );
    break;

case IDC_BUFFERS_CASCADE:
    m_dlInfoList.SetAtHead();
    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
{
if( 0 == i )
    m_dlInfoList.GetCurrent()->ResetCascade();
m_dlInfoList.GetCurrent()->CascadeWindow();
m_dlInfoList++;
}
    break;

case IDC_HELP_ABOUT:
    DialogBox(ghInst, MAKEINTRESOURCE(IDD_ABOUT),
    m_hwnd, (DLGPROC)AboutDlgProc);
    break;

default:
    return FALSE;
    }

    /* If we broke out of the switch, it means we handled the message
     * so we return TRUE to indicate this.
     */
    return TRUE;
}

/* DuplicateBuffer()
 *
 *    Does the work of duplicating a FileInfo object.
 */
void MainWnd::DuplicateBuffer( FileInfo *pfiSource )
{
    FileInfo*pfiNew;

    if( NULL == pfiSource )
return;

    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
MessageBox( "No more controls allowed" );
        return;
    }

    if( pfiSource->Is3D())
{
m_n3DBuffers++;
pfiNew = new FileInfo3D( this );
}
    else
pfiNew = new FileInfo( this );

    if( NULL == pfiNew )
return;

    pfiNew->Duplicate( pfiSource );
    m_dlInfoList.Append( pfiNew );
}

/* OnFileOpen()
 *
 *    Handles the File|Open menu command.
 */
FileInfo *MainWnd::OnFileOpen( LPTSTR lpszForcePath )
{
    TCHAR           szFileName[MAX_PATH];
    FileInfo        *pFileInfo = NULL;
    LPTSTR    lpszFileTitle;
    int             nFileIndex;
    DWORD    dwFlags;

    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
MessageBox( "No more controls allowed" );
        return NULL;
    }

    if( NULL != lpszForcePath )
    {
dwFlags = OPENFILENAME_F_GETPOS2;
if( grs.dwDefaultFocusFlag & DSBCAPS_STICKYFOCUS )
    dwFlags |= OPENFILENAME_F_STICKYFOCUS;
else if( grs.dwDefaultFocusFlag & DSBCAPS_GLOBALFOCUS )
    dwFlags |= OPENFILENAME_F_GLOBALFOCUS;

if( grs.fOpen3D )
    dwFlags |= OPENFILENAME_F_3D;
lpszFileTitle = strrchr( lpszForcePath, '\\' );
if( NULL == lpszFileTitle )
    nFileIndex = 0;
else
    nFileIndex =  lpszFileTitle - lpszForcePath + sizeof(TCHAR);
lstrcpy( szFileName, lpszForcePath );
    }
    else
    {
// Open the file, and check its format, etc.
if( !OpenFileDialog( m_hwnd, szFileName, &nFileIndex, &dwFlags ))
    return NULL;
    }

    // Allocate the memory for the structure.
    if( dwFlags & OPENFILENAME_F_3D )
    {
pFileInfo = new FileInfo3D( this );
    }
    else
pFileInfo = new FileInfo( this );

    if( NULL == pFileInfo )
    {
MessageBox( "Cannot add this file -- out of memory",
    MB_OK|MB_ICONSTOP );
goto ERROR_DONE_ROUTINE;
    }

    if( m_dlInfoList.GetElementCount() == 0 )
pFileInfo->ResetCascade();

    m_dlInfoList.Append( pFileInfo );

    pFileInfo->SetSticky((dwFlags & OPENFILENAME_F_STICKYFOCUS)
? TRUE : FALSE );
    pFileInfo->SetGlobal((dwFlags & OPENFILENAME_F_GLOBALFOCUS)
? TRUE : FALSE );
    pFileInfo->Set3D((dwFlags & OPENFILENAME_F_3D) ? TRUE : FALSE );
    pFileInfo->SetNotify((dwFlags & OPENFILENAME_F_NOTIFY) ? TRUE : FALSE );
    pFileInfo->SetUseGetPos2((dwFlags & OPENFILENAME_F_GETPOS2)
? TRUE : FALSE );
    pFileInfo->SetMuteAtMax((dwFlags & OPENFILENAME_F_MUTEMAX) ? TRUE : FALSE );

    if( pFileInfo->LoadWave( szFileName, nFileIndex ) != 0 )
    {
    m_dlInfoList.Remove( pFileInfo );
    goto ERROR_DONE_ROUTINE;
    }

    // If we fail after this, make sure to update the list!!!
    if( pFileInfo->Is3D())
{
if( m_n3DBuffers++ == 0 )
    {
    // We only want to create this dialog once, and if this is the
    // first 3D buffer opened, then there should be no listener DLG yet
    ASSERT( NULL == ghwndListener );

    gpListenerInfo = new ListenerInfo;
    ghwndListener = CreateDialogParam( ghInst,
MAKEINTRESOURCE(IDD_3DLISTENER),
AppWnd.GetHwnd(),
(DLGPROC)ListenerInfoDlgProc,
(LPARAM)(gpListenerInfo));
    ::ShowWindow( ghwndListener, SW_SHOW );
    }
}

    // If there are no buffers open, then disable the buffers menu
    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar( m_hwnd );

    return pFileInfo;

ERROR_DONE_ROUTINE:
    if( pFileInfo != NULL )
    {
delete pFileInfo;
pFileInfo = NULL;
    }
    return NULL;
}

/* DestroyFileInfo()
 *
 *    Destroys a FileInfo structure and removes it from the list.
 */
void MainWnd::DestroyFileInfo( FileInfo *pfi )
{
    if( NULL == pfi )
return;

    if( pfi->Is3D() )
{
if( --m_n3DBuffers == 0 )
    {
    ::DestroyWindow( ghwndListener );
    ghwndListener = NULL;
    delete gpListenerInfo;
    gpListenerInfo = NULL;
    }
}

    m_dlInfoList.Remove( pfi );
    delete pfi;
    UpdateStatus();
}

BOOL MainWnd::OnPaint( WPARAM wParam, LPARAM lParam )
    {
    PAINTSTRUCTps;
    RECTrcClient;
    SIZEsizeExtent;
    TCHARszText[128];

    if( !BeginPaint( m_hwnd, &ps ))
return FALSE;

    GetClientRect( m_hwnd, &rcClient );

    SetBkMode( ps.hdc, TRANSPARENT );

    wsprintf( szText, "Free HW Mem: %luKb", m_dscaps.dwFreeHwMemBytes / 1024 );
    GetTextExtentPoint32( ps.hdc, szText, lstrlen(szText), &sizeExtent );

    DrawText( ps.hdc, szText, -1, &rcClient, DT_TOP | DT_LEFT );

    wsprintf( szText, "Free HW Buffers: %lu", m_dscaps.dwFreeHwMixingStaticBuffers );
    rcClient.top += sizeExtent.cy;
    DrawText( ps.hdc, szText, -1, &rcClient, DT_TOP | DT_LEFT );

    EndPaint( m_hwnd, &ps );
    return TRUE;
    }

void MainWnd::UpdateStatus( void )
    {
    if( gpds )
{
// Get updated statistics on the DSound device
gpds->GetCaps( &m_dscaps );

// Force a window client area repaint
InvalidateRect( m_hwnd, NULL, TRUE );
UpdateWindow();
}
    }

////////////////////////////////////////////////////////////////////////////
// BatchOpenFiles()
//
//    Takes an array of string pointers and tries to open each as a file to
// playback.  If fPlay is TRUE, the files will be played as they are being
// opened.  If fLoop is TRUE, they will also be set to loop.
//
// Returns: FALSE in the event of catastrophic failure, otherwise TRUE.
//
BOOL MainWnd::BatchOpenFiles( PSTR *ppszFiles, int nFiles, BOOL fPlay, BOOL fLoop )
{
    FileInfo*pfi;
    inti;

    // Cap the number of files we can load out of the given set if we'd load
    // too many otherwise
    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
MessageBox( "No more controls allowed" );
        return FALSE;
    }

    for( i = 0; i < nFiles; i++ )
    {
if(( pfi = OnFileOpen( ppszFiles[i] )) == NULL )
    continue;

// LOOP is only obeyed if PLAY was also specified
if( fPlay )
{
    if( fLoop )
{
pfi->SetLooped( TRUE );
}
    pfi->PlayBuffer();
}

pfi->UpdateUI();
    }

    return TRUE;
}

// =====================================
// IsSameFormat
// =====================================
BOOL IsSameFormat(LPWAVEFORMATEX pwfx1, LPWAVEFORMATEX pwfx2)
{
//if (pwfx1->wFormatTag != pwfx2->wFormatTag)
//return FALSE;
if (pwfx1->nChannels != pwfx2->nChannels)
return FALSE;
if (pwfx1->nSamplesPerSec != pwfx2->nSamplesPerSec)
return FALSE;
if (pwfx1->wBitsPerSample != pwfx2->wBitsPerSample)
return FALSE;

return TRUE;
}


MAINWND.H

#ifndef __MAINWND_H__
#define __MAINWND_H__

// Pickup the ASSERT_HWND() macro
#include "DbList.h"

class MainWnd
{
    friend LRESULT CALLBACK MainWndProc(HWND, unsigned, WPARAM, LPARAM);
public:
    MainWnd();
    ~MainWnd();

    BOOL Create();
    BOOL BatchOpenFiles( PSTR *, int, BOOL, BOOL );

    void UpdateStatus( void );
    inline HWND GetHwnd()    { return m_hwnd; }

    void DuplicateBuffer( FileInfo *pfiSource );
    void DestroyFileInfo( FileInfo *pfi );

    // For convenience, we redefine some global window related functions as
    // members of this class since we can automagically validate and pass the
    // HWND for the class object.
    BOOL UpdateWindow()  { ASSERT_HWND( m_hwnd );
return ::UpdateWindow( m_hwnd ); }
    BOOL ShowWindow( int cmdShow )  { ASSERT_HWND( m_hwnd );
return ::ShowWindow( m_hwnd, cmdShow ); }
    LRESULT SendMessage( UINT m, WPARAM w, LPARAM l )
{ ASSERT_HWND( m_hwnd ); return ::SendMessage( m_hwnd, m, w, l ); }
    LRESULT PostMessage( UINT m, WPARAM w, LPARAM l )
{ ASSERT_HWND( m_hwnd ); return ::PostMessage( m_hwnd, m, w, l ); }
    int MessageBox( LPCSTR, UINT uType = MB_OK | MB_APPLMODAL );
    int MessageBox( UINT uResID, UINT uType = MB_OK | MB_APPLMODAL );

protected:
    void OnDestroy( void );
    BOOL OnCommand( WPARAM, LPARAM );
    BOOL OnTimer( WPARAM, LPARAM );
    BOOL OnPaint( WPARAM, LPARAM );
    BOOL OnDropFiles( WPARAM );
    BOOL OnInitMenu( WPARAM );
BOOL OnInitMenuPopup(WPARAM);

    FileInfo *OnFileOpen( LPTSTR );

protected:
    HWND    m_hwnd;
    BOOL    m_fCreated;
    int    m_n3DBuffers;
    DSCAPS  m_dscaps;

    DbLinkedList<class FileInfo *>    m_dlInfoList;

};

#endif


DBLIST.CPP

#include <windows.h>
#include "debug.h"

template<class T>
DbLinkedList<T>::DbLinkedList()
{
    m_pTail = m_pHead = m_pCurrent = NULL;
}

template<class T>
DbLinkedList<T>::DbLinkedList( T emptyElt )
{
    m_pTail = m_pHead = m_pCurrent = NULL;
    m_emptyT = emptyElt;
}

template<class T>
DbLinkedList<T>::~DbLinkedList()
{
    Clear();
}

template<class T>
void DbLinkedList<T>::Clear( void )
{
    DblNode<T>*pTemp;

    while( m_nElementCount )
    {
pTemp = m_pHead->pNext;
delete m_pHead;
m_pHead = pTemp;
m_nElementCount--;
    }

    m_pHead = m_pTail = m_pCurrent = NULL;
}

template<class T>
void DbLinkedList<T>::Remove( T val )
{
    DblNode<T>*pTemp = m_pHead;

    while( pTemp )
    {
if( pTemp->value == val )
{
    if( pTemp->pNext )
    pTemp->pNext->pPrev = pTemp->pPrev;
    if( pTemp->pPrev )
    pTemp->pPrev->pNext = pTemp->pNext;
    if( m_pHead == pTemp )
    m_pHead = pTemp->pNext;
    if( m_pTail == pTemp )
    m_pTail = pTemp->pPrev;

    delete pTemp;

    m_nElementCount--;
    pTemp = NULL;
    return;
}

pTemp = pTemp->pNext;
    }
}

template<class T>
void DbLinkedList<T>::RemoveCurrent()
{
    if( m_pCurrent )
    {
ASSERT( m_nElementCount > 0 );

if( m_pCurrent->pNext )
m_pCurrent->pNext->pPrev = m_pCurrent->pPrev;
if( m_pCurrent->pPrev )
m_pCurrent->pPrev->pNext = m_pCurrent->pNext;
if( m_pHead == m_pCurrent )
m_pHead = m_pCurrent->pNext;
if( m_pTail == m_pCurrent )
m_pTail = m_pCurrent->pPrev;

delete m_pCurrent;

m_nElementCount--;
m_pCurrent = NULL;
    }
}

template<class T>
T DbLinkedList<T>::GetCurrent()
{
    if( m_pCurrent )
    {
ASSERT( m_nElementCount > 0 );

return m_pCurrent->value;
    }
    return m_emptyT;
}

// TRUE == success, FALSE == failure
template<class T>
BOOL DbLinkedList<T>::InsertAfterCurrent( T val )
{
    DblNode<T> *pTemp = new DblNode<T>;

    if( NULL == pTemp )
return FALSE;

    if( NULL == m_pCurrent )
    {
AssertValid();
if( NULL != m_pHead )
   {
pTemp->value = val;
pTemp->pPrev = m_pHead;
pTemp->pNext = m_pHead->pNext;
if( m_pHead->pNext )
    m_pHead->pNext->pPrev = pTemp;
if( m_pTail == m_pHead )
    m_pTail = pTemp;
   }
    } else {
pTemp->value = val;
pTemp->pPrev = m_pCurrent;
pTemp->pNext = m_pCurrent->pNext;
if( m_pCurrent->pNext )
    m_pCurrent->pNext->pPrev = pTemp;
// If we were pointing at the tail, we are no longer pointing
// at it because we put a new element after the current one.
if( m_pTail == m_pCurrent )
    m_pTail = pTemp;
    }

    return TRUE;
}

// TRUE == success, FALSE == failure
template<class T>
BOOL DbLinkedList<T>::InsertBeforeCurrent( T val )
{
    DblNode<T> *pTemp = new DblNode<T>;

    if( NULL == pTemp )
return FALSE;

    if( NULL == m_pCurrent )
    {
AssertValid();
if( NULL != m_pHead )
   {
pTemp->value = val;
pTemp->pPrev = m_pHead;
pTemp->pNext = m_pHead->pNext;
if( m_pHead->pNext )
    m_pHead->pNext->pPrev = pTemp;
if( m_pTail == m_pHead )
    m_pTail = pTemp;
   }
    } else {
pTemp->value = val;
pTemp->pPrev = m_pCurrent;
pTemp->pNext = m_pCurrent->pNext;
if( m_pCurrent->pNext )
    m_pCurrent->pNext->pPrev = pTemp;
// If we were pointing at the tail, we are no longer pointing
// at it because we put a new element after the current one.
if( m_pTail == m_pCurrent )
    m_pTail = pTemp;
    }

    return TRUE;
}

template<class T>
void DbLinkedList<T>::Append( T val )
{
    DblNode<T> *pNode = new DblNode<T>;

    if( NULL == pNode )
return;

    AssertValid();

    pNode->value = val;
    pNode->pNext = NULL;
    pNode->pPrev = m_pTail;

    if( m_pTail )
m_pTail->pNext = pNode;
    if( !m_pHead )
m_pHead = pNode;
    m_pTail = pNode;
    m_nElementCount++;

    // If there were no elements in the list, the m_pCurrent will be NULL
    if( NULL == m_pCurrent )
    {
ASSERT( m_nElementCount == 1 );
m_pCurrent = m_pHead;
    }
}

// Do some assertions that determine whether or not this object is in a valid
// state.  If it's not, one of these will fire.
template<class T>
void DbLinkedList<T>::AssertValid( void )
{
    if( m_nElementCount )
    {
// If there are elements, then both these pointers should point
        // at a legitimate DblNode object, or the list is corrupted
ASSERT( NULL != m_pHead );
ASSERT( NULL != m_pTail );
ASSERT( NULL != m_pCurrent );
    }
    else
    {
ASSERT( NULL == m_pHead );
ASSERT( NULL == m_pTail );
ASSERT( NULL == m_pCurrent );
    }
}


RESHEAD.H

#ifdef APSTUDIO_INVOKED// Built from inside VC
#include "afxres.h"

#else// Built from the command-line (some command-lines don't have MFC headers)

#undef RC_INVOKED
#define RC_INVOKED

#undef IDC_STATIC
#define IDC_STATIC-1

#include "windows.h"
#include "commctrl.h"

#endif


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dsshow3d.rc
//
#define IDR_MAINMENU                    101
#define IDB_SOUNDCARD                   102
#define IDD_ABOUT                       103
#define IDI_SPEAKER_RGB                 107
#define IDI_SPEAKER_RGB_3D              109
#define IDD_DSENUMBOX                   109
#define IDB_WAVE_3D                     110
#define IDD_FILEOPEN_NEST               110
#define IDB_WAVE_2D                     111
#define IDB_BUTTON_LOOP                 115
#define IDI_SMALL_3D                    119
#define IDB_OVERLAY_SW                  121
#define IDB_OVERLAY_HW                  122
#define IDB_OVERLAY_S                   123
#define IDB_OVERLAY_H                   124
#define IDB_BUTTON_CLOSE                127
#define IDB_BUTTON_PLAY                 128
#define IDB_BUTTON_STOP                 129
#define IDB_BUTTON_AUDIBLE              130
#define IDB_BUTTON_LOOP2                131
#define IDB_BUTTON_MUTE                 132
#define IDD_BUFFER                      132
#define IDM_POPUPS                      134
#define IDD_BUFFER3D                    135
#define IDM_BUFFERDLG                   135
#define IDD_3DLISTENER                  136
#define IDD_SETTINGS                    137
#define IDS_MB_DRIVERENUM_MESSAGE       512
#define ID_OK                           1001
#define IDC_FONEST_3D                   1001
#define IDC_FONEST_MUTEATMAX            1002
#define IDC_FONEST_STICKY_RADIO         1007
#define IDC_DSENUM_COMBO                1008
#define IDC_FONEST_GLOBAL_RADIO         1008
#define IDC_FONEST_GETPOS_RADIO         1009
#define IDC_FONEST_GETPOS2_RADIO        1010
#define IDC_FONEST_LOCAL_RADIO          1012
#define IDC_FONEST_GETPOS_GROUP         1013
#define IDC_BUFFERDLG_PLAY_BUTTON       1014
#define IDC_BUFFERDLG_FREQ_SLIDER       1023
#define IDS_BUFFERTYPE_HARDWARE         1024
#define IDS_BUFFERTYPE_SOFTWARE         1025
#define IDC_BUFFERDLG_VOL_SLIDER        1026
#define IDS_FOCUSMODE_LOCAL             1026
#define IDC_BUFFERDLG_PAN_SLIDER        1027
#define IDS_FOCUSMODE_STICKY            1027
#define IDS_FOCUSMODE_GLOBAL            1028
#define IDS_GETPOSMODE_NORMAL           1029
#define IDS_GETPOSMODE_GETPOS2          1030
#define IDC_BUFFERDLG_FOCUS_TEXT        1031
#define IDS_DATAFORMAT_STEREO           1031
#define IDC_BUFFERDLG_GETPOS_TEXT       1032
#define IDS_DATAFORMAT_MONO             1032
#define IDC_BUFFERDLG_PAN_TEXT          1033
#define IDC_BUFFERDLG_VOL_TEXT          1034
#define IDC_BUFFERDLG_FREQ_TEXT         1035
#define IDC_BUFFERDLG_WRITECURSOR_TEXT  1036
#define IDC_BUFFERDLG_DATAFORMAT_TEXT   1037
#define IDC_BUFFERDLG_PLAYCURSOR_TEXT   1038
#define IDC_BUFFERDLG_PROGRESS_SLIDER   1039
#define IDC_BUFFERDLG_PROGRESS_SPIN     1057
#define IDC_BUFFERDLG_OUTERVOL_SLIDER   1059
#define IDC_BUFFERDLG_OUTERVOL_TEXT     1060
#define IDC_BUFFERDLG_INNERANGLE_EDIT   1062
#define IDC_BUFFERDLG_OUTERANGLE_EDIT   1063
#define IDC_BUFFERDLG_OUTERANGLE_SPIN   1064
#define IDC_BUFFERDLG_INNERANGLE_SPIN   1065
#define IDC_BUFFERDLG_PROGRESS_TEXT     1066
#define IDC_BUFFERDLG_BUFFERTYPE_TEXT   1067
#define IDC_BUFFERDLG_LOOPED_CHECK      1068
#define IDC_LISTENER_DISTANCEFACTOR_EDIT 1069
#define IDC_LISTENER_DOPPLERFACTOR_EDIT 1070
#define IDC_LISTENER_ROLLOFFFACTOR_EDIT 1071
#define IDC_SETTINGS_DSD_DEVICE_COMBO   1072
#define IDC_SETTINGS_DSD_DEFAULT_CHECK  1073
#define IDC_SETTINGS_DSD_FORMAT_COMBO   1074
#define IDC_SETTINGS_INITIALDIR_EDIT    1075
#define IDC_SETTINGS_EXCLUSIVE_CHECK    1077
#define IDC_SETTINGS_DSD_DEVICE_TEXT    1078
#define IDC_SETTINGS_OPEN3D_CHECK       1079
#define IDC_SETTINGS_FOCUS_COMBO        1080
#define IDC_SETTINGS_BROWSE_INITIALDIR_BUTTON 1081
#define IDC_LISTENER_X_SLIDER           1084
#define IDC_LISTENER_Y_SLIDER           1085
#define IDC_LISTENER_Z_SLIDER           1086
#define IDC_LISTENER_X_TEXT             1087
#define IDC_LISTENER_Y_TEXT             1088
#define IDC_LISTENER_Z_TEXT             1089
#define IDC_BUFFERDLG_X_SLIDER          1090
#define IDC_BUFFERDLG_Y_SLIDER          1091
#define IDC_BUFFERDLG_Z_SLIDER          1092
#define IDC_BUFFERDLG_X_TEXT            1093
#define IDC_BUFFERDLG_Y_TEXT            1094
#define IDC_BUFFERDLG_Z_TEXT            1095
#define IDC_BUFFERDLG_VX_SLIDER         1100
#define IDC_BUFFERDLG_VY_SLIDER         1101
#define IDC_BUFFERDLG_VZ_SLIDER         1102
#define IDC_BUFFERDLG_VX_TEXT           1103
#define IDC_BUFFERDLG_VY_TEXT           1104
#define IDC_BUFFERDLG_VZ_TEXT           1105
#define IDC_BUFFERDLG_DISABLE           1110
#define IDC_BUFFERDLG_MAXMUTE_SLIDER    1111
#define IDC_BUFFERDLG_MAXMUTE_TEXT      1112
#define IDC_BUFFERDLG_CURDIST_TEXT      1113
#define IDC_BUFFERDLG_NOTIFY_FRAME      1114
#define IDC_FONEST_NOTIFYCHECK          1115
#define IDC_BUFFERDLG_MINDIST_SLIDER    1116
#define IDC_BUFFERDLG_NOTIFY_SLIDER3D   1117
#define IDC_BUFFERDLG_MINDIST_TEXT      1118
#define IDC_BUFFERDLG_NOTIFY_FRAME3D    1119
#define IDC_BUFFERDLG_NOTIFY_SLIDER     1120
#define IDC_BUFFERDLG_NOTIFY_CHECK3D    1121
#define IDC_BUFFERDLG_NOTIFY_TEXT       1122
#define ID_FREQCONTEXT_FILEDEFAULT      40001
#define IDC_FILE_EXIT                   40002
#define ID_FREQCONTEXT_8000HZ           40002
#define IDC_FILE_OPEN                   40003
#define ID_FREQCONTEXT_11025HZ          40003
#define ID_FREQCONTEXT_22050HZ          40004
#define IDC_HELP_ABOUT                  40005
#define ID_FREQCONTEXT_44100HZ          40005
#define ID_VOLCONTEXT_0DB               40006
#define IDC_OPTIONS_ENUMDRIVERS         40007
#define ID_VOLCONTEXT_10DB              40007
#define ID_VOLCONTEXT_20DB              40008
#define ID_VOLCONTEXT_30DB              40009
#define ID_VOLCONTEXT_100DB             40010
#define ID_PANCONTEXT_CENTER            40011
#define ID_PANCONTEXT_10DB_LEFT         40012
#define ID_PANCONTEXT_10DB_RIGHT        40013
#define ID_PANCONTEXT_FULL_LEFT         40014
#define ID_PANCONTEXT_FULL_RIGHT        40015
#define ID_BUFFERDLG_FILE_OPEN          40019
#define ID_OUTERVOLCONTEXT_0DB          40020
#define ID_OUTERVOLCONTEXT_10DB         40021
#define ID_OUTERVOLCONTEXT_20DB         40022
#define ID_OUTERVOLCONTEXT_30DB         40023
#define ID_OUTERVOLCONTEXT_100DB        40024
#define ID_OPTIONS_FORMAT_8M8           40025
#define ID_OPTIONS_FORMAT_8M16          40026
#define ID_OPTIONS_FORMAT_8S8           40027
#define ID_OPTIONS_FORMAT_8S16          40028
#define ID_OPTIONS_FORMAT_11M8          40029
#define ID_OPTIONS_FORMAT_11M16         40030
#define ID_OPTIONS_FORMAT_11S8          40031
#define ID_OPTIONS_FORMAT_11S16         40032
#define ID_OPTIONS_FORMAT_22M8          40033
#define ID_OPTIONS_FORMAT_22M16         40034
#define ID_OPTIONS_FORMAT_22S8          40035
#define ID_OPTIONS_FORMAT_22S16         40036
#define ID_OPTIONS_FORMAT_44S16         40037
#define ID_OPTIONS_FORMAT_44S8          40038
#define ID_OPTIONS_FORMAT_44M16         40039
#define ID_OPTIONS_FORMAT_44M8          40040
#define IDC_OPTIONS_SETTINGS            40041
#define IDC_BUFFERS_MINIMIZEALL         40042
#define IDC_BUFFERS_RESTOREALL          40043
#define IDC_BUFFERS_CLOSEALL            40044
#define IDC_BUFFERS_CASCADE             40045
#define ID_POSCONTEXT_ORIGIN            40047
#define ID_POSCONTEXT_LISTENERPOSITION  40049
#define ID_VELOCONTEXT_ORIGIN           40050
#define ID_BUFFERDLG_DUPLICATE          40051

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        138
#define _APS_NEXT_COMMAND_VALUE         40052
#define _APS_NEXT_CONTROL_VALUE         1130
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


WAVE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:               wave.c
 *  Content:    Wave library routines.
 *      This file is used for loading/saving waves, and reading and
 *      writing waves in smaller blocks.
 *      Uses WaveOpenFile, WaveReadFile and WaveCloseReadFile for
 *      single block access to reading wave files.
 *      Uses WaveCreateFile, WaveWriteFile, WaveCloseWriteFile for
 *      single block access for writing  wave files.
 *      Uses WaveLoadFile to load a whole wave file into memory.
 *      Uses WaveSaveFile to save a whole wave file into memory.
 *
 ***************************************************************************/

/* PROTOTYPES */
#include <windows.h>
#include <mmsystem.h>
#include "wave.h"
#include "debug.h"
#include "windowsx.h"

/* ROUTINES */
/* -------------------------------------------------------*/

/* This function will open a wave input file and prepare it for reading,
 * so the data can be easily
 * read with WaveReadFile. Returns 0 if successful, the error code if not.
 *      pszFileName - Input filename to load.
 *      phmmioIn    - Pointer to handle which will be used
 *          for further mmio routines.
 *      ppwfxInfo   - Ptr to ptr to WaveFormatEx structure
 *          with all info about the file.
 *
*/
int WaveOpenFile(
TCHAR*pszFileName,                              // (IN)
HMMIO *phmmioIn,                                // (OUT)
WAVEFORMATEX **ppwfxInfo,                       // (OUT)
MMCKINFO *pckInRIFF                             // (OUT)
)
{
HMMIO           hmmioIn;
MMCKINFO        ckIn;           // chunk info. for general use.
PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.
WORD            cbExtraAlloc;   // Extra bytes for waveformatex
int             nError;         // Return value.

// Initialization...
*ppwfxInfo = NULL;
nError = 0;
hmmioIn = NULL;

if ((hmmioIn = mmioOpen(pszFileName, NULL, MMIO_ALLOCBUF | MMIO_READ)) == NULL)
{
nError = ER_CANNOTOPEN;
goto ERROR_READING_WAVE;
}

if ((nError = (int)mmioDescend(hmmioIn, pckInRIFF, NULL, 0)) != 0)
{
goto ERROR_READING_WAVE;
}

if ((pckInRIFF->ckid != FOURCC_RIFF) || (pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E')))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Search the input file for for the 'fmt ' chunk.     */
    ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if ((nError = (int)mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

/* Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
    * if there are extra parameters at the end, we'll ignore them */

    if (ckIn.cksize < (long) sizeof(PCMWAVEFORMAT))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Read the 'fmt ' chunk into <pcmWaveFormat>.*/
    if (mmioRead(hmmioIn, (HPSTR) &pcmWaveFormat, (long) sizeof(pcmWaveFormat)) != (long) sizeof(pcmWaveFormat))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

// Ok, allocate the waveformatex, but if its not pcm
// format, read the next word, and thats how many extra
// bytes to allocate.
if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM)
cbExtraAlloc = 0;

else
{
// Read in length of extra bytes.
if (mmioRead(hmmioIn, (LPTSTR) &cbExtraAlloc,
(long) sizeof(cbExtraAlloc)) != (long) sizeof(cbExtraAlloc))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

}

// Ok, now allocate that waveformatex structure.
if ((*ppwfxInfo = GlobalAlloc(GMEM_FIXED, sizeof(WAVEFORMATEX)+cbExtraAlloc)) == NULL)
{
nError = ER_MEM;
goto ERROR_READING_WAVE;
}

// Copy the bytes from the pcm structure to the waveformatex structure
memcpy(*ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat));
(*ppwfxInfo)->cbSize = cbExtraAlloc;

// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
if (cbExtraAlloc != 0)
{
if (mmioRead(hmmioIn, (LPTSTR) (((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(cbExtraAlloc)),
(long) (cbExtraAlloc)) != (long) (cbExtraAlloc))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}
}

/* Ascend the input file out of the 'fmt ' chunk. */
if ((nError = mmioAscend(hmmioIn, &ckIn, 0)) != 0)
{
goto ERROR_READING_WAVE;

}

goto TEMPCLEANUP;

ERROR_READING_WAVE:
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

TEMPCLEANUP:
*phmmioIn = hmmioIn;

return(nError);

}

/*      This routine has to be called before WaveReadFile as it searchs for the chunk to descend into for
reading, that is, the 'data' chunk.  For simplicity, this used to be in the open routine, but was
taken out and moved to a separate routine so there was more control on the chunks that are before
the data chunk, such as 'fact', etc... */

int WaveStartDataRead(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRIFF
)
{
int                     nError;

nError = 0;

// Do a nice little seek...
if ((nError = mmioSeek(*phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
ASSERT(FALSE);
}

nError = 0;
//      Search the input file for for the 'data' chunk.
pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a');
if ((nError = mmioDescend(*phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

goto CLEANUP;

ERROR_READING_WAVE:

CLEANUP:
return(nError);
}

/*      This will read wave data from the wave file.  Makre sure we're descended into
the data chunk, else this will fail bigtime!
hmmioIn         - Handle to mmio.
cbRead          - # of bytes to read.
pbDest          - Destination buffer to put bytes.
cbActualRead- # of bytes actually read.

*/

int WaveReadFile(
HMMIO hmmioIn,                          // IN
UINT cbRead,                            // IN
BYTE *pbDest,                           // IN
MMCKINFO *pckIn,                        // IN.
UINT *cbActualRead                      // OUT.

)
{

MMIOINFO    mmioinfoIn;         // current status of <hmmioIn>
int                     nError;
UINT            cT, cbDataIn;

nError = 0;

if (nError = mmioGetInfo(hmmioIn, &mmioinfoIn, 0) != 0)
{
goto ERROR_CANNOT_READ;
}

cbDataIn = cbRead;
if (cbDataIn > pckIn->cksize)
cbDataIn = pckIn->cksize;

pckIn->cksize -= cbDataIn;

for (cT = 0; cT < cbDataIn; cT++)
{
/* Copy the bytes from the io to the buffer. */
if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
    if ((nError = mmioAdvance(hmmioIn, &mmioinfoIn, MMIO_READ)) != 0)
{
goto ERROR_CANNOT_READ;
}
    if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
nError = ER_CORRUPTWAVEFILE;
goto ERROR_CANNOT_READ;
}
}

// Actual copy.
*((BYTE*)pbDest+cT) = *((BYTE*)mmioinfoIn.pchNext)++;
}

if ((nError = mmioSetInfo(hmmioIn, &mmioinfoIn, 0)) != 0)
{
goto ERROR_CANNOT_READ;
}

*cbActualRead = cbDataIn;
goto FINISHED_READING;

ERROR_CANNOT_READ:
*cbActualRead = 0;

FINISHED_READING:
return(nError);

}

/*      This will close the wave file openned with WaveOpenFile.
phmmioIn - Pointer to the handle to input MMIO.
ppwfxSrc - Pointer to pointer to WaveFormatEx structure.

Returns 0 if successful, non-zero if there was a warning.

*/
int WaveCloseReadFile(
HMMIO *phmmio,                                  // IN
WAVEFORMATEX **ppwfxSrc                 // IN
)
{

if (*ppwfxSrc != NULL)
{
GlobalFree(*ppwfxSrc);
*ppwfxSrc = NULL;
}

if (*phmmio != NULL)
{
mmioClose(*phmmio, 0);
*phmmio = NULL;
}

return(0);

}

/*      This routine will create a wave file for writing.  This will automatically overwrite any
existing file with the same name, so be careful and check before hand!!!
pszFileName     - Pointer to filename to write.
phmmioOut               - Pointer to HMMIO handle that is used for further writes
pwfxDest                - Valid waveformatex destination structure.
pckOut                  - Pointer to be set with the MMCKINFO.
pckOutRIFF              - Pointer to be set with the RIFF info.

*/
int WaveCreateFile(
TCHAR*pszFileName,                              // (IN)
HMMIO *phmmioOut,                               // (OUT)
WAVEFORMATEX *pwfxDest,                 // (IN)
MMCKINFO *pckOut,                               // (OUT)
MMCKINFO *pckOutRIFF                    // (OUT)

)
{

int             nError;                         // Return value.
DWORD           dwFactChunk;            // Contains the actual fact chunk. Garbage until WaveCloseWriteFile.
MMCKINFO        ckOut1;

dwFactChunk = (DWORD)-1;
nError = 0;

*phmmioOut = mmioOpen(pszFileName, NULL,
MMIO_ALLOCBUF | MMIO_READWRITE|MMIO_CREATE);

    if (*phmmioOut == NULL)
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot save WAVE file
}

    /* Create the output file RIFF chunk of form type 'WAVE'.
     */
    pckOutRIFF->fccType = mmioFOURCC('W', 'A', 'V', 'E');
pckOutRIFF->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOutRIFF, MMIO_CREATERIFF)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* We are now descended into the 'RIFF' chunk we just created.
     * Now create the 'fmt ' chunk. Since we know the size of this chunk,
     * specify it in the MMCKINFO structure so MMIO doesn't have to seek
     * back and set the chunk size after ascending from the chunk.
     */
    pckOut->ckid = mmioFOURCC('f', 'm', 't', ' ');
    pckOut->cksize = sizeof(PCMWAVEFORMAT);   // we know the size of this ck.
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* Write the PCMWAVEFORMAT structure to the 'fmt ' chunk if its that type. */
    if (pwfxDest->wFormatTag == WAVE_FORMAT_PCM)
{
    if (mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(PCMWAVEFORMAT))
!= sizeof(PCMWAVEFORMAT))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}
}

else
{
// Write the variable length size.
if ((UINT)mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(*pwfxDest)+pwfxDest->cbSize)
!= (sizeof(*pwfxDest)+pwfxDest->cbSize))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

}

    /* Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

// Now create the fact chunk, not required for PCM but nice to have.  This is filled
// in when the close routine is called.
ckOut1.ckid = mmioFOURCC('f', 'a', 'c', 't');
ckOut1.cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, &ckOut1, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if (mmioWrite(*phmmioOut, (HPSTR)&dwFactChunk, sizeof(dwFactChunk)) != sizeof(dwFactChunk))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;
}

// Now ascend out of the fact chunk...
    if ((nError = mmioAscend(*phmmioOut, &ckOut1, 0)) != 0)
{
nError = ER_CANNOTWRITE;        // cannot write file, probably
goto ERROR_CANNOT_WRITE;
}

goto DONE_CREATE;

ERROR_CANNOT_WRITE:
// Maybe delete the half-written file?  Ah forget it for now, its good to leave the
// file there for debugging...

DONE_CREATE:
return(nError);

}

/*      This routine has to be called before any data is written to the wave output file, via wavewritefile.  This
sets up the data to write, and creates the data chunk.
*/

int WaveStartDataWrite(
HMMIO *phmmioOut,                               // (IN)
MMCKINFO *pckOut,                               // (IN)
MMIOINFO *pmmioinfoOut                  // (OUT)
)
{

int             nError;

nError = 0;
 /* Create the 'data' chunk that holds the waveform samples.  */
    pckOut->ckid = mmioFOURCC('d', 'a', 't', 'a');
pckOut->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if ((nError = mmioGetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

goto CLEANUP;
ERROR_CANNOT_WRITE:

CLEANUP:
return(nError);
}

/* This routine will write out data to a wave file.
hmmioOut                - Handle to hmmioOut filled by WaveCreateFile
cbWrite                 - # of bytes to write out.
pbSrc                   - Pointer to source.
pckOut                  - pointer to ckOut filled by WaveCreateFile
cbActualWrite   - # of actual bytes written.
pmmioinfoOut    - Pointer to mmioinfoOut filled by WaveCreateFile.

Returns 0 if successful, else the error code.

 */

int WaveWriteFile(
HMMIO hmmioOut,                         // (IN)
UINT cbWrite,                           // (IN)
BYTE *pbSrc,                            // (IN)
MMCKINFO *pckOut,                       // (IN)
UINT *cbActualWrite,            // (OUT)
MMIOINFO *pmmioinfoOut          // (IN)
)
{

int                     nError;
UINT            cT;

nError = 0;

*cbActualWrite = 0;

for (cT=0; cT < cbWrite; cT++)
{
if (pmmioinfoOut->pchNext == pmmioinfoOut->pchEndWrite)
{
pmmioinfoOut->dwFlags |= MMIO_DIRTY;
if ((nError = mmioAdvance(hmmioOut, pmmioinfoOut, MMIO_WRITE)) != 0)
{
    goto ERROR_CANNOT_WRITE;
}
}
*((BYTE*)pmmioinfoOut->pchNext)++ = *((BYTE*)pbSrc+cT);
(*cbActualWrite)++;
}

ERROR_CANNOT_WRITE:
// What to do here?  Well, for now, nothing, just return that error.  (maybe delete the
// file later?

return(nError);

}

/*      This routine will close a wave file used for writing.  Returns 0 if successful, else
the error code.
phmmioOut       - Pointer to mmio handle for saving.
pckOut          - Pointer to the MMCKINFO for saving.
pckOutRiff      - Pointer to the riff MMCKINFO for saving.
pmmioinfoOut- Pointer to mmioinfo for saving.
cSamples        - # of samples saved, for the fact chunk.  For PCM, this isn't used but
  will be written anyway, so this can be zero as long as programs ignore
  this field when they load PCM formats.

*/
int WaveCloseWriteFile(
HMMIO *phmmioOut,               // (IN)
MMCKINFO *pckOut,               // (IN)
MMCKINFO *pckOutRIFF,   // (IN)
MMIOINFO *pmmioinfoOut, // (IN)
DWORD cSamples                  // (IN)
)
{

int                     nError;

nError = 0;

if (*phmmioOut == NULL)
return(0);

pmmioinfoOut->dwFlags |= MMIO_DIRTY;
    if ((nError = mmioSetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
// cannot flush, probably...
goto ERROR_CANNOT_WRITE;
}

 /* Ascend the output file out of the 'data' chunk -- this will cause
     * the chunk size of the 'data' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

// Do this here instead...
if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

nError = mmioSeek(*phmmioOut, 0, SEEK_SET);
if ((nError = (int)mmioDescend(*phmmioOut, pckOutRIFF, NULL, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

nError = 0;
pckOut->ckid = mmioFOURCC('f', 'a', 'c', 't');
if ((nError = mmioDescend(*phmmioOut, pckOut, pckOutRIFF, MMIO_FINDCHUNK)) == 0)
{
// If it didn't fail, write the fact chunk out, if it failed, not critical, just
// assert (below).
    nError = mmioWrite(*phmmioOut, (HPSTR)&cSamples, sizeof(DWORD));
nError = mmioAscend(*phmmioOut, pckOut, 0);
nError = 0;
}
else
{
nError = 0;
ASSERT(FALSE);
}

// CANTWRITEFACT:
    /* Ascend the output file out of the 'RIFF' chunk -- this will cause
     * the chunk size of the 'RIFF' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

ERROR_CANNOT_WRITE:
if (*phmmioOut != NULL)
{
mmioClose(*phmmioOut, 0);
*phmmioOut = NULL;
}

return(nError);

}

/*      This routine will copy from a source wave file to a destination wave file all those useless chunks
(well, the ones useless to conversions, etc --> apparently people use them!).  The source will be
seeked to the begining, but the destination has to be at a current pointer to put the new chunks.
This will also seek     back to the start of the wave riff header at the end of the routine.

phmmioIn                - Pointer to input mmio file handle.
pckIn                   - Pointer to a nice ckIn to use.
pckInRiff               - Pointer to the main riff.
phmmioOut               - Pointer to output mmio file handle.
pckOut                  - Pointer to nice ckOut to use.
pckOutRiff              - Pointer to the main riff.

Returns 0 if successful, else the error code.  If this routine fails, it still attemps to seek back to
the start of the wave riff header, though this too could be unsuccessful.
*/

int WaveCopyUselessChunks(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRiff,
HMMIO *phmmioOut,
MMCKINFO *pckOut,
MMCKINFO *pckOutRiff)
{

int                             nError;

nError = 0;
// First seek to the stinking start of the file, not including the riff header...
if ((nError = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
nError = ER_CANNOTREAD;
goto ERROR_IN_PROC;
}

nError = 0;

    while (mmioDescend(*phmmioIn, pckIn, pckInRiff, 0) == 0)
    {

//  quickly check for corrupt RIFF file--don't ascend past end!
if ((pckIn->dwDataOffset + pckIn->cksize) > (pckInRiff->dwDataOffset + pckInRiff->cksize))
    goto ERROR_IN_PROC;

switch (pckIn->ckid)
{
    //  explicitly skip these...
    case mmioFOURCC('f', 'm', 't', ' '):
break;

    case mmioFOURCC('d', 'a', 't', 'a'):
break;

    case mmioFOURCC('f', 'a', 'c', 't'):
break;

    case mmioFOURCC('J', 'U', 'N', 'K'):
break;

    case mmioFOURCC('P', 'A', 'D', ' '):
break;

    case mmioFOURCC('c', 'u', 'e', ' '):
break;

    //  copy chunks that are OK to copy
    case mmioFOURCC('p', 'l', 's', 't'):
// although without the 'cue' chunk, it doesn't make much sense
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    case mmioFOURCC('D', 'I', 'S', 'P'):
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    //  don't copy unknown chunks
    default:
break;
}

//  step up to prepare for next chunk..
mmioAscend(*phmmioIn, pckIn, 0);
    }

ERROR_IN_PROC:
{
int nErrorT;
// Seek back to riff header
nErrorT = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET);
}

return(nError);
}

/** BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
 *
 *  DESCRIPTION:
 *
 *
 *  ARGUMENTS:
 *      (LPWAVECONVCB lpwc, LPMMCKINFO lpck)
 *
 *  RETURN (BOOL NEAR PASCAL):
 *
 *
 *  NOTES:
 *
 **  */

BOOL riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
{
    MMCKINFO    ck;
    HPSTR       hpBuf;

    //
    //
    //
    hpBuf = (HPSTR)GlobalAllocPtr(GHND, lpck->cksize);
    if (!hpBuf)
return (FALSE);

    ck.ckid   = lpck->ckid;
    ck.cksize = lpck->cksize;
    if (mmioCreateChunk(hmmioDst, &ck, 0))
goto rscc_Error;

    if (mmioRead(hmmioSrc, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioWrite(hmmioDst, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioAscend(hmmioDst, &ck, 0))
goto rscc_Error;

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (TRUE);

rscc_Error:

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (FALSE);
} /* RIFFSupCopyChunk() */

/*      This routine loads a full wave file into memory.  Be careful, wave files can get
pretty big these days :).
szFileName      -       sz Filename
cbSize          -       Size of loaded wave (returned)
cSamples        -       # of samples loaded.
ppwfxInfo       -       Pointer to pointer to waveformatex structure.  The wfx structure
IS ALLOCATED by this routine!  Make sure to free it!
ppbData         -       Pointer to a byte pointer (globalalloc) which is allocated by this
routine.  Make sure to free it!

Returns 0 if successful, else the error code.
*/

int WaveLoadFile(
TCHAR*pszFileName,                      // (IN)
UINT *cbSize,                           // (OUT)
WAVEFORMATEX **ppwfxInfo,       // (OUT)
BYTE **ppbData                          // (OUT)
)
{

HMMIO                           hmmioIn;
MMCKINFO                        ckInRiff;
MMCKINFO                        ckIn;
int                                     nError;
UINT                            cbActualRead;

*ppbData = NULL;
*ppwfxInfo = NULL;
*cbSize = 0;

if ((nError = WaveOpenFile(pszFileName, &hmmioIn, ppwfxInfo, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

if ((nError = WaveStartDataRead(&hmmioIn, &ckIn, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

// Ok, size of wave data is in ckIn, allocate that buffer.
if ((*ppbData = (BYTE *)GlobalAlloc(GMEM_FIXED, ckIn.cksize)) == NULL)
{
nError = ER_MEM;
goto ERROR_LOADING;
}

if ((nError = WaveReadFile(hmmioIn, ckIn.cksize, *ppbData, &ckIn, &cbActualRead)) != 0)
{
goto ERROR_LOADING;
}

*cbSize = cbActualRead;
goto DONE_LOADING;

ERROR_LOADING:
if (*ppbData != NULL)
{
GlobalFree(*ppbData);
*ppbData = NULL;
}
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

DONE_LOADING:
// Close the wave file.
if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

return(nError);

}

/*      This routine saves a wave file in currently in memory.
pszFileName -   FileName to save to.  Automatically overwritten, be careful!
cbSize          -       Size in bytes to write.
cSamples        -       # of samples to write, used to make the fact chunk. (if !PCM)
pwfxDest        -       Pointer to waveformatex structure.
pbData          -       Pointer to the data.
*/

int WaveSaveFile(
TCHAR*pszFileName,                      // (IN)
UINT cbSize,                            // (IN)
DWORD cSamples,                         // (IN)
WAVEFORMATEX *pwfxDest,         // (IN)
BYTE *pbData                            // (IN)
)
{

HMMIO           hmmioOut;
MMCKINFO        ckOut;
MMCKINFO        ckOutRIFF;
MMIOINFO        mmioinfoOut;
UINT            cbActualWrite;
int                     nError;

if ((nError = WaveCreateFile(pszFileName, &hmmioOut, pwfxDest, &ckOut, &ckOutRIFF)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveStartDataWrite(&hmmioOut, &ckOut, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveWriteFile(hmmioOut, cbSize, pbData, &ckOut, &cbActualWrite, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveCloseWriteFile(&hmmioOut, &ckOut, &ckOutRIFF, &mmioinfoOut, cSamples)) != 0)
{
goto ERROR_SAVING;
}

ERROR_SAVING:

return(nError);

}


WAVE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wave.h
 *  Content:wave header
 *
 ***************************************************************************/
#ifndef __WAVE_INCLUDED__
#define __WAVE_INCLUDED__
#include <windows.h>

#ifdef __cplusplus
extern "C" {
#endif

#define WAVEVERSION 1

#ifndef ER_MEM
#define ER_MEM 0xe000
#endif

#ifndef ER_CANNOTOPEN
#define ER_CANNOTOPEN 0xe100
#endif

#ifndef ER_NOTWAVEFILE
#define ER_NOTWAVEFILE 0xe101
#endif

#ifndef ER_CANNOTREAD
#define ER_CANNOTREAD 0xe102
#endif

#ifndef ER_CORRUPTWAVEFILE
#define ER_CORRUPTWAVEFILE0xe103
#endif

#ifndef ER_CANNOTWRITE
#define ER_CANNOTWRITE0xe104
#endif

int WaveOpenFile(TCHAR*, HMMIO *, WAVEFORMATEX **, MMCKINFO *);
int WaveStartDataRead(HMMIO *, MMCKINFO *, MMCKINFO *);
int WaveReadFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *);
int WaveCloseReadFile(HMMIO *, WAVEFORMATEX **);

int WaveCreateFile(TCHAR*, HMMIO *, WAVEFORMATEX *, MMCKINFO *, MMCKINFO *);
int WaveStartDataWrite(HMMIO *, MMCKINFO *, MMIOINFO *);
int WaveWriteFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *, MMIOINFO *);
int WaveCloseWriteFile(HMMIO *, MMCKINFO *, MMCKINFO *, MMIOINFO *, DWORD);

int WaveLoadFile(TCHAR*, UINT *, WAVEFORMATEX **, BYTE **);
int WaveSaveFile(TCHAR*, UINT, DWORD, WAVEFORMATEX *, BYTE *);

int WaveCopyUselessChunks(HMMIO *, MMCKINFO *, MMCKINFO *, HMMIO *, MMCKINFO *, MMCKINFO *);
BOOL riffCopyChunk(HMMIO, HMMIO, const LPMMCKINFO);

#ifdef __cplusplus
}
#endif

#endif


README.TXT

DirectSound Streaming Test (dsstream)
-------------------------------------

This sample illustrates how to play a long sound using a streaming
DirectSoundBuffer.  Because of this design, it may not work correctly when
using very short sounds, like only 1 or 2 seconds long.  Those files are
more appropriate for static buffers; see DirectSound Mixing Test (dsshow)
for examples of how to code such buffers.

Open a .wav files using the File.Open dialog.  The UI of the sample only
allows you to open one file at a time; however, this is a limitation of the
sample, not a limitation of DirectSound.

There are buttons to Play and Stop the file, and a checkbox to indicate
whether the file should be looped or not.  Regardless of the checkbox,
the DirectSoundBuffer will be played with looping since a streaming buffer
is being used.

There are sliders to control the sound's frequency, pan, and volume settings.
In addition, there is a slider indicating the progress through the sound
data.

The last thing to know: you can toggle Options.Enumerate Drivers, which
will show you what objects DirectSoundEnumerate will list for you, and allow
you to select one.  This only happens when you start up Dsstream, so to see
the effect, select this menu item, exit Dsstream, and then run Dsstream
again.


DEBUG.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.h
 *  Content:debug header
 *
 ***************************************************************************/
#ifndef __DEBUG_INCLUDED__
#define __DEBUG_INCLUDED__
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "DSSTREAM"  // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef _WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);
    void WINAPI _Assert( char * szFile, int iLine );

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
    #define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__)
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
    #define ASSERT(x)
#endif

#ifdef __cplusplus
}
#endif
#endif  // __DEBUG_INCLUDED__


DSSTREAM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dsstream.c
 *  Content:   Illustrates streaming data from a disk WAVE file to a
 *             DirectSound secondary buffer for playback.
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>

#include "dsstream.h"
#include "wassert.h"

char szAppClass[] = "DSStreamWndClass";
char szAppName[]  = "DSStream";

char szAppTitle[64];
char szAppCaption[64];
char szPan[32];
char szVolume[32];
char szFrequency[32];
char szProgress[32];
char szOpenFilter[128];
char szOpenDLGTitle[64];

char szTemp[256];
char szDebug[128];
char szFileBuffer[MAX_PATH];
char szFileTitle[MAX_PATH];
char szCDStartPath[MAX_PATH];        // The path to start the Open dialog in

// Registry Key and Value names that allow us to retrive a path something like
// "C:\DXSDK\SDK\MEDIA", but matching the current install.
static const TCHAR gszRegKeyDirect3D[] = TEXT("Software\\Microsoft\\Direct3D");
static const TCHAR gszRegValueD3DPath[] = TEXT("D3D Path");

// Size of longest token below
#define MAX_TOKEN_LEN7

static TCHAR gszPlayToken[] = TEXT("PLAY");
static TCHAR gszLoopToken[] = TEXT("LOOP");
static TCHAR gszCloseToken[] = TEXT("CLOSE");
static TCHAR gszStickyToken[] = TEXT("STICKY");
static TCHAR gszGlobalToken[] = TEXT("GLOBAL");

LPDIRECTSOUNDlpDS = NULL;
LPDIRECTSOUNDBUFFERlpDSBStreamBuffer = NULL;
LPDIRECTSOUNDNOTIFY lpDirectSoundNotify = NULL;

WAVEINFOCAwiWave;

HWNDhWndMain, hWndPan, hWndPanText, hWndVol, hWndVolText, hWndFreqText;
HWNDhWndBar, hWndPlay, hWndStop, hWndLoopCheck, hWndFreq, hWndProg;
HWNDhWndProgText;

#ifdef DEBUG
HWNDhWndList;
#endif

HINSTANCEhInst;

static BOOLbFileOpen = FALSE, bPlaying = FALSE;
static BOOL     bEnumDrivers = FALSE, gfCloseOnDone = FALSE;
static UINT     uTimerID = 0, uLastPercent = 100;
static GUID     guID;
static DWORDgdwFocus = 0;

static BOOL InitApp( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void BuildTitleBarText( void );
// Call this in initialization to set the szCDStartPath[] global variable
static void GetMediaStartPath( void );
static void FillDataBuffer( void );
static void LoadFromCommandLine( LPSTR lpszCmd );
extern void UpdateProgressBar(void);
/******************************************************************************
 * WinMain()
 *
 * Entry point for all Windows programs - performs initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
    {
    MSG     msg;

    hInst = hInstance;

    /* Make sure the common controls are loaded for our use */
    InitCommonControls();

    if( !hPrevInstance )
        if( !InitApp( hInstance ))
            {
            ErrorMessageBox( IDS_ERROR_APPINIT, MB_ICONSTOP );
            return( FALSE );
    }

    if( !InitInstance( hInstance, nCmdShow ))
        {
        ErrorMessageBox( IDS_ERROR_INSTANCEINIT, MB_ICONSTOP );
        return( FALSE );
}

    // We know how to take exactly one file name from the command-line and open
    // it as a file to play.  We can also accept a couple command like /play,
    // /close, and /loop
    if( lpCmdLine[0] )
LoadFromCommandLine( lpCmdLine );

    while( GetMessage((LPMSG)&msg, NULL, 0, 0 ))
        {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
        }

    UnregisterClass( szAppClass, hInstance );
    return( msg.wParam );
    } /* End of WinMain() */

/*****************************************************************************/
/* InitApp()     */
/*      */
/*   Inits things that only need to be created once for the this application */
/* (like creating the window class).     */
/*****************************************************************************/
static BOOL InitApp( HINSTANCE hInstance )
    {
    WNDCLASSwc;

    /* Set up and register a window class */
    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName= szAppClass;
    wc.lpfnWndProc= (WNDPROC)MainWindowProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= sizeof( DWORD );
    wc.hInstance= hInstance;
    wc.hIcon            = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON1 ));
    wc.hCursor= LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground= (HBRUSH)(COLOR_WINDOW);
    wc.lpszMenuName= MAKEINTRESOURCE( IDR_MAINMENU );

    if( !RegisterClass( &wc ))
        {
ErrorMessageBox( IDS_ERROR_REGISTERCLASS, MB_ICONSTOP );
        return( FALSE );
        }
    return( TRUE );
    } /* End of InitApp() */

/*****************************************************************************/
/* InitInstance()     */
/*      */
/* Performs initialization that must be done for each application instance.  */
/*      */
/*****************************************************************************/
static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
    {
    HWNDhWnd;
    HRESULTdsRetVal;
    RECTcrect;
    intcx, cy;
    UINTuCharsRead;
    BOOLfUseGuid = FALSE;
    HMENUhSysMenu;

    DbgInitialize( TRUE );

    // Initialize the Media start path for common open boxes
    GetMediaStartPath();

    LoadString( hInstance, IDS_APP_TITLE, szAppTitle, sizeof(szAppTitle));
    LoadString( hInstance, IDS_APP_CAPTION, szAppCaption, sizeof(szAppCaption));
    LoadString( hInstance, IDS_TBTITLE_PAN, szPan, sizeof(szPan));
    LoadString( hInstance, IDS_TBTITLE_VOLUME, szVolume, sizeof(szVolume));
    LoadString( hInstance, IDS_TBTITLE_FREQUENCY,
        szFrequency, sizeof(szFrequency));
    LoadString( hInstance, IDS_TBTITLE_PROGRESS,
        szProgress, sizeof(szProgress));
    LoadString( hInstance, IDS_OPEN_DLGTITLE,
    szOpenDLGTitle, sizeof(szOpenDLGTitle));
/* This is a little trick designed to allow us to load a common dialog box
 * filter string, which is really a concatentation of several NULL-terminated
 * strings. Note that while is is possible to enter something else into the
 * resource as placeholders for the NULL characters, this has the undesireable
 * effect of forcing us to search-and-replace byte-by-byte and doesn't make it
 * as easy to internationalize our strings...
 */
    memset( szOpenFilter, 0, sizeof(szOpenFilter));
    uCharsRead = LoadString( hInstance, IDS_OPEN_FILTER1,
szOpenFilter, sizeof(szOpenFilter)) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER2,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead ) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER3,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead ) + 1;
    LoadString( hInstance, IDS_OPEN_FILTER4,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead );

    /* Calculate the size of the client window */
    cx = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX + BUTTON_CX + PAN_TB_CX
+ 2*GetSystemMetrics( SM_CXBORDER ) + PAN_TEXT_CX + TEXT_SPACE_CX;

    cy = 2*(BORDER_SPACE_CY + GetSystemMetrics( SM_CYBORDER ))
+ PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY + PROG_TB_CY
+ GetSystemMetrics( SM_CYMENU ) + 3*CONTROL_SPACE_CY
+ GetSystemMetrics( SM_CYCAPTION );

    /* Create an application window */
#ifdef DEBUG
    hWnd = CreateWindow( szAppClass,/* class name */
szAppCaption,/* caption for window */
WS_OVERLAPPEDWINDOW/* style */
 & ~WS_THICKFRAME | WS_BORDER,
CW_USEDEFAULT,/* x position */
CW_USEDEFAULT,/* y position */
cx,/* width */
cy + 200,               /* height */
NULL,/* parent window */
NULL,/* menu */
hInstance,/* instance */
NULL );/* parms */
#else
    hWnd = CreateWindow( szAppClass,/* class name */
szAppCaption,/* caption for window */
WS_OVERLAPPEDWINDOW/* style */
 & ~WS_THICKFRAME | WS_BORDER,
CW_USEDEFAULT,/* x position */
CW_USEDEFAULT,/* y position */
cx,/* width */
cy,/* height */
NULL,/* parent window */
NULL,/* menu */
hInstance,/* instance */
NULL );/* parms */
#endif

    if( !hWnd )
        {
ErrorMessageBox( IDS_ERROR_MAINWNDCREATE, MB_ICONSTOP );
        return( FALSE );
}

    hWndMain = hWnd;
    GetClientRect( hWndMain, &crect );

    // Apparently WM_INITMENU isn't called for the main window's context version
    // of the system menu, so we must gray these at startup as well.
    hSysMenu = GetSystemMenu( hWndMain, FALSE );
    EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );

#ifdef DEBUG
    cy = 2*BORDER_SPACE_CY + PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY + PROG_TB_CY
    + 3*CONTROL_SPACE_CY;

    hWndList = CreateWindow( "listbox", NULL, WS_CHILD | WS_VISIBLE
    | LBS_NOINTEGRALHEIGHT | WS_VSCROLL,
0, cy, crect.right-crect.left,
crect.bottom - crect.top - cy,
hWnd, NULL, hInstance, NULL );
#endif

    /* Create some controls for things like volume, panning, etc. */
    if( CreateChildren( crect ))
        return( FALSE );

    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    /* Create the main DirectSound object */

    bEnumDrivers = GetProfileInt( "DSSTREAM", "EnumDrivers", FALSE );
    if( bEnumDrivers && !DoDSoundEnumerate( &guID ))
{
fUseGuid = TRUE;
}
    dsRetVal = DirectSoundCreate( fUseGuid ? &guID : NULL, &lpDS, NULL );

    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSCREATE, MB_ICONSTOP );
        return( FALSE );
        }

    dsRetVal = lpDS->lpVtbl->SetCooperativeLevel( lpDS,
                                                hWndMain,
                                                DSSCL_NORMAL );
    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSCOOPERATIVE, MB_ICONSTOP );
        return( FALSE );
        }

    return( TRUE );
    } /* End of InitInstance() */

/****************************************************************************/
/* MainWindowProc()                                                         */
/*                                                                          */
/*    Messages for our main window are handled here                         */
/*                                                                          */
/****************************************************************************/
LRESULT CALLBACK MainWindowProc( HWND hWnd, unsigned uMsg,
WPARAM wParam, LPARAM lParam )
    {
#ifndef DEBUG
    LPMINMAXINFO lpMinMax;
#endif
    DWORD   dwCDErr = 0, dwProg;
    float   fPercent;
    UINT    uPercent;
    BOOL    bResult = FALSE;
    int    nChkErr;
    HRESULT dsrval;

    switch( uMsg )
        {
        case WM_DSSTREAM_PROGRESS:
            dwProg = (DWORD)lParam;
    dwProg  = dwProg % wiWave.mmckInRIFF.cksize;
            fPercent = (float)((dwProg * 100)/ wiWave.mmckInRIFF.cksize);
            SendMessage( hWndProg, TBM_SETPOS,TRUE, (DWORD)(float)(fPercent*(float)PROG_MULTIPLIER));
            uPercent = (UINT)fPercent;
wsprintf( szTemp, "%s: %u%%", szProgress, uPercent );
Static_SetText( hWndProgText, szTemp );
break;

         /*This message will be posted by the helper DLL when the TimeFunc
         * is done streaming the WAVE file. It serves as notification that the
         * caller should terminate WAVE playback and end the MM timer event.
         */
        case WM_DSSTREAM_DONE:
            /* Emulate a WM_COMMAND to ourselves */
DPF(0, "received a dsstream_done message");
            PostMessage( hWnd, WM_COMMAND, MAKEWPARAM( IDM_STOP, 0 ), 0L );
            break;

#ifdef DEBUG
        case WM_DSSTREAM_DEBUG:
            if( LOWORD(wParam) == DEBUGF_PLAYPOSITION )
                {
                wsprintf( szDebug, "pp = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_WRITEPOSITION )
                {
                wsprintf( szDebug, "wp = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_NEXTWRITE )
                {
                wsprintf( szDebug, "nw = %li", lParam );
                ListBox_AddString( hWndList, szDebug );
                DPF( 4, szDebug );
                }
            else if( LOWORD(wParam) == DEBUGF_SKIP )
                {
                ListBox_AddString( hWndList, "Skipped segment read" );
                DPF( 5, szDebug );
                }
            break;
#endif

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDM_FILE_OPEN:
                    {
                    OPENFILENAMEofn;
/*
 * Clear out and fill in an OPENFILENAME structure in preparation
 * for creating a common dialog box to open a file.
 */
                    memset( &ofn, 0, sizeof(OPENFILENAME));
                    ofn.lStructSize= sizeof(OPENFILENAME);
                    ofn.hwndOwner= hWnd;
                    ofn.hInstance= hInst;
                    ofn.lpstrFilter= szOpenFilter;
                    ofn.nFilterIndex= 1;
                    szFileBuffer[0]= '\0';
                    ofn.lpstrFile= szFileBuffer;
                    ofn.nMaxFile= sizeof(szFileBuffer);
ofn.lpstrFileTitle= szFileTitle;
ofn.nMaxFileTitle= sizeof(szFileTitle);
 ofn.lpstrInitialDir = szCDStartPath;
                    ofn.lpstrDefExt= "WAV";
                    ofn.lpstrTitle= szOpenDLGTitle;
                    ofn.Flags= OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

                    bResult = GetOpenFileName( &ofn ); /* Do the dialog box */
/*
 *A return of TRUE indicates that the user did not select a filename.
 * The possible reasons are: Cancel was clicked, or an error occured.
 * If Cancel was clicked, the CommDlgExtendedError() function will not
 * return a valid code.  For anything else, an error code will come back.
 */
                    if( bResult == FALSE )
                        {
                        dwCDErr = CommDlgExtendedError();
                        if( dwCDErr )
                            {
                            /* Handle a common dialog box error */
                            HandleCommDlgError( dwCDErr );
                            }
                        else/* Clicked Cancel, so finish msg processing */
                            return( 0 );
                        }
                    else
                        {
// Copy the directory name we opened from so that we start there
// next time we open the dialog box...
lstrcpy( szCDStartPath, szFileBuffer );
szCDStartPath[ofn.nFileOffset] = '\0';

                        if( bFileOpen )
                            {
// Before we force a close, disable auto close
// because the user has picked a new file and is
// obviously not ready for us to go away yet.
    gfCloseOnDone = FALSE;
/* Need to close the previous file before we open a new one.  The best
 * way to do this is by faking a menu command, so that we only have the
 * actual code in one place and it can easily be changed.
 */
                            SendMessage( hWnd, WM_COMMAND,
                            MAKEWPARAM( IDM_FILE_CLOSE, 0 ), 0L );
                            }

if(( nChkErr = StreamBufferSetup()) != 0 )
                            {
            // Error opening the WAVE file so abort
    break;
                            }
                        else
                            {
                            bFileOpen = TRUE;
                            EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
                            MF_BYCOMMAND | MF_ENABLED );
                            EnableWindow( hWndPlay, TRUE );
                            EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
                            MF_BYCOMMAND | MF_ENABLED );
                            DrawMenuBar( hWnd );
BuildTitleBarText();
                            }
                        }
                    }
                    break;

                case IDM_FILE_CLOSE:
                    SendMessage( hWnd, WM_COMMAND,
                    MAKEWPARAM( IDM_STOP,
                                            DSSTREAM_STOPF_NOREOPEN ), 0L );
    BuildTitleBarText();
                    break;

                case IDM_OPTIONS_ENUMDRIVERS:
                    bEnumDrivers = !bEnumDrivers;
                    if( bEnumDrivers )
                        {
                        LoadString( hInst, IDS_ENUMWARNING, szTemp, sizeof(szTemp));
                        MessageBox( hWnd, szTemp, szAppCaption, MB_OK );
                        }
                    break;

                case IDM_HELP_ABOUT:
                    DialogBox( hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndMain,
                    (DLGPROC)DLG_About );
                    break;

                case IDC_LOOPCHECK:
                    wiWave.bLoopFile = !wiWave.bLoopFile;
                    Button_SetCheck( hWndLoopCheck, wiWave.bLoopFile );
                    if( !bPlaying && bFileOpen )
                        ResetWavePlayer();
                    break;

                case IDM_PLAY:
DPF(0, "In idm_play");
                    if( bPlaying )
    {
gfCloseOnDone = FALSE;
SendMessage( hWnd, WM_COMMAND, MAKEWPARAM( IDM_STOP, 0 ), 0L );
}

                    if( bFileOpen && lpDSBStreamBuffer )
                    {
dsrval = SetupNotifications();
if (dsrval != DS_OK)
{
DPF(0, "In Play command, setupnotifications failed");
break;
}

                        // Ensure that position is at 0, ready to go
                        dsrval = lpDSBStreamBuffer->lpVtbl->SetCurrentPosition(lpDSBStreamBuffer, 0 );
ASSERT(dsrval == DS_OK);
#ifdef DEBUG
{
DWORD dwWrite, dwPlay;
dsrval = lpDSBStreamBuffer->lpVtbl->GetCurrentPosition(lpDSBStreamBuffer, &dwPlay, &dwWrite);
ASSERT(dsrval == DS_OK);
if (dwPlay != 0)
DPF(0, "Couldn't set pos to 0");
}
#endif
wiWave.bDonePlaying = FALSE;
DPF(0, "calling play()");
                        dsrval = lpDSBStreamBuffer->lpVtbl->Play( lpDSBStreamBuffer,0, 0, DSBPLAY_LOOPING );
                    }
                    else
                    {
                        bPlaying = FALSE;
                        break;
                    }

                    bPlaying = TRUE;
EnableWindow(hWndPlay,FALSE);
EnableMenuItem(GetMenu( hWnd ), IDM_PLAY, MF_BYCOMMAND | MF_GRAYED );
                    EnableMenuItem(GetMenu( hWnd ), IDM_STOP, MF_BYCOMMAND | MF_ENABLED );
                    EnableWindow( hWndStop, TRUE );
                    DrawMenuBar( hWnd );
                    break;

                case IDM_STOP:
DPF(0, "received a idm_stop");
if (gfCloseOnDone)
wiWave.bDonePlaying = TRUE;
                    if( bPlaying )
                        {
bPlaying = FALSE;
dsrval = lpDSBStreamBuffer->lpVtbl->Stop( lpDSBStreamBuffer );
EnableMenuItem(GetMenu( hWnd ), IDM_STOP,MF_BYCOMMAND | MF_GRAYED );
EnableWindow(hWndStop, FALSE );
DrawMenuBar( hWnd );
                        }
// Short circuit to allow command-line forced shutdown
if(!( HIWORD(wParam) & DSSTREAM_STOPF_NOREOPEN ) && !gfCloseOnDone )
{
ResetWavePlayer();
            EnableMenuItem(GetMenu( hWnd ), IDM_PLAY, MF_BYCOMMAND | MF_ENABLED );
EnableWindow(hWndPlay, TRUE);
DrawMenuBar( hWnd );
break;
}
                    else
                        {
                        if( bFileOpen )
                            {
DPF(0, "In Stop: Closing read file");
WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
if (lpDirectSoundNotify)
dsrval = lpDirectSoundNotify->lpVtbl->Release(lpDirectSoundNotify);
lpDirectSoundNotify = NULL;

if( lpDSBStreamBuffer )
dsrval = lpDSBStreamBuffer->lpVtbl->Release(lpDSBStreamBuffer );
lpDSBStreamBuffer = NULL;

bFileOpen = FALSE;
// The file is closed, so disable the close option
EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
MF_BYCOMMAND | MF_GRAYED );
EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
MF_BYCOMMAND | MF_GRAYED );
EnableWindow( hWndPlay, FALSE );
DrawMenuBar( hWnd );
PostMessage( hWnd, WM_DSSTREAM_PROGRESS, 0L, 0L );
if( gfCloseOnDone &&
!(HIWORD(wParam) && DSSTREAM_STOPF_NOEXIT))
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDM_FILE_EXIT, 0 ), 0L );
                            }
            }
    break;

                case IDM_FILE_EXIT:
                    DestroyWindow( hWnd );
                    break;
                }
            break;
#ifndef DEBUG
        case WM_GETMINMAXINFO:
    /*
     * We know exactly how big this window should be, and it's sort of a
     * little pop-up control panel, so we can disable window sizing by
     * forcing all the minimum and maximum sizes to be the calculated size.
     */
            lpMinMax = (LPMINMAXINFO)lParam;

            lpMinMax->ptMaxSize.x = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX
                                    + BUTTON_CX + PAN_TB_CX + PAN_TEXT_CX
                                    + TEXT_SPACE_CX
                                    + 2*GetSystemMetrics( SM_CXBORDER );
            lpMinMax->ptMaxSize.y = 2*(BORDER_SPACE_CY
                                    + GetSystemMetrics( SM_CYBORDER ))
                                    + PAN_TB_CY + VOL_TB_CY + FREQ_TB_CY
                                    + PROG_TB_CY + 3*CONTROL_SPACE_CY
                                    + GetSystemMetrics( SM_CYMENU )
                                    + GetSystemMetrics( SM_CYCAPTION );

            lpMinMax->ptMinTrackSize.x = lpMinMax->ptMaxTrackSize.x
                            = lpMinMax->ptMaxSize.x;

            lpMinMax->ptMinTrackSize.y = lpMinMax->ptMaxTrackSize.y
                            = lpMinMax->ptMaxSize.y;
            break;
#endif
        case WM_HSCROLL:
            if(((HWND)lParam == hWndPan) && lpDSBStreamBuffer )
                {
                HandlePanScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            else if(((HWND)lParam == hWndVol) && lpDSBStreamBuffer )
                {
                HandleVolScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            else if(((HWND)lParam == hWndFreq) && lpDSBStreamBuffer )
                {
                HandleFreqScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            break;

        case WM_INITMENU:
    {
    HMENUhSysMenu = GetSystemMenu( hWnd, FALSE );

    EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );

    if((HMENU)wParam != GetMenu( hWnd ))
                break;
            CheckMenuItem((HMENU)wParam, IDM_OPTIONS_ENUMDRIVERS,
                                bEnumDrivers ? MF_CHECKED : MF_UNCHECKED );
    }
            break;

        case WM_DESTROY:
/*
 * Free all the DirectSound objects we created
 */
    SendMessage( hWnd, WM_COMMAND,
                          MAKEWPARAM( IDM_STOP, DSSTREAM_STOPF_NOREOPEN
| DSSTREAM_STOPF_NOEXIT ), 0 );

            if( lpDS ) dsrval = lpDS->lpVtbl->Release( lpDS );

            WriteProfileString( "DSSTREAM", "EnumDrivers",
                                        bEnumDrivers ? "1" : "0" );

            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
        }
    return 0L;
    } /* WindowProc */

/*****************************************************************************/
/* DLG_About()                                                               */
/*                                                                           */
/*   Dialog procedure for the Help...About... box which simply pops up a     */
/* little copyright message and brief program description.                   */
/*                                                                           */
/*****************************************************************************/
BOOL CALLBACK DLG_About( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
    {
    switch( msg )
        {
        case WM_INITDIALOG:
            break;

        case WM_COMMAND:
            switch( LOWORD(wParam))
                {
                case IDOK:
                    EndDialog( hDlg, FALSE );
                    return( TRUE );

                default:
                    break;
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }

/*****************************************************************************/
/* HandleCommDlgError()                                                      */
/*                                                                           */
/*    The function translates extended common dialog error codes into a      */
/* string resource ID, loads that string from our module, and displays it in */
/* a message box. This implementation only covers the general CD error codes.*/
/*                                                                           */
/*****************************************************************************/
int HandleCommDlgError( DWORD dwError )
    {
    char szTitle[128];
    UINT uMsgID;

    if( dwError == CDERR_DIALOGFAILURE )
        uMsgID = IDS_CDERR_DIALOGFAILURE;
    else
        uMsgID = (UINT)dwError + IDS_CDERR_GENERAL_BASE;

    LoadString( hInst, uMsgID, szTemp, sizeof(szTemp));
    LoadString( hInst, IDS_CDERR_TITLESTRING, szTitle, sizeof(szTitle));
    MessageBox( GetActiveWindow(), szTemp, szTitle,
                    MB_OK | MB_ICONEXCLAMATION );

    return( 0 );
    }

/*****************************************************************************/
/* StreamBufferSetup()                                                       */
/*                                                                           */
/* This function uses the filename stored in the global character array to*/
/* open a WAVE file. Then it creates a secondary DirectSoundBuffer object    */
/* which will later be used to stream that file from disk during playback.   */
/*                                                                           */
/*****************************************************************************/
int StreamBufferSetup( void )
    {
    DSBUFFERDESC dsbd;
    HRESULT      dsRetVal;

    int nChkErr;
int nRem;

    /* This portion of the WAVE I/O is patterned after what's in DSTRWAVE, which
     * was in turn adopted from WAVE.C which is part of the DSSHOW sample.
     */

    if(( nChkErr = WaveOpenFile( szFileBuffer, &wiWave.hmmio, &wiWave.pwfx, &wiWave.mmckInRIFF )) != 0 )
        {
        ErrorMessageBox( IDS_ERROR_WAVEFILEOPEN, MB_ICONEXCLAMATION );
        return( ERR_WAVE_OPEN_FAILED );
        }

    if( wiWave.pwfx->wFormatTag != WAVE_FORMAT_PCM )
        {
        ErrorMessageBox( IDS_ERROR_WAVENOTPCM, MB_ICONEXCLAMATION );
        WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
        return( ERR_WAVE_INVALID_FORMAT );
}
    // Seek to the data chunk. mmck.ckSize will be the size of all the data in the file.
    if(( nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF )) != 0 )
        {
        ErrorMessageBox( IDS_ERROR_WAVESEEKFAILED, MB_ICONEXCLAMATION );
        WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
        return( ERR_WAVE_CORRUPTED_FILE );
        }

    // Calculate a buffer length 3 sec. long. This should be an integral number of the
// number of bytes in one notification period.
wiWave.dwNotifySize = wiWave.pwfx->nSamplesPerSec * 3 * (DWORD)wiWave.pwfx->nBlockAlign;
wiWave.dwNotifySize = wiWave.dwNotifySize/NUM_PLAY_NOTIFICATIONS;

// the notify size should be an intergral multiple of the nBlockAlignvalue.
if ((nRem = wiWave.dwNotifySize%(DWORD)wiWave.pwfx->nBlockAlign) != 0)
{
wiWave.dwNotifySize += (wiWave.pwfx->nBlockAlign - nRem);
}
wiWave.dwBufferSize = wiWave.dwNotifySize * NUM_PLAY_NOTIFICATIONS;
    DPF(0, "BufferSize = %lu ", wiWave.dwBufferSize);

#ifdef DEBUG
    wsprintf( szDebug, "BufferSize = %lu", wiWave.dwBufferSize );
    ListBox_AddString( hWndList, szDebug );
#endif

     //Create the secondary DirectSoundBuffer object to receive our sound data.
    memset( &dsbd, 0, sizeof( DSBUFFERDESC ));
    dsbd.dwSize = sizeof( DSBUFFERDESC );
    // Use new GetCurrentPosition() accuracy (DirectX 2 feature)
    dsbd.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY| DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2 | gdwFocus;
    dsbd.dwBufferBytes = wiWave.dwBufferSize;

    //Set Format properties according to the WAVE file we just opened
    dsbd.lpwfxFormat = wiWave.pwfx;

dsRetVal = lpDS->lpVtbl->CreateSoundBuffer( lpDS, &dsbd,&lpDSBStreamBuffer,NULL );
    if( dsRetVal != DS_OK )
        {
        ErrorMessageBox( IDS_ERROR_DSBCREATE, MB_ICONEXCLAMATION );
        return( ERR_CREATEDSB_FAILED );
        }

    wiWave.lpDSBStreamBuffer = lpDSBStreamBuffer;
    wiWave.bFoundEnd = FALSE;
wiWave.dwNextWriteOffset = 0;
    wiWave.bLoopFile = Button_GetCheck( hWndLoopCheck );

// now get the pointer to the notification interface.
dsRetVal = IDirectSoundNotify_QueryInterface(lpDSBStreamBuffer, &IID_IDirectSoundNotify, &((LPVOID)lpDirectSoundNotify));
if (dsRetVal != DS_OK)
{
ErrorMessageBox(IDS_ERROR_QINOTIFY_FAILED, MB_ICONEXCLAMATION);
    return (dsRetVal);
    }

// Fill data in the buffer.
    FillDataBuffer();

// we're set to play now.
    wiWave.bDonePlaying = FALSE;

#ifdef DEBUG
    wsprintf( szDebug, "wiWave.dwBufferSize = %lu", wiWave.dwBufferSize );
    DPF( 3, "StreamBufferSetup Debug" );
    DPF( 3, szDebug );
#endif

    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    SendMessage( hWndFreq, TBM_SETPOS, TRUE,(LPARAM)wiWave.pwfx->nSamplesPerSec / FREQ_MULTIPLIER );
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    UpdateFromControls();
    return( 0 );
    }

/*****************************************************************************/
/* ResetWavePlayer()                                                         */
/*                                                                           */
/*  Performs a subset of the above operations (in StreamBufferSetup). Things */
/* not done include creating a DSB and opening the file (it's already open). */
/*                                                                           */
/*****************************************************************************/
void ResetWavePlayer( void )
    {
int nChkErr = DS_OK;
DPF(1, "Resetting wave player");
ASSERT(bPlaying == FALSE);

    /* Seek to the data chunk */
    if(( nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF )) != 0 )
        {
// This shouldn't happen, since we've done this before.
        DPF(0, "Error seeking to file in ResetWavePlayer");
return;
        }

    wiWave.bFoundEnd = FALSE;
wiWave.dwNextWriteOffset = 0;

DPF(0, "Reset: Filldatabuffer");
    FillDataBuffer();

    wiWave.bDonePlaying = FALSE;
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    }

/*****************************************************************************/
/* FillDataBuffer()                                                          */
/*                                                                           */
/*   This function fills the sound buffer with a block of data, starting at  */
/* the current read position.  The entire buffer is filled.                  */
/*                                                                           */
/*****************************************************************************/
 void FillDataBuffer( void )
 {
    LPBYTE  lpWrite1, lpWrite2;
    DWORDdwLen1, dwLen2;
    UINTuActualBytesWritten;
    intnChkErr;
    HRESULTdsRetVal;
DWORD dwBytes = wiWave.dwBufferSize;

// This is the initial read. So we fill the entire buffer.
// This will not wrap around so the 2nd pointers will be NULL.
    dsRetVal = lpDSBStreamBuffer->lpVtbl->Lock( lpDSBStreamBuffer, 0, dwBytes,
&((LPVOID)lpWrite1), &dwLen1,
&(LPVOID)lpWrite2, &dwLen2, 0 );
ASSERT(lpWrite2 == NULL);
ASSERT(dwLen2 == 0);

if (dwLen1 != dwBytes)
{
DPF(0, "FillDataBuffer: dwBytes != dwLen1");
}

    if( dsRetVal != DS_OK )
{
ASSERT(FALSE);
return;
}

ASSERT(dwLen1);
ASSERT( NULL != lpWrite1 );
ASSERT(wiWave.dwNextWriteOffset < wiWave.dwBufferSize);

    nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLen1, lpWrite1,
                            &wiWave.mmck, &uActualBytesWritten );
// if the number of bytes written is less than the
// amount we requested, we have a short file.
if (uActualBytesWritten < dwLen1)
{
DPF(0, "FillDataBuffer: Actual written is less than dwlen1");
if (!wiWave.bLoopFile)
{
// we set the bFoundEnd flag if the length is less than
// one notify period long which is when the first notification comes in.
// The next notification will then call send a message to process a stop.
if (uActualBytesWritten < wiWave.dwNotifySize)
wiWave.bFoundEnd = TRUE;
// Fill in silence for the rest of the buffer.
DPF(0, "Filling in silence");
FillMemory(lpWrite1+uActualBytesWritten, dwLen1-uActualBytesWritten,
(BYTE)(wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0));
}
else
{
// we are looping.
UINT uWritten = uActualBytesWritten;// from previous call above.
while (uWritten < dwLen1)
{// this will keep reading in until the buffer is full. For very short files.
nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF );
ASSERT(nChkErr == 0);// we've already this before so shouldn't fail.
nChkErr = WaveReadFile(wiWave.hmmio, (UINT)dwLen1-uWritten,
lpWrite1 + uWritten, &wiWave.mmck, &uActualBytesWritten);
uWritten += uActualBytesWritten;
} // while
ASSERT(wiWave.bFoundEnd == FALSE);
} // else
}

// now unlock the buffer.
dsRetVal = lpDSBStreamBuffer->lpVtbl->Unlock( lpDSBStreamBuffer, (LPVOID)lpWrite1, dwLen1,NULL, 0 );

wiWave.dwNextWriteOffset += dwLen1;
// this is a circular buffer. Do mod buffersize.
if (wiWave.dwNextWriteOffset >= wiWave.dwBufferSize)
wiWave.dwNextWriteOffset -= wiWave.dwBufferSize;

DPF(3, "Setting dwProgress = 0");
wiWave.dwProgress = 0;
wiWave.dwLastPos = 0;
}

/*****************************************************************************/
/* CreateChildren()                                                          */
/*                                                                           */
/*   This function creates a bunch of child controls for the main window.    */
/* Most of them are used for controling various things about a playing sound */
/* file, like volume and panning. Returns FALSE if no errors, TRUE otherwise.*/
/*                                                                           */
/*****************************************************************************/
int CreateChildren( RECT crect )
    {
    SIZE  Size;
    HDC   hDC;
    int  x, y;
    UINT  uType;
    char  szTemplate[128], szType[32];
    LPSTR lpszControl;

    LoadString( hInst, IDS_ERROR_CHILDTEMPLATE, szTemplate, sizeof(szTemplate));

    /* Don't handle failure for this one, because the app will still run fine */
    hWndBar = CreateWindow( "static", NULL,
                            WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
                            0, 0, crect.right, 2, hWndMain,
                            (HMENU)0, hInst, NULL );

    hDC = GetDC( hWndMain );
    if( !GetTextExtentPoint32( hDC, szPan, strlen(szPan), &Size ))
        {
        ErrorMessageBox( IDS_ERROR_GETTEXTEXTENT, MB_ICONEXCLAMATION );
        ReleaseDC( hWndMain, hDC );
        return( TRUE );
        }
    ReleaseDC( hWndMain, hDC );

    y = BORDER_SPACE_CY;

    /* STATIC control -- text label for the pan trackbar */
    if(( hWndPanText = CreateWindow( "static", szPan,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX + PAN_TB_CX + TEXT_SPACE_CX,
                                    y + (PAN_TB_CY - Size.cy)/2,
                                    PAN_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szPan;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
        }

    /* PAN (left to right balance) trackbar control */
    if(( hWndPan = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, PAN_TB_CX, PAN_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szPan;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    SendMessage( hWndPan, TBM_SETRANGE, FALSE, MAKELONG( PAN_MIN, PAN_MAX ));
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    SendMessage( hWndPan, TBM_SETPAGESIZE, 0L, PAN_PAGESIZE );

    y += PAN_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the volume trackbar */
    if(( hWndVolText = CreateWindow( "static", szVolume,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX + VOL_TB_CX + TEXT_SPACE_CX,
                                    y + (VOL_TB_CY - Size.cy)/2,
                                    VOL_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}

    /* Create the VOLUME trackbar */
    if(( hWndVol = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, VOL_TB_CX, VOL_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    SendMessage( hWndVol, TBM_SETRANGE, FALSE,
                                        MAKELONG( VOL_MIN, VOL_MAX ));
    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndVol, TBM_SETPAGESIZE, 0L, VOL_PAGESIZE );

    y += VOL_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the frequency trackbar */
    if(( hWndFreqText = CreateWindow( "static", szFrequency,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX + FREQ_TB_CX + TEXT_SPACE_CX,
                                    y + (FREQ_TB_CY - Size.cy)/2,
                                    FREQ_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szFrequency;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}

    /* Create the FREQUENCY trackbar */
    if(( hWndFreq = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
                                BORDER_SPACE_CX,
                                y, FREQ_TB_CX, FREQ_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szFrequency;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    SendMessage( hWndFreq, TBM_SETRANGE, FALSE, MAKELONG( FREQ_MIN, FREQ_MAX ));
    SendMessage( hWndFreq, TBM_SETPOS, TRUE, FREQ_MAX );
    SendMessage( hWndFreq, TBM_SETPAGESIZE, 0L, FREQ_PAGESIZE );

    y += FREQ_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the progress trackbar */
    if(( hWndProgText = CreateWindow( "static", szProgress,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX + PROG_TB_CX + TEXT_SPACE_CX,
                                    y + (PROG_TB_CY - Size.cy)/2,
                                    PROG_TEXT_CX, Size.cy,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szProgress;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}

    /* Create the PROGRESSS trackbar */
    if(( hWndProg = CreateWindow( TRACKBAR_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE | TBS_HORZ
| TBS_BOTTOM | WS_DISABLED,
                                BORDER_SPACE_CX,
                                y, PROG_TB_CX, PROG_TB_CY,
                                hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szProgress;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    SendMessage( hWndProg, TBM_SETRANGE,
    FALSE, MAKELPARAM( PROG_MIN, PROG_MAX ));
    SendMessage( hWndProg, TBM_SETPOS, TRUE, 0L );

    x = BORDER_SPACE_CX + PAN_TEXT_CX + TEXT_SPACE_CX
    + PAN_TB_CX + CONTROL_SPACE_CX;
    y += PROG_TB_CY;
    y -= 2*(BUTTON_CY + BUTTON_SPACE_CY) + CHECK_CY;

    /* Create the LOOPED CHECKBOX */
    LoadString( hInst, IDS_CHECK_LOOPED, szTemp, sizeof(szTemp));
    if(( hWndLoopCheck = CreateWindow( "button", szTemp,
                                WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
                                x, y, CHECK_CX, CHECK_CY, hWndMain,
                                (HMENU)IDC_LOOPCHECK, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_CHECK;
        goto DISPLAY_CREATE_FAILURE;
}
    y += CHECK_CY + BUTTON_SPACE_CY;

    /* Create the PLAY BUTTON */
    LoadString( hInst, IDS_BUTTON_PLAY, szTemp, sizeof(szTemp));
    if(( hWndPlay = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDM_PLAY, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
}
    y += BUTTON_CY + BUTTON_SPACE_CY;

    /* Create the STOP BUTTON */
    LoadString( hInst, IDS_BUTTON_STOP, szTemp, sizeof(szTemp));
    if(( hWndStop = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDM_STOP, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
}

    UpdateFromControls();
    goto RETURN_NORMAL;

DISPLAY_CREATE_FAILURE:
    LoadString( hInst, uType, szType, sizeof(szType));
    wsprintf( szTemp, szTemplate, lpszControl, szType );
    MessageBox( GetActiveWindow(), szTemp,
                        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    return( TRUE );

RETURN_NORMAL:
    return( FALSE );
    }

/********************************************************************************/
/* UpdateFromControls()                                                         */
/*                                                                              */
/*    This function gets all the required values from the DirectSoundBuffer and */
/* updates the screen interface controls.                                       */
/*                                                                              */
/********************************************************************************/
void UpdateFromControls( void )
    {
    longlPan, lVol, lFreq;
    HRESULT hr;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lFreq = (LONG)SendMessage( hWndFreq, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    /* Set the volume and then the pan */
    if( lpDSBStreamBuffer )
        {
        /* Set the volume */
        wsprintf( szTemp, "%s: %lidB", szVolume,
                                            ( lVol + VOL_SHIFT ) / VOL_DIV );
        Static_SetText( hWndVolText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetVolume( lpDSBStreamBuffer,
                                            (((lVol+VOL_SHIFT) * VOL_MULT)) );
        if( hr != 0 )
            DPF( 0, "Unable to SetVolume in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set volume to %lidB",
                                            ( lVol + VOL_SHIFT ) / VOL_DIV );
            DPF( 3, szDebug );
            }

        /* Set the Pan */
        wsprintf( szTemp, "%s: %lidB", szPan, ( lPan + PAN_SHIFT ) / PAN_DIV );
        Static_SetText( hWndPanText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetPan( lpDSBStreamBuffer,
                                            (((lPan+PAN_SHIFT) * PAN_MULT)) );
        if( hr != 0 )
            DPF( 0, "Unable to SetPan in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set pan to %lidB",
                                            ( lPan + PAN_SHIFT ) / PAN_DIV );
            DPF( 3, szDebug );
            }

        /* Set the frequency */
        wsprintf( szTemp, "%s: %liHz", szFrequency, lFreq * FREQ_MULTIPLIER );
        Static_SetText( hWndFreqText, szTemp );

        hr = lpDSBStreamBuffer->lpVtbl->SetFrequency( lpDSBStreamBuffer,
                                                        lFreq * FREQ_MULTIPLIER);
        if( hr != 0 )
            DPF( 0, "Unable to SetFrequency in UpdateFromControls()" );
        else
            {
            wsprintf( szDebug, "Set frequency to %liHz", lFreq*FREQ_MULTIPLIER );
            DPF( 3, szDebug );
            }
        }
        return;
    }

/********************************************************************************/
/* HandlePanScroll()                                                            */
/*                                                                              */
/*   Handles the pan trackbar scroll when a WM_HSCROLL is received.             */
/*                                                                              */
/********************************************************************************/
void HandlePanScroll( int nCode, int nPos )
    {
    long  lPan, lDelta;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEUP:
            if( lPan >= PAN_MIN - 1 )
                lDelta = -1;
            break;
        case TB_LINEDOWN:
            if( lPan <= PAN_MAX + 1 )
                lDelta = 1;
            break;
        case TB_PAGEUP:
            if( lPan >= PAN_MIN - PAN_PAGESIZE )
                lDelta = -16;
            break;
        case TB_PAGEDOWN:
            if( lPan <= PAN_MAX + PAN_PAGESIZE )
                lDelta = 16;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndPan, TBM_SETPOS, TRUE, lPan + lDelta );
    else
        SendMessage( hWndPan, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }

/********************************************************************************/
/* HandleVolScroll()                                                            */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleVolScroll( int nCode, int nPos )
    {
    long  lVol, lDelta;

    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEDOWN:
            if( lVol <= VOL_MAX - 1 )
                lDelta = 1;
            break;
        case TB_LINEUP:
            if( lVol >= VOL_MIN + 1 )
                lDelta = -1;
            break;
        case TB_PAGEDOWN:
            if( lVol <= VOL_MAX - VOL_PAGESIZE )
                lDelta = 10;
            break;
        case TB_PAGEUP:
            if( lVol >= VOL_MIN + VOL_PAGESIZE )
                lDelta = -10;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (lVol + lDelta));
    else
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }

/********************************************************************************/
/* HandleFreqScroll()                                                           */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleFreqScroll( int nCode, int nPos )
    {
    long  lFreq, lDelta;

    lFreq = (LONG)SendMessage( hWndFreq, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEDOWN:
            if( lFreq <= FREQ_MAX-1 )
                lDelta = 1;
            break;
        case TB_LINEUP:
            if( lFreq >= FREQ_MIN+1 )
                lDelta = -1;
            break;
        case TB_PAGEDOWN:
            if( lFreq <= FREQ_MAX - FREQ_PAGESIZE )
                lDelta = 10;
            break;
        case TB_PAGEUP:
            if( lFreq >= FREQ_MIN + FREQ_PAGESIZE )
                lDelta = -10;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndFreq, TBM_SETPOS, TRUE, (lFreq + lDelta));
    else
        SendMessage( hWndFreq, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }

/****************************************************************************/
/* ErrorMessageBox()                                                        */
/*                                                                          */
/*   A little routine to load error messages from the string resource table */
/* and pop them up in a MessageBox() for the world to see. The dwMBFlags    */
/* parameter allows the caller to specify the type of icon to use.          */
/*                                                                          */
/****************************************************************************/
void ErrorMessageBox( UINT uID, DWORD dwMBFlags )
    {
    LoadString( hInst, uID, szTemp, sizeof(szTemp));
    MessageBox( GetActiveWindow(), szTemp, szAppTitle, MB_OK | dwMBFlags );
    }

/****************************************************************************/
/* BuildTitleBar()                                                          */
/*                                                                          */
/*   Small routine to build and set the title bar text depending on whether */
/* or not a file is open.                                                   */
/****************************************************************************/
void BuildTitleBarText( void )
    {
    char szTitle[ sizeof(szAppCaption) + MAX_PATH + sizeof(" - ")];

    lstrcpy( szTitle, szAppCaption );
    if( bFileOpen )
{
lstrcat( szTitle, " - " );
lstrcat( szTitle, szFileTitle );
}
    SetWindowText( hWndMain, szTitle );
    }

/****************************************************************************/
/* GetMediaStartPath()                                                      */
/*                                                                          */
/*   This helper function attempts to get the media directory for Direct3D, */
/* which is where all the installed DX wave files go. If it can't find that */
/* it settles for the media sub-directory of the Windows directory.         */
/****************************************************************************/
void GetMediaStartPath( void )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
gszRegKeyDirect3D,
0, KEY_READ, &hReg ))
{
        goto REG_OPEN_FAILED;
}
    else
{
// Query the Registry for the path to the media directory
cbStartPathLen = sizeof( szCDStartPath );
if( ERROR_SUCCESS != RegQueryValueEx( hReg, gszRegValueD3DPath,
NULL, NULL,
szCDStartPath, &cbStartPathLen ))
    {
    goto REG_OPEN_FAILED;
    }
RegCloseKey( hReg );
hReg = NULL;
}

    return;

REG_OPEN_FAILED:
    // Start off by getting the Windows directory -- we're trying to build a
    // file path like "C:\WINDOWS\MEDIA", but the WINDOWS directory could be
    // named anything, so we must ask.
    GetWindowsDirectory( szCDStartPath, sizeof(szCDStartPath));
    // If there's no trailing backslash, append one
    if( lstrcmp( &szCDStartPath[lstrlen(szCDStartPath)], TEXT("\\") ))
lstrcat( szCDStartPath, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( szCDStartPath, TEXT("MEDIA"));
    }

void LoadFromCommandLine( LPSTR lpszCmd )
    {
    LPSTR lpsz = lpszCmd;
    LPSTR lpToken;
    BOOL  fStartPlaying = FALSE, fStartLooping = FALSE;
    char  szToken[MAX_TOKEN_LEN+1];
    int   i;

    if( !lpsz )
return;

    // Clear leading spaces
    while( *lpsz == ' ' )
lpsz++;

    // If we need to accept more command-line parameters later, we can
    // extend the code below into a loop that searchs for each one.
    while( *lpsz == '/' || *lpsz == '-' )
{
// Don't advance lpsz until we're sure we really should be reading
// this string (i.e. we recognize that it's the play command
lpToken = ++lpsz;
for( i = 0; i < MAX_TOKEN_LEN; i++ )
    {
    if( !*lpToken || *lpToken == ' ' )
break;
    szToken[i] = *lpToken++;
    }
szToken[i] = 0;

if( !lstrcmpi( szToken, gszPlayToken ))
    {
    // Automagically start playing the file
    fStartPlaying = TRUE;
    lpsz = lpToken;
    }
else if( !lstrcmpi( szToken, gszLoopToken ))
    {
    // Set the player in looping mode at startup
    fStartLooping = TRUE;
    lpsz = lpToken;
    }
else if( !lstrcmpi( szToken, gszStickyToken ))
    {
    // Use Sticky Focus for the buffer
    gdwFocus = DSBCAPS_STICKYFOCUS;
    lpsz = lpToken;
    }
else if( !lstrcmpi( szToken, gszGlobalToken ))
    {
    // Use Global Focus for the buffer
    gdwFocus = DSBCAPS_GLOBALFOCUS;
    lpsz = lpToken;
    }
else if( !lstrcmpi( szToken, gszCloseToken ))
    {
    // "/close" will cause the program to shutdown after it's done
    // playing the file that was presumably loaded at the command-line
    gfCloseOnDone = TRUE;
    lpsz = lpToken;
    }
else
    {
    // Unrecognized parameter followed the slash, so skip over it
    // and find the next break
    while( *lpsz && *lpsz != ' ' )
lpsz++;
    }
// Clear any spaces out again
while( *lpsz == ' ' )
    lpsz++;
}

    // If that's all that was on the command-line, simply return
    if( !*lpsz )
return;

    // ASSUMPTION: We assume that a single filename is the only remaining
    // parameter.  This works out okay because anything else will fail in the
    // file load inside StreamBufferSetup();
    lstrcpy( szFileBuffer, lpsz );

    // Search backwards and find the last backslash, stopping at the
    // beginning of the file name
    lpsz = &szFileBuffer[lstrlen(szFileBuffer)];

    while( lpsz > szFileBuffer )
{
if( *(lpsz-1) == '\\' )
    {
    break;
    }
lpsz--;
}
    // Fake the szFileTitle, which normally gets set by the Common Dialog
    lstrcpy( szFileTitle, lpsz );
    lstrcpy( szCDStartPath, szFileBuffer );
    szCDStartPath[lpsz-szFileBuffer] = 0;

    if( fStartLooping )
{
// Allowing auto-close when the user will have plenty of time to click
// stop would cause the app to shutdown right as they hit the button,
// which is weird behavior.

gfCloseOnDone = FALSE;
Button_SetCheck( hWndLoopCheck, TRUE );
}

    if( StreamBufferSetup() != 0 )
{
// Error opening the WAVE file so abort
return;
}
    else
{
bFileOpen = TRUE;
EnableMenuItem( GetMenu( hWndMain ), IDM_PLAY,
MF_BYCOMMAND | MF_ENABLED );
EnableWindow( hWndPlay, TRUE );
EnableMenuItem( GetMenu( hWndMain ), IDM_FILE_CLOSE,
MF_BYCOMMAND | MF_ENABLED );
DrawMenuBar( hWndMain );
BuildTitleBarText();

if( fStartPlaying )
    SendMessage( hWndMain, WM_COMMAND, MAKEWPARAM( IDM_PLAY, 0 ), 0L );
}
    }

// ==========================================================
// SetupNotifications
//Sets notifications and also creates the events.
// ==========================================================
int SetupNotifications(void)
{
int hr = DS_OK;
DSBPOSITIONNOTIFY dsbPosNotify[NUM_PLAY_NOTIFICATIONS +1 ];
DWORD dwSize = wiWave.dwNotifySize;
DWORD dwThreadId;
int i;

// Create the 2 events. One for Play one for stop.
hNotifyEvent[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
ASSERT(hNotifyEvent[0]);
hNotifyEvent[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
ASSERT(hNotifyEvent[1]);

// setup the first one.
dsbPosNotify[0].dwOffset = dwSize;
dsbPosNotify[0].hEventNotify = hNotifyEvent[0];
DPF(0, "Set notifies for position %lu", dsbPosNotify[0].dwOffset);

for (i = 1; i < NUM_PLAY_NOTIFICATIONS; i++)
{
dsbPosNotify[i].dwOffset = dsbPosNotify[i-1].dwOffset + dwSize;
dsbPosNotify[i].hEventNotify = hNotifyEvent[0];
DPF(0, "Set notifies for positions %lu", dsbPosNotify[i].dwOffset);
}

dsbPosNotify[i-1].dwOffset -= 1;
// set the stop notification.
dsbPosNotify[i].dwOffset = DSBPN_OFFSETSTOP;
dsbPosNotify[i].hEventNotify = hNotifyEvent[1];

// Now create the thread to wait on the events created.
if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)HandleNotifications, NULL, 0, &dwThreadId) == NULL)
{
DPF(0, "CreateThread failed");
goto HandleErr;
}
else
DPF(0, "Created thread");

// setup notification
hr = lpDirectSoundNotify->lpVtbl->SetNotificationPositions(lpDirectSoundNotify,
NUM_PLAY_NOTIFICATIONS +1,
dsbPosNotify);

HandleErr:
if (hr != DS_OK)
{if (hr == DSERR_INVALIDPARAM)
DPF(0, "SetupNotificationPos failed. Invalid Param");
if (hr == DSERR_OUTOFMEMORY)
DPF(0, "SetupNotificationPos failed. OutOfMemory");
CloseHandle(hNotifyEvent[0]);
CloseHandle(hNotifyEvent[1]);
hNotifyEvent[0] = hNotifyEvent[1] = (HANDLE)NULL;
}
#ifdef DEBUG
else
{
DPF(3, "SetNotificationPos succeeded.");
}
#endif

return (hr);
}


DSSTREAM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:DSStream.h
 *  Content:DirectSound Stream Sample Application Header
 *
 ***************************************************************************/
#ifndef __DSSTREAM_INCLUDED__
#define __DSSTREAM_INCLUDED__

#include <dsound.h>
#include "resource.h"
#include "debug.h"

#define NUM_PLAY_NOTIFICATIONS4// number of notifications *not* including stop.

#define DSSTREAM_STOPF_NOREOPEN0x0001
#define DSSTREAM_STOPF_NOEXIT0x0002// Prevent the stop code from calling exit

#define ERR_WAVE_OPEN_FAILED-100
#define ERR_WAVE_INVALID_FORMAT-101
#define ERR_CREATEDSB_FAILED-102
#define ERR_WAVE_CORRUPTED_FILE-103

#define BORDER_SPACE_CX10
#define BORDER_SPACE_CY10
#define CONTROL_SPACE_CX4
#define CONTROL_SPACE_CY4
#define TEXT_SPACE_CY0
#define TEXT_SPACE_CX2

#define BUTTON_CX70
#define BUTTON_CY32
#define CHECK_CX70
#define CHECK_CY22
#define BUTTON_SPACE_CY6

#define PAN_TEXT_CX             140
#define VOL_TEXT_CX             140
#define FREQ_TEXT_CX            140
#define PROG_TEXT_CX            140

#define PAN_TB_CX200
#define PAN_TB_CY30
#define VOL_TB_CX200
#define VOL_TB_CY30
#define FREQ_TB_CX200
#define FREQ_TB_CY30
#define PROG_TB_CX200
#define PROG_TB_CY30

// The values for PAN may change in range...

//#define PAN_TB_MIN              0
//#define PAN_TB_MAX              2000
//#define PAN_TB_CENTER           1000
//#define PAN_MULTIPLIER          1
#define PAN_MIN                 0
#define PAN_MAX                 800
#define PAN_CENTER              400
#define PAN_SHIFT               (-400)
#define PAN_PAGESIZE            10
#define PAN_DIV                 10
#define PAN_MULT                10

//#define PAN_DSB_MIN           (-400)
//#define PAN_DSB_MAX           400
//#define PAN_DSB_CENTER0

//#define VOL_TB_MIN              0
//#define VOL_TB_MAX              1000
//#define VOL_MULTIPLIER          1
#define VOL_SHIFT               (-400)
#define VOL_MIN0
#define VOL_MAX                 400
#define VOL_PAGESIZE            10
#define VOL_DIV                 10
#define VOL_MULT                10
#define FREQ_MIN441
#define FREQ_MAX4410
#define FREQ_PAGESIZE100
#define FREQ_MULTIPLIER10
#define PROG_MIN0
#define PROG_MAX10000
#define PROG_MULTIPLIER         100

/* WAVE I/O subsystem defines */

#define WAVEVERSION 1

#ifndef ER_MEM
#define ER_MEM 0xe000
#endif

#ifndef ER_CANNOTOPEN
#define ER_CANNOTOPEN 0xe100
#endif

#ifndef ER_NOTWAVEFILE
#define ER_NOTWAVEFILE 0xe101
#endif

#ifndef ER_CANNOTREAD
#define ER_CANNOTREAD 0xe102
#endif

#ifndef ER_CORRUPTWAVEFILE
#define ER_CORRUPTWAVEFILE0xe103
#endif

#ifndef ER_CANNOTWRITE
#define ER_CANNOTWRITE0xe104
#endif

/* Streaming communication defines and structures */

#define WM_DSSTREAM_DONEWM_USER + 0x100/* Make our own app messages */
#define WM_DSSTREAM_DEBUGWM_USER + 0x101
#define WM_DSSTREAM_PROGRESSWM_USER + 0x102

#define DEBUGF_PLAYPOSITION0x0300
#define DEBUGF_WRITEPOSITION0x0301
#define DEBUGF_NEXTWRITE0x0302
#define DEBUGF_SKIP0x0303

/*
 * This structure keeps all the data that the TimeFunc callback uses in one
 * place.  In this implementation, that means the global data segement.  This
 * is setup so that if you wanted to put your callback in a DLL, all you'd need
 * to do is pass the address of this structure as a parameter.
 */

typedef struct waveinfoca_tag
{
    WAVEFORMATEX         *pwfx;    /* Wave Format data structure */
    HMMIO                hmmio;    /* MM I/O handle for the WAVE */
    MMCKINFO             mmck;    /* Multimedia RIFF chunk */
    MMCKINFO             mmckInRIFF;    /* Use in opening a WAVE file */
    LPDIRECTSOUNDBUFFER  lpDSBStreamBuffer; /* Points to DirectSoundBuffer */
    DWORD                dwBufferSize;    /* Size of the entire buffer */
DWORD dwNotifySize;// size of each notification period.
    DWORD                dwNextWriteOffset; /* Offset to next buffer segment */
    DWORD                dwProgress;    /* Used with above to show prog. */
DWORD dwLastPos;// the last play position returned by GetCurrentPos().
    //DWORD                dwBytesRemaining;  /* Bytes 'til timer shutdown */
    BOOL                 bDonePlaying;    /* Signals early abort to timer */
    BOOL                 bLoopFile;    /* Should we loop playback? */
    BOOL                 bFoundEnd;    /* Timer found file end */
} WAVEINFOCA, *LPWAVEINFOCA;

// Notify events. We deal with 2 notifications.
HANDLE hNotifyEvent[2];
int SetupNotifications(void);
DWORD HandleNotifications(LPVOID);

/* Function declarations */

LRESULT CALLBACK MainWindowProc( HWND, unsigned, WPARAM, LPARAM );
BOOL CALLBACK DLG_About( HWND, UINT, WPARAM, LPARAM );
void CALLBACK TimeFunc( UINT, UINT, DWORD, DWORD, DWORD );
BOOL CALLBACK DSEnumDlgProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK DSEnumProc( LPGUID, LPSTR, LPSTR, LPVOID );

void ErrorMessageBox( UINT, DWORD );
void HandlePanScroll( int, int );
void HandleVolScroll( int, int );
void HandleFreqScroll( int, int );
void ResetWavePlayer( void );
void UpdateFromControls( void );

int CreateChildren( RECT );
int HandleCommDlgError( DWORD );
int StreamBufferSetup( void );

BOOL DoDSoundEnumerate( LPGUID );

int WaveOpenFile(char *, HMMIO *, WAVEFORMATEX **, MMCKINFO *);
int WaveStartDataRead(HMMIO *, MMCKINFO *, MMCKINFO *);
int WaveReadFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *);
int WaveCloseReadFile(HMMIO *, WAVEFORMATEX **);

int WaveCreateFile(char *, HMMIO *, WAVEFORMATEX *, MMCKINFO *, MMCKINFO *);
int WaveStartDataWrite(HMMIO *, MMCKINFO *, MMIOINFO *);
int WaveWriteFile(HMMIO, UINT, BYTE *, MMCKINFO *, UINT *, MMIOINFO *);
int WaveCloseWriteFile(HMMIO *, MMCKINFO *, MMCKINFO *, MMIOINFO *, DWORD);

int WaveLoadFile(char *, UINT *, DWORD *, WAVEFORMATEX **, BYTE **);
int WaveSaveFile(char *, UINT, DWORD, WAVEFORMATEX *, BYTE *);

int WaveCopyUselessChunks(HMMIO *, MMCKINFO *, MMCKINFO *, HMMIO *,
MMCKINFO *, MMCKINFO *);
BOOL riffCopyChunk(HMMIO, HMMIO, const LPMMCKINFO);

#endif /* __DSSTREAM_INCLUDED__ */


DSTRENUM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dstrenum.c
 *  Content:   Illustrates enumerating DirectSound drivers
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <memory.h>

#include "dsstream.h"

extern HINSTANCE hInst;
extern HWND      hWndMain;

/****************************************************************************/
/* DoDSoundEnumerate()                                                      */
/*                                                                          */
/*   This function takes care of handling the DirectSound enumeration, which*/
/* simply means creating a dialog box, at this point...                     */
/****************************************************************************/
BOOL DoDSoundEnumerate( LPGUID lpGUID )
    {
    if( DialogBoxParam( hInst, MAKEINTRESOURCE(IDD_DSENUMBOX), hWndMain,
                        DSEnumDlgProc, (LPARAM)lpGUID ))
        return( TRUE );

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumDlgProc()                                                          */
/*                                                                          */
/*   Dialog procedure for the DSound enumeration choice dialog. Allows the  */
/* user to choose from installed drivers.  Returns TRUE on error.           */
/****************************************************************************/
BOOL CALLBACK DSEnumDlgProc( HWND hDlg, UINT msg,
                                WPARAM wParam, LPARAM lParam )
    {
    static HWND   hCombo;
    static LPGUID lpGUID;
    LPGUID        lpTemp;
    int           i;

    switch( msg )
        {
        case WM_INITDIALOG:
            hCombo = GetDlgItem( hDlg, IDC_DSENUM_COMBO );
            lpGUID = (LPGUID)lParam;

            if( DirectSoundEnumerate( (LPDSENUMCALLBACK)DSEnumProc, &hCombo ) != DS_OK )
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            if( ComboBox_GetCount( hCombo ))
                ComboBox_SetCurSel( hCombo, 0 );
            else
                {
                EndDialog( hDlg, TRUE );
                return( TRUE );
                }
            return( TRUE );

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDOK:
                    for( i = 0; i < ComboBox_GetCount( hCombo ); i++ )
                        {
                        lpTemp = (LPGUID)ComboBox_GetItemData( hCombo, i );
                        if( i == ComboBox_GetCurSel( hCombo ))
                            {
                            if( lpTemp != NULL )
                                memcpy( lpGUID, lpTemp, sizeof(GUID));
    else
        lpGUID = NULL;
    }
                        if( lpTemp )
                            LocalFree( lpTemp );
                        }
                    // If we got the NULL GUID, then we want to open the default
                    // sound driver, so return with an error and the init code
    // will know not to pass in the guID and will send NULL
    // instead.
                    if( lpGUID == NULL )
        EndDialog( hDlg, TRUE );
                    else
                        EndDialog( hDlg, FALSE );
                    return( TRUE );

                case IDCANCEL:
                    // Force a NULL GUID
                    EndDialog( hDlg, TRUE );
                    return( TRUE );
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }

/****************************************************************************/
/* DSEnumProc()                                                             */
/*                                                                          */
/*   This is the Enumeration procedure called by DirectSoundEnumerate with  */
/* the parameters of each DirectSound Object available in the system.       */
/****************************************************************************/
BOOL CALLBACK DSEnumProc( LPGUID lpGUID, LPSTR lpszDesc,
                                LPSTR lpszDrvName, LPVOID lpContext )
    {
    HWND   hCombo = *(HWND *)lpContext;
    LPGUID lpTemp = NULL;

    if( lpGUID != NULL )
        {
        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
    return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));
}

    ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    ComboBox_FindString( hCombo, 0, lpszDesc ),
    lpTemp );
    return( TRUE );
    }


DSTRWAVE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:               dstrwave.c
 *  Content:    Wave library routines.
 *
 *      This file is used for loading/saving waves, and reading and
 *      writing waves in smaller blocks.
 *      Uses WaveOpenFile, WaveReadFile and WaveCloseReadFile for
 *      single block access to reading wave files.
 *      Uses WaveCreateFile, WaveWriteFile, WaveCloseWriteFile for
 *      single block access for writing  wave files.
 *      Uses WaveLoadFile to load a whole wave file into memory.
 *      Uses WaveSaveFile to save a whole wave file from memory.
 *
 ***************************************************************************/

/* PROTOTYPES */
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "dsstream.h"
#include "wassert.h"

/* ROUTINES */
/* -------------------------------------------------------*/

/* This function will open a wave input file and prepare it for reading,
 * so the data can be easily
 * read with WaveReadFile. Returns 0 if successful, the error code if not.
 *      pszFileName - Input filename to load.
 *      phmmioIn    - Pointer to handle which will be used
 *          for further mmio routines.
 *      ppwfxInfo   - Ptr to ptr to WaveFormatEx structure
 *          with all info about the file.
 *
*/
int WaveOpenFile(
char *pszFileName,                              // (IN)
HMMIO *phmmioIn,                                // (OUT)
WAVEFORMATEX **ppwfxInfo,                       // (OUT)
MMCKINFO *pckInRIFF                             // (OUT)
)
{
HMMIO           hmmioIn;
MMCKINFO        ckIn;           // chunk info. for general use.
PCMWAVEFORMAT   pcmWaveFormat;  // Temp PCM structure to load in.
WORD            cbExtraAlloc;   // Extra bytes for waveformatex
int             nError;         // Return value.

// Initialization...
*ppwfxInfo = NULL;
nError = 0;
hmmioIn = NULL;

if ((hmmioIn = mmioOpen(pszFileName, NULL, MMIO_ALLOCBUF | MMIO_READ)) == NULL)
{
nError = ER_CANNOTOPEN;
goto ERROR_READING_WAVE;
}

if ((nError = (int)mmioDescend(hmmioIn, pckInRIFF, NULL, 0)) != 0)
{
goto ERROR_READING_WAVE;
}

if ((pckInRIFF->ckid != FOURCC_RIFF) || (pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E')))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Search the input file for for the 'fmt ' chunk.     */
    ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if ((nError = (int)mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

/* Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
    * if there are extra parameters at the end, we'll ignore them */

    if (ckIn.cksize < (long) sizeof(PCMWAVEFORMAT))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}

/* Read the 'fmt ' chunk into <pcmWaveFormat>.*/
    if (mmioRead(hmmioIn, (HPSTR) &pcmWaveFormat, (long) sizeof(pcmWaveFormat)) != (long) sizeof(pcmWaveFormat))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

// Ok, allocate the waveformatex, but if its not pcm
// format, read the next word, and thats how many extra
// bytes to allocate.
if (pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM)
cbExtraAlloc = 0;

else
{
// Read in length of extra bytes.
if (mmioRead(hmmioIn, (LPSTR) &cbExtraAlloc,
(long) sizeof(cbExtraAlloc)) != (long) sizeof(cbExtraAlloc))
{
nError = ER_CANNOTREAD;
goto ERROR_READING_WAVE;
}

}

// Ok, now allocate that waveformatex structure.
if ((*ppwfxInfo = GlobalAlloc(GMEM_FIXED, sizeof(WAVEFORMATEX)+cbExtraAlloc)) == NULL)
{
nError = ER_MEM;
goto ERROR_READING_WAVE;
}

// Copy the bytes from the pcm structure to the waveformatex structure
memcpy(*ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat));
(*ppwfxInfo)->cbSize = cbExtraAlloc;

// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
if (cbExtraAlloc != 0)
{
if (mmioRead(hmmioIn, (LPSTR) (((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(cbExtraAlloc)),
(long) (cbExtraAlloc)) != (long) (cbExtraAlloc))
{
nError = ER_NOTWAVEFILE;
goto ERROR_READING_WAVE;
}
}

/* Ascend the input file out of the 'fmt ' chunk. */
if ((nError = mmioAscend(hmmioIn, &ckIn, 0)) != 0)
{
goto ERROR_READING_WAVE;

}

goto TEMPCLEANUP;

ERROR_READING_WAVE:
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

TEMPCLEANUP:
*phmmioIn = hmmioIn;

return(nError);

}

/*      This routine has to be called before WaveReadFile as it searchs for the chunk to descend into for
reading, that is, the 'data' chunk.  For simplicity, this used to be in the open routine, but was
taken out and moved to a separate routine so there was more control on the chunks that are before
the data chunk, such as 'fact', etc... */

int WaveStartDataRead(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRIFF
)
{
int                     nError;

nError = 0;

// Do a nice little seek...
if ((nError = mmioSeek(*phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
Assert(FALSE);
}

nError = 0;
//      Search the input file for for the 'data' chunk.
pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a');
if ((nError = mmioDescend(*phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK)) != 0)
{
goto ERROR_READING_WAVE;
}

goto CLEANUP;

ERROR_READING_WAVE:

CLEANUP:
return(nError);
}

/*      This will read wave data from the wave file.  Makre sure we're descended into
the data chunk, else this will fail bigtime!
hmmioIn         - Handle to mmio.
cbRead          - # of bytes to read.
pbDest          - Destination buffer to put bytes.
cbActualRead- # of bytes actually read.

*/

int WaveReadFile(
HMMIO hmmioIn,                          // IN
UINT cbRead,                            // IN
BYTE *pbDest,                           // IN
MMCKINFO *pckIn,                        // IN.
UINT *cbActualRead                      // OUT.

)
{

MMIOINFO    mmioinfoIn;         // current status of <hmmioIn>
int                     nError;
UINT                    cT, cbDataIn, uCopyLength;

nError = 0;

if (nError = mmioGetInfo(hmmioIn, &mmioinfoIn, 0) != 0)
{
goto ERROR_CANNOT_READ;
}

cbDataIn = cbRead;
if (cbDataIn > pckIn->cksize)
cbDataIn = pckIn->cksize;

pckIn->cksize -= cbDataIn;

for (cT = 0; cT < cbDataIn; )
{
/* Copy the bytes from the io to the buffer. */
if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
    if ((nError = mmioAdvance(hmmioIn, &mmioinfoIn, MMIO_READ)) != 0)
{
goto ERROR_CANNOT_READ;
}
    if (mmioinfoIn.pchNext == mmioinfoIn.pchEndRead)
{
nError = ER_CORRUPTWAVEFILE;
goto ERROR_CANNOT_READ;
}
}

// Actual copy.
uCopyLength = (UINT)(mmioinfoIn.pchEndRead - mmioinfoIn.pchNext);
if((cbDataIn - cT) < uCopyLength )
uCopyLength = cbDataIn - cT;
memcpy( (BYTE*)(pbDest+cT), (BYTE*)mmioinfoIn.pchNext, uCopyLength );
cT += uCopyLength;
mmioinfoIn.pchNext += uCopyLength;
}

if ((nError = mmioSetInfo(hmmioIn, &mmioinfoIn, 0)) != 0)
{
goto ERROR_CANNOT_READ;
}

*cbActualRead = cbDataIn;
goto FINISHED_READING;

ERROR_CANNOT_READ:
*cbActualRead = 0;

FINISHED_READING:
return(nError);

}

/*      This will close the wave file openned with WaveOpenFile.
phmmioIn - Pointer to the handle to input MMIO.
ppwfxSrc - Pointer to pointer to WaveFormatEx structure.

Returns 0 if successful, non-zero if there was a warning.

*/
int WaveCloseReadFile(
HMMIO *phmmio,                                  // IN
WAVEFORMATEX **ppwfxSrc                 // IN
)
{

if (*ppwfxSrc != NULL)
{
GlobalFree(*ppwfxSrc);
*ppwfxSrc = NULL;
}

if (*phmmio != NULL)
{
mmioClose(*phmmio, 0);
*phmmio = NULL;
}

return(0);

}

/*      This routine will create a wave file for writing.  This will automatically overwrite any
existing file with the same name, so be careful and check before hand!!!
pszFileName     - Pointer to filename to write.
phmmioOut               - Pointer to HMMIO handle that is used for further writes
pwfxDest                - Valid waveformatex destination structure.
pckOut                  - Pointer to be set with the MMCKINFO.
pckOutRIFF              - Pointer to be set with the RIFF info.

*/
int WaveCreateFile(
char *pszFileName,                              // (IN)
HMMIO *phmmioOut,                               // (OUT)
WAVEFORMATEX *pwfxDest,                 // (IN)
MMCKINFO *pckOut,                               // (OUT)
MMCKINFO *pckOutRIFF                    // (OUT)

)
{

int             nError;                         // Return value.
DWORD           dwFactChunk;            // Contains the actual fact chunk. Garbage until WaveCloseWriteFile.
MMCKINFO        ckOut1;

dwFactChunk = (DWORD)-1;
nError = 0;

*phmmioOut = mmioOpen(pszFileName, NULL,
MMIO_ALLOCBUF | MMIO_READWRITE|MMIO_CREATE);

    if (*phmmioOut == NULL)
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot save WAVE file
}

    /* Create the output file RIFF chunk of form type 'WAVE'.
     */
    pckOutRIFF->fccType = mmioFOURCC('W', 'A', 'V', 'E');
pckOutRIFF->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOutRIFF, MMIO_CREATERIFF)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* We are now descended into the 'RIFF' chunk we just created.
     * Now create the 'fmt ' chunk. Since we know the size of this chunk,
     * specify it in the MMCKINFO structure so MMIO doesn't have to seek
     * back and set the chunk size after ascending from the chunk.
     */
    pckOut->ckid = mmioFOURCC('f', 'm', 't', ' ');
    pckOut->cksize = sizeof(PCMWAVEFORMAT);   // we know the size of this ck.
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

    /* Write the PCMWAVEFORMAT structure to the 'fmt ' chunk if its that type. */
    if (pwfxDest->wFormatTag == WAVE_FORMAT_PCM)
{
    if (mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(PCMWAVEFORMAT))
!= sizeof(PCMWAVEFORMAT))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}
}

else
{
// Write the variable length size.
if ((UINT)mmioWrite(*phmmioOut, (HPSTR) pwfxDest, sizeof(*pwfxDest)+pwfxDest->cbSize)
!= (sizeof(*pwfxDest)+pwfxDest->cbSize))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

}

    /* Ascend out of the 'fmt ' chunk, back into the 'RIFF' chunk.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

// Now create the fact chunk, not required for PCM but nice to have.  This is filled
// in when the close routine is called.
ckOut1.ckid = mmioFOURCC('f', 'a', 'c', 't');
ckOut1.cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, &ckOut1, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if (mmioWrite(*phmmioOut, (HPSTR)&dwFactChunk, sizeof(dwFactChunk)) != sizeof(dwFactChunk))
{
nError = ER_CANNOTWRITE;
goto ERROR_CANNOT_WRITE;
}

// Now ascend out of the fact chunk...
    if ((nError = mmioAscend(*phmmioOut, &ckOut1, 0)) != 0)
{
nError = ER_CANNOTWRITE;        // cannot write file, probably
goto ERROR_CANNOT_WRITE;
}

goto DONE_CREATE;

ERROR_CANNOT_WRITE:
// Maybe delete the half-written file?  Ah forget it for now, its good to leave the
// file there for debugging...

DONE_CREATE:
return(nError);

}

/*      This routine has to be called before any data is written to the wave output file, via wavewritefile.  This
sets up the data to write, and creates the data chunk.
*/

int WaveStartDataWrite(
HMMIO *phmmioOut,                               // (IN)
MMCKINFO *pckOut,                               // (IN)
MMIOINFO *pmmioinfoOut                  // (OUT)
)
{

int             nError;

nError = 0;
 /* Create the 'data' chunk that holds the waveform samples.  */
    pckOut->ckid = mmioFOURCC('d', 'a', 't', 'a');
pckOut->cksize = 0;
    if ((nError = mmioCreateChunk(*phmmioOut, pckOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;    // cannot write file, probably
}

if ((nError = mmioGetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

goto CLEANUP;
ERROR_CANNOT_WRITE:

CLEANUP:
return(nError);
}

/* This routine will write out data to a wave file.
hmmioOut                - Handle to hmmioOut filled by WaveCreateFile
cbWrite                 - # of bytes to write out.
pbSrc                   - Pointer to source.
pckOut                  - pointer to ckOut filled by WaveCreateFile
cbActualWrite   - # of actual bytes written.
pmmioinfoOut    - Pointer to mmioinfoOut filled by WaveCreateFile.

Returns 0 if successful, else the error code.

 */

int WaveWriteFile(
HMMIO hmmioOut,                         // (IN)
UINT cbWrite,                           // (IN)
BYTE *pbSrc,                            // (IN)
MMCKINFO *pckOut,                       // (IN)
UINT *cbActualWrite,            // (OUT)
MMIOINFO *pmmioinfoOut          // (IN)
)
{

int                     nError;
UINT            cT;

nError = 0;

*cbActualWrite = 0;

for (cT=0; cT < cbWrite; cT++)
{
if (pmmioinfoOut->pchNext == pmmioinfoOut->pchEndWrite)
{
pmmioinfoOut->dwFlags |= MMIO_DIRTY;
if ((nError = mmioAdvance(hmmioOut, pmmioinfoOut, MMIO_WRITE)) != 0)
{
    goto ERROR_CANNOT_WRITE;
}
}
*((BYTE*)pmmioinfoOut->pchNext)++ = *((BYTE*)pbSrc+cT);
(*cbActualWrite)++;
}

ERROR_CANNOT_WRITE:
// What to do here?  Well, for now, nothing, just return that error.  (maybe delete the
// file later?

return(nError);

}

/*      This routine will close a wave file used for writing.  Returns 0 if successful, else
the error code.
phmmioOut       - Pointer to mmio handle for saving.
pckOut          - Pointer to the MMCKINFO for saving.
pckOutRiff      - Pointer to the riff MMCKINFO for saving.
pmmioinfoOut- Pointer to mmioinfo for saving.
cSamples        - # of samples saved, for the fact chunk.  For PCM, this isn't used but
  will be written anyway, so this can be zero as long as programs ignore
  this field when they load PCM formats.

*/
int WaveCloseWriteFile(
HMMIO *phmmioOut,               // (IN)
MMCKINFO *pckOut,               // (IN)
MMCKINFO *pckOutRIFF,   // (IN)
MMIOINFO *pmmioinfoOut, // (IN)
DWORD cSamples                  // (IN)
)
{

int                     nError;

nError = 0;

if (*phmmioOut == NULL)
return(0);

pmmioinfoOut->dwFlags |= MMIO_DIRTY;
    if ((nError = mmioSetInfo(*phmmioOut, pmmioinfoOut, 0)) != 0)
{
// cannot flush, probably...
goto ERROR_CANNOT_WRITE;
}

 /* Ascend the output file out of the 'data' chunk -- this will cause
     * the chunk size of the 'data' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOut, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

// Do this here instead...
if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

nError = mmioSeek(*phmmioOut, 0, SEEK_SET);
if ((nError = (int)mmioDescend(*phmmioOut, pckOutRIFF, NULL, 0)) != 0)
{
goto ERROR_CANNOT_WRITE;
}

nError = 0;
pckOut->ckid = mmioFOURCC('f', 'a', 'c', 't');
if ((nError = mmioDescend(*phmmioOut, pckOut, pckOutRIFF, MMIO_FINDCHUNK)) == 0)
{
// If it didn't fail, write the fact chunk out, if it failed, not critical, just
// assert (below).
    nError = mmioWrite(*phmmioOut, (HPSTR)&cSamples, sizeof(DWORD));
nError = mmioAscend(*phmmioOut, pckOut, 0);
nError = 0;
}
else
{
nError = 0;
Assert(FALSE);
}

// CANTWRITEFACT:
    /* Ascend the output file out of the 'RIFF' chunk -- this will cause
     * the chunk size of the 'RIFF' chunk to be written.
     */
    if ((nError = mmioAscend(*phmmioOut, pckOutRIFF, 0)) != 0)
goto ERROR_CANNOT_WRITE;    // cannot write file, probably

ERROR_CANNOT_WRITE:
if (*phmmioOut != NULL)
{
mmioClose(*phmmioOut, 0);
*phmmioOut = NULL;
}

return(nError);

}

/*      This routine will copy from a source wave file to a destination wave file all those useless chunks
(well, the ones useless to conversions, etc --> apparently people use them!).  The source will be
seeked to the begining, but the destination has to be at a current pointer to put the new chunks.
This will also seek     back to the start of the wave riff header at the end of the routine.

phmmioIn                - Pointer to input mmio file handle.
pckIn                   - Pointer to a nice ckIn to use.
pckInRiff               - Pointer to the main riff.
phmmioOut               - Pointer to output mmio file handle.
pckOut                  - Pointer to nice ckOut to use.
pckOutRiff              - Pointer to the main riff.

Returns 0 if successful, else the error code.  If this routine fails, it still attemps to seek back to
the start of the wave riff header, though this too could be unsuccessful.
*/

int WaveCopyUselessChunks(
HMMIO *phmmioIn,
MMCKINFO *pckIn,
MMCKINFO *pckInRiff,
HMMIO *phmmioOut,
MMCKINFO *pckOut,
MMCKINFO *pckOutRiff)
{

int                             nError;

nError = 0;
// First seek to the stinking start of the file, not including the riff header...
if ((nError = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET)) == -1)
{
nError = ER_CANNOTREAD;
goto ERROR_IN_PROC;
}

nError = 0;

    while (mmioDescend(*phmmioIn, pckIn, pckInRiff, 0) == 0)
    {

//  quickly check for corrupt RIFF file--don't ascend past end!
if ((pckIn->dwDataOffset + pckIn->cksize) > (pckInRiff->dwDataOffset + pckInRiff->cksize))
    goto ERROR_IN_PROC;

switch (pckIn->ckid)
{
    //  explicitly skip these...
    case mmioFOURCC('f', 'm', 't', ' '):
break;

    case mmioFOURCC('d', 'a', 't', 'a'):
break;

    case mmioFOURCC('f', 'a', 'c', 't'):
break;

    case mmioFOURCC('J', 'U', 'N', 'K'):
break;

    case mmioFOURCC('P', 'A', 'D', ' '):
break;

    case mmioFOURCC('c', 'u', 'e', ' '):
break;

    //  copy chunks that are OK to copy
    case mmioFOURCC('p', 'l', 's', 't'):
// although without the 'cue' chunk, it doesn't make much sense
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    case mmioFOURCC('D', 'I', 'S', 'P'):
riffCopyChunk(*phmmioIn, *phmmioOut, pckIn);
break;

    //  don't copy unknown chunks
    default:
break;
}

//  step up to prepare for next chunk..
mmioAscend(*phmmioIn, pckIn, 0);
    }

ERROR_IN_PROC:
{
int nErrorT;
// Seek back to riff header
nErrorT = mmioSeek(*phmmioIn, pckInRiff->dwDataOffset + sizeof(FOURCC), SEEK_SET);
}

return(nError);
}

/** BOOL RIFFAPI riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
 *
 *  DESCRIPTION:
 *
 *
 *  ARGUMENTS:
 *      (LPWAVECONVCB lpwc, LPMMCKINFO lpck)
 *
 *  RETURN (BOOL NEAR PASCAL):
 *
 *
 *  NOTES:
 *
 **  */

BOOL riffCopyChunk(HMMIO hmmioSrc, HMMIO hmmioDst, const LPMMCKINFO lpck)
{
    MMCKINFO    ck;
    HPSTR       hpBuf;

    //
    //
    //
    hpBuf = (HPSTR)GlobalAllocPtr(GHND, lpck->cksize);
    if (!hpBuf)
return (FALSE);

    ck.ckid   = lpck->ckid;
    ck.cksize = lpck->cksize;
    if (mmioCreateChunk(hmmioDst, &ck, 0))
goto rscc_Error;

    if (mmioRead(hmmioSrc, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioWrite(hmmioDst, hpBuf, lpck->cksize) != (LONG)lpck->cksize)
goto rscc_Error;

    if (mmioAscend(hmmioDst, &ck, 0))
goto rscc_Error;

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (TRUE);

rscc_Error:

    if (hpBuf)
GlobalFreePtr(hpBuf);

    return (FALSE);
} /* RIFFSupCopyChunk() */

/*      This routine loads a full wave file into memory.  Be careful, wave files can get
pretty big these days :).
szFileName      -       sz Filename
cbSize          -       Size of loaded wave (returned)
cSamples        -       # of samples loaded.
ppwfxInfo       -       Pointer to pointer to waveformatex structure.  The wfx structure
IS ALLOCATED by this routine!  Make sure to free it!
ppbData         -       Pointer to a byte pointer (globalalloc) which is allocated by this
routine.  Make sure to free it!

Returns 0 if successful, else the error code.
*/

int WaveLoadFile(
char *pszFileName,                      // (IN)
UINT *cbSize,                           // (OUT)
DWORD *pcSamples,                       // (OUT)
WAVEFORMATEX **ppwfxInfo,       // (OUT)
BYTE **ppbData                          // (OUT)
)
{

HMMIO                           hmmioIn;
MMCKINFO                        ckInRiff;
MMCKINFO                        ckIn;
int                                     nError;
UINT                            cbActualRead;

*ppbData = NULL;
*ppwfxInfo = NULL;
*cbSize = 0;

if ((nError = WaveOpenFile(pszFileName, &hmmioIn, ppwfxInfo, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

if ((nError = WaveStartDataRead(&hmmioIn, &ckIn, &ckInRiff)) != 0)
{
goto ERROR_LOADING;
}

// Ok, size of wave data is in ckIn, allocate that buffer.
if ((*ppbData = (BYTE *)GlobalAlloc(GMEM_FIXED, ckIn.cksize)) == NULL)
{
nError = ER_MEM;
goto ERROR_LOADING;
}

if ((nError = WaveReadFile(hmmioIn, ckIn.cksize, *ppbData, &ckIn, &cbActualRead)) != 0)
{
goto ERROR_LOADING;
}

*cbSize = cbActualRead;
goto DONE_LOADING;

ERROR_LOADING:
if (*ppbData != NULL)
{
GlobalFree(*ppbData);
*ppbData = NULL;
}
if (*ppwfxInfo != NULL)
{
GlobalFree(*ppwfxInfo);
*ppwfxInfo = NULL;
}

DONE_LOADING:
// Close the wave file.
if (hmmioIn != NULL)
{
mmioClose(hmmioIn, 0);
hmmioIn = NULL;
}

return(nError);

}

/*      This routine saves a wave file in currently in memory.
pszFileName -   FileName to save to.  Automatically overwritten, be careful!
cbSize          -       Size in bytes to write.
cSamples        -       # of samples to write, used to make the fact chunk. (if !PCM)
pwfxDest        -       Pointer to waveformatex structure.
pbData          -       Pointer to the data.
*/

int WaveSaveFile(
char *pszFileName,                      // (IN)
UINT cbSize,                            // (IN)
DWORD cSamples,                         // (IN)
WAVEFORMATEX *pwfxDest,         // (IN)
BYTE *pbData                            // (IN)
)
{

HMMIO           hmmioOut;
MMCKINFO        ckOut;
MMCKINFO        ckOutRIFF;
MMIOINFO        mmioinfoOut;
UINT            cbActualWrite;
int                     nError;

if ((nError = WaveCreateFile(pszFileName, &hmmioOut, pwfxDest, &ckOut, &ckOutRIFF)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveStartDataWrite(&hmmioOut, &ckOut, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveWriteFile(hmmioOut, cbSize, pbData, &ckOut, &cbActualWrite, &mmioinfoOut)) != 0)
{
goto ERROR_SAVING;
}

if ((nError = WaveCloseWriteFile(&hmmioOut, &ckOut, &ckOutRIFF, &mmioinfoOut, cSamples)) != 0)
{
goto ERROR_SAVING;
}

ERROR_SAVING:

return(nError);

}


NOTIFY.C

// ===============================================================
// notify.c
// ===============================================================
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>

#include <dsound.h>
#include "dsstream.h"

// externs
extern LPDIRECTSOUNDBUFFER lpDSBStreamBuffer;
extern LPDIRECTSOUNDNOTIFY lpDirectSoundNotify;
extern WAVEINFOCA wiWave;
extern HWND  hWndMain;

// function prototypes
void UpdateProgressBar(void);

// ===============================================================
// HandleNotifications
//Deals with the notification events.
//Two events have been defined in SetNotifications().
//The first (index 0) handles position notifications, triggered
//after wiWave.dwNotifySize bytes and the second event deals with
//the stop notification.
//Initally, the entire buffer is filled with the FillDataBuffer()
//call, when the user asks the stream to play. With each subsequent
//notification, handled by this function, the rest of the file
//is read into the buffer.
// ===============================================================
DWORD HandleNotifications(LPVOID lpvoid)
{
DWORD hr = DS_OK;
#ifdef DEBUG
DWORD dwPlayPos, dwWritePos;
#endif
DWORD hRet = 0;

while((hRet = WaitForMultipleObjects(2, hNotifyEvent, FALSE, INFINITE))!= WAIT_FAILED)
{
if (wiWave.bDonePlaying == TRUE)
{
DPF(0, "Posting stop message");
UpdateProgressBar();
SendMessage( hWndMain, WM_DSSTREAM_DONE, (WPARAM)0, (LPARAM)0 );
break; //quit while loop.
}

// Deal with the event that got signalled.
switch(hRet-WAIT_OBJECT_0)
{
case 0:
{// A play notification has been received.
LPBYTE lpWrite1;
DWORD dwWrite1;
UINT cbActual = 0;
// update the progress slider and wiWave.dwProgress.
UpdateProgressBar();

// If the entire file has been read into the buffer, bFoundEnd will be TRUE.
if (!wiWave.bFoundEnd)
{// read in more of the file.
DPF(0, "Received a notify event with FoundEnd = FALSE. NR = %lu", wiWave.dwNextWriteOffset );
#ifdef DEBUG
hr = lpDSBStreamBuffer->lpVtbl->GetCurrentPosition(lpDSBStreamBuffer, &dwPlayPos, &dwWritePos);
DPF(1, "dwPlayPos = %lu and dwWritePos = %lu", dwPlayPos, dwWritePos);
#endif
// Lock the buffer.
hr = lpDSBStreamBuffer->lpVtbl->Lock(lpDSBStreamBuffer,
wiWave.dwNextWriteOffset,
wiWave.dwNotifySize,
&lpWrite1, &dwWrite1,
NULL, NULL, 0);
if (hr != DS_OK)
{
DPF(0, "couldn't lock buffer with hr = %lu", hr);
return hr;
}

if (dwWrite1 < wiWave.dwNotifySize)
{
DPF(0, "Lock returned number of bytes and requested size differ");
}

// now read the file.
hr = WaveReadFile(wiWave.hmmio, dwWrite1, lpWrite1, &wiWave.mmck, &cbActual);

//if cbActual is less than the amount asked for, we may have
//reached end of file. If we are looping, we will simply read the
//file in again until we have written dwWrite1 bytes into the buffer.
//If not looping we fill with silence.
if ((DWORD)cbActual < dwWrite1)
{
DPF(3, "ReadFile read less than what we asked for. less by %lu", dwWrite1-(DWORD)cbActual);
if (!wiWave.bLoopFile)
{
wiWave.bFoundEnd = TRUE;
// fill up the rest of the buffer with silence.
DPF(0, "Filling in silence");
FillMemory( lpWrite1+cbActual, (UINT)dwWrite1-cbActual,
(BYTE)(wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0));
}
else
{// we're looping. Read in the file again.
int nChkErr = 0;
UINT cbWritten = cbActual;// from previous call above.
DPF(0, "Looping so read in file again");
while (cbWritten < (UINT)dwWrite1)// write only one notification worth.
{// this will keep reading in until the buffer is full. For very short files.
nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF );
ASSERT(nChkErr == 0);// we've already this before so shouldn't fail.
nChkErr = WaveReadFile(wiWave.hmmio, (UINT)dwWrite1-cbWritten,
lpWrite1 + cbWritten, &wiWave.mmck, &cbActual);
ASSERT(nChkErr == 0);// we've already this before so shouldn't fail.
cbWritten += cbActual;
} // while
ASSERT(wiWave.bFoundEnd == FALSE);
}// if or not looping
}

//unlock now
hr = lpDSBStreamBuffer->lpVtbl->Unlock(lpDSBStreamBuffer, (LPVOID)lpWrite1, dwWrite1,  NULL, 0 );
// setup the nextwrite offset.
wiWave.dwNextWriteOffset += dwWrite1;
if (wiWave.dwNextWriteOffset >= wiWave.dwBufferSize)
wiWave.dwNextWriteOffset -= wiWave.dwBufferSize;
}// if !bFoundEnd.
else
{
// We have read in the whole file. We will keep filling in silence
// in the buffer to allow the remaining sound bytes filled earlier
// to be played.
DPF(1, "Received a notify event and bFoundEnd = TRUE");
ASSERT(wiWave.bLoopFile == FALSE);
#ifdef DEBUG
// if we've read all the file and we're not looping,
hr = lpDSBStreamBuffer->lpVtbl->GetCurrentPosition(lpDSBStreamBuffer, &dwPlayPos, &dwWritePos);
DPF(1, "dwPlayPos = %lu and dwWritePos = %lu", dwPlayPos, dwWritePos);
#endif
// Allow the rest of the bytes to be played and fill here
// with silence. The next notification will quit the while loop.
hr = lpDSBStreamBuffer->lpVtbl->Lock(lpDSBStreamBuffer,
wiWave.dwNextWriteOffset,
wiWave.dwNotifySize,
&lpWrite1, &dwWrite1,
NULL, NULL, 0);
DPF(0, "Filling in silence");
FillMemory(lpWrite1, dwWrite1,(BYTE)(wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0));

hr = lpDSBStreamBuffer->lpVtbl->Unlock(lpDSBStreamBuffer, (LPVOID)lpWrite1, dwWrite1,  NULL, 0 );

// We don't want to cut off the sound before it's done playing.
// If doneplaying is set, the next notification event will post a stop message.
if (wiWave.mmckInRIFF.cksize > wiWave.dwNotifySize &&
wiWave.dwProgress >= wiWave.mmckInRIFF.cksize - wiWave.dwNotifySize)
{
wiWave.bDonePlaying = TRUE;
}
else// for short files.
if (wiWave.dwProgress >= wiWave.mmckInRIFF.cksize)
wiWave.bDonePlaying = TRUE;
}// else
}
break;

case 1:
{// A stop notification has been received.
DPF(0, "Received the stop event");
// update ui.
//UpdateProgressBar();
#ifdef DEBUG
hr = lpDSBStreamBuffer->lpVtbl->GetCurrentPosition(lpDSBStreamBuffer, &dwPlayPos, &dwWritePos);
DPF(1, "dwPlayPos = %lu and dwWritePos = %lu", dwPlayPos, dwWritePos);
#endif
// Set bDonePlaying to True.
wiWave.bDonePlaying = TRUE;
// call stop.
if (lpDSBStreamBuffer)
{
hr = lpDSBStreamBuffer->lpVtbl->Stop(lpDSBStreamBuffer);
ASSERT(hr == DS_OK);// why would this fail in tis app?
}
CloseHandle(hNotifyEvent[0]);
CloseHandle(hNotifyEvent[1]);
hNotifyEvent[0] = hNotifyEvent[1] = (HANDLE)NULL;
break;
}

default:
ASSERT(FALSE);// we should never fall in here.
break;

}// end switch

} // while

//UpdateProgressBar();
DPF(0, "Exiting thread");
return TRUE;
} //handlenotifications()

// ===========================================================
// UpdateProgressBar
// Gets the current play position and updates the progress
// bar accordingly.
// ===========================================================
void UpdateProgressBar(void)
{
DWORD dwPlayPos, dwWritePos;
int hr = 0;
DWORD dwPlayed = 0;

hr = lpDSBStreamBuffer->lpVtbl->GetCurrentPosition(lpDSBStreamBuffer, &dwPlayPos, &dwWritePos);
if (hr != DS_OK)
{
DPF(0, "getCurrentPos returned failure");
}

DPF(1, "CurrentPos = %lu and LastPos = %lu", dwPlayPos, wiWave.dwLastPos);

if (dwPlayPos < wiWave.dwLastPos)
{
dwPlayed = wiWave.dwBufferSize - wiWave.dwLastPos + dwPlayPos;
}
else
{
dwPlayed = dwPlayPos - wiWave.dwLastPos;
}

wiWave.dwProgress += dwPlayed;

wiWave.dwLastPos = dwPlayPos;

DPF(1, "wiWave.dwProgress =  %lu", wiWave.dwProgress);
DPF(1, "Posting %lu", wiWave.dwProgress);
PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, wiWave.dwProgress );
}


DEBUG.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.c
 *  Content:debug code
 *
 ***************************************************************************/

#ifdef DEBUG

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include "debug.h"

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifdef _WIN32
    #include <wchar.h>
#else
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
    #define wsprintfA           wsprintf
    #define MessageBoxA         MessageBox
#endif

//
//
//
BOOL    __gfDbgEnabled          = TRUE;         // master enable
UINT    __guDbgLevel            = 0;            // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF
//
//  Description:
//
//
//  Arguments:
//      LPSTR szFormat:
//
//      va_list va:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF
(
    LPSTR                   szFormat,
    va_list                 va
)
{
    char                ach[DEBUG_MAX_LINE_LEN];
    BOOL                fDebugBreak = FALSE;
    BOOL                fPrefix     = TRUE;
    BOOL                fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch (*szFormat)
        {
            case '!':
                fDebugBreak = TRUE;
                szFormat++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFormat++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFormat++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
    {
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");
    }

#ifdef _WIN32
    wvsprintfA(ach + lstrlenA(ach), szFormat, va);
#else
    wvsprintf(ach + lstrlenA(ach), szFormat, (LPSTR)va);
#endif

    if (fCRLF)
    {
        lstrcatA(ach, "\r\n");
    }

    OutputDebugStringA(ach);

    if (fDebugBreak)
    {
        DebugBreak();
    }
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf
//
//  Description:
//      dprintf() is called by the DPF() macro if DEBUG is defined at compile
//      time. It is recommended that you only use the DPF() macro to call
//      this function--so you don't have to put #ifdef DEBUG around all
//      of your code.
//
//  Arguments:
//      UINT uDbgLevel:
//
//      LPSTR szFormat:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf
(
    UINT                    uDbgLevel,
    LPSTR                   szFormat,
    ...
)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFormat);
    DbgVPrintF(szFormat, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (BOOL):
//      Returns the previous debugging state.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable
(
    BOOL                    fEnable
)
{
    BOOL                fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel
//
//  Description:
//
//
//  Arguments:
//      UINT uLevel:
//
//  Return (UINT):
//      Returns the previous debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel
(
    UINT                    uLevel
)
{
    UINT                uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgGetLevel
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (UINT):
//      Returns the current debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgGetLevel
(
    void
)
{
    return (__guDbgLevel);
} // DbgGetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (UINT):
//      Returns the debugging level that was set.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize
(
    BOOL                    fEnable
)
{
    UINT                uLevel;

    uLevel = GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, (UINT)-1);
    if ((UINT)-1 == uLevel)
    {
        //
        //  if the debug key is not present, then force debug output to
        //  be disabled. this way running a debug version of a component
        //  on a non-debugging machine will not generate output unless
        //  the debug key exists.
        //
        uLevel  = 0;
        fEnable = FALSE;
    }

    DbgSetLevel(uLevel);
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

//--------------------------------------------------------------------------;
//
//  void _Assert
//
//  Description:
//      This routine is called if the ASSERT macro (defined in debug.h)
//      tests and expression that evaluates to FALSE.  This routine
//      displays an "assertion failed" message box allowing the user to
//      abort the program, enter the debugger (the "retry" button), or
//      ignore the assertion and continue executing.  The message box
//      displays the file name and line number of the _Assert() call.
//
//  Arguments:
//      char *  szFile: Filename where assertion occurred.
//      int     iLine:  Line number of assertion.
//
//--------------------------------------------------------------------------;

#ifndef _WIN32
#pragma warning(disable:4704)
#endif

void WINAPI _Assert
(
    char *  szFile,
    int     iLine
)
{
    static char     ach[300];       // debug output (avoid stack overflow)
    int            id;
#ifndef _WIN32
    int             iExitCode;
#endif

    wsprintfA(ach, "Assertion failed in file %s, line %d.  [Press RETRY to debug.]", (LPSTR)szFile, iLine);

    id = MessageBoxA(NULL, ach, "Assertion Failed",
            MB_SYSTEMMODAL | MB_ICONHAND | MB_ABORTRETRYIGNORE );

switch (id)
{

case IDABORT:               // Kill the application.
#ifndef _WIN32
        iExitCode = 0;
        _asm
        {
        movah, 4Ch
        moval, BYTE PTR iExitCode
        int     21h
        }
#else
        FatalAppExit(0, TEXT("Good Bye"));
#endif // WIN16
break;

case IDRETRY:               // Break into the debugger.
DebugBreak();
break;

case IDIGNORE:              // Ignore assertion, continue executing.
break;
}
} // _Assert

#ifndef _WIN32
#pragma warning(default:4704)
#endif

#endif // #ifdef DEBUG


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by dsstream.rc
//
#define IDR_MAINMENU                    101
#define IDD_ABOUTBOX                    102
#define IDD_DSENUMBOX                   103
#define IDI_ICON1                       107
#define IDI_ICON2                       108
#define IDC_DSENUM_COMBO                1000
#define IDS_TBTITLE_PAN                 8192
#define IDS_TBTITLE_VOLUME              8193
#define IDS_TBTITLE_FREQUENCY           8194
#define IDS_TBTITLE_PROGRESS            8195
#define IDS_BUTTON_PLAY                 8196
#define IDS_BUTTON_STOP                 8197
#define IDS_CHECK_LOOPED                8198
#define IDS_APP_TITLE                   8208
#define IDS_APP_CAPTION                 8209
#define IDS_APP_NAME                    8210
#define IDS_OPEN_DLGTITLE               8211
#define IDS_OPEN_FILTER1                8212
#define IDS_OPEN_FILTER2                8213
#define IDS_OPEN_FILTER3                8214
#define IDS_OPEN_FILTER4                8215
#define IDS_ENUMWARNING                 8216
#define IDS_ERROR_APPINIT               8224
#define IDS_ERROR_INSTANCEINIT          8225
#define IDS_ERROR_REGISTERCLASS         8226
#define IDS_ERROR_MAINWNDCREATE         8227
#define IDS_ERROR_DSCREATE              8229
#define IDS_ERROR_WAVEFILEOPEN          8230
#define IDS_ERROR_WAVENOTPCM            8231
#define IDS_ERROR_WAVESEEKFAILED        8232
#define IDS_ERROR_DSBCREATE             8233
#define IDS_ERROR_PAN                   8234
#define IDS_ERROR_VOL                   8236
#define IDS_ERROR_FREQ                  8238
#define IDS_ERROR_DSCOOPERATIVE         8239
#define IDS_ERROR_QINOTIFY_FAILED       8240
#define IDS_ERROR_PROG                  8241
#define IDS_ERROR_CHILDTEMPLATE         8244
#define IDS_ERROR_TRACKBAR              8245
#define IDS_ERROR_BUTTON                8246
#define IDS_ERROR_STATICTEXT            8247
#define IDS_ERROR_GETTEXTEXTENT         8248
#define IDS_ERROR_CHECK                 8249
#define IDS_CDERR_GENERAL_BASE          16384
#define IDS_CDERR_FINDRESFAILURE        16385
#define IDS_CDERR_INITIALIZATION        16386
#define IDS_CDERR_LOADRESFAILURE        16387
#define IDS_CDERR_LOCKRESFAILURE        16388
#define IDS_CDERR_LOADSTRFAILURE        16389
#define IDS_CDERR_MEMALLOCFAILURE       16390
#define IDS_CDERR_MEMLOCKFAILURE        16391
#define IDS_CDERR_NOHINSTANCE           16392
#define IDS_CDERR_NOHOOK                16393
#define IDS_CDERR_NOTEMPLATE            16394
#define IDS_CDERR_REGISTERMSGFAIL       16395
#define IDS_CDERR_STRUCTSIZE            16396
#define IDS_CDERR_DIALOGFAILURE         16397
#define IDS_CDERR_TITLESTRING           16398
#define IDC_LOOPCHECK                   40001
#define IDM_FILE_EXIT                   40002
#define IDM_HELP_ABOUT                  40003
#define IDM_FILE_OPEN                   40004
#define IDM_FILE_CLOSE                  40005
#define IDM_PLAY                        40006
#define IDM_STOP                        40007
#define IDM_OPTIONS_ENUMDRIVERS         40008
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        111
#define _APS_NEXT_COMMAND_VALUE         40009
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


WASSERT.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wassert.c
 *  Content:Windows assert handler
 *              You must externally define hWndMain and szAppName for this
 *              to work.
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

extern HWND hWndMain;
extern char szAppName[];

#include "wassert.h"

#ifdef WASSERT
void AssertFail(char szErr[], char szFileName[], int nLine, char szMessage[])
{
char szT[256];

if (szMessage != NULL)
wsprintf(szT, "Assert(%s);\nFile %s, line %d.  %s", szErr, szFileName, nLine, szMessage);
else
wsprintf(szT, "Assert(%s);\nFile %s, line %d.", szErr, szFileName, nLine);
switch (MessageBox(hWndMain, szT, szAppName, MB_ABORTRETRYIGNORE | MB_ICONSTOP | MB_APPLMODAL))
{
case IDABORT:
SendMessage(hWndMain, WM_CLOSE, 0, 0);
case IDRETRY:
_asm int 3;
// Fall Through //
case IDIGNORE:
break;

} // switch
} // AssertFail

#endif // ASSERT


WASSERT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wassert.h
 *  Content:assert header
 *
 ***************************************************************************/
#ifndef __WASSERT_INCLUDED__
#define __WASSERT_INCLUDED__

#ifdef DEBUG
#define WASSERT          // Enable the Assert() macro
#endif

#ifdef WASSERT

#ifdef _cplusplus
extern "C" {
#endif

void AssertFail(char [], char [], int, char[]);
#define Assert(f) ((f) ? (void)NULL : (void)AssertFail(#f, __FILE__, __LINE__,NULL))
#define AssertMessage(f, szMessage) ((f) ? (void)NULL : (void)AssertFail(#f, __FILE__, __LINE__, szMessage))

#else
#define Assert(f) (void)NULL// Macro that does nothing
#define AssertMessage(f, szMessage) (void)NULL

#ifdef _cplusplus
};
#endif

#endif // ~ASSERT

#endif


README.TXT

Duel Sample Game (x86 only)
---------------------------

This program requires less than 1 Meg of video ram.

The commands which this game recognizes are listed on the opening screen.

    ESC, F12        - Quit
    NUMPAD 4    - Turn left
    NUMPAD 6    - Turn right
    NUMPAD 5    - Stop moving
    NUMPAD 8    - Accelerate forward
    NUMPAD 2    - Accelerate backward
    SPACEBAR    - Fire
    ENTER    - Starts game
    F5              - toggle frame rate display

To play multiplayer one machine hosts a game, the others join.

To play using TCP/IP over the Internet, the people who are joining the
game must enter the IP address of the machine that hosted the game.
You can find out the IP address of your machine by running "winipcfg".
If you have a net card and a modem installed, you will need to make
sure you read the IP address of the modem connection to the Internet.
The modem will show up as a "PPP Adapter".  DirectPlay will not work
through proxies or firewalls.

Also see the DPLAUNCH example.


COMM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       comm.c
 *  Content:    DirectPlay related code
 *
 *
 ***************************************************************************/
#include "comm.h"
#include "lobby.h"

/*
 * Externals
 */
extern LPGUIDglpGuid;// duel's guid
extern LPDPLCONNECTIONglpdplConnection;// connection settings

/*
 * Globals
 */
LPDPSESSIONDESC2glpdpSD;// current session description
LPDIRECTPLAY2glpDP= NULL;// directplay object pointer
LPDIRECTPLAY3AglpDP3A= NULL;

/*
 * DPlayClose
 *
 * Wrapper for DirectPlay Close API
 */
HRESULT DPlayClose(void)
{
HRESULT hr=E_FAIL;

if (glpDP)
hr = IDirectPlay2_Close(glpDP);

return hr;
}

/*
 * DPlayCreate
 *
 * Wrapper for DirectPlay Create API. Retrieves a DirectPlay2/DirectPlay2A interface
 * based on the UNICODE flag
 *
 */
HRESULT DPlayCreate(LPVOID lpCon)
{
HRESULThr= E_FAIL;
LPDIRECTPLAY3lpDP3= NULL;

hr = CoCreateInstance(&CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
&IID_IDirectPlay3A, (LPVOID *) &glpDP3A );

if ( DP_OK == hr )
{
if (lpCon)
{
hr = IDirectPlay3_InitializeConnection( glpDP3A, lpCon, 0);
}

// query for a DirectPlay2(A) interface
#ifdef UNICODE
hr = IDirectPlay_QueryInterface(glpDP3A,&IID_IDirectPlay2,(LPVOID *)&glpDP);
#else
hr = IDirectPlay_QueryInterface(glpDP3A,&IID_IDirectPlay2A,(LPVOID *)&glpDP);
#endif

}

return hr;
}

/*
 * DPlayCreatePlayer
 *
 * Wrapper for DirectPlay CreatePlayer API.
 */

HRESULT DPlayCreatePlayer(LPDPID lppidID, LPTSTR lptszPlayerName, HANDLE hEvent,
  LPVOID lpData, DWORD dwDataSize)
{
HRESULT hr=E_FAIL;
DPNAME name;

ZeroMemory(&name,sizeof(name));
name.dwSize = sizeof(DPNAME);

#ifdef UNICODE
name.lpszShortName = lptszPlayerName;
#else
name.lpszShortNameA = lptszPlayerName;
#endif

if (glpDP)
hr = IDirectPlay2_CreatePlayer(glpDP, lppidID, &name, hEvent, lpData,
  dwDataSize, 0);

return hr;
}

/*
 * DPlayCreateSession
 *
 * Wrapper for DirectPlay CreateSession API.Uses the global application guid (glpGuid).
 */
HRESULT DPlayCreateSession(LPTSTR lptszSessionName)
{
HRESULT hr = E_FAIL;
DPSESSIONDESC2 dpDesc;

    ZeroMemory(&dpDesc, sizeof(dpDesc));
    dpDesc.dwSize = sizeof(dpDesc);
dpDesc.dwFlags = DPSESSION_MIGRATEHOST | DPSESSION_KEEPALIVE;

#ifdef UNICODE
dpDesc.lpszSessionName = lptszSessionName;
#else
dpDesc.lpszSessionNameA = lptszSessionName;
#endif

// set the application guid
if (glpGuid)
dpDesc.guidApplication = *glpGuid;

if (glpDP)
hr = IDirectPlay2_Open(glpDP, &dpDesc, DPOPEN_CREATE);

return hr;
}

/*
 * DPlayDestroyPlayer
 *
 * Wrapper for DirectPlay DestroyPlayer API.
 */
HRESULT DPlayDestroyPlayer(DPID pid)
{
HRESULT hr=E_FAIL;

    if (glpDP)
hr = IDirectPlay2_DestroyPlayer(glpDP, pid);

return hr;
}

/*
 * DPlayEnumPlayers
 *
 * Wrapper for DirectPlay API EnumPlayers
 */
HRESULT DPlayEnumPlayers(LPGUID lpSessionGuid, LPDPENUMPLAYERSCALLBACK2 lpEnumCallback,
 LPVOID lpContext, DWORD dwFlags)
{
HRESULT hr=E_FAIL;

if (glpDP)
hr = IDirectPlay2_EnumPlayers(glpDP, lpSessionGuid, lpEnumCallback, lpContext, dwFlags);

return hr;
}

/*
 * DPlayEnumSessions
 *
 * Wrapper for DirectPlay EnumSessions API.
 */
HRESULT DPlayEnumSessions(DWORD dwTimeout, LPDPENUMSESSIONSCALLBACK2 lpEnumCallback,
  LPVOID lpContext, DWORD dwFlags)
{
HRESULT hr = E_FAIL;
    DPSESSIONDESC2 dpDesc;

ZeroMemory(&dpDesc, sizeof(dpDesc));
dpDesc.dwSize = sizeof(dpDesc);
if (glpGuid)
dpDesc.guidApplication = *glpGuid;

if (glpDP)
hr = IDirectPlay2_EnumSessions(glpDP, &dpDesc, dwTimeout, lpEnumCallback,
lpContext, dwFlags);

return hr;
}

/*
 * DPlayGetPlayerData
 *
 * Wrapper for DirectPlay GetPlayerData API.
 */
HRESULT DPlayGetPlayerData(DPID pid, LPVOID lpData, LPDWORD lpdwDataSize, DWORD dwFlags)
{
HRESULT hr=E_FAIL;

if (glpDP)
hr = IDirectPlay2_GetPlayerData(glpDP, pid, lpData, lpdwDataSize, dwFlags);

return hr;
}

/*
 * DPlayGetSessionDesc
 *
 * Wrapper for DirectPlay GetSessionDesc API.
 */
HRESULT DPlayGetSessionDesc(void)
{
HRESULT hr=E_FAIL;
DWORD dwSize;

// free old session desc, if any
if (glpdpSD)
{
free(glpdpSD);
glpdpSD = NULL;
}

if (glpDP)
{
// first get the size for the session desc
if ((hr = IDirectPlay2_GetSessionDesc(glpDP, NULL, &dwSize)) == DPERR_BUFFERTOOSMALL)
{
// allocate memory for it
glpdpSD = (LPDPSESSIONDESC2) malloc(dwSize);
if (glpdpSD)
{
// now get the session desc
hr = IDirectPlay2_GetSessionDesc(glpDP, glpdpSD, &dwSize);
}
else
{
hr = E_OUTOFMEMORY;
}
}
}

return hr;
}

/*
 * IsDPlay
 *
 * Returns TRUE if a DirectPlay interface exists, otherwise FALSE.
 */
BOOL IsDPlay(void)
{
return (glpDP ? TRUE:FALSE);
}

/*
 * DPlayOpenSession
 *
 * Wrapper for DirectPlay OpenSession API.
 */
HRESULT DPlayOpenSession(LPGUID lpSessionGuid)
{
HRESULT hr = E_FAIL;
DPSESSIONDESC2 dpDesc;

    ZeroMemory(&dpDesc, sizeof(dpDesc));
    dpDesc.dwSize = sizeof(dpDesc);

// set the session guid
if (lpSessionGuid)
dpDesc.guidInstance = *lpSessionGuid;
// set the application guid
if (glpGuid)
dpDesc.guidApplication = *glpGuid;

// open it
if (glpDP)
hr = IDirectPlay2_Open(glpDP, &dpDesc, DPOPEN_JOIN);

return hr;
}

/*
 * DPlayReceive
 *
 * Wrapper for DirectPlay Receive API
 */
HRESULT DPlayReceive(LPDPID lpidFrom, LPDPID lpidTo, DWORD dwFlags, LPVOID lpData, LPDWORD lpdwDataSize)
{
HRESULT hr = E_FAIL;

if (glpDP)
hr = IDirectPlay2_Receive(glpDP, lpidFrom, lpidTo, dwFlags, lpData, lpdwDataSize);

return hr;
}

/*
 * DPlayRelease
 *
 * Wrapper for DirectPlay Release API.
 */
HRESULT DPlayRelease(void)
{
HRESULT hr = E_FAIL;

if (glpDP)
{
// free session desc, if any
if (glpdpSD)
{
free(glpdpSD);
glpdpSD = NULL;
}

// free connection settings structure, if any (lobby stuff)
if (glpdplConnection)
{
free(glpdplConnection);
glpdplConnection = NULL;
}
// release dplay
hr = IDirectPlay2_Release(glpDP);
glpDP = NULL;
}

if (glpDP3A)
{
hr = IDirectPlay3_Release(glpDP3A);
glpDP3A = NULL;
}

return hr;
}

/*
 * DPlaySend
 *
 * Wrapper for DirectPlay Send API.
 */
HRESULT DPlaySend(DPID idFrom, DPID idTo, DWORD dwFlags, LPVOID lpData, DWORD dwDataSize)
{
HRESULT hr = E_FAIL;

if (glpDP)
hr = IDirectPlay2_Send(glpDP, idFrom, idTo, dwFlags, lpData, dwDataSize);

return hr;
}

/*
 * DPlaySetPlayerData
 *
 * Wrapper for DirectPlay SetPlayerData API
 */
HRESULT DPlaySetPlayerData(DPID pid, LPVOID lpData, DWORD dwSize, DWORD dwFlags)
{
HRESULT hr=E_FAIL;

if (glpDP)
hr = IDirectPlay2_SetPlayerData(glpDP, pid, lpData, dwSize, dwFlags);

return hr;
}


COMM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:comm.h
 *  Content:communication routines include file
 *
 *
 ***************************************************************************/
#define IDIRECTPLAY2_OR_GREATER
#include <dplay.h>
#include "duel.h"

/*
 * Prototypes
 */
HRESULT DPlayClose(void);
HRESULT DPlayCreate(LPVOID lpCon);
HRESULT DPlayCreatePlayer(LPDPID lppidID, LPTSTR lpPlayerName, HANDLE hEvent,
  LPVOID lpData, DWORD dwDataSize);
HRESULT DPlayCreateSession(LPTSTR lptszSessionName);
HRESULT DPlayDestroyPlayer(DPID pid);
HRESULT DPlayEnumPlayers(LPGUID lpSessionGuid, LPDPENUMPLAYERSCALLBACK2 lpEnumCallback,
 LPVOID lpContext, DWORD dwFlags);
HRESULT DPlayEnumSessions(DWORD dwTimeout, LPDPENUMSESSIONSCALLBACK2 lpEnumCallback,
  LPVOID lpContext, DWORD dwFlags);
HRESULT DPlayGetPlayerData(DPID pid, LPVOID lpData, LPDWORD lpdwDataSize, DWORD dwFlags);
HRESULT DPlayGetSessionDesc(void);
BOOLIsDPlay(void);
HRESULT DPlayOpenSession(LPGUID lpSessionGuid);
HRESULT DPlayReceive(LPDPID lpidFrom, LPDPID lpidTo, DWORD dwFlags, LPVOID lpData,
 LPDWORD lpdwDataSize);
HRESULT DPlayRelease(void);
HRESULT DPlaySend(DPID idFrom, DPID idTo, DWORD dwFlags, LPVOID lpData,
  DWORD dwDataSize);
HRESULT DPlaySetPlayerData(DPID pid, LPVOID lpData, DWORD dwSize, DWORD dwFlags);


DDUTIL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddutil.h"

/*
 *  DDLoadBitmap
 *
 *  create a DirectDrawSurface from a bitmap resource.
 *
 */
extern "C" IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCTSTR tszBitmap, int dx, int dy)
{
    HBITMAP             hbm;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    IDirectDrawSurface *pdds;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), tszBitmap, IMAGE_BITMAP, dx, dy, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, tszBitmap, IMAGE_BITMAP, dx, dy, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
return NULL;

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);      // get size of bitmap

    //
    // create a DirectDrawSurface for this bitmap
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
return NULL;

    DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);

    DeleteObject(hbm);

    return pdds;
}

/*
 *  DDReLoadBitmap
 *
 *  load a bitmap from a file or resource into a directdraw surface.
 *  normaly used to re-load a surface after a restore.
 *
 */
HRESULT DDReLoadBitmap(IDirectDrawSurface *pdds, LPCTSTR tszBitmap)
{
    HBITMAP             hbm;
    HRESULT             hr;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), tszBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, tszBitmap, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
    {
OutputDebugString(TEXT("handle is null\n"));
return E_FAIL;
    }

    hr = DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);
    if (hr != DD_OK)
    {
OutputDebugString(TEXT("ddcopybitmap failed\n"));
    }

    DeleteObject(hbm);
    return hr;
}

/*
 *  DDCopyBitmap
 *
 *  draw a bitmap into a DirectDrawSurface
 *
 */
extern "C" HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy)
{
    HDC                 hdcImage;
    HDC                 hdc;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    HRESULT             hr;

    if (hbm == NULL || pdds == NULL)
return E_FAIL;

    //
    // make sure this surface is restored.
    //
    pdds->Restore();

    //
    //  select bitmap into a memoryDC so we can use it.
    //
    hdcImage = CreateCompatibleDC(NULL);
    if (!hdcImage)
OutputDebugString(TEXT("createcompatible dc failed\n"));
    SelectObject(hdcImage, hbm);

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);    // get size of bitmap
    dx = dx == 0 ? bm.bmWidth  : dx;    // use the passed size, unless zero
    dy = dy == 0 ? bm.bmHeight : dy;

    //
    // get size of surface.
    //
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
    pdds->GetSurfaceDesc(&ddsd);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)
    {
StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);
pdds->ReleaseDC(hdc);
    }

    DeleteDC(hdcImage);

    return hr;
}

//
//  DDLoadPalette
//
//  Create a DirectDraw palette object from a bitmap resoure
//
//  if the resource does not exist or NULL is passed create a
//  default 332 palette.
//
extern "C" IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCTSTR tszBitmap)
{
    IDirectDrawPalette* ddpal;
    int                 i;
    int                 n;
HANDLE              fh;
    HRSRC               h;
    LPBITMAPINFOHEADER  lpbi;
    PALETTEENTRY        ape[256];
    RGBQUAD *           prgb;
DWORDdwRead;

    //
    // build a 332 palette as the default.
    //
    for (i=0; i<256; i++)
    {
ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
ape[i].peFlags = (BYTE)0;
    }

    //
    // get a pointer to the bitmap resource.
    //
    if (tszBitmap && (h = FindResource(NULL, tszBitmap, RT_BITMAP)))
    {
lpbi = (LPBITMAPINFOHEADER)LockResource(LoadResource(NULL, h));
if (!lpbi)
    OutputDebugString(TEXT("lock resource failed\n"));
prgb = (RGBQUAD*)((BYTE*)lpbi + lpbi->biSize);

if (lpbi == NULL || lpbi->biSize < sizeof(BITMAPINFOHEADER))
    n = 0;
else if (lpbi->biBitCount > 8)
    n = 0;
else if (lpbi->biClrUsed == 0)
    n = 1 << lpbi->biBitCount;
else
    n = lpbi->biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    ape[i].peRed   = prgb[i].rgbRed;
    ape[i].peGreen = prgb[i].rgbGreen;
    ape[i].peBlue  = prgb[i].rgbBlue;
    ape[i].peFlags = 0;
}
    }
    else if (tszBitmap && (fh = CreateFile(tszBitmap, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
    {
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;

ReadFile(fh, &bf, sizeof(bf), &dwRead, NULL);
ReadFile(fh, &bi, sizeof(bi), &dwRead, NULL);
ReadFile(fh, ape, sizeof(ape), &dwRead, NULL);
CloseHandle(fh);

if (bi.biSize != sizeof(BITMAPINFOHEADER))
    n = 0;
else if (bi.biBitCount > 8)
    n = 0;
else if (bi.biClrUsed == 0)
    n = 1 << bi.biBitCount;
else
    n = bi.biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    BYTE r = ape[i].peRed;
    ape[i].peRed  = ape[i].peBlue;
    ape[i].peBlue = r;
}
    }

    pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);

    return ddpal;
}

/*
 * DDColorMatch
 *
 * convert a RGB color to a pysical color.
 *
 * we do this by leting GDI SetPixel() do the color matching
 * then we lock the memory and see what it got mapped to.
 */
extern "C" DWORD DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb)
{
    COLORREF rgbT;
    HDC hdc;
    DWORD dw = CLR_INVALID;
    DDSURFACEDESC ddsd;
    HRESULT hres;

    //
    //  use GDI SetPixel to color match for us
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
rgbT = GetPixel(hdc, 0, 0);             // save current pixel value
SetPixel(hdc, 0, 0, rgb);               // set our value
pdds->ReleaseDC(hdc);
    }

    //
    // now lock the surface so we can read back the converted color
    //
    ddsd.dwSize = sizeof(ddsd);
    while ((hres = pdds->Lock(NULL, &ddsd, 0, NULL)) == DDERR_WASSTILLDRAWING)
;

    if (hres == DD_OK)
    {
dw  = *(DWORD *)ddsd.lpSurface;                     // get DWORD
dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount)-1;  // mask it to bpp
pdds->Unlock(NULL);
    }

    //
    //  now put the color that was there back.
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
SetPixel(hdc, 0, 0, rgbT);
pdds->ReleaseDC(hdc);
    }

    return dw;
}

/*
 * DDSetColorKey
 *
 * set a color key for a surface, given a RGB.
 * if you pass CLR_INVALID as the color key, the pixel
 * in the upper-left corner will be used.
 */
extern "C" HRESULT DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb)
{
    DDCOLORKEY          ddck;

    ddck.dwColorSpaceLowValue  = DDColorMatch(pdds, rgb);
    ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;
    return pdds->SetColorKey(DDCKEY_SRCBLT, &ddck);
}


DDUTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/

#include <tchar.h>

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif/* __cplusplus */

extern IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCTSTR szBitmap);
extern IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCTSTR szBitmap, int dx, int dy);
extern HRESULT              DDReLoadBitmap(IDirectDrawSurface *pdds, LPCTSTR szBitmap);
extern HRESULT              DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy);
extern DWORD                DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb);
extern HRESULT              DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb);

#ifdef __cplusplus
}
#endif/* __cplusplus */


DS3DUTIL.C

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ds3dutil.c
 *  Content:    Routines for dealing with sounds from resources
 *              The last 3 functions (the wave file parsing code)
 *              are copied from dsutil.cpp
 *
 *
 ***************************************************************************/
#include <tchar.h>
#include <dsound.h>
#include "ds3dutil.h"
//*************************** FUNCTION PROTOTYPES **************************/
BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader,
                         BYTE **ppbWaveData,DWORD *pcbWaveSize);
BOOL DSGetWaveResource ( HMODULE hModule, LPCTSTR lpName,
                         WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,
                         DWORD *pcbWaveSize);
BOOL DSFillSoundBuffer(  IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize);
BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName);

//*************************** GLOBAL VARIABLES **************************/

_TCHAR gszWaveString[5] = _T("WAVE");

/*****************************************************************************
FUNCTION:  WaveInit

PURPOSE:   Loads a WAVEDATA struct with the named WAVE resource.

PARAMETERS:
lplpWD  A pointer to a WAVEDATA struct.

lpDS:   A pointer to an IDirectSound object.  This object must be,
        initialized BEFORE being passed to this function, and MUST have been
        initialized with the DSBCAPS_CTRL3D flag.

lpName: A string that contains the name of the .WAV resource.
*****************************************************************************/
BOOL WaveInit(LPWAVEDATA *lplpWD, LPDIRECTSOUND lpDS, LPCTSTR lpName)
{
DSBUFFERDESC dsBD = {0};
BYTE *pbWaveData=NULL;
LPWAVEDATA lpWD;

    lpWD = (*lplpWD) = (LPWAVEDATA)malloc(sizeof(WAVEDATA));
    lpWD->lpDirectSoundBuffer = NULL;
    lpWD->lpName = NULL;
    lpWD->lpDS = NULL;

    #ifndef UNICODE
        lpWD->lpName = (LPTSTR)malloc(strlen(lpName) + 1);
    #else
        lpWD->lpName = (LPTSTR)malloc(2*(wcslen(lpName) + 1));
    #endif
    if (lpWD->lpName != NULL)
    {
        _tcscpy(lpWD->lpName, lpName);

        if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
        {
            dsBD.dwSize = sizeof(DSBUFFERDESC);
            dsBD.dwFlags = DSBCAPS_STATIC     | DSBCAPS_GETCURRENTPOSITION2  | DSBCAPS_CTRL3D;
            if (DS_OK == IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpWD->lpDirectSoundBuffer, NULL))
            {
                if (DSFillSoundBuffer(lpWD->lpDirectSoundBuffer, pbWaveData, dsBD.dwBufferBytes))
                {
                    lpWD->lpDS = lpDS; //also copy the DirectSoundObject
                    return(TRUE);
                }
                IDirectSoundBuffer_Release(lpWD->lpDirectSoundBuffer);
                lpWD->lpDirectSoundBuffer = NULL;
            }
        }
        free(lpWD->lpName);
        lpWD->lpName = NULL;
    }
    free(*lplpWD);
    *lplpWD = NULL;
    return(FALSE);
};

/*****************************************************************************
FUNCTION:   WaveGetBuffers

PARAMS:     lplpWD:
                A pointer to a WAVEDATA struct.
            lplpDirectSoundBuffer:
                The address of a pointer to a DirectSoundBuffer Interface.
            lplpDirectSound3DBuffer:
                The address of a pointer to a DirectSound3DBuffer Interface.
            bOurShip:
                A Boolean telling whether the ship requesting the sound
                buffers is OUR ship.  If it is our ship, then instead of
                duplicating sound buffers for it we just copy the global
                pointers in.  That way we make sure that the global
                pointers are actually being put to good use before we
                go allocating more resources.

            Both of these must be Release()'d after their use.

PURPOSE:    Gives the caller back two interface pointers:  one to a regular
            DirectSoundBuffer interface and one to a 3D buffer interface.  These
            MUST be Released() by the caller.

RETURNS:    Should return TRUE, unless something really catastrophic has happened.

NOTES:      BOTH of these Interfaces MUST be ->Release()'d when the object
            or whatever is done using them.  Otherwise the buffer they point
            to will just hang around and waste memory.  This wastes processing time,
            since each buffer is checked by the mixer quite frequently to see if it
            is playing.

            Defaults:  Both Position and Velocity vectors are set to (0,0,0).

**************************************************************************************/
BOOL WaveGetBuffers (LPWAVEDATA lpWD,
                    LPDIRECTSOUNDBUFFER   *lplpDirectSoundBuffer,
                    LPDIRECTSOUND3DBUFFER *lplpDirectSound3DBuffer,
                    BOOL bOurShip)
{
DSBCAPS dsbc; //in case there's a problem duplicating hardware buffer
DSBUFFERDESC dsBD   ={0};
BYTE *pbWaveData    =NULL;

    *lplpDirectSoundBuffer = NULL;
    *lplpDirectSound3DBuffer = NULL;
    //so if we get to "failed:", we know what worked and what didn't.
    if (bOurShip)
    {//This is OUR ship, so we'll copy the buffer pointer.
        *lplpDirectSoundBuffer   = lpWD->lpDirectSoundBuffer;
    }
    else
    {
        //This is not our ship, so we'll have to dup a buffer or create a new one.
        if (DS_OK!=IDirectSound_DuplicateSoundBuffer(lpWD->lpDS, lpWD->lpDirectSoundBuffer, lplpDirectSoundBuffer))
        {
            //degrading to software
            dsbc.dwSize = sizeof(dsbc);
            if (DS_OK!=IDirectSoundBuffer_GetCaps(lpWD->lpDirectSoundBuffer, &dsbc))
            {
                goto failed;
            }
            //was the original one in hardware?
            if (DSBCAPS_LOCHARDWARE & dsbc.dwFlags)
            {
                //It's a HARDWARE buffer? Then we need to create a software sound
                //buffer, not duplicate this hardware one.
                if (DSGetWaveResource(NULL, lpWD->lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
                {
                    dsBD.dwSize = sizeof(DSBUFFERDESC);
                    dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_GETCURRENTPOSITION2  | DSBCAPS_CTRL3D;
                    if (DS_OK == IDirectSound_CreateSoundBuffer(lpWD->lpDS, &dsBD, lplpDirectSoundBuffer, NULL))
                    {
                        if (!DSFillSoundBuffer(*lplpDirectSoundBuffer, pbWaveData, dsBD.dwBufferBytes))
                        {//DSFillSoundBuffer failed when creating software buffer for ship.
                            goto failed;
                        }
                    }
                    else
                    {//CreateSoundBuffer failed when creating software buffer for ship.
                        goto failed;
                    }
                }
                else
                {//GetWaveResource failed trying to duplicating a SOFTWARE buffer for ship.
                    goto failed;
                }
            }
            else
            {//it wasn't even trying to duplicate a hardware buffer, and
             //could not duplicate a software buffer.
                goto failed;
            }
        } //end the duplicate-regular-soundbuffer part.
    }

    //now we'll try to get a 3D buffer.
    if (DS_OK==IDirectSoundBuffer_QueryInterface(*lplpDirectSoundBuffer, &IID_IDirectSound3DBuffer, (void **)lplpDirectSound3DBuffer))
    {
        return TRUE; //buffer was allocated!!!!
    }
    else
    {
    //Query for a 3D SoundBuffer Failed!!
        goto failed;
    }

failed:
    if (*lplpDirectSoundBuffer!=NULL)
    {
        if (!bOurShip)
            IDirectSoundBuffer_Release(*lplpDirectSoundBuffer);
        *lplpDirectSoundBuffer=NULL;
    }

    if (*lplpDirectSound3DBuffer!=NULL)
    {
        IDirectSoundBuffer_Release(*lplpDirectSound3DBuffer);
        *lplpDirectSound3DBuffer=NULL;
    }
    return FALSE;
};

/*****************************************************************************
FUNCTION:  WaveFree

PURPOSE:   When this is called, we HOPE that all the buffers
           (both the normal and the 3D ones) duplicated from "lpDirectSoundBuffer"
           in the function WaveGetBuffers() have been ->Release()'d.  This frees
           up the original buffer (lpDirectSoundBuffer) that all the other buffers
           have been duplicated from.
*****************************************************************************/
void WaveFree(LPWAVEDATA lpWD)
{
    if (lpWD!=NULL)
    {
        if (lpWD->lpDirectSoundBuffer != NULL)
          IDirectSoundBuffer_Release(lpWD->lpDirectSoundBuffer);
        if (lpWD->lpName != NULL)
            free(lpWD->lpName);
        free(lpWD);
    }
};

/*****************************************************************************
FUNCTION:   WaveReload

PURPOSE:    This reloads the wave's sound effect.
*****************************************************************************/
BOOL WaveReload(LPWAVEDATA lpWD)
{
    return DSReloadSoundBuffer(lpWD->lpDirectSoundBuffer, lpWD->lpName);
};

/*****************************************************************************
FUNCTION:  DSGetWaveResource

PURPOSE:   Sets the following:
            a. A pointer to a WAV file header structure
            b. A pointer to the WAV file data.
            c. A DWORD which is the length of the data (in bytes, not samples)
*****************************************************************************/
BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
                       WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,
                       DWORD *pcbWaveSize)
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    void *pvRes;

    if (((hResInfo = FindResource(hModule, lpName, gszWaveString)) != NULL) &&
        ((hResData = LoadResource(hModule, hResInfo)) != NULL) &&
        ((pvRes = LockResource(hResData)) != NULL) &&
        DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize))
    {
        return TRUE;
    }
    return FALSE;
}

/*****************************************************************************
FUNCTION:   DSParseWaveResource

PURPOSE:    This does the real meat of the file parsing, and is called by
            DSGetWaveResource
*****************************************************************************/
BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader,
                         BYTE **ppbWaveData,DWORD *pcbWaveSize)
{
    DWORD *pdw;
    DWORD *pdwEnd;
    DWORD dwRiff;
    DWORD dwType;
    DWORD dwLength;

    if (ppWaveHeader)
        *ppWaveHeader = NULL;

    if (ppbWaveData)
        *ppbWaveData = NULL;

    if (pcbWaveSize)
        *pcbWaveSize = 0;

    pdw = (DWORD *)pvRes;
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
        goto exit;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
        goto exit;      // not a WAV

    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);

    while (pdw < pdwEnd)
    {
        dwType = *pdw++;
        dwLength = *pdw++;

        switch (dwType)
        {
        case mmioFOURCC('f', 'm', 't', ' '):
            if (ppWaveHeader && !*ppWaveHeader)
            {
                if (dwLength < sizeof(WAVEFORMAT))
                    goto exit;      // not a WAV

                *ppWaveHeader = (WAVEFORMATEX *)pdw;

                if ((!ppbWaveData || *ppbWaveData) &&
                    (!pcbWaveSize || *pcbWaveSize))
                {
                    return TRUE;
                }
            }
            break;

        case mmioFOURCC('d', 'a', 't', 'a'):
            if ((ppbWaveData && !*ppbWaveData) ||
                (pcbWaveSize && !*pcbWaveSize))
            {
                if (ppbWaveData)
                    *ppbWaveData = (LPBYTE)pdw;

                if (pcbWaveSize)
                    *pcbWaveSize = dwLength;

                if (!ppWaveHeader || *ppWaveHeader)
                    return TRUE;
            }
            break;
        }

        pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
    }

exit:
    return FALSE;
}

/*****************************************************************************
FUNCTION:   DSFillSoundBuffer

PURPOSE:    Given an already-initialized DirectSoundBuffer, and pointer to
            some sound data, and the sound data's size, this writes the data
            to the DirectSoundBuffer.
*****************************************************************************/
BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize)
{
    if (pDSB && pbWaveData && cbWaveSize)
    {
        LPVOID pMem1, pMem2;
        DWORD dwSize1, dwSize2;

        if (SUCCEEDED(IDirectSoundBuffer_Lock(pDSB, 0, cbWaveSize,
            &pMem1, &dwSize1, &pMem2, &dwSize2, 0)))
        {
            ZeroMemory(pMem1, dwSize1);
            CopyMemory(pMem1, pbWaveData, dwSize1);

            if ( 0 != dwSize2 )
                CopyMemory(pMem2, pbWaveData+dwSize1, dwSize2);

            IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
            return TRUE;
        }
    }
    return FALSE;
}

/*****************************************************************************
FUNCTION:   DSReloadSoundBuffer

PURPOSE:    If an application with WRITE_PRIMARY takes the focus from our
            humble window, then our secondary sound buffers become "lost" and
            we must call restore and re-fill them.
*****************************************************************************/
BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName)
{
    BOOL result=FALSE;
    BYTE *pbWaveData;
    DWORD cbWaveSize;

    if (DSGetWaveResource(NULL, lpName, NULL, &pbWaveData, &cbWaveSize))
    {
        if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)))
    {
        if (DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
            {
        result = TRUE;
            }
        }

    }
    return result;
}


DS3DUTIL.H

#ifndef DS3DUTIL_HEADER
#define DS3DUTIL_HEADER

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ds3dutil.h
 *  Content:    Header file prototyping routines for dealing with
 *              sounds from resources.   Much of this stuff
 *              (especially the wave file parsing code) is copied
 *              from dsutil.cpp
 *
 *
 ***************************************************************************/
#include <dsound.h>

typedef struct
{
LPDIRECTSOUNDBUFFER lpDirectSoundBuffer;
LPTSTR lpName;
LPDIRECTSOUND lpDS;
}
WAVEDATA, *LPWAVEDATA;

BOOL WaveInit(LPWAVEDATA *lplpWD, LPDIRECTSOUND lpDS, LPCTSTR lpName);
BOOL WaveGetBuffers(LPWAVEDATA lpWD,
                    LPDIRECTSOUNDBUFFER   *ppDirectSoundBuffer,
                    LPDIRECTSOUND3DBUFFER *ppDirectSound3DBuffer,
                    BOOL bOurShip);
void WaveFree(LPWAVEDATA lpWD);
BOOL WaveReload(LPWAVEDATA lpWD);

#endif


DUEL.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       duel.c
 *  Content:    Multi-player duel
 *
 *
 ***************************************************************************/
#define INITGUID
#include "duel.h"
#include "gameproc.h"
#include "gfx.h"
#include "comm.h"
#include "input.h"
#include "lobby.h"
#include "wizard.h"
#include "util.h"
#include "sfx.h"

// {33925241-05F8-11d0-8063-00A0C90AE891}
DEFINE_GUID(DUEL_GUID,
0x33925241, 0x5f8, 0x11d0, 0x80, 0x63, 0x0, 0xa0, 0xc9, 0xa, 0xe8, 0x91);

/*
 * Externals
 */
extern DWORDgdwFrameCount;
extern DWORDgdwFrameTime;
extern intgnProgramState;
extern SHIPgOurShip;
extern LPDPLCONNECTIONglpdplConnection;
extern DPIDgOurID;
extern BOOL             gbNoField;

/*
 * Globals
 */
LPGUID                  glpGuid;// Duel's GUID
HWND                    ghWndMain;// Main application window handle
HINSTANCEghinst;// Application instance handle
BOOL                    gbShowFrameCount=TRUE;// Show FPS ?
BOOL                    gbIsActive;// Is the application active ?
BOOL                    gbUseEmulation;// DDHEL or DDHAL for Graphics
BOOLgbIsHost;// Are we hosting or joining a game
DWORD                   gdwKeys;// User keyboard input
DWORD                   gdwOldKeys;             // Last frame's keyboard input
BOOLgbFullscreen=FALSE;// Window or FullScreen mode ?
RECTgrcWindow;// client rectangle of main window
HANDLEghThread;// handle to wizard thread
TCHARgtszClassName[MAX_CLASSNAME]; // Duel's class name
BOOL                    gbReliable;             // sends are reliable

/*
 * Statics
 */
static BOOLgbReinitialize;// used for switching display modes

/*
 * WinMain
 */
int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                        int nCmdShow )
{
BOOL bHelp=FALSE;
    MSG     msg;

ghinst = hinstance;

CoInitialize( NULL );

// Parse command line
    while( lpCmdLine[0] == '-' )
    {
        lpCmdLine++;

        switch (*lpCmdLine++)
        {
        case 'e':
            gbUseEmulation = TRUE;
            break;
        case 'd':
            gbNoField = TRUE;
            break;
        case '?':
default:
            bHelp= TRUE;
            gbFullscreen= FALSE;  // give help in windowed mode
            break;
        }

        while( isspace(*lpCmdLine) )
{
lpCmdLine++;
}
    }

    /*
     * Give user help if asked for
     */

    if( bHelp )
    {
TCHAR tszHelpMsg[MAX_HELPMSG];
TCHAR tszTitle[MAX_WINDOWTITLE];

LoadString(ghinst, IDS_DUEL_HELP, tszHelpMsg, MAX_HELPMSG);
LoadString(ghinst, IDS_DUEL_TITLE, tszTitle, MAX_WINDOWTITLE);
        MessageBox(ghWndMain, tszHelpMsg, tszTitle, MB_OK );
return TRUE;
    }

    if( !InitApplication(hinstance) )
    {
        return FALSE;
    }

// were we launched by a lobby ?
    if (LaunchedByLobby())
    {
// start game
PostMessage(ghWndMain, UM_LAUNCH, 0, 0);
        gbIsActive = TRUE;
    }

    gdwFrameTime = timeGetTime();

while( TRUE )
    {
        if (gbIsActive)
        {
            // any windows messages ? (returns immediately)
            if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
            {
                if( !GetMessage( &msg, NULL, 0, 0 ) )
                {
                    return msg.wParam;
                }
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else
            {
                // Poll our receive queue. Polling is used in the sample only for simplicity.
                // Receiving messages using an event is the recommended way.
        if (gnProgramState != PS_SPLASH)
                {
                    ReceiveMessages();
                }

                // update screen
    if (!UpdateFrame())
                {
                    ExitGame();
                }
    }
        }
        else
        {
            // any windows messages ? (blocks until a message arrives)
            if( !GetMessage( &msg, NULL, 0, 0 ) )
            {
                return msg.wParam;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

CoUninitialize();

} /* WinMain */

/*
 * MainWndproc
 *
 * Callback for all Windows messages
 */
long WINAPI MainWndproc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;
DWORD dwRetCode;
DWORD dwTid;

    switch( message )
    {
    case WM_SIZE:
    case WM_MOVE:
// get the client rectangle
        if (gbFullscreen)
        {
            SetRect(&grcWindow, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
        }
        else
        {
            GetClientRect(hWnd, &grcWindow);
            ClientToScreen(hWnd, (LPPOINT)&grcWindow);
            ClientToScreen(hWnd, (LPPOINT)&grcWindow+1);
        }
break;

    case WM_ACTIVATE:
// ignore this message during reinitializing graphics
if (gbReinitialize) return 0;

        // When we are deactivated, although we don't update our screen, we still need to
        // to empty our receive queue periodically as messages will pile up otherwise.
        // Polling the receive queue continuously even when we are deactivated causes our app
        // to consume all the CPU time. To avoid hogging the CPU, we block on GetMessage() WIN API
        // and setup a timer to wake ourselves up at regular intervals to process our messages.

        if (LOWORD(wParam) == WA_INACTIVE)
        {
            // deactivated
            gbIsActive = FALSE;
            if (PS_ACTIVE == gnProgramState)
            {
                SetTimer(hWnd, RECEIVE_TIMER_ID, RECEIVE_TIMEOUT, NULL);
            }
        }
        else
        {
            // activated
            gbIsActive = TRUE;
            if (PS_ACTIVE == gnProgramState)
            {
                KillTimer(hWnd, RECEIVE_TIMER_ID);
            }
        }

        // set game palette, if activated in game mode
        if (gbIsActive && (gnProgramState != PS_SPLASH))
SetGamePalette();

        ReacquireInputDevices();

        return 0;

    case WM_CREATE:
        break;

    case WM_SYSKEYUP:
switch( wParam )
        {
        // handle ALT+ENTER (fullscreen/window mode)
        case VK_RETURN:
// mode switch is allowed only during the game
if (gnProgramState == PS_ACTIVE)
{
gbReinitialize = TRUE;
                ReleaseLocalData();  //only sound buffers have to be rels'd anyway.
                CleanupSfx();
                CleanupInput();
CleanupGraphics();
DestroyWindow(ghWndMain);
gbFullscreen = !gbFullscreen;
InitGraphics();
                InitInput();
                InitSfx();
                InitLocalSoundData();
gbReinitialize = FALSE;
            }
            break;
        }
        break;

    case WM_KEYDOWN:
        switch( wParam )
        {
        case 'r':
        case 'R':
// toggle reliable status
gbReliable = !gbReliable;
UpdateTitle();
            break;

case VK_F1:
{
TCHAR tszHelpMsg[MAX_HELPMSG];
TCHAR tszTitle[MAX_WINDOWTITLE];

LoadString(ghinst, IDS_DUEL_HELP, tszHelpMsg, MAX_HELPMSG);
LoadString(ghinst, IDS_DUEL_TITLE, tszTitle, MAX_WINDOWTITLE);
MessageBox(ghWndMain, tszHelpMsg, tszTitle, MB_OK );
}
break;

        case VK_F5:
            gbShowFrameCount = !gbShowFrameCount;
            if( gbShowFrameCount )
            {
                gdwFrameCount = 0;
                gdwFrameTime = timeGetTime();
            }
            break;

        case VK_RETURN:
            if( (gnProgramState == PS_SPLASH) && !gbFullscreen)
{
// get connection settings from user
ghThread = CreateThread(NULL, 0, (LPVOID)DoWizard, 0, 0, &dwTid);
}
            break;

        case VK_ESCAPE:
        case VK_F12:
// adios
ExitGame();
            return 0;
        }
        break;

    case WM_ERASEBKGND:
    return 1;

    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
if (gnProgramState == PS_SPLASH)
{
            // display the splash screen
            bltSplash(NULL);
}

        EndPaint( hWnd, &ps );
        return 1;

case UM_LAUNCH:
// cleanup the wizard thread
if (ghThread)
{
// wait for thread to exit
while (!GetExitCodeThread(ghThread, &dwRetCode));
CloseHandle(ghThread);
}

// start the game in rest mode
gnProgramState = PS_REST;
LaunchGame();
return 1;

case UM_ABORT:
// cleanup the wizard thread
if (ghThread)
{
// wait for thread to exit
while (!GetExitCodeThread(ghThread, &dwRetCode));
CloseHandle(ghThread);
}
        ExitGame();
return 1;

    case WM_TIMER:
        ReceiveMessages();
        break;

    case WM_DESTROY:
// if gbReinitialize is TRUE don't quit, we are just switching display modes
if (!gbReinitialize)
{
CleanupApplication();
        PostQuitMessage( 0 );
}
        return 0;

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

} /* MainWndproc */

/*
 * InitApplication
 *
 * Do that initialization stuff...
 */
BOOL InitApplication( HINSTANCE hinst )
{
    WNDCLASS    wc;
    BOOL        rc;

    glpGuid = (LPGUID) &DUEL_GUID;

LoadString(ghinst, IDS_DUEL_CLASSNAME, gtszClassName, MAX_CLASSNAME);

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndproc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinst;
    wc.hIcon = LoadIcon( hinst, MAKEINTRESOURCE(DUEL_ICON));
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = gtszClassName;
    rc = RegisterClass( &wc );
    if( !rc )
    {
        return FALSE;
    }

// Initialize all components
if ((!InitGraphics()) || (!InitInput()) || (!InitSfx()))
{
return FALSE;
}

// start in splash mode
    gnProgramState = PS_SPLASH;

    return TRUE;

} /* initApplication */

/*
 * CleanupApplication
 *
 * Calls clean up on all components
 */
void CleanupApplication( void )
{
CleanupComm();
    CleanupSfx();
CleanupGraphics();
CleanupInput();
}

/*
 * LaunchedByLobby
 *
 * Determines if we were launched by a lobby. If so, gets the connection settings
 * and creates our player using the information from the lobby
 */
BOOL LaunchedByLobby(void)
{
    HRESULT hr;
    HWND    hwndStatus;

    // create a lobby object
    hr = DPLobbyCreate();
    if (FAILED(hr))
    {
        ShowError(IDS_DPLOBBY_ERROR_C);
        return FALSE;
    }

    // get connection settings from the lobby (into glpdplConnection)
    hr = DPLobbyGetConnectionSettings();
    if (FAILED(hr))
    {
        if (DPERR_NOTLOBBIED == hr)
        {
            // we were not lobbied - start up game normally
            hr = DPLobbyRelease();
            if (FAILED(hr))
            {
                ShowError(IDS_DPLOBBY_ERROR_R);
                goto FAIL;
            }
            // move on
            return FALSE;
        }
        else
        {
            ShowError(IDS_DPLOBBY_ERROR_GCS);
            goto FAIL;
        }
    }

// are we hosting or joining ?
if (glpdplConnection->dwFlags & DPLCONNECTION_CREATESESSION)
{
gbIsHost = TRUE;
}

    // set our session flags
    glpdplConnection->lpSessionDesc->dwFlags = DPSESSION_MIGRATEHOST |
                                                 DPSESSION_KEEPALIVE;

    // let lobby know our connection flags
    hr = DPLobbySetConnectionSettings();
    if (FAILED(hr))
    {
        ShowError(IDS_DPLOBBY_ERROR_SCS);
        goto FAIL;
    }

    if ( !gbIsHost )
    {
        // show splash screen and
        // connection status if we are joining a game
        UpdateWindow(ghWndMain);
        hwndStatus = ShowConnectStatus();
    }
    else
    {
        // we are hosting, don't need connection status
        hwndStatus = NULL;
    }

    // connect to the lobby
    hr = DPLobbyConnect();

    if ( hwndStatus )
    {
        // get rid of the connectino status window
        DestroyWindow(hwndStatus);
    }

    if (FAILED(hr))
    {
        ShowError(IDS_DPLOBBY_ERROR_CONNECT);
        goto FAIL;
    }

    // create our player
    hr = DPlayCreatePlayer(
                            &gOurID,
#ifdef UNICODE
glpdplConnection->lpPlayerName->lpszShortName,
#else
glpdplConnection->lpPlayerName->lpszShortNameA,
#endif
NULL,
NULL,
0
                          );

    if (FAILED(hr))
    {
ShowError(IDS_DPLAY_ERROR_CP);
        goto FAIL;
    }

    // cleanup
    hr = DPLobbyRelease();
    if (FAILED(hr))
    {
        ShowError(IDS_DPLOBBY_ERROR_R);
        goto FAIL;
    }

    // we were lobbied
    return TRUE;

FAIL:
// cleanup and exit
DPLobbyRelease();
    ExitGame();
    return FALSE;
}

/*
 * Displays error to the user
 */
BOOL ShowError( int iStrID )
{
TCHAR tszMsg[MAX_ERRORMSG];
TCHAR tszTitle[MAX_WINDOWTITLE];

LoadString(ghinst, iStrID, tszMsg, MAX_ERRORMSG);
LoadString(ghinst, IDS_DUEL_ERROR_TITLE, tszTitle, MAX_WINDOWTITLE);
    MessageBox( ghWndMain, tszMsg, tszTitle, MB_OK );
    return FALSE;
}

/*
 * Displays connection status to the user
 */
HWND ShowConnectStatus(void)
{
    HWND hwnd;

    hwnd = CreateDialog( ghinst, MAKEINTRESOURCE(IDD_CONNECT_STATUS), ghWndMain, NULL);

    return hwnd;

}

/*
 * UpdateTitle
 *
 * Updates the window title based on application status
 */
void UpdateTitle(void)
{
DWORD dwFeatures;
  TCHAR tszTitle[MAX_WINDOWTITLE];
UINT iStringID;

// calculate title features
dwFeatures = 0;
if (gbReliable)
dwFeatures |= 1;
if (gbIsHost)
dwFeatures |= 2;

switch (dwFeatures)
{
case 0:
iStringID = IDS_DUEL_TITLE;
break;
case 1:
iStringID = IDS_DUEL_RELIABLE_TITLE;
break;
case 2:
iStringID = IDS_DUEL_HOST_TITLE;
break;
case 3:
iStringID = IDS_DUEL_HOST_RELIABLE_TITLE;
break;
}

// get appropriate window title for these features
LoadString(ghinst, iStringID, tszTitle, MAX_WINDOWTITLE);
    // change window title
    SetWindowText(ghWndMain, tszTitle);
}


DUEL.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:duel.h
 *  Content:main include file
 *
 *
 ***************************************************************************/

#ifndef DUEL_INCLUDED
#define DUEL_INCLUDED

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "resource.h"
#include <tchar.h>

/*
 * Application messages
 */

#define UM_LAUNCH       WM_USER
#define UM_ABORT        WM_USER+1
#define UM_RESTARTTIMER WM_USER+2

// program states
enum
{
    PS_SPLASH,
    PS_ACTIVE,
    PS_REST
};

#define     MAX_SCREEN_X    639
#define     MAX_SCREEN_Y    479
#define     MAX_PLAYERNAME  50
#defineMAX_SESSIONNAME 50
#defineMAX_SPNAME50
#defineMAX_CLASSNAME50
#defineMAX_WINDOWTITLE 50
#defineMAX_ERRORMSG256
#defineMAX_FONTNAME50
#defineMAX_HELPMSG512

#define     RECEIVE_TIMER_ID    1
#define     RECEIVE_TIMEOUT     1000    // in milliseconds

#define     ENUM_TIMER_ID    2
#define     ENUM_TIMEOUT     500    // in milliseconds

// default window size
#defineMAX_DEFWIN_X640
#defineMAX_DEFWIN_Y480

// tree view image info
#define CX_BITMAP25
#define CY_BITMAP25
#define NUM_BITMAPS 2

// registry info
#define DUEL_KEY (TEXT("Software\\Microsoft\\Duel"))

// macros
#define DEBUG_OUT(s) OutputDebugString(s);

/*
 * fn prototypes
 */

int WINAPIWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                        int nCmdShow );
long WINAPIMainWndproc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
BOOL    InitApplication(HINSTANCE hinst);
void            CleanupApplication(void);
BOOL            LaunchedByLobby(void);
BOOLShowError( int err );
HWNDShowConnectStatus(void);
void            UpdateTitle(void);

#endif


GAMEPROC.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       gameproc.c
 *  Content:    Game processing code
 *
 *
 ***************************************************************************/
#include "gameproc.h"
#include "util.h"
#include "gfx.h"
#include "comm.h"
#include "input.h"
#include "lobby.h"
#include "wizard.h"
#include "dsound.h"
#include "sfx.h"
#include "stdio.h"

/*
 * Externals
 */
extern DWORDgdwKeys;
extern LPDIRECTDRAWSURFACEglpShip0;
extern LPDIRECTDRAWSURFACEglpShip1;
extern LPDIRECTDRAWSURFACEglpShip2;
extern LPDIRECTDRAWSURFACEglpShip3;
extern BOOLgbShowFrameCount;
extern LPDIRECTDRAWSURFACEglpNum;
extern BOOLgbIsHost;
extern BOOLgbIsActive;
extern LPDPSESSIONDESC2glpdpSD;
extern HWNDghWndMain;
extern HINSTANCEghinst;
extern BOOL                     gbReliable;

/*
 * Globals
 */
FRAGgFrags[64];// ship framents
BLOCKSgBlocks;// field layout
LPVOID            glpvReceiveBuffer = NULL;// buffer to store received messages
DWORD           gdwReceiveBufferSize = 0;// size of buffer
DPIDgOurID;// our player id
BOOLgbHaveHostInit;// do we need to do host initializaton
intgnProgramState;// current state of the game
DWORDgdwFrameCount;// used for fps calc
DWORDgdwFramesLast;// ..
DWORDgdwFrameTime;// ..
BOOL                            gbUpdate;                   // TRUE if player data needs to be updated
BOOL                            gbNoField;                  // display blocks ?
BOOL                            gbSessionLost;              // did we get disconnected ?
HOSTMSG                         gHostMsg;                   // message buffer
BLOCKHITMSG                     gBlockHitMsg;               // ..
SHIPHITMSG                      gShipHitMsg;                // ..
ADDBLOCKMSG                     gAddBlockMsg;               // ..
CONTROLMSG                      gControlMsg;                // ..
SYNCMSG                         gSyncMsg;                   // ..
TCHAR                           gDebugBuff[MAX_ERRORMSG];   // buffer for debug output

/*
 * Statics
 */

static double      gDirx[40] =
{
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891007,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688,
    -1.000000,
    -0.987688,
    -0.951056,
    -0.891006,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434
};

static double      gDiry[40] =
{
    -1.000000,
    -0.987688,
    -0.951057,
    -0.891007,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434,
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891006,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688
};

/*
 * InitMessageBuffers
 *
 * Sets up buffes used for sending different types of messages
 */
void InitMessageBuffers(void)
{
    gHostMsg.byType        = MSG_HOST;
    gBlockHitMsg.byType    = MSG_BLOCKHIT;
    gShipHitMsg.byType     = MSG_SHIPHIT;
    gAddBlockMsg.byType    = MSG_ADDBLOCK;
    gControlMsg.byType     = MSG_CONTROL;
    gSyncMsg.byType        = MSG_SYNC;
}

/*
 * LaunchGame
 *
 * Sets up the game layout and launches
 */
void LaunchGame(void)
{
HRESULT hr;

// initialize global message buffers
    InitMessageBuffers();

// update window title
UpdateTitle();

// get current session description (glpdpSD is initialized in here)
    hr = DPlayGetSessionDesc();
    if (FAILED(hr) || (!glpdpSD))
{
        ShowError(IDS_DPLAY_ERROR_GSD);
        goto ABORT;
}

// initialize random number seed
srand((int)GetTickCount());

// clear front buffer before changing palette
EraseScreen();
FlipScreen();

        // Begin collecting input
        ReacquireInputDevices();

    // initialize us
  hr = InitOurShip();
    if (FAILED(hr))
    {
        goto ABORT;
    }

    // get the field layout
if( gbIsHost )
{
        // initialize field (done only by host)
        if (!gbNoField)
            InitField();

        // we have host initialization
gbHaveHostInit = TRUE;

        // start updating screen
        gbIsActive = TRUE;
}
else
{
        // get it from host, if we are joining
gbHaveHostInit = FALSE;
}

    // success
    return;

ABORT:
    // something went wrong, terminate game
    ExitGame();
}

/*
 * ExitGame
 *
 * Game termination code
 */
void ExitGame(void)
{
    // shut down app
PostMessage( ghWndMain, WM_CLOSE, 0, 0 );
}

/*
 * InitOurShip
 *
 * Initializes our ship's initial attributes
 */
HRESULT InitOurShip(void)
{
    int i;
    SHIP ship;
    HRESULT hr;

// wipe out everything
ZeroMemory(&ship, sizeof(ship));

// initialize sound buffers, tell function that it's OUR ship
    // so that it can use the global sound buffers instead of duplicating
    // from them and wasting resources.
InitPlayerLocalSoundData(&ship, TRUE);

// calculate ship type based on the number of players in the game
    // we cycle through four ships (Y,R,G,B) for now.

ship.byType = (BYTE) ((glpdpSD->dwCurrentPlayers-1) % NUM_SHIP_TYPES);
    ship.dPosX  = randInt(0,MAX_SHIP_X);
    ship.dPosY  = randInt(0,MAX_SHIP_Y);
    ship.cFrame = randInt(0, MAX_SHIP_FRAME);
ship.bEnable = TRUE;

    // set our local data
hr = DPlaySetPlayerData(gOurID, &ship, sizeof(ship), DPSET_LOCAL);
    if (FAILED(hr))
    {
        ShowError(IDS_DPLAY_ERROR_SPLD);
        goto FAIL;
    }

    // no ship fragments
    for(i=0; i<64; i++)
        gFrags[i].valid = FALSE;

    // success
    return DP_OK;

FAIL:
    // failed
    return hr;
}

/*
 *FUNCTION:   InitPlayerLocalSoundData
 *
 *
 *NOTES:      Takes a ship and initializes ONLY its soundbuffers and sound flags.
 *            It leaves everything else alone.  Good for when going to
 *            full-screen mode, when we need to get rid of the sound buffers
 *            for a bit, but don't want to change the data.
 */
void InitPlayerLocalSoundData(LPSHIP lpShip, BOOL bOurShip)
{
int i;
    if (gbSoundInitialized)
    {
        for (i=0; i<MAX_SOUNDS; i++)
        {
            if (FALSE == WaveGetBuffers(gSoundEffect[i],&lpShip->lpDirectSoundBuffer[i], &lpShip->lpDirectSound3DBuffer[i], bOurShip))
            {//if it didn't work, get rid of all the other sound buffers
             //the current ones have already been set NULL by the failed function.
                ShowError(IDS_DSOUND_DUPBUF);
                for (--i; i>=0; i--)
                {
                    // if it's our ship then we don't want to release the regular
                    // (non-3D) buffer because it's just a copy of a pointer to
                    // the corresponding GLOBAL sound buffer.
                    IDirectSoundBuffer_Release(lpShip->lpDirectSound3DBuffer[i]);
                    if (!bOurShip)
                    {
                        IDirectSoundBuffer_Release(lpShip->lpDirectSoundBuffer  [i]);
                    }
                    lpShip->lpDirectSoundBuffer  [i] = NULL;
                    lpShip->lpDirectSound3DBuffer[i] = NULL;
                }
                return;
            }
        }

        lpShip->bFiring        = FALSE;
        lpShip->bBounced       = FALSE;
        lpShip->bDeath         = FALSE;
        lpShip->bBlockHit      = FALSE;
        lpShip->bEngineRunning = FALSE;
        lpShip->bMoving        = FALSE;
    }
    else
    {
    //if sound initialization has already failed in InitSfx(), then we want the ships'
    //sound buffers to be NULL so that we don't try to release them later.
        for (i=0; i<MAX_SOUNDS; i++)
        {
            lpShip->lpDirectSoundBuffer  [i] = NULL;
            lpShip->lpDirectSound3DBuffer[i] = NULL;
        }
    }
};

/*
 *FUNCTION:   SetPlayerLocalSoundDataCB (CALLBACK)
 *
 *PURPOSE:    Initializes and registers a player's local SOUND data ONLY.
 */
BOOL WINAPI SetPlayerLocalSoundDataCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
                     DWORD dwFlags, LPVOID lpContext)
{
SHIP ship;
    DWORD dwSize = sizeof(ship);
    HRESULT hr;
    BOOL bOurShip;

    DPlayGetPlayerData(dpId, &ship, &dwSize, DPGET_LOCAL);

// no player data yet
if (0 == dwSize)
return TRUE;

    //we need to know if it's our ship because we use the global sound buffers
    //while all the other ships have to DUPLICATE their sound buffers from
    //those global buffers.
    bOurShip = (dpId == gOurID);
    InitPlayerLocalSoundData(&ship, bOurShip);

hr = DPlaySetPlayerData(dpId, &ship, dwSize, DPSET_LOCAL);
    return (DP_OK == hr);
};

/*
 *FUNCTION:   InitLocalSoundData
 *
 *PURPOSE:    Initializes and registers all players' sound data ONLY
 */
HRESULT InitLocalSoundData(void)
{
    HRESULT hr;
hr = DPlayEnumPlayers(&(glpdpSD->guidInstance), SetPlayerLocalSoundDataCB, NULL, 0);
    return hr;
}

/*
 *FUNCTION:   ReleaseLocalSoundData
 *
 *NOTES:      Releases a single ship's local sound buffers.
 */
void ReleasePlayerLocalSoundData(LPSHIP lpShip)
{
int i;

    if (gbSoundInitialized)
    {
        for (i=0; i<MAX_SOUNDS; i++)
        {
            if (lpShip->lpDirectSoundBuffer[i] != NULL)
            {
                IDirectSoundBuffer_Stop     (lpShip->lpDirectSoundBuffer[i]);
                //only release if it's not just a pointer copy from the global.
                if (lpShip->lpDirectSoundBuffer[i] != gSoundEffect[i]->lpDirectSoundBuffer)
                {
                    //This ship's buffer is not identical to its global, we can release it.
                    IDirectSoundBuffer_Release  (lpShip->lpDirectSoundBuffer[i]);
                }
                lpShip->lpDirectSoundBuffer[i] = NULL;
            }

            if (lpShip->lpDirectSound3DBuffer[i] != NULL)
            {
                IDirectSound3DBuffer_Release(lpShip->lpDirectSound3DBuffer[i]);
                lpShip->lpDirectSound3DBuffer[i] = NULL;
            }
        }
    }
};

/*
 *FUNCTION:   ReleasePlayerLocalData  (CALLBACK)
 *
 *PURPOSE:    Retrieves and releases a player's local data from dplay.
 */
BOOL WINAPI ReleasePlayerLocalDataCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
                 DWORD dwFlags, LPVOID lpContext)
{
SHIP ship;
    DWORD dwSize = sizeof(SHIP);
    HRESULT hr;

    hr = DPlayGetPlayerData(dpId, &ship, &dwSize, DPGET_LOCAL);
    if (FAILED(hr))
    {
        wsprintf(gDebugBuff, TEXT("Get Player local data failed for id %d\n"), dpId);
        DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

// no player data yet
if (0 == dwSize)
return TRUE;

    ReleasePlayerLocalSoundData(&ship);

    hr = DPlaySetPlayerData(dpId, &ship,  dwSize, DPSET_LOCAL);
    if (FAILED(hr))
    {
        wsprintf(gDebugBuff, TEXT("Set Player local data failed for id %d\n"), dpId);
        DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

    // success
return TRUE;
FAIL:
    // we failed
    return FALSE;
}

/*
 *FUNCTION:   ReleaseLocalData
 *
 *PURPOSE:    Releases local data of ALL players.
 */
void ReleaseLocalData(void)
{
     HRESULT hr;

     if (gnProgramState == PS_ACTIVE)
     {
         hr = DPlayEnumPlayers(&(glpdpSD->guidInstance), ReleasePlayerLocalDataCB, NULL, 0);
         if (FAILED(hr))
         {
             ShowError(IDS_DPLAY_ERROR_EP);
         }
     }
};

/*
 * ProcessUserInput
 *
 * Processes any input from the user and updates our ship state
 */
void ProcessUserInput(LPSHIP lpShip)
{
    static dwOldKeys = 0;

    gbUpdate = FALSE;

    // DSOUND: check if the engine was turned off
    if (!(gdwKeys    & (KEY_DOWN | KEY_UP)) &&
(dwOldKeys & (KEY_DOWN | KEY_UP)))
    {
        gdwKeys |= KEY_ENGINEOFF;

        gControlMsg.byState = (BYTE) gdwKeys;
        // let everyone know that we just turned our engine off
        SendGameMessage((LPGENERICMSG) &gControlMsg, DPID_ALLPLAYERS);
    }

    // update our ship state
    UpdateState(lpShip, gdwKeys);

    // remember current keys for next frame
    dwOldKeys = gdwKeys;
}

/*
 * UpdateState
 *
 * Updates the current state of our ship, given user input
 */
void UpdateState(LPSHIP lpShip, DWORD dwControls)
{
    lpShip->dwKeys = dwControls;

    if( dwControls & KEY_LEFT )
    {
        gbUpdate = TRUE;
        lpShip->cFrame -= 1;
if( lpShip->cFrame < 0 )
lpShip->cFrame += MAX_SHIP_FRAME;
    }
    if( dwControls & KEY_RIGHT )
    {
        gbUpdate = TRUE;
        lpShip->cFrame += 1;
if( lpShip->cFrame >= MAX_SHIP_FRAME )
lpShip->cFrame -= MAX_SHIP_FRAME;
    }
    if( dwControls & KEY_UP )
    {
        gbUpdate = TRUE;
        lpShip->dVelX += gDirx[lpShip->cFrame] * 10.0 / 1000.0;
        lpShip->dVelY += gDiry[lpShip->cFrame] * 10.0 / 1000.0;
    }
    if( dwControls & KEY_DOWN )
    {
        gbUpdate = TRUE;
        lpShip->dVelX -= gDirx[lpShip->cFrame] * 10.0 / 1000.0;
        lpShip->dVelY -= gDiry[lpShip->cFrame] * 10.0 / 1000.0;
    }
    if( dwControls & KEY_STOP )
    {
        gbUpdate = TRUE;
        lpShip->dVelX = 0.0;
        lpShip->dVelY = 0.0;
    }
    if( !lpShip->bBulletEnable && lpShip->bEnable )
    {
if( dwControls & KEY_FIRE )
{
            gbUpdate = TRUE;
// launch a new bullet
lpShip->dBulletPosX = (WORD) (gDirx[lpShip->cFrame]*6.0 + 16.0 + lpShip->dPosX);
lpShip->dBulletPosY = (WORD) (gDiry[lpShip->cFrame]*6.0 + 16.0 + lpShip->dPosY);
lpShip->dBulletVelX = gDirx[lpShip->cFrame]*500.0/1000.0;
lpShip->dBulletVelY = gDiry[lpShip->cFrame]*500.0/1000.0;
lpShip->bBulletEnable = TRUE;
lpShip->dwBulletFrame = 0;
}
    }
}

/*
 * SendSync
 *
 * Sends a sync message with the rendevous position. We are using a synchronization technique in
 * which every player informs everyone else where the player is going to be at the end of the
 * next sync interval. Based on this rendezvous position, everyone tries to move their corresponding
 * ghosts to the rendezvous position by the end of the interval.
 */
void SendSync(LPSHIP lpShip)
{
gSyncMsg.byShipType = lpShip->byType;
    gSyncMsg.cFrame = lpShip->cFrame;
    gSyncMsg.dPosX  = lpShip->dPosX + lpShip->dVelX*1000;
    gSyncMsg.dPosY  = lpShip->dPosY + lpShip->dVelY*1000;

    SendGameMessage((LPGENERICMSG) &gSyncMsg, DPID_ALLPLAYERS);
}

/*
 * UpdateDisplayStatus
 *
 * Updates the disable timeout. Enables the ship if disable timeout has elapsed.
 */
void UpdateDisplayStatus(LPSHIP lpShip)
{
    DWORD dwTickDiff;
    DWORD dwTickCount;

    // current time
    dwTickCount = timeGetTime();

    // time elapsed since last update
    dwTickDiff =  dwTickCount - lpShip->dwLastTick;

    // timestamp
    lpShip->dwLastTick = dwTickCount;

    // update time-out
    lpShip->iCountDown -= dwTickDiff;

    // time-out ?
if( lpShip->iCountDown < 0 )
{
        // get new position and enable our lpShip
    lpShip->dPosX = randInt(0,MAX_SHIP_X);
    lpShip->dPosY = randInt(0,MAX_SHIP_Y);
    lpShip->cFrame = randInt(0, MAX_SHIP_FRAME);
lpShip->bEnable = TRUE;
}
}

/*
 * UpdatePosition
 *
 * Updates our ship's position
 */
void UpdatePosition(DPID dpId, LPSHIP lpShip )
{
    int     x,y;
    BYTE    oldxCell, oldyCell, newxCell, newyCell, mask, col, row;
    double  thisTick, totalTick, xtick, ytick;
    DWORD   dwTickCount;
    DWORD   dwTickDiff;

    if( !lpShip->bEnable )
        return;

    // how long has it been since we last updated
    dwTickCount = timeGetTime();
    dwTickDiff = dwTickCount - lpShip->dwLastTick;

    // current timestamp
    lpShip->dwLastTick = dwTickCount;

oldxCell = (int)(lpShip->dPosX+16.0) >> 4;
oldyCell = (int)(lpShip->dPosY+16.0) >> 4;

    // compute new position
    lpShip->dPosX += lpShip->dVelX * dwTickDiff;
    lpShip->dPosY += lpShip->dVelY * dwTickDiff;

newxCell = (int)(lpShip->dPosX+16.0) >> 4;
newyCell = (int)(lpShip->dPosY+16.0) >> 4;
if(oldxCell != newxCell)
{
        // we allow ghosts to pass through the blocks
if( (dpId == gOurID) && IsHit( newxCell, newyCell ) )
{
if( lpShip->dVelX > 0.0 )
lpShip->dPosX = (oldxCell << 4) + 15 - 16;
else
lpShip->dPosX = (oldxCell << 4) - 16;
lpShip->dVelX = -lpShip->dVelX*0.9;
newxCell = oldxCell;
            lpShip->bBounced = TRUE;
}
}
if(oldyCell != newyCell)
{
        // we allow ghosts to pass through the blocks
if( (dpId == gOurID) && IsHit( newxCell, newyCell ) )
{
if( lpShip->dVelY > 0.0 )
lpShip->dPosY = (oldyCell << 4) + 15 - 16;
else
lpShip->dPosY = (oldyCell << 4) - 16;

            lpShip->dVelY = -lpShip->dVelY*0.9;
            lpShip->bBounced = TRUE;
    }
}

    if( lpShip->dPosX > MAX_SHIP_X )
    {
        lpShip->dPosX = MAX_SHIP_X;
        lpShip->dVelX = -lpShip->dVelX*0.9;
        lpShip->bBounced = TRUE;
    }
    else if ( lpShip->dPosX < 0 )
    {
        lpShip->dPosX =0;
        lpShip->dVelX = -lpShip->dVelX*0.9;
        lpShip->bBounced = TRUE;
    }
    if( lpShip->dPosY > MAX_SHIP_Y )
    {
        lpShip->dPosY = MAX_SHIP_Y;
        lpShip->dVelY = -lpShip->dVelY*0.9;
        lpShip->bBounced = TRUE;
    }
    else if ( lpShip->dPosY < 0 )
    {
        lpShip->dPosY =0;
        lpShip->dVelY = -lpShip->dVelY*0.9;
        lpShip->bBounced = TRUE;
    }

    if ((dpId == gOurID) && lpShip->bBounced)
    {
        SendSync(lpShip);
    }

    if( !lpShip->bBulletEnable )
        return;

    // update the active bullet
    lpShip->dwBulletFrame += dwTickDiff;
    if( lpShip->dwBulletFrame >= MAX_BULLET_FRAME )
    {
        lpShip->bFiring = FALSE;
        lpShip->bBulletEnable = FALSE;
return;
    }

    if( lpShip->dBulletVelX != 0.0 )
xtick = 8.0/lpShip->dBulletVelX;
    else
        xtick = 999999.0;

    if( lpShip->dBulletVelY != 0.0 )
ytick = 8.0/lpShip->dBulletVelY;
    else
        ytick = 999999.0;

    if( xtick < 0.0 )
        xtick = -xtick;
    if( ytick < 0.0 )
        ytick = -ytick;

    if( xtick < ytick )
        thisTick = xtick;
    else
        thisTick = ytick;

    if( thisTick > dwTickDiff )
        thisTick = dwTickDiff;

    for( totalTick = 0.0; totalTick < dwTickDiff; )
    {
        totalTick += thisTick;

        lpShip->dBulletPosX += lpShip->dBulletVelX * thisTick;
        lpShip->dBulletPosY += lpShip->dBulletVelY * thisTick;

        if( lpShip->dBulletPosX > MAX_BULLET_X )
        {
            lpShip->dBulletPosX = MAX_BULLET_X;
            lpShip->dBulletVelX = -lpShip->dBulletVelX*0.9;
        }
        else if ( lpShip->dBulletPosX < 0 )
        {
            lpShip->dBulletPosX =0;
            lpShip->dBulletVelX = -lpShip->dBulletVelX*0.9;
        }
        if( lpShip->dBulletPosY > MAX_BULLET_Y )
        {
            lpShip->dBulletPosY = MAX_BULLET_Y;
            lpShip->dBulletVelY = -lpShip->dBulletVelY*0.9;
        }
        else if ( lpShip->dBulletPosY < 0 )
        {
            lpShip->dBulletPosY =0;
            lpShip->dBulletVelY = -lpShip->dBulletVelY*0.9;
        }

        // check to see if it hit anything
        x = (int)(lpShip->dBulletPosX + 0.5) + 1;
        y = (int)(lpShip->dBulletPosY + 0.5) + 1;

        row = y >> 4;
        col = x >> 4;
        mask = 1 << (col & 0x7);
        col = col >> 3;
        if( gBlocks.bits[row][col] & mask )
        {
// dwScored a block hit
            gBlockHitMsg.byRow = row;
            gBlockHitMsg.byCol = col;
            gBlockHitMsg.byMask = mask;
SendGameMessage((LPGENERICMSG) &gBlockHitMsg, DPID_ALLPLAYERS);

gBlocks.bits[row][col] &= ~mask;
lpShip->dwScore += 10;
lpShip->bBulletEnable = FALSE;
            lpShip->bBlockHit = TRUE;
            lpShip->bFiring   = FALSE;
        }
    }
}

/*
 * IsHit
 *
 * Tells if there is a block at (x,y) location
 */
BOOL IsHit( int x, int y )
{
    int col, mask;

    // outside screen boundaries?
    if( (x < 0) || (y < 0) || (x >= 40) || (y >= 30) )
        return TRUE;

    // look at the block bits
    mask = 1 << (x & 0x7);
    col = x >> 3;
    if( gBlocks.bits[y][col] & mask )
        return TRUE;
    else
        return FALSE;
}

/*
 * InitField
 *
 * Initializes block positions on the field
 */
void InitField(void)
{
    int i, x, y;

    // clear all gBlocks
    for(x=0; x<5; x++)
        for(y=0; y<30; y++)
    gBlocks.bits[y][x] = 0;

    // set random gBlocks
    for(i=0; i<400; i++)
    {
        x = randInt(0, 40);
y = randInt(0, 30);
if( !setBlock(x, y) ) i--;
    }
}

/*
 * AddBlock
 *
 * Adds a block to the field
 */
void AddBlock(void)
{
int x,y;

    // maybe add a block?
if(gbIsHost && gbIsActive && ( randInt( 0, 100 ) > 98 ))
{
x = randInt( 0, 40);
y = randInt( 0, 30);
if( setBlock( x, y) )
{
            gAddBlockMsg.byX    = (BYTE) x;
            gAddBlockMsg.byY    = (BYTE) y;
SendGameMessage((LPGENERICMSG) &gAddBlockMsg, DPID_ALLPLAYERS);
}
}
}

/*
 * setBlock
 *
 * Turns on a block
 */
BOOL setBlock( int x, int y )
{
    BYTE  mask, col;

    mask = 1 << (x & 0x7);
    col = x >> 3;

    // is Block already set?
    if( gBlocks.bits[y][col] & mask )
        return FALSE;

    // set the block and return success
    gBlocks.bits[y][col] |= mask;
    return TRUE;
}

/*
 * AddFrag
 *
 * Turns on a fragment
 */
void AddFrag(LPSHIP lpShip, int offX, int offY)
{
    int i;
    for(i=0; i<64; i++) // find available fragment
    {
        if( !gFrags[i].valid )
    break;
    }
    if( i == 64 )
        return;

    gFrags[i].dPosX = offX + lpShip->dPosX;
    gFrags[i].dPosY = offY + lpShip->dPosY;
    switch( lpShip->byType )
    {
    case 0: gFrags[i].surf = glpShip0;    break;
    case 1: gFrags[i].surf = glpShip1;    break;
    case 2: gFrags[i].surf = glpShip2;    break;
    case 3: gFrags[i].surf = glpShip3;    break;
default: DEBUG_OUT(TEXT("Unknown ship type\n")); return;
    }
    gFrags[i].src.top = 32 * ( (int)lpShip->cFrame / 10 ) + offX;
    gFrags[i].src.left = 32 * ( (int)lpShip->cFrame % 10 ) + offY;
    gFrags[i].src.right = gFrags[i].src.left + 8;
    gFrags[i].src.bottom = gFrags[i].src.top + 8;
    gFrags[i].dVelX = ((double)offX - 12.0)/24.0;
    gFrags[i].dVelY = ((double)offY - 12.0)/24.0;
    gFrags[i].valid = TRUE;
}

/*
 * UpdateFragment
 *
 * Updates the position of a fragment
 */
void UpdateFragment(int i)
{
    DWORD   dwTickCount;
    static DWORD   dwTickDiff;
    static DWORD dwLastTick;

    if( i == 0)
    {
        dwTickCount = timeGetTime();
        dwTickDiff = dwTickCount - dwLastTick;
        dwLastTick = dwTickCount;
    }

    if( !gFrags[i].valid )
        return;

    gFrags[i].dPosX += (int) (gFrags[i].dVelX * dwTickDiff);
    gFrags[i].dPosY += (int) (gFrags[i].dVelY * dwTickDiff);
    if( (gFrags[i].dPosX < 0.0) || (gFrags[i].dPosX >= 632.0) ||
        (gFrags[i].dPosY < 0.0) || (gFrags[i].dPosY >= 472.0) )
    {
        gFrags[i].valid = FALSE;
    }
}

/*
 * DestroyShip
 *
 * Renders a bunch of fragments to show that the ship is destroyed
 */
void DestroyShip( LPSHIP lpShip )
{
// Set flag for explosion sound
lpShip->bDeath  = TRUE;

    // add ship fragments
    AddFrag(lpShip, 0, 0);
    AddFrag(lpShip, 8, 0);
    AddFrag(lpShip, 16, 0);
    AddFrag(lpShip, 24, 0);
    AddFrag(lpShip, 0, 8);
    AddFrag(lpShip, 8, 8);
    AddFrag(lpShip, 16, 8);
    AddFrag(lpShip, 24, 8);
    AddFrag(lpShip, 0, 16);
    AddFrag(lpShip, 8, 16);
    AddFrag(lpShip, 16, 16);
    AddFrag(lpShip, 24, 16);
    AddFrag(lpShip, 0, 24);
    AddFrag(lpShip, 8, 24);
    AddFrag(lpShip, 16, 24);
    AddFrag(lpShip, 24, 24);

    // Play explosion sound
    ProcessSoundFlags(lpShip);
}

/*
 * UpdateFrame
 *
 * Refreshes the screen
 */
BOOL UpdateFrame( void )
{
    int i;
    DWORD dwTickCount;
    SHIP  ship;
    DWORD dwSize;
    HRESULT hr;
    static dwSyncLastTick = 0;
    static dwUpdateLastTick = 0;

    switch( gnProgramState )
    {
    case PS_ACTIVE:
        // DINPUT: use DirectInput to read game-play keys
    DI_ReadKeys();

        // get our local data
        dwSize = sizeof(ship);
        hr = DPlayGetPlayerData(gOurID, &ship, &dwSize, DPGET_LOCAL);
        if (FAILED(hr))
        {
            wsprintf(gDebugBuff, TEXT("Get Player local data failed for id %d\n"), gOurID);
            DEBUG_OUT(gDebugBuff);
            goto FAIL;
        }

        if (!ship.bEnable)
        {
            // update disable timeout and display status
            UpdateDisplayStatus(&ship);
        }
        else
        {
            // process any change in game controls
            ProcessUserInput(&ship);

            dwTickCount = timeGetTime();

            // synchronize if it's time
            if (gbIsActive && ((dwTickCount - dwSyncLastTick) > SYNC_INTERVAL))
            {
                SendSync(&ship);
                dwSyncLastTick = dwTickCount;
            }

            // if our player changed any keys, let everyone know
            if (gbUpdate)
            {
                // control the number of packets we send
                if ((dwTickCount - dwUpdateLastTick) > UPDATE_INTERVAL)
                {
            // let others know
                    gControlMsg.byState = (BYTE) gdwKeys;
                    SendGameMessage((LPGENERICMSG) &gControlMsg, DPID_ALLPLAYERS);
                    dwUpdateLastTick = dwTickCount;
                }
            }
        }

    // save ship data as RenderPlayerCB reads stored data
    hr = DPlaySetPlayerData(gOurID, &ship, sizeof(ship), DPSET_LOCAL);
        if (FAILED(hr))
        {
            ShowError(IDS_DPLAY_ERROR_SPLD);
            goto FAIL;
        }

// update fragments
for(i=0; i<64; i++)
UpdateFragment(i);

    // add a block
        if (!gbNoField)
        AddBlock();

        // render everything
        if (!DrawScreen())
        {
            goto FAIL;
        }
        break;

    case PS_REST:
if( gbHaveHostInit )
{
SetGamePalette();
gnProgramState = PS_ACTIVE;
}
break;
    }

    // success
    return TRUE;

FAIL:
    // failed
    return FALSE;
}

/*
 *FUNCTION:   ProcessSoundFlags
 *
 *PARAMETERS:
 *    lpShip: Points to a ship structure (originally retrieved from
 *            GetPlayerData) in RenderPlayer.
 *
 *NOTES:      All y-coordinates must be made negative, because the
 *            3D Sound API's use an opposite coordinate system than
 *            the screen.
 */
void ProcessSoundFlags(LPSHIP lpShip)
{
    int i;
    BOOL bStart[MAX_SOUNDS]; //flags, used so we can
    BOOL bStop [MAX_SOUNDS];

    if (!gbSoundInitialized)
{
// clear bounce flag so we don't send a message every time through the loop
        lpShip->bBounced = FALSE;
        return;
}

    //if one is NULL, so are the other ones and we aren't initialized yet.
    for (i=0; i<MAX_SOUNDS; i++)

{
        if (lpShip->lpDirectSoundBuffer[i]==NULL)
            return;
    }

    //set all our temporary flags to FALSE.
    for (i=0; i<MAX_SOUNDS; i++)
    {
        bStart[i] = FALSE;
        bStop[i]  = FALSE;
    }

    if (lpShip->dwKeys & KEY_ENGINEOFF)
        {
        bStop[SENGINE] = TRUE;
        lpShip->bEngineRunning = FALSE;
        }

    if (lpShip->dwKeys & (KEY_UP | KEY_DOWN))
        {
        if (!lpShip->bEngineRunning) //turn on engine
            {
            bStart[SENGINE] = TRUE;
            if (!lpShip->bMoving)   //"fire-up-engine" sound
                {
                bStart[SSTART] = TRUE;
                IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[SSTART],
                                                  P2M(lpShip->dPosX - 320),
                                                 -P2M(lpShip->dPosY - 240),
                                                 D3DVAL(0),
                                                 DS3D_DEFERRED);
                bStart[SSTART] = TRUE;
                lpShip->bMoving = TRUE;
                }
            lpShip->bEngineRunning = TRUE;
            }
        }

    if (lpShip->bMoving)
        {
        IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[SENGINE],
                                         P2M(lpShip->dPosX - 320),
                                        -P2M(lpShip->dPosY - 240),
                                        D3DVAL(0),
                                        DS3D_DEFERRED);
        IDirectSound3DBuffer_SetVelocity(lpShip->lpDirectSound3DBuffer[SENGINE],
                                         D3DVAL(lpShip->dVelX * 10), //exagerater vel
                                         -D3DVAL(lpShip->dVelY * 10),
                                         D3DVAL(0),
                                         DS3D_DEFERRED);
        }

    if (lpShip->dwKeys & KEY_STOP)
        {
        if (lpShip->bMoving)
            {
            IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[SSTOP],
                                              P2M(lpShip->dPosX - 320),
                                             -P2M(lpShip->dPosY - 240),
                                             D3DVAL(0),
                                             DS3D_DEFERRED);
            bStart[SSTOP]   = TRUE;
            bStop[SENGINE]  = TRUE;
            lpShip->bEngineRunning  = FALSE;
            lpShip->bMoving         = FALSE;
            }
        }

    if (lpShip->dwKeys & KEY_FIRE)
        {
        if (!lpShip->bFiring)
            {
            IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[BFIRE],
                                              P2M(lpShip->dPosX - 320),
                                             -P2M(lpShip->dPosY - 240),
                                             D3DVAL(0),
                                             DS3D_DEFERRED);
            bStart[BFIRE]   = TRUE;
            lpShip->bFiring = TRUE;
            }
        }

    if (lpShip->bBlockHit)
        {
        IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[LBOOM],

                                          P2M(lpShip->dBulletPosX - 320),
                                         -P2M(lpShip->dBulletPosY - 240),
                                         D3DVAL(0),
                                         DS3D_DEFERRED);

        bStart[LBOOM] = TRUE;
        lpShip->bBlockHit = FALSE;
        }

    if (lpShip->bBounced)
        {
        IDirectSound3DBuffer_SetPosition(lpShip->lpDirectSound3DBuffer[SBOUNCE],
                                         P2M(lpShip->dPosX - 320),
                                         -P2M(lpShip->dPosY - 240),
                                         D3DVAL(0),
                                         DS3D_DEFERRED);

        bStart[SBOUNCE] = TRUE;
        lpShip->bBounced = FALSE;
        }

    if (lpShip->bDeath)
        {
        bStop [BFIRE]   = TRUE;
        bStop [SBOUNCE] = TRUE;
        bStop [SSTOP]   = TRUE;
        bStop [SSTART]  = TRUE;
        bStop [SENGINE] = TRUE;
        bStart[SBOOM]   = TRUE;
        lpShip->bDeath  = FALSE;  //turn off sound flag.
        }

    //stop, update, and start sounds.
    for (i=0; i<MAX_SOUNDS; i++)
        {
        if (bStop[i])
            {
            IDirectSoundBuffer_Stop(lpShip->lpDirectSoundBuffer[i]);
            bStart[i] = FALSE;
            }
        }
    IDirectSound3DListener_CommitDeferredSettings(glpDirectSound3DListener);

    for (i=0; i<MAX_SOUNDS; i++)
        {
        if (bStart[i])
            {
             IDirectSoundBuffer_SetCurrentPosition(lpShip->lpDirectSoundBuffer[i], 0);

             if (DSERR_BUFFERLOST==IDirectSoundBuffer_Play(lpShip->lpDirectSoundBuffer[i],
                                                           0,
                                                           0,
                                                           (i==SENGINE) ? DSBPLAY_LOOPING : 0))
                {
                WaveReload(gSoundEffect[i]);
                }
            }
        }
    lpShip->dwKeys = 0;
};

/*
 * RenderPlayerCB
 *
 * Renders a ship in its current state. Also checks if we hit the ship and informs
 * the ship that it has been destroyed.
  */

BOOL WINAPI RenderPlayerCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
DWORD dwFlags, LPVOID lpContext)
{
    SHIP ship, ourShip;
    DWORD dwSize = sizeof(ship);
    BOOL bHit = FALSE;
    HRESULT hr;
    DWORD dwTickCount;

    // get ship data
    hr = DPlayGetPlayerData(dpId, &ship, &dwSize, DPGET_LOCAL);

    if (FAILED(hr))
    {
        wsprintf(gDebugBuff, TEXT("Get Player local data failed for id %d\n"), dpId);
        DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

// no player data yet
if (0 == dwSize)
return TRUE;

    // ignore current ship ?
    if (ship.bIgnore)
    {
    // if this ship was being ignored, update ignore time-out
    // A time-out is used here to ensure that this ship doesn't get ignored
    // forever on our screen in case the destroy message was dropped.
        dwTickCount = timeGetTime();
    ship.iCountDown -= dwTickCount - ship.dwLastTick;
        ship.dwLastTick = dwTickCount;
    if( ship.iCountDown < 0 )
{
            ship.bIgnore = FALSE;
}

        // save ship data
    hr = DPlaySetPlayerData(dpId, &ship, sizeof(ship), DPSET_LOCAL);
        if (FAILED(hr))
        {
wsprintf(gDebugBuff, TEXT("Set Player local data failed for id %d\n"), dpId);
DEBUG_OUT(gDebugBuff);
            goto FAIL;
        }
        // we are ignoring this ship, so just bail
        return TRUE;
    }

    // bail, if ship is disabled
    if (!ship.bEnable) return TRUE;

// update ship's position
UpdatePosition(dpId, &ship);

    // get our player data to compare with others
    dwSize = sizeof(ship);
    hr = DPlayGetPlayerData(gOurID, &ourShip, &dwSize, DPGET_LOCAL);
    if (FAILED(hr))
    {
        wsprintf(gDebugBuff, TEXT("Get Player local data failed for id %d\n"), dpId);
        DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

// check if our bullet hit the current ship
if((dpId != gOurID) && ourShip.bBulletEnable && ship.bEnable)
{
if( (ourShip.dBulletPosX > ship.dPosX) &&
(ourShip.dBulletPosX < (ship.dPosX + 32.0) ) &&
(ourShip.dBulletPosY > ship.dPosY) &&
(ourShip.dBulletPosY < (ship.dPosY + 32.0) ) )
{
// hasta-la-vista baby
DestroyShip(&ship);
            // we nailed it
            bHit = TRUE;
            // turn off ship locally
            ship.bEnable = FALSE;
// temporarily ignore ship until we get a response
ship.bIgnore = TRUE;
// set its ignore time-out
ship.iCountDown = HIDE_TIMEOUT;
            // time-stamp
            ship.dwLastTick = timeGetTime();
// turn our bullet off
ship.bBulletEnable = FALSE;
// update our score
            ourShip.dwScore += 1000;
        // save our score
        hr = DPlaySetPlayerData(gOurID, &ourShip, sizeof(ourShip), DPSET_LOCAL);
            if (FAILED(hr))
            {
                ShowError(IDS_DPLAY_ERROR_SPLD);
                goto FAIL;
            }
}
}

// render the ship
if (ship.bBulletEnable) DrawBullet(&ship);
if (ship.bEnable)       DrawShip(&ship);

    ProcessSoundFlags(&ship);

// save ship data
hr = DPlaySetPlayerData(dpId, &ship, sizeof(ship), DPSET_LOCAL);
    if (FAILED(hr))
    {
wsprintf(gDebugBuff, TEXT("Set Player local data failed for id %d\n"), dpId);
DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

// inform the player
    if (bHit)
    {
        gShipHitMsg.Id = dpId;
        SendGameMessage((LPGENERICMSG) &gShipHitMsg, dpId);
    }

    // success
    return TRUE;

FAIL:
    // failed
    return FALSE;
}

/*
 * DrawScreen
 *
 * Renders the current frame
 */
BOOL DrawScreen( void )
{
    BYTE    mask, col;
    int     x, y;
    HRESULT hr;

// clear screen
    EraseScreen();

// render players
    hr = DPlayEnumPlayers(NULL, RenderPlayerCB, NULL, 0);
    if (FAILED(hr))
    {
        ShowError(IDS_DPLAY_ERROR_EP);
        goto FAIL;
    }

// render field
    for( y=0; y<30; y++)
    {
        for( x=0; x<40; x++)
{
mask = 1 << (x & 0x7);
col = x >> 3;
if( gBlocks.bits[y][col] & mask )
DrawBlock( x, y );
}
    }

// render score
    if (!DrawScore())
    {
        goto FAIL;
    }

// render fragments
    DrawFragments();

// render frame rate
    if( gbShowFrameCount )
        DisplayFrameRate();

// show
    FlipScreen();

    // success
    return TRUE;

FAIL:
    return FALSE;
}

/*
 * DisplayFrameRate
 *
 * Renders current frame rate
 */
void DisplayFrameRate( void )
{
    DWORDtime2;
    charbuff[256];
static DWORDdwFrames;

    gdwFrameCount++;
    time2 = timeGetTime() - gdwFrameTime;
    if( time2 > 1000 )
    {
        dwFrames = (gdwFrameCount*1000)/time2;
        gdwFrameTime = timeGetTime();
        gdwFrameCount = 0;
    }
    if( dwFrames == 0 )
    {
        return;
    }

    if (dwFrames != gdwFramesLast)
    {
        gdwFramesLast = dwFrames;
    }

    if( dwFrames > 99 )
    {
dwFrames = 99;
    }
    buff[0] = (char)((dwFrames / 10) + '0');
    buff[1] = (char)((dwFrames % 10) + '0');
    buff[2] = '\0';
    bltScore(buff, 295, 10);
}

/*
 * DrawScore
 *
 * Renders our current score
 */
BOOL DrawScore( void )
{
    SHIP        ship;
    DWORD       dwSize;
    char        dwScorebuf[11];
    int         rem;
    HRESULT     hr;

    dwSize = sizeof(ship);
    hr = DPlayGetPlayerData(gOurID, &ship, &dwSize, DPGET_LOCAL);
    if (FAILED(hr))
    {
        wsprintf(gDebugBuff, TEXT("Get Player local data failed for id %d\n"), gOurID);
        DEBUG_OUT(gDebugBuff);
        goto FAIL;
    }

    // blt everything in reverse order if we are doing destination transparency
    // calculate dwScore string
    dwScorebuf[0] = (BYTE)ship.dwScore/100000 + '0';
    rem = ship.dwScore % 100000;
    dwScorebuf[1] = rem/10000 + '0';
    rem = ship.dwScore % 10000;
    dwScorebuf[2] = rem/1000 + '0';
    rem = ship.dwScore % 1000;
    dwScorebuf[3] = rem/100 + '0';
    rem = ship.dwScore % 100;
    dwScorebuf[4] = rem/10 + '0';
    rem = ship.dwScore % 10;
    dwScorebuf[5] = rem + '0';
    dwScorebuf[6] = '\0';

    bltScore(dwScorebuf, 8, 8);

// save ship data
hr = DPlaySetPlayerData(gOurID, &ship, sizeof(ship), DPSET_LOCAL);
    if (FAILED(hr))
    {
        ShowError(IDS_DPLAY_ERROR_SPLD);
        goto FAIL;
    }

    return TRUE;

FAIL:
    // failed
    return FALSE;
}

/*
 * DrawBlock
 *
 * Renders a block
 */
void DrawBlock( int x, int y )
{
    RECT    src;

    src.top = 0;
    src.left = 224;
    src.right = src.left + 16;
    src.bottom = src.top + 16;
    bltObject( x << 4, y << 4, glpNum, &src, DDBLTFAST_SRCCOLORKEY );
}

/*
 * DrawShip
 *
 * Renders a ship
 */
void DrawShip( LPSHIP lpShip )
{
    RECT    src;
    LPDIRECTDRAWSURFACE surf;

    src.top = 32 * (lpShip->cFrame / 10 );
    src.left = 32 * (lpShip->cFrame % 10 );
    src.right = src.left + 32;
    src.bottom = src.top + 32;
    switch( lpShip->byType )
    {
    case 0: surf = glpShip0; break;
    case 1: surf = glpShip1; break;
    case 2: surf = glpShip2; break;
    case 3: surf = glpShip3; break;
    default: DEBUG_OUT(TEXT("Ship type not specified\n")); return;
    }
    bltObject((int)lpShip->dPosX, (int)lpShip->dPosY, surf, &src, DDBLTFAST_SRCCOLORKEY );
}

/*
 * DrawBullet
 *
 * Renders a bullet
 */
void DrawBullet( LPSHIP lpShip )
{
    RECT    src;

    src.top = BULLET_Y;
    src.left = BULLET_X + (lpShip->byType)*4;
    src.right = src.left + 3;
    src.bottom = src.top + 3;
    bltObject((int)lpShip->dBulletPosX, (int)lpShip->dBulletPosY, glpNum, &src, DDBLTFAST_SRCCOLORKEY );
}

/*
 * DrawFragments
 *
 * Renders the fragments
 */

void DrawFragments( void )
{
    int     i;

    for(i=0; i<64; i++)
    {
if( gFrags[i].valid )
{
bltObject((int)gFrags[i].dPosX, (int)gFrags[i].dPosY, gFrags[i].surf,
&(gFrags[i].src), DDBLTFAST_SRCCOLORKEY );
}
    }
}

/*
 * ReceiveGameMessages
 *
 * Checks if there are any messages for us and receives them
 */
HRESULT ReceiveMessages( void )
{
DPIDidFrom, idTo;
LPVOIDlpvMsgBuffer;
DWORDdwMsgBufferSize;
HRESULThr;

// read all messages in queue
dwMsgBufferSize = gdwReceiveBufferSize;
lpvMsgBuffer = glpvReceiveBuffer;

while (TRUE)
{
// see what's out there
idFrom = 0;
idTo = 0;

hr = DPlayReceive(&idFrom, &idTo, DPRECEIVE_ALL, lpvMsgBuffer, &dwMsgBufferSize);
if (hr == DPERR_BUFFERTOOSMALL)
{
if (lpvMsgBuffer == NULL)
{
lpvMsgBuffer = GlobalAllocPtr(GHND, dwMsgBufferSize);
if (lpvMsgBuffer == NULL)
return (DPERR_NOMEMORY);
glpvReceiveBuffer = lpvMsgBuffer;
gdwReceiveBufferSize = dwMsgBufferSize;
}
else if (dwMsgBufferSize > gdwReceiveBufferSize)
{
lpvMsgBuffer = GlobalReAllocPtr(lpvMsgBuffer, dwMsgBufferSize, 0);
if (lpvMsgBuffer == NULL)
return (DPERR_NOMEMORY);
glpvReceiveBuffer = lpvMsgBuffer;
gdwReceiveBufferSize = dwMsgBufferSize;
}
}
else if ((hr == DP_OK) &&
                 ((dwMsgBufferSize >= sizeof(GENERICMSG)) ||
                  (dwMsgBufferSize >= sizeof(DPMSG_GENERIC))))
{
if (idFrom == DPID_SYSMSG)
            {
DoSystemMessage((LPDPMSG_GENERIC) lpvMsgBuffer, dwMsgBufferSize, idFrom, idTo);
            }
else
            {
DoApplicationMessage((LPGENERICMSG) lpvMsgBuffer, dwMsgBufferSize, idFrom, idTo);
            }
}
else
break;
};

    return hr;
}

/*
 * DoSystemMessage
 *
 * Evaluates system messages and performs appropriate actions
 */
void DoSystemMessage( LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize, DPID idFrom, DPID idTo )
{
    switch( lpMsg->dwType)
    {
    case DPSYS_CREATEPLAYERORGROUP:
        {
        LPDPMSG_CREATEPLAYERORGROUP lpAddMsg = (LPDPMSG_CREATEPLAYERORGROUP) lpMsg;

            if( gbIsHost)
    {
                gHostMsg.Blocks = gBlocks;
    SendGameMessage((LPGENERICMSG) &gHostMsg, lpAddMsg->dpId);
    }
        }
break;

    case DPSYS_DESTROYPLAYERORGROUP:
        {
            LPSHIP lpShip;
        LPDPMSG_DESTROYPLAYERORGROUP lpDestroyMsg = (LPDPMSG_DESTROYPLAYERORGROUP) lpMsg;

            if ((sizeof(SHIP) != lpDestroyMsg->dwLocalDataSize) ||
                (NULL == lpDestroyMsg->lpLocalData))
                break;

            lpShip = lpDestroyMsg->lpLocalData;
            ReleasePlayerLocalSoundData(lpShip);
        }
break;

case DPSYS_HOST:
        {
            gbIsHost = TRUE;
UpdateTitle();
        }

break;

    case DPSYS_SESSIONLOST:
        // inform user that session was lost
        ShowError(IDS_DPLAY_ERROR_SL);
        gbSessionLost = TRUE;
        break;
    }
}

/*
 * DoApplicationMessage
 *
 * Evaluates an application message and performs appropriate actions
 */
void DoApplicationMessage( LPGENERICMSG lpMsg, DWORD dwMsgSize, DPID idFrom, DPID idTo )
{
    HRESULT hr;

    switch( lpMsg->byType )
    {
    case MSG_HOST:
{
LPHOSTMSG       lpHost;

if( !gbIsHost )
{
lpHost = (LPHOSTMSG) lpMsg;
                // receive the field layout
gBlocks = lpHost->Blocks;

                // have host initializtion at this point
gbHaveHostInit = TRUE;

                // start updating screen
                gbIsActive = TRUE;
}
}
break;

    case MSG_BLOCKHIT:
{
LPBLOCKHITMSG   lpBlockHit;

lpBlockHit = (LPBLOCKHITMSG) lpMsg;
gBlocks.bits[lpBlockHit->byRow][lpBlockHit->byCol] &= ~lpBlockHit->byMask;
}
break;

    case MSG_ADDBLOCK:
{
LPADDBLOCKMSG   lpAddBlock;

lpAddBlock = (LPADDBLOCKMSG) lpMsg;
setBlock( lpAddBlock->byX, lpAddBlock->byY);
}
break;

    case MSG_SHIPHIT:
        {
    LPSHIPHITMSG    lpShipHit = (LPSHIPHITMSG) lpMsg;
            SHIP ship;
            DWORD dwSize;

            dwSize = sizeof(SHIP);
 // get player local data
hr = DPlayGetPlayerData(lpShipHit->Id, &ship, &dwSize, DPGET_LOCAL);
            if (FAILED(hr))
                return;

// no player data yet
if (0 == dwSize)
return;

if (!ship.bIgnore)
            {
// explode the ship on our screen
DestroyShip(&ship);

    // turn it off
ship.bEnable = FALSE;
ship.bBulletEnable = FALSE;

// if it is us
if (lpShipHit->Id == gOurID)
{
                    // set our hide time-out
    ship.iCountDown = HIDE_TIMEOUT;
                    ship.dwLastTick = timeGetTime();
                    // let the world know that we are dead
                    gShipHitMsg.Id = gOurID;
SendGameMessage((LPGENERICMSG) &gShipHitMsg, DPID_ALLPLAYERS);
}
}
            // update player local data
  DPlaySetPlayerData(lpShipHit->Id, &ship, sizeof(ship), DPSET_LOCAL);
        }
break;

    case MSG_CONTROL:
        {
            LPCONTROLMSG lpControlMsg;
            SHIP ship;
            DWORD dwSize;

            lpControlMsg = (LPCONTROLMSG) lpMsg;
            dwSize = sizeof(SHIP);
// get player local data
hr = DPlayGetPlayerData(idFrom, &ship, &dwSize, DPGET_LOCAL);
            if (FAILED(hr))
                return;

// no player data yet
if (0 == dwSize)
return;

            // update its State
            UpdateState(&ship, (DWORD)lpControlMsg->byState);

            // save it back
   DPlaySetPlayerData(idFrom, &ship, dwSize, DPSET_LOCAL);
        }
        break;

    case MSG_SYNC:
        {
            LPSYNCMSG lpSyncMsg;
            SHIP ship;
            DWORD dwSize;

            lpSyncMsg = (LPSYNCMSG) lpMsg;
            dwSize = sizeof(SHIP);
// get player local data
hr = DPlayGetPlayerData(idFrom, &ship, &dwSize, DPGET_LOCAL);
            if (FAILED(hr))
                return;

// we are seeing this player for the first time
// so do the initialization
if (0 == dwSize)
{
ZeroMemory(&ship, sizeof(ship));

// initialize sound buffers (this should never be us)
                if(gOurID == idFrom)
                {
                    MessageBox (NULL, "gOurID == idFrom", "Ack!!!", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
                    _asm int 3
                }
InitPlayerLocalSoundData(&ship, FALSE);

ship.byType = lpSyncMsg->byShipType;
                ship.dPosX = lpSyncMsg->dPosX;
                ship.dPosY = lpSyncMsg->dPosY;
                ship.cFrame = lpSyncMsg->cFrame;
                ship.dwLastTick = timeGetTime();
                ship.bEnable = TRUE;
}

if (ship.bEnable)
            {
                // head towards rendezvous location (accelerate/decelerate as necessary)
                ship.dVelX = (lpSyncMsg->dPosX - ship.dPosX)/1000;
                ship.dVelY = (lpSyncMsg->dPosY - ship.dPosY)/1000;
                ship.cFrame = lpSyncMsg->cFrame;
            }
            else if (!ship.bIgnore)
            // Ship is alive, but we just don't know it.
// So, display it at the rendezvous location.
            {
                ship.dPosX = lpSyncMsg->dPosX;
                ship.dPosY = lpSyncMsg->dPosY;
                ship.cFrame = lpSyncMsg->cFrame;
                ship.dwLastTick = timeGetTime();
                ship.bEnable = TRUE;
            }

            // save it back
   DPlaySetPlayerData(idFrom, &ship, sizeof(ship), DPSET_LOCAL);
        }
        break;

    default:
{
            wsprintf(gDebugBuff, TEXT("Unknown message type %d\n"), lpMsg->byType);
DEBUG_OUT( gDebugBuff );
}
break;
    }
}

/*
 * SendGameMessage
 *
 * Sends a message to specified player(s)
 */
void SendGameMessage( LPGENERICMSG lpMsg, DPID idTo )
{
    int             nBytes;
DWORDdwFlags = 0;

    if (gbSessionLost)
    {
        // no sends when we are not in the session
        return;
    }

    switch( lpMsg->byType )
    {
    case MSG_HOST:
        nBytes = sizeof( HOSTMSG );
dwFlags = DPSEND_GUARANTEED;
break;

    case MSG_BLOCKHIT:
nBytes = sizeof( BLOCKHITMSG );
break;

    case MSG_SHIPHIT:
nBytes = sizeof( SHIPHITMSG );
break;

    case MSG_ADDBLOCK:
nBytes = sizeof( ADDBLOCKMSG );
break;

    case MSG_CONTROL:
nBytes = sizeof( CONTROLMSG );
        break;

    case MSG_SYNC:
nBytes = sizeof( SYNCMSG );
        break;

    default:
        return;
}

    if (gbReliable)
    {
    dwFlags = DPSEND_GUARANTEED;
    }

// Send the message to the relevant player(s)
DPlaySend(gOurID, idTo, dwFlags, (LPVOID)lpMsg, nBytes);
}

/*
 * CleanupComm
 *
 * Cleans up communication stuff
 */
void CleanupComm(void)
{
HRESULT hr;

//free up all the local sound buffers
    ReleaseLocalData();

    // free the receive buffer
    if (glpvReceiveBuffer)
{
    GlobalFreePtr(glpvReceiveBuffer);
glpvReceiveBuffer = NULL;
}

    // delete our player
    if( gOurID )
    {
        hr = DPlayDestroyPlayer(gOurID);
        if (FAILED(hr))
        {
            ShowError(IDS_DPLAY_ERROR_DP);
        }
gOurID = 0;
    }

// cleanup directplay objects
hr = DPlayClose();
hr = DPlayRelease();
}


GAMEPROC.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:gameproc.h
 *  Content:game processing info. include file
 *
 *
 ***************************************************************************/
#define IDIRECTPLAY2_OR_GREATER
#include <ddraw.h>
#include <dplay.h>
#include <dsound.h>
#include "sfx.h"
#include "duel.h"

// align on single byte boundaries
// this is a stop-gap measure until the structures can be re-arranged.
#pragma pack(1)

#define     MAX_SHIP_X     (MAX_SCREEN_X - 32)
#define     MAX_SHIP_Y     (MAX_SCREEN_Y - 32)
#define     MAX_SHIP_FRAME 40
#define     MAX_BULLET_X    (MAX_SCREEN_X - 3)
#define     MAX_BULLET_Y    (MAX_SCREEN_Y - 3)
#define     MAX_BULLET_FRAME 400

#defineNUM_SHIP_TYPES 4

#defineDEF_SHOW_DELAY     (2000)
#defineMAX_BUFFER_SIZE   256

#define     UPDATE_INTERVAL     40      // interval between position updates in milliseconds (25 FPS)
#define     SYNC_INTERVAL       1000    // synchronize once every second
#define     HIDE_TIMEOUT        5000    // time for which a ship is disabled after a hit

/*
 * keyboard commands
 */

#define KEY_STOP        0x00000001l
#define KEY_DOWN        0x00000002l
#define KEY_LEFT        0x00000004l
#define KEY_RIGHT       0x00000008l
#define KEY_UP          0x00000010l
#define KEY_FIRE        0x00000020l
#define KEY_ENGINEOFF   0x00000040l

/*
 * Offsets for the bullet bitmap
 */

#define     BULLET_X    304
#define     BULLET_Y    0

typedef struct _frag
{
    doubledPosX;
    doubledPosY;
    LPDIRECTDRAWSURFACE surf;
    RECT        src;
    double      dVelX;
    double      dVelY;
    BOOL        valid;
} FRAG, *LPFRAG;

/*
 * structures
 */

typedef struct _SHIP
{
    doubledPosX, dPosY;// ship x and y position
    doubledBulletPosX, dBulletPosY;// bullet x and y position
    DWORDdwBulletFrame;// bullet frame
    char cFrame;// current ship frame
BYTEbyType;// ship type
    BOOL                bEnable;// is this ship active?
    BOOL                bBulletEnable;// Is there an active bullet?

    doubledVelX, dVelY;// ship x and y velocity (pixels/millisecond)
    doubledBulletVelX, dBulletVelY;// bullet x and y velocity (pixels/millisecond)
    DWORDdwScore;// current score
    DWORD               dwLastTick;// most recent time stamp
    BOOL                bIgnore;                // flag used to synchronize ship explosions
intiCountDown;                 // enable time-out
    DWORD               dwFrameCount;               // number of frames since beginning of time
    /* DSOUND members */
    LPDIRECTSOUNDBUFFER   lpDirectSoundBuffer  [MAX_SOUNDS]; // SoundBuffer interface
    LPDIRECTSOUND3DBUFFER lpDirectSound3DBuffer[MAX_SOUNDS]; // 3D interface (to same buffer)
    DWORD                 dwKeys;                            // the keyboard state
    BOOL                  bEngineRunning;                    // These BOOLs keep track of the ship's
    BOOL                  bMoving;                           //   last condition so we can play sounds
    BOOL                  bBounced;                          //   when they change
    BOOL                  bBlockHit;
    BOOL                  bDeath;
    BOOL                  bFiring;
    /* DSOUND members */

} SHIP, *LPSHIP;

typedef struct _BLOCKS
{
    BYTE        bits[30][5];
} BLOCKS, *LPBLOCKS;

//----------------------------------------------------------
// communication packet structures
//----------------------------------------------------------
#define MSG_HOST        0x11    // message containing field layout, sent by host
#define MSG_BLOCKHIT    0x22    // block hit message
#define MSG_SHIPHIT     0x33    // ship hit message
#define MSG_ADDBLOCK0x44    // add block message
#define MSG_CONTROL     0x55    // game keys message
#define MSG_SYNC        0x66    // synchronization message containing the rendezvous location

typedef struct _GENERICMSG
{
    BYTE        byType;
} GENERICMSG, *LPGENERICMSG;

typedef struct _HOSTMSG
{
    BYTE        byType;
    BLOCKS      Blocks;
} HOSTMSG, *LPHOSTMSG;

typedef struct _BLOCKHITMSG
{
    BYTE        byType;
    BYTE        byRow;
    BYTE        byCol;
    BYTE        byMask;
} BLOCKHITMSG, *LPBLOCKHITMSG;

typedef struct _SHIPHITMSG
{
    BYTE        byType;
    DPID        Id;
} SHIPHITMSG, *LPSHIPHITMSG;

typedef struct _ADDBLOCKMSG
{
    BYTE        byType;
    BYTE        byX;
    BYTE        byY;
} ADDBLOCKMSG, *LPADDBLOCKMSG;

typedef struct _CONTROLMSG
{
    BYTE        byType;
    BYTE        byState;
} CONTROLMSG, *LPCONTROLMSG;

typedef struct _SYNCMSG
{
    BYTE        byType;
    BYTE        byShipType;     // this is needed only when sends are unreliable
    char        cFrame;
    double      dPosX;
    double      dPosY;
} SYNCMSG, *LPSYNCMSG;

/*
 * Prototypes
 */
voidLaunchGame(void);
voidExitGame(void);
HRESULT InitOurShip(void);

HRESULT InitLocalSoundData(void);
void InitPlayerLocalSoundData(LPSHIP lpShip, BOOL bOurShip);
BOOL WINAPI SetPlayerLocalSoundDataCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
                     DWORD dwFlags, LPVOID lpContext);

void ReleaseLocalData(void);
void ReleasePlayerLocalSoundData(LPSHIP lpShip);
BOOL WINAPI ReleasePlayerLocalDataCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
                 DWORD dwFlags, LPVOID lpContext);

void    UpdateState(LPSHIP lpShip, DWORD dwControls);
void    SendSync(LPSHIP lpShip);
void    UpdateDisplayStatus(LPSHIP lpShip);
void    UpdatePosition( DPID dpId, LPSHIP ship );
BOOL    IsHit( int x, int y );
void    InitField(void);
BOOL    setBlock( int x, int y );
void    AddFrag(LPSHIP lpShip, int offX, int offY);
void    UpdateFragment(int i);
void    DestroyShip( LPSHIP lpShip);
voidDestroyGame( void );
BOOLUpdateFrame( void );

void    ProcessSoundFlags(LPSHIP lpShip);
BOOL WINAPI RenderPlayerCB(DPID dpId, DWORD dwPlayerType, LPCDPNAME lpName,
                     DWORD dwFlags, LPVOID lpContext);
BOOLDrawScreen( void );
BOOL    DrawScore( void );
void    DrawShip( LPSHIP lpShip );
void    DrawBlock( int x, int y );
void    DrawBullet( LPSHIP lpShip );
void    DrawFragments( void );
void    DisplayFrameRate( void );

voidGetConnection(void);
HRESULT ReceiveMessages( void );
voidDoSystemMessage( LPDPMSG_GENERIC lpMsg, DWORD dwMsgSize, DPID idFrom, DPID idTo );
void    DoApplicationMessage( LPGENERICMSG lpMsg, DWORD dwMsgSize, DPID idFrom, DPID idTo );
void    SendGameMessage( LPGENERICMSG lpMsg, DPID idTo );
voidCleanupComm(void);

// restore default alignment
#pragma pack()


GFX.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       gfx.c
 *  Content:    DirectDraw related code.
 *
 *
 ***************************************************************************/
#include "gfx.h"
#include "input.h"
#include "ddutil.h"

/*
 * Externals
 */
extern BOOL             gbUseEmulation;     // user DDHEL or DDHAL ?
extern BOOLgbFullscreen;// window or fullscreen mode ?
extern HWNDghWndMain;// main window
extern DWORDgdwKeys;
extern int              gnProgramState;     // current state of program (rest, splash, active, etc.)
extern RECTgrcWindow;          // client window rectangle
extern BOOLgbFullscreen;// fullscreen or window mode ?
extern HINSTANCEghinst;// application instance handle
extern TCHARgtszClassName[];// Duel's class name

/*
 * Globals
 */
LPDIRECTDRAWSURFACE     glpFrontBuffer;// primary surface
LPDIRECTDRAWSURFACE     glpBackBuffer;// back buffer for animation
LPDIRECTDRAWSURFACEglpSplash;// splash screen
LPDIRECTDRAWSURFACE     glpShip0;// ship bitmaps
LPDIRECTDRAWSURFACE     glpShip1;
LPDIRECTDRAWSURFACE     glpShip2;
LPDIRECTDRAWSURFACE     glpShip3;
LPDIRECTDRAWSURFACE     glpNum;// Numbers bitmap
LPDIRECTDRAW            glpDD;// DirectDraw interface
LPDIRECTDRAWPALETTE     glpArtPalette=NULL;// Game screen palette
LPDIRECTDRAWPALETTE     glpSplashPalette=NULL;// Splash screen palette
LPDIRECTDRAWCLIPPERglpClipper=NULL;// Clipper for front buffer
DWORD                   gdwFillColor;
intgnGameBPP;// primary surface bit depth
#ifdef DEBUG
BOOL                    gbHELBlt = FALSE;
#endif

BOOL InitGraphics( void )
{
    DDCAPS          ddcaps;
    HRESULT         ddrval;
    DDSURFACEDESC   ddsd;
    DDSCAPS         ddscaps;
TCHARtszTitle[MAX_WINDOWTITLE];

LoadString(ghinst, IDS_DUEL_TITLE, tszTitle, MAX_WINDOWTITLE);

// Create a window
    ghWndMain = CreateWindowEx(
WS_EX_APPWINDOW,  // WS_EX_TOPMOST,
        gtszClassName,
        tszTitle,
 // don't show the window yet
        WS_POPUP |   // non-app window POPUP
        WS_SYSMENU,  // so we get an icon in the tray
        0,
        0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        NULL,
        NULL,
        ghinst,
        NULL );

    if( !ghWndMain )
    {
        return FALSE;
    }

    UpdateWindow( ghWndMain );
    SetFocus( ghWndMain );

// ddraw stuff begins here
    if( gbUseEmulation )
        ddrval = DirectDrawCreate( (LPVOID) DDCREATE_EMULATIONONLY, &glpDD, NULL );
    else
        ddrval = DirectDrawCreate( NULL, &glpDD, NULL );

    if( ddrval != DD_OK )
        return ShowError(IDS_DDRAW_ERROR_DDC);

// set access mode based on fullscreen/window
if (gbFullscreen)
{
ddrval = glpDD->lpVtbl->SetCooperativeLevel( glpDD, ghWndMain,
                            DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
}
else
{
ddrval = glpDD->lpVtbl->SetCooperativeLevel( glpDD, ghWndMain,
                            DDSCL_NORMAL);
}

    if( ddrval != DD_OK )
        return ShowError(IDS_DDRAW_ERROR_SCL);

if (gbFullscreen)
{
    // set the mode to 640 by 480 by 8
ddrval = glpDD->lpVtbl->SetDisplayMode( glpDD, 640, 480, 8 );

if( ddrval != DD_OK )
    return ShowError(IDS_DDRAW_ERROR_SDM);
}
else
{
RECT rcWork;
RECT rc;
HDC hdc;
DWORD dwStyle;

//
//  when in rome (I mean when in windows) we should use the
//  current mode
//
hdc = GetDC(NULL);
gnGameBPP = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL);
ReleaseDC(NULL, hdc);

//
// if we are still a WS_POPUP window we should convert to a
// normal app window so we look like a windows app.
//
dwStyle = GetWindowStyle(ghWndMain);
dwStyle &= ~WS_POPUP;
dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX;
SetWindowLong(ghWndMain, GWL_STYLE, dwStyle);

// set window size
SetRect(&rc, 0, 0, MAX_DEFWIN_X, MAX_DEFWIN_Y);

AdjustWindowRectEx(&rc,
GetWindowStyle(ghWndMain),
GetMenu(ghWndMain) != NULL,
GetWindowExStyle(ghWndMain));

SetWindowPos(ghWndMain, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

SetWindowPos(ghWndMain, HWND_NOTOPMOST, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

//
//  make sure our window does not hang outside of the work area
//  this will make people who have the tray on the top or left
//  happy.
//
SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
GetWindowRect(ghWndMain, &rc);
if (rc.left < rcWork.left) rc.left = rcWork.left;
if (rc.top  < rcWork.top)  rc.top  = rcWork.top;
SetWindowPos(ghWndMain, NULL, rc.left, rc.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}

    // check the color key hardware capabilities
    ddcaps.dwSize = sizeof( ddcaps );

memset( &ddsd, 0, sizeof( ddsd ) );
ddsd.dwSize = sizeof( ddsd );

if (gbFullscreen)
{
// Create surfaces
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
  DDSCAPS_FLIP |
  DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpFrontBuffer, NULL );
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSFB);

// get a pointer to the back buffer
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = glpFrontBuffer->lpVtbl->GetAttachedSurface(
glpFrontBuffer,
&ddscaps,
&glpBackBuffer );
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_GAS);
}
else
{
// window case, create the primary surface
// and create a backbuffer in offscreen memory
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpFrontBuffer, NULL );
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSFB);

ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

ddsd.dwWidth = MAX_DEFWIN_X;
ddsd.dwHeight = MAX_DEFWIN_Y;
ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpBackBuffer, NULL );
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSBB);

ddrval = glpDD->lpVtbl->CreateClipper(glpDD, 0, &glpClipper, NULL);
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CC);

ddrval = glpClipper->lpVtbl->SetHWnd(glpClipper, 0, ghWndMain);
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_SH);

ddrval = glpFrontBuffer->lpVtbl->SetClipper(glpFrontBuffer, glpClipper);
if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_SC);
}

    ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
#ifdef DEBUG
    if( gbHELBlt )
        ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
#endif

    ddsd.dwWidth = 320;
    ddsd.dwHeight = 128;
    ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpShip0, NULL );
    if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSS0);

    ddsd.dwWidth = 320;
    ddsd.dwHeight = 128;
    ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpShip1, NULL );
    if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSS1);

    ddsd.dwWidth = 320;
    ddsd.dwHeight = 128;
    ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpShip2, NULL );
    if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSS2);

    ddsd.dwWidth = 320;
    ddsd.dwHeight = 128;
    ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpShip3, NULL );
    if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSS3);

    ddsd.dwHeight = 16;
    ddrval = glpDD->lpVtbl->CreateSurface( glpDD, &ddsd, &glpNum, NULL );
    if( ddrval != DD_OK )
return ShowError(IDS_DDRAW_ERROR_CSN);

    if( !RestoreSurfaces() )
        return ShowError(IDS_DDRAW_ERROR_RS);

    gdwKeys = 0;
ShowWindow(ghWndMain, SW_SHOW);
    return TRUE;
}

void CleanupGraphics(void)
{
    if( glpShip0 != NULL )
glpShip0->lpVtbl->Release( glpShip0 );

    if( glpShip1 != NULL )
glpShip1->lpVtbl->Release( glpShip1 );

    if( glpShip2 != NULL )
glpShip2->lpVtbl->Release( glpShip2 );

    if( glpShip3 != NULL )
glpShip3->lpVtbl->Release( glpShip3 );

    if( glpNum != NULL )
glpNum->lpVtbl->Release( glpNum );

if (!gbFullscreen && glpClipper)
glpClipper->lpVtbl->Release( glpClipper );

    if( glpFrontBuffer != NULL )
glpFrontBuffer->lpVtbl->Release( glpFrontBuffer );

     if( glpArtPalette != NULL )
glpArtPalette->lpVtbl->Release( glpArtPalette );

    if( glpSplashPalette != NULL )
glpSplashPalette->lpVtbl->Release( glpSplashPalette );

   if( !gbFullscreen && (glpBackBuffer != NULL ))
glpBackBuffer->lpVtbl->Release( glpBackBuffer );

    if( glpDD != NULL )
        glpDD->lpVtbl->Release( glpDD );
}

void bltSplash( LPRECT rc)
{
    HRESULT     ddrval;
    HBITMAP     hbm;

    if( ( glpFrontBuffer == NULL ) ||
        ( glpSplashPalette == NULL ) ||
( glpBackBuffer == NULL ) )
    {
        return;
    }

    // set the palette before loading the splash screen
glpFrontBuffer->lpVtbl->SetPalette( glpFrontBuffer, glpSplashPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), TEXT("SPLASH"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if ( NULL == hbm )
return;

    // if the surface is lost, DDCopyBitmap will fail and the surface will
    // be restored below.
    ddrval = DDCopyBitmap( glpBackBuffer, hbm, 0, 0, 0, 0 );

    DeleteObject( hbm );

    while( 1 )
    {
        if( NULL == rc)
{
ddrval = glpFrontBuffer->lpVtbl->Blt( glpFrontBuffer,
&grcWindow, glpBackBuffer, NULL, DDBLT_WAIT, NULL);
}
else
{
ddrval = glpFrontBuffer->lpVtbl->Blt( glpFrontBuffer,
&grcWindow, glpBackBuffer, rc, DDBLT_WAIT, NULL);
}

        if( ddrval == DD_OK )
    return;
        if( ddrval == DDERR_SURFACELOST )
            if( !RestoreSurfaces() )
                return;
        if( ddrval != DDERR_WASSTILLDRAWING )
            return;
    }
}

void bltScore( char *num, int x, int y )
{
    char *c;
    RECT    src;
    int     i;

    for(c=num; *c != '\0'; c++)
    {
        i = *c - '0';
        src.left = i*16;
        src.top = 0;
        src.right = src.left + 16;
        src.bottom = src.top + 16;
bltObject( x, y, glpNum, &src, DDBLTFAST_SRCCOLORKEY );
        x += 16;
    }
}

void bltObject( int x, int y, LPDIRECTDRAWSURFACE surf, LPRECT src, DWORD flags )
{
    HRESULT ddrval;

    while( 1 )
    {
        ddrval = glpBackBuffer->lpVtbl->BltFast( glpBackBuffer, x, y, surf, src, flags );
        if( ddrval == DD_OK )
    return;
        if( ddrval == DDERR_SURFACELOST )
            if( !RestoreSurfaces() )
                return;
        if( ddrval != DDERR_WASSTILLDRAWING )
            return;
    }
}

void EraseScreen( void )
{
    DDBLTFX     ddbltfx;
    HRESULT     ddrval;

    // Erase the background
    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = gdwFillColor;
    while( 1 )
    {
        ddrval = glpBackBuffer->lpVtbl->Blt( glpBackBuffer, NULL, NULL,
                 NULL, DDBLT_COLORFILL, &ddbltfx );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if( !RestoreSurfaces() )
                return;
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }
}

void FlipScreen( void )
{
    HRESULT     ddrval;

    // Flip the surfaces
if (gbFullscreen)
{
while( 1 )
{
ddrval = glpFrontBuffer->lpVtbl->Flip( glpFrontBuffer, NULL, 0 );
if( ddrval == DD_OK )
{
break;
}
if( ddrval == DDERR_SURFACELOST )
{
if( !RestoreSurfaces() )
{
return;
}
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
break;
}
}
    }
else
{
    ddrval = IDirectDrawSurface_Blt(
                    glpFrontBuffer,          // dest surface
                    &grcWindow,              // dest rect
                    glpBackBuffer,           // src surface
                    NULL,                   // src rect (all of it)
                    DDBLT_WAIT,
                    NULL);
}
}

BOOL RestoreSurfaces( void )
{
    HRESULTddrval;
    HBITMAP     hbm;

    ddrval = glpFrontBuffer->lpVtbl->Restore(glpFrontBuffer);
    if( ddrval != DD_OK )
        return FALSE;
ddrval = glpBackBuffer->lpVtbl->Restore(glpBackBuffer);
if( ddrval != DD_OK )
return FALSE;
    ddrval = glpShip0->lpVtbl->Restore(glpShip0);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = glpShip1->lpVtbl->Restore(glpShip1);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = glpShip2->lpVtbl->Restore(glpShip2);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = glpShip3->lpVtbl->Restore(glpShip3);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = glpNum->lpVtbl->Restore(glpNum);
    if( ddrval != DD_OK )
        return FALSE;

    // Create and set the palette for the splash bitmap
    glpSplashPalette = DDLoadPalette( glpDD, TEXT("SPLASH") );
    if( NULL == glpSplashPalette )
return FALSE;

    // Create and set the palette for the art bitmap
    glpArtPalette = DDLoadPalette( glpDD, TEXT("Duel8") );
    if( NULL == glpArtPalette )
return FALSE;

    // set the palette before loading the art
    glpFrontBuffer->lpVtbl->SetPalette( glpFrontBuffer, glpArtPalette );

    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), TEXT("Duel8"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

    if( NULL == hbm )
        return FALSE;

    ddrval = DDCopyBitmap( glpShip0, hbm, 0, 0, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
return FALSE;
    }

    ddrval = DDCopyBitmap( glpShip1, hbm, 0, 128, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
return FALSE;
    }

    ddrval = DDCopyBitmap( glpShip2, hbm, 0, 256, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
return FALSE;
    }

    ddrval = DDCopyBitmap( glpShip3, hbm, 0, 384, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
return FALSE;
    }

    ddrval = DDCopyBitmap( glpNum, hbm, 0, 512, 320, 16 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
return FALSE;
    }

    DeleteObject( hbm );

    // set colorfill colors and color gdwKeys according to bitmap contents
    gdwFillColor = DDColorMatch( glpShip0, CLR_INVALID );

    DDSetColorKey( glpShip0, CLR_INVALID );
    DDSetColorKey( glpShip1, CLR_INVALID );
    DDSetColorKey( glpShip2, CLR_INVALID );
    DDSetColorKey( glpShip3, CLR_INVALID );
    DDSetColorKey( glpNum, CLR_INVALID );

    return TRUE;
}

void SetGamePalette(void)
{
if (glpFrontBuffer)
glpFrontBuffer->lpVtbl->SetPalette( glpFrontBuffer, glpArtPalette );
}

void FlipToGDI(void)
{
if (glpDD)
glpDD->lpVtbl->FlipToGDISurface( glpDD );
}


GFX.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:gfx.h
 *  Content:graphics routines include file
 *
 *
 ***************************************************************************/
#include "duel.h"
#include "ddraw.h"

/*
 * Prototypes
 */
BOOLInitGraphics( void );
voidCleanupGraphics(void);
void    bltSplash( LPRECT );
void    bltScore( char *num, int x, int y );
void    bltObject( int x, int y, LPDIRECTDRAWSURFACE surf, LPRECT src, DWORD flags );
voidEraseScreen( void );
voidFlipScreen( void );
BOOLRestoreSurfaces( void );
voidSetGamePalette(void);
voidFlipToGDI(void);


INPUT.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.c
 *  Content:    DirectInput functionality for Multi-player duel
 *
 *
 ***************************************************************************/

#include "input.h"
#include "gameproc.h"

extern HINSTANCEghinst;// program instance
extern HWND                     ghWndMain;              // app window handle

static LPDIRECTINPUTlpdi;// DirectInput interface
static LPDIRECTINPUTDEVICE      lpdiKeyboard;           // keyboard device interface
static BOOLfKeybdAcquired;// has the keyboard been acquired?

extern DWORDgdwKeys;// gameplay keys

/*
*
* InitInput
*
* Initialize DirectInput objects & devices
*
*/
BOOL InitInput(void)
{
GUIDguid = GUID_SysKeyboard;
   HRESULT  hRes;

// try to create di object (DIRECTINPUT_VERSION == DX5)
if(DirectInputCreate(ghinst, DIRECTINPUT_VERSION, &lpdi, NULL) != DI_OK)
{
      // creation failed, try DX3 compatibility
      if(DirectInputCreate(ghinst, 0x0300, &lpdi, NULL) != DI_OK)
      {
         ShowError(IDS_DINPUT_ERROR_DIC);
         return FALSE;
      }
}

// try to create keyboard device
        if(lpdi->lpVtbl->CreateDevice(lpdi, &guid, &lpdiKeyboard, NULL) !=DI_OK)
{
ShowError(IDS_DINPUT_ERROR_CD);
return FALSE;
}

        // Tell DirectInput that we want to receive data in keyboard format
        if (lpdiKeyboard->lpVtbl->SetDataFormat(lpdiKeyboard, &c_dfDIKeyboard) != DI_OK)
        {
                ShowError(IDS_DINPUT_ERROR_DF);
                return FALSE;
        }

        // set cooperative level
        if(lpdiKeyboard->lpVtbl->SetCooperativeLevel(lpdiKeyboard, ghWndMain,
                         DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
{
ShowError(IDS_DINPUT_ERROR_SP);
return FALSE;
        }

// try to acquire the keyboard
   hRes = lpdiKeyboard->lpVtbl->Acquire(lpdiKeyboard);
   if(SUCCEEDED(hRes))
   {
      // keyboard was acquired
      fKeybdAcquired = TRUE;
}
   else
   {
      // keyboard was NOT acquired
      fKeybdAcquired = FALSE;
   }

// if we get here, all objects were created successfully
return TRUE;

}

/*
*
* DI_ReadKeys
*
* Use DirectInput to read game-play keys
*
*/
void DI_ReadKeys(void)
{
BYTE rgbKeybd[256];
   HRESULT hRes;

   hRes = lpdiKeyboard->lpVtbl->GetDeviceState(lpdiKeyboard, sizeof(rgbKeybd), rgbKeybd);
   if(hRes != DI_OK)
{
      if(hRes == DIERR_INPUTLOST)
      {
         // we lost control of the keyboard, reacquire
         fKeybdAcquired = FALSE;
         if(SUCCEEDED(lpdiKeyboard->lpVtbl->Acquire(lpdiKeyboard)))
         {
            fKeybdAcquired = TRUE;
         }
      }

      // failed to read the keyboard, just return
return;
}

// reset key states
gdwKeys = gdwKeys ^ gdwKeys;

// check & update key states
if(rgbKeybd[DIK_NUMPAD5] & 0x80)
gdwKeys |= KEY_STOP;

if((rgbKeybd[DIK_NUMPAD2] & 0x80) || (rgbKeybd[DIK_DOWN] & 0x80))
gdwKeys |= KEY_DOWN;

if((rgbKeybd[DIK_NUMPAD4] & 0x80) || (rgbKeybd[DIK_LEFT] & 0x80))
gdwKeys |= KEY_LEFT;

if((rgbKeybd[DIK_NUMPAD6] & 0x80) || (rgbKeybd[DIK_RIGHT] & 0x80))
gdwKeys |= KEY_RIGHT;

if((rgbKeybd[DIK_NUMPAD8] & 0x80) || (rgbKeybd[DIK_UP] & 0x80))
gdwKeys |= KEY_UP;

if(rgbKeybd[DIK_SPACE] & 0x80)
gdwKeys |= KEY_FIRE;

}

/*
*
* CleanupInput
*
* Cleans up DirectInput objects
*
*/
void CleanupInput(void)
{
if(fKeybdAcquired)
{
lpdiKeyboard->lpVtbl->Unacquire(lpdiKeyboard);
fKeybdAcquired = FALSE;
}

if(lpdiKeyboard != NULL)
lpdiKeyboard->lpVtbl->Release(lpdiKeyboard);

if(lpdi!= NULL)
lpdi->lpVtbl->Release(lpdi);

}

/*
*
* ReacquireInputDevices
*
* Reacquires DirectInput devices as needed
*
*/
BOOL ReacquireInputDevices(void)
{
// try to acquire the keyboard
        if(lpdiKeyboard != NULL)
        {
                lpdiKeyboard->lpVtbl->Acquire(lpdiKeyboard);
     }
        else
        {
                // keyboard device has not been created.
                fKeybdAcquired = FALSE;
                return FALSE;
        }

        // if we get here, we are acquired again
        fKeybdAcquired = TRUE;
        return TRUE;
}


INPUT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:input.h
 *  Content:input routines include file
 *
 *
 ***************************************************************************/

#ifndef _INPUT_H
#define _INPUT_H

#include <dinput.h>
#include "duel.h"

BOOLInitInput(void);
voidDI_ReadKeys(void);
voidCleanupInput(void);
BOOL                    ReacquireInputDevices(void);

#endif // _INPUT_H


LOBBY.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       lobby.c
 *  Content:    DirectPlayLobby related code
 *
 *
 ***************************************************************************/
#include "lobby.h"

/*
 * Externals
 */
extern LPDIRECTPLAY2glpDP; // directplay object pointer

/*
 * Globals
 */
LPDPLCONNECTIONglpdplConnection;// connection settings

/*
 * Statics
 */
static LPDIRECTPLAYLOBBYglpDPL;// lobby object pointer

/*
 * DPLobbyCreate
 *
 * Wrapper for DirectPlayLobby DirectPlayLobbyCreate API.
 */
HRESULT DPLobbyCreate(void)
{
HRESULT hr=E_FAIL;

hr = DirectPlayLobbyCreate(NULL, &glpDPL, NULL, NULL, 0);

return hr;
}

/*
 * DPLobbyConnect
 *
 * Wrapper for DirectPlayLobby Connect API.
 */
HRESULT DPLobbyConnect(void)
{
HRESULT hr=E_FAIL;

hr = IDirectPlayLobby_Connect(glpDPL, 0, &glpDP, NULL) ;

return hr;
}

/*
 * DPLobbyGetConnectionSettings
 *
 * Wrapper for DirectPlayLobby GetConnectionSettings API
 */
HRESULT DPLobbyGetConnectionSettings(void)
{
HRESULT hr=E_FAIL;
DWORD dwSize;

if (glpDPL)
{
// get size for the connection settings structure
hr = IDirectPlayLobby_GetConnectionSettings(glpDPL, 0, NULL, &dwSize);
if (DPERR_BUFFERTOOSMALL == hr)
{
// if we already have one, free it
if (glpdplConnection)
{
free(glpdplConnection);
glpdplConnection = NULL;
}

// allocate memory for the new one
glpdplConnection = (LPDPLCONNECTION) malloc(dwSize);

// get the connection settings
if (glpdplConnection)
hr = IDirectPlayLobby_GetConnectionSettings(glpDPL, 0, glpdplConnection, &dwSize);
}
}

return hr;
}

/*
 * DPLobbyRelease
 *
 * Wrapper for DirectPlayLobby Release API
 */
HRESULT DPLobbyRelease(void)
{
HRESULT hr=E_FAIL;

// free our connection settings
if (glpdplConnection)
{
free(glpdplConnection);
glpdplConnection = NULL;
}

// release the lobby object
if (glpDPL)
{
hr = IDirectPlayLobby_Release(glpDPL);
glpDPL = NULL;
}
return hr;
}

/*
 * DPLobbySetConnectionSettings
 *
 * Wrapper for DirectPlayLobby SetConnectionSettings API
 */
HRESULT DPLobbySetConnectionSettings(void)
{
HRESULT hr=E_FAIL;

    hr = IDirectPlayLobby_SetConnectionSettings(glpDPL, 0, 0, glpdplConnection);

return hr;
}


LOBBY.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:lobby.h
 *  Content:lobby related routines include file
 *
 *
 ***************************************************************************/
#include <dplobby.h>
#include "duel.h"

/*
 * Prototypes
 */

HRESULT DPLobbyCreate(void);
HRESULT DPLobbyConnect(void);
HRESULT DPLobbyGetConnectionSettings(void);
HRESULT DPLobbyRelease(void);
HRESULT DPLobbySetConnectionSettings(void);


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by duel.rc
//
#define IDS_DDRAW_ERROR_DDC             1
#define IDS_DDRAW_ERROR_SCL             2
#define IDS_DDRAW_ERROR_SDM             3
#define IDS_DDRAW_ERROR_CSFB            4
#define IDS_DDRAW_ERROR_GAS             5
#define IDS_DDRAW_ERROR_CC              6
#define IDS_DDRAW_ERROR_SH              7
#define IDS_DDRAW_ERROR_SC              8
#define IDS_DDRAW_ERROR_CSS0            9
#define IDS_DDRAW_ERROR_CSS1            10
#define IDS_DDRAW_ERROR_CSS2            11
#define IDS_DDRAW_ERROR_CSS3            12
#define IDS_DDRAW_ERROR_CSN             13
#define IDS_DDRAW_ERROR_RS              14
#define IDS_DINPUT_ERROR_DIC            15
#define IDS_DINPUT_ERROR_ED             16
#define IDS_DINPUT_ERROR_CD             17
#define IDS_DINPUT_ERROR_SP             18
#define IDS_DINPUT_ERROR_A              19
#define IDS_DPLAY_ERROR_CP              20
#define IDS_DPLAY_ERROR_JS              21
#define IDS_DPLAY_ERROR_CS              22
#define IDS_DDRAW_ERROR_CSBB            23
#define IDS_DUEL_CLASSNAME              24
#define IDS_DUEL_MSG_UNKNOWN            25
#define IDS_DUEL_ERROR_TITLE            26
#define IDS_DUEL_TITLE                  27
#define IDS_WIZARD_FONTNAME             29
#define IDS_WIZARD_TITLE_SP             30
#define IDS_WIZARD_TITLE_GS             31
#define IDS_WIZARD_TITLE_JS             32
#define IDS_WIZARD_TITLE_HS             33
#define IDS_WIZARD_TITLE                34
#define IDS_DUEL_HELP                   35
#define IDS_DDUTIL_ERROR_LI             36
#define IDS_DDUTIL_ERROR_DDCB           37
#define IDS_DDUTIL_ERROR_CCDC           38
#define IDS_WIZARD_ERROR_GSG            39
#define IDS_DINPUT_ERROR_DF             40
#define IDS_DPLAY_ERROR_IDC             41
#define IDS_DSOUND_LOADWAVES            42
#define IDS_DSOUND_DUPBUF               43
#define IDS_DPLAY_ERROR_SL              44
#define IDS_DPLAY_ERROR_GPLD            45
#define IDS_DPLAY_ERROR_SPLD            46
#define IDS_DPLAY_ERROR_GPRD            47
#define IDS_DPLAY_ERROR_SPRD            48
#define IDS_DPLAY_ERROR_GSD             49
#define IDS_DPLAY_ERROR_EP              50
#define IDS_DPLAY_ERROR_ES              51
#define IDS_DUEL_ERROR_CRT              52
#define IDS_DPLAY_ERROR_C               52
#define IDS_DUEL_ERROR_CRE              53
#define IDS_DPLAY_ERROR_R               53
#define IDS_DPLAY_ERROR_DP              54
#define IDS_DPLOBBY_ERROR_R             55
#define IDS_DPLOBBY_ERROR_GC            56
#define IDS_DPLOBBY_ERROR_GCS           56
#define IDS_DPLOBBY_ERROR_SCS           57
#define IDS_DPLOBBY_ERROR_C             58
#define IDS_DPLOBBY_ERROR_CONNECT       59
#define IDS_DUEL_HOST_TITLE             60
#define IDS_DUEL_HOST_RELIABLE_TITLE    61
#define IDS_DUEL_RELIABLE_TITLE         62
#define IDS_DPLAY_ERROR_CLSID           63
#define IDB_OPEN_SESSION                101
#define IDB_CLOSED_SESSION              102
#define IDB_PLAYER                      103
#define DUEL_ICON                       104
#define IDD_GAMESETUP                   108
#define IDD_CONNECT_STATUS              114
#define IDD_CHOOSEPROVIDER              129
#define IDD_JOINSESSION                 130
#define IDD_HOSTSESSION                 131
#define IDC_HOST_PLAYERNAME             1001
#define IDC_SP_TITLE                    1003
#define IDC_GAMESETUP_TITLE             1004
#define IDC_HOST_TITLE                  1005
#define IDC_HOST_SESSION_TITLE          1006
#define IDC_HOST_PLAYER_TITLE           1007
#define IDC_JOIN_SESSION_TITLE          1008
#define IDC_JOIN_PLAYER_TITLE           1009
#define IDC_DUEL_STATUSTEXT             1010
#define IDC_SERVICEPROVIDERS            1021
#define IDC_JOIN_SESSION                1024
#define IDC_HOST_SESSIONNAME            1025
#define IDC_JOIN_PLAYERNAME             1026
#define IDC_HOSTSESSION                 1027
#define IDC_JOINSESSION                 1028

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        115
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1011
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SFX.C

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       sfx.c
 *  Content:    Routines
 *
 *
 ***************************************************************************/
#include "resource.h"
#include "duel.h"
#include "ds3dutil.h"
#include "sfx.h"

/***************************** EXTERNALS ***************************/
extern  HWND ghWndMain;

/******************************* GLOBAL VARIABLES ***************************/
LPDIRECTSOUND                           glpDirectSound;
LPDIRECTSOUND3DLISTENER                      glpDirectSound3DListener;
LPDIRECTSOUNDBUFFER                     glpPrimaryBuffer;
LPWAVEDATA                              gSoundEffect[MAX_SOUNDS];

_TCHAR *szResourceName[MAX_SOUNDS] =
{_T("BFIRE"),  //Bullet Firing
 _T("SBOOM"),  //Ship Exploding
 _T("SENGINE"),//Ship Engine
 _T("SSTART"), //Starting Engine
 _T("SSTOP"),  //Stopping Engine (key 5)
 _T("SBOUNCE"),//Bouncing off a block or window edge
 _T("LBOOM")}; //Block destruction

BOOL gbSoundInitialized = FALSE; //did the sound engine initialize or not?

/***************************** FUNCTION PROTOTYPES **************************/
BOOL Init_Sounds(void);
void Free_Sounds(void);
BOOL Init_Globals(void);
void Free_Globals(void);

/****************************************************************************
FUNCTION:   InitSfx

PURPOSE:    Initializes global variables, then loads the gSoundEffect "objects"
            with sound data.  This should always return TRUE.  If for some reason
            sound can't be initialized, then it will note that fact and still
            return TRUE.
*****************************************************************************/
BOOL InitSfx(void)
{
if (Init_Globals())
    {
    if (Init_Sounds())
        {
        gbSoundInitialized=TRUE;
        return TRUE;
        }
    Free_Globals();
    }
gbSoundInitialized = FALSE;
return TRUE;
};

/****************************************************************************
FUNCTION:   CleanupSfx

PURPOSE:    Frees everything that was allocated by InitSfx, if it was
            successfully initialized in the beginning.
*****************************************************************************/
void CleanupSfx(void)
{
if (gbSoundInitialized)
    {
    Free_Sounds();
    Free_Globals();
    }
};

/****************************************************************************
FUNCTION:   Init_Globals

PURPOSE:    Initializes the three main global variables. After this is done,
            we should have allocated:
            a. A DirectSound Object
            b. A DirectSound3DListener Object
            c. A Primary Buffer (16 bit, stereo, 22050khz)

            These are all global.
*****************************************************************************/
BOOL Init_Globals(void)
{
PCMWAVEFORMAT pcmwf;
DSBUFFERDESC dsbdesc;

if (DS_OK == DirectSoundCreate(NULL, &glpDirectSound, NULL))
    {
    memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
    pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
    pcmwf.wf.nChannels = 2;
    pcmwf.wf.nSamplesPerSec = 22050;
    pcmwf.wf.nBlockAlign = 4;
    pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
    pcmwf.wBitsPerSample = 16;
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
    dsbdesc.dwSize = sizeof(DSBUFFERDESC);
    dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D;
    dsbdesc.dwBufferBytes = 0; //dwBufferBytes and lpwfxFormat must be set this way.
    dsbdesc.lpwfxFormat = NULL;

    if (DS_OK== IDirectSound_SetCooperativeLevel(glpDirectSound, ghWndMain,DSSCL_NORMAL))
        {
        if (DS_OK== IDirectSound_CreateSoundBuffer(glpDirectSound, &dsbdesc, &glpPrimaryBuffer, NULL))
            {
            if (DS_OK==glpPrimaryBuffer->lpVtbl->QueryInterface(glpPrimaryBuffer, &IID_IDirectSound3DListener, (void **)&glpDirectSound3DListener))
                {
                if (DS_OK==IDirectSound3DListener_SetPosition(glpDirectSound3DListener, 0.f, 0.f, -1.f, DS3D_DEFERRED))
                    {
                    if (DS_OK==IDirectSound3DListener_SetDopplerFactor(glpDirectSound3DListener, 9.9f ,DS3D_DEFERRED))
                        {
                        if (DS_OK==IDirectSound3DListener_SetRolloffFactor(glpDirectSound3DListener, 0.25f ,DS3D_DEFERRED))
                            {
                            if (DS_OK==IDirectSound3DListener_CommitDeferredSettings(glpDirectSound3DListener))
                                {
                                return TRUE;
                                }
                            }
                        }
                    }
                IDirectSound3DListener_Release(glpDirectSound3DListener);
                }
            glpPrimaryBuffer->lpVtbl->Release(glpPrimaryBuffer);
            }
        }
    IDirectSound_Release(glpDirectSound);
    }
return (FALSE);
};

/*****************************************************************************
FUNCTION:   Free_Globals

PURPOSE:    Frees up all three of the global interfaces that were created by
            Init_Globabls.

    NOTES:      Free_Sounds MUST be called before this.
*****************************************************************************/
void Free_Globals(void)
{
if (glpPrimaryBuffer!=NULL)
    glpPrimaryBuffer->lpVtbl->Release(glpPrimaryBuffer);
if (glpDirectSound3DListener!=NULL)
    IDirectSound3DListener_Release(glpDirectSound3DListener);
if (glpDirectSound!=NULL)
    IDirectSound_Release(glpDirectSound);
};

/****************************************************************************
FUNCTION:   Init_Sounds

PURPOSE:    Loads the gSoundEffect array (of sound objects) with the correct
            data from the WAVE resources, so that only a GetBuffers call needs
            to be made to get access to the sound effects.

NOTES:      If this fails, all sound objects will end up uninitialized.
            Init_Globals MUST be called before this function.
*****************************************************************************/
BOOL Init_Sounds(void)
{
int i;

for (i=0; i<MAX_SOUNDS; i++)
    {
    if (FALSE==WaveInit(&gSoundEffect[i], glpDirectSound, szResourceName[i]))
        {
        gSoundEffect[i] = NULL;
        for (--i; i>=0; i--)
            {
            WaveFree(gSoundEffect[i]);
            gSoundEffect[i] = NULL;
            }
        ShowError(IDS_DSOUND_LOADWAVES);
        return FALSE;
        }
    }
return TRUE;
};

/*****************************************************************************
FUNCTION:   Free_Sounds

PURPOSE:    Frees up any sound objects that were loaded into memory.
*****************************************************************************/
void Free_Sounds(void)
{int i;

for (i=0; i<MAX_SOUNDS; i++)
    {
    if (gSoundEffect[i]!=NULL)
        {
        WaveFree(gSoundEffect[i]);
        gSoundEffect[i] = NULL;
        }
    }
};


SFX.H

#ifndef SFX_HEADER
#define SFX_HEADER
/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       sfx.h
 *  Content:    Routines
 *
 *
 ***************************************************************************/

#include <dsound.h>
#include "ds3dutil.h"
/******************* DEFINITIONS ***************************
These will be used to index the array of CSound3D objects.
*********************************************************/
#define BFIRE       0 //Bullet Firing
#define SBOOM       1 //Ship Exploding
#define SENGINE     2 //Ship Engine
#define SSTART      3 //Starting Engine
#define SSTOP       4 //Stopping Engine (key 5)
#define SBOUNCE     5 //Bouncing off a block or window edge
#define LBOOM       6 //Block destruction

#define MAX_SOUNDS  7

/******************* DEFINITIONS FOR SHARED INFO ************/
#define ENGINE_STATE 0x00000001l      // Set if Engine is running

/**************** CONVERSION MACROS **************************
These would be faster if hard-coded, but are left like this
for readability.
**************************************************************/
#define SCREEN_METERS D3DVAL(6)         //(How many meters wide is the screen?)
#define PPM    D3DVAL(640/SCREEN_METERS)  //How many pixels per meter?
#define MPP    D3DVAL(SCREEN_METERS/640.f)  //How many meters per pixel?
#define P2M(x) D3DVAL(D3DVAL(x) * MPP)   //converts pixels to meters
#define M2P(x) D3DVAL(D3DVAL(x) * PPM)   //converts meters to pixels

/****************** FUNCTION PROTOTYPES *********************/
BOOL InitSfx(void);
void CleanupSfx(void);
/************************ VARIABLES *************************/
extern LPWAVEDATA           gSoundEffect[MAX_SOUNDS];
extern BOOL                 gbSoundInitialized;
extern CHAR *               gszResourceName[MAX_SOUNDS];
extern LPDIRECTSOUND        glpDirectSound;
extern LPDIRECTSOUND3DLISTENER   glpDirectSound3DListener;
extern LPDIRECTSOUNDBUFFER  glpPrimaryBuffer;

#endif


UTIL.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       util.c
 *  Content:    miscellaneous functions
 *
 *
 ***************************************************************************/
#include "util.h"

/*
 * Globals
 */
static const BYTE GuidMap[] = { 3, 2, 1, 0, '-', 5, 4, '-', 7, 6, '-',
                                8, 9, '-', 10, 11, 12, 13, 14, 15 };
static const WCHAR wszDigits[] = L"0123456789ABCDEF";

/*
 * StringFromGUID
 *
 * Converts a GUID into a wide string
 */
int StringFromGUID(LPGUID lpguid, LPWSTR lpwsz)
{
    int i;

    const BYTE * pBytes = (const BYTE *) lpguid;

    *lpwsz++ = L'{';

    for (i = 0; i < sizeof(GuidMap); i++)
    {
        if (GuidMap[i] == '-')
        {
            *lpwsz++ = L'-';
        }
        else
        {
            *lpwsz++ = wszDigits[ (pBytes[GuidMap[i]] & 0xF0) >> 4 ];
            *lpwsz++ = wszDigits[ (pBytes[GuidMap[i]] & 0x0F) ];
        }
    }
    *lpwsz++ = L'}';
    *lpwsz   = L'\0';

    return GUIDSTR_MAX;
}

/*
 * IsEqualGuid
 *
 * Determines if two guids are equal
 */
BOOL  IsEqualGuid(GUID *lpguid1, GUID *lpguid2)
{
   return (
      ((PLONG) lpguid1)[0] == ((PLONG) lpguid2)[0] &&
      ((PLONG) lpguid1)[1] == ((PLONG) lpguid2)[1] &&
      ((PLONG) lpguid1)[2] == ((PLONG) lpguid2)[2] &&
      ((PLONG) lpguid1)[3] == ((PLONG) lpguid2)[3]);
}

// convert a hex char to an int - used by str to guid conversion
// we wrote our own, since the ole one is slow, and requires ole32.dll
// we use ansi strings here, since guids won't get internationalized
int GetDigit(LPSTR lpstr)
{
char ch = *lpstr;

    if (ch >= '0' && ch <= '9')
        return(ch - '0');
    if (ch >= 'a' && ch <= 'f')
        return(ch - 'a' + 10);
    if (ch >= 'A' && ch <= 'F')
        return(ch - 'A' + 10);
    return(0);
}
// walk the string, writing pairs of bytes into the byte stream (guid)
// we need to write the bytes into the byte stream from right to left
// or left to right as indicated by fRightToLeft
void ConvertField(LPBYTE lpByte,LPSTR * ppStr,int iFieldSize,BOOL fRightToLeft)
{
int i;

for (i=0;i<iFieldSize ;i++ )
{
// don't barf on the field separators
if ('-' == **ppStr) (*ppStr)++;
if (fRightToLeft == TRUE)
{
// work from right to left within the byte stream
*(lpByte + iFieldSize - (i+1)) = 16*GetDigit(*ppStr) + GetDigit((*ppStr)+1);
}
else
{
// work from  left to right within the byte stream
*(lpByte + i) = 16*GetDigit(*ppStr) + GetDigit((*ppStr)+1);
}
*ppStr+=2; // get next two digit pair
}
} // ConvertField

// convert the passed in string to a real GUID
// walk the guid, setting each byte in the guid to the two digit hex pair in the
// passed string
HRESULT GUIDFromString(LPWSTR lpWStr, GUID * pGuid)
{
BYTE * lpByte; // byte index into guid
int iFieldSize; // size of current field we're converting
// since its a guid, we can do a "brute force" conversion
char lpTemp[GUID_STRING_SIZE];
char *lpStr = lpTemp;

WideToAnsi(lpStr,lpWStr,GUID_STRING_SIZE);

// make sure we have a {xxxx-...} type guid
if ('{' !=  *lpStr) return E_FAIL;
lpStr++;

lpByte = (BYTE *)pGuid;
// data 1
iFieldSize = sizeof(unsigned long);
ConvertField(lpByte,&lpStr,iFieldSize,TRUE);
lpByte += iFieldSize;

// data 2
iFieldSize = sizeof(unsigned short);
ConvertField(lpByte,&lpStr,iFieldSize,TRUE);
lpByte += iFieldSize;

// data 3
iFieldSize = sizeof(unsigned short);
ConvertField(lpByte,&lpStr,iFieldSize,TRUE);
lpByte += iFieldSize;

// data 4
iFieldSize = 8*sizeof(unsigned char);
ConvertField(lpByte,&lpStr,iFieldSize,FALSE);
lpByte += iFieldSize;

// make sure we ended in the right place
if ('}' != *lpStr)
{
memset(pGuid,0,sizeof(GUID));
return E_FAIL;
}

return S_OK;
}// GUIDFromString

/*
 ** WideToAnsi
 *
 *  CALLED BY:everywhere
 *
 *  PARAMETERS: lpStr - destination string
 *lpWStr - string to convert
 *cchStr - size of dest buffer
 *
 *  DESCRIPTION:
 *converts unicode lpWStr to ansi lpStr.
 *fills in unconvertable chars w/ DPLAY_DEFAULT_CHAR "-"
 *
 *
 *  RETURNS:  if cchStr is 0, returns the size required to hold the string
 *otherwise, returns the number of chars converted
 *
 */
int WideToAnsi(LPSTR lpStr,LPWSTR lpWStr,int cchStr)
{

int rval;
BOOL bDefault;

// use the default code page (CP_ACP)
// -1 indicates WStr must be null terminated
rval = WideCharToMultiByte(CP_ACP,0,lpWStr,-1,lpStr,cchStr,"-",&bDefault);

return rval;

} // WideToAnsi

/*
 ** AnsiToWide
 *
 *  CALLED BY: everywhere
 *
 *  PARAMETERS: lpWStr - dest string
 *lpStr  - string to convert
 *cchWstr - size of dest buffer
 *
 *  DESCRIPTION: converts Ansi lpStr to Unicode lpWstr
 *
 *
 *  RETURNS:  if cchStr is 0, returns the size required to hold the string
 *otherwise, returns the number of chars converted
 *
 */
int AnsiToWide(LPWSTR lpWStr,LPSTR lpStr,int cchWStr)
{
int rval;

rval =  MultiByteToWideChar(CP_ACP,0,lpStr,-1,lpWStr,cchWStr);

return rval;
}  // AnsiToWide

/*
 * randInt
 *
 * returns a random integer in the specified range
 */
int randInt( int low, int high )
{
    int range = high - low;
    int num = rand() % range;
    return( num + low );
}

/*
 * randDouble
 *
 * returns a random double in the specified range
 */
double randDouble( double low, double high )
{
    double range = high - low;
    double num = range * (double)rand()/(double)RAND_MAX;
    return( num + low );
}


UTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:util.h
 *  Content:miscellaneous utilities include file
 *
 *
 ***************************************************************************/

#include <windows.h>

#define GUIDSTR_MAX 38
#define GUID_STRING_SIZE 64

intStringFromGUID(GUID *lpguid, LPWSTR lpsz);
BOOLIsEqualGuid(GUID *lpguid1, LPGUID lpguid2);
intGetDigit(LPSTR lpstr);
voidConvertField(LPBYTE lpByte,LPSTR * ppStr,int iFieldSize,BOOL fRightToLeft);
HRESULT GUIDFromString(LPWSTR lpWStr, GUID * pGuid);
intWideToAnsi(LPSTR lpStr,LPWSTR lpWStr,int cchStr);
intAnsiToWide(LPWSTR lpWStr,LPSTR lpStr,int cchWStr);
intrandInt( int low, int high );
doublerandDouble( double low, double high );


WIZARD.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       wizard.c
 *  Content:    User input (setup wizard) related code
 *
 *
 ***************************************************************************/
#include <windows.h>
#include "prsht.h"
#include "wizard.h"
#include "util.h"
#include "comm.h"
#include "gameproc.h"

/*
 * Externals
 */
extern HINSTANCEghinst;// program instance
extern HWNDghWndMain;// main window
extern BOOLgbIsHost;// is the user hosting/joining a game
extern DPIDgOurID;// player id
extern LPGUIDglpGuid;// duel's guid
extern LPDIRECTPLAY3glpDP3A;// An IDirectPlay3 interface.

/*
 * Globals to this module
 */
static HFONTghTitleFont;// font for titles on setup wizard
static HFONTghHeadingFont;// font for headings on setup wizard
static intgnSession,gnPlayer; // indexes for tree view images
static HKEYghDuelKey=NULL;// duel registry key handle
static DWORDgdwDuelDisp;// key created or opened
static HTREEITEMghtiSession;// points to a visible session item in tree control
//  used for inserting players into tree control
static HWNDghWndSPCtl;// handle to service provider control

/*
 * SetupFonts
 *
 * Initializes font structures (used for wizard controls)
 */
void SetupFonts(HDC hDC)
{
LOGFONT lf;
TCHAR tszFontName[MAX_FONTNAME];

LoadString(ghinst, IDS_WIZARD_FONTNAME, tszFontName, MAX_FONTNAME);

ZeroMemory(&lf,sizeof(lf));
lf.lfHeight = -MulDiv(11,GetDeviceCaps(hDC, LOGPIXELSY),72);
lf.lfWeight = 500;
lf.lfItalic = TRUE;
_tcscpy(lf.lfFaceName,tszFontName);
ghTitleFont = CreateFontIndirect(&lf);

ZeroMemory(&lf,sizeof(lf));
lf.lfHeight = -MulDiv(11,GetDeviceCaps(hDC, LOGPIXELSY),72);
lf.lfWeight = 500;
_tcscpy(lf.lfFaceName,tszFontName);
ghHeadingFont = CreateFontIndirect(&lf);
}

/*
 * CleanupFonts
 *
 * Cleans up font structures
 */
void CleanupFonts(void)
{
if (ghTitleFont) DeleteObject(ghTitleFont);
if (ghHeadingFont) DeleteObject(ghHeadingFont);
}

/*
 * RegSet
 *
 * Stores a data value in the registry
 */
LONG RegSet(LPCTSTR lptszName, CONST BYTE * lpData, DWORD dwSize)
{
#ifdef UNICODE
dwSize *= 2; // calc number of bytes
#endif
return RegSetValueEx(ghDuelKey, lptszName, 0, REG_SZ, lpData, dwSize);
}

/*
 * RegSetA
 *
 * Stores data as an ascii string in the registry
 */
LONG RegSetA(LPCTSTR lptszName, CONST BYTE * lpData, DWORD dwSize)
{
return RegSetValueEx(ghDuelKey, lptszName, 0, REG_SZ, lpData, dwSize);
}

/*
 * RegGet
 *
 * Queries a value from the registry
 */
LONG RegGet(LPCTSTR lptszName, LPBYTE lpData, LPDWORD lpdwDataSize)
{
DWORD dwType;

return RegQueryValueEx(ghDuelKey, lptszName, NULL, &dwType, lpData, lpdwDataSize);
}

/*
 * DoWizard
 *
 * Creates and launches a wizard (property sheets) for user input
 */
DWORD WINAPI DoWizard(LPVOID pv)
{
PROPSHEETPAGE psp[4];
    PROPSHEETHEADER psh;
TCHAR tszTitle1[MAX_WINDOWTITLE];
TCHAR tszTitle2[MAX_WINDOWTITLE];
TCHAR tszTitle3[MAX_WINDOWTITLE];
TCHAR tszTitle4[MAX_WINDOWTITLE];
TCHAR tszTitle5[MAX_WINDOWTITLE];
    HDC hDC;

CoInitialize( NULL );

LoadString(ghinst, IDS_WIZARD_TITLE_SP, tszTitle1, MAX_WINDOWTITLE);
LoadString(ghinst, IDS_WIZARD_TITLE_GS, tszTitle2, MAX_WINDOWTITLE);
LoadString(ghinst, IDS_WIZARD_TITLE_JS, tszTitle3, MAX_WINDOWTITLE);
LoadString(ghinst, IDS_WIZARD_TITLE_HS, tszTitle4, MAX_WINDOWTITLE);
LoadString(ghinst, IDS_WIZARD_TITLE, tszTitle5, MAX_WINDOWTITLE);

    psp[0].dwSize = sizeof(PROPSHEETPAGE);
    psp[0].dwFlags = PSP_USETITLE;
    psp[0].hInstance = ghinst;
    psp[0].pszTemplate = MAKEINTRESOURCE(IDD_CHOOSEPROVIDER);
    psp[0].pszIcon = NULL;
    psp[0].pfnDlgProc = DlgProcChooseProvider;
    psp[0].pszTitle = tszTitle1;
    psp[0].lParam = 0;
    psp[0].pfnCallback = NULL;

    psp[1].dwSize = sizeof(PROPSHEETPAGE);
    psp[1].dwFlags = PSP_USETITLE;
    psp[1].hInstance = ghinst;
    psp[1].pszTemplate = MAKEINTRESOURCE(IDD_GAMESETUP);
    psp[1].pszIcon = NULL;
    psp[1].pfnDlgProc = DlgProcGameSetup;
    psp[1].pszTitle = tszTitle2;
    psp[1].lParam = 0;
    psp[1].pfnCallback = NULL;

    psp[2].dwSize = sizeof(PROPSHEETPAGE);
    psp[2].dwFlags = PSP_USETITLE;
    psp[2].hInstance = ghinst;
    psp[2].pszTemplate = MAKEINTRESOURCE(IDD_JOINSESSION);
    psp[2].pszIcon = NULL;
    psp[2].pfnDlgProc = DlgProcJoinSession;
    psp[2].pszTitle = tszTitle3;
    psp[2].lParam = 0;
    psp[2].pfnCallback = NULL;

    psp[3].dwSize = sizeof(PROPSHEETPAGE);
    psp[3].dwFlags = PSP_USETITLE;
    psp[3].hInstance = ghinst;
    psp[3].pszTemplate = MAKEINTRESOURCE(IDD_HOSTSESSION);
    psp[3].pszIcon = NULL;
    psp[3].pfnDlgProc = DlgProcHostSession;
    psp[3].pszTitle = tszTitle4;
    psp[3].lParam = 0;
    psp[3].pfnCallback = NULL;

psh.dwSize = sizeof(PROPSHEETHEADER);
    psh.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD;
    psh.hwndParent = ghWndMain;
    psh.hInstance = ghinst;
    psh.pszIcon = NULL;
    psh.pszCaption = tszTitle5;
    psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
    psh.nStartPage = 0;
    psh.ppsp = (LPCPROPSHEETPAGE) &psp;
    psh.pfnCallback = NULL;

// open/create duel registry key
    if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_CURRENT_USER,
                    DUEL_KEY,
                    0,
                    NULL,
                    REG_OPTION_NON_VOLATILE,
                    KEY_ALL_ACCESS,
                    NULL,
                    &ghDuelKey,
                        &gdwDuelDisp
                    ))
    {
        goto ABORT;
    }

    hDC = GetDC(ghWndMain);
    if (!hDC)
    {
        goto ABORT;
    }

// setup fonts
SetupFonts(hDC);

// launch the wizard
    PropertySheet(&psh);

// was communication initialized correctly ?
if (!IsDPlay())
{
        goto ABORT;
}

// cleanup
CleanupFonts();
    ReleaseDC(ghWndMain, hDC);

CoUninitialize();
    // success
PostMessage( ghWndMain, UM_LAUNCH, 0, 0 );
    return 0;

ABORT:
    // failure
    CleanupFonts();
    if (hDC)
    {
        ReleaseDC(ghWndMain, hDC);
    }
    PostMessage( ghWndMain, UM_ABORT, 0, 0 );
return 1;
}

/*
 * DPEnumConnectionsCallback
 *
 * creates SP list box entries and associates connection data with them
 */
BOOL FAR PASCAL DPEnumConnectionsCallback(
LPCGUIDlpguidSP,
LPVOIDlpConnection,
DWORDdwSize,
LPCDPNAMElpName,
DWORDdwFlags,
LPVOIDlpContext)
{

    HWNDhWnd = (HWND) lpContext;
    LRESULTiIndex;
LPVOIDlpConnectionBuffer;

// store service provider name in combo box
iIndex = SendMessage(hWnd, LB_ADDSTRING, 0, (LPARAM) lpName->lpszShortNameA);

if (iIndex == CB_ERR)
goto FAILURE;

// make space for Connection Shortcut
lpConnectionBuffer = GlobalAllocPtr(GHND, dwSize);
if (lpConnectionBuffer == NULL)
goto FAILURE;

// store pointer to GUID in combo box
memcpy(lpConnectionBuffer, lpConnection, dwSize);

if (iIndex != LB_ERR)
SendMessage(hWnd, LB_SETITEMDATA, iIndex, (LPARAM) lpConnectionBuffer);

FAILURE:
    return (TRUE);
}

/*
 * DlgProcChooseProvider
 *
 * Dialog procedure for the choose service provider dialog
 */
BOOL CALLBACK DlgProcChooseProvider(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
LPVOIDlpCon = NULL;
    static  LONG    iIndex;
    static  HWND hWndCtl;
    HRESULT hr;

    switch (msg)
    {
case WM_NOTIFY:
switch (((NMHDR FAR *) lParam)->code)
{
case PSN_SETACTIVE:
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_NEXT);
return(TRUE);

case PSN_WIZNEXT:
// Release previously selected DPlay object, if any.
DPlayRelease();
if (iIndex != LB_ERR)
{
lpCon = (LPVOID) SendMessage(hWndCtl, LB_GETITEMDATA, iIndex, 0);

if (lpCon)
{
// create directplay object
if ((hr = DPlayCreate(lpCon)) == DP_OK)
                    {
    return(TRUE);
                    }
                    else
                    {
                        ShowError(IDS_DPLAY_ERROR_IDC);
                    }
}
}
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return(TRUE);

case PSN_QUERYCANCEL:
ReleaseSPData();
DPlayRelease();
return(TRUE);
}
break;

    case WM_INITDIALOG:
SendDlgItemMessage(hDlg, IDC_SP_TITLE, WM_SETFONT, (WPARAM)ghTitleFont, MAKELPARAM(TRUE,0));

        hWndCtl = GetDlgItem(hDlg, IDC_SERVICEPROVIDERS);
        if (hWndCtl == NULL) return(TRUE);

// remember the service provider control. used later in freeing sp information.
ghWndSPCtl = hWndCtl;

if ((hr = DPlayCreate(lpCon)) != DP_OK)
        {
// class not available, so tell them to get DirectX 5 or later
if ((hr == REGDB_E_CLASSNOTREG) || (hr == REGDB_E_IIDNOTREG))
ShowError(IDS_DPLAY_ERROR_CLSID);
else
ShowError(IDS_DPLAY_ERROR_IDC);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
PostQuitMessage(-1);
return(TRUE);
        }

//Insert all available connections into the connection listbox.
        IDirectPlay3_EnumConnections( glpDP3A, glpGuid, DPEnumConnectionsCallback, hWndCtl, 0);
        SetFocus(hWndCtl);

        SendMessage(hWndCtl, LB_SETCURSEL, iIndex, 0);
        iIndex = SendMessage(hWndCtl, LB_GETCURSEL, 0, 0);
        return(TRUE);

    case WM_COMMAND:
        if ( HIWORD(wParam) == LBN_SELCHANGE)
        {
            iIndex = SendMessage(hWndCtl, LB_GETCURSEL, 0, 0);

return(TRUE);
        }
break;
    }
    return (FALSE);
}

/*
 * DlgProcGameSetup
 *
 * Dialog procedure for the Game Setup Dialog.
 */
BOOL CALLBACK DlgProcGameSetup (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
case WM_NOTIFY:
switch (((NMHDR FAR *) lParam)->code)
{
case PSN_SETACTIVE:
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_NEXT);
return(TRUE);

case PSN_WIZNEXT:
if (gbIsHost)
SetWindowLong(hDlg, DWL_MSGRESULT, IDD_HOSTSESSION);
return(TRUE);

case PSN_QUERYCANCEL:
ReleaseSPData();
DPlayRelease();
return(TRUE);
}
break;

    case WM_INITDIALOG:
// setup title fonts
SendDlgItemMessage(hDlg, IDC_GAMESETUP_TITLE, WM_SETFONT, (WPARAM)ghTitleFont, MAKELPARAM(TRUE,0));
SendDlgItemMessage(hDlg, IDC_JOINSESSION, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));
SendDlgItemMessage(hDlg, IDC_HOSTSESSION, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));
// host by default
SendDlgItemMessage(hDlg, IDC_HOSTSESSION, BM_SETCHECK, 1, 0);
SendDlgItemMessage(hDlg, IDC_JOINSESSION, BM_SETCHECK, 0, 0);
gbIsHost = TRUE;
        return(TRUE);

    case WM_COMMAND:
        if (HIWORD(wParam) == BN_CLICKED)
switch (LOWORD(wParam))
{
case IDC_HOSTSESSION:
if (SendDlgItemMessage(hDlg, IDC_HOSTSESSION, BM_GETCHECK, 1, 0) == 1)
{
gbIsHost = TRUE;
}
return(TRUE);
case IDC_JOINSESSION:
if (SendDlgItemMessage(hDlg, IDC_JOINSESSION, BM_GETCHECK, 1, 0) == 1)
{
gbIsHost = FALSE;
}
return(TRUE);
}
break;
}
    return(FALSE);
}

/*
 * EnumPlayer
 *
 * EnumeratePlayer callback. Inserts player information into the passed in tree view control.
 */
BOOL WINAPI EnumPlayer(DPID pidID, DWORD dwPlayerType, LPCDPNAME lpName,
DWORD dwFlags, LPVOID lpContext)
{
    HWND hWnd = (HWND) lpContext;
HTREEITEM hItem;

#ifdef UNICODE
hItem = AddItemToTree(hWnd, lpName->lpszShortName, 0, -1);
#else
hItem = AddItemToTree(hWnd, lpName->lpszShortNameA, 0, -1);
#endif

    return(TRUE);
}

/*
 * EnumSession
 *
 * EnumSessions callback. Inserts session description information in the passed in
 * tree view control.
 */
BOOL WINAPI EnumSession(LPCDPSESSIONDESC2 lpDPSessionDesc, LPDWORD lpdwTimeOut, DWORD dwFlags,
LPVOID lpContext)
{
    HWND hWnd = (HWND) lpContext;
HTREEITEM hItem;
LPTREEDATA lpdata;

    if(dwFlags & DPESC_TIMEDOUT) return FALSE;       // don't try again

if (hWnd == NULL) return FALSE;

// allocate memory to remember the guid
lpdata = (LPTREEDATA) malloc(sizeof(TREEDATA));
if (!lpdata) return FALSE;

lpdata->guid = lpDPSessionDesc->guidInstance;
lpdata->dwRefresh = 1;

#ifdef UNICODE
hItem = AddItemToTree(hWnd, lpDPSessionDesc->lpszSessionName, (DWORD)lpdata, 1);
#else
hItem = AddItemToTree(hWnd, lpDPSessionDesc->lpszSessionNameA, (DWORD)lpdata, 1);
#endif

if (hItem)
{
// it was new
TreeView_SelectItem(hWnd, hItem);
// enumerate players for new session
DPlayEnumPlayers((LPGUID)&lpDPSessionDesc->guidInstance, EnumPlayer, (LPVOID) hWnd, DPENUMPLAYERS_SESSION);
}
else
{
// it was already in the tree
free(lpdata);
}

    return(TRUE);
}

void ResetTreeRefresh(HWND hwndTV)
{
TV_ITEM item;
    HTREEITEM hItem;
    LPTREEDATA psearchdata;

memset(&item,0,sizeof(item));

    // get the first app item (child of the running apps item)
    hItem = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_ROOT,(LPARAM) NULL);

    while (hItem)
    {
// gets its data
    item.mask = TVIF_PARAM;
    item.hItem = hItem;
SendMessage( hwndTV, TVM_GETITEM, 0, (LPARAM)&item );
// set the dwRefresh to 0
psearchdata = (LPTREEDATA)item.lParam;
if (psearchdata) psearchdata->dwRefresh = 0;
    SendMessage( hwndTV, TVM_SETITEM, 0, (LPARAM)&item );

// get the next one
    hItem = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_NEXT, (LPARAM)hItem );
    }

return ;
}  // ResetTreeRefresh

void RemoveStaleSessions(HWND hwndTV)
{
TV_ITEM item;
    HTREEITEM hItem,hItemNext;
    LPTREEDATA psearchdata;

memset(&item,0,sizeof(item));

    // get the first app item (child of the running apps item)
    hItem = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_ROOT,(LPARAM) NULL);

    while (hItem)
    {
// get the next one
    hItemNext = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_NEXT, (LPARAM)hItem );

// gets its data
    item.mask = TVIF_PARAM;
    item.hItem = hItem;
SendMessage( hwndTV, TVM_GETITEM, 0, (LPARAM)&item );

// if dwRefresh is still 0, it wasn't enum'ed
psearchdata = (LPTREEDATA)item.lParam;
if (psearchdata && (0 == psearchdata->dwRefresh))
{
free(psearchdata);
SendMessage( hwndTV, TVM_DELETEITEM, 0, (LPARAM)hItem );
}

hItem = hItemNext;
    }

return ;

}  // RemoveStaleSessions

/*
 * DlgProcJoinSession
 *
 * Dialog procedure for Join Session Dialog
 */
BOOL CALLBACK DlgProcJoinSession (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static  NM_TREEVIEW nmtv;
    static  HWND hWndCtl;
static  TCHAR tszPlayerName[MAX_PLAYERNAME+1];
static  HANDLEdphEvent = NULL;
TV_ITEM tvItem;
TCHARtszSessionName[MAX_SESSIONNAME+1];
DWORD   dwPNameSize;
    HRESULT hr;
HTREEITEM htiCur, htiNext;
LPGUID  lpGuid;

    switch (msg)
    {
case WM_NOTIFY:
switch (((NMHDR FAR *) lParam)->code)
{
case PSN_SETACTIVE:
if (hWndCtl) TreeView_DeleteAllItems(hWndCtl);

// enum sessions and let dplay decide the timeout
hr = DPlayEnumSessions(0, EnumSession, (LPVOID) hWndCtl, DPENUMSESSIONS_ASYNC);

if (FAILED(hr))
{
return (FALSE);
}

// enumerate players for all sessions
ghtiSession = TreeView_GetFirstVisible(hWndCtl);

while (ghtiSession)
{
// delete previous players from display
if ((htiNext = htiCur = TreeView_GetChild(hWndCtl, ghtiSession))
!= (HTREEITEM)0)
{
do
{
htiNext = TreeView_GetNextSibling(hWndCtl, htiCur);
TreeView_DeleteItem(hWndCtl, htiCur);
htiCur = htiNext;
} while (htiNext);
}

tvItem.hItem = ghtiSession;
tvItem.pszText = tszSessionName;
tvItem.cchTextMax = MAX_SESSIONNAME;
TreeView_GetItem(hWndCtl, &tvItem);
// enumerate players for selected session
DPlayEnumPlayers((LPGUID)tvItem.lParam, EnumPlayer, (LPVOID) hWndCtl, DPENUMPLAYERS_SESSION);
ghtiSession = TreeView_GetNextItem(hWndCtl, ghtiSession, TVGN_NEXTVISIBLE);
}

// set Finish button highlite
if (GetDlgItemText(hDlg, IDC_JOIN_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME) &&
(htiCur = TreeView_GetSelection(hWndCtl)))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg),PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}

SetFocus(hWndCtl);

// start up timer
                SetTimer(hDlg, ENUM_TIMER_ID, ENUM_TIMEOUT, NULL);

return(TRUE);

case PSN_WIZFINISH:
                KillTimer(hDlg, ENUM_TIMER_ID);

// add user selections to registry
if (ghDuelKey)
{
RegSet(TEXT("PlayerName"), (CONST BYTE *)tszPlayerName,
sizeof(tszPlayerName));
}

// get the session guid
if (nmtv.itemNew.lParam)
{
// user selected a session item, so just grab its lParam
lpGuid = (LPGUID)nmtv.itemNew.lParam;
}
else
{
// user selected a player item, so grab its parent's (session) lParam
htiCur = TreeView_GetParent(hWndCtl, nmtv.itemNew.hItem);
if (!htiCur)
{
// fail finish
ShowError(IDS_WIZARD_ERROR_GSG);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return (TRUE);
}

tvItem.hItem = htiCur;
tvItem.pszText = tszSessionName;
tvItem.cchTextMax = MAX_SESSIONNAME;
TreeView_GetItem(hWndCtl, &tvItem);
lpGuid = (LPGUID)tvItem.lParam;
}

// open session
if ((hr = DPlayOpenSession(lpGuid)) != DP_OK)
{
// fail finish
ShowError(IDS_DPLAY_ERROR_JS);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return (TRUE);
}

// create player
if ((hr = DPlayCreatePlayer(&gOurID, tszPlayerName, NULL, NULL, 0)) != DP_OK)
{
// fail finish
ShowError(IDS_DPLAY_ERROR_CP);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return (TRUE);
}

// everything went well, release allocated memory and finish
ReleaseSessionData(hWndCtl);
ReleaseSPData();
return(TRUE);

case PSN_QUERYCANCEL:
                KillTimer(hDlg, ENUM_TIMER_ID);
ReleaseSPData();
ReleaseSessionData(hWndCtl);
DPlayRelease();
return(TRUE);

case PSN_WIZBACK:
                KillTimer(hDlg, ENUM_TIMER_ID);
ReleaseSessionData(hWndCtl);
return(TRUE);

case TVN_SELCHANGING:
nmtv = *((NM_TREEVIEW *) lParam);

// set Finish button status
if (GetDlgItemText(hDlg, IDC_JOIN_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME) &&
(htiCur = TreeView_GetSelection(hWndCtl)))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg),PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}
return(FALSE);

case NM_CLICK:
return(FALSE);
}
break;
    case WM_INITDIALOG:
// setup title fonts
SendDlgItemMessage(hDlg, IDC_JOIN_SESSION_TITLE, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));
SendDlgItemMessage(hDlg, IDC_JOIN_PLAYER_TITLE, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));

// setup user's previous data
dwPNameSize = MAX_PLAYERNAME+1;
tszPlayerName[0]=0;
if (ghDuelKey && (RegGet(TEXT("PlayerName"),(LPBYTE)tszPlayerName,&dwPNameSize) == ERROR_SUCCESS))
SetDlgItemText(hDlg, IDC_JOIN_PLAYERNAME, tszPlayerName);

        hWndCtl = GetDlgItem(hDlg, IDC_JOIN_SESSION);
        if (hWndCtl == NULL) return(TRUE);
InitTreeViewImageLists(hWndCtl);
        return(TRUE);

case WM_TIMER:

ResetTreeRefresh(hWndCtl);
// enum sessions and let dplay decide the timeout
DPlayEnumSessions(0, EnumSession, (LPVOID) hWndCtl, DPENUMSESSIONS_ASYNC);

RemoveStaleSessions(hWndCtl);

// set Finish button highlite
if (GetDlgItemText(hDlg, IDC_JOIN_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME) &&
(htiCur = TreeView_GetSelection(hWndCtl)))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg),PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}

break;

    case WM_COMMAND:

switch (LOWORD(wParam))
{
case IDC_JOIN_PLAYERNAME:
if (HIWORD(wParam) == EN_CHANGE)
{
// set Finish button status
if (GetDlgItemText(hDlg, IDC_JOIN_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME) &&
(htiCur = TreeView_GetSelection(hWndCtl)))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}
}
break;
}
break;
    }
    return (FALSE);
}

/*
 * DlgProcHostSession
 *
 * Dialog proc for Host Session Dialog
 */
BOOL CALLBACK DlgProcHostSession(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
staticTCHAR tszSessionName[MAX_SESSIONNAME+1], tszPlayerName[MAX_PLAYERNAME+1];
HRESULT hr;
DWORDdwPNameSize, dwSNameSize;

switch (msg) {
case WM_NOTIFY:
switch (((NMHDR FAR *) lParam)->code)
{
case PSN_SETACTIVE:
if (GetDlgItemText(hDlg, IDC_HOST_SESSIONNAME, tszSessionName, MAX_SESSIONNAME) &&
GetDlgItemText(hDlg, IDC_HOST_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}
return(TRUE);

case PSN_WIZFINISH:
// add user selections to registry
if (ghDuelKey)
{
RegSet(TEXT("PlayerName"), (CONST BYTE *)tszPlayerName, sizeof(tszPlayerName));
RegSet(TEXT("SessionName"), (CONST BYTE *)tszSessionName, sizeof(tszSessionName));
}

// create session
if ((hr = DPlayCreateSession(tszSessionName)) != DP_OK)
{
// fail finish
ShowError(IDS_DPLAY_ERROR_CS);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return (TRUE);
}

// create player
if ((hr = DPlayCreatePlayer(&gOurID, tszPlayerName, NULL, NULL, 0)) != DP_OK)
{
ShowError(IDS_DPLAY_ERROR_CP);
SetWindowLong(hDlg, DWL_MSGRESULT, -1);
return (TRUE);
}

// everything went well, release allocated memory and finish
ReleaseSPData();
return(TRUE);

case PSN_WIZBACK:
SetWindowLong(hDlg, DWL_MSGRESULT, IDD_GAMESETUP);
return(TRUE);

case PSN_QUERYCANCEL:
// release allocated memory
ReleaseSPData();
// release dplay
DPlayRelease();
return(TRUE);
}
break;
case WM_INITDIALOG:
// setup title font
SendDlgItemMessage(hDlg, IDC_HOST_TITLE, WM_SETFONT, (WPARAM)ghTitleFont, MAKELPARAM(TRUE,0));
SendDlgItemMessage(hDlg, IDC_HOST_SESSION_TITLE, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));
SendDlgItemMessage(hDlg, IDC_HOST_PLAYER_TITLE, WM_SETFONT, (WPARAM)ghHeadingFont, MAKELPARAM(TRUE,0));

dwPNameSize = MAX_PLAYERNAME+1;
dwSNameSize = MAX_SESSIONNAME+1;
tszPlayerName[0]=0;
tszSessionName[0]=0;
if (ghDuelKey)
{
if (RegGet(TEXT("PlayerName"), (LPBYTE)tszPlayerName, &dwPNameSize) == ERROR_SUCCESS)
SetDlgItemText(hDlg, IDC_HOST_PLAYERNAME, tszPlayerName);
if (RegGet(TEXT("SessionName"), (LPBYTE)tszSessionName, &dwSNameSize) == ERROR_SUCCESS)
SetDlgItemText(hDlg, IDC_HOST_SESSIONNAME, tszSessionName);
}

return(TRUE);

    case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_HOST_SESSIONNAME:
if (HIWORD(wParam) == EN_CHANGE)
{
if (GetDlgItemText(hDlg, IDC_HOST_SESSIONNAME, tszSessionName, MAX_SESSIONNAME) &&
GetDlgItemText(hDlg, IDC_HOST_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}

return TRUE;
}
break;

case IDC_HOST_PLAYERNAME:
if (HIWORD(wParam) == EN_CHANGE)
{
if (GetDlgItemText(hDlg, IDC_HOST_SESSIONNAME, tszSessionName, MAX_SESSIONNAME) &&
GetDlgItemText(hDlg, IDC_HOST_PLAYERNAME, tszPlayerName, MAX_PLAYERNAME))
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_FINISH);
}
else
{
PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK|PSWIZB_DISABLEDFINISH);
}
}
break;
}
break;
    }

return(FALSE);
}

/*
 * InitTreeViewImageLists - creates an image list, adds three bitmaps to
 *     it, and associates the image list with a tree-view control.
 * Returns TRUE if successful or FALSE otherwise.
 * hwndTV - handle of the tree-view control
 *
 * Global variables and constants
 *     gnSession, and gnPlayer - integer variables for
 *         indexes of the images
 *     CX_BITMAP and CY_BITMAP - width and height of an icon
 *     NUM_BITMAPS - number of bitmaps to add to the image list
 */
BOOL InitTreeViewImageLists(HWND hwndTV)
{
    HIMAGELIST himl;  // handle of image list
    HBITMAP hbmp;     // handle of bitmap

    // Create the image list.
    if ((himl = ImageList_Create(CX_BITMAP, CY_BITMAP,
            FALSE, NUM_BITMAPS, 0)) == NULL)
        return FALSE;

    // Add the session and player bitmaps.
    hbmp = LoadBitmap(ghinst, MAKEINTRESOURCE(IDB_CLOSED_SESSION));
    gnSession = ImageList_Add(himl, hbmp, (HBITMAP) NULL);
    DeleteObject(hbmp);

    hbmp = LoadBitmap(ghinst, MAKEINTRESOURCE(IDB_PLAYER));
    gnPlayer = ImageList_Add(himl, hbmp, (HBITMAP) NULL);
    DeleteObject(hbmp);

    // Fail if not all of the images were added.
    if (ImageList_GetImageCount(himl) < 2)
        return FALSE;

    // Associate the image list with the tree-view control.
    TreeView_SetImageList(hwndTV, himl, TVSIL_NORMAL);

    return TRUE;
}

HTREEITEM FindItemInTree(HWND hwndTV,LPTREEDATA pdata)
{
TV_ITEM item;
    HTREEITEM hItem;
    LPTREEDATA psearchdata;

    memset(&item,0,sizeof(item));

    // get the first app item (child of the running apps item)
    hItem = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_ROOT,(LPARAM) NULL);

    while (hItem)
    {
// gets its data
    item.mask = TVIF_PARAM;
    item.hItem = hItem;
SendMessage( hwndTV, TVM_GETITEM, 0, (LPARAM)&item );
// is this the one?
psearchdata = (LPTREEDATA)item.lParam;
if (IsEqualGUID(&(psearchdata->guid),&(pdata->guid)))
{
// mark the item as seen
psearchdata->dwRefresh = 1;
    SendMessage( hwndTV, TVM_SETITEM, 0, (LPARAM)&item );
return hItem;
}
// try the next one
    hItem = (HTREEITEM)SendMessage( hwndTV, TVM_GETNEXTITEM, TVGN_NEXT, (LPARAM)hItem );
    }

    return NULL;

} // FindItemInTree

/*
 * AddItemToTree - adds items to a tree-view control.
 * Returns the handle of the newly added item.
 * hwndTV - handle of the tree-view control
 * lpszItem - text of the item to add
 * nLevel - level at which to add the item
 */
HTREEITEM AddItemToTree(HWND hwndTV, LPTSTR lptszItem, DWORD dwData, int nLevel)
{
    TV_ITEM tvi;
    TV_INSERTSTRUCT tvins;
    static HTREEITEM hPrev = (HTREEITEM) TVI_FIRST;
    static HTREEITEM hPrevRootItem = NULL;
    static HTREEITEM hPrevLev2Item = NULL;
HTREEITEM hItem;

if (1 == nLevel)
{
hItem = FindItemInTree(hwndTV,(LPTREEDATA)dwData);
if (hItem) return NULL;
}
memset(&tvi,0,sizeof(tvi));
memset(&tvins,0,sizeof(tvins));

    tvi.mask = TVIF_TEXT | TVIF_IMAGE
        | TVIF_SELECTEDIMAGE | TVIF_PARAM;

 // Set the state
if (nLevel == 1)
{
tvi.mask |= TVIF_STATE;
tvi.state = TVIS_SELECTED;
}

    // Set the text of the item.
    tvi.pszText = lptszItem;

   // Set the image
if (nLevel == 1)
{
    tvi.iImage = gnSession;
tvi.iSelectedImage = gnSession;
}
else
{
tvi.iImage = gnPlayer;
tvi.iSelectedImage = gnPlayer;
}

    // Save the heading level in the item's application-defined

// data area.
    tvi.lParam = (LPARAM) dwData;

    tvins.item = tvi;
    tvins.hInsertAfter = hPrev;

    // Set the parent item based on the specified level.
    if (nLevel == -1)
tvins.hParent = hPrevRootItem;
    else if (nLevel == 1)
        tvins.hParent = TVI_ROOT;
    else if (nLevel == 2)
        tvins.hParent = hPrevRootItem;
else
        tvins.hParent = hPrevLev2Item;

    // Add the item to the tree-view control.
    hPrev = (HTREEITEM) SendMessage(hwndTV, TVM_INSERTITEM, 0,
         (LPARAM) (LPTV_INSERTSTRUCT) &tvins);

    // Save the handle of the item.
    if (nLevel == 1)
        hPrevRootItem = hPrev;
    else if (nLevel == 2)
        hPrevLev2Item = hPrev;

    return hPrev;
}

/*
 * ReleaseSessionData
 *
 * Releases the memory allocated for session guids
 */
void ReleaseSessionData(HWND hWndCtl)
{
HTREEITEM htiSession;
TV_ITEM tvItem;
TCHAR tszSessionName[MAX_SESSIONNAME+1];

htiSession = TreeView_GetRoot(hWndCtl);
while (htiSession)
{
tvItem.hItem = htiSession;
tvItem.pszText = tszSessionName;
tvItem.cchTextMax = MAX_SESSIONNAME;
TreeView_GetItem(hWndCtl, &tvItem);
if (tvItem.lParam) free((LPVOID)tvItem.lParam);
htiSession = TreeView_GetNextSibling(hWndCtl, htiSession);
}
}

/*
 * ReleaseSPData
 *
 * Releases the memory allocated for service provider guids
 * depends on global variable ghWndSPControl
 */
void ReleaseSPData(void)
{
LPVOID lpCon = NULL;
int count,index;

if (ghWndSPCtl)
{
count = SendMessage(ghWndSPCtl, LB_GETCOUNT, 0, 0);
for (index = 0; index < count; index++)
{
lpCon = (LPVOID) SendMessage(ghWndSPCtl, LB_GETITEMDATA, index, 0);
if (lpCon) GlobalFreePtr(lpCon);
}
}
}


WIZARD.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:wizard.h
 *  Content:user input routines include file
 *
 *
 ***************************************************************************/
#define IDIRECTPLAY2_OR_GREATER
#include <commctrl.h>
#include <dplay.h>
#include "duel.h"

// put this structure in the tree control
// the guid is the session guid, the refresh is used
// to detect when sessions go away
typedef struct _TREEDATA
{
GUID guid;
DWORDdwRefresh;
} TREEDATA,*LPTREEDATA;

voidSetupFonts(HDC hDC);

voidCleanupFonts(void);

LONGRegSet(LPCTSTR lptszName, CONST BYTE * lpData, DWORD dwSize);

LONGRegSetA(LPCTSTR lptszName, CONST BYTE * lpData, DWORD dwSize);

LONGRegGet(LPCTSTR lptszName, LPBYTE lpData, LPDWORD lpdwDataSize);

DWORDWINAPI DoWizard(LPVOID pv);

BOOLCALLBACK DlgProcChooseProvider(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOL FAR PASCAL DPEnumConnectionsCallback(LPCGUID lpguidSP, LPVOID lpConnection, DWORD dwSize,
LPCDPNAME lpName, DWORD dwFlags, LPVOID lpContext);

BOOLWINAPIEnumSession(LPCDPSESSIONDESC2 lpDPSessionDesc, LPDWORD lpdwTimeOut,
   DWORD dwFlags,  LPVOID pContext);

BOOLCALLBACK DlgProcGameSetup (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOLWINAPI EnumPlayer(DPID pidID, DWORD dwPlayerType, LPCDPNAME lpName,
DWORD dwFlags, LPVOID lpContext);

BOOLCALLBACK DlgProcJoinSession (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOLCALLBACK DlgProcHostSession (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOLInitTreeViewImageLists(HWND hwndTV);

HTREEITEM AddItemToTree(HWND hwndTV, LPTSTR lpszItem, DWORD dwData, int nLevel);

voidReleaseSessionData(HWND hWndCtl);

voidReleaseSPData(void);


README.TXT

DirectX Capability Viewer

Displays capabilities for DirectDraw, Direct3D, DirectSound, DirectPlay,
and DirectInput.


DXPRINT.C

/*
**-----------------------------------------------------------------------------
** Name:    dxprint.c
** Purpose: print's DX Tree View info to selected printer
** Notes:
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Includes
**-----------------------------------------------------------------------------
*/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commctrl.h>
#include <commdlg.h>        // Common Print Dialog
#include <tchar.h>          // Unicode string functions and types

#include <ddraw.h>
#include <dsound.h>
#include <dplay.h>
#include <d3d.h>
#include "dxview.h"

/*
**-----------------------------------------------------------------------------
** Local Variables
**-----------------------------------------------------------------------------
*/

BOOL g_fAbortPrint      = FALSE;       // Did User Abort Print operation ?!?
HWND g_hAbortPrintDlg   = NULL;        // Print Abort Dialog handle
BOOL g_PrintToFile      = FALSE;       // Don't print to printer print to dxview.log
HANDLE g_FileHandle     = NULL;        // Handle to log file

/*
**-----------------------------------------------------------------------------
** Local Prototypes
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK PrintTreeStats(
    HINSTANCE   hInstance,
    HWND        hWindow,
    HWND        hTreeWnd,
    HTREEITEM   hRoot);

void DoMessage (DWORD dwTitle, DWORD dwMsg);

/*
**-----------------------------------------------------------------------------
** Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:    DXView_OnPrint
** Purpose: Print user defined stuff
**-----------------------------------------------------------------------------
*/

BOOL DXView_OnPrint (
    HWND hWindow,
    HWND hTreeWnd,
    BOOL fPrintAll)
{
    HINSTANCE hInstance;
    HTREEITEM hRoot;

    // Check Parameters
    if ((! hWindow) || (! hTreeWnd))
    {
        // Invalid parameters
        return FALSE;
    }

    // Get hInstance
    hInstance = (HINSTANCE)GetWindowLong (hWindow, GWL_HINSTANCE);
    if (! hInstance)
        return FALSE;

    if (fPrintAll)
    {
        hRoot = NULL;
    }
    else
    {
        hRoot = TreeView_GetSelection (hTreeWnd);
        if (! hRoot)
        {
            DoMessage (IDS_PRINT_WARNING, IDS_PRINT_NEEDSELECT);
        }
    }
    g_PrintToFile = FALSE;
    // Do actual printing
    return PrintTreeStats (hInstance, hWindow, hTreeWnd, hRoot);

} // End DXView_OnPrint

BOOL DXView_OnFile (
    HWND hWindow,
    HWND hTreeWnd,
    BOOL fPrintAll)
{
    HINSTANCE hInstance;
    HTREEITEM hRoot;

    // Check Parameters
    if ((! hWindow) || (! hTreeWnd))
    {
        // Invalid parameters
        return FALSE;
    }

    // Get hInstance
    hInstance = (HINSTANCE)GetWindowLong (hWindow, GWL_HINSTANCE);
    if (! hInstance)
        return FALSE;

    if (fPrintAll)
    {
        hRoot = NULL;
    }
    else
    {
        hRoot = TreeView_GetSelection (hTreeWnd);
        if (! hRoot)
        {
            DoMessage (IDS_PRINT_WARNING, IDS_PRINT_NEEDSELECT);
        }
    }
    g_PrintToFile = TRUE;
    // Do actual printing
    return PrintTreeStats (hInstance, hWindow, hTreeWnd, hRoot);

} // End DXView_OnPrint

/*
**-----------------------------------------------------------------------------
** Name:    PrintDialogProc
** Purpose: Dialog procedure for printing
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK PrintDialogProc (
    HWND    hDialog,
    UINT    uiMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
    switch (uiMsg)
    {
    case WM_INITDIALOG:
        {
        // Disable system menu on dialog
        HMENU hSysMenu = GetSystemMenu (hDialog, FALSE);
        EnableMenuItem (hSysMenu, SC_CLOSE, MF_GRAYED);
        }
        return TRUE;

    case WM_COMMAND:
        {
        // User is aborting print operation
        g_fAbortPrint = TRUE;
        EnableWindow (GetParent (hDialog), TRUE);
        DestroyWindow (hDialog);
        g_hAbortPrintDlg = NULL;
        }
        return TRUE;
    } // End switch

    return FALSE;
} // End PrintDialogProc

/*
**-----------------------------------------------------------------------------
** Name:    AbortProc
** Purpose: Abort procedure for printing
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK AbortProc (HDC hPrinterDC, int iCode)
{
    MSG msg;

    while (! g_fAbortPrint && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    {
        if ((! g_hAbortPrintDlg) || ! IsDialogMessage (g_hAbortPrintDlg, &msg))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }

    return !g_fAbortPrint;
} // End AbortProc

/*
**-----------------------------------------------------------------------------
** Name:    PrintStats
** Purpose: Print user defined stuff
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK PrintTreeStats(
    HINSTANCE   hInstance,
    HWND        hWindow,
    HWND        hTreeWnd,
    HTREEITEM   hRoot)
{
    static DOCINFO  di;
    static PRINTDLG pd;

    BOOL        fDone           = FALSE;
    BOOL        fFindNext       = FALSE;
    BOOL        fResult         = FALSE;
    BOOL        fStartDoc       = FALSE;
    BOOL        fDisableWindow  = FALSE;
    LPTSTR      pstrTitle       = NULL;
    LPTSTR      pstrBuff        = NULL;
    TEXTMETRIC  tm;
    DWORD       dwCurrCopy;
    HANDLE      hHeap;
    DWORD       cbSize;
    DWORD       cchLen;
    TV_ITEM     tvi;
    HTREEITEM   hStartTree, hTempTree;
    PRINTCBINFO pci;

    // Check Parameters
    if ((! hInstance) || (! hWindow) || (! hTreeWnd))
    {
        // Error, invalid parameters
        return FALSE;
    }

    // Get Starting point for tree
    if (hRoot)
        hStartTree = hRoot;
    else
        hStartTree = TreeView_GetRoot (hTreeWnd);
    if (! hStartTree)
    {
        // Error, invalid tree
        return FALSE;
    }

    // Initialize Print Dialog structure
    pd.lStructSize          = sizeof (PRINTDLG);
    pd.hwndOwner            = hWindow;
    pd.hDevMode             = NULL;
    pd.hDevNames            = NULL;
    pd.hDC                  = NULL;
    pd.Flags                = PD_ALLPAGES | PD_RETURNDC;
    pd.nFromPage            = 0;
    pd.nToPage              = 0;
    pd.nMinPage             = 0;
    pd.nMaxPage             = 0;
    pd.nCopies              = 1;
    pd.hInstance            = NULL;
    pd.lCustData            = 0L;
    pd.lpfnPrintHook        = NULL;
    pd.lpfnSetupHook        = NULL;
    pd.lpPrintTemplateName  = NULL;
    pd.lpSetupTemplateName  = NULL;
    pd.hPrintTemplate       = NULL;
    pd.hSetupTemplate       = NULL;

    if(g_PrintToFile)
    {
        pci.hdcPrint = NULL;
    }else
    {
        // Call Common Print Dialog to get printer DC
        if ((! PrintDlg (&pd)) || (! pd.hDC))
        {
            // Error, Print Dialog failed or user canceled
            return TRUE;
        }
        pci.hdcPrint = pd.hDC;
        // Get Text metrics for printing
        if (! GetTextMetrics (pci.hdcPrint, &tm))
        {
            // Error, TextMetrics failed
            goto lblCLEANUP;
        }
    }
    if(g_PrintToFile)
    {
        pci.dwLineHeight   = 1;
        pci.dwCharWidth    = 1;
        pci.dwCharsPerLine = 80;
        pci.dwLinesPerPage = 66;
    }else
    {
        pci.dwLineHeight   = tm.tmHeight + tm.tmExternalLeading;
        pci.dwCharWidth    = tm.tmAveCharWidth;
        pci.dwCharsPerLine = GetDeviceCaps (pci.hdcPrint, HORZRES) / pci.dwCharWidth;
        pci.dwLinesPerPage = GetDeviceCaps (pci.hdcPrint, VERTRES) / pci.dwLineHeight;
    }

    // Get Heap
    hHeap = GetProcessHeap ();
    if (! hHeap)
    {
        // Error, no heap associated with this process
        goto lblCLEANUP;
    }

    // Create line buffer
    cbSize = (pci.dwCharsPerLine + 1) * sizeof(TCHAR);
    pstrBuff = (LPTSTR) HeapAlloc (hHeap, HEAP_NO_SERIALIZE, cbSize);
    if (! pstrBuff)
    {
        // Error, not enough memory
        goto lblCLEANUP;
    }

    // Disable Parent window
    EnableWindow (hWindow, FALSE);
    fDisableWindow = TRUE;

    // Start Printer Abort Dialog
    g_fAbortPrint = FALSE;
    g_hAbortPrintDlg = CreateDialog (hInstance, MAKEINTRESOURCE (IDD_ABORTPRINTDLG),
                                     hWindow, (DLGPROC)PrintDialogProc);
    if (! g_hAbortPrintDlg)
    {
        // Error, unable to create abort dialog
        goto lblCLEANUP;
    }

    //
    // Set Document title to Root string
    //
    tvi.mask            = TVIF_CHILDREN | TVIF_TEXT;
    tvi.hItem           = hStartTree;
    tvi.pszText         = pstrBuff;
    tvi.cchTextMax      = pci.dwCharsPerLine;
    if (TreeView_GetItem (hTreeWnd, &tvi))
    {
        SetWindowText (g_hAbortPrintDlg, pstrBuff);
        SetAbortProc (pd.hDC, AbortProc);
        cchLen = _tcsclen (pstrBuff);
        cbSize = (cchLen + 1) * sizeof(TCHAR);
        pstrTitle = (LPTSTR) HeapAlloc (hHeap, HEAP_NO_SERIALIZE, cbSize);
        if (! pstrTitle)
        {
            // Error, not enough memory
            goto lblCLEANUP;
        }

        _tcsncpy (pstrTitle, pstrBuff, cchLen);
        pstrTitle[cchLen] = 0;
    }
    else
    {
        SetWindowText (g_hAbortPrintDlg, TEXT("Unknown"));
        SetAbortProc (pd.hDC, AbortProc);
        cchLen = _tcsclen (TEXT("Unknown"));
        cbSize = (cchLen + 1) * sizeof(TCHAR);
        pstrTitle = (LPTSTR) HeapAlloc (hHeap, HEAP_NO_SERIALIZE, cbSize);
        if (! pstrTitle)
        {
            // Error, not enough memory
            goto lblCLEANUP;
        }

        _tcsncpy (pstrTitle, TEXT("Unknown"), cchLen);
        pstrTitle[cchLen] = 0;
    }

    // Initialize Document Structure
    di.cbSize       = sizeof(DOCINFO);
    di.lpszDocName  = pstrTitle;
    di.lpszOutput   = NULL;
    di.lpszDatatype = NULL;
    di.fwType       = 0;

    // Start document
    if(g_PrintToFile)
    {
        g_FileHandle = CreateFile("dxview.log",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    } else
    if (StartDoc (pd.hDC, &di) < 0)
    {
        // Error, StartDoc failed
        goto lblCLEANUP;
    }
    fStartDoc = TRUE;

    // Print requested number of copies
    fResult = FALSE;
    for (dwCurrCopy = 0; dwCurrCopy < (DWORD)pd.nCopies; dwCurrCopy++)
    {

        pci.hCurrTree       = hStartTree;
        pci.fStartPage      = TRUE;
        pci.dwCurrIndent    = 0;

        // Note:  We are basically doing an pre-order traversal
        //        of the Tree.  Printing the current node
        //        before moving on to it's children or siblings

        fDone               = FALSE;
        while (! fDone)
        {
            // Check if we need to start a new page
            if (! PrintStartPage (&pci))
            {
                goto lblCLEANUP;
            }

            //
            // Get Current Item in Tree
            // and print it's text info and associated Node caps
            //

            tvi.mask            = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
            tvi.hItem           = pci.hCurrTree;
            tvi.pszText         = pstrBuff;
            tvi.lParam          = 0;
            tvi.cchTextMax      = pci.dwCharsPerLine;
            if (TreeView_GetItem (hTreeWnd, &tvi))
            {
                cchLen = _tcslen (pstrBuff);
                if (cchLen > 0)
                {
                    int xOffset = (int)(pci.dwCurrIndent * DEF_TAB_SIZE * pci.dwCharWidth);
                    int yOffset = (int)(pci.dwLineHeight * pci.dwCurrLine);

                    // Print this line
                    if (! PrintLine (xOffset, yOffset, pstrBuff, cchLen, &pci))
                    {
                        goto lblCLEANUP;
                    }

                    // Advance to next line in page
                    if (! PrintNextLine (&pci))
                    {
                        goto lblCLEANUP;
                    }

                    // Check if there is any additional node info
                    // that needs to be printed
                    if (tvi.lParam != 0)
                    {
                        NODEINFO * pni = (NODEINFO*)tvi.lParam;

                        if (pni && pni->printCallback)
                        {
                            // Force indent to offset node info from tree info
                            pci.dwCurrIndent += 2;

                            if (! pni->printCallback(pni->lParam1, pni->lParam2, &pci))
                            {
                                // Error, callback failed
                                goto lblCLEANUP;
                            }

                            // Recover indent
                            pci.dwCurrIndent -= 2;
                        }
                    }
                }
            } // End if TreeView_GetItem()

            //
            // Get Next Item in tree
            //

            // Get first child, if any
            if (tvi.cChildren)
            {
                // Get First child
                hTempTree = TreeView_GetChild (hTreeWnd, pci.hCurrTree);
                if (hTempTree)
                {
                    // Increase Indentation
                    pci.dwCurrIndent++;

                    pci.hCurrTree = hTempTree;
                    continue;
                }
            }

            // Exit, if we are the root
            if (pci.hCurrTree == hRoot)
            {
                // We have reached the root, so stop
                PrintEndPage (&pci);
                fDone = TRUE;
                continue;
            }

            // Get next sibling in the chain
            hTempTree = TreeView_GetNextSibling (hTreeWnd, pci.hCurrTree);
            if (hTempTree)
            {
                pci.hCurrTree = hTempTree;
                continue;
            }

            // Get next Ancestor yet to be processed
            // (uncle, granduncle, etc)
            fFindNext = FALSE;
            while (! fFindNext)
            {
                hTempTree = TreeView_GetParent (hTreeWnd, pci.hCurrTree);
                if ((! hTempTree) || (hTempTree == hRoot))
                {
                    // We have reached the root, so stop
                    PrintEndPage (&pci);
                    fDone       = TRUE;
                    fFindNext   = TRUE;
                }
                else
                {
                    // Move up to the parent
                    pci.hCurrTree = hTempTree;

                    // Decrease Indentation
                    pci.dwCurrIndent--;

                    // Since we have already processed the parent
                    // we want to get the uncle/aunt node
                    hTempTree = TreeView_GetNextSibling (hTreeWnd, pci.hCurrTree);
                    if (hTempTree)
                    {
                        // Found a non-processed node
                        pci.hCurrTree = hTempTree;
                        fFindNext = TRUE;
                    }
                }
            }
        } // End While (! fDone)
    } // End for num copies

    // Success
    fResult = TRUE;

lblCLEANUP:
    // End Document
    if (fStartDoc)
    {
        if(g_PrintToFile)
        {
            CloseHandle(g_FileHandle);
        } else
        EndDoc (pd.hDC);
        fStartDoc = FALSE;
    }

    // Re-enable main window
    // Note:  Do this before destroying abort dialog
    //        otherwise the main window loses focus
    if (fDisableWindow)
    {
        EnableWindow (hWindow, TRUE);
        fDisableWindow = FALSE;
    }

    // Destroy Abort Dialog
    if (g_hAbortPrintDlg)
    {
        DestroyWindow (g_hAbortPrintDlg);
        g_hAbortPrintDlg = NULL;
    }

    // Free title memory
    if (pstrTitle)
    {
        HeapFree (hHeap, 0, (LPVOID)pstrTitle);
        pstrTitle       = NULL;
        di.lpszDocName  = NULL;
    }

    // Free buffer memory
    if (pstrBuff)
    {
        HeapFree (hHeap, 0, (LPVOID)pstrBuff);
        pstrBuff = NULL;
    }

    // Cleanup printer DC
    if (pd.hDC)
    {
        DeleteDC (pd.hDC);
        pd.hDC = NULL;
    }

    // Return success/failure
    return fResult;
} // End PrintTreeStats

/*
**-----------------------------------------------------------------------------
** Name:    PrintStartPage
** Purpose: check if we need to start a new page
**-----------------------------------------------------------------------------
*/

BOOL PrintStartPage (PRINTCBINFO * pci)
{
    if(g_PrintToFile)
        return TRUE;
    if (! pci)
        return FALSE;

    // Check if we need to start a new page
    if (pci->fStartPage)
    {
        // Check for user abort
        if (g_fAbortPrint)
        {
            // Error, user canceled printing job
            return FALSE;
        }

        // Start new page
        if (StartPage (pci->hdcPrint) < 0)
        {
            // Error, StartPage failed
            return FALSE;
        }

        // Reset line count
        pci->fStartPage = FALSE;
        pci->dwCurrLine = 0;
    }

    // Success
    return TRUE;
} // End PrintStartPage

/*
**-----------------------------------------------------------------------------
** Name:    PrintEndPage
** Purpose: check if we need to start a new page
**-----------------------------------------------------------------------------
*/

BOOL PrintEndPage (PRINTCBINFO * pci)
{
    if(g_PrintToFile)
        return TRUE;
    if (! pci)
        return FALSE;

    // Check if we need to end this page
    if (! pci->fStartPage)
    {
        // End page
        if (EndPage (pci->hdcPrint) < 0)
        {
            // Error, end page failed
            return FALSE;
        }
        pci->fStartPage = TRUE;

        // Check for user abort
        if (g_fAbortPrint)
        {
            // Error, user canceled printing job
            return FALSE;
        }
    }

    // Success
    return TRUE;
} // End PrintEndPage

/*
**-----------------------------------------------------------------------------
** Name:    PrintLine
** Purpose: Prints text to page at specified location
**-----------------------------------------------------------------------------
*/
static DWORD iLastXPos;

BOOL PrintLine (
    int xOffset,
    int yOffset,
    LPCTSTR pszBuff,
    DWORD  cchBuff,
    PRINTCBINFO * pci)
{
    if (! pci)
    {
        // Programming error
        return FALSE;
    }

    // Check if we need to start a new page
    if (! PrintStartPage (pci))
        return FALSE;

    if ((! pszBuff) || (! cchBuff))
    {
        // Nothing to print
        // assume it's OK and just keep printing
        return TRUE;
    }

    // Print text out to buffer current line
    if(g_PrintToFile)
    {
        DWORD Foobar;
        TCHAR Temp[80];

        if(((xOffset-iLastXPos)/pci->dwCharWidth)>80)
            return TRUE;
        memset(Temp,' ',sizeof(TCHAR)*79);
        Temp[(xOffset-iLastXPos)/pci->dwCharWidth]=0;
        WriteFile(g_FileHandle,Temp,(xOffset-iLastXPos)/pci->dwCharWidth,&Foobar,NULL);
        iLastXPos = (xOffset-iLastXPos)+(pci->dwCharWidth * cchBuff);

        WriteFile(g_FileHandle,pszBuff,cchBuff,&Foobar,NULL);
    }else
    {
        TextOut (pci->hdcPrint, xOffset, yOffset, pszBuff, cchBuff);
    };

    return TRUE;
} // End PrintLine

/*
**-----------------------------------------------------------------------------
** Name:    PrintNextLine
** Purpose: advance to next line on page
**-----------------------------------------------------------------------------
*/

BOOL PrintNextLine (PRINTCBINFO * pci)
{

    if(g_PrintToFile)
    {
        DWORD Foobar;

        WriteFile(g_FileHandle,"\r\n",2,&Foobar,NULL);
        iLastXPos = 0;
        return TRUE;
    };
    if (! pci)
        return FALSE;

    pci->dwCurrLine++;

    // Check if we need to end the page
    if (pci->dwCurrLine >= pci->dwLinesPerPage)
    {
        return PrintEndPage (pci);
    }

    // Success
    return TRUE;
} // End PrintNextLine

/*
**-----------------------------------------------------------------------------
** Name:    DoMessage
** Purpose: display warning message to user
**-----------------------------------------------------------------------------
*/

#define MAX_TITLE   64
#define MAX_MESSAGE 256

void DoMessage (DWORD dwTitle, DWORD dwMsg)
{
    TCHAR szTitle[MAX_TITLE];
    TCHAR szMsg[MAX_MESSAGE];
    HMODULE hModule;

    hModule = GetModuleHandle (NULL);

    LoadString (hModule, dwTitle, szTitle, MAX_TITLE);
    LoadString (hModule, dwMsg, szMsg, MAX_MESSAGE);
    MessageBox (NULL, szMsg, szTitle, MB_OK);
} // End DoMessage

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


DXVIEW.C

/****************************************************************************

    Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.

    PROGRAM: dxview.c

    PURPOSE: DirectX Device Viewer

    FUNCTIONS:

    COMMENTS:

****************************************************************************/

#include "dxview.h"

#pragma comment(lib, "dxguid")
#pragma comment(lib, "ddraw")
#pragma comment(lib, "dsound")
#pragma comment(lib, "dplayx")
#pragma comment(lib, "dinput")
#pragma comment(lib, "comctl32")

/****************************************************************************
 ***************************************************************************/

HINSTANCE   g_hInstance;
char        g_szAppName[]   = "DXView";
char        g_szClassName[] = "DXView";
char        g_szTitle[]     = "DirectX Device Viewer";
HWND        g_hwndMain;

HWND        g_hwndLV;        // List view
HWND        g_hwndTV;        // Tree view
HIMAGELIST  g_hImageList;
HFONT       g_hFont;
int         g_xPaneSplit;
int         g_xHalfSplitWidth;
BOOL        g_bSplitMove;
DWORD       g_dwViewState;
DWORD       g_tmAveCharWidth;
extern BOOL g_PrintToFile;
UINTg_idTimer;
LPLOCALAPPg_lpLocalApp;

HINSTANCE g_hDSoundLib = NULL;

typedef HRESULT (WINAPI * PFN_DSCREATE)(LPGUID lpguid, LPDIRECTSOUND * ppDS, IUnknown FAR * pUnkOuter);
typedef BOOL (WINAPI * PFN_DSENUMERATE)(LPDSENUMCALLBACK lpDSEnumCallback, LPVOID lpContext);
typedef HRESULT (WINAPI * PFN_DSCCREATE)(LPGUID lpGUID,LPDIRECTSOUNDCAPTURE *lplpDSC,LPUNKNOWN pUnkOuter);
typedef BOOL (WINAPI * PFN_DSCENUMERATE)(LPDSENUMCALLBACK lpDSEnumCallback,LPVOID lpContext);

/****************************************************************************
 ***************************************************************************/

IDirectDraw *lpDD;                  // DirectDraw object
IDirectDraw2 *lpDD2;                // DirectDraw object
IDirect3D2  *lp3D;                  // Direct3D object
GUID *       ddid;

IDirectSound *lpDS;                 // DirectSound object
GUID *       dsid;

IDirectSoundCapture *lpDSC;         // DirectSoundCapture object
GUID *       dscid;

IDirectPlay3A  *lpDP3A;             // DirectPlay object
GUID *       dpid;

/****************************************************************************
 ***************************************************************************/

#define DDCAPDEF(name,val,flag) {name, FIELD_OFFSET(DDCAPS,val), flag}
#define DDVALDEF(name,val)      {name, FIELD_OFFSET(DDCAPS,val), 0}
#define DDHEXDEF(name,val)      {name, FIELD_OFFSET(DDCAPS,val), 0xFFFFFFFF}
#define ROPDEF(name,dwRops,rop) DDCAPDEF(name,dwRops[((rop>>16)&0xFF)/32],(1<<((rop>>16)&0xFF)%32))

#define DSCAPDEF(name,val,flag) {name, FIELD_OFFSET(DSCAPS,val), flag}
#define DSVALDEF(name,val)      {name, FIELD_OFFSET(DSCAPS,val), 0}

#define DSCCAPDEF(name,val,flag) {name, FIELD_OFFSET(DSCCAPS,val), flag}
#define DSCVALDEF(name,val)      {name, FIELD_OFFSET(DSCCAPS,val), 0}

#define DPCAPDEF(name,val,flag) {name, FIELD_OFFSET(DPCAPS,val), flag}
#define DPHEXDEF(name,val)      {name, FIELD_OFFSET(DPCAPS,val), 0xFFFFFFFF}
#define DPVALDEF(name,val)      {name, FIELD_OFFSET(DPCAPS,val), 0}

#define D3CAPDEF(name,val,flag) {name, FIELD_OFFSET(D3DDEVICEDESC,val), flag}
#define D3HEXDEF(name,val)      {name, FIELD_OFFSET(D3DDEVICEDESC,val), 0xFFFFFFFF}
#define D3VALDEF(name,val)      {name, FIELD_OFFSET(D3DDEVICEDESC,val), 0}

#define SURFCAPDEF(name,val,flag) {name, FIELD_OFFSET(DDSURFACEDESC,val), flag}
#define SURFVALDEF(name,val)      {name, FIELD_OFFSET(DDSURFACEDESC,val), 0}

#define MAKEMODE(xres,yres,bpp) (((DWORD)xres << 20) | ((DWORD)yres << 8) | bpp)
#define GETXRES(mode)           (int)((mode >> 20) & 0x0FFF)
#define GETYRES(mode)           (int)((mode >> 8)  & 0x0FFF)
#define GETCRES(mode)           (int)((mode >> 0)  & 0x00FF)

IDirectDraw * DDCreate(GUID *pid);

BOOL PrintValueLine(char * szText, DWORD dwValue, PRINTCBINFO *lpInfo)
{
    char    szBuff[80];
    DWORD cchLen;
    int xName, xVal, yLine;

    // Calculate Name and Value column x offsets
    xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
    xVal    = xName + (32 * lpInfo->dwCharWidth);
    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

    // Print name
    wsprintf (szBuff, szText);
    cchLen = _tcslen (szText);
    if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;

    // Print value
    wsprintf (szBuff, "%d", dwValue);
    cchLen = _tcslen (szBuff);
    if (! PrintLine (xVal, yLine, szBuff, cchLen, lpInfo))
        return FALSE;

    // Advance to next line on page
    if (! PrintNextLine (lpInfo))
        return FALSE;

   return TRUE;
}

VOID VidMem(long lParam1, long foo2)
{
    HRESULT ddrval;
    DWORD dwTotalMem;
    DWORD dwFreeMem;
    DDSCAPS DDSCaps;

    LVAddColumn(g_hwndLV, 0, "Type", 24);
    LVAddColumn(g_hwndLV, 1, "Total", 10);
    LVAddColumn(g_hwndLV, 2, "Free", 10);

    if (DDCreate((GUID*)lParam1))
    {
        IDirectDraw_QueryInterface(lpDD,&IID_IDirectDraw2,&lpDD2);

        DDSCaps.dwCaps = DDSCAPS_VIDEOMEMORY;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        LVAddText(g_hwndLV, 0, "Video");
        LVAddText(g_hwndLV, 1, "%d", dwTotalMem);
        LVAddText(g_hwndLV, 2, "%d", dwFreeMem);

        DDSCaps.dwCaps = DDSCAPS_LOCALVIDMEM;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        LVAddText(g_hwndLV, 0, "Video (local)");
        LVAddText(g_hwndLV, 1, "%d", dwTotalMem);
        LVAddText(g_hwndLV, 2, "%d", dwFreeMem);

        DDSCaps.dwCaps = DDSCAPS_NONLOCALVIDMEM;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        LVAddText(g_hwndLV, 0, "Video (non-local)");
        LVAddText(g_hwndLV, 1, "%d", dwTotalMem);
        LVAddText(g_hwndLV, 2, "%d", dwFreeMem);

        DDSCaps.dwCaps = DDSCAPS_TEXTURE;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        LVAddText(g_hwndLV, 0, "Texture");
        LVAddText(g_hwndLV, 1, "%d", dwTotalMem);
        LVAddText(g_hwndLV, 2, "%d", dwFreeMem);
    }
}

BOOL VidMemPrint(long lParam1, long lParam2, PRINTCBINFO *lpInfo)
{
    HRESULT ddrval;
    DWORD dwTotalMem;
    DWORD dwFreeMem;
    DDSCAPS DDSCaps;

    if (DDCreate((GUID*)lParam1))
    {
        IDirectDraw_QueryInterface(lpDD,&IID_IDirectDraw2,&lpDD2);

        DDSCaps.dwCaps = DDSCAPS_VIDEOMEMORY;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        PrintValueLine("TotalMem",dwTotalMem,lpInfo);
        PrintValueLine("FreeMem",dwFreeMem,lpInfo);

        DDSCaps.dwCaps = DDSCAPS_LOCALVIDMEM;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        PrintValueLine("TotalVideoMem",dwTotalMem,lpInfo);
        PrintValueLine("FreeVideoMem",dwFreeMem,lpInfo);

        DDSCaps.dwCaps = DDSCAPS_NONLOCALVIDMEM;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        PrintValueLine("TotalAGPMem",dwTotalMem,lpInfo);
        PrintValueLine("FreeAGPMem",dwFreeMem,lpInfo);

        DDSCaps.dwCaps = DDSCAPS_TEXTURE;
        ddrval = IDirectDraw2_GetAvailableVidMem(lpDD2, &DDSCaps, &dwTotalMem, &dwFreeMem );
        if(ddrval != DD_OK)
        {
            dwTotalMem = 0;
            dwFreeMem = 0;
        };
        PrintValueLine("TotalTextureMem",dwTotalMem,lpInfo);
        PrintValueLine("FreeTextureMem",dwFreeMem,lpInfo);
    }
    return TRUE;
};

/****************************************************************************
 ***************************************************************************/
CAPDEF OtherInfoDefs[] =
{
    DDVALDEF("VidMemTotal",                   dwVidMemTotal),
    DDVALDEF("VidMemFree",                    dwVidMemFree),
    DDHEXDEF("AlphaBltConstBitDepths",        dwAlphaBltConstBitDepths),
    DDCAPDEF("  8",                           dwAlphaBltConstBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaBltConstBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaBltConstBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaBltConstBitDepths, DDBD_32),
    DDHEXDEF("AlphaBltPixelBitDepths",        dwAlphaBltPixelBitDepths),
    DDCAPDEF("  8",                           dwAlphaBltPixelBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaBltPixelBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaBltPixelBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaBltPixelBitDepths, DDBD_32),
    DDHEXDEF("AlphaBltSurfaceBitDepths",      dwAlphaBltSurfaceBitDepths),
    DDCAPDEF("  8",                           dwAlphaBltSurfaceBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaBltSurfaceBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaBltSurfaceBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaBltSurfaceBitDepths, DDBD_32),
    DDHEXDEF("AlphaOverlayConstBitDepths",    dwAlphaOverlayConstBitDepths),
    DDCAPDEF("  8",                           dwAlphaOverlayConstBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaOverlayConstBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaOverlayConstBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaOverlayConstBitDepths, DDBD_32),
    DDHEXDEF("AlphaOverlayPixelBitDepths",    dwAlphaOverlayPixelBitDepths),
    DDCAPDEF("  8",                           dwAlphaOverlayPixelBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaOverlayPixelBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaOverlayPixelBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaOverlayPixelBitDepths, DDBD_32),
    DDHEXDEF("AlphaOverlaySurfaceBitDepths",  dwAlphaOverlaySurfaceBitDepths),
    DDCAPDEF("  8",                           dwAlphaOverlaySurfaceBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwAlphaOverlaySurfaceBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwAlphaOverlaySurfaceBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwAlphaOverlaySurfaceBitDepths, DDBD_32),
    DDHEXDEF("ZBufferBitDepths",              dwZBufferBitDepths),
    DDCAPDEF("  8",                           dwZBufferBitDepths, DDBD_8),
    DDCAPDEF("  16",                          dwZBufferBitDepths, DDBD_16),
    DDCAPDEF("  24",                          dwZBufferBitDepths, DDBD_24),
    DDCAPDEF("  32",                          dwZBufferBitDepths, DDBD_32),
    DDVALDEF("MaxVisibleOverlays",            dwMaxVisibleOverlays),
    DDVALDEF("CurrVisibleOverlays",           dwCurrVisibleOverlays),
    DDVALDEF("NumFourCCCodes",                dwNumFourCCCodes),
    DDVALDEF("AlignBoundarySrc",              dwAlignBoundarySrc),
    DDVALDEF("AlignSizeSrc",                  dwAlignSizeSrc),
    DDVALDEF("AlignBoundaryDest",             dwAlignBoundaryDest),
    DDVALDEF("AlignSizeDest",                 dwAlignSizeDest),
    DDVALDEF("AlignStrideAlign",              dwAlignStrideAlign),
    DDVALDEF("MinOverlayStretch",             dwMinOverlayStretch),
    DDVALDEF("MaxOverlayStretch",             dwMaxOverlayStretch),
    DDVALDEF("MinLiveVideoStretch",           dwMinLiveVideoStretch),
    DDVALDEF("MaxLiveVideoStretch",           dwMaxLiveVideoStretch),
    DDVALDEF("MinHwCodecStretch",             dwMinHwCodecStretch),
    DDVALDEF("MaxHwCodecStretch",             dwMaxHwCodecStretch),
    DDVALDEF("dwReserved1",                   dwReserved1),
    DDVALDEF("dwReserved2",                   dwReserved2),
    DDVALDEF("dwReserved3",                   dwReserved3),

    //DDHEXDEF("dwCaps",                      dwCaps),
    DDVALDEF("dwMaxVideoPorts",               dwMaxVideoPorts),
    DDVALDEF("dwCurrVideoPorts",               dwCurrVideoPorts),
    //DDVALDEF("dwSVBCaps2",                   dwSVBCaps2),
    { "", 0, 0 }
};

/****************************************************************************
 ***************************************************************************/

#define GEN_CAPS(dwCaps)                                                     \
\
    DDCAPDEF("3D",                        dwCaps, DDCAPS_3D),                   \
    DDCAPDEF("ALIGNBOUNDARYDEST",         dwCaps, DDCAPS_ALIGNBOUNDARYDEST),    \
    DDCAPDEF("ALIGNSIZEDEST",             dwCaps, DDCAPS_ALIGNSIZEDEST),        \
    DDCAPDEF("ALIGNBOUNDARYSRC",          dwCaps, DDCAPS_ALIGNBOUNDARYSRC),     \
    DDCAPDEF("ALIGNSIZESRC",              dwCaps, DDCAPS_ALIGNSIZESRC),         \
    DDCAPDEF("ALIGNSTRIDE",               dwCaps, DDCAPS_ALIGNSTRIDE),          \
    DDCAPDEF("GDI",                       dwCaps, DDCAPS_GDI),                  \
    DDCAPDEF("OVERLAY",                   dwCaps, DDCAPS_OVERLAY),              \
    DDCAPDEF("OVERLAYCANTCLIP",           dwCaps, DDCAPS_OVERLAYCANTCLIP),      \
    DDCAPDEF("OVERLAYFOURCC",             dwCaps, DDCAPS_OVERLAYFOURCC),        \
    DDCAPDEF("OVERLAYSTRETCH",            dwCaps, DDCAPS_OVERLAYSTRETCH),       \
    DDCAPDEF("PALETTE",                   dwCaps, DDCAPS_PALETTE),              \
    DDCAPDEF("PALETTEVSYNC",              dwCaps, DDCAPS_PALETTEVSYNC),         \
    DDCAPDEF("READSCANLINE",              dwCaps, DDCAPS_READSCANLINE),         \
    DDCAPDEF("STEREOVIEW",                dwCaps, DDCAPS_STEREOVIEW),           \
    DDCAPDEF("VBI",                       dwCaps, DDCAPS_VBI),                  \
    DDCAPDEF("ZOVERLAYS",                 dwCaps, DDCAPS_ZOVERLAYS),            \
    DDCAPDEF("NOHARDWARE",                dwCaps, DDCAPS_NOHARDWARE),           \
    DDCAPDEF("BANKSWITCHED",              dwCaps, DDCAPS_BANKSWITCHED),

#define GEN_CAPS2(dwCaps2)                                                      \
    DDCAPDEF("CERTIFIED",                 dwCaps2,DDCAPS2_CERTIFIED),           \
    DDCAPDEF("NO2DDURING3DSCENE",         dwCaps2,DDCAPS2_NO2DDURING3DSCENE),   \
    DDCAPDEF("VIDEOPORT",                 dwCaps2,DDCAPS2_VIDEOPORT),           \
    DDCAPDEF("AUTOFLIPOVERLAY",           dwCaps2,DDCAPS2_AUTOFLIPOVERLAY),     \
    DDCAPDEF("CANBOBINTERLEAVED",         dwCaps2,DDCAPS2_CANBOBINTERLEAVED),   \
    DDCAPDEF("CANBOBNONINTERLEAVED",      dwCaps2,DDCAPS2_CANBOBNONINTERLEAVED),\
    DDCAPDEF("COLORCONTROLOVERLAY",       dwCaps2,DDCAPS2_COLORCONTROLOVERLAY), \
    DDCAPDEF("COLORCONTROLPRIMARY",       dwCaps2,DDCAPS2_COLORCONTROLPRIMARY), \
    DDCAPDEF("NONLOCALVIDMEM",            dwCaps2,DDCAPS2_NONLOCALVIDMEM),      \
    DDCAPDEF("NONLOCALVIDMEMCAPS",        dwCaps2,DDCAPS2_NONLOCALVIDMEMCAPS),  \
    DDCAPDEF("WIDESURFACES",              dwCaps2,DDCAPS2_WIDESURFACES),        \
    DDCAPDEF("NOPAGELOCKREQUIRED",        dwCaps2,DDCAPS2_NOPAGELOCKREQUIRED),

#define GEN_BLTCAPS(dwCaps)                                                     \
    DDCAPDEF("BLT",                       dwCaps, DDCAPS_BLT),                  \
    DDCAPDEF("BLTCOLORFILL",              dwCaps, DDCAPS_BLTCOLORFILL),         \
    DDCAPDEF("BLTDEPTHFILL",              dwCaps, DDCAPS_BLTDEPTHFILL),         \
    DDCAPDEF("BLTFOURCC",                 dwCaps, DDCAPS_BLTFOURCC),            \
    DDCAPDEF("BLTSTRETCH",                dwCaps, DDCAPS_BLTSTRETCH),           \
    DDCAPDEF("BLTQUEUE",                  dwCaps, DDCAPS_BLTQUEUE),             \
    DDCAPDEF("COLORKEY",                  dwCaps, DDCAPS_COLORKEY),             \
    DDCAPDEF("ALPHA",                     dwCaps, DDCAPS_ALPHA),                \
    DDCAPDEF("CKEYHWASSIST",              dwCaps, DDCAPS_COLORKEYHWASSIST),     \
    DDCAPDEF("CANCLIP",                   dwCaps, DDCAPS_CANCLIP),              \
    DDCAPDEF("CANCLIPSTRETCHED",          dwCaps, DDCAPS_CANCLIPSTRETCHED),     \
    DDCAPDEF("CANBLTSYSMEM",              dwCaps, DDCAPS_CANBLTSYSMEM),         \
    DDCAPDEF("ZBLTS",                     dwCaps, DDCAPS_ZBLTS),

#define GEN_BLTCAPS2(dwCaps2)                                                   \
    DDCAPDEF("CANDROPZ16BIT",             dwCaps2,DDCAPS2_CANDROPZ16BIT),       \
    DDCAPDEF("NOPAGELOCKREQUIRED",        dwCaps2,DDCAPS2_NOPAGELOCKREQUIRED),

/****************************************************************************
 ***************************************************************************/
CAPDEF GenCaps[] =
{
    GEN_CAPS(dwCaps)
    GEN_CAPS2(dwCaps2)
    { "", 0, 0 }
};

//
// NOTE GenCaps and CapsDefs are the same
// because some are blt caps that apply to VRAM->VRAM blts and
// some are "general" caps...
//
CAPDEF CapsDefs[] =
{
    GEN_BLTCAPS(dwCaps)
    GEN_BLTCAPS2(dwCaps2)
    { "", 0, 0 }
};
CAPDEF SVBCapsDefs[] =
{
    GEN_BLTCAPS(dwSVBCaps)
    GEN_BLTCAPS2(dwSVBCaps2)
    { "", 0, 0 }
};
CAPDEF VSBCapsDefs[] =
{
    GEN_BLTCAPS(dwVSBCaps)
//  GEN_BLTCAPS2(dwVSBCaps2)
    { "", 0, 0 }
};
CAPDEF SSBCapsDefs[] =
{
    GEN_BLTCAPS(dwSSBCaps)
//  GEN_BLTCAPS2(dwSSBCaps2)
    { "", 0, 0 }
};
CAPDEF NLVBCapsDefs[] =
{
    GEN_BLTCAPS(dwNLVBCaps)
    GEN_BLTCAPS2(dwNLVBCaps2)
    { "", 0, 0 }
};

/****************************************************************************
 ***************************************************************************/
#define GEN_CKCAPS(dwCKeyCaps)                                                              \
    DDCAPDEF("DESTBLT",                 dwCKeyCaps, DDCKEYCAPS_DESTBLT),                  \
    DDCAPDEF("DESTBLTCLRSPACE",         dwCKeyCaps, DDCKEYCAPS_DESTBLTCLRSPACE),          \
    DDCAPDEF("DESTBLTCLRSPACEYUV",      dwCKeyCaps, DDCKEYCAPS_DESTBLTCLRSPACEYUV),       \
    DDCAPDEF("DESTBLTYUV",              dwCKeyCaps, DDCKEYCAPS_DESTBLTYUV),               \
    DDCAPDEF("DESTOVERLAY",             dwCKeyCaps, DDCKEYCAPS_DESTOVERLAY),              \
    DDCAPDEF("DESTOVERLAYCLRSPACE",     dwCKeyCaps, DDCKEYCAPS_DESTOVERLAYCLRSPACE),      \
    DDCAPDEF("DESTOVERLAYCLRSPACEYUV",  dwCKeyCaps, DDCKEYCAPS_DESTOVERLAYCLRSPACEYUV),   \
    DDCAPDEF("DESTOVERLAYONEACTIVE",    dwCKeyCaps, DDCKEYCAPS_DESTOVERLAYONEACTIVE),     \
    DDCAPDEF("DESTOVERLAYYUV",          dwCKeyCaps, DDCKEYCAPS_DESTOVERLAYYUV),           \
    DDCAPDEF("SRCBLT",                  dwCKeyCaps, DDCKEYCAPS_SRCBLT),                   \
    DDCAPDEF("SRCBLTCLRSPACE",          dwCKeyCaps, DDCKEYCAPS_SRCBLTCLRSPACE),           \
    DDCAPDEF("SRCBLTCLRSPACEYUV",       dwCKeyCaps, DDCKEYCAPS_SRCBLTCLRSPACEYUV),        \
    DDCAPDEF("SRCBLTYUV",               dwCKeyCaps, DDCKEYCAPS_SRCBLTYUV),                \
    DDCAPDEF("SRCOVERLAY",              dwCKeyCaps, DDCKEYCAPS_SRCOVERLAY),               \
    DDCAPDEF("SRCOVERLAYCLRSPACE",      dwCKeyCaps, DDCKEYCAPS_SRCOVERLAYCLRSPACE),       \
    DDCAPDEF("SRCOVERLAYCLRSPACEYUV",   dwCKeyCaps, DDCKEYCAPS_SRCOVERLAYCLRSPACEYUV),    \
    DDCAPDEF("SRCOVERLAYONEACTIVE",     dwCKeyCaps, DDCKEYCAPS_SRCOVERLAYONEACTIVE),      \
    DDCAPDEF("SRCOVERLAYYUV",           dwCKeyCaps, DDCKEYCAPS_SRCOVERLAYYUV),

CAPDEF CKeyCapsDefs[] =
{
    GEN_CKCAPS(dwCKeyCaps)
    { "", 0, 0}
};
CAPDEF VSBCKeyCapsDefs[] =
{
    GEN_CKCAPS(dwVSBCKeyCaps)
    { "", 0, 0}
};
CAPDEF SVBCKeyCapsDefs[] =
{
    GEN_CKCAPS(dwSVBCKeyCaps)
    { "", 0, 0}
};
CAPDEF SSBCKeyCapsDefs[] =
{
    GEN_CKCAPS(dwSSBCKeyCaps)
    { "", 0, 0}
};
CAPDEF NLVBCKeyCapsDefs[] =
{
    GEN_CKCAPS(dwNLVBCKeyCaps)
    { "", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
#define GEN_FXCAPS(dwFXCaps)                                                            \
    DDCAPDEF("BLTARITHSTRETCHY",          dwFXCaps, DDFXCAPS_BLTARITHSTRETCHY),         \
    DDCAPDEF("BLTARITHSTRETCHYN",         dwFXCaps, DDFXCAPS_BLTARITHSTRETCHYN),        \
    DDCAPDEF("BLTMIRRORLEFTRIGHT",        dwFXCaps, DDFXCAPS_BLTMIRRORLEFTRIGHT),       \
    DDCAPDEF("BLTMIRRORUPDOWN",           dwFXCaps, DDFXCAPS_BLTMIRRORUPDOWN),          \
    DDCAPDEF("BLTROTATION",               dwFXCaps, DDFXCAPS_BLTROTATION),              \
    DDCAPDEF("BLTROTATION90",             dwFXCaps, DDFXCAPS_BLTROTATION90),            \
    DDCAPDEF("BLTSHRINKX",                dwFXCaps, DDFXCAPS_BLTSHRINKX),               \
    DDCAPDEF("BLTSHRINKXN",               dwFXCaps, DDFXCAPS_BLTSHRINKXN),              \
    DDCAPDEF("BLTSHRINKY",                dwFXCaps, DDFXCAPS_BLTSHRINKY),               \
    DDCAPDEF("BLTSHRINKYN",               dwFXCaps, DDFXCAPS_BLTSHRINKYN),              \
    DDCAPDEF("BLTSTRETCHX",               dwFXCaps, DDFXCAPS_BLTSTRETCHX),              \
    DDCAPDEF("BLTSTRETCHXN",              dwFXCaps, DDFXCAPS_BLTSTRETCHXN),             \
    DDCAPDEF("BLTSTRETCHY",               dwFXCaps, DDFXCAPS_BLTSTRETCHY),              \
    DDCAPDEF("BLTSTRETCHYN",              dwFXCaps, DDFXCAPS_BLTSTRETCHYN),             \
    DDCAPDEF("OVERLAYARITHSTRETCHY",      dwFXCaps, DDFXCAPS_OVERLAYARITHSTRETCHY),     \
    DDCAPDEF("OVERLAYARITHSTRETCHYN",     dwFXCaps, DDFXCAPS_OVERLAYARITHSTRETCHYN),    \
    DDCAPDEF("OVERLAYSHRINKX",            dwFXCaps, DDFXCAPS_OVERLAYSHRINKX),           \
    DDCAPDEF("OVERLAYSHRINKXN",           dwFXCaps, DDFXCAPS_OVERLAYSHRINKXN),          \
    DDCAPDEF("OVERLAYSHRINKY",            dwFXCaps, DDFXCAPS_OVERLAYSHRINKY),           \
    DDCAPDEF("OVERLAYSHRINKYN",           dwFXCaps, DDFXCAPS_OVERLAYSHRINKYN),          \
    DDCAPDEF("OVERLAYSTRETCHX",           dwFXCaps, DDFXCAPS_OVERLAYSTRETCHX),          \
    DDCAPDEF("OVERLAYSTRETCHXN",          dwFXCaps, DDFXCAPS_OVERLAYSTRETCHXN),         \
    DDCAPDEF("OVERLAYSTRETCHY",           dwFXCaps, DDFXCAPS_OVERLAYSTRETCHY),          \
    DDCAPDEF("OVERLAYSTRETCHYN",          dwFXCaps, DDFXCAPS_OVERLAYSTRETCHYN),         \
    DDCAPDEF("OVERLAYMIRRORLEFTRIGHT",    dwFXCaps, DDFXCAPS_OVERLAYMIRRORLEFTRIGHT),   \
    DDCAPDEF("OVERLAYMIRRORUPDOWN",       dwFXCaps, DDFXCAPS_OVERLAYMIRRORUPDOWN),

CAPDEF FXCapsDefs[] =
{
    GEN_FXCAPS(dwFXCaps)
    { "", 0, 0}
};
CAPDEF VSBFXCapsDefs[] =
{
    GEN_FXCAPS(dwVSBFXCaps)
    { "", 0, 0}
};
CAPDEF SVBFXCapsDefs[] =
{
    GEN_FXCAPS(dwSVBFXCaps)
    { "", 0, 0}
};
CAPDEF SSBFXCapsDefs[] =
{
    GEN_FXCAPS(dwSSBFXCaps)
    { "", 0, 0}
};
CAPDEF NLVBFXCapsDefs[] =
{
    GEN_FXCAPS(dwNLVBFXCaps)
    { "", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF PalCapsDefs[] =
{
    DDCAPDEF("4BIT",              dwPalCaps, DDPCAPS_4BIT),
    DDCAPDEF("8BITENTRIES",       dwPalCaps, DDPCAPS_8BITENTRIES),
    DDCAPDEF("8BIT",              dwPalCaps, DDPCAPS_8BIT),
    DDCAPDEF("INITIALIZE",        dwPalCaps, DDPCAPS_INITIALIZE),
    DDCAPDEF("PRIMARYSURFACE",    dwPalCaps, DDPCAPS_PRIMARYSURFACE),
    DDCAPDEF("PRIMARYSURFACELEFT",dwPalCaps, DDPCAPS_PRIMARYSURFACELEFT),
    DDCAPDEF("VSYNC",             dwPalCaps, DDPCAPS_VSYNC),
    { "", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF SurfCapsDefs[] =
{
    DDCAPDEF( "3DDEVICE",             ddsCaps.dwCaps, DDSCAPS_3DDEVICE),
    DDCAPDEF( "ALPHA",                ddsCaps.dwCaps, DDSCAPS_ALPHA),
    DDCAPDEF( "BACKBUFFER",           ddsCaps.dwCaps, DDSCAPS_BACKBUFFER),
    DDCAPDEF( "COMPLEX",              ddsCaps.dwCaps, DDSCAPS_COMPLEX),
    DDCAPDEF( "FLIP",                 ddsCaps.dwCaps, DDSCAPS_FLIP),
    DDCAPDEF( "FRONTBUFFER",          ddsCaps.dwCaps, DDSCAPS_FRONTBUFFER),
    DDCAPDEF( "MIPMAP",               ddsCaps.dwCaps, DDSCAPS_MIPMAP),
    DDCAPDEF( "OFFSCREENPLAIN",       ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN),
    DDCAPDEF( "OVERLAY",              ddsCaps.dwCaps, DDSCAPS_OVERLAY),
    DDCAPDEF( "PALETTE",              ddsCaps.dwCaps, DDSCAPS_PALETTE),
    DDCAPDEF( "PRIMARYSURFACE",       ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE),
    DDCAPDEF( "PRIMARYSURFACELEFT",   ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACELEFT),
    DDCAPDEF( "SYSTEMMEMORY",         ddsCaps.dwCaps, DDSCAPS_SYSTEMMEMORY),
    DDCAPDEF( "TEXTURE",              ddsCaps.dwCaps, DDSCAPS_TEXTURE),
    DDCAPDEF( "VIDEOMEMORY",          ddsCaps.dwCaps, DDSCAPS_VIDEOMEMORY),
    DDCAPDEF( "VISIBLE",              ddsCaps.dwCaps, DDSCAPS_VISIBLE),
    DDCAPDEF( "WRITEONLY",            ddsCaps.dwCaps, DDSCAPS_WRITEONLY),
    DDCAPDEF( "ZBUFFER",              ddsCaps.dwCaps, DDSCAPS_ZBUFFER),
    DDCAPDEF( "OWNDC",                ddsCaps.dwCaps, DDSCAPS_OWNDC),
    DDCAPDEF( "LIVEVIDEO",            ddsCaps.dwCaps, DDSCAPS_LIVEVIDEO),
    DDCAPDEF( "HWCODEC",              ddsCaps.dwCaps, DDSCAPS_HWCODEC),
    DDCAPDEF( "MODEX",                ddsCaps.dwCaps, DDSCAPS_MODEX),
    { "", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF SVisionCapsDefs[] =
{
    DDCAPDEF( "ENIGMA",  dwSVCaps, DDSVCAPS_ENIGMA),
    DDCAPDEF( "FLICKER", dwSVCaps, DDSVCAPS_FLICKER),
    DDCAPDEF( "REDBLUE", dwSVCaps, DDSVCAPS_REDBLUE),
    DDCAPDEF( "SPLIT",   dwSVCaps, DDSVCAPS_SPLIT),
    { "", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
#define GEN_ROPS(dwRops)\
    ROPDEF("SRCCOPY",    dwRops, SRCCOPY),      \
    ROPDEF("SRCPAINT",   dwRops, SRCPAINT),     \
    ROPDEF("SRCAND",     dwRops, SRCAND),       \
    ROPDEF("SRCINVERT",  dwRops, SRCINVERT),    \
    ROPDEF("SRCERASE",   dwRops, SRCERASE),     \
    ROPDEF("NOTSRCCOPY", dwRops, NOTSRCCOPY),   \
    ROPDEF("NOTSRCERASE",dwRops, NOTSRCERASE),  \
    ROPDEF("MERGECOPY",  dwRops, MERGECOPY),    \
    ROPDEF("MERGEPAINT", dwRops, MERGEPAINT),   \
    ROPDEF("PATCOPY",    dwRops, PATCOPY),      \
    ROPDEF("PATPAINT",   dwRops, PATPAINT),     \
    ROPDEF("PATINVERT",  dwRops, PATINVERT),    \
    ROPDEF("DSTINVERT",  dwRops, DSTINVERT),    \
    ROPDEF("BLACKNESS",  dwRops, BLACKNESS),    \
    ROPDEF("WHITENESS",  dwRops, WHITENESS),

CAPDEF ROPCapsDefs[] =
{
    GEN_ROPS(dwRops)
    {"", 0, 0}
};
CAPDEF VSBROPCapsDefs[] =
{
    GEN_ROPS(dwVSBRops)
    {"", 0, 0}
};
CAPDEF SVBROPCapsDefs[] =
{
    GEN_ROPS(dwSVBRops)
    {"", 0, 0}
};
CAPDEF SSBROPCapsDefs[] =
{
    GEN_ROPS(dwSSBRops)
    {"", 0, 0}
};
CAPDEF NLVBROPCapsDefs[] =
{
    GEN_ROPS(dwNLVBRops)
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS DDCapDefs[] =
{
    {"",                    NULL,               (LPARAM)0,                  NULL},
    {"Memory",              VidMem,             (LPARAM)0,                  VidMemPrint},

    {"+Caps",               NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)GenCaps,           DDPrintCaps},
    {"Palette Caps",        DDAddCaps,          (LPARAM)PalCapsDefs,        DDPrintCaps},
    {"Surface Caps",        DDAddCaps,          (LPARAM)SurfCapsDefs,       DDPrintCaps},
    {"Stereo Vision Caps",  DDAddCaps,          (LPARAM)SVisionCapsDefs,    DDPrintCaps},

    {"+Video - Video",      NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)CapsDefs,           DDPrintCaps},
    {"Color Key",           DDAddCaps,          (LPARAM)CKeyCapsDefs,       DDPrintCaps},
    {"FX",                  DDAddCaps,          (LPARAM)FXCapsDefs,         DDPrintCaps},
    {"ROPS",                DDAddCaps,          (LPARAM)ROPCapsDefs,        DDPrintCaps},
    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"+System - Video",     NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)SVBCapsDefs,        DDPrintCaps},
    {"Color Key",           DDAddCaps,          (LPARAM)SVBCKeyCapsDefs,    DDPrintCaps},
    {"FX",                  DDAddCaps,          (LPARAM)SVBFXCapsDefs,      DDPrintCaps},

{"ROPS",                DDAddCaps,          (LPARAM)SVBROPCapsDefs,     DDPrintCaps},
    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"+Video - System",     NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)VSBCapsDefs,        DDPrintCaps},
    {"Color Key",           DDAddCaps,          (LPARAM)SSBCKeyCapsDefs,    DDPrintCaps},
    {"FX",                  DDAddCaps,          (LPARAM)SSBFXCapsDefs,      DDPrintCaps},
    {"ROPS",                DDAddCaps,          (LPARAM)VSBROPCapsDefs,     DDPrintCaps},
    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"+System - System",    NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)SSBCapsDefs,        DDPrintCaps},
    {"Color Key",           DDAddCaps,          (LPARAM)SSBCKeyCapsDefs,    DDPrintCaps},
    {"FX",                  DDAddCaps,          (LPARAM)SSBFXCapsDefs,      DDPrintCaps},
    {"ROPS",                DDAddCaps,          (LPARAM)SSBROPCapsDefs,     DDPrintCaps},
    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"+NonLocal - Video",   NULL,               (LPARAM)0,                  NULL},
    {"General",             DDAddCaps,          (LPARAM)NLVBCapsDefs,       DDPrintCaps},
    {"Color Key",           DDAddCaps,          (LPARAM)NLVBCKeyCapsDefs,   DDPrintCaps},
    {"FX",                  DDAddCaps,          (LPARAM)NLVBFXCapsDefs,     DDPrintCaps},
    {"ROPS",                DDAddCaps,          (LPARAM)NLVBROPCapsDefs,    DDPrintCaps},
    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"-",                   NULL,               (LPARAM)0,                  NULL},

    {"Video Modes",         DDAddVideoModes,    (LPARAM)0,                  DDPrintVideoModes},
    {"FourCC Formats",      DDFourCCFormat,     (LPARAM)0,                  DDPrintFourCCFormat},
    {"Other",               DDAddCaps,          (LPARAM)OtherInfoDefs,      DDPrintCaps},

    { NULL, 0, 0, NULL}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF ValidFlags[] =
{
    D3CAPDEF("COLORMODEL",            dwFlags, D3DDD_COLORMODEL),
    D3CAPDEF("DEVCAPS",               dwFlags, D3DDD_DEVCAPS),
    D3CAPDEF("TRANSFORMCAPS",         dwFlags, D3DDD_TRANSFORMCAPS),
    D3CAPDEF("LIGHTINGCAPS",          dwFlags, D3DDD_LIGHTINGCAPS),
    D3CAPDEF("BCLIPPING",             dwFlags, D3DDD_BCLIPPING),
    D3CAPDEF("LINECAPS",              dwFlags, D3DDD_LINECAPS),
    D3CAPDEF("TRICAPS",               dwFlags, D3DDD_TRICAPS),
    D3CAPDEF("DEVICERENDERBITDEPTH",  dwFlags, D3DDD_DEVICERENDERBITDEPTH),
    D3CAPDEF("DEVICEZBUFFERBITDEPTH", dwFlags, D3DDD_DEVICEZBUFFERBITDEPTH),
    D3CAPDEF("MAXBUFFERSIZE",         dwFlags, D3DDD_MAXBUFFERSIZE),
    D3CAPDEF("MAXVERTEXCOUNT",        dwFlags, D3DDD_MAXVERTEXCOUNT),
    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF ColorModel[] =
{
    D3CAPDEF("D3DCOLOR_MONO",           dcmColorModel, D3DCOLOR_MONO),
    D3CAPDEF("D3DCOLOR_RGB",            dcmColorModel, D3DCOLOR_RGB),
    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DevCaps[] =
{
    D3CAPDEF("SORTINCREASINGZ",             dwDevCaps, D3DDEVCAPS_SORTINCREASINGZ),
    D3CAPDEF("SORTDECREASINGZ",             dwDevCaps, D3DDEVCAPS_SORTDECREASINGZ),
    D3CAPDEF("SORTEXACT",                   dwDevCaps, D3DDEVCAPS_SORTEXACT),
    D3CAPDEF("EXECUTESYSTEMMEMORY",         dwDevCaps, D3DDEVCAPS_EXECUTESYSTEMMEMORY),
    D3CAPDEF("EXECUTEVIDEOMEMORY",          dwDevCaps, D3DDEVCAPS_EXECUTEVIDEOMEMORY),
    D3CAPDEF("TLVERTEXSYSTEMEMORY",         dwDevCaps, D3DDEVCAPS_TLVERTEXSYSTEMMEMORY),
    D3CAPDEF("TLVERTEXVIDEOMEMORY",         dwDevCaps, D3DDEVCAPS_TLVERTEXVIDEOMEMORY),
    D3CAPDEF("TEXTURESYSTEMMEMORY",         dwDevCaps, D3DDEVCAPS_TEXTURESYSTEMMEMORY),
    D3CAPDEF("TEXTUREVIDEOMEMORY",          dwDevCaps, D3DDEVCAPS_TEXTUREVIDEOMEMORY),
    D3CAPDEF("FLOATTLVERTEX"              , dwDevCaps, D3DDEVCAPS_FLOATTLVERTEX),
    D3CAPDEF("DRAWPRIMTLVERTEX"           , dwDevCaps, D3DDEVCAPS_DRAWPRIMTLVERTEX),
    D3CAPDEF("CANRENDERAFTERFLIP"         , dwDevCaps, D3DDEVCAPS_CANRENDERAFTERFLIP),
    D3CAPDEF("TEXTURENONLOCALVIDMEM" , dwDevCaps, D3DDEVCAPS_TEXTURENONLOCALVIDMEM),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TransformCaps[] =
{
    D3CAPDEF("CLIP",  dtcTransformCaps.dwCaps, D3DTRANSFORMCAPS_CLIP),
    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF LightingCaps[] =
{
    D3CAPDEF("D3DLIGHTINGMODEL_RGB",       dlcLightingCaps.dwLightingModel, D3DLIGHTINGMODEL_RGB),
    D3CAPDEF("D3DLIGHTINGMODEL_MONO",      dlcLightingCaps.dwLightingModel, D3DLIGHTINGMODEL_MONO),

    D3CAPDEF("D3DLIGHTCAPS_POINT",         dlcLightingCaps.dwCaps,          D3DLIGHTCAPS_POINT),
    D3CAPDEF("D3DLIGHTCAPS_SPOT",          dlcLightingCaps.dwCaps,          D3DLIGHTCAPS_SPOT),
    D3CAPDEF("D3DLIGHTCAPS_DIRECTIONAL",   dlcLightingCaps.dwCaps,          D3DLIGHTCAPS_DIRECTIONAL),
    D3CAPDEF("D3DLIGHTCAPS_PARALLELPOINT", dlcLightingCaps.dwCaps,          D3DLIGHTCAPS_PARALLELPOINT),
    D3VALDEF("dwNumLights",                dlcLightingCaps.dwNumLights),
    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF BClipping[] =
{
    D3CAPDEF("bClipping", bClipping, TRUE),
    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriMisc[] =
{
    D3CAPDEF("MASKPLANES",            dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_MASKPLANES),
    D3CAPDEF("MASKZ",                 dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_MASKZ),
    D3CAPDEF("LINEPATTERNREP",        dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_LINEPATTERNREP),
    D3CAPDEF("CONFORMANT",            dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CONFORMANT),
    D3CAPDEF("CULLNONE",              dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLNONE),
    D3CAPDEF("CULLCW",                dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLCW),
    D3CAPDEF("CULLCCW",               dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLCCW),

    D3CAPDEF("DITHER",              dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_DITHER),
    D3CAPDEF("ROP2",                dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_ROP2),
    D3CAPDEF("XOR",                 dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_XOR),
    D3CAPDEF("PAT",                 dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_PAT),
    D3CAPDEF("ZTEST",               dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_ZTEST),
    D3CAPDEF("SUBPIXEL",            dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_SUBPIXEL),
    D3CAPDEF("SUBPIXELX",           dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_SUBPIXELX),
    D3CAPDEF("FOGVERTEX",           dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_FOGVERTEX),
    D3CAPDEF("FOGTABLE",            dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_FOGTABLE),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriFog[] =
{
    D3CAPDEF("FOGVERTEX",       dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_FOGVERTEX),
    D3CAPDEF("FOGTABLE",        dpcTriCaps.dwRasterCaps,         D3DPRASTERCAPS_FOGTABLE),

    D3CAPDEF("FOG FLAT",        dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGFLAT             ),
    D3CAPDEF("FOG GOURAUD",     dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGGOURAUD          ),
    D3CAPDEF("FOG PHONG",       dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGPHONG            ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriCull[] =
{
    D3CAPDEF("CULL NONE",           dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLNONE),
    D3CAPDEF("CULL CW",             dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLCW),
    D3CAPDEF("CULL CCW",            dpcTriCaps.dwMiscCaps,           D3DPMISCCAPS_CULLCCW),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriTextureBlend[] =
{
    D3CAPDEF("DECAL",           dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_DECAL              ),
    D3CAPDEF("MODULATE",        dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_MODULATE           ),
    D3CAPDEF("DECALALPHA",      dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_DECALALPHA         ),
    D3CAPDEF("MODULATEALPHA",   dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_MODULATEALPHA      ),
    D3CAPDEF("DECALMASK",       dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_DECALMASK          ),
    D3CAPDEF("MODULATEMASK",    dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_MODULATEMASK       ),
    D3CAPDEF("COPY",            dpcTriCaps.dwTextureBlendCaps,   D3DPTBLENDCAPS_COPY               ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriTextureFilter[] =
{
    D3CAPDEF("NEAREST",         dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_NEAREST           ),
    D3CAPDEF("LINEAR",          dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_LINEAR            ),
    D3CAPDEF("MIPNEAREST",      dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_MIPNEAREST        ),
    D3CAPDEF("MIPLINEAR",       dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_MIPLINEAR         ),
    D3CAPDEF("LINEARMIPNEAREST",dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_LINEARMIPNEAREST  ),
    D3CAPDEF("LINEARMIPLINEAR", dpcTriCaps.dwTextureFilterCaps,  D3DPTFILTERCAPS_LINEARMIPLINEAR   ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriTextureCaps[] =
{
    D3CAPDEF("PERSPECTIVE",     dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_PERSPECTIVE       ),
    D3CAPDEF("POW2",            dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_POW2              ),
    D3CAPDEF("ALPHA",           dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_ALPHA             ),
    D3CAPDEF("TRANSPARENCY",    dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_TRANSPARENCY      ),
    D3CAPDEF("BORDER",          dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_BORDER            ),
    D3CAPDEF("SQUAREONLY",      dpcTriCaps.dwTextureCaps,        D3DPTEXTURECAPS_SQUAREONLY        ),

    D3CAPDEF("WRAP",            dpcTriCaps.dwTextureAddressCaps, D3DPTADDRESSCAPS_WRAP             ),
    D3CAPDEF("MIRROR",          dpcTriCaps.dwTextureAddressCaps, D3DPTADDRESSCAPS_MIRROR           ),
    D3CAPDEF("CLAMP",           dpcTriCaps.dwTextureAddressCaps, D3DPTADDRESSCAPS_CLAMP            ),
    D3CAPDEF("BORDER",          dpcTriCaps.dwTextureAddressCaps, D3DPTADDRESSCAPS_BORDER           ),
    D3CAPDEF("INDEPENDENTUV",   dpcTriCaps.dwTextureAddressCaps, D3DPTADDRESSCAPS_INDEPENDENTUV    ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriZCmpCaps[] =
{
    D3CAPDEF("NEVER",               dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_NEVER                 ),
    D3CAPDEF("LESS",                dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_LESS                  ),
    D3CAPDEF("EQUAL",               dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_EQUAL                 ),
    D3CAPDEF("LESSEQUAL",           dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_LESSEQUAL             ),
    D3CAPDEF("GREATER",             dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_GREATER               ),
    D3CAPDEF("NOTEQUAL",            dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_NOTEQUAL              ),
    D3CAPDEF("GREATEREQUAL",        dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_GREATEREQUAL          ),
    D3CAPDEF("ALWAYS",              dpcTriCaps.dwZCmpCaps,           D3DPCMPCAPS_ALWAYS                ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriACmpCaps[] =
{
    D3CAPDEF("NEVER",                  dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_NEVER                 ),
    D3CAPDEF("LESS",                   dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_LESS                  ),
    D3CAPDEF("EQUAL",                  dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_EQUAL                 ),
    D3CAPDEF("LESSEQUAL",              dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_LESSEQUAL             ),
    D3CAPDEF("GREATER",                dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_GREATER               ),
    D3CAPDEF("NOTEQUAL",               dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_NOTEQUAL              ),
    D3CAPDEF("GREATEREQUAL",           dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_GREATEREQUAL          ),
    D3CAPDEF("ALWAYS",                 dpcTriCaps.dwAlphaCmpCaps,       D3DPCMPCAPS_ALWAYS                ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriSrcBlendCaps[] =
{
    D3CAPDEF("ZERO",                 dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_ZERO                ),
    D3CAPDEF("ONE",                  dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_ONE                 ),
    D3CAPDEF("SRCCOLOR",             dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_SRCCOLOR            ),
    D3CAPDEF("INVSRCCOLOR",          dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_INVSRCCOLOR         ),
    D3CAPDEF("SRCALPHA",             dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_SRCALPHA            ),
    D3CAPDEF("INVSRCALPHA",          dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_INVSRCALPHA         ),
    D3CAPDEF("DESTALPHA",            dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_DESTALPHA           ),
    D3CAPDEF("INVDESTALPHA",         dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_INVDESTALPHA        ),
    D3CAPDEF("DESTCOLOR",            dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_DESTCOLOR           ),
    D3CAPDEF("INVDESTCOLOR",         dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_INVDESTCOLOR        ),
    D3CAPDEF("SRCALPHASAT",          dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_SRCALPHASAT         ),
    D3CAPDEF("BOTHSRCALPHA",         dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_BOTHSRCALPHA        ),
    D3CAPDEF("BOTHINVSRCALPHA",      dpcTriCaps.dwSrcBlendCaps,       D3DPBLENDCAPS_BOTHINVSRCALPHA     ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriDestBlendCaps[] =
{
    D3CAPDEF("ZERO",                 dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_ZERO                ),
    D3CAPDEF("ONE",                  dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_ONE                 ),
    D3CAPDEF("SRCCOLOR",             dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_SRCCOLOR            ),
    D3CAPDEF("INVSRCCOLOR",          dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_INVSRCCOLOR         ),
    D3CAPDEF("SRCALPHA",             dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_SRCALPHA            ),
    D3CAPDEF("INVSRCALPHA",          dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_INVSRCALPHA         ),
    D3CAPDEF("DESTALPHA",            dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_DESTALPHA           ),
    D3CAPDEF("INVDESTALPHA",         dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_INVDESTALPHA        ),
    D3CAPDEF("DESTCOLOR",            dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_DESTCOLOR           ),
    D3CAPDEF("INVDESTCOLOR",         dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_INVDESTCOLOR        ),
    D3CAPDEF("SRCALPHASAT",          dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_SRCALPHASAT         ),
    D3CAPDEF("BOTHSRCALPHA",         dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_BOTHSRCALPHA        ),
    D3CAPDEF("BOTHINVSRCALPHA",      dpcTriCaps.dwDestBlendCaps,      D3DPBLENDCAPS_BOTHINVSRCALPHA     ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF TriShadeCaps[] =
{
    D3CAPDEF("COLOR FLAT MONO",        dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORFLATMONO       ),
    D3CAPDEF("COLOR FLAT RGB",         dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORFLATRGB        ),
    D3CAPDEF("COLOR GOURAUD MONO",     dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORGOURAUDMONO    ),
    D3CAPDEF("COLOR GOURAUD RGB",      dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORGOURAUDRGB     ),
    D3CAPDEF("COLOR PHONG MONO",       dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORPHONGMONO      ),
    D3CAPDEF("COLOR PHONG RGB",        dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_COLORPHONGRGB       ),

    D3CAPDEF("SPECULAR FLAT MONO",     dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARFLATMONO    ),
    D3CAPDEF("SPECULAR FLAT RGB",      dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARFLATRGB     ),
    D3CAPDEF("SPECULAR GOURAUD MONO",  dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARGOURAUDMONO ),
    D3CAPDEF("SPECULAR GOURAUD RGB",   dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARGOURAUDRGB  ),
    D3CAPDEF("SPECULAR PHONG MONO",    dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARPHONGMONO   ),
    D3CAPDEF("SPECULAR PHONG RGB",     dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_SPECULARPHONGRGB    ),

    D3CAPDEF("ALPHA FLAT BLEND",       dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAFLATBLEND      ),
    D3CAPDEF("ALPHA FLAT STIPPLED",    dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAFLATSTIPPLED   ),
    D3CAPDEF("ALPHA GOURAUD BLEND",    dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAGOURAUDBLEND   ),
    D3CAPDEF("ALPHA GOURAUD STIPPLED", dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED),
    D3CAPDEF("ALPHA PHONG BLEND",      dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAPHONGBLEND     ),
    D3CAPDEF("ALPHA PHONG STIPPLED",   dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_ALPHAPHONGSTIPPLED  ),

    D3CAPDEF("FOG FLAT",               dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGFLAT             ),
    D3CAPDEF("FOG GOURAUD",            dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGGOURAUD          ),
    D3CAPDEF("FOG PHONG",              dpcTriCaps.dwShadeCaps,          D3DPSHADECAPS_FOGPHONG            ),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF D3dMisc[] =
{
    D3CAPDEF("D3DCOLOR_MONO",         dcmColorModel, D3DCOLOR_MONO),
    D3CAPDEF("D3DCOLOR_RGB",          dcmColorModel, D3DCOLOR_RGB),

    D3CAPDEF("bClipping", bClipping, TRUE),
    D3CAPDEF("CLIP",  dtcTransformCaps.dwCaps, D3DTRANSFORMCAPS_CLIP),

    D3VALDEF("MaxBufferSize",         dwMaxBufferSize),
    D3VALDEF("MaxVertexCount",        dwMaxVertexCount),
    D3HEXDEF("DeviceRenderBitDepth",  dwDeviceRenderBitDepth),
    D3CAPDEF("  8",                   dwDeviceRenderBitDepth, DDBD_8),
    D3CAPDEF("  16",                  dwDeviceRenderBitDepth, DDBD_16),
    D3CAPDEF("  24",                  dwDeviceRenderBitDepth, DDBD_24),
    D3CAPDEF("  32",                  dwDeviceRenderBitDepth, DDBD_32),
    D3HEXDEF("DeviceZBufferBitDepth", dwDeviceZBufferBitDepth),
    D3CAPDEF("  8",                   dwDeviceZBufferBitDepth, DDBD_8),
    D3CAPDEF("  16",                  dwDeviceZBufferBitDepth, DDBD_16),
    D3CAPDEF("  24",                  dwDeviceZBufferBitDepth, DDBD_24),
    D3CAPDEF("  32",                  dwDeviceZBufferBitDepth, DDBD_32),
    D3VALDEF("MinTextureWidth",       dwMinTextureWidth),
    D3VALDEF("MinTextureHeight",      dwMinTextureHeight),
    D3VALDEF("MaxTextureWidth",       dwMaxTextureWidth),
    D3VALDEF("MaxTextureHeight",      dwMaxTextureHeight),
    D3VALDEF("MinStippleWidth",       dwMinStippleWidth),
    D3VALDEF("MaxStippleWidth",       dwMaxStippleWidth),
    D3VALDEF("MinStippleHeight",      dwMinStippleHeight),
    D3VALDEF("MaxStippleHeight",      dwMaxStippleHeight),

    {"",0,0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS D3CapDefs[] =
{
    {"",                D3AddCaps, (LPARAM)ValidFlags,       D3PrintCaps},
    {"DevCaps",         D3AddCaps, (LPARAM)DevCaps,          D3PrintCaps},
    {"Misc",            D3AddCaps, (LPARAM)D3dMisc,          D3PrintCaps},
//  {"ColorModel",      D3AddCaps, (LPARAM)ColorModel,       D3PrintCaps},
//  {"TransformCaps",   D3AddCaps, (LPARAM)TransformCaps,    D3PrintCaps},
//  {"BClipping",       D3AddCaps, (LPARAM)BClipping,        D3PrintCaps},
    {"LightingCaps",    D3AddCaps, (LPARAM)LightingCaps,     D3PrintCaps},

    {"+Triangle Caps",      NULL,      (LPARAM)0,                NULL},
    {"Z Compare",           D3AddCaps, (LPARAM)TriZCmpCaps,      D3PrintCaps},
    {"Alpha Compare",       D3AddCaps, (LPARAM)TriACmpCaps,      D3PrintCaps},
    {"Misc",                D3AddCaps, (LPARAM)TriMisc,          D3PrintCaps},
    {"Cull",                D3AddCaps, (LPARAM)TriCull,          D3PrintCaps},
    {"Fog",                 D3AddCaps, (LPARAM)TriFog,           D3PrintCaps},
    {"Texture Caps",        D3AddCaps, (LPARAM)TriTextureCaps,   D3PrintCaps},
    {"Texture Filter",      D3AddCaps, (LPARAM)TriTextureFilter, D3PrintCaps},
    {"Texture Blend",       D3AddCaps, (LPARAM)TriTextureBlend,  D3PrintCaps},
    {"Texture Source Blend",D3AddCaps, (LPARAM)TriSrcBlendCaps,  D3PrintCaps},
    {"Texture Dest Blend",  D3AddCaps, (LPARAM)TriDestBlendCaps, D3PrintCaps},
    {"-",                   NULL,      (LPARAM)0,                NULL},

    {"+Line Caps",          NULL,          (LPARAM)0,                NULL},
    {"Z Compare",           D3AddLineCaps, (LPARAM)TriZCmpCaps,      D3PrintCaps},
    {"Alpha Compare",       D3AddLineCaps, (LPARAM)TriACmpCaps,      D3PrintCaps},
    {"Misc",                D3AddLineCaps, (LPARAM)TriMisc,          D3PrintCaps},
    {"Cull",                D3AddLineCaps, (LPARAM)TriCull,          D3PrintCaps},
    {"Fog",                 D3AddLineCaps, (LPARAM)TriFog,           D3PrintCaps},
    {"Texture Caps",        D3AddLineCaps, (LPARAM)TriTextureCaps,   D3PrintCaps},
    {"Texture Filter",      D3AddLineCaps, (LPARAM)TriTextureFilter, D3PrintCaps},
    {"Texture Blend",       D3AddLineCaps, (LPARAM)TriTextureBlend,  D3PrintCaps},
    {"Texture Source Blend",D3AddLineCaps, (LPARAM)TriSrcBlendCaps,  D3PrintCaps},
    {"Texture Dest Blend",  D3AddLineCaps, (LPARAM)TriDestBlendCaps, D3PrintCaps},
    {"-",                   NULL,          (LPARAM)0,                NULL},

    {"Texture Formats",     D3AddTexF, (LPARAM)0,                0},
    {NULL, 0, 0, NULL}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DSInfo[] =
{
    DSVALDEF("MinSecondarySampleRate",          dwMinSecondarySampleRate),
    DSVALDEF("MaxSecondarySampleRate",          dwMaxSecondarySampleRate),
    DSVALDEF("PrimaryBuffers",                  dwPrimaryBuffers),
    DSVALDEF("MaxHwMixingAllBuffers",           dwMaxHwMixingAllBuffers),
    DSVALDEF("MaxHwMixingStaticBuffers",        dwMaxHwMixingStaticBuffers),
    DSVALDEF("MaxHwMixingStreamingBuffers",     dwMaxHwMixingStreamingBuffers),
    DSVALDEF("FreeHwMixingAllBuffers",          dwFreeHwMixingAllBuffers),
    DSVALDEF("FreeHwMixingStaticBuffers",       dwFreeHwMixingStaticBuffers),
    DSVALDEF("FreeHwMixingStreamingBuffers",    dwFreeHwMixingStreamingBuffers),
    DSVALDEF("MaxHw3DAllBuffers",               dwMaxHw3DAllBuffers),
    DSVALDEF("MaxHw3DStaticBuffers",            dwMaxHw3DStaticBuffers),
    DSVALDEF("MaxHw3DStreamingBuffers",         dwMaxHw3DStreamingBuffers),
    DSVALDEF("FreeHw3DAllBuffers",              dwFreeHw3DAllBuffers),
    DSVALDEF("FreeHw3DStaticBuffers",           dwFreeHw3DStaticBuffers),
    DSVALDEF("FreeHw3DStreamingBuffers",        dwFreeHw3DStreamingBuffers),
    DSVALDEF("TotalHwMemBytes",                 dwTotalHwMemBytes),
    DSVALDEF("FreeHwMemBytes",                  dwFreeHwMemBytes),
    DSVALDEF("MaxContigFreeHwMemBytes",         dwMaxContigFreeHwMemBytes),
    DSVALDEF("UnlockTransferRateHwBuffers",     dwUnlockTransferRateHwBuffers),
    DSVALDEF("PlayCpuOverheadSwBuffers",        dwPlayCpuOverheadSwBuffers),
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DSGeneralCaps[] =
{
    DSCAPDEF("PRIMARYMONO",       dwFlags,    DSCAPS_PRIMARYMONO),
    DSCAPDEF("PRIMARYSTEREO",     dwFlags,    DSCAPS_PRIMARYSTEREO),
    DSCAPDEF("PRIMARY8BIT",       dwFlags,    DSCAPS_PRIMARY8BIT),
    DSCAPDEF("PRIMARY16BIT",      dwFlags,    DSCAPS_PRIMARY16BIT),
    DSCAPDEF("CONTINUOUSRATE",    dwFlags,    DSCAPS_CONTINUOUSRATE),
    DSCAPDEF("EMULDRIVER",        dwFlags,    DSCAPS_EMULDRIVER),
    DSCAPDEF("SECONDARYMONO",     dwFlags,    DSCAPS_SECONDARYMONO),
    DSCAPDEF("SECONDARYSTEREO",   dwFlags,    DSCAPS_SECONDARYSTEREO),
    DSCAPDEF("SECONDARY8BIT",     dwFlags,    DSCAPS_SECONDARY8BIT),
    DSCAPDEF("SECONDARY16BIT",    dwFlags,    DSCAPS_SECONDARY16BIT),
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS DSCapDefs[] =
{
    {"",                    DSAddCaps,          (LPARAM)DSInfo,         DSPrintCaps},
    {"General",             DSAddCaps,          (LPARAM)DSInfo,         DSPrintCaps},
    {"General Caps",        DSAddCaps,          (LPARAM)DSGeneralCaps,  DSPrintCaps},
    {NULL, 0, 0, NULL}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DSCInfo[] =
{
    DSCVALDEF("Channels",          dwChannels),
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DSCGeneralCaps[] =
{
    DSCCAPDEF("EMULDRIVER",        dwFlags,    DSCCAPS_EMULDRIVER),
    DSCCAPDEF("WAVE_FORMAT_1M08",  dwFormats,  WAVE_FORMAT_1M08),
    DSCCAPDEF("WAVE_FORMAT_1S08",  dwFormats,  WAVE_FORMAT_1S08),
    DSCCAPDEF("WAVE_FORMAT_1M16",  dwFormats,  WAVE_FORMAT_1M16),
    DSCCAPDEF("WAVE_FORMAT_1S16",  dwFormats,  WAVE_FORMAT_1S16),
    DSCCAPDEF("WAVE_FORMAT_2M08",  dwFormats,  WAVE_FORMAT_2M08),
    DSCCAPDEF("WAVE_FORMAT_2S08",  dwFormats,  WAVE_FORMAT_2S08),
    DSCCAPDEF("WAVE_FORMAT_2M16",  dwFormats,  WAVE_FORMAT_2M16),
    DSCCAPDEF("WAVE_FORMAT_2S16",  dwFormats,  WAVE_FORMAT_2S16),
    DSCCAPDEF("WAVE_FORMAT_4M08",  dwFormats,  WAVE_FORMAT_4M08),
    DSCCAPDEF("WAVE_FORMAT_4S08",  dwFormats,  WAVE_FORMAT_4S08),
    DSCCAPDEF("WAVE_FORMAT_4M16",  dwFormats,  WAVE_FORMAT_4M16),
    DSCCAPDEF("WAVE_FORMAT_4S16",  dwFormats,  WAVE_FORMAT_4S16),
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS DSCCapDefs[] =
{
    {"",                    DSCAddCaps,          (LPARAM)DSCInfo,         DSCPrintCaps},
    {"General",             DSCAddCaps,          (LPARAM)DSCInfo,         DSCPrintCaps},
    {"General Caps",        DSCAddCaps,          (LPARAM)DSCGeneralCaps,  DSCPrintCaps},
    {NULL, 0, 0, NULL}
};

/****************************************************************************
 ***************************************************************************/
CAPDEF DPInfo[] =
{
    DPVALDEF("Size",                dwSize),
    DPHEXDEF("Flags",               dwFlags),
    DPVALDEF("MaxBufferSize",       dwMaxBufferSize),
    DPVALDEF("MaxQueueSize",        dwMaxQueueSize),
    DPVALDEF("MaxPlayers",          dwMaxPlayers),
    DPVALDEF("HundredBaud",         dwHundredBaud),

DPVALDEF("Latency",             dwLatency),
    DPVALDEF("MaxLocalPlayers",     dwMaxLocalPlayers),
    DPVALDEF("HeaderLength",        dwHeaderLength),
    DPVALDEF("Timeout",             dwTimeout),
    {"", 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS DPCapDefs[] =
{
    {"",                    DPAddCaps,(LPARAM)DPInfo,  DPPrintCaps},
    {"Sessions",            DPAddSessions,      (LPARAM)0,            DPPrintSessions},
    {NULL, 0, 0}
};

/****************************************************************************
 ***************************************************************************/
CAPDEFS DPLocalAppsDef[] =
{
    {"LocalApplications",DPAddLocalApps,(LPARAM)0,DPPrintLocalApps},
    {NULL, 0, 0}
};

//================================================================
//  WinMain - entry point
//================================================================
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;

    CoInitialize( NULL );
    g_hInstance = hInstance; // Store instance handle in our global variable
    g_idTimer = 0;                  //Used by DPlay
    g_lpLocalApp = NULL;    //Used by DPLay

    // Try to load the dsound.dll
    g_hDSoundLib = LoadLibrary("DSOUND.DLL");
    if (g_hDSoundLib == NULL)
    {       // couldn't load the library so put up a message and quit.
        MessageBox(g_hwndMain, "Could not load DSOUND.DLL", g_szAppName, MB_OK);
        CoUninitialize();
        return 0;       // terminate after showing the message.
    }

    //Create the initial local app list.
    DPAddLocalApps(0L, 0);

    if (InitInstance(hInstance, lpCmdLine, nCmdShow, DXView_WIDTH, DXView_HEIGHT))
    {
        while(GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    if (g_lpLocalApp)
        FreeLocalAppList();

    // Unload dsound.dll
    FreeLibrary(g_hDSoundLib);

    CoUninitialize();
    return(msg.wParam);
}

//================================================================
//  InitInstance - create main window
//================================================================
BOOL InitInstance(HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow, int iWidth, int iHeight)
{
    WNDCLASS  wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW; // Class style(s).
    wc.lpfnWndProc   = (WNDPROC)WndProc;        // Window Procedure
    wc.cbClsExtra    = 0;                       // No per-class extra data.
    wc.cbWndExtra    = 0;                       // No per-window extra data.
    wc.hInstance     = hInstance;               // Owner of this class
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DIRECTX)); // Icon name from .RC
    wc.hCursor       = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_SPLIT));// Cursor
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1); // Default color
    wc.lpszMenuName  = "Menu";                   // Menu name from .RC
    wc.lpszClassName = g_szClassName;            // Name to register as

    if(!RegisterClass(&wc))
    {
        return FALSE;
    }

    // Create a main window for this application instance.
    g_hwndMain = CreateWindowEx(
        0,
        g_szClassName,   // See RegisterClass() call.
        g_szTitle,       // Text for window title bar.
        WS_OVERLAPPEDWINDOW,// Window style.
        CW_USEDEFAULT, CW_USEDEFAULT, iWidth, iHeight, // Use default positioning
        NULL,            // Overlapped windows have no parent.
        NULL,            // Use the window class menu.
        hInstance,       // This instance owns this window.
        NULL);

    // If window could not be created, return "failure"
    if (! g_hwndMain)
    {
        return(FALSE);
    }

    // Make the window visible; update its client area; and return "success"
    ShowWindow (g_hwndMain, nCmdShow); // Show the window

    return(TRUE);              // We succeeded...
}

//================================================================
//  WndProc - main window proc
//================================================================
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_CREATE:
            return DXView_OnCreate(hwnd);

        case WM_SIZE:
            DXView_OnSize(hwnd);
            break;

        case WM_LBUTTONDOWN:
            g_bSplitMove = TRUE;
            SetCapture(hwnd);
            break;

        case WM_LBUTTONUP:
            g_bSplitMove = FALSE;
            ReleaseCapture();
            break;

        case WM_MOUSEMOVE:
            if(g_bSplitMove)
            {
                RECT    rect;
                // change the value from unsigned to signed
                int     x = (int)(short)LOWORD(lParam);

                GetClientRect(hwnd, &rect);
                if (rect.left > x)
                {
                    x = rect.left;
                }
                else if (rect.right < x)
                {
                    x = rect.right;
                }
                g_xPaneSplit = (x - g_xHalfSplitWidth);
                DXView_OnSize(hwnd);
            }
            break;

        case WM_NOTIFY:
            if (((NMHDR*)lParam)->hwndFrom == g_hwndTV)
            {
                if (((NMHDR*)lParam)->code == TVN_SELCHANGED)
                    DXView_OnTreeSelect(g_hwndTV, (NM_TREEVIEW*)lParam);
            }

            if (((NMHDR*)lParam)->hwndFrom == g_hwndLV)
            {
                if (((NMHDR*)lParam)->code == NM_RDBLCLK)
                    DXView_OnListViewDblClick(g_hwndLV, (NM_LISTVIEW*)lParam);
            }

            break;

       case WM_TIMER:
// refresh the session list
EnumSessions( lpDP3A );
break;

       case WM_COMMAND:  // message: command from application menu
            DXView_OnCommand(hwnd, wParam);
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 0;

        case WM_DESTROY:  // message: window being destroyed
            DXView_Cleanup();  // Free per item struct for all items
            PostQuitMessage(0);
            break;
    }

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

//================================================================
//================================================================
BOOL DXView_OnCreate(HWND hwnd)
{
    HDC hDC;
    int PixelsPerInch;
    TEXTMETRIC tm;
    static TCHAR szBuf[MAX_PATH];

    hDC = GetDC(hwnd);
    PixelsPerInch = GetDeviceCaps(hDC, LOGPIXELSX);
    g_hFont = GetStockObject(ANSI_FIXED_FONT);
    SelectObject(hDC, g_hFont);
    GetTextMetrics(hDC, &tm);
    g_tmAveCharWidth = tm.tmAveCharWidth;
    ReleaseDC(hwnd, hDC);

    // Initialize global data
    g_dwViewState = IDM_VIEWAVAIL;
    g_xPaneSplit = PixelsPerInch * 12 / 4;
    g_xHalfSplitWidth = GetSystemMetrics(SM_CXSIZEFRAME) / 2;

    // Make sure that the common control library read to rock
    InitCommonControls();

    CheckMenuItem(GetMenu(hwnd), g_dwViewState, MF_BYCOMMAND | MF_CHECKED);

    // Create the list view window.
    g_hwndLV = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "",
        WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT,
        0, 0, 0, 0, hwnd, (HMENU)IDC_LV, g_hInstance, NULL);

    // create the tree view window.
    g_hwndTV = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
        WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES |
        TVS_HASBUTTONS | TVS_LINESATROOT,
        0, 0, 0, 0, hwnd, (HMENU)IDC_TV, g_hInstance, NULL);

    // create our image list.
    DXView_InitImageList();

    // Initialize the tree view
    DXView_FillTree(g_hwndTV);

    return(TRUE);
}

//================================================================
//================================================================
IDirectDraw * DDCreate(GUID *pid)
{
    if (lpDD && pid == ddid)
        return lpDD;

    if (lp3D)
    {
        IDirect3D2_Release(lp3D);
        lp3D = NULL;
    }

    if (lpDD)
    {
        IDirectDraw_Release(lpDD);
        lpDD = NULL;
    }

    // There is no need to create DirectDraw emulation-only just to get
    // the HEL caps.  In fact, this will fail if there is another DirectDraw
    // app running and using the hardware.
    if( pid == (GUID *)DDCREATE_EMULATIONONLY )
    {
        pid = NULL;
    }
    if (DirectDrawCreate(pid, &lpDD, NULL) == DD_OK)
    {
        IDirectDraw_QueryInterface(lpDD,&IID_IDirect3D2,&lp3D);
        ddid = pid;
        return lpDD;
    }
    if(!g_PrintToFile)
        MessageBox(g_hwndMain, "DirectDrawCreate failed.", g_szAppName, MB_OK);
    return NULL;
}

//================================================================
//================================================================
IDirectSound * DSCreate(GUID *pid)
{
PFN_DSCREATE pfn_DSCreate;

    if (lpDS && pid == dsid)
        return lpDS;

    if (lpDS)
    {
        IDirectSound_Release(lpDS);
        lpDS = NULL;
    }

// Call DirectSoundCreate.
pfn_DSCreate = (PFN_DSCREATE)GetProcAddress(g_hDSoundLib, "DirectSoundCreate");
if (pfn_DSCreate == NULL)
{
        MessageBox(g_hwndMain, "DSOUND.DLL does not implement DirectSoundCreate()", g_szAppName, MB_OK);
return NULL;
}

if ((*pfn_DSCreate)(pid, &lpDS, NULL) == DD_OK)
{
dsid = pid;
return lpDS;
}

    if(!g_PrintToFile)
        MessageBox(g_hwndMain, "DirectSoundCreate failed.", g_szAppName, MB_OK);
    return NULL;
}

//================================================================
//================================================================
IDirectSoundCapture * DSCCreate(GUID *pid)
{
PFN_DSCCREATE pfn_DSCCreate;

    if (lpDSC && pid == dsid)
        return lpDSC;

    if (lpDSC)
    {
        IDirectSoundCapture_Release(lpDSC);
        lpDSC = NULL;
    }

pfn_DSCCreate = (PFN_DSCCREATE)GetProcAddress(g_hDSoundLib, "DirectSoundCaptureCreate");
if (pfn_DSCCreate == NULL)
{
        MessageBox(g_hwndMain, "DSOUND.DLL does not implement DirectSoundCaptureCreate()", g_szAppName, MB_OK);
return NULL;
}

if ((*pfn_DSCCreate)(pid, &lpDSC, NULL) == DD_OK)
{
        dscid = pid;
        return lpDSC;
}

    if(!g_PrintToFile)
        MessageBox(g_hwndMain, "DirectSoundCaptureCreate failed.", g_szAppName, MB_OK);
    return NULL;
}

//================================================================
//================================================================
IDirectPlay3 * DPCreate(GUID *pid)
{
LPDIRECTPLAYlpDP = NULL;
HRESULThr;

    if (lpDP3A && pid == dpid)
        return lpDP3A;

    if (lpDP3A)
{
        lpDP3A->lpVtbl->Release(lpDP3A);
lpDP3A = NULL;
}

    if (DirectPlayCreate(pid, &lpDP, NULL) == DD_OK)
    {
        hr = lpDP->lpVtbl->QueryInterface( lpDP, &IID_IDirectPlay3A, (LPVOID *) &lpDP3A );
lpDP->lpVtbl->Release(lpDP);
dpid = pid;
        return lpDP3A;
    }
    if(!g_PrintToFile)
        MessageBox(g_hwndMain, "DirectPlayCreate failed.", g_szAppName, MB_OK);
    return NULL;
}

//================================================================
//================================================================
void AddCapsToTV(HTREEITEM hRoot, CAPDEFS *pcds, LPARAM lParam1)
{
    HTREEITEM hTree;
    HTREEITEM hParent[20];
    char *name;
    int level=0;
    BOOL fRoot = TRUE; // the first one is always a root

    hParent[0] = hRoot;

    while (name = pcds->szName)
    {
        if (*name == '-')
        {
            level--;
            name++;
        }

        if (*name == '+')
        {
            fRoot = TRUE;
            name++;
        }

        if (*name)
        {
            hTree = TVAddNode(hParent[level], name, fRoot, IDI_CAPS,
                              pcds->Callback, lParam1,
                              pcds->lParam2, pcds->printCallback);

            if (fRoot)
            {
                level++;
                hParent[level] = hTree;
                fRoot = FALSE;
            }
        }

        pcds++;  // Get next Cap bit definition
    }
}

//================================================================
//================================================================
char c_szYes[] = "Yes";
char c_szNo[] = "No";
char c_szCurrentMode[] = "Current Mode";

//================================================================
// AddMoreCapsToLV is like AddCapsToLV, except it doesn't add the
// column headers like AddCapsToLV does.
//================================================================
void AddMoreCapsToLV(CAPDEF *pcd, LPVOID pv)
{
    DWORD dwValue;

    while(pcd->szName && *pcd->szName)
    {
        dwValue = *(DWORD *)(((BYTE *)pv) + pcd->dwOffset);

        if (pcd->dwFlag == 0)
        {
            LVAddText(g_hwndLV, 0, "%s", pcd->szName);
            LVAddText(g_hwndLV, 1, "%d", dwValue);
        }
        else if (pcd->dwFlag == 0xFFFFFFFF)     // hex
        {
            LVAddText(g_hwndLV, 0, "%s", pcd->szName);
            LVAddText(g_hwndLV, 1, "0x%08X", dwValue);
        }
        else
        {
            if (pcd->dwFlag & dwValue)
            {
                LVAddText(g_hwndLV, 0, pcd->szName);
                LVAddText(g_hwndLV, 1, c_szYes);
            }
            else if (g_dwViewState == IDM_VIEWALL)
            {
                LVAddText(g_hwndLV, 0, pcd->szName);
                LVAddText(g_hwndLV, 1, c_szNo);
            }
        }

        pcd++;  // Get next Cap bit definition
    }
}

//================================================================
// AddColsToLV adds the column headers but no data.
//================================================================
void AddColsToLV(void)
{
    LVAddColumn(g_hwndLV, 0, "Name", 24);
    LVAddColumn(g_hwndLV, 1, "Value", 10);
}

//================================================================
//================================================================
void AddCapsToLV(CAPDEF *pcd, LPVOID pv)
{
    AddColsToLV();
    AddMoreCapsToLV(pcd, pv);
}

//================================================================
//================================================================
BOOL PrintCapsToDC(CAPDEF *pcd, LPVOID pv, PRINTCBINFO * lpInfo)
{
    DWORD dwValue;
    DWORD cchLen;
    int   xName, xVal, yLine;

    // Check Parameters
    if ((! pcd) || (!lpInfo))
        return FALSE;

    // Calculate Name and Value column x offsets
    xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
    xVal    = xName + (32 * lpInfo->dwCharWidth);

    while (pcd->szName && *pcd->szName)
    {
        dwValue = *(DWORD *)(((BYTE *)pv) + pcd->dwOffset);
        yLine = lpInfo->dwCurrLine * lpInfo->dwLineHeight;

        if (pcd->dwFlag)
        {
            if (pcd->dwFlag & dwValue)
            {
                // Print Name
                cchLen = _tcslen (pcd->szName);
                if (! PrintLine (xName, yLine, pcd->szName, cchLen, lpInfo))
                    return FALSE;

                // Print Yes in value column
                cchLen = _tcslen (c_szYes);
                if (! PrintLine (xVal, yLine, c_szYes, cchLen, lpInfo))
                    return FALSE;

                // Advance to next line on page
                if (! PrintNextLine (lpInfo))
                    return FALSE;
            }
            else if (g_dwViewState == IDM_VIEWALL)
            {
                // Print name
                cchLen = _tcslen (pcd->szName);
                if (! PrintLine (xName, yLine, pcd->szName, cchLen, lpInfo))
                    return FALSE;

                // Print No in value column
                cchLen = _tcslen (c_szNo);
                if (! PrintLine (xVal, yLine, c_szNo, cchLen, lpInfo))
                    return FALSE;

                // Advance to next line on page
                if (! PrintNextLine (lpInfo))
                    return FALSE;
            }
        }
        else
        {
            char    szBuff[80];

            // Print name
            wsprintf (szBuff, pcd->szName, "test");
            cchLen = _tcslen (pcd->szName);
            if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
                return FALSE;

            // Print value
            wsprintf (szBuff, "%d", dwValue);
            cchLen = _tcslen (szBuff);
            if (! PrintLine (xVal, yLine, szBuff, cchLen, lpInfo))
                return FALSE;

            // Advance to next line on page
            if (! PrintNextLine (lpInfo))
                return FALSE;
        }

        pcd++;  // Get next Cap bit definition
    }

    // Success
    return TRUE;
} // End PrintCapsToDC

//================================================================
//================================================================
void DDAddCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DDCreate((GUID*)lParam1))
    {
        DDCAPS ddcaps;

        ddcaps.dwSize = sizeof(ddcaps);

        if (lParam1 == DDCREATE_EMULATIONONLY)
            IDirectDraw_GetCaps(lpDD, NULL, &ddcaps);
        else
            IDirectDraw_GetCaps(lpDD, &ddcaps, NULL);

        AddCapsToLV((CAPDEF *)lParam2, (LPVOID)&ddcaps);
    }
}

//================================================================
//================================================================
BOOL DDPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DDCreate((GUID*)lParam1))
    {
        DDCAPS ddcaps;

        ddcaps.dwSize = sizeof(ddcaps);

        if (lParam1 == DDCREATE_EMULATIONONLY)
            IDirectDraw_GetCaps(lpDD, NULL, &ddcaps);
        else
            IDirectDraw_GetCaps(lpDD, &ddcaps, NULL);

        // Print DirectDraw caps
        return PrintCapsToDC((CAPDEF *)lParam2, (LPVOID)&ddcaps, lpInfo);
    }

    // DDCreate failed but keep printing
    return TRUE;
}

//================================================================
//================================================================
void DSAddCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DSCreate((GUID*)lParam1))
    {
        DSCAPS dscaps;

        dscaps.dwSize = sizeof(dscaps);

        IDirectSound_GetCaps(lpDS, &dscaps);

        AddCapsToLV((CAPDEF *)lParam2, (LPVOID)&dscaps);
    }
}

//================================================================
//================================================================
BOOL DSPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DSCreate((GUID*)lParam1))
    {
        DSCAPS dscaps;

        dscaps.dwSize = sizeof(dscaps);

        IDirectSound_GetCaps(lpDS, &dscaps);

        // Print DirectSound Caps
        return PrintCapsToDC((CAPDEF *)lParam2, (LPVOID)&dscaps, lpInfo);
    }

    // DSCreate failed but keep printing
    return TRUE;
}

//================================================================
//================================================================
void DSCAddCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DSCCreate((GUID*)lParam1))
    {
        DSCCAPS dsccaps;

        dsccaps.dwSize = sizeof(dsccaps);

        IDirectSoundCapture_GetCaps(lpDSC, &dsccaps);

        AddCapsToLV((CAPDEF *)lParam2, (LPVOID)&dsccaps);
    }
}

//================================================================
//================================================================
BOOL DSCPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DSCCreate((GUID*)lParam1))
    {
        DSCCAPS dsccaps;

        dsccaps.dwSize = sizeof(dsccaps);

        IDirectSoundCapture_GetCaps(lpDSC, &dsccaps);

        // Print DirectSound Caps
        return PrintCapsToDC((CAPDEF *)lParam2, (LPVOID)&dsccaps, lpInfo);
    }

    // DSCCreate failed but keep printing
    return TRUE;
}

//================================================================
//================================================================
void DPAddCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use
HRESULT hr = DPERR_GENERIC;

CAPDEF * pcd = (CAPDEF *) lParam2;

    if ( DPCreate( (GUID*) lParam1) )
    {
        DPCAPSdpcaps,
dpcapsGuar;
DWORDdwValue,
dwValueGuar;

memset(&dpcaps, 0, sizeof(DPCAPS));
memset(&dpcapsGuar, 0, sizeof(DPCAPS));

        dpcaps.dwSize = sizeof(DPCAPS);
        dpcapsGuar.dwSize = sizeof(DPCAPS);

        hr = lpDP3A->lpVtbl->GetCaps(lpDP3A, &dpcapsGuar, DPGETCAPS_GUARANTEED);

if (FAILED(hr))
{
return;
}

        lpDP3A->lpVtbl->GetCaps(lpDP3A, &dpcaps, 0);

if (FAILED(hr))
{
return;
}

LVAddColumn(g_hwndLV, 0, "Name", 24);
LVAddColumn(g_hwndLV, 1, "Non-guaranteed", 16);
LVAddColumn(g_hwndLV, 2, "Guaranteed", 16);

while(pcd->szName && *pcd->szName)
{
dwValue = *((DWORD *)((BYTE *)&dpcaps + pcd->dwOffset));
dwValueGuar = *((DWORD *)((BYTE *)&dpcapsGuar + pcd->dwOffset));

LVAddText(g_hwndLV, 0, pcd->szName, "test");
LVAddText(g_hwndLV, 1, "%d", dwValue);
LVAddText(g_hwndLV, 2, "%d", dwValueGuar);
pcd++;  // Get next Cap bit definition
}

LVAddText( g_hwndLV, 0, "Group Optimized" );
LVAddText( g_hwndLV, 1, ( dpcaps.dwFlags & DPCAPS_GROUPOPTIMIZED ? c_szYes : c_szNo ) );
LVAddText( g_hwndLV, 2, ( dpcapsGuar.dwFlags & DPCAPS_GROUPOPTIMIZED ? c_szYes : c_szNo ) );

LVAddText( g_hwndLV, 0, "Guaranteed Optimized" );
LVAddText( g_hwndLV, 1, ( dpcaps.dwFlags & DPCAPS_GUARANTEEDOPTIMIZED ? c_szYes : c_szNo ) );
LVAddText( g_hwndLV, 2, ( dpcapsGuar.dwFlags & DPCAPS_GUARANTEEDOPTIMIZED ? c_szYes : c_szNo ) );

LVAddText( g_hwndLV, 0, "Guaranteed Supported" );
LVAddText( g_hwndLV, 1, ( dpcaps.dwFlags & DPCAPS_GUARANTEEDSUPPORTED ? c_szYes : c_szNo ) );
LVAddText( g_hwndLV, 2, ( dpcapsGuar.dwFlags & DPCAPS_GUARANTEEDSUPPORTED ? c_szYes : c_szNo) );

LVAddText( g_hwndLV, 0, "Keep Alive Optimized" );
LVAddText( g_hwndLV, 1, ( dpcaps.dwFlags & DPCAPS_KEEPALIVEOPTIMIZED ? c_szYes : c_szNo ) );
LVAddText( g_hwndLV, 2, ( dpcapsGuar.dwFlags & DPCAPS_KEEPALIVEOPTIMIZED ? c_szYes : c_szNo ) );

lpDP3A->lpVtbl->Release(lpDP3A);
lpDP3A = NULL;

    }
}

//================================================================
//================================================================
BOOL FAR PASCAL DPEnumLocalAppsCallback( LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags)
{
LPLOCALAPP * lp = (LPLOCALAPP *) lpContext;
LPLOCALAPP lpTemp = NULL;
WCHAR wszBuffer[128];

if (StringFromGUID2( &(lpAppInfo->guidApplication), wszBuffer, 128 ))
{
char szBuffer[128];

    wcstombs(szBuffer, wszBuffer, wcslen(wszBuffer)+1);
if (g_hwndLV)
{
//when we are called the first time,
//the listview hasn't been created yet.
LVAddText(g_hwndLV, 0, lpAppInfo->lpszAppNameA);
LVAddText(g_hwndLV, 1, szBuffer);
}
}

//Add to our local linked list of app names.
//This list is used to provide the app names when we
//enumerate sessions later.
lpTemp = *lp;
*lp = (LPLOCALAPP) LocalAlloc( LPTR, sizeof( LOCALAPP )+strlen(lpAppInfo->lpszAppNameA)+1 );

if (*lp)
{
(*lp)->guidApplication = lpAppInfo->guidApplication;
strcpy( (*lp)->szAppNameA, lpAppInfo->lpszAppNameA );
(*lp)->lpNext = lpTemp;
}

return TRUE;
}

//================================================================
//================================================================
void DPAddLocalApps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

LPDIRECTPLAYLOBBYAlpDPLA= NULL;
HRESULThr= DPERR_GENERIC;
    if (g_lpLocalApp)
{
FreeLocalAppList();
g_lpLocalApp = NULL;
}

if ( g_hwndLV)
{
//When we are called the first time,
//the list view has not been created yet.
LVAddColumn(g_hwndLV, 0, "Name", 16);
LVAddColumn(g_hwndLV, 1, "guidApplication", 48);
}

    hr = DirectPlayLobbyCreateA(NULL, &lpDPLA, NULL, NULL, 0 );

if (SUCCEEDED(hr))
{
lpDPLA->lpVtbl->EnumLocalApplications(lpDPLA, DPEnumLocalAppsCallback, &g_lpLocalApp, 0 );
lpDPLA->lpVtbl->Release(lpDPLA);
}

}

//================================================================
//================================================================
BOOL FAR PASCAL DPEnumLocalAppsPrintCallback( LPCDPLAPPINFO lpAppInfo, LPVOID lpContext, DWORD dwFlags)
{
    intxName,
xGuid,
yLine;
    PRINTCBINFO * lpInfo = (PRINTCBINFO *) lpContext;
    DWORD cchLen;
WCHAR wszBuffer[128];
char szBuffer[128];

    if (! lpInfo)
        return FALSE;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        // We could reset lpdwTimeOut and return true to continue waiting
        // NOTE: This does not tell DirectPlay to query again for hosts.
        //       This just gives potentially slow hosts more time to respond
        //       to our initial query.
        return FALSE;  // Stop waiting for hosts
    }

    xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
    xGuid   = xName + (24 * lpInfo->dwCharWidth);
    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

    // Print AppInfo information

cchLen = strlen (lpAppInfo->lpszAppNameA);
if (! PrintLine (xName, yLine, lpAppInfo->lpszAppNameA, cchLen, lpInfo))
return FALSE;

if (StringFromGUID2( &(lpAppInfo->guidApplication), wszBuffer, 128 ))
{
    wcstombs(szBuffer, wszBuffer, wcslen(wszBuffer)+1);

if (! PrintLine (xGuid, yLine, szBuffer, strlen(szBuffer), lpInfo))
return FALSE;

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

}

return TRUE;
}

//================================================================
//================================================================
BOOL DPPrintLocalApps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

LPDIRECTPLAYLOBBYAlpDPLA = NULL;

    DirectPlayLobbyCreateA(NULL, &lpDPLA, NULL, NULL, 0 );

if (lpDPLA)
{
lpDPLA->lpVtbl->EnumLocalApplications(lpDPLA,
DPEnumLocalAppsPrintCallback,
(LPVOID)lpInfo,
0 );
lpDPLA->lpVtbl->Release(lpDPLA);
    }

    // DPLobbyCreate failed but keep printing
    return TRUE;
}

//================================================================
//================================================================
BOOL DPPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    if (DPCreate((GUID*)lParam1))
    {
        DPCAPS dpcaps;
BOOLbKeepPrinting;
HRESULThr = DPERR_GENERIC;
int xName,
xGuid,
yLine;

        dpcaps.dwSize = sizeof(dpcaps);
        hr = lpDP3A->lpVtbl->GetCaps(lpDP3A, &dpcaps, 0);

if (FAILED(hr))
{
// GetCaps failed but keep printing
return TRUE;
}

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
xGuid   = xName + (24 * lpInfo->dwCharWidth);
yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

if (! PrintLine (xName, yLine, "Non-guaranteed", 14, lpInfo))
return FALSE;

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

        bKeepPrinting = PrintCapsToDC ((CAPDEF *)lParam2, (LPVOID)&dpcaps, lpInfo);

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;
// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

if (bKeepPrinting)
{
        hr = lpDP3A->lpVtbl->GetCaps(lpDP3A, &dpcaps, DPGETCAPS_GUARANTEED );
if (FAILED(hr))
{
// GetCaps failed but keep printing
return TRUE;
}

xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
xGuid   = xName + (24 * lpInfo->dwCharWidth);
yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

if (! PrintLine (xName, yLine, "Guaranteed", 10, lpInfo))
return FALSE;

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;

// Print DirectPlay caps
bKeepPrinting = PrintCapsToDC ((CAPDEF *)lParam2, (LPVOID)&dpcaps, lpInfo);

// Advance printer one line
if (! PrintNextLine (lpInfo))
return FALSE;
}

return bKeepPrinting;
    }

    // DPCreate failed but keep printing
    return TRUE;
}

//================================================================
//================================================================
void D3AddCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the CAP3DDEVICEDESC Struct
    // lParam2 is the CAPDEF table we should use

    // Unlike other AddCaps function this info has been prethought for us
    // so just print it out.
    AddCapsToLV((CAPDEF *)lParam2, (LPVOID)lParam1);
}

//================================================================
//================================================================
void D3AddLineCaps(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the CAP3DDEVICEDESC Struct
    // lParam2 is the CAPDEF table we should use

    CAP3DDEVICEDESC *p = (CAP3DDEVICEDESC*)lParam1;
    D3DPRIMCAPS     save;

    // slam the line caps over the triangle caps so we can use the
    // same tables
    save = p->d3dDeviceDesc.dpcTriCaps;
    p->d3dDeviceDesc.dpcTriCaps = p->d3dDeviceDesc.dpcLineCaps;
    AddCapsToLV((CAPDEF *)lParam2, (LPVOID)lParam1);
    p->d3dDeviceDesc.dpcTriCaps = save;
}

//================================================================
//================================================================

int BitCount(DWORD dw)
{
    int i;

    for (i=0; dw; dw=dw>>1)
        i += (dw & 1);

    return i;
}

//================================================================
//================================================================
HRESULT CALLBACK EnumTextureCallback(DDSURFACEDESC *pddsd, LPVOID lParam)
{
    DDPIXELFORMAT ddpf = pddsd->ddpfPixelFormat;
    TCHAR ach[80];

    if (ddpf.dwRBitMask == 0 && ddpf.dwGBitMask == 0 && ddpf.dwBBitMask == 0)
        wsprintf(ach, "%d bit", ddpf.dwRGBBitCount);
    else if (BitCount(ddpf.dwRGBAlphaBitMask) == 0)
        wsprintf(ach, "%d bit %d%d%d",
            ddpf.dwRGBBitCount,
            BitCount(ddpf.dwRBitMask),
            BitCount(ddpf.dwGBitMask),
            BitCount(ddpf.dwBBitMask));
    else
        wsprintf(ach, "%d bit %d%d%d%d",
            ddpf.dwRGBBitCount,
            BitCount(ddpf.dwRBitMask),
            BitCount(ddpf.dwGBitMask),
            BitCount(ddpf.dwBBitMask),
            BitCount(ddpf.dwRGBAlphaBitMask));

    LVAddText(g_hwndLV, 0, ach);

    ach[0] = 0;

    if (ddpf.dwFlags & DDPF_RGB)
        lstrcat(ach,"RGB ");
    if (ddpf.dwFlags & DDPF_YUV)
        lstrcat(ach,"YUV ");
    if (ddpf.dwFlags & DDPF_PALETTEINDEXED8)
        lstrcat(ach,"PAL8 ");
    if (ddpf.dwFlags & DDPF_PALETTEINDEXED4)
        lstrcat(ach,"PAL4 ");
    if (ddpf.dwFlags & DDPF_ALPHA)
        lstrcat(ach,"ALPHA ");
    if (ddpf.dwFlags & DDPF_ALPHAPIXELS)
        lstrcat(ach,"ALPHAPIXELS ");

    LVAddText(g_hwndLV, 1, ach);

    return DDENUMRET_OK;
}

//================================================================
//================================================================

void D3AddTexF(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the CAP3DDEVICEDESC Struct
    // lParam2 is 0

    CAP3DDEVICEDESC *p = (CAP3DDEVICEDESC*)lParam1;

    //
    // this is going to be tricky
    // to enum the texture formats we are going to need
    // a Direct3DDevice, to get a device we need a surface
    // to get a surface we might need to chage the video mode!
    // also to create a device we will need a palette in 8 bpp modes!
    // mabey we should just use the primary surface?
    //
    if (DDCreate(p->device) && lp3D)
    {
        IDirectDrawSurface *Surface=NULL;
        IDirectDrawPalette *Palette=NULL;
        IDirect3DDevice2   *Device=NULL;

        DDSURFACEDESC ddsd;
        ZeroMemory(&ddsd, sizeof(ddsd));

        ddsd.dwSize = sizeof(ddsd);
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE; // | DDSCAPS_VIDEOMEMORY;

        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_NOWINDOWCHANGES);
        IDirectDraw_CreateSurface(lpDD, &ddsd, &Surface, NULL);
        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_NORMAL);

        if (Surface)
        {
            //
            // before we can create a 3D device, we need to
            // attach a palette (8 bpp modes only)
            //
            IDirectDrawSurface_GetSurfaceDesc(Surface, &ddsd);

            if (ddsd.ddpfPixelFormat.dwRGBBitCount == 8)
            {
                HDC hdc;
                PALETTEENTRY ape[256];

                hdc = GetDC(NULL);
                GetSystemPaletteEntries(hdc, 0, 256, ape);
                ReleaseDC(NULL, hdc);

                IDirectDraw_CreatePalette(lpDD, DDPCAPS_8BIT, ape, &Palette, NULL);

                if (Palette)
                {
                    IDirectDrawSurface_SetPalette(Surface, Palette);
                }
            }
        }

        if (Surface)
        {
            IDirect3D2_CreateDevice(lp3D, &p->driver, Surface, &Device);

            if (Device == NULL)
            {
                IDirectDrawSurface_Release(Surface);
                Surface = NULL;

                //
                // we cant create a 3d device for the primary, create a
                // 555 offscreen surface and try that
                //
                ddsd.dwSize = sizeof(ddsd);
                ddsd.dwFlags = DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_HEIGHT | DDSD_WIDTH;
                ddsd.dwWidth = 100;
                ddsd.dwHeight = 100;
                ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE | DDSCAPS_VIDEOMEMORY;
                ddsd.ddpfPixelFormat.dwSize            = sizeof(DDPIXELFORMAT);
                ddsd.ddpfPixelFormat.dwFlags           = DDPF_RGB;
                ddsd.ddpfPixelFormat.dwFourCC          = 0;
                ddsd.ddpfPixelFormat.dwRGBBitCount     = 16;
                ddsd.ddpfPixelFormat.dwRBitMask        = 0x7C00; // 0xF800;
                ddsd.ddpfPixelFormat.dwGBitMask        = 0x03E0; // 0x07E0;
                ddsd.ddpfPixelFormat.dwBBitMask        = 0x001F; // 0x001F;
                ddsd.ddpfPixelFormat.dwRGBAlphaBitMask = 0x0000;

                IDirectDraw_CreateSurface(lpDD, &ddsd, &Surface, NULL);

                if (Surface != NULL)
                {
                    IDirect3D2_CreateDevice(lp3D, &p->driver, Surface, &Device);
                }
            }

            if (Device)
            {
                LVAddColumn(g_hwndLV, 0, "Format", 24);
                LVAddColumn(g_hwndLV, 1, "Flags", 24);

                IDirect3DDevice2_EnumTextureFormats(Device, EnumTextureCallback, NULL);
                IDirect3DDevice2_Release(Device);
            }
            else
            {
                LVAddColumn(g_hwndLV, 0, "Message", 50);
                LVAddText(g_hwndLV, 0, "Cant create D3D Device, change display mode.");
            }

            if (Surface)
            {
                IDirectDrawSurface_Release(Surface);
            }

            if (Palette)
            {
                IDirectDrawPalette_Release(Palette);
            }
        }
        else
        {
            LVAddColumn(g_hwndLV, 0, "Message", 50);
            LVAddText(g_hwndLV, 0, "Cant create surface.");
        }
    }
}

//================================================================
//================================================================
BOOL D3PrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the CAP3DDEVICEDESC Struct
    // lParam2 is the CAPDEF table we should use

    // Unlike other PrintCaps function this info has been prethought for us
    // so just print it out.
    return PrintCapsToDC ((CAPDEF *)lParam2, (LPVOID)lParam1, lpInfo);
}

//================================================================
// EnumSessionsCallback
//================================================================
BOOL PASCAL EnumSessionsCallback
(
LPCDPSESSIONDESC2 lpDesc,         // Pointer to Session Description Struct
LPDWORD lpdwTimeOut,          // Used to extend the timeout if hosts aren't responding quickly enough.
DWORD dwFlags,                 // Flags (used to notify us when we've timed out).
LPVOIDlpContext
)
{
BOOLbDataAvailable;
charszBuffer[128];
DWORDdwSize= 128;
LPLV_INSTANCEGUIDSTRUCTlpInstGuidStruct = NULL;
LV_ITEMlvi;

inti, count;
BOOLbGuidNotFound;

memset( &lvi, 0, sizeof( LV_ITEM ));

    if (dwFlags & DPESC_TIMEDOUT)
    {
        // We could reset lpdwTimeOut and return true to continue waiting
        // NOTE: This does not tell DirectPlay to query again for hosts.
        //       This just gives potentially slow hosts more time to respond
        //       to our initial query.
        return FALSE;  // Stop waiting for hosts
    }

i = 0;
count = ListView_GetItemCount( g_hwndLV );
bGuidNotFound = TRUE;

while ((bGuidNotFound) && ( i < count))
{
lvi.iItem = i;
lvi.mask = LVIF_PARAM;
lvi.lParam = 0;

bDataAvailable =ListView_GetItem( g_hwndLV, &lvi );

if (bDataAvailable)
{
lpInstGuidStruct = (LPLV_INSTANCEGUIDSTRUCT) lvi.lParam;

if (lpInstGuidStruct)
{
if ( IsEqualGUID(&(lpDesc->guidInstance),
&(lpInstGuidStruct->guidInstance) ) )
{
//This session is already in our list. Mark that it is active,
//so it doesn't get deleted when the next timer fires.
lpInstGuidStruct->dwRefresh = TRUE;
bGuidNotFound = FALSE;
}
}
else
{
//This item has no pointer to a guidInstance, it must be invalid.
ListView_DeleteItem( g_hwndLV, i);
}
}
else
{
//This item has no data, so it must be invalid.
ListView_DeleteItem( g_hwndLV, i);
}

i++;
}

    if (bGuidNotFound)
{
// This session was in our list view.
// Add new session information to table
i = LVAddText(g_hwndLV, 0, ( lpDesc->lpszSessionNameA ? lpDesc->lpszSessionNameA : "<NO SESSION NAME>" ) );

GetAppName(szBuffer, &(lpDesc->guidApplication), dwSize);
LVAddText(g_hwndLV, 1, szBuffer);

LVAddText(g_hwndLV, 2, "%d", lpDesc->dwMaxPlayers);
LVAddText(g_hwndLV, 3, "%d", lpDesc->dwCurrentPlayers);
LVAddText(g_hwndLV, 4, (lpDesc->dwFlags&DPSESSION_SECURESERVER ? c_szYes : c_szNo ) );
LVAddText(g_hwndLV, 5, (lpDesc->dwFlags&DPSESSION_PASSWORDREQUIRED ? c_szYes : c_szNo ));

//Keep track of our guidInstance so we'll recognize this session
//when we enumerate later
lpInstGuidStruct = (LPLV_INSTANCEGUIDSTRUCT) LocalAlloc( LPTR, sizeof(LV_INSTANCEGUIDSTRUCT));

if (lpInstGuidStruct)
{
LV_ITEMlvi;
memset( &lvi, 0, sizeof( LV_ITEM));
lpInstGuidStruct->guidInstance = lpDesc->guidInstance;
lpInstGuidStruct->dwRefresh = TRUE;

lvi.iItem = i;
lvi.mask = LVIF_PARAM;
lvi.lParam = (long) lpInstGuidStruct;

ListView_SetItem( g_hwndLV, (LPARAM)&lvi, );
}
}

    //Keep enumerating until it times out on its own.
return TRUE;
}

//================================================================
// EnumPrintSessionsCallback
//================================================================
BOOL PASCAL EnumPrintSessionsCallback
(
    LPCDPSESSIONDESC2 lpDesc,         // Pointer to Session Description Struct
    LPDWORD         lpdwTimeOut,    // Used to extend the timeout if hosts aren't responding quickly enough.
    DWORD           dwFlags,         // Flags (used to notify us when we've timed out).
    LPVOID          lpContext         // User definable data passed in from EnumSessions() call.
)
{
    int xName,  yLine;
    PRINTCBINFO * lpInfo = (PRINTCBINFO *) lpContext;
    CHAR szBuff[256],
 szAppName[128];
    DWORD cchLen;

    if (! lpInfo)
        return FALSE;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        // We could reset lpdwTimeOut and return true to continue waiting
        // NOTE: This does not tell DirectPlay to query again for hosts.
        //       This just gives potentially slow hosts more time to respond
        //       to our initial query.
        return FALSE;  // Stop waiting for hosts
    }

    xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

    // Print session information
    wsprintf (szBuff, "Session Name   : %s", lpDesc->lpszSessionNameA);
    cchLen = strlen (szBuff);
    if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);
GetAppName( szAppName, &(lpDesc->guidApplication), 128 );
    wsprintf (szBuff, "App Name/GUID  : %s", szAppName);
cchLen = strlen (szBuff);
    if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);
    wsprintf (szBuff, "Max Players    : %d", lpDesc->dwMaxPlayers);
    cchLen = strlen (szBuff);
    if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);
    wsprintf (szBuff, "Cur Players    : %d", lpDesc->dwCurrentPlayers);
    cchLen = strlen (szBuff);
if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);
 wsprintf (szBuff, "Secure Session : %s",(lpDesc->dwFlags&DPSESSION_SECURESERVER ? c_szYes : c_szNo ) );
    cchLen = strlen (szBuff);
if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

    yLine   = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);
 wsprintf (szBuff, "Password Req'd : %s",(lpDesc->dwFlags&DPSESSION_PASSWORDREQUIRED  ? c_szYes : c_szNo ) );
    cchLen = strlen (szBuff);
if (! PrintLine (xName, yLine, szBuff, cchLen, lpInfo))
        return FALSE;
    if (! PrintNextLine (lpInfo))
        return FALSE;

// Advance printer one line
    if (! PrintNextLine (lpInfo))
        return FALSE;

    // Success
    return TRUE;
}

//================================================================
//================================================================
void DPAddSessions(LPARAM lParam1, LPARAM lParam2)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use
HRESULThr = DPERR_GENERIC;

    if (DPCreate((GUID*)lParam1))
    {
        LVAddColumn(g_hwndLV, 0, "Session Name", 16);
        LVAddColumn(g_hwndLV, 1, "Application Name/GUID", 24);
        LVAddColumn(g_hwndLV, 2, "Max", 4);
        LVAddColumn(g_hwndLV, 3, "Cur", 4);
        LVAddColumn(g_hwndLV, 4, "Sec", 4);
        LVAddColumn(g_hwndLV, 5, "Pwd", 5);

        hr = EnumSessions(lpDP3A );
if (SUCCEEDED(hr))
{
g_idTimer = SetTimer(g_hwndMain, 1, TIMER_PERIOD, NULL);
}
    }
}

//================================================================
//================================================================
BOOL DPPrintSessions(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    // lParam1 is the GUID for the driver we should open
    // lParam2 is the CAPDEF table we should use

    DPSESSIONDESC2   dps;
    HCURSOR         hCur=NULL;
HRESULThr = DPERR_GENERIC;

    memset(&dps, 0, sizeof(DPSESSIONDESC2));
    dps.dwSize = sizeof(DPSESSIONDESC2);

    hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

    if (DPCreate((GUID*)lParam1))
    {
        hr = lpDP3A->lpVtbl->EnumSessions (lpDP3A, &dps, 0, EnumPrintSessionsCallback,
(LPVOID)lpInfo,
DPENUMSESSIONS_ALL|
DPENUMSESSIONS_PASSWORDREQUIRED );

    }

    if (hCur)
        SetCursor(hCur);

    return TRUE;
}

//================================================================
//================================================================
typedef struct LLMode
{
    DWORD x,y,bpp;
        BOOL IsModeX;
        struct LLMode *Next;
}LinkMode;

static LinkMode *pModesHead = NULL;

void DDFourCCFormat(LPARAM lParam1, LPARAM lParam2)
{
    HRESULT ddrval;
    int iNumOfCodes,iCount;
    DWORD *FourCC;
    char szText[5]={0,0,0,0,0};

    if(lpDD != NULL)
    {
        ddrval = IDirectDraw_GetFourCCCodes(lpDD,&iNumOfCodes, NULL);
        LVAddColumn(g_hwndLV, 0, "Codes", 24);
        LVAddColumn(g_hwndLV, 1, "", 24);
        if( ddrval == DD_OK)
        {
            FourCC = GlobalAlloc(GPTR,(sizeof(DWORD)*iNumOfCodes));
            if(FourCC)
            {
                ddrval = IDirectDraw_GetFourCCCodes(lpDD,&iNumOfCodes, FourCC);
                // Assume all FourCC values are ascii strings
                for(iCount = 0;iCount < iNumOfCodes; iCount++)
                {
                    memcpy(szText,&FourCC[iCount],4);
                    LVAddText(g_hwndLV, 0, "%s", szText);
                }
            }
        }
    }
}

//================================================================
//================================================================
BOOL DDPrintFourCCFormat(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    HRESULT ddrval;
    int iNumOfCodes,iCount;
    DWORD *FourCC;
    char szText[5]={0,0,0,0,0};
    int  xCode, yLine;

    if(lpDD != NULL)
    {
        ddrval = IDirectDraw_GetFourCCCodes(lpDD,&iNumOfCodes, NULL);
        if( ddrval == DD_OK)
        {
            FourCC = GlobalAlloc(GPTR,(sizeof(DWORD)*iNumOfCodes));
            if(FourCC)
            {
                xCode  = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
                ddrval = IDirectDraw_GetFourCCCodes(lpDD,&iNumOfCodes, FourCC);
                if (ddrval == DD_OK)
                {
                    // Assume all FourCC values are ascii strings
                    for(iCount = 0;iCount < iNumOfCodes; iCount++)
                    {
                        yLine  = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

                        memcpy(szText,&FourCC[iCount],4);

                        // Print Code
                        if (! PrintLine (xCode, yLine, szText, 4, lpInfo))
                            return FALSE;

                        if (! PrintNextLine (lpInfo))
                            return FALSE;
                    }
                }
            }
        }
    }

    // Success
    return TRUE;
}

//================================================================
//================================================================

//================================================================
// DisplayEnumModes
//================================================================
HRESULT CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID Context)
{
    if(pddsd->ddsCaps.dwCaps & DDSCAPS_STANDARDVGAMODE)
    {
        LVAddText(g_hwndLV, 0, "%dx%dx%d (StandardVGA)", pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }else if(pddsd->ddsCaps.dwCaps & DDSCAPS_MODEX)
    {
        LVAddText(g_hwndLV, 0, "%dx%dx%d (ModeX)", pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }else
    {
        LVAddText(g_hwndLV, 0, "%dx%dx%d ", pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }
    return DDENUMRET_OK;
} /* EnumModesCallback */

HRESULT CALLBACK EnumDisplayModesCallbackPrint(LPDDSURFACEDESC pddsd, LPVOID Context)
{
    TCHAR szBuff[80];
    DWORD cchLen;
    PRINTCBINFO * lpInfo = Context;
    int xMode, yLine;

    if (! lpInfo)
        return FALSE;

    xMode = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
    yLine = (lpInfo->dwCurrLine * lpInfo->dwLineHeight);

    if(pddsd->ddsCaps.dwCaps & DDSCAPS_STANDARDVGAMODE)
    {
        wsprintf(szBuff, TEXT("%dx%dx%d (StandardVGA)"), pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }else if(pddsd->ddsCaps.dwCaps & DDSCAPS_MODEX)
    {
        wsprintf(szBuff, TEXT("%dx%dx%d (ModeX)"), pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }else
    {
        wsprintf(szBuff, TEXT("%dx%dx%d "), pddsd->dwWidth, pddsd->dwHeight, pddsd->ddpfPixelFormat.dwRGBBitCount);
    }
    // Print Mode Info
    cchLen = _tcslen (szBuff);
    if (! PrintLine (xMode, yLine, szBuff, cchLen, lpInfo))
        return DDENUMRET_CANCEL;
    // Advance to next line
    if (! PrintNextLine (lpInfo))
        return DDENUMRET_CANCEL;

return DDENUMRET_OK;
} /* EnumModesCallback */

//================================================================
// PrintEnumModes
//================================================================
/*BOOL PrintEnumModes ()
{
    LinkMode *tmp= NULL;

    tmp = pModesHead;
    while (tmp != NULL) //cycle though all modes since unique modes don't always happen
    {

        if (tmp->IsModeX)
            wsprintf (szBuff, TEXT("%dx%dx%d (ModeX)"), tmp->x, tmp->y, tmp->bpp);
        else
            wsprintf (szBuff, TEXT("%dx%dx%d "), tmp->x, tmp->y, tmp->bpp);

        tmp = tmp->Next;
    }

    // Success
    return TRUE;
}*/ /* EnumModesCallback */

//================================================================
// Should we hourglass the cursor? this takes a while
//================================================================
void DDAddVideoModes(LPARAM lParam1, LPARAM lParam2)
{
    DWORD mode;
    DDSURFACEDESC ddsd;

    LVAddColumn(g_hwndLV, 0, "Mode", 24);
    LVAddColumn(g_hwndLV, 1, "", 24);

    // lParam1 is the GUID for the driver we should open
    // lParam2 is not used

    if (DDCreate((GUID*)lParam1))
    {
        // Get the current mode mode for this driver
        ddsd.dwSize = sizeof(DDSURFACEDESC);
        IDirectDraw_GetDisplayMode(lpDD, &ddsd);

        mode = MAKEMODE(ddsd.dwWidth, ddsd.dwHeight, ddsd.ddpfPixelFormat.dwRGBBitCount);

        // Find all mode for this driver and add them to the listview
        //Erase previous list
        {
            LinkMode *tmp, *tmp1;

            tmp = pModesHead;
            while(tmp != NULL)
            {
                tmp1 = tmp;
                tmp = tmp->Next;
                GlobalFree(tmp1);  //Note We will exit with a list in place
            }
            pModesHead = NULL;
        }
        //Get Modes
        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWMODEX | DDSCL_NOWINDOWCHANGES);
        IDirectDraw_EnumDisplayModes(lpDD, DDEDM_STANDARDVGAMODES, NULL, (LPVOID)mode, EnumDisplayModesCallback);
        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_NORMAL);
    }
}

//================================================================
// Should we hourglass the cursor? this takes a while
//================================================================
BOOL DDPrintVideoModes(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo)
{
    BOOL  fResult = TRUE;   // Even if we can't enum modes, let's keep on printing other stuff
    DWORD mode;
    DDSURFACEDESC ddsd;

    LVAddColumn(g_hwndLV, 0, "Mode", 24);
    LVAddColumn(g_hwndLV, 1, "", 24);

    // lParam1 is the GUID for the driver we should open
    // lParam2 is not used

    if (DDCreate((GUID*)lParam1))
    {
        // Get the current mode mode for this driver
        ddsd.dwSize = sizeof(DDSURFACEDESC);
        IDirectDraw_GetDisplayMode(lpDD, &ddsd);

        mode = MAKEMODE(ddsd.dwWidth, ddsd.dwHeight, ddsd.ddpfPixelFormat.dwRGBBitCount);

        // Find all mode for this driver and add them to the listview
        //Erase previous list
        {
            LinkMode *tmp, *tmp1;

            tmp = pModesHead;
            while(tmp != NULL)
            {
                tmp1 = tmp;
                tmp = tmp->Next;
                GlobalFree(tmp1);  //Note We will exit with a list in place
            }
            pModesHead = NULL;
        }

        //Get Mode with ModeX
        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWMODEX | DDSCL_NOWINDOWCHANGES);
        IDirectDraw_EnumDisplayModes(lpDD, DDEDM_STANDARDVGAMODES, NULL, (LPVOID)lpInfo, EnumDisplayModesCallbackPrint);
        IDirectDraw_SetCooperativeLevel(lpDD, g_hwndMain, DDSCL_NORMAL);
    }

    // Success
    return fResult;
} // End DDPrintVideoModes

//================================================================
//================================================================
void DXView_OnTreeSelect(HWND hwndTV, NM_TREEVIEW *ptv)
{
    NODEINFO *pni;

    SendMessage(g_hwndLV, WM_SETREDRAW, FALSE, 0);
    LVDeleteAllItems(g_hwndLV);
    LVAddColumn(g_hwndLV, 0, "", 0);

    if (ptv == NULL)
    {
        TV_ITEM tvi;
        // get lParam of current tree node
        tvi.hItem  = TreeView_GetSelection(g_hwndTV);
        tvi.mask   = TVIF_PARAM;
        tvi.lParam = 0;
        TreeView_GetItem(g_hwndTV, &tvi);
        pni = (NODEINFO*)tvi.lParam;
    }
    else
    {
        pni = (NODEINFO*)ptv->itemNew.lParam;
    }

    if (pni && pni->Callback)
    {
        pni->Callback(pni->lParam1, pni->lParam2);
    }

    SendMessage(g_hwndLV, WM_SETREDRAW, TRUE, 0);
    InvalidateRect(g_hwndLV, NULL, TRUE);
}

//================================================================
//================================================================
void DXView_OnListViewDblClick(HWND hwndLV, NM_LISTVIEW *plv)
{
    LV_ITEM lvi;

    lvi.mask   = LVIF_PARAM;
    lvi.lParam = 0;
    lvi.iItem  = plv->iItem;
    ListView_GetItem(hwndLV, &lvi);
}

//================================================================
//================================================================
void DXView_OnCommand(HWND hwnd, WPARAM wParam)
{
    HMENU hMenu;
    BOOL  fPrintAll;

    switch(LOWORD(wParam))
    {
        case IDM_VIEWAVAIL:
        case IDM_VIEWALL:
            hMenu = GetMenu(hwnd);
            CheckMenuItem(hMenu, g_dwViewState, MF_BYCOMMAND | MF_UNCHECKED);
            g_dwViewState = LOWORD(wParam);
            CheckMenuItem(hMenu, g_dwViewState, MF_BYCOMMAND | MF_CHECKED);
            DXView_OnTreeSelect(g_hwndTV, NULL);
            break;

        case IDM_PRINT:
        case IDM_PRINTALL:
            // Print all statistics or just current selection
            if (LOWORD(wParam) == IDM_PRINTALL)
                fPrintAll = TRUE;
            else
                fPrintAll = FALSE;
            // Print out statistics
            DXView_OnPrint (hwnd, g_hwndTV, fPrintAll);
            break;

        case IDM_PRINTTOFILE:
                fPrintAll = TRUE;
                g_PrintToFile = TRUE;
            // Print out statistics
            DXView_OnFile (hwnd, g_hwndTV, fPrintAll);
            break;

        case IDM_ABOUT:
            DialogBox(g_hInstance, "About", hwnd, (DLGPROC)About);
            break;

        case IDM_EXIT:
            PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;
    }
}

//================================================================

//================================================================
void DXView_Cleanup()
{
    if (lp3D)
        IDirect3D2_Release(lp3D);

    if (lpDD)
        IDirectDraw_Release(lpDD);

    if (lpDS)
        IDirectSound_Release(lpDS);

    if (lpDSC)
        IDirectSoundCapture_Release(lpDSC);

    if (lpDP3A)
{
        lpDP3A->lpVtbl->Release(lpDP3A);
lpDP3A = NULL;
}

    if(g_hImageList)
        ImageList_Destroy(g_hImageList);
}

//================================================================
//================================================================
BOOL DXView_InitImageList()
{
    int cxSmIcon;
    int cySmIcon;
    UINT Index;
    HICON hIcon;

    if (g_hImageList)
        return TRUE;

    cxSmIcon = GetSystemMetrics(SM_CXSMICON);
    cySmIcon = GetSystemMetrics(SM_CYSMICON);

    //  First, create the image list that is needed.
    if((g_hImageList = ImageList_Create(cxSmIcon, cySmIcon, TRUE, IDI_LASTIMAGE - IDI_FIRSTIMAGE, 10)) == NULL)
        return(FALSE);

    //
    //  Initialize the image list with all of the icons that we'll be using
    //  Once set, send its handle to all interested child windows.
    //
    for (Index = IDI_FIRSTIMAGE; Index <= IDI_LASTIMAGE; Index++)
    {
        hIcon = LoadImage(g_hInstance, MAKEINTRESOURCE(Index), IMAGE_ICON, cxSmIcon, cySmIcon, 0);
        ImageList_AddIcon(g_hImageList, hIcon);
        DestroyIcon(hIcon);
    }

    TreeView_SetImageList(g_hwndTV, g_hImageList, TVSIL_NORMAL);

    return(TRUE);
}

//================================================================
//================================================================
HRESULT CALLBACK DDEnumCallBack(GUID *pid, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    TCHAR szText[256];

    if (HIWORD(pid) != 0)
    {
        GUID temp = *pid;
        pid = (GUID *)LocalAlloc(LPTR, sizeof(GUID));
        *pid = temp;
    }

    // Add subnode to treeview
    if (lpDriverName && *lpDriverName)
        wsprintf(szText, "%s (%s)", lpDriverDesc, lpDriverName);
    else
        lstrcpy(szText, lpDriverDesc);

    DDCapDefs[0].szName = szText;
    AddCapsToTV(hParent, DDCapDefs, (LPARAM)pid);

    return(DDENUMRET_OK);
}

//================================================================
//================================================================
HRESULT CALLBACK DSEnumCallBack(const GUID *lpGUID, LPCSTR lpDriverDesc, LPCSTR lpDriverName, LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    TCHAR     szText[256];
    LPGUID    lpTemp = NULL;

    if( lpGUID != NULL )
        {
        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
        return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));
    }

    // Add subnode to treeview
    if (lpDriverName && *lpDriverName)
        wsprintf(szText, "%s (%s)", lpDriverDesc, lpDriverName);
    else
        lstrcpy(szText, lpDriverDesc);

    DSCapDefs[0].szName = szText;
    AddCapsToTV(hParent, DSCapDefs, (LPARAM)lpTemp);

    return(DDENUMRET_OK);
}

//================================================================
//================================================================
HRESULT CALLBACK DSCEnumCallBack(const GUID *lpGUID, LPCSTR lpDriverDesc, LPCSTR lpDriverName, LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    TCHAR     szText[256];
    LPGUID    lpTemp = NULL;

    if( lpGUID != NULL )
    {
        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )
        return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));
    }

    // Add subnode to treeview
    if (lpDriverName && *lpDriverName)
        wsprintf(szText, "%s (%s)", lpDriverDesc, lpDriverName);
    else
        lstrcpy(szText, lpDriverDesc);

    DSCCapDefs[0].szName = szText;
    AddCapsToTV(hParent, DSCCapDefs, (LPARAM)lpTemp);

    return(DDENUMRET_OK);
}

//================================================================
//================================================================
BOOL CALLBACK DPEnumCallback(GUID *pid, LPSTR szName, DWORD major_ver, DWORD minor_ver, LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    TCHAR szText[256];

    if (HIWORD(pid) != 0)
    {
        GUID temp = *pid;
        pid = (GUID *)LocalAlloc(LPTR, sizeof(GUID));
        *pid = temp;
    }

    wsprintf(szText, "%s %d.%d", szName, major_ver, minor_ver);

    DPCapDefs[0].szName = szText;
    AddCapsToTV(hParent, DPCapDefs, (LPARAM)pid);

    return TRUE;
}

//================================================================
//================================================================
HRESULT CALLBACK D3EnumCallback(
            LPGUID pid,
            LPSTR lpDriverDesc,
            LPSTR lpDriverName,
            LPD3DDEVICEDESC lpD3DDeviceDesc1,
            LPD3DDEVICEDESC lpD3DDeviceDesc2,
            LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    TCHAR szText[256];

    //Store this info now is much easier than recreating it later.
    CAP3DDEVICEDESC *temp;
    if(lpD3DDeviceDesc1->dwFlags != 0)
    {
        temp = LocalAlloc(LPTR, sizeof(CAP3DDEVICEDESC));
        memcpy(temp,lpD3DDeviceDesc1,sizeof(CAP3DDEVICEDESC));
    }else
    {
        temp = LocalAlloc(LPTR, sizeof(CAP3DDEVICEDESC));
        memcpy(temp,lpD3DDeviceDesc2,sizeof(CAP3DDEVICEDESC));
    }
    temp->device = ddid;
    memcpy(&temp->driver,pid,sizeof(GUID));

    // Add subnode to treeview
    if (lpDriverName && *lpDriverName)
        wsprintf(szText, "%s (%s)", lpDriverDesc, lpDriverName);
    else
        lstrcpy(szText, lpDriverDesc);

    D3CapDefs[0].szName = szText;
    AddCapsToTV(hParent, D3CapDefs, (LPARAM)temp);

    return(DDENUMRET_OK);
}

HRESULT CALLBACK DDEnumCallBack3D(GUID *pid, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext)
{
    HTREEITEM hParent = (HTREEITEM)lpContext;
    HTREEITEM hTree;
    TCHAR szText[256];

    if (HIWORD(pid) != 0)
    {
        GUID temp = *pid;
        pid = (GUID *)LocalAlloc(LPTR, sizeof(GUID));
        *pid = temp;
    }

    // Add subnode to treeview
    if (lpDriverName && *lpDriverName)
        wsprintf(szText, "%s (%s)", lpDriverDesc, lpDriverName);
    else
        lstrcpy(szText, lpDriverDesc);

    hTree = TVAddNode(hParent,szText,TRUE,IDI_CAPS, NULL, 0, 0, NULL);

    if (DDCreate(pid) && lp3D)
    {
        IDirect3D2_EnumDevices(lp3D, D3EnumCallback, hTree);
    }

    return(DDENUMRET_OK);
}

//================================================================
//================================================================
void DXView_FillTree(HWND hwndTV)
{
    HTREEITEM   hTree, hTreeSubNode;

    PFN_DSENUMERATE pfn_DSEnumerate;
    PFN_DSCENUMERATE pfn_DSCEnumerate;

    // Add direct draw devices

    hTree = TVAddNode(TVI_ROOT, "DirectDraw Devices", TRUE,
                      IDI_DIRECTX, NULL, 0, 0, NULL);

    // Add Display Driver node(s) and capability nodes to treeview
    DirectDrawEnumerate(DDEnumCallBack, hTree);

    // Add Hardware Emulation Layer node to treeview
    DDEnumCallBack((GUID *)DDCREATE_EMULATIONONLY, "Hardware Emulation Layer", NULL, (LPVOID)hTree);

    TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);
    TreeView_SelectItem(g_hwndTV, hTree);

    // Add direct 3D devices
    hTree = TVAddNode(TVI_ROOT, "Direct3D Devices", TRUE,
                      IDI_DIRECTX, NULL, 0, 0, NULL);
    DirectDrawEnumerate(DDEnumCallBack3D, hTree);

    TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);

    pfn_DSEnumerate = (PFN_DSENUMERATE)GetProcAddress(g_hDSoundLib, "DirectSoundEnumerateA");
    if (pfn_DSEnumerate == NULL)
    {
        MessageBox(g_hwndMain, "DSOUND.DLL does not implement DirectSoundEnumerate()", g_szAppName, MB_OK);
    }
    else
    {
        // Add direct sound devices
        hTree = TVAddNode(TVI_ROOT, "DirectSound Devices", TRUE,
                                          IDI_DIRECTX, NULL, 0, 0, NULL);

        (*pfn_DSEnumerate)(DSEnumCallBack, hTree);

        TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);
    }

    // Get a pointer to the DirectSoundCaptureEnumerate
    pfn_DSCEnumerate = (PFN_DSCENUMERATE)GetProcAddress(g_hDSoundLib, "DirectSoundCaptureEnumerateA");
    if (pfn_DSCEnumerate == NULL)
    {
        MessageBox(g_hwndMain, "DSOUND.DLL does not implement DirectSoundCaptureEnumerate(). The system may not have DirectX 5.0 installed.", g_szAppName, MB_OK);
    }
    else
    {
        // Add direct sound capture devices
        hTree = TVAddNode(TVI_ROOT, "DirectSoundCapture Devices", TRUE,
                                                    IDI_DIRECTX, NULL, 0, 0, NULL);
        (*pfn_DSCEnumerate)(DSCEnumCallBack, hTree);
        TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);
    }

    // Add direct play devices

    hTree = TVAddNode(TVI_ROOT, "DirectPlay Devices", TRUE,
                      IDI_DIRECTX, NULL, 0, 0, NULL);

    hTreeSubNode = TVAddNode(hTree, "Connections", TRUE,
                      IDI_CAPS, NULL, 0, 0, NULL);
    TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);

    DirectPlayEnumerate(DPEnumCallback, hTreeSubNode);
    TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);

    AddCapsToTV(hTree, DPLocalAppsDef, 0);
    TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);

    //
    //  DirectInput goo is in dinput.c
    //
    DI_FillTree();
}

//================================================================
//  About - process about box
//================================================================
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_INITDIALOG:
            return(TRUE);

        case WM_COMMAND:                      // message: received a command
            if(LOWORD(wParam) == IDOK        // "OK" box selected?
            || LOWORD(wParam) == IDCANCEL) {  // System menu close command?
                EndDialog(hDlg, TRUE);        // Exit the dialog
                return(TRUE);
            }
            break;
    }
    return(FALSE); // Didn't process the message
}

//================================================================
//================================================================
//  DXView_OnSize
//
//  DESCRIPTION:
//     Called whenever the size of the app window has changed or the size
//     of its child controls should be adjusted.
//
//  PARAMETERS:
//     hWnd, handle of app window.
//
//================================================================
//================================================================
void DXView_OnSize(HWND hWnd)
{
    HDWP hDWP;
    RECT ClientRect;
    int Height;
    HWND hKeyTreeWnd;
    HWND hValueListWnd;
    int x;
    int dx;

    if (IsIconic(hWnd))
        return;

    if ((hDWP = BeginDeferWindowPos(2)) != NULL)
    {
        //  Data structure used when calling GetEffectiveClientRect (which takes into
        //  account space taken up by the toolbars/status bars).  First half of the
        //  pair is zero when at the end of the list, second half is the control id.
        int s_EffectiveClientRectData[] = {
            1, 0,                               //  For the menu bar, but is unused
            0, 0                                //  First zero marks end of data
        };

        GetEffectiveClientRect(hWnd, &ClientRect, s_EffectiveClientRectData);
        Height = ClientRect.bottom - ClientRect.top;

        hKeyTreeWnd = g_hwndTV;

        DeferWindowPos(hDWP, hKeyTreeWnd, NULL, 0, ClientRect.top, g_xPaneSplit,
            Height, SWP_NOZORDER | SWP_NOACTIVATE);

        x = g_xPaneSplit + GetSystemMetrics(SM_CXSIZEFRAME);
        dx = ClientRect.right - ClientRect.left - x;

        hValueListWnd = g_hwndLV;

        DeferWindowPos(hDWP, hValueListWnd, NULL, x, ClientRect.top, dx, Height,
            SWP_NOZORDER | SWP_NOACTIVATE);

        EndDeferWindowPos(hDWP);
    }
}
#ifdef _X86_
#pragma optimize("", off)
#endif
/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

void LVAddColumn(HWND hwndLV, int i, char *name, int width)
{
    LV_COLUMN col;

    if (i == 0)
    {
        while(ListView_DeleteColumn(hwndLV, 0))
            ;
    }

    col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
    col.fmt  = LVCFMT_LEFT;
    col.pszText = name;
    col.cchTextMax = 0;
    col.cx = width * g_tmAveCharWidth;
    col.iSubItem = 0;
    ListView_InsertColumn(hwndLV, i, &col);
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
int LVAddText(HWND hwndLV, int col, const char *sz, ...)
{
    LV_ITEM lvi;
    char    ach[80];
    va_list vl;

    va_start(vl, sz );
    wvsprintf(ach,sz, vl);

    lvi.mask        = LVIF_TEXT;
    lvi.iSubItem    = 0;
    lvi.state       = 0;
    lvi.stateMask   = 0;
    lvi.pszText     = ach;
    lvi.cchTextMax  = 0;
    lvi.iImage      = 0;
    lvi.lParam      = 0;

    if (col == 0)
    {
            lvi.iItem    = 0x7FFF;
            lvi.iSubItem = 0;
        return ListView_InsertItem(hwndLV, &lvi);
    }
    else
    {
        lvi.iItem    = ListView_GetItemCount(hwndLV)-1;
        lvi.iSubItem = col;
        return ListView_SetItem(hwndLV, &lvi);
    }
    va_end(vl);
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
void LVDeleteAllItems( HWND hwndLV )
{

//Used by DPLay asynchronous enum sessions
if (g_idTimer)
{

BOOLbDataAvailable;
LPLV_INSTANCEGUIDSTRUCTlpInstGuidStruct = NULL;
LV_ITEMlvi;
inti, count;

KillTimer(g_hwndMain, g_idTimer );
g_idTimer = 0;

memset( &lvi, 0, sizeof( LV_ITEM ));

count= ListView_GetItemCount( g_hwndLV );

for ( i = 0; i < count; i++ )
{
lvi.iItem = i;
lvi.mask = LVIF_PARAM;
lvi.lParam = 0;

bDataAvailable =ListView_GetItem( g_hwndLV, &lvi );

if ((bDataAvailable) && (lvi.lParam))
LocalFree( (LPVOID) lvi.lParam );

}

}

ListView_DeleteAllItems( hwndLV );
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
HTREEITEM TVAddNode(
    HTREEITEM       hParent,
    LPSTR           szText,
    BOOL            fKids,
    int             iImage,
    SELCALLBACK     Callback,
    LPARAM          lParam1,
    LPARAM          lParam2,
    PRINTCALLBACK   printCallback)
{
    TV_INSERTSTRUCT tvi;
    NODEINFO *pni;

    pni = (NODEINFO *)LocalAlloc(LPTR, sizeof(NODEINFO));

    if (pni == NULL)
        return NULL;

    pni->lParam1        = lParam1;
    pni->lParam2        = lParam2;
    pni->Callback       = Callback;
    pni->printCallback  = printCallback;

    // Add Node to treeview
    tvi.hParent             = hParent;
    tvi.hInsertAfter        = TVI_LAST;
    tvi.item.mask           = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN;
    tvi.item.iImage         = iImage - IDI_FIRSTIMAGE;
    tvi.item.iSelectedImage = iImage - IDI_FIRSTIMAGE;
    tvi.item.lParam         = (LPARAM)pni;
    tvi.item.cChildren      = fKids;
    tvi.item.pszText        = szText;

    return TreeView_InsertItem(g_hwndTV, &tvi);
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
HRESULT EnumSessions( LPDIRECTPLAY3A lpDP3A )
{

    DPSESSIONDESC2   dps;
HRESULThr = DPERR_GENERIC;

if (lpDP3A)
{
memset(&dps, 0, sizeof(dps));
dps.dwSize = sizeof(dps);

ResetListViewRefresh( g_hwndLV );

hr = lpDP3A->lpVtbl->EnumSessions(lpDP3A, &dps, 0, EnumSessionsCallback,
NULL,
DPENUMSESSIONS_ALL|
DPENUMSESSIONS_PASSWORDREQUIRED|
DPENUMSESSIONS_ASYNC);
RemoveStaleSessions();
}

return hr;
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
void FreeLocalAppList()
{
LOCALAPP * lpTemp;

while( g_lpLocalApp )
{
lpTemp = (LPLOCALAPP) g_lpLocalApp->lpNext;
LocalFree( g_lpLocalApp );
g_lpLocalApp = lpTemp;
}
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
void GetAppName(LPSTR szNameBuffer, LPCGUID lpguidApplication, DWORD dwBufferSize)
{
LPLOCALAPPlpTemp = g_lpLocalApp;
WCHARwszBuffer[128];

while (lpTemp)
{
if (IsEqualGUID( &(lpTemp->guidApplication), lpguidApplication ))
{
strncpy( szNameBuffer, lpTemp->szAppNameA, dwBufferSize );
return;
}
else
{
lpTemp = (LPLOCALAPP)lpTemp->lpNext;
}
}

//If we ended up here, the GUID doesn't match anything we have installed locally.
if (StringFromGUID2( lpguidApplication, wszBuffer, 128 ))
{
    wcstombs(szNameBuffer, wszBuffer, wcslen(wszBuffer)+1);
}

}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
void RemoveStaleSessions()
{
BOOL bDataAvailable;
int i= 0,
count= ListView_GetItemCount( g_hwndLV );

LPLV_INSTANCEGUIDSTRUCTlpInstGuidStruct = NULL;
LV_ITEMlvi;

memset( &lvi, 0, sizeof(LV_ITEM) );

while( i < count)
{
lvi.iItem = i;
lvi.mask = LVIF_PARAM;
lvi.lParam = 0;

bDataAvailable =ListView_GetItem( g_hwndLV, &lvi );

if (bDataAvailable)
{
lpInstGuidStruct = (LPLV_INSTANCEGUIDSTRUCT) lvi.lParam;
if (lpInstGuidStruct)
{
if (FALSE == lpInstGuidStruct->dwRefresh)
{
LocalFree( lpInstGuidStruct );
ListView_DeleteItem( g_hwndLV, i);
}
}
}
else
{
//This item has no data, so it must be invalid.
ListView_DeleteItem( g_hwndLV, i);
}

i++;
}

}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
void ResetListViewRefresh( HWND hwndLV )
{
BOOL bDataAvailable;
int i= 0,
count= ListView_GetItemCount( hwndLV );

LPLV_INSTANCEGUIDSTRUCTlpInstGuidStruct = NULL;
LV_ITEMlvi;

memset( &lvi, 0, sizeof(LV_ITEM) );

while( i < count)
{
lvi.iItem = i;
lvi.mask = LVIF_PARAM;
lvi.lParam = 0;

bDataAvailable =ListView_GetItem( hwndLV, &lvi );

if (bDataAvailable)
{
lpInstGuidStruct = (LPLV_INSTANCEGUIDSTRUCT) lvi.lParam;

if (lpInstGuidStruct)
{
//Mark this session to be deleted next time if it does not
//respond to this enum request.
lpInstGuidStruct->dwRefresh = FALSE;
}
}
else
{
//This item has no data, so it must be invalid.
ListView_DeleteItem( hwndLV, i);
}

i++;
}

}

#ifdef _X86_
#pragma optimize("", on)
#endif


DXVIEW.H

/****************************************************************************

    Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.

    DirectX Device Viewer - Common header file

****************************************************************************/

#include <windows.h>
#ifndef RC_INVOKED
#include <windowsx.h>
#include <mmsystem.h>
#include <commctrl.h>
#include <tchar.h>

#include <ddraw.h>
//#include <mmreg.h>
#include <dsound.h>
#include <dplay.h>
#include <dplobby.h>
#include <d3d.h>
#include <dinput.h>
#endif

//================================================================
// Defines
//================================================================

// Window Width and Height
#define DXView_WIDTH       700
#define DXView_HEIGHT      400

#define DEF_TAB_SIZE        3

// Child controls
#define IDC_LV          0x2000
#define IDC_TV          0x2003

// Imagelist first and last icons
#define IDI_FIRSTIMAGE  IDI_DIRECTX
#define IDI_LASTIMAGE   IDI_CAPSOPEN

#define IDC_SPLIT                       100

#define IDD_ABORTPRINTDLG               1001

#define IDM_EXIT                        40001
#define IDM_ABOUT                       40002
#define IDM_VIEWAVAIL                   40003
#define IDM_VIEWALL                     40004
#define IDM_PRINT                       40005
#define IDM_PRINTALL                    40006
#define IDM_PRINTTOFILE                 40007

#define IDI_DIRECTX                     100
#define IDI_CAPS                        101
#define IDI_CAPSOPEN                    102

#define IDS_PRINT_WARNING               1
#define IDS_PRINT_NEEDSELECT            2
#define IDS_PRINT_NOROOT                3

#define TIMER_PERIOD500
//================================================================
// Typedefs
//================================================================

typedef struct
{
    HDC         hdcPrint;       // In:      Printer DC
    HWND        hTreeWnd;       // In:      tree window
    HTREEITEM   hCurrTree;      // In:      current tree item handle
    DWORD       dwCharWidth;    // In:      average char width
    DWORD       dwLineHeight;   // In:      max line height
    DWORD       dwCurrLine;     // In/Out:  curr line position on page
    DWORD       dwCharsPerLine; // In:      maximum chars per line (based on avg. char width)
    DWORD       dwLinesPerPage; // In:      maximum lines per page
    DWORD       dwCurrIndent;   // In:      Current tab setting
    BOOL        fStartPage;     // In/Out:  need to a start new page ?!?
} PRINTCBINFO;

typedef void (*SELCALLBACK)(LPARAM lParam1, LPARAM lParam2);
typedef BOOL (*PRINTCALLBACK)(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);

typedef struct
{
    SELCALLBACK     Callback;
    LPARAM          lParam1;
    LPARAM          lParam2;
    PRINTCALLBACK   printCallback;
} NODEINFO;

typedef struct
{
    char *  szName;         // name of cap
    DWORD   dwOffset;       // offset to cap
    DWORD   dwFlag;         // bit flag for cal
} CAPDEF;

typedef struct
{
    char *szName;         // name of cap
    SELCALLBACK     Callback;
    LPARAM          lParam2;
    PRINTCALLBACK   printCallback;
} CAPDEFS;

typedef struct
{
    D3DDEVICEDESC d3dDeviceDesc;
    GUID* device;       // DirectDraw device (can be NULL)
    GUID  driver;       // Direct3D driver
} CAP3DDEVICEDESC;

typedef struct
{
GUIDguidInstance;
DWORDdwRefresh;
} LV_INSTANCEGUIDSTRUCT, * LPLV_INSTANCEGUIDSTRUCT;

typedef struct LOCALAPP
{
struct LOCALAPP *lpNext;
GUIDguidApplication;
    charszAppNameA[1];
} LOCALAPP, *LPLOCALAPP;

typedef struct
{
HWNDhwndLV;
LPLOCALAPP *lp;
} ENUMLOCALAPPSTRUCT, *LPENUMLOCALAPPSTRUCT;

//================================================================
// Function prototypes
//================================================================

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

BOOL InitInstance(HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow, int iWidth, int iHeight);

//================================================================
// DXView treeview/listview helper functions
//================================================================

BOOL DXView_OnCreate(HWND hwnd);
void DXView_OnCommand(HWND hwnd, WPARAM wParam);
void DXView_OnSize(HWND hwnd);
void DXView_OnTreeSelect(HWND hwndTV, NM_TREEVIEW *ptv);
void DXView_OnListViewDblClick(HWND hwndLV, NM_LISTVIEW *plv);
void DXView_Cleanup(void);
void DXView_FillTree(HWND hwndTV);
BOOL DXView_InitImageList(void);
BOOL DXView_OnPrint (HWND hWindow, HWND hTreeView, BOOL fPrintAll);
BOOL DXView_OnFile (HWND hWindow, HWND hTreeWnd,BOOL fPrintAll);
void LVAddColumn(HWND hwndLV, int i, char *name, int width);
int  LVAddText(HWND hwndLV, int col, const char *sz, ...);
void LVDeleteAllItems(HWND hwndLV);
HTREEITEM TVAddNode(HTREEITEM hParent, char *szText,
                    BOOL fKids, int iImage,
                    SELCALLBACK Callback,
                    LPARAM lParam1, LPARAM lParam2,
                    PRINTCALLBACK printCallback);
void AddCapsToTV(HTREEITEM hParent, CAPDEFS *pcds, LPARAM lParam1);
void AddColsToLV(void);
void AddCapsToLV(CAPDEF *pcd, LPVOID pv);
void AddMoreCapsToLV(CAPDEF *pcd, LPVOID pv);
BOOL PrintCapsToDC(CAPDEF *pcd, LPVOID pv, PRINTCBINFO * lpInfo);

// Caps add to ListView callbacks
void DDAddCaps(LPARAM lParam1, LPARAM lParam2);
void DSAddCaps(LPARAM lParam1, LPARAM lParam2);
void DSCAddCaps(LPARAM lParam1, LPARAM lParam2);
void DPAddCaps(LPARAM lParam1, LPARAM lParam2);
void DPAddLocalApps(LPARAM lParam1, LPARAM lParam2);
void D3AddCaps(LPARAM lParam1, LPARAM lParam2);
void D3AddLineCaps(LPARAM lParam1, LPARAM lParam2);
void D3AddTexF(LPARAM lParam1, LPARAM lParam2);
void DDAddVideoModes(LPARAM lParam1, LPARAM lParam2);
void DPAddSessions(LPARAM lParam1, LPARAM lParam2);
void DDFourCCFormat(LPARAM lParam1, LPARAM lParam2);

// Caps Print Callbacks
BOOL DDPrintCaps (LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DSPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DSCPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DPPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DPPrintLocalApps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL D3PrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DDPrintVideoModes(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DPPrintSessions(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);
BOOL DDPrintFourCCFormat(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO * lpInfo);

// Printer Helper functions
BOOL PrintStartPage (PRINTCBINFO * pci);
BOOL PrintEndPage (PRINTCBINFO * pci);
BOOL PrintLine (int x, int y, LPCTSTR lpszBuff, DWORD cchBuff, PRINTCBINFO * pci);
BOOL PrintNextLine (PRINTCBINFO * pci);

//DirectPlay helper functions
HRESULT EnumSessions( LPDIRECTPLAY3A lpDP3A );
void RemoveStaleSessions();
void ResetListViewRefresh( HWND hwndLV );

// DirectInput helper functions
void DI_FillTree(void);

void FreeLocalAppList();
void GetAppName(LPSTR szNameBuffer, LPCGUID lpguidApplication, DWORD dwBufferSize);

#ifdef DX_3D
HRESULT CALLBACK D3EnumCallback(
            LPGUID pid,
            LPSTR lpDriverDesc,
            LPSTR lpDriverName,
            LPD3DDEVICEDESC lpD3DDeviceDesc1,
            LPD3DDEVICEDESC lpD3DDeviceDesc2,
            LPVOID lpContext);
#endif

//================================================================
// DXView external variables
//================================================================

extern HINSTANCE    g_hInstance;
extern HWND         g_hwndLV;        // List view
extern HWND         g_hwndTV;        // Tree view


DINPUT.C

/****************************************************************************

    Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.

    PROGRAM: dinput.c

    PURPOSE: DirectX Device Viewer - DirectInput module

    FUNCTIONS:

    COMMENTS:

****************************************************************************/

#include "dxview.h"

#define DICAPDEF(name,val,flag) {name, FIELD_OFFSET(DIDEVCAPS,val), flag}
#define DIVALDEF(name,val)      {name, FIELD_OFFSET(DIDEVCAPS,val), 0}

/****************************************************************************
 *
 *  DI_CreateDI
 *
 *  Create a DirectInput pointer.  Try the current DirectInput version
 *  first; if that doesn't work, try version 3.0.
 *
 ****************************************************************************/

LPDIRECTINPUT
DI_CreateDI(void)
{
    LPDIRECTINPUT pdi;

    if (SUCCEEDED(DirectInputCreate(g_hInstance, DIRECTINPUT_VERSION,
                                    &pdi, NULL))) {
    } else
    if (SUCCEEDED(DirectInputCreate(g_hInstance, 0x0300,
                                    &pdi, NULL))) {
    } else {
        pdi = 0;
    }

    return pdi;
}

/****************************************************************************
 *
 *  DI_CreateDevice
 *
 *  Create a device with the specified instance GUID.
 *
 ****************************************************************************/

LPDIRECTINPUTDEVICE
DI_CreateDevice(LPCGUID pguid)
{
    LPDIRECTINPUT pdi;
    LPDIRECTINPUTDEVICE pdev;

    pdi = DI_CreateDI();
    if (pdi) {
        if (SUCCEEDED(IDirectInput_CreateDevice(pdi, pguid, &pdev, NULL))) {
        } else {
            pdev = 0;
        }
        IDirectInput_Release(pdi);
    } else {
        pdev = 0;
    }
    return pdev;
}

/****************************************************************************
 *
 *  DI3Info
 *
 *      Structure that describes the caps maintained by DIDEVCAPS
 *      that existed in DirectX 3.
 *
 ****************************************************************************/

CAPDEF DI3Info[] =
{
    DIVALDEF("Axes",                dwAxes),
    DIVALDEF("Buttons",             dwButtons),
    DIVALDEF("POVs",                dwPOVs),

    DICAPDEF("ATTACHED",            dwFlags,        DIDC_ATTACHED),
    DICAPDEF("POLLEDDEVICE",        dwFlags,        DIDC_POLLEDDEVICE),
    DICAPDEF("EMULATED",            dwFlags,        DIDC_EMULATED),

    {"", 0, 0}
};

/****************************************************************************
 *
 *  DI5Info
 *
 *      Structure that describes the caps maintained by DIDEVCAPS
 *      that exist in DirectX 5 and were't in DirectX 3.
 *
 ****************************************************************************/

CAPDEF DI5Info[] =
{
    DICAPDEF("FORCEFEEDBACK",       dwFlags,        DIDC_FORCEFEEDBACK),
    DICAPDEF("FFATTACK",            dwFlags,        DIDC_FFATTACK),
    DICAPDEF("FFFADE",              dwFlags,        DIDC_FFFADE),
    DICAPDEF("SATURATION",          dwFlags,        DIDC_SATURATION),
    DICAPDEF("POSNEGCOEFFICIENTS",  dwFlags,        DIDC_POSNEGCOEFFICIENTS),
    DICAPDEF("POSNEGSATURATION",    dwFlags,        DIDC_POSNEGSATURATION),

    DIVALDEF("FFSamplePeriod",      dwFFSamplePeriod),
    DIVALDEF("FFMinTimeResolution", dwFFMinTimeResolution),
    {"", 0, 0}
};

/****************************************************************************
 *
 *  DISubTypeList
 *
 *      List of various DirectInput device subtypes and their names.
 *
 ****************************************************************************/

typedef struct SUBTYPEINFO {
    DWORD dwDevType;
    LPCTSTR pszName;
} SUBTYPEINFO;

#define SUBTYPE(type, subtype, name)    \
    {   MAKEWORD(DIDEVTYPE_##type, \
        DIDEVTYPE##type##_##subtype), \
        TEXT("%d - ") TEXT(name) }

SUBTYPEINFO DISubTypes[] = {
    SUBTYPE(MOUSE,          UNKNOWN,            "Unknown"),
    SUBTYPE(MOUSE,          TRADITIONAL,        "Traditional"),
    SUBTYPE(MOUSE,          FINGERSTICK,        "Fingerstick"),
    SUBTYPE(MOUSE,          TOUCHPAD,           "Touchpad"),
    SUBTYPE(MOUSE,          TRACKBALL,          "Trackball"),

    SUBTYPE(KEYBOARD,       UNKNOWN,            "Unknown"),
    SUBTYPE(KEYBOARD,       PCXT,               "XT"),
    SUBTYPE(KEYBOARD,       OLIVETTI,           "Olivetti"),
    SUBTYPE(KEYBOARD,       PCAT,               "AT"),
    SUBTYPE(KEYBOARD,       PCENH,              "Enhanced"),
    SUBTYPE(KEYBOARD,       NOKIA1050,          "Nokia 1050"),
    SUBTYPE(KEYBOARD,       NOKIA9140,          "Nokia 9140"),
    SUBTYPE(KEYBOARD,       NEC98,              "NEC98"),
    SUBTYPE(KEYBOARD,       NEC98LAPTOP,        "NEC98 Laptop"),
    SUBTYPE(KEYBOARD,       NEC98106,           "NEC98 106"),
    SUBTYPE(KEYBOARD,       JAPAN106,           "Japan 106"),
    SUBTYPE(KEYBOARD,       JAPANAX,            "Japan AX"),
    SUBTYPE(KEYBOARD,       J3100,              "J3100"),

    SUBTYPE(JOYSTICK,       UNKNOWN,            "Unknown"),
    SUBTYPE(JOYSTICK,       TRADITIONAL,        "Traditional"),
    SUBTYPE(JOYSTICK,       FLIGHTSTICK,        "Flightstick"),
    SUBTYPE(JOYSTICK,       GAMEPAD,            "Gamepad"),
    SUBTYPE(JOYSTICK,       RUDDER,             "Rudder"),
    SUBTYPE(JOYSTICK,       WHEEL,              "Wheel"),
    SUBTYPE(JOYSTICK,       HEADTRACKER,        "Head tracker"),
    { 0, 0 },
};

/****************************************************************************
 *
 *  DIAddRow
 *
 *  Add a row to the growing two-column listview or printer.
 *
 *      lpInfo = print context or NULL if adding to listview
 *      pszName = name of cap
 *      pszFormat = wsprintf-style format string
 *      ... = inserts for wsprintf
 *
 ****************************************************************************/

BOOL __cdecl
DIAddRow(PRINTCBINFO *lpInfo, LPCTSTR pszName, LPCTSTR pszFormat, ...)
{
    BOOL fRc;
    TCHAR szBuf[1024];
    int cch;
    va_list ap;

    va_start(ap, pszFormat);
    cch = wvsprintf(szBuf, pszFormat, ap);
    va_end(ap);

    if (lpInfo == NULL) {
        LVAddText(g_hwndLV, 0, pszName, 0);
        LVAddText(g_hwndLV, 1, TEXT("%s"), szBuf);
    } else {
        int xName, xVal, yLine;

        // Calculate Name and Value column x offsets
        xName   = (lpInfo->dwCurrIndent * DEF_TAB_SIZE * lpInfo->dwCharWidth);
        xVal    = xName + (32 * lpInfo->dwCharWidth);
        yLine = lpInfo->dwCurrLine * lpInfo->dwLineHeight;

        // Print name
        fRc = PrintLine(xName, yLine, pszName, lstrlen(pszName), lpInfo);
        if (!fRc) goto done;

        // Print value
        fRc = PrintLine(xVal, yLine, szBuf, cch, lpInfo);
        if (!fRc) goto done;

        // Advance to next line on page
        fRc = PrintNextLine(lpInfo);
        if (!fRc) goto done;
    }

    fRc = TRUE;

done:;
    return fRc;
}

/****************************************************************************
 *
 *  DIAddTypes
 *
 *  Emit the device type information to the output device.
 *
 *      lpInfo = print context or NULL if adding to listview
 *      dwDevType = device type to decode
 *
 ****************************************************************************/

BOOL DIAddTypes(PRINTCBINFO *lpInfo, DWORD dwDevType)
{
    BOOL fRc;
    DWORD dwType;
    LPCTSTR pszValue;
    SUBTYPEINFO *psti;

    /*
     *  Add the type code.
     */

    dwType = GET_DIDEVICE_TYPE(dwDevType);
    switch (dwType) {
    case DIDEVTYPE_MOUSE:   pszValue = TEXT("%d - Mouse"); break;
    case DIDEVTYPE_KEYBOARD:pszValue = TEXT("%d - Keyboard"); break;
    case DIDEVTYPE_JOYSTICK:pszValue = TEXT("%d - Joystick"); break;
    default:                pszValue = TEXT("%d"); break;
    }

    fRc = DIAddRow(lpInfo, "Type", pszValue, dwType);
    if (!fRc) goto done;

    /*
     *  Add the sub type code.
     */

    pszValue = TEXT("%d");
    for (psti = DISubTypes; psti->dwDevType; psti++) {
        if (psti->dwDevType == (dwDevType & 0xFFFF)) {
            pszValue = psti->pszName;
            break;
        }
    }

    fRc = DIAddRow(lpInfo, TEXT("Subtype"), pszValue,
                   GET_DIDEVICE_SUBTYPE(dwDevType));
    if (!fRc) goto done;

    fRc = TRUE;
done:;
    return fRc;
}

/****************************************************************************
 *
 *  DIAddCapsToTarget
 *
 *  Add the caps either to the listview or to the printer.
 *
 *      lpInfo = print context or NULL if adding to listview
 *      pcd = pointer to CAPDEF array describing the caps
 *      pv = pointer to structure to be parsed
 *
 ****************************************************************************/

BOOL
DIAddCapsToTarget(PRINTCBINFO *lpInfo, CAPDEF *pcd, LPVOID pv)
{
    BOOL fRc;

    if (lpInfo) {
        fRc = PrintCapsToDC(pcd, pv, lpInfo);
    } else {
        AddMoreCapsToLV(pcd, pv);
        fRc = TRUE;
    }
    return fRc;
}

/****************************************************************************
 *
 *  DIPrintCaps
 *
 *      lpInfo = print context or NULL if adding to listview
 *      lParam1 = state info recorded by DIEnumDevCallback (LPGUID)
 *      lParam2 = refdata (not used)
 *
 ****************************************************************************/

BOOL
DIPrintCaps(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO *lpInfo)
{
    BOOL fRc;
    LPGUID pguid = (LPGUID)lParam1;
    LPDIRECTINPUTDEVICE pdev;

    if (lpInfo == NULL) {
        AddColsToLV();
    }

    fRc = TRUE;

    pdev = DI_CreateDevice(pguid);

    if (pdev) {
        DIDEVCAPS caps;
        HRESULT hres;

        /*
         *  First use the DX3 caps.
         */
        caps.dwSize = sizeof(DIDEVCAPS_DX3);

        hres = IDirectInputDevice_GetCapabilities(pdev, &caps);
        if (SUCCEEDED(hres)) {

            fRc = DIAddTypes(lpInfo, caps.dwDevType) &&
                  DIAddCapsToTarget(lpInfo, DI3Info, &caps);
        }

        /*
         *  Now get the DX5 caps if we haven't cancelled printing yet.
         */
        if (fRc) {
            caps.dwSize = sizeof(DIDEVCAPS);

            hres = IDirectInputDevice_GetCapabilities(pdev, &caps);
            if (SUCCEEDED(hres)) {

                fRc = DIAddCapsToTarget(lpInfo, DI5Info, &caps);
            }
        }

        IDirectInputDevice_Release(pdev);
    }
    return fRc;
}

/****************************************************************************
 *
 *  DIAddCaps
 *
 *      lParam1 = state info recorded by DIEnumDevCallback (LPGUID)
 *      lParam2 = refdata (not used)
 *
 ****************************************************************************/

void
DIAddCaps(LPARAM lParam1, LPARAM lParam2)
{
    DIPrintCaps(lParam1, lParam2, NULL);
}

/****************************************************************************
 *
 *  DIEnumEffCallback
 *
 *  Add the enumerated DirectInput effect to the listview.
 *
 ****************************************************************************/

BOOL CALLBACK
DIEnumEffCallback(LPCDIEFFECTINFO pei, LPVOID lpInfo)
{
    BOOL fRc;
    const GUID *pguid = &pei->guid;

    fRc = DIAddRow(lpInfo, pei->tszName,
                   TEXT("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"),
                    pguid->Data1, pguid->Data2, pguid->Data3,
                    pguid->Data4[0], pguid->Data4[1],
                    pguid->Data4[2], pguid->Data4[3],
                    pguid->Data4[4], pguid->Data4[5],
                    pguid->Data4[6], pguid->Data4[7]);
    if (fRc) {
        return DIENUM_CONTINUE;
    } else {
        return DIENUM_STOP;
    }
}

/****************************************************************************
 *
 *  DIPrintEffects
 *
 *      lpInfo = print context or NULL if adding to listview
 *      lParam1 = state info recorded by DIEnumDevCallback (LPGUID)
 *      lParam2 = refdata (not used)
 *
 ****************************************************************************/

BOOL
DIPrintEffects(LPARAM lParam1, LPARAM lParam2, PRINTCBINFO *lpInfo)
{
    BOOL fRc;
    LPGUID pguid = (LPGUID)lParam1;
    LPDIRECTINPUTDEVICE pdev;

    /*
     *  The effects are not a simple name/value thing.
     *  But DXView doesn't like multiple nesting levels,
     *  so we mash it into the name/value paradigm
     *  because I'm lazy.  Someday I won't be quite so
     *  lazy and do it right.
     */

    if (lpInfo == NULL) {
        AddColsToLV();
    }

    fRc = TRUE;
    pdev = DI_CreateDevice(pguid);

    if (pdev) {
        LPDIRECTINPUTDEVICE2 pdev2;
        HRESULT hres;

        hres = IDirectInputDevice_QueryInterface(
                        pdev, &IID_IDirectInputDevice2, (LPVOID *)&pdev2);

        if (SUCCEEDED(hres)) {
            /*
             *  Enumerate the effects and add them to the listview.
             */
            IDirectInputDevice2_EnumEffects(pdev2,
                                            DIEnumEffCallback, lpInfo,
                                            DIEFT_ALL);

            IDirectInputDevice2_Release(pdev2);
        }

        IDirectInputDevice_Release(pdev);
    }

    return fRc;
}

/****************************************************************************
 *
 *  DIAddEffects
 *
 *      lParam1 = state info recorded by DIEnumDevCallback (LPGUID)
 *      lParam2 = refdata (not used)
 *
 ****************************************************************************/

void
DIAddEffects(LPARAM lParam1, LPARAM lParam2)
{
    DIPrintEffects(lParam1, lParam2, NULL);
}

/****************************************************************************
 ***************************************************************************/

CAPDEFS DICapDefs[] =
{
    {"",                    DIAddCaps,          0,              DIPrintCaps},
    {"Effects",             DIAddEffects,       0,              DIPrintEffects},
    {NULL, 0, 0, NULL}
};

/****************************************************************************
 *
 *  DIEnumDevCallback
 *
 *  Add the enumerated DirectInput device to the treeview.
 *
 ****************************************************************************/

BOOL CALLBACK
DIEnumDevCallback(LPCDIDEVICEINSTANCE pinst, LPVOID pv)
{
    HTREEITEM hParent = pv;
    TCHAR     szText[MAX_PATH + 2 + MAX_PATH + 2];
    LPGUID    pguid;

    pguid = LocalAlloc(LPTR, sizeof(GUID));
    if (pguid == NULL) {
        return DIENUM_STOP;
    }

    *pguid = pinst->guidInstance;

    wsprintf(szText, "%s (%s)", pinst->tszInstanceName,
                                pinst->tszProductName);

    DICapDefs[0].szName = szText;
    AddCapsToTV(hParent, DICapDefs, (LPARAM)pguid);

    return(DIENUM_CONTINUE);
}

/****************************************************************************
 *
 *  DI_FillTree
 *
 *  Add the DirectInput nodes to the treeview.
 *
 ****************************************************************************/

void
DI_FillTree(void)
{
    LPDIRECTINPUT pdi;
    HTREEITEM hTree;

    // Add direct input devices if DInput is found

    pdi = DI_CreateDI();
    if (pdi) {

        hTree = TVAddNode(TVI_ROOT, "DirectInput Devices", TRUE,
                          IDI_DIRECTX, NULL, 0, 0, NULL);

        IDirectInput_EnumDevices(pdi, 0, DIEnumDevCallback, hTree,
                                 DIEDFL_ALLDEVICES);

        TreeView_Expand(g_hwndTV, hTree, TVE_EXPAND);

        IDirectInput_Release(pdi);
    }
}


README.TXT

Egg
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Loading a simple XOF of a single mesh object.


EGG.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: egg.c
 *
 ***************************************************************************/

#include "rmdemo.h"

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMMESHBUILDER egg_builder = NULL;
    LPDIRECT3DRMFRAME egg = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    HRESULT rval;

    view = view;/* not used */

    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetPosition(lights, scene, D3DVAL(5), D3DVAL(5),
                                -D3DVAL(1))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.9),
                                  D3DVAL(0.8), D3DVAL(0.7), &light1)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, light1)))
    goto generic_error;
    if (FAILED(lights->lpVtbl->SetRotation(lights, scene, D3DVAL(0), D3DVAL(1), D3DVAL(1),
                                D3DVAL(-0.02))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &light2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, light2)))
goto generic_error;

    /*
     * load mesh file
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &egg_builder)))
goto generic_error;
    rval = egg_builder->lpVtbl->Load(egg_builder, "egg.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load egg.x\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }

    /*
     * create an egg frame within the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &egg)))
goto generic_error;

    /*
     * add the loaded mesh into the frame
     */
    if (FAILED(egg->lpVtbl->AddVisual(egg, (LPDIRECT3DRMVISUAL)egg_builder)))
goto generic_error;

    /*
     * set up the frames position, orientation and rotation
     */
    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0), D3DVAL(0), -D3DVAL(10))))
goto generic_error;
    if (FAILED(camera->lpVtbl->SetOrientation(camera, scene, D3DVAL(0), D3DVAL(0), D3DVAL(1), D3DVAL(0),
                                   D3DVAL(1), D3DVAL(0))))
   goto generic_error;
    if (FAILED(egg->lpVtbl->SetRotation(egg, scene, D3DVAL(0), D3DVAL(1), D3DVAL(1),
                             D3DVAL(0.02))))
     goto generic_error;

    RELEASE(egg);
    RELEASE(lights);
    RELEASE(egg_builder);
    RELEASE(light1);
    RELEASE(light2);
    return TRUE;

generic_error:
    Msg("A failure occured while building the scene.\n");
ret_with_error:
    RELEASE(egg);
    RELEASE(lights);
    RELEASE(egg_builder);
    RELEASE(light1);
    RELEASE(light2);
    return FALSE;
}

void
OverrideDefaults(Defaults *defaults)
{
    defaults->bNoTextures = TRUE;
    lstrcpy(defaults->Name, "Egg Direct3DRM Example");
}


README.TXT

Faces
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Creation of mesh objects one face at a time.


FACES.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: faces.c
 *
 ***************************************************************************/

/*
 * Example program to demonstrate MeshAddFaces
 */

#include "rmdemo.h"

D3DVECTOR cube_vertices[] = {-D3DVAL(0.5), -D3DVAL(0.5), -D3DVAL(0.5),
    -D3DVAL(0.5), -D3DVAL(0.5), D3DVAL(0.5),
    -D3DVAL(0.5), D3DVAL(0.5), -D3DVAL(0.5),
    -D3DVAL(0.5), D3DVAL(0.5), D3DVAL(0.5),
    D3DVAL(0.5), -D3DVAL(0.5), -D3DVAL(0.5),
    D3DVAL(0.5), -D3DVAL(0.5), D3DVAL(0.5),
    D3DVAL(0.5), D3DVAL(0.5), -D3DVAL(0.5),
    D3DVAL(0.5), D3DVAL(0.5), D3DVAL(0.5)
};

/*
 * face_data is a vertex count followed by the vertex reference in the
 * vertices list. If normal_count > 0, then pairs of vectors are
 * referenced: first the vertex and secondly the normal reference. the
 * list is terminated by a 0 (vertex count to a face)
 */

BOOL
build_face1(LPDIRECT3DRMMESHBUILDER msh)
{
    /*
     * add a single face to a mesh.
     */

    /*
     * Single face with all normals pointing in the same direction.
     */

    int vertex_count = 4;
    D3DVECTOR vertices[4] = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
    };
    int normal_count = 4;
    D3DVECTOR normals[4] = {D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    };
    int face_data[] = {4, 0, 0, 1, 1, 2, 2, 3, 3, 0};

    if (FAILED(msh->lpVtbl->AddFaces(msh, vertex_count, vertices, normal_count,
                          normals, face_data, NULL)))
  return FALSE;
    return TRUE;
}

BOOL
build_face2(LPDIRECT3DRMMESHBUILDER msh)
{
    /*
     * Single face with normals pointing in different directions to
     * approximate a curved surface.
     */
    int vertex_count = 4;
    D3DVECTOR vertices[4] = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
    };
    int normal_count = 4;
    D3DVECTOR normals[4] = {
D3DVAL(0.5), D3DVAL(0.7), D3DVAL(0.5),
D3DVAL(0.5), D3DVAL(0.7), -D3DVAL(0.5),
-D3DVAL(0.5), D3DVAL(0.7), -D3DVAL(0.5),
-D3DVAL(0.5), D3DVAL(0.7), D3DVAL(0.5)
    };
    int face_data[] = {4, 0, 0, 1, 1, 2, 2, 3, 3, 0};

    if (FAILED(msh->lpVtbl->AddFaces(msh, vertex_count, vertices, normal_count,
                          normals, face_data, NULL)))
  return FALSE;
    return TRUE;
}

BOOL
build_cube1(LPDIRECT3DRMMESHBUILDER msh)
{
    /*
     * cube 1 has planar faces, one normal is shared by all vertices of a
     * face
     */
    int face1_data[] = {
4, 1, 0, 5, 1, 7, 2, 3, 3,
4, 2, 4, 3, 5, 7, 6, 6, 7,
4, 5, 8, 4, 9, 6, 10, 7, 11,
4, 0, 12, 2, 13, 6, 14, 4, 15,
4, 0, 16, 4, 17, 5, 18, 1, 19,
4, 0, 20, 1, 21, 3, 22, 2, 23,
    0};

    D3DVECTOR cube1_normals[] = {
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
D3DVAL(0.0), -D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), -D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), -D3DVAL(1.0), D3DVAL(0.0),
D3DVAL(0.0), -D3DVAL(1.0), D3DVAL(0.0),
-D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
- D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
- D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
- D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0)
    };

    if (FAILED(msh->lpVtbl->AddFaces(msh, 8, cube_vertices, 6 * 4, cube1_normals,
  face1_data, NULL)))
  return FALSE;
    return TRUE;
}

BOOL
build_cube2(LPDIRECT3DRMMESHBUILDER msh)
{
    /*
     * cube 2 has rounded faces, the normals radiate out at each vertex,
     * one normal to each vertex
     */

    int face2_data[] = {
4, 1, 1, 5, 5, 7, 7, 3, 3,
4, 2, 2, 3, 3, 7, 7, 6, 6,
4, 5, 5, 4, 4, 6, 6, 7, 7,
4, 0, 0, 2, 2, 6, 6, 4, 4,
4, 0, 0, 4, 4, 5, 5, 1, 1,
4, 0, 0, 1, 1, 3, 3, 2, 2,
    0};

    D3DVECTOR cube2_normals[] = {
-D3DVAL(1.0), -D3DVAL(1.0), -D3DVAL(1.0),
-D3DVAL(1.0), -D3DVAL(1.0), D3DVAL(1.0),
-D3DVAL(1.0), D3DVAL(1.0), -D3DVAL(1.0),
-D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0),
D3DVAL(1.0), -D3DVAL(1.0), -D3DVAL(1.0),
D3DVAL(1.0), -D3DVAL(1.0), D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(1.0), -D3DVAL(1.0),
D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0)
    };
    if (FAILED(msh->lpVtbl->AddFaces(msh, 8, cube_vertices, 8, cube2_normals,
  face2_data, NULL)))
  return FALSE;
    return TRUE;
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_GOURAUD;
    LPDIRECT3DRMMESHBUILDER light_builder, face1_builder, face2_builder;
    LPDIRECT3DRMMESHBUILDER cube1_builder, cube2_builder;
    LPDIRECT3DRMMESH light_mesh, face1_mesh, face2_mesh, cube1_mesh, cube2_mesh;
    LPDIRECT3DRMFRAME frame1, frame2, face_frame1, face_frame2, light_frame;
    LPDIRECT3DRMFRAME lights;
    LPDIRECT3DRMLIGHT light1, light2;
    HRESULT rval;

    view = view;/* not used */
    light_builder = NULL; face1_builder = NULL; face2_builder = NULL;
    cube1_builder = NULL; cube2_builder = NULL;
    light_mesh = NULL; face1_mesh = NULL; face2_mesh = NULL;
    cube1_mesh = NULL; cube2_mesh = NULL;
    frame1 = NULL; frame2 = NULL; face_frame1 = NULL; face_frame2 = NULL;
    light_frame = NULL; lights = NULL; light1 = NULL; light2 = NULL;

    /*
     * This Demo shows the construction of planar faces\n"); with vertex
     * normals for realistic modelling of\n"); flat and curved surfaces by
     * polygonal approximation.\n");
     */

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &face_frame1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &face_frame2)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame2)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &light_frame)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, light_frame, &lights)))
goto generic_error;

    if (FAILED(lights->lpVtbl->SetPosition(lights, light_frame, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(2.2))))
goto generic_error;
    if (FAILED(light_frame->lpVtbl->SetPosition(light_frame, scene, D3DVAL(0.0), D3DVAL(0.4), D3DVAL(0.0))))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_POINT, D3DVAL(0.9),
                                  D3DVAL(0.8), D3DVAL(0.7), &light1)))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.02),
                                  D3DVAL(0.01), D3DVAL(0.01), &light2)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, light1)))
goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, light2)))
goto generic_error;
    RELEASE(light1);
    RELEASE(light2);

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &face1_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &face2_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &cube1_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &cube2_builder)))
goto generic_error;

    if (!build_face1(face1_builder))
goto generic_error;
    if (!build_face2(face2_builder))
goto generic_error;
    if (!build_cube1(cube1_builder))
goto generic_error;
    if (!build_cube2(cube2_builder))
goto generic_error;

    if (FAILED(face1_builder->lpVtbl->CreateMesh(face1_builder, &face1_mesh)))
goto generic_error;
    if (FAILED(face2_builder->lpVtbl->CreateMesh(face2_builder, &face2_mesh)))
goto generic_error;
    if (FAILED(cube1_builder->lpVtbl->CreateMesh(cube1_builder, &cube1_mesh)))
goto generic_error;
    if (FAILED(cube2_builder->lpVtbl->CreateMesh(cube2_builder, &cube2_mesh)))
goto generic_error;

    RELEASE(face1_builder);
    RELEASE(face2_builder);
    RELEASE(cube1_builder);
    RELEASE(cube2_builder);

    if (FAILED(face_frame1->lpVtbl->AddVisual(face_frame1, (LPDIRECT3DRMVISUAL) face1_mesh)))
goto generic_error;
    if (FAILED(face_frame2->lpVtbl->AddVisual(face_frame2, (LPDIRECT3DRMVISUAL) face2_mesh)))
goto generic_error;
    if (FAILED(frame1->lpVtbl->AddVisual(frame1, (LPDIRECT3DRMVISUAL) cube1_mesh)))
goto generic_error;
    if (FAILED(frame2->lpVtbl->AddVisual(frame2, (LPDIRECT3DRMVISUAL) cube2_mesh)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &light_builder)))
goto generic_error;
    rval = light_builder->lpVtbl->Load(light_builder, "sphere0.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere0.x\n");
goto ret_with_error;
    }
    if (FAILED(light_builder->lpVtbl->SetColorRGB(light_builder, D3DVAL(1), D3DVAL(1),
                                        D3DVAL(1))))
goto generic_error;
    if (FAILED(light_builder->lpVtbl->Scale(light_builder, D3DVAL(0.1), D3DVAL(0.1),
                                 D3DVAL(0.1))))
 goto generic_error;
    if (FAILED(light_builder->lpVtbl->SetQuality(light_builder, D3DRMRENDER_UNLITFLAT)))
goto generic_error;
    if (FAILED(light_builder->lpVtbl->CreateMesh(light_builder, &light_mesh)))
goto generic_error;
    RELEASE(light_builder);

    if (FAILED(lights->lpVtbl->AddVisual(lights, (LPDIRECT3DRMVISUAL) light_mesh)))
goto generic_error;

    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0.0), D3DVAL(2), -D3DVAL(5))))
goto generic_error;
    if (FAILED(face_frame1->lpVtbl->SetPosition(face_frame1, scene, -D3DVAL(0.5),
                                     D3DVAL(0.5), D3DVAL(2))))
     goto generic_error;
    if (FAILED(face_frame2->lpVtbl->SetPosition(face_frame2, scene, -D3DVAL(0.5),
                                     -D3DVAL(1.5), D3DVAL(2))))
     goto generic_error;
    if (FAILED(frame1->lpVtbl->SetPosition(frame1, scene, -D3DVAL(2.0), D3DVAL(0.0), D3DVAL(2))))
goto generic_error;
    if (FAILED(frame2->lpVtbl->SetPosition(frame2, scene, D3DVAL(1.5), D3DVAL(0.0), D3DVAL(2))))
goto generic_error;

    if (FAILED(face_frame1->lpVtbl->SetOrientation(face_frame1, scene, D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0),
                                        D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1))))
goto generic_error;
    if (FAILED(face_frame2->lpVtbl->SetOrientation(face_frame2, scene, D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0),
                                        D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1))))
goto generic_error;
    if (FAILED(camera->lpVtbl->SetOrientation(camera, scene, D3DVAL(0.0), -D3DVAL(0.2), D3DVAL(1),
                                   D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0))))
   goto generic_error;
    if (FAILED(frame1->lpVtbl->SetRotation(frame1, scene, D3DVAL(1), D3DVAL(0.0), D3DVAL(1),
                                -D3DVAL(0.005))))
goto generic_error;
    if (FAILED(frame2->lpVtbl->SetRotation(frame2, scene, D3DVAL(0.0), D3DVAL(1), D3DVAL(1),
                                -D3DVAL(0.005))))
goto generic_error;
    if (FAILED(light_frame->lpVtbl->SetRotation(light_frame, scene, D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0),
                                     -D3DVAL(0.02))))
     goto generic_error;

    RELEASE(light_mesh);
    RELEASE(face1_mesh);
    RELEASE(face2_mesh);
    RELEASE(cube1_mesh);
    RELEASE(cube2_mesh);

    RELEASE(frame1);
    RELEASE(frame2);
    RELEASE(face_frame1);
    RELEASE(face_frame2);
    RELEASE(light_frame);
    RELEASE(lights);
    return TRUE;
generic_error:
    Msg("An error has occurred while building the scene.\n");
ret_with_error:
    RELEASE(light_builder);
    RELEASE(face1_builder);
    RELEASE(face2_builder);
    RELEASE(cube1_builder);
    RELEASE(cube2_builder);
    RELEASE(light_mesh);
    RELEASE(face1_mesh);
    RELEASE(face2_mesh);
    RELEASE(cube1_mesh);
    RELEASE(cube2_mesh);
    RELEASE(frame1);
    RELEASE(frame2);
    RELEASE(face_frame1);
    RELEASE(face_frame2);
    RELEASE(light_frame);
    RELEASE(lights);
    RELEASE(light1);
    RELEASE(light2);
    return FALSE;
}

void
OverrideDefaults(Defaults *defaults)
{
    defaults->bNoTextures = TRUE;
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Faces Direct3DRM Example");
}


README.TXT

FastFile
--------
FastFile provides a way to create a very fast way to access large numbers
of files.

You use FFCREATE.EXE to create a single flat file.   Just run FFCREATE in
the directory that contains all of the files you wish to access.   A file
will be created at the root (RESULT.FF) that is the FastFile.  (you can
specify a filename on the command line to override the default of RESULT.FF;
if you do so, make sure that the result file is NOT generated in the
current directory).

Once you have created your FastFile, you can use the FastFile routines to
access it:

BOOL FastFileInit( LPSTR fname, int max_handles ):
Call to initialize access to a FastFile.
    fname      : name of FastFile
    max_handles: maximum number of file handles you want to have open at the
 same time.
    returns TRUE if succeeds, FALSE otherwise

void FastFileFini( void ):
Call when you are finished accessing your FastFile

HFASTFILE FastFileOpen( LPSTR name ):
Call to open an individual file in a FastFile (read-only access is supported)
    name: name if individual file
    returns a handle, or NULL if fail

BOOL FastFileClose( HFASTFILE hff ):
Call to close an individual file
    hff: handle to an individual file
    returns TRUE if succeeded, FALSE otherwise

BOOL FastFileRead( HFASTFILE hff, LPVOID ptr, int size )
Call to read from an individual file
    hff:  handle to an individual file
    ptr:  buffer to copy data
    size: size of data to copy
    returns TRUE if succeeded, FALSE otherwise

BOOL FastFileSeek( HFASTFILE hff, int off, int type )
Call to seek to an offset in an individual file
    hff:  handle to an individual file
    off:  offset to seek to
    type: seek type: SEEK_SET (from start), SEEK_CUR (from current pos),
      or SEEK_END (from end)
    returns TRUE if succeeded, FALSE otherwise

long FastFileTell( HFASTFILE hff )
Call to get the current position in an individual file
    hff:  handle to an individual file
    returns current position if succeeded, -1 otherwise


FASTFILE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       fastfile.h
 *  Content:Definitions for fastfile access.
 *
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTBILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/

typedef LPVOIDHFASTFILE;

extern BOOL FastFileInit( LPSTR fname, int max_handles );
extern void FastFileFini( void );
extern HFASTFILE FastFileOpen( LPSTR name );
extern BOOL FastFileClose( HFASTFILE pfe );
extern BOOL FastFileRead( HFASTFILE pfh, LPVOID ptr, int size );
extern BOOL FastFileSeek( HFASTFILE pfe, int off, int how );
extern long FastFileTell( HFASTFILE pfe );
extern LPVOID FastFileLock( HFASTFILE pfe, int off, int len );
extern BOOL FastFileUnlock( HFASTFILE pfe, int off, int len );


FFCREATE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       ffcreate.c
 *  Content:Fast file I/O for large numbers of files.
 *Turns all files in a directory into a single file.
 *This single file contains a directory + all the files.
 *
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTBILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <fcntl.h>
#include <io.h>
#include <malloc.h>

#ifdef __WATCOMC__
#define _open open
#define _close close
#define _lseek lseek
#define _read read
#define _write write
#define _stricmp stricmp
#define _S_IREAD S_IREAD
#define _S_IWRITE S_IWRITE
#endif

#include "ffent.h"

#define BLOCK_SIZE16*1024

/*
 * Compare
 *
 * quicksort comparison routine
 */
int Compare( const LPFILEENTRY p1, const LPFILEENTRY p2 )
{
    return( _stricmp( (p1)->name,(p2)->name ) );
}

/*
 * main
 */
main( int argc, char *argv[] )
{
    HANDLEdir;
    WIN32_FIND_DATAfd;
    intout;
    intin;
    unsigned longcnt;
    unsigned longtmp;
    LPFILEENTRYpfe;
    inti;
    intbytes;
    intoutbytes;
    char*buff;
    char*fname;
    char*dename;
    longpos;

    /*
     * get i/o buffer
     */
    buff = malloc( BLOCK_SIZE );
    if( buff == NULL ) {
printf( "Out of memory!\n" );
exit( 1 );
    }

    /*
     * get fastfile name, open file
     */
    if( argc < 2 ) {
fname = "\\result.ff";
    } else {
fname = argv[1];
    }
    printf( "Creating FastFile \"%s\"\n", fname );
    out = _open( fname, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY,
    _S_IREAD | _S_IWRITE );
    if( out < 0 ) {
printf( "Could not open file \"%s\"", fname );
exit( 1 );
    }

    /*
     * build a header
     */
    cnt = 0;
    printf( "Pass 1: building header\n" );
    dir = FindFirstFile( "*.*", &fd );
    if( dir == NULL ) {
printf( "Could not open current directory\n" );
_close( out );
exit( 1 );
    }
    pfe = NULL;
    while( 1 ) {
if( !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
    cnt++;
    pfe = realloc( pfe, (cnt+1) * sizeof( FILEENTRY ) );
    memset( &pfe[cnt-1], 0, sizeof( FILEENTRY )*2 );
    if( pfe == NULL ) {
printf( "Out of memory!\n" );
_close( out );
exit( 1 );
    }
    dename = fd.cAlternateFileName;
    if( dename[0] == 0 ) {
dename = fd.cFileName;
    }
    printf( "File %d: %s                 \r", cnt, dename );
    pfe[cnt-1].offset = 0;
    strcpy( pfe[cnt-1].name, dename );
}
if( !FindNextFile( dir, &fd ) ) {
    break;
}
    }
    FindClose( dir );

    if( cnt == 0 ) {
printf( "No files found!\n" );
exit( 0 );
    }

    /*
     * sort the directory
     */
    qsort( pfe, cnt, sizeof( FILEENTRY ), (LPVOID) Compare );

    /*
     * write the number of directory entries + the directory
     */
    tmp = cnt+1;
    bytes = _write( out, &tmp, sizeof( tmp ) );
    if( bytes != sizeof( tmp ) ) {
printf( "Error writing output file\n" );
_close( out );
exit( 1 );
    }
    bytes = _write( out, pfe, tmp * sizeof( FILEENTRY ) );
    if( bytes != (int) (tmp * sizeof( FILEENTRY )) ) {
printf( "Error writing output file\n" );
_close( out );
exit( 1 );
    }

    /*
     * now read all of the files one by one and add them to the fastfile
     */
    printf( "Pass 2: adding data files                  \n" );
    for( i=0;i<(int)cnt;i++ ) {
/*
 * save current file position
 */
pfe[i].offset = _lseek( out, 0, SEEK_CUR );
if( pfe[i].offset < 0 ) {
    printf( "\nSeek error on output file\n" );
    _close( out );
    exit( 1 );
}

/*
 * open next file to add
 */
in = _open( pfe[i].name, O_RDONLY | O_BINARY, 0 );
printf( "File %d: \"%s\", offset=%ld                          \r",
i+1, pfe[i].name, pfe[i].offset );
if( in < 0 ) {
    printf( "\nError opening file %s\n", pfe[i].name );
    _close( out );
    exit( 1 );
}

/*
 * copy the data in the file
 */
while( 1 ) {
    bytes = _read( in, buff, BLOCK_SIZE );
    if( bytes == 0 ) {
break;
    }
    if( bytes < 0 ) {
printf( "\nError reading file %s\n", pfe[i].name );
_close( in );
_close( out );
exit( 1 );
    }
    outbytes = _write( out, buff, bytes );
    if( bytes != outbytes ) {
printf( "\nError writing output file\n" );
_close( in );
_close( out );
exit( 1 );
    }
    if( bytes < BLOCK_SIZE ) {
break;
    }
}
_close( in );
    }

    /*
     * get position of file end
     */
    pfe[i].offset = _lseek( out, 0, SEEK_CUR );

    /*
     * seek to the start of the directory (right after the # of entries)
     */
    pos = _lseek( out, sizeof( tmp ), SEEK_SET );
    if( pos != sizeof( tmp ) ) {
printf( "Seek error on output file\n" );
_close( out );
exit( 1 );
    }

    /*
     * re-write the directory with the offsets setup
     */
    bytes = _write( out, pfe, tmp * sizeof( FILEENTRY ) );
    if( bytes != (int) (tmp * sizeof( FILEENTRY )) ) {
printf( "Error writing output file\n" );
_close( out );
exit( 1 );
    }

    /*
     * all done
     */
    printf( "FastFile \"%s\" created:                   \n", fname );
    printf( "    %ld files\n", tmp );
    printf( "    %ld total file size\n", pfe[i].offset );

    _close( out );
    return 0;

} /* main */


FFENT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       ffent.h
 *  Content:FastFile entry definition (internal)
 *
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTBILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/

#pragma pack(1)
typedef struct {
    longoffset;
    charname[13];
} FILEENTRY, *LPFILEENTRY;
#pragma pack()


FASTFILE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       fastfile.c
 *  Content:Fast file I/O for large numbers of files.
 *Uses a single file built using FFCREATE.EXE; this file
 *contains a directory + all the files.
 *
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTBILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 ***************************************************************************/
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <search.h>
#include <stdio.h>
#include "fastfile.h"
#include "ffent.h"

#ifdef DEBUG
#define ODS( a ) OutputDebugString( a )
#else
#define ODS( a )
#endif

#ifdef __WATCOMC__
#define _stricmp stricmp
#endif

typedef struct {
    BOOLinuse;
    LONGpos;
    LONGsize;
    LPFILEENTRYpfe;
} FILEHANDLE, FAR *LPFILEHANDLE;

static int              LockCount;
static HANDLE           hFile;
static HANDLEhFileMapping;
static LPFILEENTRYpFE;
static LPBYTE           pBase;
static DWORDdwFECnt;
static LPFILEHANDLElpFH;
static DWORDdwFHCnt;
static longlFileEnd;

/*
 * Compare
 *
 * bsearch comparison routine
 */
int Compare( LPFILEENTRY p1, LPFILEENTRY p2 )
{
    return( _stricmp( (p1)->name,(p2)->name ) );

} /* Compare */

/*
 * FastFileInit
 *
 * Initialize for fast file access. The master file and maximum number
 * of open "files" are specified.
 */
BOOL FastFileInit( LPSTR fname, int max_handles )
{
    HRSRC  h;

    LockCount = 0;
    FastFileFini();

    /*
     * get a file handle array
     */
    lpFH = LocalAlloc( LPTR, max_handles * sizeof( FILEHANDLE ) );
    if( lpFH == NULL ) {
return FALSE;
    }
    dwFHCnt = max_handles;

    /*
     * try our resourse file first
     */
    if (h = FindResource(NULL, fname, RT_RCDATA))
    {
        pBase = LockResource(LoadResource(NULL, h));

        if (pBase == NULL)
        {
            ODS( "FastFileInit: unable to lock resource\r\n" );
            FastFileFini();
            return FALSE;
        }

        ODS( "FastFileInit: opened resource: "); ODS(fname); ODS("\r\n");
    }
    else
    {

        /*
         * create a memory mapped file for the master file
         */
        hFile = CreateFile( fname, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0 );

        if( hFile == NULL || hFile == (HANDLE)HFILE_ERROR )
{
            ODS( "FastFileInit: CreateFile(" ); ODS( fname ); ODS( ")\r\n" );
            hFile = NULL;
            FastFileFini();
            return FALSE;
        }
        hFileMapping = CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL );
        if( hFileMapping == NULL ) {
            ODS( "FastFileInit: CreateFileMapping Failed\r\n" );
            FastFileFini();
            return FALSE;
        }
        pBase = MapViewOfFile( hFileMapping, FILE_MAP_READ, 0, 0, 0 );
        if( pBase == NULL ) {
            ODS( "FastFileInit: MapViewOfFile Failed\r\n" );
            FastFileFini();
            return FALSE;
        }
    }

    /*
     * get initial data from the memory mapped file
     */
    dwFECnt = *((DWORD *) pBase);
    pFE = (LPFILEENTRY) (pBase + 4);
    lFileEnd = pFE[dwFECnt-1].offset;

    return TRUE;

} /* FastFileInit */

/*
 * FastFileFini
 *
 * Clean up resources
 */
void FastFileFini( void )
{
    //
    //  dont unmap things if we have locks out standing
    //
    if (LockCount != 0)
        return;

    if( hFileMapping != NULL && pBase ) {
        UnmapViewOfFile( pBase );
    }
    if( hFileMapping != NULL ) {
CloseHandle( hFileMapping );
hFileMapping = NULL;
    }
    if( hFile != NULL ) {
CloseHandle( hFile );
hFile = NULL;
    }
    if( lpFH != NULL ) {
LocalFree( lpFH );
lpFH = NULL;
    }
    dwFHCnt = 0;
    pBase = NULL;
    dwFECnt = 0;
    pFE = NULL;

} /* FastFileFini */

/*
 * FastFileOpen
 *
 * Search the directory for the file, and return a file handle if found.
 */
HFASTFILE FastFileOpen( LPSTR name )
{
    FILEENTRYfe;
    LPFILEENTRYpfe;

    if( pFE == NULL ) {
ODS( "FastFileOpen: not initialized\r\n" );
return NULL;
    }
    if( name == NULL || name[0] == 0 ) {
ODS( "FastFileOpen: invalid name\r\n" );
return NULL;
    }

    strcpy( fe.name, name );
    pfe = bsearch( &fe, pFE, dwFECnt, sizeof( FILEENTRY ), (LPVOID) Compare );
    if( pfe != NULL ) {
DWORDi;
for( i=0;i<dwFHCnt;i++ ) {
    if( !lpFH[i].inuse ) {
lpFH[i].inuse = TRUE;
lpFH[i].pos = pfe->offset;
lpFH[i].size = (pfe+1)->offset - pfe->offset;
lpFH[i].pfe = pfe;
return &lpFH[i];
    }
}
ODS( "FastFileOpen: Out of file handles\r\n" );
    } else {
ODS( "FastFileOpen: File \"" ); ODS( name ); ODS( "\" not found\r\n" );
    }

    return NULL;

} /* FastFileOpen */

/*
 * FastFileClose
 *
 * Mark a fast file handle as closed
 */
BOOL FastFileClose( LPFILEHANDLE pfh )
{
    if( pfh == NULL || pfh->inuse != TRUE ) {
ODS( "FastFileClose: invalid handle\r\n" );
return FALSE;
    }
    pfh->inuse = FALSE;
    return TRUE;

} /* FastFileClose */

/*
 * FastFileLock
 *
 * return a memory pointer into a fast file
 */
LPVOID FastFileLock( LPFILEHANDLE pfh, int pos, int size )
{
    if( pfh == NULL || pfh->inuse != TRUE ) {
        ODS( "FastFileLock: invalid handle\r\n" );
        return NULL;
    }
    if( size < 0 ) {
        ODS( "FastFileLock: invalid size\r\n" );
        return NULL;
    }
    if( (pos + size) > ((pfh->pfe)+1)->offset ) {
        ODS( "FastFileLock: read past end of file\r\n" );
        return NULL;
    }
    LockCount++;
    return pBase + pfh->pos + pos;

} /* FastFileLock */

/*
 * FastFileUnlock
 *
 */
BOOL FastFileUnlock( LPFILEHANDLE pfh, int pos, int size )
{
    if( pfh == NULL || pfh->inuse != TRUE ) {
        ODS( "FastFileUnlock: invalid handle\r\n" );
        return FALSE;
    }
    if( size < 0 ) {
        ODS( "FastFileUnlock: invalid size\r\n" );
        return FALSE;
    }
    if( (pos + size) > ((pfh->pfe)+1)->offset ) {
        ODS( "FastFileUnlock: read past end of file\r\n" );
        return FALSE;
    }

    LockCount--;
    return TRUE;

} /* FastFileUnlock */

/*
 * FastFileRead
 *
 * read from a fast file (memcpy!)
 */
BOOL FastFileRead( LPFILEHANDLE pfh, LPVOID ptr, int size )
{
    if( pfh == NULL || pfh->inuse != TRUE ) {
ODS( "FastFileRead: invalid handle\r\n" );
return FALSE;
    }
    if( size < 0 ) {
ODS( "FastFileRead: invalid size\r\n" );
return FALSE;
    }
    if( (pfh->pos + size) > ((pfh->pfe)+1)->offset ) {
ODS( "FastFileRead: read past end of file\r\n" );
return FALSE;
    }
    memcpy( ptr, pBase + pfh->pos, size );
    pfh->pos += size;
    return TRUE;

} /* FastFileRead */

/*
 * FastFileSeek
 *
 * Set to a new position in a fast file.  Uses standard SEEK_SET, SEEK_CUR,
 * SEEK_END definitions.
 */
BOOL FastFileSeek( LPFILEHANDLE pfh, int off, int how )
{
    LPFILEENTRYpfe;

    if( pfh == NULL || pfh->inuse != TRUE ) {
ODS( "FastFileSeek: invalid handle\r\n" );
return FALSE;
    }
    pfe = pfh->pfe;
    if( how == SEEK_SET ) {
if( off < 0 || off >= pfh->size ) {
    ODS( "FastFileSeek: Invalid offset\r\n" );
    return FALSE;
}
off += pfe->offset;
    } else if( how == SEEK_END ) {
if( off < 0 || off >= pfh->size ) {
    ODS( "FastFileSeek: Invalid offset\r\n" );
    return FALSE;
}
off = (pfe+1)->offset - off;
    } else if( how == SEEK_CUR ) {
off = pfh->pos + off;
if( off < pfe->offset || off >= (pfe+1)->offset ) {
    ODS( "FastFileSeek: Invalid offset\r\n" );
    return FALSE;
}
    } else {
ODS( "FastFileSeek: Invalid 'how' field\r\n" );
return FALSE;
    }
    pfh->pos = off;
    return TRUE;

} /* FastFileSeek */

/*
 * FastFileTell
 *
 * Get the current position in a fast file
 */
long FastFileTell( LPFILEHANDLE pfh )
{
    if( pfh == NULL || pfh->inuse != TRUE ) {
ODS( "FastFileTell: invalid handle\r\n" );
return -1;
    }
    return pfh->pos - pfh->pfe->offset;

} /* FastFileTell */


DEBUG.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.c
 *  Content:debug code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>
#include <dsound.h>
#include "debug.h"

#if defined(DEBUG) || defined(_DEBUG)

//
//  since we don't UNICODE our debugging messages, use the ASCII entry
//  points regardless of how we are compiled.
//
#ifdef _WIN32
    #include <wchar.h>
#else
    #define lstrcatA            lstrcat
    #define lstrlenA            lstrlen
    #define GetProfileIntA      GetProfileInt
    #define OutputDebugStringA  OutputDebugString
    #define wsprintfA           wsprintf
    #define MessageBoxA         MessageBox
#endif

//
//
//
BOOL    __gfDbgEnabled          = TRUE;         // master enable
UINT    __guDbgLevel            = 0;            // current debug level

//--------------------------------------------------------------------------;
//
//  void DbgVPrintF
//
//  Description:
//
//
//  Arguments:
//      LPSTR szFormat:
//
//      va_list va:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL DbgVPrintF
(
    LPSTR                   szFormat,
    va_list                 va
)
{
    char                ach[DEBUG_MAX_LINE_LEN];
    BOOL                fDebugBreak = FALSE;
    BOOL                fPrefix     = TRUE;
    BOOL                fCRLF       = TRUE;

    ach[0] = '\0';

    for (;;)
    {
        switch (*szFormat)
        {
            case '!':
                fDebugBreak = TRUE;
                szFormat++;
                continue;

            case '`':
                fPrefix = FALSE;
                szFormat++;
                continue;

            case '~':
                fCRLF = FALSE;
                szFormat++;
                continue;
        }

        break;
    }

    if (fDebugBreak)
    {
        ach[0] = '\007';
        ach[1] = '\0';
    }

    if (fPrefix)
    {
        lstrcatA(ach, DEBUG_MODULE_NAME ": ");
    }

#ifdef _WIN32
    wvsprintfA(ach + lstrlenA(ach), szFormat, va);
#else
    wvsprintf(ach + lstrlenA(ach), szFormat, (LPSTR)va);
#endif

    if (fCRLF)
    {
        lstrcatA(ach, "\r\n");
    }

    OutputDebugStringA(ach);

    if (fDebugBreak)
    {
        DebugBreak();
    }
} // DbgVPrintF()

//--------------------------------------------------------------------------;
//
//  void dprintf
//
//  Description:
//      dprintf() is called by the DPF() macro if DEBUG is defined at compile
//      time. It is recommended that you only use the DPF() macro to call
//      this function--so you don't have to put #ifdef DEBUG around all
//      of your code.
//
//  Arguments:
//      UINT uDbgLevel:
//
//      LPSTR szFormat:
//
//  Return (void):
//      No value is returned.
//
//--------------------------------------------------------------------------;

void FAR CDECL dprintf
(
    UINT                    uDbgLevel,
    LPSTR                   szFormat,
    ...
)
{
    va_list va;

    if (!__gfDbgEnabled || (__guDbgLevel < uDbgLevel))
        return;

    va_start(va, szFormat);
    DbgVPrintF(szFormat, va);
    va_end(va);
} // dprintf()

//--------------------------------------------------------------------------;
//
//  BOOL DbgEnable
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (BOOL):
//      Returns the previous debugging state.
//
//--------------------------------------------------------------------------;

BOOL WINAPI DbgEnable
(
    BOOL                    fEnable
)
{
    BOOL                fOldState;

    fOldState      = __gfDbgEnabled;
    __gfDbgEnabled = fEnable;

    return (fOldState);
} // DbgEnable()

//--------------------------------------------------------------------------;
//
//  UINT DbgSetLevel
//
//  Description:
//
//
//  Arguments:
//      UINT uLevel:
//
//  Return (UINT):
//      Returns the previous debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgSetLevel
(
    UINT                    uLevel
)
{
    UINT                uOldLevel;

    uOldLevel    = __guDbgLevel;
    __guDbgLevel = uLevel;

    return (uOldLevel);
} // DbgSetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgGetLevel
//
//  Description:
//
//
//  Arguments:
//      None.
//
//  Return (UINT):
//      Returns the current debugging level.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgGetLevel
(
    void
)
{
    return (__guDbgLevel);
} // DbgGetLevel()

//--------------------------------------------------------------------------;
//
//  UINT DbgInitialize
//
//  Description:
//
//
//  Arguments:
//      BOOL fEnable:
//
//  Return (UINT):
//      Returns the debugging level that was set.
//
//--------------------------------------------------------------------------;

UINT WINAPI DbgInitialize
(
    BOOL                    fEnable
)
{
    UINT                uLevel;

    uLevel = GetProfileIntA(DEBUG_SECTION, DEBUG_MODULE_NAME, (UINT)-1);
    if ((UINT)-1 == uLevel)
    {
        //
        //  if the debug key is not present, then force debug output to
        //  be disabled. this way running a debug version of a component
        //  on a non-debugging machine will not generate output unless
        //  the debug key exists.
        //
        uLevel  = 0;
        fEnable = FALSE;
    }

    DbgSetLevel(uLevel);
    DbgEnable(fEnable);

    return (__guDbgLevel);
} // DbgInitialize()

void WINAPI DbgDumpWFX( int nLevel, PWAVEFORMATEX pwfx )
    {
    DPF( nLevel, "Dumping WAVEFORMATEX at 0x%08lX", pwfx );
    DPF( nLevel, "wFormatTag = %u, nChannels = %u, nSamplesPerSec = %lu",
pwfx->wFormatTag, pwfx->nChannels, pwfx->nSamplesPerSec );
    DPF( nLevel, "nAvgBytesPerSec = %lu, nBlockAlign = %u, wBitsPerSample = %lu",
pwfx->nAvgBytesPerSec, pwfx->nBlockAlign, pwfx->wBitsPerSample );
    DPF( nLevel, "cbSize = %u",pwfx->cbSize );
    }

//--------------------------------------------------------------------------;
//
//  void _Assert
//
//  Description:
//      This routine is called if the ASSERT macro (defined in debug.h)
//      tests and expression that evaluates to FALSE.  This routine
//      displays an "assertion failed" message box allowing the user to
//      abort the program, enter the debugger (the "retry" button), or
//      ignore the assertion and continue executing.  The message box
//      displays the file name and line number of the _Assert() call.
//
//  Arguments:
//      char *  szFile: Filename where assertion occurred.
//      int     iLine:  Line number of assertion.
//
//--------------------------------------------------------------------------;

#ifndef _WIN32
#pragma warning(disable:4704)
#endif

void WINAPI _Assert
(
    char *  szFile,
    int     iLine
)
{
    static char     ach[300];       // debug output (avoid stack overflow)
    int            id;
#ifndef _WIN32
    int             iExitCode;
#endif

    wsprintfA(ach, "Assertion failed in file %s, line %d.  [Press RETRY to debug.]", (LPSTR)szFile, iLine);

    id = MessageBoxA(NULL, ach, "Assertion Failed",
            MB_SYSTEMMODAL | MB_ICONHAND | MB_ABORTRETRYIGNORE );

switch (id)
{

case IDABORT:               // Kill the application.
#ifndef _WIN32
        iExitCode = 0;
        _asm
        {
        movah, 4Ch
        moval, BYTE PTR iExitCode
        int     21h
        }
#else
        FatalAppExit(0, TEXT("Good Bye"));
#endif // WIN16
break;

case IDRETRY:               // Break into the debugger.
DebugBreak();
break;

case IDIGNORE:              // Ignore assertion, continue executing.
break;
}
} // _Assert

#ifndef _WIN32
#pragma warning(default:4704)
#endif

#endif // #if defined(DEBUG) || defined(_DEBUG)

// Silly little function gives meaningful error messages from HRESULT's
LPSTR TranslateDSError( HRESULT hr )
    {
    switch( hr )
{
case DSERR_ALLOCATED:
    return "DSERR_ALLOCATED";

case DSERR_CONTROLUNAVAIL:
    return "DSERR_CONTROLUNAVAIL";

case DSERR_INVALIDPARAM:
    return "DSERR_INVALIDPARAM";

case DSERR_INVALIDCALL:
    return "DSERR_INVALIDCALL";

case DSERR_GENERIC:
    return "DSERR_GENERIC";

case DSERR_PRIOLEVELNEEDED:
    return "DSERR_PRIOLEVELNEEDED";

case DSERR_OUTOFMEMORY:
    return "DSERR_OUTOFMEMORY";

case DSERR_BADFORMAT:
    return "DSERR_BADFORMAT";

case DSERR_UNSUPPORTED:
    return "DSERR_UNSUPPORTED";

case DSERR_NODRIVER:
    return "DSERR_NODRIVER";

case DSERR_ALREADYINITIALIZED:
    return "DSERR_ALREADYINITIALIZED";

case DSERR_NOAGGREGATION:
    return "DSERR_NOAGGREGATION";

case DSERR_BUFFERLOST:
    return "DSERR_BUFFERLOST";

case DSERR_OTHERAPPHASPRIO:
    return "DSERR_OTHERAPPHASPRIO";

case DSERR_UNINITIALIZED:
    return "DSERR_UNINITIALIZED";

default:
    return "Unknown HRESULT";
}
    }


DEBUG.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.h
 *  Content:debug header
 *
 ***************************************************************************/
#ifndef __DEBUG_INCLUDED__
#define __DEBUG_INCLUDED__
#ifdef __cplusplus
extern "C"
{
#endif

LPSTR TranslateDSError( HRESULT );

//
//
//
//
#if defined(DEBUG) || defined(_DEBUG)
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "FDFILTER"    // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef _WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#if defined(DEBUG) || defined(_DEBUG)
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);
    void WINAPI _Assert( char * szFile, int iLine );
    void WINAPI DbgDumpWFX( int, PWAVEFORMATEX );

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
    #define DPWFXDbgDumpWFX
    #define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__)
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0
    #define DbgDumpWFX(x,y)0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
    #define DPWFX()
    #define ASSERT(x)
#endif

#if defined(DEBUG) || defined(_DEBUG)
#define ASSERT_HWND( h )    ASSERT( IsWindow((h)) )
#else
#define ASSERT_HWND( h )    ASSERT( NULL != (h) )
#endif

#ifdef __cplusplus
}
#endif
#endif  // __DEBUG_INCLUDED__


FDAUDIO.CPP

//////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1996-1997 Microsoft Corporation. All Rights Reserved.
//
// File:FDAUDIO.CPP
// Contents:Audio related routines for full-duplex device usage
//
// Description:This file includes functionality for enumerating devices &
//formats, initializing devices and buffers, and the CALLBACK
//procedures for dealing with full duplex.
//

// So we can get the IID_IDirectSoundNotify and GUID_NULL GUID declared
#defineINITGUID
#include <objbase.h>
#include <cguid.h>

#include "fdfilter.h"
#include "fdaudio.h"
#include "filter.h"

static BOOL IsDSDeviceInList(LPGUID, PDSDEVICEDESC );
static BOOL EnumDSDevices( void );
static BOOL EnumDSCDevices( void );
static BOOL CALLBACK DSEnumProc( LPGUID, LPCSTR, LPCSTR, LPVOID );
static BOOL InitInputDevice(void);
static BOOL InitPrimarySoundBuffer( void );
static void ProcessInput( DWORD );

extern HINSTANCE g_hDSoundLib;
typedef HRESULT (WINAPI * PFN_DSCREATE)(LPGUID lpguid, LPDIRECTSOUND * ppDS, IUnknown FAR * pUnkOuter);
typedef BOOL (WINAPI * PFN_DSENUMERATE)(LPDSENUMCALLBACK lpDSEnumCallback, LPVOID lpContext);
typedef HRESULT (WINAPI * PFN_DSCCREATE)(LPGUID lpGUID,LPDIRECTSOUNDCAPTURE *lplpDSC,LPUNKNOWN pUnkOuter);
typedef BOOL (WINAPI * PFN_DSCENUMERATE)(LPDSENUMCALLBACK lpDSEnumCallback,LPVOID lpContext);

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
static BOOL InitSoundData( BOOL fAllowFormatCancel, BOOL fReset )
{
    if( NULL == gpwfxOutput )
{
gpwfxOutput = new WAVEFORMATEX;
if( NULL == gpwfxOutput )
    goto ISD_Fail;
ZeroMemory( gpwfxOutput, sizeof(WAVEFORMATEX));
gpwfxOutput->wFormatTag = WAVE_FORMAT_PCM;
DPF( 3, "Created gpwfxOutput" );
DPWFX( 3, gpwfxOutput );
}

    if( NULL == gpwfxInput )
{
gpwfxInput = new WAVEFORMATEX;
if( NULL == gpwfxInput )
    goto ISD_Fail;
ZeroMemory( gpwfxInput, sizeof(WAVEFORMATEX));
gpwfxInput->wFormatTag = WAVE_FORMAT_PCM;
DPF( 3, "Created gpwfxInput" );
DPWFX( 3, gpwfxInput );
}

    EnumDSDevices();
    if( NULL == gpdsddOutputDevices )
{
MessageBox( ghMainWnd, "No DirectSound devices are available!", gszAppName, MB_OK );
goto ISD_Fail;
}

    EnumDSCDevices();
if( NULL == gpdsddInputDevices )
{
MessageBox( ghMainWnd, "No DirectSoundCapture devices are available!", gszAppName, MB_OK );
goto ISD_Fail;
}

    if( !DialogBox( ghInst, MAKEINTRESOURCE(IDD_DEVICES),
    ghMainWnd, (DLGPROC)SelectDevicesDlgProc ))
goto ISD_Fail;

if( fReset )
{
    StopBuffers();
    DestroyBuffers();
    CloseDSCDevice(FALSE);
    CloseDSDevice();
}

if( !InitPrimarySoundBuffer())
goto ISD_Fail;

if( !InitInputDevice() )
goto ISD_Fail;

    if( !DialogBoxParam( ghInst, MAKEINTRESOURCE(IDD_FORMATS),
    ghMainWnd, (DLGPROC)SelectFormatsDlgProc,
    (LPARAM)fAllowFormatCancel ))
goto ISD_Fail;

    return TRUE;

ISD_Fail:
    return FALSE;
}

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL InitSoundDevices( BOOL fAllowFormatCancel )
    {
inti;
DWORDdwThreadID;

ZeroMemory(grgEventNotify, sizeof(grgEventNotify));
for( i = 0; i < NUM_EVENTS; i++ )
{
grgEventNotify[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if( NULL == grgEventNotify[i] )
goto ISD_Fail;
}

ghThread = CreateThread( NULL, 0, ThreadProcessInput,
 NULL, 0, &dwThreadID );
if( NULL == ghThread )
goto ISD_Fail;

return InitSoundData( fAllowFormatCancel, FALSE );

ISD_Fail:
    return FALSE;
    }

BOOL ReOpenSoundDevices( BOOL fAllowFormatCancel )
    {
return InitSoundData( fAllowFormatCancel, TRUE );
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
static BOOL IsDSDeviceInList( LPGUID lpguidDevice, PDSDEVICEDESC pdsddList )
    {
    PDSDEVICEDESC pdsdd = pdsddList;

    while( pdsdd )
{
// This works because operator== is overloaded for GUIDS
if( NULL == lpguidDevice )
    {
    if( pdsdd->guDevice == GUID_NULL )
return TRUE;
    }
else if( pdsdd->guDevice == *lpguidDevice )
    return TRUE;

pdsdd = pdsdd->pNext;
}
    return FALSE;
    }

//////////////////////////////////////////////////////////////////////////////
// EnumDSDevices()
//
//    Enumerates the DirectSound devices with the help of DirectSoundEnumerate
// and DSEnumProc.  Adds entries to a global list about each device.
//
static BOOL EnumDSDevices( void )
    {
PFN_DSENUMERATE pfn_DSEnumerate;

// Get a pointer to DirectSoundEnumerate()
pfn_DSEnumerate = (PFN_DSENUMERATE)GetProcAddress(g_hDSoundLib, "DirectSoundEnumerateA");

if (pfn_DSEnumerate == NULL)
{
        MessageBox(ghMainWnd, "DSOUND.DLL does not implement DirectSoundEnumerate()", gszAppName, MB_OK);
}
else
{
(*pfn_DSEnumerate)(DSEnumProc, &gpdsddOutputDevices);
}

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// EnumDSCDevices()
//
//    Enumerates the DirectSoundCapture devices with the help of
// DirectSoundCaptureEnumerate and DSEnumProc.
// Adds entries to a global list about each device.
//
static BOOL EnumDSCDevices( void )
    {
PFN_DSCENUMERATE pfn_DSCEnumerate;

// Get a pointer to DirectSoundEnumerate()
pfn_DSCEnumerate = (PFN_DSCENUMERATE)GetProcAddress(g_hDSoundLib, "DirectSoundCaptureEnumerateA");

if (pfn_DSCEnumerate == NULL)
{
        MessageBox(ghMainWnd, "DSOUND.DLL does not implement DirectSoundCaptureEnumerate(). The system may not have DirectX 5.0 installed.", gszAppName, MB_OK);
}
else
{
(*pfn_DSCEnumerate)(DSEnumProc, &gpdsddInputDevices);
}

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// InitInputDevice()
//
//    Creates and initializes the input device for the application.
//
static BOOL InitInputDevice( void )
{
HRESULTdsrval;
PFN_DSCCREATEpfn_DSCCreate;

// create a DirectSoundCapture object.
pfn_DSCCreate = (PFN_DSCCREATE)GetProcAddress(g_hDSoundLib, "DirectSoundCaptureCreate");
if (pfn_DSCCreate == NULL)
{
        MessageBox(ghMainWnd, "DSOUND.DLL does not implement DirectSoundCaptureCreate(). The system may not have DirectX 5.0 installed.", gszAppName, MB_OK);
goto IID_ExitError;
}
else
{
if (FAILED(dsrval = (*pfn_DSCCreate)(&gpdsddIn->guDevice, &gpdsc, NULL)))
{
DPF( 0, "Couldn't open DirectSoundCapture device (%s)", TranslateDSError(dsrval));
goto IID_ExitError;
}
}
return TRUE;

IID_ExitError:
return FALSE;
}

//////////////////////////////////////////////////////////////////////////////
// InitPrimarySoundBuffer()
//
//    Creates and initializes the primary sound buffer for the application.
// We need the primary buffer in order to get the 3D listener interface and
// also to select output format type.
//
static BOOL InitPrimarySoundBuffer( void )
    {
    HRESULT    dsrval;
    DSBUFFERDESC    dsbd;
PFN_DSCREATEpfn_DSCreate;

    ZeroMemory( &dsbd, sizeof(DSBUFFERDESC));

    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;

// Create a directsound object.
pfn_DSCreate = (PFN_DSCREATE)GetProcAddress(g_hDSoundLib, "DirectSoundCreate");
if (pfn_DSCreate == NULL)
{
MessageBox(ghMainWnd, "DSOUND.DLL does not implement DirectSoundCreate()", gszAppName, MB_OK);
goto IPSB_ExitError;
}
else
{
if (FAILED(dsrval = (*pfn_DSCreate)(&gpdsddOut->guDevice, &gpds, NULL)))
{
DPF( 0, "Couldn't open DirectSound device (%s)", TranslateDSError(dsrval));
goto IPSB_ExitError;
}
}

    if( FAILED( dsrval = gpds->SetCooperativeLevel( ghMainWnd, DSSCL_PRIORITY )))
{
DPF( 0, "Couldn't get PRIORITY cooperative level (%s)", TranslateDSError(dsrval));
goto IPSB_ExitError;
}

    if( FAILED( dsrval = gpds->CreateSoundBuffer( &dsbd, &gpdsbPrimary, NULL )))
{
DPF( 0, "Couldn't create primary buffer (%s)", TranslateDSError(dsrval));
goto IPSB_ExitError;
}

    return TRUE;

IPSB_ExitError:
    if( NULL != gpdsbPrimary )
{
DPF( 1, "Releasing Primary in InitPrimarySoundBuffer() error cleanup" );
gpdsbPrimary->Release();
gpdsbPrimary = NULL;
}
    if( NULL != gpds )
{
DPF( 1, "Releasing DSound object in InitPrimarySoundBuffer() error cleanup" );
gpds->Release();
gpds= NULL;
}

    return FALSE;
    }

//////////////////////////////////////////////////////////////////////////////
// DSEnumProc()
//
//   DirectSoundXXXEnumerate() callback procedure which fills the DSDEVICEDESC list
// with data about available devices.
//
static BOOL CALLBACK DSEnumProc( LPGUID lpguidDevice, LPCSTR lpszDesc,
                                LPCSTR lpszDrvName, LPVOID lpContext )
    {
    PDSDEVICEDESC   pdsddNew;
    PDSDEVICEDESC   *ppdsddList = (PDSDEVICEDESC *)lpContext;

    if( !IsDSDeviceInList( lpguidDevice, *ppdsddList ))
{
if(( pdsddNew = new DSDEVICEDESC ) == NULL )
    {
    return TRUE;
    }

ZeroMemory( pdsddNew, sizeof(DSDEVICEDESC));

if( NULL != lpguidDevice )
    pdsddNew->guDevice = *lpguidDevice;
else
    pdsddNew->guDevice = GUID_NULL;

if(( pdsddNew->pszDeviceDesc = new char[lstrlen(lpszDesc)+1]) == NULL )
    {
    delete pdsddNew;
    return TRUE;
    }
lstrcpy( pdsddNew->pszDeviceDesc, lpszDesc );

pdsddNew->pNext = *ppdsddList;
*ppdsddList = pdsddNew;
}

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ClearDSDeviceList( PDSDEVICEDESC *ppdsdd )
    {
    PDSDEVICEDESC   pdsddCur, pdsddTemp;

    pdsddCur = *ppdsdd;

    while( pdsddCur )
{
if( NULL != pdsddCur->pszDeviceDesc )
    delete[] pdsddCur->pszDeviceDesc;

pdsddTemp = pdsddCur->pNext;
delete pdsddCur;
pdsddCur = pdsddTemp;
}
    *ppdsdd = NULL;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void FillDeviceCombo( HWND hCombo, PDSDEVICEDESC pdsddDevices )
    {
    PDSDEVICEDESC   pdsdd = pdsddDevices;
    int    idx;

    if( NULL == hCombo )
return;

    while( pdsdd )
{
idx = ComboBox_InsertString( hCombo, -1, pdsdd->pszDeviceDesc );
ComboBox_SetItemData( hCombo, idx, pdsdd );
pdsdd = pdsdd->pNext;
}
    if( NULL != pdsddDevices )
ComboBox_SetCurSel( hCombo, 0 );
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ScanAvailableDSFormats( void )
    {
    WAVEFORMATEX    wfx;
    HRESULT    dsrval;
    HCURSOR    hCursor;
    int    i;

    DPF( 3, "Scanning %u DirectSound formats for availability", NUM_FORMATCODES );
    if( NULL == gpds || NULL == gpdsbPrimary )
{
        for( i = 0; i < NUM_FORMATCODES; i++ )
    aOutputFormats[i].fEnabled = FALSE;
return;
}

    // This might take a second or two, so throw up the hourglass
    hCursor = GetCursor();
    SetCursor( LoadCursor( NULL, IDC_WAIT ));

    ZeroMemory( &wfx, sizeof(wfx));
    wfx.wFormatTag = WAVE_FORMAT_PCM;

    for( i = 0; i < NUM_FORMATCODES; i++ )
{
FormatCodeToWFX( aOutputFormats[i].dwCode, &wfx );

if( FAILED( dsrval = gpdsbPrimary->SetFormat( &wfx )))
    {
    DPF( 5, "Failed with SetFormat() for %u format", aOutputFormats[i].dwCode );
    aOutputFormats[i].fEnabled = FALSE;
    }
else
    {
    DPF( 5, "Succeeded with SetFormat() for %u format", aOutputFormats[i].dwCode );
    aOutputFormats[i].fEnabled = TRUE;
    }
}
    SetCursor( hCursor );
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void ScanAvailableDSCFormats( void )
    {
    WAVEFORMATEX    wfx;
    HRESULT    dsrval;
    HCURSOR    hCursor;
    int    i;
DSCBUFFERDESCdscbd;
LPDIRECTSOUNDCAPTUREBUFFERpdscb;

    DPF( 3, "Scanning %u DirectSoundCapture formats for availability", NUM_FORMATCODES );
    if( NULL == gpdsc )
{
        for( i = 0; i < NUM_FORMATCODES; i++ )
    aInputFormats[i].fEnabled = FALSE;
return;
}

    // This might take a second or two, so throw up the hourglass
    hCursor = GetCursor();
    SetCursor( LoadCursor( NULL, IDC_WAIT ));

    for( i = 0; i < NUM_FORMATCODES; i++ )
aOutputFormats[i].fEnabled = FALSE;

    ZeroMemory( &wfx, sizeof(wfx));
    wfx.wFormatTag = WAVE_FORMAT_PCM;

    for( i = 0; i < NUM_FORMATCODES; i++ )
{
FormatCodeToWFX( aInputFormats[i].dwCode, &wfx );

ZeroMemory(&dscbd, sizeof(dscbd));
dscbd.dwSize = sizeof(dscbd);
dscbd.dwBufferBytes = wfx.nAvgBytesPerSec;
dscbd.lpwfxFormat = &wfx;

pdscb = NULL;

if ((dsrval = gpdsc->CreateCaptureBuffer(&dscbd, &pdscb, NULL)))
    {
    DPF( 5, "Failed with DirectSoundCapture::CreateCaptureBuffer() for %u format", aInputFormats[i].dwCode );
    aInputFormats[i].fEnabled = FALSE;
    }
else
    {
    DPF( 5, "Succeeded with DirectSoundCapture::CreateCaptureBuffer() for %u format", aInputFormats[i].dwCode );
    pdscb->Release();
    aInputFormats[i].fEnabled = TRUE;
    }
}

    SetCursor( hCursor );
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void CloseDSDevice( void )
    {
    if( NULL != gpdsbOutput )
{
gpdsbOutput->Release();
gpdsbOutput = NULL;
}
    if( NULL != gpdsbPrimary )
{
gpdsbPrimary->Release();
gpdsbPrimary = NULL;
}
    if( NULL != gpds )
{
gpds->Release();
gpds = NULL;
}
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
void CloseDSCDevice( BOOL fBufferOnly )
    {
if( NULL != gpdscbInput )
{
gpdscbInput->Release();
gpdscbInput = NULL;
}
if( (NULL != gpdsc) && !fBufferOnly )
{
gpdsc->Release();
gpdsc = NULL;
}
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL InitBuffers( void )
    {
    HRESULT    dsrval;
inti;
LPDIRECTSOUNDNOTIFYpdsn;

    if( gfBuffersInitialized )
return TRUE;
if (NULL == gpdscbInput)
return FALSE;

    EnterCriticalSection( &gcsBufferData );

ZeroMemory(grgdsbpn, sizeof(grgdsbpn));

for( i = 0; i < NUM_BUFFERS; i++ )
{
grgdsbpn[i].dwOffset = (gcbBufferSize * i) + gcbBufferSize - 1;
grgdsbpn[i].hEventNotify = grgEventNotify[i];
}
grgdsbpn[i].dwOffset = DSBPN_OFFSETSTOP;
grgdsbpn[i].hEventNotify = grgEventNotify[i];

if( FAILED( dsrval = gpdscbInput->QueryInterface(IID_IDirectSoundNotify, (LPVOID *)&pdsn ) ) )
{
DPF( 0, "Unable to QI for IDirectSoundNotify on input (%s)", TranslateDSError(dsrval));
goto Abort_InitBuffers;
}
dsrval = pdsn->SetNotificationPositions(NUM_POSNOTIFS, grgdsbpn);
pdsn->Release();
if( FAILED( dsrval ) )
{
DPF( 0, "Unable to SetNotificationPositions on input (%s)", TranslateDSError(dsrval));
goto Abort_InitBuffers;
}

ZeroMemory( &gdsbdOutput, sizeof(gdsbdOutput));
    gdsbdOutput.dwSize = sizeof(gdsbdOutput);
    gdsbdOutput.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS;
    gdsbdOutput.dwBufferBytes = NUM_BUFFERS * gcbBufferSize / 2;
    // We want the format of this secondary to match the format of the input
    // data.  This lets us blindly copy the data out and DSound worries about
    // matching the bits and channels to our desired primary buffer format.
    gdsbdOutput.lpwfxFormat = gpwfxInput;

    if( FAILED( dsrval = gpds->CreateSoundBuffer( &gdsbdOutput, &gpdsbOutput, NULL )))
{
DPF( 0, "Unable to create sound buffer for output (%s)", TranslateDSError(dsrval));
goto Abort_InitBuffers;
}

    InterlockedExchange( &gfBuffersInitialized, TRUE );
    LeaveCriticalSection( &gcsBufferData );
    return TRUE;

Abort_InitBuffers:
    // TODO: If there were any buffers put out, we must Reset to get them back
    ASSERT( DestroyBuffers());
    LeaveCriticalSection( &gcsBufferData );
    return FALSE;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL StartBuffers( void )
    {
    if( NULL == gpdscbInput || NULL == gpdsbOutput )
return FALSE;

    gpdscbInput->Start( DSCBSTART_LOOPING );

    // Rewind the output buffer, fill it with silence, and play it
    gpdsbOutput->SetCurrentPosition( 0 );
    WriteSilenceToOutput( 0, gdsbdOutput.dwBufferBytes );
    gpdsbOutput->Play( 0, 0, DSBPLAY_LOOPING );
    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL StopBuffers( void )
    {
    if( NULL == gpdscbInput || NULL == gpdsbOutput )
return FALSE;

    gpdscbInput->Stop();
    gpdsbOutput->Stop();

return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL DestroyBuffers( void )
    {
ZeroMemory( grgdsbpn, sizeof(grgdsbpn) );
    if( NULL != gpdsbOutput )
{
gpdsbOutput->Release();
gpdsbOutput = NULL;
}
    InterlockedExchange( &gfBuffersInitialized, FALSE );
    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
//
//
//
//
BOOL WriteSilenceToOutput( DWORD dwStart, DWORD cbLength )
    {
    PBYTE   pb1, pb2;
    DWORD   cb1, cb2;

    if(( !dwStart && !cbLength ) || NULL == gpdsbOutput
|| NULL == gdsbdOutput.lpwfxFormat )
return FALSE;

    if( SUCCEEDED( gpdsbOutput->Lock( dwStart, cbLength, (LPVOID*)&pb1, &cb1, (LPVOID*)&pb2, &cb2, 0 )))
{
FillMemory( pb1, cb1, (gdsbdOutput.lpwfxFormat->wBitsPerSample == 8) ? 128 : 0 );
if( NULL != pb2 && cb2 )
    FillMemory( pb2, cb2, (gdsbdOutput.lpwfxFormat->wBitsPerSample == 8) ? 128 : 0 );

gpdsbOutput->Unlock( pb1, cb1, pb2, cb2 );
return TRUE;
}

    return FALSE;
    }

static void ProcessInput( DWORD iEvent )
{
    staticDWORD dwWritePosition = 0xFFFFFFFF;
    HRESULTdsrval;
PBYTEpb1, pb2;
DWORDcb1, cb2;
BOOLfRestoredBuffer = FALSE;
LPBYTEpbInput1 = NULL;
DWORDcbInput1;
LPBYTEpbInput2;
DWORDcbInput2;
DWORDibInputStart;
DWORDcbInputEnd;

if( NULL == gpdscbInput )
{
return;
}

EnterCriticalSection( &gcsBufferData );

if( NUM_BUFFERS == iEvent )
{
dwWritePosition = 0xFFFFFFFF;
goto WIC_SkipDataProcessing;
}
else if( iEvent > 0 )
{
ibInputStart = grgdsbpn[iEvent-1].dwOffset+1;
}
else
{
ibInputStart = 0;
}
cbInputEnd = grgdsbpn[iEvent].dwOffset + 1 - ibInputStart;

dsrval = gpdscbInput->Lock(ibInputStart, cbInputEnd,
   (LPVOID *)&pbInput1, &cbInput1, (LPVOID *)&pbInput2, &cbInput2, 0);
if( FAILED(dsrval) )
{
DPF( 0, "Couldn't Lock input buffer. (%s)", TranslateDSError(dsrval));
goto WIC_SkipDataProcessing;
}

if( (NULL != pbInput2) || (0 != cbInput2) )
{
DPF( 0, "Second input buffer pointer is non-NULL");
}

if( NULL != gpdsbOutput )
{
DWORD dwStatus;
if( FAILED( dsrval = gpdsbOutput->GetStatus( &dwStatus )))
{
DPF( 0, "Unable to get buffer status. (%s)", TranslateDSError(dsrval));
goto WIC_SkipDataProcessing;
}
if( dwStatus & DSBSTATUS_BUFFERLOST )
{
if( FAILED( dsrval = gpdsbOutput->Restore()))
{
DPF( 0, "Couldn't Restore output buffer. (%s)", TranslateDSError(dsrval));
goto WIC_SkipDataProcessing;
}
dwWritePosition = 0;
fRestoredBuffer = TRUE;
}
else
{
if( dwWritePosition == 0xFFFFFFFF )
{
DWORDdwPlay, dwWrite;

gpdsbOutput->GetCurrentPosition( &dwPlay, &dwWrite );

// Need to add some extra to make sure we're ahead
// of the write cursor
dwWritePosition = dwWrite + (2*gcbBufferSize);
while( dwWritePosition >= gdsbdOutput.dwBufferBytes )
dwWritePosition -= gdsbdOutput.dwBufferBytes;
}
}

if( SUCCEEDED( gpdsbOutput->Lock( dwWritePosition,
cbInput1,
(LPVOID *)&pb1, &cb1, (LPVOID *)&pb2, &cb2, 0 )))
{
if( NULL != pb1 && NULL != gpFilter )
{
gpFilter->Transform( pbInput1, cb1, pb1 );
}
if( cb2 && NULL != pb2 && NULL != gpFilter )
{
gpFilter->Transform( pbInput1 + cb1,
cbInput1 - cb1, pb2 );
}
gpdsbOutput->Unlock( pb1, cb1, pb2,
cbInput1 - cb1 );
dwWritePosition += cbInput1;
while( dwWritePosition >= gdsbdOutput.dwBufferBytes )
dwWritePosition -= gdsbdOutput.dwBufferBytes;
}
if( fRestoredBuffer )
{
gpdsbOutput->SetCurrentPosition( 0 );
if( FAILED( dsrval = gpdsbOutput->Play( 0, 0, DSBPLAY_LOOPING )))
DPF( 0, "Unable to restart restored buffer. (%s)", TranslateDSError(dsrval));
dwWritePosition = 0xFFFFFFFF;
}
}
WIC_SkipDataProcessing:
if( NULL != pbInput1 )
gpdscbInput->Unlock(pbInput1, cbInput1, pbInput2, cbInput2);
LeaveCriticalSection( &gcsBufferData );
}

DWORD WINAPI ThreadProcessInput( LPVOID )
{
DWORDdwResult;

while( TRUE )
{
dwResult = WaitForMultipleObjects( NUM_EVENTS, grgEventNotify,
   FALSE, INFINITE );

// Terminate processing?
if( (WAIT_OBJECT_0 + iEventTerminate) == dwResult )
{
break;
}

if( (WAIT_OBJECT_0 <= dwResult) && (dwResult <= (WAIT_OBJECT_0 + iEventReset)) )
{
ProcessInput( dwResult - WAIT_OBJECT_0 );
}
}

return 0;
}


FDAUDIO.H

#ifndef __AUDIO_H__
#define __AUDIO_H__

BOOL InitSoundDevices( BOOL );
BOOL ReOpenSoundDevices( BOOL );
BOOL FormatCodeToText( DWORD, LPSTR, int );
BOOL FormatCodeToWFX( DWORD, PWAVEFORMATEX );
void DisableFormatCode( PFORMATDATA, DWORD );
BOOL WriteSilenceToOutput( DWORD, DWORD );

void ClearDSDeviceList( PDSDEVICEDESC * );
void FillDeviceCombo( HWND, PDSDEVICEDESC );

void ScanAvailableDSFormats( void );
void ScanAvailableDSCFormats( void );
void CloseDSDevice( void );
void CloseDSCDevice(BOOL fBufferOnly);

BOOL InitBuffers( void );
BOOL DestroyBuffers( void );
BOOL StartBuffers( void );
BOOL StopBuffers( void );

DWORD WINAPI ThreadProcessInput( LPVOID );

#endif


FDFILTER.CPP

//////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1996-1997 Microsoft Corporation. All Rights Reserved.
//
// File:FDFILTER.CPP
// Contents:General routines related to the Full-Duplex Filter Sample
//
// Description:This file includes the window procedures and initialization
//and destruction code.
//

#define __DEFINE_GLOBAL_VARIABLES__
#include "fdfilter.h"
#undef __DEFINE_GLOBAL_VARIABLES__
#include "fdaudio.h"
#include "filter.h"

#include <commctrl.h>

static BOOL InitInstance( HINSTANCE, HINSTANCE, LPSTR, int );

static void DestroyInstance( void );

HINSTANCE g_hDSoundLib;// module handle to dsound.dll.

///////////////////////////////////////////////////////////////////////////////
// DestroyInstance()
//
//    Does all of the cleanup for an instance of the application.
//
static void DestroyInstance( void )
    {
DestroyBuffers();
CloseDSCDevice(FALSE);
CloseDSDevice();
    if( NULL != gpwfxOutput )
{
delete gpwfxOutput;
gpwfxOutput = NULL;
}
    if( NULL != gpFilter )
{
delete gpFilter;
gpFilter = NULL;
}
    if( NULL != gpdsddOutputDevices )
{
ClearDSDeviceList( &gpdsddOutputDevices );
}

if( NULL != gpdsddInputDevices )
{
ClearDSDeviceList( &gpdsddInputDevices );
}

// wait for the thread to quit
if( NULL != ghThread )
{
// Set the Terminate Event so the thread will quit
SetEvent( grgEventNotify[iEventTerminate] );

WaitForSingleObject( ghThread, INFINITE );
CloseHandle( ghThread );
}

for(int i = 0; i < NUM_EVENTS; i++ )
{
if( NULL != grgEventNotify[i] )
CloseHandle( grgEventNotify[i] );
}
    // Get rid of the critical section object
    DeleteCriticalSection( &gcsBufferData );
    }

//////////////////////////////////////////////////////////////////////////////
// InitInstance()
//
//    This function is responsible for all initialization that must occur
// when a new instance of our application is started.
//
static BOOL InitInstance( HINSTANCE hInstance, LPSTR lpszCommandLine, int cmdShow )
    {
    // Used to protect buffer data from multi-threaded access
    InitializeCriticalSection( &gcsBufferData );

    if(( ghMainWnd = CreateDialog( ghInst, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)MainDlgProc )) == NULL )
{
DPF( 0, "Call to CreateDialog() failed." );
DestroyInstance();
return FALSE;
}

    HMENU hSysMenu = GetSystemMenu( ghMainWnd, FALSE );
    EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );

    /* Continue doing other initialization stuff */
    if( !InitSoundDevices( TRUE ))
{
DPF( 0, "Call to InitSoundDevices() failed." );
goto II_ExitError;
}

    ShowWindow( ghMainWnd, cmdShow );
    UpdateWindow( ghMainWnd );

    return TRUE;    // TRUE indicates success

II_ExitError:
    if( NULL != ghMainWnd )
{
DestroyWindow( ghMainWnd );
ghMainWnd = NULL;
}

    return FALSE;   // FALSE indicates failure on initialization
    }// InitInstance()

//////////////////////////////////////////////////////////////////////////////
// WinMain()
//
//   Main entry point for this program's execution.  Everything starts here.
//
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int cmdShow)
    {
    MSG   msg;

    DbgInitialize( TRUE );

    InitCommonControls();

    // Save instance handle
    ghInst = hInstance;

// Load dsound.dll
g_hDSoundLib = LoadLibrary("DSOUND.DLL");
if (g_hDSoundLib == NULL)
{
MessageBox(ghMainWnd, "Could not load DSOUND.DLL", gszAppName, MB_OK);
return 0;
}

    if (!InitInstance(hInstance, lpszCmdLine, cmdShow))
{
FreeLibrary(g_hDSoundLib);
return 0;
}

    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0))
{
// Only Translate and Dispatch the message if it's not going
// to one of our many modeless dialog windows
if( !IsDialogMessage( ghMainWnd, &msg ))
    {
    TranslateMessage((LPMSG)&msg);
    DispatchMessage((LPMSG)&msg);
    }
}

FreeLibrary(g_hDSoundLib);
    return (int)msg.wParam;
    }// WinMain()

//////////////////////////////////////////////////////////////////////////////
// MainDlgProc()
//
//    This the equivalent of a main window procedure, except our main window
// is really a dialog box.
//
BOOL CALLBACK MainDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    static HWNDhInputDeviceText, hInputFormatText, hEnableCheck, hFilterCombo;
    static HWNDhOutputDeviceText, hPrimaryFormatText, hSecondaryFormatText;

    DWORD   dwCode;
    int    ndx;

    switch( message )
{
case WM_INITDIALOG:
    hInputDeviceText = GetDlgItem( hDlg, IDC_MAIN_INPUTDEVICE_TEXT );
    hInputFormatText = GetDlgItem( hDlg, IDC_MAIN_INPUTFORMAT_TEXT );
    hOutputDeviceText = GetDlgItem( hDlg, IDC_MAIN_OUTPUTDEVICE_TEXT );
    hPrimaryFormatText = GetDlgItem( hDlg, IDC_MAIN_PRIMARYFORMAT_TEXT );
    hSecondaryFormatText = GetDlgItem( hDlg, IDC_MAIN_SECONDARYFORMAT_TEXT );
    hFilterCombo = GetDlgItem( hDlg, IDC_MAIN_FILTER_COMBO );
    hEnableCheck = GetDlgItem( hDlg, IDC_MAIN_ENABLEFILTER_CHECK );

    ndx = ComboBox_AddString( hFilterCombo, "None (pass-through)" );
    ComboBox_SetItemData( hFilterCombo, ndx, 0 );

    ComboBox_SetCurSel( hFilterCombo, ndx );

    ndx = ComboBox_AddString( hFilterCombo, "Gargle" );
    ComboBox_SetItemData( hFilterCombo, ndx, 1 );

    EnterCriticalSection( &gcsBufferData );
    if( NULL != gpFilter )
delete gpFilter;
    gpFilter = new CFilter;
    LeaveCriticalSection( &gcsBufferData );
    break;

case WM_PAINT:
    {
    PAINTSTRUCTps;
    charszFormat[64];

    BeginPaint( hDlg, &ps );
    if( NULL != gpdsddIn )
Static_SetText( hInputDeviceText, gpdsddIn->pszDeviceDesc );
    if( NULL != gpdsddOut )
Static_SetText( hOutputDeviceText, gpdsddOut->pszDeviceDesc );

    FormatCodeToText( gdwInputFormat, szFormat, sizeof(szFormat));
    Static_SetText( hInputFormatText, szFormat );

    FormatCodeToText( gdwOutputFormat, szFormat, sizeof(szFormat));
    Static_SetText( hPrimaryFormatText, szFormat );

    FormatCodeToText( gdwInputFormat, szFormat, sizeof(szFormat));
    Static_SetText( hSecondaryFormatText, szFormat );

    EndPaint( hDlg, &ps );
    }
    break;

case WM_COMMAND:
    switch( LOWORD(wParam))
{
case IDC_MAIN_FILTER_COMBO:
    if( HIWORD( wParam ) == CBN_SELCHANGE )
{
if(( ndx = ComboBox_GetCurSel( hFilterCombo )) == -1 )
    break;
dwCode = ComboBox_GetItemData( hFilterCombo, ndx );
if( 0 == dwCode )
    {
    EnterCriticalSection( &gcsBufferData );
    if( NULL != gpFilter )
delete gpFilter;
    gpFilter = new CFilter;
    LeaveCriticalSection( &gcsBufferData );
    }
if( 1 == dwCode )
    {
    EnterCriticalSection( &gcsBufferData );
    if( NULL != gpFilter )
delete gpFilter;
    gpFilter = (CFilter *)new CGargle( gpwfxInput->nSamplesPerSec,
gpwfxInput->nChannels,
gpwfxInput->wBitsPerSample / 8 );
    LeaveCriticalSection( &gcsBufferData );
    }
}
    break;

case IDC_MAIN_ENABLEFILTER_CHECK:
if (NULL == gpdscbInput)
break;
    if( Button_GetCheck( hEnableCheck ))
{
// Put our hands over our eyes and hold our breaths,
// here comes the data!
if( !gfBuffersInitialized )
    InitBuffers();
StartBuffers();
}
    else
{
StopBuffers();
}
    break;

case ID_SETTINGS_DEVICES:
    if( FAILED( ReOpenSoundDevices( FALSE )))
break;

    Button_SetCheck( hEnableCheck, FALSE );

    EnterCriticalSection( &gcsBufferData );
    if( gpFilter )
gpFilter->SetFormat( gpwfxInput );
    LeaveCriticalSection( &gcsBufferData );
    InvalidateRect( hDlg, NULL, FALSE );
    UpdateWindow( hDlg );
    break;

case ID_SETTINGS_FORMATS:
    if( Button_GetCheck( hEnableCheck ))
{
Button_SetCheck( hEnableCheck, FALSE );
StopBuffers();
}
CloseDSCDevice(TRUE);
    DestroyBuffers();

DialogBoxParam( ghInst, MAKEINTRESOURCE(IDD_FORMATS),
    ghMainWnd, (DLGPROC)SelectFormatsDlgProc, 0L );
    InvalidateRect( hDlg, NULL, FALSE );
    UpdateWindow( hDlg );

    EnterCriticalSection( &gcsBufferData );
    if( gpFilter )
gpFilter->SetFormat( gpwfxInput );
    LeaveCriticalSection( &gcsBufferData );
    break;

case ID_HELP_ABOUT:
    DialogBox( ghInst, MAKEINTRESOURCE(IDD_ABOUT),
    ghMainWnd, (DLGPROC)AboutDlgProc );
    break;

case ID_FILE_EXIT:
case IDCANCEL:
    // Be sure to stop the buffers when we're done
    StopBuffers();
    DestroyWindow( hDlg );
    break;

default:
    return FALSE;
}
    break;

case WM_DESTROY:
    DestroyInstance();
    PostQuitMessage(0);
    break;

default:
    return FALSE;
}

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// AboutDlgProc()
//
//    Small dialog procedure for the About Box.
//
BOOL CALLBACK AboutDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    if( message == WM_COMMAND && LOWORD(wParam) == IDOK )
{
EndDialog( hDlg, FALSE );
return TRUE;
}
    else
return FALSE;
    }

//////////////////////////////////////////////////////////////////////////////
// CancelWarningDlgProc()
//
//    Small dialog procedure for the Formats DLG's cancel warning box.
//
BOOL CALLBACK CancelWarningDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    if( message == WM_COMMAND && LOWORD(wParam) == IDOK )
{
EndDialog( hDlg, TRUE );
return TRUE;
}
    else if( message == WM_COMMAND && LOWORD(wParam) == IDCANCEL )
{
EndDialog( hDlg, FALSE );
return TRUE;
}
    else
return FALSE;
    }

//////////////////////////////////////////////////////////////////////////////
// FormatCodeToText()
//
//    This function reads format codes and puts out a text string for them.
// It does not check for invalid codes.  FALSE return means the buffer was
// invalid in some way, TRUE means success.
//
BOOL FormatCodeToText( DWORD dwFormat, LPSTR lpszBuf, int nBufSize )
    {
    DWORD   dwFreq;

    // The longest string we'll ever put in is 21 characters (including NULL)
    if( NULL == lpszBuf || nBufSize < 21 )
return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    dwFreq = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    wsprintf( lpszBuf, "%u Hz, %u-bit %s", dwFreq, FC_GETBITS(dwFormat),
FC_GETCHANNELS(dwFormat) == 1 ? "Mono" : "Stereo" );

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// FormatCodeToWFX()
//
//    This function reads format codes and fills most of the fields of a
// WAVEFORMATEX structure based on the values read.  It does not fill the
// wFormatTag or cbSize members.
//
BOOL FormatCodeToWFX( DWORD dwFormat, PWAVEFORMATEX pwfx )
    {
    DWORD   dwFreq;

    if( NULL == pwfx )
return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    pwfx->nSamplesPerSec = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    pwfx->wBitsPerSample = (WORD)FC_GETBITS(dwFormat);
    pwfx->nChannels = (WORD)FC_GETCHANNELS(dwFormat);

    // The nBlockAlign calculation below only works for whole-byte samples
    ASSERT( pwfx->wBitsPerSample % 8 == 0 );

    pwfx->nBlockAlign = pwfx->nChannels * (pwfx->wBitsPerSample / 8);
    pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;

    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// DisableFormatCode()
//
//    Disables the given format code
//
void DisableFormatCode( PFORMATDATA pfd, DWORD dwCode )
    {
    inti;

    for( i = 0; i < NUM_FORMATCODES; i++ )
{
if( pfd[i].dwCode == dwCode )
    {
    pfd[i].fEnabled = FALSE;
    break;
    }
}
    }

//////////////////////////////////////////////////////////////////////////////
// FillFormatListBox()
//
//    This little helper function takes an array FORMATDATA structures and
// fills the given ListBox with text/data entries that represent all the
// formats.
//
void FillFormatListBox( PFORMATDATA pfd, HWND hList )
    {
    char    szFormat[64];

    for( int i = 0; i < NUM_FORMATCODES; i++ )
{
if( pfd[i].fEnabled )
    {
    FormatCodeToText( pfd[i].dwCode, szFormat, sizeof(szFormat));
    ListBox_SetItemData( hList, ListBox_AddString( hList, szFormat),
    pfd[i].dwCode );
    }
}
    }

//////////////////////////////////////////////////////////////////////////////
// SelectDevicesDlgProc()
//
//    Procedure for the dialog which queries the user for device selection.
//
BOOL CALLBACK SelectDevicesDlgProc( HWND hDlg, UINT message,
    WPARAM wParam, LPARAM lParam )
    {
    static HWNDhDirectSoundCaptureCombo, hDirectSoundCombo;

    switch( message )
{
case WM_INITDIALOG:
    hDirectSoundCaptureCombo = GetDlgItem( hDlg, IDC_DEVICES_DIRECTSOUNDCAPTURE_COMBO );
    hDirectSoundCombo = GetDlgItem( hDlg, IDC_DEVICES_DIRECTSOUND_COMBO );

    FillDeviceCombo( hDirectSoundCaptureCombo, gpdsddInputDevices );
    FillDeviceCombo( hDirectSoundCombo, gpdsddOutputDevices );
    break;

case WM_COMMAND:
    switch( LOWORD(wParam))
{
case IDOK:
    gpdsddIn = (PDSDEVICEDESC)ComboBox_GetItemData( hDirectSoundCaptureCombo,
ComboBox_GetCurSel(hDirectSoundCaptureCombo));
    gpdsddOut = (PDSDEVICEDESC)ComboBox_GetItemData( hDirectSoundCombo,
ComboBox_GetCurSel(hDirectSoundCombo));
    EndDialog( hDlg, TRUE );
    break;
case IDCANCEL:
    EndDialog( hDlg, FALSE );
    break;
default:
    return FALSE;
}
    break;

default:
    return FALSE;
}
    return TRUE;
    }

//////////////////////////////////////////////////////////////////////////////
// SelectFormatsDlgProc()
//
//   Procedure the format selection dialog.
//
BOOL CALLBACK SelectFormatsDlgProc( HWND hDlg, UINT message,
    WPARAM wParam, LPARAM lParam )
    {
    static HWNDhOutputList, hInputList, hOK;
    static BOOLfAllowFormatCancel = FALSE;
    HRESULTdsrval;
    DWORDdwOutFormat, dwInFormat;
    intndx;

    switch( message )
{
case WM_INITDIALOG:
    hOutputList = GetDlgItem( hDlg, IDC_FORMATS_OUTPUT_LISTBOX );
    hInputList = GetDlgItem( hDlg, IDC_FORMATS_INPUT_LISTBOX );
    hOK = GetDlgItem( hDlg, IDOK );

    if( NULL == gpds || NULL == gpdsbPrimary || NULL == gpdsc )
{
EndDialog( hDlg, FALSE );
return FALSE;
}
    ScanAvailableDSFormats();
    FillFormatListBox( aOutputFormats, hOutputList );
    if( !ListBox_GetCount( hOutputList ))
{
MessageBox( ghMainWnd, "Warning: no output formats available",
gszAppName, MB_OK );
EndDialog( hDlg, FALSE );
return FALSE;
}
    // lParam is a BOOL flag, which is TRUE if cancel is "allowed". If
    // the app is running and the devices are selected, the user clearly
    // must select a set of formats or else end the application.
    fAllowFormatCancel = (BOOL)lParam;

    break;

case WM_COMMAND:
    switch( LOWORD(wParam))
{
case IDC_FORMATS_INPUT_LISTBOX:
    if( ListBox_GetCurSel( hOutputList ) != -1 &&
    ListBox_GetCurSel( hInputList ) != -1 )
Button_Enable( hOK, TRUE );
    else
Button_Enable( hOK, FALSE );
    break;

case IDC_FORMATS_OUTPUT_LISTBOX:
    if( HIWORD(wParam) == LBN_SELCHANGE )
{
dwOutFormat = ListBox_GetItemData( hOutputList,
    ListBox_GetCurSel( hOutputList ));
FormatCodeToWFX( dwOutFormat, gpwfxOutput );
DPF( 3, "Formats DLG: Setting gpwfxOutput" );
DPWFX( 3, gpwfxOutput );
if( FAILED( dsrval = gpdsbPrimary->SetFormat( gpwfxOutput )))
    {
    DPF( 1, "Formats DLG: Failed to set DSound primary to %lu (%s)",
dwOutFormat, TranslateDSError(dsrval));
    FormatCodeToWFX( gdwOutputFormat, gpwfxOutput );
    DPF( 1, "Formats DLG: Attempting to reset gpwfxOutput" );
    DPWFX( 1, gpwfxOutput );
    if( FAILED( dsrval = gpdsbPrimary->SetFormat( gpwfxOutput )))
{
DPF( 0, "Formats DLG: Failed to set OLD format (%lu) on primary (%s)",
    gdwOutputFormat, gpwfxOutput );
// TODO: Finish this handler
}
    ndx = ListBox_FindItemData( hOutputList, -1, gdwOutputFormat );
    ListBox_SetCurSel( hOutputList, ndx );
    // TODO: Find the item corresponding to the old format
    //       and select it back? (without re-entering this code)
    }
else
    {
    gdwOutputFormat = dwOutFormat;

    ScanAvailableDSCFormats();
    ListBox_ResetContent( hInputList );
    FillFormatListBox( aInputFormats, hInputList );
    }
Button_Enable( hOK, FALSE );
}
    break;

case IDOK:
    ndx = ListBox_GetCurSel( hOutputList );
    // OK button should be disabled unless there's a selection
    // in both listboxes
    ASSERT( ndx != -1 );

    dwOutFormat = ListBox_GetItemData( hOutputList, ndx );
    FormatCodeToWFX( dwOutFormat, gpwfxOutput );
    // We shouldn't get to this dialog if there's no output
    ASSERT( NULL != gpds && NULL != gpdsbPrimary );
    if(( dsrval = gpdsbPrimary->SetFormat( gpwfxOutput )))
{
DPF( 0, "Can't set format %lu (%s)",
dwOutFormat, TranslateDSError(dsrval));
goto Abort_OK;
}
    else
{
gdwOutputFormat = dwOutFormat;
}

    ndx = ListBox_GetCurSel( hInputList );
    // OK button should be disabled unless there's a selection
    // in both listboxes
    ASSERT( ndx != -1 );

    dwInFormat = ListBox_GetItemData( hInputList, ndx );
    FormatCodeToWFX( dwInFormat, gpwfxInput );

{
DSCBUFFERDESCdscbd = {0};

gcbBufferSize = max( 4096, gpwfxInput->nAvgBytesPerSec / 8 );
gcbBufferSize -= gcbBufferSize % gpwfxInput->nBlockAlign;
DPF( 1, "Set input buffer size to %lu bytes", gcbBufferSize );

dscbd.dwSize = sizeof(dscbd);
dscbd.dwBufferBytes = gcbBufferSize * NUM_BUFFERS;
dscbd.lpwfxFormat = gpwfxInput;

if ((dsrval = gpdsc->CreateCaptureBuffer(&dscbd, &gpdscbInput, NULL)))
{
DPF( 0, "Unable to open selected input device! format = %lu, dsrval = %lu", dwInFormat, dsrval );
goto Abort_OK;
}
else
{
gdwInputFormat = dwInFormat;
}
}

    EndDialog( hDlg, TRUE );
    Abort_OK:
    break;
case IDCANCEL:
    if( fAllowFormatCancel )
EndDialog( hDlg, FALSE );
    else
{
if( !DialogBox( ghInst,
MAKEINTRESOURCE(IDD_CANCELWARNING),
ghMainWnd, (DLGPROC)CancelWarningDlgProc ))
    {
    EndDialog( hDlg, FALSE );
    PostMessage( ghMainWnd, WM_DESTROY, 0, 0 );
    return FALSE;
    }
}
    break;

default:
    return FALSE;
}
    break;

default:
    return FALSE;
}
    return TRUE;
    }


FDFILTER.H

#ifndef __FDFILTER_H__
#define __FDFILTER_H__

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <d3dtypes.h>

#include "debug.h"
#include "resource.h"

class CFilter;
class CGargle;

#define REG_SETTINGS_KEY    TEXT("\\Software\\Microsoft\\DFilter")
#define REG_SETTING_INPUTDEVICE    TEXT("InputDevice");
#define REG_SETTING_OUTPUTDEVICE    TEXT("OutputDevice");
#define REG_SETTING_INFORMAT    TEXT("InputFormat");
#define REG_SETTING_OUTFORMAT    TEXT("OutputFormat");

typedef struct tag_dsdevdesc
{
    GUID    guDevice;    // Device GUID
    PSTR    pszDeviceDesc;    // Description string
    struct tag_dsdevdesc *pNext;
} DSDEVICEDESC, *PDSDEVICEDESC;

typedef struct tag_fd
{
    DWORD   dwCode;
    BOOL    fEnabled;
} FORMATDATA, *PFORMATDATA;

#define NUM_FORMATCODES    (16)

#define FC_GETFREQCODE(fc)((fc) / 1000)
#define FC_GETBITS(fc)((fc) % 100)
#define FC_GETCHANNELS(fc)(((fc) % 1000) / 100)

#define NUM_BUFFERS(16)
#defineNUM_EVENTS_CONTROL(2)
#defineNUM_POSNOTIFS(NUM_BUFFERS + 1)
#define NUM_EVENTS(NUM_BUFFERS + NUM_EVENTS_CONTROL)
#defineiEventReset(NUM_BUFFERS)
#defineiEventTerminate(iEventReset + 1)

#ifdef __DEFINE_GLOBAL_VARIABLES__

FORMATDATA   aInputFormats[NUM_FORMATCODES] = {{ 8108, TRUE }, { 8208, TRUE },
    { 8116, TRUE }, { 8216, TRUE },
    { 11108, TRUE }, { 11208, TRUE },
    { 11116, TRUE }, { 11216, TRUE },
    { 22108, TRUE }, { 22208, TRUE },
    { 22116, TRUE }, { 22216, TRUE },
    { 44108, TRUE }, { 44208, TRUE },
    { 44116, TRUE }, { 44216, TRUE }};

FORMATDATA   aOutputFormats[NUM_FORMATCODES] = {{ 8108, TRUE }, { 8208, TRUE },
    { 8116, TRUE }, { 8216, TRUE },
    { 11108, TRUE }, { 11208, TRUE },
    { 11116, TRUE }, { 11216, TRUE },
    { 22108, TRUE }, { 22208, TRUE },
    { 22116, TRUE }, { 22216, TRUE },
    { 44108, TRUE }, { 44208, TRUE },
    { 44116, TRUE }, { 44216, TRUE }};

LPDIRECTSOUND    gpds;
LPDIRECTSOUNDBUFFER gpdsbPrimary;
LPWAVEFORMATEX    gpwfxInput, gpwfxOutput;
LPDIRECTSOUNDCAPTUREgpdsc;

char    gszAppName[] = "FDFilter";
char    gszAppDescription[] = "Full-Duplex Filter Sample";
HINSTANCE    ghInst;
HWND    ghMainWnd;

DWORD    gdwOutputFormat, gdwInputFormat, gcbBufferSize;
PDSDEVICEDESC    gpdsddOutputDevices, gpdsddOut;
PDSDEVICEDESC    gpdsddInputDevices, gpdsddIn;

CRITICAL_SECTION    gcsBufferData;
LONG    gfBuffersInitialized;
LPDIRECTSOUNDBUFFER gpdsbOutput;
DSBUFFERDESC    gdsbdOutput;
CFilter *    gpFilter;

LPDIRECTSOUNDCAPTUREBUFFERgpdscbInput;
DSBPOSITIONNOTIFYgrgdsbpn[NUM_POSNOTIFS];
HANDLEgrgEventNotify[NUM_EVENTS];
HANDLEghThread;

#else

extern FORMATDATA    aInputFormats[], aOutputFormats[];
extern LPDIRECTSOUND    gpds;
extern LPDIRECTSOUNDBUFFER  gpdsbPrimary;
extern LPWAVEFORMATEX    gpwfxInput, gpwfxOutput;
extern LPDIRECTSOUNDCAPTUREgpdsc;

extern char    gszAppName[];
extern char    gszAppDescription[];
extern HINSTANCE    ghInst;
extern HWND    ghMainWnd;

extern DWORD    gdwOutputFormat, gdwInputFormat, gcbBufferSize;
extern PDSDEVICEDESC    gpdsddOutputDevices, gpdsddOut;
extern PDSDEVICEDESC    gpdsddInputDevices, gpdsddIn;

extern CRITICAL_SECTION    gcsBufferData;
extern LONG    gfBuffersInitialized;
extern LPDIRECTSOUNDBUFFER  gpdsbOutput;
extern DSBUFFERDESC    gdsbdOutput;
extern CFilter *    gpFilter;

extern LPDIRECTSOUNDCAPTUREBUFFERgpdscbInput;
extern DSBPOSITIONNOTIFYgrgdsbpn[NUM_POSNOTIFS];
extern HANDLEgrgEventNotify[NUM_EVENTS];
extern HANDLEghThread;
#endif

int PASCAL WinMain( HINSTANCE, HINSTANCE, LPSTR, int );
BOOL CALLBACK MainDlgProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK AboutDlgProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK CancelWarningDlgProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK SelectDevicesDlgProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK SelectFormatsDlgProc( HWND, UINT, WPARAM, LPARAM );

void FillFormatListBox( PFORMATDATA, HWND );

#endif// __FDFILTER_H__


FILTER.CPP

#include "fdfilter.h"
#include "filter.h"

HRESULT CFilter::Transform( PBYTE pbIn, int cbLength, PBYTE pbOut )
    {
    if( NULL == pbIn || NULL == pbOut )
return ResultFromScode( E_POINTER );
    if( 0 == cbLength )
return NOERROR;

    CopyMemory( pbOut, pbIn, cbLength );
    return NOERROR;
    }

// Merely stubbed out for this simple filter -- we have no need for the
// extra information
HRESULT CFilter::SetFormat( PWAVEFORMATEX pwfx )
    {
    if( NULL == pwfx )
return ResultFromScode( E_POINTER );

    return NOERROR;
    }


FILTER.H

#ifndef __FILTER_H__
#define __FILTER_H__

class CFilter
    {
    public:
virtual HRESULT Transform( PBYTE, int, PBYTE );
virtual HRESULT SetFormat( PWAVEFORMATEX );
    };

class CGargle : public CFilter
    {
    public:
CGargle( int nFreq = 0, int nChannels = 0, int nBytesPerSample = 0 );
~CGargle();
virtual HRESULT Transform( PBYTE, int, PBYTE );
virtual HRESULT SetFormat( PWAVEFORMATEX );

    protected:
enum Shape { SHAPE_TRIANGLE, SHAPE_SQUARE };

int m_nPhase, m_nGargleRate;
int m_nBytesPerSample, m_nSamplesPerSec, m_nChannels;

Shapem_Shape;

void MessItAbout( PBYTE, DWORD, PBYTE );
    };

class C3DPosition : public CFilter
    {
    public:
virtual HRESULT Transform( PWAVEHDR );

    protected:
//GetPosition( D3DVECTOR * );
    };

#endif//__FILTER_H__


GARGLE.CPP

#include "fdfilter.h"
#include "filter.h"

CGargle::CGargle( int nFreq, int nChannels, int nBytesPerSample )
    {
    m_nPhase = 0;
    m_Shape = SHAPE_TRIANGLE;
    m_nGargleRate = 10;
    m_nBytesPerSample = nBytesPerSample;
    m_nSamplesPerSec = nFreq;
    m_nChannels = nChannels;
    }

//Below resutls in cl error C2600 - the compiler
//has already generated a default destructor due
//to the abstract delcaration of the CGargle class
//in FDFILTER.H
//CGargle::~CGargle()
//    {
//    }

// Copy the required information about the new format for the buffer
HRESULT CGargle::SetFormat( PWAVEFORMATEX pwfx )
    {
    if( NULL == pwfx )
        return ResultFromScode( E_POINTER );

    m_nSamplesPerSec = pwfx->nSamplesPerSec;
    m_nChannels = pwfx->nChannels;
    m_nBytesPerSample = pwfx->wBitsPerSample / 8;

    return NOERROR;
    }

//
// MessItAbout
//
// Mess the sound about by modulating it with a waveform.
// We know the frequency of the modulation (from the slider setting
// which we were told through our internal interface, IGargle, and
// which we stored in m_GargleRate).  At the end of the call we
// record what part of the waveform we finished at in m_Phase and
// we resume at that point next time.
// Uses and updates m_Phase
// Uses m_SamplesPerSec, m_Channels, m_GargleRate, m_Shape
//
void CGargle::MessItAbout( PBYTE pbIn, DWORD cb, PBYTE pbOut )
{
    // We know how many samples per sec and how
    // many channels so we can calculate the modulation period in samples.
    //

    int Period = (m_nSamplesPerSec * m_nChannels) / m_nGargleRate;

    while( cb > 0 ) {
        --cb;

        // If m_Shape is 0 (triangle) then we multiply by a triangular waveform
        // that runs 0..Period/2..0..Period/2..0... else by a square one that
        // is either 0 or Period/2 (same maximum as the triangle) or zero.
        //
        {
            // m_Phase is the number of samples from the start of the period.
            // We keep this running from one call to the next,
            // but if the period changes so as to make this more
            // than Period then we reset to 0 with a bang.  This may cause
            // an audible click or pop (but, hey! it's only a sample!)
            //
            ++m_nPhase;
            if( m_nPhase > Period )
                m_nPhase = 0;

            int M = m_nPhase;      // m is what we modulate with

            if( m_Shape == SHAPE_TRIANGLE ) {   // Triangle
                if( M > Period / 2 )
                    M = Period - M;  // handle downslope
            } else if( m_Shape == SHAPE_TRIANGLE ) {             // Square wave
                if( M <= Period / 2 )
                    M = Period / 2;
                else M = 0;
            }

            if( m_nBytesPerSample == 1 ) {
                // 8 bit sound uses 0..255 representing -128..127
                // Any overflow, even by 1, would sound very bad.
                // so we clip paranoically after modulating.
                // I think it should never clip by more than 1
                //
                int i = *pbIn - 128;            // sound sample, zero based
                i = (i * M * 2) / Period;       // modulate
                if(i > 127)                     // clip
                    i = 127;
                if(i < -128)
                    i = -128;

               *pbOut = (unsigned char)(i + 128);       // reset zero offset to 128

            } else if( m_nBytesPerSample == 2 ) {
                // 16 bit sound uses 16 bits properly (0 means 0)
                // We still clip paranoically
                //
                short int *psi = (short int *)pbOut;
                int i = *((short int *)pbIn);   // in a register, we might hope
                i = (i*M*2)/Period;     // modulate
                if(i > 32767)
                    i = 32767;          // clip
                if(i < -32768)
                    i = -32768;
                *psi = (short)i;
                ++pbIn; // nudge it on another 8 bits here to get a 16 bit step
                ++pbOut;
                --cb;   // and nudge the count too.
            } else {
//               DbgBreak("Too many bytes per sample");
               // just leave it alone!
            }
        }
        ++pbIn;   // move on 8 bits to next sound sample
        ++pbOut;
    }
} // MessItAbout

//
// Transform
//
//
HRESULT CGargle::Transform( PBYTE pbIn, int cbWrite, PBYTE pbOut )
{
    // Actually transform the data
    //
    if( m_nSamplesPerSec && m_nChannels && m_nBytesPerSample )
        MessItAbout( pbIn, cbWrite, pbOut );

    return NOERROR;

} // Transform


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by fdfilter.rc
//
#define IDD_MAIN                        101
#define IDM_MAIN                        102
#define IDD_ABOUT                       103
#define IDI_MAIN                        104
#define IDD_DEVICES                     106
#define IDD_FORMATS                     107
#define IDD_CANCELWARNING               108
#define IDC_MAIN_INPUT_LISTBOX          1000
#define IDC_MAIN_OUTPUT_LISTBOX         1001
#define IDC_MAIN_WAVEOUT_RADIO          1002
#define IDC_MAIN_DIRECTSOUND_RADIO      1003
#define IDC_MAIN_3DPOSITION_RADIO       1004
#define IDC_MAIN_GARGLE_RADIO           1005
#define IDC_MAIN_ECHO_RADIO             1006
#define IDC_DEVICES_DIRECTSOUNDCAPTURE_COMBO 1009
#define IDC_DEVICES_DIRECTSOUND_COMBO   1010
#define IDC_FORMATS_OUTPUT_LISTBOX      1010
#define IDC_FORMATS_INPUT_LISTBOX       1011
#define IDC_MAIN_INPUTDEVICE_TEXT       1012
#define IDC_MAIN_INPUTFORMAT_TEXT       1013
#define IDC_MAIN_OUTPUTDEVICE_TEXT      1014
#define IDC_MAIN_PRIMARYFORMAT_TEXT     1015
#define IDC_MAIN_FILTER_COMBO           1016
#define IDC_MAIN_ENABLEFILTER_CHECK     1017
#define IDC_MAIN_SECONDARYFORMAT_TEXT   1018
#define ID_FILE_EXIT                    40001
#define ID_HELP_ABOUT                   40002
#define ID_SETTINGS_DEVICES             40003
#define ID_SETTINGS_FORMATS             40004

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        109
#define _APS_NEXT_COMMAND_VALUE         40005
#define _APS_NEXT_CONTROL_VALUE         1018
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


FFDONUTS.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ffdonuts.c
 *  Content:    DirectInput ForceFeedback sample
 *
 *
 ***************************************************************************/

#define INITGUID

#include "ffdonuts.h"

LPDIRECTDRAWSURFACE     lpFrontBuffer;
LPDIRECTDRAWSURFACE     lpBackBuffer;
LPDIRECTDRAWSURFACE     lpDonut;
LPDIRECTDRAWSURFACE     lpPyramid;
LPDIRECTDRAWSURFACE     lpCube;
LPDIRECTDRAWSURFACE     lpSphere;
LPDIRECTDRAWSURFACE     lpShip;
LPDIRECTDRAWSURFACE     lpNum;
LPDIRECTDRAW            lpDD;
LPDIRECTDRAWPALETTE     lpArtPalette;
LPDIRECTDRAWPALETTE     lpSplashPalette;
BOOL                    bPlayIdle = FALSE;
BOOL                    bPlayBuzz = FALSE;
BOOL                    bPlayRev = FALSE;
DWORD                   lastInput = 0;
BOOL                    lastThrust = FALSE;
BOOL                    lastShield = FALSE;
int                     showDelay = 0;
HWND                    hWndMain;
BOOL                    bShowFrameCount=TRUE;
BOOL                    bIsActive;
BOOL                    bMouseVisible;
DWORD                   dwFrameCount;
DWORD                   dwFrameTime;
DWORD                   dwFrames;
DWORD                   dwFramesLast;
BOOL                    bUseEmulation;
BOOL                    bTest=FALSE;
BOOL                    bStress=FALSE;
DWORD                   dwTransType;
RGBQUAD                 SPalette[256];
DWORD                   lastTickCount;
int                     score;
int                     ProgramState;
int                     level;
int                     restCount;
DWORD                   dwFillColor;
BOOL                    bSpecialEffects = FALSE;
DWORD                   ShowLevelCount = 3000;
DWORD                   ScreenX;
DWORD                   ScreenY;
DWORD                   ScreenBpp;
#ifdef USE_DSOUND
BOOL                    bWantSound = TRUE;  //global hack to turn off sound
BOOL                    bSoundEnabled = FALSE;
#endif

int getint(char**p, int def);

#ifdef DEBUG
char                    DebugBuf[256];
BOOL                    bHELBlt = FALSE;
#endif

DBLNODE                 DL;             // Display List

#ifdef USE_DSOUND
LPDIRECTSOUND           lpDS;
HSNDOBJ                 hsoBeginLevel     = NULL;
HSNDOBJ                 hsoEngineIdle     = NULL;
HSNDOBJ                 hsoEngineRev      = NULL;
HSNDOBJ                 hsoSkidToStop     = NULL;
HSNDOBJ                 hsoShieldBuzz     = NULL;
HSNDOBJ                 hsoShipExplode    = NULL;
HSNDOBJ                 hsoFireBullet     = NULL;
HSNDOBJ                 hsoShipBounce     = NULL;
HSNDOBJ                 hsoDonutExplode   = NULL;
HSNDOBJ                 hsoPyramidExplode = NULL;
HSNDOBJ                 hsoCubeExplode    = NULL;
HSNDOBJ                 hsoSphereExplode  = NULL;
#endif

void setup_game(void)
{
    restCount = GetTickCount();
    initLevel( ++level );
    // set the palette
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpArtPalette );
}

/*
 * MainWndproc
 *
 * Callback for all Windows messages
 */
long FAR PASCAL MainWndproc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
        bIsActive = (BOOL) wParam;
        if( bIsActive )
        {
            bMouseVisible = FALSE;
            lastTickCount = GetTickCount();
            bSpecialEffects = FALSE;
            // we have just been reactivated...
            // time to acquire our device(s) again
            inputAcquireDevices();
        }
        else
        {
            bMouseVisible = TRUE;
        }
        break;

    case WM_CREATE:
        break;

    case WM_SETCURSOR:
        if( !bMouseVisible )
        {
            SetCursor(NULL);
        }
        else
        {
            SetCursor(LoadCursor( NULL, IDC_ARROW ));
        }
        return TRUE;

    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_F3:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                if( bSoundEnabled )
                {
                    DestroySound();
                }
                else
                {
                    InitializeSound();
                }
            }
#endif
            break;
        case VK_F5:
            bShowFrameCount = !bShowFrameCount;
            if( bShowFrameCount )
            {
                dwFrameCount = 0;
                dwFrameTime = timeGetTime();
            }
            break;
        case VK_RETURN:
            if( ProgramState == PS_SPLASH )
            {
                ProgramState = PS_BEGINREST;
                setup_game();
            }
            break;
        case VK_ESCAPE:
        case VK_F12:
            PostMessage( hWnd, WM_CLOSE, 0, 0 );
            return 0;
        case VK_F1:
            bSpecialEffects = !bSpecialEffects;
            break;
        }
        break;

    case WM_KEYUP:
        switch( wParam )
        {
        case VK_NUMPAD7:
            lastInput &= ~KEY_SHIELD;
            break;
        case VK_NUMPAD5:
            lastInput &= ~KEY_STOP;
            break;
        case VK_DOWN:
        case VK_NUMPAD2:
            lastInput &= ~KEY_DOWN;
            break;
        case VK_LEFT:
        case VK_NUMPAD4:
            lastInput &= ~KEY_LEFT;
            break;
        case VK_RIGHT:
        case VK_NUMPAD6:
            lastInput &= ~KEY_RIGHT;
            break;
        case VK_UP:
        case VK_NUMPAD8:
            lastInput &= ~KEY_UP;
            break;
        case VK_NUMPAD3:
            lastInput &= ~KEY_THROW;
            break;
        case VK_SPACE:
            lastInput &= ~KEY_FIRE;
            break;
        }
        break;

    case WM_ERASEBKGND:
        return 1;

    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
        EndPaint( hWnd, &ps );
        return 1;

    case WM_DESTROY:
        lastInput=0;
        DestroyGame();
        inputCleanupDirectInput();
        PostQuitMessage( 0 );
        break;

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

} /* MainWndproc */

/*
 * initApplication
 *
 * Do that Windows initialization stuff...
 */
static BOOL initApplication( HANDLE hInstance, int nCmdShow )
{
    WNDCLASS    wc;
    BOOL        rc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndproc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, "DONUTS_ICON");
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "DonutsClass";
    rc = RegisterClass( &wc );
    if( !rc )
    {
        return FALSE;
    }

    hWndMain = CreateWindowEx(0,  // WS_EX_TOPMOST,
        "DonutsClass",
        "Donuts",
        WS_VISIBLE | // so we don't have to call ShowWindow
        WS_POPUP |   // non-app window
        WS_SYSMENU,  // so we get an icon in the tray
        0,
        0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    UpdateWindow( hWndMain );

    return TRUE;

} /* initApplication */

/*
 * WinMain
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                        int nCmdShow )
{
    MSG     msg;

    while( lpCmdLine[0] == '-' )
    {
        lpCmdLine++;

        switch (*lpCmdLine++)
        {
        case 'e':
            bUseEmulation = TRUE;
            break;
        case 't':
            bTest = TRUE;
            break;
        case 'x':
            bStress= TRUE;
            bTest = TRUE;
            break;
        }
        while( IS_SPACE(*lpCmdLine) )
        {
            lpCmdLine++;
        }
    }

    ScreenX = getint(&lpCmdLine, 640);
    ScreenY = getint(&lpCmdLine, 480);
    ScreenBpp = getint(&lpCmdLine, 8);

    if( !initApplication(hInstance, nCmdShow) )
    {
        return FALSE;
    }

    // initialize DirectInput functionality
    if( !inputInitDirectInput(hInstance, hWndMain) )
    {
        inputCleanupDirectInput();
        DestroyWindow( hWndMain );
        return FALSE;
    }

    if( !InitializeGame() )
    {
        DestroyWindow( hWndMain );
        return FALSE;
    }

    dwFrameTime = timeGetTime();

    while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( !GetMessage( &msg, NULL, 0, 0 ) )
            {
                return msg.wParam;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if ( bIsActive )
        {
            UpdateFrame();
        }
        else
        {
            WaitMessage();
        }
    }
} /* WinMain */

void DestroyGame( void )
{
}

BOOL InitializeGame( void )
{
    DDCAPS          ddcaps;
    HRESULT         ddrval;
    DDSURFACEDESC   ddsd;
    DDSCAPS         ddscaps;
#ifdef NT_HACK
    DDSURFACEDESC DDSurfDesc;
#endif

    score = 0;
    if( bTest )
        ShowLevelCount = 1000;

    if( bUseEmulation )
        ddrval = DirectDrawCreate( (LPVOID) DDCREATE_EMULATIONONLY, &lpDD, NULL );
    else
        ddrval = DirectDrawCreate( NULL, &lpDD, NULL );

    if( ddrval != DD_OK )
        return CleanupAndExit("DirectDrawCreate Failed!");

    ddrval = lpDD->lpVtbl->SetCooperativeLevel( lpDD, hWndMain,
                            DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
        return CleanupAndExit("SetCooperativeLevel Failed");

    #ifdef NT_HACK
        DDSurfDesc.dwSize = sizeof(DDSurfDesc);
        ddrval = lpDD->lpVtbl->GetDisplayMode(lpDD,&DDSurfDesc);
        if(ddrval == DD_OK)
        ScreenBpp = DDSurfDesc.ddpfPixelFormat.dwRGBBitCount;
    #endif

    // set the mode
    ddrval = lpDD->lpVtbl->SetDisplayMode( lpDD, ScreenX, ScreenY, ScreenBpp );
    if( ddrval != DD_OK )
        return CleanupAndExit("SetDisplayMode Failed!");

    // check the color key hardware capabilities
    dwTransType = DDBLTFAST_SRCCOLORKEY;
    ddcaps.dwSize = sizeof( ddcaps );

#ifdef DEBUG
    if( GetProfileInt( "Donuts", "force_dest_blt", 0) )
    {
        dwTransType = DDBLTFAST_DESTCOLORKEY;
    }
    bHELBlt = GetProfileInt( "Donuts", "force_HEL_blt", bHELBlt );
#endif

    // Create surfaces
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpFrontBuffer, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface FrontBuffer Failed!");

    // get a pointer to the back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpFrontBuffer->lpVtbl->GetAttachedSurface(
                lpFrontBuffer,
                &ddscaps,
                &lpBackBuffer );
    if( ddrval != DD_OK )
        return CleanupAndExit("GetAttachedDurface Failed!");

    ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
#ifdef DEBUG
    if( bHELBlt )
        ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
#endif
    ddsd.dwWidth = 320;
    ddsd.dwHeight = 384;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpDonut, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpDonut Failed!");

    ddsd.dwHeight = 128;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpPyramid, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpPyramid Failed!");

    ddsd.dwHeight = 32;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpCube, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpCube Failed!");

    ddsd.dwHeight = 32;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpSphere, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpSphere Failed!");
    // Set the background color fill color

    ddsd.dwHeight = 256;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpShip, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpShip Failed!");

    ddsd.dwHeight = 16;
    ddrval = lpDD->lpVtbl->CreateSurface( lpDD, &ddsd, &lpNum, NULL );
    if( ddrval != DD_OK )
        return CleanupAndExit("CreateSurface lpNum Failed!");

    if( !RestoreSurfaces() )
        return CleanupAndExit("RestoreSurfaces Failed!");

    DL.next = DL.prev = &DL;            // null display list
    DL.type = OBJ_SHIP;
    DL.surf = lpShip;
    lastTickCount = GetTickCount();

#ifdef USE_DSOUND
    if(bWantSound)
    {
        InitializeSound();
    }
#endif

    if(bTest)
    {
        ProgramState = PS_ACTIVE;
        setup_game();
    }
    else
    {
        ProgramState = PS_SPLASH;
    }
    return TRUE;
}

BOOL CleanupAndExit( char *err)
{
#ifdef DEBUG
    wsprintf(DebugBuf, "___CleanupAndExit  err = %s\n", err );
    OutputDebugString( DebugBuf );
#endif

    // make the cursor visible
    SetCursor(LoadCursor( NULL, IDC_ARROW ));
    bMouseVisible = TRUE;

    if( lpDonut != NULL )
        lpDonut->lpVtbl->Release( lpDonut );

    if( lpPyramid != NULL )
        lpPyramid->lpVtbl->Release( lpPyramid );

    if( lpCube != NULL )
        lpCube->lpVtbl->Release( lpCube );

    if( lpSphere != NULL )
        lpSphere->lpVtbl->Release( lpSphere );

    if( lpShip != NULL )
        lpShip->lpVtbl->Release( lpShip );

    if( lpNum != NULL )
        lpNum->lpVtbl->Release( lpNum );

    if( lpFrontBuffer != NULL )
        lpFrontBuffer->lpVtbl->Release( lpFrontBuffer );

    if( lpArtPalette != NULL )
        lpArtPalette->lpVtbl->Release( lpArtPalette );

    if( lpSplashPalette != NULL )
        lpSplashPalette->lpVtbl->Release( lpSplashPalette );

    if( lpDD != NULL )
        lpDD->lpVtbl->Release( lpDD );

    //
    // warn user if there is one
    //

    if( !bStress )
    {
        MessageBox( hWndMain, err, "ERROR", MB_OK );
    }
    return FALSE;
}

void bltSplash( void )
{
    HRESULT     ddrval;
    HBITMAP     hbm;

    // set the palette before loading the splash screen
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpSplashPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), "SPLASH", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if ( NULL == hbm )
        return;

    // if the surface is lost, DDCopyBitmap will fail and the surface will
    // be restored in FlipScreen.
    ddrval = DDCopyBitmap( lpBackBuffer, hbm, 0, 0, 0, 0 );

    DeleteObject( hbm );

    FlipScreen();
}

#ifdef USE_DSOUND
//
// play a sound, but first set the panning according to where the
// object is on the screen.  fake 3D sound.
//
void playPanned(HSNDOBJ hSO, DBLNODE *object)
{
    IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(hSO);

        if(!bWantSound)
                return;   // No sound our Work is done

    if (pDSB)
    {
        switch(ScreenX)
        {
        case 320:
            IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
                ((object->dst.right + object->dst.left) / 2) / 320.0) - 10000.0));
            break;
        case 640:
            IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
                ((object->dst.right + object->dst.left) / 2) / 640.0) - 10000.0));
            break;
        case 1024:
            IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
                ((object->dst.right + object->dst.left) / 2) / 1024.0) - 10000.0));
            break;
        case 1280:
            IDirectSoundBuffer_SetPan(pDSB, (LONG)((20000.0 *
                ((object->dst.right + object->dst.left) / 2) / 1280.0) - 10000.0));
            break;
        }

        IDirectSoundBuffer_Play(pDSB, 0, 0, 0);
    }
}
#endif

void UpdateFrame( void )
{
    switch( ProgramState )
    {
        case PS_SPLASH:
            // display the splash screen
            bltSplash();
            return;
        case PS_ACTIVE:
            UpdateDisplayList();
            CheckForHits();
            DrawDisplayList();
            if ( isDisplayListEmpty() )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjStop(hsoEngineIdle);
                    SndObjStop(hsoEngineRev);
                }
#endif
                bPlayIdle = FALSE;
                bPlayRev = FALSE;
                lastThrust = lastShield = FALSE;
                ProgramState = PS_BEGINREST;
                restCount = GetTickCount();
                initLevel( ++level );
            }
            return;
        case PS_BEGINREST:
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjPlay(hsoBeginLevel, 0);
            }
#endif
            ProgramState = PS_REST;
            //
            // FALLTHRU
            //
        case PS_REST:
            if( ( GetTickCount() - restCount ) > ShowLevelCount )
            {
#ifdef USE_DSOUND
                if(bWantSound)
                {
                    SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);
                }
#endif
                bPlayIdle = TRUE;
                lastTickCount = GetTickCount();
                ProgramState = PS_ACTIVE;
            }
            else
            {
                DisplayLevel();
            }
            return;
    }
}

void DisplayLevel( void )
{
    char buf[10];

    EraseScreen();
    buf[0] = 10 + '0';
    buf[1] = 11 + '0';
    buf[2] = 12 + '0';
    buf[3] = 11 + '0';
    buf[4] = 10 + '0';
    buf[5] = '\0';
    bltScore( buf, ScreenX/2-64, ScreenY/2-8 );
    buf[0] = level / 100 + '0';
    buf[1] = level / 10 + '0';
    buf[2] = level % 10 + '0';
    buf[3] = '\0';
    bltScore( buf, ScreenX/2+22, ScreenY/2-8 );
    FlipScreen();
}

void bltScore( char *num, int x, int y )
{
    char *c;
    RECT    src;
    int     i;
    HRESULT ddrval;

    for(c=num; *c != '\0'; c++)
    {
        while( 1 )
        {
            i = *c - '0';
            src.left = i*16;
            src.top = 0;
            src.right = src.left + 16;
            src.bottom = src.top + 16;
            ddrval = lpBackBuffer->lpVtbl->BltFast( lpBackBuffer, x, y, lpNum, &src, dwTransType );
            if( ddrval == DD_OK )
            {
                break;
            }
            if( ddrval == DDERR_SURFACELOST )
            {
                if( !RestoreSurfaces() )
                    return;
            }
            if( ddrval != DDERR_WASSTILLDRAWING )
            {
                return;
            }
        }
        x += 16;
    }
}

void CheckForHits( void )
{
    LPDBLNODE   bullet, target, save;
    int         frame, x, y, l, t;
    BOOL        hit;

    // update screen rects
    target = &DL;
    do
    {
        frame = (DWORD)target->frame;
        switch( target->type )
        {
            case OBJ_DONUT:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 64;
                target->dst.bottom = target->dst.top + 64;
                target->src.left = 64 * (frame % 5);
                target->src.top = 64 * (frame /5);
                target->src.right = target->src.left + 64;
                target->src.bottom = target->src.top + 64;
                break;
            case OBJ_PYRAMID:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 32;
                target->dst.bottom = target->dst.top + 32;
                target->src.left = 32 * (frame % 10);
                target->src.top = 32 * (frame /10);
                target->src.right = target->src.left + 32;
                target->src.bottom = target->src.top + 32;
                break;
            case OBJ_SPHERE:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 16;
                target->dst.bottom = target->dst.top + 16;
                target->src.left = 16 * (frame % 20);
                target->src.top = 16 * (frame /20);
                target->src.right = target->src.left + 16;
                target->src.bottom = target->src.top + 16;
                break;
            case OBJ_CUBE:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 16;
                target->dst.bottom = target->dst.top + 16;
                target->src.left = 16 * (frame % 20);
                target->src.top = 16 * (frame /20);
                target->src.right = target->src.left + 16;
                target->src.bottom = target->src.top + 16;
                break;
            case OBJ_SHIP:
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 32;
                target->dst.bottom = target->dst.top + 32;
                if( lastShield )
                    target->src.top = 32 * (frame / 10) + 128;
                else
                    target->src.top = 32 * (frame /10);
                target->src.left = 32 * (frame % 10);
                target->src.right = target->src.left + 32;
                target->src.bottom = target->src.top + 32;
                break;
            case OBJ_BULLET:
                frame = (DWORD)target->frame/20 % 4;
                target->dst.left = (DWORD)target->posx;
                target->dst.top = (DWORD)target->posy;
                target->dst.right = target->dst.left + 3;
                target->dst.bottom = target->dst.top + 3;
                target->src.left = BULLET_X + frame*4;
                target->src.top = BULLET_Y;
                target->src.right = target->src.left + 3;
                target->src.bottom = target->src.top + 3;
                break;
        }
        target = target->next;
    }
    while( target != &DL );

    bullet=&DL;
    do
    {
        hit = FALSE;
        if((bullet->type != OBJ_BULLET) && (bullet != &DL))
        {
            bullet = bullet->next;
            continue;
        }

        x = (bullet->dst.left + bullet->dst.right) / 2;
        y = (bullet->dst.top + bullet->dst.bottom) / 2;
        for(target=DL.next; target != &DL; target = target->next)
        {
            if( ( target->type != OBJ_DONUT ) &&
                ( target->type != OBJ_PYRAMID ) &&
                ( target->type != OBJ_SPHERE ) &&
                ( target->type != OBJ_CUBE ) )
                continue;

            if( (x >= target->dst.left) &&
                (x <  target->dst.right) &&
                (y >= target->dst.top) &&
                (y <  target->dst.bottom) )
            {
                if ((bullet != &DL) || !lastShield)
                {
                    // the bullet hit the target
                    switch( target->type )
                    {
                    case OBJ_DONUT:
#ifdef USE_DSOUND
                        if(bWantSound)
                        {
                            playPanned(hsoDonutExplode, target);
                        }
#endif
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_PYRAMID, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        score += 10;
                        break;
                    case OBJ_PYRAMID:
#ifdef USE_DSOUND
                        if(bWantSound)
                        {
                            playPanned(hsoPyramidExplode, target);
                        }
#endif
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_CUBE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_CUBE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        score += 20;
                        break;
                    case OBJ_CUBE:
#ifdef USE_DSOUND
                        if(bWantSound)
                        {
                            playPanned(hsoCubeExplode, target);
                        }
#endif
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, target->dst.left,
                            target->dst.top, -1.0, -1.0 );
                        break;
                        score += 40;
                    case OBJ_SPHERE:
#ifdef USE_DSOUND
                        if(bWantSound)
                        {
                            playPanned(hsoSphereExplode, target);
                        }
#endif
                        score += 20;
                    }

                    l = target->dst.left;
                    t = target->dst.top;
                    DeleteFromList( target );
                }

                hit = TRUE;
            }

            if( hit )
            {
                if( bullet == &DL )
                {
                    hit = FALSE;
                    if (!lastShield && !showDelay && !bTest)
                    {
                        // ship has exploded
#ifdef USE_DSOUND
                        if(bWantSound)
                        {
                            playPanned(hsoShipExplode, bullet);
                        }
#endif
                        inputPlayEffect(EF_EXPLODE, 0);

                        score -= 150;
                        if (score < 0)
                            score = 0;

                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_SPHERE, l, t, -1.0, -1.0 );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        addObject( OBJ_BULLET, l, t,
                            randDouble( -0.5, 0.5 ), randDouble( -0.5, 0.5 ) );
                        initShip(TRUE);
                    }
                }

                break;
            }
        }

        if( hit )
        {
            save = bullet;
            bullet = bullet->next;

            DeleteFromList( save );
        }
        else
        {
            bullet = bullet->next;
        }

    } while (bullet != &DL);
}

void EraseScreen( void )
{
    DDBLTFX     ddbltfx;
    HRESULT     ddrval;

    if( bSpecialEffects )   // cool looking screen with no colorfill
        return;

    // Erase the background
    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = dwFillColor;
    while( 1 )
    {
        ddrval = lpBackBuffer->lpVtbl->Blt( lpBackBuffer, NULL, NULL,
                 NULL, DDBLT_COLORFILL, &ddbltfx );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if( !RestoreSurfaces() )
                return;
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }
}

void FlipScreen( void )
{
    HRESULT     ddrval;

    // Flip the surfaces
    while( 1 )
    {
        ddrval = lpFrontBuffer->lpVtbl->Flip( lpFrontBuffer, NULL, 0 );
        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if( !RestoreSurfaces() )
            {
                return;
            }
        }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            break;
        }
    }
}

void DrawDisplayList( void )
{
    LPDBLNODE   this;
    LPDBLNODE   last;
    HRESULT     ddrval;
    char        scorebuf[11];
    int         rem;

    // blt everything in reverse order if we are doing destination transparency
    // calculate score string
    scorebuf[0] = score/10000000 + '0';
    rem = score % 10000000;
    scorebuf[1] = rem/1000000 + '0';
    rem = score % 1000000;
    scorebuf[2] = rem/100000 + '0';
    rem = score % 100000;
    scorebuf[3] = rem/10000 + '0';
    rem = score % 10000;
    scorebuf[4] = rem/1000 + '0';
    rem = score % 1000;
    scorebuf[5] = rem/100 + '0';
    rem = score % 100;
    scorebuf[6] = rem/10 + '0';
    rem = score % 10;
    scorebuf[7] = rem + '0';
#ifdef USE_DSOUND
    if( bSoundEnabled )
    {
        scorebuf[8] = 14 + '0';
        scorebuf[9] = 13 + '0';
            scorebuf[10] = '\0';
    }
    else
#endif
    {
        scorebuf[8] = '\0';
    }

    EraseScreen();
    if( dwTransType == DDBLTFAST_DESTCOLORKEY )
    {
        bltScore(scorebuf, 10, ScreenY-26);

        if( bShowFrameCount )
            DisplayFrameRate();

        this = DL.next; // start with the topmost bitmap
        last = DL.next; // don't blt it twice

        if (showDelay)
            last = &DL;
    }
    else
    {
        this = &DL;     // start with the bottommost bitmap (the ship)
        last = &DL;     // don't blt it twice

        if (showDelay)
            this = this->prev;
    }

    do
    {
        while( 1 )
        {
            ddrval = lpBackBuffer->lpVtbl->BltFast( lpBackBuffer, this->dst.left, this->dst.top, this->surf, &(this->src), dwTransType );
            if( ddrval == DD_OK )
            {
                break;
            }
            if( ddrval == DDERR_SURFACELOST )
            {
                if( !RestoreSurfaces() )
                    return;
            }
            if( ddrval != DDERR_WASSTILLDRAWING )
            {
                return;
            }
        }
        if( dwTransType != DDBLTFAST_DESTCOLORKEY )
        {
            this = this->prev;
        }
        else
        {
            this = this->next;
        }
    }
    while( this != last );

    if( dwTransType != DDBLTFAST_DESTCOLORKEY )
    {
        bltScore(scorebuf, 10, ScreenY-26);

        if( bShowFrameCount )
            DisplayFrameRate();
    }

    FlipScreen();
}

void DisplayFrameRate( void )
{
    DWORD               time2;
    char                buff[256];

    dwFrameCount++;
    time2 = timeGetTime() - dwFrameTime;
    if( time2 > 1000 )
    {
        dwFrames = (dwFrameCount*1000)/time2;
        dwFrameTime = timeGetTime();
        dwFrameCount = 0;
    }
    if( dwFrames == 0 )
    {
        return;
    }

    if (dwFrames != dwFramesLast)
    {
        dwFramesLast = dwFrames;
    }

    if( dwFrames > 99 )
    {
        dwFrames = 99;
    }
    buff[0] = (char)((dwFrames / 10) + '0');
    buff[1] = (char)((dwFrames % 10) + '0');
    buff[2] = '\0';
    bltScore(buff, ScreenX/2-25, 10);
}

void DeleteFromList( LPDBLNODE this )
{
    this->next->prev = this->prev;
    this->prev->next = this->next;
    LocalFree( this );
}

void UpdateDisplayList( void )
{
    LPDBLNODE   this;
    LPDBLNODE   save;
    DWORD       thisTickCount = GetTickCount();
    DWORD       tickDiff = thisTickCount - lastTickCount;
    double      maxx, maxy;
    double      maxframe;
    DWORD       input = lastInput;
    BOOL        event = FALSE;
    BOOL        fBounce = FALSE;
    LONG        lAngle = 0;

    if( bTest )
    {
        input |= (KEY_RIGHT | KEY_FIRE);
    }
    lastTickCount = thisTickCount;

    // get the state of the input device
    input = inputProcessDeviceInput();

    if (showDelay)
    {
        showDelay -= (int)tickDiff;
        if (showDelay < 0)
        {
            showDelay = 0;
            lastShield = FALSE;
            initShip( FALSE );
        }
    }

    // update the ship
    if( !showDelay )
    {
        DL.posx += DL.velx * (double)tickDiff;
        DL.posy += DL.vely * (double)tickDiff;
    }
    if( DL.posx > MAX_SHIP_X )
    {
        DL.posx = MAX_SHIP_X;
        DL.velx = -DL.velx;
        event = TRUE;
        // set the bounce angle
        // (bouncing off the right edge of the screen)
        fBounce = TRUE;
        lAngle = 90;
    }
    else if ( DL.posx < 0 )
    {
        DL.posx =0;
        DL.velx = -DL.velx;
        event = TRUE;
        // set the bounce angle
        // (bouncing off the left edge of the screen)
        fBounce = TRUE;
        lAngle = 270;
    }
    if( DL.posy > MAX_SHIP_Y )
    {
        DL.posy = MAX_SHIP_Y;
        DL.vely = -DL.vely;
        event = TRUE;
        // set the bounce angle
        // (bouncing off the bottom edge of the screen)
        fBounce = TRUE;
        lAngle = 180;
    }
    else if ( DL.posy < 0 )
    {
        DL.posy =0;
        DL.vely = -DL.vely;
        event = TRUE;
        // set the bounce angle
        // (bouncing off the top edge of the screen)
        fBounce = TRUE;
        lAngle = 0;
    }

    if (event)
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
            playPanned(hsoShipBounce, &DL);
        }
#endif
        event = FALSE;
    }

    if(fBounce)
    {
        // "bounce" the ship
        inputPlayEffect(EF_BOUNCE, lAngle);
    }

    if ((event = (showDelay || ((input & KEY_SHIELD) == KEY_SHIELD))) !=
        lastShield)
    {
        if (event && !showDelay)
        {
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);
            }
#endif
            bPlayBuzz = TRUE;
        }
        else
        {
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjStop(hsoShieldBuzz);
            }
#endif
            bPlayBuzz = FALSE;
        }
        lastShield = event;
    }
    if (event)
    {
        input &= ~(KEY_FIRE);
    }

    if (input & KEY_FIRE)
    {
        if( !showDelay )
        {
            // add a bullet to the scene
            score--;
            if(score < 0)
                score = 0;

#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjPlay(hsoFireBullet, 0);
            }
#endif

           // play the "fire" effect
           inputPlayEffect(EF_FIRE, 0);

            addObject( OBJ_BULLET, Dirx[(int)DL.frame]*6.0 + 16.0 + DL.posx,
                                   Diry[(int)DL.frame]*6.0 + 16.0 + DL.posy,
                                   Dirx[(int)DL.frame]*500.0/1000.0,
                                   Diry[(int)DL.frame]*500.0/1000.0 );
        }
    }

    event = FALSE;
    if( input & KEY_LEFT )
    {
            DL.frame -= 1.0;
            if( DL.frame < 0.0 )
                DL.frame += MAX_SHIP_FRAME;
    }
    if( input & KEY_RIGHT )
    {
            DL.frame += 1.0;
            if( DL.frame >= MAX_SHIP_FRAME)
                DL.frame -= MAX_SHIP_FRAME;
    }
    if( input & KEY_UP )
    {
            DL.velx += Dirx[(int)DL.frame] * 10.0/1000.0;
            DL.vely += Diry[(int)DL.frame] * 10.0/1000.0;
            event = TRUE;
    }
    if( input & KEY_DOWN )
    {
            DL.velx -= Dirx[(int)DL.frame] * 10.0/1000.0;
            DL.vely -= Diry[(int)DL.frame] * 10.0/1000.0;
            event = TRUE;
    }

    if (event != lastThrust)
    {
        if (event)
        {
            input &= ~KEY_STOP;
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjStop(hsoSkidToStop);
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);
            }
#endif
            bPlayRev = TRUE;
        }
        else
        {
#ifdef USE_DSOUND
            if(bWantSound)
            {
                SndObjStop(hsoEngineRev);
            }
#endif
            bPlayRev = FALSE;
        }

        lastThrust = event;
    }

    if( input & KEY_STOP )
    {
#ifdef USE_DSOUND
        if(bWantSound)
        {
            if (DL.velx || DL.vely)
            {
                playPanned(hsoSkidToStop, &DL);
            }
        }
#endif
        DL.velx = 0;
        DL.vely = 0;
    }

    this = DL.next;
    do
    {
        this->posx += this->velx * (double)tickDiff;
        this->posy += this->vely * (double)tickDiff;
        this->frame += this->delay * (double)tickDiff;
        switch( this->type )
        {
            case OBJ_DONUT:
                maxx = (double)MAX_DONUT_X;
                maxy = (double)MAX_DONUT_Y;
                maxframe = (double)MAX_DONUT_FRAME;
                break;
            case OBJ_PYRAMID:
                maxx = (double)MAX_PYRAMID_X;
                maxy = (double)MAX_PYRAMID_Y;
                maxframe = (double)MAX_PYRAMID_FRAME;
                break;
            case OBJ_SPHERE:
                maxx = (double)MAX_SPHERE_X;
                maxy = (double)MAX_SPHERE_Y;
                maxframe = (double)MAX_SPHERE_FRAME;
                break;
            case OBJ_CUBE:
                maxx = (double)MAX_CUBE_X;
                maxy = (double)MAX_CUBE_Y;
                maxframe = (double)MAX_CUBE_FRAME;
                break;
            case OBJ_BULLET:
                maxx = (double)MAX_BULLET_X;
                maxy = (double)MAX_BULLET_Y;
                maxframe = (double)MAX_BULLET_FRAME;
                if( this->frame >= (double)MAX_BULLET_FRAME )
                {
                    save = this;
                    this = this->next;
                    DeleteFromList( save );
                    continue;
                }
                break;
        }
        if( this != &DL )
        {
            if( this->posx > maxx )
            {
                this->posx = maxx;
                this->velx = -this->velx;
            }
            else if ( this->posx < 0 )
            {
                this->posx =0;
                this->velx = -this->velx;
            }
            if( this->posy > maxy )
            {
                this->posy = maxy;
                this->vely = -this->vely;
            }
            else if ( this->posy < 0 )
            {
                this->posy =0;
                this->vely = -this->vely;
            }
            if( this->frame >= maxframe )
            {
                this->frame -= maxframe;
            }
            this = this->next;
        }
    }
    while( this != &DL );
}

BOOL isDisplayListEmpty( void )
{
    LPDBLNODE ptr;

    for(ptr=DL.next; ptr != &DL; ptr = ptr->next)
    {
        if(ptr->type != OBJ_BULLET)
            return FALSE;
    }
    return TRUE;
}

void initShip( BOOL delay )
{
    DL.posx = (double)(ScreenX/2-16);       // center the ship
    DL.posy = (double)(ScreenY/2-16);
    DL.frame = 0.0;
    if( bTest )
    {
        DL.velx = 0.25;
        DL.vely = 0.5;
    }
    else
    {
        DL.velx = DL.vely = 0.0;        // not moving
    }
    if( !bTest && delay )
        showDelay = DEF_SHOW_DELAY;
}

void initLevel( int level )
{
    int     i;

    // clear any stray bullets out of the display list
    while( DL.next != &DL )
    {
        DeleteFromList( DL.next );
    }
    for(i=0; i<(2*level-1); i++)
    {
        addObject( OBJ_DONUT, -1.0, -1.0, -1.0, -1.0 );
    }
    initShip(TRUE);
}

void addObject( SHORT type, double x, double y, double vx, double vy )
{
    LPDBLNODE   new;

    new = (LPDBLNODE) LocalAlloc( LPTR, sizeof( DBLNODE ) );
    if( new == NULL)
        return;

    new->type = type;
    switch( type )
    {
        case OBJ_DONUT:
            if( x < 0.0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_DONUT_X );
                new->posy = randDouble( 0.0, (double)MAX_DONUT_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 30.0*randDouble( 0.1, 0.4 )/1000.0;
            new->surf = lpDonut;
            linkObject( new );
            break;
        case OBJ_PYRAMID:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_PYRAMID_X );
                new->posy = randDouble( 0.0, (double)MAX_PYRAMID_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 1.5*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 0.3, 1.0 )/1000.0;
            new->surf = lpPyramid;
            linkObject( new );
            break;
        case OBJ_SPHERE:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_SPHERE_X );
                new->posy = randDouble( 0.0, (double)MAX_SPHERE_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 3.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 1.5, 2.0 )/1000.0;
            new->surf = lpSphere;
            linkObject( new );
            break;
        case OBJ_CUBE:
            if( x < 0) // no position specified?
            {
                new->posx = randDouble( 0.0, (double)MAX_CUBE_X );
                new->posy = randDouble( 0.0, (double)MAX_CUBE_Y );
            }
            else
            {
                new->posx = x;
                new->posy = y;
            }
            new->velx = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->vely = 4.0*randDouble( -50.0/1000.0, 50.0/1000.0 );
            new->frame = randDouble( 0, 30 );
            new->delay = 40.0*randDouble( 0.8, 2.0 )/1000.0;
            new->surf = lpCube;
            linkObject( new );
            break;
        case OBJ_BULLET:
            new->posx = x;
            new->posy = y;
            new->velx = vx;
            new->vely = vy;
            new->frame = 0.0;
            new->delay = 1.0;
            new->surf = lpNum;
            linkObject( new );
            break;
    }
}

void linkObject( LPDBLNODE new )
{
    new->next = DL.next;
    new->prev = &DL;
    DL.next->prev = new;
    DL.next = new;
}

void linkLastObject( LPDBLNODE new )
{
    new->prev = DL.prev;
    new->next = &DL;
    DL.prev->next = new;
    DL.prev = new;
}

BOOL RestoreSurfaces( void )
{
    HRESULT     ddrval;
    HBITMAP     hbm;

    ddrval = lpFrontBuffer->lpVtbl->Restore(lpFrontBuffer);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpDonut->lpVtbl->Restore(lpDonut);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpPyramid->lpVtbl->Restore(lpPyramid);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpCube->lpVtbl->Restore(lpCube);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpSphere->lpVtbl->Restore(lpSphere);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpShip->lpVtbl->Restore(lpShip);
    if( ddrval != DD_OK )
        return FALSE;
    ddrval = lpNum->lpVtbl->Restore(lpNum);
    if( ddrval != DD_OK )
        return FALSE;

    // Create and set the palette for the splash bitmap
    lpSplashPalette = DDLoadPalette( lpDD, "SPLASH" );
    if( NULL == lpSplashPalette )
        return CleanupAndExit("DDLoadPalette SPLASH");

    // Create and set the palette for the art bitmap
    lpArtPalette = DDLoadPalette( lpDD, "DONUTS8" );
    if( NULL == lpArtPalette )
        return CleanupAndExit("DDLoadPalette DONUTS");

    // set the palette before loading the art
    lpFrontBuffer->lpVtbl->SetPalette( lpFrontBuffer, lpArtPalette );

    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

    if( NULL == hbm )
        return FALSE;

    ddrval = DDCopyBitmap( lpDonut, hbm, 0, 0, 320, 384 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    // NOTE: Why are we calling LoadImage again?  StretchBlt (which is
    // called in DDCopyBitmap) does not work properly when performing
    // an 8-bpp to 24- or 32-bpp blt multiple times from the same
    // bitmap.  The workaround is to call LoadImage before each
    // StretchBlt because the first StretchBlt after a LoadImage will
    // work.
    if(ScreenBpp >= 24)
    {
        DeleteObject( hbm );
        hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

        if( NULL == hbm )
            return FALSE;
    }

    ddrval = DDCopyBitmap( lpPyramid, hbm, 0, 384, 320, 128 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
        DeleteObject( hbm );
        hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

        if( NULL == hbm )
            return FALSE;
    }

    ddrval = DDCopyBitmap( lpSphere, hbm, 0, 512, 320, 32 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
        DeleteObject( hbm );
        hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

        if( NULL == hbm )
            return FALSE;
    }

    ddrval = DDCopyBitmap( lpCube, hbm, 0, 544, 320, 32 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
        DeleteObject( hbm );
        hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

        if( NULL == hbm )
            return FALSE;
    }

    ddrval = DDCopyBitmap( lpShip, hbm, 0, 576, 320, 256 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    if(ScreenBpp >= 24)
    {
        DeleteObject( hbm );
        hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );

        if( NULL == hbm )
            return FALSE;
    }

    ddrval = DDCopyBitmap( lpNum, hbm, 0, 832, 320, 16 );
    if( ddrval != DD_OK )
    {
        DeleteObject( hbm );
        return FALSE;
    }

    DeleteObject( hbm );

    // set colorfill colors and color keys according to bitmap contents
    dwFillColor = DDColorMatch( lpDonut, CLR_INVALID );

    DDSetColorKey( lpDonut, CLR_INVALID );
    DDSetColorKey( lpPyramid, CLR_INVALID );
    DDSetColorKey( lpCube, CLR_INVALID );
    DDSetColorKey( lpSphere, CLR_INVALID );
    DDSetColorKey( lpShip, CLR_INVALID );
    DDSetColorKey( lpNum, CLR_INVALID );

    return TRUE;
}

int randInt( int low, int high )
{
    int range = high - low;
    int num = rand() % range;
    return( num + low );
}

double randDouble( double low, double high )
{
    double range = high - low;
    double num = range * (double)rand()/(double)RAND_MAX;
    return( num + low );
}

#ifdef USE_DSOUND
void InitializeSound( void )
{
        if(!bWantSound)
                return; // out of here
    bSoundEnabled = FALSE;
    if (SUCCEEDED(DirectSoundCreate(NULL, &lpDS, NULL)))
    {
        if (SUCCEEDED(lpDS->lpVtbl->SetCooperativeLevel(lpDS, hWndMain,
            DSSCL_NORMAL)))
        {
            hsoBeginLevel     = SndObjCreate(lpDS, "BeginLevel",      1);
            hsoEngineIdle     = SndObjCreate(lpDS, "EngineIdle",      1);
            hsoEngineRev      = SndObjCreate(lpDS, "EngineRev",       1);
            hsoSkidToStop     = SndObjCreate(lpDS, "SkidToStop",      1);
            hsoShieldBuzz     = SndObjCreate(lpDS, "ShieldBuzz",      1);
            hsoShipExplode    = SndObjCreate(lpDS, "ShipExplode",     1);
            hsoFireBullet     = SndObjCreate(lpDS, "Gunfire",        25);
            hsoShipBounce     = SndObjCreate(lpDS, "ShipBounce",      4);
            hsoDonutExplode   = SndObjCreate(lpDS, "DonutExplode",   10);
            hsoPyramidExplode = SndObjCreate(lpDS, "PyramidExplode", 12);
            hsoCubeExplode    = SndObjCreate(lpDS, "CubeExplode",    15);
            hsoSphereExplode  = SndObjCreate(lpDS, "SphereExplode",  10);
            bSoundEnabled = TRUE;

            if( bPlayIdle )
                SndObjPlay(hsoEngineIdle, DSBPLAY_LOOPING);

            if( bPlayBuzz )
                SndObjPlay(hsoShieldBuzz, DSBPLAY_LOOPING);

            if( bPlayRev )
                SndObjPlay(hsoEngineRev, DSBPLAY_LOOPING);
        }
        else
        {
            lpDS->lpVtbl->Release(lpDS);
            lpDS = NULL;
        }
    }
}

void DestroySound( void )
{
        if(!bWantSound)
                return; //No work to be done
    bSoundEnabled = FALSE;
    if (lpDS)
    {
        SndObjDestroy(hsoBeginLevel);
        hsoBeginLevel = NULL;
        SndObjDestroy(hsoEngineIdle);
        hsoEngineIdle = NULL;
        SndObjDestroy(hsoEngineRev);
        hsoEngineRev = NULL;
        SndObjDestroy(hsoSkidToStop);
        hsoSkidToStop = NULL;
        SndObjDestroy(hsoShieldBuzz);
        hsoShieldBuzz = NULL;
        SndObjDestroy(hsoShipExplode);
        hsoShipExplode = NULL;
        SndObjDestroy(hsoFireBullet);
        hsoFireBullet = NULL;
        SndObjDestroy(hsoShipBounce);
        hsoShipBounce = NULL;
        SndObjDestroy(hsoDonutExplode);
        hsoDonutExplode = NULL;
        SndObjDestroy(hsoPyramidExplode);
        hsoPyramidExplode = NULL;
        SndObjDestroy(hsoCubeExplode);
        hsoCubeExplode = NULL;
        SndObjDestroy(hsoSphereExplode);
        hsoSphereExplode = NULL;

        lpDS->lpVtbl->Release(lpDS);
        lpDS = NULL;
    }
}
#endif

int getint(char**p, int def)
{
    int i=0;

    while (IS_SPACE(**p))
        (*p)++;

    if (!IS_NUM(**p))
        return def;

    while (IS_NUM(**p))
        i = i*10 + *(*p)++ - '0';

    while (IS_SPACE(**p))
        (*p)++;

    return i;
}


FFDONUTS.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ffdonuts.h
 *  Content:    main include file
 *
 *
 ***************************************************************************/

#ifndef DONUTS_INCLUDED
#define DONUTS_INCLUDED

#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <float.h>
#include "resource.h"
#include "ddutil.h"
#include "input.h"
#ifdef USE_DSOUND
    #include <dsound.h>
    #include "dsutil.h"
#endif

#define DEF_SHOW_DELAY     (2000)

#define IS_NUM(c)     ((c) >= '0' && (c) <= '9')
#define IS_SPACE(c)   ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == 'x')

enum
{
    OBJ_DONUT = 0,
    OBJ_PYRAMID,
    OBJ_CUBE,
    OBJ_SPHERE,
    OBJ_SHIP,
    OBJ_BULLET
};

// program states
enum
{
    PS_SPLASH,
    PS_ACTIVE,
    PS_BEGINREST,
    PS_REST
};

#define     MAX_SCREEN_X    (ScreenX-1)
#define     MAX_SCREEN_Y    (ScreenY-1)
#define     MAX_DONUT_X     MAX_SCREEN_X - 64
#define     MAX_DONUT_Y     MAX_SCREEN_Y - 64
#define     MAX_DONUT_FRAME 30
#define     MAX_PYRAMID_X     MAX_SCREEN_X - 32
#define     MAX_PYRAMID_Y     MAX_SCREEN_Y - 32
#define     MAX_PYRAMID_FRAME 40
#define     MAX_SPHERE_X     MAX_SCREEN_X - 16
#define     MAX_SPHERE_Y     MAX_SCREEN_Y - 16
#define     MAX_SPHERE_FRAME 40
#define     MAX_CUBE_X     MAX_SCREEN_X - 16
#define     MAX_CUBE_Y     MAX_SCREEN_Y - 16
#define     MAX_CUBE_FRAME 40
#define     MAX_SHIP_X     MAX_SCREEN_X - 32
#define     MAX_SHIP_Y     MAX_SCREEN_Y - 32
#define     MAX_SHIP_FRAME 40
#define     MAX_BULLET_X    MAX_SCREEN_X - 3;
#define     MAX_BULLET_Y    MAX_SCREEN_Y - 3;
#define     MAX_BULLET_FRAME 400

// Offsets for the bullet bitmap
#define     BULLET_X    304
#define     BULLET_Y    0

/*
 * structures
 */

/*
 * DBLNODE - a node in a generic doubly-linked list
 */
typedef struct _DBLNODE
{
    struct  _DBLNODE    FAR *next;  // link to next node
    struct  _DBLNODE    FAR *prev;  // link to previous node
    SHORT               type;       // type of object
    double              posx, posy; // actual x and y position
    double              velx, vely; // x and y velocity (pixels/millisecond)
    double              frame;      // current frame
    double              delay;      // frame/millisecond
    RECT                src, dst;   // source and destination rects
    LPDIRECTDRAWSURFACE surf;       // surface containing bitmap
} DBLNODE;
typedef DBLNODE FAR *LPDBLNODE;

double      Dirx[40] =
{
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891007,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688,
    -1.000000,
    -0.987688,
    -0.951056,
    -0.891006,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434
};

double      Diry[40] =
{
    -1.000000,
    -0.987688,
    -0.951057,
    -0.891007,
    -0.809017,
    -0.707107,
    -0.587785,
    -0.453990,
    -0.309017,
    -0.156434,
    0.000000,
    0.156434,
    0.309017,
    0.453991,
    0.587785,
    0.707107,
    0.809017,
    0.891007,
    0.951057,
    0.987688,
    1.000000,
    0.987688,
    0.951057,
    0.891006,
    0.809017,
    0.707107,
    0.587785,
    0.453990,
    0.309017,
    0.156434,
    0.000000,
    -0.156435,
    -0.309017,
    -0.453991,
    -0.587785,
    -0.707107,
    -0.809017,
    -0.891007,
    -0.951057,
    -0.987688
};

/*
 * fn prototypes
 */
void    DestroyGame( void );
BOOL    InitializeGame( void );
void    makeFontStuff( void );
void    UpdateFrame( void );
BOOL    CleanupAndExit( char *err );
BOOL    RestoreSurfaces( void );
BOOL    isDisplayListEmpty( void );
void    initShip( BOOL delay );
void    initLevel( int level );
void    addObject( SHORT type, double x, double y, double vx, double vy );
void    linkObject( LPDBLNODE new );
void    linkLastObject( LPDBLNODE new );
void    UpdateDisplayList( void );
void    DrawDisplayList( void );
int     randInt( int low, int high );
double  randDouble( double low, double high );
void    DeleteFromList( LPDBLNODE this );
void    CheckForHits( void );
void    bltScore( char *num, int x, int y );
void    DisplayFrameRate( void );
void    bltSplash( void );
void    EraseScreen( void );
void    FlipScreen( void );
void    DisplayLevel( void );
void    InitializeSound( void );
void    DestroySound( void );

#endif


INPUT.C

/*==========================================================================
 *
 * Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 * File:       input.c
 * Content:    DirectInput functionality for FFDonuts sample
 *
 * Functions:
 * inputInitDirectInput()
 * inputCleanupDirectInput()
 * inputEnumDeviceProc(LPDIDEVICEINSTANCE pdidi, LPVOID pv);
 * inputAcquireDevices(void);
 * inputCreateEffects(void);
 * inputProcessDeviceInput(void);
 * inputPrepareDevice(void);
 * inputPlayEffect(DWORD dwEffectFlag);
 *
 *
 ***************************************************************************/

#include "input.h"
#include "resource.h"

// file global variables
static BOOL                 fIsFFDevice  = FALSE;   // does our device support
                                                    // ForceFeedback
static DWORD                dwGain       = FF_ADULT;// gain selection from user
static LPDIRECTINPUT        gpdi         = NULL;    // base DirectInput object
static LPDIRECTINPUTDEVICE2 gpdiJoystick = NULL;    // DirectInputDevice2 objects
                                                    // support ForceFeedback
static LPDIRECTINPUTEFFECT  gpdieBounce  = NULL;    // effect used when "bouncing"
                                                    // off of the screen edges
static LPDIRECTINPUTEFFECT  gpdieExplode = NULL;    // effect used when the ship
                                                    // explodes
static LPDIRECTINPUTEFFECT  gpdieFire    = NULL;    // effect used when firing

//===========================================================================
// inputInitDirectInput
//
// Creates and initializes DirectInput objects
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL inputInitDirectInput(HINSTANCE hInst, HWND hWnd)
{
    HRESULT             hRes;
    LPDIRECTINPUTDEVICE pdiTempDevice       = NULL;
    DIDEVCAPS           didc;
    GUID                guidDevice;
    TCHAR               tszBuf[256];

    // create the base DirectInput object
    hRes = DirectInputCreate(hInst, DIRECTINPUT_VERSION, &gpdi, NULL);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("DirectInputCreate() failed - %08Xh\n\n")
                  TEXT("DirectX 5 or later required."), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // enumerate for joystick devices
    hRes = gpdi->lpVtbl->EnumDevices(gpdi, DIDEVTYPE_JOYSTICK,
                                    (LPDIENUMDEVICESCALLBACK)inputEnumDeviceProc,
                                    &guidDevice,
                                    DIEDFL_ATTACHEDONLY);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("EnumDevices() failed - %08Xh"), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // create a temporary "Device 1" object
    hRes = gpdi->lpVtbl->CreateDevice(gpdi, &guidDevice, &pdiTempDevice, NULL);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("CreateDevice() failed - %08Xh\n\n")
                  TEXT("This version of ""Space Donuts"" requires a JOYSTICK device."), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // get a "Device 2" object
    //
    // this is needed for access to the ForceFeedback functionality
    hRes = pdiTempDevice->lpVtbl->QueryInterface(pdiTempDevice,
                                                &IID_IDirectInputDevice2,
                                                &gpdiJoystick);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("QueryInterface(IID_IDirectInputDevice2) failed - %08Xh"), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // we no longer need the temporary device, go ahead and release it.
    if(pdiTempDevice)
    {
        pdiTempDevice->lpVtbl->Release(pdiTempDevice);
        pdiTempDevice = NULL;
    }

    // set the device's data format
    //
    // This tells the device object to act like a specific device --
    // in our case, like a joystick
    hRes = gpdiJoystick->lpVtbl->SetDataFormat(gpdiJoystick, &c_dfDIJoystick);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("SetDataFormat(Joystick) failed - %08Xh"), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // set the device's cooperative level
    //
    // ForceFeedback requires Exclusive access to the device.
    hRes = gpdiJoystick->lpVtbl->SetCooperativeLevel(gpdiJoystick, hWnd,
                                                    DISCL_EXCLUSIVE | DISCL_FOREGROUND);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("SetCooperativeLevel(Exclusive | Foreground) failed - %08Xh"), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // set joystick parameters (deadzone, etc)
    if(!inputPrepareDevice())
    {
        MessageBox(hWnd, TEXT("Device preparation failed"),
                  TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }

    // get the device capabilities
    //
    // We're going to check to see if the device we created supports
    // ForceFeedback.  If so, we will create effects, if not, we'll
    // support standard joystick functionality
    fIsFFDevice = FALSE;
    didc.dwSize = sizeof(DIDEVCAPS);
    hRes = gpdiJoystick->lpVtbl->GetCapabilities(gpdiJoystick, &didc);
    if(FAILED(hRes))
    {
        wsprintf(tszBuf, TEXT("GetCapabilities() failed - %08Xh"), hRes);
        MessageBox(hWnd, tszBuf, TEXT("Space Donuts - Force Feedback"), MB_OK);
        return FALSE;
    }
    if(didc.dwFlags & DIDC_FORCEFEEDBACK)
    {
        OutputDebugString("ForceFeedback device found.\n");

        // get the gain level from the user
        DialogBox(hInst, MAKEINTRESOURCE(IDD_FORCE), hWnd, inputForceLevelDlgProc);

        // we're supporting ForceFeedback
        fIsFFDevice = TRUE;
        if(!inputCreateEffect(EF_BOUNCE | EF_FIRE | EF_EXPLODE))
        {
            OutputDebugString("inputCreateEffects() failed - ForceFeedback disabled\n");
        }
    } //** end if(ForceFeedback device)

    // if we get here, we succeeded
    return TRUE;

} //*** end inputInitDirectInput()

//===========================================================================
// inputCleanupDirectInput
//
// Cleans up DirectInput objects
//
// Parameters: none
//
// Returns: nothing
//
//===========================================================================
void inputCleanupDirectInput(void)
{
    OutputDebugString("Cleaning up after DirectInput\n");

    // Release() effect objects
    if(gpdieBounce)
    {
        gpdieBounce->lpVtbl->Release(gpdieBounce);
        gpdieBounce = NULL;
    }
    if(gpdieExplode)
    {
        gpdieExplode->lpVtbl->Release(gpdieExplode);
        gpdieExplode = NULL;
    }
    if(gpdieFire)
    {
        gpdieFire->lpVtbl->Release(gpdieFire);
        gpdieFire = NULL;
    }

    // Unacquire() and Release() device objects
    //
    // It is perfectly safe to call Unacquire() on a device that is not
    // currently acquired.  In fact, it is good practice to call
    // Unacquire() just prior to Release().
    if(gpdiJoystick)
    {
        gpdiJoystick->lpVtbl->Unacquire(gpdiJoystick);
        gpdiJoystick->lpVtbl->Release(gpdiJoystick);
        gpdiJoystick = NULL;
    }

    // Release() base object
    if(gpdi)
    {
        gpdi->lpVtbl->Release(gpdi);
        gpdi = NULL;
    }

} //*** end inputCleanupDirectInput()

//===========================================================================
// inputEnumDeviceProc
//
// Enumerates DirectInput devices of type specified in call to
//  IDirectInput::EnumDevices()
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL CALLBACK inputEnumDeviceProc(LPDIDEVICEINSTANCE pdidi, LPVOID pv)
{
    GUID *pguidDevice = NULL;

    // validate pv
    // BUGBUG

    // report back the instance guid of the device we enumerated
    if(pv)
    {

        pguidDevice = (GUID *)pv;

        *pguidDevice = pdidi->guidInstance;

    }

    // BUGBUG for now, stop after the first device has been found
    return DIENUM_STOP;

} //*** end inputEnumDeviceProc()

//===========================================================================
// inputEnumEffectTypeProc
//
// Enumerates ForceFeedback effect types (ie "Constant Force").
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL CALLBACK inputEnumEffectTypeProc(LPCDIEFFECTINFO pei, LPVOID pv)
{
    GUID *pguidEffect = NULL;

    // validate pv
    // BUGBUG

    // report back the guid of the effect we enumerated
    if(pv)
    {

        pguidEffect = (GUID *)pv;

        *pguidEffect = pei->guid;

    }

    // BUGBUG - look at this some more....
    return DIENUM_STOP;

} //*** end inputEnumEffectTypeProc()

//===========================================================================
// inputAcquireDevices
//
// Acquires the input device(s).
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL inputAcquireDevices(void)
{
    if(!gpdiJoystick)
    {
        return FALSE;
    }

    // reacquire the device
    if(SUCCEEDED(gpdiJoystick->lpVtbl->Acquire(gpdiJoystick)))
    {

        // DirectInput automatically resets the device whenever
        // ownership changes, so we can assume we've got a device
        // unsullied by its previous owner.
        inputCreateEffect(EF_BOUNCE | EF_FIRE | EF_EXPLODE);

        return TRUE;
    }
    // if we get here, we did >not< acquire the device
    return FALSE;

} //*** end inputAcquireDevices()

//===========================================================================
// inputCreateEffect
//
// Creates the DirectInputEffect object(s) used by the application
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL inputCreateEffect(DWORD dwEffectFlags)
{
    HRESULT         hRes;
    GUID            guidEffect;
    DIEFFECT        diEffect;
    DIENVELOPE      diEnvelope;
    DWORD           rgdwAxes[2];
    LONG            rglDirections[2];
    DICONSTANTFORCE dicf;
    DIPERIODIC      dipf;
    TCHAR           tszBuf[256];

    // make sure that we have a non-NULL device object
    if(!gpdiJoystick)
    {
        return FALSE;
    }

    // initialize DIEFFECT and DIENVELOPE structures
    ZeroMemory(&diEffect, sizeof(DIEFFECT));
    ZeroMemory(&diEnvelope, sizeof(DIENVELOPE));

    // these fields are the same for all effects we will be creating
    diEffect.dwSize                     = sizeof(DIEFFECT);
    diEffect.dwSamplePeriod             = 0; // use default sample period
    diEffect.dwTriggerButton            = DIEB_NOTRIGGER;
    diEffect.dwTriggerRepeatInterval    = 0;
    diEffect.rgdwAxes                   = rgdwAxes;
    diEffect.rglDirection               = rglDirections;
    diEffect.dwGain                     = dwGain; // gain selected by user

    // enumerate for a constant force effect
    //
    // both the "bounce" and "fire" effects will be based on the first
    // constant force effect enumerated
    if((dwEffectFlags & EF_BOUNCE) || (dwEffectFlags & EF_FIRE))
    {
        hRes = gpdiJoystick->lpVtbl->EnumEffects(gpdiJoystick,
                                                (LPDIENUMEFFECTSCALLBACK)inputEnumEffectTypeProc,
                                                &guidEffect, DIEFT_CONSTANTFORCE);
        if(FAILED(hRes))
        {
            OutputDebugString("EnumEffects(Constant Force) failed\n");
            return FALSE;
        }
    }

    // create "bounce" effect
    if(dwEffectFlags & EF_BOUNCE)
    {
        // if we have already created this effect...
        //
        // Call Release() before recreating it
        if(gpdieBounce)
        {
            gpdieBounce->lpVtbl->Release(gpdieBounce);
            gpdieBounce = NULL;
        }

        // prepare the DICONSTANTFORCE structure
        //
        // this is the type-specific data for this force
        dicf.lMagnitude                     = 10000;

        // what axes and directions to use?
        // (directions do not matter at this point, set them to 0)
        rgdwAxes[0]                         = DIJOFS_X;
        rgdwAxes[1]                         = DIJOFS_Y;
        rglDirections[0]                    = 0;
        rglDirections[1]                    = 0;

        // prepare the DIEFFECT structure
        //
        // fill in the force-specific values
        diEffect.dwFlags                    = DIEFF_OBJECTOFFSETS | DIEFF_POLAR;
        diEffect.dwDuration                 = 200000;
        diEffect.cAxes                      = 2;
        diEffect.lpEnvelope                 = NULL;
        diEffect.cbTypeSpecificParams       = sizeof(DICONSTANTFORCE);
        diEffect.lpvTypeSpecificParams      = &dicf;

        // call CreateEffect()
        hRes = gpdiJoystick->lpVtbl->CreateEffect(gpdiJoystick, &guidEffect,
                                                    &diEffect, &gpdieBounce,
                                                    NULL);
        if(FAILED(hRes))
        {
            wsprintf(tszBuf, "CreateEffect(Bounce) failed - %08Xh\n", hRes);
            OutputDebugString(tszBuf);
            return FALSE;
        }

    } //** end if(bounce effect)

    // create "fire" effect
    if(dwEffectFlags & EF_FIRE)
    {
        // if we have already created this effect...
        //
        // Call Release() before recreating it
        if(gpdieFire)
        {
            gpdieFire->lpVtbl->Release(gpdieFire);
            gpdieFire = NULL;
        }

        // prepare the DICONSTANTFORCE structure
        //
        // this is the type-specific data for this force
        dicf.lMagnitude                     = 10000;

        // what axes and directions to use?
        rgdwAxes[0]                         = DIJOFS_Y;
        rglDirections[0]                    = 1;

        // prepare the DIEFFECT structure
        //
        // fill in the force-specific values
        diEffect.dwFlags                    = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        diEffect.dwDuration                 = 20000;
        diEffect.cAxes                      = 1;
        diEffect.lpEnvelope                 = NULL;
        diEffect.cbTypeSpecificParams       = sizeof(DICONSTANTFORCE);
        diEffect.lpvTypeSpecificParams      = &dicf;

        // call CreateEffect()
        hRes = gpdiJoystick->lpVtbl->CreateEffect(gpdiJoystick, &guidEffect,
                                                    &diEffect, &gpdieFire,
                                                    NULL);
        if(FAILED(hRes))
        {
            wsprintf(tszBuf, "CreateEffect(Fire) failed - %08Xh\n", hRes);
            OutputDebugString(tszBuf);
            return FALSE;
        }

    } //** end if(fire effect)

    // enumerate for a periodic effect
    //
    // the "explode" effect will be based on the first
    // periodic effect enumerated
    if((dwEffectFlags & EF_EXPLODE))
    {
        hRes = gpdiJoystick->lpVtbl->EnumEffects(gpdiJoystick,
                                                (LPDIENUMEFFECTSCALLBACK)inputEnumEffectTypeProc,
                                                &guidEffect, DIEFT_PERIODIC);
        if(FAILED(hRes))
        {
            OutputDebugString("EnumEffects(Periodic Force) failed\n");
            return FALSE;
        }
    }
    // create "explode" effect
    if(dwEffectFlags & EF_FIRE)
    {
        // if we have already created this effect...
        //
        // Call Release() before recreating it
        if(gpdieExplode)
        {
            gpdieExplode->lpVtbl->Release(gpdieExplode);
            gpdieExplode = NULL;
        }

        // prepare the DIENVELOPE structure
        //
        // We want to shape the explode effect so that it starts
        // at it's peak and then fades out
        diEnvelope.dwSize                   = sizeof(DIENVELOPE);
        diEnvelope.dwAttackLevel            = 0;
        diEnvelope.dwAttackTime             = 0;
        diEnvelope.dwFadeLevel              = 0;
        diEnvelope.dwFadeTime               = 1000000;

        // prepare the DIPERIODIC structure
        //
        // this is the type-specific data for this force
        dipf.dwMagnitude                    = 10000;
        dipf.lOffset                        = 0;
        dipf.dwPhase                        = 0;
        dipf.dwPeriod                       = 100000;

        // what axes and directions to use?
        rgdwAxes[0]                         = DIJOFS_X;
        rglDirections[0]                    = 0;

        // prepare the DIEFFECT structure
        //
        // fill in the force-specific values
        diEffect.dwFlags                    = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        diEffect.dwDuration                 = 1000000;
        diEffect.cAxes                      = 1;
        diEffect.lpEnvelope                 = &diEnvelope;
        diEffect.cbTypeSpecificParams       = sizeof(DIPERIODIC);
        diEffect.lpvTypeSpecificParams      = &dipf;

        // call CreateEffect()
        hRes = gpdiJoystick->lpVtbl->CreateEffect(gpdiJoystick, &guidEffect,
                                                    &diEffect, &gpdieExplode,
                                                    NULL);
        if(FAILED(hRes))
        {
            wsprintf(tszBuf, "CreateEffect(Explode) failed - %08Xh\n", hRes);
            OutputDebugString(tszBuf);
            return FALSE;
        }

    } //** end if(explode effect)

    return TRUE;

} //*** end inputCreateEffects()

//===========================================================================
// inputProcessDeviceInput
//
// Processes data from the input device.  Uses GetDeviceState().
//
// Parameters:
//
// Returns:
//
//===========================================================================
DWORD inputProcessDeviceInput(void)
{
    HRESULT     hRes;
    DIJOYSTATE  dijs;
    DWORD       dwInput = 0;

    // poll the joystick to read the current state
    hRes = gpdiJoystick->lpVtbl->Poll(gpdiJoystick);

    // read the device state
    hRes = gpdiJoystick->lpVtbl->GetDeviceState(gpdiJoystick, sizeof(DIJOYSTATE),
                                                &dijs);
    if(FAILED(hRes))
    {
        if((hRes == DIERR_INPUTLOST))
        {
            inputAcquireDevices();
        }

        // we did not read anything, return no motion
        return 0;
    }

    // process device state
    //
    // to preserve as much of the existing input handling code from the
    // original space donuts sample, we will be converting the joystick data to
    // "keyboard" input

    //* x-axis (left)
    if(dijs.lX < 0)
    {
        dwInput |= KEY_LEFT;
    }
    //* x-axis (right)
    if(dijs.lX > 0)
    {
        dwInput |= KEY_RIGHT;
    }
    //* y-axis (forward)
    if(dijs.lY < 0)
    {
        dwInput |= KEY_UP;
    }
    //* y-axis (backward)
    if(dijs.lY > 0)
    {
        dwInput |= KEY_DOWN;
    }
    //* button 0 (fire)
    if(dijs.rgbButtons[0] & 0x80)
    {
        dwInput |= KEY_FIRE;
    }
    //* button 1 (shield)
    if(dijs.rgbButtons[1] & 0x80)
    {
        dwInput |= KEY_SHIELD;
    }
    //* button 2 (stop) - requires a joystick with more than 2 buttons
    if(dijs.rgbButtons[2] & 0x80)
    {
        dwInput |= KEY_STOP;
    }

    // return the new device state
    return dwInput;

} //*** end inputProcessDeviceInput()

//===========================================================================
// inputPrepareDevice
//
// Performs device preparation by setting the device's parameters (ie
// deadzone).
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL inputPrepareDevice(void)
{
    HRESULT       hRes;
    DIPROPRANGE   dipr;
    DIPROPDWORD   dipdw;

    // quick check to make sure that the object pointer is non-NULL
    if(!gpdiJoystick)
    {
        return FALSE;
    }

    // call Unacquire() on the device
    //
    // SetParameter() will fail if a device is currently acquired, we are
    // doing this here in case we get careless and forget to call this
    // function either before we call Acquire() or after we call Unacquire().
    gpdiJoystick->lpVtbl->Unacquire(gpdiJoystick);

    // set the axis ranges for the device
    //
    // We will use the same range for the X and Y axes.  We are setting them
    // fairly low since we are not concerned with anything other than
    // "left", "right", "forward", "backward" and "centered"
//* prepare DIPROPRANGE structure
    dipr.diph.dwSize        = sizeof(DIPROPRANGE);
dipr.diph.dwHeaderSize  = sizeof(dipr.diph);
dipr.diph.dwHow         = DIPH_BYOFFSET;
dipr.lMin               = RANGE_MIN;  // negative to the left/top
dipr.lMax               = RANGE_MAX;  // positive to the right/bottom
    //* x-axis
    dipr.diph.dwObj         = DIJOFS_X;
    //* set the x-axis range property
    hRes = gpdiJoystick->lpVtbl->SetProperty(gpdiJoystick, DIPROP_RANGE, &dipr.diph);
    if(FAILED(hRes))
    {
        OutputDebugString("SetProperty(RANGE, X-Axis) failed.\n");
        return FALSE;
    }
    //* y-axis
    dipr.diph.dwObj         = DIJOFS_Y;
    hRes = gpdiJoystick->lpVtbl->SetProperty(gpdiJoystick, DIPROP_RANGE, &dipr.diph);
    if(FAILED(hRes))
    {
        OutputDebugString("SetProperty(RANGE, Y-Axis) failed.\n");
        return FALSE;
    }

    // set the deadzone for the device
    //
    // We will use the same deadzone percentage for the X and Y axes.
    // This call uses a symbolic constant for the deadzone percentage so that
    // it is easy to change if we decide we don't like it.
//* prepare DIPROPDWORD structure
dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
dipdw.diph.dwHow        = DIPH_BYOFFSET;
dipdw.dwData            = DEADZONE;
    //* set the x-axis range property
    dipdw.diph.dwObj         = DIJOFS_X;
    hRes = gpdiJoystick->lpVtbl->SetProperty(gpdiJoystick, DIPROP_DEADZONE, &dipdw.diph);
    if(FAILED(hRes))
    {
        OutputDebugString("SetProperty(DEADZONE, X-Axis) failed.\n");
        return FALSE;
    }
    //* y-axis
    dipdw.diph.dwObj         = DIJOFS_Y;
    hRes = gpdiJoystick->lpVtbl->SetProperty(gpdiJoystick, DIPROP_DEADZONE, &dipdw.diph);
    if(FAILED(hRes))
    {
        OutputDebugString("SetProperty(DEADZONE, Y-Axis) failed.\n");
        return FALSE;
    }

    // set the ForceFeedback gain
    //
    // If the device supports feedback, use the user selected gain level
    // to scale the strength of the forces applied to the stick.  We do this
    // so that if a small child is playing the game, the stick does not jerk
    // hard enough to hurt them, yet an adult can have a stronger force
    // experience
    if(fIsFFDevice)
    {
        // BUGBUG get setting from user (done somewhere else)
        dwGain = FF_ADULT;
    }

    // Acquire the device(s)
    //
    // This is being done as a convenience since we unacquired earlier in
    // this function.  This does not guarantee that the device will be
    // acquired at the time we return from the function (in other words, we
    // are not going to spin here until we get a succeessful acquisition).
    inputAcquireDevices();

    // we've actually done somthing here
    return TRUE;

} //** end inputPrepareDevice()

//===========================================================================
// inputPlayEffect
//
// Plays specified effect object.
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL inputPlayEffect(DWORD dwEffectFlags, LONG lDirection)
{
    HRESULT         hRes;
    DIEFFECT        diEffect;
    LONG            rglDirections[2] = { 0, 0 };

    // initialize DIEFFECT structure
    ZeroMemory(&diEffect, sizeof(DIEFFECT));
    diEffect.dwSize = sizeof(DIEFFECT);

    // play "bounce" effect?
    if(dwEffectFlags & EF_BOUNCE)
    {
        if(gpdieBounce)
        {
            // set the direction
            //
            // since this is a polar coordinate effect, we will pass the angle
            // in as the direction relative to the x-axis, and will leave 0
            // for the y-axis direction
            //
            // Direction is passed in in degrees, we convert to 100ths
            // of a degree to make it easier for the caller.
            rglDirections[0]        = lDirection * 100;
            diEffect.dwFlags        = DIEFF_OBJECTOFFSETS | DIEFF_POLAR;
            diEffect.cAxes          = 2;
            diEffect.rglDirection   = rglDirections;
            hRes = gpdieBounce->lpVtbl->SetParameters(gpdieBounce,
                                                        &diEffect,
                                                        DIEP_DIRECTION);
            if(FAILED(hRes))
            {
                OutputDebugString("SetParameters(Bounce effect) failed\n");
                return FALSE;
            }

            // play the effect
            hRes = gpdieBounce->lpVtbl->Start(gpdieBounce, 1, 0);
            if(FAILED(hRes))
            {
                OutputDebugString("Start(Bounce effect) failed\n");
                return FALSE;
            }

        }

    } //** end if(play bounce)

    // play "fire" effect?
    if(dwEffectFlags & EF_FIRE)
    {
        if(gpdieFire)
        {
            // play the effect
            hRes = gpdieFire->lpVtbl->Start(gpdieFire, 1, 0);
            if(FAILED(hRes))
            {
                OutputDebugString("Start(Fire effect) failed\n");
                return FALSE;
            }
        }
    } //** end if(play fire)

    // play "explode" effect?
    if(dwEffectFlags & EF_EXPLODE)
    {
        if(gpdieExplode)
        {
            // BUGBUG how many iterations of the effect??
            hRes = gpdieExplode->lpVtbl->Start(gpdieExplode, 1, 0);
            if(FAILED(hRes))
            {
                OutputDebugString("Start(Explode effect) failed\n");
                return FALSE;
            }
        }
    } //** end if(play explode)

    return TRUE;

} //*** end inputPlayEffect()

//===========================================================================
// inputForceLevelDlgProc
//
// Dialog proceedure for handling the Force Level dialog box.
//
// Parameters:
//
// Returns:
//
//===========================================================================
BOOL CALLBACK inputForceLevelDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                                    LPARAM lParam)
{
    int nSelection = 0;

    switch(uMsg)
    {
        case WM_INITDIALOG:
             // select default / current setting
             switch(dwGain)
             {
                case FF_CHILD:
                     nSelection = IDC_CHILD;
                     break;

                case FF_BODYBUILDER:
                     nSelection = IDC_BODYBUILDER;
                     break;

                case FF_ADULT:
                default:
                     nSelection = IDC_ADULT;
                     break;
             }
             CheckRadioButton(hWnd, IDC_CHILD, IDC_BODYBUILDER, nSelection);
             return TRUE;

        case WM_COMMAND:
             {
                switch(LOWORD(wParam))
                {
                    case IDOK:
                         // get user's force level selection
                         // BUGBUG
                         if(IsDlgButtonChecked(hWnd, IDC_CHILD))
                         {
                            OutputDebugString("Child level\n");
                            dwGain = FF_CHILD;
                         }
                         else if(IsDlgButtonChecked(hWnd, IDC_BODYBUILDER))
                         {
                            OutputDebugString("Bodybuilder level\n");

dwGain = FF_BODYBUILDER;
                         }
                         else
                         {
                            OutputDebugString("Adult level (Default)\n");
                            dwGain = FF_ADULT;
                         }
                         EndDialog(hWnd, 0);
                         break;
                }

             }
             break;

    }

    return FALSE;
}


INPUT.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       input.h
 *  Content:    input functionality include file
 *
 *
 ***************************************************************************/

#ifndef _INPUT_H
#define _INPUT_H

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif/* __cplusplus */

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

#include <dinput.h>

// prototypes
BOOL            inputInitDirectInput(HINSTANCE hInst, HWND hWnd);
void            inputCleanupDirectInput(void);
BOOL CALLBACK   inputEnumDeviceProc(LPDIDEVICEINSTANCE pdidi, LPVOID pv);
BOOL CALLBACK   inputEnumEffectTypeProc(LPCDIEFFECTINFO pei, LPVOID pv);
BOOL            inputAcquireDevices(void);
BOOL            inputCreateEffect(DWORD dwEffectFlags);
DWORD           inputProcessDeviceInput(void);
BOOL            inputPrepareDevice(void);
BOOL            inputPlayEffect(DWORD dwEffectFlags, LONG lDirection);
BOOL CALLBACK   inputForceLevelDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                                       LPARAM lParam);

// constants used for scaling the input device
#define DEADZONE        2500            // 25% of the axis range
#define RANGE_MAX       1000            // maximum positive axis value
#define RANGE_MIN       -1000           // minimum negative axis value
#define FF_CHILD        5000            // "Child"          gain == 50%
#define FF_ADULT        7500            // "Adult"          gain == 75%
#define FF_BODYBUILDER  10000           // "Bodybuilder"    gain == 100%

// "effect flags" - these are internal identifiers and in no way map to DirectInput
#define EF_BOUNCE   0x00000001l
#define EF_EXPLODE  0x00000002l
#define EF_FIRE     0x00000004l

// "keyboard" commands - moved from original .h file
#define KEY_STOP    0x00000001l
#define KEY_DOWN    0x00000002l
#define KEY_LEFT    0x00000004l
#define KEY_RIGHT   0x00000008l
#define KEY_UP      0x00000010l
#define KEY_FIRE    0x00000020l
#define KEY_THROW   0x00000040l
#define KEY_SHIELD  0x00000080l

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

#endif _INPUT_H


RESOURCE.H

#define DONUTS_ICON                 101

#define IDD_FORCE                   200
#define IDC_STRENGTHTEXT            201
#define IDC_CHILD                   202
#define IDC_ADULT                   203
#define IDC_BODYBUILDER             204


README.TXT

Flip2D (x86 only)
-----------------

Basic demonstration of DirectDraw page flipping.  Renders a spinning cube
to the back buffer, then flips.

App Note:
The palette chosen for 8bpp is Red, Green and Blue wash. There isn't enough
room for Yellow, Orange, and Purple. The code in the sample app itself
decides to map Yellow, Orange and Purple to Red. If you specify "DrawWithGDI"
with the Cube menu, then Yellow/Orange/Purple are drawn without very many
shades (because it's using system colors and there are only a few shades
of yellow/orange/purple are available).


DUMB3D.H

/**************************************************************************
  dumb3d.h - A simple linear algebra library for 3D.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#if !defined(DUMB3D_HPP)
#define DUMB3D_HPP

/*----------------------------------------------------------------------------

This header contains the declarations for the dumb3d functions.

*/

// real type

typedef double real;

#define M_PI            3.14159265358979323846

// forward declarations

class point_4;
class vector_4;
class matrix_4x4;

/*----------------------------------------------------------------------------

globally useful functions.

*/

inline vector_4 operator-( point_4 const &Operand1, point_4 const &Operand2 );

inline vector_4 operator+( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline vector_4 operator-( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline vector_4 operator*( vector_4 const &Multiplicand,
    real const &Multiplier );

inline vector_4 operator*( real const &Multiplier,
    vector_4 const &Multiplicand );

inline point_4 operator+( point_4 const &Operand1, vector_4 const &Operand2 );
inline point_4 operator-( point_4 const &Operand1, vector_4 const &Operand2 );
inline point_4 operator+( vector_4 const &Operand2, point_4 const &Operand1 );

inline vector_4 operator-( vector_4 const &Operand1 );

inline vector_4 CrossProduct( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline real DotProduct( vector_4 const &Operand1, vector_4 const &Operand2 );

matrix_4x4 operator*( matrix_4x4 const &Multiplicand,
    matrix_4x4 const &Multiplier );

vector_4 operator*( matrix_4x4 const &Multiplicand,
    vector_4 const &Multiplier );

point_4 operator*( matrix_4x4 const &Multiplicand,
    point_4 const &Multiplier );

/*----------------------------------------------------------------------------

quadruple.  Base class for homogeneous vectors and points.

*/

class quadruple
{
public:

  inline real GetElement( int Row ) const;

  inline real GetX( void ) const;
  inline real GetY( void ) const;
  inline real GetZ( void ) const;
  inline real GetW( void ) const;

  inline void SetElement( int Row, real Value );

  inline void SetX( real Value );
  inline void SetY( real Value );
  inline void SetZ( real Value );
  inline void SetW( real Value );

protected:

  inline quadruple( void );
  inline quadruple( real X, real Y, real Z, real W );
  inline quadruple( quadruple const & );
  inline quadruple &operator=( quadruple const & );

  real aElements[4];
};

/*----------------------------------------------------------------------------

point_4.  This class represents a homogeneous 3D point.

*/

class point_4 :
  public quadruple
{
public:

  inline point_4( void );
  inline point_4( real X, real Y, real Z );

  inline void Homogenize( void );
};

/*----------------------------------------------------------------------------

vector_4.  This class represents a homogeneous 3D vector.

*/

class vector_4 :
  public quadruple
{
public:

  inline vector_4( void );
  inline vector_4( real X, real Y, real Z );

  vector_4 &Normalize( void );
};

/*----------------------------------------------------------------------------

matrix_4x4.  This class represents row major 4x4 homogeneous matrices.

*/

class matrix_4x4
{
public:

  matrix_4x4( void );

  matrix_4x4 &ConcatenateXRotation( real Degrees );
  matrix_4x4 &ConcatenateYRotation( real Degrees );
  matrix_4x4 &ConcatenateZRotation( real Degrees );

  matrix_4x4 &ConcatenateXTranslation( real Distance );
  matrix_4x4 &ConcatenateYTranslation( real Distance );
  matrix_4x4 &ConcatenateZTranslation( real Distance );

  inline real GetElement( int Row, int Column ) const;
  inline matrix_4x4 &SetElement( int Row, int Column, real Value );
  inline matrix_4x4 & operator=(matrix_4x4 const & m);

protected:

  enum do_not_initialize { DoNotInitialize };

  inline matrix_4x4( do_not_initialize );

  real aElements[4][4];
};

/*----------------------------------------------------------------------------

view transform.

*/

class view_transform :
  public matrix_4x4
{
public:

  view_transform( point_4 const &Viewpoint, vector_4 const &ViewDirection,
    vector_4 const &Up );
};

/*----------------------------------------------------------------------------

inline function definitions.

*/

inline vector_4 operator-( point_4 const &Operand1, point_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline vector_4 operator+( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() + Operand2.GetX(),
          Operand1.GetY() + Operand2.GetY(),
          Operand1.GetZ() + Operand2.GetZ());
}

inline vector_4 operator-( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline vector_4 operator-( vector_4 const &Operand1 )
{
  return vector_4(-Operand1.GetX(),-Operand1.GetY(),-Operand1.GetZ());
}

inline vector_4 operator*( vector_4 const &Multiplicand,
    real const &Multiplier )
{
  return vector_4(Multiplicand.GetX() * Multiplier,
          Multiplicand.GetY() * Multiplier,
          Multiplicand.GetZ() * Multiplier);
}

inline vector_4 operator*( real const &Multiplier,
    vector_4 const &Multiplicand )
{
  return vector_4(Multiplicand.GetX() * Multiplier,
          Multiplicand.GetY() * Multiplier,
          Multiplicand.GetZ() * Multiplier);
}

inline point_4 operator+( point_4 const &Operand1, vector_4 const &Operand2 )
{
  return point_4(Operand1.GetX() + Operand2.GetX(),
          Operand1.GetY() + Operand2.GetY(),
          Operand1.GetZ() + Operand2.GetZ());
}

inline point_4 operator-( point_4 const &Operand1, vector_4 const &Operand2 )
{
  return point_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline point_4 operator+( vector_4 const &Operand1, point_4 const &Operand2 )
{
  return Operand2 + Operand1;
}

inline vector_4 CrossProduct( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  real X = Operand1.GetY() * Operand2.GetZ() -
        Operand1.GetZ() * Operand2.GetY();
  real Y = Operand1.GetZ() * Operand2.GetX() -
        Operand1.GetX() * Operand2.GetZ();
  real Z = Operand1.GetX() * Operand2.GetY() -
        Operand1.GetY() * Operand2.GetX();

  return vector_4(X,Y,Z);
}

inline real DotProduct( vector_4 const &Operand1, vector_4 const &Operand2 )
{
  return Operand1.GetX() * Operand2.GetX() +
      Operand1.GetY() * Operand2.GetY() +
      Operand1.GetZ() * Operand2.GetZ();
}

inline real quadruple::GetElement( int Row ) const
{
  return aElements[Row];
}

inline real quadruple::GetX( void ) const
{
  return aElements[0];
}

inline real quadruple::GetY( void ) const
{
  return aElements[1];
}

inline real quadruple::GetZ( void ) const
{
  return aElements[2];
}

inline real quadruple::GetW( void ) const
{
  return aElements[3];
}

inline void quadruple::SetElement( int Row, real Value )
{
  aElements[Row] = Value;
}

inline void quadruple::SetX( real Value )
{
  aElements[0] = Value;
}

inline void quadruple::SetY( real Value )
{
  aElements[1] = Value;
}

inline void quadruple::SetZ( real Value )
{
  aElements[2] = Value;
}

inline void quadruple::SetW( real Value )
{
  aElements[3] = Value;
}

inline void point_4::Homogenize( void )
{
  aElements[0] = aElements[0] / aElements[3];
  aElements[1] = aElements[1] / aElements[3];
  aElements[2] = aElements[2] / aElements[3];
}

inline quadruple::quadruple( void )
{
  aElements[0] = aElements[1] = aElements[2] = aElements[3] = 0;
}

inline quadruple::quadruple( real X, real Y, real Z, real W )
{
  aElements[0] = X;
  aElements[1] = Y;
  aElements[2] = Z;
  aElements[3] = W;
}

inline quadruple::quadruple( quadruple const &Source )
{
  aElements[0] = Source.aElements[0];
  aElements[1] = Source.aElements[1];
  aElements[2] = Source.aElements[2];
  aElements[3] = Source.aElements[3];
}

inline quadruple &quadruple::operator=( quadruple const &Source )
{
  aElements[0] = Source.aElements[0];
  aElements[1] = Source.aElements[1];
  aElements[2] = Source.aElements[2];
  aElements[3] = Source.aElements[3];

  return *this;
}

inline point_4::point_4( void ) :
  quadruple(0,0,0,1)
{

}

inline point_4::point_4( real X, real Y, real Z ) :
  quadruple(X,Y,Z,1)
{
#if 0
  char aBuffer[100];
  sprintf(aBuffer,"X: %f Y: %f Z: %f",X,Y,Z);
  MessageBox(0,aBuffer,"foobar",MB_OK);
  sprintf(aBuffer,"X: %f Y: %f Z: %f W:%f",aElements[0],aElements[1],
    aElements[2],aElements[3]);
  MessageBox(0,aBuffer,"foobar",MB_OK);
#endif
}

inline vector_4::vector_4( void ) :
  quadruple(0,0,0,0)
{

}

inline vector_4::vector_4( real X, real Y, real Z ) :
  quadruple(X,Y,Z,0)
{

}

inline real matrix_4x4::GetElement( int Row, int Column ) const
{
  return aElements[Row][Column];
}

inline matrix_4x4 &matrix_4x4::SetElement( int Row, int Column, real Value )
{
  aElements[Row][Column] = Value;

  return *this;
}

inline matrix_4x4::matrix_4x4( do_not_initialize )
{

}

inline matrix_4x4 & matrix_4x4::operator=(matrix_4x4 const & m)
{
    memcpy((void *) aElements, (void *) m.aElements,sizeof(aElements));
    return *this;
}
#endif


FIXED.H

/**************************************************************************

    Fixed.h - 16.16 Fixed point class

 **************************************************************************/

inline long fixed_mul(long a, long b) {return MulDiv(a, b, 65536);}
inline long fixed_div(long a, long b) {return MulDiv(a, 65536, b);}

class Fixed {
    private:
        long fx;
    public:
        Fixed()             {}
        ~Fixed()            {}

        Fixed(long l)       {fx = l<<16;}
        Fixed(int i)        {fx = (long)i<<16;}
        Fixed(double d)     {fx = (long)(d * 65536.0);}

        int Int()           {return (int)(fx >> 16);}
        int Frac()          {return (int)(fx & 0xFFFF);}

        operator int()      {return (int)(fx >> 16);}
        operator double()   {return (double)fx / 65536.0;}

        Fixed operator +(Fixed a) {Fixed c; c.fx = fx + a.fx; return c;}
        Fixed operator -(Fixed a) {Fixed c; c.fx = fx - a.fx; return c;}
        Fixed operator *(Fixed a) {Fixed c; c.fx = fixed_mul(fx,a.fx); return c;}
        Fixed operator /(Fixed a) {Fixed c; c.fx = fixed_div(fx,a.fx); return c;}

        int operator <(Fixed a)  {return fx < a.fx;}
        int operator >(Fixed a)  {return fx > a.fx;}
        int operator ==(Fixed a) {return fx == a.fx;}
        int operator !=(Fixed a) {return fx != a.fx;}

//      Fixed& operator  =(Fixed a) {fx  = a.fx; return *this;}
        Fixed& operator +=(Fixed a) {fx += a.fx; return *this;}
        Fixed& operator -=(Fixed a) {fx -= a.fx; return *this;}
        Fixed& operator *=(Fixed a) {fx  = fixed_mul(fx,a.fx); return *this;}
        Fixed& operator /=(Fixed a) {fx  = fixed_div(fx,a.fx); return *this;}
};


FLIPCUBE.CPP

/**************************************************************************

    FLIPCUBE.CPP - A spinning cube demo for DirectDraw

    basic page fliping app, just render to the back buffer and flip
    that is all I do.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <dinput.h>
#include <math.h>

#include "flipcube.h"
#include "dumb3d.h"

// code is in tri.cpp
extern void Triangle8(BYTE *p, int next_scan, POINT P0, POINT P1, POINT P2, DWORD c);

/**************************************************************************
  Global Variables
 **************************************************************************/

static char szAppName[]="DirectDraw Spinning Cube";

static HINSTANCE  hInstApp;
static BOOL       fAppActive;
static BOOL       fAppPaused;
static HWND       hwndApp;
static HACCEL     hAccelApp;
static HFONT      AppFont;
static SIZE       ScreenSize;
static BOOL       fDrawWithGDI;

/**************************************************************************
  DirectDraw Globals
 **************************************************************************/

IDirectDraw            *dd;
IDirectDrawSurface     *FrontBuffer;
IDirectDrawSurface     *BackBuffer;
IDirectDrawPalette     *Palette;

/**************************************************************************
  DirectInput Globals
 **************************************************************************/
LPDIRECTINPUTlpdi;
LPDIRECTINPUTDEVICE             lpdiZoom;       // Used for zooming
LPDIRECTINPUTDEVICE             lpdiRot;        // Use for rotation
BOOL                            fMouseAcquired = FALSE; // Acquired for rot'n

/**************************************************************************
  dumb 3D Globals
 **************************************************************************/

//*** Cube vertices, normals, shades, and modeling transform
static point_4 CubeVertices[8] =
{
  point_4( -10,  10, -10 ),
  point_4( -10,  10,  10 ),
  point_4(  10,  10,  10 ),
  point_4(  10,  10, -10 ),
  point_4(  10, -10, -10 ),
  point_4(  10, -10,  10 ),
  point_4( -10, -10,  10 ),
  point_4( -10, -10, -10 )
};
static vector_4   CubeSurfaceNormals[6];
static real       CubeSurfaceShades[6];
static matrix_4x4 CubeTransform;

//*** Cube edges - ordered indices into the vertex array
const int CubeFaces[6][4] =
{
  0, 1, 2, 3,
  2, 1, 6, 5,
  3, 2, 5, 4,
  0, 3, 4, 7,
  1, 0, 7, 6,
  4, 5, 6, 7
};

//*** Cube colors - one RGB color per surface
const unsigned char CubeColors[6][3] =
{
  240,  20,  20,    // Unsaturated Red
   20, 240,  20,    // Unsaturated Green
   20,  20, 240,    // Unsaturated Blue
  128,  64,   0,    // Brown
  240,  20, 240,    // Unsaturated Magenta
  240, 240,  20     // Unsaturated Yellow
};

//*** Lighting
vector_4   LightSourceDirection;
const real AmbientLight = 0.2;

//*** Viewing and perspective
static matrix_4x4  ViewPerspective;
static point_4     Viewpoint(60, 60, 60);
static vector_4    Up(0, 1, 0);
static point_4     Origin;

/**************************************************************************
   Internal function declarations
 **************************************************************************/

LONG  CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
BOOL  InitDInput(void);
BOOL  AppIdle(void);
void  RenderFrame(void);

void  TransformCube(matrix_4x4 const &Transform);
BOOL  ProjectAndDrawCube(HDC hdc, int XOffset, int YOffset);
BOOL  ProjectAndDrawCube(IDirectDrawSurface *pdds, int XOffset, int YOffset);

/**************************************************************************
  AppAbout

  Description:
    This function handles messages belonging to the "About" dialog box.
  The only message that it looks for is WM_COMMAND, indicating the user
  has pressed the "OK" button.
 **************************************************************************/

BOOL FAR PASCAL AppAbout(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/**************************************************************************
  DDInit

  Description:
    initialize all the DirectDraw specific stuff
 **************************************************************************/

BOOL DDInit()
{
    HRESULT err;

    err = DirectDrawCreate(NULL, &dd, NULL);

    if (err != DD_OK)
        return FALSE;

    err = dd->SetCooperativeLevel(hwndApp,
        DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

    if (err != DD_OK)
        return FALSE;

// NEWNEW init DirectInput iff DDraw inits ok
if(!InitDInput())
return FALSE;

    return TRUE;
}

/**************************************************************************
  CreateMouse
 **************************************************************************/

BOOL CreateMouse(GUID &guid, LPDIRECTINPUTDEVICE& lpdiMouse, DWORD dwAccess)
{
    HRESULT err;

    err = lpdi->CreateDevice(guid, &lpdiMouse, NULL);

    if(err != DI_OK)
    {
            MessageBox(NULL, "Unable to Create DirectInput Mouse Device",
                    "DirectDraw Spinning Cube", MB_OK);
            goto fail;
    }

    // Tell DirectInput that we want to receive data in mouse format
    err = lpdiMouse->SetDataFormat(&c_dfDIMouse);

    if(err != DI_OK)
    {
            MessageBox(NULL, "Unable to Access DirectInput Device as a mouse",
                    "DirectDraw Spinning Cube", MB_OK);
            goto fail;
    }

    // set desired access mode
    err = lpdiMouse->SetCooperativeLevel(hwndApp, dwAccess);
    if(err != DI_OK)
    {
        // Don't complain if asking for DISCL_NONEXCLUSIVE since
        // not all systems support it.  (The caller will gracefully
        // degrade.)
        if (dwAccess & DISCL_EXCLUSIVE) {
            MessageBox(NULL, "Unable to set cooperativity level",
                    "DirectDraw Spinning Cube", MB_OK);
        }
        goto fail;
    }

    return TRUE;

fail:;
    if (lpdiMouse)      lpdiMouse->Release(),       lpdiMouse = 0;
    return FALSE;
}

/**************************************************************************
  InitDInput
 **************************************************************************/
BOOL InitDInput(void)
{
HRESULTerr;
GUID     guid = GUID_SysMouse;
    // Need DirectX 3 or better
    err = DirectInputCreate(hInstApp, 0x0300, &lpdi, NULL);

if(err != DI_OK)
{
MessageBox(NULL, "Unable to Create DirectInput Object",
"DirectDraw Spinning Cube", MB_OK);
return FALSE;
}

        // Create a mouse for zooming.  Zooming is done non-exclusively.
        if (!CreateMouse(guid, lpdiZoom, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND))
        {
                /* Note!  This is not a fatal error */
        } else {

            if (fAppActive) lpdiZoom->Acquire();

        }

        // Create a mouse for rotation.  Rotation is done exclusively.
        if (!CreateMouse(guid, lpdiRot, DISCL_EXCLUSIVE | DISCL_FOREGROUND))
        {
                goto fail;
        }

        // if we get here, all DirectInput objects were created ok
return TRUE;

fail:
        if (lpdiZoom)  lpdiZoom->Release(), lpdiZoom = NULL;
        if (lpdiRot)   lpdiRot ->Release(), lpdiRot  = NULL;
        if (lpdi)      lpdi    ->Release(), lpdi     = NULL;
        return FALSE;

}

/**************************************************************************
  DDSetMode
 **************************************************************************/

BOOL DDSetMode(int width, int height, int bpp)
{
    HRESULT err;

    err = dd->SetDisplayMode(width, height, bpp);

    if (err != DD_OK)
        return FALSE;

    ScreenSize.cx = width;
    ScreenSize.cy = height;

    // get rid of any previous surfaces.
    if (BackBuffer)  BackBuffer->Release(),     BackBuffer = NULL;
    if (FrontBuffer) FrontBuffer->Release(),    FrontBuffer = NULL;
    if (Palette)     Palette->Release(),        Palette = NULL;

    //
    // Create surfaces
    //
    // what we want is a tripple buffered surface in video memory
    // so we try to create this first.
    //
    // if we cant get a triple buffered surface, we try again
    // for a double buffered surface (still must be in video memory)
    //
    // if we cant get a double buffered surface, we try for a double
    // buffered surface not being specific about video memory, we will
    // get back a main-memory surface, that work use HW page flipping
    // but at least we run.
    //
    // NOTE you need to recreate the surfaces for a new display mode
    // they wont work when/if the mode is changed.
    //
    DDSURFACEDESC ddsd;

    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.dwBackBufferCount = 2;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX |
                          DDSCAPS_VIDEOMEMORY;

    // try to get a triple buffered video memory surface.
    err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);

    if (err != DD_OK)
    {
        // try to get a double buffered video memory surface.
        ddsd.dwBackBufferCount = 1;
        err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
    }

    if (err != DD_OK)
    {
        // settle for a main memory surface.
        ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY;
        err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
    }

    if (err != DD_OK)
        return FALSE;

    // get a pointer to the back buffer
    DDSCAPS caps;
    caps.dwCaps = DDSCAPS_BACKBUFFER;
    err = FrontBuffer->GetAttachedSurface(&caps, &BackBuffer);

    if (err != DD_OK)
        return FALSE;

    // create a palette if we are in a paletized display mode.
    //
    // NOTE because we want to be able to show dialog boxs and
    // use our menu, we leave the windows reserved colors as is
    // so things dont look ugly.
    //
    // palette is setup like so:
    //
    //      10      windows system colors
    //      64      red wash
    //      64      grn wash
    //      64      blu wash
    //
    PALETTEENTRY ape[256];
    HDC hdc = GetDC(NULL);
    if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE)
    {
        // get the current windows colors.
        GetSystemPaletteEntries(hdc, 0, 256, ape);

        // make a red, grn, and blu wash for our cube.
        for (int i=0; i<64; i++)
        {
            ape[10+64*0+i].peRed   = i * 255/63;
            ape[10+64*0+i].peGreen = 0;
            ape[10+64*0+i].peBlue  = 0;

            ape[10+64*1+i].peRed   = 0;
            ape[10+64*1+i].peGreen = i * 255/63;
            ape[10+64*1+i].peBlue  = 0;

            ape[10+64*2+i].peRed   = 0;
            ape[10+64*2+i].peGreen = 0;
            ape[10+64*2+i].peBlue  = i * 255/63;
        }

        // create the palette.
        err = dd->CreatePalette(DDPCAPS_8BIT, ape, &Palette, NULL);

        if (err == DD_OK)
        {
            FrontBuffer->SetPalette(Palette);
        }
    }
    ReleaseDC(NULL, hdc);

    if (AppFont)
        DeleteObject(AppFont);

    AppFont = CreateFont(width < 640 ? 24 : 48,
        0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        NONANTIALIASED_QUALITY,
        VARIABLE_PITCH,
        "Comic Sans MS");

    return TRUE;
}

/**************************************************************************
  DDTerm
 **************************************************************************/

void DDTerm()
{
    if (BackBuffer)  BackBuffer->Release(),     BackBuffer = NULL;
    if (FrontBuffer) FrontBuffer->Release(),    FrontBuffer = NULL;
    if (Palette)     Palette->Release(),        Palette = NULL;
    if (dd)          dd->Release(),             dd = NULL;
    if (lpdi)        lpdi->Release(),           lpdi = NULL;
}

/**************************************************************************
  ModeCallback
 **************************************************************************/

#define MAKEMENUITEMDATA(width, height, bpp) \
        (width) | ((height) << 12) | ((bpp) << 24)

HRESULT CALLBACK ModeCallback(LPDDSURFACEDESC pdds, LPVOID lParam)
{
    HMENU hmenu = (HMENU)lParam;
    char ach[80];
    int n;
    int width  = pdds->dwWidth;
    int height = pdds->dwHeight;
    int bpp    = pdds->ddpfPixelFormat.dwRGBBitCount;

    n = GetMenuItemCount(hmenu);
    wsprintf(ach,"%dx%dx%d",width,height,bpp);
    AppendMenu(hmenu,MF_STRING,MENU_MODE+n,ach);

    MENUITEMINFO mii;

    // pack the mode info into a DWORD and set the extra item data.
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_DATA;
    mii.dwItemData = MAKEMENUITEMDATA(width, height, bpp);
    SetMenuItemInfo(hmenu, MENU_MODE+n, MF_BYCOMMAND, &mii);

    //return S_TRUE to stop enuming modes, S_FALSE to continue
    return S_FALSE;
}

/**************************************************************************
  AppInit

  Description:
    This is called when the application is first loaded. It initializes
  all variables, registers the window class, and creates the main app
  window.
 **************************************************************************/

BOOL AppInit(HINSTANCE hInst,HINSTANCE hPrev,int sw,LPSTR szCmdLine)
{
  WNDCLASS cls;

  /* Save instance handle for DialogBoxes */
  hInstApp = hInst;

  if (!hPrev)
  {
    //***  Register a class for the main application window
    cls.hCursor        = LoadCursor(0,IDC_ARROW);

    //*** Just for fun, we'll draw our own spinning cube icon.
    cls.hIcon          = LoadIcon(hInst, "AppIcon");
    cls.lpszMenuName   = "AppMenu";
    cls.lpszClassName  = szAppName;
    cls.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
    cls.hInstance      = hInst;
    cls.style          = CS_VREDRAW | CS_HREDRAW;
    cls.lpfnWndProc    = (WNDPROC)AppWndProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = 0;

    if (!RegisterClass(&cls))
      return FALSE;
  }

  hAccelApp = LoadAccelerators(hInst, "AppAccel");

  //*** Set and normalize the light source
  LightSourceDirection = vector_4(50, 30, -15);
  LightSourceDirection.Normalize();

  //*** Distance to view plane:
  ViewPerspective.SetElement(3, 2, 1/300.0);
  ViewPerspective.SetElement(3, 3, 0);

  //*** Viewport scaling - some arbitrary number like 3.5 will do
  ViewPerspective.SetElement(0, 0, 3.5);
  ViewPerspective.SetElement(1, 1, 3.5);

  //*** Calculate the initial normals and shades
  TransformCube(CubeTransform);

  //*** Then generate an interesting rotation for the spin
  CubeTransform.ConcatenateYRotation(6.0);
  CubeTransform.ConcatenateXRotation(3.5);
  CubeTransform.ConcatenateZRotation(2.0);

  hwndApp = CreateWindowEx(
                  WS_EX_APPWINDOW,
                  szAppName,           // Class name
                  szAppName,           // Caption
                  WS_POPUP |
                  WS_SYSMENU |
                  WS_CAPTION,
  0, 0,        // Position
  640,480,       // Size
                  0,                   // Parent window (no parent)
                  0,                   // use class menu
                  hInst,               // handle to window instance
                  0                    // no params to pass on
                  );
  ShowWindow(hwndApp,sw);
  UpdateWindow(hwndApp);

  if (!DDInit())
    return FALSE;

  // Enumerate all posible display modes, and stick them in our menu.
  // we use the extra item DWORD of a menu item to store the mode info
  HMENU hmenu = CreatePopupMenu();
  dd->EnumDisplayModes(0,NULL,(LPVOID)hmenu,ModeCallback);
  AppendMenu(GetMenu(hwndApp),MF_POPUP,(UINT)hmenu,"&Modes");

  if (!DDSetMode(640,480,8) &&
      !DDSetMode(640,480,16))
    return FALSE;

  return TRUE;
}

/**************************************************************************
  WinMain

  Description:
    The main procedure for the App.  After initializing, it just goes
  into a message-processing loop until it gets a WM_QUIT message.
 **************************************************************************/

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
  MSG     msg;

  //*** Call initialization procedure
  if (!AppInit(hInst,hPrev,sw,szCmdLine))
    return FALSE;

    //*** Polling messages from event queue until quit
  for (;;)
  {
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
      if (msg.message == WM_QUIT)
        break;

      if (!hwndApp || !TranslateAccelerator(hwndApp, hAccelApp, &msg))
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    else
    {
      if (AppIdle())
        WaitMessage();
    }
  }

  DDTerm();
  return msg.wParam;
}

/**************************************************************************
  AppPause

 **************************************************************************/

void AppPause(BOOL f)
{
    if (f)
    {
        DDSCAPS caps;
        FrontBuffer->GetCaps(&caps);

        // if we are in ModeX go back to a windows mode
        // so we can see the menu or dialog box.

        if (caps.dwCaps & DDSCAPS_MODEX)
        {
            DDSetMode(640,480,8);
        }

        // turn off rotation while paused
        if (lpdiRot) lpdiRot->Unacquire();
        fMouseAcquired = FALSE;

        fAppPaused = TRUE;
        dd->FlipToGDISurface();
        DrawMenuBar(hwndApp);
        RedrawWindow(hwndApp, NULL, NULL, RDW_FRAME);
    }
    else
    {
        fAppPaused = FALSE;

    }
}

/**************************************************************************
  MagnifyCube

  Description:
    Magnify the cube the indicated number of times.  A negative number
    makes it smaller.
 **************************************************************************/

void MagnifyCube(double times)
{
    matrix_4x4 m;
    double factor = pow(1.5, times);
    m.SetElement(0,0,factor);
    m.SetElement(1,1,factor);
    m.SetElement(2,2,factor);
    TransformCube(m);
}

/**************************************************************************
  AppIdle

  return TRUE if the app is idle
  return FALSE if the app is not idle.

  Description:
 **************************************************************************/

BOOL AppIdle()
{
  DIMOUSESTATE dims;

  //*** Spin while the app is active, lbutton is up, and spinning is on.

  //*** Spin while the app is iconized.
  if (fAppActive && !fAppPaused)
  {
    //*** If the app is active, spin the cube and redraw

    // See if any zooming needs to be done.
    if(lpdiZoom &&
       lpdiZoom->GetDeviceState(sizeof(DIMOUSESTATE), &dims) == DI_OK) {
        // 240 units of motion in the Z-axis equals one unit of
        // magnification / shrinkage.
        if(dims.lZ) {
           MagnifyCube(dims.lZ / 240.0);
        }
    }

    if(fMouseAcquired)
 {
//** If we have the mouse acquired...

// user spins cube if GetDeviceState succeeds and if the left button (button 0) is held
                if(lpdiRot->GetDeviceState(sizeof(DIMOUSESTATE), &dims) == DI_OK)
{
         if(dims.rgbButtons[0] & 0x80)
         {
            if(dims.lX || dims.lY)
            {
               matrix_4x4 Movement;
               Movement.ConcatenateYRotation(dims.lX);
               Movement.ConcatenateXRotation(dims.lY);
               TransformCube(Movement);
            }

         }
         else
   {
   // unacquire the mouse
                           lpdiRot->Unacquire();
   fMouseAcquired = FALSE;
   }
      }
    }
    else
    {
         TransformCube(CubeTransform);
    }
    RenderFrame();
    return FALSE;
  }
  else
  {
    //*** Don't do anything when not the active app
    return TRUE;
  }
}

/**************************************************************************
  RenderFrame

  render the frame into the back buffer and do a page flip.

  things to NOTE:

    we use the blter to clear the backbuffer, this usualy is a big
    win blters are real fast.

    we use GDI to draw the frame rate, and info text

    we either use GDI to draw the faces of the cube, or our own code
    based on the fDrawWithGDI global variable.

 **************************************************************************/

int FrameRate;
int FrameCount;
int FrameCount0;
DWORD FrameTime;
DWORD FrameTime0;

void RenderFrame()
{
  HDC hdc;

  //*** always need to handle DDERR_SURFACELOST, this will happen
  //*** when we get switched away from.

  if (FrontBuffer->IsLost() == DDERR_SURFACELOST)
    FrontBuffer->Restore();

  //*** use the blter to do a color fill to clear the back buffer

  DDBLTFX ddbltfx;
  ddbltfx.dwSize = sizeof(ddbltfx);
  ddbltfx.dwFillColor = 0;
  BackBuffer->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&ddbltfx);

  //*** based on the fDrawWithGDI global variable, we either
  //*** render the polygons ourself or let GDI do it

  BOOL fGDI = fDrawWithGDI;

  //*** render the cube with our own code.
  //*** we need to do this outside of the GetDC, because we cant
  //*** lock a buffer while we have a DC on it.
  //*** if ProjectAndDrawCube returns FALSE it did not want to
  //*** draw for some reason, so go ahead and use GDI

  if (!fGDI)
     fGDI = !ProjectAndDrawCube(BackBuffer, ScreenSize.cx/2, ScreenSize.cy/2);

  if (BackBuffer->GetDC(&hdc) == DD_OK)
  {
    //*** use GDI to draw the cube.
    if (fGDI)
      ProjectAndDrawCube(hdc, ScreenSize.cx/2, ScreenSize.cy/2);

    //*** draw stats, like frame number and frame rate

    char ach[128];
    int len;
    static char szHelp[] = "F10=Menu F7=Smaller F8=Larger";

    SetBkMode(hdc, TRANSPARENT);
    SelectObject(hdc, AppFont);

    len = wsprintf(ach, "FPS %02d Frame %05d", FrameRate, FrameCount);

    SetTextColor(hdc, RGB(255, 255, 0));
    TextOut(hdc, 0, 0, ach, len);
    TextOut(hdc, 0, ScreenSize.cy-(ScreenSize.cx<640 ? 24:48),szHelp,sizeof(szHelp)-1);

    BackBuffer->ReleaseDC(hdc);
  }

  //*** we have rendered the backbuffer, call flip so we can see it
  FrontBuffer->Flip(NULL, DDFLIP_WAIT);

  FrameCount++;
  FrameTime = timeGetTime();

  if (FrameTime - FrameTime0 > 1000)
  {
    FrameRate = (FrameCount - FrameCount0) * 1000 / (FrameTime - FrameTime0);
    FrameTime0 = FrameTime;
    FrameCount0 = FrameCount;
  }
}

/**************************************************************************
  AppInitMenuPopup

    If it's the Cube menu, then check or uncheck the GDI item accordingly.

    If it's the mode list popup, then add a MENUBARBREAK as necessary.

 **************************************************************************/

void AppInitMenuPopup(HWND hwnd, HMENU hmenuPopup, UINT uPos, BOOL fSys)
{
    if (fSys) return;       /* Don't mess with the sysmenu */

    switch (uPos) {
    case 0:                 /* Cube menu */
        CheckMenuItem(hmenuPopup, MENU_GDI,
                      fDrawWithGDI ? MF_CHECKED : MF_UNCHECKED);
        break;

    case 1:                 /* Mode menu */
        /*
         *  Compute how many menu items fit on the screen.
         *
         *  Note that we use ScreenSize instead of SM_CYSCREEN.
         *  This allows us to do the right thing in the face of
         *  multiple monitors.  (ScreenSize is the size of our
         *  monitor.)
         */
        int cmi;

        cmi = GetMenuItemCount(hmenuPopup);
        if (cmi) {

            RECT rcClient;
            GetClientRect(hwnd, &rcClient);

            RECT rc;
            GetMenuItemRect(hwnd, hmenuPopup, 0, &rc);

            int dyMenuItem = rc.bottom - rc.top;

            /*
             *  Aargh.  If the menu has never appeared yet, USER
             *  returns an empty rectangle.  DON'T DIVIDE BY ZERO!
             *
             *  In such case, we use the height of the menu bar.
             *  Not perfect, but close enough.  And it happens only
             *  once.
             */
            if (dyMenuItem == 0) {
                dyMenuItem = GetSystemMetrics(SM_CYMENU);
            }

            /*
             *  Aargh.  You can't change MF_MENUBARBREAK without
             *  also changing the text, so we have to get the
             *  old text and set it back.
             *
             *  While we're here, we may as well put a check-box
             *  next to the item that matches our current screen res.
             */

            HDC hdc = GetDC(0);

            DWORD dwItemDataMatch = MAKEMENUITEMDATA(
                            GetDeviceCaps(hdc, HORZRES),
                            GetDeviceCaps(hdc, VERTRES),
                            GetDeviceCaps(hdc, BITSPIXEL) *
                            GetDeviceCaps(hdc, PLANES));
            ReleaseDC(0, hdc);

            MENUITEMINFO mii;
            TCHAR tsz[80];

            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_STATE;
            mii.dwTypeData = tsz;

            /*
             *  Compute the number of MI's that fit in our client area.
             *  Note: Client area, not screen size.  This ensures that
             *  the menu pops in a reasonable location.
             */

            int cmiScreen = rcClient.bottom / dyMenuItem;
 BOOL bModeMatch = FALSE;
            for (int imi = 0; imi < cmi; imi++) {
                mii.cch = sizeof(tsz) / sizeof(tsz[0]);
                if (GetMenuItemInfo(hmenuPopup, imi, MF_BYPOSITION, &mii)) {
                    if (imi > 0 && imi % cmiScreen == 0) {
                        mii.fType |= MFT_MENUBARBREAK;
                    } else {
                        mii.fType &= ~MFT_MENUBARBREAK;
                    }

                    if (mii.dwItemData == dwItemDataMatch) {
                        mii.fState |= MFS_CHECKED;
bModeMatch = TRUE;
                    } else {
                        mii.fState &= ~MFS_CHECKED;
                    }

                    SetMenuItemInfo(hmenuPopup, imi, MF_BYPOSITION, &mii);
                }
            }
if(bModeMatch == FALSE) _asm int 3

        }
        break;
    }

}

/**************************************************************************
  AppWndProc

  Description:
    Main window proc. Standard Windows fare.
 **************************************************************************/

LONG CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  RECT Rect;

  switch (msg)
  {
    case WM_CREATE:
      break;

    case WM_ACTIVATEAPP:
      //*** Keep track of whether or not the app is in the foreground
      fAppActive = (BOOL)wParam;
          // re-acquire the zooming controller when we are activated
          if (fAppActive) {
                if (lpdiZoom) lpdiZoom->Acquire();
          } else{           // unacquire everything if app is not active
                if (lpdiZoom) lpdiZoom->Unacquire();
                if (lpdiRot) lpdiRot->Unacquire();
fMouseAcquired = FALSE;
  }
      break;

    case WM_SETCURSOR:
      if (fAppActive && !fAppPaused)
      {
        SetCursor(NULL);
        return 1;
      }
      break;

    case WM_ENTERMENULOOP:
      AppPause(TRUE);
      break;

    case WM_EXITMENULOOP:
      AppPause(FALSE);
      break;

    case WM_INITMENUPOPUP:
        AppInitMenuPopup(hwnd, (HMENU)wParam,
                         (UINT)LOWORD(lParam), (BOOL)HIWORD(lParam));

break;

    case WM_COMMAND:
      switch(LOWORD(wParam))
      {
        case MENU_ABOUT:
          AppPause(TRUE);
          DialogBox(hInstApp, "AppAbout", hwnd, (DLGPROC)AppAbout);
          AppPause(FALSE);
          break;

        case MENU_EXIT:
          PostMessage(hwnd, WM_CLOSE, 0, 0L);
          break;

        case MENU_LARGER:
          MagnifyCube(+1.0);
          break;

        case MENU_SMALLER:
          MagnifyCube(-1.0);
          break;

        case MENU_GDI:
          fDrawWithGDI = !fDrawWithGDI;
          break;
      }
      if (LOWORD(wParam) >= MENU_MODE && LOWORD(wParam) < MENU_MODE+100)
      {
        MENUITEMINFO mii;

        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_DATA;
        GetMenuItemInfo(GetMenu(hwnd), LOWORD(wParam), MF_BYCOMMAND, &mii);

        DDSetMode(
            (mii.dwItemData >> 0)  & 0xFFF,
            (mii.dwItemData >> 12) & 0xFFF,
            (mii.dwItemData >> 24) & 0x0FF);
      }
      return 0L;

    case WM_DESTROY:
  // clean up DirectInput objects
          if (fMouseAcquired) lpdiRot->Unacquire();
          if (lpdiZoom)  lpdiZoom->Release(), lpdiZoom = NULL;
          if (lpdiRot)   lpdiRot ->Release(), lpdiRot  = NULL;
          if (lpdi)      lpdi    ->Release(), lpdi     = NULL;

      hwndApp = NULL;
      PostQuitMessage(0);
      break;

    case WM_PAINT:
      break;

    case WM_MOVE:
    case WM_SIZE:
    case WM_DISPLAYCHANGE:
      if (fAppActive && !IsIconic(hwnd))
      {
        SetRect(&Rect, 0, GetSystemMetrics(SM_CYCAPTION), GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
        AdjustWindowRectEx(&Rect, WS_POPUP | WS_CAPTION, FALSE, 0);
        SetWindowPos(hwnd, NULL, Rect.left, Rect.top, Rect.right-Rect.left, Rect.bottom-Rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
      }
      break;

    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
          if(lpdiRot->Acquire() == DI_OK)
     {
        fMouseAcquired = TRUE;
     }
  else
  {
// not acquired, mouse will not do anything
  }
      break;

  }

  return DefWindowProc(hwnd,msg,wParam,lParam);
}

/**************************************************************************
  TransformCube

  Description:
    Transforms the cube vertices by the current rotation matrix.
    Recalculates normals and flat shade values for the
  directional light source.
 **************************************************************************/

void TransformCube(matrix_4x4 const &Transform)
{
  int i;

  //*** Transform the cube by the matrix
  for (i = 0; i < 8; ++i)
    CubeVertices[i] = Transform * CubeVertices[i];

  //*** Recalculate normals and shades
  for (i = 0; i < 6; ++i)
  {
    //*** Normals are perpendicular to two edges of the cube
    vector_4 Edge1, Edge2;
    Edge1 = CubeVertices[CubeFaces[i][1]] - CubeVertices[CubeFaces[i][0]];
    Edge2 = CubeVertices[CubeFaces[i][3]] - CubeVertices[CubeFaces[i][0]];
    CubeSurfaceNormals[i] = CrossProduct(Edge1, Edge2);
    CubeSurfaceNormals[i].Normalize();

    //*** Cosine shading based on the surface normal, clamped to [0, 1]
    real Shade = DotProduct(CubeSurfaceNormals[i], LightSourceDirection);
    Shade = Shade + AmbientLight;
    if (Shade < 0) Shade = 0;
    else if (Shade > 1.0) Shade = 1.0;
    CubeSurfaceShades[i] = Shade;
  }
}

/**************************************************************************
  ProjectAndDrawCube

  Description:
    Projects the cube vertices for the current viewpoint then culls
  in screen space and draws into the DC via GDI.
 **************************************************************************/

BOOL ProjectAndDrawCube(HDC hdc, int XOffset, int YOffset)
{
  //*** Create a viewing transform for the current eye position
  vector_4 ViewDirection = Origin - Viewpoint;
  ViewDirection.Normalize();
  view_transform View(Viewpoint, ViewDirection, Up);

  //*** Transform and project the vertices into screen space
  int i;
  POINT aScreenVertices[8];
  for (i = 0; i < 8; ++i)
  {
    point_4 Temp = View * CubeVertices[i];
    Temp = ViewPerspective * Temp;
    Temp.Homogenize();

    aScreenVertices[i].x = (int)Temp.GetX() + XOffset;
    aScreenVertices[i].y = (int)Temp.GetY() + YOffset;
  }

  SelectPen(hdc, GetStockPen(NULL_PEN));

  for (i = 0; i < 6; ++i)
  {
    //*** Standard culling operation based on the z value of the
    //*** cross product of the edges: are the vertices oriented in the
    //*** counterclockwise or clockwise direction?
    real v1 = aScreenVertices[ CubeFaces[i][2] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real w1 = aScreenVertices[ CubeFaces[i][0] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real v2 = aScreenVertices[ CubeFaces[i][2] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    real w2 = aScreenVertices[ CubeFaces[i][0] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    if ((v1*w2 - v2*w1) <= 0)
      continue;

    //*** Create a brush for the shaded face color using the selected dither

    HBRUSH hbr;

    //*** Get the shading colors

    int Red, Green, Blue;

    Red   = (int)(CubeColors[i][0] * CubeSurfaceShades[i]);
    Green = (int)(CubeColors[i][1] * CubeSurfaceShades[i]);
    Blue  = (int)(CubeColors[i][2] * CubeSurfaceShades[i]);

    //*** Create the dithered or PALETTERGB brush

    COLORREF cr;

    cr = RGB(Red, Green, Blue);
    hbr = CreateSolidBrush(cr);

    //*** Collect the correct points in an array
    POINT aQuadVertices[4];
    for (int j = 0; j < 4; ++j)
      aQuadVertices[j] = aScreenVertices[ CubeFaces[i][j] ];

    //*** Use GDI to draw the face
    hbr = SelectBrush(hdc, hbr);
    Polygon(hdc, aQuadVertices, 4);
    hbr = SelectBrush(hdc, hbr);
    DeleteObject(hbr);
  }

  return TRUE;
}

/**************************************************************************
  ProjectAndDrawCube

  Description:
    Projects the cube vertices for the current viewpoint then culls
  in screen space and draws them into a DirectDrawSurface via custom code
 **************************************************************************/

BOOL ProjectAndDrawCube(IDirectDrawSurface *pdds, int XOffset, int YOffset)
{
  //*** Lock the DirectDraw surface
  DDSURFACEDESC ddsd;
  ddsd.dwSize = sizeof(ddsd);

  if (pdds->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL) != DD_OK)
    return FALSE;

  //*** This code only works for 8bpp
  if (ddsd.ddpfPixelFormat.dwRGBBitCount != 8)
  {
    pdds->Unlock(NULL);
    return FALSE;
  }

  //*** Create a viewing transform for the current eye position
  vector_4 ViewDirection = Origin - Viewpoint;
  ViewDirection.Normalize();
  view_transform View(Viewpoint, ViewDirection, Up);

  //*** Transform and project the vertices into screen space
  int i;
  POINT aScreenVertices[8];
  for (i = 0; i < 8; ++i)
  {
    point_4 Temp = View * CubeVertices[i];
    Temp = ViewPerspective * Temp;
    Temp.Homogenize();

    aScreenVertices[i].x = (int)Temp.GetX() + XOffset;
    aScreenVertices[i].y = (int)Temp.GetY() + YOffset;

    //*** !!! OUR CODE DOES NOT CLIP, SO FAIL IF WE NEED CLIPPING
    if (aScreenVertices[i].x < 0 || aScreenVertices[i].x >= ScreenSize.cx ||
        aScreenVertices[i].y < 0 || aScreenVertices[i].y >= ScreenSize.cy)
    {
      pdds->Unlock(NULL);
      return FALSE;
    }
  }

  for (i = 0; i < 6; ++i)
  {
    //*** Standard culling operation based on the z value of the
    //*** cross product of the edges: are the vertices oriented in the
    //*** counterclockwise or clockwise direction?
    real v1 = aScreenVertices[ CubeFaces[i][2] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real w1 = aScreenVertices[ CubeFaces[i][0] ].x -
      aScreenVertices[ CubeFaces[i][1] ].x;
    real v2 = aScreenVertices[ CubeFaces[i][2] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    real w2 = aScreenVertices[ CubeFaces[i][0] ].y -
      aScreenVertices[ CubeFaces[i][1] ].y;
    if ((v1*w2 - v2*w1) <= 0)
      continue;

    //*** Get the shading color, palette is setup like so:
    //*** 10 system, 64 red, 64 green, 64 blue

    BYTE color;

    if (CubeColors[i][0] >= 128)
        color = (BYTE)(10 + 0*64 + (63 * CubeSurfaceShades[i]));
    else if (CubeColors[i][1] >= 128)
        color = (BYTE)(10 + 1*64 + (63 * CubeSurfaceShades[i]));
    else
        color = (BYTE)(10 + 2*64 + (63 * CubeSurfaceShades[i]));

    //*** Use code in tri.cpp draw the face

    Triangle8((BYTE*)ddsd.lpSurface, ddsd.lPitch,
        aScreenVertices[CubeFaces[i][0]],
        aScreenVertices[CubeFaces[i][1]],
        aScreenVertices[CubeFaces[i][2]],
        color);

    Triangle8((BYTE*)ddsd.lpSurface, ddsd.lPitch,
        aScreenVertices[CubeFaces[i][2]],
        aScreenVertices[CubeFaces[i][3]],
        aScreenVertices[CubeFaces[i][0]],
        color);
  }

  //*** Never ever forget to unlock!
  pdds->Unlock(NULL);
  return TRUE;
}


FLIPCUBE.H

/**************************************************************************

    FLIPCUBE.H - A spinning cube demo for WinG

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

/* Menu Items */
#define MENU_ABOUT        1
#define MENU_EXIT         2
#define MENU_SPIN         3
#define MENU_LARGER       4
#define MENU_SMALLER      5
#define MENU_GDI          6

#define MENU_MODE         100


DUMB3D.CPP

/**************************************************************************
  dumb3d.cpp - A simple linear algebra library for 3D.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include<memory.h>
#include<math.h>
#include"dumb3d.h"

/*--------------------------------------------------------------------------

matrix multiplication.

*/

matrix_4x4 operator*( matrix_4x4 const &Multiplicand,
    matrix_4x4 const &Multiplier )
{
  matrix_4x4 ReturnMatrix;

  for(int i = 0;i < 4;i++)
  {
    for(int j = 0;j < 4;j++)
    {
      real Value = 0;

      for(int k = 0;k < 4;k++)
      {
        Value += Multiplicand.GetElement(i,k) *
            Multiplier.GetElement(k,j);
      }

      ReturnMatrix.SetElement(i,j,Value);
    }
  }

  return ReturnMatrix;
}

vector_4 operator*( matrix_4x4 const &Multiplicand,
    vector_4 const &Multiplier )
{
  vector_4 ReturnPoint;

  for(int i = 0;i < 4;i++)
  {
    real Value = 0;

    for(int k = 0;k < 4;k++)
    {
      Value += Multiplicand.GetElement(i,k) *
          Multiplier.GetElement(k);
    }

    ReturnPoint.SetElement(i,Value);
  }

  return ReturnPoint;
}

point_4 operator*( matrix_4x4 const &Multiplicand,
    point_4 const &Multiplier )
{
  point_4 ReturnPoint;

  for(int i = 0;i < 4;i++)
  {
    real Value = 0;

    for(int k = 0;k < 4;k++)
    {
      Value += Multiplicand.GetElement(i,k) *
          Multiplier.GetElement(k);
    }

    ReturnPoint.SetElement(i,Value);
  }

  return ReturnPoint;
}

/*--------------------------------------------------------------------------

constructor.

*/

matrix_4x4::matrix_4x4( void )
{
  for(int Counter = 0;Counter < 16;Counter++)
  {
    aElements[0][Counter] = 0;
  }

  aElements[0][0] = aElements[1][1] = aElements[2][2] = aElements[3][3] = 1;
}

/*--------------------------------------------------------------------------

Rotations.

*/

matrix_4x4 &matrix_4x4::ConcatenateXRotation( real Degrees )
{
  real Temp01, Temp11, Temp21, Temp31;
  real Temp02, Temp12, Temp22, Temp32;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp01 = aElements[0][1] * Cos + aElements[0][2] * Sin;
  Temp11 = aElements[1][1] * Cos + aElements[1][2] * Sin;
  Temp21 = aElements[2][1] * Cos + aElements[2][2] * Sin;
  Temp31 = aElements[3][1] * Cos + aElements[3][2] * Sin;

  Temp02 = aElements[0][1] * -Sin + aElements[0][2] * Cos;
  Temp12 = aElements[1][1] * -Sin + aElements[1][2] * Cos;
  Temp22 = aElements[2][1] * -Sin + aElements[2][2] * Cos;
  Temp32 = aElements[3][1] * -Sin + aElements[3][2] * Cos;

  aElements[0][1] = Temp01;
  aElements[1][1] = Temp11;
  aElements[2][1] = Temp21;
  aElements[3][1] = Temp31;
  aElements[0][2] = Temp02;
  aElements[1][2] = Temp12;
  aElements[2][2] = Temp22;
  aElements[3][2] = Temp32;

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateYRotation( real Degrees )
{
  real Temp00, Temp10, Temp20, Temp30;
  real Temp02, Temp12, Temp22, Temp32;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp00 = aElements[0][0] * Cos + aElements[0][2] * -Sin;
  Temp10 = aElements[1][0] * Cos + aElements[1][2] * -Sin;
  Temp20 = aElements[2][0] * Cos + aElements[2][2] * -Sin;
  Temp30 = aElements[3][0] * Cos + aElements[3][2] * -Sin;

  Temp02 = aElements[0][0] * Sin + aElements[0][2] * Cos;
  Temp12 = aElements[1][0] * Sin + aElements[1][2] * Cos;
  Temp22 = aElements[2][0] * Sin + aElements[2][2] * Cos;
  Temp32 = aElements[3][0] * Sin + aElements[3][2] * Cos;

  aElements[0][0] = Temp00;
  aElements[1][0] = Temp10;
  aElements[2][0] = Temp20;
  aElements[3][0] = Temp30;
  aElements[0][2] = Temp02;
  aElements[1][2] = Temp12;
  aElements[2][2] = Temp22;
  aElements[3][2] = Temp32;

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateZRotation( real Degrees )
{
  real Temp00, Temp10, Temp20, Temp30;
  real Temp01, Temp11, Temp21, Temp31;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp00 = aElements[0][0] * Cos + aElements[0][1] * Sin;
  Temp10 = aElements[1][0] * Cos + aElements[1][1] * Sin;
  Temp20 = aElements[2][0] * Cos + aElements[2][1] * Sin;
  Temp30 = aElements[3][0] * Cos + aElements[3][1] * Sin;

  Temp01 = aElements[0][0] * -Sin + aElements[0][1] * Cos;
  Temp11 = aElements[1][0] * -Sin + aElements[1][1] * Cos;
  Temp21 = aElements[2][0] * -Sin + aElements[2][1] * Cos;
  Temp31 = aElements[3][0] * -Sin + aElements[3][1] * Cos;

  aElements[0][0] = Temp00;
  aElements[1][0] = Temp10;
  aElements[2][0] = Temp20;
  aElements[3][0] = Temp30;
  aElements[0][1] = Temp01;
  aElements[1][1] = Temp11;
  aElements[2][1] = Temp21;
  aElements[3][1] = Temp31;

  return *this;
}

/*--------------------------------------------------------------------------

Translations.

*/

matrix_4x4 &matrix_4x4::ConcatenateXTranslation( real Distance )
{
  aElements[0][3] = aElements[0][0] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][0] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][0] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][0] * Distance + aElements[3][3];

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateYTranslation( real Distance )
{
  aElements[0][3] = aElements[0][1] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][1] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][1] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][1] * Distance + aElements[3][3];

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateZTranslation( real Distance )
{
  aElements[0][3] = aElements[0][2] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][2] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][2] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][2] * Distance + aElements[3][3];

  return *this;
}

/*--------------------------------------------------------------------------

vector normalize.

*/

vector_4 &vector_4::Normalize( void )
{
  real Length = sqrt(GetX()*GetX() + GetY()*GetY() + GetZ()*GetZ());

  SetX(GetX() / Length);
  SetY(GetY() / Length);
  SetZ(GetZ() / Length);

  return *this;
}

/*--------------------------------------------------------------------------

view transform ctor.

*/

view_transform::view_transform( point_4 const &Viewpoint,
  vector_4 const &ViewDirection, vector_4 const &Up )
{
  // translate the viewpoint to the origin

  this->ConcatenateXTranslation(-Viewpoint.GetX());
  this->ConcatenateYTranslation(-Viewpoint.GetY());
  this->ConcatenateZTranslation(-Viewpoint.GetZ());

  // get view vectors set up

  vector_4 Right = -CrossProduct(ViewDirection,Up);
  vector_4 ReallyUp = CrossProduct(Right,ViewDirection);

  matrix_4x4 LookDownZ;

  for(int Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(0,Counter,Right.GetElement(Counter));
  }

  for(Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(1,Counter,ReallyUp.GetElement(Counter));
  }

  for(Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(2,Counter,ViewDirection.GetElement(Counter));
  }

  this->matrix_4x4::operator=(LookDownZ * *this);
}


TRI.CPP

/**************************************************************************

    TRI.CPP - Simple triangle rasterizer

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include "Fixed.h"

#define SWAP(x,y)   ((x)^=(y)^=(x)^=(y))
//SWAP has a compiler generated problem on alpha.... do it the old fashioned way:
#define SWAPPT(a,b) {POINT p; p=a;a=b;b=p;} //(SWAP(a.y,b.y), SWAP(a.x,b.x))

/**************************************************************************

    FillScan8

    fill a scan from x0 to x1 (inclusive) with color c

    does optimal DWORD writes, making sure to stay aligned so
    it is safe for video memory.

 **************************************************************************/
inline void FillScan8(BYTE *p, int x0, int x1, DWORD c)
{
    int dx;
    int z;

    dx = x1-x0+1;
    p += x0;

    if (dx > 4)
    {
        if (z = (DWORD)p & 0x03)
        {
            while (z++ < 4)
            {
                *p++ = (BYTE)c;
                dx--;
            }
        }

        while (dx >= 4)
        {
            *((DWORD*)p) = c;
            p += 4;
            dx -= 4;
        }
    }

    while (dx-- > 0)
    {
        *p++ = (BYTE)c;
    }
}

/**************************************************************************

    Triangle8

    rasterize a solid color triangle into a 8bpp memory buffer.

    NOTE this code does no clipping you better pass

    rasterizes a solid color triangle into a memory buffer with any pitch
    also is careful to always write DWORD aligned, so it is safe to be
    used on video memory (and faster too...)

 **************************************************************************/

void Triangle8(BYTE *p, int next_scan, POINT P0, POINT P1, POINT P2, DWORD c)
{
    Fixed   d,d0;
    Fixed   x,x0;
    int     y;

    //
    // expand the color to a DWORD
    //
    c |= c<<8;
    c |= c<<16;

    //
    //  sort points so P0.y <= P1.y <= P2.y
    //
    if (P0.y > P1.y) SWAPPT(P0,P1);
    if (P1.y > P2.y) SWAPPT(P1,P2);
    if (P0.y > P1.y) SWAPPT(P0,P1);

    //
    //  check for quick out?
    //
    if (P2.y - P0.y == 0)
    {
        return;
    }

    //
    //  compute "long" side walk from P0 to P2
    //
    d = (Fixed)(P2.x - P0.x) / (Fixed)(P2.y - P0.y);

    x  = P0.x;
    y  = P0.y;
    p += P0.y * next_scan;   // point p to correct scan.

    //
    //  do the top
    //
    if (P0.y < P1.y)
    {
        d0 = (Fixed)(P1.x - P0.x) / (Fixed)(P1.y - P0.y);
        x0 = P0.x;

        //
        // check for left or right fill
        //
        if (d < d0)
        {
            while (y < P1.y)
            {
                FillScan8(p, x, x0, c);
                y++;
                p  += next_scan;
                x  += d;
                x0 += d0;
            }
        }
        else
        {
            while (y < P1.y)
            {
                FillScan8(p, x0, x, c);
                y++;
                p  += next_scan;
                x  += d;
                x0 += d0;
            }
        }
    }

    //
    // do the bottom.
    //

    if (P2.y - P1.y == 0)
    {
        return;
    }

    d0 = (Fixed)(P2.x - P1.x) / (Fixed)(P2.y - P1.y);
    x0 = P1.x;

    //
    // check for left or right fill
    //
    if (x < x0)
    {
        while (y < P2.y)
        {
            FillScan8(p, x, x0, c);
            y++;
            p  += next_scan;
            x  += d;
            x0 += d0;
        }
    }
    else
    {
        while (y < P2.y)
        {
            FillScan8(p, x0, x, c);
            y++;
            p  += next_scan;
            x  += d;
            x0 += d0;
        }
    }
}


README.TXT

Flip3D

Direct3D Immediate Mode (DrawPrimitive) Sample
Copyright (C) 1997 by Microsoft Corporation. All Rights Reserved.

This is a small, self-contained DrawPrimitive sample with
the following features.
    It does its own transform and lighting, so uses D3DTLVERTEX
        data only in its DrawPrimitive calls.
    It is full-screen only, which simplifies the code.
    It illustrates how to use GDI to do color conversion
        into texture surfaces.  See the D3DTexture class in
        texture.h and texture.cpp.


DUMB3D.CPP

/**************************************************************************
  dumb3d.cpp - A simple linear algebra library for 3D.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include<memory.h>
#include<math.h>
#include"dumb3d.h"

/*--------------------------------------------------------------------------

matrix multiplication.

*/

matrix_4x4 operator*( matrix_4x4 const &Multiplicand,
    matrix_4x4 const &Multiplier )
{
  matrix_4x4 ReturnMatrix;

  for(int i = 0;i < 4;i++)
  {
    for(int j = 0;j < 4;j++)
    {
      real Value = 0;

      for(int k = 0;k < 4;k++)
      {
        Value += Multiplicand.GetElement(i,k) *
            Multiplier.GetElement(k,j);
      }

      ReturnMatrix.SetElement(i,j,Value);
    }
  }

  return ReturnMatrix;
}

vector_4 operator*( matrix_4x4 const &Multiplicand,
    vector_4 const &Multiplier )
{
  vector_4 ReturnPoint;

  for(int i = 0;i < 4;i++)
  {
    real Value = 0;

    for(int k = 0;k < 4;k++)
    {
      Value += Multiplicand.GetElement(i,k) *
          Multiplier.GetElement(k);
    }

    ReturnPoint.SetElement(i,Value);
  }

  return ReturnPoint;
}

point_4 operator*( matrix_4x4 const &Multiplicand,
    point_4 const &Multiplier )
{
  point_4 ReturnPoint;

  for(int i = 0;i < 4;i++)
  {
    real Value = 0;

    for(int k = 0;k < 4;k++)
    {
      Value += Multiplicand.GetElement(i,k) *
          Multiplier.GetElement(k);
    }

    ReturnPoint.SetElement(i,Value);
  }

  return ReturnPoint;
}

/*--------------------------------------------------------------------------

constructor.

*/

matrix_4x4::matrix_4x4( void )
{
  for(int Counter = 0;Counter < 16;Counter++)
  {
    aElements[0][Counter] = 0;
  }

  aElements[0][0] = aElements[1][1] = aElements[2][2] = aElements[3][3] = 1;
}

/*--------------------------------------------------------------------------

Rotations.

*/

matrix_4x4 &matrix_4x4::ConcatenateXRotation( real Degrees )
{
  real Temp01, Temp11, Temp21, Temp31;
  real Temp02, Temp12, Temp22, Temp32;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp01 = aElements[0][1] * Cos + aElements[0][2] * Sin;
  Temp11 = aElements[1][1] * Cos + aElements[1][2] * Sin;
  Temp21 = aElements[2][1] * Cos + aElements[2][2] * Sin;
  Temp31 = aElements[3][1] * Cos + aElements[3][2] * Sin;

  Temp02 = aElements[0][1] * -Sin + aElements[0][2] * Cos;
  Temp12 = aElements[1][1] * -Sin + aElements[1][2] * Cos;
  Temp22 = aElements[2][1] * -Sin + aElements[2][2] * Cos;
  Temp32 = aElements[3][1] * -Sin + aElements[3][2] * Cos;

  aElements[0][1] = Temp01;
  aElements[1][1] = Temp11;
  aElements[2][1] = Temp21;
  aElements[3][1] = Temp31;
  aElements[0][2] = Temp02;
  aElements[1][2] = Temp12;
  aElements[2][2] = Temp22;
  aElements[3][2] = Temp32;

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateYRotation( real Degrees )
{
  real Temp00, Temp10, Temp20, Temp30;
  real Temp02, Temp12, Temp22, Temp32;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp00 = aElements[0][0] * Cos + aElements[0][2] * -Sin;
  Temp10 = aElements[1][0] * Cos + aElements[1][2] * -Sin;
  Temp20 = aElements[2][0] * Cos + aElements[2][2] * -Sin;
  Temp30 = aElements[3][0] * Cos + aElements[3][2] * -Sin;

  Temp02 = aElements[0][0] * Sin + aElements[0][2] * Cos;
  Temp12 = aElements[1][0] * Sin + aElements[1][2] * Cos;
  Temp22 = aElements[2][0] * Sin + aElements[2][2] * Cos;
  Temp32 = aElements[3][0] * Sin + aElements[3][2] * Cos;

  aElements[0][0] = Temp00;
  aElements[1][0] = Temp10;
  aElements[2][0] = Temp20;
  aElements[3][0] = Temp30;
  aElements[0][2] = Temp02;
  aElements[1][2] = Temp12;
  aElements[2][2] = Temp22;
  aElements[3][2] = Temp32;

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateZRotation( real Degrees )
{
  real Temp00, Temp10, Temp20, Temp30;
  real Temp01, Temp11, Temp21, Temp31;

  real Radians = (Degrees/360) * M_PI * 2;

  real Sin = sin(Radians), Cos = cos(Radians);

  Temp00 = aElements[0][0] * Cos + aElements[0][1] * Sin;
  Temp10 = aElements[1][0] * Cos + aElements[1][1] * Sin;
  Temp20 = aElements[2][0] * Cos + aElements[2][1] * Sin;
  Temp30 = aElements[3][0] * Cos + aElements[3][1] * Sin;

  Temp01 = aElements[0][0] * -Sin + aElements[0][1] * Cos;
  Temp11 = aElements[1][0] * -Sin + aElements[1][1] * Cos;
  Temp21 = aElements[2][0] * -Sin + aElements[2][1] * Cos;
  Temp31 = aElements[3][0] * -Sin + aElements[3][1] * Cos;

  aElements[0][0] = Temp00;
  aElements[1][0] = Temp10;
  aElements[2][0] = Temp20;
  aElements[3][0] = Temp30;
  aElements[0][1] = Temp01;
  aElements[1][1] = Temp11;
  aElements[2][1] = Temp21;
  aElements[3][1] = Temp31;

  return *this;
}

/*--------------------------------------------------------------------------

Translations.

*/

matrix_4x4 &matrix_4x4::ConcatenateXTranslation( real Distance )
{
  aElements[0][3] = aElements[0][0] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][0] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][0] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][0] * Distance + aElements[3][3];

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateYTranslation( real Distance )
{
  aElements[0][3] = aElements[0][1] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][1] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][1] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][1] * Distance + aElements[3][3];

  return *this;
}

matrix_4x4 &matrix_4x4::ConcatenateZTranslation( real Distance )
{
  aElements[0][3] = aElements[0][2] * Distance + aElements[0][3];
  aElements[1][3] = aElements[1][2] * Distance + aElements[1][3];
  aElements[2][3] = aElements[2][2] * Distance + aElements[2][3];
  aElements[3][3] = aElements[3][2] * Distance + aElements[3][3];

  return *this;
}

/*--------------------------------------------------------------------------

vector normalize.

*/

vector_4 &vector_4::Normalize( void )
{
  real Length = sqrt(GetX()*GetX() + GetY()*GetY() + GetZ()*GetZ());

  SetX(GetX() / Length);
  SetY(GetY() / Length);
  SetZ(GetZ() / Length);

  return *this;
}

/*--------------------------------------------------------------------------

view transform ctor.

*/

view_transform::view_transform( point_4 const &Viewpoint,
  vector_4 const &ViewDirection, vector_4 const &Up )
{
  // translate the viewpoint to the origin

  this->ConcatenateXTranslation(-Viewpoint.GetX());
  this->ConcatenateYTranslation(-Viewpoint.GetY());
  this->ConcatenateZTranslation(-Viewpoint.GetZ());

  // get view vectors set up

  vector_4 Right = -CrossProduct(ViewDirection,Up);
  vector_4 ReallyUp = CrossProduct(Right,ViewDirection);

  matrix_4x4 LookDownZ;

  for(int Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(0,Counter,Right.GetElement(Counter));
  }

  for(Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(1,Counter,ReallyUp.GetElement(Counter));
  }

  for(Counter = 0;Counter < 3;Counter++)
  {
    LookDownZ.SetElement(2,Counter,ViewDirection.GetElement(Counter));
  }

  this->matrix_4x4::operator=(LookDownZ * *this);
}


DUMB3D.H

/**************************************************************************
  dumb3d.h - A simple linear algebra library for 3D.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#if !defined(DUMB3D_HPP)
#define DUMB3D_HPP

/*----------------------------------------------------------------------------

This header contains the declarations for the dumb3d functions.

*/

// real type

typedef double real;

#define M_PI            3.14159265358979323846

// forward declarations

class point_4;
class vector_4;
class matrix_4x4;

/*----------------------------------------------------------------------------

globally useful functions.

*/

inline vector_4 operator-( point_4 const &Operand1, point_4 const &Operand2 );

inline vector_4 operator+( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline vector_4 operator-( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline vector_4 operator*( vector_4 const &Multiplicand,
    real const &Multiplier );

inline vector_4 operator*( real const &Multiplier,
    vector_4 const &Multiplicand );

inline point_4 operator+( point_4 const &Operand1, vector_4 const &Operand2 );
inline point_4 operator-( point_4 const &Operand1, vector_4 const &Operand2 );
inline point_4 operator+( vector_4 const &Operand2, point_4 const &Operand1 );

inline vector_4 operator-( vector_4 const &Operand1 );

inline vector_4 CrossProduct( vector_4 const &Operand1,
    vector_4 const &Operand2 );

inline real DotProduct( vector_4 const &Operand1, vector_4 const &Operand2 );

matrix_4x4 operator*( matrix_4x4 const &Multiplicand,
    matrix_4x4 const &Multiplier );

vector_4 operator*( matrix_4x4 const &Multiplicand,
    vector_4 const &Multiplier );

point_4 operator*( matrix_4x4 const &Multiplicand,
    point_4 const &Multiplier );

/*----------------------------------------------------------------------------

quadruple.  Base class for homogeneous vectors and points.

*/

class quadruple
{
public:

  inline real GetElement( int Row ) const;

  inline real GetX( void ) const;
  inline real GetY( void ) const;
  inline real GetZ( void ) const;
  inline real GetW( void ) const;

  inline void SetElement( int Row, real Value );

  inline void SetX( real Value );
  inline void SetY( real Value );
  inline void SetZ( real Value );
  inline void SetW( real Value );

protected:

  inline quadruple( void );
  inline quadruple( real X, real Y, real Z, real W );
  inline quadruple( quadruple const & );
  inline quadruple &operator=( quadruple const & );

  real aElements[4];
};

/*----------------------------------------------------------------------------

point_4.  This class represents a homogeneous 3D point.

*/

class point_4 :
  public quadruple
{
public:

  inline point_4( void );
  inline point_4( real X, real Y, real Z );

  inline void Homogenize( void );
};

/*----------------------------------------------------------------------------

vector_4.  This class represents a homogeneous 3D vector.

*/

class vector_4 :
  public quadruple
{
public:

  inline vector_4( void );
  inline vector_4( real X, real Y, real Z );

  vector_4 &Normalize( void );
};

/*----------------------------------------------------------------------------

matrix_4x4.  This class represents row major 4x4 homogeneous matrices.

*/

class matrix_4x4
{
public:

  matrix_4x4( void );

  matrix_4x4 &ConcatenateXRotation( real Degrees );
  matrix_4x4 &ConcatenateYRotation( real Degrees );
  matrix_4x4 &ConcatenateZRotation( real Degrees );

  matrix_4x4 &ConcatenateXTranslation( real Distance );
  matrix_4x4 &ConcatenateYTranslation( real Distance );
  matrix_4x4 &ConcatenateZTranslation( real Distance );

  inline real GetElement( int Row, int Column ) const;
  inline matrix_4x4 &SetElement( int Row, int Column, real Value );
  inline matrix_4x4 & operator=(matrix_4x4 const & m);

protected:

  enum do_not_initialize { DoNotInitialize };

  inline matrix_4x4( do_not_initialize );

  real aElements[4][4];
};

/*----------------------------------------------------------------------------

view transform.

*/

class view_transform :
  public matrix_4x4
{
public:

  view_transform( point_4 const &Viewpoint, vector_4 const &ViewDirection,
    vector_4 const &Up );
};

/*----------------------------------------------------------------------------

inline function definitions.

*/

inline vector_4 operator-( point_4 const &Operand1, point_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline vector_4 operator+( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() + Operand2.GetX(),
          Operand1.GetY() + Operand2.GetY(),
          Operand1.GetZ() + Operand2.GetZ());
}

inline vector_4 operator-( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  return vector_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline vector_4 operator-( vector_4 const &Operand1 )
{
  return vector_4(-Operand1.GetX(),-Operand1.GetY(),-Operand1.GetZ());
}

inline vector_4 operator*( vector_4 const &Multiplicand,
    real const &Multiplier )
{
  return vector_4(Multiplicand.GetX() * Multiplier,
          Multiplicand.GetY() * Multiplier,
          Multiplicand.GetZ() * Multiplier);
}

inline vector_4 operator*( real const &Multiplier,
    vector_4 const &Multiplicand )
{
  return vector_4(Multiplicand.GetX() * Multiplier,
          Multiplicand.GetY() * Multiplier,
          Multiplicand.GetZ() * Multiplier);
}

inline point_4 operator+( point_4 const &Operand1, vector_4 const &Operand2 )
{
  return point_4(Operand1.GetX() + Operand2.GetX(),
          Operand1.GetY() + Operand2.GetY(),
          Operand1.GetZ() + Operand2.GetZ());
}

inline point_4 operator-( point_4 const &Operand1, vector_4 const &Operand2 )
{
  return point_4(Operand1.GetX() - Operand2.GetX(),
          Operand1.GetY() - Operand2.GetY(),
          Operand1.GetZ() - Operand2.GetZ());
}

inline point_4 operator+( vector_4 const &Operand1, point_4 const &Operand2 )
{
  return Operand2 + Operand1;
}

inline vector_4 CrossProduct( vector_4 const &Operand1,
    vector_4 const &Operand2 )
{
  real X = Operand1.GetY() * Operand2.GetZ() -
        Operand1.GetZ() * Operand2.GetY();
  real Y = Operand1.GetZ() * Operand2.GetX() -
        Operand1.GetX() * Operand2.GetZ();
  real Z = Operand1.GetX() * Operand2.GetY() -
        Operand1.GetY() * Operand2.GetX();

  return vector_4(X,Y,Z);
}

inline real DotProduct( vector_4 const &Operand1, vector_4 const &Operand2 )
{
  return Operand1.GetX() * Operand2.GetX() +
      Operand1.GetY() * Operand2.GetY() +
      Operand1.GetZ() * Operand2.GetZ();
}

inline real quadruple::GetElement( int Row ) const
{
  return aElements[Row];
}

inline real quadruple::GetX( void ) const
{
  return aElements[0];
}

inline real quadruple::GetY( void ) const
{
  return aElements[1];
}

inline real quadruple::GetZ( void ) const
{
  return aElements[2];
}

inline real quadruple::GetW( void ) const
{
  return aElements[3];
}

inline void quadruple::SetElement( int Row, real Value )
{
  aElements[Row] = Value;
}

inline void quadruple::SetX( real Value )
{
  aElements[0] = Value;
}

inline void quadruple::SetY( real Value )
{
  aElements[1] = Value;
}

inline void quadruple::SetZ( real Value )
{
  aElements[2] = Value;
}

inline void quadruple::SetW( real Value )
{
  aElements[3] = Value;
}

inline void point_4::Homogenize( void )
{
  aElements[0] = aElements[0] / aElements[3];
  aElements[1] = aElements[1] / aElements[3];
  aElements[2] = aElements[2] / aElements[3];
}

inline quadruple::quadruple( void )
{
  aElements[0] = aElements[1] = aElements[2] = aElements[3] = 0;
}

inline quadruple::quadruple( real X, real Y, real Z, real W )
{
  aElements[0] = X;
  aElements[1] = Y;
  aElements[2] = Z;
  aElements[3] = W;
}

inline quadruple::quadruple( quadruple const &Source )
{
  aElements[0] = Source.aElements[0];
  aElements[1] = Source.aElements[1];
  aElements[2] = Source.aElements[2];
  aElements[3] = Source.aElements[3];
}

inline quadruple &quadruple::operator=( quadruple const &Source )
{
  aElements[0] = Source.aElements[0];
  aElements[1] = Source.aElements[1];
  aElements[2] = Source.aElements[2];
  aElements[3] = Source.aElements[3];

  return *this;
}

inline point_4::point_4( void ) :
  quadruple(0,0,0,1)
{

}

inline point_4::point_4( real X, real Y, real Z ) :
  quadruple(X,Y,Z,1)
{
#if 0
  char aBuffer[100];
  sprintf(aBuffer,"X: %f Y: %f Z: %f",X,Y,Z);
  MessageBox(0,aBuffer,"foobar",MB_OK);
  sprintf(aBuffer,"X: %f Y: %f Z: %f W:%f",aElements[0],aElements[1],
    aElements[2],aElements[3]);
  MessageBox(0,aBuffer,"foobar",MB_OK);
#endif
}

inline vector_4::vector_4( void ) :
  quadruple(0,0,0,0)
{

}

inline vector_4::vector_4( real X, real Y, real Z ) :
  quadruple(X,Y,Z,0)
{

}

inline real matrix_4x4::GetElement( int Row, int Column ) const
{
  return aElements[Row][Column];
}

inline matrix_4x4 &matrix_4x4::SetElement( int Row, int Column, real Value )
{
  aElements[Row][Column] = Value;

  return *this;
}

inline matrix_4x4::matrix_4x4( do_not_initialize )
{

}

inline matrix_4x4 & matrix_4x4::operator=(matrix_4x4 const & m)
{
    memcpy((void *) aElements, (void *) m.aElements,sizeof(aElements));
    return *this;
}
#endif


FLIPCUBE.CPP

/**************************************************************************

    FLIP3D.CPP - A spinning cube demo for DirectDraw

    using Direct3D imediate mode (RenderPrimitive) for rendering.

    uses D3DTLVERTEX, this app does all its own transform and lighting
    dumb3d.cpp is our "3d" package.

    basic page fliping app, just render to the back buffer and flip
    that is all I do.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995 - 1998 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#define D3D_OVERLOADS

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <d3d.h>
#include <math.h>

#include "flipcube.h"
#include "dumb3d.h"
#include "d3dtex.h"

/**************************************************************************
  Global Variables
 **************************************************************************/

char szAppName[]="Flip3D";

HINSTANCE  hInstApp;
BOOL       fAppActive;
BOOL       fAppPaused;
HWND       hwndApp;
HACCEL     hAccelApp;
HFONT      AppFont;
SIZE   ScreenSize;

/**************************************************************************
  DirectDraw Globals
 **************************************************************************/

IDirectDraw            *dd;
IDirectDrawSurface     *FrontBuffer;
IDirectDrawSurface     *BackBuffer;
IDirectDrawPalette     *Palette;
char                    DeviceName[128];

/**************************************************************************
  Direct3D Globals
 **************************************************************************/

IDirect3D2             *d3d;
IDirect3DDevice2       *d3dDevice;
IDirect3DViewport2     *d3dViewport;
IDirect3DLight         *d3dLight;
char                   *d3dName;

D3DCULL                 CullMode = D3DCULL_CCW;

D3DTexture              Texture;

D3DTEXTUREHANDLE TextureHandle      = 0;
BOOL             TexturePerspective = TRUE;
BOOL             TextureDither      = FALSE;
D3DTEXTUREBLEND  TextureBlend       = D3DTBLEND_MODULATE;
D3DMATERIALHANDLE hMat= 0;
LPDIRECT3DMATERIAL2 lpMat=NULL;

/**************************************************************************
  dumb 3D Globals
 **************************************************************************/

//*** Cube vertices, normals, shades, and modeling transform
static point_4 CubeVertices[8] =
{
  point_4( -10,  10, -10 ),
  point_4( -10,  10,  10 ),
  point_4(  10,  10,  10 ),
  point_4(  10,  10, -10 ),
  point_4(  10, -10, -10 ),
  point_4(  10, -10,  10 ),
  point_4( -10, -10,  10 ),
  point_4( -10, -10, -10 )
};
static vector_4   CubeSurfaceNormals[6];
static real       CubeSurfaceShades[6];
static matrix_4x4 CubeTransform;

//*** Cube edges - ordered indices into the vertex array
const int CubeFaces[6][4] =
{
  0, 1, 2, 3,
  2, 1, 6, 5,
  3, 2, 5, 4,
  0, 3, 4, 7,
  1, 0, 7, 6,
  4, 5, 6, 7
};

//*** Cube colors - one RGB color per surface
const unsigned char CubeColors[6][3] =
{
  240,  20,  20,    // Unsaturated Red
   20, 240,  20,    // Unsaturated Green
   20,  20, 240,    // Unsaturated Blue
  128,  64,   0,    // Brown
  240,  20, 240,    // Unsaturated Magenta
  240, 240,  20     // Unsaturated Yellow
};

//*** Lighting
vector_4   LightSourceDirection;
const real AmbientLight = 0.2;

//*** Viewing and perspective
static matrix_4x4  ViewPerspective;
static point_4     Viewpoint(60, 60, 60);
static vector_4    Up(0, 1, 0);
static point_4     Origin;

//*** Interaction
static POINT       Move;
static POINT       Last;

/**************************************************************************
   Internal function declarations
 **************************************************************************/
LONG  CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
BOOL  AppIdle(void);
void  ClearFrame(void);
void  RenderFrame(void);

void  TransformCube(matrix_4x4 const &Transform);

/**************************************************************************
 **************************************************************************/
BOOL  ProjectAndDrawCube(IDirect3DDevice2 *pd3d, int XOffset, int YOffset);

/**************************************************************************
 **************************************************************************/
void BuildDeviceMenu(void);
void BuildModeMenu(void);
BOOL DDSetMode(int,int,int);

/**************************************************************************
  AppAbout

  Description:
    This function handles messages belonging to the "About" dialog box.
  The only message that it looks for is WM_COMMAND, indicating the user
  has pressed the "OK" button.
 **************************************************************************/

BOOL FAR PASCAL AppAbout(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/**************************************************************************
  DDTerm
 **************************************************************************/

#define RELEASE(x) if (x) { x->Release(); x = NULL; }

void DDTerm(BOOL fAll=TRUE)
{
    RELEASE(d3dDevice);
    RELEASE(d3dViewport);
    RELEASE(d3dLight);

    RELEASE(BackBuffer);
    RELEASE(FrontBuffer);
    RELEASE(Palette);
RELEASE(lpMat);

    Texture.Release();

    if (fAll)
    {
        RELEASE(d3d);
        RELEASE(dd);
    }
}

/**************************************************************************
 * FindDeviceCallback
 **************************************************************************/

BOOL CALLBACK FindDeviceCallback(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam)
{
    char ach[128];
    char * szFind = (char *)lParam;

    wsprintf(ach,"%s (%s)",szName, szDevice);

    if (lstrcmpi(szFind, szDevice) == 0 || lstrcmpi(szFind, ach) == 0)
    {
        DirectDrawCreate(lpGUID, &dd, NULL);
        return DDENUMRET_CANCEL;
    }
    return DDENUMRET_OK;
}

/**************************************************************************
  DDInit

  Description:
    initialize all the DirectDraw/Direct3D specific stuff
 **************************************************************************/

BOOL DDInit(char *szDevice=NULL)
{
    HRESULT err;

    DDTerm();

    if (szDevice && szDevice[0])
        DirectDrawEnumerate(FindDeviceCallback, (LPVOID)szDevice);

    if (dd == NULL)
    {
        szDevice = NULL;
        err = DirectDrawCreate(NULL, &dd, NULL);
    }

    if (dd == NULL)
    {
        MessageBox(hwndApp, "This app requires DirectDraw", szAppName, MB_OK);
        return FALSE;
    }

    err = dd->SetCooperativeLevel(hwndApp,
        DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

    if (err != DD_OK)
        return FALSE;

    err = dd->QueryInterface(IID_IDirect3D2, (void**)&d3d);

    if (err != DD_OK)
    {
        MessageBox(hwndApp, "This app requires DirectX 5.0", szAppName, MB_OK);
        return FALSE;
    }
    //
    //Not imp
    //
    BuildModeMenu();

    if (!DDSetMode(640,480,16) &&
        !DDSetMode(640,480,8))
    {
        return FALSE;
    }

    char ach[128];
    wsprintf(ach, "%s - %s %s", szAppName, szDevice ? szDevice : "DISPLAY", d3dName);
    SetWindowText(hwndApp, ach);

    return TRUE;
}

/**************************************************************************
  Init3DState
 **************************************************************************/
BOOL Init3DState()
{
    D3DMATERIAL mat;

    /*
     * Set default render state
     */
    d3dDevice->SetRenderState(D3DRENDERSTATE_ZENABLE, 0);
    d3dDevice->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, 0);
    d3dDevice->SetRenderState(D3DRENDERSTATE_SHADEMODE, D3DSHADE_FLAT);

    /*
     * load our single texture map from a resource
     */
    Texture.Load(d3dDevice, "Win95");
//  Texture.Load(d3dDevice, "Checker");
    TextureHandle = Texture.GetHandle();

    if (d3d->CreateMaterial(&lpMat, NULL) != D3D_OK) {
return FALSE;
    }
    memset(&mat, 0, sizeof(D3DMATERIAL));
    mat.dwSize = sizeof(D3DMATERIAL);
    mat.diffuse.r = (D3DVALUE)1.0;
    mat.diffuse.g = (D3DVALUE)1.0;
    mat.diffuse.b = (D3DVALUE)1.0;
    mat.ambient.r = (D3DVALUE)1.0;
    mat.ambient.g = (D3DVALUE)1.0;
    mat.ambient.b = (D3DVALUE)1.0;
    mat.specular.r = (D3DVALUE)1.0;
    mat.specular.g = (D3DVALUE)1.0;
    mat.specular.b = (D3DVALUE)1.0;
    mat.power = (float)40.0;
    mat.hTexture = TextureHandle;
    mat.dwRampSize = 1;
    lpMat->SetMaterial(&mat);
    lpMat->GetHandle(d3dDevice, &hMat);

    return TRUE;
}

/**************************************************************************
  Init3D
 **************************************************************************/
BOOL Init3D()
{
    HRESULT err = E_FAIL;

    //
    // create a Direct3D device.
    // first try HAL, then RGB, RAMP
    //
    d3dName = "HAL";
    err = d3d->CreateDevice(IID_IDirect3DHALDevice, BackBuffer, &d3dDevice);

    if (err != DD_OK)
    {
        d3dName = "RGB";
        err = d3d->CreateDevice(IID_IDirect3DRGBDevice, BackBuffer, &d3dDevice);
    }

    if (err != DD_OK)
    {
        d3dName = "RAMP";
        err = d3d->CreateDevice(IID_IDirect3DRampDevice, BackBuffer, &d3dDevice);
    }

    if (err != DD_OK)
        return FALSE;

    //
    // now make a Viewport
    //
    err = d3d->CreateViewport(&d3dViewport, NULL);

    if (err != DD_OK)
        return FALSE;

    err = d3dDevice->AddViewport(d3dViewport);

    /*
     * Setup the viewport for a reasonable viewing area
     */
    D3DVIEWPORT2 viewData;
    memset(&viewData, 0, sizeof(D3DVIEWPORT2));
    viewData.dwSize = sizeof(D3DVIEWPORT2);
    viewData.dwX = 0;
    viewData.dwY = 0;
    viewData.dwWidth  = ScreenSize.cx;
    viewData.dwHeight = ScreenSize.cy;
    viewData.dvClipX = -1.0f;
    viewData.dvClipWidth = 2.0f;
    viewData.dvClipHeight = (D3DVALUE)(ScreenSize.cy * 2.0 / ScreenSize.cx);
    viewData.dvClipY = viewData.dvClipHeight / 2.0f;
    viewData.dvMinZ = 0.0f;
    viewData.dvMaxZ = 1.0f;
    err = d3dViewport->SetViewport2(&viewData);

    if (err != DD_OK)
return FALSE;

    err = d3dDevice->SetCurrentViewport(d3dViewport);

    if (err != DD_OK)
        return FALSE;

    if (!Init3DState())
return FALSE;

    return TRUE;
}

/**************************************************************************
  DDSetMode
 **************************************************************************/

BOOL DDSetMode(int width, int height, int bpp)
{
    HRESULT err;

    err = dd->SetDisplayMode(width, height, bpp);

    if (err != DD_OK)
        return FALSE;

    ScreenSize.cx = width;
    ScreenSize.cy = height;

    // get rid of any previous surfaces.
    DDTerm(FALSE);

    //
    // Create surfaces
    //
    // what we want is a double buffered surface in video memory
    // so we try to create this first.
    //
    // if we cant get a double buffered surface, we try for a double
    // buffered surface not being specific about video memory, we will
    // get back a main-memory surface, that wont use HW page flipping
    // but at least we run.
    //
    // NOTE you need to recreate the surfaces for a new display mode
    // they wont work when/if the mode is changed.
    //
    DDSURFACEDESC ddsd;

    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX |
                          DDSCAPS_3DDEVICE |
                          DDSCAPS_VIDEOMEMORY;
#ifdef TRIPLE_BUFFER
    ddsd.dwBackBufferCount = 2;
#else
    ddsd.dwBackBufferCount = 1;
#endif

    // try to get a triple/double buffered video memory surface.
    err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);

    // try to get a double buffered video memory surface.
    if (err != DD_OK && ddsd.dwBackBufferCount == 2)
    {
       ddsd.dwBackBufferCount = 1;
       err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
    }

    if (err != DD_OK)
    {
        // settle for a main memory surface.
        ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY;
        err = dd->CreateSurface(&ddsd, &FrontBuffer, NULL);
    }

    if (err != DD_OK)
        return FALSE;

    // get a pointer to the back buffer
    DDSCAPS caps;
    caps.dwCaps = DDSCAPS_BACKBUFFER;
    err = FrontBuffer->GetAttachedSurface(&caps, &BackBuffer);

    if (err != DD_OK)
        return FALSE;

    // create a palette if we are in a paletized display mode.
    //
    // NOTE because we want to be able to show dialog boxs and
    // use our menu, we leave the windows reserved colors as is
    // so things dont look ugly.
    //
    // palette is setup like so:
    //
    //      10      windows system colors
    //      64      red wash
    //      64      grn wash
    //      64      blu wash
    //
    if (bpp == 8)
    {
        PALETTEENTRY ape[256];

        // get the current windows colors.
        GetPaletteEntries((HPALETTE)GetStockObject(DEFAULT_PALETTE), 0,  10, &ape[0]);
        GetPaletteEntries((HPALETTE)GetStockObject(DEFAULT_PALETTE), 10, 10, &ape[246]);

        // make a red, grn, and blu wash for our cube.
        for (int i=0; i<64; i++)
        {
            ape[10+64*0+i].peRed   = i * 255/63;
            ape[10+64*0+i].peGreen = 0;
            ape[10+64*0+i].peBlue  = 0;

            ape[10+64*1+i].peRed   = 0;
            ape[10+64*1+i].peGreen = i * 255/63;
            ape[10+64*1+i].peBlue  = 0;

            ape[10+64*2+i].peRed   = 0;
            ape[10+64*2+i].peGreen = 0;
            ape[10+64*2+i].peBlue  = i * 255/63;
        }

        // create the palette.
        err = dd->CreatePalette(DDPCAPS_8BIT, ape, &Palette, NULL);

        if (err == DD_OK)
        {
            FrontBuffer->SetPalette(Palette);
            BackBuffer->SetPalette(Palette);
        }
    }

    if (!Init3D())
        return FALSE;

    if (AppFont)
        DeleteObject(AppFont);

    AppFont = CreateFont(width < 640 ? 24 : 48,
        0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        NONANTIALIASED_QUALITY,
        VARIABLE_PITCH,
        "Comic Sans MS");

    return TRUE;
}

/**************************************************************************
  BuildModeMenu
 **************************************************************************/
#define MAKEMENUITEMDATA(width, height, bpp) \
        (width) | ((height) << 12) | ((bpp) << 24)

HRESULT CALLBACK BuildModeMenuCallback(LPDDSURFACEDESC pdds, LPVOID lParam)
{
    HMENU hmenu = (HMENU)lParam;
    char ach[80];
    int n;
    int width  = pdds->dwWidth;
    int height = pdds->dwHeight;
    int bpp    = pdds->ddpfPixelFormat.dwRGBBitCount;

    n = GetMenuItemCount(hmenu);
    wsprintf(ach,"%dx%dx%d",width,height,bpp);
    AppendMenu(hmenu,MF_STRING,MENU_MODE+n,ach);

    MENUITEMINFO mii;

    // pack the mode info into a DWORD and set the extra item data.
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_DATA;
    mii.dwItemData = MAKEMENUITEMDATA(width, height, bpp);
    SetMenuItemInfo(hmenu, MENU_MODE+n, MF_BYCOMMAND, &mii);

    //return S_TRUE to stop enuming modes, S_FALSE to continue
    return S_FALSE;
}

void BuildModeMenu()
{
    // Enumerate all posible display modes, and stick them in our menu.
    // we use the extra item DWORD of a menu item to store the mode info

    DeleteMenu(GetMenu(hwndApp), 2, MF_BYPOSITION);

    HMENU hmenu = CreatePopupMenu();
    dd->EnumDisplayModes(0,NULL,(LPVOID)hmenu,BuildModeMenuCallback);
    AppendMenu(GetMenu(hwndApp),MF_POPUP,(UINT)hmenu,"&Modes");
}

/**************************************************************************
 * BuildDeviceMenu
 **************************************************************************/

BOOL CALLBACK BuildDeviceMenuCallback(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam)
{
    HMENU hmenu = (HMENU)lParam;
    char ach[80];
    int n;

    n = GetMenuItemCount(hmenu);
    wsprintf(ach,"%s (%s)",szName, szDevice);
    AppendMenu(hmenu,MF_STRING,MENU_DEVICE+n,ach);
    return DDENUMRET_OK;
}

void BuildDeviceMenu()
{
    HMENU hmenu = CreatePopupMenu();
    DirectDrawEnumerate(BuildDeviceMenuCallback, (LPVOID)hmenu);
    AppendMenu(GetMenu(hwndApp),MF_POPUP,(UINT)hmenu,"&Device");
}

/**************************************************************************
  AppInit

  Description:
    This is called when the application is first loaded. It initializes
  all variables, registers the window class, and creates the main app
  window.
 **************************************************************************/

BOOL AppInit(HINSTANCE hInst,HINSTANCE hPrev,int sw,LPSTR szCmdLine)
{
  WNDCLASS cls;

  /* Save instance handle for DialogBoxes */
  hInstApp = hInst;

  if (!hPrev)
  {
    //***  Register a class for the main application window
    cls.hCursor        = LoadCursor(0,IDC_ARROW);

    //*** Just for fun, we'll draw our own spinning cube icon.
    cls.hIcon          = LoadIcon(hInst, "AppIcon");
    cls.lpszMenuName   = "AppMenu";
    cls.lpszClassName  = szAppName;
    cls.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
    cls.hInstance      = hInst;
    cls.style          = CS_VREDRAW | CS_HREDRAW;
    cls.lpfnWndProc    = (WNDPROC)AppWndProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = 0;

    if (!RegisterClass(&cls))
      return FALSE;
  }

  hAccelApp = LoadAccelerators(hInst, "AppAccel");

  //*** Set and normalize the light source
  LightSourceDirection = vector_4(50, 30, -15);
  LightSourceDirection.Normalize();

  //*** Distance to view plane:
  ViewPerspective.SetElement(3, 2, 1/300.0);
  ViewPerspective.SetElement(3, 3, 0);

  //*** Viewport scaling - some arbitrary number like 3.5 will do
  ViewPerspective.SetElement(0, 0, 3.5);
  ViewPerspective.SetElement(1, 1, 3.5);

  //*** Calculate the initial normals and shades
  TransformCube(CubeTransform);

  //*** Then generate an interesting rotation for the spin
  CubeTransform.ConcatenateYRotation(6.0);
  CubeTransform.ConcatenateXRotation(3.5);
  CubeTransform.ConcatenateZRotation(2.0);

  hwndApp = CreateWindowEx(
                  WS_EX_APPWINDOW,
                  szAppName,           // Class name
                  szAppName,           // Caption
                  WS_POPUP |
                  WS_SYSMENU |
                  WS_CAPTION,
  0, 0,        // Position
  640,480,       // Size
                  0,                   // Parent window (no parent)
                  0,                   // use class menu
                  hInst,               // handle to window instance
                  0                    // no params to pass on
                  );
  ShowWindow(hwndApp,sw);
  UpdateWindow(hwndApp);

  BuildDeviceMenu();

  //
  // read the device name from WIN.INI so we come up on the last device
  // that the user picked.
  //
  GetProfileString(szAppName, "Device", "", DeviceName, sizeof(DeviceName));

  if (!DDInit(DeviceName))
      return FALSE;

  return TRUE;
}

/**************************************************************************
  WinMain

  Description:
    The main procedure for the App.  After initializing, it just goes
  into a message-processing loop until it gets a WM_QUIT message.
 **************************************************************************/

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
  MSG     msg;

  //*** Call initialization procedure
  if (!AppInit(hInst,hPrev,sw,szCmdLine))
    return FALSE;

  //*** Polling messages from event queue until quit
  for (;;)
  {
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
      if (msg.message == WM_QUIT)
        break;

      if (!hwndApp || !TranslateAccelerator(hwndApp, hAccelApp, &msg))
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    else
    {
      if (AppIdle())
        WaitMessage();
    }
  }

  DDTerm();
  return msg.wParam;
}

/**************************************************************************
  AppPause

 **************************************************************************/

void AppPause(BOOL f)
{
    if (f)
    {
        DDSCAPS caps;
        FrontBuffer->GetCaps(&caps);

        // if we are in ModeX go back to a windows mode
        // so we can see the menu or dialog box.

        if (caps.dwCaps & DDSCAPS_MODEX)
        {
            DDSetMode(640,480,8);
        }

        fAppPaused = TRUE;
        dd->FlipToGDISurface();
        DrawMenuBar(hwndApp);
        RedrawWindow(hwndApp, NULL, NULL, RDW_FRAME);
    }
    else
    {
        fAppPaused = FALSE;
    }
}

/**************************************************************************
  MagnifyCube

  Description:
    Magnify the cube the indicated number of times.  A negative number
    makes it smaller.
 **************************************************************************/

void MagnifyCube(double times)
{
    matrix_4x4 m;
    double factor = pow(1.5, times);
    m.SetElement(0,0,factor);
    m.SetElement(1,1,factor);
    m.SetElement(2,2,factor);
    TransformCube(m);
}

/**************************************************************************
  AppIdle

  return TRUE if the app is idle
  return FALSE if the app is not idle.

  Description:
 **************************************************************************/

BOOL AppIdle()
{
  //*** Spin while the app is active, lbutton is up, and spinning is on.

  //*** Spin while the app is iconized.
  if (fAppActive && !fAppPaused)
  {
    //*** If the app is active, spin the cube and redraw
    if (GetAsyncKeyState(VK_LBUTTON) < 0)
    {
        if(Move.x || Move.y)
        {
            matrix_4x4 Movement;
            Movement.ConcatenateYRotation(Move.x);
            Movement.ConcatenateXRotation(Move.y);
            Move.x = Move.y = 0;
            TransformCube(Movement);
        }
    }
    else
    {
        TransformCube(CubeTransform);
    }
    RenderFrame();
    return FALSE;
  }
  else
  {
    //*** Don't do anything when not the active app
    return TRUE;
  }
}

/**************************************************************************
  RenderFrame

  render the frame into the back buffer and do a page flip.

  things to NOTE:

    we use the blter to clear the backbuffer, this usualy is a big
    win blters are real fast.

    we use GDI to draw the frame rate, and info text

 **************************************************************************/

int FrameRate;
int FrameCount;
int FrameCount0;
DWORD FrameTime;
DWORD FrameTime0;

void RenderFrame()
{
  HDC hdc;

  //*** always need to handle DDERR_SURFACELOST, this will happen
  //*** when we get switched away from.

  if (FrontBuffer->IsLost() == DDERR_SURFACELOST)
  {
     FrontBuffer->Restore();
     Texture.Restore();
  }

  //*** use the blter to do a color fill to clear the back buffer

  DDBLTFX ddbltfx;
  ddbltfx.dwSize = sizeof(ddbltfx);
  ddbltfx.dwFillColor = 0;
  BackBuffer->Blt(NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&ddbltfx);

  ProjectAndDrawCube(d3dDevice, ScreenSize.cx/2, ScreenSize.cy/2);

  if (BackBuffer->GetDC(&hdc) == DD_OK)
  {
    //*** draw stats, like frame number and frame rate

    char ach[128];
    int len;
    static char szHelp[] = "F10=Menu F7=Smaller F8=Larger";

    SetBkMode(hdc, TRANSPARENT);
    SelectObject(hdc, AppFont);

    len = wsprintf(ach, "FPS %02d Frame %05d (%s)", FrameRate, FrameCount, d3dName);

    SetTextColor(hdc, RGB(255, 255, 0));
    TextOut(hdc, 0, 0, ach, len);
    TextOut(hdc, 0, ScreenSize.cy-(ScreenSize.cx<640 ? 24:48),szHelp,sizeof(szHelp)-1);

    BackBuffer->ReleaseDC(hdc);
  }

  //*** we have rendered the backbuffer, call flip so we can see it
  FrontBuffer->Flip(NULL, DDFLIP_WAIT);

  FrameCount++;
  FrameTime = timeGetTime();

  if (FrameTime - FrameTime0 > 1000)
  {
    FrameRate = (FrameCount - FrameCount0) * 1000 / (FrameTime - FrameTime0);
    FrameTime0 = FrameTime;
    FrameCount0 = FrameCount;
  }
}

/**************************************************************************
  AppInitMenuPopup

    If it's the Cube menu, then check or uncheck the GDI item accordingly.

    If it's the mode list popup, then add a MENUBARBREAK as necessary.

 **************************************************************************/

void AppInitMenuPopup(HWND hwnd, HMENU hmenu, UINT uPos, BOOL fSys)
{
    if (fSys) return;       /* Don't mess with the sysmenu */

    switch (uPos) {
    case 0:                 /* Cube menu */
        CheckMenuItem(hmenu, MENU_CULL_NONE, CullMode == D3DCULL_NONE ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_CULL_CW,   CullMode == D3DCULL_CW   ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_CULL_CCW,  CullMode == D3DCULL_CCW  ? MF_CHECKED : MF_UNCHECKED);

        CheckMenuItem(hmenu, MENU_TEXTURE,    TextureHandle ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_DITHER,     TextureDither ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_PERSPECTIVE,TexturePerspective ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_MODULATE,   TextureBlend == D3DTBLEND_MODULATE ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_COPY,       TextureBlend == D3DTBLEND_COPY ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hmenu, MENU_DECAL,      TextureBlend == D3DTBLEND_DECAL ? MF_CHECKED : MF_UNCHECKED);
        break;

    case 1:                 /* Device menu */
    case 2:                 /* Mode menu */
        /*
         *  Compute how many menu items fit on the screen.
         *
         *  Note that we use ScreenSize instead of SM_CYSCREEN.
         *  This allows us to do the right thing in the face of
         *  multiple monitors.  (ScreenSize is the size of our
         *  monitor.)
         */
        int cmi;

        cmi = GetMenuItemCount(hmenu);
        if (cmi) {

            RECT rcClient;
            GetClientRect(hwnd, &rcClient);

            RECT rc;
            GetMenuItemRect(hwnd, hmenu, 0, &rc);

            int dyMenuItem = rc.bottom - rc.top;

            /*
             *  Aargh.  If the menu has never appeared yet, USER
             *  returns an empty rectangle.  DON'T DIVIDE BY ZERO!
             *
             *  In such case, we use the height of the menu bar.
             *  Not perfect, but close enough.  And it happens only
             *  once.
             */
            if (dyMenuItem == 0) {
                dyMenuItem = GetSystemMetrics(SM_CYMENU);
            }

            /*
             *  Aargh.  You can't change MF_MENUBARBREAK without
             *  also changing the text, so we have to get the
             *  old text and set it back.
             *
             *  While we're here, we may as well put a check-box
             *  next to the item that matches our current screen res.
             */

            DDSURFACEDESC ddsd;
            ZeroMemory(&ddsd, sizeof(ddsd));
            ddsd.dwSize = sizeof(ddsd);
            FrontBuffer->GetSurfaceDesc(&ddsd);
            int width  = ddsd.dwWidth;
            int height = ddsd.dwHeight;
            int bpp    = ddsd.ddpfPixelFormat.dwRGBBitCount;
            DWORD dwItemDataMatch = MAKEMENUITEMDATA(width, height, bpp);

            MENUITEMINFO mii;
            TCHAR tsz[80];

            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_STATE;
            mii.dwTypeData = tsz;

            /*
             *  Compute the number of MI's that fit in our client area.
             *  Note: Client area, not screen size.  This ensures that
             *  the menu pops in a reasonable location.
             */

            int cmiScreen = rcClient.bottom / dyMenuItem;

            for (int imi = 0; imi < cmi; imi++) {
                mii.cch = sizeof(tsz) / sizeof(tsz[0]);
                if (GetMenuItemInfo(hmenu, imi, MF_BYPOSITION, &mii)) {
                    if (imi > 0 && imi % cmiScreen == 0) {
                        mii.fType |= MFT_MENUBARBREAK;
                    } else {
                        mii.fType &= ~MFT_MENUBARBREAK;
                    }

                    if (mii.dwItemData == dwItemDataMatch) {
                        mii.fState |= MFS_CHECKED;
                    } else {
                        mii.fState &= ~MFS_CHECKED;
                    }

                    SetMenuItemInfo(hmenu, imi, MF_BYPOSITION, &mii);
                }
            }

        }
        break;
    }

}

/**************************************************************************
  AppWndProc

  Description:
    Main window proc. Standard Windows fare.
 **************************************************************************/

LONG CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  switch (msg)
  {
    case WM_CREATE:
      break;

    case WM_ACTIVATEAPP:
      //*** Keep track of whether or not the app is in the foreground
      fAppActive = (BOOL)wParam;
      break;

    case WM_SETCURSOR:
      if (fAppActive && !fAppPaused && BackBuffer)
      {
        SetCursor(NULL);
        return 1;
      }
      break;

    case WM_ENTERMENULOOP:
      AppPause(TRUE);
      break;

    case WM_EXITMENULOOP:
      AppPause(FALSE);
      break;

    case WM_INITMENUPOPUP:
        AppInitMenuPopup(hwnd, (HMENU)wParam,
                         (UINT)LOWORD(lParam), (BOOL)HIWORD(lParam));
        break;

    case WM_COMMAND:
      switch(LOWORD(wParam))
      {
        case MENU_ABOUT:
          AppPause(TRUE);
          DialogBox(hInstApp, "AppAbout", hwnd, (DLGPROC)AppAbout);
          AppPause(FALSE);
          break;

        case MENU_EXIT:
          PostMessage(hwnd, WM_CLOSE, 0, 0L);
          break;

        case MENU_LARGER:
          MagnifyCube(+1.0);
          break;

        case MENU_SMALLER:
          MagnifyCube(-1.0);
          break;

        case MENU_TEXTURE:
          TextureHandle = TextureHandle ? 0 : Texture.GetHandle();
          break;

        case MENU_PERSPECTIVE:  TexturePerspective = !TexturePerspective; break;
        case MENU_DITHER:       TextureDither      = !TextureDither; break;

        case MENU_MODULATE:     TextureBlend = D3DTBLEND_MODULATE; break;
        case MENU_COPY:         TextureBlend = D3DTBLEND_COPY;     break;
        case MENU_DECAL:        TextureBlend = D3DTBLEND_DECAL;    break;

        case MENU_CULL_NONE:    CullMode = D3DCULL_NONE; break;
        case MENU_CULL_CW:      CullMode = D3DCULL_CW;   break;
        case MENU_CULL_CCW:     CullMode = D3DCULL_CCW;  break;
      }
      if (LOWORD(wParam) >= MENU_DEVICE && LOWORD(wParam) < MENU_DEVICE+100)
      {
        GetMenuString(GetMenu(hwnd), LOWORD(wParam), DeviceName, sizeof(DeviceName), MF_BYCOMMAND);
        WriteProfileString(szAppName, "Device", DeviceName);
        DDInit(DeviceName);
      }
      if (LOWORD(wParam) >= MENU_MODE && LOWORD(wParam) < MENU_MODE+100)
      {
        MENUITEMINFO mii;

        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_DATA;
        GetMenuItemInfo(GetMenu(hwnd), LOWORD(wParam), MF_BYCOMMAND, &mii);

        DDSetMode(
            (mii.dwItemData >> 0)  & 0xFFF,
            (mii.dwItemData >> 12) & 0xFFF,
            (mii.dwItemData >> 24) & 0x0FF);
      }
      return 0L;

    case WM_DESTROY:
      hwndApp = NULL;
      PostQuitMessage(0);
      break;

    case WM_PAINT:
      break;

#if 0   // fullscreen apps dont need to do this!
        // and using SM_CYSCREEN is incorrect for other devices.
    case WM_MOVE:
    case WM_SIZE:
    case WM_DISPLAYCHANGE:
      if (fAppActive && !IsIconic(hwnd))
      {
RECT Rect;
SetRect(&Rect, 0, GetSystemMetrics(SM_CYCAPTION), GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
        AdjustWindowRectEx(&Rect, WS_POPUP | WS_CAPTION, FALSE, 0);
        SetWindowPos(hwnd, NULL, Rect.left, Rect.top, Rect.right-Rect.left, Rect.bottom-Rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
      }
      break;
#endif

    case WM_LBUTTONDOWN:
      //*** Get the start location for mouse rotations
      Last.x = LOWORD(lParam);
      Last.y = HIWORD(lParam);
      break;

    case WM_MOUSEMOVE:
      //*** While the mouse button is down, keep track of movement
      //*** to update the eye position
      if(GetKeyState(VK_LBUTTON) < 0)
      {
        Move.x = (int)LOWORD(lParam) - Last.x;
        Move.y = (int)HIWORD(lParam) - Last.y;
        Last.x = LOWORD(lParam);
        Last.y = HIWORD(lParam);
      }
      break;
  }

  return DefWindowProc(hwnd,msg,wParam,lParam);
}

/**************************************************************************
  TransformCube

  Description:
    Transforms the cube vertices by the current rotation matrix.
    Recalculates normals and flat shade values for the
  directional light source.
 **************************************************************************/

void TransformCube(matrix_4x4 const &Transform)
{
  int i;

  //*** Transform the cube by the matrix
  for (i = 0; i < 8; ++i)
    CubeVertices[i] = Transform * CubeVertices[i];

  //*** Recalculate normals and shades
  for (i = 0; i < 6; ++i)
  {
    //*** Normals are perpendicular to two edges of the cube
    vector_4 Edge1, Edge2;
    Edge1 = CubeVertices[CubeFaces[i][1]] - CubeVertices[CubeFaces[i][0]];
    Edge2 = CubeVertices[CubeFaces[i][3]] - CubeVertices[CubeFaces[i][0]];
    CubeSurfaceNormals[i] = CrossProduct(Edge1, Edge2);
    CubeSurfaceNormals[i].Normalize();

    //*** Cosine shading based on the surface normal, clamped to [0, 1]
    real Shade = DotProduct(CubeSurfaceNormals[i], LightSourceDirection);

    if (CullMode != D3DCULL_CCW)
    {
        if (Shade < 0) Shade = -Shade;
    }

    Shade = Shade + AmbientLight;
    if (Shade < 0) Shade = 0;
    else if (Shade > 1.0) Shade = 1.0;
    CubeSurfaceShades[i] = Shade;
  }
}

/**************************************************************************
  ProjectAndDrawCube

  Description:
    Projects the cube vertices for the current viewpoint then culls
  in screen space and draws into the DC via GDI.
 **************************************************************************/

BOOL ProjectAndDrawCube(IDirect3DDevice2 *dev, int XOffset, int YOffset)
{
  HRESULT err;

  //*** Create a viewing transform for the current eye position
  vector_4 ViewDirection = Origin - Viewpoint;
  ViewDirection.Normalize();
  view_transform View(Viewpoint, ViewDirection, Up);

  //*** Transform and project the vertices into screen space
  int i;
  float aScreenVerticesX[8];
  float aScreenVerticesY[8];
  float aScreenVerticesZ[8];
  float aScreenVerticesW[8];

  for (i = 0; i < 8; ++i)
  {
    point_4 Temp = View * CubeVertices[i];
    Temp = ViewPerspective * Temp;
    Temp.Homogenize();

    aScreenVerticesX[i] = (float)Temp.GetX() + XOffset;
    aScreenVerticesY[i] = (float)Temp.GetY() + YOffset;
    aScreenVerticesZ[i] = (float)Temp.GetZ();
    aScreenVerticesW[i] = (float)Temp.GetW();

  }

  err = dev->BeginScene();

  //
  // NOTE NOTE NOTE NOTE NOTE
  // dont be as stupid as this sample, dont change the render
  // state every frame unless you need to.
  //
  dev->SetLightState(D3DLIGHTSTATE_MATERIAL,hMat);
  dev->SetRenderState(D3DRENDERSTATE_CULLMODE,           CullMode);
  dev->SetRenderState(D3DRENDERSTATE_DITHERENABLE,       TextureDither);
  dev->SetRenderState(D3DRENDERSTATE_TEXTUREPERSPECTIVE, TexturePerspective);
  dev->SetRenderState(D3DRENDERSTATE_TEXTUREMAPBLEND,    TextureBlend);
  dev->SetRenderState(D3DRENDERSTATE_TEXTUREHANDLE,      TextureHandle);

  for (i = 0; i < 6; ++i)
  {
    //*** Get the shading colors

    int Red, Green, Blue;

    Red   = (int)(CubeColors[i][0] * CubeSurfaceShades[i]);
    Green = (int)(CubeColors[i][1] * CubeSurfaceShades[i]);
    Blue  = (int)(CubeColors[i][2] * CubeSurfaceShades[i]);

    //*** Collect the correct points in an array
    D3DTLVERTEX TLVertices[6];

    for (int j = 0; j < 4; ++j)
    {
        TLVertices[j].sx = aScreenVerticesX[CubeFaces[i][j]];
        TLVertices[j].sy = aScreenVerticesY[CubeFaces[i][j]];
        TLVertices[j].sz = aScreenVerticesZ[CubeFaces[i][j]];
        TLVertices[j].rhw = (float)(1.0 / aScreenVerticesW[CubeFaces[i][j]]);
        TLVertices[j].color = RGB_MAKE(Red, Green, Blue);
        TLVertices[j].specular = RGB_MAKE(0,0,0);
    }

    TLVertices[0].tu = 0.0f; TLVertices[0].tv = 0.0f;
    TLVertices[1].tu = 1.0f; TLVertices[1].tv = 0.0f;
    TLVertices[2].tu = 1.0f; TLVertices[2].tv = 1.0f;
    TLVertices[3].tu = 0.0f; TLVertices[3].tv = 1.0f;

    //*** Use DrawPrim to draw the face
    err = dev->DrawPrimitive(D3DPT_TRIANGLEFAN, D3DVT_TLVERTEX, TLVertices, 4, D3DDP_WAIT);
  }

  err = dev->EndScene();

  return TRUE;
}


FLIPCUBE.H

/**************************************************************************

    FLIPCUBE.H - A spinning cube demo for WinG

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

/* Menu Items */
#define MENU_ABOUT        1
#define MENU_EXIT         2
#define MENU_SPIN         3
#define MENU_LARGER       4
#define MENU_SMALLER      5
#define MENU_TEXTURE      7
#define MENU_PERSPECTIVE  9
#define MENU_DITHER       10

#define MENU_MODULATE     20
#define MENU_COPY         21
#define MENU_DECAL        22

#define MENU_CULL_NONE    30
#define MENU_CULL_CW      31
#define MENU_CULL_CCW     32

#define MENU_MODE         100
#define MENU_DEVICE       200


README.TXT

Fly
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Using animations to set the position of a spaceship along a curved
path.


FLY.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: fly.c
 *
 ***************************************************************************/

/*
 * landscape fly by
 */

#include <math.h>
#include <stdlib.h>
#include "rmdemo.h"

typedef struct PATHINFO {
    D3DVALUE t;
    LPDIRECT3DRMFRAME chase;
    LPDIRECT3DRMFRAME plane_frame;
    LPDIRECT3DRMANIMATION flight_path;
} pathInfo;

#define NUM_SMOKE 7
int smoke_num = 0;
int done_all = 0;

LPDIRECT3DRMFRAME smoke[NUM_SMOKE];

static void CDECL cleanupObjects(LPDIRECT3DRMOBJECT obj, void* arg)
{
    pathInfo *info = (pathInfo*) arg;
    int i;

    for (i = 0; i < NUM_SMOKE; i++)
smoke[i]->lpVtbl->Release(smoke[i]);
    info->chase->lpVtbl->Release(info->chase);
    info->plane_frame->lpVtbl->Release(info->plane_frame);
    info->flight_path->lpVtbl->Release(info->flight_path);
}

static void CDECL moveCamera(LPDIRECT3DRMFRAME camera, void *arg, D3DVALUE delta)
{
    D3DVECTOR dir, up;
    D3DVECTOR dirCam, upCam;
    struct PATHINFO *info;
    LPDIRECT3DRMFRAME scene;
    D3DVALUE a_bit;

    info = (struct PATHINFO *) arg;
    camera->lpVtbl->GetScene(camera, &scene);
    info->t += D3DVAL(0.04);
    info->flight_path->lpVtbl->SetFrame(info->flight_path, camera);
    info->flight_path->lpVtbl->SetTime(info->flight_path, info->t);

    info->flight_path->lpVtbl->SetFrame(info->flight_path, info->plane_frame);
    info->flight_path->lpVtbl->SetTime(info->flight_path, info->t + D3DVAL(0.5));

    info->flight_path->lpVtbl->SetFrame(info->flight_path, info->chase);
    info->flight_path->lpVtbl->SetTime(info->flight_path, info->t + D3DVAL(1.0));

    camera->lpVtbl->LookAt(camera, info->plane_frame, scene, D3DRMCONSTRAIN_Z);
    info->plane_frame->lpVtbl->LookAt(info->plane_frame, info->chase,scene,
                                      D3DRMCONSTRAIN_Y);
    camera->lpVtbl->GetOrientation(camera, scene, &dirCam, &upCam);
    info->plane_frame->lpVtbl->GetOrientation(info->plane_frame, scene,
                                              &dir, &up);
    up.x = dir.x - dirCam.x;
    up.y = dir.y - dirCam.y + D3DVAL(1.0);
    up.z = dir.z - dirCam.z;

    info->plane_frame->lpVtbl->SetOrientation(info->plane_frame, scene,
      dir.x, dir.y, dir.z,
      up.x, up.y, up.z);

    if (done_all < NUM_SMOKE) {
scene->lpVtbl->AddVisual(scene, (LPDIRECT3DRMVISUAL) smoke[smoke_num]);
done_all++;
    } else {
if (smoke_num == NUM_SMOKE) {
    smoke_num = 0;
}
    }
    a_bit = D3DDivide(D3DDivide(D3DVAL(smoke_num), D3DVAL(NUM_SMOKE)),
                     D3DVAL(10.0));
    info->flight_path->lpVtbl->SetFrame(info->flight_path, smoke[smoke_num]);
    info->flight_path->lpVtbl->SetTime(info->flight_path,
                                       info->t + D3DVAL(0.4) - a_bit);
    smoke[smoke_num]->lpVtbl->SetOrientation(smoke[smoke_num], scene,
     dir.x, dir.y, dir.z,
     up.x, up.y, up.z);
    smoke_num++;
    scene->lpVtbl->Release(scene);
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMFRAME lights = NULL;
    D3DRMBOX box;
    LPDIRECT3DRMMESHBUILDER plane_builder = NULL;
    LPDIRECT3DRMMESHBUILDER mesh_builder = NULL;
    LPDIRECT3DRMMESHBUILDER smoke_builder = NULL;
    LPDIRECT3DRMMESH plane = NULL;
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMMESH smokemesh = NULL;
    LPDIRECT3DRMLIGHT ambient = NULL;
    LPDIRECT3DRMLIGHT parallel = NULL;
    D3DCOLOR smokec;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMFRAME sl = NULL;
    LPDIRECT3DRMFRAME sr = NULL;
    HRESULT rval;
    int i;
    int numPts = 11;
    D3DVECTOR path[] = {
-D3DVAL(8.0), D3DVAL(3.0), -D3DVAL(12.0),
-D3DVAL(4.0), D3DVAL(2.0), -D3DVAL(8.0),
-D3DVAL(2.0), D3DVAL(0.0), -D3DVAL(4.0),
D3DVAL(9.0), -D3DVAL(1.0), D3DVAL(7.0),
D3DVAL(4.0), D3DVAL(6.0), D3DVAL(10.0),
-D3DVAL(4.0), D3DVAL(5.0), D3DVAL(9.0),
D3DVAL(5.5), D3DVAL(3.5), -D3DVAL(6.5),
D3DVAL(2.0), D3DVAL(5.0), -D3DVAL(10.0),
D3DVAL(0.0), D3DVAL(4.0), -D3DVAL(15.0),
-D3DVAL(5.0), D3DVAL(4.0), -D3DVAL(15.0),
-D3DVAL(8.0), D3DVAL(3.0), -D3DVAL(12.0)
    };
    D3DVALUE path_t[] = {
        D3DVAL(0), D3DVAL(1), D3DVAL(2), D3DVAL(3), D3DVAL(4), D3DVAL(5), D3DVAL(6), D3DVAL(7), D3DVAL(8), D3DVAL(9), D3DVAL(10)
    };
    static pathInfo info;

    if (FAILED(view->lpVtbl->SetField(view, D3DVAL(0.8))))
goto generic_error;
    if (FAILED(dev->lpVtbl->SetQuality(dev, D3DRMRENDER_GOURAUD)))
goto generic_error;
#ifdef FOG
    if (FAILED(dev->lpVtbl->SetDither(dev, TRUE)))
goto generic_error;
    if (FAILED(scene->lpVtbl->SetFogEnable(scene, TRUE)))
goto generic_error;
    if (FAILED(scene->lpVtbl->SetFogParams(scene, 1, 30, 1)))
goto generic_error;
#endif

    /*
     * This Demo flies a plane through a small landscape, followed by a
     * camera. The paths are spline curves.
     */

    /*
     * Initialise smoke trail
     */
    smokec = D3DRMCreateColorRGBA(D3DVAL(0.6), D3DVAL(0.6), D3DVAL(0.6),
D3DVAL(0.5));
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &smoke_builder)))
goto generic_error;
    rval = smoke_builder->lpVtbl->Load(smoke_builder, "sphere0.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere0.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(smoke_builder->lpVtbl->Scale(smoke_builder, D3DVAL(0.015), D3DVAL(0.015),
                                 D3DVAL(0.015))))
 goto generic_error;

    if (FAILED(smoke_builder->lpVtbl->CreateMesh(smoke_builder, &smokemesh)))
goto generic_error;
    for (i = 0; i < NUM_SMOKE; i++) {
if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &smoke[i])))
    goto generic_error;
if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, smoke[i], &sl)))
    goto generic_error;
if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, smoke[i], &sr)))
    goto generic_error;

if (FAILED(sl->lpVtbl->AddVisual(sl, (LPDIRECT3DRMVISUAL) smokemesh)))
    goto generic_error;
if (FAILED(sr->lpVtbl->AddVisual(sr, (LPDIRECT3DRMVISUAL) smokemesh)))
    goto generic_error;
if (FAILED(sr->lpVtbl->SetPosition(sr, smoke[i], D3DVAL(-0.1), D3DVAL(0.0),
                                   D3DVAL(0.0))))
   goto generic_error;
if (FAILED(smoke[i]->lpVtbl->SetMaterialMode(smoke[i], D3DRMMATERIAL_FROMFRAME)))
    goto generic_error;
if (FAILED(smoke[i]->lpVtbl->SetColor(smoke[i], smokec)))
    goto generic_error;
if (FAILED(sl->lpVtbl->SetMaterialMode(sl, D3DRMMATERIAL_FROMPARENT)))
    goto generic_error;
if (FAILED(sr->lpVtbl->SetMaterialMode(sr, D3DRMMATERIAL_FROMPARENT)))
    goto generic_error;
RELEASE(sl);
RELEASE(sr);
    }
    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetPosition(lights, scene, D3DVAL(5.0), D3DVAL(5.0),
    -D3DVAL(5.0))))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_PARALLELPOINT, D3DVAL(0.8),
D3DVAL(0.6), D3DVAL(0.7), &parallel)))
goto generic_error;

    if (FAILED(lights->lpVtbl->AddLight(lights, parallel)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &ambient)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, ambient)))
goto generic_error;

    /*
     * load mesh file
     */

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &mesh_builder)))
goto generic_error;
    if (FAILED(mesh_builder->lpVtbl->Load(mesh_builder, "land4.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL)))
goto generic_error;
    if (FAILED(mesh_builder->lpVtbl->Scale(mesh_builder, D3DVAL(10.0), D3DVAL(8.0),
                                D3DVAL(10.0))))
goto generic_error;
    if (FAILED(mesh_builder->lpVtbl->GetBox(mesh_builder, &box)))
goto generic_error;

    /*
     * Color the landscape's faces.
     */
    if (mesh_builder) {
LPDIRECT3DRMFACEARRAY faces;
LPDIRECT3DRMFACE this_face;
int face_count, vertex_count;
int j;
D3DVALUE range, height;
D3DVECTOR *coords;

if (FAILED(mesh_builder->lpVtbl->GetFaces(mesh_builder, &faces)))
    goto generic_error;
face_count = faces->lpVtbl->GetSize(faces);

range = box.max.y - box.min.y;

/*
 * color the faces according to the height
 */
for (i = 0; i < face_count; i++) {
    faces->lpVtbl->GetElement(faces, i, &this_face);
    vertex_count = this_face->lpVtbl->GetVertexCount(this_face);
    coords = (LPD3DVECTOR) malloc(vertex_count * sizeof(D3DVECTOR));
    this_face->lpVtbl->GetVertices(this_face, &vertex_count,
                                   coords, NULL);
    if (vertex_count) {
/*
 * find maximum height of the face
 */
height = coords[0].y;
for (j = 1; j < vertex_count; j++) {
    if (coords[j].y > height)
height = coords[j].y;
}
height = D3DDivide((height - box.min.y), range);

if (height < D3DVAL(0.03))/* water */
    this_face->lpVtbl->SetColorRGB(this_face,
     D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.5));
else if (height < D3DVAL(0.3))/* greenery */
    this_face->lpVtbl->SetColorRGB(this_face,
     D3DVAL(0.1), D3DVAL(0.8), D3DVAL(0.1));
else if (height < D3DVAL(0.5))/* rocks */
    this_face->lpVtbl->SetColorRGB(this_face, D3DVAL(0.6),
                                    D3DVAL(0.3),D3DVAL(0.3));
else if (height < D3DVAL(0.7))/* dirty snow */
    this_face->lpVtbl->SetColorRGB(this_face, D3DVAL(0.8),
                                    D3DVAL(0.65),
                                    D3DVAL(0.65));
else/* snow */
    this_face->lpVtbl->SetColorRGB(this_face, D3DVAL(1.0),
                                    D3DVAL(1.0),D3DVAL(1.0));
    }
    free(coords);
    RELEASE(this_face);
}
RELEASE(faces);
    }
    if (FAILED(mesh_builder->lpVtbl->CreateMesh(mesh_builder, &mesh)))
goto generic_error;
    if (FAILED(frame->lpVtbl->AddVisual(frame, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &plane_builder)))
goto generic_error;
    rval = plane_builder->lpVtbl->Load(plane_builder, "dropship.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load dropship.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(plane_builder->lpVtbl->Scale(plane_builder, D3DVAL(0.015),
                                 D3DVAL(0.008), D3DVAL(0.015))))
 goto generic_error;
    if (FAILED(plane_builder->lpVtbl->CreateMesh(plane_builder, &plane)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateAnimation(lpD3DRM, &info.flight_path)))
goto generic_error;
    info.flight_path->lpVtbl->SetOptions(info.flight_path, D3DRMANIMATION_CLOSED
     | D3DRMANIMATION_SPLINEPOSITION
 | D3DRMANIMATION_POSITION);
    for (i = 0; i < numPts; i++)
info.flight_path->lpVtbl->AddPositionKey(info.flight_path, path_t[i],
                                                 path[i].x,
                                         path[i].y, path[i].z);

    info.t = D3DVAL(0.0);
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &info.chase)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &info.plane_frame)))
goto generic_error;
    if (FAILED(info.plane_frame->lpVtbl->AddVisual(info.plane_frame,
                                        (LPDIRECT3DRMVISUAL) plane)))
goto generic_error;

    if (FAILED(camera->lpVtbl->AddMoveCallback(camera, moveCamera, (void *) &info)))
goto generic_error;
    if (FAILED(camera->lpVtbl->AddDestroyCallback(camera, cleanupObjects, &info)))
goto generic_error;

    RELEASE(lights);
    RELEASE(plane_builder);
    RELEASE(mesh_builder);
    RELEASE(smoke_builder);
    RELEASE(plane);
    RELEASE(mesh);
    RELEASE(smokemesh);
    RELEASE(ambient);
    RELEASE(parallel);
    RELEASE(frame);
    return TRUE;

generic_error:
    Msg("A failure has occurred while building the scene.\n");
ret_with_error:
    RELEASE(lights);
    RELEASE(plane_builder);
    RELEASE(mesh_builder);
    RELEASE(smoke_builder);
    RELEASE(plane);
    RELEASE(mesh);
    RELEASE(smokemesh);
    RELEASE(ambient);
    RELEASE(parallel);
    RELEASE(frame);
    RELEASE(sl);
    RELEASE(sr);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bNoTextures = TRUE;
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Fly Direct3DRM Example");
}


README.TXT

The Fox & The Bear Sample
-------------------------

You need to run FOXBEAR from the FOXBEAR directory for it to find all of
its files.

Commands for using Foxbear:

    F12      - quit
    NUMPAD 2 - crouch
    NUMPAD 3 - throw an apple
    NUMPAD 4 - move right (hold down to run faster)
    NUMPAD 5 - stop
    NUMPAD 6 - move left (hold down to run faster)
    NUMPAD 7 - jump

Options for running foxbear:

WIN.INI             Command line

use_emulation=0                   Use hardware acceleration
use_emulation=1     -e            Use emulation, not hardware
use_dsound=0        -S            Do not use Direct Sound
use_dsound=1                      Use Direct Sound
buffers=0                         Double or trible buffer vram size dependent
buffers=1           -1            Use no back buffer
buffers=2           -2            Double buffer
                    -d            Double buffer
buffers=4           -4            Use 3 back buffers
                    -s            Use stretch
sysmem=1                          Use system memory when creating surfaces
                    -x            Demo or stress mode (keeps running)

command line dash parameters may be followed by x-resolution y-resolution color-depth


BMP.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:bmp.c
 *  Content:Bitmap reader
 *
 ***************************************************************************/
#include "foxbear.h"

/*
 * gfxLoadBitmap
 */
GFX_HBM gfxLoadBitmap(LPSTR szFileName)
{
    HFASTFILE                  pfile;
    BITMAPFILEHEADER UNALIGNED *pbf;
    BITMAPINFOHEADER UNALIGNED *pbi;
    GFX_HBM                    hbm;
    BOOL                       trans = FALSE;

    pfile = FastFileOpen( szFileName );

    if( pfile == NULL )
    {
return NULL;
    }

    pbf = (BITMAPFILEHEADER *)FastFileLock(pfile, 0, 0);
    pbi = (BITMAPINFOHEADER *)(pbf+1);

    if (pbf->bfType != 0x4d42 ||
        pbi->biSize != sizeof(BITMAPINFOHEADER))
    {
Msg("Failed to load");
Msg(szFileName);
FastFileClose( pfile );
return NULL;
    }

    /*
     * TOTAL HACK for FoxBear, FoxBear does not use any masks, it draws
     * sprites with transparent colors, but the code still loads the masks
     * if a mask exists the sprite is transparent, else it is not, so
     * you cant get rid of the masks or nothing will be transparent!!
     *
     * if the code tries to load a mask, just return a non-zero value.
     */

    if( pbi->biBitCount == 1 )
    {
        Msg("some code is still using masks, stop that!");
FastFileClose( pfile );
        return NULL;
    }

    /*
     * ANOTHER TOTAL HACK for FoxBear, some of the bitmaps in FoxBear
     * are a solid color, detect these and dont waste VRAM on them.
     */
    if( !bTransDest && pbi->biBitCount == 8 )
    {
        int x,y;
        BYTE c;
        BYTE UNALIGNED *pb = (LPBYTE)pbi + pbi->biSize + 256 * sizeof(COLORREF);
        RGBQUAD UNALIGNED *prgb = (RGBQUAD *)((LPBYTE)pbi + pbi->biSize);
        COLORREF rgb;

        c = *pb;

        for(y=0; y<(int)pbi->biHeight; y++ )
        {
            for( x=0; x<(int)pbi->biWidth; x++ )
            {
                if (c != *pb++)
                    goto not_solid;
            }
            pb += ((pbi->biWidth + 3) & ~3) - pbi->biWidth;
        }

        rgb = RGB(prgb[c].rgbRed,prgb[c].rgbGreen,prgb[c].rgbBlue);
        hbm = gfxCreateSolidColorBitmap(rgb);

        FastFileClose( pfile );
        return hbm;
    }
not_solid:

    /*
     * figure out iff the bitmap has the transparent color in it.
     */
    if( pbi->biBitCount == 8 )
    {
        int x,y;
        BYTE UNALIGNED *pb = (LPBYTE)pbi + pbi->biSize + 256 * sizeof(COLORREF);
        DWORD UNALIGNED *prgb = (DWORD *)((LPBYTE)pbi + pbi->biSize);

        for(y=0; y<(int)pbi->biHeight && !trans; y++ )
        {
            for( x=0; x<(int)pbi->biWidth && !trans; x++ )
            {
                if (prgb[*pb++] == 0x00FFFFFF)
                    trans=TRUE;
            }
            pb += ((pbi->biWidth + 3) & ~3) - pbi->biWidth;
        }
    }

    hbm = gfxCreateVramBitmap(pbi, trans);

    if( hbm == NULL )
    {
FastFileClose( pfile );
        return GFX_FALSE;
    }

#if 0
    {
        DDSCAPS ddscaps;

        IDirectDrawSurface_GetCaps(((GFX_BITMAP *)hbm)->lpSurface, &ddscaps);

        if( !(ddscaps.dwCaps & DDSCAPS_VIDEOMEMORY) )
{
    Msg( "%s is in system memory", szFileName );
}
    }
#endif

    FastFileClose( pfile );

    return hbm;

} /* gfxLoadBitmap */


DDRAW.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:ddraw.c
 *  Content:Misc. Direct Draw access routines
 *
 ***************************************************************************/
#include "foxbear.h"

typedef struct {
    GUID *guid;
    GUID DriverGUID;
    CHAR DriverDesc[128];
    CHAR DriverName[128];
} Devices;

Devices aDDDevs[15];
int     MaxDevIndex = 0;
int     DevIndex = 0;

BOOL    bUseEmulation;
BOOL    bUseSysMem;
int     nBufferCount;
int     CmdLineBufferCount;
BOOLbTransDest;
BOOL    bColorFill;

HRESULT CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID Context);

HRESULT CALLBACK DDEnumCallback(GUID * lpGUID, LPSTR DRiverDesc, LPSTR DriverName, LPVOID lpContext);

/*
 * DDInit
 */
BOOL DDInit( void )
{
    DirectDrawEnumerate(&DDEnumCallback,NULL);
    DDEnumCallback((GUID *)DDCREATE_EMULATIONONLY, "Hardware Emulation Layer", "", NULL);
    return TRUE;
}
/*
 * DDEnable
 */
BOOL DDEnable( void )
{
    LPDIRECTDRAWlpdd;
    LPDIRECTDRAW2   lpdd2;
    DDCAPS              ddcaps;
    HRESULTddrval;
    BOOLuse_dest;
OSVERSIONINFO osv;

osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
//
//What are we running on ?
//
GetVersionEx(&osv);

    nBufferCount = GetProfileInt( "FoxBear", "buffers", CmdLineBufferCount);
    bUseEmulation = GetProfileInt( "FoxBear", "use_emulation", bUseEmulation);
    bUseSysMem = GetProfileInt( "FoxBear", "sysmem", 0);
    use_dest = GetProfileInt( "FoxBear", "use_dest", 0 );

    if ((lpDD == NULL) || (lpDD2 == NULL))
    {
        if( bUseEmulation)
            DevIndex = MaxDevIndex-1;
        ddrval = DirectDrawCreate(aDDDevs[DevIndex].guid,&lpdd,NULL);
        if(ddrval == DD_OK)
        {
            ddrval = IDirectDraw_QueryInterface(lpdd, &IID_IDirectDraw2, &lpdd2);
        }
    }
    else
    {
        lpdd = lpDD;
        lpdd2 = lpDD2;
        ddrval = DD_OK;
    }

    if( ddrval != DD_OK )
    {
        Msg("DirectDrawCreate failed err=%d", ddrval);
        goto error;
    }

    /*
     * grab exclusive mode if we are going to run as fullscreen
     * otherwise grab normal mode.
     */
    if (lpDD == NULL)
    {
NumModes = 0;

        if (bFullscreen)
        {
            ddrval = IDirectDraw_SetCooperativeLevel( lpdd, hWndMain,
                            DDSCL_ALLOWMODEX |
                            DDSCL_EXCLUSIVE |
    DDSCL_FULLSCREEN );

            ddrval = IDirectDraw_EnumDisplayModes(lpdd, DDEDM_STANDARDVGAMODES, NULL, 0, EnumDisplayModesCallback);
            if(ddrval != DD_OK)
            {
    ddrval = IDirectDraw_EnumDisplayModes(lpdd, 0, NULL, 0, EnumDisplayModesCallback);
            };
}

        else
        {
            ddrval = IDirectDraw_SetCooperativeLevel( lpdd, hWndMain,
    DDSCL_NORMAL );

    // in normal windowed mode, just add some "stock" window
    // sizes

    ModeList[NumModes].w = 320;
    ModeList[NumModes].h = 200;
    NumModes++;

    ModeList[NumModes].w = 320;
    ModeList[NumModes].h = 240;
    NumModes++;

    ModeList[NumModes].w = 512;
    ModeList[NumModes].h = 384;
    NumModes++;

    ModeList[NumModes].w = 640;
    ModeList[NumModes].h = 400;
    NumModes++;

    ModeList[NumModes].w = 640;
    ModeList[NumModes].h = 480;
    NumModes++;
        }

        if( ddrval != DD_OK )
        {
            Msg("SetCooperativeLevel failed err=%d", ddrval);
            goto error;
        }
    }

    if (bFullscreen)
    {
        Msg("SetDisplayMode %d %d %d",GameMode.cx,GameMode.cy, GameBPP);
        if((GameMode.cx == 320) && (GameMode.cy == 200) && (GameBPP == 8))
        {
            ddrval = IDirectDraw2_SetDisplayMode(lpdd2, GameMode.cx, GameMode.cy, GameBPP,0,DDSDM_STANDARDVGAMODE);
        } else {
            ddrval = IDirectDraw_SetDisplayMode( lpdd, GameMode.cx, GameMode.cy, GameBPP);
        }

        if (ddrval != DD_OK && (GameMode.cx != 640 || GameMode.cy != 480))
        {
            Msg( "cant set mode trying 640x480" );

            GameMode.cx = 640;
            GameMode.cy = 480;
    GameSize = GameMode;

            ddrval = IDirectDraw_SetDisplayMode( lpdd,
                GameMode.cx, GameMode.cy, GameBPP);
        }

        if (ddrval != DD_OK && GameBPP != 8)
        {
            Msg( "cant set mode trying 640x480x8" );

            GameBPP = 8;

            ddrval = IDirectDraw_SetDisplayMode( lpdd,
                GameMode.cx, GameMode.cy, GameBPP);
        }

        if (ddrval != DD_OK && GameBPP != 16)
        {
            Msg( "cant set mode trying 640x480x16" );

            GameBPP = 16;

            ddrval = IDirectDraw_SetDisplayMode( lpdd,
                GameMode.cx, GameMode.cy, GameBPP);
        }

        if( ddrval != DD_OK )
        {
            Msg("SetMode failed err=%d", ddrval);
            goto error;
        }
    }
    else
    {
        RECT rcWork;
        RECT rc;
        HDC hdc;
        DWORD dwStyle;

        //
        //  when in rome (I mean when in windows) we should use the
        //  current mode
        //
        hdc = GetDC(NULL);
        GameBPP = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL);
        ReleaseDC(NULL, hdc);

        //
        // if we are still a WS_POPUP window we should convert to a
        // normal app window so we look like a windows app.
        //
        dwStyle = GetWindowStyle(hWndMain);
        dwStyle &= ~WS_POPUP;
        dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX;
        SetWindowLong(hWndMain, GWL_STYLE, dwStyle);

        if (bStretch)
            SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2);
        else
            SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy);

        AdjustWindowRectEx(&rc,
            GetWindowStyle(hWndMain),
            GetMenu(hWndMain) != NULL,
            GetWindowExStyle(hWndMain));

        SetWindowPos(hWndMain, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
            SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

        SetWindowPos(hWndMain, HWND_NOTOPMOST, 0, 0, 0, 0,
            SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        //
        //  make sure our window does not hang outside of the work area
        //  this will make people who have the tray on the top or left
        //  happy.
        //
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
        GetWindowRect(hWndMain, &rc);
        if (rc.left < rcWork.left) rc.left = rcWork.left;
        if (rc.top  < rcWork.top)  rc.top  = rcWork.top;
        SetWindowPos(hWndMain, NULL, rc.left, rc.top, 0, 0,
            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
    }

    /*
     * check capabilites
     */
    ddcaps.dwSize = sizeof( ddcaps );
    ddrval = IDirectDraw_GetCaps( lpdd, &ddcaps, NULL );

    if( ddrval != DD_OK )
    {
        Msg("GetCaps failed err=%d", ddrval);
        goto error;
    }

    if( ddcaps.dwCaps & DDCAPS_NOHARDWARE )
    {
        Msg( "No hardware support at all" );
    }

    if( ddcaps.dwCaps & DDCAPS_BLTCOLORFILL )
    {
        bColorFill = TRUE;
        Msg( "Device supports color fill" );
    }
    else
    {
        bColorFill = FALSE;
        Msg( "Device does not support color fill" );
    }

    /*
     * default to double buffered on 1mb, triple buffered
     * on > 1mb
     */
    if (nBufferCount == 0)
    {
        if( ddcaps.dwVidMemTotal <= 1024L*1024L*(GameBPP/8) ||
            GameMode.cx > 640 )
        {
            Msg("double buffering (not enough memory)");
            nBufferCount = 2;
        }
        else
        {
            Msg("triple buffering");
            nBufferCount = 3;
        }
    }

    if( ddcaps.dwCaps & DDCAPS_COLORKEY )
    {
        if( ddcaps.dwCKeyCaps & DDCKEYCAPS_SRCBLT )
        {
            Msg( "Can do Src colorkey in hardware" );
        }

        if( ddcaps.dwCKeyCaps & DDCKEYCAPS_DESTBLT )
        {
            Msg( "Can do Dest colorkey in hardware" );
            if( use_dest || !(ddcaps.dwCKeyCaps & DDCKEYCAPS_SRCBLT) )
            {
                /*
                 * since direct draw doesn't support
                 * destination color key in emulation, only
                 * use it if there is enough vram ...
                 */
                if( ddcaps.dwVidMemTotal >= 2 * 1024L*1024L*(GameBPP/8) )
                {
                    Msg( "Using destination color key" );
                    bTransDest = TRUE;
                }
            }
        }
    }
    else
    {
        Msg( "Can't do color key in hardware!" );
    }

    lpDD = lpdd;
    lpDD2 = lpdd2;
    return TRUE;

error:
    return FALSE;

} /* DDEnable */

/*
 * DDDisable
 */
BOOL DDDisable( BOOL fFinal )
{
    if( lpClipper )
    {
        IDirectDrawClipper_Release(lpClipper);
        lpClipper = NULL;
    }

    if( lpBackBuffer )
    {
        IDirectDrawSurface_Release(lpBackBuffer);
        lpBackBuffer = NULL;
    }

    if( lpFrontBuffer )
    {
        IDirectDrawSurface_Release(lpFrontBuffer);
        lpFrontBuffer = NULL;
    }

    if( lpStretchBuffer )
    {
        IDirectDrawSurface_Release(lpStretchBuffer);
        lpStretchBuffer = NULL;
    }

    //
    // fFinal is TRUE when the app is exiting, FALSE if we are
    // just seting a new game size..
    //
    if ( fFinal )
    {
        if( lpDD != NULL )
        {
            IDirectDraw_Release( lpDD );
            IDirectDraw_Release( lpDD2 );
            lpDD = NULL;
        }
    }

    return TRUE;
}

/*
 * DDClear
 *
 * clear the front buffer and all backbuffers.
 */
BOOL DDClear( void )
{
    DDBLTFX     ddbltfx;
    inti;
    HRESULT     ddrval;

    UpdateWindow(hWndMain);

    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = DDColorMatch(lpBackBuffer, RGB(0, 0, 200));

    if (bFullscreen)
    {
        /*
         * do it for all buffers, we either have 1 or 2 back buffers
         * make sure we get them all, 4 is plenty!
         */
        for( i=0; i<4; i++ )
        {
            ddrval = IDirectDrawSurface_Blt(
                            lpBackBuffer,           // dest surface
                            NULL,                   // dest rect
                            NULL,                   // src surface
                            NULL,                   // src rect
                            DDBLT_COLORFILL | DDBLT_WAIT,
                            &ddbltfx);

            if( ddrval != DD_OK )
            {
                Msg("Fill failed ddrval =0x%08lX", ddrval);
                return FALSE;
            }

            ddrval = IDirectDrawSurface_Flip(lpFrontBuffer, NULL, DDFLIP_WAIT);

            if( ddrval != DD_OK )
            {
                Msg("Flip failed ddrval =0x%08lX", ddrval );
                return FALSE;
            }
        }
    }
    else
    {
        ddrval = IDirectDrawSurface_Blt(
                        lpFrontBuffer,          // dest surface
                        &rcWindow,              // dest rect
                        NULL,                   // src surface
                        NULL,                   // src rect
                        DDBLT_COLORFILL | DDBLT_WAIT,
                        &ddbltfx);

        if( ddrval != DD_OK )
        {
            Msg("Fill failed ddrval =0x%08lX", ddrval);
            return FALSE;
        }
    }

    return TRUE;

} /* DDClear */

/*
 * DDCreateFlippingSurface
 *
 * create a FrontBuffer and a BackBuffer(s)
 *
 */
BOOL DDCreateFlippingSurface( void )
{
    DDPIXELFORMAT       ddpf;
    DDSURFACEDESCddsd;
    HRESULTddrval;
    DDSCAPSddscaps;
    DDCAPS              ddcaps;

    ddcaps.dwSize = sizeof( ddcaps );

    if( IDirectDraw_GetCaps( lpDD, &ddcaps, NULL ) != DD_OK )
        return FALSE;

    /*
     * fill in surface desc:
     * want a primary surface with 2 back buffers
     */
    ZeroMemory( &ddsd, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );

    if (bFullscreen && nBufferCount > 1)
    {
        //
        //  fullscreen case, create a primary (ie front) and
        //  either 1  or 2 back buffers
        //
        ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
        ddsd.dwBackBufferCount = nBufferCount-1;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                DDSCAPS_FLIP | DDSCAPS_COMPLEX;

        OutputDebugString("Creating multiple backbuffer primary\n\r");
        ddrval = IDirectDraw_CreateSurface( lpDD, &ddsd, &lpFrontBuffer, NULL );

        if( ddrval != DD_OK )
        {
            Msg( "CreateSurface FAILED! %08lx", ddrval );
            return FALSE;
        }

        /*
         * go find the back buffer
         */
        ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
        ddrval = IDirectDrawSurface_GetAttachedSurface(
                    lpFrontBuffer,
                    &ddscaps,
                    &lpBackBuffer );

        if( ddrval != DD_OK )
        {
            Msg( "GetAttachedSurface failed! err=%d",ddrval );
            return FALSE;
        }

        /*
         *  if we are stretching create a buffer to stretch into
         *
         *  NOTE we always make this buffer in system memory because
         *  we render to the backbuffer (in VRAM) at half the size
         *  now we need to stretch into the backbuffer.  we could just
         *  do a VRAM->VRAM stretch, but this is REAL REAL REAL slow on
         *  some cards (banked cards..)
         */
        if( bStretch && (ddcaps.dwCaps & DDCAPS_BANKSWITCHED) )
        {
            Msg( "On bank switched hardware, creating stretch buffer" );
            lpStretchBuffer = DDCreateSurface( GameSize.cx, GameSize.cy,
    TRUE, FALSE );
        }
    }
    else if (bFullscreen && nBufferCount == 1)
    {
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

        OutputDebugString("Creating no backbuffer primary\n\r");
        ddrval = IDirectDraw_CreateSurface( lpDD, &ddsd, &lpFrontBuffer, NULL );

        if( ddrval != DD_OK )
        {
            Msg( "CreateSurface FAILED! %08lx", ddrval );
            return FALSE;
        }

        IDirectDrawSurface_AddRef(lpFrontBuffer);
        lpBackBuffer = lpFrontBuffer;
    }
    else
    {
        //
        //  window case, create the primary surface
        //  and create a backbuffer in offscreen memory.
        //
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

        ddrval = IDirectDraw_CreateSurface( lpDD, &ddsd, &lpFrontBuffer, NULL );

        if( ddrval != DD_OK )
        {
            Msg( "CreateSurface FAILED! %08lx", ddrval );
            return FALSE;
        }

        lpBackBuffer = DDCreateSurface( GameSize.cx, GameSize.cy, FALSE, FALSE );

        if( lpBackBuffer == NULL )
        {
            Msg( "Cant create the backbuffer" );
            return FALSE;
        }

        //
        // now create a DirectDrawClipper object.
        //
        ddrval = IDirectDraw_CreateClipper(lpDD, 0, &lpClipper, NULL);

        if( ddrval != DD_OK )
        {
            Msg("Cant create clipper");
            return FALSE;
        }

        ddrval = IDirectDrawClipper_SetHWnd(lpClipper, 0, hWndMain);

        if( ddrval != DD_OK )
        {
            Msg("Cant set clipper window handle");
            return FALSE;
        }

        ddrval = IDirectDrawSurface_SetClipper(lpFrontBuffer, lpClipper);

        if( ddrval != DD_OK )
        {
            Msg("Cant attach clipper to front buffer");
            return FALSE;
        }
    }

    /*
     * init the color key
     */
    ddpf.dwSize = sizeof(ddpf);
    IDirectDrawSurface_GetPixelFormat(lpFrontBuffer, &ddpf);

    /*
     * we use white as the color key, if we are in a 8bpp mode, we know
     * what white is (because we use a 332 palette) if we are not in a
     * a 8bpp mode we dont know what white is and we need to figure it
     * out from the device (remember 16bpp comes in two common flavors
     * 555 and 565).  if we wanted to any random color as the color key
     * we would call DDColorMatch (see below) to convert a RGB into a
     * physical color.
     */
    if (ddpf.dwRGBBitCount == 8)
        dwColorKey = 0xff;
    else
        dwColorKey = ddpf.dwRBitMask | ddpf.dwGBitMask | ddpf.dwBBitMask;

    Msg("dwColorKey = 0x%08lX", dwColorKey);

    if( bTransDest )
    {
DDCOLORKEYddck;
        ddck.dwColorSpaceLowValue = dwColorKey;
        ddck.dwColorSpaceHighValue = dwColorKey;
        IDirectDrawSurface_SetColorKey( lpBackBuffer, DDCKEY_DESTBLT, &ddck);
    }

    return TRUE;

} /* DDCreateFlippingSurface */

/*
 * DDCreateSurface
 */
LPDIRECTDRAWSURFACE DDCreateSurface(
DWORD width,
DWORD height,
BOOL sysmem,
BOOL trans )
{
    DDSURFACEDESCddsd;
    HRESULTddrval;
    DDCOLORKEYddck;
    LPDIRECTDRAWSURFACEpsurf;

    /*
     * fill in surface desc
     */
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;

    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    if( sysmem || bUseSysMem )
    {
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    }

    ddsd.dwHeight = height;
    ddsd.dwWidth = width;

    ddrval = IDirectDraw_CreateSurface( lpDD, &ddsd, &psurf, NULL );

    /*
     * set the color key for this bitmap
     */
    if( ddrval == DD_OK )
    {
if( trans && !bTransDest )
{
            ddck.dwColorSpaceLowValue = dwColorKey;
            ddck.dwColorSpaceHighValue = dwColorKey;
            IDirectDrawSurface_SetColorKey( psurf, DDCKEY_SRCBLT, &ddck);
}
    }
    else
    {
Msg( "CreateSurface FAILED, rc = %ld", (DWORD) LOWORD( ddrval ) );
psurf = NULL;
    }

     return psurf;

} /* DDCreateSurface */

DWORD DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb)
{
    COLORREF rgbT;
    HDC hdc;
    DWORD dw = CLR_INVALID;
    DDSURFACEDESC ddsd;
    HRESULT hres;

    if (IDirectDrawSurface_GetDC(pdds, &hdc) == DD_OK)
    {
        rgbT = GetPixel(hdc, 0, 0);
        SetPixel(hdc, 0, 0, rgb);
        IDirectDrawSurface_ReleaseDC(pdds, hdc);
    }

    ddsd.dwSize = sizeof(ddsd);
    hres = IDirectDrawSurface_Lock(
        pdds, NULL, &ddsd, DDLOCK_WAIT, NULL);

    if (hres == DD_OK)
    {
        dw  = *(DWORD *)ddsd.lpSurface;
if(ddsd.ddpfPixelFormat.dwRGBBitCount != 32)
    dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount)-1;
        IDirectDrawSurface_Unlock(pdds, NULL);
    }
    else
    {
IDirectDrawSurface_GetSurfaceDesc(pdds,&ddsd);
if(ddsd.ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)
    Msg("Failed to lock Primary Surface!");
else
    Msg("Failed to lock NON-PRIMARY Surface!");
    }

    if (IDirectDrawSurface_GetDC(pdds, &hdc) == DD_OK)
    {
        SetPixel(hdc, 0, 0, rgbT);
        IDirectDrawSurface_ReleaseDC(pdds, hdc);
    }

    return dw;
}

/*
 * ReadPalFile
 *
 * Create a DirectDrawPalette from a palette file
 *
 * if the palette files cant be found, make a default 332 palette
 */
LPDIRECTDRAWPALETTE ReadPalFile( char *fname )
{
    int                 i;
    intfh;
    HRESULTddrval;
    IDirectDrawPalette *ppal;

    struct  {
        DWORD           dwRiff;
        DWORD           dwFileSize;
        DWORD           dwPal;
        DWORD           dwData;
        DWORD           dwDataSize;
        WORD            palVersion;
        WORD            palNumEntries;
        PALETTEENTRY    ape[256];
    }   pal;

    pal.dwRiff = 0;

    if (fname)
    {
        fh = _lopen( fname, OF_READ);

        if (fh != -1)
        {
            _lread(fh, &pal, sizeof(pal));
            _lclose(fh);
        }
    }

    /*
     * if the file is not a palette file, or does not exist
     * default to a 332 palette
     */
    if (pal.dwRiff != 0x46464952 || // 'RIFF'
        pal.dwPal  != 0x204C4150 || // 'PAL '
        pal.dwData != 0x61746164 || // 'data'
        pal.palVersion != 0x0300 ||
        pal.palNumEntries > 256  ||
        pal.palNumEntries < 1)
    {
        Msg("Can't open palette file, using default 332.");

        for( i=0; i<256; i++ )
        {
            pal.ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
            pal.ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
            pal.ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
            pal.ape[i].peFlags = (BYTE)0;
        }
    }

    ddrval = IDirectDraw_CreatePalette(
    lpDD,
    DDPCAPS_8BIT,
                            pal.ape,
    &ppal,
    NULL );
    return ppal;

} /* ReadPalFile */

/*
 * Splash
 *
 * Draw a splash screen during startup
 * NOTE the screen has been cleared in DDCreateFlippingSurface
 */
void Splash( void )
{
    HDC hdc;
    HRESULT err = 0;

    DDClear();

//    if ((err = IDirectDrawSurface_GetDC(lpFrontBuffer, &hdc)) == DD_OK)
    if ((hdc = GetDC(hWndMain)) )
    {
        char ach[256];
        int len;

        len = wsprintf(ach,
            "FoxBear is loading......   Device:%s %s",
            aDDDevs[DevIndex].DriverDesc, aDDDevs[DevIndex].DriverName);

        SetTextColor(hdc, RGB(255,255,255));
        SetBkMode(hdc, TRANSPARENT);
        TextOut(hdc, rcWindow.left, rcWindow.top, ach, len);

        //IDirectDrawSurface_ReleaseDC(lpFrontBuffer, hdc);
        ReleaseDC(hWndMain,hdc);
    }
    else
    {
Msg("GetDC failed! 0x%x",err);
    }

} /* Splash */

/*
 * MEMORY ALLOCATION ROUTINES...
 */

/*
 * MemAlloc
 */
LPVOID MemAlloc( UINT size )
{
    LPVOIDptr;

    ptr = LocalAlloc( LPTR, size );
    return ptr;

} /* MemAlloc */

/*
 * CMemAlloc
 */
LPVOID CMemAlloc( UINT cnt, UINT isize )
{
    DWORDsize;
    LPVOIDptr;

    size = cnt * isize;
    ptr = LocalAlloc( LPTR, size );
    return ptr;

} /* CMemAlloc */

/*
 * MemFree
 */
void MemFree( LPVOID ptr )
{
    if( ptr != NULL )
    {
LocalFree( ptr );
    }

} /* MemFree */

HRESULT CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID Context)
{
    int ChkMode;

    Msg("Mode: %dx%dx%d", pddsd->dwWidth, pddsd->dwHeight,pddsd->ddpfPixelFormat.dwRGBBitCount);

    for(ChkMode = 0;ChkMode < NumModes;ChkMode++)
    {
        if(
            (ModeList[ChkMode].w == (int)pddsd->dwWidth)&&
            (ModeList[ChkMode].h == (int)pddsd->dwHeight)&&
            (ModeList[ChkMode].bpp == (int)pddsd->ddpfPixelFormat.dwRGBBitCount)&&
            (ModeList[ChkMode].dwFlags == (int)pddsd->ddsCaps.dwCaps)
          )
            return DDENUMRET_OK;
    }

    ModeList[NumModes].w   = pddsd->dwWidth;
    ModeList[NumModes].h   = pddsd->dwHeight;
    ModeList[NumModes].bpp = pddsd->ddpfPixelFormat.dwRGBBitCount;
    ModeList[NumModes].dwFlags = pddsd->ddsCaps.dwCaps;

    NumModes++;

    return DDENUMRET_OK;
}

HRESULT CALLBACK DDEnumCallback(GUID * lpGUID, LPSTR DriverDesc, LPSTR DriverName, LPVOID lpContext)
{
    if(MaxDevIndex >= sizeof(aDDDevs)/sizeof(aDDDevs[0]))
        return E_FAIL;

    Msg("Device: %s (%s)", DriverDesc, DriverName);

    if(lpGUID == NULL || lpGUID == (GUID*)DDCREATE_EMULATIONONLY)
    {
        aDDDevs[MaxDevIndex].guid = lpGUID;
    }
    else
    {
        aDDDevs[MaxDevIndex].DriverGUID = *lpGUID;
        aDDDevs[MaxDevIndex].guid = &aDDDevs[MaxDevIndex].DriverGUID;
    }

    lstrcpyn(aDDDevs[MaxDevIndex].DriverDesc, DriverDesc, 128);
    lstrcpyn(aDDDevs[MaxDevIndex].DriverName, DriverName, 128);

    MaxDevIndex++;
    return DDENUMRET_OK;
}


FBSOUND.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       fbsound.c
 *  Content:Game sound effect routines
 *
 ***************************************************************************/
#include "foxbear.h"

/*
 * Array of pointers to our sound effects
 */
LPDIRECTSOUNDlpDS;
LPDIRECTSOUNDBUFFER     lpSoundEffects[NUM_SOUND_EFFECTS];

char szSoundEffects[NUM_SOUND_EFFECTS][MAX_PATH] =
{
    "STOP",
    "THROW",
    "JUMP",
    "STUNNED",
    "STRIKE02",
    "MISS02"
};

/*
 * DSEnable
 *
 * Figures out whether or not to use DirectSound, based on an entry
 * in WIN.INI.  Sets a module-level flag and goes about creating the
 * DirectSound object if necessary.  Returns TRUE if successful.
 */
BOOL DSEnable( HWND hwnd )
{
    HRESULTdsrval;
    BOOL                bUseDSound;

    bUseDSound = GetProfileInt("FoxBear", "use_dsound", bWantSound);

    if (!bUseDSound)
    {
        lpDS = NULL;
        return TRUE;
    }

    if (lpDS != NULL)
    {
        Msg( "DSEnable, already enabled" );
        return TRUE;
    }

    dsrval = DirectSoundCreate(NULL, &lpDS, NULL);

    if (dsrval != DS_OK)
    {
        Msg("DirectSoundCreate FAILED");
        return FALSE;
    }

    dsrval = IDirectSound_SetCooperativeLevel(lpDS, hwnd, DSSCL_NORMAL);

    if (dsrval != DS_OK)
    {
        DSDisable();
        Msg("SetCooperativeLevel FAILED");
        return FALSE;
    }

    return TRUE;

} /* DSEnable */

/*
 * DSDisable
 *
 * Turn off DirectSound
 */
BOOL DSDisable( void )
{
    if (lpDS == NULL)
    {
return TRUE;
    }

    IDirectSound_Release(lpDS);
    lpDS = NULL;

    return TRUE;

} /* DSDisable */

/*
 * InitSound
 *
 * Sets up the DirectSound object and loads all sounds into secondary
 * DirectSound buffers.  Returns FALSE on error, or TRUE if successful
 */
BOOL InitSound( HWND hwndOwner )
{
    int idx;
    DSBUFFERDESC dsBD;
    IDirectSoundBuffer *lpPrimary;

    DSEnable(hwndOwner);

    if (lpDS == NULL)
        return TRUE;

    /*
     * Load all sounds -- any that can't load for some reason will have NULL
     * pointers instead of valid SOUNDEFFECT data, and we will know not to
     * play them later on.
     */
    for( idx = 0; idx < NUM_SOUND_EFFECTS; idx++ )
    {
        if (SoundLoadEffect((EFFECT)idx))
        {
            DSBCAPS  caps;

            caps.dwSize = sizeof(caps);
            IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps);

            if (caps.dwFlags & DSBCAPS_LOCHARDWARE)
                Msg( "Sound effect %s in hardware", szSoundEffects[idx]);
            else
                Msg( "Sound effect %s in software", szSoundEffects[idx]);
        }
        else
        {
            Msg( "cant load sound effect %s", szSoundEffects[idx]);
        }
    }

    /*
     * get the primary buffer and start it playing
     *
     * by playing the primary buffer, DirectSound knows to keep the
     * mixer active, even though we are not making any noise.
     */

    ZeroMemory( &dsBD, sizeof(DSBUFFERDESC) );
    dsBD.dwSize = sizeof(dsBD);
    dsBD.dwFlags = DSBCAPS_PRIMARYBUFFER;

    if (SUCCEEDED(IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpPrimary, NULL)))
    {
        if (!SUCCEEDED(IDirectSoundBuffer_Play(lpPrimary, 0, 0, DSBPLAY_LOOPING)))
        {
            Msg("Unable to play Primary sound buffer");
        }

        IDirectSoundBuffer_Release(lpPrimary);
    }
    else
    {
        Msg("Unable to create Primary sound buffer");
    }

    return TRUE;

} /* InitSound */

/*
 * DestroySound
 *
 * Undoes everything that was done in a InitSound call
 */
BOOL DestroySound( void )
{
    DWORDidxKill;

    for( idxKill = 0; idxKill < NUM_SOUND_EFFECTS; idxKill++ )
    {
SoundDestroyEffect( (EFFECT)idxKill );
    }

    DSDisable();
    return TRUE;

} /* DestroySound */

/*
 * SoundDestroyEffect
 *
 * Frees up resources associated with a sound effect
 */
BOOL SoundDestroyEffect( EFFECT sfx )
{
    if(lpSoundEffects[sfx])
    {
        IDirectSoundBuffer_Release(lpSoundEffects[sfx]);
        lpSoundEffects[sfx] = NULL;
    }
    return TRUE;

} /* SoundDestryEffect */

/*
 * SoundLoadEffect
 *
 * Initializes a sound effect by loading the WAV file from a resource
 */
BOOL SoundLoadEffect( EFFECT sfx )
{
    if (lpDS && lpSoundEffects[sfx] == NULL && *szSoundEffects[sfx])
    {
        //
        //  use DSLoadSoundBuffer (in ..\misc\dsutil.c) to load
        //  a sound from a resource.
        //
        lpSoundEffects[sfx] = DSLoadSoundBuffer(lpDS, szSoundEffects[sfx]);
    }

    return lpSoundEffects[sfx] != NULL;

} /* SoundLoadEffect */

/*
 * SoundPlayEffect
 *
 * Plays the sound effect specified.
 * Returns TRUE if succeeded.
 */
BOOL SoundPlayEffect( EFFECT sfx )
{
    HRESULT     dsrval;
    IDirectSoundBuffer *pdsb = lpSoundEffects[sfx];

    if( !lpDS || !pdsb )
    {
return FALSE;
    }

    /*
     * Rewind the play cursor to the start of the effect, and play
     */
    IDirectSoundBuffer_SetCurrentPosition(pdsb, 0);
    dsrval = IDirectSoundBuffer_Play(pdsb, 0, 0, 0);

    if (dsrval == DSERR_BUFFERLOST)
    {
        Msg("** %s needs restored", szSoundEffects[sfx]);

        dsrval = IDirectSoundBuffer_Restore(pdsb);

        if (dsrval == DS_OK)
        {
            if (DSReloadSoundBuffer(pdsb, szSoundEffects[sfx]))
            {
                Msg("** %s has been restored", szSoundEffects[sfx]);
                IDirectSoundBuffer_SetCurrentPosition(pdsb, 0);
                dsrval = IDirectSoundBuffer_Play(pdsb, 0, 0, 0);
            }
            else
            {
                dsrval = E_FAIL;
            }
        }
    }

    return (dsrval == DS_OK);

} /* SoundPlayEffect */

/*
 * SoundStopEffect
 *
 * Stops the sound effect specified.
 * Returns TRUE if succeeded.
 */
BOOL SoundStopEffect( EFFECT sfx )
{
    HRESULTdsrval;

    if( !lpDS || !lpSoundEffects[sfx] )
    {
return FALSE;
    }

    dsrval = IDirectSoundBuffer_Stop(lpSoundEffects[sfx]);

    return SUCCEEDED(dsrval);

} /* SoundStopEffect */


FBSOUND.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       fbsound.h
 *  Content:Includes for FoxBear DirectSound support
 *
 ***************************************************************************/
#ifndef __FBSOUND_INCLUDED__
#define __FBSOUND_INCLUDED__

/*
 * types of sound effects
 */
typedef enum enum_EFFECT
{
    SOUND_STOP = 0,
    SOUND_THROW,
    SOUND_JUMP,
    SOUND_STUNNED,
    SOUND_BEARSTRIKE,
    SOUND_BEARMISS,
} EFFECT;

#define NUM_SOUND_EFFECTS       6

/*
 * fn prototypes
 */
BOOL InitSound( HWND );
BOOL DestroySound( void );
BOOL DSDisable( void );
BOOL DSEnable( HWND );
BOOL SoundLoadEffect( EFFECT );
BOOL SoundPlayEffect( EFFECT );
BOOL SoundStopEffect( EFFECT );
BOOL SoundDestroyEffect( EFFECT );

#endif


FOXBEAR.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:winfox.c
 *  Content:Windows fox sample game
 *
 ***************************************************************************/
#include "foxbear.h"
#include "rcids.h"      // for FOX_ICON

LPDIRECTDRAWSURFACE     lpFrontBuffer;
LPDIRECTDRAWSURFACE     lpBackBuffer;
LPDIRECTDRAWCLIPPER     lpClipper;
LPDIRECTDRAWSURFACE     lpStretchBuffer;
LPDIRECTDRAWSURFACE     lpFrameRate;
LPDIRECTDRAWSURFACE     lpInfo;
LPDIRECTDRAWPALETTE     lpPalette;
LPDIRECTDRAW            lpDD;
LPDIRECTDRAW2           lpDD2;
SHORT                   lastInput = 0;
HWND                    hWndMain;
RECT                    rcWindow;
BOOL                    bShowFrameCount=TRUE;
BOOL                    bIsActive;
BOOL                    bPaused;

BOOL                    bStretch;
BOOL                    bFullscreen=TRUE;
BOOL                    bStress=FALSE;     // just keep running if true
BOOL                    bHelp=FALSE;       // help requested
RECT                    GameRect;          // game rect
SIZE                    GameSize;          // game is this size
SIZE                    GameMode;          // display mode size
UINT                    GameBPP;           // the bpp we want
DWORD                   dwColorKey;        // our color key
DWORD                   AveFrameRate;
DWORD                   AveFrameRateCount;
BOOL                    bWantSound = TRUE;

extern int              DevIndex;
extern int              MaxDevIndex;

#define OUR_APP_NAME  "Win Fox Application"

#define ODS OutputDebugString

BOOL InitGame(void);
void ExitGame(void);
void initNumSurface(void);
#ifndef DEBUG
void __cdecl Msg( LPSTR fmt, ... ) { }
#endif

/*
 * PauseGame()
 */
void PauseGame()
{
    Msg("**** PAUSE");
    bPaused = TRUE;
    InvalidateRect(hWndMain, NULL, TRUE);
}

/*
 * UnPauseGame()
 */
void UnPauseGame()
{
    if (GetForegroundWindow() == hWndMain)
    {
        Msg("**** UNPAUSE");
        bPaused = FALSE;
    }
}

/*
 * RestoreGame()
 */
BOOL RestoreGame()
{
    if (lpFrontBuffer == NULL || IDirectDrawSurface_Restore(lpFrontBuffer) != DD_OK)
    {
        Msg("***** cant restore FrontBuffer");
        return FALSE;
    }

    if (!bFullscreen)
    {
        if (lpBackBuffer == NULL || IDirectDrawSurface_Restore(lpBackBuffer) != DD_OK)
        {
            Msg("***** cant restore BackBuffer");
            return FALSE;
        }
    }

    if (lpStretchBuffer && IDirectDrawSurface_Restore(lpStretchBuffer) != DD_OK)
    {
        Msg("***** cant restore StretchBuffer");
        return FALSE;
    }

    if (lpFrameRate == NULL || lpInfo == NULL ||
        IDirectDrawSurface_Restore(lpFrameRate) != DD_OK ||
        IDirectDrawSurface_Restore(lpInfo) != DD_OK)
    {
        Msg("***** cant restore frame rate stuff");
        return FALSE;
    }
    initNumSurface();

    if (!gfxRestoreAll())
    {
        Msg("***** cant restore art");
        return FALSE;
    }

    return TRUE;
}

/*
 * ProcessFox
 */
BOOL ProcessFox(SHORT sInput)
{
    if ((lpFrontBuffer && IDirectDrawSurface_IsLost(lpFrontBuffer) == DDERR_SURFACELOST) ||
        (lpBackBuffer && IDirectDrawSurface_IsLost(lpBackBuffer) == DDERR_SURFACELOST))
    {
        if (!RestoreGame())
        {
            PauseGame();
            return FALSE;
        }
    }

    ProcessInput(sInput);
    NewGameFrame();
    return TRUE;

} /* ProcessFox */

static HFONThFont;

DWORD   dwFrameCount;
DWORDdwFrameTime;
DWORDdwFrames;
DWORD   dwFramesLast;
SIZE    sizeFPS;
SIZEsizeINFO;
intFrameRateX;
charszFPS[]   = "FPS %02d";
char    szINFO[]  = "%dx%dx%d%s     F6=mode F8=x2 ALT+ENTER=Window";
char    szINFOW[] = "%dx%dx%d%s     F6=mode F8=x2 ALT+ENTER=Fullscreen";

charszFrameRate[128];
charszInfo[128];

COLORREF InfoColor      = RGB(0,152,245);
COLORREF FrameRateColor = RGB(255,255,0);
COLORREF BackColor= RGB(255,255,255);

/*
 * initNumSurface
 */
void initNumSurface( void )
{
    HDC        hdc;
    RECT        rc;
    int         len;

    dwFramesLast = 0;

    len = wsprintf(szFrameRate, szFPS, 0, 0);

    if( lpFrameRate && IDirectDrawSurface_GetDC(lpFrameRate, &hdc ) == DD_OK )
    {
        SelectObject(hdc, hFont);
        SetTextColor(hdc, FrameRateColor);
        SetBkColor(hdc, BackColor);
        SetBkMode(hdc, OPAQUE);
        SetRect(&rc, 0, 0, 10000, 10000);
        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szFrameRate, len, NULL);
        GetTextExtentPoint(hdc, szFrameRate, 4, &sizeFPS);
        FrameRateX = sizeFPS.cx;
        GetTextExtentPoint(hdc, szFrameRate, len, &sizeFPS);

        IDirectDrawSurface_ReleaseDC(lpFrameRate, hdc);
    }

    if (bFullscreen)
        len = wsprintf(szInfo, szINFO,
                       GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");
    else
        len = wsprintf(szInfo, szINFOW,
                       GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");

    if( lpInfo && IDirectDrawSurface_GetDC(lpInfo, &hdc ) == DD_OK )
    {
        SelectObject(hdc, hFont);
        SetTextColor(hdc, InfoColor);
        SetBkColor(hdc, BackColor);
        SetBkMode(hdc, OPAQUE);
        SetRect(&rc, 0, 0, 10000, 10000);
        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szInfo, len, NULL);
        GetTextExtentPoint(hdc, szInfo, len, &sizeINFO);

        IDirectDrawSurface_ReleaseDC(lpInfo, hdc);
    }

} /* initNumSurface */

/*
 * makeFontStuff
 */
static BOOL makeFontStuff( void )
{
    DDCOLORKEY      ddck;
    HDC             hdc;

    if (hFont != NULL)
    {
        DeleteObject(hFont);
    }

    hFont = CreateFont(
        GameSize.cx <= 512 ? 12 : 24,
        0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        NONANTIALIASED_QUALITY, // DEFAULT_QUALITY,
        VARIABLE_PITCH,
        "Arial" );

    /*
     * make a sample string so we can measure it with the current font.
     */
    if( hFont != NULL)
    {
        initNumSurface();

        hdc = GetDC(NULL);
        SelectObject(hdc, hFont);
        GetTextExtentPoint(hdc, szFrameRate, lstrlen(szFrameRate), &sizeFPS);
        GetTextExtentPoint(hdc, szInfo, lstrlen(szInfo), &sizeINFO);
        ReleaseDC(NULL, hdc);
    }
    /*
     * Create a surface to copy our bits to.
     */
    lpFrameRate = DDCreateSurface(sizeFPS.cx, sizeFPS.cy, FALSE,TRUE);
    lpInfo = DDCreateSurface(sizeINFO.cx, sizeINFO.cy, FALSE,TRUE);

    if( lpFrameRate == NULL || lpInfo == NULL )
    {
 return FALSE;
    }

    /*
     * now set the color key, we use a totaly different color than
     * the rest of the app, just to be different so drivers dont always
     * get white or black as the color key...
     *
     * dont forget when running on a dest colorkey device, we need
     * to use the same color key as the rest of the app.
     */
    if( bTransDest )
        BackColor = RGB(255,255,255);
    else
        BackColor = RGB(128,64,255);

    ddck.dwColorSpaceLowValue  = DDColorMatch(lpInfo, BackColor);
    ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;

    IDirectDrawSurface_SetColorKey( lpInfo, DDCKEY_SRCBLT, &ddck);
    IDirectDrawSurface_SetColorKey( lpFrameRate, DDCKEY_SRCBLT, &ddck);

    /*
     * now draw the text for real
     */
    initNumSurface();

    return TRUE;
}

/*
 * DisplayFrameRate
 */
void DisplayFrameRate( void )
{
    DWORD               time2;
    char                buff[256];
    HDC                 hdc;
    HRESULT             ddrval;
    RECT                rc;
    DWORD               dw;

    if( !bShowFrameCount )
    {
        return;
    }

    dwFrameCount++;
    time2 = timeGetTime() - dwFrameTime;
    if( time2 > 1000 )
    {
        dwFrames = (dwFrameCount*1000)/time2;
        dwFrameTime = timeGetTime();
        dwFrameCount = 0;

        AveFrameRate += dwFrames;
        AveFrameRateCount++;
    }

    if( dwFrames == 0 )
    {
        return;
    }

    if( dwFrames != dwFramesLast )
    {
        dwFramesLast = dwFrames;

        if( IDirectDrawSurface_GetDC(lpFrameRate, &hdc ) == DD_OK )
        {
            buff[0] = (char)((dwFrames / 10) + '0');
            buff[1] = (char)((dwFrames % 10) + '0');
            if(hFont != NULL)
            {
                SelectObject(hdc, hFont);
                SetTextColor(hdc, FrameRateColor);
                SetBkColor(hdc, BackColor);
                TextOut(hdc, FrameRateX, 0, buff, 2);
            }
            IDirectDrawSurface_ReleaseDC(lpFrameRate, hdc);
        }
    }

    /*
     * put the text on the back buffer.
     */
    if (bTransDest)
        dw = DDBLTFAST_DESTCOLORKEY | DDBLTFAST_WAIT;
    else
        dw = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT;

    SetRect(&rc, 0, 0, sizeFPS.cx, sizeFPS.cy);
    ddrval = IDirectDrawSurface_BltFast(lpBackBuffer,
           GameRect.left + (GameSize.cx - sizeFPS.cx)/2, GameRect.top + 20,
           lpFrameRate, &rc, dw);

    SetRect(&rc, 0, 0, sizeINFO.cx, sizeINFO.cy);
    ddrval = IDirectDrawSurface_BltFast(lpBackBuffer,
           GameRect.left + 10, GameRect.bottom - sizeINFO.cy - 10,
           lpInfo, &rc, dw);

} /* DisplayFrameRate */

/*
 * MainWndProc
 *
 * Callback for all Windows messages
 */
long FAR PASCAL MainWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;

    switch( message )
    {
    case WM_SIZE:
    case WM_MOVE:
        if (IsIconic(hWnd))
        {
            Msg("FoxBear is minimized, pausing");
            PauseGame();
        }

        if (bFullscreen)
        {
            SetRect(&rcWindow, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
        }
        else
        {
            GetClientRect(hWnd, &rcWindow);
            ClientToScreen(hWnd, (LPPOINT)&rcWindow);
            ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
        }
        Msg("WINDOW RECT: [%d,%d,%d,%d]", rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom);
        break;

    case WM_ACTIVATEAPP:
        bIsActive = (BOOL)wParam && GetForegroundWindow() == hWnd;

        if (bIsActive)
            Msg("FoxBear is active");
        else
            Msg("FoxBear is not active");

        //
        // while we were not-active something bad happened that caused us
        // to pause, like a surface restore failing or we got a palette
        // changed, now that we are active try to fix things
        //
        if (bPaused && bIsActive)
        {
            if (RestoreGame())
            {
                UnPauseGame();
            }
            else
            {
               if (GetForegroundWindow() == hWnd)
               {
                    //
                    //  we are unable to restore, this can happen when
                    //  the screen resolution or bitdepth has changed
                    //  we just reload all the art again and re-create
                    //  the front and back buffers.  this is a little
                    //  overkill we could handle a screen res change by
                    //  just recreating the front and back buffers we dont
                    //  need to redo the art, but this is way easier.
                    //
                    if (InitGame())
                    {
                        UnPauseGame();
                    }
                }
            }
        }
        break;

    case WM_QUERYNEWPALETTE:
        //
        //  we are getting the palette focus, select our palette
        //
        if (!bFullscreen && lpPalette && lpFrontBuffer)
        {
            HRESULT ddrval;

            ddrval = IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
            if( ddrval == DDERR_SURFACELOST )
            {
                IDirectDrawSurface_Restore( lpFrontBuffer );

                ddrval= IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
                if( ddrval == DDERR_SURFACELOST )
                {
                   Msg("  Failed to restore palette after second try");
                }
            }

            //
            // Restore normal title if palette is ours
            //

            if( ddrval == DD_OK )
            {
                SetWindowText( hWnd, OUR_APP_NAME );
            }
        }
        break;

    case WM_PALETTECHANGED:
        //
        //  if another app changed the palette we dont have full control
        //  of the palette. NOTE this only applies for FoxBear in a window
        //  when we are fullscreen we get all the palette all of the time.
        //
        if ((HWND)wParam != hWnd)
        {
            if( !bFullscreen )
            {
                if( !bStress )
                {
                    Msg("***** PALETTE CHANGED, PAUSING GAME");
                    PauseGame();
                }
                else
                {
                    Msg("Lost palette but continuing");
                    SetWindowText( hWnd, OUR_APP_NAME
                                 " - palette changed COLORS PROBABLY WRONG" );
                }
            }
        }
        break;

    case WM_DISPLAYCHANGE:
        break;

    case WM_CREATE:
        break;

    case WM_SETCURSOR:
        if (bFullscreen && bIsActive)
        {
            SetCursor(NULL);
            return TRUE;
        }
        break;

    case WM_SYSKEYUP:
        switch( wParam )
        {
        // handle ALT+ENTER (fullscreen)
        case VK_RETURN:
            bFullscreen = !bFullscreen;
            ExitGame();
            DDDisable(TRUE);        // destroy DirectDraw object
            GameMode.cx = 320;
            GameMode.cy = 200;
            InitGame();
            break;
        }
        break;

    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_NUMPAD5:
            lastInput=KEY_STOP;
            break;
        case VK_DOWN:
        case VK_NUMPAD2:
            lastInput=KEY_DOWN;
            break;
        case VK_LEFT:
        case VK_NUMPAD4:
            lastInput=KEY_LEFT;
            break;
        case VK_RIGHT:
        case VK_NUMPAD6:
            lastInput=KEY_RIGHT;
            break;
        case VK_UP:
        case VK_NUMPAD8:
            lastInput=KEY_UP;
            break;
        case VK_HOME:
        case VK_NUMPAD7:
            lastInput=KEY_JUMP;
            break;
        case VK_NUMPAD3:
            lastInput=KEY_THROW;
            break;
        case VK_F5:
            bShowFrameCount = !bShowFrameCount;
            if( bShowFrameCount )
            {
              dwFrameCount = 0;
              dwFrameTime = timeGetTime();
            }
        break;

        case VK_F6:
            {
            static i;
            //
            // find our current mode in the mode list
            //
            if(bFullscreen)
            {
                for (i=0; i<NumModes; i++)
                {
                    if (ModeList[i].bpp == (int)GameBPP &&
                        ModeList[i].w   == GameSize.cx &&
                        ModeList[i].h   == GameSize.cy)
                    {
                        break;
                    }
                }
            }else
            {
                for (i=0; i<NumModes; i++)
                {
                    if (ModeList[i].w   == GameSize.cx &&
                        ModeList[i].h   == GameSize.cy)
                    {
                        break;
                    }
                }
            }
            //
            // now step to the next mode, wrapping to the first one.
            //
            if (++i >= NumModes)
            {
                i = 0;
            }
            Msg("ModeList %d %d",i,NumModes);
            GameMode.cx = ModeList[i].w;
            GameMode.cy = ModeList[i].h;
            GameBPP     = ModeList[i].bpp;
            bStretch    = FALSE;
            InitGame();
            }
            break;
        case VK_F7:
            GameBPP = GameBPP == 8 ? 16 : 8;
            InitGame();
            break;

        case VK_F8:
            if (bFullscreen)
            {
                bStretch = !bStretch;
                InitGame();
            }
            else
            {
                RECT rc;

                GetClientRect(hWnd, &rc);

                bStretch = (rc.right  != GameSize.cx) ||
                           (rc.bottom != GameSize.cy);

                if (bStretch = !bStretch)
                    SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2);
                else
                    SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy);

                AdjustWindowRectEx(&rc,
                    GetWindowStyle(hWnd),
                    GetMenu(hWnd) != NULL,
                    GetWindowExStyle(hWnd));

                SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                    SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            }
            break;
        case VK_F9:
            DevIndex ++;
            bUseEmulation = FALSE;
            if (DevIndex >= MaxDevIndex)
                DevIndex = 0;

            ExitGame();
            DDDisable(TRUE);        // destroy DirectDraw object
            InitGame();
            break;
        case VK_F4:
            // treat F4 like ALT+ENTER (fullscreen)
            PostMessage(hWnd, WM_SYSKEYUP, VK_RETURN, 0);
            break;

        case VK_F3:
            bPaused = !bPaused;
            break;

        case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
            return 0;
        }
        break;

    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
        if (bPaused)
        {
            char *sz = "Game is paused, this is not a bug.";
            TextOut(ps.hdc, 0, 0, sz, lstrlen(sz));
        }
        EndPaint( hWnd, &ps );
        return 1;

    case WM_DESTROY:
        hWndMain = NULL;
        lastInput=0;
        DestroyGame();          // end of game
        DDDisable(TRUE);        // destroy DirectDraw object
        PostQuitMessage( 0 );
        break;
    }

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

} /* MainWndProc */

/*
 * initApplication
 *
 * Do that Windows initialization stuff...
 */
static BOOL initApplication( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASS wc;
    BOOL     rc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, MAKEINTATOM(FOX_ICON));
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "WinFoxClass";
    rc = RegisterClass( &wc );
    if( !rc )
    {
        return FALSE;
    }

    hWndMain = CreateWindowEx(
        WS_EX_APPWINDOW,
        "WinFoxClass",
        OUR_APP_NAME,
        WS_VISIBLE |    // so we dont have to call ShowWindow
        WS_SYSMENU |    // so we get a icon in in our tray button
        WS_POPUP,
        0,
        0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        NULL,
        NULL,
        hInstance,
        NULL );

    if( !hWndMain )
    {
        return FALSE;
    }

    UpdateWindow( hWndMain );
    SetFocus( hWndMain );

    return TRUE;

} /* initApplication */

/*
 * ExitGame
 *
 * Exiting current game, clean up
 */
void ExitGame( void )
{
    if( lpFrameRate )
    {
        IDirectDrawSurface_Release(lpFrameRate);
        lpFrameRate = NULL;
    }

    if( lpInfo )
    {
        IDirectDrawSurface_Release(lpInfo);
        lpInfo = NULL;
    }

    if( lpPalette )
    {
        IDirectDrawSurface_Release(lpPalette);
        lpPalette = NULL;
    }

    DestroyGame();

} /* ExitGame */

/*
 * InitGame
 *
 * Initializing current game
 */
BOOL InitGame( void )
{
    ExitGame();

    GameSize = GameMode;

    /*
     * initialize sound
     */
    InitSound( hWndMain );

    /*
     * init DirectDraw, set mode, ...
     * NOTE GameMode might be set to 640x480 if we cant get the asked for mode.
     */
    if( !PreInitializeGame() )
    {
        return FALSE;
    }

    if (bStretch && bFullscreen)
    {
        GameSize.cx     = GameMode.cx / 2;
        GameSize.cy     = GameMode.cy / 2;
        GameRect.left   = GameMode.cx - GameSize.cx;
        GameRect.top    = GameMode.cy - GameSize.cy;
        GameRect.right  = GameMode.cx;
        GameRect.bottom = GameMode.cy;

        if (lpStretchBuffer)
            Msg("Stretching using a system-memory stretch buffer");
        else
            Msg("Stretching using a VRAM->VRAM blt");
    }
    else
    {
        GameRect.left   = (GameMode.cx - GameSize.cx) / 2;
        GameRect.top    = (GameMode.cy - GameSize.cy) / 2;
        GameRect.right  = GameRect.left + GameSize.cx;
        GameRect.bottom = GameRect.top + GameSize.cy;
    }

    /*
     * setup our palette
     */
    if( GameBPP == 8 )
    {
        lpPalette = ReadPalFile( NULL );        // create a 332 palette

        if( lpPalette == NULL )
        {
            Msg( "Palette create failed" );
            return FALSE;
        }

        IDirectDrawSurface_SetPalette( lpFrontBuffer, lpPalette );
    }

    /*
     *  load all the art and things.
     */
    if( !InitializeGame() )
    {
        return FALSE;
    }

    /*
     * init our code to draw the FPS
     */
    makeFontStuff();

    /*
     * spew some stats
     */
    {
        DDCAPSddcaps;
        ddcaps.dwSize = sizeof( ddcaps );
        IDirectDraw_GetCaps( lpDD, &ddcaps, NULL );
        Msg( "Total=%ld, Free VRAM=%ld", ddcaps.dwVidMemTotal, ddcaps.dwVidMemFree );
        Msg( "Used = %ld", ddcaps.dwVidMemTotal- ddcaps.dwVidMemFree );
    }

    return TRUE;

} /* InitGame */

#define IS_NUM(c)     ((c) >= '0' && (c) <= '9')
#define IS_SPACE(c)   ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == 'x')

int getint(char**p, int def)
{
    int i=0;

    while (IS_SPACE(**p))
        (*p)++;

    if (!IS_NUM(**p))
        return def;

    while (IS_NUM(**p))
        i = i*10 + *(*p)++ - '0';

    while (IS_SPACE(**p))
        (*p)++;

    return i;
}

/*
 * WinMain
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                    int nCmdShow )
{
    MSGmsg;

    DDInit();
    while( lpCmdLine[0] == '-' || lpCmdLine[0] == '/')
    {
        lpCmdLine++;

        switch (*lpCmdLine++)
        {
        case 'e':
            bUseEmulation = TRUE;
            break;
        case 'w':
            bFullscreen = FALSE;
            break;
        case 'f':
            bFullscreen = TRUE;
            break;
        case '1':
            CmdLineBufferCount = 1;
            break;
        case '2':
        case 'd':
            CmdLineBufferCount = 2;
            break;
        case '3':
            CmdLineBufferCount = 3;
            break;
        case 's':
            bStretch = TRUE;
            break;
        case 'S':
            bWantSound = FALSE;
            break;
        case 'x':
            bStress= TRUE;
            break;
        case '?':
            bHelp= TRUE;
            bFullscreen= FALSE;  // give help in windowed mode
            break;
        }

        while( IS_SPACE(*lpCmdLine) )
        {
            lpCmdLine++;
        }
    }

    GameMode.cx = getint(&lpCmdLine, 640);
    GameMode.cy = getint(&lpCmdLine, 480);
    GameBPP = getint(&lpCmdLine, 8);

    /*
     * create window and other windows things
     */
    if( !initApplication(hInstance, nCmdShow) )
    {
        return FALSE;
    }

    /*
     * Give user help if asked for
     *
     * This is ugly for now because the whole screen is black
     * except for the popup box.  This could be fixed with some
     * work to get the window size right when it was created instead
     * of delaying that work. see ddraw.c
     *
     */

    if( bHelp )
    {
        MessageBox(hWndMain,
                   "F12 - Quit\n"
                   "NUMPAD 2  - crouch\n"
                   "NUMPAD 3  - apple\n"
                   "NUMPAD 4  - right\n"
                   "NUMPAD 5  - stop\n"
                   "NUMPAD 6  - left\n"
                   "NUMPAD 7  - jump\n"
                   "\n"
                   "Command line parameters\n"
                   "\n"
                    "-e   Use emulator\n"
                    "-S   No Sound\n"
                    "-1   No backbuffer\n"
                    "-2   One backbuffer\n"
                    "-4   Three backbuffers\n"
                    "-s   Use stretch\n"
                    "-x   Demo or stress mode\n",
                   OUR_APP_NAME, MB_OK );
    }

    /*
     * initialize for game play
     */

    if( !InitGame() )
    {
        return FALSE;
    }
    dwFrameTime = timeGetTime();

    while( 1 )
    {
        if (PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!GetMessage( &msg, NULL, 0, 0))
            {
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if (!bPaused && (bIsActive || !bFullscreen))
        {
            ProcessFox(lastInput);
            lastInput=0;
        }
        else
        {
            WaitMessage();
        }
    }

    if (AveFrameRateCount)
    {
        AveFrameRate = AveFrameRate / AveFrameRateCount;
        Msg("Average frame rate: %d", AveFrameRate);
    }

    return msg.wParam;

} /* WinMain */

#ifdef DEBUG

/*
 * Msg
 */
void __cdecl Msg( LPSTR fmt, ... )
{
    char    buff[256];
    va_list  va;

    va_start(va, fmt);

    //
    // format message with header
    //

    lstrcpy( buff, "FOXBEAR:" );
    wvsprintf( &buff[lstrlen(buff)], fmt, va );
    lstrcat( buff, "\r\n" );

    //
    // To the debugger unless we need to be quiet
    //

    if( !bStress )
    {
        OutputDebugString( buff );
    }

} /* Msg */

#endif


FOXBEAR.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:foxbear.h
 *  Content:main include file
 *
 ***************************************************************************/
#ifndef __FOXBEAR_INCLUDED__
#define __FOXBEAR_INCLUDED__

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <dsound.h>
#include "gfx.h"
#include "fbsound.h"
#include "gameproc.h"
#include "fastfile.h"
#include "dsutil.h"

int getint(char**p, int def);

#define QUOTE(x) #x
#define QQUOTE(y) QUOTE(y)
#define REMIND(str) __FILE__ "(" QQUOTE(__LINE__) "):" str

/*
 * keyboard commands
 */
enum
{
    KEY_STOP = 1,
    KEY_DOWN,
    KEY_LEFT,
    KEY_RIGHT,
    KEY_UP,
    KEY_JUMP,
    KEY_THROW
};

/*
 * global data
 */
extern LPDIRECTDRAWlpDD;
extern LPDIRECTDRAW2lpDD2;
extern LPDIRECTDRAWSURFACElpFrontBuffer;
extern LPDIRECTDRAWSURFACElpBackBuffer;
extern LPDIRECTDRAWSURFACE      lpStretchBuffer;
extern LPDIRECTDRAWCLIPPER      lpClipper;
extern DWORDlastKey;
extern BOOL                     bModeX;     // we are in a modex mode
extern BOOL                     bColorFill;     // device supports color fill
extern BOOL                     bTransDest;     // we should use dest color key
extern BOOL                     bColorFill;     // device supports color fill
extern int                      nBufferCount;   // buffer count
extern int                      CmdLineBufferCount;   // buffer count
extern BOOL                     bStretch;       // stretch
extern BOOL                     bFullscreen;    // run in fullscreen mode
extern BOOL                     bStress;        // just keep running
extern BOOL                     bUseEmulation;  // dont use HW use SW
extern RECT                     GameRect;       // game is here
extern SIZE                     GameSize;       // game is this size
extern SIZE                     GameMode;       // display mode size
extern UINT                     GameBPP;        // the bpp we want
extern DWORD                    dwColorKey;     // the color key
extern HWND                     hWndMain;       // the foxbear window
extern RECT                     rcWindow;       // where the FoxBear window is.
extern BOOL                     bIsActive;      // we are the active app.
extern BOOL                     bPaused;        //
extern BOOLbWantSound;     // Set the default action in DSEnable

/*
 * list of display modes
 */
struct {int w,
            h,
            bpp;
        DWORD dwFlags;
} ModeList[100];

int NumModes;

/*
 * map a point that assumes 640x480 to the current game size.
 */
#define MapDX(x) (((x) * GameSize.cx) / C_SCREEN_W)
#define MapDY(y) (((y) * GameSize.cy) / C_SCREEN_H)
#define MapX(x)  (GameRect.left + MapDX(x))
#define MapY(y)  (GameRect.top  + MapDY(y))
#define MapRX(x) ((GameSize.cx == C_SCREEN_W) ? x : MapDX(x)+1)
#define MapRY(y) ((GameSize.cy == C_SCREEN_H) ? y : MapDY(y)+1)

void PauseGame(void);
void UnPauseGame(void);

/*
 * fn prototypes
 */
/* ddraw.c */
extern BOOL DDInit( void );
extern BOOL DDEnable( void );
extern BOOL DDDisable( BOOL );
extern LPDIRECTDRAWSURFACE DDCreateSurface( DWORD width, DWORD height, BOOL sysmem, BOOL trans );
extern BOOL DDCreateFlippingSurface( void );
extern BOOL DDClear( void );
extern DWORD DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb);
extern void Splash( void );

extern LPVOID CMemAlloc( UINT cnt, UINT isize );
extern LPVOID MemAlloc( UINT size );
extern void   MemFree( LPVOID ptr );

extern void __cdecl Msg( LPSTR fmt, ... );

LPDIRECTDRAWPALETTE ReadPalFile( char *fname );

#endif


GAMEPROC.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:gameproc.c
 *  Content:Game processing routines
 *
 ***************************************************************************/
#include "foxbear.h"

GFX_HBMhBuffer;
HBITMAPLIST*hBitmapList;
HBITMAPLIST*hTileList;
HPOSLIST*hForePosList;
HPOSLIST*hMidPosList;
HPOSLIST*hBackPosList;
HSURFACELIST*hSurfaceList;
HPLANE*hForeground;
HPLANE*hMidground;
HPLANE*hBackground;
HSPRITE*hFox;
HSPRITE*hBear;
HSPRITE*hApple;
USHORTchewCount;
LONGchewDif;

/*
 * ErrorMessage
 */
void ErrorMessage( CHAR *pText )
{
    char ach[128];

    wsprintf( ach, "FOXBEAR FATAL ERROR: %s\r\n", pText );
    OutputDebugString(ach);
    gfxEnd( hBuffer );
    exit( 0 );

} /* ErrorMessage */

/*
 * InitBuffer
 */
BOOL InitBuffer( GFX_HBM *hBuffer )
{
    *hBuffer = gfxBegin();

    if( *hBuffer == NULL )
    {
        ErrorMessage( "gfxBegin failed" );
        return FALSE;
    }
    return TRUE;

} /* InitBuffer */

/*
 * DestroyBuffer
 */
void DestroyBuffer ( GFX_HBM hBuffer )
{
    if( gfxEnd( hBuffer ) == FALSE )
    {
        ErrorMessage( "gfxEnd in DestroyBuffer" );
    }

} /* DestroyBuffer */

/*
 * LoadBitmaps
 */
HBITMAPLIST *LoadBitmaps( void )
{
    HBITMAPLIST *hBitmapList;
    CHAR fileName[32];
    USHORT i;
    USHORT n;
    BOOL     bInitDone = TRUE;

    if( !FastFileInit( "foxbear.art", 5 ) )
    {
        Msg( "Could not load art file err=%08lX" , GetLastError());
        IDirectDraw_FlipToGDISurface(lpDD);
        MessageBox(hWndMain,"Could not load art file","Error",MB_OK);
        return NULL;
    }
    hBitmapList = CMemAlloc( C_TILETOTAL + C_FBT + C_BBT, sizeof (HBITMAPLIST) );

    Msg( "Loading tiles" );
    for( i = 0; i < C_TILETOTAL; ++i )
    {
        wsprintf( fileName, "%03u.BMP", i + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n = C_TILETOTAL;

    Msg( "Loading FoxWalk" );
    for( i = n; i < n + C_FOXWALK; ++i )
    {
        wsprintf( fileName, "FW%02uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXWALK;

    Msg( "Loading FoxWalk2" );
    for( i = n; i < n + C_FOXWALK; ++i )
    {
        wsprintf( fileName, "FW%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXWALK;

    Msg( "Loading FoxRun" );
    for( i = n; i < n + C_FOXRUN; ++i )
    {
        wsprintf( fileName, "FR%02uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXRUN;

    Msg( "Loading FoxRun2" );
    for( i = n; i < n + C_FOXRUN; ++i )
    {
        wsprintf( fileName, "FR%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXRUN;

    Msg( "Loading FoxStill" );
    for( i = n; i < n + C_FOXSTILL; ++i )
    {
        wsprintf( fileName, "FS%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTILL;

    Msg( "Loading FoxStill2" );
    for( i = n; i < n + C_FOXSTILL; ++i )
    {
        wsprintf( fileName, "FS%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTILL;

    Msg( "Loading FoxStunned" );
    for( i = n; i < n + C_FOXSTUNNED; ++i )
    {
        wsprintf( fileName, "FK%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTUNNED;

    Msg( "Loading FoxStunned2" );
    for( i = n; i < n + C_FOXSTUNNED; ++i )
    {
        wsprintf( fileName, "FK%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTUNNED;

    Msg( "Loading FoxCrouch" );
    for( i = n; i < n + C_FOXCROUCH; ++i )
    {
        wsprintf( fileName, "FC%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXCROUCH;

    Msg( "Loading FoxCrouch2" );
    for( i = n; i < n + C_FOXCROUCH; ++i )
    {
        wsprintf( fileName, "FC%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXCROUCH;

    Msg( "Loading FoxStop" );
    for( i = n; i < n + C_FOXSTOP; ++i )
    {
        wsprintf( fileName, "FCD%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTOP;

    Msg( "Loading FoxStop2" );
    for( i = n; i < n + C_FOXSTOP; ++i )
    {
        wsprintf( fileName, "FCD%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXSTOP;

    Msg( "Loading FoxThrow" );
    for( i = n; i < n + C_FOXTHROW; ++i )
    {
        wsprintf( fileName, "FT%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXTHROW;

    Msg( "Loading FoxThrow2" );
    for( i = n; i < n + C_FOXTHROW; ++i )
    {
        wsprintf( fileName, "FT%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXTHROW;

    Msg( "Loading FoxJumpThrow" );
    for( i = n; i < n + C_FOXJUMPTHROW; ++i )
    {
        wsprintf( fileName, "FJT%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXJUMPTHROW;

    Msg( "Loading FoxJumpThrow2" );
    for( i = n; i < n + C_FOXJUMPTHROW; ++i )
    {
        wsprintf( fileName, "FJT%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXJUMPTHROW;

    Msg( "Loading FoxJump" );
    for( i = n; i < n + C_FOXJUMP; ++i )
    {
        wsprintf( fileName, "FJ%1uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXJUMP;

    Msg( "Loading FoxJump2" );
    for( i = n; i < n + C_FOXJUMP; ++i )
    {
        wsprintf( fileName, "FJ%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXJUMP;

    Msg( "Loading FoxCrouchWalk" );
    for( i = n; i < n + C_FOXCROUCHWALK; ++i )
    {
        wsprintf( fileName, "FCW%02uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXCROUCHWALK;

    Msg( "Loading FoxCrouchWalk2" );
    for( i = n; i < n + C_FOXCROUCHWALK; ++i )
    {
        wsprintf( fileName, "FCW%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXCROUCHWALK;

    Msg( "Loading FoxBlurr" );
    for( i = n; i < n + C_FOXBLURR; ++i )
    {
        wsprintf( fileName, "FB%02uR.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXBLURR;

    Msg( "Loading FoxBlurr2" );
    for( i = n; i < n + C_FOXBLURR; ++i )
    {
        wsprintf( fileName, "FB%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_FOXBLURR;

    Msg( "Loading BearMiss" );
    for( i = n; i < n + C_BEARMISS; ++i )
    {
        wsprintf( fileName, "BM%1uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_BEARMISS;

    Msg( "Loading BearStrike" );
    for( i = n; i < n + C_BEARSTRIKE; ++i )
    {
        wsprintf( fileName, "BS%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_BEARSTRIKE;

    Msg( "Loading BearWalk" );
    for( i = n; i < n + C_BEARWALK; ++i )
    {
        wsprintf( fileName, "BW%02uL.BMP", i - n + 1 );
        hBitmapList[i].hBM = gfxLoadBitmap( fileName );
        if(hBitmapList[i].hBM == NULL)
            bInitDone = FALSE;
    }
    n += C_BEARWALK;

    FastFileFini();

    if(bInitDone == FALSE)
    {
        Msg("Error while loading Bitmaps");
        return NULL;
    }

    return hBitmapList;

} /* LoadBitmaps */

/*
 * InitTiles
 */
void InitTiles(
              HBITMAPLIST **hTileList,
              HBITMAPLIST *hBitmapList,
              USHORT tileCount )
{
    *hTileList = CreateTiles( hBitmapList, tileCount );

} /* InitTiles */

/*
 * InitPlane
 */
void InitPlane(
  HPLANE **hPlane,
              HPOSLIST **hPosList,
 LPSTR szFileName,
              USHORT width,
              USHORT height,
              USHORT denom )
{
    *hPlane   = CreatePlane( width, height, denom );
    *hPosList = CreatePosList( szFileName, width, height );

} /* InitPlane */

/*
 * InitSurface
 */
void InitSurface(
        HSURFACELIST **pphSurfaceList,
        CHAR *szFileName,
        USHORT width,
        USHORT height )
{
    *pphSurfaceList = CreateSurfaceList( szFileName, width, height );

} /* InitSurface */

/*
 * InitFox
 */
void InitFox ( HSPRITE **pphFox, HBITMAPLIST *phBitmapList )
{
    GFX_HBM   hBM;
    GFX_HBM   hBM_src;
    ACTION    action;
    DIRECTION direction;
    USHORT    i;

    LONG      startX= C_FOX_STARTX;
    LONG      startY= C_FOX_STARTY;
    USHORT    boundW= 108;
    USHORT    boundH= 105;
    LONG      as=   6;
    SHORT     x[C_FBT]= {  7, 15, 18, 11,  6, 3,  7, 15, 17, 11,  6, 3,
                         7, 15, 18, 11,  6, 3,  7, 15, 17, 11,  6, 3,
                        10, 3,  5, 16,  9, 13, 31, 24,  9, 3,  5, 16, 10, 13, 33, 23,
                        10, 3,  5, 16,  9, 13, 31, 24,  9, 3,  5, 16, 10, 13, 33, 23,
                        11, 11, 31, 31,  7, 7, 27, 27,  8, 10,  8, 10,
                        26, 6, 26, 6, 17, 21, 21, 24, 17, 21, 21, 24,
                         1, 0,  0, 1,  0, 0,  1, 0,  0, 1,  1, 1,
                         1, 0,  0, 1,  0, 0,  1, 0,  0, 1,  1, 1,
                         2, 2, -1, 0,  2, 2, -1, 0 };
    SHORT     y[C_FBT]= { 20, 24, 26, 25, 27, 19, 20, 25, 26, 25, 29, 21,
                        20, 24, 26, 25, 27, 19, 20, 25, 26, 25, 29, 21,
                        42, 42, 31, 19, 13, 11, 20, 33, 40, 43, 31, 19, 14, 12, 20, 33,
                        42, 42, 31, 19, 13, 11, 20, 33, 40, 43, 31, 19, 14, 12, 20, 33,
                        14, 14, 20, 20, 58, 58, 26, 26, 20, 24, 20, 24,
                         0, 9,  0, 9, 20, 11, 10, 9, 20, 11, 10, 9,
                        61, 61, 61, 61, 60, 60, 61, 61, 61, 61, 60, 60,
                        61, 61, 61, 61, 60, 60, 61, 61, 61, 61, 60, 60,
                        45, 45, 45, 45, 45, 45, 45, 45 };
    USHORT    w[C_FBT]= { 75, 73, 73, 82, 92, 84, 74, 74, 73, 81, 91, 84,
                        75, 73, 73, 82, 92, 84, 74, 74, 73, 81, 91, 84,
                        88, 92, 88, 78, 80, 78, 70, 84, 88, 92, 88, 78, 79, 79, 68, 85,
                        88, 92, 88, 78, 80, 78, 70, 84, 88, 92, 88, 78, 79, 79, 68, 85,
                        65, 65, 61, 61, 88, 88, 72, 72, 57, 86, 57, 86,
                        54, 92, 54, 92, 59, 57, 57, 52, 59, 57, 57, 52,
                        98, 99, 99, 99,100,100, 98,101,100, 99,100, 98,
                        98, 99, 99, 99,100,100, 98,101,100, 99,100, 98,
                        94, 94, 97, 96, 94, 94, 97, 96 };
    USHORT    h[C_FBT]= { 78, 74, 72, 73, 71, 79, 78, 73, 72, 73, 69, 77,
                        78, 74, 72, 73, 71, 79, 78, 73, 72, 73, 69, 77,
                        56, 56, 67, 79, 85, 87, 78, 65, 58, 55, 67, 79, 84, 86, 78, 65,
                        56, 56, 67, 79, 85, 87, 78, 65, 58, 55, 67, 79, 84, 86, 78, 65,
                        84, 84, 85, 85, 40, 40, 72, 72, 78, 74, 78, 74,
                        88, 82, 88, 82, 84, 87, 86, 85, 84, 87, 86, 85,
                        37, 37, 37, 37, 38, 38, 37, 37, 37, 37, 38, 38,
                        37, 37, 37, 37, 38, 38, 37, 37, 37, 37, 38, 38,
                        54, 53, 51, 54, 54, 53, 51, 54 };

    *pphFox = CreateSprite( C_FBT, startX, startY, boundW, boundH, C_FORE_W * C_TILE_W, C_FORE_H * C_TILE_H, (SHORT) as, TRUE );

    for( i = 0; i < C_FBT; ++i )
    {
        hBM_src = phBitmapList[i + C_TILETOTAL].hBM;

        if( i < 12 )
        {
            action = WALK;
            direction = RIGHT;
        }
        else if( (i >= 12) && (i < 24) )
        {
            action = WALK;
            direction = LEFT;
        }
        else if( (i >= 24) && (i < 40) )
        {
            action = RUN;
            direction = RIGHT;
        }
        else if( (i >= 40) && (i < 56) )
        {
            action = RUN;
            direction = LEFT;
        }
        else if( i == 56 )
        {
            action = STILL;
            direction = RIGHT;
        }
        else if( i == 57 )
        {
            action = STILL;
            direction = LEFT;
        }
        else if( i == 58 )
        {
            action = STUNNED;
            direction = RIGHT;
        }
        else if( i == 59 )
        {
            action = STUNNED;
            direction = LEFT;
        }
        else if( i == 60 )
        {
            action = CROUCH;
            direction = RIGHT;
        }
        else if( i == 61 )
        {
            action = CROUCH;
            direction = LEFT;
        }
        else if( i == 62 )
        {
            action = STOP;
            direction = RIGHT;
        }
        else if( i == 63 )
        {
            action = STOP;
            direction = LEFT;
        }
        else if( (i >= 64) && (i < 66) )
        {
            action = THROW;
            direction = RIGHT;
        }
        else if( (i >= 66) && (i < 68) )
        {
            action = THROW;
            direction = LEFT;
        }
        else if( (i >= 68) && (i < 70) )
        {
            action = JUMPTHROW;
            direction = RIGHT;
        }
        else if( (i >= 70) && (i < 72) )
        {
            action = JUMPTHROW;
            direction = LEFT;
        }
        else if( (i >= 72) && (i < 76) )
        {
            action = JUMP;
            direction = RIGHT;
        }
        else if( (i >= 76) && (i < 80) )
        {
            action = JUMP;
            direction = LEFT;
        }
        else if( (i >= 80) && (i < 92) )
        {
            action = CROUCHWALK;
            direction = RIGHT;
        }
        else if( (i >= 92) && (i < 104) )
        {
            action = CROUCHWALK;
            direction = LEFT;
        }
        else if( (i >= 104) && (i < 108) )
        {
            action = BLURR;
            direction = RIGHT;
        }
        else if( (i >= 108) && (i < 112) )
        {
            action = BLURR;
            direction = LEFT;
        }

        hBM = hBM_src;

        BitBltSprite(
            *pphFox,
            hBM,
            action,
            direction,
            x[i],
            y[i],
            w[i],
            h[i] );
    }
    SetSpriteAction( *pphFox, STILL, RIGHT );

} /* InitFox */

/*
 * InitBear
 */
void InitBear( HSPRITE **pphBear, HBITMAPLIST *phBitmapList )
{
    GFX_HBM   hBM_src;
    ACTION    action;
    DIRECTION direction;
    USHORT    i;

    LONG      startX= C_BEAR_STARTX;
    LONG      startY= C_BEAR_STARTY;
    USHORT    boundW= 196;
    USHORT    boundH=  88;
    LONG      as=   6;
    USHORT    x[C_BBT]= { 14, 10,
                         8, 12, 13, 14, 10, 10,  9, 9,  9, 9,  8, 9,
                        11, 6,  1, 0,  3, 13, 11, 7,  1, 1,  3, 14 };
    USHORT    y[C_BBT]= {  7, 7,
                         3, 8,  9, 7,  7, 3,  3, 3,  3, 3,  3, 3,
                         1, 1,  2, 2,  3, 1,  0, 1,  1, 2,  3, 2 };
    USHORT    w[C_BBT]= {127,129,
                       127,153,183,153,129,138,146,150,152,151,143,139,
                       131,136,140,141,136,125,131,135,140,140,136,126 };
    USHORT    h[C_BBT]= { 80, 80,
                        84, 79, 78, 80, 80, 84, 84, 84, 84, 84, 84, 84,
                        86, 86, 86, 85, 84, 86, 87, 86, 87, 85, 84, 86 };

    *pphBear = CreateSprite( C_BBT, startX, startY, boundW, boundH, C_FORE_W * C_TILE_W, C_FORE_H * C_TILE_H, (SHORT) as, TRUE );

    for( i = 0; i < C_BBT; ++i )
    {
        hBM_src = phBitmapList[i + C_TILETOTAL + C_FBT].hBM;

        if( i < 2 )
        {
            action = MISS;
            direction = LEFT;
        }
        else if( (i >= 2) && (i < 8) )
        {
            action = STRIKE;
            direction = LEFT;
        }
        else if( (i >= 8) && (i < 14) )
        {
            action = CHEW;
            direction = LEFT;
        }
        else if( (i >= 14) && (i < 26) )
        {
            action = WALK;
            direction = LEFT;
        }

        BitBltSprite (
            *pphBear,
            hBM_src,
            action,
            direction,
            x[i],
            y[i],
            w[i],
            h[i] );
    }

    SetSpriteAction( *pphBear, WALK, LEFT );
    SetSpriteVelX( *pphBear, -C_BEAR_WALKMOVE, P_ABSOLUTE );
    SetSpriteSwitch( *pphBear, C_BEAR_WALKSWITCH, P_ABSOLUTE );

} /* InitBear */

/*
 * InitApple
 */
VOID InitApple ( HSPRITE **pphApple, HBITMAPLIST *phBitmapList )
{
    *pphApple = CreateSprite( 1, 50 * C_UNIT, 390 * C_UNIT, 32, 32, C_FORE_W * C_TILE_W, C_FORE_H * C_TILE_H, 0, FALSE );

    BitBltSprite( *pphApple, phBitmapList[61].hBM, NONE, RIGHT, 0, 0, 32, 32 );

    SetSpriteAction( *pphApple, NONE, RIGHT );

} /* InitApple */

/*
 * PreInitializeGame
 */
BOOL PreInitializeGame( void )
{
    return InitBuffer( &hBuffer);

} /* PreInitializeGame */

/*
 * InitializeGame
 */
BOOL InitializeGame ( void )
{
    Splash();

    hBitmapList = LoadBitmaps();
    if( hBitmapList == NULL )
    {
        return FALSE;
    }

    InitTiles( &hTileList, hBitmapList, C_TILETOTAL );

    InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE_H, C_FORE_DENOM );
    TilePlane( hForeground, hTileList, hForePosList );

    InitPlane( &hMidground, &hMidPosList, "MIDLIST", C_MID_W, C_MID_H, C_MID_DENOM );
    TilePlane( hMidground, hTileList, hMidPosList );

    InitPlane( &hBackground, &hBackPosList, "BACKLIST", C_BACK_W, C_BACK_H, C_BACK_DENOM );
    TilePlane( hBackground, hTileList, hBackPosList );

    InitSurface( &hSurfaceList, "SURFLIST", C_FORE_W, C_FORE_H );
    SurfacePlane( hForeground, hSurfaceList );

    InitFox( &hFox, hBitmapList );
    InitBear( &hBear, hBitmapList );
    InitApple( &hApple, hBitmapList );

    DDClear();      // clear all the backbuffers.

    return TRUE;

} /* InitializeGame */

extern void DisplayFrameRate( void );

/*
 * NewGameFrame
 */
int NewGameFrame( void )
{

    SetSpriteX( hFox, 0, P_AUTOMATIC );
    SetSpriteY( hFox, 0, P_AUTOMATIC );

    SetPlaneVelX( hBackground, GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hMidground,  GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hForeground, GetSpriteVelX(hFox), P_ABSOLUTE );

    SetPlaneX( hBackground, 0, P_AUTOMATIC );
    SetPlaneX( hMidground,  0, P_AUTOMATIC );
    SetPlaneX( hForeground, 0, P_AUTOMATIC );

    SetSpriteX( hBear,0, P_AUTOMATIC );
    SetSpriteX( hApple, 0, P_AUTOMATIC );
    SetSpriteY( hApple, 0, P_AUTOMATIC );

    /*
     * once all sprites are processed, display them
     *
     * If we are using destination transparency instead of source
     * transparency, we need to paint the background with the color key
     * and then paint our sprites and planes in reverse order.
     *
     * Since destination transparency will allow you to only write pixels
     * on the destination if the transparent color is present, reversing
     * the order (so that the topmost bitmaps are drawn first instead of
     * list) causes everything to come out ok.
     */
    if( bTransDest )
    {
        gfxFillBack( dwColorKey );

        DisplayFrameRate();

        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );

        DisplayPlane( hBuffer, hForeground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hBackground );
    }
    else
    {
        DisplayPlane( hBuffer, hBackground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hForeground );

        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );

        DisplayFrameRate();
    }

    gfxSwapBuffers();

    return 0;

} /* NewGameFrame */

/*
 * DestroyGame
 */
void DestroyGame()
{
    if (hBuffer)
    {
        DestroyTiles( hTileList );
        DestroyPlane( hForeground );
        DestroyPlane( hMidground );
        DestroyPlane( hBackground );
        DestroyBuffer( hBuffer );
        DestroySound();

        hTileList   = NULL;
        hForeground = NULL;
        hMidground  = NULL;
        hBackground = NULL;
        hBuffer     = NULL;
    }

} /* DestroyGame */

/*
 * ProcessInput
 */
BOOL ProcessInput( SHORT input )
{
    static BOOL fBearPlaying = FALSE;
    LONG      foxSpeedX;
    LONG      foxSpeedY;
    LONG      foxX;
    LONG      foxY;
    LONG      bearX;
    LONG      bearY;
    LONG      appleX;
    LONG      appleY;
    ACTION    foxAction;
    DIRECTION foxDir;
    BOOL      cont = TRUE;

    foxSpeedX = GetSpriteVelX( hFox );
    foxAction = GetSpriteAction( hFox );
    foxDir    = GetSpriteDirection( hFox );

    if( (GetSpriteActive(hFox) == FALSE) && (input != 4209) )
    {
        input = 0;
    }
    switch( input )
    {
    case KEY_DOWN:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxAction == STILL )
        {
            SetSpriteAction( hFox, CROUCH, SAME );
        }
        else if( foxAction == WALK )
        {
            SetSpriteAction( hFox, CROUCHWALK, SAME );
        }
        break;

    case KEY_LEFT:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxSpeedX == 0 )
        {
            if( foxAction == STILL )
            {
              if( foxDir == RIGHT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, WALK, LEFT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else if( foxAction == CROUCH )
            {
              if( foxDir == RIGHT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, CROUCHWALK, LEFT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else
            {
              SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
            }
        } else {
            SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
        }
        break;

    case KEY_RIGHT:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxSpeedX == 0 )
        {
            if( foxAction == STILL )
            {
              if( foxDir == LEFT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, WALK, RIGHT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else if( foxAction == CROUCH )
            {
              if( foxDir == LEFT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, CROUCHWALK, RIGHT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else
            {
              SetSpriteVelX( hFox, C_FOX_XMOVE, P_RELATIVE );
            }
        }
        else
        {
            SetSpriteVelX( hFox, C_FOX_XMOVE, P_RELATIVE );
        }
        break;

    case KEY_STOP:
        if( foxAction == STOP )
        {
            break;
        }
        else if( (foxAction == RUN) || (foxAction == BLURR) )

{
            SetSpriteAction( hFox, STOP, SAME );
            SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STOP );
        } else {
            SetSpriteVelX( hFox, 0, P_ABSOLUTE );
        }
        break;

    case KEY_UP:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxAction == CROUCH )
        {
            SetSpriteAction( hFox, STILL, SAME );
        }
        else if( foxAction == CROUCHWALK )
        {
            SetSpriteAction( hFox, WALK, SAME );
        }
        break;

    case KEY_JUMP:
        if( foxAction == STOP )
        {
            break;
        }
        else
        if( (foxAction == STILL) || (foxAction == WALK) ||
            (foxAction == RUN) || (foxAction == CROUCH) ||
            (foxAction == CROUCHWALK) )
        {
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitchType( hFox, TIME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
            SetSpriteVelY( hFox, -C_FOX_JUMPMOVE, P_ABSOLUTE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SoundPlayEffect( SOUND_JUMP );
        }
        break;

    case KEY_THROW:
        if( foxAction == STOP )
        {
            break;
        }
        else if( (foxAction == STILL) || (foxAction == WALK) ||
               (foxAction == RUN) || (foxAction == CROUCH) ||
               (foxAction == CROUCHWALK) )
        {
            SetSpriteAction( hFox, THROW, SAME );
            SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
            SetSpriteVelX( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hFox, TIME );
        }
        else if( foxAction == JUMP )
        {
            SetSpriteAccY( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
            SetSpriteAction( hFox, JUMPTHROW, SAME );
            SetSpriteVelY( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SetSpriteSwitchForward( hFox, TRUE );
        }
        break;

    default:
        break;
    }

    /*
     * Fox actions follow...
     */
    if( GetSpriteActive(hFox) == FALSE )
    {
        goto bearActions;
    }

    if( abs(GetSpriteVelX( hFox )) < C_FOX_XMOVE )
    {
        SetSpriteVelX( hFox, 0, P_ABSOLUTE );
    }

    foxAction = GetSpriteAction( hFox );

    if( GetSpriteVelY(hFox) == 0 )
    {
        if( GetSurface( hForeground, hFox ) == FALSE )
        {
            if( (foxAction == WALK) || (foxAction == RUN) ||
            (foxAction == CROUCHWALK) )
            {
              SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            }
            else if( foxAction == STOP )
            {
              SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
              SetSpriteAccX( hFox, 0, P_ABSOLUTE );
            }
        }
    }
    else if( GetSpriteVelY(hFox) > 2 * C_UNIT )
    {
        if( (foxAction == WALK) || (foxAction == RUN) ||
            (foxAction == CROUCHWALK) )
        {
            SetSpriteSwitchForward( hFox, FALSE );
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitchType( hFox, TIME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
        }
        if( foxAction == STOP )
        {
            SetSpriteAction( hFox, STUNNED, SAME );
            SetSpriteAccX( hFox, -GetSpriteVelX(hFox) / 25, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STUNNED );
        }
    }

    foxSpeedX = GetSpriteVelX( hFox );
    foxSpeedY = GetSpriteVelY( hFox );
    foxAction = GetSpriteAction( hFox );
    foxDir    = GetSpriteDirection( hFox );

    switch( foxAction ) {
    case STUNNED:
        if( (GetSpriteVelY(hFox) >= 0) &&
            (!GetSurface( hForeground, hFox ) == FALSE) )
        {
            SetSpriteAccY( hFox, 0, P_ABSOLUTE );
            SetSpriteAction( hFox, STOP, SAME );
            SetSpriteVelY( hFox, 0, P_ABSOLUTE );
            SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
            // SetSurface( hForeground, hFox );
            SoundPlayEffect( SOUND_STOP );
        }
        break;

    case CROUCHWALK:
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, CROUCH, SAME );
        }
        else if( foxSpeedX > C_FOX_WALKMOVE )
        {
            SetSpriteVelX( hFox, C_FOX_WALKMOVE, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_WALKMOVE )
        {
            SetSpriteVelX( hFox, -C_FOX_WALKMOVE, P_ABSOLUTE );
        }
        break;

    case STOP:
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, STILL, SAME );
            SetSpriteAccX( hFox, 0, P_ABSOLUTE );
        }
        break;

    case RUN:
        if( (foxSpeedX < C_FOX_WALKTORUN ) && (foxSpeedX > 0) )
        {
            SetSpriteAction( hFox, WALK, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX > C_FOX_RUNTOBLURR )
        {
            SetSpriteAction( hFox, BLURR, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
        }
        else if( (foxSpeedX > -C_FOX_WALKTORUN ) && (foxSpeedX < 0) )
        {
            SetSpriteAction( hFox, WALK, LEFT );
            SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_RUNTOBLURR )
        {
            SetSpriteAction( hFox, BLURR, LEFT );
            SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
        }
        break;

    case WALK:
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, STILL, SAME );
        }
        else if( foxSpeedX > C_FOX_WALKTORUN )
        {
            SetSpriteAction( hFox, RUN, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_WALKTORUN )
        {
            SetSpriteAction( hFox, RUN, LEFT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        break;

    case BLURR:
        if( (foxSpeedX < C_FOX_RUNTOBLURR ) && (foxSpeedX > C_FOX_WALKTORUN) )
        {
            SetSpriteAction( hFox, RUN, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        else if( (foxSpeedX > -C_FOX_RUNTOBLURR ) && (foxSpeedX < -C_FOX_WALKTORUN) )
        {
            SetSpriteAction( hFox, RUN, LEFT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        break;

    case JUMPTHROW:
        if( !GetSpriteSwitchDone(hFox) == FALSE )
        {
            SetSpriteSwitchForward( hFox, FALSE );
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SoundPlayEffect( SOUND_THROW );
        }
        else
        if( (GetSpriteBitmap(hFox) == 1) &&
            (GetSpriteDirection(hFox) == RIGHT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == LEFT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 15 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        break;

    case THROW:
        if( !GetSpriteSwitchDone(hFox) == FALSE )
        {
            SetSpriteAction( hFox, STILL, SAME );
            SetSpriteSwitchType( hFox, HOR );
            SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SoundPlayEffect( SOUND_THROW );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == RIGHT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == LEFT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 20 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        break;

    case JUMP:
        if( (foxSpeedY >= 0) && (!GetSpriteSwitchForward( hFox ) == FALSE) )
        {
            SetSpriteSwitchForward( hFox, FALSE );
        }
        else if( GetSpriteSwitchForward( hFox ) == FALSE )
        {
            if( (!GetSurface( hForeground, hFox ) == FALSE) ||
            (!GetSurface( hForeground, hFox ) == FALSE) )
            {
              if( foxSpeedX >= C_FOX_RUNMOVE )
              {
                  SetSpriteAction( hFox, RUN, SAME );
                  SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
              }
              else if( foxSpeedX == 0 )
              {
                  SetSpriteAction( hFox, STILL, SAME );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, WALK, SAME );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
              }

              SetSpriteAccY( hFox, 0, P_ABSOLUTE );
              SetSpriteVelY( hFox, 0, P_ABSOLUTE );
              SetSpriteSwitchType( hFox, HOR );
              SetSpriteSwitchForward( hFox, TRUE );
//SetSurface( hForeground, hFox );
              SetSpriteSwitchDone( hFox, FALSE );
            }
        }
        break;

    }

    /*
     * Bear Actions
     */
    bearActions:

    foxX   = GetSpriteX( hFox );
    foxY   = GetSpriteY( hFox );
    bearX  = GetSpriteX( hBear );
    bearY  = GetSpriteY( hBear );
    appleX = GetSpriteX( hApple );
    appleY = GetSpriteY( hApple );

    switch( GetSpriteAction( hBear ) ) {
    case STRIKE:
        if( GetSpriteBitmap( hBear ) == 2 )
        {
            if( (bearX > foxX - C_UNIT * 30) && (bearX < foxX + C_UNIT * 40) &&
                (bearY < foxY + C_UNIT * 60) )
            {
              SetSpriteActive( hFox, FALSE );
                if( !fBearPlaying )
                {
                    SoundPlayEffect( SOUND_BEARSTRIKE );
                    fBearPlaying = TRUE;
                }
            }
            else
            {
              SetSpriteAction( hBear, MISS, SAME );
              SetSpriteSwitch( hBear, C_BEAR_MISSSWITCH, P_ABSOLUTE );
              SetSpriteSwitchDone( hBear, FALSE );
            }
        }
        else if( !GetSpriteSwitchDone( hBear ) == FALSE )
        {
            SetSpriteAction( hBear, CHEW, SAME );
            SetSpriteSwitchDone( hBear, FALSE );
            chewCount = 0;
            fBearPlaying = FALSE;
        }
        break;

    case MISS:
        if( !fBearPlaying )
        {
            SoundPlayEffect( SOUND_BEARMISS );
            fBearPlaying = TRUE;
        }
        if( !GetSpriteSwitchDone( hBear ) == FALSE )
        {
            SetSpriteAction( hBear, WALK, SAME );
            SetSpriteVelX( hBear, -C_BEAR_WALKMOVE, P_ABSOLUTE );
            SetSpriteSwitch( hBear, C_BEAR_WALKSWITCH, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, HOR );
            fBearPlaying = FALSE;
        }
        break;

    case WALK:
        if( (!GetSpriteActive(hApple) == FALSE) && (appleX > bearX) &&
            (appleX > bearX + 80 * C_UNIT) && (appleY > bearY + 30 * C_UNIT) )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, TIME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
            SetSpriteSwitchDone( hBear, FALSE );
        }
        else if( (bearX > foxX - C_UNIT * 30) &&
                 (bearX < foxX + C_UNIT * 30) &&
               (bearY < foxY + C_UNIT * 60) )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, TIME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
            SetSpriteSwitchDone( hBear, FALSE );
        }
        break;

    case CHEW:
        ++chewCount;
        if( chewCount >= 200 )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hBear, FALSE );

            if( GetSpriteDirection(hFox) == RIGHT )
            {
              SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
              SetPlaneSlideX( hMidground,  -C_BOUNDDIF, P_RELATIVE );
              SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
            }

            chewDif = GetSpriteX(hFox);

            SetSpriteActive( hFox, TRUE );
            SetSpriteAction( hFox, STUNNED, LEFT );
            SetSpriteX( hFox, GetSpriteX(hBear), P_ABSOLUTE );
            SetSpriteY( hFox, GetSpriteY(hBear), P_ABSOLUTE );
            SetSpriteAccX( hFox, 0, P_ABSOLUTE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SetSpriteVelX( hFox, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hFox, -10 * C_UNIT, P_ABSOLUTE );
            SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STUNNED );

            chewDif -= GetSpriteX(hFox);

            SetPlaneSlideX( hForeground, -chewDif, P_RELATIVE );
            SetPlaneSlideX( hMidground, -chewDif, P_RELATIVE );
            SetPlaneSlideX( hBackground, -chewDif, P_RELATIVE );
            SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
            SetPlaneIncremX( hMidground,  C_BOUNDINCREM, P_ABSOLUTE );
            SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
        }
        break;
    }

    /*
     * Apple actions...
     */
    if( (GetSpriteVelY(hApple) != 0) && (GetSpriteY(hApple) >= 420 * C_UNIT) )
    {
        SetSpriteX( hApple, 0, P_ABSOLUTE );
        SetSpriteY( hApple, 0, P_ABSOLUTE );
        SetSpriteAccX( hApple, 0, P_ABSOLUTE );
        SetSpriteAccY( hApple, 0, P_ABSOLUTE );
        SetSpriteVelX( hApple, 0, P_ABSOLUTE );
        SetSpriteVelY( hApple, 0, P_ABSOLUTE );
        SetSpriteActive( hApple, FALSE );
    }

    return cont;

} /* ProcessInput */


GAMEPROC.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:gameproc.h
 *  Content:include file for game processing info
 *
 ***************************************************************************/
#ifndef __GAMEPROC_INCLUDED__
#define __GAMEPROC_INCLUDED__

typedef enum enum_POSITION {
    P_ABSOLUTE,
    P_RELATIVE,
    P_AUTOMATIC
} POSITION;

typedef enum enum_SWITCHING {
    HOR,
    VER,
    TIME
} SWITCHING;

typedef enum enum_DIRECTION {
    SAME,
    RIGHT,
    LEFT
} DIRECTION;

typedef enum enum_ACTION {
    NONE,
    STILL,
    WALK,
    RUN,
    JUMP,
    THROW,
    CROUCH,
    STOP,
    STUNNED,
    JUMPTHROW,
    CROUCHWALK,
    BLURR,
    STRIKE,
    MISS,
    CHEW,
} ACTION;

typedef SHORT HPOSLIST;
typedef BOOL HSURFACELIST;

typedef struct struct_HPLANE {
    GFX_HBM *hBM;
    BOOL    *surface;
    LONG     x;
    LONG     y;
    USHORT   width;
    USHORT   height;
    LONG     xv;
    LONG     xslide;
    LONG     xincrem;
    USHORT   denom;
} HPLANE, FAR *LPHPLANE;

typedef struct struct_HSPRITE_BM {
    GFX_HBM   hBM;
    ACTION    action;
    DIRECTION direction;
    SHORT     x;
    SHORT     y;
    USHORT    width;
    USHORT    height;
} HSPRITE_BM;

typedef struct struct_HSPRITE {
    HSPRITE_BM *hSBM;
    USHORT      bitmapCount;
    ACTION      currentAction;
    DIRECTION   currentDirection;
    USHORT      currentBitmap;
    BOOL        active;
    LONG        x;
    LONG        y;
    USHORT      width;
    USHORT      height;
    LONG        xv;
    LONG        yv;
    LONG        xa;
    LONG        ya;
    SHORT       xmax;
    SHORT       ymax;
    LONG        absSwitch;
    LONG        relSwitch;
    SWITCHING   switchType;
    BOOL        switchForward;
    BOOL        switchDone;
} HSPRITE, FAR *LPHSPRITE, FAR * FAR *LPLPHSPRITE;

typedef struct struct_HBITMAPLIST
{
    GFX_HBM *hBM;
} HBITMAPLIST, FAR *LPHBITMAPLIST;

#include "tile.h"
#include "plane.h"
#include "sprite.h"

#define C_UNIT        (LONG) 65536

#define C_TILE_W                32
#define C_TILE_H                32
#define C_SCREEN_W             640
#define C_SCREEN_H             480

#define C_FORE_W                80
#define C_FORE_H                15
#define C_MID_W                 40
#define C_MID_H                 15
#define C_BACK_W                25
#define C_BACK_H                15
#define C_WORLD_W               20
#define C_WORLD_H               15

#define C_BACK_DENOM            12
#define C_MID_DENOM              3
#define C_FORE_DENOM             1

#define C_TILETOTAL            123      // TILE BITMAP TOTAL
#define C_FBT                  112      // FOX BITMAP TOTAL
#define C_BBT                   26      // BEAR BITMAP TOTAL

#define C_FOXSTILL               1      // NUMBER OF BITMAPS
#define C_FOXWALK               12
#define C_FOXRUN                16
#define C_FOXJUMP                4
#define C_FOXTHROW               2
#define C_FOXCROUCH              1
#define C_FOXSTOP                1
#define C_FOXSTUNNED             1
#define C_FOXJUMPTHROW           2
#define C_FOXCROUCHWALK         12
#define C_FOXBLURR               4

#define C_BEARMISS               2
#define C_BEARWALK              12
#define C_BEARSTRIKE            12

#define C_FOX_XMOVE          (LONG)   C_UNIT / 4

#define C_BOUNDINCREM        (LONG)     5 * C_UNIT
#define C_BOUNDDIF           (LONG)   240 * C_UNIT

#define C_FOX_STARTX         (LONG)   150 * C_UNIT
#define C_FOX_STARTY         (LONG)   318 * C_UNIT

#define C_FOX_WALKMOVE       (LONG)     6 * C_UNIT
#define C_FOX_RUNMOVE        (LONG)    18 * C_UNIT
#define C_FOX_JUMPMOVE       (LONG)     9 * C_UNIT

#define C_FOX_WALKSWITCH     (LONG)     6 * C_UNIT
#define C_FOX_JUMPSWITCH     (LONG)     9 * C_UNIT
#define C_FOX_THROWSWITCH    (LONG)    15 * C_UNIT
#define C_FOX_RUNSWITCH      (LONG)    18 * C_UNIT
#define C_FOX_BLURRSWITCH    (LONG)    18 * C_UNIT

#define C_FOX_WALKTORUN      (LONG)     4 * C_UNIT
#define C_FOX_RUNTOBLURR     (LONG)    14 * C_UNIT

#define C_BEAR_STARTX        (LONG)   600 * C_UNIT
#define C_BEAR_STARTY        (LONG)   329 * C_UNIT

#define C_BEAR_WALKMOVE      (LONG)     1 * C_UNIT

#define C_BEAR_WALKSWITCH    (LONG)     6 * C_UNIT
#define C_BEAR_STRIKESWITCH  (LONG)     8 * C_UNIT
#define C_BEAR_MISSSWITCH    (LONG)    10 * C_UNIT

extern void ErrorMessage( LPSTR );
extern BOOL InitBuffer( GFX_HBM* );
extern void DestroyBuffer( GFX_HBM );
extern HBITMAPLIST *LoadBitmaps( void );
extern void InitTiles( HBITMAPLIST**, HBITMAPLIST*, USHORT );
extern void InitPlane( HPLANE**, HPOSLIST**, CHAR*, USHORT, USHORT, USHORT );
extern void InitSurface( HSURFACELIST**, CHAR*, USHORT, USHORT );
extern void InitFox( HSPRITE**, HBITMAPLIST* );
extern void InitBear( HSPRITE**, HBITMAPLIST* );
extern void InitApple( HSPRITE**, HBITMAPLIST* );
extern BOOL PreInitializeGame( void );
extern BOOL InitializeGame( void );
extern BOOL GetInput( void );
extern BOOL ProcessInput ( SHORT input );
extern int NewGameFrame( void );
extern void DestroyGame( void );
#endif


GFX.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:gfx.c
 *  Content:graphics API
 *
 ***************************************************************************/
#include "foxbear.h"

GFX_BITMAP  *lpVRAM;

static BOOL fForceRestore = FALSE;

/*
 * gfxBlt
 */
BOOL gfxBlt(RECT *dst, GFX_HBM bm, POINT *src)
{
    GFX_BITMAP* pbm = (GFX_BITMAP*)bm;
    HRESULT     ddrval;
    DWORD       bltflags;
    RECT        rc;
    int         x,y,dx,dy;

    if( GameSize.cy == C_SCREEN_H )
    {
        y         = dst->top;
        dy        = dst->bottom - dst->top;
        rc.top    = src->y;
        rc.bottom = rc.top  + dy;
    }
    else
    {
        y         = MapY(dst->top);
        dy        = MapY(dst->bottom) - y;
        rc.top    = MapDY(src->y);
        rc.bottom = rc.top  + dy;
    }

    if( GameSize.cx == C_SCREEN_W )
    {
        x         = dst->left;
        dx        = dst->right  - dst->left;
        rc.left   = src->x;
        rc.right  = rc.left + dx;
    }
    else
    {
        x         = MapX(dst->left);
        dx        = MapX(dst->right) - x;
        rc.left   = MapDX(src->x);
        rc.right  = rc.left + dx;
    }

    if( dx == 0 || dy == 0 )
    {
        return TRUE;
    }

    if (pbm->lpSurface)
    {
        if( pbm->bTrans )
            bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_SRCCOLORKEY;
        else
            bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_NOCOLORKEY;
        ddrval = IDirectDrawSurface_BltFast(
                        lpBackBuffer, x, y,
                        pbm->lpSurface, &rc, bltflags | DDBLTFAST_WAIT);

        if (ddrval != DD_OK)
        {
            Msg("BltFast failed err=%d", ddrval);
        }
    }
    else
    {
        DDBLTFX     ddbltfx;

        rc.left   = x;
        rc.top    = y;
        rc.right  = rc.left + dx;
        rc.bottom = rc.top  + dy;

        ddbltfx.dwSize = sizeof( ddbltfx );
        ddbltfx.dwFillColor = pbm->dwColor;

        ddrval = IDirectDrawSurface_Blt(
                        lpBackBuffer,           // dest surface
                        &rc,                    // dest rect
                        NULL,                   // src surface
                        NULL,                   // src rect
                        DDBLT_COLORFILL | DDBLT_WAIT,
                        &ddbltfx);
    }

    return TRUE;

} /* gfxBlt */

/*
 * gfxCreateSolidColorBitmap
 */
GFX_HBM gfxCreateSolidColorBitmap(COLORREF rgb)
{
    GFX_BITMAP *pvram;

    pvram = MemAlloc( sizeof( *pvram ) );

    if( pvram == NULL )
    {
return NULL;
    }

    pvram->dwColor = DDColorMatch(lpBackBuffer, rgb);
    pvram->lpSurface = NULL;
    pvram->lpbi = NULL;
    pvram->bTrans = FALSE;

    pvram->link = lpVRAM;
    lpVRAM = pvram;

    return (GFX_HBM) pvram;

} /* gfxCreateSolidColorBitmap */

/*
 * gfxCreateBitmap
 */
GFX_HBM gfxCreateVramBitmap(BITMAPINFOHEADER UNALIGNED *lpbi,BOOL bTrans)
{
    GFX_BITMAP *pvram;

    pvram = MemAlloc( sizeof( *pvram ) );

    if( pvram == NULL )
    {
return NULL;
    }
    pvram->lpSurface = DDCreateSurface(MapRX(lpbi->biWidth),
                                       MapRY(lpbi->biHeight), FALSE, TRUE);
    pvram->lpbi = lpbi;
    pvram->dwColor = 0;
    pvram->bTrans = bTrans;

    if( pvram->lpSurface == NULL )
    {
return NULL;
    }

    pvram->link = lpVRAM;
    lpVRAM = pvram;
    gfxRestore((GFX_HBM) pvram);

    return (GFX_HBM) pvram;

} /* gfxCreateVramBitmap */

/*
 * gfxDestroyBitmap
 */
BOOL gfxDestroyBitmap ( GFX_HBM hbm )
{
    GFX_BITMAP *p = (GFX_BITMAP *)hbm;

    if (hbm == NULL || hbm == GFX_TRUE)
    {
return FALSE;
    }

    if (p->lpSurface)
    {
        IDirectDrawSurface_Release(p->lpSurface);
        p->lpSurface = NULL;
    }

    if (p->lpbi)
    {
        p->lpbi = NULL;
    }

    MemFree((VOID *)p);

    return TRUE;

} /* gfxDestroyBitmap */

/*
 * gfxStretchBackBuffer()
 */
BOOL gfxStretchBackbuffer()
{
    if (lpStretchBuffer)
    {
        IDirectDrawSurface_Blt(
                            lpStretchBuffer,        // dest surface
                            NULL,                   // dest rect (all of it)
                            lpBackBuffer,           // src surface
                            &GameRect,              // src rect
                            DDBLT_WAIT,
                            NULL);

        IDirectDrawSurface_Blt(
                            lpBackBuffer,           // dest surface
                            NULL,                   // dest rect (all of it)
                            lpStretchBuffer,        // src surface
                            NULL,                   // src rect
                            DDBLT_WAIT,
                            NULL);
    }
    else
    {
        IDirectDrawSurface_Blt(
                        lpBackBuffer,           // dest surface
                        NULL,                   // dest rect (all of it)
                        lpBackBuffer,           // src surface
                        &GameRect,              // src rect
                        DDBLT_WAIT,
                        NULL);
    }

    return TRUE;

} /* gfxStretchBackbuffer */

/*
 * gfxFlip
 */
BOOL gfxFlip( void )
{
    HRESULT     ddrval;

    ddrval = IDirectDrawSurface_Flip( lpFrontBuffer, NULL, DDFLIP_WAIT );
    if( ddrval != DD_OK )
    {
Msg( "Flip FAILED, rc=%08lx", ddrval );
return FALSE;
    }
    return TRUE;

} /* gfxFlip */

/*
 * gfxUpdateWindow
 */
BOOL gfxUpdateWindow()
{
    HRESULT     ddrval;

    ddrval = IDirectDrawSurface_Blt(
                    lpFrontBuffer,          // dest surface
                    &rcWindow,              // dest rect
                    lpBackBuffer,           // src surface
                    NULL,                   // src rect (all of it)
                    DDBLT_WAIT,
                    NULL);

    return ddrval == DD_OK;

} /* gfxUpdateWindow */

/*
 * gfxSwapBuffers
 *
 * this is called when the game loop has rendered a frame into
 * the backbuffer, its goal is to display something for the user to see.
 *
 * there are four cases...
 *
 * Fullscreen:
 *      we just call IDirectDrawSurface::Flip(lpFrontBuffer)
 *      being careful to handle return code right.
 *
 * Fullscreen (stretched):
 *      the game loop has rendered a frame 1/2 the display
 *      size, we do a Blt to stretch the frame to the backbuffer
 *      the we just call IDirectDrawSurface::Flip(lpFrontBuffer)
 *
 * Window mode (foreground palette):
 *      in this case we call IDirectDrawSurface::Blt to copy
 *      the back buffer to the window.
 *
 * Window mode (background palette):
 *      in this case we are in a window, but we dont own the
 *      palette. all our art was loaded to a specific palette
 *      IDirectDrawSurface::Blt does not do color translation
 *      we have a few options in this case...
 *
 *          reload or remap the art to the the current palette
 *          (we can do this easily with a GetDC, StetchDIBits)
 *          FoxBear has *alot* of art, so this would be too slow.
 *
 *          use GDI to draw the backbuffer, GDI will handle
 *          the color conversion so things will look correct.
 *
 *          pause the game (this is what we do so this function
 *          will never be called)
 *
 */
BOOL gfxSwapBuffers( void )
{
    if( bFullscreen )
    {
        if( bStretch )
{
            gfxStretchBackbuffer();
}

        if (nBufferCount > 1)
            return gfxFlip();
        else
            return TRUE;
    }
    else
    {
        return gfxUpdateWindow();
    }

} /* gfxSwapBuffers */

/*
 * gfxBegin
 */
GFX_HBM gfxBegin( void )
{
    if( !DDEnable() )
    {
return NULL;
    }

    if( !DDCreateFlippingSurface() )
    {
        DDDisable(TRUE);
return NULL;
    }
    Splash();

    return GFX_TRUE;

} /* gfxBegin */

/*
 * gfxEnd
 */
BOOL gfxEnd ( GFX_HBM hbm )
{
    GFX_BITMAP  *curr;
    GFX_BITMAP  *next;

    for( curr = lpVRAM; curr; curr=next )
    {
        next = curr->link;
        gfxDestroyBitmap ((GFX_HBM)curr);
    }

    lpVRAM = NULL;

    return DDDisable(FALSE);

    return TRUE;

} /* gfxEnd */

/*
 * gfxRestore
 *
 * restore the art when one or more surfaces are lost
 */
BOOL gfxRestore(GFX_HBM bm)
{
    GFX_BITMAP *pbm = (GFX_BITMAP*)bm;
    HRESULT     ddrval;
    HDC hdc;
    LPVOID lpBits;
    RGBQUAD *prgb;
    int i,w,h;
    RECT rc;

    struct {
        BITMAPINFOHEADER bi;
        RGBQUAD          ct[256];
    }   dib;

    IDirectDrawSurface *pdds = pbm->lpSurface;
    BITMAPINFOHEADER   UNALIGNED *pbi  = pbm->lpbi;

    if (pdds == NULL)
        return TRUE;

    if (IDirectDrawSurface_Restore(pdds) != DD_OK)
        return FALSE;

    if (pbi == NULL)
        return TRUE;

    //
    // in 8bbp mode if we get switched away from while loading
    // (and palette mapping) our art, the colors will not be correct
    // because some app may have changed the system palette.
    //
    // if we are in stress mode, just keep going.  It is more important
    // to make progress than to get the colors right.
    //

    if (!bFullscreen &&
         GameBPP == 8 &&
         GetForegroundWindow() != hWndMain &&
         !bStress )
    {
        Msg("gfxRestore: **** foreground window changed while loading art!");
        fForceRestore = TRUE;
        PauseGame();
        return FALSE;
    }

    dib.bi = *pbi;

    prgb = (RGBQUAD *)((LPBYTE)pbi + pbi->biSize);
    lpBits = (LPBYTE)(prgb + pbi->biClrUsed);

    if( pbi->biClrUsed == 0 && pbi->biBitCount <= 8 )
    {
        lpBits = (LPBYTE)(prgb + (1<<pbi->biBitCount));
    }

    w = MapRX(pbi->biWidth);
    h = MapRY(pbi->biHeight);
    /*
     * hack to make sure fox off-white doesn't become
     * pure white (which is transparent)
     */
    for( i=0; i<256; i++ )
    {
        dib.ct[i] = prgb[i];

        if( dib.ct[i].rgbRed   == 0xff &&
            dib.ct[i].rgbGreen == 0xff &&
            dib.ct[i].rgbBlue  == 224 )
        {
            dib.ct[i].rgbBlue = 0x80;
        }
        else
        if( dib.ct[i].rgbRed   == 251 &&
            dib.ct[i].rgbGreen == 243 &&
            dib.ct[i].rgbBlue  == 234 )
        {
            dib.ct[i].rgbBlue = 0x80;
        }
    }

    /*
     * if we are in 8bit mode we know the palette is 332 we can
     * do the mapping our self.
     *
     * NOTE we can only do this in fullscreen mode
     * in windowed mode, we have to share the palette with
     * the window manager and we dont get all of the colors
     * in the order we assume.
     *
     */
    if (bFullscreen && GameBPP == pbi->biBitCount && GameBPP == 8 )
    {
        BYTE xlat332[256];
        DDSURFACEDESC ddsd;
        int x,y,dib_pitch;
        BYTE *src, *dst;
        BOOL stretch;
        IDirectDrawSurface *pdds1;
        HDC hdc1;

        stretch = w != pbi->biWidth || h != pbi->biHeight;

        for( i=0;i<256;i++ )
        {
            xlat332[i] =
                ((dib.ct[i].rgbRed   >> 0) & 0xE0 ) |
                ((dib.ct[i].rgbGreen >> 3) & 0x1C ) |
                ((dib.ct[i].rgbBlue  >> 6) & 0x03 );
        }

        /*
         * if we are stretching copy into the back buffer
         * then use GDI to stretch later.
         */
        if( stretch )
{
            pdds1 = lpBackBuffer;
}
        else
{
            pdds1 = pdds;
}

        ddsd.dwSize = sizeof(ddsd);
        ddrval = IDirectDrawSurface_Lock(
            pdds1, NULL, &ddsd, DDLOCK_WAIT, NULL);

        if( ddrval == DD_OK )
        {
            dib_pitch = (pbi->biWidth+3)&~3;
            src = (BYTE *)lpBits + dib_pitch * (pbi->biHeight-1);
            dst = (BYTE *)ddsd.lpSurface;
            for( y=0; y<(int)pbi->biHeight; y++ )
            {
                for( x=0; x<(int)pbi->biWidth; x++ )
                {
                    dst[x] = xlat332[src[x]];
                }
                dst += ddsd.lPitch;
                src -= dib_pitch;
            }
            IDirectDrawSurface_Unlock(pdds1, NULL);
        }
        else
        {
            Msg("Lock failed err=%d", ddrval);
            return FALSE;
        }

        if( stretch )
        {
            if( IDirectDrawSurface_GetDC(pdds,&hdc) == DD_OK )
            {
                if( IDirectDrawSurface_GetDC(pdds1,&hdc1) == DD_OK )
                {
    SetStretchBltMode(hdc, COLORONCOLOR);
                    StretchBlt(hdc, 0, 0, w, h,
                        hdc1, 0, 0, pbi->biWidth, pbi->biHeight, SRCCOPY);
                    IDirectDrawSurface_ReleaseDC(pdds1,hdc1);
                }
                IDirectDrawSurface_ReleaseDC(pdds,hdc);
            }
        }
    }
    else if( IDirectDrawSurface_GetDC(pdds,&hdc) == DD_OK )
    {
        SetStretchBltMode(hdc, COLORONCOLOR);
        StretchDIBits(hdc, 0, 0, w, h,
            0, 0, pbi->biWidth, pbi->biHeight,
            lpBits, (BITMAPINFO *)&dib.bi, DIB_RGB_COLORS, SRCCOPY);

        IDirectDrawSurface_ReleaseDC(pdds,hdc);
    }

    /*
     * show the art while loading...
     */
    rc.left = rcWindow.left,
    rc.top  = rcWindow.top + 20;
    rc.right = rc.left + w;
    rc.bottom = rc.top + h;
    IDirectDrawSurface_Blt(lpFrontBuffer, &rc, pdds, NULL, DDBLT_WAIT, NULL);

    return TRUE;

} /* gfxRestore */

/*
 * gfxRestoreAll
 *
 * restore the art when one or more surfaces are lost
 */
BOOL gfxRestoreAll()
{
    GFX_BITMAP  *curr;
    HWND hwndF = GetForegroundWindow();

    Splash();

    for( curr = lpVRAM; curr != NULL; curr = curr->link)
    {
if (curr->lpSurface &&
    (fForceRestore || IDirectDrawSurface_IsLost(curr->lpSurface) == DDERR_SURFACELOST))
        {
            if( !gfxRestore(curr) )
            {
                Msg( "gfxRestoreAll: ************ Restore FAILED!" );
                return FALSE;
            }
        }
    }

    DDClear();
    fForceRestore = FALSE;
    return TRUE;

} /* gfxRestoreAll */

/*
 * gfxFillBack
 */
void gfxFillBack( DWORD dwColor )
{
    DDBLTFXddbltfx;

    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = dwColor;

    IDirectDrawSurface_Blt(
lpBackBuffer,// dest surface
NULL,// dest rect
NULL,// src surface
NULL,// src rect
                        DDBLT_COLORFILL | DDBLT_WAIT,
                        &ddbltfx);

} /* gfxFillBack */


GFX.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:gfx.h
 *  Content:graphics routines include file
 *
 ***************************************************************************/
#ifndef __GFX_INCLUDED__
#define __GFX_INCLUDED__

#define GFX_FALSE ((GFX_HBM)0)
#define GFX_TRUE  ((GFX_HBM)1)

typedef struct _GFX_BITMAP
{
    struct _GFX_BITMAP      *link;      // linked in a chain for restore.
    DWORD                   dwColor;    // solid fill color
    BOOL                    bTrans;     // transparent?
    LPDIRECTDRAWSURFACE     lpSurface;  // the DirectDrawSurface.
    LPBITMAPINFOHEADER      lpbi;       // the DIB to restore from.

} GFX_BITMAP;

typedef VOID *GFX_HBM;

//
// Prototypes
//

GFX_HBM gfxLoadBitmap(LPSTR);
GFX_HBM gfxCreateVramBitmap(BITMAPINFOHEADER UNALIGNED * ,BOOL);
GFX_HBM gfxCreateSolidColorBitmap(COLORREF rgb);
BOOL    gfxDestroyBitmap(GFX_HBM);
BOOL    gfxSwapBuffers(void);
GFX_HBM gfxBegin(VOID);
BOOL    gfxEnd(GFX_HBM);
void    gfxFillBack( DWORD dwColor );
BOOL    gfxBlt(RECT *dst, GFX_HBM bm, POINT *src);
BOOL    gfxRestore(GFX_HBM bm);
BOOL    gfxRestoreAll(void);

#endif


PLANE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:       plane.c
 *  Content:    plane manipulation functions
 *
 ***************************************************************************/
#include "foxbear.h"

/*
 * CreatePlane
 */
HPLANE *CreatePlane ( USHORT width, USHORT height, USHORT denom )
{
    HPLANE      *hPlane;
    USHORT      num_elems;
    USHORT      elem_size;

    num_elems = width * height;
    elem_size = sizeof (GFX_HBM);

    hPlane = MemAlloc( sizeof (HPLANE) );
    if( hPlane == NULL )
    {
ErrorMessage( "hPlane in CreatePlane" );
    }

    hPlane->hBM  = CMemAlloc( num_elems, elem_size );
    hPlane->surface = CMemAlloc( num_elems, sizeof hPlane->surface );
    hPlane->x = 0;
    hPlane->y = 0;
    hPlane->width = width;
    hPlane->height = height;
    hPlane->denom = denom;
    hPlane->xslide = 0;
    hPlane->xincrem = 0;
    hPlane->xv = 0;

    if( hPlane->hBM == NULL )
    {
MemFree( hPlane );
ErrorMessage( "hPlane->hBM in CreatePlane" );
    }

    return hPlane;

} /* CreatePlane */

/*
 * TilePlane
 */
BOOL TilePlane( HPLANE *hPlane, HBITMAPLIST *hTileList, HPOSLIST *posList )
{
    USHORT i;

    for( i = 0; i < hPlane->width * hPlane->height; ++i )
    {
hPlane->surface[i] = FALSE;

if( posList[i] >= 0 )
{
    hPlane->hBM[i] = hTileList[ posList[i] ].hBM;
}
else
{
    hPlane->hBM[i] = NULL;
}
    }

    return TRUE;

} /* TilePlane */

/*
 * SurfacePlane
 */
BOOL SurfacePlane( HPLANE *hPlane, HSURFACELIST *hSurfaceList )
{
    USHORT i;

    for( i = 0; i < hPlane->width * hPlane->height; ++i )
    {
if( hSurfaceList[i] == FALSE )
{
    hPlane->surface[i] = FALSE;
}
else
{
    hPlane->surface[i] = TRUE;
}
    }

    return TRUE;

} /* SurfacePlane */

/*
 * SetSurface
 */
BOOL SetSurface( HPLANE  *hPlane, HSPRITE *hSprite )
{
    SHORT c;
    SHORT n;
    SHORT x;
    SHORT y;

    c = hSprite->currentBitmap;
    x = (hSprite->x >> 16) / C_TILE_W;
    y = (SHORT) hSprite->y >> 16;

    y += hSprite->hSBM[c].y + hSprite->hSBM[c].height;
    y /= C_TILE_H;

    n = (x % hPlane->width) + y * hPlane->width;

    if( hPlane->surface[n] == FALSE )
    {
if( !hPlane->surface[n + hPlane->width] == FALSE )
{
    y += 1;
}
if( !hPlane->surface[n - hPlane->width] == FALSE )
{
    y -= 1;
}
    }

    y *= C_TILE_H;
    y -= hSprite->hSBM[c].y + hSprite->hSBM[c].height;

    SetSpriteY( hSprite, y << 16, P_ABSOLUTE );

    return TRUE;

} /* SetSurface */

/*
 * GetSurface
 */
BOOL GetSurface( HPLANE *hPlane, HSPRITE *hSprite )
{
    SHORT  c;
    SHORT  x;
    SHORT  y;

    c = hSprite->currentBitmap;
    x = ((hSprite->x >> 16) + hSprite->width / 2) / C_TILE_H;
    y = ((hSprite->y >> 16) + hSprite->hSBM[c].y + hSprite->hSBM[c].height) / C_TILE_W;

    return hPlane->surface[(x % hPlane->width) + y * hPlane->width];

} /* GetSurface */

/*
 * SetPlaneX
 */
BOOL SetPlaneX( HPLANE *hPlane, LONG x, POSITION position )
{
    LONG xincrem;

    if( position == P_ABSOLUTE )
    {
hPlane->x = x;
    }
    else if( position == P_RELATIVE )
    {
hPlane->x += x;
    }
    else if( position == P_AUTOMATIC )
    {
if( hPlane->xslide > 0 )
{
    xincrem = hPlane->xincrem;
}
else if( hPlane->xslide < 0 )
{
    xincrem = -hPlane->xincrem;
}
else
{
    xincrem = 0;
}

hPlane->x += (hPlane->xv + xincrem) / hPlane->denom;
hPlane->xslide -= xincrem;
hPlane->xv = 0;

if( abs(hPlane->xslide) < hPlane->xincrem )
{
    hPlane->x += hPlane->xslide / hPlane->denom;
    hPlane->xslide = 0;
    hPlane->xincrem = 0;
}
    }

    if( hPlane->x < 0 )
    {
hPlane->x += (hPlane->width * C_TILE_W) << 16;
    }
    else if( hPlane->x >= (hPlane->width * C_TILE_W) << 16 )
    {
hPlane->x -= (hPlane->width * C_TILE_W) << 16;
    }

    return TRUE;

} /* SetPlaneX */

/*
 * GetPlaneX
 */
LONG GetPlaneX( HPLANE *hPlane )
{
    return hPlane->x;

} /* GetPlaneX */

/*
 * SetPlaneY
 */
BOOL SetPlaneY( HPLANE *hPlane, LONG y, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
hPlane->y = y;
    }
    else
    {
hPlane->y += y;
    }

    if( hPlane->y < 0 )
    {
hPlane->y += (hPlane->height * C_TILE_H) << 16;
    }
    else if( hPlane->y >= (hPlane->height * C_TILE_H) << 16 )
    {
hPlane->y -= (hPlane->height * C_TILE_H) << 16;
    }

    return TRUE;

} /* SetPlaneY */

/*
 * GetPlaneY
 */
LONG GetPlaneY( HPLANE *hPlane )
{
    return hPlane->y;

} /* GetPlaneY */

/*
 * SetPlaneSlideX
 */
BOOL SetPlaneSlideX( HPLANE *hPlane, LONG xslide, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
hPlane->xslide = xslide;
    }
    else if( position == P_RELATIVE )
    {
hPlane->xslide += xslide;
    }
    return TRUE;

} /* SetPlaneSlideX */

/*
 * SetPlaneVelX
 */
BOOL SetPlaneVelX( HPLANE *hPlane, LONG xv, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
hPlane->xv = xv;
    }
    else if( position == P_RELATIVE )
    {
hPlane->xv += xv;
    }

    return TRUE;
} /* SetPlaneVelX */

/*
 * SetPlaneIncremX
 */
BOOL SetPlaneIncremX( HPLANE *hPlane, LONG xincrem, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
hPlane->xincrem = xincrem;
    }
    else if( position == P_RELATIVE )
    {
hPlane->xincrem += xincrem;
    }

    return TRUE;

} /* SetPlaneIncremX */

/*
 * ScrollPlane
 */
BOOL ScrollPlane( HSPRITE *hSprite )
{
    if( (GetSpriteX(hSprite) <= C_FOX_STARTX) && (GetSpriteVelX(hSprite) < 0) )
    {
return TRUE;
    }

    if( (GetSpriteX(hSprite) >= C_FOX_STARTX) && (GetSpriteVelX(hSprite) > 0) )
    {
return TRUE;
    }
    return FALSE;

} /* ScrollPlane */

/*
 * DisplayPlane
 */
BOOL DisplayPlane ( GFX_HBM  hBuffer, HPLANE *hPlane )
{
    USHORT      n;
    USHORT      i;
    USHORT      j;
    USHORT      x1;
    USHORT      y1;
    USHORT      x2;
    USHORT      y2;
    USHORT      xmod;
    USHORT      ymod;
    POINT       src;
    RECT        dst;

    x1 = (hPlane->x >> 16) / C_TILE_W;
    y1 = (hPlane->y >> 16) / C_TILE_H;
    x2 = x1 + C_SCREEN_W / C_TILE_W;
    y2 = y1 + C_SCREEN_H / C_TILE_H;
    xmod = (hPlane->x >> 16) % C_TILE_W;
    ymod = (hPlane->y >> 16) % C_TILE_H;

    for( j = y1; j < y2; ++j )
    {
for( i = x1; i <= x2; ++i )
{
    n = (i % hPlane->width) + j * hPlane->width;
    if( hPlane->hBM[n] != NULL )
    {
if( i == x1 )
{
    dst.left  = 0;
    dst.right = dst.left + C_TILE_W - xmod;
    src.x     = xmod;
}
else if( i == x2 )
{
    dst.left  = (i - x1) * C_TILE_W - xmod;
    dst.right = dst.left + xmod;
    src.x     = 0;
} else {
    dst.left  = (i - x1) * C_TILE_W - xmod;
    dst.right = dst.left + C_TILE_W;
    src.x     = 0;
}

if( j == y1 )
{
    dst.top    = 0;
    dst.bottom = dst.top + C_TILE_H - ymod;
    src.y      = ymod;
}
else if( j == y2 )
{
    dst.top    = (j - y1) * C_TILE_H - ymod;
    dst.bottom = dst.top + ymod;
    src.y      = 0;
} else {
    dst.top    = (j - y1) * C_TILE_H - ymod;
    dst.bottom = dst.top + C_TILE_H;
    src.y      = 0;
}

gfxBlt(&dst,hPlane->hBM[n],&src);
    }
}
    }

    return TRUE;

} /* DisplayPlane */

/*
 * DestroyPlane
 */
BOOL DestroyPlane ( HPLANE *hPlane )
{
    if( hPlane == NULL )
    {
ErrorMessage( "hPlane in DestroyPlane" );
    }

    if( hPlane->hBM == NULL )
    {
ErrorMessage( "hPlane->hBM in DestroyPlane" );
    }

    MemFree( hPlane->hBM );
    MemFree( hPlane );

    return TRUE;

} /* DestroyPlane */


PLANE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:plane.h
 *  Content:planes include file
 *
 ***************************************************************************/
#ifndef __PLANE_INCLUDED__
#define __PLANE_INCLUDED__

HPLANE *CreatePlane( USHORT, USHORT, USHORT );
BOOL    TilePlane( HPLANE*, HBITMAPLIST*, HPOSLIST* );
BOOL    SurfacePlane( HPLANE*, HSURFACELIST* );
BOOL    SetSurface( HPLANE*, HSPRITE* );
BOOL    GetSurface( HPLANE*, HSPRITE* );
BOOL    SetPlaneX( HPLANE*, LONG, POSITION );
LONG    GetPlaneX( HPLANE* );
BOOL    SetPlaneY( HPLANE*, LONG, POSITION );
LONG    GetPlaneY( HPLANE* );
BOOL    SetPlaneSlideX( HPLANE*, LONG, POSITION );
BOOL    SetPlaneVelX( HPLANE*, LONG, POSITION );
BOOL    SetPlaneIncremX( HPLANE*, LONG, POSITION );
BOOL    ScrollPlane( HSPRITE* );
BOOL    DisplayPlane( GFX_HBM, HPLANE* );
BOOL    DestroyPlane( HPLANE* );

#endif


RCIDS.H

#define FOX_ICON101


SPRITE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:sprite.c
 *  Content:sprite manipulation functions
 *
 ***************************************************************************/
#include "foxbear.h"

/*
 * CreateSprite
 */
HSPRITE *CreateSprite (
    USHORT    bitmapCount,
    LONG      x,
    LONG      y,
    USHORT    width,
    USHORT    height,
    USHORT    xmax,
    USHORT    ymax,
    SHORT     as,
    BOOL      active )
{
    HSPRITE *hSprite;
    USHORT   i;

    hSprite = MemAlloc( sizeof (HSPRITE) );
    if( hSprite == NULL )
    {
        ErrorMessage( "hSprite in CreateSprite" );
    }

    hSprite->hSBM = CMemAlloc( bitmapCount, sizeof (HSPRITE_BM) );
    if( hSprite->hSBM == NULL )
    {
        MemFree( hSprite );
        ErrorMessage( "hSprite->hSBM in CreateSprite" );
    }

    hSprite->active        = active;
    hSprite->bitmapCount   = bitmapCount;
    hSprite->x             = x;
    hSprite->y             = y;
    hSprite->width         = width;
    hSprite->height        = height;
    hSprite->xv            = 0;
    hSprite->yv            = 0;
    hSprite->xa            = 0;
    hSprite->ya            = 0;
    hSprite->xmax          = xmax;
    hSprite->ymax          = ymax;
    hSprite->absSwitch     = as;
    hSprite->relSwitch     = 0;
    hSprite->switchType    = HOR;
    hSprite->switchForward = TRUE;
    hSprite->switchDone    = FALSE;

    for( i = 0; i < bitmapCount; ++i )
    {
        hSprite->hSBM[i].hBM = NULL;
    }

    return hSprite;

} /* CreateSprite */

/*
 * BitBltSprite
 */
BOOL BitBltSprite (
    HSPRITE   *hSprite,
    GFX_HBM    hBM,
    ACTION     action,
    DIRECTION  direction,
    SHORT      x,
    SHORT      y,
    USHORT     w,
    USHORT     h )
{
    USHORT count;

    if( hSprite == NULL )
    {
        ErrorMessage( "hSprite in BitBltSprite" );
    }

    if( hBM == NULL )
    {
        ErrorMessage( "hBM in BitBltSprite" );
    }

    if( (x >= hSprite->width) || (y >= hSprite->height) )
    {
        ErrorMessage( "x or y in BitBltSprite" );
    }

    count = 0;
    while( hSprite->hSBM[count].hBM != NULL )
    {
        count++;
        if( count >= hSprite->bitmapCount )
    {
            ErrorMessage( "Bitmap overflow in BitBltSprite" );
        }
    }

    hSprite->hSBM[count].hBM       = hBM;
    hSprite->hSBM[count].action    = action;
    hSprite->hSBM[count].direction = direction;
    hSprite->hSBM[count].x         = x;
    hSprite->hSBM[count].y         = y;
    hSprite->hSBM[count].width     = w;
    hSprite->hSBM[count].height    = h;

    return TRUE;

} /* BitBltSprite */

/*
 * SetSpriteAction
 */
BOOL SetSpriteAction ( HSPRITE *hSprite, ACTION action, DIRECTION direction )
{
    USHORT c;

    c = 0;

    if( direction == SAME )
    {
        direction = hSprite->currentDirection;
    }

    while( (hSprite->hSBM[c].action != action) || (hSprite->hSBM[c].direction != direction) )
    {
        ++c;
    }

    hSprite->currentAction    = action;
    hSprite->currentDirection = direction;
    hSprite->currentBitmap    = c;
    hSprite->relSwitch        = 0;

    return TRUE;

} /* SetSpriteAction */

/*
 * ChangeSpriteDirection
 */
BOOL ChangeSpriteDirection( HSPRITE *hSprite )
{
    DIRECTION direction;

    if( hSprite->currentDirection == RIGHT )
    {
        direction = LEFT;
    }
    else
    {
        direction = RIGHT;
    }

    SetSpriteAction( hSprite, hSprite->currentAction, direction );

    return TRUE;

} /* ChangeSpriteDirection */

/*
 * GetSpriteAction
 */
ACTION GetSpriteAction( HSPRITE *hSprite )
{
    return hSprite->currentAction;

} /* GetSpriteAction */

/*
 * GetSpriteDirection
 */
DIRECTION GetSpriteDirection( HSPRITE *hSprite )
{
    return hSprite->currentDirection;

} /* GetSpriteDirection */

/*
 * SetSpriteActive
 */
BOOL SetSpriteActive( HSPRITE *hSprite, BOOL active )
{
    hSprite->active = active;

    if( active == FALSE )
    {
        hSprite->xv = 0;
        hSprite->yv = 0;
        hSprite->xa = 0;
        hSprite->ya = 0;
    }

    return TRUE;

} /* SetSpriteActive */

/*
 * GetSpriteActive
 */
BOOL GetSpriteActive( HSPRITE *hSprite )
{
    return hSprite->active;

} /* GetSpriteActive */

/*
 * SetSpriteVelX
 */
BOOL SetSpriteVelX( HSPRITE *hSprite, LONG xv, POSITION position )
{
    if( hSprite->active == FALSE )
    {
        return FALSE;
    }

    if( position == P_ABSOLUTE )
    {
        hSprite->xv = xv;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->xv += xv;
    }

    return TRUE;

} /* SetSpriteVelX */

/*
 * GetSpriteVelX
 */
LONG GetSpriteVelX( HSPRITE *hSprite )
{
    return hSprite->xv;

} /* GetSpriteVelX */

/*
 * SetSpriteVelY
 */
BOOL SetSpriteVelY( HSPRITE *hSprite, LONG  yv, POSITION position )
{
    if( hSprite->active == FALSE )
    {
        return FALSE;
    }

    if( position == P_ABSOLUTE )
    {
        hSprite->yv = yv;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->yv += yv;
    }

    return TRUE;

} /* SetSpriteVelY */

/*
 * GetSpriteVelY
 */
LONG GetSpriteVelY( HSPRITE *hSprite )
{
    return hSprite->yv;

} /* GetSpriteVelY */

/*
 * SetSpriteAccX
 */
BOOL SetSpriteAccX ( HSPRITE *hSprite, LONG xa, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
        hSprite->xa = xa;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->xa += xa;
    }
    return TRUE;

} /* SetSpriteAccX */

/*
 * GetSpriteAccX
 */
LONG GetSpriteAccX( HSPRITE *hSprite )
{
    return hSprite->xa;

} /* GetSpriteAccX */

/*
 * SetSpriteAccY
 */
BOOL SetSpriteAccY ( HSPRITE *hSprite, LONG ya, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
        hSprite->ya = ya;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->ya += ya;
    }
    return TRUE;

} /* SetSpriteAccY */

/*
 * GetSpriteAccY
 */
LONG GetSpriteAccY( HSPRITE *hSprite )
{
    return hSprite->ya;

} /* GetSpriteAccY */

/*
 * SetSpriteX
 */
BOOL SetSpriteX( HSPRITE *hSprite, LONG x, POSITION position )
{
    if( hSprite->active == FALSE )
    {
        return FALSE;
    }

    if( position == P_AUTOMATIC )
    {
        hSprite->xv += hSprite->xa;
        hSprite->x  += hSprite->xv;
    }
    else if( position == P_ABSOLUTE )
    {
        hSprite->x = x;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->x += x;
    }

    if( hSprite->x < 0 )
    {
        hSprite->x += hSprite->xmax << 16;
    }
    else if( hSprite->x >= hSprite->xmax << 16 )
    {
        hSprite->x -= hSprite->xmax << 16;
    }
    return TRUE;

} /* SetSpriteX */

/*
 * GetSpriteX
 */
LONG GetSpriteX( HSPRITE *hSprite )
{
    return hSprite->x;

} /* GetSpriteX */

/*
 * SetSpriteY
 */
BOOL SetSpriteY ( HSPRITE *hSprite, LONG y, POSITION position )
{
    if( hSprite->active == FALSE )
    {
        return FALSE;
    }

    if( position == P_AUTOMATIC )
    {
        hSprite->yv += hSprite->ya;
        hSprite->y  += hSprite->yv;
    }
    else if( position == P_ABSOLUTE )
    {
        hSprite->y = y;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->y += y;
    }

    if( hSprite->y < 0 )
    {
        hSprite->y += hSprite->ymax << 16;
    }
    else if( hSprite->y >= hSprite->ymax << 16 )
    {
        hSprite->y -= hSprite->ymax << 16;
    }

    return TRUE;

} /* SetSpriteY */

/*
 * GetSpriteY
 */
LONG GetSpriteY( HSPRITE *hSprite )
{
    return hSprite->y;

} /* GetSpriteY */

/*
 * SetSpriteSwitch
 */
BOOL SetSpriteSwitch ( HSPRITE *hSprite, LONG absSwitch, POSITION position )
{
    if( position == P_ABSOLUTE )
    {
        hSprite->absSwitch = absSwitch;
    }
    else if( position == P_RELATIVE )
    {
        hSprite->absSwitch += absSwitch;
    }
    return TRUE;

} /* SetSpriteSwitch */

/*
 * IncrementSpriteSwitch
 */
BOOL IncrementSpriteSwitch ( HSPRITE *hSprite, LONG n )
{
    hSprite->relSwitch += n;
    return TRUE;

} /* IncrementSpriteSwitch */

/*
 * SetSpriteSwitchType
 */
BOOL SetSpriteSwitchType( HSPRITE *hSprite, SWITCHING switchType )
{
    hSprite->switchType = switchType;
    hSprite->relSwitch  = 0;
    return TRUE;

} /* SetSpriteSwitchType */

/*
 * GetSpriteSwitchType
 */
SWITCHING GetSpriteSwitchType ( HSPRITE *hSprite )
{
    return hSprite->switchType;

} /* GetSpriteSwitchType */

/*
 * SetSpriteSwitchForward
 */
BOOL SetSpriteSwitchForward( HSPRITE *hSprite, BOOL switchForward )
{
    hSprite->switchForward = switchForward;

    return TRUE;

} /* SetSpriteSwitchForward */

/*
 * GetSpriteSwitchForward
 */
BOOL GetSpriteSwitchForward( HSPRITE *hSprite )
{
    return hSprite->switchForward;

} /* GetSpriteSwitchForward */

/*
 * SetSpriteSwitchDone
 */
BOOL SetSpriteSwitchDone( HSPRITE *hSprite, BOOL switchDone )
{
    hSprite->switchDone = switchDone;
    return TRUE;

} /* SetSpriteSwitchDone */

/*
 * GetSpriteSwitchDone
 */
BOOL GetSpriteSwitchDone( HSPRITE *hSprite )
{
    return hSprite->switchDone;

} /* GetSpriteSwitchDone */

/*
 * SetSpriteBitmap
 */
BOOL SetSpriteBitmap ( HSPRITE *hSprite, USHORT currentBitmap )
{
    USHORT c;

    c = 0;
    while( (hSprite->currentAction != hSprite->hSBM[c].action) ||
           (hSprite->currentDirection != hSprite->hSBM[c].direction) )
    {
        ++c;
    }
    hSprite->currentBitmap = c + currentBitmap;
    return TRUE;

} /* SetSpriteBitmap */

/*
 * GetSpriteBitmap
 */
USHORT GetSpriteBitmap( HSPRITE *hSprite )
{
    USHORT count;

    count = 0;
    while( (hSprite->currentAction != hSprite->hSBM[count].action) ||
       (hSprite->currentDirection != hSprite->hSBM[count].direction) )
    {
        ++count;
    }
    return hSprite->currentBitmap - count;

} /* GetSpriteBitmap */

/*
 * advanceSpriteBitmap
 */
static BOOL advanceSpriteBitmap( HSPRITE *hSprite )
{
    SHORTc;
    SHORTn;
    ACTIONcurAct;
    ACTIONact;
    DIRECTIONcurDir;
    DIRECTIONdir;

    curAct = hSprite->currentAction;
    curDir = hSprite->currentDirection;

//
// See if we're cycling forward or backward though the images.
//
    if( hSprite->switchForward ) // Are we cycling forward?
    {
        c   = hSprite->currentBitmap + 1;

// Does the next image exceed the number of images we have?
        if( c >= hSprite->bitmapCount )
{
// if the next image is past the end of the list,
// we need to set it to the start of the series.
            SetSpriteBitmap( hSprite, 0 );
c   = hSprite->currentBitmap;
        }
else
{
act = hSprite->hSBM[c].action;
dir = hSprite->hSBM[c].direction;

// By examining the action and direction fields we can tell
// if we've past the current series of images and entered
// another series.
if( (curAct != act) || (curDir != dir) )
{
SetSpriteBitmap( hSprite, 0 );
}
else // We're still in the series, use the next image.
{
hSprite->currentBitmap = c;
}
}
    }
else //cycling backwards
{
        c   = hSprite->currentBitmap - 1;

        if( c < 0 ) // Is the next image past the beginning of the list?
{
            n = 0;

// Find the last bitmap in the series
            while( (n <= hSprite->bitmapCount) &&
       (curAct == hSprite->hSBM[n].action) &&
           (curDir == hSprite->hSBM[n].direction) )
{
++n;
}

            hSprite->currentBitmap = n - 1;
        }

else
{
act = hSprite->hSBM[c].action;
dir = hSprite->hSBM[c].direction;
// Is the next image past the of the series
if( (curAct != act) || (curDir != dir) )
{
n = c + 1;
while( (n <= hSprite->bitmapCount) &&
   (curAct == hSprite->hSBM[n].action) &&
       (curDir == hSprite->hSBM[n].direction) )
{
++n;
}

hSprite->currentBitmap = n - 1;
}
else  // The next image is fine, use it.
{
hSprite->currentBitmap = c;
}
}
    }
    return TRUE;

} /* advanceSpriteBitmap */

/*
 * DisplaySprite
 */
BOOL DisplaySprite ( GFX_HBM hBuffer, HSPRITE *hSprite, LONG xPlane )
{
    USHORTcount;
    SHORTleft;
    SHORTright;
    SHORTshortx;
    SHORTshorty;
    SHORTplanex;
    POINT       src;
    RECT        dst;

    if( hSprite->active == FALSE )
    {
        return FALSE;
    }

    count = hSprite->currentBitmap;
    shortx = (SHORT) (hSprite->x >> 16);
    shorty = (SHORT) (hSprite->y >> 16);
    planex = (SHORT) (xPlane >> 16);
    src.x = 0;
    src.y = 0;

    if( shortx < planex - C_SCREEN_W )
    {
        shortx += hSprite->xmax;
    }
    else if( shortx >= planex + C_SCREEN_W )
    {
        shortx -= hSprite->xmax;
    }

    left = shortx - planex;

    if( hSprite->currentDirection == RIGHT )
    {
        left += hSprite->hSBM[count].x;
    }
    else
    {
        left += hSprite->width - hSprite->hSBM[count].x - hSprite->hSBM[count].width;
    }

    right = left + hSprite->hSBM[count].width;

    if( left > C_SCREEN_W )
    {
        left = C_SCREEN_W;
    }
    else if( left < 0 )
    {
        src.x = -left;
        left = 0;
    }

    if( right > C_SCREEN_W )
    {
        right = C_SCREEN_W;
    }
    else if( right < 0 )
    {
        right = 0;
    }

    dst.left   = left;
    dst.right  = right;
    dst.top    = shorty + hSprite->hSBM[count].y;
    dst.bottom = dst.top + hSprite->hSBM[count].height;

    gfxBlt(&dst,hSprite->hSBM[count].hBM,&src);

    if( hSprite->switchType == HOR )
    {
        hSprite->relSwitch += abs(hSprite->xv);

        if( hSprite->relSwitch >= hSprite->absSwitch )
{
            hSprite->relSwitch = 0;
            advanceSpriteBitmap( hSprite );
        }
    }
    else if( hSprite->switchType == VER )
    {
        hSprite->relSwitch += abs(hSprite->yv);

        if( hSprite->relSwitch >= hSprite->absSwitch )
    {
            hSprite->relSwitch = 0;
            advanceSpriteBitmap( hSprite );

            if( GetSpriteBitmap( hSprite ) == 0 )
        {
                SetSpriteSwitchDone( hSprite, TRUE );
            }
        }
    }
    else if( hSprite->switchType == TIME )
    {
        hSprite->relSwitch += C_UNIT;

        if( hSprite->relSwitch >= hSprite->absSwitch )
    {
            hSprite->relSwitch = 0;
            advanceSpriteBitmap( hSprite );

            if( GetSpriteBitmap( hSprite ) == 0 )
        {
                SetSpriteSwitchDone( hSprite, TRUE );
            }
        }
    }

    return TRUE;

} /* DisplaySprite */

/*
 * DestroySprite
 */
BOOL DestroySprite ( HSPRITE *hSprite )
{
    USHORT i;

    if( hSprite == NULL )
    {
        ErrorMessage( "hSprite in DestroySprite" );
    }

    if( hSprite->hSBM == NULL )
    {
        ErrorMessage( "hSprite->hSBM in DestroySprite" );
    }

    for( i = 0; i < hSprite->bitmapCount; ++i )
    {
        if( !gfxDestroyBitmap( hSprite->hSBM[i].hBM ) )
    {
            ErrorMessage( "gfxDestroyBitmap (hBM) in DestroySprite" );
        }
    }

    MemFree( hSprite->hSBM );
    MemFree( hSprite );

    return TRUE;

} /* DestroySprite */


SPRITE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:sprite.h
 *  Content:sprite include file
 *
 ***************************************************************************/
#ifndef __SPRITE_INCLUDED__
#define __SPRITE_INCLUDED__

HSPRITE  *CreateSprite( USHORT, LONG, LONG, USHORT, USHORT, USHORT, USHORT, SHORT, BOOL );
BOOL      BitBltSprite( HSPRITE*, GFX_HBM, ACTION, DIRECTION, SHORT, SHORT, USHORT, USHORT );
BOOL      SetSpriteAction( HSPRITE*, ACTION, DIRECTION );
ACTION    GetSpriteAction( HSPRITE* );
BOOL      ChangeSpriteDirection( HSPRITE* );
DIRECTION GetSpriteDirection( HSPRITE* );
BOOL      SetSpriteBitmap( HSPRITE*, USHORT );
USHORT    GetSpriteBitmap( HSPRITE* );
BOOL      SetSpriteActive( HSPRITE*, BOOL );
BOOL      GetSpriteActive( HSPRITE* );
BOOL      SetSpriteVelX( HSPRITE*, LONG, POSITION );
LONG      GetSpriteVelX( HSPRITE* );
BOOL      SetSpriteVelY( HSPRITE*, LONG, POSITION );
LONG      GetSpriteVelY( HSPRITE* );
BOOL      SetSpriteAccX( HSPRITE*, LONG, POSITION );
LONG      GetSpriteAccX( HSPRITE* );
BOOL      SetSpriteAccY( HSPRITE*, LONG, POSITION );
LONG      GetSpriteAccY( HSPRITE* );
BOOL      SetSpriteX( HSPRITE*, LONG, POSITION );
LONG      GetSpriteX( HSPRITE* );
BOOL      SetSpriteY( HSPRITE*, LONG, POSITION );
LONG      GetSpriteY( HSPRITE* );
BOOL      SetSpriteSwitch( HSPRITE*, LONG, POSITION );
BOOL      IncrementSpriteSwitch( HSPRITE*, LONG );
BOOL      SetSpriteSwitchType( HSPRITE*, SWITCHING );
SWITCHING GetSpriteSwitchType( HSPRITE* );
BOOL      SetSpriteSwitchForward( HSPRITE*, BOOL );
BOOL      GetSpriteSwitchForward( HSPRITE* );
BOOL      SetSpriteSwitchDone( HSPRITE*, BOOL );
BOOL      GetSpriteSwitchDone( HSPRITE* );
BOOL      DisplaySprite( GFX_HBM, HSPRITE*, LONG );
BOOL      DestroySprite( HSPRITE* );
#endif


TILE.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:tile.c
 *  Content:tile loading and initialization functions
 *
 ***************************************************************************/
#include "foxbear.h"

/*
 * CreateTiles
 */
HBITMAPLIST *CreateTiles( HBITMAPLIST *phBitmapList, USHORT n )
{
    HBITMAPLIST *hTileList;
    USHORT i;

    hTileList = CMemAlloc( n, sizeof (HBITMAPLIST) );

    if( hTileList == NULL )
    {
ErrorMessage( "hTileList in CreateTiles" );
    }

    for( i = 0; i < n; ++i )
    {
hTileList[i].hBM = phBitmapList[i].hBM;
    }
    return hTileList;

} /* CreateTiles */

/*
 * DestroyTiles
 */
BOOL DestroyTiles( HBITMAPLIST *phTileList )
{
    MemFree( phTileList );

    return TRUE;

} /* DestroyTiles */

/*
 * getData
 */
LPSTR getData(LPSTR fileName)
{
    LPSTR       p = NULL;
    HRSRChRes;

    hRes = FindResource(NULL, fileName, RT_RCDATA);

    if( hRes != NULL )
    {
        p = LockResource(LoadResource(NULL, hRes));
    }

    return p;

} /* getData */

/*
 * CreatePosList
 */
HPOSLIST *CreatePosList( LPSTR fileName, USHORT width, USHORT height )
{
    HPOSLIST*hPosList;
    USHORTpos;
    LPSTRp;

    p = getData( fileName );

    if( p == NULL )
    {
ErrorMessage( "p in CreatePosList" );
    }

    hPosList = CMemAlloc( width * height, sizeof (USHORT) );

    if( hPosList == NULL )
    {
ErrorMessage( "posList in CreatePosList" );
    }

    for( pos = 0; pos < width * height; ++pos )
    {
hPosList[pos] = (USHORT) getint(&p, 0) - 1;
    }

    return hPosList;

} /* CreatePosList */

/*
 * CreateSurfaceList
 */
HSURFACELIST *CreateSurfaceList( LPSTR fileName, USHORT width, USHORT height )
{
    HSURFACELIST*hSurfaceList;
    USHORTpos;
    USHORTvalue;
    LPSTRp;

    p = getData( fileName );

    if( p == NULL )
    {
ErrorMessage( "p in CreateSurfaceList" );
    }

    hSurfaceList = CMemAlloc( width * height, sizeof (HSURFACELIST) );

    if( hSurfaceList == NULL )
    {
ErrorMessage( "posList in CreateSurfaceList" );
    }

    for( pos = 0; pos < width * height; ++pos )
    {
value = (USHORT) getint(&p, 0);

if( value == 0 )
{
    hSurfaceList[pos] = FALSE;
}
else
{
    hSurfaceList[pos] = TRUE;
}
    }

    return hSurfaceList;

} /* CreateSurfaceList */

/*
 * DestoryPosList
 */
BOOL DestroyPosList ( HPOSLIST *posList )
{
    if( posList == NULL )
    {
ErrorMessage( "posList in DestroyPosList" );
    }

    MemFree( posList );
    return TRUE;

} /* DestroyPosList */


TILE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *  Copyright (C) 1994-1995 ATI Technologies Inc. All Rights Reserved.
 *
 *  File:tile.h
 *  Content:tile include file
 *
 ***************************************************************************/
#ifndef TILE__H__
#define TILE__H__

HBITMAPLIST  *CreateTiles( HBITMAPLIST*, USHORT );
void       ReloadTiles( HBITMAPLIST*, USHORT );
BOOL          DestroyTiles( HBITMAPLIST* );
HPOSLIST     *CreatePosList( CHAR*, USHORT, USHORT );
HSURFACELIST *CreateSurfaceList( CHAR*, USHORT, USHORT );
BOOL          DestroyPosList( HPOSLIST* );

#endif


README.TXT

Globe
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Loading a simple XOF of a single mesh object with textures.


GLOBE.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: globe.c
 *
 ***************************************************************************/

#include "rmdemo.h"

D3DRMRENDERQUALITY quality = D3DRMRENDER_GOURAUD;

LPDIRECT3DRMANIMATION anim;
int motion_points = 17;

D3DVECTOR motion[17] =
{{-D3DVAL(14), D3DVAL(4), D3DVAL(45)},
{D3DVAL(9), -D3DVAL(3), D3DVAL(36)},
{D3DVAL(5), D3DVAL(0), D3DVAL(5)},
{-D3DVAL(5), D3DVAL(8), D3DVAL(25)},
{D3DVAL(0), D3DVAL(3), D3DVAL(20)},
{-D3DVAL(3), -D3DVAL(4), D3DVAL(10)},
{D3DVAL(8), D3DVAL(10), D3DVAL(15)},
{D3DVAL(16), D3DVAL(0), D3DVAL(30)},
{D3DVAL(10), -D3DVAL(4), D3DVAL(42)},
{-D3DVAL(15), D3DVAL(0), D3DVAL(37)},
{-D3DVAL(5), -D3DVAL(7), D3DVAL(15)},
{D3DVAL(5), D3DVAL(5), D3DVAL(20)},
{D3DVAL(5), D3DVAL(10), D3DVAL(30)},
{D3DVAL(13), D3DVAL(8), D3DVAL(50)},
{D3DVAL(0), D3DVAL(8), D3DVAL(25)},
{D3DVAL(0), D3DVAL(0), D3DVAL(20)},
{-D3DVAL(14), D3DVAL(4), D3DVAL(45)}
};

static void CDECL cleanupPath(LPDIRECT3DRMOBJECT obj, void* arg)
{
    anim->lpVtbl->Release(anim);
}

static void CDECL moveCamera(LPDIRECT3DRMFRAME frame, void *arg, D3DVALUE delta)
{
    static D3DVALUE t = D3DVAL(0.0);
    LPDIRECT3DRMFRAME world_frame = (LPDIRECT3DRMFRAME) arg;
    LPDIRECT3DRMFRAME scene;

    frame->lpVtbl->GetScene(frame, &scene);

    t += D3DVAL(0.08);
    anim->lpVtbl->SetFrame(anim, frame);
    anim->lpVtbl->SetTime(anim, t);

    frame->lpVtbl->LookAt(frame, world_frame, scene, D3DRMCONSTRAIN_Z);

    scene->lpVtbl->Release(scene);
}

/*
 * world_scene
 */
BOOL
run_world_scene(LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMFRAME light1 = NULL;
    LPDIRECT3DRMFRAME world_frame = NULL;
    LPDIRECT3DRMLIGHT l1 = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMMESHBUILDER sphere3_builder = NULL;
    LPDIRECT3DRMMATERIAL mat = NULL;
    HRESULT rval;
    D3DRMBOX box;
    D3DVALUE miny, maxy, height;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &light1)))
goto generic_error;
    if (FAILED(light1->lpVtbl->SetPosition(light1, scene, D3DVAL(2), D3DVAL(0.0), D3DVAL(22))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.9),
                                  D3DVAL(0.9), D3DVAL(0.9), &l1)))
  goto generic_error;
    if (FAILED(light1->lpVtbl->AddLight(light1, l1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &l2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l2)))
goto generic_error;
    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0))))
goto generic_error;
    if (FAILED(camera->lpVtbl->SetOrientation(camera, scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0),
D3DVAL(1), D3DVAL(0.0))))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &world_frame)))
goto generic_error;
    if (FAILED(world_frame->lpVtbl->SetPosition(world_frame, scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(30))))
goto generic_error;
    if (FAILED(world_frame->lpVtbl->SetOrientation(world_frame, scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1),
                                        D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0))))
goto generic_error;
    if (FAILED(world_frame->lpVtbl->SetRotation(world_frame, scene, D3DVAL(0.03),
                                     D3DVAL(0.1), D3DVAL(0.0), D3DVAL(0.1))))
     goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &sphere3_builder)))
goto generic_error;
    rval = sphere3_builder->lpVtbl->Load(sphere3_builder, "sphere3.x",
    NULL, D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
Msg("Failed to load sphere3.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(lpD3DRM->lpVtbl->CreateMaterial(lpD3DRM, D3DVAL(20.0), &mat)))
goto generic_error;
    if (FAILED(sphere3_builder->lpVtbl->SetMaterial(sphere3_builder, mat)))
goto generic_error;
    mat->lpVtbl->Release(mat); mat = NULL;
    if (FAILED(sphere3_builder->lpVtbl->Scale(sphere3_builder,
       D3DVAL(2), D3DVAL(2), D3DVAL(2))))
   goto generic_error;

    if (FAILED(sphere3_builder->lpVtbl->SetColorRGB(sphere3_builder, D3DVAL(1), D3DVAL(1), D3DVAL(1))))
goto generic_error;
    if (FAILED(sphere3_builder->lpVtbl->GetBox(sphere3_builder, &box)))
goto generic_error;
    maxy = box.max.y;
    miny = box.min.y;
    height = maxy - miny;

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap
(lpD3DRM, D3DRMWRAP_CYLINDER, NULL,
 D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
 D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
 D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
 D3DVAL(0.0), D3DDivide(miny, height),
 D3DVAL(1.0), D3DDivide(-D3DVAL(1.0), height),
 &wrap
)))
goto generic_error;
    if (FAILED(wrap->lpVtbl->Apply(wrap, (LPDIRECT3DRMOBJECT) sphere3_builder)))
goto generic_error;

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tex1.ppm", &tex);
    if (rval != D3DRM_OK) {
Msg("Failed to load tex1.ppm\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(tex->lpVtbl->SetShades(tex, 32)))
goto generic_error;
    if (FAILED(sphere3_builder->lpVtbl->SetTexture(sphere3_builder, tex)))
goto generic_error;

    if (FAILED(world_frame->lpVtbl->AddVisual(world_frame, (LPDIRECT3DRMVISUAL) sphere3_builder)))
goto generic_error;

    if (FAILED(camera->lpVtbl->AddMoveCallback(camera, moveCamera,
    (void *) world_frame)))
    goto generic_error;
    if (FAILED(camera->lpVtbl->AddDestroyCallback(camera, cleanupPath, NULL)))
goto generic_error;

    RELEASE(light1);
    RELEASE(world_frame);
    RELEASE(sphere3_builder);
    RELEASE(l1);
    RELEASE(l2);
    RELEASE(tex);
    RELEASE(wrap);
    return TRUE;
generic_error:
    Msg("An error occurred while building the scene.\n");
ret_with_error:
    RELEASE(light1);
    RELEASE(world_frame);
    RELEASE(l1);
    RELEASE(l2);
    RELEASE(tex);
    RELEASE(wrap);
    RELEASE(sphere3_builder);
    RELEASE(mat);
    return FALSE;
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    int i;

    view = view;/* not used */

    /*
     * This example flys a camera around a globe. The path taken is
     * defined by a spline curve.
     */

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateAnimation(lpD3DRM, &anim)))
goto generic_error;
    if (FAILED(anim->lpVtbl->SetOptions(anim, D3DRMANIMATION_CLOSED | D3DRMANIMATION_LINEARPOSITION | D3DRMANIMATION_POSITION)))
goto generic_error;
    for (i = 0; i < motion_points; i++) {
        if (FAILED(anim->lpVtbl->AddPositionKey(anim, D3DVAL(i), motion[i].x, motion[i].y,
                         motion[i].z/2)))
 goto generic_error;
    }
    if (!run_world_scene(scene, camera))
goto ret_with_error;
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    lstrcpy(defaults->Name, "Globe Direct3DRM Example");
}


README.TXT

Hier1
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

A simple hierarchy of two meshes demonstrating the concept of frames.


HIER1.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: hier1.c
 *
 ***************************************************************************/

/*
 * RL Tutorial program 2 frame hierarchy
 */

#include "rmdemo.h"

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_FLAT;
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMFRAME axis = NULL;
    LPDIRECT3DRMMESH torus_mesh = NULL;
    LPDIRECT3DRMMESH sphere_mesh = NULL;
    LPDIRECT3DRMFRAME torus = NULL;
    LPDIRECT3DRMFRAME sphere = NULL;
    LPDIRECT3DRMLIGHT lp = NULL;
    LPDIRECT3DRMLIGHT la = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    HRESULT rval;

    view = view;/* not used */

    /*
     * This Demo shows a simple hierarchy of frames
     */

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;

    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetPosition(lights, scene, D3DVAL(5), D3DVAL(5),
                                -D3DVAL(9))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_POINT, D3DVAL(0.9),
                                  D3DVAL(0.8), D3DVAL(0.7), &lp)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, lp)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &la)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, la)))
goto generic_error;

    /*
     * load mesh files
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "torus.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load torus.x\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &torus_mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "sphere2.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere2.x\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &sphere_mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(torus_mesh->lpVtbl->SetGroupColorRGB(torus_mesh, -1, D3DVAL(0.0),
                                            D3DVAL(0.0), D3DVAL(1.0))))
    goto generic_error;

    /*
     * create a torus frame within the scene create axis frame within
     * frame of sphere create torus frame within frame of axis
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &sphere)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, sphere, &axis)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, axis, &torus)))
goto generic_error;

    {
/*
 * load a texture and wrap it onto the sphere
 */
LPDIRECT3DRMTEXTURE tex;
LPDIRECT3DRMWRAP wrap;
D3DVALUE height;

D3DRMBOX box;
D3DVALUE miny, maxy;
if (FAILED(sphere_mesh->lpVtbl->GetBox(sphere_mesh, &box)))
    goto generic_error;
maxy = box.max.y;
miny = box.min.y;
height = maxy - miny;

if (FAILED(lpD3DRM->lpVtbl->CreateWrap(lpD3DRM, D3DRMWRAP_CYLINDER, NULL, D3DVAL(0.0),
                          D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
                          D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
                          D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
                          D3DDivide(maxy, height), D3DVAL(1.0),
                          D3DDivide(D3DVAL(1), height), &wrap)))
  goto generic_error;
if (FAILED(wrap->lpVtbl->Apply(wrap, (LPDIRECT3DRMOBJECT)sphere_mesh)))
    goto generic_error;
RELEASE(wrap);

rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tex2.ppm", &tex);
if (rval != D3DRM_OK) {
            Msg("Failed to load tex2.ppm\n%s", D3DRMErrorToString(rval));
    goto ret_with_error;
}
if (FAILED(tex->lpVtbl->SetColors(tex, 16)))
    goto generic_error;
if (FAILED(tex->lpVtbl->SetShades(tex, 8)))
    goto generic_error;
if (FAILED(sphere_mesh->lpVtbl->SetGroupTexture(sphere_mesh, -1, tex)))
    goto generic_error;
RELEASE(tex);
    }
    /*
     * add the loaded mesh into the frame
     */
    if (FAILED(torus->lpVtbl->AddVisual(torus, (LPDIRECT3DRMVISUAL) torus_mesh)))
goto generic_error;
    if (FAILED(sphere->lpVtbl->AddVisual(sphere, (LPDIRECT3DRMVISUAL) sphere_mesh)))
goto generic_error;

    /*
     * set up the frames position, orientation and rotation
     */
    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0), D3DVAL(0), -D3DVAL(10))))
goto generic_error;
    if (FAILED(sphere->lpVtbl->SetPosition(sphere, scene, D3DVAL(0), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(axis->lpVtbl->SetPosition(axis, sphere, D3DVAL(2), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(torus->lpVtbl->SetPosition(torus, axis, D3DVAL(1.0), D3DVAL(0), D3DVAL(0))))
goto generic_error;

    if (FAILED(sphere->lpVtbl->SetRotation(sphere, scene, D3DVAL(0), D3DVAL(1.0), D3DVAL(0.5),D3DVAL(0.05))))
goto generic_error;
    if (FAILED(axis->lpVtbl->SetRotation(axis, sphere, D3DVAL(1), D3DVAL(0), D3DVAL(0), D3DVAL(0.01))))
goto generic_error;
    if (FAILED(torus->lpVtbl->SetRotation(torus, axis, D3DVAL(0), D3DVAL(0), D3DVAL(1), -D3DVAL(0.05))))
goto generic_error;

    RELEASE(lights);
    RELEASE(axis);
    RELEASE(torus_mesh);
    RELEASE(sphere_mesh);
    RELEASE(torus);
    RELEASE(sphere);
    RELEASE(lp);
    RELEASE(la);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(lights);
    RELEASE(axis);
    RELEASE(torus_mesh);
    RELEASE(sphere_mesh);
    RELEASE(torus);
    RELEASE(sphere);
    RELEASE(lp);
    RELEASE(la);
    RELEASE(builder);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Frame Hierarchy D3D RM Example");
}


README.TXT

Hier2
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Another simple hierarchy of four meshes.


HIER2.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: hier2.c
 *
 ***************************************************************************/

/*
 * D3D RM Tutorial program frame hierarchy 2
 */

#include "rmdemo.h"

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_FLAT;
    LPDIRECT3DRMLIGHT l1 = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMMESHBUILDER torus_builder = NULL;
    LPDIRECT3DRMMESHBUILDER sphere_builder = NULL;
    LPDIRECT3DRMMESHBUILDER cube1_builder = NULL;
    LPDIRECT3DRMMESHBUILDER cube2_builder = NULL;
    LPDIRECT3DRMMESH cube1_mesh = NULL;
    LPDIRECT3DRMMESH cube2_mesh = NULL;
    LPDIRECT3DRMMESH torus_mesh = NULL;
    LPDIRECT3DRMMESH sphere_mesh = NULL;
    LPDIRECT3DRMFRAME torus = NULL;
    LPDIRECT3DRMFRAME sphere = NULL;
    LPDIRECT3DRMFRAME cube1 = NULL;
    LPDIRECT3DRMFRAME cube2 = NULL;
    HRESULT rval;
    view = view;/* not used */

    /*
     * This Demo shows a more complex hierarchy of frames
     */

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;
    if (FAILED(dev->lpVtbl->SetShades(dev, 16)))
goto generic_error;

    /*
     * initialize the lights in the scene
     */

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetPosition(lights, scene, D3DVAL(5), D3DVAL(5),
                                -D3DVAL(5))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_POINT, D3DVAL(0.9),
                                  D3DVAL(0.8), D3DVAL(0.7), &l1)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, l1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &l2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l2)))
goto generic_error;
    /*
     * load mesh files
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &torus_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &sphere_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &cube1_builder)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &cube2_builder)))
goto generic_error;
    rval = torus_builder->lpVtbl->Load(torus_builder, "torus.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load torus.x.\n");
goto ret_with_error;
    }
    rval = sphere_builder->lpVtbl->Load(sphere_builder, "sphere4.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere4.x.\n");
goto ret_with_error;
    }
    rval = cube1_builder->lpVtbl->Load(cube1_builder, "cube.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load cube.x.\n");
goto ret_with_error;
    }
    rval = cube2_builder->lpVtbl->Load(cube2_builder, "cube.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load cube.x.\n");
goto ret_with_error;
    }

    if (FAILED(cube1_builder->lpVtbl->Scale(cube1_builder, D3DVAL(0.25), D3DVAL(0.5),
                                 D3DVAL(1.0))))
 goto generic_error;
    if (FAILED(cube2_builder->lpVtbl->Scale(cube2_builder, D3DVAL(0.5), D3DVAL(1.5),
                                 D3DVAL(1.0))))
 goto generic_error;

    if (FAILED(cube1_builder->lpVtbl->SetColorRGB(cube1_builder, D3DVAL(0.7),
                                        D3DVAL(0.0), D3DVAL(0.8))))
goto generic_error;
    if (FAILED(cube2_builder->lpVtbl->SetColorRGB(cube2_builder, D3DVAL(0.0),
                                        D3DVAL(1.0), D3DVAL(0.5))))
goto generic_error;
    if (FAILED(torus_builder->lpVtbl->SetColorRGB(torus_builder, D3DVAL(0.2),
                                        D3DVAL(1.0), D3DVAL(0.8))))
goto generic_error;

    if (FAILED(torus_builder->lpVtbl->CreateMesh(torus_builder, &torus_mesh)))
goto generic_error;
    if (FAILED(sphere_builder->lpVtbl->CreateMesh(sphere_builder, &sphere_mesh)))
goto generic_error;
    if (FAILED(cube1_builder->lpVtbl->CreateMesh(cube1_builder, &cube1_mesh)))
goto generic_error;
    if (FAILED(cube2_builder->lpVtbl->CreateMesh(cube2_builder, &cube2_mesh)))
goto generic_error;
    RELEASE(torus_builder);
    RELEASE(sphere_builder);
    RELEASE(cube1_builder);
    RELEASE(cube2_builder);

    /*
     * create a torus frame within the scene create torus frame within
     * frame of sphere cube frame within frame of torus
     */

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &sphere)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, sphere, &torus)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, torus, &cube1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, torus, &cube2)))
goto generic_error;

    /*
     * add the loaded mesh into the frame
     */

    if (FAILED(torus->lpVtbl->AddVisual(torus, (LPDIRECT3DRMVISUAL) torus_mesh)))
goto generic_error;
    if (FAILED(sphere->lpVtbl->AddVisual(sphere, (LPDIRECT3DRMVISUAL) sphere_mesh)))
goto generic_error;
    if (FAILED(cube1->lpVtbl->AddVisual(cube1, (LPDIRECT3DRMVISUAL) cube1_mesh)))
goto generic_error;
    if (FAILED(cube2->lpVtbl->AddVisual(cube2, (LPDIRECT3DRMVISUAL) cube2_mesh)))
goto generic_error;

    /*
     * set up the frames position, orientation and rotations
     */

    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0), D3DVAL(0), -D3DVAL(25))))
goto generic_error;
    if (FAILED(sphere->lpVtbl->SetPosition(sphere, scene, D3DVAL(0), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(torus->lpVtbl->SetPosition(torus, sphere, D3DVAL(6), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(cube1->lpVtbl->SetPosition(cube1, torus, D3DVAL(0), D3DVAL(4), D3DVAL(0))))
goto generic_error;
    if (FAILED(cube2->lpVtbl->SetPosition(cube2, torus, D3DVAL(0), -D3DVAL(4), D3DVAL(0))))
goto generic_error;

    if (FAILED(sphere->lpVtbl->SetRotation(sphere, scene, D3DVAL(0), D3DVAL(0), D3DVAL(1),D3DVAL(0.01))))
goto generic_error;
    if (FAILED(torus->lpVtbl->SetRotation(torus, sphere, D3DVAL(1), D3DVAL(0), D3DVAL(0), D3DVAL(0.02))))
goto generic_error;
    if (FAILED(cube1->lpVtbl->SetRotation(cube1, torus, D3DVAL(0.1), D3DVAL(0.2),
                               D3DVAL(0.7), D3DVAL(0.03))))
       goto generic_error;
    if (FAILED(cube2->lpVtbl->SetRotation(cube2, torus, D3DVAL(0.7), D3DVAL(0.1),
                               D3DVAL(0.2), D3DVAL(0.03))))
       goto generic_error;

    RELEASE(lights);
    RELEASE(torus_mesh);
    RELEASE(sphere_mesh);
    RELEASE(cube1_mesh);
    RELEASE(cube2_mesh);
    RELEASE(torus);
    RELEASE(sphere);
    RELEASE(cube1);
    RELEASE(cube2);
    RELEASE(l1);
    RELEASE(l2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(l1);
    RELEASE(l2);
    RELEASE(lights);
    RELEASE(torus_builder);
    RELEASE(sphere_builder);
    RELEASE(cube1_builder);
    RELEASE(cube2_builder);
    RELEASE(cube1_mesh);
    RELEASE(cube2_mesh);
    RELEASE(torus_mesh);
    RELEASE(sphere_mesh);
    RELEASE(torus);
    RELEASE(sphere);
    RELEASE(cube1);
    RELEASE(cube2);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bNoTextures = TRUE;
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Frame Hierarchy II D3D RM Example");
}


README.TXT

MEMTIME

This is an x86-only sample.

Memory bandwidth tests.  Times various operations on system and video
memory. Memtime is intended to show the advantages and disadvantages of
different drawing methods.

BUILDING WITH MICROSOFT VISUAL C++ 4.0 AND HIGHER

From the File/Open Workspace menu, open the makefile file and reply
'Yes' when asked if you wish to create a wrapper project.


MEMTIME.CPP

//--------------------------------------------------------------------------;
//
//  File: memtime.cpp
//
//  Description:
//      This code times various things about system memory and video
//      memory. It is intended to show the advantages and disadvantages
//      of different drawing methoids.
//      Please refer to the documented for a more detailed discriptions of
//      what is going on.
//
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//---------------------------------------------------------------------------
//
//  Copyright (c) 1994 - 1997 Microsoft Corporation.  All Rights Reserved.
//
//---------------------------------------------------------------------------

#include<windows.h>
#include<windowsx.h>
#include<commdlg.h>
#include <ddraw.h>

#include"memtime.h"

/*----------------------------------------------------------------------------*\
|                                                                                                                                                          |
|   g l o b a l   v a r i a b l e s                                                                                    |
|                                                                                                                                                          |
\*----------------------------------------------------------------------------*/
static  char    szAppName[]="Direct Draw video/system memory timing tests";

static HINSTANCE    hInstApp;
static HWND         hwndApp;
static HACCEL       hAccelApp;
static HPALETTE     hpalApp;

static DWORD ScreenHeight, ScreenWidth;
static PCHAR pSource, pDest;

static LPDIRECTDRAW pDirectDrawObj;
static LPDIRECTDRAWSURFACE pOnScreenBuf;
static LPDIRECTDRAWSURFACE pOffScreenBuf;
static LPDIRECTDRAWSURFACE pSysMemBuf;

static ULONG ModeXFrameCount;
static ULONG ModeYFrameCount;
static ULONG StretchFrameCount;

/*----------------------------------------------------------------------------

Timers

*/

extern "C" MemFunc DwordMemCopy_Pitch;
extern "C" MemFunc ByteMemCopy_Pitch;

extern "C" MemFunc DwordMemFill_Pitch;
extern "C" MemFunc ByteMemFill_Pitch;

extern "C" MemFunc VertMemSto_Pitch;

TIMING_RESULT SystemTests[]=
{
    {0, DwordMemCopy_Pitch, "System to System dword copy"},
    {0, ByteMemCopy_Pitch, "System to System byte copy"},

    {0, DwordMemFill_Pitch, "System dword Fill"},
    {0, ByteMemFill_Pitch, "System byte Fill"},

    {0, VertMemSto_Pitch, "System vertical byte fill"}
};

TIMING_RESULT VideoTests[]=
{
    {0, DwordMemCopy_Pitch, "System to Video dword Copy"},
    {0, ByteMemCopy_Pitch, "System to Video byte Copy"},

    {0, DwordMemFill_Pitch, "Video dword Fill"},
    {0, ByteMemFill_Pitch, "Video byte Fill"},

    {0, VertMemSto_Pitch, "Video vertical byte fill"}
};

/*----------------------------------------------------------------------------

defines

*/

#define NUMBER_OF(aa) (sizeof(aa)/sizeof((aa)[0]))

/*----------------------------------------------------------------------------*\
|
|   f u n c t i o n   d e f i n i t i o n s
|
\*----------------------------------------------------------------------------*/

LRESULT CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
int  ErrMsg (LPSTR sz,...);
LONG AppCommand (HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
void PrintTimingResults( void );

void AppExit(void);
BOOL AppIdle(void);
BOOL DirectDrawInit();
VOID DoSystemTest( PTIMING_RESULT pTimeObject );
VOID DoVideoTests( PTIMING_RESULT pTimeObject );
ULONG GetFPS( int width, int height );
ULONG GetStretchFPS( int width, int height );

/*----------------------------------------------------------------------------*\
|   AppAbout( hDlg, uiMessage, wParam, lParam )
|
|   Description:
|       This function handles messages belonging to the "About" dialog box.
|       The only message that it looks for is WM_COMMAND, indicating the use
|       has pressed the "OK" button.  When this happens, it takes down
|       the dialog box.
|
|   Arguments:
|       hDlg        window handle of about dialog window
|       uiMessage   message number
|       wParam      message-dependent
|       lParam      message-dependent
|
|   Returns:
|       TRUE if message has been processed, else FALSE
|
\*----------------------------------------------------------------------------*/
BOOL CALLBACK
AppAbout( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch (msg) {
        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK) {
                EndDialog(hwnd,TRUE);
            }
            break;
        case WM_INITDIALOG:
            return TRUE;
    }
    return FALSE;
}

/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)
|
|   Description:
|       This is called when the application is first loaded into
|       memory.  It performs all initialization that doesn't need to be done
|       once per instance.
|
|   Arguments:
|       hInstance               instance handle of current instance
|       hPrev                   instance handle of previous instance
|
|   Returns:
|       TRUE if successful, FALSE if not
|
\*----------------------------------------------------------------------------*/
BOOL
AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw, LPSTR szCmdLine )
{
    WNDCLASS cls;
    HRESULT ddrval;
    DDSURFACEDESC ddsd;
    BOOL returnValue = FALSE;

    /* Save instance handle for DialogBoxs */
    hInstApp = hInst;

    hAccelApp = LoadAccelerators(hInst, "AppAccel");

    if (!hPrev) {
        /*
         *      Register a class for the main application window
         */
        cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
        cls.hIcon                  = LoadIcon(hInst,"AppIcon");
        cls.lpszMenuName   = "AppMenu";
        cls.lpszClassName  = szAppName;
        cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        cls.hInstance      = hInst;
        cls.style                  = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
        cls.lpfnWndProc    = (WNDPROC)AppWndProc;
        cls.cbWndExtra     = 0;
        cls.cbClsExtra     = 0;

        if (RegisterClass(&cls)) {
            hwndApp = CreateWindow (
                        szAppName,      // Class name
                        szAppName,      // Caption
                        WS_OVERLAPPEDWINDOW,        // Style bits
                        50, 50,         // Position
                        400+20,400+75,  // Size
                        (HWND)NULL,     // Parent window (no parent)
                        (HMENU)NULL,    // use class menu
                        hInst,          // handle to window instance
                        (LPSTR)NULL);   // no params to pass on
            ShowWindow(hwndApp,sw);

            // init our direct draw stuff
            ddrval = DirectDrawCreate( NULL, &pDirectDrawObj, NULL );
            if( DD_OK == ddrval) {
                // clear out the surface desctiptions
                ZeroMemory( &ddsd, sizeof( ddsd ) );
                // set the size
                ddsd.dwSize = sizeof( ddsd );

                // figure out what display mode we are in now
                ddrval = pDirectDrawObj->GetDisplayMode( &ddsd );
                if( DD_OK == ddrval ) {
                    // make sure we're in  a 8-bit mode
                    if( ddsd.ddpfPixelFormat.dwRGBBitCount >= 8) {
                        ScreenHeight = ddsd.dwHeight;
                        ScreenWidth = ddsd.dwWidth;

                        // if we are biger than 800x600 it's to much memory
                        // to move around. Clip it
                        if(ScreenHeight > 600) ScreenHeight = 600;
                        if(ScreenWidth > 800) ScreenWidth = 800;

                        if( (ScreenWidth < 800) || (ScreenHeight < 600) ) {
                            ErrMsg("Resolutions smaller than 800x600 may yeild different numbers because of L2 cache effects");
                        }

                        // allocate some memory we are going to play with
                        pSource = (PCHAR)GlobalAllocPtr(GHND, ScreenHeight * ScreenWidth);
                        pDest = (PCHAR)GlobalAllocPtr(GHND, ScreenHeight * ScreenWidth);

                        // make sure the memory worked and it's dword alligned.
                        // (we're in Win32, when is not going to be dword alligned?)
                        if( pSource && pDest && !((DWORD)pSource & 0x3) && !((DWORD)pDest & 0x3)) {
                            returnValue = TRUE;
                        } else {
                            ErrMsg("Memory allocation failure");
                        }
                    } else {
                        ErrMsg("You must be in a 8-bit (or better) color mode to run this app");
                    }
                } else {
                    ErrMsg("DirectDraw failure getting the display mode");
                }
            } else {
                ErrMsg("DirectDraw create failure");
            }
        } else {
            ErrMsg("Register Class failure");
        }
    }
    return returnValue;
}

/*----------------------------------------------------------------------------*\
|   BandwidthTestsSetup
|
|   Description:
|       Setup the surfaces for the bandwidth tests
|
|   Returns:
|       TRUE if successful, FALSE if not
\*----------------------------------------------------------------------------*/
BOOL
BandwidthTestsSetup( VOID )
{
    DDSURFACEDESC ddsd;
    BOOL returnValue = FALSE;
    HRESULT ddrval;

    if( pOnScreenBuf ) pOnScreenBuf->Release();
    if( pOffScreenBuf ) pOffScreenBuf->Release();
    if( pSysMemBuf ) pSysMemBuf->Release();

    // Tell the world we love it and want to share
    ddrval = pDirectDrawObj->SetCooperativeLevel( hwndApp, DDSCL_NORMAL );
    if( DD_OK == ddrval ) {
        // clear out out stuff
        ZeroMemory( &ddsd, sizeof( ddsd ) );
        ddsd.dwSize = sizeof( ddsd );

        ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
        ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

        ddsd.dwHeight = ScreenHeight;
        ddsd.dwWidth = ScreenWidth;
        // create our off screen video memory.
        ddrval = pDirectDrawObj->CreateSurface( &ddsd, &pOffScreenBuf, NULL );
        if( DD_OK == ddrval ) {
            // yay!
            returnValue = TRUE;
        } else {
            ErrMsg("Failure creating video surface");
        }
    } else {
        ErrMsg("Failure setting Co-op mode");
    }
    return returnValue;
}
/*----------------------------------------------------------------------------*\
|   AppExit()
|
|   Description:
|       app is just about to exit, cleanup
|
\*----------------------------------------------------------------------------*/
void AppExit()
{
    if( pSource ) GlobalFreePtr(pSource);
    if( pDest ) GlobalFreePtr(pDest);
    if( pOffScreenBuf ) pOffScreenBuf->Release();
    if( pOnScreenBuf ) pOnScreenBuf->Release();
    if( pSysMemBuf ) pSysMemBuf->Release();
    if( pDirectDrawObj ) pDirectDrawObj->Release();

}

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                                                      |
|
|   Description:
|       The main procedure for the App.  After initializing, it just goes
|       into a message-processing loop until it gets a WM_QUIT message
|       (meaning the app was closed).                                                                              |
|
|   Arguments:
|       hInst       instance handle of this instance of the app
|       hPrev       instance handle of previous instance, NULL if first
|       szCmdLine   ->null-terminated command line
|       cmdShow     specifies how the window is initially displayed
|
|       Returns:
|               The exit code as specified in the WM_QUIT message.
|
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG msg;

    /* Call initialization procedure */
    if (!AppInit(hInst,hPrev,sw,szCmdLine))
    return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;) {
        if(!GetMessage(&msg, NULL, 0, 0)) {
            return msg.wParam;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

/*----------------------------------------------------------------------------*\
|   AppPaint(hwnd, hdc)
|
|   Description:
|       The paint function.  Right now this does nothing.
|
|   Arguments:
|       hwnd    window painting into
|       hdc     display context to paint to
|
|   Returns:
|       nothing
|
\*----------------------------------------------------------------------------*/
AppPaint (HWND hwnd, HDC hdc)
{
    TEXTMETRIC tm;
    int     x,y;
    RECT    rc;

#define PRINT_STUFF(sz) (TextOut(hdc, x, y, sz, lstrlen(sz)), y += tm.tmHeight+1)

    SelectObject(hdc, GetStockObject(ANSI_FIXED_FONT));
    GetTextMetrics(hdc, &tm);

    GetClientRect(hwnd,&rc);

    SetTextColor(hdc,GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(hdc,GetSysColor(COLOR_WINDOW));

    // setup start pos
    x=4;
    y=4;

    PRINT_STUFF("Select 'Time All' to run tests");
    y += (tm.tmHeight+1) * 2;
    PRINT_STUFF("The screen will go blank during some tests");

    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )
|
|   Description:
|       The window proc for the app's main (tiled) window.
|       This processes all of the parent window's messages.
|
\*----------------------------------------------------------------------------*/
LRESULT CALLBACK AppWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch (msg) {
        case WM_COMMAND:
            return AppCommand(hwnd,msg,wParam,lParam);
            break;

        case WM_PAINT:
            hdc = BeginPaint(hwnd,&ps);
            AppPaint( hwnd, hdc );
            break;

        case WM_DESTROY:
            hAccelApp = NULL;
            PostQuitMessage(0);
            break;

    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*----------------------------------------------------------------------------*\
|   AppCommand(hwnd, msg, wParam, lParam )
|
|   Description:
|       handles WM_COMMAND messages for the main window (hwndApp)
|       of the parent window's messages.
|
\*----------------------------------------------------------------------------*/
LONG AppCommand (HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    char achFileName[128] = "";

    switch(wParam) {
        case MENU_ABOUT:
            DialogBox(hInstApp,"AppAbout",hwnd,(DLGPROC)AppAbout);
            break;

        // do all the timing tests
        case MENU_TIMEALL:
            {
            HCURSOR Arror = SetCursor(LoadCursor(0,IDC_WAIT));

            ShowCursor(FALSE);  //take care of animated Plus! cursors

            DWORD PriorityClass = GetPriorityClass(GetCurrentProcess());
            int ThreadPriority = GetThreadPriority(GetCurrentThread());

            if( NUMBER_OF(SystemTests) != NUMBER_OF(VideoTests))
            {
                ErrMsg("test number mismatch");
                break;
            }

            // Jack this thread up to realtime.
            // NOTE: This is very bad from for the real world. The rule is
            // "The Higher your ptiority, the less work your thread should do".
            // We are doing a lot of work. But this is only a test app, so who cares.
            SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
            SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);

            // setup the surfaces for the bandwidth tests
            BandwidthTestsSetup();
            // Walk through the list of timers, and call them all

            for(int Counter = 0;Counter < NUMBER_OF(SystemTests) ;Counter++) {

                SetWindowText(hwndApp,SystemTests[Counter].pDescription);

                DoSystemTest( &SystemTests[Counter] );

                SetWindowText(hwndApp,VideoTests[Counter].pDescription);

                DoVideoTests( &VideoTests[Counter] );

            }

            // do 320x240 frame rate tests
            ModeXFrameCount = GetFPS(320,240);
            // do 640x480 frame rate tests
            ModeYFrameCount = GetFPS(640,480);
            // do 320x240 stretch to 640x480 frame rate tests
            StretchFrameCount = GetStretchFPS(640,480);

            // return ourselves to normal
            SetPriorityClass(GetCurrentProcess(),PriorityClass);
            SetThreadPriority(GetCurrentThread(),ThreadPriority);

            SetCursor(Arror);
            SetWindowText(hwndApp,szAppName);

            ShowCursor(TRUE);
            PrintTimingResults();
            }
            break;

        case MENU_EXIT:
            PostMessage(hwnd,WM_CLOSE,0,0L);
            break;
    }
    return 0L;
}

/*----------------------------------------------------------------------------*\
|   ErrMsg - Opens a Message box with a error message in it.  The user can
|       select the OK button to continue
\*----------------------------------------------------------------------------*/
int ErrMsg (LPSTR sz,...)
{
        char ach[128];
        va_list va;

        va_start( va, sz );
        wvsprintf ( ach, sz, va );       /* Format the string */
        va_end( va );
        MessageBox(hwndApp,ach,szAppName,MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
        return FALSE;
}

/*----------------------------------------------------------------------------*\
|   PrintTimingResults( void )
|
|   Description:
|       Print the results of the timting tests
|
\*----------------------------------------------------------------------------*/
#include <stdio.h>

VOID
PrintTimingResults( void )
{
    double result;
    LARGE_INTEGER perfFreq;
    char OutputBuffer[2048];
    char TempOutputBuffer[2048];
#define PRINT_DOUBLE(d) {sprintf(TempOutputBuffer,"%12.8f",d);strcat(OutputBuffer,TempOutputBuffer);}
#define PRINT_STRING(s) {sprintf(TempOutputBuffer,"%s",s);strcat(OutputBuffer,TempOutputBuffer);}

    OutputBuffer[0]=0;
    TempOutputBuffer[0]=0;

    // get the frequemcy out of timer
    QueryPerformanceFrequency( &perfFreq );

    // Turn all the time resutls into MB per second
    for(int Counter = 0; Counter < NUMBER_OF(SystemTests) ; Counter++) {
        if(SystemTests[Counter].Time) {
            result = (double)SystemTests[Counter].Iterations;
            result /= (double)SystemTests[Counter].Time / (double)perfFreq.LowPart;
            result /= (double)1000000.0;
            PRINT_DOUBLE(result);
        } else {
            PRINT_STRING("N/A");
        }
        PRINT_STRING(" MB/sec \t");
        PRINT_STRING(SystemTests[Counter].pDescription);
        PRINT_STRING("\n");

        if(VideoTests[Counter].Time) {
            result = (double)VideoTests[Counter].Iterations;
            result /= (double)VideoTests[Counter].Time / (double)perfFreq.LowPart;
            result /= (double)1000000.0;
            PRINT_DOUBLE(result);
        } else {
            PRINT_STRING("N/A");
        }
        PRINT_STRING(" MB/sec \t");
        PRINT_STRING(VideoTests[Counter].pDescription);
        PRINT_STRING("\n");

    }

    if( ModeXFrameCount ) {
        PRINT_DOUBLE((double)ModeXFrameCount/5.0);
    } else {
        PRINT_STRING("N/A");
    }
    PRINT_STRING(" FPS \t\t");
    PRINT_STRING("320x240 Frame Rate");
    PRINT_STRING("\n");

    if( ModeYFrameCount ) {
        PRINT_DOUBLE((double)ModeYFrameCount/5.0);
    } else {
        PRINT_STRING("N/A");
    }
    PRINT_STRING(" FPS \t\t");
    PRINT_STRING("640x480 Frame Rate");
    PRINT_STRING("\n");

    if( StretchFrameCount ) {
        PRINT_DOUBLE((double)StretchFrameCount/5.0);
    } else {
        PRINT_STRING("N/A");
    }
    PRINT_STRING(" FPS \t\t");
    PRINT_STRING("320x240 stretched to 640x480");
    PRINT_STRING("\n");

#ifndef LOR_TESTS
    // display the results
    MessageBox(0, OutputBuffer ,"Timing Results",MB_OK);
#endif

}

VOID DoSystemTest( PTIMING_RESULT pTimeObject )
{
    LARGE_INTEGER startTime, endTime;

    QueryPerformanceCounter( &startTime );

    // do the memory copy
    pTimeObject->Iterations = (*pTimeObject->pTimer)(pSource, pDest, ScreenHeight, ScreenWidth, ScreenWidth, 100 );

    QueryPerformanceCounter( &endTime );

    // if it takes more than 32bits of clock ticks
    // it's not worth profiling.
    pTimeObject->Time = endTime.LowPart - startTime.LowPart;
}

VOID DoVideoTests( PTIMING_RESULT pTimeObject )
{
    DDSURFACEDESC ddsd;
    HRESULT returnValue;
    LARGE_INTEGER startTime, endTime;

    ZeroMemory( &ddsd, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );

    // lock the video memory surfce
    // NOTE: We are about to do a lock for a REALLY long time.
    // this is really bad form in the real world.
    //
    returnValue = pOffScreenBuf->Lock( NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL);
    if( returnValue == DD_OK )
    {
        if( ddsd.lPitch && ddsd.dwWidth && ddsd.dwHeight && ddsd.lpSurface)
        {
            // get the time, and do the copy
            QueryPerformanceCounter( &startTime );

            pTimeObject->Iterations = (*pTimeObject->pTimer)( pSource, ddsd.lpSurface, ddsd.dwHeight, ddsd.dwWidth, ddsd.lPitch, 100 );

            QueryPerformanceCounter( &endTime );

            pOffScreenBuf->Unlock( ddsd.lpSurface );
            // stor the delta time
            pTimeObject->Time = endTime.LowPart - startTime.LowPart;
        }
        else
        {
            pOffScreenBuf->Unlock( ddsd.lpSurface );
            ErrMsg("Lock returned bogus Session Description stuff");
        }
    }
    else
        ErrMsg("Can't lock surface");

    return;
}

BOOL
InitFullScreen(
    DWORD Height,
    DWORD Width
    )
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;
    HRESULT ddrval;
    ULONG returnValue = FALSE;

    // Kill our current offscreen surface.
    if( pOffScreenBuf ) {
        ddrval = pOffScreenBuf->Release();
        if( ddrval != DD_OK ) {
            ErrMsg("Can't release offscreen buf");
        }
    }

    // Get exclusive mode
    ddrval = pDirectDrawObj->SetCooperativeLevel( hwndApp, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | DDSCL_NOWINDOWCHANGES );

    if( ddrval == DD_OK ){
        // set the display mode to the requested mode
        ddrval = pDirectDrawObj->SetDisplayMode( Height, Width, 8);
        if(ddrval == DD_OK){
            ddsd.dwSize = sizeof( ddsd );
            ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
            ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                          DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX;
            ddsd.dwBackBufferCount = 1;
            // get the primary buffer
            ddrval = pDirectDrawObj->CreateSurface( &ddsd, &pOnScreenBuf, NULL );

            if( ddrval == DD_OK ) {
                ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
                // the back buffer might be in system memory depending
                // on the card and the mode.
                ddrval = pOnScreenBuf->GetAttachedSurface(&ddscaps, &pOffScreenBuf);

                if( ddrval == DD_OK ) {
                    returnValue = TRUE;
                }
            }
        } else {
            ErrMsg("can't set display mode %d x %d",Width,Height);
        }
    }
    return returnValue;
}

BOOL
CreateSysMemSurface( VOID )
{
    DDSURFACEDESC ddsd;
    ULONG returnValue;

    // clear out out stuff
    ZeroMemory( &ddsd, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );

    // build a surface in system memory
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY;

    ddsd.dwHeight = 240;
    ddsd.dwWidth = 320;
    returnValue = pDirectDrawObj->CreateSurface( &ddsd, &pSysMemBuf, NULL );
    if( DD_OK == returnValue ) {
        return TRUE;
    }

    return FALSE;
}

ULONG
GetStretchFPS( int width, int height )
{
    ULONG time;
    HRESULT ddrval;
    DDSURFACEDESC ddsd;
    DDBLTFX     ddbltfx;
    ULONG FrameCount;

    // set the mode (and create the surfaces)
    if( InitFullScreen( width, height ) ) {
        // get the system memory surface
        if( CreateSysMemSurface() ) {

            // build the ROP for the blt
            ZeroMemory( &ddbltfx, sizeof(ddbltfx) );
            ddbltfx.dwSize = sizeof( ddbltfx );
            ddbltfx.dwROP = SRCCOPY;

            time = timeGetTime();
            // wait for a seconds for the mode to settle out
            // (I don't need to do this.. I'm just paranoid)
            while( (timeGetTime() - time) < 1000);
            FrameCount = 0;
            time = timeGetTime();

            // Initialize the surface description structure
            ZeroMemory(&ddsd, sizeof(ddsd));
            ddsd.dwSize=sizeof(ddsd);

            ddrval = 0;
            // do this for 5 seconds
            while( ((timeGetTime() - time) < 5000) && (DD_OK == ddrval) ) {
                // simluate rendering and off screen frame (with a copy)
                ddrval = pSysMemBuf->Lock( NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
                if (FAILED(ddrval))
                {
                    FrameCount=0;
                    break;
                }

                DwordMemCopy_Pitch( pSource, ddsd.lpSurface, ddsd.dwHeight, ddsd.dwWidth, ddsd.lPitch, 1);

                ddrval = pSysMemBuf->Unlock( NULL );
                if (FAILED(ddrval))
                {
                    FrameCount=0;
                    break;
                }

                // blt the system buffer into the back buffer
                ddrval = pOffScreenBuf->Blt( NULL, pSysMemBuf, NULL, DDBLT_ROP, &ddbltfx);
                if (FAILED(ddrval))
                {
                    FrameCount=0;
                    break;
                }

                // flip the back buffer on screen
                ddrval = pOnScreenBuf->Flip( NULL, DDFLIP_WAIT );
                if (FAILED(ddrval))
                {
                    FrameCount=0;
                    break;
                }

                FrameCount++;
            }
        }
        pDirectDrawObj->RestoreDisplayMode();
        if( DD_OK != ddrval ) {
            ErrMsg("Test failure %X",ddrval);
        }

        pOffScreenBuf->Release();
        pOffScreenBuf = NULL;
        pOnScreenBuf->Release();
        pOnScreenBuf = NULL;
        pSysMemBuf->Release();
        pSysMemBuf = NULL;
    }
    return FrameCount;
}

ULONG GetFPS( int width, int height )
{

ULONG time;
    DDSURFACEDESC ddsd;
    HRESULT ddrval;
    ULONG FrameCount;

    ZeroMemory( &ddsd, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );

    // set the mode (and setup the surfaces)
    if( InitFullScreen( width, height ) )
    {
        time = timeGetTime();
        // wait for a seconds for the mode to settle out
        // (I don't need to do this, I'm just paranoid)
        while( (timeGetTime() - time) < 1000);
        ddrval = DD_OK;
        FrameCount = 0;
        time = timeGetTime();
        while( ((timeGetTime() - time) < 5000) && (ddrval == DD_OK) )
        {
            // simluate a render into the back buffer with a copy
            ddrval = pOffScreenBuf->Lock( NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
            if (FAILED(ddrval))
            {
                FrameCount=0;
                break;
            }
            // Stuff some Junk into the offscreen surface
            DwordMemCopy_Pitch( pSource, ddsd.lpSurface, ddsd.dwHeight, ddsd.dwWidth, ddsd.lPitch, 1 );

            ddrval = pOffScreenBuf->Unlock( NULL );
            if (FAILED(ddrval))
            {
                FrameCount=0;
                break;
            }

            // flip the off screen surface on screen
            ddrval = pOnScreenBuf->Flip( NULL, DDFLIP_WAIT );
            if (FAILED(ddrval))
            {
                FrameCount=0;
                break;
            }

            FrameCount++;
        }
        pDirectDrawObj->RestoreDisplayMode();
        if( FAILED(ddrval) )
            ErrMsg("Test failure %X",ddrval);
        pOffScreenBuf->Release();
        pOffScreenBuf = NULL;
        pOnScreenBuf->Release();
        pOnScreenBuf = NULL;
    }

    return FrameCount;
}

/*****************************************************************************
 *
 * dprintf() is called by the DPF macro if DEBUG is defined at compile time.
 *
 * The messages will be send to COM1: like any debug message. To
 * enable debug output, add the following to WIN.INI :
 *
 * [debug]
 * QA=1
 *
 ****************************************************************************/

#ifdef DEBUG

#define MODNAME "QA"

#define _WINDLL
#include <stdarg.h>

void FAR CDECL dprintf(LPSTR szFormat, ...)
{
    char ach[128];
    va_list va;

    static BOOL fDebug = -1;

    if (fDebug == -1) {
        fDebug = GetProfileIntA("Debug", MODNAME, TRUE);
    }

    if (!fDebug) return;

    lstrcpyA(ach, MODNAME ": ");
    va_start(va, szFormat);
    wvsprintfA(ach+lstrlenA(ach),szFormat,(LPSTR)va);
    va_end(va);
    lstrcatA(ach, "\r\n");

    OutputDebugStringA(ach);
}

#endif


MEMTIME.H

/* Menu Items */
#define MENU_ABOUT       2
#define MENU_EXIT        4
#define MENU_OPEN        5
#define MENU_COPY        6
#define MENU_PASTE       7
#define MENU_CUT         8
#define MENU_TIMEALL 9
#define MENU_REALTIME 10

#define MENU_TIME       50

/****************************************************************************
****************************************************************************/
struct _timing_result;

typedef DWORD __cdecl MemFunc(PVOID pSource, PVOID pDest, DWORD Heigh, DWORD Width, DWORD Pitch, DWORD Count);

typedef struct _timing_result
{
        DWORD Time;
        MemFunc *pTimer;
        char const *pDescription;
        DWORD Iterations;
} TIMING_RESULT, *PTIMING_RESULT;

#ifdef DEBUG
    extern void FAR CDECL dprintf(LPSTR szFormat, ...);
    #define DPF dprintf
#else
    #define DPF ; / ## /
#endif


README.TXT

Mid2Strm Sample Readme File
===========================

This sample is related to the mstream sample, and contains a subset of the
functionality of mstream.

Mid2strm converts a .mid file (standard midi file) into a format which can
be easily sent to the the midiStreamXxx APIs.  It takes a .mid file as
input, and produces a .mds file as output.  Note that .mds files are not
a standard Windows file format; for example, they can't be played back by
mplayer.  Your application must play the file back by sending the data to
the midiStreamXxx APIs as shown in the mstream sample.

The midiStreamXxx APIs expect a buffer containing 3 DWORDs per MIDI event:
the event, the time stamp, and the stream ID.  The mid2strm sample will
create a buffer of this format and store it as a .mds file.  Of course,
the file will be larger than the .mid file.  To save space, you can specify
the -c option.  This specifies that stream IDs should not be included in the
file, compressing the file by 1/3.  This assumes that all stream IDs will be
zero; for most applications, this is sufficient.  Note, however, that the
stream ID DWORD will have to be inserted into the buffer before it is sent
to the midiStreamXxx APIs.

Please see the mstream readme file for more information.


MID2STRM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:mid2strm.c
 *  Content:Converts a MIDI file into a MDS (MidiStream) File.
 *
 ***************************************************************************/
#include <stdio.h>
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <assert.h>

// MIDI file constants
//
#define MThd0x6468544D// Start of file
#define MTrk0x6B72544D// Start of track

#define MIDI_SYSEX((BYTE)0xF0)// SysEx begin
#define MIDI_SYSEXEND((BYTE)0xF7)// SysEx begin
#define MIDI_META((BYTE)0xFF)// Meta event begin
#define MIDI_META_TEMPO((BYTE)0x51)
#define MIDI_META_EOT((BYTE)0x2F)// End-of-track

#define MIDI_NOTEOFF((BYTE)0x80)// + note + velocity
#define MIDI_NOTEON((BYTE)0x90)// + note + velocity
#define MIDI_POLYPRESS  ((BYTE)0xA0)// + pressure (2 bytes)
#define MIDI_CTRLCHANGE ((BYTE)0xB0)// + ctrlr + value
#define MIDI_PRGMCHANGE((BYTE)0xC0)// + new patch
#define MIDI_CHANPRESS((BYTE)0xD0)// + pressure (1 byte)
#define MIDI_PITCHBEND((BYTE)0xE0)// + pitch bend (2 bytes)

#define CB_STREAMBUF(4096)// Size of each stream buffer

#define MIDS_SHORTMSG(0x00000000)
#define MIDS_TEMPO(0x01000000)

// Macros for swapping hi/lo-endian data
//
#define WORDSWAP(w)(((w) >> 8) | \
(((w) << 8) & 0xFF00))

#define DWORDSWAP(dw) (((dw) >> 24) |\
(((dw) >> 8) & 0x0000FF00) |\
(((dw) << 8) & 0x00FF0000) |\
(((dw) << 24) & 0xFF000000))

// In debug builds, TRACKERR will show us where the parser died
//
#ifdef _DEBUG
#define TRACKERR(p,sz) ShowTrackError(p,sz);
#else
#define TRACKERR(p,sz)
#endif

// These structures are stored in MIDI files; they need to be byte
// aligned.
//
#pragma pack(1)

// Chunk header. dwTag is either MTrk or MThd.
//
typedef struct
{
DWORDdwTag;// Type
DWORDcbChunk;// Length (hi-lo)
} MIDICHUNK;

// Contents of MThd chunk.
typedef struct
{
WORDwFormat;// Format (hi-lo)
WORDcTrack;// # tracks (hi-lo)
WORDwTimeDivision;// Time division (hi-lo)
} MIDIFILEHDR;

#pragma pack()

// One event we're reading or writing to a track
//
typedef struct
{
DWORDtkEvent;// Absolute time of event
BYTEabEvent[4];// Event type and parameters if channel msg
DWORDcbEvent;// Of data which follows if meta or sysex
LPBYTEpEvent;// -> Event data if applicable
} MEVENT;

// Description of a track open for read
//
#define ITS_F_ENDOFTRK0x00000001

typedef struct
{
DWORDfdwTrack;// Track status
DWORDcbTrack;// Total bytes in track
DWORDcbLeft;// Bytes left unread in track
LPBYTEpTrack;// -> start of track data
LPBYTEpTrackPointer;// -> next byte to read
DWORDtkNextEventDue;// Absolute time of next event in track
BYTEbRunningStatus;// Running status from last channel msg
#ifdef _DEBUG
DWORDnTrack;// # of this track for debugging
#endif
} INTRACKSTATE;

// Description of the input MIDI file
//
typedef struct
{
DWORDcbFile;// Total bytes in file
LPBYTEpFile;// -> entire file in memory
DWORDcbLeft;// Bytes left unread
LPBYTEpFilePointer;// -> next byte to read

DWORDdwTimeDivision;// Original time division
DWORDdwFormat;// Original format
DWORDcTrack;// Track count (specifies apIts size)
INTRACKSTATE*apIts;// -> array of tracks in this file
} INFILESTATE;

// Description of a stream buffer on the output side
//
typedef struct STREAMBUF *PSTREAMBUF;
typedef struct STREAMBUF
{
LPBYTEpBuffer;// -> Start of actual buffer
DWORDtkStart;// Tick time just before first event
LPBYTEpbNextEvent;// Where to write next event
DWORDcbLeft; // bytes left in buffer
DWORDcbLeftUncompressed;// bytes left when uncompressed
PSTREAMBUFpNext;// Next buffer
} STREAMBUF;

// Description of output stream open for write
//
typedef struct
{
DWORDtkTrack;// Current tick position in track
PSTREAMBUFpFirst;// First stream buffer
PSTREAMBUFpLast;// Last (current) stream buffer
} OUTSTREAMSTATE;

// Format of structs within a MSD file
//
// 'fmt ' chunk
//

#define MDS_F_NOSTREAMID0x00000001// Stream ID's skipped; reader inserts
typedef struct
{
DWORDdwTimeFormat;// Low word == time format in SMF format
DWORDcbMaxBuffer;// Guaranteed max buffer size
DWORDdwFlags;// Format flags
} MIDSFMT;

// 'data' chunk buffer header
//
typedef struct
{
DWORDtkStart;// Absolute tick offset at start of buffer
DWORDcbBuffer;// Bytes in this buffer
} MIDSBUFFER;

// A few globals
//
static HANDLEhInFile= INVALID_HANDLE_VALUE;
static HANDLEhOutFile= INVALID_HANDLE_VALUE;
static INFILESTATE  ifs;
static OUTSTREAMSTATE ots;
static BOOLfCompress= FALSE;

// Messages
//
static char szInitErrMem[]= "Out of memory.\n";
static char szInitErrInFile[]= "Read error on input file or file is corrupt.\n";

#ifdef _DEBUG
static char gteBadRunStat[] = "Reference to missing running status.";
static char gteRunStatMsgTrunc[] = "Running status message truncated";
static char gteChanMsgTrunc[] = "Channel message truncated";
static char gteSysExLenTrunc[] = "SysEx event truncated (length)";
static char gteSysExTrunc[] = "SysEx event truncated";
static char gteMetaNoClass[] ="Meta event truncated (no class byte)";
static char gteMetaLenTrunc[] = "Meta event truncated (length)";
static char gteMetaTrunc[] = "Meta event truncated";
#endif

// Prototypes
//
static BOOL Init(LPSTR szInFile, LPSTR szOutFile);
static LPBYTEGetInFileData(DWORD cbToGet);
static void Cleanup(void);
static BOOLBuildNewTracks(void);
static BOOLWriteStreamBuffers(void);
static BOOLGetTrackVDWord(INTRACKSTATE* pTs, LPDWORD lpdw);
static BOOLGetTrackEvent(INTRACKSTATE* pTs, MEVENT *pMe);
static BOOLAddEventToStream(MEVENT *pMe);
static LPBYTEGetOutStreamBytes(DWORD tkNow, DWORD cbNeeded, DWORD cbUncompressed);
#ifdef _DEBUG
static void ShowTrackError(INTRACKSTATE* pTs, char* szErr);
#endif

void main(int argc, char* argv[])
{
UINTidxFnames;

if (argc < 3)
{
fprintf(stderr, "Format is mid2strm [-c] infile outfile\n");
fprintf(stderr, "-c\tNo-stream-id compression\n");
exit(1);
}

idxFnames = 1;
if (argv[1][0] == '-')
{
++idxFnames;
if (argv[1][1] == 'c')
fCompress = TRUE;
}

if (!Init(argv[idxFnames], argv[idxFnames+1]))
exit(1);

if (!BuildNewTracks())
exit(1);

if (!WriteStreamBuffers())
exit(1);

// Add cleanup code!!!
//
Cleanup();

exit(0);
}

// Init
//
// Open the input and output files
// Allocate and read the entire input file into memory
// Validate the input file structure
// Allocate the input track structures and initialize them
// Initialize the output track structures
//
// Return TRUE on success
// Prints its own error message if something goes wrong
//
static BOOL Init(LPSTR szInFile, LPSTR szOutFile)
{
BOOLfRet = FALSE;
DWORDcbRead;
LPDWORDlpdwTag;
LPDWORDlpcbHeader;
DWORDcbHeader;
MIDIFILEHDR*pHeader;
INTRACKSTATE*pTs;
UINTidx;

// Initialize things we'll try to free later if we fail
//
ifs.cbFile = 0;
ifs.pFile = NULL;
ifs.apIts = NULL;

// Attempt to open the input and output files
//
hInFile = CreateFile(szInFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hInFile)
{
fprintf(stderr, "Could not open \"%s\" for read.\n", szInFile);
goto Init_Cleanup;
}

hOutFile = CreateFile(szOutFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hOutFile)
{
fprintf(stderr, "Could not open \"%s\" for write.\n", szOutFile);
goto Init_Cleanup;
}

// Figure out how big the input file is and allocate a chunk of memory big enough
// to hold the whole thing. Read the whole file in at once.
//
if (((UINT)-1) == (ifs.cbFile = GetFileSize(hInFile, NULL)))
{
fprintf(stderr, "File system error on input file.\n");
goto Init_Cleanup;
}

if (NULL == (ifs.pFile = GlobalAllocPtr(GPTR, ifs.cbFile)))
{
fprintf(stderr, szInitErrMem);
goto Init_Cleanup;
}

if ((!ReadFile(hInFile, ifs.pFile, ifs.cbFile, &cbRead, NULL)) ||
cbRead != ifs.cbFile)
{
fprintf(stderr, szInitErrInFile);
goto Init_Cleanup;
}

// Set up to read from the memory buffer. Read and validate
// - MThd header
// - size of file header chunk
// - file header itself
//
ifs.cbLeft = ifs.cbFile;
ifs.pFilePointer = ifs.pFile;

if (NULL == (lpdwTag = (LPDWORD)GetInFileData(sizeof(*lpdwTag))) ||
*lpdwTag != MThd ||
NULL == (lpcbHeader = (LPDWORD)GetInFileData(sizeof(*lpcbHeader))) ||
(cbHeader = DWORDSWAP(*lpcbHeader)) < sizeof(MIDIFILEHDR) ||
NULL == (pHeader = (MIDIFILEHDR*)GetInFileData(cbHeader)))
{
fprintf(stderr, szInitErrInFile);
goto Init_Cleanup;
}

// File header is stored in hi-lo order. Swap this into Intel order and save
// parameters in our native int size (32 bits)
//
ifs.dwFormat = (DWORD)WORDSWAP(pHeader->wFormat);
ifs.cTrack = (DWORD)WORDSWAP(pHeader->cTrack);
ifs.dwTimeDivision = (DWORD)WORDSWAP(pHeader->wTimeDivision);

// We know how many tracks there are; allocate the structures for them and parse
// them. The parse merely looks at the MTrk signature and track chunk length
// in order to skip to the next track header.
//
ifs.apIts = (INTRACKSTATE*)GlobalAllocPtr(GPTR, ifs.cTrack*sizeof(INTRACKSTATE));
if (NULL == ifs.apIts)
{
fprintf(stderr, szInitErrMem);
goto Init_Cleanup;
}

for (idx = 0, pTs = ifs.apIts; idx < ifs.cTrack; ++idx, ++pTs)
{
if (NULL == (lpdwTag = (LPDWORD)GetInFileData(sizeof(*lpdwTag))) ||
*lpdwTag != MTrk ||
NULL == (lpcbHeader = (LPDWORD)GetInFileData(sizeof(*lpcbHeader))))
{
fprintf(stderr, szInitErrInFile);
goto Init_Cleanup;
}

cbHeader = DWORDSWAP(*lpcbHeader);
pTs->cbTrack = cbHeader;
pTs->cbLeft = cbHeader;
pTs->pTrack = GetInFileData(cbHeader);
if (NULL == pTs->pTrack)
{
fprintf(stderr, szInitErrInFile);
goto Init_Cleanup;
}

#ifdef _DEBUG
pTs->nTrack = idx;
#endif
pTs->pTrackPointer = pTs->pTrack;
pTs->cbLeft = pTs->cbTrack;
pTs->fdwTrack = 0;
pTs->bRunningStatus = 0;

// Handle bozo MIDI files which contain empty track chunks
//
if (!pTs->cbLeft)
{
pTs->fdwTrack |= ITS_F_ENDOFTRK;
continue;
}

// We always preread the time from each track so the mixer code can
// determine which track has the next event with a minimum of work
//
if (!GetTrackVDWord(pTs, &pTs->tkNextEventDue))
{
fprintf(stderr, szInitErrInFile);
goto Init_Cleanup;
}
}

ots.tkTrack = 0;
ots.pFirst = NULL;
ots.pLast = NULL;

fRet = TRUE;

Init_Cleanup:
if (!fRet)
Cleanup();

return fRet;
}

//
// GetInFileData
//
// Gets the requested number of bytes of data from the input file and returns
// a pointer to them.
//
// Returns a pointer to the data or NULL if we'd read more than is
// there.
//
static LPBYTEGetInFileData(DWORD cbToGet)
{
LPBYTEpRet;

if (ifs.cbLeft < cbToGet)
return NULL;

pRet = ifs.pFilePointer;

ifs.cbLeft -= cbToGet;
ifs.pFilePointer += cbToGet;

return pRet;
}

//
// Cleanup
//
// Free anything we ever allocated
//
static void Cleanup(void)
{
PSTREAMBUFpCurr;
PSTREAMBUFpNext;

if (hInFile != INVALID_HANDLE_VALUE) CloseHandle(hInFile);
if (hOutFile != INVALID_HANDLE_VALUE) CloseHandle(hOutFile);
if (ifs.pFile)GlobalFreePtr(ifs.pFile);
if (ifs.apIts)GlobalFreePtr(ifs.apIts);

pCurr = ots.pFirst;
while (pCurr)
{
pNext = pCurr->pNext;
GlobalFreePtr(pCurr);
pCurr = pNext;
}
}

//
// BuildNewTracks
//
// This is where the actual work gets done.
//
// Until all tracks are done,
//  Scan the tracks to find the next due event
//  Figure out where the event belongs in the new mapping
//  Put it there
// Add end of track metas to all new tracks that now have any data
//
// Return TRUE on success
// Prints its own error message if something goes wrong
//
static BOOLBuildNewTracks(void)
{
INTRACKSTATE*pTs;
INTRACKSTATE*pTsFound;
UINTidx;
DWORDtkNext;
MEVENTme;

for(;;)
{
// Find nearest event due
//
pTsFound = NULL;
tkNext = 0xFFFFFFFFL;

for (idx = 0, pTs = ifs.apIts; idx < ifs.cTrack; ++idx, ++pTs)
if ((!(pTs->fdwTrack & ITS_F_ENDOFTRK)) && (pTs->tkNextEventDue < tkNext))
{
tkNext = pTs->tkNextEventDue;
pTsFound = pTs;
}

// None found? We must be done
//
if (!pTsFound)
break;

// Ok, get the event header from that track
//

if (!GetTrackEvent(pTsFound, &me))
{
fprintf(stderr, "MIDI file is corrupt!\n");
return FALSE;
}

// Don't add end of track event 'til we're done
//
if (me.abEvent[0] == MIDI_META && me.abEvent[1] == MIDI_META_EOT)
continue;

if (!AddEventToStream(&me))
{
fprintf(stderr, "Out of memory building tracks.\n");
return FALSE;
}
}

return TRUE;
}

//
// WriteStreamBuffers
//
// Write stream buffers into an MDS file (RIFF MIDS format)
//
// Return TRUE on success
// Prints its own error message if something goes wrong
//
#define FOURCC_MIDSmmioFOURCC('M','I','D','S')
#define FOURCC_fmt  mmioFOURCC('f','m','t',' ')
#define FOURCC_data mmioFOURCC('d','a','t','a')

static BOOLWriteStreamBuffers(void)
{
DWORDcbFmt;
DWORDcbData;
DWORDcbRiff;
PSTREAMBUFpsb;
FOURCCfcc;
FOURCCfcc2;
MIDSFMTfmt;
MIDSBUFFERdata;
DWORDcb;
DWORDcBuffers;

// Walk buffer list to find entire size of data chunk
//
cbData = sizeof(cBuffers);
cBuffers = 0;
for (psb = ots.pFirst; psb; psb = psb->pNext, ++cBuffers)
cbData += sizeof(MIDSBUFFER) + (CB_STREAMBUF - psb->cbLeft);

cbFmt = sizeof(fmt);

// Figure size of entire RIFF chunk
//
cbRiff =
sizeof(FOURCC) +// RIFF form type ('MIDS')
sizeof(FOURCC) +// Format chunk type ('fmt ')
sizeof(DWORD) + // Format chunk size
sizeof(MIDSFMT) +// Format chunk contents
sizeof(FOURCC) +// Data chunk type ('data')
sizeof(DWORD) +// Data chunk size
cbData;// Data chunk contents

fcc = FOURCC_RIFF;
fcc2 = FOURCC_MIDS;
if ((!WriteFile(hOutFile, &fcc, sizeof(fcc), &cb, NULL)) ||
(!WriteFile(hOutFile, &cbRiff, sizeof(cbRiff), &cb, NULL)) ||
(!WriteFile(hOutFile, &fcc2, sizeof(fcc2), &cb, NULL)))
return FALSE;

fmt.dwTimeFormat = ifs.dwTimeDivision;
fmt.cbMaxBuffer= CB_STREAMBUF;
fmt.dwFlags = 0;
if (fCompress)
fmt.dwFlags|= MDS_F_NOSTREAMID;

fcc = FOURCC_fmt;
if ((!WriteFile(hOutFile, &fcc, sizeof(fcc), &cb, NULL)) ||
(!WriteFile(hOutFile, &cbFmt, sizeof(cbFmt), &cb, NULL)) ||
(!WriteFile(hOutFile, &fmt, sizeof(fmt), &cb, NULL)))
return FALSE;

fcc = FOURCC_data;
if ((!WriteFile(hOutFile, &fcc, sizeof(fcc), &cb, NULL)) ||
(!WriteFile(hOutFile, &cbData, sizeof(cbData), &cb, NULL)) ||
(!WriteFile(hOutFile, &cBuffers, sizeof(cBuffers), &cb, NULL)))
return FALSE;

for (psb = ots.pFirst; psb; psb = psb->pNext)
{
data.tkStart = psb->tkStart;
data.cbBuffer = CB_STREAMBUF - psb->cbLeft;

if ((!WriteFile(hOutFile, &data, sizeof(data), &cb, NULL)) ||
(!WriteFile(hOutFile, psb->pBuffer, data.cbBuffer, &cb, NULL)))
return FALSE;
}

return TRUE;
}

//
// GetTrackVDWord
//
// Attempts to parse a variable length DWORD from the given track. A VDWord
// in a MIDI file
//  (a) is in lo-hi format
//  (b) has the high bit set on every byte except the last
//
// Returns the DWORD in *lpdw and TRUE on success; else
// FALSE if we hit end of track first. Sets ITS_F_ENDOFTRK
// if we hit end of track.
//
static BOOLGetTrackVDWord(INTRACKSTATE* pTs, LPDWORD lpdw)
{
BYTEb;
DWORDdw = 0;

if (pTs->fdwTrack & ITS_F_ENDOFTRK)
return FALSE;

do
{
if (!pTs->cbLeft)
{
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

b = *pTs->pTrackPointer++;
--pTs->cbLeft;

 dw = (dw << 7) | (b & 0x7F);
} while (b & 0x80);

*lpdw = dw;

return TRUE;
}

//
// GetTrackEvent
//
// Fills in the event struct with the next event from the track
//
// pMe->tkEvent will contain the absolute tick time of the event
// pMe->abEvent[0] will contain
//  MIDI_META if the event is a meta event;
//   in this case pMe->abEvent[1] will contain the meta class
//  MIDI_SYSEX or MIDI_SYSEXEND if the event is a SysEx event
//  Otherwise, the event is a channel message and pMe->abEvent[1]
//   and pMe->abEvent[2] will contain the rest of the event.
//
// pMe->cbEvent will contain
//  The total length of the channel message in pMe->abEvent if
//   the event is a channel message
//  The total length of the paramter data pointed to by
//   pMe->pEvent otherwise
//
// pMe->pEvent will point at any additional paramters if the
//  event is a SysEx or meta event with non-zero length; else
//  it will contain NULL
//
// Returns TRUE on success or FALSE on any kind of parse error
// Prints its own error message ONLY in the debug version
//
// Maintains the state of the input track (i.e. pTs->cbLeft,
// pTs->pTrackPointers, and pTs->bRunningStatus).
//
static BOOLGetTrackEvent(INTRACKSTATE* pTs, MEVENT *pMe)
{
BYTEb;
UINTcbEvent;

pMe->pEvent = NULL;

// Already at end of track? There's nothing to read.
//
if ((pTs->fdwTrack & ITS_F_ENDOFTRK) || !pTs->cbLeft)
return FALSE;

// Get the first byte, which determines the type of event.
//
b = *pTs->pTrackPointer++;
--pTs->cbLeft;

// If the high bit is not set, then this is a channel message
// which uses the status byte from the last channel message
// we saw. NOTE: We do not clear running status across SysEx or
// meta events even though the spec says to because there are
// actually files out there which contain that sequence of data.
//
if (!(b & 0x80))
{
// No previous status byte? We're hosed.
//
if (!pTs->bRunningStatus)
{
TRACKERR(pTs, gteBadRunStat);
return FALSE;
}

pMe->abEvent[0] = pTs->bRunningStatus;
pMe->abEvent[1] = b;

b = pMe->abEvent[0] & 0xF0;
pMe->cbEvent = 2;

// Only program change and channel pressure events are 2 bytes long;
// the rest are 3 and need another byte
//
if (b != MIDI_PRGMCHANGE && b != MIDI_CHANPRESS)
{
if (!pTs->cbLeft)
{
TRACKERR(pTs, gteRunStatMsgTrunc);
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

pMe->abEvent[2] = *pTs->pTrackPointer++;
--pTs->cbLeft;
++pMe->cbEvent;
}
}
else if ((b & 0xF0) != MIDI_SYSEX)
{
// Not running status, not in SysEx range - must be
// normal channel message (0x80-0xEF)
//
pMe->abEvent[0] = b;
pTs->bRunningStatus = b;

// Strip off channel and just keep message type
//
b &= 0xF0;

cbEvent = (b == MIDI_PRGMCHANGE || b == MIDI_CHANPRESS) ? 1 : 2;
pMe->cbEvent = cbEvent + 1;

if (pTs->cbLeft < cbEvent)
{
TRACKERR(pTs, gteChanMsgTrunc);
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

pMe->abEvent[1] = *pTs->pTrackPointer++;
if (cbEvent == 2)
pMe->abEvent[2] = *pTs->pTrackPointer++;

pTs->cbLeft -= cbEvent;
}
else if (b == MIDI_SYSEX || b == MIDI_SYSEXEND)
{
// One of the SysEx types. (They are the same as far as we're concerned;
// there is only a semantic difference in how the data would actually
// get sent when the file is played. We must take care to put the correct
// event type back on the output track, however.)
//
// Parse the general format of:
//  BYTE bEvent (MIDI_SYSEX or MIDI_SYSEXEND)
//  VDWORD cbParms
//  BYTE   abParms[cbParms]
//
pMe->abEvent[0] = b;
if (!GetTrackVDWord(pTs, &pMe->cbEvent))
{
TRACKERR(pTs, gteSysExLenTrunc);
return FALSE;
}

if (pTs->cbLeft < pMe->cbEvent)
{
TRACKERR(pTs, gteSysExTrunc);
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

pMe->pEvent = pTs->pTrackPointer;
pTs->pTrackPointer += pMe->cbEvent;
pTs->cbLeft -= pMe->cbEvent;
}
else if (b == MIDI_META)
{
// It's a meta event. Parse the general form:
//  BYTEbEvent(MIDI_META)
//BYTEbClass
//  VDWORDcbParms
//BYTEabParms[cbParms]
//
pMe->abEvent[0] = b;

if (!pTs->cbLeft)
{
TRACKERR(pTs, gteMetaNoClass);
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

pMe->abEvent[1] = *pTs->pTrackPointer++;
--pTs->cbLeft;

if (!GetTrackVDWord(pTs, &pMe->cbEvent))
{
TRACKERR(pTs, gteMetaLenTrunc);
return FALSE;
}

// NOTE: Perfectly valid to have a meta with no data
// In this case, cbEvent == 0 and pEvent == NULL
//
if (pMe->cbEvent)
{
if (pTs->cbLeft < pMe->cbEvent)
{
TRACKERR(pTs, gteMetaTrunc);
pTs->fdwTrack |= ITS_F_ENDOFTRK;
return FALSE;
}

pMe->pEvent = pTs->pTrackPointer;
pTs->pTrackPointer += pMe->cbEvent;
pTs->cbLeft -= pMe->cbEvent;
}

if (pMe->abEvent[1] == MIDI_META_EOT)
pTs->fdwTrack |= ITS_F_ENDOFTRK;
}
else
{
// Messages in this range are system messages and aren't supposed to
// be in a normal MIDI file. If they are, we've misparsed or the
// authoring software is stpuid.
//
return FALSE;
}

// Event time was already stored as the current track time
//
pMe->tkEvent = pTs->tkNextEventDue;

// Now update to the next event time. The code above MUST properly
// maintain the end of track flag in case the end of track meta is
// missing.
//
if (!(pTs->fdwTrack & ITS_F_ENDOFTRK))
{
DWORDtkDelta;

if (!GetTrackVDWord(pTs, &tkDelta))
return FALSE;

pTs->tkNextEventDue += tkDelta;
}

return TRUE;
}

//
// AddEventToStream
//
// Put the given event onto the given output track.
// pMe must point to an event filled out in accordance with the
// description given in GetTrackEvent
//
// Returns TRUE on sucess or FALSE if we're out of memory
//
static BOOLAddEventToStream(MEVENT *pMe)
{
PDWORDpdw;
DWORDtkNow;
DWORDtkDelta;
UINTcdw;

tkNow = ots.tkTrack;

// Delta time is absolute event time minus absolute time
// already gone by on this track
//
tkDelta = pMe->tkEvent - ots.tkTrack;

// Event time is now current time on this track
//
ots.tkTrack = pMe->tkEvent;

if (pMe->abEvent[0] < MIDI_SYSEX)
{
// Channel message. We know how long it is, just copy it. Need 3 DWORD's: delta-t,
// stream-ID, event
//
// TODO: Compress with running status
//

cdw = (fCompress ? 2 : 3);
if (NULL == (pdw = (PDWORD)GetOutStreamBytes(tkNow, cdw * sizeof(DWORD), 3 * sizeof(DWORD))))
return FALSE;

*pdw++ = tkDelta;
if (!fCompress)
*pdw++ = 0;
*pdw =(pMe->abEvent[0]) |
(((DWORD)pMe->abEvent[1]) << 8) |
(((DWORD)pMe->abEvent[2]) << 16) |
MIDS_SHORTMSG;

}
else if (pMe->abEvent[0] == MIDI_SYSEX || pMe->abEvent[0] == MIDI_SYSEXEND)
{
fprintf(stderr, "NOTE: Ignoring SysEx for now.\n");
}
else
{
// Better be a meta event.
//  BYTEbEvent
//  BYTE bClass
//VDWORDcbParms
//BYTEabParms[cbParms]
//
assert(pMe->abEvent[0] == MIDI_META);

// The only meta-event we care about is change tempo
//
if (pMe->abEvent[1] != MIDI_META_TEMPO)
return TRUE;

assert(pMe->cbEvent == 3);

cdw = (fCompress ? 2 : 3);
pdw = (PDWORD)GetOutStreamBytes(tkNow, cdw * sizeof(DWORD), 3 * sizeof(DWORD));
if (NULL == pdw)
return FALSE;

*pdw++ = tkDelta;
if (!fCompress)
*pdw++ = (DWORD)-1;
*pdw =(pMe->pEvent[2]) |
(((DWORD)pMe->pEvent[1]) << 8) |
(((DWORD)pMe->pEvent[0]) << 16) |
MIDS_TEMPO;
}

return TRUE;
}

//
// GetOutStreamBytes
//
// This function performs the memory management and pseudo-file I/O for output
// tracks.
//
// We build a linked list of stream buffers as they would exist if they were
// about to be played. Each buffer is CB_STREAMBUF bytes long maximum. They are
// filled as full as possible; events are not allowed to cross buffers.
//
// Returns a pointer to the number of requested bytes or NULL if we're out of memory
//
static LPBYTEGetOutStreamBytes(DWORD tkNow, DWORD cbNeeded, DWORD cbUncompressed)
{
LPBYTEpb;

// Round request up to the next DWORD boundry. This aligns the final output buffer correctly
// and allows the above routines to deal with byte-aligned data
//
cbNeeded = (cbNeeded + 3) & ~3;
cbUncompressed = (cbUncompressed + 3) & ~3;

assert(cbUncompressed >= cbNeeded);

if (NULL == ots.pLast || cbUncompressed > ots.pLast->cbLeftUncompressed)
{
PSTREAMBUF pNew;

pNew = GlobalAllocPtr(GHND, sizeof(*pNew) + CB_STREAMBUF);
if (NULL == pNew)
return NULL;

pNew->pBuffer = (LPBYTE)(pNew + 1);
pNew->tkStart= tkNow;
pNew->pbNextEvent = pNew->pBuffer;
pNew->cbLeft = CB_STREAMBUF;
pNew->cbLeftUncompressed = CB_STREAMBUF;
pNew->pNext = NULL;

if (!ots.pLast)
{
ots.pFirst = pNew;
ots.pLast  = pNew;
}
else
{
ots.pLast->pNext = pNew;
ots.pLast= pNew;
}
}

// If there's STILL not enough room for the requested block, then an event is bigger than
// the buffer size -- this is unacceptable.
//
if (cbNeeded > ots.pLast->cbLeft)
{
fprintf(stderr, "NOTE: An event requested %lu bytes of memory; the\n", cbNeeded);
fprintf(stderr, "      maximum configured buffer size is %lu.\n", (DWORD)CB_STREAMBUF);

return NULL;
}

pb = ots.pLast->pbNextEvent;

ots.pLast->pbNextEvent += cbNeeded;
ots.pLast->cbLeft -= cbNeeded;
ots.pLast->cbLeftUncompressed -= cbUncompressed;

return pb;
}

#ifdef _DEBUG
static void ShowTrackError(INTRACKSTATE* pTs, char* szErr)
{
fprintf(stderr, "Track %u: %s\n", pTs->nTrack, szErr);
fprintf(stderr, "Track offset %lu\n", (DWORD)(pTs->pTrackPointer - pTs->pTrack));
fprintf(stderr, "Track total %lu  Track left %lu\n", pTs->cbTrack, pTs->cbLeft);
}
#endif


README.TXT

DirectX Samples MISC Directory Readme
Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.

1. D3DUtils

        d3dutils.h, d3dutils.inl and d3dutils.cpp implement a set
of helper functions for C++ programmers for the following tasks:

1a. Matrix construction

D3DMATRIX ZeroMatrix();
        Returns the zero matrix.

D3DMATRIX IdentityMatrix();
        Returns the identity matrix.

D3DMATRIX ProjectionMatrix(const float Zn, const float Zf,
        const float fov);
        Returns the projection matrix:

        [c      0       0       0
         0      c       0       0
         0      0       Q       s
         0      0       -QZn    0]

                s = sin(fov/2)
                c = cos(fov/2)
                Q = s / (1 - Zn/Zf)
        Zn and Zf specify the near and far clipping planes.
        fov specifies the field of view angle in radians.

D3DMATRIX RotateXMatrix(const float rads);
D3DMATRIX RotateYMatrix(const float rads);
D3DMATRIX RotateZMatrix(const float rads);
        Constructs a matrix that rotates about the given axis
by rads radians.

D3DMATRIX TranslateMatrix(const float dx, const float dy,
        const float dz);
D3DMATRIX TranslateMatrix(const D3DVECTOR& v);
        Constructs a matrix to translate by the given vector.

D3DMATRIX ViewMatrix(const D3DVECTOR& from, const D3DVECTOR& at,
        const D3DVECTOR& up, const float roll);

        from is the location of the eye
        at is the location of the eye's point of interest
        up is the global "up" vector
        roll is a roll angle about the axis of the viewer

        Constructs a view matrix to translate and rotate such that
the viewer is looking down the +Z axis from the origin.

D3DMATRIX ScaleMatrix(const float size);
D3DMATRIX ScaleMatrix(const float a, const float b, const float c);
D3DMATRIX ScaleMatrix(const D3DVECTOR& v);
        Uniform and nonuniform scale matrices.

D3DMATRIX MatrixMult (const D3DMATRIX& a, const D3DMATRIX& b);
        Returns a*b

D3DMATRIX MatrixInverse (const D3DMATRIX& a);
        Returns the inverse of a.

D3DMATRIX MatrixTranspose(const D3DMATRIX& a);
        Returns the transpose of a.

D3DVECTOR TransformVector(const D3DVECTOR& v, const D3DMATRIX& m);
        Returns v*m. Divides the result through by the
perspective component of v*m.

D3DVECTOR TransformNormal(const D3DVECTOR& v, const D3DMATRIX& m);
        Returns v*MatrixTranspose(MatrixInverse(m)). This is the
correct way to transform a normal vector.
        Note, this routine should not be used to transform batches
of vectors because it would repeatedly perform the transpose and
inverse operations on the input matrix.

1b. Light management

Data Members

        D3DLIGHT2       light;
                Structure describing the light.

        LPDIRECT3DLIGHT lpD3DLight;
                Interface pointer for the light.

        int             changed;
                Dirty bit for the light.

Member Functions

HRESULT AddToViewport(LPDIRECT3DVIEWPORT2 lpView);
        Adds the light to the viewport.

HRESULT RemoveFromViewport(LPDIRECT3DVIEWPORT2 lpView);
        Removes the light from the viewport.

HRESULT Set();
        Forces the light to be updated through SetLight.

The Set*/Get* functions enable the application to update the light's
properties with single function calls. The Set* functions set
the dirty bit so the light will be updated when necessary.

SetColor/GetColor
SetPosition/GetPosition
SetDirection/GetDirection
SetAttenuation/GetAttenuation
SetRange/GetRange
SetFalloff/GetFalloff
SetUmbra/GetUmbra
SetPenumbra/GetPenumbra
SetFlags/GetFlags

Several classes are derived from Light. This is basically to
simplify construction, since some elements of D3DLIGHT2 are
not relevant to certain light types.

DirectionalLight(LPDIRECT3D2 lpD3D,
                 const D3DVECTOR& color,
                 const D3DVECTOR& direction);
ParallelPointLight(LPDIRECT3D2 lpD3D,
                   const D3DVECTOR& color,
                   const D3DVECTOR& position);
PointLight(LPDIRECT3D2 lpD3D,
           const D3DVECTOR& color,
           const D3DVECTOR& position);
SpotLight(LPDIRECT3D2 lpD3D,
          const D3DVECTOR& color,
          const D3DVECTOR& position,
          const D3DVECTOR& direction,
          const float umbra_angle,
          const float penumbra_angle);

1c. Material management

        The Material class simplifies dealing with Direct3D
materials.

Data Members

        D3DMATERIAL     Mat;
                Structure describing the material.

        D3DMATERIALHANDLE hMat;
                The material's handle.

LPDIRECT3DMATERIAL2 lpMat;
                Pointer to the material's interface.

        int             changed;
                Dirty bit for the material.

Member Functions

HRESULT SetAsCurrent(LPDIRECT3DDEVICE2 lpDev);
        Sets the material as current to the device.

HRESULT SetAsBackground(LPDIRECT3DDEVICE2 lpDev);
        Sets the material as the current background on the device.

HRESULT Set(void);
        Forces the material to be updated using SetMaterial.

The Set*/Get* functions enable the application to update the various
material properties with single function calls. The Set* functions
set the dirty bit so the material will be updated when necessary.
        SetDiffuse/GetDiffuse
        SetAlpha/GetAlpha
        SetAmbient/GetAmbient
        SetEmissive/GetEmissive
        SetSpecular/GetSpecular
        SetPower/GetPower
        SetRampSize/GetRampSize
        SetTextureHandle/GetTextureHandle

See boids and pplane for examples of how to use the Material class.

2. D3DTexture

        d3dtex.h and d3dtex.cpp implement the D3DTexture class, which
simplifies texture managment.  D3DTexture's features are as follows:
        - Keeps a system memory copy of the texture to restore the surface.
        - Enables loading textures from bitmaps
        - Uses DirectDraw's GetDC function to access GDI's support
          for color-converting blts to load the textures.
          Note, D3DTexture does not currently support textures with
          alpha because GDI does not support bitmaps with alpha.

        See boids and flip3d for examples of how to use D3DTexture.

3.  GetDXVersion

    getdxver.cpp implements sample code showing how to detect which
version of DirectX is currently running (1.0, 2.0, ..., 5.0).

        See boids and flip3d for examples of how to use GetDXVersion.

4.1 RMMain

Uses D3DRM to setup a window for rendering, ask the sample to build
it's scene and then renders the scene.

rmmain.cpp

Main source file which includes WinMain and message handler.  Creates
D3DRM, uses it to setup the window for rendering and then asks the
sample to build it's scene.

rmmain.h

Header for rmmain.cpp.

rmmain.rc

Resource file for rmmain.cpp.

rmerror.cpp

D3DRM error code to string and error message box.

rmerror.h

Header for rmerror.cpp.

rmdemo.h

Because the samples do not create and manage all DirectDraw and
Direct3D objects themselves, the source is much simpler than the
immediate mode samples, but they can only run in a window.
Optionally, the D3DRM samples can be linked to D3DApp via another
harness.  This will allow them to enter fullscreen modes.  Fly is
linked to these files.

rmfull.cpp

Main source file which includes WinMain and message handler.  Uses
D3DApp to setup rendering.

rmfull.h

Header for rmfull.cpp.

rmfull.rc

Defines menus and keyboard accelerator.

rmfullrc.h

Header for rmfull.rc.

rmstats.cpp

Calculates and displays frame rate and other information.


D3DMACS.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dmacs.h
 *
 *  Useful macros for generating execute buffers.  Consult the D3D sample
 *  code for examples of their usage.
 *
 *  Use OP_NOP to QWORD align triangle and line instructions.
 */

#ifndef __D3DMACS_H__
#define __D3DMACS_H__

#undef RELEASE

#ifndef __cplusplus
#define MAKE_MATRIX(lpDev, handle, data) \
{   if (lpDev->lpVtbl->CreateMatrix(lpDev, &handle) != D3D_OK) \
    return FALSE; \
    if (lpDev->lpVtbl->SetMatrix(lpDev, handle, &data) != D3D_OK) \
    return FALSE; \
}
#define RELEASE(x) {if (x != NULL) {x->lpVtbl->Release(x); x = NULL;}}
#endif

#ifdef __cplusplus
#define MAKE_MATRIX(lpDev, handle, data) \
{   if (lpDev->CreateMatrix(&handle) != D3D_OK) \
    return FALSE; \
    if (lpDev->SetMatrix(handle, &data) != D3D_OK) \
    return FALSE; \
}
#define RELEASE(x) {if (x != NULL) {x->Release(); x = NULL;}}
#endif

#define PUTD3DINSTRUCTION(op, sz, cnt, ptr) \
(   ((LPD3DINSTRUCTION) ptr)->bOpcode = op, \
    ((LPD3DINSTRUCTION) ptr)->bSize = sz, \
    ((LPD3DINSTRUCTION) ptr)->wCount = cnt, \
    ptr = (void *)(((LPD3DINSTRUCTION) ptr) + 1) \
)

#define VERTEX_DATA(loc, cnt, ptr) \
(   (((ptr) != (loc))? memcpy((ptr), (loc), sizeof(D3DVERTEX) * (cnt)) : 0), \
    ptr = (void *)(((LPD3DVERTEX) (ptr)) + (cnt)) \
)

// OP_MATRIX_MULTIPLY size: 4 (sizeof D3DINSTRUCTION)
#define OP_MATRIX_MULTIPLY(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_MATRIXMULTIPLY, sizeof(D3DMATRIXMULTIPLY), cnt, ptr)

// MATRIX_MULTIPLY_DATA size: 12 (sizeof MATRIXMULTIPLY)
#define MATRIX_MULTIPLY_DATA(src1, src2, dest, ptr) \
(   ((LPD3DMATRIXMULTIPLY) ptr)->hSrcMatrix1 = src1, \
    ((LPD3DMATRIXMULTIPLY) ptr)->hSrcMatrix2 = src2, \
    ((LPD3DMATRIXMULTIPLY) ptr)->hDestMatrix = dest, \
    ptr = (void *)(((LPD3DMATRIXMULTIPLY) ptr) + 1) \
)

// OP_STATE_LIGHT size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_LIGHT(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATELIGHT, sizeof(D3DSTATE), cnt, ptr)

// OP_STATE_TRANSFORM size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_TRANSFORM(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATETRANSFORM, sizeof(D3DSTATE), cnt, ptr)

// OP_STATE_RENDER size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_RENDER(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATERENDER, sizeof(D3DSTATE), cnt, ptr)

// STATE_DATA size: 8 (sizeof D3DSTATE)
#define STATE_DATA(type, arg, ptr) \
(   ((LPD3DSTATE) ptr)->drstRenderStateType = (D3DRENDERSTATETYPE)type, \
    ((LPD3DSTATE) ptr)->dwArg[0] = arg, \
    ptr = (void *)(((LPD3DSTATE) ptr) + 1) \
)

// OP_PROCESS_VERTICES size: 4 (sizeof D3DINSTRUCTION)
#define OP_PROCESS_VERTICES(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_PROCESSVERTICES, sizeof(D3DPROCESSVERTICES), cnt, ptr)

// PROCESSVERTICES_DATA size: 16 (sizeof D3DPROCESSVERTICES)
#define PROCESSVERTICES_DATA(flgs, strt, cnt, ptr) \
(   ((LPD3DPROCESSVERTICES) ptr)->dwFlags = flgs, \
    ((LPD3DPROCESSVERTICES) ptr)->wStart = strt, \
    ((LPD3DPROCESSVERTICES) ptr)->wDest = strt, \
    ((LPD3DPROCESSVERTICES) ptr)->dwCount = cnt, \
    ((LPD3DPROCESSVERTICES) ptr)->dwReserved = 0, \
    ptr = (void *)(((LPD3DPROCESSVERTICES) ptr) + 1) \
)

// OP_TRIANGLE_LIST size: 4 (sizeof D3DINSTRUCTION)
#define OP_TRIANGLE_LIST(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_TRIANGLE, sizeof(D3DTRIANGLE), cnt, ptr)

#define TRIANGLE_LIST_DATA(loc, count, ptr) \
(   ((ptr) != (loc))? memcpy((ptr), (loc), sizeof(D3DTRIANGLE) * (count)) : 0, \
    ptr = (void *)(((LPD3DTRIANGLE) (ptr)) + (count)) \
)

// OP_TRIANGLEEX_LIST size: 4 (sizeof D3DINSTRUCTION)
#define OP_TRIANGLEEX_LIST(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_TRIANGLEEX, sizeof(D3DTRIANGLEEX), cnt, ptr)

#define TRIANGLEEX_LIST_DATA(loc, count, ptr) \
(   ((ptr) != (loc))? memcpy((ptr), (loc), sizeof(D3DTRIANGLEEX) * (count)) : 0, \
    ptr = (void *)(((LPD3DTRIANGLEEX) (ptr)) + (count)) \
)

// OP_LINE_LIST size: 4 (sizeof D3DINSTRUCTION)
#define OP_LINE_LIST(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_LINE, sizeof(D3DLINE), cnt, ptr)

#define LINE_LIST_DATA(loc, count, ptr) \
(   ((ptr) != (loc))? memcpy((ptr), (loc), sizeof(D3DLINE) * (count)) : 0, \
    ptr = (void *)(((LPD3DLINE) (ptr)) + (count)) \
)

// OP_POINT_LIST size: 8 (sizeof D3DINSTRUCTION + sizeof D3DPOINT)
#define OP_POINT_LIST(first, cnt, ptr) \
(   PUTD3DINSTRUCTION(D3DOP_POINT, sizeof(D3DPOINT), 1, ptr), \
    ((LPD3DPOINT)(ptr))->wCount = cnt, \
    ((LPD3DPOINT)(ptr))->wFirst = first, \
    ptr = (void*)(((LPD3DPOINT)(ptr)) + 1) \
)

// OP_SPAN_LIST size: 8 (sizeof D3DINSTRUCTION + sizeof D3DSPAN)
#define OP_SPAN_LIST(first, cnt, ptr) \
(   PUTD3DINSTRUCTION(D3DOP_SPAN, sizeof(D3DSPAN), 1, ptr), \
    ((LPD3DSPAN)(ptr))->wCount = cnt, \
    ((LPD3DSPAN)(ptr))->wFirst = first, \
    ptr = (void*)(((LPD3DSPAN)(ptr)) + 1) \
)

// OP_BRANCH_FORWARD size: 18 (sizeof D3DINSTRUCTION + sizeof D3DBRANCH)
#define OP_BRANCH_FORWARD(tmask, tvalue, tnegate, toffset, ptr) \
(   PUTD3DINSTRUCTION(D3DOP_BRANCHFORWARD, sizeof(D3DBRANCH), 1, ptr), \
    ((LPD3DBRANCH) ptr)->dwMask = tmask, \
    ((LPD3DBRANCH) ptr)->dwValue = tvalue, \
    ((LPD3DBRANCH) ptr)->bNegate = tnegate, \
    ((LPD3DBRANCH) ptr)->dwOffset = toffset, \
    ptr = (void *)(((LPD3DBRANCH) (ptr)) + 1) \
)

// OP_SET_STATUS size: 20 (sizeof D3DINSTRUCTION + sizeof D3DSTATUS)
#define OP_SET_STATUS(flags, status, _x1, _y1, _x2, _y2, ptr) \
(   PUTD3DINSTRUCTION(D3DOP_SETSTATUS, sizeof(D3DSTATUS), 1, ptr), \
    ((LPD3DSTATUS)(ptr))->dwFlags = flags, \
    ((LPD3DSTATUS)(ptr))->dwStatus = status, \
    ((LPD3DSTATUS)(ptr))->drExtent.x1 = _x1, \
    ((LPD3DSTATUS)(ptr))->drExtent.y1 = _y1, \
    ((LPD3DSTATUS)(ptr))->drExtent.x2 = _x2, \
    ((LPD3DSTATUS)(ptr))->drExtent.y2 = _y2, \
    ptr = (void *)(((LPD3DSTATUS) (ptr)) + 1) \
)

// OP_NOP size: 4
#define OP_NOP(ptr) \
    PUTD3DINSTRUCTION(D3DOP_TRIANGLE, sizeof(D3DTRIANGLE), 0, ptr)

#define OP_EXIT(ptr) \
    PUTD3DINSTRUCTION(D3DOP_EXIT, 0, 0, ptr)

#define QWORD_ALIGNED(ptr) \
    !(0x00000007L & (ULONG)(ptr))

#endif // __D3DMACS_H__


D3DMAIN.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dmain.h
 *
 */
#ifndef __D3DMAIN_H__
#define __D3DMAIN_H__

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <search.h>
#include <ddraw.h>
#include <d3d.h>
#include "d3dapp.h"    /* prototypes for D3D helper functions */
#include "d3dres.h"    /* defines constants used in d3dmain.rc */
#include "d3ddemo.h"    /* prototypes for functions to communicate with
       each sample */
#define START_WIN_SIZE 320  /* initial size of the window */

#ifdef __cplusplus
extern "C" {
#endif

typedef struct tagd3dmainglobals {
    HWND hWndMain;    /* application window handle */
    HINSTANCE hInstApp;    /* application instance for dialog boxes */

    D3DAppRenderState rstate; /* struct describing the current render state */

    BOOL bSingleStepMode; /* render one frame at a time */
    BOOL bDrawAFrame; /* render on this pass of the main loop */
    BOOL bClearsOn; /* clear the back and z-buffer each frame */
    BOOL bShowFrameRate; /* show the frame rate at the top */
    BOOL bShowInfo; /* show window information at the bottom */
    BOOL bResizingDisabled; /* do not allow resizing */

    BOOL bResized; /* the window has resized or some other drastic change, the
                      entire client area should be cleared */
    BOOL bQuit;   /* program is about to terminate */

    LPDIRECTDRAWSURFACE lpFrameRateBuffer; /* frame rate surface */
    LPDIRECTDRAWSURFACE lpInfoBuffer;   /* window info surface */
} d3dmainglobals;

void __cdecl Msg( LPSTR fmt, ... );
/*
 * STATS.CPP FUNCTION PROTOTYPES
 */
BOOL InitFontAndTextBuffers(void);
void ReleaseFontAndTextBuffers(void);
BOOL WriteInfoBuffer(void);
BOOL WriteFrameRateBuffer(float fps, long tps);
void ResetFrameRate(void);
BOOL CalculateFrameRate(void);
BOOL DisplayFrameRate(int* count, LPD3DRECT lpExtents );

#ifdef __cplusplus
};
#endif

#endif // __D3DMAIN_H__


DDCALLS.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: ddcalls.c
 *
 *  Manages DirectDraw objects needed for rendering.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"

/***************************************************************************/
/*                         Direct Draw Enumeration                         */
/***************************************************************************/

typedef struct {
    D3DAppDDDriver* driver;
    int    numdriver;
} DDEnumCallbackContext;

/*
 * D3DAppIDDEnumCallback
 * Callback function used during enumeration of DirectDraw drivers.
 */
BOOL FAR PASCAL D3DAppIDDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext)
{
    LPDIRECTDRAW lpDD;
    DDCAPS DriverCaps, HELCaps;
    DDEnumCallbackContext *context = (DDEnumCallbackContext*)lpContext;
    D3DAppDDDriver* driver = &context->driver[context->numdriver];

    /*
     * Create the DirectDraw device using this driver.  If it fails,
     * just move on to the next driver.
     */
    if (FAILED(DirectDrawCreate(lpGUID, &lpDD, NULL))) {
return DDENUMRET_OK;
    }
    /*
     * Get the capabilities of this DirectDraw driver.  If it fails,
     * just move on to the next driver.
     */
    memset(&DriverCaps, 0, sizeof(DDCAPS));
    DriverCaps.dwSize = sizeof(DDCAPS);
    memset(&HELCaps, 0, sizeof(DDCAPS));
    HELCaps.dwSize = sizeof(DDCAPS);
    if (FAILED(lpDD->lpVtbl->GetCaps(lpDD, &DriverCaps, &HELCaps))) {
lpDD->lpVtbl->Release(lpDD);
return DDENUMRET_OK;
    }
    if (lpGUID && (DriverCaps.dwCaps & DDCAPS_3D)) {
/*
 * We have found a secondary 3d hardware device.  Return the DD object
 * and stop enumeration.
 */
memcpy(&driver->Guid, lpGUID, sizeof(GUID));
memcpy(&driver->HWCaps, &DriverCaps, sizeof(DDCAPS));
lstrcpy(driver->Name, lpDriverName);
driver->bIsPrimary = FALSE;
context->numdriver++;
    } else {
/*
 * It's the primary, fill in some fields.
 */
memset(&driver->Guid, 0, sizeof(GUID));
memcpy(&driver->HWCaps, &DriverCaps, sizeof(DDCAPS));
lstrcpy(driver->Name, "Primary Device");
driver->bIsPrimary = TRUE;
context->numdriver++;
    }
    lpDD->lpVtbl->Release(lpDD);
    if (context->numdriver >= D3DAPP_MAXDDDRIVERS)
return DDENUMRET_CANCEL;
    else
        return DDENUMRET_OK;
}

/*
 * D3DAppIEnumDDDevices
 * Enumerate all 3D capable devices and the primary display, filling in
 * the driver information variables.  lpDriver must point to a region
 * with enough room for the maximum DD drivers.
 */
BOOL
D3DAppIEnumDDDevices(int* NumDriver, D3DAppDDDriver* lpDriver)
{
    DDEnumCallbackContext context;

    context.numdriver = 0;
    context.driver = lpDriver;
    LastError = DirectDrawEnumerate(D3DAppIDDEnumCallback, &context);
    if (LastError != DD_OK) {
D3DAppISetErrorString("DirectDrawEnumerate failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    *NumDriver = context.numdriver;
    return TRUE;
}

/***************************************************************************/
/*                         Direct Draw Creation                            */
/***************************************************************************/

/*
 * D3DAppICreateDD
 * Creates the DirectDraw device and saves the current palette.
 * It is assumed that a 3D capable secondary DD driver is not the
 * primary device and hence cannot operate in a window (ie it's a
 * fullscreen only device displaying on a second monitor).  Valid flags:
 *     D3DAPP_ONLYDDEMULATION    Always use the DirectDraw HEL
 */
BOOL
D3DAppICreateDD(DWORD flags, LPGUID lpGuid)
{
    HDC hdc;
    int i;
    LPDIRECTDRAW lpDD = NULL;

    if (flags & D3DAPP_ONLYDDEMULATION)
lpGuid = NULL;

    if (!lpGuid)
d3dappi.bIsPrimary = TRUE;
    else
d3dappi.bIsPrimary = FALSE;

    LastError = DirectDrawCreate(lpGuid, &d3dappi.lpDD, NULL);
    if (LastError != DD_OK) {
D3DAppISetErrorString("DirectDrawCreate failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    /*
     * Save the original palette for when we are paused.  Just in case we
     * start in a fullscreen mode, put them in ppe.
     */
    hdc = GetDC(NULL);
    GetSystemPaletteEntries(hdc, 0, (1 << 8),
    (LPPALETTEENTRY)(&Originalppe[0]));
    for (i = 0; i < 256; i++)
        ppe[i] = Originalppe[i];
    ReleaseDC(NULL, hdc);
    return TRUE;
}

/***************************************************************************/
/*                   Enumerating the display modes                         */
/***************************************************************************/
/*
 * EnumDisplayModesCallback
 * Callback to save the display mode information.
 */
static HRESULT
CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID lpContext)
{
    /*
     * Very large resolutions cause problems on some hardware.  They are also
     * not very useful for real-time rendering.  We have chosen to disable
     * them by not reporting them as available.
     */
    if (pddsd->dwWidth > 1024 || pddsd->dwHeight > 768)
return DDENUMRET_OK;
    /*
     * Save this mode at the end of the mode array and increment mode count
     */
    d3dappi.Mode[d3dappi.NumModes].w = pddsd->dwWidth;
    d3dappi.Mode[d3dappi.NumModes].h = pddsd->dwHeight;
    d3dappi.Mode[d3dappi.NumModes].bpp = pddsd->ddpfPixelFormat.dwRGBBitCount;
    d3dappi.Mode[d3dappi.NumModes].bThisDriverCanDo = FALSE;
    d3dappi.NumModes++;
    if (d3dappi.NumModes == D3DAPP_MAXMODES)
return DDENUMRET_CANCEL;
    else
return DDENUMRET_OK;
}

/*
 * CompareModes
 * Compare two display modes during sorting.  Modes are sorted by depth and
 * then resolution.
 */
static int
_cdecl CompareModes(const void* element1, const void* element2) {
    D3DAppMode *lpMode1, *lpMode2;

    lpMode1 = (D3DAppMode*)element1;
    lpMode2 = (D3DAppMode*)element2;

    if (lpMode1->bpp > lpMode2->bpp)
        return -1;
    else if (lpMode2->bpp > lpMode1->bpp)
        return 1;
    else if (lpMode1->w > lpMode2->w)
        return -1;
    else if (lpMode2->w > lpMode1->w)
        return 1;
    else if (lpMode1->h > lpMode2->h)
        return -1;
    else if (lpMode2->h > lpMode1->h)
        return 1;
    else
        return 0;
}

/*
 * EnumerateDisplayModes
 * Generates the list of available display modes.
 */
BOOL
D3DAppIEnumDisplayModes(void)
{
    int i;
    /*
     * Get a list of available display modes from DirectDraw
     */
    d3dappi.NumModes = 0;
    LastError = d3dappi.lpDD->lpVtbl->EnumDisplayModes(d3dappi.lpDD, 0, NULL,
0, EnumDisplayModesCallback);
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("EnumDisplayModes failed.\n%s",
      D3DAppErrorToString(LastError));
d3dappi.NumModes = 0;
        return FALSE;
    }
    /*
     * Sort the list of display modes
     */
    qsort((void *)&d3dappi.Mode[0], (size_t)d3dappi.NumModes, sizeof(D3DAppMode),
          CompareModes);
    /*
     * Pick a default display mode.  640x480x16 is a very good mode for
     * rendering, so choose it over all others.  Otherwise, just take the
     * first one.  This selection may be overriden later if a driver is
     * created which cannot render in this mode.
     */
    d3dappi.CurrMode = 0;
    for (i = 0; i < d3dappi.NumModes; i++) {
if (d3dappi.Mode[i].w == 640 && d3dappi.Mode[i].h == 480 &&
    d3dappi.Mode[i].bpp == 16)
    d3dappi.CurrMode = i;
    }
    memcpy(&d3dappi.ThisMode, &d3dappi.Mode[d3dappi.CurrMode],
   sizeof(D3DAppMode));
    return TRUE;
}

/***************************************************************************/
/*               Creating Front and Back Buffers (and misc surf funcs)     */
/***************************************************************************/
/*
 * D3DAppICreateSurface
 * Create a DirectDraw Surface of the given description.  Using this function
 * ensures that all surfaces end up in system memory if that option was set.
 * Returns the result of the CreateSurface call.
 */
HRESULT
D3DAppICreateSurface(LPDDSURFACEDESC lpDDSurfDesc,
                LPDIRECTDRAWSURFACE FAR *lpDDSurface) {
    HRESULT result;
    if (d3dappi.bOnlySystemMemory)
        lpDDSurfDesc->ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    result = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, lpDDSurfDesc,
 lpDDSurface, NULL);
    return result;
}

/*
 * D3DAppIGetSurfDesc
 * Get the description of the given surface.  Returns the result of the
 * GetSurfaceDesc call.
 */
HRESULT
D3DAppIGetSurfDesc(LPDDSURFACEDESC lpDDSurfDesc,LPDIRECTDRAWSURFACE lpDDSurf)
{
    HRESULT result;
    memset(lpDDSurfDesc, 0, sizeof(DDSURFACEDESC));
    lpDDSurfDesc->dwSize = sizeof(DDSURFACEDESC);
    result = lpDDSurf->lpVtbl->GetSurfaceDesc(lpDDSurf, lpDDSurfDesc);
    return result;
}

/*
 * D3DAppICreateBuffers
 * Creates the front and back buffers for the window or fullscreen case
 * depending on the bFullscreen flag.  In the window case, bpp is ignored.
 */
BOOL
D3DAppICreateBuffers(HWND hwnd, int w, int h, int bpp, BOOL bFullscreen, BOOL bIsHardware)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;

    /*
     * Release any old objects that might be lying around.  This should have
     * already been taken care of, but just in case...
     */
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    /*
     * The size of the buffers is going to be w x h, so record it now
     */
    if (w < D3DAPP_WINDOWMINIMUM)
w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
h = D3DAPP_WINDOWMINIMUM;
    szBuffers.cx = w;
    szBuffers.cy = h;

    if (bFullscreen) {
        /*
         * Create a complex flipping surface for fullscreen mode with one
 * back buffer.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
        DDSCAPS_3DDEVICE | DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
if (bIsHardware)
    ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpFrontBuffer);
    if(LastError != DD_OK) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
    } else {
D3DAppISetErrorString("CreateSurface for fullscreen flipping surface failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
}
/*
 * Obtain a pointer to the back buffer surface created above so we
 * can use it later.  For now, just check to see if it ended up in
 * video memory (FYI).
 */
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    LastError = d3dappi.lpFrontBuffer->lpVtbl->GetAttachedSurface(d3dappi.lpFrontBuffer, &ddscaps, &d3dappi.lpBackBuffer);
    if(LastError != DD_OK) {
            D3DAppISetErrorString("GetAttachedSurface failed to get back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to get surface description of back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
        d3dappi.bBackBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    }
    else {
        /*
         * In the window case, create a front buffer which is the primary
 * surface and a back buffer which is an offscreen plane surface.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
        ddsd.dwSize = sizeof(DDSURFACEDESC);
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
/*
 * If we specify system memory when creating a primary surface, we
 * won't get the actual primary surface in video memory.  So, don't
 * use D3DAppICreateSurface().
 */
LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD,
&ddsd, &d3dappi.lpFrontBuffer, NULL);
        if(LastError != DD_OK ) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    } else {
D3DAppISetErrorString("CreateSurface for window front buffer failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
        }
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth = w;
    ddsd.dwHeight = h;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
if (bIsHardware)
    ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
else
    ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpBackBuffer);
    if (LastError != DD_OK) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    } else {
D3DAppISetErrorString("CreateSurface for window back buffer failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
}
/*
 * Check to see if the back buffer is in video memory (FYI).
 */
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to get surface description for back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
d3dappi.bBackBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
        /*
         * Create the DirectDraw Clipper object and attach it to the window
 * and front buffer.
         */
        LastError = d3dappi.lpDD->lpVtbl->CreateClipper(d3dappi.lpDD, 0,
&lpClipper, NULL);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("CreateClipper failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    LastError = lpClipper->lpVtbl->SetHWnd(lpClipper, 0, hwnd);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to window failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    LastError =
     d3dappi.lpFrontBuffer->lpVtbl->SetClipper(d3dappi.lpFrontBuffer,
       lpClipper);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    }

    D3DAppIClearBuffers();
    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpFrontBuffer);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(lpClipper);
    return FALSE;
}

/*
 * D3DAppICheckForPalettized
 * If the front/back buffer is palettized, we need to create a palette.
 */
BOOL
D3DAppICheckForPalettized(void)
{
    DDSURFACEDESC ddsd;
    /*
     * Get the back buffer surface description and check to see if it's
     * palettized
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to get surface description for back buffer for palettizing.\n%s",
      D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    bPrimaryPalettized =
(ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) ? TRUE : FALSE;

    if (bPrimaryPalettized) {
int i;
/*
 * Get the current palette.
 */
 HDC hdc = GetDC(NULL);
GetSystemPaletteEntries(hdc, 0, (1 << 8), ppe);
ReleaseDC(NULL, hdc);
/*
 * Change the flags on the palette entries to allow D3D to change
 * some of them.  In the window case, we must not change the top and
 * bottom ten (system colors), but in a fullscreen mode we can have
 * all but the first and last.
 */
        if (!d3dappi.bFullscreen) {
    for (i = 0; i < 10; i++) ppe[i].peFlags = D3DPAL_READONLY;
    for (i = 10; i < 256 - 10; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
    for (i = 256 - 10; i < 256; i++) ppe[i].peFlags = D3DPAL_READONLY;
        } else {
    ppe[0].peFlags = D3DPAL_READONLY;
    for (i = 1; i < 255; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
    ppe[255].peFlags = D3DPAL_READONLY;
}
/*
 * Create a palette using the old colors and new flags
 */
LastError = d3dappi.lpDD->lpVtbl->CreatePalette(d3dappi.lpDD,
   DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
   ppe, &lpPalette, NULL);
if (LastError != DD_OK) {
    D3DAppISetErrorString("CreatePalette failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
/*
 * Set this as the front and back buffers' palette
 */
LastError =
       d3dappi.lpBackBuffer->lpVtbl->SetPalette(d3dappi.lpBackBuffer,
        lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
LastError =
     d3dappi.lpFrontBuffer->lpVtbl->SetPalette(d3dappi.lpFrontBuffer,
       lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on front buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
/*
 * The palette is now valid, so set it again on anyt WM_ACTIVATE
 */
bPaletteActivate = TRUE;
    }
    return TRUE;
exit_with_error:
    RELEASE(lpPalette);
    return FALSE;
}

/***************************************************************************/
/*                           Creation of Z-Buffer                          */
/***************************************************************************/
/*
 * D3DAppICreateZBuffer
 * Create a Z-Buffer of the appropriate depth and attach it to the back
 * buffer.
 */
BOOL
D3DAppICreateZBuffer(int w, int h, int driver)
{
    DDSURFACEDESC ddsd;
    DWORD devDepth;
    /*
     * Release any Z-Buffer that might be around just in case.
     */
    RELEASE(d3dappi.lpZBuffer);

    /*
     * If this driver does not do z-buffering, don't create a z-buffer
     */
    if (!d3dappi.Driver[driver].bDoesZBuffer)
return TRUE;

    memset(&ddsd, 0 ,sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS |
   DDSD_ZBUFFERBITDEPTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
    ddsd.dwHeight = h;
    ddsd.dwWidth = w;
    /*
     * If this is a hardware D3D driver, the Z-Buffer MUST end up in video
     * memory.  Otherwise, it MUST end up in system memory.
     */
    if (d3dappi.Driver[driver].bIsHardware)
ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    else
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    /*
     * Get the Z buffer bit depth from this driver's D3D device description
     */
    devDepth = d3dappi.Driver[driver].Desc.dwDeviceZBufferBitDepth;
    if (devDepth & DDBD_32)
ddsd.dwZBufferBitDepth = 32;
    else if (devDepth & DDBD_24)
ddsd.dwZBufferBitDepth = 24;
    else if (devDepth & DDBD_16)
ddsd.dwZBufferBitDepth = 16;
    else if (devDepth & DDBD_8)
ddsd.dwZBufferBitDepth = 8;
    else {
D3DAppISetErrorString("Unsupported Z-buffer depth requested by device.\n");
return FALSE;
    }
    LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, &ddsd,
    &d3dappi.lpZBuffer,
    NULL);
    if(LastError != DD_OK) {
if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
    if (d3dappi.bFullscreen) {
D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
    } else {
D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    }
} else {
    D3DAppISetErrorString("CreateSurface for Z-buffer failed.\n%s",
  D3DAppErrorToString(LastError));
}
        goto exit_with_error;
    }
    /*
     * Attach the Z-buffer to the back buffer so D3D will find it
     */
    LastError =
       d3dappi.lpBackBuffer->lpVtbl->AddAttachedSurface(d3dappi.lpBackBuffer,
d3dappi.lpZBuffer);
    if(LastError != DD_OK) {
        D3DAppISetErrorString("AddAttachedBuffer failed for Z-Buffer.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Find out if it ended up in video memory.
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpZBuffer);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to get surface description of Z buffer.\n%s",
      D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    d3dappi.bZBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    if (d3dappi.Driver[driver].bIsHardware && !d3dappi.bZBufferInVideo) {
D3DAppISetErrorString("Could not fit the Z-buffer in video memory for this hardware device.\n");
goto exit_with_error;
    }

    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpZBuffer);
    return FALSE;
}

/***************************************************************************/
/*                             WM_SIZE Handler                             */
/***************************************************************************/
/*
 * D3DAppIHandleWM_SIZE
 * Processes the WM_SIZE message.  Resizes all the buffers and re-creates
 * device if necessary.
 */
BOOL
D3DAppIHandleWM_SIZE(LRESULT* lresult, HWND hwnd, UINT message,
     WPARAM wParam, LPARAM lParam)
{
    int w, h, i;
    /*
     * If we have minimzied, take note and call the default window proc
     */
    if (wParam == SIZE_MINIMIZED) {
d3dappi.bMinimized = TRUE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * In fullscreen mode, restore our surfaces and let DDraw take
     * care of the rest.
     */
    if (d3dappi.bFullscreen) {
D3DAppIValidateDirtyRects();
D3DAppCheckForLostSurfaces();
d3dappi.bMinimized = FALSE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * If we are minimized, this is the un-minimized size message.
     */
    if (d3dappi.bMinimized) {
/*
 * Restore our surfaces and update the dirty rectangle info
 */
D3DAppIValidateDirtyRects();
D3DAppCheckForLostSurfaces();
d3dappi.bMinimized = FALSE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * Since we are still here, this must be a regular, window resize
     * message.  A new viewport will definitely be needed, but the
     * device and buffers will only be re-created if they have gotten bigger
     * or change size by a very large amount.
     */
    D3DAppIGetClientWin(hwnd);
    w = LOWORD(lParam);
    h = HIWORD(lParam);
    /*
     * If w and h are under the minimum, create buffers of the minimum size
     */
    if (w < D3DAPP_WINDOWMINIMUM)
        w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
        h = D3DAPP_WINDOWMINIMUM;
    /*
     * Destroy the viewport and all execute buffers
     */
    d3dappi.bRenderingIsOK = FALSE;
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    /*
     * Only create a new device and buffers if they changed significantly,
     * otherwise just make sure the old buffers aren't lost.
     */
    if ((w > szBuffers.cx || h > szBuffers.cy) ||
(w < szBuffers.cx / 2 || h < szBuffers.cy / 2)) {
/*
 * Release the device
 */
RELEASE(d3dappi.lpD3DDevice);
/*
 * Release the old buffers
 */
RELEASE(d3dappi.lpZBuffer);
RELEASE(lpPalette);
RELEASE(lpClipper);
RELEASE(d3dappi.lpBackBuffer);
RELEASE(d3dappi.lpFrontBuffer);
/*
 * Create new ones
 */
ATTEMPT(D3DAppICreateBuffers(hwnd, w, h, D3DAPP_BOGUS, FALSE, d3dappi.ThisDriver.bIsHardware));
ATTEMPT(D3DAppICheckForPalettized());
ATTEMPT(D3DAppICreateZBuffer(w, h, d3dappi.CurrDriver));
/*
 * Create the driver
 */
ATTEMPT(D3DAppICreateDevice(d3dappi.CurrDriver));
/*
 * Since the driver did not change, the texture surfaces are still valid.
 * We just need to get new handles.
 */
if (d3dappi.ThisDriver.bDoesTextures) {
    for (i = 0; i < d3dappi.NumUsableTextures; i++) {
D3DAppIGetTextureHandle(i);
    }
}
    } else {
D3DAppCheckForLostSurfaces();
    }
    /*
     * Call the device create callback to create the viewport, set the render
     * state and clear the dirty rectangle info
     */
    ATTEMPT(D3DAppICallDeviceCreateCallback(w, h));
    ATTEMPT(D3DAppISetRenderState());
    D3DAppIValidateDirtyRects();
    d3dappi.bRenderingIsOK = TRUE;
    /*
     * Call the default window proc
     */
    *lresult = DefWindowProc(hwnd, message, wParam, lParam);
    return TRUE;
exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    return FALSE;
}

/***************************************************************************/
/*              Setting the display mode and cooperative level             */
/***************************************************************************/
/*
 * D3DAppISetCoopLevel
 * Set the cooperative level to exclusive mode for fullscreen and normal for
 * a window.  Set the bIgnoreWM_SIZE flag because SetCooperativeLevel
 * generates a WM_SIZE message you do not have to resize the buffers on.
 */
BOOL
D3DAppISetCoopLevel(HWND hwnd, BOOL bFullscreen)
{
    if (bFullscreen) {
bIgnoreWM_SIZE = TRUE;
LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
   hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
bIgnoreWM_SIZE = FALSE;
if(LastError != DD_OK ) {
    D3DAppISetErrorString("SetCooperativeLevel to fullscreen failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
d3dappi.bFullscreen = TRUE;
    } else {
bIgnoreWM_SIZE = TRUE;
        LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
 hwnd, DDSCL_NORMAL);
bIgnoreWM_SIZE = FALSE;
if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetCooperativeLevel to normal failed.\n%s",
  D3DAppErrorToString(LastError));
            return FALSE;
        }
d3dappi.bFullscreen = FALSE;
    }
    return TRUE;
}

/*
 * D3DAppISetDisplayMode
 * Set the display mode to the given dimensions and bits per pixel.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppISetDisplayMode(int w, int h, int bpp)
{
    d3dappi.ThisMode.w = w; d3dappi.ThisMode.h = h;
    d3dappi.ThisMode.bpp = bpp;
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->SetDisplayMode(d3dappi.lpDD, w, h,
     bpp);
    bIgnoreWM_SIZE = FALSE;
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("SetDisplayMode to %dx%dx%d failed\n%s",
      w, h, bpp, D3DAppErrorToString(LastError));
        return FALSE;
    }
    return TRUE;
}

/*
 * D3DAppIRestoreDispMode
 * Restores the display mode to the current windows display mode.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppIRestoreDispMode(void)
{
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->RestoreDisplayMode(d3dappi.lpDD);

if (LastError != DD_OK) {
D3DAppISetErrorString("RestoreDisplayMode failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    bIgnoreWM_SIZE = FALSE;
    return TRUE;
}

/*
 * D3DAppRememberWindowsMode
 * Record the current display mode in d3dappi.WindowsDisplay
 */
BOOL
D3DAppIRememberWindowsMode(void)
{
    DDSURFACEDESC ddsd;

    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    LastError = d3dappi.lpDD->lpVtbl->GetDisplayMode(d3dappi.lpDD, &ddsd);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Getting the current display mode failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    d3dappi.WindowsDisplay.w = ddsd.dwWidth;
    d3dappi.WindowsDisplay.h = ddsd.dwHeight;
    d3dappi.WindowsDisplay.bpp = ddsd.ddpfPixelFormat.dwRGBBitCount;
    return TRUE;
}

/***************************************************************************/
/*                          Misc DD Utilities                              */
/***************************************************************************/

/*
 * D3DAppIClearBuffers
 * Clear the front and back buffers to black
 */
BOOL
D3DAppIClearBuffers(void)
{
    DDSURFACEDESC ddsd;
    RECT dst;
    DDBLTFX ddbltfx;
    /*
     * Find the width and height of the front buffer by getting its
     * DDSURFACEDESC
     */
    if (d3dappi.lpFrontBuffer) {
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpFrontBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failure getting the surface description of the front buffer before clearing.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
/*
 * Clear the front buffer to black
 */
memset(&ddbltfx, 0, sizeof(ddbltfx));
ddbltfx.dwSize = sizeof(DDBLTFX);
SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
LastError = d3dappi.lpFrontBuffer->lpVtbl->Blt(d3dappi.lpFrontBuffer,
    &dst, NULL, NULL,
    DDBLT_COLORFILL | DDBLT_WAIT,
    &ddbltfx);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    }
    if (d3dappi.lpBackBuffer) {
/*
 * Find the width and height of the back buffer by getting its
 * DDSURFACEDESC
 */
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failure while getting the surface description of the back buffer before clearing.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
/*
 * Clear the back buffer to black
 */
memset(&ddbltfx, 0, sizeof(ddbltfx));
ddbltfx.dwSize = sizeof(DDBLTFX);
SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
LastError = d3dappi.lpBackBuffer->lpVtbl->Blt(d3dappi.lpBackBuffer, &dst,
     NULL, NULL,
     DDBLT_COLORFILL | DDBLT_WAIT,
     &ddbltfx);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    }
    return TRUE;
}

/*
 * D3DAppIBPPToDDBD
 * Convert an integer bit per pixel number to a DirectDraw bit depth flag
 */
DWORD
D3DAppIBPPToDDBD(int bpp)
{
    switch(bpp) {
case 1:
    return DDBD_1;
case 2:
    return DDBD_2;
case 4:
    return DDBD_4;
case 8:
    return DDBD_8;
case 16:
    return DDBD_16;
case 24:
    return DDBD_24;
case 32:
    return DDBD_32;
default:
    return (DWORD)D3DAPP_BOGUS;
    }
}

/*
 * D3DAppTotalVideoMemory
 * Returns the amount of total video memory supported (not free)
 */
DWORD
D3DAppTotalVideoMemory(void)
{
    DDCAPS DriverCaps, HELCaps;
    memset (&DriverCaps, 0, sizeof(DDCAPS));
    DriverCaps.dwSize = sizeof(DDCAPS);
    memset (&HELCaps, 0, sizeof(DDCAPS));
    HELCaps.dwSize = sizeof(DDCAPS);
    LastError = d3dappi.lpDD->lpVtbl->GetCaps(d3dappi.lpDD, &DriverCaps,
      &HELCaps);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Getting DD capabilities failed while checking total video memory.\n%s",
      D3DAppErrorToString(LastError));
return 0L;
    }
    if (DriverCaps.dwVidMemTotal)
return DriverCaps.dwVidMemTotal;
    else
return HELCaps.dwVidMemTotal;
}


DDMM.CPP

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddmm.cpp
 *  Content:    Routines for using DirectDraw on a multimonitor system
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#define WINVER 0x0400
#define _WIN32_WINDOWS 0x0400
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddmm.h"

#define COMPILE_MULTIMON_STUBS
#include "multimon.h"

/*
 *  OneMonitorCallback
 */
BOOL CALLBACK OneMonitorCallback(HMONITOR hMonitor, HDC hdc, LPRECT prc, LPARAM lParam)
{
    HMONITOR *phMonitorFound = (HMONITOR *)lParam;

    if (*phMonitorFound == 0)
        *phMonitorFound = hMonitor;
    else
        *phMonitorFound = (HMONITOR) INVALID_HANDLE_VALUE;

    return TRUE;
}

/*
 *  OneMonitorFromWindow
 *
 *  similar to the Win32 function MonitorFromWindow, except
 *  only returns a HMONITOR if a window is on a single monitor.
 *
 *  if the window handle is NULL, the primary monitor is returned
 *  if the window is not visible returns NULL
 *  if the window is on a single monitor returns its HMONITOR
 *  if the window is on more than on monitor returns INVALID_HANDLE_VALUE
 */
HMONITOR OneMonitorFromWindow(HWND hwnd)
{
    HMONITOR hMonitor = NULL;
    RECT rc;

    if (hwnd)
    {
        GetClientRect(hwnd, &rc);
        ClientToScreen(hwnd, (LPPOINT)&rc);
        ClientToScreen(hwnd, (LPPOINT)&rc+1);
    }
    else
    {
        //SetRect(&rc,0,0,1,1);
        SetRectEmpty(&rc);
    }

    EnumDisplayMonitors(NULL, &rc, OneMonitorCallback, (LPARAM)&hMonitor);
    return hMonitor;
}

/*
 * FindDeviceCallback
 */
typedef struct {
    LPSTR   szDevice;
    GUID*   lpGUID;
    GUID    GUID;
    BOOL    fFound;
}   FindDeviceData;

BOOL CALLBACK FindDeviceCallback(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam)
{
    FindDeviceData *p = (FindDeviceData*)lParam;

    if (lstrcmpi(p->szDevice, szDevice) == 0)
    {
if (lpGUID)
{
    p->GUID = *lpGUID;
    p->lpGUID = &p->GUID;
}
else
{
    p->lpGUID = NULL;
}
p->fFound = TRUE;
return FALSE;
    }
    return TRUE;
}

/*
 * DirectDrawCreateFromDevice
 *
 * create a DirectDraw object for a particular device
 */
IDirectDraw * DirectDrawCreateFromDevice(LPSTR szDevice)
{
    IDirectDraw*    pdd = NULL;
    FindDeviceData  find;

    find.szDevice = szDevice;
    find.fFound   = FALSE;
    DirectDrawEnumerate(FindDeviceCallback, (LPVOID)&find);

    if (find.fFound)
    {
        DirectDrawCreate(find.lpGUID, &pdd, NULL);
    }

    return pdd;
}

/*
 * DirectDrawDeviceFromWindow
 *
 * find the direct draw device that should be used for a given window
 *
 * the return code is a "unique id" for the device, it should be used
 * to determine when your window moves from one device to another.
 *
 *      case WM_MOVE:
 *          if (MyDevice != DirectDrawDeviceFromWindow(hwnd,NULL,NULL))
 *          {
 *              // handle moving to a new device.
 *          }
 *
 */
int DirectDrawDeviceFromWindow(HWND hwnd, LPSTR szDevice, RECT *prc)
{
    HMONITOR hMonitor;

    if (GetSystemMetrics(SM_CMONITORS) <= 1)
    {
        if (prc) SetRect(prc,0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
        if (szDevice) lstrcpy(szDevice, "DISPLAY");
        return -1;
    }

    hMonitor = OneMonitorFromWindow(hwnd);

    if (hMonitor == NULL || hMonitor == INVALID_HANDLE_VALUE)
    {
if (prc) SetRectEmpty(prc);
if (szDevice) *szDevice=0;
        return 0;
    }
    else
    {
if (prc != NULL || szDevice != NULL)
{
    MONITORINFOEX mi;
    mi.cbSize = sizeof(mi);
    GetMonitorInfo(hMonitor, &mi);
    if (prc) *prc = mi.rcMonitor;
    if (szDevice) lstrcpy(szDevice, mi.szDevice);
}
        return (int)hMonitor;
    }
}

/*
 * DirectDrawCreateFromWindow
 */
IDirectDraw * DirectDrawCreateFromWindow(HWND hwnd)
{
    IDirectDraw *pdd;
    char szDevice[80];

    //
    // if there is only one monitor, just create a DD object!
    //
    if (GetSystemMetrics(SM_CMONITORS) <= 1)
    {
DirectDrawCreate(NULL, &pdd, NULL);
        return pdd;
    }

    //
    // find the direct draw device that the window is on
    //
    if (DirectDrawDeviceFromWindow(hwnd, szDevice, NULL))
    {
//
// the window is only on one device,
// do a create for only that device
//
        return DirectDrawCreateFromDevice(szDevice);
    }
    else
    {
//
// the window is off the screen or spans two
// monitors, do a DirectDrawCreate(NULL)
        //
        DirectDrawCreate(NULL, &pdd, NULL);
return pdd;
    }
}


DDMM.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddmm.cpp
 *  Content:    Routines for using DirectDraw on a multimonitor system
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif  /* __cplusplus */

IDirectDraw * DirectDrawCreateFromDevice(LPSTR szDevice);
IDirectDraw * DirectDrawCreateFromWindow(HWND hwnd);
int           DirectDrawDeviceFromWindow(HWND hwnd, LPSTR szDevice, RECT*prc);

#ifdef __cplusplus
}
#endif/* __cplusplus */


DDUTIL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddutil.h"

/*
 *  DDLoadBitmap
 *
 *  create a DirectDrawSurface from a bitmap resource.
 *
 */
extern "C" IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap, int dx, int dy)
{
    HBITMAP             hbm;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    IDirectDrawSurface *pdds;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, dx, dy, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, dx, dy, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
return NULL;

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);      // get size of bitmap

    //
    // create a DirectDrawSurface for this bitmap
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
return NULL;

    DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);

    DeleteObject(hbm);

    return pdds;
}

/*
 *  DDReLoadBitmap
 *
 *  load a bitmap from a file or resource into a directdraw surface.
 *  normaly used to re-load a surface after a restore.
 *
 */
HRESULT DDReLoadBitmap(IDirectDrawSurface *pdds, LPCSTR szBitmap)
{
    HBITMAP             hbm;
    HRESULT             hr;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
    {
OutputDebugString("handle is null\n");
return E_FAIL;
    }

    hr = DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);
    if (hr != DD_OK)
    {
OutputDebugString("ddcopybitmap failed\n");
    }

    DeleteObject(hbm);
    return hr;
}

/*
 *  DDCopyBitmap
 *
 *  draw a bitmap into a DirectDrawSurface
 *
 */
extern "C" HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy)
{
    HDC                 hdcImage;
    HDC                 hdc;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    HRESULT             hr;

    if (hbm == NULL || pdds == NULL)
return E_FAIL;

    //
    // make sure this surface is restored.
    //
    pdds->Restore();

    //
    //  select bitmap into a memoryDC so we can use it.
    //
    hdcImage = CreateCompatibleDC(NULL);
    if (!hdcImage)
OutputDebugString("createcompatible dc failed\n");
    SelectObject(hdcImage, hbm);

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);    // get size of bitmap
    dx = dx == 0 ? bm.bmWidth  : dx;    // use the passed size, unless zero
    dy = dy == 0 ? bm.bmHeight : dy;

    //
    // get size of surface.
    //
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
    pdds->GetSurfaceDesc(&ddsd);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)
    {
StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);
pdds->ReleaseDC(hdc);
    }

    DeleteDC(hdcImage);

    return hr;
}

//
//  DDLoadPalette
//
//  Create a DirectDraw palette object from a bitmap resoure
//
//  if the resource does not exist or NULL is passed create a
//  default 332 palette.
//
extern "C" IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap)
{
    IDirectDrawPalette* ddpal;
    int                 i;
    int                 n;
    int                 fh;
    HRSRC               h;
    LPBITMAPINFOHEADER  lpbi;
    PALETTEENTRY        ape[256];
    RGBQUAD *           prgb;

    //
    // build a 332 palette as the default.
    //
    for (i=0; i<256; i++)
    {
ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
ape[i].peFlags = (BYTE)0;
    }

    //
    // get a pointer to the bitmap resource.
    //
    if (szBitmap && (h = FindResource(NULL, szBitmap, RT_BITMAP)))
    {
lpbi = (LPBITMAPINFOHEADER)LockResource(LoadResource(NULL, h));
if (!lpbi)
    OutputDebugString("lock resource failed\n");
prgb = (RGBQUAD*)((BYTE*)lpbi + lpbi->biSize);

if (lpbi == NULL || lpbi->biSize < sizeof(BITMAPINFOHEADER))
    n = 0;
else if (lpbi->biBitCount > 8)
    n = 0;
else if (lpbi->biClrUsed == 0)
    n = 1 << lpbi->biBitCount;
else
    n = lpbi->biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    ape[i].peRed   = prgb[i].rgbRed;
    ape[i].peGreen = prgb[i].rgbGreen;
    ape[i].peBlue  = prgb[i].rgbBlue;
    ape[i].peFlags = 0;
}
    }
    else if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)
    {
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;

_lread(fh, &bf, sizeof(bf));
_lread(fh, &bi, sizeof(bi));
_lread(fh, ape, sizeof(ape));
_lclose(fh);

if (bi.biSize != sizeof(BITMAPINFOHEADER))
    n = 0;
else if (bi.biBitCount > 8)
    n = 0;
else if (bi.biClrUsed == 0)
    n = 1 << bi.biBitCount;
else
    n = bi.biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    BYTE r = ape[i].peRed;
    ape[i].peRed  = ape[i].peBlue;
    ape[i].peBlue = r;
}
    }

    pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);

    return ddpal;
}

/*
 * DDColorMatch
 *
 * convert a RGB color to a pysical color.
 *
 * we do this by leting GDI SetPixel() do the color matching
 * then we lock the memory and see what it got mapped to.
 */
extern "C" DWORD DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb)
{
    COLORREF rgbT;
    HDC hdc;
    DWORD dw = CLR_INVALID;
    DDSURFACEDESC ddsd;
    HRESULT hres;

    //
    //  use GDI SetPixel to color match for us
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
rgbT = GetPixel(hdc, 0, 0);             // save current pixel value
SetPixel(hdc, 0, 0, rgb);               // set our value
pdds->ReleaseDC(hdc);
    }

    //
    // now lock the surface so we can read back the converted color
    //
    ddsd.dwSize = sizeof(ddsd);
    while ((hres = pdds->Lock(NULL, &ddsd, 0, NULL)) == DDERR_WASSTILLDRAWING)
;

    if (hres == DD_OK)
    {
dw  = *(DWORD *)ddsd.lpSurface;                     // get DWORD
        if(ddsd.ddpfPixelFormat.dwRGBBitCount < 32)
            dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount)-1;  // mask it to bpp
pdds->Unlock(NULL);
    }

    //
    //  now put the color that was there back.
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
SetPixel(hdc, 0, 0, rgbT);
pdds->ReleaseDC(hdc);
    }

    return dw;
}

/*
 * DDSetColorKey
 *
 * set a color key for a surface, given a RGB.
 * if you pass CLR_INVALID as the color key, the pixel
 * in the upper-left corner will be used.
 */
extern "C" HRESULT DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb)
{
    DDCOLORKEY          ddck;

    ddck.dwColorSpaceLowValue  = DDColorMatch(pdds, rgb);
    ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;
    return pdds->SetColorKey(DDCKEY_SRCBLT, &ddck);
}


DDUTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif/* __cplusplus */

extern IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap);
extern IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap, int dx, int dy);
extern HRESULT              DDReLoadBitmap(IDirectDrawSurface *pdds, LPCSTR szBitmap);
extern HRESULT              DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int x, int y, int dx, int dy);
extern DWORD                DDColorMatch(IDirectDrawSurface *pdds, COLORREF rgb);
extern HRESULT              DDSetColorKey(IDirectDrawSurface *pdds, COLORREF rgb);

#ifdef __cplusplus
}
#endif/* __cplusplus */


DDUTIL2.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddutil2.h"

/*
 *  DDLoadBitmap
 *
 *  create a DirectDrawSurface from a bitmap resource.
 *
 */
extern "C" IDirectDrawSurface3 * DDLoadBitmap2(IDirectDraw2 *pdd, LPCSTR szBitmap, int dx, int dy)
{
    HBITMAP             hbm;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    IDirectDrawSurface3 *pdds;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, dx, dy, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, dx, dy, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
return NULL;

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);      // get size of bitmap

    //
    // create a DirectDrawSurface for this bitmap
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;

    LPDIRECTDRAWSURFACE lpDDSTemp;
    if (pdd->CreateSurface(&ddsd, &lpDDSTemp, NULL) != DD_OK)
    {
        OutputDebugString("CreateSurf failed");
        return NULL;
    };
    if (lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3,(void**)&pdds) != DD_OK)
    {
        OutputDebugString("QueryInterface failed");
        return NULL;
    }
    lpDDSTemp->Release();

    DDCopyBitmap2(pdds, hbm, 0, 0, 0, 0);

    DeleteObject(hbm);

    return pdds;
}

/*
 *  DDReLoadBitmap
 *
 *  load a bitmap from a file or resource into a directdraw surface.
 *  normaly used to re-load a surface after a restore.
 *
 */
HRESULT DDReLoadBitmap2(IDirectDrawSurface3 *pdds, LPCSTR szBitmap)
{
    HBITMAP             hbm;
    HRESULT             hr;

    //
    //  try to load the bitmap as a resource, if that fails, try it as a file
    //
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

    if (hbm == NULL)
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
    {
OutputDebugString("handle is null\n");
return E_FAIL;
    }

    hr = DDCopyBitmap2(pdds, hbm, 0, 0, 0, 0);
    if (hr != DD_OK)
    {
OutputDebugString("ddcopybitmap failed \n");
    }

    DeleteObject(hbm);
    return hr;
}

/*
 *  DDCopyBitmap
 *
 *  draw a bitmap into a DirectDrawSurface
 *
 */
extern "C" HRESULT DDCopyBitmap2(IDirectDrawSurface3 *pdds, HBITMAP hbm, int x, int y, int dx, int dy)
{
    HDC                 hdcImage;
    HDC                 hdc;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    HRESULT             hr;

    if (hbm == NULL || pdds == NULL)
return E_FAIL;

    //
    // make sure this surface is restored.
    //
    pdds->Restore();

    //
    //  select bitmap into a memoryDC so we can use it.
    //
    hdcImage = CreateCompatibleDC(NULL);
    if (!hdcImage)
OutputDebugString("createcompatible dc failed\n");
    SelectObject(hdcImage, hbm);

    //
    // get size of the bitmap
    //
    GetObject(hbm, sizeof(bm), &bm);    // get size of bitmap
    dx = dx == 0 ? bm.bmWidth  : dx;    // use the passed size, unless zero
    dy = dy == 0 ? bm.bmHeight : dy;

    //
    // get size of surface.
    //
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
    pdds->GetSurfaceDesc(&ddsd);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)
    {
        StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);
        pdds->ReleaseDC(hdc);
    }

    DeleteDC(hdcImage);

    return hr;
}

//
//  DDLoadPalette
//
//  Create a DirectDraw palette object from a bitmap resoure
//
//  if the resource does not exist or NULL is passed create a
//  default 332 palette.
//
extern "C" IDirectDrawPalette * DDLoadPalette2(IDirectDraw2 *pdd, LPCSTR szBitmap)
{
    IDirectDrawPalette* ddpal;
    int                 i;
    int                 n;
    int                 fh;
    HRSRC               h;
    LPBITMAPINFOHEADER  lpbi;
    PALETTEENTRY        ape[256];
    RGBQUAD *           prgb;

    //
    // build a 332 palette as the default.
    //
    for (i=0; i<256; i++)
    {
ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
ape[i].peFlags = (BYTE)0;
    }

    //
    // get a pointer to the bitmap resource.
    //
    if (szBitmap && (h = FindResource(NULL, szBitmap, RT_BITMAP)))
    {
lpbi = (LPBITMAPINFOHEADER)LockResource(LoadResource(NULL, h));
if (!lpbi)
    OutputDebugString("lock resource failed\n");
prgb = (RGBQUAD*)((BYTE*)lpbi + lpbi->biSize);

if (lpbi == NULL || lpbi->biSize < sizeof(BITMAPINFOHEADER))
    n = 0;
else if (lpbi->biBitCount > 8)
    n = 0;
else if (lpbi->biClrUsed == 0)
    n = 1 << lpbi->biBitCount;
else
    n = lpbi->biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    ape[i].peRed   = prgb[i].rgbRed;
    ape[i].peGreen = prgb[i].rgbGreen;
    ape[i].peBlue  = prgb[i].rgbBlue;
    ape[i].peFlags = 0;
}
    }
    else if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)
    {
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;

_lread(fh, &bf, sizeof(bf));
_lread(fh, &bi, sizeof(bi));
_lread(fh, ape, sizeof(ape));
_lclose(fh);

if (bi.biSize != sizeof(BITMAPINFOHEADER))
    n = 0;
else if (bi.biBitCount > 8)
    n = 0;
else if (bi.biClrUsed == 0)
    n = 1 << bi.biBitCount;
else
    n = bi.biClrUsed;

//
//  a DIB color table has its colors stored BGR not RGB
//  so flip them around.
//
for(i=0; i<n; i++ )
{
    BYTE r = ape[i].peRed;
    ape[i].peRed  = ape[i].peBlue;
    ape[i].peBlue = r;
}
    }

    pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);

    return ddpal;
}

/*
 * DDColorMatch
 *
 * convert a RGB color to a pysical color.
 *
 * we do this by leting GDI SetPixel() do the color matching
 * then we lock the memory and see what it got mapped to.
 */
extern "C" DWORD DDColorMatch2(IDirectDrawSurface3 *pdds, COLORREF rgb)
{
    COLORREF rgbT;
    HDC hdc;
    DWORD dw = CLR_INVALID;
    DDSURFACEDESC ddsd;
    HRESULT hres;

    //
    //  use GDI SetPixel to color match for us
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
rgbT = GetPixel(hdc, 0, 0);             // save current pixel value
SetPixel(hdc, 0, 0, rgb);               // set our value
pdds->ReleaseDC(hdc);
    }

    //
    // now lock the surface so we can read back the converted color
    //
    ddsd.dwSize = sizeof(ddsd);
    while ((hres = pdds->Lock(NULL, &ddsd, 0, NULL)) == DDERR_WASSTILLDRAWING)
;

    if (hres == DD_OK)
    {
dw  = *(DWORD *)ddsd.lpSurface;                     // get DWORD
dw &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount)-1;  // mask it to bpp
pdds->Unlock(NULL);
    }

    //
    //  now put the color that was there back.
    //
    if (rgb != CLR_INVALID && pdds->GetDC(&hdc) == DD_OK)
    {
SetPixel(hdc, 0, 0, rgbT);
pdds->ReleaseDC(hdc);
    }

    return dw;
}

/*
 * DDSetColorKey
 *
 * set a color key for a surface, given a RGB.
 * if you pass CLR_INVALID as the color key, the pixel
 * in the upper-left corner will be used.
 */
extern "C" HRESULT DDSetColorKey2(IDirectDrawSurface3 *pdds, COLORREF rgb)
{
    DDCOLORKEY          ddck;

    ddck.dwColorSpaceLowValue  = DDColorMatch2(pdds, rgb);
    ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;
    return pdds->SetColorKey(DDCKEY_SRCBLT, &ddck);
}


DDUTIL2.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddutil.cpp
 *  Content:    Routines for loading bitmap and palettes from resources
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif/* __cplusplus */

extern IDirectDrawPalette * DDLoadPalette2(IDirectDraw2 *pdd, LPCSTR szBitmap);
extern IDirectDrawSurface3 * DDLoadBitmap2(IDirectDraw2 *pdd, LPCSTR szBitmap, int dx, int dy);
extern HRESULT              DDReLoadBitmap2(IDirectDrawSurface3 *pdds, LPCSTR szBitmap);
extern HRESULT              DDCopyBitmap2(IDirectDrawSurface3 *pdds, HBITMAP hbm, int x, int y, int dx, int dy);
extern DWORD                DDColorMatch2(IDirectDrawSurface3 *pdds, COLORREF rgb);
extern HRESULT              DDSetColorKey2(IDirectDrawSurface3 *pdds, COLORREF rgb);

#ifdef __cplusplus
}
#endif/* __cplusplus */


DSUTIL.C

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>

typedef struct
{
    BYTE *pbWaveData;               // pointer into wave resource (for restore)
    DWORD cbWaveSize;               // size of wave data (for restore)
    int iAlloc;                     // number of buffers.
    int iCurrent;                   // current buffer
    IDirectSoundBuffer* Buffers[1]; // list of buffers

} SNDOBJ, *HSNDOBJ;

#define _HSNDOBJ_DEFINED
#include "dsutil.h"

static const char c_szWAV[] = "WAV";

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName)
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};
    BYTE *pbWaveData;

    if (DSGetWaveResource(NULL, lpName, &dsBD.lpwfxFormat, &pbWaveData, &dsBD.dwBufferBytes))
    {
        dsBD.dwSize = sizeof(dsBD);
        dsBD.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2;

        if (SUCCEEDED(IDirectSound_CreateSoundBuffer(pDS, &dsBD, &pDSB, NULL)))
        {
            if (!DSFillSoundBuffer(pDSB, pbWaveData, dsBD.dwBufferBytes))
            {
                IDirectSoundBuffer_Release(pDSB);
                pDSB = NULL;
            }
        }
        else
        {
            pDSB = NULL;
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName)
{
    BOOL result=FALSE;
    BYTE *pbWaveData;
    DWORD cbWaveSize;

    if (DSGetWaveResource(NULL, lpName, NULL, &pbWaveData, &cbWaveSize))
    {
        if (SUCCEEDED(IDirectSoundBuffer_Restore(pDSB)) &&
            DSFillSoundBuffer(pDSB, pbWaveData, cbWaveSize))
{
    result = TRUE;
        }
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pcbWaveSize)
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    void *pvRes;

    if (((hResInfo = FindResource(hModule, lpName, c_szWAV)) != NULL) &&
        ((hResData = LoadResource(hModule, hResInfo)) != NULL) &&
        ((pvRes = LockResource(hResData)) != NULL) &&
        DSParseWaveResource(pvRes, ppWaveHeader, ppbWaveData, pcbWaveSize))
    {
        return TRUE;
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// SndObj fns
///////////////////////////////////////////////////////////////////////////////

SNDOBJ *SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent)
{
    SNDOBJ *pSO = NULL;
    LPWAVEFORMATEX pWaveHeader;
    BYTE *pbData;
    UINT cbData;

    if (DSGetWaveResource(NULL, lpName, &pWaveHeader, &pbData, &cbData))
    {
        if (iConcurrent < 1)
            iConcurrent = 1;

        if ((pSO = (SNDOBJ *)LocalAlloc(LPTR, sizeof(SNDOBJ) +
    (iConcurrent-1) * sizeof(IDirectSoundBuffer *))) != NULL)
        {
            int i;

            pSO->iAlloc = iConcurrent;
            pSO->pbWaveData = pbData;
            pSO->cbWaveSize = cbData;
            pSO->Buffers[0] = DSLoadSoundBuffer(pDS, lpName);

    for (i=1; i<pSO->iAlloc; i++)
    {
if (FAILED(IDirectSound_DuplicateSoundBuffer(pDS,
                    pSO->Buffers[0], &pSO->Buffers[i])))
                {
    pSO->Buffers[i] = DSLoadSoundBuffer(pDS, lpName);
    if (!pSO->Buffers[i]) {
SndObjDestroy(pSO);
pSO = NULL;
break;
    }
}
            }
        }
    }

    return pSO;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void SndObjDestroy(SNDOBJ *pSO)
{
    if (pSO)
    {
        int i;

        for (i=0; i<pSO->iAlloc; i++)
        {
            if (pSO->Buffers[i])
            {
                IDirectSoundBuffer_Release(pSO->Buffers[i]);
                pSO->Buffers[i] = NULL;
            }
        }
        LocalFree((HANDLE)pSO);
    }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

IDirectSoundBuffer *SndObjGetFreeBuffer(SNDOBJ *pSO)
{
    IDirectSoundBuffer *pDSB;

    if (pSO == NULL)
        return NULL;

    if (pDSB = pSO->Buffers[pSO->iCurrent])
    {
        HRESULT hres;
        DWORD dwStatus;

        hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

        if (FAILED(hres))
            dwStatus = 0;

        if ((dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
        {
            if (pSO->iAlloc > 1)
            {
                if (++pSO->iCurrent >= pSO->iAlloc)
                    pSO->iCurrent = 0;

                pDSB = pSO->Buffers[pSO->iCurrent];
                hres = IDirectSoundBuffer_GetStatus(pDSB, &dwStatus);

                if (SUCCEEDED(hres) && (dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
                {
                    IDirectSoundBuffer_Stop(pDSB);
                    IDirectSoundBuffer_SetCurrentPosition(pDSB, 0);
                }
            }
            else
            {
                pDSB = NULL;
            }
        }

        if (pDSB && (dwStatus & DSBSTATUS_BUFFERLOST))
        {
            if (FAILED(IDirectSoundBuffer_Restore(pDSB)) ||
                !DSFillSoundBuffer(pDSB, pSO->pbWaveData, pSO->cbWaveSize))
            {
                pDSB = NULL;
            }
        }
    }

    return pDSB;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjPlay(SNDOBJ *pSO, DWORD dwPlayFlags)
{
    BOOL result = FALSE;

    if (pSO == NULL)
        return FALSE;

    if ((!(dwPlayFlags & DSBPLAY_LOOPING) || (pSO->iAlloc == 1)))
    {
IDirectSoundBuffer *pDSB = SndObjGetFreeBuffer(pSO);
if (pDSB != NULL) {
    result = SUCCEEDED(IDirectSoundBuffer_Play(pDSB, 0, 0, dwPlayFlags));
}
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL SndObjStop(SNDOBJ *pSO)
{
    int i;

    if (pSO == NULL)
        return FALSE;

    for (i=0; i<pSO->iAlloc; i++)
    {
        IDirectSoundBuffer_Stop(pSO->Buffers[i]);
        IDirectSoundBuffer_SetCurrentPosition(pSO->Buffers[i], 0);
    }

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD cbWaveSize)
{
    if (pDSB && pbWaveData && cbWaveSize)
    {
        LPVOID pMem1, pMem2;
        DWORD dwSize1, dwSize2;

        if (SUCCEEDED(IDirectSoundBuffer_Lock(pDSB, 0, cbWaveSize,
            &pMem1, &dwSize1, &pMem2, &dwSize2, 0)))
        {
            CopyMemory(pMem1, pbWaveData, dwSize1);

            if ( 0 != dwSize2 )
                CopyMemory(pMem2, pbWaveData+dwSize1, dwSize2);

            IDirectSoundBuffer_Unlock(pDSB, pMem1, dwSize1, pMem2, dwSize2);
            return TRUE;
        }
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData,DWORD *pcbWaveSize)
{
    DWORD *pdw;
    DWORD *pdwEnd;
    DWORD dwRiff;
    DWORD dwType;
    DWORD dwLength;

    if (ppWaveHeader)
        *ppWaveHeader = NULL;

    if (ppbWaveData)
        *ppbWaveData = NULL;

    if (pcbWaveSize)
        *pcbWaveSize = 0;

    pdw = (DWORD *)pvRes;
    dwRiff = *pdw++;
    dwLength = *pdw++;
    dwType = *pdw++;

    if (dwRiff != mmioFOURCC('R', 'I', 'F', 'F'))
        goto exit;      // not even RIFF

    if (dwType != mmioFOURCC('W', 'A', 'V', 'E'))
        goto exit;      // not a WAV

    pdwEnd = (DWORD *)((BYTE *)pdw + dwLength-4);

    while (pdw < pdwEnd)
    {
        dwType = *pdw++;
        dwLength = *pdw++;

        switch (dwType)
        {
        case mmioFOURCC('f', 'm', 't', ' '):
            if (ppWaveHeader && !*ppWaveHeader)
            {
                if (dwLength < sizeof(WAVEFORMAT))
                    goto exit;      // not a WAV

                *ppWaveHeader = (WAVEFORMATEX *)pdw;

                if ((!ppbWaveData || *ppbWaveData) &&
                    (!pcbWaveSize || *pcbWaveSize))
                {
                    return TRUE;
                }
            }
            break;

        case mmioFOURCC('d', 'a', 't', 'a'):
            if ((ppbWaveData && !*ppbWaveData) ||
                (pcbWaveSize && !*pcbWaveSize))
            {
                if (ppbWaveData)
                    *ppbWaveData = (LPBYTE)pdw;

                if (pcbWaveSize)
                    *pcbWaveSize = dwLength;

                if (!ppWaveHeader || *ppWaveHeader)
                    return TRUE;
            }
            break;
        }

        pdw = (DWORD *)((BYTE *)pdw + ((dwLength+1)&~1));
    }

exit:
    return FALSE;
}


DSUTIL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       dsutil.cpp
 *  Content:    Routines for dealing with sounds from resources
 *
 *
 ***************************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

///////////////////////////////////////////////////////////////////////////////
//
// DSLoadSoundBuffer    Loads an IDirectSoundBuffer from a Win32 resource in
//                      the current application.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the buffer.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns an IDirectSoundBuffer containing the wave data or NULL on error.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//      IDirectSoundBuffer *pDSB = DSLoadSoundBuffer(pDS, "Turtle");
//
//      if (pDSB)
//      {
//          IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//          /* ... */
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *DSLoadSoundBuffer(IDirectSound *pDS, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSReloadSoundBuffer  Reloads an IDirectSoundBuffer from a Win32 resource in
//                      the current application. normally used to handle
//                      a DSERR_BUFFERLOST error.
// Params:
//  pDSB        -- Pointer to an IDirectSoundBuffer to be reloaded.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
// Returns a BOOL indicating whether the buffer was successfully reloaded.
//
// example:
//  in the application's resource script (.RC file)
//      Turtle WAV turtle.wav
//
//  some code in the application:
//  TryAgain:
//      HRESULT hres = IDirectSoundBuffer_Play(pDSB, 0, 0, DSBPLAY_TOEND);
//
//      if (FAILED(hres))
//      {
//          if ((hres == DSERR_BUFFERLOST) &&
//              DSReloadSoundBuffer(pDSB, "Turtle"))
//          {
//              goto TryAgain;
//          }
//          /* deal with other errors... */
//      }
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSReloadSoundBuffer(IDirectSoundBuffer *pDSB, LPCTSTR lpName);

///////////////////////////////////////////////////////////////////////////////
//
// DSGetWaveResource    Finds a WAV resource in a Win32 module.
//
// Params:
//  hModule     -- Win32 module handle of module containing WAV resource.
//                 Pass NULL to indicate current application.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  ppWaveHeader-- Optional pointer to WAVEFORMATEX * to receive a pointer to
//                 the WAVEFORMATEX header in the specified WAV resource.
//                 Pass NULL if not required.
//
//  ppbWaveData -- Optional pointer to BYTE * to receive a pointer to the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
//  pdwWaveSize -- Optional pointer to DWORD to receive the size of the
//                 waveform data in the specified WAV resource.  Pass NULL if
//                 not required.
//
// Returns a BOOL indicating whether a valid WAV resource was found.
//
///////////////////////////////////////////////////////////////////////////////
BOOL DSGetWaveResource(HMODULE hModule, LPCTSTR lpName,
    WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

///////////////////////////////////////////////////////////////////////////////
//
// HSNDOBJ             Handle to a SNDOBJ object.
//
//  SNDOBJs are implemented in dsutil as an example layer built on top
//      of DirectSound.
//
//      A SNDOBJ is generally used to manage individual
//      sounds which need to be played multiple times concurrently.  A
//      SNDOBJ represents a queue of IDirectSoundBuffer objects which
//      all refer to the same buffer memory.
//
//      A SNDOBJ also automatically reloads the sound resource when
//      DirectSound returns a DSERR_BUFFERLOST
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _HSNDOBJ_DEFINED
DECLARE_HANDLE32(HSNDOBJ);
#endif

///////////////////////////////////////////////////////////////////////////////
//
// SndObjCreate     Loads a SNDOBJ from a Win32 resource in
//    the current application.
//
// Params:
//  pDS         -- Pointer to an IDirectSound that will be used to create
//                 the SNDOBJ.
//
//  lpName      -- Name of WAV resource to load the data from.  Can be a
//                 resource id specified using the MAKEINTRESOURCE macro.
//
//  iConcurrent -- Integer representing the number of concurrent playbacks of
//                 to plan for.  Attempts to play more than this number will
//                 succeed but will restart the least recently played buffer
//                 even if it is not finished playing yet.
//
// Returns an HSNDOBJ or NULL on error.
//
// NOTES:
//      SNDOBJs automatically restore and reload themselves as required.
//
///////////////////////////////////////////////////////////////////////////////
HSNDOBJ SndObjCreate(IDirectSound *pDS, LPCTSTR lpName, int iConcurrent);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjDestroy  Frees a SNDOBJ and releases all of its buffers.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to free.
//
///////////////////////////////////////////////////////////////////////////////
void SndObjDestroy(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjPlayPlays a buffer in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
//  dwPlayFlags -- Flags to pass to IDirectSoundBuffer::Play.  It is not
//                 legal to play an SndObj which has more than one buffer
//                 with the DSBPLAY_LOOPING flag.  Pass 0 to stop playback.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjPlay(HSNDOBJ hSO, DWORD dwPlayFlags);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjStopStops one or more buffers in a SNDOBJ.
//
// Params:
//  hSO         -- Handle to a SNDOBJ to play a buffer from.
//
///////////////////////////////////////////////////////////////////////////////
BOOL SndObjStop(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// SndObjGetFreeBuffer    returns one of the cloned buffers that is
//    not currently playing
//
// Params:
//  hSO -- Handle to a SNDOBJ
//
// NOTES:
//  This function is provided so that callers can set things like pan etc
//  before playing the buffer.
//
// EXAMPLE:
//  ...
//
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer *SndObjGetFreeBuffer(HSNDOBJ hSO);

///////////////////////////////////////////////////////////////////////////////
//
// helper routines
//
///////////////////////////////////////////////////////////////////////////////

BOOL DSFillSoundBuffer(IDirectSoundBuffer *pDSB, BYTE *pbWaveData, DWORD dwWaveSize);
BOOL DSParseWaveResource(void *pvRes, WAVEFORMATEX **ppWaveHeader, BYTE **ppbWaveData, DWORD *pdwWaveSize);

#ifdef __cplusplus
}
#endif


GETDXVER.CPP

/**************************************************************************

    GetDXVer.cpp

    Demonstrates how applications can detect what version of DirectX
    is installed.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <dinput.h>

/****************************************************************************
 *
 *      GetDXVersion
 *
 *  This function returns two arguments:
 *dwDXVersion:
 *    0    No DirectX installed
 *    0x100   DirectX version 1 installed
 *    0x200   DirectX 2 installed
 *    0x300   DirectX 3 installed
 *    0x500   At least DirectX 5 installed.
 *    0x501   At least DirectX 5a installed.
 *dwDXPlatform:
 *    0                            Unknown (This is a failure case. Should never happen)
 *    VER_PLATFORM_WIN32_WINDOWS    Windows 9X platform
 *    VER_PLATFORM_WIN32_NT    Windows NT platform
 *
 * Please note that this code is intended as a general guideline. Your app will
 * probably be able to simply query for functionality (via COM's QueryInterface)
 * for one or two components.
 * Please also note:
 *      "if (dxVer != 0x500) return FALSE;" is BAD.
 *      "if (dxVer < 0x500) return FALSE;" is MUCH BETTER.
 * to ensure your app will run on future releases of DirectX.
 *
 ****************************************************************************/

typedef HRESULT (WINAPI *DIRECTDRAWCREATE)(GUID *, LPDIRECTDRAW *, IUnknown *);
typedef HRESULT (WINAPI *DIRECTINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUT *, IUnknown *);

void GetDXVersion(LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform)
{
    HRESULT    hr;
    HINSTANCE    DDHinst = 0;
    HINSTANCE    DIHinst = 0;
    LPDIRECTDRAW    pDDraw = 0;
    LPDIRECTDRAW2    pDDraw2 = 0;
    DIRECTDRAWCREATE    DirectDrawCreate = 0;
    DIRECTINPUTCREATE    DirectInputCreate = 0;
    OSVERSIONINFO    osVer;
    LPDIRECTDRAWSURFACE    pSurf = 0;
    LPDIRECTDRAWSURFACE3    pSurf3 = 0;

    /*
     * First get the windows platform
     */
    osVer.dwOSVersionInfoSize = sizeof(osVer);
    if (!GetVersionEx(&osVer))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
return;
    }

    if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
*pdwDXPlatform = VER_PLATFORM_WIN32_NT;
/*
 * NT is easy... NT 4.0 is DX2, 4.0 SP3 is DX3, 5.0 is DX5
 * and no DX on earlier versions.
 */
if (osVer.dwMajorVersion < 4)
{
    *pdwDXPlatform = 0;//No DX on NT3.51 or earlier
    return;
}
if (osVer.dwMajorVersion == 4)
{
    /*
     * NT4 up to SP2 is DX2, and SP3 onwards is DX3, so we are at least DX2
     */
    *pdwDXVersion = 0x200;

            /*
             * We're not supposed to be able to tell which SP we're on, so check for dinput
             */
            DIHinst = LoadLibrary("DINPUT.DLL");
            if (DIHinst == 0)
            {
                /*
                 * No DInput... must be DX2 on NT 4 pre-SP3
                 */
                OutputDebugString("Couldn't LoadLibrary DInput\r\n");
        return;
            }

            DirectInputCreate = (DIRECTINPUTCREATE)
                                    GetProcAddress(DIHinst, "DirectInputCreateA");
            FreeLibrary(DIHinst);

            if (DirectInputCreate == 0)
            {
                /*
                 * No DInput... must be pre-SP3 DX2
                 */
                OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
        return;
            }

    /*
     * It must be NT4, DX2
     */
    *pdwDXVersion = 0x300; //DX3 on NT4 SP3 or higher
    return;
}
/*
 * Else it's NT5 or higher, and it's DX5a or higher:
 */
*pdwDXVersion = 0x501; //DX5a on NT5
return;
    }

    /*
     * Not NT... must be Win9x
     */
    *pdwDXPlatform = VER_PLATFORM_WIN32_WINDOWS;

    /*
     * If we are on Memphis or higher, then we are at least DX5a
     */
    if ( (osVer.dwBuildNumber & 0xffff) > 1353) //Check for higher than developer release
    {
*pdwDXVersion = 0x501; //DX5a on Memphis or higher
return;
    }

    /*
     * Now we know we are in Windows 9x (or maybe 3.1), so anything's possible.
     * First see if DDRAW.DLL even exists.
     */
    DDHinst = LoadLibrary("DDRAW.DLL");
    if (DDHinst == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
return;
    }

    /*
     *  See if we can create the DirectDraw object.
     */
    DirectDrawCreate = (DIRECTDRAWCREATE)
                            GetProcAddress(DDHinst, "DirectDrawCreate");
    if (DirectDrawCreate == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't LoadLibrary DDraw\r\n");
return;
    }

    hr = DirectDrawCreate(NULL, &pDDraw, NULL);
    if (FAILED(hr))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't create DDraw\r\n");
return;
    }

    /*
     *  So DirectDraw exists.  We are at least DX1.
     */
    *pdwDXVersion = 0x100;

    /*
     *  Let's see if IID_IDirectDraw2 exists.
     */
    hr = pDDraw->QueryInterface(IID_IDirectDraw2, (LPVOID *)&pDDraw2);
    if (FAILED(hr))
    {
/*
 * No IDirectDraw2 exists... must be DX1
 */
pDDraw->Release();
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't QI DDraw2\r\n");
return;
    }
    /*
     * IDirectDraw2 exists. We must be at least DX2
     */
    pDDraw2->Release();
    *pdwDXVersion = 0x200;

    /*
     *  See if we can create the DirectInput object.
     */
    DIHinst = LoadLibrary("DINPUT.DLL");
    if (DIHinst == 0)
    {
        /*
         * No DInput... must be DX2
         */
        OutputDebugString("Couldn't LoadLibrary DInput\r\n");
pDDraw->Release();
FreeLibrary(DDHinst);
return;
    }

    DirectInputCreate = (DIRECTINPUTCREATE)
                            GetProcAddress(DIHinst, "DirectInputCreateA");
    FreeLibrary(DIHinst);

    if (DirectInputCreate == 0)
    {
        /*
         * No DInput... must be DX2
         */
FreeLibrary(DDHinst);
pDDraw->Release();
        OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
return;
    }

    /*
     * DirectInputCreate exists. That's enough to tell us that we are at least DX3
     */
    *pdwDXVersion = 0x300;

    /*
     * Checks for 3a vs 3b?
     */

    /*
     * We can tell if DX5 is present by checking for the existence of IDirectDrawSurface3.
     * First we need a surface to QI off of.
     */
    DDSURFACEDESC desc;

    ZeroMemory(&desc, sizeof(desc));
    desc.dwSize = sizeof(desc);
    desc.dwFlags = DDSD_CAPS;
    desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    hr = pDDraw->SetCooperativeLevel(NULL,DDSCL_NORMAL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't Set coop level\r\n");
return;
    }

    hr = pDDraw->CreateSurface(&desc, &pSurf, NULL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't CreateSurface\r\n");
return;
    }

    /*
     * Try for the IDirectDrawSurface3 interface. If it works, we're on DX5 at least
     */
    if ( FAILED(pSurf->QueryInterface(IID_IDirectDrawSurface3,(LPVOID*)&pSurf3)) )
    {
        pDDraw->Release();
        FreeLibrary(DDHinst);

        return;
    }

    /*
     * QI for IDirectDrawSurface3 succeeded. We must be at least DX5
     */
    *pdwDXVersion = 0x500;

    pSurf->Release();
    pDDraw->Release();
    FreeLibrary(DDHinst);

    return;
}


LBPRINTF.C

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       lbprintf.c
 *  Content:    Routines to display text in a listbox
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "lbprintf.h"

static HWNDhWndListBox;
static DWORDdwPos;

/*
 * LBCreate
 *
 * Create a list box on a given parent window
 */
void LBCreate( HWND hWnd, DWORD pos )
{
    hWndListBox = CreateWindow(
"listbox",// class
NULL,// caption
WS_BORDER | WS_CHILDWINDOW | WS_VSCROLL | LBS_NOINTEGRALHEIGHT,// style
0,// x pos
0,// y pos
0,// width
0,// height
hWnd,// parent window
NULL,// menu
GetWindowInstance( hWnd ), // instance
NULL// parms
);

    ShowWindow( hWndListBox, SW_NORMAL );
    UpdateWindow( hWndListBox );

    dwPos = pos;
    if( dwPos > 100 )
    {
dwPos = 100;
    }

} /* LBCreate */

/*
 * LBSize
 *
 * New size for the listbox.   Should be called whenever the parent window
 * is resized
 */
void LBSize( DWORD width, DWORD height )
{
    DWORDy;

    y = (height*(100-dwPos))/100;
    MoveWindow( hWndListBox, 0, y, width-1, height-y-1, TRUE );

} /* LBSize */

/*
 * LBClear
 *
 * Clear the listbox
 */
void LBClear( void )
{
    SendMessage( hWndListBox, LB_RESETCONTENT, 0, 0L );

} /* LBClear */

/*
 * LBPrintf
 */
void __cdecl LBPrintf( LPSTR fmt, ... )
{
    charbuff[512];
    UINTsel;

    va_list marker;
    va_start (marker,fmt);

    wvsprintf( buff, fmt, marker );
    SendMessage( hWndListBox, LB_ADDSTRING, 0, (LONG) (LPSTR) buff );
    sel = (UINT) SendMessage( hWndListBox, LB_GETCOUNT, 0, 0L );
    if( sel != LB_ERR )
    {
SendMessage( hWndListBox, LB_SETCURSEL, sel-1, 0L );
    }

} /* LBPrintf */

typedef struct
{
    HRESULTrval;
    LPSTRstr;
} ERRLIST;

static ERRLIST elErrors[] =
{
    { DD_OK, "DD_OK" },
    { DDERR_ALREADYINITIALIZED, "DDERR_ALREADYINITIALIZED" },
    { DDERR_CANNOTATTACHSURFACE, "DDERR_CANNOTATTACHSURFACE" },
    { DDERR_CANNOTDETACHSURFACE, "DDERR_CANNOTDETACHSURFACE" },
    { DDERR_CURRENTLYNOTAVAIL, "DDERR_CURRENTLYNOTAVAIL" },
    { DDERR_EXCEPTION, "DDERR_EXCEPTION" },
    { DDERR_GENERIC, "DDERR_GENERIC" },
    { DDERR_HEIGHTALIGN, "DDERR_HEIGHTALIGN" },
    { DDERR_INCOMPATIBLEPRIMARY, "DDERR_INCOMPATIBLEPRIMARY" },
    { DDERR_INVALIDCAPS, "DDERR_INVALIDCAPS" },
    { DDERR_INVALIDCLIPLIST, "DDERR_INVALIDCLIPLIST" },
    { DDERR_INVALIDMODE, "DDERR_INVALIDMODE" },
    { DDERR_INVALIDOBJECT, "DDERR_INVALIDOBJECT" },
    { DDERR_INVALIDPARAMS, "DDERR_INVALIDPARAMS" },
    { DDERR_INVALIDPIXELFORMAT, "DDERR_INVALIDPIXELFORMAT" },
    { DDERR_INVALIDRECT, "DDERR_INVALIDRECT" },
    { DDERR_LOCKEDSURFACES, "DDERR_LOCKEDSURFACES" },
    { DDERR_NO3D, "DDERR_NO3D" },
    { DDERR_NOALPHAHW, "DDERR_NOALPHAHW" },
    { DDERR_NOCLIPLIST, "DDERR_NOCLIPLIST" },
    { DDERR_NOCOLORCONVHW, "DDERR_NOCOLORCONVHW" },
    { DDERR_NOCOOPERATIVELEVELSET, "DDERR_NOCOOPERATIVELEVELSET" },
    { DDERR_NOCOLORKEY, "DDERR_NOCOLORKEY" },
    { DDERR_NOCOLORKEYHW, "DDERR_NOCOLORKEYHW" },
    { DDERR_NOEXCLUSIVEMODE, "DDERR_NOEXCLUSIVEMODE" },
    { DDERR_NOFLIPHW, "DDERR_NOFLIPHW" },
    { DDERR_NOGDI, "DDERR_NOGDI" },
    { DDERR_NOMIRRORHW, "DDERR_NOMIRRORHW" },
    { DDERR_NOTFOUND, "DDERR_NOTFOUND" },
    { DDERR_NOOVERLAYHW, "DDERR_NOOVERLAYHW" },
    { DDERR_NORASTEROPHW, "DDERR_NORASTEROPHW" },
    { DDERR_NOROTATIONHW, "DDERR_NOROTATIONHW" },
    { DDERR_NOSTRETCHHW, "DDERR_NOSTRETCHHW" },
    { DDERR_NOT4BITCOLOR, "DDERR_NOT4BITCOLOR" },
    { DDERR_NOT4BITCOLORINDEX, "DDERR_NOT4BITCOLORINDEX" },
    { DDERR_NOT8BITCOLOR, "DDERR_NOT8BITCOLOR" },
    { DDERR_NOTEXTUREHW, "DDERR_NOTEXTUREHW" },
    { DDERR_NOVSYNCHW, "DDERR_NOVSYNCHW" },
    { DDERR_NOZBUFFERHW, "DDERR_NOZBUFFERHW" },
    { DDERR_NOZOVERLAYHW, "DDERR_NOZOVERLAYHW" },
    { DDERR_OUTOFCAPS, "DDERR_OUTOFCAPS" },
    { DDERR_OUTOFMEMORY, "DDERR_OUTOFMEMORY" },
    { DDERR_OUTOFVIDEOMEMORY, "DDERR_OUTOFVIDEOMEMORY" },
    { DDERR_OVERLAYCANTCLIP, "DDERR_OVERLAYCANTCLIP" },
    { DDERR_OVERLAYCOLORKEYONLYONEACTIVE, "DDERR_OVERLAYCOLORKEYONLYONEACTIVE" },
    { DDERR_PALETTEBUSY, "DDERR_PALETTEBUSY" },
    { DDERR_COLORKEYNOTSET, "DDERR_COLORKEYNOTSET" },
    { DDERR_SURFACEALREADYATTACHED, "DDERR_SURFACEALREADYATTACHED" },
    { DDERR_SURFACEALREADYDEPENDENT, "DDERR_SURFACEALREADYDEPENDENT" },
    { DDERR_SURFACEBUSY, "DDERR_SURFACEBUSY" },
    { DDERR_SURFACEISOBSCURED, "DDERR_SURFACEISOBSCURED" },
    { DDERR_SURFACELOST, "DDERR_SURFACELOST" },
    { DDERR_SURFACENOTATTACHED, "DDERR_SURFACENOTATTACHED" },
    { DDERR_TOOBIGHEIGHT, "DDERR_TOOBIGHEIGHT" },
    { DDERR_TOOBIGSIZE, "DDERR_TOOBIGSIZE" },
    { DDERR_TOOBIGWIDTH, "DDERR_TOOBIGWIDTH" },
    { DDERR_UNSUPPORTED, "DDERR_UNSUPPORTED" },
    { DDERR_UNSUPPORTEDFORMAT, "DDERR_UNSUPPORTEDFORMAT" },
    { DDERR_UNSUPPORTEDMASK, "DDERR_UNSUPPORTEDMASK" },
    { DDERR_VERTICALBLANKINPROGRESS, "DDERR_VERTICALBLANKINPROGRESS" },
    { DDERR_WASSTILLDRAWING, "DDERR_WASSTILLDRAWING" },
    { DDERR_XALIGN, "DDERR_XALIGN" },
    { DDERR_INVALIDDIRECTDRAWGUID, "DDERR_INVALIDDIRECTDRAWGUID" },
    { DDERR_DIRECTDRAWALREADYCREATED, "DDERR_DIRECTDRAWALREADYCREATED" },
    { DDERR_NODIRECTDRAWHW, "DDERR_NODIRECTDRAWHW" },
    { DDERR_PRIMARYSURFACEALREADYEXISTS, "DDERR_PRIMARYSURFACEALREADYEXISTS" },
    { DDERR_NOEMULATION, "DDERR_NOEMULATION" },
    { DDERR_REGIONTOOSMALL, "DDERR_REGIONTOOSMALL" },
    { DDERR_CLIPPERISUSINGHWND, "DDERR_CLIPPERISUSINGHWND" },
    { DDERR_NOCLIPPERATTACHED, "DDERR_NOCLIPPERATTACHED" },
    { DDERR_NOHWND, "DDERR_NOHWND" },
    { DDERR_HWNDSUBCLASSED, "DDERR_HWNDSUBCLASSED" },
    { DDERR_HWNDALREADYSET, "DDERR_HWNDALREADYSET" },
    { DDERR_NOPALETTEATTACHED, "DDERR_NOPALETTEATTACHED" },
    { DDERR_NOPALETTEHW, "DDERR_NOPALETTEHW" },
    { DDERR_BLTFASTCANTCLIP, "DDERR_BLTFASTCANTCLIP" },
    { DDERR_NOBLTHW, "DDERR_NOBLTHW" },
    { DDERR_NODDROPSHW, "DDERR_NODDROPSHW" },
    { DDERR_OVERLAYNOTVISIBLE, "DDERR_OVERLAYNOTVISIBLE" },
    { DDERR_NOOVERLAYDEST, "DDERR_NOOVERLAYDEST" },
    { DDERR_INVALIDPOSITION, "DDERR_INVALIDPOSITION" },
    { DDERR_NOTAOVERLAYSURFACE, "DDERR_NOTAOVERLAYSURFACE" },
    { DDERR_EXCLUSIVEMODEALREADYSET, "DDERR_EXCLUSIVEMODEALREADYSET" },
    { DDERR_NOTFLIPPABLE, "DDERR_NOTFLIPPABLE" },
    { DDERR_CANTDUPLICATE, "DDERR_CANTDUPLICATE" },
    { DDERR_NOTLOCKED, "DDERR_NOTLOCKED" },
    { DDERR_CANTCREATEDC, "DDERR_CANTCREATEDC" },
    { DDERR_NODC, "DDERR_NODC" },
    { DDERR_WRONGMODE, "DDERR_WRONGMODE" },
    { DDERR_IMPLICITLYCREATED, "DDERR_IMPLICITLYCREATED" },
};

/*
 * getErrorString
 */
static LPSTR getErrorString( HRESULT ddrval )
{
    inti;

    for( i=0;i<sizeof( elErrors )/sizeof( elErrors[0] );i++ )
    {
if( ddrval == elErrors[i].rval )
{
    return elErrors[i].str;
}
    }
    return "Unknown Error Code";

} /* getErrorString */

/*
 * LBPrintfDDRC
 *
 * Display a DirectDraw error code in human readable form
 */
void __cdecl LBPrintfDDRC( HRESULT ddrval, LPSTR fmt, ... )
{
    charbuff[512];

    va_list marker;
    va_start (marker,fmt);

    wvsprintf( buff, fmt, marker );
    LBPrintf( "%s, rc=%ld (0x%08lx:%s)", buff, LOWORD( ddrval ),
ddrval, getErrorString( ddrval ) );

} /* LBPrintfDDRC */


LBPRINTF.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:lbprintf.h
 *  Content:list box printf header file
 *
 ***************************************************************************/
#ifndef __LBPRINTF_INCLUDED__

#define __LBPRINTF_INCLUDED__
#ifdef __cplusplus
extern "C" {
#endif
extern void LBCreate( HWND hWnd, DWORD pos );
extern void LBSize( DWORD dwWidth, DWORD dwHeight );
extern void LBClear( void );
extern void __cdecl LBPrintf( LPSTR fmt, ... );
extern void __cdecl LBPrintfDDRC( HRESULT rc, LPSTR fmt, ... );

#ifdef __cplusplus
}
#endif

#endif


MISC.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: misc.c
 *
 *  Miscellaneous functions not involving DD and D3D.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"
#include <stdarg.h>

/***************************************************************************/
/*                          Setting Defaults                               */
/***************************************************************************/
/*
 * D3DAppISetDefaults
 * Set all the global variables to their default values.  Do not reset the
 * image files.
 */
void
D3DAppISetDefaults(void)
{
    int n;
    char backup[D3DAPP_MAXTEXTURES][50];

    n = d3dappi.NumTextures;
    memcpy(&backup[0][0], &d3dappi.ImageFile[0][0], 50 * D3DAPP_MAXTEXTURES);
    ZEROMEM(d3dappi);
    memcpy(&d3dappi.ImageFile[0][0], &backup[0][0], 50 * D3DAPP_MAXTEXTURES);
    d3dappi.NumTextures = n;
    d3dappi.bAppActive = TRUE;
    ZEROMEM(d3dapprs);
    d3dapprs.bZBufferOn = TRUE;
    d3dapprs.bPerspCorrect = FALSE;
    d3dapprs.ShadeMode = D3DSHADE_GOURAUD;
    d3dapprs.TextureFilter = D3DFILTER_NEAREST;
    d3dapprs.TextureBlend = D3DTBLEND_MODULATE;
    d3dapprs.FillMode = D3DFILL_SOLID;
    d3dapprs.bDithering = FALSE;
    d3dapprs.bSpecular = TRUE;
    d3dapprs.bAntialiasing = FALSE;
    d3dapprs.bFogEnabled = FALSE;
    d3dapprs.FogColor = RGB_MAKE(0, 0, 0);
    d3dapprs.FogMode = D3DFOG_LINEAR;
    d3dapprs.FogStart = D3DVAL(6.0);
    d3dapprs.FogEnd = D3DVAL(11.0);

    lpClipper = NULL;
    lpPalette = NULL;
    bPrimaryPalettized = FALSE;
    bPaletteActivate = FALSE;
    bIgnoreWM_SIZE = FALSE;
    ZEROMEM(ppe);
    ZEROMEM(Originalppe);
    LastError = DD_OK;
    ZEROMEM(LastErrorString);
    D3DDeviceDestroyCallback = NULL;
    D3DDeviceDestroyCallbackContext = NULL;
    D3DDeviceCreateCallback = NULL;
    D3DDeviceCreateCallbackContext = NULL;
}

/***************************************************************************/
/*                Calling Device Create And Destroy Callbacks              */
/***************************************************************************/
BOOL
D3DAppICallDeviceDestroyCallback(void)
{
    if (D3DDeviceDestroyCallback) {
if (CallbackRefCount) {
    --CallbackRefCount;
    return (D3DDeviceDestroyCallback)(D3DDeviceDestroyCallbackContext);
}
    }
    return TRUE;
}

BOOL
D3DAppICallDeviceCreateCallback(int w, int h)
{
    if (D3DDeviceCreateCallback) {
++CallbackRefCount;
return (D3DDeviceCreateCallback)(w, h, &d3dappi.lpD3DViewport,
 D3DDeviceCreateCallbackContext);
    }
    return TRUE;
}

/***************************************************************************/
/*            Choosing and verifying the driver and display mode           */
/***************************************************************************/
/*
 * D3DAppIPickDriver
 * Choose a driver from the list of available drivers which can render to one
 * of the given depths.  Hardware is prefered.  Mono-lighting drivers are
 * prefered over RGB.
 */
BOOL
D3DAppIPickDriver(int* driver, DWORD depths)
{
    int i, j;
    j = 0;
    for (i = 0; i < d3dappi.NumDrivers; i++)
if (d3dappi.Driver[i].Desc.dwDeviceRenderBitDepth & depths)
    break;
    if (i >= d3dappi.NumDrivers) {
*driver = D3DAPP_BOGUS;
return TRUE;
    }
    j = i;
    for (i = 0; i < d3dappi.NumDrivers; i++) {
if (d3dappi.Driver[i].Desc.dwDeviceRenderBitDepth & depths) {
    if (d3dappi.Driver[i].bIsHardware &&
      !d3dappi.Driver[j].bIsHardware)
          j = i;
    else if (d3dappi.Driver[i].bIsHardware ==
     d3dappi.Driver[j].bIsHardware) {
if (d3dappi.Driver[i].Desc.dcmColorModel & D3DCOLOR_MONO &&
    !(d3dappi.Driver[j].Desc.dcmColorModel & D3DCOLOR_MONO))
j = i;
    }
}
    }
    if (j >= d3dappi.NumDrivers)
*driver = D3DAPP_BOGUS;
    else
*driver = j;
    return TRUE;
}

/*
 * D3DAppIFilterDisplayModes
 * Set the bThisDriverCanDo flag for each display mode if the given driver
 * can render in that depth.  Also checks to make sure there is enough
 * total video memory for front/back/z-buffer in video memory if it's a
 * hardware device.
 */
BOOL
D3DAppIFilterDisplayModes(int driver)
{
    int i;
    DWORD depths = d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth;

    for (i = 0; i < d3dappi.NumModes; i++) {
d3dappi.Mode[i].bThisDriverCanDo = FALSE;
if (!(D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths))
    continue;
d3dappi.Mode[i].bThisDriverCanDo = TRUE;

    }
    d3dappi.ThisMode.bThisDriverCanDo =
     d3dappi.Mode[d3dappi.CurrMode].bThisDriverCanDo;
    return TRUE;
}

/*
 * D3DAppIPickDisplayMode
 * Pick a display mode of one of the given depths.  640x480x16 is prefered.
 */
BOOL
D3DAppIPickDisplayMode(int *mode, DWORD depths)
{
    int i, j;
    for (i = 0; i < d3dappi.NumModes; i++)
if (D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths)
    break;
    j = i;
    for (; i < d3dappi.NumModes; i++) {
if (!(D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths))
    continue;
if (d3dappi.Mode[i].w == 640 && d3dappi.Mode[i].h == 480 &&
    d3dappi.Mode[i].bpp == 16) {
    j = i;
    break;
}
    }
    if (j >= d3dappi.NumModes)
*mode = D3DAPP_BOGUS;
    else
*mode = j;
    return TRUE;
}

/*
 * D3DAppIVerifyDriverAndMode
 * Verifies the selected driver and mode combination.  If the driver is
 * specified, the mode will be changed to accomodate the driver if it's not
 * compatible.  If the driver is not specified, one will be selected which is
 * compatible with the specified mode.  If neither are specified, a suitable
 * pair will be returned.
 */
BOOL
D3DAppIVerifyDriverAndMode(int* lpdriver, int* lpmode)
{
    DWORD depths;
    int driver, mode, i;
    driver = *lpdriver; mode = *lpmode;

    if (mode == D3DAPP_USEWINDOW && !d3dappi.bIsPrimary) {
D3DAppISetErrorString("Cannot render to a window when the DirectDraw device is not the primary.\n");
goto exit_with_error;
    }

    /*
     * If I've been ask to choose a driver, choose one which is compatible
     * with the specified mode.
     */
    if (driver == D3DAPP_YOUDECIDE) {
if (mode == D3DAPP_USEWINDOW) {
    /*
     * I must find a driver for this display depth
     */
    depths = D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp);
    ATTEMPT(D3DAppIPickDriver(&driver, depths));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the current display depth.\n");
goto exit_with_error;
    }
    /*
     * I don't need to go through the mode selection since I've
     * verified it here
     */
    goto ret_ok;
} else if (mode == D3DAPP_YOUDECIDE) {
    /*
     * I'm free to choose any driver which can use even one
     * supported depth
     */
    if (d3dappi.bIsPrimary)
depths = D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp);
    else
depths = 0;
    for (i = 0; i < d3dappi.NumModes; i++)
depths |= D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp);
    ATTEMPT(D3DAppIPickDriver(&driver, depths));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the current display depth or any supported fullscreen mode.\n");
goto exit_with_error;
    }
    /*
     * The mode will be chosen in the next section
     */
} else {
    /*
     * Must pick a driver which uses the given mode depth
     */
    ATTEMPT(D3DAppIPickDriver(&driver,
  D3DAppIBPPToDDBD(d3dappi.Mode[mode].bpp)));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the specified fullscreen mode.\n");
goto exit_with_error;
    }
    /*
     * I don't need to go through the mode selection since I've
     * verified it here
     */
    goto ret_ok;
}
    }

    /*
     * At this stage, I have a driver I want to match the mode to.
     */
    if (mode == D3DAPP_YOUDECIDE) {
/*
 * If it's my choice, I prefer windowed over fullscreen
 */
if (d3dappi.bIsPrimary) {
    if (D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp) &
    d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
mode = D3DAPP_USEWINDOW;
goto ret_ok;
    }
}
/*
 * Either this is not a primary DD device or the driver cannot use
 * the Windows display depth
 */
ATTEMPT(D3DAppIPickDisplayMode(&mode,
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth));
if (mode == D3DAPP_BOGUS) {
    D3DAppISetErrorString("The selected D3D device driver is not compatible with the current display depth or any supported fullscreen modes.\n");
    goto exit_with_error;
}
goto ret_ok;
    } else if (mode == D3DAPP_USEWINDOW) {
/*
 * Check to see if this driver can use the Windows display depth
 */
if (D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp) &
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
    goto ret_ok;
} else {
    /*
     * Since it cannot, call this function again to choose any
     * display mode which is compatible
     */
    mode = D3DAPP_YOUDECIDE;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    if (driver == D3DAPP_BOGUS)
goto exit_with_error;
    goto ret_ok;
}
    } else {
/*
 * Check to see if this driver can use the specified fullscreen mode
 */
if (D3DAppIBPPToDDBD(d3dappi.Mode[mode].bpp) &
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
    goto ret_ok;
} else {
    /*
     * Since it cannot, call this function again to choose any
     * display mode which is compatible
     */
    mode = D3DAPP_YOUDECIDE;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    if (driver == D3DAPP_BOGUS)
goto exit_with_error;
    goto ret_ok;
}
    }

ret_ok:
    *lpdriver = driver; *lpmode = mode;
    return TRUE;
exit_with_error:
    return FALSE;
}

/***************************************************************************/
/*                        Dirty Rectangle Functions                        */
/***************************************************************************/
/*
 * D3DAppIValidateDirtyRects
 * Set the dirty rectangles for the front and back buffers to the entire
 * client size.
 */
void
D3DAppIValidateDirtyRects(void)
{
    NumDirtyClientRects = 1; NumDirtyBackRects = 1; NumDirtyZRects = 1;
    SetRect((LPRECT)&DirtyClient[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
    SetRect((LPRECT)&DirtyBack[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
    SetRect((LPRECT)&DirtyZ[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
}

/*
 * D3DAppICopyRectList
 * Copy a list of rectangles to another
 */
void
D3DAppICopyRectList(int* dstnum, LPD3DRECT dst, int srcnum, LPD3DRECT src)
{
    int i;
    for (i = 0; i < srcnum; i++)
dst[i] = src[i];
    *dstnum = srcnum;
}

/*
 * MERGE macro
 * Set first rectangle to be the smallest rectangle containing both rects
 */
#undef MERGE
#define MERGE(rc1, rc2)\
    do {\
if (rc2.x1 < rc1.x1) rc1.x1 = rc2.x1;\
if (rc2.y1 < rc1.y1) rc1.y1 = rc2.y1;\
if (rc2.x2 > rc1.x2) rc1.x2 = rc2.x2;\
if (rc2.y2 > rc1.y2) rc1.y2 = rc2.y2;\
    } while(0)

/*
 * D3DAppIMergeRectLists
 * Merge two lists of rectangles to create another list of rectangles. The
 * merged list of rectangles covers all the area of the original two with NO
 * OVERLAPPING amongst it's rectangles.
 */
void
D3DAppIMergeRectLists(int* dstnum, LPD3DRECT dst, int src1num, LPD3DRECT src1,
     int src2num, LPD3DRECT src2)
{
    LPD3DRECT rc;
    int* isvalid;
    int num, i, j, intersect;
    rc = (LPD3DRECT)malloc(sizeof(D3DRECT) * D3DAPP_MAXCLEARRECTS * 2);
    memset(rc, 0, sizeof(D3DRECT) * D3DAPP_MAXCLEARRECTS * 2);
    isvalid = (int*)malloc(sizeof(int) * D3DAPP_MAXCLEARRECTS * 2);
    memset(isvalid, 0, sizeof(int) * D3DAPP_MAXCLEARRECTS * 2);
    for (i = 0; i < src1num; i++) {
memcpy(&rc[i], &src1[i], sizeof(D3DRECT));
if ((rc[i].x1 == 0 && rc[i].x2 == 0) ||
    (rc[i].y1 == 0 && rc[i].y2 == 0))
    isvalid[i] = 0;
        else if (rc[i].x1 <= rc[i].x2 && rc[i].y1 <= rc[i].y2)
    isvalid[i] = 1;
        else
            isvalid[i] = 0;
    }
    for (i = 0; i < src2num; i++) {
memcpy(&rc[i + src1num], &src2[i], sizeof(D3DRECT));
        if (rc[i + src1num].x1 <= rc[i + src1num].x2 &&
            rc[i + src1num].y1 <= rc[i + src1num].y2)
    isvalid[i + src1num] = 1;
        else
            isvalid[i + src1num] = 0;

    }
    num = src1num + src2num;
    for (i = 0; i < num - 1; i++) {
        if (!isvalid[i]) continue;
j = i + 1;
do {
    intersect = 0;
    for (; j < num; j++) {
if (j != i && isvalid[j]) {
    if (rc[i].x1 < rc[j].x1) {
if (rc[i].x2 < rc[j].x1)
    continue;
    } else {
if (rc[j].x2 < rc[i].x1)
    continue;
    }
    if (rc[i].y1 < rc[j].y1) {
if (rc[i].y2 < rc[j].y1)
    continue;
    } else {
if (rc[j].y2 < rc[i].y1)
    continue;
    }
    MERGE(rc[i], rc[j]);
    isvalid[j] = 0;
    j = 0; intersect = 1;
    break;
}
    }
} while(intersect);
    }

    for (i = 0, j = 0; i < num; i++)
if (isvalid[i]) ++j;
    if (j > D3DAPP_MAXCLEARRECTS) {
for (i = 0; i < num; i++)
    if (isvalid[i]) break;
if (i < num) {
    *dstnum = 1;
    dst[0] = rc[i];
    for (; i < num; i++) {
if (isvalid[i]) {
    MERGE(dst[0], rc[i]);
}
    }
} else {
    *dstnum = 0;
}
    } else {
for (i = 0, j = 0; i < num; i++) {
    if (isvalid[i]) {
memcpy(&dst[j], &rc[i], sizeof(D3DRECT));
++j;
    }
}
*dstnum = j;
    }
    free(rc);
    free(isvalid);
}

/***************************************************************************/
/*                     Getting and Setting Window Attribs                  */
/***************************************************************************/
/*
 * D3DAppISetClientSize
 * Set the client size of the given window.  A WM_SIZE message is generated,
 * but ignored.
 */
void
D3DAppISetClientSize(HWND hwnd, int w, int h, BOOL bReturnFromFullscreen)
{
    RECT rc;

    bIgnoreWM_SIZE = TRUE;
    if (bReturnFromFullscreen) {
SetRect(&rc, 0, 0, w, h);
AdjustWindowRectEx(&rc, GetWindowLong(hwnd, GWL_STYLE),
           GetMenu(hwnd) != NULL,
           GetWindowLong(hwnd, GWL_EXSTYLE));
SetWindowPos(hwnd, NULL, 0, 0, rc.right-rc.left,
             rc.bottom-rc.top,
             SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
             SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

    } else {
/*
 * This is the only way to set the client size correctly if the menu
 * is stacked, so do it unless we are returning from a fullscreen
 * mode.
 */
SendMessage(hwnd, WM_SIZE, SIZE_RESTORED, w + h << 16);
GetWindowRect(hwnd, &rc);
SetWindowPos(hwnd, NULL, 0, 0, rc.right-rc.left,
             rc.bottom-rc.top,
             SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
             SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
    }
    bIgnoreWM_SIZE = FALSE;
    d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
    ClientToScreen(hwnd, &d3dappi.pClientOnPrimary);
    d3dappi.szClient.cx = w; d3dappi.szClient.cy = h;
}

/*
 * D3DAppIGetClientWin
 * Gets the client window size and sets it in the D3DAppInfo structure
 */
void
D3DAppIGetClientWin(HWND hwnd)
{
    RECT rc;
    if (!d3dappi.bFullscreen) {
d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
ClientToScreen(hwnd, &d3dappi.pClientOnPrimary);
GetClientRect(hwnd, &rc);
d3dappi.szClient.cx = rc.right;
d3dappi.szClient.cy = rc.bottom;
    } else {
/*
 * In the fullscreen case, we must be careful because if the window
 * frame has been drawn, the client size has shrunk and this can
 * cause problems, so it's best to report the entire screen.
 */
d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
d3dappi.szClient.cx = d3dappi.ThisMode.w;
d3dappi.szClient.cy = d3dappi.ThisMode.h;
    }
}

/***************************************************************************/
/*                              Error reporting                            */
/***************************************************************************/

/*
 * D3DAppISetErrorString
 * Set the global variable which records the last error string.
 */
void
D3DAppISetErrorString( LPSTR fmt, ... )
{
    char buff[256];
    va_list args;

    buff[0] = 0;
    va_start(args, fmt);
    wvsprintf(&buff[0], fmt, args);
    va_end(args);

    lstrcat(buff, "\r\n");
    lstrcpy(LastErrorString, buff);
}

/* dpf
 * Debug printf.  Very useful for fullscreen exclusive mode or when surfaces
 * are in video memory.
 */
void __cdecl
dpf( LPSTR fmt, ... )
{
    char buff[256];
    va_list args;

    lstrcpy(buff, "D3DApp: ");
    va_start(args, fmt);
    wvsprintf(&buff[lstrlen(buff)], fmt, args);
    va_end(args);

    lstrcat(buff, "\r\n");
    OutputDebugString(buff);
}


MULTIMON.H

#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0500)

//=============================================================================
//
// MULTIMON
// stub module that fakes multiple monitor apis on pre Memphis Win32 OSes
//
// By using this header your code will work unchanged on Win95,
// you will get back default values from GetSystemMetrics() for new metrics
// and the new APIs will act like only one display is present.
//
// exactly one source must include this with COMPILE_MULTIMON_STUBS defined
//
//=============================================================================

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif  /* __cplusplus */

//
// if we are building on Win95/NT4 headers we need to declare this stuff ourselves
//
#ifndef SM_CMONITORS

#define SM_XVIRTUALSCREEN       76
#define SM_YVIRTUALSCREEN       77
#define SM_CXVIRTUALSCREEN      78
#define SM_CYVIRTUALSCREEN      79
#define SM_CMONITORS            80
#define SM_SAMEDISPLAYFORMAT    81

DECLARE_HANDLE(HMONITOR);

#define MONITOR_DEFAULTTONULL       0x00000000
#define MONITOR_DEFAULTTOPRIMARY    0x00000001
#define MONITOR_DEFAULTTONEAREST    0x00000002

#define MONITORINFOF_PRIMARY        0x00000001

typedef struct tagMONITORINFO
{
    DWORD   cbSize;
    RECT    rcMonitor;
    RECT    rcWork;
    DWORD   dwFlags;
} MONITORINFO, *LPMONITORINFO;

#define CCHDEVICENAME 32

#ifdef __cplusplus
typedef struct tagMONITORINFOEX : public tagMONITORINFO
{
    TCHAR       szDevice[CCHDEVICENAME];
} MONITORINFOEX, *LPMONITORINFOEX;
#else
typedef struct
{
    MONITORINFO;
    TCHAR       szDevice[CCHDEVICENAME];
} MONITORINFOEX, *LPMONITORINFOEX;
#endif

typedef BOOL (CALLBACK* MONITORENUMPROC)(HMONITOR, HDC, LPRECT, LPARAM);

#endif  // SM_CMONITORS

#undef GetMonitorInfo
#undef GetSystemMetrics
#undef MonitorFromWindow
#undef MonitorFromRect
#undef MonitorFromPoint
#undef EnumDisplayMonitors

//
// define this to compile the stubs
// otherwise you get the declarations
//
#ifdef COMPILE_MULTIMON_STUBS

//-----------------------------------------------------------------------------
//
// Implement the API stubs.
//
//-----------------------------------------------------------------------------

int      (WINAPI* g_pfnGetSystemMetrics)(int);
HMONITOR (WINAPI* g_pfnMonitorFromWindow)(HWND, BOOL);
HMONITOR (WINAPI* g_pfnMonitorFromRect)(LPCRECT, BOOL);
HMONITOR (WINAPI* g_pfnMonitorFromPoint)(POINT, BOOL);
BOOL     (WINAPI* g_pfnGetMonitorInfo)(HMONITOR, LPMONITORINFO);
BOOL     (WINAPI* g_pfnEnumDisplayMonitors)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);

BOOL InitMultipleMonitorStubs(void)
{
    HMODULE hUser32;
    static BOOL fInitDone;

    if (fInitDone)
    {
        return g_pfnGetMonitorInfo != NULL;
    }

    if ((hUser32 = GetModuleHandle(TEXT("USER32"))) &&
        (*(FARPROC*)&g_pfnGetSystemMetrics    = GetProcAddress(hUser32,"GetSystemMetrics")) &&
        (*(FARPROC*)&g_pfnMonitorFromWindow   = GetProcAddress(hUser32,"MonitorFromWindow")) &&
        (*(FARPROC*)&g_pfnMonitorFromRect     = GetProcAddress(hUser32,"MonitorFromRect")) &&
        (*(FARPROC*)&g_pfnMonitorFromPoint    = GetProcAddress(hUser32,"MonitorFromPoint")) &&
        (*(FARPROC*)&g_pfnEnumDisplayMonitors = GetProcAddress(hUser32,"EnumDisplayMonitors")) &&
#ifdef UNICODE
        (*(FARPROC*)&g_pfnGetMonitorInfo      = GetProcAddress(hUser32,"GetMonitorInfoW")) &&
#else
        (*(FARPROC*)&g_pfnGetMonitorInfo      = GetProcAddress(hUser32,"GetMonitorInfoA")) &&
#endif
        //
        // Old builds of Memphis had different indices for these metrics, and
        // some of the APIs and structs have changed since then, so validate that
        // the returned metrics are not totally stupid.  (for example on an old
        // Memphis build, the new index for SM_CYVIRTUALSCREEN will fetch 0)
        //
        // If this is preventing you from using the shell on secondary monitors
        // under Memphis then upgrade to a new Memphis build that is in sync with
        // the current version of the multi-monitor APIs.
        //
        (GetSystemMetrics(SM_CXVIRTUALSCREEN) >= GetSystemMetrics(SM_CXSCREEN)) &&
        (GetSystemMetrics(SM_CYVIRTUALSCREEN) >= GetSystemMetrics(SM_CYSCREEN)) )
    {
        fInitDone = TRUE;
        return TRUE;
    }
    else
    {
        g_pfnGetSystemMetrics    = NULL;
        g_pfnMonitorFromWindow   = NULL;
        g_pfnMonitorFromRect     = NULL;
        g_pfnMonitorFromPoint    = NULL;
        g_pfnGetMonitorInfo      = NULL;
        g_pfnEnumDisplayMonitors = NULL;

        fInitDone = TRUE;
        return FALSE;
    }
}

//-----------------------------------------------------------------------------
//
// fake implementations of Monitor APIs that work with the primary display
// no special parameter validation is made since these run in client code
//
//-----------------------------------------------------------------------------

int WINAPI
xGetSystemMetrics(int nIndex)
{
    if (InitMultipleMonitorStubs())
        return g_pfnGetSystemMetrics(nIndex);

    switch (nIndex)
    {
    case SM_CMONITORS:
    case SM_SAMEDISPLAYFORMAT:
        return 1;

    case SM_XVIRTUALSCREEN:
    case SM_YVIRTUALSCREEN:
        return 0;

    case SM_CXVIRTUALSCREEN:
        nIndex = SM_CXSCREEN;
        break;

    case SM_CYVIRTUALSCREEN:
        nIndex = SM_CYSCREEN;
        break;
    }

    return GetSystemMetrics(nIndex);
}

#define xPRIMARY_MONITOR ((HMONITOR)0x42)

HMONITOR WINAPI
xMonitorFromRect(LPCRECT lprcScreenCoords, UINT uFlags)
{
    if (InitMultipleMonitorStubs())
        return g_pfnMonitorFromRect(lprcScreenCoords, uFlags);

    if ((uFlags & (MONITOR_DEFAULTTOPRIMARY | MONITOR_DEFAULTTONEAREST)) ||
        ((lprcScreenCoords->right > 0) &&
        (lprcScreenCoords->bottom > 0) &&
        (lprcScreenCoords->left < GetSystemMetrics(SM_CXSCREEN)) &&
        (lprcScreenCoords->top < GetSystemMetrics(SM_CYSCREEN))))
    {
        return xPRIMARY_MONITOR;
    }

    return NULL;
}

HMONITOR WINAPI
xMonitorFromPoint(POINT ptScreenCoords, UINT uFlags)
{
    if (InitMultipleMonitorStubs())
        return g_pfnMonitorFromPoint(ptScreenCoords, uFlags);

    if ((uFlags & (MONITOR_DEFAULTTOPRIMARY | MONITOR_DEFAULTTONEAREST)) ||
        ((ptScreenCoords.x >= 0) &&
        (ptScreenCoords.x < GetSystemMetrics(SM_CXSCREEN)) &&
        (ptScreenCoords.y >= 0) &&
        (ptScreenCoords.y < GetSystemMetrics(SM_CYSCREEN))))
    {
        return xPRIMARY_MONITOR;
    }

    return NULL;
}

HMONITOR WINAPI
xMonitorFromWindow(HWND hWnd, UINT uFlags)
{
    RECT rc;

    if (InitMultipleMonitorStubs())
        return g_pfnMonitorFromWindow(hWnd, uFlags);

    if (uFlags & (MONITOR_DEFAULTTOPRIMARY | MONITOR_DEFAULTTONEAREST))
        return xPRIMARY_MONITOR;

    if (GetWindowRect(hWnd, &rc))
        return xMonitorFromRect(&rc, uFlags);

    return NULL;
}

BOOL WINAPI
xGetMonitorInfo(HMONITOR hMonitor, LPMONITORINFO lpMonitorInfo)
{
    RECT rcWork;

    if (InitMultipleMonitorStubs())
        return g_pfnGetMonitorInfo(hMonitor, lpMonitorInfo);

    if ((hMonitor == xPRIMARY_MONITOR) && lpMonitorInfo &&
        (lpMonitorInfo->cbSize >= sizeof(MONITORINFO)) &&
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0))
    {
        lpMonitorInfo->rcMonitor.left = 0;
        lpMonitorInfo->rcMonitor.top  = 0;
        lpMonitorInfo->rcMonitor.right  = GetSystemMetrics(SM_CXSCREEN);
        lpMonitorInfo->rcMonitor.bottom = GetSystemMetrics(SM_CYSCREEN);
        lpMonitorInfo->rcWork = rcWork;
        lpMonitorInfo->dwFlags = MONITORINFOF_PRIMARY;

        if (lpMonitorInfo->cbSize >= sizeof(MONITORINFOEX))
            lstrcpy(((MONITORINFOEX*)lpMonitorInfo)->szDevice, TEXT("DISPLAY"));

        return TRUE;
    }

    return FALSE;
}

BOOL WINAPI
xEnumDisplayMonitors(HDC hdcOptionalForPainting,
    LPCRECT lprcEnumMonitorsThatIntersect, MONITORENUMPROC lpfnEnumProc,
    LPARAM lData)
{
    RECT rcCallback, rcLimit;

    if (InitMultipleMonitorStubs())
        return g_pfnEnumDisplayMonitors(hdcOptionalForPainting,
            lprcEnumMonitorsThatIntersect, lpfnEnumProc, lData);

    if (!lpfnEnumProc)
        return FALSE;

    rcLimit.left   = 0;
    rcLimit.top    = 0;
    rcLimit.right  = GetSystemMetrics(SM_CXSCREEN);
    rcLimit.bottom = GetSystemMetrics(SM_CYSCREEN);

    if (hdcOptionalForPainting)
    {
        RECT rcClip;
        HWND hWnd;

        if ((hWnd = WindowFromDC(hdcOptionalForPainting)) == NULL)
            return FALSE;

        switch (GetClipBox(hdcOptionalForPainting, &rcClip))
        {
        default:
            MapWindowPoints(NULL, hWnd, (LPPOINT)&rcLimit, 2);
            if (IntersectRect(&rcCallback, &rcClip, &rcLimit))
                break;
            //fall thru
        case NULLREGION:
             return TRUE;
        case ERROR:
             return FALSE;
        }

        rcLimit = rcCallback;
    }

    if (!lprcEnumMonitorsThatIntersect ||
        IntersectRect(&rcCallback, lprcEnumMonitorsThatIntersect, &rcLimit))
    {
        lpfnEnumProc(xPRIMARY_MONITOR, hdcOptionalForPainting, &rcCallback,
            lData);
    }

    return TRUE;
}

#undef xPRIMARY_MONITOR
#undef COMPILE_MULTIMON_STUBS

#else// COMPILE_MULTIMON_STUBS

extern intWINAPI xGetSystemMetrics(int);
extern HMONITOR WINAPI xMonitorFromWindow(HWND, UINT);
extern HMONITOR WINAPI xMonitorFromRect(LPCRECT, UINT);
extern HMONITOR WINAPI xMonitorFromPoint(POINT, UINT);
extern BOOLWINAPI xGetMonitorInfo(HMONITOR, LPMONITORINFO);
extern BOOLWINAPI xEnumDisplayMonitors(HDC, LPCRECT, MONITORENUMPROC, LPARAM);

#endif// COMPILE_MULTIMON_STUBS

//
// build defines that replace the regular APIs with our versions
//
#define GetSystemMetrics    xGetSystemMetrics
#define MonitorFromWindow   xMonitorFromWindow
#define MonitorFromRect     xMonitorFromRect
#define MonitorFromPoint    xMonitorFromPoint
#define GetMonitorInfo      xGetMonitorInfo
#define EnumDisplayMonitors xEnumDisplayMonitors

#ifdef __cplusplus
}
#endif/* __cplusplus */

#endif  /* !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0500) */


RMDEMO.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmdemo.h
 *
 ***************************************************************************/
#ifndef __RMDEMO_H__
#define __RMDEMO_H__

#include <d3drmwin.h>
#include <d3drm.h>
#include "rmerror.h"

#undef RELEASE
#ifdef __cplusplus
#define RELEASE(x) if (x != NULL) {x->Release(); x = NULL;}
#else
#define RELEASE(x) if (x != NULL) {x->lpVtbl->Release(x); x = NULL;}
#endif

#ifdef __cplusplus
extern "C" {
#endif
    /*
     * A copy of LPDIRECT3DRM for use by the examples.
     */
    extern LPDIRECT3DRM lpD3DRM;
    /*
     * Builds the scene.
     */
    BOOL BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view, LPDIRECT3DRMFRAME scene,
LPDIRECT3DRMFRAME camera);

    /*
     * Allows each example to begin with different defaults
     */
    typedef struct Defaultstag {
BOOL bNoTextures;
BOOL bResizingDisabled;
BOOL bConstRenderQuality;
char Name[50];
    } Defaults;
    void OverrideDefaults(Defaults* defaults);

#ifdef __cplusplus
};
#endif

#endif  //__RMDEMO_H__


RMERROR.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmerror.cpp
 *
 *  Error reporting code for D3DRM examples.
 *
 */

#include "rmerror.h"
#include <stdarg.h>

/* Msg
 * Displays a message box containing the given formatted string.
 */
void __cdecl
Msg( LPSTR fmt, ... )
{
    char buff[256];
    va_list args;

    va_start(args, fmt);
    wvsprintf(buff, fmt, args);
    va_end(args);

    lstrcat(buff, "\r\n");
    MessageBox( NULL, buff, "D3DRM Example Message", MB_OK );
}

/*
 * D3DRMErrorToString
 * Returns a pointer to a string describing the given DD, D3D or D3DRM error code.
 */
char*
D3DRMErrorToString(HRESULT error)
{
    switch(error) {
        case DD_OK:
    /* Also includes D3D_OK and D3DRM_OK */
            return "No error.\0";
        case DDERR_ALREADYINITIALIZED:
            return "This object is already initialized.\0";
        case DDERR_BLTFASTCANTCLIP:
            return "Return if a clipper object is attached to the source surface passed into a BltFast call.\0";
        case DDERR_CANNOTATTACHSURFACE:
            return "This surface can not be attached to the requested surface.\0";
        case DDERR_CANNOTDETACHSURFACE:
            return "This surface can not be detached from the requested surface.\0";
        case DDERR_CANTCREATEDC:
            return "Windows can not create any more DCs.\0";
        case DDERR_CANTDUPLICATE:
            return "Can't duplicate primary & 3D surfaces, or surfaces that are implicitly created.\0";
        case DDERR_CLIPPERISUSINGHWND:
            return "An attempt was made to set a cliplist for a clipper object that is already monitoring an hwnd.\0";
        case DDERR_COLORKEYNOTSET:
            return "No src color key specified for this operation.\0";
        case DDERR_CURRENTLYNOTAVAIL:
            return "Support is currently not available.\0";
        case DDERR_DIRECTDRAWALREADYCREATED:
            return "A DirectDraw object representing this driver has already been created for this process.\0";
        case DDERR_EXCEPTION:
            return "An exception was encountered while performing the requested operation.\0";
        case DDERR_EXCLUSIVEMODEALREADYSET:
            return "An attempt was made to set the cooperative level when it was already set to exclusive.\0";
        case DDERR_GENERIC:
            return "Generic failure.\0";
        case DDERR_HEIGHTALIGN:
            return "Height of rectangle provided is not a multiple of reqd alignment.\0";
        case DDERR_HWNDALREADYSET:
            return "The CooperativeLevel HWND has already been set. It can not be reset while the process has surfaces or palettes created.\0";
        case DDERR_HWNDSUBCLASSED:
            return "HWND used by DirectDraw CooperativeLevel has been subclassed, this prevents DirectDraw from restoring state.\0";
        case DDERR_IMPLICITLYCREATED:
            return "This surface can not be restored because it is an implicitly created surface.\0";
        case DDERR_INCOMPATIBLEPRIMARY:
            return "Unable to match primary surface creation request with existing primary surface.\0";
        case DDERR_INVALIDCAPS:
            return "One or more of the caps bits passed to the callback are incorrect.\0";
        case DDERR_INVALIDCLIPLIST:
            return "DirectDraw does not support the provided cliplist.\0";
        case DDERR_INVALIDDIRECTDRAWGUID:
            return "The GUID passed to DirectDrawCreate is not a valid DirectDraw driver identifier.\0";
        case DDERR_INVALIDMODE:
            return "DirectDraw does not support the requested mode.\0";
        case DDERR_INVALIDOBJECT:
            return "DirectDraw received a pointer that was an invalid DIRECTDRAW object.\0";
        case DDERR_INVALIDPARAMS:
            return "One or more of the parameters passed to the function are incorrect.\0";
        case DDERR_INVALIDPIXELFORMAT:
            return "The pixel format was invalid as specified.\0";
        case DDERR_INVALIDPOSITION:
            return "Returned when the position of the overlay on the destination is no longer legal for that destination.\0";
        case DDERR_INVALIDRECT:
            return "Rectangle provided was invalid.\0";
        case DDERR_LOCKEDSURFACES:
            return "Operation could not be carried out because one or more surfaces are locked.\0";
        case DDERR_NO3D:
            return "There is no 3D present.\0";
        case DDERR_NOALPHAHW:
            return "Operation could not be carried out because there is no alpha accleration hardware present or available.\0";
        case DDERR_NOBLTHW:
            return "No blitter hardware present.\0";
        case DDERR_NOCLIPLIST:
            return "No cliplist available.\0";
        case DDERR_NOCLIPPERATTACHED:
            return "No clipper object attached to surface object.\0";
        case DDERR_NOCOLORCONVHW:
            return "Operation could not be carried out because there is no color conversion hardware present or available.\0";
        case DDERR_NOCOLORKEY:
            return "Surface doesn't currently have a color key\0";
        case DDERR_NOCOLORKEYHW:
            return "Operation could not be carried out because there is no hardware support of the destination color key.\0";
        case DDERR_NOCOOPERATIVELEVELSET:
            return "Create function called without DirectDraw object method SetCooperativeLevel being called.\0";
        case DDERR_NODC:
            return "No DC was ever created for this surface.\0";
        case DDERR_NODDROPSHW:
            return "No DirectDraw ROP hardware.\0";
        case DDERR_NODIRECTDRAWHW:
            return "A hardware-only DirectDraw object creation was attempted but the driver did not support any hardware.\0";
        case DDERR_NOEMULATION:
            return "Software emulation not available.\0";
        case DDERR_NOEXCLUSIVEMODE:
            return "Operation requires the application to have exclusive mode but the application does not have exclusive mode.\0";
        case DDERR_NOFLIPHW:
            return "Flipping visible surfaces is not supported.\0";
        case DDERR_NOGDI:
            return "There is no GDI present.\0";
        case DDERR_NOHWND:
            return "Clipper notification requires an HWND or no HWND has previously been set as the CooperativeLevel HWND.\0";
        case DDERR_NOMIRRORHW:
            return "Operation could not be carried out because there is no hardware present or available.\0";
        case DDERR_NOOVERLAYDEST:
            return "Returned when GetOverlayPosition is called on an overlay that UpdateOverlay has never been called on to establish a destination.\0";
        case DDERR_NOOVERLAYHW:
            return "Operation could not be carried out because there is no overlay hardware present or available.\0";
        case DDERR_NOPALETTEATTACHED:
            return "No palette object attached to this surface.\0";
        case DDERR_NOPALETTEHW:
            return "No hardware support for 16 or 256 color palettes.\0";
        case DDERR_NORASTEROPHW:
            return "Operation could not be carried out because there is no appropriate raster op hardware present or available.\0";
        case DDERR_NOROTATIONHW:
            return "Operation could not be carried out because there is no rotation hardware present or available.\0";
        case DDERR_NOSTRETCHHW:
            return "Operation could not be carried out because there is no hardware support for stretching.\0";
        case DDERR_NOT4BITCOLOR:
            return "DirectDrawSurface is not in 4 bit color palette and the requested operation requires 4 bit color palette.\0";
        case DDERR_NOT4BITCOLORINDEX:
            return "DirectDrawSurface is not in 4 bit color index palette and the requested operation requires 4 bit color index palette.\0";
        case DDERR_NOT8BITCOLOR:
            return "DirectDrawSurface is not in 8 bit color mode and the requested operation requires 8 bit color.\0";
        case DDERR_NOTAOVERLAYSURFACE:
            return "Returned when an overlay member is called for a non-overlay surface.\0";
        case DDERR_NOTEXTUREHW:
            return "Operation could not be carried out because there is no texture mapping hardware present or available.\0";
        case DDERR_NOTFLIPPABLE:
            return "An attempt has been made to flip a surface that is not flippable.\0";
        case DDERR_NOTFOUND:
            return "Requested item was not found.\0";
        case DDERR_NOTLOCKED:
            return "Surface was not locked.  An attempt to unlock a surface that was not locked at all, or by this process, has been attempted.\0";
        case DDERR_NOTPALETTIZED:
            return "The surface being used is not a palette-based surface.\0";
        case DDERR_NOVSYNCHW:
            return "Operation could not be carried out because there is no hardware support for vertical blank synchronized operations.\0";
        case DDERR_NOZBUFFERHW:
            return "Operation could not be carried out because there is no hardware support for zbuffer blitting.\0";
        case DDERR_NOZOVERLAYHW:
            return "Overlay surfaces could not be z layered based on their BltOrder because the hardware does not support z layering of overlays.\0";
        case DDERR_OUTOFCAPS:
            return "The hardware needed for the requested operation has already been allocated.\0";
        case DDERR_OUTOFMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OUTOFVIDEOMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OVERLAYCANTCLIP:
            return "The hardware does not support clipped overlays.\0";
        case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
            return "Can only have ony color key active at one time for overlays.\0";
        case DDERR_OVERLAYNOTVISIBLE:
            return "Returned when GetOverlayPosition is called on a hidden overlay.\0";
        case DDERR_PALETTEBUSY:
            return "Access to this palette is being refused because the palette is already locked by another thread.\0";
        case DDERR_PRIMARYSURFACEALREADYEXISTS:
            return "This process already has created a primary surface.\0";
        case DDERR_REGIONTOOSMALL:
            return "Region passed to Clipper::GetClipList is too small.\0";
        case DDERR_SURFACEALREADYATTACHED:
            return "This surface is already attached to the surface it is being attached to.\0";
        case DDERR_SURFACEALREADYDEPENDENT:
            return "This surface is already a dependency of the surface it is being made a dependency of.\0";
        case DDERR_SURFACEBUSY:
            return "Access to this surface is being refused because the surface is already locked by another thread.\0";
        case DDERR_SURFACEISOBSCURED:
            return "Access to surface refused because the surface is obscured.\0";
        case DDERR_SURFACELOST:
            return "Access to this surface is being refused because the surface memory is gone. The DirectDrawSurface object representing this surface should have Restore called on it.\0";
        case DDERR_SURFACENOTATTACHED:
            return "The requested surface is not attached.\0";
        case DDERR_TOOBIGHEIGHT:
            return "Height requested by DirectDraw is too large.\0";
        case DDERR_TOOBIGSIZE:
            return "Size requested by DirectDraw is too large, but the individual height and width are OK.\0";
        case DDERR_TOOBIGWIDTH:
            return "Width requested by DirectDraw is too large.\0";
        case DDERR_UNSUPPORTED:
            return "Action not supported.\0";
        case DDERR_UNSUPPORTEDFORMAT:
            return "FOURCC format requested is unsupported by DirectDraw.\0";
        case DDERR_UNSUPPORTEDMASK:
            return "Bitmask in the pixel format requested is unsupported by DirectDraw.\0";
        case DDERR_VERTICALBLANKINPROGRESS:
            return "Vertical blank is in progress.\0";
        case DDERR_WASSTILLDRAWING:
            return "Informs DirectDraw that the previous Blt which is transfering information to or from this Surface is incomplete.\0";
        case DDERR_WRONGMODE:
            return "This surface can not be restored because it was created in a different mode.\0";
        case DDERR_XALIGN:
            return "Rectangle provided was not horizontally aligned on required boundary.\0";
        case D3DERR_BADMAJORVERSION:
            return "D3DERR_BADMAJORVERSION\0";
        case D3DERR_BADMINORVERSION:
            return "D3DERR_BADMINORVERSION\0";
        case D3DERR_EXECUTE_LOCKED:
            return "D3DERR_EXECUTE_LOCKED\0";
        case D3DERR_EXECUTE_NOT_LOCKED:
            return "D3DERR_EXECUTE_NOT_LOCKED\0";
        case D3DERR_EXECUTE_CREATE_FAILED:
            return "D3DERR_EXECUTE_CREATE_FAILED\0";
        case D3DERR_EXECUTE_DESTROY_FAILED:
            return "D3DERR_EXECUTE_DESTROY_FAILED\0";
        case D3DERR_EXECUTE_LOCK_FAILED:
            return "D3DERR_EXECUTE_LOCK_FAILED\0";
        case D3DERR_EXECUTE_UNLOCK_FAILED:
            return "D3DERR_EXECUTE_UNLOCK_FAILED\0";
        case D3DERR_EXECUTE_FAILED:
            return "D3DERR_EXECUTE_FAILED\0";
        case D3DERR_EXECUTE_CLIPPED_FAILED:
            return "D3DERR_EXECUTE_CLIPPED_FAILED\0";
        case D3DERR_TEXTURE_NO_SUPPORT:
            return "D3DERR_TEXTURE_NO_SUPPORT\0";
        case D3DERR_TEXTURE_NOT_LOCKED:
            return "D3DERR_TEXTURE_NOT_LOCKED\0";
        case D3DERR_TEXTURE_LOCKED:
            return "D3DERR_TEXTURELOCKED\0";
        case D3DERR_TEXTURE_CREATE_FAILED:
            return "D3DERR_TEXTURE_CREATE_FAILED\0";
        case D3DERR_TEXTURE_DESTROY_FAILED:
            return "D3DERR_TEXTURE_DESTROY_FAILED\0";
        case D3DERR_TEXTURE_LOCK_FAILED:
            return "D3DERR_TEXTURE_LOCK_FAILED\0";
        case D3DERR_TEXTURE_UNLOCK_FAILED:
            return "D3DERR_TEXTURE_UNLOCK_FAILED\0";
        case D3DERR_TEXTURE_LOAD_FAILED:
            return "D3DERR_TEXTURE_LOAD_FAILED\0";
        case D3DERR_MATRIX_CREATE_FAILED:
            return "D3DERR_MATRIX_CREATE_FAILED\0";
        case D3DERR_MATRIX_DESTROY_FAILED:
            return "D3DERR_MATRIX_DESTROY_FAILED\0";
        case D3DERR_MATRIX_SETDATA_FAILED:
            return "D3DERR_MATRIX_SETDATA_FAILED\0";
        case D3DERR_SETVIEWPORTDATA_FAILED:
            return "D3DERR_SETVIEWPORTDATA_FAILED\0";
        case D3DERR_MATERIAL_CREATE_FAILED:
            return "D3DERR_MATERIAL_CREATE_FAILED\0";
        case D3DERR_MATERIAL_DESTROY_FAILED:
            return "D3DERR_MATERIAL_DESTROY_FAILED\0";
        case D3DERR_MATERIAL_SETDATA_FAILED:
            return "D3DERR_MATERIAL_SETDATA_FAILED\0";
        case D3DERR_LIGHT_SET_FAILED:
            return "D3DERR_LIGHT_SET_FAILED\0";
case D3DRMERR_BADOBJECT:
    return "D3DRMERR_BADOBJECT\0";
case D3DRMERR_BADTYPE:
    return "D3DRMERR_BADTYPE\0";
case D3DRMERR_BADALLOC:
    return "D3DRMERR_BADALLOC\0";
case D3DRMERR_FACEUSED:
    return "D3DRMERR_FACEUSED\0";
case D3DRMERR_NOTFOUND:
    return "D3DRMERR_NOTFOUND\0";
case D3DRMERR_NOTDONEYET:
    return "D3DRMERR_NOTDONEYET\0";
case D3DRMERR_FILENOTFOUND:
    return "The file was not found.\0";
case D3DRMERR_BADFILE:
    return "D3DRMERR_BADFILE\0";
case D3DRMERR_BADDEVICE:
    return "D3DRMERR_BADDEVICE\0";
case D3DRMERR_BADVALUE:
    return "D3DRMERR_BADVALUE\0";
case D3DRMERR_BADMAJORVERSION:
    return "D3DRMERR_BADMAJORVERSION\0";
case D3DRMERR_BADMINORVERSION:
    return "D3DRMERR_BADMINORVERSION\0";
case D3DRMERR_UNABLETOEXECUTE:
    return "D3DRMERR_UNABLETOEXECUTE\0";
        default:
            return "Unrecognized error value.\0";
    }
}


RMERROR.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmerror.h
 *
 *  Error reporting code for D3DRM examples.
 *
 */

#ifndef __ERROR_H__
#define __ERROR_H__

#include <ddraw.h>
#include <d3d.h>
#include <d3drmwin.h>
#include <d3drm.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Msg
 * Displays a message box containing the given formatted string.
 */
void __cdecl
Msg( LPSTR fmt, ... );

/*
 * D3DRMErrorToString
 * Returns a pointer to a string describing the given DD, D3D or D3DRM error code.
 */
char*
D3DRMErrorToString(HRESULT error);

#ifdef __cplusplus
};
#endif
#endif // __ERROR_H__


RMFULL.CPP

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmfull.cpp
 *
 *  Each of the Direct3D retained mode (D3DRM) samples may be linked with
 *  this file.  It contains the code which allows them to run in the Windows
 *  environment as a window or fullscreen.  It is a modified version of
 *  d3dmain.cpp.  Comparing these two files is instructive.
 *
 *  A window is created using rmfull.res which allows the user to select the
 *  Direct3D driver to use and change the render options.  The D3DApp
 *  collection of functions is used to initialize DirectDraw, Direct3D and
 *  keep surfaces and D3D devices available for rendering.
 *
 *  Frame rate and a screen mode information buffer is Blt'ed to the screen
 *  by functions in rmstats.cpp.
 *
 *  Individual samples are executed through two functions, BuildScene and
 *  OverrideDefaults, as described in rmdemo.h.  Samples can also read
 *  mouse input via ReadMouse.
 */

#include "rmfull.h"
#include <stdarg.h>

/*
 * GLOBAL VARIABLES
 */
D3DAppInfo* d3dapp;    /* Pointer to read only collection of DD and D3D
       objects maintained by D3DApp */
rmfullglobals myglobs;    /* collection of global variables */
LPDIRECT3DRM lpD3DRM;/* Direct3DRM object */

/*
 *  INTERNAL FUNCTION PROTOTYPES
 */
static BOOL AppInit(HINSTANCE hInstance, LPSTR lpCmdLine);
static BOOL CreateD3DApp(LPSTR lpCmdLine);
static BOOL BeforeDeviceDestroyed(LPVOID lpContext);
static BOOL AfterDeviceCreated(int w, int h, LPDIRECT3DVIEWPORT* lpViewport,
       LPVOID lpContext);
void CleanUpAndPostQuit(void);
static void InitGlobals(void);
static BOOL AppPause(BOOL f);
void ReportD3DAppError(void);
static BOOL RenderLoop(void);
static BOOL RestoreSurfaces();
long FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam,
   LPARAM lParam );
extern "C" void ReadMouse(int*, int*, int*);
extern "C" char* D3DRMErrorToString(HRESULT error);
BOOL CreateD3DRM(HWND win);
BOOL SetRenderState(void);

/****************************************************************************/
/*                            WinMain                                       */
/****************************************************************************/
/*
 * Initializes the application then enters a message loop which calls sample's
 * RenderScene until a quit message is received.
 */
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
    int failcount = 0; /* number of times RenderLoop has failed */
    MSGmsg;
    HACCEL hAccelApp;

    hPrevInstance;
    /*
     * Create the window and initialize all objects needed to begin rendering
     */
    if(!AppInit(hInstance, lpCmdLine))
    return FALSE;
    hAccelApp = LoadAccelerators(hInstance, "AppAccel");

    while (!myglobs.bQuit) {
/*
 * Monitor the message queue until there are no pressing
 * messages
 */
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
CleanUpAndPostQuit();
                break;
    }
            if (!myglobs.hWndMain || !TranslateAccelerator(myglobs.hWndMain,
   hAccelApp, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
/*
 * If the app is not minimized, not about to quit, not paused, either the
 * active fullscreen app or in a window and D3D has been initialized,
 * we can render
 */
        } else if (d3dapp->bRenderingIsOK && !d3dapp->bMinimized
   && !d3dapp->bPaused && !myglobs.bQuit
   && (d3dapp->bAppActive || !d3dapp->bFullscreen)) {
    /*
     * If were are not in single step mode or if we are and the
     * bDrawAFrame flag is set, render one frame
     */
            if (!(myglobs.bSingleStepMode && !myglobs.bDrawAFrame)) {
/*
 * Attempt to render a frame, if it fails, take a note.  If
 * rendering fails more than twice, abort execution.
 */
if (!RenderLoop()) {
    ++failcount;
    if (failcount == 3) {
Msg("Rendering has failed too many times.  Aborting execution.\n");
CleanUpAndPostQuit();
break;
    }
}
    }
    /*
     * Reset the bDrawAFrame flag if we are in single step mode
     */
            if (myglobs.bSingleStepMode)
                myglobs.bDrawAFrame = FALSE;
        } else {
    WaitMessage();
}
    }
    DestroyWindow(myglobs.hWndMain);
    return msg.wParam;
}

/****************************************************************************/
/*             D3DApp Initialization and callback functions                 */
/****************************************************************************/
/*
 * AppInit
 * Creates the window and initializes all objects necessary to begin rendering
 */
static BOOL
AppInit(HINSTANCE hInstance, LPSTR lpCmdLine)
{
    WNDCLASS wc;
    HMENU hmenu;
    DWORD flags;
    int i;
    Defaults defaults;

    /*
     * Register the window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, "AppIcon");
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = "AppMenu";
    wc.lpszClassName = "Example";
    if (!RegisterClass(&wc))
        return FALSE;
    /*
     * Initialize the global variables and allow the sample code to override
     * some of these default settings.
     */
    InitGlobals();
    myglobs.hInstApp = hInstance;
    defaults.bNoTextures = myglobs.bNoTextures;
    defaults.bConstRenderQuality = myglobs.bConstRenderQuality;
    defaults.bResizingDisabled = FALSE;
    lstrcpy(defaults.Name, "D3DRM Example");
    OverrideDefaults(&defaults);
    myglobs.bNoTextures = defaults.bNoTextures;
    myglobs.bConstRenderQuality = defaults.bConstRenderQuality;
    myglobs.lpCmdLine = lpCmdLine;

    /*
     * Enumerate the DD drivers
     */
    if (!D3DAppEnumerateDDDevices(&myglobs.NumDDDrivers, &myglobs.DDDriver[0])) {
ReportD3DAppError();
return FALSE;
    }
    /*
     * Choose the last device enumerated which will probably be secondary 3d hardware.
     */
    myglobs.CurrDDDriver = myglobs.NumDDDrivers - 1;

    /*
     * Create the window
     */
    if (defaults.bResizingDisabled)
flags =  WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
 WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    else
flags = WS_OVERLAPPEDWINDOW;
    /*
     * Create a window with some default settings that may change
     */
    myglobs.hWndMain = CreateWindowEx(
          WS_EX_APPWINDOW,
 "Example",
 defaults.Name,
 flags,
         CW_USEDEFAULT, CW_USEDEFAULT,
 START_WIN_SIZE, START_WIN_SIZE,
         NULL,    /* parent window */
 NULL,    /* menu handle */
 hInstance,    /* program handle */
 NULL);    /* create parms */
    if (!myglobs.hWndMain){
    Msg("CreateWindowEx failed");
    return FALSE;
    }
    /*
     * Display the window
     */
    ShowWindow(myglobs.hWndMain, SW_SHOWNORMAL);
    UpdateWindow(myglobs.hWndMain);
    /*
     * Add the list of DD drivers D3DApp found to the file menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 0);
    for (i = 0; i < myglobs.NumDDDrivers; i++) {
        InsertMenu(hmenu,  6 + i, MF_BYPOSITION | MF_STRING,
   MENU_FIRST_DDDRIVER + i, myglobs.DDDriver[i].Name);
    }
    /*
     * Create the D3DRM object which are initialized only when the program
     * starts
     */
    if (!CreateD3DRM(myglobs.hWndMain))
        return FALSE;
    /*
     * Call D3DApp to initialize all DD and D3D objects necessary to render.
     * D3DApp will call the device creation callback which will initialize the
     * viewport and the sample's execute buffers.
     */
    if (!CreateD3DApp(lpCmdLine))
return FALSE;
    /*
     * Create the scene to be rendered by calling this sample's BuildScene
     */
    if (!BuildScene(myglobs.dev, myglobs.view, myglobs.scene, myglobs.camera))
return FALSE;

    return TRUE;
}

/*
 * CreateD3DRM
 * Create main D3DRM objects which are only initialized once.
 */
BOOL
CreateD3DRM(HWND win)
{
    HRESULT rval;

    /*
     * Create the D3DRM object
     */
    rval = Direct3DRMCreate(&lpD3DRM);
    if (rval != D3DRM_OK) {
Msg("Failed to create Direct3DRM.\n%s", D3DAppErrorToString(rval));
return FALSE;
    }
    /*
     * Create the master scene frame and camera frame
     */
    rval = lpD3DRM->CreateFrame(NULL, &myglobs.scene);
    if (rval != D3DRM_OK) {
Msg("Failed to create the master scene frame.\n%s", D3DAppErrorToString(rval));
return FALSE;
    }
    rval = lpD3DRM->CreateFrame(myglobs.scene, &myglobs.camera);
    if (rval != D3DRM_OK) {
Msg("Failed to create the camera's frame.\n%s", D3DAppErrorToString(rval));
return FALSE;
    }
    rval = myglobs.camera->SetPosition(myglobs.scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
    if (rval != D3DRM_OK) {
Msg("Failed to position the camera in the frame.\n%s", D3DAppErrorToString(rval));
return FALSE;
    }
    return TRUE;
}

/*
 * CreateD3DApp
 * Create all DirectDraw and Direct3D objects necessary to begin rendering.
 * Add the list of D3D drivers to the file menu.
 */
static BOOL
CreateD3DApp(LPSTR lpCmdLine)
{
    HMENU hmenu;
    int i;
    LPSTR option;
    BOOL bOnlySystemMemory, bOnlyEmulation;
    DWORD flags;

    /*
     * Parse the command line in seach of one of the following options:
     *     systemmemory  All surfaces should be created in system memory.
     *                   Hardware DD and D3D devices are disabled, but
     *                   debugging during the Win16 lock becomes possible.
     *     emulation     Do not use hardware DD or D3D devices.
     */
    bOnlySystemMemory = FALSE;
    bOnlyEmulation = FALSE;
    option = strtok(lpCmdLine, " -");
    while(option != NULL )   {
        if (!lstrcmp(option, "systemmemory")) {
            bOnlySystemMemory = TRUE;
        } else if (!lstrcmp(option, "emulation")) {
    bOnlyEmulation = TRUE;
} else {
            Msg("Invalid command line options given.\nLegal options: -systemmemory, -emulation\n");
            return FALSE;
        }
        option = strtok(NULL, " -");
    }
    /*
     * Set the flags to pass to the D3DApp creation based on command line
     */
    flags = ((bOnlySystemMemory) ? D3DAPP_ONLYSYSTEMMEMORY : 0) |
    ((bOnlyEmulation) ? (D3DAPP_ONLYD3DEMULATION |
 D3DAPP_ONLYDDEMULATION) : 0);
    /*
     * Create all the DirectDraw and D3D objects neccesary to render.  The
     * AfterDeviceCreated callback function is called by D3DApp to create the
     * viewport and the example's execute buffers.
     */
    if (!D3DAppCreateFromHWND(flags, myglobs.hWndMain,
      myglobs.DDDriver[myglobs.CurrDDDriver].bIsPrimary ? NULL : &myglobs.DDDriver[myglobs.CurrDDDriver].Guid,
      AfterDeviceCreated, NULL, BeforeDeviceDestroyed, NULL, &d3dapp)) {
ReportD3DAppError();
return FALSE;
    }
    /*
     * Add the the list of display modes D3DApp found to the mode menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 2);
    for (i = 0; i < d3dapp->NumModes; i++) {
        char ach[80];
        wsprintf(ach,"%dx%dx%d", d3dapp->Mode[i].w, d3dapp->Mode[i].h,
 d3dapp->Mode[i].bpp);
        AppendMenu(hmenu, MF_STRING, MENU_FIRST_MODE+i, ach);
    }
    /*
     * Add the list of D3D drivers D3DApp foudn to the file menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 0);
    for (i = 0; i < d3dapp->NumDrivers; i++) {
        InsertMenu(hmenu, 7 + myglobs.NumDDDrivers + i, MF_BYPOSITION | MF_STRING,
   MENU_FIRST_DRIVER + i, d3dapp->Driver[i].Name);
    }

    return TRUE;
}

/*
 * DestroyD3DApp
 *
 * Destroy D3DApp and changes to menu
 */
static void
DestroyD3DApp(void)
{
    HMENU hmenu;
    int i;

    /*
     * Remove the list of display modes
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 4);
    for (i = 0; i < d3dapp->NumModes; i++) {
        DeleteMenu(hmenu, MENU_FIRST_MODE + i, MF_BYCOMMAND);
    }
    /*
     * Remove the list of D3D drivers
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 0);
    for (i = 0; i < d3dapp->NumDrivers; i++) {
        DeleteMenu(hmenu, MENU_FIRST_DRIVER + i, MF_BYCOMMAND);
    }
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    D3DAppDestroy();
}

/*
 * AfterDeviceCreated
 * D3DApp will call this function immediately after the D3D device has been
 * created (or re-created).  D3DApp expects the D3D viewport to be created and
 * returned.  In this case, we will return NULL because we only have a D3DRM
 * viewport.  This is fine as long as we don't use any of the D3D viewport
 * functionality of D3DApp.
 */
static BOOL
AfterDeviceCreated(int w, int h, LPDIRECT3DVIEWPORT* lplpViewport, LPVOID lpContext)
{
    HRESULT rval;

    rval = lpD3DRM->CreateDeviceFromD3D(d3dapp->lpD3D, d3dapp->lpD3DDevice,
&myglobs.dev);
    if (rval != D3DRM_OK) {
        Msg("Creation of D3DRM device failed.\n%s", D3DAppErrorToString(rval));
        return FALSE;
    }
    /*
     * Create the D3DRM viewport using the camera frame.  Set the background
     * depth to a large number.  The width and height may be slightly
     * adjusted, so get them from the device to be sure.
     */
    w = myglobs.dev->GetWidth();
    h = myglobs.dev->GetHeight();
    rval = lpD3DRM->CreateViewport(myglobs.dev, myglobs.camera, 0, 0, w,
   h, &myglobs.view);
    if (rval != D3DRM_OK) {
Msg("Failed to create the D3DRM viewport.\n%s",
    D3DAppErrorToString(rval));
RELEASE(myglobs.dev);
return FALSE;
    }
    rval = myglobs.view->SetBack(D3DVAL(5000.0));
    if (rval != D3DRM_OK) {
Msg("Failed to set the background depth of the D3DRM viewport.\n%s",
    D3DAppErrorToString(rval));
RELEASE(myglobs.dev);
RELEASE(myglobs.view);
return FALSE;
    }
    /*
     * Set the render quality, fill mode, lighting state and color shade info
     */
    if (!SetRenderState())
return FALSE;

    /*
     * Return NULL for the viewport
     */
    *lplpViewport = NULL;
    /*
     * Create and initialize the surfaces containing the frame rate and
     * window information
     */
    InitFontAndTextBuffers();

    return TRUE;
}

/*
 * BeforeDeviceDestroyed
 * D3DApp will call this function before the current D3D device is destroyed
 * to give the app the opportunity to destroy objects it has created with the
 * DD or D3D objects.
 */
static BOOL
BeforeDeviceDestroyed(LPVOID lpContext)
{
    RELEASE(myglobs.view);
    RELEASE(myglobs.dev);
    return TRUE;
}

/****************************************************************************/
/*                            Rendering loop                                */
/****************************************************************************/
/*
 * RenderLoop
 * Render the next frame and update the window
 */
static BOOL
RenderLoop()
{
    D3DRECT extents[D3DAPP_MAXCLEARRECTS];
    int count;
    HRESULT rval;
    static BOOL b = FALSE; // Clear the second buffer on also

    /*
     * If all the DD and D3D objects have been initialized we can render
     */
    if (d3dapp->bRenderingIsOK) {
/*
 * Restore any lost surfaces
 */
        if (!RestoreSurfaces()) {
    /*
     * Restoring surfaces sometimes fails because the surfaces cannot
     * yet be restored.  If this is the case, the error will show up
     * somewhere else and we should return success here to prevent
     * unnecessary error's being reported.
     */
    return TRUE;
}
/*
 * Force an update of the entire client window if the resized flag is set
 */
if (myglobs.bResized || b)
    myglobs.view->ForceUpdate(0, 0, d3dapp->szClient.cx, d3dapp->szClient.cy);
/*
 * Use b to makesure the second buffer is cleared also
 */
if (b)
    b = FALSE;
if (myglobs.bResized)
    b = TRUE;

/*
 * Calculate the frame rate
 */
        if (!CalculateFrameRate())
    return FALSE;

/*
 * Tick the scene
 */
rval = myglobs.scene->Move(D3DVAL(1.0));
if (rval != D3DRM_OK) {
    Msg("Moving scene failed.\n%s", D3DAppErrorToString(rval));
    return FALSE;
}
/*
 * Clear the viewport
 */
rval = myglobs.view->Clear();
if (rval != D3DRM_OK) {
    Msg("Clearing viewport failed.\n%s", D3DAppErrorToString(rval));
    return FALSE;
}
/*
 * Render the scene to the viewport
 */
rval = myglobs.view->Render(myglobs.scene);
if (rval != D3DRM_OK) {
    Msg("Rendering scene failed.\n%s", D3DAppErrorToString(rval));
    return FALSE;
}
/*
 * Blt the frame rate and window stat text to the back buffer
 */
count = 0;
        if (!DisplayFrameRate(&count, &extents[0]))
    return FALSE;
for (;count;--count)
    myglobs.view->ForceUpdate(extents[count-1].x1, extents[count-1].y1,
      extents[count-1].x2, extents[count-1].y2);
/*
 * Update the window
 */
rval = myglobs.dev->Update();
if (rval != D3DRM_OK) {
    Msg("Updating device failed.\n%s", D3DAppErrorToString(rval));
    return FALSE;
}
/*
 * Blt or flip the back buffer to the front buffer.  If this fails,
 * don't report an error.
 */
D3DAppShowBackBuffer(myglobs.bResized ? D3DAPP_SHOWALL : NULL);

/*
 * Reset the resize flag
 */
        myglobs.bResized = FALSE;
    }
    return TRUE;
}

/*
 * AppPause
 * Pause and unpause the application
 */
static BOOL
AppPause(BOOL f)
{
    /*
     * Flip to the GDI surface and halt rendering
     */
    if (!D3DAppPause(f))
return FALSE;
    /*
     * When returning from a pause, reset the frame rate count
     */
    if (!f) {
        ResetFrameRate();
myglobs.bResized = TRUE;
    }
    return TRUE;
}

/*
 * RestoreSurfaces
 * Restores any lost surfaces.  Returns TRUE if all surfaces are not lost and
 * FALSE if one or more surfaces is lost and can not be restored at the
 * moment.
 */
static BOOL
RestoreSurfaces()
{
    HRESULT d3drval;

    /*
     * Have D3DApp check all the surfaces it's in charge of
     */
    if (!D3DAppCheckForLostSurfaces()) {
            return FALSE;
    }
    /*
     * Check frame rate and info surfaces and re-write them if they
     * were lost.
     */
    if (myglobs.lpFrameRateBuffer->IsLost() == DDERR_SURFACELOST) {
        d3drval = myglobs.lpFrameRateBuffer->Restore();
        if (d3drval != DD_OK) {
            return FALSE;
        }
if (!WriteFrameRateBuffer(0.0f, 0))
    return FALSE;
    }
    if (myglobs.lpInfoBuffer->IsLost() == DDERR_SURFACELOST) {
        d3drval = myglobs.lpInfoBuffer->Restore();
        if (d3drval != DD_OK) {
            return FALSE;
        }
if (!WriteInfoBuffer())
    return FALSE;
    }
    return TRUE;
}

/*************************************************************************
  Windows message handlers
 *************************************************************************/
/*
 * AppAbout
 * About box message handler
 */
BOOL
FAR PASCAL AppAbout(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/*
 * WindowProc
 * Main window message handler.
 */
long
FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam,
   LPARAM lParam )
{
    int i;
    BOOL bStop;
    LRESULT lresult;

    /*
     * Give D3DApp an opportunity to process any messages it MUST see in order
     * to perform it's function.
     */
    if (!D3DAppWindowProc(&bStop, &lresult, hWnd, message, wParam, lParam)) {
ReportD3DAppError();
CleanUpAndPostQuit();
return 0;
    }
    /*
     * If bStop is set by D3DApp, the app should not process the message but
     * return lresult.
     */
    if (bStop)
return lresult;

    if (!d3dapp || !d3dapp->bRenderingIsOK) {
return DefWindowProc(hWnd, message, wParam, lParam);
    }

    switch( message ) {
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MOUSEMOVE:
    /*
     * Record the mouse state for ReadMouse
     */
    myglobs.mouse_buttons = wParam;
    myglobs.mouse_x = LOWORD(lParam);
    myglobs.mouse_y = HIWORD(lParam);
    break;
        case WM_ENTERMENULOOP:
            AppPause(TRUE);
            break;
        case WM_EXITMENULOOP:
            AppPause(FALSE);
            break;
    case WM_DESTROY:
            myglobs.hWndMain = NULL;
            CleanUpAndPostQuit();
            break;
        case WM_INITMENUPOPUP:
    /*
             * Check and enable the appropriate menu items
     */
            CheckMenuItem((HMENU)wParam, MENU_STEP, (myglobs.bSingleStepMode) ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wParam, MENU_GO, (myglobs.bSingleStepMode) ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem((HMENU)wParam, MENU_PHONG, MF_GRAYED);
    if (!myglobs.bConstRenderQuality) {
CheckMenuItem((HMENU)wParam, MENU_LIGHTING, (myglobs.RenderQuality & D3DRMLIGHT_MASK) == D3DRMLIGHT_ON ? MF_CHECKED : MF_GRAYED);
CheckMenuItem((HMENU)wParam, MENU_FLAT, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_FLAT ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem((HMENU)wParam, MENU_GOURAUD, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_GOURAUD ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem((HMENU)wParam, MENU_PHONG, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_PHONG ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_PHONG, MF_GRAYED);
CheckMenuItem((HMENU)wParam, MENU_POINT, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_POINTS ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem((HMENU)wParam, MENU_WIREFRAME, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_WIREFRAME ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem((HMENU)wParam, MENU_SOLID, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_SOLID ? MF_CHECKED : MF_UNCHECKED);
    } else {
EnableMenuItem((HMENU)wParam, MENU_LIGHTING, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_FLAT, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_GOURAUD, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_PHONG, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_POINT, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_WIREFRAME, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_SOLID, MF_GRAYED);
    }
            if (!myglobs.bNoTextures && d3dapp->ThisDriver.bDoesTextures) {
CheckMenuItem((HMENU)wParam, MENU_POINT_FILTER, (myglobs.TextureQuality == D3DRMTEXTURE_NEAREST) ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem((HMENU)wParam, MENU_LINEAR_FILTER, (myglobs.TextureQuality == D3DRMTEXTURE_LINEAR) ? MF_CHECKED : MF_UNCHECKED);
    } else {
EnableMenuItem((HMENU)wParam, MENU_POINT_FILTER, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_LINEAR_FILTER, MF_GRAYED);
    }
            CheckMenuItem((HMENU)wParam, MENU_DITHERING, (myglobs.bDithering) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_ANTIALIAS, (myglobs.bAntialiasing) ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wParam, MENU_ANTIALIAS, MF_GRAYED);
    if (d3dapp->bIsPrimary) {
CheckMenuItem((HMENU)wParam, MENU_FULLSCREEN, d3dapp->bFullscreen ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_FULLSCREEN, d3dapp->bFullscreen && !d3dapp->ThisDriver.bCanDoWindow ? MF_GRAYED : MF_ENABLED);
EnableMenuItem((HMENU)wParam, MENU_NEXT_MODE, (!d3dapp->bFullscreen) ? MF_GRAYED : MF_ENABLED);
EnableMenuItem((HMENU)wParam, MENU_PREVIOUS_MODE, (!d3dapp->bFullscreen) ? MF_GRAYED : MF_ENABLED);
    } else {
EnableMenuItem((HMENU)wParam, MENU_FULLSCREEN, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_NEXT_MODE, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_PREVIOUS_MODE, MF_GRAYED);
    }
    for (i = 0; i < d3dapp->NumModes; i++) {
CheckMenuItem((HMENU)wParam, MENU_FIRST_MODE + i, (i == d3dapp->CurrMode) ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_FIRST_MODE + i, (d3dapp->Mode[i].bThisDriverCanDo) ? MF_ENABLED : MF_GRAYED);
    }
            for (i = 0; i < d3dapp->NumDrivers; i++) {
                CheckMenuItem((HMENU)wParam, MENU_FIRST_DRIVER + i, (i == d3dapp->CurrDriver) ? MF_CHECKED : MF_UNCHECKED);
            }
            break;
        case WM_COMMAND:
            switch(LOWORD(wParam)) {
                case MENU_ABOUT:
                    AppPause(TRUE);
                    DialogBox(myglobs.hInstApp, "AppAbout", myglobs.hWndMain, (DLGPROC)AppAbout);
                    AppPause(FALSE);
                    break;
                case MENU_EXIT:
            CleanUpAndPostQuit();
            break;
                case MENU_STEP:
    /*
     * Begin single step more or draw a frame if in single
     * step mode
     */
                    if (!myglobs.bSingleStepMode) {
                        myglobs.bSingleStepMode = TRUE;
                        myglobs.bDrawAFrame = TRUE;
                    } else if (!myglobs.bDrawAFrame) {
                        myglobs.bDrawAFrame = TRUE;
                    }
                    break;
                case MENU_GO:
    /*
     * Exit single step mode
     */
                    if (myglobs.bSingleStepMode) {
                        myglobs.bSingleStepMode = FALSE;
                        ResetFrameRate();
                    }
                    break;
                case MENU_STATS:
    /*
     * Toggle output of frame rate and window info
     */
                    if ((myglobs.bShowFrameRate) && (myglobs.bShowInfo)) {
                        myglobs.bShowFrameRate = FALSE;
                        myglobs.bShowInfo = FALSE;
                        break;
                    }
                    if ((!myglobs.bShowFrameRate) && (!myglobs.bShowInfo)) {
                        myglobs.bShowFrameRate = TRUE;
                        break;
                    }
                    myglobs.bShowInfo = TRUE;
                    break;
        case MENU_FULLSCREEN:
    if (d3dapp->bFullscreen) {
/*
 * Return to a windowed mode.  Let D3DApp decide which
 * D3D driver to use in case this one cannot render to
 * the Windows display depth
 */
if (!D3DAppWindow(D3DAPP_YOUDECIDE, D3DAPP_YOUDECIDE)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
    break;
}
    } else {
/*
 * Enter the current fullscreen mode.  D3DApp may
 * resort to another mode if this driver cannot do
 * the currently selected mode.
 */
if (!D3DAppFullscreen(d3dapp->CurrMode)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
    break;
}
    }
                break;
/*
 * Lighting toggle
 */
case MENU_LIGHTING:
    myglobs.RenderQuality ^= D3DRMLIGHT_ON;
    SetRenderState();
    break;
/*
 * Fill mode selection
 */
case MENU_POINT:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_POINTS;
    SetRenderState();
    break;
case MENU_WIREFRAME:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_WIREFRAME;
    SetRenderState();
    break;
case MENU_SOLID:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_SOLID;
    SetRenderState();
    break;
/*
 * Shade mode selection
 */
case MENU_FLAT:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_FLAT;
    SetRenderState();
    break;
case MENU_GOURAUD:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_GOURAUD;
    SetRenderState();
    break;
case MENU_PHONG:
    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_PHONG;
    SetRenderState();
    break;

case MENU_DITHERING:
    myglobs.bDithering = !myglobs.bDithering;
    SetRenderState();
    break;
case MENU_ANTIALIAS:
    myglobs.bAntialiasing = !myglobs.bAntialiasing;
    SetRenderState();
    break;
/*
 * Texture filter selection
 */
case MENU_POINT_FILTER:
    if (myglobs.TextureQuality == D3DRMTEXTURE_NEAREST)
break;
    myglobs.TextureQuality = D3DRMTEXTURE_NEAREST;
    SetRenderState();
    break;
case MENU_LINEAR_FILTER:
    if (myglobs.TextureQuality == D3DRMTEXTURE_LINEAR)
break;
    myglobs.TextureQuality = D3DRMTEXTURE_LINEAR;
    SetRenderState();

break;
case MENU_NEXT_MODE:
    /*
     * Enter the next usable fullscreen mode
     */
    i = d3dapp->CurrMode;
    do {
++i;
if (i >= d3dapp->NumModes)
    i = 0;
if (!d3dapp->Mode[i].bThisDriverCanDo)
    continue;
else {
    if (!D3DAppFullscreen(i)) {
ReportD3DAppError();
CleanUpAndPostQuit();
    }
    break;
}
    } while(i != d3dapp->CurrMode);
    break;
case MENU_PREVIOUS_MODE:
    /*
     * Enter the previous usable fullscreen mode
     */
    i = d3dapp->CurrMode;
    do {
--i;
if (i < 0)
    i = d3dapp->NumModes - 1;
if (!d3dapp->Mode[i].bThisDriverCanDo)
    continue;
else {
    if (!D3DAppFullscreen(i)) {
ReportD3DAppError();
CleanUpAndPostQuit();
    }
    break;
}
    } while(i != d3dapp->CurrMode);
                    break;
            }
            if (   LOWORD(wParam) >= MENU_FIRST_DRIVER
&& LOWORD(wParam) < MENU_FIRST_DRIVER + D3DAPP_MAXD3DDRIVERS
&& d3dapp->CurrDriver != LOWORD(wParam) - MENU_FIRST_DRIVER) {
/*
 * Change the D3D driver
 */
if (!D3DAppChangeDriver(LOWORD(wParam) - MENU_FIRST_DRIVER,
NULL)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
}
            }
            if (   LOWORD(wParam) >= MENU_FIRST_MODE
&& LOWORD(wParam) < MENU_FIRST_MODE+100) {
/*
 * Switch to the selected fullscreen mode
 */
if (!D3DAppFullscreen(LOWORD(wParam) - MENU_FIRST_MODE)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
}
            }
            if (   LOWORD(wParam) >= MENU_FIRST_DDDRIVER
&& LOWORD(wParam) < MENU_FIRST_DDDRIVER + D3DAPP_MAXDDDRIVERS
&& myglobs.CurrDDDriver != LOWORD(wParam) - MENU_FIRST_DDDRIVER) {
/*
 * Change the DD driver
 */
DestroyD3DApp();
myglobs.CurrDDDriver = LOWORD(wParam) - MENU_FIRST_DDDRIVER;
SetWindowPos(myglobs.hWndMain, HWND_NOTOPMOST, 0, 0, START_WIN_SIZE, START_WIN_SIZE, SWP_NOMOVE | SWP_SHOWWINDOW);
if (!CreateD3DApp(myglobs.lpCmdLine))
    return FALSE;
            }
    /*
     * Whenever we receive a command in single step mode, draw a frame
     */
    if (myglobs.bSingleStepMode)
myglobs.bDrawAFrame = TRUE;
            return 0L;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

/****************************************************************************/
/*                          Additional Functions                            */
/****************************************************************************/
/*
 * ReadMouse
 * Returns the mouse status for interaction with sample code
 */
void
ReadMouse(int* b, int* x, int* y)
{
    *b = myglobs.mouse_buttons;
    *x = myglobs.mouse_x;
    *y = myglobs.mouse_y;
}

/*
 * SetRenderState
 * Set the render quality, dither toggle and shade info if any of them has
 * changed
 */
BOOL
SetRenderState(void)
{
    HRESULT rval;
    /*
     * Set the number of buffers so D3DRM can keep track of extents properly
     */
    rval = myglobs.dev->SetBufferCount(d3dapp->bFullscreen &&
       d3dapp->bBackBufferInVideo ? 2 : 1);
    if (rval != D3DRM_OK) {
Msg("Setting the buffer count failed.\n%s", D3DAppErrorToString(rval));
return FALSE;
    }
    /*
     * Set the render quality (light toggle, fill mode, shade mode)
     */
    if (myglobs.dev->GetQuality() != myglobs.RenderQuality) {
rval = myglobs.dev->SetQuality(myglobs.RenderQuality);
if (rval != D3DRM_OK) {
    Msg("Setting the render quality failed.\n%s",
D3DAppErrorToString(rval));
    return FALSE;
}
    }
    /*
     * Set dithering toggle
     */
    if (myglobs.dev->GetDither() != myglobs.bDithering) {
rval = myglobs.dev->SetDither(myglobs.bDithering);
if (rval != D3DRM_OK) {
    Msg("Setting dither mode failed.\n%s", D3DAppErrorToString(rval));
    return FALSE;
}
    }
    /*
     * Set the texture quality (point or linear filtering)
     */
    if (myglobs.dev->GetTextureQuality() != myglobs.TextureQuality) {
rval = myglobs.dev->SetTextureQuality(myglobs.TextureQuality);
if (rval != D3DRM_OK) {
    Msg("Setting texture quality failed.\n%s",
D3DAppErrorToString(rval));
    return FALSE;
}
    }
    /*
     * Set shade info based on current bits per pixel
     */
    switch (d3dapp->ThisMode.bpp) {
case 1:
    if (FAILED(myglobs.dev->SetShades(4)))
goto shades_error;
    if (FAILED(lpD3DRM->SetDefaultTextureShades(4)))
goto shades_error;
    break;
case 16:
    if (FAILED(myglobs.dev->SetShades(32)))
goto shades_error;
    if (FAILED(lpD3DRM->SetDefaultTextureColors(64)))
goto shades_error;
    if (FAILED(lpD3DRM->SetDefaultTextureShades(32)))
goto shades_error;
    break;
case 24:
case 32:
    if (FAILED(myglobs.dev->SetShades(256)))
goto shades_error;
    if (FAILED(lpD3DRM->SetDefaultTextureColors(64)))
goto shades_error;
    if (FAILED(lpD3DRM->SetDefaultTextureShades(256)))
goto shades_error;
    break;
    }
    return TRUE;
shades_error:
    Msg("A failure occurred while setting color shade information.\n");
    return FALSE;
}

/****************************************************************************/
/*          Initialization, error reporting and release functions.          */
/****************************************************************************/
/*
 * InitGlobals
 * Called once at program initialization to initialize global variables.
 */
static void
InitGlobals(void)
{
    d3dapp = NULL;
    memset(&myglobs, 0, sizeof(myglobs));
    myglobs.bShowFrameRate = TRUE;
    myglobs.bShowInfo = TRUE;
    myglobs.RenderQuality = D3DRMLIGHT_ON | D3DRMFILL_SOLID |
    D3DRMSHADE_GOURAUD;
    myglobs.TextureQuality = D3DRMTEXTURE_NEAREST;
}

/*
 * CleanUpAndPostQuit
 * Release all D3D objects, post a quit message and set the bQuit flag
 */
void
CleanUpAndPostQuit(void)
{
    if (myglobs.bQuit)
return;
    if (!D3DAppDestroy())
ReportD3DAppError();
    RELEASE(myglobs.scene);
    RELEASE(myglobs.camera);
    RELEASE(lpD3DRM);
    myglobs.bQuit = TRUE;
    PostQuitMessage( 0 );
}

/*
 * ReportD3DAppError
 * Reports an error during a d3d app call.
 */
void
ReportD3DAppError(void)
{
    Msg("%s", D3DAppLastErrorString());
}

/* Msg
 * Message output for error notification.
 */
void __cdecl
Msg( LPSTR fmt, ... )
{
    char buff[256];
    va_list args;

    va_start(args, fmt);
    wvsprintf(buff, fmt, args);
    va_end(args);

    lstrcat(buff, "\r\n");
    AppPause(TRUE);
    if (d3dapp && d3dapp->bFullscreen)
SetWindowPos(myglobs.hWndMain, HWND_NOTOPMOST, 0, 0, 0, 0,
     SWP_NOSIZE | SWP_NOMOVE);
    MessageBox( NULL, buff, "D3D Example Message", MB_OK );
    if (d3dapp && d3dapp->bFullscreen)
SetWindowPos(myglobs.hWndMain, HWND_TOPMOST, 0, 0, 0, 0,
     SWP_NOSIZE | SWP_NOMOVE);
    AppPause(FALSE);
}

/*
 * D3DRMErrorToString
 * Allows the samples to return error strings.
 */
char*
D3DRMErrorToString(HRESULT error)
{
    return D3DAppErrorToString(error);
}


RMFULL.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmfull.h
 *
 */
#ifndef __RMFULL_H__
#define __RMFULL_H__

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <search.h>
#include <ddraw.h>
#include <d3d.h>
#include <direct.h>
#include <d3drmwin.h>
#include "d3dapp.h"    /* prototypes for D3D helper functions */
#include "rmdemo.h"    /* prototypes for functions to commumicate
       with each sample */
#include "rmfullrc.h"    /* defines constants used in rmfull.rc */
#define START_WIN_SIZE 320  /* initial size of the window */

#ifdef __cplusplus
extern "C" {
#endif

typedef struct tagrmfullglobals {
    HWND hWndMain;    /* application window handle */
    HINSTANCE hInstApp;    /* application instance for dialog boxes */
    LPSTR lpCmdLine;    /* command line parameters */

    LPDIRECT3DRMDEVICE dev;/* Direct3DRM device */
    LPDIRECT3DRMVIEWPORT view;/* Direct3DRM viewport through which we view
   the scene */
    LPDIRECT3DRMFRAME scene;    /* Master frame in which others are placed */
    LPDIRECT3DRMFRAME camera;/* Frame describing the users POV */

    BOOL bSingleStepMode; /* render one frame at a time */
    BOOL bDrawAFrame; /* render on this pass of the main loop */
    BOOL bShowFrameRate; /* show the frame rate at the top */
    BOOL bShowInfo; /* show window information at the bottom */

    BOOL bResized; /* the window has resized or some other drastic change, the
                      entire client area should be cleared */
    BOOL bQuit;   /* program is about to terminate */

    BOOL bNoTextures;/* this sample doesn't use any textures */
    BOOL bConstRenderQuality;/* this sample is not constructed with
   MeshBuilders and so the RenderQuality
   cannot be changed */
    D3DRMRENDERQUALITY RenderQuality;/* current shade mode, fill mode and
   lighting state */
    D3DRMTEXTUREQUALITY TextureQuality; /* current texture interpolation */
    BOOL bDithering;/* is dithering on? */
    BOOL bAntialiasing;/* is antialiasing on? */

    int mouse_buttons;/* mouse button state */
    int mouse_x;/* mouse cursor x position */
    int mouse_y;/* mouse cursor y position */

    int    CurrDDDriver;   /* Current DirectDraw driver */
    int    NumDDDrivers;   /* Number of DirectDraw drivers */
    D3DAppDDDriver  DDDriver[D3DAPP_MAXDDDRIVERS]; /* DirectDraw drivers information */

    LPDIRECTDRAWSURFACE lpFrameRateBuffer; /* frame rate surface */
    LPDIRECTDRAWSURFACE lpInfoBuffer;   /* window info surface */
} rmfullglobals;

void __cdecl Msg( LPSTR fmt, ... );
/*
 * STATS.CPP FUNCTION PROTOTYPES
 */
BOOL InitFontAndTextBuffers(void);
BOOL WriteInfoBuffer(void);
BOOL WriteFrameRateBuffer(float fps, long tps);
void ResetFrameRate(void);
BOOL CalculateFrameRate();
BOOL DisplayFrameRate(int* count, LPD3DRECT lpExtents );

#ifdef __cplusplus
};
#endif

#endif // __RMFULL_H__


RMFULLRC.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmfullrc.h
 *
 ***************************************************************************/

#define MENU_ABOUT                      1
#define MENU_EXIT                       2
#define MENU_FULLSCREEN                 7
#define MENU_NEXT_MODE                  8
#define MENU_PREVIOUS_MODE              9
#define MENU_STATS                      10
#define MENU_FLAT                       12
#define MENU_GOURAUD                    13
#define MENU_PHONG                      14
#define MENU_POINT_FILTER               17
#define MENU_LINEAR_FILTER              18
#define MENU_POINT                      20
#define MENU_WIREFRAME                  21
#define MENU_SOLID                      22
#define MENU_DITHERING                  24
#define MENU_ANTIALIAS                  26
#define MENU_STEP                       27
#define MENU_GO                         28
#define MENU_LIGHTING29
#define MENU_FIRST_DRIVER               80
#define MENU_FIRST_MODE                 100
#define MENU_FIRST_DDDRIVER200

#define IDC_STATIC                     -1


RMMAIN.CPP

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmmain.cpp
 *
 *  Each of the Direct3D retained mode (D3DRM) samples must be linked with
 *  this file.  It contains the code which allows them to run in the Windows
 *  environment.
 *
 *  A window is created using the rmmain.res which allows the user to select
 *  the Direct3D driver to use and change the render options.
 *
 *  Individual samples are executed through two functions, BuildScene and
 *  OverrideDefaults, as described in rmdemo.h.  Samples can also read
 *  mouse input via ReadMouse.
 */

#define INITGUID

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <direct.h>
#include <d3drmwin.h>
#include "rmdemo.h"             /* prototypes for functions to commumicate
                                   with each sample */
#include "rmmain.h"             /* defines constants used in rmmain.rc */
#include "rmerror.h"            /* prototypes for error reporting: error.c */

#define MAX_DRIVERS 5           /* maximum D3D drivers we ever expect to find */

/*
 * GLOBAL VARIABLES
 */
LPDIRECT3DRM lpD3DRM;           /* Direct3DRM object */
LPDIRECTDRAWCLIPPER lpDDClipper;/* DirectDrawClipper object */

struct _myglobs {
    LPDIRECT3DRMDEVICE dev;     /* Direct3DRM device */
    LPDIRECT3DRMVIEWPORT view;  /* Direct3DRM viewport through which we view
                                   the scene */
    LPDIRECT3DRMFRAME scene;    /* Master frame in which others are placed */
    LPDIRECT3DRMFRAME camera;   /* Frame describing the users POV */

    GUID DriverGUID[MAX_DRIVERS];     /* GUIDs of the available D3D drivers */
    char DriverName[MAX_DRIVERS][50]; /* names of the available D3D drivers */
    int  NumDrivers;                  /* number of available D3D drivers */
    int  CurrDriver;                  /* number of D3D driver currently
                                         being used */

    D3DRMRENDERQUALITY RenderQuality;   /* current shade mode, fill mode and
                                           lighting state */
    D3DRMTEXTUREQUALITY TextureQuality; /* current texture interpolation */
    BOOL bDithering;                    /* is dithering on? */
    BOOL bAntialiasing;                 /* is antialiasing on? */

    BOOL bQuit;                 /* program is about to terminate */
    BOOL bInitialized;          /* all D3DRM objects have been initialized */
    BOOL bMinimized;            /* window is minimized */
    BOOL bSingleStepMode;       /* render one frame at a time */
    BOOL bDrawAFrame;           /* render on this pass of the main loop */
    BOOL bNoTextures;           /* this sample doesn't use any textures */
    BOOL bConstRenderQuality;   /* this sample is not constructed with
                                   MeshBuilders and so the RenderQuality
                                   cannot be changed */

    int BPP;                    /* bit depth of the current display mode */

    int mouse_buttons;          /* mouse button state */
    int mouse_x;                /* mouse cursor x position */
    int mouse_y;                /* mouse cursor y position */
} myglobs;

/*
 * PROTOTYPES
 */
static HWND InitApp(HINSTANCE, int);
static void InitGlobals(void);
long FAR PASCAL WindowProc(HWND, UINT, WPARAM, LPARAM);
static HRESULT CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, int driver, int width, int height);
static BOOL RenderLoop(void);
static void CleanUpAndPostQuit(void);
static HRESULT SetRenderState(void);
static BOOL EnumDevices(HWND win);
extern "C" void ReadMouse(int*, int*, int*);

/****************************************************************************/
/*                               WinMain                                    */
/****************************************************************************/
/*
 * Initializes the application then enters a message loop which renders the
 * scene until a quit message is received.
 */
int PASCAL
WinMain (HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, int cmdshow)
{
    HWND    hwnd;
    MSG     msg;
    HACCEL  accel;
    int     failcount = 0;  /* number of times RenderLoop has failed */

    prev_inst;
    cmdline;

    /*
     * Create the window and initialize all objects needed to begin rendering
     */
    if (!(hwnd = InitApp(this_inst, cmdshow)))
        return 1;

    accel = LoadAccelerators(this_inst, "AppAccel");

    while (!myglobs.bQuit) {
        /*
         * Monitor the message queue until there are no pressing
         * messages
         */
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                CleanUpAndPostQuit();
                break;
            }
            if (!TranslateAccelerator(msg.hwnd, accel, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
if (myglobs.bQuit)
    break;
        /*
         * If the app is not minimized, not about to quit and D3DRM has
         * been initialized, we can render
         */
        if (!myglobs.bMinimized && !myglobs.bQuit && myglobs.bInitialized) {
            /*
             * If were are not in single step mode or if we are and the
             * bDrawAFrame flag is set, render one frame
             */
            if (!(myglobs.bSingleStepMode && !myglobs.bDrawAFrame)) {
                /*
                 * Attempt to render a frame, if it fails, take a note.  If
                 * rendering fails more than twice, abort execution.
                 */
                if (!RenderLoop())
                    ++failcount;
                if (failcount > 2) {
                    Msg("Rendering has failed too many times.  Aborting execution.\n");
                    CleanUpAndPostQuit();
                    break;
                }
            }
            /*
             * Reset the bDrawAFrame flag if we are in single step mode
             */
            if (myglobs.bSingleStepMode)
                myglobs.bDrawAFrame = FALSE;
        } else {
    WaitMessage();
}
    }
    DestroyWindow(hwnd);
    return msg.wParam;
}

/****************************************************************************/
/*                   Initialization and object creation                     */
/****************************************************************************/
/*
 * InitApp
 * Creates window and initializes all objects neccessary to begin rendering
 */
static HWND
InitApp(HINSTANCE this_inst, int cmdshow)
{
    HWND win;
    HDC hdc;
    DWORD flags;
    WNDCLASS wc;
    Defaults defaults;
    HRESULT rval;
    RECT rc;

    /*
     * set up and registers the window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = this_inst;
    wc.hIcon = LoadIcon(this_inst, "AppIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = "AppMenu";
    wc.lpszClassName = "D3DRM Example";
    if (!RegisterClass(&wc))
        return FALSE;
    /*
     * Initialize the global variables and allow the sample code to override
     * some of these default settings.
     */
    InitGlobals();
    defaults.bNoTextures = myglobs.bNoTextures;
    defaults.bConstRenderQuality = myglobs.bConstRenderQuality;
    defaults.bResizingDisabled = FALSE;
    lstrcpy(defaults.Name, "D3DRM Example");
    OverrideDefaults(&defaults);
    myglobs.bNoTextures = defaults.bNoTextures;
    myglobs.bConstRenderQuality = defaults.bConstRenderQuality;
    /*
     * Create the window
     */
    if (defaults.bResizingDisabled)
        flags =  WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                 WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    else
        flags = WS_OVERLAPPEDWINDOW;
    win =
        CreateWindow
        (   "D3DRM Example",            /* class */
            defaults.Name,              /* caption */
            flags,                      /* style */
            CW_USEDEFAULT,              /* init. x pos */
            CW_USEDEFAULT,              /* init. y pos */
            300,                        /* init. x size */
            300,                        /* init. y size */
            NULL,                       /* parent window */
            NULL,                       /* menu handle */
            this_inst,                  /* program handle */
            NULL                        /* create parms */
        );
    if (!win)
        return FALSE;
    /*
     * Record the current display BPP
     */
    hdc = GetDC(win);
    myglobs.BPP = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(win, hdc);
    /*
     * Enumerate the D3D drivers and select one
     */
    if (!EnumDevices(win))
        return FALSE;
    /*
     * Create the D3DRM object and the D3DRM window object
     */
    rval = Direct3DRMCreate(&lpD3DRM);
    if (rval != D3DRM_OK) {
        Msg("Failed to create Direct3DRM.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Create the master scene frame and camera frame
     */
    rval = lpD3DRM->CreateFrame(NULL, &myglobs.scene);
    if (rval != D3DRM_OK) {
        Msg("Failed to create the master scene frame.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    rval = lpD3DRM->CreateFrame(myglobs.scene, &myglobs.camera);
    if (rval != D3DRM_OK) {
        Msg("Failed to create the camera's frame.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    rval = myglobs.camera->SetPosition(myglobs.scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
    if (rval != D3DRM_OK) {
        Msg("Failed to position the camera in the frame.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Create a clipper and associate the window with it
     */
    rval = DirectDrawCreateClipper(0, &lpDDClipper, NULL);
    if (rval != DD_OK) {
        Msg("Failed to create DirectDrawClipper");
        return FALSE;
    }
    rval = lpDDClipper->SetHWnd(0, win);
    if (rval != DD_OK) {
        Msg("Failed to set hwnd on the clipper");
        return FALSE;
    }
    /*
     * Created the D3DRM device with the selected D3D driver
     */
    GetClientRect(win, &rc);
    if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver, rc.right, rc.bottom)) {
myglobs.CurrDriver = 0;
if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver, rc.right, rc.bottom)) {
    Msg("Failed to create the D3DRM device.\n%s", D3DRMErrorToString(rval));
    return FALSE;
}
    }
    /*
     * Create the scene to be rendered by calling this sample's BuildScene
     */
    if (!BuildScene(myglobs.dev, myglobs.view, myglobs.scene, myglobs.camera))
        return FALSE;
    /*
     * Display the window
     */
    ShowWindow(win, cmdshow);
    UpdateWindow(win);
    /*
     * Now we are ready to render
     */
    myglobs.bInitialized = TRUE;

    return win;
}

/*
 * CreateDevAndView
 * Create the D3DRM device and viewport with the given D3D driver and of the
 * specified size.
 */
static HRESULT
CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, int driver, int width, int height)
{
    HRESULT rval;

    if (!width || !height)
        return D3DRMERR_BADVALUE;
    /*
     * Create the D3DRM device from this window and using the specified D3D
     * driver.
     */
    rval = lpD3DRM->CreateDeviceFromClipper(lpDDClipper, &myglobs.DriverGUID[driver],
                                        width, height, &myglobs.dev);
    if (rval != D3DRM_OK)
        return rval;
    /*
     * Create the D3DRM viewport using the camera frame.  Set the background
     * depth to a large number.  The width and height may be slightly
     * adjusted, so get them from the device to be sure.
     */
    width = myglobs.dev->GetWidth();
    height = myglobs.dev->GetHeight();
    rval = lpD3DRM->CreateViewport(myglobs.dev, myglobs.camera, 0, 0, width,
                                   height, &myglobs.view);
    if (rval != D3DRM_OK) {
myglobs.bInitialized = FALSE;
        RELEASE(myglobs.dev);
        return rval;
    }
    rval = myglobs.view->SetBack(D3DVAL(5000.0));
    if (rval != D3DRM_OK) {
myglobs.bInitialized = FALSE;
        RELEASE(myglobs.dev);
        RELEASE(myglobs.view);
        return rval;
    }
    /*
     * Set the render quality, fill mode, lighting state and color shade info
     */
    if (rval = SetRenderState()) {
myglobs.bInitialized = FALSE;
        RELEASE(myglobs.dev);
        RELEASE(myglobs.view);
        return rval;
    }

    return D3DRM_OK;
}

/****************************************************************************/
/*                         D3D Device Enumeration                           */
/****************************************************************************/
/*
 * BPPToDDBD
 * Converts bits per pixel to a DirectDraw bit depth flag
 */
static DWORD
BPPToDDBD(int bpp)
{
    switch(bpp) {
        case 1:
            return DDBD_1;
        case 2:
            return DDBD_2;
        case 4:
            return DDBD_4;
        case 8:
            return DDBD_8;
        case 16:
            return DDBD_16;
        case 24:
            return DDBD_24;
        case 32:
            return DDBD_32;
        default:
            return 0;
    }
}

/*
 * enumDeviceFunc
 * Callback function which records each usable D3D driver's name and GUID
 * Chooses a driver to begin with and sets *lpContext to this starting driver
 */
static HRESULT
WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription, LPSTR lpDeviceName,
        LPD3DDEVICEDESC lpHWDesc, LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext)
{
    static BOOL hardware = FALSE; /* current start driver is hardware */
    static BOOL mono = FALSE;     /* current start driver is mono light */
    LPD3DDEVICEDESC lpDesc;
    int *lpStartDriver = (int *)lpContext;
    /*
     * Decide which device description we should consult
     */
    lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;
    /*
     * If this driver cannot render in the current display bit depth skip
     * it and continue with the enumeration.
     */
    if (!(lpDesc->dwDeviceRenderBitDepth & BPPToDDBD(myglobs.BPP)))
        return D3DENUMRET_OK;
    /*
     * Record this driver's info
     */
    memcpy(&myglobs.DriverGUID[myglobs.NumDrivers], lpGuid, sizeof(GUID));
    lstrcpy(&myglobs.DriverName[myglobs.NumDrivers][0], lpDeviceName);
    /*
     * Choose hardware over software, RGB lights over mono lights
     */
    if (*lpStartDriver == -1) {
        /*
         * this is the first valid driver
         */
        *lpStartDriver = myglobs.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if (lpDesc == lpHWDesc && !hardware) {
        /*
         * this driver is hardware and start driver is not
         */
        *lpStartDriver = myglobs.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if ((lpDesc == lpHWDesc && hardware ) || (lpDesc == lpHELDesc
                                                     && !hardware)) {
        if (lpDesc->dcmColorModel == D3DCOLOR_MONO && !mono) {
            /*
             * this driver and start driver are the same type and this
             * driver is mono while start driver is not
             */
            *lpStartDriver = myglobs.NumDrivers;
            hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
            mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
        }
    }
    myglobs.NumDrivers++;
    if (myglobs.NumDrivers == MAX_DRIVERS)
        return (D3DENUMRET_CANCEL);
    return (D3DENUMRET_OK);
}

/*
 * EnumDevices
 * Enumerate the available D3D drivers, add them to the file menu, and choose
 * one to use.
 */
static BOOL
EnumDevices(HWND win)
{
    LPDIRECTDRAW lpDD;
    LPDIRECT3D lpD3D;
    HRESULT rval;
    HMENU hmenu;
    int i;

    /*
     * Create a DirectDraw object and query for the Direct3D interface to use
     * to enumerate the drivers.
     */
    rval = DirectDrawCreate(NULL, &lpDD, NULL);
    if (rval != DD_OK) {
        Msg("Creation of DirectDraw HEL failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    rval = lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D);
    if (rval != DD_OK) {
        Msg("Creation of Direct3D interface failed.\n%s", D3DRMErrorToString(rval));
        lpDD->Release();
        return FALSE;
    }
    /*
     * Enumerate the drivers, setting CurrDriver to -1 to initialize the
     * driver selection code in enumDeviceFunc
     */
    myglobs.CurrDriver = -1;
    rval = lpD3D->EnumDevices(enumDeviceFunc, &myglobs.CurrDriver);
    if (rval != DD_OK) {
        Msg("Enumeration of drivers failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Make sure we found at least one valid driver
     */
    if (myglobs.NumDrivers == 0) {
        Msg("Could not find a D3D driver which is compatible with this display depth");
        return FALSE;
    }
    lpD3D->Release();
    lpDD->Release();
    /*
     * Add the driver names to the File menu
     */
    hmenu = GetSubMenu(GetMenu(win), 0);
    for (i = 0; i < myglobs.NumDrivers; i++) {
        InsertMenu(hmenu, 5 + i, MF_BYPOSITION | MF_STRING, MENU_FIRST_DRIVER + i,
                   myglobs.DriverName[i]);
    }
    return TRUE;
}

/****************************************************************************/
/*                             Render Loop                                  */
/****************************************************************************/
/*
 * Clear the viewport, render the next frame and update the window
 */
static BOOL
RenderLoop()
{
    HRESULT rval;
    /*
     * Tick the scene
     */
    rval = myglobs.scene->Move(D3DVAL(1.0));
    if (rval != D3DRM_OK) {
        Msg("Moving scene failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Clear the viewport
     */
    rval = myglobs.view->Clear();
    if (rval != D3DRM_OK) {
        Msg("Clearing viewport failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Render the scene to the viewport
     */
    rval = myglobs.view->Render(myglobs.scene);
    if (rval != D3DRM_OK) {
        Msg("Rendering scene failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Update the window
     */
    rval = myglobs.dev->Update();
    if (rval != D3DRM_OK) {
        Msg("Updating device failed.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }
    return TRUE;
}

/****************************************************************************/
/*                    Windows Message Handlers                              */
/****************************************************************************/
/*
 * AppAbout
 * About box message handler
 */
BOOL
FAR PASCAL AppAbout(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wparam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/*
 * WindowProc
 * Main window message handler
 */
LONG FAR PASCAL WindowProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    int i;
    HRESULT rval;
    RECT rc;

if (!myglobs.bInitialized) {
return DefWindowProc(win, msg, wparam, lparam);
}

    switch (msg)    {
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_MOUSEMOVE:
            /*
             * Record the mouse state for ReadMouse
             */
            myglobs.mouse_buttons = wparam;
            myglobs.mouse_x = LOWORD(lparam);
            myglobs.mouse_y = HIWORD(lparam);
            break;
        case WM_INITMENUPOPUP:
            /*
             * Check and enable the appropriate menu items
             */
            CheckMenuItem((HMENU)wparam, MENU_STEP,(myglobs.bSingleStepMode) ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wparam, MENU_GO,(myglobs.bSingleStepMode) ? MF_ENABLED : MF_GRAYED);
            if (!myglobs.bConstRenderQuality) {
                CheckMenuItem((HMENU)wparam, MENU_LIGHTING, (myglobs.RenderQuality & D3DRMLIGHT_MASK) == D3DRMLIGHT_ON ? MF_CHECKED : MF_GRAYED);
                CheckMenuItem((HMENU)wparam, MENU_FLAT, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_FLAT ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem((HMENU)wparam, MENU_GOURAUD, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_GOURAUD ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem((HMENU)wparam, MENU_PHONG, (myglobs.RenderQuality & D3DRMSHADE_MASK) == D3DRMSHADE_PHONG ? MF_CHECKED : MF_UNCHECKED);
                EnableMenuItem((HMENU)wparam, MENU_PHONG, MF_GRAYED);
                CheckMenuItem((HMENU)wparam, MENU_POINT, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_POINTS ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem((HMENU)wparam, MENU_WIREFRAME, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_WIREFRAME ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem((HMENU)wparam, MENU_SOLID, (myglobs.RenderQuality & D3DRMFILL_MASK) == D3DRMFILL_SOLID ? MF_CHECKED : MF_UNCHECKED);
            } else {
                EnableMenuItem((HMENU)wparam, MENU_LIGHTING, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_FLAT, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_GOURAUD, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_PHONG, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_POINT, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_WIREFRAME, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_SOLID, MF_GRAYED);
            }
            if (!myglobs.bNoTextures) {
                CheckMenuItem((HMENU)wparam, MENU_POINT_FILTER, (myglobs.TextureQuality == D3DRMTEXTURE_NEAREST) ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem((HMENU)wparam, MENU_LINEAR_FILTER, (myglobs.TextureQuality == D3DRMTEXTURE_LINEAR) ? MF_CHECKED : MF_UNCHECKED);
            } else {
                EnableMenuItem((HMENU)wparam, MENU_POINT_FILTER, MF_GRAYED);
                EnableMenuItem((HMENU)wparam, MENU_LINEAR_FILTER, MF_GRAYED);
            }
            CheckMenuItem((HMENU)wparam, MENU_DITHERING, (myglobs.bDithering) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wparam, MENU_ANTIALIAS, (myglobs.bAntialiasing) ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wparam, MENU_ANTIALIAS, MF_GRAYED);
            for (i = 0; i < myglobs.NumDrivers; i++) {
                CheckMenuItem((HMENU)wparam, MENU_FIRST_DRIVER + i,
                       (i == myglobs.CurrDriver) ? MF_CHECKED : MF_UNCHECKED);
            }
            break;
        case WM_COMMAND:
            switch(LOWORD(wparam)) {
                case MENU_ABOUT:
                    DialogBox((HINSTANCE)GetWindowLong(win, GWL_HINSTANCE),
                              "AppAbout", win, (DLGPROC)AppAbout);
                    break;
                case MENU_EXIT:
                    CleanUpAndPostQuit();
                    break;
                case MENU_STEP:
                    /*
                     * Begin single step more or draw a frame if in single
                     * step mode
                     */
                    if (!myglobs.bSingleStepMode) {
                        myglobs.bSingleStepMode = TRUE;
                        myglobs.bDrawAFrame = TRUE;
                    } else if (!myglobs.bDrawAFrame) {
                        myglobs.bDrawAFrame = TRUE;
                    }
                    break;
                case MENU_GO:
                    /*
                     * Exit single step mode
                     */
                    myglobs.bSingleStepMode = FALSE;
                    break;
                /*
                 * Lighting toggle
                 */
                case MENU_LIGHTING:
                    myglobs.RenderQuality ^= D3DRMLIGHT_ON;
                    SetRenderState();
                    break;
                /*
                 * Fill mode selection
                 */
                case MENU_POINT:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_POINTS;
                    SetRenderState();
                    break;
                case MENU_WIREFRAME:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_WIREFRAME;
                    SetRenderState();
                    break;
                case MENU_SOLID:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMFILL_MASK) | D3DRMFILL_SOLID;
                    SetRenderState();
                    break;
                /*
                 * Shade mode selection
                 */
                case MENU_FLAT:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_FLAT;
                    SetRenderState();
                    break;
                case MENU_GOURAUD:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_GOURAUD;
                    SetRenderState();
                    break;
                case MENU_PHONG:
                    myglobs.RenderQuality = (myglobs.RenderQuality & ~D3DRMSHADE_MASK) | D3DRMSHADE_PHONG;
                    SetRenderState();
                    break;

                case MENU_DITHERING:
                    myglobs.bDithering = !myglobs.bDithering;
                    SetRenderState();
                    break;
                case MENU_ANTIALIAS:
                    myglobs.bAntialiasing = !myglobs.bAntialiasing;
                    break;
                /*
                 * Texture filter selection
                 */
                case MENU_POINT_FILTER:
                    if (myglobs.TextureQuality == D3DRMTEXTURE_NEAREST)
                        break;
                    myglobs.TextureQuality = D3DRMTEXTURE_NEAREST;
                    SetRenderState();
                    break;
                case MENU_LINEAR_FILTER:
                    if (myglobs.TextureQuality == D3DRMTEXTURE_LINEAR)
                        break;
                    myglobs.TextureQuality = D3DRMTEXTURE_LINEAR;
                    SetRenderState();
                    break;
                }
                /*
                 * Changing the D3D Driver
                 */
                if (LOWORD(wparam) >= MENU_FIRST_DRIVER &&
                    LOWORD(wparam) < MENU_FIRST_DRIVER + MAX_DRIVERS &&
                    myglobs.CurrDriver != LOWORD(wparam) - MENU_FIRST_DRIVER)
{
                    /*
                     * Release the current viewport and device and create
                     * the new one
                     */
    int LastDriver = myglobs.CurrDriver;
    myglobs.bInitialized = FALSE;
                    RELEASE(myglobs.view);
                    RELEASE(myglobs.dev);
                    myglobs.CurrDriver = LOWORD(wparam)-MENU_FIRST_DRIVER;
                    GetClientRect(win, &rc);
                    if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
rc.right, rc.bottom))
    {
myglobs.CurrDriver = LastDriver;
if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
    rc.right, rc.bottom))
{
    Msg("Failed to create the D3DRM device.\n%s", D3DRMErrorToString(rval));
    CleanUpAndPostQuit();
}
else
{
    Msg("There was not enough video memory available to use the 3D accelerated hardware device.\nRestoring old software device.");
    myglobs.bInitialized = TRUE;
}
                    }
    else
    {
myglobs.bInitialized = TRUE;
    }
                }
                /*
                 * Draw a frame in single step mode after ever command
                 */
                myglobs.bDrawAFrame = TRUE;
            break;
    case WM_DESTROY:
        CleanUpAndPostQuit();
        break;
    case WM_SIZE:
        /*
         * Handle resizing of the window
         */
        {
        int width = LOWORD(lparam);
        int height = HIWORD(lparam);
        if (width && height && myglobs.view && myglobs.dev) {
            int view_width = myglobs.view->GetWidth();
            int view_height = myglobs.view->GetHeight();
            int dev_width = myglobs.dev->GetWidth();

int dev_height = myglobs.dev->GetHeight();
            /*
             * If the window hasn't changed size and we aren't returning from
             * a minimize, there is nothing to do
             */
            if (view_width == width && view_height == height &&
                !myglobs.bMinimized)
                break;
            if (width <= dev_width && height <= dev_height) {
                /*
                 * If the window has shrunk, we can use the same device with a
                 * new viewport
                 */
myglobs.bInitialized = FALSE;
                RELEASE(myglobs.view);
                rval = lpD3DRM->CreateViewport(myglobs.dev, myglobs.camera,
                                               0, 0, width, height,
                                               &myglobs.view);
                if (rval != D3DRM_OK) {
                    Msg("Failed to resize the viewport.\n%s",
                        D3DRMErrorToString(rval));
                    CleanUpAndPostQuit();
                    break;
                }
                rval = myglobs.view->SetBack(D3DVAL(5000.0));
                if (rval != D3DRM_OK) {
                    Msg("Failed to set background depth after viewport resize.\n%s",
                        D3DRMErrorToString(rval));
                    CleanUpAndPostQuit();
                    break;
                }
myglobs.bInitialized = TRUE;
            } else {
                /*
                 * If the window got larger than the current device, create a
                 * new device.
                 */
myglobs.bInitialized = FALSE;
                RELEASE(myglobs.view);
                RELEASE(myglobs.dev);
                if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
    width, height))
{
    myglobs.CurrDriver = 0;
    if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
width, height))
    {
Msg("Failed to create the D3DRM device.\n%s", D3DRMErrorToString(rval));
CleanUpAndPostQuit();
    }
    else
    {
Msg("There was not enough video memory available to use the 3D accelerated hardware device.\nUsing software rendering instead.");
myglobs.bInitialized = TRUE;
    }
                }
else
{
    myglobs.bInitialized = TRUE;
}
myglobs.bInitialized = TRUE;
            }
            /*
             * We must not longer be minimized
             */
            myglobs.bMinimized = FALSE;
        } else {
            /*
             * This is a minimize message
             */
            myglobs.bMinimized = TRUE;
        }
        }
myglobs.bDrawAFrame = TRUE;
        break;
    case WM_ACTIVATE:
        {
        /*
         * Create a Windows specific D3DRM window device to handle this
         * message
         */
        LPDIRECT3DRMWINDEVICE windev;
        if (!myglobs.dev)
            break;
        if (SUCCEEDED(myglobs.dev->QueryInterface(IID_IDirect3DRMWinDevice,
            (void **) &windev)))  {
                if (FAILED(windev->HandleActivate(wparam)))
                    Msg("Failed to handle WM_ACTIVATE.\n");
                windev->Release();
        } else {
            Msg("Failed to create Windows device to handle WM_ACTIVATE.\n");
        }
        }
        break;
    case WM_PAINT:
        if (!myglobs.bInitialized || !myglobs.dev)
            return DefWindowProc(win, msg, wparam, lparam);
        /*
         * Create a Windows specific D3DRM window device to handle this
         * message
         */
        RECT r;
        PAINTSTRUCT ps;
        LPDIRECT3DRMWINDEVICE windev;

        if (GetUpdateRect(win, &r, FALSE)) {
            BeginPaint(win, &ps);
            if (SUCCEEDED(myglobs.dev->QueryInterface(IID_IDirect3DRMWinDevice,
                (void **) &windev))) {
                if (FAILED(windev->HandlePaint(ps.hdc)))
                    Msg("Failed to handle WM_PAINT.\n");
                windev->Release();
            } else {
                Msg("Failed to create Windows device to handle WM_PAINT.\n");
            }
            EndPaint(win, &ps);
        }
        break;
    case WM_DISPLAYCHANGE:
{
    /*
     * If this display change message was generated because another application
     * has gone exclusive, ignore it.
     */
    LPDIRECTDRAW lpDD;
    LPDIRECTDRAWSURFACE lpDDS;
    DDSURFACEDESC ddsd;
    HRESULT err;

    if (DirectDrawCreate(NULL, &lpDD, NULL))
break;
    err = lpDD->SetCooperativeLevel(win, DDSCL_NORMAL);
    if (err) {
lpDD->Release();
break;
    }
    memset(&ddsd, 0, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    err = lpDD->CreateSurface(&ddsd, &lpDDS, NULL);
    if (err == DDERR_NOEXCLUSIVEMODE) {
// This exclusive mode generated WM_DISPLAYCHANGE, ignoring
lpDD->Release();
break;
    }
    if (!err)
lpDDS->Release();
    lpDD->Release();
}
GetClientRect(win, &rc);
myglobs.bInitialized = FALSE;
        if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
    rc.right, rc.bottom)) {
    myglobs.CurrDriver = 0;
    if (rval = CreateDevAndView(lpDDClipper, myglobs.CurrDriver,
rc.right, rc.bottom))
    {
Msg("Failed to create the D3DRM device.\n%s", D3DRMErrorToString(rval));
CleanUpAndPostQuit();
    }
    else
    {
// Don't bother the user with an error message here
//Msg("There was not enough video memory available to use the 3D accelerated hardware device.\nUsing software rendering instead.");
myglobs.bInitialized = TRUE;
    }
        }
else
{
    myglobs.bInitialized = TRUE;
}
break;
    default:
        return DefWindowProc(win, msg, wparam, lparam);
    }
    return 0L;
}

/*
 * SetRenderState
 * Set the render quality, dither toggle and shade info if any of them has
 * changed
 */
HRESULT
SetRenderState(void)
{
    HRESULT rval;
    /*
     * Set the render quality (light toggle, fill mode, shade mode)
     */
    if (myglobs.dev->GetQuality() != myglobs.RenderQuality) {
        rval = myglobs.dev->SetQuality(myglobs.RenderQuality);
        if (rval != D3DRM_OK)
    return rval;
    }
    /*
     * Set dithering toggle
     */
    if (myglobs.dev->GetDither() != myglobs.bDithering) {
        rval = myglobs.dev->SetDither(myglobs.bDithering);
        if (rval != D3DRM_OK)
    return rval;
    }
    /*
     * Set the texture quality (point or linear filtering)
     */
    if (myglobs.dev->GetTextureQuality() != myglobs.TextureQuality) {
        rval = myglobs.dev->SetTextureQuality(myglobs.TextureQuality);
        if (rval != D3DRM_OK)
    return rval;
    }
    /*
     * Set shade info based on current bits per pixel
     */
    switch (myglobs.BPP) {
    case 1:
        if (rval = myglobs.dev->SetShades(4))
            return rval;
        if (rval = lpD3DRM->SetDefaultTextureShades(4))
            return rval;
        break;
    case 16:
        if (rval = myglobs.dev->SetShades(32))
            return rval;
        if (rval = lpD3DRM->SetDefaultTextureColors(64))
            return rval;
        if (rval = lpD3DRM->SetDefaultTextureShades(32))
            return rval;
        break;
    case 24:
    case 32:
        if (rval = myglobs.dev->SetShades(256))
            return rval;
        if (rval = lpD3DRM->SetDefaultTextureColors(64))
            return rval;
        if (rval = lpD3DRM->SetDefaultTextureShades(256))
            return rval;
        break;
    }
    return D3DRM_OK;
}

/****************************************************************************/
/*                          Additional Functions                            */
/****************************************************************************/
/*
 * ReadMouse
 * Returns the mouse status for interaction with sample code
 */
void
ReadMouse(int* b, int* x, int* y)
{
    *b = myglobs.mouse_buttons;
    *x = myglobs.mouse_x;
    *y = myglobs.mouse_y;
}

/*
 * InitGlobals
 * Initialize the global variables
 */
void
InitGlobals(void)
{
    lpD3DRM = NULL;
    memset(&myglobs, 0, sizeof(myglobs));
    myglobs.RenderQuality = D3DRMLIGHT_ON | D3DRMFILL_SOLID |
                            D3DRMSHADE_GOURAUD;
    myglobs.TextureQuality = D3DRMTEXTURE_NEAREST;
}

/*
 * CleanUpAndPostQuit
 * Release all D3DRM objects, post a quit message and set the bQuit flag
 */
void
CleanUpAndPostQuit(void)
{
    myglobs.bInitialized = FALSE;
    RELEASE(myglobs.scene);
    RELEASE(myglobs.camera);
    RELEASE(myglobs.view);
    RELEASE(myglobs.dev);
    RELEASE(lpD3DRM);
    RELEASE(lpDDClipper);
    myglobs.bQuit = TRUE;
}


RMMAIN.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: resource.h
 *
 ***************************************************************************/

#define MENU_ABOUT                      1
#define MENU_EXIT                       2
#define MENU_STEP                       27
#define MENU_GO                         28
#define MENU_FLAT                       12
#define MENU_GOURAUD                    13
#define MENU_PHONG                      14
#define MENU_POINT_FILTER               17
#define MENU_LINEAR_FILTER              18
#define MENU_POINT                      20
#define MENU_WIREFRAME                  21
#define MENU_SOLID                      22
#define MENU_DITHERING                  24
#define MENU_ANTIALIAS                  26
#define MENU_LIGHTING29
#define MENU_FIRST_DRIVER               80

#define IDC_STATIC                     -1


RMSTATS.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rmstats.cpp
 *
 ***************************************************************************/
#include "rmfull.h"

/*
 * GLOBAL VARIABLES
 */
extern D3DAppInfo* d3dapp;    /* Pointer to read only collection of DD and D3D
               objects maintained by D3DApp */
extern rmfullglobals myglobs;    /* collection of global variables */

static struct {
    HFONT hFont;
    SIZE szFrameRate;
    SIZE szInfo;
} statglobs;

 /************************************************************************
  Frame rate and info text
 ************************************************************************/

BOOL
WriteFrameRateBuffer(float fps, long tps)
{
    HRESULT LastError;
    HDC hdc;
    RECT rc;
    char buf1[30], buf2[30], buf[60];
    int len;

    if (!myglobs.lpFrameRateBuffer)
return TRUE;
    if (fps > 0.0f)
        wsprintf(buf1, "%d.%02d fps   ",
        (int)( fps * 100 ) / 100,
        (int)( fps * 100 ) % 100);
    else
        buf1[0] = 0;
    if (tps > 0)
        wsprintf(buf2, "%ld tps   ", tps);
    else
        buf2[0] = 0;
    len = wsprintf(buf, "%s%s", buf1, buf2);
    if (!myglobs.lpFrameRateBuffer)
        return FALSE;
    LastError = myglobs.lpFrameRateBuffer->GetDC(&hdc);
    if (LastError != DD_OK) {
/*
 *  This is not vital, don't report an error.
 */
        return TRUE;
    }
    SelectObject(hdc, statglobs.hFont);
    SetTextColor(hdc, RGB(255,255,0));
    SetBkColor(hdc, RGB(0,0,0));
    SetBkMode(hdc, OPAQUE);
    GetTextExtentPoint32(hdc, buf, len, &statglobs.szFrameRate);
    SetRect(&rc, 0, 0, statglobs.szFrameRate.cx, statglobs.szFrameRate.cy);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, buf, len, NULL);
    myglobs.lpFrameRateBuffer->ReleaseDC(hdc);
    return TRUE;
}

BOOL
WriteInfoBuffer(void)
{
    HRESULT LastError;
    HDC hdc;
    RECT rc;
    char buf[40];
    int len;

    if (!myglobs.lpInfoBuffer)
return TRUE;
    if (d3dapp->bFullscreen)
len = wsprintf(buf, "%dx%dx%d (%s)", d3dapp->ThisMode.w, d3dapp->ThisMode.h, d3dapp->ThisMode.bpp,
       (d3dapp->ThisDriver.Desc.dcmColorModel == D3DCOLOR_MONO) ? "MONO" : "RGB");
    else
len = wsprintf(buf, "%dx%d (%s)", d3dapp->szClient.cx, d3dapp->szClient.cy,
       (d3dapp->ThisDriver.Desc.dcmColorModel == D3DCOLOR_MONO) ? "MONO" : "RGB");
    if (!myglobs.lpInfoBuffer)
        return FALSE;
    LastError = myglobs.lpInfoBuffer->GetDC(&hdc);
    if (LastError != DD_OK) {
/*
 *  This is not vital, don't report an error.
 */
        return TRUE;
    }

    SelectObject(hdc, statglobs.hFont);
    SetTextColor(hdc, RGB(255,255,0));
    SetBkColor(hdc, RGB(0,0,0));
    SetBkMode(hdc, OPAQUE);
    GetTextExtentPoint32(hdc, buf, len, &statglobs.szInfo);
    SetRect(&rc, 0, 0, statglobs.szInfo.cx, statglobs.szInfo.cy);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, buf, len, NULL);
    myglobs.lpInfoBuffer->ReleaseDC(hdc);
    return TRUE;
}

BOOL
InitFontAndTextBuffers(void)
{
    DDCOLORKEY          ddck;
    DDSURFACEDESC       ddsd;
    HDC hdc;
    HRESULT ddrval;
    char dummyinfo[] = "000x000x00 (MONO) 0000";
    char dummyfps[] = "000.00 fps 00000000.00 tps 0000.00 mppps";

    /*
     * Create the font.
     */
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    if (statglobs.hFont != NULL) {
        DeleteObject(statglobs.hFont);
    }
    statglobs.hFont = CreateFont(
        d3dapp->szClient.cx <= 600 ? 12 : 24,
        0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        VARIABLE_PITCH,
        "Arial" );

    hdc = GetDC(NULL);
    SelectObject(hdc, statglobs.hFont);
    GetTextExtentPoint(hdc, dummyfps, strlen(dummyfps), &statglobs.szFrameRate);
    GetTextExtentPoint(hdc, dummyinfo, strlen(dummyinfo), &statglobs.szInfo);
    ReleaseDC(NULL, hdc);

    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = statglobs.szFrameRate.cy;
    ddsd.dwWidth = statglobs.szFrameRate.cx;
    ddrval = D3DAppCreateSurface(&ddsd, &myglobs.lpFrameRateBuffer);
    if (ddrval != DD_OK) {
Msg("Could not create frame rate buffer.\n%s", D3DAppErrorToString(ddrval));
        goto exit_with_error;
    }
    memset(&ddck, 0, sizeof(ddck));
    myglobs.lpFrameRateBuffer->SetColorKey(DDCKEY_SRCBLT, &ddck);
    if (!WriteFrameRateBuffer(0.0f, 0)) {
        goto exit_with_error;
    }
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = statglobs.szInfo.cy;
    ddsd.dwWidth = statglobs.szInfo.cx;
    ddrval = D3DAppCreateSurface(&ddsd, &myglobs.lpInfoBuffer);
    if (ddrval != DD_OK) {
Msg("Could not create info buffer.\n%s", D3DAppErrorToString(ddrval));
goto exit_with_error;
    }
    memset(&ddck, 0, sizeof(ddck));
    myglobs.lpInfoBuffer->SetColorKey(DDCKEY_SRCBLT, &ddck);
    if (!WriteInfoBuffer())
goto exit_with_error;
    return TRUE;
exit_with_error:
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    if (statglobs.hFont != NULL) {
        DeleteObject(statglobs.hFont);
    }
    return FALSE;
}

/*************************************************************************
  Frame rate output.
 *************************************************************************/

#define INTERVAL 100

char StatTxt[100];
int StatTxtLen;
int count = 0;
int last_polygons = 0;
int this_frames = 0;
time_t last_time;
float fps;
long tps;

/*
 * ResetFrameRate
 * Initializes the frame rate counter.
 */
void
ResetFrameRate(void)
{
    last_time = clock();
    count = 0;
    last_polygons = 0;
    this_frames = 0;
    fps = 0.0f; tps = 0;
    StatTxt[0] = 0;
    StatTxtLen = 0;
}

BOOL
CalculateFrameRate()
{
    /*
     * Calculate the frame rate and get other stats.
     */
    count++;
    this_frames++;
    if (count == INTERVAL) {
    double t;
int p, f;
        time_t this_time;

        count = 0;
this_time = clock();
t = (this_time - last_time) / (double)CLOCKS_PER_SEC;
last_time = this_time;

p = myglobs.dev->GetTrianglesDrawn() - last_polygons;
last_polygons = myglobs.dev->GetTrianglesDrawn();

f = this_frames;
this_frames = 0;

        fps = (float)(f / t);
        tps = (long)(p / t);

        if (myglobs.bShowFrameRate) {
            if (!WriteFrameRateBuffer(fps, tps))
                return FALSE;
        }
    }
    return TRUE;
}

/*
 * DisplayFrameRate
 * Outputs frame rate info and screen mode to back buffer when appropriate.
 */
BOOL
DisplayFrameRate(int* count, LPD3DRECT lpExtents )
{
    RECT rc;
    int x, y;
    HRESULT ddrval = DD_OK;
    if (myglobs.bShowFrameRate && !myglobs.bSingleStepMode && statglobs.szFrameRate.cx > 0 && statglobs.szFrameRate.cy > 0 &&
        statglobs.szFrameRate.cx < d3dapp->szClient.cx && statglobs.szFrameRate.cy < d3dapp->szClient.cy) {
        SetRect(&rc, 0, 0, statglobs.szFrameRate.cx, statglobs.szFrameRate.cy);
        x = (int)(0.5 * (d3dapp->szClient.cx - statglobs.szFrameRate.cx) + 0.5);
        y = 0;
if (myglobs.lpFrameRateBuffer)
    ddrval = d3dapp->lpBackBuffer->BltFast(x, y, myglobs.lpFrameRateBuffer, &rc,
 DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);
if (ddrval != DD_OK) {
    /*
     * Blting the frame rate has failed. Since it is not vital, we
     * aren't going to report an error.  Check to see if the surfaces
     * have been lost and then return.
     */
    if (ddrval == DDERR_SURFACELOST) {
d3dapp->lpBackBuffer->Restore();
myglobs.lpFrameRateBuffer->Restore();
    }
    return TRUE;
}
SetRect((LPRECT)(lpExtents), x, y, statglobs.szFrameRate.cx + x, statglobs.szFrameRate.cy);
++(*count);
++lpExtents;
    }
    if (myglobs.bShowInfo && statglobs.szInfo.cx < d3dapp->szClient.cx && statglobs.szInfo.cy < d3dapp->szClient.cy) {
        SetRect(&rc, 0, 0, statglobs.szInfo.cx, statglobs.szInfo.cy);
        x = (int)(0.5 * (d3dapp->szClient.cx - statglobs.szInfo.cx) + 0.5);
        y = d3dapp->szClient.cy - statglobs.szInfo.cy;
if (myglobs.lpInfoBuffer)
    ddrval = d3dapp->lpBackBuffer->BltFast(x,y, myglobs.lpInfoBuffer, &rc,
 DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);
if (ddrval != DD_OK) {
    /*
     * Blting the info has failed. Since it is not vital, we
     * aren't going to report an error.  Check to see if the surfaces
     * have been lost and then return.
     */
    if (ddrval == DDERR_SURFACELOST) {
d3dapp->lpBackBuffer->Restore();
myglobs.lpInfoBuffer->Restore();
    }
    return TRUE;
}
SetRect((LPRECT)(lpExtents), x, y, x + statglobs.szInfo.cx, y + statglobs.szInfo.cy);
++(*count);
    }
    return TRUE;
}


README.TXT

Morph
Direct3D Retained Mode Sample
Copyright (C) 1997 Microsoft Corporation. All Rights Reserved.


MORPH.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: morph.c
 *
 ***************************************************************************/

/*
 * An example of the use of interpolators.
 */

#include "rmdemo.h"

#define NO_NORMAL {D3DVAL(0), D3DVAL(0), D3DVAL(0)}, D3DVAL(0), D3DVAL(0), 0

/* cube corners */
#define CFTL(v){{-D3DVAL(v), +D3DVAL(v), -D3DVAL(v)}, NO_NORMAL}
#define CFTR(v){{+D3DVAL(v), +D3DVAL(v), -D3DVAL(v)}, NO_NORMAL}
#define CFBR(v){{+D3DVAL(v), -D3DVAL(v), -D3DVAL(v)}, NO_NORMAL}
#define CFBL(v){{-D3DVAL(v), -D3DVAL(v), -D3DVAL(v)}, NO_NORMAL}
#define CBTL(v){{-D3DVAL(v), +D3DVAL(v), +D3DVAL(v)}, NO_NORMAL}
#define CBTR(v){{+D3DVAL(v), +D3DVAL(v), +D3DVAL(v)}, NO_NORMAL}
#define CBBR(v){{+D3DVAL(v), -D3DVAL(v), +D3DVAL(v)}, NO_NORMAL}
#define CBBL(v){{-D3DVAL(v), -D3DVAL(v), +D3DVAL(v)}, NO_NORMAL}

/* face centers */
#define FRT(v){{ D3DVAL(0),  D3DVAL(0), -D3DVAL(v)}, NO_NORMAL}
#define BCK(v){{ D3DVAL(0),  D3DVAL(0),  D3DVAL(v)}, NO_NORMAL}
#define BTM(v){{ D3DVAL(0), -D3DVAL(v),  D3DVAL(0)}, NO_NORMAL}
#define TOP(v){{ D3DVAL(0),  D3DVAL(v),  D3DVAL(0)}, NO_NORMAL}
#define LFT(v){{-D3DVAL(v),  D3DVAL(0),  D3DVAL(0)}, NO_NORMAL}
#define RGT(v){{ D3DVAL(v),  D3DVAL(0),  D3DVAL(0)}, NO_NORMAL}

D3DRMVERTEX verts0[] =
{   CFTL(1), CFTR(1), CFBR(1), CFBL(1),
    CBBR(1), CBTR(1), CBTL(1), CBBL(1),
    CFBR(1), CBBR(1), CBBL(1), CFBL(1),
    CBTL(1), CBTR(1), CFTR(1), CFTL(1),
    CBTL(1), CFTL(1), CFBL(1), CBBL(1),
    CFBR(1), CFTR(1), CBTR(1), CBBR(1),
    FRT(5), BCK(5), BTM(5), TOP(5), LFT(5), RGT(5)
};

D3DRMVERTEX verts1[] =
{   CFTL(2), CFTR(2), CFBR(2), CFBL(2),
    CBBR(2), CBTR(2), CBTL(2), CBBL(2),
    CFBR(2), CBBR(2), CBBL(2), CFBL(2),
    CBTL(2), CBTR(2), CFTR(2), CFTL(2),
    CBTL(2), CFTL(2), CFBL(2), CBBL(2),
    CFBR(2), CFTR(2), CBTR(2), CBBR(2),
    FRT(1), BCK(1), BTM(1), TOP(1), LFT(1), RGT(1)
};

unsigned faces[] =
{    0,  1, 24,  1,  2, 24,  2,  3, 24,  3,  0, 24,
     4,  5, 25,  5,  6, 25,  6,  7, 25,  7,  4, 25,
     8,  9, 26,  9, 10, 26, 10, 11, 26, 11,  8, 26,
    12, 13, 27, 13, 14, 27, 14, 15, 27, 15, 12, 27,
    16, 17, 28, 17, 18, 28, 18, 19, 28, 19, 16, 28,
    20, 21, 29, 21, 22, 29, 22, 23, 29, 23, 20, 29
};

static void CDECL destroyInterp(LPDIRECT3DRMOBJECT obj, void* arg)
{
    LPDIRECT3DRMINTERPOLATOR meshInterp = (LPDIRECT3DRMINTERPOLATOR) arg;

    if (meshInterp) meshInterp->Release();
}

void CDECL
interpolate(LPDIRECT3DRMFRAME frame, void *arg, D3DVALUE delta)
{
    static D3DVALUE val = D3DVAL(0);
    LPDIRECT3DRMINTERPOLATOR meshInterp = (LPDIRECT3DRMINTERPOLATOR) arg;

    meshInterp->Interpolate(val += delta, NULL, D3DRMINTERPOLATION_CLOSED | D3DRMINTERPOLATION_LINEAR);
}

void generate_normals(D3DRMVERTEX verts[])
{
    unsigned i;
    D3DVECTOR a, b;

    for (i = 0; i < 24; i++)
    {
D3DRMVectorSubtract(&a, &verts[faces[i * 3 + 1]].position, &verts[i].position);
D3DRMVectorSubtract(&b, &verts[faces[i * 3 + 2]].position, &verts[i].position);
D3DRMVectorCrossProduct(&verts[i].normal, &a, &b);
D3DRMVectorNormalize(&verts[i].normal);
    }
}

#define D3DTRY(x) if (FAILED(x)) goto generic_error

#define CREATE(type, obj) CreateObject(CLSID_C##type, NULL, IID_I##type, (void **) obj)

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMMESH mesh = NULL, meshProxy = NULL;
    LPDIRECT3DRMFRAME frame = NULL, proxyFrame = NULL;
    LPDIRECT3DRMFRAME axis = NULL;
    LPDIRECT3DRMFRAME orbit = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    LPDIRECT3DRMINTERPOLATOR meshInterp = NULL;

    dev, view, camera;

    /* generate flat-shading normals for faces */
    generate_normals(verts0);
    generate_normals(verts1);

    D3DTRY(lpD3DRM->CreateObject(CLSID_CDirect3DRMMeshInterpolator, NULL,
 IID_IDirect3DRMInterpolator, (void **) &meshInterp));
    D3DTRY(meshInterp->QueryInterface(IID_IDirect3DRMMesh, (void **) &meshProxy));

    D3DTRY(meshInterp->SetIndex(D3DVAL(0)));
    D3DTRY(meshProxy->SetVertices(0, 0, 30, verts1));
    D3DTRY(meshProxy->SetGroupColorRGB(0, D3DVAL(0.5), D3DVAL(0.4), D3DVAL(0.7)));

    D3DTRY(meshInterp->SetIndex(D3DVAL(25)));
    D3DTRY(meshProxy->SetVertices(0, 0, 30, verts0));
    D3DTRY(meshProxy->SetGroupColorRGB(0, D3DVAL(1), D3DVAL(0.0), D3DVAL(0.3)));

    D3DTRY(meshInterp->SetIndex(D3DVAL(40)));
    D3DTRY(meshProxy->SetGroupColorRGB(0, D3DVAL(0.6), D3DVAL(0.6), D3DVAL(0.6)));

    D3DTRY(meshInterp->SetIndex(D3DVAL(75)));
    meshProxy->Release();

    D3DTRY(lpD3DRM->CREATE(Direct3DRMLight, &light1));
    D3DTRY(light1->SetType(D3DRMLIGHT_AMBIENT));
    D3DTRY(light1->SetColorRGB(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2)));

    D3DTRY(lpD3DRM->CREATE(Direct3DRMLight, &light2));
    D3DTRY(light2->SetType(D3DRMLIGHT_DIRECTIONAL));
    D3DTRY(light2->SetColorRGB(D3DVAL(1), D3DVAL(1), D3DVAL(1)));

    D3DTRY(scene->AddLight(light1));
    D3DTRY(scene->SetSceneBackgroundRGB(D3DVAL(0.3), D3DVAL(0.6), D3DVAL(0.3)));

    D3DTRY(lpD3DRM->CREATE(Direct3DRMFrame, &frame));
    D3DTRY(scene->AddChild(frame));
    D3DTRY(frame->SetOrientation(scene, D3DVAL(0.5), -D3DVAL(0.5), D3DVAL(1),
 D3DVAL(0), D3DVAL(1), D3DVAL(0)));

    D3DTRY(frame->AddLight(light2));
    frame->Release();

    D3DTRY(lpD3DRM->CREATE(Direct3DRMFrame, &frame));
    D3DTRY(scene->AddChild(frame));
    D3DTRY(frame->SetPosition(scene, D3DVAL(0), D3DVAL(0), D3DVAL(15)));
    D3DTRY(frame->SetOrientation(scene, D3DVAL(0), D3DVAL(1.0), D3DVAL(0),
    D3DVAL(0), D3DVAL(0), D3DVAL(1)));
    D3DTRY(frame->SetRotation(scene, D3DVAL(0), D3DVAL(0.9), D3DVAL(1.0), D3DVAL(0.04)));

    D3DTRY(lpD3DRM->CREATE(Direct3DRMMesh, &mesh));
    D3DTRY(mesh->AddGroup(30, 24, 3, faces, NULL));
    D3DTRY(mesh->SetGroupQuality(0, D3DRMRENDER_FLAT));

    D3DTRY(meshInterp->AttachObject(mesh));
    D3DTRY(frame->AddVisual(mesh));

    D3DTRY(frame->AddMoveCallback(interpolate, meshInterp));
    D3DTRY(frame->AddDestroyCallback(destroyInterp, meshInterp));

    D3DTRY(lpD3DRM->CREATE(Direct3DRMFrame, &axis));
    D3DTRY(frame->AddChild(axis));
    D3DTRY(axis->SetRotation(frame, D3DVAL(0), D3DVAL(1), D3DVAL(0), D3DVAL(0.04)));

    D3DTRY(lpD3DRM->CREATE(Direct3DRMFrame, &orbit));
    D3DTRY(axis->AddChild(orbit));
    D3DTRY(orbit->SetPosition(axis, D3DVAL(2.6), D3DVAL(0), D3DVAL(0)));

    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(orbit);
    RELEASE(light1);
    RELEASE(light2);
    /* don't release the wrap */
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(orbit);
    RELEASE(light1);
    RELEASE(light2);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Interpolator Direct3DRM Example");
}


README.TXT

MOSQUITO is a simple app to demonstrate the use of overlays in DirectDraw.
To run the app you must have a display adapter and driver which support
overlays.

On a machine with overlay support, the app creates a big flapping mosquito
which flys around the screen.   If you're card doesn't support "Source Color
Keying" of overlays, you'll see an ugly black rectangular background around
the mosquito.

Some cards have better overlay support in certain resolutions that others.
If you know your card has overlay support through DirectDraw, but Mosquito
is having problems creating or displaying the overlay, try running the
application in a lower screen resolution and/or color depth.


MOSQUITO.CPP

//****************************************************************************
//* Program: Mosquito
//*
//* Description:
//*     Sample application to demonstrate the creation, display, and
//*     positioning of overlays.
//*
//* Contents:
//*     WinMain() - Main entry point for this application.
//*     MainDlgProc() - Message proc for this app.
//*     InitDirectDraw() - Creates core DirectDraw objects.
//*     FreeDirectDraw() - Frees up core DirectDraw objects.
//*     CreatePrimarySurface() - Creates Primary surface.
//*     CreateOverlay() - Creates a multi-buffer flippable overlay surface.
//*     DestroyOverlay() - Hides overlay surface and releases it.
//*     CopyBitmapToYUVSurface() - Copies a GDI bitmap to a YUV surface.
//*                                Used by LoadImageOntoSurface().
//*     LoadImageOntoSurface() - Loads a resource based bitmap onto a surface.
//*     EnumAttachedCallback() - Callback for EnumAttachedSurfaces().
//*     DisplayOverlay() - Displays our overlay on the primary.
//*     RestoreAllSurfaces() - Restores the primary and overlay surfaces.
//*     MoveOverlayTimerCallback() - TimerProc for moving the overlay surface.
//*
//* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//* PARTICULAR PURPOSE.
//*
//* Copyright 1996 - 1998 Microsoft Corporation.  All Rights Reserved.
//****************************************************************************

// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>

// Windows Header Files:
#include <windows.h>
#include <mmsystem.h> //added to compile under NT
#define INITGUID
#include <objbase.h>
#include <initguid.h>
#include <ddraw.h>

// Local Headers
#include "resource.h"

// Define some macros
#define INIT_DIRECTDRAW_STRUCT(x) (ZeroMemory(&x, sizeof(x)), x.dwSize=sizeof(x))
#define RAND_INT(x) (rand()*x/RAND_MAX)
#define RANDOM_VELOCITY() ((RAND_INT(5)+3)*2)

// Global Variables:

LPDIRECTDRAW2       g_lpdd = NULL;
LPDIRECTDRAWSURFACE g_lpddsPrimary = NULL;
LPDIRECTDRAWSURFACE g_lpddsOverlay = NULL;

int g_nOverlayXPos, g_nOverlayYPos;
int g_nOverlayXVel, g_nOverlayYVel;
int g_nOverlayWidth, g_nOverlayHeight;
int g_nOverlayFlipCounter;
DWORD g_dwOverlayXPositionAlignment;  // used to keep track of any alignment
                                      // restrictions on the X postion of
                                      // our overlay surface.  Comes from the
                                      // the DDCAPS dwBoundaryAlignDest field.
                                      // Initialized in DisplayOverlay().

// These are the pixel formats this app supports.  Most display adapters
// with overlay support will recognize one or more of these formats.
// The first on our list is the 16-bit RGB formats.  These have the widest
// support.
DDPIXELFORMAT g_ddpfOverlayFormats[] =
{   {sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 16,  0x7C00, 0x03e0, 0x001F, 0},      // 16-bit RGB 5:5:5
    {sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 16,  0xF800, 0x07e0, 0x001F, 0},   // 16-bit RGB 5:6:5
    {sizeof(DDPIXELFORMAT), DDPF_FOURCC,MAKEFOURCC('U','Y','V','Y'),0,0,0,0,0}, // UYVY
    {sizeof(DDPIXELFORMAT), DDPF_FOURCC,MAKEFOURCC('Y','U','Y','2'),0,0,0,0,0}};  // YUY2

#define NUM_OVERLAY_FORMATS (sizeof(g_ddpfOverlayFormats) / sizeof(g_ddpfOverlayFormats[0]))

// Define some error messages.  If we had many more we'd stick 'em in our RC file.
#define NO_OVERLAY_HARDWARE     "Sorry, In order to run this sample application you must "\
                                "have a display adapter and driver which support overlays."

#define UNABLE_TO_CREATE_OVERLAY    "Sorry, your display adapter appears to "\
                                    "support overlays, but we were unable to "\
                                    "create an overlay in any of the formats "\
                                    "this app supports (16-bit RGB and YUV).  "\
                                    "You may want to try shutting down other "\
                                    "DirectX apps to free video memory, or try "\
                                    "rerunning this app in a different display "\
                                    "mode."

#define UNABLE_TO_DISPLAY_OVERLAY   "Sorry, we created an overlay on your "\
                                    "system, but were unable to display it.  "\
                                    "Please try rerunning this app in "\
                                    "a different display mode."

//****************************************************************************
//* Function: FreeDirectDraw
//*
//* Description:
//*             Releases core DirectDraw objects
//****************************************************************************
void FreeDirectDraw(void)
{
if (g_lpddsPrimary)
{
g_lpddsPrimary->Release();
g_lpddsPrimary=NULL;
}

    if (g_lpdd)
{
g_lpdd->Release();
g_lpdd=NULL;
}
}

//****************************************************************************
//* Function: CreatePrimarySurface()
//*
//* Description:
//*     Creates a Primary Surface.  Implemented as a separate function from
//* InitDirectDraw() because we occasionally need to just recreate the primary
//* surface such as when a mode change occurs.
//****************************************************************************
HRESULT CreatePrimarySurface()
{
DDSURFACEDESC   ddsd;
    HRESULT         ddrval;

    if (!g_lpdd) return E_FAIL;

    INIT_DIRECTDRAW_STRUCT(ddsd);
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    ddrval = g_lpdd->CreateSurface(&ddsd, &g_lpddsPrimary, NULL );

    return ddrval;
}

//****************************************************************************
//* Function: InitDirectDraw
//*
//* Description:
//*     Performs DirectDraw initialization.  Creates a primary surface which
//*     is needed to display our overlay on.  The actual overlay surface is
//*     created later in our CreateOverlay() call.
//****************************************************************************
BOOL InitDirectDraw()
{
HRESULT         ddrval;
    LPDIRECTDRAW    lpDD1;

    ddrval = DirectDrawCreate( NULL, &lpDD1, NULL );
    if( FAILED(ddrval))
    goto ErrorOut;

    ddrval = lpDD1->QueryInterface(IID_IDirectDraw2, (void **)&g_lpdd);
    if( FAILED(ddrval))
    goto ErrorOut;

    lpDD1->Release();

    // For NORMAL cooperative level we no longer need to provide an HWND.
    ddrval = g_lpdd->SetCooperativeLevel(NULL, DDSCL_NORMAL);
    if( FAILED(ddrval))
    goto ErrorOut;

    ddrval= CreatePrimarySurface();
    if( FAILED(ddrval))
        goto ErrorOut;

    return TRUE;

ErrorOut:

FreeDirectDraw();

return FALSE;
}

//****************************************************************************
//* Function: DestroyOverlay()
//* Description:
//*     Hides the overlay then releases it's surface.
//****************************************************************************
void DestroyOverlay()
{
if (g_lpddsOverlay)
{
        // Use UpdateOverlay() with the DDOVER_HIDE flag to remove an overlay
        // from the display.
        g_lpddsOverlay->UpdateOverlay(NULL, g_lpddsPrimary, NULL, DDOVER_HIDE, NULL);
g_lpddsOverlay->Release();
g_lpddsOverlay=NULL;
}
}

//****************************************************************************
//* Function: CopyBitmapToYUVSurface
//* Description:
//*     Copies an RGB GDI bitmap to a YUV surface. Both bitmap and surface
//*     must be a multiple of 2 pixels in width for the supported YUV formats.
//*     The following formats are supported:
//*             YUY2
//*             UYVY
//*
//*     The "YUY2" YUV pixel format looks like this:
//*         As a series of BYTES:    [Y0][U][Y1][V] (reverse it for a DWORD)
//*
//*     The "UYVY" YUV pixel format looks like this:
//*         As a series of BYTES:    [U][Y0][V][Y1] (reverse it for a DWORD)
//*
//*     As you can see, both formats pack two pixels into a single DWORD. The
//*     pixels share U and V components and have separate Y components.
//*
//* Returns: TRUE if successful, otherwise FALSE.
//****************************************************************************
BOOL CopyBitmapToYUVSurface(LPDIRECTDRAWSURFACE lpDDSurf, HBITMAP hbm)
{
    HDC                 hdcImage;
    HRESULT             ddrval;
    DDSURFACEDESC       ddsd;
    DWORD               x, y, dwWidth, dwHeight;
    LONG                lPitch;
    LPBYTE              pSurf;
    DWORD               dwBytesInRow;
    COLORREF            color;
    BYTE                R,G,B, Y0,Y1,U,V;
    BOOL                bRet = FALSE;

    if (hbm == NULL || lpDDSurf == NULL)
        return FALSE;

    //
    //  select bitmap into a memoryDC so we can use it.
    //
    hdcImage = CreateCompatibleDC(NULL);
    SelectObject(hdcImage, hbm);

    INIT_DIRECTDRAW_STRUCT(ddsd);
    // Lock down the surface so we can modify it's contents.
    ddrval=lpDDSurf->Lock( NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT, NULL);
    if (FAILED(ddrval))
    goto CleanUp;

    dwWidth=ddsd.dwWidth;
    dwHeight=ddsd.dwHeight;
    lPitch=ddsd.lPitch;
    pSurf=(LPBYTE)ddsd.lpSurface;
dwBytesInRow=ddsd.dwWidth*2;

    // Go through the image 2 pixels at a time and convert to YUV
    for(y=0; y<dwHeight; y++)
    {
    for(x=0; x<dwWidth; x+=2)
{
            // The equations for color conversion used here, probably aren't
            // exact, but they seem to do an OK job.
color=GetPixel(hdcImage, x,y);
R=GetRValue(color);
G=GetGValue(color);
B=GetBValue(color);
        Y0= (BYTE)(0.29*R + 0.59*G + 0.14*B);
U= (BYTE)(128.0 - 0.14*R - 0.29*G + 0.43*B);

color=GetPixel(hdcImage, x+1,y);
R=GetRValue(color);
G=GetGValue(color);
B=GetBValue(color);
Y1= (BYTE)(0.29*R + 0.57*G + 0.14*B);
V= (BYTE)(128.0 + 0.36*R - 0.29*G - 0.07*B);

switch (ddsd.ddpfPixelFormat.dwFourCC)
{
case MAKEFOURCC('Y','U','Y','2'):
*(pSurf++) = Y0;
*(pSurf++) = U;
*(pSurf++) = Y1;
*(pSurf++) = V;
break;
case MAKEFOURCC('U','Y','V','Y'):
*(pSurf++) = U;
*(pSurf++) = Y0;
*(pSurf++) = V;
*(pSurf++) = Y1;
break;
}
}
pSurf+=(lPitch-dwBytesInRow);
    }

    lpDDSurf->Unlock(NULL);

CleanUp:
    if(hdcImage)
        DeleteDC(hdcImage);

return TRUE;
}

//****************************************************************************
//* Function: LoadImageOnToSurface
//* Description:
//*     Loads a resource based bitmap image onto a DirectDraw surface.  Can
//*     covert the bitmap to all RGB formats, plus a couple YUV formats.
//****************************************************************************
BOOL LoadImageOntoSurface(LPDIRECTDRAWSURFACE lpdds, LPSTR lpstrResID)
{
    HBITMAP hbm;
    HDC     hdcImage= NULL;
    HDC     hdcSurf = NULL;
    BOOL bRetVal = FALSE;
    HRESULT ddrval;
    DDSURFACEDESC ddsd;

    if (!lpdds)
        return FALSE;

    //
    // get surface size and format.
    //
    INIT_DIRECTDRAW_STRUCT(ddsd);
    ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;
    ddrval = lpdds->GetSurfaceDesc(&ddsd);
    if (FAILED(ddrval))
        goto Exit;

   // Load the bitmap resource.  We'll use LoadImage() since it'll scale the
    // image to fit our surface, and maintain the color information in the
    // bitmap.
    hbm = (HBITMAP) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(lpstrResID), IMAGE_BITMAP, ddsd.dwWidth, ddsd.dwHeight, LR_CREATEDIBSECTION);
if (hbm == NULL)
        goto Exit;

    // If our surface is a FOURCC YUV format, we need to do a little work to convert
    // our RGB resource bitmap into the appropriate YUV format.
    if (ddsd.ddpfPixelFormat.dwFlags == DDPF_FOURCC)
    {
        if (!CopyBitmapToYUVSurface(lpdds, hbm))
            goto Exit;
    }
    else  //Looks like we're just using a standard RGB surface format, let GDI do the work.
    {
        // Create a DC and associate the bitmap with it.
        hdcImage = CreateCompatibleDC(NULL);
        SelectObject(hdcImage, hbm);

        ddrval = lpdds->GetDC(&hdcSurf);
        if (FAILED(ddrval))
            goto Exit;

        if (BitBlt(hdcSurf, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, 0, 0, SRCCOPY) == FALSE)
            goto Exit;
    }

    bRetVal = TRUE;

Exit:
    if (hdcSurf)
        lpdds->ReleaseDC(hdcSurf);
    if (hdcImage)
        DeleteDC(hdcImage);
    if(hbm)
        DeleteObject(hbm);

    return bRetVal;

}

//****************************************************************************
//* Function: EnumAttachedCallback
//*
//* Description:
//*     Callback function for EnumAttachedSurfaces().  Used to recursively
//*     load each frame of the flying insect animation onto the appropriate
//*     overlay surface buffer.
//****************************************************************************
HRESULT WINAPI EnumAttachedCallback(LPDIRECTDRAWSURFACE lpdds, LPDDSURFACEDESC lpddsd, LPVOID lpContext)
{
    int     nResourceID = (int)lpContext;
    HRESULT hr = DDENUMRET_OK;

    // Check to see if we've gone full circle through all surfaces and are now looking at the first one again.
    if (lpdds == g_lpddsOverlay)
        goto Exit;

    if (!LoadImageOntoSurface(lpdds, MAKEINTRESOURCE(nResourceID)))
    {
        hr = DDENUMRET_CANCEL;
        goto Exit;
    }

    nResourceID++;

    lpdds->EnumAttachedSurfaces((LPVOID)nResourceID, EnumAttachedCallback);

Exit:
    lpdds->Release();
    return DDENUMRET_OK;
}

//****************************************************************************
//* Function: DisplayOverlay
//*
//* Description:
//*     Displays the overlay on the primary surface
//****************************************************************************
BOOL DisplayOverlay()
{
    HRESULT         ddrval;
    RECT            rs, rd;
    DDOVERLAYFX     ovfx;
    DDCAPS          capsDrv;
    unsigned int    uStretchFactor1000;
    unsigned int    uDestSizeAlign, uSrcSizeAlign;
    DWORD           dwUpdateFlags;

    if(!g_lpdd || !g_lpddsPrimary || !g_lpddsOverlay)
        return FALSE;

    // Get driver capabilities
    INIT_DIRECTDRAW_STRUCT(capsDrv);
    ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
    if (FAILED(ddrval))
        return FALSE;

    // We need to check the minimum stretch.  Many display adpators require that
    // the overlay be stretched by a minimum amount.  The stretch factor will
    // usually vary with the display mode (including changes in refresh rate).
    // The stretch factor is returned x1000.
    uStretchFactor1000 = capsDrv.dwMinOverlayStretch>1000 ? capsDrv.dwMinOverlayStretch : 1000;

    // Grab any alignment restrictions.  The DDCAPS struct contains a series of
    // alignment fields that are not clearly defined. They are intended for
    // overlay use.  It's important to observe alignment restrictions.
    // Many adapters with overlay capabilities require the overlay image be
    // located on 4 or even 8 byte boundaries, and have similar restrictions
    // on the overlay width (for both source and destination areas).
    uDestSizeAlign = capsDrv.dwAlignSizeDest;
    uSrcSizeAlign =  capsDrv.dwAlignSizeSrc;

    // Set the flags we'll send to UpdateOverlay
    dwUpdateFlags = DDOVER_SHOW | DDOVER_DDFX;

    // Does the overlay hardware support source color keying?
    // If so, we can hide the black background around the image.
    // This probably won't work with YUV formats
    if (capsDrv.dwCKeyCaps & DDCKEYCAPS_SRCOVERLAY)
        dwUpdateFlags |= DDOVER_KEYSRCOVERRIDE;

    // Create an overlay FX structure so we can specify a source color key.
    // This information is ignored if the DDOVER_SRCKEYOVERRIDE flag isn't set.
    INIT_DIRECTDRAW_STRUCT(ovfx);
    ovfx.dckSrcColorkey.dwColorSpaceLowValue=0; // Specify black as the color key
    ovfx.dckSrcColorkey.dwColorSpaceHighValue=0;

    // Set up our Source Rect. This is the area of the overlay surface we
    // want to display.  If you want to display your entire surface and
    // happen to know for certain that your surface width meets any alignment
    // restrictions you can go ahead and pass NULL for the source rect in your
    // calls to UpdateOverlay().  Our surface width of 320 probably will meet
    // every alignment restriction, but just in case we'll create a source rect
    // and check for it.
    rs.left=0; rs.top=0; // position 0,0 is already position (boundary aligned)
    rs.right = 320;
    rs.bottom = 240;
    //Apply any size alignment restrictions if necessary.
    if (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC && uSrcSizeAlign)
        rs.right -= rs.right % uSrcSizeAlign;

    // Set up our destination rect, indicating where we want the overlay to
    // appear on the primary surface.  This is where we have to take into
    // account any stretch factor which may be needed to ensure the overlay is
    // displayed.  Really only the destination width must be stretched for the
    // overlay hardware to work, but we stretch the height as well just to
    // maintain a proper aspect ratio.

    // Note: We use the source rect dimensions, not the surface dimensions in
    // case they differ.
    // UpdateOverlay will fail unless the minimum stretch value is observed.

    rd.left=0; rd.top=0;
    rd.right  = (rs.right*uStretchFactor1000+999)/1000; // adding 999 takes care of integer truncation problems.
    rd.bottom = rs.bottom*uStretchFactor1000/1000;

    // It's also important to observe any alignment restrictions on size and
    // position with respect to the destination rect. Tweak the destination
    // width a bit more to get the size alignment correct (Be sure to round up
    // to keep any minimum stretch restrictions met). we'll assume the
    // position 0,0 is already "position aligned".
    if (capsDrv.dwCaps & DDCAPS_ALIGNSIZEDEST && uDestSizeAlign)
        rd.right = (int)((rd.right+uDestSizeAlign-1)/uDestSizeAlign)*uDestSizeAlign;

    // Make the call to UpdateOverlay() which actually displays the overlay on
    // the screen.
    ddrval = g_lpddsOverlay->UpdateOverlay(&rs, g_lpddsPrimary, &rd, dwUpdateFlags, &ovfx);
    if(FAILED(ddrval))
    {
        // Ok, the call to UpdateOVerlay() failed.  A likely cause is the
        // driver lied about the minimum stretch needed.
        // Ideally we should try upping the destination size a bit, or
        // perhaps shrinking the source size so the destination stretch
        // is effectively higher.   For this sample, however, we'll just
        // bail!
        return FALSE;
    }

    // Set the initial position and velocity for our overlay.  We'll actually
    // move the image around using a timer proc (see MoveOverlayTimerCallback
    // below).
    g_nOverlayXPos = 0;
    g_nOverlayYPos = 0;
    g_nOverlayXVel = RANDOM_VELOCITY();
    g_nOverlayYVel = RANDOM_VELOCITY();
    g_nOverlayWidth = rd.right - rd.left;
    g_nOverlayHeight = rd.bottom - rd.top;
    g_nOverlayFlipCounter=0;

    // Set the "destination position alignment" global so we won't have to
    // keep calling GetCaps() everytime we move the overlay surface.
    if (capsDrv.dwCaps & DDCAPS_ALIGNBOUNDARYDEST)
        g_dwOverlayXPositionAlignment = capsDrv.dwAlignBoundaryDest;
    else
        g_dwOverlayXPositionAlignment = 0;

    return TRUE;
}

//****************************************************************************
//* Function: AreOverlaysSupported
//*
//* Description:
//*     Determines whether or not the display hardware supports overlays.  If
//*     so, the function returns TRUE, otherwise FALSE.
//****************************************************************************
BOOL AreOverlaysSupported()
{
    DDCAPS  capsDrv;
    HRESULT ddrval;

    // Get driver capabilities to determine Overlay support.
    INIT_DIRECTDRAW_STRUCT(capsDrv);
    ddrval = g_lpdd->GetCaps(&capsDrv, NULL);
    if (FAILED(ddrval))
        return FALSE;

    // Does the driver support overlays in the current mode?
    // (Currently the DirectDraw emulation layer does not support overlays.
    // Overlay related APIs will fail without hardware support).
    if (!(capsDrv.dwCaps & DDCAPS_OVERLAY))
        return FALSE;

    return TRUE;
}

//****************************************************************************
//* Function: CreateOverlay
//*
//* Description:
//*     This is where we create the overlay surface, and put the flying insect
//*     artwork on it.
//****************************************************************************
BOOL CreateOverlay()
{
    DDSURFACEDESC   ddsdOverlay;
    HRESULT         ddrval;
    int             i;

    if (!g_lpdd || !g_lpddsPrimary)
        return FALSE;

    // It's currently not possible to query for pixel formats supported by the
    // overlay hardware (though GetFourCCCodes() usually provides a partial
    // list).  Instead you need to call CreateSurface() to try a variety of
    // formats till one works.
    INIT_DIRECTDRAW_STRUCT(ddsdOverlay);
ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_VIDEOMEMORY;
    ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_BACKBUFFERCOUNT| DDSD_PIXELFORMAT;
ddsdOverlay.dwWidth=320;
ddsdOverlay.dwHeight=240;
    ddsdOverlay.dwBackBufferCount=2;

    // Try to create an overlay surface using one of the pixel formats in our
    // global list.
    i=0;
    do
    {
            ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
            // Try to create the overlay surface
            ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
    } while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS) );

    // If we failed to create an overlay surface, let's try again with a single
    // (non-flippable) buffer.
    if(FAILED(ddrval))
    {
        // Just in case we're short on video memory or the hardware doesn't like flippable
        // overlay surfaces, let's make another pass using a single buffer.
        ddsdOverlay.dwBackBufferCount=0;
        ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY;
        ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT;
        // Try to create the overlay surface
        ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
        i=0;
        do
        {
                ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];
                ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL);
        } while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS) );

        // We just couldn't create an overlay surface.  Let's exit.
        if (FAILED(ddrval))
            return FALSE;
    }

    // Put the first bug image onto the first buffer of our complex surface.
    if (!LoadImageOntoSurface(g_lpddsOverlay, MAKEINTRESOURCE(IDB_BUGIMAGE1)))
        return FALSE;

    // This will recursively get all back buffers and load them with the appropriate image.
    ddrval = g_lpddsOverlay->EnumAttachedSurfaces((LPVOID)IDB_BUGIMAGE2, EnumAttachedCallback);
    if(FAILED(ddrval))
        return FALSE;

    return TRUE;
}

//****************************************************************************
//* Function: RestoreAllSurfaces
//****************************************************************************
BOOL RestoreAllSurfaces()
{
    HRESULT ddrval;

    // It's possible that our surfaces were destroyed in a prior call to
    // RestoreAllSurfaces().  If this happened we need to check for it,
    // and perhaps try to recreate the surfaces.
    if (!g_lpddsPrimary)
    {
        ddrval = CreatePrimarySurface();
        if (FAILED(ddrval))
        {
            // We probably couldn't recreate the primary because someone has
            // exclusive mode.
            g_lpddsPrimary = NULL;
            return FALSE;
        }
    }

    if(!g_lpddsOverlay)
        if (!CreateOverlay())
            return FALSE;

    // Try Restoring the primary surface.
    ddrval = g_lpddsPrimary->Restore();
    if(FAILED(ddrval))
    {
        // If we weren't able to restore the primary surface, It's probably
        // because some one else has exclusive mode, or the display mode
        // has been changed such that our primary surface needs to be recreated

        // Check to see if the mode changed on us.  Is so, we'll need to recreate
        // all surfaces.  (Note: we could have watched for the WM_DISPLAYCHANGE
        // message as well)
        if (ddrval == DDERR_WRONGMODE)
        {
            g_lpddsPrimary->Release();
            DestroyOverlay();
            g_lpddsPrimary = NULL;
            g_lpddsOverlay = NULL;

            return FALSE;
        }

        else
            return FALSE;
    }

    // Try Restoring the overlay surface.
    ddrval = g_lpddsOverlay->Restore();
    if (FAILED(ddrval))
        return FALSE;

    // Reload artwork onto overlay
    // Put the first bug image onto the first buffer of our complex surface.
    if ( !LoadImageOntoSurface(g_lpddsOverlay, MAKEINTRESOURCE(IDB_BUGIMAGE1)) )
        return FALSE;

    // This will recursively get all back buffers and load them with the appropriate image.
    ddrval = g_lpddsOverlay->EnumAttachedSurfaces((LPVOID)IDB_BUGIMAGE2, EnumAttachedCallback);
    if(FAILED(ddrval))
        return FALSE;

    // Redisplay overlay
    if(!DisplayOverlay())
        return FALSE;

    return TRUE;
}

//****************************************************************************
//* Function: MoveOverlayTimerCallback
//*
//* Description:
//*     TimeProc callback for moving the overlay surface around
//****************************************************************************
void CALLBACK MoveOverlayTimerCallback(HWND hwndUnused, UINT uUnused, UINT uUnused2, DWORD dwUnused)
{
    HRESULT ddrval;
    DWORD   dwXAligned;

    // Make sure the overlay really exists before we mess with it.
    if (!g_lpddsOverlay)
        if (!RestoreAllSurfaces())
            return;

    //Add the current velocity vectors to the position.
    g_nOverlayXPos += g_nOverlayXVel;
    g_nOverlayYPos += g_nOverlayYVel;

    // Check to see if this new position puts the overlay off the edge of the screen.
    // SetOverlayPosition() won't like that.

    // Have we gone off the left edge?
    if (g_nOverlayXPos < 0)
    {
        g_nOverlayXPos = 0;
        g_nOverlayXVel = RANDOM_VELOCITY();
    }

    // Have we gone off the right edge?
    if ( (g_nOverlayXPos+g_nOverlayWidth) >  GetSystemMetrics(SM_CXSCREEN))
    {
        g_nOverlayXPos = GetSystemMetrics(SM_CXSCREEN)-g_nOverlayWidth;
        g_nOverlayXVel = -RANDOM_VELOCITY();
    }

    // Have we gone off the top edge?
    if (g_nOverlayYPos < 0)
    {
        g_nOverlayYPos = 0;
        g_nOverlayYVel = RANDOM_VELOCITY();
    }

    // Have we gone off the bottom edge?
    if ( (g_nOverlayYPos+g_nOverlayHeight) >  GetSystemMetrics(SM_CYSCREEN))
    {
        g_nOverlayYPos = GetSystemMetrics(SM_CYSCREEN)-g_nOverlayHeight;
        g_nOverlayYVel = -RANDOM_VELOCITY();
    }

    // We need to check for any alignment restrictions on the X position
    if (g_dwOverlayXPositionAlignment)
        dwXAligned = g_nOverlayXPos - g_nOverlayXPos % g_dwOverlayXPositionAlignment;
    else
        dwXAligned = g_nOverlayXPos;

    // Set the overlay to it's new position.
    ddrval = g_lpddsOverlay->SetOverlayPosition(dwXAligned, g_nOverlayYPos);
    if (ddrval == DDERR_SURFACELOST)
    {
        if (!RestoreAllSurfaces())
            return;
    }

    // Every fourth time this timer proc is called, lets flip the overlay surface.
    if (g_nOverlayFlipCounter++ > 3)
    {
        g_nOverlayFlipCounter = 0; // reset counter
        // Flip to the next image in the sequence.  This is gonna fail if we
        // our overlay surface only contains one buffer (see CreateOverlay.
        // This is a possibility), but the failure should be benign.
        g_lpddsOverlay->Flip(NULL, DDFLIP_WAIT);
    }
}

//****************************************************************************
//* Function: DisplayError
//* Description:
//*    Displays an error message box.
//****************************************************************************
int DisplayError(HINSTANCE hInst, LPSTR lpstrErr)
{
    MSGBOXPARAMS mbp;

    mbp.cbSize = sizeof(mbp);
    mbp.hwndOwner=NULL;
    mbp.hInstance=hInst;

mbp.lpszText = lpstrErr;
    mbp.lpszCaption = "Mosquito Error!";
    mbp.dwStyle = MB_OK | MB_USERICON;
    mbp.lpszIcon = MAKEINTRESOURCE(IDI_BUGICON);
    mbp.dwContextHelpId = NULL;
    mbp.lpfnMsgBoxCallback = NULL;
    mbp.dwLanguageId = NULL;

    return MessageBoxIndirect(&mbp);
}

//****************************************************************************
//* Function: MainDialogProc
//****************************************************************************
LRESULT CALLBACK MainDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int uTimer=0;

switch (message)
    {
        case WM_INITDIALOG:
            // Create a timer to periodically move the overlay image around.
            uTimer = SetTimer(NULL, 0, 50, (TIMERPROC)MoveOverlayTimerCallback);
            break;

case WM_COMMAND:
            // Check to see if the "Quit" button was clicked.
            if ( IDQUIT == LOWORD(wParam))
            {
                KillTimer(NULL, uTimer);
    EndDialog(hDlg, TRUE);
return (TRUE);
}
break;

        case WM_CLOSE:
            KillTimer(NULL, uTimer);
    EndDialog(hDlg, TRUE);
return (TRUE);
}

    return FALSE;
}

//****************************************************************************
//* Function: WinMain(HANDLE, HANDLE, LPSTR, int)
//*
//* Description:
//*     Entry point for the application.  Since we use a simple dialog for
//*     user interaction we don't need to pump messages.
//*
//****************************************************************************
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    // Call DirectDraw Initialization function
    if (!InitDirectDraw())
        return FALSE;

    if (!AreOverlaysSupported())
    {
        // Display an error dialog if the hardware doesn't support overlays.
        DisplayError(hInstance, NO_OVERLAY_HARDWARE);
        return FALSE;
    }

    if (!CreateOverlay())
    {
        // display an error dialog if we couldn't create the overlay.
        DisplayError(hInstance, UNABLE_TO_CREATE_OVERLAY);
        return FALSE;
    }

    if (!DisplayOverlay())
    {
        // Display an error dialog if we couldn't display the overlay.
        DisplayError(hInstance, UNABLE_TO_DISPLAY_OVERLAY);
        return FALSE;
    }

    // Display our "Quit" dialog box.  As part of it's initialization it'll get the overlay going.
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_QUITDIALOG), NULL, (DLGPROC)MainDialogProc);

    DestroyOverlay();
    FreeDirectDraw();

    lpCmdLine; // This will prevent 'unused formal parameter' warnings

    return TRUE;
}


RESOURCE.H

#define IDD_QUITDIALOG                  101
#define IDD_SORRY                       102
#define IDB_BUGIMAGE1                   107
#define IDB_BUGIMAGE2                   108
#define IDB_BUGIMAGE3                   109
#define IDI_BUGICON                     110
#define IDQUIT                          1000
#define IDC_ERRMESSAGE                  1001


README.TXT

                                   MSTREAM

                        Design Description and Notes

   The MSTREAM sample application is an example of how you can use the new
midiStream API that's built into Windows 95 to play MIDI data with low
latency and low processor overhead.

   The basic idea behind the implementation is to start the application and
initialize the user interface.  After that's done, the following events occur.

When a user selects and opens a file
------------------------------------
Open a MIDI output device (the Microsoft MIDI Mapper in this case)
Open the file using buffered I/O and fill our buffers with data in MIDI stream
   format
Prepare and queue the buffers using midiOutPrepareHeader and midiStreamOut
Wait until the user decides to do something else

When PLAY is selected
---------------------
Call midiStreamRestart to un-pause the device and begin playback
As buffers are returned, the callback function fills them with more data and
sends them back to be played by calling midiStreamOut() again.  IT IS
IMPORTANT THAT YOU NOTE THAT IT IS NOT NECESSARY FOR YOU TO UNPREPARE AND
PREPARE BUFFERS EVERY TIME THEY ARE RETURNED.  This is only a waste of time.
All you have to do is send them back into the subsystem with midiStreamOut().
The API documentation is a bit confusing on this point.

When PAUSE is selected
----------------------
Call midiStreamPause() and wait for a PLAY or PAUSE to call midiStreamRestart().
Note that pausing may make your audio sound kind of funny, since all notes are
turned off and some note on events may be lost when playback is restarted.

When STOP is selected
---------------------
Call midiStreamStop().  Since the callback function is in another thread, we
use a Win32 synchronization object, an event to block the main thread until
the callback thread has received all buffers.  When this happens, we know it's
okay to go ahead and call midiStreamReset() and then to go ahead and free all
our buffers.  Currently, the device is also closed using midiStreamClose().
Then, if we are resetting the file to playback starting at its beginning point
the next time PLAY is hit, reopen the file by calling StreamBufferSetup(),
which is the workhorse function for opening a file and initializing the
the converter and the buffers.

Note that the reason we must close and reopen the device is due to an apparent
bug in the Multimedia System which causes undesireable playback once a device
has been stopped.  If you disable the code for closing the device, then the rest
of the code will automatically know it does not have to reopen the device.
However, the following may occur: After the first instance of playback followed
by a midiStreamStop() and a midiStreamReset(), there will be a pause equal in
length to the first playback period before playback begins once more with a call
to midiStreamRestart().

If Looped is selected
---------------------
Notice that the converter has a little function in it called RewindConverter()
which is called by ConvertToBuffer() if the bLooped variable is TRUE.  This
function resets the track state structures and performs most of the steps
originally executed in ConvertInit() with the notable exception of opening the
file and reading in file and track header data.  It simply resets the tracks
to their initial state.

More on the buffering scheme
----------------------------
Note that there is an OUT_BUFFER_SIZE and a BUFFER_TIME_LENGTH.  The idea is
that the converter counts ticks and calculates when it has put at least
BUFFER_TIME_LENGTH milliseconds worth of events in the buffer.  At this point,
it returns to the caller with a "full" buffer.  At the very worst, there will
be as many events as can fit into OUT_BUFFER_SIZE bytes.  Imagine that you have
to MIDI files loaded into memory somehow and you want to switch between them in
a hurry to correspond to some action the player performed like switching rooms.
All you have to do is start filling the next buffer with new data (assuming
they streams use similar patch sets, time division settings, etc.) and after
(NUM_STREAM_BUFFERS-1)*BUFFER_TIME_LENGTH milliseconds, the music will switch
over automatically.  This theory is sort of illustrated by the tempo trackbar
control in this sample.  This control sets a flag which forces the converter
code to start a new buffer, with the first event being a new tempo setting.
The tempo setting is calculated as a relative increase with respect to the
last real tempo event from the file.  Of course, to implement the scheme
mentioned above requires some modification to the converter code so that it
will work with multiple MIDI files.

Since the buffering scheme uses very small buffers, it is currently rather
sensitive to heavy activity which may prevent it from completing processing
in time.  This can be solved by increasing the NUM_STREAM_BUFFERS constant,
but you must make a trade-off between latency and playback stability.

Known problems and possible improvements:
-----------------------------------------
It is more desireable to enumerate all possible MIDI output devices and then
   either allow the user to use a specific device, or choose one which has
   desireable capabilities.  It is not recommended that you ship a product
   which is hard-coded to use the MIDI Mapper only.  For more on enumerating
   MIDI output devices, see the MIDIPLYR sample application which is part of
   the Win32 SDK.

Instead of using the BUFFER_TIME_LENGTH, it would be possible to handle the
   time signature META event in MIDI files and calculate the length of a
   measure of music.  Then you could change buffers at the end of each measure,
   which would probably yield a smoother sounding transition.  It may even be
   possible to define system-exclusive events or other such extensions to the
   MIDI converter code designed to provide your application with extra data
   about when to switch between buffers or do other processing, though it is
   not necessarily recommended that you modify the MIDI file format spec.  For
   more information on that spec, contact the International MIDI Association.

You may wish to modify the way a change in tempo is handled, or remove this
   code entirely.  Right now, there is a chunk of code in the convert function
   AddEventToStreamBuffer() which detects tempo events and stores the new tempo.
   There is also code which will react to the tempo slider by calculating a new
   tempo, truncating the current buffer, and starting the next buffer with a
   tempo event reflecting the new desired tempo. It may be more desireable to
   force any tempo changes which are not encoded in the file originally to take
   effect only on buffer boundaries, instead of always creating a buffer
   boundary.  Proceeding under the above context of buffers equal in length to
   measures of music, it may make more sense to only change tempo between each
   measure.  You can also send tempo change messages using the midiOutShortMsg()
   function, similar to the way SetAllChannelVolumes() behaves.

The volume control is a channel-wide, percentage-based control which relies on
   a cache of volumes for each channel.  As the converter encounters a volume
   change message, it flags it for a callback.  This causes the MidiProc() to
   receive notification when that event is reached.  MidiProc() then grabs a
   copy of the new volume event and sends a MIDI short message to the proper
   channel which reflects the current slider position.  In other words, the
   code saves the "full" or "raw" value and then modifies it so the volume
   trackbar represents a percentage of that volume.  Though it is not shown
   here, this scheme could be broken down to allow for individual volume
   control also.  This idea could also be expanded to include the LSB volume
   controller(39), which is not handled here.

Further, by duplicating the volume code described above and making slight
   modifications to the converter (to detect other events), it is possible to
   handle pan, balance, or other controller messages using the exact same idea.

Having said the above, it should be noted that attaching the volume change code
   to a trackbar is for illustration purposes only.  The implementation shown
   and described works best for isolated volume events, like when your player
   moves away from the sound source and you need to update volume.  It should
   not really be used for real-time scrolling because the method tends to flood
   the MIDI output device with short messages, which interferes severly with
   playback.

BUG: If you are using an internal MIDI device which uses the OPL chipset, you
   should be aware of a bug which seems to occur in most of these drivers.  If
   a volume channel message is sent to these drivers, they will not reflect the
   change until a note on/off event occurs.  This means long sustaining notes
   will not reduce in volume.


DEBUG.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:debug.h
 *  Content:debug header
 *
 ***************************************************************************/
#ifndef __DEBUG_INCLUDED__
#define __DEBUG_INCLUDED__
#ifdef __cplusplus
extern "C"
{
#endif

//
//
//
//
#ifdef DEBUG
    #define DEBUG_SECTION       "Debug"     // section name for
    #define DEBUG_MODULE_NAME   "MSTREAM"   // key name and prefix for output
    #define DEBUG_MAX_LINE_LEN  255         // max line length (bytes!)
#endif

//
//  based code makes since only in win 16 (to try and keep stuff out of
//  [fixed] data segments, etc)...
//
#ifndef BCODE
#ifdef _WIN32
    #define BCODE
#else
    #define BCODE           _based(_segname("_CODE"))
#endif
#endif

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifdef DEBUG
    BOOL WINAPI DbgEnable(BOOL fEnable);
    UINT WINAPI DbgGetLevel(void);
    UINT WINAPI DbgSetLevel(UINT uLevel);
    UINT WINAPI DbgInitialize(BOOL fEnable);
    void WINAPI _Assert( char * szFile, int iLine );

    void FAR CDECL dprintf(UINT uDbgLevel, LPSTR szFmt, ...);

    #define D(x)        {x;}
    #define DPF         dprintf
    #define DPI(sz)     {static char BCODE ach[] = sz; OutputDebugStr(ach);}
    #define ASSERT(x)   if( !(x) )  _Assert( __FILE__, __LINE__)
#else
    #define DbgEnable(x)        FALSE
    #define DbgGetLevel()       0
    #define DbgSetLevel(x)      0
    #define DbgInitialize(x)    0

    #ifdef _MSC_VER
    #pragma warning(disable:4002)
    #endif

    #define D(x)
    #define DPF()
    #define DPI(sz)
    #define ASSERT(x)
#endif

#ifdef __cplusplus
}
#endif
#endif  // __DEBUG_INCLUDED__


MIDSTUFF.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:midstuff.h
 *  Content:MIDI structures and definitions used by the MSTREAM sample
 *              application in converting a MID file to a MIDI stream for
 *              playback using the midiStream API.
 *
 ***************************************************************************/
#ifndef __MIDSTUFF_H__
#define __MIDSTUFF_H__

// MIDI file constants
//
#define MThd0x6468544D// Start of file
#define MTrk0x6B72544D// Start of track

#define MIDI_SYSEX((BYTE)0xF0)// SysEx begin
#define MIDI_SYSEXEND((BYTE)0xF7)// SysEx begin
#define MIDI_META((BYTE)0xFF)// Meta event begin
#define MIDI_META_TEMPO((BYTE)0x51)// Tempo change
#define MIDI_META_EOT((BYTE)0x2F)// End-of-track

#define MIDI_NOTEOFF((BYTE)0x80)// + note + velocity
#define MIDI_NOTEON((BYTE)0x90)// + note + velocity
#define MIDI_POLYPRESS  ((BYTE)0xA0)// + pressure (2 bytes)
#define MIDI_CTRLCHANGE ((BYTE)0xB0)// + ctrlr + value
#define MIDI_PRGMCHANGE((BYTE)0xC0)// + new patch
#define MIDI_CHANPRESS((BYTE)0xD0)// + pressure (1 byte)
#define MIDI_PITCHBEND((BYTE)0xE0)// + pitch bend (2 bytes)

#define NUM_CHANNELS16

#define MIDICTRL_VOLUME((BYTE)0x07)
#define MIDICTRL_VOLUME_LSB((BYTE)0x27)
#define MIDICTRL_PAN((BYTE)0x0A)

#define MIDIEVENT_CHANNEL(dw)(dw & 0x0000000F)
#define MIDIEVENT_TYPE(dw)(dw & 0x000000F0)
#define MIDIEVENT_DATA1(dw)((dw & 0x0000FF00) >> 8)
#define MIDIEVENT_VOLUME(dw)((dw & 0x007F0000) >> 16)

// Macros for swapping hi/lo-endian data
//
#define WORDSWAP(w)(((w) >> 8) | \
(((w) << 8) & 0xFF00))

#define DWORDSWAP(dw) (((dw) >> 24) |\
(((dw) >> 8) & 0x0000FF00) |\
(((dw) << 8) & 0x00FF0000) |\
(((dw) << 24) & 0xFF000000))

// In debug builds, TRACKERR will show us where the parser died
//
#ifdef DEBUG
#define TRACKERR(p,sz) ShowTrackError(p,sz);
#else
#define TRACKERR(p,sz)
#endif

// Make a little distinction here so the various structure members are a bit
// more clearly labelled -- we have offsets and byte counts to keep track of
// that deal with both in-memory buffers and the file on disk

#defineFILEOFFDWORD

// These structures are stored in MIDI files; they need to be byte aligned.
//
#pragma pack(1)

// Chunk header. dwTag is either MTrk or MThd.
//
typedef struct
{
    DWORDdwTag;// Type
    DWORDdwChunkLength;// Length (hi-lo)
} MIDICHUNK;

// Contents of MThd chunk.
typedef struct
{
    WORDwFormat;// Format (hi-lo)
    WORDwTrackCount;// # tracks (hi-lo)
    WORDwTimeDivision;// Time division (hi-lo)
} MIDIFILEHDR;

#pragma pack() // End of need for byte-aligned structures

// Temporary event structure which stores event data until we're ready to
// dump it into a stream buffer
//
typedef struct
{
    DWORDtkEvent;// Absolute time of event
    BYTEbyShortData[4];// Event type and parameters if channel msg
    DWORDdwEventLength;// Length of data which follows if meta or sysex
    LPBYTEpLongData;// -> Event data if applicable
} TEMPEVENT, *PTEMPEVENT;

#define ITS_F_ENDOFTRK0x00000001

// Description of a track open for read
//
typedef struct
{
    DWORDfdwTrack;// Track status
    DWORDdwTrackLength;// Total bytes in track
    DWORDdwLeftInBuffer;// Bytes left unread in track buffer
    LPBYTEpTrackStart;// -> start of track data buffer
    LPBYTEpTrackCurrent;// -> next byte to read in buffer
    DWORDtkNextEventDue;// Absolute time of next event in track
    BYTEbyRunningStatus;// Running status from last channel msg

    FILEOFFfoTrackStart;// Start of track -- used for walking the file
    FILEOFFfoNextReadStart;// File offset of next read from disk
    DWORDdwLeftOnDisk;// Bytes left unread on disk
#ifdef DEBUG
    DWORDnTrack;// # of this track for debugging
#endif
} INTRACKSTATE, *PINTRACKSTATE;

// Description of the input MIDI file
//
typedef struct
{
    DWORDcbFileLength;// Total bytes in file
    DWORDdwTimeDivision;// Original time division
    DWORDdwFormat;// Original format
    DWORDdwTrackCount;// Track count (specifies pitsTracks size)
    INTRACKSTATE *pitsTracks;// -> array of tracks in this file
} INFILESTATE, *PINFILESTATE;

#endif


MSTRCONV.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:mstrconv.c
 *  Content:Converts a MIDI file into a midiStream, placing the results
 *              in a buffer that the MSTREAM sample application can use for
 *              playback with the midiStream* API under Win95.
 *
 ***************************************************************************/
#include <windows.h>
#include <windowsx.h>

#include <mmsystem.h>
#include <assert.h>
#include <stdio.h>

#include "debug.h"
#include "mstream.h"
#include "midstuff.h"

// Global stuff which is defined in the main module
//
extern char szTemp[256];
extern char szDebug[256];
extern char szAppTitle[64];

BOOLbInsertTempo = FALSE;

// A few global variables used by this module only
//
static HANDLEhInFile= INVALID_HANDLE_VALUE;
INFILESTATEifs;
static DWORDtkCurrentTime;

// Tracks how many malloc blocks exist. If there are any and we decide to shut
// down, we must scan for them and free them.  Malloc blocks are only created as
// temporary storgae blocks for extra parameter data associated with MIDI_SYSEX,
// MIDI_SYSEXEND, and MIDI_META events.
static DWORDdwMallocBlocks= 0;

extern DWORDdwBufferTickLength, dwTempoMultiplier, dwCurrentTempo;
extern DWORDdwProgressBytes, dwVolumePercent;
extern BOOLbLooped;

// Messages
//
static char szInitErrMem[]= "Out of memory.\n";
static char szInitErrInFile[]= "Read error on input file or file is corrupt.\n";
static char szNoTrackBuffMem[]= "Insufficient memory for track buffer allocation\n";

#ifdef DEBUG
static char gteBadRunStat[] = "Reference to missing running status.";
static char gteRunStatMsgTrunc[]= "Running status message truncated";
static char gteChanMsgTrunc[]= "Channel message truncated";
static char gteSysExLenTrunc[]= "SysEx event truncated (length)";
static char gteSysExTrunc[]= "SysEx event truncated";
static char gteMetaNoClass[]= "Meta event truncated (no class byte)";
static char gteMetaLenTrunc[]= "Meta event truncated (length)";
static char gteMetaTrunc[]= "Meta event truncated";
static char gteNoMem[]= "Out of memory during malloc call";
#endif

// Prototypes
//
static intAddEventToStreamBuffer( PTEMPEVENT pteTemp, LPCONVERTINFO );
static BOOLGetInFileData( LPVOID lpDest, DWORD cbToGet );
static BOOLGetTrackByte( PINTRACKSTATE ptsTrack, LPBYTE lpbyByte );
static BOOLGetTrackEvent( PINTRACKSTATE ptsTrack, PTEMPEVENT pteTemp );
static BOOLGetTrackVDWord( PINTRACKSTATE ptsTrack, LPDWORD lpdw );
static BOOLRefillTrackBuffer( PINTRACKSTATE ptsTrack );
static BOOLRewindConverter( void );

#ifdef DEBUG
static void ShowTrackError( PINTRACKSTATE ptsTrack, char* szErr );
#endif

// ConverterInit
//
// Open the input file
// Allocate and read the entire input file into memory
// Validate the input file structure
// Allocate the input track structures and initialize them
// Initialize the output track structures
//
// Return TRUE on success
// Prints its own error message if something goes wrong
//
BOOL ConverterInit( LPSTR szInFile )
    {
    BOOLfRet = TRUE;
    DWORDcbRead, dwTag, cbHeader, dwToRead;
    MIDIFILEHDRHeader;
    PINTRACKSTATEptsTrack;
    UINTidx;

    tkCurrentTime = 0;

    // Initialize things we'll try to free later if we fail
    //
    memset( &ifs, 0, sizeof(INFILESTATE));
    ifs.cbFileLength = 0;
    ifs.pitsTracks = NULL;

    // Attempt to open the input and output files
    //
    hInFile = CreateFile( szInFile, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
    if( hInFile == INVALID_HANDLE_VALUE )
{
wsprintf( szTemp, "Could not open \"%s\" for read.\n", szInFile );
MessageBox( GetActiveWindow(), szTemp,
szAppTitle, MB_OK | MB_ICONEXCLAMATION );
goto Init_Cleanup;
}

// Figure out how big the input file is.
    if((( ifs.cbFileLength = GetFileSize( hInFile, NULL )) == (UINT)-1 ))
{
MessageBox( GetActiveWindow(), "File system error on input file.\n",
        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
goto Init_Cleanup;
}

// Set up to read from the memory buffer. Read and validate
// - MThd header
// - size of file header chunk
// - file header itself
//
    if( GetInFileData( &dwTag, sizeof(DWORD))
    || ( dwTag != MThd )
    || GetInFileData( &cbHeader, sizeof(DWORD))
    || (( cbHeader = DWORDSWAP( cbHeader )) < sizeof(MIDIFILEHDR))
            || GetInFileData( &Header, cbHeader ) )
        {
        MessageBox( GetActiveWindow(), szInitErrInFile,
        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
        goto Init_Cleanup;
        }

// File header is stored in hi-lo order. Swap this into Intel order and save
// parameters in our native int size (32 bits)
//
    ifs.dwFormat = (DWORD)WORDSWAP( Header.wFormat );
    ifs.dwTrackCount = (DWORD)WORDSWAP( Header.wTrackCount );
    ifs.dwTimeDivision = (DWORD)WORDSWAP( Header.wTimeDivision );

// We know how many tracks there are; allocate the structures for them and parse
// them. The parse merely looks at the MTrk signature and track chunk length
// in order to skip to the next track header.
//
    ifs.pitsTracks = (PINTRACKSTATE)GlobalAllocPtr( GPTR,
    ifs.dwTrackCount * sizeof(INTRACKSTATE));
    if( ifs.pitsTracks == NULL )
        {
        MessageBox( GetActiveWindow(), szInitErrMem,
        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
        goto Init_Cleanup;
        }

    for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount;
    ++idx, ++ptsTrack )
{
if(( ptsTrack->pTrackStart
= GlobalAllocPtr( GHND, TRACK_BUFFER_SIZE )) == NULL )
    {
    MessageBox( GetActiveWindow(), szNoTrackBuffMem,
    szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    goto Init_Cleanup;
    }

if( GetInFileData( &dwTag, sizeof(dwTag)) || ( dwTag != MTrk )
|| GetInFileData( &cbHeader, sizeof(cbHeader)))
    {
    MessageBox( GetActiveWindow(), szInitErrInFile,
    szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    goto Init_Cleanup;
    }

cbHeader = DWORDSWAP( cbHeader );
ptsTrack->dwTrackLength = cbHeader;// Total track length
///////////////////////////////////////////////////////////////////////////////
// Here we need to determine if all track data will fit into a single one of
// our track buffers.  If not, we need to read in a buffer full and come back
// for more later, saving the file offset to continue from and the amount left
// to read in the track structure.

// Save the file offset of the beginning of this track
ptsTrack->foTrackStart = SetFilePointer( hInFile, 0, NULL,
FILE_CURRENT );

if( ptsTrack->dwTrackLength > TRACK_BUFFER_SIZE )
    dwToRead = TRACK_BUFFER_SIZE;
else
    dwToRead = ptsTrack->dwTrackLength;
if( !ReadFile( hInFile, ptsTrack->pTrackStart, dwToRead, &cbRead, NULL )
|| ( cbRead != dwToRead ))
    {
    MessageBox( GetActiveWindow(), szInitErrInFile,
szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    goto Init_Cleanup;
    }
// Save the number of bytes that didn't make it into the buffer
ptsTrack->dwLeftOnDisk = ptsTrack->dwTrackLength - cbRead;
ptsTrack->dwLeftInBuffer = cbRead;
// Save the current file offset so we can seek to it later
ptsTrack->foNextReadStart = SetFilePointer( hInFile, 0,
NULL, FILE_CURRENT );

        // Setup pointer to the current position in the track
        ptsTrack->pTrackCurrent = ptsTrack->pTrackStart;
        ptsTrack->fdwTrack = 0;
        ptsTrack->byRunningStatus = 0;
        ptsTrack->tkNextEventDue = 0;

        // Handle bozo MIDI files which contain empty track chunks
        //
        if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
            {
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            continue;
            }

        // We always preread the time from each track so the mixer code can
        // determine which track has the next event with a minimum of work
        //
        if( GetTrackVDWord( ptsTrack, &ptsTrack->tkNextEventDue ))
            {
            MessageBox( GetActiveWindow(), szInitErrInFile,
            szAppTitle, MB_OK | MB_ICONEXCLAMATION );
            goto Init_Cleanup;
            }
// Step over any unread data, advancing to the beginning of the next
// track's data
SetFilePointer( hInFile, ptsTrack->foTrackStart + ptsTrack->dwTrackLength,
NULL, FILE_BEGIN );
        }// End of track initialization code

    fRet = FALSE;

    Init_Cleanup:
    if( fRet )
        ConverterCleanup();

    return( fRet );
    }

//
// GetInFileData
//
// Gets the requested number of bytes of data from the input file and returns
// a pointer to them.
//
// Returns a pointer to the data or NULL if we'd read more than is
// there.
//
static BOOL GetInFileData( LPVOID lpDest, DWORD cbToGet )
    {
    DWORDcbRead;

    if( !ReadFile( hInFile, lpDest, cbToGet, &cbRead, NULL )
    || ( cbRead != cbToGet ))
        {
        return( TRUE );
        }

    return( FALSE );
    }

//
// ConverterCleanup
//
// Free anything we ever allocated
//
void ConverterCleanup( void )
    {
    DWORD   idx;

    if( hInFile != INVALID_HANDLE_VALUE )
        {
        CloseHandle( hInFile );
hInFile = INVALID_HANDLE_VALUE;
}

    if( ifs.pitsTracks )
        {
// De-allocate all our track buffers
for( idx = 0; idx < ifs.dwTrackCount; idx++ )
    if( ifs.pitsTracks[idx].pTrackStart )
GlobalFreePtr( ifs.pitsTracks[idx].pTrackStart );

        GlobalFreePtr( ifs.pitsTracks );
ifs.pitsTracks = NULL;
}
    }

/*****************************************************************************/
/* RewindConverter()                                                         */
/*                                                                           */
/*   This little function is an adaptation of the ConverterInit() code which */
/* resets the tracks without closing and opening the file, thus reducing the */
/* time it takes to loop back to the beginning when looping.                 */
/*****************************************************************************/
static BOOL RewindConverter( void )
    {
    DWORD   dwToRead, cbRead, idx;
    BOOL    fRet;

    PINTRACKSTATEptsTrack;

    tkCurrentTime = 0;

    for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount;
    ++idx, ++ptsTrack )
{
///////////////////////////////////////////////////////////////////////////////
// Here we need to determine if all track data will fit into a single one of
// our track buffers.  If not, we need to read in a buffer full and come back
// for more later, saving the file offset to continue from and the amount left
// to read in the track structure.

SetFilePointer( hInFile, ptsTrack->foTrackStart, NULL, FILE_BEGIN );

if( ptsTrack->dwTrackLength > TRACK_BUFFER_SIZE )
    dwToRead = TRACK_BUFFER_SIZE;
else
    dwToRead = ptsTrack->dwTrackLength;
if( !ReadFile( hInFile, ptsTrack->pTrackStart, dwToRead, &cbRead, NULL )
|| ( cbRead != dwToRead ))
    {
    MessageBox( GetActiveWindow(), szInitErrInFile,
szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    goto Rewind_Cleanup;
    }
// Save the number of bytes that didn't make it into the buffer
ptsTrack->dwLeftOnDisk = ptsTrack->dwTrackLength - cbRead;
ptsTrack->dwLeftInBuffer = cbRead;
// Save the current file offset so we can seek to it later
ptsTrack->foNextReadStart = SetFilePointer( hInFile, 0,
NULL, FILE_CURRENT );

        // Setup pointer to the current position in the track
        ptsTrack->pTrackCurrent = ptsTrack->pTrackStart;
        ptsTrack->fdwTrack = 0;
        ptsTrack->byRunningStatus = 0;
        ptsTrack->tkNextEventDue = 0;

        // Handle bozo MIDI files which contain empty track chunks
        //
        if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
            {
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            continue;
            }

        // We always preread the time from each track so the mixer code can
        // determine which track has the next event with a minimum of work
        //
        if( GetTrackVDWord( ptsTrack, &ptsTrack->tkNextEventDue ))
            {
            MessageBox( GetActiveWindow(), szInitErrInFile,
            szAppTitle, MB_OK | MB_ICONEXCLAMATION );
            goto Rewind_Cleanup;
            }
// Step over any unread data, advancing to the beginning of the next
// track's data
SetFilePointer( hInFile, ptsTrack->foTrackStart + ptsTrack->dwTrackLength,
NULL, FILE_BEGIN );
        }// End of track initialization code

    fRet = FALSE;

    Rewind_Cleanup:

    if( fRet )
return( TRUE );

    return( FALSE );
    }

/*****************************************************************************/
/* ConvertToBuffer()                                                         */
/*                                                                           */
/*    This function converts MIDI data from the track buffers setup by a     */
/* previous call to ConverterInit().  It will convert data until an error is */
/* encountered or the output buffer has been filled with as much event data  */
/* as possible, not to exceed dwMaxLength. This function can take a couple   */
/* bit flags, passed through dwFlags. Information about the success/failure  */
/* of this operation and the number of output bytes actually converted will  */
/* be returned in the CONVERTINFO structure pointed at by lpciInfo.          */
/*                                                                           */
/*****************************************************************************/
int ConvertToBuffer( DWORD dwFlags, LPCONVERTINFO lpciInfo )
    {
    static INTRACKSTATE*ptsTrack, *ptsFound;
    static DWORDdwStatus;
    static DWORDtkNext;
    static TEMPEVENTteTemp;

    int    nChkErr;
    DWORD   idx;

    lpciInfo->dwBytesRecorded = 0;

    if( dwFlags & CONVERTF_RESET )
{
dwProgressBytes = 0;
dwStatus = 0;
memset( &teTemp, 0, sizeof(TEMPEVENT));
ptsTrack = ptsFound = NULL;
}
    // If we were already done, then return with a warning...
    if( dwStatus & CONVERTF_STATUS_DONE )
{
if( bLooped )
    {
    RewindConverter();
    dwProgressBytes = 0;
    dwStatus = 0;
    }
else
    return( CONVERTERR_DONE );
}
    // The caller is asking us to continue, but we're already hosed because we
    // previously identified something as corrupt, so complain louder this time.
    else if( dwStatus & CONVERTF_STATUS_STUCK )
    {
return( CONVERTERR_STUCK );
}
    else if( dwStatus & CONVERTF_STATUS_GOTEVENT )
    {
// Turn off this bit flag
dwStatus ^= CONVERTF_STATUS_GOTEVENT;

/*
 *  The following code for this case is duplicated from below, and is
 * designed to handle a "straggler" event, should we have one left over
 * from previous processing the last time this function was called.
 */

// Don't add end of track event 'til we're done
//
if( teTemp.byShortData[0] == MIDI_META
    && teTemp.byShortData[1] == MIDI_META_EOT )
    {
    if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
    }

else if(( nChkErr = AddEventToStreamBuffer( &teTemp, lpciInfo ))
!= CONVERTERR_NOERROR )
    {
    if( nChkErr == CONVERTERR_BUFFERFULL )
{
// Do some processing and tell caller that this buffer's full
dwStatus |= CONVERTF_STATUS_GOTEVENT;
return( CONVERTERR_NOERROR );
}
    else if( nChkErr == CONVERTERR_METASKIP )
{
// We skip by all meta events that aren't tempo changes...
}
    else
{
DebugPrint( "Unable to add event to stream buffer." );
if( dwMallocBlocks )
    {
    free( teTemp.pLongData );
    dwMallocBlocks--;
    }
return( TRUE );
}
    }
}

    for( ; ; )
        {
        ptsFound = NULL;
        tkNext = 0xFFFFFFFFL;
        // Find nearest event due
        //
        for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount;
            ++idx, ++ptsTrack )
            {
            if(( !( ptsTrack->fdwTrack & ITS_F_ENDOFTRK ))
&& ( ptsTrack->tkNextEventDue < tkNext ))
                {
                tkNext = ptsTrack->tkNextEventDue;
                ptsFound = ptsTrack;
                }
    }

// None found?  We must be done, so return to the caller with a smile.
//
if( !ptsFound )
    {
    dwStatus |= CONVERTF_STATUS_DONE;
            // Need to set return buffer members properly
    return( CONVERTERR_NOERROR );
    }

// Ok, get the event header from that track
//
if( GetTrackEvent( ptsFound, &teTemp ))
            {
    // Warn future calls that this converter is stuck at a corrupt spot
    // and can't continue
            dwStatus |= CONVERTF_STATUS_STUCK;
            return( CONVERTERR_CORRUPT );
            }

// Don't add end of track event 'til we're done
//
if( teTemp.byShortData[0] == MIDI_META
    && teTemp.byShortData[1] == MIDI_META_EOT )
    {
    if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
    continue;
    }

if(( nChkErr = AddEventToStreamBuffer( &teTemp, lpciInfo ))
!= CONVERTERR_NOERROR )
    {
    if( nChkErr == CONVERTERR_BUFFERFULL )
{
// Do some processing and tell somebody this buffer is full...
dwStatus |= CONVERTF_STATUS_GOTEVENT;
return( CONVERTERR_NOERROR );
}
    else if( nChkErr == CONVERTERR_METASKIP )
{
// We skip by all meta events that aren't tempo changes...
}
    else
{
DebugPrint( "Unable to add event to stream buffer." );
if( dwMallocBlocks )
    {
    free( teTemp.pLongData );
    dwMallocBlocks--;
    }
return( TRUE );
}
    }
        }

    return( CONVERTERR_NOERROR );
    }

//
// GetTrackVDWord
//
// Attempts to parse a variable length DWORD from the given track. A VDWord
// in a MIDI file
//  (a) is in lo-hi format
//  (b) has the high bit set on every byte except the last
//
// Returns the DWORD in *lpdw and TRUE on success; else
// FALSE if we hit end of track first. Sets ITS_F_ENDOFTRK
// if we hit end of track.
//
static BOOL GetTrackVDWord( PINTRACKSTATE ptsTrack, LPDWORD lpdw )
    {
    BYTE    byByte;
    DWORD   dw = 0;

    if( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )
        return( TRUE );

    do
        {
        if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
            {
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            return( TRUE );
            }

        if( GetTrackByte( ptsTrack, &byByte ))
    return( TRUE );

        dw = ( dw << 7 ) | ( byByte & 0x7F );
        } while( byByte & 0x80 );

    *lpdw = dw;

    return( FALSE );
    }

//
// GetTrackEvent
//
// Fills in the event struct with the next event from the track
//
// pteTemp->tkEvent will contain the absolute tick time of the event
// pteTemp->byShortData[0] will contain
//  MIDI_META if the event is a meta event;
//   in this case pteTemp->byShortData[1] will contain the meta class
//  MIDI_SYSEX or MIDI_SYSEXEND if the event is a SysEx event
//  Otherwise, the event is a channel message and pteTemp->byShortData[1]
//   and pteTemp->byShortData[2] will contain the rest of the event.
//
// pteTemp->dwEventLength will contain
//  The total length of the channel message in pteTemp->byShortData if
//   the event is a channel message
//  The total length of the paramter data pointed to by
//   pteTemp->pLongData otherwise
//
// pteTemp->pLongData will point at any additional paramters if the
//  event is a SysEx or meta event with non-zero length; else
//  it will contain NULL
//
// Returns FALSE on success or TRUE on any kind of parse error
// Prints its own error message ONLY in the debug version
//
// Maintains the state of the input track (i.e. ptsTrack->dwLeftInBuffer,
// ptsTrack->pTrackPointers, and ptsTrack->byRunningStatus).
//
static BOOL GetTrackEvent( INTRACKSTATE *ptsTrack, PTEMPEVENT pteTemp )
    {
    DWORD   idx;
    BYTE    byByte;
    UINT    dwEventLength;

    // Clear out the temporary event structure to get rid of old data...
    memset( pteTemp, 0, sizeof(TEMPEVENT));

    // Already at end of track? There's nothing to read.
    //
    if(( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )
    || ( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk ))
        return( TRUE );

    // Get the first byte, which determines the type of event.
    //
    if( GetTrackByte( ptsTrack, &byByte ))
return( TRUE );

    // If the high bit is not set, then this is a channel message
    // which uses the status byte from the last channel message
    // we saw. NOTE: We do not clear running status across SysEx or
    // meta events even though the spec says to because there are
    // actually files out there which contain that sequence of data.
    //
    if( !( byByte & 0x80 ))
        {
        // No previous status byte? We're hosed.
        if( !ptsTrack->byRunningStatus )
            {
            TRACKERR(ptsTrack, gteBadRunStat);
            return( TRUE );
            }

        pteTemp->byShortData[0] = ptsTrack->byRunningStatus;
        pteTemp->byShortData[1] = byByte;

        byByte = pteTemp->byShortData[0] & 0xF0;
        pteTemp->dwEventLength = 2;

        // Only program change and channel pressure events are 2 bytes long;
        // the rest are 3 and need another byte
        //
        if(( byByte != MIDI_PRGMCHANGE ) && ( byByte != MIDI_CHANPRESS ))
            {
            if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
                {
                TRACKERR( ptsTrack, gteRunStatMsgTrunc );
                ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
                return( TRUE );
                }

            if( GetTrackByte( ptsTrack, &pteTemp->byShortData[2] ))
return( TRUE );
            ++pteTemp->dwEventLength;
            }
        }
    else if(( byByte & 0xF0 ) != MIDI_SYSEX )
        {
        // Not running status, not in SysEx range - must be
        // normal channel message (0x80-0xEF)
        //
        pteTemp->byShortData[0] = byByte;
        ptsTrack->byRunningStatus = byByte;

        // Strip off channel and just keep message type
        //
        byByte &= 0xF0;

        dwEventLength = ( byByte == MIDI_PRGMCHANGE || byByte == MIDI_CHANPRESS ) ? 1 : 2;
        pteTemp->dwEventLength = dwEventLength + 1;

        if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk ) < dwEventLength )
            {
            TRACKERR( ptsTrack, gteChanMsgTrunc );
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            return( TRUE );
            }

if( GetTrackByte( ptsTrack, &pteTemp->byShortData[1] ))
    return( TRUE );
        if( dwEventLength == 2 )
            if( GetTrackByte( ptsTrack, &pteTemp->byShortData[2] ))
return( TRUE );
        }
    else if(( byByte == MIDI_SYSEX ) || ( byByte == MIDI_SYSEXEND ))
        {
        // One of the SysEx types. (They are the same as far as we're concerned;
        // there is only a semantic difference in how the data would actually
        // get sent when the file is played. We must take care to put the proper
        // event type back on the output track, however.)
        //
        // Parse the general format of:
        //  BYTE bEvent (MIDI_SYSEX or MIDI_SYSEXEND)
        //  VDWORD cbParms
        //  BYTE   abParms[cbParms]
        //
        pteTemp->byShortData[0] = byByte;
        if( GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength ))
            {
            TRACKERR( ptsTrack, gteSysExLenTrunc );
            return( TRUE );
            }

        if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk )
            < pteTemp->dwEventLength )
            {
            TRACKERR( ptsTrack, gteSysExTrunc );
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            return( TRUE );
            }

// Malloc a temporary memory block to hold the parameter data
if(( pteTemp->pLongData = malloc( pteTemp->dwEventLength )) == NULL )
    {
    TRACKERR( ptsTrack, gteNoMem );
    return( TRUE );
    }
// Copy from the input buffer to the parameter data buffer
for( idx = 0; idx < pteTemp->dwEventLength; idx++ )
    if( GetTrackByte( ptsTrack, pteTemp->pLongData + idx ))
{
TRACKERR( ptsTrack, gteSysExTrunc );
return( TRUE );
}
// Increment our counter, which tells the program to look around for
// a malloc block to free, should it need to exit or reset before the
// block would normally be freed
dwMallocBlocks++;
        }
    else if( byByte == MIDI_META )
        {
        // It's a meta event. Parse the general form:
        //  BYTEbEvent(MIDI_META)
        //  BYTEbClass
        //  VDWORDcbParms
        //  BYTEabParms[cbParms]
        //
        pteTemp->byShortData[0] = byByte;

        if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
            {
            TRACKERR(ptsTrack, gteMetaNoClass );
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
            return( TRUE );
            }

if( GetTrackByte( ptsTrack, &pteTemp->byShortData[1] ))
    return( TRUE );

        if( GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength ))
            {
            TRACKERR( ptsTrack, gteMetaLenTrunc );
            return( TRUE );
            }

        // NOTE: It's perfectly valid to have a meta with no data
        // In this case, dwEventLength == 0 and pLongData == NULL
        //
        if( pteTemp->dwEventLength )
            {
            if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk )
            < pteTemp->dwEventLength )
                {
                TRACKERR( ptsTrack, gteMetaTrunc );
                ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
                return( TRUE );
                }

// Malloc a temporary memory block to hold the parameter data
    if(( pteTemp->pLongData = malloc( pteTemp->dwEventLength ))
        == NULL )
{
TRACKERR( ptsTrack, gteNoMem );
return( TRUE );
}
// Copy from the input buffer to the parameter data buffer
    for( idx = 0; idx < pteTemp->dwEventLength; idx++ )
 if( GetTrackByte( ptsTrack, pteTemp->pLongData + idx ))
     {
     TRACKERR( ptsTrack, gteMetaTrunc );
     return( TRUE );
     }
// Increment our counter, which tells the program to look around for
// a malloc block to free, should it need to exit or reset before the
// block would normally be freed
    dwMallocBlocks++;
            }

        if( pteTemp->byShortData[1] == MIDI_META_EOT )
            ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
        }
    else
        {
        // Messages in this range are system messages and aren't supposed to
        // be in a normal MIDI file. If they are, we've either misparsed or the
        // authoring software is stupid.
        //
        return( TRUE );
        }

    // Event time was already stored as the current track time
    //
    pteTemp->tkEvent = ptsTrack->tkNextEventDue;

    // Now update to the next event time. The code above MUST properly
    // maintain the end of track flag in case the end of track meta is
    // missing.  NOTE: This code is a continuation of the track event
    // time pre-read which is done at the end of track initialization.
    //
    if( !( ptsTrack->fdwTrack & ITS_F_ENDOFTRK ))
        {
        DWORDtkDelta;

        if( GetTrackVDWord( ptsTrack, &tkDelta ))
            return( TRUE );

        ptsTrack->tkNextEventDue += tkDelta;
        }

    return( FALSE );
    }

//
// GetTrackByte
//
// Retrieve the next byte from the track buffer, refilling the buffer from
// disk if necessary.
//
static BOOL GetTrackByte( PINTRACKSTATE ptsTrack, LPBYTE lpbyByte )
    {
    if( !ptsTrack->dwLeftInBuffer )
{
if( RefillTrackBuffer( ptsTrack ))
    return( TRUE );
}

    *lpbyByte = *ptsTrack->pTrackCurrent++;
    ptsTrack->dwLeftInBuffer--;
    return( FALSE );
    }

//
// RefillTrackBuffer()
//
// This function attempts to read in a buffer-full of data for a MIDI track.
//
BOOL RefillTrackBuffer( PINTRACKSTATE ptsTrack )
    {
    DWORDdwBytesRead, dwResult;
    BOOLbResult;

    if( ptsTrack->dwLeftOnDisk )
        {
        ptsTrack->pTrackCurrent = ptsTrack->pTrackStart;

        // Seek to the proper place in the file, indicated by
        // ptsTrack->foNextReadStart and read in the remaining data,
        // up to a maximum of the buffer size.

        if(( dwResult = SetFilePointer( hInFile,

(long)ptsTrack->foNextReadStart,
        0L, FILE_BEGIN )) == 0xFFFFFFFF )
            {
            MessageBox( GetActiveWindow(),
    "Unable to seek to track buffer location in RefillTrackBuffer()!!",
    szAppTitle, MB_OK | MB_ICONEXCLAMATION );
            return( TRUE );
            }
        if( ptsTrack->dwLeftOnDisk > TRACK_BUFFER_SIZE )
            ptsTrack->dwLeftInBuffer = TRACK_BUFFER_SIZE;
        else
            ptsTrack->dwLeftInBuffer = ptsTrack->dwLeftOnDisk;
        bResult = ReadFile( hInFile, ptsTrack->pTrackStart,
ptsTrack->dwLeftInBuffer,
&dwBytesRead, NULL );

        ptsTrack->dwLeftOnDisk -= dwBytesRead;
        ptsTrack->foNextReadStart = dwResult + dwBytesRead;
ptsTrack->dwLeftInBuffer = dwBytesRead;

        if( !bResult || ( bResult && !dwBytesRead )
    || ( bResult && dwBytesRead != ptsTrack->dwLeftInBuffer ))
            {
            MessageBox( GetActiveWindow(),
"Read operation failed prematurely!!",
szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    ptsTrack->dwLeftInBuffer = dwBytesRead;
            return( TRUE );
            }
        else
    return( FALSE );
        }

    return( TRUE );
    }

//
// AddEventToStreamBuffer
//
// Put the given event into the given stream buffer at the given location
// pteTemp must point to an event filled out in accordance with the
// description given in GetTrackEvent
//
// Returns FALSE on sucess or TRUE on an error condition
// Handles its own error notification by displaying to the appropriate
// output device (either our debugging window, or the screen).
//
static int AddEventToStreamBuffer( PTEMPEVENT pteTemp, CONVERTINFO *lpciInfo )
    {
    DWORDtkNow, tkDelta;
    MIDIEVENT*pmeEvent;

    pmeEvent = (MIDIEVENT *)( lpciInfo->mhBuffer.lpData
    + lpciInfo->dwStartOffset
    + lpciInfo->dwBytesRecorded );

    // When we see a new, empty buffer, set the start time on it...
    if( !lpciInfo->dwBytesRecorded )
lpciInfo->tkStart = tkCurrentTime;

    // Use the above set start time to figure out how much longer we should fill
    // this buffer before officially declaring it as "full"
    if( tkCurrentTime - lpciInfo->tkStart > dwBufferTickLength )
if( lpciInfo->bTimesUp )
    {
    lpciInfo->bTimesUp = FALSE;
    return( CONVERTERR_BUFFERFULL );
    }
else
    lpciInfo->bTimesUp = TRUE;

    tkNow = tkCurrentTime;

    // Delta time is absolute event time minus absolute time
    // already gone by on this track
    tkDelta = pteTemp->tkEvent - tkCurrentTime;

    // Event time is now current time on this track
    tkCurrentTime = pteTemp->tkEvent;

    if( bInsertTempo )
{
bInsertTempo = FALSE;

if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD))
    {
    // Cleanup from our write operation
    return( CONVERTERR_BUFFERFULL );
    }
if( dwCurrentTempo )
    {
    pmeEvent->dwDeltaTime = 0;
    pmeEvent->dwStreamID = 0;
    pmeEvent->dwEvent = ( dwCurrentTempo * 100 ) / dwTempoMultiplier;
    pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;

    lpciInfo->dwBytesRecorded += 3 * sizeof(DWORD);
    pmeEvent += 3 * sizeof(DWORD);
    }
}

    if( pteTemp->byShortData[0] < MIDI_SYSEX )
        {
        // Channel message. We know how long it is, just copy it.
        // Need 3 DWORD's: delta-t, stream-ID, event
if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD))
    {
    // Cleanup from our write operation
    return( CONVERTERR_BUFFERFULL );
    }

pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
pmeEvent->dwEvent = ( pteTemp->byShortData[0] )
| (((DWORD)pteTemp->byShortData[1] ) << 8 )
| (((DWORD)pteTemp->byShortData[2] ) << 16 )
| MEVT_F_SHORT;

        if((( pteTemp->byShortData[0] & 0xF0) == MIDI_CTRLCHANGE )
        && ( pteTemp->byShortData[1] == MIDICTRL_VOLUME ))
    {
    // If this is a volume change, generate a callback so we can grab
    // the new volume for our cache
    pmeEvent->dwEvent |= MEVT_F_CALLBACK;
    }
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
}
    else if(( pteTemp->byShortData[0] == MIDI_SYSEX )
|| ( pteTemp->byShortData[0] == MIDI_SYSEXEND ))
{
DebugPrint( "AddEventToStreamBuffer: Ignoring SysEx event." );
if( dwMallocBlocks )
    {
    free( pteTemp->pLongData );
    dwMallocBlocks--;
    }
}
    else
{
// Better be a meta event.
//  BYTEbyEvent
//  BYTEbyEventType
//  VDWORDdwEventLength
//  BYTEpLongEventData[dwEventLength]
        //
        assert( pteTemp->byShortData[0] == MIDI_META );

        // The only meta-event we care about is change tempo
        //
        if( pteTemp->byShortData[1] != MIDI_META_TEMPO )
            {
    if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
            return( CONVERTERR_METASKIP );
    }

        // We should have three bytes of parameter data...
        assert( pteTemp->dwEventLength == 3 );

        // Need 3 DWORD's: delta-t, stream-ID, event data
if( lpciInfo->dwMaxLength - lpciInfo->dwBytesRecorded < 3 *sizeof(DWORD))
    {
    // Cleanup the temporary event if necessary and return
    if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
    return( CONVERTERR_BUFFERFULL );
    }

pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
// Note: this is backwards from above because we're converting a single
//       data value from hi-lo to lo-hi format...
pmeEvent->dwEvent = ( pteTemp->pLongData[2] )
| (((DWORD)pteTemp->pLongData[1] ) << 8 )
| (((DWORD)pteTemp->pLongData[0] ) << 16 );

/* This next step has absolutely nothing to do with the conversion of a
 * MIDI file to a stream, it's simply put here to add the functionality
 * of the tempo slider. If you don't need this, be sure to remove the
 * next two lines.
 */
dwCurrentTempo = pmeEvent->dwEvent;
pmeEvent->dwEvent = (pmeEvent->dwEvent * 100 ) / dwTempoMultiplier;

pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;

dwBufferTickLength = ( ifs.dwTimeDivision * 1000 * BUFFER_TIME_LENGTH ) / dwCurrentTempo;
wsprintf( szTemp, "dwBufferTickLength = %lu", dwBufferTickLength );
DebugPrint( szTemp );

if( dwMallocBlocks )
    {
    free( pteTemp->pLongData );
    dwMallocBlocks--;
    }
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
}

    return( FALSE );
    }

#ifdef DEBUG
static void ShowTrackError( PINTRACKSTATE ptsTrack, LPSTR lpszErr )
    {
    wsprintf( szTemp, "Track buffer offset %lu",
    (DWORD)(ptsTrack->pTrackCurrent - ptsTrack->pTrackStart));
    DebugPrint( szTemp );
    wsprintf( szTemp, "Track total %lu  Track left %lu",
    ptsTrack->dwTrackLength, ptsTrack->dwLeftInBuffer );
    DebugPrint( szTemp );
    }

#endif


MSTREAM.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   mstream.c
 *  Content:   Illustrates streaming data from a disk MIDI file to a
 *             MIDI stream buffer for playback.
 *
 ***************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <memory.h>
#include <mmreg.h>

#include "resource.h"
#include "debug.h"
#include "midstuff.h"
#include "mstream.h"

//////////////////////////////////////////////////////////////////////////////
// Lots of global variables

char szAppClass[] = "MStreamWndClass";
char szAppName[]  = "MStream";

char szAppTitle[64];
char szAppCaption[64];
char szOpenFilter[128];
char szOpenDLGTitle[64];
char szProgress[64];
char szTempo[64];
char szVolume[64];

char szTemp[256];
char szDebug[256];
char szFileBuffer[MAX_PATH];
char szFileTitle[MAX_PATH];

HWNDhWndMain, hWndProgText, hWndProg, hWndVolText, hWndVol, hWndTempoText;
HWNDhWndTempo, hWndLoopCheck, hWndPlay, hWndPause, hWndStop;

HINSTANCEhInst;

BOOLbFileOpen = FALSE, bPlaying = FALSE, bBuffersPrepared = FALSE;
BOOLbPaused = FALSE, bLooped = FALSE;
UINTuMIDIDeviceID = MIDI_MAPPER, uCallbackStatus;
intnTextControlHeight, nCurrentBuffer, nEmptyBuffers;
DWORDdwBufferTickLength, dwTempoMultiplier, dwCurrentTempo, dwProgressBytes;
DWORDdwVolumePercent, dwVolCache[NUM_CHANNELS];

HMIDISTRM    hStream;
CONVERTINFO  ciStreamBuffers[NUM_STREAM_BUFFERS];

// Private to this module...
static HANDLEhBufferReturnEvent;

// From another module...
extern INFILESTATEifs;

///////////////////////////////////////////////////////////////////////////////
// Module-scope function declarations

static BOOL InitApp( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void FreeBuffers( void );

/******************************************************************************
 * WinMain()
 *
 * Entry point for all Windows programs - performs initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
    {
    MSG     msg;

    hInst = hInstance;

    /* Make sure the common controls are loaded for our use */
    InitCommonControls();

// Turn debugging output on or off
#ifdef DEBUG
    DbgInitialize( TRUE );
#else
    DbgInitialize( FALSE );
#endif

    if( !hPrevInstance )
        if( !InitApp( hInstance ))
            {
            ErrorMessageBox( IDS_ERROR_APPINIT, MB_ICONSTOP );
            return( FALSE );
    }

    if( !InitInstance( hInstance, nCmdShow ))
        {
        ErrorMessageBox( IDS_ERROR_INSTANCEINIT, MB_ICONSTOP );
        return( FALSE );
}

    while( GetMessage((LPMSG)&msg, NULL, 0, 0 ))
        {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
        }

    UnregisterClass( szAppClass, hInstance );
    return( msg.wParam );
    } /* End of WinMain() */

/*****************************************************************************/
/* InitApp()     */
/*      */
/*   Inits things that only need to be created once for the this application */
/* (like creating the window class).     */
/*****************************************************************************/
static BOOL InitApp( HINSTANCE hInstance )
    {
    WNDCLASSwc;

    /* Set up and register a window class */
    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName= szAppClass;
    wc.lpfnWndProc= (WNDPROC)MainWindowProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= sizeof( DWORD );
    wc.hInstance= hInstance;
    wc.hIcon= LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON3 ));
    wc.hCursor= LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground= (HBRUSH)( COLOR_WINDOW );
    wc.lpszMenuName= MAKEINTRESOURCE( IDR_MAINMENU );

    if( !RegisterClass( &wc ))
        {
ErrorMessageBox( IDS_ERROR_REGISTERCLASS, MB_ICONSTOP );
        return( FALSE );
        }
    return( TRUE );
    } /* End of InitApp() */

/*****************************************************************************/
/* InitInstance()     */
/*      */
/* Performs initialization that must be done for each application instance.  */
/*      */
/*****************************************************************************/
static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
    {
    HWNDhWnd;
    RECTcrect;
    UINTuCharsRead;
    MMRESULTmmrRetVal;

    LoadString( hInstance, IDS_APP_TITLE, szAppTitle, sizeof(szAppTitle));
    LoadString( hInstance, IDS_APP_CAPTION, szAppCaption, sizeof(szAppCaption));
    LoadString( hInstance, IDS_TBTITLE_VOLUME, szVolume, sizeof(szVolume));
    LoadString( hInstance, IDS_TBTITLE_TEMPO, szTempo, sizeof(szTempo));
    LoadString( hInstance, IDS_TBTITLE_PROGRESS,
        szProgress, sizeof(szProgress));
    LoadString( hInstance, IDS_OPEN_DLGTITLE,
    szOpenDLGTitle, sizeof(szOpenDLGTitle));
/* This is a little trick designed to allow us to load a common dialog box
 * filter string, which is really a concatentation of several NULL-terminated
 * strings. Note that while is is possible to enter something else into the
 * resource as placeholders for the NULL characters, this has the undesireable
 * effect of forcing us to search-and-replace byte-by-byte and doesn't make it
 * as easy to internationalize our strings...
 */
    memset( szOpenFilter, 0, sizeof(szOpenFilter));
    uCharsRead = LoadString( hInstance, IDS_OPEN_FILTER1,
szOpenFilter, sizeof(szOpenFilter)) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER2,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead ) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER3,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead ) + 1;
    LoadString( hInstance, IDS_OPEN_FILTER4,
    &szOpenFilter[uCharsRead],
    sizeof(szOpenFilter) - uCharsRead );

    /* Create an application window */
    hWnd = CreateWindow( szAppClass,/* class name */
szAppCaption,/* caption for window */
WS_OVERLAPPEDWINDOW,/* style */
CW_USEDEFAULT,/* x position */
CW_USEDEFAULT,/* y position */
CW_USEDEFAULT,/* width */
CW_USEDEFAULT,/* height */
NULL,/* parent window */
NULL,/* menu */
hInstance,/* instance */
NULL );/* parms */

    if( !hWnd )
        {
ErrorMessageBox( IDS_ERROR_MAINWNDCREATE, MB_ICONSTOP );
        return( FALSE );
}

    hWndMain = hWnd;
    GetClientRect( hWndMain, &crect );

    /* Create some controls for things like volume, tempo, progress, etc. */
    if( CreateChildren( crect ))
        return( FALSE );

    // Resize window, now that we know the height of the static text controls
    SetWindowPos( hWnd, NULL, 0, 0,
        2 * BORDER_SPACE_CX + TEMPO_TB_CX + 2 * CONTROL_SPACE_CX
        + VOL_TB_CX + CHECK_CX,
    2 * BORDER_SPACE_CY + nTextControlHeight + TEXT_SPACE_CY
    + CONTROL_SPACE_CY + BUTTON_CY + TEMPO_TB_CY,
    SWP_NOMOVE | SWP_NOZORDER );

    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    if(( mmrRetVal = midiStreamOpen( &hStream,
    &uMIDIDeviceID,
    (DWORD)1, (DWORD)MidiProc,
    (DWORD)0,
    CALLBACK_FUNCTION )) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
return( FALSE );
}

    return( TRUE );
    } /* End of InitInstance() */

/****************************************************************************/
/* MainWindowProc()                                                         */
/*                                                                          */
/*    Messages for our main window are handled here                         */
/*                                                                          */
/****************************************************************************/
LRESULT CALLBACK MainWindowProc( HWND hWnd, unsigned uMsg,
WPARAM wParam, LPARAM lParam )
    {
    LPMINMAXINFO    lpMinMax;
    DWORDdwCDErr = 0;
    BOOLbResult = FALSE;
    MMRESULTmmrRetVal;

    switch( uMsg )
        {
case WM_CREATE:
    hBufferReturnEvent = CreateEvent( NULL, FALSE,
    FALSE, "Wait For Buffer Return" );
    break;

case WM_MSTREAM_UPDATEVOLUME:
    SetChannelVolume( wParam, dwVolumePercent );
    break;

case WM_MSTREAM_PROGRESS:
    /* Set the Progress text */
            wsprintf( szTemp, "%s: %lu bytes", szProgress, dwProgressBytes );
            Static_SetText( hWndProgText, szTemp );
    break;

        case WM_COMMAND:
            switch( LOWORD( wParam ))
                {
                case IDM_FILE_OPEN:
                    {
                    OPENFILENAMEofn;
    /*
     * Clear out and fill in an OPENFILENAME structure in preparation
     * for creating a common dialog box to open a file.
     */
                    memset( &ofn, 0, sizeof(OPENFILENAME));
                    ofn.lStructSize= sizeof(OPENFILENAME);
                    ofn.hwndOwner= hWnd;
                    ofn.hInstance= hInst;
                    ofn.lpstrFilter= szOpenFilter;
                    ofn.nFilterIndex= 1;
                    szFileBuffer[0]= '\0';
                    ofn.lpstrFile= szFileBuffer;
                    ofn.nMaxFile= sizeof(szFileBuffer);
                    ofn.lpstrFileTitle= szFileTitle;
                    ofn.nMaxFileTitle= sizeof(szFileTitle);
                    ofn.lpstrDefExt= "MID";
                    ofn.lpstrTitle= szOpenDLGTitle;
                    ofn.Flags= OFN_FILEMUSTEXIST;

                    bResult = GetOpenFileName( &ofn ); /* Do the dialog box */

    /*
     *A return of TRUE indicates that the user did not select a filename.
     * The possible reasons are: Cancel was clicked, or an error occured.
     * If Cancel was clicked, the CommDlgExtendedError() function will not
     * return a valid code.  For anything else, an error code will come back.
     */
                    if( bResult == FALSE )
                        {
                        dwCDErr = CommDlgExtendedError();
                        if( dwCDErr )
                            {
                            /* Handle a common dialog box error */
                            HandleCommDlgError( dwCDErr );
                            }
                        else/* Clicked Cancel, so finish msg processing */
                            return( 0 );
                        }
                    else
                        {
                        if( bFileOpen )
                            {
// Need to close the previous file before we open a new one.  The best way to
// do this is by faking a menu command, so that we only have the actual code in
// one place and it can easily be changed.
                            SendMessage( hWnd, WM_COMMAND,
                            MAKEWPARAM( IDM_FILE_CLOSE, 0 ), 0L );
                            }

                        if( StreamBufferSetup())
                            {
                            // Error opening the MIDI file so abort
    // The function already took care of notification
    break;
                            }
                        else
                            {
                            bFileOpen = TRUE;
                            EnableWindow( hWndPlay, TRUE );
                            BuildTitleBarText();
                            }
                        }
                    }
                    break;

                case IDM_FILE_CLOSE:
    SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_STOP,
MSTREAM_STOPF_NOREOPEN ), 0L );
                    BuildTitleBarText();
                    break;

                case IDM_HELP_ABOUT:
                    DialogBox( hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndMain,
                    (DLGPROC)DLG_About );
                    break;

case IDM_ACTIONS_PAUSE:
                case IDC_PAUSE:
    if( bPaused )
midiStreamRestart( hStream );
    else
midiStreamPause( hStream );
    bPaused = !bPaused;
                    // If we're paused, the title bar will show (Paused)
                    BuildTitleBarText();
    break;

case IDM_ACTIONS_LOOPED:
case IDC_LOOPCHECK:
    Button_SetCheck( hWndLoopCheck, !bLooped );
    bLooped = !bLooped;
    break;

case IDM_ACTIONS_PLAY:
                case IDC_PLAY:
    // Clicking play while playback is paused will un-pause it
    if( bPaused )
{
SendMessage( hWnd, WM_COMMAND,
MAKEWPARAM( IDC_PAUSE, 0 ), 0L );
break;
}
                    // Clicking play while playing will restart from scratch
                    if( bPlaying )
    {
    // Stop the file, allowing it to bve reset so that we
// can start it over again from the beginning
    SendMessage( hWnd, WM_COMMAND,
    MAKEWPARAM( IDC_STOP, 0 ), 0L );
}

                    if( bFileOpen )
                        {
                        // Clear the status of our callback so it will handle
// MOM_DONE callbacks once more
                        uCallbackStatus = 0;
                        if(( mmrRetVal = midiStreamRestart( hStream ))
                        != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    break;
    }
                        }
                    else
                        {
                        bPlaying = FALSE;
                        break;
                        }

    bPlaying = TRUE;
    EnableWindow( hWndPause, TRUE );
    EnableWindow( hWndStop, TRUE );
                    break;

                case IDM_ACTIONS_STOP:
                case IDC_STOP:
                    if( bFileOpen || bPlaying
                    || ( uCallbackStatus != STATUS_CALLBACKDEAD ))
{
EnableWindow( hWndStop, FALSE );
EnableWindow( hWndPause, FALSE );
bPlaying = bPaused = FALSE;
if( uCallbackStatus != STATUS_CALLBACKDEAD && uCallbackStatus != STATUS_WAITINGFOREND )
    uCallbackStatus = STATUS_KILLCALLBACK;

                        if(( mmrRetVal = midiStreamStop( hStream ))
                        != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    break;
    }
                        if(( mmrRetVal = midiOutReset( (HMIDIOUT)hStream ))
                        != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    break;
    }
// Wait for the callback thread to release this thread, which it will do by
// calling SetEvent() once all buffers are returned to it
                        if( WaitForSingleObject( hBufferReturnEvent,
                        DEBUG_CALLBACK_TIMEOUT )
                            == WAIT_TIMEOUT )
    {
// Note, this is a risky move because the callback may be genuinely busy, but
// when we're debugging, it's safer and faster than freezing the application,
// which leaves the MIDI device locked up and forces a system reset...
    DebugPrint( "Timed out waiting for MIDI callback" );
    uCallbackStatus = STATUS_CALLBACKDEAD;
    }
                        }

    if( uCallbackStatus == STATUS_CALLBACKDEAD )
{
uCallbackStatus = 0;
if( bFileOpen )
    {
    ConverterCleanup();
    FreeBuffers();
    if( hStream )
{
if(( mmrRetVal = midiStreamClose( hStream ))
    != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    }
hStream = NULL;
}

    EnableWindow( hWndPlay, FALSE );
    bFileOpen = FALSE;
    }

                        if(!( HIWORD(wParam) & MSTREAM_STOPF_NOREOPEN ))
                            {
                            if( StreamBufferSetup())
{
// Error setting up for MIDI file
// Notification is already taken care of...
break;
}
                            else
                                {
                                bFileOpen = TRUE;
                                EnableWindow( hWndPlay, TRUE );
                                }
                            }
BuildTitleBarText();// Update the title bar
                        }
                    break;

                case IDM_FILE_EXIT:
                    DestroyWindow( hWnd );
                    break;
                }
            break;

case WM_INITMENU:
    if( bFileOpen )
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PLAY,
                                            MF_BYCOMMAND | MF_ENABLED );
EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
                                            MF_BYCOMMAND | MF_ENABLED );
}
    else
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PLAY,
                                            MF_BYCOMMAND | MF_GRAYED );
EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
                                            MF_BYCOMMAND | MF_GRAYED );
}
    if( bLooped )
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_LOOPED,
                                            MF_BYCOMMAND | MF_ENABLED );
    else
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_LOOPED,
                                            MF_BYCOMMAND | MF_GRAYED );
    if( bPlaying )
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PAUSE,
                                            MF_BYCOMMAND | MF_ENABLED );
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_STOP,
                                            MF_BYCOMMAND | MF_ENABLED );
}
    else
{
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_PAUSE,
                                            MF_BYCOMMAND | MF_GRAYED );
EnableMenuItem( GetMenu( hWnd ), IDM_ACTIONS_STOP,
                                            MF_BYCOMMAND | MF_GRAYED );
}
    break;

        case WM_GETMINMAXINFO:
    /*
     * We know exactly how big this window should be, and it's sort of a
     * little pop-up control panel, so we can disable window sizing by
     * forcing all the minimum and maximum sizes to be the calculated size.
     */
            lpMinMax = (LPMINMAXINFO)lParam;

            lpMinMax->ptMaxSize.x = 2*CONTROL_SPACE_CX + 2*BORDER_SPACE_CX
                                    + CHECK_CX + TEMPO_TB_CX + VOL_TB_CX
                                    + 2*GetSystemMetrics( SM_CXBORDER );
            lpMinMax->ptMaxSize.y = 2*(BORDER_SPACE_CY
                                    + GetSystemMetrics( SM_CYBORDER ))
                                    + TEXT_SPACE_CY + nTextControlHeight
                                    + TEMPO_TB_CY + BUTTON_CY
                                    + CONTROL_SPACE_CY
                                    + GetSystemMetrics( SM_CYMENU )
                                    + GetSystemMetrics( SM_CYCAPTION );

            lpMinMax->ptMinTrackSize.x = lpMinMax->ptMaxTrackSize.x
                            = lpMinMax->ptMaxSize.x;

            lpMinMax->ptMinTrackSize.y = lpMinMax->ptMaxTrackSize.y
                            = lpMinMax->ptMaxSize.y;
            break;

        case WM_HSCROLL:
            if(((HWND)lParam == hWndTempo) && bFileOpen )
                {
                HandleTempoScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            else if(((HWND)lParam == hWndVol) && bFileOpen )
                {
                HandleVolScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
                }
            break;

case WM_ENDSESSION:
    // If the sesson is ending, we need to do our WM_DESTROY processing
    if (!wParam) break;
    // NOTE!!! we are falling through to the WM_CLOSE processing.
        case WM_DESTROY:
            // Stop anything that might be playing and send a flag which will
    // tell the code not to automatically reload the file for replay.
            if( hStream )
SendMessage( hWnd, WM_COMMAND,
                          MAKEWPARAM( IDC_STOP, MSTREAM_STOPF_NOREOPEN ), 0 );

    FreeBuffers();

    if( hStream )
{
if(( mmrRetVal = midiStreamClose( hStream ))
    != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    }
hStream = NULL;
}

            CloseHandle( hBufferReturnEvent );

            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
        }
    return 0L;
    } /* MainWindowProc */

/*****************************************************************************/
/* DLG_About()                                                               */
/*                                                                           */
/*   Dialog procedure for the Help...About... box which simply pops up a     */
/* little copyright message and brief program description.                   */
/*                                                                           */
/*****************************************************************************/
BOOL CALLBACK DLG_About( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
    {
    switch( msg )
        {
        case WM_INITDIALOG:
            break;

        case WM_COMMAND:
            switch( LOWORD(wParam))
                {
                case IDOK:
                    EndDialog( hDlg, FALSE );
                    return( TRUE );

                default:
                    break;
                }
            break;

        default:
            return( FALSE );
        }

    return( FALSE );
    }

/*****************************************************************************/
/* FreeBuffers()                                                             */
/*                                                                           */
/*   This function unprepares and frees all our buffers -- something we must */
/* do to work around a bug in MMYSYSTEM that prevents a device from playing  */
/* back properly unless it is closed and reopened after each stop.           */
/*****************************************************************************/
void FreeBuffers( void )
    {
    DWORDidx;
    MMRESULTmmrRetVal;

    if( bBuffersPrepared )
{
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
    if(( mmrRetVal = midiOutUnprepareHeader( (HMIDIOUT)hStream,
&ciStreamBuffers[idx].mhBuffer,
sizeof(MIDIHDR)))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
}
    bBuffersPrepared = FALSE;
    }
// Free our stream buffers...
for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
    if( ciStreamBuffers[idx].mhBuffer.lpData )
{
GlobalFreePtr( ciStreamBuffers[idx].mhBuffer.lpData );
ciStreamBuffers[idx].mhBuffer.lpData = NULL;
}
    }

/*****************************************************************************/
/* StreamBufferSetup()                                                       */
/*                                                                           */
/*    This function uses the filename stored in the global character array to*/
/* open a MIDI file. Then it goes about converting at least the first part of*/
/* that file into a midiStream buffer for playback.                          */
/*****************************************************************************/
BOOL StreamBufferSetup( void )
    {
    int     nChkErr;
    BOOL    bFoundEnd = FALSE;
    DWORD   dwConvertFlag, idx;

    MMRESULTmmrRetVal;
    MIDIPROPTIMEDIVmptd;

    if( !hStream )
if(( mmrRetVal = midiStreamOpen( &hStream,
    &uMIDIDeviceID,
    (DWORD)1, (DWORD)MidiProc,
    (DWORD)0,
    CALLBACK_FUNCTION )) != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    return( TRUE );
    }

    for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ )
{
ciStreamBuffers[idx].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
if(( ciStreamBuffers[idx].mhBuffer.lpData
= GlobalAllocPtr( GHND, OUT_BUFFER_SIZE )) == NULL )
    {
    // Buffers we already allocated will be killed by WM_DESTROY
    // after we fail on the create by returning with -1
    return( -1 );
    }
}
    if( ConverterInit( szFileBuffer ))
return( TRUE );

    // Initialize the volume cache array to some pre-defined value
    for( idx = 0; idx < NUM_CHANNELS; idx++ )
dwVolCache[idx] = VOL_CACHE_INIT;

    mptd.cbStruct = sizeof(mptd);
    mptd.dwTimeDiv = ifs.dwTimeDivision;
    if(( mmrRetVal = midiStreamProperty( hStream, (LPBYTE)&mptd,
    MIDIPROP_SET | MIDIPROP_TIMEDIV ))
    != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return( TRUE );
}

    nEmptyBuffers = 0;
    dwConvertFlag = CONVERTF_RESET;

    for( nCurrentBuffer = 0; nCurrentBuffer < NUM_STREAM_BUFFERS;
        nCurrentBuffer++ )
{
    // Tell the converter to convert up to one entire buffer's length of output
    // data. Also, set a flag so it knows to reset any saved state variables it
    // may keep from call to call.
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
ciStreamBuffers[nCurrentBuffer].tkStart = 0;
        ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;

if(( nChkErr = ConvertToBuffer( dwConvertFlag,
&ciStreamBuffers[nCurrentBuffer] ))
    != CONVERTERR_NOERROR )
        {
    if( nChkErr == CONVERTERR_DONE )
{
bFoundEnd = TRUE;
}
    else
{
DebugPrint( "Initial conversion pass failed" );
ConverterCleanup();
return( TRUE );
}
    }
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
    = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;

if( !bBuffersPrepared )
    if(( mmrRetVal = midiOutPrepareHeader( (HMIDIOUT)hStream,
    &ciStreamBuffers[nCurrentBuffer].mhBuffer,
    sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return( TRUE );
}
if(( mmrRetVal = midiStreamOut( hStream,
    &ciStreamBuffers[nCurrentBuffer].mhBuffer,
    sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    break;
    }
dwConvertFlag = 0;

if( bFoundEnd )
    break;
}

    bBuffersPrepared = TRUE;
    nCurrentBuffer = 0;
    UpdateFromControls();
    return( FALSE );
    }

/*****************************************************************************/
/* MidiProc()                                                                */
/*                                                                           */
/*   This is the callback handler which continually refills MIDI data buffers*/
/* as they're returned to us from the audio subsystem.                       */
/*****************************************************************************/
void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
DWORD dwParam1, DWORD dwParam2 )
    {
    static intnWaitingBuffers = 0;
    MIDIEVENT*pme;
    MIDIHDR*pmh;

    MMRESULTmmrRetVal;
    intnChkErr;

    switch( uMsg )
{
case MOM_DONE:
            if( uCallbackStatus == STATUS_CALLBACKDEAD )
                return;

    nEmptyBuffers++;

    if( uCallbackStatus == STATUS_WAITINGFOREND )
{
if( nEmptyBuffers < NUM_STREAM_BUFFERS )
    {
    return;
    }
else
    {
    uCallbackStatus = STATUS_CALLBACKDEAD;
    PostMessage( hWndMain, WM_COMMAND,
MAKEWPARAM( IDC_STOP, 0 ), 0L );
    SetEvent( hBufferReturnEvent );
    return;
    }
}

// This flag is set whenever the callback is waiting for all buffers to
// come back.
            if( uCallbackStatus == STATUS_KILLCALLBACK )
{
// Count NUM_STREAM_BUFFERS-1 being returned for the last time
if( nEmptyBuffers < NUM_STREAM_BUFFERS )
                    {
                    return;
                    }
// Then send a stop message when we get the last buffer back...
else
    {
    // Change the status to callback dead
    uCallbackStatus = STATUS_CALLBACKDEAD;
    SetEvent( hBufferReturnEvent );
    return;
    }
                }

            dwProgressBytes
                += ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded;
    PostMessage( hWndMain, WM_MSTREAM_PROGRESS, 0L, 0L );

///////////////////////////////////////////////////////////////////////////////
// Fill an available buffer with audio data again...

    if( bPlaying && nEmptyBuffers )
{
 ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;

ciStreamBuffers[nCurrentBuffer].tkStart = 0;
ciStreamBuffers[nCurrentBuffer].dwBytesRecorded = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;

if(( nChkErr = ConvertToBuffer( 0,
    &ciStreamBuffers[nCurrentBuffer] ))
    != CONVERTERR_NOERROR )
        {
    if( nChkErr == CONVERTERR_DONE )
{
// Don't include this one in the count
nWaitingBuffers = NUM_STREAM_BUFFERS - 1;
uCallbackStatus = STATUS_WAITINGFOREND;
return;
}
    else
{
DebugPrint( "MidiProc() conversion pass failed!" );
ConverterCleanup();
return;
}
    }

ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
    = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;

if(( mmrRetVal = midiStreamOut( hStream,
    &ciStreamBuffers[nCurrentBuffer].mhBuffer,
    sizeof(MIDIHDR))) != MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    ConverterCleanup();
    return;
    }
nCurrentBuffer = ( nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS;
nEmptyBuffers--;
}

    break;

case MOM_POSITIONCB:
    pmh = (MIDIHDR *)dwParam1;
    pme = (MIDIEVENT *)(pmh->lpData + pmh->dwOffset);
    if( MIDIEVENT_TYPE( pme->dwEvent ) == MIDI_CTRLCHANGE )
{
if( MIDIEVENT_DATA1( pme->dwEvent ) == MIDICTRL_VOLUME_LSB )
    {
    DebugPrint( "Got an LSB volume event" );
    break;
    }
if( MIDIEVENT_DATA1( pme->dwEvent ) != MIDICTRL_VOLUME )
    break;

// Mask off the channel number and cache the volume data byte
dwVolCache[ MIDIEVENT_CHANNEL( pme->dwEvent )]
= MIDIEVENT_VOLUME( pme->dwEvent );
// Post a message so that the main program knows to counteract
// the effects of the volume event in the stream with its own
// generated event which reflects the proper trackbar position.
PostMessage( hWndMain, WM_MSTREAM_UPDATEVOLUME,
MIDIEVENT_CHANNEL( pme->dwEvent ), 0L );
}
    break;

default:
    break;
}

    return;
    }


MSTREAM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:MStream.h
 *  Content:Main header file for the MSTREAM sample application
 *
 ***************************************************************************/
#ifndef __MSTREAM_INCLUDED__
#define __MSTREAM_INCLUDED__

/*****************************************************************************/
/* Controls for buffer size, etc. */

#define TRACK_BUFFER_SIZE1024
#define OUT_BUFFER_SIZE                 1024 // Max stream buffer size in bytes
#define BUFFER_TIME_LENGTH60   // Amount to fill in milliseconds
#define NUM_STREAM_BUFFERS              2

#define DEBUG_CALLBACK_TIMEOUT2000// Wait 2 seconds for callback

/*****************************************************************************/
/* A bunch of constants used for calculating position */
/* and size of child controls for the main window.    */

#define BORDER_SPACE_CX10
#define BORDER_SPACE_CY10
#define CONTROL_SPACE_CX4
#define CONTROL_SPACE_CY4
#define TEXT_SPACE_CY3
#define TEXT_SPACE_CX2

#define BUTTON_CX68
#define BUTTON_CY26
#define CHECK_CX68
#define CHECK_CY26
#define BUTTON_SPACE_CX4
#define BUTTON_SPACE_CY4
#define CHECK_SPACE_CY4

#define TEMPO_TB_CX150
#define TEMPO_TB_CY             32
#define VOL_TB_CX150
#define VOL_TB_CY               32

#define TEMPO_TEXT_CXTEMPO_TB_CX
#define VOL_TEXT_CXVOL_TB_CX

#define VOL_TB_MIN0
#define VOL_TB_MAX1000
#define VOL_PAGESIZE10
#define VOL_MIN0
#define VOL_MAX127
#define VOL_CACHE_INIT100
#define TEMPO_MIN1
#define TEMPO_MAX500
#define TEMPO_PAGESIZE10

#define WM_MSTREAM_PROGRESS(WM_USER + 100)
#define WM_MSTREAM_UPDATEVOLUME(WM_USER + 101)

/*****************************************************************************/
/* All sorts of bit flags and error codes for */
/* communicating between various subsystems.  */

#define MSTREAM_STOPF_NOREOPEN0x0001

#define STREAMF_BUFFER_WARNING0x00000002

#define CONVERTF_RESET0x00000001

#define CONVERTF_STATUS_DONE0x00000001
#define CONVERTF_STATUS_STUCK0x00000002
#define CONVERTF_STATUS_GOTEVENT0x00000004

#define CONVERTERR_NOERROR0// No error occured
#define CONVERTERR_CORRUPT-101// The input file is corrupt
// The converter has already encountered a corrupt file and cannot convert any
// more of this file -- you must reset the converter
#define CONVERTERR_STUCK-102
#define CONVERTERR_DONE-103// Converter is done
#define CONVERTERR_BUFFERFULL-104// The buffer is full
#define CONVERTERR_METASKIP-105// Skipping unknown meta event

#define STATUS_KILLCALLBACK100// Signals that the callback should die
#define STATUS_CALLBACKDEAD200// Signals callback is done processing
#define STATUS_WAITINGFOREND300    // Callback's waiting for buffers to play

#define VOLUME_BUFFER3567// Random value which acts as a buffer id

#ifdef DEBUG
#define DebugPrint( sz )        DPF( 3, sz )
#else
#define DebugPrint( sz )
#endif

/*
 *   This structure is used to pass information to the ConvertToBuffer()
 * system and then internally by that function to send information about the
 * target stream buffer and current state of the conversion process to lower
 * level conversion routines internal to the MSTRCONV module.  See that source
 * file for specific details.
 */
typedef struct _ConvertInfo
{
    MIDIHDRmhBuffer;    // Standard Windows stream buffer header
    DWORDdwStartOffset;    // Start offset from mhStreamBuffer.lpStart
    DWORDdwMaxLength;    // Max length to convert on this pass

    DWORDdwBytesRecorded;    // Used internally by the MSTRCONV module
    DWORDtkStart;    // Used internally by the MSTRCONV module
    BOOLbTimesUp;    // Used internally by the MSTRCONV module
} CONVERTINFO, *LPCONVERTINFO;

/*****************************************************************************/
/* Function declarations */

LRESULT CALLBACK MainWindowProc( HWND, unsigned, WPARAM, LPARAM );
BOOL CALLBACK DLG_About( HWND, UINT, WPARAM, LPARAM );
void CALLBACK MidiProc( HMIDIIN, UINT, DWORD, DWORD, DWORD );

void BuildTitleBarText( void );
void ErrorMessageBox( UINT, DWORD );
void HandleTempoScroll( int, int );
void HandleVolScroll( int, int );
void MidiErrorMessageBox( MMRESULT );
void SetAllChannelVolumes( DWORD dwVolumePercent );
void SetChannelVolume( DWORD dwChannel, DWORD dwVolumePercent );
void UpdateFromControls( void );

int CreateChildren( RECT );
int HandleCommDlgError( DWORD );
BOOL StreamBufferSetup( void );

BOOL ConverterInit( LPSTR szInFile );
void ConverterCleanup( void );
intConvertToBuffer( DWORD, LPCONVERTINFO );

#endif /* __MSTREAM_INCLUDED__ */


MSTRHELP.C

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   mstream.c
 *  Content:   Illustrates streaming data from a disk MIDI file to a
 *             midiStream buffer for playback.
 *
 ***************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <memory.h>
#include <mmreg.h>

#include "debug.h"
#include "resource.h"
#include "midstuff.h"
#include "mstream.h"

extern char szAppTitle[64];
extern char szAppCaption[64];
extern char szFileBuffer[64];
extern char szFileTitle[64];
extern char szTempo[64];
extern char szVolume[64];
extern char szProgress[64];
extern char szTemp[256];
extern char szDebug[256];

extern HWND hWndMain, hWndTempo, hWndVol, hWndProg, hWndPlay, hWndPause;
extern HWND hWndStop, hWndTempoText, hWndVolText, hWndLoopCheck, hWndProgText;
extern HINSTANCE hInst;
extern HMIDISTRMhStream;

extern BOOLbFileOpen, bPlaying, bPaused, bInsertTempo;
extern intnTextControlHeight;
extern DWORDdwBufferTickLength, dwTempoMultiplier, dwCurrentTempo;
extern DWORDdwProgressBytes, dwVolumePercent, dwVolCache[NUM_CHANNELS];

#ifdef DEBUG
extern HWNDhWndList;
#endif

/*****************************************************************************/
/* CreateChildren()                                                          */
/*                                                                           */
/*   This function creates a bunch of child controls for the main window.    */
/* Most of them are used for controling various things about a playing sound */
/* file, like volume and panning. Returns FALSE if no errors, TRUE otherwise.*/
/*                                                                           */
/*****************************************************************************/
int CreateChildren( RECT crect )
    {
    SIZE  Size;
    HDC   hDC;
    int  x, y;
    UINT  uType;
    char  szTemplate[128], szType[32];
    LPSTR lpszControl;

    LoadString( hInst, IDS_ERROR_CHILDTEMPLATE, szTemplate, sizeof(szTemplate));

    /* Don't handle failure for this one, because the app will still run fine */
    CreateWindow( "static", NULL, WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
0, 0, crect.right, 2, hWndMain, (HMENU)0, hInst, NULL );

    hDC = GetDC( hWndMain );
    if( !GetTextExtentPoint32( hDC, szProgress, strlen(szProgress), &Size ))
{
ErrorMessageBox( IDS_ERROR_GETTEXTEXTENT, MB_ICONEXCLAMATION );
ReleaseDC( hWndMain, hDC );
return( TRUE );
}
    ReleaseDC( hWndMain, hDC );
    nTextControlHeight = Size.cy;

    y = BORDER_SPACE_CY;

    /* STATIC control -- text label for the TEMPO trackbar */
    if(( hWndTempoText = CreateWindow( "static", szTempo,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX,
                                    y,
                                    TEMPO_TEXT_CX, nTextControlHeight,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szTempo;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}

    /* Create the TEMPO trackbar */
    if(( hWndTempo = CreateWindow( TRACKBAR_CLASS, NULL,
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
BORDER_SPACE_CX,
y + nTextControlHeight + TEXT_SPACE_CY,
TEMPO_TB_CX, TEMPO_TB_CY,
hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szTempo;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    dwTempoMultiplier = 100;
    SendMessage( hWndTempo, TBM_SETRANGE, FALSE,
                                        MAKELONG( TEMPO_MIN, TEMPO_MAX ));
    SendMessage( hWndTempo, TBM_SETPOS, TRUE, dwTempoMultiplier );

    /* STATIC control -- text label for the VOLUME trackbar */
    if(( hWndVolText = CreateWindow( "static", szVolume,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX + TEMPO_TB_CX
                                    + CONTROL_SPACE_CX,
                                    y,
                                    VOL_TEXT_CX, nTextControlHeight,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}
    y += nTextControlHeight + TEXT_SPACE_CY;

    /* Create the VOLUME trackbar */
    if(( hWndVol = CreateWindow( TRACKBAR_CLASS, NULL,
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
BORDER_SPACE_CX + TEMPO_TB_CX
+ CONTROL_SPACE_CX,
y, VOL_TB_CX, VOL_TB_CY,
hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szVolume;
uType = IDS_ERROR_TRACKBAR;
        goto DISPLAY_CREATE_FAILURE;
}

    SendMessage( hWndVol, TBM_SETRANGE, FALSE,
                                        MAKELONG( VOL_TB_MIN, VOL_TB_MAX ));
    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_TB_MAX );
    SendMessage( hWndVol, TBM_SETPAGESIZE, 0L, VOL_PAGESIZE );

    x = BORDER_SPACE_CX + TEMPO_TB_CX + 2 * CONTROL_SPACE_CX + VOL_TB_CX;
    y = BORDER_SPACE_CY;

    /* Create the LOOPED CHECKBOX */
    LoadString( hInst, IDS_CHECK_LOOPED, szTemp, sizeof(szTemp));
    if(( hWndLoopCheck = CreateWindow( "button", szTemp,
                                WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
                                x, y, CHECK_CX, CHECK_CY, hWndMain,
                                (HMENU)IDC_LOOPCHECK, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_CHECK;
        goto DISPLAY_CREATE_FAILURE;
}

    x = BORDER_SPACE_CX + TEMPO_TB_CX + VOL_TB_CX + 2 * CONTROL_SPACE_CX + CHECK_CX
- 3 * BUTTON_CX - 2 * BUTTON_SPACE_CX;
    y = BORDER_SPACE_CY + nTextControlHeight + TEXT_SPACE_CY + TEMPO_TB_CY
+ CONTROL_SPACE_CY;

    /* STATIC control -- text label for the progress trackbar. */
    if(( hWndProgText = CreateWindow( "static", szProgress,
                                    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
                                    BORDER_SPACE_CX,
                                    y,
                                    x - BORDER_SPACE_CX, nTextControlHeight,
                                    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
        {
        lpszControl = szProgress;
uType = IDS_ERROR_STATICTEXT;
        goto DISPLAY_CREATE_FAILURE;
}

    /* Create the PLAY BUTTON */
    LoadString( hInst, IDS_BUTTON_PLAY, szTemp, sizeof(szTemp));
    if(( hWndPlay = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDC_PLAY, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
}
    x += BUTTON_CX + BUTTON_SPACE_CX;

    /* Create the PAUSE BUTTON */
    LoadString( hInst, IDS_BUTTON_PAUSE, szTemp, sizeof(szTemp));
    if(( hWndPause = CreateWindow( "button", szTemp,
    WS_CHILD | WS_VISIBLE | WS_DISABLED,
    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
    (HMENU)IDC_PAUSE, hInst, NULL )) == NULL )
{
lpszControl = szTemp;
uType = IDS_ERROR_BUTTON;
goto DISPLAY_CREATE_FAILURE;
}
    x += BUTTON_CX + BUTTON_SPACE_CX;

    /* Create the STOP BUTTON */
    LoadString( hInst, IDS_BUTTON_STOP, szTemp, sizeof(szTemp));
    if(( hWndStop = CreateWindow( "button", szTemp,
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
                                    (HMENU)IDC_STOP, hInst, NULL )) == NULL )
        {
        lpszControl = szTemp;
uType = IDS_ERROR_BUTTON;
        goto DISPLAY_CREATE_FAILURE;
}

    UpdateFromControls();
    goto RETURN_NORMAL;

DISPLAY_CREATE_FAILURE:
    LoadString( hInst, uType, szType, sizeof(szType));
    wsprintf( szTemp, szTemplate, lpszControl, szType );
    MessageBox( GetActiveWindow(), szTemp,
                        szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    return( TRUE );

RETURN_NORMAL:
    return( FALSE );
    }

/********************************************************************************/
/* HandleTempoScroll()                                                          */
/*                                                                              */
/*   Handles the tempo trackbar scroll when a WM_HSCROLL is received.           */
/*                                                                              */
/********************************************************************************/
void HandleTempoScroll( int nCode, int nPos )
    {
    long  lTempo, lDelta;

    lTempo = (LONG)SendMessage( hWndTempo, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEUP:
            if( lTempo >= TEMPO_MIN-1 )
                lDelta = -1;
            break;
        case TB_LINEDOWN:
            if( lTempo <= TEMPO_MAX+1 )
                lDelta = 1;
            break;
        case TB_PAGEUP:
            if( lTempo >= TEMPO_MIN - TEMPO_PAGESIZE )
                lDelta = -TEMPO_PAGESIZE;
            break;
        case TB_PAGEDOWN:
            if( lTempo <= TEMPO_MAX + TEMPO_PAGESIZE )
                lDelta = TEMPO_PAGESIZE;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
{
SendMessage( hWndTempo, TBM_SETPOS, TRUE, lTempo + lDelta );
dwTempoMultiplier = (DWORD)( lTempo + lDelta );
}
    else
{
SendMessage( hWndTempo, TBM_SETPOS, TRUE, (long)nPos );
dwTempoMultiplier = (DWORD)nPos;
}

    UpdateFromControls();
    }

/********************************************************************************/
/* HandleVolScroll()                                                            */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleVolScroll( int nCode, int nPos )
    {
    long  lVol, lDelta;

    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
        {
        case TB_LINEDOWN:
            if( lVol <= VOL_TB_MAX - 1 )
                lDelta = 1;
            break;
        case TB_LINEUP:
            if( lVol >= VOL_TB_MIN + 1 )
                lDelta = -1;
            break;
        case TB_PAGEDOWN:
            if( lVol <= VOL_TB_MAX - VOL_PAGESIZE )
                lDelta = VOL_PAGESIZE;
            break;
        case TB_PAGEUP:
            if( lVol >= VOL_TB_MIN + VOL_PAGESIZE )
                lDelta = -VOL_PAGESIZE;
            break;
        case TB_ENDTRACK:
            return;
        default:
            lDelta = 0;
        }

    if( lDelta )
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (lVol + lDelta));
    else
        SendMessage( hWndVol, TBM_SETPOS, TRUE, (long)nPos );

    UpdateFromControls();
    }

/********************************************************************************/
/* UpdateFromControls()                                                         */
/*                                                                              */
/*    This function gets all the required values from the DirectSoundBuffer and */
/* updates the screen interface controls.                                       */
/*                                                                              */
/********************************************************************************/
void UpdateFromControls( void )
    {
    longlTempo;

    lTempo = (LONG)SendMessage( hWndTempo, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    dwVolumePercent = (WORD)SendMessage( hWndVol, TBM_GETPOS,
    (WPARAM)0, (LPARAM)0 );

    /* Set the Volume text */
    wsprintf( szTemp, "%s: %lu%%", szVolume, dwVolumePercent / 10 );
    Static_SetText( hWndVolText, szTemp );
    if( hStream )
SetAllChannelVolumes( dwVolumePercent );

    /* Set the Tempo text */
    wsprintf( szTemp, "%s: %li%%", szTempo, lTempo );
    Static_SetText( hWndTempoText, szTemp );
    bInsertTempo = TRUE;

    /* Set the Progress text */
    wsprintf( szTemp, "%s: %lu bytes", szProgress, dwProgressBytes );
    Static_SetText( hWndProgText, szTemp );

    return;
    }

/****************************************************************************/
/* ErrorMessageBox()                                                        */
/*                                                                          */
/*   A little routine to load error messages from the string resource table */
/* and pop them up in a MessageBox() for the world to see. The dwMBFlags    */
/* parameter allows the caller to specify the type of icon to use.          */
/*                                                                          */
/****************************************************************************/
void ErrorMessageBox( UINT uID, DWORD dwMBFlags )
    {
    LoadString( hInst, uID, szTemp, sizeof(szTemp));
    MessageBox( GetActiveWindow(), szTemp, szAppTitle, MB_OK | dwMBFlags );
#ifdef DEBUG
    wsprintf( szDebug, "General error: %s", szTemp );
    DebugPrint( szDebug );
#endif
    }

/****************************************************************************/
/* MidiErrorMessageBox()                                                    */
/*                                                                          */
/*   Calls the midiOutGetErrorText() function and displays the text which   */
/* corresponds to a midi subsystem error code.                              */
/*                                                                          */
/****************************************************************************/
void MidiErrorMessageBox( MMRESULT mmr )
    {
    midiOutGetErrorText( mmr, szTemp, sizeof(szTemp));
    MessageBox( GetActiveWindow(), szTemp, szAppTitle,
    MB_OK | MB_ICONSTOP );
#ifdef DEBUG
    wsprintf( szDebug, "Midi subsystem error: %s", szTemp );
    DebugPrint( szDebug );
#endif
    }

/*****************************************************************************/
/* HandleCommDlgError()                                                      */
/*                                                                           */
/*    The function translates extended common dialog error codes into a      */
/* string resource ID, loads that string from our module, and displays it in */
/* a message box. This implementation only covers the general CD error codes.*/
/*                                                                           */
/*****************************************************************************/
int HandleCommDlgError( DWORD dwError )
    {
    char szTitle[128];
    UINT uMsgID;

    if( dwError == CDERR_DIALOGFAILURE )
        uMsgID = IDS_CDERR_DIALOGFAILURE;
    else
        uMsgID = (UINT)dwError + IDS_CDERR_GENERAL_BASE;

    LoadString( hInst, uMsgID, szTemp, sizeof(szTemp));
    LoadString( hInst, IDS_CDERR_TITLESTRING, szTitle, sizeof(szTitle));
    MessageBox( GetActiveWindow(), szTemp, szTitle,
                    MB_OK | MB_ICONEXCLAMATION );

    return( 0 );
    }

/****************************************************************************/
/* BuildTitleBarText()                                                      */
/*                                                                          */
/*   Helper function designed to updated the title bar text of the main     */
/* window to reflect the currently loaded file (if there is one).           */
/*                                                                          */
/****************************************************************************/
void BuildTitleBarText( void )
    {
    char szTitle[sizeof(szAppCaption) + MAX_PATH + sizeof( " -  (Paused)")];

    lstrcpy( szTitle, szAppCaption );
    if( bFileOpen )
{
lstrcat( szTitle, " - " );
lstrcat( szTitle, szFileTitle );
}
    if( bPaused )
    lstrcat( szTitle, " (Paused)" );
    SetWindowText( hWndMain, szTitle );
    }

/****************************************************************************/
/* SetAllChannelVolumes()                                                   */
/*                                                                          */
/*   Given a percent in tenths of a percent, sets volume on all channels to */
/* reflect the new value.                                                   */
/****************************************************************************/
void SetAllChannelVolumes( DWORD dwVolumePercent )
    {
    DWORDdwEvent, dwStatus, dwVol, idx;
    MMRESULTmmrRetVal;

    if( !bPlaying )
return;

    for( idx = 0, dwStatus = MIDI_CTRLCHANGE; idx < NUM_CHANNELS; idx++,
dwStatus++ )
{
dwVol = ( dwVolCache[idx] * dwVolumePercent ) / 1000;
dwEvent = dwStatus | ((DWORD)MIDICTRL_VOLUME << 8)
| ((DWORD)dwVol << 16);
if(( mmrRetVal = midiOutShortMsg( (HMIDIOUT)hStream, dwEvent ))
!= MMSYSERR_NOERROR )
    {
    MidiErrorMessageBox( mmrRetVal );
    return;
    }
}
    }

/****************************************************************************/
/* SetChannelVolume()                                                       */
/*                                                                          */
/*   Given a percent in tenths of a percent, sets volume on a specified     */
/* channel to reflect the new value.                                        */
/****************************************************************************/
void SetChannelVolume( DWORD dwChannel, DWORD dwVolumePercent )
    {
    DWORDdwEvent, dwVol;
    MMRESULTmmrRetVal;

    if( !bPlaying )
return;

    dwVol = ( dwVolCache[dwChannel] * dwVolumePercent ) / 1000;
    dwEvent = MIDI_CTRLCHANGE | dwChannel | ((DWORD)MIDICTRL_VOLUME << 8)
| ((DWORD)dwVol << 16);
    if(( mmrRetVal = midiOutShortMsg( (HMIDIOUT)hStream, dwEvent ))
!= MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
return;
}
    }


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by mstream.rc
//
#define IDR_MAINMENU                    101
#define IDC_PLAY                        101
#define IDD_ABOUTBOX                    102
#define IDC_STOP                        102
#define IDC_PAUSE                       103
#define IDI_ICON1                       107
#define IDI_ICON2                       108
#define IDI_ICON3                       111
#define IDS_TBTITLE_PAN                 8192
#define IDS_TBTITLE_VOLUME              8193
#define IDS_TBTITLE_FREQUENCY           8194
#define IDS_TBTITLE_PROGRESS            8195
#define IDS_BUTTON_PLAY                 8196
#define IDS_BUTTON_STOP                 8197
#define IDS_CHECK_LOOPED                8198
#define IDS_TBTITLE_TEMPO               8199
#define IDS_BUTTON_PAUSE                8200
#define IDS_APP_TITLE                   8208
#define IDS_APP_CAPTION                 8209
#define IDS_APP_NAME                    8210
#define IDS_OPEN_DLGTITLE               8211
#define IDS_OPEN_FILTER1                8212
#define IDS_OPEN_FILTER2                8213
#define IDS_OPEN_FILTER3                8214
#define IDS_OPEN_FILTER4                8215
#define IDS_ERROR_APPINIT               8224
#define IDS_ERROR_INSTANCEINIT          8225
#define IDS_ERROR_REGISTERCLASS         8226
#define IDS_ERROR_MAINWNDCREATE         8227
#define IDS_ERROR_NOMIDIMAPPER          8228
#define IDS_ERROR_CHILDTEMPLATE         8244
#define IDS_ERROR_TRACKBAR              8245
#define IDS_ERROR_BUTTON                8246
#define IDS_ERROR_STATICTEXT            8247
#define IDS_ERROR_GETTEXTEXTENT         8248
#define IDS_ERROR_CHECK                 8249
#define IDS_CDERR_GENERAL_BASE          16384
#define IDS_CDERR_FINDRESFAILURE        16385
#define IDS_CDERR_INITIALIZATION        16386
#define IDS_CDERR_LOADRESFAILURE        16387
#define IDS_CDERR_LOCKRESFAILURE        16388
#define IDS_CDERR_LOADSTRFAILURE        16389
#define IDS_CDERR_MEMALLOCFAILURE       16390
#define IDS_CDERR_MEMLOCKFAILURE        16391
#define IDS_CDERR_NOHINSTANCE           16392
#define IDS_CDERR_NOHOOK                16393
#define IDS_CDERR_NOTEMPLATE            16394
#define IDS_CDERR_REGISTERMSGFAIL       16395
#define IDS_CDERR_STRUCTSIZE            16396
#define IDS_CDERR_DIALOGFAILURE         16397
#define IDS_CDERR_TITLESTRING           16398
#define IDC_LOOPCHECK                   40001
#define IDM_FILE_EXIT                   40002
#define IDM_HELP_ABOUT                  40003
#define IDM_FILE_OPEN                   40004
#define IDM_FILE_CLOSE                  40005
#define IDM_PLAY                        40006
#define IDM_STOP                        40007
#define IDC_OPTIONS_LOOPED              40008
#define IDM_ACTIONS_LOOPED              40008
#define IDC_OPTIONS_MIDIOUTDEVICE       40009
#define IDM_ACTIONS_PLAY                40010
#define IDM_ACTIONS_PAUSE               40011
#define IDM_ACTIONS_STOP                40012
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        113
#define _APS_NEXT_COMMAND_VALUE         40013
#define _APS_NEXT_CONTROL_VALUE         1004
#define _APS_NEXT_SYMED_VALUE           104
#endif
#endif


README.TXT

OVERRIDE.EXE
------------

Sample application to demonstrate how to override the DirectPlay service
provider dialogs.

Note: MSVC may include older versions of the DirectX header files and
libraries.  This sample requires DirectX 5.  In order to avoid compile
errors, make sure the path to the DirectX 5 header files and libraries are
listed BEFORE the MSVC header files and libraries through the
Tools -> Options -> Directories menu.

Once the user has selected which service provider to use, the application
displays a custom dialog to collect the information needed for that service
provider.

If the user selected an unknown service provider, then the dialog (if any)
cannot be overidden and the application must prepare for a dialog to be
displayed.

Once the information has been collected from the user, it must be formatted
into a DirectPlay Address which can be used to initialize the service
provider.

EnumSessions must be called with the DPENUMSESSIONS_RETURNSTATUS flag and
Open must be called with the DPOPEN_RETURNSTATUS flag to supress any status
dialogs that may pop up to show to progress of the connection.


OVERRIDE.C

/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       override.c
 *  Content:Implementation of a DirectPlay launching utility
 *
 ***************************************************************************/

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <objbase.h>
#include <cguid.h>

#include "dplay.h"
#include "dplobby.h"

#include "resource.h"

#define NAMEMAX200// string size
#define TIMERID1// timer ID to use
#define TIMERINTERVAL1000// timer interval

typedef struct {
LPDIRECTPLAY3AlpDPlay;
GUIDguidInstance;
} STATUSCONTEXT, *LPSTATUSCONTEXT;

// guid for this application
// {126E6180-D307-11d0-9C4F-00A0C905425E}
DEFINE_GUID(OVERRIDE_GUID,
0x126e6180, 0xd307, 0x11d0, 0x9c, 0x4f, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e);

// prototypes
BOOL CALLBACKOverrideWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACKSessionsWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HRESULTInitializeOverrideWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A *lplpDPlayLobby);
voidDestroyOverrideWindow(HWND hWnd, LPDIRECTPLAY3A lpDPlay, LPDIRECTPLAYLOBBY2A lpDPlayLobby);
HRESULTUpdateAddressInfo(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby2A);
HRESULTDoHostOrJoin(HINSTANCE hInstance, HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby, BOOL bHost, LPDIRECTPLAY3A *lplpDPlay);
BOOL FAR PASCALDirectPlayEnumerateCallback(LPGUID lpSPGuid, LPTSTR lpszSPName,
DWORD dwMajorVersion, DWORD dwMinorVersion, LPVOID lpContext);
HRESULTGetServiceProviderGuid(HWND hWnd, LPGUID lpguidServiceProvider);
voidDeleteServiceProviderCombo(HWND hWnd);
HRESULTFillModemComboBox(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby);
BOOLDlgItemIsChecked(HWND hDlg, int nIDDlgItem);
voidEnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable);

// ---------------------------------------------------------------------------
// WinMain
// ---------------------------------------------------------------------------
// Description:             Main windows entry point.
// Arguments:
//  HINSTANCE               [in] Standard windows stuff
//  HINSTANCE               [in]
//  LPSTR                   [in]
//  int                     [in]
// Returns:
//  int
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow )
{
HRESULThr;
intiResult = 0;

// initialize COM library
hr = CoInitialize(NULL);
if FAILED(hr)
goto FAILURE;

    iResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_OVERRIDEDIALOG), NULL, OverrideWndProc, (LPARAM) hInstance);

FAILURE:
// Uninitialize the COM library
CoUninitialize();

return (iResult);
}

// ---------------------------------------------------------------------------
// OverrideWndProc
// ---------------------------------------------------------------------------
// Description:             Message callback function for Override dialog.
// Arguments:
//  HWND                    [in] Dialog window handle.
//  UINT                    [in] Window message identifier.
//  WPARAM                  [in] Depends on message.
//  LPARAM                  [in] Depends on message.
// Returns:
//  BOOL                    TRUE if message was processed internally.
BOOL CALLBACK OverrideWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCEhInstance;
static LPDIRECTPLAY3AlpDPlay;
static LPDIRECTPLAYLOBBY2AlpDPlayLobby;
HRESULThr;

    switch(uMsg)
    {
        case WM_INITDIALOG:
            // Save the instance handle
            hInstance = (HINSTANCE)lParam;

// Initialize dialog with launcher information
lpDPlay = NULL;
lpDPlayLobby = NULL;
hr = InitializeOverrideWindow(hWnd, &lpDPlayLobby);
if FAILED(hr)
SetDlgItemText(hWnd, IDC_STATUSEDIT, "Could not initialize DirectPlay");
            break;

        case WM_DESTROY:
// Destroy launcher information in dialog
DestroyOverrideWindow(hWnd, lpDPlay, lpDPlayLobby);
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
case IDC_SPCOMBO:

switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
UpdateAddressInfo(hWnd, lpDPlayLobby);
break;
}
break;

case IDC_MODEMCOMBO:

switch (HIWORD(wParam))
{
case CBN_SELCHANGE:
break;
}
break;

                case IDC_HOSTBUTTON:

SetDlgItemText(hWnd, IDC_STATUSEDIT, "Hosting...");
hr = DoHostOrJoin(hInstance, hWnd, lpDPlayLobby, TRUE, &lpDPlay);
if FAILED(hr)
SetDlgItemText(hWnd, IDC_STATUSEDIT, "Failed to host");
else
SetDlgItemText(hWnd, IDC_STATUSEDIT, "Host successfull");
                    break;

                case IDC_JOINBUTTON:

SetDlgItemText(hWnd, IDC_STATUSEDIT, "Joining...");
hr = DoHostOrJoin(hInstance, hWnd, lpDPlayLobby, FALSE, &lpDPlay);
if FAILED(hr)
SetDlgItemText(hWnd, IDC_STATUSEDIT, "Failed to join");
else
SetDlgItemText(hWnd, IDC_STATUSEDIT, "Join successfull");
                    break;

                case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, TRUE);

                    break;
            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

HRESULT InitializeOverrideWindow(HWND hWnd, LPDIRECTPLAYLOBBY2A *lplpDPlayLobby)
{
LPDIRECTPLAYLOBBYAlpDPlayLobbyA = NULL;
LPDIRECTPLAYLOBBY2AlpDPlayLobby2A = NULL;
HRESULThr;

// get ANSI DirectPlayLobby interface
hr = DirectPlayLobbyCreate(NULL, &lpDPlayLobbyA, NULL, NULL, 0);
if FAILED(hr)
goto FAILURE;

// get ANSI DirectPlayLobby2 interface
hr = lpDPlayLobbyA->lpVtbl->QueryInterface(lpDPlayLobbyA,
&IID_IDirectPlayLobby2A, (LPVOID *) &lpDPlayLobby2A);
if FAILED(hr)
goto FAILURE;

// don't need DirectPlayLobby interface anymore
lpDPlayLobbyA->lpVtbl->Release(lpDPlayLobbyA);
lpDPlayLobbyA = NULL;

// put all the service providers in combo box
DirectPlayEnumerate(DirectPlayEnumerateCallback, hWnd);
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETCURSEL, (WPARAM) 0, (LPARAM) 0);

// fill modem combo box with available modems
FillModemComboBox(hWnd, lpDPlayLobby2A);

// update display first service provider
UpdateAddressInfo(hWnd, lpDPlayLobby2A);

// return the ANSI lobby interface
*lplpDPlayLobby = lpDPlayLobby2A;

return (DP_OK);

FAILURE:
if (lpDPlayLobbyA)
lpDPlayLobbyA->lpVtbl->Release(lpDPlayLobbyA);
if (lpDPlayLobby2A)
lpDPlayLobby2A->lpVtbl->Release(lpDPlayLobby2A);

return (hr);
}

void DestroyOverrideWindow(HWND hWnd, LPDIRECTPLAY3A lpDPlay, LPDIRECTPLAYLOBBY2A lpDPlayLobby)
{
// delete combo box data items
DeleteServiceProviderCombo(hWnd);

// release the dplay interface
if (lpDPlay)
lpDPlay->lpVtbl->Release(lpDPlay);

// release the lobby interface
if (lpDPlayLobby)
lpDPlayLobby->lpVtbl->Release(lpDPlayLobby);
}

// ---------------------------------------------------------------------------
// DirectPlayEnumerateCallback
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlay.
//Enumerates service providers registered with DirectPlay.
// Arguments:
//  LPGUID[in] GUID of service provider
//  LPTSTR[in] name of service provider
//  DWORD[in] major version of DirectPlay
//  DWORD[in] minor version of DirectPlay
//  LPVOID    [in] user-defined context
// Returns:
//  BOOLTRUE to continue enumerating
BOOL FAR PASCAL DirectPlayEnumerateCallback(
LPGUIDlpSPGuid,
LPTSTRlpszSPName,
DWORDdwMajorVersion,
DWORDdwMinorVersion,
LPVOIDlpContext)
{
    HWNDhWnd = lpContext;
    LRESULTiIndex;
LPGUIDlpGuid;

// store service provider name in combo box
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_ADDSTRING, 0, (LPARAM) lpszSPName);
if (iIndex == CB_ERR)
goto FAILURE;

// make space for application GUID
lpGuid = (LPGUID) GlobalAllocPtr(GHND, sizeof(GUID));
if (lpGuid == NULL)
goto FAILURE;

// store pointer to GUID in combo box
*lpGuid = *lpSPGuid;
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

FAILURE:
    return (TRUE);
}

HRESULT GetServiceProviderGuid(HWND hWnd, LPGUID lpguidServiceProvider)
{
LONGiIndex;

// get guid for service provider
iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == CB_ERR)
return (DPERR_GENERIC);

iIndex = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == CB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

*lpguidServiceProvider = *((LPGUID) iIndex);

return (DP_OK);
}

void DeleteServiceProviderCombo(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the data stored with each combo box item
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData)// data to delete
GlobalFreePtr((LPVOID) lpData);

i += 1;
}

// delete all items in list
SendDlgItemMessage(hWnd, IDC_SPCOMBO, CB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

void DeleteSessionInstanceList(HWND hWnd)
{
WPARAMi;
LONGlpData;

// destroy the GUID's stored with each session name
i = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData)// data to delete
GlobalFreePtr((LPVOID) lpData);

i += 1;
}

// delete all items in list
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_RESETCONTENT,
(WPARAM) 0, (LPARAM) 0);
}

void SelectSessionInstance(HWND hWnd, LPGUID lpguidSessionInstance)
{
WPARAMi, iIndex;
LONGlpData;

// loop over the GUID's stored with each session name
// to find the one that matches what was passed in
i = 0;
iIndex = 0;
while (TRUE)
{
// get data pointer stored with item
lpData = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) i, (LPARAM) 0);
if (lpData == CB_ERR)// error getting data
break;

if (lpData == 0)// no data to compare to
continue;

// guid matches
if (IsEqualGUID(lpguidSessionInstance, (LPGUID) lpData))
{
iIndex = i;// store index of this string
break;
}

i += 1;
}

// select this item
SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_SETCURSEL, (WPARAM) iIndex, (LPARAM) 0);
}

HRESULT GetSessionInstanceGuid(HWND hWnd, LPGUID lpguidSessionInstance)
{
LONGiIndex;

// get guid for session
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCURSEL,
(WPARAM) 0, (LPARAM) 0);
if (iIndex == LB_ERR)
return (DPERR_GENERIC);

iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETITEMDATA,
(WPARAM) iIndex, (LPARAM) 0);
if ((iIndex == LB_ERR) || (iIndex == 0))
return (DPERR_GENERIC);

*lpguidSessionInstance = *((LPGUID) iIndex);

return (DP_OK);
}

BOOL FAR PASCAL EnumSessionsCallback(
LPCDPSESSIONDESC2lpSessionDesc,
LPDWORDlpdwTimeOut,
DWORDdwFlags,
LPVOIDlpContext)
{
HWNDhWnd = (HWND) lpContext;
LPGUIDlpGuid;
LONGiIndex;

// see if last session has been enumerated
    if (dwFlags & DPESC_TIMEDOUT)
return (FALSE);

// store session name in list
iIndex = SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_ADDSTRING,
(WPARAM) 0, (LPARAM) lpSessionDesc->lpszSessionNameA);

if (iIndex == LB_ERR)
goto FAILURE;

// make space for session instance guid
lpGuid = (LPGUID) GlobalAllocPtr( GHND, sizeof(GUID) );
if (lpGuid == NULL)
goto FAILURE;

// store pointer to guid in list
*lpGuid = lpSessionDesc->guidInstance;
SendDlgItemMessage( hWnd, IDC_SESSIONLIST, LB_SETITEMDATA, (WPARAM) iIndex, (LPARAM) lpGuid);

FAILURE:
    return (TRUE);
}

BOOL CALLBACK SessionsWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static LPSTATUSCONTEXTlpContext;
static LPDIRECTPLAY3AlpDPlay;
static UINTidTimer;
static BOOLbInsideEnumSessions;
DPSESSIONDESC2sessionDesc;
GUIDguidSessionInstance;
LONGiIndex;
HRESULThr;

    switch(uMsg)
    {
        case WM_INITDIALOG:
lpContext = (LPSTATUSCONTEXT) lParam;
lpDPlay = lpContext->lpDPlay;
bInsideEnumSessions = FALSE;

// can't join until there is a session
EnableDlgButton(hWnd, IDC_JOINSESSIONBUTTON, FALSE);

// set a timer to refresh the session list
idTimer = SetTimer(hWnd, TIMERID, TIMERINTERVAL, NULL);
            break;

        case WM_DESTROY:
if (idTimer)
{
KillTimer(hWnd, idTimer);
idTimer = 0;
}
DeleteSessionInstanceList(hWnd);
            break;

case WM_TIMER:
// make sure we don't re-enter EnumSessions
if (bInsideEnumSessions)
break;

// get guid of currently selected session
guidSessionInstance = GUID_NULL;
hr = GetSessionInstanceGuid(hWnd, &guidSessionInstance);

// delete existing session list
DeleteSessionInstanceList(hWnd);

// enum sessions
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
sessionDesc.guidApplication = OVERRIDE_GUID;

bInsideEnumSessions = TRUE;
hr = lpDPlay->lpVtbl->EnumSessions(lpDPlay, &sessionDesc, 0,
EnumSessionsCallback, hWnd,
DPENUMSESSIONS_AVAILABLE |
DPENUMSESSIONS_ASYNC |
DPENUMSESSIONS_RETURNSTATUS);
bInsideEnumSessions = FALSE;

// select the session that was previously selected
SelectSessionInstance(hWnd, &guidSessionInstance);

// hilite "Join" button only if there are sessions to join
iIndex = SendDlgItemMessage(hWnd, IDC_SESSIONLIST, LB_GETCOUNT,
   (WPARAM) 0, (LPARAM) 0);

EnableDlgButton(hWnd, IDC_JOINSESSIONBUTTON, (iIndex > 0) ? TRUE : FALSE);

switch (hr)
{
case DP_OK:
SetDlgItemText(hWnd, IDC_SESSIONSTATUSEDIT, "Searching for sessions...");
break;

case DPERR_CONNECTING:
SetDlgItemText(hWnd, IDC_SESSIONSTATUSEDIT, "Making connection...");
break;

case DPERR_NOCONNECTION:
SetDlgItemText(hWnd, IDC_SESSIONSTATUSEDIT, "Connection failed");
KillTimer(hWnd, idTimer);
idTimer = 0;
break;

default:
SetDlgItemText(hWnd, IDC_SESSIONSTATUSEDIT, "Error making connection");
KillTimer(hWnd, idTimer);
idTimer = 0;
break;
}
break;

case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDC_JOINSESSIONBUTTON:
                    // Return guid of session to join
hr = GetSessionInstanceGuid(hWnd, &lpContext->guidInstance);
if SUCCEEDED(hr)
EndDialog(hWnd, TRUE);
                    break;

                case IDCANCEL:
                    // Return failure
                    EndDialog(hWnd, FALSE);
                    break;
            }

            break;
    }

    // Allow for default processing
    return FALSE;
}

// ---------------------------------------------------------------------------
// UpdateAddressInfo
// ---------------------------------------------------------------------------
// Description:             Updates address information elements in dialog.
//Calls EnumAddressTypes() to determine what address
//information should be displayed and arranges dialog
//to display and collect the needed information.
// Arguments:
//  HWND[in] window handle
//  LPDIRECTPLAYLOBBY2A[in] DirectPlay Lobby interface to use
// Returns:
//  HRESULTDP_OK if it succeedes, otherwise the error
HRESULT UpdateAddressInfo(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby2A)
{
GUIDguidServiceProvider;
HRESULThr;

// clear and hide address dialog items
ShowWindow(GetDlgItem(hWnd, IDC_PHONEEDIT), SW_HIDE);
ShowWindow(GetDlgItem(hWnd, IDC_PHONEEDITLABEL), SW_HIDE);

ShowWindow(GetDlgItem(hWnd, IDC_MODEMCOMBO), SW_HIDE);
ShowWindow(GetDlgItem(hWnd, IDC_MODEMCOMBOLABEL), SW_HIDE);

ShowWindow(GetDlgItem(hWnd, IDC_TCPEDIT), SW_HIDE);
ShowWindow(GetDlgItem(hWnd, IDC_TCPEDITLABEL), SW_HIDE);

ShowWindow(GetDlgItem(hWnd, IDC_IPXLABEL), SW_HIDE);

ShowWindow(GetDlgItem(hWnd, IDC_SERVICEPROVIDERLABEL), SW_HIDE);

// get currently selected service provider
hr = GetServiceProviderGuid(hWnd, &guidServiceProvider);
if FAILED(hr)
goto FAILURE;

// modem service provider
if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_MODEM))
{
// show edit control to collect phone number
ShowWindow(GetDlgItem(hWnd, IDC_PHONEEDIT), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_PHONEEDITLABEL), SW_SHOW);

// show combo box to collect modem
ShowWindow(GetDlgItem(hWnd, IDC_MODEMCOMBO), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_MODEMCOMBOLABEL), SW_SHOW);
}

// internet TCP/IP service provider
else if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_TCPIP))
{
// show edit control to collect IP address
ShowWindow(GetDlgItem(hWnd, IDC_TCPEDIT), SW_SHOW);
ShowWindow(GetDlgItem(hWnd, IDC_TCPEDITLABEL), SW_SHOW);
}

// IPX service provider
else if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_IPX))
{
// no address info is needed, so just display a string
ShowWindow(GetDlgItem(hWnd, IDC_IPXLABEL), SW_SHOW);
}

// anything else, let service provider collect settings, if any
else
{
ShowWindow(GetDlgItem(hWnd, IDC_SERVICEPROVIDERLABEL), SW_SHOW);
}

FAILURE:
return (hr);
}

HRESULTCreateServiceProviderAddress(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby,
 LPVOID *lplpAddress, LPDWORD lpdwAddressSize)
{
DPCOMPOUNDADDRESSELEMENTaddressElements[3];
CHARszIPAddressString[NAMEMAX];
CHARszPhoneNumberString[NAMEMAX];
CHARszModemString[NAMEMAX];
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
DWORDdwElementCount;
GUIDguidServiceProvider;
HRESULThr;

// get currently selected service provider
hr = GetServiceProviderGuid(hWnd, &guidServiceProvider);
if FAILED(hr)
goto FAILURE;

dwElementCount = 0;

if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_MODEM))
{
// Modem needs a service provider, a phone number string and a modem string

// service provider
addressElements[dwElementCount].guidDataType = DPAID_ServiceProvider;
addressElements[dwElementCount].dwDataSize = sizeof(GUID);
addressElements[dwElementCount].lpData = (LPVOID) &DPSPGUID_MODEM;
dwElementCount++;

// add a modem string if available
lstrcpy(szModemString, "");
if (GetDlgItemText(hWnd, IDC_MODEMCOMBO, szModemString, NAMEMAX))
{
addressElements[dwElementCount].guidDataType = DPAID_Modem;
addressElements[dwElementCount].dwDataSize = lstrlen(szModemString) + 1;
addressElements[dwElementCount].lpData = szModemString;
dwElementCount++;
}

// add phone number string
lstrcpy(szPhoneNumberString, "");
GetDlgItemText(hWnd, IDC_PHONEEDIT, szPhoneNumberString, NAMEMAX);
addressElements[dwElementCount].guidDataType = DPAID_Phone;
addressElements[dwElementCount].dwDataSize = lstrlen(szPhoneNumberString) + 1;
addressElements[dwElementCount].lpData = szPhoneNumberString;
dwElementCount++;
}

// internet TCP/IP service provider
else if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_TCPIP))
{
// TCP/IP needs a service provider and an IP address

// service provider
addressElements[dwElementCount].guidDataType = DPAID_ServiceProvider;
addressElements[dwElementCount].dwDataSize = sizeof(GUID);
addressElements[dwElementCount].lpData = (LPVOID) &DPSPGUID_TCPIP;
dwElementCount++;

// IP address string
lstrcpy(szIPAddressString, "");
GetDlgItemText(hWnd, IDC_TCPEDIT, szIPAddressString, NAMEMAX);
addressElements[dwElementCount].guidDataType = DPAID_INet;
addressElements[dwElementCount].dwDataSize = lstrlen(szIPAddressString) + 1;
addressElements[dwElementCount].lpData = szIPAddressString;
dwElementCount++;
}

// IPX service provider
else if (IsEqualGUID(&guidServiceProvider, &DPSPGUID_IPX))
{
// IPX just needs a service provider

// service provider
addressElements[dwElementCount].guidDataType = DPAID_ServiceProvider;
addressElements[dwElementCount].dwDataSize = sizeof(GUID);
addressElements[dwElementCount].lpData = (LPVOID) &DPSPGUID_IPX;
dwElementCount++;
}

// anything else, let service provider collect settings, if any
else
{
// service provider
addressElements[dwElementCount].guidDataType = DPAID_ServiceProvider;
addressElements[dwElementCount].dwDataSize = sizeof(GUID);
addressElements[dwElementCount].lpData = (LPVOID) &guidServiceProvider;
dwElementCount++;
}

// see how much room is needed to store this address
hr = lpDPlayLobby->lpVtbl->CreateCompoundAddress(lpDPlayLobby,
addressElements, dwElementCount,
NULL, &dwAddressSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// allocate space
lpAddress = GlobalAllocPtr(GHND, dwAddressSize);
if (lpAddress == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// create the address
hr = lpDPlayLobby->lpVtbl->CreateCompoundAddress(lpDPlayLobby,
addressElements, dwElementCount,
lpAddress, &dwAddressSize);
if FAILED(hr)
goto FAILURE;

// return the address info
*lplpAddress = lpAddress;
*lpdwAddressSize = dwAddressSize;

return (DP_OK);

FAILURE:
if (lpAddress)
GlobalFreePtr(lpAddress);

return (hr);
}

HRESULTDoHostOrJoin(HINSTANCE hInstance, HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby, BOOL bHost, LPDIRECTPLAY3A *lplpDPlay)
{
LPDIRECTPLAY3AlpDPlay = NULL;
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
DPSESSIONDESC2sessionDesc;
STATUSCONTEXTstatusContext;
HRESULThr;

// bail if we don't have a lobby interface
if (lpDPlayLobby == NULL)
return (DPERR_INVALIDOBJECT);

// get service provider address from information in dialog
hr = CreateServiceProviderAddress(hWnd, lpDPlayLobby, &lpAddress, &dwAddressSize);
if FAILED(hr)
goto FAILURE;

// interface already exists, so release it
if (*lplpDPlay)
{
(*lplpDPlay)->lpVtbl->Release(*lplpDPlay);
*lplpDPlay = NULL;
}

// create an ANSI DirectPlay3 interface
hr = CoCreateInstance(&CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER,
  &IID_IDirectPlay3A, (LPVOID*)&lpDPlay);
if FAILED(hr)
goto FAILURE;

// initialize the connection using the address
hr = lpDPlay->lpVtbl->InitializeConnection(lpDPlay, lpAddress, 0);
if FAILED(hr)
goto FAILURE;

if (bHost)
{
// host a new session
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
sessionDesc.dwFlags = DPSESSION_MIGRATEHOST | DPSESSION_KEEPALIVE;
sessionDesc.guidApplication = OVERRIDE_GUID;
sessionDesc.dwMaxPlayers = 0;
sessionDesc.lpszSessionNameA = "Override";

// open it
hr = lpDPlay->lpVtbl->Open(lpDPlay, &sessionDesc, DPOPEN_CREATE);
}

// enumerate the sessions
else
{
// display status dialog and enumerate until we find a session
statusContext.lpDPlay = lpDPlay;
statusContext.guidInstance = GUID_NULL;

if (!DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SESSIONSDIALOG), hWnd, SessionsWndProc, (LPARAM) &statusContext))
{
hr = DPERR_USERCANCEL;
goto FAILURE;
}

// open the session selected by the use
ZeroMemory(&sessionDesc, sizeof(DPSESSIONDESC2));
sessionDesc.dwSize = sizeof(DPSESSIONDESC2);
sessionDesc.guidApplication = OVERRIDE_GUID;
sessionDesc.guidInstance = statusContext.guidInstance;

// open it
hr = lpDPlay->lpVtbl->Open(lpDPlay, &sessionDesc, DPOPEN_JOIN);
}

if FAILED(hr)
goto FAILURE;

// return the connected interface
*lplpDPlay = lpDPlay;

// set to NULL so we don't release it below
lpDPlay = NULL;

FAILURE:
if (lpDPlay)
{
lpDPlay->lpVtbl->Close(lpDPlay);
lpDPlay->lpVtbl->Release(lpDPlay);
}

if (lpAddress)
GlobalFreePtr(lpAddress);

return (hr);
}

// ---------------------------------------------------------------------------
// EnumModemAddress
// ---------------------------------------------------------------------------
// Description:             Enumeration callback called by DirectPlayLobby.
//Enumerates the DirectPlay address chunks. If the
//chunk contains modem strings, add them to the control.
// Arguments:
//  REFGUID                 [in] GUID of the address type
//  DWORD[in] size of chunk
//  LPVOID    [in] pointer to chunk
//  LPVOID    [in] user-defined context
// Returns:
//  BOOLFALSE to stop enumerating after the first callback
BOOL FAR PASCAL EnumModemAddress(REFGUID lpguidDataType, DWORD dwDataSize,
LPCVOID lpData, LPVOID lpContext)
{
HWNDhWnd = (HWND) lpContext;
LPSTRlpszStr = (LPSTR) lpData;

// modem
if (IsEqualGUID(lpguidDataType, &DPAID_Modem))
{
// loop over all strings in list
while (lstrlen(lpszStr))
{
// store modem name in combo box
SendDlgItemMessage(hWnd, IDC_MODEMCOMBO, CB_ADDSTRING, 0, (LPARAM) lpszStr);

// skip to next string
lpszStr += lstrlen(lpszStr) + 1;
}
}

return (TRUE);
}

// ---------------------------------------------------------------------------
// FillModemComboBox
// ---------------------------------------------------------------------------
// Description:             Fills combo box with modem names
// Arguments:
//  HWND                    [in]  Window handle.
//  LPDIRECTPLAYLOBBY2A     [in]  DirectPlay Lobby interface to use
//  LPGUID[out] GUID of service provider to use
// Returns:
//  HRESULTany error
HRESULT FillModemComboBox(HWND hWnd, LPDIRECTPLAYLOBBY2A lpDPlayLobby)
{
LPDIRECTPLAYlpDPlay1 = NULL;
LPDIRECTPLAY3AlpDPlay3A = NULL;
LPVOIDlpAddress = NULL;
DWORDdwAddressSize = 0;
GUIDguidServiceProvider = DPSPGUID_MODEM;
HRESULThr;

// get a DirectPlay interface for this service provider
hr = DirectPlayCreate(&guidServiceProvider, &lpDPlay1, NULL);
if FAILED(hr)
goto FAILURE;

// query for an ANSI DirectPlay3 interface
hr = lpDPlay1->lpVtbl->QueryInterface(lpDPlay1, &IID_IDirectPlay3A, (LPVOID *) &lpDPlay3A);
if FAILED(hr)
goto FAILURE;

// get size of player address for player zero
hr = lpDPlay3A->lpVtbl->GetPlayerAddress(lpDPlay3A, DPID_ALLPLAYERS, NULL, &dwAddressSize);
if (hr != DPERR_BUFFERTOOSMALL)
goto FAILURE;

// make room for it
lpAddress = GlobalAllocPtr(GHND, dwAddressSize);
if (lpAddress == NULL)
{
hr = DPERR_NOMEMORY;
goto FAILURE;
}

// get the address
hr = lpDPlay3A->lpVtbl->GetPlayerAddress(lpDPlay3A, DPID_ALLPLAYERS, lpAddress, &dwAddressSize);
if FAILED(hr)
goto FAILURE;

// get modem strings from address and put them in the combo box
hr = lpDPlayLobby->lpVtbl->EnumAddress(lpDPlayLobby, EnumModemAddress,
 lpAddress, dwAddressSize, hWnd);
if FAILED(hr)
goto FAILURE;

// select first item in list
SendDlgItemMessage(hWnd, IDC_MODEMCOMBO, CB_SETCURSEL, (WPARAM) 0, 0);

FAILURE:
if (lpDPlay1)
lpDPlay1->lpVtbl->Release(lpDPlay1);
if (lpDPlay3A)
lpDPlay3A->lpVtbl->Release(lpDPlay3A);
if (lpAddress)
GlobalFreePtr(lpAddress);

return (hr);
}

BOOL DlgItemIsChecked(HWND hDlg, int nIDDlgItem)
{
return ((SendDlgItemMessage(hDlg, nIDDlgItem, BM_GETCHECK, (WPARAM) 0, (LPARAM) 0) == BST_CHECKED) ? TRUE : FALSE);
}

void EnableDlgButton(HWND hDlg, int nIDDlgItem, BOOL bEnable)
{
EnableWindow(GetDlgItem(hDlg, nIDDlgItem), bEnable);
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by override.rc
//
#define IDD_OVERRIDEDIALOG              101
#define IDD_SESSIONSDIALOG              102
#define IDC_IPXRADIO                    1000
#define IDC_TCPIPRADIO                  1001
#define IDC_TCPRADIO                    1001
#define IDC_IPEDIT                      1002
#define IDC_TCPEDIT                     1002
#define IDC_MODEMRADIO                  1003
#define IDC_SERIALRADIO                 1004
#define IDC_PHONEEDIT                   1005
#define IDC_MODEMCOMBO                  1006
#define IDC_HOSTBUTTON                  1007
#define IDC_JOINBUTTON                  1008
#define IDC_STATUSEDIT                  1009
#define IDC_SESSIONLIST                 1010
#define IDC_SPCOMBO                     1010
#define IDC_SESSIONSTATUSEDIT           1011
#define IDC_JOINSESSIONBUTTON           1013
#define IDC_IPXLABEL                    1015
#define IDC_SERVICEPROVIDERLABEL        1016
#define IDC_TCPEDITLABEL                1017
#define IDC_MODEMCOMBOLABEL             1018
#define IDC_PHONEEDITLABEL              1021

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1022
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

This is a basic example to show how to use DirectDraw palettes.

4 separate DirectDrawPalette objects are created, and they can be attached
to the primary surface just by selecting a menu item.    Any given palette
can be cycled through its red values.  The palettes can be created in
exclusive mode or not.


PALETTE.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:palette.cpp
 *  Content:Does some basic palette manipulation
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "lbprintf.h"

static charszClass[32]="DDTest1Class";
static charszCaption[32]="DirectDraw Palette Test";

/*
 * DirectDraw objects we will use
 */
#define MAX_PALETTES4
LPDIRECTDRAWlpDD;
LPDIRECTDRAWSURFACElpDDPSurf;
LPDIRECTDRAWPALETTElpDDPal[MAX_PALETTES];
LPDIRECTDRAWPALETTElpDDPalCurr;

HWNDhWndMain;

BOOLbIsExclusive = TRUE;
BOOLbActive;

/*
 * doAttach
 *
 * attach palette to the primary surface
 */
static void doAttach( int index )
{
    HRESULTddrval;

    ddrval = lpDDPSurf->SetPalette( lpDDPal[index] );
    if( ddrval == DDERR_SURFACELOST )
    {
        lpDDPSurf->Restore();
        ddrval = lpDDPSurf->SetPalette( lpDDPal[index] );
    }
    if (!FAILED(ddrval))
    {
       LBPrintfDDRC( ddrval, "doAttach1: SetPalette" );
       lpDDPalCurr = lpDDPal[index];
    }
    else
    {
LBPrintf( "Could not attach palette" );
lpDDPalCurr = NULL;
    }

} /* doAttach */

/*
 * doDetach
 *
 * detach the current palette from the primary surface
 */
static void doDetach( void )
{
    HRESULTddrval;

    ddrval = lpDDPSurf->SetPalette( NULL );
    if( ddrval == DDERR_SURFACELOST )
    {
        lpDDPSurf->Restore();
        ddrval = lpDDPSurf->SetPalette( NULL );
    }
    LBPrintfDDRC( ddrval, "doDetach: SetPalette" );
    lpDDPalCurr = NULL;

} /* doDetach */

/*
 * doCycle
 *
 * cycle the current palette's red entries by 10
 */
static void doCycle( void )
{
    if( lpDDPalCurr != NULL )
    {
#define ENTRIES 10
#define BASE 0
PALETTEENTRYpe[ENTRIES];
HRESULTddrval;
inti;

/*
 * display the original entries
 */
ddrval = lpDDPalCurr->GetEntries( 0, BASE, ENTRIES, pe );
if( ddrval == DDERR_SURFACELOST )
{
            lpDDPSurf->Restore();
    ddrval = lpDDPalCurr->GetEntries( 0, BASE, ENTRIES, pe );
}
LBPrintfDDRC( ddrval, "GetEntries" );
LBPrintf( "Old Entries" );
for( i=0;i<ENTRIES;i++ )
{
    LBPrintf( "    pe[%d] (r,g,b) = (%d,%d,%d)", i, pe[i].peRed,
pe[i].peGreen, pe[i].peBlue );
}

/*
 * cycle the red values
 */
for( i=0;i<ENTRIES;i++ )
{
    pe[i].peRed = (BYTE) (((int)pe[i].peRed + 50) % 256);
}
ddrval = lpDDPalCurr->SetEntries( 0, BASE, ENTRIES, pe );
if( ddrval == DDERR_SURFACELOST )
{
            lpDDPSurf->Restore();
    ddrval = lpDDPalCurr->SetEntries( 0, BASE, ENTRIES, pe );
}
LBPrintfDDRC( ddrval, "SetEntries" );

/*
 * display the new values
 */
ddrval = lpDDPalCurr->GetEntries( 0, BASE, ENTRIES, pe );
if( ddrval == DDERR_SURFACELOST )
{
            lpDDPSurf->Restore();
    ddrval = lpDDPalCurr->GetEntries( 0, BASE, ENTRIES, pe );
}
LBPrintfDDRC( ddrval, "GetEntries" );
LBPrintf( "New Entries" );
for( i=0;i<ENTRIES;i++ )
{
    LBPrintf( "    pe[%d] (r,g,b) = (%d,%d,%d)", i, pe[i].peRed,
pe[i].peGreen, pe[i].peBlue );
}
    }
    else
    {
LBPrintf( "No palette attached" );
    }

} /* doCycle */

/*
 * resetExclusiveAndPalettes
 *
 * Sets exclusive mode to the desired value, and the rebuilds
 * the palettes.
 *
 * Palettes in exclusive mode bypass GDI and go directly to the display
 * driver, so the performance is much higher.
 */
static BOOL resetExclusiveAndPalettes( BOOL excl )
{
    LPPALETTEENTRYppe;
    inti;
    HRESULTddrval;

    /*
     * create palette entries
     */
    ppe = (LPPALETTEENTRY) LocalAlloc( LPTR, sizeof( PALETTEENTRY ) * 256 );
    if( ppe == NULL )
    {
return FALSE;
    }

    /*
     * release existing palettes
     */
    for( i=0;i<MAX_PALETTES;i++ )
    {
if( lpDDPal[i] != NULL )
{
    ddrval = lpDDPal[i]->Release();
    LBPrintfDDRC( ddrval, "Release" );
    lpDDPal[i] = NULL;
}
    }

    /*
     * No current palette any more.
     */
    lpDDPalCurr = NULL;

    if( excl )
    {
ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    }
    else
    {
ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_NORMAL );
    }

    LBPrintfDDRC( ddrval, "SetCooperativeLevel" );

    /*
     * set up 4 palettes
     */
    ppe[0].peRed = 0;
    ppe[0].peGreen = 0;
    ppe[0].peBlue = 0;
    ppe[255].peRed = 255;
    ppe[255].peGreen = 255;
    ppe[255].peBlue = 255;

    for( i=1;i<255;i++ )
    {
ppe[i].peRed = 0;
ppe[i].peGreen = 0;
ppe[i].peBlue = 0;
    }
    ddrval = lpDD->CreatePalette( DDPCAPS_8BIT, ppe, &lpDDPal[0], NULL );
    LBPrintfDDRC( ddrval, "CreatePalette" );

    for( i=1;i<255;i++ )
    {
ppe[i].peRed = 0;
ppe[i].peGreen = 255;
ppe[i].peBlue = 255;
    }
    ddrval = lpDD->CreatePalette( DDPCAPS_8BIT, ppe, &lpDDPal[1], NULL );
    LBPrintfDDRC( ddrval, "CreatePalette" );

    for( i=1;i<255;i++ )
    {
ppe[i].peRed = 255;
ppe[i].peGreen = 255;
ppe[i].peBlue = 0;
    }
    ddrval = lpDD->CreatePalette( DDPCAPS_8BIT, ppe, &lpDDPal[2], NULL );
    LBPrintfDDRC( ddrval, "CreatePalette" );

    for( i=1;i<255;i++ )
    {
ppe[i].peRed = 255;
ppe[i].peGreen = 0;
ppe[i].peBlue = 255;
    }
    ddrval = lpDD->CreatePalette( DDPCAPS_8BIT, ppe, &lpDDPal[3], NULL );
    LBPrintfDDRC( ddrval, "CreatePalette" );
    LocalFree( ppe );

    for( i=0;i<MAX_PALETTES;i++ )
    {
LBPrintf( "lpDDPal %d = %08lx", i+1, lpDDPal[i] );
    }
    return TRUE;

} /* resetExclusiveAndPalettes */

/*
 * WindowProc
 *
 * Messages for our window are handled here
 */
LRESULT CALLBACK WindowProc( HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam )
{

    switch( uMsg )
    {
    case WM_COMMAND:
switch( LOWORD( wParam ) )
{
case IDM_EXCLUSIVE:
    bIsExclusive = !bIsExclusive;
    resetExclusiveAndPalettes( bIsExclusive );
    break;
case IDM_ATTACH1:
case IDM_ATTACH2:
case IDM_ATTACH3:
case IDM_ATTACH4:
    doAttach( LOWORD( wParam ) - IDM_ATTACH1 );
    break;
case IDM_DETACH:
    doDetach();
    break;
case IDM_CYCLE:
    doCycle();
    break;
case IDM_CLEAR:
    LBClear();
    break;
case IDM_EXIT:
    DestroyWindow( hWnd );
    break;
}
break;

    case WM_PAINT:
    {
PAINTSTRUCTps;

BeginPaint( hWnd, &ps );
EndPaint( hWnd, &ps );
break;
    }

    case WM_INITMENU:
CheckMenuItem( (HMENU) wParam, IDM_EXCLUSIVE, MF_BYCOMMAND |
(bIsExclusive ? MF_CHECKED:MF_UNCHECKED) );
break;

    case WM_SIZE:
/*
 * resize our message listbox
 */
LBSize( LOWORD( lParam ), HIWORD( lParam ) );
break;

    case WM_DESTROY:
/*
 * free all DirectDraw objects
 */
if( lpDD != NULL )
{
    int i;

    for( i=0;i<MAX_PALETTES;i++ )
    {
if( lpDDPal[i] != NULL )
{
    lpDDPal[i]->Release();
    lpDDPal[i] = NULL;
}
    }

    if( lpDDPSurf != NULL )
    {
lpDDPSurf->Release();
lpDDPSurf = NULL;
    }
    lpDD->Release();
    lpDD = NULL;
}
PostQuitMessage( 0 );
break;

    default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
    }
    return 0L;

} /* WindowProc */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSwc;
    HRESULTddrval;
    DDSURFACEDESCddsd;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC) WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof( DWORD );
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
    wc.lpszMenuName = MAKEINTRESOURCE( IDR_MENU );
    wc.lpszClassName = szClass;
    if( !RegisterClass( &wc ) )
    {
return FALSE;
    }

    /*
     * create a window
     */
    hWndMain = CreateWindow(
szClass,// class
szCaption,// caption
WS_OVERLAPPEDWINDOW,// style
CW_USEDEFAULT,// x pos
CW_USEDEFAULT,// y pos
CW_USEDEFAULT,// width
CW_USEDEFAULT,// height
NULL,// parent window
NULL,// menu
hInstance,// instance
NULL// parms
);

    if( !hWndMain )
    {
return FALSE;
    }

    LBCreate( hWndMain, 100 );

    ShowWindow( hWndMain, nCmdShow );
    UpdateWindow( hWndMain );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    LBPrintfDDRC( ddrval, "DirectDrawCreate" );
    if( ddrval != DD_OK )
    {
DestroyWindow( hWndMain );
return FALSE;
    }

    ddrval = lpDD->SetCooperativeLevel( hWndMain, DDSCL_NORMAL );

    /*
     * create the primary surface
     */
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDPSurf, NULL );
    LBPrintfDDRC( ddrval, "CreateSurface" );
    if( ddrval != DD_OK )
    {
lpDD->Release();
DestroyWindow( hWndMain );
return FALSE;
    }

    /*
     * set up palettes
     */
    if( !resetExclusiveAndPalettes( bIsExclusive ) )
    {
lpDDPSurf->Release();
lpDD->Release();
DestroyWindow( hWndMain );
return FALSE;
    }

    return TRUE;

} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSGmsg;

    hPrevInstance = hPrevInstance;
    lpCmdLine = lpCmdLine;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( GetMessage( &msg, NULL, NULL, NULL ) )
    {
TranslateMessage( &msg );
DispatchMessage( &msg );
    }

    return( msg.wParam );

} /* WinMain */


RESOURCE.H

#define IDR_MENU                        102
#define IDM_EXIT                        40001
#define IDM_ATTACH1                     40002
#define IDM_ATTACH2                     40003
#define IDM_ATTACH3                     40004
#define IDM_ATTACH4                     40005
#define IDM_DETACH                      40006
#define IDM_CYCLE                       40007
#define IDM_CLEAR                       40008
#define IDM_EXCLUSIVE                   40009


COMMON.H

#ifndef COMMON_H
#define COMMON_H
/*
**-----------------------------------------------------------------------------
**  File:       Common.h
**  Purpose:    Common definitions and include files
**
**  Microsoft copyright (c) 1996, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <tchar.h>

#include <ddraw.h>

// Note:  Must Define D3D_OVERLOADS to get C++ version of D3DMATRIX
#define D3D_OVERLOADS
#include <d3d.h>
#include <d3dtypes.h>

#include "resource.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/
#ifndef FAR
    #define FAR
#endif

#ifndef NEAR
    #define NEAR
#endif

#ifndef CONST
    #define CONST const
#endif

#ifndef MAX_PATH
    #define MAX_PATH 260
#endif

#ifndef MAX_STRING
    #define MAX_STRING 260
#endif

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

#ifndef FALSE
    #define FALSE 0
#endif

#ifndef TRUE
    #define TRUE 1
#endif

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Macros
**-----------------------------------------------------------------------------
*/
#ifndef max
    #define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
    #define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#ifndef max3
    #define max3(a,b,c)         (((a) > (b)) ? (((a) > (c)) ? (a) : (c)) : (((b) > (c)) ? (b) : (c)))
#endif

#ifndef min3
    #define min3(a,b,c)         (((a) < (b)) ? (((a) < (c)) ? (a) : (c)) : (((b) < (c)) ? (b) : (c)))
#endif

#ifndef clamp
    #define clamp(L,V,U)        (((L) > (V)) ? (L) : (((U) < (V)) ? (U) : (V)))
#endif

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // COMMON_H


D3DSCENE.H

#ifndef D3DSCENE_H
#define D3DSCENE_H
/*
**-----------------------------------------------------------------------------
**  File:       D3DScene.h
**  Purpose:    Sample D3D Scene Management code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "D3DWin.h"

/*
**-----------------------------------------------------------------------------
**  Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:D3DScene
**  Purpose:
**-----------------------------------------------------------------------------
*/

class D3DScene {
private:
protected:
// Data
LPD3DWindow lpd3dWindow;// Associated D3D Window

public:

D3DScene ();
~D3DScene ();

HRESULT Init (LPD3DWindow lpd3dWin);
HRESULT Fini (void);

HRESULT InitViewport (void);
HRESULT FiniViewport (void);

HRESULT Render (void);
HRESULT Restore (void);

}; // End class D3DScene

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End D3DSCENE_H


D3DWIN.CPP

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWin.cpp
**  Purpose:    Shows how to set up a windowed D3D framework
**  Notes:
**
**Basic Initialization proceeds as follows:
**
**1.  Enumerate all Driver, modes, D3D devices (see DrvMgr.cpp for details)
**2.  Choose a starting driver, mode, D3D device
**- default driver = primary display driver (lpGuidDD = NULL)
**- default mode   = current desktop
**- default device = D3D device compatible with desktop mode
**3.  Validate driver, mode, D3D device
**4.  Create interfaces (from DD driver)
**  5.  Set window (from associated window handle)
**  6.  Create DD/D3D interfaces (lpDD, lpDD2, lpD3D)
**  7.  Create Primary surface (primary palette, if necessary)
**- Attach a clipper to primary surface
**  8.  Create Render surface
**- Render surface (and associated Z-buffer)
**- D3D Device
**- D3D Viewport
**
**  After initialization is complete, we have the
**following objects necessary for rendering:
**
**lpDD2 - DirectDraw interface, used for creating texture surfaces
**lpD3D - Direct3D interface, used for creating materials, lights, viewports
**lpD3DDevice - D3D device (current material, current viewport, etc.)
**lpViewport - current viewport
**lpPrimary  - front buffer
**lpRender   - render target
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Includes
**-----------------------------------------------------------------------------
*/

#include "D3DWin.h"
#include "WinProc.h"
#include "D3DScene.h"
#include "Debug.h"

/*
**-----------------------------------------------------------------------------
**D3DWindow Methods
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::D3DWindow
** Purpose: Default Constructor
**-----------------------------------------------------------------------------
*/

D3DWindow::D3DWindow (void)
{
    ZeroMemory (this, sizeof(D3DWindow));
    this->dwSize = sizeof(D3DWindow);

// Default to creating a z-buffer
createZBufferOn ();
} // End D3DWindow::D3DWindow ()

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::~D3DWindow
** Purpose: Destructor
**-----------------------------------------------------------------------------
*/

D3DWindow::~D3DWindow (void)
{
// Destroy all objects
    Fini ();

// Mark all other pointers as invalid
// In case user tries to reuse this object incorrectly
lpCurrDriver = NULL;
lpCurrMode = NULL;
lpCurrDevice = NULL;
hWindow = NULL;
lpd3dScene = NULL;

} // End D3DWindow::~D3DWindow

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Create
** Purpose: Creates a D3DWindow
**
** Basic Algorithm:
**- Validate parameters
**- Choose (and validate choices) for driver, mode, device
**- Create Interfaces
**- Set Window
**- Create Primary surface (and palette)
**- Create Render surface (and D3D device)
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Create (
HWND   hWnd,/* In:  Window */
LPGUID lpGuidDD,/* In:  Requested DirectDraw Device */
DWORD  dwW,/* In:Requested Mode */
DWORD  dwH,
DWORD  dwBPP,
DWORD  dwRefresh,
LPGUID lpGuidD3D,/* In:  Requested D3D device */
BOOL   fUseZBuffer, /* In:Create Z-buffer */
LPRECT lprSurf)/* In:  requested surface size (NULL => use client area) */
{
    HRESULT         hResult;

    // Check parameters
if ((! hWnd) || (! IsWindow (hWnd)))
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

// Set Current Window
hWindow = hWnd;

    // Set Use Z-Buffer On/Off
    if (fUseZBuffer)
        createZBufferOn ();
    else
        createZBufferOff ();

    // Choose Default Driver, Mode, device
    hResult = ChooseDriverDefaults (lpGuidDD,
dwW, dwH, dwBPP, dwRefresh,
lpGuidD3D,
FALSE,
&lpCurrDriver,
&lpCurrMode,
&lpCurrDevice);
    if (FAILED (hResult))
        return hResult;

    // Create DD/D3D Interface objects
    hResult = InitInterfaces ();
    if (FAILED (hResult))
        return hResult;

// Attach window to DD interface
hResult = InitWindow ();
if (FAILED (hResult))
goto lblCLEANUP;

    // Create Primary Surface (and palette)
    hResult = InitPrimary ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Get initial surface size
if (lprSurf)
rSurf = *lprSurf;
else
GetClientRect (hWindow, &rSurf);

// Create the Render Surface (and D3D Device)
    hResult = InitRender ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Notify the window of a successful initialization
SendMessage (hWindow, D3DWIN_INIT, 0, (LPARAM)(void *)this);

    // Success
    return DD_OK;

lblCLEANUP:
    // Cleanup
    Fini ();

    return hResult;
} // End D3DWindow::Create

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Init
** Purpose:
**
** Basic Algorithm:
**- Validate driver, mode, device
**- Create Interfaces
**- Attach Window
**- Create Primary surface (and palette)
**- Create Render surface (and D3D device)
**
** Notes:
**1.  Assumes that a valid window handle has already
**been associated with this D3DWindow
**2.  Assumes that driver, mode, device already choosen
**- however if not, reasonable defaults will be choosen
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Init (void)
{
    HRESULT         hResult;

    // Check parameters
if ((! hWindow) || (! IsWindow (hWindow)))
{
// Error, we absolutely need a valid window
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

    // Validate Curr Driver, mode, device
hResult = ValidateDefaults ();
if (FAILED (hResult))
return hResult;

    // Create DD/D3D Interface objects
    hResult = InitInterfaces ();
    if (FAILED (hResult))
goto lblCLEANUP;

// Attach the window to the DD interface
hResult = InitWindow ();
if (FAILED (hResult))
goto lblCLEANUP;

    // Create Primary Surface (and palette)
    hResult = InitPrimary ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Create Render surface (and D3D device)
    hResult = InitRender ();
    if (FAILED (hResult))
        goto lblCLEANUP;

// Notify the window of a successful initialization
SendMessage (hWindow, D3DWIN_INIT, 0, (LPARAM)(void *)this);

    // Success
    return DD_OK;

lblCLEANUP:
    // Cleanup
    Fini ();
    return hResult;
} // End D3DWindow::Init

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Fini
** Purpose: Destroys a D3DWindow
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Fini (void)
{
// Notify the window that we are cleaning up
SendMessage (hWindow, D3DWIN_FINI, 0, (LPARAM)(void *)this);

// Cleanup
    FiniRender ();
    FiniPrimary ();
//FiniWindow ();
    FiniInterfaces ();

// Success
return DD_OK;
} // End D3DWindow::Fini

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ValidateDefaults
** Purpose: Verify's current driver, mode, and device
** Notes:
**
**1.  Rather than fail completely, this will pick new defaults
**      if the current defaults don't work.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ValidateDefaults (void)
{
LPGUIDlpGuidDD, lpGuidD3D;
HRESULThResult;
    LPDDDrvInfolpDrvNew;
LPDDModeInfolpModeNew;
LPD3DDevInfolpDevNew;

// Initialize Driver Manager, if necessary
if (! DDDrvMgr::isInitialized ())
{
hResult = DDDrvMgr::Init ();
if (FAILED (hResult))
return DDERR_INVALIDPARAMS;
}

    // Get DD Guid
if (lpCurrDriver)
lpGuidDD = lpCurrDriver->GetGuid ();
else
lpGuidDD = NULL;

// Get D3D Guid
if (lpCurrDevice)
lpGuidD3D = &(lpCurrDevice->guid);
else
lpGuidD3D = NULL;

// Get Driver corresponding to DD Guid
    lpDrvNew = ValidateDriver (lpGuidDD);
if (! lpDrvNew)
    {
        // Error, invalid DD Guid
REPORTERR (DDERR_INVALIDPARAMS);
        return DDERR_INVALIDPARAMS;
    }

// Get Desktop mode and compatible D3D device
if (! GetDesktopMode (lpDrvNew, lpGuidD3D, &lpModeNew, &lpDevNew))
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Note:  Instead of complaining let's go ahead
//  and use the new defaults
// Save new defaults
lpCurrDriver = lpDrvNew;
lpCurrMode = lpModeNew;
lpCurrDevice = lpDevNew;

    // Success
    return DD_OK;
} // End D3DWindow::ValidateDefaults

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::CreateInterfaces
** Purpose: Creates DD/D3D interfaces from specified Guid
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::CreateInterfaces (LPGUID lpDDGuid)
{
LPDDDrvInfo lpDrvNew;
HRESULThResult;

// Verify Guid
lpDrvNew = ValidateDriver (lpDDGuid);
if (! lpDrvNew)
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

lpCurrDriver = lpDrvNew;

hResult = D3DWindow::InitInterfaces ();
if (FAILED (hResult))
return hResult;

// Success
return DD_OK;
} // End D3DWindow::CreateInterfaces

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitInterfaces
** Purpose: Creates DD/D3D interfaces
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitInterfaces (void)
{
    HRESULT         hResult;
    LPGUID          lpGuid;

    // Do we have a current DD Driver
    if (! lpCurrDriver)
    {
// Try to grab the Primary DD driver instead
lpCurrDriver = ValidateDriver (NULL);
if (! lpCurrDriver)
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}
    }

    // Get DD Guid
    lpGuid = lpCurrDriver->GetGuid ();

    // Create DD interface
    hResult = DirectDrawCreate (lpGuid, &lpDD, NULL);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);
goto lblERROR;
    }

    // Get DD2 interface
    hResult = lpDD->QueryInterface ((REFIID)IID_IDirectDraw2, (void **)&lpDD2);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);
        goto lblERROR;
    }

    // Get D3D interface
    hResult = lpDD2->QueryInterface ((REFIID)IID_IDirect3D2, (void **)&lpD3D);
    if (FAILED (hResult))
    {
        // Error
REPORTERR (hResult);
        goto lblERROR;
    }

// Mark this stage as done
turnValidInterfaceOn ();

    // Success
    return DD_OK;

lblERROR:
    // Failure
    FiniInterfaces ();

    return hResult;
} // End InitInterfaces

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniInterfaces
** Purpose: Destroys DD/D3D interfaces
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniInterfaces (void)
{
// Mark this stage as invalid
turnValidInterfaceOff ();

    // Release Direct3D Interface
    if (lpD3D)
    {
        lpD3D->Release ();
        lpD3D = NULL;
    }

    // Release DirectDraw2 Interface
    if (lpDD2)
    {
        lpDD2->Release ();
        lpDD2 = NULL;
    }

    // Release DirectDraw Interface
    if (lpDD)
    {
        lpDD->Release ();
        lpDD = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniInterfaces

/*
**-----------------------------------------------------------------------------
**Name:D3DWindow::InitWindow
**Purpose:Attaches Window to Direct Draw Interface
**Notes:Assumes window handle already associated with this D3DWindow.
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitWindow (void)
{
HRESULT hResult;
DWORDdwFlags;

// Check Initialization
if ((! hWindow) || (! IsWindow (hWindow)))
{
// Error, we have to have an associated window to continue
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

    // Get Cooperative Flags
    dwFlags = DDSCL_NORMAL;

    // Set Cooperative Level
    hResult = lpDD2->SetCooperativeLevel (hWindow, dwFlags);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
return hResult;
    }

// Success
return DD_OK;
} // End D3DWindow::InitWindow

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniWindow
** Purpose: Cleanups window
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniWindow (void)
{
// Currently does nothing

// Success
return DD_OK;
} // End D3DWindow::FiniWindow

/*
**-----------------------------------------------------------------------------
**Name:    D3DWindow::InitPrimary
**Purpose: Creates a primary surface
**
**Basic Algorithm:
**1.  Create primary surface (from desktop mode)
**2.  Create and attach a clipper object
**3.  Create and attach a palette object, if desktop is palettized
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitPrimary (void)
{
    HRESULThResult;
    DDSURFACEDESCddsd;
    LPDIRECTDRAWCLIPPER lpddcClipper = NULL;

    // Check Initialization
    if ((! lpCurrMode) || (! lpDD2))
    {
        // Error
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
    }

//
// Step 1.  Create Primary Surface
//

// Note:  No need to fill in width, height, etc.
//        They system knows how to do it automatically
//  for a primary surface
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

// Create Primary surface
    hResult = lpDD2->CreateSurface (&ddsd, &lpddsPrimary, NULL);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        return hResult;
    }

//
// Step 2.   Create and attach clipper
//
// Note:This needs to be done to prevent drawing outside
//of the windows current client area

// Create Clipper object
    hResult = DirectDrawCreateClipper (0L, &lpddcClipper, 0);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
goto lblCLEANUP;
    }

// Attach window to clipper
    hResult = lpddcClipper->SetHWnd (0L, hWindow);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
goto lblCLEANUP;
    }

// Attach clipper to primary surface
    hResult = lpddsPrimary->SetClipper (lpddcClipper);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
goto lblCLEANUP;
    }

    // Go ahead and release the clipper interface,
    // we don't need it anymore and it will be
    // destroyed automatically with the surface
    lpddcClipper->Release ();

// Create and attach palette, if necessary
hResult = InitPalette ();
if (FAILED (hResult))
goto lblCLEANUP;

// Mark as Valid
turnValidPrimaryOn ();

    // Success
    return DD_OK;

lblCLEANUP:
// Failure

// Cleanup before leaving
if (lpddcClipper)
lpddcClipper->Release ();

FiniPrimary ();

return hResult;
} // End D3DWindow::InitPrimary

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniPrimary
** Purpose: Destroys the Primary Surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniPrimary (void)
{
// Mark as Invalid
turnValidPrimaryOff ();

// Cleanup palette
FiniPalette ();

    // Release Primary Surface Object
    if (lpddsPrimary)
    {
        lpddsPrimary->Release ();
        lpddsPrimary = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniPrimary

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitPalette
** Purpose: Creates a primary palette if necessary
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitPalette ()
{
    HRESULT             hResult;
    HDC                 hdc;
    DWORD               ii;
    PALETTEENTRY        pePalette[256];
    DWORD               cPal;
    DWORD               cbSize;
    DWORD               dwFlags;
    DDSURFACEDESC       ddsd;

    // Destroy old palette
    FiniPalette ();

    // Make sure we are properly intialized
    // for this to work
    if ((! lpDD2) || (! lpddsPrimary))
    {
        // Error
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
    }

    // Get primary surface caps
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    hResult = lpddsPrimary->GetSurfaceDesc(&ddsd);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        return hResult;
    }

    // Make sure it is a palettized surface
    if (! isPalettized (&(ddsd.ddpfPixelFormat)))
    {
        // Success, primary isn't palettized
// So we don't need to create a palette
        return DD_OK;
    }

    // Create and save System palette
    hdc = GetDC (NULL);
    cPal = GetDeviceCaps (hdc, SIZEPALETTE);
    if (cPal)
    {
        if (cPal > 256)
            cPal = 256;

        // Get memory for palette entries
        lppePalette = new PALETTEENTRY[cPal];
        if (! lppePalette)
        {
            // Error, not enough memory
ReleaseDC (NULL, hdc);

hResult = DDERR_OUTOFMEMORY;
REPORTERR (hResult);
goto lblCLEANUP;
        }

        // Save system palette
        GetSystemPaletteEntries (hdc, 0, cPal, lppePalette);

        // Copy system palette to temporary values
        cbSize = cPal * sizeof (PALETTEENTRY);
        CopyMemory (pePalette, lppePalette, cbSize);
    }
ReleaseDC (NULL, hdc);

    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED1)
    {
        dwFlags = DDPCAPS_1BIT;

        // Only 2 palette entries, we need them all
        for (ii = 0; ii < 2; ii++)
            pePalette[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED2)
    {
        // Only 4 palette entries, we need them all
        for (ii = 0; ii < 4; ii++)
            pePalette[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_2BIT;
    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4)
    {
        // Only 16 palette entries, we will save black and white
        // and keep the rest for ourselves.

        pePalette[0].peFlags = D3DPAL_READONLY;
        pePalette[15].peFlags = D3DPAL_READONLY;

        for (ii = 1; ii < 15; ii++)
            pePalette[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_4BIT;
    }
    else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
    {
        // 256 palette entries, we can afford to be nice
        // and save the first 10 and last 10 palette entries
        // for system use
        for (ii = 0; ii < 10; ii++)
        {
            pePalette[ii].peFlags = D3DPAL_READONLY;
            pePalette[246+ii].peFlags = D3DPAL_READONLY;
        }

        for (ii = 10; ii < 246; ii++)
            pePalette[ii].peFlags = D3DPAL_FREE | PC_RESERVED;

        dwFlags = DDPCAPS_8BIT;
    }
    else
    {
        // Error, programming (unknown palette type)
hResult = DDERR_GENERIC;
REPORTERR (hResult);
goto lblCLEANUP;
    }

    // Create Primary Palette
    hResult = lpDD2->CreatePalette (dwFlags,
                                    pePalette,
                                    &lpddpPalette,
                                    NULL);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Attach palette to primary surface
    hResult = lpddsPrimary->SetPalette (lpddpPalette);
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        goto lblCLEANUP;
    }

    // Success
    return DD_OK;

lblCLEANUP:
// Cleanup before leaving
    FiniPalette ();

// Failure
return hResult;
} // D3DWindow::InitPalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniPalette
** Purpose: Destroys primary palette
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniPalette (void)
{
    // Note:  Should we Detach Palette object from surfaces
    // No way to do this that I know of...

    // Cleanup up DD Palette object
    if (lpddpPalette)
    {
        lpddpPalette->Release ();
        lpddpPalette = NULL;
    }

    // Cleanup System Palette
    if (lppePalette)
    {
        // Note:  Should we try and restore system palette here ?!?

        // Destroy system palette
        delete [] lppePalette;
        lppePalette = NULL;
    }

// Success
return DD_OK;
} // End FiniPalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ValidateSize
** Purpose: Validate Surface Size
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ValidateSize (void)
{
long x, y, w,h;
    long wwMax, whMax;  // maximum windows width, height
    long wwMin, whMin;  // minimum windows width, height

// Get Min, max window sizes
    wwMax = GetSystemMetrics (SM_CXMAXTRACK);
    whMax = GetSystemMetrics (SM_CYMAXTRACK);
    wwMin = GetSystemMetrics (SM_CXMIN);
    whMin = GetSystemMetrics (SM_CYMIN);

// Null Size => use client area of window for size
w = abs (rSurf.right - rSurf.left);
h = abs (rSurf.bottom - rSurf.top);
if ((w == 0) || (h == 0))
{
if ((! hWindow) || (! IsWindow (hWindow)))
return DDERR_GENERIC;

GetClientRect (hWindow, &rSurf);
}

// Get canonical pos, size
if (rSurf.right < rSurf.left)
{
x = rSurf.right;
w = rSurf.left - rSurf.right;
}
else
{
x = rSurf.left;
w = rSurf.right - rSurf.left;
}

if (rSurf.bottom < rSurf.top)
{
y = rSurf.bottom;
h = rSurf.top - rSurf.bottom;
}
else
{
y = rSurf.top;
h = rSurf.bottom - rSurf.top;
}

// Clamp to min,max sizes
if (w < D3DWIN_MIN_SIZE)
w = D3DWIN_MIN_SIZE;
if (w < wwMin)
w = wwMin;
if (w > wwMax)
w = wwMax;

if (h < D3DWIN_MIN_SIZE)
h = D3DWIN_MIN_SIZE;
if (h < whMin)
h = whMin;
if (h > whMax)
h = whMax;

// Save validated surface
rSurf.left   = x;
rSurf.right  = x + w;
rSurf.top    = y;
rSurf.bottom = y + h;

// Success
return DD_OK;
} // End D3DWindow::ValidateSize

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::CreateRender
** Purpose: Creates the rendering surface from requested device and
**surface size
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::CreateRender (LPGUID lpD3DGuid, LPRECT lprSurf)
{
HRESULT hResult;
LPD3DDevInfo lpDevNew;

// Check Initialization
if ((! lpCurrDriver) || (! lpCurrMode))
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Make sure this D3D device works with this mode
lpDevNew = ValidateDevice (lpCurrDriver, lpD3DGuid, lpCurrMode);
if (! lpDevNew)
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}
// Save new D3D device
lpCurrDevice = lpDevNew;

// Get Surface Size
if (! lprSurf)
GetClientRect (hWindow, &rSurf);
else
rSurf = *lprSurf;

// Cleanup old render surface
FiniRender ();

// Create new Render surface (using new Device)
hResult = InitRender ();
if (FAILED (hResult))
return hResult;

// Success
return DD_OK;
}

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::InitRender
** Purpose: Creates the rendering surface and D3D device
** Notes:
**
**1. The main problem here is a catch 22 situation.  In order to create the
**   D3D device you need a valid render surface.   However, in order to create
**     a render surface properly, you need to know some D3D device caps
**- Does the device support Z-buffers
**- Video vs. System memory
**- etc.
**
**   Fortunately, our Driver Manager already has all that information.
**   So we can break the catch 22.  Without having to resort to some extra
**     complexity in the code here.
**
**
** Basic Algorithm:
**
**  1. Validate Device
**  2. Validate Surface size
**  3. Setup Surface Desc (width, height, bpp)
**4. Create Render Surface (from surface desc)
**  5. Create Z-buffer (optional)
**6. Create D3D Device
**7. Create Viewport
**8. InitScene (optional)
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitRender (void)
{
    HRESULT         hResult;
    DWORD           dwMemType;
    LPD3DDEVICEDESC lpDeviceDesc;
    DDSURFACEDESC   ddsd;
    DWORD           dwWidth, dwHeight, dwBPP;
LPGUIDlpGuidD3D;
LPD3DDevInfolpDevNew;

// Check Initialization
    if ((! hWindow) || (! IsWindow (hWindow)) ||
(! lpCurrDevice) || (! lpCurrMode) ||
(! lpDD2) || (! lpD3D) || (! lpddsPrimary))
    {
        // Error, Not initialized properly
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
    }

//
// Step 1.  Validate Device
//
if (lpCurrDevice)
lpGuidD3D = &(lpCurrDevice->guid);
else
lpGuidD3D = NULL;

lpDevNew = ValidateDevice (lpCurrDriver, lpGuidD3D, lpCurrMode);
if (! lpDevNew)
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Save new D3D device
lpCurrDevice = lpDevNew;

//
// Step 2. Validate Surface size
//
hResult = ValidateSize ();
if (FAILED (hResult))
return hResult;

dwWidth = rSurf.right - rSurf.left;
dwHeight = rSurf.bottom - rSurf.top;

    //
// Step 3.  Setup DD surface desc (width, height, BPP)
//

// Get Memory Type
if (lpCurrDevice->isHardware ())
{
// Hardware device
dwMemType = DDSCAPS_VIDEOMEMORY;
lpDeviceDesc = &(lpCurrDevice->d3dHalDesc);
}
else
{
// Software device
// Note:  It doesn't have to be in system memory but
//        it makes debugging a lot easier
//  (Win16locks don't get in the way)
dwMemType = DDSCAPS_SYSTEMMEMORY;
lpDeviceDesc = &(lpCurrDevice->d3dHelDesc);
}

    // Get primary surface caps
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof (DDSURFACEDESC);
    hResult = lpddsPrimary->GetSurfaceDesc (&ddsd);
    if (FAILED (hResult))
    {
      // Error
      REPORTERR (hResult);
      return hResult;
    }

    // Create device surface
// Note: We don't need to specify the pixel format, it will
//       automatically default to same format as primary
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;// | DDSD_PIXELFORMAT;
    ddsd.dwWidth        = dwWidth;
    ddsd.dwHeight       = dwHeight;
    ddsd.ddsCaps.dwCaps = DDSCAPS_3DDEVICE | DDSCAPS_OFFSCREENPLAIN | dwMemType;

//
// Step 2.  Create Rendering Surface (from dd surface desc)
//
hResult = lpDD2->CreateSurface (&ddsd, &lpddsRender, NULL);
if (FAILED(hResult))
{
REPORTERR (hResult);
return hResult;
}

// Attach palette, if any
if (NULL != lpddpPalette)
{
hResult = lpddsRender->SetPalette (lpddpPalette);
if (FAILED (hResult))
{
// Error
REPORTERR (hResult);
return hResult;
}
}

//
// Step 3.Create and attach Z-buffer (optional)
//
    if ((isCreateZBuffer ()) &&
        (0L != lpDeviceDesc->dwDeviceZBufferBitDepth))
    {
        dwBPP = FlagsToBitDepth (lpDeviceDesc->dwDeviceZBufferBitDepth);

        // Create the z-buffer.
        ZeroMemory (&ddsd, sizeof(ddsd));
        ddsd.dwSize            = sizeof(ddsd);
        ddsd.dwFlags           = DDSD_CAPS   |
                                 DDSD_WIDTH  |
                                 DDSD_HEIGHT |
                                 DDSD_ZBUFFERBITDEPTH;
        ddsd.ddsCaps.dwCaps    = DDSCAPS_ZBUFFER | dwMemType;
        ddsd.dwWidth           = dwWidth;
        ddsd.dwHeight          = dwHeight;
        ddsd.dwZBufferBitDepth = dwBPP;
        hResult = lpDD2->CreateSurface (&ddsd, &lpddsZBuff, NULL);
        if (FAILED(hResult))
        {
            // Note: we may be able to continue without a z buffer
            REPORTERR (hResult);
        }
else
{
// Attach Z-buffer to rendering surface
hResult = lpddsRender->AddAttachedSurface (lpddsZBuff);
if (FAILED (hResult))
{
// Note: we may be able to continue without a z buffer
REPORTERR (hResult);
if (lpddsZBuff)
{
lpddsZBuff->Release ();
lpddsZBuff = NULL;
}
}
}
    }

    //
    // Step 4.  Create the D3D device interface
//
hResult = lpD3D->CreateDevice (lpCurrDevice->guid,
    lpddsRender,
    &lpd3dDevice);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

//
// Step 5.  Create the viewport
//
hResult = InitViewport ();
if (FAILED (hResult))
return hResult;

// Mark as valid
turnValidRenderOn ();
turnCalcRectsOn ();

//
// Step 6.Initialize Scene (if attached)
//
if (lpd3dScene)
{
hResult = lpd3dScene->Init (this);
if (FAILED (hResult))
return hResult;
}

    // Success
    return DD_OK;
} // End D3DWindow::InitRender

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::FiniRender
** Purpose: Destroys the Rendering surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniRender (void)
{
// Mark as invalid
turnValidRenderOff ();

// Cleanup Associated Scene
if (lpd3dScene)
{
lpd3dScene->Fini ();
}

// Cleanup viewport
FiniViewport ();

    // Release D3D Device
    if (lpd3dDevice)
    {
        lpd3dDevice->Release ();
        lpd3dDevice = NULL;
    }

    // Release Z Buffer
    if (lpddsZBuff)
    {
        // Detach Z-Buffer from back buffer
        if (lpddsRender)
            lpddsRender->DeleteAttachedSurface (0L, lpddsZBuff);
        lpddsZBuff->Release ();
        lpddsZBuff = NULL;
    }

    // Release rendering surface
    if (lpddsRender)
    {
        lpddsRender->Release ();
        lpddsRender = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniRender

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::InitViewport
**  Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::InitViewport (void)
{
HRESULT hResult;

// Check Initialization
if ((! lpD3D) || (! lpd3dDevice))
{
        REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
}

// Create Viewport
    hResult = lpD3D->CreateViewport (&lpd3dViewport, NULL);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Attach viewport to D3D device
    hResult = lpd3dDevice->AddViewport (lpd3dViewport);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Set up Initial Viewport parameters
hResult = UpdateViewport ();
    if (FAILED (hResult))
        return hResult;

/// Success
return DD_OK;
} // End D3DWindow::InitViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::FiniViewport
**  Purpose:    Cleanup viewport
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::FiniViewport (void)
{
// Release D3D viewport
    if (lpd3dViewport)
    {
        lpd3dViewport->Release ();
        lpd3dViewport  = NULL;
    }

// Success
return DD_OK;
} // End D3DWindow::FiniViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::UpdateViewport
**  Purpose:    Keeps viewport updated with current window size
**  Notes:
**
**1. The viewport construction here assumes that you are rendering
**Triangles using the D3DVERTEX and D3DIM is doing Transform,
**lighting, and rasterization for you.
**
**2. If you are rendering triangles using D3DTLVERTEX and doing your
**   own transform and lighting then you need to setup the viewport
**   differently.   As follows:
**
**      // Replace the following values below:
**dvClipX= 0.0f;
**dvClipY= 0.0f;
**dvClipWidth= dwSurfW;
**dvClipHeight= dvSurfH;
**
**  3. This perserves the aspect ratio.  If you don't need or want to
**     perserve the aspect ratio then set inv_aspect = 1.0 below and
**     work this constant through the rest of the viewport setup.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::UpdateViewport (void)
{
    HRESULThResult;
    D3DVIEWPORT2d3dViewport;
DWORDdwSurfW, dwSurfH;

    // Check Parameters
if ((! lpd3dDevice) || (! lpd3dViewport))
{
// Not properly initialized.
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Get Surface Width and Height
dwSurfW = abs (rSurf.right - rSurf.left);
dwSurfH = abs (rSurf.bottom - rSurf.top);

float inv_aspect;

if (dwSurfW)
inv_aspect = (float)dwSurfH/(float)dwSurfW;
else
                inv_aspect = 1.0f;

    // Update Viewport
    ZeroMemory (&d3dViewport, sizeof(d3dViewport));
    d3dViewport.dwSize= sizeof(d3dViewport);     // Always set size of structure!!!
    d3dViewport.dwX= 0UL;
    d3dViewport.dwY= 0UL;
    d3dViewport.dwWidth= dwSurfW;
    d3dViewport.dwHeight= dwSurfH;
    d3dViewport.dvClipX= -1.0f;
    d3dViewport.dvClipY= inv_aspect;
    d3dViewport.dvClipWidth= 2.0f;
    d3dViewport.dvClipHeight = 2.0f * inv_aspect;
    d3dViewport.dvMinZ= 0.0f;
    d3dViewport.dvMaxZ= 1.0f;

// Update Viewport
    hResult = lpd3dViewport->SetViewport2 (&d3dViewport);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

// Update D3D device to use this viewport
    hResult = lpd3dDevice->SetCurrentViewport (lpd3dViewport);
    if (FAILED (hResult))
    {
        REPORTERR (hResult);
        return hResult;
    }

    // Success
    return DD_OK;
} // End D3DWindow::UpdateViewport

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::DrawFrame
** Purpose: Paints current surface to window
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::DrawFrame (void)
{
    HRESULT hResult = DD_OK;

// Check Initialization
    if (! isValid ())
    {
        // Error, not properly initialized
        return DDERR_GENERIC;
    }

    if (isPaused ())
    {
        // Don't draw, if paused
        return DD_OK;
    }

    // Double check parameters
    if ((! lpddsPrimary) || (! lpddsRender))
    {
        // Error, not properly initialized
        return DDERR_GENERIC;
    }

    // Get current src, dest rectangles
    CalcRects ();

    if (! checkPaint ())
    {
        // Nothing to draw, so return immediately
        return DD_OK;
    }

    // Paint until we truly succeed or error out
    while (TRUE)
    {
// Render D3D Scene
if (lpd3dScene)
hResult = lpd3dScene->Render ();

if (SUCCEEDED (hResult))
{
// Blt Rendering surface onto primary surface
hResult = lpddsPrimary->Blt (&rDrawDest,// Dest Rect
                     lpddsRender,   // Src Surface
                 &rDrawSrc,     // Src Rect
             DDBLT_WAIT,    // Wait
         NULL);         // Blt Special Effects (none)

// Did it work ?!?
if (SUCCEEDED (hResult))
{
// Success, exit
return hResult;
}
}

        // Check if busy or drawing
        if ((DDERR_SURFACEBUSY == hResult) ||
            (DDERR_WASSTILLDRAWING == hResult))
        {
            // Try again
            continue;
        }

        // Check for lost surfaces
        while (DDERR_SURFACELOST == hResult)
        {
            // Restore surfaces
            hResult = Restore ();
        }

        // Check for real error
        if (FAILED (hResult))
        {
            // Error,
            REPORTERR (hResult);
            return hResult;
        }
    }

    // Success
    return DD_OK;
} // End D3DWindow::DrawFrame

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Move
** Purpose: Move window to new coordinates
** Notes:   This used to be simpler. (See CalcRects)
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Move (long x, long y)
{
    // Need to recalculate src, dest rectangles
    turnCalcRectsOn ();

    // Success
    return DD_OK;
} // D3DWindow::Move

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Resize
** Purpose: Resizes the Window
** Notes:
**We need to resize the render surface to stay in sync
**with the new window size
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Resize (DWORD dwWidth, DWORD dwHeight)
{
HRESULThResult;
RECTrSurfOld;

    // Need to recalculate src, dest rectangles
    turnCalcRectsOn ();

// Save old Surface coordinates
rSurfOld = rSurf;

// Update the surface rectangle
rSurf.left   = 0;
rSurf.top    = 0;
rSurf.right  = dwWidth;
rSurf.bottom = dwHeight;

// Check if properly initialized
if (! isValid ())
{
// Try to re-initialize
Fini ();
hResult = Init ();
if (FAILED (hResult))
return hResult;

// Successful re-initialization
return DD_OK;
}

    // Cleanup Render Surface (and D3D Device)
    FiniRender ();

// Re-create Render Surface (at new size)
hResult = InitRender ();
if (FAILED (hResult))
{
if ((hResult == DDERR_OUTOFMEMORY) ||
(hResult == DDERR_OUTOFVIDEOMEMORY))
{
// Try to restore old surface size
rSurf = rSurfOld;

FiniRender ();
hResult = InitRender ();
if (FAILED (hResult))
return hResult;

// Successfully returned to previous size
return DD_OK;
}

return hResult;
}

// Success
return DD_OK;
} // End D3DWindow::Resize

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::RealizePalette
**  Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::RealizePalette (void)
{
HRESULT hResult;

//
    // Realizing the palette using DirectDraw is quite different
    // from GDI. To realize the palette we call SetPalette()
    // each time our application is activated.
    //
    // NOTE: DirectDraw spots the fact that the new palette is the
    // same as the old one and so does not increase the reference
    // count of the palette.
    //

if ((lpddsPrimary) && (lpddpPalette))
{
hResult = lpddsPrimary->SetPalette (lpddpPalette);
if (FAILED (hResult))
        {
            REPORTERR (hResult);
            return hResult;
        }
}

// Success
return DD_OK;
} // End D3DWindow::RealizePalette

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Pause
** Purpose: Pause any work on DD/D3D resources
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Pause (BOOL fPause)
{
    // Turning pausing on/off ?!?
    if (fPause)
    {
        if (dwPaused == 0L)
        {
// Very first time paused, do anything special here...

// Force screen redraw
if (hWindow)
InvalidateRect (hWindow, NULL, FALSE);
        }
        dwPaused++;
    }
    else
    {
        if (dwPaused == 0L)
        {
            // Programmer Error, already paused
REPORTERR (DDERR_GENERIC);
            return DDERR_GENERIC;
        }

        // Decrement semaphore
        dwPaused--;
        if (dwPaused == 0L)
        {
// Very last time unpaused, do anything special here...

// Force screen redraw
if (hWindow)
InvalidateRect (hWindow, NULL, FALSE);
        }
    }

    // Success
    return DD_OK;
} // D3DWindow::Pause

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::AttachScene
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::AttachScene (LPD3DScene lpNewScene)
{
// Check parameters
if (! lpNewScene)
{
return DDERR_INVALIDPARAMS;
}

DetachScene ();

// Save Scene pointer
lpd3dScene = lpNewScene;

// Initialize Scene ?!?
if (isValid ())
{
lpd3dScene->Init (this);
}

// Success
return DD_OK;
} // End D3DWindow::AttachScene

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::DetachScene
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::DetachScene (void)
{
// Cleanup Scene
if (lpd3dScene)
{
lpd3dScene->Fini ();
lpd3dScene = NULL;
}

// Success
return DD_OK;
} // End D3DWindow::DetachScene

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::CalcRects
** Purpose: Recalculate src,dest rectangles
**
** Notes:Without this protection we get a GPF when we drag the window
**off the edge of the screen.  This is because we are trying to
**write pixels to a non-existent location.   The clipper object
**attached to the primary protects the primary surface from
**stray pixels outside of the window's client area.  However, it
**doesn't stop us from trying to write outside the primary.  So
**we need to protect ourselves.
**
** Basic Algorithm:
**
**     - Get Client rectangle
**     - Get Render Surface rectangle
**     - Intersect client with surface to get initial src rect
**
**     - Get Primary surface rectangle
**     - Map Src rect to primary surface coordinates
**
**     - Intersect primary with src to get dest rect.
**     - Map dest rect back into surface coordinates to get final src rect.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::CalcRects (void)
{
    // Do we need to recalculate the rectangles
// I.E. Can we avoid doing work
    if (checkCalcRects ())
    {
        HRESULT       hResult;
        DDSURFACEDESC ddsd;         // Surface desc.
        POINT         pnt;
        RECT          rClient;
        RECT          rSrc;
        RECT          rDest;
        RECT          rTemp;

        if (! isValid ())
        {
            // Error, not properly initialized
            return DDERR_GENERIC;
        }

        // Assume we are going to paint
        turnPaintOn ();

        // Get Client rectangle pos/size
        GetClientRect (hWindow, &rClient);

        // Get Surface rectangle size
        ddsd.dwSize = sizeof (DDSURFACEDESC);
        hResult = lpddsRender->GetSurfaceDesc (&ddsd);
        if (FAILED (hResult))
        {
            // Error
            REPORTERR (hResult);
            return hResult;
        }
        rSurf.right     = rSurf.left + ddsd.dwWidth;
        rSurf.bottom    = rSurf.top + ddsd.dwHeight;

        // Intersect client with surface to get initial Src Rect
        if (! IntersectRect (&rSrc, &rClient, &rSurf))
        {
            // No intersection, means nothing to draw
            turnPaintOff ();
            rSrc.left   = 0;
            rSrc.top    = 0;
            rSrc.right  = 0;
            rSrc.bottom = 0;
        }

        // Get Primary Rectangle
        ZeroMemory (&ddsd, sizeof(ddsd));
        ddsd.dwSize = sizeof (DDSURFACEDESC);
        hResult = lpddsPrimary->GetSurfaceDesc (&ddsd);
        if (FAILED (hResult))
        {
            // Error
            return hResult;
        }

        rPrim.left      = 0;
        rPrim.top       = 0;
        rPrim.right     = rPrim.left + ddsd.dwWidth;
        rPrim.bottom    = rPrim.top + ddsd.dwHeight;

        // Map src rectangle in surface coordinates to
// primary surface coordinates
        pnt.x = rSrc.left;
        pnt.y = rSrc.top;
        ClientToScreen (hWindow, &pnt);

        rTemp.left   = pnt.x;
        rTemp.top    = pnt.y;
        rTemp.right  = rTemp.left + (rSrc.right - rSrc.left);
        rTemp.bottom = rTemp.top + (rSrc.bottom - rSrc.top);

        // Intersect temp with primary to get dest Rect
        if (! IntersectRect (&rDest, &rPrim, &rTemp))
        {
            // No intersection, means nothing to draw
            turnPaintOff ();
            rDest.left   = 0;
            rDest.top    = 0;
            rDest.right  = 0;
            rDest.bottom = 0;
        }

        // Now we have to map the dest rectangle back into
        // surface space to to get the final src rectangle
        rSrc.left   = rSrc.left + (rDest.left - pnt.x);
        rSrc.top    = rSrc.top + (rDest.top - pnt.y);
        rSrc.right  = rSrc.left + (rDest.right - rDest.left);
        rSrc.bottom = rSrc.top + (rDest.bottom - rDest.top);

        // Save src and dest rectangles
        rDrawDest.left   = rDest.left;
        rDrawDest.top    = rDest.top;
        rDrawDest.right  = rDest.right;
        rDrawDest.bottom = rDest.bottom;

        rDrawSrc.left   = rSrc.left;
        rDrawSrc.top    = rSrc.top;
        rDrawSrc.right  = rSrc.right;
        rDrawSrc.bottom = rSrc.bottom;

        // No longer need to calculate rects
        turnCalcRectsOff ();
    }

    // Success
    return DD_OK;
} // End D3DWindow::CalcRects

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::Restore
** Purpose: Restores lost surfaces
** Note:    Eventually we should inform the user somehow that
**          they need to redraw the surface but for now punt
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::Restore (void)
{
    HRESULT hResult;

// Check Initialization
    if (! isValid ())
        return DDERR_GENERIC;

    // Restore Primary Surface
    if (lpddsPrimary)
    {
        hResult = lpddsPrimary->Restore ();
        if (FAILED (hResult))
            return hResult;
    }

    // Restore Z Buffer
    if (lpddsZBuff)
    {
        hResult = lpddsZBuff->Restore ();
        if (FAILED (hResult))
            return hResult;
    }

    // Restore Rendering surface
    if (lpddsRender)
    {
        hResult = lpddsRender->Restore ();
        if (FAILED (hResult))
            return hResult;
    }

// Allow D3D Scene to restore any surfaces
if (lpd3dScene)
{
hResult = lpd3dScene->Restore ();
if (FAILED (hResult))
return hResult;
}

    // Success
    return DD_OK;
} // End D3DWindow::Restore

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetSurfaceRect
** Purpose: Get bounding rectangle of surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetSurfaceRect (RECT & rSurface)
{
    HRESULT hResult;

    if (! isValid ())
    {
        // Error
        return DDERR_GENERIC;
    }

    // Force update of rectangle coordinates
    hResult = CalcRects ();
    if (FAILED (hResult))
        return hResult;

    // Return Surface rectangle
    rSurface = rSurf;

    // Success
    return DD_OK;
} // D3DWindow::GetSurfaceRect

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetPrimaryRect
** Purpose: Get bounding rectangle of primary surface
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetPrimaryRect (RECT & rPrimary)
{
    HRESULT hResult;

    if (! isValid ())
    {
        // Error
        return DDERR_GENERIC;
    }

    // Force update of rectangle coordinates
    hResult = CalcRects ();
    if (FAILED (hResult))
    {
        // Error
        REPORTERR (hResult);
        return hResult;
    }

    // Return Primary rectangle
    rPrimary = rPrim;

    // Success
    return DD_OK;
} // GetPrimaryRect

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetSrcRect
** Purpose: Get current Src Rectangle
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetSrcRect (RECT & rSrc)
{
    HRESULT hResult;

    if (! isValid ())
    {
        // Error
        return DDERR_GENERIC;
    }

    // Force update of rectangle coordinates
    hResult = CalcRects ();
    if (FAILED (hResult))
        return hResult;

    // Return Surface rectangle
    rSrc = rDrawSrc;

    // Success
    return DD_OK;
} // D3DWindow::GetSrcRect

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::GetDestRect
** Purpose: Get current Dest Rectangle
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::GetDestRect (RECT & rDest)
{
    HRESULT hResult;

    if (! isValid ())
    {
        // Error
        return DDERR_GENERIC;
    }

    // Force update of rectangle coordinates
    hResult = CalcRects ();
    if (FAILED (hResult))
        return hResult;

    // Return Surface rectangle
    rDest = rDrawDest;

    // Success
    return DD_OK;
} // D3DWindow::GetDestRect

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::ChangeDesktop
**  Purpose:    The Primary Desktop has changed Modes, so we need
**to update our current mode to stay in sync
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDesktop (void)
{
HRESULT hResult;
LPDDDrvInfo  lpDrvOld;
LPDDModeInfo lpModeOld, lpModeNew;
LPD3DDevInfo lpDevOld, lpDevNew;
LPGUID lpGuidD3D;

// Check Initialization
if ((! hWindow) || (! IsWindow (hWindow)))
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

lpDrvOld  = lpCurrDriver;
lpModeOld = lpCurrMode;
lpDevOld  = lpCurrDevice;

if (! lpDrvOld)
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

//
// Step 1. Get New Mode (and new device)
//

// Get D3D Guid
if (lpDevOld)
lpGuidD3D = &(lpDevOld->guid);
else
lpGuidD3D = NULL;

// Get New Mode and Device corresponding to current desktop
if (! GetDesktopMode (lpDrvOld, lpGuidD3D, &lpModeNew, &lpDevNew))
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Do they match
if (lpModeOld == lpModeNew)
{
// Nothing to do
return DD_OK;
}

//
// Step 2.Destroy current Mode
//
FiniRender ();
FiniPrimary ();
//  FiniMode ();// Don't do this => unnecessary mode switch

//
// Step 3.  Create new mode
//
lpCurrMode = lpModeNew;
if (lpDevNew)
lpCurrDevice = lpDevNew;

// Create Primary Surface
hResult = InitPrimary ();
if (FAILED (hResult))
{
// Error, no point in trying to restore old defaults
// they are out of sync with the current desktop
return hResult;
}

// Create Render surface
hResult = InitRender ();
if (FAILED (hResult))
{
// Error, no point in trying to restore old defaults
// they are out of sync with the current desktop
return hResult;
}

// Notify the window of a successful change in Mode
SendMessage (hWindow, D3DWIN_CHANGED_MODE, 0, 0);

// Success
    return DD_OK;
} // D3DInfo::ChangeDesktop

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ChangeDriver
** Purpose:
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDriver (LPGUID lpGuidDD, LPD3DDevInfo lpDevHint)
{
HRESULT hResult;
    LPGUID       lpGuidD3D;
LPDDDrvInfo  lpDrvNew,  lpDrvOld;
LPDDModeInfo lpModeNew, lpModeOld;
LPD3DDevInfo lpDevNew, lpDevOld;

// Get New Driver
lpDrvNew = ValidateDriver (lpGuidDD);
if (! lpDrvNew)
{
return DDERR_GENERIC;
}

// Are we already using the requested driver
if ((isValid ()) && (lpDrvNew == lpCurrDriver))
return DD_OK;

// Get reasonable defaults for Mode and D3D device
if (lpDevHint)
lpGuidD3D = &(lpDevHint->guid);
else if (lpCurrDevice)
lpGuidD3D = &(lpCurrDevice->guid);
else
lpGuidD3D = NULL;

if (! GetDesktopMode (lpDrvNew, lpGuidD3D, &lpModeNew, &lpDevNew))
{
return DDERR_GENERIC;
}

// Save old defaults
lpDrvOld= lpCurrDriver;
lpModeOld= lpCurrMode;
lpDevOld= lpCurrDevice;

// Destroy almost everything
Fini ();

// Set new defaults
lpCurrDriver = lpDrvNew;
lpCurrMode   = lpModeNew;
lpCurrDevice = lpDevNew;

// Re-create almost everything based on new driver
hResult = Init ();
if (FAILED (hResult))
{
// Try to restore old defaults
Fini ();

lpCurrDriver = lpDrvOld;
lpCurrMode   = lpModeOld;
lpCurrDevice = lpDevOld;

Init ();
return hResult;
}

// Notify the window of a successful change in Driver
SendMessage (hWindow, D3DWIN_CHANGED_DRIVER, 0, 0);

// Success
    return DD_OK;
} // End D3DWindow::ChangeDriver

/*
**-----------------------------------------------------------------------------
** Name:    D3DWindow::ChangeDevice
** Purpose: Change to a new D3D device (RAMP, RGB, Hardware, etc.)
** Notes:
**
**  Algorithm:
**- Destroy the current D3D Device (and associated surfaces)
**- Recreate a new D3D device from the new GUID
**
**  1.The new D3D Device may not be supported by the current DD Device.
**
**  2.  The new D3D Device may not be compatible with the current Mode
**- If we are windowed, then punt (we shouldn't change the desktop)
**- If we are full-screen then pick a new mode that is compatible
**- This means destroy the current mode and recreate it.
**
**-----------------------------------------------------------------------------
*/

HRESULT D3DWindow::ChangeDevice (LPGUID lpD3DGuid)
{
    HRESULT         hResult;
LPDDDrvInfolpDrvOld;
LPDDModeInfo    lpModeOld;
LPD3DDevInfolpDevNew, lpDevOld;

// Check Parameters
if (! lpD3DGuid)
{
REPORTERR (DDERR_INVALIDPARAMS);
        return DDERR_INVALIDPARAMS;

}

// Check Initialization
    if (! isValid () || (! lpddsRender))
{
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
}

// Save Original State
lpDrvOld= lpCurrDriver;
lpModeOld   = lpCurrMode;
lpDevOld= lpCurrDevice;

// Verify new D3D device belongs to current DD driver
lpDevNew = lpDrvOld->FindDevice (lpD3DGuid, NULL);
if (! lpDevNew)
{
REPORTERR (DDERR_INVALIDPARAMS);
        return DDERR_INVALIDPARAMS;
}

//
//Step 1. Verify new D3D device is supported with current mode
//
if (! lpModeOld->ModeSupported (lpDevNew))
{
// We can't change the mode
// as we shouldn't be changing the desktop
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

//
//Step 2.  Destroy Old D3D Device
//
FiniRender ();

//
//Step 3. Create new D3D Device
//

// Set new D3D device (and mode)
lpCurrDevice = lpDevNew;

// Create new D3D Device
hResult = InitRender ();
if (FAILED (hResult))
{
// Try to restore original device
lpCurrDevice = lpDevOld;
InitRender ();

// Return Error
REPORTERR (hResult);
return hResult;
}

// Notify the window of a successful change in device
SendMessage (hWindow, D3DWIN_CHANGED_DEVICE, 0, 0);

    // Success
    return DD_OK;
} // End D3DWindow::ChangeDevice

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


D3DWIN.H

#ifndef D3DWIN_H
#define D3DWIN_H
/*
**-----------------------------------------------------------------------------
**  Name:       D3DWin.h
**  Purpose:    Sample D3D framework
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Includes
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "DrvMgr.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

// Windows messages
#define D3DWIN_GET_VALID    (WM_USER+1000)
#define D3DWIN_GET_POINTER  (WM_USER+1001)
#define D3DWIN_GET_SURFACE  (WM_USER+1002)

// Notification messages
#define D3DWIN_INIT  (WM_USER+2000)
#define D3DWIN_FINI  (WM_USER+2001)
#define D3DWIN_CHANGED_DRIVER  (WM_USER+2002)
#define D3DWIN_CHANGED_MODE  (WM_USER+2003)
#define D3DWIN_CHANGED_DEVICE  (WM_USER+2004)

// Minimum surface size
#define D3DWIN_MIN_SIZE     64
#define D3DWIN_DEF_SIZE   256

/*
**-----------------------------------------------------------------------------
**  Typedefs
**-----------------------------------------------------------------------------
*/

class D3DScene;// Prevent having to include D3DScene.h
typedef D3DScene * LPD3DScene;

class D3DWindow;                    // Forward declaration
typedef D3DWindow * LPD3DWindow;

/*
**-----------------------------------------------------------------------------
**  Classes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow
**  Purpose:    Encapsulates D3D rendering info for a window
**-----------------------------------------------------------------------------
*/

class D3DWindow {
protected:
//
    // Flags
//
    enum {
        DWF_VISIBLE= 0x00000002,
        DWF_ZBUFFER= 0x00000004,
DWF_ACTIVE= 0x00000008,
    } Attributes;

    enum {
        DWF_VALID_INTERFACE = 0x00000001,
DWF_VALID_PRIMARY= 0x00000002,
DWF_VALID_RENDER    = 0x00000004,
DWF_VALID_VIEWPORT  = 0x00000008,
DWF_VALID_SCENE= 0x00000010,
        DWF_RECALC_RECTS    = 0x00000020,
        DWF_PAINT           = 0x00000040,

DWF_VALID    = 0x00000007, // DWF_VALID_INTERFACE | DWF_VALID_PRIMARY | DWF_VALID_RENDER
    } Checks;

public:

//
    // Creation Methods
//
    D3DWindow (void);
    ~D3DWindow (void);

HRESULT D3DWindow::Create (
HWND   hWnd,
LPGUID lpDDGuid= NULL,
DWORD  dwW= 0L,
DWORD  dwH= 0L,
DWORD  dwBPP= 0L,
DWORD  dwRefresh= 0L,
LPGUID lpGuidD3D= NULL,
BOOL   fUseZBuffer= TRUE,
LPRECT lprSurf= NULL);
    HRESULT Init (void);
HRESULT Fini (void);

//
    // Window Methods
    //
    HRESULT DrawFrame (void);

    HRESULT Move (long x, long y);
    HRESULT Resize (DWORD w, DWORD h);

HRESULT RealizePalette (void);

    BOOL    isPaused (void)             { return (dwPaused != 0); }
    HRESULT Pause (BOOL fOn);

BOOL    isActive (void){ return ((fAttribs & DWF_ACTIVE) ? TRUE : FALSE); }
    voidturnActiveOn (void){ fAttribs |= DWF_ACTIVE; }
    voidturnActiveOff (void){ fAttribs &= ~DWF_ACTIVE; }

    HRESULT Restore (void );

//
// Scene Methods
//
HRESULT   AttachScene (LPD3DScene lpNewScene);
HRESULT    DetachScene (void);

//
    // Driver Methods
    //
HRESULT ChangeDesktop (void);

    HRESULT ChangeDriver (LPGUIDlpGuidDD,
  LPD3DDevInfolpDevHint);
    HRESULT ChangeDevice (LPGUIDlpGuidD3D);

//
// Misc. Methods
//
BOOL isValid (void)         { return (((fChecks & DWF_VALID) == DWF_VALID) ? TRUE : FALSE); }

    BOOL isCreateZBuffer (void) { return (fAttribs & DWF_ZBUFFER); }
    void createZBufferOn (void) { fAttribs |= DWF_ZBUFFER; }
    void createZBufferOff (void){ fAttribs &= ~DWF_ZBUFFER; }

//
// Member methods
//
HWND  GetWindow (void){ return hWindow; }

LPDDDrvInfo  GetDriverInfo (void){ return lpCurrDriver; }
LPDDModeInfo  GetModeInfo (void){ return lpCurrMode; }
LPD3DDevInfo  GetDeviceInfo (void){ return lpCurrDevice; }

LPGUID  GetDDGuid (void){ return ((lpCurrDriver) ? lpCurrDriver->GetGuid () : NULL); }
BOOL  GetModeInfo (DWORD & w, DWORD & h,
   DWORD & bpp, DWORD & refresh)
{
if (! lpCurrMode)
return FALSE;

lpCurrMode->GetMode (w, h , bpp, refresh);
return TRUE;
}
LPGUID  GetD3DGuid (void){ return ((lpCurrDevice) ? &(lpCurrDevice->guid) : NULL); }

LPDIRECTDRAW  GetDD (void){ return lpDD; }
LPDIRECTDRAW2 GetDD2 (void){ return lpDD2; }
LPDIRECT3D2   GetD3D (void){ return lpD3D; }

LPDIRECTDRAWSURFACE GetPrimary (void){ return lpddsPrimary; }
LPDIRECTDRAWSURFACE GetFrontBuffer (void){ return lpddsPrimary; }
LPDIRECTDRAWSURFACE GetBackBuffer (void){ return lpddsRender; }
LPDIRECTDRAWPALETTEGetPalette (void){ return lpddpPalette; }

LPDIRECTDRAWSURFACE GetRender (void){ return lpddsRender; }
LPDIRECTDRAWSURFACE GetZBuffer (void){ return lpddsZBuff; }
LPDIRECT3DDEVICE2   GetD3DDevice (void){ return lpd3dDevice; }
LPDIRECT3DVIEWPORT2GetViewport (void){ return lpd3dViewport; }

HRESULT GetSurfaceRect (RECT & rSurface);
    HRESULT GetPrimaryRect (RECT & rPrimary);
HRESULT GetSrcRect (RECT & rSrc);
    HRESULT GetDestRect (RECT & rDest);

LPD3DScene GetScene (void){ return lpd3dScene; }

protected:

//
// Protected Methods
//

    HRESULT ValidateDefaults (void);

HRESULT CreateInterfaces (LPGUID lpGuidDD);
    HRESULT InitInterfaces (void);
    HRESULT FiniInterfaces (void);

HRESULT InitWindow (void);
HRESULT FiniWindow (void);

HRESULT InitPrimary (void);
    HRESULT FiniPrimary (void);

    HRESULT InitPalette (void);
    HRESULT FiniPalette (void);

HRESULT ValidateSize (void);

    HRESULT CreateRender (LPGUID lpD3DGuid, LPRECT lprSurf);
HRESULT InitRender (void);
    HRESULT FiniRender (void);

HRESULT InitViewport (void);
HRESULT FiniViewport (void);
HRESULT UpdateViewport (void);

    // Recalculate src,dest drawing rectangles
    BOOL checkCalcRects (void){ return (fChecks & DWF_RECALC_RECTS); }
    void turnCalcRectsOn (void) { fChecks |= DWF_RECALC_RECTS; }
    void turnCalcRectsOff(void) { fChecks &= ~DWF_RECALC_RECTS; }

    // Nothing to draw
    BOOL checkPaint (void){ return (fChecks & DWF_PAINT); }
    void turnPaintOn (void){ fChecks |= DWF_PAINT; }
    void turnPaintOff(void){ fChecks &= ~DWF_PAINT; }

// Track initialization
    void turnValidInterfaceOn (void){ fChecks |= DWF_VALID_INTERFACE; }
    void turnValidInterfaceOff(void){ fChecks &= ~DWF_VALID_INTERFACE; }

    void turnValidPrimaryOn (void){ fChecks |= DWF_VALID_PRIMARY; }
    void turnValidPrimaryOff (void){ fChecks &= ~DWF_VALID_PRIMARY; }

    void turnValidRenderOn (void){ fChecks |= DWF_VALID_RENDER; }
    void turnValidRenderOff(void){ fChecks &= ~DWF_VALID_RENDER; }

    void turnValidViewportOn (void){ fChecks |= DWF_VALID_VIEWPORT; }
    void turnValidViewportOff(void){ fChecks &= ~DWF_VALID_VIEWPORT; }

void turnValidSceneOn (void){ fChecks |= DWF_VALID_SCENE; }
void turnValidSceneOff (void){ fChecks &= ~DWF_VALID_SCENE;}

    // Flags
    BOOL isValidDefaults (void){ return ((lpCurrDriver && lpCurrMode && lpCurrDevice) ? TRUE : FALSE); }
    BOOL isValidInterface (void){ return ((fChecks & DWF_VALID_INTERFACE) ? TRUE : FALSE); }
    BOOL isValidPrimary (void)  { return ((fChecks & DWF_VALID_PRIMARY) ? TRUE : FALSE); }
    BOOL isValidRender (void)   { return ((fChecks & DWF_VALID_RENDER) ? TRUE : FALSE); }
BOOL isValidViewport (void) { return ((fChecks & DWF_VALID_VIEWPORT) ? TRUE : FALSE); }
BOOL isValidScene (void){ return ((fChecks & DWF_VALID_SCENE) ? TRUE : FALSE); }

    HRESULT CalcRects (void);

//
// Data members
//
    DWORD           dwSize;             // Structure Size
    DWORD           fAttribs;           // Attribute Flags
    DWORD           fChecks;            // Programming flags

    // Window Data
HWND            hWindow;            // Window handle
    DWORD           dwPaused;           // Paused Semaphore

    // Driver Defaults
LPDDDrvInfolpCurrDriver;// Current Driver
LPDDModeInfolpCurrMode;// Current Mode
LPD3DDevInfolpCurrDevice;// Current Device

// Interfaces
    LPDIRECTDRAW    lpDD;               // DirectDraw Interface
    LPDIRECTDRAW2   lpDD2;              // DirectDraw2 Interface
    LPDIRECT3D2     lpD3D;              // Direct3D2 Interface

    // Primary Surface
    LPDIRECTDRAWSURFACE  lpddsPrimary;  // Primary Surface
    LPDIRECTDRAWPALETTE  lpddpPalette;  // Primary Palette
    PALETTEENTRY *       lppePalette;   // Saved System palette entries

    // Render Surface
    LPDIRECTDRAWSURFACE  lpddsRender;   // Rendering surface
    LPDIRECTDRAWSURFACE  lpddsZBuff;    // Z-Buffer surface
    LPDIRECT3DDEVICE2    lpd3dDevice;// D3D Device
LPDIRECT3DVIEWPORT2  lpd3dViewport;// D3D Viewport

    // Misc.
    RECT                rPrim;          // Current primary pos,size
    RECT                rSurf;          // Current surface pos,size

    RECT                rDrawDest;// Destination rectangle
    RECT                rDrawSrc;// Source Rectangle

// D3D Scene
LPD3DScenelpd3dScene;// D3D Scene

// Friends
friendclass D3DScene;

}; // End D3DWindow

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // D3DWIN_H


DEBUG.CPP

/*
**-----------------------------------------------------------------------------
** Name:    Debug.cpp
** Purpose: Example debug code for D3D sample
** Notes:
**
** Copyright (C) 199 - 1997 Microsoft Corporation. All Rights Reserved.
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Includes
**-----------------------------------------------------------------------------
*/

#include "Debug.h"
#include "WinMain.h"
#include "WinProc.h"

/*
**-----------------------------------------------------------------------------
** Global variables
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
    DWORD   g_dwDebugLevel = DEBUG_ALWAYS;
    BOOL    g_fDebug = TRUE;
#else
    BOOL    g_fDebug = FALSE;
#endif

/*
**-----------------------------------------------------------------------------
** Local prototypes
**-----------------------------------------------------------------------------
*/

BOOL GetDDErrorString (HRESULT hResult, LPTSTR lpszError, DWORD cchError);

/*
**-----------------------------------------------------------------------------
** Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       dprintf
**  Purpose:Printf to debug output
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
void __cdecl dprintf (DWORD dwDebugLevel, LPCTSTR szFormat, ...)
{
    TCHAR   szBuffer[MAX_STRING];
    va_list va;

// Check if current debug level
    if (dwDebugLevel <= g_dwDebugLevel)
    return;

    lstrcpy (szBuffer, START_STR);
    va_start( va, szFormat );
    wvsprintf (szBuffer+lstrlen (szBuffer), szFormat, va);
    va_end( va );
    lstrcat (szBuffer, END_STR);
    OutputDebugString (szBuffer);
}
#endif // DEBUG

/*
**-----------------------------------------------------------------------------
** Name:    ReportDDError
** Purpose:
**-----------------------------------------------------------------------------
*/

void _cdecl ReportDDError (HRESULT hResult, LPCTSTR szFormat, ...)
{
    TCHAR   szMsg[MAX_STRING];
    TCHAR   szErr[MAX_STRING];
    TCHAR   szUnknown[] = TEXT("Unknown");
    DWORD   cchErr = sizeof(szErr)/sizeof(TCHAR);
    va_list va;

    // Check for Success Error code
    if (hResult == DD_OK)
        return;

    // Get DD/D3D error string
    szErr[0] = 0;
    GetDDErrorString (hResult, szErr, cchErr);
    wsprintf (szMsg, TEXT("DD/D3D Error = %s\r\n"), szErr);

    va_start( va, szFormat );
    // Append the rest of the error message
    wvsprintf (szMsg + lstrlen(szMsg), szFormat, va);
    va_end( va );

    // Dump to debugger
    DPF (DEBUG_ERROR, TEXT("%s"), szMsg);

//
// Define the following if you want an in your face Messagebox on errors
//
#ifdef DEBUG_PROMPT_ME
    HWND    hWindow;

    // Pause App
if (g_hMainWindow)
OnPause (g_hMainWindow, TRUE);

    // Display Error Message to user
    if (g_hMainWindow)
        hWindow = g_hMainWindow;
    else
        hWindow = NULL;

    MessageBox (hWindow, szMsg, g_szMainTitle, MB_OK | MB_APPLMODAL);

    // Unpause app
if (g_hMainWindow)
OnPause (g_hMainWindow, FALSE);
#endif // DEBUG_PROMPT_ME

} // ReportDDError

/*
**-----------------------------------------------------------------------------
** Name:    GetDDErrorString
** Purpose: outputs a debug string to debugger
**-----------------------------------------------------------------------------
*/

BOOL GetDDErrorString (HRESULT hResult, LPTSTR lpszErrorBuff, DWORD cchError)
{
    DWORD  cLen;
    LPTSTR lpszError;
    TCHAR  szMsg[MAX_STRING];

    // Check parameters
    if (!lpszErrorBuff || !cchError)
    {
        // Error, invalid parameters
        return FALSE;
    }

    switch (hResult)
    {
    case DD_OK:
        // The request completed successfully.
        lpszError = TEXT("DD_OK");
        break;

    case DDERR_ALREADYINITIALIZED:
        // The object has already been initialized.
        lpszError = TEXT("DDERR_ALREADYINITIALIZED");
        break;

    case DDERR_BLTFASTCANTCLIP:
        // A DirectDrawClipper object is attached to a source surface
        // that has passed into a call to the IDirectDrawSurface2::BltFast method.
        lpszError = TEXT("DDERR_BLTFASTCANTCLIP");
        break;

    case DDERR_CANNOTATTACHSURFACE:
        // A surface cannot be attached to another requested surface.
        lpszError = TEXT("DDERR_CANNOTATTACHSURFACE");
        break;

    case DDERR_CANNOTDETACHSURFACE:
        // A surface cannot be detached from another requested surface.
        lpszError = TEXT("DDERR_CANNOTDETACHSURFACE");
        break;

    case DDERR_CANTCREATEDC:
        // Windows cannot create any more device contexts (DCs).
        lpszError = TEXT("DDERR_CANTCREATEDC");
        break;

    case DDERR_CANTDUPLICATE:
        // Primary and 3D surfaces, or surfaces that are
        // implicitly created, cannot be duplicated.
        lpszError = TEXT("DDERR_CANTDUPLICATE");
        break;

    case DDERR_CANTLOCKSURFACE:
        // Access to this surface is refused because an
        // attempt was made to lock the primary surface without DCI support.
        lpszError = TEXT("DDERR_CANTLOCKSURFACE");
        break;

    case DDERR_CANTPAGELOCK:
        // An attempt to page lock a surface failed.
        // Page lock will not work on a display-memory
        // surface or an emulated primary surface.
        lpszError = TEXT("DDERR_CANTPAGELOCK");
        break;

    case DDERR_CANTPAGEUNLOCK:
        // An attempt to page unlock a surface failed.
        // Page unlock will not work on a display-memory
        // surface or an emulated primary surface.
        lpszError = TEXT("DDERR_CANTPAGEUNLOCK");
        break;

    case DDERR_CLIPPERISUSINGHWND:
        // An attempt was made to set a clip list for a DirectDrawClipper
        // object that is already monitoring a window handle.
        lpszError = TEXT("DDERR_CLIPPERISUSINGHWND");
        break;

    case DDERR_COLORKEYNOTSET:
        // No source color key is specified for this operation
        lpszError = TEXT("DDERR_COLORKEYNOTSET");
        break;

    case DDERR_CURRENTLYNOTAVAIL:
        // No support is currently available.
        lpszError = TEXT("DDERR_CURRENTLYNOTAVAIL");
        break;

    case DDERR_DCALREADYCREATED:
        // A device context (DC) has already been returned for this surface.
        // Only one DC can be retrieved for each surface.
        lpszError = TEXT("DDERR_DCALREADYCREATED");
        break;

    case DDERR_DIRECTDRAWALREADYCREATED:
        // A DirectDraw object representing this driver
        // has already been created for this process.
        lpszError = TEXT("DDERR_DIRECTDRAWALREADYCREATED");
        break;

    case DDERR_EXCEPTION:
        // An exception was encountered while
        // performing the requested operation.
        lpszError = TEXT("DDERR_EXCEPTION");
        break;

    case DDERR_EXCLUSIVEMODEALREADYSET:
        // An attempt was made to set the cooperative
        // level when it was already set to exclusive.
        lpszError = TEXT("DDERR_EXCLUSIVEMODEALREADYSET");
        break;

    case DDERR_GENERIC:
        // There is an undefined error condition.
        lpszError = TEXT("DDERR_GENERIC");
        break;

    case DDERR_HEIGHTALIGN:
        // The height of the provided rectangle
        // is not a multiple of the required alignment.
        lpszError = TEXT("DDERR_HEIGHTALIGN");
        break;

    case DDERR_HWNDALREADYSET:
        // The DirectDraw cooperative level window
        // handle has already been set. It cannot
        // be reset while the process has surfaces or palettes created.
        lpszError = TEXT("DDERR_HWNDALREADYSET");
        break;

    case DDERR_HWNDSUBCLASSED:
        // DirectDraw is prevented from restoring state because the
        // DirectDraw cooperative level window handle has been subclassed.
        lpszError = TEXT("DDERR_HWNDSUBCLASSED");
        break;

    case DDERR_IMPLICITLYCREATED:
        // The surface cannot be restored because
        // it is an implicitly created surface.
        lpszError = TEXT("DDERR_IMPLICITLYCREATED");
        break;

    case DDERR_INCOMPATIBLEPRIMARY:
        // The primary surface creation request
        // does not match with the existing primary surface.
        lpszError = TEXT("DDERR_INCOMPATIBLEPRIMARY");
        break;

    case DDERR_INVALIDCAPS:
        // One or more of the capability bits
        // passed to the callback function are incorrect.
        lpszError = TEXT("DDERR_INVALIDCAPS");
        break;

    case DDERR_INVALIDCLIPLIST:
        // DirectDraw does not support the provided clip list.
        lpszError = TEXT("DDERR_INVALIDCLIPLIST");
        break;

    case DDERR_INVALIDDIRECTDRAWGUID:
        // The globally unique identifier (GUID) passed to the
        // DirectDrawCreate function is not a valid DirectDraw driver identifier.
        lpszError = TEXT("DDERR_INVALIDDIRECTDRAWGUID");
        break;

    case DDERR_INVALIDMODE:
        // DirectDraw does not support the requested mode.
        lpszError = TEXT("DDERR_INVALIDMODE");
        break;

    case DDERR_INVALIDOBJECT:
        // DirectDraw received a pointer that was an invalid DirectDraw object.
        lpszError = TEXT("DDERR_INVALIDOBJECT");
        break;

    case DDERR_INVALIDPARAMS:
        // One or more of the parameters passed to the method are incorrect.
        lpszError = TEXT("DDERR_INVALIDPARAMS");
        break;

    case DDERR_INVALIDPIXELFORMAT:
        // The pixel format was invalid as specified.
        lpszError = TEXT("DDERR_INVALIDPIXELFORMAT");
        break;

    case DDERR_INVALIDPOSITION:
        // The position of the overlay on the destination is no longer legal.
        lpszError = TEXT("DDERR_INVALIDPOSITION");
        break;

    case DDERR_INVALIDRECT:
        // The provided rectangle was invalid.
        lpszError = TEXT("DDERR_INVALIDRECT");
        break;

    case DDERR_INVALIDSURFACETYPE:
        // The requested operation could not be performed
        // because the surface was of the wrong type.
        lpszError = TEXT("DDERR_INVALIDSURFACETYPE");
        break;

    case DDERR_LOCKEDSURFACES:
        // One or more surfaces are locked,
        // causing the failure of the requested operation.
        lpszError = TEXT("DDERR_LOCKEDSURFACES");
        break;

    case DDERR_MOREDATA:
        // There is more data available than the specified
        // buffer size could hold.
        lpszError = TEXT("DDERR_MOREDATA");
        break;

    case DDERR_NO3D:
        // No 3D hardware or emulation is present.
        lpszError = TEXT("DDERR_NO3D");
        break;

    case DDERR_NOALPHAHW:
        // No alpha acceleration hardware is present or available,
        // causing the failure of the requested operation.
        lpszError = TEXT("DDERR_NOALPHAHW");
        break;

    case DDERR_NOBLTHW:
        // No blitter hardware is present.
        lpszError = TEXT("DDERR_NOBLTHW");
        break;

    case DDERR_NOCLIPLIST:
        // No clip list is available.
        lpszError = TEXT("DDERR_NOCLIPLIST");
        break;

    case DDERR_NOCLIPPERATTACHED:
        // No DirectDrawClipper object is attached to the surface object.
        lpszError = TEXT("DDERR_NOCLIPPERATTACHED");
        break;

    case DDERR_NOCOLORCONVHW:
        // The operation cannot be carried out because
        // no color-conversion hardware is present or available.
        lpszError = TEXT("DDERR_NOCOLORCONVHW");
        break;

    case DDERR_NOCOLORKEY:
        // The surface does not currently have a color key.
        lpszError = TEXT("DDERR_NOCOLORKEY");
        break;

    case DDERR_NOCOLORKEYHW:
        // The operation cannot be carried out because there
        // is no hardware support for the destination color key.
        lpszError = TEXT("DDERR_NOCOLORKEYHW");
        break;

    case DDERR_NOCOOPERATIVELEVELSET:
        // A create function is called without the
        // IDirectDraw2::SetCooperativeLevel method being called.
        lpszError = TEXT("DDERR_NOCOOPERATIVELEVELSET");
        break;

    case DDERR_NODC:
        // No DC has ever been created for this surface.
        lpszError = TEXT("DDERR_NODC");
        break;

    case DDERR_NODDROPSHW:
        // No DirectDraw raster operation (ROP) hardware is available.
        lpszError = TEXT("DDERR_NODDROPSHW");
        break;

    case DDERR_NODIRECTDRAWHW:
        // Hardware-only DirectDraw object creation is not possible;
        // the driver does not support any hardware.
        lpszError = TEXT("DDERR_NODIRECTDRAWHW");
        break;

    case DDERR_NODIRECTDRAWSUPPORT:
        // DirectDraw support is not possible with the current display driver.
        lpszError = TEXT("DDERR_NODIRECTDRAWSUPPORT");
        break;

    case DDERR_NOEMULATION:
        // Software emulation is not available.
        lpszError = TEXT("DDERR_NOEMULATION");
        break;

    case DDERR_NOEXCLUSIVEMODE:
        // The operation requires the application to have
        // exclusive mode, but the application does not have exclusive mode.
        lpszError = TEXT("DDERR_NOEXCLUSIVEMODE");
        break;

    case DDERR_NOFLIPHW:
        // Flipping visible surfaces is not supported.
        lpszError = TEXT("DDERR_NOFLIPHW");
        break;

    case DDERR_NOGDI:
        // No GDI is present.
        lpszError = TEXT("DDERR_NOGDI");
        break;

    case DDERR_NOHWND:
        // Clipper notification requires a window handle,
        // or no window handle has been previously set
        // as the cooperative level window handle.
        lpszError = TEXT("DDERR_NOHWND");
        break;

    case DDERR_NOMIPMAPHW:
        // The operation cannot be carried out because no
        // mipmap texture mapping hardware is present or available.
        lpszError = TEXT("DDERR_NOMIPMAPHW");
        break;

    case DDERR_NOMIRRORHW:
        // The operation cannot be carried out because
        // no mirroring hardware is present or available.
        lpszError = TEXT("DDERR_NOMIRRORHW");
        break;

    case DDERR_NONONLOCALVIDMEM:
        // An attempt was made to allocate non-local video memory
        // from a device that does not support non-local video memory.
        lpszError = TEXT("DDERR_NONONLOCALVIDMEM");
        break;

    case DDERR_NOOVERLAYDEST:
        // The IDirectDrawSurface2::GetOverlayPosition method
        // is called on an overlay that the IDirectDrawSurface2::UpdateOverlay
        // method has not been called on to establish a destination.
        lpszError = TEXT("DDERR_NOOVERLAYDEST");
        break;

    case DDERR_NOOVERLAYHW:
        // The operation cannot be carried out because
        // no overlay hardware is present or available.
        lpszError = TEXT("DDERR_NOOVERLAYHW");
        break;

    case DDERR_NOPALETTEATTACHED:
        // No palette object is attached to this surface.
        lpszError = TEXT("DDERR_NOPALETTEATTACHED");
        break;

    case DDERR_NOPALETTEHW:
        // There is no hardware support for 16- or 256-color palettes.
        lpszError = TEXT("DDERR_NOPALETTEHW");
        break;

    case DDERR_NORASTEROPHW:
        // The operation cannot be carried out because
        // no appropriate raster operation hardware is present or available.
        lpszError = TEXT("DDERR_NORASTEROPHW");
        break;

    case DDERR_NOROTATIONHW:
        // The operation cannot be carried out because
        // no rotation hardware is present or available.
        lpszError = TEXT("DDERR_NOROTATIONHW");
        break;

    case DDERR_NOSTRETCHHW:
        // The operation cannot be carried out because
        // there is no hardware support for stretching.
        lpszError = TEXT("DDERR_NOSTRETCHHW");
        break;

    case DDERR_NOT4BITCOLOR:
        // The DirectDrawSurface object is not using a
        // 4-bit color palette and the requested operation
        // requires a 4-bit color palette.
        lpszError = TEXT("DDERR_NOT4BITCOLOR");
        break;

    case DDERR_NOT4BITCOLORINDEX:
        // The DirectDrawSurface object is not using a 4-bit
        // color index palette and the requested operation
        // requires a 4-bit color index palette.
        lpszError = TEXT("DDERR_NOT4BITCOLORINDEX");
        break;

    case DDERR_NOT8BITCOLOR:
        // The DirectDrawSurface object is not using an 8-bit
        // color palette and the requested operation requires
        // an 8-bit color palette.
        lpszError = TEXT("DDERR_NOT8BITCOLOR");
        break;

    case DDERR_NOTAOVERLAYSURFACE:
        // An overlay component is called for a non-overlay surface.
        lpszError = TEXT("DDERR_NOTAOVERLAYSURFACE");
        break;

    case DDERR_NOTEXTUREHW:
        // The operation cannot be carried out because no
        // texture-mapping hardware is present or available.
        lpszError = TEXT("DDERR_NOTEXTUREHW");
        break;

    case DDERR_NOTFLIPPABLE:
        // An attempt has been made to flip a surface that cannot be flipped.
        lpszError = TEXT("DDERR_NOTFLIPPABLE");
        break;

    case DDERR_NOTFOUND:
        // The requested item was not found.
        lpszError = TEXT("DDERR_NOTFOUND");
        break;

    case DDERR_NOTINITIALIZED:
        // An attempt was made to call an interface method of a DirectDraw object
        // created by CoCreateInstance before the object was initialized.
        lpszError = TEXT("DDERR_NOTINITIALIZED");
        break;

    case DDERR_NOTLOCKED:
        // An attempt is made to unlock a surface that was not locked.
        lpszError = TEXT("DDERR_NOTLOCKED");
        break;

    case DDERR_NOTPAGELOCKED:
        // An attempt is made to page unlock a surface
        // with no outstanding page locks.
        lpszError = TEXT("DDERR_NOTPAGELOCKED");
        break;

    case DDERR_NOTPALETTIZED:
        // The surface being used is not a palette-based surface.
        lpszError = TEXT("DDERR_NOTPALETTIZED");
        break;

    case DDERR_NOVSYNCHW:
        // The operation cannot be carried out because
        // there is no hardware support for vertical blank synchronized operations.
        lpszError = TEXT("DDERR_NOVSYNCHW");
        break;

    case DDERR_NOZBUFFERHW:
        // The operation to create a z-buffer in display memory
        // or to perform a blit using a z-buffer cannot be carried
        // out because there is no hardware support for z-buffers.
        lpszError = TEXT("DDERR_NOZBUFFERHW");
        break;

    case DDERR_NOZOVERLAYHW:
        // The overlay surfaces cannot be z-layered based
        // on the z-order because the hardware does not
        // support z-ordering of overlays.
        lpszError = TEXT("DDERR_NOZOVERLAYHW");
        break;

    case DDERR_OUTOFCAPS:
        // The hardware needed for the requested operation has already been allocated.
        lpszError = TEXT("DDERR_OUTOFCAPS");
        break;

    case DDERR_OUTOFMEMORY:
        // DirectDraw does not have enough memory to perform the operation.
        lpszError = TEXT("DDERR_OUTOFMEMORY");
        break;

    case DDERR_OUTOFVIDEOMEMORY:
        // DirectDraw does not have enough display memory to perform the operation.
        lpszError = TEXT("DDERR_OUTOFVIDEOMEMORY");
        break;

    case DDERR_OVERLAYCANTCLIP:
        // The hardware does not support clipped overlays.
        lpszError = TEXT("DDERR_OVERLAYCANTCLIP");
        break;

    case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
        // An attempt was made to have more than one color key active on an overlay.
        lpszError = TEXT("DDERR_OVERLAYCOLORKEYONLYONEACTIVE");
        break;

    case DDERR_OVERLAYNOTVISIBLE:
        // The IDirectDrawSurface2::GetOverlayPosition method is called on a hidden overlay.
        lpszError = TEXT("DDERR_OVERLAYNOTVISIBLE");
        break;

    case DDERR_PALETTEBUSY:
        // Access to this palette is refused
        // because the palette is locked by another thread.
        lpszError = TEXT("DDERR_PALETTEBUSY");
        break;

    case DDERR_PRIMARYSURFACEALREADYEXISTS:
        // This process has already created a primary surface.
        lpszError = TEXT("DDERR_PRIMARYSURFACEALREADYEXISTS");
        break;

    case DDERR_REGIONTOOSMALL:
        // The region passed to the
        // IDirectDrawClipper::GetClipList method is too small.
        lpszError = TEXT("DDERR_REGIONTOOSMALL");
        break;

    case DDERR_SURFACEALREADYATTACHED:
        // An attempt was made to attach a surface to
        // another surface to which it is already attached.
        lpszError = TEXT("DDERR_SURFACEALREADYATTACHED");
        break;

    case DDERR_SURFACEALREADYDEPENDENT:
        // An attempt was made to make a surface a dependency
        // of another surface to which it is already dependent.
        lpszError = TEXT("DDERR_SURFACEALREADYDEPENDENT");
        break;

    case DDERR_SURFACEBUSY:
        // Access to the surface is refused because the
        // surface is locked by another thread.
        lpszError = TEXT("DDERR_SURFACEBUSY");
        break;

    case DDERR_SURFACEISOBSCURED:
        // Access to the surface is refused
        // because the surface is obscured.
        lpszError = TEXT("DDERR_SURFACEISOBSCURED");
        break;

    case DDERR_SURFACELOST:
        // Access to the surface is refused because the
        // surface memory is gone. The DirectDrawSurface
        // object representing this surface should have
        // the IDirectDrawSurface2::Restore method called on it.
        lpszError = TEXT("DDERR_SURFACELOST");
        break;

    case DDERR_SURFACENOTATTACHED:
        // The requested surface is not attached.
        lpszError = TEXT("DDERR_SURFACENOTATTACHED");
        break;

    case DDERR_TOOBIGHEIGHT:
        // The height requested by DirectDraw is too large.
        lpszError = TEXT("DDERR_TOOBIGHEIGHT");
        break;

    case DDERR_TOOBIGSIZE:
        // The size requested by DirectDraw is too large.
        // However, the individual height and width are OK.
        lpszError = TEXT("DDERR_TOOBIGSIZE");
        break;

    case DDERR_TOOBIGWIDTH:
        // The width requested by DirectDraw is too large.
        lpszError = TEXT("DDERR_TOOBIGWIDTH");
        break;

    case DDERR_UNSUPPORTED:
        // The operation is not supported.
        lpszError = TEXT("DDERR_UNSUPPORTED");
        break;

    case DDERR_UNSUPPORTEDFORMAT:
        // The FourCC format requested is not supported by DirectDraw.
        lpszError = TEXT("DDERR_UNSUPPORTEDFORMAT");
        break;

    case DDERR_UNSUPPORTEDMASK:
        // The bitmask in the pixel format requested is not supported by DirectDraw.
        lpszError = TEXT("DDERR_UNSUPPORTEDMASK");
        break;

    case DDERR_UNSUPPORTEDMODE:
        // The display is currently in an unsupported mode.
        lpszError = TEXT("DDERR_UNSUPPORTEDMODE");
        break;

    case DDERR_VERTICALBLANKINPROGRESS:
        // A vertical blank is in progress.
        lpszError = TEXT("DDERR_VERTICALBLANKINPROGRESS");
        break;

    case DDERR_WASSTILLDRAWING:
        // The previous blit operation that is transferring
        // information to or from this surface is incomplete.
        lpszError = TEXT("DDERR_WASSTILLDRAWING");
        break;

    case DDERR_WRONGMODE:
        // This surface cannot be restored because it was created in a different mode.
        lpszError = TEXT("DDERR_WRONGMODE");
        break;

    case DDERR_XALIGN:
        // The provided rectangle was not horizontally aligned on a required boundary.
        lpszError = TEXT("DDERR_XALIGN");
        break;

    case DDERR_VIDEONOTACTIVE:
        // The video port is not active
        lpszError = TEXT("DDERR_VIDEONOTACTIVE");
        break;

        //
        // D3D Immediate Mode Errors
        //

    case D3DERR_BADMAJORVERSION:
        // ???
        lpszError = TEXT("D3DERR_BADMAJORVERSION");
        break;

    case D3DERR_BADMINORVERSION:
        // ???
        lpszError = TEXT("D3DERR_BADMINORVERSION");
        break;

    case D3DERR_INVALID_DEVICE:
        // ???
        lpszError = TEXT("D3DERR_INVALID_DEVICE");
        break;

    case D3DERR_INITFAILED:
        // ???
        lpszError = TEXT("D3DERR_INITFAILED");
        break;

    case D3DERR_EXECUTE_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_CREATE_FAILED");
        break;

    case D3DERR_EXECUTE_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_DESTROY_FAILED");
        break;

    case D3DERR_EXECUTE_LOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_LOCK_FAILED");
        break;

    case D3DERR_EXECUTE_UNLOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_UNLOCK_FAILED");
        break;

    case D3DERR_EXECUTE_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_LOCKED");
        break;

    case D3DERR_EXECUTE_NOT_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_NOT_LOCKED");
        break;

    case D3DERR_EXECUTE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_EXECUTE_FAILED");
        break;

    case D3DERR_EXECUTE_CLIPPED_FAILED:
        // ???
        lpszError = TEXT("D3DERR_CLIPPED_FAILED");
        break;

    case D3DERR_TEXTURE_NO_SUPPORT:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_NO_SUPPORT");
        break;

    case D3DERR_TEXTURE_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_CREATE_FAILED");
        break;

    case D3DERR_TEXTURE_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_DESTROY_FAILED");
        break;

    case D3DERR_TEXTURE_LOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_LOCK_FAILED");
        break;

    case D3DERR_TEXTURE_UNLOCK_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_UNLOCK_FAILED");
        break;

    case D3DERR_TEXTURE_LOAD_FAILED:
        // ???
        lpszError = TEXT("D3DERR_LOAD_FAILED");
        break;

    case D3DERR_TEXTURE_SWAP_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SWAP_FAILED");
        break;

    case D3DERR_TEXTURE_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_LOCKED");
        break;

    case D3DERR_TEXTURE_NOT_LOCKED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_NOT_LOCKED");
        break;

    case D3DERR_TEXTURE_GETSURF_FAILED:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_GETSURF_FAILED");
        break;

    case D3DERR_MATRIX_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_CREATE_FAILED");
        break;

    case D3DERR_MATRIX_DESTROY_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_DESTROY_FAILED");
        break;

    case D3DERR_MATRIX_SETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATRIX_SETDATA_FAILED");
        break;

    case D3DERR_MATRIX_GETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MAXTRIX_GETDATA_FAILED");
        break;

    case D3DERR_SETVIEWPORTDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SETVIEWPORTDATA_FAILED");
        break;

    case D3DERR_INVALIDCURRENTVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_INVALIDCURRENTVIEWPORT");
        break;

    case D3DERR_INVALIDPRIMITIVETYPE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDPRIMITIVETYPE");
        break;

    case D3DERR_INVALIDVERTEXTYPE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDVERTEXTYPE");
        break;

    case D3DERR_TEXTURE_BADSIZE:
        // ???
        lpszError = TEXT("D3DERR_TEXTURE_BADSIZE");
        break;

    case D3DERR_MATERIAL_CREATE_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_CREATE_FAILED");
        break;

    case D3DERR_MATERIAL_DESTROY_FAILED:
        // ???

lpszError = TEXT("D3DERR_MATERIAL_DESTROY_FAILED");
        break;

    case D3DERR_MATERIAL_SETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_SETDATA_FAILED");
        break;

    case D3DERR_MATERIAL_GETDATA_FAILED:
        // ???
        lpszError = TEXT("D3DERR_MATERIAL_GETDATA_FAILED");
        break;

    case D3DERR_INVALIDPALETTE:
        // ???
        lpszError = TEXT("D3DERR_INVALIDPALETTE");
        break;

    case D3DERR_ZBUFF_NEEDS_SYSTEMMEMORY:
        // ???
        lpszError = TEXT("D3DERR_ZBUFF_NEEDS_SYSTEMMEMORY");
        break;

    case D3DERR_ZBUFF_NEEDS_VIDEOMEMORY:
        // ???
        lpszError = TEXT("D3DERR_ZBUFF_NEEDS_VIDEOMEMORY");
        break;

    case D3DERR_SURFACENOTINVIDMEM:
        // ???
        lpszError = TEXT("D3DERR_SURFACENOTINVIDMEM");
        break;

    case D3DERR_LIGHT_SET_FAILED:
        // ???
        lpszError = TEXT("D3DERR_LIGHT_SET_FAILED");
        break;

    case D3DERR_LIGHTHASVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_LIGHTHASVIEWPORT");
        break;

    case D3DERR_LIGHTNOTINTHISVIEWPORT:
        // ???
        lpszError = TEXT("D3DERR_LIGHTNOTINTHISVIEWPORT");
        break;

    case D3DERR_SCENE_IN_SCENE:
        // ???
        lpszError = TEXT("D3DERR_SCENE_IN_SCENE");
        break;

    case D3DERR_SCENE_NOT_IN_SCENE:
        // ???
        lpszError = TEXT("D3DERR_SCENE_NOT_IN_SCENE");
        break;

    case D3DERR_SCENE_BEGIN_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SCENE_BEGIN_FAILED");
        break;

    case D3DERR_SCENE_END_FAILED:
        // ???
        lpszError = TEXT("D3DERR_SCENE_END_FAILED");
        break;

    case D3DERR_INBEGIN:
        // ???
        lpszError = TEXT("D3DERR_INBEGIN");
        break;

    case D3DERR_NOTINBEGIN:
        // ???
        lpszError = TEXT("D3DERR_NOTINBEGIN");
        break;

    case D3DERR_NOVIEWPORTS:
        // ???
        lpszError = TEXT("D3DERR_NOVIEWPORTS");
        break;

    case D3DERR_VIEWPORTDATANOTSET:
        // ???
        lpszError = TEXT("D3DERR_VIEWPORTDATANOTSET");
        break;

    case D3DERR_VIEWPORTHASNODEVICE:
        // ???
        lpszError = TEXT("D3DERR_VIEWPORTHASNODEVICE");
        break;

        //
        // D3D Retained Mode Errors
        //
#if 0
    case D3DRMERR_BADOBJECT:
        // ???
        lpszError = TEXT("D3DRMERR_BADOBJECT");
        break;

    case D3DRMERR_BADTYPE:
        // ???
        lpszError = TEXT("D3DRMERR_BADTYPE");
        break;

    case D3DRMERR_BADALLOC:
        // ???
        lpszError = TEXT("D3DRMERR_BADALLOC");
        break;

    case D3DRMERR_FACEUSED:
        // ???
        lpszError = TEXT("D3DRMERR_FACEUSED");
        break;

    case D3DRMERR_NOTFOUND:
        // ???
        lpszError = TEXT("D3DRMERR_NOTFOUND");
        break;

    case D3DRMERR_NOTDONEYET:
        // ???
        lpszError = TEXT("D3DRMERR_NOTDONEYET");
        break;

    case D3DRMERR_FILENOTFOUND:
        // ???
        lpszError = TEXT("D3DRMERR_FILENOTFOUND");
        break;

    case D3DRMERR_BADFILE:
        // ???
        lpszError = TEXT("D3DRMERR_BADFILE");
        break;

    case D3DRMERR_BADDEVICE:
        // ???
        lpszError = TEXT("D3DRMERR_BADDEVICE");
        break;

    case D3DRMERR_BADVALUE:
        // ???
        lpszError = TEXT("D3DRMERR_BADVALUE");
        break;

    case D3DRMERR_BADMAJORVERSION:
        // ???
        lpszError = TEXT("D3DRMERR_BADMAJORVERSION");
        break;

    case D3DRMERR_BADMINORVERSION:
        // ???
        lpszError = TEXT("D3DRMERR_BADMINORVERSION");
        break;

    case D3DRMERR_UNABLETOEXECUTE:
        // ???
        lpszError = TEXT("D3DRMERR_BADMINORVERSION");
        break;
#endif // D3DRM_ERRORS

//
// Application defined errors
//
case APPERR_GENERIC:
        // Generic Error
        lpszError = TEXT("APPERR_GENERIC");
        break;

case APPERR_INVALIDPARAMS:
        // Invalid Parameters passed into function
        lpszError = TEXT("APPERR_INVALIDPARAMS");
        break;

case APPERR_NOTINITIALIZED:
// Programmer error, called function without proper initialization
lpszError = TEXT("APPERR_NOT_INITIALIZED");
break;

    default:
        // Unknown DD/D3D/App Error
        wsprintf (szMsg, "Unknown Error #%ld", (DWORD)(hResult & 0x0000FFFFL));
        lpszError = szMsg;
        break;
    }

    // Copy DD/D3D Error string to buff
    cLen = strlen (lpszError);
    if (cLen >= cchError)
    {
        cLen = cchError - 1;
    }

    if (cLen)
    {
        strncpy (lpszErrorBuff, lpszError, cLen);
        lpszErrorBuff[cLen] = 0;
    }

    return TRUE;
} // End GetDDErrorString

/*
**-----------------------------------------------------------------------------
** End of File
**-----------------------------------------------------------------------------
*/


DEBUG.H

#ifndef DEBUG_H
#define DEBUG_H
/*
**-----------------------------------------------------------------------------
**  File:       Debug.h
**  Purpose:    Sample Debug code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
// Note:  Define DEBUG_PROMPT_ME if you want MessageBox Error prompting
//  This can get annoying quickly...
// #define DEBUG_PROMPT_ME

// Pre and Post debug string info
#define START_STRTEXT ("BOIDS: ")
#define END_STRTEXT ("\r\n")
#endif // DEBUG

// Debug Levels
#define DEBUG_ALWAYS0L
#define DEBUG_CRITICAL1L
#define DEBUG_ERROR2L
#define DEBUG_MINOR3L
#define DEBUG_WARN4L
#define DEBUG_DETAILS5L

// Sample Errors
#define APPERR_GENERICMAKE_DDHRESULT (10001)
#defineAPPERR_INVALIDPARAMSMAKE_DDHRESULT (10002)
#define APPERR_NOTINITIALIZEDMAKE_DDHRESULT (10003)
#define APPERR_OUTOFMEMORYMAKE_DDHRESULT (10004)
#define APPERR_NOTFOUNDMAKE_DDHRESULT (10005)

/*
**-----------------------------------------------------------------------------
**  Macros
**-----------------------------------------------------------------------------
*/

#ifdef DEBUG
    #define DPF dprintf
    #define ASSERT(x) \
        if (! (x)) \
        { \
            DPF (DEBUG_ALWAYS, TEXT("Assertion violated: %s, File = %s, Line = #%ld\n"), \
                 TEXT(#x), TEXT(__FILE__), (DWORD)__LINE__ ); \
            abort (); \
        }

   #define REPORTERR(x) \
       ReportDDError ((x), TEXT("File = %s, Line = #%ld\n"), \
                      TEXT(__FILE__), (DWORD)__LINE__ );

   #define FATALERR(x) \
       ReportDDError ((x), TEXT("File = %s, Line = #%ld\n"), \
                      TEXT(__FILE__), (DWORD)__LINE__ ); \
       OnPause (TRUE); \
       DestroyWindow (g_hMainWindow);
#else
   #define REPORTERR(x)
   #define DPF 1 ? (void)0 : (void)
   #define ASSERT(x)
   #define FATALERR(x) \
       OnPause (TRUE); \
       DestroyWindow (g_hMainWindow);
#endif // DEBUG

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

// Debug Variables
#ifdef DEBUG
extern DWORD g_dwDebugLevel;
#endif

extern BOOL  g_fDebug;

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Debug Routines
#ifdef DEBUG
void __cdecl dprintf (DWORD dwDebugLevel, LPCTSTR szFormat, ...);
#endif //DEBUG

void _cdecl ReportDDError (HRESULT hResult, LPCTSTR szFormat, ...);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End DEBUG_H


PPLANE.CPP

/*
**-----------------------------------------------------------------------------
**  File:       PPlane.cpp
**  Purpose:    Paper plane demo
**  Notes:Sample demonstrating D3DLVERTEX's, DrawPrimitive,
**and SetTransform
**
** Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "Debug.h"
#include "D3DScene.h"
#include <time.h>
#include "d3dutils.h"
#include "pplane.h"

/*
**-----------------------------------------------------------------------------
**Local variables
**-----------------------------------------------------------------------------
*/

LPDIRECT3DMATERIAL2 g_lpmatBackground, g_lpMat;

extern const float pi;

//#define POINT_LINE_TEST
#define GROUND_GRID
DWORDg_hBackTex;
DWORDg_hTex;
DWORDg_hBackMat;
DWORDg_hMat;

#define NUM_PLANES42
Planeplanes[NUM_PLANES];

#define PPLANE_VERTICES36
#define PPLANE_INDICES60
D3DVERTEXpplane[PPLANE_VERTICES];
WORDpplane_indices[PPLANE_INDICES];

#ifdef GROUND_GRID
#define NUM_GRID22
#define GRID_WIDTH800.0f
D3DVERTEXgrid[NUM_GRID*NUM_GRID];
#endif

#ifdef POINT_LINE_TEST
#define LOTS_OF_POINTS(222)
D3DVERTEXpoints[LOTS_OF_POINTS];
#endif

D3DMATRIXg_proj, g_view, g_world;// Matrices

D3DLIGHTg_light;// Structure defining the light
LPDIRECT3DLIGHTg_lpD3DLight;// Object pointer for the light

/*
**-----------------------------------------------------------------------------
**Local prototypes
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**D3DScene Methods
**-----------------------------------------------------------------------------
*/

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::D3DScene
** Purpose:Default Constructor
**----------------------------------------------------------------------------
*/

D3DScene::D3DScene (void)
{
lpd3dWindow = NULL;
} // End D3DScene::D3DScene

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::~D3DScene
** Purpose:Default Destructor
**----------------------------------------------------------------------------
*/

D3DScene::~D3DScene (void)
{
Fini ();
lpd3dWindow = NULL;
} // End D3DScene::~D3DScene

/*
**-----------------------------------------------------------------------------
**  Name:       DrawPPlane
**  Purpose:Draws a single Paper plane
**-----------------------------------------------------------------------------
*/

HRESULT
DrawPPlane (LPDIRECT3DDEVICE2 lpDev)
{
HRESULT hResult;

#ifdef POINT_LINE_TEST
        hResult = lpDev->DrawPrimitive (D3DPT_LINESTRIP, D3DVT_VERTEX, (LPVOID)points, LOTS_OF_POINTS, D3DDP_WAIT);
    if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}
#endif

        hResult =lpDev->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, D3DVT_VERTEX, (LPVOID)pplane, PPLANE_VERTICES, pplane_indices, PPLANE_INDICES, D3DDP_WAIT);
    if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

// Success
return hResult;
}

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindow::Init
**  Purpose:Initialize scene objects
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::Init (LPD3DWindow lpd3dWin)
{
HRESULT hResult;

// Check Parameters
if (! lpd3dWin)
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

// Save pointer to D3D Window
lpd3dWindow = lpd3dWin;

//
// generate the paper plane data
//

// right wing top
pplane[ 0] = D3DVERTEX(D3DVECTOR(0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(-0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[ 1] = D3DVERTEX(D3DVECTOR(0.75f, 0.1875f, 3.25f), Normalize(D3DVECTOR(-0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[ 2] = D3DVERTEX(D3DVECTOR(2.75f, 0.6875f, -2.0f), Normalize(D3DVECTOR(-0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[ 3] = D3DVERTEX(D3DVECTOR(2.0f, 0.5f, -4.25f), Normalize(D3DVECTOR(-0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[ 4] = D3DVERTEX(D3DVECTOR(0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(-0.25f, 1.0f, 0.0f)), rnd(), rnd());

// right wing bottom
pplane[ 5] = D3DVERTEX(D3DVECTOR(0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[ 6] = D3DVERTEX(D3DVECTOR(0.75f, 0.1875f, 3.25f), Normalize(D3DVECTOR(0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[ 7] = D3DVERTEX(D3DVECTOR(2.75f, 0.6875f, -2.0f), Normalize(D3DVECTOR(0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[ 8] = D3DVERTEX(D3DVECTOR(2.0f, 0.5f, -4.25f), Normalize(D3DVECTOR(0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[ 9] = D3DVERTEX(D3DVECTOR(0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(0.25f, -1.0f, 0.0f)), rnd(), rnd());

// left wing bottom
pplane[10] = D3DVERTEX(D3DVECTOR(-0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(-0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[11] = D3DVERTEX(D3DVECTOR(-0.75f, 0.1875f, 3.25f), Normalize(D3DVECTOR(-0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[12] = D3DVERTEX(D3DVECTOR(-2.75f, 0.6875f, -2.0f), Normalize(D3DVECTOR(-0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[13] = D3DVERTEX(D3DVECTOR(-2.0f, 0.5f, -4.25f), Normalize(D3DVECTOR(-0.25f, -1.0f, 0.0f)), rnd(), rnd());
pplane[14] = D3DVERTEX(D3DVECTOR(-0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(-0.25f, -1.0f, 0.0f)), rnd(), rnd());

// left wing top
pplane[15] = D3DVERTEX(D3DVECTOR(-0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[16] = D3DVERTEX(D3DVECTOR(-0.75f, 0.1875f, 3.25f), Normalize(D3DVECTOR(0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[17] = D3DVERTEX(D3DVECTOR(-2.75f, 0.6875f, -2.0f), Normalize(D3DVECTOR(0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[18] = D3DVERTEX(D3DVECTOR(-2.0f, 0.5f, -4.25f), Normalize(D3DVECTOR(0.25f, 1.0f, 0.0f)), rnd(), rnd());
pplane[19] = D3DVERTEX(D3DVECTOR(-0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(0.25f, 1.0f, 0.0f)), rnd(), rnd());

// right body outside
pplane[20] = D3DVERTEX(D3DVECTOR(0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[21] = D3DVERTEX(D3DVECTOR(0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[22] = D3DVERTEX(D3DVECTOR(0.0f, -2.25f, -2.75f), Normalize(D3DVECTOR(1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[23] = D3DVERTEX(D3DVECTOR(0.0f, -0.5f, 3.625f), Normalize(D3DVECTOR(1.0f, -0.1f, 0.0f)), rnd(), rnd());

// right body inside
pplane[24] = D3DVERTEX(D3DVECTOR(0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(-1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[25] = D3DVERTEX(D3DVECTOR(0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(-1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[26] = D3DVERTEX(D3DVECTOR(0.0f, -2.25f, -2.75f), Normalize(D3DVECTOR(-1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[27] = D3DVERTEX(D3DVECTOR(0.0f, -0.5f, 3.625f), Normalize(D3DVECTOR(-1.0f, 0.1f, 0.0f)), rnd(), rnd());

// left body outside
pplane[28] = D3DVERTEX(D3DVECTOR(-0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(-1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[29] = D3DVERTEX(D3DVECTOR(-0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(-1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[30] = D3DVERTEX(D3DVECTOR(0.0f, -2.25f, -2.75f), Normalize(D3DVECTOR(-1.0f, -0.1f, 0.0f)), rnd(), rnd());
pplane[31] = D3DVERTEX(D3DVECTOR(0.0f, -0.5f, 3.625f), Normalize(D3DVECTOR(-1.0f, -0.1f, 0.0f)), rnd(), rnd());

// left body inside
pplane[32] = D3DVERTEX(D3DVECTOR(-0.125f, 0.03125f, 3.5f), Normalize(D3DVECTOR(1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[33] = D3DVERTEX(D3DVECTOR(-0.5f, 0.125f, -3.5f), Normalize(D3DVECTOR(1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[34] = D3DVERTEX(D3DVECTOR(0.0f, -2.25f, -2.75f), Normalize(D3DVECTOR(1.0f, 0.1f, 0.0f)), rnd(), rnd());
pplane[35] = D3DVERTEX(D3DVECTOR(0.0f, -0.5f, 3.625f), Normalize(D3DVECTOR(1.0f, 0.1f, 0.0f)), rnd(), rnd());

// right wing top
pplane_indices[ 0] = 0;
pplane_indices[ 1] = 1;
pplane_indices[ 2] = 4;
pplane_indices[ 3] = 1;
pplane_indices[ 4] = 2;
pplane_indices[ 5] = 4;
pplane_indices[ 6] = 4;
pplane_indices[ 7] = 2;
pplane_indices[ 8] = 3;

// right wing bottom
pplane_indices[ 9] = 5;
pplane_indices[10] = 9;
pplane_indices[11] = 6;
pplane_indices[12] = 6;
pplane_indices[13] = 9;
pplane_indices[14] = 7;
pplane_indices[15] = 7;
pplane_indices[16] = 9;
pplane_indices[17] = 8;

// left wing top
pplane_indices[18] = 10;
pplane_indices[19] = 11;
pplane_indices[20] = 14;
pplane_indices[21] = 11;
pplane_indices[22] = 12;
pplane_indices[23] = 14;
pplane_indices[24] = 14;
pplane_indices[25] = 12;
pplane_indices[26] = 13;

// left wing bottom
pplane_indices[27] = 15;
pplane_indices[28] = 19;
pplane_indices[29] = 16;
pplane_indices[30] = 16;
pplane_indices[31] = 19;
pplane_indices[32] = 17;
pplane_indices[33] = 17;
pplane_indices[34] = 19;
pplane_indices[35] = 18;

// right body outside
pplane_indices[36] = 20;
pplane_indices[37] = 23;
pplane_indices[38] = 21;
pplane_indices[39] = 21;
pplane_indices[40] = 23;
pplane_indices[41] = 22;

// right body inside
pplane_indices[42] = 24;
pplane_indices[43] = 25;
pplane_indices[44] = 27;
pplane_indices[45] = 25;
pplane_indices[46] = 26;
pplane_indices[47] = 27;

// left body outside
pplane_indices[48] = 28;
pplane_indices[49] = 29;
pplane_indices[50] = 31;
pplane_indices[51] = 29;
pplane_indices[52] = 30;
pplane_indices[53] = 31;

// left body inside
pplane_indices[54] = 32;
pplane_indices[55] = 35;
pplane_indices[56] = 33;
pplane_indices[57] = 33;
pplane_indices[58] = 35;
pplane_indices[59] = 34;

// seed the random number generator
srand (time (0));

for (int i=0; i<NUM_PLANES; i++)
{
planes[i].loc = D3DVECTOR(0.0f, 0.0f, 0.0f);
planes[i].goal = D3DVECTOR(10.0f*(rnd()-rnd()), 10.0f*(rnd()-rnd()), 10.0f*(rnd()-rnd()));
planes[i].delta = D3DVECTOR(0.0f, 0.0f, 1.0f);
planes[i].yaw = 0.0f;
planes[i].pitch = 0.0f;
planes[i].roll = 0.0f;
planes[i].dyaw = 0.0f;
}

#ifdef POINT_LINE_TEST
for (i=0; i<LOTS_OF_POINTS; i++)
{
float y = cos(pi*(double)i/LOTS_OF_POINTS);
float x = (float)sin (pi*20.0*(double)i/LOTS_OF_POINTS) * (1.0f-y*y);
float z = (float)cos (pi*20.0*(double)i/LOTS_OF_POINTS) * (1.0f-y*y);
D3DVECTORp(x, y, z);

p = Normalize (p);
points[i] = _D3DVERTEX(10.0*p, p, rnd(), rnd());
}
#endif

#ifdef GROUND_GRID
floatsize = GRID_WIDTH/(NUM_GRID-1.0f);
floatoffset = GRID_WIDTH/2.0f;
for (i=0; i<NUM_GRID; i++)
{
for (int j=0; j<NUM_GRID; j++)
{
grid[j+i*NUM_GRID] = D3DVERTEX(D3DVECTOR(i*size-offset, 0.0f, j*size-offset), Normalize(D3DVECTOR(0.0f, 1.0f, 0.0f)), 0.0f, 0.0f);
}
}
#endif

// Initialize Viewport
hResult = InitViewport ();
if (FAILED (hResult))
return hResult;

    return DD_OK;
} // End D3DScene::Init

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::Fini
**  Purpose:Cleanup scene objects
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::Fini (void)
{
FiniViewport ();

// Success
return DD_OK;
} // End D3DScene::Fini

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::Render
**  Purpose:Draws scene
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::Render (void)
{
LPDIRECT3DDEVICE2lpDev;
LPDIRECT3DVIEWPORT2 lpView;
RECTrSrc;
LPD3DRECTlpExtent = NULL;
    D3DRECTd3dRect;
D3DCLIPSTATUSstatus;
HRESULThResult;

// Check Initialization
if ((! lpd3dWindow) || (! lpd3dWindow->isValid ()))
{
// Error,
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

lpDev= lpd3dWindow->lpd3dDevice;
lpView  = lpd3dWindow->lpd3dViewport;
lpd3dWindow->GetSrcRect (rSrc);

//
    // Clear both back and z-buffer.
    //
    // NOTE: Its safe to specify the z-buffer clear flag even if we
    // don't have an attached z-buffer. Direct3D will simply discard
    // the flag if no z-buffer is being used.
    //
    // NOTE: For maximum efficiency we only want to clear those
    // regions of the device surface and z-buffer which we actually
    // rendered to in the last frame. This is the purpose of the
    // array of rectangles and count passed to this function. It is
    // possible to query Direct3D for the regions of the device
    // surface that were rendered to by that execute. The application
    // can then accumulate those rectangles and clear only those
    // regions. However this is a very simple sample and so, for
    // simplicity, we will just clear the entire device surface and
    // z-buffer. Probably not something you wan't to do in a real
    // application.
    ///
d3dRect.lX1 = rSrc.left;
d3dRect.lX2 = rSrc.right;
d3dRect.lY1 = rSrc.top;
d3dRect.lY2 = rSrc.bottom;
hResult = lpView->Clear (1UL, &d3dRect,
 D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

// Calculate exclude region
if (lpExtent)
{
status.dwFlags = D3DCLIPSTATUS_EXTENTS2;
status.dwStatus = 0;
status.minx = (float)lpExtent->x1;
status.maxx = (float)lpExtent->x2;
status.miny = (float)lpExtent->y1;
status.maxy = (float)lpExtent->y2;
status.minz = 0.0f;
status.maxz = 0.0f;
}

hResult = lpDev->BeginScene ();
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

// Exclude text region
if (lpExtent)
{
hResult = lpDev->SetClipStatus (&status);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}
}

    hResult = lpDev->SetLightState (D3DLIGHTSTATE_MATERIAL, g_hMat);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

    hResult = lpDev->SetRenderState (D3DRENDERSTATE_ZENABLE, 1);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

    hResult = lpDev->SetRenderState (D3DRENDERSTATE_TEXTUREHANDLE, 0);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

// turn on some ambient light
    hResult = lpDev->SetLightState (D3DLIGHTSTATE_AMBIENT, RGBA_MAKE(60, 60, 60, 60));
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

// set the projection matrix
g_proj = ProjectionMatrix (10.0f, 500.0f, pi/2.0f);
hResult = lpDev->SetTransform (D3DTRANSFORMSTATE_PROJECTION, &g_proj);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

// play with planes
static floattic = 0.0f;
static floatspeed=2.0f;
static floatangle_tweak = 0.02f;
static D3DVECTORz_ward(0.0f, 0.0f, 1.0f);

for (int i=0; i<NUM_PLANES; i++)
{
D3DVECTORoffset;

// tweek orientation based on last position and goal
offset = planes[i].goal - planes[i].loc;

// first, tweak the pitch
if (offset.y > 1.0) {// we're too low
planes[i].pitch += angle_tweak;
if (planes[i].pitch > 0.8f)
planes[i].pitch = 0.8f;
} else if (offset.y < -1.0) {// we're too high
planes[i].pitch -= angle_tweak;
if (planes[i].pitch < -0.8f)
planes[i].pitch = -0.8f;
} else {
// add damping
planes[i].pitch *= 0.95f;
}

// now figure out yaw changes
offset.y = 0.0f;  planes[i].delta.y = 0.0f;
planes[i].delta = Normalize (planes[i].delta);
offset = Normalize (offset);
floatdot = DotProduct (offset, planes[i].delta);
offset = CrossProduct (offset, planes[i].delta);
dot = (1.0f-dot)/2.0f * angle_tweak * 10.0f;
if (offset.y > 0.01) {
planes[i].dyaw = (planes[i].dyaw*9.0f + dot) * 0.1f;
} else if (offset.y < 0.01) {
planes[i].dyaw = (planes[i].dyaw*9.0f - dot) * 0.1f;
}
planes[i].yaw += planes[i].dyaw;
planes[i].roll = -planes[i].dyaw * 9.0f;

if (rnd() < 0.03)
{
planes[i].goal = D3DVECTOR(60.0f*(rnd()-rnd()), 60.0f*(rnd()-rnd()), rnd()*300.0f - 100.0f);
}

// build the world matrix for the pplane
g_world = MatrixMult (MatrixMult (RotateYMatrix (planes[i].yaw), RotateXMatrix (planes[i].pitch)), RotateZMatrix (planes[i].roll));

// get delta buy grabbing the z axis out of the transform
planes[i].delta[0] = g_world(2, 0);
planes[i].delta[1] = g_world(2, 1);
planes[i].delta[2] = g_world(2, 2);
// update position
planes[i].loc += speed * planes[i].delta;

// before we draw the first plane use it's position to update the camera
if (i==0)
{
D3DVECTORfrom, at, local_up;
D3DVECTORup(0.0f, 1.0f, 0.0f);

local_up[0] = g_world(1, 0);
local_up[1] = g_world(1, 1);
local_up[2] = g_world(1, 2);
from = planes[0].loc - 20.0f * planes[0].delta + 6.0f * local_up;
at = planes[0].loc + planes[0].delta;

g_view = ViewMatrix (from, at, up, planes[0].roll);
hResult = lpDev->SetTransform (D3DTRANSFORMSTATE_VIEW, &g_view);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}
}

// first translate into place, then set orientation, then scale
g_world = TranslateMatrix (planes[i].loc);
g_world = MatrixMult (g_world, MatrixMult (MatrixMult (RotateYMatrix (planes[i].yaw), RotateXMatrix (planes[i].pitch)), RotateZMatrix (planes[i].roll)));
//g_world = MatrixMult (g_world, Scale(5.0f));

// apply the world matrix
hResult = lpDev->SetTransform (D3DTRANSFORMSTATE_WORLD, &g_world);
if (FAILED (hResult))
{
    REPORTERR (hResult);
return hResult;
}

// display the pplane
DrawPPlane (lpDev);
}// end of loop for each pplane

#ifdef GROUND_GRID
g_world = TranslateMatrix (0.0f, -60.0f, 0.0f);
hResult = lpDev->SetTransform (D3DTRANSFORMSTATE_WORLD, &g_world);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

        hResult = lpDev->DrawPrimitive (D3DPT_LINELIST, D3DVT_VERTEX, (LPVOID)grid, NUM_GRID*NUM_GRID, D3DDP_WAIT);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

g_world = MatrixMult (g_world, RotateYMatrix (pi/2.0f));
hResult = lpDev->SetTransform (D3DTRANSFORMSTATE_WORLD, &g_world);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

        hResult = lpDev->DrawPrimitive (D3DPT_LINELIST, D3DVT_VERTEX, (LPVOID)grid, NUM_GRID*NUM_GRID, D3DDP_WAIT);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}
#endif

tic += 0.01f;

if (lpExtent)
{
hResult = lpDev->GetClipStatus (&status);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}
}

    hResult = lpDev->EndScene ();
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
}

if (lpExtent)
{
// return clip status in extent struct
if (status.dwFlags & D3DCLIPSTATUS_EXTENTS2)
{
lpExtent->x1 = (long)floor ((double)status.minx);
lpExtent->x2 = (long)ceil ((double)status.maxx);
lpExtent->y1 = (long)floor ((double)status.miny);
lpExtent->y2 = (long)ceil ((double)status.maxy);
}
}

// Success
    return DD_OK;
} // End D3DScene::Render

/*
**----------------------------------------------------------------------------
** Name:        D3DScene::Restore
** Purpose:     Restore any scene specific surfaces that might have been
**lost on a DDERR_LOSTSURFACE message
**----------------------------------------------------------------------------
*/

HRESULT D3DScene::Restore (void)
{
// Nothing to do for now

// Success
return DD_OK;
} // End D3DScene::Restore

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::InitViewport
**  Purpose:Initialize Viewport
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::InitViewport (void)
{
LPDIRECT3D2lpD3D;
LPDIRECT3DDEVICE2   lpDev;
LPDIRECT3DVIEWPORT2 lpViewport;
HRESULThResult;
    D3DMATERIALmatBackground, mat;
    D3DMATERIALHANDLEhBackMat, hMat;

// Check Initialization
if ((! lpd3dWindow) || (! lpd3dWindow->isValid ()))
{
// Error,
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

lpD3D  = lpd3dWindow->lpD3D;
lpDev  = lpd3dWindow->lpd3dDevice;
lpViewport = lpd3dWindow->lpd3dViewport;

if ((! lpD3D) || (! lpDev) || (! lpViewport))
{
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

// Create and set up the background material
    hResult = lpD3D->CreateMaterial (&g_lpmatBackground, NULL);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
    }

    memset (&matBackground, 0, sizeof(D3DMATERIAL));
    matBackground.dwSize = sizeof(D3DMATERIAL);
    matBackground.diffuse.r = (D3DVALUE)0.2;
    matBackground.diffuse.g = (D3DVALUE)0.6;
    matBackground.diffuse.b = (D3DVALUE)1.0;
    matBackground.ambient.r = (D3DVALUE)1.0;
    matBackground.ambient.g = (D3DVALUE)1.0;
    matBackground.ambient.b = (D3DVALUE)1.0;
//    matBackground.hTexture = hBackTex;
matBackground.dwRampSize = 1;

hResult = g_lpmatBackground->SetMaterial (&matBackground);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

hResult = g_lpmatBackground->GetHandle (lpDev, &hBackMat);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

    hResult = lpViewport->SetBackground (hBackMat);
if (FAILED (hResult))
{
REPORTERR (hResult);
return hResult;
}

g_hBackMat = hBackMat;

// create and set up the plane material
    hResult = lpD3D->CreateMaterial (&g_lpMat, NULL);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
    }

    memset(&mat, 0, sizeof(D3DMATERIAL));
    mat.dwSize = sizeof(D3DMATERIAL);
    mat.diffuse.r = (D3DVALUE)1.0;
    mat.diffuse.g = (D3DVALUE)1.0;
    mat.diffuse.b = (D3DVALUE)1.0;
    mat.ambient.r = (D3DVALUE)1.0;
    mat.ambient.g = (D3DVALUE)1.0;
    mat.ambient.b = (D3DVALUE)1.0;
#define SPECULAR
#ifdef SPECULAR
    mat.specular.r = (D3DVALUE)1.0;
    mat.specular.g = (D3DVALUE)1.0;
    mat.specular.b = (D3DVALUE)1.0;
    mat.power = (float)40.0;
#else
    mat.specular.r = (D3DVALUE)0.0;
    mat.specular.g = (D3DVALUE)0.0;
    mat.specular.b = (D3DVALUE)0.0;
    mat.power = (float)0.0;
#endif
//    mat.hTexture = hTexure;
    mat.dwRampSize = 16;
    g_lpMat->SetMaterial (&mat);
    g_lpMat->GetHandle (lpDev, &hMat);
g_hMat= (DWORD) hMat;
//g_hTex= hTexure;

//g_view = ViewMatrix(100.0f);
g_proj = ProjectionMatrix (1.0f, 1000.0f, 90.0f);
g_world = IdentityMatrix ();

// set up the light
    memset (&g_light, 0, sizeof(D3DLIGHT));
    g_light.dwSize = sizeof(D3DLIGHT);
    g_light.dltType = D3DLIGHT_POINT;
// position light behind viewer
    g_light.dvPosition.x = 0.0f;
    g_light.dvPosition.y = 1000.0f;
    g_light.dvPosition.z = -100.0f;
    g_light.dcvColor.r = D3DVAL(1.0);
    g_light.dcvColor.g = D3DVAL(1.0);
    g_light.dcvColor.b = D3DVAL(1.0);
    g_light.dvAttenuation0 = (float)1.0;
    g_light.dvAttenuation1 = (float)0.0;
    g_light.dvAttenuation2 = (float)0.0;

    hResult = lpD3D->CreateLight (&g_lpD3DLight, NULL);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
    }

    hResult = g_lpD3DLight->SetLight (&g_light);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
    }

    hResult = lpViewport->AddLight (g_lpD3DLight);
if (FAILED (hResult))
{
        REPORTERR (hResult);
return hResult;
    }

// Success
    return DD_OK;
} // End D3DScene::InitViewport

/*
**-----------------------------------------------------------------------------
**  Name:       D3DScene::FiniViewport
**  Purpose:Cleanup Viewport
**-----------------------------------------------------------------------------
*/

HRESULT D3DScene::FiniViewport (void)
{
HRESULT hResult;
LPDIRECT3DVIEWPORT2 lpViewport;

// Check Initialization
if (! lpd3dWindow)
{
// Error,
REPORTERR (DDERR_GENERIC);
return DDERR_GENERIC;
}

lpViewport = lpd3dWindow->GetViewport ();

// Cleanup light
if (g_lpD3DLight)
{
// Remove from current view
    if (lpViewport)
{
hResult = lpViewport->DeleteLight (g_lpD3DLight);
if (FAILED (hResult))
{
REPORTERR (hResult);
}

}

// Release light
hResult = g_lpD3DLight->Release ();
if (FAILED (hResult))
{
REPORTERR (hResult);
}

g_lpD3DLight = NULL;
}

// Cleanup Material
    if (g_lpMat)
{
hResult = g_lpMat->Release ();
if (FAILED (hResult))
{
REPORTERR (hResult);
}

g_lpMat = NULL;
g_hMat = NULL;
}

// Cleanup Background
if (g_lpmatBackground)
{
hResult = g_lpmatBackground->Release ();
if (FAILED (hResult))
{
REPORTERR (hResult);
}

g_lpmatBackground = NULL;
g_hBackMat = NULL;
}

// Success
return DD_OK;
} // End D3DScene::FiniViewport

/*
**-----------------------------------------------------------------------------
**End of File
**-----------------------------------------------------------------------------
*/


PPLANE.H

#ifndef PPLANE_H
#define PPLANE_H
/*
**-----------------------------------------------------------------------------
** File:       PPlane.h
** Purpose:    Sample showing DrawPrimitive functionality
**
** Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Include files
**-----------------------------------------------------------------------------
*/
#include "Common.h"

/*
**-----------------------------------------------------------------------------
**Type definitions
**-----------------------------------------------------------------------------
*/

typedef struct t_plane {
D3DVECTORloc,// current location
goal,// current goal
delta;// current direction
floatyaw, pitch, roll;
floatdyaw;
} Plane;

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // PPLANE_H


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by pplane.rc
//
#define IDC_STATIC                       -1
#define IDI_MAIN_ICON                   101
#define IDR_MAIN_MENU                   102
#define IDR_MAIN_ACCEL                  113
#define IDD_ABOUT                       114
#define IDD_CHANGE_MODE                 115
#define IDD_CHANGE_DRIVER               116
#define IDD_CHANGE_DEVICE               117
#define IDC_MODES                       1001
#define IDC_DRIVERS                     1003
#define IDC_DEVICES                     1004
#define IDM_EXIT                        40001
#define IDM_ABOUT                       40002
#define ID_HELP_ABOUT                   40003
#define IDM_CHANGE_MODES                40004
#define IDM_CHANGE_DRIVERS              40005
#define IDM_CHANGE_DEVICES              40009

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        118
#define _APS_NEXT_COMMAND_VALUE         40010
#define _APS_NEXT_CONTROL_VALUE         1008
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


WINMAIN.CPP

/*
**-----------------------------------------------------------------------------
**  File:       WinMain.cpp
**  Purpose:    D3D sample showing DrawPrimitive functionality
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

// Note:  Define INITGUID in one and only one file!!!
#define  INITGUID
#include "Common.h"
#include "Debug.h"
#include "WinMain.h"
#include "WinProc.h"
#include "DrvMgr.h"
#include "D3DWin.h"
#include "D3DScene.h"

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

// Application variables
HINSTANCEg_hMainInstance= NULL;
HWND        g_hMainWindow       = NULL;
HACCEL      g_hMainAccel        = NULL;

LPCTSTR     g_szMainName        = TEXT ("PPlane");
LPCTSTR     g_szMainClass= TEXT ("PPlaneClass");
LPCTSTR     g_szMainTitle= TEXT ("Paper Plane Sample");
LPCTSTR     g_szPaused          = TEXT ("Paused");

INT         g_nExitCode         = 0L;

//
//D3D variables
//

// Driver Management (enumeration of drivers, modes, D3D devices)
DDDrvMgrg_DrvMgr;

// D3D Window management
LPD3DWindowg_lpd3dWin;

// D3D Sample Scene
LPD3DSceneg_lpd3dScene;

/*
**-----------------------------------------------------------------------------
**  Local Function Prototypes
**-----------------------------------------------------------------------------
*/

HRESULT CreateD3DWindow (
DWORDdwStyleEx,/* In:  Standard CreateWindowEx parameters */
LPCTSTRlpszClass,
    LPCTSTRlpszName,
DWORDdwStyle,
intx,
inty,
intnWidth,
intnHeight,
HWNDhParent,
HMENUhMenu,
HINSTANCEhInstance,
LPVOIDlpParam,

BOOL        fUseZBuffer,/* In:  Use Z-buffer */
HWND *lphWnd,/* Out:Window handle */
LPD3DWindow * lpd3dWnd);/* Out: Created D3DWindow pointer */

HRESULT DestroyD3DWindow (void);

extern void GetDXVersion (LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform);

/*
**-----------------------------------------------------------------------------
**  Function Definitions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       InitMain
**  Purpose:Initialize Application
**-----------------------------------------------------------------------------
*/

BOOL InitMain (void)
{
    DWORD dwDxVersion  = 0L;
    DWORD dwDxPlatform = 0L;

    DPF (DEBUG_DETAILS, TEXT("InitMain"));

    // Get hInstance handle
    g_hMainInstance = (HINSTANCE) GetModuleHandle (NULL);
    if (NULL == g_hMainInstance)
    {
// Error - GetModule Handle

DPF (DEBUG_CRITICAL, TEXT("InitMain - GetModuleHandle() failed."));
        return FALSE;
    }

    // Check for DX5.0 or greater
    GetDXVersion (&dwDxVersion, &dwDxPlatform);
    if (dwDxVersion < 0x500)
    {
        MessageBox (NULL, TEXT("This App requires DX 5.0 or greater\r\nin order to run."), TEXT("Error"), MB_OK);
        return FALSE;
    }

    // Init main window
    if (! InitMainWindow ())
        return FALSE;

    DPF (DEBUG_DETAILS, TEXT("InitMain - Success"));

    // Success
    return TRUE;
} // End InitMain

/*
**-----------------------------------------------------------------------------
**  Name:       FiniMain
**  Purpose:Cleanup Application
**-----------------------------------------------------------------------------
*/

void FiniMain (void)
{
DestroyD3DWindow ();
    UnregisterClass (g_szMainClass, g_hMainInstance);
} // End FiniMain

/*
**-----------------------------------------------------------------------------
**  Name:       RunMain
**  Purpose:    Main Message Pump
**-----------------------------------------------------------------------------
*/

void RunMain (void)
{
    MSG msg;

    DPF (DEBUG_DETAILS, TEXT("RunMain - Enter"));

    if ((! g_hMainInstance) || (! g_hMainWindow))
    {
        return;
    }

    g_hMainAccel = LoadAccelerators (g_hMainInstance, MAKEINTRESOURCE (IDR_MAIN_ACCEL));

    //
    //  The Main Message loop
    //
    //  Note:  In order to catch Idle behavior we use PeekMessage instead of GetMessage
    //
    while (TRUE)
    {
        while (TRUE)
        {

            if (! PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
            {
                // App is Idle
                break;
            }

            // Exit App ?!?
            if (msg.message == WM_QUIT)
            {
                DPF (DEBUG_DETAILS, TEXT("RunMain - Exit"));
                g_nExitCode = msg.wParam;
                return;
            }

            if (g_hMainAccel)
            {
                if (! TranslateAccelerator (g_hMainWindow, g_hMainAccel, &msg))
                {
                    TranslateMessage (&msg);
                    DispatchMessage (&msg);
                }
            }
            else
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }

        // Do some Idle processing
        OnIdle (g_hMainWindow);
    } // End While

} // End RunMain

/*
**-----------------------------------------------------------------------------
**  Name:       WinMain
**  Purpose:    Application EntryPoint
**-----------------------------------------------------------------------------
*/

INT WINAPI WinMain(
    HINSTANCE   hInstance,    // handle to current instance
    HINSTANCE   hPrevInstance,// handle to previous instance
    LPSTR       lpCmdLine,    // pointer to command line
    INT         nShowCmd)     // show state of window
{
    g_nExitCode = 0;

    DPF (DEBUG_DETAILS, TEXT("WinMain - Enter"));

    // Initialize App
    if (! InitMain ())
        return g_nExitCode;

    // Main Message Loop
    RunMain ();

    // Cleanup App
    FiniMain ();

    // Success
    DPF (DEBUG_DETAILS, TEXT("WinMain - Exit, status = %08lX"), (DWORD)g_nExitCode);
    return g_nExitCode;
} // End WinMain

 /*
**-----------------------------------------------------------------------------
**  Name:       InitMainWindow
**  Purpose:
**-----------------------------------------------------------------------------
*/

BOOL InitMainWindow (void)
{
HRESULThResult;
    WNDCLASS    wc;
    HWND        hWnd;
RECTrWin;
DWORDdwStyle, dwStyleEx;
DWORDdwWidth, dwHeight;

    DPF (DEBUG_DETAILS, TEXT("InitMainWindow - Enter"));

    // Register Main Window Class
    wc.style            = CS_DBLCLKS;
    wc.lpfnWndProc      = (WNDPROC)D3DWindowProc;// D3D Window proc
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 4;// Reserve space for lpd3dWindow pointer
    wc.hInstance        = g_hMainInstance;
    wc.hIcon            = LoadIcon (g_hMainInstance, MAKEINTRESOURCE (IDI_MAIN_ICON));
    wc.hCursor          = LoadCursor ((HINSTANCE)NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName     = MAKEINTRESOURCE (IDR_MAIN_MENU);
    wc.lpszClassName    = g_szMainClass;

    if (! RegisterClass (&wc))
    {
        // Error - Register class failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - RegisterClass failed."));
        return FALSE;
    }

// Calculate Initial window size to
// support client area of 256 x 256
rWin.left= 0;
rWin.top= 0;
rWin.right  = DEF_WIN_SIZE_X;
rWin.bottom= DEF_WIN_SIZE_Y;

// Setup a normal window with the following exceptions
//- No Minimize button (not supported yet...)
//- No Maximize button (not supported yet...)
dwStyleEx = WS_EX_OVERLAPPEDWINDOW;
dwStyle   = WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU;
AdjustWindowRectEx (&rWin, dwStyle, TRUE, dwStyleEx);

dwWidth = rWin.right - rWin.left;
dwHeight = rWin.bottom - rWin.top;

//
    // Create Main Window
//
hResult = CreateD3DWindow (dwStyleEx,
   g_szMainClass,
   g_szMainTitle,
   dwStyle,
   CW_USEDEFAULT, CW_USEDEFAULT,
   dwWidth, dwHeight,
   NULL,
   NULL,
   g_hMainInstance,
   NULL,
   TRUE,// Use Z-Buffer
   &hWnd,
   &g_lpd3dWin);
if (FAILED (hResult))
    {
        // Error - CreateWindow failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - CreateWindow failed."));
        return FALSE;
    }

//
// Create our D3D scene
//

g_lpd3dScene = new D3DScene;
if (! g_lpd3dScene)
{
        // Error - Construct D3D Scene failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - Not enough memory for D3D scene."));
        return FALSE;
}

// Attach our D3DScene to the Window
hResult = g_lpd3dWin->AttachScene (g_lpd3dScene);
if (FAILED (hResult))
{
        // Error - Attaching our D3D Scene to our D3D Window failed
        DPF (DEBUG_CRITICAL, TEXT("InitMainWindow - AttachScene failed."));
        return FALSE;
}

    g_hMainWindow = hWnd;

    // Success
    DPF (DEBUG_DETAILS, TEXT("InitMainWindow - Success"));
    return TRUE;
} // End InitMainWindow

/*
**-----------------------------------------------------------------------------
**  DD/D3D Function Definitions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
** Name:    CreateD3DWindow
** Purpose: Creates a simple D3D window
** Notes:
**-----------------------------------------------------------------------------
*/

HRESULT CreateD3DWindow (
DWORDdwStyleEx,/* In:  CreateWindowEx parameters */
LPCTSTRlpszClass,
    LPCTSTRlpszName,
DWORDdwStyle,
intx,
inty,
intnWidth,
intnHeight,
HWNDhParent,
HMENUhMenu,
HINSTANCEhInstance,
LPVOIDlpParam,

BOOL        fUseZBuffer,/* In:  Use Z-buffer */
HWND *lphWindow,/* Out:Window handle */
LPD3DWindow * lpd3dWindow)/* Out: Created D3DWindow pointer */
{
    HRESULT             hResult;
    HWND                hWnd;
    LPD3DWindow         lpd3dWin;

// Check Parameters
if (! lpszClass)
{
REPORTERR (DDERR_INVALIDPARAMS);
return DDERR_INVALIDPARAMS;
}

// Fill in defaults, if missing
if (! hInstance)
hInstance = GetModuleHandle (NULL);

if (! hParent)
hParent = GetDesktopWindow ();

    // Create D3D Window
    lpd3dWin = new D3DWindow ();
    if (! lpd3dWin)
    {
// Error, not enough memory
REPORTERR (DDERR_OUTOFMEMORY);
        return DDERR_OUTOFMEMORY;
    }

    // Create Extended Window
    hWnd = CreateWindowEx (
                dwStyleEx,// extended window style
                lpszClass,// window class
                lpszName,    // window name (title)
                dwStyle,// window style
                x,// horizontal position of window
                y,// vertical position of window
                nWidth,// window width
                nHeight,            // window height
                hParent,// handle to parent window
                hMenu,                  // handle to menu
                hInstance,              // handle to app instance
                lpParam);               // Extra Data
    if (! hWnd)
    {
        // Error, unable to create window
        delete lpd3dWin;
REPORTERR (DDERR_GENERIC);
        return DDERR_GENERIC;
    }

    //
    //  Gotcha:  You must Show the window before you switch to Fullscreen
    //           Otherwise the following can happen.
    //   1. SetCooperativeLevel (DDSCL_EXCLUSIVE) works
    //             2. SetDisplayMode (w,h,bpp) loses the exclusive mode state
    //                because the window is not fully initialized.
    //                (DDraw gets confused by the responses it gets from the window)
    //             3. CreateSurface (PRIMARY | FLIP) fails with DDERR_NOEXCLUSIVEMODE.
    //                As the exlusive mode state has been lost
    //

    // Show the Window and Paint it's contents
    ShowWindow (hWnd, SW_SHOWDEFAULT);
    UpdateWindow (hWnd);

    // Create D3DWindow from hWnd
    hResult = lpd3dWin->Create (hWnd);
    if (FAILED (hResult))
    {
        // Error, unable to create window
        delete lpd3dWin;
        DestroyWindow (hWnd);
        return hResult;
    }

    // Return results
    if (lphWindow)
        *lphWindow = hWnd;
    if (lpd3dWindow)
        *lpd3dWindow = lpd3dWin;

    // Success
    return DD_OK;
} // End CreateD3DWindow

/*
**-----------------------------------------------------------------------------
** Name:    DestroyD3DWindow
** Purpose: Destroys a D3DWindow (window and D3DWindow object)
**-----------------------------------------------------------------------------
*/

HRESULT DestroyD3DWindow (void)
{
// Check Parameters
    if ((g_hMainWindow) && (IsWindow (g_hMainWindow)))
{
DestroyWindow (g_hMainWindow);
g_hMainWindow = NULL;
}

    // Free memory associated with D3DWindow Object
    if (g_lpd3dWin)
{
        delete g_lpd3dWin;
g_lpd3dWin = NULL;
}

// Sucess
    return DD_OK;
} // End DestroyD3DWindow

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/


WINMAIN.H

#ifndef WINMAIN_H
#define WINMAIN_H
/*
**-----------------------------------------------------------------------------
**  File:       WinMain.h
**  Purpose:    Sample Window code
**  Notes:
**
**  Copyright (c) 1996 by Microsoft
**, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"

/*
**-----------------------------------------------------------------------------
**  Defines
**-----------------------------------------------------------------------------
*/

#define MIN_WIN_SIZE_X128
#define MIN_WIN_SIZE_Y  128
#define DEF_WIN_SIZE_X256
#define DEF_WIN_SIZE_Y  256

/*
**-----------------------------------------------------------------------------
**  Global Variables
**-----------------------------------------------------------------------------
*/

extern HINSTANCE    g_hMainInstance;
extern HWND         g_hMainWindow;
extern HACCEL       g_hMainAccel;

extern LPCTSTR      g_szMainName;
extern LPCTSTR      g_szMainClass;
extern LPCTSTR      g_szMainTitle;
extern LPCTSTR      g_szPaused;

extern INT          g_nExitCode;

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Windows routines
BOOL InitMain (void);
void FiniMain (void);

void RunMain (void);
INT  WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, INT nShowCmd);
BOOL InitMainWindow (void);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End WINMAIN_H


WINPROC.CPP

/*
**-----------------------------------------------------------------------------
**  File:       WinProc.cpp
**  Purpose:    A sample windows proc for supporting D3D.
**  Notes:
**
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**Include Files
**-----------------------------------------------------------------------------
*/

#include "WinProc.h"
#include "WinMain.h"
#include "Debug.h"

/*
**-----------------------------------------------------------------------------
**Local Typedefs
**-----------------------------------------------------------------------------
*/

typedef struct tagChangeDDInfo {
DDDrvInfo  * lpDrvOrig;// Original Driver
DDDrvInfo  * lpDrvNew;// New Driver (user choice)

D3DDevInfo * lpDevOrig;// Original D3D device
D3DDevInfo * lpDevNew;// New D3D device (user choice)

DDModeInfo * lpModeOrig;// Orignal Mode
DDModeInfo * lpModeNew;// New Mode (user choice)
} ChangeDDInfo;
typedef ChangeDDInfo * LPChangeDDInfo;

/*
**-----------------------------------------------------------------------------
**Local Variables
**-----------------------------------------------------------------------------
*/

// Used for 3Dfx Voodoo driver detection below
const GUID guid3DfxVoodoo = {
0x3A0CFD01,
0x9320,
0x11CF,
0xAC,0xA1,0x00,0xA0,0x24,0x13,0xC2,0xE2
};

/*
**-----------------------------------------------------------------------------
**Local function prototypes
**-----------------------------------------------------------------------------
*/

// About message proc
BOOL FAR PASCAL AboutBoxProc (HWND hWnd, UINT uiMsg,
  WPARAM wParam, LPARAM lParam);

/*
**-----------------------------------------------------------------------------
**Local function prototypes
**-----------------------------------------------------------------------------
*/

// Change Dialog
BOOL DlgDriversInit (HWND hDlg);
BOOL DlgDevicesInit (HWND hDlg);
BOOL DlgModesInit (HWND hDlg);

int _cdecl CompareModes (const void* element1, const void* element2);

LPDDDrvInfo  DlgGetDriver (HWND hDlg);
LPD3DDevInfo DlgGetDevice (HWND hDlg);
LPDDModeInfo DlgGetMode (HWND hDlg);

BOOL FAR PASCAL ChangeDriverProc (HWND hWnd, UINT uiMsg,
      WPARAM wParam, LPARAM lParam);
void OnChangeDriver (HWND hWindow, int idDialog);

/*
**-----------------------------------------------------------------------------
**Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       D3DWindowProc
**  Purpose:    handles windows messages for D3DWindow's
**-----------------------------------------------------------------------------
*/

LRESULT CALLBACK D3DWindowProc (
    HWND    hWindow,
    UINT    uiMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
LONG lResult;

    //
    // Windows messages
    //
    switch (uiMsg)
    {
case WM_ACTIVATE:
// Note:  Do we need to handle this ?!?
break;

    case WM_ACTIVATEAPP:
        return OnActivateApp (hWindow, wParam, lParam);

case WM_CLOSE:
return OnClose (hWindow);

    case WM_COMMAND:
        return OnCommand (hWindow, wParam, lParam);

    case WM_CREATE:
return OnCreate (hWindow);

    case WM_DESTROY:
return OnDestroy (hWindow);

case WM_DISPLAYCHANGE:
return OnDisplayChange (hWindow);

    case WM_ERASEBKGND:
return OnEraseBackground (hWindow, wParam, lParam);

    case WM_GETMINMAXINFO:
return OnGetMinMaxInfo (hWindow, (LPMINMAXINFO) lParam);

    case WM_MOVE:
        return OnMove (hWindow, (INT)LOWORD(lParam), (INT)HIWORD(lParam));

    case WM_PAINT:
    {
        HDC         hdc;
        PAINTSTRUCT ps;

        hdc = BeginPaint (hWindow, &ps);
        lResult = OnPaint (hWindow, hdc, &ps);
        EndPaint (hWindow, &ps);
    }
        return lResult;

case WM_PALETTECHANGED:
// Note:  We need to support this
break;

case WM_QUERYNEWPALETTE:
// Note:  We need to support this
break;

    case WM_SIZE:
        return OnSize (hWindow, (INT)LOWORD(lParam), (INT)HIWORD(lParam));

 //
     // D3D Window messages
     //
    case D3DWIN_GET_VALID:       // Get valid size
{
LPD3DWindow lpd3dWindow;

//Get D3DWindow pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
        if (! lpd3dWindow)
            return FALSE;
        return lpd3dWindow->isValid();
}

    case D3DWIN_GET_POINTER:
//Get D3DWindow pointer
return (LRESULT)GetWindowLong (hWindow, GWL_USERDATA);

    case D3DWIN_GET_SURFACE:
{
LPD3DWindow lpd3dWindow;

//Get D3DWindow pointer
lpd3dWindow =  (LPD3DWindow)(void *)GetWindowLong (hWindow, GWL_USERDATA);
        if (! lpd3dWindow)
            return NULL;
        return (LPARAM)(lpd3dWindow->GetRender ());
}

case D3DWIN_INIT:
return OnD3DInit (hWindow, (LPD3DWindow) lParam);

case D3DWIN_FINI:
return OnD3DFini (hWindow, (LPD3DWindow) lParam);

case D3DWIN_CHANGED_DRIVER:
return OnD3DChangeDriver (hWindow);

case D3DWIN_CHANGED_DEVICE:
return OnD3DChangeDevice (hWindow);

case D3DWIN_CHANGED_MODE:
return OnD3DChangeMode (hWindow);
    } // End Switch

   // Do Default window behavior
   return (DefWindowProc (hWindow, uiMsg, wParam, lParam));
} // End D3DWindowProc

/*
**-----------------------------------------------------------------------------
**  Name:       AboutBoxProc
**  Purpose:    handles messages for About Dialog box
**-----------------------------------------------------------------------------
*/
BOOL
FAR PASCAL AboutBoxProc (HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
switch (uiMsg)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
EndDialog (hWnd, TRUE);
break;

    case WM_INITDIALOG:
return TRUE;
}
return FALSE;
} // End AboutBoxProc

/*
**-----------------------------------------------------------------------------
**  Name:       OnAbout
**  Purpose:    Display About Dialog
**-----------------------------------------------------------------------------
*/

LRESULT OnAbout (HWND hWindow)
{
HINSTANCE hInstance;

if ((! hWindow) || (! IsWindow (hWindow)))
return 0L;

hInstance = (HINSTANCE) GetWindowLong (hWindow, GWL_HINSTANCE);
if (! hInstance)
return 0L;

    // Pause App
    OnPause (hWindow, TRUE);

    // Do About Dialog here
    DialogBox (hInstance, MAKEINTRESOURCE (IDD_ABOUT), hWindow, (DLGPROC)AboutBoxProc);

    // UnPause app
    OnPause (hWindow, FALSE);

    return 0L;
} // End OnAbout

/*
**-----------------------------------------------------------------------------
**  Name:       OnActivateApp
**  Purpose:    ActivateApp
**  Note:
**always let it fall through to Default window proc
**to set up keyboard focus properly, etc.
**-----------------------------------------------------------------------------
*/

LRESULT OnActivateApp (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
BOOL fActive = (BOOL)wParam;

// Get D3D Window pointer
LPD3DWindow lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);

    if (fActive)
    {
// Reactivating - Realize current palette
if (lpd3dWindow)
{
lpd3dWindow->turnActiveOn ();
lpd3dWindow->RealizePalette ();
}
    }
    else
    {
        //
        //  If we have been deactivated invalidate
        //  to show the paused display.
        //
if (lpd3dWindow)
{
lpd3dWindow->turnActiveOff ();
        InvalidateRect (hWindow, NULL, FALSE);
}

    }

    // Do default behavior
return DefWindowProc (hWindow, WM_ACTIVATEAPP, wParam, lParam);
} // End OnActivateApp

/*
**-----------------------------------------------------------------------------
**  Name:       OnClose
**  Purpose:    Shut down app
**-----------------------------------------------------------------------------
*/

LRESULT OnClose (HWND hWindow)
{
// Just Destroy the window for now
    DestroyWindow (hWindow);
return 0L;
} // End OnClose

/*
**-----------------------------------------------------------------------------
**  Name:       OnCommand
**  Purpose:    Handle User commands
**-----------------------------------------------------------------------------
*/

LRESULT OnCommand (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
HWND  hControl= (HWND)lParam;
    DWORD dwNotify= (DWORD)HIWORD(wParam);
    DWORD dwID      = (DWORD)LOWORD(wParam);

    if (hControl)
    {
//
    // Handle messages from controls here
        //
    }
    else
{
//
// Handle Menu messages here
//
switch (dwID)
{
case IDM_EXIT:
// Close down app
SendMessage (hWindow, WM_CLOSE, 0, 0);
return 0;

case IDM_ABOUT:
// Bring up About Dialog
OnAbout (hWindow);
break;

case IDM_CHANGE_DRIVERS:
// Bring up the "Change Driver" dialog
OnChangeDriver (hWindow, IDD_CHANGE_DRIVER);
break;

default:
// Unknown Menu Command
break;
}
}

    // Let Default Window Proc handle it for us.
    return DefWindowProc (hWindow, WM_COMMAND, wParam, lParam);
} // End OnCommand

/*
**-----------------------------------------------------------------------------
**  Name:       OnCreate
**  Purpose:    Initialization of any Window objects
**-----------------------------------------------------------------------------
*/

LRESULT OnCreate (HWND hWindow)
{
    // Success
    return 0L;
} // End OnCreate

/*
**-----------------------------------------------------------------------------
**  Name:       OnDestroy
**  Purpose:    Cleanup and shut down app
**-----------------------------------------------------------------------------
*/

LRESULT OnDestroy (HWND hWindow)
{
    LPD3DWindow lpd3dWindow;

    //Delete associated D3DWindow from this window
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, 0L);
if (lpd3dWindow)
lpd3dWindow->Fini ();
SetWindowLong (hWindow, GWL_USERDATA, 0L);

    // Tell windows to QUIT!
    PostQuitMessage (0);
    return 0L;
} // End OnDestroy

/*
**-----------------------------------------------------------------------------
**  Name:       OnDisplayChange
**  Purpose:    Cleanup and shut down app
**-----------------------------------------------------------------------------
*/

LRESULT OnDisplayChange (HWND hWindow)
{
HRESULThResult;
    LPD3DWindow lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

// Change Primary surface to new desktop
hResult = lpd3dWindow->ChangeDesktop ();
if (FAILED (hResult))
return 0L;

// Success
    return 0L;
} // End OnDestroy

/*
**-----------------------------------------------------------------------------
**  Name:       OnEraseBackground
**  Purpose:    Erase Window background
**  Notes:
**1.  The only time we need to do this is when
**the rendering surface is valid yet smaller than the client area
**any other situation and we are re-painting the entire client area.
**so we don't need to erase the background
**-----------------------------------------------------------------------------
*/

LRESULT OnEraseBackground (HWND hWindow, WPARAM wParam, LPARAM lParam)
{
    LPD3DWindow lpd3dWindow;
RECT        rTemp, rSurf, rClient;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

if ((lpd3dWindow->isValid ()) && (! lpd3dWindow->isPaused ()))
{
GetClientRect (hWindow, &rClient);
lpd3dWindow->GetSurfaceRect (rSurf);

// Does surface rect contain the client rectangle?
if (IntersectRect (&rTemp, &rClient, &rSurf))
{
if (! EqualRect (&rTemp, &rClient))
{
// No, then allow system to erase the background for us
return DefWindowProc (hWindow, WM_ERASEBKGND, wParam, lParam);
}
}
}

// Success
// Tell system we erased background, even though we didn't
    return 1L;
} // End OnEraseBackground

/*
**-----------------------------------------------------------------------------
**  Name:       OnGetMinMaxInfo
**  Purpose:    Set Minimum, Maximum Window sizes
**-----------------------------------------------------------------------------
*/

LRESULT OnGetMinMaxInfo (HWND hWindow, LPMINMAXINFO lpmmi)
{
RECT rMin;
DWORD dwStyle, dwStyleEx;
HMENU hMenu;
BOOL fMenu;
    LPD3DWindow  lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

// Get Window Style
dwStyle   = GetWindowLong (hWindow, GWL_STYLE);
dwStyleEx = GetWindowLong (hWindow, GWL_EXSTYLE);
hMenu  = GetMenu (hWindow);
fMenu     = (hMenu ? TRUE : FALSE);

rMin.left= 0;
rMin.top= 0;
rMin.right  = MIN_WIN_SIZE_X;
rMin.bottom= MIN_WIN_SIZE_Y;

AdjustWindowRectEx (&rMin, dwStyle, fMenu, dwStyleEx);

 lpmmi->ptMinTrackSize.x = rMin.right - rMin.left;
lpmmi->ptMinTrackSize.y = rMin.bottom  - rMin.top;

return 0L;
} // End OnGetMinMaxInfo

/*
**-----------------------------------------------------------------------------
**  Name:       OnIdle
**  Purpose:    Animate while system is idle
**-----------------------------------------------------------------------------
*/

void OnIdle (HWND hWindow)
{
    HRESULT hResult;
    LPD3DWindow  lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

    //
    // Only animate if we are the foreground app, we aren't suspended
    // and we have completed initialization.
    //
    //
    // Only animate if we are the foreground app, we aren't suspended
    // and we have completed initialization.
    //
if (! lpd3dWindow->isActive () ||
(lpd3dWindow->isPaused ()) ||
(! lpd3dWindow->isValid ()))
return;

hResult = lpd3dWindow->DrawFrame ();
    if (FAILED (hResult))
return;

    return;
} // End OnIdle

/*
**-----------------------------------------------------------------------------
**  Name:       OnMove
**  Purpose:    Move window
**-----------------------------------------------------------------------------
*/

LRESULT OnMove (HWND hWindow, INT nX, INT nY)
{
    HRESULT hResult;
    LPD3DWindow lpd3dWindow;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

    /*
     * No action if the device has not yet been created or if we are
     * suspended.
     */
    if (! lpd3dWindow->isPaused() || ! lpd3dWindow->isValid ())
    {
// Keep the D3D info in synch with the change
hResult = lpd3dWindow->Move (nX, nY);
        if (FAILED (hResult))
            return 0L;

        // Repaint the client area.
        hResult = lpd3dWindow->DrawFrame ();
        if (FAILED (hResult))
            return 0L;
    }

    // Success
    return 0L;
} // End OnMove

/*
**-----------------------------------------------------------------------------
**  Name:       OnPaint
**  Purpose:Paint the window
**-----------------------------------------------------------------------------
*/

LRESULT OnPaint (HWND hWindow, HDC hdc, LPPAINTSTRUCT lpps)
{
    HRESULT hResult;
LPD3DWindow lpd3dWindow;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
{
        PaintPaused (hWindow, hdc);
return 0L;
}

    if (lpd3dWindow->isActive () &&
!lpd3dWindow->isPaused () &&
lpd3dWindow->isValid())
    {
        //
        // NOTE: DrawFrame() re-renders the scene as well as blitting the
        // result to the primary. As all we really want to do here is
        // repaint the client area we don't really need to re-render -
        // just re-blit. For this simple sample this inefficiency
        // doesn't matter but for real applications not re-rendering
        // may be a useful optimization.
        //
        hResult = lpd3dWindow->DrawFrame ();
        if (FAILED (hResult))
            return 0L;
    }
    else
    {
        //
        // Show the suspended image if we are not active,
        // or paused, or if we have not yet created the device.
        //
        PaintPaused (hWindow, hdc);
    }

    return 0L;
} // End OnPaint

/*
**-----------------------------------------------------------------------------
**  Name:OnPause
**  Purpose:Pause/Unpause app
**-----------------------------------------------------------------------------
*/

void OnPause (HWND hWindow, BOOL fPause)
{
LPD3DWindow lpd3dWindow;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

lpd3dWindow->Pause (fPause);
} // End OnPause

/*
**-----------------------------------------------------------------------------
**  Name:       OnSize
**  Purpose:Resize main window
**-----------------------------------------------------------------------------
*/

LRESULT OnSize (HWND hWindow, INT nW, INT nH)
{
    HRESULT       hResult;
    LPD3DWindow  lpd3dWindow;

// Get D3D window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return 0L;

    // Check if we are paused
    if (! lpd3dWindow->isPaused ())
    {
// Resize D3D surfaces
hResult = lpd3dWindow->Resize (nW, nH);
        if (FAILED (hResult))
            return 0L;

        //
        // Render at the new size and show the results
        // in the main window's client area.
        //
        hResult = lpd3dWindow->DrawFrame ();
        if (FAILED (hResult))
            return 0L;
    }

    // Success
    return 0L;
} // End OnSize

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DInit
**  Purpose:Notification that associated D3D Window object
**has successfully initialized
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DInit (HWND hWindow, LPD3DWindow lpd3dWindow)
{
if ((! lpd3dWindow) || (! lpd3dWindow->isValid ()))
{
// Error, something went wrong
return 0L;
}

// Save the pointer to the D3D Window object
SetWindowLong (hWindow, GWL_USERDATA, (long)(void *)lpd3dWindow);

// Make sure we are properly marked as active/inactive
HWND hActive = GetActiveWindow ();
if (hActive == hWindow)
lpd3dWindow->turnActiveOn ();
else
lpd3dWindow->turnActiveOff ();

// Success
return 0L;
} // End OnD3DInit

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DFini
**  Purpose:Notification that the D3D window object
**is cleaning up.
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DFini (HWND hWindow, LPD3DWindow lpd3dWindow)
{
// Set our window pointer to NULL
// so we don't attempt to misuse it later.
SetWindowLong (hWindow, GWL_USERDATA, 0L);

// Success
return 0L;
} // End OnD3DFini

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeDriver
**  Purpose:Notification that our associated D3D Window object
**has changed it's DD Driver.
**  Notes:This is a good time to update our Menus
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DChangeDriver (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeDriver

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeMode
**  Purpose:Notification that our associated D3D Window object
**has changed it's mode.
**  Notes:
**1. This is probably caused by the user changing the Desktop mode
**2. This is a good time to update our Menus
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DChangeMode (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeMode

/*
**-----------------------------------------------------------------------------
**  Name:       OnD3DChangeDevice
**  Purpose:Notification that our associated D3D Window object
**has changed it's D3D device.
**  Notes:This is a good time to update our Menus
**-----------------------------------------------------------------------------
*/

LRESULT OnD3DChangeDevice (HWND hWindow)
{
// Success
return 0L;
} // End OnD3DChangeDevice

/*
**-----------------------------------------------------------------------------
**  Name:       PaintPaused
**  Purpose:Paint the window as Paused
**  Notes:
**
**  1.  We show the paused state by drawing a text string
**      in the client area of the window.
**
**-----------------------------------------------------------------------------
*/

void PaintPaused (HWND hWindow, HDC hdc)
{
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    COLORREF crOldTextColor;
    INT      oldMode;
    INT      x;
    INT      y;
    SIZE     size;
    RECT     rect;
    INT      nStrLen;

    //
    // Black background.
    //
    hOldPen   = (HPEN)SelectObject (hdc, GetStockObject (NULL_PEN));
    hOldBrush = (HBRUSH)SelectObject (hdc, GetStockObject (BLACK_BRUSH));

    //
    // White text.
    //
    oldMode = SetBkMode (hdc, TRANSPARENT);
    crOldTextColor = SetTextColor (hdc, RGB(255, 255, 255));

    //
    GetClientRect (hWindow, &rect);

    //
    // Clear the client area.
    //
    Rectangle (hdc, rect.left, rect.top, rect.right + 1, rect.bottom + 1);

    //
    // Draw the string centered in the client area.
    //
    nStrLen = strlen (g_szPaused);
    GetTextExtentPoint32 (hdc, g_szPaused, nStrLen, &size);
    x = (rect.right  - size.cx) / 2;
    y = (rect.bottom - size.cy) / 2;
    TextOut (hdc, x, y, g_szPaused, nStrLen);

    // Cleanup
    SetTextColor (hdc, crOldTextColor);
    SetBkMode (hdc, oldMode);

    SelectObject (hdc, hOldBrush);
    SelectObject (hdc, hOldPen);
} // End PaintPaused

/*
**-----------------------------------------------------------------------------
**Dialog Functions
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Name:       DlgDriversInit
**  Purpose:    Set up Drivers for dialog
**  Notes:Shows list of current Drivers
**-----------------------------------------------------------------------------
*/

BOOL DlgDriversInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;
LPDDDrvInfolpCurr, lpNext;
DWORDdwIndex;
LPGUIDlpGuidDD;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver = lpChange->lpDrvNew;

// Validate Driver
if (! lpDriver)
lpGuidDD = NULL;
else
lpGuidDD = lpDriver->GetGuid ();
lpDriver = ValidateDriver (lpGuidDD);
if (! lpDriver)
return FALSE;

// Dump Driver list to Combo Box
dwIndex = 0;
lpCurr = DDDrvMgr::g_lpDriverRoot;
while (lpCurr)
{
TCHAR szBuff[80];
DWORD dwNew;
LPGUID lpGuidDD;

lpNext = lpCurr->lpNext;

// Hack: Do a Quick check for the 3Dfx Voodoo driver
// which doesn't support windowed mode
lpGuidDD = lpCurr->GetGuid ();
if (lpGuidDD)
{
if (*lpGuidDD == guid3DfxVoodoo)
{
// Don't add 3Dfx Voodoo driver.
// This is a windowed app and it isn't supported
lpCurr = lpNext;
continue;
}
}

if (lpCurr->isPrimary ())
wsprintf (szBuff, TEXT("%s (Primary)"), lpCurr->szName);
else
wsprintf (szBuff, TEXT("%s"), lpCurr->szName);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to driver for this item
SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpCurr);

// Is it the current Driver
if (lpDriver == lpCurr)
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}

lpCurr = lpNext;
}

// Success
return TRUE;
} // End DlgDriversInit

/*
**-----------------------------------------------------------------------------
**  Name:       DlgDevicesInit
**  Purpose:    Set up Devices for dialog
**  Notes:Shows list of current Devices
**-----------------------------------------------------------------------------
*/

BOOL DlgDevicesInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;
LPD3DDevInfolpDevice;
LPD3DDevInfolpCurr, lpNext;
DWORDdwIndex;
LPGUIDlpGuidD3D;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver = lpChange->lpDrvNew;
if (! lpDriver)
return FALSE;

lpDevice = lpChange->lpDevNew;

// Validate Device
if (! lpDevice)
lpGuidD3D = NULL;
else
lpGuidD3D = &(lpDevice->guid);
lpDevice = ValidateDevice (lpDriver, lpGuidD3D, NULL);
if (! lpDevice)
return FALSE;

// Dump Device list to Combo Box
dwIndex = 0;
lpCurr = lpDriver->lpDeviceRoot;
while (lpCurr)
{
TCHAR szBuff[80];
DWORD dwNew;

lpNext = lpCurr->lpNext;

// Get Device String
wsprintf (szBuff, TEXT("%s"), lpCurr->szName);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_DEVICES, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to device for this item
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpCurr);

// Is it the current device
if (lpDevice == lpCurr)
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}

lpCurr = lpNext;
}

// Success
return TRUE;
} // End DlgDevicesInit

/*
**-----------------------------------------------------------------------------
**  Name:       CompareModes
**  Purpose:    Comparision function used with Quick sort
**to get sorted list of modes
**-----------------------------------------------------------------------------
*/

int _cdecl CompareModes (const void * element1, const void * element2)
{
LPDDModeInfo lpMode1;
LPDDModeInfo lpMode2;
DWORD w1, h1, bpp1, refresh1;
DWORD w2, h2, bpp2, refresh2;
int   iCompare = 0;

    lpMode1 = *((LPDDModeInfo *)element1);
    lpMode2 = *((LPDDModeInfo *)element2);

// Handle Null pointers
if ((! lpMode1) && (! lpMode2))
return 0;
if ((lpMode1) && (! lpMode2))
return -1;
if ((! lpMode2) && (lpMode2))
return 1;

// Get Mode Data
lpMode1->GetMode (w1, h1, bpp1, refresh1);
lpMode2->GetMode (w2, h2, bpp2, refresh2);

// Sort first on BPP then width then height
    if (bpp1 < bpp2)
iCompare = -1;
    else if (bpp1 > bpp2)
iCompare = 1;
    else if (w1 < w2)
iCompare = -1;
    else if (w1 > w2)
iCompare = 1;
    else if (h1 < h2)
iCompare = -1;
    else if (h1 > h2)
iCompare = 1;

// Equality
    return iCompare;
} // End CompareModes

/*
**-----------------------------------------------------------------------------
**  Name:       DlgModesInit
**  Purpose:    Set up Modes for dialog
**  Notes:Shows list of current modes, compatible with the current
**device.  This list is displayed in sorted order (bpp, w, h)
**-----------------------------------------------------------------------------
*/

BOOL DlgModesInit (HWND hDlg)
{
LPChangeDDInfolpChange;
LPDDDrvInfolpDriver;

LPD3DDevInfolpDevice;
LPDDModeInfolpModeCurr, lpModeRoot, lpCurr, lpNext;
DWORDcModes;
DWORDcbSize;
DWORDdwIndex;
LPDDModeInfo *lpModes;

// Check Parameters
lpChange = (LPChangeDDInfo)(void *)GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
return FALSE;

lpDriver   = lpChange->lpDrvNew;
lpDevice   = lpChange->lpDevNew;
lpModeCurr = lpChange->lpModeNew;

if (! lpDriver)
return FALSE;

if (! lpDevice)
{
lpDevice = ValidateDevice (lpDriver, NULL, NULL);
if (! lpDevice)
return FALSE;
}

lpModeRoot = lpDriver->lpModeRoot;
if (! lpModeRoot)
return FALSE;

cModes = lpDriver->countModes ();
if (! cModes)
return FALSE;

if (! lpModeCurr)
{
lpModeCurr = ValidateMode (lpDriver, 640, 480, 16, 0, lpDevice);
}

// Get memory for Mode list
cbSize = cModes * sizeof (LPDDModeInfo);
lpModes = (LPDDModeInfo *) malloc (cbSize);
if (! lpModes)
return FALSE;

// Create Mode List
dwIndex = 0;
lpCurr = lpModeRoot;
while (lpCurr)
{
lpNext = lpCurr->lpNext;

lpModes[dwIndex] = lpCurr;

dwIndex++;

lpCurr = lpNext;
}

// Sort Mode list
    qsort ((void *)lpModes, (size_t)cModes, sizeof(LPDDModeInfo), CompareModes);

// Dump Mode list to Combo Box
for (dwIndex = 0; dwIndex < cModes; dwIndex++)
{
// Make sure mode is supported by D3D device
if ((lpModes[dwIndex]) &&
(lpModes[dwIndex]->ModeSupported (lpDevice)))
{
TCHAR szBuff[80];
DWORD w, h, bpp, refresh;
DWORD dwNew;

lpModes[dwIndex]->GetMode (w, h, bpp, refresh);

// Set up Mode String
if (refresh)
wsprintf (szBuff, TEXT("%4d x %4d x %4d (%4d Hz)"),
      w, h, bpp, refresh);
else
wsprintf (szBuff, TEXT("%4d x %4d x %4d"),
      w, h, bpp);

// Add String to Combo Box
dwNew = SendDlgItemMessage (hDlg, IDC_MODES, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)szBuff);

// Set up pointer to Mode Info for this item
SendDlgItemMessage (hDlg, IDC_MODES, CB_SETITEMDATA, (WPARAM)dwNew, (LPARAM)(void *)lpModes[dwIndex]);

// Is it the current Mode
if (lpModeCurr == lpModes[dwIndex])
{
// Set as our current selection
SendDlgItemMessage (hDlg, IDC_MODES, CB_SETCURSEL, (WPARAM)dwNew, 0L);
}
}
}

// Cleanup Memory
free (lpModes);
lpModes = NULL;

// Success
return TRUE;
} // End DlgModesInit

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetDriver
**  Purpose:    Get current driver selection
**-----------------------------------------------------------------------------
*/

LPDDDrvInfo DlgGetDriver (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_DRIVERS, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to driver
LPDDDrvInfo lpDriver = (LPDDDrvInfo) SendDlgItemMessage (hDlg,
       IDC_DRIVERS,
 CB_GETITEMDATA,
 (WPARAM)dwIndex,
 (LPARAM)0);
return lpDriver;
}

// Failure
return NULL;
} // End DlgGetDriver

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetDevice
**  Purpose:    Get current device selection
**-----------------------------------------------------------------------------
*/

LPD3DDevInfo DlgGetDevice (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_DEVICES, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to device
LPD3DDevInfo lpDevice = (LPD3DDevInfo) SendDlgItemMessage (hDlg,
         IDC_DEVICES,
   CB_GETITEMDATA,
   (WPARAM)dwIndex,
   (LPARAM)0);
return lpDevice;
}

// Failure
return NULL;
} // End DlgGetDevice

/*
**-----------------------------------------------------------------------------
**  Name:       DlgGetMode
**  Purpose:    Get current mode selection
**-----------------------------------------------------------------------------
*/

LPDDModeInfo DlgGetMode (HWND hDlg)
{
DWORD dwIndex = SendDlgItemMessage (hDlg, IDC_MODES, CB_GETCURSEL, 0, 0);
if (dwIndex != CB_ERR)
{
// Get pointer to device
LPDDModeInfo lpMode = (LPDDModeInfo) SendDlgItemMessage (hDlg,
         IDC_MODES,
 CB_GETITEMDATA,
 (WPARAM)dwIndex,
 (LPARAM)0);
return lpMode;
}

// Failure
return NULL;
} // End DlgGetMode

/*
**-----------------------------------------------------------------------------
**  Name:       OnChangeDriver
**  Purpose:    Allows user to choose a new Driver from dialog
**Notes:User can also choose a new device and mode
**-----------------------------------------------------------------------------
*/

void OnChangeDriver (HWND hWindow, int idDialog)
{
HINSTANCEhInstance;
intfResult;
ChangeDDInfochangeInfo;
LPD3DWindowlpd3dWindow;

// Get application instance
hInstance = (HINSTANCE) GetWindowLong (hWindow, GWL_HINSTANCE);
if (! hInstance)
return;

// Get D3D Window pointer
lpd3dWindow =  (LPD3DWindow)GetWindowLong (hWindow, GWL_USERDATA);
if (! lpd3dWindow)
return;

// Setup Change info
changeInfo.lpDrvOrig= lpd3dWindow->GetDriverInfo ();
changeInfo.lpDrvNew= changeInfo.lpDrvOrig;

changeInfo.lpDevOrig= lpd3dWindow->GetDeviceInfo ();
changeInfo.lpDevNew= changeInfo.lpDevOrig;

changeInfo.lpModeOrig= lpd3dWindow->GetModeInfo ();
changeInfo.lpModeNew= changeInfo.lpModeOrig;

    // Pause App
    OnPause (hWindow, TRUE);

    // Do Change Mode Dialog here
    fResult = DialogBoxParam (hInstance, MAKEINTRESOURCE (idDialog),
  hWindow, (DLGPROC)ChangeDriverProc,
  (LPARAM)(void *)&changeInfo);

    // UnPause app
    OnPause (hWindow, FALSE);

// Did user request a change ?!?
if (fResult == TRUE)
{
LPGUID lpGuidDD, lpGuidD3D;

if (changeInfo.lpDrvOrig != changeInfo.lpDrvNew)
{
if (changeInfo.lpDrvNew)
lpGuidDD = changeInfo.lpDrvNew->GetGuid ();
else
lpGuidDD = NULL;

lpd3dWindow->ChangeDriver (lpGuidDD, changeInfo.lpDevNew);
}
else if (changeInfo.lpDevOrig != changeInfo.lpDevNew)
{
if (changeInfo.lpDevNew)
lpGuidD3D = &(changeInfo.lpDevNew->guid);
else
lpGuidD3D = NULL;

lpd3dWindow->ChangeDevice (lpGuidD3D);
}
}
} // End OnChangeDriver

/*
**-----------------------------------------------------------------------------
**  Name:       ChangeDriverProc
**  Purpose:    handles messages for Change Driver,
**D3D device, and mode dialog boxes
**-----------------------------------------------------------------------------
*/

BOOL CALLBACK ChangeDriverProc(
    HWND hDlg,// handle to dialog box
    UINT uiMsg,// message
    WPARAM wParam,// first message parameter
    LPARAM lParam) // second message parameter
{
BOOLfChanged;
LPChangeDDInfo  lpChange;
LPDDDrvInfolpDriver;
LPDDModeInfolpMode;
LPD3DDevInfolpDevice;

switch (uiMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
// Get Change Info
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);
if (! lpChange)
EndDialog (hDlg, FALSE);

fChanged = FALSE;

// Get New Driver
lpDriver = DlgGetDriver (hDlg);
if ((lpDriver) && (lpDriver != lpChange->lpDrvOrig))
{
fChanged = TRUE;
lpChange->lpDrvNew = lpDriver;
}
else
lpChange->lpDrvNew = lpChange->lpDrvOrig;

// Get New Device
lpDevice = DlgGetDevice (hDlg);
if ((lpDevice) && (lpDevice != lpChange->lpDevOrig))
{
fChanged = TRUE;
lpChange->lpDevNew = lpDevice;
}
else
lpChange->lpDevNew = lpChange->lpDevOrig;

// Get New Mode
lpMode = DlgGetMode (hDlg);
if ((lpMode) && (lpMode != lpChange->lpModeOrig))
{
fChanged = TRUE;
lpChange->lpModeNew = lpMode;
}
else
lpChange->lpModeNew = lpChange->lpModeOrig;

// Return success/failure
EndDialog (hDlg, fChanged);
break;

case IDCANCEL:
EndDialog (hDlg, FALSE);
break;

case IDC_DRIVERS:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current driver
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the Driver
lpDriver = DlgGetDriver (hDlg);
if ((lpDriver) && (lpDriver != lpChange->lpDrvNew))
{
lpChange->lpDrvNew = lpDriver;
lpChange->lpDevNew = NULL;// Pick a new device
lpChange->lpModeNew = NULL;// Pick a new mode

// Update the Device list
SendDlgItemMessage (hDlg, IDC_DEVICES, CB_RESETCONTENT, 0, 0);
DlgDevicesInit (hDlg);

// Update the Mode list
SendDlgItemMessage (hDlg, IDC_MODES, CB_RESETCONTENT, 0, 0);
DlgModesInit (hDlg);
}
break;
}
break;

case IDC_DEVICES:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current device
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the device
lpDevice = DlgGetDevice (hDlg);
if ((lpDevice) && (lpDevice != lpChange->lpDevNew))
{
lpChange->lpDevNew = lpDevice;

// Update the Mode list
SendDlgItemMessage (hDlg, IDC_MODES, CB_RESETCONTENT, 0, 0);
DlgModesInit (hDlg);
}
break;
}
break;

case IDC_MODES:
switch (HIWORD (wParam))
{
case CBN_SELENDOK:
// User has changed the current mode
lpChange = (LPChangeDDInfo) GetWindowLong (hDlg, DWL_USER);

// Check if user has changed the Mode
lpMode = DlgGetMode (hDlg);
if ((lpMode) && (lpMode != lpChange->lpModeNew))
{
lpChange->lpModeNew = lpMode;
}
break;
}
break;
}
break;

    case WM_INITDIALOG:
// Save pointer to ChangeInfo
SetWindowLong (hDlg, DWL_USER, (long)lParam);

// Set up the current driver, device, and mode lists
if (GetDlgItem (hDlg, IDC_DRIVERS))
if (! DlgDriversInit (hDlg))
return FALSE;

if (GetDlgItem (hDlg, IDC_DEVICES))
if (! DlgDevicesInit (hDlg))
return FALSE;

if (GetDlgItem (hDlg, IDC_MODES))
if (! DlgModesInit (hDlg))
return FALSE;

// Successful init
return TRUE;
}
return FALSE;
} // End ChangeDriverProc

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/


WINPROC.H

#ifndef WINPROC_H
#define WINPROC_H
/*
**-----------------------------------------------------------------------------
**  File:       WinProc.h
**  Purpose:    Sample Window Procedure code
**  Notes:
**
**  Copyright (c) 1995 - 1997 by Microsoft, all rights reserved
**-----------------------------------------------------------------------------
*/

/*
**-----------------------------------------------------------------------------
**  Include files
**-----------------------------------------------------------------------------
*/

#include "Common.h"
#include "D3DWin.h"

/*
**-----------------------------------------------------------------------------
**  Function Prototypes
**-----------------------------------------------------------------------------
*/

// Windows routines
LRESULT CALLBACK D3DWindowProc (HWND hWindow, UINT uiMessage,
                                WPARAM wParam, LPARAM lParam);

// Window Message helper routines
LRESULT OnAbout (HWND hWindow);
LRESULT OnActivateApp (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnClose (HWND hWindow);
LRESULT OnCommand (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnCreate (HWND hWindow);
LRESULT OnDestroy (HWND hWindow);
LRESULT OnDisplayChange (HWND hWindow);
LRESULT OnEraseBackground (HWND hWindow, WPARAM wParam, LPARAM lParam);
LRESULT OnGetMinMaxInfo (HWND hWindow, LPMINMAXINFO lpmmi);
void    OnIdle (HWND hWindow);
LRESULT OnMove (HWND hWindow, INT nX, INT nY);
void    OnPause (HWND hWindow, BOOL fPause);
LRESULT OnPaint (HWND hWindow, HDC hdc, LPPAINTSTRUCT lpps);
LRESULT OnSize (HWND hWindow, INT nW, INT nH);

// D3D Notification routines
LRESULTOnD3DInit (HWND hWindow, LPD3DWindow lpd3dWindow);
LRESULTOnD3DFini (HWND hWindow, LPD3DWindow lpd3dWindow);
LRESULTOnD3DChangeDriver (HWND hWindow);
LRESULTOnD3DChangeMode (HWND hWindow);
LRESULTOnD3DChangeDevice (HWND hWindow);

// Misc Routines
void    PaintPaused (HWND hWindow, HDC hdc);

/*
**-----------------------------------------------------------------------------
**  End of File
**-----------------------------------------------------------------------------
*/
#endif // End WINPROC_H


README.TXT

Quat
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Using quaternions to interpolate between two vectors in space.


QUAT.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: quat.c
 *
 ***************************************************************************/

/*
 * demonstration of representation of orientation using quaternions
 */

#include "rmdemo.h"

#define PI2 6.28318
#define XPOS 2.0
#define INTERPOLATE_STEP 0.05

static D3DRMQUATERNION q;

static int view_width, view_height;

void ReadMouse(int* buttons, int* x, int* y);

void CDECL
UserControl(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    int mx, my, mb;
    LPDIRECT3DRMFRAME scene;
    D3DVALUE angle;
    D3DVECTOR vx = {D3DVAL(1), D3DVAL(0), D3DVAL(0)},
     vy = {D3DVAL(0), D3DVAL(1), D3DVAL(0)};
    D3DRMQUATERNION qx, qy;
    D3DRMMATRIX4D mat;

    arg = arg;

    ReadMouse(&mb, &mx, &my);
    angle = D3DMultiply((-D3DVAL(0.5) + D3DDivide(D3DVAL(my),
                       D3DVAL(view_height))), D3DVAL(PI2));
    D3DRMQuaternionFromRotation(&qx, &vx, angle);
    angle = D3DMultiply((-D3DVAL(0.5) + D3DDivide(D3DVAL(mx),
                       D3DVAL(view_width))), D3DVAL(PI2));
    D3DRMQuaternionFromRotation(&qy, &vy, angle);
    D3DRMQuaternionMultiply(&q, &qx, &qy);
    D3DRMMatrixFromQuaternion(mat, &q);
    frame->lpVtbl->AddTransform(frame, D3DRMCOMBINE_REPLACE, mat);
    frame->lpVtbl->GetScene(frame, &scene);
    frame->lpVtbl->SetPosition(frame, scene, D3DVAL(-XPOS), D3DVAL(0),
                               D3DVAL(0));

    RELEASE(scene);
}

void CDECL
Follow(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMFRAME scene;
    int mx, my, mb;
    static D3DRMQUATERNION qstart = {D3DVAL(1), {D3DVAL(0), D3DVAL(0),D3DVAL(0)}},
                                 qend;
    static D3DVALUE alpha = D3DVAL(0);

    arg = arg;

    ReadMouse(&mb, &mx, &my);
    if (mb && alpha == D3DVAL(0)) {
qend = q;
alpha = D3DVAL(INTERPOLATE_STEP);
    }
    if (alpha > D3DVAL(0)) {
D3DRMQUATERNION interp;
D3DRMMATRIX4D mat;

D3DRMQuaternionSlerp(&interp, &qstart, &qend, alpha);
D3DRMMatrixFromQuaternion(mat, &interp);

if (alpha >= D3DVAL(1)) {
    D3DRMMatrixFromQuaternion(mat, &qend);
    alpha = D3DVAL(0);
    qstart = qend;
} else
    alpha += D3DVAL(INTERPOLATE_STEP);

frame->lpVtbl->AddTransform(frame, D3DRMCOMBINE_REPLACE, mat);
frame->lpVtbl->GetScene(frame, &scene);
frame->lpVtbl->SetPosition(frame, scene, D3DVAL(XPOS), D3DVAL(0), D3DVAL(0));
RELEASE(scene);
    }
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_FLAT;
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMFRAME user = NULL;
    LPDIRECT3DRMFRAME follower = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMLIGHT l1 = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    HRESULT rval;

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;

    view_width = view->lpVtbl->GetWidth(view);
    view_height = view->lpVtbl->GetHeight(view);

    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetOrientation(lights, scene,
   D3DVAL(-1), D3DVAL(-1), D3DVAL(0), D3DVAL(0), D3DVAL(1), D3DVAL(0))))
   goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.8),
                                  D3DVAL(0.6), D3DVAL(0.7), &l1)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, l1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &l2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l2)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "dropship.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load dropship.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->Scale(builder, D3DVAL(0.1),
                                     D3DVAL(0.08), D3DVAL(0.1))))
     goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0), D3DVAL(0),
                                D3DVAL(-12))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &user)))
goto generic_error;
    if (FAILED(user->lpVtbl->AddVisual(user, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &follower)))
goto generic_error;
    if (FAILED(follower->lpVtbl->AddVisual(follower, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;

    if (FAILED(follower->lpVtbl->SetPosition(follower, scene, D3DVAL(XPOS), D3DVAL(0),
                                  D3DVAL(0))))
  goto generic_error;

    if (FAILED(user->lpVtbl->AddMoveCallback(user, UserControl, NULL)))
goto generic_error;
    if (FAILED(follower->lpVtbl->AddMoveCallback(follower, Follow, NULL)))
goto generic_error;

    RELEASE(lights);
    RELEASE(user);
    RELEASE(follower);
    RELEASE(mesh);
    RELEASE(l1);
    RELEASE(l2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(lights);
    RELEASE(user);
    RELEASE(follower);
    RELEASE(builder);
    RELEASE(mesh);
    RELEASE(l1);
    RELEASE(l2);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bNoTextures = TRUE;
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Quaternion Direct3DRM Example");
}


README.TXT

Rockem 3D Sample Game
---------------------

This game demonstrates many of the features of Direct X, including DirectDraw,
Direct 3D & DirectSound. It also demonstrates DirectSound3D.
It is important to note that this game is not optimised for raw speed.  It
is meant to give a clear demonstration of how the DirectX technologies can
be used.

When you build this sample, it will create a rockem3d.exe file in either
the retail or debug subdirectories, depending on which version you built.
However, you must then copy the .exe up one directory to the Rockem sample
root (i.e. c:\dxsdk\sdk\samples\rockem).  This is because rockem also needs
to find its .wav files and other resources from that dir.

The commands which this game recognizes are listed on the opening screen.

    ESC            - Quit
    F1              - Camera side view
    F2              - Camera behind view
    F3              - Camera head view
    F5              - Toggle stats
    F6              - Go up a video mode
    F7              - Go down a video mode
    END             - Highest video mode
    HOME            - Lowest video mode
    Control - M     - Toggle music
    Control - S     - Toggle sound
    Control         - Block
    SpaceBar        - Attack
    Up Arrow        - Move forward
    Down Arrow      - Move backwards


CONTROL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: control.cpp
 *
 ***************************************************************************/

// Includes....
#include "rm.h"
#include "control.h"
#include "directx.h"

// Defines....
#define MOVE_NORMAL                     D3DVAL(10)
#define MOVE_FAST                       D3DVAL(20)

#define MIN_DIST_TO_OPPONENT            D3DVAL(100)

#define PLAYER_MOVE_FORWARD             VK_UP
#define PLAYER_MOVE_BACKWARD            VK_DOWN
#define PLAYER_BLOCK                    VK_CONTROL
#define PLAYER_ATTACK                   VK_SPACE

#define CAM_BOTH_IN_VIEW                VK_F1
#define CAM_OVER_THE_SHOULDER           VK_F2
#define CAM_PILOT                       VK_F3

#define NUM_BOB_FRAMES                  D3DVAL(30)
#define NUM_DEAD_FRAMES                 D3DVAL(66)
#define NUM_PUNCH_FRAMES                D3DVAL(29)
#define NUM_BEEN_HIT_FRAMES             D3DVAL(14)
#define NUM_BLOCKING_FRAMES             D3DVAL(16)
#define NUM_VICTORY_FRAMES              D3DVAL(55)

#define BOB_START                       D3DVAL(1)
#define DEAD_START                      D3DVAL(94)
#define DEAD_HIT_GROUND                 D3DVAL(115)
#define PUNCH_START                     D3DVAL(31)
#define PUNCH_ARM_COCKED                D3DVAL(50)
#define BLOCK_START                     D3DVAL(77)
#define BEEN_HIT_START                  D3DVAL(62)
#define HEAD_RETURNING                  D3DVAL(68)
#define VICTORY_START                   D3DVAL(161)

#define EDGE_LEFT                       D3DVAL(-600)
#define EDGE_RIGHT                      D3DVAL(600)

// Globals....

// States
AppState                g_appState = DOING_INTRO;
PlayerState             g_oppState = CAUTIOUS;
CameraState             g_camState = BOTH_IN_VIEW;

PlayerActionState       g_player1State = BOBBING;
PlayerActionState       g_player2State = BOBBING;

AnimArgs                g_player1AnimArgs;
AnimArgs                g_player2AnimArgs;

BOOL                    g_bPlayer1Attacking = FALSE;
BOOL                    g_bPlayer1Blocking  = FALSE;

DWORD                   g_player1health = 100;
DWORD                   g_player2health = 100;

// Timed lengths for each animation in milliseconds
#define BOB_TIME_MS     D3DVAL(500)
#define PUNCH_TIME_MS   D3DVAL(600)
#define BLOCK_TIME_MS   D3DVAL(500)
#define HIT_TIME_MS     D3DVAL(500)
#define DEAD_TIME_MS    D3DVAL(4000)
#define VICTORY_TIME_MS D3DVAL(3000)

// Timing deltas, used for scaling animation to frame rate
D3DVALUE                g_bobDelta     = NUM_BOB_FRAMES / BOB_TIME_MS;
D3DVALUE                g_attackDelta  = NUM_PUNCH_FRAMES / PUNCH_TIME_MS;
D3DVALUE                g_blockDelta   = NUM_BLOCKING_FRAMES / BLOCK_TIME_MS;
D3DVALUE                g_hitDelta     = NUM_BEEN_HIT_FRAMES / HIT_TIME_MS;
D3DVALUE                g_deadDelta    = NUM_DEAD_FRAMES / DEAD_TIME_MS;
D3DVALUE                g_victoryDelta = NUM_DEAD_FRAMES / DEAD_TIME_MS;

// Externals....
extern LPDIRECT3DRM             g_lpD3DRM;
extern LPDIRECT3DRMFRAME        g_lpScene;
extern LPDIRECT3DRMFRAME        g_lpCamera;
extern LPDIRECT3DRMFRAME        g_lpPlayer1;
extern LPDIRECT3DRMFRAME        g_lpPlayer1HeadFrame;
extern LPDIRECT3DRMFRAME        g_lpPlayer2;
extern LPDIRECT3DRMFRAME        g_lpPlayer2HeadFrame;
extern LPDIRECT3DRMFRAME        g_lpTmp;
extern LPDIRECT3DRMANIMATION    g_lpAnim;
extern LPDIRECT3DRMMESHBUILDER  g_lpRedDebris;
extern LPDIRECT3DRMMESHBUILDER  g_lpBlueDebris;
extern Debris                   g_debris[NUM_DEBRIS];
extern LPDIRECTSOUND3DLISTENER  g_lpDs3dListener; // Defined in DIRECTX.CPP

//----------------------------------------------------------------------
//
// Function     : IsKeyDown()
//
// Purpose      : Returns TRUE if specified key is being pressed
//
//----------------------------------------------------------------------

BOOL IsKeyDown(int virtKeyCode)
{
    if (GetAsyncKeyState(virtKeyCode) & 0x8000) return TRUE;

    return FALSE;
}

//----------------------------------------------------------------------
//
// Function     : Player1AnimationCallback()
//
// Purpose      : Animation call back for player 1
//
//----------------------------------------------------------------------

void CDECL Player1AnimationCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta)
{
    D3DVALUE time = g_player1AnimArgs.time;
    D3DVECTOR player1pos;
    D3DVECTOR player2pos;

    // Booleans to help with sound playing
    static BOOL bHitGround              = FALSE;
    static BOOL bPlayedWhoosh   = FALSE;

    // Get the players positions
    g_lpPlayer1->GetPosition(g_lpScene, &player1pos);
    g_lpPlayer2->GetPosition(g_lpScene, &player2pos);

    // Compute distance between players
    D3DVALUE curDist = player2pos.z - player1pos.z;

    // Do something based upon the state of player 1
    switch (g_player1State)
    {
        case BOBBING :
        {
            // Forward the bobbing animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);
            time += (g_bobDelta * delta);

            // Reset if animation has ended
            if (time > BOB_START + NUM_BOB_FRAMES) time = BOB_START;

            // Record the new time
            g_player1AnimArgs.time = time;
        }
        break;

        case PUNCHING :
        {
            // Forward the punching animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);
            time += (g_attackDelta * delta);

            // Play a whoosh sound if player 1's arm has gone back and is about to punch
            if ((time > PUNCH_ARM_COCKED) && (!bPlayedWhoosh))
            {
                // Play the whoosh
                PlaySoundDS(rand() % 2 == 0 ? WHOOSH1 : WHOOSH2,player1pos);
                bPlayedWhoosh = TRUE;
            }

            // If the punch has played, see if we hit the opponent
            if (time > PUNCH_START + NUM_PUNCH_FRAMES)
            {
                // Reset player 1's state to BOBBING
                time               = BOB_START;
                g_player1State = BOBBING;
                bPlayedWhoosh  = FALSE;

                // Play a servo sound
                PlaySoundDS(SERVO_DOWN_1,player1pos);

                // Now, decide whether we have hit the other player
                if (curDist < MIN_DIST_TO_OPPONENT + D3DVAL(20))
                {
                    // The opponent may be blocking
                    if ((g_player2State == BLOCKING) &&
                        (g_player2AnimArgs.time > BLOCK_START + (NUM_BLOCKING_FRAMES / D3DVAL(2))))
                    {
                        // The opponent blocked the punch, so play the block sound
                        PlaySoundDS(BLOCK1 + (rand() % 3),player2pos);
                        break;
                    }

                    // We're within the striking distance
                    if (g_player2health == 0) return;

                    // Add some debris into the scene to register a hit and play a sound
                    D3DVECTOR debrisOrg = player1pos;
                    D3DVECTOR debrisVel = { D3DVAL(0), D3DVAL(0), D3DVAL(-10) };

                    debrisOrg.x += D3DVAL(40);
                    debrisOrg.y += D3DVAL(40);
                    debrisOrg.z += D3DVAL(90);

                    // Add some debris
                    AddDebris(debrisOrg, debrisVel, g_lpRedDebris);

                    // Decrease the opponents health
                    if (g_player2health > 0) g_player2health -= 10;

                    if (g_player2health == 0)
                    {
                        // The opponent has died!
                        g_player2State = DEAD;
                        g_player2AnimArgs.time = DEAD_START;
                        g_player2AnimArgs.lpAnimSet->SetTime(DEAD_START);
                        PlaySoundDS(HEAD_SPRING,player2pos);

                        // And the player has victory!
                        g_player1State = VICTORY;
                        g_player1AnimArgs.time = VICTORY_START;
                        g_player1AnimArgs.lpAnimSet->SetTime(VICTORY_START);

                        PlaySoundDS(VICTORY_YEAH,player1pos);

                        return;
                    }

                    // Play a punch sound
                    PlaySoundDS(rand() % 2 == 0 ? PLAYER1_PUNCH1 : PLAYER1_PUNCH2,player2pos);

                    // Recalculate the power bars
                    RecalcPowerBars(g_player1health, g_player2health);

                    // Make sure we force the camera to the correct place
                    PositionCamera();

                    // Setup the opponents animation and state
                    g_player2State = BEEN_HIT;
                    g_player2AnimArgs.time = BEEN_HIT_START;
                    g_player2AnimArgs.lpAnimSet->SetTime(BEEN_HIT_START);

                    // What should the opponent do?
                    if (rand() % 10 < 2)
                    {
                        // Make the opponent defensive
                        g_oppState = DEFENSIVE;
                    }

                    // Play the ouch sound
                    PlaySoundDS(PLAYER2_OUCH,player1pos);
                }
            }

            // Record the new animation time
            g_player1AnimArgs.time = time;
        }
        break;

        case BLOCKING :
        {
            // Forward the blocking animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);

            // Hold the block up if CTRL is held down
            if (GetAsyncKeyState(PLAYER_BLOCK) & 0x8000)
            {
                if (time < BLOCK_START + (NUM_BLOCKING_FRAMES / 2)) time += (g_blockDelta * delta);
            }
            else
            {
                time += (g_blockDelta * delta);
            }

            // Reset player 1's state to BOBBING if the animation has ended
            if (time > BLOCK_START + NUM_BLOCKING_FRAMES)
            {
                time            = BOB_START;
                g_player1State  = BOBBING;
            }

            // Record the new animation time
            g_player1AnimArgs.time = time;
        }
        break;

        case BEEN_HIT :
        {
            // Forward the been hit animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);
            time += (g_hitDelta * delta);

            if (player1pos.z > EDGE_LEFT) g_lpPlayer1->SetPosition(g_lpScene, player1pos.x, player1pos.y, player1pos.z - D3DVAL(5));
            PositionCamera();

            // Reset player 1's state to BOBBING if the animation has ended
            if (time > BEEN_HIT_START + NUM_BEEN_HIT_FRAMES)
            {
                time            = BOB_START;
                g_player1State  = BOBBING;
            }

            // Record the new animation time
            g_player1AnimArgs.time = time;
        }
        break;

        case DEAD :
        {
            // Forward the death animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);
            time += (g_deadDelta * delta);

            // Play a crash sound if the animation has passes the DEAD_HIT_GROUND frame
            if ((time > DEAD_HIT_GROUND) && (!bHitGround))
            {
                bHitGround = TRUE;
                PlaySoundDS(BLOCK3,player1pos);
            }

            // Reset player 1's state to BOBBING if the animation has ended
            if (time > DEAD_START + NUM_DEAD_FRAMES)
            {
                time            = BOB_START;
                g_player1State  = BOBBING;
                g_player1health = 100;
                bHitGround      = FALSE;

                RecalcPowerBars(g_player1health, g_player2health);
            }

            // Record the new animation time
            g_player1AnimArgs.time = time;

            // Position the camera correctly
            PositionCamera();
        }
        break;

        case VICTORY :
        {
            // Forward the victory animation time
            g_player1AnimArgs.lpAnimSet->SetTime(time);
            time += (g_victoryDelta * delta);

            if (time > VICTORY_START + NUM_VICTORY_FRAMES)
            {
                time            = BOB_START;
                g_player1State  = BOBBING;
            }
            g_player1AnimArgs.time = time;

            PositionCamera();
        }
        break;
    }
}

//----------------------------------------------------------------------
//
// Function     : Player2AnimationCallback()
//
// Purpose      : Animation call back for player 2
//
//----------------------------------------------------------------------

void CDECL Player2AnimationCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta)
{
    D3DVALUE time = g_player2AnimArgs.time;
    D3DVECTOR player1pos;
    D3DVECTOR player2pos;

    // Booleans to help with sound playing
    static BOOL bHitGround              = FALSE;
    static BOOL bPlayedWhoosh   = FALSE;

    // Get the players positions
    g_lpPlayer1->GetPosition(g_lpScene, &player1pos);
    g_lpPlayer2->GetPosition(g_lpScene, &player2pos);

    // Compute distance between players
    D3DVALUE curDist = player2pos.z - player1pos.z;

    // Do something based upon the state of player 1
    switch (g_player2State)
    {
        case BOBBING :
        {
            // Forward the bobbing animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_bobDelta * delta);

            // Reset if animation has ended
            if (time > BOB_START + NUM_BOB_FRAMES) time = BOB_START;

            // Record the new time
            g_player2AnimArgs.time = time;
        }
        break;

        case PUNCHING :
        {
            // Forward the punching animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_attackDelta * delta);

            // Play a whoosh sound if player 2's arm has gone back and is about to punch
            if ((time > PUNCH_ARM_COCKED) && (!bPlayedWhoosh))
            {
                // Play the whoosh
                PlaySoundDS(rand() % 2 == 0 ? WHOOSH1 : WHOOSH2,player2pos);
                bPlayedWhoosh = TRUE;
            }

            // If the punch has played, see if we hit the opponent
            if (time > PUNCH_START + NUM_PUNCH_FRAMES)
            {
                time = BOB_START;
                g_player2State = BOBBING;
                bPlayedWhoosh  = FALSE;

                // Play a servo sound
                PlaySoundDS(SERVO_DOWN_2,player2pos);

                // Now, decide whether we have hit the other player
                if (curDist < MIN_DIST_TO_OPPONENT + D3DVAL(20))
                {
                    // The opponent may be blocking
                    if ((g_player1State == BLOCKING) &&
                        (g_player1AnimArgs.time > BLOCK_START + (NUM_BLOCKING_FRAMES / D3DVAL(2))))
                    {
                        // The opponent blocked the punch, so play the block sound
                        PlaySoundDS(BLOCK1 + (rand() % 3),player1pos);
                        break;
                    }

                    // We're within the striking distance
                    if (g_player1health <= 0) return;

                    // We're within the striking distance
                    // Add some debris into the scene to register a hit and play a sound
                    D3DVECTOR debrisOrg = player1pos;
                    D3DVECTOR debrisVel = { D3DVAL(0), D3DVAL(0), D3DVAL(10) };

                    debrisOrg.x += D3DVAL(-40);
                    debrisOrg.y += D3DVAL(40);
                    debrisOrg.z += D3DVAL(-10);

                    // Add some debris
                    AddDebris(debrisOrg, debrisVel, g_lpBlueDebris);

                    // Play a punch sound
                    PlaySoundDS(rand() % 2 == 0 ? PLAYER2_PUNCH1 : PLAYER2_PUNCH2,player1pos);

                    // Decrease the opponents health
                    if (g_player1health > 0) g_player1health -= 10;

                    if (g_player1health == 0)
                    {
                        // The player has died!
                        g_player1State = DEAD;
                        g_player1AnimArgs.time = DEAD_START;
                        g_player1AnimArgs.lpAnimSet->SetTime(DEAD_START);
                        PlaySoundDS(HEAD_SPRING,player1pos);

                        // And the opponent has victory!
                        g_player2State = VICTORY;
                        g_player2AnimArgs.time = VICTORY_START;
                        g_player2AnimArgs.lpAnimSet->SetTime(VICTORY_START);

                        // The crowd is not happy....
                        PlaySoundDS(VICTORY_BOO,player2pos);

                        return;
                    }

                    // Recalculate the power bars
                    RecalcPowerBars(g_player1health, g_player2health);

                    // Force a camera positional update
                    PositionCamera();

                    if (g_player1State != BEEN_HIT)
                    {
                        // Change player 1's state to BEEN_HIT,
                        g_player1State = BEEN_HIT;

                        // And set the animation time
                        g_player1AnimArgs.time = BEEN_HIT_START;
                        g_player1AnimArgs.lpAnimSet->SetTime(BEEN_HIT_START);
                    }

                    // Play the ouch sound
                    PlaySoundDS(PLAYER1_OUCH,player1pos);
                }
            }

            // Record the new animation time
            g_player2AnimArgs.time = time;
        }
        break;

        case BEEN_HIT :
        {
            // Forward the been hit animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_hitDelta * delta);

            // Move the player
            if (player2pos.z < EDGE_RIGHT) g_lpPlayer2->SetPosition(g_lpScene, player2pos.x, player2pos.y, player2pos.z + D3DVAL(5));

            // Force a camera position update
            PositionCamera();

            // Reset to BOBBING if animation has ended
            if (time > BEEN_HIT_START + NUM_BEEN_HIT_FRAMES)
            {
                time = BOB_START;
                g_player2State = BOBBING;
            }

            // Record the new animation time
            g_player2AnimArgs.time = time;
        }
        break;

        case BLOCKING :
        {
            // Forward the blocking animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_blockDelta * delta);

            // Reset to BOBBING if animation has ended
            if (time > BLOCK_START + NUM_BLOCKING_FRAMES)
            {
                time            = BOB_START;
                g_player2State  = BOBBING;
                g_oppState      = AGGRESSIVE;
            }

            // Record the new animation time
            g_player2AnimArgs.time = time;
        }
        break;

        case DEAD :
        {
            // Forward the death animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_deadDelta * delta);

            // Play the BLOCK3 sound when the opponent hits the ground
            if ((time > DEAD_HIT_GROUND) && (!bHitGround))
            {
                bHitGround = TRUE;
                PlaySoundDS(BLOCK3,player2pos);
            }

            // Reset to BOBBING if the animation has ended
            if (time > DEAD_START + NUM_DEAD_FRAMES)
            {
                time            = BOB_START;
                g_player2State  = BOBBING;
                g_player2health = 100;
                bHitGround      = FALSE;

                // Recalculate the power bars
                RecalcPowerBars(g_player1health, g_player2health);
            }
            // Record the new animation time
            g_player2AnimArgs.time = time;

            // Force a camera position update
            PositionCamera();
        }
        break;

        case VICTORY :
        {
            // Forward the victory animation time
            g_player2AnimArgs.lpAnimSet->SetTime(time);
            time += (g_victoryDelta * delta);

            if (time > VICTORY_START + NUM_VICTORY_FRAMES)
            {
                time            = BOB_START;
                g_player2State  = BOBBING;
            }

            // Record the new animation time
            g_player2AnimArgs.time = time;

            // Force a camera position update
            PositionCamera();
        }
        break;
    }
}

//----------------------------------------------------------------------
//
// Function     : CheckInputAndUpdate()
//
// Purpose      : Checks input, updates scene, moves player(s)
//
//----------------------------------------------------------------------

void CheckInputAndUpdate()
{
    D3DVECTOR player1pos;
    D3DVECTOR player2pos;
    D3DVECTOR camPos;

    // Run intro
    if (g_appState == DOING_INTRO)
    {
            RunIntro();
            return;
    }

    // Transition camera
    if (g_appState == BETWEEN_CAM_VIEWS)
    {
            TransitionCamera();
            return;
    }

    // Get the players positions
    g_lpPlayer1->GetPosition(g_lpScene, &player1pos);
    g_lpPlayer2->GetPosition(g_lpScene, &player2pos);

    // Get the camera's position
    g_lpCamera->GetPosition(g_lpScene, &camPos);

    // Calculate distance between players
    D3DVALUE curDist = player2pos.z - player1pos.z;

    // Move player forward
    if ((IsKeyDown(PLAYER_MOVE_FORWARD)) && (g_player1State != VICTORY) && (g_player1State != DEAD))
    {
            // Make sure the player can only move so close to the opponent
            if (curDist > MIN_DIST_TO_OPPONENT)
            {
                    // Move the player
                    g_lpPlayer1->SetPosition(g_lpScene, player1pos.x, player1pos.y, player1pos.z + MOVE_NORMAL);

                    // Force a camera position update
                    PositionCamera();

                    // Play a servo sound
                    PlaySoundDS(SERVO_UP_3,player1pos);

                    // Play a walk sound
                    PlaySoundDS(PLAYER1_WALK,player1pos);
            }
    }

    // Move player backward
    if ((IsKeyDown(PLAYER_MOVE_BACKWARD)) && (g_player1State != VICTORY) && (g_player1State != DEAD))
    {
            // Make sure the player can't run off the arena
            if (player1pos.z > EDGE_LEFT)
            {
                    // Move the player
                    g_lpPlayer1->SetPosition(g_lpScene, player1pos.x, player1pos.y, player1pos.z - MOVE_NORMAL);

                    // Force a camera position update
                    PositionCamera();

                    // Play a servo sound
                    PlaySoundDS(SERVO_UP_3,player1pos);

                    // Play a walk sound
                    PlaySoundDS(PLAYER1_WALK,player1pos);
            }
    }

    // Initiate a player attack
    if ((!g_bPlayer1Attacking) && (!g_bPlayer1Blocking) && (IsKeyDown(PLAYER_ATTACK)))
    {
            if ((g_player1State != PUNCHING) && (g_player1State != DEAD) && (g_player1State != VICTORY))
            {
                    // Set the player state to punching
                    g_player1State = PUNCHING;

                    // Set up the correct time for the animation
                    g_player1AnimArgs.time = D3DVAL(PUNCH_START);
                    g_player1AnimArgs.lpAnimSet->SetTime(D3DVAL(PUNCH_START));

                    g_bPlayer1Attacking = TRUE;

                    // Play a servo sound
                    PlaySoundDS(SERVO_UP_1,player1pos);
            }
    }

    // Reset the attacking flag if the PLAYER_ATTACK key is not pressed anymore
    if ((g_bPlayer1Attacking) && (!IsKeyDown(PLAYER_ATTACK)))
    {
            g_bPlayer1Attacking = FALSE;
    }

    // Initiate a player block
    if ((!g_bPlayer1Blocking) && (!g_bPlayer1Attacking) && (IsKeyDown(PLAYER_BLOCK)))
    {
            if ((g_player1State != BLOCKING) && (g_player1State != DEAD) && (g_player1State != VICTORY))
            {
                    // Set the player state to blocking
                    g_player1State = BLOCKING;

                    // Set up the correct time for the animation
                    g_player1AnimArgs.time = D3DVAL(BLOCK_START);
                    g_player1AnimArgs.lpAnimSet->SetTime(D3DVAL(BLOCK_START));

                    // Don't allow any more blocking
                    g_bPlayer1Blocking = TRUE;

                    // Play a servo sound
                    PlaySoundDS(SERVO_UP_1,player1pos);
            }
    }

    // Reset the blocking flag if the PLAYER_BLOCK key is no longer held down
    if ((g_bPlayer1Blocking) && (!IsKeyDown(PLAYER_BLOCK)))
    {
            g_bPlayer1Blocking = FALSE;
    }

    // Transition to the BOTH_IN_VIEW camera view
    if ((IsKeyDown(CAM_BOTH_IN_VIEW)) && (g_camState != BOTH_IN_VIEW))
    {
            // Create transition animation
            if (!FAILED(g_lpD3DRM->CreateAnimation(&g_lpAnim)))
            {
                    // Setup the animation options
                    g_lpAnim->SetOptions(D3DRMANIMATION_OPEN |
                                                             D3DRMANIMATION_LINEARPOSITION |
                                                             D3DRMANIMATION_POSITION);

                    // Make midway vector between both players, this is what the camera will focus on
                    D3DVECTOR vMidPoint;
                    vMidPoint.x = 0.0f;
                    vMidPoint.y = 0.0f;
                    vMidPoint.z = (player1pos.z + player2pos.z) / D3DVAL(2);

                    // Calculate vector that will keep both players in sight
                    D3DVECTOR vNewCam;
                    vNewCam.x = (float)abs((int)player2pos.z - (int)player1pos.z) + D3DVAL(300);
                    vNewCam.y = camPos.y;
                    vNewCam.z = vMidPoint.z;

                    // Add the keyframes to the animation
                    g_lpAnim->AddPositionKey(D3DVAL(0), camPos.x, camPos.y, camPos.z);
                    g_lpAnim->AddPositionKey(D3DVAL(1), vNewCam.x, vNewCam.y, vNewCam.z);

                    // Setup the initial position
                    g_lpTmp->SetPosition(g_lpScene, vMidPoint.x, vMidPoint.y, vMidPoint.z);

                    // And attach the camera to the animation
                    g_lpAnim->SetFrame(g_lpCamera);

                    g_appState = BETWEEN_CAM_VIEWS;
            }
            else
            {
                    // Create animation failed so just set the camera position
                    g_lpCamera->SetPosition(g_lpScene, D3DVAL(200), D3DVAL(100), player1pos.z + MOVE_NORMAL - D3DVAL(400));
                    g_lpCamera->LookAt(g_lpPlayer2, g_lpScene, D3DRMCONSTRAIN_Z);
                    PositionCamera();
            }

            g_camState = BOTH_IN_VIEW;
    }

    // Transition to the OVER_SHOULDER camera view
    if ((IsKeyDown(CAM_OVER_THE_SHOULDER)) && (g_camState != OVER_SHOULDER))
    {
        if (!FAILED(g_lpD3DRM->CreateAnimation(&g_lpAnim)))
        {
            // Setup the animation options
            g_lpAnim->SetOptions(D3DRMANIMATION_OPEN |
                                 D3DRMANIMATION_LINEARPOSITION |
                                 D3DRMANIMATION_POSITION);

            // Add the keyframes to the animation
            g_lpAnim->AddPositionKey(D3DVAL(0), camPos.x, camPos.y, camPos.z);
            g_lpAnim->AddPositionKey(D3DVAL(1), D3DVAL(200), D3DVAL(100), player1pos.z - D3DVAL(300));

// Setup the initial position
            g_lpTmp->SetPosition(g_lpScene, player2pos.x, player2pos.y, player2pos.z);

            // And attach the camera to the animation
            g_lpAnim->SetFrame(g_lpCamera);

            g_appState = BETWEEN_CAM_VIEWS;
        }
        else
        {
            // Create animation failed so just set the camera position
            PositionCamera();
        }

        g_camState = OVER_SHOULDER;
    }

    // Transition to the PILOT_VIEW camera view
    if ((IsKeyDown(CAM_PILOT)) && (g_camState != PILOT_VIEW))
    {
        PositionCamera();
        g_camState = PILOT_VIEW;
    }

    // Update the opponent
    UpdateOpponent();

    // Update any debris in the world
    UpdateDebris();
}

//----------------------------------------------------------------------
//
// Function     : UpdateOpponent()
//
// Purpose      : Updates opponent
//
//----------------------------------------------------------------------

void UpdateOpponent()
{
    D3DVECTOR player1pos;
    D3DVECTOR player2pos;

    if (g_player2State != BOBBING) return;

    // Random value to determine what the opponent should do based upon its state
    int r = rand() % 100;

    // Get the players positions
    g_lpPlayer1->GetPosition(g_lpScene, &player1pos);
    g_lpPlayer2->GetPosition(g_lpScene, &player2pos);

    // Calculate distance betweens players
    D3DVALUE curDist = player2pos.z - player1pos.z;

    // There is always a chance to block the player
    if ((r < 15) && (g_player1State == PUNCHING) && (g_player2State == BOBBING))
    {
        // Set the opponents state to BLOCKING ans start the animation
        g_player2State = BLOCKING;
        g_player2AnimArgs.time = BLOCK_START;
        g_player2AnimArgs.lpAnimSet->SetTime(BLOCK_START);

        // Play a servo sound
        PlaySoundDS(SERVO_UP_2,player2pos);
        return;
    }

    switch (g_oppState)
    {
        case DEFENSIVE:
        {
            // Decide whether to move opponent backwards
            if (r < 25)
            {
                // Move the opponent backwards
                g_lpPlayer2->SetPosition(g_lpScene, player2pos.x, player2pos.y, player2pos.z + MOVE_NORMAL);

                // Play a walk sound
                PlaySoundDS(PLAYER2_WALK,player2pos);

                // Play a servo sound
                PlaySoundDS(SERVO_DOWN_3,player2pos);
            }
            PositionCamera();

            // Decide whether to go from DEFENSIVE to cautious
            if (r < 10) g_oppState = CAUTIOUS;
        }
        break;

        case CAUTIOUS:
        {
            // Decide whether to go from CAUTIOUS to AGGRESSIVE
            if (r < 5)
            {
                // Make the opponent become aggresive
                g_oppState = AGGRESSIVE;
            }
        }
        break;

        case AGGRESSIVE:
        {
            // Decide whether to move the opponent towards the player
            if (r < 50)
            {
                // Move the opponent towards the player
                if (curDist > MIN_DIST_TO_OPPONENT)
                {
                    g_lpPlayer2->SetPosition(g_lpScene, player2pos.x, player2pos.y, player2pos.z - MOVE_NORMAL);
                    PositionCamera();

                    // Play a walk sound
                    PlaySoundDS(PLAYER2_WALK,player2pos);

                    // Play a servo sound
                    PlaySoundDS(SERVO_DOWN_3,player2pos);
                }
            }

            // Decide whether or not to attack the player
            if ((r < 15) && (g_player2State == BOBBING) &&
                (g_player1State != DEAD) && (curDist < MIN_DIST_TO_OPPONENT + D3DVAL(50)))
            {
                // Set the opponent's state to PUNCHING
                g_player2State = PUNCHING;
                g_player2AnimArgs.time = PUNCH_START;
                g_player2AnimArgs.lpAnimSet->SetTime(PUNCH_START);

                // Play a servo sound
                PlaySoundDS(SERVO_UP_2,player2pos);
            }

            // Decide whether or not to move the opponent backwards out of the way if the player
            // is punching
            if ((g_player1State == PUNCHING) && (r > 50) && (g_player2State == BOBBING) && (curDist < MIN_DIST_TO_OPPONENT + 50))
            {
                // Move the opponent out of the way, making sure its still in the arena
                if (player2pos.z < EDGE_RIGHT)
                {
                    g_lpPlayer2->SetPosition(g_lpScene, player2pos.x, player2pos.y, player2pos.z + D3DVAL(5));

                    // Play a servo sound
                    PlaySoundDS(SERVO_DOWN_3,player1pos);
                }
            }
        }
        break;
    }
}

//----------------------------------------------------------------------
//
// Function     : RunIntro()
//
// Purpose      : Moves camera along path
//
//----------------------------------------------------------------------

void RunIntro()
{
    static D3DVALUE time = D3DVAL(0.0f);
D3DVECTOR d3dvPos;// D3DVECTOR used for 3D position of sound
d3dvPos.x = D3DVAL(0);// set the sound at (0,0,0)..
d3dvPos.y = D3DVAL(0);
d3dvPos.z = D3DVAL(0);

    // Play the intro sound
    if (time == D3DVAL(0.0f))
    {
        PlaySoundDS(INTRO,d3dvPos);
        PlaySoundDS(CROWD_LOOP,d3dvPos, DSBPLAY_LOOPING);
                SetTimer(NULL,NULL,10000,(TIMERPROC)PlayRandomWave);
    }

    // Foward the intro animation
    time += D3DVAL(0.04);

    // Set the animation time
    g_lpAnim->SetTime(time);

    // Always look at the origin of the g_lpScene frame
    g_lpCamera->LookAt(g_lpTmp, g_lpScene, D3DRMCONSTRAIN_Z);

    // If time has exceeded 1.0 the intro is done, and we can start the demo
    if (time >= D3DVAL(1.0f))
    {
        g_appState = PLAYING_DEMO;
        g_lpAnim->Release();
        g_lpAnim = NULL;
        PositionCamera();
    }
}

//----------------------------------------------------------------------
//
// Function     : PostionCamera()
//
// Purpose      : Positions camera based upon camera state
//
//----------------------------------------------------------------------

void PositionCamera()
{
    D3DVECTOR player1pos;
    D3DVECTOR player2pos;
    D3DVECTOR camPos;

    // Don't position the camera if we are transitioning between camera views
    if (g_appState == BETWEEN_CAM_VIEWS) return;

    // Get the players positions
    g_lpPlayer1->GetPosition(g_lpScene, &player1pos);
    g_lpPlayer2->GetPosition(g_lpScene, &player2pos);

    // Get the camera's position
    g_lpCamera->GetPosition(g_lpScene, &camPos);

    switch (g_camState)
    {
        case OVER_SHOULDER:
        {
            // Position the camera such that it looks over the shoulder of the player and
            // keeps the opponent in view
            g_lpCamera->SetPosition(g_lpScene, camPos.x, camPos.y, D3DVAL(player1pos.z) + MOVE_NORMAL - 300);
            g_lpCamera->LookAt(g_lpPlayer2, g_lpScene, D3DRMCONSTRAIN_Z);
    if (g_lpDs3dListener)
    {
                // Set the listener position to where the camera is
g_lpDs3dListener->SetPosition(camPos.x, camPos.y, D3DVAL(player1pos.z) + MOVE_NORMAL - 300,DS3D_DEFERRED);
// Change listener's orientation vector
g_lpDs3dListener->SetOrientation(D3DVAL(0),D3DVAL(0),D3DVAL(1),D3DVAL(0),D3DVAL(1),D3DVAL(0),DS3D_DEFERRED);
    }
        }
        break;

        case BOTH_IN_VIEW:
        {
            // Position the camera such that it looks at both players all the time

            // Make midway point, this is what the camera will focus on
            D3DVECTOR vMidPoint;
            vMidPoint.x = 0.0f;
            vMidPoint.y = 0.0f;
            vMidPoint.z = (player1pos.z + player2pos.z) / D3DVAL(2);

            // Calculate vector that will keep both players in sight
            D3DVECTOR vNewCam;
            vNewCam.x = (float)abs((int)player2pos.z - (int)player1pos.z) + D3DVAL(300);
            vNewCam.y = camPos.y;
            vNewCam.z = vMidPoint.z;

            g_lpCamera->SetPosition(g_lpScene, vNewCam.x, vNewCam.y, vNewCam.z);

            g_lpTmp->SetPosition(g_lpScene, vMidPoint.x, vMidPoint.y, vMidPoint.z);
            g_lpCamera->LookAt(g_lpTmp, g_lpScene, D3DRMCONSTRAIN_Z);
    if (g_lpDs3dListener)
    {
                // Set the listener position to where the camera is
g_lpDs3dListener->SetPosition(vNewCam.x, vNewCam.y, vNewCam.z,DS3D_DEFERRED);
// Change listener's orientation vector
g_lpDs3dListener->SetOrientation(-D3DVAL(1),D3DVAL(0),D3DVAL(0),D3DVAL(0),D3DVAL(1),D3DVAL(0),DS3D_DEFERRED);
    }
        }
        break;

        case PILOT_VIEW:
        {
            // Position the camera such that it looks through the eyes of the player
            // and fixes on the origin of the head frame of the opponent

            g_lpCamera->SetPosition(g_lpPlayer1HeadFrame, D3DVAL(0), D3DVAL(-10), D3DVAL(0));
            g_lpTmp->SetPosition(g_lpPlayer2HeadFrame, D3DVAL(0), D3DVAL(-10), D3DVAL(0));
            g_lpCamera->LookAt(g_lpTmp, g_lpScene, D3DRMCONSTRAIN_Z);
    if (g_lpDs3dListener)
    {
                // Set the listener position to where the camera is
g_lpDs3dListener->SetPosition(player1pos.x, D3DVAL(0), player1pos.z,DS3D_DEFERRED);
// Change listener's orientation vector
g_lpDs3dListener->SetOrientation(D3DVAL(0),D3DVAL(0),D3DVAL(1),D3DVAL(0),D3DVAL(1),D3DVAL(0),DS3D_DEFERRED);
    }
        }
        break;
    }
// Commit the changes to the listener's orientation and position..
if (g_lpDs3dListener)
    g_lpDs3dListener->CommitDeferredSettings();
}

//----------------------------------------------------------------------
//
// Function     : TransitionCamera()
//
// Purpose      : Positions camera based upon camera state
//
//----------------------------------------------------------------------

void TransitionCamera()
{
    static D3DVALUE time = D3DVAL(0.0f);

    // Forward the transition animation time
    time += D3DVAL(0.04);

    // Set the time for the transitional animation
    g_lpAnim->SetTime(time);

    // Look at the g_lpScene frame
    g_lpCamera->LookAt(g_lpTmp, g_lpScene, D3DRMCONSTRAIN_Z);

    // If the animation has ended, run the demo
    if (time >= D3DVAL(1.0f))
    {
        g_appState = PLAYING_DEMO;
        g_lpAnim->Release();
        g_lpAnim = NULL;
        time = D3DVAL(0);
        PositionCamera();
    }

    // Update debris so any debris in the scene doesn't just sit there doing nothing
    UpdateDebris();
}

//----------------------------------------------------------------------
//
// Function     : AddDebris()
//
// Purpose      : Adds some debris to the scene
//
//----------------------------------------------------------------------

void AddDebris(D3DVECTOR vOrg, D3DVECTOR vVel, LPDIRECT3DRMMESHBUILDER pDebris)
{
    // vOrg is the origin of the debris, find first open spot and add 5 bits of debris
    int count = 0;

    for (int i = 0; i < NUM_DEBRIS; i ++)
    {
        // Find some debris that is not in use yet
        if (!g_debris[i].m_bInUse)
        {
            // Add some debris
            g_debris[i].m_pFrame->AddVisual(pDebris);
            g_debris[i].m_pFrame->SetPosition(g_lpScene, vOrg.x, vOrg.y, vOrg.z);
            g_debris[i].m_pFrame->SetRotation(g_lpScene, D3DVAL(0.5), D3DVAL(0.5), D3DVAL(0.5), D3DVAL(0.5));
            g_debris[i].m_life = 0;

            // Setup velocity and acceleration
            g_debris[i].m_vel.x = D3DVAL(-10 + (rand() % 20)) + vVel.x;
            g_debris[i].m_vel.y = D3DVAL(8) + D3DVAL(rand() % 10) + vVel.y;
            g_debris[i].m_vel.z = D3DVAL(-10 + (rand() % 20)) + vVel.z;

            g_debris[i].m_acc.x = D3DVAL(0);
            g_debris[i].m_acc.y = D3DVAL(-2);
            g_debris[i].m_acc.z = D3DVAL(0);

            // This piece of debris is now in use
            g_debris[i].m_bInUse = TRUE;
            g_debris[i].m_pMeshBuilder = pDebris;

            count ++;
        }

        // Return if we've added NUM_HIT_DEBRIS bits of debris
        if (count == NUM_HIT_DEBRIS) return;
    }
}

//----------------------------------------------------------------------
//
// Function     : UpdateDebris()
//
// Purpose      : Updates debris
//
//----------------------------------------------------------------------

void UpdateDebris()
{
    D3DVECTOR vPos;

    // Go through the array of debris and update any debris that is in use

    for (int i = 0; i < NUM_DEBRIS; i ++)
    {
        if (g_debris[i].m_bInUse)
        {
            // If the debris's life has not yet expired
            if (g_debris[i].m_life < DEBRIS_LIFE)
            {
                // Move the debris
                g_debris[i].m_pFrame->GetPosition(g_lpScene, &vPos);

                g_debris[i].m_vel.x += g_debris[i].m_acc.x;
                g_debris[i].m_vel.y += g_debris[i].m_acc.y;
                g_debris[i].m_vel.z += g_debris[i].m_acc.z;

                vPos.x += g_debris[i].m_vel.x;
                vPos.y += g_debris[i].m_vel.y;
                vPos.z += g_debris[i].m_vel.z;

                // Check to see whether the debris is below the floor
                if (vPos.y < D3DVAL(-60))
                {
                        // Bounce the debris
                        vPos.y = D3DVAL(-60);
                        g_debris[i].m_vel.y = -g_debris[i].m_vel.y / D3DVAL(1.5);
                }

                // Update the position of the debris
                g_debris[i].m_pFrame->SetPosition(g_lpScene, vPos.x, vPos.y, vPos.z);
            }
            else
            {
                // Remove the debris from the world
                g_debris[i].m_pFrame->DeleteVisual(g_debris[i].m_pMeshBuilder);
                g_debris[i].m_bInUse = FALSE;
            }

            // Age the debris
            g_debris[i].m_life ++;
        }
    }
}


CONTROL.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: control.h
 *
 ***************************************************************************/

#ifndef __CONTROL_H_
#define __CONTROL_H_

// Type to define what app is currently doing
typedef enum
{
    DOING_INTRO,
    PLAYING_DEMO,
    BETWEEN_CAM_VIEWS,
}
AppState;

// Type to define what state the opponent is in
typedef enum
{
    DEFENSIVE,
    CAUTIOUS,
    AGGRESSIVE,
}
PlayerState;

// Type to define what kind of camera angles were using
typedef enum
{
    OVER_SHOULDER,
    BOTH_IN_VIEW,
    PILOT_VIEW,
}
CameraState;

// Type to define what action the player is doing
typedef enum
{
    BOBBING,
    WALKING,
    PUNCHING,
    BLOCKING,
    BEEN_HIT,
    DEAD,
    VICTORY,
    DISABLED,
}
PlayerActionState;

// Structures
struct AnimArgs
{
    LPDIRECT3DRMANIMATIONSETlpAnimSet;
    D3DVALUEtime;
};

void Player1AnimationCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta);
void Player2AnimationCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta);

void CheckInputAndUpdate();
void UpdateOpponent();
void UpdateDebris();
void RunIntro();
void PositionCamera();
void TransitionCamera();
void AddDebris(D3DVECTOR vOrg, D3DVECTOR vVel, LPDIRECT3DRMMESHBUILDER pDebris);

#endif


DIRECTX.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: directx.cpp
 *
 ***************************************************************************/

// N.B. The use of RM denotes the Direct3D retained mode objects

// The Macros TRY_DD, TRY_DS, TRY_D3D, TRY_D3DRM are defined in DirectX.h and are for
// error checking.

#define INITGUID
// Includes....
#include "directx.h"
#include "mmsystem.h"
#include "winmain.h"
#include "conio.h"

// Globals....
LPDIRECTDRAW            g_lpDD = NULL;          // DirectDraw object
LPDIRECT3D              g_lpD3D = NULL;         // Direct3D object
LPDIRECTSOUND           g_lpDS = NULL;          // DirectSound object
LPDIRECT3DRM            g_lpD3DRM = NULL;       // Direct3D RM object
DWORD                   g_dwGDIMem = 0;         // Memory in available from GDI's surface

D3DDeviceInfo           g_deviceInfo;           // 3D device info
LPGUID                  g_lpD3DDeviceGuid = NULL; // Guid to 3D device

VideoMode               g_vidModes[NUM_VID_MODES];// Array of available video modes
DWORD                   g_dwCurrMode = 0;
DWORD                   g_dwNumModes = 0;

BOOL                    g_bSoundPresent = FALSE; // Do we have sound capabilites?

LPDIRECTDRAWSURFACE     g_lpPrimary = NULL;     // Primary surface
LPDIRECTDRAWSURFACE     g_lpBackBuffer = NULL;  // BackBuffer surface
LPDIRECTDRAWSURFACE     g_lpZBuffer = NULL;     // ZBuffer
LPDIRECTDRAWPALETTE     g_lpPalette = NULL;     // Palette
PALETTEENTRY            g_rPal[768];            // Rockem3D palette

LPDIRECTDRAW            g_lpSplashDD = NULL;        // DirectDraw object used for splash screen
LPDIRECTDRAWSURFACE     g_lpSplashPrimary = NULL;   // Primary surface
LPDIRECTDRAWPALETTE     g_lpSplashPalette = NULL;   // Palette

LPDIRECT3DDEVICE        g_lpD3DDevice = NULL;   // Direct3D device
LPDIRECT3DRMDEVICE      g_lpD3DRMDevice = NULL; // Direct3D RM Device
LPDIRECT3DRMVIEWPORT    g_lpD3DRMViewport = NULL; // Direct3D RM Viewport

DDCAPS                  g_driverCaps;           // Driver capabilities
DDCAPS                  g_HELcaps;              // HEL capabilities

DWORD                   g_dwZBufferBitDepth = 16;// ZBuffer bit depth
DWORD                   g_dwZBufferMemType = DDSCAPS_SYSTEMMEMORY;// Type of ZBuffer

LPDIRECTSOUNDBUFFER     g_lpSounds[NUM_SOUNDS]; // Sound buffers
LPDIRECTSOUND3DLISTENER      g_lpDs3dListener;               // Listener object for Direct 3D Sound
LPDIRECTSOUNDBUFFER             g_3DSoundBuffer;                // Direct Sound buffer for the Listener object
LPDIRECTSOUND3DBUFFER   g_lp3dSounds[NUM_SOUNDS];//3D Sound Buffers

// State booleans
BOOL                    g_bHardware3D = FALSE;  // Do we have hardware?
BOOLg_bOutOfVideoMemory = FALSE; // Out of video memory, fall back on software

float                   g_xratio = 0.0f;        // X ratio used for computing power bars
float                   g_yratio = 0.0f;        // Y ratio used for computing power bars

// Power bar x,y and width values
DWORD                   g_vidModeX;             // Current X video resolution
DWORD                   g_vidModeY;             // Current Y video resolution
DWORD                   g_vidModeBIT;           // Current video bit depth
DWORD                   g_dwFontHeight = 0;
DWORD                   g_dwAveCharWidth = 0;
DWORD                   g_lbar1 = 0;
DWORD                   g_wbar1 = 0;
DWORD                   g_lbar2 = 0;
DWORD                   g_wbar2 = 0;
DWORD                   g_hbar1 = 0;
DWORD                   g_hbar2 = 0;

// Externals....
extern HWND                     g_hWnd;         // Defined in WINMAIN.CPP
extern BOOL                     g_bSoundPaused; // Defined in WINMAIN.CPP
extern LPDIRECT3DRMFRAME        g_lpCamera;     // Defined in RM.CPP
extern LPDIRECT3DRMFRAME        g_lpScene;      // Defined in RM.CPP
extern LPDIRECT3DRMFRAME        g_lpPlayers;    // Defined in RM.CPP
extern LPDIRECT3DRMLIGHT        g_lpDir;        // Defined in RM.CPP

//----------------------------------------------------------------------
//
// Function     : TraceErrorDD()
//
// Purpose      : Traces an error (DirectDraw)
//
//----------------------------------------------------------------------

void TraceErrorDD(HRESULT hErr, char *sFile, int nLine)
{
    char dderr[256];
    char err[1024];

    switch (hErr)
    {
        case DDERR_ALREADYINITIALIZED : sprintf(dderr, "DDERR_ALREADYINITIALIZED"); break;
        case DDERR_CANNOTATTACHSURFACE : sprintf(dderr, "DDERR_CANNOTATTACHSURFACE"); break;
        case DDERR_CANNOTDETACHSURFACE : sprintf(dderr, "DDERR_CANNOTDETACHSURFACE"); break;
        case DDERR_CURRENTLYNOTAVAIL : sprintf(dderr, "DDERR_CURRENTLYNOTAVAIL"); break;
        case DDERR_EXCEPTION : sprintf(dderr, "DDERR_EXCEPTION"); break;
        case DDERR_GENERIC : sprintf(dderr, "DDERR_GENERIC"); break;
        case DDERR_HEIGHTALIGN : sprintf(dderr, "DDERR_HEIGHTALIGN"); break;
        case DDERR_INCOMPATIBLEPRIMARY : sprintf(dderr, "DDERR_INCOMPATIBLEPRIMARY"); break;
        case DDERR_INVALIDCAPS : sprintf(dderr, "DDERR_INVALIDCAPS"); break;
        case DDERR_INVALIDCLIPLIST : sprintf(dderr, "DDERR_INVALIDCLIPLIST"); break;
        case DDERR_INVALIDMODE : sprintf(dderr, "DDERR_INVALIDMODE"); break;
        case DDERR_INVALIDOBJECT : sprintf(dderr, "DDERR_INVALIDOBJECT"); break;
        case DDERR_INVALIDPARAMS : sprintf(dderr, "DDERR_INVALIDPARAMS"); break;
        case DDERR_INVALIDPIXELFORMAT : sprintf(dderr, "DDERR_INVALIDPIXELFORMAT"); break;
        case DDERR_INVALIDRECT : sprintf(dderr, "DDERR_INVALIDRECT"); break;
        case DDERR_LOCKEDSURFACES : sprintf(dderr, "DDERR_LOCKEDSURFACES"); break;
        case DDERR_NO3D : sprintf(dderr, "DDERR_NO3D"); break;
        case DDERR_NOALPHAHW : sprintf(dderr, "DDERR_NOALPHAHW"); break;
        case DDERR_NOCLIPLIST : sprintf(dderr, "DDERR_NOCLIPLIST"); break;
        case DDERR_NOCOLORCONVHW : sprintf(dderr, "DDERR_NOCOLORCONVHW"); break;
        case DDERR_NOCOOPERATIVELEVELSET : sprintf(dderr, "DDERR_NOCOOPERATIVELEVELSET"); break;
        case DDERR_NOCOLORKEY : sprintf(dderr, "DDERR_NOCOLORKEY"); break;
        case DDERR_NOCOLORKEYHW : sprintf(dderr, "DDERR_NOCOLORKEYHW"); break;
        case DDERR_NODIRECTDRAWSUPPORT : sprintf(dderr, "DDERR_NODIRECTDRAWSUPPORT"); break;
        case DDERR_NOEXCLUSIVEMODE : sprintf(dderr, "DDERR_NOEXCLUSIVEMODE"); break;
        case DDERR_NOFLIPHW : sprintf(dderr, "DDERR_NOFLIPHW"); break;
        case DDERR_NOGDI : sprintf(dderr, "DDERR_NOGDI"); break;
        case DDERR_NOMIRRORHW : sprintf(dderr, "DDERR_NOMIRRORHW"); break;
        case DDERR_NOTFOUND : sprintf(dderr, "DDERR_NOTFOUND"); break;
        case DDERR_NOOVERLAYHW : sprintf(dderr, "DDERR_NOOVERLAYHW"); break;
        case DDERR_NORASTEROPHW : sprintf(dderr, "DDERR_NORASTEROPHW"); break;
        case DDERR_NOROTATIONHW : sprintf(dderr, "DDERR_NOROTATIONHW"); break;
        case DDERR_NOSTRETCHHW : sprintf(dderr, "DDERR_NOSTRETCHHW"); break;
        case DDERR_NOT4BITCOLOR : sprintf(dderr, "DDERR_NOT4BITCOLOR"); break;
        case DDERR_NOT4BITCOLORINDEX : sprintf(dderr, "DDERR_NOT4BITCOLORINDEX"); break;
        case DDERR_NOT8BITCOLOR : sprintf(dderr, "DDERR_NOT8BITCOLOR"); break;
        case DDERR_NOTEXTUREHW : sprintf(dderr, "DDERR_NOTEXTUREHW"); break;
        case DDERR_NOVSYNCHW : sprintf(dderr, "DDERR_NOVSYNCHW"); break;
        case DDERR_NOZBUFFERHW : sprintf(dderr, "DDERR_NOZBUFFERHW"); break;
        case DDERR_NOZOVERLAYHW : sprintf(dderr, "DDERR_NOZOVERLAYHW"); break;
        case DDERR_OUTOFCAPS : sprintf(dderr, "DDERR_OUTOFCAPS"); break;
        case DDERR_OUTOFMEMORY : sprintf(dderr, "DDERR_OUTOFMEMORY"); break;
        case DDERR_OUTOFVIDEOMEMORY : sprintf(dderr, "DDERR_OUTOFVIDEOMEMORY"); break;
        case DDERR_OVERLAYCANTCLIP : sprintf(dderr, "DDERR_OVERLAYCANTCLIP"); break;
        case DDERR_OVERLAYCOLORKEYONLYONEACTIVE : sprintf(dderr, "DDERR_OVERLAYCOLORKEYONLYONEACTIVE"); break;
        case DDERR_PALETTEBUSY : sprintf(dderr, "DDERR_PALETTEBUSY"); break;
        case DDERR_COLORKEYNOTSET : sprintf(dderr, "DDERR_COLORKEYNOTSET"); break;
        case DDERR_SURFACEALREADYATTACHED : sprintf(dderr, "DDERR_SURFACEALREADYATTACHED"); break;
        case DDERR_SURFACEALREADYDEPENDENT : sprintf(dderr, "DDERR_SURFACEALREADYDEPENDENT"); break;
        case DDERR_SURFACEBUSY : sprintf(dderr, "DDERR_SURFACEBUSY"); break;
        case DDERR_CANTLOCKSURFACE : sprintf(dderr, "DDERR_CANTLOCKSURFACE"); break;
        case DDERR_SURFACEISOBSCURED : sprintf(dderr, "DDERR_SURFACEISOBSCURED"); break;
        case DDERR_SURFACELOST : sprintf(dderr, "DDERR_SURFACELOST"); break;
        case DDERR_SURFACENOTATTACHED : sprintf(dderr, "DDERR_SURFACENOTATTACHED"); break;
        case DDERR_TOOBIGHEIGHT : sprintf(dderr, "DDERR_TOOBIGHEIGHT"); break;
        case DDERR_TOOBIGSIZE : sprintf(dderr, "DDERR_TOOBIGSIZE"); break;
        case DDERR_TOOBIGWIDTH : sprintf(dderr, "DDERR_TOOBIGWIDTH"); break;
        case DDERR_UNSUPPORTED : sprintf(dderr, "DDERR_UNSUPPORTED"); break;
        case DDERR_UNSUPPORTEDFORMAT : sprintf(dderr, "DDERR_UNSUPPORTEDFORMAT"); break;
        case DDERR_UNSUPPORTEDMASK : sprintf(dderr, "DDERR_UNSUPPORTEDMASK"); break;
        case DDERR_VERTICALBLANKINPROGRESS : sprintf(dderr, "DDERR_VERTICALBLANKINPROGRESS"); break;
        case DDERR_WASSTILLDRAWING : sprintf(dderr, "DDERR_WASSTILLDRAWING"); break;
        case DDERR_XALIGN : sprintf(dderr, "DDERR_XALIGN"); break;
        case DDERR_INVALIDDIRECTDRAWGUID : sprintf(dderr, "DDERR_INVALIDDIRECTDRAWGUID"); break;
        case DDERR_DIRECTDRAWALREADYCREATED : sprintf(dderr, "DDERR_DIRECTDRAWALREADYCREATED"); break;
        case DDERR_NODIRECTDRAWHW : sprintf(dderr, "DDERR_NODIRECTDRAWHW"); break;
        case DDERR_PRIMARYSURFACEALREADYEXISTS : sprintf(dderr, "DDERR_PRIMARYSURFACEALREADYEXISTS"); break;
        case DDERR_NOEMULATION : sprintf(dderr, "DDERR_NOEMULATION"); break;
        case DDERR_REGIONTOOSMALL : sprintf(dderr, "DDERR_REGIONTOOSMALL"); break;
        case DDERR_CLIPPERISUSINGHWND : sprintf(dderr, "DDERR_CLIPPERISUSINGHWND"); break;
        case DDERR_NOCLIPPERATTACHED : sprintf(dderr, "DDERR_NOCLIPPERATTACHED"); break;
        case DDERR_NOHWND : sprintf(dderr, "DDERR_NOHWND"); break;
        case DDERR_HWNDSUBCLASSED : sprintf(dderr, "DDERR_HWNDSUBCLASSED"); break;
        case DDERR_HWNDALREADYSET : sprintf(dderr, "DDERR_HWNDALREADYSET"); break;
        case DDERR_NOPALETTEATTACHED : sprintf(dderr, "DDERR_NOPALETTEATTACHED"); break;
        case DDERR_NOPALETTEHW : sprintf(dderr, "DDERR_NOPALETTEHW"); break;
        case DDERR_BLTFASTCANTCLIP : sprintf(dderr, "DDERR_BLTFASTCANTCLIP"); break;
        case DDERR_NOBLTHW : sprintf(dderr, "DDERR_NOBLTHW"); break;
        case DDERR_NODDROPSHW : sprintf(dderr, "DDERR_NODDROPSHW"); break;
        case DDERR_OVERLAYNOTVISIBLE : sprintf(dderr, "DDERR_OVERLAYNOTVISIBLE"); break;
        case DDERR_NOOVERLAYDEST : sprintf(dderr, "DDERR_NOOVERLAYDEST"); break;
        case DDERR_INVALIDPOSITION : sprintf(dderr, "DDERR_INVALIDPOSITION"); break;
        case DDERR_NOTAOVERLAYSURFACE : sprintf(dderr, "DDERR_NOTAOVERLAYSURFACE"); break;
        case DDERR_EXCLUSIVEMODEALREADYSET : sprintf(dderr, "DDERR_EXCLUSIVEMODEALREADYSET"); break;
        case DDERR_NOTFLIPPABLE : sprintf(dderr, "DDERR_NOTFLIPPABLE"); break;
        case DDERR_CANTDUPLICATE : sprintf(dderr, "DDERR_CANTDUPLICATE"); break;
        case DDERR_NOTLOCKED : sprintf(dderr, "DDERR_NOTLOCKED"); break;
        case DDERR_CANTCREATEDC : sprintf(dderr, "DDERR_CANTCREATEDC"); break;
        case DDERR_NODC : sprintf(dderr, "DDERR_NODC"); break;
        case DDERR_WRONGMODE : sprintf(dderr, "DDERR_WRONGMODE"); break;
        case DDERR_IMPLICITLYCREATED : sprintf(dderr, "DDERR_IMPLICITLYCREATED"); break;
        case DDERR_NOTPALETTIZED : sprintf(dderr, "DDERR_NOTPALETTIZED"); break;
        case DDERR_UNSUPPORTEDMODE : sprintf(dderr, "DDERR_UNSUPPORTEDMODE"); break;
        case DDERR_NOMIPMAPHW : sprintf(dderr, "DDERR_NOMIPMAPHW"); break;
        case DDERR_INVALIDSURFACETYPE : sprintf(dderr, "DDERR_INVALIDSURFACETYPE"); break;
        case DDERR_DCALREADYCREATED : sprintf(dderr, "DDERR_DCALREADYCREATED"); break;
        case DDERR_CANTPAGELOCK : sprintf(dderr, "DDERR_CANTPAGELOCK"); break;
        case DDERR_CANTPAGEUNLOCK : sprintf(dderr, "DDERR_CANTPAGEUNLOCK"); break;
        case DDERR_NOTPAGELOCKED : sprintf(dderr, "DDERR_NOTPAGELOCKED"); break;
        case DDERR_NOTINITIALIZED : sprintf(dderr, "DDERR_NOTINITIALIZED"); break;

        default : sprintf(dderr, "Unknown Error"); break;
    }
    sprintf(err, "DirectDraw Error %s\nin file %s at line %d", dderr, sFile, nLine);
    RegError(err);
}

//----------------------------------------------------------------------
//
// Function     : TraceErrorDS()
//
// Purpose      : Traces an error (DirectSound)
//
//----------------------------------------------------------------------

void TraceErrorDS(HRESULT hErr, char *sFile, int nLine)
{
    char dserr[256];
    char err[1024];

    switch (hErr)
    {
        case DSERR_ALLOCATED : sprintf(dserr, "DSERR_ALLOCATED"); break;
        case DSERR_CONTROLUNAVAIL : sprintf(dserr, "DSERR_CONTROLUNAVAIL"); break;
        case DSERR_INVALIDPARAM : sprintf(dserr, "DSERR_INVALIDPARAM"); break;
        case DSERR_INVALIDCALL : sprintf(dserr, "DSERR_INVALIDCALL"); break;
        case DSERR_GENERIC : sprintf(dserr, "DSERR_GENERIC"); break;
        case DSERR_PRIOLEVELNEEDED : sprintf(dserr, "DSERR_PRIOLEVELNEEDED"); break;
        case DSERR_OUTOFMEMORY : sprintf(dserr, "DSERR_OUTOFMEMORY"); break;
        case DSERR_BADFORMAT : sprintf(dserr, "DSERR_BADFORMAT"); break;
        case DSERR_UNSUPPORTED : sprintf(dserr, "DSERR_UNSUPPORTED"); break;
        case DSERR_NODRIVER : sprintf(dserr, "DSERR_NODRIVER"); break;
        case DSERR_ALREADYINITIALIZED : sprintf(dserr, "DSERR_ALREADYINITIALIZED"); break;
        case DSERR_NOAGGREGATION : sprintf(dserr, "DSERR_NOAGGREGATION"); break;
        case DSERR_BUFFERLOST : sprintf(dserr, "DSERR_BUFFERLOST"); break;
        case DSERR_OTHERAPPHASPRIO : sprintf(dserr, "DSERR_OTHERAPPHASPRIO"); break;
        case DSERR_UNINITIALIZED : sprintf(dserr, "DSERR_UNINITIALIZED"); break;

        default : sprintf(dserr, "Unknown Error"); break;
    }
    sprintf(err, "DirectSound Error %s\nin file %s at line %d", dserr, sFile, nLine);
    RegError(err);
}

//----------------------------------------------------------------------
//
// Function     : TraceErrorD3D()
//
// Purpose      : Traces an error (Direct3D)
//
//----------------------------------------------------------------------

void TraceErrorD3D(HRESULT hErr, char *sFile, int nLine)
{
    char d3derr[256];
    char err[1024];

    switch (hErr)
    {
        case D3DERR_BADMAJORVERSION : sprintf(d3derr, "D3DERR_BADMAJORVERSION"); break;
        case D3DERR_BADMINORVERSION : sprintf(d3derr, "D3DERR_BADMINORVERSION"); break;
        case D3DERR_EXECUTE_CREATE_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_CREATE_FAILED"); break;
        case D3DERR_EXECUTE_DESTROY_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_DESTROY_FAILED"); break;
        case D3DERR_EXECUTE_LOCK_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_LOCK_FAILED"); break;
        case D3DERR_EXECUTE_UNLOCK_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_UNLOCK_FAILED"); break;
        case D3DERR_EXECUTE_LOCKED : sprintf(d3derr, "D3DERR_EXECUTE_LOCKED"); break;
        case D3DERR_EXECUTE_NOT_LOCKED : sprintf(d3derr, "D3DERR_EXECUTE_NOT_LOCKED"); break;
        case D3DERR_EXECUTE_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_FAILED"); break;
        case D3DERR_EXECUTE_CLIPPED_FAILED : sprintf(d3derr, "D3DERR_EXECUTE_CLIPPED_FAILED"); break;
        case D3DERR_TEXTURE_NO_SUPPORT : sprintf(d3derr, "D3DERR_TEXTURE_NO_SUPPORT"); break;
        case D3DERR_TEXTURE_CREATE_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_CREATE_FAILED"); break;
        case D3DERR_TEXTURE_DESTROY_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_DESTROY_FAILED"); break;
        case D3DERR_TEXTURE_LOCK_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_LOCK_FAILED"); break;
        case D3DERR_TEXTURE_UNLOCK_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_UNLOCK_FAILED"); break;
        case D3DERR_TEXTURE_LOAD_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_LOAD_FAILED"); break;
        case D3DERR_TEXTURE_SWAP_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_SWAP_FAILED"); break;
        case D3DERR_TEXTURE_LOCKED : sprintf(d3derr, "D3DERR_TEXTURE_LOCKED"); break;
        case D3DERR_TEXTURE_NOT_LOCKED : sprintf(d3derr, "D3DERR_TEXTURE_NOT_LOCKED"); break;
        case D3DERR_TEXTURE_GETSURF_FAILED : sprintf(d3derr, "D3DERR_TEXTURE_GETSURF_FAILED"); break;
        case D3DERR_MATRIX_CREATE_FAILED : sprintf(d3derr, "D3DERR_MATRIX_CREATE_FAILED"); break;
        case D3DERR_MATRIX_DESTROY_FAILED : sprintf(d3derr, "D3DERR_MATRIX_DESTROY_FAILED"); break;
        case D3DERR_MATRIX_SETDATA_FAILED : sprintf(d3derr, "D3DERR_MATRIX_SETDATA_FAILED"); break;
        case D3DERR_MATRIX_GETDATA_FAILED : sprintf(d3derr, "D3DERR_MATRIX_GETDATA_FAILED"); break;
        case D3DERR_SETVIEWPORTDATA_FAILED : sprintf(d3derr, "D3DERR_SETVIEWPORTDATA_FAILED"); break;
        case D3DERR_MATERIAL_CREATE_FAILED : sprintf(d3derr, "D3DERR_MATERIAL_CREATE_FAILED"); break;
        case D3DERR_MATERIAL_DESTROY_FAILED : sprintf(d3derr, "D3DERR_MATERIAL_DESTROY_FAILED"); break;
        case D3DERR_MATERIAL_SETDATA_FAILED : sprintf(d3derr, "D3DERR_MATERIAL_SETDATA_FAILED"); break;
        case D3DERR_MATERIAL_GETDATA_FAILED : sprintf(d3derr, "D3DERR_MATERIAL_GETDATA_FAILED"); break;
        case D3DERR_LIGHT_SET_FAILED : sprintf(d3derr, "D3DERR_LIGHT_SET_FAILED"); break;
        case D3DERR_SCENE_IN_SCENE : sprintf(d3derr, "D3DERR_SCENE_IN_SCENE"); break;
        case D3DERR_SCENE_NOT_IN_SCENE : sprintf(d3derr, "D3DERR_SCENE_NOT_IN_SCENE"); break;
        case D3DERR_SCENE_BEGIN_FAILED : sprintf(d3derr, "D3DERR_SCENE_BEGIN_FAILED"); break;
        case D3DERR_SCENE_END_FAILED : sprintf(d3derr, "D3DERR_SCENE_END_FAILED"); break;

        default : sprintf(d3derr, "Unknown Error"); break;
    }
    sprintf(err, "Direct3D Error %s\nin file %s at line %d", d3derr, sFile, nLine);
    RegError(err);
}

//----------------------------------------------------------------------
//
// Function     : TraceErrorD3DRM()
//
// Purpose      : Traces an error (Direct3D retained mode)
//
//----------------------------------------------------------------------

void TraceErrorD3DRM(HRESULT hErr, char *sFile, int nLine)
{
    char d3drmerr[256];
    char err[1024];

    switch (hErr)
    {
        case D3DRMERR_BADOBJECT : sprintf(d3drmerr, "D3DRMERR_BADOBJECT"); break;
        case D3DRMERR_BADTYPE : sprintf(d3drmerr, "D3DRMERR_BADTYPE"); break;
        case D3DRMERR_BADALLOC : sprintf(d3drmerr, "D3DRMERR_BADALLOC"); break;
        case D3DRMERR_FACEUSED : sprintf(d3drmerr, "D3DRMERR_FACEUSED"); break;
        case D3DRMERR_NOTFOUND : sprintf(d3drmerr, "D3DRMERR_NOTFOUND"); break;
        case D3DRMERR_NOTDONEYET : sprintf(d3drmerr, "D3DRMERR_NOTDONEYET"); break;
        case D3DRMERR_FILENOTFOUND : sprintf(d3drmerr, "D3DRMERR_FILENOTFOUND"); break;
        case D3DRMERR_BADFILE : sprintf(d3drmerr, "D3DRMERR_BADFILE"); break;
        case D3DRMERR_BADDEVICE : sprintf(d3drmerr, "D3DRMERR_BADDEVICE"); break;
        case D3DRMERR_BADVALUE : sprintf(d3drmerr, "D3DRMERR_BADVALUE"); break;
        case D3DRMERR_BADMAJORVERSION : sprintf(d3drmerr, "D3DRMERR_BADMAJORVERSION"); break;
        case D3DRMERR_BADMINORVERSION : sprintf(d3drmerr, "D3DRMERR_BADMINORVERSION"); break;
        case D3DRMERR_UNABLETOEXECUTE : sprintf(d3drmerr, "D3DRMERR_UNABLETOEXECUTE"); break;

        default : sprintf(d3drmerr, "Unknown Error"); break;
    }
    sprintf(err, "Direct3D-RM Error : %s\nin file %s at line %d", d3drmerr, sFile, nLine);
    RegError(err);
}

//----------------------------------------------------------------------
//
// Function     : SortDisplayModes()
//
// Purpose      : Sorts the list of display modes
//
//----------------------------------------------------------------------

void SortDisplayModes()
{
    // Sort by width * height
    for (DWORD i = 0; i < g_dwNumModes; i ++)
    {
        for (DWORD k = 0; k < g_dwNumModes - 1; k ++)
        {
            int c1 = g_vidModes[k].width * g_vidModes[k].height;
            int c2 = g_vidModes[k + 1].width * g_vidModes[k + 1].height;

            if (c1 > c2)
            {
                VideoMode tmp;

                // Swap the two video modes
                tmp                     = g_vidModes[k];
                g_vidModes[k]           = g_vidModes[k + 1];
                g_vidModes[k + 1]       = tmp;

                // Keep g_dwCurrMode up to date
                if (g_dwCurrMode == k)
                {
                    g_dwCurrMode = k + 1;
                }
                else if (g_dwCurrMode == k + 1)
                {
                    g_dwCurrMode = k;
                }
            }
        }
    }
}

//----------------------------------------------------------------------
//
// Function     : DDEnumCallBack()
//
// Purpose      : Call back to enumerate installed DirectDraw devices
//
//----------------------------------------------------------------------

BOOL FAR PASCAL DDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext)
{
    LPDIRECTDRAW lpDD;
    DDCAPS DriverCaps, HELCaps;

        // Make sure the guid is valid
    if (lpGUID)
    {
        // Try to create a DirectDraw object
        TRY_DD(DirectDrawCreate(lpGUID, &lpDD, NULL))

        // Get the DirectDraw capabilities
        memset(&DriverCaps, 0, sizeof(DDCAPS));
        DriverCaps.dwSize = sizeof(DDCAPS);

        memset(&HELCaps, 0, sizeof(DDCAPS));
        HELCaps.dwSize = sizeof(DDCAPS);

        TRY_DD(lpDD->GetCaps(&DriverCaps, &HELCaps))

        // Does this driver have 3D hardware capabilites?
        if (DriverCaps.dwCaps & DDCAPS_3D)
        {
            *(LPDIRECTDRAW*)lpContext = lpDD;
            return DDENUMRET_CANCEL;
        }

        *(LPDIRECTDRAW*)lpContext = NULL;
        lpDD->Release();
    }

        // Yahoo!
    return DDENUMRET_OK;
}

//----------------------------------------------------------------------
//
// Function     : DDEnumDisplayModesCallBack()
//
// Purpose      : Call back function to receive display mode information
//
//----------------------------------------------------------------------

HRESULT CALLBACK DDEnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID Context)
{
    // While each mode gets enumerated we have to decide whether
    // the 3D device and mode are compatible
    if (g_deviceInfo.lpHWGuid)
    {
        // Make sure there is enough video ram to support this mode
        //if hardware is in use
        DWORD dwBitDepthMultiplier;

        switch(pddsd->ddpfPixelFormat.dwRGBBitCount)
        {
            case 8  : dwBitDepthMultiplier = 1; break;
            case 16 : dwBitDepthMultiplier = 2; break;
            case 24 : dwBitDepthMultiplier = 3; break;
            case 32 : dwBitDepthMultiplier = 4; break;
        }

        DWORD dwVidRamNeeded = ((pddsd->dwWidth * pddsd->dwHeight) * dwBitDepthMultiplier) * 3;

        if (dwVidRamNeeded > (g_driverCaps.dwVidMemFree + g_dwGDIMem))
          return DDENUMRET_OK;

        // Make sure the Direct3D device can render at a given bit depth
        switch (pddsd->ddpfPixelFormat.dwRGBBitCount)
        {
            case 8 :
            {
                if (!(g_deviceInfo.HWDeviceDesc.dwDeviceRenderBitDepth & DDBD_8)) return DDENUMRET_OK;
            }
            break;

            case 16 :
            {
                if (!(g_deviceInfo.HWDeviceDesc.dwDeviceRenderBitDepth & DDBD_16)) return DDENUMRET_OK;
            }
            break;

            case 24 :
            {
                if (!(g_deviceInfo.HWDeviceDesc.dwDeviceRenderBitDepth & DDBD_24)) return DDENUMRET_OK;
            }
            break;

            case 32 :
            {
                if (!(g_deviceInfo.HWDeviceDesc.dwDeviceRenderBitDepth & DDBD_32)) return DDENUMRET_OK;
            }
            break;
        }

        // If we have hardware, start up in 640x480x16 if possible
        if ((pddsd->dwWidth == 640) && (pddsd->dwHeight == 480) && (pddsd->ddpfPixelFormat.dwRGBBitCount == 16))
        {
            g_dwCurrMode = g_dwNumModes;
        }
    }

    // Record the video mode information
    g_vidModes[g_dwNumModes].width  = pddsd->dwWidth;
    g_vidModes[g_dwNumModes].height = pddsd->dwHeight;
    g_vidModes[g_dwNumModes].bpp    = pddsd->ddpfPixelFormat.dwRGBBitCount;

    g_dwNumModes ++;

    return DDENUMRET_OK;
}

//------------------------------------------------------------------
//
// Function     : D3DEnumDriverCallBack()
//
// Purpose      : Enumeration Function
//
//------------------------------------------------------------------

HRESULT WINAPI D3DEnumDeviceCallBack(LPGUID lpGuid,
                                     LPSTR lpDeviceDescription,
                                     LPSTR lpDeviceName,
                                     LPD3DDEVICEDESC lpHWDesc,
                                     LPD3DDEVICEDESC lpHELDesc,
                                     LPVOID lpContext)
{
    static BOOL bFoundHardwareDevice = FALSE;

    // No need to enumerate if we already found the device that supports
    if (bFoundHardwareDevice) return D3DENUMRET_OK;

    D3DDeviceInfo* pInfo = (D3DDeviceInfo *)lpContext;

    // Is this a hardware device?
    if (lpHWDesc->dcmColorModel & pInfo->cm)
    {
        // Driver needs to pass some tests....

        // Make sure the driver has ZBuffering capabilities
        if ((lpHWDesc->dwDeviceZBufferBitDepth & DDBD_16) ||
            (lpHWDesc->dwDeviceZBufferBitDepth & DDBD_24) ||
            (lpHWDesc->dwDeviceZBufferBitDepth & DDBD_32))
        {
            // Record the HAL description for later use
            memcpy(&pInfo->HWDeviceDesc, lpHWDesc, sizeof(D3DDEVICEDESC));

            // Record the guid for later use
            pInfo->lpHWGuid = lpGuid;

            // No need to keep looking for any more devices
            bFoundHardwareDevice = TRUE;
        }

        // Yahoo!
        return D3DENUMRET_OK;
    }

    // Is this a software device?
    if (lpHELDesc->dcmColorModel & pInfo->cm)
    {
        // Record the HEL description for later use
        memcpy(&pInfo->SWDeviceDesc, lpHELDesc, sizeof(D3DDEVICEDESC));

        // Record the guid for later use
        pInfo->lpSWGuid = lpGuid;

        g_lpD3DDeviceGuid = lpGuid;
    }

    return D3DENUMRET_OK;
}

//----------------------------------------------------------------------
//
// Function     : InitD3DDevice()
//
// Purpose      : Performs initialisation for correct Direct3D device
//                        RGB, MONO, HAL etc
//
//----------------------------------------------------------------------

BOOL InitD3DDevice()
{
    memset(&g_deviceInfo, 0, sizeof(D3DDeviceInfo));

    // Use RGB colour if in hardware, RAMP if in software

    // Record the colour model that we wish to search for in the structure passed
    // to the enumeration call back
    g_deviceInfo.cm = g_bHardware3D ? D3DCOLOR_RGB : D3DCOLOR_MONO;;

    // Enumerate the drivers
    TRY_D3D(g_lpD3D->EnumDevices(D3DEnumDeviceCallBack, &g_deviceInfo))

    // Test to see whether we have hardware or software
    if (g_deviceInfo.lpHWGuid)
    {
        // We have a hardware driver!

        // Use a video memory based ZBuffer
        g_dwZBufferMemType = DDSCAPS_VIDEOMEMORY;

        // Use 16 bit ZBuffering if possible, higher if not
        if (g_deviceInfo.HWDeviceDesc.dwDeviceZBufferBitDepth & DDBD_16)
        {
            g_dwZBufferBitDepth = 16;
        }
        else if (g_deviceInfo.HWDeviceDesc.dwDeviceZBufferBitDepth & DDBD_24)
        {
            g_dwZBufferBitDepth = 24;
        }
        else if (g_deviceInfo.HWDeviceDesc.dwDeviceZBufferBitDepth & DDBD_32)
        {
            g_dwZBufferBitDepth = 32;
        }
        else
        {
            g_dwZBufferBitDepth = 0;
        }

        // Use Hardware device
        g_lpD3DDeviceGuid = g_deviceInfo.lpHWGuid;
    }
    else
    {
        // We have a software driver!

        // Use a system memory based ZBuffer
        g_dwZBufferMemType = DDSCAPS_SYSTEMMEMORY;

        // And force the bit depth to 16
        g_dwZBufferBitDepth = 16;

        // Default to the software device
        g_lpD3DDeviceGuid = g_deviceInfo.lpSWGuid;
    }

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : InitDirectX()
//
// Purpose      : Initialises DirectX (DirectDraw, Direct3D, Direct3DRM, DirectSound)
//
//----------------------------------------------------------------------

BOOL InitDirectX()
{
    FILE        *fp;
    BYTE        pal[768];

HRESULT     rval;
    DDSURFACEDESC ddsd;

    // Enumerate DirectDraw drivers to see what is installed, preferring one with
    // Hardware 3D capabilities
    TRY_DD(DirectDrawEnumerate(DDEnumCallback, &g_lpDD))

    // If g_lpDD is NULL, there isn't a DirectDraw device with hardware 3D capabilities,
    // so create a device using the HEL
    if (!g_lpDD)
    {
            TRY_DD(DirectDrawCreate(NULL, &g_lpDD, NULL))
    }

    // NOTE : Exclusive mode would normally be set here but because of the splash
    // screen being displayed it isn't. The reason is that the splash screen uses
    // 640x480x8 and that mode may not be available if we are using a hardware 3D
    // DirectDraw device.

    // Zero out caps structures
    memset(&g_driverCaps, 0, sizeof(DDCAPS));
    g_driverCaps.dwSize = sizeof(DDCAPS);

    memset(&g_HELcaps, 0, sizeof(DDCAPS));
    g_HELcaps.dwSize = sizeof(DDCAPS);

    // Get the current display mode as we can use that memory when full
    // screen exclusive
    memset(&ddsd, 0, sizeof ddsd);
    ddsd.dwSize = sizeof ddsd;
    TRY_DD(g_lpDD->GetDisplayMode(&ddsd));
    g_dwGDIMem = ddsd.lPitch * ddsd.dwHeight *
      (ddsd.ddpfPixelFormat.dwRGBBitCount / 8);

    // Get hardware capabilities
    TRY_DD(g_lpDD->GetCaps(&g_driverCaps, &g_HELcaps));

    // Global to determine whether we have hardware 3D capabilities or not
    g_bHardware3D = g_driverCaps.dwCaps & DDCAPS_3D;

    // Create Direct3D object
    TRY_D3D(g_lpDD->QueryInterface(IID_IDirect3D, (LPVOID *)&g_lpD3D));

    // Enumerate Direct3D devices, preferring hardware rendering over software
    if (!InitD3DDevice())
    {
            RegError("Error locating suitable Direct3D driver!");
            return FALSE;
    }

    // Enumerate all the display modes, this is done after locating the 3D device so
    // that any mode that is not compatible with the 3D device does not get added to
    // the list of valid modes.
    TRY_DD(g_lpDD->EnumDisplayModes(0, NULL, NULL, DDEnumDisplayModesCallback))

    // Sort display modes into lowest width * height first
    SortDisplayModes();

    // Create Direct3D RM object
    TRY_D3DRM(Direct3DRMCreate(&g_lpD3DRM))

    // Set default amount of texture colours
    g_lpD3DRM->SetDefaultTextureColors(16);

    // Set default amount of texture shades
    g_lpD3DRM->SetDefaultTextureShades(16);

    // Create DirectSound object
    rval = DirectSoundCreate(NULL, &g_lpDS, NULL);

    // Determine whether sound is present
    g_bSoundPresent = rval == DS_OK ? TRUE : FALSE;

    if (g_bSoundPresent)
    {
        // Set the DirectSound cooperative level
        TRY_DS(g_lpDS->SetCooperativeLevel(g_hWnd, DSSCL_NORMAL))

                // Create the Direct 3D Sound Buffer
                g_3DSoundBuffer = CreateSoundBuffer3D();
                if(g_3DSoundBuffer == NULL)
                {
                        RegError("Not able to create Direct 3D Sound Buffer");
            return FALSE;
                }
                // Query interface for Direct 3D Sound Listener object
                if(DS_OK != g_3DSoundBuffer->QueryInterface(IID_IDirectSound3DListener, (void**)&g_lpDs3dListener))
                {
                        RegError("Not able to create Direct 3D Sound Listener object");
            return FALSE;
                }
                // Set the Direct 3D Sound Rolloff Factor
                g_lpDs3dListener->SetRolloffFactor((FLOAT).01,DS3D_DEFERRED);
                // Change listener's orientation
                g_lpDs3dListener->SetOrientation(-D3DVAL(1), D3DVAL(0), D3DVAL(0), D3DVAL(0), D3DVAL(1), D3DVAL(0),DS3D_DEFERRED);
                // Commit the changes to Rolloff Factor and orientation
                g_lpDs3dListener->CommitDeferredSettings();

        // Null out all the sound pointers
        for (int i = 0; i < NUM_SOUNDS; i ++)
        {
            g_lpSounds[i] = NULL;
        }

        // Load the sounds
        if (!CreateBufferFromWaveFile("INTRO.WAV", INTRO))
        {
            RegError("Couldn't load INTRO.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("PUNCH1.WAV", PLAYER1_PUNCH1))
        {
            RegError("Couldn't load PUNCH1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("PUNCH3.WAV", PLAYER1_PUNCH2))
        {
            RegError("Couldn't load PUNCH3.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("PUNCH2.WAV", PLAYER2_PUNCH1))
        {
            RegError("Couldn't load PUNCH2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("PUNCH4.WAV", PLAYER2_PUNCH2))
        {
            RegError("Couldn't load PUNCH4.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("WALK0.WAV", PLAYER1_WALK))
        {
            RegError("Couldn't load WALK0.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("WALK1.WAV", PLAYER2_WALK))
        {
            RegError("Couldn't load WALK1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("WHOOSH1.WAV", WHOOSH1))
        {
            RegError("Couldn't load WHOOSH1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("WHOOSH2.WAV", WHOOSH2))
        {
            RegError("Couldn't load WHOOSH2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("DEFEND1.WAV", PLAYER1_OUCH))
        {
            RegError("Couldn't load DEFEND1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("DEFEND2.WAV", PLAYER2_OUCH))
        {
            RegError("Couldn't load DEFEND2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("HEAD.WAV", HEAD_SPRING))
        {
            RegError("Couldn't load HEAD.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("BLOCK1.WAV", BLOCK1))
        {
            RegError("Couldn't load BLOCK1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("BLOCK2.WAV", BLOCK2))
        {
            RegError("Couldn't load BLOCK2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("BLOCK3.WAV", BLOCK3))
        {
            RegError("Couldn't load BLOCK3.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("CLOOP.WAV", CROWD_LOOP))
        {
            RegError("Couldn't load CLOOP.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("CBOO.WAV", VICTORY_BOO))
        {
            RegError("Couldn't load CBOO.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("CYEAH.WAV", VICTORY_YEAH))
        {
            RegError("Couldn't load CYEAH.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVUP1.WAV", SERVO_UP_1))
        {
            RegError("Couldn't load REVUP1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVUP2.WAV", SERVO_UP_2))
        {
            RegError("Couldn't load REVUP2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVUP3.WAV", SERVO_UP_3))
        {
            RegError("Couldn't load REVUP3.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVDN1.WAV", SERVO_DOWN_1))
        {
            RegError("Couldn't load REVDOWN1.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVDN2.WAV", SERVO_DOWN_2))
        {
            RegError("Couldn't load REVDOWN2.WAV!");
            return FALSE;
        }

        if (!CreateBufferFromWaveFile("REVDN3.WAV", SERVO_DOWN_3))
        {
            RegError("Couldn't load REVDOWN3.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM1.WAV", RANDOM1))
        {
            RegError("Couldn't load RANDOM1.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM2.WAV", RANDOM2))
        {
            RegError("Couldn't load RANDOM2.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM3.WAV", RANDOM3))
        {
            RegError("Couldn't load RANDOM3.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM4.WAV", RANDOM4))
        {
            RegError("Couldn't load RANDOM4.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM5.WAV", RANDOM5))
        {
            RegError("Couldn't load RANDOM5.WAV!");
            return FALSE;
        }
                if (!CreateBufferFromWaveFile("RANDOM6.WAV", RANDOM6))
        {
            RegError("Couldn't load RANDOM6.WAV!");
            return FALSE;
        }
    }

    // Load Rockem3D's palette
    fp = fopen("ROCKEM3D.PAL", "rb");
    if (!fp)
    {
        RegError("Couldn't load ROCKEM3D.PAL!");
        return FALSE;
    }

    // Read in the raw rgb's
    fread(pal, 768, 1, fp);

    // Close the file
    fclose(fp);

    // Set up palette
    g_rPal[0].peFlags   = D3DPAL_READONLY;
    g_rPal[253].peFlags = D3DPAL_READONLY;
    g_rPal[254].peFlags = D3DPAL_READONLY;
    g_rPal[255].peFlags = D3DPAL_READONLY;

    for (int i = 1; i < 253; i++)
    {
        g_rPal[i].peRed   = pal[i * 3];
        g_rPal[i].peGreen = pal[(i * 3) + 1];
        g_rPal[i].peBlue  = pal[(i * 3) + 2];
        g_rPal[i].peFlags = D3DPAL_READONLY;
    }

    // Set the entries 253 and 254 to a colour for the power bars
    g_rPal[253].peRed   = 0;
    g_rPal[253].peGreen = 0;
    g_rPal[253].peBlue  = 255;

    g_rPal[254].peRed   = 255;
    g_rPal[254].peGreen = 0;
    g_rPal[254].peBlue  = 0;

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : SetDirectDrawExclusiveMode()
//
// Purpose      : Sets exclusive mode for DirectDraw
//
//----------------------------------------------------------------------

BOOL SetDirectDrawExclusiveMode()
{
    TRY_DD(g_lpDD->SetCooperativeLevel(g_hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX))

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : TermDirectX()
//
// Purpose      : Destroys all the DirectX objects
//
//----------------------------------------------------------------------

void TermDirectX()
{
    // Destroy everything in the reverse order in which they were created
        int i;

    // Destroy rendering device
    if (g_lpD3DDevice)
    {
        g_lpD3DDevice->Release();
        g_lpD3DDevice = NULL;
    }

    // Destroy all surfaces
    if (g_lpZBuffer)
    {
        g_lpZBuffer->Release();
        g_lpZBuffer = NULL;
    }

    if (g_lpBackBuffer)
    {
        g_lpBackBuffer->Release();
        g_lpBackBuffer = NULL;
    }

    if (g_lpPrimary)
    {
        g_lpPrimary->Release();
        g_lpPrimary = NULL;
    }

    // Restore the original video mode
    if(g_lpDD)
        g_lpDD->RestoreDisplayMode();

    // Destroy sounds
    for (i = 0; i < NUM_SOUNDS; i ++)
    {
        if (g_lpSounds[i])
        {
            g_lpSounds[i]->Release();
            g_lpSounds[i] = NULL;
        }
    }

        // Destroy DirectSound3D buffers
    for (i = 0; i < NUM_SOUNDS; i ++)
    {
        if (g_lp3dSounds[i])
        {
            g_lp3dSounds[i]->Release();
            g_lp3dSounds[i] = NULL;
        }
    }

        // Destroy DirectSound3D Listener object
    if (g_lpDs3dListener)
    {
        g_lpDs3dListener->Release();
        g_lpDs3dListener = NULL;
    }
        // Destroy DirectSound3D Primary buffer
    if (g_3DSoundBuffer)
    {
        g_3DSoundBuffer->Release();
        g_3DSoundBuffer = NULL;
    }

    // Destroy DirectSound object
    if (g_lpDS)
    {
        g_lpDS->Release();
        g_lpDS = NULL;
    }

    // Destroy Direct3D RM object
    if (g_lpD3DRM)
    {
        g_lpD3DRM->Release();
        g_lpD3DRM = NULL;
    }

    // Destroy Direct3D object
    if (g_lpD3D)
    {
        g_lpD3D->Release();
        g_lpD3D = NULL;
    }

    // Destroy DirectDraw object
    if (g_lpDD)
    {
        g_lpDD->Release();
        g_lpDD = NULL;
    }
}

//----------------------------------------------------------------------
//
// Function     : SoftwareGUID();
//
// Purpose      : Returns a pointer to a software driver GUID
//
//----------------------------------------------------------------------
HRESULT SoftwareGUID(LPGUID* ret_guid)
{
    static D3DFINDDEVICERESULT result;
    D3DFINDDEVICESEARCH search;
    HRESULT err;

    memset(&search, 0, sizeof search);
    search.dwSize = sizeof search;
    search.dwFlags = D3DFDS_HARDWARE | D3DFDS_COLORMODEL;
    search.bHardware = FALSE;
    search.dcmColorModel = D3DCOLOR_RGB;
    result.dwSize = sizeof(D3DFINDDEVICERESULT);

    if (err = g_lpD3D->FindDevice(&search, &result))
return err;
    *ret_guid = &result.guid;
    return D3D_OK;
}

//----------------------------------------------------------------------
//
// Function     : EnterVideoMode()
//
// Purpose      : Calls EnterVideoModeWHBD with mode information
//
//----------------------------------------------------------------------

BOOL EnterVideoMode(int mode)
{
    int width    = g_vidModes[mode].width;
    int height   = g_vidModes[mode].height;
    int bitdepth = g_vidModes[mode].bpp;
    g_dwCurrMode = mode;

    // Try to enter video mode described by width, height and bitdepth
    return EnterVideoModeWHBD(width, height, bitdepth);
}

//----------------------------------------------------------------------
//
// Function     : EnterVideoModeWHBD()
//
// Purpose      : Switches video mode and creates all neccessary structures
//                        required for rendering
//
//----------------------------------------------------------------------

BOOL EnterVideoModeWHBD(int width, int height, int bitdepth)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;

    // Destroy all existing viewports, devices and surfaces

    // Destroy Direct3D RM viewport
    if (g_lpD3DRMViewport)
    {
        g_lpD3DRMViewport->Release();
        g_lpD3DRMViewport = NULL;
    }

    // Destroy Direct3D RM device
    if (g_lpD3DRMDevice)
    {
        g_lpD3DRMDevice->Release();
        g_lpD3DRMDevice = NULL;
    }

    // Destroy Direct3D device
    if (g_lpD3DDevice)
    {
        g_lpD3DDevice->Release();
        g_lpD3DDevice = NULL;
    }

    // Destroy ZBuffer
    if (g_lpZBuffer)
    {
        g_lpZBuffer->Release();
        g_lpZBuffer = NULL;
    }

    // Destroy Primary surface
    if (g_lpPrimary)
    {
        g_lpPrimary->Release();
        g_lpPrimary = NULL;
    }

    // Switch video mode
    TRY_DD(g_lpDD->SetDisplayMode(width, height, bitdepth))

    // First, create complex flipping primary surface

    // Fill out surface description
    memset(&ddsd, sizeof(DDSURFACEDESC), 0);
    ddsd.dwSize                 = sizeof(DDSURFACEDESC);
    ddsd.dwFlags                = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps         = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_3DDEVICE;
    ddsd.dwBackBufferCount      = 1;

    // Create the primary surface with 1 back buffer
    TRY_DD(g_lpDD->CreateSurface(&ddsd, &g_lpPrimary, NULL))

    // Get pointer to back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    TRY_DD(g_lpPrimary->GetAttachedSurface(&ddscaps, &g_lpBackBuffer))

    // Only create a ZBuffer if g_dwZBufferBitDepth > 0
    if (g_dwZBufferBitDepth)
    {
HRESULT err;

        // Then, create Z-Buffer. The g_dwZBufferMemType and g_dwZBufferBitDepth variables
        // are set up when the Direct3D device enumeration is done at runtime
        memset(&ddsd, sizeof(DDSURFACEDESC), 0);
        ddsd.dwSize             = sizeof(DDSURFACEDESC);
        ddsd.dwFlags            = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH;;
        ddsd.dwWidth            = width;
        ddsd.dwHeight           = height;
        ddsd.ddsCaps.dwCaps     = DDSCAPS_ZBUFFER | g_dwZBufferMemType;
        ddsd.dwZBufferBitDepth  = g_dwZBufferBitDepth;

        // Create the ZBuffer
if (err = g_lpDD->CreateSurface(&ddsd, &g_lpZBuffer, NULL))
{
    if (err == DDERR_OUTOFVIDEOMEMORY)
    {
// Out of video memory - create system memory zbuffer for software renderer
memset(&ddsd, sizeof(DDSURFACEDESC), 0);
ddsd.dwSize             = sizeof(DDSURFACEDESC);
ddsd.dwFlags            = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH;;
ddsd.dwWidth            = width;
ddsd.dwHeight           = height;
ddsd.ddsCaps.dwCaps     = DDSCAPS_ZBUFFER | DDSCAPS_SYSTEMMEMORY;
ddsd.dwZBufferBitDepth  = 16;
if (err = g_lpDD->CreateSurface(&ddsd, &g_lpZBuffer, NULL))
{
    TRY_DD(err);
}
else
{
    g_bOutOfVideoMemory = TRUE;
}
    }
    else
    {
TRY_DD(err);
    }
}
else
{
    g_bOutOfVideoMemory = FALSE;
}

        // Attach ZBuffer to the back buffer
        TRY_DD(g_lpBackBuffer->AddAttachedSurface(g_lpZBuffer))
    }

    // Retrieve the caps of the primary surface
    TRY_DD(g_lpPrimary->GetCaps(&ddscaps))

    // Create and attach palette (only if we in 8-bit palettized colour modes)
    if ((bitdepth == 8) && (ddscaps.dwCaps & DDCAPS_PALETTE))
    {
        // Create the palette
        TRY_DD(g_lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE, g_rPal, &g_lpPalette, NULL))

        // Set the back buffer's palette
        TRY_DD(g_lpBackBuffer->SetPalette(g_lpPalette))

        // Set the primary surface's palette
        TRY_DD(g_lpPrimary->SetPalette(g_lpPalette))
    }

    // Create Direct3D device
    if (!g_bOutOfVideoMemory)
    {
TRY_D3D(g_lpBackBuffer->QueryInterface(*g_lpD3DDeviceGuid, (LPVOID *)&g_lpD3DDevice))
    }
    else
    {
LPGUID guidSoftware;
TRY_D3D(SoftwareGUID(&guidSoftware))
TRY_D3D(g_lpBackBuffer->QueryInterface(*guidSoftware, (LPVOID*)&g_lpD3DDevice))
    }

    // Create Direct3D RM Device from Direct3D Device
    TRY_D3DRM(g_lpD3DRM->CreateDeviceFromD3D(g_lpD3D, g_lpD3DDevice, &g_lpD3DRMDevice))

    // Set the buffer count to 2 so D3DRM can keep track of extents for fullscreen flipping surface
    TRY_D3DRM(g_lpD3DRMDevice->SetBufferCount(2))

    // Render using gouraud shading
    g_lpD3DRMDevice->SetQuality(D3DRMRENDER_GOURAUD);

    // And no dithering please (NOTE : dithering is for looks not speed!)
    g_lpD3DRMDevice->SetDither(FALSE);

    // Set texture quality
    g_lpD3DRMDevice->SetTextureQuality(D3DRMTEXTURE_NEAREST);

    // Set the number of shades for lighting
    g_lpD3DRMDevice->SetShades(8);

    // Create RM viewport from device and camera (camera has already been initialised
    // by InitScene() in RM.CPP)
    TRY_D3DRM(g_lpD3DRM->CreateViewport(g_lpD3DRMDevice,
                                        g_lpCamera,
                                        0,
                                        0,
                                        width,
                                        height,
                                        &g_lpD3DRMViewport))

    // Set the back clipping plane to be something fairly large
    g_lpD3DRMViewport->SetBack(D3DVAL(30000.0f));

    // Diddle with the lights depending on what driver we are using
    switch (g_deviceInfo.cm)
    {
        case D3DCOLOR_MONO :
        {
            // Enable the directional light only to hit the players, not the arena
            g_lpDir->SetEnableFrame(g_lpPlayers);
        }
        break;

        case D3DCOLOR_RGB :
        {
            // Enable the directional light to hit all objects
            g_lpDir->SetEnableFrame(g_lpScene);
        }
        break;
    }

    // Record video mode information
    g_vidModeX = width;
    g_vidModeY = height;
    g_vidModeBIT = bitdepth;

    // And calculate values for the power bars
    g_xratio = (float)width / 1000.0f;
    g_yratio = (float)height / 1000.0f;

    g_lbar1 = (DWORD)(float)(50.0f * g_xratio);
    g_wbar1 = (DWORD)(float)(400.0f * g_xratio);
    g_lbar2 = (DWORD)(float)(550.0f * g_xratio);
    g_wbar2 = g_wbar1;
    g_hbar1 = (DWORD)(float)(30.0f * g_yratio);
    g_hbar2 = (DWORD)(float)(20.0f * g_yratio);

    // Finally, calculate the height of the current font
    HDC hDC;
    hDC = ::GetDC(g_hWnd);

    TEXTMETRIC txtMetric;
    GetTextMetrics(hDC, &txtMetric);

    ::ReleaseDC(g_hWnd, hDC);

    g_dwFontHeight   = txtMetric.tmHeight;
    g_dwAveCharWidth = txtMetric.tmAveCharWidth;

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : EnterPrevVideoMode()
//
// Purpose      : Enters previous mode in vidModes[]
//
//----------------------------------------------------------------------

BOOL EnterPrevVideoMode()
{
    if (g_dwCurrMode > 0)
    {
        return EnterVideoMode(-- g_dwCurrMode);
    }

    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : EnterNextVideoMode()
//
// Purpose      : Enters nextmode in vidModes[]
//
//----------------------------------------------------------------------

BOOL EnterNextVideoMode()
{
    if (g_dwCurrMode < g_dwNumModes - 1)
    {
        return EnterVideoMode(++ g_dwCurrMode);
    }

    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : EnterLowestVideoMode()
//
// Purpose      : Enters lowest mode in vidModes[]
//
//----------------------------------------------------------------------

BOOL EnterLowestVideoMode()
{
    return EnterVideoMode(0);
}

//----------------------------------------------------------------------
//
// Function     : EnterHighestVideoMode()
//
// Purpose      : Enters highest mode in vidModes[]
//
//----------------------------------------------------------------------

BOOL EnterHighestVideoMode()
{
    return EnterVideoMode(g_dwNumModes - 1);
}

//----------------------------------------------------------------------
//
// Function     : ReenterVideoMode()
//
// Purpose      : Re-enters current video mode
//
//----------------------------------------------------------------------

BOOL ReenterCurrentVideoMode()
{
    return EnterVideoMode(g_dwCurrMode);
}

//----------------------------------------------------------------------
//
// Function     : CleanUp()
//
// Purpose      : Destroys all surfaces and rendering devices
//
//----------------------------------------------------------------------

void CleanUp()
{
    // Destroy everything in the reverse order that they were created

    // Destroy viewport
    if (g_lpD3DRMViewport)
    {
        g_lpD3DRMViewport->Release();
        g_lpD3DRMViewport = NULL;
    }

    // Destroy rendering devices
    if (g_lpD3DRMDevice)
    {
        g_lpD3DRMDevice->Release();
        g_lpD3DRMDevice = NULL;
    }

    if (g_lpD3DDevice)
    {
        g_lpD3DDevice->Release();
        g_lpD3DDevice = NULL;
    }

    // Destroy all surfaces
    if (g_lpZBuffer)
    {
        g_lpZBuffer->Release();
        g_lpZBuffer = NULL;
    }

    if (g_lpBackBuffer)
    {
        g_lpBackBuffer->Release();
        g_lpBackBuffer = NULL;
    }

    if (g_lpPrimary)
    {
        g_lpPrimary->Release();
        g_lpPrimary = NULL;
    }

    // Destroy palette
    if (g_lpPalette)
    {
        g_lpPalette->Release();
        g_lpPalette = NULL;
    }

    // Restore the original video mode
    g_lpDD->RestoreDisplayMode();
}

//----------------------------------------------------------------------
//
// Function     : DoSplashScreen()
//
// Purpose      : Draws splash screen (if possible)
//
//----------------------------------------------------------------------

BOOL DoSplashScreen(DWORD delay)
{
    LPDIRECTDRAWSURFACE backbuffer = NULL;
    DDSURFACEDESC       ddsd;
    DDSCAPS             ddscaps;
    HRESULT             rval;
    DWORD               dwStart;
    int                 i;
    FILE                *fp;
    BYTE                rgbs[768], scanbuf[640];
    void                *lpSurf;
    BYTE                *pSurf;
    DWORD               dummy;

    delay;

    // Create a DirectDraw device
    rval = DirectDrawCreate(NULL, &g_lpSplashDD, NULL);
    if (rval != DD_OK) goto fail;

    // Set cooperative level
    rval = g_lpSplashDD->SetCooperativeLevel(g_hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
    if (rval != DD_OK) goto fail;

    // Attempt to enter 640x480x8
    // Switch video mode
    rval = g_lpSplashDD->SetDisplayMode(640, 480, 8);
    if (rval != DD_OK) goto fail;

    // Create complex flipping primary surface

    // Clear surface caps structure
    memset(&ddscaps, 0, sizeof(DDSCAPS));

    // Fill out surface description
    memset(&ddsd, sizeof(DDSURFACEDESC), 0);
    ddsd.dwSize                 = sizeof(DDSURFACEDESC);
    ddsd.dwFlags                = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps         = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount      = 1;

    // Create the primary surface with 1 back buffer
    rval = g_lpSplashDD->CreateSurface(&ddsd, &g_lpSplashPrimary, NULL);
    if (rval != DD_OK) goto fail;

    // Get pointer to back buffer
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    rval = g_lpSplashPrimary->GetAttachedSurface(&ddscaps, &backbuffer);
    if (rval != DD_OK) goto fail;

    // Open the splash screen file
    fp = fopen("ROCKEM3D.BIN", "rb");
    if (!fp) goto fail;

    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);

    // Lock the backbuffer to get a pointer to it
    rval = backbuffer->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR, NULL);
    if (rval != DD_OK) goto fail;

    // Read the image into the backbuffer
    lpSurf = ddsd.lpSurface;

    // Read in lines of image, accounting for pitch
    pSurf = (BYTE *)lpSurf;

    // Read first dword from .BIN file, that corresponds to WIDTH and HEIGHT (two words)
    fread(&dummy, 4, 1, fp);

    for (i = 0; i < 480; i ++)
    {
        fread(scanbuf, 640, 1, fp);
        memcpy(pSurf, scanbuf, 640);
        pSurf += ddsd.lPitch;
    }

    // Close the file
    fclose(fp);

    // Unlock the surface
    rval = backbuffer->Unlock(lpSurf);
    if (rval != DD_OK) goto fail;

    // Set up the palette
    fp = fopen("SPLASH.PAL", "rb");
    if (!fp) goto fail;

    fread(rgbs, 768, 1, fp);
    fclose(fp);

    // Set up the PALETTEENTRY's from the 768 byte RGB array
    PALETTEENTRY ppe[256];
    for (i = 0; i < 256; i ++)
    {
        ppe[i].peRed   = rgbs[i * 3];
        ppe[i].peGreen = rgbs[(i * 3) + 1];
        ppe[i].peBlue  = rgbs[(i * 3) + 2];
        ppe[i].peFlags = PC_NOCOLLAPSE;
    }

    // Create the palette
    //rval = g_lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE, ppe, &g_lpPalette, NULL);
    rval = g_lpSplashDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE, ppe, &g_lpSplashPalette, NULL);
    if (rval != DD_OK) goto fail;

    // Set the backbuffer's palette
    rval = g_lpSplashPrimary->SetPalette(g_lpSplashPalette);
    if (rval != DD_OK) goto fail;

    // And flip the splash screen into view
    rval = g_lpSplashPrimary->Flip(NULL, DDFLIP_WAIT);
    if (rval != DD_OK) goto fail;

    // Wait for delay milliseconds or a specific keypress
    dwStart = timeGetTime();

    while (timeGetTime() - dwStart < delay)

{
        if (GetAsyncKeyState(VK_SPACE) & 0x8000) break;
        if (GetAsyncKeyState(VK_RETURN) & 0x8000) break;
        if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) break;
        if ((GetAsyncKeyState(VK_MENU) & 0x8000) && (GetAsyncKeyState(VK_F4) & 0x8000)) break;
    }

    backbuffer->Release();

    // Yahoo!
    return TRUE;

    fail:

    // Close file
    if (fp)
    {
        fclose(fp);
    }

    // Release palette
    if (g_lpSplashPalette)
    {
        g_lpSplashPalette->Release();
        g_lpSplashPalette = NULL;
    }

    // Release primary surface
    if (g_lpSplashPrimary)
    {
        g_lpSplashPrimary->Release();
        g_lpSplashPrimary = NULL;
    }

    if (g_lpSplashDD)
    {
        g_lpSplashDD->Release();
        g_lpSplashDD = NULL;
    }

    // Yahoo!
    return FALSE;
}

//----------------------------------------------------------------------
//
// Function     : ReleaseSplashScreen()
//
// Purpose      : Releases the splash screen
//
//----------------------------------------------------------------------

void ReleaseSplashScreen()
{
    // Release palette
    if (g_lpSplashPalette)
    {
        g_lpSplashPalette->Release();
        g_lpSplashPalette = NULL;
    }

    // Release primary surface
    if (g_lpSplashPrimary)
    {
        g_lpSplashPrimary->Release();
        g_lpSplashPrimary = NULL;
    }

    if (g_lpSplashDD)
    {
        g_lpSplashDD->Release();
        g_lpSplashDD = NULL;
    }
}

//----------------------------------------------------------------------
//
// Function     : RestoreSurfaces()
//
// Purpose      : Restores all surfaces if they somehow got lost (Alt-Tab)
//
//----------------------------------------------------------------------

BOOL RestoreSurfaces()
{
    // Attempt to restore primary surface
    if (g_lpPrimary)
    {
        if (g_lpPrimary->IsLost()) TRY_DD(g_lpPrimary->Restore())
    }

    // Attempt to restore zbuffer
    if (g_lpZBuffer)
    {
        if (g_lpZBuffer->IsLost()) TRY_DD(g_lpZBuffer->Restore())
    }

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : CreateSoundBuffer()
//
// Purpose      : Creates a DirectSound buffer
//
//----------------------------------------------------------------------

BOOL CreateSoundBuffer(DWORD dwBuf, DWORD dwBufSize, DWORD dwFreq, DWORD dwBitsPerSample, DWORD dwBlkAlign, BOOL bStereo)
{
    PCMWAVEFORMAT pcmwf;
    DSBUFFERDESC dsbdesc;

    // Set up wave format structure.
    memset( &pcmwf, 0, sizeof(PCMWAVEFORMAT) );
    pcmwf.wf.wFormatTag         = WAVE_FORMAT_PCM;
    pcmwf.wf.nChannels          = bStereo ? 2 : 1;
    pcmwf.wf.nSamplesPerSec     = dwFreq;
    pcmwf.wf.nBlockAlign        = (WORD)dwBlkAlign;
    pcmwf.wf.nAvgBytesPerSec    = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
    pcmwf.wBitsPerSample        = (WORD)dwBitsPerSample;

    // Set up DSBUFFERDESC structure.
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));  // Zero it out.
    dsbdesc.dwSize              = sizeof(DSBUFFERDESC);
    dsbdesc.dwFlags             = DSBCAPS_CTRL3D;               // Needed creation flag for Direct 3D Sound
        dsbdesc.dwBufferBytes       = dwBufSize;
    dsbdesc.lpwfxFormat         = (LPWAVEFORMATEX)&pcmwf;

    TRY_DS(g_lpDS->CreateSoundBuffer(&dsbdesc, &g_lpSounds[dwBuf], NULL))

        // Query for the 3D Sound Buffer interface.
    TRY_DS(g_lpSounds[dwBuf]->QueryInterface(IID_IDirectSound3DBuffer, (void**) &g_lp3dSounds[dwBuf]));

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : ReadData()
//
// Purpose      : Reads in data from a wave file
//
//----------------------------------------------------------------------

BOOL ReadData(LPDIRECTSOUNDBUFFER lpDSB, FILE* pFile, DWORD dwSize, DWORD dwPos)
{
    // Seek to correct position in file (if necessary)
    if (dwPos != 0xffffffff)
    {
        if (fseek(pFile, dwPos, SEEK_SET) != 0)
        {
            return FALSE;
        }
    }

    // Lock data in buffer for writing
    LPVOID pData1;
    DWORD  dwData1Size;
    LPVOID pData2;
    DWORD  dwData2Size;
    HRESULT rval;

    rval = lpDSB->Lock(0, dwSize, &pData1, &dwData1Size, &pData2, &dwData2Size, DSBLOCK_FROMWRITECURSOR);
    if (rval != DS_OK)
    {
        return FALSE;
    }

    // Read in first chunk of data
    if (dwData1Size > 0)
    {
        if (fread(pData1, dwData1Size, 1, pFile) != 1)
        {
                        char holder[256];
                        wsprintf(holder,"Data1 : %d, dwdata: %d, pFile: %d",pData1,dwData1Size,pFile);
                        OutputDebugString(holder);
            return FALSE;
        }
    }

    // read in second chunk if necessary
    if (dwData2Size > 0)
    {
        if (fread(pData2, dwData2Size, 1, pFile) != 1)
        {
            return FALSE;
        }
    }

    // Unlock data in buffer
    rval = lpDSB->Unlock(pData1, dwData1Size, pData2, dwData2Size);
    if (rval != DS_OK)
    {
        return FALSE;
    }

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : CreateSoundBufferFromWaveFile()
//
// Purpose      : Creates a DirectSound buffer from a wave file
//
//----------------------------------------------------------------------

BOOL CreateBufferFromWaveFile(char* FileName, DWORD dwBuf)
{
    // Open the wave file
    FILE* pFile = fopen(FileName,"rb");
    if (pFile == NULL) return FALSE;

    // Read in the wave header
    WaveHeader wavHdr;
    if (fread(&wavHdr, sizeof(wavHdr), 1, pFile) != 1)
    {
        fclose(pFile);
        return NULL;
    }

    // Figure out the size of the data region
    DWORD dwSize = wavHdr.dwDSize;

    // Is this a stereo or mono file?
    BOOL bStereo = wavHdr.wChnls > 1 ? TRUE : FALSE;

    // Create the sound buffer for the wave file
    if (!CreateSoundBuffer(dwBuf, dwSize, wavHdr.dwSRate, wavHdr.BitsPerSample, wavHdr.wBlkAlign, bStereo))
    {
        // Close the file
        fclose(pFile);

        return FALSE;
    }

    // Read the data for the wave file into the sound buffer
    if (!ReadData(g_lpSounds[dwBuf], pFile, dwSize, sizeof(wavHdr)))
    {
        fclose(pFile);
        return FALSE;
    }

    // Close out the wave file
    fclose(pFile);

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : StopAllSounds()
//
// Purpose      : Stops all sounds
//
//----------------------------------------------------------------------

BOOL StopAllSounds()
{
    // Make sure we have a valid sound buffer
    for (int i = 0; i < NUM_SOUNDS; i ++)
    {
        if (g_lpSounds[i])
        {
            DWORD dwStatus;
            TRY_DS(g_lpSounds[i]->GetStatus(&dwStatus));

            if ((dwStatus & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
            {
                // Play the sound
                TRY_DS(g_lpSounds[i]->Stop())
            }
        }
    }

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : PlaySoundDS()
//
// Purpose      : Plays a sound using direct sound
//
//                      D3DVECTOR
//----------------------------------------------------------------------

BOOL PlaySoundDS(DWORD dwSound,D3DVECTOR d3dvPos, DWORD dwFlags)
{

    if (g_bSoundPaused) return TRUE;

    if (!g_bSoundPresent) return TRUE;

    // Make sure the sound is valid
    if (dwSound >= NUM_SOUNDS) return FALSE;

    // Make sure we have a valid sound buffer
    if (g_lpSounds[dwSound])
    {
        DWORD dwStatus;
        TRY_DS(g_lpSounds[dwSound]->GetStatus(&dwStatus));

        if ((dwStatus & DSBSTATUS_PLAYING) != DSBSTATUS_PLAYING)
        {
           // Set the 3D position of the sound, using supplied position vector.
           TRY_DS(g_lp3dSounds[dwSound]->SetPosition(d3dvPos.x, d3dvPos.y, d3dvPos.z, DS3D_IMMEDIATE));
            // Play the sound
           TRY_DS(g_lpSounds[dwSound]->Play(0, 0, dwFlags));
        }
    }

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function     : RecalcPowerBars()
//
// Purpose      : Calculates width of power bars based upon current
//                        screen resolution
//
//----------------------------------------------------------------------

void RecalcPowerBars(DWORD player1health, DWORD player2health)
{
    g_wbar1 = (DWORD)(float)((400.0f * ((float)player1health / 100.0f)) * g_xratio);
    g_wbar2 = (DWORD)(float)((400.0f * ((float)player2health / 100.0f)) * g_xratio);
}

//----------------------------------------------------------------------
//
// Function     : CreateSoundBuffer3D()
//
// Purpose      : Creates a 3D sound buffer and returns the sound buffer
//
//----------------------------------------------------------------------

IDirectSoundBuffer *CreateSoundBuffer3D()
{
    IDirectSoundBuffer *pDSB = NULL;
    DSBUFFERDESC dsBD = {0};

        ZeroMemory( &dsBD, sizeof(DSBUFFERDESC));
        dsBD.dwSize = sizeof(dsBD);
    dsBD.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;
        dsBD.dwBufferBytes = 0;  //must be zero for primary buffer..

        if (g_lpDS->CreateSoundBuffer(&dsBD, &pDSB, NULL) != DS_OK)
                pDSB = NULL;

    return pDSB;
}

//----------------------------------------------------------------------
//
// Function     : PlayRandomWave()
//
// Purpose      : Creates a random wave at a random spot
//
//----------------------------------------------------------------------

VOID CALLBACK PlayRandomWave( HWND hwnd, UINT uMsg,UINT idEvent, DWORD dwTime )
{
        D3DVECTOR       d3dPosition;    // DSVECTOR used for the positon of the wave...

        int RandomWave[]={RANDOM1,RANDOM2,RANDOM3,RANDOM4,RANDOM5,RANDOM6};

        g_lpCamera->GetPosition(g_lpScene, &d3dPosition);
        // fill in the position generated for the wave file.
        d3dPosition.x += (rand() % 2 == 0 ? (rand() % 250) : -(rand() % 250));
        d3dPosition.z += (rand() % 2 == 0 ? (rand() % 250) : -(rand() % 250));

        PlaySoundDS(RandomWave[rand() % (sizeof(RandomWave) / sizeof(RandomWave[0]))],d3dPosition);
        return;
}


DIRECTX.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: directx.h
 *
 ***************************************************************************/

#ifndef __DIRECTX_H_
#define __DIRECTX_H_

// Includes....
#include "d3drmwin.h"
#include "dsound.h"
#include "stdio.h"

// Defines....
#define NUM_SOUNDS      30

#define INTRO           0
#define PLAYER1_PUNCH1  1
#define PLAYER1_PUNCH2  2
#define PLAYER2_PUNCH1  3
#define PLAYER2_PUNCH2  4
#define PLAYER1_WALK    5
#define PLAYER2_WALK    6
#define WHOOSH1         7
#define WHOOSH2         8
#define PLAYER1_OUCH    9
#define PLAYER2_OUCH    10
#define HEAD_SPRING     11
#define BLOCK1          12
#define BLOCK2          13
#define BLOCK3          14
#define VICTORY_BOO     15
#define VICTORY_YEAH    16
#define CROWD_LOOP      17
#define SERVO_DOWN_1    18
#define SERVO_DOWN_2    19
#define SERVO_DOWN_3    20
#define SERVO_UP_1      21
#define SERVO_UP_2      22
#define SERVO_UP_3      23
#define RANDOM124
#define RANDOM225
#define RANDOM326
#define RANDOM4 27
#define RANDOM528
#define RANDOM629

#define NUM_VID_MODES   50

// Macros

// The following macros are used for proper error handling for DirectDraw,
// DirectSound, Direct3D and Direct3D retained mode
#define TRY_DD(exp) { { HRESULT rval = exp; if (rval != DD_OK) { TraceErrorDD(rval, __FILE__, __LINE__); return FALSE; } } }
#define TRY_DS(exp) { { HRESULT rval = exp; if (rval != DS_OK) { TraceErrorDS(rval, __FILE__, __LINE__); return FALSE; } } }
#define TRY_D3D(exp) { { HRESULT rval = exp; if (rval != D3D_OK) { TraceErrorD3D(rval, __FILE__, __LINE__); return FALSE; } } }
#define TRY_D3DRM(exp) { { HRESULT rval = exp; if (rval != D3DRM_OK) { TraceErrorD3DRM(rval, __FILE__, __LINE__); return FALSE; } } }

// Structures....
typedef struct _D3DDeviceInfo
{
    D3DCOLORMODEL cm;
    LPGUID        lpHWGuid;
    D3DDEVICEDESC HWDeviceDesc;
    LPGUID        lpSWGuid;
    D3DDEVICEDESC SWDeviceDesc;
} D3DDeviceInfo;

#pragma pack(1)
struct WaveHeader
{
    BYTE        RIFF[4];          // "RIFF"
    DWORD       dwSize;           // Size of data to follow
    BYTE        WAVE[4];          // "WAVE"
    BYTE        fmt_[4];          // "fmt "
    DWORD       dw16;             // 16
    WORD        wOne_0;           // 1
    WORD        wChnls;           // Number of Channels
    DWORD       dwSRate;          // Sample Rate
    DWORD       BytesPerSec;      // Sample Rate
    WORD        wBlkAlign;        // 1
    WORD        BitsPerSample;    // Sample size
    BYTE        DATA[4];          // "DATA"
    DWORD       dwDSize;          // Number of Samples
};
#pragma pack()

struct VideoMode
{
    int         width;
    int         height;
    int         bpp;
    BOOL        bUsable;
};

// Prototypes....

// Error handling
void TraceErrorDD(HRESULT hErr, char *sFile, int nLine);
void TraceErrorDS(HRESULT hErr, char *sFile, int nLine);
void TraceErrorD3D(HRESULT hErr, char *sFile, int nLine);
void TraceErrorD3DRM(HRESULT hErr, char *sFile, int nLine);

// Callbacks
BOOL FAR PASCAL  DDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc, LPSTR lpDriverName, LPVOID lpContext);
HRESULT CALLBACK DDEnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID Context);
HRESULT WINAPI   D3DEnumDeviceCallBack(LPGUID lpGuid, LPSTR lpDeviceDescription, LPSTR lpDeviceName,
                                                                           LPD3DDEVICEDESC lpHWDesc,  LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext);

// Initialisation
BOOL InitD3DDevice();

BOOL InitDirectX();
BOOL SetDirectDrawExclusiveMode();

// Termination
void TermDirectX();

void SortDisplayModes();

BOOL EnterVideoMode(int mode);
BOOL EnterVideoModeWHBD(int width, int height, int bitdepth);
BOOL EnterNextVideoMode();
BOOL EnterPrevVideoMode();
BOOL EnterLowestVideoMode();
BOOL EnterHighestVideoMode();
BOOL ReenterCurrentVideoMode();
void CleanUp();

BOOL DoSplashScreen(DWORD delay);
void ReleaseSplashScreen();

BOOL RestoreSurfaces();

BOOL CreateSoundBuffer(DWORD dwBuf, DWORD dwBufSize, DWORD dwFreq, DWORD dwBitsPerSample, DWORD dwBlkAlign, BOOL bStereo, BOOL bStaticBuf);
BOOL ReadData(LPDIRECTSOUNDBUFFER lpDSB, FILE* pFile, DWORD dwSize, DWORD dwPos);
BOOL CreateBufferFromWaveFile(char* FileName, DWORD dwBuf);
BOOL StopAllSounds();
BOOL PlaySoundDS(DWORD dwSound, D3DVECTOR d3dvPos, DWORD dwFlags = 0);
IDirectSoundBuffer *CreateSoundBuffer3D(void);
VOID CALLBACK PlayRandomWave( HWND hwnd, UINT uMsg,UINT idEvent, DWORD dwTime );
void RecalcPowerBars(DWORD player1health, DWORD player2health);

#endif


MIDI.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: midi.cpp
 *
 ***************************************************************************/

// Includes....
#include "windows.h"
#include "midi.h"
#include "stdio.h"

// Externals....
extern HWNDg_hWnd;

//------------------------------------------------------------------
//
// Function: PlayMidi()
//
// Purpose: Plays a midi file
//
//------------------------------------------------------------------

BOOL PlayMidi(char *sFileName)
{
    char buf[256];

    sprintf(buf, "open %s type sequencer alias MUSIC", sFileName);

    if (mciSendString("close all", NULL, 0, NULL) != 0)
    {
return(FALSE);
    }

    if (mciSendString(buf, NULL, 0, NULL) != 0)
    {
return(FALSE);
    }

    if (mciSendString("play MUSIC from 0", NULL, 0, g_hWnd) != 0)
    {
return(FALSE);
    }

    // Yahoo!
    return TRUE;
}

//------------------------------------------------------------------
//
// Function: PauseMidi()
//
// Purpose: Pauses midi file
//
//------------------------------------------------------------------

BOOL PauseMidi()
{
    // Pause if we're not already paused...
    if (mciSendString("stop MUSIC", NULL, 0, NULL) != 0)
    {
return(FALSE);
    }

    // Yahoo!
    return TRUE;
}

//------------------------------------------------------------------
//
// Function: ResumeMidi()
//
// Purpose: Resumes playing of a midi file
//
//------------------------------------------------------------------

BOOL ResumeMidi()
{
    // Resume midi
    if (mciSendString("play MUSIC notify", NULL, 0, g_hWnd) != 0)
    {
return(FALSE);
    }

    // Yahoo!
    return TRUE;
}

//------------------------------------------------------------------
//
// Function: StopMidi
//
// Purpose: Stops a midi file playing
//
//------------------------------------------------------------------

BOOL StopMidi()
{
    if (mciSendString("close all", NULL, 0, NULL) != 0)
    {
return(FALSE);
    }

    // Yahoo!
    return TRUE;
}

//------------------------------------------------------------------
//
// Function: ReplayMidi()
//
// Purpose: Replays a midi file
//
//------------------------------------------------------------------

BOOL ReplayMidi()
{
    // Replay midi
    if (mciSendString("play MUSIC from 0 notify", NULL, 0, g_hWnd) != 0)
    {
return(FALSE);
    }

    // Yahoo!
    return TRUE;
}


MIDI.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: midi.h
 *
 ***************************************************************************/

#ifndef __MIDI_H
#define __MIDI_H

BOOL PlayMidi(char *sFileName);
BOOL PauseMidi();
BOOL ResumeMidi();
BOOL StopMidi();
BOOL ReplayMidi();

#endif


RESOURCE.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: resource.h
 *
 ***************************************************************************/

#define IDR_ACCEL                       104
#define IDR_MENU                        105
#define IDI_ICON1                       106
#define IDI_ICON2                       108
#define ID_FILE_EXIT                    40001
#define ID_VIEW_640X480                 40002
#define ID_VIEW_640X400                 40003
#define ID_VIEW_320X240                 40004
#define ID_VIEW_320X200                 40005
#define ID_VIEW_FULLSCREEN              40006

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        111
#define _APS_NEXT_COMMAND_VALUE         40007
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


RM.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rm.cpp
 *
 ***************************************************************************/

// Includes....
#include "rm.h"
#include "directx.h"
#include "mmsystem.h"
#include "stdio.h"
#include "control.h"

// Globals....
LPDIRECT3DRMFRAMEg_lpScene = NULL;// Scene frame
LPDIRECT3DRMFRAMEg_lpCamera = NULL;// Camera frame
LPDIRECT3DRMFRAMEg_lpArena = NULL;// Arena frame
LPDIRECT3DRMFRAMEg_lpBackground = NULL;// Background frame

LPDIRECT3DRMFRAMEg_lpPlayers = NULL;// Encapsulating frame
LPDIRECT3DRMFRAMEg_lpPlayer1 = NULL;// Human player
LPDIRECT3DRMFRAMEg_lpPlayer1HeadFrame = NULL;// Human player head frame
LPDIRECT3DRMANIMATIONSETg_lpPlayer1AnimSet = NULL;// Human player animation set
LPDIRECT3DRMFRAMEg_lpPlayer2 = NULL;// Computer player
LPDIRECT3DRMFRAMEg_lpPlayer2HeadFrame = NULL;// Computer player head frame
LPDIRECT3DRMANIMATIONSETg_lpPlayer2AnimSet = NULL;// Computer player animation set

LPDIRECT3DRMFRAMEg_lpTmp = NULL;// Temporary frame

LPDIRECT3DRMLIGHTg_lpDir;// Global light frame

LPDIRECT3DRMMESHBUILDERg_lpRedDebris = NULL;// Red debris model
LPDIRECT3DRMMESHBUILDERg_lpBlueDebris = NULL;// Blue debris model
Debrisg_debris[NUM_DEBRIS];// Debris

LPDIRECT3DRMANIMATIONg_lpAnim = NULL;// Intro anim

// Timing stuff
D3DVALUEg_timingDelta = D3DVAL(0.0f);

// Frame rate stuff
DWORDg_dwLastTime = 0;
DWORDg_dwCurTime = 0;
DWORDg_dwFpsTime = 0;
DWORDg_dwDeltaTime = 0;
DWORDg_dwFramesRendered = 0;
DWORDg_dwFps = 0;

// Externals....
extern BOOLg_bShowStats;// Defined in WINMAIN.CPP
extern BOOLg_bHardware3D;// Defined in DIRECTX.CPP
extern LPDIRECT3DRMg_lpD3DRM;// Defined in DIRECTX.CPP
extern LPDIRECT3DRMVIEWPORTg_lpD3DRMViewport;// Defined in DIRECTX.CPP
extern LPDIRECT3DRMDEVICEg_lpD3DRMDevice;// Defined in DIRECTX.CPP
extern LPDIRECTDRAWSURFACEg_lpPrimary;// Defined in DIRECTX.CPP
extern LPDIRECTDRAWSURFACEg_lpBackBuffer;// Defined in DIRECTX.CPP
extern LPDIRECTDRAWSURFACEg_lpZBuffer;// Defined in DIRECTX.CPP
extern DWORDg_vidModeX;// Defined in DIRECTX.CPP
extern DWORDg_vidModeY;// Defined in DIRECTX.CPP
extern DWORDg_vidModeBIT;// Defined in DIRECTX.CPP
extern DWORDg_dwFontHeight;// Defined in DIRECTX.CPP
extern DWORDg_dwAveCharWidth;// Defined in DIRECTX.CPP

extern DWORDg_player1health;// Defined in CONTROL.CPP
extern DWORDg_player2health;// Defined in CONTROL.CPP

extern DWORDg_lbar1;// Defined in DIRECTX.CPP
extern DWORDg_wbar1;// Defined in DIRECTX.CPP
extern DWORDg_lbar2;// Defined in DIRECTX.CPP
extern DWORDg_wbar2;// Defined in DIRECTX.CPP
extern DWORDg_hbar1;// Defined in DIRECTX.CPP
extern DWORDg_hbar2;// Defined in DIRECTX.CPP

extern AnimArgsg_player1AnimArgs;
extern AnimArgsg_player2AnimArgs;

//----------------------------------------------------------------------
//
// Function: InitScene
//
// Purpose: Initialises Direct3D RM objects and loads scene for demo
//
//----------------------------------------------------------------------

BOOL InitScene()
{
    LPDIRECT3DRMLIGHTpAmb;
    LPDIRECT3DRMFRAMEpLight;
    LPDIRECT3DRMMESHBUILDER pMeshBuilder;

    // Create the scene (parent) frame
    TRY_D3DRM(g_lpD3DRM->CreateFrame(NULL, &g_lpScene))

    // Create the camera (child of g_lpScene)
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &g_lpCamera))

    // Create the arena frame
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &g_lpArena))

    // Create the frame that encapsulates both players
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &g_lpPlayers))

    // Create player frames
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpPlayers, &g_lpPlayer1))

    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpPlayers, &g_lpPlayer2))

    // Create temporary frame
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &g_lpTmp))

    // Create lights and position in world
    TRY_D3DRM(g_lpD3DRM->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2), &pAmb))

    TRY_D3DRM(g_lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.7), D3DVAL(0.7), D3DVAL(0.7), &g_lpDir))

    // Create ambient light frame
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &pLight))

    // Add the light to the frame
    TRY_D3DRM(pLight->AddLight(pAmb))

    // Release the light frame
    pLight->Release();

    // Create directional light frame
    TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpScene, &pLight))

    // Set position and orientation of directional light
    pLight->SetPosition(g_lpScene, D3DVAL(1000), D3DVAL(1000), D3DVAL(1000));
    pLight->SetOrientation(g_lpScene, D3DVAL(-1.0), D3DVAL(-1.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0));

    // Add the light to the frame
    TRY_D3DRM(pLight->AddLight(g_lpDir))

    // Enable lights only for any object that is a child of g_lpPlayers
    TRY_D3DRM(g_lpDir->SetEnableFrame(g_lpPlayers))

    // Release the light frame
    pLight->Release();

    // Create mesh builder for arena
    TRY_D3DRM(g_lpD3DRM->CreateMeshBuilder(&pMeshBuilder))

    // Load the arena
    TRY_D3DRM(pMeshBuilder->Load("ARENA.X", NULL, D3DRMLOAD_FROMFILE, LoadTextures, NULL))

    // Make sure we use perspective correct!
    pMeshBuilder->SetPerspective(TRUE);

    // Add the mesh to the scene
    TRY_D3DRM(g_lpArena->AddVisual(pMeshBuilder))

    g_lpArena->SetZbufferMode(D3DRMZBUFFER_DISABLE);

    // Release the mesh builder
    pMeshBuilder->Release();

    // Load player 1's model
    TRY_D3DRM(g_lpD3DRM->CreateAnimationSet(&g_lpPlayer1AnimSet))

    // Load the model and animation
    TRY_D3DRM(g_lpPlayer1AnimSet->Load("SKMECH.X", NULL, D3DRMLOAD_FROMFILE, LoadTextures, NULL, g_lpPlayer1))

    // Add animation callback for player 1
    g_player1AnimArgs.lpAnimSet = g_lpPlayer1AnimSet;
    g_player1AnimArgs.time= D3DVAL(0);
    TRY_D3DRM(g_lpPlayer1->AddMoveCallback(Player1AnimationCallback, NULL))

    // Setup the initial position of player 1
    g_lpPlayer1->SetPosition(g_lpScene, D3DVAL(0), D3DVAL(0), D3DVAL(-200));

    // Load player 2's model
    TRY_D3DRM(g_lpD3DRM->CreateAnimationSet(&g_lpPlayer2AnimSet))

    // Load the model and animation
    TRY_D3DRM(g_lpPlayer2AnimSet->Load("DEMECH.X", NULL, D3DRMLOAD_FROMFILE, LoadTextures, NULL, g_lpPlayer2))

    // Add animation callback for player 2
    g_player2AnimArgs.lpAnimSet = g_lpPlayer2AnimSet;
    g_player2AnimArgs.time= D3DVAL(0);
    TRY_D3DRM(g_lpPlayer2->AddMoveCallback(Player2AnimationCallback, NULL))

    for (int i = 0; i < NUM_DEBRIS; i ++)
    {
TRY_D3DRM(g_lpD3DRM->CreateFrame(g_lpPlayers, &g_debris[i].m_pFrame))

g_debris[i].m_bInUse = FALSE;
    }

    // Load the red debris
    TRY_D3DRM(g_lpD3DRM->CreateMeshBuilder(&g_lpRedDebris))

    TRY_D3DRM(g_lpRedDebris->Load("debris_r.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))

    // Load the blue debris
    TRY_D3DRM(g_lpD3DRM->CreateMeshBuilder(&g_lpBlueDebris))

    TRY_D3DRM(g_lpBlueDebris->Load("debris_b.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))

    // Setup the initial position of player 2
    g_lpPlayer2->SetPosition(g_lpScene, D3DVAL(0), D3DVAL(0), D3DVAL(200));

    // Create the intro path
    TRY_D3DRM(g_lpD3DRM->CreateAnimation(&g_lpAnim))

    // Setup the animation options
    g_lpAnim->SetOptions(D3DRMANIMATION_OPEN |
 D3DRMANIMATION_LINEARPOSITION |
 D3DRMANIMATION_POSITION);

    // Add the starting position (as a keyframe)
    g_lpAnim->AddPositionKey(D3DVAL(0), D3DVAL(200), D3DVAL(2000), D3DVAL(0));

    // Add the ending position (as a keyframe)
    g_lpAnim->AddPositionKey(D3DVAL(1), D3DVAL(700), D3DVAL(100), D3DVAL(0));

    // Make the camera follow this animation
    g_lpAnim->SetFrame(g_lpCamera);

    // Retrieve player 1 and player 2's head frames
    LPDIRECT3DRMOBJECT tmp;

    TRY_D3DRM(g_lpD3DRM->GetNamedObject("x3ds_Head", &tmp))
    g_lpPlayer1HeadFrame = (LPDIRECT3DRMFRAME)tmp;

    TRY_D3DRM(g_lpD3DRM->GetNamedObject("x3ds_xHead", &tmp))
    g_lpPlayer2HeadFrame = (LPDIRECT3DRMFRAME)tmp;

    // Yahoo!
    return TRUE;
}

//----------------------------------------------------------------------
//
// Function: TermScene
//
// Purpose: Destroys scene
//
//----------------------------------------------------------------------

void TermScene()
{
    // Destroy the scene frame
    if (g_lpScene)
    {
g_lpScene->Release();
g_lpScene = NULL;
    }

    // Destroy the animation sets
    if (g_lpPlayer1AnimSet)
    {
g_lpPlayer1AnimSet->Release();
g_lpPlayer1AnimSet = NULL;
    }

    if (g_lpPlayer2AnimSet)
    {
g_lpPlayer2AnimSet->Release();
g_lpPlayer2AnimSet = NULL;
    }
}

//----------------------------------------------------------------------
//
// Function: RenderScene
//
// Purpose: Renders scene
//
//----------------------------------------------------------------------

BOOL RenderScene()
{
    static BOOL b = FALSE;

    // Verify both surfaces
    if (!g_lpPrimary) return FALSE;

    if (!g_lpZBuffer) return FALSE;

    // Perform some timing stuff
    g_dwCurTime  = timeGetTime();
    g_dwDeltaTime = g_dwCurTime - g_dwLastTime;
    g_dwLastTime  = g_dwCurTime;
    g_dwFpsTime  += g_dwDeltaTime;

    // Move the scene
    g_lpScene->Move(D3DVAL(g_dwDeltaTime));

    if (b) {
b = FALSE;
TRY_D3DRM(g_lpD3DRMViewport->ForceUpdate(0, 0, g_vidModeX, g_vidModeY))
    }

    // Restore the primary surface if it has been lost
    if (g_lpPrimary->IsLost())
    {
    HRESULT rval = g_lpPrimary->Restore();
    if (rval != DD_OK) return TRUE;
    TRY_D3DRM(g_lpD3DRMViewport->ForceUpdate(0, 0, g_vidModeX, g_vidModeY))
    b = TRUE;
    }

    // Restore the ZBuffer if it has been lost
    if (g_lpZBuffer->IsLost())
    {
    HRESULT rval = g_lpZBuffer->Restore();
    if (rval != DD_OK) return TRUE;
    TRY_D3DRM(g_lpD3DRMViewport->ForceUpdate(0, 0, g_vidModeX, g_vidModeY))
    }

    // Clear the viewport ready for rendering
    TRY_D3DRM(g_lpD3DRMViewport->Clear())

    // Render the scene
    TRY_D3DRM(g_lpD3DRMViewport->Render(g_lpScene))

    g_dwFramesRendered ++;

    // Show stats if necessary
    if (g_bShowStats)
    {
// String to hold stats
char sStats[256];

// If 2 seconds have elapsed, calculate the frame rate
if (g_dwFpsTime > 2000)
{
    g_dwFps= g_dwFramesRendered / 2;
    g_dwFramesRendered= 0;
    g_dwFpsTime= 0;
}

// Copy info into stat string
sprintf(sStats, "SX:%d, SY:%d, SBD:%d FPS:%d, %s", g_vidModeX, g_vidModeY, g_vidModeBIT, g_dwFps, g_bHardware3D ? "(H)" : "(S)");

// Get a DC to the backbuffer (very useful!)
HDC hDC;
g_lpBackBuffer->GetDC(&hDC);
if (!hDC) return FALSE;

// Use TextOut to draw the text onto the surface
DWORD dwStringPixelWidth = strlen(sStats) * g_dwAveCharWidth;

        SetBkMode( hDC, TRANSPARENT );
        SetTextColor( hDC, RGB(255,255,255) );
TextOut(hDC, (g_vidModeX >> 1) - (dwStringPixelWidth >> 1), g_vidModeY - g_dwFontHeight, sStats, strlen(sStats));

// Must release DC before calling Flip()
g_lpBackBuffer->ReleaseDC(hDC);
    }

    // Draw the power bars
    DDBLTFX ddBltFx;
    memset(&ddBltFx, 0, sizeof(DDBLTFX));
    ddBltFx.dwSize = sizeof(DDBLTFX);

    RECT rcBar1 = { g_lbar1, g_hbar1, g_lbar1 + g_wbar1, g_hbar1 + g_hbar2 };
    RECT rcBar2 = { g_lbar2, g_hbar1, g_lbar2 + g_wbar2, g_hbar1 + g_hbar2 };

    switch (g_vidModeBIT)
    {
case 8  : ddBltFx.dwFillColor = 253; break;
case 16 : ddBltFx.dwFillColor = 1 << 4; break;
case 24 : ddBltFx.dwFillColor = 1 << 7; break;
    }
    if (g_player1health > 0) {
TRY_DD(g_lpBackBuffer->Blt(&rcBar1, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx))
TRY_D3DRM(g_lpD3DRMViewport->ForceUpdate(rcBar1.left, rcBar1.top, rcBar1.right, rcBar1.bottom))
    }

    switch (g_vidModeBIT)
    {
case 8  : ddBltFx.dwFillColor = 254; break;
case 16 : ddBltFx.dwFillColor = 1 << 5 << 5 << 4; break;
case 24 : ddBltFx.dwFillColor = 1 << 8 << 8 << 7; break;
    }
    if (g_player2health > 0) {
TRY_DD(g_lpBackBuffer->Blt(&rcBar2, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx))
TRY_D3DRM(g_lpD3DRMViewport->ForceUpdate(rcBar2.left, rcBar2.top, rcBar2.right, rcBar2.bottom))
    }

    // And update the device
    TRY_D3DRM(g_lpD3DRMDevice->Update())

    // Finally, flip the back buffer onto the primary surface, displaying
    // the last rendered frame
    TRY_DD(g_lpPrimary->Flip(NULL, DDFLIP_WAIT))

    // Yahoo!
    return TRUE;
}

//------------------------------------------------------------------
//
// Function: LoadTextures
//
// Purpose: Loads an individual texture
//
// NOTE: Textures must have a size divisible by 2 (e.g. 128x64, 256x256)
//
//------------------------------------------------------------------

HRESULT LoadTextures(char *sName, void *pArg, LPDIRECT3DRMTEXTURE *hTexture)
{
    char *sTmpName = sName;

    // Find the extension
    while(sTmpName[0] != '.') sTmpName ++;

    // Add .ppm to the picture file used by 3D Studio (.TGA, .GIF, .CEL etc)
    strcpy(sTmpName, ".ppm");

    // Load the texture
    if (FAILED(g_lpD3DRM->LoadTexture(sName, hTexture))) return -1;

    if (!strcmp(sName, "gdk_fill.ppm"))
    {
(*hTexture)->SetShades(1);
    }

    return 0;
}


RM.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rm.h
 *
 ***************************************************************************/

#ifndef __RM_H_
#define __RM_H_

// Includes....
#include "d3drmwin.h"

// Defines....
#define NUM_DEBRIS25
#define DEBRIS_LIFE75
#define NUM_HIT_DEBRIS4

// Structures
struct Debris
{
    LPDIRECT3DRMFRAMEm_pFrame;
    LPDIRECT3DRMMESHBUILDERm_pMeshBuilder;
    BOOLm_bInUse;
    D3DVECTORm_vel;
    D3DVECTORm_acc;
    intm_life;
};

// Prototypes....
BOOL InitScene();
void TermScene();

BOOL RenderScene();

HRESULT LoadTextures(char *sName, void *pArg, LPDIRECT3DRMTEXTURE *hTexture);

#endif


WINMAIN.CPP

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation. All Rights Reserved.
 *
 *  File: winmain.cpp
 *
 ***************************************************************************/

// Defines....
#define CLASS_NAME                              "RockEm3D_Class"

// Includes....
#include "windows.h"
#include "resource.h"
#include "winmain.h"

#include "directx.h"
#include "rm.h"

#include "control.h"

#include "midi.h"

// Globals....
HINSTANCE       g_hInst = NULL;
HWND            g_hWnd  = NULL;

BOOL            g_bActive       = FALSE;
BOOL            g_bFirstActive  = TRUE;
BOOL            g_bErrorOccured = FALSE;
char            g_sError[2048];
BOOL            g_bShowStats    = FALSE;
BOOL            g_bMusicPaused  = FALSE;
BOOL            g_bSoundPaused  = FALSE;

// Externals....
extern DWORD    g_dwCurrMode; // Defined in DIRECTX.CPP

//------------------------------------------------------------------
//
// Function     : RegError()
//
// Purpose      : Registers an error
//
//------------------------------------------------------------------

void RegError(char *sErr)
{
    sprintf(g_sError, "%s\r\n", sErr);
    OutputDebugString(g_sError);
    g_bErrorOccured = TRUE;
}

//------------------------------------------------------------------
//
// Function     : InitClass()
//
// Purpose      : Initialises and registers window class
//
//------------------------------------------------------------------

BOOL InitClass(HINSTANCE hInst)
{
    WNDCLASS    wndClass;

    // Fill out WNDCLASS info
    wndClass.style              = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc        = WndProc;
    wndClass.cbClsExtra         = 0;
    wndClass.cbWndExtra         = 0;
    wndClass.hInstance          = hInst;
    wndClass.hIcon              = LoadIcon(hInst, "ROCKEM3D");
    wndClass.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground      = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName       = NULL;
    wndClass.lpszClassName      = CLASS_NAME;

    if (!RegisterClass(&wndClass)) return FALSE;

    // Everything's perfect
    return TRUE;
}

//------------------------------------------------------------------
//
// Function     : InitWindow()
//
// Purpose      : Initialises and creates the main window
//
//------------------------------------------------------------------

BOOL InitWindow(HINSTANCE hInst, int nCmdShow)
{
    // Create a window
    g_hWnd = CreateWindowEx(WS_EX_APPWINDOW,
                            CLASS_NAME,
                            "RockEm3D Demo",
                            WS_POPUP | WS_SYSMENU,
                            0, 0,
                            GetSystemMetrics(SM_CXSCREEN),
                            GetSystemMetrics(SM_CYSCREEN),
                            NULL,
                            NULL,
                            hInst,
                            NULL);

    // Return false if window creation failed
    if (!g_hWnd) return FALSE;

    // Show the window
    ShowWindow(g_hWnd, SW_SHOWNORMAL);

    // Update the window
    UpdateWindow(g_hWnd);

    // Everything's perfect
    return TRUE;
}

//------------------------------------------------------------------
//
// Function     : WndProc()
//
// Purpose      : Windows procedure to handle messages
//
//------------------------------------------------------------------

long FAR PASCAL WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// D3DVECTOR used for 3D position of sound
static D3DVECTOR d3dvPos= {D3DVAL(0), D3DVAL(0), D3DVAL(0)};

    // Handle messages
    switch (message)
    {
        case WM_KEYDOWN:
        {
            switch (wParam)
            {
                case VK_ESCAPE:
                {
                    // Time to quit....
                    PostMessage(g_hWnd, WM_CLOSE ,0 ,0);
                }
                break;

                case VK_F5:
                {
                    // Toggle stats
                    g_bShowStats = !g_bShowStats;
                }
                break;

                case 'M':
                {
                    // Toggle music
                    if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
                    {
                        if (!g_bMusicPaused)
                        {
                            PauseMidi();
                        }
                        else
                        {
                            ResumeMidi();
                        }
                        g_bMusicPaused = !g_bMusicPaused;
                    }
                }
                break;

                case 'S':
                {
                    // Toggle sound
                    if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
                    {
                        g_bSoundPaused = !g_bSoundPaused;

                        if (g_bSoundPaused)
                        {
                            // Kill all the sound effects
                            StopAllSounds();
                        }
                        else
                        {
                            // Start the crowd noise looping
                         PlaySoundDS(CROWD_LOOP,d3dvPos, DSBPLAY_LOOPING);
                        }
                    }
                }
                break;

                case VK_F6 :
                {
                    // Go up a video mode
                    if(!EnterNextVideoMode())
PostMessage(g_hWnd, WM_CLOSE ,0 ,0);
                }
                break;

                case VK_F7 :
                {
                    // Go down a video mode
                    if(!EnterPrevVideoMode())
PostMessage(g_hWnd, WM_CLOSE ,0 ,0);
                }
                break;

                case VK_END :
                {
                    // Go to highest video mode
                    if(!EnterLowestVideoMode())
PostMessage(g_hWnd, WM_CLOSE ,0 ,0);
                }
                break;

                case VK_HOME :
                {
                    // Go to lowest video mode
                    if(!EnterHighestVideoMode())
PostMessage(g_hWnd, WM_CLOSE ,0 ,0);
                }
                break;
            }
        }
        break;

        case WM_SYSCOMMAND:
        {
            switch (wParam)
            {
                // Trap ALT so it doesn't pause the app
                case SC_KEYMENU :
                {
                    return 0;
                }
                break;
            }
        }

        case WM_ACTIVATEAPP:
        {
            // Determine whether app is being activated or not
            g_bActive = (BOOL)wParam ? TRUE : FALSE;

            if (g_bActive)
            {
                while (ShowCursor(FALSE) > 0) { };
                if (!g_bMusicPaused) ResumeMidi();
            }
            else
            {
                ShowCursor(TRUE);
                PauseMidi();
            }
        }
        break;

        case WM_CLOSE:
        {
            DestroyWindow(g_hWnd);
        }
        break;

        case WM_DESTROY:
        {
            // Stop midi music
            StopMidi();

            // Destroy scene
            TermScene();

            // Terminate all the DirectX objects, surfaces devices etc
            TermDirectX();

            // Show the mouse
            ShowCursor(TRUE);

            // Time to leave this mortal coil
            PostQuitMessage(0);
        }
        break;

        case MCI_NOTIFY:
        {
            if (wParam == MCI_NOTIFY_SUCCESSFUL)
            {
                ReplayMidi();
            }
        }
        break;
    }

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

//------------------------------------------------------------------
//
// Function     : WinMain()
//
// Purpose      : Entry point to application
//
//------------------------------------------------------------------

int FAR PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;

    // Set global handle
    g_hInst = hInst;

    // Initialise window class
    if (!InitClass(hInst)) return 1;

    // Initialise window
    if (!InitWindow(hInst, nCmdShow)) return 1;

    // Initialise DirectX objects (Termination is handled in WM_DESTROY)
    if (!InitDirectX())
    {
            DestroyWindow(g_hWnd);
            return 1;
    }

    // Show the splash screen
    DoSplashScreen(2000);

    // Load the scene
    if (!InitScene())
    {
            DestroyWindow(g_hWnd);
            return 1;
    }

    // Release the splash screen
    ReleaseSplashScreen();

    // Set DirectDraw exclusive mode here so that the splash could stay
    // up during initialisation if we are using a different DirectDraw device
    // that could not support 640x480x8 for hardware rendering.
    if (!SetDirectDrawExclusiveMode())
    {
            RegError("Could not set exclusive mode!");
            return FALSE;
    }

    // Hide the mouse
    ShowCursor(FALSE);

    // Enter video mode set in g_dwCurMode
    if (!EnterVideoMode(g_dwCurrMode))
    {
            DestroyWindow(g_hWnd);
            return 1;
    }

    // Load accelerators
    HACCEL hAccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDR_ACCEL));

    // Start the music!
    PlayMidi("RockEm3D.mid");

    // Pump messages via a PeekMessage loop
    while (TRUE)
    {
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                PostQuitMessage(msg.wParam);
                return 1;
            }

            if (hAccel && (msg.hwnd == g_hWnd))
            {
                TranslateAccelerator(g_hWnd, hAccel, &msg);
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // Perform any neccessary updating during idle time
        if (g_bActive)
        {
            // Update everything
            CheckInputAndUpdate();

            // Render the current scene
            if (!RenderScene())
            {
                DestroyWindow(g_hWnd);
                return 1;
            }
        }
    }

    // Exit WinMain and terminate the app....
    return msg.wParam;
}


WINMAIN.H

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File: winmain.h
 *
 ***************************************************************************/

#ifndef __WINMAIN_H
#define __WINMAIN_H

voidRegError(char *sErr);

BOOLInitClass(HINSTANCE hInst);
BOOLInitWindow(HINSTANCE hInst, int nCmdShow);
long FAR PASCALWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int FAR PASCALWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow);

#endif


README.TXT

Basic demonstration of DirectInput exclusive mouse usage.  Lets
you draw on the screen with the mouse.  Note that this is not
particularly well-suited to DirectInput because the app acts
pretty much like a normal Windows application, but at least you
can see how the calls are made.  Right-click to display a menu
of options.

This sample program demonstrates the following:

* Creating interfaces to DirectInputDevice objects.

* Acquiring and unacquiring devices.

* Reading buffered device data.

* Event notifications for device activity.

* Restricting the mouse to an arbitrary region.

* Scaling raw mouse coordinates before using them.

* Use of relative axis mode.


SCRAWL.CPP

/**************************************************************************

    SCRAWL.CPP - A dumb drawing app demo for DirectInput

    Collects mouse data in various modes to demonstrate how it's done.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995 - 1998 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>

#define DIRECTINPUT_VERSION 0x0300      /* Remain DX3-compatible */
#include <dinput.h>

#include "scrawl.h"

/****************************************************************************
 *
 *      This is an incredibly dump app.  It just lets you "draw" on
 *      a monochrome bitmap via DirectInput.  The purpose is not to
 *      dazzle you with mind-altering brilliance.  It's just to show
 *      how to use the DirectInput mouse interface.
 *
 ****************************************************************************/

/****************************************************************************
 *
 *      Manifest constants
 *
 ****************************************************************************/

#define DINPUT_BUFFERSIZE           16

#define DINPUT_CXBITMAP             512
#define DINPUT_CYBITMAP             300

/****************************************************************************
 *
 *      Global variables
 *
 ****************************************************************************/

const char c_szAppName[] = "Scrawl";    /* My name */

HINSTANCE  g_hinst;                     /* My instance handle */
BOOL       g_fActive;                   /* Am I the active window? */

int        g_x = DINPUT_CXBITMAP / 2;   /* Virtual x-coordinate */
int        g_y = DINPUT_CYBITMAP / 2;   /* Virtual y-coordinate */

int        g_dxFuzz;                    /* Leftover x-fuzz from scaling */
int        g_dyFuzz;                    /* Leftover y-fuzz from scaling */
int        g_iSensitivity;              /* Mouse sensitivity */

HDC        g_hdc;                       /* Memory DC our picture lives in */
HBITMAP    g_hbm;                       /* Our picture */
HBITMAP    g_hbmDeselect;               /* Stock bitmap for deselecting */

HCURSOR    g_hcurCross;                 /* Crosshairs */
int        g_cxCross;                   /* Width of crosshairs cursor */
int        g_cyCross;                   /* Height of crosshairs cursor */
int        g_dxCrossHot;                /* Hotspot location of crosshairs */
int        g_dyCrossHot;                /* Hotspot location of crosshairs */
int        g_fShowCursor = 1;           /* Should the cursor be shown? */

/****************************************************************************
 *
 *      DirectInput globals
 *
 ****************************************************************************/

LPDIRECTINPUT          g_pdi;
LPDIRECTINPUTDEVICE    g_pMouse;

HANDLE                 g_hevtMouse;

/****************************************************************************
 *
 *      Complain
 *
 *      Whine and moan.
 *
 ****************************************************************************/

void CDECL
Complain(HWND hwndOwner, HRESULT hr, LPCSTR pszFormat, ...)
{
    va_list ap;
    char szBuf[1024];
    char *pszBuf;

    va_start(ap, pszFormat);

    pszBuf = szBuf + wsprintf(szBuf, pszFormat, ap);

    va_end(ap);

    wsprintf(pszBuf, "\n\nError = %08x", hr);

    MessageBox(hwndOwner, szBuf, c_szAppName, MB_OK);
}

/****************************************************************************
 *
 *      DIInit
 *
 *      Initialize the DirectInput variables.
 *
 ****************************************************************************/

BOOL DIInit(HWND hwnd)
{
    HRESULT hr;

    /*
     *  Register with DirectInput and get an IDirectInput to play with.
     */
    hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "DirectInputCreate");
        return FALSE;
    }

    /*
     *  Obtain an interface to the system mouse device.
     */
    hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "CreateDevice(SysMouse)");
        return FALSE;
    }

    /*
     *  Set the data format to "mouse format".
     */
    hr = g_pMouse->SetDataFormat(&c_dfDIMouse);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetDataFormat(SysMouse, dfDIMouse)");
        return FALSE;
    }

    /*
     *  Set the cooperativity level.
     */
    hr = g_pMouse->SetCooperativeLevel(hwnd,
                                       DISCL_EXCLUSIVE | DISCL_FOREGROUND);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetCooperativeLevel(SysMouse)");
        return FALSE;
    }

    /*
     *  Create the handle that tells us new data is available.
     */
    g_hevtMouse = CreateEvent(0, 0, 0, 0);

    if (g_hevtMouse == NULL) {
        Complain(hwnd, GetLastError(), "CreateEvent");
        return FALSE;
    }

    /*
     *  Associate the event with the device.
     */
    hr = g_pMouse->SetEventNotification(g_hevtMouse);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "SetEventNotification(SysMouse)");
        return FALSE;
    }

    /*
     *  Set the buffer size to DINPUT_BUFFERSIZE elements.
     *  The buffer size is a DWORD property associated with the device.
     */
    DIPROPDWORD dipdw =
        {
            {
                sizeof(DIPROPDWORD),        // diph.dwSize
                sizeof(DIPROPHEADER),       // diph.dwHeaderSize
                0,                          // diph.dwObj
                DIPH_DEVICE,                // diph.dwHow
            },
            DINPUT_BUFFERSIZE,              // dwData
        };

    hr = g_pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if (FAILED(hr)) {
        Complain(hwnd, hr, "Set buffer size(SysMouse)");
        return FALSE;
    }

    return TRUE;

}

/****************************************************************************
 *
 *      DITerm
 *
 *      Terminate our usage of DirectInput.
 *
 ****************************************************************************/

void DITerm(void)
{
    if (g_pdi)      g_pdi   ->Release(), g_pdi    = NULL;
    if (g_pMouse)   g_pMouse->Release(), g_pMouse = NULL;

    if (g_hevtMouse) CloseHandle(g_hevtMouse), g_hevtMouse = NULL;
}

/****************************************************************************
 *
 *      InvalidateCursorRect
 *
 *      Invalidate the rectangle that contains the cursor.
 *
 *      The coordinates are in client coordinates.
 *
 ****************************************************************************/

void InvalidateCursorRect(HWND hwnd)
{
    RECT rc = { g_x - g_dxCrossHot,             g_y - g_dyCrossHot,
                g_x - g_dxCrossHot + g_cxCross, g_y - g_dyCrossHot + g_cyCross };

    InvalidateRect(hwnd, &rc, 0);
}

/****************************************************************************
 *
 *      UpdateCursorPosition
 *
 *      Move our private cursor in the requested direction, subject
 *      to clipping, scaling, and all that other stuff.
 *
 *      This does not redraw the cursor.  You need to do that yourself.
 *
 ****************************************************************************/

void UpdateCursorPosition(int dx, int dy)
{

    /*
     *  Pick up any leftover fuzz from last time.  This is important
     *  when scaling down mouse motions.  Otherwise, the user can
     *  drag to the right extremely slow for the length of the table
     *  and not get anywhere.
     */
    dx += g_dxFuzz;     g_dxFuzz = 0;
    dy += g_dyFuzz;     g_dyFuzz = 0;

    switch (g_iSensitivity) {

    case 1:                             /* High sensitivity: Magnify! */
        dx *= 2;
        dy *= 2;
        break;

    case -1:                            /* Low sensitivity: Scale down */
        g_dxFuzz = dx % 2;              /* remember the fuzz for next time */
        g_dyFuzz = dy % 2;
        dx /= 2;
        dy /= 2;
        break;

    default:
    case 0:                             /* No sensitivity */
        ;                               /* No adjustments needed */
    }

    g_x += dx;
    g_y += dy;

    /* Clip the cursor to our client area */
    if (g_x < 0)                g_x = 0;
    if (g_x >= DINPUT_CXBITMAP) g_x = DINPUT_CXBITMAP - 1;

    if (g_y < 0)                g_y = 0;
    if (g_y >= DINPUT_CYBITMAP) g_y = DINPUT_CYBITMAP - 1;

}

/****************************************************************************
 *
 *      Scrawl_SyncAcquire
 *
 *      Acquire or unacquire the devices, depending on the the g_fActive
 *      flag.  This synchronizes the devices with our internal view of
 *      the world.
 *
 *      Also repaint the cursor so that it hides/shows in sync with
 *      acquisition.
 *
 ****************************************************************************/

void
Scrawl_SyncAcquire(HWND hwnd)
{
    if (g_fActive) {
        if (g_pMouse) g_pMouse->Acquire();
    } else {
        if (g_pMouse) g_pMouse->Unacquire();
    }

    InvalidateCursorRect(hwnd);
}

/****************************************************************************
 *
 *      Private messages
 *
 *      WM_SYNCACQUIRE forces us to re-synchronize our acquisition
 *      with the world.
 *
 ****************************************************************************/

#define WM_SYNCACQUIRE      (WM_USER + 0)

/****************************************************************************
 *
 *      Scrawl_OnClear
 *
 *      Wipe out the bitmap.
 *
 ****************************************************************************/

void Scrawl_OnClear(HWND hwnd)
{
    /*
     *  Start out all white.
     */
    PatBlt(g_hdc, 0, 0, DINPUT_CXBITMAP, DINPUT_CYBITMAP, WHITENESS);

    if (hwnd) {
        InvalidateRect(hwnd, 0, 0);
    }
}

/****************************************************************************
 *
 *      Scrawl_OnCreate
 *
 *      Set up the window by appending our custom commands to the System
 *      menu.
 *
 *      Also disable the menu items we don't want to see.
 *
 ****************************************************************************/

BOOL Scrawl_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    HMENU hmenu = GetSystemMenu(hwnd, FALSE);

    EnableMenuItem(hmenu, SC_SIZE,     MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
    EnableMenuItem(hmenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);

    AppendMenu(hmenu, MF_ENABLED | MF_STRING, IDC_CLEAR, "C&lear\tDel");
    AppendMenu(hmenu, MF_ENABLED | MF_STRING, IDC_ABOUT, "&About\tF1");

    AppendMenu(hmenu, MF_ENABLED | MF_STRING | MF_POPUP,
                      (UINT)LoadMenu(g_hinst,
                                     MAKEINTRESOURCE(IDM_SENSITIVITY)),
                     "Sensitivit&y");

    return 1;
}

/****************************************************************************
 *
 *      Scrawl_OnInitMenuPopup
 *
 *      Initialize the sensitivity item accordingly.
 *
 ****************************************************************************/

void
Scrawl_OnInitMenuPopup(HWND hwnd, HMENU hmenu, UINT item, BOOL fSystemMenu)
{
    int iSensitivity;
    for (iSensitivity = -1; iSensitivity <= 1; iSensitivity++) {
        if (g_iSensitivity == iSensitivity) {
            CheckMenuItem(hmenu, IDC_SENSITIVITY_NORMAL + iSensitivity,
                          MF_BYCOMMAND | MF_CHECKED);
        } else {
            CheckMenuItem(hmenu, IDC_SENSITIVITY_NORMAL + iSensitivity,
                          MF_BYCOMMAND | MF_UNCHECKED);
        }
    }

}

/****************************************************************************
 *
 *      Scrawl_OnKeyDown
 *
 *      See if it's one of our accelerators.
 *
 ****************************************************************************/

void Scrawl_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
    switch (vk) {
    case '1':
    case '2':
    case '3':
        PostMessage(hwnd, WM_SYSCOMMAND, IDC_SENSITIVITY_NORMAL +
                                         vk - '2', 0);
        break;

    case VK_DELETE:
        PostMessage(hwnd, WM_SYSCOMMAND, IDC_CLEAR, 0);
        break;

    case VK_F1:
        PostMessage(hwnd, WM_SYSCOMMAND, IDC_ABOUT, 0);
        break;

    }
}

/****************************************************************************
 *
 *      Scrawl_OnPaint
 *
 *      Blt out our bitmap and draw our cursor on top of it.
 *
 ****************************************************************************/

void
Scrawl_OnPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (hdc) {

        BitBlt(hdc,
               ps.rcPaint.left,
               ps.rcPaint.top,
               ps.rcPaint.right - ps.rcPaint.left,
               ps.rcPaint.bottom - ps.rcPaint.top,
               g_hdc,
               ps.rcPaint.left,
               ps.rcPaint.top,
               SRCCOPY);

        if (g_fActive && g_fShowCursor) {
            DrawIcon(hdc, g_x - g_dxCrossHot,
                          g_y - g_dyCrossHot, g_hcurCross);
        }

        EndPaint(hwnd, &ps);
    }
}

/****************************************************************************
 *
 *      Scrawl_OnButton0Down_FlushMotion
 *
 *      Flush out any motion that we are holding.
 *
 ****************************************************************************/

typedef struct BUTTON0INFO {

    HDC hdcWindow;
    BOOL fMoved;
    DWORD dwSeqLastSeen;

} BUTTON0INFO, *PBUTTON0INFO;

void Scrawl_OnButton0Down_FlushMotion(PBUTTON0INFO pb0i)
{
    if (pb0i->fMoved) {
        pb0i->fMoved = 0;
        pb0i->dwSeqLastSeen = 0;
        LineTo(pb0i->hdcWindow, g_x, g_y);
        LineTo(g_hdc, g_x, g_y);
    }

}

/****************************************************************************
 *
 *      Scrawl_OnButton0Down
 *
 *      Enter draw mode.
 *
 *      If we are drawing a curve, then read buffered data and draw
 *      lines from point to point.  By reading buffered data, we can
 *      track the motion of the mouse accurately without coalescing.
 *
 *      This function illustrates how a non-message-based program can
 *      process buffered data directly from a device, processing
 *      messages only occasionally (as required by Windows).
 *
 *      This function also illustrates how an application can piece
 *      together buffered data elements based on the sequence number.
 *      A single mouse action (e.g., moving diagonally) is reported
 *      as a series of events, all with the same sequence number.
 *      Zero is never a valid DirectInput sequence number, so it is
 *      safe to use it as a sentinel value.
 *
 ****************************************************************************/

void Scrawl_OnButton0Down(HWND hwnd)
{
    BUTTON0INFO b0i;

    /* Hide the cursor while scrawling */
    g_fShowCursor = FALSE;
    InvalidateCursorRect(hwnd);
    UpdateWindow(hwnd);

    /*
     *  For performance, draw directly onto the window's DC instead of
     *  invalidating and waiting for the WM_PAINT message.  Of course,
     *  we always draw onto our bitmap, too, since that's what really
     *  counts.
     */

    /* BUGBUG -- select a decent pen, too */
    b0i.hdcWindow = GetDC(hwnd);
    MoveToEx(b0i.hdcWindow, g_x, g_y, 0);
    MoveToEx(g_hdc, g_x, g_y, 0);

    /* BUGBUG -- save old pen */
    SelectObject(b0i.hdcWindow, GetStockObject(BLACK_PEN));
    SelectObject(g_hdc, GetStockObject(BLACK_PEN));

    b0i.fMoved = 0;
    b0i.dwSeqLastSeen = 0;

    /*
     *  Keep reading data elements until we see a "mouse button up" event.
     */
    BOOL fDone = 0;
    while (!fDone) {
        DIDEVICEOBJECTDATA od;

        DWORD dwElements = 1;

        HRESULT hr = g_pMouse->GetDeviceData(
                             sizeof(DIDEVICEOBJECTDATA), &od,
                             &dwElements, 0);

        /* Unable to read data */
        if (FAILED(hr)) {
            break;
        }

        /*
         *  If no data available, finish the element we had been
         *  collecting, and then process our message queue so
         * the system don't think we're hung.
         */
        if (dwElements == 0) {

            /* If there is a partial motion, flush it out */
            Scrawl_OnButton0Down_FlushMotion(&b0i);

            MSG msg;
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                /* If it's a quit message, we're outta here */
                if (msg.message == WM_QUIT) {
                    fDone = TRUE;
                    /*
                     * Re-post the quit message so the
                     * outer loop will see it and exit.
                     */
                    PostQuitMessage(msg.wParam);
                    break;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            continue;
        }

        /* If this is the start of a new event, flush out the old one */
        if (od.dwSequence != b0i.dwSeqLastSeen) {
            Scrawl_OnButton0Down_FlushMotion(&b0i);
            b0i.dwSeqLastSeen = od.dwSequence;
        }

        /* Look at the element to see what happened */

        switch (od.dwOfs) {

        /* DIMOFS_X: Mouse horizontal motion */
        case DIMOFS_X:
            UpdateCursorPosition(od.dwData, 0);
            b0i.fMoved = 1;
            break;

        /* DIMOFS_Y: Mouse vertical motion */
        case DIMOFS_Y:
            UpdateCursorPosition(0, od.dwData);
            b0i.fMoved = 1;
            break;

        /* DIMOFS_BUTTON0: Button 0 pressed or released */
        case DIMOFS_BUTTON0:

            if (!(od.dwData & 0x80)) { /* Button released */
                fDone = 1;
                Scrawl_OnButton0Down_FlushMotion(&b0i); /* Flush out dregs */
            }
            break;

        }
    }

    ReleaseDC(hwnd, b0i.hdcWindow);

    /* Re-show the cursor now that scrawling is finished */
    g_fShowCursor = TRUE;
    InvalidateCursorRect(hwnd);
}

/****************************************************************************
 *
 *      Scrawl_OnButton1Up
 *
 *      Pop up a context menu.
 *
 ****************************************************************************/

void Scrawl_OnButton1Up(HWND hwnd)
{
    POINT pt = { g_x, g_y };
    ClientToScreen(hwnd, &pt);

    /*
     *  Unacquire the devices so the user can interact with the menu.
     *
     *  Put the Windows cursor at the same location as our virtual cursor.
     *
     *  Hide the cursor while moving it so you don't get annoying flicker.
     */

    ShowCursor(FALSE);
    g_fActive = FALSE;
    Scrawl_SyncAcquire(hwnd);
    SetCursorPos(pt.x, pt.y);
    ShowCursor(TRUE);

    HMENU hmenuPopup = GetSystemMenu(hwnd, FALSE);

    UINT idc = TrackPopupMenuEx(hmenuPopup,
                                TPM_RIGHTBUTTON | TPM_RETURNCMD,
                                pt.x, pt.y, hwnd, 0);

    PostMessage(hwnd, WM_SYSCOMMAND, idc, 0L);

}

/****************************************************************************
 *
 *      Scrawl_OnMouseInput
 *
 *      The mouse moved while nothing was happening.  Walk the event list
 *      and update the mouse position for each event.  If we see a button
 *      event, then stop pulling events and leave the elements in the
 *      input buffer for the drawing handler to pull.
 *
 *
 ****************************************************************************/

void
Scrawl_OnMouseInput(HWND hwnd)
{
    /* Invalidate the old cursor so it will be erased */
    InvalidateCursorRect(hwnd);

    /*
     *  Attempt to read one data element.  Continue as long as
     *  device data is available.
     */
    BOOL fDone = 0;
    while (!fDone) {
        DIDEVICEOBJECTDATA od;

        DWORD dwElements = 1;

        HRESULT hr = g_pMouse->GetDeviceData(
                             sizeof(DIDEVICEOBJECTDATA), &od,
                             &dwElements, 0);

        if (hr == DIERR_INPUTLOST) {
            /*
             *  We had acquisition, but lost it.  Try to reacquire it.
             *
             *  WARNING!  DO NOT ATTEMPT TO REACQUIRE IF YOU GET
             *  DIERR_NOTACQUIRED!  Otherwise, you're extremely likely
             *  to get caught in an infinite loop:  The acquire will fail,
             *  and you'll get another DIERR_NOTACQUIRED so you'll
             *  try to aquire again, and that'll fail, etc.
             */
            PostMessage(hwnd, WM_SYNCACQUIRE, 0, 0L);
            break;
        }

        /* Unable to read data or no data available */
        if (FAILED(hr) || dwElements == 0) {
            break;
        }

        /* Look at the element to see what happened */

        switch (od.dwOfs) {

        /* DIMOFS_X: Mouse horizontal motion */
        case DIMOFS_X: UpdateCursorPosition(od.dwData, 0); break;

        /* DIMOFS_Y: Mouse vertical motion */
        case DIMOFS_Y: UpdateCursorPosition(0, od.dwData); break;

        /* DIMOFS_BUTTON0: Button 0 pressed or released */
        case DIMOFS_BUTTON0:

            if (od.dwData & 0x80) { /* Button pressed */
                fDone = 1;
                Scrawl_OnButton0Down(hwnd); /* Go into button-down mode */
            }
            break;

        /* DIMOFS_BUTTON1: Button 1 pressed or released */
        case DIMOFS_BUTTON1:

            if (!(od.dwData & 0x80)) {  /* Button released */
                fDone = 1;
                Scrawl_OnButton1Up(hwnd); /* Context menu time */
            }
        }

    }

    /* Invalidate the new cursor so it will be drawn */
    InvalidateCursorRect(hwnd);

}

/****************************************************************************
 *
 *      ScrawlWndProc
 *
 *      Application window procedure.
 *
 ****************************************************************************/

LONG CALLBACK ScrawlWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    switch (msg) {

    HANDLE_MSG(hwnd, WM_CREATE, Scrawl_OnCreate);

    HANDLE_MSG(hwnd, WM_PAINT, Scrawl_OnPaint);

    HANDLE_MSG(hwnd, WM_INITMENUPOPUP, Scrawl_OnInitMenuPopup);

    HANDLE_MSG(hwnd, WM_KEYDOWN, Scrawl_OnKeyDown);

    /*
     *  Reacquire the mouse and keyboard when we are the active window.
     *  Unacquire them when we stop being the active window.
     */
    case WM_ACTIVATE:
        g_fActive = wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE;
        Scrawl_SyncAcquire(hwnd);
        break;

    /*
     *  Unacquire the devices if a menu appears, so that the user can
     *  interact with the menu in the normal manner.
     *
     *  From Windows' point of view, we are still the active window
     *  when a menu appears, but we want to act like the menu deactivated
     *  us.
     */
    case WM_ENTERMENULOOP:
    case WM_ENTERSIZEMOVE:
        g_fActive = FALSE;
        Scrawl_SyncAcquire(hwnd);
        break;

    /*
     *  Reacquire the devices when the menu goes away.
     *
     *  SUBTLETY 1:  Windows actually sends the WM_EXITMENULOOP message
     *  before all the menu-related stuff is finished, so post ourselves
     *  a private message to reacquire after the menu has been torn
     *  down for real.
     *
     *  SUBTLETY 2:  Don't assume that just because the menu is going
     *  away that you are still the active window.  You might not be.
     *
     *  SUBTLETY 3:  Don't assume that just because you're the active
     *  window that you are restored and ready for action.  You might
     *  just be a taskbar button.
     */
    case WM_EXITMENULOOP:
    case WM_EXITSIZEMOVE:
        g_fActive = GetActiveWindow() == hwnd && !IsIconic(hwnd);
        PostMessage(hwnd, WM_SYNCACQUIRE, 0, 0L);
        break;

    case WM_SYNCACQUIRE:
        Scrawl_SyncAcquire(hwnd);
        break;

    case WM_SYSCOMMAND:

        LRESULT lRc;
        switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDC_CLEAR:
            Scrawl_OnClear(hwnd);
            lRc = 0;
            break;

        case IDC_ABOUT:
            MessageBox(hwnd, "Scrawl DirectInput Sample v1.0",
                    c_szAppName, MB_OK);
            lRc = 0;
            break;

        /*
         *  Eat the screen-saver notification.
         */
        case SC_SCREENSAVE:
            lRc = 0;
            break;

        case IDC_SENSITIVITY_LOW:
        case IDC_SENSITIVITY_NORMAL:
        case IDC_SENSITIVITY_HIGH:
            g_iSensitivity = (signed short)GET_WM_COMMAND_ID(wParam, lParam)
                                - IDC_SENSITIVITY_NORMAL;
            lRc = 0;
            break;

        default:
            lRc = DefWindowProc(hwnd, msg, wParam, lParam);
            break;
        }

        /*
         * The WM_SYSCOMMAND might've been a WM_CLOSE, in which case
         * our window no longer exists.  So be careful.
         */
        if (IsWindow(hwnd)) {
            Scrawl_SyncAcquire(hwnd);
        }
        return lRc;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
 *
 *      AppInit
 *
 *      Set up everything the application needs to get started.
 *
 ****************************************************************************/

HWND AppInit(HINSTANCE hinst, int nCmdShow)
{

    /* Save instance handle for people who care */
    g_hinst = hinst;

    /*
     *  Get our crosshairs cursor and extract the the width and
     *  hotspot location so we can draw it manually.
     */
    g_hcurCross = LoadCursor(NULL, IDC_CROSS);

    ICONINFO ii;

    GetIconInfo(g_hcurCross, &ii);

    BITMAP bm;

    GetObject(ii.hbmMask, sizeof(BITMAP), &bm);

    if (ii.hbmMask)  DeleteObject(ii.hbmMask);
    if (ii.hbmColor) DeleteObject(ii.hbmColor);

    g_dxCrossHot = ii.xHotspot;
    g_dyCrossHot = ii.yHotspot;

    g_cxCross = bm.bmWidth;
    g_cyCross = bm.bmHeight;

    /*
     *  Create our scrawl bitmap and set it up.
     */
    HDC hdc = GetDC(0);
    g_hdc = CreateCompatibleDC(hdc);
    ReleaseDC(0, hdc);

    if (!g_hdc) return NULL;

    g_hbm = CreateBitmap(DINPUT_CXBITMAP, DINPUT_CYBITMAP, 1, 1, 0);

    if (!g_hbm) return NULL;

    g_hbmDeselect = (HBITMAP) SelectObject(g_hdc, g_hbm);

    Scrawl_OnClear(0);

    /*
     *  Set up the window class.
     */
    WNDCLASS wc;

    wc.hCursor        = LoadCursor(0, IDC_ARROW);
    wc.hIcon          = LoadIcon(hinst, MAKEINTRESOURCE(IDI_MAIN));
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = c_szAppName;
    wc.hbrBackground  = 0;
    wc.hInstance      = hinst;
    wc.style          = 0;
    wc.lpfnWndProc    = ScrawlWndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;

    if (!RegisterClass(&wc)) {
        return NULL;
    }

    DWORD dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
    DWORD dwExStyle = WS_EX_APPWINDOW;

    RECT rc = { 0, 0, DINPUT_CXBITMAP, DINPUT_CYBITMAP };

    AdjustWindowRectEx(&rc, dwStyle, FALSE, dwExStyle);

    HWND hwnd = CreateWindowEx(
                    dwExStyle,          // ExStyle
                    c_szAppName,        // Class name
                    c_szAppName,        // Caption
                    dwStyle,            // Style
                    CW_USEDEFAULT, CW_USEDEFAULT,  // Position
                    rc.right - rc.left, // cx
                    rc.bottom - rc.top, // cy

0,                  // Parent window (no parent)
                    0,                  // use class menu
                    g_hinst,            // handle to module instance
                    0                   // no params to pass on
                    );

    if (!DIInit(hwnd)) {
        DestroyWindow(hwnd);
        return NULL;
    }

    ShowWindow(hwnd, nCmdShow);

    return hwnd;
}

/****************************************************************************
 *
 *      WinMain
 *
 *      Application entry point.
 *
 *      The main message loop illustrates how a message-driven program
 *      can use event notifications to be signalled when new data is
 *      available from a device.
 *
 ****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    MSG msg;
    msg.wParam = 0;         /* In case something goes horribly wrong */

    HWND hwnd = AppInit(hinst, nCmdShow);

    if (hwnd) {

        /*
         *  Since we use notification handles, we need to use
         *  MsgWaitForMultipleObjects to wait for the event or
         *  a message, whichever comes first.
         */

        BOOL fDone = FALSE;

        while (!fDone) {

            DWORD dw = MsgWaitForMultipleObjects(1, &g_hevtMouse, 0, INFINITE,
                                                 QS_ALLINPUT);

            switch (dw) {

            /* WAIT_OBJECT_0 + 0 means that g_hevtMouse was signalled */
            case WAIT_OBJECT_0 + 0:
                Scrawl_OnMouseInput(hwnd);
                break;

            /* WAIT_OBJECT_0 + 1 means that we have messages to process */
            case WAIT_OBJECT_0 + 1:

                while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                    /* If it's a quit message, we're outta here */
                    if (msg.message == WM_QUIT) {
                        fDone = TRUE;
                    } else {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                }
                break;
            }
        }
    }

    DITerm();

    if (g_hdc) {
        if (g_hbmDeselect) {
            SelectObject(g_hdc, g_hbmDeselect);
        }
        DeleteDC(g_hdc);
    }

    if (g_hbm) {
        DeleteObject(g_hbm);
    }

  return msg.wParam;

}


SCRAWL.H

/**************************************************************************

    SCRAWL.H - A dumb drawing app demo for DirectInput

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>

#define IDI_MAIN                1

#define IDM_SENSITIVITY         1

#define IDC_CLEAR               64
#define IDC_ABOUT               65

#define IDC_SENSITIVITY_LOW     96
#define IDC_SENSITIVITY_NORMAL  97
#define IDC_SENSITIVITY_HIGH    98


README.TXT

Setup Sample (x86 only)
-----------------------

This sample is an example of how to use DirectXSetup APIs
to install DirectX subsystem and DirectX drivers.  It also
serves as a working template for ISV's to install a game.

Note that this program will not run until the \Mssdk\Redist\DirectX5
directory is installed into the same directory as setup.exe.  The
entire tree must be copied in order to setup correctly.


DINSTALL.C

/*==========================================================================
 *
 *  Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dinstall.c
 *  Content:    Game SDK sample setup program
 ***************************************************************************/

#include <stdio.h>      // for sprintf
#include <windows.h>
#include <shellapi.h>   // for SHFileOperation
#include <shlobj.h>     // for SHBroweForFolder
#include "dsetup.h"
#include "dinstall.h"

/*
 * global constants and macros
 */
#define BUFFER_SIZE 512

#define LoadStrFromRes(uID,lpBuffer) \
LoadString (GetModuleHandle(NULL),\
            (uID),\
            (lpBuffer),\
            sizeof((lpBuffer)));

/*
 * list of files that will be copied from the source directory to
 * to the directory the game is created in
 */
static char* copy_list [] =
{
        "ROCKEM3D.EXE",
        "ARENA.X",
        "BLOCK1.WAV",
        "BLOCK2.WAV",
        "BLOCK3.WAV",
        "CBOO.WAV",
        "CLOOP.WAV",
        "CYEAH.WAV",
        "DEBRIS_B.X",
        "DEBRIS_R.X",
        "DEFEND1.WAV",
        "DEFEND2.WAV",
        "DEMECH.X",
        "DEMECHBK.PPM",
        "DEMECHBT.PPM",
        "DEMECHCH.PPM",
        "DEMECHGR.PPM",
        "DEMECHH1.PPM",
        "DEMECHH2.PPM",
        "DEMECHHD.PPM",
        "DEMECHHN.PPM",
        "DEMECHLA.PPM",
        "DEMECHLL.PPM",
        "DEMECHUA.PPM",
        "DEMECHUL.PPM",
        "GDK_FILL.PPM",
        "HEAD.WAV",
        "INTRO.WAV",
        "PUNCH.WAV",
        "PUNCH1.WAV",
        "PUNCH2.WAV",
        "PUNCH3.WAV",
        "PUNCH4.WAV",
        "REVDN1.WAV",
        "REVDN2.WAV",
        "REVDN3.WAV",
        "REVUP1.WAV",
        "REVUP2.WAV",
        "REVUP3.WAV",
        "RANDOM1.WAV",
        "RANDOM2.WAV",
        "RANDOM3.WAV",
        "RANDOM4.WAV",
        "RANDOM5.WAV",
        "RANDOM6.WAV",
        "ROCKEM3D.BIN",
        "ROCKEM3D.MID",
        "ROCKEM3D.PAL",
        "SKMECH.X",
        "SKMECHBK.PPM",
        "SKMECHBT.PPM",
        "SKMECHCH.PPM",
        "SKMECHGR.PPM",
        "SKMECHHN.PPM",
        "SKMECHJD.PPM",
        "SKMECHLA.PPM",
        "SKMECHLL.PPM",
        "SKMECHUA.PPM",
        "SKMECHUL.PPM",
        "SPLASH.PAL",
        "WALK0.WAV",
        "WALK1.WAV",
        "WHOOSH1.WAV",
        "WHOOSH2.WAV",
};

static char       szTitle[BUFFER_SIZE];

/*
 * prototypes
 */
BOOL FAR PASCAL masterDlgProc( HWND hdlg,DWORD message,DWORD wparam,DWORD lparam );

/*
 * globals
 */
static HANDLE   hinst;
static char     GameDirectory[MAX_PATH];    // where the user wants the game
static char     SetupDirectory[MAX_PATH];     // where the user ran setup from

/*
 * support functions
 */
void catpath(char *dst, char *src)
{
        int len = lstrlen(dst);
        if (len > 0 && (dst[len-1] != '\\' && dst[len-1] != '/'))
                lstrcat(dst,"\\");
        lstrcat(dst,src);

        // SHFileOperation needs a double null string.
        len = lstrlen(dst);
        dst[len+1] = 0;
}

/*
 * set a bitmap into a static control
 */
void SetBitmap(HWND hDlg, int id, char *szBitmap, int w, int h)
{
        HBITMAP hbm;
        HWND hwnd;

        hwnd = GetDlgItem(hDlg, id);

        if (hwnd == NULL)
                return;

        hbm = (HBITMAP)LoadImage(hinst, szBitmap, IMAGE_BITMAP, w, h,
                LR_LOADTRANSPARENT | LR_LOADMAP3DCOLORS | LR_CREATEDIBSECTION);

        if (hbm)
                hbm = (HBITMAP)SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbm);

        if (hbm)
                DeleteObject(hbm);
}

void SetInfoText(HWND hDlg, char *sz, ...)
{
        char ach[128];
        va_list marker;
        va_start (marker,sz);
        wvsprintf(ach, sz, marker);
        SetDlgItemText(hDlg, IDC_INFO, ach);
}

void _SHFree(void *p)
{
        IMalloc *pm;
        SHGetMalloc(&pm);
        if (pm)
        {
                pm->lpVtbl->Free(pm,p);
                pm->lpVtbl->Release(pm);
        }
}

/*
 * build a shortcut in the start menu
 */
void MakeShortcut()
{
        char buf[512];
        char szSetupIni[MAX_PATH];
        char szExeFile[MAX_PATH];
        int len;
        int fh;
        char szGroupName[BUFFER_SIZE];
        char szLinkName[BUFFER_SIZE];

        static char setup_ini[] =
                "[progman.groups]\r\n"
                "groupX=%s\r\n"
                "[groupX]\r\n"
                "\"%s\",\"%s\",,,,\"%s\"\r\n";

        GetWindowsDirectory(szSetupIni, sizeof(szSetupIni));
        catpath(szSetupIni, "SETUP.INI");

        lstrcpy(buf, GameDirectory);
        catpath(buf, copy_list[0]);
        GetShortPathName(buf, szExeFile, sizeof(szExeFile));

//  lstrcpy(buf, GameDirectory);
//  GetShortPathName(buf, szWork, sizeof(szWork));

        LoadStrFromRes( IDS_GROUP_NAME, szGroupName );
        LoadStrFromRes( IDS_LINK_NAME, szLinkName );
        len = wsprintf(buf, setup_ini, szGroupName, szLinkName,
                szExeFile, GameDirectory);

        fh = _lcreat(szSetupIni, 0);

        if (fh != -1)
        {
                _lwrite(fh, buf, len);
                _lclose(fh);
                WinExec("grpconv -o", SW_HIDE);
        }
}

/*
 * dlg proc for wizard dialog box, the setup is controlled from here.
 */
BOOL FAR PASCAL masterDlgProc(HWND hDlg,DWORD dwMessage,DWORD wParam,DWORD lParam)
{
        int             result;
        static int  system_restart;
        static int      current_dialog;
        static int  busy;

        char        src[MAX_PATH];
        char        dst[MAX_PATH];
        SHFILEOPSTRUCT fileop;

        char       szBuffer[BUFFER_SIZE];

    switch(dwMessage)
    {
    case WM_INITDIALOG:
                busy = 0;
                current_dialog = 0;

                LoadStrFromRes( IDS_TITLE, szTitle );
                SetWindowText( hDlg, szTitle );
                EnableWindow( GetDlgItem(hDlg, IDC_B), FALSE );
                EnableWindow( GetDlgItem(hDlg, IDC_H), FALSE );

                /*
                 * set the signon bitmap into our static control
                 */
                LoadStrFromRes( IDS_SIGNON_BITMAP_NAME, szBuffer );
                SetBitmap( hDlg, IDC_STATIC, szBuffer, 175, 195 );

                /*
                 * limit the size of the input of this text field to the length of a path
                 * put the default directory to install the game into in it
                 * select the whole thing to make it easy for people to replace it
                 * set the focus to it
                 */
                SendDlgItemMessage( hDlg, IDC_EDIT, EM_LIMITTEXT, MAX_PATH, 0L);
                LoadStrFromRes( IDS_DEFAULT_GAME_DIR, szBuffer );
                        SetDlgItemText( hDlg, IDC_EDIT, szBuffer );
                SendDlgItemMessage( hDlg, IDC_EDIT, EM_SETSEL, 0, MAKELONG(256, 256) );
                        SetFocus( GetDlgItem(hDlg, IDC_EDIT) );
                /*
                 * return 0 here indicating we have set the focus for the dialog box
                 * and it doesn't need to help us
                 */
                return 0;

    case WM_SETCURSOR:
        if (busy)
        {
            SetCursor(LoadCursor(NULL, IDC_WAIT));
            return TRUE;
        }
        break;

    case WM_COMMAND:
                switch(wParam)
                {
                case IDOK:
                        if( busy > 0 )
                        {
                                /*
                                 * busy bit keeps us from taking input while we are off doing
                                 * things that can create other dialog boxes and end up causing
                                 * us to be reentered.
                                 */
                                break;
                        }
                        else if( current_dialog == 0 )
                        {
                                        int     i;

                                        busy++;
                                        EnableWindow(GetDlgItem(hDlg,IDOK), FALSE);
                                        EnableWindow(GetDlgItem(hDlg,IDCANCEL), FALSE);
                                        SetCursor(LoadCursor(NULL, IDC_WAIT));

                                        /*
                                         * get the directory the user typed
                                         */
                                        GetWindowText( GetDlgItem( hDlg,IDC_EDIT ), GameDirectory, sizeof(GameDirectory));

                                        /*
                                         * verify that the typed in directory is valid
                                         * by having the SHELL copy WIN.INI to this directory
                                         * it will also create the directory for us.
                                         */
                                        LoadStrFromRes( IDS_CREATE_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);

                                        GetWindowsDirectory(src, sizeof(src));
                                        catpath(src,"WIN.INI");

                                        lstrcpy(dst,GameDirectory);
                                        catpath(dst,"SMAG.INI");

                                        fileop.hwnd     = hDlg;
                                        fileop.wFunc    = FO_COPY;
                                        fileop.pFrom    = src;
                                        fileop.pTo      = dst;
                                        fileop.fFlags   = FOF_SILENT | FOF_NOCONFIRMATION;

                                        if (SHFileOperation(&fileop) != 0)
                                        {
                                                // failed, the shell gave the user a error.
                                                SetInfoText(hDlg, "");
                                                EnableWindow(GetDlgItem(hDlg,IDOK), TRUE);
                                                EnableWindow(GetDlgItem(hDlg,IDCANCEL), TRUE);
                                                busy--;
                                                break;
                                        }

                                        /*
                                         * the directory is valid now delete the bogus file
                                         */
                                        fileop.hwnd     = hDlg;
                                        fileop.wFunc    = FO_DELETE;
                                        fileop.pFrom    = dst;
                                        fileop.pTo      = NULL;
                                        fileop.fFlags   = FOF_SILENT | FOF_NOCONFIRMATION;

                                        SHFileOperation(&fileop);
                                        SetInfoText(hDlg, "");

                                        /*
                                         * check if there is enough space to install the game
                                         * NOTE: there is always enough space at the moment :-)
                                         */

                                        LoadStrFromRes( IDS_DISK_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);

                                        if( 0 )
                                        {
                                                /* your code goes here */
                                        }
                                        SetInfoText(hDlg, "");

                                        /*
                                         * now setup DirectX
                                         */
                                        LoadStrFromRes( IDS_INSTALL_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);

                                        result = DirectXSetup( hDlg, NULL, DSETUP_DIRECTX );

                                        SetInfoText(hDlg, "");

                                        if( result < 0 )
                                        {
                                                if (result == DSETUPERR_NOTPREINSTALLEDONNT)
                                                {
                                                        /*
                                                         *  DirectX comes preinstalled on NT, and can only
                                                         *  be installed in an NT release or Service Pack.
                                                         *  If this error code is returned, then the required
                                                         *  version of DirectX is not preinstalled on this
                                                         *  NT machine.  The user will have to go get
                                                         *  the NT version or Service Pack required, or this
                                                         *  game will not run.  Note that any application
                                                         *  can redistribute an NT Service Pack as long as
                                                         *  it is distributed in its entirety.  Check out
                                                         *  ftp://ftp.microsoft.com/bussys/winnt/winnt-public/fixes
                                                         */
                                                        LoadStrFromRes( IDS_NTFAILED_MSG, szBuffer );
                                                        MessageBox( hDlg, szBuffer, szTitle, 0 );
                                                }
                                                else
                                                {
                                                        LoadStrFromRes( IDS_FAILED_MSG, szBuffer );
                                                        MessageBox( hDlg, szBuffer, szTitle, 0 );
                                                }
                                                EndDialog(hDlg, result);
                                                break;
                                        }
                                        else if( result == 0 )
                                        {
                                                /*
                                                 *  Check the post-install DirectX Version number,
                                                 *  to check if the game will be able to run.
                                                 *
                                                 *  DirectXSetupGetVersion will get the version
                                                 *  and revision info about the current installation.
                                                 *  Note that since we're calling this after
                                                 *  DirectXSetup, the "current" installation is the
                                                 *  post-install version of DirectX.
                                                 *
                                                 *  Since we don't care about the revision number,
                                                 *  we're leaving that parameter as NULL.
                                                 */

                                                DWORD dwVersion;

                                                if( DirectXSetupGetVersion(&dwVersion, NULL) )
                                                {
                                                        /*  we need DirectX 3.0 or better, so we're going
                                                         *  to make sure that dwVersion is at least equal
                                                         *  to 0x00040003.  If we needed DirectX 5.0
                                                         *  functionality, we'd check for 0x00040005.
                                                         *
                                                         *  If your game needs specific DX functionality,
                                                         *  this is a good place to check to ensure that
                                                         *  the correct version of DirectX is available.
                                                         *
                                                         *  On Windows NT, DirectX is pre-installed, so
                                                         *  this check will allow notification of NT users
                                                         *  that they'll need to update through a service
                                                         *  pack, or they won't be able to run the game.
                                                         */

                                                        if( dwVersion < (DWORD)0x00040003 )
                                                        {
                                                                sprintf(szBuffer,
                                                                        "Your system is preinstalled with version %d.%d "
                                                                        "of DirectX.  This game requires DirectX version "
                                                                        "4.3 or better to run.\n\n"
                                                                        "Do you want to install the game anyway?",
                                                                        HIWORD(dwVersion), LOWORD(dwVersion));

                                                                result = MessageBox( hDlg, szBuffer, szTitle, MB_YESNO);

                                                                if( result == IDNO )
                                                                {
                                                                        MessageBox( hDlg,
                                                                        "You've chosen not to install the game.\n\n"
                                                                        "Click \"OK\" to end Setup.",
                                                                        szTitle, MB_OK);
                                                                        EndDialog( hDlg, 0 );
                                                                        break;
                                                                }
                                                        }
                                                }
                                        }

                                        /*
                                         * check if there is enough space to install the game
                                         * NOTE: there is always enough space at the moment :-)
                                         */
                                        LoadStrFromRes( IDS_DISK_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);
                                        if( 0 )
                                        {
                                                /* your code goes here */
                                        }
                                        SetInfoText(hDlg, "");

                                        /*
                                         * now copy the files.
                                         */
                                        system_restart = result;

                                        LoadStrFromRes( IDS_COPYING_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);

                                        for( i = 0; i < sizeof( copy_list )/sizeof( copy_list[0] ); i++ )
                                        {
                                                lstrcpy( src, SetupDirectory );
                                                catpath( src, copy_list[i] );

                                                lstrcpy( dst, GameDirectory );
                                                catpath( dst, copy_list[i] );

                                                LoadStrFromRes( IDS_CURRENT_FILE_MSG, szBuffer );
                                                SetInfoText(hDlg, szBuffer, copy_list[i]);

                                                fileop.hwnd     = hDlg;
                                                fileop.wFunc    = FO_COPY;
                                                fileop.pFrom    = src;
                                                fileop.pTo      = dst;
                                                fileop.fFlags   = FOF_SILENT | FOF_NOCONFIRMATION;

                                                while (result = SHFileOperation(&fileop))
                                                {
                                                        char errorText[MAX_PATH+BUFFER_SIZE];

                                                        LoadStrFromRes( IDS_SETUP_FAILURE_MSG, szBuffer );
                                                        wsprintf(errorText, szBuffer, copy_list[i] );
                                                        result = MessageBox( hDlg, errorText, szTitle, MB_RETRYCANCEL );

                                                        if( result == IDCANCEL )
                                                        {
                                                                result = -1;
                                                                break;
                                                        }
                                                }

                                                if( result == 0 )
                                                {
                                                        SetFileAttributes( dst, FILE_ATTRIBUTE_NORMAL );
                                                }
                                        }
                                        SetInfoText(hDlg, "");

                                        LoadStrFromRes( IDS_STARTUP_MSG, szBuffer );
                                        SetInfoText(hDlg, szBuffer);
                                        MakeShortcut();
                                        SetInfoText(hDlg, "");

                                        if( result >= 0 )
                                        {
                                                /*
                                                 * hide current controls
                                                 */
                                                ShowWindow( GetDlgItem(hDlg, IDC_EDIT), SW_HIDE );
                                                ShowWindow( GetDlgItem(hDlg, IDC_DIRECTIONS1), SW_HIDE );
                                                ShowWindow( GetDlgItem(hDlg, IDC_DIRECTIONS2), SW_HIDE );
                                                ShowWindow( GetDlgItem(hDlg, IDC_EDITTEXT), SW_HIDE );
                                                ShowWindow( GetDlgItem(hDlg, IDC_INFO), SW_HIDE );
                                                ShowWindow( GetDlgItem(hDlg, IDC_BROWSE), SW_HIDE );

                                                if( system_restart )
                                                {
                                                        /*
                                                         * show new dialogs
                                                         */
                                                        ShowWindow( GetDlgItem(hDlg, IDC_REBOOT1), SW_SHOW );
                                                        ShowWindow( GetDlgItem(hDlg, IDC_REBOOT2), SW_SHOW );
                                                        LoadStrFromRes( IDS_REBOOT_BUTTON, szBuffer );
                                                        SetWindowText( GetDlgItem(hDlg, IDOK), szBuffer );

                                                        /*
                                                         * set the reboot bitmap into our static control
                                                         */
                                                        LoadStrFromRes( IDS_REBOOT_BITMAP_NAME, szBuffer );
                                                        SetBitmap(hDlg, IDC_STATIC, szBuffer, 270, 195);
                                                        current_dialog++;
                                                }
                                                else
                                                {
                                                        ShowWindow( GetDlgItem(hDlg, IDC_SUCCESS), SW_SHOW );
                                                        LoadStrFromRes( IDS_FINISH_BUTTON, szBuffer );
                                                        SetWindowText( GetDlgItem(hDlg, IDOK), szBuffer );
                                                        current_dialog++;
                                                        EnableWindow(GetDlgItem(hDlg,IDOK), TRUE);
                                                        busy--;
                                                        break;
                                                }
                                        }

                                        EnableWindow(GetDlgItem(hDlg,IDOK), TRUE);
                                        EnableWindow(GetDlgItem(hDlg,IDCANCEL), TRUE);
                                        busy--;

                                        if( result < 0 )
                                        {
                                                EndDialog( hDlg, result );
                                        }
                                }
                                else if (current_dialog == 1)
                                {
                                        /*
                                         * restart windows, kill apps that aren't responding, reboot
                                         */
                                        if( system_restart )
                                        {
                                                ExitWindowsEx( EWX_REBOOT, 0 );
                                        }
                                        else
                                        {
                                                EndDialog( hDlg, 0 );
                                        }
                                }
                                break;

                        case IDCANCEL:
                                if( !busy )
                                {
                                        /*
                                         * only allow cancel if we aren't doing anything else
                                         */
                                        EndDialog( hDlg, -1 );
                                }
                                break;

                        case IDC_BROWSE:
                                if( current_dialog == 0 )
                                {
                                        BROWSEINFO bi;
                                        LPITEMIDLIST pidl;
                                        char ach[MAX_PATH];

                                        bi.hwndOwner      = hDlg;
                                        bi.pidlRoot       = NULL;
                                        bi.pszDisplayName = ach;
                                        bi.lpszTitle      = NULL;
                                        bi.ulFlags        = BIF_RETURNONLYFSDIRS;
                                        bi.lpfn           = NULL;
                                        bi.lParam         = 0;
                                        bi.iImage         = 0;

                                        pidl = SHBrowseForFolder(&bi);

                                        if (pidl)
                                        {
                                                SHGetPathFromIDList(pidl, ach);
                                                SetDlgItemText(hDlg, IDC_EDIT, ach);
                                                _SHFree(pidl);
                                        }
                                }
                                break;
                }
    }
    return 0;
}

/* **************************************************************** */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow)
{
        TCHAR * p;
        TCHAR * x;
        hinst = hInstance;

        /*
         * get our fullpath name and strip the file name
         */
        GetModuleFileName(hInstance, SetupDirectory, sizeof(SetupDirectory));

        for (x=p=SetupDirectory; *p; p=AnsiNext(p))
        {
                if ((*p == '\\') || (*p == '/'))
                        x = p;
        }
        *x = 0;

        /*
         * do the setup thing, it is all one big dialog box that you show
         * and hide things from depending on the screen

* we just sign on, ask where to install, and install
         */
        DialogBox( hInstance, "DLG_MASTER", NULL, (DLGPROC)masterDlgProc );

        return 0;
} /* WinMain */


DINSTALL.H

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       dinstall.h
 *  Content:Game SDK sample setup program
 ***************************************************************************/

#ifndef __DINSTALL_H__
#define __DINSTALL_H__

#define IDC_EDIT1000
#define IDC_EDITTEXT1001
#define IDC_STATIC      1002
#define IDC_REBOOT1     1003
#define IDC_REBOOT2     1004
#define IDC_DIRECTIONS1 1005
#define IDC_DIRECTIONS21006
#define IDC_H           1007
#define IDC_B           1008
#define IDC_INFO        1009
#define IDC_SUCCESS1010
#define IDC_BROWSE1011

/* angusm */
#define IDS_DEFAULT_GAME_DIR       100
#define IDS_TITLE                  101
#define IDS_GROUP_NAME             102
#define IDS_LINK_NAME              103

#define IDS_FINISH_BUTTON          116
#define IDS_REBOOT_BUTTON          117
#define IDS_SIGNON_BITMAP_NAME     118
#define IDS_REBOOT_BITMAP_NAME     119
#define IDS_CREATE_MSG             120
#define IDS_DISK_MSG               121
#define IDS_INSTALL_MSG            122
#define IDS_FAILED_MSG             123
#define IDS_COPYING_MSG            124
#define IDS_CURRENT_FILE_MSG       125
#define IDS_SETUP_FAILURE_MSG      126
#define IDS_STARTUP_MSG            127
#define IDS_NTFAILED_MSG           128
#endif


README.TXT

Shadow
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Creating a shadow of a mesh object.


SHADOW.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: shadow.c
 *
 ***************************************************************************/

#include "rmdemo.h"

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_FLAT;
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMMESHBUILDER teapot_builder = NULL;
    LPDIRECT3DRMMESH teapot_mesh = NULL;
    LPDIRECT3DRMVISUAL teapot_shadow = NULL;
    LPDIRECT3DRMFRAME teapot = NULL;
    LPDIRECT3DRMLIGHT shadow_light = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    HRESULT rval;

    view = view;/* not used */

    if (FAILED(dev->lpVtbl->SetQuality(dev, quality)))
goto generic_error;
    if (FAILED(scene->lpVtbl->SetSceneBackgroundRGB(scene, D3DVAL(1), D3DVAL(1),
                                         D3DVAL(1))))
 goto generic_error;

    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &lights)))
goto generic_error;
    if (FAILED(lights->lpVtbl->SetPosition(lights, scene, D3DVAL(2), D3DVAL(5),
                                -D3DVAL(10))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_POINT, D3DVAL(0.9),
                                  D3DVAL(0.8), D3DVAL(0.7), &shadow_light)))
  goto generic_error;
    if (FAILED(lights->lpVtbl->AddLight(lights, shadow_light)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &l2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l2)))
goto generic_error;

    /*
     * load mesh file
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &teapot_builder)))
goto generic_error;
    rval = teapot_builder->lpVtbl->Load(teapot_builder, "teapot.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load teapot.x.\n");
goto ret_with_error;
    }
    if (FAILED(teapot_builder->lpVtbl->CreateMesh(teapot_builder, &teapot_mesh)))
goto generic_error;
    RELEASE(teapot_builder);

    /*
     * create a teapot frame within the scene
     */
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &teapot)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateShadow(lpD3DRM, (LPDIRECT3DRMVISUAL) teapot_mesh,
                                shadow_light, D3DVAL(0), D3DVAL(-3), D3DVAL(0), D3DVAL(0), D3DVAL(1),
                                D3DVAL(0), &teapot_shadow)))
goto generic_error;

    /*
     * add the loaded mesh into the frame
     */
    if (FAILED(teapot->lpVtbl->AddVisual(teapot, (LPDIRECT3DRMVISUAL) teapot_mesh)))
goto generic_error;
    if (FAILED(teapot->lpVtbl->AddVisual(teapot, teapot_shadow)))
goto generic_error;

    /*
     * set up the frames position, orientation and rotation
     */

    if (FAILED(camera->lpVtbl->SetPosition(camera, scene, D3DVAL(0), D3DVAL(0), -D3DVAL(10))))
goto generic_error;
    if (FAILED(camera->lpVtbl->SetOrientation(camera, scene, D3DVAL(0), D3DVAL(0), D3DVAL(1), D3DVAL(0),
                                   D3DVAL(1), D3DVAL(0))))
   goto generic_error;
    if (FAILED(teapot->lpVtbl->SetRotation(teapot, scene, D3DVAL(0), D3DVAL(1), D3DVAL(1),
                                D3DVAL(0.02))))
goto generic_error;

    RELEASE(lights);
    RELEASE(teapot_mesh);
    RELEASE(teapot_shadow);
    RELEASE(teapot);
    RELEASE(shadow_light);
    RELEASE(l2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(lights);
    RELEASE(teapot_builder);
    RELEASE(teapot_mesh);
    RELEASE(teapot_shadow);
    RELEASE(teapot);
    RELEASE(shadow_light);
    RELEASE(l2);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bNoTextures = TRUE;
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Shadow Direct3DRM Example");
}


README.TXT

This program demonstrated clipped blting and stretched clipped blting.  It
is a non-exclusive mode application that displays a rotating donut in a
window.  Clipped blting can be demonstrated by moving another window partially
or completely in front of the stretch window.  The rotating donut does not
overwrite the clipping window.

The size of the rotating donut can be changed with menu selections.  Any
other size than 1x1 demonstrates stretch blting.  The window can also be
resized by grabbing any one of the corners with the mouse.

Another menu option can be used to change the rate of rotation of the donut.

This is not an exclusive mode application and so it is incapable of setting
the display mode.  Therefore, it must be executed on an 8 bit per pixel
display.  It will not work correctly with other pixel depths.


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by STRETCH.RC
//
#define IDR_MENU                        1
#define ID_FILE_EXIT                    10001
#define ID_ROTATION_STOP                40001
#define ID_ROTATION_SLOW                40011
#define ID_ROTATION_FAST                40012
#define ID_SIZE_1X1                     40002
#define ID_SIZE_2X1                     40003
#define ID_SIZE_3X1                     40004
#define ID_SIZE_1X2                     40005
#define ID_SIZE_2X2                     40006
#define ID_SIZE_3X2                     40007
#define ID_SIZE_1X3                     40008
#define ID_SIZE_2X3                     40009
#define ID_SIZE_3X3                     40010

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40011
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


STRETCH.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:donut.cpp
 *
 ***************************************************************************/

#define NAME "Stretch"
#define TITLE "Stretch"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil.h"

#define SIZEX64
#define SIZEY64
char *szBitmap = "DONUT";

HWNDhwnd;
DWORD           UpdateDelay = 13;
HPALETTE        HPal = NULL;

LPDIRECTDRAWlpDD;// DirectDraw object
LPDIRECTDRAWSURFACElpDDSPrimary;// DirectDraw primary surface
LPDIRECTDRAWSURFACElpDDSOne;// Offscreen surface 1
LPDIRECTDRAWCLIPPER     lpClipper;      // clipper for primary
LPDIRECTDRAWPALETTElpDDPal;// DirectDraw palette
BOOLbActive;// is application active?

/*
 * restoreAll
 *
 * restore all lost objects
 */
BOOL restoreAll( void )
{
    return lpDDSPrimary->Restore() == DD_OK &&
           lpDDSOne->Restore()     == DD_OK &&
           DDReLoadBitmap(lpDDSOne, szBitmap) == DD_OK;

} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORDlastTickCount = 0;
    static intcurrentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORDthisTickCount;
    RECTrcRect;
    RECTdestRect;
    HRESULTddrval;
    POINT               pt;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= UpdateDelay)
    {
return;
    }

    // Move to next frame;
    lastTickCount = thisTickCount;
    currentFrame++;
    if(currentFrame > 59)
    {
        currentFrame = 0;
    }

    // Blit the stuff for the next frame
    rcRect.left   = currentFrame%10*64;
    rcRect.top    = currentFrame/10*64;
    rcRect.right  = currentFrame%10*64 + 64;
    rcRect.bottom = currentFrame/10*64 + 64;

    GetClientRect( hwnd, &destRect );
    if (destRect.right  < 128) destRect.right = 64;
    if (destRect.bottom < 64)  destRect.bottom = 64;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd, &pt );
    OffsetRect(&destRect, pt.x, pt.y);

    while( 1 )
    {
        ddrval = lpDDSPrimary->Blt( &destRect, lpDDSOne, &rcRect, 0, NULL );

if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
            if(!restoreAll())
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }
    if(ddrval != DD_OK)
    {
return;
    }
} /* updateFrame */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( void )
{
    if( lpDD != NULL )
    {
if( lpDDSPrimary != NULL )
{
    lpDDSPrimary->Release();
    lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
    lpDDSOne->Release();
    lpDDSOne = NULL;
}
if( lpDDPal != NULL )
{
    lpDDPal->Release();
    lpDDPal = NULL;
}
lpDD->Release();
lpDD = NULL;
    }
} /* finiObjects */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    RECT  rc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
    bActive = wParam;
break;

    case WM_PALETTECHANGED:
        if ((HWND)wParam == hWnd)
            break;
        // fall through to WM_QUERYNEWPALETTE
    case WM_QUERYNEWPALETTE:
        // install our palette here
        if (lpDDPal)
        {
            lpDDSPrimary->SetPalette(lpDDPal);
        }
        // reload the bitmap into the surface because the palette
        // has changed..
        DDReLoadBitmap(lpDDSOne, szBitmap);
        break;

    case WM_CREATE:
        break;

    case WM_GETMINMAXINFO:
        ((MINMAXINFO*)lParam)->ptMinTrackSize.x = SIZEX;
        ((MINMAXINFO*)lParam)->ptMinTrackSize.y = SIZEY;
        return 0;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
        finiObjects();
PostQuitMessage( 0 );
break;

    case WM_COMMAND:
    switch(LOWORD(wParam))
{
    case ID_ROTATION_STOP:
UpdateDelay = 0x7fffffff;
break;
    case ID_ROTATION_SLOW:
UpdateDelay = 200;
break;
    case ID_ROTATION_FAST:
UpdateDelay = 13;
break;
    case ID_FILE_EXIT:
                PostMessage( hWnd, WM_CLOSE, 0, 0L );
        break;

            case ID_SIZE_1X1: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*1); goto size_me;
            case ID_SIZE_2X1: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*1); goto size_me;
            case ID_SIZE_3X1: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*1); goto size_me;
            case ID_SIZE_1X2: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*2); goto size_me;
            case ID_SIZE_2X2: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*2); goto size_me;
            case ID_SIZE_3X2: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*2); goto size_me;
            case ID_SIZE_1X3: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*3); goto size_me;
            case ID_SIZE_2X3: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*3); goto size_me;
    case ID_SIZE_3X3: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*3); goto size_me;
size_me:
                AdjustWindowRectEx(&rc, GetWindowLong(hWnd, GWL_STYLE),
    GetMenu(hWnd) != NULL, GetWindowLong(hWnd, GWL_EXSTYLE));
SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                    SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE);
break;
    }
    break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects();
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSwc;
    DDSURFACEDESCddsd;
    HRESULTddrval;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
0,
NAME,
TITLE,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
128,
128,
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    PostMessage(hwnd, WM_COMMAND, ID_SIZE_1X1, 0);

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the main DirectDraw object
     */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // create a clipper for the primary surface
    ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    ddrval = lpClipper->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    ddrval = lpDDSPrimary->SetClipper( lpClipper );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // load our palette
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    // make sure to set the palette before loading bitmaps.
    if (lpDDPal)
lpDDSPrimary->SetPalette(lpDDPal);

    // load our bitmap
    lpDDSOne = DDLoadBitmap(lpDD, szBitmap, 0, 0);

    if( lpDDSOne == NULL )
    {
return initFail(hwnd);
    }

    return TRUE;
} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
        else
{
    updateFrame();
}
    }
} /* WinMain */


README.TXT

This is a version of stretch that demonstrates how a windowed DirectDraw
app can/should handle multiple monitors.

files:
    stretch.cpp     - about the same as the original stetch

                      look at how this app uses the routines
                      in ddmm.cpp

                      look at how this app handles WM_MOVE to
                      detect when the window moves monitors.

                      look at how it converts from a window
                      client cordinate to a device cordinate

    ddmm.cpp        - generic directdraw multimonitor helper routines.

                      DirectDrawCreateFromDevice
                      DirectDrawCreateFromWindow
                      DirectDrawDeviceFromWindow

    multimon.h      - stub multimon functions that use GetProcAddress

                      used by ddmm.cpp, so stretch can still run on
                      Win95 or NT4


STRETCH.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       stretch.cpp
 *
 *  stretch with multimonitor support
 *
 ***************************************************************************/

#define NAME "Stretch"
#define TITLE "Stretch"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil.h"
#include "ddmm.h"

#define SIZEX64
#define SIZEY64
char *szBitmap = "DONUT";

HWNDhwnd;
DWORD           UpdateDelay = 100;

LPDIRECTDRAWlpDD;// DirectDraw object
LPDIRECTDRAWSURFACElpDDSPrimary;// DirectDraw primary surface
LPDIRECTDRAWSURFACElpDDSOne;// Offscreen surface 1
LPDIRECTDRAWCLIPPER     lpClipper;      // clipper for primary
LPDIRECTDRAWPALETTElpDDPal;// DirectDraw palette
BOOL                    bActive;        // is application active?

/* multimon stuff
*/
int     MyDevice;
char    MyDeviceName[128];
RECT    MyDeviceRect;

BOOL ddInit(HWND);

/*
 * restoreAll
 *
 * restore all lost objects
 */
BOOL restoreAll( void )
{
    return lpDDSPrimary->Restore() == DD_OK &&
           lpDDSOne->Restore()     == DD_OK &&
           DDReLoadBitmap(lpDDSOne, szBitmap) == DD_OK;

} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORDlastTickCount = 0;
    static intcurrentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORDthisTickCount;
    RECTrcRect;
    RECTdestRect;
    HRESULTddrval;
    POINT               pt;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= UpdateDelay)
    {
return;
    }

    // Move to next frame;
    lastTickCount = thisTickCount;
    currentFrame++;
    if(currentFrame > 59)
    {
        currentFrame = 0;
    }

    // Blit the stuff for the next frame
    rcRect.left   = currentFrame%10*64;
    rcRect.top    = currentFrame/10*64;
    rcRect.right  = currentFrame%10*64 + 64;
    rcRect.bottom = currentFrame/10*64 + 64;

    GetClientRect( hwnd, &destRect );
    if (destRect.right  < 64) destRect.right = 64;
    if (destRect.bottom < 64)  destRect.bottom = 64;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd, &pt );
    pt.x -= MyDeviceRect.left;
    pt.y -= MyDeviceRect.top;
    OffsetRect(&destRect, pt.x, pt.y);

    while( 1 )
    {
        ddrval = lpDDSPrimary->Blt( &destRect, lpDDSOne, &rcRect, 0, NULL );

if( ddrval == DD_OK )
{
    break;
}
if( ddrval == DDERR_SURFACELOST )
{
            if(!restoreAll())
    {
return;
    }
}
if( ddrval != DDERR_WASSTILLDRAWING )
{
    return;
}
    }
    if(ddrval != DD_OK)
    {
return;
    }
} /* updateFrame */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( BOOL fKillDD )
{
    if( lpDDSPrimary != NULL )
    {
        lpDDSPrimary->Release();
        lpDDSPrimary = NULL;
    }
    if( lpDDSOne != NULL )
    {
        lpDDSOne->Release();
        lpDDSOne = NULL;
    }
    if( lpDDPal != NULL )
    {
        lpDDPal->Release();
        lpDDPal = NULL;
    }
    if( fKillDD && lpDD != NULL )
    {
        lpDD->Release();
        lpDD = NULL;
    }
} /* finiObjects */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    RECT  rc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
    bActive = wParam;
break;

    case WM_DISPLAYCHANGE:
        ddInit(hwnd);
        break;

    case WM_SIZE:
    case WM_MOVE:
        if (MyDevice != DirectDrawDeviceFromWindow(hwnd,NULL,NULL))
        {
            ddInit(hwnd);
        }
        break;

    case WM_PALETTECHANGED:
        if ((HWND)wParam == hWnd)
            break;
        // fall through to WM_QUERYNEWPALETTE
    case WM_QUERYNEWPALETTE:
        // install our palette here
        if (lpDDPal)
        {
            lpDDSPrimary->SetPalette(lpDDPal);
        }
        // reload the bitmap into the surface because the palette
        // has changed..
        DDReLoadBitmap(lpDDSOne, szBitmap);
        break;

    case WM_CREATE:
        break;

    case WM_GETMINMAXINFO:
        ((MINMAXINFO*)lParam)->ptMinTrackSize.x = SIZEX;
        ((MINMAXINFO*)lParam)->ptMinTrackSize.y = SIZEY;
        return 0;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
        finiObjects(TRUE);
PostQuitMessage( 0 );
        break;

    case WM_KEYUP:
        if (wParam >= '1' && wParam <= '9')
        {
            SetRect(&rc, 0, 0, SIZEX*(wParam-'0'), SIZEY*(wParam-'0'));
            goto size_me;
        }
        break;

    case WM_COMMAND:
    switch(LOWORD(wParam))
{
    case ID_ROTATION_STOP:
UpdateDelay = 0x7fffffff;
break;
    case ID_ROTATION_SLOW:
UpdateDelay = 200;
break;
    case ID_ROTATION_FAST:
                UpdateDelay = 100;
break;
    case ID_FILE_EXIT:
                PostMessage( hWnd, WM_CLOSE, 0, 0L );
        break;

            case ID_SIZE_1X1: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*1); goto size_me;
            case ID_SIZE_2X1: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*1); goto size_me;
            case ID_SIZE_3X1: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*1); goto size_me;
            case ID_SIZE_1X2: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*2); goto size_me;
            case ID_SIZE_2X2: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*2); goto size_me;
            case ID_SIZE_3X2: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*2); goto size_me;
            case ID_SIZE_1X3: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*3); goto size_me;
            case ID_SIZE_2X3: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*3); goto size_me;
    case ID_SIZE_3X3: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*3); goto size_me;
size_me:
                AdjustWindowRectEx(&rc, GetWindowLong(hWnd, GWL_STYLE),
    GetMenu(hWnd) != NULL, GetWindowLong(hWnd, GWL_EXSTYLE));
SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                    SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE);
break;
    }
    break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects(TRUE);
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * ddInit
 */
BOOL ddInit( HWND hwnd )
{
    DDSURFACEDESCddsd;
    HRESULT             ddrval;
    char                ach[128];

    /*
     * clean up our mess
     */
    finiObjects(TRUE);

    MyDevice = DirectDrawDeviceFromWindow(hwnd,MyDeviceName,&MyDeviceRect);

    if (MyDeviceName[0] == 0) lstrcpy(MyDeviceName, "(none)");
    wsprintf(ach, "%s - %s", TITLE, MyDeviceName);
    SetWindowText(hwnd, ach);

    /*
     * create the main DirectDraw object
     */
    lpDD = DirectDrawCreateFromWindow(hwnd);

    if( lpDD == NULL )
    {
return initFail(hwnd);
    }
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
return initFail(hwnd);
    }

    // create a clipper for the primary surface
    ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpClipper->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpDDSPrimary->SetClipper( lpClipper );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    // load our palette
    lpDDPal = DDLoadPalette(lpDD, szBitmap);

    // make sure to set the palette before loading bitmaps.
    if (lpDDPal)
lpDDSPrimary->SetPalette(lpDDPal);

    // load our bitmap
    lpDDSOne = DDLoadBitmap(lpDD, szBitmap, 0, 0);

    if( lpDDSOne == NULL )
    {
return initFail(hwnd);
    }

    return TRUE;
} /* doInit */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSwc;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = NULL; // MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd = CreateWindowEx(
0,
NAME,
TITLE,
        WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
CW_USEDEFAULT,
CW_USEDEFAULT,
128,
128,
NULL,
NULL,
hInstance,
NULL );

    if( !hwnd )
    {
return FALSE;
    }

    PostMessage(hwnd, WM_COMMAND, ID_SIZE_3X3, 0);

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    /*
     * create the direct draw objects
     */
    ddInit(hwnd);

    return TRUE;

} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
    MSG         msg;

    if( !doInit( hInstance, nCmdShow ) )
    {
return FALSE;
    }

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
        else
{
    updateFrame();
}
    }
} /* WinMain */


README.TXT

This is a version of stretch that demonstrates how a windowed DirectDraw
app can/should handle multiple monitors.

files:
    stretch.cpp     - about the same as the original stetch

                      look at how this app uses the routines
                      in ddmm.cpp

                      look at how this app handles WM_MOVE to
                      detect when the window moves monitors.

                      look at how it converts from a window
                      client cordinate to a device cordinate

                      look at how is shares artwork between
                      DirectDraw objects

    ddmm.cpp        - generic directdraw multimonitor helper routines.

                      DirectDrawCreateFromDevice
                      DirectDrawCreateFromWindow
                      DirectDrawDeviceFromWindow

    multimon.h      - stub multimon functions that use GetProcAddress

                      used by ddmm.cpp, so stretch can still run on
                      Win95 or NT4


DDMM.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       ddmm.cpp
 *  Content:    Routines for using DirectDraw on a multimonitor system
 *
 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#define WINVER 0x0400
#define _WIN32_WINDOWS 0x0400
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "ddmm.h"

#define COMPILE_MULTIMON_STUBS
#include "multimon.h"

/*
 *  OneMonitorCallback
 */
BOOL CALLBACK OneMonitorCallback(HMONITOR hMonitor, HDC hdc, LPRECT prc, LPARAM lParam)
{
    HMONITOR *phMonitorFound = (HMONITOR *)lParam;

    if (*phMonitorFound == 0)
        *phMonitorFound = hMonitor;
    else
        *phMonitorFound = INVALID_HANDLE_VALUE;

    return TRUE;
}

/*
 *  OneMonitorFromWindow
 *
 *  similar to the Win32 function MonitorFromWindow, except
 *  only returns a HMONITOR if a window is on a single monitor.
 *
 *  if the window handle is NULL, the primary monitor is returned
 *  if the window is not visible returns NULL
 *  if the window is on a single monitor returns its HMONITOR
 *  if the window is on more than on monitor returns INVALID_HANDLE_VALUE
 */
HMONITOR OneMonitorFromWindow(HWND hwnd)
{
    HMONITOR hMonitor = NULL;
    RECT rc;

    if (hwnd)
    {
        GetClientRect(hwnd, &rc);
        ClientToScreen(hwnd, (LPPOINT)&rc);
        ClientToScreen(hwnd, (LPPOINT)&rc+1);
    }
    else
    {
        //SetRect(&rc,0,0,1,1);
        SetRectEmpty(&rc);
    }

    EnumDisplayMonitors(NULL, &rc, OneMonitorCallback, (LPARAM)&hMonitor);
    return hMonitor;
}

/*
 * FindDeviceCallback
 */
typedef struct {
    LPSTR   szDevice;
    GUID*   lpGUID;
    GUID    GUID;
    BOOL    fFound;
}   FindDeviceData;

BOOL CALLBACK FindDeviceCallback(GUID* lpGUID, LPSTR szName, LPSTR szDevice, LPVOID lParam)
{
    FindDeviceData *p = (FindDeviceData*)lParam;

    if (lstrcmpi(p->szDevice, szDevice) == 0)
    {
if (lpGUID)
{
    p->GUID = *lpGUID;
    p->lpGUID = &p->GUID;
}
else
{
    p->lpGUID = NULL;
}
p->fFound = TRUE;
return FALSE;
    }
    return TRUE;
}

/*
 * DirectDrawCreateFromDevice
 *
 * create a DirectDraw object for a particular device
 */
IDirectDraw * DirectDrawCreateFromDevice(LPSTR szDevice)
{
    IDirectDraw*    pdd = NULL;
    FindDeviceData  find;

    find.szDevice = szDevice;
    find.fFound   = FALSE;
    DirectDrawEnumerate(FindDeviceCallback, (LPVOID)&find);

    if (find.fFound)
    {
        DirectDrawCreate(find.lpGUID, &pdd, NULL);
    }

    return pdd;
}

/*
 * DirectDrawDeviceFromWindow
 *
 * find the direct draw device that should be used for a given window
 *
 * the return code is a "unique id" for the device, it should be used
 * to determine when your window moves from one device to another.
 *
 *      case WM_MOVE:
 *          if (MyDevice != DirectDrawDeviceFromWindow(hwnd,NULL,NULL))
 *          {
 *              // handle moving to a new device.
 *          }
 *
 */
int DirectDrawDeviceFromWindow(HWND hwnd, LPSTR szDevice, RECT *prc)
{
    HMONITOR hMonitor;

    if (GetSystemMetrics(SM_CMONITORS) <= 1)
    {
        if (prc) SetRect(prc,0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
        if (szDevice) lstrcpy(szDevice, "DISPLAY");
        return -1;
    }

    hMonitor = OneMonitorFromWindow(hwnd);

    if (hMonitor == NULL || hMonitor == INVALID_HANDLE_VALUE)
    {
if (prc) SetRectEmpty(prc);
if (szDevice) *szDevice=0;
        return 0;
    }
    else
    {
if (prc != NULL || szDevice != NULL)
{
    MONITORINFOEX mi;
    mi.cbSize = sizeof(mi);
    GetMonitorInfo(hMonitor, &mi);
    if (prc) *prc = mi.rcMonitor;
    if (szDevice) lstrcpy(szDevice, mi.szDevice);
}
        return (int)hMonitor;
    }
}

/*
 * DirectDrawCreateFromWindow
 */
IDirectDraw * DirectDrawCreateFromWindow(HWND hwnd)
{
    IDirectDraw *pdd;
    char szDevice[80];

    //
    // if there is only one monitor, just create a DD object!
    //
    if (GetSystemMetrics(SM_CMONITORS) <= 1)
    {
DirectDrawCreate(NULL, &pdd, NULL);
        return pdd;
    }

    //
    // find the direct draw device that the window is on
    //
    if (DirectDrawDeviceFromWindow(hwnd, szDevice, NULL))
    {
//
// the window is only on one device,
// do a create for only that device
//
        return DirectDrawCreateFromDevice(szDevice);
    }
    else
    {
//
// the window is off the screen or spans two
// monitors, do a DirectDrawCreate(NULL)
        //
        DirectDrawCreate(NULL, &pdd, NULL);
return pdd;
    }
}


STRETCH.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       stretch.cpp
 *
 *  stretch with multimonitor support
 *
 ***************************************************************************/

#define NAME "Stretch"
#define TITLE "Stretch"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include "resource.h"
#include "ddutil2.h"
#include "ddmm.h"

#define SIZEX64
#define SIZEY64
char *szBitmap = "DONUT";

HWNDhwnd1;
HWNDhwnd2;
DWORD           UpdateDelay = 100;

LPDIRECTDRAW2lpDDArtWork;// DirectDraw object
LPDIRECTDRAWSURFACE3lpDDSArtWorkOriginal;
LPDIRECTDRAWSURFACE3lpDDSArtWork;
LPDIRECTDRAWPALETTElpDDPal = 0;// DirectDraw palette

LPDIRECTDRAW2lpDD1;// DirectDraw object
LPDIRECTDRAWSURFACE3lpDDSPrimary1;// DirectDraw primary surface
LPDIRECTDRAWSURFACE3lpDDSOne1;// Offscreen surface 1
LPDIRECTDRAWCLIPPER     lpClipper1;      // clipper for primary
DWORDThdId1;

LPDIRECTDRAW2lpDD2;// DirectDraw object
LPDIRECTDRAWSURFACE3lpDDSPrimary2;// DirectDraw primary surface
LPDIRECTDRAWSURFACE3lpDDSOne2;// Offscreen surface 1
LPDIRECTDRAWCLIPPER     lpClipper2;      // clipper for primary
DWORDThdId2;

volatile int SpinLock = 0;
BOOL                    bActive;        // is application active?
VOID *                  lpArtWork;      // shared art work

/* multimon stuff
*/
int     MyDevice1;
char    MyDeviceName1[128];
RECT    MyDeviceRect1;
int     MyDevice2;
char    MyDeviceName2[128];
RECT    MyDeviceRect2;

typedef struct
{
    HINSTANCE hInstance;
    int nCmdShow;
}Thread_Info;

Thread_Info ThdInfo;

BOOL ddInit(HWND);
BOOL ddInit1(HWND);

/*
 * restoreAll
 *
 * restore all lost objects
 */
BOOL restoreAll( void )
{
    return (
           lpDDSPrimary1->Restore() &&
           lpDDSPrimary2->Restore() &&
           lpDDSOne1->Restore() &&
           lpDDSOne2->Restore()
   );
} /* restoreAll */

/*
 * updateFrame
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame( void )
{
    static DWORDlastTickCount = 0;
    static intcurrentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORDthisTickCount;
    RECTrcRect;
    RECTdestRect;
    HRESULTddrval;
    POINT               pt;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= UpdateDelay)
    {
return;
    }

    // Move to next frame;
    lastTickCount = thisTickCount;
    currentFrame++;
    if(currentFrame > 59)
    {
        currentFrame = 0;
    }

    // Blit the stuff for the next frame
    rcRect.left   = currentFrame%10*64;
    rcRect.top    = currentFrame/10*64;
    rcRect.right  = currentFrame%10*64 + 64;
    rcRect.bottom = currentFrame/10*64 + 64;

    GetClientRect( hwnd1, &destRect );
    if (destRect.right  < 64) destRect.right = 64;
    if (destRect.bottom < 64)  destRect.bottom = 64;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd1, &pt );
    pt.x -= MyDeviceRect1.left;
    pt.y -= MyDeviceRect1.top;
    OffsetRect(&destRect, pt.x, pt.y);

    while( 1 )
    {
        ddrval = lpDDSPrimary1->Blt( &destRect, lpDDSOne1, &rcRect, 0, NULL );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if(!restoreAll())
            {
                return;
            }
     }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    };
    if(ddrval != DD_OK)
    {
        return;
    }
} /* updateFrame */

/*
 * updateFrame1
 *
 * Decide what needs to be blitted next, wait for flip to complete,
 * then flip the buffers.
 */
void updateFrame1( void )
{
    static DWORDlastTickCount = 0;
    static intcurrentFrame = 0;
    static BOOL         haveBackground = FALSE;
    DWORDthisTickCount;
    RECTrcRect;
    RECTdestRect;
    HRESULTddrval;
    POINT               pt;

    thisTickCount = GetTickCount();
    if((thisTickCount - lastTickCount) <= UpdateDelay)
    {
return;
    }

    // Move to next frame;
    lastTickCount = thisTickCount;
    currentFrame++;
    if(currentFrame > 59)
    {
        currentFrame = 0;
    }

    // Blit the stuff for the next frame
    rcRect.left   = currentFrame%10*64;
    rcRect.top    = currentFrame/10*64;
    rcRect.right  = currentFrame%10*64 + 64;
    rcRect.bottom = currentFrame/10*64 + 64;

    GetClientRect( hwnd2, &destRect );
    if (destRect.right  < 64) destRect.right = 64;
    if (destRect.bottom < 64)  destRect.bottom = 64;

    pt.x = pt.y = 0;
    ClientToScreen( hwnd2, &pt );
    pt.x -= MyDeviceRect2.left;
    pt.y -= MyDeviceRect2.top;
    OffsetRect(&destRect, pt.x, pt.y);

    while( 1 )
    {
        ddrval = lpDDSPrimary2->Blt( &destRect, lpDDSOne2, &rcRect, 0, NULL );

        if( ddrval == DD_OK )
        {
            break;
        }
        if( ddrval == DDERR_SURFACELOST )
        {
            if(!restoreAll())
            {
                return;
            }
     }
        if( ddrval != DDERR_WASSTILLDRAWING )
        {
            return;
        }
    }

    if(ddrval != DD_OK)
    {
        return;
    }
} /* updateFrame1 */

/*
 * finiObjects
 *
 * finished with all objects we use; release them
 */
static void finiObjects( BOOL fKillDD )
{
    if( lpDDSPrimary1 != NULL )
    {
        lpDDSPrimary1->Release();
        lpDDSPrimary1 = NULL;
    }
    if( lpDDSOne1 != NULL )
    {
        lpDDSOne1->Release();
        lpDDSOne1 = NULL;
    }
    if( lpDDPal != NULL )
    {
        lpDDPal->Release();
        lpDDPal = NULL;
    }
    if( fKillDD && lpDD1 != NULL )
    {
        lpDD1->Release();
        lpDD1 = NULL;
    }
} /* finiObjects */
static void finiObjects1( BOOL fKillDD )
{
    if( lpDDSPrimary2 != NULL )
    {
        lpDDSPrimary2->Release();
        lpDDSPrimary2 = NULL;
    }
    if( lpDDSOne2 != NULL )
    {
        lpDDSOne2->Release();
        lpDDSOne2 = NULL;
    }
    if( lpDDPal != NULL )
    {
        lpDDPal->Release();
        lpDDPal = NULL;
    }
    if( fKillDD && lpDD2 != NULL )
    {
        lpDD2->Release();
        lpDD2 = NULL;
    }
} /* finiObjects1 */

long FAR PASCAL WindowProc( HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam )
{
    RECT  rc;

    switch( message )
    {
    case WM_ACTIVATEAPP:
    bActive = wParam;
break;

    case WM_SIZE:
    case WM_MOVE:
        if(hWnd == hwnd1)
        {
            if (MyDevice1 != DirectDrawDeviceFromWindow(hWnd,NULL,NULL))
            {
                ddInit(hwnd1);
            }
        }
        if(hWnd == hwnd2)
        {
            if (MyDevice2 != DirectDrawDeviceFromWindow(hWnd,NULL,NULL))
            {
                ddInit1(hwnd2);
            }
        }
        break;

    case WM_PALETTECHANGED:
        if ((HWND)wParam == hWnd)
            break;
        // fall through to WM_QUERYNEWPALETTE
    case WM_QUERYNEWPALETTE:
/*--------------------------------------------------------**
**  Er um, truth is this app is broken on palette change  **
**  see ddInitArtWork to understand why.                  **
**--------------------------------------------------------*/
        break;

    case WM_CREATE:
        break;

    case WM_GETMINMAXINFO:
        ((MINMAXINFO*)lParam)->ptMinTrackSize.x = SIZEX;
        ((MINMAXINFO*)lParam)->ptMinTrackSize.y = SIZEY;
        return 0;

    case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
    break;
}
break;

    case WM_DESTROY:
        if(hWnd == hwnd1)
        {
            finiObjects(TRUE);
    }
        if(hWnd == hwnd2)
        {
            finiObjects1(TRUE);
}
PostQuitMessage( 0 );
        break;

    case WM_KEYUP:
        if (wParam >= '1' && wParam <= '9')
        {
            SetRect(&rc, 0, 0, SIZEX*(wParam-'0'), SIZEY*(wParam-'0'));
            goto size_me;
        }
        break;

    case WM_COMMAND:
    switch(LOWORD(wParam))
{
    case ID_ROTATION_STOP:
UpdateDelay = 0x7fffffff;
break;
    case ID_ROTATION_SLOW:
UpdateDelay = 200;
break;
    case ID_ROTATION_FAST:
                UpdateDelay = 100;
break;
    case ID_FILE_EXIT:
                PostMessage( hWnd, WM_CLOSE, 0, 0L );
        break;

            case ID_SIZE_1X1: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*1); goto size_me;
            case ID_SIZE_2X1: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*1); goto size_me;
            case ID_SIZE_3X1: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*1); goto size_me;
            case ID_SIZE_1X2: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*2); goto size_me;
            case ID_SIZE_2X2: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*2); goto size_me;
            case ID_SIZE_3X2: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*2); goto size_me;
            case ID_SIZE_1X3: SetRect(&rc, 0, 0, SIZEX*1, SIZEY*3); goto size_me;
            case ID_SIZE_2X3: SetRect(&rc, 0, 0, SIZEX*2, SIZEY*3); goto size_me;
    case ID_SIZE_3X3: SetRect(&rc, 0, 0, SIZEX*3, SIZEY*3); goto size_me;
size_me:
                AdjustWindowRectEx(&rc, GetWindowLong(hWnd, GWL_STYLE),
    GetMenu(hWnd) != NULL, GetWindowLong(hWnd, GWL_EXSTYLE));
SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                    SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE);
break;
    }
    break;
    }

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

} /* WindowProc */

/*
 * This function is called if the initialization function fails
 */
BOOL initFail( HWND hwnd )
{
    finiObjects(TRUE);
    finiObjects1(TRUE);
    MessageBox( hwnd, "DirectDraw Init FAILED", TITLE, MB_OK );
    DestroyWindow( hwnd );
    return FALSE;

} /* initFail */

/*
 * ddInit
 */
BOOL ddInit( HWND hwnd )
{
    DDSURFACEDESCddsd;
    HRESULT             ddrval;
    char                ach[128];

    /*
     * clean up our mess
     */
    finiObjects(TRUE);

    MyDevice1 = DirectDrawDeviceFromWindow(hwnd,MyDeviceName1,&MyDeviceRect1);

    if (MyDeviceName1[0] == 0) lstrcpy(MyDeviceName1, "(none)");
    wsprintf(ach, "%s - %s", TITLE, MyDeviceName1);
    SetWindowText(hwnd1, ach);

    /*
     * create the main DirectDraw object
     */
    LPDIRECTDRAW lpDDT;
    lpDDT = DirectDrawCreateFromWindow(hwnd);
    if( lpDDT == NULL )
    {
    return initFail(hwnd);
    }
    ddrval = lpDDT->QueryInterface(IID_IDirectDraw2, (void**)&lpDD1);

    if( lpDD1 == NULL )
    {
    return initFail(hwnd);
    }
    lpDDT->Release();

    ddrval = lpDD1->SetCooperativeLevel( hwnd, DDSCL_NORMAL );

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    LPDIRECTDRAWSURFACE lpDDSTemp;
    ddrval = lpDD1->CreateSurface( &ddsd, &lpDDSTemp, NULL );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }
    ddrval = lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3, (void**)&lpDDSPrimary1);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }
    lpDDSTemp->Release();

    // create a clipper for the primary surface
    ddrval = lpDD1->CreateClipper( 0, &lpClipper1, NULL );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpClipper1->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpDDSPrimary1->SetClipper( lpClipper1 );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

     // load our palette
     lpDDPal = DDLoadPalette2(lpDD1, szBitmap);

     // make sure to set the palette before loading bitmaps.
     if (lpDDPal)
        lpDDSPrimary1->SetPalette(lpDDPal);

    // load our bitmap
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
    ddsd.dwWidth = 2;
    ddsd.dwHeight = 2;

    if (lpDD1->CreateSurface(&ddsd, &lpDDSTemp, NULL) != DD_OK)
    {
        OutputDebugString("CreateSurf failed");
        return initFail(hwnd);
    }
    //CreateSurface must set SurfaceMemory Flag
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
    ddsd.dwWidth = 2;  // It's going away anyway
    ddsd.dwHeight = 2;
    if (lpDD1->CreateSurface(&ddsd, &lpDDSTemp, NULL) != DD_OK)
    {
        OutputDebugString("CreateSurf failed");
        return initFail(hwnd);
    };

    if (lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3,(void**)&lpDDSOne1) != DD_OK)
    {
        OutputDebugString("QueryInterface failed");
        return initFail(hwnd);
    }
    lpDDSTemp->Release();

    if( lpDDSOne1 == NULL )
    {
    return initFail(hwnd);
    }

    //Get the size of the surface
    DDSURFACEDESC DDSDesc;
    memset(&DDSDesc,0,sizeof(DDSURFACEDESC));
    DDSDesc.dwSize = sizeof(DDSURFACEDESC);
    DDSDesc.dwFlags =  DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    ddrval = lpDDSArtWork->GetSurfaceDesc(&DDSDesc);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    //Set the memory into the surface
    DDSDesc.dwFlags =  DDSD_LPSURFACE | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    DDSDesc.lpSurface = lpArtWork;

    ddrval = lpDDSOne1->SetSurfaceDesc(&DDSDesc,0);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    return TRUE;
} /* doInit */

/*
 * ddInit1
 */
BOOL ddInit1( HWND hwnd )
{
    DDSURFACEDESCddsd;
    HRESULT             ddrval;
    char                ach[128];

    /*
     * clean up our mess
     */
    finiObjects1(TRUE);

    MyDevice2 = DirectDrawDeviceFromWindow(hwnd,MyDeviceName2,&MyDeviceRect2);

    if (MyDeviceName2[0] == 0) lstrcpy(MyDeviceName2, "(none)");
    wsprintf(ach, "%s - %s", TITLE, MyDeviceName2);
    SetWindowText(hwnd, ach);

    /*
     * create the main DirectDraw object
     */
    LPDIRECTDRAW lpDDT;
    lpDDT = DirectDrawCreateFromWindow(hwnd);
    if( lpDDT == NULL )
    {
    return initFail(hwnd);
    }
    ddrval = lpDDT->QueryInterface(IID_IDirectDraw2, (void**)&lpDD2);

    if( lpDD2 == NULL )
    {
    return initFail(hwnd);
    }
    lpDDT->Release();
    ddrval = lpDD2->SetCooperativeLevel( hwnd, DDSCL_NORMAL );

    // Create the primary surface
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    LPDIRECTDRAWSURFACE lpDDSTemp;
    ddrval = lpDD2->CreateSurface( &ddsd, &lpDDSTemp, NULL );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }
    ddrval = lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3, (void**)&lpDDSPrimary2);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }
    lpDDSTemp->Release();

    // create a clipper for the primary surface
    ddrval = lpDD2->CreateClipper( 0, &lpClipper2, NULL );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpClipper2->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    ddrval = lpDDSPrimary2->SetClipper( lpClipper2 );
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    // load our palette
    lpDDPal = DDLoadPalette2(lpDD2, szBitmap);

    // make sure to set the palette before loading bitmaps.
    if (lpDDPal)
        lpDDSPrimary2->SetPalette(lpDDPal);

    //CreateSurface must set SurfaceMemory Flag
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
    ddsd.dwWidth = 2;  // It's going away anyway
    ddsd.dwHeight = 2;
    if (lpDD2->CreateSurface(&ddsd, &lpDDSTemp, NULL) != DD_OK)
    {
        OutputDebugString("CreateSurf failed");
        return initFail(hwnd);
    };

    if (lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3,(void**)&lpDDSOne2) != DD_OK)
    {
        OutputDebugString("QueryInterface failed");
        return initFail(hwnd);
    }
    lpDDSTemp->Release();

    if( lpDDSOne2 == NULL )
    {
    return initFail(hwnd);
    }

    //Get the size of the surface
    DDSURFACEDESC DDSDesc;
    memset(&DDSDesc,0,sizeof(DDSURFACEDESC));
    DDSDesc.dwSize = sizeof(DDSURFACEDESC);
    DDSDesc.dwFlags =  DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    ddrval = lpDDSArtWork->GetSurfaceDesc(&DDSDesc);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    //Set the memory into the surface
    DDSDesc.dwFlags =  DDSD_LPSURFACE | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    DDSDesc.lpSurface = lpArtWork;

    ddrval = lpDDSOne2->SetSurfaceDesc(&DDSDesc,0);
    if( ddrval != DD_OK )
    {
        return initFail(hwnd);
    }

    return TRUE;
} /* doInit1 */

/*-------------------------------------------------------**
** This code assumes that if more than one monitor exist **
** that they have the same color depth, else sharing     **
** artwork doesn't make sense                            **
**-------------------------------------------------------*/
BOOL ddInitArtWork(void )
{
    DDSURFACEDESCddsd;
    HRESULT             ddrval;

    /*
     * create the main DirectDraw object
     */
    LPDIRECTDRAW lpDDT;
    ddrval = DirectDrawCreate(NULL,&lpDDT,0);
    if( ddrval != DD_OK )
    {
    return initFail(NULL);
    }
    ddrval = lpDDT->QueryInterface(IID_IDirectDraw2, (void**)&lpDDArtWork);
    if( ddrval != DD_OK )
    {
    return initFail(NULL);
    }

    lpDDT->Release();
    ddrval = lpDDArtWork->SetCooperativeLevel( NULL, DDSCL_NORMAL );

    //CreateSurface must set SurfaceMemory Flag
    LPDIRECTDRAWSURFACE lpDDSTemp;
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
    ddsd.dwWidth = 2;  // It's going away anyway
    ddsd.dwHeight = 2;
    if (lpDDArtWork->CreateSurface(&ddsd, &lpDDSTemp, NULL) != DD_OK)
    {
        OutputDebugString("CreateSurf failed");
        return initFail(NULL);
    };

    if (lpDDSTemp->QueryInterface(IID_IDirectDrawSurface3,(void**)&lpDDSArtWork) != DD_OK)
    {
        OutputDebugString("QueryInterface failed");
        return initFail(NULL);
    }
    lpDDSTemp->Release();

    if( lpDDSArtWork == NULL )
    {
    return initFail(NULL);
    }

    // load our palette
    lpDDPal = DDLoadPalette2(lpDDArtWork, szBitmap);

    //copy the preconverted bits to the surface
    lpDDSArtWorkOriginal = DDLoadBitmap2(lpDDArtWork, szBitmap, 0, 0);
    // make sure to set the palette before loading bitmaps.
    if (lpDDPal != 0)
lpDDSArtWorkOriginal->SetPalette(lpDDPal);
    //Get the size of the surface
    DDSURFACEDESC DDSDesc;
    memset(&DDSDesc,0,sizeof(DDSURFACEDESC));
    DDSDesc.dwSize = sizeof(DDSURFACEDESC);
    DDSDesc.dwFlags =  DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    ddrval = lpDDSArtWorkOriginal->GetSurfaceDesc(&DDSDesc);
    if( ddrval != DD_OK )
    {
        return initFail(NULL);
    }

    //Alloc a surface.  The pitch must always be an integral number
    // of DWORDs, so we compute the theoretical pitch and then round it up.
    DDSDesc.lPitch =  DDSDesc.dwWidth*DDSDesc.ddpfPixelFormat.dwRGBBitCount/8;
    DDSDesc.lPitch = (DDSDesc.lPitch + 3) & ~3;

    lpArtWork = GlobalAlloc(GPTR, DDSDesc.lPitch * DDSDesc.dwHeight);

    //Set the memory into the surface
    DDSDesc.dwFlags =  DDSD_LPSURFACE | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH;
    DDSDesc.lpSurface = lpArtWork;

    ddrval = lpDDSArtWork->SetSurfaceDesc(&DDSDesc,0);
    if( ddrval != DD_OK )
    {
        return initFail(NULL);
    }

    //copy the preconverted bits to the surface
    ddrval = lpDDSArtWork->Blt(NULL,lpDDSArtWorkOriginal,NULL,DDBLT_WAIT,NULL);
    if( ddrval != DD_OK )
    {
        return initFail(NULL);
    }
    return TRUE;
} /* doInitArt */

/*
 * doInit - do work required for every instance of the application:
 *  create the window, initialize data
 */
static BOOL doInit( Thread_Info *thd)
//HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSwc;
    MSG         msg;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = thd->hInstance;
    wc.hIcon = LoadIcon( thd->hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = NULL; // MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create a window
     */
    hwnd1 = CreateWindowEx(
0,
NAME,
TITLE,
        WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
CW_USEDEFAULT,
CW_USEDEFAULT,
128,
128,
NULL,
NULL,
thd->hInstance,
NULL );

    if( !hwnd1 )
    {
return FALSE;
    }

    PostMessage(hwnd1, WM_COMMAND, ID_SIZE_3X3, 0);

    ShowWindow( hwnd1, thd->nCmdShow );
    UpdateWindow( hwnd1 );

    /*
     * create the direct draw objects
     */
    ddInit(hwnd1);

    // Wait for both window to get ready
    SpinLock ++;
while(SpinLock <2);

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
        else
{
    updateFrame();
}
    }
}

static BOOL doInit1(Thread_Info *thd)
//HINSTANCE hInstance, int nCmdShow )
{
    WNDCLASSwc;
    MSG         msg;

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = thd->hInstance;
    wc.hIcon = LoadIcon( thd->hInstance, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = NULL; // MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = NAME;
    RegisterClass( &wc );

    /*
     * create the direct draw objects
     */

    hwnd2 = CreateWindowEx(
0,
NAME,
TITLE,
        WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
CW_USEDEFAULT,
CW_USEDEFAULT,
128,
128,
NULL,
NULL,
thd->hInstance,
NULL );

    if( !hwnd2 )
    {
return FALSE;
    }

    PostMessage(hwnd2, WM_COMMAND, ID_SIZE_3X3, 0);

    ShowWindow( hwnd2, thd->nCmdShow );
    UpdateWindow( hwnd2 );

    /*
     * create the direct draw objects
     */
    ddInit1(hwnd2);

    // Wait for both window to get ready
    SpinLock ++;
while(SpinLock <2);

    while( 1 )
    {
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
    if( !GetMessage( &msg, NULL, 0, 0 ) )
return msg.wParam;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
        else
{
    updateFrame1();
}
    }

} /* doInit */

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
//    MSG         msg;
    HANDLE ThdHnd[2];

    ddInitArtWork();

    ThdInfo.hInstance = hInstance;
    ThdInfo.nCmdShow = nCmdShow;

    ThdHnd[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)doInit,&ThdInfo,0,&ThdId1);
    ThdHnd[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)doInit1,&ThdInfo,0,&ThdId2);

    WaitForMultipleObjects((DWORD)2,ThdHnd,TRUE,(DWORD)-1);
    if( lpDDSArtWork != NULL )
    {
        lpDDSArtWork->Release();
        lpDDSArtWork = NULL;
    }
    if( lpDDSArtWorkOriginal != NULL )
    {
        lpDDSArtWorkOriginal->Release();
        lpDDSArtWorkOriginal = NULL;
    }
    if( lpDDPal != NULL )
    {
        lpDDPal->Release();
        lpDDPal = NULL;
    }
    if( lpDDArtWork != NULL )
    {
        lpDDArtWork->Release();
        lpDDArtWork = NULL;
    }
   // ExitThread(0);
    return TRUE;
} /* WinMain */


README.TXT

Tex1
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Loading a simple texture mapped mesh object.


TEX1.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: tex1.c
 *
 ***************************************************************************/

/*
 * A texture mapping example using a cylindrical mapping.
 */

#include "rmdemo.h"

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMMATERIAL mat = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMLIGHT l1 = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    D3DRMBOX box;
    D3DVALUE miny, maxy;
    D3DVALUE height;
    HRESULT rval;

    dev;
    view;
    camera;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.2),
                                  D3DVAL(0.2), D3DVAL(0.2), &l1)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetOrientation(frame, scene, -D3DVAL(1), -D3DVAL(1),
                                  D3DVAL(1), D3DVAL(0), D3DVAL(1), D3DVAL(0))))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(1),
                                  D3DVAL(1), D3DVAL(1), &l2)))
  goto generic_error;
    if (FAILED(frame->lpVtbl->AddLight(frame, l2)))
goto generic_error;
    RELEASE(frame);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetPosition(frame, scene, D3DVAL(0), D3DVAL(0), D3DVAL(15))))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetRotation(frame, scene, D3DVAL(1.1), D3DVAL(0.3),
                               D3DVAL(0.0), D3DVAL(0.04))))
       goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "sphere4.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere4.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tex7.ppm", &tex);
    if (rval != D3DRM_OK) {
        Msg("Failed to load tex7.ppm.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(lpD3DRM->lpVtbl->CreateMaterial(lpD3DRM, D3DVAL(10.0), &mat)))
goto generic_error;

    if (FAILED(builder->lpVtbl->SetTexture(builder, tex)))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetMaterial(builder, mat)))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetColorRGB(builder, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))
goto generic_error;

    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(mesh->lpVtbl->GetBox(mesh, &box)))
goto generic_error;
    maxy = box.max.y;
    miny = box.min.y;
    height = maxy - miny;

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap(lpD3DRM, D3DRMWRAP_CYLINDER, NULL, D3DVAL(0.0),
                              D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
                              D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
                              D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
                              D3DDivide(miny, height), D3DVAL(1.0),
                              D3DDivide(-D3DVAL(1.0), height), &wrap)))
      goto generic_error;

    if (FAILED(wrap->lpVtbl->Apply(wrap, (LPDIRECT3DRMOBJECT)mesh)))
goto generic_error;
    if (FAILED(frame->lpVtbl->AddVisual(frame, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;

    RELEASE(frame);
    RELEASE(wrap);
    RELEASE(mesh);
    RELEASE(tex);
    RELEASE(mat);
    RELEASE(l1);
    RELEASE(l2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(mesh);
    RELEASE(builder);
    RELEASE(frame);
    RELEASE(tex);
    RELEASE(mat);
    RELEASE(wrap);
    RELEASE(l1);
    RELEASE(l2);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Texture Mapping Direct3DRM Example");
}


README.TXT

Tex3
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Using texture wraps for special effects.


TEX3.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: tex3.c
 *
 ***************************************************************************/

/*
 * A texture mapping example using a spherical mapping, relative to a
 * frame.
 */

#include "rmdemo.h"

void CDECL
applyWrap(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    LPDIRECT3DRMVISUALARRAY visuals;
    LPDIRECT3DRMVISUAL v;
    int count, i;
    LPDIRECT3DRMMESH mesh;

    frame->lpVtbl->GetVisuals(frame, &visuals);
    count = visuals->lpVtbl->GetSize(visuals);

    for (i = 0; i < count; i++) {
visuals->lpVtbl->GetElement(visuals, i, &v);

if (SUCCEEDED(v->lpVtbl->QueryInterface(v, &IID_IDirect3DRMMesh, &mesh))) {
    wrap->lpVtbl->ApplyRelative(wrap, frame, (LPDIRECT3DRMOBJECT)mesh);
    mesh->lpVtbl->Release(mesh);
}
v->lpVtbl->Release(v);
    }

    visuals->lpVtbl->Release(visuals);
}

void CDECL
cleanupWrap(LPDIRECT3DRMOBJECT obj, void* arg)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    obj;

    wrap->lpVtbl->Release(wrap);
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMFRAME axis = NULL;
    LPDIRECT3DRMFRAME child = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMLIGHT l1 = NULL;
    LPDIRECT3DRMLIGHT l2 = NULL;
    HRESULT rval;

    dev;
    view;
    camera;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.2),
                                  D3DVAL(0.2), D3DVAL(0.2), &l1)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, l1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetOrientation(frame, scene, -D3DVAL(1), -D3DVAL(1),
                                  D3DVAL(1), D3DVAL(0), D3DVAL(1), D3DVAL(0))))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(1),
                                  D3DVAL(1), D3DVAL(1), &l2)))
  goto generic_error;
    if (FAILED(frame->lpVtbl->AddLight(frame, l2)))
goto generic_error;
    RELEASE(frame);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetPosition(frame, scene, D3DVAL(0), D3DVAL(0), D3DVAL(15))))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetRotation(frame, scene, D3DVAL(1.1), D3DVAL(0.3),
                               D3DVAL(0.0), D3DVAL(0.04))))
       goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "sphere3.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere3.x.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->Scale(builder, D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2))))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);
    if (FAILED(frame->lpVtbl->AddVisual(frame, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;
    RELEASE(mesh);

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap(lpD3DRM, D3DRMWRAP_SPHERE, frame,
                              D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
                              D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
                              D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
                              D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
      D3DVAL(1.0), &wrap)))
      goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, frame, &axis)))
goto generic_error;
    if (FAILED(axis->lpVtbl->SetRotation(axis, frame, D3DVAL(0), D3DVAL(1), D3DVAL(0), D3DVAL(0.04))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, axis, &child)))
goto generic_error;
    if (FAILED(child->lpVtbl->SetPosition(child, axis, D3DVAL(3), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(child->lpVtbl->SetRotation(child, axis, D3DVAL(1), D3DVAL(1), D3DVAL(0),
                               D3DVAL(0.04))))
       goto generic_error;

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "checker.ppm", &tex);
    if (rval != D3DRM_OK) {
Msg("Failed to load checker.ppm.\n%s", D3DRMErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(tex->lpVtbl->SetColors(tex, 2)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalScale(tex, TRUE)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalOrigin(tex, 128, 128)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    if (FAILED(builder->lpVtbl->Load(builder, "tpot2.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL)))
goto generic_error;
    if (FAILED(builder->lpVtbl->Scale(builder, D3DVAL(2), D3DVAL(2), D3DVAL(2))))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetTexture(builder, tex)))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetQuality(builder, D3DRMRENDER_GOURAUD)))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(child->lpVtbl->AddVisual(child, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;
    if (FAILED(child->lpVtbl->AddMoveCallback(child, applyWrap, (void *) wrap)))
goto generic_error;
    if (FAILED(child->lpVtbl->AddDestroyCallback(child, cleanupWrap, wrap)))
goto generic_error;

    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(child);
    RELEASE(tex);
    RELEASE(l1);
    RELEASE(l2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(mesh);
    RELEASE(builder);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(child);
    RELEASE(tex);
    RELEASE(wrap);
    RELEASE(l1);
    RELEASE(l2);
    return FALSE;
}

void
OverrideDefaults(Defaults *defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Texture Mapping III Direct3DRM Example");
}


README.TXT

Tex4
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Another example of using texture wraps for special effects.


TEX4.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: tex4.c
 *
 ***************************************************************************/

/*
 * A texture mapping example using a spherical mapping relative to the
 * camera. The texture is also used as a background for the scene.
 */

#include "rmdemo.h"

void CDECL
applyWrap(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    LPDIRECT3DRMVISUALARRAY visuals;
    LPDIRECT3DRMVISUAL v;
    int count, i;
    LPDIRECT3DRMMESH mesh;

    frame->lpVtbl->GetVisuals(frame, &visuals);
    count = visuals->lpVtbl->GetSize(visuals);

    for (i = 0; i < count; i++) {
visuals->lpVtbl->GetElement(visuals, i, &v);

if (SUCCEEDED(v->lpVtbl->QueryInterface(v, &IID_IDirect3DRMMesh, &mesh))) {
    wrap->lpVtbl->ApplyRelative(wrap, frame, (LPDIRECT3DRMOBJECT)mesh);
    mesh->lpVtbl->Release(mesh);
}
v->lpVtbl->Release(v);
    }

    visuals->lpVtbl->Release(visuals);
}

void CDECL
cleanupWrap(LPDIRECT3DRMOBJECT obj, void *arg)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    obj;

    wrap->lpVtbl->Release(wrap);
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMFRAME axis = NULL;
    LPDIRECT3DRMFRAME child = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    HRESULT rval;
    dev;
    view;
    camera;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.2),
                                  D3DVAL(0.2), D3DVAL(0.2), &light1)))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(1),
                                  D3DVAL(1), D3DVAL(1), &light2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, light1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetOrientation(frame, scene, -D3DVAL(1), -D3DVAL(1),
                                  D3DVAL(1), D3DVAL(0), D3DVAL(1), D3DVAL(0))))
  goto generic_error;
    if (FAILED(frame->lpVtbl->AddLight(frame, light2)))
goto generic_error;
    RELEASE(frame);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetPosition(frame, scene, D3DVAL(0), D3DVAL(0), D3DVAL(15))))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetRotation(frame, scene, D3DVAL(1.1), D3DVAL(0.3),
                               D3DVAL(0.0), D3DVAL(0.04))))
       goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "sphere3.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere3.x.\n");
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->Scale(builder, D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2))))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);
    if (FAILED(frame->lpVtbl->AddVisual(frame, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;
    RELEASE(mesh);

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap(lpD3DRM, D3DRMWRAP_SPHERE, camera,
      D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
      D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
      D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
      D3DVAL(0.0), D3DVAL(0.0),
      D3DVAL(5.0), D3DVAL(5.0),
      &wrap)))
      goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, frame, &axis)))
goto generic_error;
    if (FAILED(axis->lpVtbl->SetRotation(axis, frame, D3DVAL(0), D3DVAL(1), D3DVAL(0), D3DVAL(0.04))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, axis, &child)))
goto generic_error;
    if (FAILED(child->lpVtbl->SetPosition(child, axis, D3DVAL(3), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(child->lpVtbl->SetRotation(child, axis, D3DVAL(1), D3DVAL(1), D3DVAL(0),
                               D3DVAL(0.04))))
       goto generic_error;

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "lake.ppm", &tex);
    if (rval != D3DRM_OK) {
Msg("Failed to load lake.ppm.\n");
goto ret_with_error;
    }
    if (FAILED(tex->lpVtbl->SetColors(tex, 32)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalScale(tex, TRUE)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalOrigin(tex, 128, 128)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "tpot2.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load tpot2.x.\n");
goto ret_with_error;
    }

    if (FAILED(builder->lpVtbl->Scale(builder, D3DVAL(2), D3DVAL(2), D3DVAL(2))))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetTexture(builder, tex)))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetQuality(builder, D3DRMRENDER_GOURAUD)))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(child->lpVtbl->AddVisual(child, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;
    if (FAILED(child->lpVtbl->AddMoveCallback(child, applyWrap, (void *) wrap)))
goto generic_error;
    if (FAILED(child->lpVtbl->AddDestroyCallback(child, cleanupWrap, wrap)))
goto generic_error;
    if (FAILED(scene->lpVtbl->SetSceneBackgroundImage(scene, tex)))
goto generic_error;

    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(child);
    RELEASE(tex);
    RELEASE(light1);
    RELEASE(light2);

    /* don't release the wrap */
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(child);
    RELEASE(tex);
    RELEASE(wrap);
    RELEASE(light1);
    RELEASE(light2);
    RELEASE(builder);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Texture Mapping IV Direct3DRM Example");
}


README.TXT

Tex5
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Using a Wrap for Chrome mapping and a Texture map as decal.


TEX5.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: tex5.c
 *
 ***************************************************************************/

/*
 * A texture mapping example showing the use of chrome mapping and decals.
 */

#include "rmdemo.h"

void CDECL
applyWrap(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    LPDIRECT3DRMVISUALARRAY visuals;
    LPDIRECT3DRMVISUAL v;
    int count, i;

    frame->lpVtbl->GetVisuals(frame, &visuals);
    if (visuals) {
count = visuals->lpVtbl->GetSize(visuals);

for (i = 0; i < count; i++) {
    visuals->lpVtbl->GetElement(visuals, i, &v);
    wrap->lpVtbl->ApplyRelative(wrap, frame, (LPDIRECT3DRMOBJECT) v);
    v->lpVtbl->Release(v);
}

visuals->lpVtbl->Release(visuals);
    }
}

void CDECL
toggleDecalScale(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMTEXTURE decal = (LPDIRECT3DRMTEXTURE) arg;
    static int i = 0;

    i++;
    if (i == 20) {
int scale;

i = 0;

scale = decal->lpVtbl->GetDecalScale(decal);
decal->lpVtbl->SetDecalScale(decal, !scale);
    }
}

void CDECL
cleanupWrap(LPDIRECT3DRMOBJECT obj, void* arg)
{
    LPDIRECT3DRMWRAP wrap = (LPDIRECT3DRMWRAP) arg;
    obj;

    wrap->lpVtbl->Release(wrap);
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMMESH mesh = NULL;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMFRAME axis = NULL;
    LPDIRECT3DRMFRAME orbit = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    HRESULT rval;

    dev;
    view;
    camera;

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.2),
                                  D3DVAL(0.2), D3DVAL(0.2), &light1)))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(1),
                                  D3DVAL(1), D3DVAL(1), &light2)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, light1)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetOrientation(frame, scene, -D3DVAL(1), -D3DVAL(1),
                                  D3DVAL(1), D3DVAL(0), D3DVAL(1), D3DVAL(0))))
  goto generic_error;
    if (FAILED(frame->lpVtbl->AddLight(frame, light2)))
goto generic_error;
    RELEASE(frame);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &frame)))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetPosition(frame, scene, D3DVAL(0), D3DVAL(0), D3DVAL(15))))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetOrientation(frame, scene, D3DVAL(0), D3DVAL(1.0), D3DVAL(0),
    D3DVAL(0), D3DVAL(0), D3DVAL(1))))
goto generic_error;
    if (FAILED(frame->lpVtbl->SetRotation(frame, scene, D3DVAL(0), D3DVAL(0.9), D3DVAL(1.0),
                               D3DVAL(0.04))))
       goto generic_error;

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "lake.ppm", &tex);
    if (rval != D3DRM_OK) {
Msg("Failed to load lake.ppm.\n");
goto ret_with_error;
    }
    if (FAILED(tex->lpVtbl->SetColors(tex, 256)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetShades(tex, 1)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalScale(tex, TRUE)))
goto generic_error;
    if (FAILED(tex->lpVtbl->SetDecalOrigin(tex, 128, 128)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "torus.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load torus.x.\n");
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->SetTexture(builder, tex)))
goto generic_error;
    if (FAILED(builder->lpVtbl->SetQuality(builder, D3DRMRENDER_GOURAUD)))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &mesh)))
goto generic_error;
    RELEASE(builder);

    if (FAILED(mesh->lpVtbl->SetGroupColorRGB(mesh, 0, D3DVAL(0.7), D3DVAL(0.3), D3DVAL(0.3))))
goto generic_error;
    if (FAILED(mesh->lpVtbl->SetGroupColorRGB(mesh, 1, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))
goto generic_error;

    if (FAILED(frame->lpVtbl->AddVisual(frame, (LPDIRECT3DRMVISUAL) mesh)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap
(lpD3DRM, D3DRMWRAP_CHROME, camera,
 D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
 D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
 D3DVAL(0.0), D3DVAL(0.0), -D3DVAL(1.0),
 D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
 -D3DVAL(1.0), &wrap)))
 goto generic_error;

    if (FAILED(frame->lpVtbl->AddMoveCallback(frame, applyWrap, (void *) wrap)))
goto generic_error;
    if (FAILED(frame->lpVtbl->AddDestroyCallback(frame, cleanupWrap, wrap)))
goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, frame, &axis)))
goto generic_error;
    if (FAILED(axis->lpVtbl->SetRotation(axis, frame, D3DVAL(0), D3DVAL(1), D3DVAL(0), D3DVAL(0.04))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, axis, &orbit)))
goto generic_error;
    if (FAILED(orbit->lpVtbl->SetPosition(orbit, axis, D3DVAL(2.6), D3DVAL(0), D3DVAL(0))))
goto generic_error;
    if (FAILED(orbit->lpVtbl->AddVisual(orbit, (LPDIRECT3DRMVISUAL) tex)))
goto generic_error;
    //orbit->lpVtbl->AddMoveCallback(orbit, toggleDecalScale, (void*) tex);
    if (FAILED(scene->lpVtbl->SetSceneBackgroundImage(scene, tex)))
goto generic_error;

    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(orbit);
    RELEASE(tex);
    RELEASE(light1);
    RELEASE(light2);
    /* don't release the wrap */
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(mesh);
    RELEASE(frame);
    RELEASE(axis);
    RELEASE(orbit);
    RELEASE(tex);
    RELEASE(wrap);
    RELEASE(light1);
    RELEASE(light2);
    RELEASE(builder);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Texture Mapping V Direct3DRM Example");
}


README.TXT

Trans
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

Using transparent texture maps in various ways.


TRANS.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: trans.c
 *
 ***************************************************************************/

#include "rmdemo.h"

unsigned char check[] = {
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
    1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 1,
    2, 3, 3, 2, 1, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
    2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1,
};

D3DRMPALETTEENTRY checkPal[] = {
    {0, 0, 0, D3DRMPALETTE_READONLY},
    {127, 255, 127, D3DRMPALETTE_READONLY},
    {0, 0, 255, D3DRMPALETTE_READONLY},
    {255, 255, 127, D3DRMPALETTE_READONLY},
    {255, 255, 255, D3DRMPALETTE_READONLY},
};

D3DRMIMAGE checkImage = {
    32, 32,
    1, 1,
    8, FALSE,
    32,
    check, NULL,
    0xff, 0xff, 0xff, 0xff,
    5, checkPal,
};

static void CDECL mutateTexture(LPDIRECT3DRMFRAME frame, void* arg, D3DVALUE delta)
{
    LPDIRECT3DRMTEXTURE tex = (LPDIRECT3DRMTEXTURE) arg;
    static int col = -1;
    static int delay = 10;
    static int count = 0;
    int i;

    if (--delay)
return;
    delay = 10;

    if (col >= 0)
for (i = 0; i < sizeof(check); i++)
    if (check[i] == 4)
check[i] = col;

    count++;
    col = count & 3;

    for (i = 0; i < sizeof(check); i++)
if (check[i] == col)
    check[i] = 4;

    tex->lpVtbl->Changed(tex, TRUE, FALSE);
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    LPDIRECT3DRMFRAME box1 = NULL;
    LPDIRECT3DRMFRAME box2 = NULL;
    LPDIRECT3DRMFRAME sphere = NULL;
    LPDIRECT3DRMFRAME decal = NULL;
    LPDIRECT3DRMFRAME light = NULL;
    LPDIRECT3DRMMESH obj = NULL;
    LPDIRECT3DRMLIGHT dlight = NULL;
    LPDIRECT3DRMLIGHT alight = NULL;
    LPDIRECT3DRMTEXTURE tex = NULL;
    LPDIRECT3DRMMESHBUILDER builder = NULL;
    LPDIRECT3DRMWRAP wrap = NULL;
    LPDIRECT3DRMTEXTURE sphereTex = NULL;
    LPDIRECT3DRMTEXTURE decalTex = NULL;
    LPDIRECT3DRMMATERIAL mat = NULL;
    D3DRMBOX box;
    D3DVALUE miny, maxy;
    D3DVALUE height;
    HRESULT rval;

    view = view;
    dev = dev;
    camera = camera;/* unused */

    if (FAILED(scene->lpVtbl->SetSceneBackground(scene, D3DRGB(D3DVAL(0.2), D3DVAL(0.2),
                                      D3DVAL(0.2)))))
      goto generic_error;

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &light)))
goto generic_error;
    if (FAILED(light->lpVtbl->SetPosition(light, scene, D3DVAL(2.0), D3DVAL(3.0),
                               D3DVAL(8.0))))
       goto generic_error;
    if (FAILED(light->lpVtbl->SetOrientation(light, scene, D3DVAL(-1.0), D3DVAL(-1.0),
                                  D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0))))
  goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(1),
                                  D3DVAL(1), D3DVAL(1), &dlight)))
  goto generic_error;
    if (FAILED(light->lpVtbl->AddLight(light, dlight)))
goto generic_error;
    RELEASE(dlight);
    RELEASE(light);

    if (FAILED(lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
                                  D3DVAL(0.1), D3DVAL(0.1), &alight)))
  goto generic_error;
    if (FAILED(scene->lpVtbl->AddLight(scene, alight)))
goto generic_error;
    RELEASE(alight);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &box1)))
goto generic_error;
    if (FAILED(box1->lpVtbl->SetRotation(box1, scene, D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
      D3DVAL(-0.02))))
      goto generic_error;
    if (FAILED(box1->lpVtbl->SetPosition(box1, scene, D3DVAL(2.0), D3DVAL(0.0), D3DVAL(7.0))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "ncube.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load ncube.x.\n");
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->SetPerspective(builder, TRUE)))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &obj)))
goto generic_error;
    RELEASE(builder);
    if (FAILED(lpD3DRM->lpVtbl->CreateTexture(lpD3DRM, &checkImage, &tex)))
goto generic_error;
    if (FAILED(obj->lpVtbl->SetGroupTexture(obj, D3DRMGROUP_ALLGROUPS, tex)))
goto generic_error;
    if (FAILED(obj->lpVtbl->SetGroupMapping(obj, D3DRMGROUP_ALLGROUPS, D3DRMMAP_PERSPCORRECT)))
goto generic_error;
    if (FAILED(box1->lpVtbl->AddVisual(box1, (LPDIRECT3DRMVISUAL) obj)))
goto generic_error;
    if (FAILED(box1->lpVtbl->AddMoveCallback(box1, mutateTexture, tex)))
goto generic_error;
    RELEASE(obj);
    RELEASE(tex);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &box2)))
goto generic_error;
    if (FAILED(box2->lpVtbl->SetRotation(box2, scene, D3DVAL(0.1), D3DVAL(1.0), D3DVAL(0.0),
                              D3DVAL(0.1))))
      goto generic_error;
    if (FAILED(box2->lpVtbl->SetPosition(box2, box1, D3DVAL(-4.0), D3DVAL(0.0), D3DVAL(0.0))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "ncube.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load ncube.x.\n");
goto ret_with_error;
    }
    //builder->lpVtbl->Scale(builder, D3DVAL(2.0), D3DVAL(2.0), D3DVAL(2.0));
    if (FAILED(builder->lpVtbl->SetPerspective(builder, TRUE)))
goto generic_error;
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &obj)))
goto generic_error;
    RELEASE(builder);
    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "checker.ppm", &tex);
    if (rval != D3DRM_OK) {
Msg("Failed to load checker.ppm.\n");
goto ret_with_error;
    }
    if (FAILED(tex->lpVtbl->SetDecalTransparency(tex, TRUE)))
goto generic_error;

    if (FAILED(obj->lpVtbl->SetGroupTexture(obj, D3DRMGROUP_ALLGROUPS, tex)))
goto generic_error;
    if (FAILED(obj->lpVtbl->SetGroupMapping(obj, D3DRMGROUP_ALLGROUPS, D3DRMMAP_PERSPCORRECT)))
goto generic_error;
    if (FAILED(box2->lpVtbl->AddVisual(box2, (LPDIRECT3DRMVISUAL) obj)))
goto generic_error;
    RELEASE(obj);
    RELEASE(tex);
    RELEASE(box2);

    if (FAILED(lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, &builder)))
goto generic_error;
    rval = builder->lpVtbl->Load(builder, "sphere3.x", NULL,
    D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
        Msg("Failed to load sphere3.x.\n");
goto ret_with_error;
    }
    if (FAILED(builder->lpVtbl->CreateMesh(builder, &obj)))
goto generic_error;
    RELEASE(builder);
    if (FAILED(obj->lpVtbl->GetBox(obj, &box)))
goto generic_error;
    maxy = box.max.y;
    miny = box.min.y;
    height = maxy - miny;

    if (FAILED(lpD3DRM->lpVtbl->CreateWrap(lpD3DRM, D3DRMWRAP_CYLINDER, NULL,
      D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
      D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
      D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0),
      D3DVAL(0.0), D3DDivide(miny, height),
              D3DVAL(1.0), D3DDivide(D3DVAL(-1.0),height),
              &wrap)))
      goto generic_error;
    if (FAILED(wrap->lpVtbl->Apply(wrap, (LPDIRECT3DRMOBJECT) obj)))
goto generic_error;
    RELEASE(wrap);

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tex3.ppm", &sphereTex);
    if (rval != D3DRM_OK) {
Msg("Failed to load tex3.ppm.\n");
goto ret_with_error;
    }
    if (FAILED(sphereTex->lpVtbl->SetColors(sphereTex, 16)))
goto generic_error;
    if (FAILED(obj->lpVtbl->SetGroupTexture(obj, D3DRMGROUP_ALLGROUPS, sphereTex)))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateMaterial(lpD3DRM, D3DVAL(16.0), &mat)))
goto generic_error;
    if (FAILED(obj->lpVtbl->SetGroupMaterial(obj, D3DRMGROUP_ALLGROUPS, mat)))
goto generic_error;
    RELEASE(sphereTex);
    RELEASE(mat);

    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, scene, &sphere)))
goto generic_error;
    if (FAILED(sphere->lpVtbl->AddVisual(sphere, (LPDIRECT3DRMVISUAL) obj)))
goto generic_error;
    RELEASE(obj);
    if (FAILED(sphere->lpVtbl->SetPosition(sphere, scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(8.0))))
goto generic_error;
    if (FAILED(sphere->lpVtbl->SetRotation(sphere, scene, D3DVAL(-0.1), D3DVAL(1.0),
                        D3DVAL(0.0), D3DVAL(0.02))))
goto generic_error;
    if (FAILED(lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, sphere, &decal)))
goto generic_error;

    rval = lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "checker.ppm", &decalTex);
    if (rval != D3DRM_OK) {
Msg("Failed to load checker.ppm.\n");
goto ret_with_error;
    }
    if (FAILED(decalTex->lpVtbl->SetDecalScale(decalTex, TRUE)))
goto generic_error;
    if (FAILED(decalTex->lpVtbl->SetDecalSize(decalTex, D3DVAL(2.0), D3DVAL(2.0))))
goto generic_error;
    if (FAILED(decalTex->lpVtbl->SetDecalOrigin(decalTex, 4, 4)))
goto generic_error;
    if (FAILED(decalTex->lpVtbl->SetDecalTransparency(decalTex, TRUE)))
goto generic_error;
    if (FAILED(decal->lpVtbl->SetPosition(decal, sphere, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(2.0))))
goto generic_error;
    if (FAILED(decal->lpVtbl->AddVisual(decal, (LPDIRECT3DRMVISUAL) decalTex)))
goto generic_error;
    RELEASE(decalTex);

    RELEASE(box1);
    RELEASE(decal);
    RELEASE(sphere);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
ret_with_error:
    RELEASE(box1);
    RELEASE(box2);
    RELEASE(sphere);
    RELEASE(decal);
    RELEASE(light);
    RELEASE(obj);
    RELEASE(dlight);
    RELEASE(alight);
    RELEASE(tex);
    RELEASE(builder);
    RELEASE(wrap);
    RELEASE(sphereTex);
    RELEASE(decalTex);
    RELEASE(mat);
    return FALSE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->bConstRenderQuality = TRUE;
    lstrcpy(defaults->Name, "Transparency Direct3DRM Example");
}


D3DAPP.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dapp.c
 *
 *  Top level D3DApp functions and internal global variables.  See
 *  d3dapp.h for more information.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"

#define INITGUID

/***************************************************************************/
/*                           GLOBAL VARIABLES                              */
/***************************************************************************/
/*
 * All DD and D3D objects which are also available to the application
 * See d3dapp.h for typedef
 */
D3DAppInfo d3dappi;
/*
 * Internal record of the render state.  See d3dapp.h for typedef
 */
D3DAppRenderState d3dapprs;
/*
 * Callback functions for D3D device creation and destruction
 */
BOOL(*D3DDeviceDestroyCallback)(LPVOID);
LPVOID D3DDeviceDestroyCallbackContext;
BOOL(*D3DDeviceCreateCallback)(int, int, LPDIRECT3DVIEWPORT2*, LPVOID);
LPVOID D3DDeviceCreateCallbackContext;
/*
 * The last error code and string
 */
HRESULT LastError;
char LastErrorString[256];
/*
 * List of dirty rectangles on back buffer and client area
 */
int NumDirtyClientRects, NumDirtyBackRects, NumDirtyZRects;
D3DRECT DirtyClient[D3DAPP_MAXCLEARRECTS];
D3DRECT DirtyBack[D3DAPP_MAXCLEARRECTS];
D3DRECT DirtyZ[D3DAPP_MAXCLEARRECTS];
/*
 * List of texture handles which is copied to D3DAppInfo structure when
 * necessary
 */
D3DTEXTUREHANDLE MasterTextureHandle[D3DAPP_MAXTEXTURES];

LPDIRECTDRAWCLIPPER lpClipper; /* Clipper in windowed case */
LPDIRECTDRAWPALETTE lpPalette; /* Front buffer's palette */
PALETTEENTRY ppe[256];         /* Current palette entries */
PALETTEENTRY Originalppe[256]; /* Windows palette entries at startup */
BOOL bD3DAppInitialized;       /* Is D3DApp initialized? */
BOOL bPrimaryPalettized;       /* Is the front buffer palettized? */
BOOL bPaletteActivate;       /* Is the front buffer's palette valid? */
BOOL bIgnoreWM_SIZE;       /* Ignore this WM_SIZE messages */
SIZE szLastClient;       /* Dimensions of the last window */
SIZE szBuffers;       /* Current buffer dimensions, not necessarily
  the same as the client window */
int CallbackRefCount;       /* How many times DeviceCreateCallback has
  been called in a row */

/***************************************************************************/
/*                               FUNCTIONS                                 */
/***************************************************************************/
/*
 * D3DAppCreateFromHWND
 */
BOOL D3DAppCreateFromHWND(DWORD flags, HWND hwnd,
  BOOL(*DeviceCreateCallback)(int, int,
      LPDIRECT3DVIEWPORT2*,
      LPVOID),
  LPVOID lpCreateContext,
  BOOL(*DeviceDestroyCallback)(LPVOID),
  LPVOID lpDestroyContext,
  D3DAppInfo** D3DApp)
{
    int driver, mode, w, h;
    /*
     * Clean the global varaibles and check the flags
     */
    D3DAppISetDefaults();
    if (flags & D3DAPP_ONLYSYSTEMMEMORY) {
d3dappi.bOnlySystemMemory = TRUE;
d3dappi.bOnlyEmulation = TRUE;
    }
    if (flags & D3DAPP_ONLYD3DEMULATION)
d3dappi.bOnlyEmulation = TRUE;
    /*
     * Create DirectDraw, remember the Windows display mode and enumerate the
     * display modes
     */
    ATTEMPT(D3DAppICreateDD(d3dappi.bOnlyEmulation ?
    D3DAPP_ONLYDDEMULATION : 0L));
    ATTEMPT(D3DAppIRememberWindowsMode());
    ATTEMPT(D3DAppIEnumDisplayModes());
    /*
     * Create Direct3D and enumerate the D3D drivers
     */
    ATTEMPT(D3DAppICreateD3D());
    ATTEMPT(D3DAppIEnumDevices());
    /*
     * Set the device creation and destroy callback functions
     */
    D3DDeviceDestroyCallback = DeviceDestroyCallback;
    D3DDeviceDestroyCallbackContext = lpDestroyContext;
    D3DDeviceCreateCallback = DeviceCreateCallback;
    D3DDeviceCreateCallbackContext = lpCreateContext;
    *D3DApp = &d3dappi;
    d3dappi.hwnd = hwnd;
    /*
     * Choose a driver and display mode.  Using the current window is
     * prefered, but a fullscreen mode may be selected.  Set the cooperative
     * level and create the front and back buffers for this mode.
     */
    driver = D3DAPP_YOUDECIDE;
    mode = D3DAPP_YOUDECIDE;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    D3DAppIGetClientWin(hwnd);
    if (mode == D3DAPP_USEWINDOW) {
w = d3dappi.szClient.cx;
h = d3dappi.szClient.cy;
ATTEMPT(D3DAppISetCoopLevel(hwnd, FALSE));
ATTEMPT(D3DAppICreateBuffers(hwnd, w, h, D3DAPP_BOGUS, FALSE,
     d3dappi.Driver[driver].bIsHardware));
/*
 * Change the currently selected mode if it's not compatible with
 * this driver.  Just to make sure that CurrMode is always a mode the
 * current driver can do.
 */
if (!(d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth &
      D3DAppIBPPToDDBD(d3dappi.Mode[d3dappi.CurrMode].bpp))){
    ATTEMPT(D3DAppIPickDisplayMode(&d3dappi.CurrMode,
        d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth));
}
    } else {
szLastClient = d3dappi.szClient;
w = d3dappi.Mode[mode].w;
h = d3dappi.Mode[mode].h;
d3dappi.szClient.cx = w; d3dappi.szClient.cy = h;
ATTEMPT(D3DAppISetCoopLevel(hwnd, TRUE));
ATTEMPT(D3DAppISetDisplayMode(w, h, d3dappi.Mode[mode].bpp));
d3dappi.CurrMode = mode;
ATTEMPT(D3DAppICreateBuffers(hwnd, w, h, d3dappi.Mode[mode].bpp, TRUE,
     d3dappi.Driver[driver].bIsHardware));
    }
    /*
     * If the front buffer is palettized, initialize its palette
     */
    ATTEMPT(D3DAppICheckForPalettized());
    /*
     * Create the Z-buffer
     */
    ATTEMPT(D3DAppICreateZBuffer(w, h, driver));
    /*
     * Create the D3D device, load the textures, call the device create
     * callback and set a default render state
     */
    ATTEMPT(D3DAppICreateDevice(driver));
    ATTEMPT(D3DAppILoadAllTextures());
    ATTEMPT(D3DAppIFilterDisplayModes(driver));  /* bThisDriverCanDo flags */
    ATTEMPT(D3DAppICallDeviceCreateCallback(w, h));
    ATTEMPT(D3DAppISetRenderState());
    /*
     * Initialize dirty rectangle information
     */
    D3DAppIValidateDirtyRects();
    /*
     * Ready to render
     */
    bD3DAppInitialized = TRUE;
    d3dappi.bRenderingIsOK = TRUE;
    return TRUE;

exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    if (d3dappi.bFullscreen) {
D3DAppIRestoreDispMode();
D3DAppISetCoopLevel(hwnd, FALSE);
    }
    RELEASE(d3dappi.lpD3D);
    RELEASE(d3dappi.lpDD);
    return FALSE;
}

/*
 * D3DAppFullscreen
 */
BOOL D3DAppFullscreen(int mode)
{
    int w, h, bpp;
    BOOL b; /* was already in a fullscreen mode? */

    d3dappi.bRenderingIsOK = FALSE;
    /*
     * Make sure this is a valid request, otherwise doctor mode so it will
     * work with this driver.
     */
    ATTEMPT(D3DAppIVerifyDriverAndMode(&d3dappi.CurrDriver, &mode));
    /*
     * Release everything
     */
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    if (d3dappi.bFullscreen) {
ATTEMPT(D3DAppIClearBuffers());
    }
    D3DAppIReleaseAllTextures();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    /*
     * Record information about the current status
     */
    b = d3dappi.bFullscreen;
    w = d3dappi.Mode[mode].w;
    h = d3dappi.Mode[mode].h;
    bpp = d3dappi.Mode[mode].bpp;
    if (!b) {
/*
 * If this is not a fullscreen mode, we'll need to record the window
 * size for when we return to it.
 */
szLastClient = d3dappi.szClient;
    }
    /*
     * Set the cooperative level and create front and back buffers
     */
    d3dappi.szClient.cx = w; d3dappi.szClient.cy = h;
    ATTEMPT(D3DAppISetCoopLevel(d3dappi.hwnd, TRUE));
    ATTEMPT(D3DAppISetDisplayMode(w, h, bpp));
    d3dappi.CurrMode = mode;
    ATTEMPT(D3DAppICreateBuffers(d3dappi.hwnd, w, h, bpp, TRUE,
 d3dappi.Driver[d3dappi.CurrDriver].bIsHardware));
    /*
     * If the front buffer is palettized, initialize its palette
     */
    ATTEMPT(D3DAppICheckForPalettized());
    /*
     * Create the Z-buffer
     */
    ATTEMPT(D3DAppICreateZBuffer(w, h, d3dappi.CurrDriver));
    /*
     * Create the D3D device, load the textures, call the device create
     * callback and set a default render state
     */
    ATTEMPT(D3DAppICreateDevice(d3dappi.CurrDriver));
    ATTEMPT(D3DAppILoadAllTextures());
    ATTEMPT(D3DAppICallDeviceCreateCallback(w, h));
    ATTEMPT(D3DAppISetRenderState());
    /*
     * Set current mode and clear dirty rectangle information
     */
    d3dappi.CurrMode = mode;
    D3DAppIValidateDirtyRects();
    d3dappi.bRenderingIsOK = TRUE;
    return TRUE;

exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    if (!b) {
D3DAppIRestoreDispMode();
D3DAppISetCoopLevel(d3dappi.hwnd, FALSE);
    }
    return FALSE;
}

/*
 * D3DAppWindow
 */
BOOL
D3DAppWindow(int w, int h)
{
    BOOL b; /* changing from a fullscreen mode? */

    if (!d3dappi.bIsPrimary) {
D3DAppISetErrorString("It is not possible to create a D3D window with a hardware DirectDraw device.  Check the bIsPrimary flag before calling D3DAppWindow.");
return FALSE;
    }
    b = d3dappi.bFullscreen;
    /*
     * If asked to set the window size, return it to the last value or use
     * a default value.
     */
    if (w == D3DAPP_YOUDECIDE) {
w = b ? szLastClient.cx : D3DAPP_DEFAULTWINDOWDIM;
    }
    if (h == D3DAPP_YOUDECIDE) {
h = b ? szLastClient.cy : D3DAPP_DEFAULTWINDOWDIM;
    }
    /*
     * Release everything
     */
    d3dappi.bRenderingIsOK = FALSE;
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    if (b) {
ATTEMPT(D3DAppIClearBuffers());
    }
    D3DAppIReleaseAllTextures();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    /*
     * Restore the display mode if we were in a fullscreen mode
     */
    if (b) {
D3DAppIRestoreDispMode();
    }
    /*
     * Set the cooperative level and create front and back buffers
     */
    D3DAppISetCoopLevel(d3dappi.hwnd, FALSE);
    D3DAppISetClientSize(d3dappi.hwnd, w, h, b);
    ATTEMPT(D3DAppICreateBuffers(d3dappi.hwnd, w, h, D3DAPP_BOGUS, FALSE,
 d3dappi.Driver[d3dappi.CurrDriver].bIsHardware));
    /*
     * If the front buffer is palettized, initialize its palette
     */
    ATTEMPT(D3DAppICheckForPalettized());
    /*
     * Create the Z-buffer
     */
    ATTEMPT(D3DAppICreateZBuffer(szBuffers.cx, szBuffers.cy,
 d3dappi.CurrDriver));
    /*
     * Create the D3D device, load the textures, call the device create
     * callback and set a default render state
     */
    ATTEMPT(D3DAppICreateDevice(d3dappi.CurrDriver));
    ATTEMPT(D3DAppILoadAllTextures());
    ATTEMPT(D3DAppICallDeviceCreateCallback(szBuffers.cx, szBuffers.cy));
    ATTEMPT(D3DAppISetRenderState());
    /*
     * Clear dirty rectangle information
     */
    D3DAppIValidateDirtyRects();
    d3dappi.bRenderingIsOK = TRUE;
    return TRUE;

exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    return FALSE;
}

/*
 * D3DAppChangeDriver
 */
BOOL
D3DAppChangeDriver(int driver, DWORD flags)
{
    int mode;

    /*
     * Verify the compatibility of this mode with the specified driver.
     * The mode may change.
     */
    if (d3dappi.bFullscreen)
mode = d3dappi.CurrMode;
    else
mode = D3DAPP_USEWINDOW;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    if (driver == D3DAPP_BOGUS || mode == D3DAPP_BOGUS)
goto exit_with_error;
    /*
     * Update the current driver and set bThisDriverCanDo flags
     */
    d3dappi.CurrDriver = driver;
    ATTEMPT(D3DAppIFilterDisplayModes(driver));
    /*
     * Either call D3DAppWindow or D3DAppFullscreen depending on mode
     */
    if (mode == D3DAPP_USEWINDOW) {
if (d3dappi.bFullscreen) {
    /*
     * We need to switch to a window.  D3DApp will either use the
     * size of the last window it saw or use a default size.
     */
    ATTEMPT(D3DAppWindow(D3DAPP_YOUDECIDE, D3DAPP_YOUDECIDE));
} else {
    /*
     * We need to recreate the current window.  Don't let D3DApp
     * decide on the size.
     */
    ATTEMPT(D3DAppWindow(d3dappi.szClient.cx, d3dappi.szClient.cy));
}
/*
 * Change the currently selected mode if it's not compatible with
 * this driver.  Just to make sure that CurrMode is always a mode the
 * current driver can do.
 */
if (!(d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth &
      D3DAppIBPPToDDBD(d3dappi.Mode[d3dappi.CurrMode].bpp))){
    ATTEMPT(D3DAppIPickDisplayMode(&d3dappi.CurrMode,
        d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth));
}
return TRUE;
    } else {
/*
 * We need to switch to fullscreen or switch fullscreen modes or stay
 * in the same fullscreen mode.  In any of these cases, we call the
 * same function.
 */
ATTEMPT(D3DAppFullscreen(mode));
return TRUE;
    }

exit_with_error:
    /*
     * The failed mode setting call would have released everything
     */
    return FALSE;
}

/*
 * D3DAppWindowProc
 */
BOOL
D3DAppWindowProc(BOOL* bStopProcessing, LRESULT* lresult, HWND hwnd,
 UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    *bStopProcessing = FALSE;
    if (!bD3DAppInitialized)
return TRUE;
    /*
     * Look for messages which effect rendering.  In some cases, we will not
     * want the app to continue processing the message, so set the flag and
     * provide a return value in lresult.
     */
    switch(message) {
case WM_SIZE:
    if (!bIgnoreWM_SIZE) {
/*
 * Too long to fit here, see ddcalls.c. Updates the buffers
 * and re-creates the device.
 */
ATTEMPT(D3DAppIHandleWM_SIZE(lresult, d3dappi.hwnd, message,
     wParam, lParam));
*bStopProcessing = TRUE;
    }
    break;
case WM_MOVE:
    /*
     * Update client window position information
     */
    d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
    ClientToScreen(hwnd, &d3dappi.pClientOnPrimary);
    break;
case WM_ACTIVATE:
    /*
     * Set the front buffer's palette
     */
    if (bPaletteActivate && bPrimaryPalettized &&
d3dappi.lpFrontBuffer) {
d3dappi.lpFrontBuffer->lpVtbl->SetPalette(d3dappi.lpFrontBuffer,
  lpPalette);
    }
    break;
        case WM_ACTIVATEAPP:
            d3dappi.bAppActive = (BOOL)wParam;
            break;
        case WM_SETCURSOR:
    /*
     * Prevent the cursor from being shown in fullscreen
     */
            if (d3dappi.bFullscreen && !d3dappi.bPaused) {
                SetCursor(NULL);
                *lresult = 1;
*bStopProcessing = TRUE;
return TRUE;
            }
            break;
case WM_MOVING:
    /*
     * Prevent the window from moving in fullscreen
     */
    if (d3dappi.bFullscreen) {
GetWindowRect(hwnd, (LPRECT)lParam);
                *lresult = 1;
*bStopProcessing = TRUE;
return TRUE;
    }
    break;
case WM_GETMINMAXINFO:
    /*
     * Ensure the window won't resize in fullscreen
     */
    if (d3dappi.bFullscreen) {
((LPMINMAXINFO)lParam)->ptMaxTrackSize.x= d3dappi.ThisMode.w;
((LPMINMAXINFO)lParam)->ptMaxTrackSize.y= d3dappi.ThisMode.h;
((LPMINMAXINFO)lParam)->ptMinTrackSize.x= d3dappi.ThisMode.w;
((LPMINMAXINFO)lParam)->ptMinTrackSize.y= d3dappi.ThisMode.h;
                *lresult = 0;
*bStopProcessing = TRUE;
return TRUE;
    } else {
((LPMINMAXINFO)lParam)->ptMaxTrackSize.x =
    d3dappi.WindowsDisplay.w;
((LPMINMAXINFO)lParam)->ptMaxTrackSize.y =
    d3dappi.WindowsDisplay.h;
                *lresult = 0;
*bStopProcessing = TRUE;
return TRUE;
    }
    break;
case WM_PAINT:
    /*
     * Clear the rectangle and blt the backbuffer image
     */
    BeginPaint(hwnd, &ps);
    if (d3dappi.bRenderingIsOK && !d3dappi.bFullscreen) {
D3DAppShowBackBuffer(D3DAPP_SHOWALL);
    }
    D3DAppIValidateDirtyRects();
    EndPaint(hwnd, &ps);
    *lresult = 1;
    *bStopProcessing = TRUE;
    return TRUE;
case WM_NCPAINT:
    /*
     * When in fullscreen mode, don't draw the window frame.
     */
    if (d3dappi.bFullscreen && !d3dappi.bPaused) {
*lresult = 0;
*bStopProcessing = TRUE;
return TRUE;
    }
    break;
    }
    return TRUE;

exit_with_error:
    return FALSE;
}

/*
 * D3DAppAddTexture
 */
BOOL
D3DAppAddTexture(const char* imagefile)
{
    if (d3dappi.NumTextures == D3DAPP_MAXTEXTURES - 1) {
D3DAppISetErrorString("Can only load %i textures.", D3DAPP_MAXTEXTURES);
return FALSE;
    }
    lstrcpy(d3dappi.ImageFile[d3dappi.NumTextures], imagefile);
    /*
     * If this driver does texture mapping, load the texture.
     * This test also tests that a device has been created.
     */
    if (d3dappi.ThisDriver.bDoesTextures && d3dappi.NumUsableTextures == d3dappi.NumTextures) {
BOOL bInVideo;
ATTEMPT(D3DAppILoadTextureSurf(d3dappi.NumTextures, &bInVideo));
if (!bInVideo && d3dappi.ThisDriver.bIsHardware) {
    D3DAppIReleaseTexture(d3dappi.NumTextures);
} else {
    ATTEMPT(D3DAppIGetTextureHandle(d3dappi.NumTextures));
    ++d3dappi.NumUsableTextures;
}
    }
    d3dappi.NumTextures++;
    return TRUE;

exit_with_error:
    d3dappi.ImageFile[d3dappi.NumTextures][0] = 0;
    return FALSE;
}

/*
 * D3DAppChangeTextureFormat
 */
BOOL
D3DAppChangeTextureFormat(int format)
{
    /*
     * Release all the textures, change the format and load them again
     */
    d3dappi.bRenderingIsOK = FALSE;
    D3DAppIReleaseAllTextures();
    d3dappi.CurrTextureFormat = format;
    memcpy(&d3dappi.ThisTextureFormat, &d3dappi.TextureFormat[format],
   sizeof(D3DAppTextureFormat));
    ATTEMPT(D3DAppILoadAllTextures());
    d3dappi.bRenderingIsOK = TRUE;
    return TRUE;

exit_with_error:
    D3DAppIReleaseAllTextures();
    return FALSE;
}

/*
 * D3DAppDisableTextures
 */
BOOL
D3DAppDisableTextures(BOOL flag)
{
    int i;
    if (flag == d3dappi.bTexturesDisabled)
return TRUE;
    if (flag) {
/*
 * Set all the texture handles to 0
 */
d3dappi.bTexturesDisabled = TRUE;
for (i = 0; i < d3dappi.NumTextures; i++)
    d3dappi.TextureHandle[i] = 0;
    } else {
/*
 * Restore the texture handles from the master array
 */
d3dappi.bTexturesDisabled = FALSE;
memcpy(d3dappi.TextureHandle, MasterTextureHandle,
       sizeof(D3DTEXTUREHANDLE) * D3DAPP_MAXTEXTURES);
    }
    return TRUE;
}

/*
 * D3DAppSwapTextures
 */
BOOL
D3DAppSwapTextures()
{
    int i;
    char tempfile[30];
    LPDIRECT3DTEXTURE2 lptempTexture;
    LPDIRECTDRAWSURFACE lptempSurface;
  if (d3dappi.bTexturesDisabled || d3dappi.NumTextures == 0) {
D3DAppISetErrorString("Cannot swap textures which are disable or not loaded.\n");
goto exit_with_error;
    }
    if (!d3dappi.ThisDriver.bDoesTextures)
return TRUE;
    /*
     * Swap texture 1 with 2, then 2 with 3, then 3 with 4, etc.
     * Don't forget the image file names, texture objects and surfaces
     */
    for (i = 0; i < d3dappi.NumUsableTextures - 1; i++) {
lstrcpy(tempfile, d3dappi.ImageFile[i]);
lstrcpy(d3dappi.ImageFile[i], d3dappi.ImageFile[i+1]);
lstrcpy(d3dappi.ImageFile[i+1], tempfile);
d3dappi.lpD3DDevice->lpVtbl->SwapTextureHandles(d3dappi.lpD3DDevice,
      d3dappi.lpTexture[i],
      d3dappi.lpTexture[i+1]);
lptempTexture = d3dappi.lpTexture[i];
d3dappi.lpTexture[i] = d3dappi.lpTexture[i+1];
d3dappi.lpTexture[i+1] = lptempTexture;
lptempSurface = d3dappi.lpTextureSurf[i];
d3dappi.lpTextureSurf[i] = d3dappi.lpTextureSurf[i+1];
d3dappi.lpTextureSurf[i+1] = lptempSurface;
    }
    return TRUE;
exit_with_error:
    return FALSE;
}

/*
 * D3DAppSetRenderState
 */
BOOL
D3DAppSetRenderState(D3DAppRenderState* lpState)
{
    /*
     * If none was provided, reset the current render state.
     */
    if (!lpState)
lpState = &d3dapprs;
    /*
     * Record this render state and set it.
     */
    if (lpState != &d3dapprs)
memcpy(&d3dapprs, lpState, sizeof(D3DAppRenderState));
    if (d3dappi.bRenderingIsOK) {
ATTEMPT(D3DAppISetRenderState());
    }
    return TRUE;

exit_with_error:
    return FALSE;
}

/*
 * D3DAppGetRenderState
 */
BOOL
D3DAppGetRenderState(D3DAppRenderState* lpState)
{
    memcpy(lpState, &d3dapprs, sizeof(D3DAppRenderState));
    return TRUE;
}

/*
 * D3DAppShowBackBuffer
 */
BOOL
D3DAppShowBackBuffer(DWORD flags)
{
    if (!d3dappi.bRenderingIsOK) {
D3DAppISetErrorString("Cannot call D3DAppShowBackBuffer while bRenderingIsOK is FALSE.\n");
return FALSE;
    }
    if (d3dappi.bPaused)
return TRUE;
    if (d3dappi.bFullscreen) {
int numtemp;
D3DRECT temp[D3DAPP_MAXCLEARRECTS];
/*
 * Flip the back and front buffers
 */
        LastError = d3dappi.lpFrontBuffer->lpVtbl->Flip(d3dappi.lpFrontBuffer,
d3dappi.lpBackBuffer,
1);
if (LastError == DDERR_SURFACELOST) {
    d3dappi.lpFrontBuffer->lpVtbl->Restore(d3dappi.lpFrontBuffer);
    d3dappi.lpBackBuffer->lpVtbl->Restore(d3dappi.lpBackBuffer);
    D3DAppIClearBuffers();
} else if (LastError != DD_OK) {
            D3DAppISetErrorString("Flipping complex display surface failed.\n%s", D3DAppErrorToString(LastError));
            return FALSE;
        }
if (d3dappi.bBackBufferInVideo) {
    /*
     * This is a real flip, so the client and back buffer dirty
     * rectangles also flip
     */
    D3DAppICopyRectList(&numtemp, temp, NumDirtyClientRects,
DirtyClient);
    D3DAppICopyRectList(&NumDirtyClientRects, DirtyClient,
NumDirtyBackRects, DirtyBack);
    D3DAppICopyRectList(&NumDirtyBackRects, DirtyBack, numtemp, temp);
} else {
    /*
     * The flip is being emulated as a blt from a system memory back
     * buffer, so the back buffer's dirty rectangles are now also the
     * client's.
     */
    D3DAppICopyRectList(&NumDirtyClientRects, DirtyClient,
NumDirtyBackRects, DirtyBack);
}
    } else {
int NumFrontRects, NumBufferRects, i;
RECT front[D3DAPP_MAXCLEARRECTS];
RECT buffer[D3DAPP_MAXCLEARRECTS];
/*
 * Set the rectangle to blt from the back to front bufer
 */
if (flags & D3DAPP_SHOWALL) {
    /*
     * Set to entire client window
     */
    NumBufferRects = 1;
    SetRect(&buffer[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
    SetRect(&front[0],
    d3dappi.pClientOnPrimary.x, d3dappi.pClientOnPrimary.y,
    d3dappi.szClient.cx + d3dappi.pClientOnPrimary.x,
    d3dappi.szClient.cy + d3dappi.pClientOnPrimary.y);
} else {
    /*
     * Merge the back and front buffer dirty rectangle lists to get
     * a list of rectangles to blt.  This will simultaneously clear
     * the smallest front buffer region while blt'ing all the back
     * buffer which changed.
     */
    D3DAppIMergeRectLists(&NumBufferRects, (LPD3DRECT)buffer,
  NumDirtyClientRects, DirtyClient,
  NumDirtyBackRects, DirtyBack);
    D3DAppICopyRectList(&NumFrontRects, (LPD3DRECT)front,
NumBufferRects, (LPD3DRECT)buffer);
    for (i = 0; i < NumFrontRects; i++) {
front[i].top += d3dappi.pClientOnPrimary.y;
front[i].left += d3dappi.pClientOnPrimary.x;
front[i].bottom += d3dappi.pClientOnPrimary.y;
front[i].right += d3dappi.pClientOnPrimary.x;
    }
}
/*
 * Blt the list of rectangles from the back to front buffer
 */
for (i = 0; i < NumBufferRects; i++) {
    LastError =
    d3dappi.lpFrontBuffer->lpVtbl->Blt(d3dappi.lpFrontBuffer,
                             &front[i], d3dappi.lpBackBuffer,
     &buffer[i], DDBLT_WAIT, NULL);
    if (LastError == DDERR_SURFACELOST) {
d3dappi.lpFrontBuffer->lpVtbl->Restore(d3dappi.lpFrontBuffer);
d3dappi.lpBackBuffer->lpVtbl->Restore(d3dappi.lpBackBuffer);
D3DAppIClearBuffers();
    } else if (LastError != DD_OK) {
D3DAppISetErrorString("Blt of back buffer to front buffer failed.\n%s", D3DAppErrorToString(LastError));
return FALSE;
    }
}
/*
 * The back buffer's dirty rectangles are now also the client's
 */
D3DAppICopyRectList(&NumDirtyClientRects, DirtyClient,
    NumDirtyBackRects, DirtyBack);
    }
    return TRUE;
}

/*
 * D3DAppRenderExtents
 */
BOOL
D3DAppRenderExtents(DWORD dwCount, LPD3DRECT extent, DWORD flags)
{
    if (dwCount > D3DAPP_MAXCLEARRECTS) {
D3DAppISetErrorString("The number of clear rectangles exceeded D3DAPP_MAXCLEARRECTS.");
return FALSE;
    }
    if (flags & D3DAPP_CLEARALL) {
/*
 * Set the back buffer dirty rectangle to the entire client area
 */
D3DRECT dummy;
dummy.x1 = dummy.y1 = 0;
dummy.x2 = d3dappi.szClient.cx;
dummy.y2 = d3dappi.szClient.cy;
D3DAppICopyRectList(&NumDirtyBackRects, DirtyBack, 1, &dummy);
D3DAppICopyRectList(&NumDirtyClientRects, DirtyClient, 1, &dummy);
D3DAppICopyRectList(&NumDirtyZRects, DirtyZ, 1, &dummy);
    } else {
/*
 * Set the back and Z buffer dirty rectangle list as specified
 */
D3DAppICopyRectList(&NumDirtyBackRects, DirtyBack, dwCount, extent);
D3DAppICopyRectList(&NumDirtyZRects, DirtyZ, dwCount, extent);
    }
    return TRUE;
}

/*
 * D3DAppClearBackBuffer
 */
BOOL
D3DAppClearBackBuffer(DWORD flags)
{
    if (!d3dappi.bRenderingIsOK) {
D3DAppISetErrorString("Cannot call D3DAppClearBackBuffer while bRenderingIsOK is FALSE.\n");
return FALSE;
    }
    if (flags & D3DAPP_CLEARALL) {
/*
 * Clear the entire back buffer
 */
int clearflags;
D3DRECT dummy;
/*
 * Decided wether to clear just back buffer or also z-buffer
 */
clearflags = D3DCLEAR_TARGET;
if (d3dapprs.bZBufferOn)
    clearflags |= D3DCLEAR_ZBUFFER;
dummy.x1 = dummy.y1 = 0;
dummy.x2 = d3dappi.szClient.cx;
dummy.y2 = d3dappi.szClient.cy;
LastError =
          d3dappi.lpD3DViewport->lpVtbl->Clear(d3dappi.lpD3DViewport,
       1, &dummy,
       clearflags);
if (LastError != D3D_OK) {
    D3DAppISetErrorString("Viewport clear failed.\n%s",
                  D3DAppErrorToString(LastError));
    return FALSE;
}
    } else {
/*
 * Clear the dirty rectangles on the back buffer
 */
LastError =
          d3dappi.lpD3DViewport->lpVtbl->Clear(d3dappi.lpD3DViewport,
                                       NumDirtyBackRects,
      DirtyBack, D3DCLEAR_TARGET);
if (LastError != D3D_OK) {
    D3DAppISetErrorString("Viewport clear of back buffer failed.\n%s",
                  D3DAppErrorToString(LastError));
    return FALSE;
}
/*
 * Clear the dirty rectangles on the Z buffer
 */
LastError =
          d3dappi.lpD3DViewport->lpVtbl->Clear(d3dappi.lpD3DViewport,
                                       NumDirtyZRects,
      DirtyZ, D3DCLEAR_ZBUFFER);
if (LastError != D3D_OK) {
    D3DAppISetErrorString("Viewport clear of Z buffer failed.\n%s",
                  D3DAppErrorToString(LastError));
    return FALSE;
}

    }
    return TRUE;
}

/*
 * D3DAppCheckForLostSurfaces
 */
#define CHECKSURF(x) if (x) {    \
if (x->lpVtbl->IsLost(x) == DDERR_SURFACELOST) {    \
    LastError = x->lpVtbl->Restore(x);    \
    if (LastError != DD_OK) goto exit_with_error;   \
    b = TRUE;    \
}    \
     }
BOOL
D3DAppCheckForLostSurfaces(void)
{
    int i;
    BOOL b = FALSE;
    /*
     * Check all the surfaces D3DApp owns and restore them if lost.

*/
    CHECKSURF(d3dappi.lpFrontBuffer);
    CHECKSURF(d3dappi.lpBackBuffer);
    CHECKSURF(d3dappi.lpZBuffer);
    if (b) {
/*
 * If any of the surfaces were lost and restored, clear all the buffers.
 * If this fails, that's fine, just move on.
 */
D3DAppIClearBuffers();
    }
    for (i = 0; i < d3dappi.NumUsableTextures; i++) {
b = FALSE;
CHECKSURF(d3dappi.lpTextureSurf[i]);
if (b) {
    ATTEMPT(D3DAppIReloadTextureSurf(i));
}
    }
    return TRUE;

exit_with_error:
    D3DAppISetErrorString("Restoring of a lost surface failed.\n%s",
          D3DAppErrorToString(LastError));
    return FALSE;
}

/*
 * D3DAppPause
 */
BOOL
D3DAppPause(BOOL flag)
{
    static int pausecount;

    /*
     * Keep a count of the number of times D3DAppPause has been called to
     * prevent pausing more than once in a row.
     */
    if (pausecount != 0) {
if (flag) {
    ++pausecount;
    return TRUE;
} else {
    --pausecount;
    if (pausecount != 0)
return TRUE;
}
    }

    d3dappi.bPaused = flag;
    if (!flag) {
/*
 * Returning from a pause
 */
        if (d3dappi.bFullscreen && bPrimaryPalettized && lpPalette) {
    /*
     * Set front buffer's palette back to what it was before pause
     */
            LastError = lpPalette->lpVtbl->SetEntries(lpPalette, 0, 0, 256,
      &ppe[0]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Setting palette entries during unpause failed.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
        }
/*
 * Dirty rectangle info is no longer valid
 */
D3DAppIValidateDirtyRects();
    }
    if (flag && d3dappi.bFullscreen) {
/*
 * Pausing in a fullscreen mode
 */
        if (bPrimaryPalettized && lpPalette) {
    /*
     * Save the front buffer's current palette and restore the
     * original Windows palette.
     */
            int i;
            LastError = lpPalette->lpVtbl->GetEntries(lpPalette, 0, 0, 256,
      &ppe[0]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Getting palette entries before a pause failed.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
            for (i = 10; i < 246; i++)
                Originalppe[i] = ppe[i];
            LastError = lpPalette->lpVtbl->SetEntries(lpPalette, 0, 0, 256,
       &Originalppe[0]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Returning palette entries to defaults failed.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
        }
/*
 * Flip to GDI surface (either front or back buffer)
 */
        if (d3dappi.lpDD) {
            LastError = d3dappi.lpDD->lpVtbl->FlipToGDISurface(d3dappi.lpDD);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Flipping to GDI surface failed.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
}
/*
 * Draw the menu and frame
 */
        DrawMenuBar(d3dappi.hwnd);
        RedrawWindow(d3dappi.hwnd, NULL, NULL, RDW_FRAME);
    }
    return TRUE;
exit_with_error:
    return FALSE;
}

/*
 * D3DAppCreateSurface
 */
BOOL
D3DAppCreateSurface(DDSURFACEDESC *ddsd, LPDIRECTDRAWSURFACE *lplpSurf)
{
    return D3DAppICreateSurface(ddsd, lplpSurf);
}

/*
 * D3DAppLastError
 */
HRESULT
D3DAppLastError(void)
{
    return LastError;
}

/*
 * D3DAppLastD3DAppISetErrorString
 */
char*
D3DAppLastErrorString(void)
{
    return LastErrorString;
}

/*
 * D3DAppDestroy
 */
BOOL
D3DAppDestroy(void)
{
    /*
     * Destroys all objects including Direct Draw.
     */
    d3dappi.bRenderingIsOK = FALSE;
    d3dappi.hwnd = NULL;
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    D3DAppIReleaseAllTextures();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    if (d3dappi.bFullscreen) {
D3DAppIRestoreDispMode();
D3DAppISetCoopLevel(d3dappi.hwnd, FALSE);
    }
    D3DAppIReleasePathList();
    RELEASE(d3dappi.lpD3D);
    RELEASE(d3dappi.lpDD);
    return TRUE;
exit_with_error:
    return FALSE;
}

/*
 * D3DAppErrorToString
 */
char*
D3DAppErrorToString(HRESULT error)
{
    switch(error) {
        case DD_OK:
            return "No error.\0";
        case DDERR_ALREADYINITIALIZED:
            return "This object is already initialized.\0";
        case DDERR_BLTFASTCANTCLIP:
            return "Return if a clipper object is attached to the source surface passed into a BltFast call.\0";
        case DDERR_CANNOTATTACHSURFACE:
            return "This surface can not be attached to the requested surface.\0";
        case DDERR_CANNOTDETACHSURFACE:
            return "This surface can not be detached from the requested surface.\0";
        case DDERR_CANTCREATEDC:
            return "Windows can not create any more DCs.\0";
        case DDERR_CANTDUPLICATE:
            return "Can't duplicate primary & 3D surfaces, or surfaces that are implicitly created.\0";
        case DDERR_CLIPPERISUSINGHWND:
            return "An attempt was made to set a cliplist for a clipper object that is already monitoring an hwnd.\0";
        case DDERR_COLORKEYNOTSET:
            return "No src color key specified for this operation.\0";
        case DDERR_CURRENTLYNOTAVAIL:
            return "Support is currently not available.\0";
        case DDERR_DIRECTDRAWALREADYCREATED:
            return "A DirectDraw object representing this driver has already been created for this process.\0";
        case DDERR_EXCEPTION:
            return "An exception was encountered while performing the requested operation.\0";
        case DDERR_EXCLUSIVEMODEALREADYSET:
            return "An attempt was made to set the cooperative level when it was already set to exclusive.\0";
        case DDERR_GENERIC:
            return "Generic failure.\0";
        case DDERR_HEIGHTALIGN:
            return "Height of rectangle provided is not a multiple of reqd alignment.\0";
        case DDERR_HWNDALREADYSET:
            return "The CooperativeLevel HWND has already been set. It can not be reset while the process has surfaces or palettes created.\0";
        case DDERR_HWNDSUBCLASSED:
            return "HWND used by DirectDraw CooperativeLevel has been subclassed, this prevents DirectDraw from restoring state.\0";
        case DDERR_IMPLICITLYCREATED:
            return "This surface can not be restored because it is an implicitly created surface.\0";
        case DDERR_INCOMPATIBLEPRIMARY:
            return "Unable to match primary surface creation request with existing primary surface.\0";
        case DDERR_INVALIDCAPS:
            return "One or more of the caps bits passed to the callback are incorrect.\0";
        case DDERR_INVALIDCLIPLIST:
            return "DirectDraw does not support the provided cliplist.\0";
        case DDERR_INVALIDDIRECTDRAWGUID:
            return "The GUID passed to DirectDrawCreate is not a valid DirectDraw driver identifier.\0";
        case DDERR_INVALIDMODE:
            return "DirectDraw does not support the requested mode.\0";
        case DDERR_INVALIDOBJECT:
            return "DirectDraw received a pointer that was an invalid DIRECTDRAW object.\0";
        case DDERR_INVALIDPARAMS:
            return "One or more of the parameters passed to the function are incorrect.\0";
        case DDERR_INVALIDPIXELFORMAT:
            return "The pixel format was invalid as specified.\0";
        case DDERR_INVALIDPOSITION:
            return "Returned when the position of the overlay on the destination is no longer legal for that destination.\0";
        case DDERR_INVALIDRECT:
            return "Rectangle provided was invalid.\0";
        case DDERR_LOCKEDSURFACES:
            return "Operation could not be carried out because one or more surfaces are locked.\0";
        case DDERR_NO3D:
            return "There is no 3D present.\0";
        case DDERR_NOALPHAHW:
            return "Operation could not be carried out because there is no alpha accleration hardware present or available.\0";
        case DDERR_NOBLTHW:
            return "No blitter hardware present.\0";
        case DDERR_NOCLIPLIST:
            return "No cliplist available.\0";
        case DDERR_NOCLIPPERATTACHED:
            return "No clipper object attached to surface object.\0";
        case DDERR_NOCOLORCONVHW:
            return "Operation could not be carried out because there is no color conversion hardware present or available.\0";
        case DDERR_NOCOLORKEY:
            return "Surface doesn't currently have a color key\0";
        case DDERR_NOCOLORKEYHW:
            return "Operation could not be carried out because there is no hardware support of the destination color key.\0";
        case DDERR_NOCOOPERATIVELEVELSET:
            return "Create function called without DirectDraw object method SetCooperativeLevel being called.\0";
        case DDERR_NODC:
            return "No DC was ever created for this surface.\0";
        case DDERR_NODDROPSHW:
            return "No DirectDraw ROP hardware.\0";
        case DDERR_NODIRECTDRAWHW:
            return "A hardware-only DirectDraw object creation was attempted but the driver did not support any hardware.\0";
        case DDERR_NOEMULATION:
            return "Software emulation not available.\0";
        case DDERR_NOEXCLUSIVEMODE:
            return "Operation requires the application to have exclusive mode but the application does not have exclusive mode.\0";
        case DDERR_NOFLIPHW:
            return "Flipping visible surfaces is not supported.\0";
        case DDERR_NOGDI:
            return "There is no GDI present.\0";
        case DDERR_NOHWND:
            return "Clipper notification requires an HWND or no HWND has previously been set as the CooperativeLevel HWND.\0";
        case DDERR_NOMIRRORHW:
            return "Operation could not be carried out because there is no hardware present or available.\0";
        case DDERR_NOOVERLAYDEST:
            return "Returned when GetOverlayPosition is called on an overlay that UpdateOverlay has never been called on to establish a destination.\0";
        case DDERR_NOOVERLAYHW:
            return "Operation could not be carried out because there is no overlay hardware present or available.\0";
        case DDERR_NOPALETTEATTACHED:
            return "No palette object attached to this surface.\0";
        case DDERR_NOPALETTEHW:
            return "No hardware support for 16 or 256 color palettes.\0";
        case DDERR_NORASTEROPHW:
            return "Operation could not be carried out because there is no appropriate raster op hardware present or available.\0";
        case DDERR_NOROTATIONHW:
            return "Operation could not be carried out because there is no rotation hardware present or available.\0";
        case DDERR_NOSTRETCHHW:
            return "Operation could not be carried out because there is no hardware support for stretching.\0";
        case DDERR_NOT4BITCOLOR:
            return "DirectDrawSurface is not in 4 bit color palette and the requested operation requires 4 bit color palette.\0";
        case DDERR_NOT4BITCOLORINDEX:
            return "DirectDrawSurface is not in 4 bit color index palette and the requested operation requires 4 bit color index palette.\0";
        case DDERR_NOT8BITCOLOR:
            return "DirectDrawSurface is not in 8 bit color mode and the requested operation requires 8 bit color.\0";
        case DDERR_NOTAOVERLAYSURFACE:
            return "Returned when an overlay member is called for a non-overlay surface.\0";
        case DDERR_NOTEXTUREHW:
            return "Operation could not be carried out because there is no texture mapping hardware present or available.\0";
        case DDERR_NOTFLIPPABLE:
            return "An attempt has been made to flip a surface that is not flippable.\0";
        case DDERR_NOTFOUND:
            return "Requested item was not found.\0";
        case DDERR_NOTLOCKED:
            return "Surface was not locked.  An attempt to unlock a surface that was not locked at all, or by this process, has been attempted.\0";
        case DDERR_NOTPALETTIZED:
            return "The surface being used is not a palette-based surface.\0";
        case DDERR_NOVSYNCHW:
            return "Operation could not be carried out because there is no hardware support for vertical blank synchronized operations.\0";
        case DDERR_NOZBUFFERHW:
            return "Operation could not be carried out because there is no hardware support for zbuffer blitting.\0";
        case DDERR_NOZOVERLAYHW:
            return "Overlay surfaces could not be z layered based on their BltOrder because the hardware does not support z layering of overlays.\0";
        case DDERR_OUTOFCAPS:
            return "The hardware needed for the requested operation has already been allocated.\0";
        case DDERR_OUTOFMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OUTOFVIDEOMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OVERLAYCANTCLIP:
            return "The hardware does not support clipped overlays.\0";
        case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
            return "Can only have ony color key active at one time for overlays.\0";
        case DDERR_OVERLAYNOTVISIBLE:
            return "Returned when GetOverlayPosition is called on a hidden overlay.\0";
        case DDERR_PALETTEBUSY:
            return "Access to this palette is being refused because the palette is already locked by another thread.\0";
        case DDERR_PRIMARYSURFACEALREADYEXISTS:
            return "This process already has created a primary surface.\0";
        case DDERR_REGIONTOOSMALL:
            return "Region passed to Clipper::GetClipList is too small.\0";
        case DDERR_SURFACEALREADYATTACHED:
            return "This surface is already attached to the surface it is being attached to.\0";
        case DDERR_SURFACEALREADYDEPENDENT:
            return "This surface is already a dependency of the surface it is being made a dependency of.\0";
        case DDERR_SURFACEBUSY:
            return "Access to this surface is being refused because the surface is already locked by another thread.\0";
        case DDERR_SURFACEISOBSCURED:
            return "Access to surface refused because the surface is obscured.\0";
        case DDERR_SURFACELOST:
            return "Access to this surface is being refused because the surface memory is gone. The DirectDrawSurface object representing this surface should have Restore called on it.\0";
        case DDERR_SURFACENOTATTACHED:
            return "The requested surface is not attached.\0";
        case DDERR_TOOBIGHEIGHT:
            return "Height requested by DirectDraw is too large.\0";
        case DDERR_TOOBIGSIZE:
            return "Size requested by DirectDraw is too large, but the individual height and width are OK.\0";
        case DDERR_TOOBIGWIDTH:
            return "Width requested by DirectDraw is too large.\0";
        case DDERR_UNSUPPORTED:
            return "Action not supported.\0";
        case DDERR_UNSUPPORTEDFORMAT:
            return "FOURCC format requested is unsupported by DirectDraw.\0";
        case DDERR_UNSUPPORTEDMASK:
            return "Bitmask in the pixel format requested is unsupported by DirectDraw.\0";
        case DDERR_VERTICALBLANKINPROGRESS:
            return "Vertical blank is in progress.\0";
        case DDERR_WASSTILLDRAWING:
            return "Informs DirectDraw that the previous Blt which is transfering information to or from this Surface is incomplete.\0";
        case DDERR_WRONGMODE:
            return "This surface can not be restored because it was created in a different mode.\0";
        case DDERR_XALIGN:
            return "Rectangle provided was not horizontally aligned on required boundary.\0";
        case D3DERR_BADMAJORVERSION:
            return "D3DERR_BADMAJORVERSION\0";
        case D3DERR_BADMINORVERSION:
            return "D3DERR_BADMINORVERSION\0";
        case D3DERR_EXECUTE_LOCKED:
            return "D3DERR_EXECUTE_LOCKED\0";
        case D3DERR_EXECUTE_NOT_LOCKED:
            return "D3DERR_EXECUTE_NOT_LOCKED\0";
        case D3DERR_EXECUTE_CREATE_FAILED:
            return "D3DERR_EXECUTE_CREATE_FAILED\0";
        case D3DERR_EXECUTE_DESTROY_FAILED:
            return "D3DERR_EXECUTE_DESTROY_FAILED\0";
        case D3DERR_EXECUTE_LOCK_FAILED:
            return "D3DERR_EXECUTE_LOCK_FAILED\0";
        case D3DERR_EXECUTE_UNLOCK_FAILED:
            return "D3DERR_EXECUTE_UNLOCK_FAILED\0";
        case D3DERR_EXECUTE_FAILED:
            return "D3DERR_EXECUTE_FAILED\0";
        case D3DERR_EXECUTE_CLIPPED_FAILED:
            return "D3DERR_EXECUTE_CLIPPED_FAILED\0";
        case D3DERR_TEXTURE_NO_SUPPORT:
            return "D3DERR_TEXTURE_NO_SUPPORT\0";
        case D3DERR_TEXTURE_NOT_LOCKED:
            return "D3DERR_TEXTURE_NOT_LOCKED\0";
        case D3DERR_TEXTURE_LOCKED:
            return "D3DERR_TEXTURELOCKED\0";
        case D3DERR_TEXTURE_CREATE_FAILED:
            return "D3DERR_TEXTURE_CREATE_FAILED\0";
        case D3DERR_TEXTURE_DESTROY_FAILED:
            return "D3DERR_TEXTURE_DESTROY_FAILED\0";
        case D3DERR_TEXTURE_LOCK_FAILED:
            return "D3DERR_TEXTURE_LOCK_FAILED\0";
        case D3DERR_TEXTURE_UNLOCK_FAILED:
            return "D3DERR_TEXTURE_UNLOCK_FAILED\0";
        case D3DERR_TEXTURE_LOAD_FAILED:
            return "D3DERR_TEXTURE_LOAD_FAILED\0";
        case D3DERR_MATRIX_CREATE_FAILED:
            return "D3DERR_MATRIX_CREATE_FAILED\0";
        case D3DERR_MATRIX_DESTROY_FAILED:
            return "D3DERR_MATRIX_DESTROY_FAILED\0";
        case D3DERR_MATRIX_SETDATA_FAILED:
            return "D3DERR_MATRIX_SETDATA_FAILED\0";
        case D3DERR_SETVIEWPORTDATA_FAILED:
            return "D3DERR_SETVIEWPORTDATA_FAILED\0";
        case D3DERR_MATERIAL_CREATE_FAILED:
            return "D3DERR_MATERIAL_CREATE_FAILED\0";
        case D3DERR_MATERIAL_DESTROY_FAILED:
            return "D3DERR_MATERIAL_DESTROY_FAILED\0";
        case D3DERR_MATERIAL_SETDATA_FAILED:
            return "D3DERR_MATERIAL_SETDATA_FAILED\0";
        case D3DERR_LIGHT_SET_FAILED:
            return "D3DERR_LIGHT_SET_FAILED\0";
        default:
            return "Unrecognized error value.\0";
    }
}


D3DAPP.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dapp.h
 *
 *  Header to be included in source using D3DApp.  Contains D3DApp function
 *  prototypes and defines.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#ifndef __D3DAPP_H__
#define __D3DAPP_H__

#define INITGUID

#include <ddraw.h>
#include <d3d.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * DEFINES
 */
#define D3DAPP_WINDOWMINIMUM 50    /* smallest buffer size allowed */
#define D3DAPP_DEFAULTWINDOWDIM 320 /* replaces window size if invalid */
#define D3DAPP_MINBUFFERSIZE 15360  /* minimum "maximum buffer size" for a
       D3D driver to be accepted */
#define D3DAPP_MINVERTEXCOUNT 320   /* minimum "maximum vertex count" for a
       D3D driver to be accepted */
#define D3DAPP_MAXD3DDRIVERS 5    /* maximum Direct3D drivers ever expected
       to find */
#define D3DAPP_MAXTEXTUREFORMATS 50 /* maximum texture formats */
#define D3DAPP_MAXMODES 50    /* maximum display modes ever expected to
       be reported by DirectDraw */
#define D3DAPP_MAXTEXTURES 15    /* maximum number of textures that wil be
       loaded and managed */
#define D3DAPP_MAXCLEARRECTS 30    /* maximum num. rectangles (ie extents)
       for clearing */
#define D3DAPP_BOGUS -100    /* unused parameters accept this */
#define D3DAPP_YOUDECIDE -25    /* Use this for certain parameters to
       have D3DApp decide an appropriate
       value for you */
#define D3DAPP_USEWINDOW -24    /* Used in place of fullscreen mode */

/*
 * DATA STRUCTURES
 */

/*
 * D3DAppD3DDriver structure
 * Describes a D3D driver
 */
typedef struct tagD3DAppD3DDriver {
    char Name[30];/* short name of the driver */
    char About[50];/* short string about the driver */
    D3DDEVICEDESC Desc; /* D3DDEVICEDESC for complete information */
    GUID Guid;/* it's GUID */
    BOOL bIsHardware;/* does this driver represent a hardware device? */
    BOOL bDoesTextures; /* does this driver do texture mapping? */
    BOOL bDoesZBuffer;  /* can this driver use a z-buffer? */
    BOOL bCanDoWindow;/* can it render to Window's display depth? */
} D3DAppD3DDriver;

/*
 * D3DAppTextureFormat stucture
 * Describes a texture format
 */
typedef struct tagD3DAppTextureFormat {
    DDSURFACEDESC ddsd;/* DDSURFACEDESC for complete information */
    BOOL bPalettized;/* is this format palettized */
    int RedBPP;/* number of red, */
    int BlueBPP;/*                blue, */
    int GreenBPP;/*                      and green bits per pixel */
    int IndexBPP;/* number of bits in palette index */
} D3DAppTextureFormat;

/*
 * D3DAppMode structure
 * Describes a display mode
 */
typedef struct tagD3DAppMode {
    int     w;      /* width */
    int    h;      /* height */
    int    bpp;      /* bits per pixel */
    BOOL    bThisDriverCanDo; /*can current D3D driver render in this mode?*/
} D3DAppMode;

/*
 * D3DAppInfo structure
 * Contains all the information D3DApp makes available to the application. A
 * pointer to the internal, read only copy is returned by the initializing
 * function.
 */
typedef struct tagD3DAppInfo {
    HWND    hwnd;   /*handle of window being managed*/
    /*
     * Direct3D objects and information
     */
    LPDIRECT3D2    lpD3D;   /* D3D object */
    LPDIRECT3DDEVICE2    lpD3DDevice;   /* D3D device */
    LPDIRECT3DVIEWPORT2    lpD3DViewport; /* D3D viewport, created by
      application */
    int    NumDrivers;   /* number of D3D drivers avail. */
    int    CurrDriver;   /* number of curr. D3D driver */
    D3DAppD3DDriver    Driver[D3DAPP_MAXD3DDRIVERS]; /* avail. drivers*/
    D3DAppD3DDriver    ThisDriver;   /* description of this driver,
   identical to Driver[CurrDriver] */

    int    NumTextureFormats; /* num texture formats avail*/
    int    CurrTextureFormat; /* current texture format
  will only change if driver changes or when app changes it*/
    D3DAppTextureFormat     TextureFormat[D3DAPP_MAXTEXTUREFORMATS];
      /* description of all avail. formats */
    D3DAppTextureFormat     ThisTextureFormat; /* description of this format,
     identical to TextureFormat[CurrTextureFormat] */

    int    NumTextures;    /* number of textures in D3DApp's
       texture list */
    char    ImageFile[D3DAPP_MAXTEXTURES][50]; /* files */
    D3DTEXTUREHANDLE    TextureHandle[D3DAPP_MAXTEXTURES]; /* handles */
    LPDIRECTDRAWSURFACE    lpTextureSurf[D3DAPP_MAXTEXTURES]; /* surfaces */
    LPDIRECT3DTEXTURE2    lpTexture[D3DAPP_MAXTEXTURES]; /* texture objs */
    int    NumUsableTextures; /* the number of currently usable
  textures (e.g. for a hardware
  device there may not be enough
          video memory*/
    /*
     * DirectDraw objects and information
     */
    LPDIRECTDRAW    lpDD;   /* DirectDraw object */
    BOOL    bIsPrimary;    /* Is this the primary DD device?
       If FALSE, we're using a hardware DD device that cannot
       display a window and so only fullscreen modes are available */
    LPDIRECTDRAWSURFACE    lpFrontBuffer; /* front buffer surface */
    LPDIRECTDRAWSURFACE    lpBackBuffer;  /* back buffer surface */
    LPDIRECTDRAWSURFACE    lpZBuffer;     /* z-buffer surface */
    BOOL    bBackBufferInVideo; /* back buf in video mem? */
    BOOL    bZBufferInVideo;/* is Z-buf in video mem? */

    int    NumModes; /* number of available display modes */
    int    CurrMode; /* number of current display mode (only
         when fullscreen) */
    D3DAppMode    Mode[D3DAPP_MAXMODES]; /* desc avail modes */
    D3DAppMode    ThisMode; /* description of this mode, identical
 to Mode[CurrMode] */
    BOOL    bFullscreen; /* in fullscreen exclusive mode? */
    D3DAppMode    WindowsDisplay; /* current Windows disply mode */

    SIZE    szClient;      /* dimensions of client win */
    POINT    pClientOnPrimary; /* position of client win */

    BOOL    bPaused;       /* the app is paused */
    BOOL    bAppActive;       /* the app is active */
    BOOL    bTexturesDisabled; /* textures are disabled */
    BOOL    bOnlySystemMemory; /* all surfaces forced into
  system memory */
    BOOL    bOnlyEmulation;    /* no hardware DD or D3D
  devices allowed */
    BOOL    bMinimized;       /* app window is minimized */
    BOOL    bRenderingIsOK;    /* All objects etc. necessary
  for rendering are in ok */
} D3DAppInfo;

/*
 * D3DAppRenderState structure
 * The "render state" is the status of this collection of D3D options and
 * variables.  This structure is used to get and set the render state.  The
 * render state will only change during program initialization and when
 * the application sets it.
 */
typedef struct tagD3DAppRenderState {
    BOOL             bZBufferOn;    /* Z buffer is on */
    BOOL             bPerspCorrect; /* perspective correction is on */
    D3DSHADEMODE     ShadeMode;     /* flat, gouraud, phong? */
    D3DTEXTUREFILTER TextureFilter; /* linear or bi-linear texture filter */
    D3DTEXTUREBLEND  TextureBlend;  /* Use shade mode or copy mode? */
    D3DFILLMODE      FillMode;    /* solid, lines or points? */
    BOOL             bDithering;    /* dithering is on */
    BOOL             bSpecular;    /* specular highlights are on */
    BOOL             bAntialiasing; /* anti-aliasing is on */

    BOOL     bFogEnabled;   /* fog is on */
    D3DCOLOR     FogColor;    /* fog color */
    D3DFOGMODE     FogMode;    /* linear, exp. etc. */
    D3DVALUE     FogStart;    /* begining depth */
    D3DVALUE     FogEnd;    /* ending depth */
} D3DAppRenderState;

/*
 * FUNCTION PROTOTYPES
 */

/*
 * D3DAppCreateFromHWND
 *
 * Call this before all other D3DApp functions (except AddTexture).
 * Initializes all DD and D3D objects necessary for rendering, enumerates the
 * available display modes and drivers and loads textures specified by prior
 * AddTexture() calls.  Caller passes the handle of the window to be manged
 * and callback functions to execute for device creation and destruction.
 *
 * DeviceCreateCallback is executed AFTER the creation of D3D device and all
 * objects D3DApp created using the device.  This allows an application to
 * reconstruct the scene and create any additional objects.  The callback
 * must create and return (in the variable provided) the DIRECT3DVIEWPORT
 * from the given width and height.  The returned pointer is stored in the
 * D3DAppInfo structure and used for clearing and setting the render state.
 * A NULL pointer is fine if D3DApp is not used for either of these
 * functions. The create callback will always be called before any calls to
 * the destroy callback.  The boolean returned indicates success or failure.
 *
 * DeviceDestroyCallback is executed BEFORE the D3D device and objects
 * created by D3DApp using the device are released.  This allows an
 * application to save data regarding the scene or release any objects
 * created from the device before it is destroyed.  The DIRECT3DVIEWPORT
 * should be released in this callback.  The boolean returned indicates the
 * success or failure.
 *
 * A pointer to the internal D3DAppInfo data structure is returned.  This
 * should be READ ONLY!
 *
 * The DirectDraw device, Direct3D driver, display mode and texture format
 * will all be chosen by D3DApp.  Hardware DD and D3D devices are prefered.
 * Mono lighting D3D drivers are prefered.  Paletted texture formats are
 * prefered.  If possible, the current window size will be used, otherwise
 * a fullscreen mode will be selected.
 *
 * Call AddTexture() to add textures to be loaded upon initialization.
 *
 * Valid flags:
 *    D3DAPP_ONLYSYSTEMMEMORY  Force all surfaces into system memory.  Also
 *                             disables hardware DD and D3D drivers.
 *    D3DAPP_ONLYD3DEMULATION  Disable D3D hardware
 *    D3DAPP_ONLYDDEMULATION   Disable DD hardware
 */
#define D3DAPP_ONLYSYSTEMMEMORY 0x00000001
#define D3DAPP_ONLYD3DEMULATION0x00000002
#define D3DAPP_ONLYDDEMULATION0x00000004
BOOL D3DAppCreateFromHWND(DWORD flags, HWND hwnd,
  BOOL(*DeviceCreateCallback)(int, int,
      LPDIRECT3DVIEWPORT2*,
      LPVOID),
  LPVOID lpCreateContext,
  BOOL(*DeviceDestroyCallback)(LPVOID),
  LPVOID lpDestroyContext,
  D3DAppInfo** D3DApp);

/*
 * D3DAppWindowProc
 * To be truly effective, D3DApp should be allowed to trap incoming window
 * messages such as WM_SIZE.  Call D3DAppWindowProc at the begining of the
 * application's main window WindowProc with the message information.  If
 * bStopProcessing is set to TRUE, stop processing the message and return
 * lresult.
 */
BOOL D3DAppWindowProc(BOOL* bStopProcessing, LRESULT* lresult, HWND hwnd,
      UINT message, WPARAM wParam, LPARAM lParam);

/*
 * D3DAppFullscreen
 * Places the app in a fullscreen mode using the current driver.
 */
BOOL D3DAppFullscreen(int mode);

/*
 * D3DAppWindow
 * Places the application in windowed mode at the given client size.  If w
 * and h are D3DAPP_YOUDECIDE, D3DApp will decide on a suitable client size.
 * If called while in fullscreen, restores the display mode and returns the
 * hooked window to the size it was before a call to D3DAppFullscreen or to
 * the size specified.
 */
BOOL D3DAppWindow(int w, int h);

/*
 * D3DAppChangeDriver
 * Changes the driver.  If the current display mode is incompatible with the
 * driver, a new one will be selected and employed.  A new texture format is
 * selected and textures are reloaded, hence their handles may change.  By
 * default, paletted formats are prefered.
 */
BOOL D3DAppChangeDriver(int driver, DWORD flags);

/*
 * D3DAppAddTexture
 * D3DApp has an internal list of textures which it maintains.  The image
 * files will be reloaded when necessary and the texture handles, objects and
 * surfaces will always be up to date and available in the D3DAppInfo
 * structure.  D3DAppAddTextures adds the given PPM image file to this
 * list.
 *
 * This is the only function which can be called before CreateD3DAppFromHWND.
 * Use it to create a list of textures to load during this creation function.
 *
 * The handles and texture objects will change when the device or texture
 * format changes.  To react to changes in the texture objects and surfaces,
 * use the callback for D3D device creation/release and make necessary
 * changes whenever calling D3DAppChangeTextureFormat.
 *
 * Image files are searched for in the current directory, D3DPATH env var,
 * and the "Software\Microsoft\Direct3D\4.0\D3D Path" registry entry.
 *
 */
BOOL D3DAppAddTexture(const char* imagefile);

/*
 * D3DAppChangeTextureFormat
 * Changes all textures to the given format.  Texture handles and objects
 * will change.
 */
BOOL D3DAppChangeTextureFormat(int format);

/*
 * D3DAppDisableTextures
 * Disables the textures by turning handles to NULL.  If the driver changes,
 * textures are not loaded until this state is toggled by another call with a
 * TRUE flag.
 */
BOOL D3DAppDisableTextures(BOOL flag);

/*
 * D3DAppSwapTextures
 * Swaps first texture with the second, second with third, etc. while keeping
 * the handles array the same.
 */
BOOL D3DAppSwapTextures(void);

/*
 * D3DAppSetRenderState
 * Uses a D3D execute buffer to set the render state.  If lpState is NULL,
 * the current settings are reset.
 */
BOOL D3DAppSetRenderState(D3DAppRenderState* lpState);

/*
 * D3DAppGetRenderState
 * Returns the current render state.
 */
BOOL D3DAppGetRenderState(D3DAppRenderState* lpState);

/*
 * D3DAppShowBackBuffer
 * Blts or flips the back buffer to the primary surface.  In the windowed
 * case, only the dirty portion of the front buffer is blt'ed over.  The
 * dirty region of front and back buffers is maintained by calls to
 * D3DAppRenderExtents(). D3DAPP_SHOWALL will force the entire front buffer
 * to be updated.
 */
#define D3DAPP_SHOWALL 0x00000001
BOOL D3DAppShowBackBuffer(DWORD flags);

/*
 * D3DAppRenderExtents
 * Tells D3DApp the extents of all regions updated on the back buffer as a
 * list of D3DRECTs (no more than D3DAPP_MAXCLEARRECTS).  Call this before
 * clearing the back buffer.  If the D3DAPP_CLEARALL flag is set, the extents
 * are ignored and the entire back buffer is assumed to have changed.
 */
#define D3DAPP_CLEARALL 0x00000001
BOOL D3DAppRenderExtents(DWORD dwCount, LPD3DRECT extent, DWORD flags);

/*
 * D3DAppClearBackBuffer
 * Clears the back buffer and Z-buffer (if enabled).  D3DAPP_CLEARALL can be
 * used to clear the entire back buffer.
 */
#define D3DAPP_CLEARALL 0x00000001
BOOL D3DAppClearBackBuffer(DWORD flags);

/*
 * D3DAppCheckForLostSurfaces
 * Checks all surfaces D3DApp has allocated and restores them if necessary.
 * An error is returned on any type of failure, but it may be best to ignore
 * it since restoring surface can fail for non-fatal reasons and the app may
 * just want to spin.
 */
BOOL D3DAppCheckForLostSurfaces(void);

/*
 * D3DAppPause
 * Use D3DAppPause(TRUE) to pause the app and D3DAppPause(FALSE) to unpause.
 * When fullscreen, the menu bar is redrawn.  bPaused is updated to reflect
 * the current status.
 */
BOOL D3DAppPause(BOOL flag);

/*
 * D3DAppErrorToString
 * Converts a DirectDraw, Direct3D or Direct3D RM error code to a string.
 */
char* D3DAppErrorToString(HRESULT error);

/*
 * D3DAppCreateSurface
 * Creates a surface described by ddsd.  Will force the surface into
 * systemmemory if D3DApp was initialized with D3DAPP_ONLYSYSTEMMEMORY.
 */
BOOL D3DAppCreateSurface(DDSURFACEDESC *ddsd, LPDIRECTDRAWSURFACE *lplpSurf);

/*
 * D3DAppLastError
 * D3DAppLastErrorString
 * Returns the last D3DApp error as a string and HRESULT.
 */
HRESULT D3DAppLastError(void);
char* D3DAppLastErrorString(void);

/*
 * D3DAppDestroy
 * Destroys all objects including Direct Draw.  Call before program
 * termination.
 */
BOOL D3DAppDestroy(void);

#ifdef __cplusplus
};
#endif

#endif // __D3DAPP_H__


D3DAPPI.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dappi.h
 *
 *  Internal header.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#ifndef __D3DAPPI_H__
#define __D3DAPPI_H__

/*
 * INCLUDED HEADERS
 */
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <search.h>
#include <ddraw.h>
#include <d3d.h>
#include "d3dapp.h"
#include "d3dmacs.h"
#include "lclib.h"  /* lclib is a override for standard string lib */

#ifdef __cplusplus
extern "C" {
#endif

/*
 * MACROS
 */
#undef ATTEMPT
#define ATTEMPT(x) if (!(x)) goto exit_with_error
#undef RELEASE
#define RELEASE(x) if (x) { x->lpVtbl->Release(x); x = NULL; }
#undef MAX
#define MAX(x, y) ((x) > (y)) ? (x) : (y)
#undef MIN
#define MIN(x, y) ((x) > (y)) ? (y) : (x)
#undef ZEROMEM
#define ZEROMEM(x) memset(&x, 0, sizeof(x))

/*
 * GLOBAL VARIABLES
 *    see d3dapp.c for descriptions
 */
extern D3DAppInfo d3dappi;
extern D3DAppRenderState d3dapprs;
extern BOOL bD3DAppInitialized;
extern HRESULT LastError;
extern LPDIRECTDRAWCLIPPER lpClipper;
extern LPDIRECTDRAWPALETTE lpPalette;
extern BOOL(*D3DDeviceDestroyCallback)(LPVOID);
extern LPVOID D3DDeviceDestroyCallbackContext;
extern BOOL(*D3DDeviceCreateCallback)(int, int, LPDIRECT3DVIEWPORT2*, LPVOID);
extern LPVOID D3DDeviceCreateCallbackContext;
extern BOOL bPrimaryPalettized;
extern BOOL bPaletteActivate;
extern BOOL bIgnoreWM_SIZE;
extern PALETTEENTRY ppe[256];
extern PALETTEENTRY Originalppe[256];
extern char LastErrorString[256];
extern SIZE szLastClient;
extern SIZE szBuffers;
extern int NumDirtyClientRects, NumDirtyBackRects, NumDirtyZRects;
extern D3DRECT DirtyClient[D3DAPP_MAXCLEARRECTS];
extern D3DRECT DirtyBack[D3DAPP_MAXCLEARRECTS];
extern D3DRECT DirtyZ[D3DAPP_MAXCLEARRECTS];
extern D3DTEXTUREHANDLE MasterTextureHandle[D3DAPP_MAXTEXTURES];
extern int CallbackRefCount;

#ifdef __cplusplus
};
#endif

/*
 * INTERNAL FUNCTION PROTOTYPES
 */
BOOL D3DAppISetRenderState(void);
BOOL D3DAppIEnumDevices(void);
BOOL D3DAppIPickDriver(int* driver, DWORD depths);
BOOL D3DAppICreateD3D(void);
BOOL D3DAppIEnumTextureFormats(void);
BOOL D3DAppICreateZBuffer(int w, int h, int driver);
BOOL D3DAppICreateDevice(int driver);
BOOL D3DAppILoadTextureSurf(int n, BOOL* bInVideo);
BOOL D3DAppIGetTextureHandle(int n);
BOOL D3DAppILoadAllTextures(void);
void D3DAppIReleaseTexture(int n);
void D3DAppIReleaseAllTextures(void);
BOOL D3DAppIReloadTextureSurf(int n);
BOOL D3DAppISetCoopLevel(HWND hwnd, BOOL bFullscreen);
BOOL D3DAppISetDisplayMode(int w, int h, int bpp);
BOOL D3DAppICheckForPalettized(void);
BOOL D3DAppIRestoreDispMode(void);
BOOL D3DAppIVerifyDriverAndMode(int* lpdriver, int* lpmode);
BOOL D3DAppIFilterDrivers(int mode);
DWORD D3DAppTotalVideoMemory(void);
BOOL D3DAppIEnumDisplayModes(void);
BOOL D3DAppIPickDisplayMode(int* mode, DWORD depths);
BOOL D3DAppISetDispMode(int w, int h, int bpp);
BOOL D3DAppICreateDD(DWORD flags);
BOOL D3DAppIFilterDisplayModes(int driver);
HRESULT D3DAppICreateSurface(LPDDSURFACEDESC lpDDSurfDesc,
                LPDIRECTDRAWSURFACE FAR *lpDDSurface);
HRESULT D3DAppIGetSurfDesc(LPDDSURFACEDESC lpDDSurfDesc,
   LPDIRECTDRAWSURFACE lpDDSurf);
BOOL D3DAppICreateBuffers(HWND hwnd, int w, int h, int bpp, BOOL bFullscreen, BOOL bIsHardware);
BOOL D3DAppIRememberWindowsMode(void);
BOOL D3DAppIClearBuffers(void);
DWORD D3DAppIBPPToDDBD(int bpp);
void D3DAppIReleasePathList(void);
LPDIRECTDRAWSURFACE D3DAppILoadSurface(LPDIRECTDRAW lpDD, LPCSTR lpName,
       LPDDSURFACEDESC lpFormat,
       DWORD memoryflag);
void D3DAppISetClientSize(HWND hwnd, int w,int h,BOOL bReturnFromFullscreen);
void D3DAppIGetClientWin(HWND hwnd);
void D3DAppISetDefaults(void);
BOOL D3DAppICallDeviceDestroyCallback(void);
BOOL D3DAppICallDeviceCreateCallback(int w, int h);
void D3DAppIMergeRectLists(int* dstnum, LPD3DRECT dst, int src1num,
   LPD3DRECT src1, int src2num, LPD3DRECT src2);
void D3DAppICopyRectList(int* dstnum, LPD3DRECT dst, int srcnum,
 LPD3DRECT src);
void D3DAppIValidateDirtyRects(void);
BOOL D3DAppIHandleWM_SIZE(LRESULT* lresult, HWND hwnd, UINT message,
  WPARAM wParam, LPARAM lParam);
void D3DAppISetErrorString( LPSTR fmt, ... );

void __cdecl dpf( LPSTR fmt, ... );

#endif // __D3DAPPI_H__


D3DCALLS.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dcalls.c
 *
 *  Calls to Direct3D objects needed for rendering.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#define INITGUID
#include "d3dappi.h"

/***************************************************************************/
/*                            Creation of D3D                              */
/***************************************************************************/
BOOL
D3DAppICreateD3D(void)
{
    LastError = d3dappi.lpDD->lpVtbl->QueryInterface(d3dappi.lpDD,
    &IID_IDirect3D2, (LPVOID*)&d3dappi.lpD3D);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Creation of IDirect3D failed.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    return TRUE;
exit_with_error:
    return FALSE;
}

/***************************************************************************/
/*                           D3D Device Enumeration                        */
/***************************************************************************/
/*
 * enumDeviceFunc
 * Device enumeration callback.  Record information about the D3D device
 * reported by D3D.
 */
static BOOL bRampHasEnumerated = FALSE;
static HRESULT
WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription,
      LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc,
      LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext)
{
    lpContext = lpContext;
/*if (! bRampHasEnumerated)
{
bRampHasEnumerated = TRUE;
return D3DENUMRET_OK;
}*/
    /*
     * Don't accept any hardware D3D devices if emulation only option is set
     */
    if (lpHWDesc->dcmColorModel && d3dappi.bOnlyEmulation)
return D3DENUMRET_OK;
    /*
     * Record the D3D driver's inforamation
     */
    memcpy(&d3dappi.Driver[d3dappi.NumDrivers].Guid, lpGuid, sizeof(GUID));
    lstrcpy(d3dappi.Driver[d3dappi.NumDrivers].About, lpDeviceDescription);
    lstrcpy(d3dappi.Driver[d3dappi.NumDrivers].Name, lpDeviceName);
    /*
     * Is this a hardware device or software emulation?  Checking the color
     * model for a valid model works.
     */
    if (lpHWDesc->dcmColorModel) {
d3dappi.Driver[d3dappi.NumDrivers].bIsHardware = TRUE;
memcpy(&d3dappi.Driver[d3dappi.NumDrivers].Desc, lpHWDesc,
       sizeof(D3DDEVICEDESC));
    } else {
d3dappi.Driver[d3dappi.NumDrivers].bIsHardware = FALSE;
memcpy(&d3dappi.Driver[d3dappi.NumDrivers].Desc, lpHELDesc,
       sizeof(D3DDEVICEDESC));
    }
    /*
     * Does this driver do texture mapping?
     */
    d3dappi.Driver[d3dappi.NumDrivers].bDoesTextures =
(d3dappi.Driver[d3dappi.NumDrivers].Desc.dpcTriCaps.dwTextureCaps &
 D3DPTEXTURECAPS_PERSPECTIVE) ? TRUE : FALSE;
    /*
     * Can this driver use a z-buffer?
     */
    d3dappi.Driver[d3dappi.NumDrivers].bDoesZBuffer =
d3dappi.Driver[d3dappi.NumDrivers].Desc.dwDeviceZBufferBitDepth
? TRUE : FALSE;
    /*
     * Can this driver render to the Windows display depth
     */
    d3dappi.Driver[d3dappi.NumDrivers].bCanDoWindow =
(d3dappi.Driver[d3dappi.NumDrivers].Desc.dwDeviceRenderBitDepth &
 D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp)) ? TRUE : FALSE;
    if (!d3dappi.bIsPrimary)
d3dappi.Driver[d3dappi.NumDrivers].bCanDoWindow = FALSE;

    d3dappi.NumDrivers++;
    if (d3dappi.NumDrivers == D3DAPP_MAXD3DDRIVERS)
        return (D3DENUMRET_CANCEL);
    return (D3DENUMRET_OK);
}

/*
 * D3DAppIEnumDevices
 * Get the available drivers from Direct3D by enumeration.
 */
BOOL
D3DAppIEnumDevices(void)
{
    d3dappi.NumDrivers = 0;
    LastError = d3dappi.lpD3D->lpVtbl->EnumDevices(d3dappi.lpD3D,
   enumDeviceFunc, NULL);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Enumeration of drivers failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    d3dappi.CurrDriver = 0;
    return TRUE;
}

/***************************************************************************/
/*                    Enumeration of texure format                         */
/***************************************************************************/
/*
 * EnumTextureFormatsCallback
 * Record information about each texture format the current D3D driver can
 * support. Choose one as the default format (paletted formats are prefered)
 * and return it through lpContext.
 */
static HRESULT
CALLBACK EnumTextureFormatsCallback(LPDDSURFACEDESC lpDDSD, LPVOID lpContext)
{
    unsigned long m;
    int r, g, b;
    int *lpStartFormat = (int *)lpContext;
    /*
     * Record the DDSURFACEDESC of this texture format
     */
    memset(&d3dappi.TextureFormat[d3dappi.NumTextureFormats], 0,
   sizeof(D3DAppTextureFormat));
    memcpy(&d3dappi.TextureFormat[d3dappi.NumTextureFormats].ddsd, lpDDSD,
   sizeof(DDSURFACEDESC));
    /*
     * Is this format palettized?  How many bits?  Otherwise, how many RGB
     * bits?
     */
    if (lpDDSD->ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) {
        d3dappi.TextureFormat[d3dappi.NumTextureFormats].bPalettized = TRUE;
d3dappi.TextureFormat[d3dappi.NumTextureFormats].IndexBPP = 8;
    } else if (lpDDSD->ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4) {
        d3dappi.TextureFormat[d3dappi.NumTextureFormats].bPalettized = TRUE;
d3dappi.TextureFormat[d3dappi.NumTextureFormats].IndexBPP = 4;
    } else if (lpDDSD->ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) {
/*
 * The sample apps don't currently understand
 * the alpha bit - just filter this format
 * away for now.
 */

return DDENUMRET_OK;
    } else
    {
        d3dappi.TextureFormat[d3dappi.NumTextureFormats].bPalettized = FALSE;
d3dappi.TextureFormat[d3dappi.NumTextureFormats].IndexBPP = 0;
        for (r = 0, m = lpDDSD->ddpfPixelFormat.dwRBitMask; !(m & 1);
       r++, m >>= 1);
        for (r = 0; m & 1; r++, m >>= 1);
        for (g = 0, m = lpDDSD->ddpfPixelFormat.dwGBitMask; !(m & 1);
       g++, m >>= 1);
        for (g = 0; m & 1; g++, m >>= 1);
        for (b = 0, m = lpDDSD->ddpfPixelFormat.dwBBitMask; !(m & 1);
       b++, m >>= 1);
        for (b = 0; m & 1; b++, m >>= 1);
d3dappi.TextureFormat[d3dappi.NumTextureFormats].RedBPP = r;
d3dappi.TextureFormat[d3dappi.NumTextureFormats].GreenBPP = g;
d3dappi.TextureFormat[d3dappi.NumTextureFormats].BlueBPP = b;
    }
    /*
     * If lpStarFormat is -1, this is the first format.  Select it.
     */
    if (*lpStartFormat == -1)
        *lpStartFormat = d3dappi.NumTextureFormats;
    /*
     * If this format is paletted, select it.
     */
    if (d3dappi.TextureFormat[d3dappi.NumTextureFormats].bPalettized) {
        *lpStartFormat = d3dappi.NumTextureFormats;
    }
    d3dappi.NumTextureFormats++;
    return DDENUMRET_OK;
}

/*
 * D3DAppIEnumTextureFormats
 * Get a list of available texture map formats from the Direct3D driver by
 * enumeration.  Choose a default format (paletted is prefered).
 */
BOOL
D3DAppIEnumTextureFormats(void)
{
    int StartFormat;
    /*
     * Set the default format to -1 to let the callback know it's being
     * called for the first time.
     */
    StartFormat = -1;
    d3dappi.NumTextureFormats = 0;
    LastError =
 d3dappi.lpD3DDevice->lpVtbl->EnumTextureFormats(d3dappi.lpD3DDevice,
  EnumTextureFormatsCallback,
                                                  (LPVOID)&StartFormat);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Enumeration of texture formats failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    memcpy(&d3dappi.ThisTextureFormat, &d3dappi.TextureFormat[StartFormat],
   sizeof(D3DAppTextureFormat));
    d3dappi.CurrTextureFormat = StartFormat;
    return TRUE;
}

/***************************************************************************/
/*                               Device creation                           */
/***************************************************************************/
/*
 * D3DAppICreateDevice
 * Create the D3D device and enumerate the texture formats
 */
BOOL
D3DAppICreateDevice(int driver)
{
    RELEASE(d3dappi.lpD3DDevice);

    if (d3dappi.Driver[driver].bIsHardware && !d3dappi.bBackBufferInVideo) {
D3DAppISetErrorString("Could not fit the rendering surfaces in video memory for this hardware device.\n");
goto exit_with_error;
    }

    d3dappi.CurrDriver = driver;
    memcpy(&d3dappi.ThisDriver, &d3dappi.Driver[driver], sizeof(D3DAppD3DDriver));
#if 1
    LastError = d3dappi.lpD3D->lpVtbl->CreateDevice(d3dappi.lpD3D,
&d3dappi.Driver[driver].Guid,
        d3dappi.lpBackBuffer, &d3dappi.lpD3DDevice);
#else
    LastError =
   d3dappi.lpBackBuffer->lpVtbl->QueryInterface(d3dappi.lpBackBuffer,
&d3dappi.Driver[driver].Guid,
      (LPVOID*)&d3dappi.lpD3DDevice);
#endif
    if (LastError != DD_OK) {
    D3DAppISetErrorString("Create D3D device failed.\n%s",
      D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    d3dappi.CurrDriver = driver;
    d3dappi.NumTextureFormats = 0;
    if (d3dappi.Driver[driver].bDoesTextures) {
if (!D3DAppIEnumTextureFormats())
    goto exit_with_error;
    }

    return TRUE;
exit_with_error:
    RELEASE(d3dappi.lpD3DDevice);
    return FALSE;
}

/***************************************************************************/
/*                      Setting the render state                           */
/***************************************************************************/
/*
 * D3DAppISetRenderState
 * Create and execute an execute buffer which will set the render state and
 * light state for the current viewport.
 */
BOOL
D3DAppISetRenderState()
{
LPDIRECT3DDEVICE2 pDev = d3dappi.lpD3DDevice;

    /*
     * If there is no D3D Viewport, we must return true because it is not
     * required.
     */
    if (!d3dappi.lpD3DViewport)
return TRUE;
    LastError = pDev->lpVtbl->BeginScene(pDev);
    if (LastError != D3D_OK) {
        D3DAppISetErrorString("BeginScene failed in SetRenderState.\n%s",
                              D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
LastError = pDev->lpVtbl->SetCurrentViewport(pDev, d3dappi.lpD3DViewport);
if (LastError !=D3D_OK) {
        D3DAppISetErrorString("SetViewport failed in SetRenderState.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Set render state
     */
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_SHADEMODE, (DWORD)d3dapprs.ShadeMode);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_TEXTUREPERSPECTIVE, (DWORD)d3dapprs.bPerspCorrect);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_ZENABLE, (DWORD)(d3dapprs.bZBufferOn &&
                                  d3dappi.ThisDriver.bDoesZBuffer));
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_ZWRITEENABLE, (DWORD)d3dapprs.bZBufferOn);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_ZFUNC, D3DCMP_LESSEQUAL);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_TEXTUREMAG, (DWORD)d3dapprs.TextureFilter);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_TEXTUREMIN, (DWORD)d3dapprs.TextureFilter);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_TEXTUREMAPBLEND, (DWORD)d3dapprs.TextureBlend);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_FILLMODE, (DWORD)d3dapprs.FillMode);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_DITHERENABLE, (DWORD)d3dapprs.bDithering);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_SPECULARENABLE, (DWORD)d3dapprs.bSpecular);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_ANTIALIAS, (DWORD)d3dapprs.bAntialiasing);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_FOGENABLE, (DWORD)d3dapprs.bFogEnabled);
    pDev->lpVtbl->SetRenderState(pDev, D3DRENDERSTATE_FOGCOLOR, (DWORD)d3dapprs.FogColor);
    /*
     * Set light state
     */
    pDev->lpVtbl->SetLightState(pDev, D3DLIGHTSTATE_FOGMODE, (DWORD)d3dapprs.bFogEnabled ?
                 d3dapprs.FogMode : D3DFOG_NONE);
    pDev->lpVtbl->SetLightState(pDev, D3DLIGHTSTATE_FOGSTART, *(unsigned long*)&d3dapprs.FogStart);
    pDev->lpVtbl->SetLightState(pDev, D3DLIGHTSTATE_FOGEND, *(unsigned long*)&d3dapprs.FogEnd);

    LastError = pDev->lpVtbl->EndScene(pDev);
    if (LastError != D3D_OK) {
        D3DAppISetErrorString("EndScene failed in SetRenderState.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
return TRUE;
exit_with_error:
    return FALSE;
}


D3DDEMO.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3ddemo.h
 *
 ***************************************************************************/
#ifndef __D3DDEMO_H__
#define __D3DDEMO_H__

#include <ddraw.h>
#include <d3d.h>
#include "d3dapp.h"
#include "d3dmath.h"
#include "d3dsphr.h"
#include "d3dmacs.h"

#ifdef __cplusplus
extern "C" {
#endif
    /*
     * SetMouseCallback
     * Called in an example to set a callback function for all WM messages
     * dealing with the mouse.  The handler should return whether or not
     * it handled the message.
     */
    BOOL SetMouseCallback(BOOL(*)(UINT, WPARAM, LPARAM));
    /*
     * SetKeyDownCallback
     * Called in an example to set a callback function for keyboard
     * messages. The handler should return whether or not it handled the
     * message.
     */
    BOOL SetKeyboardCallback(BOOL(*)(UINT, WPARAM, LPARAM));

/*
 * These functions are found in the source for each example.
 */
    /*
     * RenderScene
     * Clears the viewport and viewport Z-buffer and render the scene.
     * The extent of rendering is returned in the rectangle.
     */
    BOOL RenderScene(LPDIRECT3DDEVICE2 lpDev, LPDIRECT3DVIEWPORT2 lpView,
     LPD3DRECT lpExtent);
    /*
     * InitScene
     * Builds the model which will be rendered.
     */
    BOOL InitScene(void);

    /*
     * InitView
     * Builds execute buffer and all components needed to be added to viewport.
     */
    BOOL InitView(LPDIRECTDRAW lpDD,
  LPDIRECT3D2 lpD3D,
  LPDIRECT3DDEVICE2 lpDev,
  LPDIRECT3DVIEWPORT2 lpView,
  int NumTextures,
  LPD3DTEXTUREHANDLE TextureHandle);
    /*
     * Release all objects and free all memory allocated in InitScene
     */
    void ReleaseScene(void);

    /*
     * Release all objects and free all memory allocated in InitView
     */
    void ReleaseView(LPDIRECT3DVIEWPORT2 lpView);

    typedef struct Defaultstag {
D3DAppRenderState rs;
BOOL bTexturesDisabled;
BOOL bResizingDisabled;
BOOL bClearsOn;
char Name[30];
    } Defaults;

    /*
     * Allows each example to begin with different defaults
     */
    void OverrideDefaults(Defaults* defaults);

#ifdef __cplusplus
};
#endif

#endif // __D3DDEMO_H__


D3DMACS.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dmacs.h
 *
 *  Useful macros for generating execute buffers.  Consult the D3D sample
 *  code for examples of their usage.
 *
 *  Use OP_NOP to QWORD align triangle and line instructions.
 */

#ifndef __D3DMACS_H__
#define __D3DMACS_H__

#undef RELEASE

#ifndef __cplusplus
#define MAKE_MATRIX(lpDev, handle, data) do { \
    if (lpDev->lpVtbl->CreateMatrix(lpDev, &handle) != D3D_OK) \
    return FALSE; \
    if (lpDev->lpVtbl->SetMatrix(lpDev, handle, &data) != D3D_OK) \
        return FALSE; \
  } while (0)
#define RELEASE(x) if (x != NULL) {x->lpVtbl->Release(x); x = NULL;}
#endif

#ifdef __cplusplus
#define MAKE_MATRIX(lpDev, handle, data) do { \
    if (lpDev->CreateMatrix(&handle) != D3D_OK) \
    return FALSE; \
    if (lpDev->SetMatrix(handle, &data) != D3D_OK) \
        return FALSE; \
  } while (0)
#define RELEASE(x) if (x != NULL) {x->Release(); x = NULL;}
#endif

#define PUTD3DINSTRUCTION(op, sz, cnt, ptr) do { \
    ((LPD3DINSTRUCTION) ptr)->bOpcode = op; \
    ((LPD3DINSTRUCTION) ptr)->bSize = sz; \
    ((LPD3DINSTRUCTION) ptr)->wCount = cnt; \
    ptr = (void *)(((LPD3DINSTRUCTION) ptr) + 1); \
  } while (0)

#define VERTEX_DATA(loc, cnt, ptr) do { \
    if ((ptr) != (loc)) memcpy((ptr), (loc), sizeof(D3DVERTEX) * (cnt)); \
    ptr = (void *)(((LPD3DVERTEX) (ptr)) + (cnt)); \
  } while (0)

// OP_MATRIX_MULTIPLY size: 4 (sizeof D3DINSTRUCTION)
#define OP_MATRIX_MULTIPLY(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_MATRIXMULTIPLY, sizeof(D3DMATRIXMULTIPLY), cnt, ptr)

// MATRIX_MULTIPLY_DATA size: 12 (sizeof MATRIXMULTIPLY)
#define MATRIX_MULTIPLY_DATA(src1, src2, dest, ptr) do { \
    ((LPD3DMATRIXMULTIPLY) ptr)->hSrcMatrix1 = src1; \
    ((LPD3DMATRIXMULTIPLY) ptr)->hSrcMatrix2 = src2; \
    ((LPD3DMATRIXMULTIPLY) ptr)->hDestMatrix = dest; \
    ptr = (void *)(((LPD3DMATRIXMULTIPLY) ptr) + 1); \
  } while (0)

// OP_STATE_LIGHT size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_LIGHT(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATELIGHT, sizeof(D3DSTATE), cnt, ptr)

// OP_STATE_TRANSFORM size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_TRANSFORM(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATETRANSFORM, sizeof(D3DSTATE), cnt, ptr)

// OP_STATE_RENDER size: 4 (sizeof D3DINSTRUCTION)
#define OP_STATE_RENDER(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_STATERENDER, sizeof(D3DSTATE), cnt, ptr)

// STATE_DATA size: 8 (sizeof D3DSTATE)
#define STATE_DATA(type, arg, ptr) do { \
    ((LPD3DSTATE) ptr)->drstRenderStateType = (D3DRENDERSTATETYPE)type; \
    ((LPD3DSTATE) ptr)->dwArg[0] = arg; \
    ptr = (void *)(((LPD3DSTATE) ptr) + 1); \
  } while (0)

// OP_PROCESS_VERTICES size: 4 (sizeof D3DINSTRUCTION)
#define OP_PROCESS_VERTICES(cnt, ptr) do { \
    PUTD3DINSTRUCTION(D3DOP_PROCESSVERTICES, sizeof(D3DPROCESSVERTICES), cnt, ptr); \
  } while (0)

// PROCESSVERTICES_DATA size: 16 (sizeof D3DPROCESSVERTICES)
#define PROCESSVERTICES_DATA(flgs, strt, cnt, ptr) do { \
    ((LPD3DPROCESSVERTICES) ptr)->dwFlags = flgs; \
    ((LPD3DPROCESSVERTICES) ptr)->wStart = strt; \
    ((LPD3DPROCESSVERTICES) ptr)->wDest = strt; \
    ((LPD3DPROCESSVERTICES) ptr)->dwCount = cnt; \
    ((LPD3DPROCESSVERTICES) ptr)->dwReserved = 0; \
    ptr = (void *)(((LPD3DPROCESSVERTICES) ptr) + 1); \
  } while (0)

// OP_TRIANGLE_LIST size: 4 (sizeof D3DINSTRUCTION)
#define OP_TRIANGLE_LIST(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_TRIANGLE, sizeof(D3DTRIANGLE), cnt, ptr)

#define TRIANGLE_LIST_DATA(loc, count, ptr) do { \
    if ((ptr) != (loc)) memcpy((ptr), (loc), sizeof(D3DTRIANGLE) * (count)); \
    ptr = (void *)(((LPD3DTRIANGLE) (ptr)) + (count)); \
  } while (0)

// OP_LINE_LIST size: 4 (sizeof D3DINSTRUCTION)
#define OP_LINE_LIST(cnt, ptr) \
    PUTD3DINSTRUCTION(D3DOP_LINE, sizeof(D3DLINE), cnt, ptr)

#define LINE_LIST_DATA(loc, count, ptr) do { \
    if ((ptr) != (loc)) memcpy((ptr), (loc), sizeof(D3DLINE) * (count)); \
    ptr = (void *)(((LPD3DLINE) (ptr)) + (count)); \
  }

// OP_POINT_LIST size: 8 (sizeof D3DINSTRUCTION + sizeof D3DPOINT)
#define OP_POINT_LIST(first, cnt, ptr) do { \
    PUTD3DINSTRUCTION(D3DOP_POINT, sizeof(D3DPOINT), 1, ptr); \
    ((LPD3DPOINT)(ptr))->wCount = cnt; \
    ((LPD3DPOINT)(ptr))->wFirst = first; \
    ptr = (void*)(((LPD3DPOINT)(ptr)) + 1); \
  } while (0)

// OP_SPAN_LIST size: 8 (sizeof D3DINSTRUCTION + sizeof D3DSPAN)
#define OP_SPAN_LIST(first, cnt, ptr) do { \
    PUTD3DINSTRUCTION(D3DOP_SPAN, sizeof(D3DSPAN), 1, ptr); \
    ((LPD3DSPAN)(ptr))->wCount = cnt; \
    ((LPD3DSPAN)(ptr))->wFirst = first; \
    ptr = (void*)(((LPD3DSPAN)(ptr)) + 1); \
  } while (0)

// OP_BRANCH_FORWARD size: 18 (sizeof D3DINSTRUCTION + sizeof D3DBRANCH)
#define OP_BRANCH_FORWARD(tmask, tvalue, tnegate, toffset, ptr) do { \
    PUTD3DINSTRUCTION(D3DOP_BRANCHFORWARD, sizeof(D3DBRANCH), 1, ptr); \
    ((LPD3DBRANCH) ptr)->dwMask = tmask; \
    ((LPD3DBRANCH) ptr)->dwValue = tvalue; \
    ((LPD3DBRANCH) ptr)->bNegate = tnegate; \
    ((LPD3DBRANCH) ptr)->dwOffset = toffset; \
    ptr = (void *)(((LPD3DBRANCH) (ptr)) + 1); \
  } while (0)

// OP_SET_STATUS size: 20 (sizeof D3DINSTRUCTION + sizeof D3DSTATUS)
#define OP_SET_STATUS(flags, status, _x1, _y1, _x2, _y2, ptr) do { \
    PUTD3DINSTRUCTION(D3DOP_SETSTATUS, sizeof(D3DSTATUS), 1, ptr); \
    ((LPD3DSTATUS)(ptr))->dwFlags = flags; \
    ((LPD3DSTATUS)(ptr))->dwStatus = status; \
    ((LPD3DSTATUS)(ptr))->drExtent.x1 = _x1; \
    ((LPD3DSTATUS)(ptr))->drExtent.y1 = _y1; \
    ((LPD3DSTATUS)(ptr))->drExtent.x2 = _x2; \
    ((LPD3DSTATUS)(ptr))->drExtent.y2 = _y2; \
    ptr = (void *)(((LPD3DSTATUS) (ptr)) + 1); \
  } while (0)

// OP_NOP size: 4
#define OP_NOP(ptr) \
    PUTD3DINSTRUCTION(D3DOP_TRIANGLE, sizeof(D3DTRIANGLE), 0, ptr)

#define OP_EXIT(ptr) \
    PUTD3DINSTRUCTION(D3DOP_EXIT, 0, 0, ptr)

#define QWORD_ALIGNED(ptr) \
    ( ! ( 0x00000007L & (ULONG) (ptr) ) )

#endif // __D3DMACS_H__


D3DMAIN.CPP

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dmain.cpp
 *
 *  Each of the Direct3D samples must be linked with this file.  It contains
 *  the code which allows them to run in the Windows environment.
 *
 *  A window is created using d3dmain.res which allows the user to select the
 *  Direct3D driver to use and change the render options.  The D3DApp
 *  collection of functions is used to initialize DirectDraw, Direct3D and
 *  keep surfaces and D3D devices available for rendering.
 *
 *  Frame rate and a screen mode information buffer is Blt'ed to the screen
 *  by functions in stats.cpp.
 *
 *  Each sample is executed through the functions: InitScene, InitView,
 *  RenderScene, ReleaseView, ReleaseScene and OverrideDefaults, as described
 *  in d3ddemo.h.  Samples can also read mouse and keyboard input via
 *  SetMouseCallback and SetKeyboardCallback.
 */

#include "d3dmain.h"

/*
 * GLOBAL VARIABLES
 */
D3DAppInfo* d3dapp;    /* Pointer to read only collection of DD and D3D
       objects maintained by D3DApp */
d3dmainglobals myglobs;    /* collection of global variables */

BOOL(*MouseHandler)(UINT, WPARAM, LPARAM);    /* sample's function which traps
 mouse input */
BOOL(*KeyboardHandler)(UINT, WPARAM, LPARAM); /* sample's functions which traps
                                                 keyboard input */

/*
 *  INTERNAL FUNCTION PROTOTYPES
 */
static BOOL AppInit(HINSTANCE hInstance, LPSTR lpCmdLine);
static BOOL CreateD3DApp(LPSTR lpCmdLine);
static BOOL BeforeDeviceDestroyed(LPVOID lpContext);
static BOOL AfterDeviceCreated(int w, int h, LPDIRECT3DVIEWPORT2* lpViewport,
       LPVOID lpContext);
void CleanUpAndPostQuit(void);
static void InitGlobals(void);
static BOOL AppPause(BOOL f);
void ReportD3DAppError(void);
static BOOL RenderLoop(void);
static BOOL RestoreSurfaces();
long FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam,
   LPARAM lParam );

/****************************************************************************/
/*                            WinMain                                       */
/****************************************************************************/
/*
 * Initializes the application then enters a message loop which calls sample's
 * RenderScene until a quit message is received.
 */
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
    int failcount = 0; /* number of times RenderLoop has failed */
    MSGmsg;
    HACCEL hAccelApp;

    hPrevInstance;
    /*
     * Create the window and initialize all objects needed to begin rendering
     */
    if(!AppInit(hInstance, lpCmdLine))
    return FALSE;
    hAccelApp = LoadAccelerators(hInstance, "AppAccel");

    while (!myglobs.bQuit) {
/*
 * Monitor the message queue until there are no pressing
 * messages
 */
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
CleanUpAndPostQuit();
                break;
    }
            if (!myglobs.hWndMain || !TranslateAccelerator(myglobs.hWndMain,
   hAccelApp, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
/*
 * If the app is not minimized, not about to quit, not paused, either the
 * active fullscreen app or in a window and D3D has been initialized,
 * we can render
 */
        } else if (d3dapp->bRenderingIsOK && !d3dapp->bMinimized
   && !d3dapp->bPaused && !myglobs.bQuit
   && (d3dapp->bAppActive || !d3dapp->bFullscreen)) {
    /*
     * If were are not in single step mode or if we are and the
     * bDrawAFrame flag is set, render one frame
     */
            if (!(myglobs.bSingleStepMode && !myglobs.bDrawAFrame)) {
/*
 * Attempt to render a frame, if it fails, take a note.  If
 * rendering fails more than twice, abort execution.
 */
if (!RenderLoop()) {
    ++failcount;
    if (failcount == 3) {
Msg("Rendering has failed too many times.  Aborting execution.\n");
CleanUpAndPostQuit();
break;
    }
}
    }
    /*
     * Reset the bDrawAFrame flag if we are in single step mode
     */
            if (myglobs.bSingleStepMode)
                myglobs.bDrawAFrame = FALSE;
        } else {
            WaitMessage();
}
    }

    DestroyWindow(myglobs.hWndMain);
    return msg.wParam;
}

/****************************************************************************/
/*             D3DApp Initialization and callback functions                 */
/****************************************************************************/
/*
 * AppInit
 * Creates the window and initializes all objects necessary to begin rendering
 */
static BOOL
AppInit(HINSTANCE hInstance, LPSTR lpCmdLine)
{
    WNDCLASS wc;

    /*
     * Initialize the global variables
     */
    InitGlobals();
    myglobs.hInstApp = hInstance;
    /*
     * Register the window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( hInstance, "AppIcon");
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = "AppMenu";
    wc.lpszClassName = "Example";
    if (!RegisterClass(&wc))
        return FALSE;
    /*
     * Create a window with some default settings that may change
     */
    myglobs.hWndMain = CreateWindowEx(
         WS_EX_APPWINDOW,
 "Example",
 "D3D Example",
 WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
 WS_THICKFRAME | WS_MINIMIZEBOX,
         CW_USEDEFAULT, CW_USEDEFAULT,
 START_WIN_SIZE, START_WIN_SIZE,
         NULL,    /* parent window */
 NULL,    /* menu handle */
 hInstance,    /* program handle */
 NULL);    /* create parms */
    if (!myglobs.hWndMain){
    Msg("CreateWindowEx failed");
    return FALSE;
    }
    /*
     * Display the window
     */
    ShowWindow(myglobs.hWndMain, SW_SHOWNORMAL);
    UpdateWindow(myglobs.hWndMain);
    /*
     * Have the example initialize it components which remain constant
     * throughout execution
     */
    if (!InitScene())
        return FALSE;
    /*
     * Call D3DApp to initialize all DD and D3D objects necessary to render.
     * D3DApp will call the device creation callback which will initialize the
     * viewport and the sample's execute buffers.
     */
    if (!CreateD3DApp(lpCmdLine))
return FALSE;

    return TRUE;
}

/*
 * CreateD3DApp
 * Create all DirectDraw and Direct3D objects necessary to begin rendering.
 * Add the list of D3D drivers to the file menu.
 */
static BOOL
CreateD3DApp(LPSTR lpCmdLine)
{
    HMENU hmenu;
    int i;
    LPSTR option;
    BOOL bOnlySystemMemory, bOnlyEmulation;
    DWORD flags;
    Defaults defaults;

    /*
     * Give D3DApp a list of textures to keep track of.
     */
    D3DAppAddTexture("checker.ppm");
    D3DAppAddTexture("tex2.ppm");
    D3DAppAddTexture("tex7.ppm");
    D3DAppAddTexture("win95.ppm");
    /*
     * Parse the command line in seach of one of the following options:
     *     systemmemory  All surfaces should be created in system memory.
     *                   Hardware DD and D3D devices are disabled, but
     *                   debugging during the Win16 lock becomes possible.
     *     emulation     Do not use hardware DD or D3D devices.
     */
    bOnlySystemMemory = FALSE;
    bOnlyEmulation = FALSE;
    option = strtok(lpCmdLine, " -");
    while(option != NULL )   {
        if (!lstrcmp(option, "systemmemory")) {
            bOnlySystemMemory = TRUE;
        } else if (!lstrcmp(option, "emulation")) {
    bOnlyEmulation = TRUE;
} else {
            Msg("Invalid command line options given.\nLegal options: -systemmemory, -emulation\n");
            return FALSE;
        }
        option = strtok(NULL, " -");
    }
    /*
     * Set the flags to pass to the D3DApp creation based on command line
     */
    flags = ((bOnlySystemMemory) ? D3DAPP_ONLYSYSTEMMEMORY : 0) |
    ((bOnlyEmulation) ? (D3DAPP_ONLYD3DEMULATION |
 D3DAPP_ONLYDDEMULATION) : 0);
    /*
     * Create all the DirectDraw and D3D objects neccesary to render.  The
     * AfterDeviceCreated callback function is called by D3DApp to create the
     * viewport and the example's execute buffers.
     */
    if (!D3DAppCreateFromHWND(flags, myglobs.hWndMain, AfterDeviceCreated,
      NULL, BeforeDeviceDestroyed, NULL, &d3dapp)) {
ReportD3DAppError();
return FALSE;
    }
    /*
     * Add the the list of display modes D3DApp found to the mode menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 4);
    for (i = 0; i < d3dapp->NumModes; i++) {
        char ach[80];
        wsprintf(ach,"%dx%dx%d", d3dapp->Mode[i].w, d3dapp->Mode[i].h,
 d3dapp->Mode[i].bpp);
        AppendMenu(hmenu, MF_STRING, MENU_FIRST_MODE+i, ach);
    }
    /*
     * Add the list of D3D drivers D3DApp foudn to the file menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 0);
    for (i = 0; i < d3dapp->NumDrivers; i++) {
        InsertMenu(hmenu, 6 + i, MF_BYPOSITION | MF_STRING,
   MENU_FIRST_DRIVER + i, d3dapp->Driver[i].Name);
    }
    /*
     * Allow the sample to override the default render state and other
     * settings
     */
    if (!D3DAppGetRenderState(&defaults.rs)) {
ReportD3DAppError();
return FALSE;
    }
    lstrcpy(defaults.Name, "D3D Example");
    defaults.bTexturesDisabled = FALSE;
    defaults.bResizingDisabled = myglobs.bResizingDisabled;
    defaults.bClearsOn = myglobs.bClearsOn;
    OverrideDefaults(&defaults);
    myglobs.bClearsOn = defaults.bClearsOn;
    myglobs.bResizingDisabled = defaults.bResizingDisabled;
    /*
     * Apply any changes to the render state
     */
    memcpy(&myglobs.rstate, &defaults.rs, sizeof(D3DAppRenderState));
    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
return FALSE;
    }
    /*
     * If I should begin with textures off, disable them and re-create
     * the view.
     */
    if (defaults.bTexturesDisabled) {
if (!D3DAppDisableTextures(defaults.bTexturesDisabled)) {
    ReportD3DAppError();
    return FALSE;
}
/*
 * Release all objects (ie execute buffers) created by InitView
 */
ReleaseView(d3dapp->lpD3DViewport);
/*
 * Create the sample's execute buffers via InitView
 */
if (!InitView(d3dapp->lpDD, d3dapp->lpD3D, d3dapp->lpD3DDevice,
      d3dapp->lpD3DViewport, d3dapp->NumUsableTextures,
      d3dapp->TextureHandle)) {
    Msg("InitView failed.\n");
    CleanUpAndPostQuit();
    return FALSE;
}
    }

    SetWindowText(myglobs.hWndMain, defaults.Name);

    return TRUE;
}

/*
 * AfterDeviceCreated
 * D3DApp will call this function immediately after the D3D device has been
 * created (or re-created).  D3DApp expects the D3D viewport to be created and
 * returned.  The sample's execute buffers are also created (or re-created)
 * here.
 */
static BOOL
AfterDeviceCreated(int w, int h, LPDIRECT3DVIEWPORT2* lplpViewport, LPVOID lpContext)
{
    HMENU hmenu;
    int i;
    char ach[20];
    LPDIRECT3DVIEWPORT2 lpD3DViewport;
    HRESULT rval;

    /*
     * Create the D3D viewport object
     */
    rval = d3dapp->lpD3D->CreateViewport(&lpD3DViewport, NULL);
    if (rval != D3D_OK) {
    Msg("Create D3D viewport failed.\n%s", D3DAppErrorToString(rval));
CleanUpAndPostQuit();
return FALSE;
    }
    /*
     * Add the viewport to the D3D device
     */
    rval = d3dapp->lpD3DDevice->AddViewport(lpD3DViewport);
    if (rval != D3D_OK) {
    Msg("Add D3D viewport failed.\n%s", D3DAppErrorToString(rval));
CleanUpAndPostQuit();
return FALSE;
    }
    /*
     * Setup the viewport for a reasonable viewing area
     */
    D3DVIEWPORT2 viewData;
    memset(&viewData, 0, sizeof(D3DVIEWPORT2));
    viewData.dwSize = sizeof(D3DVIEWPORT2);
    viewData.dwX = viewData.dwY = 0;
    viewData.dwWidth = w;
    viewData.dwHeight = h;
viewData.dvClipX = -1.0f;
viewData.dvClipWidth = 2.0f;
viewData.dvClipHeight = h * 2.0f / w;
viewData.dvClipY = viewData.dvClipHeight / 2.0f;
viewData.dvMinZ = 0.0f;
viewData.dvMaxZ = 1.0f;
    rval = lpD3DViewport->SetViewport2(&viewData);
    if (rval != D3D_OK) {
    Msg("SetViewport failed.\n%s", D3DAppErrorToString(rval));
CleanUpAndPostQuit();
return FALSE;
    }
    /*
     * Return the viewport to D3DApp so it can use it
     */
    *lplpViewport = lpD3DViewport;
    /*
     * Create the sample's execute buffers via InitView
     */
    if (!InitView(d3dapp->lpDD, d3dapp->lpD3D, d3dapp->lpD3DDevice,
  lpD3DViewport, d3dapp->NumUsableTextures,
  d3dapp->TextureHandle)) {
Msg("InitView failed.\n");
CleanUpAndPostQuit();
return FALSE;
    }
    /*
     * Add the list of texture formats found by D3DApp to the texture menu
     */
    hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 3);
    for (i = 0; i < d3dapp->NumTextureFormats; i++) {
if (d3dapp->TextureFormat[i].bPalettized) {
    wsprintf(ach, "%d-bit Palettized",
     d3dapp->TextureFormat[i].IndexBPP);
} else {
    wsprintf(ach, "%d%d%d RGB", d3dapp->TextureFormat[i].RedBPP,
     d3dapp->TextureFormat[i].GreenBPP,
     d3dapp->TextureFormat[i].BlueBPP);
}
        AppendMenu(hmenu, MF_STRING, MENU_FIRST_FORMAT + i, ach);
    }
    /*
     * Create and initialize the surfaces containing the frame rate and
     * window information
     */
    if (!InitFontAndTextBuffers()) {
Msg("InitFontAndTextBuffers failed.\n");
CleanUpAndPostQuit();
return FALSE;
}

    return TRUE;
}

/*
 * BeforeDeviceDestroyed
 * D3DApp will call this function before the current D3D device is destroyed
 * to give the app the opportunity to destroy objects it has created with the
 * DD or D3D objects.
 */
static BOOL
BeforeDeviceDestroyed(LPVOID lpContext)
{
    HMENU hmenu;
    int i;
    /*
     * Release the font object and buffers containing stats
     */
    ReleaseFontAndTextBuffers();
    /*
     * Release all objects (ie execute buffers) created by InitView
     */
    ReleaseView(d3dapp->lpD3DViewport);
    /*
     * Since we created the viewport it is our responsibility to release
     * it.  Use D3DApp's pointer to it since we didn't save one.
     */
    d3dapp->lpD3DViewport->Release();
    /*
     * Delete the list of texture formats from the texture menu because
     * they are likely to change
     */
    if (myglobs.hWndMain) {
hmenu = GetSubMenu(GetMenu(myglobs.hWndMain), 3);
if (hmenu) {
    for (i = 0; i < d3dapp->NumTextureFormats; i++) {
DeleteMenu(hmenu, MENU_FIRST_FORMAT + i, MF_BYCOMMAND);
    }
}
    }
    return TRUE;
}

/****************************************************************************/
/*                            Rendering loop                                */
/****************************************************************************/
/*
 * RenderLoop
 * Render the next frame and update the window
 */
static BOOL
RenderLoop()
{
    D3DRECT extents[D3DAPP_MAXCLEARRECTS];
    int count;
    /*
     * If all the DD and D3D objects have been initialized we can render
     */
    if (d3dapp->bRenderingIsOK) {
/*
 * Restore any lost surfaces
 */
        if (!RestoreSurfaces()) {
    /*
     * Restoring surfaces sometimes fails because the surfaces cannot
     * yet be restored.  If this is the case, the error will show up
     * somewhere else and we should return success here to prevent
     * unnecessary error's being reported.
     */
    return TRUE;
}
/*
 * Calculate the frame rate
 */
        if (!CalculateFrameRate())
    return FALSE;

        /*
         * Clear the back buffer and Z buffer if enabled.  If bResized is set,
 * clear the entire window.
         */
if (myglobs.bClearsOn) {
    if (!D3DAppClearBackBuffer(myglobs.bResized ?
       D3DAPP_CLEARALL : NULL)) {
ReportD3DAppError();
return FALSE;
    }
}
/*
 * Call the sample's RenderScene to render this frame
 */
{
// init the extent to something wrong
extents[0].x1 = 2048;
extents[0].x2 = 0;
extents[0].y1 = 2048;
extents[0].y2 = 0;
    if (!RenderScene(d3dapp->lpD3DDevice, d3dapp->lpD3DViewport,
     &extents[0])) {
Msg("RenderScene failed.\n");
return FALSE;
    }
    count = 1;
}
/*
 * Blt the frame rate and window stat text to the back buffer
 */
        if (!DisplayFrameRate(&count, &extents[1]))
    return FALSE;
/*
 * Give D3DApp the extents so it can keep track of dirty sections of
 * the back and front buffers.  Look to see if the extent from RenderScene
 * changed any.  If not, skip it's dirty rect.
 */

if (extents[0].x1 == 2048.0) {// nothing changed
if (!D3DAppRenderExtents(count-1, &extents[1], myglobs.bResized ?
 D3DAPP_CLEARALL : NULL)) {
ReportD3DAppError();
return FALSE;
}
} else {
// correct for not quite right extent behavior in some drivers
if (extents[0].x1 != 0)
extents[0].x1 -= 1;
if (extents[0].y1 != 0)
extents[0].y1 -= 1;
if (!D3DAppRenderExtents(count, extents, myglobs.bResized ?
 D3DAPP_CLEARALL : NULL)) {
ReportD3DAppError();
return FALSE;
}
}
/*
 * Blt or flip the back buffer to the front buffer.  Don't report an
 * error if this fails.
 */
D3DAppShowBackBuffer(myglobs.bResized ? D3DAPP_SHOWALL : NULL);

/*
 * Reset the resize flag
 */
        myglobs.bResized = FALSE;
    }
    return TRUE;
}

/*
 * AppPause
 * Pause and unpause the application
 */
static BOOL
AppPause(BOOL f)
{
    /*
     * Flip to the GDI surface and halt rendering
     */
    if (!D3DAppPause(f))
return FALSE;
    /*
     * When returning from a pause, reset the frame rate count
     */
    if (!f) {
        ResetFrameRate();
    }
    return TRUE;
}

/*
 * RestoreSurfaces
 * Restores any lost surfaces.  Returns TRUE if all surfaces are not lost and
 * FALSE if one or more surfaces is lost and can not be restored at the
 * moment.
 */
static BOOL
RestoreSurfaces()
{
    HRESULT d3drval;

    /*
     * Have D3DApp check all the surfaces it's in charge of
     */
    if (!D3DAppCheckForLostSurfaces()) {
            return FALSE;
    }
    /*
     * Check frame rate and info surfaces and re-write them if they
     * were lost.
     */
    if (myglobs.lpFrameRateBuffer->IsLost() == DDERR_SURFACELOST) {
        d3drval = myglobs.lpFrameRateBuffer->Restore();
        if (d3drval != DD_OK) {
            return FALSE;
        }
if (!WriteFrameRateBuffer(0.0f, 0))
    return FALSE;
    }
    if (myglobs.lpInfoBuffer->IsLost() == DDERR_SURFACELOST) {
        d3drval = myglobs.lpInfoBuffer->Restore();
        if (d3drval != DD_OK) {
            return FALSE;
        }
if (!WriteInfoBuffer())
    return FALSE;
    }
    return TRUE;
}

/*************************************************************************
  Windows message handlers
 *************************************************************************/
/*
 * AppAbout
 * About box message handler
 */
BOOL
FAR PASCAL AppAbout(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_COMMAND:
      if (LOWORD(wParam) == IDOK)
        EndDialog(hwnd, TRUE);
      break;

    case WM_INITDIALOG:
      return TRUE;
  }
  return FALSE;
}

/*
 * WindowProc
 * Main window message handler.
 */
long
FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam,
   LPARAM lParam )
{
    int i;
    BOOL bStop;
    LRESULT lresult;

    /*
     * Give D3DApp an opportunity to process any messages it MUST see in order
     * to perform it's function.
     */
    if (!D3DAppWindowProc(&bStop, &lresult, hWnd, message, wParam, lParam)) {
ReportD3DAppError();
CleanUpAndPostQuit();
return 0;
    }

    /*
     * Prevent resizing through this message
     */
    if (message == WM_GETMINMAXINFO && myglobs.bResizingDisabled && !d3dapp->bFullscreen && !d3dapp->bMinimized) {
RECT rc;
GetWindowRect(hWnd, &rc);
((LPMINMAXINFO)lParam)->ptMaxTrackSize.x = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMaxTrackSize.y = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMinTrackSize.x = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMinTrackSize.y = START_WIN_SIZE;
        return 0;
    }

    /*
     * If bStop is set by D3DApp, the app should not process the message but
     * return lresult.
     */
    if (bStop)
return lresult;

    switch( message ) {
        case WM_MOUSEMOVE:
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
    /*
     * Call the sample's MouseHandler if available
     */
            if (!MouseHandler)
                break;
            if ((MouseHandler)(message, wParam, lParam))
                return 1;
            break;
        case WM_KEYDOWN:
    /*
     * Call the sample's keyboard handler if available
     */
            if (!KeyboardHandler)
                break;
            if ((KeyboardHandler)(message, wParam, lParam))
                return 1;
            break;
/*
 * Pause and unpause the app when entering/leaving the menu
 */
        case WM_ENTERMENULOOP:
            AppPause(TRUE);
            break;
        case WM_EXITMENULOOP:
            AppPause(FALSE);
            break;
    case WM_DESTROY:
            myglobs.hWndMain = NULL;
            CleanUpAndPostQuit();
            break;
        case WM_INITMENUPOPUP:
    /*
             * Check and enable the appropriate menu items
     */
    if (d3dapp->ThisDriver.bDoesZBuffer) {
EnableMenuItem((HMENU)wParam, MENU_ZBUFFER, MF_ENABLED);
CheckMenuItem((HMENU)wParam, MENU_ZBUFFER, myglobs.rstate.bZBufferOn ? MF_CHECKED : MF_UNCHECKED);
    } else {
EnableMenuItem((HMENU)wParam, MENU_ZBUFFER, MF_GRAYED);
CheckMenuItem((HMENU)wParam, MENU_ZBUFFER, MF_UNCHECKED);
    }
            CheckMenuItem((HMENU)wParam, MENU_STEP, (myglobs.bSingleStepMode) ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wParam, MENU_GO, (myglobs.bSingleStepMode) ? MF_ENABLED : MF_GRAYED);
            CheckMenuItem((HMENU)wParam, MENU_FLAT, (myglobs.rstate.ShadeMode == D3DSHADE_FLAT) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_GOURAUD, (myglobs.rstate.ShadeMode == D3DSHADE_GOURAUD) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_PHONG, (myglobs.rstate.ShadeMode == D3DSHADE_PHONG) ? MF_CHECKED : MF_UNCHECKED);
    EnableMenuItem((HMENU)wParam, MENU_PHONG, MF_GRAYED);
            CheckMenuItem((HMENU)wParam, MENU_CLEARS, myglobs.bClearsOn ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_POINT, (myglobs.rstate.FillMode == D3DFILL_POINT) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_WIREFRAME, (myglobs.rstate.FillMode == D3DFILL_WIREFRAME) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_SOLID, (myglobs.rstate.FillMode == D3DFILL_SOLID) ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_DITHERING, myglobs.rstate.bDithering ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_SPECULAR, myglobs.rstate.bSpecular ? MF_CHECKED : MF_UNCHECKED);
            EnableMenuItem((HMENU)wParam, MENU_SPECULAR, MF_ENABLED);
            CheckMenuItem((HMENU)wParam, MENU_FOG, myglobs.rstate.bFogEnabled ? MF_CHECKED : MF_UNCHECKED);
            CheckMenuItem((HMENU)wParam, MENU_ANTIALIAS, myglobs.rstate.bAntialiasing ? MF_CHECKED : MF_UNCHECKED);
    if (d3dapp->ThisDriver.bDoesTextures) {
CheckMenuItem((HMENU)wParam, MENU_TEXTURE_TOGGLE, (!d3dapp->bTexturesDisabled) ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_TEXTURE_TOGGLE, MF_ENABLED);
EnableMenuItem((HMENU)wParam, MENU_TEXTURE_SWAP, (d3dapp->bTexturesDisabled) ? MF_GRAYED : MF_ENABLED);
CheckMenuItem((HMENU)wParam, MENU_PERSPCORRECT, myglobs.rstate.bPerspCorrect ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_PERSPCORRECT, (d3dapp->bTexturesDisabled) ? MF_GRAYED : MF_ENABLED);
CheckMenuItem((HMENU)wParam, MENU_POINT_FILTER, (myglobs.rstate.TextureFilter == D3DFILTER_NEAREST) ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_POINT_FILTER, (d3dapp->bTexturesDisabled) ? MF_GRAYED : MF_ENABLED);
CheckMenuItem((HMENU)wParam, MENU_LINEAR_FILTER, (myglobs.rstate.TextureFilter == D3DFILTER_LINEAR) ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_LINEAR_FILTER, (d3dapp->bTexturesDisabled) ? MF_GRAYED : MF_ENABLED);
for (i = 0; i < d3dapp->NumTextureFormats; i++) {
    CheckMenuItem((HMENU)wParam, MENU_FIRST_FORMAT + i, (i == d3dapp->CurrTextureFormat) ? MF_CHECKED : MF_UNCHECKED);
    EnableMenuItem((HMENU)wParam, MENU_FIRST_FORMAT + i, (d3dapp->bTexturesDisabled) ? MF_GRAYED : MF_ENABLED);
}
    } else {
EnableMenuItem((HMENU)wParam, MENU_TEXTURE_SWAP, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_TEXTURE_TOGGLE, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_POINT_FILTER, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_LINEAR_FILTER, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_PERSPCORRECT, MF_GRAYED);
    }
    if (d3dapp->bIsPrimary) {
CheckMenuItem((HMENU)wParam, MENU_FULLSCREEN, d3dapp->bFullscreen ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_FULLSCREEN, d3dapp->bFullscreen && !d3dapp->ThisDriver.bCanDoWindow ? MF_GRAYED : MF_ENABLED);
EnableMenuItem((HMENU)wParam, MENU_NEXT_MODE, (!d3dapp->bFullscreen) ? MF_GRAYED : MF_ENABLED);
EnableMenuItem((HMENU)wParam, MENU_PREVIOUS_MODE, (!d3dapp->bFullscreen) ? MF_GRAYED : MF_ENABLED);
    } else {
EnableMenuItem((HMENU)wParam, MENU_FULLSCREEN, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_NEXT_MODE, MF_GRAYED);
EnableMenuItem((HMENU)wParam, MENU_PREVIOUS_MODE, MF_GRAYED);
    }
    for (i = 0; i < d3dapp->NumModes; i++) {
CheckMenuItem((HMENU)wParam, MENU_FIRST_MODE + i, (i == d3dapp->CurrMode) ? MF_CHECKED : MF_UNCHECKED);
EnableMenuItem((HMENU)wParam, MENU_FIRST_MODE + i, (d3dapp->Mode[i].bThisDriverCanDo) ? MF_ENABLED : MF_GRAYED);
    }
            for (i = 0; i < d3dapp->NumDrivers; i++) {
                CheckMenuItem((HMENU)wParam, MENU_FIRST_DRIVER + i, (i == d3dapp->CurrDriver) ? MF_CHECKED : MF_UNCHECKED);
            }
            break;
case WM_GETMINMAXINFO:
    /*
     * Some samples don't like being resized, such as those which use
     * screen coordinates (TLVERTEXs).
     */
    if (myglobs.bResizingDisabled) {
((LPMINMAXINFO)lParam)->ptMaxTrackSize.x = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMaxTrackSize.y = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMinTrackSize.x = START_WIN_SIZE;
((LPMINMAXINFO)lParam)->ptMinTrackSize.y = START_WIN_SIZE;
return 0;
    }
    break;
        case WM_COMMAND:
            switch(LOWORD(wParam)) {
                case MENU_ABOUT:
                    AppPause(TRUE);
                    DialogBox(myglobs.hInstApp, "AppAbout", myglobs.hWndMain, (DLGPROC)AppAbout);
                    AppPause(FALSE);
                    break;
                case MENU_EXIT:
            CleanUpAndPostQuit();
            break;
                case MENU_STEP:
    /*
     * Begin single step more or draw a frame if in single
     * step mode
     */
                    if (!myglobs.bSingleStepMode) {
                        myglobs.bSingleStepMode = TRUE;
                        myglobs.bDrawAFrame = TRUE;
                    } else if (!myglobs.bDrawAFrame) {
                        myglobs.bDrawAFrame = TRUE;
                    }
                    break;
                case MENU_GO:
    /*
     * Exit single step mode
     */
                    if (myglobs.bSingleStepMode) {
                        myglobs.bSingleStepMode = FALSE;
                        ResetFrameRate();
                    }
                    break;
                case MENU_STATS:
    /*
     * Toggle output of frame rate and window info
     */
                    if ((myglobs.bShowFrameRate) && (myglobs.bShowInfo)) {
                        myglobs.bShowFrameRate = FALSE;
                        myglobs.bShowInfo = FALSE;
                        break;
                    }
                    if ((!myglobs.bShowFrameRate) && (!myglobs.bShowInfo)) {
                        myglobs.bShowFrameRate = TRUE;
                        break;
                    }
                    myglobs.bShowInfo = TRUE;

break;
        case MENU_FULLSCREEN:
    if (d3dapp->bFullscreen) {
/*
 * Return to a windowed mode.  Let D3DApp decide which
 * D3D driver to use in case this one cannot render to
 * the Windows display depth
 */
if (!D3DAppWindow(D3DAPP_YOUDECIDE, D3DAPP_YOUDECIDE)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
    break;
}
    } else {
/*
 * Enter the current fullscreen mode.  D3DApp may
 * resort to another mode if this driver cannot do
 * the currently selected mode.
 */
if (!D3DAppFullscreen(d3dapp->CurrMode)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
    break;
}
    }
                break;
/*
 * Texture filter method selection
 */
                case MENU_POINT_FILTER:
                    if (myglobs.rstate.TextureFilter == D3DFILTER_NEAREST)
                        break;
                    myglobs.rstate.TextureFilter = D3DFILTER_NEAREST;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_LINEAR_FILTER:
                    if (myglobs.rstate.TextureFilter == D3DFILTER_LINEAR)
                        break;
                    myglobs.rstate.TextureFilter = D3DFILTER_LINEAR;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
/*
 * Shading selection
 */
                case MENU_FLAT:
                    if (myglobs.rstate.ShadeMode == D3DSHADE_FLAT)
                        break;
                    myglobs.rstate.ShadeMode = D3DSHADE_FLAT;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_GOURAUD:
                    if (myglobs.rstate.ShadeMode == D3DSHADE_GOURAUD)
                        break;
                    myglobs.rstate.ShadeMode = D3DSHADE_GOURAUD;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
                case MENU_PHONG:
                    if (myglobs.rstate.ShadeMode == D3DSHADE_PHONG)
                        break;
                    myglobs.rstate.ShadeMode = D3DSHADE_PHONG;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
/*
 * Fill mode selection
 */
                case MENU_POINT:
                    if (myglobs.rstate.FillMode == D3DFILL_POINT)
                        break;
                    myglobs.rstate.FillMode = D3DFILL_POINT;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
                case MENU_WIREFRAME:
                    if (myglobs.rstate.FillMode == D3DFILL_WIREFRAME)
                        break;
                    myglobs.rstate.FillMode = D3DFILL_WIREFRAME;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
                case MENU_SOLID:
                    if (myglobs.rstate.FillMode == D3DFILL_SOLID)
                        break;
                    myglobs.rstate.FillMode = D3DFILL_SOLID;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
                case MENU_PERSPCORRECT:
    /*
     * Toggle perspective correction
     */
                    myglobs.rstate.bPerspCorrect =
!myglobs.rstate.bPerspCorrect;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
                    break;
                case MENU_CLEARS:
    /*
     * Toggle the clearing the the back buffer and Z-buffer
     * and set the resized flag to clear the entire window
     */
                    myglobs.bClearsOn = !myglobs.bClearsOn;
                    if (myglobs.bClearsOn)
                        myglobs.bResized = TRUE;
                    break;
                case MENU_ZBUFFER:
    /*
     * Toggle the use of a Z-buffer
     */
                    myglobs.rstate.bZBufferOn = !myglobs.rstate.bZBufferOn;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_DITHERING:
    /*
     * Toggle dithering
     */
                    myglobs.rstate.bDithering = !myglobs.rstate.bDithering;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_SPECULAR:
    /*
     * Toggle specular highlights
     */
                    myglobs.rstate.bSpecular = !myglobs.rstate.bSpecular;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_FOG:
    /*
     * Toggle fog
     */
                    myglobs.rstate.bFogEnabled = !myglobs.rstate.bFogEnabled;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_ANTIALIAS:
    /*
     * Toggle anti-aliasing
     */
                    myglobs.rstate.bAntialiasing =
!myglobs.rstate.bAntialiasing;
                    if (!D3DAppSetRenderState(&myglobs.rstate)) {
ReportD3DAppError();
break;
    }
    break;
                case MENU_TEXTURE_TOGGLE:
    /*
     * Release the sample's execute buffers, toggle the
     * texture disabled state and recreate them.
     */
    ReleaseView(d3dapp->lpD3DViewport);
    D3DAppDisableTextures(!d3dapp->bTexturesDisabled);
    {
if (!InitView(d3dapp->lpDD, d3dapp->lpD3D,
      d3dapp->lpD3DDevice,
      d3dapp->lpD3DViewport,
      d3dapp->NumUsableTextures,
      d3dapp->TextureHandle)) {
    Msg("InitView failed.\n");
    CleanUpAndPostQuit();
    break;
}
    }
    myglobs.bResized = TRUE;
    break;
case MENU_TEXTURE_SWAP:
    /*
     * Swap textures using the load command
     */
    if (!D3DAppSwapTextures()) {
ReportD3DAppError();
break;
    }
    /*
     * Just in case we have a texture background
     */
    myglobs.bResized = TRUE;
    break;
case MENU_NEXT_MODE:
    /*
     * Enter the next usable fullscreen mode
     */
    i = d3dapp->CurrMode;
    do {
++i;
if (i >= d3dapp->NumModes)
    i = 0;
if (!d3dapp->Mode[i].bThisDriverCanDo)
    continue;
else {
    if (!D3DAppFullscreen(i)) {
ReportD3DAppError();
CleanUpAndPostQuit();
    }
    break;
}
    } while(i != d3dapp->CurrMode);
    break;
case MENU_PREVIOUS_MODE:
    /*
     * Enter the previous usable fullscreen mode
     */
    i = d3dapp->CurrMode;
    do {
--i;
if (i < 0)
    i = d3dapp->NumModes - 1;
if (!d3dapp->Mode[i].bThisDriverCanDo)
    continue;
else {
    if (!D3DAppFullscreen(i)) {
ReportD3DAppError();
CleanUpAndPostQuit();
    }
    break;
}
    } while(i != d3dapp->CurrMode);
                    break;
            }
            if (   LOWORD(wParam) >= MENU_FIRST_FORMAT
&& LOWORD(wParam) < MENU_FIRST_FORMAT +
    D3DAPP_MAXTEXTUREFORMATS
&& d3dapp->CurrTextureFormat !=
    LOWORD(wParam) - MENU_FIRST_FORMAT) {
/*
 * Release the sample's execute buffers, change the texture
 * format and recreate the view.
 */
ReleaseView(d3dapp->lpD3DViewport);
if (!D3DAppChangeTextureFormat(LOWORD(wParam) -
         MENU_FIRST_FORMAT)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
}
{
    if (!InitView(d3dapp->lpDD, d3dapp->lpD3D,
  d3dapp->lpD3DDevice, d3dapp->lpD3DViewport,
  d3dapp->NumUsableTextures,
  d3dapp->TextureHandle)) {
Msg("InitView failed.\n");
CleanUpAndPostQuit();
break;
    }
}
                ResetFrameRate();
            }
            if (   LOWORD(wParam) >= MENU_FIRST_DRIVER
&& LOWORD(wParam) < MENU_FIRST_DRIVER + D3DAPP_MAXD3DDRIVERS
&& d3dapp->CurrDriver != LOWORD(wParam) - MENU_FIRST_DRIVER) {
/*
 * Change the D3D driver
 */
if (!D3DAppChangeDriver(LOWORD(wParam) - MENU_FIRST_DRIVER,
NULL)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
}
            }
            if (   LOWORD(wParam) >= MENU_FIRST_MODE
&& LOWORD(wParam) < MENU_FIRST_MODE+100) {
/*
 * Switch to the selected fullscreen mode
 */
if (!D3DAppFullscreen(LOWORD(wParam) - MENU_FIRST_MODE)) {
    ReportD3DAppError();
    CleanUpAndPostQuit();
}
            }
    /*
     * Whenever we receive a command in single step mode, draw a frame
     */
    if (myglobs.bSingleStepMode)
myglobs.bDrawAFrame = TRUE;
            return 0L;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

/****************************************************************************/
/*                 Setting up callbacks for examples                        */
/****************************************************************************/
 /*
  * SetMouseCallback
  * Called in an example to set a callback function for all WM messages
  * dealing with the mouse.  The handler should return whether or not
  * it handled the message.
  */
BOOL
SetMouseCallback(BOOL(*Handler)(UINT, WPARAM, LPARAM))
{
    MouseHandler = Handler;
    return TRUE;
}

/*
 * SetKeyboardCallback
 * Called in an example to set a callback function for all WM messages
 * dealing with the keyboard. The handler should return whether or not it
 * handled the message.
 */
BOOL
SetKeyboardCallback(BOOL(*Handler)(UINT, WPARAM, LPARAM)) {

    KeyboardHandler = Handler;
    return TRUE;
}

/****************************************************************************/
/*          Initialization, error reporting and release functions.          */
/****************************************************************************/
/*
 * InitGlobals
 * Called once at program initialization to initialize global variables.
 */
static void
InitGlobals(void)
{
    d3dapp = NULL;
    memset(&myglobs.rstate, 0, sizeof(myglobs.rstate));
    memset(&myglobs, 0, sizeof(myglobs));
    myglobs.bClearsOn = TRUE;
    myglobs.bShowFrameRate = TRUE;
    myglobs.bShowInfo = TRUE;
    MouseHandler = NULL;
    KeyboardHandler = NULL;
}

/*
 * CleanUpAndPostQuit
 * Release all D3D objects, post a quit message and set the bQuit flag
 */
void
CleanUpAndPostQuit(void)
{
    if (myglobs.bQuit)
return;
    if (!D3DAppDestroy())
ReportD3DAppError();
    ReleaseScene();
    myglobs.bQuit = TRUE;
    PostQuitMessage( 0 );
}

/*
 * ReportD3DAppError
 * Reports an error during a d3d app call.
 */
void
ReportD3DAppError(void)
{
    Msg("%s", D3DAppLastErrorString());
}

/* Msg
 * Message output for error notification.
 */
void __cdecl
Msg( LPSTR fmt, ... )
{
    char buff[256];
    va_list va;

    va_start( va, fmt );
    wvsprintf( buff, fmt, va );
    va_end( va );
    lstrcat(buff, "\r\n");
    AppPause(TRUE);
    if (d3dapp && d3dapp->bFullscreen)
SetWindowPos(myglobs.hWndMain, HWND_NOTOPMOST, 0, 0, 0, 0,
     SWP_NOSIZE | SWP_NOMOVE);
    MessageBox( myglobs.hWndMain, buff, "D3D Example Message", MB_OK );
    if (d3dapp && d3dapp->bFullscreen)
SetWindowPos(myglobs.hWndMain, HWND_TOPMOST, 0, 0, 0, 0,
     SWP_NOSIZE | SWP_NOMOVE);
    AppPause(FALSE);
}


D3DMATH.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dmath.h
 *
 ***************************************************************************/
#ifndef __D3DMATH_H__
#define __D3DMATH_H__

#include <math.h>

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Normalises the vector v
 */
LPD3DVECTOR D3DVECTORNormalise(LPD3DVECTOR v);

/*
 * Calculates cross product of a and b.
 */
LPD3DVECTOR D3DVECTORCrossProduct(LPD3DVECTOR lpd, LPD3DVECTOR lpa, LPD3DVECTOR lpb);

/*
 * lpDst = lpSrc1 * lpSrc2
 * lpDst can be equal to lpSrc1 or lpSrc2
 */
LPD3DMATRIX MultiplyD3DMATRIX(LPD3DMATRIX lpDst, LPD3DMATRIX lpSrc1,
                              LPD3DMATRIX lpSrc2);
/*
 * -1 d = a
 */
LPD3DMATRIX D3DMATRIXInvert(LPD3DMATRIX d, LPD3DMATRIX a);

/*
 * Set the rotation part of a matrix such that the vector lpD is the new
 * z-axis and lpU is the new y-axis.
 */
LPD3DMATRIX D3DMATRIXSetRotation(LPD3DMATRIX lpM, LPD3DVECTOR lpD, LPD3DVECTOR lpU);

/*
 * Calculates a point along a B-Spline curve defined by four points. p
 * n output, contain the point. t Position
 * along the curve between p2 and p3.  This position is a float between 0
 * and 1. p1, p2, p3, p4    Points defining spline curve. p, at parameter
 * t along the spline curve
 */
void spline(LPD3DVECTOR p, float t, LPD3DVECTOR p1, LPD3DVECTOR p2,
            LPD3DVECTOR p3, LPD3DVECTOR p4);

#ifdef __cplusplus
};
#endif

#endif // __D3DMATH_H__


D3DRES.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: ddres.h
 *
 ***************************************************************************/

#define MENU_ABOUT                      1
#define MENU_EXIT                       2
#define MENU_TEXTURE_TOGGLE             3
#define MENU_TEXTURE_SWAP               4
#define MENU_FULLSCREEN                 7
#define MENU_NEXT_MODE                  8
#define MENU_PREVIOUS_MODE              9
#define MENU_STATS                      10
#define MENU_FLAT                       12
#define MENU_GOURAUD                    13
#define MENU_PHONG                      14
#define MENU_ZBUFFER                    15
#define MENU_PERSPCORRECT               16
#define MENU_POINT_FILTER               17
#define MENU_LINEAR_FILTER              18
#define MENU_CLEARS                     19
#define MENU_POINT                      20
#define MENU_WIREFRAME                  21
#define MENU_SOLID                      22
#define MENU_SPECULAR                   23
#define MENU_DITHERING                  24
#define MENU_FOG                        25
#define MENU_ANTIALIAS                  26
#define MENU_STEP                       27
#define MENU_GO                         28
#define MENU_FIRST_FORMAT               40
#define MENU_FIRST_DRIVER               100
#define MENU_FIRST_MODE                 200

#define IDC_STATIC                     -1


D3DSPHR.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: d3dsphr.h
 *
 ***************************************************************************/
#ifndef __D3DSPHR_H__
#define __D3DSPHR_H__

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Generates a sphere around the y-axis centered at the origin including
 * normals and texture coordiantes.  Returns TRUE on success and FALSE on
 * failure.
 *     sphere_r     Radius of the sphere.
 *     num_rings    Number of full rings not including the top and bottom
 *    caps.
 *     num_sections Number of sections each ring is divided into.  Each
 *    section contains two triangles on full rings and one
 *    on top and bottom caps.
 *     sx, sy, sz   Scaling along each axis.  Set each to 1.0 for a
 *    perfect sphere.
 *     plpv         On exit points to the vertices of the sphere.  The
 *    function allocates this space.  Not allocated if
 *    function fails.
 *     plptri       On exit points to the triangles of the sphere which
 *    reference vertices in the vertex list.  The function
 *    allocates this space. Not allocated if function fails.
 *     pnum_v       On exit contains the number of vertices.
 *     pnum_tri     On exit contains the number of triangles.
 */
BOOL
GenerateSphere(float sphere_r, int num_rings, int num_sections, float sx,
       float sy, float sz, LPD3DVERTEX* plpv,
       LPD3DTRIANGLE* plptri, int* pnum_v, int* pnum_tri);

#ifdef __cplusplus
};
#endif

#endif // __D3DSPHR_H__


DDCALLS.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: ddcalls.c
 *
 *  Manages DirectDraw objects needed for rendering.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"

/***************************************************************************/
/*                         Direct Draw Creation                            */
/***************************************************************************/
/*
 * D3DAppIDDEnumCallback
 * Callback function used during enumeration of DirectDraw drivers.
 * During enumeration, if a 3D capable hardware device is found, it is
 * created and *(LPDIRECTDRAW*)lpContext is set to it.  Otherwise, does
 * nothing.
 */
BOOL FAR PASCAL D3DAppIDDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc,
      LPSTR lpDriverName, LPVOID lpContext)
{
    LPDIRECTDRAW lpDD;
    DDCAPS DriverCaps, HELCaps;

    /*
     * A NULL GUID* indicates the DirectDraw HEL which we are not interested
     * in at the moment.
     */
    if (lpGUID) {
/*
 * Create the DirectDraw device using this driver.  If it fails,
 * just move on to the next driver.
 */
if (FAILED(DirectDrawCreate(lpGUID, &lpDD, NULL))) {
    return DDENUMRET_OK;
}
/*
 * Get the capabilities of this DirectDraw driver.  If it fails,
 * just move on to the next driver.
 */
memset(&DriverCaps, 0, sizeof(DDCAPS));
DriverCaps.dwSize = sizeof(DDCAPS);
memset(&HELCaps, 0, sizeof(DDCAPS));
HELCaps.dwSize = sizeof(DDCAPS);
if (FAILED(lpDD->lpVtbl->GetCaps(lpDD, &DriverCaps, &HELCaps))) {
    lpDD->lpVtbl->Release(lpDD);
    return DDENUMRET_OK;
}
if (DriverCaps.dwCaps & DDCAPS_3D) {
    /*
     * We have found a 3d hardware device.  Return the DD object
     * and stop enumeration.
     */
    d3dappi.bIsPrimary = FALSE;
    *(LPDIRECTDRAW*)lpContext = lpDD;
    return DDENUMRET_CANCEL;
}
lpDD->lpVtbl->Release(lpDD);
    }
    return DDENUMRET_OK;
}

/*
 * D3DAppICreateDD
 * Creates the DirectDraw device and saves the current palette. If a 3D
 * capable DD driver is available, use it as the DD device, otherwise, use
 * the HEL.  It is assumed that a 3D capable DD hardware driver is not the
 * primary device and hence cannot operate in a window (ie it's a fullscreen
 * only device displaying on a second monitor).  Valid flags:
 *     D3DAPP_ONLYDDEMULATION    Always use the DirectDraw HEL
 */
BOOL
D3DAppICreateDD(DWORD flags)
{
    HDC hdc;
    int i;
    LPDIRECTDRAW lpDD = NULL;

    /*
     * If we aren't forced to use the DirectDraw HEL, search for a 3D capable
     * DirectDraw hardware driver and create it.
     */
    if (!(flags & D3DAPP_ONLYDDEMULATION)) {
LastError = DirectDrawEnumerate(D3DAppIDDEnumCallback, &lpDD);
if (LastError != DD_OK) {
    D3DAppISetErrorString("DirectDrawEnumerate failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    }
    if (!lpDD) {
/*
 * If we haven't created a hardware DD device by now, resort to HEL
 */
d3dappi.bIsPrimary = TRUE;
LastError = DirectDrawCreate(NULL, &d3dappi.lpDD, NULL);
if (LastError != DD_OK) {
    D3DAppISetErrorString("DirectDrawCreate failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    } else {
d3dappi.lpDD = lpDD;
    }
    /*
     * Save the original palette for when we are paused.  Just in case we
     * start in a fullscreen mode, put them in ppe.
     */
    hdc = GetDC(NULL);
    GetSystemPaletteEntries(hdc, 0, (1 << 8),
    (LPPALETTEENTRY)(&Originalppe[0]));
    for (i = 0; i < 256; i++)
        ppe[i] = Originalppe[i];
    ReleaseDC(NULL, hdc);
    return TRUE;
}

/***************************************************************************/
/*                   Enumerating the display modes                         */
/***************************************************************************/
/*
 * EnumDisplayModesCallback
 * Callback to save the display mode information.
 */
static HRESULT
CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID lpContext)
{
    /*
     * Very large resolutions cause problems on some hardware.  They are also
     * not very useful for real-time rendering.  We have chosen to disable
     * them by not reporting them as available.
     */
    if (pddsd->dwWidth > 1024 || pddsd->dwHeight > 768)
return DDENUMRET_OK;
    /*
     * Save this mode at the end of the mode array and increment mode count
     */
    d3dappi.Mode[d3dappi.NumModes].w = pddsd->dwWidth;
    d3dappi.Mode[d3dappi.NumModes].h = pddsd->dwHeight;
    d3dappi.Mode[d3dappi.NumModes].bpp = pddsd->ddpfPixelFormat.dwRGBBitCount;
    d3dappi.Mode[d3dappi.NumModes].bThisDriverCanDo = FALSE;
    d3dappi.NumModes++;
    if (d3dappi.NumModes == D3DAPP_MAXMODES)
return DDENUMRET_CANCEL;
    else
return DDENUMRET_OK;
}

/*
 * CompareModes
 * Compare two display modes during sorting.  Modes are sorted by depth and
 * then resolution.
 */
static int
CompareModes(const void* element1, const void* element2)
{
    D3DAppMode *lpMode1, *lpMode2;

    lpMode1 = (D3DAppMode*)element1;
    lpMode2 = (D3DAppMode*)element2;

    if (lpMode1->bpp > lpMode2->bpp)
        return -1;
    else if (lpMode2->bpp > lpMode1->bpp)
        return 1;
    else if (lpMode1->w > lpMode2->w)
        return -1;
    else if (lpMode2->w > lpMode1->w)
        return 1;
    else if (lpMode1->h > lpMode2->h)
        return -1;
    else if (lpMode2->h > lpMode1->h)
        return 1;
    else
        return 0;
}

/*
 * EnumerateDisplayModes
 * Generates the list of available display modes.
 */
BOOL
D3DAppIEnumDisplayModes(void)
{
    int i;
    /*
     * Get a list of available display modes from DirectDraw
     */
    d3dappi.NumModes = 0;
    LastError = d3dappi.lpDD->lpVtbl->EnumDisplayModes(d3dappi.lpDD, 0, NULL,
0, EnumDisplayModesCallback);
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("EnumDisplayModes failed.\n%s",
      D3DAppErrorToString(LastError));
d3dappi.NumModes = 0;
        return FALSE;
    }
    /*
     * Sort the list of display modes
     */
    qsort((void *)&d3dappi.Mode[0], (size_t)d3dappi.NumModes, sizeof(D3DAppMode),
          CompareModes);
    /*
     * Pick a default display mode.  640x480x16 is a very good mode for
     * rendering, so choose it over all others.  Otherwise, just take the
     * first one.  This selection may be overriden later if a driver is
     * created which cannot render in this mode.
     */
    d3dappi.CurrMode = 0;
    for (i = 0; i < d3dappi.NumModes; i++) {
if (d3dappi.Mode[i].w == 640 && d3dappi.Mode[i].h == 480 &&
    d3dappi.Mode[i].bpp == 16)
    d3dappi.CurrMode = i;
    }
    memcpy(&d3dappi.ThisMode, &d3dappi.Mode[d3dappi.CurrMode],
   sizeof(D3DAppMode));
    return TRUE;
}

/***************************************************************************/
/*               Creating Front and Back Buffers (and misc surf funcs)     */
/***************************************************************************/
/*
 * D3DAppICreateSurface
 * Create a DirectDraw Surface of the given description.  Using this function
 * ensures that all surfaces end up in system memory if that option was set.
 * Returns the result of the CreateSurface call.
 */
HRESULT
D3DAppICreateSurface(LPDDSURFACEDESC lpDDSurfDesc,
                LPDIRECTDRAWSURFACE FAR *lpDDSurface) {
    HRESULT result;
    if (d3dappi.bOnlySystemMemory)
        lpDDSurfDesc->ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    result = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, lpDDSurfDesc,
 lpDDSurface, NULL);
    return result;
}

/*
 * D3DAppIGetSurfDesc
 * Get the description of the given surface.  Returns the result of the
 * GetSurfaceDesc call.
 */
HRESULT
D3DAppIGetSurfDesc(LPDDSURFACEDESC lpDDSurfDesc,LPDIRECTDRAWSURFACE lpDDSurf)
{
    HRESULT result;
    memset(lpDDSurfDesc, 0, sizeof(DDSURFACEDESC));
    lpDDSurfDesc->dwSize = sizeof(DDSURFACEDESC);
    result = lpDDSurf->lpVtbl->GetSurfaceDesc(lpDDSurf, lpDDSurfDesc);
    return result;
}

/*
 * D3DAppICreateBuffers
 * Creates the front and back buffers for the window or fullscreen case
 * depending on the bFullscreen flag.  In the window case, bpp is ignored.
 */
BOOL
D3DAppICreateBuffers(HWND hwnd, int w, int h, int bpp, BOOL bFullscreen, BOOL bIsHardware)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;

    /*
     * Release any old objects that might be lying around.  This should have
     * already been taken care of, but just in case...
     */
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    /*
     * The size of the buffers is going to be w x h, so record it now
     */
    if (w < D3DAPP_WINDOWMINIMUM)
w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
h = D3DAPP_WINDOWMINIMUM;
    szBuffers.cx = w;
    szBuffers.cy = h;

    if (bFullscreen) {
        /*
         * Create a complex flipping surface for fullscreen mode with one
 * back buffer.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
        DDSCAPS_3DDEVICE | DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;
if (bIsHardware)
    ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpFrontBuffer);
    if(LastError != DD_OK) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
    } else {
D3DAppISetErrorString("CreateSurface for fullscreen flipping surface failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
}
/*
 * Obtain a pointer to the back buffer surface created above so we
 * can use it later.  For now, just check to see if it ended up in
 * video memory (FYI).
 */
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    LastError = d3dappi.lpFrontBuffer->lpVtbl->GetAttachedSurface(d3dappi.lpFrontBuffer, &ddscaps, &d3dappi.lpBackBuffer);
    if(LastError != DD_OK) {
            D3DAppISetErrorString("GetAttachedSurface failed to get back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to get surface description of back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
        d3dappi.bBackBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    }
    else {
        /*
         * In the window case, create a front buffer which is the primary
 * surface and a back buffer which is an offscreen plane surface.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
        ddsd.dwSize = sizeof(DDSURFACEDESC);
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
/*
 * If we specify system memory when creating a primary surface, we
 * won't get the actual primary surface in video memory.  So, don't
 * use D3DAppICreateSurface().
 */
LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD,
&ddsd, &d3dappi.lpFrontBuffer, NULL);
        if(LastError != DD_OK ) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    } else {
D3DAppISetErrorString("CreateSurface for window front buffer failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
        }
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth = w;
    ddsd.dwHeight = h;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
if (bIsHardware)
    ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
else
    ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpBackBuffer);
    if (LastError != DD_OK) {
    if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    } else {
D3DAppISetErrorString("CreateSurface for window back buffer failed.\n%s",
      D3DAppErrorToString(LastError));
    }
            goto exit_with_error;
}
/*
 * Check to see if the back buffer is in video memory (FYI).
 */
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to get surface description for back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
d3dappi.bBackBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
        /*
         * Create the DirectDraw Clipper object and attach it to the window
 * and front buffer.
         */
        LastError = d3dappi.lpDD->lpVtbl->CreateClipper(d3dappi.lpDD, 0,
&lpClipper, NULL);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("CreateClipper failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    LastError = lpClipper->lpVtbl->SetHWnd(lpClipper, 0, hwnd);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to window failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    LastError =
     d3dappi.lpFrontBuffer->lpVtbl->SetClipper(d3dappi.lpFrontBuffer,
       lpClipper);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
    }

    D3DAppIClearBuffers();
    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpFrontBuffer);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(lpClipper);
    return FALSE;
}

/*
 * D3DAppICheckForPalettized
 * If the front/back buffer is palettized, we need to create a palette.
 */
BOOL
D3DAppICheckForPalettized(void)
{
    DDSURFACEDESC ddsd;
    /*
     * Get the back buffer surface description and check to see if it's
     * palettized
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to get surface description for back buffer for palettizing.\n%s",
      D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    bPrimaryPalettized =
(ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) ? TRUE : FALSE;

    if (bPrimaryPalettized) {
int i;
/*
 * Get the current palette.
 */
 HDC hdc = GetDC(NULL);
GetSystemPaletteEntries(hdc, 0, (1 << 8), ppe);
ReleaseDC(NULL, hdc);
/*
 * Change the flags on the palette entries to allow D3D to change
 * some of them.  In the window case, we must not change the top and
 * bottom ten (system colors), but in a fullscreen mode we can have
 * all but the first and last.
 */
        if (!d3dappi.bFullscreen) {
    for (i = 0; i < 10; i++) ppe[i].peFlags = D3DPAL_READONLY;
    for (i = 10; i < 256 - 10; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
    for (i = 256 - 10; i < 256; i++) ppe[i].peFlags = D3DPAL_READONLY;
        } else {
    ppe[0].peFlags = D3DPAL_READONLY;
    for (i = 1; i < 255; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
    ppe[255].peFlags = D3DPAL_READONLY;
}
/*
 * Create a palette using the old colors and new flags
 */
LastError = d3dappi.lpDD->lpVtbl->CreatePalette(d3dappi.lpDD,
   DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
   ppe, &lpPalette, NULL);
if (LastError != DD_OK) {
    D3DAppISetErrorString("CreatePalette failed.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
/*
 * Set this as the front and back buffers' palette
 */
LastError =
       d3dappi.lpBackBuffer->lpVtbl->SetPalette(d3dappi.lpBackBuffer,
        lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on back buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
LastError =
     d3dappi.lpFrontBuffer->lpVtbl->SetPalette(d3dappi.lpFrontBuffer,
       lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on front buffer.\n%s",
  D3DAppErrorToString(LastError));
            goto exit_with_error;
}
/*
 * The palette is now valid, so set it again on anyt WM_ACTIVATE
 */
bPaletteActivate = TRUE;
    }
    return TRUE;
exit_with_error:
    RELEASE(lpPalette);
    return FALSE;
}

/***************************************************************************/
/*                           Creation of Z-Buffer                          */
/***************************************************************************/
/*
 * D3DAppICreateZBuffer
 * Create a Z-Buffer of the appropriate depth and attach it to the back
 * buffer.
 */
BOOL
D3DAppICreateZBuffer(int w, int h, int driver)
{
    DDSURFACEDESC ddsd;
    DWORD devDepth;
    /*
     * Release any Z-Buffer that might be around just in case.
     */
    RELEASE(d3dappi.lpZBuffer);

    /*
     * If this driver does not do z-buffering, don't create a z-buffer
     */
    if (!d3dappi.Driver[driver].bDoesZBuffer)
return TRUE;

    memset(&ddsd, 0 ,sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS |
   DDSD_ZBUFFERBITDEPTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
    ddsd.dwHeight = h;
    ddsd.dwWidth = w;
    /*
     * If this is a hardware D3D driver, the Z-Buffer MUST end up in video
     * memory.  Otherwise, it MUST end up in system memory.
     */
    if (d3dappi.Driver[driver].bIsHardware)
ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    else
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    /*
     * Get the Z buffer bit depth from this driver's D3D device description
     */
    devDepth = d3dappi.Driver[driver].Desc.dwDeviceZBufferBitDepth;
    if (devDepth & DDBD_32)
ddsd.dwZBufferBitDepth = 32;
    else if (devDepth & DDBD_24)
ddsd.dwZBufferBitDepth = 24;
    else if (devDepth & DDBD_16)
ddsd.dwZBufferBitDepth = 16;
    else if (devDepth & DDBD_8)
ddsd.dwZBufferBitDepth = 8;
    else {
D3DAppISetErrorString("Unsupported Z-buffer depth requested by device.\n");
return FALSE;
    }
    LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, &ddsd,
    &d3dappi.lpZBuffer,
    NULL);
    if(LastError != DD_OK) {
if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
    if (d3dappi.bFullscreen) {
D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
    } else {
D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
    }
} else {
    D3DAppISetErrorString("CreateSurface for Z-buffer failed.\n%s",
  D3DAppErrorToString(LastError));
}
        goto exit_with_error;
    }
    /*
     * Attach the Z-buffer to the back buffer so D3D will find it
     */
    LastError =
       d3dappi.lpBackBuffer->lpVtbl->AddAttachedSurface(d3dappi.lpBackBuffer,
d3dappi.lpZBuffer);
    if(LastError != DD_OK) {
        D3DAppISetErrorString("AddAttachedBuffer failed for Z-Buffer.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Find out if it ended up in video memory.
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpZBuffer);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to get surface description of Z buffer.\n%s",
      D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    d3dappi.bZBufferInVideo =
          (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    if (d3dappi.Driver[driver].bIsHardware && !d3dappi.bZBufferInVideo) {
D3DAppISetErrorString("Could not fit the Z-buffer in video memory for this hardware device.\n");
goto exit_with_error;
    }

    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpZBuffer);
    return FALSE;
}

/***************************************************************************/
/*                             WM_SIZE Handler                             */
/***************************************************************************/
/*
 * D3DAppIHandleWM_SIZE
 * Processes the WM_SIZE message.  Resizes all the buffers and re-creates
 * device if necessary.
 */
BOOL
D3DAppIHandleWM_SIZE(LRESULT* lresult, HWND hwnd, UINT message,
     WPARAM wParam, LPARAM lParam)
{
    int w, h, i;
    /*
     * If we have minimzied, take note and call the default window proc
     */
    if (wParam == SIZE_MINIMIZED) {
d3dappi.bMinimized = TRUE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * In fullscreen mode, restore our surfaces and let DDraw take
     * care of the rest.
     */
    if (d3dappi.bFullscreen) {
D3DAppIValidateDirtyRects();
D3DAppCheckForLostSurfaces();
d3dappi.bMinimized = FALSE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * If we are minimized, this is the un-minimized size message.
     */
    if (d3dappi.bMinimized) {
/*
 * Restore our surfaces and update the dirty rectangle info
 */
D3DAppIValidateDirtyRects();
D3DAppCheckForLostSurfaces();
d3dappi.bMinimized = FALSE;
*lresult = DefWindowProc(hwnd, message, wParam, lParam);
return TRUE;
    }
    /*
     * Since we are still here, this must be a regular, window resize
     * message.  A new viewport will definitely be needed, but the
     * device and buffers will only be re-created if they have gotten bigger
     * or change size by a very large amount.
     */
    D3DAppIGetClientWin(hwnd);
    w = LOWORD(lParam);
    h = HIWORD(lParam);
    /*
     * If w and h are under the minimum, create buffers of the minimum size
     */
    if (w < D3DAPP_WINDOWMINIMUM)
        w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
        h = D3DAPP_WINDOWMINIMUM;
    /*
     * Destroy the viewport and all execute buffers
     */
    d3dappi.bRenderingIsOK = FALSE;
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    /*
     * Only create a new device and buffers if they changed significantly,
     * otherwise just make sure the old buffers aren't lost.
     */
    if ((w > szBuffers.cx || h > szBuffers.cy) ||
(w < szBuffers.cx / 2 || h < szBuffers.cy / 2)) {
/*
 * Release the device
 */
RELEASE(d3dappi.lpD3DDevice);
/*
 * Release the old buffers
 */
RELEASE(d3dappi.lpZBuffer);
RELEASE(lpPalette);
RELEASE(lpClipper);
RELEASE(d3dappi.lpBackBuffer);
RELEASE(d3dappi.lpFrontBuffer);
/*
 * Create new ones
 */
ATTEMPT(D3DAppICreateBuffers(hwnd, w, h, D3DAPP_BOGUS, FALSE, d3dappi.ThisDriver.bIsHardware));
ATTEMPT(D3DAppICheckForPalettized());
ATTEMPT(D3DAppICreateZBuffer(w, h, d3dappi.CurrDriver));
/*
 * Create the driver
 */
ATTEMPT(D3DAppICreateDevice(d3dappi.CurrDriver));
/*
 * Since the driver did not change, the texture surfaces are still valid.
 * We just need to get new handles.
 */
if (d3dappi.ThisDriver.bDoesTextures) {
    for (i = 0; i < d3dappi.NumUsableTextures; i++) {
D3DAppIGetTextureHandle(i);
    }
}
    } else {
D3DAppCheckForLostSurfaces();
    }
    /*
     * Call the device create callback to create the viewport, set the render
     * state and clear the dirty rectangle info
     */
    ATTEMPT(D3DAppICallDeviceCreateCallback(w, h));
    ATTEMPT(D3DAppISetRenderState());
    D3DAppIValidateDirtyRects();
    d3dappi.bRenderingIsOK = TRUE;
    /*
     * Call the default window proc
     */
    *lresult = DefWindowProc(hwnd, message, wParam, lParam);
    return TRUE;
exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    return FALSE;
}

/***************************************************************************/
/*              Setting the display mode and cooperative level             */
/***************************************************************************/
/*
 * D3DAppISetCoopLevel
 * Set the cooperative level to exclusive mode for fullscreen and normal for
 * a window.  Set the bIgnoreWM_SIZE flag because SetCooperativeLevel
 * generates a WM_SIZE message you do not have to resize the buffers on.
 */
BOOL
D3DAppISetCoopLevel(HWND hwnd, BOOL bFullscreen)
{
    if (bFullscreen) {
bIgnoreWM_SIZE = TRUE;
LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
   hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
bIgnoreWM_SIZE = FALSE;
if(LastError != DD_OK ) {
    D3DAppISetErrorString("SetCooperativeLevel to fullscreen failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
d3dappi.bFullscreen = TRUE;
    } else {
bIgnoreWM_SIZE = TRUE;
        LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
 hwnd, DDSCL_NORMAL);
bIgnoreWM_SIZE = FALSE;
if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetCooperativeLevel to normal failed.\n%s",
  D3DAppErrorToString(LastError));
            return FALSE;
        }
d3dappi.bFullscreen = FALSE;
    }
    return TRUE;
}

/*
 * D3DAppISetDisplayMode
 * Set the display mode to the given dimensions and bits per pixel.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppISetDisplayMode(int w, int h, int bpp)
{
    d3dappi.ThisMode.w = w; d3dappi.ThisMode.h = h;
    d3dappi.ThisMode.bpp = bpp;
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->SetDisplayMode(d3dappi.lpDD, w, h,
     bpp);
    bIgnoreWM_SIZE = FALSE;
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("SetDisplayMode to %dx%dx%d failed\n%s",
      w, h, bpp, D3DAppErrorToString(LastError));
        return FALSE;
    }
    return TRUE;
}

/*
 * D3DAppIRestoreDispMode
 * Restores the display mode to the current windows display mode.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppIRestoreDispMode(void)
{
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->RestoreDisplayMode(d3dappi.lpDD);
    if (LastError != DD_OK) {
D3DAppISetErrorString("RestoreDisplayMode failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    bIgnoreWM_SIZE = FALSE;
    return TRUE;
}

/*
 * D3DAppRememberWindowsMode
 * Record the current display mode in d3dappi.WindowsDisplay
 */
BOOL
D3DAppIRememberWindowsMode(void)
{
    DDSURFACEDESC ddsd;

    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    LastError = d3dappi.lpDD->lpVtbl->GetDisplayMode(d3dappi.lpDD, &ddsd);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Getting the current display mode failed.\n%s",
      D3DAppErrorToString(LastError));
return FALSE;
    }
    d3dappi.WindowsDisplay.w = ddsd.dwWidth;
    d3dappi.WindowsDisplay.h = ddsd.dwHeight;

d3dappi.WindowsDisplay.bpp = ddsd.ddpfPixelFormat.dwRGBBitCount;
    return TRUE;
}

/***************************************************************************/
/*                          Misc DD Utilities                              */
/***************************************************************************/

/*
 * D3DAppIClearBuffers
 * Clear the front and back buffers to black
 */
BOOL
D3DAppIClearBuffers(void)
{
    DDSURFACEDESC ddsd;
    RECT dst;
    DDBLTFX ddbltfx;
    /*
     * Find the width and height of the front buffer by getting its
     * DDSURFACEDESC
     */
    if (d3dappi.lpFrontBuffer) {
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpFrontBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failure getting the surface description of the front buffer before clearing.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
/*
 * Clear the front buffer to black
 */
memset(&ddbltfx, 0, sizeof(ddbltfx));
ddbltfx.dwSize = sizeof(DDBLTFX);
SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
LastError = d3dappi.lpFrontBuffer->lpVtbl->Blt(d3dappi.lpFrontBuffer,
    &dst, NULL, NULL,
    DDBLT_COLORFILL | DDBLT_WAIT,
    &ddbltfx);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    }
    if (d3dappi.lpBackBuffer) {
/*
 * Find the width and height of the back buffer by getting its
 * DDSURFACEDESC
 */
LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failure while getting the surface description of the back buffer before clearing.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
/*
 * Clear the back buffer to black
 */
memset(&ddbltfx, 0, sizeof(ddbltfx));
ddbltfx.dwSize = sizeof(DDBLTFX);
SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
LastError = d3dappi.lpBackBuffer->lpVtbl->Blt(d3dappi.lpBackBuffer, &dst,
     NULL, NULL,
     DDBLT_COLORFILL | DDBLT_WAIT,
     &ddbltfx);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
  D3DAppErrorToString(LastError));
    return FALSE;
}
    }
    return TRUE;
}

/*
 * D3DAppIBPPToDDBD
 * Convert an integer bit per pixel number to a DirectDraw bit depth flag
 */
DWORD
D3DAppIBPPToDDBD(int bpp)
{
    switch(bpp) {
case 1:
    return DDBD_1;
case 2:
    return DDBD_2;
case 4:
    return DDBD_4;
case 8:
    return DDBD_8;
case 16:
    return DDBD_16;
case 24:
    return DDBD_24;
case 32:
    return DDBD_32;
default:
    return (DWORD)D3DAPP_BOGUS;
    }
}

/*
 * D3DAppTotalVideoMemory
 * Returns the amount of total video memory supported (not free)
 */
DWORD
D3DAppTotalVideoMemory(void)
{
    DDCAPS DriverCaps, HELCaps;
    memset (&DriverCaps, 0, sizeof(DDCAPS));
    DriverCaps.dwSize = sizeof(DDCAPS);
    memset (&HELCaps, 0, sizeof(DDCAPS));
    HELCaps.dwSize = sizeof(DDCAPS);
    LastError = d3dappi.lpDD->lpVtbl->GetCaps(d3dappi.lpDD, &DriverCaps,
      &HELCaps);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Getting DD capabilities failed while checking total video memory.\n%s",
      D3DAppErrorToString(LastError));
return 0L;
    }
    if (DriverCaps.dwVidMemTotal)
return DriverCaps.dwVidMemTotal;
    else
return HELCaps.dwVidMemTotal;
}


LCLIB.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: lclib.h
 *
 */

#include<windows.h>
#include"lclib.h"

char* LSTRCHR( const char* lpString, int bChar )
{
    if( lpString != NULL )
    {
while( *lpString != 0 )
{
    if( *lpString == bChar )
    {
return (char*)lpString;
    }

    lpString++;
}
    }
    return NULL;

} /* LSTRCHR */

char* LSTRRCHR( const char* lpString, int bChar )
{
    if( lpString != NULL )
    {
const char*lpBegin;

lpBegin = lpString;

while( *lpString != 0 )
{
    lpString++;
}

        while( 1 )
{
    if( *lpString == bChar )
    {
return (char*)lpString;
    }

    if( lpString == lpBegin )
    {
 break;
    }

    lpString--;
}
    }

    return NULL;
} /* LSTRRCHR */


LCLIB.H

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: lclib.h
 *
 */

#ifndef __LCLIB_H__
#define __LCLIB_H__

char* LSTRRCHR( const char*, int );
char* LSTRCHR( const char*, int );

#endif


MISC.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: misc.c
 *
 *  Miscellaneous functions not involving DD and D3D.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"

/***************************************************************************/
/*                          Setting Defaults                               */
/***************************************************************************/
/*
 * D3DAppISetDefaults
 * Set all the global variables to their default values.  Do not reset the
 * image files.
 */
void
D3DAppISetDefaults(void)
{
    int n;
    char backup[D3DAPP_MAXTEXTURES][50];

    n = d3dappi.NumTextures;
    memcpy(&backup[0][0], &d3dappi.ImageFile[0][0], 50 * D3DAPP_MAXTEXTURES);
    ZEROMEM(d3dappi);
    memcpy(&d3dappi.ImageFile[0][0], &backup[0][0], 50 * D3DAPP_MAXTEXTURES);
    d3dappi.NumTextures = n;
    d3dappi.bAppActive = TRUE;
    ZEROMEM(d3dapprs);
    d3dapprs.bZBufferOn = TRUE;
    d3dapprs.bPerspCorrect = FALSE;
    d3dapprs.ShadeMode = D3DSHADE_GOURAUD;
    d3dapprs.TextureFilter = D3DFILTER_NEAREST;
    d3dapprs.TextureBlend = D3DTBLEND_MODULATE;
    d3dapprs.FillMode = D3DFILL_SOLID;
    d3dapprs.bDithering = FALSE;
    d3dapprs.bSpecular = TRUE;
    d3dapprs.bAntialiasing = FALSE;
    d3dapprs.bFogEnabled = FALSE;
    d3dapprs.FogColor = RGB_MAKE(0, 0, 0);
    d3dapprs.FogMode = D3DFOG_LINEAR;
    d3dapprs.FogStart = D3DVAL(6.0);
    d3dapprs.FogEnd = D3DVAL(11.0);

    lpClipper = NULL;
    lpPalette = NULL;
    bPrimaryPalettized = FALSE;
    bPaletteActivate = FALSE;
    bIgnoreWM_SIZE = FALSE;
    ZEROMEM(ppe);
    ZEROMEM(Originalppe);
    LastError = DD_OK;
    ZEROMEM(LastErrorString);
    D3DDeviceDestroyCallback = NULL;
    D3DDeviceDestroyCallbackContext = NULL;
    D3DDeviceCreateCallback = NULL;
    D3DDeviceCreateCallbackContext = NULL;
}

/***************************************************************************/
/*                Calling Device Create And Destroy Callbacks              */
/***************************************************************************/
BOOL
D3DAppICallDeviceDestroyCallback(void)
{
    if (D3DDeviceDestroyCallback) {
if (CallbackRefCount) {
    --CallbackRefCount;
    return (D3DDeviceDestroyCallback)(D3DDeviceDestroyCallbackContext);
}
    }
    return TRUE;
}

BOOL
D3DAppICallDeviceCreateCallback(int w, int h)
{
    if (D3DDeviceCreateCallback) {
++CallbackRefCount;
return (D3DDeviceCreateCallback)(w, h, &d3dappi.lpD3DViewport,
 D3DDeviceCreateCallbackContext);
    }
    return TRUE;
}

/***************************************************************************/
/*            Choosing and verifying the driver and display mode           */
/***************************************************************************/
/*
 * D3DAppIPickDriver
 * Choose a driver from the list of available drivers which can render to one
 * of the given depths.  Hardware is prefered.  Mono-lighting drivers are
 * prefered over RGB.
 */
BOOL
D3DAppIPickDriver(int* driver, DWORD depths)
{
    int i, j;
    j = 0;
    for (i = 0; i < d3dappi.NumDrivers; i++)
if (d3dappi.Driver[i].Desc.dwDeviceRenderBitDepth & depths)
    break;
    if (i >= d3dappi.NumDrivers) {
*driver = D3DAPP_BOGUS;
return TRUE;
    }
    j = i;
    for (i = 0; i < d3dappi.NumDrivers; i++) {
if (d3dappi.Driver[i].Desc.dwDeviceRenderBitDepth & depths) {
    if (d3dappi.Driver[i].bIsHardware &&
      !d3dappi.Driver[j].bIsHardware)
          j = i;
    else if (d3dappi.Driver[i].bIsHardware ==
     d3dappi.Driver[j].bIsHardware) {
if (d3dappi.Driver[i].Desc.dcmColorModel & D3DCOLOR_MONO &&
    !(d3dappi.Driver[j].Desc.dcmColorModel & D3DCOLOR_MONO))
j = i;
    }
}
    }
    if (j >= d3dappi.NumDrivers)
*driver = D3DAPP_BOGUS;
    else
*driver = j;
    return TRUE;
}

/*
 * D3DAppIFilterDisplayModes
 * Set the bThisDriverCanDo flag for each display mode if the given driver
 * can render in that depth.  Also checks to make sure there is enough
 * total video memory for front/back/z-buffer in video memory if it's a
 * hardware device.
 */
BOOL
D3DAppIFilterDisplayModes(int driver)
{
    int i;
    DWORD depths = d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth;

    for (i = 0; i < d3dappi.NumModes; i++) {
d3dappi.Mode[i].bThisDriverCanDo = FALSE;
if (!(D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths))
    continue;
d3dappi.Mode[i].bThisDriverCanDo = TRUE;

    }
    d3dappi.ThisMode.bThisDriverCanDo =
     d3dappi.Mode[d3dappi.CurrMode].bThisDriverCanDo;
    return TRUE;
}

/*
 * D3DAppIPickDisplayMode
 * Pick a display mode of one of the given depths.  640x480x16 is prefered.
 */
BOOL
D3DAppIPickDisplayMode(int *mode, DWORD depths)
{
    int i, j;
    for (i = 0; i < d3dappi.NumModes; i++)
if (D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths)
    break;
    j = i;
    for (; i < d3dappi.NumModes; i++) {
if (!(D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp) & depths))
    continue;
if (d3dappi.Mode[i].w == 640 && d3dappi.Mode[i].h == 480 &&
    d3dappi.Mode[i].bpp == 16) {
    j = i;
    break;
}
    }
    if (j >= d3dappi.NumModes)
*mode = D3DAPP_BOGUS;
    else
*mode = j;
    return TRUE;
}

/*
 * D3DAppIVerifyDriverAndMode
 * Verifies the selected driver and mode combination.  If the driver is
 * specified, the mode will be changed to accomodate the driver if it's not
 * compatible.  If the driver is not specified, one will be selected which is
 * compatible with the specified mode.  If neither are specified, a suitable
 * pair will be returned.
 */
BOOL
D3DAppIVerifyDriverAndMode(int* lpdriver, int* lpmode)
{
    DWORD depths;
    int driver, mode, i;
    driver = *lpdriver; mode = *lpmode;

    if (mode == D3DAPP_USEWINDOW && !d3dappi.bIsPrimary) {
D3DAppISetErrorString("Cannot render to a window when the DirectDraw device is not the primary.\n");
goto exit_with_error;
    }

    /*
     * If I've been ask to choose a driver, choose one which is compatible
     * with the specified mode.
     */
    if (driver == D3DAPP_YOUDECIDE) {
if (mode == D3DAPP_USEWINDOW) {
    /*
     * I must find a driver for this display depth
     */
    depths = D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp);
    ATTEMPT(D3DAppIPickDriver(&driver, depths));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the current display depth.\n");
goto exit_with_error;
    }
    /*
     * I don't need to go through the mode selection since I've
     * verified it here
     */
    goto ret_ok;
} else if (mode == D3DAPP_YOUDECIDE) {
    /*
     * I'm free to choose any driver which can use even one
     * supported depth
     */
    if (d3dappi.bIsPrimary)
depths = D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp);
    else
depths = 0;
    for (i = 0; i < d3dappi.NumModes; i++)
depths |= D3DAppIBPPToDDBD(d3dappi.Mode[i].bpp);
    ATTEMPT(D3DAppIPickDriver(&driver, depths));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the current display depth or any supported fullscreen mode.\n");
goto exit_with_error;
    }
    /*
     * The mode will be chosen in the next section
     */
} else {
    /*
     * Must pick a driver which uses the given mode depth
     */
    ATTEMPT(D3DAppIPickDriver(&driver,
  D3DAppIBPPToDDBD(d3dappi.Mode[mode].bpp)));
    if (driver == D3DAPP_BOGUS) {
D3DAppISetErrorString("Cannot find a D3D device driver which is compatible with the specified fullscreen mode.\n");
goto exit_with_error;
    }
    /*
     * I don't need to go through the mode selection since I've
     * verified it here
     */
    goto ret_ok;
}
    }

    /*
     * At this stage, I have a driver I want to match the mode to.
     */
    if (mode == D3DAPP_YOUDECIDE) {
/*
 * If it's my choice, I prefer windowed over fullscreen
 */
if (d3dappi.bIsPrimary) {
    if (D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp) &
    d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
mode = D3DAPP_USEWINDOW;
goto ret_ok;
    }
}
/*
 * Either this is not a primary DD device or the driver cannot use
 * the Windows display depth
 */
ATTEMPT(D3DAppIPickDisplayMode(&mode,
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth));
if (mode == D3DAPP_BOGUS) {
    D3DAppISetErrorString("The selected D3D device driver is not compatible with the current display depth or any supported fullscreen modes.\n");
    goto exit_with_error;
}
goto ret_ok;
    } else if (mode == D3DAPP_USEWINDOW) {
/*
 * Check to see if this driver can use the Windows display depth
 */
if (D3DAppIBPPToDDBD(d3dappi.WindowsDisplay.bpp) &
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
    goto ret_ok;
} else {
    /*
     * Since it cannot, call this function again to choose any
     * display mode which is compatible
     */
    mode = D3DAPP_YOUDECIDE;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    if (driver == D3DAPP_BOGUS)
goto exit_with_error;
    goto ret_ok;
}
    } else {
/*
 * Check to see if this driver can use the specified fullscreen mode
 */
if (D3DAppIBPPToDDBD(d3dappi.Mode[mode].bpp) &
d3dappi.Driver[driver].Desc.dwDeviceRenderBitDepth) {
    goto ret_ok;
} else {
    /*
     * Since it cannot, call this function again to choose any
     * display mode which is compatible
     */
    mode = D3DAPP_YOUDECIDE;
    ATTEMPT(D3DAppIVerifyDriverAndMode(&driver, &mode));
    if (driver == D3DAPP_BOGUS)
goto exit_with_error;
    goto ret_ok;
}
    }

ret_ok:
    *lpdriver = driver; *lpmode = mode;
    return TRUE;
exit_with_error:
    return FALSE;
}

/***************************************************************************/
/*                        Dirty Rectangle Functions                        */
/***************************************************************************/
/*
 * D3DAppIValidateDirtyRects
 * Set the dirty rectangles for the front and back buffers to the entire
 * client size.
 */
void
D3DAppIValidateDirtyRects(void)
{
    NumDirtyClientRects = 1; NumDirtyBackRects = 1; NumDirtyZRects = 1;
    SetRect((LPRECT)&DirtyClient[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
    SetRect((LPRECT)&DirtyBack[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
    SetRect((LPRECT)&DirtyZ[0], 0, 0, d3dappi.szClient.cx,
    d3dappi.szClient.cy);
}

/*
 * D3DAppICopyRectList
 * Copy a list of rectangles to another
 */
void
D3DAppICopyRectList(int* dstnum, LPD3DRECT dst, int srcnum, LPD3DRECT src)
{
    int i;
    for (i = 0; i < srcnum; i++)
dst[i] = src[i];
    *dstnum = srcnum;
}

/*
 * MERGE macro
 * Set first rectangle to be the smallest rectangle containing both rects
 */
#undef MERGE
#define MERGE(rc1, rc2)\
    do {\
if (rc2.x1 < rc1.x1) rc1.x1 = rc2.x1;\
if (rc2.y1 < rc1.y1) rc1.y1 = rc2.y1;\
if (rc2.x2 > rc1.x2) rc1.x2 = rc2.x2;\
if (rc2.y2 > rc1.y2) rc1.y2 = rc2.y2;\
    } while(0)

/*
 * D3DAppIMergeRectLists
 * Merge two lists of rectangles to create another list of rectangles. The
 * merged list of rectangles covers all the area of the original two with NO
 * OVERLAPPING amongst it's rectangles.
 */
void
D3DAppIMergeRectLists(int* dstnum, LPD3DRECT dst, int src1num, LPD3DRECT src1,
     int src2num, LPD3DRECT src2)
{
    LPD3DRECT rc;
    int* isvalid;
    int num, i, j, intersect;
    rc = (LPD3DRECT)malloc(sizeof(D3DRECT) * D3DAPP_MAXCLEARRECTS * 2);
    memset(rc, 0, sizeof(D3DRECT) * D3DAPP_MAXCLEARRECTS * 2);
    isvalid = (int*)malloc(sizeof(int) * D3DAPP_MAXCLEARRECTS * 2);
    memset(isvalid, 0, sizeof(int) * D3DAPP_MAXCLEARRECTS * 2);
    for (i = 0; i < src1num; i++) {
memcpy(&rc[i], &src1[i], sizeof(D3DRECT));
if ((rc[i].x1 == 0 && rc[i].x2 == 0) ||
    (rc[i].y1 == 0 && rc[i].y2 == 0))
    isvalid[i] = 0;
        else if (rc[i].x1 <= rc[i].x2 && rc[i].y1 <= rc[i].y2)
    isvalid[i] = 1;
        else
            isvalid[i] = 0;
    }
    for (i = 0; i < src2num; i++) {
memcpy(&rc[i + src1num], &src2[i], sizeof(D3DRECT));
        if (rc[i + src1num].x1 <= rc[i + src1num].x2 &&
            rc[i + src1num].y1 <= rc[i + src1num].y2)
    isvalid[i + src1num] = 1;
        else
            isvalid[i + src1num] = 0;

    }
    num = src1num + src2num;
    for (i = 0; i < num - 1; i++) {
        if (!isvalid[i]) continue;
j = i + 1;
do {
    intersect = 0;
    for (; j < num; j++) {
if (j != i && isvalid[j]) {
    if (rc[i].x1 < rc[j].x1) {
if (rc[i].x2 < rc[j].x1)
    continue;
    } else {
if (rc[j].x2 < rc[i].x1)
    continue;
    }
    if (rc[i].y1 < rc[j].y1) {
if (rc[i].y2 < rc[j].y1)
    continue;
    } else {
if (rc[j].y2 < rc[i].y1)
    continue;
    }
    MERGE(rc[i], rc[j]);
    isvalid[j] = 0;
    j = 0; intersect = 1;
    break;
}
    }
} while(intersect);
    }

    for (i = 0, j = 0; i < num; i++)
if (isvalid[i]) ++j;
    if (j > D3DAPP_MAXCLEARRECTS) {
for (i = 0; i < num; i++)
    if (isvalid[i]) break;
if (i < num) {
    *dstnum = 1;
    dst[0] = rc[i];
    for (; i < num; i++) {
if (isvalid[i]) {
    MERGE(dst[0], rc[i]);
}
    }
} else {
    *dstnum = 0;
}
    } else {
for (i = 0, j = 0; i < num; i++) {
    if (isvalid[i]) {
memcpy(&dst[j], &rc[i], sizeof(D3DRECT));
++j;
    }
}
*dstnum = j;
    }
    free(rc);
    free(isvalid);
}

/***************************************************************************/
/*                     Getting and Setting Window Attribs                  */
/***************************************************************************/
/*
 * D3DAppISetClientSize
 * Set the client size of the given window.  A WM_SIZE message is generated,
 * but ignored.
 */
void
D3DAppISetClientSize(HWND hwnd, int w, int h, BOOL bReturnFromFullscreen)
{
    RECT rc;

    bIgnoreWM_SIZE = TRUE;
    if (bReturnFromFullscreen) {
SetRect(&rc, 0, 0, w, h);
AdjustWindowRectEx(&rc, GetWindowLong(hwnd, GWL_STYLE),
           GetMenu(hwnd) != NULL,
           GetWindowLong(hwnd, GWL_EXSTYLE));
SetWindowPos(hwnd, NULL, 0, 0, rc.right-rc.left,
             rc.bottom-rc.top,
             SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
             SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

    } else {
/*
 * This is the only way to set the client size correctly if the menu
 * is stacked, so do it unless we are returning from a fullscreen
 * mode.
 */
SendMessage(hwnd, WM_SIZE, SIZE_RESTORED, w + h << 16);
GetWindowRect(hwnd, &rc);
SetWindowPos(hwnd, NULL, 0, 0, rc.right-rc.left,
             rc.bottom-rc.top,
             SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
             SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
    }
    bIgnoreWM_SIZE = FALSE;
    d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
    ClientToScreen(hwnd, &d3dappi.pClientOnPrimary);
    d3dappi.szClient.cx = w; d3dappi.szClient.cy = h;
}

/*
 * D3DAppIGetClientWin
 * Gets the client window size and sets it in the D3DAppInfo structure
 */
void
D3DAppIGetClientWin(HWND hwnd)
{
    RECT rc;
    if (!d3dappi.bFullscreen) {
d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
ClientToScreen(hwnd, &d3dappi.pClientOnPrimary);
GetClientRect(hwnd, &rc);
d3dappi.szClient.cx = rc.right;
d3dappi.szClient.cy = rc.bottom;
    } else {
/*
 * In the fullscreen case, we must be careful because if the window
 * frame has been drawn, the client size has shrunk and this can
 * cause problems, so it's best to report the entire screen.
 */
d3dappi.pClientOnPrimary.x = d3dappi.pClientOnPrimary.y = 0;
d3dappi.szClient.cx = d3dappi.ThisMode.w;
d3dappi.szClient.cy = d3dappi.ThisMode.h;
    }
}

/***************************************************************************/
/*                              Error reporting                            */
/***************************************************************************/

/*
 * D3DAppISetErrorString
 * Set the global variable which records the last error string.
 */
void
D3DAppISetErrorString( LPSTR fmt, ... )
{
    char buff[256];
    va_list va;

    buff[0] = 0;
    va_start( va, fmt );
    wvsprintf(&buff[0], fmt, va);
    va_end( va );
    lstrcat(buff, "\r\n");
    lstrcpy(LastErrorString, buff);
}

/* dpf
 * Debug printf.  Very useful for fullscreen exclusive mode or when surfaces
 * are in video memory.
 */
void __cdecl
dpf( LPSTR fmt, ... )
{
    char buff[256];
    va_list va;

    lstrcpy(buff, "D3DApp: ");
    va_start( va, fmt );
    wvsprintf(&buff[lstrlen(buff)], fmt, va);
    va_end( va );
    lstrcat(buff, "\r\n");
    OutputDebugString(buff);
}


STATS.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: stats.cpp
 *
 ***************************************************************************/
#include "d3dmain.h"

/*
 * GLOBAL VARIABLES
 */
extern D3DAppInfo* d3dapp;    /* Pointer to read only collection of DD and D3D
       objects maintained by D3DApp */
extern d3dmainglobals myglobs;    /* collection of global variables */

static struct {
    HFONT hFont;
    SIZE szFrameRate;
    SIZE szInfo;
} statglobs;

 /************************************************************************
  Frame rate and info text
 ************************************************************************/

BOOL
WriteFrameRateBuffer(float fps, long tps)
{
    HRESULT LastError;
    HDC hdc;
    RECT rc;
    char buf1[30], buf2[30], buf[60];
    int len;

    if (!myglobs.lpFrameRateBuffer)
return TRUE;
    if (fps > 0.0f)
        wsprintf(buf1, "%d.%02d fps   ",
        (int)( fps * 100 ) / 100,
        (int)( fps * 100 ) % 100);
    else
        buf1[0] = 0;
    if (tps > 0)
        wsprintf(buf2, "%ld tps   ", tps);
    else
        buf2[0] = 0;
    len = wsprintf(buf, "%s%s", buf1, buf2);
    if (!myglobs.lpFrameRateBuffer)
        return FALSE;
    LastError = myglobs.lpFrameRateBuffer->GetDC(&hdc);
    if (LastError != DD_OK) {
/*
 * If this fails, it's not vital, so don't report an error.
 */
return TRUE;
    }
    SelectObject(hdc, statglobs.hFont);
    SetTextColor(hdc, RGB(255,255,0));
    SetBkColor(hdc, RGB(0,0,0));
    SetBkMode(hdc, OPAQUE);
    GetTextExtentPoint32(hdc, buf, len, &statglobs.szFrameRate);
    SetRect(&rc, 0, 0, statglobs.szFrameRate.cx, statglobs.szFrameRate.cy);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, buf, len, NULL);
    myglobs.lpFrameRateBuffer->ReleaseDC(hdc);
    return TRUE;
}

BOOL
WriteInfoBuffer(void)
{
    HRESULT LastError;
    HDC hdc;
    RECT rc;
    char buf[40];
    int len;

    if (!myglobs.lpInfoBuffer)
return TRUE;
    if (d3dapp->bFullscreen)
len = wsprintf(buf, "%dx%dx%d (%s)", d3dapp->ThisMode.w, d3dapp->ThisMode.h, d3dapp->ThisMode.bpp,
       (d3dapp->ThisDriver.Desc.dcmColorModel == D3DCOLOR_MONO) ? "MONO" : "RGB");
    else
len = wsprintf(buf, "%dx%d (%s)", d3dapp->szClient.cx, d3dapp->szClient.cy,
       (d3dapp->ThisDriver.Desc.dcmColorModel == D3DCOLOR_MONO) ? "MONO" : "RGB");
    if (!myglobs.lpInfoBuffer)
        return FALSE;
    LastError = myglobs.lpInfoBuffer->GetDC(&hdc);
    if (LastError != DD_OK) {
/*
 * If this fails, it's not vital, so don't report an error.
 */
return TRUE;
    }

    SelectObject(hdc, statglobs.hFont);
    SetTextColor(hdc, RGB(255,255,0));
    SetBkColor(hdc, RGB(0,0,0));
    SetBkMode(hdc, OPAQUE);
    GetTextExtentPoint32(hdc, buf, len, &statglobs.szInfo);
    SetRect(&rc, 0, 0, statglobs.szInfo.cx, statglobs.szInfo.cy);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, buf, len, NULL);
    myglobs.lpInfoBuffer->ReleaseDC(hdc);
    return TRUE;
}

void
ReleaseFontAndTextBuffers(void)
{
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    if (statglobs.hFont != NULL) {
        DeleteObject(statglobs.hFont);
statglobs.hFont = NULL;
    }
}

BOOL
InitFontAndTextBuffers(void)
{
    DDCOLORKEY          ddck;
    DDSURFACEDESC       ddsd;
    HDC hdc;
    HRESULT ddrval;
    char dummyinfo[] = "000x000x00 (MONO) 0000";
    char dummyfps[] = "000.00 fps 00000000.00 tps 0000.00 mppps";

    /*
     * Create the font.
     */
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    if (statglobs.hFont != NULL) {
        DeleteObject(statglobs.hFont);
statglobs.hFont = NULL;
    }
    statglobs.hFont = CreateFont(
        d3dapp->szClient.cx <= 600 ? 12 : 24,
        0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        VARIABLE_PITCH,
        "Arial" );

    hdc = GetDC(NULL);
    SelectObject(hdc, statglobs.hFont);
    GetTextExtentPoint(hdc, dummyfps, strlen(dummyfps), &statglobs.szFrameRate);
    GetTextExtentPoint(hdc, dummyinfo, strlen(dummyinfo), &statglobs.szInfo);
    ReleaseDC(NULL, hdc);

    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = statglobs.szFrameRate.cy;
    ddsd.dwWidth = statglobs.szFrameRate.cx;
    ddrval = D3DAppCreateSurface(&ddsd, &myglobs.lpFrameRateBuffer);
    if (ddrval != DD_OK) {
Msg("Could not create frame rate buffer.\n%s", D3DAppErrorToString(ddrval));
        goto exit_with_error;
    }
    memset(&ddck, 0, sizeof(ddck));
    myglobs.lpFrameRateBuffer->SetColorKey(DDCKEY_SRCBLT, &ddck);
    if (!WriteFrameRateBuffer(0.0f, 0)) {
        goto exit_with_error;
    }
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwHeight = statglobs.szInfo.cy;
    ddsd.dwWidth = statglobs.szInfo.cx;
    ddrval = D3DAppCreateSurface(&ddsd, &myglobs.lpInfoBuffer);
    if (ddrval != DD_OK) {
Msg("Could not create info buffer.\n%s", D3DAppErrorToString(ddrval));
goto exit_with_error;
    }
    memset(&ddck, 0, sizeof(ddck));
    myglobs.lpInfoBuffer->SetColorKey(DDCKEY_SRCBLT, &ddck);
    if (!WriteInfoBuffer())
goto exit_with_error;
    return TRUE;
exit_with_error:
    RELEASE(myglobs.lpInfoBuffer);
    RELEASE(myglobs.lpFrameRateBuffer);
    if (statglobs.hFont != NULL) {
        DeleteObject(statglobs.hFont);
    }
    return FALSE;
}

/*************************************************************************
  Frame rate output.
 *************************************************************************/

#define INTERVAL 100

char StatTxt[100];
int StatTxtLen;
int count = 0;
int last_polygons = 0;
int this_frames = 0;
time_t last_time;
float fps;
long tps;

/*
 * ResetFrameRate
 * Initializes the frame rate counter.
 */
void
ResetFrameRate(void)
{
    last_time = clock();
    count = 0;
    last_polygons = 0;
    this_frames = 0;
    fps = 0.0f; tps = 0;
    StatTxt[0] = 0;
    StatTxtLen = 0;
}

BOOL
CalculateFrameRate(void)
{
    /*
     * Calculate the frame rate and get other stats.
     */
    count++;
    this_frames++;
    if (count == INTERVAL) {
    double t;
int p, f;
D3DSTATS stats;
        time_t this_time;

        count = 0;
this_time = clock();
t = (this_time - last_time) / (double)CLOCKS_PER_SEC;
last_time = this_time;

        memset(&stats, 0, sizeof(D3DSTATS));
        stats.dwSize = sizeof(D3DSTATS);
        d3dapp->lpD3DDevice->GetStats(&stats);
p = stats.dwTrianglesDrawn - last_polygons;
last_polygons = stats.dwTrianglesDrawn;

f = this_frames;
this_frames = 0;

        fps = (float)(f / t);
        tps = (long)(p / t);

        if (myglobs.bShowFrameRate) {
            if (!WriteFrameRateBuffer(fps, tps))
                return FALSE;
        }
    }
    return TRUE;
}

/*
 * DisplayFrameRate
 * Outputs frame rate info and screen mode to back buffer when appropriate.
 */
BOOL
DisplayFrameRate(int* count, LPD3DRECT lpExtents )
{
    RECT rc;
    int x, y;
    HRESULT ddrval = DD_OK;
    if (myglobs.bShowFrameRate && !myglobs.bSingleStepMode && statglobs.szFrameRate.cx > 0 && statglobs.szFrameRate.cy > 0 &&
        statglobs.szFrameRate.cx < d3dapp->szClient.cx && statglobs.szFrameRate.cy < d3dapp->szClient.cy) {
        SetRect(&rc, 0, 0, statglobs.szFrameRate.cx, statglobs.szFrameRate.cy);
        x = (int)(0.5 * (d3dapp->szClient.cx - statglobs.szFrameRate.cx) + 0.5);
        y = 0;
if (myglobs.lpFrameRateBuffer)
    ddrval = d3dapp->lpBackBuffer->BltFast(x, y, myglobs.lpFrameRateBuffer, &rc,
 DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);
if (ddrval != DD_OK) {
    /*
     * Blting the frame rate has failed. Since it is not vital, we
     * aren't going to report an error.
     */
    return TRUE;
}
SetRect((LPRECT)(lpExtents), x, y, statglobs.szFrameRate.cx + x, statglobs.szFrameRate.cy);
++(*count);
++lpExtents;
    }
    if (myglobs.bShowInfo && statglobs.szInfo.cx < d3dapp->szClient.cx && statglobs.szInfo.cy < d3dapp->szClient.cy) {
        SetRect(&rc, 0, 0, statglobs.szInfo.cx, statglobs.szInfo.cy);
        x = (int)(0.5 * (d3dapp->szClient.cx - statglobs.szInfo.cx) + 0.5);
        y = d3dapp->szClient.cy - statglobs.szInfo.cy;
if (myglobs.lpInfoBuffer)
    ddrval = d3dapp->lpBackBuffer->BltFast(x,y, myglobs.lpInfoBuffer, &rc,
 DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);
if (ddrval != DD_OK) {
    /*
     * Blting the info has failed. Since it is not vital, we
     * aren't going to report an error.
     */
    return TRUE;
}
SetRect((LPRECT)(lpExtents), x, y, x + statglobs.szInfo.cx, y + statglobs.szInfo.cy);
++(*count);
    }
    return TRUE;
}


TEXTURE.C

/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: texture.c
 *
 *  Loads and manages textures.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c    Miscellaneous calls
 */

#include "d3dappi.h"

#define MAGICBYTES 2

/*
 * STATIC FUNCTION DECLARATIONS
 */
static void D3DAppIAddPathList(const char *path);
static void D3DAppIInitialisePathList();
static FILE * D3DAppIFindFile(const char *name, const char *mode);
static BOOL loadPPMHeader(FILE *fp, DWORD *width, DWORD *height, int *maxgrey);

/***************************************************************************/
/*                        Managing the texture list                        */
/***************************************************************************/
/*
 * D3DAppILoadTextureSurf
 * Creates a texture map surface and texture object from the numbered PPM
 * file.  This is done in a two step process.  A source texture surface and
 * object are created in system memory.  A second, initially empty, texture
 * surface is created (in video memory if hardware is present).  The source
 * texture is loaded into the destination texture surface and then discarded.
 * This process allows a device to compress or reformat a texture map as it
 * enters video memory during the Load call.
 */
BOOL
D3DAppILoadTextureSurf(int n, BOOL* bInVideo)
{
    DDSURFACEDESC ddsd;
    LPDIRECTDRAWSURFACE lpSrcTextureSurf = NULL;
    LPDIRECT3DTEXTURE2 lpSrcTexture = NULL;
    LPDIRECTDRAWPALETTE lpDstPalette = NULL;
    PALETTEENTRY ppe[256];
    DWORD pcaps;
    /*
     * Release the surface if it is hanging around
     */
    RELEASE(d3dappi.lpTextureSurf[n]);
    /*
     * Create a surface in system memory and load the PPM file into it.
     * Query for the texture interface.
     */
    lpSrcTextureSurf = D3DAppILoadSurface(d3dappi.lpDD, d3dappi.ImageFile[n],
  &d3dappi.ThisTextureFormat.ddsd,
  DDSCAPS_SYSTEMMEMORY);
    if (!lpSrcTextureSurf)
goto exit_with_error;
    LastError = lpSrcTextureSurf->lpVtbl->QueryInterface(lpSrcTextureSurf,
     &IID_IDirect3DTexture2,
     (LPVOID*)&lpSrcTexture);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to obtain D3D texture interface for a source texture.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Create an empty texture surface to load the source texture into.
     * The DDSCAPS_ALLOCONLOAD flag allows the DD driver to wait until the
     * load call to allocate the texture in memory because at this point,
     * we may not know how much memory the texture will take up (e.g. it
     * could be compressed to an unknown size in video memory).
     * Make sure SW renderers get textures in system memory
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, lpSrcTextureSurf);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not get the surface description of the source texture.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_ALLOCONLOAD;
    if (!d3dappi.ThisDriver.bIsHardware)
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpTextureSurf[n]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not create the destination texture surface.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) {
pcaps = DDPCAPS_8BIT | DDPCAPS_ALLOW256;
    } else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4) {
pcaps = DDPCAPS_4BIT;
    } else {
pcaps = 0;
    }
    if (pcaps) {
memset(ppe, 0, sizeof(PALETTEENTRY) * 256);
LastError = d3dappi.lpDD->lpVtbl->CreatePalette(d3dappi.lpDD, pcaps,
 ppe, &lpDstPalette, NULL);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to create a palette for the destination texture.\n%s",
  D3DAppErrorToString(LastError));
    goto exit_with_error;
}
LastError = d3dappi.lpTextureSurf[n]->lpVtbl->SetPalette(d3dappi.lpTextureSurf[n],
lpDstPalette);
if (LastError != DD_OK) {
    D3DAppISetErrorString("Failed to set the destination texture's palette.\n%s",
  D3DAppErrorToString(LastError));
    goto exit_with_error;
}
//lpDstPalette->lpVtbl->Release(lpDstPalette);
    }
    /*
     * Query our destination surface for a texture interface
     */
    LastError = d3dappi.lpTextureSurf[n]->lpVtbl->QueryInterface(d3dappi.lpTextureSurf[n],
     &IID_IDirect3DTexture2,
     (LPVOID*)&d3dappi.lpTexture[n]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to obtain D3D texture interface for a destination texture.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Load the source texture into the destination.  During this call, a
     * driver could compress or reformat the texture surface and put it in
     * video memory.
     */
    LastError = d3dappi.lpTexture[n]->lpVtbl->Load(d3dappi.lpTexture[n], lpSrcTexture);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not load a source texture into a destination texture.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }

    /*
     * Now we are done with the source texture
     */
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);

    /*
     * Did the texture end up in video memory?
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpTextureSurf[n]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not get the surface description of the loaded texture surface.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    if (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY)
*bInVideo = TRUE;
    else
*bInVideo = FALSE;

    return TRUE;

exit_with_error:
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);
    RELEASE(lpDstPalette);
    RELEASE(d3dappi.lpTexture[n]);
    RELEASE(d3dappi.lpTextureSurf[n]);
    return FALSE;
}

/*
 * D3DAppIReloadTextureSurf
 * Reloads a lost and restored texture surface
 */
BOOL
D3DAppIReloadTextureSurf(int n)
{
    LPDIRECTDRAWSURFACE lpSrcTextureSurf = NULL;
    LPDIRECT3DTEXTURE2 lpSrcTexture = NULL;

    /*
     * Create a surface in system memory and load the PPM file into it.
     * Query for the texture interface.
     */
    lpSrcTextureSurf = D3DAppILoadSurface(d3dappi.lpDD, d3dappi.ImageFile[n],
  &d3dappi.ThisTextureFormat.ddsd,
  DDSCAPS_SYSTEMMEMORY);
    if (!lpSrcTextureSurf)
goto exit_with_error;
    LastError = lpSrcTextureSurf->lpVtbl->QueryInterface(lpSrcTextureSurf,
     &IID_IDirect3DTexture2,
     (LPVOID*)&lpSrcTexture);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Failed to obtain D3D texture interface for a source texture.\n%s", D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Load the source texture into the destination.  During this call, a
     * driver could compress or reformat the texture surface and put it in
     * video memory.
     */
    LastError = d3dappi.lpTexture[n]->lpVtbl->Load(d3dappi.lpTexture[n], lpSrcTexture);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not load a source texture into a destination texture.\n%s",
      D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * Now we are done with the source texture
     */
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);

    return TRUE;

exit_with_error:
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);
    return FALSE;
}

/*
 * D3DAppIGetTextureHandle
 * Get a texture handle from the current D3D device for this texture and save
 * it in the MasterTextureHandle list and public texture handle list.
 */
BOOL
D3DAppIGetTextureHandle(int n)
{
    LastError = d3dappi.lpTexture[n]->lpVtbl->GetHandle(d3dappi.lpTexture[n],
       d3dappi.lpD3DDevice, &MasterTextureHandle[n]);
    if (LastError != DD_OK) {
D3DAppISetErrorString("Could not get a handle to loaded texture %i.\n%s",
      n, D3DAppErrorToString(LastError));
goto exit_with_error;
    }
    /*
     * If textures are enabled, put the handle in the public texture list,
     * otherwise, keep it as zero.
     */
    if (!d3dappi.bTexturesDisabled) {
d3dappi.TextureHandle[n] = MasterTextureHandle[n];
    } else {
d3dappi.TextureHandle[n] = 0;
    }
    return TRUE;
exit_with_error:
    MasterTextureHandle[n] = 0;
    d3dappi.TextureHandle[n] = 0;
    return FALSE;
}

/*
 * D3DAppIReleaseTexture
 * Release this texture surface and texture interface.  Remember, a texture
 * handle is NOT and object and does not need to be released or destroyed.
 * The handle is no longer valid after the device is destroyed, so set it to
 * zero here.
 */
void
D3DAppIReleaseTexture(int n)
{
    RELEASE(d3dappi.lpTexture[n]);
    RELEASE(d3dappi.lpTextureSurf[n]);
    MasterTextureHandle[n] = 0;
    d3dappi.TextureHandle[n] = 0;
}

/*
 * D3DAppIReleaseAllTextures
 * Release all texture surfaces and texture interfaces
 */
void
D3DAppIReleaseAllTextures(void)
{
    int i;
    for (i = 0; i < d3dappi.NumTextures; i++) {
D3DAppIReleaseTexture(i);
    }
}

/*
 * D3DAppILoadAllTextures
 * Load all texture surfaces, qeury them for texture interfaces and get
 * handles for them from the current D3D driver.
 */
BOOL
D3DAppILoadAllTextures(void)
{
    int i;
    if (d3dappi.ThisDriver.bDoesTextures) {
d3dappi.NumUsableTextures = 0;
for (i = 0; i < d3dappi.NumTextures; i++) {
    BOOL bInVideo;
    ATTEMPT(D3DAppILoadTextureSurf(i, &bInVideo));
    if (!bInVideo && d3dappi.ThisDriver.bIsHardware) {
/*
 * If a texture fails to load into video memory for a hardware
 * device, stop the NumUsableTextures count here.
 */
D3DAppIReleaseTexture(i);
break;
    } else {
++d3dappi.NumUsableTextures;
    }
}
for (i = 0; i < d3dappi.NumUsableTextures; i++) {
    ATTEMPT(D3DAppIGetTextureHandle(i));
}
    } else {
d3dappi.NumUsableTextures = 0;
    }
    return TRUE;

exit_with_error:
    for (i = 0; i < d3dappi.NumTextures; i++) {
D3DAppIReleaseTexture(i);
    }
    return FALSE;
}

/***************************************************************************/
/*                    Loading a PPM file into a surface                    */
/***************************************************************************/
/*
 * LoadSurface
 * Loads a ppm file into a texture map DD surface of the given format.  The
 * memory flag specifies DDSCAPS_SYSTEMMEMORY or DDSCAPS_VIDEOMEMORY.
 */
LPDIRECTDRAWSURFACE
D3DAppILoadSurface(LPDIRECTDRAW lpDD, LPCSTR lpName,
   LPDDSURFACEDESC lpFormat, DWORD memoryflag)
{
    LPDIRECTDRAWSURFACE lpDDS;
    DDSURFACEDESC ddsd, format;
    D3DCOLOR colors[256];
    D3DCOLOR c;
    DWORD dwWidth, dwHeight;
    int i, j;
    FILE *fp;
    char *lpC;
    LPDIRECTDRAWPALETTE lpDDPal;
    PALETTEENTRY ppe[256];
    int psize;
    DWORD pcaps;
    int color_count;
    BOOL bQuant = FALSE;
    HRESULT ddrval;

    /*
     * Find the image file and open it
     */
    fp = D3DAppIFindFile(lpName, "rb");
    if (fp == NULL) {
        D3DAppISetErrorString("Cannot find %s.\n", lpName);
return NULL;
    }
    /*
     * Parse the PPM header
     */
    if (!loadPPMHeader(fp, &dwWidth, &dwHeight, &i)) {
fclose(fp);
D3DAppISetErrorString("Could not load or parse PPM header in %s.\n", lpName);
return NULL;
    }
    /*
     * Create a surface of the given format using the dimensions of the PPM
     * file.
     */
    memcpy(&format, lpFormat, sizeof(DDSURFACEDESC));
    if (format.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) {
        bQuant = TRUE;
psize = 256;
pcaps = DDPCAPS_8BIT | DDPCAPS_ALLOW256;
    } else if (format.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4) {
        bQuant = TRUE;
psize = 16;
pcaps = DDPCAPS_4BIT;
    }
    memcpy(&ddsd, &format, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | memoryflag;
    ddsd.dwHeight = dwHeight;
    ddsd.dwWidth = dwWidth;

    ddrval = lpDD->lpVtbl->CreateSurface(lpDD, &ddsd, &lpDDS, NULL);
    if (ddrval != DD_OK) {
        D3DAppISetErrorString("CreateSurface for texture failed (loadtex).\n%s",
      D3DAppErrorToString(ddrval));
return NULL;
    }
    /*
     * Lock the surface so it can be filled with the PPM file
     */
    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddrval = lpDDS->lpVtbl->Lock(lpDDS, NULL, &ddsd, 0, NULL);
    if (ddrval != DD_OK) {
lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Lock failed while loading surface (loadtex).\n%s",
      D3DAppErrorToString(ddrval));
return NULL;
    }
    /*
     * The method of loading depends on the pixel format of the dest surface
     */
    if (!bQuant) {
/*
 * The texture surface is not palettized
 */
        unsigned long* lpLP;
unsigned short* lpSP;
unsigned char* lpCP;
        unsigned long m;
        int s;
        int red_shift, red_scale;
        int green_shift, green_scale;
        int blue_shift, blue_scale;
/*
 * Determine the red, green and blue masks' shift and scale.
 */
        for (s = 0, m = format.ddpfPixelFormat.dwRBitMask; !(m & 1);
       s++, m >>= 1);
        red_shift = s;
        red_scale = 255 / (format.ddpfPixelFormat.dwRBitMask >> s);
        for (s = 0, m = format.ddpfPixelFormat.dwGBitMask; !(m & 1);
       s++, m >>= 1);
        green_shift = s;
        green_scale = 255 / (format.ddpfPixelFormat.dwGBitMask >> s);
        for (s = 0, m = format.ddpfPixelFormat.dwBBitMask; !(m & 1);
       s++, m >>= 1);
        blue_shift = s;
        blue_scale = 255 / (format.ddpfPixelFormat.dwBBitMask >> s);
/*
 * Each RGB bit count requires different pointers
 */
switch (format.ddpfPixelFormat.dwRGBBitCount) {
    case 32 :
for (j = 0; j < (int)dwHeight; j++) {
    /*
     * Point to next row in texture surface
     */
    lpLP = (unsigned long*)(((char*)ddsd.lpSurface) +
    ddsd.lPitch * j);
    for (i = 0; i < (int)dwWidth; i++) {
int r, g, b;
/*
 * Read each value, scale it and shift it into position
 */
r = getc(fp) / red_scale;
g = getc(fp) / green_scale;
b = getc(fp) / blue_scale;
*lpLP = (r << red_shift) | (g << green_shift) |
(b << blue_shift);
lpLP++;
    }
}
break;
    case 16 :
for (j = 0; j < (int)dwHeight; j++) {
    lpSP = (unsigned short*)(((char*)ddsd.lpSurface) +
    ddsd.lPitch * j);
    for (i = 0; i < (int)dwWidth; i++) {
int r, g, b;
r = getc(fp) / red_scale;
g = getc(fp) / green_scale;
b = getc(fp) / blue_scale;
*lpSP = (r << red_shift) | (g << green_shift) |
(b << blue_shift);
lpSP++;
    }
}
break;
    case 8:
for (j = 0; j < (int)dwHeight; j++) {
    lpCP = (unsigned char*)(((char*)ddsd.lpSurface) +
    ddsd.lPitch * j);
    for (i = 0; i < (int)dwWidth; i++) {
int r, g, b;
r = getc(fp) / red_scale;
g = getc(fp) / green_scale;
b = getc(fp) / blue_scale;
*lpCP = (r << red_shift) | (g << green_shift) |
(b << blue_shift);
lpCP++;
    }
}
break;
    default:
/*
 * This wasn't a format I recognize
 */
    lpDDS->lpVtbl->Unlock(lpDDS, NULL);
fclose(fp);
lpDDS->lpVtbl->Release(lpDDS);
                D3DAppISetErrorString("Unknown pixel format (loadtex).");
return NULL;
}
/*
 * Unlock the texture and return the surface pointer
 */
lpDDS->lpVtbl->Unlock(lpDDS, NULL);
        fclose(fp);
        return (lpDDS);
    }

    /*
     * We assume the 8-bit palettized case
     */
    color_count = 0;/* number of colors in the texture */
    for (j = 0; j < (int)dwHeight; j++) {
/*
 * Point to next row in surface
 */
lpC = ((char*)ddsd.lpSurface) + ddsd.lPitch * j;
for (i = 0; i < (int)dwWidth; i++) {
    int r, g, b, k;
    /*
     * Get the next red, green and blue values and turn them into a
     * D3DCOLOR
     */
    r = getc(fp);
    g = getc(fp);
    b = getc(fp);
            c = RGB_MAKE(r, g, b);
    /*
     * Search for this color in a table of colors in this texture
     */
            for (k = 0; k < color_count; k++)
                if (c == colors[k]) break;
            if (k == color_count) {
/*
 * This is a new color, so add it to the list
 */
                color_count++;
/*
 * More than 256 and we fail (8-bit)
 */
                if (color_count > psize) {
    color_count--;
    k = color_count - 1;
                    //goto burst_colors;
}
                colors[k] = c;
            }
    /*
     * Set the "pixel" value on the surface to be the index into the
     * color table
     */
    if (psize == 16) {
if ((i & 1) == 0)
    *lpC = k & 0xf;
else {
    *lpC |= (k & 0xf) << 4;
    lpC++;
}
    } else {
*lpC = (char)k;
lpC++;
    }
        }
    }
    /*
     * Close the file and unlock the surface
     */
    fclose(fp);
    lpDDS->lpVtbl->Unlock(lpDDS, NULL);

//burst_colors:
    if (color_count > psize) {
/*
 * If there are more than 256 colors, we overran our palette
 */
        lpDDS->lpVtbl->Unlock(lpDDS, NULL);
lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Palette burst. (loadtex).\n");
return (NULL);
    }

    /*
     * Create a palette with the colors in our color table
     */
    memset(ppe, 0, sizeof(PALETTEENTRY) * 256);
    for (i = 0; i < color_count; i++) {
ppe[i].peRed = (unsigned char)RGB_GETRED(colors[i]);
        ppe[i].peGreen = (unsigned char)RGB_GETGREEN(colors[i]);
ppe[i].peBlue = (unsigned char)RGB_GETBLUE(colors[i]);
    }
    /*
     * Set all remaining entry flags to D3DPAL_RESERVED, which are ignored by
     * the renderer.
     */
    for (; i < 256; i++)
ppe[i].peFlags = D3DPAL_RESERVED;
    /*
     * Create the palette with the DDPCAPS_ALLOW256 flag because we want to
     * have access to all entries.
     */
    ddrval = lpDD->lpVtbl->CreatePalette(lpDD,
 DDPCAPS_INITIALIZE | pcaps,
 ppe, &lpDDPal, NULL);
    if (ddrval != DD_OK) {
        lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Create palette failed while loading surface (loadtex).\n%s",
      D3DAppErrorToString(ddrval));
return (NULL);
    }
    /*
     * Finally, bind the palette to the surface
     */
    ddrval = lpDDS->lpVtbl->SetPalette(lpDDS, lpDDPal);
    if (ddrval != DD_OK) {
lpDDS->lpVtbl->Release(lpDDS);
lpDDPal->lpVtbl->Release(lpDDPal);
        D3DAppISetErrorString("SetPalette failed while loading surface (loadtex).\n%s",
      D3DAppErrorToString(ddrval));
return (NULL);
    }

    lpDDPal->lpVtbl->Release(lpDDPal);

    return lpDDS;
}

static BOOL
ppm_getbyte(FILE *fp, char *newByte)
{
    char cchar;
    int cc;

    /* Get a byte, and dump comments */
    cchar = cc = getc(fp);
    if (cc == EOF) {
      return FALSE;
    }

    if (cchar == '#') {
/* Read until next end of line */
do {
    cchar = cc = getc(fp);
    if (cc == EOF)
return FALSE;
} while (cchar != '\n' && cchar != '\r');
    }

  *newByte = cchar;

  return TRUE;
}

static BOOL
ppm_getint(FILE *fp, int *newInt)
{
  int cint;
  char cchar;

  do {
    if (!ppm_getbyte(fp, &cchar)) return FALSE;
  } while (isspace(cchar));

  if (!isdigit(cchar)) {
    return FALSE;
  }

  cint = 0;

  do {
    cint = (cint * 10) + (cchar - '0');
    if (!ppm_getbyte(fp, &cchar)) return FALSE;
  } while(isdigit(cchar));

  *newInt = cint;

  return TRUE;
}

static BOOL
loadPPMHeader(FILE *fp, DWORD *width, DWORD *height, int *maxgrey)
{
    char magic[MAGICBYTES], cchar;

    /* Slurp up ppm header until we get width, height and maxgrey values */

    /* Read and check the magic bytes */
    if (fread(magic, MAGICBYTES, 1, fp) != 1)
return FALSE;
    if (magic[0] != 'P' || magic[1] != '6')
return FALSE;

    /* Now we can actually read some numbers */
    if (!ppm_getint(fp, width))
return FALSE;
    if (!ppm_getint(fp, height))
return FALSE;
    if (!ppm_getint(fp, maxgrey))
return FALSE;

    /* Slurp up rest of white space so we get to actual data */
    do {
if (!ppm_getbyte(fp, &cchar))
    return FALSE;
    } while (cchar == ' ' || cchar == '\t' || cchar == '\n' || cchar == '\r');

    fseek(fp, -1, SEEK_CUR);

    return TRUE;
}

/***************************************************************************/
/*                         Finding Textures                                */
/***************************************************************************/

#define MAXPATH    256
#define PATHSEP    ';'
#define FILESEP    '\\'
#define MAXCONTENTS 25
#define RESPATH     "Software\\Microsoft\\Direct3D"

static int PathListInitialised = FALSE;

/*
 * PathList structure
 * A list of directories in which to search for the texture.
 */
static struct {
    int count;
    char *contents[MAXCONTENTS];
} PathList;

/*
 * D3DAppIAddPathList
 * Add this string to the search path list
 */
static void
D3DAppIAddPathList(const char *path)
{
    char *p;
    char *elt;
    int len;

    while (path) {
p = LSTRCHR(path, PATHSEP);
if (p)
    len = p - path;
else
    len = lstrlen(path);
elt = (char *) malloc(len + 1);
if (elt == NULL)
    return;
lstrcpyn(elt, path, len + 1);
elt[len] = '\0';
PathList.contents[PathList.count] = elt;
PathList.count++;
if (p)
    path = p + 1;
else
    path = NULL;
if (PathList.count == MAXCONTENTS)
    return;
    }
    return;
}

/*
 * D3DAppIInitialisePathList
 * Create a search path with the D3DPATH env. var and D3D Path registry entry
 */
static void
D3DAppIInitialisePathList()
{
    long result;
    HKEY key;
    DWORD type, size;
    static char buf[512];
    char* path;

    if (PathListInitialised)
return;
    PathListInitialised = TRUE;

    PathList.count = 0;
    path = getenv("D3DPATH");
    D3DAppIAddPathList(".");
    if (path != NULL) {
        D3DAppIAddPathList(path);
        return;
    }
    result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RESPATH, 0, KEY_READ, &key);
    if (result == ERROR_SUCCESS) {
        size = sizeof(buf);
result = RegQueryValueEx(key, "D3D Path", NULL, &type, (LPBYTE) buf,
                         &size);
RegCloseKey(key);
if (result == ERROR_SUCCESS && type == REG_SZ)
    D3DAppIAddPathList(buf);
    }
}

/*
 * D3DAppIFindFile
 * Find and open a file using the current search path.
 */
static FILE*
D3DAppIFindFile(const char *name, const char *mode)
{
    FILE *fp;
    char buf[MAXPATH];
    static char filesep[] = {FILESEP, 0};
    int i;

    D3DAppIInitialisePathList();

    fp = fopen(name, mode);
    if (fp != NULL)
return fp;

    for (i = 0; i < PathList.count; i++) {
lstrcpy(buf, PathList.contents[i]);
lstrcat(buf, filesep);
lstrcat(buf, name);
fp = fopen(buf, mode);
if (fp)
    return fp;
    }
    return NULL;
}

/*
 * D3DAppIReleasePathList
 * Release the path list for program termination
 */
void
D3DAppIReleasePathList(void)
{
    int i;
    for (i = 0; i < PathList.count; i++) {
        free(PathList.contents[i]);
        PathList.contents[i] = NULL;
    }
    PathList.count = 0;
    PathListInitialised = FALSE;
}


TRIANGLE.C

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: triangle.c
 *
 ***************************************************************************/

#include <math.h>
#include <d3d.h>
#include "d3ddemo.h"

LPDIRECT3DMATERIAL2 lpBmat, lpMat1;
#define NUM_SQUARESTRIP 50
#define NUM_VERTICES (4 + (NUM_SQUARESTRIP + 1) * 2)
#define NUM_TRIANGLES 1
#define NUM_TSTRIPIDX ((NUM_SQUARESTRIP + 1) * 2 )
#define NUM_TFANIDX (NUM_TSTRIPIDX + 1)
#define NUM_INDICES (NUM_TSTRIPIDX + NUM_TFANIDX)
D3DTLVERTEX src_v[NUM_VERTICES];
WORD src_t[NUM_INDICES];
DWORD hTex;
DWORD hMat;

void CreateGridStrip(int x)
{
}

void
OverrideDefaults(Defaults* defaults)
{
    lstrcpy(defaults->Name, "Triangle D3D Example");
    defaults->rs.bPerspCorrect = FALSE;
    defaults->bResizingDisabled = FALSE;
}

BOOL
RenderScene(LPDIRECT3DDEVICE2 lpDev, LPDIRECT3DVIEWPORT2 lpView,
            LPD3DRECT lpExtent)
{
LPD3DTLVERTEX tstrip_v = src_v+3;
        D3DCLIPSTATUS status={D3DCLIPSTATUS_EXTENTS2, 0, 2048.0f, 0.0f, 2048.0f, 0.0f, 0.0f, 0.0f};

    if (lpDev->lpVtbl->BeginScene(lpDev) != D3D_OK)
        return FALSE;
    if (lpDev->lpVtbl->SetLightState(lpDev, D3DLIGHTSTATE_MATERIAL, hMat) != D3D_OK)
        return FALSE;
    if (lpDev->lpVtbl->SetRenderState(lpDev, D3DRENDERSTATE_TEXTUREHANDLE, hTex) != D3D_OK)
        return FALSE;
    if (lpDev->lpVtbl->SetClipStatus(lpDev, &status) != D3D_OK)
        return FALSE;
/*if (lpDev->lpVtbl->DrawIndexedPrimitive(lpDev, D3DPT_TRIANGLELIST,
   D3DVT_TLVERTEX, (LPVOID)src_v, 3, (LPWORD)src_t,
                                           NUM_TRIANGLES*3, D3DDP_WAIT) != D3D_OK)
return FALSE;
if (lpDev->lpVtbl->DrawIndexedPrimitive(lpDev, D3DPT_TRIANGLESTRIP,
   D3DVT_TLVERTEX, (LPVOID)tstrip_v, NUM_TSTRIPIDX, (LPWORD)src_t,
                                           NUM_TSTRIPIDX, D3DDP_WAIT) != D3D_OK)
return FALSE;
if (lpDev->lpVtbl->DrawIndexedPrimitive(lpDev, D3DPT_TRIANGLEFAN,
   D3DVT_TLVERTEX, (LPVOID)tstrip_v, NUM_TSTRIPIDX + 1, (LPWORD)src_t + NUM_TSTRIPIDX,
                                           NUM_TFANIDX, D3DDP_WAIT) != D3D_OK)
return FALSE;*/
if (lpDev->lpVtbl->DrawPrimitive(lpDev, D3DPT_TRIANGLEFAN,
                                           D3DVT_TLVERTEX, (LPVOID)tstrip_v, NUM_TSTRIPIDX, D3DDP_WAIT) != D3D_OK)
return FALSE;
/*if (lpDev->lpVtbl->DrawPrimitive(lpDev, D3DPT_TRIANGLESTRIP,
                                           D3DVT_TLVERTEX, (LPVOID)tstrip_v, NUM_TSTRIPIDX, D3DDP_WAIT) != D3D_OK)
return FALSE;
if (lpDev->lpVtbl->DrawPrimitive(lpDev, D3DPT_TRIANGLELIST,
                                           D3DVT_TLVERTEX, (LPVOID)src_v, 3, D3DDP_WAIT) != D3D_OK)
return FALSE;*/
    if (lpDev->lpVtbl->GetClipStatus(lpDev, &status) != D3D_OK)
        return FALSE;
    if (lpDev->lpVtbl->EndScene(lpDev) != D3D_OK)
        return FALSE;
lpExtent->x1 = (LONG)floor((double)status.minx);
lpExtent->x2 = (LONG)ceil((double)status.maxx);
lpExtent->y1 = (LONG)floor((double)status.miny);
lpExtent->y2 = (LONG)ceil((double)status.maxy);
    return TRUE;
}

void
ReleaseScene(void)
{
    return;
}

void
ReleaseView(LPDIRECT3DVIEWPORT2 lpView)
{
    lpView;
    RELEASE(lpMat1);
    RELEASE(lpBmat);
}

BOOL
InitScene(void)
{
int i;
LPD3DTLVERTEX tstrip_v = src_v+3;

    /*
     * Setup vertices for single triangle
     */
    memset(&src_v[0], 0, sizeof(D3DVERTEX) * NUM_VERTICES);
    src_v[0].sx = D3DVAL(10.0);
    src_v[0].sy = D3DVAL(10.0);
    src_v[0].sz = D3DVAL(0.1);
    src_v[0].rhw = D3DVAL(1.0);
    src_v[0].color = RGBA_MAKE(255, 0, 255, 255);
    src_v[0].specular = RGB_MAKE(0, 0, 255);
    src_v[0].tu = D3DVAL(0.0);
    src_v[0].tv = D3DVAL(0.0);
    src_v[1].sx = D3DVAL(300.0);
    src_v[1].sy = D3DVAL(50.0);
    src_v[1].sz = D3DVAL(0.9);
    src_v[1].rhw = D3DVAL(2.0);
    src_v[1].color = RGBA_MAKE(255, 255, 255, 255);
    src_v[1].specular = RGB_MAKE(0, 0, 0);
    src_v[1].tu = D3DVAL(1.0);
    src_v[1].tv = D3DVAL(1.0);
    src_v[2].sx = D3DVAL(150.0);
    src_v[2].sy = D3DVAL(180.0);
    src_v[2].sz = D3DVAL(0.6);
    src_v[2].rhw = D3DVAL(1.0);
    src_v[2].color = RGBA_MAKE(255, 255, 0, 255);
    src_v[2].specular = RGB_MAKE(0, 0, 0);
    src_v[2].tu = D3DVAL(0.0);
    src_v[2].tv = D3DVAL(1.0);

    /*
     * Setup vertices for triangle strip
     */
for (i=0; i<=NUM_SQUARESTRIP; ++i)
{
tstrip_v[i*2].sx = i*300.0f/NUM_SQUARESTRIP;
tstrip_v[i*2].sy = 200.0f;
tstrip_v[i*2].sz = .5f;
tstrip_v[i*2].rhw = 1.0f;
tstrip_v[i*2].color = RGBA_MAKE(255, 255, 255, 255);
tstrip_v[i*2].specular = RGB_MAKE(0, 0, 0);
tstrip_v[i*2].tu = (float)i/NUM_SQUARESTRIP;
tstrip_v[i*2].tv = 0.0f;
tstrip_v[i*2+1].sx = i*300.0f/NUM_SQUARESTRIP;
tstrip_v[i*2+1].sy = 0.0f;
tstrip_v[i*2+1].sz = .5f;
tstrip_v[i*2+1].rhw = 1.0f;
tstrip_v[i*2+1].color = RGBA_MAKE(255, 255, 255, 255);
tstrip_v[i*2+1].specular = RGB_MAKE(0, 0, 0);
tstrip_v[i*2+1].tu = (float)i/NUM_SQUARESTRIP;
tstrip_v[i*2+1].tv = 1.0f;
}
/* Set up base vertex for trifan */
tstrip_v[i*2].sx = 0.0f;
tstrip_v[i*2].sy = 100.0f;
tstrip_v[i*2].sz = .5f;
tstrip_v[i*2].rhw = 1.0f;
tstrip_v[i*2].color = RGBA_MAKE(255, 255, 255, 255);
tstrip_v[i*2].specular = RGB_MAKE(0, 0, 0);
tstrip_v[i*2].tu = 0.0f;
tstrip_v[i*2].tv = 0.5f;

/*
 * Set Up Triangle strip indices & base Tri Fan index
 */
for (i=0; i<=NUM_TSTRIPIDX; ++i)
src_t[i]=i;
/*
 * Set Up Triangle fan indices
 */
for (i=0; i<=NUM_SQUARESTRIP; ++i)
{
src_t[NUM_TSTRIPIDX+1+i]=i*2+1;
src_t[NUM_INDICES-1-i]=i*2;
}
    return TRUE;
}

BOOL
InitView(LPDIRECTDRAW lpDD, LPDIRECT3D2 lpD3D, LPDIRECT3DDEVICE2 lpDev,
   LPDIRECT3DVIEWPORT2 lpView, int NumTextures,
   LPD3DTEXTUREHANDLE TextureHandle)
{
    D3DMATERIAL bmat, mat;
    D3DMATERIALHANDLE hBmat, hMat1;

    if (lpD3D->lpVtbl->CreateMaterial(lpD3D, &lpBmat, NULL) != D3D_OK) {
return FALSE;
    }
    memset(&bmat, 0, sizeof(D3DMATERIAL));
    bmat.dwSize = sizeof(D3DMATERIAL);
    bmat.diffuse.r = (D3DVALUE)1.0;
    bmat.diffuse.g = (D3DVALUE)1.0;
    bmat.diffuse.b = (D3DVALUE)1.0;
    bmat.ambient.r = (D3DVALUE)1.0;
    bmat.ambient.g = (D3DVALUE)1.0;
    bmat.ambient.b = (D3DVALUE)1.0;
    bmat.hTexture = TextureHandle[0];
    bmat.dwRampSize = 1;
    lpBmat->lpVtbl->SetMaterial(lpBmat, &bmat);
    lpBmat->lpVtbl->GetHandle(lpBmat, lpDev, &hBmat);
    lpView->lpVtbl->SetBackground(lpView, hBmat);

    if (lpD3D->lpVtbl->CreateMaterial(lpD3D, &lpMat1, NULL) != D3D_OK) {
return FALSE;
    }
    memset(&mat, 0, sizeof(D3DMATERIAL));
    mat.dwSize = sizeof(D3DMATERIAL);
    mat.diffuse.r = (D3DVALUE)1.0;
    mat.diffuse.g = (D3DVALUE)1.0;
    mat.diffuse.b = (D3DVALUE)1.0;
    mat.ambient.r = (D3DVALUE)1.0;
    mat.ambient.g = (D3DVALUE)1.0;
    mat.ambient.b = (D3DVALUE)1.0;
#define SPECULAR
#ifdef SPECULAR
    mat.specular.r = (D3DVALUE)1.0;
    mat.specular.g = (D3DVALUE)1.0;
    mat.specular.b = (D3DVALUE)1.0;
    mat.power = (float)40.0;
#else
    mat.specular.r = (D3DVALUE)0.0;
    mat.specular.g = (D3DVALUE)0.0;
    mat.specular.b = (D3DVALUE)0.0;
    mat.power = (float)0.0;
#endif
    mat.hTexture = TextureHandle[1];
    mat.dwRampSize = 16;
    lpMat1->lpVtbl->SetMaterial(lpMat1, &mat);
    lpMat1->lpVtbl->GetHandle(lpMat1, lpDev, &hMat1);
hMat= (DWORD) hMat1;
hTex=TextureHandle[1];
    return TRUE;
}


README.TXT

Uvis
Direct3D Retained Mode Sample
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

This is an important example demonstrating how D3D can be used with
D3DRM. It demonstrates how a D3D immediate mode execute buffer can be
used as a visual in a D3D retained mode scene.


UVIS.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: uvis.cpp
 *
 ***************************************************************************/

#include <math.h>
#include <stdlib.h>
#include <d3d.h>
#include "d3dmacs.h"
#include "rmdemo.h"

typedef struct _Flame {
    intvalid;
    D3DVECTORvelocity;/* direction the flame is going */
    D3DVECTORorientation;/* random vector normal to velocity */
    D3DVECTORposition;/* current position */
    inttime;/* current time */
    intlifetime;/* time to die */
} Flame;

/*
 * A flame is a diamond shape oriented along its direction vector.
 * This updates the four vertices of the flame, given its current time.
 */
void UpdateFlame(Flame* flame, D3DVERTEX v[])
{
    D3DVECTOR dir = flame->velocity;
    D3DVECTOR off = flame->orientation;
    D3DVECTOR norm;
    D3DVECTOR start, end, left, right;
    double size;
    int i;

    D3DRMVectorNormalise(&dir);

    /*
     * Calculate a normal vector to the flame
     */
    D3DRMVectorCrossProduct(&norm, &dir, &off);
    D3DRMVectorNormalise(&norm);

    /*
     * The size starts off small, gets bigger towards the middle
     * and smaller towards the end.
     */
    if (flame->time < flame->lifetime / 2)
size = (double) flame->time / (double)(flame->lifetime / 2);
    else
size = ((double) (flame->lifetime - flame->time)
/ (double)(flame->lifetime / 2));

    /*
     * Calculate the four corners of the diamond.
     */
    D3DRMVectorScale(&dir, &dir, D3DVAL(size));
    D3DRMVectorScale(&off, &off, D3DVAL(size / 4));
    start = flame->position;
    D3DRMVectorAdd(&end, &start, &dir);
    D3DRMVectorScale(&dir, &dir, D3DVAL(0.5));
    D3DRMVectorAdd(&left, &start, &dir);
    right = left;
    D3DRMVectorAdd(&left, &left, &off);
    D3DRMVectorSubtract(&right, &right, &off);

    /*
     * Update the flame's position.
     */
    D3DRMVectorAdd(&flame->position, &flame->position, &flame->velocity);
    flame->time++;
    if (flame->time == flame->lifetime)
flame->valid = 0;

    /*
     * And fill in the vertices.  There are eight, four for each side of
     * the flame.
     */
    i = 0;
    v[i].x = start.x; v[i].y = start.y; v[i].z = start.z;
    v[i].nx = -norm.x; v[i].ny = -norm.y; v[i].nz = -norm.z;
    v[i].tu = D3DVAL(0); v[i].tv = D3DVAL(0);
    i++;

    v[i].x = left.x; v[i].y = left.y; v[i].z = left.z;
    v[i].nx = -norm.x; v[i].ny = -norm.y; v[i].nz = -norm.z;
    v[i].tu = D3DVAL(1); v[i].tv = D3DVAL(0);
    i++;

    v[i].x = end.x; v[i].y = end.y; v[i].z = end.z;
    v[i].nx = -norm.x; v[i].ny = -norm.y; v[i].nz = -norm.z;
    v[i].tu = D3DVAL(1); v[i].tv = D3DVAL(1);
    i++;

    v[i].x = right.x; v[i].y = right.y; v[i].z = right.z;
    v[i].nx = -norm.x; v[i].ny = -norm.y; v[i].nz = -norm.z;
    v[i].tu = D3DVAL(0); v[i].tv = D3DVAL(1);
    i++;

    v[i].x = start.x; v[i].y = start.y; v[i].z = start.z;
    v[i].nx = norm.x; v[i].ny = norm.y; v[i].nz = norm.z;
    v[i].tu = D3DVAL(0); v[i].tv = D3DVAL(0);
    i++;

    v[i].x = right.x; v[i].y = right.y; v[i].z = right.z;
    v[i].nx = norm.x; v[i].ny = norm.y; v[i].nz = norm.z;
    v[i].tu = D3DVAL(0); v[i].tv = D3DVAL(1);
    i++;

    v[i].x = end.x; v[i].y = end.y; v[i].z = end.z;
    v[i].nx = norm.x; v[i].ny = norm.y; v[i].nz = norm.z;
    v[i].tu = D3DVAL(1); v[i].tv = D3DVAL(1);
    i++;

    v[i].x = left.x; v[i].y = left.y; v[i].z = left.z;
    v[i].nx = norm.x; v[i].ny = norm.y; v[i].nz = norm.z;
    v[i].tu = D3DVAL(1); v[i].tv = D3DVAL(0);
    i++;

}

void InitFlame(Flame* flame)
{
    D3DVECTOR d, u;

    flame->valid = TRUE;

    do {
      D3DRMVectorRandom(&d);
      d.y = d.y * d.y;
      d.y = d.y * d.y;
    } while (d.y < D3DVAL(0.3));

    /*
     * Pick a vector normal to d
     */
    if (d.y != D3DVAL(0.0) || d.z != D3DVAL(0.0))
    {
u.x = D3DVAL(0.0);
if (d.y == D3DVAL(0.0))
{
    u.y = D3DVAL(1.0);
    u.z = D3DVAL(0.0);
} else {
    D3DVALUE n_fix =
D3DVAL(1.0)
+ D3DDivide(D3DMultiply(d.z, d.z), D3DMultiply(d.y, d.y));
    u.z = D3DVAL(sqrt(D3DDivide(D3DVAL(1.0), D3DVAL(n_fix))));
    u.y = -D3DMultiply(u.z, D3DDivide(d.z, d.y));
}
    } else {
u.x = D3DVAL(0.0);
u.y = D3DVAL(0.0);
u.z = D3DVAL(1.0);
    }

    /*
     * Randomize it.
     */
    D3DRMVectorRotate(&u, &u, &d, D3DDivide(D3DVAL(6.28 * (rand() % 100)),
        D3DVAL(100.0)));

    D3DRMVectorScale(&d, &d, D3DVAL(0.1));
    flame->velocity = d;
    flame->orientation = u;
    flame->position.x = D3DVAL(0);
    flame->position.y = D3DVAL(0);
    flame->position.z = D3DVAL(0);
    flame->time = 0;
    do {
flame->lifetime = rand() % 30;
    } while (flame->lifetime < 5);
}

#define MAX_FLAMES100

typedef struct _Fire {
    Flame flames[MAX_FLAMES];
    LPDIRECT3DRMDEVICE dev;
    LPDIRECT3DEXECUTEBUFFER eb;
    LPDIRECT3DMATERIAL mat;
} Fire;

void CDECL CleanupFireObjects(LPDIRECT3DRMOBJECT dev, void* arg)
{
    Fire* fire = (Fire*) arg;

    if (fire->eb) {
fire->eb->Release();
fire->mat->Release();
fire->eb = NULL;
fire->dev = NULL;
    }
}

typedef struct _FireExecuteBuffer {
    D3DVERTEXv[8 * MAX_FLAMES];
    D3DINSTRUCTIONop_state_light1;
    D3DSTATEstate1;
    D3DINSTRUCTIONop_set_status;
    D3DSTATUSsetstatus1;
    D3DINSTRUCTIONop_process_vertices1;
    D3DPROCESSVERTICESprocessvertices1;
    D3DINSTRUCTIONop_state_render;
    D3DSTATEstate2;
    D3DSTATEstate3;
    D3DSTATEstate4;
    D3DINSTRUCTIONop_triangle_list;
    D3DTRIANGLEtri[4 * MAX_FLAMES];
    D3DINSTRUCTIONexit1;
} FireExecuteBuffer;

BOOL CreateFireObjects(Fire* fire, LPDIRECT3DRMDEVICE dev)
{
    D3DEXECUTEBUFFERDESC desc;
    D3DEXECUTEDATA data;
    LPDIRECT3D lpD3D = NULL;
    LPDIRECT3DDEVICE lpD3DDev = NULL;
    LPDIRECT3DMATERIAL mat = NULL;
    LPDIRECT3DEXECUTEBUFFER eb = NULL;
    D3DMATERIALHANDLE hMat;
    D3DMATERIAL orange;
    void* p;
    int i;

    RELEASE(fire->eb);

    dev->GetDirect3DDevice(&lpD3DDev);
    if (!lpD3DDev)
goto generic_error;
    if (FAILED(lpD3DDev->GetDirect3D(&lpD3D)))
goto generic_error;

    desc.dwSize = sizeof(desc);
    desc.dwFlags = D3DDEB_BUFSIZE;
    desc.dwBufferSize = sizeof(FireExecuteBuffer);

    if (FAILED(lpD3DDev->CreateExecuteBuffer(&desc, &eb, NULL)))
goto generic_error;

    if (FAILED(lpD3D->CreateMaterial(&mat, NULL)))
goto generic_error;
    if (FAILED(mat->GetHandle(lpD3DDev, &hMat)))
goto generic_error;

    memset(&orange, 0, sizeof(orange));
    orange.dwSize = sizeof(orange);
    orange.diffuse.r = D3DVAL(1.0);
    orange.diffuse.g = D3DVAL(0.5);
    orange.diffuse.b = D3DVAL(0.0);
    orange.ambient.r = D3DVAL(1.0);
    orange.ambient.g = D3DVAL(0.5);
    orange.ambient.b = D3DVAL(0.0);
    orange.dwRampSize = 32;
    if (FAILED(mat->SetMaterial(&orange)))
goto generic_error;

    if (FAILED(eb->Lock(&desc)))
goto generic_error;
    p = (void*) ((char*) desc.lpData + 8 * MAX_FLAMES * sizeof(D3DVERTEX));

    OP_STATE_LIGHT(1, p);
STATE_DATA(D3DLIGHTSTATE_MATERIAL, hMat, p);

    OP_SET_STATUS(D3DSETSTATUS_ALL, D3DSTATUS_DEFAULT, 2048, 2048, 0, 0, p);

    OP_PROCESS_VERTICES(1, p);
PROCESSVERTICES_DATA(D3DPROCESSVERTICES_TRANSFORMLIGHT,
 0, 8 * MAX_FLAMES, p);
    OP_STATE_RENDER(3, p);
STATE_DATA(D3DRENDERSTATE_SHADEMODE, D3DSHADE_FLAT, p);
STATE_DATA(D3DRENDERSTATE_TEXTUREHANDLE, 0, p);
STATE_DATA(D3DRENDERSTATE_BLENDENABLE, FALSE, p);

    OP_TRIANGLE_LIST(4 * MAX_FLAMES, p);
    for (i = 0; i < MAX_FLAMES; i++) {
D3DTRIANGLE* t;
int base;

t = (D3DTRIANGLE*) p;
base = 4 * i;

t->v1 = base + 0;
t->v2 = base + 1;
t->v3 = base + 3;
t->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
t++;

t->v1 = base + 1;
t->v2 = base + 2;
t->v3 = base + 3;
t->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
t++;

t->v1 = base + 0;
t->v2 = base + 1;
t->v3 = base + 3;
t->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
t++;

t->v1 = base + 1;
t->v2 = base + 2;
t->v3 = base + 3;
t->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
t++;

p = (char*) t;
    }
    OP_EXIT(p);

    if (FAILED(eb->Unlock()))
goto generic_error;

    data.dwSize = sizeof(data);
    data.dwVertexOffset = 0;
    data.dwVertexCount = 8 * MAX_FLAMES;
    data.dwInstructionOffset = 8 * MAX_FLAMES * sizeof(D3DVERTEX);
    data.dwInstructionLength = sizeof(FireExecuteBuffer) - data.dwInstructionOffset;
    data.dwHVertexOffset = 0;
    if (FAILED(eb->SetExecuteData(&data)))
goto generic_error;

    fire->eb = eb;
    fire->mat = mat;
    fire->dev = dev;
    if (FAILED(dev->AddDestroyCallback(CleanupFireObjects, fire)))
goto generic_error;

    RELEASE(lpD3DDev);
    RELEASE(lpD3D);
    return TRUE;
generic_error:
    RELEASE(lpD3D);
    RELEASE(lpD3DDev);
    RELEASE(mat);
    RELEASE(eb);
    return FALSE;
}

BOOL RenderFire(Fire* fire, LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view)
{
    D3DVERTEX* v;
    D3DEXECUTEBUFFERDESC desc;
    D3DEXECUTEDATA data;
    LPDIRECT3DDEVICE lpD3DDev = NULL;
    LPDIRECT3DVIEWPORT lpD3DView = NULL;
    int i;

    if (fire->dev != dev) {
if (!CreateFireObjects(fire, dev))
    return FALSE;
    }

    dev->GetDirect3DDevice(&lpD3DDev);
    view->GetDirect3DViewport(&lpD3DView);
    if (!lpD3DDev || !lpD3DView)
goto ret_with_error;

    for (i = 0; i < MAX_FLAMES; i++)
if (!fire->flames[i].valid)
    InitFlame(&fire->flames[i]);

    desc.dwSize = sizeof(desc);
    desc.dwFlags = 0;

    if (FAILED(fire->eb->Lock(&desc)))
goto ret_with_error;
    v = (D3DVERTEX*) desc.lpData;

    for (i = 0; i < MAX_FLAMES; i++)
UpdateFlame(&fire->flames[i], &v[8 * i]);

    if (FAILED(fire->eb->Unlock()))
goto ret_with_error;

    if (FAILED(lpD3DDev->Execute(fire->eb, lpD3DView, D3DEXECUTE_CLIPPED)))
goto ret_with_error;

    data.dwSize = sizeof data;
    if (FAILED(fire->eb->GetExecuteData(&data)))
goto ret_with_error;
    if (FAILED(view->ForceUpdate(data.dsStatus.drExtent.x1,
      data.dsStatus.drExtent.y1,
      data.dsStatus.drExtent.x2,
      data.dsStatus.drExtent.y2)))
      goto ret_with_error;

    RELEASE(lpD3DDev);
    RELEASE(lpD3DView);
    return TRUE;
ret_with_error:
    RELEASE(lpD3DDev);
    RELEASE(lpD3DView);
    return FALSE;
}

int CDECL FireCallback(LPDIRECT3DRMUSERVISUAL uvis,
 void* arg,
 D3DRMUSERVISUALREASON reason,
 LPDIRECT3DRMDEVICE dev,
 LPDIRECT3DRMVIEWPORT view)
{
    Fire* fire = (Fire*) arg;

    if (reason == D3DRMUSERVISUAL_CANSEE)
return TRUE;

    if (reason == D3DRMUSERVISUAL_RENDER) {
if (!RenderFire(fire, dev, view))
    return DDERR_GENERIC;
else
    return D3D_OK;
    }

    return 0;
}

void CDECL DestroyFire(LPDIRECT3DRMOBJECT obj, void* arg)
{
    Fire* fire = (Fire*) arg;

    if (fire->dev)
fire->dev->DeleteDestroyCallback(CleanupFireObjects, arg);
    CleanupFireObjects((LPDIRECT3DRMOBJECT)fire->dev, (void*) fire);
    free(fire);
}

LPDIRECT3DRMUSERVISUAL CreateFire()
{
    Fire* fire;
    LPDIRECT3DRMUSERVISUAL uvis = NULL;

    fire = (Fire*)malloc(sizeof(Fire));
    if (!fire)
goto ret_with_error;
    memset(fire, 0, sizeof(Fire));

    if (FAILED(lpD3DRM->CreateUserVisual(FireCallback, (void*) fire, &uvis)))
goto ret_with_error;
    if (FAILED(uvis->AddDestroyCallback(DestroyFire, (void*) fire)))
goto ret_with_error;
    return uvis;
ret_with_error:
    if (fire)
free(fire);
    RELEASE(uvis);
    return NULL;
}

BOOL
BuildScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view,
   LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera)
{
    D3DRMRENDERQUALITY quality = D3DRMRENDER_GOURAUD;
    LPDIRECT3DRMFRAME lights = NULL;
    LPDIRECT3DRMFRAME frame = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    LPDIRECT3DRMUSERVISUAL uvis = NULL;

    view = view;/* not used */

    if (FAILED(dev->SetQuality(quality)))
goto generic_error;

    /*
     * initialize the lights in the scene
     */
    if (FAILED(lpD3DRM->CreateFrame(scene, &lights)))
goto generic_error;
    if (FAILED(lights->SetPosition(scene, D3DVAL(5), D3DVAL(5), -D3DVAL(1))))
goto generic_error;
    if(FAILED(lights->SetOrientation(scene, D3DVAL(0), D3DVAL(0), D3DVAL(1),
   D3DVAL(0), D3DVAL(1), D3DVAL(1))))
   goto generic_error;
    if(FAILED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.9),
  D3DVAL(0.8), D3DVAL(0.7), &light1)))
  goto generic_error;
    if (FAILED(lights->AddLight(light1)))
goto generic_error;
    if(FAILED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL(0.1),
  D3DVAL(0.1), D3DVAL(0.1), &light2)))
  goto generic_error;
    if (FAILED(scene->AddLight(light2)))
goto generic_error;

    /*
     * create a frame within the scene
     */
    if (FAILED(lpD3DRM->CreateFrame(scene, &frame)))
goto generic_error;

    /*
     * add the fire into the frame
     */
    uvis = CreateFire();
    if (!uvis)
goto generic_error;
    if (FAILED(frame->AddVisual(uvis)))
goto generic_error;

    /*
     * set up the frames position, orientation and rotation
     */
    if (FAILED(camera->SetPosition(scene, D3DVAL(0), D3DVAL(0.5), -D3DVAL(10))))
goto generic_error;
    if(FAILED(camera->SetOrientation(scene, D3DVAL(0), D3DVAL(0), D3DVAL(1), D3DVAL(0),
   D3DVAL(1), D3DVAL(0))))
   goto generic_error;
    if(FAILED(frame->SetRotation(scene, D3DVAL(0), D3DVAL(1), D3DVAL(0),
       D3DVAL(0.02))))
       goto generic_error;

    RELEASE(uvis);
    RELEASE(frame);
    RELEASE(lights);
    RELEASE(light1);
    RELEASE(light2);
    return TRUE;
generic_error:
    Msg("A failure occurred while building the scene.\n");
    RELEASE(lights);
    RELEASE(frame);
    RELEASE(light1);
    RELEASE(light2);
    RELEASE(uvis);
    return FALSE;
}

void
OverrideDefaults(Defaults *defaults)
{
    defaults->bConstRenderQuality = TRUE;
    defaults->bNoTextures = TRUE;
    lstrcpy(defaults->Name, "User Visual Direct3DRM Example");
}


README.TXT

Direct3DRM Object Viewer
Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.

This is a stand alone D3DRM app that allows you to load and view 3d
objects.

The initial scene has a light source in the top right. To see this the
view can be moved backwards and forwards using the T and R
keys. Additional objects can be loaded from the file menu, and new
light sources can be added from the lights menu.  Objects can be
rotated with the left mouse button and dragged with the right.

The last object that was rotated or dragged remains the current
selection.

The device quality can be altered using the Renderer menu. Using this
menu the type of device used can also be switched from ramp to
RGB. When using the RGB device the color of lights in the scene can be
changed in the same way as you change the color of objects. In 256
color mode it is advisable to dither an RGB device for best results.

Additional keyboard controls

T Forwards
R Back
Z Move current selection forwards
X Move current selection back.
Ctrl+G Gouraud shade
Ctrl+F Flat Shade
Ctrl+D Dither toggle.
Arrow Keys Move left/right/up/down
Delete Delete the current selection.


FILE.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: file.cpp
 *
 ***************************************************************************/

#include <d3drmwin.h>
#include "viewer.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <commdlg.h>

char* OpenNewFile( HWND hwnd, const char *wndTitle )
{
    static char file[256];
    static char fileTitle[256];
    static char filter[] = "X files (*.x)\0*.x\0"
   "All Files (*.*)\0*.*\0";
    OPENFILENAME ofn;

    lstrcpy( file, "");
    lstrcpy( fileTitle, "");

    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hwnd;
#ifdef WIN32
    ofn.hInstance         = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);
#else
    ofn.hInstance         = (HINSTANCE) GetWindowWord(hwnd, GWW_HINSTANCE);
#endif
    ofn.lpstrFilter       = filter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter    = 0L;
    ofn.nFilterIndex      = 1L;
    ofn.lpstrFile         = file;
    ofn.nMaxFile          = sizeof(file);
    ofn.lpstrFileTitle    = fileTitle;
    ofn.nMaxFileTitle     = sizeof(fileTitle);
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrTitle        = wndTitle;
    ofn.nFileOffset       = 0;
    ofn.nFileExtension    = 0;
    ofn.lpstrDefExt       = "*.x";
    ofn.lCustData         = 0;

    ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    if (GetOpenFileName(&ofn))
        return (char*)ofn.lpstrFile;
    else
        return NULL;
}


GETDXVER.CPP

/**************************************************************************

    GetDXVer.cpp

    Demonstrates how applications can detect what version of DirectX
    is installed.

 **************************************************************************/
/**************************************************************************

    (C) Copyright 1995-1997 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and
    distribute the Sample Files (and/or any modified version) in
    any way you find useful, provided that you agree that
    Microsoft has no warranty obligations or liability for any
    Sample Application Files which are modified.

 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
#include <dinput.h>
#include <d3drm.h>
#include "viewer.h"

/****************************************************************************
 *
 *      GetDXVersion
 *
 *  This function returns two arguments:
 *dwDXVersion:
 *    0    No DirectX installed
 *    0x100   DirectX version 1 installed
 *    0x200   DirectX 2 installed
 *    0x300   DirectX 3 installed
 *    0x500   At least DirectX 5 installed.
 *    0x501   At least DirectX 5a installed.
 *dwDXPlatform:
 *    0                            Unknown (This is a failure case. Should never happen)
 *    VER_PLATFORM_WIN32_WINDOWS    Windows 9X platform
 *    VER_PLATFORM_WIN32_NT    Windows NT platform
 *
 ****************************************************************************/

typedef HRESULT (WINAPI *DIRECTDRAWCREATE)(GUID *, LPDIRECTDRAW *, IUnknown *);
typedef HRESULT (WINAPI *DIRECTINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUT *, IUnknown *);

void WINAPI GetDXVersion(LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform)
{
    HRESULT    hr;
    HINSTANCE    DDHinst = 0;
    HINSTANCE    DIHinst = 0;
    LPDIRECTDRAW    pDDraw = 0;
    LPDIRECTDRAW2    pDDraw2 = 0;
    DIRECTDRAWCREATE    DirectDrawCreate = 0;
    DIRECTINPUTCREATE    DirectInputCreate = 0;
    OSVERSIONINFO    osVer;
    LPDIRECTDRAWSURFACE    pSurf = 0;
    LPDIRECTDRAWSURFACE3    pSurf3 = 0;

    /*
     * First get the windows platform
     */
    osVer.dwOSVersionInfoSize = sizeof(osVer);
    if (!GetVersionEx(&osVer))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
return;
    }

    if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
*pdwDXPlatform = VER_PLATFORM_WIN32_NT;
/*
 * NT is easy... NT 4.0 is DX2, 4.0 SP3 is DX3, 5.0 is DX5
 * and no DX on earlier versions.
 */
if (osVer.dwMajorVersion < 4)
{
    *pdwDXPlatform = 0;//No DX on NT3.51 or earlier
    return;
}
if (osVer.dwMajorVersion == 4)
{
    /*
     * NT4 up to SP2 is DX2, and SP3 onwards is DX3, so we are at least DX2
     */
    *pdwDXVersion = 0x200;

            /*
             * We're not supposed to be able to tell which SP we're on, so check for dinput
             */
            DIHinst = LoadLibrary("DINPUT.DLL");
            if (DIHinst == 0)
            {
                /*
                 * No DInput... must be DX2 on NT 4 pre-SP3
                 */
                OutputDebugString("Couldn't LoadLibrary DInput\r\n");
        return;
            }

            DirectInputCreate = (DIRECTINPUTCREATE)
                                    GetProcAddress(DIHinst, "DirectInputCreateA");
            FreeLibrary(DIHinst);

            if (DirectInputCreate == 0)
            {
                /*
                 * No DInput... must be pre-SP3 DX2
                 */
                OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
        return;
            }

    /*
     * It must be NT4, DX2
     */
    *pdwDXVersion = 0x300; //DX3 on NT4 SP3 or higher
    return;
}
/*
 * Else it's NT5 or higher, and it's DX5a or higher:
 */
*pdwDXVersion = 0x501; //DX5a on NT5
return;
    }

    /*
     * Not NT... must be Win9x
     */
    *pdwDXPlatform = VER_PLATFORM_WIN32_WINDOWS;

    /*
     * If we are on Memphis or higher, then we are at least DX5a
     */
    if ( (osVer.dwBuildNumber & 0xffff) > 1353) //Check for higher than developer release
    {
*pdwDXVersion = 0x501; //DX5a on Memphis or higher
return;
    }

    /*
     * Now we know we are in Windows 9x (or maybe 3.1), so anything's possible.
     * First see if DDRAW.DLL even exists.
     */
    DDHinst = LoadLibrary("DDRAW.DLL");
    if (DDHinst == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
return;
    }

    /*
     *  See if we can create the DirectDraw object.
     */
    DirectDrawCreate = (DIRECTDRAWCREATE)
                            GetProcAddress(DDHinst, "DirectDrawCreate");
    if (DirectDrawCreate == 0)
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't LoadLibrary DDraw\r\n");
return;
    }

    hr = DirectDrawCreate(NULL, &pDDraw, NULL);
    if (FAILED(hr))
    {
*pdwDXVersion = 0;
*pdwDXPlatform = 0;
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't create DDraw\r\n");
return;
    }

    /*
     *  So DirectDraw exists.  We are at least DX1.
     */
    *pdwDXVersion = 0x100;

    /*
     *  Let's see if IID_IDirectDraw2 exists.
     */
    hr = pDDraw->QueryInterface(IID_IDirectDraw2, (LPVOID *)&pDDraw2);
    if (FAILED(hr))
    {
/*
 * No IDirectDraw2 exists... must be DX1
 */
pDDraw->Release();
FreeLibrary(DDHinst);
        OutputDebugString("Couldn't QI DDraw2\r\n");
return;
    }
    /*
     * IDirectDraw2 exists. We must be at least DX2
     */
    pDDraw2->Release();
    *pdwDXVersion = 0x200;

    /*
     *  See if we can create the DirectInput object.
     */
    DIHinst = LoadLibrary("DINPUT.DLL");
    if (DIHinst == 0)
    {
        /*
         * No DInput... must be DX2
         */
        OutputDebugString("Couldn't LoadLibrary DInput\r\n");
pDDraw->Release();
FreeLibrary(DDHinst);
return;
    }

    DirectInputCreate = (DIRECTINPUTCREATE)
                            GetProcAddress(DIHinst, "DirectInputCreateA");
    FreeLibrary(DIHinst);

    if (DirectInputCreate == 0)
    {
        /*
         * No DInput... must be DX2
         */
FreeLibrary(DDHinst);
pDDraw->Release();
        OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n");
return;
    }

    /*
     * DirectInputCreate exists. That's enough to tell us that we are at least DX3
     */
    *pdwDXVersion = 0x300;

    /*
     * Checks for 3a vs 3b?
     */

    /*
     * We can tell if DX5 is present by checking for the existence of IDirectDrawSurface3.
     * First we need a surface to QI off of.
     */
    DDSURFACEDESC desc;

    ZeroMemory(&desc, sizeof(desc));
    desc.dwSize = sizeof(desc);
    desc.dwFlags = DDSD_CAPS;
    desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    hr = pDDraw->SetCooperativeLevel(NULL,DDSCL_NORMAL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't Set coop level\r\n");
return;
    }

    hr = pDDraw->CreateSurface(&desc, &pSurf, NULL);
    if (FAILED(hr))
    {
/*
 * Failure. This means DDraw isn't properly installed.
 */
pDDraw->Release();
FreeLibrary(DDHinst);
*pdwDXVersion = 0;
        OutputDebugString("Couldn't CreateSurface\r\n");
return;
    }

    /*
     * Try for the IDirectDrawSurface3 interface. If it works, we're on DX5 at least
     */
    if ( FAILED(pSurf->QueryInterface(IID_IDirectDrawSurface3,(LPVOID*)&pSurf3)) )
    {
        pDDraw->Release();
        FreeLibrary(DDHinst);

        return;
    }

    /*
     * QI for IDirectDrawSurface3 succeeded. We must be at least DX5
     */
    *pdwDXVersion = 0x500;

    pSurf->Release();
    pDDraw->Release();
    FreeLibrary(DDHinst);

    return;
}


COLOR.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: color.cpp
 *
 ***************************************************************************/

#include <d3drmwin.h>
#include "viewer.h"
#include <windows.h>
#include <string.h>
#include <commdlg.h>
#include <d3drm.h>

static DWORD dwNewAlpha;

VOID
setAlphaDialog(HWND hdlg, DWORD dwVal)
{
    TCHAR szVal[5];

    wsprintf(szVal, "%d", dwVal);
    SendDlgItemMessage(hdlg, COLOR_ALPHA, EM_SETSEL,
       (WPARAM)0, (LPARAM)-1);
    SendDlgItemMessage(hdlg, COLOR_ALPHA, EM_REPLACESEL,
       (WPARAM)FALSE, (LPARAM)szVal);
}

#define COLOR_OK0
#define COLOR_BADVAL1
#define COLOR_NONEWVAL2

DWORD
getAlphaFromDialog(HWND hdlg, LPDWORD pdwAlpha)
{
    WORD cchAlpha;
    TCHAR szAlpha[5];
    TCHAR* pszStop;
    DWORD dwAlpha;

    cchAlpha = (WORD)SendDlgItemMessage(hdlg, COLOR_ALPHA,
EM_LINELENGTH,
(WPARAM)0, (LPARAM)0);
    if (cchAlpha > 3)
    {
return (COLOR_BADVAL);
    }

    if (cchAlpha == 0)
    {
return (COLOR_NONEWVAL);
    }

    *szAlpha = (char)cchAlpha;
    SendDlgItemMessage(hdlg, COLOR_ALPHA, EM_GETLINE,
       (WPARAM)0, (LPARAM)szAlpha);
    szAlpha[cchAlpha] = '\0';
    pszStop = &szAlpha[cchAlpha];
    dwAlpha = strtoul(szAlpha, &pszStop, 10);
    if (dwAlpha > 255)
    {
return (COLOR_BADVAL);
    }

    *pdwAlpha = dwAlpha;

    return (COLOR_OK);
}

UINT APIENTRY CCHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    DWORD dwAlpha;
    DWORD dwRet;

    switch (uiMsg)
    {
case WM_INITDIALOG:
    setAlphaDialog(hdlg, dwNewAlpha);
    break;

case WM_COMMAND:
    if (wParam == IDOK)
    {
/*
 * Get the Alpha Value
 */
dwRet = getAlphaFromDialog(hdlg, &dwAlpha);
switch (dwRet)
{
    case COLOR_NONEWVAL:
    break;
    case COLOR_OK:
    dwNewAlpha = dwAlpha;
break;
    case COLOR_BADVAL:
setAlphaDialog(hdlg, dwNewAlpha);
break;
}
    }

    break;
    }

    return 0; /* Always give default behaviour */
}

int ChooseNewColor(HWND win, D3DCOLOR* current)
{
    CHOOSECOLOR cc;
    COLORREF clr;
    COLORREF aclrCust[16];
    int i;

    for (i = 0; i < 16; i++)
aclrCust[i] = RGB(255, 255, 255);

    clr =
RGB
(   (int) (255 * D3DRMColorGetRed(*current)),
    (int) (255 * D3DRMColorGetGreen(*current)),
    (int) (255 * D3DRMColorGetBlue(*current))
);

    memset(&cc, 0, sizeof(CHOOSECOLOR));
    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = win;
    cc.hInstance = (HWND)GetWindowLong(win, GWL_HINSTANCE);
    cc.rgbResult = clr;
    cc.lpCustColors = aclrCust;
    cc.Flags = CC_RGBINIT|CC_FULLOPEN|CC_ENABLETEMPLATE|CC_ENABLEHOOK;
    cc.lpfnHook = CCHookProc;
    cc.lpTemplateName = "ViewerChooseColor";
    dwNewAlpha = (DWORD) (255 * D3DRMColorGetAlpha(*current));

    if (ChooseColor(&cc))
    {*current =
    D3DRMCreateColorRGBA
    (D3DVAL(GetRValue(cc.rgbResult) / D3DVAL(255.0)),
D3DVAL(GetGValue(cc.rgbResult) / D3DVAL(255.0)),
D3DVAL(GetBValue(cc.rgbResult) / D3DVAL(255.0)),
D3DVAL(dwNewAlpha / D3DVAL(255.0))
    );
return TRUE;
    }
    else return FALSE;
}


RODCONE.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rodcone.cpp
 *
 ***************************************************************************/

/*
 * Sample code for building objects out of rods and cones.
 */

#include <d3drmwin.h>
#include "viewer.h"
#include "rodcone.h"
#include <math.h>

static unsigned long rod_faces[] =
{   8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, /* end 1 */
    4, 0, 0, 1, 1, 9, 1, 8, 0,/* side 0 */
    4, 1, 1, 2, 2, 10, 2, 9, 1,/* side 1 */
    4, 2, 2, 3, 3, 11, 3, 10, 2, /* side 2 */
    4, 3, 3, 4, 4, 12, 4, 11, 3, /* side 3 */
    4, 4, 4, 5, 5, 13, 5, 12, 4, /* side 4 */
    4, 5, 5, 6, 6, 14, 6, 13, 5, /* side 5 */
    4, 6, 6, 7, 7, 15, 7, 14, 6, /* side 6 */
    4, 7, 7, 0, 0, 8, 0, 15, 7,/* side 7 */
    8, 8, 0, 9, 1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7, /* end 2 */
    0,
};

void AddRod(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b)
{
    D3DVECTOR d, u, r;
    D3DVECTOR v[16];
    D3DVECTOR n[8];
    D3DVALUE f;
    int i;

    /*
     * Find the unit vector along the rod.
     */
    d.x = b.x - a.x;
    d.y = b.y - a.y;
    d.z = b.z - a.z;
    D3DRMVectorNormalise(&d);

    /*
     * Pick a vector normal to d
     */
    if (d.y != D3DVAL(0.0) || d.z != D3DVAL(0.0))
    {u.x = D3DVAL(0.0);
if (d.y == D3DVAL(0.0))
{   u.y = D3DVAL(1.0);
    u.z = D3DVAL(0.0);
} else
{   D3DVALUE n_fix =
D3DVAL(1.0)
    +D3DDivide(D3DMultiply(d.z, d.z), D3DMultiply(d.y, d.y));
#ifdef FIXED_POINT_API
    double un_val = (double)n_fix / (double)(1<<16);
    u.z = D3DVAL(sqrt(1/un_val));
#else
    u.z = D3DVAL(sqrt(D3DDivide(D3DVAL(1.0), D3DVAL(n_fix))));
#endif
    u.y = -D3DMultiply(u.z, D3DDivide(d.z, d.y));
}
    } else
    {u.x = D3DVAL(0.0);
u.y = D3DVAL(0.0);
u.z = D3DVAL(1.0);
    }

    /*
     * Now find a vector normal to them both, to give us a coordinate
     * system in the plane normal to the rod.
     */
    D3DRMVectorCrossProduct(&r, &d, &u);

    /*
     * Scale down the coordinates to the radius of the rod.
     */
    u.x = D3DMultiply(u.x, radius);
    u.y = D3DMultiply(u.y, radius);
    u.z = D3DMultiply(u.z, radius);
    r.x = D3DMultiply(r.x, radius);
    r.y = D3DMultiply(r.y, radius);
    r.z = D3DMultiply(r.z, radius);

    /*
     * Calculate the corners of an octagon.
     */
    f = D3DVAL((float)sqrt(2.0) / (2 * (1 + (float)sqrt(2.0) / 2)));
    v[0].x = u.x + D3DMultiply(r.x, f);
    v[0].y = u.y + D3DMultiply(r.y, f);
    v[0].z = u.z + D3DMultiply(r.z, f);

    v[1].x = D3DMultiply(u.x, f) + r.x;
    v[1].y = D3DMultiply(u.y, f) + r.y;
    v[1].z = D3DMultiply(u.z, f) + r.z;

    v[2].x = D3DMultiply(-u.x, f) + r.x;
    v[2].y = D3DMultiply(-u.y, f) + r.y;
    v[2].z = D3DMultiply(-u.z, f) + r.z;

    v[3].x = -u.x + D3DMultiply(r.x, f);
    v[3].y = -u.y + D3DMultiply(r.y, f);
    v[3].z = -u.z + D3DMultiply(r.z, f);

    v[4].x = -u.x - D3DMultiply(r.x, f);
    v[4].y = -u.y - D3DMultiply(r.y, f);
    v[4].z = -u.z - D3DMultiply(r.z, f);

    v[5].x = D3DMultiply(-u.x, f) - r.x;
    v[5].y = D3DMultiply(-u.y, f) - r.y;
    v[5].z = D3DMultiply(-u.z, f) - r.z;

    v[6].x = D3DMultiply(u.x, f) - r.x;
    v[6].y = D3DMultiply(u.y, f) - r.y;
    v[6].z = D3DMultiply(u.z, f) - r.z;

    v[7].x = u.x - D3DMultiply(r.x, f);
    v[7].y = u.y - D3DMultiply(r.y, f);
    v[7].z = u.z - D3DMultiply(r.z, f);

    /*
     * Add the rod endpoints and calculate the vertex normals.
     */
    for (i = 0; i < 8; i++)
    {n[i] = v[i];
D3DRMVectorNormalise(&n[i]);
v[i + 8].x = v[i].x + b.x;
v[i + 8].y = v[i].y + b.y;
v[i + 8].z = v[i].z + b.z;
v[i].x += a.x;
v[i].y += a.y;
v[i].z += a.z;
    }

    /*
     * Now add the faces.
     */
    mesh->AddFaces(16, v, 8, n, rod_faces, NULL);
}

static unsigned long cone_faces[] =
{   8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, /* end 1 */
    3, 0, 0, 1, 1, 8, 1,/* side 0 */
    3, 1, 1, 2, 2, 8, 1,/* side 1 */
    3, 2, 2, 3, 3, 8, 1, /* side 2 */
    3, 3, 3, 4, 4, 8, 1, /* side 3 */
    3, 4, 4, 5, 5, 8, 1, /* side 4 */
    3, 5, 5, 6, 6, 8, 1, /* side 5 */
    3, 6, 6, 7, 7, 8, 1, /* side 6 */
    3, 7, 7, 0, 0, 8, 1,/* side 7 */
    0,
};

void AddCone(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b)
{
    D3DVECTOR d, u, r;
    D3DVECTOR v[16];
    D3DVECTOR n[8];
    D3DVALUE f;
    int i;

    /*
     * Find the unit vector along the rod.
     */
    d.x = b.x - a.x;
    d.y = b.y - a.y;
    d.z = b.z - a.z;
    D3DRMVectorNormalise(&d);

    /*
     * Pick a vector normal to d
     */
    if (d.y != D3DVAL(0.0) || d.z != D3DVAL(0.0))
    {u.x = D3DVAL(0.0);
if (d.y == D3DVAL(0.0))
{   u.y = D3DVAL(1.0);
    u.z = D3DVAL(0.0);
} else
{   D3DVALUE n_fix =
D3DVAL(1.0)
    +D3DDivide(D3DMultiply(d.z, d.z), D3DMultiply(d.y, d.y));
#ifdef FIXED_POINT_API
    double un_val = (double)n_fix / (double)(1<<16);
    u.z = D3DVAL(sqrt(1 / un_val));
#else
    u.z = D3DVAL(sqrt(D3DVAL(1.0) / D3DVAL(n_fix)));
#endif
    u.y = - D3DDivide(D3DMultiply(u.z, d.z), d.y);
}
    } else
    {u.x = D3DVAL(0.0);
u.y = D3DVAL(0.0);
u.z = D3DVAL(1.0);
    }

    /*
     * Now find a vector normal to them both, to give us a coordinate
     * system in the plane normal to the rod.
     */
    D3DRMVectorCrossProduct(&r, &d, &u);

    /*
     * Scale down the coordinates to the radius of the rod.
     */
    u.x = D3DMultiply(u.x, radius);
    u.y = D3DMultiply(u.y, radius);
    u.z = D3DMultiply(u.z, radius);
    r.x = D3DMultiply(r.x, radius);
    r.y = D3DMultiply(r.y, radius);
    r.z = D3DMultiply(r.z, radius);

    /*
     * Calculate the corners of an octagon.
     */
    f = D3DVAL((float)sqrt(2.0) / (2 * (1 + (float)sqrt(2.0) / 2)));
    v[0].x = u.x + D3DMultiply(r.x, f);
    v[0].y = u.y + D3DMultiply(r.y, f);
    v[0].z = u.z + D3DMultiply(r.z, f);

    v[1].x = D3DMultiply(u.x, f) + r.x;
    v[1].y = D3DMultiply(u.y, f) + r.y;
    v[1].z = D3DMultiply(u.z, f) + r.z;

    v[2].x = D3DMultiply(-u.x, f) + r.x;
    v[2].y = D3DMultiply(-u.y, f) + r.y;
    v[2].z = D3DMultiply(-u.z, f) + r.z;

    v[3].x = -u.x + D3DMultiply(r.x, f);
    v[3].y = -u.y + D3DMultiply(r.y, f);
    v[3].z = -u.z + D3DMultiply(r.z, f);

    v[4].x = -u.x - D3DMultiply(r.x, f);
    v[4].y = -u.y - D3DMultiply(r.y, f);
    v[4].z = -u.z - D3DMultiply(r.z, f);

    v[5].x = D3DMultiply(-u.x, f) - r.x;
    v[5].y = D3DMultiply(-u.y, f) - r.y;
    v[5].z = D3DMultiply(-u.z, f) - r.z;

    v[6].x = D3DMultiply(u.x, f) - r.x;
    v[6].y = D3DMultiply(u.y, f) - r.y;
    v[6].z = D3DMultiply(u.z, f) - r.z;

    v[7].x = u.x - D3DMultiply(r.x, f);
    v[7].y = u.y - D3DMultiply(r.y, f);
    v[7].z = u.z - D3DMultiply(r.z, f);

    v[8] = b;

    /*
     * Calculate the vertex normals.
     */
    for (i = 0; i < 8; i++)
    {n[i] = v[i];
D3DRMVectorNormalise(&n[0]);
v[i].x += a.x;
v[i].y += a.y;
v[i].z += a.z;
    }

    /*
     * Now add the faces.
     */
    mesh->AddFaces(9, v, 8, n, cone_faces, NULL);
}


RODCONE.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: rodcone.h
 *
 ***************************************************************************/

void AddRod(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b);
void AddCone(LPDIRECT3DRMMESHBUILDER mesh, D3DVALUE radius, D3DVECTOR a, D3DVECTOR b);


SEL.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: sel.cpp
 *
 ***************************************************************************/

#include <math.h>
#include <d3drmwin.h>
#include "sel.h"
#include "rodcone.h"
#include "viewer.h"

static int showBoxes = FALSE;
static LPDIRECT3DRMFRAME sFrame = NULL;
static LPDIRECT3DRMVISUAL sVisual = NULL;
static LPDIRECT3DRMLIGHT sLight = NULL;
static LPDIRECT3DRMMESH selectionBox = NULL;
static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX*);

LPDIRECT3DRMFRAME clipboardFrame = NULL;
LPDIRECT3DRMVISUAL clipboardVisual = NULL;

void ShowBoxes(int show)
{
    showBoxes = show;
    SelectVisual(sVisual, sFrame);
}

int ToggleBoxes()
{
    ShowBoxes(!showBoxes);
    return showBoxes;
}

LPDIRECT3DRMFRAME SelectedFrame()
{
    return sFrame;
}

LPDIRECT3DRMVISUAL SelectedVisual()
{
    return sVisual;
}

LPDIRECT3DRMLIGHT SelectedLight()
{
    return sLight;
}

void DeselectVisual()
{
    if (sFrame && selectionBox)
sFrame->DeleteVisual(selectionBox);
    sFrame = NULL;
    sVisual = NULL;
    selectionBox = NULL;
}

void SelectVisual(LPDIRECT3DRMVISUAL visual, LPDIRECT3DRMFRAME frame)
{
    DeselectVisual();
    sVisual = visual;
    sFrame = frame;

    if (sVisual)
    {LPDIRECT3DRMLIGHTARRAY lights;

sLight = 0;
sFrame->GetLights(&lights);
if (lights)
{   if (lights->GetSize())
    {lights->GetElement(0, &sLight);
sLight->Release(); /* reinstate reference count */
    }
    lights->Release();
}

if (showBoxes && visual)
{   D3DRMBOX box;
    LPDIRECT3DRMMESHBUILDER lpMeshVis;
    LPDIRECT3DRMPROGRESSIVEMESH lpProgMeshVis;
    LPDIRECT3DRMMESHBUILDER builder;

    if (SUCCEEDED(visual->QueryInterface(IID_IDirect3DRMProgressiveMesh,
 (LPVOID*) &lpProgMeshVis)))
    {
lpProgMeshVis->GetBox(&box);
lpProgMeshVis->Release();
    }

    if (SUCCEEDED(visual->QueryInterface(IID_IDirect3DRMMeshBuilder,
     (LPVOID*) &lpMeshVis)))
    {
lpMeshVis->GetBox(&box);
lpMeshVis->Release();
    }
    builder = makeBox(&box);
    builder->CreateMesh(&selectionBox);
    sFrame->AddVisual(selectionBox);
    selectionBox->Release();
}
    }
}

void SelectPM(LPDIRECT3DRMPROGRESSIVEMESH lpPM)
{
    float fVal;
    HRESULT hres;
    HWND win = active_window->win;

    if (!lpPM)
    {
if (active_window->lpPM)
{
    active_window->lpPM->Release();
    active_window->lpPM = NULL;
    EnableScrollBar(win, SB_VERT, ESB_DISABLE_BOTH);
}
return;
    }

    lpPM->AddRef();

    if (active_window->lpPM)
    {
active_window->lpPM->Release();
    }

    active_window->lpPM = lpPM;

    hres = lpPM->GetDetail(&fVal);
    if (SUCCEEDED(hres))
    {
SetScrollPos(win, SB_VERT, (DWORD)(100.0 - (DWORD)(fVal * 100.0)), TRUE);
gfVal = fVal;
    }

    EnableScrollBar(win, SB_VERT, ESB_ENABLE_BOTH);
}

void FindAndSelectVisual(LPDIRECT3DRMVIEWPORT view, int x, int y)
{
    LPDIRECT3DRMVISUAL visual;
    LPDIRECT3DRMFRAME frame;
    LPDIRECT3DRMPICKEDARRAY picked;
    LPDIRECT3DRMFRAMEARRAY frames;
    LPDIRECT3DRMMESHBUILDER mesh;

    /*
     * Make sure we don't try to select the selection box of the current
     * selection.
     */
    DeselectVisual();

    view->Pick(x, y, &picked);
    if (picked)
    {if (picked->GetSize())
{
    LPDIRECT3DRMPROGRESSIVEMESH pm;

    picked->GetPick(0, &visual, &frames, 0);
    frames->GetElement(frames->GetSize() - 1, &frame);

    if (SUCCEEDED(visual->QueryInterface(IID_IDirect3DRMProgressiveMesh,
 (LPVOID*) &pm))) {
SelectPM(pm);
        pm->Release();
        SelectVisual(pm, frame);
    }

    if (SUCCEEDED(visual->QueryInterface(IID_IDirect3DRMMeshBuilder, (void **) &mesh)))
    {
SelectPM(NULL);
SelectVisual(mesh, frame);
mesh->Release();
    }

    frame->Release();
    frames->Release();
    visual->Release();
}
picked->Release();
    }
}

void CutVisual()
{
    LPDIRECT3DRMFRAME frame;

    if (clipboardFrame)
clipboardFrame->Release();

    if (sFrame) {
clipboardFrame = sFrame;
clipboardVisual = sVisual;

DeselectVisual();

clipboardFrame->AddRef();
clipboardFrame->GetParent(&frame);
if (frame) {
    frame->DeleteChild(clipboardFrame);
    frame->Release();
}
    }
}

void CopyVisual()
{
    LPDIRECT3DRMFRAME frame;

    if (clipboardFrame)
clipboardFrame->Release();

    if (sFrame) {
sFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &clipboardFrame);
sVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &clipboardVisual);

clipboardFrame->AddVisual(clipboardVisual);
clipboardVisual->Release();

clipboardFrame->GetParent(&frame);
if (frame) {
    frame->DeleteChild(clipboardFrame);
    frame->Release();
}
    }
}

void PasteVisual(LPDIRECT3DRMFRAME scene)
{
    if (clipboardFrame)
    {
LPDIRECT3DRMFRAME frame;
LPDIRECT3DRMVISUAL visual;

clipboardFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &frame);
clipboardVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &visual);

frame->AddVisual(visual);
scene->AddChild(frame);
visual->Release();
frame->Release();
    }
}

void DeleteVisual()
{
    if (sFrame)
    {
LPDIRECT3DRMFRAME parent, frame = sFrame;

DeselectVisual();
SelectPM(NULL);
frame->GetParent(&parent);
parent->DeleteChild(frame);
parent->Release();
    }
}

void ClearClipboard()
{
    if (clipboardFrame)
clipboardFrame->Release();
}

static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX* box)
{
    LPDIRECT3DRMMESHBUILDER2 mesh;
    static D3DVECTOR zero = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
    static D3DVECTOR dir = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
    D3DVECTOR a, b;

    lpD3DRM->CreateMeshBuilder(&mesh);

    dir.z = box->max.z + D3DVAL(1.0);
    AddRod(mesh, D3DVAL(0.05), zero, dir);
    a = dir;
    a.z += D3DVAL(0.6);
    AddCone(mesh, D3DVAL(0.2), dir, a);
    a = box->min;
    b = a;
    b.y = box->max.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->max.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->min.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->min.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.z = box->max.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->max.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->max.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.x = box->min.x;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b; b.y = box->min.y;
    AddRod(mesh, D3DVAL(0.05), a, b);
    b.y = box->max.y; a = b; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a = b = box->max; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);
    a.y = box->min.y; b = a; b.z = box->min.z;
    AddRod(mesh, D3DVAL(0.05), a, b);

    mesh->SetColor(D3DRMCreateColorRGB(D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0)));
    return mesh;
}


SEL.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: sel.h
 *
 ***************************************************************************/

void ShowBoxes(int);
int ToggleBoxes(void);
LPDIRECT3DRMFRAME SelectedFrame(void);
LPDIRECT3DRMVISUAL SelectedVisual(void);
LPDIRECT3DRMLIGHT SelectedLight(void);
void DeselectVisual(void);
void SelectVisual(LPDIRECT3DRMVISUAL visual, LPDIRECT3DRMFRAME frame);
void FindAndSelectVisual(LPDIRECT3DRMVIEWPORT view, int x, int y);
void CutVisual(void);
void CopyVisual(void);
void PasteVisual(LPDIRECT3DRMFRAME scene);
void DeleteVisual(void);
void ClearClipboard(void);


VIEWER.CPP

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: viewer.cpp
 *
 ***************************************************************************/

#define INITGUID
#include <d3drmwin.h>
#include "viewer.h"

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <direct.h>
#include "sel.h"

static char ViewerClass[32] = "ViewerClass";
static BOOL Render(void);
HRESULT loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex);

AppInfo *active_window = NULL;

LPDIRECT3DRM lpD3DRM1 = 0;
LPDIRECT3DRM2 lpD3DRM = 0;
LPDIRECTDRAWCLIPPER lpDDClipper = 0;
BOOL bQuit = FALSE;

static BOOL FirstInstance(HINSTANCE);
static HWND AnyInstance(HINSTANCE this_inst, int cmdshow);
long FAR PASCAL WindowProc(HWND, UINT, WPARAM, LPARAM);
static BOOL CreateDevice(HWND, AppInfo*);
static void Idle();
float gfVal = 1.0F;

char* MyErrorToString(HRESULT error);

/* Msg
 * Message output for error notification.
 */
void __cdecl
Msg( LPSTR fmt, ... )
{
    char buff[256];
    va_list args;

    va_start(args, fmt);
    wvsprintf(buff, fmt, args);
    va_end(args);

    lstrcat(buff, "\r\n");
    MessageBox( NULL, buff, "Viewer Message", MB_OK );
}

char* LSTRRCHR( const char* lpString, int bChar )
{
    if( lpString != NULL )
    {
const char*lpBegin;

lpBegin = lpString;

while( *lpString != 0 )
{
    lpString++;
}

        while( 1 )
{
    if( *lpString == bChar )
    {
return (char*)lpString;
    }

    if( lpString == lpBegin )
    {
 break;
    }

    lpString--;
}
    }

    return NULL;
} /* LSTRRCHR */

/*
 * Initialization, message loop
 */
int PASCAL WinMain
    (HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, int cmdshow)
{
    MSGmsg;
    intidle;
    intdone = FALSE;
    HACCELaccel;
    HRESULTrval;
    HWNDhwnd;
    DWORDdwDXVersion, dwDXPlatform;

    prev_inst = prev_inst;
    cmdline = cmdline;

    /*
     * Need DX5 version for alpha blending and progressive meshes.
     */
    GetDXVersion(&dwDXVersion, &dwDXPlatform);
    if (dwDXVersion < 0x500)
    {
Msg("This application requires DirectX version 5 or above.");
return 1;
    }

    rval = Direct3DRMCreate(&lpD3DRM1);
    if (rval != D3DRM_OK) {
Msg("Failed to create Direct3DRM.\n%s", MyErrorToString(rval));
return 1;
    }

    rval = lpD3DRM1->QueryInterface(IID_IDirect3DRM2, (LPVOID*)&lpD3DRM);
    if (rval != D3DRM_OK) {
Msg("Failed to QI for IID_IDirect3DRM2.\n%s", MyErrorToString(rval));
return 1;
    }

    if (!prev_inst)
if (!FirstInstance(this_inst))
    return 1;

    if (!(hwnd = AnyInstance(this_inst, cmdshow)))
return 1;
    accel = LoadAccelerators(this_inst, "ViewerAccel");

    while (!done) {
idle = TRUE;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    idle = FALSE;
    if (msg.message == WM_QUIT || bQuit) {
done = TRUE;
break;
    }
    if (!TranslateAccelerator(msg.hwnd, accel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
    }
}
if (done) {
    break;
} else if (!active_window->bMinimized && !bQuit) {
    if (idle) Idle();
    if (!Render()) {
Msg("Rendering failed.\n");
done = TRUE;
break;
    }
} else {
    WaitMessage();
}
    }
    SelectPM(NULL);
    RELEASE(active_window->scene);
    RELEASE(active_window->camera);
    RELEASE(active_window->view);
    RELEASE(active_window->dev);
    RELEASE(active_window->dev1);
    ClearClipboard();
    RELEASE(lpD3DRM);
    RELEASE(lpD3DRM1);
    RELEASE(lpDDClipper);
    DestroyWindow(hwnd);
    return msg.wParam;
}

/*
 * Register window class for the application, and do any other
 * application initialization
 */
static BOOL FirstInstance(HINSTANCE this_inst)
{
    WNDCLASSwc;
    BOOLrc;

    /*
     * set up and register window class
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = this_inst;
    wc.hIcon = LoadIcon(this_inst, "ViewerIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = "ViewerMenu";
    wc.lpszClassName = ViewerClass;
    rc = RegisterClass(&wc);

    return rc;
}

/*
 * Do work required for every instance of the application:
 * create the window, initialize data
 */
static HWND AnyInstance(HINSTANCE this_inst, int cmdshow)
{
    HWND win;
    AppInfo *info;
    SCROLLINFO si = { sizeof(SCROLLINFO), SIF_ALL, 0, 100, 1, 0};

    /*
     * create main window
     */
    win =
CreateWindow
(   ViewerClass,/* class */
    "Direct3D Object Viewer",/* caption */
    WS_OVERLAPPEDWINDOW | WS_VSCROLL,/* style */
    CW_USEDEFAULT,/* init. x pos */
    CW_USEDEFAULT,/* init. y pos */
    400,/* init. x size */
    400,/* init. y size */
    NULL,/* parent window */
    NULL,/* menu handle */
    this_inst,/* program handle */
    NULL/* create parms */
);

    if (!win) return FALSE;

    EnableScrollBar(win, SB_VERT, ESB_DISABLE_BOTH);
    SetScrollInfo(win, SB_VERT, &si, TRUE);

    /*
     * Create a clipper for this window
     */
    if (FAILED(DirectDrawCreateClipper(0, &lpDDClipper, NULL))) {
return FALSE;
    }
    if (FAILED(lpDDClipper->SetHWnd(0, win))) {
RELEASE(lpDDClipper);
return FALSE;
    }
    /*
     * set up data associated with this window
     */
    info = (AppInfo*) malloc(sizeof(AppInfo));
    memset(info, 0, sizeof(AppInfo));
    info->win = win;

    SetWindowLong(win, 0, (long) info);
    info->model = D3DCOLOR_MONO;
    if (!CreateDevice(win, info)) {
return FALSE;
    }

    /*
     * display window
     */
    ShowWindow(win, cmdshow);
    UpdateWindow(win);

    return win;
}

/*
 * Processes messages for the about dialog.
 */
BOOL FAR PASCAL AboutDlgProc
    (HWND win, unsigned msg, WORD wparam, LONG lparam)
{
    lparam = lparam;

    switch (msg)
    {
    case WM_INITDIALOG:
return TRUE;

    case WM_COMMAND:
if (wparam == IDOK)
{   EndDialog(win, TRUE);
    return TRUE;
}
break;
    }
    return FALSE;
}

/*
 * Create a simple scene.
 */
static BOOL CreateScene(AppInfo* info)
{
    LPDIRECT3DRMFRAME2 frame = NULL;
    LPDIRECT3DRMFRAME2 light = NULL;
    LPDIRECT3DRMMESHBUILDER2 builder = NULL;
    LPDIRECT3DRMLIGHT light1 = NULL;
    LPDIRECT3DRMLIGHT light2 = NULL;
    LPDIRECT3DRMMATERIAL mat = NULL;
    HRESULT rval;

    if (FAILED(lpD3DRM->CreateFrame(NULL, &info->scene)))
goto generic_error;
    if (FAILED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light1)))
goto generic_error;
    if (FAILED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL(0.1), D3DVAL(0.1), D3DVAL(0.1), &light2)))
goto generic_error;

    if (FAILED(lpD3DRM->CreateFrame(info->scene, &light)))
goto generic_error;
    if (FAILED(light->SetPosition(info->scene, D3DVAL(2.0), D3DVAL(2.0), D3DVAL(5.0))))
goto generic_error;
    if (FAILED(light->SetOrientation(info->scene, D3DVAL(-1.0), D3DVAL(-1.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0))))
goto generic_error;
    if (FAILED(light->AddLight(light1)))
goto generic_error;
    RELEASE(light1);
    if (FAILED(info->scene->AddLight(light2)))
goto generic_error;
    RELEASE(light2);
    if (FAILED(lpD3DRM->CreateMeshBuilder(&builder)))
goto generic_error;
    rval = builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL);
    if (rval != D3DRM_OK) {
Msg("Failed to load camera.x.\n%s", MyErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->SetQuality(D3DRMRENDER_UNLITFLAT)))
goto generic_error;
    if (FAILED(light->AddVisual(builder)))
goto generic_error;
    RELEASE(builder);
    RELEASE(light);

    if (FAILED(lpD3DRM->CreateFrame(info->scene, &frame)))
goto generic_error;
    if (FAILED(frame->SetRotation(info->scene, D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(-0.02))))
goto generic_error;
    if (FAILED(frame->SetPosition(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0))))
goto generic_error;
    if (FAILED(lpD3DRM->CreateMeshBuilder(&builder)))
goto generic_error;
    rval = builder->Load("mslogo.x", NULL, D3DRMLOAD_FROMFILE,
loadTextures, NULL);
    if (rval != D3DRM_OK) {
Msg("Failed to load mslogo.x.\n%s", MyErrorToString(rval));
goto ret_with_error;
    }
    if (FAILED(builder->SetColorRGB(D3DVAL(0.8), D3DVAL(0.8), D3DVAL(0.8))))
goto generic_error;
    if (FAILED(lpD3DRM->CreateMaterial(D3DVAL(10.0), &mat)))
goto generic_error;
    if (FAILED(builder->SetMaterial(mat)))
goto generic_error;

    RELEASE(mat);
    if (FAILED(frame->AddVisual(builder)))
goto generic_error;
    RELEASE(builder);
    RELEASE(frame);

    if (FAILED(lpD3DRM->CreateFrame(info->scene, &info->camera)))
goto generic_error;
    if (FAILED(info->camera->SetPosition(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0))))
goto generic_error;
    return TRUE;
generic_error:
    Msg("A failure occurred while creating the scene.\n");
ret_with_error:
    RELEASE(frame);
    RELEASE(light);
    RELEASE(builder);
    RELEASE(light1);
    RELEASE(light2);
    RELEASE(mat);
    return FALSE;
}

static DWORD bppToddbd(int bpp)
{
    switch(bpp) {
    case 1:
return DDBD_1;
    case 2:
return DDBD_2;
    case 4:
return DDBD_4;
    case 8:
return DDBD_8;
    case 16:
return DDBD_16;
    case 24:
return DDBD_24;
    case 32:
return DDBD_32;
    }
    return 0;
}

/*
 * Find a device, preferably hardware, for a particular color model.
 */
LPGUID
FindDevice(D3DCOLORMODEL cm, BOOL bSoftwareOnly)
{
    LPDIRECTDRAW lpDD;
    LPDIRECT3D lpD3D;
    D3DFINDDEVICESEARCH search;
    static D3DFINDDEVICERESULT result;
    HRESULT error;
    HDC hdc;
    int bpp;

    hdc = GetDC(NULL);
    bpp = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(NULL, hdc);

    if (DirectDrawCreate(NULL, &lpDD, NULL))
    return NULL;

    if (lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D)) {
    lpDD->Release();
return NULL;
    }

    memset(&search, 0, sizeof search);
    search.dwSize = sizeof search;
    search.dwFlags = D3DFDS_COLORMODEL;
    search.dcmColorModel = (cm == D3DCOLOR_MONO) ? D3DCOLOR_MONO : D3DCOLOR_RGB;
    if (bSoftwareOnly)
    {
search.dwFlags |= D3DFDS_HARDWARE;
search.bHardware = FALSE;
    }

    memset(&result, 0, sizeof result);
    result.dwSize = sizeof result;

    error = lpD3D->FindDevice(&search, &result);

    if (error == DD_OK) {
/*
 * If the device found is hardware but cannot support the current
 * bit depth, then fall back to software rendering.
 */
if (result.ddHwDesc.dwFlags
    && !(result.ddHwDesc.dwDeviceRenderBitDepth & bppToddbd(bpp))) {

    search.dwFlags |= D3DFDS_HARDWARE;
    search.bHardware = FALSE;
    memset(&result, 0, sizeof result);
    result.dwSize = sizeof result;
    error = lpD3D->FindDevice(&search, &result);
}
    }

    lpD3D->Release();
    lpDD->Release();

    if (error)
return NULL;
    else
return &result.guid;
}

/*
 * Create the device and viewport.
 */
static BOOL CreateDevice(HWND win, AppInfo* info)
{
    RECT r;
    int bpp;
    HDC hdc;

    GetClientRect(win, &r);
    if (FAILED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper,
FindDevice(info->model, FALSE),
r.right, r.bottom,
&info->dev)))
    {
if (FAILED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper,
    FindDevice(info->model, TRUE),
    r.right, r.bottom,
    &info->dev)))
{
    goto generic_error;
}
    }
    info->dev->SetRenderMode(D3DRMRENDERMODE_BLENDEDTRANSPARENCY|D3DRMRENDERMODE_SORTEDTRANSPARENCY);
    if (FAILED(info->dev->QueryInterface(IID_IDirect3DRMDevice,
 (LPVOID*)&info->dev1)))
goto generic_error;

    hdc = GetDC(win);
    bpp = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(win, hdc);
    switch (bpp)
    {
    case 1:
if (FAILED(info->dev->SetShades(4)))
    goto generic_error;
if (FAILED(lpD3DRM->SetDefaultTextureShades(4)))
    goto generic_error;
break;
    case 16:
if (FAILED(info->dev->SetShades(32)))
    goto generic_error;
if (FAILED(lpD3DRM->SetDefaultTextureColors(64)))
    goto generic_error;
if (FAILED(lpD3DRM->SetDefaultTextureShades(32)))
    goto generic_error;
if (FAILED(info->dev->SetDither(FALSE)))
    goto generic_error;
break;
    case 24:
    case 32:
if (FAILED(info->dev->SetShades(256)))
    goto generic_error;
if (FAILED(lpD3DRM->SetDefaultTextureColors(64)))
    goto generic_error;
if (FAILED(lpD3DRM->SetDefaultTextureShades(256)))
    goto generic_error;
if (FAILED(info->dev->SetDither(FALSE)))
    goto generic_error;
break;
    default:
if (FAILED(info->dev->SetDither(FALSE)))
    goto generic_error;
    }
    if (!CreateScene(info))
goto ret_with_error;
    if (FAILED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0,
info->dev->GetWidth(),
info->dev->GetHeight(), &info->view)))
goto generic_error;
    if (FAILED(info->view->SetBack(D3DVAL(5000.0))))
goto generic_error;

    return TRUE;
generic_error:
    Msg("An error occurred while creating the device.\n");
ret_with_error:
    return FALSE;
}

/*
 * Regenerate the device if the color model changes or the window size
 * changes.
 */
static BOOL RebuildDevice(HWND win, AppInfo* info, int width, int height)
{
    HRESULT rval;
    int old_dither = info->dev->GetDither();
    D3DRMRENDERQUALITY old_quality = info->dev->GetQuality();
    D3DRMTEXTUREQUALITY old_texture_quality = info->dev->GetTextureQuality();
    int old_shades = info->dev->GetShades();

    RELEASE(info->view);
    RELEASE(info->dev);
    RELEASE(info->dev1);
    if (lpD3DRM->CreateDeviceFromClipper(lpDDClipper, FindDevice(info->model, FALSE),
width, height, &info->dev))
    {
if (rval = lpD3DRM->CreateDeviceFromClipper(lpDDClipper, FindDevice(info->model, TRUE),
    width, height, &info->dev))
{
    Msg("Creating a device from HWND failed while rebuilding device.\n%s", MyErrorToString(rval));
    return FALSE;
}
    }
    info->dev->SetRenderMode(D3DRMRENDERMODE_BLENDEDTRANSPARENCY|D3DRMRENDERMODE_SORTEDTRANSPARENCY);
    if (FAILED(info->dev->QueryInterface(IID_IDirect3DRMDevice,
 (LPVOID*)&info->dev1)))
goto generic_error;

    if (FAILED(info->dev->SetDither(old_dither)))
goto generic_error;
    if (FAILED(info->dev->SetQuality(old_quality)))
goto generic_error;
    if (FAILED(info->dev->SetTextureQuality(old_texture_quality))) {
goto generic_error;
    }
    if (FAILED(info->dev->SetShades(old_shades)))
goto generic_error;
    width = info->dev->GetWidth();
    height = info->dev->GetHeight();
    if (FAILED(lpD3DRM->CreateViewport(info->dev, info->camera,
  0, 0, width, height, &info->view)))
  goto generic_error;
    if (FAILED(info->view->SetBack(D3DVAL(400.0))))
goto generic_error;
    return TRUE;
generic_error:
    Msg("A failure occurred while rebuilding the device.\n");
    return FALSE;
}

/*
 * Resize the viewport and device when the window size changes.
 */
static BOOL ResizeViewport(HWND win, AppInfo* info, int width, int height)
{
    int view_width = info->view->GetWidth();
    int view_height = info->view->GetHeight();
    int dev_width = info->dev->GetWidth();
    int dev_height = info->dev->GetHeight();

    if (view_width == width && view_height == height)
return TRUE;

    if (width <= dev_width && height <= dev_height) {
RELEASE(info->view);
if (FAILED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, width, height, &info->view)))
    goto generic_error;
if (FAILED(info->view->SetBack(D3DVAL(400.0))))
    goto generic_error;
    }

    if (!RebuildDevice(win, info, width, height))
return FALSE;
    return TRUE;
generic_error:
    Msg("A failure occurred while resizing the viewport.\n");
    return FALSE;
}

/*
 * Place an object in front of the camera.
 */
static BOOL PlaceMesh(LPDIRECT3DRMVISUAL mesh, AppInfo *info)
{
    LPDIRECT3DRMFRAME2 frame;

    if (FAILED(lpD3DRM->CreateFrame(info->scene, &frame)))
return FALSE;
    if (FAILED(frame->AddVisual(mesh)))
return FALSE;
    if (FAILED(frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15.0))))
return FALSE;
    frame->Release();
    return TRUE;
}

static BOOL ScaleScene(LPDIRECT3DRMFRAME frame, AppInfo *info)
{
  /* Some trickery form the RL2 viewer to scale a scene down to
     managable proportions */
  LPDIRECT3DRMMESHBUILDER2 mbuilder;
  D3DRMBOX box;
  D3DVALUE maxDim;

  lpD3DRM->CreateMeshBuilder(&mbuilder);
  mbuilder->AddFrame(frame);
  mbuilder->GetBox(&box);
  mbuilder->Release();

  maxDim = box.max.x - box.min.x;
  if (box.max.y - box.min.y > maxDim)
    maxDim = box.max.y - box.min.y;
  if (box.max.z - box.min.z > maxDim)
    maxDim = box.max.z - box.min.z;

  frame->AddScale(D3DRMCOMBINE_BEFORE, D3DDivide(D3DVAL(8.0), maxDim),
  D3DDivide(D3DVAL(8.0), maxDim),
  D3DDivide(D3DVAL(8.0), maxDim));

  frame->SetPosition(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15.0));

  return TRUE;
}

typedef struct {
    LPDIRECT3DRMANIMATIONSET animset;
    D3DVALUE time;
} animationCallbackArgs;

static void CDECL animationCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta)
{
    animationCallbackArgs* cb = (animationCallbackArgs *) arg;

    obj = obj;
    cb->animset->SetTime(cb->time);
    cb->time += delta;
}

static BOOL setAnimationCallback(LPDIRECT3DRMFRAME frame,
 LPDIRECT3DRMANIMATIONSET animset)
{
    animationCallbackArgs *cb;

    cb = (animationCallbackArgs*)malloc(sizeof(animationCallbackArgs));
    if (!cb)
return FALSE;

    cb->animset = animset;
    cb->time = D3DVAL(0);
    if (FAILED(frame->AddMoveCallback(animationCallback, (void *) cb)))
return FALSE;
    return TRUE;
}

static BOOL LoadAnimationSet(const char *filename, AppInfo *info)
{
  LPDIRECT3DRMANIMATIONSET lpAnimSet;
  LPDIRECT3DRMFRAME2 lpFrame;

  /* Create a new parent frame for the animation, load it, and set up the
     callback */

  if (FAILED(lpD3DRM->CreateFrame(info->scene, &lpFrame)))
    return FALSE;

  if (FAILED(lpD3DRM->CreateAnimationSet(&lpAnimSet)))
    return FALSE;

  if (FAILED(lpAnimSet->Load((LPVOID)filename, NULL,
     D3DRMLOAD_FROMFILE, loadTextures,
     NULL, lpFrame)))
    return FALSE;

  ScaleScene(lpFrame, info);

  setAnimationCallback(lpFrame, lpAnimSet);

  return TRUE;
}

static BOOL LoadFrameHierarchy(const char *filename, AppInfo *info)
{
  LPDIRECT3DRMFRAME2 lpFrame;

  if (FAILED(lpD3DRM->CreateFrame(info->scene, &lpFrame)))
    return FALSE;

  if (FAILED(lpFrame->Load((LPVOID)filename, NULL, D3DRMLOAD_FROMFILE,
   loadTextures, NULL)))
    return FALSE;

  ScaleScene(lpFrame, info);

  return TRUE;
}

static int
    left_drag = FALSE, right_drag = FALSE,
    last_x, last_y;

/*
 * Render the scene into the viewport.
 */
static BOOL Render() {
    if (FAILED(active_window->scene->Move(D3DVAL(1.0))))
return FALSE;
    if (FAILED(active_window->view->Clear()))
return FALSE;
    if (FAILED(active_window->view->Render(active_window->scene)))
return FALSE;
    if (FAILED(active_window->dev->Update()))
return FALSE;
    return TRUE;
}

static void Idle()
{
    LPDIRECT3DRMFRAME selected = SelectedFrame();

    if (active_window == NULL) return;
    if (left_drag && selected)
selected->SetRotation(active_window->scene, D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0));
    if (right_drag && selected)
selected->SetVelocity(active_window->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), FALSE);
}

static int FillModeToMenuItem(D3DRMFILLMODE fm)
{
    switch (fm) {
    case D3DRMFILL_POINTS:
return 2;

    case D3DRMFILL_WIREFRAME:
return 3;

    case D3DRMFILL_SOLID:
return 4;
    }
    return -1;
}

static int ShadeModeToMenuItem(D3DRMSHADEMODE sm)
{
    switch (sm) {
    case D3DRMSHADE_FLAT:
return 6;

    case D3DRMSHADE_GOURAUD:
return 7;

    case D3DRMSHADE_PHONG:
return 8;
    }
    return -1;
}

static BOOL ToggleLighting(HWND win, AppInfo* info)
{
    HMENU menu;
    D3DRMRENDERQUALITY quality = info->dev->GetQuality();
    D3DRMLIGHTMODE mode = (D3DRMLIGHTMODE)(quality & D3DRMLIGHT_MASK);
    HRESULT rval;

    if (mode == D3DRMLIGHT_ON)
mode = D3DRMLIGHT_OFF;
    else
mode = D3DRMLIGHT_ON;

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, 0, (MF_BYPOSITION
    | (mode == D3DRMLIGHT_ON
       ? MF_CHECKED
       : MF_UNCHECKED)));

    quality = (quality & ~D3DRMLIGHT_MASK) | mode;
    rval = info->dev->SetQuality(quality);
    if (rval != D3DRM_OK) {
Msg("Setting the render quality while toggling lighting failed.\n%s", MyErrorToString(rval));
return FALSE;
    }
    return TRUE;
}

static BOOL SetFillMode(HWND win, AppInfo* info, D3DRMFILLMODE fm)
{
    HMENU menu;
    D3DRMRENDERQUALITY quality = info->dev->GetQuality();
    D3DRMFILLMODE oldfm = (D3DRMFILLMODE)(quality & D3DRMFILL_MASK);
    HRESULT rval;

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, FillModeToMenuItem(oldfm),
  MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, FillModeToMenuItem(fm),
  MF_BYPOSITION|MF_CHECKED);

    quality = (quality & ~D3DRMFILL_MASK) | fm;
    rval = info->dev->SetQuality(quality);
    if (rval != D3DRM_OK) {
Msg("Setting the render quality while changing the fill mode failed.\n%s", MyErrorToString(rval));
return FALSE;
    }
    return TRUE;
}

static BOOL SetShadeMode(HWND win, AppInfo* info, D3DRMSHADEMODE sm)
{
    HMENU menu;
    D3DRMRENDERQUALITY quality = info->dev->GetQuality();
    D3DRMSHADEMODE oldsm = (D3DRMSHADEMODE)(quality & D3DRMSHADE_MASK);
    HRESULT rval;

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, ShadeModeToMenuItem(oldsm),
  MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, ShadeModeToMenuItem(sm),
  MF_BYPOSITION|MF_CHECKED);

    quality = (quality & ~D3DRMSHADE_MASK) | sm;
    rval = info->dev->SetQuality(quality);
    if (rval != D3DRM_OK) {
Msg("Setting the render quality while changing the shade mode failed.\n%s", MyErrorToString(rval));
return FALSE;
    }
    return TRUE;
}

static BOOL SetModel(HWND win, AppInfo* info, D3DRMCOLORMODEL model)
{
    HMENU menu;
    D3DRMCOLORMODEL oldModel = info->model;

    if (oldModel == model) return TRUE;

    info->model = model;
    if (!RebuildDevice(win, info, info->dev->GetWidth(), info->dev->GetHeight()))
return FALSE;

    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);
    CheckMenuItem(menu, 9 + (int) oldModel, MF_BYPOSITION|MF_UNCHECKED);
    CheckMenuItem(menu, 9 + (int) model, MF_BYPOSITION|MF_CHECKED);
    return TRUE;
}

static BOOL ToggleDither(HWND win, AppInfo *info)
{
    HMENU menu;
    int dither = info->dev->GetDither();
    int checked;
    dither = !dither;
    if (FAILED(info->dev->SetDither(dither))) {
Msg("Toggling dithering failed.\n");
return FALSE;
    }
    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);

    if (dither) checked = MF_CHECKED;
    else checked = MF_UNCHECKED;

    //CheckMenuItem(menu, MENU_DITHER, MF_BYCOMMAND|checked);
    CheckMenuItem(menu, 13, MF_BYPOSITION|checked);
    return TRUE;
}

static BOOL ToggleTextureFiltering(HWND win, AppInfo *info)
{
    HMENU menu;
    D3DRMTEXTUREQUALITY tq = info->dev->GetTextureQuality();
    int checked;
    if (tq == D3DRMTEXTURE_NEAREST)
tq = D3DRMTEXTURE_LINEAR;
    else
tq = D3DRMTEXTURE_NEAREST;

    if (FAILED(info->dev->SetTextureQuality(tq))) {
Msg("Setting texture quality failed.\n");
return FALSE;
    }
    menu = GetMenu(win);
    menu = GetSubMenu(menu, 2);

    if (tq == D3DRMTEXTURE_LINEAR) checked = MF_CHECKED;
    else checked = MF_UNCHECKED;

    CheckMenuItem(menu, 14, MF_BYPOSITION|checked);
    return TRUE;
}

static BOOL
CreateLight(WPARAM wparam, AppInfo* info)
{
    LPDIRECT3DRMMESHBUILDER2 builder = NULL;
    LPDIRECT3DRMLIGHT light = NULL;
    LPDIRECT3DRMFRAME2 frame = NULL;
    HRESULT rval;

    if (FAILED(lpD3DRM->CreateMeshBuilder(&builder)))
goto generic_error;

    if (wparam == MENU_LIGHT_DIRECTIONAL) {
rval = builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE,
     NULL, NULL);
if (rval != D3DRM_OK) {
    Msg("Failed to load camera.x.\n%s", MyErrorToString(rval));
    goto ret_with_error;
}
if (FAILED(builder->SetQuality(D3DRMRENDER_UNLITFLAT)))
    goto generic_error;
if (FAILED(lpD3DRM->CreateLightRGB
    (D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light)))
    goto generic_error;
    } else if (wparam == MENU_LIGHT_PARALLEL_POINT) {
rval = builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE,
     NULL, NULL);
if (rval != D3DRM_OK) {
    Msg("Failed to load sphere2.x.\n%s", MyErrorToString(rval));
    goto ret_with_error;
}
if (FAILED(builder->SetQuality(D3DRMRENDER_UNLITFLAT)))
    goto generic_error;
if (FAILED(builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2))))
    goto generic_error;
if (FAILED(lpD3DRM->CreateLightRGB
    (D3DRMLIGHT_PARALLELPOINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light)))
    goto generic_error;
    } else if (wparam == MENU_LIGHT_POINT) {
rval = builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE,
     NULL, NULL);
if (rval != D3DRM_OK) {
    Msg("Failed to load sphere2.x.\n%s", MyErrorToString(rval));
    goto ret_with_error;
}
if (FAILED(builder->SetQuality(D3DRMRENDER_UNLITFLAT)))
    goto generic_error;
if (FAILED(builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2))))
    goto generic_error;
if (FAILED(lpD3DRM->CreateLightRGB
    (D3DRMLIGHT_POINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light)))
    goto generic_error;
    } else if (wparam == MENU_LIGHT_SPOT) {
rval = builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE,
     NULL, NULL);
if (rval != D3DRM_OK) {
    Msg("Failed to load camera.x.\n%s", MyErrorToString(rval));
    goto ret_with_error;
}
if (FAILED(builder->SetQuality(D3DRMRENDER_UNLITFLAT)))
    goto generic_error;
if (FAILED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light)))
    goto generic_error;
    }
    if (FAILED(lpD3DRM->CreateFrame(info->scene, &frame)))
goto generic_error;
    if (FAILED(frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0))))
goto generic_error;
    if (FAILED(frame->AddVisual(builder)))
goto generic_error;
    if (FAILED(frame->AddLight(light)))
goto generic_error;

    builder->Release(), frame->Release(), light->Release();
    return TRUE;
generic_error:
    Msg("A failure occurred while creating a new light.\n");
ret_with_error:
    RELEASE(builder);
    RELEASE(light);
    RELEASE(frame);
    return FALSE;
}

HRESULT loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex)
{
    /* IDirect3DRM::LoadTexture checks whether the texture is a PPM or
    BMP which it knows how to load. If you want to load other formats
    you could add code to the callback to load the code into an D3DRMIMAGE
    structure and call IDirect3DRM::CreateTexture */

    return lpD3DRM1->LoadTexture(name, tex);
}

#define SIGN_EXTEND(w)    ((((int)(w)) << 16) >> 16)

/*
 * Handle messages for the main application window
 */
LONG FAR PASCAL WindowProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
    static HCURSOR oldCursor = NULL;
    AppInfo *info;
    LPDIRECT3DRMFRAME sFrame = SelectedFrame();
    LPDIRECT3DRMVISUAL sVisual = SelectedVisual();

    info = (AppInfo *) GetWindowLong(win, 0);
    active_window = info;

    switch (msg)
    {
    case WM_KEYDOWN:
{   D3DVECTOR dir, up, right;

    info->camera->GetOrientation(info->scene, &dir, &up);
    D3DRMVectorCrossProduct(&right, &up, &dir);
    up.x /= D3DVAL(4.0);
    up.y /= D3DVAL(4.0);
    up.z /= D3DVAL(4.0);
    right.x /= D3DVAL(4.0);
    right.y /= D3DVAL(4.0);
    right.z /= D3DVAL(4.0);

    switch (wparam)
    {
    case 'T':
info->camera->SetVelocity(info->scene, dir.x, dir.y, dir.z, FALSE);
break;

    case 'Y':
info->camera->SetVelocity(info->scene, D3DVAL(-100.0) * dir.x,
  D3DVAL(-100.0) * dir.y,
  D3DVAL(-100.0) * dir.z, FALSE);

    case 'R':
info->camera->SetVelocity(info->scene, -dir.x, -dir.y, -dir.z, FALSE);
break;

    case 'E':
info->camera->SetVelocity(info->scene, D3DVAL(100.0) * dir.x,
  D3DVAL(100.0) * dir.y,
  D3DVAL(100.0) * dir.z, FALSE);
    case VK_UP:
info->camera->SetVelocity(info->scene, up.x, up.y, up.z, FALSE);
break;

    case VK_DOWN:
info->camera->SetVelocity(info->scene, -up.x, -up.y, -up.z, FALSE);
break;

    case VK_RIGHT:
info->camera->SetVelocity(info->scene, right.x, right.y, right.z, FALSE);
break;

    case VK_LEFT:
info->camera->SetVelocity(info->scene, -right.x, -right.y, -right.z, FALSE);
break;

    case 'X':
if (sFrame)
    sFrame->SetVelocity(info->scene, dir.x, dir.y, dir.z, FALSE);
break;

    case 'Z':
if (sFrame)
    sFrame->SetVelocity(info->scene, -dir.x, -dir.y, -dir.z, FALSE);
break;

    case VK_SUBTRACT:
if (sFrame)
{   sFrame->AddScale(D3DRMCOMBINE_BEFORE, D3DVAL(0.9), D3DVAL(0.9), D3DVAL(0.9));
    SelectVisual(sVisual, sFrame);
}
break;

    case VK_ADD:
if (sFrame)
{   sFrame->AddScale(D3DRMCOMBINE_BEFORE, D3DVAL(1.1), D3DVAL(1.1), D3DVAL(1.1));
    SelectVisual(sVisual, sFrame);
}
break;
    }
}
break;

    case WM_KEYUP:
switch (wparam)
{
case 'T':
case 'R':
case VK_UP:
case VK_DOWN:
case VK_LEFT:
case VK_RIGHT:
    info->camera->SetVelocity(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), FALSE);
    break;

case 'Z':
case 'X':
    if (sFrame)
sFrame->SetVelocity(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), FALSE);
    break;
}
break;

    case WM_LBUTTONDOWN:
{   HCURSOR hCur;
    int x = LOWORD(lparam);
    int y = HIWORD(lparam);
    last_x = x;
    last_y = y;
    FindAndSelectVisual(info->view, x, y);
    left_drag = TRUE;
    SetCapture(win);
    /* change to a groovy cursor */
    hCur = LoadCursor(NULL, IDC_ARROW);
    if (hCur) oldCursor = SetCursor(hCur);
    else oldCursor = NULL;
}
break;

    case WM_LBUTTONUP:
ReleaseCapture();
left_drag = FALSE;
if (oldCursor) SetCursor(oldCursor);
break;

    case WM_RBUTTONDOWN:
{
    HCURSOR hCur;
    int x = LOWORD(lparam);
    int y = HIWORD(lparam);
    last_x = x;
    last_y = y;
    FindAndSelectVisual(info->view, x, y);
    right_drag = TRUE;
    SetCapture(win);
    /* change to a groovy cursor */
    hCur = LoadCursor(NULL, IDC_ARROW);
    if (hCur) oldCursor = SetCursor(hCur);
    else oldCursor = NULL;
}
break;

    case WM_RBUTTONUP:
right_drag = FALSE;
ReleaseCapture();
if (oldCursor) SetCursor(oldCursor);
break;

    case WM_MOUSEMOVE:
if ((wparam & MK_LBUTTON) && sFrame && left_drag)
{   double delta_x, delta_y;
    delta_x = SIGN_EXTEND(LOWORD(lparam)) - last_x;
    delta_y = -SIGN_EXTEND((HIWORD(lparam)) - last_y);
    last_x = SIGN_EXTEND(LOWORD(lparam));
    last_y = SIGN_EXTEND(HIWORD(lparam));
    {
double delta_r = sqrt(delta_x * delta_x + delta_y * delta_y);
double radius = 50;
double denom;

denom = sqrt(radius * radius + delta_r * delta_r);

if (delta_r == 0 || denom == 0) break;
sFrame->SetRotation
(   info->camera,
    D3DDivide(D3DVAL((float) delta_y), D3DVAL((float) delta_r)),
    D3DDivide(D3DVAL((float) -delta_x), D3DVAL((float) delta_r)),
    D3DVAL(0.0),
    D3DDivide(D3DVAL((float) delta_r), D3DVAL((float) denom))
);
    }
}
else if ((wparam & MK_RBUTTON) && sFrame && right_drag)
{   double delta_x, delta_y;
    D3DVECTOR p1;
    D3DRMVECTOR4D p2;

    delta_x = SIGN_EXTEND(LOWORD(lparam)) - last_x;
    delta_y = SIGN_EXTEND(HIWORD(lparam)) - last_y;
    last_x = SIGN_EXTEND(LOWORD(lparam));
    last_y = SIGN_EXTEND(HIWORD(lparam));
    sFrame->GetPosition(info->scene, &p1);
    info->view->Transform(&p2, &p1);
    p2.x += D3DMultiply(D3DVAL((float)delta_x), p2.w);
    p2.y += D3DMultiply(D3DVAL((float)delta_y), p2.w);
    info->view->InverseTransform(&p1, &p2);
    sFrame->SetPosition(info->scene, p1.x, p1.y, p1.z);
}
break;

    case WM_VSCROLL:
    {
if (active_window->lpPM)
{
    HRESULT hres;
    float fVal = gfVal;

    switch (LOWORD(wparam))
    {
    case SB_THUMBPOSITION:
fVal = (1.0F - ((float)HIWORD(wparam) / 100.0F));
break;
    case SB_LINEUP:
fVal += 0.01F;
break;
    case SB_LINEDOWN:
fVal -= 0.01F;
break;
    case SB_PAGEUP:
fVal += 0.1F;
break;
    case SB_PAGEDOWN:
fVal -= 0.1F;
break;
    case SB_THUMBTRACK:
fVal = (1.0F - ((float)HIWORD(wparam) / 100.0F));
break;
    default:
return 0;
    }

    if (fVal > 1.0F)
    {
fVal = 1.0F;
    }
    if (fVal < 0.0F)
    {
fVal = 0.0F;
    }

    hres = active_window->lpPM->SetDetail(fVal);
    gfVal = fVal;

    if (SUCCEEDED(hres))
    {
SetScrollPos(win, SB_VERT, (DWORD)(100.0 - (DWORD)(fVal * 100.0)),
     TRUE);
    }
    else
    {
if (hres != D3DRMERR_NOTENOUGHDATA)
{
    Msg("Error setting PM vertex detail");
}
    }
}
return 0;
    }
    case WM_COMMAND:
switch( wparam & 0xffff )
{
case MENU_FILE_ABOUT:
            DialogBox((HINSTANCE) GetWindowLong(win, GWL_HINSTANCE),"AboutBox", win, (DLGPROC)AboutDlgProc);
    break;

case MENU_FILE_OPEN:
  {  LPDIRECT3DRMMESHBUILDER2 builder;
     HRESULT rval;
     char *file = OpenNewFile(win, "Open a Mesh file");
     if (file)
       {
 if (FAILED(lpD3DRM->CreateMeshBuilder(&builder))) {
   Msg("Failed the create a builder for the new mesh.\n");
   break;
 }
 rval = builder->Load(file, NULL, D3DRMLOAD_FROMFILE,
      loadTextures, NULL);
 if (rval != D3DRM_OK) {
   Msg("Loading %s failed.\n%s", file, MyErrorToString(rval));
   builder->Release();
   break;
 }
 if (!PlaceMesh((LPDIRECT3DRMVISUAL)builder, info)) {
   Msg("Placing the mesh in the scene failed.\n");
   builder->Release();
   break;
 }
 builder->Release();
       }

     /*LoadAnimationSet(file, info);*/
     break;
  }

case MENU_FILE_OPEN_PMESH:
  {  LPDIRECT3DRMPROGRESSIVEMESH lpMesh;
     HRESULT rval;
     char *file = OpenNewFile(win, "Open a Progressive Mesh file");
     if (file)
       {
 if (FAILED(lpD3DRM->CreateProgressiveMesh(&lpMesh))) {
   Msg("Failed to create a new progressive mesh.\n");
   break;
 }
 rval = lpMesh->Load(file, NULL,
     D3DRMLOAD_FROMFILE |
     D3DRMLOAD_ASYNCHRONOUS,
     NULL, NULL);
 if (rval != D3DRM_OK) {
   Msg("Loading %s failed.\n%s", file, MyErrorToString(rval));
   lpMesh->Release();
   break;
 }
 if (!PlaceMesh((LPDIRECT3DRMVISUAL)lpMesh, info)) {
   Msg("Placing the mesh in the scene failed.\n");
   lpMesh->Release();
   break;
 }
 lpMesh->Release();
       }
     break;
  }

case MENU_FILE_OPEN_ANIMSET:
  {
    char *file = OpenNewFile(win, "Open Animation ...");
    if (file) {
      if (LoadAnimationSet(file, info) == FALSE) {
Msg("Loading and placing of %s failed.\n", file);
      }
    }
    break;
  }

case MENU_FILE_OPEN_FRAME:
    {
      char *file = OpenNewFile(win, "Open Frame ...");
      if (file) {
if (LoadFrameHierarchy(file, info) == FALSE) {
  Msg("Loading and placing of %s failed.\n", file);
}
      }
      break;
    }

case MENU_FILE_EXIT:
    PostQuitMessage(0);
    break;

case MENU_EDIT_CUT:
    CutVisual();
    break;

case MENU_EDIT_COPY:
    CopyVisual();
    break;

case MENU_EDIT_PASTE:
    PasteVisual(info->scene);
    break;

case MENU_EDIT_DELETE:
    DeleteVisual();
    break;

case MENU_EDIT_COLOR:
    if (sFrame)
    {
LPDIRECT3DRMMESHBUILDER mesh;
HRESULT rval;

if (FAILED(sVisual->QueryInterface(IID_IDirect3DRMMeshBuilder,
   (void**) &mesh)))
    break;

if (SelectedLight())
{
    D3DCOLOR c = SelectedLight()->GetColor();

    if (ChooseNewColor(win, &c)) {
mesh->SetColor(c);
SelectedLight()->SetColor(c);
    }
} else {
    D3DCOLOR c;

    if (mesh->GetFaceCount()) {
LPDIRECT3DRMFACEARRAY faces;
LPDIRECT3DRMFACE face;
mesh->GetFaces(&faces);
faces->GetElement(0, &face);
c = face->GetColor();
RELEASE(face);
RELEASE(faces);
    } else
c = D3DRMCreateColorRGB(D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));

    if (ChooseNewColor(win, &c)) {
rval = mesh->SetColor(c);
if (rval != D3DRM_OK)
    Msg("Failed to set the mesh's color.\n%s", MyErrorToString(rval));
    }
}

RELEASE(mesh);
    }
    break;

case MENU_EDIT_BOXES:
    {
HMENU menu;
int checked = ToggleBoxes() ? MF_CHECKED : MF_UNCHECKED;
menu = GetMenu(win);
menu = GetSubMenu(menu, 1);
CheckMenuItem(menu, MENU_EDIT_BOXES, MF_BYCOMMAND|checked);
    }
    break;

case MENU_QUALITY_LIGHTING:
    ToggleLighting(win, info);
    break;

case MENU_QUALITY_POINTS:
    SetFillMode(win, info, D3DRMFILL_POINTS);
    break;

case MENU_QUALITY_WIREFRAME:
    SetFillMode(win, info, D3DRMFILL_WIREFRAME);
    break;

case MENU_QUALITY_SOLID:
    SetFillMode(win, info, D3DRMFILL_SOLID);
    break;

case MENU_QUALITY_FLAT:
    SetShadeMode(win, info, D3DRMSHADE_FLAT);
    break;

case MENU_QUALITY_GOURAUD:
    SetShadeMode(win, info, D3DRMSHADE_GOURAUD);
    break;

case MENU_QUALITY_PHONG:
    SetShadeMode(win, info, D3DRMSHADE_PHONG);
    break;

case MENU_MODEL_MONO:
    if (!SetModel(win, info, D3DCOLOR_MONO))
bQuit = TRUE;
    break;

case MENU_MODEL_RGB:
    if (!SetModel(win, info, D3DCOLOR_RGB))
bQuit = TRUE;
    break;

case MENU_DITHER:
    ToggleDither(win, info);
    break;

case MENU_TEXTURE_FILTERING:
    ToggleTextureFiltering(win, info);
    break;

case MENU_LIGHT_DIRECTIONAL:
case MENU_LIGHT_PARALLEL_POINT:
case MENU_LIGHT_POINT:
case MENU_LIGHT_SPOT:
    {
CreateLight(wparam, info);
    }
    break;
}
break;

    case WM_DESTROY:
PostQuitMessage( 0 );
bQuit = TRUE;
break;

    case WM_SIZE:
{
    int width = LOWORD(lparam);
    int height = HIWORD(lparam);
if (width && height) {
if (!ResizeViewport(win, info, width, height)) {
    bQuit = TRUE;
    break;
}
active_window->bMinimized = FALSE;
} else {
active_window->bMinimized = TRUE;
}
}
break;

    case WM_ACTIVATE:
{   LPDIRECT3DRMWINDEVICE windev;
    if (!info || !info->dev) break;
    if (SUCCEEDED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev)))
    {if (FAILED(windev->HandleActivate(wparam)))
    Msg("Failed to handle WM_ACTIVATE.\n");
windev->Release();
    } else {
Msg("Failed to create Windows device to handle WM_ACTIVATE.\n");
    }
}
break;

    case WM_PAINT:
if (info)
{   RECT r;
    PAINTSTRUCT ps;
    LPDIRECT3DRMWINDEVICE windev;

    if (!info || !info->dev) break;
    if (GetUpdateRect(win, &r, FALSE))
    {BeginPaint(win, &ps);
    if (SUCCEEDED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev)))
{   if (FAILED(windev->HandlePaint(ps.hdc)))
Msg("Failed to handle WM_PAINT.\n");
    windev->Release();
} else {
    Msg("Failed to create Windows device to handle WM_PAINT.\n");
}
EndPaint(win, &ps);
    }
}

else return DefWindowProc(win, msg, wparam, lparam);

    default:
return DefWindowProc(win, msg, wparam, lparam);
    }
    return 0L;
}

/*
 * MyErrorToString
 * Returns a pointer to a string describing the given error code.
 */
char*
MyErrorToString(HRESULT error)
{
    switch(error) {
        case DD_OK:
    /* Also includes D3D_OK and D3DRM_OK */
            return "No error.\0";
        case DDERR_ALREADYINITIALIZED:
            return "This object is already initialized.\0";
        case DDERR_BLTFASTCANTCLIP:
            return "Return if a clipper object is attached to the source surface passed into a BltFast call.\0";
        case DDERR_CANNOTATTACHSURFACE:
            return "This surface can not be attached to the requested surface.\0";
        case DDERR_CANNOTDETACHSURFACE:
            return "This surface can not be detached from the requested surface.\0";
        case DDERR_CANTCREATEDC:
            return "Windows can not create any more DCs.\0";
        case DDERR_CANTDUPLICATE:
            return "Can't duplicate primary & 3D surfaces, or surfaces that are implicitly created.\0";
        case DDERR_CLIPPERISUSINGHWND:
            return "An attempt was made to set a cliplist for a clipper object that is already monitoring an hwnd.\0";
        case DDERR_COLORKEYNOTSET:
            return "No src color key specified for this operation.\0";
        case DDERR_CURRENTLYNOTAVAIL:
            return "Support is currently not available.\0";
        case DDERR_DIRECTDRAWALREADYCREATED:
            return "A DirectDraw object representing this driver has already been created for this process.\0";
        case DDERR_EXCEPTION:
            return "An exception was encountered while performing the requested operation.\0";
        case DDERR_EXCLUSIVEMODEALREADYSET:
            return "An attempt was made to set the cooperative level when it was already set to exclusive.\0";
        case DDERR_GENERIC:
            return "Generic failure.\0";
        case DDERR_HEIGHTALIGN:
            return "Height of rectangle provided is not a multiple of reqd alignment.\0";
        case DDERR_HWNDALREADYSET:
            return "The CooperativeLevel HWND has already been set. It can not be reset while the process has surfaces or palettes created.\0";
        case DDERR_HWNDSUBCLASSED:
            return "HWND used by DirectDraw CooperativeLevel has been subclassed, this prevents DirectDraw from restoring state.\0";
        case DDERR_IMPLICITLYCREATED:
            return "This surface can not be restored because it is an implicitly created surface.\0";
        case DDERR_INCOMPATIBLEPRIMARY:
            return "Unable to match primary surface creation request with existing primary surface.\0";
        case DDERR_INVALIDCAPS:
            return "One or more of the caps bits passed to the callback are incorrect.\0";
        case DDERR_INVALIDCLIPLIST:
            return "DirectDraw does not support the provided cliplist.\0";
        case DDERR_INVALIDDIRECTDRAWGUID:
            return "The GUID passed to DirectDrawCreate is not a valid DirectDraw driver identifier.\0";
        case DDERR_INVALIDMODE:
            return "DirectDraw does not support the requested mode.\0";
        case DDERR_INVALIDOBJECT:
            return "DirectDraw received a pointer that was an invalid DIRECTDRAW object.\0";
        case DDERR_INVALIDPARAMS:
            return "One or more of the parameters passed to the function are incorrect.\0";
        case DDERR_INVALIDPIXELFORMAT:
            return "The pixel format was invalid as specified.\0";
        case DDERR_INVALIDPOSITION:
            return "Returned when the position of the overlay on the destination is no longer legal for that destination.\0";
        case DDERR_INVALIDRECT:
            return "Rectangle provided was invalid.\0";
        case DDERR_LOCKEDSURFACES:
            return "Operation could not be carried out because one or more surfaces are locked.\0";
        case DDERR_NO3D:
            return "There is no 3D present.\0";
        case DDERR_NOALPHAHW:
            return "Operation could not be carried out because there is no alpha accleration hardware present or available.\0";
        case DDERR_NOBLTHW:
            return "No blitter hardware present.\0";
        case DDERR_NOCLIPLIST:
            return "No cliplist available.\0";
        case DDERR_NOCLIPPERATTACHED:
            return "No clipper object attached to surface object.\0";
        case DDERR_NOCOLORCONVHW:
            return "Operation could not be carried out because there is no color conversion hardware present or available.\0";
        case DDERR_NOCOLORKEY:
            return "Surface doesn't currently have a color key\0";
        case DDERR_NOCOLORKEYHW:
            return "Operation could not be carried out because there is no hardware support of the destination color key.\0";
        case DDERR_NOCOOPERATIVELEVELSET:
            return "Create function called without DirectDraw object method SetCooperativeLevel being called.\0";
        case DDERR_NODC:
            return "No DC was ever created for this surface.\0";
        case DDERR_NODDROPSHW:
            return "No DirectDraw ROP hardware.\0";
        case DDERR_NODIRECTDRAWHW:
            return "A hardware-only DirectDraw object creation was attempted but the driver did not support any hardware.\0";
        case DDERR_NOEMULATION:
            return "Software emulation not available.\0";
        case DDERR_NOEXCLUSIVEMODE:
            return "Operation requires the application to have exclusive mode but the application does not have exclusive mode.\0";
        case DDERR_NOFLIPHW:
            return "Flipping visible surfaces is not supported.\0";
        case DDERR_NOGDI:
            return "There is no GDI present.\0";
        case DDERR_NOHWND:
            return "Clipper notification requires an HWND or no HWND has previously been set as the CooperativeLevel HWND.\0";
        case DDERR_NOMIRRORHW:
            return "Operation could not be carried out because there is no hardware present or available.\0";
        case DDERR_NOOVERLAYDEST:
            return "Returned when GetOverlayPosition is called on an overlay that UpdateOverlay has never been called on to establish a destination.\0";
        case DDERR_NOOVERLAYHW:
            return "Operation could not be carried out because there is no overlay hardware present or available.\0";
        case DDERR_NOPALETTEATTACHED:
            return "No palette object attached to this surface.\0";
        case DDERR_NOPALETTEHW:
            return "No hardware support for 16 or 256 color palettes.\0";
        case DDERR_NORASTEROPHW:
            return "Operation could not be carried out because there is no appropriate raster op hardware present or available.\0";
        case DDERR_NOROTATIONHW:
            return "Operation could not be carried out because there is no rotation hardware present or available.\0";
        case DDERR_NOSTRETCHHW:
            return "Operation could not be carried out because there is no hardware support for stretching.\0";
        case DDERR_NOT4BITCOLOR:
            return "DirectDrawSurface is not in 4 bit color palette and the requested operation requires 4 bit color palette.\0";
        case DDERR_NOT4BITCOLORINDEX:
            return "DirectDrawSurface is not in 4 bit color index palette and the requested operation requires 4 bit color index palette.\0";
        case DDERR_NOT8BITCOLOR:
            return "DirectDrawSurface is not in 8 bit color mode and the requested operation requires 8 bit color.\0";
        case DDERR_NOTAOVERLAYSURFACE:
            return "Returned when an overlay member is called for a non-overlay surface.\0";
        case DDERR_NOTEXTUREHW:
            return "Operation could not be carried out because there is no texture mapping hardware present or available.\0";
        case DDERR_NOTFLIPPABLE:
            return "An attempt has been made to flip a surface that is not flippable.\0";
        case DDERR_NOTFOUND:
            return "Requested item was not found.\0";
        case DDERR_NOTLOCKED:
            return "Surface was not locked.  An attempt to unlock a surface that was not locked at all, or by this process, has been attempted.\0";
        case DDERR_NOTPALETTIZED:
            return "The surface being used is not a palette-based surface.\0";
        case DDERR_NOVSYNCHW:
            return "Operation could not be carried out because there is no hardware support for vertical blank synchronized operations.\0";
        case DDERR_NOZBUFFERHW:
            return "Operation could not be carried out because there is no hardware support for zbuffer blitting.\0";
        case DDERR_NOZOVERLAYHW:
            return "Overlay surfaces could not be z layered based on their BltOrder because the hardware does not support z layering of overlays.\0";
        case DDERR_OUTOFCAPS:
            return "The hardware needed for the requested operation has already been allocated.\0";
        case DDERR_OUTOFMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OUTOFVIDEOMEMORY:
            return "DirectDraw does not have enough memory to perform the operation.\0";
        case DDERR_OVERLAYCANTCLIP:
            return "The hardware does not support clipped overlays.\0";
        case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
            return "Can only have ony color key active at one time for overlays.\0";
        case DDERR_OVERLAYNOTVISIBLE:
            return "Returned when GetOverlayPosition is called on a hidden overlay.\0";
        case DDERR_PALETTEBUSY:
            return "Access to this palette is being refused because the palette is already locked by another thread.\0";
        case DDERR_PRIMARYSURFACEALREADYEXISTS:
            return "This process already has created a primary surface.\0";
        case DDERR_REGIONTOOSMALL:
            return "Region passed to Clipper::GetClipList is too small.\0";
        case DDERR_SURFACEALREADYATTACHED:
            return "This surface is already attached to the surface it is being attached to.\0";
        case DDERR_SURFACEALREADYDEPENDENT:
            return "This surface is already a dependency of the surface it is being made a dependency of.\0";
        case DDERR_SURFACEBUSY:
            return "Access to this surface is being refused because the surface is already locked by another thread.\0";
        case DDERR_SURFACEISOBSCURED:
            return "Access to surface refused because the surface is obscured.\0";
        case DDERR_SURFACELOST:
            return "Access to this surface is being refused because the surface memory is gone. The DirectDrawSurface object representing this surface should have Restore called on it.\0";
        case DDERR_SURFACENOTATTACHED:
            return "The requested surface is not attached.\0";
        case DDERR_TOOBIGHEIGHT:
            return "Height requested by DirectDraw is too large.\0";
        case DDERR_TOOBIGSIZE:
            return "Size requested by DirectDraw is too large, but the individual height and width are OK.\0";
        case DDERR_TOOBIGWIDTH:
            return "Width requested by DirectDraw is too large.\0";
        case DDERR_UNSUPPORTED:
            return "Action not supported.\0";
        case DDERR_UNSUPPORTEDFORMAT:
            return "FOURCC format requested is unsupported by DirectDraw.\0";
        case DDERR_UNSUPPORTEDMASK:
            return "Bitmask in the pixel format requested is unsupported by DirectDraw.\0";
        case DDERR_VERTICALBLANKINPROGRESS:
            return "Vertical blank is in progress.\0";
        case DDERR_WASSTILLDRAWING:
            return "Informs DirectDraw that the previous Blt which is transfering information to or from this Surface is incomplete.\0";
        case DDERR_WRONGMODE:
            return "This surface can not be restored because it was created in a different mode.\0";
        case DDERR_XALIGN:
            return "Rectangle provided was not horizontally aligned on required boundary.\0";
        case D3DERR_BADMAJORVERSION:
            return "D3DERR_BADMAJORVERSION\0";
        case D3DERR_BADMINORVERSION:
            return "D3DERR_BADMINORVERSION\0";
        case D3DERR_EXECUTE_LOCKED:
            return "D3DERR_EXECUTE_LOCKED\0";
        case D3DERR_EXECUTE_NOT_LOCKED:
            return "D3DERR_EXECUTE_NOT_LOCKED\0";
        case D3DERR_EXECUTE_CREATE_FAILED:
            return "D3DERR_EXECUTE_CREATE_FAILED\0";
        case D3DERR_EXECUTE_DESTROY_FAILED:
            return "D3DERR_EXECUTE_DESTROY_FAILED\0";
        case D3DERR_EXECUTE_LOCK_FAILED:
            return "D3DERR_EXECUTE_LOCK_FAILED\0";
        case D3DERR_EXECUTE_UNLOCK_FAILED:
            return "D3DERR_EXECUTE_UNLOCK_FAILED\0";
        case D3DERR_EXECUTE_FAILED:
            return "D3DERR_EXECUTE_FAILED\0";
        case D3DERR_EXECUTE_CLIPPED_FAILED:
            return "D3DERR_EXECUTE_CLIPPED_FAILED\0";
        case D3DERR_TEXTURE_NO_SUPPORT:
            return "D3DERR_TEXTURE_NO_SUPPORT\0";
        case D3DERR_TEXTURE_NOT_LOCKED:
            return "D3DERR_TEXTURE_NOT_LOCKED\0";
        case D3DERR_TEXTURE_LOCKED:
            return "D3DERR_TEXTURELOCKED\0";
        case D3DERR_TEXTURE_CREATE_FAILED:
            return "D3DERR_TEXTURE_CREATE_FAILED\0";
        case D3DERR_TEXTURE_DESTROY_FAILED:
            return "D3DERR_TEXTURE_DESTROY_FAILED\0";
        case D3DERR_TEXTURE_LOCK_FAILED:
            return "D3DERR_TEXTURE_LOCK_FAILED\0";
        case D3DERR_TEXTURE_UNLOCK_FAILED:
            return "D3DERR_TEXTURE_UNLOCK_FAILED\0";
        case D3DERR_TEXTURE_LOAD_FAILED:
            return "D3DERR_TEXTURE_LOAD_FAILED\0";
        case D3DERR_MATRIX_CREATE_FAILED:
            return "D3DERR_MATRIX_CREATE_FAILED\0";
        case D3DERR_MATRIX_DESTROY_FAILED:
            return "D3DERR_MATRIX_DESTROY_FAILED\0";
        case D3DERR_MATRIX_SETDATA_FAILED:
            return "D3DERR_MATRIX_SETDATA_FAILED\0";
        case D3DERR_SETVIEWPORTDATA_FAILED:
            return "D3DERR_SETVIEWPORTDATA_FAILED\0";
        case D3DERR_MATERIAL_CREATE_FAILED:
            return "D3DERR_MATERIAL_CREATE_FAILED\0";
        case D3DERR_MATERIAL_DESTROY_FAILED:
            return "D3DERR_MATERIAL_DESTROY_FAILED\0";
        case D3DERR_MATERIAL_SETDATA_FAILED:
            return "D3DERR_MATERIAL_SETDATA_FAILED\0";
        case D3DERR_LIGHT_SET_FAILED:
            return "D3DERR_LIGHT_SET_FAILED\0";
case D3DRMERR_BADOBJECT:
    return "D3DRMERR_BADOBJECT\0";
case D3DRMERR_BADTYPE:
    return "D3DRMERR_BADTYPE\0";
case D3DRMERR_BADALLOC:
    return "D3DRMERR_BADALLOC\0";
case D3DRMERR_FACEUSED:
    return "D3DRMERR_FACEUSED\0";
case D3DRMERR_NOTFOUND:
    return "D3DRMERR_NOTFOUND\0";
case D3DRMERR_NOTDONEYET:
    return "D3DRMERR_NOTDONEYET\0";
case D3DRMERR_FILENOTFOUND:
    return "The file was not found.\0";
case D3DRMERR_BADFILE:
    return "D3DRMERR_BADFILE\0";
case D3DRMERR_BADDEVICE:
    return "D3DRMERR_BADDEVICE\0";
case D3DRMERR_BADVALUE:
    return "D3DRMERR_BADVALUE\0";
case D3DRMERR_BADMAJORVERSION:
    return "D3DRMERR_BADMAJORVERSION\0";
case D3DRMERR_BADMINORVERSION:
    return "D3DRMERR_BADMINORVERSION\0";
case D3DRMERR_UNABLETOEXECUTE:
    return "D3DRMERR_UNABLETOEXECUTE\0";
        default:
            return "Unrecognized error value.\0";
    }
}


VIEWER.H

/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: viewer.h
 *
 ***************************************************************************/

#define MENU_FILE_ABOUT  1
#define MENU_FILE_OPEN  2
#define MENU_FILE_OPEN_ANIMSET            3
#define MENU_FILE_OPEN_FRAME              4
#define MENU_FILE_EXIT  5
#define MENU_FILE_NEW_WINDOW      6
#define MENU_FILE_OPEN_PMESH      7

#define MENU_EDIT_CUT100
#define MENU_EDIT_COPY101
#define MENU_EDIT_PASTE102
#define MENU_EDIT_DELETE103
#define MENU_EDIT_COLOR104
#define MENU_EDIT_BOXES105

#define COLOR_ALPHAACCEL                711
#define COLOR_ALPHA                     714

#define MENU_QUALITY_LIGHTING200
#define MENU_QUALITY_POINTS201
#define MENU_QUALITY_WIREFRAME202
#define MENU_QUALITY_SOLID203
#define MENU_QUALITY_FLAT204
#define MENU_QUALITY_GOURAUD205
#define MENU_QUALITY_PHONG206

#define MENU_MODEL_MONO207
#define MENU_MODEL_RGB208

#define MENU_DITHER209

#define MENU_TEXTURE_FILTERING210

#define MENU_LIGHT_DIRECTIONAL301
#define MENU_LIGHT_PARALLEL_POINT 302
#define MENU_LIGHT_POINT303
#define MENU_LIGHT_SPOT304

#undef RELEASE
#ifdef __cplusplus
#define RELEASE(x) if (x != NULL) {x->Release(); x = NULL;}
#else
#define RELEASE(x) if (x != NULL) {x->lpVtbl->Release(x); x = NULL;}
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef struct _AppInfo
{
    LPDIRECT3DRMFRAME2 scene, camera;
    LPDIRECT3DRMDEVICE dev1;
    LPDIRECT3DRMDEVICE2 dev;
    LPDIRECT3DRMVIEWPORT view;
    D3DRMCOLORMODEL model;
    LPDIRECT3DRMPROGRESSIVEMESH lpPM;
    BOOL bMinimized;
    HWND win;
} AppInfo;

extern AppInfo* active_window;

extern LPDIRECT3DRM2 lpD3DRM;
extern float gfVal;

extern void SelectPM(LPDIRECT3DRMPROGRESSIVEMESH);
extern void WINAPI GetDXVersion(LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform);
char* OpenNewFile(HWND, const char *);
int ChooseNewColor(HWND, D3DCOLOR*);
LPDIRECT3DRMFRAME D3DRMLoadXAFAsFrame(const char* s);
HRESULT D3DRMLoadXAFAsList(const char* s, size_t*, LPDIRECT3DRMOBJECT**);

#ifdef __cplusplus
}
#endif


README.TXT

(NOTE: this file contains extended ASCII and may not appear
       correctly if your font does not support extended ASCII
       characters.  This file will appear correctly in the
       MS-DOS Editor.)

This effect uses palette animation.

Here is how it works:

Imagine a 4x4 display using 4 colors.  We could set the colors
up to look something like this:

   1 2 3 4
   1 2 3 4
   1 2 3 4
   1 2 3 4

Now we can cycle all of the colors in each row to the right. (The
one on the right will wrap-around to the left.)

   4 1 2 3
   4 1 2 3
   4 1 2 3
   4 1 2 3

If we continue this cycling we would get animated lines moving
to the right.  The same can be done to animate the lines going
down. i.e.

   1 1 1 1      4 4 4 4
   2 2 2 2  ->  1 1 1 1
   3 3 3 3  ->  2 2 2 2
   4 4 4 4      3 3 3 3

Now if we expand our palette to 16 color we can combine moving
down and right at the same time.

   Move right:

       1  2  3  4       4  1  2  3
       5  6  7  8  ->   8  5  6  7
       9 10 11 12  ->  12  9 10 11
      13 14 15 16      16 13 14 15

   Move down:

       1  2  3  4      13 14 15 16
       5  6  7  8  ->   1  2  3  4
       9 10 11 12  ->   5  6  7  8
      13 14 15 16       9 10 11 12

   Move right and down:

       1  2  3  4       4  1  2  3      16 13 14 15
       5  6  7  8  ->   8  5  6  7  ->   4  1  2  3
       9 10 11 12  ->  12  9 10 11  ->   8  5  6  7
      13 14 15 16      16 13 14 15      12  9 10 11

Now if you tile these 4x4 blocks end to end and cycle the colors
as above, you get a moving checkerboard.

                      ->                    ->
                      ->                    ->

Wormhole does the same thing, except it uses 15x15 blocks (225
colors) and instead of tiling the blocks end to end on a flat
plane, it tiles them in 3D converging at the center of the
wormhole.

The following code will generate the 3D wormhole using the
aforementioned 15x15 grids.

//Do all the work!
//convert r,theta,z to x,y,x to screen x,y
//plot the point
//z=-1.0+(log(2.0*j/DIVS) is the line that sets the math eqn for plot
//Feel free to try other functions!
//Cylindrical coordinates, i.e. z=f(r,theta)

#define STRETCH 25
#define PI      3.14159265358979323846
#define XCENTER 160
#define YCENTER 50
#define DIVS    1200
#define SPOKES  2400

void transarray(void)
{
float x,y,z;
int i,j,color;
for(j=1;j<DIVS+1;j++)
for(i=0;i<SPOKES;i++)
{
z=-1.0+(log(2.0*j/DIVS));
x=(320.0*j/DIVS*cos(2*PI*i/SPOKES));
y=(240.0*j/DIVS*sin(2*PI*i/SPOKES));
y=y-STRETCH*z;
x+=XCENTER;
y+=YCENTER;
color=((i/8)%15)+15*((j/6)%15)+1;
if ((x>=0)&&(x<=320)&&(y>=0)&&(y<=200))
plot((int) x,(int) y,color);
}
}

After loading the bitmap to a direct draw surface, all that is
left to do is rotate the colors and you have a wormhole!


WORMHOLE.CPP

/***************************************************************************
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 ***************************************************************************/
#include <windows.h>
#include <ddraw.h>

HWND            hWnd;
PALETTEENTRY    pe[256];
BOOL            bActive;
BOOL            bIsInitialized=FALSE;

LPDIRECTDRAW            lpDD = NULL;
LPDIRECTDRAWSURFACE     lpDDSPrimary = NULL;
LPDIRECTDRAWSURFACE     lpDDSOne = NULL;
LPDIRECTDRAWCLIPPER     lpClipper = NULL;
LPDIRECTDRAWPALETTE     lpDDPal = NULL;

BOOL restoreAll();
void updateFrame();
static void finiObjects();
long FAR PASCAL WindowProc(HWND,UINT,WPARAM,LPARAM);
BOOL initFail(HWND);
static BOOL doInit(HINSTANCE, int);
void CyclePalette();
BOOL readBMPIntoSurfaces();
int PASCAL WinMain(HINSTANCE,HINSTANCE,LPSTR,int);

BOOL restoreAll()
{
    BOOL bResult;

    bResult=lpDDSPrimary->Restore() == DD_OK &&
            lpDDSOne->Restore()     == DD_OK;

    readBMPIntoSurfaces();

    return(bResult);
}

void updateFrame()
{
    RECT                rcRect;
    RECT                destRect;
    HRESULT             ddrval;
    POINT               pt;

    rcRect.left=0;
    rcRect.top=0;
    rcRect.right=640;
    rcRect.bottom=480;

    GetClientRect(hWnd,&destRect);

    pt.x=pt.y=0;
    ClientToScreen(hWnd,&pt);
    OffsetRect(&destRect,pt.x,pt.y);

    while(1)
    {
        ddrval=lpDDSPrimary->Blt(&destRect,lpDDSOne,&rcRect,0,NULL);

        if(ddrval==DD_OK)
        {
            break;
        }
        if(ddrval==DDERR_SURFACELOST)
        {
            if(!restoreAll())
            {
                return;
            }
            continue;
        }
        if(ddrval!=DDERR_WASSTILLDRAWING)
        {
            return;
        }
    }
}

static void finiObjects()
{
    if(lpDD!=NULL)
    {
        if(lpDDSPrimary!=NULL)
        {
            lpDDSPrimary->Release();
            lpDDSPrimary=NULL;
        }
        if(lpDDSOne!=NULL)
        {
            lpDDSOne->Release();
            lpDDSOne=NULL;
        }
        if(lpDDPal!=NULL)
        {
            lpDDPal->Release();
            lpDDPal=NULL;
        }
        lpDD->Release();
        lpDD=NULL;
    }
}

long FAR PASCAL WindowProc(HWND hWnd,UINT message,
                           WPARAM wParam,LPARAM lParam )
{
    switch(message)
    {
        case WM_ACTIVATE:
            bActive = wParam;
            break;

        case WM_CREATE:

            break;

        case WM_SETCURSOR:

            SetCursor(NULL);
    if( bIsInitialized )
    {
                updateFrame();
                lpDDPal->GetEntries(0,0,256,pe);
    }
            break;

        case WM_KEYDOWN:

            switch(wParam)
            {
                case VK_ESCAPE:

                case VK_F12:

                    PostMessage(hWnd,WM_CLOSE,0,0);
                    break;
            }
            break;

        case WM_DESTROY:

            finiObjects();
            PostQuitMessage(0);
            break;
    }
    return DefWindowProc(hWnd,message,wParam,lParam);
}

BOOL initFail(HWND hWnd)
{
    finiObjects();
    MessageBox(hWnd,"DirectDraw Init FAILED","WormHole",MB_OK);
    DestroyWindow(hWnd);
    return FALSE;
}

static BOOL doInit(HINSTANCE hInstance,int nCmdShow)
{
    WNDCLASS            wc;
    DDSURFACEDESC       ddsd;
    HRESULT             ddrval;

    wc.style=          CS_HREDRAW|CS_VREDRAW;
    wc.lpfnWndProc=    WindowProc;
    wc.cbClsExtra=     0;
    wc.cbWndExtra=     0;
    wc.hInstance=      hInstance;
    wc.hIcon=          LoadIcon(hInstance,IDI_APPLICATION);
    wc.hCursor=        LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground=  NULL;
    wc.lpszMenuName=   NULL;
    wc.lpszClassName=  "WormHole";
    RegisterClass(&wc);

    hWnd=CreateWindowEx(
        0,
        "WormHole",
        "WormHole",
        WS_POPUP,
        0,
        0,
        GetSystemMetrics(SM_CXSCREEN),
        GetSystemMetrics(SM_CYSCREEN),
        NULL,
        NULL,
        hInstance,
        NULL );

    if(!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd,nCmdShow);
    UpdateWindow(hWnd);

    ddrval=DirectDrawCreate(NULL,&lpDD,NULL);

    if(ddrval!=DD_OK)
    {
        return initFail(hWnd);
    }

    ddrval=lpDD->SetCooperativeLevel(hWnd,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
    ddrval=lpDD->SetDisplayMode(640,480,8);

    if(ddrval!=DD_OK)
    {
        return initFail(hWnd);
    }

    ddsd.dwSize=sizeof(ddsd);
    ddsd.dwFlags=DDSD_CAPS;
    ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;

    ddrval=lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL);

    if(ddrval!=DD_OK)
    {
        return initFail(hWnd);
    }

    ddsd.dwSize=sizeof(ddsd);
    ddsd.dwFlags=DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth=640;
    ddsd.dwHeight=480;

    lpDD->CreateSurface(&ddsd,&lpDDSOne,NULL);
    if(lpDDSOne==NULL)
    {
        return initFail(hWnd);
    }

    bIsInitialized = TRUE;
    return TRUE;
}

void CyclePalette()
{
    int                 reg[15];
    int                 k;

    for(k=0;k<15;k++)
    {
        reg[k]=pe[k+30].peRed;
    }
    for(k=45;k<255;k++)
    {
        pe[k-15].peRed=pe[k].peRed;
    }
    for(k=0;k<15;k++)
    {
        pe[k+240].peRed=reg[k];
    }
    for(k=0;k<15;k++)
    {
        reg[k]=pe[k+30].peGreen;
    }
    for(k=45;k<255;k++)
    {
        pe[k-15].peGreen=pe[k].peGreen;
    }
    for(k=0;k<15;k++)
    {
        pe[k+240].peGreen=reg[k];
    }
    for(k=0;k<15;k++)
    {
        reg[k]=pe[k+30].peBlue;
    }
    for(k=45;k<255;k++)
    {
        pe[k-15].peBlue=pe[k].peBlue;
    }
    for(k=0;k<15;k++)
    {
        pe[k+240].peBlue=reg[k];
    }

    for(k=2;k<17;k++)
    {
        reg[k-2]=pe[15*k+14].peRed;
        pe[15*k+14].peRed=pe[15*k+13].peRed;
        pe[15*k+13].peRed=pe[15*k+12].peRed;
        pe[15*k+12].peRed=pe[15*k+11].peRed;
        pe[15*k+11].peRed=pe[15*k+10].peRed;
        pe[15*k+10].peRed=pe[15*k+9].peRed;
        pe[15*k+9].peRed=pe[15*k+8].peRed;
        pe[15*k+8].peRed=pe[15*k+7].peRed;
        pe[15*k+7].peRed=pe[15*k+6].peRed;
        pe[15*k+6].peRed=pe[15*k+5].peRed;
        pe[15*k+5].peRed=pe[15*k+4].peRed;
        pe[15*k+4].peRed=pe[15*k+3].peRed;
        pe[15*k+3].peRed=pe[15*k+2].peRed;
        pe[15*k+2].peRed=pe[15*k+1].peRed;
        pe[15*k+1].peRed=pe[15*k].peRed;
        pe[15*k].peRed=reg[k-2];
        reg[k-2]=pe[15*k+14].peGreen;
        pe[15*k+14].peGreen=pe[15*k+13].peGreen;
        pe[15*k+13].peGreen=pe[15*k+12].peGreen;
        pe[15*k+12].peGreen=pe[15*k+11].peGreen;
        pe[15*k+11].peGreen=pe[15*k+10].peGreen;
        pe[15*k+10].peGreen=pe[15*k+9].peGreen;
        pe[15*k+9].peGreen=pe[15*k+8].peGreen;
        pe[15*k+8].peGreen=pe[15*k+7].peGreen;
        pe[15*k+7].peGreen=pe[15*k+6].peGreen;
        pe[15*k+6].peGreen=pe[15*k+5].peGreen;
        pe[15*k+5].peGreen=pe[15*k+4].peGreen;
        pe[15*k+4].peGreen=pe[15*k+3].peGreen;
        pe[15*k+3].peGreen=pe[15*k+2].peGreen;
        pe[15*k+2].peGreen=pe[15*k+1].peGreen;
        pe[15*k+1].peGreen=pe[15*k].peGreen;
        pe[15*k].peGreen=reg[k-2];
        reg[k-2]=pe[15*k+14].peBlue;
        pe[15*k+14].peBlue=pe[15*k+13].peBlue;
        pe[15*k+13].peBlue=pe[15*k+12].peBlue;
        pe[15*k+12].peBlue=pe[15*k+11].peBlue;
        pe[15*k+11].peBlue=pe[15*k+10].peBlue;
        pe[15*k+10].peBlue=pe[15*k+9].peBlue;
        pe[15*k+9].peBlue=pe[15*k+8].peBlue;
        pe[15*k+8].peBlue=pe[15*k+7].peBlue;
        pe[15*k+7].peBlue=pe[15*k+6].peBlue;
        pe[15*k+6].peBlue=pe[15*k+5].peBlue;
        pe[15*k+5].peBlue=pe[15*k+4].peBlue;
        pe[15*k+4].peBlue=pe[15*k+3].peBlue;
        pe[15*k+3].peBlue=pe[15*k+2].peBlue;
        pe[15*k+2].peBlue=pe[15*k+1].peBlue;
        pe[15*k+1].peBlue=pe[15*k].peBlue;
        pe[15*k].peBlue=reg[k-2];
    }

    lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,NULL);

    if(lpDDPal->SetEntries(0,0,256,pe)!=DD_OK)
    {
        return;
    }
}

BOOL readBMPIntoSurfaces()
{
    HRESULT             ddrval;
    HRSRC               hBMP;
    RGBQUAD             Palette[256];
    PALETTEENTRY        pe[256];
    DDSURFACEDESC       DDSDesc;
    LPSTR               lpBits;
    LPSTR               lpSrc;
    BYTE                *lpBMP;
    int                 i;

    hBMP=FindResource(NULL,"wormhole_bmp",RT_BITMAP);
    if( hBMP == NULL )
    {
        return FALSE;
    }

    lpBMP=(BYTE *)LockResource(LoadResource(NULL, hBMP));

    memcpy(Palette,&lpBMP[sizeof(BITMAPINFOHEADER)],sizeof(Palette));

    FreeResource(hBMP);

    for(i=0;i<256;i++)
    {
        pe[i].peRed=Palette[i].rgbRed;
        pe[i].peGreen=Palette[i].rgbGreen;
        pe[i].peBlue=Palette[i].rgbBlue;
    }

    if (NULL == lpDDPal) {

      ddrval=lpDD->CreatePalette(DDPCAPS_8BIT,pe,&lpDDPal,NULL);

      if(ddrval!=DD_OK)
      {
          return FALSE;
      }
    }

    lpDDSPrimary->SetPalette(lpDDPal);

    DDSDesc.dwSize=sizeof(DDSDesc);
    ddrval=lpDDSOne->Lock(NULL,&DDSDesc,0,NULL);
    if(ddrval!=DD_OK)
    {
        return FALSE;
    }

    lpBits=(LPSTR)DDSDesc.lpSurface;
    lpSrc=(LPSTR)
          (&lpBMP[sizeof(BITMAPINFOHEADER)+sizeof(Palette)+(640*480)]);
    for(i=0;i<480;i++)
    {
        memcpy(lpBits,lpSrc,640);
        lpBits+=DDSDesc.lPitch;
        lpSrc-=640;
    }
    lpDDSOne->Unlock(NULL);

    return TRUE;
}

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,int nCmdShow)
{
    MSG         msg;

    if(!doInit(hInstance,nCmdShow))
    {
        return FALSE;
    }

    readBMPIntoSurfaces();
    updateFrame();

    while(1)
    {
        if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
        {
            if(!GetMessage(&msg,NULL,0,0))
            {
                return msg.wParam;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if(bActive)
        {
            CyclePalette();
        }
        else
        {
            WaitMessage();
        }
    }
}


README.TXT

Readme.txt for CSTEXT

1. Purpose

This sample application shows how to accept keyboard input and display it to the screen, so that complex scripts such as Arabic, Hebrew, and Thai are displayed correctly. See the guidelines in section 4 below.

2. How to run the sample

   1. Compile the sample as described in section 5 below
   2. Execute the executable cstext.exe, by one of the standard methods.
   3. Type a few characters, switch the keyboard to another language, type more characters.
   4. Click on the Edit menu and choose "Use Edit Control".
   5. Type characters, swith keyboards, type again.
   6. In the edit control right click to see options for
-entering Unicode control characters,
- switching keyboard layout,
- etc.

3. System Requirements

This program will run on any version of Windows 95 or Windows NT 4.0 and beyond. However, to see the effect of displaying text in two different ways, it is best used on a platform that supports complex script, such as Arabic, Hebrew, or Thai Windows 95 or Windows NT.

4. What's the point?

This sample shows the proper use of Win32 messages, APIs, and controls to process keyboard input and display the text so that complex scripts are displayed correctly. Follow these guidelines to process complex scripts correctly.

4.1  Use Unicode as your character encoding if the target platform is Windows NT.

4.2  Use an edit control if possible, since the edit control will handle all processing of complex script for you, including input, display, cut and paste, input of Unicode control characters, etc.

4.3 Use ExtTextOut to display all of the text at once. Displaying text character by character as it is entered will result in improper display of context sensitive text. To see this, invoke this application, switch the keyboard to Thai or Arabic and type. Note that the top row is displayed incorrectly, one character at a time. The second row is correct.

4.4 Don't cache character widths; use GetTextExtentExPoint.
Applications that cache character widths implicitly assume that characters always have the same width. As a result, they may measure line lengths of complex scripts incorrectly, because the width of a character depends on the surrounding characters. The text extent functions, such as GetTextExtentPointEx, have been extended to work correctly with complex scripts on platforms that support those scripts.

5. How to build the sample

To build this sample, set up the SDK environment as for other samples. Then, simply copy all files in this directory to a clean subdirectory and type nmake at the command prompt.

If the sample is to be run on Windows 95 or Windows 98, be sure to comment out the DEFINES=
line in the makefile. That is, it should appears as follows:

#DEFINES=UNICODE

To enter Unicode control characters, compile as a Unicode application and run on Windows NT. To do this, make sure that the following line

DEFINES=-DUNICODE

is enabled in the makefile, i.e., it is not commented out by a "#" sign.


CSTEXT.H

//
//  CS Sample Header File
//

#ifdef _UNICODE
#ifndef UNICODE
#define UNICODE
#endif
#endif

#if 0

#ifndef _vstprintf

#ifdef UNICODE
#define _vstprintf vsprintf
#else
#define _vstprintf vswprintf
#endif// UNICODE

#endif// _vstprintf

#endif

#define MAX_MESSAGE 100
#define BUFFER_SIZE 256
#define XSTART      5
#define YSTART      40


CSTEXT.C

////////////////////////////////////////////////////////////////////////
//
// CSTEXT
// A sample application that shows how to display complex scripts such
// as Arabic, Hebrew, Thai, or Indic scripts.
//
// This sample works best if executed on a platform enabled for complex
// scripts, such as Arabic, Hebrew, or Thai Windows NT 4.0 or Windows 95,
// or Windows NT 5.0 with the appropriate locale installed.
//
// The WndProc function puts out text typed by the user to the client area
// in two ways: 1) using ExtTextOut to put out each character as it
// is typed by the user, and 2) saving each character typed in a
// buffer and putting out the whole buffer using ExtTextOut.
//
// If you run the program on a system that supports complex scripts, such as
// Arabic, Hebrew, or Thai Windows NT, and switch the keyboard to one of
// those languages, you will see different results in the two output lines.
// For example, on Arabic Windows NT, the line of characters displayed one
// by one will show only the stand-alone Arabic characters, without joining,
// and without the proper bidirectional layout. The line displayed using
// the whole buffer will be shown correctly
//
// Choose the help menu for guidelines on programming for complex scripts.
//
// Written by F. Avery Bishop, with advice from David C. W. Brown.
//
// Copyright (c) 1997, Microsoft Corporation. All rights reserved.
//
/////////////////////////////////////////////////////////////////////////

#include "cstext.h"
#include <stdarg.h>
#include <windows.h>
#include <tchar.h>
#include "resource.h"

#ifndef UNICODE
#pragma message("Warning: This sample works best as a Unicode application")
#endif

// Global variables
int         g_xStartOneChar = XSTART ;  // X position to display next character
HINSTANCE   g_hInstance ;
TCHAR       g_szTitle[MAX_MESSAGE] ;    // Title bar text

// Function Prototypes, in the order defined
LRESULT CALLBACK WndProc         (HWND , UINT , WPARAM , LPARAM) ;
BOOL CALLBACK    EditDialogProc  (HWND , UINT , WPARAM , LPARAM) ;
BOOL CALLBACK    HelpDialogProc  (HWND , UINT , WPARAM , LPARAM) ;
void             InitializeFont  (HWND , LONG, LPCHOOSEFONT , LPLOGFONT) ;
BOOL             InitApplication (HINSTANCE , LPTSTR) ;
BOOL             InitInstance    (HINSTANCE , LPTSTR , int) ;
int              RcMessageBox    (HWND , int , int , ...) ;

//
//   FUNCTION: WndProc (HWND, UINT, WPARAM, LPARAM)
//
//   PURPOSE: Window Procedure
//
//   COMMENTS:
//          This function puts out text typed by the user to the client area
//          in two ways: 1) using ExtTextOut to put out each character as it
//          is typed by the user, and 2) saving each character typed in a
//          buffer and putting out the whole buffer using ExtTextOut.
//
LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HFONT    hTextFont ;        // Font for text typed by user
    static TCHAR    szOutputBuffer[BUFFER_SIZE] ; // Buffer of characters typed
    static int      nChars = 0 ;       // Current size of buffer
    static RECT     rcBufferLine = {0,0,0,0} ;// Rectangle for the text to be displayed
    static UINT     uiAlign = TA_LEFT ;// Alignment and reading order flag
    static CHOOSEFONT cf ;             // This is static to keep last values as defaults
    static LOGFONT  lf ;               // Same for this.

    PAINTSTRUCT     ps ;               // Standard paint structure
    HDC             hDc ;              // HDC used in two ways. See WM_CHAR and WM_PAINT below
    SIZE            sOneChar ;         // Used in GetTextExtentPoint32.
    // Normally these should be in resources so they can be localized.
    TCHAR           szCharLabel[]
                    = TEXT("Characters displayed one by one, as typed:") ;
    TCHAR           szBuffLabel[]
                    = TEXT("All text in the line displayed in one call to ExtTextOut:") ;
    TEXTMETRIC      tm ;

    int nxStartBuffer, nyStartBuffer ;  // x and y positions to display text.
    int cCharWidths ;                   // Count of character widths, used in GetCharWidth32

    switch (message)
    {
    case WM_CREATE :

        InitializeFont (hWnd, 20, &cf, &lf) ; // Routine defined below
        hTextFont = CreateFontIndirect (&lf) ;

        if (NULL == hTextFont) {
            RcMessageBox (hWnd, IDS_CHOOSEFONT_FAILED, MB_ICONEXCLAMATION | MB_OK);
            break ;
        } ;

        return 0 ;

    case WM_CHAR :  // Process keyboard input

        switch ((TCHAR) wParam)
        {
        case VK_BACK :

            if (nChars > 0){
                nChars-- ;

                // Reset starting point for next character output
                hDc = GetDC(hWnd) ;
                SelectObject(hDc, hTextFont) ;
                GetTextExtentPoint32(hDc, szOutputBuffer, nChars, &sOneChar) ;
                ReleaseDC(hWnd, hDc) ;
                g_xStartOneChar = sOneChar.cx + XSTART;

                InvalidateRect (hWnd, NULL, TRUE) ;
            }
            break ;

        case VK_RETURN :

            // This sample ignores return. Most apps will add CR/LF or exit.
            break ;

        case VK_ESCAPE :
        case VK_END :
        case VK_HOME :

            // Processing other non-printable characters is left as an exercise
            break ;

        default:

            // Process all normal characters

        // First use the old (incorrect) method to display only the last
        // character typed

            // NOTE: This is an example of what *not* to do, because
            // characters that should join or otherwise interact
            // typographically will show as separate, stand alone characters.
            hDc = GetDC (hWnd) ;
            SelectObject (hDc, hTextFont) ;
            SetBkMode (hDc, TRANSPARENT) ;
            ExtTextOut (hDc, g_xStartOneChar, YSTART, 0,
                NULL, (LPCTSTR) &wParam, 1, NULL) ;

            // Get the next character position
            GetCharWidth (hDc, (UINT) wParam, (UINT) wParam, &cCharWidths) ;
            // This assumes left to right scripts, so it will break on
            // Arabic and Hebrew!
            g_xStartOneChar += cCharWidths ;

            ReleaseDC (hWnd, hDc) ;

        // Next, display the whole buffer of all characters typed in so far

            szOutputBuffer[nChars] = (TCHAR) wParam ;
            if (nChars < BUFFER_SIZE-1) {
                nChars++ ;
            }
            // This will generate a WM_PAINT message. In the processing
            // of WM_PAINT below, we display the text buffer in the
            // rectangle rcBufferLine. This is the recommended approach.
            InvalidateRect (hWnd, &rcBufferLine, TRUE) ;
        }

        return 0 ;

    case WM_PAINT :

        hDc = BeginPaint (hWnd, &ps) ;

        SelectObject (hDc, hTextFont) ;

        // Get line positions for the current font
        GetTextMetrics (hDc, &tm) ;

        nyStartBuffer = YSTART + 3*tm.tmHeight ;

        GetClientRect (hWnd, &rcBufferLine) ;

        rcBufferLine.top = nyStartBuffer ;
        rcBufferLine.bottom = nyStartBuffer + tm.tmHeight ;

        // Set the x position for right or left aligned text
        if (uiAlign & TA_RIGHT) {
            nxStartBuffer = rcBufferLine.right - XSTART ;
        } else {
            nxStartBuffer = XSTART ;
        }

        SetTextAlign (hDc, uiAlign) ;

        // Write the whole text buffer in the "character by character" rectangle
        // Note how the complex script comes out right this time.
        // This only happens when the whole client area is invalidated, i.e.,
        // when the user types backspace, clears the buffer, or changes the
        // font, or when the window is covered and uncovered.
        ExtTextOut (hDc, nxStartBuffer, YSTART, ETO_OPAQUE,
            NULL, szOutputBuffer, nChars, NULL) ;

        // Write the whole text buffer in the line buffer rectangle
        // This happens every time the user enters a character, which
        // is why this line always looks right, even for complex scripts.
        ExtTextOut (hDc, nxStartBuffer, nyStartBuffer, ETO_OPAQUE,
            &rcBufferLine, szOutputBuffer, nChars, NULL) ;

        // Write out labels describing the text
        SelectObject(hDc, GetStockObject(ANSI_VAR_FONT)) ;
        SetTextAlign(hDc, TA_LEFT) ;
        GetTextMetrics (hDc, &tm) ;

        ExtTextOut (hDc, XSTART, YSTART-tm.tmHeight, ETO_OPAQUE,
            NULL, szCharLabel, lstrlen(szCharLabel), NULL) ;

        ExtTextOut (hDc, XSTART, nyStartBuffer-tm.tmHeight, ETO_OPAQUE,
            NULL, szBuffLabel, lstrlen(szBuffLabel), NULL) ;

        EndPaint (hWnd, &ps) ;

        return 0 ;

    case WM_COMMAND :

        switch (LOWORD(wParam))
        {
        case IDM_EDIT_CLEAR :
            // Clear the character buffer
            nChars = 0 ;

            // Reset starting point for next character output
            g_xStartOneChar = XSTART ;
            InvalidateRect (hWnd, NULL, TRUE) ;

            break ;

        case IDM_EDITCONTROL :
            // Use an edit control to enter and display text.
            // This is the recommended approach, because all
            // complex script processing is handled by the system.
            // Complex script support is also available in the
            // Richedit control (not shown here).
            DialogBox (g_hInstance, MAKEINTRESOURCE(IDD_CSSAMPLE), NULL, EditDialogProc) ;

            break ;

        case IDM_EDIT_TOGGLEALIGN :
            // This aligns the text to the left or right
            // edge of the client area. It is NOT the same
            // as reading order.
            if (uiAlign & TA_RIGHT){
                uiAlign = uiAlign & ~TA_RIGHT ;
            } else {
                uiAlign = uiAlign | TA_RIGHT & ~TA_LEFT ;
            }

            InvalidateRect (hWnd, NULL, TRUE) ;

            break ;

        case IDM_EDIT_TOGGLEREADING :
            // This will set the reading order as LTR, as is used
            // for European and Asian scripts, or RTL, the most common
            // reading area in Middle Eastern scripts such as Arabic
            // and Hebrew.
            uiAlign ^= TA_RTLREADING ;
            InvalidateRect (hWnd, NULL, TRUE) ;

            break ;

        case IDM_EDIT_SETFONT :
            // Let the user change font.
            ChooseFont (&cf) ;
            if (hTextFont){
                DeleteObject (hTextFont) ;
            }

            hTextFont = CreateFontIndirect (&lf) ;

            if (NULL == hTextFont) {
                RcMessageBox (hWnd, IDS_CHOOSEFONT_FAILED, MB_ICONEXCLAMATION | MB_OK) ;
                break ;
            }

            RcMessageBox(hWnd, IDS_FONTCHANGED , MB_OK, lf.lfFaceName) ;

            // Reset starting point for next character output
            hDc = GetDC(hWnd) ;
            SelectObject(hDc, hTextFont) ;
            GetTextExtentPoint32(hDc, szOutputBuffer, nChars, &sOneChar) ;
            ReleaseDC(hWnd, hDc) ;
            g_xStartOneChar = sOneChar.cx + XSTART ;

            InvalidateRect (hWnd, NULL, TRUE) ;

            break ;

        case IDM_ABOUT_HELP :

            DialogBox (g_hInstance, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDialogProc) ;

            break;

        case IDM_ABOUT_ABOUT :

            DialogBox (g_hInstance, MAKEINTRESOURCE(IDD_ABOUT), NULL, HelpDialogProc);

            break ;

        case IDM_EXIT :

            DestroyWindow (hWnd) ;

            break ;

        default :

            return 0 ;
        }

        return 0 ;

        case WM_DESTROY :

            PostQuitMessage (0) ;

            return 0 ;

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

//
//   FUNCTION: BOOL CALLBACK EditDialogProc (HWND , UINT , WPARAM , LPARAM)
//
//   PURPOSE: Dialog procedure for the edit control dialog box.
//
//   COMMENTS:
//        This is standard processing for edit controls.
//
BOOL CALLBACK EditDialogProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HFONT        hEditFont ;
    static CHOOSEFONT   cf ;
    static LOGFONT      lf ;

    HWND                hWndEdit ;

    switch (uMsg)
    {
    case WM_INITDIALOG :

        InitializeFont (hDlg, 24, &cf, &lf) ;
        hEditFont = CreateFontIndirect (&lf) ;

        // Set font of edit control
        SendDlgItemMessage (hDlg, ID_EDITCONTROL, WM_SETFONT,
            (WPARAM) hEditFont,  MAKELPARAM(TRUE, 0)) ;

        return TRUE ;

    case WM_CLOSE :

        EndDialog (hDlg, wParam) ;

        return 0 ;

    case WM_COMMAND :

        switch (wParam)
        {
        case IDE_EDIT_FONT :

            if (hEditFont) {
                DeleteObject (hEditFont) ;
            }

            ChooseFont (&cf) ;
            hEditFont = CreateFontIndirect (&lf) ;

            SendDlgItemMessage (hDlg, ID_EDITCONTROL, WM_SETFONT,
                (WPARAM) hEditFont,  MAKELPARAM(TRUE, 0)) ;

            break ;

        case IDE_CLEAR :

            hWndEdit = GetDlgItem (hDlg, ID_EDITCONTROL) ;
            SetWindowText (hWndEdit, TEXT("")) ;

            break ;

        case IDE_CLOSE :

            DeleteObject (hEditFont) ;
            EndDialog (hDlg, wParam) ;
        }
    }

    return FALSE ;
}

//
//   FUNCTION: BOOL CALLBACK HelpDialogProc (HWND , UINT , WPARAM , LPARAM)
//
//   PURPOSE: Dialog procedure for the Help dialog box.
//
//   COMMENTS:
//        This does nothing but close the dialog box.
//
BOOL CALLBACK HelpDialogProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE :

        EndDialog (hDlg, wParam) ;
        return 0 ;

    case WM_COMMAND :

        switch (wParam)
        {
        case IDOK :

            EndDialog (hDlg, wParam) ;
        }
    }

    return FALSE ;
}

//
//   FUNCTION: _tWinMain (HINSTANCE , HINSTANCE , LPTSTR , int)
//
//   PURPOSE: Standard WinMain function
//
//   COMMENTS:
//        The prototype is _tWinMain, so it can be compiled either as
//        a Unicode or an ANSI application.
//
int WINAPI _tWinMain (HINSTANCE hInstance, HINSTANCE hPrev,
                      LPSTR pszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("Complex Script Display") ;
    MSG msg ;
    HANDLE hAccelTable ;

    // Perform application initialization:
    if (!InitApplication (hInstance, szAppName)) {
        return FALSE ;
    }
    // Perform instance initialization:
    if (!InitInstance (hInstance, szAppName, nCmdShow)) {
        return FALSE ;
    }

    hAccelTable = LoadAccelerators (hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1)) ;

    if (!hAccelTable) {
        return 0 ;
    }

    // Main message loop:
    while (GetMessage (&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage (&msg) ;
            DispatchMessage (&msg) ;
        }
    }
    return 0 ;
}

//
//   FUNCTION: InitializeFont (HWND , LONG , LPCHOOSEFONT , LPLOGFONT)
//
//   PURPOSE:  Fills in font structures with initial values.
//
//   REMARKS:  Since it contains only assignment statements, this function does no
//             error checking, has no return value..
//
void InitializeFont (HWND hWnd, LONG lHeight, LPCHOOSEFONT lpCf, LPLOGFONT lpLf)
{
    lpCf->lStructSize   = sizeof (CHOOSEFONT) ;
    lpCf->hwndOwner     = hWnd ;
    lpCf->hDC           = NULL ;
    lpCf->lpLogFont     = lpLf;
    lpCf->iPointSize    = 10;
    lpCf->Flags         = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT
        | CF_NOSIZESEL ;
    lpCf->rgbColors     = RGB (0,0,0);
    lpCf->lCustData     = 0;
    lpCf->lpfnHook      = NULL;
    lpCf->lpTemplateName= NULL;
    lpCf->hInstance     = g_hInstance;
    lpCf->lpszStyle     = NULL;
    lpCf->nFontType     = SIMULATED_FONTTYPE;
    lpCf->nSizeMin      = 0;
    lpCf->nSizeMax      = 0;

    lpLf->lfHeight      = lHeight ;
    lpLf->lfWidth       = 0 ;
    lpLf->lfEscapement  = 0 ;
    lpLf->lfOrientation = 0 ;
    lpLf->lfWeight      = FW_NORMAL ;
    lpLf->lfItalic      = FALSE ;
    lpLf->lfUnderline   = FALSE ;
    lpLf->lfStrikeOut   = FALSE ;
    lpLf->lfCharSet     = DEFAULT_CHARSET ;
    lpLf->lfOutPrecision= OUT_DEFAULT_PRECIS ;
    lpLf->lfClipPrecision = CLIP_DEFAULT_PRECIS ;
    lpLf->lfQuality     = DEFAULT_QUALITY ;
    lpLf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE ;
    lstrcpy(lpLf->lfFaceName, TEXT("Arial")) ;
    lpLf->lfFaceName[LF_FACESIZE] = 0 ;
}

//
//   FUNCTION: InitApplication(HINSTANCE, LPTSTR)
//
//   PURPOSE:  Fills in window class structure with parameters that describe
//   the main window, and registers the window.
//
BOOL InitApplication (HINSTANCE hInstance, LPTSTR szAppName)
{

    WNDCLASS  wc ;

    wc.style         = CS_HREDRAW | CS_VREDRAW ;
    wc.lpfnWndProc   = (WNDPROC) WndProc ;
    wc.cbClsExtra    = 0 ;
    wc.cbWndExtra    = 0 ;
    wc.hInstance     = hInstance ;
    wc.hIcon         = LoadIcon  (NULL, IDI_APPLICATION) ;
    wc.hCursor       = LoadCursor  (NULL, IDC_ARROW) ;
    wc.hbrBackground =  (HBRUSH) (COLOR_WINDOW+1) ;
    wc.lpszMenuName  = MAKEINTRESOURCE (IDR_MENU1) ;
    wc.lpszClassName = szAppName ;

    return  (RegisterClass(&wc)) ;
}

//
//   FUNCTION: InitInstance (HANDLE, LPTSTR, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        This function saved the instance handle in a global variable and
//        created and displays the main program window.
//

BOOL InitInstance (HINSTANCE hInstance, LPTSTR szAppName, int nCmdShow)
{
    HWND hWnd ;

    g_hInstance = hInstance ; // Store instance handle in global variable

    LoadString (hInstance, IDS_TITLE, g_szTitle, MAX_MESSAGE) ;

    hWnd = CreateWindow (szAppName, g_szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
        NULL, NULL, hInstance, NULL) ;

    if (!hWnd) {
        return FALSE ;
    }

    ShowWindow (hWnd, nCmdShow) ;
    UpdateWindow (hWnd) ;

    return TRUE ;
}
//
//  Function RcMessageBox (HWND, INT, INT, ...)
//
//  Purpose: Display a message box with formated output similar to sprintf
//
//  Remarks:
//      This function loads the string identified by nMessageID from the
//      resource segment, uses vsprintf to format it using the variable
//      parameters, and displays it to the user in a message box using the
//      icon specified by nIcons.
//

int RcMessageBox (HWND hWnd, int nMessageID, int nIcons, ...)
{
    // This can be changed to get the current lang id.
    WORD wCurrentLang = MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US) ;
    TCHAR szLoadBuff[MAX_MESSAGE], szOutPutBuff[3*MAX_MESSAGE] ;
    va_list valArgs ;

    va_start (valArgs, nIcons) ;

    LoadString (g_hInstance, nMessageID, szLoadBuff, MAX_MESSAGE) ;

    wvsprintf (szOutPutBuff, szLoadBuff, valArgs) ;

    va_end (valArgs) ;

    return (MessageBoxEx (hWnd, szOutPutBuff, g_szTitle, nIcons, wCurrentLang)) ;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by CSText.rc
//
#define IDS_CAPTION                     1
#define IDS_TITLE                       2
#define IDD_CSSAMPLE                    101
#define IDR_MENU1                       102
#define IDR_ACCELERATOR1                103
#define IDD_HELP                        104
#define IDD_ABOUT                       105
#define ID_CLEAR                        1001
#define IDE_CLEAR                       1001
#define ID_EXIT                         1002
#define ID_CLOSE                        1002
#define IDE_CLOSE                       1002
#define ID_EDITCONTROL                  1003
#define IDC_LOGICALTEXT                 1004
#define IDC_EDIT_FONT                   1005
#define IDE_EDIT_FONT                   1005
#define IDM_EXIT                        40002
#define ID_EDIT_USEEDITCONTROL          40003
#define IDM_EDITCONTROL                 40004
#define IDS_APPNAME                     40004
#define IDM_EDIT_CLEAR                  40005
#define IDS_CHOOSEFONT_FAILED           40005
#define IDS_TEXTOUT_FAILED              40006
#define IDS_FONTCHANGED                 40007
#define IDM_EDIT_SETFONT                40008
#define IDM_EDIT_TOGGLEALIGN            40012
#define IDM_EDIT_TOGGLEREADING          40013
#define IDM_ABOUT_HELP                  40014
#define IDM_ABOUT_ABOUT                 40015
#define IDC_TYPE                        -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        106
#define _APS_NEXT_COMMAND_VALUE         40016
#define _APS_NEXT_CONTROL_VALUE         1008
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

Font Usage

SUMMARY
=======

The FONTVIEW sample demonstrates several programming aspects. As a
stand-alone application, it allows you to experiment with the various
parameters of the CreateFont function, as well as examine the fonts and
metrics returned by the call.

MORE INFORMATION
================

This sample also demonstrates the following:

  - Using CreateFont in your application.
  - Creating a code base that can be compiled for 16-bit or 32-bit Windows.
  - Creating an About box with a custom bitmap.
  - Implementing the Help menu to interact with the Windows help engine.
  - Incorporating a Status Bar in your application.
  - Incorporating a Button Bar in your application.


DISPLAY.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#include <windows.h>
#include <string.h>
#include <math.h>

#if defined (WIN32)
#define _MoveTo(hdc,x,y) MoveToEx(hdc,x,y,NULL)
#else
#define _MoveTo(hdc,x,y) MoveTo(hdc,x,y)
#endif

// These are the 'exported' functions from this file:
void DrawAscii (HDC hdc, RECT *pRect, WORD direction);
void DrawGlyph (HDC hdc, RECT *pRect, BYTE glyph, HFONT hfont);
BYTE FindChar (HDC hdc, RECT *pRect, int x, int y);

#pragma warning(4 : 4309)
// A data structure that DrawAscii will use:
char ascii[]=
    {
    // Low Ascii:

    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,

    // Standard Ascii:

    ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',',
    '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F',
    'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '{', '|', '}', '~', ' ',

    // High Ascii:

    '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
    '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
    '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
    '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
    '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
    '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
    '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
    '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',

    '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
    '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
    '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
    '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
    '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
    '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
    '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
    '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',

    // End of the list:

    000, 000};
#pragma warning(3 : 4309)

/* A function that the CharSet window will use to display the text */
void DrawAscii (HDC hdc, RECT *pRect, WORD direction)
{
    BYTE *pch;
    int h, w, incx, incy, width;
    unsigned int  wDisplay;
    POINT pt;
    TEXTMETRIC tm;
    BOOL bLineMode = FALSE;

    GetTextMetrics (hdc, &tm);
    h = tm.tmHeight;
    w = tm.tmMaxCharWidth;

    incx = 0;
    incy = h;
    pt.x = 0;
    pt.y = 0;

    //
    pt.y = tm.tmAscent + tm.tmExternalLeading;
    //

    pch = &(ascii[tm.tmFirstChar]);
    wDisplay = pRect->right -pRect->left;

    //
    SetTextAlign (hdc, TA_BASELINE | TA_CENTER);
    SetBkMode (hdc, TRANSPARENT);
    //

    while (pch[0] && (pch[0]<=tm.tmLastChar)) {
#if defined (WIN32)
        SIZE size;
        GetTextExtentPoint (hdc, pch, 1, &size);
        width = size.cx;
#else
        width = LOWORD (GetTextExtent (hdc, pch, 1));
#endif
        if ((unsigned)(pt.x + width) > wDisplay) {
            pt.x = 0;
            pt.y += incy;
        }
        TextOut (hdc, pt.x + (width/2), pt.y, pch, 1);
        pt.x += width;
        pch++;
    }
    return;

    direction;
}

// Return the character that was drawn at the provided location
// This allows us to 'click' on a character, and determined what
// it was we actually clicked on.
BYTE FindChar (HDC hdc, RECT *pRect, int x, int y)
{
    BYTE *pch;
    int h, w, incx, incy, width;
    unsigned int wDisplay;
    POINT pt;
    TEXTMETRIC tm;
    BOOL bLineMode = FALSE;

    GetTextMetrics (hdc, &tm);
    h = tm.tmHeight;
    w = tm.tmMaxCharWidth;

    incx = 0;
    incy = h;
    pt.x = 0;
    pt.y = 0;

    pch = &(ascii[tm.tmFirstChar]);
    wDisplay = pRect->right -pRect->left;

    while (pch[0] && (pch[0]<=tm.tmLastChar)) {
#if defined (WIN32)
        SIZE size;
        GetTextExtentPoint (hdc, pch, 1, &size);
        width = size.cx;
#else
        width = LOWORD (GetTextExtent (hdc, pch, 1));
#endif
        if ((unsigned)(pt.x + width) > wDisplay) {
            pt.x = 0;
            pt.y += incy;
        }
        if ((x>=pt.x) && (x <=(pt.x+width)) && (y>=pt.y) && (y<=pt.y+incy)) return pch[0];
        pt.x += width;
        pch++;
    }
    return 0;
}

// Essentially, a StretchBlt, but draws cicular pels.
BOOL CircleBlt (HDC hdcDest, int xDst, int yDst, int iWidthDst, int iHeightDst, HDC hdcSrc, int xSrc, int ySrc, int iWidthSrc, int iHeightSrc, DWORD dwROP)
{
    int x, y, x1, y1, x2, y2;
    DWORD rgb;
    float fXUnit, fYUnit;
    HBRUSH hbrush, hbrushOld;

    if (iWidthSrc != 0){
        fXUnit = (float)iWidthDst / (float)iWidthSrc;
    } else {
        fXUnit = (float)iWidthDst;
    }
    if (iHeightSrc != 0) {
        fYUnit = (float)iHeightDst / (float)iHeightSrc;
    } else {
        fYUnit = (float)iHeightDst;
    }

    for (y=ySrc; y<(ySrc+iHeightSrc); y++) {
        for (x=xSrc; x<(xSrc+iWidthSrc); x++) {

            rgb = GetPixel (hdcSrc, x, y);
            if (rgb != -1 && rgb != 0x00FFFFFF) {
                hbrush = CreateSolidBrush (rgb);
                hbrushOld = SelectObject (hdcDest, hbrush);
                x1 = xDst + (int)((x-xSrc)*fXUnit);
                y1 = yDst + (int)((y-ySrc)*fYUnit);
                x2 = xDst + (int)(((x-xSrc)+1)*fXUnit)+1;
                y2 = yDst + (int)(((y-ySrc)+1)*fYUnit)+1;
                Ellipse (hdcDest, x1, y1, x2, y2);
                DeleteObject(SelectObject(hdcDest, hbrushOld));
            }
        }
    }

    return TRUE;

    dwROP;
}

double Radian(double ang)
{
        return ang * (double)3.1415926535 / (double)180.0;
}

void CalcRot (double x, double y, double ang, double* x1, double* y1)
{
        ang = Radian(-ang);
        *x1 = (x * cos(ang)) - (y * sin(ang));
        *y1 = (x * sin(ang)) + (y * cos(ang));
}

void DrawGlyph (HDC hdc, RECT *pRect, BYTE glyph, HFONT hfont)
{
    TEXTMETRIC tm;
    LOGFONT lf;
    int wChar, h1, h2, w1, w2, x, y;
    double xTL, yTL, xTR, yTR, xBL, yBL, xBR, yBR, xML, yML, xMR, yMR;
    int cWidth, cExtentW, cExtentH;
    int unit, margin;
    RECT rect, rectChar;
    HDC hdcMem;
    HBITMAP hbitmap, hbitmapOld;
    HFONT hfontOld, hfontTmp;
    char szText[80];
    double fAng, fWa, fWb, fHa, fHb, fHeight, fWidth;
    BOOL bRotateable;
    double fxOff, fyOff;
#if defined(TT_AVAILABLE)
    ABC abc;
#endif

    GetTextMetrics (hdc, &tm);
    hfontTmp = SelectObject (hdc, GetStockObject(SYSTEM_FONT));
    GetObject (hfontTmp, sizeof(LOGFONT), &lf);

// Some fields of 'lf' need to be whacked out depending on the type of
// font involved. For instance, lfEscapement should only be non-zero for
// rotatable fonts.
        bRotateable = FALSE;
#if defined (TMPF_VECTOR)
        bRotateable = (tm.tmPitchAndFamily & TMPF_VECTOR);
#endif
#if defined (TMPF_TRUETYPE)
        bRotateable |= (tm.tmPitchAndFamily & TMPF_TRUETYPE);
#endif
        if (!bRotateable) {
                lf.lfEscapement = 0;
        }

    SelectObject (hdc, hfontTmp);
    GetCharWidth (hdc, glyph, glyph, &cWidth);

#if defined (WIN32)
    {
        SIZE size;
        GetTextExtentPoint (hdc, (LPSTR)&glyph, 1, &size);
        cExtentW = size.cx;
        cExtentH = size.cy;
    }
#else
    cExtentW = LOWORD (GetTextExtent (hdc, (LPSTR)&glyph, 1));
    cExtentH = HIWORD (GetTextExtent (hdc, (LPSTR)&glyph, 1));
#endif

    wChar = max(cWidth, cExtentW);

#if defined(TT_AVAILABLE)
    if (GetCharABCWidths (hdc, (UINT)glyph, (UINT)glyph, &abc)) {
        wChar = abc.abcA + abc.abcB + abc.abcC;
        wChar = max(abc.abcA,0) + max (abc.abcB,0) + max (abc.abcC,0);
    } else {
        abc.abcA = 0;
        abc.abcB = wChar;
        abc.abcC = 0;
    }
#endif

// ****
        // Lets figure out how large of a bitmap we need to draw this character into.
        // This will take lfEscapement into account.

        // Turn the 'escapement' value into a valid angle
    fAng = 90.0 - ((double)lf.lfEscapement/10.0);

    // We now need to calculate two values for height and width. These
    // represent the bases of the triangles that are formed as the
    // character cell rotates. Thus fWa+fWb is the 'width' of the space
    // required by the given character at the current rotation.
    // At 0, 90, 180, 270 and 360, one of the two values will be '0'.
    fWa = (double)cExtentW * sin(Radian(fAng));
    fHa = (double)cExtentW * sin(Radian((double)lf.lfEscapement/10.0));
    fHb = (double)cExtentH * sin(Radian(fAng));
    fWb = (double)cExtentH * sin(Radian((double)lf.lfEscapement/10.0));

    // This gives us a width/height of:
    fHeight = fabs(fHa) + fabs(fHb);
    fWidth = fabs(fWa) + fabs(fWb);
// ****

    h1 = pRect->bottom - pRect->top;
    h2 = tm.tmHeight + tm.tmExternalLeading;
    h2 = (int)fHeight;
    if (h2 == 0) {
        MessageBeep (0);
        h2 = 10;
    }

    w1 = pRect->right - pRect->left;
    w2 = (int)fWidth;

    unit = (h1 / h2);

    margin = (h1 - (unit * h2)) / 2;
    margin = 0;
    // Define a rectangle that will enclose our drawing area
    SetRect (&rect, 0, 0, wChar*unit, (tm.tmHeight*unit) + (tm.tmExternalLeading*unit));
    OffsetRect (&rect, margin, margin);
    // Define a rectangle that will define the bounding box of the character
    SetRect (&rectChar, 0, 0, wChar*unit, tm.tmHeight*unit);
    OffsetRect (&rectChar, margin, margin + (tm.tmExternalLeading*unit));

// ****
    // These are floating point values.
    SetRect (&rect, 0, 0, (int)(fWidth)*unit, ((int)(fHeight)*unit) + (tm.tmExternalLeading*unit));
    OffsetRect (&rect, margin, margin);
    // Define a rectangle that will define the bounding box of the character
    SetRect (&rectChar, 0, 0, (int)(fWidth)*unit, ((int)(fHeight)*unit));
    OffsetRect (&rectChar, margin, margin + (tm.tmExternalLeading*unit));

    // Calculate the points of the 'character cell'. This will take full
    // rotation into consideration.
        CalcRot (0, 0, (double)lf.lfEscapement/10.0, &xTL, &yTL);
        CalcRot ((double)cExtentW, 0, (double)lf.lfEscapement/10.0, &xTR, &yTR);
        CalcRot (0, (double)cExtentH, (double)lf.lfEscapement/10.0, &xBL, &yBL);
        CalcRot ((double)cExtentW, (double)cExtentH, (double)lf.lfEscapement/10.0, &xBR, &yBR);
        CalcRot (0, (double)tm.tmAscent, (double)lf.lfEscapement/10.0, &xML, &yML);
        CalcRot ((double)cExtentW, (double)tm.tmAscent, (double)lf.lfEscapement/10.0, &xMR, &yMR);

        fxOff = min(xTL, min(xTR, min(xBL, xBR)));
        fyOff = min(yTL, min(yTR, min(yBL, yBR)));

// Draw a rectangle around the drawing limits of the character.
    _MoveTo (hdc, (int)((xTL-fxOff)*(double)unit), (int)((yTL-fyOff)*(double)unit));
    LineTo (hdc, (int)((xTR-fxOff)*(double)unit), (int)((yTR-fyOff)*(double)unit));
    LineTo (hdc, (int)((xBR-fxOff)*(double)unit), (int)((yBR-fyOff)*(double)unit));
    LineTo (hdc, (int)((xBL-fxOff)*(double)unit), (int)((yBL-fyOff)*(double)unit));
    LineTo (hdc, (int)((xTL-fxOff)*(double)unit), (int)((yTL-fyOff)*(double)unit));

// Draw the baseline, which indicates the advance width of the character
    _MoveTo (hdc, (int)((xML-fxOff)*(double)unit), (int)((yML-fyOff)*(double)unit));
    LineTo (hdc, (int)((xMR-fxOff)*(double)unit), (int)((yMR-fyOff)*(double)unit));

    hdcMem = CreateCompatibleDC (hdc);
    if (hdcMem) {
        hbitmap = CreateCompatibleBitmap (hdc, (int)(fWidth), (int)(fHeight));
        if (hbitmap) {
            hbitmapOld = SelectObject (hdcMem, hbitmap);
            BitBlt (hdcMem, 0, 0, (int)(fWidth), (int)(fHeight), NULL, 0, 0, WHITENESS);
            hfontOld = SelectObject (hdcMem, hfont);

            SetTextAlign (hdcMem, TA_LEFT | TA_BASELINE);
            if (TextOut (hdcMem, (int)(xML-fxOff), (int)(yML-fyOff), (LPSTR)&glyph, 1)) {

                                /*
                // Just for debugging, lets mark some special pixels
                        SetPixel (hdcMem, (int)(xML-fxOff), (int)(yML-fyOff), 0x808080); // Base Lft
                        SetPixel (hdcMem, (int)(xTL-fxOff), (int)(yTL-fyOff), 0x808080); // Top Left
                        SetPixel (hdcMem, (int)(xTR-fxOff), (int)(yTR-fyOff), 0x808080); // Top Right
                        SetPixel (hdcMem, (int)(xBL-fxOff), (int)(yBL-fyOff), 0x808080); // Btm Left
                        SetPixel (hdcMem, (int)(xBR-fxOff), (int)(yBR-fyOff), 0x808080); // Btm Right
                        SetPixel (hdcMem, (int)(xML-fxOff), (int)(yML-fyOff), 0x808080); // Base Lft
                        SetPixel (hdcMem, (int)(xMR-fxOff), (int)(yMR-fyOff), 0x808080); // Base Rt
                */

                CircleBlt(hdc, margin, margin,
                    rectChar.right-rectChar.left, rectChar.bottom-rectChar.top,
                    hdcMem, 0, 0, (int)(fWidth), (int)(fHeight), SRCPAINT);

            } else {
                MessageBox (GetFocus(),
                        "Unable to perform TextOut", "DisplayGlyph", MB_OK);
            }
            SelectObject (hdcMem, hbitmapOld);
            SelectObject (hdcMem, hfontOld);
            DeleteObject (hbitmap);
        } else {
            MessageBox (GetFocus(), "Unable To create Bitmap", "DisplayGlyph", MB_OK);
        }
        DeleteDC (hdcMem);
    } else {
        MessageBox (GetFocus(), "Unable to create DC", "DisplayGlyph", MB_OK);
    }

    hfontTmp = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, "");

    if (hfontTmp) {
        hfontOld = SelectObject (hdc, hfontTmp);
        x = rect.right + 10;
        GetTextMetrics (hdc, &tm);

        y = tm.tmHeight + tm.tmExternalLeading;

        y += tm.tmHeight + tm.tmExternalLeading;
        wsprintf (szText, "Character = '%c' %u (0x%X)", (char)glyph, (int)glyph, (int)glyph);
        TextOut (hdc, x, y, szText, lstrlen(szText));

        y += tm.tmHeight + tm.tmExternalLeading;
        wsprintf (szText, "CharWidth = %i", cWidth);
        TextOut (hdc, x, y, szText, lstrlen(szText));

                // ABC Width - Eventually, I want to 'draw' this into the character
                // cell, but for now, this at least makes the information easily
                // available:
#if defined (TT_AVAILABLE)
        y += tm.tmHeight + tm.tmExternalLeading;
        wsprintf (szText, "A|B|C = %i | %u | %i", abc.abcA, abc.abcB, abc.abcC);
        TextOut (hdc, x, y, szText, lstrlen(szText));
#endif

        y += tm.tmHeight + tm.tmExternalLeading;
        wsprintf (szText, "CharExtentW = %i", cExtentW);
        TextOut (hdc, x, y, szText, lstrlen(szText));

        y += tm.tmHeight + tm.tmExternalLeading;
        wsprintf (szText, "CharExtentH = %i", cExtentH);
        TextOut (hdc, x, y, szText, lstrlen(szText));

                // These are for some values I was using to debug the character
                // cell rotation calculation:

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "(%i,%i) : (%i,%i) [%i]", (int)xTL, (int)yTL, (int)xTR, (int)yTR,(int)(((double)lf.lfEscapement/10.0)*10));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "(%i,%i) : (%i,%i)", (int)xML, (int)yML, (int)xMR, (int)yMR);
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "(%i,%i) : (%i,%i)", (int)xBL, (int)yBL, (int)xBR, (int)yBR);
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fAng = %i", (int)(fAng));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fHa = %i", (int)(fHa));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fHb = %i", (int)(fHb));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fWa = %i", (int)(fWa));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fWb = %i", (int)(fWb));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fWd = %i", (int)(fWd));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fHd = %i", (int)(fHd));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fHeight = %i", (int)(fHeight));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        //y += tm.tmHeight + tm.tmExternalLeading;
        //wsprintf (szText, "fWidth = %i", (int)(fWidth));
        //TextOut (hdc, x, y, szText, lstrlen(szText));

        SelectObject (hdc, hfontOld);
        DeleteObject (hfontTmp);
    }
}


FONTVIEW.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define NOMINMAX
#include <windows.h>

#include "FontView.H"

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if !defined (WIN32)
#define APIENTRY FAR PASCAL
typedef unsigned int UINT;
#define WNDPROC FARPROC
#define DLGPROC FARPROC
#endif

char szAppName[] = "FontView";
char szAppTitle[] = "FontView - A Font Examination Utility";

BOOL bZoomed = FALSE;

HWND    hwndMain, hwndClient;
HWND    hwndMenuField, hwndTimeField;
HWND    hwndLabel1, hwndCreateParam, hwndCreateValue, hwndZoom;

LOGFONT lf = {0,0,0,0,0,0,0,0,0,0,0,0,0,""};

HMENU   hSysMenuMain = (HMENU)NULL;
HMENU   hFileMenu    = (HMENU)NULL;
HMENU   hEditMenu    = (HMENU)NULL;
HMENU   hViewMenu    = (HMENU)NULL;
HMENU   hMetricsMenu = (HMENU)NULL;
HMENU   hHelpMenu    = (HMENU)NULL;
HMENU   hSysMenuAdv  = (HMENU)NULL;
HMENU   hMenu        = (HMENU)NULL;

LONG APIENTRY WndProc         (HWND, UINT, UINT, LONG);
LONG APIENTRY ClientProc      (HWND, UINT, UINT, LONG);
LONG APIENTRY ClientCharSetProc      (HWND, UINT, UINT, LONG);
LONG APIENTRY ClientGlyphProc      (HWND, UINT, UINT, LONG);

BOOL FillInParam (HWND, int);
BOOL FillInValue (HWND, int);
int FAR PASCAL EnumFontNames (LPLOGFONT, LPTEXTMETRIC, short, LPINT);

WNDPROC fpCharSetProc = 0;
WNDPROC fpGlyphProc = 0;

/* Dialogs.C Prototypes */
BOOL APIENTRY SimpleDlgProc   (HWND, UINT, UINT, LONG);
BOOL APIENTRY CreateDlgProc   (HWND, UINT, UINT, LONG);
BOOL APIENTRY MetricsDlgProc   (HWND, UINT, UINT, LONG);
BOOL APIENTRY EnumDlgProc   (HWND, UINT, UINT, LONG);

/* Display.C Prototypes */
void DrawAscii (HDC hdc, RECT *pRect, WORD direction);
BYTE FindChar (HDC hdc, RECT *pRect, int x, int y);
void DrawGlyph (HDC hdc, RECT *pRect, BYTE glyph, HFONT hfont);

/* ToolBar.C Prototypes */
BOOL InitToolBar (HANDLE);
BOOL CreateToolBar (HWND, HANDLE, int);
BOOL AdjustToolBar (HWND);
HWND AddToolLabel (HANDLE , int, LPSTR, int, DWORD);
HWND AddToolCombo (HANDLE, int, int, DWORD);
HWND AddToolButton (HANDLE, int, LPSTR, int, int, DWORD);
BOOL AddToolSpace (int, int);
int ToolBarHeight (HWND);

/* StatusBar.C Prototypes */
BOOL InitStatusBar (HANDLE);
BOOL CreateStatusBar (HWND, HANDLE, int);
BOOL AdjustStatusBar (HWND);
HWND AddStatusField (HANDLE, int, int, int, BOOL);
int StatusBarHeight (HWND);

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{

    MSG         msg;
    WNDCLASS    wndclass;
    HANDLE      hAccelTable;

    if (!hPrevInst) {
        wndclass.style         = CS_HREDRAW | CS_VREDRAW;
#if defined(WIN32)
        wndclass.lpfnWndProc   = (WNDPROC)WndProc;
#else
        wndclass.lpfnWndProc   = WndProc;
#endif
        wndclass.cbClsExtra    = 0;
        wndclass.cbWndExtra    = 0;
        wndclass.hInstance     = hInst;
        wndclass.hIcon         = LoadIcon (hInst, szAppName);
        wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
        wndclass.lpszMenuName  = szAppName;
        wndclass.lpszClassName = szAppName;

        if (!RegisterClass (&wndclass)) {
            return FALSE;
        }

        wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
#if defined(WIN32)
        wndclass.lpfnWndProc   = (WNDPROC)ClientProc;
#else
        wndclass.lpfnWndProc   = ClientProc;
#endif
        wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
        wndclass.lpszMenuName  = NULL;
        wndclass.lpszClassName = "FontClient";

        if (!RegisterClass (&wndclass)) {
            return FALSE;
        }

        fpCharSetProc = MakeProcInstance ((WNDPROC)ClientCharSetProc, hInst);
        fpGlyphProc   = MakeProcInstance ((WNDPROC)ClientGlyphProc, hInst);

        if (!InitToolBar (hInst)) {
            return FALSE;
        }

        if (!InitStatusBar (hInst)) {
            return FALSE;
        }
    }

    hwndMain = CreateWindow (szAppName, szAppName,
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT, 0,
        CW_USEDEFAULT, 0,
        NULL, NULL, hInst, NULL);

    if (!hwndMain) {
        return FALSE;
    }
    /* Get handles to the various menus. Some of these we will use later */
    /* to display menu descriptions in the status bar */
        hSysMenuMain = GetSystemMenu(hwndMain,FALSE);
    hMenu = GetMenu(hwndMain);
    if (hMenu) {
        hFileMenu = GetSubMenu(hMenu,0);
        hEditMenu = GetSubMenu(hMenu,1);
        hViewMenu = GetSubMenu(hMenu, 2);
        hMetricsMenu = GetSubMenu(hMenu,3);
        hHelpMenu = GetSubMenu(hMenu,4);
    }

    if (CreateToolBar (hwndMain, hInst, ID_TOOLBAR)) {
        hwndLabel1 = AddToolLabel (hInst, 0, "CreateFont:", 0, SS_RIGHT);
        AddToolSpace (6, 0);
        hwndCreateParam = AddToolCombo (hInst, ID_CREATEPARAM, -25, CBS_DROPDOWN | WS_VSCROLL);
        FillInParam (hwndCreateParam, 0);

        AddToolSpace (10, 0);
        hwndCreateValue = AddToolCombo (hInst, ID_CREATEVALUE, -30, CBS_DROPDOWN | WS_VSCROLL);
        FillInValue (hwndCreateValue, CFP_BASE+0);

        AddToolSpace (10, 0);
        hwndZoom = AddToolButton (hInst, ID_ZOOM, "ZOOM", 24, 0, BS_OWNERDRAW);

    } else {
        return FALSE;
    }

    if (CreateStatusBar (hwndMain, hInst, ID_STATUSBAR)) {
        hwndMenuField = AddStatusField (hInst, ID_MENUFIELD, 100, 0, FALSE);
        hwndTimeField = AddStatusField (hInst, ID_TIMEFIELD, -13, -13, TRUE);
        if (hwndTimeField) {
            SetTimer (hwndMain, 1, 1000, NULL);
        }
    } else {
        return FALSE;
    }

    hwndClient = CreateWindow ("FontClient", "FontClient",
        WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | WS_VISIBLE,
        0, 0, 0, 0,
        hwndMain,(HMENU)666, hInst, NULL);

    SetWindowLong (hwndClient, GWL_WNDPROC, (DWORD)fpCharSetProc);
    bZoomed = FALSE;

    if (!hwndMain) {
        return FALSE;
    }

    ShowWindow (hwndMain, nCmdShow);
    InvalidateRect (hwndZoom, NULL, TRUE);

    hAccelTable = LoadAccelerators (hInst, szAppName);

    while (GetMessage (&msg, NULL, 0, 0)) {
        if (!TranslateAccelerator (hwndMain, hAccelTable, &msg)) {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }

    return msg.wParam;

    lpCmdLine; // Just to resolve reference
}

/* ================================================================================================ *\
    Fill in the first combo box with all the different 'CreateFont' parameters that we can change
\* ================================================================================================ */
BOOL FillInParam (HWND hwnd, int focus)
{
    int index;

    index = 0;
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"nHeight");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_HEIGHT);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"nWidth");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_WIDTH);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"nEscapement");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_ESCAPEMENT);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"nOrientation");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_ORIENTATION);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"nWeight");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_WEIGHT);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cItalic");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_ITALIC);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cUnderline");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_UNDERLINE);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cStrikeOut");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_STRIKEOUT);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cCharSet");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_CHARSET);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cOutputPrecision");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_OUTPUTPRECISION);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cClipPrecision");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_CLIPPRECISION);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cQuality");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_QUALITY);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"cPitch...");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_PITCH);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"c...Family");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_FAMILY);
    SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"lpFacename");
    SendMessage (hwnd, CB_SETITEMDATA, index++, CFP_FACENAME);
    SendMessage (hwnd, CB_SETCURSEL, focus, 0);
    return TRUE;
}

/* ================================================================================================ *\
    Fill in the second combo box with valid values, based on the current setting in the first combo
\* ================================================================================================ */
BOOL FillInValue (HWND hwnd, int param)
{
    int i, index, focus;
    long data;
    char szTmp[80];
    ATOM atom;
    HDC  hdc;
    FARPROC lpEnumFonts;
    HINSTANCE hInst;

    SendMessage (hwnd, CB_RESETCONTENT, 0, 0);
    focus = -1;
    index = 0;

    // Each of the following cases will contain two different 'loops', the first one will fill the
    // combo box with values, the second will locate the current setting in the combobox and set
    // the focus to it. If the value is not currently in the combobox, then it will be inserted at
    // the proper location.

    switch (param) {
        case CFP_HEIGHT:
            // Lets just toss in a range of heights, from -72 to 72, by twos
            // If the user wants to enter in a custom value, they need to use
            // the 'CreateFont' dialog for now.
            for (i=-72; i<72; i+=2) {
                if (i < 0) {
                    wsprintf (szTmp, "%d = cell height = %u", i, abs(i));
                } else if (i == 0) {
                    wsprintf (szTmp, "%u = default", i);
                } else {
                    wsprintf (szTmp, "%u = character height", i);
                }
                SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)szTmp);
                SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)i);
            }

            for (i=0; i<index; i++) {
                data = (long)SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == lf.lfHeight) {
                    focus = i;
                    break;
                } else if (data > lf.lfHeight) {
                    wsprintf (szTmp, "%u", lf.lfHeight);
                    SendMessage (hwnd, CB_INSERTSTRING, i, (DWORD)(LPSTR)szTmp);
                    SendMessage (hwnd, CB_SETITEMDATA, i, (DWORD)lf.lfHeight);
                    focus = i;
                    break;
                }
            }
            if (focus == -1) {
                wsprintf (szTmp, "%u", lf.lfHeight);
                focus = (int)SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)szTmp);
                SendMessage (hwnd, CB_SETITEMDATA, focus, (DWORD)lf.lfHeight);
            }

            break;

        case CFP_WIDTH:
            // Lets just toss in a range of widths, from 0 to 72, by twos
            // If the user wants to enter in a custom value, they need to use
            // the 'CreateFont' dialog for now.
            for (i=0; i<72; i+=2) {
                if (i==0) {
                    wsprintf (szTmp, "%u = default aspect", i);
                } else {
                    wsprintf (szTmp, "%u", i);
                }
                SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)szTmp);
                SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)i);
            }
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == lf.lfWidth) {
                    focus = i;
                    break;
                } else if (data > lf.lfWidth) {
                    wsprintf (szTmp, "%u", lf.lfWidth);
                    SendMessage (hwnd, CB_INSERTSTRING, i, (DWORD)(LPSTR)szTmp);
                    SendMessage (hwnd, CB_SETITEMDATA, i, (DWORD)lf.lfWidth);
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_ESCAPEMENT:
            for (i=0; i<4500; i+=10) {
                wsprintf (szTmp, "%u = (%u/10 degrees)", i, i);
                SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)szTmp);
                SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)i);
            }
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == lf.lfEscapement) {
                    focus = i;
                    break;
                } else if (data > lf.lfEscapement) {
                    wsprintf (szTmp, "%u = (%u/10 degrees)", lf.lfWidth);
                    SendMessage (hwnd, CB_INSERTSTRING, i, (DWORD)(LPSTR)szTmp);
                    SendMessage (hwnd, CB_SETITEMDATA, i, (DWORD)lf.lfEscapement);
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_ORIENTATION:
            for (i=0; i<4500; i+=10) {
                wsprintf (szTmp, "%u = (%u/10 degrees)", i, i);
                SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)szTmp);
                SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)i);
            }
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == lf.lfOrientation) {
                    focus = i;
                    break;
                } else if (data > lf.lfOrientation) {
                    wsprintf (szTmp, "%u = (%u/10 degrees)", lf.lfWidth);
                    SendMessage (hwnd, CB_INSERTSTRING, i, (DWORD)(LPSTR)szTmp);
                    SendMessage (hwnd, CB_SETITEMDATA, i, (DWORD)lf.lfOrientation);
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_WEIGHT:
            index = 0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = FW_DONTCARE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 0);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"100 = FW_THIN");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 100);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"200 = FW_EXTRALIGHT");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 200);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"300 = FW_LIGHT");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 300);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"400 = FW_NORMAL");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 400);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"500 = FW_MEDIUM");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 500);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"600 = FW_SEMIBOLD");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 600);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"700 = FW_BOLD");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 700);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"800 = FW_EXTRABOLD");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 800);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"900 = FW_HEAVY");
            SendMessage (hwnd, CB_SETITEMDATA, index++, 900);

            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == lf.lfWeight) {
                    focus = i;
                    break;
                } else if (data > lf.lfWeight) {
                    wsprintf (szTmp, "%u", lf.lfWeight);
                    SendMessage (hwnd, CB_INSERTSTRING, i, (DWORD)(LPSTR)szTmp);
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_ITALIC:
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FALSE");
            SendMessage (hwnd, CB_SETITEMDATA, 0, (DWORD)FALSE);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"TRUE");
            SendMessage (hwnd, CB_SETITEMDATA, 1, (DWORD)TRUE);
            focus = (lf.lfItalic?1:0);
            break;

        case CFP_UNDERLINE:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FALSE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FALSE);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"TRUE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)TRUE);
            focus = (lf.lfUnderline?1:0);
            break;

        case CFP_STRIKEOUT:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FALSE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FALSE);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"TRUE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)TRUE);  \
            focus = (lf.lfStrikeOut?1:0);
            break;

        case CFP_CHARSET:
            index = 0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = ANSI_CHARSET");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)ANSI_CHARSET);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"SYMBOL_CHARSET");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)SYMBOL_CHARSET);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"SHIFTJIS_CHARSET");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)SHIFTJIS_CHARSET);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"OEM_CHARSET");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)OEM_CHARSET);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)lf.lfCharSet) {
                    focus = i;
                    break;
                }
            }
            break;

            break;

        case CFP_OUTPUTPRECISION:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = OUT_DEFAULT_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)OUT_DEFAULT_PRECIS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"OUT_STRING_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)OUT_STRING_PRECIS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"OUT_CHARACTER_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)OUT_CHARACTER_PRECIS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"OUT_STROKE_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)OUT_STROKE_PRECIS);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)lf.lfOutPrecision) {
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_CLIPPRECISION:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = CLIP_DEFAULT_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)CLIP_DEFAULT_PRECIS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"CLIP_CHARACTER_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)CLIP_CHARACTER_PRECIS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"CLIP_STROKE_PRECIS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)CLIP_STROKE_PRECIS);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)lf.lfClipPrecision) {
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_QUALITY:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = DEFAULT_QUALITY");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)DEFAULT_QUALITY);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"DRAFT_QUALITY");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)DRAFT_QUALITY);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"PROOF_QUALITY");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)PROOF_QUALITY);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)lf.lfQuality) {
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_PITCH:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = DEFAULT_PITCH");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)DEFAULT_PITCH);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FIXED_PITCH");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FIXED_PITCH);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"VARIABLE_PITCH");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)VARIABLE_PITCH);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)(0x03&lf.lfPitchAndFamily)) {
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_FAMILY:
            index=0;
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"0 = FF_DONTCARE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_DONTCARE);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FF_ROMAN");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_ROMAN);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FF_SWISS");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_SWISS);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FF_MODERN");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_MODERN);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FF_SCRIPT");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_SCRIPT);
            SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"FF_DECORATIVE");
            SendMessage (hwnd, CB_SETITEMDATA, index++, (DWORD)FF_DECORATIVE);
            for (i=0; i<index; i++) {
                data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                if (data == (long)(0xFC&lf.lfPitchAndFamily)) {
                    focus = i;
                    break;
                }
            }
            break;

        case CFP_FACENAME:
            index = (int)SendMessage (hwnd, CB_ADDSTRING, 0, (DWORD)(LPSTR)"(none)");
            SendMessage (hwnd, CB_SETITEMDATA, index, 0);
            if (lstrcmp(lf.lfFaceName, "(none)") == 0) {
                lf.lfFaceName[0] = 0;
            }
            hdc = GetDC (hwnd);

#if defined (WIN32)
            hInst = (HINSTANCE)GetWindowLong (hwnd, GWL_HINSTANCE);
#else
            hInst = (HINSTANCE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif
            lpEnumFonts = MakeProcInstance ((FARPROC)EnumFontNames, hInst);
            EnumFonts (hdc, NULL, (FONTENUMPROC)lpEnumFonts, (LPARAM)&hwnd);
            FreeProcInstance (lpEnumFonts);
            ReleaseDC (hwnd, hdc);
            atom = GlobalFindAtom ((LPSTR)lf.lfFaceName);
            if (atom == (ATOM)0 && (lstrlen (lf.lfFaceName)>0)) {
                index = (int)SendMessage (hwnd, CB_ADDSTRING, 0, (LONG)(LPSTR)lf.lfFaceName);
                focus = index;
            } else if (lstrlen(lf.lfFaceName)==0) {
                index = (int)SendMessage (hwnd, CB_FINDSTRING, (UINT)-1, (LONG)(LPSTR)"(none)");
                focus = index;
            } else {
                index = (int)SendMessage (hwnd, CB_GETCOUNT, 0, 0);
                for (i=0; i<index; i++) {
                    data = SendMessage (hwnd, CB_GETITEMDATA, i, 0);
                    if ((DWORD)data == (DWORD)atom) {
                        focus = i;
                        break;
                    }
                }
            }
            break;
    }
    if (focus >= 0) {
        SendMessage (hwnd, CB_SETCURSEL, focus, 0);
    } else {
        SendMessage (hwnd, CB_SETCURSEL, 0, 0);
    }
    return TRUE;
}

// Font enumeration callback. This adds the font name to the atom list.
int FAR PASCAL EnumFontNames (LPLOGFONT lpLogFont, LPTEXTMETRIC lpTextMetric, short nFontType, LPINT lpData)
{
    HWND hwnd;
    FARPROC lpFontEnumProc;
    HDC  hdc;
    ATOM atom;
    HWND hwndCombo;
    int  index;

    hwnd = GetFocus();
    hdc  = GetDC(hwnd);
    hwndCombo = (HWND)*lpData;

    atom = GlobalFindAtom ((LPSTR)lpLogFont->lfFaceName);
    if (atom == (ATOM)0) {
        atom = GlobalAddAtom (lpLogFont->lfFaceName);
    }
    if (index = SendMessage(hwndCombo, CB_FINDSTRING, (UINT)-1, (LONG)(LPSTR)lpLogFont->lfFaceName) == CB_ERR) {
        index = (int)SendMessage (hwndCombo, CB_ADDSTRING, 0, (LONG)(LPSTR)lpLogFont->lfFaceName);
        SendMessage (hwndCombo, CB_SETITEMDATA, index, atom);
    }

    ReleaseDC(hwnd, hdc);

    return TRUE;

    lpTextMetric;   // unreferenced formal paramater
    nFontType;      // unreferenced formal parameter
    lpFontEnumProc; // unreferenced formal parameter
}

// A quick little routine that will center one window over another, handy for dialog boxes.
BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
    RECT    rChild, rParent;
    int     wChild, hChild, wParent, hParent;
    int     wScreen, hScreen, xNew, yNew;
    HDC     hdc;

    GetWindowRect (hwndChild, &rChild);
    wChild = rChild.right - rChild.left;
    hChild = rChild.bottom - rChild.top;

    GetWindowRect (hwndParent, &rParent);
    wParent = rParent.right - rParent.left;
    hParent = rParent.bottom - rParent.top;

    hdc = GetDC (hwndChild);
    wScreen = GetDeviceCaps (hdc, HORZRES);
    hScreen = GetDeviceCaps (hdc, VERTRES);
    ReleaseDC (hwndChild, hdc);

    xNew = rParent.left + ((wParent - wChild) /2);
    if (xNew < 0) {
        xNew = 0;
    } else if ((xNew+wChild) > wScreen) {
        xNew = wScreen - wChild;
    }

    yNew = rParent.top  + ((hParent - hChild) /2);
    if (yNew < 0) {
        yNew = 0;
    } else if ((yNew+hChild) > hScreen) {
        yNew = hScreen - hChild;
    }

    return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

LONG APIENTRY WndProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    HINSTANCE hInst;
    HDC hdc;
    PAINTSTRUCT ps;
    FARPROC lpDlgFunc;
    char    szMsg [80];
    time_t  lTime;
    struct tm *datetime;

#define HOUR (datetime->tm_hour)
#define MIN (datetime->tm_min)
#define SEC (datetime->tm_sec)

    char    szTime [20];
    int     index;
    RECT rect;

static int  idMenuSelect;
    int     tmp, wmEvent, wmId;
    HWND    wmhWnd;
    POINT   point;
    BOOL bRedraw;
    int wmMenuCmd, wmFlags;
    HMENU wmhMenu;

        if (hwnd) {
#if defined (WIN32)
        hInst = (HINSTANCE)GetWindowLong (hwnd, GWL_HINSTANCE);
#else
        hInst = (HINSTANCE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif
        }

    switch (msg) {
    case WM_CREATE:
            return DefWindowProc (hwnd, msg, wParam, lParam);
            break;

        case WM_TIMER:
            time (&lTime);
            datetime = localtime (&lTime);
            wsprintf (szTime, "%02d:%02d:%02d %s",
            (HOUR%12?HOUR%12:12), MIN, SEC, (LPSTR)(HOUR/12?"PM":"AM"));
            SetWindowText (hwndTimeField, szTime);
            break;

        case WM_SIZE:
            AdjustToolBar (hwnd);
            AdjustStatusBar (hwnd);
            GetClientRect (hwnd, &rect);
            rect.top += ToolBarHeight (hwnd);
            rect.bottom -= StatusBarHeight (hwnd);
            SetWindowPos (hwndClient, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, SWP_NOZORDER);
            break;

        case WM_CHAR:
            switch (wParam) {

case VK_F10:
                    wParam = VK_MENU;
                    return DefWindowProc (hwnd, msg, wParam, lParam);
                    break;
            }
            break;

        case WM_PARENTNOTIFY:
#if defined (WIN32)
            wmEvent = wParam; // Same as Win16, but only on (x,y) msgs
#else
            wmEvent = wParam;
#endif
            point.x = (int)LOWORD(lParam);
            point.y = (int)HIWORD(lParam);

            ClientToScreen (hwnd, &point);
            wmhWnd  = WindowFromPoint(point);
            if (wmhWnd) {
#if defined (WIN32)
                wmId = GetWindowLong (wmhWnd, GWL_ID);
#else
                wmId = GetWindowWord (wmhWnd, GWW_ID);
#endif
                        } else {
                                break;
                        }
            switch (wmEvent) {
                case WM_LBUTTONDOWN:
                case WM_MBUTTONDOWN:
                case WM_RBUTTONDOWN:
                    if (!LoadString (hInst, (UINT)wmId, szMsg, sizeof(szMsg))) {
                        wsprintf ((LPSTR)szMsg, "Unable to load ParentNotify string #%u", wmId);
                    }
                    SetWindowText (hwndMenuField, szMsg);
                    break;
            }
            break;

        case WM_COMMAND:
#if defined (WIN32)
            wmId = LOWORD(wParam);
            wmEvent = HIWORD(wParam);
#else
            wmId = wParam;
            wmEvent = HIWORD(lParam);
#endif

            switch (wmId) {
                case ID_CREATEPARAM:

                    switch (wmEvent) {
                        case CBN_SETFOCUS: //Indicates that the combo box has received input focus.
                        case CBN_DROPDOWN: //Informs the owner of the combo box that its list box is about to be dropped down.
                            if (!LoadString (hInst, wmId, szMsg, sizeof(szMsg))) {
                                wsprintf ((LPSTR)szMsg, "Unable to load FontName string #%u", wmId);
                            }
                            SetWindowText (hwndMenuField, szMsg);
                            break;

                        case CBN_SELCHANGE: //Sent when the selection has been changed.
                            index = (int)SendMessage (hwndCreateParam, CB_GETCURSEL, 0, 0);
                            if (index != CB_ERR) {
                                SendMessage (hwndCreateParam, CB_GETLBTEXT, index, (LONG)(LPSTR)szMsg);
                                FillInValue (hwndCreateValue, CFP_BASE+index);
                            } else {
                                wsprintf (szMsg, "FontName: CB_ERR while retrieving text");
                            }
                            SetWindowText (hwndMenuField, szMsg);
                            break;
                    }
                    break;

                case ID_CREATEVALUE:
                    switch (wmEvent){
                        int iCreateParam;
                        int iParamValue;

                        case CBN_SETFOCUS: //Indicates that the combo box has received input focus.
                        case CBN_DROPDOWN: //Informs the owner of the combo box that its list box is about to be dropped down.
                            if (!LoadString (hInst, wmId, szMsg, sizeof(szMsg))) {
                                wsprintf (szMsg, "Unable to load FontSize string #%u", wmId);
                            }
                            break;

                        case CBN_SELCHANGE: //Sent when the selection has been changed.
                            index = (int)SendMessage (hwndCreateValue, CB_GETCURSEL, 0, 0);
                            bRedraw = FALSE;
                            if (index != CB_ERR) {
                                SendMessage (hwndCreateValue, CB_GETLBTEXT, index, (LONG)(LPSTR)szMsg);
                                iCreateParam =
                                    (int)SendMessage (hwndCreateParam, CB_GETITEMDATA,
                                        (WORD)SendMessage (hwndCreateParam, CB_GETCURSEL, 0, (DWORD)0),
                                    (DWORD)0);
                                iParamValue = (int)SendMessage (hwndCreateValue, CB_GETITEMDATA, index, 0);

                                switch (iCreateParam) {
                                    BYTE tmpByte;

                                    case CFP_HEIGHT:
                                        if (lf.lfHeight != iParamValue) {
                                            lf.lfHeight = iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_WIDTH:
                                        if (lf.lfWidth != iParamValue) {
                                            lf.lfWidth = iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_ESCAPEMENT:
                                        if (lf.lfEscapement != iParamValue) {
                                            lf.lfEscapement= iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_ORIENTATION:
                                        if (lf.lfOrientation!= iParamValue) {
                                            lf.lfOrientation= iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_WEIGHT:
                                        if (lf.lfWeight != iParamValue) {
                                            lf.lfWeight= iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_ITALIC:
                                        if ((int)lf.lfItalic != iParamValue) {
                                            lf.lfItalic = (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_UNDERLINE:
                                        if (lf.lfUnderline != (BYTE)iParamValue) {
                                            lf.lfUnderline= (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_STRIKEOUT:
                                        if (lf.lfStrikeOut != (BYTE)iParamValue) {
                                            lf.lfStrikeOut= (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_CHARSET:
                                        if (lf.lfCharSet != (BYTE)iParamValue) {
                                            lf.lfCharSet= (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_OUTPUTPRECISION:
                                        if (lf.lfOutPrecision != (BYTE)iParamValue) {
                                            lf.lfOutPrecision = (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_CLIPPRECISION:
                                        if (lf.lfClipPrecision != (BYTE)iParamValue) {
                                            lf.lfClipPrecision = (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_QUALITY:
                                        if (lf.lfQuality != (BYTE)iParamValue) {
                                            lf.lfQuality = (BYTE)iParamValue;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_PITCH:
                                        tmpByte = lf.lfPitchAndFamily;
                                        tmpByte &= 0xFC;
                                        tmpByte |= iParamValue;
                                        if (tmpByte != lf.lfPitchAndFamily) {
                                            lf.lfPitchAndFamily = tmpByte;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_FAMILY:
                                        tmpByte = lf.lfPitchAndFamily;
                                        tmpByte &= 0x03;
                                        tmpByte |= iParamValue;
                                        if (tmpByte != lf.lfPitchAndFamily) {
                                            lf.lfPitchAndFamily = tmpByte;
                                            bRedraw = TRUE;
                                        }
                                        break;

                                    case CFP_FACENAME:
                                        if (lstrcmp (lf.lfFaceName, szMsg)!=0) {
                                            if (lstrcmp(lf.lfFaceName, "(none)") == 0) {
                                                lf.lfFaceName[0] = 0;
                                            } else {
                                                wsprintf (lf.lfFaceName, "%s",(LPSTR)szMsg);
                                                bRedraw = TRUE;
                                            }
                                        }
                                        break;
                                }
                            } else {
                                wsprintf (szMsg, "CreateValue: CB_ERR while retrieving text");
                            }
                            if (bRedraw) {
                                InvalidateRect (hwndClient, NULL, TRUE);
                            }
                            SetWindowText (hwndMenuField, szMsg);
                            break;
                    }
                    break;

                // The Menu Selections
                case IDM_NEW:
                case IDM_OPEN:
                case IDM_CLOSE:
                case IDM_SAVE:
                case IDM_SAVEAS:
                case IDM_PRINT:
                case IDM_PRINTSET:
                    SetWindowText (hwndMenuField, "This command is not yet implemented.");
                    break;

                case IDM_EXIT:
                    DestroyWindow(hwnd);
                    break;

                case IDM_UNDO:
                case IDM_CUT:
                case IDM_COPY:
                case IDM_PASTE:
                case IDM_DELETE:
                case IDM_FIND:
                case IDM_REPLACE:
                    SetWindowText (hwndMenuField, "This command is not yet implemented.");
                    break;

                                case ID_ZOOM:
                                        if (bZoomed) {
                        SetWindowLong (hwndClient, GWL_WNDPROC, (LONG)fpCharSetProc);
                        bZoomed = FALSE;
                    } else {
                        SetWindowLong (hwndClient, GWL_WNDPROC, (LONG)fpGlyphProc);
                        bZoomed = TRUE;
                    }
                    InvalidateRect (hwndClient, NULL, TRUE);
                    break;

                case IDM_CHARSET: // Display the full characater set
                    SetWindowLong (hwndClient, GWL_WNDPROC, (LONG)fpCharSetProc);
                    InvalidateRect (hwndClient, NULL, TRUE);
                    bZoomed = FALSE;
                    break;

                case IDM_GLYPH: // Display a single glyph from the font
                    SetWindowLong (hwndClient, GWL_WNDPROC, (LONG)fpGlyphProc);
                    InvalidateRect (hwndClient, NULL, TRUE);
                    bZoomed = TRUE;
                    break;

                    break;

                case IDM_TOOLBAR: // This would hide the tool bar
                    SetWindowText (hwndMenuField, "This command is not yet implemented.");
                    break;

                case IDM_STATUSBAR: // This would hide the status bar
                    SetWindowText (hwndMenuField, "This command is not yet implemented.");
                    break;

                case IDM_CREATEFONT:
                    lpDlgFunc = MakeProcInstance((FARPROC) CreateDlgProc, hInst);
                    DialogBoxParam(hInst, "CreateFont", hwnd, (DLGPROC)lpDlgFunc, (DWORD)(LPLOGFONT)&lf);
                    FreeProcInstance(lpDlgFunc);
                    index = (int)SendMessage (hwndCreateParam, CB_GETCURSEL, 0, 0);
                    if (index != CB_ERR) {
                        FillInValue (hwndCreateValue, CFP_BASE+index);
                    } else {
                        wsprintf (szMsg, "CreateFont: CB_ERR while setting values");
                    }
                    InvalidateRect (hwndClient, NULL, TRUE);

                    break;

                case IDM_TEXTMETRIC:
                    lpDlgFunc = MakeProcInstance((FARPROC) MetricsDlgProc, hInst);
                    DialogBoxParam(hInst, "TextMetric", hwnd, (DLGPROC)lpDlgFunc, (DWORD)(LPLOGFONT)&lf);
                    FreeProcInstance(lpDlgFunc);
                    index = (int)SendMessage (hwndCreateParam, CB_GETCURSEL, 0, 0);
                    if (index != CB_ERR) {
                        FillInValue (hwndCreateValue, CFP_BASE+index);
                    } else {
                        wsprintf (szMsg, "CreateFont: CB_ERR while setting values");
                    }
                    InvalidateRect (hwndClient, NULL, TRUE);
                    break;

                case IDM_ENUMFONTS:
                    lpDlgFunc = MakeProcInstance((FARPROC) EnumDlgProc, hInst);
                    DialogBoxParam(hInst, "EnumFonts", hwnd, (DLGPROC)lpDlgFunc, (DWORD)(LPLOGFONT)&lf);
                    FreeProcInstance(lpDlgFunc);
                    index = (int)SendMessage (hwndCreateParam, CB_GETCURSEL, 0, 0);
                    if (index != CB_ERR) {
                        FillInValue (hwndCreateValue, CFP_BASE+index);
                    } else {
                        wsprintf (szMsg, "CreateFont: CB_ERR while setting values");
                    }
                    InvalidateRect (hwndClient, NULL, TRUE);
                    break;

                case IDM_OUTLINEMETRICS:
                    SetWindowText (hwndMenuField, "This command is not yet implemented.");
                    break;

                case IDM_HELP_CONTENTS:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"CONTENTS");
                    break;
                case IDM_HELP_INDEX:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"INDEX");
                    break;
                case IDM_HELP_OVERVIEW:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"OVERVIEW");
                    break;
                case IDM_HELP_GLOSSARY:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"GLOSSARY");
                    break;
                case IDM_HELP_TUTORIAL:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"TUTORIAL");
                    break;
                case IDM_HELP_DEMO:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"DEMO");
                    break;
                case IDM_HELP_HELP:
                    WinHelp (hwnd, "FontView.HLP", HELP_KEY, (DWORD)(LPSTR)"HELP");
                    break;

                case IDM_ABOUT:
                    lpDlgFunc = MakeProcInstance((FARPROC) SimpleDlgProc, hInst);
                    DialogBox(hInst,
                        (wmId==IDM_ABOUT)?"FONTVIEW":"UsageBox",
                        hwnd,
                        (DLGPROC)lpDlgFunc);
                    FreeProcInstance(lpDlgFunc);
                    break;

            }
            break;

    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps);
        EndPaint (hwnd, &ps);
        break;

    case WM_MENUSELECT:
        /* A menu item is hilited, get description text for status bar */
        tmp = idMenuSelect;

#if defined (WIN32)
        wmMenuCmd = LOWORD(wParam);
        wmFlags = HIWORD(wParam);
        wmhMenu = (HMENU)lParam;
#else
        wmMenuCmd =  wParam;
        wmFlags = LOWORD(lParam);
        wmhMenu = (HMENU)HIWORD(lParam);
#endif

        if((wmhMenu == 0) && (wmFlags = -1)) {
                tmp = idMenuSelect = 0;
                SetWindowText(hwndMenuField, "Ready...");
        } else if(wmhMenu == 0) {
            /* don't do anything for this */
        } else if(wmFlags & MF_POPUP) {
            if((HMENU)wmMenuCmd == hSysMenuMain)
                    idMenuSelect = IDM_SYSMENU;
            else if((HMENU)wmMenuCmd == hFileMenu)
                    idMenuSelect = IDM_FILE;
            else if((HMENU)wmMenuCmd == hEditMenu)
                    idMenuSelect = IDM_EDIT;
            else if((HMENU)wmMenuCmd == hViewMenu)
                    idMenuSelect = IDM_VIEW;
            else if((HMENU)wmMenuCmd == hMetricsMenu)
                    idMenuSelect = IDM_DIALOG;
            else if((HMENU)wmMenuCmd == hHelpMenu)
                    idMenuSelect = IDM_HELP;
        } else {
            if(wmMenuCmd != 0) {            /* separators have wparam of 0 */
                idMenuSelect = wmMenuCmd;
            }
        }

        if (idMenuSelect != tmp) {
            if (!LoadString ( hInst, (int)idMenuSelect, szMsg, sizeof(szMsg))) {
                wsprintf ((LPSTR)szMsg, "Unable to load menu string #%u", idMenuSelect);
            }
            SetWindowText (hwndMenuField, szMsg);
        }
        break;

        case WM_QUERYENDSESSION:
            return (MessageBox (GetFocus(), "Exit Application?", szAppTitle,
                MB_ICONQUESTION | MB_YESNO) == IDYES);

        case WM_DESTROY:
            if (hwnd == hwndMain) {
                PostQuitMessage (0);
            }
            break;

        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}

LONG APIENTRY ClientProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    switch (msg) {
        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}

LONG APIENTRY ClientCharSetProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    HINSTANCE hInst;
    PAINTSTRUCT ps;
    HFONT hfont, hfontPrev;
    RECT rect;
    HDC hdc;
    int x, y;
    BYTE glyph;

        if (hwnd) {
#if defined (WIN32)
        hInst = (HINSTANCE)GetWindowLong (hwnd, GWL_HINSTANCE);
#else
        hInst = (HINSTANCE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif
        }
    switch (msg) {
        case WM_PAINT:
            hdc = BeginPaint (hwnd, &ps);
            hfont = CreateFontIndirect (&lf);
            hfontPrev = SelectObject (hdc, hfont);
            GetClientRect (hwnd, &rect);
            DrawAscii (hdc, &rect, 0);
            SelectObject (hdc, hfontPrev);
            DeleteObject (hfont);
            EndPaint (hwnd, &ps);
            break;

        case WM_LBUTTONDBLCLK:
            x = LOWORD(lParam);
            y = HIWORD(lParam);
            //OutputDebugString ("WM_LBUTTONDOWN\n\r");
            hdc = GetWindowDC (hwnd);
            hfont = CreateFontIndirect (&lf);
            hfontPrev = SelectObject (hdc, hfont);
            GetClientRect (hwnd, &rect);

            glyph = FindChar (hdc, &rect, x, y);
            if (glyph) {
                SetWindowLong (hwndClient, GWL_WNDPROC, (LONG)fpGlyphProc);
                bZoomed = TRUE;
                SendMessage (hwndClient, WM_USER, (UINT)glyph, 0);
                InvalidateRect (hwndClient, NULL, TRUE);
            }

            SelectObject (hdc, hfontPrev);
            DeleteObject (hfont);
            ReleaseDC (hwnd, hdc);
            break;

        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}

LONG APIENTRY ClientGlyphProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    HINSTANCE hInst;
    PAINTSTRUCT ps;
    HFONT hfont, hfontPrev;
    RECT rect;
    HDC hdc;
    TEXTMETRIC tm;
    static BYTE glyph = 'A';

        if (hwnd) {
#if defined (WIN32)
        hInst = (HINSTANCE)GetWindowLong (hwnd, GWL_HINSTANCE);
#else
        hInst = (HINSTANCE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif
        }

    switch (msg) {
        case WM_LBUTTONDBLCLK:
        case WM_LBUTTONDOWN:
            glyph--;
            InvalidateRect (hwnd, NULL, TRUE);
            break;

        case WM_RBUTTONDBLCLK:
        case WM_RBUTTONDOWN:
            glyph++;
            InvalidateRect (hwnd, NULL, TRUE);
            break;

        case WM_USER:
            glyph = (BYTE)wParam;
            break;

        case WM_PAINT:
            hdc = BeginPaint (hwnd, &ps);
            hfont = CreateFontIndirect (&lf);
            hfontPrev = SelectObject (hdc, hfont);
            GetClientRect (hwnd, &rect);

            GetTextMetrics (hdc, &tm);
            if (glyph < tm.tmFirstChar) {
                MessageBeep (0);
                glyph = tm.tmFirstChar;
            }
            if (glyph > tm.tmLastChar) {
                MessageBeep(0);
                glyph = tm.tmLastChar;
            }

            DrawGlyph (hdc, &rect, glyph, hfont);
            SelectObject (hdc, hfontPrev);
            DeleteObject (hfont);
            EndPaint (hwnd, &ps);
            break;

        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}


FONTVIEW.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define IDM_SYSMENU  666

#define IDM_FILE        100
#define IDM_NEW             IDM_FILE+1
#define IDM_OPEN            IDM_FILE+2
#define IDM_CLOSE           IDM_FILE+3
#define IDM_SAVE            IDM_FILE+4
#define IDM_SAVEAS          IDM_FILE+5
#define IDM_PRINT           IDM_FILE+6
#define IDM_PRINTSET        IDM_FILE+7
#define IDM_EXIT            IDM_FILE+8

#define IDM_EDIT        110
#define IDM_UNDO            IDM_EDIT+1
#define IDM_CUT             IDM_EDIT+2
#define IDM_COPY            IDM_EDIT+3
#define IDM_PASTE           IDM_EDIT+4
#define IDM_DELETE          IDM_EDIT+5
#define IDM_FIND            IDM_EDIT+6
#define IDM_REPLACE         IDM_EDIT+7

#define IDM_VIEW        120
#define IDM_CHARSET         IDM_VIEW+1
#define IDM_GLYPH           IDM_VIEW+2
#define IDM_TOOLBAR         IDM_VIEW+3
#define IDM_STATUSBAR       IDM_VIEW+4

#define IDM_DIALOG        130
#define IDM_CREATEFONT      IDM_DIALOG+1
#define IDM_TEXTMETRIC      IDM_DIALOG+2
#define IDM_ENUMFONTS       IDM_DIALOG+3
#define IDM_OUTLINEMETRICS  IDM_DIALOG+4

#define IDM_HELP        140
#define IDM_HELP_CONTENTS   IDM_HELP+1
#define IDM_HELP_INDEX      IDM_HELP+2
#define IDM_HELP_OVERVIEW   IDM_HELP+3
#define IDM_HELP_GLOSSARY   IDM_HELP+4
#define IDM_HELP_TUTORIAL   IDM_HELP+5
#define IDM_HELP_DEMO       IDM_HELP+6
#define IDM_HELP_HELP       IDM_HELP+7
#define IDM_ABOUT           IDM_HELP+8

#define ID_TOOLBAR      150
#define ID_CREATEPARAM      ID_TOOLBAR+1
#define ID_CREATEVALUE      ID_TOOLBAR+2
#define ID_ZOOM                         ID_TOOLBAR+3

#define ID_STATUSBAR    160
#define ID_MENUFIELD        ID_STATUSBAR+1
#define ID_TIMEFIELD        ID_STATUSBAR+2
#define ID_NUMFIELD         ID_STATUSBAR+3
#define ID_OVRFIELD         ID_STATUSBAR+4
#define ID_SCROLLFIELD      ID_STATUSBAR+5
#define ID_CAPSFIELD        ID_STATUSBAR+6

#define CFP_BASE        200
#define CFP_HEIGHT          CFP_BASE+0
#define CFP_WIDTH           CFP_BASE+1
#define CFP_ESCAPEMENT      CFP_BASE+2
#define CFP_ORIENTATION     CFP_BASE+3
#define CFP_WEIGHT          CFP_BASE+4
#define CFP_ITALIC          CFP_BASE+5
#define CFP_UNDERLINE       CFP_BASE+6
#define CFP_STRIKEOUT       CFP_BASE+7
#define CFP_CHARSET         CFP_BASE+8
#define CFP_OUTPUTPRECISION CFP_BASE+9
#define CFP_CLIPPRECISION   CFP_BASE+10
#define CFP_QUALITY         CFP_BASE+11
#define CFP_PITCH           CFP_BASE+12
#define CFP_FAMILY          CFP_BASE+13
#define CFP_FACENAME        CFP_BASE+14

#define CFD_BASE        220
#define CFD_HEIGHT          CFD_BASE+0
#define CFD_WIDTH           CFD_BASE+1
#define CFD_ESCAPEMENT      CFD_BASE+2
#define CFD_ORIENTATION     CFD_BASE+3
#define CFD_WEIGHT          CFD_BASE+4
#define CFD_ITALIC          CFD_BASE+5
#define CFD_UNDERLINE       CFD_BASE+6
#define CFD_STRIKEOUT       CFD_BASE+7
#define CFD_CHARSET         CFD_BASE+8
#define CFD_OUTPUTPRECISION CFD_BASE+9
#define CFD_CLIPPRECISION   CFD_BASE+10
#define CFD_QUALITY         CFD_BASE+11
#define CFD_PITCHANDFAMILY  CFD_BASE+12
#define CFD_FACENAME        CFD_BASE+13

#define CFD_DEFAULT         CFD_BASE+14

#define TMD_BASE        240
#define TMD_HEIGHT          TMD_BASE+0
#define TMD_ASCENT          TMD_BASE+1
#define TMD_DESCENT         TMD_BASE+2
#define TMD_INTERNAL        TMD_BASE+3
#define TMD_EXTERNAL        TMD_BASE+4
#define TMD_AVEWIDTH        TMD_BASE+5
#define TMD_MAXWIDTH        TMD_BASE+6
#define TMD_WEIGHT          TMD_BASE+7
#define TMD_ITALIC          TMD_BASE+8
#define TMD_UNDERLINE       TMD_BASE+9
#define TMD_STRUCKOUT       TMD_BASE+10
#define TMD_FIRSTCHAR       TMD_BASE+11
#define TMD_LASTCHAR        TMD_BASE+12
#define TMD_DEFAULTCHAR     TMD_BASE+13
#define TMD_BREAKCHAR       TMD_BASE+14
#define TMD_PITCHANDFAMILY  TMD_BASE+15
#define TMD_CHARSET         TMD_BASE+16
#define TMD_OVERHANG        TMD_BASE+17
#define TMD_DIGITIZEDASPECTX TMD_BASE+18
#define TMD_DIGITIZEDASPECTY TMD_BASE+19

#define TMD_SET             TMD_BASE+20

#define ED_BASE         260
#define ED_PREV             ED_BASE+0
#define ED_NEXT             ED_BASE+1
#define ED_TYPE             ED_BASE+2
#define ED_SET              ED_BASE+3
#define ED_HEX              ED_BASE+4
#define ED_SAMPLE           ED_BASE+5


DIALOGS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define NOMINMAX
#include <windows.h>
#include "FontView.h"

#include <stdlib.h>

#if !defined (APIENTRY)
#define APIENTRY FAR PASCAL
#endif

BOOL CenterWindow (HWND, HWND);

typedef struct FONTENUM {
    short       ft;
    TEXTMETRIC  tm;
    LOGFONT     lf;
} FONTSTRUCT;
typedef FONTSTRUCT      *PFONTSTRUCT;
typedef FONTSTRUCT NEAR *NPFONTSTRUCT;
typedef FONTSTRUCT FAR  *LPFONTSTRUCT;

typedef struct FONTLIST {
    int     count;
    HANDLE  hList;
} FONTLIST;
typedef FONTLIST        *PFONTLIST;
typedef FONTLIST NEAR   *NPFONTLIST;
typedef FONTLIST FAR    *LPFONTLIST;

void SetDlgItemValue (HWND hDlg, int nIDDlgItem, int wValue, BOOL bSigned, int nBase)
{
    char szValue[20];

    switch (nBase) {
        case 8:
            wsprintf (szValue, "0o%o", wValue);
            SetDlgItemText (hDlg, nIDDlgItem, szValue);
            break;
        case 16:
            wsprintf (szValue, "0x%x", wValue);
            SetDlgItemText (hDlg, nIDDlgItem, szValue);
            break;
        default:
            SetDlgItemInt (hDlg, nIDDlgItem, wValue, bSigned);
            break;
    }
}

int FAR PASCAL DlgEnumFontSizes (lpLogFont, lpTextMetric, nFontType, lpData)
    LPLOGFONT lpLogFont;
    LPTEXTMETRIC lpTextMetric;
    short nFontType;
    LPHANDLE lpData;
{
    HANDLE hFonts;
    LPFONTSTRUCT pFS;
    LPFONTLIST pFL;

    /*
        This function will lock down the incoming handle, properly alloc, and realloc the
        handle within it to hold the data of the fonts enumerated, then unlock the handle.
    */
    hFonts = *lpData;
    pFL = (LPFONTLIST)GlobalLock (hFonts);
    if (!pFL) {
        return FALSE;
    } else if (pFL->count == 0) {
        pFL->hList = GlobalAlloc (GHND, sizeof(FONTSTRUCT));
    } else {
        pFL->hList = GlobalReAlloc (pFL->hList, sizeof(FONTSTRUCT)*(1+pFL->count), GMEM_MOVEABLE);
    }

    if (pFL->hList) {
        pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
        if (pFS) {
            pFS[pFL->count].ft = nFontType;
            pFS[pFL->count].tm = *lpTextMetric;
            pFS[pFL->count].lf = *lpLogFont;
            GlobalUnlock (pFL->hList);
            pFL->count++;
        }
        GlobalUnlock (hFonts);

    } else {
        GlobalUnlock (hFonts);
        return FALSE;
    }

    return TRUE;
}

int FAR PASCAL DlgEnumFontNames (lpLogFont, lpTextMetric, nFontType, lpData)
    LPLOGFONT lpLogFont;
    LPTEXTMETRIC lpTextMetric;
    short nFontType;
    LPHANDLE lpData;
{
    HDC  hdc;
    HWND hwnd;
    HANDLE hInst;
    FARPROC lpFontEnumProc;

/*
   This function is just a pass through. For each face encountered, it will in turn enumerate all
   sizes available. the lpData, which is a FAR * to a FONTSTRUCT structure will simply be passed
   on to the second enumeration procedure which will fill it in.
 */
    hwnd = GetFocus();
#if defined (WIN32)
    hInst = (HANDLE)GetWindowLong (hwnd, GWL_HINSTANCE);
#elif defined (WIN16)
    hInst = (HANDLE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif

    lpFontEnumProc = MakeProcInstance((FARPROC)DlgEnumFontSizes, hInst);
    if (lpFontEnumProc) {
        hdc  = GetDC(hwnd);
        EnumFonts (hdc, lpLogFont->lfFaceName, (FONTENUMPROC)lpFontEnumProc, (LPARAM)lpData);
        ReleaseDC(hwnd, hdc);
        FreeProcInstance (lpFontEnumProc);
    } else {
        MessageBox (GetFocus(), "Couldn't create a proc instance", "FontView", MB_OK);
        return FALSE;
    }
    return TRUE;

    lpTextMetric;  // unreferenced formal parameter
    nFontType;     // unreferenced formal parameter
}

BOOL APIENTRY SimpleDlgProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    int wmId;
    static HBITMAP hbmFontView;
    static BITMAP bmFontView;
    RECT rect;
    HDC hdc, hdcSrc;
    HBITMAP hbmOld;
    PAINTSTRUCT ps;
    HANDLE hInst;

#if defined (WIN32)
    hInst = (HANDLE)GetWindowLong (hwnd, GWL_HINSTANCE);
#elif defined (WIN16)
    hInst = (HANDLE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif

    switch (msg) {
        case WM_INITDIALOG:
            CenterWindow (hwnd, GetWindow (hwnd, GW_OWNER));
            hbmFontView = LoadBitmap (hInst, "FONTVIEW");
            GetObject (hbmFontView,sizeof(BITMAP), &bmFontView);
            if (!hbmFontView) MessageBeep(0);
            return (TRUE);

        case WM_DESTROY:
            DeleteObject (hbmFontView);
            break;

        case WM_PAINT:
            hdc = BeginPaint (hwnd, &ps);
            GetWindowRect (hwnd, &rect);
            ScreenToClient (hwnd, (LPPOINT)&rect.left);
            ScreenToClient (hwnd, (LPPOINT)&rect.right);
            hdc = GetDC (hwnd);
            hdcSrc = CreateCompatibleDC (hdc);
            hbmOld = SelectObject (hdcSrc, hbmFontView);
            if (!BitBlt (hdc, 0, 0, bmFontView.bmWidth, bmFontView.bmHeight, hdcSrc, 0, 0, SRCCOPY)) {
                MessageBeep(0);
            }
            SelectObject (hdcSrc, hbmOld);
            DeleteDC (hdcSrc);
            EndPaint (hwnd, &ps);
            break;

        case WM_COMMAND:
#if defined (WIN32)
            wmId = LOWORD(wParam);
#elif defined (WIN16)
            wmId = wParam;
#endif
            switch (wmId) {

                case IDOK:
                    EndDialog(hwnd, TRUE);
                    return (TRUE);

                case IDCANCEL:
                    EndDialog(hwnd, TRUE);
                    return (TRUE);
            }
            break;
    }
    return (FALSE);

    lParam; // unreferenced formal parameter
}

/*
    This dialog will present edit controls for all the parameters of a CreateFont call.
    The user can put any value in any of the fields, no validation is done. These parameters
    are then used to create a font with, and that will be the font that will be displayed.
 */
BOOL APIENTRY CreateDlgProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    int wmId, i;
    BOOL bDone;
static LOGFONT lfDlg;
static LPLOGFONT lplf;

    switch (msg) {
        case WM_INITDIALOG:
            CenterWindow (hwnd, GetWindow (hwnd, GW_OWNER));
            lplf = (LOGFONT *)lParam;
            lfDlg = *lplf;
            SetDlgItemInt (hwnd, CFD_HEIGHT, (int)lfDlg.lfHeight, TRUE);
            SetDlgItemInt (hwnd, CFD_WIDTH, (int)lfDlg.lfWidth, TRUE);
            SetDlgItemInt (hwnd, CFD_ESCAPEMENT, (int)lfDlg.lfEscapement, TRUE);
            SetDlgItemInt (hwnd, CFD_ORIENTATION, (int)lfDlg.lfOrientation, TRUE);
            SetDlgItemInt (hwnd, CFD_WEIGHT, (int)lfDlg.lfWeight, FALSE);
            SetDlgItemInt (hwnd, CFD_ITALIC, (int)lfDlg.lfItalic, FALSE);
            SetDlgItemInt (hwnd, CFD_UNDERLINE, (int)lfDlg.lfUnderline, FALSE);
            SetDlgItemInt (hwnd, CFD_STRIKEOUT, (int)lfDlg.lfStrikeOut, FALSE);
            SetDlgItemInt (hwnd, CFD_CHARSET, (int)lfDlg.lfCharSet, FALSE);
            SetDlgItemInt (hwnd, CFD_OUTPUTPRECISION, (int)lfDlg.lfOutPrecision, FALSE);
            SetDlgItemInt (hwnd, CFD_CLIPPRECISION, (int)lfDlg.lfClipPrecision, FALSE);
            SetDlgItemInt (hwnd, CFD_QUALITY, (int)lfDlg.lfQuality, FALSE);
            SetDlgItemInt (hwnd, CFD_PITCHANDFAMILY, (int)lfDlg.lfPitchAndFamily, FALSE);
            SetDlgItemText(hwnd, CFD_FACENAME, lfDlg.lfFaceName);

            return (TRUE);

        case WM_COMMAND:
#if defined (WIN32)
            wmId = LOWORD(wParam);
#elif defined (WIN16)
            wmId = wParam;
#endif
            switch (wmId) {
                case CFD_DEFAULT:
                    // Set all elements to ZERO. This will give us a 'default' font
                    for (i=CFD_BASE; i<=CFD_PITCHANDFAMILY; i++) {
                        SetDlgItemInt (hwnd, i, 0, FALSE);
                    }
                    SetDlgItemText (hwnd, CFD_FACENAME, "");
                    break;

                case IDOK:
                    // Get the data from the edit control, we will then use this for a 'CreatFont' call
                    lfDlg.lfHeight = GetDlgItemInt (hwnd, CFD_HEIGHT, &bDone, TRUE);
                    lfDlg.lfWidth = GetDlgItemInt (hwnd, CFD_WIDTH, &bDone, TRUE);
                    lfDlg.lfEscapement = GetDlgItemInt (hwnd, CFD_ESCAPEMENT, &bDone, TRUE);
                    lfDlg.lfOrientation = GetDlgItemInt (hwnd, CFD_ORIENTATION, &bDone, TRUE);
                    lfDlg.lfWeight = GetDlgItemInt (hwnd, CFD_WEIGHT, &bDone, FALSE);
                    lfDlg.lfItalic = (BYTE)GetDlgItemInt (hwnd, CFD_ITALIC, &bDone, FALSE);
                    lfDlg.lfUnderline = (BYTE)GetDlgItemInt (hwnd, CFD_UNDERLINE, &bDone, FALSE);
                    lfDlg.lfStrikeOut = (BYTE)GetDlgItemInt (hwnd, CFD_STRIKEOUT, &bDone, FALSE);
                    lfDlg.lfCharSet = (BYTE)GetDlgItemInt (hwnd, CFD_CHARSET, &bDone, FALSE);
                    lfDlg.lfOutPrecision = (BYTE)GetDlgItemInt (hwnd, CFD_OUTPUTPRECISION, &bDone, FALSE);
                    lfDlg.lfClipPrecision = (BYTE)GetDlgItemInt (hwnd, CFD_CLIPPRECISION, &bDone, FALSE);
                    lfDlg.lfQuality = (BYTE)GetDlgItemInt (hwnd, CFD_QUALITY, &bDone, FALSE);
                    lfDlg.lfPitchAndFamily = (BYTE)GetDlgItemInt (hwnd, CFD_PITCHANDFAMILY, &bDone, FALSE);
                    GetDlgItemText(hwnd, CFD_FACENAME, lfDlg.lfFaceName,20);
                    // and copy the data into our external structure
                    *lplf = lfDlg;
                    EndDialog(hwnd, TRUE);
                    return (TRUE);

                case IDCANCEL:
                    // Exit without changing anything
                    EndDialog(hwnd, TRUE);
                    return (TRUE);

            }
            break;
    }
    return (FALSE);

    lParam; //unreferenced formal paramter
}

/*
 This dialog will display the TEXTMETRIC data that is retrieved from a 'GetTextMetric' call.
 If the 'Select' button is clicked, then the current CreateFont data will be replaced with
 as much data from TEXTMETRIC as possible.
*/
BOOL APIENTRY MetricsDlgProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
static LOGFONT lfDlg;
static LPLOGFONT lplf;
static TEXTMETRIC tm;
static char szFacename[LF_FACESIZE];
    int wmId;
    BOOL bDone;
    TEXTMETRIC tmTmp;
    HFONT hfont, hfontPrev;
    HDC   hdc;
    char szBuffer[LF_FACESIZE+15];

    switch (msg) {
        case WM_INITDIALOG:
            CenterWindow (hwnd, GetWindow (hwnd, GW_OWNER));
            lplf = (LOGFONT *)lParam;
            lfDlg = *lplf;

            hfont = CreateFontIndirect (&lfDlg);
            hdc = GetDC (hwnd);
            hfontPrev = SelectObject (hdc, hfont);
            GetTextMetrics (hdc, &tm);
            GetTextFace    (hdc, sizeof(szFacename), szFacename);
            SelectObject (hdc, hfontPrev);
            DeleteObject (hfont);
            ReleaseDC (hwnd, hdc);

            wsprintf (szBuffer, "TextMetrics: %s", (LPSTR)szFacename);
            SetWindowText (hwnd, szBuffer);

            SetDlgItemInt (hwnd, TMD_HEIGHT, (int)tm.tmHeight, TRUE);
            SetDlgItemInt (hwnd, TMD_ASCENT, (int)tm.tmAscent, TRUE);
            SetDlgItemInt (hwnd, TMD_DESCENT, (int)tm.tmDescent, TRUE);
            SetDlgItemInt (hwnd, TMD_INTERNAL, (int)tm.tmInternalLeading, TRUE);
            SetDlgItemInt (hwnd, TMD_EXTERNAL, (int)tm.tmExternalLeading, TRUE);
            SetDlgItemInt (hwnd, TMD_AVEWIDTH, (int)tm.tmAveCharWidth, TRUE);
            SetDlgItemInt (hwnd, TMD_MAXWIDTH, (int)tm.tmMaxCharWidth, TRUE);
            SetDlgItemInt (hwnd, TMD_WEIGHT, (int)tm.tmWeight, TRUE);
            SetDlgItemInt (hwnd, TMD_ITALIC, (int)tm.tmItalic, FALSE);
            SetDlgItemInt (hwnd, TMD_UNDERLINE, (int)tm.tmUnderlined, FALSE);
            SetDlgItemInt (hwnd, TMD_STRUCKOUT, (int)tm.tmStruckOut, FALSE);
            SetDlgItemInt (hwnd, TMD_FIRSTCHAR, (int)tm.tmFirstChar, FALSE);
            SetDlgItemInt (hwnd, TMD_LASTCHAR, (int)tm.tmLastChar, FALSE);
            SetDlgItemInt (hwnd, TMD_DEFAULTCHAR, (int)tm.tmDefaultChar, FALSE);
            SetDlgItemInt (hwnd, TMD_BREAKCHAR, (int)tm.tmBreakChar, FALSE);
            SetDlgItemInt (hwnd, TMD_PITCHANDFAMILY, (int)tm.tmPitchAndFamily, FALSE);
            SetDlgItemInt (hwnd, TMD_CHARSET, (int)tm.tmCharSet, FALSE);
            SetDlgItemInt (hwnd, TMD_OVERHANG, (int)tm.tmOverhang, TRUE);
            SetDlgItemInt (hwnd, TMD_DIGITIZEDASPECTX, (int)tm.tmDigitizedAspectX, TRUE);
            SetDlgItemInt (hwnd, TMD_DIGITIZEDASPECTY, (int)tm.tmDigitizedAspectY, TRUE);

            return (TRUE);

        case WM_COMMAND:
#if defined (WIN32)
            wmId = LOWORD(wParam);
#elif defined (WIN16)
            wmId = wParam;
#endif
            switch (wmId) {
                case IDOK:
                    // Lets pull in as much data from the TEXTMETRIC structure as possible...
                    lfDlg.lfHeight         = tm.tmHeight;
                    lfDlg.lfWidth          = tm.tmAveCharWidth;
                    //lfDlg.lfEscapement - No Use
                    //lfDlg.lfOrientation - No Use
                    lfDlg.lfWeight         = tm.tmWeight;
                    lfDlg.lfItalic         = tm.tmItalic;
                    lfDlg.lfUnderline      = tm.tmUnderlined;
                    lfDlg.lfStrikeOut      = tm.tmStruckOut;
                    lfDlg.lfCharSet        = tm.tmCharSet;
                    //lfDlg.lfOutPrecision - No Use
                    //lfDlg.lfClipPrecision - No Use
                    //lfDlg.lfQuality - No Use
                    lfDlg.lfPitchAndFamily = tm.tmPitchAndFamily;
                    lstrcpy(lfDlg.lfFaceName, szFacename);

                    // Lets create a font with this new data
                    hfont = CreateFontIndirect (&lfDlg);
                    hdc = GetDC (hwnd);
                    hfontPrev = SelectObject (hdc, hfont);
                    GetTextMetrics (hdc, &tmTmp);
                    // Get the face name
                    GetTextFace    (hdc, sizeof(szBuffer), szBuffer);
                    SelectObject (hdc, hfontPrev);
                    DeleteObject (hfont);
                    ReleaseDC (hwnd, hdc);

                    // And verify that we did indeed get the same font.
                    bDone = TRUE;
                    bDone = bDone && (tm.tmHeight==tmTmp.tmHeight);
                    bDone = bDone && (tm.tmAscent==tmTmp.tmAscent);
                    bDone = bDone && (tm.tmDescent==tmTmp.tmDescent);
                    bDone = bDone && (tm.tmInternalLeading==tmTmp.tmInternalLeading);
                    bDone = bDone && (tm.tmExternalLeading==tmTmp.tmExternalLeading);
                    bDone = bDone && (tm.tmAveCharWidth==tmTmp.tmAveCharWidth);
                    bDone = bDone && (tm.tmMaxCharWidth==tmTmp.tmMaxCharWidth);
                    bDone = bDone && (tm.tmWeight==tmTmp.tmWeight);
                    bDone = bDone && (tm.tmItalic==tmTmp.tmItalic);
                    bDone = bDone && (tm.tmUnderlined==tmTmp.tmUnderlined);
                    bDone = bDone && (tm.tmStruckOut==tmTmp.tmStruckOut);
                    bDone = bDone && (tm.tmFirstChar==tmTmp.tmFirstChar);
                    bDone = bDone && (tm.tmLastChar==tmTmp.tmLastChar);
                    bDone = bDone && (tm.tmDefaultChar==tmTmp.tmDefaultChar);
                    bDone = bDone && (tm.tmBreakChar==tmTmp.tmBreakChar);
                    bDone = bDone && (tm.tmPitchAndFamily==tmTmp.tmPitchAndFamily);
                    bDone = bDone && (tm.tmCharSet==tmTmp.tmCharSet);
                    // Did it work?
                    if (bDone) {
                        *lplf = lfDlg;
                        EndDialog(hwnd, TRUE);
                        return (TRUE);
                    } else {
                        // We need to take a close look at the font verification
                        // code. Currently, it sometimes will report that the
                        // font didn't get properly selected, even if it did.
                        *lplf = lfDlg;
                        EndDialog(hwnd, TRUE);
                        return (TRUE);

                        // This is what we want to do once we beef up the font
                        // verification code:
                        lfDlg = *lplf;
                        MessageBox (GetFocus(),
                            "Unable to re-create font from TextMetrics",
                            "FontView", MB_OK);
                    }
                    break;

                case IDCANCEL:
                    EndDialog(hwnd, TRUE);
                    return (TRUE);

            }
            break;
    }
    return (FALSE);

    /* Just For Reference */
    lParam;
}

BOOL FillEnumFields (HWND hwnd, int iType, LPTEXTMETRIC ptm, LPLOGFONT plf, int nBase)
{
            SetDlgItemValue (hwnd, TMD_HEIGHT, (int)ptm->tmHeight, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_ASCENT, (int)ptm->tmAscent, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_DESCENT, (int)ptm->tmDescent, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_INTERNAL, (int)ptm->tmInternalLeading, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_EXTERNAL, (int)ptm->tmExternalLeading, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_AVEWIDTH, (int)ptm->tmAveCharWidth, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_MAXWIDTH, (int)ptm->tmMaxCharWidth, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_WEIGHT, (int)ptm->tmWeight, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_ITALIC, (int)ptm->tmItalic, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_UNDERLINE, (int)ptm->tmUnderlined, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_STRUCKOUT, (int)ptm->tmStruckOut, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_FIRSTCHAR, (int)ptm->tmFirstChar, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_LASTCHAR, (int)ptm->tmLastChar, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_DEFAULTCHAR, (int)ptm->tmDefaultChar, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_BREAKCHAR, (int)ptm->tmBreakChar, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_PITCHANDFAMILY, (int)ptm->tmPitchAndFamily, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_CHARSET, (int)ptm->tmCharSet, FALSE, nBase);
            SetDlgItemValue (hwnd, TMD_OVERHANG, (int)ptm->tmOverhang, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_DIGITIZEDASPECTX, (int)ptm->tmDigitizedAspectX, TRUE, nBase);
            SetDlgItemValue (hwnd, TMD_DIGITIZEDASPECTY, (int)ptm->tmDigitizedAspectY, TRUE, nBase);

            SetDlgItemValue (hwnd, CFD_HEIGHT, (int)plf->lfHeight, TRUE, nBase);
            SetDlgItemValue (hwnd, CFD_WIDTH, (int)plf->lfWidth, TRUE, nBase);
            SetDlgItemValue (hwnd, CFD_ESCAPEMENT, (int)plf->lfEscapement, TRUE, nBase);
            SetDlgItemValue (hwnd, CFD_ORIENTATION, (int)plf->lfOrientation, TRUE, nBase);
            SetDlgItemValue (hwnd, CFD_WEIGHT, (int)plf->lfWeight, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_ITALIC, (int)plf->lfItalic, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_UNDERLINE, (int)plf->lfUnderline, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_STRIKEOUT, (int)plf->lfStrikeOut, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_CHARSET, (int)plf->lfCharSet, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_OUTPUTPRECISION, (int)plf->lfOutPrecision, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_CLIPPRECISION, (int)plf->lfClipPrecision, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_QUALITY, (int)plf->lfQuality, FALSE, nBase);
            SetDlgItemValue (hwnd, CFD_PITCHANDFAMILY, (int)plf->lfPitchAndFamily, FALSE, nBase);
            SetDlgItemText(hwnd, CFD_FACENAME, plf->lfFaceName);

            SetDlgItemValue (hwnd, ED_TYPE, (int)iType, FALSE, nBase);

            return TRUE;
}

BOOL DrawSample (HWND hwnd, LPLOGFONT plf)
{
    HFONT   hfont, hfontPrev;
    HDC     hdc;
    RECT    r;

    hfont = CreateFontIndirect (plf);
    hdc = GetDC (hwnd);
    hfontPrev = SelectObject (hdc, hfont);
    GetWindowRect (hwnd, &r);
    ScreenToClient (hwnd, (LPPOINT)&r.left);
    ScreenToClient (hwnd, (LPPOINT)&r.right);

    Rectangle (hdc, r.left, r.top, r.right, r.bottom);

    InflateRect (&r, -1, -1);
    SetTextAlign (hdc, TA_BOTTOM | TA_CENTER);
    ExtTextOut (hdc, r.left + ((r.right-r.left)/2), r.bottom, ETO_CLIPPED, &r,"AaBbCcDdEe 012345", 17, NULL);

    SelectObject (hdc, hfontPrev);
    DeleteObject (hfont);
    ReleaseDC (hwnd, hdc);
    return TRUE;
}

/*
    Display a dialog that the user can use to enumerate through all of the fonts in the system.
    Show him not only all of the metrics for the font, but a sample of the font as well.
    If the user picks the 'Select' button, then the Metrics of this font will be used for
    the CreateFont call.
*/
BOOL APIENTRY EnumDlgProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    static HANDLE /*TO FONTLIST*/ hFonts;
    static int iLoc=0;
    static int count=0;
    static BOOL bHex = FALSE;
    static LOGFONT *lplf;
    static LOGFONT lfDlg;
    int wmId, i, j;
    HANDLE hInst;
    HWND   hwndItem;
    HDC hdc;
    FARPROC lpEnumFonts;
    LPFONTLIST    pFL;
    LPFONTSTRUCT  pFS;
    char szTmp[80];
    HFONT hfont, hfontPrev;
    BOOL bDone;
    TEXTMETRIC tmTmp;

    switch (msg) {
        case WM_INITDIALOG:
            lplf = (LOGFONT *)lParam;
            lfDlg = *lplf;
            iLoc = 0;
            count = 0;
            bHex = FALSE;
            /* First, lets enumerate ALL fonts, and store them in our list */
            hFonts = GlobalAlloc (GHND, sizeof(FONTLIST));
            if (hFonts) {

#if defined (WIN32)
    hInst = (HANDLE)GetWindowLong (hwnd, GWL_HINSTANCE);
#elif defined (WIN16)
    hInst = (HANDLE)GetWindowWord (hwnd, GWW_HINSTANCE);
#endif

                lpEnumFonts = MakeProcInstance((FARPROC)DlgEnumFontNames, hInst);
                if (lpEnumFonts) {
                    hdc  = GetDC(hwnd);
                    // The enumeration function will lock down the handle
                    EnumFonts (hdc, NULL, (FONTENUMPROC)lpEnumFonts, (LPARAM)&hFonts);
                    // The handle will come back to us properly unlocked
                    ReleaseDC(hwnd, hdc);
                    FreeProcInstance (lpEnumFonts);

                 }
            }
            pFL = (LPFONTLIST)GlobalLock (hFonts);
            if (pFL) {
                pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                if (pFS) {
                    iLoc = 0;
                    j = 100;
                    for (i=0; i<pFL->count; i++) {
                        if (lstrcmp(lfDlg.lfFaceName, pFS[i].lf.lfFaceName) == 0) {
                            if (abs(lfDlg.lfHeight-pFS[i].lf.lfHeight) < j) {
                                j = abs(lfDlg.lfHeight-pFS[i].lf.lfHeight);
                                iLoc = i;
                            }
                        }
                    }
                    FillEnumFields (hwnd, pFS[iLoc].ft, &pFS[iLoc].tm, &pFS[iLoc].lf, (bHex?16:10));
                    hwndItem = GetDlgItem (hwnd, ED_SAMPLE);
                    DrawSample (hwndItem, &pFS[iLoc].lf);
                    GlobalUnlock (pFL->hList);

                }
                count = pFL->count;
                GlobalUnlock (hFonts);

            }
            /* now fill in the dialog values */
            wsprintf (szTmp, "EnumFonts %d of %d", iLoc+1, count);
            SetWindowText (hwnd, szTmp);
            CenterWindow (hwnd, GetWindow (hwnd, GW_OWNER));

            return TRUE;

        case WM_PAINT:
            pFL = (LPFONTLIST)GlobalLock (hFonts);
            if (pFL) {
                pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                if (pFS) {
                    FillEnumFields (hwnd, pFS[iLoc].ft, &pFS[iLoc].tm, &pFS[iLoc].lf, (bHex?16:10));
                    hwndItem = GetDlgItem (hwnd, ED_SAMPLE);
                    DrawSample (hwndItem, &pFS[iLoc].lf);
                    GlobalUnlock (pFL->hList);
                }
                GlobalUnlock (hFonts);
            }
            return 0;

        case WM_COMMAND:
#if defined (WIN32)
            wmId = LOWORD(wParam);
#elif defined (WIN16)
            wmId = wParam;
#endif
            switch (wmId) {

                case ED_HEX:
                    // Display the data in either Hex mode or Dec mode.
                    bHex = !bHex;
                    CheckDlgButton (hwnd, wmId, bHex);
                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    if (pFL) {
                        pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                        if (pFS) {
                            FillEnumFields (hwnd, pFS[iLoc].ft, &pFS[iLoc].tm, &pFS[iLoc].lf, (bHex?16:10));
                            if (GlobalUnlock (pFL->hList)) {
                                MessageBox (GetFocus(), "In HEX", "Unlock pFL->hList", MB_OK);
                            }
                        }
                        if (GlobalUnlock (hFonts)) {
                            MessageBox (GetFocus(), "In HEX", "Unlock hFonts", MB_OK);
                        }

                    }
                    break;

                case ED_PREV:
                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    if (pFL) {
                        if (iLoc > 0) {
                            iLoc--;
                            pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                            if (pFS) {
                                FillEnumFields (hwnd, pFS[iLoc].ft, &pFS[iLoc].tm, &pFS[iLoc].lf, (bHex?16:10));
                                hwndItem = GetDlgItem (hwnd, ED_SAMPLE);
                                DrawSample (hwndItem, &pFS[iLoc].lf);
                                GlobalUnlock (pFL->hList);
                            }
                        }
                        GlobalUnlock (hFonts);
                    }
                    wsprintf (szTmp, "EnumFonts %d of %d", iLoc+1, count);
                    SetWindowText (hwnd, szTmp);
                    break;

                case ED_NEXT:
                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    if (pFL) {
                        if ((pFL->count-1) > iLoc) {
                            iLoc++;
                            pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                            if (pFS) {
                                FillEnumFields (hwnd, pFS[iLoc].ft, &pFS[iLoc].tm, &pFS[iLoc].lf, (bHex?16:10));
                                hwndItem = GetDlgItem (hwnd, ED_SAMPLE);
                                DrawSample (hwndItem, &pFS[iLoc].lf);
                                GlobalUnlock (pFL->hList);
                            }

                        }
                        GlobalUnlock (hFonts);
                    }
                    wsprintf (szTmp, "EnumFonts %d of %d", iLoc+1, count);
                    SetWindowText (hwnd, szTmp);
                    break;

                case IDOK:
                    // Copy the LOGFONT structure from the enumeration list, into our private LF
                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    bDone = FALSE;
                    if (pFL) {
                        if ((pFL->count-1) >= iLoc) {
                            pFS = (LPFONTSTRUCT)GlobalLock (pFL->hList);
                            if (pFS) {
                                lfDlg = pFS[iLoc].lf;

                    // Lets create a font with this new data
                    hfont = CreateFontIndirect (&lfDlg);
                    hdc = GetDC (hwnd);
                    hfontPrev = SelectObject (hdc, hfont);
                    GetTextMetrics (hdc, &tmTmp);
                    // Get the face name
                    GetTextFace    (hdc, sizeof(szTmp), szTmp);
                    SelectObject (hdc, hfontPrev);
                    DeleteObject (hfont);
                    ReleaseDC (hwnd, hdc);

                    // And verify that we did indeed get the same font.
                    bDone = TRUE;
                    bDone = bDone && (pFS[iLoc].tm.tmHeight==tmTmp.tmHeight);
                    bDone = bDone && (pFS[iLoc].tm.tmAscent==tmTmp.tmAscent);
                    bDone = bDone && (pFS[iLoc].tm.tmDescent==tmTmp.tmDescent);
                    bDone = bDone && (pFS[iLoc].tm.tmInternalLeading==tmTmp.tmInternalLeading);
                    bDone = bDone && (pFS[iLoc].tm.tmExternalLeading==tmTmp.tmExternalLeading);
                    bDone = bDone && (pFS[iLoc].tm.tmAveCharWidth==tmTmp.tmAveCharWidth);

bDone = bDone && (pFS[iLoc].tm.tmMaxCharWidth==tmTmp.tmMaxCharWidth);
                    bDone = bDone && (pFS[iLoc].tm.tmWeight==tmTmp.tmWeight);
                    bDone = bDone && (pFS[iLoc].tm.tmItalic==tmTmp.tmItalic);
                    bDone = bDone && (pFS[iLoc].tm.tmUnderlined==tmTmp.tmUnderlined);
                    bDone = bDone && (pFS[iLoc].tm.tmStruckOut==tmTmp.tmStruckOut);
                    bDone = bDone && (pFS[iLoc].tm.tmFirstChar==tmTmp.tmFirstChar);
                    bDone = bDone && (pFS[iLoc].tm.tmLastChar==tmTmp.tmLastChar);
                    bDone = bDone && (pFS[iLoc].tm.tmDefaultChar==tmTmp.tmDefaultChar);
                    bDone = bDone && (pFS[iLoc].tm.tmBreakChar==tmTmp.tmBreakChar);
                    bDone = bDone && (pFS[iLoc].tm.tmPitchAndFamily==tmTmp.tmPitchAndFamily);
                    bDone = bDone && (pFS[iLoc].tm.tmCharSet==tmTmp.tmCharSet);
                    // Did it work?
                    if (bDone) {
                        *lplf = lfDlg;
                        //EndDialog(hwnd, TRUE);
                        //return (TRUE);
                    } else {
                        // Again, font verification code is'nt quite up to
                        // snuff yet, so just Select the font anyway:
                        *lplf = lfDlg;

                        // ...and this is what we want to do once we fix the
                        // font verification code:
                        //lfDlg = *lplf;
                        //MessageBox (GetFocus(),
                        //    "Unable to re-create font from TextMetrics",
                        //    "FontView", MB_OK);
                    }

                                GlobalUnlock (pFL->hList);
                            }
                        }
                        GlobalUnlock (hFonts);
                    }

                    // Now set our master lf to this value. This way, we 'could' verify that the
                    // LOGFONT structure will in fact select this particular font, we just aren't yet
                    // *lplf = lfDlg;

                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    if (pFL) {
                        if (GlobalFree (pFL->hList)) {
                            MessageBox (GetFocus(), "Failed To Free", "pFL->hList", MB_OK);
                        } else {
                            GlobalUnlock (hFonts);
                            if (GlobalFree (hFonts)) {
                                MessageBox (GetFocus(), "Failed To Free", "hFonts", MB_OK);
                            }
                        }
                    }
                    EndDialog(hwnd, TRUE);
                    return (bDone);

                case IDCANCEL:
                    pFL = (LPFONTLIST)GlobalLock (hFonts);
                    if (pFL) {
                        if (GlobalFree (pFL->hList)) {
                            MessageBox (GetFocus(), "Failed To Free", "pFL->hList", MB_OK);
                        } else {
                            GlobalUnlock (hFonts);
                            if (GlobalFree (hFonts)) {
                                MessageBox (GetFocus(), "Failed To Free", "hFonts", MB_OK);
                            }
                        }
                    }
                    EndDialog(hwnd, TRUE);
                    return (TRUE);
            }
            break;
    }
    return (FALSE);

    lParam; // unreferenced formal parameter
}


STATUS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define NOMINMAX
#include <windows.h>

#include <stdlib.h> // For 'abs'

#if !defined (APIENTRY)
#define APIENTRY FAR PASCAL
#endif

#define MAXSTATUS 10
typedef struct _tagStatus {
    HWND    hwnd;
    int     iMaxWidth, iMinWidth, iGiveWidth;
} Status;
Status  statusField[MAXSTATUS];

HWND    hwndStatus;
int    cntStatusField = 0;
int    dyStatus, cxStatusBorder, cyStatusBorder, cxFrame, cyFrame, dyField;
HFONT   hfontStatus;
TEXTMETRIC tmStatusFont;
HBRUSH hbrBtnFace;

LONG APIENTRY StatusProc      (HWND, UINT, UINT, LONG);
LONG APIENTRY StatusFieldProc (HWND, UINT, UINT, LONG);

BOOL InitStatusBar (HANDLE hInstance)
{
    WNDCLASS    wndclass;

    hbrBtnFace = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = (WNDPROC)StatusProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance      = hInstance;
    wndclass.hIcon     = NULL;
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = hbrBtnFace;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = "SamplerStatus";

    if (!RegisterClass (&wndclass))
        return FALSE;

    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = (WNDPROC)StatusFieldProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance      = hInstance;
    wndclass.hIcon     = NULL;
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = hbrBtnFace;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = "StatusField";

    if (!RegisterClass (&wndclass))
        return FALSE;

}

BOOL CreateStatusBar (HWND hwnd, HANDLE hInst, int iId)
{
    cxStatusBorder = GetSystemMetrics (SM_CXBORDER);
    cyStatusBorder = GetSystemMetrics (SM_CYBORDER);

    hwndStatus = CreateWindow ("SamplerStatus", "SamplerStatus",
        WS_CHILD | WS_BORDER | WS_VISIBLE,
        0, 0, 0, 0,
        hwnd, (HMENU)iId, hInst, NULL);

    if (!hwndStatus) {
        return FALSE;
    }
    return TRUE;
}

int StatusBarHeight (HWND hwnd)
{
    RECT rect;
    GetClientRect (hwndStatus, &rect);
    return rect.bottom-rect.top;

    hwnd; // unreferenced formal parameter
}

BOOL AdjustStatusBar (HWND hwnd)
{
    RECT rect;
    GetClientRect (hwnd, &rect);
    MoveWindow (hwndStatus,
        rect.left-cxStatusBorder,
        rect.bottom - dyStatus + cyStatusBorder,
        rect.right - rect.left + (cxStatusBorder*2),
        dyStatus,
    TRUE);
    return TRUE;
}

HWND AddStatusField (HANDLE hInst, int iId, int iMin, int iMax, BOOL bNewGroup)
{
    LONG lStyle;

    if (cntStatusField >= MAXSTATUS) return (HWND)0; // No room left in our fixed array

    statusField[cntStatusField].hwnd = CreateWindow ("StatusField", "",
        WS_CHILD | WS_VISIBLE,
        0, 0, 0, 0,
        hwndStatus, (HMENU)iId, hInst, NULL);

    if (!statusField[cntStatusField].hwnd) return (HWND)0; // CreateWindow failed for some reason

    if (iMin < 0) {
    statusField[cntStatusField].iMinWidth = tmStatusFont.tmAveCharWidth*abs(iMin);
    } else {
    statusField[cntStatusField].iMinWidth = iMin;
    }

    if (iMax < 0) {
    statusField[cntStatusField].iMaxWidth = tmStatusFont.tmAveCharWidth*abs(iMax);
    } else {
    statusField[cntStatusField].iMaxWidth = iMax;
    }

    if (bNewGroup) {
        lStyle = GetWindowLong (statusField[cntStatusField].hwnd, GWL_STYLE);
        lStyle |= WS_GROUP;
        SetWindowLong (statusField[cntStatusField].hwnd, GWL_STYLE, lStyle);
    }

    return statusField[cntStatusField++].hwnd;
}

BOOL DestroyStatusBar (void)
{
    return DeleteObject (hbrBtnFace);
}

LONG APIENTRY StatusProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    int x, y, i;
    int wAvailWidth, wFlexWidth, cntFlexWidth, wNeedWidth, cntNeedWidth;
    RECT    rect, border;
    HBRUSH  hBrush;
    LOGFONT lfTmp;

    switch (msg) {
        case WM_CREATE:
            hfontStatus = CreateFont(14, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0,
                VARIABLE_PITCH | FF_SWISS, "");

                        if (GetObject (hfontStatus, sizeof(LOGFONT), &lfTmp)) {
                                if ((lfTmp.lfPitchAndFamily & VARIABLE_PITCH) &&
                                   (lfTmp.lfPitchAndFamily & FF_SWISS)) {
                                } else {
                                        MessageBox (GetFocus(),
"Unable to get an unnamed variable pitch swiss font", "Status Bar CreateFont Error",
                                                MB_OK);
                hfontStatus = CreateFont(14, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0,
                        VARIABLE_PITCH | FF_SWISS, "Arial");
                                }
                        }

            if (!hfontStatus) {
                MessageBox (GetFocus(), "Failed To Create Font", "StatusProc", MB_OK);
            }
            hdc = GetDC (hwnd);
            SelectObject (hdc, hfontStatus);
            GetTextMetrics (hdc, &tmStatusFont);
            cxStatusBorder = GetSystemMetrics (SM_CXBORDER);
            cyStatusBorder = GetSystemMetrics (SM_CYBORDER);
            cxFrame = 3*cxStatusBorder;
            cyFrame = 3*cyStatusBorder;
            dyField = tmStatusFont.tmHeight + (2*cyStatusBorder);
            dyStatus = dyField + (2*cyFrame);
            ReleaseDC (hwnd, hdc);
            return DefWindowProc (hwnd, msg, wParam, lParam);

        case WM_DESTROY:
            if (hfontStatus) {
                DeleteObject (hfontStatus);
            }
            break;

        case WM_SIZE:
        if (cntStatusField) {
                GetClientRect (hwnd, &rect);
                wAvailWidth = rect.right - rect.left - (cxStatusBorder*8);
                wNeedWidth = 0;
                cntNeedWidth = 0;
                cntFlexWidth = 0;

                /* First Pass: Dole out to fields that have a minimum need */
                for (i=0; i<cntStatusField; i++) {
                    statusField[i].iGiveWidth = 0; // Make sure all are initialized to 0
                    if (statusField[i].iMinWidth) {
                        /* (n, ?) */
                        statusField[i].iGiveWidth = statusField[i].iMinWidth;
                        wAvailWidth -= (statusField[i].iGiveWidth + cxStatusBorder*2);
                        if (GetWindowLong(statusField[i].hwnd, GWL_STYLE) & WS_GROUP) {
                            wAvailWidth -= cxStatusBorder*4;
                        }
                    } else {
                        /* They didn't specify a minimum... don't give them anything yet */
                        /* (0, ?) */
                        statusField[i].iGiveWidth = 0;
                    }

                    /* For those that have a minimum, but can grow to be as large as possible...*/
                    /* (n, 0) */
                    if ((statusField[i].iMinWidth >0) && (statusField[i].iMaxWidth ==0)) {
                        ++cntFlexWidth;
                    }

                    /* For those that have a max that is greater then their min... */
                    /* Includes (0,n) and (n,>n) */
                    if (statusField[i].iMaxWidth > statusField[i].iGiveWidth) {
                        wNeedWidth += (statusField[i].iMaxWidth - statusField[i].iGiveWidth);
                        ++cntNeedWidth;
                    }
                }

                /* Second Pass: Dole out to fields that have a stated maximum need */
                /* This will also hit those who had no minimum, but did have a maximum */
                /* It will still not give anything to those with no min, no max */
                if ((cntNeedWidth > 0) && (wAvailWidth > 0)) {
                    if (wNeedWidth > wAvailWidth) {
                        wNeedWidth = wAvailWidth;
                    }
                    wNeedWidth = wNeedWidth / cntNeedWidth;
                    for (i=0; i<cntStatusField; i++) {
                        if (statusField[i].iMaxWidth > statusField[i].iGiveWidth) {
                            statusField[i].iGiveWidth += wNeedWidth;
                            wAvailWidth -= (statusField[i].iGiveWidth + cxStatusBorder*2);
                            if (GetWindowLong(statusField[i].hwnd, GWL_STYLE) & WS_GROUP) {
                                wAvailWidth -= cxStatusBorder*4;
                            }
                        }
                    }
                }

                /* Third Pass: Dole out the remaining to fields that want all they can get */
                /* This includes those who had a minimum, but no maximum */
                if ((cntFlexWidth > 0) && (wAvailWidth > 0)) {
                    wFlexWidth = wAvailWidth / cntFlexWidth;
                    for (i=0; i<cntStatusField; i++) {
                        if (statusField[i].iMaxWidth==0) {
                            statusField[i].iGiveWidth += wFlexWidth;
                            wAvailWidth -= ((wFlexWidth - statusField[i].iMinWidth) + cxStatusBorder*2);
                            if (GetWindowLong(statusField[i].hwnd, GWL_STYLE) & WS_GROUP) {
                                wAvailWidth -= cxStatusBorder*4;
                            }
                        }
                    }
                }

                x = cxStatusBorder*4;
                y = rect.top + (2*cyStatusBorder);
                for (i=0; i<cntStatusField; i++) {
                    if (GetWindowLong (statusField[i].hwnd, GWL_STYLE) & WS_GROUP) {
                        x += (cxStatusBorder*4);
                    }
                    MoveWindow (statusField[i].hwnd, x, y, statusField[i].iGiveWidth, dyField, TRUE);
                    x += statusField[i].iGiveWidth + (cxStatusBorder*2);
                }
            }
            break;

        case WM_PAINT:
            hdc = BeginPaint (hwnd, &ps);
            GetClientRect (hwnd, &rect);

            hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            border = rect;
            border.bottom = border.top + cyStatusBorder;
            FillRect (hdc, &border, hBrush);
            DeleteObject (hBrush);

            hBrush = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
            border = rect;
            border.top = border.bottom - cyStatusBorder;
            FillRect (hdc, &border, hBrush);
            DeleteObject (hBrush);

            EndPaint (hwnd, &ps);

            return DefWindowProc (hwnd, msg, wParam, lParam);

        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}

LONG APIENTRY StatusFieldProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    HDC     hdc;
    PAINTSTRUCT ps;
    RECT    rect, border;
    HBRUSH  hBrush;
    WORD    edge = 1;
    HFONT   hTmp;
    char szText[80];
    int len;

    switch (msg) {
        case WM_PAINT:
            hdc = BeginPaint (hwnd, &ps);
            GetClientRect (hwnd, &rect);

            hBrush = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
            border = rect;
            border.bottom = border.top + cyStatusBorder;
            FillRect (hdc, &border, hBrush);
            border = rect;
            border.right = border.left + cxStatusBorder;
            FillRect (hdc, &border, hBrush);
            DeleteObject (hBrush);

            hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            border = rect;
            border.top = border.bottom - cyStatusBorder;
            FillRect (hdc, &border, hBrush);
            border = rect;
            border.left = border.right - cxStatusBorder;
            FillRect (hdc, &border, hBrush);
            DeleteObject (hBrush);

            if (len = GetWindowText(hwnd, szText, sizeof (szText))) {
                hTmp = SelectObject(hdc, hfontStatus);

                SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
                SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));

                InflateRect (&rect, -(cxStatusBorder*2), -cyStatusBorder);
                ExtTextOut(hdc, rect.left, rect.top,
                    ETO_OPAQUE | ETO_CLIPPED,
                    &rect,
                    (LPSTR)szText,
                    len, NULL);

                SelectObject (hdc, hTmp);
            }

            EndPaint (hwnd, &ps);
            break;

        case WM_SETTEXT:
            InvalidateRect (hwnd, NULL, TRUE);
            return DefWindowProc (hwnd, msg, wParam, lParam);

        default:
            return DefWindowProc (hwnd, msg, wParam, lParam);
    }
    return 0L;
}


TOOLS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define NOMINMAX
#include <windows.h>

#include <stdlib.h> // For 'abs'

#if !defined (APIENTRY)
#define APIENTRY FAR PASCAL
#endif

HWND    hwndTools, hwndToolText, hwndToolCombo, hwndToolButton, hwndMain;
HFONT   hfontTools=0;
TEXTMETRIC tmToolFont;
int     dyTools = 0, cxToolBorder, cyToolBorder, cntToolCtrls = 0, dyCombo;
int     xCurrent = 10;
HBRUSH  hbrBtnFace=0, hbrWindow=0;
HANDLE  hInst=0;

#define MAXCTRLS 25
#define TC_SPACE 0
#define TC_LABEL 1
#define TC_COMBO 2
#define TC_BUTTON 3
typedef struct _tagTools {
                        HWND    hwnd;
                        WORD    wType;
                        int     iWidth, iHeight;
                        HICON   hIcon;
                } Tools;
Tools  toolCtrl[MAXCTRLS];

LONG APIENTRY ToolsProc       (HWND, UINT, UINT, LONG);
LONG APIENTRY MyComboProc     (HWND, UINT, UINT, LONG);

BOOL InitToolBar (HANDLE hInstance)
{
        WNDCLASS    wndclass;

        hbrBtnFace = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        hbrWindow = CreateSolidBrush(GetSysColor(COLOR_WINDOW));

        wndclass.style         = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc   = (WNDPROC)ToolsProc;
        wndclass.cbClsExtra    = 0;
        wndclass.cbWndExtra    = 0;
        wndclass.hInstance      = hInstance;
        wndclass.hIcon     = NULL;
        wndclass.hbrBackground = hbrBtnFace;
        wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
        wndclass.lpszMenuName  = NULL;
        wndclass.lpszClassName = "SamplerTools";

        if (!RegisterClass (&wndclass))
                return FALSE;
}

BOOL CreateToolBar (HWND hwnd, HANDLE hInstance, int iId)
{
        HWND hwndTmp;
        RECT rect;

#if defined (WIN32)
        hInst = (HANDLE)GetWindowLong (hwnd, GWL_HINSTANCE);
#else
        hInst = GetWindowWord (hwnd, GWW_HINSTANCE);
#endif

        if (hbrBtnFace==0) {
                hbrBtnFace = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        }
        if (hbrWindow==0) {
                hbrWindow = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        }

        cxToolBorder = GetSystemMetrics (SM_CXBORDER);
        cyToolBorder = GetSystemMetrics (SM_CYBORDER);

        hwndTools = CreateWindow ("SamplerTools", "SamplerTools",
                WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | WS_VISIBLE,
                0, 0, 0, 0,
        hwnd, (HMENU)iId, hInst, NULL);
        if (!hwndTools) {
                return FALSE;
        }

        /* Lets find out how big a combo box is... */
        hwndTmp = CreateWindow ("COMBOBOX", "Combo",
                        WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST,
                        0, 0, 0, 0,
                        hwndTools, NULL, hInst, NULL);
        if (hwndTmp) {
                SendMessage (hwndTmp, WM_SETFONT, (UINT)hfontTools, MAKELONG (TRUE, 0));
                GetClientRect (hwndTmp, &rect);
                dyCombo = rect.bottom - rect.top;
                DestroyWindow (hwndTmp);
        } else {
                dyCombo = 30; // Just for a default value
        }

        hwndMain = hwnd; // So we can pass WM_CONTROL messages back to the master parent

        return TRUE;

        hInstance; // unreferenced formal parameter
}

int ToolBarHeight (HWND hwnd)
{
        RECT rect;
        GetClientRect (hwndTools, &rect);
        return rect.bottom-rect.top;

        hwnd; //unreferenced formal parameter
}

BOOL AdjustToolBar (HWND hwnd)
{
        RECT rect;

        GetClientRect (hwnd, &rect);
        MoveWindow (hwndTools,
                rect.left-cxToolBorder,
                rect.top - cyToolBorder,
                rect.right - rect.left + (cxToolBorder*2),
                dyTools,
                TRUE);
        return TRUE;
}

void UpdatePositions (void)
{
        int i, x, y, dx, dy, cnt;

        x = 10;
        for (i=0; i<cntToolCtrls; i++) {
                switch (toolCtrl[i].wType) {
                        case TC_SPACE:
                                dx = toolCtrl[i].iWidth;
                                break;
                        case TC_LABEL:
                                dy = toolCtrl[i].iHeight;
                                y = (dyTools/2) - (dy/2) - 1;
                                dx = toolCtrl[i].iWidth;
                                break;
                        case TC_COMBO:
                                dy = toolCtrl[i].iHeight;
                                y = (dyTools/2) - (dy/2) - 1;
                                dx = toolCtrl[i].iWidth;
                                cnt = (int)SendMessage (toolCtrl[i].hwnd, CB_GETCOUNT, (UINT)0, (LONG)0);
                                if (cnt > 5) cnt = 5;
                                dy = dy * cnt;
                                break;
                        case TC_BUTTON:
                                dy = toolCtrl[i].iHeight;
                                y = (dyTools/2) - (dy/2) - 1;
                                dx = toolCtrl[i].iWidth;
                                break;
                        default:
                                dy = toolCtrl[i].iHeight;
                                y = (dyTools/2) - (dy/2) - 1;
                                dx = toolCtrl[i].iWidth;
                                break;
                }
                if (toolCtrl[i].wType != TC_SPACE) {
                        MoveWindow (toolCtrl[i].hwnd, x, y, dx, dy, FALSE);
                }
                x += dx;
        }
        if (hwndTools) {
                UpdateWindow (hwndTools);
        }
}

BOOL AddToolSpace (int iWidth, int iHeight)
{
        if (cntToolCtrls >= MAXCTRLS) return FALSE;
        toolCtrl[cntToolCtrls].hwnd = 0;
        toolCtrl[cntToolCtrls].wType = TC_SPACE;
        toolCtrl[cntToolCtrls].iWidth = iWidth;
        toolCtrl[cntToolCtrls].iHeight = iHeight;
        if ((toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder)) > dyTools) {
                dyTools = (toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder));
        }
        UpdatePositions();
        cntToolCtrls++;
        return TRUE;
}

HWND AddToolLabel (HANDLE hInst, int iId, LPSTR szLabel, int iWidth, DWORD dwStyle)
{
        HDC hdc;

        if (cntToolCtrls >= MAXCTRLS) return (HWND)0; // No room left in our fixed array

        toolCtrl[cntToolCtrls].hwnd = CreateWindow ("STATIC", szLabel,
                WS_CHILD | WS_VISIBLE | dwStyle,
                0, 0, 0, 0,
                hwndTools, (HMENU)iId, hInst, NULL);

        if (!toolCtrl[cntToolCtrls].hwnd) return (HWND)0; // CreateWindow failed for some reason

        SendMessage (toolCtrl[cntToolCtrls].hwnd, WM_SETFONT, (UINT)hfontTools, MAKELONG (TRUE, 0));
        toolCtrl[cntToolCtrls].wType = TC_LABEL;

        hdc = GetDC (hwndTools);
        if (iWidth < 0) {
                toolCtrl[cntToolCtrls].iWidth = tmToolFont.tmAveCharWidth * abs(iWidth);
        } else if (iWidth == 0) {
#if defined (WIN32)
                SIZE size;
                GetTextExtentPoint (hdc, szLabel, lstrlen(szLabel), &size);
                toolCtrl[cntToolCtrls].iWidth = size.cx;
#else
                toolCtrl[cntToolCtrls].iWidth = LOWORD(GetTextExtent (hdc, szLabel, lstrlen(szLabel)));
#endif
        } else {
                toolCtrl[cntToolCtrls].iWidth = iWidth;
        }
        toolCtrl[cntToolCtrls].iHeight = tmToolFont.tmHeight;

        if ((toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder)) > dyTools) {
                dyTools = (toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder));
        }

        ReleaseDC (hwndTools, hdc);
        UpdatePositions();
        return toolCtrl[cntToolCtrls++].hwnd;
}

HWND AddToolCombo (HANDLE hInst, int iId, int iWidth, DWORD dwStyle)
{

        if (cntToolCtrls >= MAXCTRLS) return (HWND)0; // No room left in our fixed array

        if (dwStyle==0) dwStyle = CBS_DROPDOWNLIST;
        toolCtrl[cntToolCtrls].hwnd = CreateWindow ("COMBOBOX", "",
                WS_CHILD | WS_VISIBLE | dwStyle,
                0, 0, 0, 0,
                hwndTools, (HMENU)iId, hInst, NULL);

        if (!toolCtrl[cntToolCtrls].hwnd) return (HWND)0; // CreateWindow failed for some reason

        SendMessage (toolCtrl[cntToolCtrls].hwnd, WM_SETFONT, (UINT)hfontTools, MAKELONG (TRUE, 0));
        toolCtrl[cntToolCtrls].wType = TC_COMBO;

        if (iWidth < 0) {
                toolCtrl[cntToolCtrls].iWidth = tmToolFont.tmAveCharWidth * abs(iWidth);
        } else if (iWidth == 0) {
                toolCtrl[cntToolCtrls].iWidth = tmToolFont.tmAveCharWidth * 15; // just a default width
        } else {
                toolCtrl[cntToolCtrls].iWidth = iWidth;
        }
        toolCtrl[cntToolCtrls].iHeight = dyCombo;

        if ((toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder)) > dyTools) {
                dyTools = (toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder));
        }

        UpdatePositions();
        return toolCtrl[cntToolCtrls++].hwnd;
}

HWND AddToolButton (HANDLE hInst, int iId, LPSTR szLabel, int iWidth, int iHeight, DWORD dwStyle)
{
        HDC hdc;

        if (cntToolCtrls >= MAXCTRLS) return (HWND)0; // No room left in our fixed array

        if (dwStyle == 0) dwStyle = BS_PUSHBUTTON;
        toolCtrl[cntToolCtrls].hwnd = CreateWindow ("BUTTON", szLabel,
                WS_CHILD | WS_VISIBLE | dwStyle,
                0, 0, 0, 0,
                hwndTools, (HMENU)iId, hInst, NULL);

        if (!toolCtrl[cntToolCtrls].hwnd) return (HWND)0; // CreateWindow failed for some reason

        SendMessage (toolCtrl[cntToolCtrls].hwnd, WM_SETFONT, (UINT)hfontTools, MAKELONG (TRUE, 0));
        toolCtrl[cntToolCtrls].wType = TC_BUTTON;

        hdc = GetDC (hwndTools);
        SelectObject (hdc, hfontTools);
        if (iWidth < 0) {
                toolCtrl[cntToolCtrls].iWidth = tmToolFont.tmAveCharWidth * abs(iWidth);
                toolCtrl[cntToolCtrls].iWidth += (6*cxToolBorder);
        } else if (iWidth == 0) {
#if defined (WIN32)
                SIZE size;
                GetTextExtentPoint (hdc, szLabel, lstrlen(szLabel), &size);
                toolCtrl[cntToolCtrls].iWidth = size.cx;
#else
                toolCtrl[cntToolCtrls].iWidth = LOWORD(GetTextExtent (hdc, szLabel, lstrlen(szLabel)));
#endif
                toolCtrl[cntToolCtrls].iWidth += (6*cxToolBorder);
        } else {
                toolCtrl[cntToolCtrls].iWidth = iWidth;
        }
        if (iHeight < 0) {
                toolCtrl[cntToolCtrls].iHeight = tmToolFont.tmHeight;
                toolCtrl[cntToolCtrls].iHeight += (6*cyToolBorder);
        } else if (iHeight==0) {
                toolCtrl[cntToolCtrls].iHeight = dyTools - (6*cyToolBorder);
        } else {
                toolCtrl[cntToolCtrls].iHeight = iHeight;
        }

        if ((toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder)) > dyTools) {
                dyTools = (toolCtrl[cntToolCtrls].iHeight + (6*cyToolBorder));
        }

        if (dwStyle & BS_OWNERDRAW) {
                toolCtrl[cntToolCtrls].hIcon = LoadIcon (hInst, szLabel);
        } else {
                toolCtrl[cntToolCtrls].hIcon = NULL;
        }

        ReleaseDC (hwndTools, hdc);
        UpdatePositions();
        return toolCtrl[cntToolCtrls++].hwnd;
}

BOOL DestroyToolBar (void)
{
        return DeleteObject (hbrBtnFace);
}

void DrawButton (HDC hdc, RECT rect, BOOL bDown, HICON hIcon)
{
        HBRUSH  hBrush, hbrFrame, hbrFace, hbrHilite, hbrShadow;
        RECT    border;
        int     i;

        hbrFrame = CreateSolidBrush(GetSysColor(COLOR_WINDOWFRAME));
        hbrFace = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        hbrHilite = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        hbrShadow = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));

        FillRect (hdc, &rect, hbrFace);

        if (hIcon) {
                if (bDown) {
                        DrawIcon (hdc, rect.left + (4*cyToolBorder), rect.top + (4*cyToolBorder), hIcon);
                } else {
                        DrawIcon (hdc, rect.left + (3*cyToolBorder), rect.top + (3*cyToolBorder), hIcon);
                }
        }

        hBrush = hbrFrame;
        border = rect; border.bottom = border.top + cyToolBorder;
        FillRect (hdc, &border, hBrush);
        border = rect; border.right = border.left + cxToolBorder;
        FillRect (hdc, &border, hBrush);
        border = rect; border.top = border.bottom - cyToolBorder;
        FillRect (hdc, &border, hBrush);
        border = rect; border.left = border.right - cxToolBorder;
        FillRect (hdc, &border, hBrush);

        for (i= 0; i<2; i++) {
                InflateRect (&rect, -cxToolBorder, -cyToolBorder);
                hBrush = (bDown?hbrShadow:hbrHilite);
                border = rect; border.bottom = border.top + cyToolBorder;
                FillRect (hdc, &border, hBrush);
                border = rect; border.right = border.left + cxToolBorder;
                FillRect (hdc, &border, hBrush);
                if (!bDown) {
                        hBrush = hbrShadow;
                        border = rect; border.top = border.bottom - cyToolBorder;
                        FillRect (hdc, &border, hBrush);
                        border = rect; border.left = border.right - cxToolBorder;
                        FillRect (hdc, &border, hBrush);
                }
        }

        DeleteObject (hbrFrame);
        DeleteObject (hbrFace);
        DeleteObject (hbrHilite);
        DeleteObject (hbrShadow);

}

LONG APIENTRY ToolsProc (HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
        HDC          hdc;
        PAINTSTRUCT      ps;
        int          iType, idCtrl, msgCtrl, i;
        RECT         rect, border;
        COLORREF         clrColor;
        HWND         hwndCtl;
        LONG         lStyle;
        HBRUSH       hBrush;
        LPDRAWITEMSTRUCT lpdi;
        HICON        hIcon;
        LOGFONT     lfTmp ={0,0,0,0,0,0,0,0,0,0,0,0,0,0};

        switch (msg) {
                case WM_CREATE:
                        lfTmp.lfHeight = 14;
                        lfTmp.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
                        hfontTools = CreateFontIndirect (&lfTmp);
                        if (!hfontTools) {
                                MessageBox (GetFocus(),
                                        "Unable to get an unnamed variable pitch swiss font",
                                        "Tool Bar CreateFont Error",
                                         MB_OK);
                                hfontTools = CreateFont(14, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0,
                                        VARIABLE_PITCH | FF_SWISS, "Arial");
                        }

                        if (!hfontTools) {
                                MessageBox (GetFocus(), "Failed To Create Font", "StatusProc", MB_OK);
                        }
                        hdc = GetDC (hwnd);
                        SelectObject (hdc, hfontTools);
                        GetTextMetrics (hdc, &tmToolFont);
                        ReleaseDC (hwnd, hdc);
                        return DefWindowProc (hwnd, msg, wParam, lParam);

                case WM_DESTROY:
                        if (hfontTools) {
                                DeleteObject (hfontTools);
                        }
                        break;

                case WM_SIZE:
                        UpdatePositions();
                        break;

#if defined (WIN32)
                case WM_CTLCOLORLISTBOX:
                case WM_CTLCOLOREDIT:
                case WM_CTLCOLORSTATIC:
                case WM_CTLCOLORBTN:
                case WM_CTLCOLORDLG:
                case WM_CTLCOLORMSGBOX:
                case WM_CTLCOLORSCROLLBAR:
                        iType   = msg - WM_CTLCOLORMSGBOX;
                        hdc     = (HDC)wParam;
                        hwndCtl = (HWND)lParam;
#else
                case WM_CTLCOLOR:
                        hdc = wParam;
                        hwndCtl = LOWORD(lParam);
                        iType = HIWORD (lParam);
#endif
                        switch (iType) {
                                case CTLCOLOR_EDIT: //Edit control
                                        clrColor = GetSysColor (COLOR_BTNFACE);
                                        hBrush = hbrWindow;
                                        break;

                                case CTLCOLOR_LISTBOX: //List-box control
                                        lStyle = GetWindowLong (hwndCtl, GWL_STYLE);
                                        if (lStyle & CBS_SIMPLE) {
                                                clrColor = GetSysColor (COLOR_WINDOW);
                                                hBrush = hbrWindow;
                                        } else {
                                                clrColor = GetSysColor (COLOR_BTNFACE);
                                                hBrush = hbrBtnFace;
                                        }
                                        break;

                                case CTLCOLOR_STATIC:
                                        clrColor = GetSysColor (COLOR_BTNFACE);
                                        hBrush = hbrBtnFace;
                                        break;

                                case CTLCOLOR_BTN:
                                        clrColor = GetSysColor (COLOR_BTNFACE);
                                        hBrush = hbrBtnFace;
                                        break;

                                case CTLCOLOR_SCROLLBAR:
                                case CTLCOLOR_DLG:
                                case CTLCOLOR_MSGBOX:
                                default:
                                        return DefWindowProc (hwnd, msg, wParam, lParam);
                        }
                        SetBkColor(hdc, clrColor);
                return (LONG)hBrush;

                case WM_PAINT:
                        hdc = BeginPaint (hwnd, &ps);
                        GetClientRect (hwnd, &rect);

                        /* Shade the top of the bar white */
                        hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
                        border = rect;
                        border.bottom = border.top + cyToolBorder;
                        FillRect (hdc, &border, hBrush);
                        DeleteObject (hBrush);

                        /* Shade the bottom of the bar dark gray */
                        hBrush = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
                        border = rect;
                        border.top = border.bottom - cyToolBorder;
                        FillRect (hdc, &border, hBrush);
                        DeleteObject (hBrush);

                        EndPaint (hwnd, &ps);

                        return DefWindowProc (hwnd, msg, wParam, lParam);

                case WM_DRAWITEM: // Indicates that an owner-draw control needs to be redrawn.
                        lpdi = (LPDRAWITEMSTRUCT)lParam;

                        switch (lpdi->itemAction) {
                                // handle normal drawing of button, but check if its selected or focus
                                case ODA_SELECT:
                                case ODA_DRAWENTIRE:
                                        // handle button pressed down select state -- button down bitmap
                                        //   text is right & down 2 pixels
                                                                                hIcon = NULL;
                                        for (i=0; i< cntToolCtrls; i++) {
                                                if (toolCtrl[i].hwnd == lpdi->hwndItem) {
                                                        hIcon = toolCtrl[i].hIcon;
                                                }
                                        }
                                        if (lpdi->itemState & ODS_SELECTED) {
                                                DrawButton (lpdi->hDC,lpdi->rcItem, TRUE, hIcon);
                                        } else { // not selected -- button up; text is in normal position
                                                DrawButton (lpdi->hDC,lpdi->rcItem, FALSE, hIcon);
                                        }
                                        return TRUE;
                        }
                        break;

                case WM_COMMAND:
#if defined (WIN32)
                        idCtrl = LOWORD(wParam);
                        msgCtrl = HIWORD(wParam);
                        hwndCtl = (HWND)lParam;
#else
                        idCtrl = wParam;
                        msgCtrl = HIWORD (lParam);
                        hwndCtl = LOWORD (lParam);
#endif

                        if (GetWindowLong (hwndCtl, GWL_STYLE) & BS_OWNERDRAW) {
                                if (msgCtrl == BN_DOUBLECLICKED) {
                                        PostMessage (hwndCtl, WM_LBUTTONDOWN, 0, 0);
                                        return TRUE;
                                }
                        }
                        PostMessage (hwndMain, msg, wParam, lParam);
                        return DefWindowProc (hwnd, msg, wParam, lParam);

                default:
                        return DefWindowProc (hwnd, msg, wParam, lParam);
        }
        return 0L;
}


README.TXT

Displaying Font Codepoints

The GRIDFONT sample, written in C++, displays a grid containing Unicode
codepoints. The user is able to select a font, and then see which codepoints
are covered by the font.

Note: There is a font distributed with the Win32 SDK which covers more than
one thousand Unicode characters. In order to use this application to its
full potential, you will want to install that font by using Fonts in the
Control Panel. The font is named l_10646.TTF.


APP.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifndef TEXT
// this is non-Unicode Win16 build
#define CHAR   char
#define UCHAR  unsigned char
#define TCHAR  CHAR
#define LPTSTR LPSTR
#define TEXT(x) x
#define COORD   int

#else
#define COORD   LONG
#define HINSTANCE HMODULE
#endif

// Menu item IDs
#define IDM_PRINT       1
#define IDM_EXIT        2
#define IDM_NEXTPAGE    3
#define IDM_PREVPAGE    4
#define IDM_NEXTSECTION 5
#define IDM_PREVSECTION 6
#define IDM_ZOOMIN      7
#define IDM_ZOOMOUT     8
#define IDM_FONT        9
#define IDM_DECIMAL     10
#define IDM_HELP        11
#define IDM_ABOUT       12
#define IDM_ALLPAGES    13

#define IDM_FIRSTPAGE   200  // pseudo menu items
#define IDM_LASTPAGE    201

// String Table IDs
#define IDS_CLASSNAME    1024
#define IDS_MSGCAPTION   1025
#define IDS_UCFONT       1026
#define IDS_ANSIFONT     1027
#define IDS_LABELFONT    1028
#define IDS_HEADERFONT   1029
#define IDS_PRINTERR     1030
#define IDS_NOTIMPLEM    1031

// Manifest Constants
#define DONE TRUE
#define INCH1     1440
#define INCH2      720  // half
#define INCH4      360  // quarter
#define INCH8      180  // eigth
#define INCH10     144  // tenth

// Paper Size: Redefine for non-US
#define PAGEWIDTH  (8*INCH1+INCH2)
#define PAGEHEIGHT (11*INCH1)

#include "resource.h"


BOX.CXX

#include <windows.h>
#include <windowsx.h>
#include "app.h"
#include "box.hxx"

CTextField::CTextField(TCHAR *sz)
{
    _sz = sz;
}

void CTextField::Paint(CCanvas &canvas, int x, int y)
{
    SetTextAlign(canvas, TA_LEFT | TA_BASELINE );
    CFontSelect fs(canvas, _font);
    canvas.Text(x, y, _sz, lstrlen(_sz) );
}

void CTextField::GetExtent(CCanvas &canvas, SIZE *pSize)
{
CFontSelect fs(canvas, _font);
    GetTextExtentPoint(canvas, _sz, lstrlen(_sz), pSize);
}

void CTextField::SetFont(HFONT hfont)
{
    _font = hfont;
}

//-----------------------------------------------
CBoxFormat::CBoxFormat(SIZE sizeChar) :
        _fontAlias(TEXT("Arial Narrow"), -10, FALSE),
#ifdef CHARBITS
        _fontCtype(TEXT("Arial"), 12, TRUE, FALSE, TRUE)
#else
        _fontCtype(TEXT("Arial"), 7, TRUE, FALSE, FALSE)
#endif

{
        _size = sizeChar;
}

//-----------------------------------------------
CBox::CBox( CBoxFormat &bxf, UINT iChar, HFONT hfont ) :
        _fontBlock( hfont ),
        _Block(1, 1, iChar, bxf ),
        _bxf(bxf),
#ifdef CHARBITS
        _Alias(TEXT("Character Type Bits:")),

#else
_Alias(TEXT("Character Decomposition: ")),
#endif
_fuFormat(HEXADECIMAL)
{

    _fontBlock.Update( -40, TRUE);
    _Block.SetFont(_fontBlock);

    _sizeBox.cx = bxf._size.cx+100; // shadow 100/20 poitns
    _sizeBox.cy = bxf._size.cy+100;

#ifdef UNICODE
    _sizeBox.cx += INCH4;
    _sizeBox.cy += INCH4;
#endif

    _Alias.SetFont(bxf._fontAlias);
    _iChar = iChar;
}

void CBox::Paint(CCanvas &canvas, POINT pt, RECT rc)
{
    int x = pt.x;
    int y = pt.y;

#ifdef UNICODE
    // adjust to length of header

    SIZE size;
    _Alias.GetExtent(canvas, &size);
    _sizeBox.cx += size.cx;
#endif
    // make rc large enough
    rc.left = pt.x;
    rc.top = pt.y;
    rc.right = pt.x+_sizeBox.cx-100;
    rc.bottom = pt.y+_sizeBox.cy-100;

    OffsetRect( &rc, 100, 100 );
    FillRect(canvas, &rc, GetStockBrush(GRAY_BRUSH));
    OffsetRect( &rc, -100, -100 );
    FillRect(canvas, &rc, GetStockBrush(WHITE_BRUSH));

#ifdef UNICODE
    CBlackPen pen(canvas, PS_SOLID, 20);
    Rectangle(canvas, rc.left, rc.top, rc.right, rc.bottom);

    pt.x += INCH8;
    pt.y += INCH8;
#endif

    _Block.SetFormat(_fuFormat);
    _Block.Paint(canvas, rc, pt);

#ifdef UNICODE
    _Alias.Paint(canvas, x+=(INCH1-INCH10), y+= INCH4);
    _Alias.GetExtent(canvas, &size);

    pt.x = x;
    pt.y = y+INCH10;

    USHORT uTemp[2]={_iChar, 0};

    SetTextColor(canvas, RGB(0,128,0));
#ifdef CHARBITS

    USHORT uType[2];

    for( int i = 0; i < 3; i++ )
    {
        pt.y += size.cy;

        GetStringTypeW(1<<i, uTemp, 2, uType );
        CCodeGrid Ctype(1,1, size, uType[0]);
        Ctype.SetFont(_bxf._fontCtype);
Ctype.SetFormat(HEXADECIMAL, 4);
        Ctype.Paint(canvas, rc, pt);
    }
#else // ifdef DECOMP
int cch =FoldString(MAP_COMPOSITE, (LPCTSTR) &_iChar, 1, NULL, 0);

LPTSTR pch = new TCHAR[cch];
FoldString(MAP_COMPOSITE, (LPCTSTR) &_iChar, 1, pch, cch);
SIZE sizedecomp;
sizedecomp.cx = _bxf._size.cx/2;
sizedecomp.cy = _bxf._size.cy/2;
    // pt.y += sizedecomp.cy;

CFont fontDecomp(_fontBlock);
fontDecomp.Update(-20, TRUE);

for(int i=0; i < cch; i++ )
{
CCharGrid Decomp(1,1,sizedecomp, pch[i] );
Decomp.SetCharTable();// exclude char images not in font
        Decomp.SetFont(fontDecomp);
        Decomp.Paint(canvas, rc, pt);

CCodeGrid Code(1,1,sizedecomp, pch[i] );
// but don't exclude their code nums
        Code.SetFont(_bxf._fontCtype);
     Code.SetTextOrg(sizedecomp.cx/2, 9*sizedecomp.cy/10);
Code.SetFormat(HEXADECIMAL, 4);
        Code.Paint(canvas, rc, pt);

        pt.x += sizedecomp.cx;
}
delete pch;
#endif
    SetTextColor(canvas, RGB(0,0,0));
#endif
}

/*
*/

UINT CBox::Hittest(CCanvas &canvas, POINT pt)
{
     return 0x00C5;
}


CSET.CXX

#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include "app.h"

#include "cset.hxx"

//+--------------------------------------------------------
// Class:       CSet
//
// Purpose:     Set of characters
//
// History:     03-Aug-1994     asmusf  created
//----------------------------------------------------------

// pure inline

//+--------------------------------------------------------
// Class:       CCTypesSet
//
// Purpose:     Set of chars defined by common CType
//
// History:     03-Aug-1994     asmusf  created
//----------------------------------------------------------

CCTypeSet :: CCTypeSet(WORD fC1Mask, WORD fC2Test, WORD fC3Mask)
{
_fC1Mask = fC1Mask;
_fC2Test = fC2Test;
_fC3Mask = fC3Mask;
GetPageData( 0 );  // Page 0 is ALWAYS the default
}

CCTypeSet :: In(WORD iChar)
{
WORD iPage = iChar & 0xFF00;
if( iPage != _iPage )
{
GetPageData(iPage);
}
return _pCharInSet[iChar &0xFF];
}

void CCTypeSet :: GetPageData(WORD iPage)
{
WORD i;
TCHAR vchTemp[256];
WORD vCtype[256];

for ( i=0; i < 256; i++ )
{
vchTemp[i] = iPage+i;
_pCharInSet[i] = FALSE;
}
if( _fC1Mask )
{
GetStringTypeEx(0,CT_CTYPE1,vchTemp,256, vCtype);
for( i=0; i<256;i++ ){
_pCharInSet[i] = _pCharInSet[i] || (vCtype[i] & _fC1Mask);
}
}
if( _fC2Test )
{
GetStringTypeEx(0,CT_CTYPE2,vchTemp,256, vCtype);
for( i=0; i<256;i++ ){
_pCharInSet[i] = _pCharInSet[i] || (vCtype[i] == _fC2Test);  //enumerated value
}
}
if( _fC3Mask )
{
GetStringTypeEx(0,CT_CTYPE3,vchTemp,256, vCtype);
for( i=0; i<256;i++ ){
_pCharInSet[i] = _pCharInSet[i] || (vCtype[i] & _fC3Mask);
}
}
_iPage = iPage;
}


FONT.CXX

#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <string.h>
#include <memory.h>
#include "app.h"

#include "font.hxx"
//+--------------------------------------------------------
// Class:       CFont
//
// Purpose:     Abstract a font
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CFont::CFont(HFONT hfont)
{
    _hfont = hfont;
    _fDel = FALSE;       // owned elsewhere
}

CFont::CFont(TCHAR *szFace, int iHeight, BOOL fBold, BOOL fItalic, BOOL fUnder)
{
    LOGFONT lf;
    _fDel = FALSE;      // not initialized

    memset(&lf,0,sizeof (LOGFONT));
    // rude but effective: truncate
    if( lstrlen( szFace ) > sizeof(lf.lfFaceName) )
    {
        szFace[sizeof(lf.lfFaceName)] = 0;
    }
    lstrcpy(lf.lfFaceName, szFace);

    lf.lfHeight = iHeight * 20;         // twips internal, points in API

    if( fBold )
    {
        lf.lfWeight = FW_BOLD;
    }
    lf.lfItalic = fItalic;
    lf.lfUnderline = fUnder;
    Create(lf);
}

BOOL CFont::Create(LOGFONT &lf)
{
    if( _fDel )
    {
        DeleteObject(_hfont);
        _fDel = FALSE;
    }
    if( _hfont = CreateFontIndirect(&lf) )
    {
        _fDel = TRUE;
        return TRUE;
    }
    return FALSE;
}

CFont::~CFont()
{
    if( _fDel )
    {
        DeleteObject(_hfont);
    }
}

BOOL CFont::Update(int iHeight, BOOL fBold)
{
    LOGFONT lf;
    GetObject(_hfont, sizeof(LOGFONT), &lf);
    lf.lfWeight = (fBold ? FW_BOLD : FW_NORMAL );

    lf.lfHeight = iHeight * 20;         // twips internal, points in API

    return Create(lf);
}

BOOL CFont::Choose(HWND hwnd)
{
    CHOOSEFONT cf ;
    LOGFONT lf ;

    GetObject(_hfont, sizeof(LOGFONT), &lf);

    // match from TWIPS to LOGPIXELS used in common Dialog
    CScreenCanvas canvas(NULL);
    lf.lfHeight = MulDiv(lf.lfHeight,GetDeviceCaps(canvas, LOGPIXELSY),INCH1);

    lf.lfCharSet = ANSI_CHARSET;

    cf.lStructSize      = sizeof (CHOOSEFONT) ;
    cf.hwndOwner        = hwnd ;
    cf.hDC              = NULL ;
    cf.lpLogFont        = &lf ;
    cf.iPointSize       = 0 ;
    cf.Flags            = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS
                                                 | CF_EFFECTS ;
    cf.rgbColors        = 0L ;
    cf.lCustData        = 0L ;
    cf.lpfnHook         = NULL ;
    cf.lpTemplateName   = NULL ;
    cf.hInstance        = NULL ;
    cf.lpszStyle        = NULL ;
    cf.nFontType        = 0 ;               // Returned from ChooseFont
    cf.nSizeMin         = 0 ;
    cf.nSizeMax         = 0 ;

    if( !ChooseFont (&cf) )
    {
        return FALSE;
    }
//    MessageBox( hwnd, lf.lfFaceName, TEXT("Font Chosen"), MB_OK );

    // adjust back to using TWIPS
    lf.lfHeight = MulDiv(lf.lfHeight,INCH1,GetDeviceCaps(canvas, LOGPIXELSY));
    lf.lfWidth=0;               // font mapper knows best

    // create the font
    return Create(lf);
}


GRID.CXX

#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include "app.h"

#include "grid.hxx"

// These are global variables set by CFontRange::SetFontRange()
// and used by CTextGrid::SetCharTable(). This is safe as only
// one font is selected at a given time by the program.

int             vSegCount;
USHORT *        vStartCount;
USHORT *        vEndCount;

CSet   * vpCSet;

//+--------------------------------------------------------
// Class:       CFontSelect
//
// Purpose:     Select/Deselect a font
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

// pure inline

//+--------------------------------------------------------
// Class:       CGridIt
//
// Purpose:     Iterate over a grid
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

// pure inline

//+--------------------------------------------------------
// Class:       CLineGrid
//
// Purpose:     Create an n x m Grid of lines
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CLineGrid::CLineGrid(UINT cCol, UINT cRow, SIZE size )
{
    _size = size;
    _cCol = cCol;
    _cRow = cRow;
    SetStyle();
    SetWeight();
}

void CLineGrid::SetStyle(int iStyle)
{
    _iStyle = iStyle;
}

void CLineGrid::SetWeight(int nWeight)
{
    _nWeight = nWeight*20;  // internal twips, API is points
}

void CLineGrid::Paint(CCanvas& canvas, RECT rc, POINT pt)
{
    int cx, cy;
    UINT i;

    // set up pen
    CBlackPen pen(canvas, _iStyle, _nWeight);

    // Draw the grid
    for(cx = pt.x, i=0; i<=_cCol; i++, cx+=_size.cx )
    {
        if( cx >= rc.left && cx <= rc.right )
        {
            canvas.Line(cx, pt.y,
                        cx, pt.y+_cRow*_size.cy);
        }
    }
    for(cy = pt.y, i=0; i<=_cRow; i++, cy+=_size.cy )
    {
        if( cy >= rc.top && cy <= rc.bottom )
        {
            canvas.Line(pt.x,                cy,
                        pt.x+_cCol*_size.cx, cy);
        }
    }
}

//+--------------------------------------------------------
// Class:       CTextGrid
//
// Purpose:     Create an n x m grid of textual elements
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

void CTextGrid::SetFont(HFONT hfont)
{
    _font = hfont;
}

void CTextGrid :: Paint(CCanvas& canvas, RECT rc, POINT pt)
{
    // Choose text alignment
    SetTextAlign(HDC(canvas), TA_BASELINE|TA_CENTER);

    CFontSelect fs(canvas, _font);

    for( CGridIt It( _cCol, _cRow, _size, pt ); !It.Done(); ++It)
    {
        if( It.Cx()+_size.cx > rc.left && It.Cx() < rc.right &&
                It.Cy()+_size.cy > rc.top && It.Cy() < rc.bottom )
        {
            DrawElement(canvas, It.Cx()+_ptOrg.x, It.Cy()+_ptOrg.y,
                                It.Col(), It.Row());
        }
    }
}

UINT CTextGrid::Hittest(POINT pt, POINT ptTest)
{
    for( CGridIt It( _cCol, _cRow, _size, pt ); !It.Done(); ++It)
    {
        if( It.Cx() <= ptTest.x && It.Cy() <= ptTest.y  &&
            It.Cx()+_size.cx > ptTest.x && It.Cy() +_size.cy > ptTest.y)
            {
                return   _cRow* It.Col()+ It.Row() + _iEltOffset;
            }
    }
    return 0xFFFF;
}

void CTextGrid::SetCharTable()
{
     USHORT i;
 UINT imin = _iEltOffset;
 UINT imax = imin + _cCol*_cRow - 1;

 // Make sure the block is used by the font
 if (_pCharUsed != NULL)
     LocalFree (LocalHandle(_pCharUsed));
 // Allocate buffer and make it not used (LPTR put zero in memory)
 if ((_pCharUsed = (USHORT *)LocalAlloc(LPTR, _cCol*_cRow*sizeof(USHORT))) == NULL)
     return;
 if (imin > vEndCount[vSegCount-1] || imax < vStartCount[0])
     return;
 for (i = 0; i < vSegCount; i++)
     {
     if (imax < vStartCount[i]) // no more useful cmap ranges
         break;
     if (imin <= vEndCount[i] && imax >= vStartCount[i]) // check for include
     {
         UINT icur = imin;
         while (icur++ < vStartCount[i]);  // scan until reaching vStartCount
 icur--;
 while (icur <= imax && icur <= vEndCount[i])
 _pCharUsed[icur++ - imin] = TRUE;
 }
  }
}

//+--------------------------------------------------------
// Class:       CCharGrid
//
// Purpose:     Create an n x m Grid of single characters
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CCharGrid :: CCharGrid(UINT cCol, UINT cRow, SIZE size, UINT iEltOffset)
{
    _size  = size;
    _cCol  = cCol;
    _cRow  = cRow;
    _iEltOffset = iEltOffset;
    SetTextOrg();
}

#define NBSP 0x00A0// no break space

void CCharGrid::DrawElement(CCanvas& canvas, COORD x, COORD y, UINT i, UINT j)
{
WORD iChar = _cRow*i+j+_iEltOffset;
#ifdef UNICODE
static CCTypeSet Combining(0,0,C3_NONSPACING || C3_DIACRITIC || C3_VOWELMARK);
static CCTypeSet Alpha(C1_ALPHA, 0, 0);
#endif

if ( vpCSet && vpCSet->In(iChar) )
{
    SetTextColor(canvas, RGB(128,0,128));
}
    if (_pCharUsed == NULL || _pCharUsed[_cRow*i+j]) // if char exists draw it
{
#ifdef UNICODE
if( Combining.In(iChar) && !Alpha.In(iChar) )
{
TCHAR pch[2] = { iChar,0 };
CFontSelect fs(canvas, _font);
//SIZE size;
    //GetTextExtentPoint(canvas, pch, 2, &size);
//canvas.Text(x, y, pch, 2 );
canvas.Char(x+_size.cx/4/*+size.cx/2*/,y, iChar);

    // set up pen
    CBlackPen pen(canvas, PS_DOT, 0);
CBrush brush(canvas);

// draw dotted circle
int r = 7*_size.cx/16;
canvas.Circle(x, y-r/2, r);
}
else
{
        canvas.Char(x, y, iChar);
}
#else
canvas.Char(x, y, iChar);
#endif
}
    SetTextColor(canvas, RGB(0,0,0));
}

//+--------------------------------------------------------
// Class:       CCodeGrid
//
// Purpose:     Create an n x m Grid, numbered in sequence
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CCodeGrid :: CCodeGrid(UINT cCol, UINT cRow, SIZE size, UINT iEltOffset)
{
    _size  = size;
    _cCol  = cCol;
    _cRow  = cRow;
    _iEltOffset = iEltOffset;
    SetTextOrg();
    SetFormat();
}

void CCodeGrid :: SetFormat(UINT fuFormat, UINT cDigits)
{
    _cDigits = cDigits;

    _szFormat[0]='%';
    if( fuFormat == DECIMAL )
    {
        _cDigits = 4;       // Constraint:
                            // This format for Decimal
        _szFormat[1]='3';   // really only works with _cDigits == 4
        _szFormat[2]='d';   //
        _szFormat[3]=' ';   // (blank padding for better positioning)
    }
    else //
    {
        _szFormat[1]='0';
        _szFormat[2]=_cDigits%10+'0';
        _szFormat[3]='X';
    }
    _szFormat[4]='\0';
}

void CCodeGrid::DrawElement(CCanvas &canvas, COORD x, COORD y, UINT i, UINT j)
{
     TCHAR sz[10];
 if (_pCharUsed == NULL || _pCharUsed[_cRow*i+j]) // if char exists draw it
         {
         wsprintf( sz,_szFormat, (_cRow*i+j+_iEltOffset));
         canvas.Text(x, y, sz, _cDigits);
 }
}

//+--------------------------------------------------------
// Class:       CCharBlock
//
// Purpose:     Create an n x m lined block of characters and codes
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CCharBlock::CCharBlock(UINT cCol, UINT cRow, UINT iBlockOffset, const CBlockFormat &bf) :
     _Line(cCol, cRow, bf._size),
     _Char(cCol, cRow, bf._size, iBlockOffset),
     _Code(cCol, cRow, bf._size, iBlockOffset)
{
     // LINE GRID
     _Line.SetStyle(PS_SOLID);

     // LARGE CHARACTER each cell
     _Char.SetFont(bf._fontChar);

     // CODE POINT label each cell
     _Code.SetTextOrg(bf._size.cx/2, 9*bf._size.cy/10);
     _Code.SetFont(bf._fontCode);
     _Code.SetFormat(HEXADECIMAL,4);
}

void CCharBlock::Paint(CCanvas& canvas, RECT rc, POINT pt)
{
    _Line.Paint(canvas, rc, pt);
#ifdef UNICODE
    _Char.SetCharTable();
    _Code.SetCharTable();
#endif
    _Char.Paint(canvas, rc, pt);
    _Code.Paint(canvas, rc, pt);
}

UINT CCharBlock::Hittest(POINT pt, POINT ptTest)
{
    return _Code.Hittest(pt, ptTest);
}

void CCharBlock :: SetFormat(UINT fuFormat)
{
    _Code.SetFormat(fuFormat&DECIMAL,4);
}

void CCharBlock::SetFont(HFONT font)
{
    _Char.SetFont(font);
}

//+--------------------------------------------------------
// Class:       CBlockFrame
//
// Purpose:     Create an n x m frame around a block of characters
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CBlockFrame::CBlockFrame(UINT cCol, UINT cRow, POINT pt, UINT iBlockOffset,
                                TCHAR * szHeader, const CFrameFormat &ff):
     CCharBlock(cCol, cRow, iBlockOffset, ff ),
     _size(ff._size),
     _Cols(cCol,    1, ff._size, iBlockOffset/16),
     _cRow(cRow),
     _cCol(cCol)
{

     // COLUMN label above first row
     _Cols.SetFormat(HEXADECIMAL,3);
     _Cols.SetFont(ff._fontLabel);

     // if not first block on page, suppress row labels
     if( iBlockOffset % 0x100 )
     {
        _pRows = NULL;
     }
     else
     {
        // ROW label to left of first column
        _pRows = new CCodeGrid(1, cRow,  ff._size);
        _pRows->SetFont(ff._fontLabel);
     }

     // BLOCK HEADER
     _szHeader = new TCHAR[lstrlen(szHeader)+sizeof(TCHAR)];
     lstrcpy(_szHeader, szHeader);
     _fontHeader = ff._fontHeader;

}

CBlockFrame::~CBlockFrame()
{
    delete _pRows;
    delete _szHeader;
}

void CBlockFrame::Paint(CCanvas& canvas, RECT rc, POINT pt)
{
    Draw(canvas, pt);
    {
        POINT ptCols = {pt.x, pt.y-_size.cy};
        _Cols.Paint(canvas, rc, ptCols);
    }
    CCharBlock::Paint(canvas, rc, pt);

    // these two are optional. Test first, then draw
    if(_pRows )
    {
        POINT ptRows = {pt.x-_size.cx, pt.y};
        _pRows->Paint(canvas, rc, ptRows);
    }
}

void CBlockFrame::Draw(CCanvas& canvas, POINT pt)
{
    UINT dy = 3*_size.cy/2;     // height of short uprights
    UINT cx = _cCol*_size.cx;
    UINT cy = _cRow*_size.cy;
RECT rc;

    // set up pen
    CBlackPen pen(canvas, PS_SOLID, 40);    // Solid Black 40/20 points

    //Draw block divider lines

    canvas.Line(pt.x,    pt.y-dy, pt.x,    pt.y+cy);  // left edge
    canvas.Line(pt.x+cx, pt.y-dy, pt.x+cx, pt.y+cy);  // right edge
    canvas.Line(pt.x,    pt.y,    pt.x+cx, pt.y);     // top edge
    canvas.Line(pt.x,    pt.y+cy, pt.x+cx, pt.y+cy);  // bottom

    CFontSelect fs(canvas, _fontHeader);

    SetTextAlign(canvas, TA_BASELINE|TA_LEFT);
rc.left = pt.x + _size.cx/12;
rc.top = pt.y - 6*_size.cy/5;
rc.right = pt.x + cx - _size.cx/12;
rc.bottom = pt.y;
    canvas.RCText(&rc,
      _szHeader,
                 lstrlen(_szHeader));
}

// The following Array contains the widhts of the Unicode blocks in
// columns. Each Block has a corresponding entry in the stringtable
// giving its block header. "Unassigned" blocks can span page boun-
// daries.

static UINT aBlockWidth[]=
{
    2,6,2,6,   // 0000
    8,8,       // 0100
    5,6,5,     // 0200
    7,6,3,     // 0300
    16,        // 0400
    3,6,7,     // 0500
    16,        // 0600
    32,        // 0700 - 08FF
    8,8,       // 0900
    8,8,       // 0A00
    8,8,       // 0B00
    8,8,       // 0C00
    8,8,       // 0D00
    8,8,       // 0E00
    16,        // 0F00
    10,6,     // 1000
16,   // 1100
    192,       // 1200 - 1DFF
16,   // 1E00
16,   // 1F00
    7,3,3,3,   // 2000
    5,4,7,     // 2100
    16,        // 2200
    16,        // 2300
    4,2,10,    // 2400
    8,2,6,     // 2500
    16,        // 2600
    12,4,      // 2700
    128,       // 2800 - 2FFF
    4,6,6,     // 3000
    3,6,1,6,   // 3100
    16,        // 3200
    16,        // 3300
    144,       // 3400 - 3CFF
    3,13,      // 3D00
    96,        // 3E00 - 43FF
16,   // 4400
144,   // 4500 - 4DFF
    82*16,     // 4E00 - 9FFF
    64*16,     // A000 - DFFF
    25*16,     // E000 - F8FF
    32,        // F900 - FAFF
5,11,   // FB00
    32,        // FC00 - FDFF
    2,5,9,     // FE00
    15,1,      // FF00
    200        // Sentinel
};

//+--------------------------------------------------------
// Class:       CBlockFormat
//
// Purpose:     Block formatting
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CBlockFormat::CBlockFormat() :
    _fontCode    (TEXT("Arial Narrow"), -6),
#ifdef UNICODE
    _fontChar    (TEXT("Lucida Sans Unicode"), -16, TRUE)
#else
    _fontChar    (TEXT("Lucida Sans"), -16, TRUE)
#endif
{
};

//+--------------------------------------------------------
// Class:       CFrameFormat
//
// Purpose:     Frame formatting
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

CFrameFormat::CFrameFormat() :
    _fontHeader  (TEXT("Arial"), -12, TRUE),
    _fontLabel   (TEXT("Arial"), -10, TRUE)
{
}

//+--------------------------------------------------------
// Class:       CPageFormat
//
// Purpose:     Page formatting
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CPageFormat::CPageFormat(UINT fuFormat) :
    _fontPageNum (TEXT("Times New Roman"), -10, FALSE)
{
    _size.cx =  4*INCH2/5;
    _size.cy =  INCH2;

    SetFormat(fuFormat);
}

void CPageFormat::SetFormat(UINT fuFormat)
{
    _fuFormat = fuFormat;

    if( fuFormat & PAGEPRINT )
    {
        _pt.x=     INCH2+(INCH2*7)/10;
        _pt.y=     INCH1+(INCH1*7)/10;
    }
    else
    {
        _pt.x=     INCH2;
        _pt.y=     INCH1;
    }

    // locations of headers / footers
    _ptPE[0].x = _pt.x-_size.cx;
    _ptPE[0].y = _size.cy/2;
    _ptPE[1].x = _pt.x+_size.cx*16;
    _ptPE[1].y = _size.cy/2;
    _ptPE[2].x = _pt.x+(_size.cx*15)/2;
    _ptPE[2].y = _pt.y+(_size.cy*33)/2;
}

//+--------------------------------------------------------
// Class:       CPage
//
// Purpose:     One or more blocks
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CPage::CPage(HINSTANCE hInst, CPageFormat &pf, UINT nPage) :
    _cBlock(0),
    _pf(pf),
    _hInst(hInst),
    _PageHeadL (1, 1, pf._size,nPage*256      ),
    _PageHeadR (1, 1, pf._size,(nPage+1)*256-1),
    _PageNums  (1, 1, pf._size,nPage+1        )
                                // page numbers are 1 based on output
{
    // Set up page elementss

    _PageHeadL.SetFont(pf._fontLabel);
    _PageHeadR.SetFont(pf._fontLabel);
    _PageNums.SetFont(pf._fontPageNum);

    InitPage( nPage);
    SetFormat(_pf._fuFormat);
}

CPage::~CPage()
{
    while( _cBlock )
    {
        delete _apBlock[--_cBlock];
    }
}

void CPage::SetFormat(UINT fuFormat)
{
    // set it
    _pf.SetFormat(fuFormat);

    // apply it
    for( UINT i = 0; i < _cBlock ; ++i )
    {
        _apBlock[i]->SetFormat(fuFormat&MASKROOT);
    }

    _PageHeadL.SetFormat(HEXADECIMAL, 4);
    _PageHeadR.SetFormat(HEXADECIMAL, 4);
    _PageNums.SetFormat(DECIMAL);
}

void CPage::Paint(CCanvas& canvas, RECT rc)
{
    for( UINT i = 0; i < _cBlock; ++i )
    {
        _apBlock[i]->Paint(canvas, rc, _aptBlock[i]);
    }

    if( _pf._fuFormat & PAGEELEMS )
    {
        _PageHeadL.Paint(canvas, rc, _pf._ptPE[0]);
        _PageHeadR.Paint(canvas, rc, _pf._ptPE[1]);
        _PageNums.Paint (canvas, rc, _pf._ptPE[2]);
    }
}

UINT CPage::Hittest(POINT ptTest)
{
    UINT uHit = 0xFFFF;

    for( UINT i = 0; i < _cBlock && uHit ==0xFFFF ; ++i )
    {
        uHit = _apBlock[i]->Hittest(_aptBlock[i], ptTest);
    }
    return uHit;
}

//-- protected member functions...

UINT CPage::InitPage(UINT nPage)
{
    UINT iEnd = 0;
    TCHAR szBlockHeader[40];
    UINT i;
    POINT ptBlock=_pf._pt;

    // Set up blocks

    for( i=0; i < sizeof(aBlockWidth)/sizeof(UINT); i++ )
    {
        if( nPage*16 < (iEnd+=aBlockWidth[i]) )
        {
            break;
        }
    }
    UINT iStart = max(nPage*16, iEnd-aBlockWidth[i]);

    do
    {
        LoadString(_hInst, i, szBlockHeader, 40);
        _apBlock[_cBlock]= new CBlockFrame(
                  min( aBlockWidth[i],      // grid width in columns
                       (nPage+1)*16-iStart),// (but at most to end of page)
                  16,                       // cRow always 16
                  ptBlock,                  // grid origin
                  iStart*16,                // first char offset
                  szBlockHeader,            // header string
                  _pf);                     // common formatting

        _aptBlock[_cBlock] = ptBlock;
        _cBlock++;

        ptBlock.x+=_pf._size.cx*aBlockWidth[i];
        iStart=iEnd;
    }
    while(
            (i++ < sizeof(aBlockWidth)/sizeof(UINT))
                &&
            ((nPage+1)*16 >= (iEnd+=aBlockWidth[i]))
                &&
            (_cBlock < 4)
         );

    return _cBlock;
}

void CPage::SetFont(HFONT hfont)
{
    for( UINT i = 0; i < _cBlock ; ++i )
    {
        _apBlock[i]->SetFont(hfont);
    }
}

//+--------------------------------------------------------
// Class:       CModel
//
// Purpose:     Iterator over pages
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CModel::CModel(HINSTANCE hInst, HWND hwnd, UINT fuFormat, UINT fPageMode) :
    _iPage(0),
#ifdef UNICODE
    _macPage(0x100),
#else
    _macPage(1),
#endif
    _pf(fuFormat),
    _hInst(hInst),
    _fPageMode(fPageMode)
{
    _pPage = new CPage(hInst, _pf, _iPage);
    _fr.SetFontRange(hwnd, _pf._fontChar);
}

CModel::~CModel()
{
    delete _pPage;
}

void CModel::NextPage()
{

    if (_fPageMode == ALLPAGES)
        SetPage(_iPage+=(_iPage+1<_macPage? 1 : 0));
else
    {
   int iNewPage = _iPage;
        while (!_fr.IsPageUsed(++iNewPage) && iNewPage < (int)_macPage);// scan until first found
if (iNewPage < (int)_macPage)   // change only if under boundary
    SetPage(_iPage = (UINT)iNewPage);
}
}

void CModel::PrevPage()
{
    if (_fPageMode == ALLPAGES)
        SetPage(_iPage-=(_iPage? 1 : 0));
else
    {
   int iNewPage = _iPage;
        while (!_fr.IsPageUsed(--iNewPage) && iNewPage >= 0); // scan until first found
if (iNewPage >= 0)                                // change only if above boundary
    SetPage(_iPage = (UINT)iNewPage);
}
}

void CModel::NextSection()
{
_iPage+=(_iPage+16<_macPage? 16 : 0); // increment by 16 pages
    if (_fPageMode == ALLPAGES || _fr.IsPageUsed(_iPage)) // if in USEDONLY mode and
        SetPage(_iPage); // the page is not used
else
NextPage();
} // we skip to the next

void CModel::PrevSection()
{
    _iPage-=(_iPage >= 16 ? 16 : 0);
    if (_fPageMode == ALLPAGES || _fr.IsPageUsed(_iPage)) // if in USEDONLY mode and
        SetPage(_iPage); // the page is not used
else
PrevPage(); // we skip to the previous
}

void CModel::SetPage(UINT nPage)
{
    delete _pPage;
    _pPage = new CPage(_hInst, _pf, nPage);
}

void CModel::GetFormat(UINT &fuFormat)
{
    fuFormat=_pf._fuFormat;
}

void CModel::SetFormat(UINT fuFormat)
{
    _pPage->SetFormat(fuFormat);
}

HFONT CModel::GetFont()
{
    return _pf._fontChar;
}

BOOL CModel::CreateFont(LOGFONT &lf)
{
    if(_pf._fontChar.Create(lf))
    {
         _pPage->SetFont(_pf._fontChar);
         return TRUE;
    }
    return FALSE;
}

BOOL CModel::ChooseFont(HWND hwnd)
{
    if(_pf._fontChar.Choose(hwnd))
    {
 _fr.SetFontRange(hwnd, _pf._fontChar);
         _pPage->SetFont(_pf._fontChar);
         return TRUE;
    }
    return FALSE;
}

UINT CModel::Hittest( POINT pt)
{
    return _pPage->Hittest( pt);
}

void CModel::GetPageMode(UINT &fPageMode)
{
    fPageMode = _fPageMode;
}

void CModel::SetPageMode(UINT fPageMode)
{
    _fPageMode = fPageMode;
if (fPageMode == USEDONLY && !_fr.IsPageUsed(_iPage))//if the current page is empty
    {
UINT iCurrentPage = _iPage;
NextPage();//we skip to next
if (iCurrentPage == _iPage)//if there is no next page
    PrevPage();//we go to prev (has to be one)
}
}

void CModel :: SetCSet(CSet * pCSet)
{
vpCSet = pCSet;
}

//+----------------------------------------------------------------------
// ClassCFontRange
//
// Purpose:Set character coverage of the target font
//
// History:6-Dec-1993michelsu created
//-----------------------------------------------------------------------
void CFontRange::SetFontRange(HWND hwnd, HFONT hfont)
{
    CScreenCanvas scanvas(hwnd);     // provide the DC
    CFontSelect fs(scanvas, hfont);

    USHORT   i;

    for (i=0;i<256;i++)
        _fPageUsed[i]=FALSE;         // assume no page used
#ifdef UNICODE
    if (CountUCSegments(scanvas))    // set page used flag for relevant pages
        for (i=0;i<vSegCount;i++) _fPageUsed[(vStartCount[i] & 0xFF00) >> 8] = TRUE;
    else                             // if the font is not TrueType, set only the 1st page
        _fPageUsed[0] =TRUE;
#else
    _fPageUsed[0] =TRUE;
#endif
}

CFontRange::~CFontRange()
{
    if (vStartCount != NULL)
        LocalFree (LocalHandle (vStartCount));
    if (vEndCount != NULL)
        LocalFree (LocalHandle (vEndCount));
}
//+----------------------------------------------------------------------
// ClassCFontRange
//
// Purpose:Get character coverage of the target font (_pf._fontChar)
//
// History:6-Dec-1993michelsu created (borrowed in large from TTFONTS)
//-----------------------------------------------------------------------
BOOL CFontRange::CountUCSegments(HDC hdc)
{
#define CMAPHEX0x70616d63 // = "cmap" (reversed)
#define NBYTES   256
#define OFFSETERROR 0
#define MBERROR TEXT("Application Error.")
#define MBERRORFLAGS MB_OK | MB_ICONHAND

typedef struct tagTABLE{
    USHORT platformID;
    USHORT encodingID;
    ULONG  offset;
} TABLE, *PTABLE;

typedef struct tagSUBTABLE{
    USHORT format;
    USHORT length;
    USHORT version;
    USHORT segCountX2;
    USHORT searchRange;
    USHORT entrySelector;
    USHORT rangeShift;
} SUBTABLE, *PSUBTABLE;

    DWORD       cbData;
    USHORT      aShort[2];
    DWORD       nBytes;
    USHORT      i, nTables;
    PTABLE      pTable;
    PSUBTABLE   pSubTable;
    ULONG       offset,offsetFormat4;
    BYTE        buffer[NBYTES];

    // find number of encoding tables, second long in cmap
    nBytes = GetFontData(hdc, CMAPHEX, 0, aShort, 4);
    if (nBytes == GDI_ERROR || nBytes == 0)//GDI error or no cmap table
        return FALSE;
    nTables = aShort[1];
    SwapShort (&nTables);

    // limit ourself to 32 encoding tables (largely enough)
    cbData = nTables * sizeof(TABLE);
    if (cbData >NBYTES)
    {
    MessageBox (NULL, TEXT("cbData >NBYTES"),MBERROR , MBERRORFLAGS);
    return FALSE;
    }

    // get array of encoding tables.
    // Check each one for PlatformId = 3, Encoding ID = 1.
    nBytes=GetFontData (hdc, CMAPHEX, 4, buffer, cbData);
    pTable = (PTABLE)buffer;
    offsetFormat4 = OFFSETERROR;
    for (i = 0; i< nTables; i++)
    {
        SwapShort (&(pTable->encodingID));
        SwapShort (&(pTable->platformID));
        if ((pTable->platformID == 3)&&(pTable->encodingID == 1))
            {
            offsetFormat4 = pTable->offset;
            SwapULong (&offsetFormat4);
            break;
            }
        pTable++;
        }
    if (offsetFormat4 == OFFSETERROR) //Can not find 3,1 subtable
        return FALSE;

    /* Get the beginning of the subtable, especially the segment count */
    nBytes=GetFontData (hdc, CMAPHEX, offsetFormat4, buffer, sizeof(SUBTABLE));
    pSubTable = (PSUBTABLE) buffer;
    SwapShort (&(pSubTable->format));
    SwapShort (&(pSubTable->segCountX2));
    if (pSubTable->format != 4)
        {
        MessageBox (NULL, TEXT("format !=4"), MBERROR, MBERRORFLAGS);
        return FALSE;
        }
    vSegCount = pSubTable->segCountX2 / 2;

    /* Now that we know how many segments that the font contains,
     *  free up the old memory, and realloc. the two global arrays.
     */
    if (vStartCount != NULL)
        LocalFree (LocalHandle (vStartCount));
    if (vEndCount != NULL)
        LocalFree (LocalHandle (vEndCount));
    vStartCount = (USHORT *)LocalAlloc (LPTR, vSegCount * sizeof(USHORT));
    vEndCount = (USHORT *)LocalAlloc (LPTR, vSegCount * sizeof(USHORT));
    if ((vStartCount == NULL) || (vEndCount == NULL))
        {
        MessageBox (NULL, TEXT("LocalAlloc failed"), MBERROR, MBERRORFLAGS);
        return FALSE;
        }

    /* read in the array of endCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT));  /* skip constant # bytes in subtable */
    cbData = vSegCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, vEndCount, cbData );
    for (i = 0; i<vSegCount; i++)
        SwapShort (& (vEndCount[i]));

    /* read in the array of startCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT))   /* skip constant # bytes in subtable */
           + (vSegCount * sizeof (USHORT)) /* skip endCount array */
           + sizeof (USHORT);             /* skip reservedPad */
    cbData = vSegCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, vStartCount, cbData );
    for (i = 0; i<vSegCount; i++)
        SwapShort (& (vStartCount[i]));

    return TRUE;
}

void CFontRange::SwapShort (PUSHORT p)
{
SHORT temp;

    temp =(SHORT)( HIBYTE (*p) + (LOBYTE(*p) << 8));
    *p = temp;
}

void CFontRange::SwapULong (PULONG p)
{
ULONG temp;

    temp = (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    *p = temp;
}


APP.CXX

//+--------------------------------------------------------
// File:        App.cxx
//
// Classes:     CController
//
// Functions:   WinMain
//              WndProc
//              MakeWindowClass
//              AboutDlgProc
//PropDlgProc
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

#include <windows.h>
#include <windowsx.h>
#include "app.hxx"

// global for use by PropDlgProc
CCTypeSet * pCSet = NULL;

//+--------------------------------------------------------
// Class:       CController
//
// Purpose:     Controlling the application, UI handler
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

void CController::Create(HWND hwnd, LONG lParam )
{
    HANDLE handle = ((LPCREATESTRUCT) lParam)->hInstance;
    _hInst = (HINSTANCE)handle;
    _funAbout = (DLGPROC) MakeProcInstance((FARPROC)AboutDlgProc, _hInst);
_funProp = (DLGPROC) MakeProcInstance((FARPROC) PropDlgProc, _hInst );

    // Set up the Main View
    _pView = new CScrollableView(((LPCREATESTRUCT) lParam)->cx,
                                ((LPCREATESTRUCT) lParam)->cy);
    _pView->SetScale(100);

    SetScrollRange(hwnd, SB_VERT, 0, PAGEHEIGHT, FALSE);
    SetScrollRange(hwnd, SB_HORZ, 0, PAGEWIDTH, FALSE);

    SetScrollPos(hwnd, SB_HORZ, 0, TRUE );
    SetScrollPos(hwnd, SB_VERT, 0, TRUE );

    // Set up the Model
    _pModel= new CModel(_hInst, hwnd, HEXADECIMAL, USEDONLY);

    // Set up the initial state of the Menu
    Page(hwnd, IDM_FIRSTPAGE);

    // initially no pop-up character box
    _pBox = 0;
}

void CController::Destroy()
{

    FreeProcInstance((FARPROC)AboutDlgProc);

    delete _pView;
    delete _pModel;
    PostQuitMessage(0);
}

void CController::Size ( LONG lParam )
{
   _pView->SetSize ( LOWORD(lParam), HIWORD(lParam) );
}

// Message Box with text from resource file
void CController::AlertBox( HWND hwnd, UINT idsText, UINT fuStyle)
{
    TCHAR szText[100];
    TCHAR szCaption[100];

    LoadString(_hInst, idsText, (LPTSTR)szText, 100);
    LoadString(_hInst, IDS_MSGCAPTION, (LPTSTR)szCaption, 100);

    ::MessageBox (hwnd, (LPTSTR)szText, (LPTSTR)szCaption, fuStyle) ;
};

// Menu commands processing

void CController::Command ( HWND hwnd, WPARAM wID )
{
    switch ( wID )
    {
      // File Menu
      case IDM_PRINT:
           {
                CPrintRequest job(hwnd, 1, _pModel->GetMaxPage());

                if(!job.Cancelled() && !job.Error())
                {
                    CPrintCanvas canvas (job);
                    job.Print (_hInst, canvas, _pModel);

                    if(job.Error())
                    {
                        AlertBox (hwnd, IDS_PRINTERR, MB_OK | MB_ICONEXCLAMATION) ;
                    }
                }
           }
           break;
      case IDM_EXIT:
           SendMessage ( hwnd, WM_CLOSE, 0L, 0L );
           break;

      // View Menu
      case IDM_NEXTPAGE:
           Page(hwnd, IDM_NEXTPAGE);
           break;
      case IDM_PREVPAGE:
           Page(hwnd, IDM_PREVPAGE);
           break;
      case IDM_NEXTSECTION:
           Page(hwnd, IDM_NEXTSECTION);
           break;
      case IDM_PREVSECTION:
           Page(hwnd, IDM_PREVSECTION);
           break;
      case IDM_ZOOMIN:
           {
               UINT iScale;
               _pView->GetScale(iScale);
               iScale -= (iScale > 50 ? 25 : 0);
               _pView->SetScale(iScale);
               _pView->Invalidate(hwnd);
           }
           break;
      case IDM_ZOOMOUT:
           {
               UINT iScale;
               _pView->GetScale(iScale);
               iScale += (iScale < 200 ? 25 : 0);
               _pView->SetScale(iScale);
               _pView->Invalidate(hwnd);
           }
           break;

      // Options Menu
      case IDM_FONT:
           _pModel->ChooseFont(hwnd);
           _pView->Invalidate(hwnd);
           break;
     case IDM_DECIMAL:
           {
               UINT fuFormat;
               _pModel->GetFormat(fuFormat);
               fuFormat ^= DECIMAL;
               CheckMenuItem (GetMenu(hwnd), IDM_DECIMAL,
                        fuFormat & DECIMAL ? MF_CHECKED : MF_UNCHECKED ) ;
              _pModel->SetFormat(fuFormat);
           }
           _pView->Invalidate(hwnd);
           break;
      case IDM_ALLPAGES:
           {
               UINT fPageMode;
               _pModel->GetPageMode(fPageMode);
               fPageMode ^= ALLPAGES;
               CheckMenuItem (GetMenu(hwnd), IDM_ALLPAGES,
                        fPageMode & ALLPAGES ? MF_CHECKED : MF_UNCHECKED ) ;
               _pModel->SetPageMode(fPageMode);
           }
           _pView->Invalidate(hwnd);
           break;

  case IDM_SHOWPROP:
     {
   DialogBox( _hInst, TEXT("PROPERTIES"),hwnd, _funProp );
   _pModel->SetCSet(pCSet);
   }
           _pView->Invalidate(hwnd);
   break;

      // Help Menu
      case IDM_HELP:
           AlertBox ( hwnd, IDS_NOTIMPLEM, MB_ICONINFORMATION | MB_OK);
           break;
      case IDM_ABOUT:
           DialogBox ( _hInst, TEXT("AboutBox"), hwnd, _funAbout );
           break;
     }
}

void CController::Page(HWND hwnd, WPARAM wParam)
{
    UINT iPage = _pModel->GetPage();
    switch (wParam)
    {
    case IDM_PREVPAGE:
        _pModel->PrevPage();
        break;
    case IDM_NEXTPAGE:
        _pModel->NextPage();
        break;
    case IDM_NEXTSECTION:
        _pModel->NextSection();
        break;
    case IDM_PREVSECTION:
        _pModel->PrevSection();
        break;
    case IDM_FIRSTPAGE:
        _pModel->SetPage( 0 );
        break;
    case IDM_LASTPAGE:
        _pModel->SetPage( _pModel->GetMaxPage() -1 );
        break;
    }
    if (iPage != _pModel->GetPage())
    {
        _pView->Invalidate(hwnd);
    }
    HMENU hmenu = GetMenu(hwnd);

    EnableMenuItem (hmenu, IDM_PREVPAGE, _pModel->CanPrevPage()  ?
                      MF_ENABLED : MF_DISABLED | MF_GRAYED ) ;
    EnableMenuItem (hmenu, IDM_NEXTPAGE, _pModel->CanNextPage() ?
                      MF_ENABLED : MF_DISABLED | MF_GRAYED ) ;
    EnableMenuItem (hmenu, IDM_NEXTSECTION, _pModel->CanNextSection() ?
                      MF_ENABLED : MF_DISABLED | MF_GRAYED ) ;
    EnableMenuItem (hmenu, IDM_PREVSECTION,_pModel->CanPrevSection() ?
                      MF_ENABLED : MF_DISABLED | MF_GRAYED ) ;
}

void CController::ButtonDown(HWND hwnd, LONG lParam )
{
    static RECT rc;
    SetCapture (hwnd);

// second mouse click removes pop-up box
    if( _pBox )
    {
        delete _pBox;
        _pBox = 0;
_pView->Invalidate(hwnd, &rc);
        return;
    }

// locate character selected
    CScreenCanvas canvas(hwnd);

    POINT pt = {LOWORD(lParam), HIWORD(lParam)};

    UINT iChar = _pView->Hittest(canvas, pt, _pModel);

    if( iChar == 0xFFFF )
    {
        return;  // no char selected
    }

// create the pop-up box
    SIZE size = { 4*(INCH1-INCH8)/5, INCH1};
    CBoxFormat bxf(size);

    HFONT hfont = _pModel->GetFont();
    _pBox = new CBox(bxf, iChar, hfont);

// match current format
    UINT fuFormat;
_pModel->GetFormat(fuFormat);
_pBox->SetFormat(fuFormat & DECIMAL);

    canvas.DPtoLP(&pt);
    pt.x -= size.cx/2;
    pt.y -= size.cy/2;
#ifdef UNICODE
    pt.x -= size.cx; // accommodate wider popup
#endif

    GetClientRect(hwnd, &rc);

// show it
    _pBox->Paint(canvas, pt, rc);

//--- dead code....
    size = _pBox->GetSize();

POINT ptSize;
ptSize.x = size.cx;
ptSize.y = size.cy;
canvas.LPtoDP(&ptSize);
pt.x+=ptSize.x;
pt.y+=ptSize.y;
}

void CController::ButtonUp(HWND hwnd, LONG lParam )
{
    ReleaseCapture();
}

void CController::KeyDown(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    switch( wParam )
    {
    case VK_PRIOR:
         Page(hwnd, IDM_PREVPAGE);
         break;
    case VK_NEXT:
         Page(hwnd, IDM_NEXTPAGE);
         break;
    case VK_HOME:
         Page(hwnd, IDM_FIRSTPAGE);
         break;
    case VK_END:
         Page(hwnd, IDM_LASTPAGE);
         break;
    case VK_UP:
         _pView->SetVScrollPos(hwnd, SB_LINEUP, lParam, _pModel);
         break;
    case VK_DOWN:
         _pView->SetVScrollPos(hwnd, SB_LINEDOWN, lParam, _pModel);
         break;
    case VK_LEFT:
         _pView->SetHScrollPos(hwnd, SB_LINEUP, lParam, _pModel);
         break;
    case VK_RIGHT:
         _pView->SetHScrollPos(hwnd, SB_LINEDOWN, lParam, _pModel);
         break;
    }
}

void CController::KeyUp(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
}

void CController::HScroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (_pBox)
{
// Remove box by simulating second mouse click
ButtonDown(hwnd, 0);
UpdateWindow(hwnd);
}
    _pView->SetHScrollPos(hwnd, wParam, lParam, _pModel);
}

void CController::VScroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (_pBox)
{
// Remove box by simulating second mouse click
ButtonDown(hwnd, 0);
UpdateWindow(hwnd);
}
    _pView->SetVScrollPos(hwnd, wParam, lParam, _pModel);
}

// Main application

// WinMain - Main window funtion
//
int WINAPI WinMain
   ( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdParam, int cmdShow )
{
    TCHAR  szAppName [] = TEXT("Grid") ;
    TCHAR  szCaption [] = TEXT("Character Grid") ;

    // Create Window Class

    if ( hPrevInst == 0 )
    {
        MakeWindowClass ( WndProc, szAppName, hInst );
    }

    // Create Window

    CWindow win ( szCaption, szAppName, hInst );

    win.Show ( cmdShow );

    MSG  msg;

    while ( GetMessage (&msg, NULL, 0, 0 ) )
    {
        TranslateMessage ( &msg );
        DispatchMessage ( &msg );
    }

    return msg.wParam;
}

// Make Window Class
//
void MakeWindowClass ( WNDPROC WndProc, LPTSTR szAppName, HINSTANCE hInst )
{
    WNDCLASS cl;

    cl.style = CS_HREDRAW | CS_VREDRAW;
    cl.lpfnWndProc = WndProc;
    cl.cbClsExtra = 0;
    cl.cbWndExtra = 0;
    cl.hInstance = hInst;
    cl.hIcon = LoadIcon ( hInst, szAppName );
    cl.hCursor = LoadCursor ( NULL, IDC_ARROW );
    cl.hbrBackground = GetStockBrush ( WHITE_BRUSH );
    cl.lpszMenuName = szAppName;
    cl.lpszClassName = szAppName;

    RegisterClass (&cl);
}

// Window Proc
//

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    static CController ctrl;

    switch (message)
    {
        case WM_CREATE:
            ctrl.Create(hwnd, lParam);
            return 0;
        case WM_SIZE:
            ctrl.Size(lParam);
            return 0;
        case WM_PAINT:
            ctrl.Paint(hwnd);
            return 0;
        case WM_COMMAND:
            ctrl.Command(hwnd, wParam);
            return 0;
        case WM_LBUTTONUP:
            ctrl.ButtonUp(hwnd, lParam);
            return 0;
        case WM_LBUTTONDOWN:
            ctrl.ButtonDown(hwnd, lParam);
            return 0;
        case WM_KEYUP:
            ctrl.KeyUp(hwnd, wParam, lParam);
            return 0;
        case WM_KEYDOWN:
            ctrl.KeyDown(hwnd, wParam, lParam);
            return 0;
        case WM_VSCROLL:
            ctrl.VScroll(hwnd, wParam, lParam );
            return 0;
        case WM_HSCROLL:
            ctrl.HScroll(hwnd, wParam, lParam );
            return 0;
        case WM_DESTROY:
            ctrl.Destroy();
            return 0;

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

// "About" dialog box procedure
// Process messages from dialog box
//

BOOL CALLBACK AboutDlgProc
   ( HWND hwnd, UINT message, WPARAM wParam, LONG lParam )
{

    switch(message)
    {
       case WM_INITDIALOG:
            return TRUE;
       case WM_COMMAND:
            switch (wParam) //Command ID
            {
               case IDOK:
               case IDCANCEL:
                    EndDialog(hwnd, 0);
                    return TRUE;
            }
            break;
    }
    return FALSE;
}

extern CPrintAux PrGlobal;

//======== PrintDlgProc ===========================================

BOOL CALLBACK PrintDlgProc
   ( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch (message)
    {
        case WM_INITDIALOG:
             SetWindowText (hwndDlg, (TCHAR *)lParam) ;
             EnableMenuItem (GetSystemMenu (hwndDlg, FALSE), SC_CLOSE, MF_GRAYED) ;
             return TRUE ;

        case WM_COMMAND:
             PrGlobal._bUserAbort = TRUE ;
             EnableWindow (GetParent (hwndDlg), TRUE) ;
             DestroyWindow (hwndDlg) ;
             PrGlobal._hDlgPrint = 0 ;
             return TRUE ;
    }
    return FALSE ;
}

//======== AbortProc ===========================================

BOOL CALLBACK AbortProc(HDC hdcPrn, short nCode)
{
    MSG   msg ;

    while (!PrGlobal._bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (!PrGlobal._hDlgPrint
                ||
            !IsDialogMessage (PrGlobal._hDlgPrint, &msg))
        {
            TranslateMessage (&msg) ;
            DispatchMessage (&msg) ;
        }
    }
    return !PrGlobal._bUserAbort ;
}

//======== PropDlgProc ===========================================
struct t_Ctype
{
WORD fVal[3];
LPTSTRpszName;
} vCtype [] =  {
 0x0001,0,0, TEXT("UPPER "),  // A
 0x0002,0,0, TEXT("LOWER "),  //a
 0x0004,0,0, TEXT("DIGIT "),  //1
 0x0008,0,0, TEXT("SPACE "),  //_
 0x0010,0,0, TEXT("PUNCT "),  //!
 0x0020,0,0, TEXT("CNTRL "),
 0x0040,0,0, TEXT("BLANK "),
 0x0080,0,0, TEXT("XDIGIT "),//x
 0x0100,0,0, TEXT("ALPHA "),  //alpha

 0, 0x0001,0, TEXT("LEFTTORIGHT "), //>
 0, 0x0002,0, TEXT("RIGHTTOLEFT "), //<
 0, 0x0003,0, TEXT("EUROPENUMBER "),
 0, 0x0004,0, TEXT("EUROPESEPARATOR "),
 0, 0x0005,0, TEXT("EUROPETERMINATOR "),
 0, 0x0006,0, TEXT("ARABICNUMBER "),
 0, 0x0007,0, TEXT("COMMONSEPARATOR "),
 0, 0x0008,0, TEXT("BLOCKSEPARATOR "),
 0, 0x0009,0, TEXT("SEGMENTSEPARATOR "),
 0, 0x000a,0, TEXT("WHITESPACE "),  // _
 0, 0x000b,0, TEXT("OTHERNEUTRAL "),  //<>

 0,0,0x0001, TEXT("NONSPACING "),
 0,0,0x0002, TEXT("DIACRITIC "),  //  "
 0,0,0x0004, TEXT("VOWELMARK "),
 0,0,0x0008, TEXT("SYMBOL "),  //  =
 0,0,0x0010, TEXT("KATAKANA "),  //  katakana a
 0,0,0x0020, TEXT("HIRAGANA "),  //  hiragana a
 0,0,0x0040, TEXT("HALFWIDTH "),  //  han
 0,0,0x0080, TEXT("FULLWIDTH "),  //  zen
 0,0,0x0100, TEXT("IDEOGRAPH "),  //  kan
 0,0,0x0200, TEXT("KASHIDA "),
 0,0,0x0400, TEXT("LEXICAL "),
 0,0,0x8000, TEXT("C3ALPHA ")//  alpha
};

BOOL CALLBACK PropDlgProc
   ( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
int i;
    switch (message)
    {
       case WM_INITDIALOG:
   for( i=0; i<sizeof vCtype/sizeof t_Ctype; i++)
{
   SendDlgItemMessage(hwndDlg, IDC_PROPLIST, LB_ADDSTRING, i, (LPARAM) vCtype[i].pszName);
}
            return TRUE;
       case WM_COMMAND:
            switch (wParam) //Command ID
            {
               case IDOK:
   if( pCSet )
   {
delete pCSet;
}
   i = SendDlgItemMessage(hwndDlg, IDC_PROPLIST, LB_GETCARETINDEX, 0, 0);
   pCSet = new CCTypeSet(vCtype[i].fVal[0],vCtype[i].fVal[1], vCtype[i].fVal[2]);
                    EndDialog(hwndDlg, 0);
return TRUE;
               case IDCANCEL:
   if( pCSet )
{
delete pCSet;
}
                    EndDialog(hwndDlg, 0);
                    return TRUE;
            }
            break;
    }
    return FALSE ;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by APP.RC
//
#define IDC_PROPLIST                    1000
#define IDM_SHOWPROP                    40001
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40002
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


VIEW.CXX

//--------------------------------------------------------------------
// File: View.Cxx
//
// Classes:
//      CClientCanvas - Device context for client area
//      CPrintCanvas  - Device context for printing
//
//      CView          - Base class for Views
//      CScrollaleView - Scrollable View
//      CFullPageView  - NonScrollable FullPage View
//
// History: 22-Jan-1993 Asmusf  Created
//
// Copyright 1993 - 1998 Microsoft Corp. All Rights reserved
//
//--------------------------------------------------------------------
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include "app.h"
#include "view.hxx"
#include "grid.hxx"

//+--------------------------------------------------------
// Class:       CView
//
// Purpose:     Standard fixed View for printing, or base class
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CView::CView(int cx, int cy)
{
        _iScale = 100;        // percentage magnification
        _ptOrg.x = 0;         // 0..(PAGEWIDTH - widht of window)
        _ptOrg.y = 0;         // 0..(PAGEHEIGHT - height of window)
        _ptScroll.x = 0;      // 0..PAGEWIDTH
        _ptScroll.y = 0;      // 0..PAGEHEIGHT
        _size.cx = cx;        // width of window (dev. coord)
        _size.cy = cy;        // widht of window (dev. coord)
}

void CView::SetSize( int cx, int cy)
{
        _size.cx = cx;
        _size.cy = cy;
}

void CView::Invalidate(HWND hwnd, LPRECT lpRect)
{
        InvalidateRect(hwnd, lpRect, TRUE);
}

BOOL CView::Paint(CCanvas& canvas, CModel * _pModel, RECT rc)
{
        // Scale using MapMode = logical TWIPS
        canvas.Scale(_iScale);

        // Client rect in logical coordinates
        _ptClient.x=_size.cx;
        _ptClient.y=_size.cy;
        canvas.DPtoLP(&_ptClient);

        // Adjust GDIwindow origin, set _ptOrg
        Scroll(canvas);

        // ClipRectangle in logical coordinates
        POINT pt;
        pt.x = rc.left; pt.y = rc.top;
        canvas.DPtoLP(&pt);
        rc.left = pt.x; rc.top = pt.y;

        pt.x = rc.right; pt.y = rc.bottom;
        canvas.DPtoLP(&pt);
        rc.right = pt.x; rc.bottom = pt.y;

        // Paint
        _pModel->Paint(canvas, rc);

        return TRUE;  // screen Paints don't fail
}

UINT CView::Hittest(CCanvas& canvas, POINT ptTest, CModel * _pModel)
{
        // Scale using MapMode = logical TWIPS
        canvas.Scale(_iScale);

        // Client rect in logical coordinates
        _ptClient.x=_size.cx;
        _ptClient.y=_size.cy;
        canvas.DPtoLP(&_ptClient);

        // Adjust GDIwindow origin, set _ptOrg
        Scroll(canvas);

        // Hittest

        canvas.DPtoLP(&ptTest);
        return _pModel->Hittest( ptTest);
}

void CView::Scroll(CCanvas &canvas)
{
        int dx=MulDiv(_ptClient.x,_ptScroll.x,PAGEWIDTH);
        int dy=MulDiv(_ptClient.y,_ptScroll.y,PAGEHEIGHT);

        // Adjust GDIwindow origin, set _ptOrg
        _ptOrg.x = _ptScroll.x-dx;
        _ptOrg.y = _ptScroll.y-dy;

        canvas.Scroll(_ptOrg.x, _ptOrg.y);
}

//+--------------------------------------------------------
// Class:       CScrollableView
//
// Purpose:     View for scrollable window
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
void CScrollableView::SetHScrollPos(HWND hwnd, WPARAM wParam, LPARAM lParam, CModel * pModel)
{
    CScreenCanvas canvas(hwnd);
    int nScrollPos = GetScrollPos( hwnd, SB_HORZ );
    canvas.Scale(_iScale);

    SIZE size;
    pModel->GetLineSize(&size);

    // scroll position relative to page dimension
    switch (wParam)
    {
    case SB_LINEUP:
         nScrollPos -= 2 * size.cx;
         break;
    case SB_LINEDOWN:
         nScrollPos += 2 * size.cx;
         break;

    case SB_PAGEUP:
         nScrollPos -= _ptClient.x; // size of Window in log coord
         break;

    case SB_PAGEDOWN:
         nScrollPos += _ptClient.x;
         break;

    case SB_THUMBPOSITION:
         nScrollPos = LOWORD(lParam);
         break;

    default:
         break;
    }

    nScrollPos = max( 0, min (nScrollPos, PAGEWIDTH));

    POINT pt;
    pt.x =  GetScrollPos( hwnd, SB_HORZ ) - nScrollPos;
    if( pt.x )
    {
        SetScrollPos(hwnd, SB_HORZ, nScrollPos, TRUE );

        pt.y = 0;
        canvas.LPtoDP(&pt);

        pt.x -= MulDiv( _ptClient.x, pt.x, PAGEWIDTH);
        ScrollWindow(hwnd, pt.x, pt.y, NULL, NULL );
    }
    _ptScroll.x = nScrollPos;
}

void CScrollableView::SetVScrollPos(HWND hwnd, WPARAM wParam, LPARAM lParam, CModel * pModel)
{
    CScreenCanvas canvas(hwnd);
    int nScrollPos = GetScrollPos( hwnd, SB_VERT );
    canvas.Scale(_iScale);

    SIZE size;
    pModel->GetLineSize(&size);

    // scroll position relative to page dimension
    switch (wParam)
    {
    case SB_LINEUP:
         nScrollPos -= 2 * size.cy;
         break;
    case SB_LINEDOWN:
         nScrollPos += 2 * size.cy;
         break;

    case SB_PAGEUP:
         nScrollPos -= _ptClient.y; // size of Window in log coord
         break;

    case SB_PAGEDOWN:
         nScrollPos += _ptClient.y;
         break;

    case SB_THUMBPOSITION:
         nScrollPos = LOWORD(lParam);
         break;

    default:
         break;
    }

    nScrollPos = max( 0, min (nScrollPos, PAGEHEIGHT));

    POINT pt;
    pt.y =  GetScrollPos( hwnd, SB_VERT ) - nScrollPos;
    if( pt.y )
    {
        SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE );

        pt.x = 0;
        canvas.LPtoDP(&pt);

        pt.y -= MulDiv( _ptClient.y, pt.y, PAGEHEIGHT);
        ScrollWindow(hwnd, pt.x, pt.y, NULL, NULL );
    }
    _ptScroll.y = nScrollPos;
}

//+--------------------------------------------------------
// Class:       CPrintCanvas
//
// Purpose:     Device Context for Printing
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------

// fully inline //

//+--------------------------------------------------------
// Class:       CPrintRequest
//
// Purpose:     Print Job selection and parameters
//
// History:     22-Jan-1993     asmusf  created
//----------------------------------------------------------
CPrintAux PrGlobal;     // global flags for printing control

CPrintRequest::CPrintRequest(HWND hwnd, UINT nMinPage, UINT nMaxPage ) :
        _hwnd(hwnd),
        _hdc(0),
        _hDevNames(0)
{
    PRINTDLG pd;

    pd.lStructSize= sizeof(PRINTDLG);
    pd.hwndOwner=NULL;
    pd.hDevMode=NULL;
    pd.hDevNames=NULL;
    pd.hDC;
    pd.nFromPage = 0xFFFF; // cause initial blank field
    pd.nToPage   = 0xFFFF; // in dialog
    pd.nMinPage=nMinPage;
    pd.nMaxPage=nMaxPage;
    pd.nCopies=1;
    pd.hInstance=NULL;
    pd.lCustData=0L;
    pd.lpfnPrintHook=NULL;
    pd.lpfnSetupHook=NULL;
    pd.lpPrintTemplateName=NULL;
    pd.lpSetupTemplateName=NULL;
    pd.hPrintTemplate=NULL;
    pd.hSetupTemplate=NULL;

    pd.Flags = PD_RETURNDEFAULT;

    if( PrintDlg(&pd) )
    {
        pd.Flags = PD_SELECTION | PD_HIDEPRINTTOFILE | PD_RETURNDC;
        if( PrintDlg( &pd ) )
        {
             if(pd.Flags & PD_PAGENUMS)
             {
                 _nFromPage = pd.nFromPage;
                 _nToPage = pd.nToPage;
             }
             else if (pd.Flags & PD_SELECTION )
             {
                 _nFromPage = _nToPage = 0xFFFF;
             }
             else   // all non empty pages
             {
                 _nFromPage = nMinPage;
                 _nToPage = nMaxPage;
             }
             _hdc = pd.hDC;
             _hDevNames = pd.hDevNames;

             if( pd.hDevMode )
             {
                GlobalFree (pd.hDevMode);
             }
             _status = PREQ_SUCCESS;
             return;
        }
        else
        {
            _status = PREQ_ERROR;
            return;
        }
    }

    _status = PREQ_CANCEL;
}

CPrintRequest:: ~CPrintRequest()
{
    if( _hDevNames )
    {
        GlobalFree( _hDevNames );
    }

    if( _hdc )
    {
        DeleteDC(_hdc );
    }
}

UINT CPrintRequest::Print(HINSTANCE hInst, CCanvas &canvas, CModel *pModel)
{
     // needs to become app & font name....
     static TCHAR szMessage [] = TEXT("Grid: Printing") ;

     TCHAR szCaption[30];

     BOOL       fError = PREQ_SUCCESS ;
     ABORTPROC  lpfnAbortProc;
     DLGPROC    lpfnPrintDlgProc ;
     short      cxPage, cyPage ;

     cxPage = GetDeviceCaps (canvas, HORZRES) ;
     cyPage = GetDeviceCaps (canvas, VERTRES) ;

     // disable user input to main window
     EnableWindow (_hwnd, FALSE) ;

     lpfnPrintDlgProc = (DLGPROC) MakeProcInstance ((FARPROC) PrintDlgProc, hInst) ;

     LoadString(hInst, IDS_MSGCAPTION, szCaption, 30);
     PrGlobal._bUserAbort = FALSE ;
     PrGlobal._hDlgPrint =
                CreateDialogParam (hInst, TEXT("PrintDlgBox"), _hwnd,
                                lpfnPrintDlgProc, (LONG)(LPSTR) szCaption) ;

     lpfnAbortProc = (ABORTPROC) MakeProcInstance ((FARPROC) AbortProc, hInst) ;

     SetAbortProc(canvas, lpfnAbortProc);

     DOCINFO docinfo;
     docinfo.cbSize = sizeof(DOCINFO);
     docinfo.lpszDocName = szMessage;
     docinfo.lpszOutput = NULL;

     if (Escape (canvas, STARTDOC, sizeof szMessage - 1, (LPCSTR)szMessage, NULL) > 0)
     //if (StartDoc(canvas, &docinfo) > 0)
     {
          UINT fuFormat;
          pModel->GetFormat(fuFormat);
          pModel->SetFormat(fuFormat | PAGEPRINT|PAGEELEMS);
          UINT iSavePage = pModel->GetPage();

          // create printer view to
          CView  View(cxPage, cyPage);

          if( _nToPage == 0xFFFF || _nFromPage == 0xFFFF )
          {
                _nToPage =   pModel->GetPage()+1;
                _nFromPage = pModel->GetPage()+1;
          }

          for( ; _nFromPage <= _nToPage && ! fError; _nFromPage++ )
          {
             if (pModel->IsModelPageUsed(_nFromPage-1))
                {
                if( StartPage(canvas) < 0 )
                    {
                    fError = PREQ_ERRSTARTPAGE ;
                    break;
                    }
                pModel->SetPage(_nFromPage-1);  // <- pages are 0 based in Model
                RECT rc;
                rc.left = rc.top = 0;
                rc.right= cxPage;
                rc.bottom = cyPage;
                View.Paint (canvas, pModel, rc) ;

                if ( EndPage(canvas) < 0)
                    {
                    fError = PREQ_ERRENDPAGE ;
                    }
}
          }
          if( !fError )
          {
               EndDoc(canvas);
          }
          // restore screen format
          pModel->SetPage(iSavePage);
          pModel->SetFormat(fuFormat);
     }
     else
     {
          fError = PREQ_ERRSTARTDOC ;
     }

     if (!PrGlobal._bUserAbort)
     {
          EnableWindow (_hwnd, TRUE) ;
          DestroyWindow (PrGlobal._hDlgPrint) ;
     }

     FreeProcInstance ((FARPROC)lpfnPrintDlgProc) ;
     FreeProcInstance ((FARPROC)lpfnAbortProc) ;

     if( PrGlobal._bUserAbort )
     {
        fError |= PREQ_USERABORT;
     }

     return fError ;
}


README.TXT

Demonstration of the Font Functions

SUMMARY
=======

The TTFONTS sample is an easy-to-use, powerful utility that allows you to
explore the font system. There is a toolbar on top of the main frame window
with buttons that allow the following actions:

  - Enumerating all of the fonts installed for the display.
  - Getting TEXTMETRIC & OUTLINETEXTMETRIC information.
  - Creating a font based on an arbitrary LOGFONT structure.
  - Getting "font data" by using the GetFontData function.
  - Enumerating all of the fonts available to the default printer.

MORE INFORMATION
================

This sample is designed to provide the user with an easy interface to the
function calls related to the font system. It will not protect against
meaningless values, nor will it hide system oddities. Most of the buttons
on the toolbar are self explanatory and represent a single system function.

Pressing the EnumFonts button shows all of the face names, listed
horizontally, and each of the fonts within that face name, listed
vertically. TrueType fonts are marked with a small colored TT bitmap. Fonts
that have the DEVICE_FONTTYPE bit are marked with a small bitmap image of a
printer. When the enumeration windows are showing, you can click the left
mouse button to copy the information about a selected font into the LOGFONT
and TEXTMETRIC dialog boxes. You can dismiss this window without changing
the dialog boxes by clicking the right mouse button or typing any character.

The Display window is able to operate in any one of three modes. These are
listed in the Display menu. The first just writes Hello in the middle of the
screen and grids the background. This is useful when utilizing the
lfEscapement and lfOrientation fields of the LOGFONT structure. The second
mode writes all of the glyphs between the tmFirstChar and tmLastChar values
stored in the TEXTMETRIC structure. The final mode is used only for true
type fonts. It calls GetFontData, finds the cmap table, and displays glyphs
from the different ranges in this table. Use the horizontal scroll bar in
the display window to step through the ranges.


DIALOGS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  dialogs.c -- module for the two dialogs (LOGFONT & TEXTMETRIC)
*   Includes the window procedure and an initialization routine.
*  the LogFontWndProc is actually used for multiple dialogs.
\**************************************************************************/

#include <windows.h>
#include <string.h>
#include <stdio.h>
#include "ttfonts.h"

int initDlg(HWND hwndMain)
{
  /*  load three dialogs and position them. Set the minimize icon for
   *   this CLASS and it affects all dialog windows.
   */

  hwndDlgLF = CreateDialog (hInst, TEXT("logfontDlg"), hwndMain,
                (DLGPROC)LogFontWndProc);
  if (hwndDlgLF == NULL) return FALSE;

  SetWindowPos (hwndDlgLF, NULL,
                CHILDLEFT(1), CHILDTOP,
                0,0, SWP_NOZORDER | SWP_NOSIZE);

  hwndDlgTM = CreateDialog (hInst, TEXT("textmetricDlg"), hwndMain,
                (DLGPROC)LogFontWndProc);
  if (hwndDlgTM == NULL) return FALSE;

  SetWindowPos (hwndDlgTM, NULL,
                CHILDLEFT(0), CHILDTOP,
                0,0, SWP_NOZORDER | SWP_NOSIZE);

  hwndDlgOLTM = CreateDialog (hInst, TEXT("oltextmetricDlg"), hwndMain,
                (DLGPROC)LogFontWndProc);
  if (hwndDlgOLTM == NULL) return FALSE;
  ShowWindow (hwndDlgOLTM, SW_MINIMIZE);

  hwndDlgFD = CreateDialog (hInst, TEXT("getfontdataDlg"), hwndMain,
                (DLGPROC)FontDataWndProc);
  if (hwndDlgFD == NULL) return FALSE;
  ShowWindow (hwndDlgFD, SW_MINIMIZE);
  SetWindowPos (hwndDlgFD, HWND_TOP,
                0, 0,
                0,0, SWP_NOSIZE );

  SetClassLong (hwndDlgLF, GCL_HICON, (LONG)LoadIcon(hInst, TEXT("ttfontsIcon")));

  return TRUE;
}

/* first and last string IDs from string table in RC file */
#define FIRSTSTRING 1
#define LASTSTRING  20

/**************************************************************************\
*
*  function:  FontDataWndProc
*
*  input parameters:  normal window procedure parameters.
*
*  Allow the user to select a table, an offset, and a byte count.
*   on command message, post self a user message (allows the user
*   message to come in from other sources too).  On the user message,
*   just call GetFontData and display the results.
*
*
  // UNICODE NOTICE
  //  Parts of this are held as ANSI because the dwTable
  //  in the fontdata is stored with four 8 bit chars.
  //  And because there is not yet a wide char version of sscanf().
*
*
\**************************************************************************/
LRESULT CALLBACK FontDataWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
#define NCHAR 255
TCHAR buffer[NCHAR];
HDC hdc;
int nBytes, nStrings, i;

  switch (message) {

    /* fill combo box w/ table names, and
     *  entry fields w/ meaningful initial values.
     */
    case WM_INITDIALOG:
      SetDlgItemInt (hwnd, DID_DWOFFSET, 0, TRUE);
      SetDlgItemInt (hwnd, DID_CBDATA, 50, TRUE);
      for (i = FIRSTSTRING; i<= LASTSTRING; i++) {
        LoadString (GetModuleHandle (NULL), i, buffer, NCHAR);
        SendDlgItemMessage (hwnd, DID_DWTABLE, CB_ADDSTRING, 0, (LPARAM)buffer);
      }

    return TRUE;

    /* If the user hits the DOIT button, post message back to the
     *  main window.  It will get an HDC and then post us the proper
     *  user message.
     */
    case WM_COMMAND:
      if (wParam == DID_DOIT)
        PostMessage (hwndMain, WM_COMMAND, TBID_GETFONTDATA, 0);
    break;  /* end WM_COMMAND */

    /**********************************************************************\
    *  WMU_GETFONTDATA
    *
    *  lParam - HDC.
    *
    * User message.  Parse the contents of the entry fields, and make the
    *  GetFontData() call.  Put results in the listbox.
    \**********************************************************************/
    case WMU_GETFONTDATA: {
        DWORD dwTable, dwOffset, cbData;
        LPBYTE   lpDataBuffer;
        DWORD dwNBytes;

        hdc = (HDC) lParam;
        SendDlgItemMessage (hwnd, DID_LISTBOX, LB_RESETCONTENT, 0, 0);

        {
          // UNICODE NOTICE.  GetDlgItemTextA returns ANSI strings.
          //  we are doing manipulation on a byte by byte basis

          CHAR  fourbytes[5];
          CHAR  sbBuffer[NCHAR];

          nBytes = GetDlgItemTextA (hwnd, DID_DWTABLE, fourbytes, 5);
          if (nBytes == 0) {
            dwTable = 0;
          } else {
            dwTable = (DWORD) ((fourbytes[3] << 24)
                            +  (fourbytes[2] << 16)
                            +  (fourbytes[1] <<  8)
                            +  (fourbytes[0]));
          }

          // UNICODE NOTICE.  GetDlgItemTextA returns ANSI strings.
          //  sscanf expects single byte strings.

          GetDlgItemTextA (hwnd, DID_DWOFFSET, sbBuffer, NCHAR);
          sscanf (sbBuffer, "%x", &dwOffset);
          GetDlgItemTextA (hwnd, DID_CBDATA  , sbBuffer, NCHAR);
          sscanf (sbBuffer, "%x", &cbData);
        }

        lpDataBuffer = (LPBYTE) LocalAlloc (LPTR, cbData);
        if (lpDataBuffer == NULL) {
          MessageBox (NULL, szAllocFailed, szMBERROR, MBERRORFLAGS);
          return 0;
        }

        dwNBytes = GetFontData (hdc, dwTable, dwOffset, (LPVOID) lpDataBuffer, cbData);

        if (dwNBytes == -1) {
          MessageBox (NULL, szFontDataErr, szMBERROR, MBERRORFLAGS);
        } else {

          nStrings = dwNBytes / 16;
          for (i = 0; i< nStrings; i++) {
            wsprintf (buffer,
                      TEXT("%02x%02x %02x%02x  %02x%02x %02x%02x  %02x%02x %02x%02x  %02x%02x %02x%02x"),
                      (int)lpDataBuffer[i*16+0],
                      (int)lpDataBuffer[i*16+1],
                      (int)lpDataBuffer[i*16+2],
                      (int)lpDataBuffer[i*16+3],
                      (int)lpDataBuffer[i*16+4],
                      (int)lpDataBuffer[i*16+5],
                      (int)lpDataBuffer[i*16+6],
                      (int)lpDataBuffer[i*16+7],
                      (int)lpDataBuffer[i*16+8],
                      (int)lpDataBuffer[i*16+9],
                      (int)lpDataBuffer[i*16+10],
                      (int)lpDataBuffer[i*16+11],
                      (int)lpDataBuffer[i*16+12],
                      (int)lpDataBuffer[i*16+13],
                      (int)lpDataBuffer[i*16+14],
                      (int)lpDataBuffer[i*16+15]);
            SendDlgItemMessage (hwnd, DID_LISTBOX, LB_ADDSTRING, 0, (LONG) buffer);
          }
        }

        LocalFree ( LocalHandle ((LPVOID)lpDataBuffer));
    } return TRUE;  /* end WMU_GETFONTDATA */

  } /* end switch */
  return 0;
}

/**************************************************************************\
*
*  function:  LogFontWndProc
*
*  input parameters:  normal window procedure parameters.
*  global variables:
*
* This window procedure is used for two completely different dialog boxes.
\**************************************************************************/
LRESULT CALLBACK LogFontWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static LPLOGFONT    lplf;
static LPTEXTMETRIC lptm;

  switch (message) {

    /**********************************************************************\
    *  WMU_DEMOTOLF
    *
    *  lParam - pointer to LOGFONT structure.
    *
    * User message.  Take the input LOGFONT and fill the edit fields of the
    *  dialog box.
    \**********************************************************************/
    case WMU_DEMOTOLF: {
      lplf = (LPLOGFONT) lParam;

      SetDlgItemInt (hwnd, DIDHEIGHT , lplf->lfHeight,        TRUE);
      SetDlgItemInt (hwnd, DIDWIDTH  , lplf->lfWidth,         TRUE);
      SetDlgItemInt (hwnd, DIDESCAPE , lplf->lfEscapement,    TRUE);
      SetDlgItemInt (hwnd, DIDORIENT , lplf->lfOrientation,   TRUE);
      SetDlgItemInt (hwnd, DIDWEIGHT , lplf->lfWeight,        TRUE);
      SetDlgItemInt (hwnd, DIDITALIC , lplf->lfItalic,        FALSE);
      SetDlgItemInt (hwnd, DIDUNDERL , lplf->lfUnderline,     FALSE);
      SetDlgItemInt (hwnd, DIDSTRIKE , lplf->lfStrikeOut,     FALSE);
      SetDlgItemInt (hwnd, DIDCHARSE , lplf->lfCharSet,       FALSE);
      SetDlgItemInt (hwnd, DIDOUTPRE , lplf->lfOutPrecision,  FALSE);
      SetDlgItemInt (hwnd, DIDCLIPPR , lplf->lfClipPrecision, FALSE);
      SetDlgItemInt (hwnd, DIDQUALIT , lplf->lfQuality,       FALSE);
      SetDlgItemInt (hwnd, DIDPITCHA , lplf->lfPitchAndFamily,FALSE);
      SetDlgItemText (hwnd, DIDFACENA, lplf->lfFaceName);

    } break;

    /**********************************************************************\
    *  WMU_LFTODEMO
    *
    *  lParam - pointer to LOGFONT structure.
    *
    * User message.  Fill the input LOGFONT with the contents of the
    *  edit fields of dialog box.
    \**********************************************************************/
    case WMU_LFTODEMO: {
      BOOL  success;
      lplf = (LPLOGFONT) lParam;

      lplf->lfHeight =        GetDlgItemInt (hwnd, DIDHEIGHT, &success , TRUE);
      lplf->lfWidth  =        GetDlgItemInt (hwnd, DIDWIDTH , &success , TRUE);
      lplf->lfEscapement =    GetDlgItemInt (hwnd, DIDESCAPE, &success , TRUE);
      lplf->lfOrientation =   GetDlgItemInt (hwnd, DIDORIENT, &success , TRUE);
      lplf->lfWeight =        GetDlgItemInt (hwnd, DIDWEIGHT, &success , TRUE);
      lplf->lfItalic =        (BYTE) GetDlgItemInt (hwnd, DIDITALIC, &success , FALSE);
      lplf->lfUnderline =     (BYTE) GetDlgItemInt (hwnd, DIDUNDERL, &success , FALSE);
      lplf->lfStrikeOut =     (BYTE) GetDlgItemInt (hwnd, DIDSTRIKE, &success , FALSE);
      lplf->lfCharSet =       (BYTE) GetDlgItemInt (hwnd, DIDCHARSE, &success , FALSE);
      lplf->lfOutPrecision =  (BYTE) GetDlgItemInt (hwnd, DIDOUTPRE, &success , FALSE);
      lplf->lfClipPrecision = (BYTE) GetDlgItemInt (hwnd, DIDCLIPPR, &success , FALSE);
      lplf->lfQuality =       (BYTE) GetDlgItemInt (hwnd, DIDQUALIT, &success , FALSE);
      lplf->lfPitchAndFamily =(BYTE) GetDlgItemInt (hwnd, DIDPITCHA, &success , FALSE);
      GetDlgItemText (hwnd, DIDFACENA, lplf->lfFaceName, LF_FACESIZE);

    } break;

    /**********************************************************************\
    *  WMU_DEMOTOTM
    *
    *  lParam - pointer to TEXTMETRIC structure.
    *
    * User message.  Take the input LOGFONT and fill the list box with
    *  strings.  Turn off update before hand, then reenable when complete.
    \**********************************************************************/
    case WMU_DEMOTOTM: {
      TCHAR buffer[100];

      lptm = (LPTEXTMETRIC) lParam;

      SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETREDRAW, FALSE, 0);
      SendDlgItemMessage (hwnd, DID_LISTBOX, LB_RESETCONTENT, 0, 0);

#define LBPUT SendDlgItemMessage (hwnd, DID_LISTBOX, LB_ADDSTRING, 0, (LONG) buffer);

      wsprintf (buffer, TEXT("tmHeight        \t%d")  ,(int) lptm->tmHeight           ); LBPUT
      wsprintf (buffer, TEXT("tmAscent        \t%d")  ,(int) lptm->tmAscent           ); LBPUT
      wsprintf (buffer, TEXT("tmDescent       \t%d")  ,(int) lptm->tmDescent          ); LBPUT
      wsprintf (buffer, TEXT("tmInternalLeading\t%d") ,(int) lptm->tmInternalLeading  ); LBPUT
      wsprintf (buffer, TEXT("tmExternalLeading\t%d") ,(int) lptm->tmExternalLeading  ); LBPUT
      wsprintf (buffer, TEXT("tmAveCharWidth  \t%d")  ,(int) lptm->tmAveCharWidth     ); LBPUT
      wsprintf (buffer, TEXT("tmMaxCharWidth  \t%d")  ,(int) lptm->tmMaxCharWidth     ); LBPUT
      wsprintf (buffer, TEXT("tmWeight        \t%d")  ,(int) lptm->tmWeight           ); LBPUT
      wsprintf (buffer, TEXT("tmOverhang      \t%d")  ,(int) lptm->tmOverhang         ); LBPUT
      wsprintf (buffer, TEXT("tmDigitizedAspectX\t%d"),(int) lptm->tmDigitizedAspectX ); LBPUT
      wsprintf (buffer, TEXT("tmDigitizedAspectY\t%d"),(int) lptm->tmDigitizedAspectY ); LBPUT
      wsprintf (buffer, TEXT("tmItalic        \t%d")  ,(int) lptm->tmItalic           ); LBPUT
      wsprintf (buffer, TEXT("tmUnderlined    \t%d")  ,(int) lptm->tmUnderlined       ); LBPUT
      wsprintf (buffer, TEXT("tmStruckOut     \t%d")  ,(int) lptm->tmStruckOut        ); LBPUT
      wsprintf (buffer, TEXT("tmFirstChar     \t%d")  ,(int) lptm->tmFirstChar        ); LBPUT
      wsprintf (buffer, TEXT("tmLastChar      \t%d")  ,(int) lptm->tmLastChar         ); LBPUT
      wsprintf (buffer, TEXT("tmDefaultChar   \t%d")  ,(int) lptm->tmDefaultChar      ); LBPUT
      wsprintf (buffer, TEXT("tmBreakChar     \t%d")  ,(int) lptm->tmBreakChar        ); LBPUT
      wsprintf (buffer, TEXT("tmPitchAndFamily\t%d")  ,(int) lptm->tmPitchAndFamily   ); LBPUT
      wsprintf (buffer, TEXT("tmCharSet       \t%d")  ,(int) lptm->tmCharSet          ); LBPUT

      SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETREDRAW, TRUE, 0);
      InvalidateRect (hwnd, NULL, TRUE);
      UpdateWindow (hwnd);

    } break;

    /**********************************************************************\
    *  WMU_DEMOTOOLTM
    *
    *  lParam - HDC.
    *
    * User message.  With the input HDC, allocate space for OUTLINETEXTMETRIC
    *  structure, query it from the HDC, and fill the listbox.  (Allocate the
    *  OUTLINETEXTMETRIC structure dynamically since it is variable size.)
    \**********************************************************************/
    case WMU_DEMOTOOLTM: {
      HDC hdc;
      TCHAR buffer[100];
      UINT cbData;
      LPOUTLINETEXTMETRIC lpoltm;
      LPBYTE lptStr;

      hdc = (HDC) lParam;

      /* figure out how large the structure is, alloc, and re-query
       *  unless cbData ==0, then post no-op string and exit.
       */
      cbData = GetOutlineTextMetrics (hdc, 0, NULL);
      if (cbData == 0) {
        SendDlgItemMessage (hwnd, DID_LISTBOX, LB_RESETCONTENT, 0, 0);
        wsprintf (buffer, TEXT("cbData == 0")); LBPUT
        return 0;
      }
      lpoltm = (LPOUTLINETEXTMETRIC)LocalAlloc (LPTR, cbData);
      GetOutlineTextMetrics (hdc, cbData, lpoltm);

      /* freeze redraw of listbox, and clear contents. */
      SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETREDRAW, FALSE, 0);
      SendDlgItemMessage (hwnd, DID_LISTBOX, LB_RESETCONTENT, 0, 0);

      wsprintf (buffer, TEXT("otmSize                \t%d"),(int) lpoltm->otmSize               );            LBPUT
      wsprintf (buffer, TEXT("otmTextMetrics { }"));                                                      LBPUT
      wsprintf (buffer, TEXT("otmFiller              \t%d"),(int)(BYTE) lpoltm->otmFiller             );      LBPUT

      wsprintf (buffer, TEXT("otmPanoseNumber           "));                                                  LBPUT
// removed for beta 2.      wsprintf (buffer, TEXT("\t ulCulture        \t%d"),   (int) lpoltm->otmPanoseNumber.ulCulture        ); LBPUT
      wsprintf (buffer, TEXT("\t bFamilyType      \t0x%lx"),(int) lpoltm->otmPanoseNumber.bFamilyType      ); LBPUT
      wsprintf (buffer, TEXT("\t bSerifStyle      \t0x%lx"),(int) lpoltm->otmPanoseNumber.bSerifStyle      ); LBPUT
      wsprintf (buffer, TEXT("\t bWeight          \t0x%lx"),(int) lpoltm->otmPanoseNumber.bWeight          ); LBPUT
      wsprintf (buffer, TEXT("\t bProportion      \t0x%lx"),(int) lpoltm->otmPanoseNumber.bProportion      ); LBPUT
      wsprintf (buffer, TEXT("\t bContrast        \t0x%lx"),(int) lpoltm->otmPanoseNumber.bContrast        ); LBPUT
      wsprintf (buffer, TEXT("\t bStrokeVariation \t0x%lx"),(int) lpoltm->otmPanoseNumber.bStrokeVariation ); LBPUT
      wsprintf (buffer, TEXT("\t bArmStyle        \t0x%lx"),(int) lpoltm->otmPanoseNumber.bArmStyle        ); LBPUT
      wsprintf (buffer, TEXT("\t bLetterform      \t0x%lx"),(int) lpoltm->otmPanoseNumber.bLetterform      ); LBPUT
      wsprintf (buffer, TEXT("\t bMidline         \t0x%lx"),(int) lpoltm->otmPanoseNumber.bMidline         ); LBPUT
      wsprintf (buffer, TEXT("\t bXHeight         \t0x%lx"),(int) lpoltm->otmPanoseNumber.bXHeight         ); LBPUT

      wsprintf (buffer, TEXT("otmfsSelection         \t%d"),(UINT) lpoltm->otmfsSelection        ); LBPUT
      wsprintf (buffer, TEXT("otmfsType              \t%d"),(UINT) lpoltm->otmfsType             ); LBPUT
      wsprintf (buffer, TEXT("otmsCharSlopeRise      \t%d"),(UINT) lpoltm->otmsCharSlopeRise     ); LBPUT
      wsprintf (buffer, TEXT("otmsCharSlopeRun       \t%d"),(UINT) lpoltm->otmsCharSlopeRun      ); LBPUT
      wsprintf (buffer, TEXT("otmItalicAngle         \t%d"),(UINT) lpoltm->otmItalicAngle        ); LBPUT
      wsprintf (buffer, TEXT("otmEMSquare            \t%d"),(UINT) lpoltm->otmEMSquare           ); LBPUT
      wsprintf (buffer, TEXT("otmAscent              \t%d"),(UINT) lpoltm->otmAscent             ); LBPUT
      wsprintf (buffer, TEXT("otmDescent             \t%d"),(int) lpoltm->otmDescent             ); LBPUT
      wsprintf (buffer, TEXT("otmLineGap             \t%d"),(int) lpoltm->otmLineGap             ); LBPUT
      wsprintf (buffer, TEXT("otmsCapEmHeight        \t%d"),(UINT) lpoltm->otmsCapEmHeight        ); LBPUT
      wsprintf (buffer, TEXT("otmsXHeight            \t%d"),(UINT) lpoltm->otmsXHeight            ); LBPUT
      wsprintf (buffer, TEXT("otmrcFontBox           \t (%d, %d, %d, %d)"),
                                                      (int) lpoltm->otmrcFontBox.left,
                                                      (int) lpoltm->otmrcFontBox.top,
                                                      (int) lpoltm->otmrcFontBox.right,
                                                      (int) lpoltm->otmrcFontBox.bottom   ); LBPUT
      wsprintf (buffer, TEXT("otmMacAscent           \t%d"),(int) lpoltm->otmMacAscent          ); LBPUT
      wsprintf (buffer, TEXT("otmMacDescent          \t%d"),(int) lpoltm->otmMacDescent         ); LBPUT
      wsprintf (buffer, TEXT("otmMacLineGap          \t%d"),(UINT) lpoltm->otmMacLineGap         ); LBPUT
      wsprintf (buffer, TEXT("otmusMinimumPPEM       \t%d"),(UINT) lpoltm->otmusMinimumPPEM      ); LBPUT

      wsprintf (buffer, TEXT("otmptSubscriptSize     \t(%d, %d)"),
                                                      (int) lpoltm->otmptSubscriptSize.x,
                                                      (int) lpoltm->otmptSubscriptSize.y    ); LBPUT
      wsprintf (buffer, TEXT("otmptSubscriptOffset   \t(%d, %d)"),
                                                      (int) lpoltm->otmptSubscriptOffset.x,
                                                      (int) lpoltm->otmptSubscriptOffset.y  ); LBPUT
      wsprintf (buffer, TEXT("otmptSuperscriptSize   \t(%d, %d)"),
                                                      (int) lpoltm->otmptSuperscriptSize.x,
                                                      (int) lpoltm->otmptSuperscriptSize.y  ); LBPUT
      wsprintf (buffer, TEXT("otmptSuperscriptOffset \t(%d, %d)"),
                                                      (int) lpoltm->otmptSuperscriptOffset.x,
                                                      (int) lpoltm->otmptSuperscriptOffset.y); LBPUT

      wsprintf (buffer, TEXT("otmsStrikeoutSize      \t%d"),(UINT) lpoltm->otmsStrikeoutSize     ); LBPUT
      wsprintf (buffer, TEXT("otmsStrikeoutPosition  \t%d"),(int) lpoltm->otmsStrikeoutPosition );  LBPUT
      wsprintf (buffer, TEXT("otmsUnderscoreSize     \t%d"),(int) lpoltm->otmsUnderscoreSize    );  LBPUT
      wsprintf (buffer, TEXT("otmsUnderscorePosition \t%d"),(UINT) lpoltm->otmsUnderscorePosition); LBPUT

      /* the last 4 fields are incorrectly typed as PSTR,
       *  they are in fact offsets from the top of the
       *  OUTLINETEXTMETRIC structure, to the location of the string.
       */
      lptStr = (LPBYTE)lpoltm;
      lptStr += (UINT) (PBYTE) lpoltm->otmpFamilyName;
      wsprintf (buffer, TEXT("otmpFamilyName:   %s"),lptStr); LBPUT

      lptStr = (LPBYTE)lpoltm;
      lptStr += (UINT) (PBYTE) lpoltm->otmpFaceName;
      wsprintf (buffer, TEXT("otmpFaceName:   %s"),lptStr);   LBPUT

      lptStr = (LPBYTE)lpoltm;
      lptStr += (UINT) (PBYTE) lpoltm->otmpStyleName;
      wsprintf (buffer, TEXT("otmpStyleName:   %s"),lptStr);  LBPUT

      wsprintf (buffer, TEXT("otmpFullName:"));               LBPUT
      lptStr = (LPBYTE)lpoltm;
      lptStr += (UINT) (PBYTE) lpoltm->otmpFullName;
      wsprintf (buffer, TEXT("  %s"),lptStr);                 LBPUT

      SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETREDRAW, TRUE, 0);
      InvalidateRect (hwnd, NULL, TRUE);
      UpdateWindow (hwnd);

      /* balance LocalAlloc(), release memory. */
      LocalFree (LocalHandle (lpoltm));

    } break;

    /**********************************************************************\
    *  WM_CHILDACTIVATE
    *
    *  In order for these MDI child windows (dialogs) to be activated
    *   correctly, the dialog procedure here must call the Win32 API
    *   DefMDIChildProc ().
    \**********************************************************************/
    case WM_CHILDACTIVATE:
      DefMDIChildProc(hwnd, message, wParam, lParam);
      return TRUE;
    break;

  } /* end switch */
  return 0;
}


DISPLAY.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  display.c -- module for the window with the test font.
*   Includes the window procedure and an initialization routine.
*
* store the handle to the test font in the extra bytes of this window.
*
*
* HORZ_SCROLL used to step through UC ranges when in "all glyphs" mode.
* VERT_SCROLL:
*   scroll bar range is the total number of lines needed for all glyphs
*    minus the number which may be shown in the window.
*   scroll bar position is then the first line which is to be drawn.
*
*
*
\**************************************************************************/

#include <windows.h>
#include <string.h>
#include "ttfonts.h"

/* defines for the gridding pattern in background of window */
#define GRIDCOLOR  PALETTEINDEX (3)
#define TICKSPACE  10

/* global variables store the start and end codepoints for UC ranges. */
USHORT *endCount= NULL;
USHORT *startCount= NULL;

int CountUCSegments (HDC);
/* error return value from CountUCSegments */
#define SEGMENTERROR -1

HWND   hwndFaceBanner;
TCHAR szHello[] = TEXT("Hello");

VOID BlamGlyphs (HDC, PTEXTMETRIC, USHORT, USHORT, PRECT, BOOL);

int initDisplay(HWND hwndParent)
{
WNDCLASS  wc;

  wc.style = CS_VREDRAW | CS_HREDRAW;
  wc.lpfnWndProc = (WNDPROC)DisplayWndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInst;
  wc.hIcon = LoadIcon(hInst, TEXT("ttfontsIcon"));
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = NULL;
  wc.lpszMenuName = NULL;
  wc.lpszClassName = TEXT("display");

  if (!RegisterClass(&wc)) return (FALSE);

  hwndDisplay = CreateMDIWindow(
      TEXT("display"),
      TEXT("Display"),
      WS_CHILD | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_VISIBLE |
                 WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_VSCROLL | WS_HSCROLL,
      CHILDLEFT(2),
      CHILDTOP,
      GetSystemMetrics (SM_CXFULLSCREEN)/3 - 10,
      GetSystemMetrics (SM_CYFULLSCREEN)/3,
      hwndParent, hInst, 0);

  /* create child window to display face name */
  hwndFaceBanner = CreateWindow(
        TEXT("STATIC"), TEXT(""),
        WS_CHILD | WS_VISIBLE | WS_BORDER,
        -1,0,
        GetSystemMetrics (SM_CXFULLSCREEN),
        GetSystemMetrics (SM_CYMENU),
        hwndDisplay, NULL, hInst, NULL);

  if (!hwndDisplay) return (FALSE);

  return TRUE;
}

/**************************************************************************\
*
*  function:  DisplayWndProc()
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   gDisplaymode - menu state
\**************************************************************************/
LRESULT CALLBACK DisplayWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HANDLE hPenGrid;
static LPLOGFONT     lplf;
static LPTEXTMETRIC  lptm;
static HFONT  hfont;
static HDC    hdcStatic;  /* Sent to other window procedures. */

static HDC hdc;
static RECT rect;
static int min, max;
static TEXTMETRIC tm;
static int iSeg;
static TCHAR buffer[100];

static int nCharPerLine, nTotalGlyphs, lineHeight;
static int nLinesTotal, nLinesWindow, nLinesDifference;
static int scrollRangeHigh, topLineShowing;
int i;

  switch (message) {

    /**********************************************************************\
    *  WMU_DEMOTOLF
    *
    *  lParam - pointer to LOGFONT structure.
    *
    * User message.  Fill up the display LOGFONT from the HFONT
    *  in the extra bytes.
    \**********************************************************************/
    case WMU_DEMOTOLF: {
      lplf = (LPLOGFONT) lParam;

      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      GetObject (hfont, sizeof(LOGFONT), lplf);
    } return 0;

    /**********************************************************************\
    *  WMU_DEMOTOTM
    *
    *  lParam - pointer to TEXTMETRIC structure.
    *
    * User message.  Fill up the TEXTMETRIC from the HFONT
    *  in the extra bytes.
    \**********************************************************************/
    case WMU_DEMOTOTM: {
      lptm = (LPTEXTMETRIC) lParam;

      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);

      hdc = GetDC (hwnd);
      SelectObject (hdc,hfont);
      GetTextMetrics (hdc, lptm);
      ReleaseDC (hwnd, hdc);
    } return 0;

    /**********************************************************************\
    *  WMU_DEMOGETDC
    *
    * User message.  Return an HDC.
    \**********************************************************************/
    case WMU_DEMOGETDC: {
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);

      hdcStatic = GetDC (hwnd);
      SelectObject (hdcStatic,hfont);
      return (LRESULT)hdcStatic;
    } break;

    /**********************************************************************\
    *  WMU_DEMORELEASEDC
    *
    *  lParam - the same HDC returned by WMU_DEMOGETDC
    *
    * User message.  Release the HDC.
    \**********************************************************************/
    case WMU_DEMORELEASEDC: {
      hdcStatic = (HDC) lParam;
      ReleaseDC (hwnd, hdcStatic);
    } return 0;

    /**********************************************************************\
    *  WMU_HFONTTODEMO
    *
    *  lParam - hFont
    *
    * User message.  Use the input HFONT to set a new
    *  font for this window.  Store the new font in the HFONT extra bytes.
    \**********************************************************************/
    case WMU_HFONTTODEMO: {
      /* Get and delete the last font placed in this window. */
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      DeleteObject (hfont);

      hfont = (HFONT) lParam;
      SetWindowLong (hwnd, GWL_USERDATA, (LONG) hfont);

      /* make sure that scroll metrics are ok */
      switch (gDisplaymode) {
        case IDM_MODEHELLO:   SendMessage (hwnd, WMU_NEWMODEHELLO, 0,0);
          break;
        case IDM_MODETMRANGE: SendMessage (hwnd, WMU_NEWMODETMRANGE, 0,0);
          break;
        case IDM_MODEALL:
          if (!SendMessage (hwndDisplay, WMU_NEWMODEALL, 0,0)) {
            MessageBox (NULL, szResetDisplay, szMBERROR, MBERRORFLAGS);
            SendMessage (hwndMain, WM_COMMAND, IDM_MODEHELLO, 0);
            return 0;
          }
          break;
      } /* end switch */

      InvalidateRect (hwnd, NULL, TRUE);
    } return 0;

    /**********************************************************************\
    *  WMU_NEWFONT
    *
    *  lParam - pointer to LOGFONT structure.
    *
    * User message.  Use the input LOGFONT structure to create a new
    *  font for this window.  Send a WMU_HFONTTODEMO message to set the
    *  font into the extra bytes for the window.
    \**********************************************************************/
    case WMU_NEWFONT: {
      lplf = (LPLOGFONT) lParam;

      /* Create a new logical font and set it into the windows extra bytes. */
      hfont = CreateFontIndirect (lplf);

      SendMessage (hwnd, WMU_HFONTTODEMO, 0, (LPARAM) hfont);
    } return 0;

    /**********************************************************************\
    *  WM_CREATE
    *
    * Create pens for drawing with later.
    \**********************************************************************/
    case WM_CREATE:
      hPenGrid   = CreatePen (PS_SOLID, 1, GRIDCOLOR);
      SetWindowLong (hwnd, GWL_USERDATA, (LONG) GetStockObject (SYSTEM_FONT));
    break;

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Complement of the WM_CREATE message.  Delete the pens that were
    *  created, free startCount, endCount arrays.
    *
    \**********************************************************************/
    case WM_DESTROY:
      DeleteObject (hPenGrid);
      if (startCount != NULL) LocalFree (LocalHandle (startCount));
      if (endCount != NULL) LocalFree (LocalHandle (endCount));
    break;

    /**********************************************************************\
    *  WM_ERASEBKGND
    *
    * Offset the origin conditional on gDisplaymode.  Grid the window.
    \**********************************************************************/
    case WM_ERASEBKGND: {
      HDC hdc;
      RECT rect;
      int i;

      hdc = (HDC)wParam;

      GetClientRect (hwnd, &rect);
      FillRect (hdc, &rect, GetStockObject (LTGRAY_BRUSH));

      if (gDisplaymode == IDM_MODEHELLO) {
        SetViewportOrgEx (hdc, rect.right /2, rect.bottom/2, NULL);
        OffsetRect (&rect, -rect.right/2, -rect.bottom/2);

        SelectObject(hdc, hPenGrid);
        /* Draw vertical lines.  */
        for (i = 0; i<= rect.right; i+=TICKSPACE){
          MoveToEx (hdc, i, rect.top, NULL);
          LineTo (hdc, i, rect.bottom);
          MoveToEx (hdc, -i, rect.top, NULL);
          LineTo (hdc, -i, rect.bottom);
        }
        MoveToEx (hdc, 1, rect.top, NULL);
        LineTo (hdc, 1, rect.bottom);

        /* Draw horizontal lines.  */
        for (i = 0; i<= rect.bottom; i+=TICKSPACE){
          MoveToEx (hdc, rect.left,i, NULL);
          LineTo (hdc, rect.right,i);
          MoveToEx (hdc, rect.left,-i, NULL);
          LineTo (hdc, rect.right,-i);
        }
        MoveToEx (hdc, rect.left, 1, NULL);
        LineTo (hdc, rect.right,1);

      }

    } return TRUE;

    /**********************************************************************\
    *  WM_VSCROLL
    *
    * Slide the contents of the window up and down.  Notice that the
    *  scroll bar thumb position is important for painting.
    \**********************************************************************/
    case WM_VSCROLL:

      switch (LOWORD(wParam)){
        int OldPos, NewPos;

        case SB_PAGEDOWN:
        case SB_LINEDOWN:
          OldPos = GetScrollPos (hwnd, SB_VERT);
          SetScrollPos (hwnd, SB_VERT, (OldPos+1), TRUE);
          NewPos = GetScrollPos (hwnd, SB_VERT);
          ScrollWindow (hwnd, 0,(OldPos-NewPos)*lineHeight, NULL, NULL);
        break;

        case SB_PAGEUP:
        case SB_LINEUP:
          OldPos = GetScrollPos (hwnd, SB_VERT);
          SetScrollPos (hwnd, SB_VERT, (OldPos-1), TRUE);
          NewPos = GetScrollPos (hwnd, SB_VERT);
          ScrollWindow (hwnd, 0,(OldPos-NewPos)*lineHeight, NULL, NULL);
        break;

        case SB_THUMBPOSITION:
          OldPos = GetScrollPos (hwnd, SB_VERT);
          NewPos = HIWORD (wParam);
          SetScrollPos (hwnd, SB_VERT, NewPos, TRUE);
          ScrollWindow (hwnd, 0, (OldPos-NewPos)*lineHeight, NULL, NULL);
        break;

      }
    break;

    /**********************************************************************\
    *  WM_HSCROLL
    *
    * The horz scroll should only be present when in MODEALL.
    * Step through the allglyphs ranges.
    * In every case, inform the window the range has changed,
    *  and invalidate it to force a repaint.
    \**********************************************************************/
    case WM_HSCROLL:

      switch (LOWORD(wParam)){
        int OldPos, NewPos;

        case SB_PAGEDOWN:
        case SB_LINEDOWN:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos+1), TRUE);
          SendMessage (hwnd,WMU_NEWMODE_NEWSEG, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

        case SB_PAGEUP:
        case SB_LINEUP:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos-1), TRUE);
          SendMessage (hwnd,WMU_NEWMODE_NEWSEG, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

        case SB_THUMBPOSITION:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          NewPos = HIWORD (wParam);
          SetScrollPos (hwnd, SB_HORZ, NewPos, TRUE);
          SendMessage (hwnd,WMU_NEWMODE_NEWSEG, 0,0);
          InvalidateRect (hwnd, NULL, TRUE);
        break;

      }

    break;

    /**********************************************************************\
    * WMU_NEWMODEALL
    *
    *   returns - TRUE if a scalable font file.
    *             FALSE if GetFontData fails.
    *
    * This message is sent one time when the user checks "Show all glyphs."
    *  Here we set up the horizontal scroll bar, and the start/endCount arrays.
    \**********************************************************************/
    case WMU_NEWMODEALL: {
      HDC hdc;
      int nSegments;

      SetCursor (LoadCursor (NULL, IDC_WAIT));
      hdc = GetDC(hwnd);
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      SelectObject (hdc,hfont);
      nSegments = CountUCSegments (hdc); /* slow computation */
      ReleaseDC (hwnd, hdc);
      SetCursor (LoadCursor (NULL, IDC_ARROW));

      if (nSegments == SEGMENTERROR) return FALSE;

      gDisplaymode =IDM_MODEALL;
      ShowWindow (hwndFaceBanner, SW_HIDE);

      SetScrollRange (hwnd, SB_HORZ, 0, (nSegments-1), TRUE);
      SetScrollPos (hwnd, SB_HORZ, 0, TRUE);

      SendMessage (hwndDisplay, WMU_NEWMODE_NEWSEG, 0,0);
      return TRUE;

    } break;

    /**********************************************************************\
    *  WMU_NEWMODEHELLO
    *  WMU_NEWMODETMRANGE
    *  WMU_NEWMODE_NEWSEG
    *
    *
    * Sent every time an event occurs which will change the layout of
    *  the display glyphs.  I.e.
    *       window resized.
    *       display mode switch
    *       allglyph display mode, next range
    *
    * Be careful because the SetScrollRange() may cause a new WM_SIZE
    *  which in turn will cause this message to be resent.
    *
    \**********************************************************************/
    case WMU_NEWMODEHELLO:
      /* Set the window title text. */
      SetWindowText (hwnd, TEXT("Display"));

      gDisplaymode =IDM_MODEHELLO;
      ShowWindow (hwndFaceBanner, SW_SHOW);

      SetScrollRange (hwnd, SB_HORZ, 0, 0, TRUE);
      SetScrollRange (hwnd, SB_VERT, 0, 0, TRUE);
    break;

    case WMU_NEWMODETMRANGE: {
      /* Set the window title text. */
      SetWindowText (hwnd, TEXT("Display [tmFirstChar, tmLastChar]"));

      gDisplaymode =IDM_MODETMRANGE;
      ShowWindow (hwndFaceBanner, SW_HIDE);

      SetScrollRange (hwnd, SB_HORZ, 0, 0, TRUE);

      hdc = GetDC(hwnd);
      GetClientRect (hwnd, &rect);

      /* subtract off the scroll width if it is NOT there.
       *  (if it is, it will be automatically excluded by GetClientRect)
       *  if it is not there, it may be added, and we want space for it.
       */
      GetScrollRange (hwnd, SB_VERT, &min, &max);
      if (min == max) rect.right -= GetSystemMetrics (SM_CXVSCROLL);

      /* select the font into the window to get text metrics for. */
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      SelectObject (hdc,hfont);
      GetTextMetrics (hdc, &tm);

      nCharPerLine = rect.right / tm.tmAveCharWidth;
      nTotalGlyphs = tm.tmLastChar-tm.tmFirstChar;
      lineHeight = tm.tmHeight + tm.tmExternalLeading;
      nLinesTotal = nTotalGlyphs / nCharPerLine;
      nLinesWindow = rect.bottom / lineHeight;
      nLinesDifference = nLinesTotal - nLinesWindow;
      scrollRangeHigh = nLinesTotal - nLinesWindow;

      /* reset the scroll bar range.  If there is no need for one,
       *  then set the position to 0, and the range to 0,0 and it
       *  will not be visisble.
       */
      if (nLinesDifference >0) {
        SetScrollRange (hwnd, SB_VERT, 0, (nLinesDifference +1), TRUE);
      } else {
        SetScrollPos (hwnd, SB_VERT, 0, TRUE);
        SetScrollRange (hwnd, SB_VERT, 0, 0, TRUE);
      }

      ReleaseDC (hwnd, hdc);
    } return FALSE;

    case WMU_NEWMODE_NEWSEG: {
      gDisplaymode =IDM_MODEALL;
      SetScrollPos (hwnd, SB_VERT, 0, TRUE);

      hdc = GetDC(hwnd);
      GetClientRect (hwnd, &rect);

      /* subtract off the scroll width if it is NOT there.
       *  (if it is, it will be automatically excluded by GetClientRect)
       *  if it is not there, it may be added, and we want space for it.
       */
      GetScrollRange (hwnd, SB_VERT, &min, &max);
      if (min == max) rect.right -= GetSystemMetrics (SM_CXVSCROLL);

      /* select the font into the window to get text metrics for. */
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      SelectObject (hdc,hfont);
      GetTextMetrics (hdc, &tm);

      iSeg = GetScrollPos (hwnd, SB_HORZ);

      /* Set the window title text to show display range. */
      wsprintf (buffer, TEXT("Display [%04x, %04x]"),
                                  (int)startCount[iSeg],
                                  (int)endCount[iSeg]);
      SetWindowText (hwnd, buffer);

      nTotalGlyphs = endCount[iSeg] - startCount[iSeg];

      nCharPerLine = rect.right / tm.tmAveCharWidth;
      nLinesTotal = nTotalGlyphs / nCharPerLine;

      lineHeight = tm.tmHeight + tm.tmExternalLeading;
      nLinesWindow = rect.bottom / lineHeight;
      nLinesDifference = nLinesTotal - nLinesWindow;
      scrollRangeHigh = nLinesTotal - nLinesWindow;

      /* reset the scroll bar range.  If there is no need for one,
       *  then set the position to 0, and the range to 0,0 and it
       *  will not be visisble.
       */
      if (nLinesDifference >0) {
        SetScrollRange (hwnd, SB_VERT, 0, (nLinesDifference +1), FALSE);
      } else {
        SetScrollPos (hwnd, SB_VERT, 0, TRUE);
        SetScrollRange (hwnd, SB_VERT, 0, 0, TRUE);
      }

      ReleaseDC (hwnd, hdc);
    } return FALSE;

    /**********************************************************************\
    *  WM_SIZE
    *
    \**********************************************************************/
    case WM_SIZE:
      /* make sure that scroll metrics are ok */
      switch (gDisplaymode) {
        case IDM_MODEHELLO:   SendMessage (hwnd, WMU_NEWMODEHELLO, 0,0);
          break;
        case IDM_MODETMRANGE: SendMessage (hwnd, WMU_NEWMODETMRANGE, 0,0);
          break;
        case IDM_MODEALL:     SendMessage (hwnd, WMU_NEWMODE_NEWSEG, 0,0);
          break;
      } /* end switch */

    break;  /* fall through to MDIChildProc */

    /**********************************************************************\
    *  WM_PAINT
    *
    * Offset the origin conditional on gDisplaymode.  Write the "Hello"
    *  string, or step though all of the glyphs (from TEXTMETRIC.tmFirstChar
    *  to TEXTMETRIC.tmLastChar), or paint all of the glyphs in the UC segment.
    \**********************************************************************/
    case WM_PAINT: {
      HDC hdc;
      PAINTSTRUCT ps;
      RECT rect;

      hdc = BeginPaint(hwnd, &ps);

      GetClientRect (hwnd, &rect);
      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      SelectObject (hdc,hfont);
      SetBkMode (hdc, TRANSPARENT);
      SetTextAlign (hdc, TA_LEFT | TA_TOP | TA_UPDATECP);
      GetTextMetrics (hdc, &tm);
      MoveToEx (hdc, 0,0, NULL);

      switch (gDisplaymode) {
        case IDM_MODEHELLO: {
          TCHAR szFace[LF_FACESIZE+2];
          TCHAR szBuffer[LF_FACESIZE+100];
          SIZE  ExtentSize;

          SetViewportOrgEx (hdc, rect.right /2, rect.bottom/2, NULL);
          TextOut (hdc, 0, 0, szHello, lstrlen (szHello));
          GetTextFace (hdc, LF_FACESIZE+2, szFace);
          GetTextExtentPoint (hdc, szHello, lstrlen (szHello), &ExtentSize);
          wsprintf (szBuffer,
                    TEXT("GetTextFace: \"%s\",  GetTextExtentPoint: (%d, %d)"),
                    szFace, ExtentSize.cx, ExtentSize.cy);

          SetWindowText (hwndFaceBanner, szBuffer);
        } break;

        case IDM_MODETMRANGE: {
          USHORT start, end;

          topLineShowing= GetScrollPos (hwnd, SB_VERT);
          start = tm.tmFirstChar;
          start += (topLineShowing * nCharPerLine);
          end = tm.tmLastChar;
          BlamGlyphs (hdc, &tm, start, end, &rect, FALSE);

        } break;

        case IDM_MODEALL: {
          USHORT start, end;

          iSeg= GetScrollPos (hwnd, SB_HORZ);
          topLineShowing = GetScrollPos (hwnd, SB_VERT);
          start = startCount[iSeg];
          start += (topLineShowing * nCharPerLine);
          end = endCount[iSeg];

          BlamGlyphs (hdc, &tm, start, end, &rect, FALSE);
        } break;

      } /* end switch */

      EndPaint (hwnd, &ps);

    } return FALSE; /* end WM_PAINT */

    /**********************************************************************\
    *  WMU_PRINT
    *
    * user message.  Get an HDC for the printer, send it formatted
    *  output based on the mode of the display window.
    \**********************************************************************/
    case WMU_PRINT: {
      HDC hdc;
      RECT rect;
      POINT point;
      int nchar;
      int ScrollMin, ScrollMax;
      UINT cbData;
      DOCINFO di;
      di.cbSize      = sizeof(DOCINFO);
      di.lpszDocName = TEXT("ttfonts");
      di.lpszOutput  = NULL;

      SetCursor (LoadCursor (NULL, IDC_WAIT));

      hdc = GetPrinterDC();
      if (hdc == NULL) return 0;
      StartDoc  (hdc, &di);
      StartPage (hdc);

      hfont = (HFONT) GetWindowLong (hwnd, GWL_USERDATA);
      SelectObject (hdc,hfont);
      rect.top = rect.left = 0;
      rect.right = GetDeviceCaps (hdc, HORZRES);
      rect.bottom = GetDeviceCaps (hdc, VERTRES);
      SetBkMode (hdc, TRANSPARENT);
      SetTextAlign (hdc, TA_LEFT | TA_TOP | TA_UPDATECP);
      GetTextMetrics (hdc, &tm);

      /* write a little banner at the top */
      MoveToEx (hdc, 0,0, NULL);
      TextOut (hdc, 0,0,TEXT ("ttfonts -- "), 11);

      cbData = GetOutlineTextMetrics (hdc, 0, NULL);
      if (cbData == 0) {
        TextOut (hdc, 0,0, szFontDataErr,
                lstrlen(szFontDataErr));
      } else {
        LPOUTLINETEXTMETRIC lpoltm;
        LPBYTE lptStr;

        lpoltm = (LPOUTLINETEXTMETRIC)LocalAlloc (LPTR, cbData);
        GetOutlineTextMetrics (hdc, cbData, lpoltm);

        lptStr = (LPBYTE)lpoltm;
        lptStr += (UINT) (PBYTE) lpoltm->otmpFamilyName;
        TextOut (hdc, 0,0, (LPCTSTR) lptStr, lstrlen((LPCTSTR)lptStr));
        LocalFree (LocalHandle (lpoltm));
      }

      /* Draw a thick line, and leave the cursor in the right place. */
      GetCurrentPositionEx (hdc, &point);  /* fill point */
      point.x = rect.right;
      point.y += tm.tmHeight;
      for (i = 0; i<5; i++) {
        MoveToEx (hdc, point.x, point.y, NULL);
        LineTo (hdc, 0, point.y);
        point.y++;
      }

      switch (gDisplaymode) {
        case IDM_MODEHELLO:
          SetViewportOrgEx (hdc, rect.right /2, rect.bottom/2, NULL);
          TextOut (hdc, 0, 0, szHello, lstrlen (szHello));
        break;

        case IDM_MODETMRANGE:
          BlamGlyphs (hdc, &tm, tm.tmFirstChar, tm.tmLastChar, &rect, TRUE);
        break;

        case IDM_MODEALL:
          /* the horz scroll bar has the number of segments stored as
           *  the scroll bar range, query and use it.
           */
          GetScrollRange (hwnd, SB_HORZ, &ScrollMin, &ScrollMax);

          for (i = 0; i<= ScrollMax; i++) {
            MoveToEx (hdc,rect.right, point.y, NULL);
            LineTo (hdc, rect.left, point.y);

            SelectObject (hdc,GetStockObject (DEVICE_DEFAULT_FONT));
            nchar = wsprintf (buffer, TEXT("[%04x, %04x]"), (int)startCount[i],
                                                      (int)endCount[i]);
            TextOut (hdc, 0,0,buffer, nchar);
            SelectObject (hdc,hfont);

            BlamGlyphs (hdc, &tm, startCount[i], endCount[i], &rect, TRUE);

            GetCurrentPositionEx (hdc, &point);
            point.x = 0;
            point.y += (tm.tmHeight + tm.tmExternalLeading);
            MoveToEx (hdc, point.x, point.y, NULL);
            if ((point.y+tm.tmHeight+tm.tmExternalLeading) > rect.bottom) {
              point.x = point.y = 0;
              EndPage   (hdc);
              StartPage (hdc);
              MoveToEx (hdc, point.x, point.y, NULL);
            }
          }
        break;  /* end IDM_MODEALL */
      } /* end switch */

      EndPage   (hdc);
      EndDoc    (hdc);

      DeleteDC (hdc);
      SetCursor (LoadCursor (NULL, IDC_ARROW));

    } return TRUE;

  } /* end switch */

  return (DefMDIChildProc(hwnd, message, wParam, lParam));
}

/**************************************************************************\
*
*  function:  BlamGlyphs
*
*  input parameters:
*   hdc with font selected into it.
*   pointer to a text metric structure.
*   start and end of code point range to print
*   pointer to rectangle which serves as bounds.
*
*  Starting at the current point, write one glyph after the other, starting
*   with the "Start" parameter, and ending with the "End" parameter.  If
*   the next character will overrun on the right, start again on a new line.
*  Special case Start = 0xfff which happens because the start/endCount
*   arrays always end with this value.
*
*
  // UNICODE NOTICE
  //  This function is held as unicode because it needs to display
  //  wide characters (i.e. need TextOutW).
*
*
\**************************************************************************/
VOID BlamGlyphs (HDC hdc, PTEXTMETRIC ptm, USHORT Start, USHORT End, PRECT prect, BOOL printing)
{
USHORT i;
POINT point;
WCHAR outChar;

  if (Start == 0xffff) return;

  for (i = Start; i<=End; i++) {

    /* special case the non-spacing diacritic marks. U+0300 -> U+036F
     *  Write a space first, for them to 'modify.'
     */
    if ( (0x0300 <= i) && (i <= 0x036F) ) {
      outChar = (WCHAR) 0x0020;
      TextOutW (hdc, 0,0, &outChar, 1);
    }

    outChar = (WCHAR) i;
    TextOutW (hdc, 0,0, &outChar, 1);

    /* Watch for overflow in x */
    GetCurrentPositionEx (hdc, &point);
    if (point.x > (prect->right - ptm->tmAveCharWidth)) {
      point.x = 0;
      point.y += (ptm->tmHeight + ptm->tmExternalLeading);
      MoveToEx (hdc, point.x, point.y, NULL);
    }

    /* Watch for overflow in y */
    if (printing) {
      if (point.y > (prect->bottom-(ptm->tmHeight+ptm->tmExternalLeading))) {
        EndPage   (hdc);
        StartPage (hdc);
        point.x = point.y = 0;
        MoveToEx (hdc, point.x, point.y, NULL);
      }
    } else {
      if (point.y > prect->bottom) {
        return;
      }
    }
  } /* end for (i= Start; i<=End ... */
}

/**************************************************************************\
*
* All of the code below is used for parsing "font data."  It will only
*  make sense if you have a copy of the True Type font spec.  In short,
*  we grab the 'cmap' table, look through it for a subtable, and then
*  get two parallel arrays from the subtable.  Complications arise because
*  the true type data is "BIG ENDIAN" and NT is being run "little endian."
*  For this reason, once we locate the short or long, we call Swap* to
*  change the byte ordering.
*
\**************************************************************************/

VOID SwapShort (PUSHORT);
VOID SwapULong  (PULONG);

typedef struct tagTABLE{
    USHORT platformID;
    USHORT encodingID;
    ULONG  offset;
} TABLE, *PTABLE;

typedef struct tagSUBTABLE{
    USHORT format;
    USHORT length;
    USHORT version;
    USHORT segCountX2;
    USHORT searchRange;
    USHORT entrySelector;
    USHORT rangeShift;
} SUBTABLE, *PSUBTABLE;

/* 'cmap' is passed in by value in a DWORD */
#define CMAPHEX  0x70616d63
#define NBYTES   256
#define OFFSETERROR  0

/**************************************************************************\
*
*  function:  CountUCSegments()
*
*  input parameters:
*   hdc - with the logical font set into it.
*   prect - pointer to client rectangle.

*
*  Global variables:
*   startCount
*   endCount
*
*  returns:
*   number of UC segments or
*   SEGMENTERROR if there is some kind of error.
*
*  essential side effect:
*   Fills in global startCount, endCount arrays.
\**************************************************************************/
int CountUCSegments (HDC hdc)
{
DWORD       cbData;
USHORT      aShort[2];
DWORD       nBytes;
USHORT      i, nTables;
PTABLE      pTable;
PSUBTABLE   pSubTable;
ULONG       offset,offsetFormat4;
USHORT      segCount;
BYTE        buffer[NBYTES];

    /* find number of "subtables", second long in cmap */
    nBytes=GetFontData (hdc, CMAPHEX, 0, aShort, 4);
    if (nBytes == GDI_ERROR) {
      MessageBox (NULL, szFontDataErr, szMBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }
    if (nBytes == 0) {
      MessageBox (NULL, szNoCMAPTable, szMBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }
    nTables = aShort[1];
    SwapShort (&nTables);

    cbData = nTables * sizeof(TABLE);
    if (cbData >NBYTES) {
      MessageBox (NULL, szCBDataTooBig, szMBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* get array of subtables information.  Check each one for 3,1*/
    nBytes=GetFontData (hdc, CMAPHEX, 4, buffer, cbData);
    pTable = (PTABLE)buffer;
    offsetFormat4 = OFFSETERROR;
    for (i = 0; i< nTables; i++) {

        SwapShort (&(pTable->encodingID));
        SwapShort (&(pTable->platformID));

        if ((pTable->platformID == 3)&&(pTable->encodingID == 1)) {
          offsetFormat4 = pTable->offset;
          SwapULong (&offsetFormat4);
          break;
        }
        pTable++;
    }

    /* verify that we got the correct offset to the FORMAT 4 subtable */
    if (offsetFormat4 == OFFSETERROR){
      MessageBox (NULL, szNoTable, szMBERROR , MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* Get the beginning of the subtable, especially the segment count */
    nBytes=GetFontData (hdc, CMAPHEX, offsetFormat4, buffer, sizeof(SUBTABLE));
    pSubTable = (PSUBTABLE) buffer;
    SwapShort (&(pSubTable->format));
    SwapShort (&(pSubTable->segCountX2));

    if (pSubTable->format != 4){
      MessageBox (NULL, szFormatNot4, szMBERROR, MBERRORFLAGS);
      return SEGMENTERROR;
    }

    segCount = pSubTable->segCountX2 / 2;

    /* Now that we know how many segments that the font contains,
     *  free up the old memory, and realloc. the two global arrays.
     */
    if (startCount != NULL) LocalFree (LocalHandle (startCount));
    if (endCount != NULL) LocalFree (LocalHandle (endCount));
    startCount = LocalAlloc (LPTR, segCount * sizeof(USHORT));
    endCount = LocalAlloc (LPTR, segCount * sizeof(USHORT));

    if ((startCount == NULL) || (endCount == NULL)) {
      MessageBox (NULL, szAllocFailed, szMBERROR, MBERRORFLAGS);
      return SEGMENTERROR;
    }

    /* read in the array of endCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT));  /* skip constant # bytes in subtable */
    cbData = segCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, endCount, cbData );
    for (i = 0; i<segCount; i++)
        SwapShort (& (endCount[i]));

    /* read in the array of startCount values */
    offset = offsetFormat4
           + (7 * sizeof (USHORT))   /* skip constant # bytes in subtable */
           + (segCount * sizeof (USHORT)) /* skip endCount array */
           + sizeof (USHORT);             /* skip reservedPad */
    cbData = segCount * sizeof (USHORT);
    nBytes=GetFontData (hdc, CMAPHEX, offset, startCount, cbData );
    for (i = 0; i<segCount; i++)
        SwapShort (& (startCount[i]));

    return segCount;
}

VOID SwapShort (PUSHORT p)
{
SHORT temp;

    temp =(SHORT)( HIBYTE (*p) + (LOBYTE(*p) << 8));
    *p = temp;
}

VOID SwapULong (PULONG p)
{
ULONG temp;

    temp = (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    temp <<= 8;
    *p >>=8;

    temp += (LONG) ((BYTE) *p);
    *p = temp;
}


ALLFONT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  allfont.c -- module to display the result of enumerating all fonts.
*   Includes the window procedure and an initialization routine.
*
*  There are actually two windows serviced by this module.  One for the
*   display fonts, one for the printer fonts.
*
*
*  Data structure:
*   Since we keep a modest amount of data for each possible font, and
*   since the amount of this information is known only at run time,
*   the main data structure used here is all allocated dynamically.
*   parFonts points to an array of ARFONT structures.  These structures
*   store two int's and then three pointers to arrays of LOGFONT,
*   TEXTMETRIC, and int's respectively.
*
*   parFonts--> |--------------------|  |--------------------|
*               |  nFonts (Family0)  |  |  nFonts (Family1)  |
*               |       cySpace      |  |       cySpace      | ...
*               |        hdc         |  |        hdc         |
*               |--------------------|  |--------------------|
*               | lf  |  tm |  Type  |  | lf  |  tm |  Type  |
*               |--------------------|  |--------------------|
*                 |       |       |       |       |       |
*                 |       |       |       |       |       |
*                 V       V       V       V       V       V
*               |----| |------| |----|  |----| |------| |----|
*               |LOG | |TEXT  | |Type|  |LOG | |TEXT  | |Type|
*               |FONT| |METRIC| |    |  |FONT| |METRIC| |    |
*               |0,0 | |0,0   | |0,0 |  |1,0 | |1,0   | |1,0 |
*               |----| |------| |----|  |----| |------| |----|
*               |LOG | |TEXT  | |Type|  |LOG | |TEXT  | |Type|
*               |FONT| |METRIC| |    |  |FONT| |METRIC| |    |
*               |0,1 | |0,1   | |0,1 |  |1,1 | |1,1   | |1,1 |
*               |----| |------| |----|  |----| |------| |----|
*
*                ...    ...      ...     ...    ...      ...
*
*               |----| |------| |----|
*               |LOG | |TEXT  | |Type|
*               |FONT| |METRIC| |    |  |----| |------| |----|
*               |0,  | |0,    | |0,  |  |LOG | |TEXT  | |Type|
*               |nFon| |nFonts| |nFon|  |FONT| |METRIC| |    |
*               |----| |------| |----|  |1,  | |1,    | |1,  |
*                                       |nFon| |nFonts| |nFon|
*                                       |----| |------| |----|
*
*
*   So, to get the FontType of the 4th font within the 2nd family use:
*    parFonts[1].Type[3]
*
*   There is one final ARFONT structure which has no linked lists atttached,
*    but has a blank HDC as padding on the far right of the window.
*
*   The pointer to the array of ARFONT structures is stored in the
*    GWL_PARFONTS extra bytes for each window.  The number of face names
*    for each window is stored in the GWLU_NFACES extra bytes.
*
\**************************************************************************/

#include <windows.h>
#include <string.h>
#include "ttfonts.h"

typedef struct tagArFonts{
  int        nFonts;
  int        cySpace;
  HDC        hdc;
  LOGFONT    *lf;
  TEXTMETRIC *tm;
  int        *Type;
} ARFONTS, *PARFONTS;

#define ALLFONTBORDER  10
#define CXDEF         130
#define BMSIZE         14

#define GWLU_PARFONTS 0
#define GWLU_NFACES   4

/* forward declare function prototypes. */
VOID     DrawBitmapXY (HDC, HBITMAP, int, int);

PARFONTS BuildFontList(HDC, LPINT);
VOID     BuildBitmapStrips (HWND, LPRECT, PARFONTS, int);

int APIENTRY MyEnumFaces(LPLOGFONT, LPTEXTMETRIC, DWORD, LPVOID);
int APIENTRY MyEnumCopy (LPLOGFONT, LPTEXTMETRIC, DWORD, LPVOID);
int APIENTRY MyEnumCount(LPLOGFONT, LPTEXTMETRIC, DWORD, LPINT);

int initAllFont(HWND hwndParent)
{
WNDCLASS  wc;
HDC       hdc;

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC)AllFontsWndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 8;
  wc.hInstance = hInst;
  wc.hIcon = LoadIcon(hInst, TEXT("ttfontsIcon"));
  wc.hCursor = LoadCursor(NULL, IDC_CROSS);
  wc.hbrBackground = GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = TEXT("AllFonts");

  if (!RegisterClass(&wc)) return (FALSE);

  /* create a window to show all of the display fonts, and send it
   *  the proper WMU_CREATE message with an HDC for the display.
   */
  hwndDisplayFonts = CreateMDIWindow(
      TEXT("AllFonts"),
      TEXT("EnumFonts (display)"),
      WS_CHILD | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
      WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_HSCROLL,
      0,0,
      GetSystemMetrics (SM_CXFULLSCREEN),
      GetSystemMetrics (SM_CYFULLSCREEN) - GetSystemMetrics (SM_CYMENU),
      hwndParent, hInst, 0);

  ShowWindow (hwndDisplayFonts, SW_HIDE);
  hdc = GetDC (hwndDisplayFonts);
  SendMessage (hwndDisplayFonts, WMU_CREATE, (DWORD)hdc, 0);
  ReleaseDC (hwndDisplayFonts, hdc);
  ShowWindow (hwndDisplayFonts, SW_SHOWMINIMIZED);

  if (!hwndDisplayFonts) return (FALSE);

  /* create a window to show all of the printer fonts, and send it
   *  the proper WMU_CREATE message with an HDC for the printer.
   */

  hwndPrinterFonts = CreateMDIWindow(
      TEXT("AllFonts"),
      TEXT("EnumFonts (printer)"),
      WS_CHILD | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
      WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_HSCROLL,
      0,0,
      GetSystemMetrics (SM_CXFULLSCREEN),
      GetSystemMetrics (SM_CYFULLSCREEN) - GetSystemMetrics (SM_CYMENU),
      hwndParent, hInst, 0);
  if (!hwndPrinterFonts) return (FALSE);

  ShowWindow (hwndPrinterFonts, SW_HIDE);
  hdc = GetPrinterDC ();
  if (hdc == NULL) {
    SetWindowLong (hwndPrinterFonts, GWLU_PARFONTS, (LONG) NULL);
    DestroyWindow (hwndPrinterFonts);
    SendMessage (hwndMain, WM_COMMAND, IDU_NOPRINTER, 0);
  } else {
    SendMessage (hwndPrinterFonts, WMU_CREATE, (DWORD)hdc, 0);
    DeleteDC (hdc);
    ShowWindow (hwndPrinterFonts, SW_SHOWMINIMIZED);
  }

  return TRUE;
}

/**************************************************************************\
*  function:  GetPrinterDC
*
*  input parameters:  none.
*
* Simply return an HDC for the default printer.
*  Note that this HDC should be deleted later (DeleteDC).
*
*
\**************************************************************************/
HDC GetPrinterDC ()
{
  PRINTER_INFO_2  *pi2;
  DWORD  dwNeeded, dwReturned;
  HDC hdc;

  /* Figure out how much memory we need and alloc it. */
  EnumPrinters (PRINTER_ENUM_DEFAULT | PRINTER_ENUM_LOCAL,
                 NULL, 2,
                 NULL, 0,
                 &dwNeeded, &dwReturned);

  /* Watch for case of no default printer... return NULL. */
  if (dwNeeded == 0) return NULL;

  pi2 = LocalAlloc (LPTR, dwNeeded);

  /* Now actually get the information, and create an HDC if successful. */
  if (EnumPrinters (PRINTER_ENUM_DEFAULT | PRINTER_ENUM_LOCAL,
                 NULL, 2,
                 (LPBYTE) pi2, dwNeeded,
                 &dwNeeded, &dwReturned)) {

    hdc = CreateDC (pi2->pDriverName, pi2->pPrinterName, pi2->pPortName, NULL);
  } else {
    MessageBox (NULL, szEnumPrinterR, szMBERROR,MBERRORFLAGS);
    hdc = NULL;
  }

  /* Free memory */
  LocalFree (LocalHandle (pi2));

  return hdc;
}

/**************************************************************************\
*
*  function:  AllFontsWndProc
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   hwndMain - Main window.  place to return focus.
*
* Create array of structures which contain, among other things, bitmap
*  strips containing the face names of fonts.  These strips are later
*  scrolled back and forth as well as selected with the mouse.
*
\**************************************************************************/
LRESULT CALLBACK AllFontsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rectClient;
int i,j;
int thumbpos;
int nStrips;
PARFONTS parFonts;
int nFaces;

  /* Get the pointer out of the userdata extra bytes.  */
  parFonts = (PARFONTS) GetWindowLong (hwnd, GWLU_PARFONTS);
  nFaces   =            GetWindowLong (hwnd, GWLU_NFACES);

  if ((parFonts == NULL) && (message != WMU_CREATE))
    return (DefMDIChildProc(hwnd, message, wParam, lParam));

  switch (message) {

    /**********************************************************************\
    *  WMU_CREATE
    *
    * USER create message (sent after the window is created, before it
    *  is shown for the first time).  Use this rather than WM_CREATE because
    *  we want to have control over the message parameters.
    *
    * wParam - HDC which we build the font list for.
    *
    * First build the 2d variable arrays of fonts (c.f. BuildFontList), then
    *  create the bitmap strips (stored in HDCs in ARFONT structures),
    *  Set the parFonts pointer into the extra bytes for use by other messages.
    \**********************************************************************/
    case WMU_CREATE: {
      int rightPos;
      HDC hdc;

      hdc = (HDC) wParam;

      GetClientRect (hwnd, &rectClient);
      InflateRect (&rectClient, 0, -ALLFONTBORDER);

      parFonts = BuildFontList (hdc, &nFaces);

      BuildBitmapStrips (hwnd, &rectClient, parFonts, nFaces);

      SetWindowLong (hwnd, GWLU_PARFONTS, (LONG) parFonts);
      SetWindowLong (hwnd, GWLU_NFACES,   nFaces);

      /* Compute the number of bitmap strips visible in the window.
       * set the scroll bar range,
       * or make it invisible if there are not enough fonts to justify.
       */
      nStrips = (rectClient.right - rectClient.left) /CXDEF + 1;
      if (nFaces > nStrips) {
        rightPos = nFaces - nStrips + 1;
        SetScrollRange (hwnd, SB_HORZ, 0, rightPos, FALSE);
      } else {
        SetScrollRange (hwnd, SB_HORZ, 0, 0, FALSE);
      }

    } break;

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Free up the memory allocated at create time.  Notice that parFonts
    *  is allocated in BuildFontList, as are its subarrays.
    \**********************************************************************/
    case WM_DESTROY:
      for (i= 0; i<nFaces; i++) {
        LocalFree ( LocalHandle ((LPVOID)parFonts[i].lf   ));
        LocalFree ( LocalHandle ((LPVOID)parFonts[i].tm   ));
        LocalFree ( LocalHandle ((LPVOID)parFonts[i].Type ));
        DeleteDC (parFonts[i].hdc);
      }
      /* also get the final, blank one */
      DeleteDC (parFonts[nFaces].hdc);

      LocalFree ( LocalHandle ((LPVOID)parFonts));
    break;

    /**********************************************************************\
    *  WM_HSCROLL
    *
    * Slide the contents of the window back and forth.  Notice that the
    *  scroll bar thumb position is important for painting and hit testing.
    \**********************************************************************/
    case WM_HSCROLL:
      /* compute number of strips for page scrolling. */
      GetClientRect (hwnd, &rectClient);
      nStrips = (rectClient.right - rectClient.left) /CXDEF + 1;

      switch (LOWORD(wParam)){
        int OldPos, NewPos;

        case SB_LINEDOWN:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos+1), TRUE);
          NewPos = GetScrollPos (hwnd, SB_HORZ);
          ScrollWindow (hwnd, (OldPos-NewPos)*CXDEF, 0, NULL, NULL);
        break;

        case SB_PAGEDOWN:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos+(nStrips-1)), TRUE);
          NewPos = GetScrollPos (hwnd, SB_HORZ);
          ScrollWindow (hwnd, (OldPos-NewPos)*CXDEF, 0, NULL, NULL);
        break;

        case SB_LINEUP:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos-1), TRUE);
          NewPos = GetScrollPos (hwnd, SB_HORZ);
          ScrollWindow (hwnd, (OldPos-NewPos)*CXDEF, 0, NULL, NULL);
        break;

        case SB_PAGEUP:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          SetScrollPos (hwnd, SB_HORZ, (OldPos-(nStrips-1)), TRUE);
          NewPos = GetScrollPos (hwnd, SB_HORZ);
          ScrollWindow (hwnd, (OldPos-NewPos)*CXDEF, 0, NULL, NULL);
        break;

        case SB_THUMBPOSITION:
          OldPos = GetScrollPos (hwnd, SB_HORZ);
          NewPos = HIWORD (wParam);
          SetScrollPos (hwnd, SB_HORZ, NewPos, TRUE);
          ScrollWindow (hwnd, (OldPos-NewPos)*CXDEF, 0, NULL, NULL);
        break;

      }
    break;

    /**********************************************************************\
    *  WM_LBUTTONDOWN
    *
    * Hittest based on the mouse position.  When finished, hide the window.
    \**********************************************************************/
    case WM_LBUTTONDOWN: {
      POINT mousePt;

      mousePt.x = LOWORD(lParam);
      mousePt.y = HIWORD(lParam);

      mousePt.x -= 0;
      mousePt.y -= ALLFONTBORDER;

      i = mousePt.x / CXDEF;
      i += GetScrollPos (hwnd, SB_HORZ);
      if (i < 0) i = 0;
      if (i >= nFaces) i = (nFaces-1);

      j = mousePt.y / parFonts[i].cySpace;
      if (j < 0) j = 0;
      if (j >= parFonts[i].nFonts) j = (parFonts[i].nFonts-1);

      ShowWindow(hwnd, SW_MINIMIZE);
      UpdateWindow(hwndMain);
      flyWinWin(hwndMain,  hwnd, hwndDlgLF, 40);
      SendMessage (hwndDlgLF, WMU_DEMOTOLF, 0,(LONG) &(parFonts[i].lf[j]));

      flyWinWin(hwndMain,  hwnd, hwndDlgTM, 40);
      SendMessage (hwndDlgTM, WMU_DEMOTOTM, 0,(LONG) &(parFonts[i].tm[j]));
      SetFocus (hwndMain);

    }break;

    /**********************************************************************\
    *  WM_RBUTTONDOWN, WM_CHAR
    *
    * Put the window away without changing the LOGFONT or TEXTMETRIC dlgs.
    \**********************************************************************/
    case WM_RBUTTONDOWN:
    case WM_CHAR       :
      ShowWindow(hwnd, SW_MINIMIZE);
      SetFocus (hwndMain);
    break;

    /**********************************************************************\
    *  WM_PAINT
    *
    * Using the HDCs stored in the parFonts array, blt enough strips of
    *  font family names to cover the window.
    \**********************************************************************/
    case WM_PAINT : {
      PAINTSTRUCT ps;
      HDC hdc;
      int iStrip;

      if (parFonts == NULL)  break;

      GetClientRect (hwnd, &rectClient);
      hdc = BeginPaint(hwnd, &ps);

      thumbpos = GetScrollPos (hwnd, SB_HORZ);

      nStrips = (rectClient.right - rectClient.left) /CXDEF + 1;

      for (iStrip= 0; iStrip<nStrips; iStrip++)
          BitBlt (hdc, iStrip*CXDEF,0,
                  CXDEF,
                  (rectClient.bottom - rectClient.top),
                  parFonts[iStrip+thumbpos].hdc, 0,0, SRCCOPY);

      EndPaint (hwnd, &ps);
    } break;

    default: break;
    } /* end switch */

  return (DefMDIChildProc(hwnd, message, wParam, lParam));
}

/**************************************************************************\
*
*  function:  DrawBitmapXY()
*
*  input parameters:  HDC, HBITMAP,
*   X, Y - destination position.  (get width and height from hbitmap)
*
*  Draw the bitmap into the hdc.  Source rectangle computed to include the
*   whole bitmap.
*
*  global variables: none.
*
\**************************************************************************/
VOID DrawBitmapXY (HDC hdc, HBITMAP hbm, int x, int y)
{
    BOOL f;
    HDC hdcBits;
    BITMAP bm;

    hdcBits = CreateCompatibleDC(hdc);
    GetObject (hbm, sizeof(BITMAP), &bm);
    SelectObject(hdcBits,hbm);
    f = BitBlt(hdc,x,y,bm.bmWidth, bm.bmHeight,hdcBits,0,0,SRCCOPY);
    DeleteDC(hdcBits);
}

/**************************************************************************\
*  function:  BuildBitmapStrips
*
*  input parameters:
*   hwnd - window this will all be painted on some day, needed for GetDC
*   lprectClient - Pointer to client window rect, bitmap strips are this high
*   parFonts - array with all the font information.
*   nFaces - number of face names (array size of parFonts).
*
* Once the fonts have all been queried, create bitmaps and HDCs for them, and
*  draw the different fonts into the different bitmap strips.
\**************************************************************************/
VOID BuildBitmapStrips (HWND hwnd, LPRECT lprectClient,
                        PARFONTS parFonts, int nFaces)
{
int     i,j;
HFONT   hNewFont;
HBITMAP hbm;
HBITMAP hbmtt, hbmdevice;
RECT    rectClip, rectStrip;
HDC     hdc;
int     height;
int     rightshift;

  /* load 2 bitmaps used to "tag" true type and printer fonts. */
  hbmtt =     LoadBitmap (hInst, TEXT("bmtt"));
  hbmdevice = LoadBitmap (hInst, TEXT("bmdevice"));

  /* establish bounds of vertical strip that all fonts are drawn into. */
  hdc              = GetDC (hwnd);
  rectStrip.left   = rectStrip.top =0;
  rectStrip.right  = CXDEF;
  rectStrip.bottom = (lprectClient->bottom - lprectClient->top);

  /* step through all of the face names.  For each one, compute the cySpace-
   *  i.e. number of pixels per entry.  also create a memory DC that we can
   *  draw the font into.  This DC provides the STRIP later blt'ed out at
   *  paint time.
   */
  for (i= 0; i<nFaces; i++) {
    parFonts[i].cySpace = rectStrip.bottom / parFonts[i].nFonts;
    parFonts[i].hdc     = CreateCompatibleDC (hdc);

    /* When there is only one font, then make a small bitmap.  If there
     * is more than one, then make a bitmap the whole height of the window.
     */
    height = (parFonts[i].nFonts == 1) ?
      parFonts[i].lf[0].lfHeight + ALLFONTBORDER :
      rectStrip.bottom;
    hbm = CreateCompatibleBitmap (hdc, rectStrip.right,height);

    SelectObject (parFonts[i].hdc, hbm);
    FillRect (parFonts[i].hdc, &rectStrip, GetStockObject(WHITE_BRUSH));
    SelectObject (parFonts[i].hdc, GetStockObject(NULL_BRUSH));

    /* step through each font for the face name.  Establish a bounding
     *  rectangle for the name, write the name and mark with bitmaps if apropos
     */
    for (j = 0; j<parFonts[i].nFonts; j++) {
      rectClip.left   = 0;
      rectClip.right  = rectClip.left + CXDEF - 5;
      rectClip.top    = j*parFonts[i].cySpace+ALLFONTBORDER;
      rectClip.bottom = rectClip.top+ parFonts[i].lf[j].lfHeight;
      rightshift = 0;

      /* for true type fonts, add special bitmap on left, top */
      if (parFonts[i].Type[j] & TRUETYPE_FONTTYPE) {
        DrawBitmapXY (parFonts[i].hdc, hbmtt,rectClip.left,rectClip.top);
        rightshift = BMSIZE;
      }

      /* for printer fonts, add printer bitmap on left, shifted down
       */
      if (parFonts[i].Type[j] & DEVICE_FONTTYPE) {
        DrawBitmapXY (parFonts[i].hdc, hbmdevice,rectClip.left,rectClip.top+ BMSIZE);
        rightshift = BMSIZE;
      }

      /* Create a logical font to draw the face name with */
      hNewFont = CreateFontIndirect (&(parFonts[i].lf[j]));
      SelectObject (parFonts[i].hdc, hNewFont);

      /* iff bitmap drawn, shift text over to the right. */
      rectClip.left+=rightshift;

      /* draw the face name in the rectangle allotted. */
      ExtTextOut (parFonts[i].hdc,
                       rectClip.left, rectClip.top, ETO_CLIPPED,
                       &rectClip,
                       parFonts[i].lf[j].lfFaceName,
                       lstrlen(parFonts[i].lf[j].lfFaceName),
                       NULL );

      SelectObject (parFonts[i].hdc, GetStockObject (SYSTEM_FONT));
      DeleteObject (hNewFont);
    }
  }

  /* make one final strip on the far right side as a white space buffer */
  hbm = CreateCompatibleBitmap (hdc, rectStrip.right, rectStrip.bottom);
  SelectObject (parFonts[nFaces].hdc, hbm);
  FillRect (parFonts[nFaces].hdc, &rectStrip, GetStockObject(WHITE_BRUSH));

  ReleaseDC (hwnd, hdc);
  DeleteObject (hbmtt);
  DeleteObject (hbmdevice);
}

/* In the callback functions from the enumerations, there is a limited
 *  ability to pass in parameters.  For that reason, declare the following
 *  global variables to be used by any of the call back functions.
 */
HDC      hdcGlobal;
PARFONTS parFontsGlobal;
int      iFace,jFont;
int      nFaces;

/* General call structure:
 *
 *  BuildFontList()
 *      EnumFonts
 *          MyEnumCount()
 *      LocalAlloc
 *      EnumFonts
 *          MyEnumFaces()
 *              EnumFonts
 *                  MyEnumCount()
 *              LocalAlloc
 *              LocalAlloc
 *              LocalAlloc
 *              EnumFonts
 *                  MyEnumCopy()
 */

/**************************************************************************\
*  function:  BuildFontList
*
*  input parameters:
*   hdcIn - hdc to query the fonts for.
*   retnFaces - (OUT) pointer to nFaces value
*
* Enumerate all of the fonts for this DC allocating and filling up the
*  parFonts structure as needed.
\**************************************************************************/
PARFONTS BuildFontList(HDC hdcIn, LPINT retnFaces)
{

  nFaces = 0;

  /* hdcGlobal is global variable also used by the callback functions. */
  hdcGlobal = hdcIn;

  /* count the total number of face names. */
  EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumCount, (LPARAM)&nFaces);

  /* allocate the pointer to the array of PArFont structures. */
  parFontsGlobal = (PARFONTS)LocalAlloc (LPTR, sizeof(ARFONTS) * (nFaces+1));

  /* step through all fonts again.  For each one fill a LOGFONT and
   * a TEXTMETRIC stucture.
   */
  iFace = 0;
  EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumFaces, (LPARAM)NULL);

  *retnFaces = nFaces;
  return parFontsGlobal;
}

/**************************************************************************\
*  function:  MyEnumFaces
*
*  input parameters:  c.f. EnumFonts
*
* Count the number of fonts for this particular face name.  Allocate the
*  LOGFONT and TEXTMETRIC arrays large enough to hold all of them.  Then
*  enumerate the fonts once again to fill in these arrays.
\**************************************************************************/
int APIENTRY MyEnumFaces(
       LPLOGFONT  lpLogFont,
       LPTEXTMETRIC lpTEXTMETRICs,
       DWORD  fFontType,
       LPVOID  lpData)
{
int nFonts;

  UNREFERENCED_PARAMETER (lpTEXTMETRICs);
  UNREFERENCED_PARAMETER (fFontType);
  UNREFERENCED_PARAMETER (lpData);

  nFonts = 0;
  EnumFonts (hdcGlobal, lpLogFont->lfFaceName, (FONTENUMPROC)MyEnumCount, (LPARAM)&nFonts);

  parFontsGlobal[iFace].lf   = (LPLOGFONT)LocalAlloc (LPTR, sizeof(LOGFONT)    * nFonts);
  parFontsGlobal[iFace].tm   = (LPTEXTMETRIC)LocalAlloc (LPTR, sizeof(TEXTMETRIC) * nFonts);
  parFontsGlobal[iFace].Type = (LPINT)LocalAlloc (LPTR, sizeof(int)        * nFonts);

  if ((parFontsGlobal[iFace].lf   == NULL) ||
      (parFontsGlobal[iFace].tm   == NULL) ||
      (parFontsGlobal[iFace].Type == NULL)) {
    MessageBox (hwndMain, szAllocFailed, szMBERROR,MBERRORFLAGS);
    return FALSE;
  }

  parFontsGlobal[iFace].nFonts = nFonts;

  jFont = 0;
  EnumFonts (hdcGlobal, lpLogFont->lfFaceName, (FONTENUMPROC)MyEnumCopy, (LPARAM)NULL);

  iFace++;

  return TRUE;
}

/**************************************************************************\
*  function:  MyEnumCopy
*
*  input parameters:  c.f. EnumFonts
*
* Each time that this function is called, copy the LOGFONT and TEXTMETRIC
*  structures into the proper place in the global arrays.  Incr jFont.
\**************************************************************************/
int APIENTRY MyEnumCopy(
       LPLOGFONT  lpLogFont,
       LPTEXTMETRIC lpTEXTMETRICs,
       DWORD  fFontType,
       LPVOID  lpData)
{
LOGFONT    *lplf;
TEXTMETRIC *lptm;
int        *pType;

  UNREFERENCED_PARAMETER (lpData);

  lplf  = parFontsGlobal[iFace].lf;
  lptm  = parFontsGlobal[iFace].tm;
  pType = parFontsGlobal[iFace].Type;

  lplf[jFont]  = *lpLogFont;
  lptm[jFont]  = *lpTEXTMETRICs;
  pType[jFont] = fFontType;

  jFont++;
  return TRUE;
}

/**************************************************************************\
*  function:  MyEnumCount
*
*  input parameters:  c.f. EnumFonts
*
* Simply increment the variable that lpData points to.
\**************************************************************************/
int APIENTRY MyEnumCount(
       LPLOGFONT  lpLogFont,
       LPTEXTMETRIC lpTEXTMETRICs,
       DWORD  fFontType,
       LPINT  lpData)
{
    UNREFERENCED_PARAMETER (lpLogFont);
    UNREFERENCED_PARAMETER (lpTEXTMETRICs);
    UNREFERENCED_PARAMETER (fFontType);

    (*lpData)++;
    return TRUE;
}


TOOLBAR.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  toolbar.c -- module for the "toolbar" on top of the main window.
*   Includes the window procedure and an initialization routine.
\**************************************************************************/

#include <windows.h>
#include "ttfonts.h"

/* for the initial positioning of the buttons within the toolbar. */
#define SPACEBUTTON 8
#define CXBUTTON ((GetSystemMetrics (SM_CXFULLSCREEN)) /5 -2*SPACEBUTTON)
#define BUTTONTOP    TOOLBARHEIGHT/8
#define BUTTONHEIGHT TOOLBARHEIGHT*3/4
#define BUTTONLEFT(x) ((2*x+1)*SPACEBUTTON + x*CXBUTTON)

#define BORDER     2

int initTB (HWND hwndParent)
{
WNDCLASS  wc;

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC)ToolBarWndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInst;
  wc.hIcon = NULL;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = TEXT("ToolBar");

  if (!RegisterClass(&wc)) return (FALSE);

  hwndTB = CreateWindow(
      TEXT("ToolBar"),
      NULL,
      WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
      0,0,
      GetSystemMetrics (SM_CXFULLSCREEN),
      TOOLBARHEIGHT,
      hwndParent, NULL, hInst, NULL);

  if (!hwndTB) return (FALSE);

  return TRUE;
}

/**************************************************************************\
*
*  function:  ToolBarWndProc
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   hwndMain - parent of the toolbar.
*
* When the window is created, create the various buttons.  When those
*  buttons send WM_COMMAND messages later, send the messages back to hwndMain.
*
\**************************************************************************/
LRESULT CALLBACK ToolBarWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND  hwndButton, hwndEnumPrinter;
  switch (message) {

    /**********************************************************************\
    *  WM_CREATE
    *
    * Create the various buttons which are on the toolbar.  Once the buttons
    *  are created, set the window ID so that the WM_COMMANDS may be
    *  distinguished.
    \**********************************************************************/
    case WM_CREATE: {
      hwndButton = CreateWindow(
        TEXT("BUTTON"),TEXT("EnumFonts"),
        WS_CHILD | WS_VISIBLE,
        BUTTONLEFT(0),BUTTONTOP, CXBUTTON, BUTTONHEIGHT,
        hwnd, NULL, hInst, NULL);
      SetWindowLong (hwndButton, GWL_ID, TBID_ENUM);

      hwndButton = CreateWindow(
        TEXT("BUTTON"),TEXT("CreateFont"),
        WS_CHILD | WS_VISIBLE,
        BUTTONLEFT(1),BUTTONTOP, CXBUTTON, BUTTONHEIGHT,
        hwnd, NULL, hInst, NULL);
      SetWindowLong (hwndButton, GWL_ID, TBID_CREATE);

      hwndButton = CreateWindow(
        TEXT("BUTTON"),TEXT("GetMetrics"),
        WS_CHILD | WS_VISIBLE ,
        BUTTONLEFT(2),BUTTONTOP, CXBUTTON, BUTTONHEIGHT,
        hwnd, NULL, hInst, NULL);
      SetWindowLong (hwndButton, GWL_ID, TBID_GETTM);

      hwndButton = CreateWindow(
        TEXT("BUTTON"),TEXT("GetFontData"),
        WS_CHILD | WS_VISIBLE ,
        BUTTONLEFT(3),BUTTONTOP, CXBUTTON, BUTTONHEIGHT,
        hwnd, NULL, hInst, NULL);
      SetWindowLong (hwndButton, GWL_ID, TBID_GETFONTDATA);

      hwndEnumPrinter = CreateWindow(
        TEXT("BUTTON"),TEXT("Enum(Printer)"),
        WS_CHILD | WS_VISIBLE,
        BUTTONLEFT(4),BUTTONTOP, CXBUTTON, BUTTONHEIGHT,
        hwnd, NULL, hInst, NULL);
      SetWindowLong (hwndEnumPrinter, GWL_ID, TBID_PRINT);

    } break;

    /**********************************************************************\
    *  WM_COMMAND
    *
    * Send the command messages back to hwndMain.
    *  except for the one to disable the printer button.
    \**********************************************************************/
    case WM_COMMAND:
      if (wParam == IDU_NOPRINTER)
        EnableWindow (hwndEnumPrinter, FALSE);
      else
        PostMessage (hwndMain,message, wParam, lParam);
    break;

    /**********************************************************************\
    *  WM_PAINT
    *
    * Paint two rectangular strips, one on top, one on bottom.
    \**********************************************************************/
    case WM_PAINT : {
      PAINTSTRUCT ps;
      RECT rect;
      HDC hdc;

      hdc = BeginPaint(hwnd, &ps);
      GetClientRect (hwnd, &rect);
      rect.right --;
      rect.bottom --;

      SelectObject (hdc, GetStockObject (BLACK_PEN));
      MoveToEx (hdc, rect.right, rect.top, NULL);
      LineTo (hdc, rect.right, rect.bottom);
      LineTo (hdc, rect.left, rect.bottom);
      SelectObject (hdc, GetStockObject (WHITE_PEN));
      LineTo (hdc, rect.left, rect.top);
      LineTo (hdc, rect.right, rect.top);

      EndPaint (hwnd, &ps);
    } break;

  } /* end switch */
  return (DefWindowProc(hwnd, message, wParam, lParam));
}


TTFONTS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  ttfonts.c -- Font enumeration and manipulation program for Win32/NT.
*
* design:  There is a main frame window (hwndMain) with a child toolbar,
*  two child dialogs, and a child demonstration window.  All window handles
*  are global and are accesible to all modules.  There is a module for each
*  of the classes of windows.  Each module contains an initialization routine
*  to register the class and create the window(s).  Each module also contains
*  a window procedure for that class.  The two windows which list all of the
*  fonts are also children of the main window, and they are hidden and shown
*  as needed.
*
*  Communication between windows is via message passing.  The toolbar passes
*  all of its command messages back to the main window.  Other windows pass
*  information back and forth with the following USER defined messages:
*  WMU_LFTODEMO, WMU_DEMOTOLF, WMU_DEMOTOTM...  Rectangles fly on the screen
*  (c.f. flyWinWin) to mirror message trafic, i.e. data flow.
*
\**************************************************************************/

#include <windows.h>
#include "ttfonts.h"

/* Misc. defines for size, color, and appearance of drawing. */
#define GRIDCOLOR  PALETTEINDEX (6)
#define TICKSPACE  20
#define FWW_STEPS  40

TCHAR szMBERROR     [MAX_PATH];
TCHAR szInitString  [MAX_PATH];
TCHAR szAllocFailed [MAX_PATH];
TCHAR szResetDisplay[MAX_PATH];
TCHAR szNoCMAPTable [MAX_PATH];
TCHAR szFontDataErr [MAX_PATH];
TCHAR szCBDataTooBig[MAX_PATH];
TCHAR szNoTable     [MAX_PATH];
TCHAR szFormatNot4  [MAX_PATH];
TCHAR szEnumPrinterR[MAX_PATH];
TCHAR szHelpPathName[] = TEXT("ttfonts.HLP");

/**************************************************************************\
*
*  function:  WinMain()
*
*  input parameters:  c.f. generic sample
*
\**************************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    MSG    msg;
    HWND   hwndInfo;
    CLIENTCREATESTRUCT clientcreate;
    RECT   clientRect;
    HANDLE haccel;

    UNREFERENCED_PARAMETER( lpCmdLine );
    UNREFERENCED_PARAMETER(  nCmdShow );
    hInst = hInstance;

    /* Check for previous instance.  If none, then register class. */
    if (!hPrevInstance) {
      WNDCLASS  wc;

      wc.style = 0;
      wc.lpfnWndProc = (WNDPROC)MainWndProc;

      wc.cbClsExtra = 0;
      wc.cbWndExtra = 0;
      wc.hInstance = hInstance;
      wc.hIcon = LoadIcon(hInstance, TEXT("ttfontsIcon"));
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = NULL;
      wc.lpszMenuName =  TEXT("ttfontsMenu");
      wc.lpszClassName = TEXT("ttfonts");

      if (!RegisterClass(&wc)) return (FALSE);

    }  /* class registered o.k. */

    /* Create the main window.  Return false if CreateWindow() fails */
    hwndMain = CreateWindow(
        TEXT("ttfonts"),
        TEXT("TTFonts"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL, NULL, hInst, NULL);

    if (!hwndMain) return (FALSE);

    /* Initialize global strings that are used later */
    LoadString (hInst, IDS_MBERROR,      szMBERROR,      MAX_PATH);
    LoadString (hInst, IDS_INITSTRING,   szInitString,   MAX_PATH);
    LoadString (hInst, IDS_ALLOCFAILED,  szAllocFailed,  MAX_PATH);
    LoadString (hInst, IDS_RESETDISPLAY, szResetDisplay, MAX_PATH);
    LoadString (hInst, IDS_NOCMAPTABLE,  szNoCMAPTable,  MAX_PATH);
    LoadString (hInst, IDS_CBDATATOOBIG, szCBDataTooBig, MAX_PATH);
    LoadString (hInst, IDS_NOTABLE,      szNoTable,      MAX_PATH);
    LoadString (hInst, IDS_FORMATNOT4,   szFormatNot4,   MAX_PATH);
    LoadString (hInst, IDS_FONTDATAERR,  szFontDataErr,  MAX_PATH);
    LoadString (hInst, IDS_ENUMPRINTERR, szEnumPrinterR,  MAX_PATH);

    /* create temporary window to display while initialization completes */
    hwndInfo = CreateWindow(
        TEXT("EDIT"),
        szInitString,
        WS_CHILD | WS_VISIBLE | ES_READONLY | ES_MULTILINE | ES_CENTER,
        0,0,
        GetSystemMetrics (SM_CXFULLSCREEN),
        GetSystemMetrics (SM_CYFULLSCREEN),
        hwndMain, NULL, hInst, NULL);

    ShowWindow(hwndMain, SW_SHOWMAXIMIZED);
    UpdateWindow(hwndInfo);

    /* create the MDI "client" window. Invisible initially. */
    clientcreate.hWindowMenu  = NULL;  /* don't add window names to menu */
    clientcreate.idFirstChild = 1;
    GetClientRect (hwndMain, &clientRect);
    clientRect.top += TOOLBARHEIGHT;
    hwndClient = CreateWindow(
        TEXT("MDICLIENT"),
        NULL,
        WS_CHILD | WS_CLIPCHILDREN | MDIS_ALLCHILDSTYLES,
        0, clientRect.top,
        clientRect.right, clientRect.bottom - clientRect.top,
        hwndMain, NULL, hInst, (LPVOID)&clientcreate);
    if (!hwndClient) return (FALSE);

    /* perform initialization for other windows...allfont is SLOW */
    if (!initTB(hwndMain))         return FALSE;
    if (!initDisplay(hwndClient))     return FALSE;
    if (!initDlg(hwndClient))      return FALSE;
    if (!initAllFont(hwndClient))  return FALSE;

    SendMessage (hwndMain, WM_COMMAND, IDM_ARRANGE,0);

    /* Load the accelerator table that provides clipboard support. */
    haccel = LoadAccelerators (hInst, TEXT("ttfontsAccel"));

    /* get rid of the temporary "please wait..." window. */
    DestroyWindow (hwndInfo);
    ShowWindow(hwndClient, SW_SHOW);

    /* Loop getting messages and dispatching them. */
    while (GetMessage(&msg,NULL, 0,0)) {
      if (!TranslateAccelerator(hwndMain, haccel, &msg))
      if (!IsDialogMessage (hwndDlgTM, &msg))
      if (!IsDialogMessage (hwndDlgOLTM, &msg))
      if (!IsDialogMessage (hwndDlgLF, &msg))
      if (!IsDialogMessage (hwndDlgFD, &msg)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }

    return (msg.wParam);
}

/**************************************************************************\
*
*  function:  MainWndProc()
*
*  input parameters:  normal window procedure parameters.
*
\**************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HANDLE     hPenGrid;
static LOGFONT    lf;
static TEXTMETRIC tm;

  switch (message) {

    /**********************************************************************\
    *  WM_COMMAND
    *
    * The WM_COMMAND messages here are passed up from the toolbar.
    *  Take whatever action is necesarry for the various buttons.
    *  (Either showing/hiding enumeration windows, or moving information
    *  in structures between different windows.)
    \**********************************************************************/
    case WM_COMMAND: {

      switch (LOWORD(wParam)) {
        HDC hdcDemo;

        case TBID_ENUM:
          ShowWindow(hwndDisplayFonts, SW_SHOWMAXIMIZED);
        break;

        case TBID_PRINT:
          ShowWindow(hwndPrinterFonts, SW_SHOWMAXIMIZED);
        break;

        /* Send the LOGFONT dialog a message to fill in a LOGFONT structure,
         *  then pass that structure along to the DEMO window.
         */
        case TBID_CREATE:
          SendMessage (hwndDlgLF, WMU_LFTODEMO, 0, (LONG) &lf);
          flyWinWin (hwndMain, hwndDlgLF, hwndDisplay, FWW_STEPS);
          SendMessage (hwndDisplay , WMU_NEWFONT, 0, (LONG) &lf);
        break;

        /* Same exchange as LOGFONT for the TEXTMETRIC structure.
         *  However, for the OUTLINETEXTMETRIC stucture,
         *  send the DEMO window a message asking for an HDC with the
         *  current font selected into it.  Pass that HDC along to the
         *  OUTLINETEXTMETRIC dialog so that it can query the structure,
         *  and display the values.
         */
        case TBID_GETTM:

          SendMessage (hwndDisplay , WMU_DEMOTOTM, 0, (LONG) &tm);
          flyWinWin (hwndMain, hwndDisplay, hwndDlgTM, FWW_STEPS);
          SendMessage (hwndDlgTM, WMU_DEMOTOTM, 0, (LONG) &tm);

          hdcDemo = (HDC) SendMessage (hwndDisplay , WMU_DEMOGETDC, 0, 0);
          flyWinWin (hwndMain, hwndDisplay, hwndDlgOLTM, FWW_STEPS);
          SendMessage (hwndDlgOLTM, WMU_DEMOTOOLTM, 0, (LPARAM) hdcDemo);
          SendMessage (hwndDisplay , WMU_DEMORELEASEDC, 0, (LPARAM) hdcDemo);

        break;

        /* Send message down to hwndDlgFD w/ HDC from hwndDisplay.
         *  hwndDlgFD will parse its params, and call GetFontData().
         */
        case TBID_GETFONTDATA:

          hdcDemo = (HDC) SendMessage (hwndDisplay , WMU_DEMOGETDC, 0, 0);
          flyWinWin (hwndMain, hwndDisplay, hwndDlgFD, FWW_STEPS);
          SendMessage (hwndDlgFD, WMU_GETFONTDATA, 0, (LPARAM) hdcDemo);
          SendMessage (hwndDisplay , WMU_DEMORELEASEDC, 0, (LPARAM) hdcDemo);

        break;

        /* Send message to display window to fill in LOGFONT, then
         *  send that LOGFONT data back to the logfont dialog.
         */
        case IDM_GETOBJECT:
          flyWinWin (hwndMain, hwndDisplay, hwndDlgLF, FWW_STEPS);
          SendMessage (hwndDisplay , WMU_DEMOTOLF, 0, (LPARAM) &lf);
          SendMessage (hwndDlgLF , WMU_DEMOTOLF, 0, (LPARAM) &lf);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDM_*_FONT
        *
        * Send the display window the proper message for it to use one
        *  of the GetStockObject() fonts.  Then send this window a message
        *  to "getobject" so that the logfont will be copied from the
        *  display window to the logfont dialog.
        \******************************************************************/
        case IDM_ANSI_FIXED_FONT    :
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(ANSI_FIXED_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;
        case IDM_ANSI_VAR_FONT      :
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(ANSI_VAR_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;
        case IDM_DEVICE_DEFAULT_FONT:
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(DEVICE_DEFAULT_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;
        case IDM_OEM_FIXED_FONT     :
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(OEM_FIXED_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;
        case IDM_SYSTEM_FIXED_FONT  :
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(SYSTEM_FIXED_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;
        case IDM_SYSTEM_FONT        :
          SendMessage (hwndDisplay , WMU_HFONTTODEMO, 0,
                       (LPARAM) GetStockObject(SYSTEM_FONT));
          SendMessage (hwnd, WM_COMMAND, IDM_GETOBJECT, 0);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDM_CHOOSEFONT
        *
        * Get the LOGFONT from the dialog, initialize the CHOOSEFONT struct,
        *  put up the ChooseFont() dialog, take the resulting LOGFONT and put
        *  it back in the dialog.  Force a createfont message.
        \******************************************************************/
        case IDM_CHOOSEFONT: {
          CHOOSEFONT   chf;

          /* fill a log font ahead of time to init the common dialog */
          SendMessage (hwndDlgLF , WMU_LFTODEMO, 0, (LPARAM) &lf);

          chf.lStructSize = sizeof(CHOOSEFONT);
          chf.hwndOwner = hwnd;
          chf.hDC = NULL;
          chf.lpLogFont = &lf;
          chf.iPointSize = 0;
          chf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
          // chf.Flags |= CF_EFFECTS; // no support for COLOR yet in TTFONTS.
          chf.rgbColors = RGB(0,0,0);
          chf.lCustData = 0;
          chf.lpfnHook = NULL;
          chf.lpTemplateName = NULL;
          chf.hInstance = hInst;
          chf.lpszStyle = NULL;
          chf.nFontType = SIMULATED_FONTTYPE;
          chf.nSizeMin = 0;
          chf.nSizeMax = 0;

          if( ChooseFont( &chf ) != FALSE ) {
            /* flyWinWin from client to dlgLF since we don't have hwnd
             *  for the common dialog, and since it is not a child window.
             */
            flyWinWin (hwndMain, hwndClient, hwndDlgLF, FWW_STEPS);
            SendMessage (hwndDlgLF , WMU_DEMOTOLF, 0, (LPARAM) chf.lpLogFont);
            SendMessage (hwnd, WM_COMMAND, TBID_CREATE, 0);
          }
        } break;

        /* Simply send message to display window, and it does all work. */
        case IDM_PRINT:
          SendMessage (hwndDisplay , WMU_PRINT, 0, 0);
        break;

        /* No printer, disable menu and button. */
        case IDU_NOPRINTER:
          EnableMenuItem (GetMenu(hwnd), TBID_PRINT, MF_GRAYED);
          EnableMenuItem (GetMenu(hwnd), IDM_PRINT , MF_GRAYED);
          SendMessage (hwndTB , WM_COMMAND, IDU_NOPRINTER, 0);
        break;

        case IDM_HELP:
          WinHelp( hwnd, szHelpPathName, HELP_INDEX, (DWORD) NULL );
        break;

        case IDM_ABOUT:
          DialogBox (hInst, TEXT("aboutBox"), hwnd, (DLGPROC) About);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDM_FLYWINWIN
        *
        * Check menu on and off.  Query state in FlyWinWin() function.
        \******************************************************************/
        case IDM_FLYWINWIN:
          if (GetMenuState (GetMenu (hwnd),IDM_FLYWINWIN, MF_BYCOMMAND)
                == MF_CHECKED) {
            CheckMenuItem(GetMenu (hwnd), LOWORD(wParam) , MF_UNCHECKED);
          } else {
            CheckMenuItem(GetMenu (hwnd), LOWORD(wParam) , MF_CHECKED);
          }
        break;

        /******************************************************************\
        *  WM_COMMAND, IDM_MODE*
        *
        * manage mutually exclusive menu.
        *  inform the window that it has a new mode.
        *  invalidate window to force repaint.
        *  Special case MODEALL to check that it is o.k. first.
        \******************************************************************/
        case IDM_MODEALL    :
          if (!SendMessage (hwndDisplay, WMU_NEWMODEALL, 0,0)) {
            return 0;
          }
          CheckMenuItem(GetMenu (hwnd), IDM_MODEHELLO  , MF_UNCHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODETMRANGE, MF_UNCHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODEALL    , MF_CHECKED);
          InvalidateRect (hwndDisplay, NULL, TRUE);
        return 0;

        case IDM_MODEHELLO  :
          CheckMenuItem(GetMenu (hwnd), IDM_MODEHELLO  , MF_CHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODETMRANGE, MF_UNCHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODEALL    , MF_UNCHECKED);
          SendMessage (hwndDisplay, WMU_NEWMODEHELLO, 0,0);
          InvalidateRect (hwndDisplay, NULL, TRUE);
        return 0;

        case IDM_MODETMRANGE:
          CheckMenuItem(GetMenu (hwnd), IDM_MODEHELLO  , MF_UNCHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODETMRANGE, MF_CHECKED);
          CheckMenuItem(GetMenu (hwnd), IDM_MODEALL    , MF_UNCHECKED);
          SendMessage (hwndDisplay, WMU_NEWMODETMRANGE, 0,0);
          InvalidateRect (hwndDisplay, NULL, TRUE);
        return 0;

        /******************************************************************\
        *  WM_COMMAND, IDM_*
        *
        * misc. MDI responsibilities.  hwndClient does real work.
        \******************************************************************/
        case IDM_CASCADE: SendMessage (hwndClient, WM_MDICASCADE, 0,0); break;
        case IDM_TILE:    SendMessage (hwndClient, WM_MDITILE, 0,0); break;
        case IDM_ARRANGE:
          ShowWindow(hwndDisplayFonts, SW_SHOWMINIMIZED);
          ShowWindow(hwndPrinterFonts, SW_SHOWMINIMIZED);
          ShowWindow(hwndDlgOLTM, SW_SHOWMINIMIZED);
          ShowWindow(hwndDlgFD, SW_SHOWMINIMIZED);
          ShowWindow(hwndDlgTM, SW_RESTORE);
          SetWindowPos (hwndDlgTM, HWND_TOP,
              CHILDLEFT(0), CHILDTOP, 0,0, SWP_NOSIZE);
          ShowWindow(hwndDlgLF, SW_RESTORE);
          SetWindowPos (hwndDlgLF, HWND_TOP,
              CHILDLEFT(1), CHILDTOP, 0,0, SWP_NOSIZE);
          ShowWindow(hwndDisplay, SW_RESTORE);
          SetWindowPos (hwndDisplay, HWND_TOP,
              CHILDLEFT(2), CHILDTOP,
              GetSystemMetrics (SM_CXFULLSCREEN)/3 - 10,
              GetSystemMetrics (SM_CYFULLSCREEN)/3, 0);
          SendMessage (hwndClient, WM_MDIICONARRANGE, 0,0);
        break;

      } /* end switch */
    } break;  /* end WM_COMMAND */

    /**********************************************************************\
    *  WM_CREATE
    *
    * Create pens for drawing with later.
    \**********************************************************************/
    case WM_CREATE:
      hPenGrid   = CreatePen (PS_SOLID, 1, GRIDCOLOR);
      CheckMenuItem (GetMenu (hwnd), IDM_FLYWINWIN, MF_BYCOMMAND | MF_CHECKED);
      CheckMenuItem (GetMenu (hwnd), IDM_MODEHELLO, MF_BYCOMMAND | MF_CHECKED);
      gDisplaymode = IDM_MODEHELLO;
    break;

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Complement of the WM_CREATE message.  Delete the pens that were
    *  created and then call postquitmessage.
    \**********************************************************************/
    case WM_DESTROY:
      DeleteObject (hPenGrid);
      WinHelp( hwnd, szHelpPathName, (UINT) HELP_QUIT, (DWORD) NULL );
      PostQuitMessage(0);
    break;

    /**********************************************************************\
    *  WM_SIZE
    *
    * When the frame is sized, then change the size of the MDI client.
    \**********************************************************************/
    case WM_SIZE: {

      RECT r;
      GetClientRect (hwnd, &r);
      r.top += TOOLBARHEIGHT;
      SetWindowPos (hwndClient, HWND_BOTTOM,
                    r.left, r.top, r.right, r.bottom - r.top, 0);
      return TRUE;

    } break;

    default: break;
    } /* end switch */

  return (DefFrameProc(hwnd, hwndClient, message, wParam, lParam));
}

/***************************************************************************\
*    FUNCTION: About
\***************************************************************************/
LRESULT CALLBACK About(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  if ((message == WM_COMMAND) && (LOWORD(wParam) == IDOK)) {
    EndDialog (hwnd, TRUE);
    return TRUE;
  }
  if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) {
    EndDialog (hwnd, TRUE);
    return TRUE;
  }
  return FALSE;
}

/**************************************************************************\
*  function:  flyWinWin
*
* Send rectangles flying on the screen to indicate data flow.
*
*  input parameters:
*   handParent - parent window in which other two exist (grandchildren o.k.)
*   hwndFrom - rectangles originate here.
*   hwndTo   - and travel here.
*   steps    - number of steps to take.  Fewer is faster.
*
\**************************************************************************/
VOID flyWinWin(HWND handParent, HWND hwndFrom, HWND hwndTo, int steps)
{
RECT rectFrom, rectTo;
RECT rectI;
HDC hdc;
int i, iLeft, iTop, iRight, iBottom;

    /* The menu bar contains a checked/unchecked item which stores the
     *  state of the user's preference on whether this "information flow"
     *  should be done or not.  If unchecked, then return before doing
     *  anything.
     */
    if (GetMenuState (GetMenu (hwndMain),IDM_FLYWINWIN, MF_BYCOMMAND)
                != MF_CHECKED) return;

    /* retrieve the window rects in screen coordinates. */
    GetWindowRect (hwndFrom, &rectFrom);
    GetWindowRect (hwndTo, &rectTo);

    /* convert them to be relative to the parent window. */
    ScreenToClient (handParent,(LPPOINT)&rectFrom.left);
    ScreenToClient (handParent,(LPPOINT)&rectFrom.right);
    ScreenToClient (handParent,(LPPOINT)&rectTo.left);
    ScreenToClient (handParent,(LPPOINT)&rectTo.right);

    /* Get an HDC, set the ROP so that painting twice will restore to the
     *  same state.  Then select in the NULL brush so that the Rectangle()
     *  call will not fill in the interior.
     */
    hdc = GetDC (handParent);
    SetROP2(hdc, R2_NOT);
    SelectObject (hdc, GetStockObject (NULL_BRUSH));

    /* Compute the increment to change on each step.  Notice that round
     *  off loss will cause the target window (hwndTo) to be "missed" if
     *  the windows nearly allign on some edge, and there are a large number
     *  of steps.
     */
    iLeft =   (rectTo.left   - rectFrom.left)   /steps;
    iTop =    (rectTo.top    - rectFrom.top)    /steps;
    iRight =  (rectTo.right  - rectFrom.right)  /steps;
    iBottom = (rectTo.bottom - rectFrom.bottom) /steps;

    /* Draw the series of rectangles the first time. */
    rectI = rectFrom;
    for (i= 0; i<steps; i++) {
      rectI.left += iLeft;
      rectI.top += iTop;
      rectI.right += iRight;
      rectI.bottom += iBottom;

      Rectangle (hdc, rectI.left, rectI.top , rectI.right, rectI.bottom);
    }

    /* start from the same place, and draw them again... this time erases. */
    rectI = rectFrom;
    for (i= 0; i< steps; i++) {
      rectI.left += iLeft;
      rectI.top += iTop;
      rectI.right += iRight;
      rectI.bottom += iBottom;

      Rectangle (hdc, rectI.left, rectI.top , rectI.right, rectI.bottom);
    }

    ReleaseDC (handParent,hdc);
}


TTFONTS.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/* ttfonts.h - header file
*/

/* function prototypes, window procedures first. */
LRESULT CALLBACK MainWndProc    (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ToolBarWndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK AllFontsWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK LogFontWndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK FontDataWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DisplayWndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM);

/* the initialization routines for the different modules. */
int initDlg(HWND);
int initDisplay(HWND);
int initTB(HWND);
int initAllFont(HWND);

/* misc. routine to show data flow to the user. */
VOID flyWinWin(HWND, HWND, HWND, int);

HDC      GetPrinterDC (VOID);

/* the height of the toolbar in pels. */
#define TOOLBARHEIGHT 30

/* for the initial positioning of the three child windows. */
#define CHILDLEFT(x) (GetSystemMetrics (SM_CXFULLSCREEN) * x/3 + 5)
#define CHILDTOP     TOOLBARHEIGHT*6/5

/* the default size of some of the dialogs. */
#define DLGWIDTH   110
#define DLGHEIGHT  160

/* menu IDs */
#define  IDM_CASCADE        1001
#define  IDM_TILE           1002
#define  IDM_ARRANGE        1003
#define  IDM_HELP           2001
#define  IDM_ABOUT          2002

#define  IDM_FLYWINWIN      2000
#define  IDM_GRID           2001

#define  IDM_GETOBJECT               3000
#define  IDM_CHOOSEFONT              3001
#define  IDM_ANSI_FIXED_FONT         3002
#define  IDM_ANSI_VAR_FONT           3003
#define  IDM_DEVICE_DEFAULT_FONT     3004
#define  IDM_OEM_FIXED_FONT          3005
#define  IDM_SYSTEM_FIXED_FONT       3006
#define  IDM_SYSTEM_FONT             3007

/* dialog IDs from the LOGFONT dialog. */
#define  DID_X              101
#define  DIDHEIGHT          201
#define  DIDWIDTH           202
#define  DIDESCAPE          203
#define  DIDORIENT          204
#define  DIDWEIGHT          205
#define  DIDITALIC          206
#define  DIDUNDERL          207
#define  DIDSTRIKE          208
#define  DIDCHARSE          209
#define  DIDOUTPRE          210
#define  DIDCLIPPR          211
#define  DIDQUALIT          212
#define  DIDPITCHA          213
#define  DIDFACENA          214

/* dialog ID from the TEXTMETRIC dialog. */
#define DID_LISTBOX         500

/* dialog ID from the FontData dialog. */
#define DID_DWTABLE         200
#define DID_DWOFFSET        201
#define DID_CBDATA          203
#define DID_DOIT            300

/* button window IDs from toolbar. */
#define TBID_ENUM     57
#define TBID_CREATE   58
#define TBID_GETTM    59
#define TBID_PRINT    60
#define TBID_GETFONTDATA 61
#define IDM_PRINT     70

/* user defined wm_command to send to main window */
#define IDU_NOPRINTER 71

/* USER messages used between windows. */
#define  WMU_HFONTTODEMO      WM_USER+0x1000
#define  WMU_LFTODEMO         WM_USER+0x1001
#define  WMU_DEMOTOLF         WM_USER+0x1002
#define  WMU_DEMOTOTM         WM_USER+0x1003
#define  WMU_DEMOTOOLTM       WM_USER+0x1004
#define  WMU_DEMOGETDC        WM_USER+0x1005
#define  WMU_DEMORELEASEDC    WM_USER+0x1006
#define  WMU_GETFONTDATA      WM_USER+0x1007
#define  WMU_PREPAREALLGLYPHS WM_USER+0x1008

#define  WMU_NEWFONT          WM_USER+0x1100
#define  WMU_NEWMODEHELLO     WM_USER+0x1101
#define  WMU_NEWMODETMRANGE   WM_USER+0x1102
#define  WMU_NEWMODEALL       WM_USER+0x1103
#define  WMU_NEWMODE_NEWSEG   WM_USER+0x1104
#define  WMU_PRINT            WM_USER+0x1110

/* user defined create messsage, sent to window once CreateWindow() returns */
#define WMU_CREATE          WM_USER+0x1000

/* define strings and flags for use in warning message boxes */
#define MBERRORFLAGS     MB_OK | MB_ICONHAND

/* define string table IDs */
#define IDS_MBERROR       32
#define IDS_INITSTRING    33
#define IDS_ALLOCFAILED   34
#define IDS_RESETDISPLAY  35
#define IDS_NOCMAPTABLE   36
#define IDS_CBDATATOOBIG  37
#define IDS_NOTABLE       38
#define IDS_FORMATNOT4    39
#define IDS_FONTDATAERR   40
#define IDS_ENUMPRINTERR  41

extern TCHAR szMBERROR      [MAX_PATH];
extern TCHAR szInitString   [MAX_PATH];
extern TCHAR szAllocFailed  [MAX_PATH];
extern TCHAR szResetDisplay [MAX_PATH];
extern TCHAR szNoCMAPTable  [MAX_PATH];
extern TCHAR szCBDataTooBig [MAX_PATH];
extern TCHAR szNoTable      [MAX_PATH];
extern TCHAR szFormatNot4   [MAX_PATH];
extern TCHAR szFontDataErr  [MAX_PATH];
extern TCHAR szEnumPrinterR [MAX_PATH];

/* Global variables. */
HANDLE hInst;
HWND   hwndMain, hwndDisplay;
HWND   hwndTB, hwndDlgLF, hwndDlgTM, hwndDlgOLTM, hwndDlgFD;
HWND   hwndDisplayFonts, hwndPrinterFonts;
HWND   hwndClient;

/* the display window works in one of 3 modes
 *  this is shown to the user by a check mark in the 'Display' menu
 *  it is stored internally in the following variable
 *  (contains the checked menu ID)
 */
int gDisplaymode;
#define  IDM_MODEHELLO          101
#define  IDM_MODETMRANGE        102
#define  IDM_MODEALL            103


README.TXT

GDI Sample Application

The GDIDemo sample contains several examples of using GDI calls to generate
graphics.


BOUNCE.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef WORD WPARAM;
#endif

#define BOUNCECLASS  "BOUNCEDEMO"

typedef struct _MYXFORM
{
    int xForm[3][3];
} MYXFORM;
typedef MYXFORM      *PMYXFORM;
typedef MYXFORM NEAR *NPMYXFORM;
typedef MYXFORM FAR  *LPMYXFORM;

/*
** BALL OBJECT
*/
typedef struct _BALLDATA
{
    HWND     hWnd;
    int      nWidth;
    int      nHeight;
    int      xDirection;
    int      yDirection;
    BOOL     bNewPosition;
    int      xPosition;
    int      yPosition;
    COLORREF crColor;
    MYXFORM  xForm;
} BALLDATA;
typedef BALLDATA      *PBALLDATA;
typedef BALLDATA NEAR *NPBALLDATA;
typedef BALLDATA FAR  *LPBALLDATA;

HANDLE ballCreate(HWND,int,int,COLORREF);
BOOL   ballDestroy(HANDLE);
VOID   ballBounce(HANDLE);
BOOL   ballSetDimensions(HANDLE,int,int);
BOOL   ballGetDimensions(HANDLE,LPPOINT);
BOOL   ballSetDirection(HANDLE,int,int);
BOOL   ballGetDirection(HANDLE,LPPOINT);
BOOL   ballGetPosition(HANDLE,LPPOINT);
BOOL   ballSetPosition(HANDLE,int,int);

typedef struct _BOUNCEDATA
{
    HANDLE hBall1;
    HANDLE hBall2;
    HANDLE hBall3;
    HANDLE hBall4;
} BOUNCEDATA;
typedef BOUNCEDATA      *PBOUNCEDATA;
typedef BOUNCEDATA NEAR *NPBOUNCEDATA;
typedef BOUNCEDATA FAR  *LPBOUNCEDATA;

/*
** DRAW WINDOW ROUTINES (draw.c)
*/
HWND  FAR      CreateBounceWindow(HWND,int);
LONG  APIENTRY BounceProc(HWND,UINT,WPARAM,LONG);
BOOL           BounceCreateProc(HWND);
VOID           BounceDestroyProc(HWND);
BOOL           BounceCommandProc(HWND,WPARAM,LONG);
VOID           BouncePaintProc(HWND);

VOID           BounceObjects(HWND);
VOID           BounceRefresh(HWND);

VOID           CheckEdgePosition(HWND,HANDLE);


DIALOG.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| DIALOG MODULE
|   This module contains the dialogbox routines for this application.
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"

/*---------------------------------------------------------------------------*\
| DISPLAY DIALOG BOX
|   This is a routine to display a generic modal-dialog box.
|
\*---------------------------------------------------------------------------*/
int FAR DisplayDialogBox(HWND hWnd, LPSTR lpszTemplate, WNDPROC lpfFunction, LONG lExtra)
{
    HANDLE  hInstance;
    WNDPROC lpfDlg;
    int     nRet;

    nRet = -1;
    if(hInstance = GETINSTANCE(hWnd))
    {
        if(lpfDlg = MakeProcInstance(lpfFunction,hInstance))
        {
            nRet = DialogBoxParam(hInstance,lpszTemplate,hWnd,(DLGPROC)lpfDlg,lExtra);
            FreeProcInstance(lpfDlg);
        }
    }
    return(nRet);
}

/*---------------------------------------------------------------------------*\
| ABOUT DIALOG PROCEDURE
|   This is the main dialog box routine for the HELPABOUT template.
|
\*---------------------------------------------------------------------------*/
BOOL APIENTRY AboutDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LONG lParam)
{
    lParam = lParam;

    switch(wMsg)
    {
        /*
        ** Set the focus to the OK button.
        */
        case WM_INITDIALOG:
            SetFocus(GetDlgItem(hDlg,IDOK));
            break;

        /*
        ** Look for an ESC or RETURN event.
        */
        case WM_COMMAND:
            switch(wParam)
            {
                case IDOK:
                case IDCANCEL:
                    EndDialog(hDlg,TRUE);
                    break;

                default:
                    return(FALSE);
            }
            break;

        /*
        ** Wash the background of the aboutbox to give it a nice blue-scaling
        ** effect.  Invalidate the OK button to force it to the top.  This
        ** seems to be necessary since the OK button gets overwritten during
        ** the washing.
        */
        case WM_PAINT:
            PaintWindow(hDlg,COLOR_SCALE_BLUE);
            InvalidateRect(GetDlgItem(hDlg,IDOK),NULL,TRUE);

        /*
        ** Default handler.
        */
        default:
            return(FALSE);
    }
    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| PAINT WND BACKGROUND
|   This routine is used to wash the background of a window.
|
\*---------------------------------------------------------------------------*/
VOID PaintWindow(HWND hWnd, int nColor)
{
    HDC         hDC;
    int         nMapMode,idx,nSize,nReserved,nLoop;
    RECT        rect;
    HBRUSH      hBrush;
    PAINTSTRUCT ps;
    HPALETTE    hPal;

    if(hDC = BeginPaint(hWnd,&ps))
    {
        GetClientRect(hWnd,&rect);
        nMapMode = SetMapMode(hDC,MM_ANISOTROPIC);

        if(GetDeviceCaps(hDC,RASTERCAPS) & RC_PALETTE)
        {
            nReserved = GetDeviceCaps(hDC,NUMRESERVED);
            nSize     = GetDeviceCaps(hDC,SIZEPALETTE) - nReserved;

            if(hPal = CreateColorScalePalette(hDC,nColor))
            {
                hPal = SelectPalette(hDC,hPal,FALSE);
                RealizePalette(hDC);

#ifdef WIN16
                SetWindowExtEx(hDC,nSize,nSize);
                SetViewportExtEx(hDC,rect.right,-rect.bottom);
                SetViewportOrgEx(hDC,0,rect.bottom);
#else

                SetWindowExtEx(hDC,nSize,nSize,NULL);
                SetViewportExtEx(hDC,rect.right,-rect.bottom,NULL);
                SetViewportOrgEx(hDC,0,rect.bottom,NULL);

#endif

                nLoop = nSize >> 1;
                for(idx=0; idx < nLoop; idx++)
                {
                    hBrush = CreateSolidBrush(PALETTEINDEX(idx+nLoop));
                    SetRect(&rect,idx,idx,nSize-idx,nSize-idx);
                    FillRect(hDC,&rect,hBrush);
                    DeleteObject(hBrush);
                }
                DeleteObject(SelectPalette(hDC,hPal,FALSE));
                RealizePalette(hDC);
            }
        }
        else
        {

#ifdef WIN16

            SetWindowExtEx(hDC,512,512);
            SetViewportExtEx(hDC,rect.right,-rect.bottom);
            SetViewportOrgEx(hDC,0,rect.bottom);

#else

            SetWindowExtEx(hDC,512,512,NULL);
            SetViewportExtEx(hDC,rect.right,-rect.bottom,NULL);
            SetViewportOrgEx(hDC,0,rect.bottom,NULL);

#endif

            for(idx=0; idx < 256; idx++)
            {
                hBrush = CreateSolidBrush(RGB(0,0,idx));
                SetRect(&rect,idx,idx,512-idx,512-idx);
                FillRect(hDC,&rect,hBrush);
                DeleteObject(hBrush);
            }
        }

        SetMapMode(hDC,nMapMode);

        EndPaint(hWnd,&ps);
    }
    return;
}

/*---------------------------------------------------------------------------*\
| CREATE COLOR SCALE PALETTE
|   This routine creates a palette representing the scale values of a
|   particular RGB color.  A gray-scale palette can also be created.
|
\*---------------------------------------------------------------------------*/
HPALETTE CreateColorScalePalette(HDC hDC, int nColor)
{
    HPALETTE     hPalette;
    GLOBALHANDLE hMem;
    LPLOGPALETTE lpMem;
    int          idx,nReserved,nSize;

    hPalette = NULL;
    if(GetDeviceCaps(hDC,RASTERCAPS) & RC_PALETTE)
    {
        nReserved = GetDeviceCaps(hDC,NUMRESERVED);
        nSize     = GetDeviceCaps(hDC,SIZEPALETTE) - nReserved;

        if(hMem = GlobalAlloc(GHND,(DWORD)sizeof(LOGPALETTE)+(sizeof(PALETTEENTRY)*nSize)))
        {
            if(lpMem = (LPLOGPALETTE)GlobalLock(hMem))
            {
                lpMem->palNumEntries = (WORD)nSize;
                lpMem->palVersion    = (WORD)0x0300;

                switch(nColor)
                {
                    case COLOR_SCALE_RED:
                        for(idx=0; idx < nSize; idx++)
                        {
                            lpMem->palPalEntry[idx].peRed   = (BYTE)idx;
                            lpMem->palPalEntry[idx].peGreen = 0;
                            lpMem->palPalEntry[idx].peBlue  = 0;
                            lpMem->palPalEntry[idx].peFlags = PC_RESERVED;
                        }
                        break;

                    case COLOR_SCALE_GREEN:
                        for(idx=0; idx < nSize; idx++)
                        {
                            lpMem->palPalEntry[idx].peRed   = 0;
                            lpMem->palPalEntry[idx].peGreen = (BYTE)idx;
                            lpMem->palPalEntry[idx].peBlue  = 0;
                            lpMem->palPalEntry[idx].peFlags = PC_RESERVED;
                        }
                        break;

                    case COLOR_SCALE_BLUE:
                        for(idx=0; idx < nSize; idx++)
                        {
                            lpMem->palPalEntry[idx].peRed   = 0;
                            lpMem->palPalEntry[idx].peGreen = 0;
                            lpMem->palPalEntry[idx].peBlue  = (BYTE)idx;
                            lpMem->palPalEntry[idx].peFlags = PC_RESERVED;
                        }
                        break;

                    default:
                    case COLOR_SCALE_GRAY:
                        for(idx=0; idx < nSize; idx++)
                        {
                            lpMem->palPalEntry[idx].peRed   = (BYTE)idx;
                            lpMem->palPalEntry[idx].peGreen = (BYTE)idx;
                            lpMem->palPalEntry[idx].peBlue  = (BYTE)idx;
                            lpMem->palPalEntry[idx].peFlags = PC_RESERVED;
                        }
                        break;
                }

                hPalette = CreatePalette(lpMem);

                GlobalUnlock(hMem);
            }
            GlobalFree(hMem);
        }
    }
    return(hPalette);
}


DRAW.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| RANDOM OBJECTS MODULE
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include "draw.h"

/*---------------------------------------------------------------------------*\
| CREATE DRAW WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
HWND FAR CreateDrawWindow(HWND hWndClient, int nItem)
{
    HANDLE          hInstance;
    MDICREATESTRUCT mcs;
    static char buffer[256];

    hInstance = GETINSTANCE(hWndClient);
    LoadString (hInstance, DRAWTITLE, buffer, 256);

    /*
    ** Initialize the MDI create struct for creation of the
    ** test window.
    */
    mcs.szClass = DRAWCLASS;
    mcs.szTitle = buffer;
    mcs.hOwner  = hInstance;
    mcs.x       = CW_USEDEFAULT;
    mcs.y       = CW_USEDEFAULT;
    mcs.cx      = CW_USEDEFAULT;
    mcs.cy      = CW_USEDEFAULT;
    mcs.style   = 0l;
    mcs.lParam  = (LONG)nItem;

    return((HWND)SendMessage(hWndClient,WM_MDICREATE,0,(LONG)(LPMDICREATESTRUCT)&mcs));
}

/*---------------------------------------------------------------------------*\
| DRAW WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
LONG APIENTRY DrawProc(HWND hWnd, UINT wMsg, WPARAM wParam, LONG lParam)
{
    switch(wMsg)
    {
        case WM_CREATE:
            DrawCreateProc(hWnd);
            break;

        case WM_COMMAND:
            DrawCommandProc(hWnd,wParam,lParam);
            break;

        case WM_TIMER:
            DrawObject(hWnd);
            break;

        case WM_PAINT:
            DrawPaintProc(hWnd);
            break;

        case WM_DESTROY:
            DrawDestroyProc(hWnd);
            break;

        default:
            return(DefMDIChildProc(hWnd,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| DRAW CREATE PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL DrawCreateProc(HWND hWnd)
{
    PDRAWDATA pdd;

    if(AllocWindowInfo(hWnd,sizeof(DRAWDATA)))
    {
        if(pdd = (PDRAWDATA)LockWindowInfo(hWnd))
        {
            pdd->nObject = 0;

            UnlockWindowInfo(hWnd);

            SetTimer(hWnd,1,50,NULL);
            return(TRUE);
        }
        FreeWindowInfo(hWnd);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| DRAW COMMAND PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL DrawCommandProc(HWND hWnd, WPARAM wParam, LONG lParam)
{
    hWnd   = hWnd;
    wParam = wParam;
    lParam = lParam;

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| DRAW PAINT PROCEDURE
\*---------------------------------------------------------------------------*/
VOID DrawPaintProc(HWND hWnd)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWnd,&ps))
        EndPaint(hWnd,&ps);

    return;
}

/*---------------------------------------------------------------------------*\
| DRAW DESTROY PROCEDURE
\*---------------------------------------------------------------------------*/
VOID DrawDestroyProc(HWND hWnd)
{
    KillTimer(hWnd,1);
    FreeWindowInfo(hWnd);
    return;
}

VOID DrawObject(HWND hWnd)
{
    PDRAWDATA pdd;
    RECT      rect;
    HDC       hDC;
    int       x1,y1,x2,y2,x3,y3,x4,y4,r,g,b,nObject;
    HBRUSH    hBrush;
    char      szDebug[80];

    if(pdd = (PDRAWDATA)LockWindowInfo(hWnd))
    {
        if(hDC = GetDC(hWnd))
        {
            GetClientRect(hWnd,&rect);

            // avoid divide by zero errors when the window is small.
            if ( rect.right== 0) rect.right++;
            if ( rect.bottom== 0) rect.bottom++;

            r = lRandom() % 255;
            g = lRandom() % 255;
            b = lRandom() % 255;

            if(hBrush = SelectObject(hDC,CreateSolidBrush(RGB(r,g,b))))
            {
                x1 = lRandom() % rect.right;
                y1 = lRandom() % rect.bottom;
                x2 = lRandom() % rect.right;
                y2 = lRandom() % rect.bottom;
                x3 = lRandom() % rect.right;
                y3 = lRandom() % rect.bottom;
                x4 = lRandom() % rect.right;
                y4 = lRandom() % rect.bottom;

                nObject = lRandom() % 5;

                switch(nObject)
                {
                    default:
                    case OBJ_RECTANGLE:
                        wsprintf(szDebug,"Rectangle(%d,%d,%d,%d)\n",x1,y1,x2,y2);
                        DEBUGOUT(szDebug);
                        Rectangle(hDC,x1,y1,x2,y2);
                        break;

                    case OBJ_ELLIPSE:
                        wsprintf(szDebug,"Ellipse(%d,%d,%d,%d)\n",x1,y1,x2,y2);
                        DEBUGOUT(szDebug);
                        Ellipse(hDC,x1,y1,x2,y2);
                        break;

                    case OBJ_ROUNDRECT:
                        wsprintf(szDebug,"RoundRect(%d,%d,%d,%d,%d,%d)\n",x1,y1,x2,y2,x3,y3);
                        DEBUGOUT(szDebug);
                        RoundRect(hDC,x1,y1,x2,y2,x3,y3);
                        break;

                    case OBJ_CHORD:
                        wsprintf(szDebug,"Chord(%d,%d,%d,%d,%d,%d,%d,%d)\n",x1,y1,x2,y2,x3,y3,x4,y4);
                        DEBUGOUT(szDebug);
                        Chord(hDC,x1,y1,x2,y2,x3,y3,x4,y4);
                        break;

                    case OBJ_PIE:
                        wsprintf(szDebug,"Pie(%d,%d,%d,%d,%d,%d,%d,%d)\n",x1,y1,x2,y2,x3,y3,x4,y4);
                        DEBUGOUT(szDebug);
                        Pie(hDC,x1,y1,x2,y2,x3,y3,x4,y4);
                        break;

                }

                DeleteObject(SelectObject(hDC,hBrush));
            }
            ReleaseDC(hWnd,hDC);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}


DRAW.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef WORD WPARAM;
//#else
//typedef DWORD WPARAM;
#endif

#define DRAWCLASS  "DRAWDEMO"

#define OBJ_RECTANGLE       0
#define OBJ_ELLIPSE         1
#define OBJ_ROUNDRECT       2
#define OBJ_CHORD           3
#define OBJ_PIE             4

typedef struct _DRAWDATA
{
    int    nObject;
} DRAWDATA;
typedef DRAWDATA      *PDRAWDATA;
typedef DRAWDATA NEAR *NPDRAWDATA;
typedef DRAWDATA FAR  *LPDRAWDATA;

/*
** DRAW WINDOW ROUTINES (draw.c)
*/
HWND  FAR      CreateDrawWindow(HWND,int);
LONG  APIENTRY DrawProc(HWND,UINT,WPARAM,LONG);
BOOL           DrawCreateProc(HWND);
VOID           DrawDestroyProc(HWND);
BOOL           DrawCommandProc(HWND,WPARAM,LONG);
VOID           DrawPaintProc(HWND);

VOID           DrawObject(HWND);


GDIDEMO.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| GDIDEMO MODULE
|   This is the main entry-point module for the GDIDEMO application.  It is
|   intended to provide simple demonstrations of graphical functionality of
|   WIN32.  This module is only concerned with the FRAME-WINDOW object.
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include "poly.h"
#include "xform.h"
#include "maze.h"
#include "draw.h"
#include "bounce.h"

HWND hWndMDIClient = NULL; /* Handle to MDI client */

/*---------------------------------------------------------------------------*\
| WINDOWS MAIN PROCEDURE
|   This is the process entry-point routine.  This is the basis for all
|   application events.
\*---------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszLine, int nShow)
{
    HWND hWndFrame;
    MSG  msg;

    lpszLine = lpszLine;

    /*
    ** If there's a previous instance of this application, then we do not need
    ** to register it again.
    */
    if(!hPrevInst)
        if(!RegisterAppClass(hInst))
            return(1);

    /*
    ** Enter the application message-polling loop.  This is the anchor for
    ** the application.
    */
    msg.wParam = 1;
    if(hWndFrame = CreateAppWindow(hInst))
    {
ShowWindow(hWndFrame,nShow);
        UpdateWindow(hWndFrame);

        while(GetMessage(&msg,NULL,0,0))
        {
            if(!TranslateMDISysAccel(hWndMDIClient,&msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }
    UnregisterAppClass(hInst);

    return(msg.wParam);
}

/*---------------------------------------------------------------------------*\
| CLIENT WINDOW PROCEDURE
|   This is the main window function for the client-window created.  This is
|   the frame window which encompasses the MDI control window.
\*---------------------------------------------------------------------------*/
LONG APIENTRY WndProc(HWND hWndFrame, UINT wMsg, WPARAM wParam, LONG lParam)
{
    HWND hWndClient;

    switch(wMsg)
    {
        /*
        ** Set up any pre-display stuff.  This is where we create the MDI
        ** control window.
        */
        case WM_CREATE:
            CreateProc(hWndFrame);
/*
** DEMO MODE - These PostMessages Are for Demonstration Only
*/
            PostMessage(hWndFrame,WM_COMMAND,IDM_DEMO_DRAW,0);
            PostMessage(hWndFrame,WM_COMMAND,IDM_DEMO_POLYBEZIER,0);
            PostMessage(hWndFrame,WM_COMMAND,IDM_DEMO_BOUNCE,0);
            PostMessage(hWndFrame,WM_COMMAND,IDM_WINDOW_TILE,0);

            break;

        /*
        ** Paint the background of the frame.  This really is a NOP since
        ** the MDI control is displayed over the frame's client window.
        */
        case WM_PAINT:
            PaintProc(hWndFrame);
            break;

        /*
        ** Time to die.
        */
        case WM_DESTROY:
            DestroyProc(hWndFrame);
            break;

        /*
        ** Process the frame-commands.  We need to let the default handler
        ** have this event, since the MDI control handles the
        ** commands as well.
        */
        case WM_COMMAND:
            CommandProc(hWndFrame,wParam,lParam);

        /*
        ** Since this is the frame-window, we need to grab the MDI client
        ** control window to pass to the MDI control.  We store this as
        ** part of the extra-object information for the frame-window.
        */
        default:
            hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND);
            return(DefFrameProc(hWndFrame,hWndClient,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| CLIENT CREATE PROCEDURE
|   This is the main event-handler for the WM_CREATE message.  It is here
|   we create the MDI control window for the application.  We store this
|   information as part of the frame-window extra object information.
\*---------------------------------------------------------------------------*/
BOOL CreateProc(HWND hWndFrame)
{
    /*
    ** Set the window information to contain the child window.
    */
    if(hWndMDIClient = CreateMDIClientWindow(hWndFrame))
        SetWindowLong(hWndFrame,CLIENTWND,(LONG)hWndMDIClient);

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| COMMAND PROCEDURE
|   This is the main event-handler for the WM_COMMAND event for the window
|   application.  All we really do is process the MENU commands for creating
|   the DEMO windows.
\*---------------------------------------------------------------------------*/
BOOL CommandProc(HWND hWndFrame, WPARAM wParam, LONG lParam)
{
    HWND hWndClient;

    lParam = lParam;

    switch(wParam)
    {
        /*
        ** Demo the poly-bezier window.
        */
        case IDM_DEMO_POLYBEZIER:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                CreatePolyWindow(hWndClient,0);
            break;

        /*
        ** Demo the xform's.
        */
        case IDM_DEMO_XFORM:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                CreateXFormWindow(hWndClient,0);
            break;

        /*
        ** Demo the maze.
        */
        case IDM_DEMO_MAZE:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                CreateMazeWindow(hWndClient,0);
            break;

        /*
        ** Demo random drawing objects.
        */
        case IDM_DEMO_DRAW:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                CreateDrawWindow(hWndClient,0);
            break;

        /*
        ** Demo bouncing region balls.
        */
        case IDM_DEMO_BOUNCE:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                CreateBounceWindow(hWndClient,0);
            break;

        /*
        ** MDI cascade the demo windows.
        */
        case IDM_WINDOW_CASCADE:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                SendMessage(hWndClient,WM_MDICASCADE,0,0l);
            break;

        /*
        ** MDI tile the demo windows.
        */
        case IDM_WINDOW_TILE:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                SendMessage(hWndClient,WM_MDITILE,0,0l);
            break;

        /*
        ** MDI arrange the MDI icons.
        */
        case IDM_WINDOW_ICON:
            if(hWndClient = (HWND)GetWindowLong(hWndFrame,CLIENTWND))
                SendMessage(hWndClient,WM_MDIICONARRANGE,0,0l);
            break;

        /*
        ** Display the about box.
        */
        case IDM_HELP_ABOUT:
    DisplayDialogBox(hWndFrame,MAKEINTRESOURCE(ABOUTBOX),(WNDPROC)AboutDlgProc,0l);
            break;

        /*
        ** Command not recognized.
        */
        default:
            return(FALSE);
    }
    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| CLIENT PAINT PROCEDURE
|   This is the main event-handler for the WM_PAINT message.
\*---------------------------------------------------------------------------*/
VOID PaintProc(HWND hWndFrame)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWndFrame,&ps))
        EndPaint(hWndFrame,&ps);

    return;
}

/*---------------------------------------------------------------------------*\
| CLIENT DESTROY PROCEDURE
|   This routine is called to cleanup global resources upon exit of the
|   application.
\*---------------------------------------------------------------------------*/
VOID DestroyProc(HWND hWndFrame)
{
    hWndFrame = hWndFrame;

    PostQuitMessage(0);

    return;
}

DWORD FAR lRandom(VOID)
{
    static DWORD glSeed = (DWORD)-365387184;

    glSeed *= 69069;
    return(++glSeed);
}


GDIDEMO.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef int (APIENTRY *WNDPROC)();
typedef WORD WPARAM;
typedef VOID *PVOID;
typedef WORD UINT;
#endif

#define APPCLASS   "GDIDEMO"
#define APPTITLE   "Windows GDI Demonstration"

#define APPMENU   1000
#define APPICON   1001
#define ABOUTBOX  1002

#define IDM_DEMO_POLYBEZIER 100
#define IDM_DEMO_XFORM      101
#define IDM_DEMO_MAZE       102
#define IDM_DEMO_DRAW       103
#define IDM_DEMO_BOUNCE     104

#define IDM_WINDOW_CASCADE  200
#define IDM_WINDOW_TILE     201
#define IDM_WINDOW_ICON     202
#define IDM_HELP_ABOUT      900

#define COLOR_SCALE_RED     1
#define COLOR_SCALE_GREEN   2
#define COLOR_SCALE_BLUE    3
#define COLOR_SCALE_GRAY    4

#define CLIENTWND           0

#define BOUNCETITLE  1
#define DRAWTITLE    2
#define MAZETITLE    3
#define POLYTITLE    4
#define XFORMTITLE   5

/*
** MAIN WINDOW ROUTINES (gdidemo.c)
*/
LONG  APIENTRY WndProc(HWND,UINT,WPARAM,LONG);
BOOL           CreateProc(HWND);
VOID           DestroyProc(HWND);
BOOL           CommandProc(HWND,WPARAM,LONG);
VOID           PaintProc(HWND);
DWORD FAR      lRandom(VOID);

/*
** INITIALIZATION ROUTINES (init.c)
*/
BOOL FAR RegisterAppClass(HANDLE);
VOID FAR UnregisterAppClass(HANDLE);
HWND FAR CreateAppWindow(HANDLE);
HWND FAR CreateMDIClientWindow(HWND);

/*
** DIALOG ROUTINES (dialog.c)
*/
BOOL APIENTRY AboutDlgProc(HWND,UINT,WPARAM,LONG);
int  FAR      DisplayDialogBox(HWND,LPSTR,WNDPROC,LONG);
VOID          PaintWindow(HWND,int);
HPALETTE      CreateColorScalePalette(HDC,int);

/*
** WINDOW ROUTINES (wininfo.c)
*/
BOOL  FAR AllocWindowInfo(HWND,WORD);
PVOID FAR LockWindowInfo(HWND);
BOOL  FAR UnlockWindowInfo(HWND);
BOOL  FAR FreeWindowInfo(HWND);

#ifdef TESTDEBUG
#define DEBUGOUT(lpszString) OutputDebugString(lpszString)
#else
#define DEBUGOUT(lpszString) {}
#endif

/*
** Porting macros...
*/
#if defined(_WIN32) || defined(WIN32)
#define GETINSTANCE(hWnd)   (HANDLE)GetWindowLong(hWnd,GWL_HINSTANCE)
#define GETCLASSBRUSH(hWnd) (HBRUSH)GetClassLong(hWnd,GCL_HBRBACKGROUND)
#else
#define GETINSTANCE(hWnd)   (HANDLE)GetWindowWord(hWnd,GWW_HINSTANCE)
#define GETCLASSBRUSH(hWnd) (HBRUSH)GetClassWord(hWnd,GCW_HBRBACKGROUND)
#endif


INIT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| INITIALIZATION MODULE
|   This module contains startup-routines for the creation of windows for this
|   application.
|
|   RegisterAppClass
|   UnregisterAppClass
|   CreateAppWindow
|   CreateMDIClientWindow
|
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include "poly.h"
#include "xform.h"
#include "maze.h"
#include "draw.h"
#include "bounce.h"

/*---------------------------------------------------------------------------*\
| REGISTER APPLICATION CLASS
|   This routine registers all classes necessary for the application.
\*---------------------------------------------------------------------------*/
BOOL FAR RegisterAppClass(HANDLE hInstance)
{
    WNDCLASS wndClass;

    /*
    ** Set the common wndClass information.  This is common to all windows
    ** of this application.
    */
    wndClass.style         = CS_HREDRAW | CS_VREDRAW;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(LONG);
    wndClass.hCursor       = LoadCursor(NULL,IDC_ARROW);
    wndClass.hInstance     = hInstance;

    /*
    ** Register the main top-level window (frame).
    */
    wndClass.lpfnWndProc   = WndProc;
    wndClass.hIcon         = LoadIcon(hInstance,MAKEINTRESOURCE(APPICON));
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = MAKEINTRESOURCE(APPMENU);
    wndClass.lpszClassName = APPCLASS;

    if(!RegisterClass(&wndClass))
        return(FALSE);

    /*
    ** Register the polybezier demo window.
    */
    wndClass.lpfnWndProc   = PolyProc;
    wndClass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = POLYCLASS;

    if(!RegisterClass(&wndClass))
    {
        UnregisterClass(APPCLASS,hInstance);
        return(FALSE);
    }

    /*
    ** Register the xform demo window.
    */
    wndClass.lpfnWndProc   = XFormProc;
    wndClass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = XFORMCLASS;

    if(!RegisterClass(&wndClass))
    {
        UnregisterClass(APPCLASS ,hInstance);
        UnregisterClass(POLYCLASS,hInstance);
        return(FALSE);
    }

    /*
    ** Register the maze demo window.
    */
    wndClass.lpfnWndProc   = MazeProc;
    wndClass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hbrBackground = GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = MAZECLASS;

    if(!RegisterClass(&wndClass))
    {
        UnregisterClass(APPCLASS  ,hInstance);
        UnregisterClass(POLYCLASS ,hInstance);
        UnregisterClass(XFORMCLASS,hInstance);
        return(FALSE);
    }

    /*
    ** Register the random draw demo window.
    */
    wndClass.lpfnWndProc   = DrawProc;
    wndClass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = DRAWCLASS;

    if(!RegisterClass(&wndClass))
    {
        UnregisterClass(APPCLASS  ,hInstance);
        UnregisterClass(POLYCLASS ,hInstance);
        UnregisterClass(XFORMCLASS,hInstance);
        UnregisterClass(MAZECLASS ,hInstance);
        return(FALSE);
    }

    /*
    ** Register the bouncing ball demo window.
    */
    wndClass.lpfnWndProc   = BounceProc;
    wndClass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName  = NULL;
    wndClass.lpszClassName = BOUNCECLASS;

    if(!RegisterClass(&wndClass))
    {
        UnregisterClass(APPCLASS  ,hInstance);
        UnregisterClass(POLYCLASS ,hInstance);
        UnregisterClass(XFORMCLASS,hInstance);
        UnregisterClass(MAZECLASS ,hInstance);
        UnregisterClass(DRAWCLASS ,hInstance);
        return(FALSE);
    }

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| UNREGISTER APPLICATION CLASS
|   This routine unregisters all classes registered for the application.
|   It is typically called upon termination of the application.
\*---------------------------------------------------------------------------*/
VOID FAR UnregisterAppClass(HANDLE hInstance)
{
    UnregisterClass(APPCLASS   ,hInstance);
    UnregisterClass(POLYCLASS  ,hInstance);
    UnregisterClass(XFORMCLASS ,hInstance);
    UnregisterClass(MAZECLASS  ,hInstance);
    UnregisterClass(DRAWCLASS  ,hInstance);
    UnregisterClass(BOUNCECLASS,hInstance);

    return;
}

/*---------------------------------------------------------------------------*\
| CREATE APPLICATION WINDOW
|   This routine creates the main top-level window.
\*---------------------------------------------------------------------------*/
HWND FAR CreateAppWindow(HANDLE hInstance)
{
    /*
    ** Upon creation of the window, initialize the extra object information.
    */
    return(CreateWindow(APPCLASS,APPTITLE,WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
            NULL,NULL,hInstance,NULL));
}

/*---------------------------------------------------------------------------*\
| CREATE MDI CLIENT WINDOW
|   This routine creates the client window which handles the MDI windows.
|   The window will eventually be size to fit into the frame-window's client
|   area.
\*---------------------------------------------------------------------------*/
HWND FAR CreateMDIClientWindow(HWND hWndFrame)
{
    HWND               hWndClient;
    HANDLE             hInstance;
    CLIENTCREATESTRUCT ccs;

    /*
    ** Initialize the client struct to point to define the menu and child
    ** identifiers.  The menu item specifies the parent menu in which the
    ** the list of MDI childs will be appended to this menu.
    */
    ccs.hWindowMenu  = NULL;
    ccs.idFirstChild = 0;

    hInstance = GETINSTANCE(hWndFrame);

    hWndClient = CreateWindow("MDICLIENT",NULL,WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
            0,0,1,1,hWndFrame,NULL,hInstance,(LPVOID)&ccs);

    return(hWndClient);
}


MAZE.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| MAZE MODULE
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include  "maze.h"

/*---------------------------------------------------------------------------*\
| CREATE MAZE WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
HWND FAR CreateMazeWindow(HWND hWndClient, int nItem)
{
    HANDLE          hInstance;
    MDICREATESTRUCT mcs;
    static char buffer[256];

    hInstance = GETINSTANCE(hWndClient);
    LoadString (hInstance, MAZETITLE, buffer, 256);

    /*
    ** Initialize the MDI create struct for creation of the
    ** test window.
    */
    mcs.szClass = MAZECLASS;
    mcs.szTitle = buffer;
    mcs.hOwner  = hInstance;
    mcs.x       = CW_USEDEFAULT;
    mcs.y       = CW_USEDEFAULT;
    mcs.cx      = CW_USEDEFAULT;
    mcs.cy      = CW_USEDEFAULT;
    mcs.style   = 0l;
    mcs.lParam  = (LONG)nItem;

    return((HWND)SendMessage(hWndClient,WM_MDICREATE,0,(LONG)(LPMDICREATESTRUCT)&mcs));
}

/*---------------------------------------------------------------------------*\
| MAZE WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
LONG APIENTRY MazeProc(HWND hWnd, UINT wMsg, WPARAM wParam, LONG lParam)
{
    switch(wMsg)
    {
        case WM_CREATE:
            MazeCreateProc(hWnd);
            break;

        case WM_COMMAND:
            MazeCommandProc(hWnd,wParam,lParam);
            break;

        case WM_TIMER:
            DrawMaze(hWnd);
            break;

        case WM_PAINT:
            MazePaintProc(hWnd);
            break;

        case WM_DESTROY:
            MazeDestroyProc(hWnd);
            break;

        default:
            return(DefMDIChildProc(hWnd,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| MAZE CREATE PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL MazeCreateProc(HWND hWnd)
{
    PMAZEDATA ppd;

    if(AllocWindowInfo(hWnd,sizeof(MAZEDATA)))
    {
        if(ppd = (PMAZEDATA)LockWindowInfo(hWnd))
        {
            UnlockWindowInfo(hWnd);
            return(TRUE);
        }
        FreeWindowInfo(hWnd);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| MAZE COMMAND PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL MazeCommandProc(HWND hWnd, WPARAM wParam, LONG lParam)
{
    hWnd   = hWnd;
    wParam = wParam;
    lParam = lParam;

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| MAZE PAINT PROCEDURE
\*---------------------------------------------------------------------------*/
VOID MazePaintProc(HWND hWnd)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWnd,&ps))
        EndPaint(hWnd,&ps);

    return;
}

/*---------------------------------------------------------------------------*\
| MAZE DESTROY PROCEDURE
\*---------------------------------------------------------------------------*/
VOID MazeDestroyProc(HWND hWnd)
{
    KillTimer(hWnd,1);
    FreeWindowInfo(hWnd);
    return;
}

VOID DrawMaze(HWND hWnd)
{
    hWnd = hWnd;

    return;
}


MAZE.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef WORD WPARAM;
//#else
//typedef DWORD WPARAM;
#endif

#define MAZECLASS  "MAZEDEMO"

typedef struct _MAZEDATA
{
    int nMaze;
} MAZEDATA;
typedef MAZEDATA      *PMAZEDATA;
typedef MAZEDATA NEAR *NPMAZEDATA;
typedef MAZEDATA FAR  *LPMAZEDATA;

/*
** MAZE WINDOW ROUTINES (maze.c)
*/
HWND  FAR      CreateMazeWindow(HWND,int);
LONG  APIENTRY MazeProc(HWND,UINT,WPARAM,LONG);
BOOL           MazeCreateProc(HWND);
VOID           MazeDestroyProc(HWND);
BOOL           MazeCommandProc(HWND,WPARAM,LONG);
VOID           MazePaintProc(HWND);

VOID DrawMaze(HWND);


POLY.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| POLYBEZIER MODULE
|   This module contains the routines for demo
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include  "poly.h"

/*---------------------------------------------------------------------------*\
| CREATE BEZIER WINDOW PROCEDURE
|   Create the bezier MDI-child window.
\*---------------------------------------------------------------------------*/
HWND FAR CreatePolyWindow(HWND hWndClient, int nItem)
{
    HANDLE          hInstance;
    MDICREATESTRUCT mcs;
    static char buffer[256];

    hInstance = GETINSTANCE(hWndClient);
    LoadString (hInstance, POLYTITLE, buffer, 256);

    /*
    ** Initialize the MDI create struct for creation of the
    ** test window.
    */
    mcs.szClass = POLYCLASS;
    mcs.szTitle = buffer;
    mcs.hOwner  = hInstance;
    mcs.x       = CW_USEDEFAULT;
    mcs.y       = CW_USEDEFAULT;
    mcs.cx      = CW_USEDEFAULT;
    mcs.cy      = CW_USEDEFAULT;
    mcs.style   = 0l;
    mcs.lParam  = (LONG)nItem;

    return((HWND)SendMessage(hWndClient,WM_MDICREATE,0,(LONG)(LPMDICREATESTRUCT)&mcs));
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER WINDOW PROCEDURE
|   This is the main window function for the polybezier demo window.
\*---------------------------------------------------------------------------*/
LONG APIENTRY PolyProc(HWND hWnd, UINT wMsg, WPARAM wParam, LONG lParam)
{
    switch(wMsg)
    {
        case WM_CREATE:
            PolyCreateProc(hWnd);
            break;

        case WM_COMMAND:
            PolyCommandProc(hWnd,wParam,lParam);
            break;

        case WM_MOVE:
            PolyRedraw(hWnd);
            break;

        case WM_TIMER:
            PolyDrawBez(hWnd);
            break;

        case WM_PAINT:
            PolyPaintProc(hWnd);
            break;

        case WM_DESTROY:
            PolyDestroyProc(hWnd);
            break;

        default:
            return(DefMDIChildProc(hWnd,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER CREATE PROCEDURE
|   Create the polybezier window for the demo application.  This is a child
|   of the MDI client window.  Allocate the extra object information for
|   handling of the polybezier demo.
\*---------------------------------------------------------------------------*/
BOOL PolyCreateProc(HWND hWnd)
{
    PPOLYDATA ppd;

    if(AllocWindowInfo(hWnd,sizeof(POLYDATA)))
    {
        if(ppd = (PPOLYDATA)LockWindowInfo(hWnd))
        {
            ppd->nBezTotal  = 20;
            ppd->nBezCurr   = 0;
            ppd->nColor     = 0;
            ppd->hBezBuffer = GlobalAlloc(GHND,(DWORD)(sizeof(BEZBUFFER) * MAX_BEZIER));

            UnlockWindowInfo(hWnd);

            PolyInitPoints(hWnd);

            SetTimer(hWnd,1,50,NULL);
            return(TRUE);
        }
        FreeWindowInfo(hWnd);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER COMMAND PROCEDURE
|   Process polybezier commands.  This is a NOP for now.  But who knows what
|   tomorrow may bring.
\*---------------------------------------------------------------------------*/
BOOL PolyCommandProc(HWND hWnd, WPARAM wParam, LONG lParam)
{
    hWnd   = hWnd;
    wParam = wParam;
    lParam = lParam;

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER PAINT PROCEDURE
|   Repaint the bezier window.  All we really do here is validate our window,
|   and reset the array of bezier objects.
\*---------------------------------------------------------------------------*/
VOID PolyPaintProc(HWND hWnd)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWnd,&ps))
        EndPaint(hWnd,&ps);

    PolyRedraw(hWnd);

    return;
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER DESTROY PROCEDURE
|   Kill the polybezier demo.  Free up the resources allocated on behalf of
|   this object.
\*---------------------------------------------------------------------------*/
VOID PolyDestroyProc(HWND hWnd)
{
    PPOLYDATA ppd;

    KillTimer(hWnd,1);
    if(ppd = (PPOLYDATA)LockWindowInfo(hWnd))
    {
        GlobalFree(ppd->hBezBuffer);
        UnlockWindowInfo(hWnd);
    }
    FreeWindowInfo(hWnd);

    return;
}

/*---------------------------------------------------------------------------*\
| GET NEW VELOCITY
|   This routine creates a new velocity for the bezier points.  Each bezier
|   point is randomly chosen.  The two inside points should have a speed
|   less then the endpoints (most of the time-better effect).
\*---------------------------------------------------------------------------*/
int PolyNewVel(int i)
{
    int nRet;

    if ((i == 1) || (i == 2))
        nRet = (int)((lRandom() % VELMAX) / 3) + VELMIN;
    else
        nRet = (int)(lRandom() % VELMAX) + VELMIN;

    return((nRet < 0) ? -nRet : nRet);
}

/*---------------------------------------------------------------------------*\
| INITIALIZE POLYBEZIER POINTS
|   This routine initializes the polybezier points for the first object.  This
|   is performed on startup of the window.
\*---------------------------------------------------------------------------*/
VOID PolyInitPoints(HWND hWnd)
{
    PPOLYDATA   ppd;
    LPBEZBUFFER lpBez;
    int         idx;
    RECT        rect;

    if(ppd = (PPOLYDATA)LockWindowInfo(hWnd))
    {
        if(lpBez = (LPBEZBUFFER)GlobalLock(ppd->hBezBuffer))
        {
            GetClientRect(hWnd,&rect);

            for(idx=0; idx < BEZ_PTS-1; idx++)
            {
                lpBez->pPts[idx].x = lRandom() % rect.right;
                lpBez->pPts[idx].y = lRandom() % rect.bottom;

                ppd->pVel[idx].x = PolyNewVel(idx);
                ppd->pVel[idx].y = PolyNewVel(idx);
            }
            GlobalUnlock(ppd->hBezBuffer);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}

/*---------------------------------------------------------------------------*\
| POLYBEZIER REDRAW
|   This routine resets the bezier curves and redraws the poly-bezier client
|   area.
\*---------------------------------------------------------------------------*/
VOID PolyRedraw(HWND hWnd)
{
    PPOLYDATA   ppd;
    LPBEZBUFFER lpBez,lpCurr;
    HDC         hDC;
    int         i,j;
    RECT        rect;

    if(ppd = (PPOLYDATA)LockWindowInfo(hWnd))
    {
        if(lpBez = (LPBEZBUFFER)GlobalLock(ppd->hBezBuffer))
        {
            if(hDC = GetDC(hWnd))
            {
                /*
                ** Save the current bezier.  Set the first bezier in the
                ** array to that curve, and use it as a basis for the next
                ** series.
                */
                lpCurr        = lpBez+ppd->nBezCurr;
                *lpBez        = *lpCurr;
                ppd->nBezCurr = 0;

                /*
                ** Clean the curves (all but the first curve).
                */
                for(j=1; j < ppd->nBezTotal; j++)
                {
                    for(i=0; i < BEZ_PTS; i++)
                    {
                        (lpBez+j)->pPts[i].x = -1;
                        (lpBez+j)->pPts[i].y = 0;
                    }
                }

                /*
                ** Clear the display.
                */
                GetClientRect(hWnd,&rect);
                BitBlt(hDC,0,0,rect.right, rect.bottom,(HDC)0,0,0,0);

                /*
                ** Draw the first curve in the bezier array.
                */
#if defined(_WIN32) && defined(WIN32)
                PolyBezier(hDC,lpBez->pPts,BEZ_PTS);
#else
                Polyline(hDC,lpBez->pPts,BEZ_PTS);
#endif
                ReleaseDC(hWnd,hDC);
            }
            GlobalUnlock(ppd->hBezBuffer);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}

VOID PolyDrawBez(HWND hWnd)
{
    PPOLYDATA   ppd;
    LPBEZBUFFER lpBez,lpCurr,lpPrev;
    int         idx,x,y;
    RECT        rect;
    HDC         hDC;
    HPEN        hPen;

static COLORREF crColor[] = {0x000000FF,0x0000FF00,0x00FF0000,0x0000FFFF,
                             0x00FF00FF,0x00FFFF00,0x00FFFFFF,0x00000080,
                             0x00008000,0x00800000,0x00008080,0x00800080,
                             0x00808000,0x00808080,0x000000FF,0x0000FF00,
                             0x00FF0000,0x0000FFFF,0x00FF00FF,0x00FFFF00};

    if(ppd = (PPOLYDATA)LockWindowInfo(hWnd))
    {
        if(lpBez = (LPBEZBUFFER)GlobalLock(ppd->hBezBuffer))
        {
            if(hDC = GetDC(hWnd))
            {
                GetClientRect(hWnd,&rect);

                lpPrev = lpBez+ppd->nBezCurr;

                ppd->nBezCurr += 1;

                if(ppd->nBezCurr >= ppd->nBezTotal)
                {
                    ppd->nBezCurr = 0;
                    ppd->nColor  = (++ppd->nColor % 20);
                }
                lpCurr = lpBez+ppd->nBezCurr;

                if(lpCurr->pPts[0].x != -1)
                {
                    hPen = SelectObject(hDC,GetStockObject(BLACK_PEN));
#if defined(_WIN32) && defined(WIN32)
                    PolyBezier(hDC,lpCurr->pPts,BEZ_PTS);
#else
                    Polyline(hDC,lpCurr->pPts,BEZ_PTS);
#endif
                    SelectObject(hDC,hPen);
                }

                for(idx=0; idx < BEZ_PTS; idx++)
                {
                    x  = lpPrev->pPts[idx].x;
                    y  = lpPrev->pPts[idx].y;
                    x += ppd->pVel[idx].x;
                    y += ppd->pVel[idx].y;

                    if(x >= rect.right)
                    {
                        x = rect.right - ((x - rect.right)+1);
                        ppd->pVel[idx].x = -PolyNewVel(idx);
                    }

                    if(x <= rect.left)
                    {
                        x = rect.left + ((rect.left - x)+1);
                        ppd->pVel[idx].x = PolyNewVel(idx);
                    }

                    if(y >= rect.bottom)
                    {
                        y = rect.bottom - ((y - rect.bottom)+1);
                        ppd->pVel[idx].y = -PolyNewVel(idx);
                    }

                    if(y <= rect.top)
                    {
                        y = rect.top + ((rect.top - y)+1);
                        ppd->pVel[idx].y = PolyNewVel(idx);
                    }

                    lpCurr->pPts[idx].x = x;
                    lpCurr->pPts[idx].y = y;

                }

                hPen = SelectObject(hDC,CreatePen(PS_SOLID,1,crColor[ppd->nColor]));
#if defined(_WIN32) && defined(WIN32)
                PolyBezier(hDC,lpCurr->pPts,BEZ_PTS);
#else
                Polyline(hDC,lpCurr->pPts,BEZ_PTS);
#endif
                DeleteObject(SelectObject(hDC,hPen));

#if defined(_WIN32) && defined(WIN32)
                SetROP2(hDC,R2_COPYPEN);
#endif
                ReleaseDC(hWnd,hDC);
            }
            GlobalUnlock(ppd->hBezBuffer);
        }
        UnlockWindowInfo(hWnd);
    }
}


POLY.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef WORD WPARAM;
//#else
//typedef DWORD WPARAM;
#endif

#define POLYCLASS  "POLYDEMO"

#define MAX_BEZIER 80
#define VELMAX     10
#define VELMIN      2
#define BEZ_CURVES  1              //Number of Curves in the Bezier
#define BEZ_PTS    3*BEZ_CURVES+1  //Number of Points is the #Curves * 3 + 1

/*
** This structue defines a basic bezier curve.  It is intended to be used in
** an array of beziers.
*/
typedef struct _BEZBUFFER
{
    POINT pPts[BEZ_PTS];
} BEZBUFFER;
typedef BEZBUFFER      *PBEZBUFFER;
typedef BEZBUFFER NEAR *NPBEZBUFFER;
typedef BEZBUFFER FAR  *LPBEZBUFFER;

/*
** This is the main object for the polybezier window.  This structure defines
** a series of bezier curves and the distance between each curve.
*/
typedef struct _POLYDATA
{
    int      nBezTotal;
    int      nBezCurr;
    int      nColor;
    POINT    pVel[BEZ_PTS];
    HANDLE   hBezBuffer;
} POLYDATA;
typedef POLYDATA      *PPOLYDATA;
typedef POLYDATA NEAR *NPPOLYDATA;
typedef POLYDATA FAR  *LPPOLYDATA;

/*
** POLYBEZIER WINDOW ROUTINES (poly.c)
*/
HWND  FAR      CreatePolyWindow(HWND,int);
LONG  APIENTRY PolyProc(HWND,UINT,WPARAM,LONG);
BOOL           PolyCreateProc(HWND);
VOID           PolyDestroyProc(HWND);
BOOL           PolyCommandProc(HWND,WPARAM,LONG);
VOID           PolyPaintProc(HWND);

VOID           PolyRedraw(HWND);
int            PolyNewVel(int);
VOID           PolyInitPoints(HWND);
VOID           PolyDrawBez(HWND);


BOUNCE.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| BOUNCING BALL MODULE
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include "bounce.h"

VOID XFormClear(PMYXFORM);
VOID XFormScale(PMYXFORM,int,int);
VOID XFormTrans(PMYXFORM,int,int);
VOID XFormPoint(PMYXFORM,LPPOINT);

/*---------------------------------------------------------------------------*\
| CREATE BOUNCE WINDOW PROCEDURE
|
\*---------------------------------------------------------------------------*/
HWND FAR CreateBounceWindow(HWND hWndClient, int nItem)
{
    HANDLE          hInstance;
    MDICREATESTRUCT mcs;
    static char buffer[256];

    hInstance = GETINSTANCE(hWndClient);
    LoadString (hInstance, BOUNCETITLE, buffer, 256);

    /*
    ** Initialize the MDI create struct for creation of the
    ** test window.
    */
    mcs.szClass = BOUNCECLASS;
    mcs.szTitle = buffer;
    mcs.hOwner  = hInstance;
    mcs.x       = CW_USEDEFAULT;
    mcs.y       = CW_USEDEFAULT;
    mcs.cx      = CW_USEDEFAULT;
    mcs.cy      = CW_USEDEFAULT;
    mcs.style   = 0l;
    mcs.lParam  = (LONG)nItem;

    return((HWND)SendMessage(hWndClient,WM_MDICREATE,0,(LONG)(LPMDICREATESTRUCT)&mcs));
}

/*---------------------------------------------------------------------------*\
| BOUNCE WINDOW PROCEDURE
|
\*---------------------------------------------------------------------------*/
LONG APIENTRY BounceProc(HWND hWnd, UINT wMsg, WPARAM wParam, LONG lParam)
{
    switch(wMsg)
    {
        case WM_CREATE:
            BounceCreateProc(hWnd);
            break;

        case WM_MOVE:
            BounceRefresh(hWnd);
            break;

        case WM_COMMAND:
            BounceCommandProc(hWnd,wParam,lParam);
            break;

        case WM_TIMER:
            BounceObjects(hWnd);
            break;

        case WM_PAINT:
            BouncePaintProc(hWnd);
            break;

        case WM_DESTROY:
            BounceDestroyProc(hWnd);
            break;

        default:
            return(DefMDIChildProc(hWnd,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| BOUNCE CREATE PROCEDURE
|
\*---------------------------------------------------------------------------*/
BOOL BounceCreateProc(HWND hWnd)
{
    PBOUNCEDATA pbd;

    if(AllocWindowInfo(hWnd,sizeof(BOUNCEDATA)))
    {
        if(pbd = (PBOUNCEDATA)LockWindowInfo(hWnd))
        {
            pbd->hBall1 = ballCreate(hWnd,20,20,RGB(255,0,0));
            pbd->hBall2 = ballCreate(hWnd,20,20,RGB(0,0,255));
            pbd->hBall3 = ballCreate(hWnd,20,20,RGB(0,255,0));
            pbd->hBall4 = ballCreate(hWnd,20,20,RGB(255,0,255));

            UnlockWindowInfo(hWnd);

            SetTimer(hWnd,1,50,NULL);
            return(TRUE);
        }
        FreeWindowInfo(hWnd);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| BOUNCE COMMAND PROCEDURE
|
\*---------------------------------------------------------------------------*/
BOOL BounceCommandProc(HWND hWnd, WPARAM wParam, LONG lParam)
{
    hWnd   = hWnd;
    wParam = wParam;
    lParam = lParam;

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| BOUNCE PAINT PROCEDURE
|
\*---------------------------------------------------------------------------*/
VOID BouncePaintProc(HWND hWnd)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWnd,&ps))
        EndPaint(hWnd,&ps);

    BounceRefresh(hWnd);

    return;
}

/*---------------------------------------------------------------------------*\
| BOUNCE DESTROY PROCEDURE
|
\*---------------------------------------------------------------------------*/
VOID BounceDestroyProc(HWND hWnd)
{
    PBOUNCEDATA pbd;

    KillTimer(hWnd,1);
    if(pbd = (PBOUNCEDATA)LockWindowInfo(hWnd))
    {
        ballDestroy(pbd->hBall1);
        ballDestroy(pbd->hBall2);
        ballDestroy(pbd->hBall3);
        ballDestroy(pbd->hBall4);

        UnlockWindowInfo(hWnd);
    }
    FreeWindowInfo(hWnd);
    return;
}

/*---------------------------------------------------------------------------*\
| BOUNCE REFRESH
|
\*---------------------------------------------------------------------------*/
VOID BounceRefresh(HWND hWnd)
{
    PBOUNCEDATA pbd;
    RECT        rect;
    HDC         hDC;
    POINT       pDimensions;
    int         xDirection,yDirection;

    if(pbd = (PBOUNCEDATA)LockWindowInfo(hWnd))
    {
        if(hDC = GetDC(hWnd))
        {
            GetClientRect(hWnd,&rect);
            FillRect(hDC,&rect,GETCLASSBRUSH(hWnd));

            xDirection = ((lRandom() % 5)+5);
            yDirection = ((lRandom() % 5)+5);
            ballSetPosition(pbd->hBall1,0,0);
            ballSetDirection(pbd->hBall1,xDirection,yDirection);

            ballGetDimensions(pbd->hBall2,&pDimensions);
            ballSetPosition(pbd->hBall2,rect.right-pDimensions.x,0);
            xDirection = -(((int)lRandom() % 5)+5);
            yDirection =  ((lRandom() % 5)+5);
            ballSetDirection(pbd->hBall2,xDirection,yDirection);

            ballGetDimensions(pbd->hBall3,&pDimensions);
            ballSetPosition(pbd->hBall3,0,rect.bottom-pDimensions.y);
            xDirection =  ((lRandom() % 5)+5);
            yDirection = -((int)(lRandom() % 5)+5);
            ballSetDirection(pbd->hBall3,xDirection,yDirection);

            ballGetDimensions(pbd->hBall4,&pDimensions);
            ballSetPosition(pbd->hBall4,rect.right-pDimensions.x,rect.bottom-pDimensions.y);
            xDirection = -((int)(lRandom() % 5)+5);
            yDirection = -((int)(lRandom() % 5)+5);
            ballSetDirection(pbd->hBall4,xDirection,yDirection);

            ballBounce(pbd->hBall1);
            ballBounce(pbd->hBall2);
            ballBounce(pbd->hBall3);
            ballBounce(pbd->hBall4);

            ReleaseDC(hWnd,hDC);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}

VOID BounceObjects(HWND hWnd)
{
    PBOUNCEDATA pbd;

    if(pbd = (PBOUNCEDATA)LockWindowInfo(hWnd))
    {
        /*
        ** Determine if the balls hit any of the edges of the display.  If
        ** they do, then these functions reset their position to something
        ** more desireable and re-do their direction.
        */
        CheckEdgePosition(hWnd,pbd->hBall1);
        CheckEdgePosition(hWnd,pbd->hBall2);
        CheckEdgePosition(hWnd,pbd->hBall3);
        CheckEdgePosition(hWnd,pbd->hBall4);

        ballBounce(pbd->hBall1);
        ballBounce(pbd->hBall2);
        ballBounce(pbd->hBall3);
        ballBounce(pbd->hBall4);

        UnlockWindowInfo(hWnd);
    }
    return;
}

VOID CheckEdgePosition(HWND hWnd, HANDLE hBall)
{
    POINT pPos,pDir,pDim;
    int   xNewPos,yNewPos,xNewDir,yNewDir;
    RECT  rect;

    ballGetPosition(hBall,&pPos);
    ballGetDirection(hBall,&pDir);
    ballGetDimensions(hBall,&pDim);

    GetClientRect(hWnd,&rect);

    /*
    ** Check each edge of the client rectagle.  If the ball goes past the
    ** boundries, reset the position and give it a new direction.
    */
    xNewDir = pDir.x;
    yNewDir = pDir.y;
    xNewPos = pPos.x+pDir.x;
    yNewPos = pPos.y+pDir.y;

    if(xNewPos < rect.left)
    {
        xNewDir = ((lRandom() % 5)+5);
        ballSetPosition(hBall,rect.left,pPos.y);
    }
    if((xNewPos+pDim.x) > rect.right)
    {
        xNewDir = -(((int)lRandom() % 5)+5);
        ballSetPosition(hBall,rect.right-pDim.x,pPos.y);
    }
    if(yNewPos < rect.top)
    {
        yNewDir = ((lRandom() % 5)+5);
        ballSetPosition(hBall,pPos.x,rect.top);
    }
    if((yNewPos+pDim.y) > rect.bottom)
    {
        yNewDir = -(((int)lRandom() % 5)+5);
        ballSetPosition(hBall,pPos.x,rect.bottom-pDim.y);
    }
    ballSetDirection(hBall,xNewDir,yNewDir);

    return;
}

/*---------------------------------------------------------------------------*\
| CLEAR XFORM
|
\*---------------------------------------------------------------------------*/
VOID XFormClear(PMYXFORM pXForm)
{
    int Row,Col;

    for(Row=0; Row < 3; Row++)
        for(Col=0; Col < 3; Col++)
            if(Row == Col)
                pXForm->xForm[Row][Col] = 1;
            else
                pXForm->xForm[Row][Col] = 0;
    return;
}

/*---------------------------------------------------------------------------*\
| XFORM SCALE
|
\*---------------------------------------------------------------------------*/
VOID XFormScale(PMYXFORM pXForm, int xScale, int yScale)
{
    int idx;

    for(idx=0; idx < 3; idx++)
    {
        pXForm->xForm[idx][0] = pXForm->xForm[idx][0] * xScale;
        pXForm->xForm[idx][1] = pXForm->xForm[idx][1] * yScale;
    }
    return;
}

VOID XFormTrans(PMYXFORM pXForm, int xTrans, int yTrans)
{
    pXForm->xForm[2][0] = pXForm->xForm[2][0] + xTrans;
    pXForm->xForm[2][1] = pXForm->xForm[2][1] + yTrans;

    return;
}

VOID XFormPoint(PMYXFORM pXForm, LPPOINT pPoint)
{
    int x,y;

    x = (pXForm->xForm[0][0] * pPoint->x) + (pXForm->xForm[1][0] * pPoint->y) + pXForm->xForm[2][0];
    y = (pXForm->xForm[0][1] * pPoint->x) + (pXForm->xForm[1][1] * pPoint->y) + pXForm->xForm[2][1];

    pPoint->x = x;
    pPoint->y = y;

    return;
}

/*
** -------------------------------
** -------------------------------
** -------------------------------
*/

HANDLE ballCreate(HWND hWnd, int nWidth, int nHeight, COLORREF crColor)
{
    HANDLE    hBall;
    PBALLDATA pbd;

    if(hBall = LocalAlloc(LHND,sizeof(BALLDATA)))
    {
        if(pbd = (PBALLDATA)LocalLock(hBall))
        {
            pbd->hWnd         = hWnd;
            pbd->nWidth       = nWidth;
            pbd->nHeight      = nHeight;
            pbd->xDirection   = 0;
            pbd->yDirection   = 0;
            pbd->bNewPosition = FALSE;
            pbd->xPosition    = 0;
            pbd->yPosition    = 0;
            pbd->crColor      = crColor;
            XFormClear(&pbd->xForm);

            LocalUnlock(hBall);
            return(hBall);
        }
        LocalFree(hBall);
    }
    return(NULL);
}

BOOL ballDestroy(HANDLE hBall)
{
    if(LocalFree(hBall))
        return(FALSE);
    return(TRUE);
}

VOID ballBounce(HANDLE hBall)
{
    PBALLDATA pbd;
    HDC       hDC;
    HBRUSH    hBrush;
    POINT     pOrg,pExt;
    HRGN      hTmp,hOld,hNew;

    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        /*
        ** Create old ball object.
        */
        pOrg.x = 0;
        pOrg.y = 0;
        pExt.x = pbd->nWidth;
        pExt.y = pbd->nHeight;
        XFormPoint(&pbd->xForm,&pOrg);
        XFormPoint(&pbd->xForm,&pExt);
        hOld = CreateEllipticRgn(pOrg.x,pOrg.y,pExt.x,pExt.y);

        /*
        ** Create new ball object.
        */
        if(pbd->bNewPosition)
        {
            pbd->bNewPosition = FALSE;
            XFormClear(&pbd->xForm);
            XFormTrans(&pbd->xForm,pbd->xPosition,pbd->yPosition);
        }
        else
            XFormTrans(&pbd->xForm,pbd->xDirection,pbd->yDirection);
        pOrg.x = 0;
        pOrg.y = 0;
        pExt.x = pbd->nWidth;
        pExt.y = pbd->nHeight;
        XFormPoint(&pbd->xForm,&pOrg);
        XFormPoint(&pbd->xForm,&pExt);
        hNew = CreateEllipticRgn(pOrg.x,pOrg.y,pExt.x,pExt.y);

        if(hDC = GetDC(pbd->hWnd))
        {
            hTmp = CreateRectRgn(1,1,2,2);
            CombineRgn(hTmp,hOld,hNew,RGN_DIFF);
            FillRgn(hDC,hTmp,GETCLASSBRUSH(pbd->hWnd));

            hBrush = CreateSolidBrush(pbd->crColor);
            FillRgn(hDC,hNew,hBrush);
            DeleteObject(hBrush);

            ReleaseDC(pbd->hWnd,hDC);

            DeleteObject(hTmp);
        }

        DeleteObject(hOld);
        DeleteObject(hNew);

        LocalUnlock(hBall);
    }
    return;
}

BOOL ballGetDimensions(HANDLE hBall, LPPOINT pDimension)
{
    PBALLDATA pbd;
    BOOL      bOK;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pDimension->x = pbd->nWidth;
        pDimension->y = pbd->nHeight;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}

BOOL ballSetDimensions(HANDLE hBall, int nWidth, int nHeight)
{
    PBALLDATA pbd;
    BOOL      bOK;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pbd->nWidth  = nWidth;
        pbd->nHeight = nHeight;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}

BOOL ballGetDirection(HANDLE hBall, LPPOINT pDirection)
{
    PBALLDATA pbd;
    BOOL      bOK;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pDirection->x = pbd->xDirection;
        pDirection->y = pbd->yDirection;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}

BOOL ballSetDirection(HANDLE hBall, int xDirection, int yDirection)
{
    PBALLDATA pbd;
    BOOL      bOK;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pbd->xDirection = xDirection;
        pbd->yDirection = yDirection;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}

BOOL ballGetPosition(HANDLE hBall, LPPOINT pPosition)
{
    PBALLDATA pbd;
    BOOL      bOK;
    POINT     pOrg;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pOrg.x = 0;
        pOrg.y = 0;
        XFormPoint(&pbd->xForm,&pOrg);

        pPosition->x = pOrg.x;
        pPosition->y = pOrg.y;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}

BOOL ballSetPosition(HANDLE hBall, int x, int y)
{
    PBALLDATA pbd;
    BOOL      bOK;

    bOK = FALSE;
    if(pbd = (PBALLDATA)LocalLock(hBall))
    {
        pbd->bNewPosition = TRUE;
        pbd->xPosition    = x;
        pbd->yPosition    = y;

        bOK = TRUE;
        LocalUnlock(hBall);
    }
    return(bOK);
}


WININFO.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*---------------------------------------------------------------------------*\
| WINDOW INFORMATION MODULE
|   This module contains the routines which deal with obtaining the extra
|   object information associated with a window.  For these to work, the
|   window class must reserve the 0th word of the win-class object to be
|   used to hold global-memory handle.
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"

/*---------------------------------------------------------------------------*\
| ALLOC WINDOW INFO
|   This routine allocates memory out of the application heap for storing
|   extra memory for the window.  It is alway referenced as offset 0.
\*---------------------------------------------------------------------------*/
BOOL FAR AllocWindowInfo(HWND hWnd, WORD wSize)
{
    HANDLE hsd;

    if(hsd = LocalAlloc(LHND,(WORD)wSize))
    {
        SetWindowLong(hWnd,0,(LONG)hsd);
        return(TRUE);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| LOCK WINDOW INFO
|   This routine de-references the extra-memory associated with the window.
|   it locks the object and gives the caller a pointer to the memory.
\*---------------------------------------------------------------------------*/
PVOID FAR LockWindowInfo(HWND hWnd)
{
    HANDLE hMem;
    PVOID  pMem;

    pMem = NULL;
    if(hMem = (HANDLE)GetWindowLong(hWnd,0))
        pMem = (PVOID)LocalLock(hMem);

    return(pMem);
}

/*---------------------------------------------------------------------------*\
| UNLOCK WINDOW INFO
|   This routine unlocks the memory the caller has previously locked.
\*---------------------------------------------------------------------------*/
BOOL FAR UnlockWindowInfo(HWND hWnd)
{
    HANDLE hMem;

    if(hMem = (HANDLE)GetWindowLong(hWnd,0))
        if(!LocalUnlock(hMem))
            return(TRUE);

    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| FREE WINDOW INFO
|   This routine frees the object memory associated with the window.
\*---------------------------------------------------------------------------*/
BOOL FAR FreeWindowInfo(HWND hWnd)
{
    LOCALHANDLE hMem;

    if(hMem = (HANDLE)GetWindowLong(hWnd,0))
        LocalFree(hMem);
    return(TRUE);
}


XFORM.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*-------------------------------------------------------------------------*\
| XFORM MODULE
\*---------------------------------------------------------------------------*/

#include <windows.h>
#include "gdidemo.h"
#include "xform.h"

VOID ResetXForm(HWND);
VOID DrawXForm(HWND);

/*---------------------------------------------------------------------------*\
| CREATE XFORM WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
HWND FAR CreateXFormWindow(HWND hWndClient, int nItem)
{
    HANDLE          hInstance;
    MDICREATESTRUCT mcs;
    static char buffer[256];

    hInstance = GETINSTANCE(hWndClient);
    LoadString (hInstance, XFORMTITLE, buffer, 256);

    /*
    ** Initialize the MDI create struct for creation of the
    ** test window.
    */
    mcs.szClass = XFORMCLASS;
    mcs.szTitle = buffer;
    mcs.hOwner  = hInstance;
    mcs.x       = CW_USEDEFAULT;
    mcs.y       = CW_USEDEFAULT;
    mcs.cx      = CW_USEDEFAULT;
    mcs.cy      = CW_USEDEFAULT;
    mcs.style   = 0l;
    mcs.lParam  = (LONG)nItem;

    return((HWND)SendMessage(hWndClient,WM_MDICREATE,0,(LONG)(LPMDICREATESTRUCT)&mcs));
}

/*---------------------------------------------------------------------------*\
| XFORM WINDOW PROCEDURE
\*---------------------------------------------------------------------------*/
LONG APIENTRY XFormProc(HWND hWnd, UINT wMsg, WPARAM wParam, LONG lParam)
{
    switch(wMsg)
    {
        case WM_CREATE:
            XFormCreateProc(hWnd);
            break;

        case WM_MOVE:
            ResetXForm(hWnd);
            break;

        case WM_TIMER:
            DrawXForm(hWnd);
            break;

        case WM_COMMAND:
            XFormCommandProc(hWnd,wParam,lParam);
            break;

        case WM_PAINT:
            XFormPaintProc(hWnd);
            break;

        case WM_DESTROY:
            XFormDestroyProc(hWnd);
            break;

        default:
            return(DefMDIChildProc(hWnd,wMsg,wParam,lParam));
    }
    return(0l);
}

/*---------------------------------------------------------------------------*\
| XFORM CREATE PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL XFormCreateProc(HWND hWnd)
{
    PXFORMDATA pxd;

#ifndef NOT_IMPLEMENTED

    RECT       rect;
    int        dx,dy;

#endif

    if(AllocWindowInfo(hWnd,sizeof(XFORMDATA)))
    {
        if(pxd = (PXFORMDATA)LockWindowInfo(hWnd))
        {

#ifndef NOT_IMPLEMENTED

            GetClientRect(hWnd,&rect);

            dx = (rect.right + rect.left) >> 4;
            dy = (rect.bottom + rect.top) >> 4;

            pxd->pObject[0].x = dx << 1;
            pxd->pObject[0].y = 0;
            pxd->pObject[1].x = dx;
            pxd->pObject[1].y = dy;
            pxd->pObject[2].x = 0;
            pxd->pObject[2].y = dy;
            pxd->pObject[3].x = dx << 1;
            pxd->pObject[3].y = 0;
            pxd->nObject      = 4;

            pxd->xForm.eM11 = (float) 0.866;
            pxd->xForm.eM12 = (float) 0.5;
            pxd->xForm.eM21 = (float)-0.5;
            pxd->xForm.eM22 = (float) 0.866;
            pxd->xForm.eDx  = (float) 0;
            pxd->xForm.eDy  = (float) 0;

#endif

            UnlockWindowInfo(hWnd);

            SetTimer(hWnd,1,500,NULL);

            return(TRUE);
        }
        FreeWindowInfo(hWnd);
    }
    return(FALSE);
}

/*---------------------------------------------------------------------------*\
| XFORM COMMAND PROCEDURE
\*---------------------------------------------------------------------------*/
BOOL XFormCommandProc(HWND hWnd, WPARAM wParam, LONG lParam)
{
    hWnd   = hWnd;
    wParam = wParam;
    lParam = lParam;

    return(TRUE);
}

/*---------------------------------------------------------------------------*\
| XFORM PAINT PROCEDURE
\*---------------------------------------------------------------------------*/
VOID XFormPaintProc(HWND hWnd)
{
    HDC         hDC;
    PAINTSTRUCT ps;

    if(hDC = BeginPaint(hWnd,&ps))
        EndPaint(hWnd,&ps);

    ResetXForm(hWnd);

    return;
}

/*---------------------------------------------------------------------------*\
| XFORM DESTROY PROCEDURE
\*---------------------------------------------------------------------------*/
VOID XFormDestroyProc(HWND hWnd)
{
    KillTimer(hWnd,1);
    FreeWindowInfo(hWnd);
    return;
}

VOID ResetXForm(HWND hWnd)
{
    PXFORMDATA pxd;
    HDC        hDC;

#ifndef NOT_IMPLEMENTED

    RECT       rect;

#endif

    if(pxd = (PXFORMDATA)LockWindowInfo(hWnd))
    {
        if(hDC = GetDC(hWnd))
        {

#ifndef NOT_IMPLEMENTED

            GetClientRect(hWnd,&rect);
            BitBlt(hDC,0,0,rect.right,rect.bottom,(HDC)0,0,0,0);

            SetViewportOrgEx(hDC,rect.right >> 1,rect.bottom >> 1,NULL);
            SetWorldTransform(hDC,(LPXFORM)&pxd->xForm);
            Polyline(hDC,pxd->pObject,pxd->nObject);
            ModifyWorldTransform(hDC,(LPXFORM)&pxd->xForm,MWT_IDENTITY);

            // I have to put this in since GDI currently doesn't
            // reset the drawing attributes -- 5/24/91
            //
            SetViewportOrgEx(hDC,0,0,NULL);

#endif

            ReleaseDC(hWnd,hDC);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}

VOID DrawXForm(HWND hWnd)
{
    PXFORMDATA pxd;
    HDC        hDC;
#ifndef NOT_IMPLEMENTED
    XFORM      xForm;
    RECT       rect;
#endif

    if(pxd = (PXFORMDATA)LockWindowInfo(hWnd))
    {
        if(hDC = GetDC(hWnd))
        {

#ifndef NOT_IMPLEMENTED

            GetClientRect(hWnd,&rect);
            SetViewportOrgEx(hDC,rect.right >> 1, rect.bottom >> 1,NULL);

            SetWorldTransform(hDC,(LPXFORM)&pxd->xForm);
            Polyline(hDC,pxd->pObject,pxd->nObject);

            ModifyWorldTransform(hDC,(LPXFORM)&pxd->xForm,MWT_RIGHTMULTIPLY);
            Polyline(hDC,pxd->pObject,pxd->nObject);

            GetWorldTransform(hDC,(LPXFORM)&xForm);
            pxd->xForm.eM11 = xForm.eM11;
            pxd->xForm.eM12 = xForm.eM12;
            pxd->xForm.eM21 = xForm.eM21;
            pxd->xForm.eM22 = xForm.eM22;
            pxd->xForm.eDx  = xForm.eDx;
            pxd->xForm.eDy  = xForm.eDy;

            ModifyWorldTransform(hDC,(LPXFORM)&pxd->xForm,MWT_IDENTITY);

            // I have to put this in since GDI currently doesn't
            // reset the drawing attributes -- 5/24/91
            //
            SetViewportOrgEx(hDC,0,0,NULL);

#endif
            ReleaseDC(hWnd,hDC);
        }
        UnlockWindowInfo(hWnd);
    }
    return;
}


XFORM.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#ifdef WIN16
#define APIENTRY FAR PASCAL
typedef WORD WPARAM;
//#else
//typedef DWORD WPARAM;
#endif

#define XFORMCLASS "XFORMDEMO"

typedef struct _XFORMDATA
{
#ifndef NOT_IMPLEMENTED
    XFORM xForm;
#endif
    POINT pObject[4];
    int   nObject;
} XFORMDATA;
typedef XFORMDATA      *PXFORMDATA;
typedef XFORMDATA NEAR *NPXFORMDATA;
typedef XFORMDATA FAR  *LPXFORMDATA;

/*
** XFORM WINDOW ROUTINES (xform.c)
*/
HWND  FAR      CreateXFormWindow(HWND,int);
LONG  APIENTRY XFormProc(HWND,UINT,WPARAM,LONG);
BOOL           XFormCreateProc(HWND);
VOID           XFormDestroyProc(HWND);
BOOL           XFormCommandProc(HWND,WPARAM,LONG);
VOID           XFormPaintProc(HWND);


README.TXT

Demonstrating GDI and USER Functions in Fractals

SUMMARY
=======

The MANDEL sample demonstrates GDI and USER functions by working with
fractals. The Mandelbrot Dream provides the following features:

  - Drawing the Mandelbrot set and the corresponding Julia set.
  - Zooming into any of the sets.
  - MDI fractal drawing windows.
  - Floating point math and fixed point math.
  - Shifting color table entries.
  - Changing palette entries and animating palette (color cycling).
  - Loading and saving bitmaps created with special effects.
  - Changing bitmap colors with flood fill.
  - Boundary tracing and creating a clip region for special effects.
  - Enumerating printers.
  - Loading RLE (or converting .bmp files to RLE) for playing in viewer.
  - Saving the RLE in memory to disk.

Note: The sample makes use of 64-bit integers for its fixed point math.

MORE INFORMATION
================

Drawing the Mandelbrot Set and the Corresponding Julia Set

  - Choose the Mandelbrot Set menu item from the Create menu to create an
    MDI child window to draw the set in, if one has not already been
    created.

  - Choose either "use Fix Point math" or "use Floating Point math" from the
    Draw menu to decide if floating point or fixed point math is to be used.
    Fixed point is faster, however, you lose resolution sooner as you zoom
    in.

  - Choose the number of iterations from the Iteration menu item and choose
    Step from the Draw menu. The higher the number of iterations, the more
    detail in the picture, but the more time it takes to generate the
    picture. The step determines whether every scan line is drawn. The more
    scan lines, the better the picture, but the longer it takes to generate
    the picture.

  - Choose Draw Set from the Draw menu.

  - Use the right mouse button to select a point in the Mandelbrot set (the
    drawing surface of the Mandlebrot window). A new Julia MDI window is
    created. Choose Draw Set from the Draw menu to start drawing.

  - The point selected with the right mouse button determines the complex
    constant to use for the Julia Set.

Zooming Into Any of the Sets

  - Click, drag and release with the left mouse button to describe the zoom
    in the region. A new MDI child of the same type as the parent
    (Mandelbrot window or Julia window) is created.

  - Choose Draw Set from the Draw menu to start drawing.

MDI Fractal Drawing Windows

  - Choose either Mandelbrot Set or Julia Set from the Create menu to create
    a new MDI window for drawing.

  - Or, use the left mouse button to describe a zoom-in region in either a
    Mandelbrot or Julia MDI window to create a new MDI window for drawing.

  - Or, click on the Mandelbrot window with the right mouse button to create
    a Julia MDI window corresponding to the mouse click position in the
    Mandelbrot window.

Floating Point Math and Fixed Point Math

Choose the appropriate menu item ("Floating Point math" or "Fix Point math")
from the Draw menu. The Fix Point math uses 20.11 fixed point integer
arithmetic for calculations.

Shifting Color Table Entries

Choose Shift from the Color menu or hit F10 to shift the color table entry.
The picture of the active MDI window is updated.

Changing Palette Entries and Animating Palette (Color Cycling)

Choose Cycle from the Color menu or hit F11 to start color cycling the
picture. The menu item is grayed if the display device does not support
palette management.

Loading and Saving Bitmaps Created With Special Effects

Choose Load Bitmap from the Bitmap menu to load a bitmap into the active
child window. Or, choose Save Bitmap As to save the picture in the active
MDI child window.

Changing Bitmap Colors With Flood Fill

Choose Custom from the Color menu to select a color. The cursor is changed
to a paint can over the active child window. Click with the left mouse
button on the picture; the old color under the cursor is changed to the new
color.

Boundary Tracing and Creating a Clip Region For Special Effects

From any active Mandelbrot window, choose Set Mandelbrot Clip region from
the Region menu. The boundary of the escape region is traced. The region is
then selected as a clip region. Thus, if you load a bitmap for display, the
bitmap only shows through the clip region. The new picture can then be
saved. To remove the clip region, choose Remove Clip Region from the Region
menu.

Note:  The boundary tracing algorithm may trace out a small island of only
several pixels and stop. If that happens, change the size of the window or
create another zoom window and trace again.

Enumerating Printers

On start up, the Mandelbrot Dream enumerates the printers and inserts the
printers into the Print menu. Selecting the printer on the Print menu prints
the picture in the active MDI window.

Loading RLE (or convert .bmp files to RLE) for Playing in Viewer

  - Choose the Viewer item from the Create menu to create a viewer window or
    bring any existing viewer window to the top.

  - Select Load Bitmap(s) from the File menu for loading RLE or .bmp files
    into the memory from disk.

  - Select Play or Play Continuously from the Options menu for viewing.

  - For a demonstration, load the .\rsc\julia.rle file and select Play
    Continuously. The Julia.rle is a collection of the various julia sets
    along the boundary of escaping and non-escaping points of the Mandelbrot
    Set.

Saving the RLE in Memory to Disk

  - Choose the Viewer item from the Create menu to create a viewer window or
    bring any existing viewer window to the top.

  - Select Save Bitmap(s) from the File menu for saving the RLE(s) from
    memory to disk.


BNDSCAN.H

/******************************Module*Header*******************************\
* Module Name: bndscan.h
*
* Contains the #defines used for finding boundary points
*
* Created: 19-Mar-1992 16:10:14
* Author: Petrus Wong
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* The eight neighbors of any boundary point are represented by thier
* relative direction.  For example, north, south, east and west.
* The directions are #defined clockwise.  And these defined values will
* be used with shifting operations.
*
* Dependencies:
*
*   none
*
\**************************************************************************/
#define EAST        0x0001
#define SOUTHEAST   0x0002
#define SOUTH       0x0004
#define SOUTHWEST   0x0008
#define WEST        0x0010
#define NORTHWEST   0x0020
#define NORTH       0x0040
#define NORTHEAST   0x0080

//
// Turning left and right are also defined.
//
#define LEFT    1
#define RIGHT    2

//
// This is maximum number of boundary points we are going to handle.
// Works pretty well in most cases.  This limit exists for catching
// the unprobable situation of infinite loop.
//
#define MAXPOINT    50000

#define MM_SELCLIPRGN           7050

//
// Internal data structure used for boundary point finding only.
//
typedef struct _node {
    DWORD   dwDirection;
    BOOL    bEscape;
}NODE;

//
// externs
//
extern HPEN hpnGreen;
extern HPEN hpnBlack;
extern HWND ghwndMain;
extern char gtext[256];
extern HBITMAP SaveBitmap   (HWND, HPALETTE);
extern LONG lMul(LONG, LONG);
extern LONG lDiv(LONG, LONG);


DIBMP.C

/******************************Module*Header*******************************\
* Module Name: dibmp.c
*
* Contains functions that manipulate the DIB color
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* These functions will be called from the ChildWndProc() as it processes
* the corresponding WM_COMMAND message for changing the color of the bitmap.
*
* iCreatePenFrPal() is called by InitializeApp() for creating pens based on
* either system or custom palette.
*
* Dependencies:
*       none
*
\**************************************************************************/
#include <windows.h>
#include <stdio.h>
#include "julia.h"
#include "dibmp.h"

#define CYCLETHRD

//#define DEBUG

#ifndef DEBUG
   #undef OutputDebugString
   #define OutputDebugString(LPCSTR)
#endif

extern HPALETTE        ghPal, ghPalOld;
extern HWND ghwndMain;
extern HWND ghwndClient;
extern HANDLE hQuitEvent;
extern char   gtext[256];
extern HANDLE ghAccel;
BOOL bValidMode(INT);
BOOL bColor2BW(PBITMAPINFO);
BOOL bChangeDIBColor(HDC, PINFO, INT);
BOOL bColorShift(PBITMAPINFO);
BOOL bColorReplace(PBITMAPINFO);
BOOL bColorCycle(HDC, RECT, PBYTE, PBITMAPINFO, PINFO);
BOOL bCycle(HWND);
BOOL bColor2Mono(HDC, PINFO, PBITMAPINFO);
INT  iCreatePenFrPal(HDC, PVOID *, INT, HPALETTE *);
HPALETTE CopyPalette(HPALETTE);
extern PINFO pGetInfoData(HWND);
extern BOOL bReleaseInfoData(HWND);

/******************************Public*Routine******************************\
*
* bChangeDIBColor
*
* Effects: Main entry point for the color changing operations.  It
*          1. validates the operation
*          2. retrieves the bitmap info from the graphics engine
*          3. dispatches the call to the right function
*          4. saves the resulting bitmap
*          5. updates the screen
\**************************************************************************/
BOOL bChangeDIBColor(HDC hDC, PINFO pInfo, INT iMode)
{
    BOOL                 bSuccess;
    HBITMAP              hTmpBmp, hBmpOld;
    PBITMAPINFO          pbmi;
    PBYTE                pBits;
    BITMAPINFO           bmi;
    PBYTE                pjTmp, pjTmpBmi;
    ULONG                sizBMI;
    RECT                 rc;
    BOOL                 bCycle;

    bSuccess = TRUE;
    if (!pInfo->hBmpSaved)
{
        MessageBox(ghwndMain, GetStringRes(IDS_NO_BITMAP), NULL, MB_OK);
        return FALSE;
    }

    if (!bValidMode(iMode))
{
        OutputDebugString ("bChangeDIBColor: invalid mode!");
        return FALSE;
    }

    if (pInfo->hPal) {
        HPALETTE hPalOld;
        int     ii;

        hPalOld = SelectPalette(hDC, pInfo->hPal, FALSE);
        ii = RealizePalette(hDC);

        if (ii){
            UpdateColors (hDC);
        }

    }

    //
    // Let the graphics engine to retrieve the dimension of the bitmap for us
    // GetDIBits uses the size to determine if it's BITMAPCOREINFO or BITMAPINFO
    // if BitCount != 0, color table will be retrieved
    //
    bmi.bmiHeader.biSize = 0x28;              // GDI need this to work
    bmi.bmiHeader.biBitCount = 0;             // don't get the color table
    if ((GetDIBits(hDC, pInfo->hBmpSaved, 0, 0, (LPSTR)NULL, &bmi, DIB_RGB_COLORS)) == 0)
{
        OutputDebugString ("GetDIBits failed!");
        return FALSE;
    }

    //
    // Now that we know the size of the image, alloc enough memory to retrieve
    // the actual bits
    //
    if ((pBits = (PBYTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                bmi.bmiHeader.biSizeImage)) == NULL)
{
        return FALSE;
    }

    //
    // Note: 24 bits per pixel has no color table.  So, we don't have to
    // allocate memory for retrieving that.  Otherwise, we do.
    //
    pbmi = &bmi;                                      // assume no color table
    if (bmi.bmiHeader.biBitCount != 24) {             // has color table
        sizBMI = sizeof(BITMAPINFO)+sizeof(RGBQUAD)*(1<<bmi.bmiHeader.biBitCount);
        //
        // I need more memory for the color table
        //
        if ((pbmi = (PBITMAPINFO)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizBMI )) == NULL)
{
            bSuccess = FALSE;
            goto ErrExit1;
        }

        //
        // Now that we've a bigger chunk of memory, let's copy the Bitmap
        // info header data over
        //
        pjTmp = (PBYTE)pbmi;
        pjTmpBmi = (PBYTE)&bmi;
        sizBMI = sizeof(BITMAPINFOHEADER);

        while(sizBMI--)
        {
            *(((PBYTE)pjTmp)++) = *((pjTmpBmi)++);
        }

    }

    //
    // Bitmap can't be selected into a DC when calling GetDIBits
    // Assume that the hDC is the DC where the bitmap would have been selected
    // if indeed it has been selected
    //
    if (hTmpBmp = CreateCompatibleBitmap(hDC, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight)) {
        hBmpOld = SelectObject(hDC, hTmpBmp);
        if ((GetDIBits(hDC, pInfo->hBmpSaved, 0, pbmi->bmiHeader.biHeight, (LPSTR)pBits, pbmi, DIB_RGB_COLORS))==0)
{
            bSuccess = FALSE;
            goto ErrExit4;
        }
    } else
{
        OutputDebugString ("Failed in creating bitmap!");
        bSuccess = FALSE;
        goto ErrExit3;
    }

    bCycle = FALSE;
    switch (iMode) {
        case MM_BW:
            if ((!bColor2Mono(hDC, pInfo, pbmi)) ||
                (pInfo->hBmpMono == NULL))
{
                OutputDebugString ("Failed in Color2Mono!");
                bSuccess = FALSE;
                goto ErrExit4;
            }
            pInfo->bUseMono = TRUE;
            break;
        case MM_SHIFT:

            if (!bColorShift(pbmi))
{
                OutputDebugString ("Failed in ColorShift!");
                bSuccess = FALSE;
                goto ErrExit4;
            }
            break;
        case MM_CUSTOM:
            bColorReplace(pbmi);
            break;

        case MM_CYCLE:
            bCycle = TRUE;
            break;

        default:
            bSuccess = FALSE;
            break;
    }

    if (SetDIBits(hDC, pInfo->hBmpSaved, 0, pbmi->bmiHeader.biHeight, (LPSTR)pBits,
            pbmi, DIB_RGB_COLORS) == 0)
{
        bSuccess = FALSE;
        goto ErrExit4;
    }

    if (bCycle)
        pInfo->bSetDIBsToDevice = TRUE;

#if 0
    if (pInfo->bSetDIBsToDevice && !pInfo->bUseMono) {
        //GetWindowRect(pInfo->hwnd, &rc);
        GetClientRect(pInfo->hwnd, &rc);
        //SetMapMode(hDC, MM_TEXT);

        //if (SetDIBitsToDevice(hDC, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
        //     0, 0, 0, pbmi->bmiHeader.biHeight, (LPSTR)pBits, pbmi, DIB_RGB_COLORS) == 0) {
        //    MessageBox(ghwndMain, "Failed in SetDIBitsToDevice!", "Error", MB_OK);
//    MessageBox(ghwndMain, "SetDIBitsToDevice    G   [              !", " G   [", MB_OK);
        //    bSuccess = FALSE;
        //    goto ErrExit4;
        //}
    } else
{
#endif
        InvalidateRect(pInfo->hwnd, NULL, FALSE);
//  }

    if (bCycle) {
        //
        // This is commented out because I don't want the system colors
        // be affected by color cycling
        //
        //SetSystemPaletteUse(hDC, SYSPAL_NOSTATIC);
        bColorCycle(hDC, rc, pBits, pbmi, pInfo);
        //SetSystemPaletteUse(hDC, SYSPAL_STATIC);
    }

ErrExit4:
    SelectObject(hDC, hBmpOld);
    DeleteObject(hTmpBmp);
ErrExit3:
    GlobalFree(pbmi);
ErrExit1:
    GlobalFree(pBits);
    return bSuccess;
}

/******************************Public*Routine******************************\
*
* bValidMode
*
* Effects: validates the change mode for ChangeDIBColor function
*          returns TRUE if mode is valid.  Otherwise, FALSE
\**************************************************************************/
BOOL bValidMode(INT iMode)
{
    if ((iMode == MM_BW)     || (iMode == MM_SHIFT) ||
        (iMode == MM_CUSTOM) || (iMode == MM_CYCLE))
        return TRUE;
    else
        return FALSE;
}

/******************************Public*Routine******************************\
*
* bColor2BW
*
* Effects: changes the color table in the bitmapinfo to black and white
*
* Warnings: doesn't check if pbmi is valid or not
\**************************************************************************/
BOOL bColor2BW(PBITMAPINFO pbmi)
{
    INT iEntry;

    //
    // CR! What's the right behavior?  Maybe I should convert this to a
    // monochrome bitmap.  This will be very useful in MaskBlt().
    //
    iEntry = 1<<pbmi->bmiHeader.biBitCount;
    while (--iEntry >= 0) {
        if (iEntry <= iEntry / 2) {
                pbmi->bmiColors[iEntry].rgbBlue     = 0x00;
                pbmi->bmiColors[iEntry].rgbGreen    = 0x00;
                pbmi->bmiColors[iEntry].rgbRed      = 0x00;
                pbmi->bmiColors[iEntry].rgbReserved = 0x00;
         } else {
                pbmi->bmiColors[iEntry].rgbBlue     = 0xFF;
                pbmi->bmiColors[iEntry].rgbGreen    = 0xFF;
                pbmi->bmiColors[iEntry].rgbRed      = 0xFF;
                pbmi->bmiColors[iEntry].rgbReserved = 0x00;
        }
    }
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bColorShift
*
* Effects: Shifting entries of the color table
*
\**************************************************************************/

BOOL bColorShift(PBITMAPINFO pbmi)
{
    INT iEntry;
    RGBQUAD     Tmp;

    iEntry = 1<<pbmi->bmiHeader.biBitCount;

    Tmp.rgbBlue     = pbmi->bmiColors[iEntry-1].rgbBlue;
    Tmp.rgbGreen    = pbmi->bmiColors[iEntry-1].rgbGreen;
    Tmp.rgbRed      = pbmi->bmiColors[iEntry-1].rgbRed;
    Tmp.rgbReserved = pbmi->bmiColors[iEntry-1].rgbReserved;

    while (--iEntry > 0) {
         pbmi->bmiColors[iEntry].rgbBlue     =
            pbmi->bmiColors[iEntry-1].rgbBlue;
         pbmi->bmiColors[iEntry].rgbGreen    =
            pbmi->bmiColors[iEntry-1].rgbGreen;
         pbmi->bmiColors[iEntry].rgbRed      =
            pbmi->bmiColors[iEntry-1].rgbRed;
         pbmi->bmiColors[iEntry].rgbReserved =
            pbmi->bmiColors[iEntry-1].rgbReserved;
    }
    pbmi->bmiColors[0].rgbBlue     = Tmp.rgbBlue;
    pbmi->bmiColors[0].rgbGreen    = Tmp.rgbGreen;
    pbmi->bmiColors[0].rgbRed      = Tmp.rgbRed;
    pbmi->bmiColors[0].rgbReserved = Tmp.rgbReserved;
    return TRUE;

}

BOOL bColorReplace(PBITMAPINFO pbmi)
{
    return FALSE;
    UNREFERENCED_PARAMETER(pbmi);
}

/******************************Public*Routine******************************\
*
* bColorCycle
*
* Effects:  Create a logical palette with a good spread of color
*           Animate the palette, shift the palette entries and animate
*           again for 256 times altogether.
*           This creates the color cycling effect.
*
* Warnings: Only works in device that support palette.
\**************************************************************************/

BOOL bColorCycle(HDC hDC, RECT rc, PBYTE pBits, PBITMAPINFO pbmi, PINFO pInfo)
{
    INT             iEntry, i, iNumClr;
    PLOGPALETTE     plogPat;
    ULONG           ulSize;
    BOOL            bSuccess;
    HPALETTE        hPal, hPalOld;
    PALETTEENTRY    peTemp;

#ifdef CYCLETHRD
    DWORD           dwWait;
#else
    MSG             msg;
#endif
    BOOL            bQuit;

//UINT uRC;

    bSuccess = TRUE;
    iEntry = 1<<pbmi->bmiHeader.biBitCount;

    iNumClr = 236;          // 256 minus 20 reserved colors
    ulSize = sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*256;

    if ((plogPat = (PLOGPALETTE) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, ulSize)) == NULL)
{
        bSuccess = FALSE;
        goto ErrExit1;
    }

    plogPat->palVersion = 0x300;
    plogPat->palNumEntries = (WORD) 256;

    {
    int     iFirst, iSecond, iInc;

        iFirst = iNumClr/3;
        iSecond = iFirst * 2;
        iInc = (iNumClr <= 256) ? 3 : 1;

        for (i = 0; i < iNumClr; i++) {
            if (i < iFirst) {
                plogPat->palPalEntry[i+10].peBlue   = 255-(i+1)*iInc;
                plogPat->palPalEntry[i+10].peGreen  = 0;
                plogPat->palPalEntry[i+10].peRed    = (i+1)*iInc;
                plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
                continue;
            }
            if (i < iSecond) {
                plogPat->palPalEntry[i+10].peBlue   = 0;
                plogPat->palPalEntry[i+10].peGreen  = (i+1-iFirst)*iInc;
                plogPat->palPalEntry[i+10].peRed    = 255-(i+1-iFirst)*iInc;
                plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
                continue;
            }
            plogPat->palPalEntry[i+10].peBlue   = (i+1-iSecond)*iInc;
            plogPat->palPalEntry[i+10].peGreen  = 255-(i+1-iSecond)*iInc;
            plogPat->palPalEntry[i+10].peRed    = 0;
            plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
        }
    }

    if ((hPal = CreatePalette(plogPat)) == (HPALETTE) NULL)
{
        bSuccess = FALSE;
        goto ErrExit2;
    }

    pInfo->hCyclePal = hPal;

    hPalOld = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

#ifndef CYCLETHRD

    bQuit = FALSE;
    while (TRUE && !bQuit) {

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            //sprintf( gtext,"msg = %lx\n", msg.message);
            //OutputDebugString( gtext );

            if ((msg.message == WM_QUIT) || (msg.message == WM_CLOSE) ||
                ((msg.message == WM_SYSCOMMAND) && (msg.wParam == SC_CLOSE))) {
                sprintf( gtext,"msg = %lx, wPram = %lx\n", msg.message, msg.wParam);
                OutputDebugString( gtext );
                bQuit = TRUE;
                PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam);
            } else {
                if (!TranslateAccelerator( ghwndMain, ghAccel, &msg) &&
                    !TranslateMDISysAccel(  ghwndClient, &msg)          ) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
           }
        }

#else
    bQuit = FALSE;
    while (TRUE) {
        //
        // If parent gets a WM_CLOSE, we will return
        //
        dwWait = WaitForSingleObject(pInfo->hQuitEvent, 0);
        if (dwWait == WAIT_TIMEOUT)
{
            MessageBox(ghwndMain,
       GetStringRes(IDS_CYCLE_THREAD_QUIT),
   GetStringRes (IDS_CYCLE_THREAD), MB_OK);
            break;
        }
#endif
        if (!pInfo->bClrCycle) {
            sprintf( gtext,"NOT to animate palette\n");
            OutputDebugString( gtext );
            bQuit = TRUE;
        }

        if (!bQuit) {
            sprintf( gtext,"About to animate palette\n");
            OutputDebugString( gtext );

            peTemp = plogPat->palPalEntry[10];
            for (i = 0; i < iNumClr; i++) {
                plogPat->palPalEntry[i+10] = plogPat->palPalEntry[i+10+1];
            }
            plogPat->palPalEntry[i-1+10] = peTemp;

            if (!AnimatePalette(hPal, 0, 256, plogPat->palPalEntry)) {
                sprintf( gtext,"Anmiate palette failed\n");
                OutputDebugString( gtext );
            }
            Sleep(50);

//
// This is not needed anymore starting from build 475 14-Jun-1993
//
#if 0
            //
            // Not realizing stops color-cycling after moving back into
            // foreground
            //
            if ((uRC = RealizePalette(hDC)) == GDI_ERROR) {
                sprintf( gtext,"Realize palette failed\n");
                OutputDebugString( gtext );
            }

            if (!UpdateColors(hDC)) {
                sprintf( gtext,"Update Colors failed\n");
                OutputDebugString( gtext );
            }
#endif
        }

    }

    SelectPalette(hDC, hPalOld, 0);
    DeleteObject(hPal);
ErrExit2:
    GlobalFree(plogPat);
ErrExit1:
    return bSuccess;

}

/******************************Public*Routine******************************\
*
* bCycle
*
* Effects: Wrapper for doing color cycling in a separate thread
*          Called from MM_CYCLE
*
* Warnings: presents problem when the MDI child is closed if this thread
*           is not done yet.  We will have to wait until DeleteDC is
*           fully functional.
\**************************************************************************/
BOOL bCycle(HWND hwnd)
{
    HDC     hDC;
    PINFO   pInfo;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
        return 0L;
    }

    hDC = GetDC(pInfo->hwnd);
    bChangeDIBColor(hDC, pInfo, MM_CYCLE);
    ReleaseDC(pInfo->hwnd, hDC);

    bReleaseInfoData(hwnd);

    ExitThread(0);
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bColor2Mono
*
* Effects: Create a monochrome bitmap out of the colored one
*          Using a different a source background clr every time it is called
*          Saving the Monochrome bitmap in pInfo
*
* Warnings: It's more straight forward to use GetDIBits than this but
*           that means some big changes to the bChangeDIBColor.
*
\**************************************************************************/

BOOL bColor2Mono(HDC hDC, PINFO pInfo, PBITMAPINFO pbmi) {
    HDC     hDCMemDest, hDCMemSrc;
    HBITMAP hOldBmpDest, hOldBmpSrc;
    INT     iWidth, iHeight, iEntry;
    static INT iCount=0;

    iWidth = pbmi->bmiHeader.biWidth;
    iHeight = pbmi->bmiHeader.biHeight;

    if (pInfo->hBmpMono)
        DeleteObject(pInfo->hBmpMono);

    pInfo->hBmpMono = CreateBitmap(iWidth, iHeight, pbmi->bmiHeader.biPlanes, 1, NULL);
    if (pInfo->hBmpMono == NULL) {
        return(FALSE);
    }

    hDCMemDest = CreateCompatibleDC(hDC);
    hDCMemSrc  = CreateCompatibleDC(hDC);

    if (pInfo->hPal) {
        SelectPalette(hDCMemSrc, pInfo->hPal, FALSE);
        RealizePalette(hDCMemSrc);
    }

    iEntry = 1<<pbmi->bmiHeader.biBitCount;
    SetBkColor(hDCMemSrc, RGB( pbmi->bmiColors[iCount].rgbRed,
                               pbmi->bmiColors[iCount].rgbGreen,
                               pbmi->bmiColors[iCount].rgbBlue) );
    //SetBkColor(hDCMemDest, RGB( 0, 0, 0));
    //SetTextColor(hDCMemDest, RGB( 255, 255, 255));

    iCount++;
    if (iCount >= iEntry)
        iCount = 0;

    hOldBmpDest = SelectObject(hDCMemDest, pInfo->hBmpMono);
    hOldBmpSrc  = SelectObject(hDCMemSrc,  pInfo->hBmpSaved);
    BitBlt(hDCMemDest, 0, 0, iWidth, iHeight, hDCMemSrc, 0, 0, SRCCOPY);

    pInfo->hBmpMono = SelectObject(hDCMemDest, hOldBmpDest);
    SelectObject(hDCMemSrc, hOldBmpSrc);

    DeleteDC(hDCMemDest);
    DeleteDC(hDCMemSrc);

    return(TRUE);

}

/******************************Public*Routine******************************\
*
* iCreatePenFrPal
*
* Effects: Create an array of pens from palette.
*          If device supports palette, then first creates a logical palette
*          with a good spread of color. Then select the logical palette into
*          the DC. Create pen that corresponds to each palette entry.
*          If system does not support palette, then use the system palette.
*
*          prghPen pointer to an array of hPen
*                  If this is NULL, the required size of the array is
*                  returned. If this is not NULL, the array will be filled
*                  with hPens.
*
*          returns the number of hPens created.
*
\**************************************************************************/
INT iCreatePenFrPal(HDC hDC, PVOID *prghPen, INT iWidth, HPALETTE *phPal)
{
    INT             iNumClr, iResult, i, iEntry;
    PLOGPALETTE     plogPat;
    ULONG           ulSize;

    iResult = 0;

    if (!((GetDeviceCaps(hDC, RASTERCAPS)) & RC_PALETTE)) {

        if (iNumClr = GetSystemPaletteEntries(hDC, 0, 0, NULL)) {
            if (prghPen == NULL) {
                return (iNumClr);
            }

            if (iWidth < 0)
{
                MessageBox(GetFocus(), GetStringRes (IDS_IVALID_PEN_WIDTH),
   NULL, MB_OK);
                return 0L;
            }

            for (i = 0; i < iNumClr; i++) {
                prghPen[i] =
                     (PVOID) CreatePen(PS_SOLID, iWidth, PALETTEINDEX(i));
                iResult = i;
#if 0
                SelectObject(hDC, (HPEN)prghPen[i]);
                MoveToEx(hDC, i*iWidth, 0, NULL);
                LineTo(hDC, i*iWidth, 300);
#endif
            }
            return iResult;
        }

        // GetPaletteEntries returns 0
        iNumClr = 1 << GetDeviceCaps(hDC, BITSPIXEL);

        if (iNumClr <= 16) {
            sprintf( gtext,"Assertion failed: iCreatePenFrPal() \n");
            OutputDebugString( gtext);
            sprintf( gtext,"iNumClr <= 16! iNumClr = %d\n", iNumClr);
            OutputDebugString( gtext);
            return (iNumClr);
        }

        // Assuming at least 16 bit device
        //
        iNumClr = 768;      // COLORREF format: 0x00bbggrr r,g,b=[0,255]

        if (prghPen == NULL) {
            return (iNumClr);
        }

        if (iWidth < 0)
{
            MessageBox(GetFocus(), GetStringRes(IDS_IVALID_PEN_WIDTH),
   NULL, MB_OK);
            return 0L;
        }

        for (i = 0; i < iNumClr; i++) {
            if (i < 256) {
                prghPen[i] =
                    (PVOID) CreatePen(PS_SOLID, iWidth, RGB(i,0,255-i));
                iResult = i;
#if 0
                SelectObject(hDC, (HPEN)prghPen[i]);
                MoveToEx(hDC, i*iWidth, 0, NULL);
                LineTo(hDC, i*iWidth, 300);
#endif
                continue;
            }

            if (i < 512) {
                prghPen[i] =
                    (PVOID) CreatePen(PS_SOLID, iWidth, RGB(511-i,i-256,0));
                iResult = i;
#if 0
                SelectObject(hDC, (HPEN)prghPen[i]);
                MoveToEx(hDC, i*iWidth, 0, NULL);
                LineTo(hDC, i*iWidth, 300);
#endif
                continue;
            }

            prghPen[i] =
                (PVOID) CreatePen(PS_SOLID, iWidth, RGB(0,767-i,i-512));
            iResult = i;
#if 0
            SelectObject(hDC, (HPEN)prghPen[i]);
            MoveToEx(hDC, i*iWidth, 0, NULL);
            LineTo(hDC, i*iWidth, 300);
#endif
        }

        return iResult;
    }

    //
    // Palette managed device
    //
    iEntry = iNumClr = 1 << GetDeviceCaps(hDC, BITSPIXEL);

    if (iNumClr > 256) {
        sprintf( gtext,"Assertion failed: iCreatePenFrPal() \n");
        OutputDebugString( gtext);
        sprintf( gtext,"iNumClr > 256! iNumClr = %d\n", iNumClr);
        OutputDebugString( gtext);
        iEntry=iNumClr = 256;
    }

    ulSize = sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*iNumClr;

    iNumClr -= GetDeviceCaps(hDC, NUMRESERVED);

    if (prghPen == NULL)
        return iNumClr;

    if ((plogPat = (PLOGPALETTE) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, ulSize)) == NULL)
{
        goto ErrExit1;
    }

    plogPat->palVersion = 0x300;
    plogPat->palNumEntries = (WORD) iEntry;

    {
    int     iFirst, iSecond, iInc;

        iFirst = iNumClr/3;
        iSecond = iFirst * 2;
        iInc = (iNumClr <= 256) ? 3 : 1;

        for (i = 0; i < iNumClr; i++) {
            if (i < iFirst) {
                plogPat->palPalEntry[i+10].peBlue   = 255-(i+1)*iInc;
                plogPat->palPalEntry[i+10].peGreen  = 0;
                plogPat->palPalEntry[i+10].peRed    = (i+1)*iInc;
                plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
                continue;
            }
            if (i < iSecond) {
                plogPat->palPalEntry[i+10].peBlue   = 0;
                plogPat->palPalEntry[i+10].peGreen  = (i+1-iFirst)*iInc;
                plogPat->palPalEntry[i+10].peRed    = 255-(i+1-iFirst)*iInc;
                plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
                continue;
            }
            plogPat->palPalEntry[i+10].peBlue   = (i+1-iSecond)*iInc;
            plogPat->palPalEntry[i+10].peGreen  = 255-(i+1-iSecond)*iInc;
            plogPat->palPalEntry[i+10].peRed    = 0;
            plogPat->palPalEntry[i+10].peFlags  = PC_RESERVED;
        }
    }

    DeleteObject(*phPal);

    if ((*phPal = CreatePalette(plogPat)) == (HPALETTE) NULL)
{
        goto ErrExit2;
    }
    if (*phPal) {
       SelectPalette(hDC, *phPal, FALSE);
       RealizePalette(hDC);
    }

    for (i = 0; i < iNumClr; i++) {
        HPEN    hPen;

        hPen = CreatePen(PS_SOLID, iWidth, PALETTEINDEX(i+10));
        prghPen[i] = (PVOID) hPen;
        iResult = i;
#if 0
        SelectObject(hDC, hPen);
        MoveToEx(hDC, i*iWidth, 0, NULL);
        LineTo(hDC, i*iWidth, 300);
#endif
    }

ErrExit2:
    GlobalFree(plogPat);
ErrExit1:
    return iResult;

}

/******************************Public*Routine******************************\
*
* HPALETTE CopyPalette
*
\**************************************************************************/

HPALETTE CopyPalette(HPALETTE hPalSrc)
{
    PLOGPALETTE     plogPal;
    PBYTE           pjTmp;
    int             iNumEntries=0;
    HPALETTE        hPal;

    if ((iNumEntries = GetPaletteEntries(hPalSrc, 0, iNumEntries, NULL)) == 0)
{
        OutputDebugString ("No entry in palette to copy!");
        return (HPALETTE) NULL;
    }

    if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
            sizeof(DWORD) + sizeof(PALETTEENTRY)*iNumEntries )) == NULL)
{
        return (HPALETTE) NULL;
    }

    plogPal->palVersion = 0x300;
    plogPal->palNumEntries = (WORD) iNumEntries;
    pjTmp = (PBYTE) plogPal;
    pjTmp += 8;
    GetPaletteEntries(hPalSrc, 0, iNumEntries, (PPALETTEENTRY)pjTmp);
    hPal = CreatePalette(plogPal);

    GlobalFree(plogPal);

    return hPal;
}


DIBMP.H

/******************************Module*Header*******************************\
* Module Name: dibmp.h
*
* Header files for dibmp.c
*
* Created: 14-Apr-1992 10:29:04
* Author: Petrus Wong
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* Contains #define for the menu IDs
*
* Dependencies:
*
*   none
*
\**************************************************************************/
#define MM_BW          7004
#define MM_DRAW_SET7005
#define MM_SHIFT        7006
#define MM_CUSTOM7007
#define MM_CYCLE7008


JTYPES.H

/******************************Module*Header*******************************\
* Module Name: jtypes.h
*
* Contains the INFO structure and handy macros
*
* Created: 10-Jan-1992 13:21:01
* Author: Petrus Wong
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* The INFO structure is allocated dynmamically when the MDI child is
* created and freed when the window is about to be destroyed.  The pointer
* to this data structure is stored in the LONG user data window structure.
* This data structure stores the per-window's info.
*
* Dependencies:
*
*   #include windows.h
*
\**************************************************************************/
//
// Handy macros for the transformations used in fractal generations
//
#define Xform(Pt, SrcFrom, SrcTo, DestFrom, DestTo) \
         (((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom))+DestFrom)
#define Xform2(Pt, SrcFrom, SrcTo, DestFrom, DestTo) \
         ((int) ((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom)+DestFrom))
#define XformFix(Pt, SrcFrom, SrcTo, DestFrom, DestTo) \
         (lMul(lDiv(Pt-SrcFrom,SrcTo-SrcFrom), DestTo-DestFrom)+DestFrom)

#define SIZEOFCAPTIONTEXT20
#define MAX_FRAME           800
#define MAX_FILE            800

typedef struct _FileInfo{
    HANDLE      hFile;
    HANDLE      hMapFile;
    LPVOID      lpvMapView;
} FILEINFO, *PFILEINFO;

typedef struct _RLEDATA{
    ULONG           ulFiles;
    ULONG           ulFrames;
    HPALETTE        hPal;
    LPBITMAPINFO    pbmi;
    FILEINFO        rgFileInfo[MAX_FILE];
    PBYTE           rgpjFrame[MAX_FRAME];
    ULONG           ulSize[MAX_FRAME];
    PBITMAPINFO     rgpbmi[MAX_FRAME];
} RLEDATA, *PRLEDATA;

typedef struct _PerWndInfo {
    char    CaptionBarText[SIZEOFCAPTIONTEXT];
    HWND    hwnd;               // hJulia or hView, the drawing surface
    HWND    hParent;            // ghwndClient, the client area of main frame
    HWND    hTextWnd;           // hTextWnd, the status window
    RECT    rcClient;           // hJulia & hTextWnd's parent rc
    HDC     hdcClient;          // DC handle of client area of main frame
    HRGN    hRgnPath;           // Region handle for the drawing surface
    HANDLE  hThrd;              // handle to drawing thread or play thread
    HANDLE  hThrd0;
    BOOL    bDrawing;           // curently drawing?
    DWORD   dwThreadId;         // drawing thread or play thread ID
    DWORD   dwElapsed;          // elapsed time for the drawing operation
    double  xFrom;              // drawing range in floating points
    double  xTo;                //
    double  yFrom;              //         Floating Points
    double  yTo;                //
    double  c1;                 // C = c1 + i c2 where c1, c2 are reals
    double  c2;                 // the C value corresponding to the Julia set
    LONG    lxFrom;             // drawing range in fix points
    LONG    lxTo;               //
    LONG    lyFrom;             //         Fix Points
    LONG    lyTo;               //
    LONG    lc1;                // C = c1 + i c2 where c1, c2 are reals
    LONG    lc2;                // the C value corresponding to the Julia set
    HBITMAP hBmpSaved;          // saved bitmap for the drawing
    BOOL    bSizeChng;          //        not used
    BOOL    bMandel;            // Drawing is a Mandelbrot or Julia set?
    int     iIteration;         // number of computations done on each pixel
    int     iStep;              // drawn on every one, two or three line(s)?
    BOOL    bStretch;           // stretching bitmaps
    int     iStretchMode;       // stretching mode to use
    BOOL    bSetDIBsToDevice;   // SetDIBsToDevice reather than BitBlt?
    BOOL    bFill;              // FloodFill mode presently?
    HBRUSH  hBrush;             // handle to brush
    HANDLE  hQuitEvent;         // Event for quiting color cycling thread
    HANDLE  hCycleThrd;         // Color cycling thread
    DWORD   dwCycleThrdID;      // Color cycling thread ID
    BOOL    bClrCycle;          // For suspending and resuming color cycle thrd
    BOOL    bFirstTime;         // Creating color cycling thrd only once
    DWORD   dwSuspend;          // Store return result of suspend/resume cycle thrd
    HBITMAP hBmpMono;           // Monochrome bitmap
    BOOL    bUseMono;           // Display monochrome bitmap instead
    HANDLE  hPrtThrd;           // Print thread
    DWORD   dwPrtThrdID;        // Print thread ID
    HPALETTE    hPal;           // The palette used for the drawing
    HPALETTE    hHTPal;         // The halftone palette used for the drawing
    HPALETTE    hCyclePal;
    RLEDATA RleData;            // RLE data for RLE viewer
    BOOL    bPlayRleCont;       // Playing RLE continuously
    PVOID   *prghPen;           // Pointer to array of hPen
    INT     iPen;               // number of Pens
    INT     iPriority;          // thread priority
    BOOL    bUseDIB;            // Should we use the DIB for stretch?
    BOOL    bCoreHdr;           // used in bDrawDIB
} INFO, *PINFO;

typedef struct _PrtData{
    INFO        info;
    int         index;
    BOOL        bUseDefault;
    DEVMODE     DevMode;
} PRTDATA, *PPRTDATA;

typedef struct _FileHdr{
    DWORD       bfSize;
    WORD        bfReserved1;
    WORD        bfReserved2;
    DWORD       bfOffbits;
} FILEHDR;


JULIA.C

/******************************Module*Header*******************************\
* Module Name: julia.c
*
* Main module for the Mandelbrot Dream
*       contains almost everything; windows procedure + misc stuff
*
* Created: 24-Oct-1991 18:34:08
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* The Mandelbrot Dream serves to demonstrate the GDI and USER
* functionalities in the setting of fractals.
*
* The Mandelbrot Dream provides the following functions:
*       1.  Drawing the Mandelbrot set and the corresponding julia set
*       2.  Zooming into any of the set
*       3.  MDI fractal drawing windows
*       4.  Floating Point Math/Fix Point Math
*       5.  Shifting color table entries
*       6.  Changing palette entries and animating palatte aka color cycling
*       7.  Loading/Saving bitmap created with special effect
*       8.  Changing bitmap color with flood fill
*       9.  Boundary tracing and creating a clip region out of it for
*           creating special effect
*      10.  Enumerate printers for printing
*      11.  Load RLE (or convert .bmp files to RLE) for playing in viewer
*      12.  Save the RLE in memory to disk.
*
* Note: Users can now draw and saves the julia sets on disk as bmps in
*       the Julia windows.  These bitmaps can then be read into the memory
*       (and converted to RLE format) one by one for displaying in sequence
*       in the viewer window.  Eg Load the julia.rle in the viewer window
*       and select the play or play continuously menu item.     02-Jan-1993
*
* Note2: The fix point math in this sample makes use of the LargeInteger
*        64 bit math library.
*
* Dependencies:
*
*       none
*
\**************************************************************************/
#include <windows.h>
#include <stdlib.h>
#include <commdlg.h>
#include <stdarg.h>
#include <math.h>
#include <stdio.h>
#include <shellapi.h>
#include "julia.h"

//
// For T1 to create all pens in advance.  This is not a good approach
// because pens are per thread basis.  The drawing threads won't be able
// to use them if they are not created in their threads.    18-Sep-1992
//
//#define THRDONE

#define CYCLETHRD

#define PRTTHRD
#define NEWPRTAPI

#ifndef DEBUG
   #undef OutputDebugString
   #define OutputDebugString(LPCSTR)
#endif

//
// Forward declarations.
//
BOOL InitializeApp            (INT*);
LONG APIENTRY MainWndProc     (HWND, UINT, DWORD, LONG);
LONG APIENTRY ChildWndProc    (HWND, UINT, DWORD, LONG);
BOOL CALLBACK About           (HWND, UINT, DWORD, LONG);
LONG APIENTRY TextWndProc     (HWND, UINT, DWORD, LONG);
LONG APIENTRY JuliaWndProc    (HWND, UINT, DWORD, LONG);
LONG APIENTRY ViewerWndProc   (HWND, UINT, DWORD, LONG);
LONG APIENTRY ViewSurfWndProc (HWND, UINT, DWORD, LONG);
BOOL APIENTRY SuspendDrawThrd (HWND, LONG);
BOOL APIENTRY ResumeDrawThrd  (HWND, LONG);
BOOL StartDraw       (PINFO);
BOOL StartDrawFix    (PINFO);
BOOL StartDraw2      (PINFO);
BOOL StartMandelbrot (PINFO);
BOOL StartMandelbrotFix (PINFO);
HBITMAP SaveBitmap   (HWND, HPALETTE);
void DrawBitmap      (HDC, PINFO, int, int, int, int);
BOOL bDrawDIB        (HDC, PINFO, int, int, int, int);
LONG lMul(LONG, LONG);
LONG lDiv(LONG, LONG);
PINFO pGetInfoData(HWND);
BOOL bReleaseInfoData(HWND);
BOOL bCheckMutexMenuItem(PINFO, HMENU, UINT);
VOID vChkMenuItem(PINFO, HMENU, UINT);
BOOL bInitInfo(PINFO);
BOOL bResetGlobal(VOID);
HBRUSH hBrCreateBrush(HDC, DWORD);
BOOL bPrintBmp(PPRTDATA);
BOOL bStoreRleFile(HDC, PINFO, PSTR);
BOOL bFreeRleFile(PINFO);
BOOL bPlayRle(PINFO);
BOOL bSaveRleFile(HDC, PINFO, PSTR);
BOOL bPlayRleCont2(PINFO);
BOOL bSelectDIBPal(HDC, PINFO, LPBITMAPINFO, BOOL);
HBITMAP DIBfromDDB(HDC, HBITMAP, PINFO);

//
// Global variable declarations.
//

HPEN   hpnRed;
HPEN   hpnBlack;
HPEN   hpnGreen;
INT    giPen = 0;

HANDLE ghModule;
HWND   ghwndMain = NULL;
HWND   ghwndClient = NULL;
HANDLE ghAccel;

HMENU  hMenu, hChildMenu, hViewMenu;
HMENU  hViewSubOne, hSubMenuOne, hSubMenuThree;
HMENU  hPrinterMenu;

CHAR   gszFile[20];
CHAR   gszMapName[20];
char   gtext[256];

BOOL   gFloat = TRUE;
LONG   gStep = 1;
LONG   gIteration = 500;
BOOL   gbStretch = TRUE;
INT    giStretchMode = COLORONCOLOR;
INT    giDmOrient = DMORIENT_PORTRAIT;
INT    giNPrinters = 0;

HPALETTE        ghPal, ghPalOld;

double xFrom, xTo, yFrom, yTo, c1, c2;
LONG   lxFrom, lxTo, lyFrom, lyTo, lc1, lc2;

/******************************Public*Routine******************************\
*
* WinMain
*
\**************************************************************************/

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nShowCmd)
{
    MSG    msg;

    ghModule = GetModuleHandle(NULL);
    if (!InitializeApp(&giPen))
{
OutputDebugString("memory: InitializeApp failure!");
return 0;
    }

    if (!(ghAccel = LoadAccelerators (ghModule, MAKEINTRESOURCE(ACCEL_ID))))
OutputDebugString("memory: Load Accel failure!");

    while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator( ghwndMain, ghAccel, &msg) &&
    !TranslateMDISysAccel( ghwndClient, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
    }

    DeleteObject(ghPal);

    return 1;

    UNREFERENCED_PARAMETER(lpCmdLine);
    UNREFERENCED_PARAMETER(nShowCmd);
    UNREFERENCED_PARAMETER(hInstance);
    UNREFERENCED_PARAMETER(hPrevInstance);
}

/***************************************************************************\
* InitializeApp
*
\***************************************************************************/

BOOL InitializeApp(INT *piPen)
{
    WNDCLASS wc;
    HDC      hDC;

#ifdef THRDONE
    INT      iNumClr;
#endif

    wc.style            = CS_OWNDC;
    wc.lpfnWndProc      = (WNDPROC)MainWndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = sizeof(LONG);
    wc.hInstance        = ghModule;
    wc.hIcon            = LoadIcon(ghModule, MAKEINTRESOURCE(APPICON));
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)(COLOR_APPWORKSPACE);
    wc.lpszMenuName     = "MainMenu";
    wc.lpszClassName    = "MandelClass";

    if (!RegisterClass(&wc))
return FALSE;

    wc.lpfnWndProc      = (WNDPROC)ChildWndProc;
    wc.hIcon            = LoadIcon(ghModule, MAKEINTRESOURCE(APPICON));
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "ChildClass";

    if (!RegisterClass(&wc))
return FALSE;

    wc.lpfnWndProc      = (WNDPROC)ViewerWndProc;
    wc.hIcon            = LoadIcon(ghModule, MAKEINTRESOURCE(VIEWICON));
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "ViewerClass";

    if (!RegisterClass(&wc))
return FALSE;

    wc.style            = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = (WNDPROC)TextWndProc;
    wc.hIcon            = NULL;
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)(COLOR_BTNSHADOW);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "Text";

    if (!RegisterClass(&wc))
    return FALSE;

    wc.style            = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = (WNDPROC)JuliaWndProc;
    wc.hIcon            = NULL;

    //
    // Nope.  Can't have this, screw up my Paint Can cursor
    //
    //wc.hCursor          = LoadCursor(NULL, IDC_ARROW);

    wc.hCursor          = NULL;
    wc.hbrBackground    = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "Julia";

    if (!RegisterClass(&wc))
    return FALSE;

    wc.style            = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = (WNDPROC)ViewSurfWndProc;
    wc.hIcon            = NULL;
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "View";

    if (!RegisterClass(&wc))
    return FALSE;

    //
    // Notice, submenu is zero-based
    //
    hMenu       = LoadMenu(ghModule, "MainMenu");
    hChildMenu  = LoadMenu(ghModule, "ChildMenu");
    hViewMenu   = LoadMenu(ghModule, "ViewMenu");
    hViewSubOne = GetSubMenu(hViewMenu, 1);
    hSubMenuOne = GetSubMenu(hMenu, 1);
    hSubMenuThree = GetSubMenu(hChildMenu, 8);
    hPrinterMenu = GetSubMenu(hChildMenu, 7);

    //
    // Disable color-cycling for display devices that does not support
    // palette like the VGA.  As as 29-May-1992, the MIPS display driver
    // is the only one that supports palette
    //
    hDC = GetDC(NULL);
    if (!((GetDeviceCaps(hDC, RASTERCAPS)) & RC_PALETTE)) {
EnableMenuItem(hChildMenu, MM_CYCLE, MF_GRAYED);
    }

#ifdef THRDONE

    if ((iNumClr = iCreatePenFrPal(hDC, NULL, 0, &ghPal)) != 0)
{
sprintf( gtext,"iNumClr = %d\n", iNumClr);
OutputDebugString( gtext);

if ((pInfo->prghPen = (PVOID*) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(HPEN)*iNumClr)) == NULL)
    OutputDebugString ("Failed in Memory Allocation for pInfo->prghPen!");
else
if ((*piPen = iCreatePenFrPal(hDC, pInfo->prghPen, 0, &ghPal)) == 0)
OutputDebugString("Failed in creating pen!");
    }

#endif
    ReleaseDC(NULL, hDC);

    ghwndMain = CreateWindowEx(0L, "MandelClass",
   GetStringRes (IDS_MANDEL_DREAM),

    WS_OVERLAPPED   | WS_CAPTION     | WS_BORDER       |
    WS_THICKFRAME   | WS_MAXIMIZEBOX | WS_MINIMIZEBOX  |
    WS_CLIPCHILDREN | WS_VISIBLE     | WS_SYSMENU,
    80, 70, 550, 400 /* 550 */,
    NULL, hMenu, ghModule, NULL);

    if (ghwndMain == NULL)
return FALSE;

    bInitPrinter(ghwndMain);

    SetWindowLong(ghwndMain, GWL_USERDATA, 0L);

    SetFocus(ghwndMain);    /* set initial focus */

    PostMessage(ghwndMain, WM_COMMAND, MM_MANDEL, 0L);
    PostMessage(ghwndMain, WM_COMMAND, MM_CREATE_MANDEL_THREAD, 0L);

    return TRUE;
}

/******************************Public*Routine******************************\
*
* MainWndProc
*
\**************************************************************************/

long APIENTRY MainWndProc(
    HWND hwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    static int         iJuliaCount=1;
    static int         iMandelCount=1;
    static int         iViewerCount=1;
    CLIENTCREATESTRUCT clientcreate;
    HWND               hwndChildWindow;
    static FARPROC     lpfnSuspendThrd, lpfnResumeThrd;

    switch (message) {

      case WM_CREATE:
SetWindowLong(hwnd, 0, (LONG)NULL);

clientcreate.hWindowMenu  = hSubMenuOne;
clientcreate.idFirstChild = 1;

ghwndClient = CreateWindow("MDICLIENT", NULL,
    WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
    0,0,0,0,
    hwnd, NULL, ghModule, (LPVOID)&clientcreate);
lpfnSuspendThrd = (FARPROC)MakeProcInstance (SuspendDrawThrd, ghModule);
lpfnResumeThrd  = (FARPROC)MakeProcInstance (ResumeDrawThrd, ghModule);

return 0L;

      case WM_DESTROY: {
bCleanupPrinter();
PostQuitMessage(0);
return 0L;
      }

      //
      // Wait! User is going to zero out our app's visible region.  This
      // is going to mess up our drawing (we are not keeping any shadow
      // bitmap in this version yet.) So, let's suspend our drawing thread
      // first before user does that.  We will resume after user is done.
      //
      case WM_SYSCOMMAND: {
LONG        lResult;

//
// We'll enumerate our children and suspend their drawing thread.
//
EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnSuspendThrd, lParam);

//
// Now, let user does it supposed to do
//
lResult = DefFrameProc(hwnd,  ghwndClient, message, wParam, lParam);

//
// User's done, we'll resume the suspended threads in our children
//
EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnResumeThrd, lParam);

return lResult;
break;
      }
#if 0
      //
      // Our window's size is going to change, we'll make sure the new
      // window is a square.
      //
      case WM_WINDOWPOSCHANGING: {
    PWINDOWPOS pWndPos;
    RECT       rect;
    LONG       lcx, lcy;

    GetWindowRect(hwnd, &rect);
    lcx = rect.right-rect.left;
    lcy = rect.bottom-rect.top;
    pWndPos = (PWINDOWPOS)lParam;
    if ((pWndPos->cy > lcy) || (pWndPos->cx > lcx))
pWndPos->cx =  pWndPos->cy =
   ((pWndPos->cx > pWndPos->cy) ? pWndPos->cy : pWndPos->cx);
    else if ((pWndPos->cy < lcy) || (pWndPos->cx < lcx))
     pWndPos->cx =  pWndPos->cy =
((pWndPos->cx > pWndPos->cy) ? pWndPos->cy : pWndPos->cx);
    break;
      }
#endif

      case WM_COMMAND:

switch (LOWORD(wParam)) {
    case IDM_TILE:
SendMessage(ghwndClient, WM_MDITILE, 0L, 0L);
return 0L;
    case IDM_CASCADE:
SendMessage(ghwndClient, WM_MDICASCADE, 0L, 0L);
return 0L;
    case IDM_ARRANGE:
SendMessage(ghwndClient, WM_MDIICONARRANGE, 0L, 0L);
return 0L;

    //
    // Create Julia or Mandelbrot set
    //
    case MM_JULIA:
    case MM_MANDEL: {
HANDLE hInfo;
PINFO  pInfo;
MDICREATESTRUCT mdicreate;

hInfo = LocalAlloc(LHND, (WORD) sizeof(INFO));
if (hInfo == NULL)
{
    OutputDebugString("Failed to Allocate Info!");
    return 0L;
}
if ((pInfo = (PINFO)LocalLock(hInfo)) == NULL)
{
    OutputDebugString("Failed in LocalLock, hInfo");
    return 0L;
}

bInitInfo(pInfo);
wsprintf((LPSTR) &(pInfo->CaptionBarText),
 (LOWORD(wParam) == MM_JULIA) ?
 GetStringRes (IDS_JULIA):
 GetStringRes (IDS_MANDELBROT),
 (LOWORD(wParam) == MM_JULIA) ? iJuliaCount : iMandelCount);
if (LOWORD(wParam) == MM_JULIA) {
    c1 = 0.360284;
    c2 = 0.100376;
    lc1 = 738;          //.3603515
    lc2 = 206;          //.1005859
    pInfo->bMandel = FALSE;
} else {
    pInfo->bMandel = TRUE;
}

//
// Fill in the MDICREATE structure for MDI child creation
//
mdicreate.szClass = "ChildClass";
mdicreate.szTitle = (LPTSTR)&(pInfo->CaptionBarText);
mdicreate.hOwner  = ghModule;
mdicreate.x       =
mdicreate.y       = CW_USEDEFAULT;
mdicreate.cx      = 300;
mdicreate.cy      = 300;
mdicreate.style   = 0L;
mdicreate.lParam  = (LONG) hInfo;

/*Create Child Window*/
hwndChildWindow =
    (HANDLE) SendMessage(ghwndClient, WM_MDICREATE,
0L,
(LONG)(LPMDICREATESTRUCT)&mdicreate);

if (hwndChildWindow == NULL)
{
    OutputDebugString ("Failed in Creating Child Window");
    return 0L;
}

(LOWORD(wParam) == MM_JULIA) ? iJuliaCount++ : iMandelCount++ ;
LocalUnlock(hInfo);
return ((LONG)hwndChildWindow);
    }

    case MM_RLEVIEWER: {
HANDLE hInfo;
PINFO  pInfo;
MDICREATESTRUCT mdicreate;

hInfo = LocalAlloc(LHND, (WORD) sizeof(INFO));
if (hInfo == NULL)
{
    OutputDebugString ("Failed to Allocate Info!");
    return 0L;
}
if ((pInfo = (PINFO)LocalLock(hInfo)) == NULL)
{
    OutputDebugString ("Failed in LocalLock, hInfo");
    return 0L;
}

bInitInfo(pInfo);
wsprintf((LPSTR) &(pInfo->CaptionBarText), GetStringRes (IDS_VIEWER), iViewerCount );

//
// Fill in the MDICREATE structure for MDI child creation
//
mdicreate.szClass = "ViewerClass";
mdicreate.szTitle = (LPTSTR)&(pInfo->CaptionBarText);
mdicreate.hOwner  = ghModule;
mdicreate.x       =
mdicreate.y       = CW_USEDEFAULT;
mdicreate.cx      = 300;
mdicreate.cy      = 300;
mdicreate.style   = 0L;
mdicreate.lParam  = (LONG) hInfo;

/*Create Child Window*/
hwndChildWindow =
    (HANDLE) SendMessage(ghwndClient, WM_MDICREATE,
0L,
(LONG)(LPMDICREATESTRUCT)&mdicreate);

if (hwndChildWindow == NULL)
{
    OutputDebugString ("Failed in Creating Child Window");
    return 0L;
}

iViewerCount++ ;
LocalUnlock(hInfo);
return ((LONG)hwndChildWindow);

    }

    case MM_ABOUT:
if (DialogBox(ghModule, "AboutBox", ghwndMain, (DLGPROC)About) == -1)
   OutputDebugString ("DEMO: About Dialog Creation Error!");
return 0L;

    //
    // Only my children know how to deal with these messages, so
    // pass these to children for processing
    //
    case MM_CREATE_JULIA_THREAD:
    case MM_SET_XFORM_ATTR:
    case MM_CREATE_MANDEL_THREAD:
    case MM_OPT_4:              // currently not used
    case MM_DRAW_SET:
    case MM_SETDIB2DEVICE:
    case MM_BW:
    case MM_SHIFT:
    case MM_CUSTOM:
    case MM_CYCLE:
    case MM_TP_IDLE:
    case MM_TP_LOW:
    case MM_TP_BELOW_NORMAL:
    case MM_TP_NORMAL:
    case MM_TP_ABOVE_NORMAL:
    case MM_TP_HIGH:
    case MM_TP_TIME_CRITICAL:
    case MM_FLOAT:
    case MM_FIX:
    case MM_ITERATION_100:
    case MM_ITERATION_500:
    case MM_ITERATION_1000:
    case MM_ITERATION_5000:
    case MM_ITERATION_DOUBLE:
    case MM_STEP_ONE:
    case MM_STEP_TWO:
    case MM_STEP_THREE:
    case MM_SAVE:
    case MM_SAVE_MONO:
    case MM_LOAD:
    case MM_STRETCHBLT:
    case MM_BITBLT:
    case MM_BLACKONWHITE:
    case MM_COLORONCOLOR:
    case MM_WHITEONBLACK:
    case MM_HALFTONE:
    case MM_CLIP:
    case MM_RM_CLIP:
    case MM_SELCLIPRGN:
    case MM_ERASE:
    case MM_PORTRAIT:
    case MM_LANDSCAPE:
    case MM_PRINTER:
    case MM_PRINTER + 1:
    case MM_PRINTER + 2:
    case MM_PRINTER + 3:
    case MM_PRINTER + 4:
    case MM_PRINTER + 5:
    case MM_PRINTER + 6:
    case MM_PRINTER + 7:
    case MM_PRINTER + 8:
    case MM_PRINTER + 9:
    case MM_RLELOAD_DEMO:
    case MM_RLEPLAYCONT:
    case MM_RLELOAD:
    case MM_RLESAVE:
    case MM_CLEAR:
    case MM_RLEPLAY:
    {
HWND hActiveChild;

hActiveChild = (HANDLE) SendMessage(ghwndClient, WM_MDIGETACTIVE, 0L, 0L);
if (hActiveChild)
    SendMessage(hActiveChild, WM_COMMAND, wParam, lParam);
return 0L;
    }

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

return DefFrameProc(hwnd,  ghwndClient, message, wParam, lParam);
    }
}

/***************************************************************************\
* ChildWndProc
*
\***************************************************************************/

long APIENTRY ChildWndProc(
    HWND hwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    static FARPROC     lpfnSuspendThrd, lpfnResumeThrd;
    static BOOL        bDIB2Device = FALSE;

    sprintf( gtext,"message = %lx\n", message);
    OutputDebugString( gtext);

    switch (message) {
case WM_COMMAND: {
  PINFO       pInfo;
  HWND        hTextWnd;

  switch (LOWORD(wParam)) {
    //
    // Create a Julia drawing thread
    //
    case MM_CREATE_JULIA_THREAD: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
sprintf( gtext,"(%g, %g) <-> (%g, %g)", pInfo->xFrom, pInfo->yFrom, pInfo->xTo, pInfo->yTo);
SetWindowText(hTextWnd, gtext);
sprintf( gtext,"(c1 = %g, c2 = %g)\n\n", pInfo->c1, pInfo->c2);
OutputDebugString( gtext );

if (pInfo->hThrd)
    CloseHandle(pInfo->hThrd);

pInfo->hThrd = CreateThread(NULL, 0,
 (gFloat ? (LPTHREAD_START_ROUTINE)StartDraw : (LPTHREAD_START_ROUTINE)StartDrawFix),
 pInfo,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwThreadId );

       if (pInfo->hThrd && pInfo->bDrawing)
   {
   if (!SetThreadPriority(pInfo->hThrd, pInfo->iPriority))
     OutputDebugString ("Can't set Priority!");
       }

       bReleaseInfoData(hwnd);
       return 0L;
    }

    //
    // Reset pInfo reflecting new transformation
    //
    case MM_SET_XFORM_ATTR: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
SetWindowText(hTextWnd, GetStringRes (IDS_CLICK_HERE_VIEW));

pInfo->xFrom      = xFrom;
pInfo->xTo        = xTo;
pInfo->yFrom      = yFrom;
pInfo->yTo        = yTo;
pInfo->c1         = c1;
pInfo->c2         = c2;
pInfo->lxFrom      = lxFrom;
pInfo->lxTo        = lxTo;
pInfo->lyFrom      = lyFrom;
pInfo->lyTo        = lyTo;
pInfo->lc1         = lc1;
pInfo->lc2         = lc2;

       bReleaseInfoData(hwnd);
       return 0L;
    }

    //
    // Create a Mandelbrot drawing thread
    //
    case MM_CREATE_MANDEL_THREAD: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
sprintf( gtext,"(%g, %g) <-> (%g, %g)", pInfo->xFrom, pInfo->yFrom, pInfo->xTo, pInfo->yTo);
SetWindowText(hTextWnd, gtext);
sprintf( gtext,"(c1 = %g, c2 = %g)\n\n", pInfo->c1, pInfo->c2);
OutputDebugString( gtext );

if (pInfo->hThrd)
    CloseHandle(pInfo->hThrd);

pInfo->hThrd = CreateThread(NULL, 0,
 (gFloat ? (LPTHREAD_START_ROUTINE)StartMandelbrot : (LPTHREAD_START_ROUTINE)StartMandelbrotFix),
 pInfo,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwThreadId );

       if (pInfo->hThrd && pInfo->bDrawing)
   {
  if (!SetThreadPriority(pInfo->hThrd, pInfo->iPriority))
     OutputDebugString ("Can't set Priority!");
       }

       bReleaseInfoData(hwnd);
       return 0L;
    }

    //
    // Create a Julia drawing thread using algorithm StartDraw2
    // Currently not used
    //
    case MM_OPT_4: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
SetWindowText(hTextWnd, "MM_OPT_4");

sprintf( gtext,"xFrom = %g, xTo = %g, yFrom = %g, yTo = %g\n", pInfo->xFrom, pInfo->xTo, pInfo->yFrom, pInfo->yTo);
OutputDebugString( gtext );

if (pInfo->hThrd)
    CloseHandle(pInfo->hThrd);

pInfo->hThrd = CreateThread(NULL, 0,
 (LPTHREAD_START_ROUTINE)StartDraw2,
 pInfo,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwThreadId );

       if (pInfo->hThrd && pInfo->bDrawing)
   {
      if (!SetThreadPriority(pInfo->hThrd, pInfo->iPriority))
     OutputDebugString ("Can't set Priority!");
       }

       bReleaseInfoData(hwnd);
       return 0L;
    }

    case MM_DRAW_SET: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

PostMessage(hwnd, WM_COMMAND,
    pInfo->bMandel ? (DWORD)((WORD)MM_CREATE_MANDEL_THREAD) : (DWORD)((WORD)MM_CREATE_JULIA_THREAD),
    (LONG)0L);

       bReleaseInfoData(hwnd);
       return 0L;
    }

    {
    int     iPriority;

case MM_TP_IDLE:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_IDLE;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_IDLE);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_LOW:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_LOWEST;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_LOW);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_BELOW_NORMAL:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_BELOW_NORMAL;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_BELOW_NORMAL);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_NORMAL:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_NORMAL;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_NORMAL);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_ABOVE_NORMAL:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_ABOVE_NORMAL;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_ABOVE_NORMAL);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_HIGH:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_HIGHEST;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_HIGH);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    goto CWP_SET_PRIORITY;

case MM_TP_TIME_CRITICAL:
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    iPriority = THREAD_PRIORITY_TIME_CRITICAL;
    bCheckMutexMenuItem(pInfo, hChildMenu, MM_TP_TIME_CRITICAL);
    DrawMenuBar(GetParent(GetParent(hwnd))) ;

CWP_SET_PRIORITY:
    {
       HANDLE       hThrd;

       hThrd = pInfo->hThrd;
       pInfo->iPriority = iPriority;

       if (hThrd && pInfo->bDrawing)
   {
      if (!SetThreadPriority(hThrd, iPriority))
     OutputDebugString ("Can't set Priority!");
       }

    }
    bReleaseInfoData(hwnd);
    return 0L;

    }

    case MM_FLOAT: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_FLOAT);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gFloat = TRUE;
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_FIX: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_FIX);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gFloat = FALSE;
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ITERATION_100: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_ITERATION_100);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gIteration = 100;
pInfo->iIteration = 100;
SetWindowText(pInfo->hTextWnd, GetStringRes (IDS_ITER_100));
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ITERATION_500: {
if ((pInfo = pGetInfoData(hwnd)) == NULL){
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_ITERATION_500);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gIteration = 500;

pInfo->iIteration = 500;
SetWindowText(pInfo->hTextWnd, GetStringRes (IDS_ITER_500));
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ITERATION_1000: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_ITERATION_1000);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gIteration = 1000;
pInfo->iIteration = 1000;
SetWindowText(pInfo->hTextWnd, GetStringRes (IDS_ITER_1000));
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ITERATION_5000: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_ITERATION_5000);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gIteration = 5000;
pInfo->iIteration = 5000;
SetWindowText(pInfo->hTextWnd, GetStringRes (IDS_ITER_5000));
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ITERATION_DOUBLE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_ITERATION_DOUBLE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gIteration *= 2;
pInfo->iIteration = gIteration;
sprintf( gtext, GetStringRes (IDS_ITERATION), pInfo->iIteration);
SetWindowText(pInfo->hTextWnd, gtext);
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_STEP_ONE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_STEP_ONE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gStep = 1;
pInfo->iStep = 1;

bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_STEP_TWO:  {
if ((pInfo = pGetInfoData(hwnd)) == NULL){
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_STEP_TWO);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gStep = 2;
pInfo->iStep = 2;

bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_STEP_THREE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bCheckMutexMenuItem(pInfo, hChildMenu, MM_STEP_THREE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
gStep = 3;
pInfo->iStep = 3;

bReleaseInfoData(hwnd);
return 0L;
    }

    case MM_LOAD: {
HDC hDC;
OPENFILENAME ofn;
char szDirName[256];
char szFile[256], szFileTitle[256];
static char *szFilter;
RECT    rc;

if ((pInfo = pGetInfoData(hwnd)) == NULL){
    return 0L;
}

szFilter = GetStringRes (IDS_FILE_LIST1);

GetSystemDirectory((LPSTR) szDirName, 256);
strcpy(szFile, "*.bmp\0");

ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = pInfo->hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = (LPSTR) NULL;
ofn.Flags = 0L;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = "BMP";

if (!GetOpenFileName(&ofn))
    return 0L;

GetClientRect(pInfo->hwnd, &rc);
hDC = GetDC(pInfo->hwnd);
if (LoadBitmapFile(hDC, pInfo, szFile))
  bDrawDIB(hDC, pInfo, 0, 0, rc.right, rc.bottom);
ReleaseDC(hwnd, hDC);

bReleaseInfoData(hwnd);

return 0L;
    }

    case MM_SAVE: {
HDC hDC;
OPENFILENAME ofn;
char szDirName[256];
char szFile[256], szFileTitle[256];
static char *szFilter;

szFilter = GetStringRes (IDS_FILE_LIST2);

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);
#if 0
{
HPALETTE hPalTmp;

hPalTmp = CopyPalette(pInfo->hPal);
DeleteObject(pInfo->hPal);
pInfo->hPal = hPalTmp;
}
#endif
//
// saving special effects user might have created in window
//
if (pInfo->hBmpSaved)
    DeleteObject(pInfo->hBmpSaved);
pInfo->hBmpSaved = SaveBitmap(pInfo->hwnd, pInfo->hPal);
pInfo->bUseDIB = FALSE;

GetSystemDirectory((LPSTR) szDirName, 256);
strcpy(szFile, "*.bmp\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = pInfo->hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 0L;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = (LPSTR) NULL;
ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = (LPSTR)NULL;

if (!GetSaveFileName(&ofn)) {
    ReleaseDC(pInfo->hwnd, hDC);
    bReleaseInfoData(hwnd);
    return 0L;
}

SelectPalette(hDC,
   ((pInfo->iStretchMode == HALFTONE) ? pInfo->hHTPal : pInfo->hPal),
   FALSE);
RealizePalette(hDC);
UpdateColors(hDC);

//
// test
//
ghPal = pInfo->hPal;

SaveBitmapFile(hDC, pInfo->hBmpSaved, szFile);

ReleaseDC(pInfo->hwnd, hDC);

bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_SAVE_MONO: {
HDC hDC;
OPENFILENAME ofn;
char szDirName[256];
char szFile[256], szFileTitle[256];
static char *szFilter;

if ((pInfo = pGetInfoData(hwnd)) == NULL){
    return 0L;
}

szFilter = GetStringRes (IDS_FILE_LIST2);

GetSystemDirectory((LPSTR) szDirName, 256);
strcpy(szFile, "*.bmp\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = pInfo->hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 0L;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = GetStringRes (IDS_SAVING_MONO_BITMAP);
ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = (LPSTR)NULL;

if (!GetSaveFileName(&ofn))
    return 0L;

hDC = GetDC(pInfo->hwnd);

SaveBitmapFile(hDC, pInfo->hBmpMono, szFile);
ReleaseDC(pInfo->hwnd, hDC);

bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_STRETCHBLT: {
if ((pInfo = pGetInfoData(hwnd)) == NULL){
    return 0L;
}
gbStretch = TRUE;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_STRETCHBLT);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->bStretch = gbStretch;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;

    }
    case MM_BITBLT: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
gbStretch = FALSE;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_BITBLT);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->bStretch = gbStretch;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;

    }
    case MM_BLACKONWHITE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
giStretchMode = BLACKONWHITE;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_BLACKONWHITE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->iStretchMode = giStretchMode;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;

    }
    case MM_COLORONCOLOR: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
giStretchMode = COLORONCOLOR;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_COLORONCOLOR);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->iStretchMode = giStretchMode;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;

    }
    case MM_WHITEONBLACK: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
giStretchMode = WHITEONBLACK;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_WHITEONBLACK);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->iStretchMode = giStretchMode;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;

    }
    case MM_HALFTONE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
giStretchMode = HALFTONE;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_HALFTONE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
pInfo->iStretchMode = giStretchMode;
InvalidateRect(pInfo->hwnd, NULL, FALSE);

bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_SETDIB2DEVICE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
bDIB2Device = (bDIB2Device ? FALSE : TRUE);
pInfo->bSetDIBsToDevice = bDIB2Device;
CheckMenuItem(hChildMenu, MM_SETDIB2DEVICE, (bDIB2Device ? MF_CHECKED : MF_UNCHECKED));
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_BW: {
HDC hDC;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);
bChangeDIBColor(hDC, pInfo, MM_BW);
ReleaseDC(hwnd, hDC);
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_SHIFT: {
HDC hDC;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);
bChangeDIBColor(hDC, pInfo, MM_SHIFT);
ReleaseDC(hwnd, hDC);
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_CUSTOM: {
static DWORD argbCust[16] = {
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255),
    RGB(255, 255, 255), RGB(255, 255, 255)
};
CHOOSECOLOR cc;
BOOL bResult;
DWORD rgbOld;
HBRUSH hBrush;
HDC hDC;

rgbOld = RGB(255, 255, 255);
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = ghwndMain;
cc.hInstance = ghModule;
cc.rgbResult = rgbOld;
cc.lpCustColors = argbCust;
cc.Flags = CC_RGBINIT | CC_SHOWHELP;
cc.lCustData = 0;
cc.lpfnHook = NULL;
cc.lpTemplateName = NULL;

bResult = ChooseColor(&cc);
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
if (bResult) {
    hDC = GetDC(pInfo->hwnd);
    hBrush = hBrCreateBrush(hDC, cc.rgbResult);
    ReleaseDC(pInfo->hwnd, hDC);
    if (pInfo->hBrush)
DeleteObject(pInfo->hBrush);
    pInfo->hBrush = hBrush;
    pInfo->bFill = TRUE;
}
bReleaseInfoData(hwnd);
return 0L;
}

#ifndef CYCLETHRD
    case MM_CYCLE: {
HDC hDC;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);

if (pInfo->bClrCycle) {
    CheckMenuItem(hChildMenu, MM_CYCLE, MF_UNCHECKED);
    pInfo->bClrCycle = FALSE;
} else {
    CheckMenuItem(hChildMenu, MM_CYCLE, MF_CHECKED);
    pInfo->bClrCycle = TRUE;
    bChangeDIBColor(hDC, pInfo, MM_CYCLE);
}

ReleaseDC(hwnd, hDC);
bReleaseInfoData(hwnd);
return 0L;
    }
#else

    case MM_CYCLE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if (pInfo->bFirstTime)
{
    if (!SetEvent(pInfo->hQuitEvent))
{
OutputDebugString ("Can't set Quit Event!");
return 0L;
    }

    if (pInfo->hCycleThrd)
CloseHandle(pInfo->hCycleThrd);

    pInfo->hCycleThrd = CreateThread(NULL, 0,
     (LPTHREAD_START_ROUTINE)bCycle,
     (LPVOID)hwnd,
     STANDARD_RIGHTS_REQUIRED,
     &pInfo->dwCycleThrdID );
    pInfo->bClrCycle = TRUE;
    pInfo->bFirstTime = FALSE;
    CheckMenuItem(hChildMenu, MM_CYCLE, MF_CHECKED);
} else {
    if (pInfo->bClrCycle) {
CheckMenuItem(hChildMenu, MM_CYCLE, MF_UNCHECKED);
pInfo->bClrCycle = FALSE;
pInfo->dwSuspend = SuspendThread(pInfo->hCycleThrd);
    } else {
CheckMenuItem(hChildMenu, MM_CYCLE, MF_CHECKED);
pInfo->bClrCycle = TRUE;
pInfo->dwSuspend = ResumeThread(pInfo->hCycleThrd);
    }
    if (pInfo->dwSuspend == -1) {
(pInfo->bClrCycle ?
 sprintf( gtext,"Error in resuming thread\n") :
 sprintf( gtext,"Error in suspending thread\n")  );
OutputDebugString( gtext );
    }
}

bReleaseInfoData(hwnd);
return 0L;
    }
#endif
    case MM_CLIP: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
sprintf( gtext,"(%g, %g) <-> (%g, %g)", pInfo->xFrom, pInfo->yFrom, pInfo->xTo, pInfo->yTo);
SetWindowText(hTextWnd, gtext);
sprintf( gtext,"(c1 = %g, c2 = %g)\n\n", pInfo->c1, pInfo->c2);
OutputDebugString( gtext );
if (!pInfo->bMandel)
{
    MessageBox(ghwndMain,
   GetStringRes (IDS_BOUNDARY),
   NULL, MB_OK);
    return 0L;
}

if (pInfo->hThrd)
    CloseHandle(pInfo->hThrd);

pInfo->hThrd = CreateThread(NULL, 0,
 (gFloat ? (LPTHREAD_START_ROUTINE)bBoundaryScanFix : (LPTHREAD_START_ROUTINE)bBoundaryScanFix),
 pInfo,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwThreadId );

       bReleaseInfoData(hwnd);
       return 0L;
    }
    case MM_RM_CLIP: {
HDC     hDC;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hDC = GetDC(pInfo->hwnd);
SelectClipRgn(hDC, (HRGN) NULL);
ReleaseDC(pInfo->hwnd, hDC);
bReleaseInfoData(hwnd);
InvalidateRect(pInfo->hwnd, NULL, FALSE);
return 0L;

    }
    case MM_SELCLIPRGN: {
HDC     hDC;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);

if (pInfo->hRgnPath != (HRGN) NULL) {
    SelectClipRgn(hDC, pInfo->hRgnPath);
}

ReleaseDC(pInfo->hwnd, hDC);
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_ERASE: {
HDC     hDC;
RECT    rc;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}
hDC = GetDC(pInfo->hwnd);
if (pInfo->hRgnPath != (HRGN) NULL) {
    SelectClipRgn(hDC, pInfo->hRgnPath);
}
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
GetClientRect(pInfo->hwnd, &rc);
PatBlt(hDC, 0, 0, rc.right, rc.bottom, PATCOPY);
ReleaseDC(pInfo->hwnd, hDC);
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_PORTRAIT: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

giDmOrient = DMORIENT_PORTRAIT;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_PORTRAIT);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_LANDSCAPE: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

giDmOrient = DMORIENT_LANDSCAPE;
bCheckMutexMenuItem(pInfo, hChildMenu, MM_LANDSCAPE);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
bReleaseInfoData(hwnd);
return 0L;
    }
    case MM_PRINTER:
    case MM_PRINTER + 1:
    case MM_PRINTER + 2:
    case MM_PRINTER + 3:
    case MM_PRINTER + 4:
    case MM_PRINTER + 5:
    case MM_PRINTER + 6:
    case MM_PRINTER + 7:
    case MM_PRINTER + 8:

#ifdef PRTTHRD
    case MM_PRINTER + 9: {
PINFO       pInfo;
PRTDATA     PrtData, *pPrtData;
ULONG       sizINFO;
PBYTE       pjTmpInfo, pjTmp;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if (pInfo->hBmpSaved == NULL)
{
    MessageBox(ghwndMain,
       GetStringRes (IDS_NO_SAVED_BITMAP),
   NULL, MB_OK);
    return 0L;
}

//
// Copy the info structure to PrtData
//
pPrtData = &PrtData;
pjTmp    = (PBYTE)&(pPrtData->info);
pjTmpInfo = (PBYTE)pInfo;
sizINFO = sizeof(INFO);

while(sizINFO--)
{
    *(((PBYTE)pjTmp)++) = *((pjTmpInfo)++);
}

PrtData.index = LOWORD(wParam) - MM_PRINTER;

if (giDmOrient == DMORIENT_PORTRAIT) {
    PrtData.bUseDefault = TRUE;
} else {
    PrtData.bUseDefault = FALSE;
    PrtData.DevMode.dmSize = sizeof(DEVMODE);
    PrtData.DevMode.dmDriverExtra = 0;
    PrtData.DevMode.dmOrientation = DMORIENT_LANDSCAPE;
    PrtData.DevMode.dmFields = DM_ORIENTATION;
}

if (pInfo->hPrtThrd)
    CloseHandle(pInfo->hPrtThrd);

pInfo->hPrtThrd = CreateThread(NULL, 0,
 (LPTHREAD_START_ROUTINE)bPrintBmp,
 &PrtData,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwPrtThrdID );

bReleaseInfoData(hwnd);
return 0L;
    }

#else
    case MM_PRINTER + 9: {
HDC         hdcPrinter, hDC;
int         index;
DEVMODE     devmode;
DEVMODE     *pdevmode;
PINFO       pInfo;
int         iWidth, iHeight;

index = LOWORD(wParam) - MM_PRINTER;

if (giDmOrient == DMORIENT_PORTRAIT)
    pdevmode = NULL;
else {
    pdevmode = &devmode;
    devmode.dmSize = sizeof(DEVMODE);
    devmode.dmDriverExtra = 0;
    devmode.dmOrientation = DMORIENT_LANDSCAPE;
    devmode.dmFields = DM_ORIENTATION;
}

if (!(hdcPrinter = CreateDC( "", gpszPrinterNames[index],
     "", pdevmode)))
{
    return(0L);
}

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

iWidth = GetDeviceCaps(hdcPrinter, HORZRES);
iHeight = GetDeviceCaps(hdcPrinter, VERTRES);

// !!! Why is it necessary to save the image over again?  May not want to
//     do this because user may want to print the nice HT bitmap. So,
//     use the DIB src.
#if 0
if (pInfo->hBmpSaved)
    DeleteObject(pInfo->hBmpSaved);

pInfo->hBmpSaved = SaveBitmap(pInfo->hwnd, pInfo->hPal);
#endif
Escape(hdcPrinter, STARTDOC, 20, "Mandelbrot", NULL);
bDrawDIB(hdcPrinter, pInfo, 0, 0, iWidth, iHeight);
Escape(hdcPrinter, NEWFRAME, 0, NULL, NULL);
Escape(hdcPrinter, ENDDOC, 0, NULL, NULL);
ReleaseDC(pInfo->hwnd, hDC);
bReleaseInfoData(hwnd);
DeleteDC(hdcPrinter);
return 0L;
    }
#endif
    default:
       return 0L;

  }

}
case WM_SETFOCUS:
    break;

case WM_MDIACTIVATE: {
    PINFO       pInfo;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    if ((HWND) lParam == hwnd) {                // being activated
SendMessage(GetParent(hwnd), WM_MDISETMENU,
    (DWORD)  hChildMenu,
    (LONG)   hSubMenuThree) ;

(pInfo->bClrCycle ?
    CheckMenuItem(hChildMenu, MM_CYCLE, MF_CHECKED) :
    CheckMenuItem(hChildMenu, MM_CYCLE, MF_UNCHECKED) );

vChkMenuItem(pInfo, hChildMenu, MF_CHECKED);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
goto MDI_ACT_EXIT;
    }

    if ((HWND) wParam == hwnd) {                // being deactivated

vChkMenuItem(pInfo, hChildMenu, MF_UNCHECKED);
DrawMenuBar(GetParent(GetParent(hwnd))) ;
    }
MDI_ACT_EXIT:
    bReleaseInfoData(hwnd);
    return 0L;
}

case WM_QUERYNEWPALETTE:
case WM_PALETTECHANGED: {
    PINFO       pInfo;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    SendMessage(pInfo->hwnd, message, wParam, lParam);
    bReleaseInfoData(hwnd);
    return 0L;
}

#if 0
case WM_WINDOWPOSCHANGING: {
    PWINDOWPOS  pWndPos;
    PINFO       pInfo;
    HWND        hTextWnd;
    int         iCyText, iCxBorder, iCyBorder, iCyCaption;
    RECT        rect, rcl;
    LONG        lcx, lcy;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
break;
    }

    hTextWnd = pInfo->hTextWnd;

    bReleaseInfoData(hwnd);

    iCyText = GetWindowLong(hTextWnd, GWL_USERDATA);
    iCxBorder = GetSystemMetrics(SM_CXBORDER);
    iCyBorder = GetSystemMetrics(SM_CYBORDER);
    iCyCaption = GetSystemMetrics(SM_CYCAPTION) - iCyBorder;
    GetClientRect(GetParent(hwnd), &rcl);
    GetWindowRect(hwnd, &rect);
    lcx = rect.right-rect.left;
    lcy = rect.bottom-rect.top;
    pWndPos = (PWINDOWPOS)lParam;
    if ((pWndPos->cy > lcy) || (pWndPos->cx > lcx)) {
pWndPos->cx =
   ((pWndPos->cx > pWndPos->cy) ? pWndPos->cy-iCyText : pWndPos->cx);
pWndPos->cy = pWndPos->cx + iCyText;
    } else { if ((pWndPos->cy < lcy) || (pWndPos->cx < lcx)) {
     pWndPos->cx =
((pWndPos->cx > pWndPos->cy) ? pWndPos->cy-iCyText : pWndPos->cx);
     pWndPos->cy = pWndPos->cx + iCyText;
   }
    }
    break;
}
#endif
case WM_SIZE: {
    HANDLE      hThrd;
    PINFO       pInfo;
    HWND        hTextWnd, hJulia;
    BOOL        bMandel;
    WORD        wCx;
    int         iCyText;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
break;
    }

    hTextWnd = pInfo->hTextWnd;
    hJulia   = pInfo->hwnd;
    hThrd    = pInfo->hThrd;
    bMandel  = pInfo->bMandel;
    bReleaseInfoData(hwnd);
    iCyText = GetWindowLong(hTextWnd, GWL_USERDATA);
    wCx = (WORD) (HIWORD(lParam) - iCyText);

    MoveWindow(hJulia, 0, 0,
   LOWORD(lParam),
   wCx,
   TRUE);

    MoveWindow(hTextWnd,
       0,
       wCx,
       LOWORD(lParam),
       iCyText,
       TRUE);

    if (hThrd) {
TerminateThread(hThrd, (DWORD)0L);
/*
PostMessage(hwnd, WM_COMMAND,
    bMandel ? (DWORD)((WORD)MM_CREATE_MANDEL_THREAD) : (DWORD)((WORD)MM_CREATE_JULIA_THREAD),
    (LONG)0L);
  */
    }

    break;
}

//
// display info in the status window
//
case WM_USER+0xa: {
    PINFO       pInfo;
    static ULONG ulClick = 0;
    HWND        hTextWnd;

    ulClick++;
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    hTextWnd = pInfo->hTextWnd;
    switch (ulClick % 6) {
case 0: sprintf( gtext,"%g <= x <= %g, %g <= y <= %g", pInfo->xFrom, pInfo->xTo, pInfo->yTo, pInfo->yFrom);
break;
case 1: sprintf( gtext,"c1 = %g, c2 = %g", pInfo->c1, pInfo->c2);
break;
case 2: sprintf( gtext,
     GetStringRes (IDS_ELAPSED_TIME),
 (ULONG) pInfo->dwElapsed);
break;
case 3: sprintf( gtext,
     GetStringRes (IDS_ITERATION),
 pInfo->iIteration);
break;
case 4:sprintf( gtext,
 GetStringRes (IDS_STEP),
 pInfo->iStep);
break;
case 5:
(gFloat ? sprintf( gtext, GetStringRes (IDS_FLOATING_PT))
        : sprintf( gtext, GetStringRes (IDS_FIXED_PT))) ;
break;

default: break;
    }
    SetWindowText(hTextWnd, gtext);
    bReleaseInfoData(hwnd);
    return 0L;
}

case WM_SYSCOMMAND: {
    LONG        lResult;

    EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnSuspendThrd, lParam);

    lResult = DefMDIChildProc(hwnd, message, wParam, lParam);

    EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnResumeThrd, lParam);

    return lResult;
    break;
}

case WM_CREATE: {
    PINFO           pInfo;
    HANDLE          hInfo;
    HWND            hTextWnd, hJulia;
    RECT            rcl;

    //
    // CR! MakeProcInstance is noop!
    //
    lpfnSuspendThrd = (FARPROC)MakeProcInstance (SuspendDrawThrd, ghModule);
    lpfnResumeThrd  = (FARPROC)MakeProcInstance (ResumeDrawThrd, ghModule);

    hTextWnd = CreateWindow("Text", NULL,
    WS_BORDER | SS_LEFT | WS_CHILD | WS_VISIBLE,
    0, 0, 0, 0,
    hwnd,
    (HMENU) 2,
    ghModule,
    NULL);

    GetClientRect(hwnd, &rcl);
    hJulia = CreateWindow("Julia", (LPSTR) NULL,
  WS_CHILD      | WS_VISIBLE     |
  WS_BORDER,
  0,0, rcl.right-rcl.left,
  rcl.bottom-rcl.top-GetWindowLong(hTextWnd, GWL_USERDATA),
  hwnd, (HMENU)1, ghModule, (LPVOID)NULL);

    SetWindowText(hTextWnd, GetStringRes (IDS_SELECT_DRAW_SET));

    hInfo = (HANDLE) ((LPMDICREATESTRUCT) ((LPCREATESTRUCT) lParam)->lpCreateParams)->lParam ;
    if (hInfo) {
if ((pInfo = (PINFO)LocalLock(hInfo)) == NULL)
{
    OutputDebugString ("Failed in LocalLock, hNode");
    break;
}
else
{
    HDC hDC;

    if (!GetClientRect(hwnd, &pInfo->rcClient))
OutputDebugString ("Failed in GetClientRect!");

    pInfo->hTextWnd = hTextWnd;
    pInfo->hwnd = hJulia;
    hDC = GetDC(hJulia);
    pInfo->hHTPal = CreateHalftonePalette(hDC);
    ReleaseDC(hJulia, hDC);
#ifdef CYCLETHRD
    //
    // Creating a signal quit color cycling event
    //
    if ((pInfo->hQuitEvent = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL)
OutputDebugString ("Failed in creating Quit Event!");
#endif
    SetWindowLong(hwnd, 0, (LONG) hInfo);
    LocalUnlock(hInfo);
}
    }
else
{
OutputDebugString ("Can't allocate hInfo!");
    }

#if 0
    //
    // Initialize printers here will detect printers availiability
    // more often, but kind of overkill.
    //
    bInitPrinter(hwnd);
#endif
    break;
}

case WM_CLOSE: {
    SendMessage(GetParent(hwnd), WM_MDISETMENU,
    (DWORD) hMenu,
    (LONG)  hSubMenuOne) ;
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    break;
}

case WM_DESTROY: {
    PINFO            pInfo;
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
break;
    }

    if (pInfo->hThrd) {
TerminateThread(pInfo->hThrd, (DWORD)0L);
CloseHandle(pInfo->hThrd);
    }
    if (pInfo->hPrtThrd) {
TerminateThread(pInfo->hPrtThrd, (DWORD)0L);
CloseHandle(pInfo->hPrtThrd);
    }
    if (pInfo->hCycleThrd) {
TerminateThread(pInfo->hCycleThrd, (DWORD)0L);
CloseHandle(pInfo->hCycleThrd);
    }

#ifdef CYCLETHRD
    //
    // Cleanup color cycling
    //
    if (!ResetEvent(pInfo->hQuitEvent))
OutputDebugString ("Failed in reseting quit event!");
#endif
    if (pInfo->hBmpMono)
DeleteObject(pInfo->hBmpMono);

    if (pInfo->hBmpSaved)
DeleteObject(pInfo->hBmpSaved);

    if (pInfo->hBrush)
DeleteObject(pInfo->hBrush);

    bReleaseInfoData(hwnd);
    LocalFree((HANDLE) GetWindowLong(hwnd, 0));
    break;
}

default:
    return DefMDIChildProc(hwnd, message, wParam, lParam);

    } //switch
    return DefMDIChildProc(hwnd, message, wParam, lParam);
}

/******************************Public*Routine******************************\
*
* ViewerWndProc
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG APIENTRY ViewerWndProc(
    HWND hwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    static FARPROC     lpfnSuspendThrd, lpfnResumeThrd;

    switch (message) {
case WM_COMMAND: {
  PINFO       pInfo;
  HWND        hTextWnd;

  switch (LOWORD(wParam)) {
    case MM_RLELOAD_DEMO: {
HDC             hDC;
char            szDirName[256];
char            szFile[400];
HWND            hViewSurf;

GetCurrentDirectory(256, (LPTSTR) szDirName);

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if ((hViewSurf = pInfo->hwnd) == NULL)
    return 0L;

hTextWnd = pInfo->hTextWnd;
sprintf( gtext,"Loading bitmap(s) into memory");
SetWindowText(hTextWnd, gtext);

hDC = GetDC(hViewSurf);
strcpy(szFile, szDirName);
strcat(szFile, "\\rsc\\julia.rle");

if (bStoreRleFile(hDC, pInfo, szFile)) {
    pInfo->RleData.ulFiles++;
    PostMessage(ghwndMain, WM_COMMAND, MM_RLEPLAYCONT, 0L);
}

ReleaseDC(hViewSurf, hDC);

bReleaseInfoData(hwnd);
return 0L;

    }

    case MM_RLELOAD: {
HDC             hDC;
OPENFILENAME    ofn;
char            szDirName[256];
char            szFile[256], szFileTitle[256];
static char     *szFilter;
RECT            rc;
HWND            hViewSurf;

szFilter = GetStringRes (IDS_FILE_LIST1);

GetSystemDirectory((LPSTR) szDirName, 256);
strcpy(szFile, "*.bmp\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = (LPSTR) NULL;
ofn.Flags = 0L;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = "BMP";

if (!GetOpenFileName(&ofn))
    return 0L;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if ((hViewSurf = pInfo->hwnd) == NULL)
    return 0L;

hTextWnd = pInfo->hTextWnd;
sprintf( gtext, GetStringRes (IDS_LOADING_BITMAPS));
SetWindowText(hTextWnd, gtext);

GetClientRect(hwnd, &rc);
hDC = GetDC(hViewSurf);
if (bStoreRleFile(hDC, pInfo, szFile))
    pInfo->RleData.ulFiles++;
ReleaseDC(hViewSurf, hDC);

bReleaseInfoData(hwnd);
return 0L;

    }

    case MM_RLESAVE: {
HDC             hDC;
OPENFILENAME    ofn;
char            szDirName[256];
char            szFile[256], szFileTitle[256];
static char     *szFilter;
HWND            hViewSurf;

szFilter = GetStringRes (IDS_FILE_LIST3);

GetSystemDirectory((LPSTR) szDirName, 256);
strcpy(szFile, "*.rle\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 0L;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = szDirName;
ofn.lpstrTitle = GetStringRes(IDS_SAVING_MEMORY_RLE);
ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = (LPSTR)NULL;

if (!GetSaveFileName(&ofn))
    return 0L;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if ((hViewSurf = pInfo->hwnd) == NULL)
    return 0L;

hTextWnd = pInfo->hTextWnd;
sprintf( gtext, GetStringRes (IDS_SAVING_LOADED_BMP));
SetWindowText(hTextWnd, gtext);

hDC = GetDC(hViewSurf);
bSaveRleFile(hDC, pInfo, szFile);
ReleaseDC(hViewSurf, hDC);

bReleaseInfoData(hwnd);
return 0L;
    }

    case MM_CLEAR: {
if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

hTextWnd = pInfo->hTextWnd;
sprintf( gtext,GetStringRes (IDS_DISCARD_LOADED_BMP));
SetWindowText(hTextWnd, gtext);

bFreeRleFile(pInfo);

bReleaseInfoData(hwnd);
return 0L;
    }

    case MM_RLEPLAY: {
HDC     hDC;
HWND    hViewSurf;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if ((hViewSurf = pInfo->hwnd) == NULL)
    return 0L;

hTextWnd = pInfo->hTextWnd;
sprintf( gtext, GetStringRes (IDS_PLAY_LOADED_BMP));
SetWindowText(hTextWnd, gtext);

hDC = GetDC(hViewSurf);
EnableMenuItem(hViewSubOne, MM_CLEAR, MF_GRAYED);

if (pInfo->hThrd0)
    CloseHandle(pInfo->hThrd0);

pInfo->hThrd0 = CreateThread(NULL, 0,
     (LPTHREAD_START_ROUTINE)bPlayRle,
     pInfo,
     STANDARD_RIGHTS_REQUIRED,
     &pInfo->dwThreadId );

if (pInfo->hThrd0)
{
    if (!SetThreadPriority(pInfo->hThrd0, THREAD_PRIORITY_BELOW_NORMAL))

OutputDebugString ("Can't set Priority!");
}

//bPlayRle(hDC, pInfo);
EnableMenuItem(hViewSubOne, MM_CLEAR, MF_ENABLED);
ReleaseDC(hViewSurf, hDC);

bReleaseInfoData(hwnd);
return 0L;
    }

    case MM_RLEPLAYCONT: {
HWND    hViewSurf;

if ((pInfo = pGetInfoData(hwnd)) == NULL) {
    return 0L;
}

if ((hViewSurf = pInfo->hwnd) == NULL)
    return 0L;

hTextWnd = pInfo->hTextWnd;
sprintf( gtext, GetStringRes (IDS_PLAY_BMP_CONT));
SetWindowText(hTextWnd, gtext);

if (pInfo->bFirstTime)
{
    if (!SetEvent(pInfo->hQuitEvent))
{
OutputDebugString ("Can't set Quit Event!");
    return 0L;
    }

    EnableMenuItem(hViewSubOne, MM_CLEAR, MF_GRAYED);

    if (pInfo->hThrd)
CloseHandle(pInfo->hThrd);

    pInfo->hThrd = CreateThread(NULL, 0,
 (LPTHREAD_START_ROUTINE)bPlayRleCont2,
 pInfo,
 STANDARD_RIGHTS_REQUIRED,
 &pInfo->dwThreadId );

    if (pInfo->hThrd) {
if (!SetThreadPriority(pInfo->hThrd, THREAD_PRIORITY_BELOW_NORMAL))
     OutputDebugString ("Can't set Priority!");
    }

    pInfo->bPlayRleCont = TRUE;
    pInfo->bFirstTime = FALSE;
    CheckMenuItem(hViewMenu, MM_RLEPLAYCONT, MF_CHECKED);
}
else
{
    if (pInfo->bPlayRleCont) {
EnableMenuItem(hViewSubOne, MM_CLEAR, MF_ENABLED);
CheckMenuItem(hViewMenu, MM_RLEPLAYCONT, MF_UNCHECKED);
pInfo->bPlayRleCont = FALSE;
pInfo->dwSuspend = SuspendThread(pInfo->hThrd);
    } else {
EnableMenuItem(hViewSubOne, MM_CLEAR, MF_GRAYED);
CheckMenuItem(hViewMenu, MM_RLEPLAYCONT, MF_CHECKED);
pInfo->bPlayRleCont = TRUE;
pInfo->dwSuspend = ResumeThread(pInfo->hThrd);
    }
    if (pInfo->dwSuspend == -1) {
(pInfo->bPlayRleCont ?
 sprintf( gtext,"Error in resuming thread\n") :
 sprintf( gtext,"Error in suspending thread\n")  );
OutputDebugString( gtext );
    }
}

bReleaseInfoData(hwnd);
return 0L;
    }

    default:
       return 0L;

  } //switch

} // WM_COMMAND
case WM_SETFOCUS:
    break;

case WM_MDIACTIVATE: {
    PINFO       pInfo;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    if ((HWND) lParam == hwnd) {
SendMessage(GetParent(hwnd), WM_MDISETMENU,
    (WPARAM)  hViewMenu,
    (LPARAM)  NULL) ;
DrawMenuBar(GetParent(GetParent(hwnd))) ;
    }

    bReleaseInfoData(hwnd);
    return 0L;
}
case WM_QUERYNEWPALETTE:
case WM_PALETTECHANGED: {
    PINFO       pInfo;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    SendMessage(pInfo->hwnd, message, wParam, lParam);
    bReleaseInfoData(hwnd);
    return 0L;
}

case WM_SIZE: {
    PINFO       pInfo;
    HWND        hTextWnd, hView;
    WORD        wCx;
    int         iCyText;

    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
break;
    }

    hTextWnd = pInfo->hTextWnd;
    hView    = pInfo->hwnd;
    bReleaseInfoData(hwnd);
    iCyText = GetWindowLong(hTextWnd, GWL_USERDATA);
    wCx = (WORD) (HIWORD(lParam) - iCyText);

    MoveWindow(hView, 0, 0,
   LOWORD(lParam),
   wCx,
   TRUE);

    MoveWindow(hTextWnd,
       0,
       wCx,
       LOWORD(lParam),
       iCyText,
       TRUE);

    break;
}

//
// display info in the status window
//
case WM_USER+0xa: {
    PINFO       pInfo;
    static ULONG ulClick = 0;
    HWND        hTextWnd;

    ulClick++;
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
return 0L;
    }

    hTextWnd = pInfo->hTextWnd;
    switch (ulClick % 4) {
case 0:
sprintf( gtext, GetStringRes (IDS_FRAMES));
break;
case 1:
sprintf( gtext, GetStringRes (IDS_FRAMES));
break;
case 2: sprintf( gtext, (pInfo->bPlayRleCont ?
               GetStringRes (IDS_CONT_PLAY) :
                       GetStringRes (IDS_SINGLE_PLAY)));
break;
case 3: sprintf( gtext,"");
break;
default: break;
    }
    SetWindowText(hTextWnd, gtext);
    bReleaseInfoData(hwnd);
    return 0L;
}

case WM_SYSCOMMAND: {
    LONG        lResult;

    EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnSuspendThrd, lParam);

    lResult = DefMDIChildProc(hwnd, message, wParam, lParam);

    EnumChildWindows(ghwndClient, (WNDENUMPROC)lpfnResumeThrd, lParam);

    return lResult;
    break;
}

case WM_CREATE: {
    PINFO           pInfo;
    HANDLE          hInfo;
    HWND            hTextWnd, hView;
    RECT            rcl;

    //
    // CR! MakeProcInstance is noop!
    //
    lpfnSuspendThrd = (FARPROC)MakeProcInstance (SuspendDrawThrd, ghModule);
    lpfnResumeThrd  = (FARPROC)MakeProcInstance (ResumeDrawThrd, ghModule);

    hTextWnd = CreateWindow("Text", NULL,
    WS_BORDER | SS_LEFT | WS_CHILD | WS_VISIBLE,
    0, 0, 0, 0,
    hwnd,
    (HMENU) 2,
    ghModule,
    NULL);

    GetClientRect(hwnd, &rcl);
    hView = CreateWindow("View", (LPSTR) NULL,
  WS_CHILD      | WS_VISIBLE     |
  WS_BORDER,
  0,0, rcl.right-rcl.left,
  rcl.bottom-rcl.top-GetWindowLong(hTextWnd, GWL_USERDATA),
  hwnd, (HMENU)1, ghModule, (LPVOID)NULL);

    SetWindowText(hTextWnd, GetStringRes (IDS_SELECT_DRAW_SET));
    hInfo = (HANDLE) ((LPMDICREATESTRUCT) ((LPCREATESTRUCT) lParam)->lpCreateParams)->lParam ;
    if (hInfo) {
if ((pInfo = (PINFO)LocalLock(hInfo)) == NULL)
{
    OutputDebugString ("Failed in LocalLock, hNode");
    break;
}
else
{
    HDC hDC;

    if (!GetClientRect(hwnd, &pInfo->rcClient))
OutputDebugString ("Failed in GetClientRect!");

    pInfo->hTextWnd = hTextWnd;
    pInfo->hwnd = hView;

    hDC = GetDC(hView);
    pInfo->hHTPal = CreateHalftonePalette(hDC);
    ReleaseDC(hView, hDC);

    //
    // Creating a signal quit play continuous event
    //
    if ((pInfo->hQuitEvent = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL)
OutputDebugString ("Failed in creating Quit Event!");

    SetWindowLong(hwnd, 0, (LONG) hInfo);
    LocalUnlock(hInfo);
}
    }
else
{
OutputDebugString ("Can't allocate hInfo!");
    }

    break;
}

case WM_CLOSE: {
    SendMessage(GetParent(hwnd), WM_MDISETMENU,
    (DWORD) hMenu,
    (LONG)  hSubMenuOne) ;
    DrawMenuBar(GetParent(GetParent(hwnd))) ;
    break;
}

case WM_DESTROY: {
    PINFO            pInfo;
    if ((pInfo = pGetInfoData(hwnd)) == NULL) {
break;
    }

    TerminateThread(pInfo->hThrd, (DWORD)0L);
    CloseHandle(pInfo->hThrd);

    //
    // Cleanup continuous play
    //
    if (!ResetEvent(pInfo->hQuitEvent))
OutputDebugString ("Failed in reseting quit event!");

    bFreeRleFile(pInfo);
    bReleaseInfoData(hwnd);
    LocalFree((HANDLE) GetWindowLong(hwnd, 0));
    break;
}

default:
    return DefMDIChildProc(hwnd, message, wParam, lParam);

    } //switch
    return DefMDIChildProc(hwnd, message, wParam, lParam);
}

/******************************Public*Routine******************************\
*
* ViewSurfWndProc
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG APIENTRY ViewSurfWndProc (HWND hwnd, UINT message, DWORD wParam, LONG lParam)
{
    switch (message)
    {
       case WM_CREATE: {
    break;
       }

       case WM_DESTROY: {
    break;
       }

       case WM_QUERYNEWPALETTE: {
     HWND        hParent;
     PINFO       pInfo;
     HDC         hDC;
     UINT        i;
     HPALETTE    hOldPal;

     if ((hParent=GetParent(hwnd)) == NULL)
 {
OutputDebugString ("Can't get hParent!");
return 0L;
     }
     if ((pInfo = pGetInfoData(hParent)) == NULL) {
  return 0L;
     }

     // If palette realization causes a palette change,
     // we need to do a full redraw.

     hDC = GetDC (hwnd);

     hOldPal = SelectPalette (hDC,
       pInfo->RleData.hPal,
       0);

     i = RealizePalette(hDC);

     SelectPalette (hDC, hOldPal, 0);
     ReleaseDC (hwnd, hDC);
     bReleaseInfoData(hParent);

     if (i) {
 InvalidateRect (hwnd, (LPRECT) (NULL), TRUE);
 return TRUE;
     } else
 return FALSE;

       }
       case WM_PALETTECHANGED: {
     HWND        hParent;
     PINFO       pInfo;
     HDC         hDC;
     UINT        i;
     HPALETTE    hOldPal;

     if ((hParent=GetParent(hwnd)) == NULL) {
 MessageBox(ghwndMain, "Can't get hParent!", "Error", MB_OK);
 return 0L;
     }
     if ((pInfo = pGetInfoData(hParent)) == NULL) {
  return 0L;
     }

     // if we were not responsible for palette change and if
     // palette realization causes a palette change, do a redraw.

     if ((HWND)wParam != hwnd){
hDC = GetDC (hwnd);

hOldPal = SelectPalette (hDC,
pInfo->RleData.hPal,
0);

i = RealizePalette (hDC);

if (i){
    UpdateColors (hDC);
}
SelectPalette (hDC, hOldPal, 0);
ReleaseDC (hwnd, hDC);
    }
    bReleaseInfoData(hParent);
    break;
       }

       case WM_PAINT:
 {
     PAINTSTRUCT ps;
     HDC         hDC;
     RECT        rc;

     GetClientRect(hwnd,&rc);
     hDC = BeginPaint(hwnd, &ps);
     EndPaint(hwnd, &ps);
     return 0L;
 }

    } // switch
    return DefWindowProc(hwnd, message, wParam, lParam);
}

/***************************************************************************\
* About
*
* About dialog proc.
*
\***************************************************************************/

BOOL CALLBACK About(
    HWND hDlg,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    switch (message) {
    case WM_INITDIALOG:
return TRUE;

    case WM_COMMAND:
if (wParam == IDOK)
    EndDialog(hDlg, wParam);
break;
    }

    return FALSE;

    UNREFERENCED_PARAMETER(lParam);
    UNREFERENCED_PARAMETER(hDlg);
}

/*************************************************************************
*
* TextWndProc
*
* Text Window proc.
*
\***************************************************************************/

LONG APIENTRY TextWndProc (HWND hwnd, UINT message, DWORD wParam, LONG lParam)
{
    static HFONT hFont = (HFONT) NULL;

    switch (message)
    {
    case WM_CREATE:
{
    LOGFONT    lf;
    HDC        hDC;
    HFONT      hOldFont;
    TEXTMETRIC tm;
    RECT       rect;
    LONG       lHeight;

    SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), (PVOID) &lf, FALSE);

    hDC = GetDC(hwnd);
    // this is the height for 8 point size font in pixels
    lf.lfHeight = 8 * GetDeviceCaps(hDC, LOGPIXELSY) / 72;

    hFont = CreateFontIndirect(&lf);
    hOldFont = SelectObject(hDC, hFont);
    GetTextMetrics(hDC, &tm);
    GetClientRect(GetParent(hwnd), &rect);

    // base the height of the window on size of text
    lHeight = tm.tmHeight+6*GetSystemMetrics(SM_CYBORDER)+2;
    // saved the height for later reference
    SetWindowLong(hwnd, GWL_USERDATA, lHeight);
    SetWindowPos(hwnd, NULL,
    0,
    rect.bottom-lHeight,
    rect.right-rect.left,
    lHeight,
    SWP_NOZORDER | SWP_NOMOVE);

    ReleaseDC(hwnd, hDC);
    break;
}

    case WM_LBUTTONDOWN: {
PostMessage(GetParent(hwnd), WM_USER+0xa, (DWORD)0L, (LONG)0L);
break;
    }

    case WM_DESTROY:
    if (hFont)
DeleteObject(hFont);
    break;

    case WM_SETTEXT:
    DefWindowProc(hwnd, message, wParam, lParam);
    InvalidateRect(hwnd,NULL,TRUE);
    UpdateWindow(hwnd);
    return 0L;

    case WM_PAINT:
{
    PAINTSTRUCT ps;
    RECT   rc;
    char   ach[128];
    int    len, nxBorder, nyBorder;
    HFONT  hOldFont = NULL;

    BeginPaint(hwnd, &ps);

    GetClientRect(hwnd,&rc);

    nxBorder = GetSystemMetrics(SM_CXBORDER);
    rc.left  += 9*nxBorder;
    rc.right -= 9*nxBorder;

    nyBorder = GetSystemMetrics(SM_CYBORDER);
    rc.top    += 3*nyBorder;
    rc.bottom -= 3*nyBorder;

    // 3D Text
    len = GetWindowText(hwnd, ach, sizeof(ach));
    SetBkColor(ps.hdc, GetSysColor(COLOR_BTNFACE));

    SetBkMode(ps.hdc, TRANSPARENT);
    SetTextColor(ps.hdc, RGB(64,96,96));
    if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder+2, rc.top+2, ETO_OPAQUE | ETO_CLIPPED,
&rc, ach, len, NULL);

    SetTextColor(ps.hdc, RGB(128,128,128));
    if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder+1, rc.top+1, ETO_CLIPPED,
&rc, ach, len, NULL);

    SetTextColor(ps.hdc, RGB(255,255,255));
    if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder, rc.top, ETO_CLIPPED,
&rc, ach, len, NULL);

    SetBkMode(ps.hdc, OPAQUE);

    if (hOldFont)
SelectObject(ps.hdc, hOldFont);

    EndPaint(hwnd, &ps);
    return 0L;
}
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

/**************************************************************************\
*
* JuliaWndProc
*
\**************************************************************************/

LONG APIENTRY JuliaWndProc (HWND hwnd, UINT message, DWORD wParam, LONG lParam)
{
    //
    // These statics are shared by all the Julia windows.  But, this is
    // fine because only one Julia window is tracking at any one time.
    // Ideally, we should place this in the per-window INFO data structure.
    // But, this is not necessary.
    //
    static BOOL    bTrack = FALSE;
    static int     OrgX, OrgY;
    static int     PrevX, PrevY;
    static HDC     hDC;
    static HCURSOR hCurArrow, hCurPaintCan;

    switch (message)
    {
       case WM_CREATE: {

    hpnRed     = CreatePen(PS_SOLID, 0, RGB(0xFF, 0,    0));
    hpnGreen   = CreatePen(PS_SOLID, 0, RGB(0,    0xFF, 0));
    hpnBlack   = CreatePen(PS_SOLID, 0, RGB(0,    0,    0));

    hCurPaintCan = LoadCursor(ghModule, MAKEINTRESOURCE(PAINTCURSOR));
    hCurArrow = LoadCursor(NULL, IDC_ARROW);
    break;
       }

       case WM_DESTROY: {
    HWND        hParent;
    PINFO       pInfo;

    DeleteObject(hpnRed);
    DeleteObject(hpnGreen);
    DeleteObject(hpnBlack);

    if ((hParent=GetParent(hwnd)) == NULL)
{
OutputDebugString ("Can't get hParent!");
break;
    }
    if ((pInfo = pGetInfoData(hParent)) == NULL) {
break;
    }

    // we might have open resource if we ever load bitmap in this wnd.
    bFreeRleFile(pInfo);
    bReleaseInfoData(hParent);
    break;
       }

       case WM_QUERYNEWPALETTE: {
     HWND        hParent;
     PINFO       pInfo;
     HDC         hDC;
     UINT        i;
     HPALETTE    hOldPal;

     if ((hParent=GetParent(hwnd)) == NULL)
 {
OutputDebugString ("Can't get hParent!");
return 0L;
     }
     if ((pInfo = pGetInfoData(hParent)) == NULL) {
  return 0L;
     }

     // If palette realization causes a palette change,
     // we need to do a full redraw.

     hDC = GetDC (hwnd);

     hOldPal = SelectPalette (hDC,
       ((pInfo->iStretchMode == HALFTONE) ? pInfo->hHTPal : pInfo->hPal),
       0);

     i = RealizePalette(hDC);
     SelectPalette (hDC, hOldPal, 0);
     ReleaseDC (hwnd, hDC);
     bReleaseInfoData(hParent);

     if (i) {
 InvalidateRect (hwnd, (LPRECT) (NULL), TRUE);
 return TRUE;
     } else
 return FALSE;

       }
       case WM_PALETTECHANGED: {
     HWND        hParent;
     PINFO       pInfo;
     HDC         hDC;
     UINT        i;
     HPALETTE    hOldPal;

     if ((hParent=GetParent(hwnd)) == NULL) {
OutputDebugString ("Can't get hParent!");
return 0L;
     }
     if ((pInfo = pGetInfoData(hParent)) == NULL) {
  return 0L;
     }

     // if we were not responsible for palette change and if
     // palette realization causes a palette change, do a redraw.

     if ((HWND)wParam != hwnd){
hDC = GetDC (hwnd);

hOldPal = SelectPalette (hDC,
       ((pInfo->iStretchMode == HALFTONE) ? pInfo->hHTPal : pInfo->hPal),
0);

i = RealizePalette (hDC);

if (i){
    UpdateColors (hDC);
}
SelectPalette (hDC, hOldPal, 0);
ReleaseDC (hwnd, hDC);
    }
    bReleaseInfoData(hParent);
    break;
       }

       case WM_PAINT:
 {
     PAINTSTRUCT ps;
     HWND        hParent;
     PINFO       pInfo;
     HDC         hDC;
     RECT        rc;

     GetClientRect(hwnd,&rc);
     hDC = BeginPaint(hwnd, &ps);
     EndPaint(hwnd, &ps);
     if ((hParent=GetParent(hwnd)) == NULL) {
OutputDebugString ("Can't get hParent!");
return 0L;
     }
     if ((pInfo = pGetInfoData(hParent)) == NULL) {
  return 0L;
}

     if (pInfo->hBmpSaved) {
hDC = GetDC(hwnd);
bDrawDIB(hDC, pInfo, 0, 0, rc.right, rc.bottom);
ReleaseDC(hwnd, hDC);
     }
     bReleaseInfoData(hParent);

     //EndPaint(hwnd, &ps);
     return 0L;
 }

       case WM_RBUTTONDOWN: {
 RECT   rc;
 HANDLE hParent;
 PINFO  pInfo;
 int    x, y;
 HWND   hJulia;
 HANDLE hTextWnd;

 x = (int) LOWORD(lParam);
 y = (int) HIWORD(lParam);
 if ((hParent=GetParent(hwnd)) == NULL) {
     OutputDebugString ("Can't get hParent!");
     break;
 }

 if ((pInfo = pGetInfoData(hParent)) == NULL) {
     break;
 }

 hTextWnd = pInfo->hTextWnd;

 if (GetWindowLong(GetParent(hwnd), GWL_STYLE) & WS_MAXIMIZE) {
     GetClientRect(ghwndMain, &rc);
     rc.bottom -= GetWindowLong(hTextWnd,GWL_USERDATA);
 } else {
     GetClientRect(hwnd, &rc);
 }

 if (pInfo->bMandel) {

     hJulia = (HWND) SendMessage(ghwndMain, WM_COMMAND,
 (DWORD)((WORD)MM_JULIA), 0L);
     if (hJulia) {
 //GetClientRect(hwnd, &rc);
 //
 // calculate the c value corresponding to the point
 //
 c1 = Xform((double) x, 0.0, (double) rc.right, pInfo->xFrom, pInfo->xTo);
 c2 = Xform((double) y, 0.0, (double) rc.bottom, pInfo->yFrom, pInfo->yTo);
 lc1 = XformFix(x, 0, rc.right, pInfo->lxFrom, pInfo->lxTo);
 lc2 = XformFix(y, 0, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

sprintf( gtext,"(c1 = %g, c2 = %g)\n", c1, c2);
OutputDebugString( gtext );

 //
 // Reset globals to default values for creating Julia set
 // (Entire set, not zoom in)
 //
 bResetGlobal();
 PostMessage(hJulia, WM_COMMAND, (DWORD)((WORD) MM_SET_XFORM_ATTR), 0L);
     } else {
 OutputDebugString ("Can't create hJulia!");
     }
 }
 bReleaseInfoData(hParent);
 break;
       }
       case WM_LBUTTONDOWN: {
 HANDLE hParent;
 PINFO  pInfo;
 int    x, y;
 DWORD  dwRGB;
 HBRUSH hBrOld;

 x = (int) LOWORD(lParam);
 y = (int) HIWORD(lParam);
 if ((hParent=GetParent(hwnd)) == NULL) {
     OutputDebugString ("Can't get hParent!");
     break;
 }

 if ((pInfo = pGetInfoData(hParent)) == NULL) {
     break;
 }

 if (pInfo->bFill) {
    hDC = GetDC(hwnd);
    hBrOld = SelectObject(hDC, pInfo->hBrush);

    dwRGB = GetPixel(hDC, x, y);

    ExtFloodFill(hDC, x, y, (COLORREF)dwRGB, FLOODFILLSURFACE);
    SelectObject(hDC, hBrOld);
    ReleaseDC(hwnd, hDC);
    pInfo->bFill = FALSE;

    if (pInfo->hBmpSaved)
DeleteObject(pInfo->hBmpSaved);

    pInfo->hBmpSaved = SaveBitmap(hwnd, pInfo->hPal);
    pInfo->bUseDIB = FALSE;

    SetCursor(hCurArrow);
 } else {
    bTrack = TRUE;
    OrgX = PrevX = x = LOWORD(lParam);
    OrgY = PrevY = y = HIWORD(lParam);

    hDC = GetDC(hwnd);
    SetCapture(hwnd);
 }
 bReleaseInfoData(hParent);
 break;
       }
       case WM_MOUSEMOVE: {
 RECT rectClient;
 int NextX;
 int NextY;
 HANDLE hParent;
 PINFO  pInfo;

 if ((hParent=GetParent(hwnd)) == NULL) {
     OutputDebugString ("Can't get hParent!");
     break;
 }

 if ((pInfo = pGetInfoData(hParent)) == NULL) {
     break;
 }

 if (pInfo->bFill) {
 SetCursor(hCurPaintCan);
 } else {
 SetCursor(hCurArrow);
 }

 bReleaseInfoData(hParent);

 // Update the selection region
 if (bTrack) {
     NextX = (SHORT) LOWORD(lParam);
     NextY = (SHORT) HIWORD(lParam);

     // Do not draw outside the window's client area

     GetClientRect (hwnd, &rectClient);
     if (NextX < rectClient.left) {
 NextX = rectClient.left;
     } else if (NextX >= rectClient.right) {
 NextX = rectClient.right - 1;
     }
     if (NextY < rectClient.top) {
 NextY = rectClient.top;
     } else if (NextY >= rectClient.bottom) {
 NextY = rectClient.bottom - 1;
     }
     if ((NextX != PrevX) || (NextY != PrevY)) {
SetROP2(hDC, R2_NOT);           // Erases the previous box

MoveToEx(hDC, OrgX, OrgY, NULL);
LineTo(hDC, OrgX, PrevY);
LineTo(hDC, PrevX, PrevY);
LineTo(hDC, PrevX, OrgY);
LineTo(hDC, OrgX, OrgY);
     // Get the current mouse position

PrevX = NextX;
PrevY = NextY;
MoveToEx(hDC, OrgX, OrgY, NULL);   // Draws the new box
LineTo(hDC, OrgX, PrevY);
LineTo(hDC, PrevX, PrevY);
LineTo(hDC, PrevX, OrgY);
LineTo(hDC, OrgX, OrgY);
     }
 }
 break;

       }

       case WM_LBUTTONUP: {
 RECT rc;
 HANDLE hParent;
 PINFO  pInfo;
 int NextX;
 int NextY;
 int iDistX, iDistY, iAbsDstX, iAbsDstY;
 HWND hZoom;
 HANDLE hTextWnd;

 if (!bTrack)
    break;

 // End the selection
    ReleaseCapture();
    bTrack = FALSE;

    MoveToEx(hDC, OrgX, OrgY, NULL);   // Erases the box
    LineTo(hDC, OrgX, PrevY);
    LineTo(hDC, PrevX, PrevY);
    LineTo(hDC, PrevX, OrgY);
    LineTo(hDC, OrgX, OrgY);

    NextX = LOWORD(lParam);
    NextY = HIWORD(lParam);

    iDistX = NextX - OrgX;
    iDistY = NextY - OrgY;
    iAbsDstX = (iDistX > 0 ? iDistX : -iDistX);
    iAbsDstY = (iDistY > 0 ? iDistY : -iDistY);
    if (iAbsDstX > iAbsDstY) {
NextY = OrgY + (iDistY > 0 ? iAbsDstX : -iAbsDstX);
    } else if (iAbsDstX < iAbsDstY) {
NextX = OrgX + (iDistX > 0 ? iAbsDstY : -iAbsDstY);
    }

    MoveToEx(hDC, OrgX, OrgY, NULL);   // Draws the new box
    LineTo(hDC, OrgX, NextY);
    LineTo(hDC, NextX, NextY);
    LineTo(hDC, NextX, OrgY);
    LineTo(hDC, OrgX, OrgY);

    SetROP2(hDC, R2_COPYPEN);

    ReleaseDC(hwnd, hDC);
    if ((hParent=GetParent(hwnd)) == NULL) {
OutputDebugString ("Can't get hParent!");
break;
    }

    if ((pInfo = pGetInfoData(hParent)) == NULL) {
break;
    }

    hTextWnd = pInfo->hTextWnd;
    sprintf(gtext, GetStringRes (IDS_MOUSE), OrgX, OrgY, NextX, NextY);

    //SetWindowText(hTextWnd, gtext);
    OutputDebugString(gtext);

    if (GetWindowLong(GetParent(hwnd), GWL_STYLE) & WS_MAXIMIZE) {

GetClientRect(ghwndMain, &rc);
rc.bottom -= GetWindowLong(hTextWnd,GWL_USERDATA);
sprintf(gtext, "(%d, %d), (%d, %d)\n", rc.left, rc.top, rc.right, rc.bottom);
//SetWindowText(hTextWnd, gtext);
OutputDebugString(gtext);

    } else {
GetClientRect(hwnd, &rc);
    }

    if ((OrgX == NextX) && (OrgY == NextY)) {
bReleaseInfoData(hParent);
break;
    }

    hZoom = (HWND) SendMessage(ghwndMain, WM_COMMAND,
       pInfo->bMandel ? (DWORD)((WORD)MM_MANDEL) : (DWORD)((WORD)MM_JULIA),
       0L);
    if (hZoom) {
//GetClientRect(hwnd, &rc);
xFrom = Xform((double) OrgX, 0.0, (double) rc.right, pInfo->xFrom, pInfo->xTo);
xTo   = Xform((double) NextX, 0.0, (double) rc.right, pInfo->xFrom, pInfo->xTo);
yFrom = Xform((double) OrgY, 0.0, (double) rc.bottom, pInfo->yFrom, pInfo->yTo);
yTo   = Xform((double) NextY, 0.0, (double) rc.bottom, pInfo->yFrom, pInfo->yTo);
lxFrom = XformFix(OrgX, 0, rc.right, pInfo->lxFrom, pInfo->lxTo);
lxTo   = XformFix(NextX, 0, rc.right, pInfo->lxFrom, pInfo->lxTo);
lyFrom = XformFix(OrgY, 0, rc.bottom, pInfo->lyFrom, pInfo->lyTo);
lyTo   = XformFix(NextY, 0, rc.bottom, pInfo->lyFrom, pInfo->lyTo);
if (!pInfo->bMandel) {
    c1 = pInfo->c1;
    c2 = pInfo->c2;
    lc1 = pInfo->lc1;
    lc2 = pInfo->lc2;
}
PostMessage(hZoom, WM_COMMAND, (DWORD)((WORD) MM_SET_XFORM_ATTR), 0L);
    } else {
OutputDebugString ("Can't create hZoom!");
    }
 bReleaseInfoData(hParent);
 break;
       } // case WM_LBUTTONUP
    } // switch
    return DefWindowProc(hwnd, message, wParam, lParam);
}

/******************************Public*Routine******************************\
*
* SuspendDrawThrd
*
* Effects: Enumerates all the MDI children.  Suspending the drawing thread
*          in those windows, if any.
*
* Warnings: assumes the MDI children has class name "ChildClass."
*
\**************************************************************************/

BOOL APIENTRY SuspendDrawThrd (HWND hwnd, LONG lParam) {
   HANDLE      hThrd;
   PINFO       pInfo;
   DWORD       dwSuspend;
   BOOL        bDrawing;
   char        sz[30];

   GetClassName(hwnd, sz, 15);
   if (strcmp(sz, "ChildClass") != 0)
return 1L;
       if ((pInfo = pGetInfoData(hwnd)) == NULL) {
   return 1L;
       }

       bDrawing = pInfo->bDrawing;
       hThrd    = pInfo->hThrd;
       bReleaseInfoData(hwnd);

       if (hThrd && bDrawing) {
   dwSuspend = SuspendThread(hThrd);
   sprintf( gtext,"\nSuspend: dwSuspend = %d, dwSuspend = %g\n", dwSuspend, dwSuspend);
   OutputDebugString( gtext );

   if (dwSuspend == -1) {
       sprintf( gtext,"Error in suspending thread\n");
       OutputDebugString( gtext );
   }
       }
   return 1L;
   UNREFERENCED_PARAMETER(lParam);
}

/******************************Public*Routine******************************\
*
* ResumeDrawThrd
*
* Effects: Enumerates all the MDI children.  Resuming the drawing thread
*          in those windows, if any.
*
* Warnings: Assumes the MDI children has class name "ChildClass." Also,
*           assumes the drawing has been suspended by SuspendDrawThrd.
*
\**************************************************************************/

BOOL APIENTRY ResumeDrawThrd  (HWND hwnd, LONG lParam) {
   HANDLE      hThrd;
   PINFO       pInfo;
   DWORD       dwSuspend;
   BOOL        bDrawing;
   char        sz[30];

   GetClassName(hwnd, sz, 15);
   if (strcmp(sz, "ChildClass") != 0)
return 1L;
      if ((pInfo = pGetInfoData(hwnd)) == NULL) {
  return 1L;
      }

      bDrawing = pInfo->bDrawing;
      hThrd    = pInfo->hThrd;
      bReleaseInfoData(hwnd);

      if (hThrd && bDrawing) {
  dwSuspend = ResumeThread(hThrd);
  sprintf( gtext,"Resume: dwSuspend = %d, dwSuspend = %g\n", dwSuspend, dwSuspend);
  OutputDebugString( gtext );

  if (dwSuspend == -1) {
      sprintf( gtext,"Error in resuming thread\n");
      OutputDebugString( gtext );
  }
      }
   return 1L;
   UNREFERENCED_PARAMETER(lParam);
}

/******************************Public*Routine******************************\
*
* lMul
*
* Effects: Fix point multiplication
*
* Warnings: 9.23 fix point representation used. This is only good for
*           multiplication in a limited range.  Will overflow.
*           CR! Will be implemented as macros in the future
*
\**************************************************************************/

LONG lMul(LONG l1, LONG l2)
{
    LONGLONG   lliTmp;
    LONG       lReturn;

    lliTmp = l1;
    lliTmp *= l2;
    lReturn = (LONG)(lliTmp >> 23);

    return(lReturn);
}

/******************************Public*Routine******************************\
*
* lDiv
*
* Effects: fix point division
*
* Warnings: 9.23 fix point representation used. This is only good for
*           division in a limited range.  Will overflow.
*           CR! Will be implemented as macros in the future
*
\**************************************************************************/

LONG lDiv(long l1, long l2)
{
    LONGLONG   lliTmp;
    LONG       lReturn;

    lliTmp  = (LONGLONG)l1;
    lliTmp  <<= 23;

    lReturn = (LONG)(lliTmp / (LONGLONG)l2);

return (lReturn);
}

/**************************************************************************\
*
* StartDrawFix
*                                            2
* Effects: Draw'g the Julia Set for Q (z) = z  + c, where z, c are complex
*                                    c
*          Fact:   |Q (0)| = |c| > 2, the orbit of 0 escapes immediately
*                    c
*          claim:  |z| >= |c| = 2 + l, l > 0 escapes under Q
*                                                           c
*          The Julia Set: Basin Boundaries algorithm.
*
* Warnings:
*
\**************************************************************************/
BOOL StartDrawFix(PINFO pInfo)
{
   DWORD       dwTick1;
   HDC         hDC;
   RECT        rc;

   int m, n, i, iPrev;
   int xCurr, yCurr;
   int iPen;

   LONG c1, c2;
   LONG x0, y0, x1, y1, x, y, z;

   int  iBand=1;
   int  iScan=0;
   int  iDiff=0;

#ifndef THRDONE
   INT      iNumClr;
#endif

   HDC          hDCMem;
   HBITMAP      hBitmap, hOldBitmap;

   iPen = pInfo->iPen + 1;
   iPrev = FIRST_PIXEL;    // a big value to signal the first pixel
   //iPrev = pInfo->iIteration + FIRST_PIXEL;
   c1 = pInfo->lc1;
   c2 = pInfo->lc2;

   pInfo->bMandel = FALSE;
   pInfo->bDrawing = TRUE;
   hDC = GetDC(pInfo->hwnd);

#ifndef THRDONE
    if ((iNumClr = iCreatePenFrPal(hDC, NULL, 0, &(pInfo->hPal))) != 0) {
sprintf( gtext,"iNumClr = %d\n", iNumClr);
OutputDebugString( gtext);

if ((pInfo->prghPen = (PVOID*) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(HPEN)*iNumClr)) == NULL) {
    OutputDebugString ("Failed in Memory Allocation for pInfo->prghPen!");
} else {
    if ((pInfo->iPen = iCreatePenFrPal(hDC, pInfo->prghPen, 0, &(pInfo->hPal))) == 0)
OutputDebugString ("Failed in creating pen!");
}
    }

   hDCMem = CreateCompatibleDC(hDC);

   SelectPalette(hDCMem, pInfo->hPal, FALSE);
   SelectPalette(hDC,    pInfo->hPal, FALSE);
   RealizePalette(hDC);
   iPen = pInfo->iPen + 1;
#endif

   GetClientRect(pInfo->hwnd, &rc);
   hBitmap = CreateCompatibleBitmap(hDC, (int)rc.right, (int)rc.bottom);
   hOldBitmap = SelectObject(hDCMem, hBitmap);
   yCurr = rc.top;

   dwTick1 = GetTickCount();
   iScan = (((iScan = BATCH / rc.right) == 0) ? 1 : iScan);

   for (n=rc.top; n<=rc.bottom; n+=pInfo->iStep, yCurr+=pInfo->iStep, iPrev = FIRST_PIXEL) {
       xCurr = rc.left;               // since LineTo excludes last point
       MoveToEx(hDCMem, 0, yCurr, NULL);
       y0 = XformFix(n, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

       for (m=rc.left; m<=rc.right; m++, xCurr++) {
   x0 = XformFix(m, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
   x = x0;
   y = y0;

   for (i=1; i<=pInfo->iIteration; i++) {
       x1 = lMul(x - y, x + y) + c1;    // Z = x1 + i x2

       y1 = (lMul(x, y) * 2) + c2;
       x = x1;
       y = y1;                          //    2       2     2 1/2     2
       z = lMul(x, x) + lMul(y, y);     // |Z|  = ((x1  + x2 )   ) > 2

       if (z > 33554432)
    break;
   }

   if (i != iPrev)
   {
      if (iPrev != FIRST_PIXEL)
      {
switch(iPrev)
{
   case 1:
   if (pInfo->prghPen)
  SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
   break;
   default:

       if (iPrev >= pInfo->iIteration)
   {
       SelectObject(hDCMem, hpnBlack);
       break;
       }
   if (pInfo->prghPen)
   SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[iPrev % iPen]);
       break;
}
iPrev = i;
LineTo(hDCMem,xCurr,yCurr);
      }
      else
 iPrev = i;     // remember the color for the first pixel
   }
       }
       switch(i) {
   case 1:
  if (pInfo->prghPen)
  SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
  break;
   default:
      if (iPrev >= pInfo->iIteration) {
      SelectObject(hDCMem, hpnBlack);
      break;
      }
  if (pInfo->prghPen)
 SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[i % iPen]);
      break;
       }

       LineTo(hDCMem,xCurr,yCurr);

       sprintf(gtext, GetStringRes (IDS_DRAWING), ((FLOAT)n/(FLOAT)rc.bottom)*100);

       SetWindowText(pInfo->hTextWnd, gtext);

       if (n >= iScan * iBand) {
    BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
    iBand++;
       }
   }

   if ((iDiff = iScan * iBand - rc.bottom) != 0) {
BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan-iDiff, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
   }

   sprintf(gtext, GetStringRes (IDS_DRAWING_DONE));

   SetWindowText(pInfo->hTextWnd, gtext);

   ReleaseDC(pInfo->hwnd, hDC);

   pInfo->dwElapsed = GetTickCount() - dwTick1;

   if (pInfo->hBmpSaved)
       DeleteObject(pInfo->hBmpSaved);

   pInfo->hBmpSaved = SelectObject(hDCMem, hOldBitmap);
   pInfo->bDrawing = FALSE;
#ifndef THRDONE
   if (pInfo->prghPen != NULL) {
       for (i = 0; i <= pInfo->iPen; i++) {
   DeleteObject((HPEN) (pInfo->prghPen)[i]);
       }
       GlobalFree(pInfo->prghPen);
   }
#endif
   DeleteDC(hDCMem);
   ExitThread(0);
   return TRUE;
}

/**************************************************************************\
*
* StartDraw
*                                            2
* Effects: Draw'g the Julia Set for Q (z) = z  + c, where z, c are complex
*                                    c
*          Fact:   |Q (0)| = |c| > 2, the orbit of 0 escapes immediately
*                    c
*          claim:  |z| >= |c| = 2 + l, l > 0 escapes under Q
*                                                           c
*          The Julia Set: Basin Boundaries algorithm.
*
* Warnings:
*
\**************************************************************************/
BOOL StartDraw(PINFO pInfo)
{
   DWORD       dwTick1;
   HDC         hDC;
   RECT        rc;

   int m, n, i, iPrev;
   int xCurr, yCurr;
   int iPen;

   double c1, c2;
   double x0, y0, x1, y1, x, y, z;

   int  iBand=1;
   int  iScan=0;
   int  iDiff=0;

   HDC          hDCMem;
   HBITMAP      hBitmap, hOldBitmap;

#ifndef THRDONE
   INT      iNumClr;
#endif

   iPen = pInfo->iPen + 1;
   iPrev = FIRST_PIXEL;    // a big value to signal the first pixel
   //iPrev = pInfo->iIteration + FIRST_PIXEL;
   c1 = pInfo->c1;
   c2 = pInfo->c2;

   pInfo->bMandel = FALSE;
   pInfo->bDrawing = TRUE;
   hDC = GetDC(pInfo->hwnd);

#ifndef THRDONE
    if ((iNumClr = iCreatePenFrPal(hDC, NULL, 0, &(pInfo->hPal))) != 0) {
sprintf( gtext,"iNumClr = %d\n", iNumClr);
OutputDebugString( gtext);

if ((pInfo->prghPen = (PVOID*) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(HPEN)*iNumClr)) == NULL) {
    OutputDebugString ("Failed in Memory Allocation for pInfo->prghPen!");
} else {
    if ((pInfo->iPen = iCreatePenFrPal(hDC, pInfo->prghPen, 0, &(pInfo->hPal))) == 0)
   OutputDebugString ("Failed in creating pen!");
}
    }

   hDCMem = CreateCompatibleDC(hDC);

   SelectPalette(hDCMem, pInfo->hPal, FALSE);
   SelectPalette(hDC,    pInfo->hPal, FALSE);
   RealizePalette(hDC);
   iPen = pInfo->iPen + 1;
#endif

   GetClientRect(pInfo->hwnd, &rc);
   hBitmap = CreateCompatibleBitmap(hDC, (int)rc.right, (int)rc.bottom);
   hOldBitmap = SelectObject(hDCMem, hBitmap);
   yCurr = rc.top;

   dwTick1 = GetTickCount();

   iScan = (((iScan = BATCH / rc.right) == 0) ? 1 : iScan);

   for (n=rc.top; n<=rc.bottom; n+=pInfo->iStep, yCurr+=pInfo->iStep, iPrev = FIRST_PIXEL) {
       xCurr = rc.left;               // since LineTo excludes last point
       MoveToEx(hDCMem, 0, yCurr, NULL);
       y0 = Xform((double) n, 0.0, (double) rc.bottom, pInfo->yFrom, pInfo->yTo);

       for (m=rc.left; m<=rc.right; m++, xCurr++) {
   x0 = Xform((double) m, 0.0, (double) rc.right, pInfo->xFrom, pInfo->xTo);
   x = x0;
   y = y0;

   for (i=1; i<=pInfo->iIteration; i++) {
       x1 = (x - y) * (x + y) + c1;     // Z = x1 + i x2

       y1 = 2 * x * y + c2;
       x = x1;
       y = y1;                          //    2       2     2 1/2     2
       z = x * x + y * y;               // |Z|  = ((x1  + x2 )   ) > 2

       if (z > 4.0)
    break;
   }

   if (i != iPrev)
   {
      if (iPrev != FIRST_PIXEL)
      {
switch(iPrev)
{
   case 1:
   if (pInfo->prghPen)
  SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
   break;
   default:

       if (iPrev >= pInfo->iIteration) {
       SelectObject(hDCMem, hpnBlack);
       break;
       }
   if(pInfo->prghPen)
           SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[iPrev % iPen]);
       break;
}
iPrev = i;
LineTo(hDCMem,xCurr,yCurr);
      }
      else
 iPrev = i;     // remember the color for the first pixel
   }
       }
       switch(i) {
  case 1:
  SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
  break;
  default:
      if (iPrev >= pInfo->iIteration) {
     SelectObject(hDCMem, hpnBlack);
     break;
      }
  if (pInfo->prghPen)
          SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[i % iPen]);
      break;
       }

       LineTo(hDCMem,xCurr,yCurr);

       sprintf(gtext, GetStringRes (IDS_DRAWING), ((FLOAT)n/(FLOAT)rc.bottom)*100);
       SetWindowText(pInfo->hTextWnd, gtext);

       if (n >= iScan * iBand) {
    BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
    iBand++;
       }
   }

   if ((iDiff = iScan * iBand - rc.bottom) != 0) {
BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan-iDiff, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
   }

   sprintf(gtext, GetStringRes(IDS_DRAWING_DONE));

   SetWindowText(pInfo->hTextWnd, gtext);

   ReleaseDC(pInfo->hwnd, hDC);

   pInfo->dwElapsed = GetTickCount() - dwTick1;

   if (pInfo->hBmpSaved)
       DeleteObject(pInfo->hBmpSaved);

   pInfo->hBmpSaved = SelectObject(hDCMem, hOldBitmap);
   pInfo->bDrawing = FALSE;
#ifndef THRDONE
   if (pInfo->prghPen != NULL) {
       for (i = 0; i <= pInfo->iPen; i++) {
   DeleteObject((HPEN) (pInfo->prghPen)[i]);
       }
       GlobalFree(pInfo->prghPen);
   }
#endif
   DeleteDC(hDCMem);

   ExitThread(0);
   return TRUE;
}

/**************************************************************************\
*
* StartDraw2
*                                            2
* Effects: Draw'g the Julia Set for Q (z) = z  + c, where z, c are complex
*
*          The Julia Set: Backward iteration algorithm.
*
* Warnings:
*
\**************************************************************************/

BOOL StartDraw2(PINFO pInfo)
{
   HDC         hDC;
   RECT        rc;
   int         m, n, i;
   double      c1, c2;
   double      x0, y0, w0, w1, pi, theta, r;

   pi = 22.0/7.0;
   c1 = 0.360284;
   c2 = 0.100376;
   x0 = 1.5;
   y0 = 1.5;

   hDC = GetDC(pInfo->hwnd);

   GetClientRect(pInfo->hwnd, &rc);
   for (i=0; i<=15000; i++) {
    w0 = x0 - c1;
    w1 = y0 - c2;
    sprintf( gtext,"w(%g, %g) xy(%g, %g)\n", w0, w1, x0, y0);
    OutputDebugString( gtext );

    if (w0 == 0.0) {
theta = pi/2;
sprintf( gtext,"(w0 == 0.0) theta = %g\n\n", theta);
OutputDebugString( gtext );

    } else {
if (w0 > 0.0) {
    theta = atan(w1/w0);
    sprintf( gtext,"(w0 > 0.0) theta = %g\n\n", theta);
    OutputDebugString( gtext );

} else { // w0 < 0.0
    theta = pi + atan(w1/w0);
    sprintf( gtext,"(w0 < 0.0) theta = %g\n\n", theta);
    OutputDebugString( gtext );
}
    }
    r = sqrt(w0 * w0 + w1 + w1);
    //theta = theta/2.0 + ((int) ((2.0*rand()/(RAND_MAX+1.0)))*pi);
    //theta = theta/2.0 + ((2.0*rand()/((double)RAND_MAX+1.0))*pi);
    r = sqrt(r);
    x0 = r*cos(theta);
    y0 = r*sin(theta);
    if (i > 50) {
m = Xform2(x0, pInfo->xFrom, pInfo->xTo, 0.0, (double) rc.right);
n = Xform2(y0, pInfo->yFrom, pInfo->yTo, 0.0, (double) rc.bottom);
SetPixel(hDC, m, n, 0x000000ff);
    }
   }

   ReleaseDC(pInfo->hwnd, hDC);

   ExitThread(0);
   return TRUE;
}

/**************************************************************************\
*
* StartMandelbrotFix
*                                            2
* Effects: Draw'g the Mandelbrot Set for Q (z) = z  + c, where z, c complex
*
* Warnings:
\**************************************************************************/
BOOL StartMandelbrotFix(PINFO pInfo)
{
   DWORD       dwTick1;
   HDC         hDC;
   RECT        rc;
   int         m, n, i, iPrev;
   int         xCurr, yCurr;
   int         iPen;

   LONG c1, c2;
   LONG x1, y1, x, y, r;
#ifndef THRDONE
   INT      iNumClr;
#endif

   HDC          hDCMem;
   HBITMAP      hBitmap, hOldBitmap;

   int  iBand=1;
   int  iScan=0;
   int  iDiff=0;

   iPen = pInfo->iPen + 1;
   pInfo->bMandel = TRUE;
   pInfo->bDrawing = TRUE;
   hDC = GetDC(pInfo->hwnd);

#ifndef THRDONE
    if ((iNumClr = iCreatePenFrPal(hDC, NULL, 0, &(pInfo->hPal))) != 0) {
sprintf( gtext,"iNumClr = %d\n", iNumClr);
OutputDebugString( gtext);

if ((pInfo->prghPen = (PVOID*) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(HPEN)*iNumClr)) == NULL) {
    OutputDebugString ("Failed in Memory Allocation for pInfo->prghPen!");
} else {
    if ((pInfo->iPen = iCreatePenFrPal(hDC, pInfo->prghPen, 0, &(pInfo->hPal))) == 0)
OutputDebugString ("Failed in creating pen!");
}
    }

   hDCMem = CreateCompatibleDC(hDC);

   SelectPalette(hDCMem, pInfo->hPal, FALSE);
   SelectPalette(hDC,    pInfo->hPal, FALSE);
   RealizePalette(hDC);
   iPen = pInfo->iPen + 1;
#endif

   GetClientRect(pInfo->hwnd, &rc);

   hBitmap = CreateCompatibleBitmap(hDC, (int)rc.right, (int)rc.bottom);
   hOldBitmap = SelectObject(hDCMem, hBitmap);

   dwTick1 = GetTickCount();
   yCurr = rc.top;

   iScan = (((iScan = BATCH / rc.right) == 0) ? 1 : iScan);

   for (n=rc.top; n<=rc.bottom; n+=pInfo->iStep, yCurr+=pInfo->iStep, iPrev = FIRST_PIXEL) {
       xCurr = rc.left;                   // since LineTo excludes last point
       MoveToEx(hDCMem, 0, yCurr, NULL);
       c2 = XformFix(n, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

       for (m=rc.left; m<=rc.right; m++, xCurr++) {
   c1 = XformFix(m, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
   x = c1;
   y = c2;

   for (i=1; i<=pInfo->iIteration; i++) {
       x1 = lMul(x - y, x + y) + c1;
       y1 = (lMul(x, y) * 2) + c2;
       r = lMul(x1, x1) + lMul(y1, y1);
       x = x1;
       y = y1;
       if (r > 33554432) {
   break;
       }
   }

   if (i != iPrev) {
       if (iPrev != FIRST_PIXEL) {
 switch(iPrev) {
   case 1:
   SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]); break;
   default:
       if (iPrev >= pInfo->iIteration) {
    SelectObject(hDCMem, hpnBlack);
    break;
       }
       SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[iPrev % iPen]);
       break;
 }
 iPrev = i;
 LineTo(hDCMem, xCurr, yCurr);
       }
       else
  iPrev = i;    // remember the color for the first pixel
   }
       }

       switch(i) {
   case 1:
       SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
       break;
   default:
       if (i >= pInfo->iIteration) {
    SelectObject(hDCMem, hpnBlack);
    break;
       }
       SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[i % iPen]);
       break;
       }
       LineTo(hDCMem,xCurr,yCurr);

       sprintf(gtext, GetStringRes (IDS_DRAWING), ((FLOAT)n/(FLOAT)rc.bottom)*100);

       SetWindowText(pInfo->hTextWnd, gtext);

       if (n >= iScan * iBand) {
    BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
    iBand++;
       }

   }

   if ((iDiff = iScan * iBand - rc.bottom) != 0) {
BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan-iDiff, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
   }

   sprintf(gtext, GetStringRes (IDS_DRAWING_DONE));

   SetWindowText(pInfo->hTextWnd, gtext);

   ReleaseDC(pInfo->hwnd, hDC);

   pInfo->dwElapsed = GetTickCount() - dwTick1;

   if (pInfo->hBmpSaved)
       DeleteObject(pInfo->hBmpSaved);

   pInfo->hBmpSaved = SelectObject(hDCMem, hOldBitmap);
   pInfo->bDrawing = FALSE;

#ifndef THRDONE
   if (pInfo->prghPen != NULL) {
       for (i = 0; i <= pInfo->iPen; i++) {
   DeleteObject((HPEN) (pInfo->prghPen)[i]);
       }
       GlobalFree(pInfo->prghPen);
   }
#endif

   DeleteDC(hDCMem);

   ExitThread(0);
   return TRUE;
}

/**************************************************************************\
*
* StartMandelbrot
*                                            2
* Effects: Draw'g the Mandelbrot Set for Q (z) = z  + c, where z, c complex
*
* Warnings:
*
\**************************************************************************/
BOOL StartMandelbrot(PINFO pInfo)
{
   DWORD       dwTick1;
   HDC         hDC;
   RECT        rc;
   int         m, n, i, iPrev;
   int         xCurr, yCurr;
   int         iPen;

#ifndef THRDONE
   INT      iNumClr;
   char     text[256];
#endif

   double c1, c2;
   double x1, y1, x, y, r;

   HDC          hDCMem;
   HBITMAP      hBitmap, hOldBitmap;

   int  iBand=1;
   int  iScan=0;
   int  iDiff=0;

   iPrev = 0;
   iPen = pInfo->iPen + 1;
   pInfo->bMandel = TRUE;
   pInfo->bDrawing = TRUE;
   hDC = GetDC(pInfo->hwnd);

#ifndef THRDONE
    if ((iNumClr = iCreatePenFrPal(hDC, NULL, 0, &(pInfo->hPal))) != 0) {
sprintf( gtext,"iNumClr = %d\n", iNumClr);
OutputDebugString( gtext);

if ((pInfo->prghPen = (PVOID*) GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(HPEN)*iNumClr)) == NULL) {
    OutputDebugString ("Failed in Memory Allocation for pInfo->prghPen!");
} else {
    if ((pInfo->iPen = iCreatePenFrPal(hDC, pInfo->prghPen, 1, &(pInfo->hPal))) == 0)
OutputDebugString ("Failed in creating pen!");
}
    }

   hDCMem = CreateCompatibleDC(hDC);

   SelectPalette(hDCMem, pInfo->hPal, FALSE);
   SelectPalette(hDC,    pInfo->hPal, FALSE);
   RealizePalette(hDC);
   iPen = pInfo->iPen + 1;
   wsprintf(text, "iPen = %d\n", iPen);
   //MessageBox(ghwndMain, text, "Error", MB_OK);
#endif

   GetClientRect(pInfo->hwnd, &rc);

   hBitmap = CreateCompatibleBitmap(hDC, (int)rc.right, (int)rc.bottom);
   hOldBitmap = SelectObject(hDCMem, hBitmap);

#if 0
 {
   int iWidth, i, j;

   iWidth = rc.right/iNumClr;

   for (i = 0; i < iNumClr; i++) {
       SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[i % iNumClr]);
       for (j = 0; j < iWidth; j++) {
    MoveToEx(hDCMem, i*iWidth+j, 0, NULL);
    LineTo(hDCMem, i*iWidth+j, rc.bottom);
       }

   }
 }
#endif

   dwTick1 = GetTickCount();

   iScan = (((iScan = BATCH / rc.right) == 0) ? 1 : iScan);

   yCurr = rc.top;
   for (n=rc.top; n<=rc.bottom; n+=pInfo->iStep, yCurr+=pInfo->iStep, iPrev = FIRST_PIXEL) {
       xCurr = rc.left;                   // since LineTo excludes last point
       MoveToEx(hDCMem, 0, yCurr, NULL);
       c2 = Xform((double) n, 0.0, (double) rc.bottom, pInfo->yFrom, pInfo->yTo);

       for (m=rc.left; m<=rc.right; m++, xCurr++) {
   c1 = Xform((double) m, 0.0, (double) rc.right, pInfo->xFrom, pInfo->xTo);
   x = c1;
   y = c2;

   for (i=1; i<=pInfo->iIteration; i++) {
       x1 = (x - y) * (x + y) + c1;
       y1 = 2 * x * y + c2;
       r = x1 * x1 + y1 * y1;
       x = x1;
       y = y1;
       if (r > 4.0) {
   break;
       }
   }

   if (i != iPrev) {
       if (iPrev != FIRST_PIXEL) {
 switch(iPrev) {
   case 1:
   SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
    break;
   default:
       if (iPrev >= pInfo->iIteration) {
    SelectObject(hDCMem, hpnBlack);
    break;
       }
       SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[iPrev % iPen]);
       break;
 }
 iPrev = i;
 LineTo(hDCMem, xCurr, yCurr);
       }
       else
  iPrev = i;    // remember the color for the first pixel
   }
       }

       switch(i) {
      case 1:
      SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[0]);
       break;
      default:
  if (i >= pInfo->iIteration) {
       SelectObject(hDCMem, hpnBlack);
       break;
  }
  SelectObject(hDCMem, (HPEN)(pInfo->prghPen)[i % iPen]);
  break;
       }

       LineTo(hDCMem,xCurr,yCurr);

       sprintf(gtext, GetStringRes (IDS_DRAWING), ((FLOAT)n/(FLOAT)rc.bottom)*100);

       SetWindowText(pInfo->hTextWnd, gtext);

       if (n >= iScan * iBand) {
    BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
    iBand++;
       }
   }

   if ((iDiff = iScan * iBand - rc.bottom) != 0) {
BitBlt(hDC, 0, iScan * (iBand-1), rc.right, iScan-iDiff, hDCMem, 0, iScan * (iBand-1), SRCCOPY);
   }

   sprintf(gtext, GetStringRes (IDS_DRAWING_DONE));

   SetWindowText(pInfo->hTextWnd, gtext);

   ReleaseDC(pInfo->hwnd, hDC);

   pInfo->dwElapsed = GetTickCount() - dwTick1;

   if (pInfo->hBmpSaved)
       DeleteObject(pInfo->hBmpSaved);

   pInfo->hBmpSaved = SelectObject(hDCMem, hOldBitmap);
   pInfo->bDrawing = FALSE;
#ifndef THRDONE
   if (pInfo->prghPen != NULL) {
       for (i = 0; i <= pInfo->iPen; i++) {
   DeleteObject((HPEN) (pInfo->prghPen)[i]);
       }
       GlobalFree(pInfo->prghPen);
   }
#endif

   DeleteDC(hDCMem);

   ExitThread(0);
   return TRUE;
}

/******************************Public*Routine******************************\
*
* Xform
*
* Effects: Given m, find x.
*
*          Xform(Pt) : Src |--> Dest
* eg.
*          Src |----|--------|      |-->   Dest |----|--------|
*              -2   x        2                  0    m       320
*
*          x = (m - 0)/(320 - 0) * (2 - -2) + -2
*
* Warnings: 1. This will become a macro in the future for speed.  For now,
*              it is here for debugging purposes.
*
\**************************************************************************/
#if 0
double Xform(
   double Pt,
   double SrcFrom,
   double SrcTo,
   double DestFrom,
   double DestTo)
{
 //sprintf( gtext,"%g = Xform(%g, %g, %g, %g, %g)\n", ((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom))+DestFrom, Pt, SrcFrom, SrcTo, DestFrom, DestTo);
 //OutputDebugString( gtext );
 return(((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom))+DestFrom);
}
#endif

/******************************Public*Routine******************************\
*
* Xform2
*
* Effects: Given x, find m.
*
*          Xform(Pt) : Src |--> Dest
* eg.
*          Src |----|--------|      |-->   Dest |----|--------|
*              -2   x        2                  0    m       320
*
*          m = (x - -2)/(2 - -2) * 320
*
* Warnings: 1. This will become a macro in the future for speed.  For now,
*              it is here for debugging purposes.
*
\**************************************************************************/
#if 0
int Xform2(
   double Pt,
   double SrcFrom,
   double SrcTo,
   double DestFrom,
   double DestTo)
{
 //sprintf( gtext,"%g = Xform(%g, %g, %g, %g, %g)\n", ((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom))+DestFrom, Pt, SrcFrom, SrcTo, DestFrom, DestTo);
 //OutputDebugString( gtext );
 return((int) ((Pt-SrcFrom)/(SrcTo-SrcFrom)*(DestTo-DestFrom)+DestFrom));
}
#endif

/******************************Public*Routine******************************\
*
* SaveBitmap
*
* Effects:  Returns the handle of a bitmap corresponding to the window.
*           using utilizing BitBlt.
*
* Warnings:
*   // should we set the dirty flag here?
*
*
\**************************************************************************/

HBITMAP SaveBitmap(HWND hWnd, HPALETTE hPal) {
    HDC     hdcMem, hDC;
    HBITMAP hBitmap, hOldBitmap;
    RECT    rc;
    HPALETTE hPalOld, hPalMemOld;
    int     ii;

    hDC = GetDC(hWnd);
    GetClientRect(hWnd, &rc);

    if (hPal) {

hPalOld = SelectPalette(hDC, hPal, FALSE);
ii=RealizePalette(hDC);
if (ii){
    UpdateColors (hDC);
}

    }

    hdcMem = CreateCompatibleDC(hDC);

    if (hPal) {

hPalMemOld = SelectPalette(hdcMem, hPal, FALSE);
RealizePalette(hdcMem);
    }

    hBitmap = CreateCompatibleBitmap(hDC, (int)rc.right-rc.left, (int)rc.bottom-rc.top);

    hOldBitmap = SelectObject(hdcMem, hBitmap);

    BitBlt(hdcMem, 0, 0, (int)rc.right-rc.left, (int)rc.bottom-rc.top, hDC, 0, 0, SRCCOPY);
    hBitmap = SelectObject(hdcMem, hOldBitmap);

    DeleteDC(hdcMem);
    ReleaseDC(hWnd, hDC);

    return(hBitmap);

}

/******************************Public*Routine******************************\
*
* DrawBitmap
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

void DrawBitmap(HDC hdc, PINFO pInfo, int xStart, int yStart, int cx, int cy) {
    BITMAP bm;
    HDC    hdcMem;
    POINT  ptSize, ptOrg;
    HPALETTE hPalOld, hPalMemOld;
    char    text[128];

    if ((pInfo->hPal) && !(pInfo->bUseMono)) {

hPalOld = SelectPalette(hdc,
     ((pInfo->iStretchMode == HALFTONE) ? pInfo->hHTPal : pInfo->hPal),
     FALSE);
//RealizePalette(hdc);

wsprintf(text, "Realized Palette = %d", RealizePalette(hdc));

OutputDebugString (text);

UpdateColors (hdc);
    }

    hdcMem = CreateCompatibleDC(hdc);

    if ((pInfo->hPal) && !(pInfo->bUseMono)) {

hPalMemOld = SelectPalette(hdcMem,
     ((pInfo->iStretchMode == HALFTONE) ? pInfo->hHTPal : pInfo->hPal),
      FALSE);
//RealizePalette(hdcMem);
wsprintf(text, "Realized Palette = %d", RealizePalette(hdcMem));
OutputDebugString (text);

UpdateColors (hdcMem);

    }

    SelectObject(hdcMem, (pInfo->bUseMono ? pInfo->hBmpMono : pInfo->hBmpSaved));
    SetMapMode(hdcMem, GetMapMode(hdc));

    GetObject((pInfo->bUseMono ? pInfo->hBmpMono : pInfo->hBmpSaved), sizeof(BITMAP), (LPSTR)&bm);
    ptSize.x = bm.bmWidth;
    ptSize.y = bm.bmHeight;
    DPtoLP(hdc, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;
    DPtoLP(hdcMem, &ptOrg, 1);

    if (pInfo->bStretch) {
SetStretchBltMode(hdc, pInfo->iStretchMode);
// cx+1 and cy+1: temporary work around for a bug!
StretchBlt(hdc, xStart, yStart, cx+1, cy+1,
       hdcMem, ptOrg.x, ptOrg.y, ptSize.x, ptSize.y,SRCCOPY);
    } else {
BitBlt(hdc, xStart, yStart, ptSize.x, ptSize.y,
       hdcMem, xStart, yStart, SRCCOPY);
    }

    if (pInfo->bUseMono)
pInfo->bUseMono = FALSE;

    DeleteDC(hdcMem);
    UNREFERENCED_PARAMETER(cx);
    UNREFERENCED_PARAMETER(cy);
}

/******************************Public*Routine******************************\
*
* bDrawDIB
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bDrawDIB(HDC hDC, PINFO pInfo, int xStart, int yStart, int cx, int cy)
{
    HBITMAP     hBmpOld, hBmp;
    BOOL        bSuccess;
    PBITMAPINFO pbmi;
    HDC         hdcMem;
    BITMAP      bm;

    bSuccess = TRUE;
    if (pInfo->bUseMono) {
DrawBitmap(hDC, pInfo, xStart, yStart, cx, cy);
return bSuccess;
    }

    if ((hBmp = pInfo->hBmpSaved) == NULL) {

MessageBox(ghwndMain, GetStringRes (IDS_NO_BITMAP_TO_DRAW),
       NULL, MB_OK);
return FALSE;
    }

    if (pInfo->bStretch) {
SetStretchBltMode(hDC, pInfo->iStretchMode);

if (pInfo->bUseDIB) { // this'll give a src for halftone...
    pbmi = (PBITMAPINFO) (pInfo->RleData.rgpbmi[0]);

    // Select and realize the appropriate palette to destination DC
    if ((pInfo->iStretchMode == HALFTONE) && (pInfo->hHTPal)) {
SelectPalette(hDC, pInfo->hHTPal, FALSE);
RealizePalette(hDC);
    } else {
if ((pInfo->iStretchMode != HALFTONE) && (pInfo->hPal)) {
    SelectPalette(hDC, pInfo->hPal, FALSE);
    RealizePalette(hDC);
}
    }
    if (pInfo->bCoreHdr) {
StretchDIBits(hDC, xStart, yStart, cx, cy,
  0, 0, ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcWidth,
((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcHeight,
  (LPSTR)pInfo->RleData.rgpjFrame[0], pbmi, DIB_RGB_COLORS, SRCCOPY);
    } else {
StretchDIBits(hDC, xStart, yStart, cx, cy,
  0, 0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
  (LPSTR)pInfo->RleData.rgpjFrame[0], pbmi, DIB_RGB_COLORS, SRCCOPY);
    }
} else { // image is dirty or created on this device

    HBITMAP     hDIB;

    //
    // Get the source DIB info from the DDB for halftoning

//
    hDIB = DIBfromDDB(hDC, hBmp, pInfo);

    hdcMem = CreateCompatibleDC(hDC);
    hBmpOld = SelectObject(hdcMem, hDIB);

    if ((pInfo->iStretchMode == HALFTONE) && (pInfo->hHTPal)) {
SelectPalette(hDC, pInfo->hHTPal, FALSE);
RealizePalette(hDC);
    } else {
if ((pInfo->iStretchMode != HALFTONE) && (pInfo->hPal)) {
    SelectPalette(hDC, pInfo->hPal, FALSE);
    RealizePalette(hDC);
}
    }

    //
    // Use StretchBlt here instead of StretchDIBits just for
    // demonstrating using the API with halftone
    //
    GetObject(hDIB, sizeof(BITMAP), (LPSTR)&bm);
    StretchBlt(hDC, xStart, yStart, cx, cy, hdcMem,
  0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);

    DeleteDC(hdcMem);

}
return bSuccess;
    }

    //
    // Not stretching...No halftone
    //

    GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bm);

    // Select and realize the appropriate palette to destination DC
    if (pInfo->hPal) {
SelectPalette(hDC, pInfo->hPal, FALSE);
RealizePalette(hDC);
    }

    if (pInfo->bUseDIB) { // this'll give a src
pbmi = (PBITMAPINFO) (pInfo->RleData.rgpbmi[0]);

// default to use SetDIBitsToDevice
SetDIBitsToDevice(hDC, xStart, yStart, bm.bmWidth, bm.bmHeight,
      0, 0, 0, bm.bmHeight, pInfo->RleData.rgpjFrame[0],
      pbmi, DIB_RGB_COLORS);
    } else { // image is dirty or created on this device

hdcMem = CreateCompatibleDC(hDC);
hBmpOld = SelectObject(hdcMem, hBmp);

BitBlt(hDC, xStart, yStart, bm.bmWidth, bm.bmHeight,
       hdcMem, 0, 0, SRCCOPY);

DeleteDC(hdcMem);
    }

    return bSuccess;
}

/******************************Public*Routine******************************\
*
* pGetInfoData(HWND hwnd)
*
* Effects:  calls LocalLock, returning pointer to Info structure.
*           assumes hwnd contains handle to Info structure at index 0.
*           should call bReleaseInfoData when done.
*           Global alert: ghwndMain used.
*
* Warnings:
*
\**************************************************************************/

PINFO pGetInfoData(HWND hwnd)
{
    HANDLE hInfo;
    PINFO  pInfo;

    hInfo = (HANDLE) GetWindowLong(hwnd, 0);
    if (hInfo == NULL) {
OutputDebugString ("Null Info handle retrieved - GetInfo");
return NULL;
    }

    if ((pInfo = (PINFO)LocalLock(hInfo)) == NULL) {
return NULL;
    }

    return pInfo;
}

/******************************Public*Routine******************************\
*
* bReleaseInfoData(HWND hwnd)
*
* Effects: calls LocalUnlock.  Returns whatever LocalUnlock returns.
*
* Warnings: assumes LocalLock was called previously.
*           assumes hwnd contains handle to Info structure at index 0.
*           Global alert: ghwndMain used.
*
\**************************************************************************/

BOOL bReleaseInfoData(HWND hwnd)
{
    HANDLE hInfo;

    hInfo = (HANDLE) GetWindowLong(hwnd, 0);
    if (hInfo == NULL) {
OutputDebugString ("Null Info handle retrieved - ReleaseInfo");
return FALSE;
    }

    return (LocalUnlock(hInfo));
}

/******************************Public*Routine******************************\
*
* bCheckMutexMenuItem
*
* Effects: Put a check mark on uiCheckItem in the hMenu.  Remove previous
*          check mark, if any, on items in the same group.  Returns TRUE
*          if successful, FALSE otherwise (item not exists).
*
* Warnings:
*
\**************************************************************************/

BOOL bCheckMutexMenuItem(PINFO pInfo, HMENU hMenu, UINT uiCheckItem)
{

    switch(uiCheckItem) {
case MM_TP_IDLE:
case MM_TP_LOW:
case MM_TP_BELOW_NORMAL:
case MM_TP_NORMAL:
case MM_TP_ABOVE_NORMAL:
case MM_TP_HIGH:
case MM_TP_TIME_CRITICAL:
    switch (pInfo->iPriority) {
case THREAD_PRIORITY_IDLE:
    CheckMenuItem(hMenu, MM_TP_IDLE, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_LOWEST:
    CheckMenuItem(hMenu, MM_TP_LOW, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_BELOW_NORMAL:
    CheckMenuItem(hMenu, MM_TP_BELOW_NORMAL, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_NORMAL:
    CheckMenuItem(hMenu, MM_TP_NORMAL, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_ABOVE_NORMAL:
    CheckMenuItem(hMenu, MM_TP_ABOVE_NORMAL, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_HIGHEST:
    CheckMenuItem(hMenu, MM_TP_HIGH, MF_UNCHECKED);
    break;
case THREAD_PRIORITY_TIME_CRITICAL:
    CheckMenuItem(hMenu, MM_TP_TIME_CRITICAL, MF_UNCHECKED);
    break;
default:
    break;
    }
    break;
case MM_FLOAT:
case MM_FIX:
    CheckMenuItem(hMenu, MM_FLOAT, MF_UNCHECKED);
    CheckMenuItem(hMenu, MM_FIX,   MF_UNCHECKED);
    break;
case MM_ITERATION_100:
case MM_ITERATION_500:
case MM_ITERATION_1000:
case MM_ITERATION_5000:
case MM_ITERATION_DOUBLE:
    switch (pInfo->iIteration) {
case 100:
    CheckMenuItem(hMenu, MM_ITERATION_100, MF_UNCHECKED);
    break;
case 500:
    CheckMenuItem(hMenu, MM_ITERATION_500, MF_UNCHECKED);
    break;
case 1000:
    CheckMenuItem(hMenu, MM_ITERATION_1000, MF_UNCHECKED);
    break;
case 5000:
    CheckMenuItem(hMenu, MM_ITERATION_5000, MF_UNCHECKED);
    break;
default:
    CheckMenuItem(hMenu, MM_ITERATION_DOUBLE, MF_UNCHECKED);
    break;
    }
    break;
case MM_STEP_ONE:
case MM_STEP_TWO:
case MM_STEP_THREE:
    switch (pInfo->iStep) {
case 1:
    CheckMenuItem(hMenu, MM_STEP_ONE, MF_UNCHECKED);
    break;
case 2:
    CheckMenuItem(hMenu, MM_STEP_TWO, MF_UNCHECKED);
    break;
default:
    CheckMenuItem(hMenu, MM_STEP_THREE, MF_UNCHECKED);
    break;
    }
    break;
case MM_STRETCHBLT:
case MM_BITBLT:
CheckMenuItem(hMenu, MM_STRETCHBLT,   MF_UNCHECKED);
CheckMenuItem(hMenu, MM_BITBLT,       MF_UNCHECKED);
    break;
case MM_BLACKONWHITE:
case MM_COLORONCOLOR:
case MM_WHITEONBLACK:
case MM_HALFTONE:
    switch (pInfo->iStretchMode) {
case BLACKONWHITE:
    CheckMenuItem(hMenu, MM_BLACKONWHITE, MF_UNCHECKED);
    break;
case COLORONCOLOR:
    CheckMenuItem(hMenu, MM_COLORONCOLOR, MF_UNCHECKED);
    break;
case WHITEONBLACK:
    CheckMenuItem(hMenu, MM_WHITEONBLACK, MF_UNCHECKED);
    break;
case HALFTONE:
    CheckMenuItem(hMenu, MM_HALFTONE, MF_UNCHECKED);
    break;
default:
    break;
    }
    break;
case MM_PORTRAIT:
case MM_LANDSCAPE:
CheckMenuItem(hMenu, MM_PORTRAIT,   MF_UNCHECKED);
CheckMenuItem(hMenu, MM_LANDSCAPE,  MF_UNCHECKED);
    break;
default:
    return FALSE;
    }
    CheckMenuItem(hMenu, uiCheckItem, MF_CHECKED);
    return TRUE;
}

/******************************Public*Routine******************************\
*
* vChkMenuItem
*
* Effects:  Helper function for checking or unchecking menu items based on
*           values set in the pInfo.  Gets called when processing
*           WM_MDIACTIVATE
*
* Warnings:
*
\**************************************************************************/

VOID vChkMenuItem(PINFO pInfo, HMENU hMenu, UINT uiFlag)
{
    (pInfo->bStretch ?
CheckMenuItem(hMenu, MM_STRETCHBLT, uiFlag) :
CheckMenuItem(hMenu, MM_BITBLT, uiFlag) );

    switch (pInfo->iPriority) {
case THREAD_PRIORITY_IDLE:
    CheckMenuItem(hMenu, MM_TP_IDLE, uiFlag);
    break;
case THREAD_PRIORITY_LOWEST:
    CheckMenuItem(hMenu, MM_TP_LOW, uiFlag);
    break;
case THREAD_PRIORITY_BELOW_NORMAL:
    CheckMenuItem(hMenu, MM_TP_BELOW_NORMAL, uiFlag);
    break;
case THREAD_PRIORITY_NORMAL:
    CheckMenuItem(hMenu, MM_TP_NORMAL, uiFlag);
    break;
case THREAD_PRIORITY_ABOVE_NORMAL:
    CheckMenuItem(hMenu, MM_TP_ABOVE_NORMAL, uiFlag);
    break;
case THREAD_PRIORITY_HIGHEST:
    CheckMenuItem(hMenu, MM_TP_HIGH, uiFlag);
    break;
case THREAD_PRIORITY_TIME_CRITICAL:
    CheckMenuItem(hMenu, MM_TP_TIME_CRITICAL, uiFlag);
    break;
default:
    break;
    }

    switch (pInfo->iStretchMode) {
case BLACKONWHITE:
    CheckMenuItem(hMenu, MM_BLACKONWHITE, uiFlag);
    break;
case COLORONCOLOR:
    CheckMenuItem(hMenu, MM_COLORONCOLOR, uiFlag);
    break;
case WHITEONBLACK:
    CheckMenuItem(hMenu, MM_WHITEONBLACK, uiFlag);
    break;
case HALFTONE:
    CheckMenuItem(hMenu, MM_HALFTONE, uiFlag);
    break;
default:
    break;
    }

    switch (pInfo->iStep) {
case 1:
    CheckMenuItem(hMenu, MM_STEP_ONE, uiFlag);
    break;
case 2:
    CheckMenuItem(hMenu, MM_STEP_TWO, uiFlag);
    break;
default:
    CheckMenuItem(hMenu, MM_STEP_THREE, uiFlag);
    break;
    }

    switch (pInfo->iIteration) {
case 100:
    CheckMenuItem(hMenu, MM_ITERATION_100, uiFlag);
    break;
case 500:
    CheckMenuItem(hMenu, MM_ITERATION_500, uiFlag);
    break;
case 1000:
    CheckMenuItem(hMenu, MM_ITERATION_1000, uiFlag);
    break;
case 5000:
    CheckMenuItem(hMenu, MM_ITERATION_5000, uiFlag);
    break;
default:
    CheckMenuItem(hMenu, MM_ITERATION_DOUBLE, uiFlag);
    break;
    }
    return;
}

/******************************Public*Routine******************************\
*
* bInitInfo
*
* Effects: Initialize the Info data structure
*
* Warnings:
*
\**************************************************************************/

BOOL bInitInfo(PINFO pInfo)
{
    pInfo->hParent      = ghwndClient;
    pInfo->xFrom        = -2.0;
    pInfo->xTo          = 1.0;
    pInfo->yFrom        = 1.5;
    pInfo->yTo          = -1.5;
    pInfo->lxFrom       = -16777216;                 // 9.23 fix point
    pInfo->lxTo         =  8388608;                  // representation of
    pInfo->lyFrom       =  12582912;                 // -2, 1.0, 1.5, and -1.5
    pInfo->lyTo         = -12582912;                 //
    pInfo->iIteration   = gIteration;
    pInfo->iStep        = gStep;
    pInfo->bStretch     = gbStretch;
    pInfo->iStretchMode = giStretchMode;
    wsprintf((LPSTR) &(pInfo->CaptionBarText), "");
    pInfo->hwnd         = NULL;
    pInfo->hTextWnd     = NULL;
    pInfo->rcClient.top    = 0;
    pInfo->rcClient.left   = 0;
    pInfo->rcClient.bottom = 0;
    pInfo->rcClient.right  = 0;
    pInfo->hdcClient    = NULL;
    pInfo->hRgnPath     = NULL;
    pInfo->hThrd0       = NULL;
    pInfo->hThrd        = NULL;
    pInfo->bDrawing     = FALSE;
    pInfo->dwThreadId   = 0;
    pInfo->dwElapsed    = 0L;
    pInfo->c1           = 0.0;
    pInfo->c2           = 0.0;
    pInfo->lc1          = 0L;
    pInfo->lc2          = 0L;
    pInfo->hBmpSaved    = NULL;
    pInfo->bSizeChng    = FALSE;
    pInfo->bMandel      = TRUE;
    pInfo->bSetDIBsToDevice = FALSE;
    pInfo->bFill        = FALSE;
    pInfo->hBrush       = NULL;
    pInfo->hQuitEvent   = NULL;
    pInfo->hCycleThrd   = NULL;
    pInfo->dwCycleThrdID = 0;
    pInfo->bClrCycle    = FALSE;
    pInfo->bFirstTime   = TRUE;
    pInfo->dwSuspend    = 0;
    pInfo->hBmpMono     = NULL;
    pInfo->bUseMono     = FALSE;
    pInfo->hPrtThrd     = NULL;
    pInfo->dwPrtThrdID  = 0;
    pInfo->hPal         = NULL;
    pInfo->hCyclePal    = NULL;
    pInfo->RleData.ulFiles  = 0;
    pInfo->RleData.ulFrames = 0;
    pInfo->bPlayRleCont = FALSE;
    pInfo->prghPen      = NULL;
    pInfo->iPen         = 0;
    pInfo->iPriority    = THREAD_PRIORITY_NORMAL;
    pInfo->bUseDIB      = FALSE;
    pInfo->bCoreHdr     = FALSE;

    return TRUE;
}

/******************************Public*Routine******************************\
*
* bResetGlobal
*
* Effects: Set (l) x/y From/To to their default values
*          Used for Julia set
*
* Warnings:
*
\**************************************************************************/

BOOL bResetGlobal(VOID)
{
    xFrom  =  -2.0;
    xTo    =   2.0;
    yFrom  =   2.0;
    yTo    =  -2.0;
    lxFrom =  -16777216;
    lxTo   =   16777216;
    lyFrom =   16777216;
    lyTo   =  -16777216;
    return TRUE;
}

/******************************Public*Routine******************************\
*
* hBrCreateBrush
*
* Effects: Creates a brush with the specified RGB
*
* Warnings:
*
\**************************************************************************/

HBRUSH hBrCreateBrush(HDC hDC, DWORD dwRGB)
{
    HDC hdcMem;
    HBRUSH hbr;
    HBRUSH hbrOld;
    HBITMAP hbmPat;
    HBITMAP hbmOld;

    hbr = CreateSolidBrush(dwRGB);
    hdcMem = CreateCompatibleDC(hDC);

    //
    // Minimum size for a bitmap to be used in a fill pattern is 8x8
    //
    hbmPat = CreateCompatibleBitmap(hDC, 8, 8);

    hbmOld = SelectObject(hdcMem, hbmPat);
    hbrOld = SelectObject(hdcMem, hbr);
    PatBlt(hdcMem, 0, 0, 8, 8, PATCOPY);

    //
    // Deselect hbmPat and hbr
    //
    SelectObject(hdcMem, hbmOld);
    SelectObject(hdcMem, hbrOld);

    DeleteDC(hdcMem);
    DeleteObject(hbr);

    hbr = CreatePatternBrush(hbmPat);

    DeleteObject(hbmPat);

    return hbr;
}

/******************************Public*Routine******************************\
*
* bPrintBmp
*
* Effects: A Thread routine for printing bitmap
*
* Warnings:
*
\**************************************************************************/

BOOL bPrintBmp(PPRTDATA pPrtData) {
    HDC         hdcPrinter;
    int         iWidth, iHeight;

#ifdef NEWPRTAPI
    DOCINFO     DocInfo;
#endif

    if (pPrtData->bUseDefault) {
  hdcPrinter = CreateDC( "", gpszPrinterNames[pPrtData->index],
 "", NULL);
    } else {
  hdcPrinter = CreateDC( "", gpszPrinterNames[pPrtData->index],
 "", &(pPrtData->DevMode));
    }

    if (!hdcPrinter)
    {
ExitThread(0);
return(FALSE);
    }

    iWidth = GetDeviceCaps(hdcPrinter, HORZRES);
    iHeight = GetDeviceCaps(hdcPrinter, VERTRES);

#ifdef NEWPRTAPI

    memset(&DocInfo, 0, sizeof(DOCINFO));
    DocInfo.cbSize      = sizeof(DOCINFO);
    DocInfo.lpszDocName = pPrtData->info.CaptionBarText;
    DocInfo.lpszOutput   = NULL;
    StartDoc(hdcPrinter, &DocInfo);
    StartPage(hdcPrinter);
    bDrawDIB(hdcPrinter, &(pPrtData->info), 0, 0, iWidth, iHeight);
    EndPage(hdcPrinter);
    EndDoc(hdcPrinter);

#else

    Escape(hdcPrinter, STARTDOC, 20, "Mandelbrot", NULL);
    bDrawDIB(hdcPrinter, &(pPrtData->info), 0, 0, iWidth, iHeight);
    Escape(hdcPrinter, NEWFRAME, 0, NULL, NULL);
    Escape(hdcPrinter, ENDDOC, 0, NULL, NULL);

#endif

    DeleteDC(hdcPrinter);
    ExitThread(0);
    return(TRUE);

}

/******************************Public*Routine******************************\
*
* StoreRleFile
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bStoreRleFile(HDC hDC, PINFO pInfo, PSTR pszFileName)
{
  BOOL                bSuccess;
  HANDLE              hFile, hMapFile;
  LPVOID              pMapFile;
  LPBITMAPINFOHEADER  pbmh;
  LPBITMAPINFO        pbmi;
  PBYTE               pjTmp, pjDIBits, pjRleBits;
  ULONG               sizBMI, sizImage, ulfSize;
  DWORD               dwOffBits;
  LONG                lScan;
  INT                 iNumClr;
  ULONG               ulFrames, ulFiles, ulOffset;
  PFILEINFO           pFileInfo;
  DWORD               dwFileSizeLow, dwFileSizeHigh;
  WORD                wBitCount;
  BOOL                bCoreHdr;

  bSuccess = TRUE;

  if ((hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
  OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) == (HANDLE)-1) {
      bSuccess = FALSE;
      goto ErrExit1;
  }

  dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
  if ((dwFileSizeLow == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) {
      bSuccess = FALSE;
      goto ErrExit2;
  }

  //
  // Create a map file of the opened file
  //
  if ((hMapFile = CreateFileMapping(hFile, NULL,
   PAGE_READONLY, 0, 0, NULL)) == (HANDLE)-1) {
      bSuccess = FALSE;
      goto ErrExit2;
  }

  //
  // Map a view of the whole file
  //
  if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0)) == NULL) {
      bSuccess = FALSE;
      goto ErrExit3;
  }

  ulFiles = pInfo->RleData.ulFiles;
  pFileInfo = &(pInfo->RleData.rgFileInfo[ulFiles]);
  pFileInfo->hFile = hFile;
  pFileInfo->hMapFile = hMapFile;
  pFileInfo->lpvMapView = pMapFile;

  ulFrames = pInfo->RleData.ulFrames;
  ulOffset = 0;

  while (TRUE) {
    //
    // First check that it is a bitmap file
    //
    if (*((PWORD)pMapFile) != 0x4d42) {              // 'BM'
OutputDebugString ("This is not a DIB bitmap file!");
bSuccess = FALSE;
goto ErrExit3;
    }
#ifdef DEBUG
    {
    static ULONG ulCnt=0;
    char buf[128];

    ulCnt++;
    wsprintf(buf, "ulCnt = %ld", ulCnt);
    OutputDebugString (buf);
    }
#endif
    //
    // Saving the bfSize field in the BITMAPFILEHEADER for incrementing to the
    // next frame.  bfSize does not start at DWORD boundary...
    //
    {
      PBYTE   pjTmp;
      FILEHDR fHdr, *pfHdr;
      ULONG   ulSiz;

      pjTmp = (PBYTE)pMapFile+2;
      pfHdr = &fHdr;

      ulSiz = sizeof(FILEHDR);
      while (ulSiz--) {
  *(((PBYTE)pfHdr)++) = *(((PBYTE)pjTmp)++);
      }
      ulfSize = fHdr.bfSize;
      dwOffBits = fHdr.bfOffbits;
    }

    //
    // advance pMapFile to point pass BITMAPFILEHEADER
    //
    //pMapFile = (PBYTE)pMapFile + sizeof(BITMAPFILEHEADER);

    //
    // Since the file header doesn't end on DWORD boundary...
    //
    pbmh = (LPBITMAPINFOHEADER)((PBYTE)pMapFile + sizeof(BITMAPFILEHEADER));

    {
BITMAPCOREHEADER bmch, *pbmch;
BITMAPINFOHEADER bmih, *pbmih;
PBYTE            pjTmp;
ULONG            ulSiz;

pbmch = &bmch;
pbmih = &bmih;

pjTmp = (PBYTE)pbmh;
ulSiz = sizeof(BITMAPCOREHEADER);
while (ulSiz--) {
    *(((PBYTE)pbmch)++) = *(((PBYTE)pjTmp)++);
}

pjTmp = (PBYTE)pbmh;
ulSiz = sizeof(BITMAPINFOHEADER);
while (ulSiz--) {
    *(((PBYTE)pbmih)++) = *(((PBYTE)pjTmp)++);
}

//
// Use the size to determine if it is a BitmapCoreHeader or
// BitmapInfoHeader
//
if (bmch.bcSize == sizeof(BITMAPCOREHEADER))
{
    wBitCount = bmch.bcBitCount;
    iNumClr = ((wBitCount == 24) ? 0 : (1 << wBitCount));
    sizBMI = sizeof(BITMAPCOREHEADER)+sizeof(RGBTRIPLE)*iNumClr;
    bCoreHdr = TRUE;
}
else            // BITMAPINFOHEADER
{
    wBitCount = bmih.biBitCount;
    switch (wBitCount) {
case 16:
case 32:
    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3;
    break;
case 24:
    sizBMI = sizeof(BITMAPINFOHEADER);
    break;
default:
    iNumClr = (1 << wBitCount);
    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*iNumClr;
    break;
    }
    bCoreHdr = FALSE;
}
    }

    if ((pbmi = (LPBITMAPINFO) LocalAlloc(LMEM_FIXED,sizBMI)) == NULL) {
bSuccess = FALSE;
goto ErrExit3;
    }

    //
    // Make sure we pass in a DWORD aligned BitmapInfo to CreateDIBitmap
    // Otherwise, exception on the MIPS platform
    // CR!!!  Equivalent to memcpy
    //
    pjTmp = (PBYTE)pbmi;

    while(sizBMI--)
    {
*(((PBYTE)pjTmp)++) = *(((PBYTE)pbmh)++);
    }

    //
    // assuming CreateDIBitmap() is doing a byte fetch...
    //
    pjDIBits = (PBYTE)pMapFile + dwOffBits;

    if (pbmi->bmiHeader.biCompression == BI_RGB)
    {
    //
    // Converting to RLE bits...
    //

OutputDebugString ("Converting to RLE format!");
lScan = pbmi->bmiHeader.biHeight;
sizImage = pbmi->bmiHeader.biSizeImage;

//
// select the palette into the DC first...
//
bSelectDIBPal(hDC, pInfo, pbmi, bCoreHdr);
if ((pInfo->hBmpSaved = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)pbmi,
    CBM_INIT, pjDIBits, pbmi, DIB_RGB_COLORS)) == NULL) {
    bSuccess = FALSE;
    goto ErrExit4;
}

// We want to retrieve the RLE format...
pbmi->bmiHeader.biCompression = ((wBitCount==4) ? BI_RLE4 : BI_RLE8);

pbmi->bmiHeader.biSizeImage = 0;
pbmi->bmiHeader.biXPelsPerMeter = 0;
pbmi->bmiHeader.biYPelsPerMeter = 0;
pbmi->bmiHeader.biClrUsed = 0;
pbmi->bmiHeader.biClrImportant = 0;

if (GetDIBits(hDC, pInfo->hBmpSaved, 0, lScan, NULL, pbmi,
    DIB_RGB_COLORS) == 0) {
     OutputDebugString ("Not all scans are converted!");
     bSuccess = FALSE;
     goto ErrExit4;
}

if (pbmi->bmiHeader.biSizeImage == 0) {
    if (sizImage == 0) {
OutputDebugString ("biSizeImage == 0!");
bSuccess = FALSE;
goto ErrExit4;
    }
    OutputDebugString ("Engine returns zero image size, making one up!");
    sizImage = (sizImage *3)/2;
} else {
    sizImage = pbmi->bmiHeader.biSizeImage;
}

if ((pjRleBits = (PBYTE) LocalAlloc(LMEM_FIXED, sizImage)) == NULL) {
     bSuccess = FALSE;
     goto ErrExit4;
}

pbmi->bmiHeader.biSizeImage = sizImage;
if (GetDIBits(hDC, pInfo->hBmpSaved, 0, lScan, pjRleBits, pbmi,
    DIB_RGB_COLORS) < lScan) {
     bSuccess = FALSE;
     goto ErrExit5;
}

SetDIBitsToDevice(hDC, 0, 0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
      0, 0, 0, lScan, pjRleBits, pbmi, DIB_RGB_COLORS);

pInfo->RleData.rgpjFrame[ulFrames] = pjRleBits;
    } else {
bSelectDIBPal(hDC, pInfo, pbmi, bCoreHdr);
lScan = pbmi->bmiHeader.biHeight;
if (bCoreHdr) {
   lScan = ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcHeight;
   SetDIBitsToDevice(hDC, 0, 0, ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcWidth, ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcHeight,
      0, 0, 0, lScan, pjDIBits, pbmi, DIB_RGB_COLORS);
} else {
   lScan = pbmi->bmiHeader.biHeight;
   SetDIBitsToDevice(hDC, 0, 0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
      0, 0, 0, lScan, pjDIBits, pbmi, DIB_RGB_COLORS);
}
pInfo->RleData.rgpjFrame[ulFrames] = pjDIBits;
    }

    pInfo->RleData.ulSize[ulFrames] = sizImage;
    pInfo->RleData.rgpbmi[ulFrames] = pbmi;

    //
    // Go to next frame
    //
    pMapFile = (PBYTE)pMapFile + ulfSize;
    ulOffset += ulfSize;
    ulFrames++;

    //
    // end condition
    //
    if (ulOffset >= dwFileSizeLow) {
break;
    }

  }

    //
    // set pbmi to the very first bmi
    //
    pInfo->RleData.pbmi = pInfo->RleData.rgpbmi[0];

    pInfo->RleData.hPal = pInfo->hPal;
    pInfo->RleData.ulFrames = ulFrames;
    return (bSuccess);

ErrExit5:
    LocalFree(pjRleBits);
ErrExit4:
    LocalFree(pbmi);
ErrExit3:
    CloseHandle(pFileInfo->hMapFile);
ErrExit2:
    CloseHandle(pFileInfo->hFile);
ErrExit1:
    return (bSuccess);

}

/******************************Public*Routine******************************\
*
* bSelectDIBPal
*
* Effects: Creates a logical palette from the DIB and select it into the DC
*          and realize the palette. Saving the hPal in the pInfo->hPal
*
* Warnings: Based on Windows NT DIB support.  If PM support 16,24,32 bpp
*           we need to modify this routine.
*
\**************************************************************************/

BOOL bSelectDIBPal(HDC hDC, PINFO pInfo, LPBITMAPINFO pbmi, BOOL bCoreHdr)
{
  LOGPALETTE    *plogPal;
  UINT          uiSizPal;
  INT           i, iNumClr;
  WORD          wBitCount;

  if (bCoreHdr) {
    wBitCount = ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcBitCount;
  } else {
    wBitCount = pbmi->bmiHeader.biBitCount;
  }

  switch (wBitCount) {
    case 16:
    case 24:
    case 32:                            // Does PM supports these?
return FALSE;
    default:
iNumClr = (1 << wBitCount);
break;
  }

  uiSizPal = sizeof(WORD)*2 + sizeof(PALETTEENTRY)*iNumClr;
  if ((plogPal = (LOGPALETTE *) LocalAlloc(LMEM_FIXED,uiSizPal)) == NULL) {
      pInfo->hPal = NULL;
      return FALSE;
  }

  plogPal->palVersion = 0x300;
  plogPal->palNumEntries = (WORD) iNumClr;

  if (bCoreHdr) {
    for (i=0; i<iNumClr; i++) {
plogPal->palPalEntry[i].peRed   = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtRed;
plogPal->palPalEntry[i].peGreen = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtGreen;
plogPal->palPalEntry[i].peBlue  = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtBlue;
plogPal->palPalEntry[i].peFlags = PC_RESERVED;
    }
  } else {
    for (i=0; i<iNumClr; i++) {
plogPal->palPalEntry[i].peRed   = pbmi->bmiColors[i].rgbRed;
plogPal->palPalEntry[i].peGreen = pbmi->bmiColors[i].rgbGreen;
plogPal->palPalEntry[i].peBlue  = pbmi->bmiColors[i].rgbBlue;
plogPal->palPalEntry[i].peFlags = PC_RESERVED;
    }
  }

  DeleteObject(pInfo->hPal);
  pInfo->hPal = CreatePalette((LPLOGPALETTE)plogPal);
  if ((pInfo->hPal) == NULL) {
      return FALSE;
  }

  if ((GetDeviceCaps(hDC, RASTERCAPS)) & RC_PALETTE) {
    SelectPalette(hDC, pInfo->hPal, FALSE);
    RealizePalette(hDC);
  }

  GlobalFree(plogPal);

  return TRUE;
}

/******************************Public*Routine******************************\
*
* bFreeRleFile
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bFreeRleFile(PINFO pInfo)
{
    ULONG               ulFiles;
    ULONG               ulFrames;
    ULONG               i;
    PFILEINFO           pFileInfo;

    ulFiles = pInfo->RleData.ulFiles;
    ulFrames = pInfo->RleData.ulFrames;

    for (i = 0; i < ulFrames; i++) {
LocalFree(pInfo->RleData.rgpjFrame[i]);
LocalFree(pInfo->RleData.rgpbmi[i]);
    }

    for (i = 0; i < ulFiles; i++) {
pFileInfo = &(pInfo->RleData.rgFileInfo[i]);
CloseHandle(pFileInfo->hFile);
CloseHandle(pFileInfo->hMapFile);
UnmapViewOfFile(pFileInfo->lpvMapView);
    }

    pInfo->RleData.ulFiles = 0;
    pInfo->RleData.ulFrames = 0;
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bPlayRle
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bPlayRle(PINFO pInfo)
{
    ULONG               ulFrames;
    ULONG               i;
    LPBITMAPINFO        pbmi;
    HDC                 hDC;
    HWND                hViewSurf;
    int                 ii;

//    RECT                rc;

    hDC = GetDC(hViewSurf=pInfo->hwnd);

    SelectPalette(hDC, pInfo->RleData.hPal, FALSE);
    ii=RealizePalette(hDC);
    if (ii){
UpdateColors (hDC);
    }

    ulFrames = pInfo->RleData.ulFrames;

    for (i = 0; i < ulFrames; i++) {
pbmi = pInfo->RleData.rgpbmi[i];
SetDIBitsToDevice(hDC,
  0, 0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
  0, 0, 0, pbmi->bmiHeader.biHeight,
  pInfo->RleData.rgpjFrame[i], pbmi, DIB_RGB_COLORS);

#if 0
GetClientRect(pInfo->hwnd, &rc);
StretchDIBits(hDC,
       0, 0, rc.right, rc.bottom,
       0, 0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
       (LPSTR)pInfo->RleData.rgpjFrame[i], pbmi, DIB_RGB_COLORS,
       SRCCOPY);
#endif
    }

    ReleaseDC(hViewSurf, hDC);

    return TRUE;
}

/******************************Public*Routine******************************\
*
* bSaveRleFile
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bSaveRleFile(HDC hDC, PINFO pInfo, PSTR pszFileName)
{
    int                 hFile;
    OFSTRUCT            ofReOpenBuff;
    BOOL                bSuccess;
    BITMAPFILEHEADER    bfh;
    ULONG               ulFrames;
    ULONG               i;
    LPBITMAPINFO        pbmi;
    ULONG               ulNumClr;
    ULONG               ulSize;

    bSuccess = TRUE;
    ulFrames = pInfo->RleData.ulFrames;

    if (ulFrames == 0) {
MessageBox(GetFocus(), GetStringRes (IDS_NO_RLE_TO_SAVE),
           NULL, MB_OK);
return FALSE;
    }

    // Let's open the file and get ready for writing
    if ((hFile = OpenFile(pszFileName, (LPOFSTRUCT)&ofReOpenBuff,
 OF_CREATE | OF_WRITE)) == -1) {
OutputDebugString ("Failed in OpenFile!");
return FALSE;
    }

    for (i = 0; i < ulFrames; i++) {

pbmi = pInfo->RleData.rgpbmi[i];

switch(pbmi->bmiHeader.biBitCount) {
    case 24:
ulSize = 0;
break;
    case 16:
    case 32:
ulSize = sizeof(DWORD)*3;
break;
    default:
ulNumClr = 1<<pbmi->bmiHeader.biBitCount;
ulSize = sizeof(RGBQUAD)*ulNumClr;
break;
}
#if 0
ulNumClr = ((pbmi->bmiHeader.biBitCount == 24)
       ? 0
       : (1<<pbmi->bmiHeader.biBitCount));

#endif
// fill in the info for the BitmapFileHeader
bfh.bfType    = 0x4D42;                            // 'BM'
bfh.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+
ulSize;
//sizeof(RGBQUAD)*ulNumClr;
bfh.bfSize    = bfh.bfOffBits + pbmi->bmiHeader.biSizeImage;
bfh.bfReserved1 =
bfh.bfReserved2 = 0;

// Write out the file header now
if (_lwrite(hFile, (LPSTR)&bfh, sizeof(BITMAPFILEHEADER)) == -1) {
    bSuccess = FALSE;
    goto ErrExit1;
}

// Now write out the BitmapInfoHeader and color table, if any
if (_lwrite(hFile, (LPSTR)pbmi, sizeof(BITMAPINFOHEADER) +
ulSize) == -1) {
//sizeof(RGBQUAD)*ulNumClr) == -1) {
    bSuccess = FALSE;
    goto ErrExit1;
}

// write the bits also
if (_lwrite(hFile, (LPSTR)pInfo->RleData.rgpjFrame[i],
   pbmi->bmiHeader.biSizeImage) == -1) {
    bSuccess = FALSE;
    goto ErrExit1;
}

    }

ErrExit1:
    _lclose(hFile);
    return bSuccess;
}

/******************************Public*Routine******************************\
*
* bPlayRleCont
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bPlayRleCont(HDC hDC, PINFO pInfo)
{
    BOOL    bQuit;
    MSG     msg;
    int     ii;

    bQuit = FALSE;

    SelectPalette(hDC, pInfo->RleData.hPal, FALSE);
    ii=RealizePalette(hDC);
    if (ii){
UpdateColors (hDC);
    }

    while (TRUE && !bQuit) {

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

    if ((msg.message == WM_QUIT) || (msg.message == WM_CLOSE) ||
((msg.message == WM_SYSCOMMAND) && (msg.wParam == SC_CLOSE))) {
bQuit = TRUE;
PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam);
    } else {
if (!TranslateAccelerator(msg.hwnd, ghAccel, &msg)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
   }
}

if (!pInfo->bPlayRleCont) {
    bQuit = TRUE;
}

if (!bQuit) {
    bPlayRle(pInfo);
}

    }
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bPlayRleCont2
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bPlayRleCont2(PINFO pInfo)
{
    HDC             hDC;
    HWND            hViewSurf;
    DWORD           dwWait;
    BOOL            bQuit;
    int             ii;

    hDC = GetDC(hViewSurf=pInfo->hwnd);

    SelectPalette(hDC, pInfo->RleData.hPal, FALSE);
    ii=RealizePalette(hDC);
    if (ii){
UpdateColors (hDC);
    }

    bQuit = FALSE;
    while (TRUE) {
//
// If parent gets a WM_CLOSE, we will return
//
dwWait = WaitForSingleObject(pInfo->hQuitEvent, 0);
if (dwWait == WAIT_TIMEOUT) {
MessageBox(ghwndMain,
           GetStringRes (IDS_PLAY_THREAD_END),
           GetStringRes (IDS_PLAY_THREAD),
           MB_OK);
    break;
}

if (!pInfo->bPlayRleCont) {
    bQuit = TRUE;
}

if (!bQuit)
    bPlayRle(pInfo);
    }

    ReleaseDC(hViewSurf, hDC);

    ExitThread(0);
    return TRUE;

}

/******************************Public*Routine******************************\
*
* DIBfromDDB
*
* Effects:  Call GetDIBits to retrieve the DIB info from DDB
*
* Warnings:
*
\**************************************************************************/

HBITMAP DIBfromDDB(HDC hDC, HBITMAP hBmp, PINFO pInfo)
{
    LPBITMAPINFO        pbmi;
    HBITMAP             hDIB;
    INT                 iBitCount, iNumClr;
    BITMAP              bm;
    DWORD               sizImage;
    PBYTE               pjBits;
    ULONG               sizBMI;
    LONG                lScan;

    iBitCount = GetDeviceCaps(hDC, BITSPIXEL);
    switch (iBitCount) {
case 16:
case 32:
    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3;
    break;
case 24:
    sizBMI = sizeof(BITMAPINFOHEADER);
    break;
default:
    iNumClr = (1 << iBitCount);
    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*iNumClr;
    break;
    }

    if ((pbmi = (LPBITMAPINFO) LocalAlloc(LMEM_FIXED,sizBMI)) == NULL) {
goto ErrExit1;
    }

    pbmi->bmiHeader.biSize = 0x28;              // GDI need this to work
    pbmi->bmiHeader.biBitCount = 0;             // don't get the color table
    pbmi->bmiHeader.biCompression = BI_RGB;
    pbmi->bmiHeader.biSizeImage = 0;
    pbmi->bmiHeader.biXPelsPerMeter = 0;
    pbmi->bmiHeader.biYPelsPerMeter = 0;
    pbmi->bmiHeader.biClrUsed = 0;
    pbmi->bmiHeader.biClrImportant = 0;

    GetObject(hBmp, sizeof(BITMAP), &bm);

    //
    // Important!  Select the correct palette corresponding to the DDB
    //
    SelectPalette(hDC, pInfo->hPal, FALSE);
    if (GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, pbmi,
DIB_RGB_COLORS) == 0) {
 goto ErrExit2;
    }

    sizImage = pbmi->bmiHeader.biSizeImage;
    if (sizImage == 0) {
goto ErrExit2;
    }

    if ((pjBits = (PBYTE) LocalAlloc(LMEM_FIXED, sizImage)) == NULL) {
 goto ErrExit2;
    }

    lScan = pbmi->bmiHeader.biHeight;

    if (GetDIBits(hDC, pInfo->hBmpSaved, 0, lScan, pjBits, pbmi,
DIB_RGB_COLORS) < lScan) {
 goto ErrExit3;
    }

    //
    // Saving the DIB...free memory when the windows is closed.
    //
    pInfo->RleData.rgpjFrame[0] = pjBits;
    pInfo->RleData.rgpbmi[0]    = pbmi;
    pInfo->RleData.pbmi         = (PBITMAPINFO) &(pInfo->RleData.rgpbmi[0]);
    pInfo->RleData.ulFrames     = 1;
    pInfo->RleData.ulFiles      = 1;

    // set flag to use original DIB as source for blting so HT can be done
    pInfo->bUseDIB = TRUE;

    pInfo->bCoreHdr = FALSE;

    // hDIB = CreateDIBitmap(hDC, NULL, CBM_INIT, pjBits, pbmi, DIB_RGB_COLORS);
    hDIB = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, &pjBits, NULL, 0);

    return hDIB;

ErrExit3:
    LocalFree(pjBits);
ErrExit2:
    LocalFree(pbmi);
ErrExit1:
    return ((HBITMAP)NULL);
}

//---------------------------------------------------------------------------
//
// FUNCTION:    GetStringRes (int id INPUT ONLY)
//
// COMMENTS:    Load the resource string with the ID given, and return a
//              pointer to it.  Notice that the buffer is common memory so
//              the string must be used before this call is made a second time.
//
//---------------------------------------------------------------------------

LPTSTR GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


JULIA.H

/******************************Module*Header*******************************\
* Module Name: julia.h
*
* Header file for Julia.c
*
* Created: 24-Oct-1991 18:15:12
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* Contains the #define values for the menu items' id and handy globals
*
* Dependencies:
*       none
*
\**************************************************************************/

#include <string.h>
#include <winspool.h>
#include <drivinit.h>
#include "jtypes.h"
#include "bndscan.h"
#include "dibmp.h"
#include "printer.h"

//
// A large number.  Used in all the fix-point versions of the fractal
// generation algorithms.
//
#define FIRST_PIXEL     429496796

//
// Threshold value (pixels) for blting drawing from shadow bitmap to
// screen.  Used in fractal drawing algorithm.  Minimize device access.
//
#define BATCH           25000

//
// menu item IDs
//
#define MM_ABOUT        8000
#define MM_JULIA8001
#define MM_MANDEL  8002
#define MM_RLEVIEWER    8006
#define MM_SAVE 8003
#define MM_SAVE_MONO    8004
#define MM_LOAD 8005

#define MM_CREATE_JULIA_THREAD7001
#define MM_SET_XFORM_ATTR7002
#define MM_CREATE_MANDEL_THREAD7003

#define MM_FLOAT        7009
#define MM_FIX          7010

#define MM_TP_IDLE              7030
#define MM_TP_LOW               7031
#define MM_TP_BELOW_NORMAL      7032
#define MM_TP_NORMAL            7033
#define MM_TP_ABOVE_NORMAL      7034
#define MM_TP_HIGH              7035
#define MM_TP_TIME_CRITICAL     7036
#define MM_ITERATION_100        7011
#define MM_ITERATION_500        7012
#define MM_ITERATION_1000       7013
#define MM_ITERATION_5000       7014
#define MM_ITERATION_DOUBLE     7015
#define MM_STEP_ONE             7016
#define MM_STEP_TWO             7017
#define MM_STEP_THREE           7018
#define MM_STRETCHBLT           7019
#define MM_BITBLT               7020
#define MM_BLACKONWHITE         7021
#define MM_COLORONCOLOR         7022
#define MM_WHITEONBLACK         7023
#define MM_HALFTONE             7024
#define MM_OPT_4                7025
#define MM_CLIP                 7026
#define MM_RM_CLIP              7027
#define MM_ERASE                7028
#define MM_SETDIB2DEVICE        7029

// defined in bndscan.h
//
// #define MM_SELCLIPRGN           7050

// defined in printer.h
//
// #define MM_PORTRAIT             7040
// #define MM_LANDSCAPE            7041
// #define MM_PRINTER              9000

#define MM_RLELOAD_DEMO     9800
#define MM_RLELOAD          9801
#define MM_RLESAVE          9802
#define MM_CLEAR            9803
#define MM_RLEPLAY          9804
#define MM_RLEPLAYCONT      9805

#define IDM_CASCADE30
#define IDM_TILE31
#define IDM_ARRANGE32
#define IDM_CLOSEALL33

//
// Resource IDs
//
#define ACCEL_ID        100
#define APPICON         1001
#define VIEWICON        1003
#define PAINTCURSOR     1002

//
// Handy globals
//
extern HPEN   hpnRed;
extern HPEN   hpnBlack;
extern HPEN   hpnGreen;
extern INT    giPen;

extern HANDLE ghModule;
extern HWND   ghwndMain;
extern HWND   ghwndClient;
extern HANDLE ghAccel;

extern HMENU  hMenu, hChildMenu, hViewMenu;
extern HMENU  hViewSubOne, hSubMenuOne, hSubMenuThree;
extern HMENU  hPrinterMenu;

extern CHAR   gszFile[20];
extern CHAR   gszMapName[20];
extern char   gtext[256];

extern BOOL   gFloat;
extern LONG   gStep;
extern LONG   gIteration;
extern BOOL   gbStretch;
extern INT    giStretchMode;
extern INT    giDmOrient;
extern INT    giNPrinters;

extern HPALETTE        ghPal, ghPalOld;

extern double xFrom, xTo, yFrom, yTo, c1, c2;
extern LONG   lxFrom, lxTo, lyFrom, lyTo, lc1, lc2;

extern PPRINTER_INFO_1     gpPrinters;
extern PSZ                *gpszPrinterNames;
extern PSZ                *gpszDeviceNames;

BOOL bCycle(HWND);
BOOL bCleanupPrinter(VOID);
INT  iCreatePenFrPal(HDC, PVOID *, INT, HPALETTE *);
BOOL bBoundaryScanFix(PINFO);
BOOL bChangeDIBColor(HDC, PINFO, INT);
BOOL bInitPrinter(HWND);
BOOL bCleanupPrinter(VOID);
BOOL SaveBitmapFile(HDC, HBITMAP, PSTR);
BOOL LoadBitmapFile(HDC, PINFO, PSTR);

HPALETTE CopyPalette(HPALETTE);

//void cdecl ErrorOut(char errstring[30]);
LPTSTR GetStringRes (int id);

#define IDS_NO_BITMAP               100
#define IDS_CYCLE_THREAD_QUIT       101
#define IDS_CYCLE_THREAD            102
#define IDS_IVALID_PEN_WIDTH        103
#define IDS_MANDEL_DREAM            104
#define IDS_JULIA                   105
#define IDS_MANDELBROT              106
#define IDS_VIEWER                  107
#define IDS_CLICK_HERE_VIEW         108
#define IDS_ITER_100                109
#define IDS_ITER_500                110
#define IDS_ITER_1000               111
#define IDS_ITER_5000               112
#define IDS_ITERATION               113
#define IDS_FILE_LIST1              114
#define IDS_FILE_LIST2              115
#define IDS_FILE_LIST3              116
#define IDS_SAVING_MONO_BITMAP      117
#define IDS_BOUNDARY                118
#define IDS_NO_SAVED_BITMAP         119
#define IDS_ELAPSED_TIME            120
#define IDS_STEP                    121
#define IDS_FLOATING_PT             122
#define IDS_FIXED_PT                123
#define IDS_SELECT_DRAW_SET         124
#define IDS_LOADING_BITMAPS         125
#define IDS_SAVING_MEMORY_RLE       126
#define IDS_SAVING_LOADED_BMP       127
#define IDS_DISCARD_LOADED_BMP      128
#define IDS_PLAY_LOADED_BMP         129
#define IDS_PLAY_BMP_CONT           130
#define IDS_FRAMES                  131
#define IDS_FILES                   132
#define IDS_CONT_PLAY               133
#define IDS_SINGLE_PLAY             134
#define IDS_MOUSE                   135
#define IDS_DRAWING                 136
#define IDS_DRAWING_DONE            137
#define IDS_NO_BITMAP_TO_DRAW       138
#define IDS_NO_RLE_TO_SAVE          139
#define IDS_PLAY_THREAD_END         140
#define IDS_PLAY_THREAD             141
#define IDS_ERR_NOT_A_DIB           142
#define IDS_ERR_CANT_ENUM_PRINTERS  143
#define IDS_NO_BITMAP_TO_SAVE       144
#define IDS_FILE_OPEN_FAILED        145
#define IDS_BITMAP_NOT_CREATED      146


LOADBMP.C

/******************************Module*Header*******************************\
* Module Name: loadbmp.c
*
* Contains function that loads a bitmap file
*
* Created: 08-Jan-1992 11:06:37
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* Contains the main routine for loading a DI bitmap file.
*
* Dependencies:
*
*   (#defines)
*   (#includes)
*       #include <windows.h>
*       #include "jtypes.h"
*
\**************************************************************************/
#include <windows.h>
#include "julia.h"

extern HWND ghwndMain;
BOOL bSelectDIBPal(HDC, PINFO, LPBITMAPINFO, BOOL);
BOOL bFreeRleFile(PINFO);
BOOL LoadBitmapFile(HDC, PINFO, PSTR);

/******************************Public*Routine******************************\
*
* LoadBitmapFile
*
* Effects:  Loads the bitmap from file and put into pInfo->hBmpSaved
*
* Warnings: pszFileName contains the full path
*
\**************************************************************************/

BOOL LoadBitmapFile(HDC hDC, PINFO pInfo, PSTR pszFileName)
{
    BOOL            bSuccess;
    HANDLE          hFile, hMapFile;
    LPVOID          pMapFile;
    LPBITMAPINFOHEADER pbmh;
    LPBITMAPINFO    pbmi;
    PBYTE           pjTmp;
    ULONG           sizBMI;
    INT             iNumClr;
    BOOL            bCoreHdr;
    PFILEINFO       pFileInfo;

    bSuccess = TRUE;

    if ((hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) == (HANDLE)-1)
{
        OutputDebugString("Fail in file open");
        bSuccess = FALSE;
        goto ErrExit1;
    }

    //
    // Create a map file of the opened file
    //
    if ((hMapFile = CreateFileMapping(hFile, NULL,
                             PAGE_READONLY, 0, 0, NULL)) == (HANDLE)-1)
{
        OutputDebugString("Fail in creating map file");
        bSuccess = FALSE;
        goto ErrExit2;

    }

    //
    // Map a view of the whole file
    //
    if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0)) == NULL)
{
        OutputDebugString("Fail in mapping view of the Map File object");
        bSuccess = FALSE;
        goto ErrExit3;
    }

    //
    // Saving the DIB file handle, etc in pInfo...
    // freeing existing objects, if any
    //
    bFreeRleFile(pInfo);
    pFileInfo = &(pInfo->RleData.rgFileInfo[0]);
    pFileInfo->hFile      = hFile;
    pFileInfo->hMapFile   = hMapFile;
    pFileInfo->lpvMapView = pMapFile;

    //
    // First check that it is a bitmap file
    //
    if (*((PWORD)pMapFile) != 0x4d42)  // 'BM'
{
        MessageBox(ghwndMain,
       GetStringRes (IDS_ERR_NOT_A_DIB),
   NULL, MB_OK);
        bSuccess = FALSE;
        goto ErrExit3;
    }

    //
    // The file header doesn't end on DWORD boundary...
    //
    pbmh = (LPBITMAPINFOHEADER)((PBYTE)pMapFile + sizeof(BITMAPFILEHEADER));

    {
        BITMAPCOREHEADER bmch, *pbmch;
        BITMAPINFOHEADER bmih, *pbmih;
        PBYTE            pjTmp;
        ULONG            ulSiz;

        pbmch = &bmch;
        pbmih = &bmih;

        pjTmp = (PBYTE)pbmh;
        ulSiz = sizeof(BITMAPCOREHEADER);
        while (ulSiz--) {
            *(((PBYTE)pbmch)++) = *(((PBYTE)pjTmp)++);
        }

        pjTmp = (PBYTE)pbmh;
        ulSiz = sizeof(BITMAPINFOHEADER);
        while (ulSiz--) {
            *(((PBYTE)pbmih)++) = *(((PBYTE)pjTmp)++);
        }

        //
        // Use the size to determine if it is a BitmapCoreHeader or
        // BitmapInfoHeader
        //
        // Does PM supports 16 and 32 bpp? How?
        //
        if (bmch.bcSize == sizeof(BITMAPCOREHEADER))
        {
            WORD wBitCount;

            wBitCount = bmch.bcBitCount;
            iNumClr = ((wBitCount == 24) ? 0 : (1 << wBitCount));
            sizBMI = sizeof(BITMAPCOREHEADER)+sizeof(RGBTRIPLE)*iNumClr;
            bCoreHdr = TRUE;
        }
        else            // BITMAPINFOHEADER
        {
            WORD wBitCount;

            wBitCount = bmih.biBitCount;
            switch (wBitCount) {
                case 16:
                case 32:
                    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3;
                    break;
                case 24:
                    sizBMI = sizeof(BITMAPINFOHEADER);
                    break;
                default:
                    iNumClr = (1 << wBitCount);
                    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*iNumClr;
                    break;
            }
            bCoreHdr = FALSE;
        }

    }

    if ((pbmi = (LPBITMAPINFO) LocalAlloc(LMEM_FIXED,sizBMI)) == NULL)
{
        bSuccess = FALSE;
        goto ErrExit3;
    }

    //
    // Make sure we pass in a DWORD aligned BitmapInfo to CreateDIBitmap
    // Otherwise, exception on the MIPS platform
    // CR!!!  Equivalent to memcpy
    //
    pjTmp = (PBYTE)pbmi;

    while(sizBMI--)
    {
        *(((PBYTE)pjTmp)++) = *(((PBYTE)pbmh)++);
    }

    //
    // assuming CreateDIBitmap() is doing a byte fetch...
    //
    pMapFile = (PBYTE)pMapFile + ((BITMAPFILEHEADER *)pMapFile)->bfOffBits;

    //
    // Select the palette into the DC first before CreateDIBitmap()
    //
    bSelectDIBPal(hDC, pInfo, pbmi, bCoreHdr);
    if ((pInfo->hBmpSaved = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)pbmi,
                        CBM_INIT, pMapFile, pbmi, DIB_RGB_COLORS)) == NULL)
{
        OutputDebugString("Fail in creating DIB bitmap from file!");
        bSuccess = FALSE;
        goto ErrExit4;
    }

    //
    // Saving the DIB...free memory when the windows is closed.
    //
    pInfo->RleData.rgpjFrame[0] = pMapFile;
    pInfo->RleData.rgpbmi[0]    = pbmi;
    pInfo->RleData.pbmi         = (PBITMAPINFO) &(pInfo->RleData.rgpbmi[0]);
    pInfo->RleData.ulFrames     = 1;
    pInfo->RleData.ulFiles      = 1;

    // set flag to use original DIB as source for blting so HT can be done
    pInfo->bUseDIB = TRUE;

    pInfo->bCoreHdr = bCoreHdr;

    return (bSuccess);

ErrExit4:
    LocalFree(pbmi);
ErrExit3:
    CloseHandle(hMapFile);
ErrExit2:
    CloseHandle(hFile);
ErrExit1:

    return (bSuccess);

}


PRINTER.C

/******************************Module*Header*******************************\
* Module Name: printer.c
*
* Contains functions for enermerating printers
*
* Created: 16-Apr-1992 11:19:00
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* Before printing the bInitPrinter is called for enumerating printers
* and doing the setup for printing
*
* When Mandelbrot Dream exits, bCleanupPrinter is called to free up
* memory
*
* Dependencies:
*
*   (#defines)
*   (#includes)
*
\**************************************************************************/
#include <windows.h>
#include <winspool.h>
#include <drivinit.h>
#include "julia.h"
#include "printer.h"

//
// Globals for printing
//
PPRINTER_INFO_1     gpPrinters       = NULL;
PSZ                *gpszPrinterNames = NULL;
PSZ                *gpszDeviceNames  = NULL;

extern HMENU  hPrinterMenu;
extern INT    giNPrinters;
extern HWND   ghwndMain;

BOOL bInitPrinter(HWND);
BOOL bCleanupPrinter(VOID);

/******************************Public*Routine******************************\
*
* bInitPrinter
*
* Effects: Enumerating printers...
*
* Warnings: Globals alert!!
*
\**************************************************************************/

BOOL bInitPrinter(HWND hwnd) {
    BOOL        bSuccess;
    DWORD       cbPrinters;
    DWORD       cbNeeded, cReturned, j;
    int         i;

    bSuccess = TRUE;
    cbPrinters = 4096L;

    if (!(gpPrinters = (PPRINTER_INFO_1)LocalAlloc((LMEM_FIXED | LMEM_ZEROINIT),
                                                  cbPrinters)))
    {
        OutputDebugString( "InitPrint: LocalAlloc for gpPrinters failed.");
        return (FALSE);
    }

    if (!EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 1, (LPBYTE)gpPrinters,
                      cbPrinters, &cbNeeded, &cReturned))
    {
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            LocalFree((LOCALHANDLE)gpPrinters);
            gpPrinters = (PPRINTER_INFO_1)LocalAlloc((LMEM_FIXED | LMEM_ZEROINIT),
                                               cbNeeded);
            cbPrinters = cbNeeded;

            if (!EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 1, (LPBYTE)gpPrinters,
                              cbPrinters, &cbNeeded, &cReturned))
            {
                MessageBox(ghwndMain,
       GetStringRes (IDS_ERR_CANT_ENUM_PRINTERS),
       NULL, MB_OK);
                return (FALSE);
            }

        }
        else
        {
            MessageBox(ghwndMain,
       GetStringRes (IDS_ERR_CANT_ENUM_PRINTERS),
   NULL, MB_OK);
            return (FALSE);
        }
    }

    // allocate some memory.

    gpszPrinterNames = (PSZ *)LocalAlloc((LMEM_FIXED | LMEM_ZEROINIT),
                                        cReturned * (DWORD)sizeof(PSZ));

    gpszDeviceNames = (PSZ *)LocalAlloc((LMEM_FIXED | LMEM_ZEROINIT),
                                        cReturned * (DWORD)sizeof(PSZ));

    if (giNPrinters != 0) {
        for (i = 0; i < giNPrinters; i++) {
            RemoveMenu(hPrinterMenu, 3, MF_BYPOSITION);
        }
        giNPrinters = 0;
    }

    // insert each printer name into the menu.

    j = giNPrinters = cReturned;
    for (i = 0; i < (INT) cReturned; i++)
    {
        // insert into menu from bottom up.

        j--;
        InsertMenu(hPrinterMenu, 4, MF_BYCOMMAND | MF_STRING,
                   MM_PRINTER + i, (LPSTR)gpPrinters[j].pName);

        // save a list of printer names, so we can associate them
        // with their menu indices later.

        gpszPrinterNames[i] = gpPrinters[j].pName;
        gpszDeviceNames[i] = gpPrinters[j].pDescription;
    }
#if 0
    //
    // Use this if this is called in the MDI child instead
    //
    DrawMenuBar(GetParent(GetParent(hwnd)));
#endif
    //
    // Use this instead if this is called in InitializeApp
    //
    DrawMenuBar(hwnd);
    return (bSuccess);
}

/******************************Public*Routine******************************\
*
* bCleanupPrinter
*
* Effects:  Local freeing
*
* Warnings: globals!!!
*
\**************************************************************************/

BOOL bCleanupPrinter(VOID)
{
    if (gpPrinters != NULL)
        LocalFree((LOCALHANDLE)gpPrinters);
    if (gpszPrinterNames != NULL)
        LocalFree((LOCALHANDLE)gpszPrinterNames);
    if (gpszDeviceNames  != NULL)
        LocalFree((LOCALHANDLE)gpszDeviceNames);

    return TRUE;
}


PRINTER.H

/******************************Module*Header*******************************\
* Module Name: printer.h
*
* (Brief description)
*
* Created: 16-Apr-1992 15:07:38
* Author: Petrus Wong [PetrusW]
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* (General description of its use)
*
* Dependencies:
*
*   (#defines)
*   (#includes)
*
\**************************************************************************/
//
// menu item IDs
//
#define MM_PORTRAIT             7040
#define MM_LANDSCAPE            7041
#define MM_PRINTER              9000


BNDSCAN.C

/******************************Module*Header*******************************\
* Module Name: bndscan.c
*
* Contains the boundary scaning functions
*
* Created: 14-Apr-1992 10:59:52
*
* Copyright 1993 - 1998 Microsoft Corporation
*
\**************************************************************************/
#include <windows.h>
#include <stdio.h>
#include "jtypes.h"
#include "bndscan.h"

BOOLbBoundaryScanFix(PINFO);
LPPOINT pptTrace(LONG, LONG, PINFO, INT *, HDC);
DWORDdwGetNextBoundaryPoint(DWORD, POINT, LPPOINT, PINFO);
BOOLbEscape(POINT, LONG, LONG, LONG, LONG, int, RECT);
BOOLbDirToPt(DWORD, POINT, LPPOINT);
DWORD   dwShift(DWORD, int, int);

/******************************Public*Routine******************************\
*
* bBoundaryScanFix
*
* Effects:  Traces the outline of the mandelbrot set.  Currently,
*           1. Uses the old method to set the color of each pixel
*           2. Remember the first pixel from top that doesn't escape
*              st we can do boundary trace later.
*           3. When the whole set is done, do boundary trace.
*           4. Store the boundary points in an array. Then build a path.
*           5. Converts the path to region and then select the region as
*              current clip region.
*
* Warnings: Only traces the first non-singular pixel island from top.
\**************************************************************************/

BOOL bBoundaryScanFix(PINFO pInfo)
{
    DWORD       dwTick1;
    int         m, n, o, p;
    HDC         hDC;
    RECT        rc;
    LONG        c1, c2;
    LONG        x0, y0;
    int         iIteration;
    LPPOINT     lpPt;
    int iCount;
    POINTPt;
    BOOL        bFirstPtSet;

    pInfo->bMandel = TRUE;
    pInfo->bDrawing = TRUE;
    bFirstPtSet = FALSE;
    iIteration = pInfo->iIteration;
    hDC = GetDC(pInfo->hwnd);
    GetClientRect(pInfo->hwnd, &rc);

    dwTick1 = GetTickCount();
    for (n=rc.bottom/2; n<=rc.bottom; n++) {
c2 = y0 = XformFix(n, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

        for (m=rc.left; m<=rc.right; m++) {
    c1 = x0 = XformFix(m, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
    Pt.x = m;
    Pt.y = n;
    if (bEscape(Pt, x0, y0, c1, c2, iIteration, rc)) {
                //SetPixel(hDC, m, n, 0x000000ff);
            } else {

                if (!bFirstPtSet)
                {
                    o = m;
                    p = n;
                    bFirstPtSet = TRUE;
                }
                goto BOUNDTRACE;
            }
        }
    }

BOUNDTRACE:

    SelectObject(hDC, hpnBlack);
    if ((lpPt = pptTrace(o, p, pInfo, &iCount, hDC)) == NULL)
    {
        m = o+1;

        for (n=p; n<=rc.bottom; n++) {
    c2 = y0 = XformFix(n, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

            while (m <= rc.right)
            {
        c1 = x0 = XformFix(m, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
        Pt.x = m;
        Pt.y = n;

        if (!bEscape(Pt, x0, y0, c1, c2, iIteration, rc))
                {
                    o = m;
                    p = n;
                    goto BOUNDTRACE;
                }

                m++;
            }
            m = rc.left;
        }
    }

    if (!BeginPath(hDC)) {
        sprintf( gtext,"Failing in BeginPath!\n");
        OutputDebugString( gtext );
    }

    MoveToEx(hDC, m, n, NULL);
    Polyline(hDC, lpPt, iCount);

    if (EndPath(hDC)) {
        StrokePath(hDC);
    }

    //
    // Stroking discards the path
    //
    if (!BeginPath(hDC)) {
        sprintf( gtext,"Failing in BeginPath!\n");
        OutputDebugString( gtext );
    }

    MoveToEx(hDC, m, n, NULL);
    Polyline(hDC, lpPt, iCount);

    if (EndPath(hDC)) {
        //
        // Convert the path to region
        //
        pInfo->hRgnPath = PathToRegion(hDC);

#if 0
        //
        // Can't use SelectClipPath to set the clipping region here
        // because we are on a different thread.  This is only a cached DC.
        //
        if (SelectClipPath(hDC, RGN_COPY)) {

            // Testing if the clip region is effective or not
            //
            //MoveToEx(hDC, 0, 0, NULL);
            //GetClientRect(pInfo->hwnd, &rc);
            //LineTo(hDC, rc.right, rc.bottom);
        }
#endif
    }

    if (pInfo->hRgnPath != (HRGN) NULL) {
        //
        // let the main thread who owns the DC to set the clipping region.
        //
        SendMessage(ghwndMain, WM_COMMAND, MM_SELCLIPRGN, 0L);
    }

    pInfo->dwElapsed = GetTickCount() - dwTick1;
    pInfo->hBmpSaved = SaveBitmap(pInfo->hwnd, pInfo->hPal);
    pInfo->bDrawing = FALSE;

    ReleaseDC(pInfo->hwnd, hDC);
    //
    // GlobalAlloc was called in pptTrace for storing the boundary points
    //
    GlobalFree((HANDLE) lpPt);
    ExitThread(0);
    return TRUE;
}

/******************************Public*Routine******************************\
*
* pptTrace
*
* Effects:  Given a boundary point, traces the whole boundary of the
*           set by calling dwGetNextBoundaryPoint() repeatedly.  Returns
*           a pointer to an array of boundary points and also the count
*           of boundary points in the array, if successful.  Otherwise,
*           returns null.
*           m, n is the x, y coordinates of the pixel of the initial
*           boundary point.
*
* Warnings:
*
* History:
*  20-Feb-1992 -by- Petrus Wong
* Wrote it.
\**************************************************************************/

LPPOINT pptTrace(LONG m, LONG n, PINFO pInfo, INT * piCount, HDC hDC)
{
    DWORD        dwFace;
    LPPOINT      lpPt, lpTmpPt;
    POINT        Init;
    int          iNumPt;

    //
    // MAXPOINT points should be enough for storing the boundary for now.  If
    // it's not enough, it is more likely that it is an error.
    //
    if ((lpPt = (PPOINT) GlobalAlloc(GMEM_FIXED, MAXPOINT * sizeof(POINT))) == NULL)
{
        return (LPPOINT) NULL;
    }
    lpTmpPt = lpPt;
    lpPt->x = Init.x = m;
    lpPt->y = Init.y = n;
    lpPt++;
    iNumPt = 1;
    dwFace = dwGetNextBoundaryPoint(EAST, Init, lpPt, pInfo);
    //
    // If can't find next boundary point, return null
    //
    if (dwFace == 0)
        return((LPPOINT)NULL);
//#if 0
    //
    // remove the ifdef if we wanted to see the boundary as we trace
    //
    MoveToEx(hDC, Init.x, Init.y, NULL);
    LineTo(hDC, lpPt->x, lpPt->y);
//#endif
    //
    // Keep looking for next boundary point until I get back to where I
    // started or exceeding MAXPOINT points
    //
    while ((iNumPt < MAXPOINT) &&
           ((lpPt->x != lpTmpPt->x) || (lpPt->y != lpTmpPt->y))) {
        Init.x = lpPt->x;
        Init.y = lpPt->y;
        lpPt++;
        iNumPt++;
dwFace = dwGetNextBoundaryPoint(dwFace, Init, lpPt, pInfo);
        //
        // If can't find next boundary point, return null
        //
        if (dwFace == 0)
            return((LPPOINT)NULL);
//#if 0
        //
        // remove the ifdef if we wanted to see the boundary as we trace
        //
        MoveToEx(hDC, Init.x, Init.y, NULL);
        LineTo(hDC, lpPt->x, lpPt->y);
//#endif

    }
    //
    // It is more likely that it is an error of the algorithm, if ever happens.
    //
    if (iNumPt >= MAXPOINT)
{
OutputDebugString ("Not enough memory!!");
    }
    *piCount = iNumPt;
    return (LPPOINT) lpTmpPt;
}

/******************************Public*Routine******************************\
*
* dwGetNextBoundaryPoint
*
* Effects:  Start checking the 8 neighbors in clockwise direction.  Returns
*           the first neighbor that does not escape.
*           dwFace      where we face
*           InitPt      where we are right now in pixel coordinates
*
* Warnings: assumes that we start at a boundary point.  Does not trace into
*           a bay if the entrance closed. Ie, it cuts cycle.
*
* History:
*  12-Feb-1992 -by- Petrus Wong
* Wrote it.
\**************************************************************************/

DWORD dwGetNextBoundaryPoint(DWORD dwFace, POINT InitPt, LPPOINT lpPt, PINFO pInfo)
{
    BOOL    bEsc;
    DWORD   dwExplore;
    DWORD   dwEnd;
    int     i;
    NODE    arg[8];
    POINT   BndPt;
    LONG    lx, c1, ly, c2;
    RECT    rc;

    GetClientRect(pInfo->hwnd, &rc);
    //
    // Start exploring clockwise, ending at where we came from
    //
    dwExplore = dwShift(dwFace, RIGHT, 3);
    dwEnd = dwShift(dwFace, LEFT, 4);
    i = 0;
    bDirToPt(dwExplore, InitPt, &BndPt);
    c1 = lx = XformFix(BndPt.x, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
    c2 = ly = XformFix(BndPt.y, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

    while (bEsc = bEscape(BndPt, lx, ly, c1, c2, pInfo->iIteration, rc)) {
        arg[i].dwDirection = dwExplore;
        arg[i].bEscape     = TRUE;

        i++;

        if (dwExplore == dwEnd)
            break;

        dwExplore = dwShift(dwExplore, LEFT, 1);
bDirToPt(dwExplore, InitPt, &BndPt);
c1 = lx = XformFix(BndPt.x, rc.left, rc.right, pInfo->lxFrom, pInfo->lxTo);
c2 = ly = XformFix(BndPt.y, rc.top, rc.bottom, pInfo->lyFrom, pInfo->lyTo);

    }

    if (bEsc) {
        return 0L;
    }

    arg[i].dwDirection = dwExplore;
    arg[i].bEscape     = FALSE;

    bDirToPt(arg[i].dwDirection, InitPt, lpPt);

    return arg[i].dwDirection;
}

/******************************Public*Routine******************************\
*
* bEscape
*
* Effects: Test if the point excapes based on the number of iterations
*
* Warnings:
*
* History:
*  16-Feb-1993      Petrus Wong     9.23 fix
*  20-Feb-1992 -by- Petrus Wong
* Wrote it.
\**************************************************************************/

BOOL bEscape(POINT Pt, LONG x, LONG y, LONG c1, LONG c2, int iIteration, RECT rc)
{
    LONG x1, y1, z;
    BOOL bEscape;
    int i;

    bEscape = FALSE;

    //
    // Treat points outside of client rect as escaping
    //
    if ((Pt.x < rc.left)   || (Pt.x > rc.right-1) ||
        (Pt.y > rc.bottom-1) || (Pt.y < rc.top))     {
        return TRUE;
    }
    for (i=1; i<=iIteration; i++) {
        x1 = lMul(x - y, x + y) + c1;
        y1 = (lMul(x, y) * 2) + c2;
        x = x1;
        y = y1;                             //    2       2     2 1/2     2
        z = lMul(x, x) + lMul(y, y);        // |Z|  = ((x1  + x2 )   ) > 2

        if (z > 33554432) {
            bEscape = TRUE;
            break;
        }
    }
    return bEscape;
}

/******************************Public*Routine******************************\
*
* bDirToPt
*
* Effects: Returns the pixel coordinates based the direction we are facing
*          and our current position
*
* Warnings:
*
* History:
*  20-Feb-1992
* Wrote it.
\**************************************************************************/

BOOL bDirToPt(DWORD dwDirection, POINT InitPt, LPPOINT lpPt)
{
    BOOL bSuccess;

    bSuccess = TRUE;
    switch ((LONG)dwDirection) {
        case EAST:
            lpPt->x = InitPt.x+1;
            lpPt->y = InitPt.y;
            break;
        case SOUTHEAST:
            lpPt->x = InitPt.x+1;
            lpPt->y = InitPt.y+1;
            break;
        case SOUTH:
            lpPt->x = InitPt.x;
            lpPt->y = InitPt.y+1;
            break;
        case SOUTHWEST:
            lpPt->x = InitPt.x-1;
            lpPt->y = InitPt.y+1;
            break;
        case WEST:
            lpPt->x = InitPt.x-1;
            lpPt->y = InitPt.y;
            break;
        case NORTHWEST:
            lpPt->x = InitPt.x-1;
            lpPt->y = InitPt.y-1;
            break;
        case NORTH:
            lpPt->x = InitPt.x;
            lpPt->y = InitPt.y-1;
            break;
        case NORTHEAST:
            lpPt->x = InitPt.x+1;
            lpPt->y = InitPt.y-1;
            break;
        default:
            bSuccess = FALSE;
            break;
    }
    return bSuccess;
}

/******************************Public*Routine******************************\
*
* dwShift
*                                     NORTH
*                           NW        0x0040        NE
*                               0x0020      0x0080
* Effects:      WEST    0x0010          *           0x0001  EAST
*                               0x0008      0x0002
*                           SW        0x0004        SE
*                                     SOUTH
*
*               Shift dwOpnd left or right by iNum.
*               dwOpnd contains the above values. The shift operation is
*               closed.
*
\**************************************************************************/

DWORD dwShift(DWORD dwOpnd, int iDir, int iNum)
{
    DWORD dwTmp;

    dwTmp = ((dwOpnd << 8) | dwOpnd);
    switch ((LONG)iDir) {
case LEFT:
            dwTmp = dwTmp << iNum;
    dwTmp = dwTmp & 0x0ff00;
            dwTmp = dwTmp >> 8;
            break;
case RIGHT:
            dwTmp = dwTmp >> iNum;
            dwTmp = dwTmp & 0x0ff;
            break;
default:
            dwTmp = 0L;
    break;
    }
    return dwTmp;
}


SAVEBMP.C

/******************************Module*Header*******************************\
* Module Name: savebmp.c
*
*
* Created: 06-Jan-1992 10:59:36
*
* Copyright 1993 - 1998 Microsoft Corporation
*
* Contains the main routine, SaveBitmapFile, for saving a DDB into file
* in DIB format.
*
* Dependencies:
*
*   (#defines)
*   (#includes)
*       #include <windows.h>
*       #include "jtypes.h"
*
\**************************************************************************/
#include <windows.h>
#include "julia.h"

extern HPALETTE ghPal;
extern HWND ghwndMain;
extern char   gtext[256];
BOOL SaveBitmapFile(HDC, HBITMAP, PSTR);
// void ErrorOut(char errstring[30]);

/******************************Public*Routine******************************\
* SaveBitmapFile
*
*
* Effects: Save pInfo->hBmpSaved into disk specified by pszFileName
*
* Warnings: assumes hBmpSaved is not selected into window's DC other than
*           pInfo->hwnd's DC
*
\**************************************************************************/

BOOL SaveBitmapFile(HDC hDC, HBITMAP hBmp, PSTR pszFileName)
{
    int         hFile;
    OFSTRUCT    ofReOpenBuff;
    HBITMAP     hTmpBmp, hBmpOld;
    BOOL        bSuccess;
    BITMAPFILEHEADER    bfh;
    PBITMAPINFO pbmi;
    PBYTE       pBits;
    BITMAPINFO  bmi;
    PBYTE pjTmp, pjTmpBmi;
    ULONG sizBMI;

    bSuccess = TRUE;
#if 0
    if (ghPal) {
        SelectPalette(hDC, ghPal, FALSE);
        RealizePalette(hDC);
    }
#endif
    if (!hBmp) {
        MessageBox(ghwndMain,
       GetStringRes (IDS_NO_BITMAP_TO_SAVE),
   NULL, MB_OK);
        return FALSE;
    }

    //
    // Let the graphics engine to retrieve the dimension of the bitmap for us
    // GetDIBits uses the size to determine if it's BITMAPCOREINFO or BITMAPINFO
    // if BitCount != 0, color table will be retrieved
    //
    bmi.bmiHeader.biSize = 0x28;              // GDI need this to work
    bmi.bmiHeader.biBitCount = 0;             // don't get the color table
    if ((GetDIBits(hDC, hBmp, 0, 0, (LPSTR)NULL, &bmi, DIB_RGB_COLORS)) == 0) {
       return FALSE;
    }

    //
    // Now that we know the size of the image, alloc enough memory to retrieve
    // the actual bits
    //
    if ((pBits = (PBYTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                bmi.bmiHeader.biSizeImage)) == NULL) {
        return FALSE;
    }

    //
    // Note: 24 bits per pixel has no color table.  So, we don't have to
    // allocate memory for retrieving that.  Otherwise, we do.
    //
    pbmi = &bmi;                                      // assume no color table

    switch (bmi.bmiHeader.biBitCount) {
        case 24:                                      // has color table
            sizBMI = sizeof(BITMAPINFOHEADER);
            break;
        case 16:
        case 32:
            sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3;
            break;
        default:
            sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*(1<<bmi.bmiHeader.biBitCount);
            break;

    }

    //
    // Allocate memory for color table if it is not 24bpp...
    //
    if (sizBMI != sizeof(BITMAPINFOHEADER)) {
        ULONG       sizTmp;
        //
        // I need more memory for the color table
        //
        if ((pbmi = (PBITMAPINFO)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizBMI )) == NULL) {
            bSuccess = FALSE;
            goto ErrExit1;
        }
        //
        // Now that we've a bigger chunk of memory, let's copy the Bitmap
        // info header data over
        //
        pjTmp = (PBYTE)pbmi;
        pjTmpBmi = (PBYTE)&bmi;
        sizTmp = sizeof(BITMAPINFOHEADER);

        while(sizTmp--)
        {
            *(((PBYTE)pjTmp)++) = *((pjTmpBmi)++);
        }
    }

    //
    // Let's open the file and get ready for writing
    //
    if ((hFile = OpenFile(pszFileName, (LPOFSTRUCT)&ofReOpenBuff,
                 OF_CREATE | OF_WRITE)) == -1) {
        MessageBox(ghwndMain, GetStringRes(IDS_FILE_OPEN_FAILED),
   NULL, MB_OK);
        goto ErrExit2;
    }

    //
    // But first, fill in the info for the BitmapFileHeader
    //
    bfh.bfType = 0x4D42;                            // 'BM'
    bfh.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+sizBMI+
        pbmi->bmiHeader.biSizeImage;
    bfh.bfReserved1 =
    bfh.bfReserved2 = 0;
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER)+sizBMI;

    //
    // Write out the file header now
    //
    if (_lwrite(hFile, (LPSTR)&bfh, sizeof(BITMAPFILEHEADER)) == -1) {
        bSuccess = FALSE;
        goto ErrExit3;
    }

    //
    // Bitmap can't be selected into a DC when calling GetDIBits
    // Assume that the hDC is the DC where the bitmap would have been selected
    // if indeed it has been selected
    //
    if (hTmpBmp = CreateCompatibleBitmap(hDC, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight)) {
        hBmpOld = SelectObject(hDC, hTmpBmp);
        if ((GetDIBits(hDC, hBmp, 0, pbmi->bmiHeader.biHeight, (LPSTR)pBits, pbmi, DIB_RGB_COLORS))==0){
            bSuccess = FALSE;
            goto ErrExit4;
        }
    } else {
        MessageBox(ghwndMain,
GetStringRes (IDS_BITMAP_NOT_CREATED),
NULL, MB_OK);
        bSuccess = FALSE;
        goto ErrExit3;
    }

    //
    // Now write out the BitmapInfoHeader and color table, if any
    //
    if (_lwrite(hFile, (LPSTR)pbmi, sizBMI) == -1) {
        bSuccess = FALSE;
        goto ErrExit4;
    }

    //
    // write the bits also
    //
    if (_lwrite(hFile, (LPSTR)pBits, pbmi->bmiHeader.biSizeImage) == -1) {
        bSuccess = FALSE;
        goto ErrExit4;
    }

ErrExit4:
    SelectObject(hDC, hBmpOld);
    DeleteObject(hTmpBmp);
ErrExit3:
    _lclose(hFile);
ErrExit2:
    GlobalFree(pbmi);
ErrExit1:
    GlobalFree(pBits);
    return bSuccess;
}

#if 0
/************************************************************************
 * void ErrorOut(char errstring[30])
 *
 * Purpose: Print out an meainful error code by means of
 *  GetLastError and printf
 *
 * Inputs:  errstring - the action that failed, passed by the
 *      calling proc.
 *
 * Returns: none
 *
 * Calls:   GetLastError
 *
 * History:
 * 09-13-91 Pete Grey   Created.
 *
\************************************************************************/

void ErrorOut(char errstring[30])
{
DWORD Error;
CHAR  str[80];

Error= GetLastError();
wsprintf((LPSTR) str, "Error on %s = %d\n", errstring, Error);
OutputDebugString(str);
}

#endif


README.TXT

Enhanced Metafile Editor

SUMMARY
=======

The MFEDIT sample demonstrates the enhanced metafile functions.

The metafile editor provides the following features:

 1. Playback and recording of GDI calls.
 2. Embedding bitmaps and enhanced metafiles into another enhanced metafile
    with transformations.
 3. Hit-testing against enhanced metafile records.
 4. Random-access playback.
 5. Playback of metafile records one-by-one.
 6. Selective recording of existing enhanced metafile records into a new
    enhanced metafile.
 7. Drawing with pens, text, beziers, lines, ellipses, and rectangles,
    embedding bitmaps, and embedding enhanced metafiles.

MORE INFORMATION
================

1. Playback and Recording of GDI Calls

To playback an existing enhanced metafile, choose Load Metafile from the
File menu or click on the Eject button to bring up the Open File dialog box
and select the appropriate file. Then click on the Play button to play it on
the drawing surface.

To record, click on the Record button and draw on the drawing surface with
the graphic tools provided. When you are done, click on the Stop button.

The GDI calls are recorded as c:\metafx.emf where x is 0, 1, 2, 3, etc. If
you want to save the metafile with a different name, choose Record Metafile
As from the File menu. The new filename will be used as the root for all
metafiles recorded; 0, 1, 2, etc, will be appended to the root name.

The default drawing tool is "pen". To select a different drawing tool, click
on the desired tool button in the Control Panel.

2. Embedding Bitmaps and Enhanced Metafiles Into Another Enhanced Metafile
   with Transformations

Click the Record button. Select the Bitmap or Metafile tool and then embed
the currently loaded bitmap or metafile as described in section 7 below.
When you are done, click the Stop button.

3. Hit-Testing Against Enhanced Metafile Records

Playback an enhanced metafile by clicking on the Playback button. Then
choose Hit Test from the Edit menu. The cursor will be changed to a cross
when the mouse pointer is over the drawing surface.

Then, click on the graphic object played back in the drawing surface. The
editor will search through the metafile record after record to find the
record that corresponds to the object based on the mouse position. The
search provides a visual cue by changing the graphic objects to red as it
goes until it hits the corresponding object. If there is a hit, the record
number is displayed on the Control Panel, a beep is issued, and a hit
message is displayed on the bottom.

When you are done, remove the check from the Hit Test menu item.

4. Random Access Playback

Click on the number button in the Control Panel to playback a particular
record. To access a non single-digit record, click on the 10+ button an
appropriate number of times, and then the appropriate number button, to
bring the sum to the record desired.

5. Playback of Metafile Records One-by-One

Click on the Fast Forward button to play the metafile records one at a time.

6. Selective Recording of Existing Enhanced Metafile Records into a New
   Enhanced Metafile.

Click on the Record button and the appropriate number button for playing
back selected metafile records in the drawing surface. The playback records
are recorded into the new metafile. When you are done, click on the Stop
button.

7. Drawing With Pens, Text, Beziers, Lines, Ellipses, and Rectangles,
   Embedding Bitmaps, and Embedding Enhanced Metafiles

The default pen is black. To change the pen color, simply choose the Pen
menu item from the Options menu.

The default brush used by the Fill Rectangle and Fill Ellipse routines is
black. To change the brush color, simply choose Brush from the Option menu.

The "Text" tool uses the default system font. To change the font and font
attributes, simply choose Font from the Options menu.

The "Bezier" tool takes four points initially and three thereafter. To draw
a bezier curve, simply select the "Bezier" tool and click on the drawing
surface three or four times to place the control points.

To embed a currently loaded bitmap, simply select the "Bitmap" tool and
click three points on the drawing surface to describe the destination of
where you would like the bitmap to go. The editor will do the proper
transform to the bitmap and embed the bitmap to the drawing.

To load a bitmap, simply choose Load Bitmap from the File menu and do the
selection. The "Bitmap" tool optionally takes a mask bitmap. The mask bitmap
has to be a monochrome bitmap.

To load a mask bitmap, simply choose Load Mask Bitmap from the File menu to
do the selection. Select a color bitmap as the mask has the effect of
resetting the mask to none.

To embed a currently loaded enhanced metafile, simply select the "Embed
Enhanced Metafile" tool and click three points on the drawing surface to
describe the destination of where you would like the enhanced metafile to
go. The editor will do the proper transformation on the metafile and embed
it to the drawing.


MFEDIT.C

/******************************Module*Header*******************************\
* Module Name: mfedit.c
*
* Main module for the Enhanced Metafile Editor
*       contains everything
*
* Copyright 1992 - 1998 Microsoft Corporation
*
* The Enhanced Metafile Editor serves to demonstrate the enhanced metafile
* APIs in Windows NT.
*
* The Editor provides the following functions:
*       1.  Playback and recording of GDI calls
*       2.  Embedding bitmap and enhanced metafile into another enhanced
*           metafile with transformation
*       3.  Hit-testing against enhanced metafile records
*       4.  Random access playback
*       5.  Playback metafile records one-by-one
*       6.  Selective recording of existing enhanced metafile records into
*           a new enhanced metafile
*       7.  drawing with pen, text, bezier, line, ellipse, rectangle and
*           embedding bitmap and enhanced metafile tools
*
*
\**************************************************************************/
#include "precomp.h"

/******************************Public*Routine******************************\
*
* FUNCTION:  PolyDraw95(HDC, LPPOINT, LPBYTE, int)
*
* PURPOSE:   Draws the points returned from a call to GetPath()
*            to an HDC
*
* NOTES:     This function is similar to the Windows NT PolyDraw
*            function, which draws a set of line segments and Bezier
*            curves. Because PolyDraw is not supported in Windows 95
*            this PolyDraw95 function is used instead.
*
\**************************************************************************/

BOOL PolyDraw95(HDC  hdc,               // handle of a device context
                CONST LPPOINT lppt,     // array of points
                CONST LPBYTE lpbTypes,  // line and curve identifiers
                int  cCount)            // count of points
{
  int i;

  for (i=0; i<cCount; i++)
    switch (lpbTypes[i]) {
      case PT_MOVETO :
         MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL);
         break;

      case PT_LINETO | PT_CLOSEFIGURE:
      case PT_LINETO :
         LineTo(hdc, lppt[i].x, lppt[i].y);
         break;

      case PT_BEZIERTO | PT_CLOSEFIGURE:
      case PT_BEZIERTO :
        PolyBezierTo(hdc, &lppt[i], 3);
       i+=2;
         break;
    }

   return TRUE;
}

/**************************************************************************\
*
* vErrOut
*
* Effects:  Put up a message box with the error string and error code
*           returned.
*
\**************************************************************************/

void vErrOut(char errstring[MAX_ERR_STRING])
{
    DWORD   dwError, dwThrdId;
    char    str[MAX_ERR_STRING];

    if (gbDebug) {
        dwError = GetLastError();
        dwThrdId = GetCurrentThreadId();
        wsprintf((LPSTR) str, "%s, tid = %lx, Error code = %d\n",
                 errstring, dwThrdId, dwError);
        OutputDebugString((LPCTSTR)str);
        //MessageBox(GetFocus(), (LPSTR)str, "MfEdit Error", MB_OK);
    }
}

/**************************************************************************\
*
* FlushPalette
*
\**************************************************************************/

static void
FlushPalette(HDC hdc, int nColors)
{
    LOGPALETTE *pPal;
    HPALETTE hpal, hpalOld;
    int i;

    if (nColors == 256)
    {
        pPal = (LOGPALETTE *) LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT,
                                         sizeof(LOGPALETTE) + (nColors-1) * sizeof(PALETTEENTRY));

        if (pPal)
        {
    pPal->palVersion = 0x300;
    pPal->palNumEntries = nColors;

        // Mark everything PC_NOCOLLAPSE and PC_RESERVED to force every thing
        // into the palette.  Colors are already black because we zero initialized
        // during memory allocation.

            for (i = 0; i < nColors; i++)
            {
                pPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE | PC_RESERVED;
            }

            hpal = CreatePalette(pPal);
            LocalFree(pPal);

            hpalOld = SelectPalette(hdc, hpal, FALSE);
            RealizePalette(hdc);

            SelectPalette(hdc, hpalOld, FALSE);
            DeleteObject(hpal);
        }
    }
}

/**************************************************************************\
*
* UpdateStaticMapping
*
\**************************************************************************/

static void
UpdateStaticMapping(PALETTEENTRY *pe332Palette)
{
    HPALETTE hpalStock;
    int iStatic, i332;
    int iMinDist, iDist;
    int iDelta;
    int iMinEntry;
    PALETTEENTRY *peStatic, *pe332;

    hpalStock = GetStockObject(DEFAULT_PALETTE);

    // The system should always have one of these
    //TKASSERT(hpalStock != NULL);
    // Make sure there's the correct number of entries
    //TKASSERT(GetPaletteEntries(hpalStock, 0, 0, NULL) == STATIC_COLORS);

    // Get the current static colors
    GetPaletteEntries(hpalStock, 0, STATIC_COLORS, apeDefaultPalEntry);

    // Zero the flags in the static colors because they are used later
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Zero the flags in the incoming palette because they are used later
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = 0;
        pe332++;
    }

    // Try to match each static color exactly
    // This saves time by avoiding the least-squares match for each
    // exact match
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            if (peStatic->peRed == pe332->peRed &&
                peStatic->peGreen == pe332->peGreen &&
                peStatic->peBlue == pe332->peBlue)
            {
                //TKASSERT(pe332->peFlags != COLOR_USED);

                peStatic->peFlags = EXACT_MATCH;
                pe332->peFlags = COLOR_USED;
                aiDefaultOverride[iStatic] = i332;

                break;
            }

            pe332++;
        }

        peStatic++;
    }

    // Match each static color as closely as possible to an entry
    // in the 332 palette by minimized the square of the distance
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        // Skip colors already matched exactly
        if (peStatic->peFlags == EXACT_MATCH)
        {
            peStatic++;
            continue;
        }

        iMinDist = MAX_COL_DIST+1;
#if DBG
        iMinEntry = -1;
#endif

        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            // Skip colors already used
            if (pe332->peFlags == COLOR_USED)
            {
                pe332++;
                continue;
            }

            // Compute Euclidean distance squared
            iDelta = pe332->peRed-peStatic->peRed;
            iDist = iDelta*iDelta;
            iDelta = pe332->peGreen-peStatic->peGreen;
            iDist += iDelta*iDelta;
            iDelta = pe332->peBlue-peStatic->peBlue;
            iDist += iDelta*iDelta;

            if (iDist < iMinDist)
            {
                iMinDist = iDist;
                iMinEntry = i332;
            }

            pe332++;
        }

        //TKASSERT(iMinEntry != -1);

        // Remember the best match
        aiDefaultOverride[iStatic] = iMinEntry;
        pe332Palette[iMinEntry].peFlags = COLOR_USED;

        peStatic++;
    }

    // Zero the flags in the static colors because they may have been
    // set.  We want them to be zero so the colors can be remapped
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Reset the 332 flags because we may have set them
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = PC_NOCOLLAPSE;
        pe332++;
    }

#if 0
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        PrintMessage("Static color %2d maps to %d\n",
                     iStatic, aiDefaultOverride[iStatic]);
    }
#endif
}

#define SwapPalE(i,j) { \
    PALETTEENTRY palE; \
    palE = pPal->palPalEntry[i]; \
    pPal->palPalEntry[i] = pPal->palPalEntry[j]; \
    pPal->palPalEntry[j] = palE; }

/**************************************************************************\
*
* SaveStaticEntries
*
\**************************************************************************/

VOID SaveStaticEntries(HDC hDC)
{
    int i;

    if (gbSystemColorsInUse) {
        for (i = COLOR_SCROLLBAR; i <= COLOR_BTNHIGHLIGHT; i++)
            gacrSave[i - COLOR_SCROLLBAR] = GetSysColor(i);

        gbStaticSaved = TRUE;
    }
}

/**************************************************************************\
*
* UseStaticEntries
*
\**************************************************************************/

static VOID UseStaticEntries(HDC hdc)
{
    SetSysColors(NUM_STATIC_COLORS, gaiStaticIndex, gacrBlackAndWhite);
    gbSystemColorsInUse = TRUE;

    PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);
}

/**************************************************************************\
*
* hCreateRGBPalette
*
\**************************************************************************/

HPALETTE hCreateRGBPalette(HDC hDC, int index)
{
    PIXELFORMATDESCRIPTOR   pfd;
    PLOGPALETTE             pLogPal, pPal;
    int                     nClr, i, n;
    HPALETTE                hPal;
    BOOL                    bUseStaticClr;

    if (DescribePixelFormat(hDC, index, sizeof(PIXELFORMATDESCRIPTOR), &pfd) == 0) {
        return (HPALETTE)NULL;
    }

    bUseStaticClr = pfd.dwFlags & PFD_NEED_SYSTEM_PALETTE;

    if (pfd.dwFlags & PFD_NEED_PALETTE) {
        nClr = 1 << pfd.cColorBits;

        if ((pLogPal = (PLOGPALETTE) LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                        (nClr-1) * sizeof(PALETTEENTRY))) != NULL) {

            pLogPal->palVersion = 0x300;
            pLogPal->palNumEntries = nClr;

            for (i = 0; i < nClr; i++) {
                pLogPal->palPalEntry[i].peRed   =
    ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
                pLogPal->palPalEntry[i].peGreen =
    ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
                pLogPal->palPalEntry[i].peBlue  =
    ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
                pLogPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
            }

            if ( nClr == 256) {
                if (bUseStaticClr) {
                    pLogPal->palPalEntry[0].peFlags = 0;
                    pLogPal->palPalEntry[255].peFlags = 0;

                    SaveStaticEntries(hDC);
                    SetSystemPaletteUse(hDC, SYSPAL_NOSTATIC);
                } else {
                    if ((pfd.cRedBits   == 3) && (pfd.cRedShift   == 0) &&
                        (pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) &&
                        (pfd.cBlueBits  == 2) && (pfd.cBlueShift  == 6)) {
                        UpdateStaticMapping(pLogPal->palPalEntry);

                        for (i = 0; i < 20; i++) {
                            pLogPal->palPalEntry[aiDefaultOverride[i]] =
                                apeDefaultPalEntry[i];
                        }

                    }
                }
            }

            FlushPalette(hDC, nClr);

            hPal = CreatePalette(pLogPal);
            LocalFree(pLogPal);
            SelectPalette(hDC, hPal, FALSE);
            RealizePalette(hDC);

            if (bUseStaticClr)
                UseStaticEntries(hDC);

        }
    }
    // set up logical indices for CI mode
    else if( pfd.iPixelType == PFD_TYPE_COLORINDEX ) {
        if (pfd.cColorBits == 4) {

            // for 4-bit, create a logical palette with 16 entries

            n = 16;
            pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                    (n-1) * sizeof(PALETTEENTRY));
            pPal->palVersion = 0x300;
            pPal->palNumEntries = n;

            for( i = 0; i < 8; i ++) {
                pPal->palPalEntry[i] = apeDefaultPalEntry[i];
            }
            for (i = 8; i < 16; i++) {
                pPal->palPalEntry[i] = apeDefaultPalEntry[i+4];
            }

            // conform expects indices 0..3 to be BLACK,RED,GREEN,BLUE, so
            //  we rearrange the table for now.

            SwapPalE(1,9)
            SwapPalE(2,10)
            SwapPalE(3,12)

        } else if (pfd.cColorBits == 8) {

            // for 8-bit, create a logical palette with 256 entries, making
            // sure that the 20 system colors exist in the palette

            n = 256;
            pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                    (n-1) * sizeof(PALETTEENTRY));
            pPal->palVersion = 0x300;
            pPal->palNumEntries = n;

            hPal = GetStockObject (DEFAULT_PALETTE);

            // start by copying default palette into new one

            GetPaletteEntries( hPal, 0, 20, pPal->palPalEntry);

            // conform expects indices 0..3 to be BLACK,RED,GREEN,BLUE, so
            // we rearrange the table for now.

            SwapPalE(1,13)
            SwapPalE(2,14)
            SwapPalE(3,16)

            for( i = 20; i < n; i ++) {
                pPal->palPalEntry[i].peRed   = (BYTE) (i - 1);
                pPal->palPalEntry[i].peGreen = (BYTE) (i - 2);
                pPal->palPalEntry[i].peBlue  = (BYTE) (i - 3);
                pPal->palPalEntry[i].peFlags = (BYTE) 0;
            }

            // If we are taking possession of the system colors,
            // must guarantee that 0 and 255 are black and white
            // (respectively), so that they can remap to the
            // remaining two static colors.  All other entries must
            // be marked as PC_NOCOLLAPSE.

            if ( gbUseStaticColors )
            {
                pPal->palPalEntry[0].peRed =
                pPal->palPalEntry[0].peGreen =
                pPal->palPalEntry[0].peBlue = 0x00;

                pPal->palPalEntry[255].peRed =
                pPal->palPalEntry[255].peGreen =
                pPal->palPalEntry[255].peBlue = 0xFF;

                pPal->palPalEntry[0].peFlags =
                pPal->palPalEntry[255].peFlags = 0;

                for ( i = 1 ; i < 255 ; i++ )
                {
                    pPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;

                // This is a workaround for a GDI palette "feature".  If
                // any of the static colors are repeated in the palette,
                // those colors will map to the first occurance.  So, for
                // our case where there are only two static colors (black
                // and white), if a white color appears anywhere in the
                // palette other than in the last  entry, the static white
                // will remap to the first white.  This destroys the nice
                // one-to-one mapping we are trying to achieve.
                //
                // There are two ways to workaround this.  The first is to
                // simply not allow a pure white anywhere but in the last
                // entry.  Such requests are replaced with an attenuated
                // white of (0xFE, 0xFE, 0xFE).
                //
                // The other way is to mark these extra whites with
                // PC_RESERVED which will cause GDI to skip these entries
                // when mapping colors.  This way the app gets the actual
                // colors requested, but can have side effects on other
                // apps.

                    if ( pPal->palPalEntry[i].peRed   == 0xFF &&
                         pPal->palPalEntry[i].peGreen == 0xFF &&
                         pPal->palPalEntry[i].peBlue  == 0xFF )
                    {
                        pPal->palPalEntry[i].peFlags |= PC_RESERVED;
                    }
                }

                SaveStaticEntries(hDC);
                SetSystemPaletteUse(hDC, SYSPAL_NOSTATIC);
            }

        } else {
            // for pixel formats > 8 bits deep, create a logical palette with
            // 4096 entries

            n = 4096;
            pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                    (n-1) * sizeof(PALETTEENTRY));
            pPal->palVersion = 0x300;
            pPal->palNumEntries = n;

            for( i = 0; i < n; i ++) {
                pPal->palPalEntry[i].peRed   = (BYTE) ((i & 0x000f) << 4);
                pPal->palPalEntry[i].peGreen = (BYTE) (i & 0x00f0);
                pPal->palPalEntry[i].peBlue  = (BYTE) ((i & 0x0f00) >> 4);
                pPal->palPalEntry[i].peFlags = (BYTE) 0;
            }

            // conform expects indices 0..3 to be BLACK,RED,GREEN,BLUE, so
            //  we rearrange the table for now.

            SwapPalE(1,0xf)
            SwapPalE(2,0xf0)
            SwapPalE(3,0xf00)
        }

        hPal = CreatePalette(pPal);
        LocalFree(pPal);

        FlushPalette(hDC, n);

        SelectPalette(hDC, hPal, FALSE);
        n = RealizePalette(hDC);

        if ( gbUseStaticColors )
            UseStaticEntries(hDC);
    }

    return hPal;
}

/******************************Public*Routine******************************\
*
* bSetupRC
*
* Effects: Sets up the OpenGL rendering context
*
* Warnings:
*
\**************************************************************************/

#ifdef OPENGL_EMF
BOOL bSetupRC(HDC hDC, PIXELFORMATDESCRIPTOR *ppfdIn)
{
    PIXELFORMATDESCRIPTOR   pfd, *ppfd;
    INT                     iPfmt;
    BOOL                    bRet=FALSE;
    HPALETTE                hPal=(HPALETTE)NULL;
    HGLRC                   hRC;
    char                    text[128];

    if ((ppfdIn != (PIXELFORMATDESCRIPTOR*)NULL) && gbUseMfPFD) {
        ppfd = ppfdIn;
    } else {
        ppfd = &pfd;
        pfd.nSize   = sizeof(PIXELFORMATDESCRIPTOR);
        pfd.nVersion= 1;
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
        pfd.iPixelType  = PFD_TYPE_RGBA;
        pfd.cColorBits  = 24;
        pfd.cRedBits    =
        pfd.cRedShift   =
        pfd.cGreenBits  =
        pfd.cGreenShift =
        pfd.cBlueBits   =
        pfd.cBlueShift  =
        pfd.cAlphaBits  =
        pfd.cAlphaShift = 0;
        pfd.cAccumBits  = 24;
        pfd.cAccumRedBits   =
        pfd.cAccumGreenBits =
        pfd.cAccumBlueBits  =
        pfd.cAccumAlphaBits = 0;
        pfd.cDepthBits  = 24;
        pfd.cStencilBits = 24;
        pfd.cAuxBuffers =
        pfd.iLayerType  =
        pfd.bReserved   = 0;
        pfd.dwLayerMask = PFD_MAIN_PLANE;
        pfd.dwVisibleMask = 0;
        pfd.dwDamageMask = 0;
    }

    if ((iPfmt = ChoosePixelFormat(hDC, ppfd)) != 0) {
        bRet = SetPixelFormat(hDC, iPfmt, ppfd);

        gbDB = (ppfd->dwFlags & PFD_DOUBLEBUFFER) ? TRUE : FALSE;

        if ((hPal = hCreateRGBPalette(hDC, iPfmt)) != (HPALETTE)NULL) {
            SelectPalette(hDC, hPal, FALSE);
            RealizePalette(hDC);
            bRet = bRet && TRUE;
        } else {
            bRet = FALSE;
        }

        hRC = wglCreateContext(hDC);
        wglMakeCurrent(hDC, hRC);

        wsprintf(text, "bSetupRC: hDC=%lx, hRC=%lx", hDC, hRC);
        vErrOut(text);

    }
    return bRet;
}

/**************************************************************************\
*
* bSetRC2MatchEmfRC
*
* Effects:  If the bOpenGL flag is not set in the metafile header,
*           return FALSE.
*
*           If the bOpenGL flag is set, retrieve the pfd from the enhanced
*           metafile, if exists.  Set the pixel format with the retrieved
*           pfd on the hDC.  If the pfd is not recorded, let bSetupRC pick
*           the pfd.
*
*           If ppfd is not null, copy the pfd in the metafile record,
*           to the ppfd.  If ppfd->nSize == 0, that means the pfd is not
*           found in the metafile record.
*
* Warnings:
*
* History:
*  05-May-1996
* Wrote it.
\**************************************************************************/

BOOL bSetRC2MatchEmfRC(HDC                      hDC,
                       ENHMETAHEADER            EnhMetaHdr,
                       HENHMETAFILE             hEnhMf,
                       PIXELFORMATDESCRIPTOR    *ppfdIn)
{
    BOOL                    bHasPFD = FALSE, bRCSet = FALSE;
    char                    text[128];
    PIXELFORMATDESCRIPTOR   pfd;

    if (EnhMetaHdr.bOpenGL) {

        if (gpfnGetEnhMetaFilePixelFormat != (PROC)NULL) {

            bHasPFD = ((gpfnGetEnhMetaFilePixelFormat)(hEnhMf,
                                      sizeof(PIXELFORMATDESCRIPTOR),
                                      &pfd) == 0) ? FALSE : TRUE;

            bRCSet = bSetupRC(hDC, (bHasPFD ? &pfd : NULL));

            wsprintf(text,
                     "bSetRC2MatchEmfRC: bRCSet=%ld, bHasPFD=%ld",
                     bRCSet, bHasPFD);
            vErrOut(text);

        } else {
            vErrOut("bSetRC2MatchEmfRC: gpfnGetEnhMetaFilePixelFormat is NULL");
        }

    } else {
        vErrOut("bSetRC2MatchEmfRC: EnhMetaHdr.bOpenGL is FALSE");
    }

    if (ppfdIn != (PIXELFORMATDESCRIPTOR*)NULL) {
        if (bHasPFD) {
            memcpy(ppfdIn, &pfd, sizeof(PIXELFORMATDESCRIPTOR));
        } else {
            ppfdIn->nSize = 0;
        }
    }

    return bRCSet;
}

/******************************Public*Routine******************************\
*
* bCleanUpRC
*
* Effects:  Cleans up the OpenGL rendering context
*
* Warnings:
*
\**************************************************************************/

BOOL bCleanUpRC(VOID)
{
    HGLRC       hRC;
    HDC         hDC;
    char        text[128];

    hRC = wglGetCurrentContext();
    hDC = wglGetCurrentDC();

    wsprintf(text, "bCleanUpRC: hDC=%lx, hRC=%lx", hDC, hRC);
    vErrOut(text);

    wglMakeCurrent(hDC, NULL);
    wglDeleteContext(hRC);
    return TRUE;

}

#else
BOOL bSetupRC(HDC hDC, PIXELFORMATDESCRIPTOR *ppfdIn)
{
    return TRUE;
}

BOOL bSetRC2MatchEmfRC(HDC                      hDC,
                       ENHMETAHEADER            EnhMetaHdr,
                       HENHMETAFILE             hEnhMf,
                       PIXELFORMATDESCRIPTOR    *ppfd)
{
    return TRUE;
}

BOOL bCleanUpRC(VOID)
{
    return TRUE;
}
#endif

/**************************************************************************\
*
* WinMain
*
* Effects:
*
* Warnings:
*
* History:
*  06-May-1996  Retrieve the proc addr for GetEnhMetaFilePixelFormat.
* Wrote it.
\**************************************************************************/

int WINAPI WinMain(
           HINSTANCE hInstance,
           HINSTANCE hPrevInstance,
           LPSTR lpCmdLine,
           int nShowCmd)
{
    MSG      msg;
    HANDLE   hAccel;
    HMODULE  hMod;

    ghModule = GetModuleHandle(NULL);

//
// GDI32 may not support the OpenGL enhanced metafile (Win95 doesn't), in
// which case, the entry point doesn't exist, so we will do a GetProcAddress
// instead.
//
    hMod = LoadLibrary("GDI32.DLL");
    if (hMod != (HMODULE)NULL) {

        gpfnGetEnhMetaFilePixelFormat =
                GetProcAddress(hMod, "GetEnhMetaFilePixelFormat");

        if (gpfnGetEnhMetaFilePixelFormat == (PROC)NULL)
            vErrOut("Main: GetProcAddress(GetEnhMetaFilePixelFormat) failed");

    } else {
        vErrOut("Main: LoadLibrary(GDI32) failed");
    }

    if (!InitializeApp()) {
        MessageBox(ghwndMain,
            GetStringRes(IDS_INITAPPFAIL), GetStringRes(IDS_ERROR), MB_OK);
        return 0;
    }

    if (!(hAccel = LoadAccelerators (ghModule, MAKEINTRESOURCE(ACCEL_ID))))
        MessageBox(ghwndMain,
            GetStringRes(IDS_LOADACCFAIL), GetStringRes(IDS_ERROR), MB_OK);

    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!TranslateAccelerator( ghwndMain, hAccel, &msg) ) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return 1;

    UNREFERENCED_PARAMETER(lpCmdLine);
    UNREFERENCED_PARAMETER(nShowCmd);
    UNREFERENCED_PARAMETER(hInstance);
    UNREFERENCED_PARAMETER(hPrevInstance);
}

/***************************************************************************\
* InitializeApp
*
\***************************************************************************/

BOOL InitializeApp(void)
{
    WNDCLASS wc;
    int index;
    HDC hDC;

    wc.style            = CS_DBLCLKS;
    wc.lpfnWndProc      = (WNDPROC)MainWndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra= sizeof(DWORD);
    wc.hInstance        = ghModule;
    wc.hIcon            = LoadIcon(ghModule, MAKEINTRESOURCE(APP_ICON));
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName     = "MainMenu";
    wc.lpszClassName= "MetafDemoClass";

    if (!RegisterClass(&wc))
return FALSE;

    wc.style            = CS_OWNDC | CS_SAVEBITS;
    wc.lpfnWndProc      = (WNDPROC)DrawSurfWndProc;
    wc.hIcon            = NULL;
    wc.hCursor          = NULL;
    wc.hbrBackground    = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "DrawSurfClass";

    if (!RegisterClass(&wc))
return FALSE;

    wc.style= CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc= (WNDPROC)TextWndProc;
    wc.hIcon= NULL;
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszMenuName= NULL;
    wc.lpszClassName= "Text";

    if (!RegisterClass(&wc))
            return FALSE;

    hMenu= LoadMenu(ghModule, "MainMenu");

    for (index = 0; index < OD_BTN_CNT; index++) {
        ghBmpDn[index] = (PVOID)LoadBitmap(ghModule, (LPCSTR)MAKEINTRESOURCE(BMID_BASED+index));
        ghBmpUp[index] = (PVOID)LoadBitmap(ghModule, (LPCSTR)MAKEINTRESOURCE(BMID_BASEU+index));
    }
    for (index = 0; index < OD_TOOL_CNT; index++) {
        ghToolBmpDn[index] = (PVOID)LoadBitmap(ghModule, (LPCSTR)MAKEINTRESOURCE(BMID_TOOLBASED+index));
        ghToolBmpUp[index] = (PVOID)LoadBitmap(ghModule, (LPCSTR)MAKEINTRESOURCE(BMID_TOOLBASEU+index));

    }

    ghwndMain = CreateWindowEx(0L,
            GetStringRes(IDS_MFDEMOCL), GetStringRes2(IDS_EMF),
    WS_OVERLAPPED   | WS_CAPTION     | WS_BORDER       |
    WS_THICKFRAME   | WS_MAXIMIZEBOX | WS_MINIMIZEBOX  |
    WS_CLIPCHILDREN | WS_VISIBLE     | WS_SYSMENU,
            80, 70, 600, 300,
    NULL, hMenu, ghModule, NULL);

    if (ghwndMain == NULL)
return FALSE;

    SetWindowLong(ghwndMain, GWL_USERDATA, 0L);
    ghwndNext = SetClipboardViewer(ghwndMain);

    if (gbFit2Wnd)
        CheckMenuItem(hMenu, MM_FIT2WND, MF_CHECKED);
    else
        CheckMenuItem(hMenu, MM_FIT2WND, MF_UNCHECKED);

    if (gbImport3X)
        CheckMenuItem(hMenu, MM_IMPORT_3X, MF_CHECKED);
    else
        CheckMenuItem(hMenu, MM_IMPORT_3X, MF_UNCHECKED);

    if (gbExport3X)
        CheckMenuItem(hMenu, MM_EXPORT_3X, MF_CHECKED);
    else
        CheckMenuItem(hMenu, MM_EXPORT_3X, MF_UNCHECKED);

    SetFocus(ghwndMain);    /* set initial focus */

    gDib.ulFiles = gDib.ulFrames = 0;
    hDC = GetDC(NULL);
    ghHT = CreateHalftonePalette(hDC);
    ReleaseDC(NULL, hDC);

    return TRUE;
}

/******************************Public*Routine******************************\
*
* lProcessWmCreate
*
* Effects:  process WM_CREATE message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessWmCreate(HWND hWnd)
{
    SetWindowLong(hWnd, 0, (LONG)NULL);
    ghDCMem = CreateCompatibleDC(NULL);

    ghwndCtrlPanel = CreateDialog(ghModule,
                                    (LPCSTR)MAKEINTRESOURCE(DID_CTRLPANEL),
                                    hWnd,
                                    (DLGPROC) CtrlPanelDlgProc);

    ghwndDrawSurf = CreateWindow("DrawSurfClass",
                                    NULL,

WS_BORDER | WS_CHILD | WS_VISIBLE,
                                    0, 0, 0, 0,
                                    hWnd,
                                    NULL,
                                    ghModule,
                                    NULL);

    ghTextWnd = CreateWindow("Text",
                                NULL,
                                WS_BORDER | SS_LEFT | WS_CHILD | WS_VISIBLE,
                                0, 0, 0, 0,
                                hWnd,
                                NULL,               //(HMENU) 2,
                                ghModule,
                                NULL);

    ghbrRed     = CreateSolidBrush(RGB(255, 0, 0));
    ghbrAppBkgd = CreateSolidBrush(GetSysColor(COLOR_BACKGROUND));
    ghpnWide    = CreatePen(PS_SOLID, 5, RGB(0, 0, 0));
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessWmDrawClip
*
* Effects:  process the WM_DRAWCLIPBOARD message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessWmDrawClip(UINT message, DWORD wParam, LONG lParam)
{
    if ((IsClipboardFormatAvailable(CF_METAFILEPICT)) ||
        (IsClipboardFormatAvailable(CF_ENHMETAFILE)) )
        EnableMenuItem(hMenu, MM_PASTE, MF_ENABLED);
    else
        EnableMenuItem(hMenu, MM_PASTE,  MF_GRAYED);

    if (ghwndNext)
        SendMessage(ghwndNext, message, wParam, lParam);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessWmSize
*
* Effects:  process the WM_SIZE message
*
* Warnings:
*
\**************************************************************************/

LRESULT lProcessWmSize(HWND hWnd, UINT message, DWORD wParam, LONG lParam)
{
    RECT        rc;
    LONG        lcyCtrlPanel, lcyDrawSurf;

    GetWindowRect(ghwndCtrlPanel, &rc);
    lcyCtrlPanel = rc.bottom-rc.top;
    lcyDrawSurf = HIWORD(lParam) - lcyCtrlPanel - glcyStatus;

    //
    // CR!! Alternatively, this window can be created with cy
    //      equals to cy of the screen and saving this call
    //      altogether.
    //
    MoveWindow(ghwndCtrlPanel,
               0, 0, LOWORD(lParam), lcyCtrlPanel, TRUE);

    //
    // This ordering guarantees the text window paints correctly
    //
    MoveWindow(ghTextWnd,
               0, lcyCtrlPanel + lcyDrawSurf,
               LOWORD(lParam),                    // cx of hwnd
               glcyStatus, TRUE);

    MoveWindow(ghwndDrawSurf,
               0, lcyCtrlPanel,
               LOWORD(lParam),                    // cx of hwnd
               lcyDrawSurf, TRUE);

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

/******************************Public*Routine******************************\
*
* lProcessWmDestroy
*
* Effects:  process the WM_DESTROY message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessWmDestroy(VOID)
{
    if (ghDCMem != (HDC)NULL)
        DeleteDC(ghDCMem);
    if (ghMetaf != (HENHMETAFILE)NULL)
        DeleteEnhMetaFile(ghMetaf);
    DestroyWindow(ghwndCtrlPanel);
    DeleteObject(ghbrRed);
    DeleteObject(ghbrCur);
    DeleteObject(ghpnCur);
    DeleteObject(ghbrAppBkgd);
    DeleteObject(ghpnWide);
    if (ghHT)
        DeleteObject(ghHT);
    ChangeClipboardChain(ghwndMain, ghwndNext);
    bFreeDibFile(&gDib);
    PostQuitMessage(0);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDPlayStep
*
* Effects:  process the WM_COMMAND DID_X messages
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDPlayStep(DWORD wParam, INT *piPlus)
{
    HDC           hDCDrawSurf;
    ENHMETAHEADER EnhMetaHdr;
    RECT          rcClientDS;
    int           iRecord;
    PLAYINFO      PlayInfo;
    BOOL          bRCSet = FALSE;

    if (ghMetaf == 0)
        return 0L;

    GetEnhMetaFileHeader(ghMetaf, sizeof(EnhMetaHdr), &EnhMetaHdr);
    iRecord = LOWORD(wParam) - DID_ZERO + *piPlus;
    SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, iRecord, FALSE);
    PlayInfo.iRecord = iRecord;
    PlayInfo.bPlayContinuous = FALSE;
    *piPlus = 0;

    if ((EnhMetaHdr.nRecords > 1) && (iRecord > 0) &&
        (iRecord <= (INT) EnhMetaHdr.nRecords)) {
        hDCDrawSurf = GetDC(ghwndDrawSurf);

        bRCSet = bSetRC2MatchEmfRC(hDCDrawSurf, EnhMetaHdr, ghMetaf, NULL);
        if (!bRCSet) {
            vErrOut("lProcessDIDPlayStep failed");
        }

        if (gbFit2Wnd) {
            GetClientRect(ghwndDrawSurf, &rcClientDS);
            EnumEnhMetaFile(hDCDrawSurf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, &rcClientDS);
        } else {
            EnumEnhMetaFile(hDCDrawSurf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, (LPRECT)&EnhMetaHdr.rclBounds);
        }
        //
        // Enabling the user to record a metafile record selectively
        //

        if ((gbRecording) && (ghDCMetaf != NULL)) {
            EnumEnhMetaFile(ghDCMetaf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, (LPRECT)&EnhMetaHdr.rclBounds);
        }

        if (bRCSet) {
            bCleanUpRC();
        }

        ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDPlayTenPlus
*
* Effects:  process the WM_COMMAND DID_TEN_PLUS message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDPlayTenPlus(INT *piPlus)
{
    if (ghMetaf == 0)
        return 0L;

    *piPlus += 10;
    SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, *piPlus, FALSE);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMCopy
*
* Effects:  process the WM_COMMAND MM_COPY message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMCopy(VOID)
{
    if ((ghMetaf == 0) && (ghmf == 0)) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFFORCPY));
        return 0L;
    }

    OpenClipboard(ghwndMain);
    EmptyClipboard();

    if (gbExport3X)
    {
        HGLOBAL         hmem;
        LPMETAFILEPICT  lpmfp;
        RECT            rcClientDS;
        DWORD           x, y, mm;
        HDC             hDCDrawSurf;
        LPBYTE          pjData;
        UINT            uiSize;

        hDCDrawSurf = GetDC(ghwndDrawSurf);

        if (ghmf == 0) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_CNVEMFTO3X));

            if (!(uiSize = GetWinMetaFileBits(ghMetaf, 0, NULL, MM_ANISOTROPIC, hDCDrawSurf))) {
                MessageBox(ghwndMain, GetStringRes(IDS_GETWMFBTSFAIL),
                    GetStringRes(IDS_ERROR), MB_OK);
                goto COPY_3X_EXIT;
            }

            if ((pjData = (LPBYTE) LocalAlloc(LMEM_FIXED, uiSize)) == NULL) {
                MessageBox(ghwndMain, GetStringRes(IDS_MALLOCFAIL),
                    GetStringRes(IDS_ERROR), MB_OK);
                goto COPY_3X_EXIT;
            }

            if (!(uiSize = GetWinMetaFileBits(ghMetaf, uiSize, pjData, MM_ANISOTROPIC, hDCDrawSurf))) {
                MessageBox(ghwndMain, GetStringRes(IDS_GETWMFBTSFAIL2),
                    GetStringRes(IDS_ERROR), MB_OK);
                LocalFree(pjData);
                goto COPY_3X_EXIT;
            }

            ghmf = SetMetaFileBitsEx(uiSize, (LPBYTE) pjData);
            LocalFree(pjData);
        }

        if ((hmem = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE,
                            sizeof(METAFILEPICT))) == NULL) {

            SetWindowText(ghTextWnd, GetStringRes(IDS_MALLOCFAIL2));
            goto COPY_3X_EXIT;

        }

        lpmfp = (LPMETAFILEPICT)GlobalLock(hmem);
        lpmfp->mm = mm = MM_ANISOTROPIC;

        GetClientRect(ghwndDrawSurf, &rcClientDS);
        x = rcClientDS.right - rcClientDS.left;
        x *= 2540;
        x /= GetDeviceCaps(hDCDrawSurf, LOGPIXELSX);
        lpmfp->xExt = x;                                // ie. in 0.01mm

        y = rcClientDS.bottom - rcClientDS.top;
        y *= 2540;
        y /= GetDeviceCaps(hDCDrawSurf, LOGPIXELSY);
        lpmfp->yExt = y;                                // ie. in 0.01mm

        lpmfp->hMF = CopyMetaFile(ghmf, NULL);

        GlobalUnlock(hmem);
        SetWindowText(ghTextWnd, GetStringRes(IDS_CPY3XMFTOCLP));
        SetClipboardData(CF_METAFILEPICT, hmem);

COPY_3X_EXIT:
        ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
        goto COPY_EXIT;

    }

    //
    // gbExport3X == FALSE
    //
    if (ghMetaf == 0)           // requires conversion
    {
        UINT            uiSize;
        LPVOID          pvData;
        HDC             hDCDrawSurf;

        SetWindowText(ghTextWnd, "Converting 3X Metafile to Enhanced Metafile format");
        if (!(uiSize = GetMetaFileBitsEx(ghmf, 0, NULL))) {
            MessageBox(ghwndMain, GetStringRes(IDS_GETMFBTSXFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            SetWindowText(ghTextWnd, GetStringRes(IDS_CNVFAIL));
            goto COPY_EXIT;
        }

        if ((pvData = (LPVOID) LocalAlloc(LMEM_FIXED, uiSize)) == NULL) {
            MessageBox(ghwndMain, GetStringRes(IDS_MALLOCFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            SetWindowText(ghTextWnd, GetStringRes(IDS_CNVFAIL));
            goto COPY_EXIT;
        }

        if (!(uiSize = GetMetaFileBitsEx(ghmf, uiSize, pvData))) {
            MessageBox(ghwndMain, GetStringRes(IDS_GETWMFBTSFAIL2X),
                GetStringRes(IDS_ERROR), MB_OK);
            goto COPY_ENH_EXIT;
        }

        hDCDrawSurf = GetDC(ghwndDrawSurf);

        // !!! provide the correct picture extents in the METAFILEPICT structure
        // where possible
        ghMetaf = SetWinMetaFileBits(uiSize, (LPBYTE)pvData, hDCDrawSurf, NULL);

COPY_ENH_EXIT:
        LocalFree(pvData);
        ReleaseDC(ghwndDrawSurf ,hDCDrawSurf);

        if (ghMetaf == 0) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_CNVFAIL));
            goto COPY_EXIT;
        }

    }

    //
    // No Conversion required
    //
    {

        HENHMETAFILE hEmfTmp;

        hEmfTmp = CopyEnhMetaFile(ghMetaf, NULL);

        if (hEmfTmp) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_CPYEMFTOCLP));
            SetClipboardData(CF_ENHMETAFILE, hEmfTmp);
            DeleteEnhMetaFile(hEmfTmp);
        }

    }

COPY_EXIT:

    CloseClipboard();
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMPaste
*
* Effects:  process the WM_COMMAND MM_PASTE message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMPaste(HWND hwnd, BOOL *pbReset)
{

    OpenClipboard(ghwndMain);

    if (gbImport3X)
    {
        HANDLE      hmem;
        DWORD       dwXSugExt, dwYSugExt, dwMM;
        HDC         hDCDrawSurf;
        RECT        rc;
        INT         iSavedDC;

        hmem = GetClipboardData(CF_METAFILEPICT);

        if (hmem)
        {
            LPMETAFILEPICT lpmfp;

            SetWindowText(ghTextWnd, GetStringRes(IDS_PST3XMF));
            lpmfp = (LPMETAFILEPICT)GlobalLock(hmem);
            ghmf  = lpmfp->hMF;
            dwMM  = lpmfp->mm;
            dwXSugExt = lpmfp->xExt;        // in 0.01 mm
            dwYSugExt = lpmfp->yExt;
            GlobalUnlock(hmem);

            hDCDrawSurf = GetDC(ghwndDrawSurf);

            iSavedDC = SaveDC(hDCDrawSurf);

            GetClientRect(ghwndDrawSurf, &rc);

            SetMapMode(hDCDrawSurf, dwMM);
            if ((dwXSugExt > 0 )&& (dwYSugExt > 0))
            {                               // suggested width & height of image
                DWORD x;
                DWORD y;

                // no. of pixels in x and y
                x = dwXSugExt;
                x *= GetDeviceCaps(hDCDrawSurf,LOGPIXELSX);
                x /= 2540;

                y = dwYSugExt;
                y *= GetDeviceCaps(hDCDrawSurf,LOGPIXELSY);
                y /= 2540;

                SetWindowExtEx(hDCDrawSurf, x, y, NULL);

                if (gbFit2Wnd)
                    SetViewportExtEx(hDCDrawSurf, rc.right, rc.bottom, NULL);
                else
                    SetViewportExtEx(hDCDrawSurf, x, y, NULL);

            } else {
                SetWindowText(ghTextWnd, GetStringRes(IDS_NOINFO3XMFX));
                SetWindowExtEx(hDCDrawSurf, rc.right, rc.bottom, NULL);
                SetViewportExtEx(hDCDrawSurf, rc.right, rc.bottom, NULL);
            }

            SetViewportOrgEx(hDCDrawSurf, 0, 0, NULL);
            SetWindowOrgEx(hDCDrawSurf, 0, 0, NULL);

            SetBoundsRect(hDCDrawSurf, NULL, DCB_ENABLE | DCB_SET);
            PlayMetaFile(hDCDrawSurf, ghmf);
            {
            UINT    uiRC;
            char    text[128];

            wsprintf(text, "dwMM = %d\n", dwMM);
            OutputDebugString(text);
            wsprintf(text, "dwXSugExt = %d\n", dwXSugExt);
            OutputDebugString(text);
            wsprintf(text, "dwYSugExt = %d\n", dwYSugExt);
            OutputDebugString(text);

            uiRC = GetBoundsRect(hDCDrawSurf, &rc, DCB_RESET); // in logical coordinates
            wsprintf(text, "GetBoundsRect = %d\n", uiRC);
            OutputDebugString(text);
            wsprintf(text, "left     = %d\n", rc.left);
            OutputDebugString(text);
            wsprintf(text, "right    = %d\n", rc.right);
            OutputDebugString(text);
            wsprintf(text, "top      = %d\n", rc.top);
            OutputDebugString(text);
            wsprintf(text, "bottom   = %d\n", rc.bottom);
            OutputDebugString(text);
            }

// !!!
// saving the wmf as an Aldus mf
//
{
OPENFILENAME    ofn;
char            szFile[256], szFileTitle[256];
static char     *szFilter;
UINT            uiSize;
HANDLE          hFile, hMapFile;
LPVOID          pMapFile;
DWORD           dwHigh, dwLow;

BuildFilterStrs(-1);
szFilter = BuildFilterStrs(IDS_FT_WMF);

strcpy(szFile, "*.wmf\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrCustomFilter = (LPSTR) NULL;
ofn.nMaxCustFilter = 0L;
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = GetStringRes(IDS_SAVEMF);
ofn.Flags = 0L;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = "WMF";

if (!GetOpenFileName(&ofn))
    return 0L;

uiSize = GetMetaFileBitsEx(ghmf, 0, NULL);
dwHigh = 0;
dwLow  = uiSize;

if ((hFile = CreateFile(szFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == (HANDLE)-1) {
    MessageBox(ghwndMain, GetStringRes(IDS_FILEOPNFAIL),
        GetStringRes(IDS_ERROR), MB_OK);
    return 0L;
}

//
// Create a map file of the opened file
//
if ((hMapFile = CreateFileMapping(hFile, NULL,
                         PAGE_READWRITE, dwHigh, dwLow, "MapF")) == NULL) {
    MessageBox(ghwndMain, GetStringRes(IDS_MAPFCREFAIL),
        GetStringRes(IDS_ERROR), MB_OK);
    goto ErrorExit1;
}

//
// Map a view of the whole file
//
if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_WRITE, 0, 0, uiSize)) == NULL) {
    MessageBox(ghwndMain, GetStringRes(IDS_MAPVWMAPFOFAIL),
        GetStringRes(IDS_ERROR), MB_OK);
    goto ErrorExit2;
}

if (uiSize) {
    APMFILEHEADER   AldHdr;
    PAPMFILEHEADER  pAldHdr;
    PBYTE           pjTmp;
    INT             iSize;
    char            text[128];

    AldHdr.key = ALDUS_ID;
    AldHdr.hmf = 0;                                 // Unused; must be zero
    AldHdr.bbox.Left   = 0;                         // in metafile units
    AldHdr.bbox.Top    = 0;
    //AldHdr.bbox.Right  = rc.right - rc.left;        // in logical coordinates
    //AldHdr.bbox.Bottom = rc.bottom - rc.top;

    switch (dwMM) {
        case MM_HIENGLISH:
            AldHdr.inch = 1000;
            AldHdr.bbox.Right  = (SHORT)dwXSugExt;
            AldHdr.bbox.Bottom = (SHORT)dwYSugExt;
            break;
        case MM_HIMETRIC:
            AldHdr.inch = 1440;
            AldHdr.bbox.Right  = (SHORT)(dwXSugExt / 2540 * 1440);
            AldHdr.bbox.Bottom = (SHORT)(dwYSugExt / 2540 * 1440);
            break;
        case MM_LOENGLISH:
            AldHdr.inch = 100;
            AldHdr.bbox.Right  = (SHORT)dwXSugExt;
            AldHdr.bbox.Bottom = (SHORT)dwYSugExt;
            break;
        case MM_LOMETRIC:
            AldHdr.inch = 254;
            AldHdr.bbox.Right  = (SHORT)dwXSugExt;
            AldHdr.bbox.Bottom = (SHORT)dwYSugExt;
            break;
        case MM_TEXT:
            AldHdr.inch = (WORD) (GetDeviceCaps(hDCDrawSurf, HORZRES) * 25.4 /
                          GetDeviceCaps(hDCDrawSurf, HORZSIZE));
            AldHdr.bbox.Right  = (SHORT)dwXSugExt;
            AldHdr.bbox.Bottom = (SHORT)dwYSugExt;
            break;
        case MM_TWIPS:
            AldHdr.inch = 1440;
            AldHdr.bbox.Right  = (SHORT)dwXSugExt;
            AldHdr.bbox.Bottom = (SHORT)dwYSugExt;
            break;
        default:
            AldHdr.inch = 1440;
            AldHdr.bbox.Right  = (SHORT)(dwXSugExt / 2540 * 1440);
            AldHdr.bbox.Bottom = (SHORT)(dwYSugExt / 2540 * 1440);
            break;
    }

    wsprintf(text, "MM           = %d\n", dwMM);
    OutputDebugString(text);
    wsprintf(text, "AldHdr.inch  = %d\n", AldHdr.inch);
    OutputDebugString(text);

    AldHdr.reserved = 0;
    AldHdr.checksum = 0;
    {
    WORD    *p;

    for (p = (WORD *)&AldHdr, AldHdr.checksum = 0;
            p < (WORD *)&(AldHdr.checksum); ++p)
        AldHdr.checksum ^= *p;
    }

    pAldHdr = &AldHdr;
    pjTmp = (PBYTE)pMapFile;

    iSize = 22;

    //!!! use memcpy...
    while (iSize--) {
        *(((PBYTE)pjTmp)++) = *(((PBYTE)pAldHdr)++);
    }

    pMapFile = (PBYTE)pMapFile + 22;
    GetMetaFileBitsEx(ghmf, uiSize, pMapFile);
}

UnmapViewOfFile(pMapFile);

ErrorExit2:
  CloseHandle(hMapFile);
ErrorExit1:
  CloseHandle(hFile);
}

            RestoreDC(hDCDrawSurf, iSavedDC);
            ReleaseDC(ghwndDrawSurf, hDCDrawSurf);

        } else {
            SetWindowText(ghTextWnd, GetStringRes(IDS_GET3XFRMCLPFAIL));
        }

        goto PASTE_EXIT;

    }

    //
    // gbImport3X == FALSE
    //
    {
        HENHMETAFILE hEmfTmp;
        ENHMETAHEADER EnhMetaHdr;

        hEmfTmp = GetClipboardData(CF_ENHMETAFILE);
        if (hEmfTmp) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_PSTEMF));
            if (ghMetaf != (HENHMETAFILE)NULL)
                DeleteEnhMetaFile(ghMetaf);
            ghMetaf = CopyEnhMetaFile(hEmfTmp, NULL);
            DeleteEnhMetaFile(hEmfTmp);
            GetEnhMetaFileHeader(ghMetaf, sizeof(EnhMetaHdr), &EnhMetaHdr);
            SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, EnhMetaHdr.nRecords, FALSE);
            *pbReset = TRUE;
        } else {
            SetWindowText(ghTextWnd, GetStringRes(IDS_GETEMFFROMCLPFAIL));
        }
    }
PASTE_EXIT:

    CloseClipboard();
    EnableMenuItem(hMenu, MM_COPY,  MF_ENABLED);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMDel
*
* Effects:  process the WM_COMMAND MM_DEL message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMDel(VOID)
{
    OpenClipboard(ghwndMain);
    EmptyClipboard();
    CloseClipboard();
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMPen
*
* Effects:  process the WM_COMMAND MM_PEN message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMPen(HWND hwnd)
{
    HDC     hDC;
    DWORD   dwRGB;

    if (bChooseNewColor(hwnd, &dwRGB)) {
        hDC = GetDC(ghwndDrawSurf);
        if (ghpnCur != NULL)
            DeleteObject(ghpnCur);
        ghpnCur = CreatePen(PS_SOLID, 1, dwRGB);
        SelectObject(hDC, ghpnCur);
        if (ghDCMetaf != NULL)
            SelectObject(ghDCMetaf, ghpnCur);
        ReleaseDC(ghwndDrawSurf, hDC);
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMBrush
*
* Effects:  process the WM_COMMAND MM_BRUSH message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMBrush(HWND hwnd)
{
    HDC     hDC;
    static DWORD   dwRGB=RGB(255, 255, 255);

    if (bChooseNewColor(hwnd, &dwRGB)) {
        hDC = GetDC(ghwndDrawSurf);
        if (ghbrCur != NULL)
            DeleteObject(ghbrCur);
        ghbrCur = hBrCreateBrush(hDC, dwRGB);
        SelectObject(hDC, ghbrCur);
        if (ghDCMetaf != NULL)
            SelectObject(ghDCMetaf, ghbrCur);
        ReleaseDC(ghwndDrawSurf, hDC);
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMFont
*
* Effects:  process the WM_COMMAND MM_FONT message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMFont(VOID)
{
    HDC     hDC;
    char    text[128];

    if (bChooseNewFont(ghwndMain, &glf, &gCrText)) {
        ghCurFont = CreateFontIndirect(&glf);

        hDC = GetDC(ghwndDrawSurf);
        EnumFonts(hDC, glf.lfFaceName, (FONTENUMPROC)iTT, (LPARAM)&gbTT);
        wsprintf(text, "gbTT = %d\n", gbTT);
        //OutputDebugString(text);
        ReleaseDC(ghwndDrawSurf, hDC);

        if (ghDCMetaf != NULL)
            SelectObject(ghDCMetaf, ghCurFont);
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMStrokeFill
*
* Effects:  process the WM_COMMAND MM_TTOUTLN_STROKEFILL message
*
* Warnings:
*
* History:
*  03-Jan-1996 -by- Petrus Wong [PetrusW]
* Wrote it.
\**************************************************************************/

LONG lProcessMMStrokeFill(VOID)
{
    gbSFOutln = (gbSFOutln ? FALSE : TRUE);
    if (gbSFOutln) {
        CheckMenuItem(hMenu, MM_TTOUTLN_STROKEFILL, MF_CHECKED);
        CheckMenuItem(hMenu, MM_TTOUTLN_POLYDRAW, MF_UNCHECKED);
        gbPDOutln = FALSE;
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMPolyDraw
*
* Effects:  process the WM_COMMAND MM_TTOUTLN_POLYDRAW message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMPolyDraw(VOID)
{
    gbPDOutln = (gbPDOutln ? FALSE : TRUE);
    if (gbPDOutln) {
        CheckMenuItem(hMenu, MM_TTOUTLN_STROKEFILL, MF_UNCHECKED);
        CheckMenuItem(hMenu, MM_TTOUTLN_POLYDRAW, MF_CHECKED);
        gbSFOutln = FALSE;
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMMltfmts
*
* Effects:  process the WM_COMMAND MM_C_MLTFMTS message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMMltfmts(VOID)
{
    CMTMLTFMT *pMfmt;

    if ((gbRecording) && (ghDCMetaf != NULL)) {
if ((pMfmt = pLoadMltFmtFile()) != NULL) {
    GdiComment(ghDCMetaf,
       sizeof(CMTMLTFMT)+pMfmt->aemrformat[0].cbData,
       (CONST BYTE *) pMfmt);
    Free(pMfmt);
}
else
    MessageBox(ghwndMain,
                       GetStringRes(IDS_LDMFMTFILEFAIL),
       GetStringRes(IDS_ERROR),
       MB_OK);
    }

    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMHittest
*
* Effects:  process the WM_COMMAND MM_HITTEST message
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMHittest(VOID)
{
    static BOOL bHitTest=FALSE;
    HWNDhwndRecBtn;

    bHitTest = (bHitTest ? FALSE : TRUE);
    hwndRecBtn = GetDlgItem(ghwndCtrlPanel, DID_RECORD);
    if (bHitTest) {
CheckMenuItem(hMenu, MM_HITTEST, MF_CHECKED);
EnableMenuItem(hMenu, MM_RECORD, MF_GRAYED);
EnableWindow(hwndRecBtn, FALSE);
gbHitTest = TRUE;
    } else {
CheckMenuItem(hMenu, MM_HITTEST, MF_UNCHECKED);
EnableMenuItem(hMenu, MM_RECORD, MF_ENABLED);
EnableWindow(hwndRecBtn, TRUE);
gbHitTest = FALSE;
return 0L;
    }

    if (ghMetaf == 0) {
SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFLDFORHTST));
return 0L;
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMLEAbout
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMLEAbout(VOID)
{
    if (DialogBox(ghModule, (LPCSTR)"AboutBox", ghwndMain, (DLGPROC)About) == -1)
MessageBox(ghwndMain, GetStringRes(IDS_DLGABOUTCREFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMAbout
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMAbout(VOID)
{
    if (DialogBox(ghModule, "AboutBox", ghwndMain, (DLGPROC)About) == -1)
       MessageBox(ghwndMain, GetStringRes(IDS_DLGABOUTCREFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMLoadMaskBmp
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMLoadMaskBmp(HWND hwnd)
{
    SetWindowText(ghTextWnd, GetStringRes(IDS_LDMSKBM));
    bGetBMP(hwnd, TRUE);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMLoadBmp
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMLoadBmp(HWND hwnd)
{
    SetWindowText(ghTextWnd, GetStringRes(IDS_LDBM));
    bGetBMP(hwnd, FALSE);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMSaveBmp
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMSaveBmp(VOID)
{
    SetWindowText(ghTextWnd, GetStringRes(IDS_SVDRAWSFASBM));
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDOpen
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDOpen(HWND hwnd, CHAR *szLoadedMetaf, BOOL *pbReset)
{
    ENHMETAHEADER EnhMetaHdr;
    HENHMETAFILE  hEmfTmp;

    SetWindowText(ghTextWnd, GetStringRes(IDS_LDMF));
    //
    // If user hit cancel, we still have the original metafile
    //
    //DeleteEnhMetaFile(ghMetaf);
    //ghMetaf = hemfLoadMetafile(hwnd);
    hEmfTmp = hemfLoadMetafile(hwnd);
    if (hEmfTmp != 0) {
char szDesc[256];

if (ghMetaf != (HENHMETAFILE)NULL)
            DeleteEnhMetaFile(ghMetaf);
ghMetaf = CopyEnhMetaFile(hEmfTmp, NULL);
GetEnhMetaFileHeader(ghMetaf, sizeof(EnhMetaHdr), &EnhMetaHdr);
SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, EnhMetaHdr.nRecords, FALSE);
DeleteEnhMetaFile(hEmfTmp);
EnableMenuItem(hMenu, MM_COPY,MF_ENABLED);
if (GetEnhMetaFileDescription(ghMetaf, 256, szDesc) != 0) {
    char    szText[256];
    char    *szTmp, szSource[256];

    szTmp = (char *)strtok(szDesc, "\\0");
    strcpy(szSource, szTmp);
    szTmp = (char *)strtok(NULL, "\\0\\0");
    wsprintf(szText, "Source: %s  Title: %s", szSource, szTmp);
    SetWindowText(ghTextWnd, szText);
    strcpy(szLoadedMetaf, szTmp);
} else {
    strcpy(szLoadedMetaf, "");

}
    //} else {
    //  SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, 0, FALSE);
    }
    *pbReset = TRUE;
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMRec
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMRec(HWND hwnd, CHAR *szFilename)
{
    if (gbHitTest) {
SetWindowText(ghTextWnd, GetStringRes(IDS_MUSTESCHTSTMD));
return 0L;
    }

    SetWindowText(ghTextWnd, GetStringRes(IDS_REC));
    if (!gbRecording) {
ghDCMetaf = hDCRecordMetafileAs(hwnd, szFilename);
    }

    if (ghDCMetaf == NULL) {
       SetWindowText(ghTextWnd, GetStringRes(IDS_CREMFDCFAIL));
       return 0L;
    }

    // Parse the szFilename for the title and GdiComment the metafile with it.
    {
charszComment[256];
char*szTmp, szTmp2[256];

szTmp = (char *)strtok(szFilename, "\\");
strcpy(szTmp2, szTmp);
while (szTmp != NULL) {
    szTmp = (char *)strtok(NULL, "\\");
    if (szTmp != NULL) {
strcpy(szTmp2, szTmp);
    }
}
szTmp = (char *)strtok(szTmp2, ".");
wsprintf((LPSTR) szComment, "MfEdit:\\0%s\\0\\0", szTmp);
#if 0
if (!GdiComment(ghDCMetaf, 256, szComment)) {
    MessageBox(ghwndMain, GetStringRes(IDS_ADDCMNTFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
}
#endif
    }

    gbRecording = TRUE;

    if (ghpnCur != NULL)
SelectObject(ghDCMetaf, ghpnCur);

    if (ghbrCur != NULL)
SelectObject(ghDCMetaf, ghbrCur);

    if (ghCurFont != NULL)
SelectObject(ghDCMetaf, ghCurFont);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDRec
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDRec(HWND hwnd, INT *piMetafCnt, char *szFilename)
{
    int  iWidthMM, iHeightMM, iWidthPels, iHeightPels, iMMPerPelX, iMMPerPelY;
    char szComment[256];
    char szTitle[256];
    RECT rc;
    HDC  hDC;

    if (gbHitTest) {
SetWindowText(ghTextWnd, GetStringRes(IDS_MUSTESCHTSTMD));
return 0L;
    }

    SetWindowText(ghTextWnd, GetStringRes(IDS_REC));
    if (!gbRecording) {

hDC = GetDC(hwnd);
iWidthMM    = GetDeviceCaps(hDC, HORZSIZE);
iHeightMM   = GetDeviceCaps(hDC, VERTSIZE);
iWidthPels  = GetDeviceCaps(hDC, HORZRES);
iHeightPels = GetDeviceCaps(hDC, VERTRES);
ReleaseDC(hwnd, hDC);
iMMPerPelX  = (iWidthMM * 100)/iWidthPels;
iMMPerPelY  = (iHeightMM * 100)/iHeightPels;
GetClientRect(ghwndDrawSurf, &rc);
rc.left   = rc.left * iMMPerPelX;
rc.top  = rc.top * iMMPerPelY;
rc.right  = rc.right * iMMPerPelX;
rc.bottom = rc.bottom * iMMPerPelY;

{
   char szFilenameWithExt[256];
   char suffix[20];
   char szDesc[256];
   char *szTmp, szTmp2[256];

   //
   // assemble a new metafile name with the emf extension from
   // the generic szFilename
   //
   wsprintf((LPSTR) suffix, "%d.emf", *piMetafCnt);
   *piMetafCnt++;
   strcpy(szFilenameWithExt, szFilename);
   strcat(szFilenameWithExt, suffix);

   //
   // parse szFilename for the title for description
   //
   szTmp = (char *)strtok(szFilename, "\\");
   strcpy(szTmp2, szTmp);
   while (szTmp != NULL) {
       szTmp = (char *)strtok(NULL, "\\");
       if (szTmp != NULL) {
   strcpy(szTmp2, szTmp);
       }
   }
   szTmp = (char *)strtok(szTmp2, ".");
   strcpy(szTitle, szTmp);
   wsprintf(szDesc, "SDK Enhanced Metafile Editor\\0%s\\0\\0", szTitle);
   ghDCMetaf = CreateEnhMetaFile((HDC)NULL, szFilenameWithExt, (LPRECT)&rc, (LPSTR)szDesc);
           if (gb3D)
                bSetupRC(ghDCMetaf, NULL);
}

        bSetAdvancedGraphics(ghDCMetaf);
    }

    if (ghDCMetaf == NULL) {
       SetWindowText(ghTextWnd, GetStringRes(IDS_CREMFDCFAIL));
       return 0L;
    }
    wsprintf((LPSTR) szComment, "MfEdit:\\0%s\\0\\0", szTitle);
#if 0
    if (!GdiComment(ghDCMetaf, 256, szComment)) {
MessageBox(ghwndMain, GetStringRes(IDS_ADDCMNTFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
    }
#endif
    gbRecording = TRUE;

    if (ghpnCur != NULL)
SelectObject(ghDCMetaf, ghpnCur);

    if (ghbrCur != NULL)
SelectObject(ghDCMetaf, ghbrCur);

    if (ghCurFont != NULL)
SelectObject(ghDCMetaf, ghCurFont);

    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDStop
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDStop(VOID)
{
    SetWindowText(ghTextWnd, GetStringRes(IDS_STOP));
    if (gbRecording) {
        if (gb3D && gbRCSet)
            bCleanUpRC();
ghMetaf = CloseEnhMetaFile(ghDCMetaf);
gbRecording = FALSE;
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDPlay
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDPlay(char *szLoadedMetaf)
{
    HDC                     hDCDrawSurf;
    ENHMETAHEADER           EnhMetaHdr;
    RECT                    rcClientDS;
    int                     iEntries;
    PLOGPALETTE             plogPal;
    PBYTE                   pjTmp;
    HPALETTE                hPal;
    char                    szTmp[256];
    BOOL    bRCSet = FALSE;
#ifdef OPENGL_EMF
    PIXELFORMATDESCRIPTOR   pfd;
#endif
    BOOL                    bHasPFD = TRUE;

    wsprintf(szTmp, GetStringRes(IDS_FMT_PLAYMF), szLoadedMetaf);
    SetWindowText(ghTextWnd, szTmp);
    if (ghMetaf != NULL) {
hDCDrawSurf = GetDC(ghwndDrawSurf);
GetEnhMetaFileHeader(ghMetaf, sizeof(ENHMETAHEADER), &EnhMetaHdr);

iEntries = GetEnhMetaFilePaletteEntries(ghMetaf, 0, NULL);

if (iEntries) {
    if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
    sizeof(DWORD) + sizeof(PALETTEENTRY)*iEntries )) == NULL) {
MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
                    GetStringRes(IDS_ERROR), MB_OK);
    }

    plogPal->palVersion = 0x300;
    plogPal->palNumEntries = (WORD) iEntries;
    pjTmp = (PBYTE) plogPal;
    pjTmp += 4;

    GetEnhMetaFilePaletteEntries(ghMetaf, iEntries, (PPALETTEENTRY)pjTmp);
    hPal = CreatePalette(plogPal);
    GlobalFree(plogPal);

    SelectPalette(hDCDrawSurf, hPal, FALSE);
    RealizePalette(hDCDrawSurf);
}

#ifdef OPENGL_EMF
        bRCSet = bSetRC2MatchEmfRC(hDCDrawSurf, EnhMetaHdr, ghMetaf, &pfd);
        bHasPFD = (pfd.nSize != 0) ? TRUE : FALSE;
        if (!bRCSet) {
            vErrOut("lProcessDIDPlay (DspDC) failed");
        }
#endif
if (gbFit2Wnd) {
    GetClientRect(ghwndDrawSurf, &rcClientDS);
    if (!PlayEnhMetaFile( hDCDrawSurf, ghMetaf, (LPRECT) &rcClientDS)) {
chartext[128];

wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
OutputDebugString(text);
    } else {
                if (gbDB)
                   SwapBuffers(hDCDrawSurf);
            }
} else {
    RECT rc;

    rc.top = rc.left = 0;
    rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
    rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
    if (!PlayEnhMetaFile( hDCDrawSurf, ghMetaf, (LPRECT) &rc)) {
chartext[128];

wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
OutputDebugString(text);
    } else {
                if (gbDB)
                   SwapBuffers(hDCDrawSurf);
            }
}

        if (bRCSet) {
            bCleanUpRC();
            bRCSet = FALSE;
        }

//
// Enabling the user to embed another metafile
//
if ((gbRecording) && (ghDCMetaf != NULL)) {
    if (hPal != (HPALETTE)NULL) {
SelectPalette(ghDCMetaf, hPal, FALSE);
RealizePalette(ghDCMetaf);
    }

#ifdef OPENGL_EMF
            if (EnhMetaHdr.bOpenGL && !gbRCSet) {
                char        text[128];

                bRCSet = bSetupRC(ghDCMetaf, (bHasPFD ? &pfd : NULL));
                wsprintf(text,
                         "lProcessDIDPlay: (MetaDC) bRCSet=%ld, bHasPFD=%ld",
                         bRCSet, bHasPFD);
                vErrOut(text);
            }
#endif
    {
    RECT rc;

    rc.top = rc.left = 0;
    rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
    rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
    if (!PlayEnhMetaFile( ghDCMetaf, ghMetaf, (LPRECT) &rc)) {
chartext[128];

wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
OutputDebugString(text);
    } else {
                if (gbDB)
                   SwapBuffers(ghDCMetaf);
            }

            if (bRCSet) {
                bCleanUpRC();
            }

    }
}

ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
    } else {
SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFFORPLAY));
    }

    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDFf
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDFf(BOOL *pbReset)
{
    HDC     hDCDrawSurf;
    ENHMETAHEADER   EnhMetaHdr;
    RECT    rcClientDS;
    static int    iRecord = 0;
    PLAYINFO    PlayInfo;
    int     iEntries;
    PLOGPALETTE     plogPal;
    PBYTE    pjTmp;
    HPALETTE    hPal;
    static BOOL     bInitRC = FALSE;
    static BOOL     bRCSet = FALSE;
    BOOL    bHasPFD = FALSE;

    if (ghMetaf == 0)
return 0L;

    PlayInfo.iRecord = ++iRecord;
    PlayInfo.bPlayContinuous = TRUE;

    GetEnhMetaFileHeader(ghMetaf, sizeof(EnhMetaHdr), &EnhMetaHdr);
    SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, iRecord, FALSE);
    if ((EnhMetaHdr.nRecords > 1) && (iRecord <= (INT)EnhMetaHdr.nRecords)) {
hDCDrawSurf = GetDC(ghwndDrawSurf);

iEntries = GetEnhMetaFilePaletteEntries(ghMetaf, 0, NULL);

if (iEntries) {
    if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
    sizeof(DWORD) + sizeof(PALETTEENTRY)*iEntries )) == NULL) {
MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
                    GetStringRes(IDS_ERROR), MB_OK);
    }

    plogPal->palVersion = 0x300;
    plogPal->palNumEntries = (WORD) iEntries;
    pjTmp = (PBYTE) plogPal;
    pjTmp += 4;

    GetEnhMetaFilePaletteEntries(ghMetaf, iEntries, (PPALETTEENTRY)pjTmp);
    hPal = CreatePalette(plogPal);
    GlobalFree(plogPal);

    SelectPalette(hDCDrawSurf, hPal, FALSE);
    RealizePalette(hDCDrawSurf);
}

        if (!bInitRC) {
            bRCSet = bSetRC2MatchEmfRC(hDCDrawSurf, EnhMetaHdr, ghMetaf, NULL);
            bInitRC = TRUE;
            if (!bRCSet) {
                vErrOut("lProcessDIDFf failed");
            }
        }

if (gbFit2Wnd) {
    GetClientRect(ghwndDrawSurf, &rcClientDS);
    EnumEnhMetaFile(hDCDrawSurf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, (LPRECT) &rcClientDS);
} else {
    EnumEnhMetaFile(hDCDrawSurf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, (LPRECT) &EnhMetaHdr.rclBounds);
}
//
// Enabling the user to record a metafile records selectively
//
if ((gbRecording) && (ghDCMetaf != NULL)) {
    SelectPalette(ghDCMetaf, hPal, FALSE);
    RealizePalette(ghDCMetaf);
    EnumEnhMetaFile(ghDCMetaf, ghMetaf, (ENHMFENUMPROC)bPlayRecord, (LPVOID) &PlayInfo, (LPRECT)&EnhMetaHdr.rclBounds);
}

ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
    }

    if ((iRecord == (INT) EnhMetaHdr.nRecords) || *pbReset) {
iRecord = 0;
if (*pbReset)
    SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, 0, FALSE);
*pbReset = FALSE;

        if (bRCSet) {
            bCleanUpRC();
            bRCSet = FALSE;
        }

        bInitRC = FALSE;
    }
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessDIDClear
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessDIDClear(VOID)
{
    HDC     hDCDrawSurf;
    HGDIOBJ hObjOld;
    RECT    rcDrawSurf;

    SetWindowText(ghTextWnd, GetStringRes(IDS_DRAWSFCLR));
    hDCDrawSurf = GetDC(ghwndDrawSurf);
    ghbrAppBkgd = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    hObjOld = SelectObject(hDCDrawSurf, ghbrAppBkgd);
    GetClientRect(ghwndDrawSurf, &rcDrawSurf);
    PatBlt(hDCDrawSurf, 0, 0, rcDrawSurf.right, rcDrawSurf.bottom, PATCOPY);
    ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
    SelectObject(hDCDrawSurf, hObjOld);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMImport3X
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMImport3X(VOID)
{
    gbImport3X = (gbImport3X ? FALSE : TRUE);

    if (gbImport3X)
CheckMenuItem(hMenu, MM_IMPORT_3X, MF_CHECKED);
    else
CheckMenuItem(hMenu, MM_IMPORT_3X, MF_UNCHECKED);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMExport3X
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMExport3X(VOID)
{
    gbExport3X = (gbExport3X ? FALSE : TRUE);

    if (gbExport3X)
CheckMenuItem(hMenu, MM_EXPORT_3X, MF_CHECKED);
    else
CheckMenuItem(hMenu, MM_EXPORT_3X, MF_UNCHECKED);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMFit2Wnd
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMFit2Wnd(VOID)
{
    gbFit2Wnd = (gbFit2Wnd ? FALSE : TRUE);

    if (gbFit2Wnd)
CheckMenuItem(hMenu, MM_FIT2WND, MF_CHECKED);
    else
CheckMenuItem(hMenu, MM_FIT2WND, MF_UNCHECKED);
    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessMMPrint
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessMMPrint(VOID)
{
    DWORD   dwThrdID;
    HANDLE  hThrd;
    PPRTDATA pPrtData;

    if (ghMetaf == 0) {
SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFFORPRT));
return 0L;
    }

    //
    // bPrintMf is supposed to free up the memory when done.
    //
    if ((pPrtData = (PPRTDATA) GlobalAlloc(GPTR, sizeof(PRTDATA))) == NULL) {
SetWindowText(ghTextWnd, GetStringRes(IDS_MALLOCFAIL2));
return 0L;
    }

    pPrtData->hMetaf = ghMetaf;
    pPrtData->bFit2Wnd = gbFit2Wnd;

    hThrd = CreateThread(NULL, 0,
 (LPTHREAD_START_ROUTINE)bPrintMf,
 pPrtData, STANDARD_RIGHTS_REQUIRED,
 &dwThrdID);

    //
    // Free the memory if CreateThread fails...
    //
    if (hThrd == NULL) {
SetWindowText(ghTextWnd, GetStringRes(IDS_CREPRTTHRDFAIL));
GlobalFree(pPrtData);
    }

    return 0L;
}

/******************************Public*Routine******************************\
*
* lProcessWmCommand
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

LONG lProcessWmCommand(HWND     hwnd,
                       UINT     message,
                       DWORD    wParam,
                       LONG     lParam,
                       INT      *piMetafCnt,
                       BOOL     *pbReset,
                       char     *szFilename,
                       char     *szLoadedMetaf)
{
    static int     iPlus=0;

    switch (LOWORD(wParam)) {
        case DID_ZERO:
        case DID_ONE:
        case DID_TWO:
        case DID_THREE:
        case DID_FOUR:
        case DID_FIVE:
        case DID_SIX:
        case DID_SEVEN:
        case DID_EIGHT:
        case DID_NINE:
            return lProcessDIDPlayStep(wParam, &iPlus);

        case DID_TEN_PLUS:
            return lProcessDIDPlayTenPlus(&iPlus);

        case MM_PAGESETUP:
        case MM_CUT:
            return 0L;

        case MM_COPY:
            return lProcessMMCopy();

        case MM_PASTE:
            return lProcessMMPaste(hwnd, pbReset);

        case MM_DEL:
            return lProcessMMDel();

        case MM_PEN:
            return lProcessMMPen(hwnd);

        case MM_BRUSH:
            return lProcessMMBrush(hwnd);

        case MM_FONT:
            return lProcessMMFont();

        case MM_TTOUTLN_STROKEFILL:
            return lProcessMMStrokeFill();

        case MM_TTOUTLN_POLYDRAW:
            return lProcessMMPolyDraw();

        case MM_C_WND_MF:
    return 0L;

        case MM_C_BEGIN_GP:
        case MM_C_END_GP:
    return 0L;

case MM_C_MLTFMTS:
    return lProcessMMMltfmts();

case MM_HITTEST:
    return lProcessMMHittest();

case MM_LEABOUT:
    return lProcessMMLEAbout();

case MM_ABOUT:
    return lProcessMMAbout();

case MM_LOAD_MASKBMP:
    return lProcessMMLoadMaskBmp(hwnd);

case MM_LOAD_BMP:
    return lProcessMMLoadBmp(hwnd);

case MM_SAVE_BMP:
    return lProcessMMSaveBmp();

        case MM_LOAD:
case DID_OPEN:
    return lProcessDIDOpen(hwnd, szLoadedMetaf, pbReset);

case MM_RECORD:
    return lProcessMMRec(hwnd, szFilename);

case DID_RECORD:
    return lProcessDIDRec(hwnd, piMetafCnt, szFilename);

case DID_STOP:
    return lProcessDIDStop();

case DID_PLAY:
    return lProcessDIDPlay(szLoadedMetaf);

case DID_FF:
    return lProcessDIDFf(pbReset);

case DID_CLEAR:
    return lProcessDIDClear();

        case DID_PEN:
            SetWindowText(ghTextWnd, "Pen");
            return 0L;
        case DID_TEXT:
            SetWindowText(ghTextWnd, "Text");
            return 0L;
        case DID_RECT:
            SetWindowText(ghTextWnd, "Rectangle");
            return 0L;
        case DID_FILLRECT:
            SetWindowText(ghTextWnd, "Filled Rectangle");
            return 0L;
        case DID_ELLIPSE:
            SetWindowText(ghTextWnd, "Ellipse");
            return 0L;
        case DID_FILLELLIPSE:
            SetWindowText(ghTextWnd, "Filled Ellipse");
            return 0L;
        case DID_LINE:
            SetWindowText(ghTextWnd, "Line");
            return 0L;
        case DID_BEZIER:
            SetWindowText(ghTextWnd,
                "Bezier: Click with Left button for placing control points");
            return 0L;
        case DID_BMPOBJ:
            SetWindowText(ghTextWnd,
                "Bitmap: Click three points for the destination of the bitmap");
            return 0L;
        case DID_METAF:
            SetWindowText(ghTextWnd,
                "External Metafile: Click three points for the destination of the Metafile");
            return 0L;
case MM_IMPORT_3X:
    return lProcessMMImport3X();

case MM_EXPORT_3X:
    return lProcessMMExport3X();

case MM_FIT2WND:
    return lProcessMMFit2Wnd();

case MM_PRINT:
    return lProcessMMPrint();

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

/***************************************************************************\
* MainWndProc
*
\***************************************************************************/

long APIENTRY MainWndProc(
    HWND hwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    static int         iMetafCnt=0;
    static char        szFilename[256] = "c:\\metaf";
    static BOOL        bReset=FALSE;
    static char        szLoadedMetaf[256] = " ";

    switch (message) {

      case WM_CREATE: {
        return lProcessWmCreate(hwnd);
      }
      case WM_DRAWCLIPBOARD: {
        return lProcessWmDrawClip(message, wParam, lParam);
      }
      case WM_SIZE: {
        return lProcessWmSize(hwnd, message, wParam, lParam);
      }

      case WM_DESTROY: {
        return lProcessWmDestroy();
      }
      case WM_COMMAND: {
        return lProcessWmCommand(hwnd,
                                 message,
                                 wParam,
                                 lParam,
                                 &iMetafCnt,
                                 &bReset,
                                 szFilename,
                                 szLoadedMetaf);
      }
      default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
}

/******************************Public*Routine******************************\
*
* DrawSurfWndProc
*       Drawing surface window procedure
*
* Effects:  Trapping all mouse messages and call the DrawStuff appropriately
*           for drawing to the drawing surface DC and metafile DC as needed.
*
* Warnings:
*
\**************************************************************************/

long APIENTRY DrawSurfWndProc(
    HWND hwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    static BOOL    bTrack = FALSE;
    static int     OrgX, OrgY;
    static int     PrevX, PrevY;
    static HDC     hDC;
    static HCURSOR hCurArrow, hCurHT;
    static BOOL    b3DSetup = FALSE;

    switch (message) {
case WM_CREATE: {
    RECT                    rect;

    hDC = GetDC(hwnd);

            {
            char text[128];

            wsprintf(text, "DrawSurfWndProc: hDC=%lx", hDC);
            vErrOut(text);
            }

            bSetAdvancedGraphics(hDC);

            if (gb3D)
                b3DSetup = bSetupRC(hDC, NULL);

    ReleaseDC(hwnd, hDC);

    GetClientRect(GetParent(hwnd), &rect);

    SetWindowPos(hwnd, NULL,
    0,
    30,
    rect.right-rect.left,
    rect.bottom-rect.top-30,
    SWP_NOZORDER | SWP_NOMOVE);
            CreateCaret(hwnd, NULL, 1, CARET_HEIGHT);
#if 0
    //CreateCaret(hwnd, NULL, 1, 12);
    //hbmp = LoadBitmap(ghModule, (LPCSTR)MAKEINTRESOURCE(BMID_TOOLBASED));

    hPal = CreatePalette((LOGPALETTE *)&LogPal);
    hDC = GetDC(hwnd);
    hOldPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    hbmp = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)&DIBHdr, CBM_CREATEDIB, &dib, (LPBITMAPINFO)&DIBHdr, DIB_PAL_COLORS);
    CreateCaret(hwnd, hbmp, 1, 12);

    ReleaseDC(hwnd, hDC);
#endif

    ghCurFont = GetStockObject(SYSTEM_FONT);
    GetObject(ghCurFont, sizeof(LOGFONT), &glf);
    hCurArrow = LoadCursor(NULL, IDC_ARROW);
    hCurHT = LoadCursor(NULL, IDC_CROSS);
    break;
}

case WM_LBUTTONDOWN: {
    int    x, y;

    x = (int) LOWORD(lParam);
    y = (int) HIWORD(lParam);

    if (gbHitTest) {
hDC = GetDC(hwnd);
bHitTest(hDC, x, y);
ReleaseDC(hwnd, hDC);
break;
    }

    bTrack = TRUE;
    OrgX = PrevX = x;
    OrgY = PrevY = y;

    hDC = GetDC(hwnd);
    SetCapture(hwnd);
    break;
}

case WM_MOUSEMOVE: {
    RECT rectClient;
    int NextX;
    int NextY;

    if (gbHitTest) {
SetCursor(hCurHT);
    } else {
SetCursor(hCurArrow);
    }

    // Update the selection region
    if (bTrack) {
NextX = (SHORT) LOWORD(lParam);
NextY = (SHORT) HIWORD(lParam);

// Do not draw outside the window's client area

GetClientRect (hwnd, &rectClient);
if (NextX < rectClient.left) {
    NextX = rectClient.left;
} else if (NextX >= rectClient.right) {
    NextX = rectClient.right - 1;
}
if (NextY < rectClient.top) {
    NextY = rectClient.top;
} else if (NextY >= rectClient.bottom) {
    NextY = rectClient.bottom - 1;
}
if ((NextX != PrevX) || (NextY != PrevY)) {
   SetROP2(hDC, R2_NOT);   // Erases the previous box
   bDrawStuff(hDC, OrgX, OrgY, PrevX, PrevY, TRUE, TRUE, FALSE, NULL);

   //
   // Optimization.  Do not record in metafile DC if it is going
   // to be erased.  So only call bDrawStuff with the PEN tool.
   //
   if (gbRecording && (ghDCMetaf != NULL) && (gdwCurTool == DID_PEN)) {
       bDrawStuff(ghDCMetaf, OrgX, OrgY, PrevX, PrevY, TRUE, TRUE, FALSE, NULL);
   }

// Get the current mouse position
   PrevX = NextX;
   PrevY = NextY;

   //
   // SetROP2(hDC, R2_COPYPEN);
   //This is commented out because we don't want to erase
   //the background as it sweeps.
   //
   bDrawStuff(hDC, OrgX, OrgY, PrevX, PrevY, FALSE, TRUE, FALSE, NULL);

   if (gbRecording && (ghDCMetaf != NULL) && (gdwCurTool == DID_PEN)) {
       bDrawStuff(ghDCMetaf, OrgX, OrgY, PrevX, PrevY, FALSE, TRUE, FALSE, NULL);
   }

}
    }
    break;

}

      case WM_LBUTTONUP: {
        int NextX;
        int NextY;

        if (!bTrack)
           break;

        // End the selection
           ReleaseCapture();
           bTrack = FALSE;

        // Erases the box
           //
           // SetROP2(hDC, R2_NOT);
           //   This is assumed to be R2_NOT, thus unnecessary
           //
           bDrawStuff(hDC, OrgX, OrgY, PrevX, PrevY, TRUE, FALSE, FALSE, NULL);

           if (gbRecording && (ghDCMetaf != NULL) && (gdwCurTool == DID_PEN)) {
               bDrawStuff(ghDCMetaf, OrgX, OrgY, PrevX, PrevY, TRUE, FALSE, FALSE, NULL);
           }

           //
           // Apparently, at buttonup time, the mouse location is not clamped to
           // the client area.  So, the final ellipse or rectangle will be extended
           // outside the client area.
           //
           // NextX = (SHORT) LOWORD(lParam);
           // NextY = (SHORT) HIWORD(lParam);
           NextX = PrevX;
           NextY = PrevY;

        // Draws the new box
           SetROP2(hDC, R2_COPYPEN);
           bDrawStuff(hDC, OrgX, OrgY, NextX, NextY, FALSE, FALSE, TRUE, NULL);

           ReleaseDC(hwnd, hDC);

           if (gbRecording && (ghDCMetaf != NULL)) {
                bDrawStuff(ghDCMetaf, OrgX, OrgY, NextX, NextY, FALSE, FALSE, FALSE, NULL);
           }

        break;
      } // case WM_LBUTTONUP

#ifdef JAPAN
      case WM_IME_REPORT:
        if (IR_STRING == wParam) {
            LPSTR   lpsz, lpszMeta;

            if (!(lpsz = lpszMeta = GlobalLock((HANDLE)lParam))) {
                return FALSE;
            }
            hDC = GetDC(hwnd);
    while(*lpsz) { // Draw all characters in the string
bDrawStuff(hDC, 0, 0, 0, 0, TRUE, FALSE, FALSE, lpsz);
IsDBCSLeadByte((BYTE)*lpsz) ? lpsz += 2 : ++lpsz;
    }
            ReleaseDC(hwnd, hDC);
            if (gbRecording && (ghDCMetaf != NULL)) {
while(*lpszMeta) { // Draw all characters in the string
    bDrawStuff(ghDCMetaf, 0, 0, 0, 0, TRUE, FALSE, FALSE, lpszMeta);
    IsDBCSLeadByte((BYTE)*lpszMeta) ? lpszMeta += 2 : ++lpszMeta;
}
            }
            GlobalUnlock((HANDLE)lParam);
            return TRUE;
        }
return DefWindowProc(hwnd, message, wParam, lParam);
        break;
#endif

      case WM_CHAR: {

        if (gdwCurTool != DID_TEXT)
            break;

        hDC = GetDC(hwnd);
        bDrawStuff(hDC, 0, 0, 0, 0, TRUE, FALSE, FALSE, (LPSTR)&wParam);
        ReleaseDC(hwnd, hDC);

        if (gbRecording && (ghDCMetaf != NULL)) {
            bDrawStuff(ghDCMetaf, 0, 0, 0, 0, TRUE, FALSE, FALSE, (LPSTR)&wParam);
        }

        break;
      }

      case WM_DESTROY: {
        HDC         hDC;
        HPALETTE    hPal;

        DestroyCaret();
        DeleteObject(ghCurFont);

        if (gb3D && b3DSetup) {
            bCleanUpRC();
        }

        hDC = GetDC(hwnd);
        hPal = SelectObject(hDC, GetStockObject(DEFAULT_PALETTE));
        DeleteObject(hPal);
        ReleaseDC(hwnd, hDC);

PostQuitMessage(0);
return 0L;
      }

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

/***************************************************************************\
* About
*
* About dialog proc.
*
\***************************************************************************/

BOOL CALLBACK About (
    HWND hDlg,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    switch (message) {
    case WM_INITDIALOG:
        return TRUE;

    case WM_COMMAND:
        if (wParam == IDOK)

EndDialog(hDlg, wParam);
        break;
    }

    return FALSE;

    UNREFERENCED_PARAMETER(lParam);
    UNREFERENCED_PARAMETER(hDlg);
}

/***************************************************************************\
*
* TextWndProc
*
* Text Window procedure for displaying miscellaneous messages to user.
*
\***************************************************************************/

LONG APIENTRY TextWndProc (HWND hwnd, UINT message, DWORD wParam, LONG lParam)
{
    static HFONT hFont = (HFONT) NULL;

    switch (message)
    {
    case WM_CREATE:
        {
    LOGFONT    lf;
    HDC        hDC;
    HFONT      hOldFont;
            TEXTMETRIC tm;
    //RECT       rect;

            SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), (PVOID) &lf, (UINT)FALSE);

    hDC = GetDC(hwnd);
    // this is the height for 8 point size font in pixels
    lf.lfHeight = 8 * GetDeviceCaps(hDC, LOGPIXELSY) / 72;

    hFont = CreateFontIndirect(&lf);
    hOldFont = SelectObject(hDC, hFont);
    GetTextMetrics(hDC, &tm);

    // base the height of the window on size of text
    glcyStatus = tm.tmHeight+6*GetSystemMetrics(SM_CYBORDER)+2;
            ReleaseDC(hwnd, hDC);
            break;
        }

    case WM_DESTROY:
    if (hFont)
DeleteObject(hFont);
    break;

    case WM_SETTEXT:
            DefWindowProc(hwnd, message, wParam, lParam);
            InvalidateRect(hwnd,NULL,FALSE);
            UpdateWindow(hwnd);
            return 0L;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT   rc;
            char   ach[128];
            int    len, nxBorder, nyBorder;
            HFONT  hOldFont = NULL;

            BeginPaint(hwnd, &ps);

            GetClientRect(hwnd,&rc);

            nxBorder = GetSystemMetrics(SM_CXBORDER);
    rc.left  += 9*nxBorder;
            rc.right -= 9*nxBorder;

            nyBorder = GetSystemMetrics(SM_CYBORDER);
    rc.top    += 3*nyBorder;
    rc.bottom -= 3*nyBorder;

    // 3D Text
            len = GetWindowText(hwnd, ach, sizeof(ach));
    SetBkColor(ps.hdc, GetSysColor(COLOR_BTNFACE));

    SetBkMode(ps.hdc, TRANSPARENT);
    SetTextColor(ps.hdc, RGB(64,96,96));
    if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder+2, rc.top+2, ETO_OPAQUE | ETO_CLIPPED,
                &rc, ach, len, NULL);

    SetTextColor(ps.hdc, RGB(128,128,128));
      if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder+1, rc.top+1, ETO_CLIPPED,
&rc, ach, len, NULL);

    SetTextColor(ps.hdc, RGB(255,255,255));
    if (hFont)
hOldFont = SelectObject(ps.hdc, hFont);
    ExtTextOut(ps.hdc, rc.left+2*nxBorder, rc.top, ETO_CLIPPED,
&rc, ach, len, NULL);

    SetBkMode(ps.hdc, OPAQUE);

    if (hOldFont)
SelectObject(ps.hdc, hOldFont);

            EndPaint(hwnd, &ps);
            return 0L;
        }
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

/******************************Public*Routine******************************\
*
* CtrlPanelDlgProc
*       The Control Panel dialog procedure
*
* Effects:  Responsible for drawing the owner draw buttons.  Notifying
*           parent of user's action.
*
* Warnings:
*
\**************************************************************************/

LONG APIENTRY CtrlPanelDlgProc(HWND hwnd, UINT msg, DWORD dwParam, LONG lParam)
{
    switch (msg) {
        case WM_INITDIALOG: {
            int index;

            for (index = 0; index < OD_BTN_CNT; index++) {
                grHwndCtrlBtn[index] = (PVOID)GetDlgItem(hwnd, (INT)(ID_OD_BTN_BASE+index));
            }
            for (index = 0; index < OD_TOOL_CNT; index++) {
                grHwndToolBtn[index] = (PVOID)GetDlgItem(hwnd, (INT)(ID_OD_TOOL_BASE+index));
            }
            return TRUE;
        }

        case WM_DRAWITEM: {
            PDRAWITEMSTRUCT pDIS = (PDRAWITEMSTRUCT) lParam;
            HBITMAP hBmpOld;
            BITMAP  bm;
            HANDLE  hCtl;
            HDC     hDCCtl;

            if (pDIS->CtlID == gdwCurCtrl) {
                GetObject((HBITMAP) ghBmpDn[pDIS->CtlID - ID_OD_BTN_BASE], sizeof(BITMAP), (LPSTR)&bm);
                hBmpOld = SelectObject(ghDCMem, (HBITMAP) ghBmpDn[pDIS->CtlID - ID_OD_BTN_BASE]);
            }

            if (pDIS->CtlID == gdwCurTool) {
                GetObject((HBITMAP)ghToolBmpDn[pDIS->CtlID - ID_OD_TOOL_BASE], sizeof(BITMAP), (LPSTR)&bm);
                hBmpOld = SelectObject(ghDCMem, (HBITMAP)ghToolBmpDn[pDIS->CtlID - ID_OD_TOOL_BASE]);
            }

            if ((pDIS->CtlID < ID_OD_TOOL_BASE) && (pDIS->CtlID != gdwCurCtrl)) {
                GetObject((HBITMAP)ghBmpUp[pDIS->CtlID - ID_OD_BTN_BASE], sizeof(BITMAP), (LPSTR)&bm);
                hBmpOld = SelectObject(ghDCMem, (HBITMAP)ghBmpUp[pDIS->CtlID - ID_OD_BTN_BASE]);
            }

            if ((pDIS->CtlID >= ID_OD_TOOL_BASE) && (pDIS->CtlID != gdwCurTool)) {
                GetObject((HBITMAP)ghToolBmpUp[pDIS->CtlID - ID_OD_TOOL_BASE], sizeof(BITMAP), (LPSTR)&bm);
                hBmpOld = SelectObject(ghDCMem, (HBITMAP)ghToolBmpUp[pDIS->CtlID - ID_OD_TOOL_BASE]);
            }

            //
            // pDIS->hDC is clipped to the update region but unfortunately
            // that doesn't work well with StretchBlt.  StretchBlt is used
            // because I don't have to make sure that the bitmap size is
            // exactly the same as the size of the button.
            //
            hCtl   = GetDlgItem(hwnd, pDIS->CtlID);
            hDCCtl = GetDC(hCtl);
            StretchBlt(hDCCtl,                                //pDIS->hDC,
                   pDIS->rcItem.left, pDIS->rcItem.top,
                   pDIS->rcItem.right - pDIS->rcItem.left,
                   pDIS->rcItem.bottom - pDIS->rcItem.top,
                   ghDCMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
            ReleaseDC(hCtl, hDCCtl);
            SelectObject(ghDCMem, hBmpOld);
            break;
        }

        case WM_COMMAND: {
            DWORD dwOldCtrl = gdwCurCtrl;
            DWORD dwOldTool = gdwCurTool;

            switch (dwParam) {
                case DID_ONE:
                case DID_TWO:
                case DID_THREE:
                case DID_FOUR:
                case DID_FIVE:
                case DID_SIX:
                case DID_SEVEN:
                case DID_EIGHT:
                case DID_NINE:
                case DID_ZERO:
                case DID_TEN_PLUS:
                    //SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, dwParam - DID_ZERO, FALSE);
                    SendMessage(ghwndMain, WM_COMMAND, dwParam, lParam);
                    break;
                case DID_OPEN:
                case DID_RECORD:
                case DID_STOP:
                case DID_PLAY:
                case DID_FF:
                    SendMessage(ghwndMain, WM_COMMAND, dwParam, lParam);
                    gdwCurCtrl = dwParam;
                    InvalidateRect((HWND)grHwndCtrlBtn[dwOldCtrl - ID_OD_BTN_BASE], NULL, FALSE);
                    InvalidateRect((HWND)grHwndCtrlBtn[gdwCurCtrl - ID_OD_BTN_BASE], NULL, FALSE);
                    break;
                case DID_CLEAR:
                    SendMessage(ghwndMain, WM_COMMAND, dwParam, lParam);
                    break;
                case DID_TEXT:
                case DID_PEN:
                case DID_RECT:
                case DID_FILLRECT:
                case DID_ELLIPSE:
                case DID_FILLELLIPSE:
                case DID_LINE:
                case DID_BEZIER:
                case DID_BMPOBJ:
                case DID_METAF:
                    SendMessage(ghwndMain, WM_COMMAND, dwParam, lParam);
                    gdwCurTool = dwParam;
                    InvalidateRect((HWND)grHwndToolBtn[dwOldTool - ID_OD_TOOL_BASE], NULL, FALSE);
                    InvalidateRect((HWND)grHwndToolBtn[gdwCurTool - ID_OD_TOOL_BASE], NULL, FALSE);
                    break;
            }
            break;
        }

        case WM_PAINT:
            {
                HDC hdc;
                RECT rc, rcDlg;
                PAINTSTRUCT ps;
                HPEN hpenWindowFrame, hpenDarkGray;
                int  icyDlg;
                int  icyBorder;

                icyBorder = GetSystemMetrics(SM_CYBORDER);

                GetWindowRect(hwnd, &rcDlg);
                icyDlg = rcDlg.right - rcDlg.left;

                /*
                 * Draw our border lines.
                 */
                GetClientRect(hwnd, &rc);
                hdc = BeginPaint(hwnd, &ps);

                SelectObject(hdc, GetStockObject(WHITE_PEN));
                MoveToEx(hdc, rc.left, rc.top, NULL);
                LineTo(hdc, rc.right, rc.top);

                hpenDarkGray = CreatePen(PS_SOLID, 1, DARKGRAY);
                SelectObject(hdc, hpenDarkGray);
                MoveToEx(hdc, rc.left, (rc.top + icyDlg) - icyBorder - 1, NULL);
                LineTo(hdc, rc.right, (rc.top + icyDlg) - icyBorder - 1);

                hpenWindowFrame = CreatePen(PS_SOLID, icyBorder,
                        GetSysColor(COLOR_WINDOWFRAME));
                SelectObject(hdc, hpenWindowFrame);
                MoveToEx(hdc, rc.left, (rc.top + icyDlg) - icyBorder, NULL);
                LineTo(hdc, rc.right, (rc.top + icyDlg) - icyBorder);

                EndPaint(hwnd, &ps);
                DeleteObject(hpenWindowFrame);
                DeleteObject(hpenDarkGray);
            }

            break;

        //case WM_CTLCOLOR:
        case WM_CTLCOLORDLG:
        //case WM_CTLCOLORLISTBOX:
        case WM_CTLCOLORSTATIC:
            switch (GET_WM_CTLCOLOR_TYPE(dwParam, lParam, msg)) {
                case CTLCOLOR_DLG:
                //case CTLCOLOR_LISTBOX:
                    return (BOOL)GetStockObject(LTGRAY_BRUSH);

                case CTLCOLOR_STATIC:
                    SetBkMode(GET_WM_CTLCOLOR_HDC(dwParam, lParam, msg),
                              TRANSPARENT);
                //    SetTextColor(GET_WM_CTLCOLOR_HDC(dwParam, lParam, msg),
                //              RGB(255,0,0));
                //    SetBkColor(GET_WM_CTLCOLOR_HDC(dwParam, lParam, msg),
                //            LIGHTGRAY);
                //              RGB(255, 255,0));
                    return (BOOL)GetStockObject(DKGRAY_BRUSH);
            }
            //return (BOOL)NULL;
            return (BOOL)GetStockObject(LTGRAY_BRUSH);

        default:
            return FALSE;
    }

    return FALSE;
}

/******************************Public*Routine******************************\
*
* bDrawStuff
*
* Effects:  The drawing routines are localized here.
*           bErase is TRUE if this fcn is called for erasing previous object.
*           (as in tracking objects.)  It is FALSE, otherwise.
*
*           bMove is TRUE if this fcn is called inside the WM_MOUSEMOVE (as
*           in tracking objects.)  It is FALSE, otherwise.
*
*           bCntPt is TRUE if this fcn is to increment either the iCnt or
*           iCntMF counter (used only in processing metafile or bezier.)
*           It is FALSE, otherwise.
*
*           lpstr contains the character to be drawn by TextOut when it is
*           not NULL.
*
* Warnings: Metafile and Bezier assume that the caller is calling this fcn
*           to draw in the screen DC first. Then draw it to the metafile DC.
*           Thus, when it is called to draw on the metafile DC, the points
*           would have been set already.
*
\**************************************************************************/

BOOL bDrawStuff(HDC hDC, INT OrgX, INT OrgY,
                         INT NextX, INT NextY,
                         BOOL bErase,
                         BOOL bMove,
                         BOOL bCntPt,
                         LPSTR lpstr) {
    BOOL bSuccess;
    HGDIOBJ hObjOld;
    static POINT rgPts[MAX_POINTS], rgPtsMF[MAX_POINTS_MF], rgPtsBMP[MAX_POINTS_BMP];
    static int   iCnt=0, iCntMF=0, iCntBMP=0;
    static BOOL  bCaretShown=FALSE;

    bSuccess = TRUE;
    if (bCaretShown) {
        HideCaret(ghwndDrawSurf);
        bCaretShown = FALSE;
    }

    switch (gdwCurTool) {
        case DID_PEN:
            if (bErase) {
                MoveToEx(hDC, NextX, NextY, NULL);
            } else {
                //
                // Override the ROP2 st. the pen won't erase its track
                //
                SetROP2(hDC, R2_COPYPEN);
                LineTo(hDC, NextX, NextY);
            }
            break;
        case DID_TEXT: {
            POINT   Pt;
#if 0
    HDC hDCMem;
#endif

            if (lpstr == NULL) {
                ShowCaret(ghwndDrawSurf);
                bCaretShown = TRUE;
                SetCaretPos(NextX, NextY-CARET_HEIGHT);
                MoveToEx(hDC, NextX, NextY, NULL);

#if 0
StretchDIBits(hDC, 20, 20+120, 64, 64, 0, 64, 64, -64, &dib,
(LPBITMAPINFO)&DIBHdr, DIB_PAL_COLORS, SRCCOPY);

hDCMem = CreateCompatibleDC(hDC);
SelectPalette(hDCMem, hPal, FALSE);
RealizePalette(hDCMem);
SelectObject(hDCMem, hbmp);
BitBlt(hDC, 0,0,64,64,hDCMem, 0,0,SRCCOPY);
DeleteDC(hDCMem);
#endif

    SetFocus(ghwndDrawSurf);
    break;
            }

            SetTextAlign(hDC, TA_BASELINE | TA_LEFT | TA_UPDATECP);
            hObjOld = SelectObject(hDC, ghCurFont);
            SetTextColor(hDC, gCrText);

            if ((gbSFOutln || gbPDOutln) && gbTT) {
                // get rid of the char box
                SetBkMode(hDC, TRANSPARENT);
                BeginPath(hDC);
                TextOut(hDC, NextX, NextY, lpstr, IsDBCSLeadByte((BYTE)*lpstr) ? 2 : 1);
                EndPath(hDC);

                if (gbSFOutln) {
                    StrokeAndFillPath(hDC);
                    goto DT_UPDATE;
                }

                //
                // Get path and polydraw
                //
                {
                int     iNumPt;
                PBYTE   pjTypes;
                PPOINT  pPts;

                if (iNumPt = GetPath(hDC, NULL, NULL, 0)) {
                    if ((pPts = (PPOINT)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                                                sizeof(POINT)*iNumPt )) == NULL) {
                        MessageBox(ghwndMain, GetStringRes(IDS_CREPNTFAIL),
                            GetStringRes(IDS_ERROR), MB_OK);
                        break;
                    }

                    if ((pjTypes = (PBYTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                                                sizeof(BYTE)*iNumPt )) == NULL) {
                        MessageBox(ghwndMain, GetStringRes(IDS_CREPBYTEFAIL),
                            GetStringRes(IDS_ERROR), MB_OK);
                        goto GP_EXIT1;
                    }

                    GetPath(hDC, pPts, pjTypes, iNumPt);
                }
                PolyDraw95(hDC, pPts, pjTypes, iNumPt);
                LocalFree(pjTypes);

GP_EXIT1:
                LocalFree(pPts);

                }

            } else {
                TextOut(hDC, NextX, NextY, lpstr, IsDBCSLeadByte((BYTE)*lpstr) ? 2 : 1);
            }
DT_UPDATE:
            //
            // Updating current position
            //
            {
            LONG    lHeight;
            LONG    lWidth;
            TEXTMETRIC     tm;

                if (GetTextMetrics(hDC, &tm)) {
                    lHeight = tm.tmHeight;
                    lWidth  = tm.tmMaxCharWidth;
                }

                GetCurrentPositionEx(hDC, (LPPOINT) &Pt);
                SetCaretPos(Pt.x, Pt.y-CARET_HEIGHT);

            }
            ShowCaret(ghwndDrawSurf);
            bCaretShown = TRUE;

            break;

#if 0

#define PT_LINECLOSE     (PT_LINETO | PT_CLOSEFIGURE)
#define PT_BEZIERCLOSE (PT_BEZIERTO | PT_CLOSEFIGURE)

            hpnRed = CreatePen(PS_SOLID, 0, RGB(255,0,0));
            SelectObject(hDC, hpnRed);

            while (iNumPt--) {
                static POINT pPnt[3];
                static int   iCnt=0;

                switch (*pjTypes++) {
                    case PT_MOVETO: {
                        MoveToEx(hDC, pPts->x, pPts->y, NULL);
                        pPts++;
                        break;
                    }
                    case PT_LINETO: {
                        LineTo(hDC, pPts->x, pPts->y);
                        pPts++;
                        break;

                    }
                    case PT_LINECLOSE: {
                        LineTo(hDC, pPts->x, pPts->y);
                        pPts++;
                        goto GP_EXIT2;
                    }
                    case PT_BEZIERTO: {
                        pPnt[iCnt].x = pPts->x;
                        pPnt[iCnt].y = pPts->y;
                        pPts++;

                        if (iCnt < 2) {
                            iCnt++;
                        } else {
                            PolyBezierTo(hDC, pPnt, 3);
                            iCnt = 0;
                        }
                        break;
                    }
                    case PT_BEZIERCLOSE: {
                        pPnt[iCnt].x = pPts->x;
                        pPnt[iCnt].y = pPts->y;
                        pPts++;

                        if (iCnt < 2) {
                            iCnt++;
                        } else {
                            PolyBezierTo(hDC, pPnt, 3);
                            iCnt = 0;
                        }
                        goto GP_EXIT2;
                    }

                    default:
                        break;
                }

            }

#endif
        }
        case DID_RECT:
            hObjOld = SelectObject(hDC, GetStockObject(NULL_BRUSH));
            Rectangle(hDC, OrgX, OrgY, NextX, NextY);
            SelectObject(hDC, hObjOld);
            break;
        case DID_FILLRECT:
            Rectangle(hDC, OrgX, OrgY, NextX, NextY);
            break;
        case DID_ELLIPSE:
            hObjOld = SelectObject(hDC, GetStockObject(NULL_BRUSH));
            Ellipse(hDC, OrgX, OrgY, NextX, NextY);
            SelectObject(hDC, hObjOld);
            break;
        case DID_FILLELLIPSE:
            Ellipse(hDC, OrgX, OrgY, NextX, NextY);
            break;
        case DID_LINE:
            MoveToEx(hDC, OrgX, OrgY, NULL);
            LineTo(hDC, NextX, NextY);
            break;
        case DID_BEZIER:
            if (bErase || bMove)
                return bSuccess;

            if (bCntPt) {
                rgPts[iCnt].x = NextX;
                rgPts[iCnt].y = NextY;
                iCnt++;

                if (iCnt == MAX_POINTS - 1)
                    iCnt = 0;
            }

            if ((iCnt % 3) == 1) {              // (iCnt + 1) % 3 == 1
                //
                // Override the ROP2 st. the pen won't erase its track
                //
                SetROP2(hDC, R2_COPYPEN);
                PolyBezier(hDC, (LPPOINT)&rgPts, (DWORD) iCnt);
            }
            return bSuccess;

        case DID_BMPOBJ: {
            static BOOL          bBltReady = FALSE;

            if (bErase || bMove)
                return bSuccess;

            if (ghBmp == NULL) {
                SetWindowText(ghTextWnd, GetStringRes(IDS_NOBMFOREMBED));
                return bSuccess;
            }

            if (bCntPt) {
                bBltReady = FALSE;
                rgPtsBMP[iCntBMP].x = NextX;
                rgPtsBMP[iCntBMP].y = NextY;
                iCntBMP++;

                if (iCntBMP < MAX_POINTS_BMP) {
                    return bSuccess;
                }
            } else {
                //
                // Caller don't want to increment counter, so must be doing
                // recording, so we just Blt again...
                //
                // But, if the Blt data is no good, bail out...
                //
                if (!bBltReady) {
                    return bSuccess;
                }
                bPlgBlt(hDC, rgPtsBMP);
                return bSuccess;
            }
            bBltReady = TRUE;

            bPlgBlt(hDC, rgPtsBMP);
            iCntBMP = 0;                         // reset
            return bSuccess;
        }

        case DID_METAF: {
            ENHMETAHEADER EnhMetaHdr;
            RECT          rcClientDS;
            static XFORM         xform;
            static BOOL          bXformReady = FALSE;
            int           iEntries;
            PLOGPALETTE     plogPal;
            PBYTE           pjTmp;
            HPALETTE        hPal;
            static BOOL     bRCSet = FALSE;

            if (bErase || bMove)
                return bSuccess;

            if (ghMetaf == NULL) {
                SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFFOREMBED));
                return bSuccess;
            }

            if (bCntPt) {
                bXformReady = FALSE;
                rgPtsMF[iCntMF].x = NextX;
                rgPtsMF[iCntMF].y = NextY;
                iCntMF++;

                if (iCntMF < MAX_POINTS_MF) {
                    return bSuccess;
                }
            } else {
                //
                // Caller don't want to increment counter, so must be doing
                // recording, so we just set xform and play it again...
                //
                // But, if the xform data is no good, bail out...
                //
                if (!bXformReady) {
                    return bSuccess;
                }

                GetEnhMetaFileHeader(ghMetaf, sizeof(ENHMETAHEADER), &EnhMetaHdr);
                SetWorldTransform(hDC, &xform);
                GetClientRect(ghwndDrawSurf, &rcClientDS);

                iEntries = GetEnhMetaFilePaletteEntries(ghMetaf, 0, NULL);

                if (iEntries) {
                    if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                            sizeof(DWORD) + sizeof(PALETTEENTRY)*iEntries )) == NULL) {
                        MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
                            GetStringRes(IDS_ERROR), MB_OK);
                    }

                    plogPal->palVersion = 0x300;
                    plogPal->palNumEntries = (WORD) iEntries;
                    pjTmp = (PBYTE) plogPal;
                    pjTmp += 4;

                    GetEnhMetaFilePaletteEntries(ghMetaf, iEntries, (PPALETTEENTRY)pjTmp);
                    hPal = CreatePalette(plogPal);
                    GlobalFree(plogPal);

                    SelectPalette(hDC, hPal, FALSE);
                    RealizePalette(hDC);
                }

                bRCSet = bSetRC2MatchEmfRC(hDC, EnhMetaHdr, ghMetaf, NULL);
                if (!bRCSet) {
                    vErrOut("DID_METAF failed");
                }

                //PlayEnhMetaFile(hDC, ghMetaf, (LPRECT) &rcClientDS);
                {
                RECT rc;

                rc.top = rc.left = 0;
                rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
                rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
                if (!PlayEnhMetaFile(hDC, ghMetaf, (LPRECT) &rc)) {
                    char    text[128];

                    wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
                    OutputDebugString(text);
                } else {
                    if (gbDB)
                       SwapBuffers(hDC);
                }

                }
                ModifyWorldTransform(hDC, NULL, MWT_IDENTITY);

                if (bRCSet) {
                    bCleanUpRC();
                    bRCSet = FALSE;
                }

                return bSuccess;
            }

            GetEnhMetaFileHeader(ghMetaf, sizeof(ENHMETAHEADER), &EnhMetaHdr);
            //
            // Based on the three points, top-left, top-right and bottom-left
            // (in this order), of the destination, solve equations for the
            // elements of the transformation matrix.
            //
            xform.eDx = (float) rgPtsMF[0].x;
            xform.eDy = (float) rgPtsMF[0].y;
            xform.eM11 = (rgPtsMF[1].x - xform.eDx)/(EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left);
            xform.eM12 = (rgPtsMF[1].y - xform.eDy)/(EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left);
            xform.eM21 = (rgPtsMF[2].x - xform.eDx)/(EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top);
            xform.eM22 = (rgPtsMF[2].y - xform.eDy)/(EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top);

            bXformReady = TRUE;
            SetWorldTransform(hDC, &xform);
            GetClientRect(ghwndDrawSurf, &rcClientDS);

            iEntries = GetEnhMetaFilePaletteEntries(ghMetaf, 0, NULL);

            if (iEntries) {
                if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                        sizeof(DWORD) + sizeof(PALETTEENTRY)*iEntries )) == NULL) {
                    MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
                        GetStringRes(IDS_ERROR), MB_OK);
                }

                plogPal->palVersion = 0x300;
                plogPal->palNumEntries = (WORD) iEntries;
                pjTmp = (PBYTE) plogPal;
                pjTmp += 4;

                GetEnhMetaFilePaletteEntries(ghMetaf, iEntries, (PPALETTEENTRY)pjTmp);
                hPal = CreatePalette(plogPal);
                GlobalFree(plogPal);

                SelectPalette(hDC, hPal, FALSE);
                RealizePalette(hDC);
            }

            if (!bRCSet) {
                bRCSet = bSetRC2MatchEmfRC(hDC, EnhMetaHdr, ghMetaf, NULL);
                if (!bRCSet) {
                    vErrOut("DID_METAF failed");
                }
            }

            //PlayEnhMetaFile(hDC, ghMetaf, (LPRECT) &rcClientDS);
            {
            RECT rc;

            rc.top = rc.left = 0;
            rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
            rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
            if (!PlayEnhMetaFile(hDC, ghMetaf, (LPRECT) &rc)) {
                 char    text[128];

                 wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
                 OutputDebugString(text);
            } else {
                if (gbDB)
                   SwapBuffers(hDC);
            }

            }
            ModifyWorldTransform(hDC, NULL, MWT_IDENTITY);
            iCntMF = 0;                         // reset
            if (bRCSet) {
                bCleanUpRC();
                bRCSet = FALSE;
            }
            return bSuccess;
        }
        default:
            break;
    }
    //
    // Reset counter, user has selected other tools.
    //
    iCnt = 0;
    iCntMF = 0;
    iCntBMP = 0;
    return bSuccess;
}

/******************************Public*Routine******************************\
*
* hemfLoadMetafile
*
* Effects:   Brings up the Open file common dialog
*            Get the enhanced metafile spec'd by user
*            returns the handle to the enhanced metafile if successfull
*               otherwise, returns 0.
*
* Warnings:
*
\**************************************************************************/

HENHMETAFILE hemfLoadMetafile(HWND hwnd) {
    OPENFILENAME    ofn;
    char            szFile[256], szFileTitle[256];
    static char     *szFilter;

    HMETAFILE       hmf;
    UINT            uiSize;
    LPVOID          pvData;
    HDC             hDCDrawSurf;
    HENHMETAFILE    hemf;

    HANDLE                  hFile, hMapFile;
    LPVOID                  pMapFile;
    LPENHMETAHEADER         pemh;

    BOOL        bSuccess;
    char            text[128];

    bSuccess = TRUE;

    BuildFilterStrs(-1);
    BuildFilterStrs(IDS_FT_EMF);
  szFilter = BuildFilterStrs(IDS_FT_WMF);

    strcpy(szFile, "*.emf\0");
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = GetStringRes(IDS_LDMF);
    ofn.Flags = 0L;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "EMF";

    if (!GetOpenFileName(&ofn))

return 0L;

    if ((hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) == (HANDLE)-1) {
        wsprintf(text, GetStringRes(IDS_FMT_FILEOPNFAIL), GetLastError());
        MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        return 0L;
    }

    //
    // Create a map file of the opened file
    //
    if ((hMapFile = CreateFileMapping(hFile, NULL,
                             PAGE_READONLY, 0, 0, "MapF")) == NULL) {
        wsprintf(text, GetStringRes(IDS_FMT_CREMAPFILEFAIL), GetLastError());
        MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        bSuccess = FALSE;
        goto ErrorExit1;
    }

    //
    // Map a view of the whole file
    //
    if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0)) == NULL) {
        wsprintf(text, GetStringRes(IDS_FMT_MAPVWMAPFOFAIL), GetLastError());
        MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        bSuccess = FALSE;
        goto ErrorExit2;
    }

    //
    // First check that if it is an enhanced metafile
    //
    pemh = (LPENHMETAHEADER) pMapFile;
    if (pemh->dSignature == META32_SIGNATURE) {
        hemf = GetEnhMetaFile(szFile);
        goto HLM_EXIT;
    }

    //
    // If it has an ALDUS header skip it
    // Notice: APMSIZE is used because the HANDLE and RECT of the structure
    //         depends on the environment
    //
    if (*((LPDWORD)pemh) == ALDUS_ID) {
        //METAFILEPICT    mfp;

        MessageBox(ghwndMain, GetStringRes(IDS_ALDUSMF), "Hey!", MB_OK);
        uiSize = *((LPDWORD) ((PBYTE)pMapFile + APMSIZE + 6));
        hDCDrawSurf = GetDC(ghwndDrawSurf);

        // Notice: mtSize is size of the file in word.
        // if LPMETAFILEPICT is NULL
        //    MM_ANISOTROPIC mode and default device size will be used.
        hemf = SetWinMetaFileBits(uiSize*2L, (PBYTE)pMapFile + APMSIZE, hDCDrawSurf, NULL);
#if 0
        switch ( ((PAPMFILEHEADER) pMapFile)->inch ) {
            // !!! End up in an upside down image
            //
            case 1440:
                mfp.mm = MM_TWIPS;
                OutputDebugString("MM_TWIPS\n");
                break;
            case 2540:
                OutputDebugString("MM_HIMETRIC\n");
                mfp.mm = MM_HIMETRIC;
                break;
            case 254:
                OutputDebugString("MM_LOMETRIC\n");
                mfp.mm = MM_LOMETRIC;
                break;
            case 1000:
                OutputDebugString("MM_HIENGLISH\n");
                mfp.mm = MM_HIENGLISH;
                break;
            case 100:
                OutputDebugString("MM_LOENGLISH\n");
                mfp.mm = MM_LOENGLISH;
                break;
            default:
                // !!! In addition, text is too small
                //
                OutputDebugString("MM_ANISOTROPIC\n");
                mfp.mm = MM_ANISOTROPIC;
                mfp.xExt = (((PAPMFILEHEADER) pMapFile)->bbox.Right - ((PAPMFILEHEADER) pMapFile)->bbox.Left)
                           * ((PAPMFILEHEADER) pMapFile)->inch * 2560;
                mfp.yExt = (((PAPMFILEHEADER) pMapFile)->bbox.Bottom - ((PAPMFILEHEADER) pMapFile)->bbox.Top)
                           * ((PAPMFILEHEADER) pMapFile)->inch * 2560;
                break;
        }
        mfp.hMF = 0;
        hemf = SetWinMetaFileBits(uiSize*2L, (PBYTE)pMapFile + APMSIZE, hDCDrawSurf, &mfp);
#endif

        if (!hemf) {
            char text[256];

            wsprintf(text, GetStringRes(IDS_FMT_SETWMFBTSFAIL), GetLastError());
            MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        }

        ghmf = SetMetaFileBitsEx(uiSize*2L, (PBYTE)pMapFile + APMSIZE);
        if (!ghmf) {
            char text[256];

            wsprintf(text, GetStringRes(IDS_FMT_SETWMFBTSXFAIL), GetLastError());
            MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        }

// !!! Displaying the Windows format metafile
//if (!PlayMetaFile(hDCDrawSurf, ghmf)) {
//    wsprintf(text, GetStringRes(IDS_FMT_PLAYMFFAIL), GetLastError());
//    MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
//}
        ReleaseDC(ghwndDrawSurf, hDCDrawSurf);
        goto HLM_EXIT;
    }

    //
    // It is a Windows 3x format metafile (hopefully)
    //
    if (!(hmf = GetMetaFile((LPCSTR)szFile))) {
        char text[256];

        wsprintf(text, GetStringRes(IDS_FMT_GETMFFAIL), GetLastError());
        MessageBox(ghwndMain, text, GetStringRes(IDS_ERROR), MB_OK);
        bSuccess = FALSE;
        goto ErrorExit3;
    }

    if (!(uiSize = GetMetaFileBitsEx(hmf, 0, NULL))) {
        MessageBox(ghwndMain, GetStringRes(IDS_GETMFBTSXFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
        return NULL;
    }

    if ((pvData = (LPVOID) LocalAlloc(LMEM_FIXED, uiSize)) == NULL) {
        MessageBox(ghwndMain, GetStringRes(IDS_MALLOCFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
        bSuccess = FALSE;
        goto ErrorExit3;
    }

    if (!(uiSize = GetMetaFileBitsEx(hmf, uiSize, pvData))) {
        MessageBox(ghwndMain, GetStringRes(IDS_GETMFBTSXFAIL2),
            GetStringRes(IDS_ERROR), MB_OK);
        bSuccess = FALSE;
        goto ErrorExit3;
    }

    DeleteMetaFile(hmf);

    hDCDrawSurf = GetDC(ghwndDrawSurf);
    hemf = SetWinMetaFileBits(uiSize, (LPBYTE)pvData, hDCDrawSurf, NULL);
    ghmf = SetMetaFileBitsEx(uiSize, (LPBYTE) pvData);

    LocalFree(pvData);

    ReleaseDC(ghwndDrawSurf ,hDCDrawSurf);

HLM_EXIT:
ErrorExit3:
    UnmapViewOfFile(pMapFile);

ErrorExit2:
    CloseHandle(hMapFile);
ErrorExit1:
    CloseHandle(hFile);

    if (bSuccess)
        return hemf;
    else
        return 0L;
}

/******************************Public*Routine******************************\
*
* hDCRecordMetafileAs
*
* Effects:   Brings up the SaveAs common dialog
*            Creates the enhanced metafile with the filename spec'd by user
*            Modifies the second arg to reflect the new default filename
*            less extension
*            returns the created metafile DC if successful, otherwise, 0
*
* Warnings:
*
\**************************************************************************/

HDC hDCRecordMetafileAs(HWND hwnd, LPSTR szFilename) {
    OPENFILENAME ofn;
    char szFile[256], szFileTitle[256];
    static char *szFilter;
    char *szTmp, szTmp2[256];
    HDC  hDCMeta;

    int iWidthMM, iHeightMM, iWidthPels, iHeightPels, iMMPerPelX, iMMPerPelY;
    RECT rc;
    HDC hDC;

    BuildFilterStrs(-1);
    szFilter = BuildFilterStrs(IDS_FT_EMF);
    strcpy(szFile, "*.emf\0");
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 0L;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = GetStringRes(IDS_SAVEMFAS);
    ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = (LPSTR)NULL;

    if (!GetSaveFileName(&ofn)) {
        return 0L;
    }

    hDC = GetDC(hwnd);
    iWidthMM = GetDeviceCaps(hDC, HORZSIZE);
    iHeightMM = GetDeviceCaps(hDC, VERTSIZE);
    iWidthPels = GetDeviceCaps(hDC, HORZRES);
    iHeightPels = GetDeviceCaps(hDC, VERTRES);
    ReleaseDC(hwnd, hDC);
    iMMPerPelX = (iWidthMM * 100)/iWidthPels;
    iMMPerPelY = (iHeightMM * 100)/iHeightPels;
    GetClientRect(ghwndDrawSurf, &rc);
    rc.left = rc.left * iMMPerPelX;
    rc.top = rc.top * iMMPerPelY;
    rc.right = rc.right * iMMPerPelX;
    rc.bottom = rc.bottom * iMMPerPelY;

    //hDCMeta = CreateEnhMetaFile((HDC)NULL, szFile, (LPRECT)NULL, (LPSTR)NULL);
    {
        CHAR    szDesc[256];

        wsprintf(szDesc, "SDK Enhanced Metafile Editor\\0%s\\0\\0", szFileTitle);
        hDCMeta = CreateEnhMetaFile((HDC)NULL, szFile, (LPRECT)&rc, (LPSTR)szDesc);

        if (gb3D)
            gbRCSet = bSetupRC(hDCMeta, NULL);

        bSetAdvancedGraphics(hDCMeta);
    }

    //
    // parses the new filename, removes the extension and copy it into
    // szFilename
    //
    strcpy(szFilename, "");
    szTmp = (char *)My_mbstok(szFile, "\\");
    strcpy(szTmp2, szTmp);
    while (szTmp != NULL) {
        szTmp = (char *)My_mbstok(NULL, "\\");
        if (szTmp != NULL) {
            strcat(szFilename, szTmp2);
            strcpy(szTmp2, szTmp);
            strcat(szFilename, "\\");
        }
    }
    szTmp = (char *)My_mbstok(szTmp2, ".");
    strcat(szFilename, szTmp);

    return hDCMeta;
}

/******************************Public*Routine******************************\
*
* bPlayRecord
*
* Effects:  Play metafile
*           if PlayInfo.bPlayContinuous is TRUE
*               play metafile from 1st record up to the PlayInfo.iRecord th
*                   record
*           else only play the PlayInfo.iRecord th record and those preceding
*               records that are relevant like MoveTo, etc.
*           Terminates enumeration after playing up to the
*               PlayInfo.iRecord th record
*
* Warnings:
*
\**************************************************************************/

BOOL APIENTRY bPlayRecord(HDC hDC, LPHANDLETABLE lpHandleTable,
                                   LPENHMETARECORD lpEnhMetaRecord,
                                   UINT nHandles,
                                   LPVOID lpData) {
    BOOL bSuccess;
    static int  iCnt=0;
    int         i;
    char        ach[128];
    char        achTmp[128];
    LONG        lNumDword;

    bSuccess = TRUE;

    lNumDword = (lpEnhMetaRecord->nSize-8) / 4;

    iCnt++;
    if (((PLAYINFO *) lpData)->bPlayContinuous) {
        bSuccess = PlayEnhMetaFileRecord(hDC, lpHandleTable,
                                             lpEnhMetaRecord, nHandles);
        if (iCnt == ((PLAYINFO *) lpData)->iRecord) {
            wsprintf((LPSTR) ach, "%s", rgMetaName[lpEnhMetaRecord->iType]);
            for (i=0; i < lNumDword; i++) {
                wsprintf((LPSTR) achTmp, "%ld ", lpEnhMetaRecord->dParm[i]);
                if ((strlen(ach)+strlen(achTmp))/sizeof(char) >= 128)
                    break;
                strcat(ach, achTmp);
            }
        SetWindowText(ghTextWnd, ach);
        }
    } else {

        switch (lpEnhMetaRecord->iType) {
            case EMR_SETWINDOWEXTEX:
            case EMR_SETWINDOWORGEX:
            case EMR_SETVIEWPORTEXTEX:
            case EMR_SETVIEWPORTORGEX:
            case EMR_SETBRUSHORGEX:
            case EMR_SETMAPMODE:
            case EMR_SETBKMODE:
            case EMR_SETPOLYFILLMODE:
            case EMR_SETROP2:
            case EMR_SETSTRETCHBLTMODE:
            case EMR_SETTEXTALIGN:
            case EMR_SETTEXTCOLOR:
            case EMR_SETBKCOLOR:
            case EMR_OFFSETCLIPRGN:
            case EMR_MOVETOEX:
            case EMR_SETMETARGN:
            case EMR_EXCLUDECLIPRECT:
            case EMR_INTERSECTCLIPRECT:
            case EMR_SCALEVIEWPORTEXTEX:
            case EMR_SCALEWINDOWEXTEX:
            case EMR_SAVEDC:
            case EMR_RESTOREDC:
            case EMR_SETWORLDTRANSFORM:
            case EMR_MODIFYWORLDTRANSFORM:
            case EMR_SELECTOBJECT:
            case EMR_CREATEPEN:
            case EMR_CREATEBRUSHINDIRECT:
            case EMR_DELETEOBJECT:
            case EMR_SELECTPALETTE:
            case EMR_CREATEPALETTE:
            case EMR_SETPALETTEENTRIES:
            case EMR_RESIZEPALETTE:
            case EMR_REALIZEPALETTE:
            case EMR_SETARCDIRECTION:
            case EMR_SETMITERLIMIT:
            case EMR_BEGINPATH:
            case EMR_ENDPATH:
            case EMR_CLOSEFIGURE:
            case EMR_SELECTCLIPPATH:
            case EMR_ABORTPATH:
            case EMR_EXTCREATEFONTINDIRECTW:
            case EMR_CREATEMONOBRUSH:
            case EMR_CREATEDIBPATTERNBRUSHPT:
            case EMR_EXTCREATEPEN:
                goto PlayRec;
            default:
                break;
        } //switch

        if (iCnt == ((PLAYINFO *) lpData)->iRecord) {
PlayRec:
            bSuccess = PlayEnhMetaFileRecord(hDC, lpHandleTable,
                                             lpEnhMetaRecord, nHandles);
            wsprintf((LPSTR) ach, "%s", rgMetaName[lpEnhMetaRecord->iType]);
            for (i=0; i < lNumDword; i++) {
                wsprintf((LPSTR) achTmp, "%ld ", lpEnhMetaRecord->dParm[i]);
                if ((strlen(ach)+strlen(achTmp))/sizeof(char) >= 128)
                    break;
                strcat(ach, achTmp);
            }
            SetWindowText(ghTextWnd, ach);
        }
    }

    if (iCnt == ((PLAYINFO *) lpData)->iRecord) {
        iCnt = 0;

        if (gbDB)
            SwapBuffers(hDC);

        return FALSE;
    }
    return bSuccess;
}

/******************************Public*Routine******************************\
*
* LoadBitmapFile
*
* Effects:  Loads the bitmap from file and return the bitmap
*
* Warnings: pszFileName contains the full path
*
\**************************************************************************/

HBITMAP hBmpLoadBitmapFile(HDC hDC, PSTR pszFileName)
{
    HANDLE              hFile, hMapFile;
    LPVOID              pMapFile, pMapFileTmp;
    LPBITMAPINFOHEADER  pbmh;
    LPBITMAPINFO        pbmi;
    PBYTE               pjTmp;
    ULONG               sizBMI;
    HBITMAP             hBitmap;
    INT                 iNumClr;
    BOOL                bCoreHdr;
    WORD                wBitCount;
    PFILEINFO           pFileInfo;

    hBitmap = NULL;

    if ((hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) == (HANDLE)-1) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_FILEOPNFAIL));
        goto ErrExit1;
    }

    //
    // Create a map file of the opened file
    //
    if ((hMapFile = CreateFileMapping(hFile, NULL,
                             PAGE_READONLY, 0, 0, NULL)) == (HANDLE)-1) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_CREMAPFILEFAIL));
        goto ErrExit2;

    }

    //
    // Map a view of the whole file
    //
    if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0)) == NULL) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_MAPVWOFMAPFOFAIL));
        goto ErrExit3;
    }

    pMapFileTmp = pMapFile;

    //
    // First check that it is a bitmap file
    //
    if (*((PWORD)pMapFile) != 0x4d42) {              // 'BM'
        MessageBox(ghwndMain, GetStringRes(IDS_NOTDIBBMFILE),
            GetStringRes(IDS_ERROR), MB_OK);
        goto ErrExit3;
    }

    //
    // The file header doesn't end on DWORD boundary...
    //
    pbmh = (LPBITMAPINFOHEADER)((PBYTE)pMapFile + sizeof(BITMAPFILEHEADER));

    {
        BITMAPCOREHEADER bmch, *pbmch;
        BITMAPINFOHEADER bmih, *pbmih;
        PBYTE            pjTmp;
        ULONG            ulSiz;

        pbmch = &bmch;
        pbmih = &bmih;

        pjTmp = (PBYTE)pbmh;
        ulSiz = sizeof(BITMAPCOREHEADER);
        while (ulSiz--) {
            *(((PBYTE)pbmch)++) = *(((PBYTE)pjTmp)++);
        }

        pjTmp = (PBYTE)pbmh;
        ulSiz = sizeof(BITMAPINFOHEADER);
        while (ulSiz--) {
            *(((PBYTE)pbmih)++) = *(((PBYTE)pjTmp)++);
        }

        //
        // Use the size to determine if it is a BitmapCoreHeader or
        // BitmapInfoHeader
        //
        // Does PM supports 16 and 32 bpp? How?
        //
        if (bmch.bcSize == sizeof(BITMAPCOREHEADER))
        {
            wBitCount = bmch.bcBitCount;
            iNumClr = ((wBitCount == 24) ? 0 : (1 << wBitCount));
            sizBMI = sizeof(BITMAPCOREHEADER)+sizeof(RGBTRIPLE)*iNumClr;
            bCoreHdr = TRUE;
        }
        else            // BITMAPINFOHEADER
        {
            wBitCount = bmih.biBitCount;
            switch (wBitCount) {
                case 16:
                case 32:
                    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3;
                    break;
                case 24:
                    sizBMI = sizeof(BITMAPINFOHEADER);
                    break;
                default:
                    iNumClr = (1 << wBitCount);
                    sizBMI = sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*iNumClr;
                    break;
            }
            bCoreHdr = FALSE;
        }
    }

    if ((pbmi = (LPBITMAPINFO) LocalAlloc(LMEM_FIXED,sizBMI)) == NULL) {
        MessageBox(ghwndMain, GetStringRes(IDS_MALLOCFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
        goto ErrExit3;
    }

    //
    // Make sure we pass in a DWORD aligned BitmapInfo to CreateDIBitmap
    // Otherwise, exception on the MIPS platform
    // CR!!!  Equivalent to memcpy
    //
    pjTmp = (PBYTE)pbmi;

    while(sizBMI--)
    {
        *(((PBYTE)pjTmp)++) = *(((PBYTE)pbmh)++);
    }

    pMapFile = (PBYTE)pMapFile + ((BITMAPFILEHEADER *)pMapFile)->bfOffBits;

// !!! Use CreateBitmap for monochrome bitmap?

    //
    // Select the palette into the DC first before CreateDIBitmap()
    //
    bSelectDIBPal(hDC, pbmi, bCoreHdr);

// !!! We always pass a screen DC to this routine.
// !!! Maybe we should pass a metafile DC to this routine too.
// !!! The bitmap handle created for the screen DC won't give correct
// !!! color for the metafile DC.  So now, we always use the original
// !!! DIB info.
    if ((hBitmap = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)pbmi,
                        CBM_INIT, pMapFile, pbmi, DIB_RGB_COLORS)) == NULL) {
        SetWindowText(ghTextWnd, "Fail in creating DIB bitmap from file!");
        goto ErrExit4;
    }

    // reset gbUseDIB flag, now that we have opened up a new DIB
    gbUseDIB = FALSE;

// !!! Always use the DIB info o.w. metafile DC don't get the right color.
#if 0
    if (GetDeviceCaps(hDC, BITSPIXEL) < wBitCount) {
#endif
        gbUseDIB = TRUE;
        bFreeDibFile(&gDib);
        pFileInfo = &(gDib.rgFileInfo[0]);
        pFileInfo->hFile        = hFile;
        pFileInfo->hMapFile     = hMapFile;
        pFileInfo->lpvMapView   = pMapFileTmp;

        gDib.rgpjFrame[0]       = pMapFile;
        gDib.rgpbmi[0]          = pbmi;
        gDib.rgbCoreHdr[0]      = bCoreHdr;
        gDib.ulFrames           =
        gDib.ulFiles            = 1;
        return (hBitmap);
#if 0
    }
#endif

ErrExit4:
    LocalFree(pbmi);
ErrExit3:
    CloseHandle(hMapFile);
ErrExit2:
    CloseHandle(hFile);
ErrExit1:

    return (hBitmap);

}

/******************************Public*Routine******************************\
*
* bFreeDibFile
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bFreeDibFile(PDIBDATA pDibData)
{
    ULONG               ulFiles;
    ULONG               ulFrames;
    ULONG               i;
    PFILEINFO           pFileInfo;

    ulFiles = pDibData->ulFiles;
    ulFrames = pDibData->ulFrames;

    for (i = 0; i < ulFrames; i++) {
        LocalFree(pDibData->rgpjFrame[i]);
        LocalFree(pDibData->rgpbmi[i]);
    }

    for (i = 0; i < ulFiles; i++) {
        pFileInfo = &(pDibData->rgFileInfo[i]);
        CloseHandle(pFileInfo->hFile);
        CloseHandle(pFileInfo->hMapFile);
        UnmapViewOfFile(pFileInfo->lpvMapView);
    }

    pDibData->ulFiles = 0;
    pDibData->ulFrames = 0;
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bGetBMP
*
* Effects: call common dialog and pass the filename to hBmpLoadBitmapFile
*          return TRUE if successful, FALSE otherwise
*
* Warnings:
*
\**************************************************************************/

BOOL bGetBMP(HWND hwnd, BOOL bMask) {
    OPENFILENAME    ofn;
    char            szFile[256], szFileTitle[256];
    static char     *szFilter;
    BOOL            bSuccess;
    HDC             hDC;

    bSuccess = FALSE;

    BuildFilterStrs(-1);
    BuildFilterStrs(IDS_FT_DIB);
    szFilter = BuildFilterStrs(IDS_FT_RLE);

    strcpy(szFile, "*.bmp\0");
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = (bMask ? GetStringRes(IDS_LDMSK) : "Load Bitmap");
    ofn.Flags = 0L;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "BMP";

    if (!GetOpenFileName(&ofn))
        return 0L;

    hDC = GetDC(ghwndDrawSurf);
    if (bMask) {
        ghBmpMask = hBmpLoadBitmapFile(hDC, szFile);
        if (ghBmpMask != NULL)
            bSuccess = TRUE;
    } else {
        ghBmp = hBmpLoadBitmapFile(hDC, szFile);
        if (ghBmp != NULL)
            bSuccess = TRUE;
    }
    ReleaseDC(ghwndDrawSurf, hDC);

    return bSuccess;
}

/******************************Public*Routine******************************\
*
* bHitTest
*
* Effects:  Enumerates metafile records
*           Calling bDoHitTest to process each record found.
*               The mouse position is passed to the bDoHitTest
*
* Warnings:
*
\**************************************************************************/

BOOL bHitTest(HDC hDC, INT x, INT y) {
    BOOL          bSuccess;
    ENHMETAHEADER EnhMetaHdr;
    RECT          rcClientDS;
    HTDATA        htData;
    static        HCURSOR hCurHT, hCurWait;

    bSuccess = TRUE;

    if (ghMetaf == 0)
        return 0L;

    hCurHT = LoadCursor(NULL, IDC_CROSS);
    hCurWait = LoadCursor(NULL, IDC_WAIT);

    GetEnhMetaFileHeader(ghMetaf, sizeof(EnhMetaHdr), &EnhMetaHdr);

    htData.point.x = x;
    htData.point.y = y;
    htData.iRecord = EnhMetaHdr.nRecords;

    SetCursor(hCurWait);
    if (gbFit2Wnd) {
        GetClientRect(ghwndDrawSurf, &rcClientDS);
        EnumEnhMetaFile(hDC, ghMetaf, (ENHMFENUMPROC)bDoHitTest, (LPVOID) &htData, (LPRECT)&rcClientDS);
    } else {
        RECT rc;

        rc.top = rc.left = 0;
        rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
        rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
        EnumEnhMetaFile(hDC, ghMetaf, (ENHMFENUMPROC)bDoHitTest, (LPVOID) &htData, (LPRECT)&rc);
    }
    SetCursor(hCurHT);

    return bSuccess;
}

/******************************Public*Routine******************************\
*
* bDoHitTest
*
* Effects:      Play all records related to transformation
*               Remember new mouse position if the record is a MoveTo
*               Convert rectangle, ellipse, lineto and bezier to path
*               Widen the path and convert it to region.
*               Test if the mouse position is inside the region.
*
* Warnings:     Only handle rectangle, ellipse, line and polybezier
*
\**************************************************************************/

BOOL APIENTRY bDoHitTest(HDC hDC, LPHANDLETABLE lpHandleTable,
                                  LPENHMETARECORD lpEnhMetaRecord,
                                  UINT nHandles,
                                  LPVOID lpData) {
    BOOL            bSuccess;
    char            ach[128];
    char            achTmp[128];
    POINT           PtOrg;
    LONG            lNumDword;
    XFORM           xfSave;
    SIZE            SizeWndEx, SizeViewEx;
    POINT           ptWndOrgin, ptViewOrgin;
    int             i, iMode;
    HRGN            hRgn;
    PPOINT          pPt, pPtTmp;
    static HGDIOBJ  hObjOld=NULL;
    static LONG     lCurX=0;
    static LONG     lCurY=0;
    static BOOL     bXform=FALSE;
    static int      iCnt=0;

    iCnt++;

    //
    // select a wide pen for widen path later on
    //
    hObjOld = SelectObject(hDC, ghpnWide);

    //
    // save the mouse hit position, this was passed in as a POINT structure
    //
    PtOrg.x = (((HTDATA *)lpData)->point).x;
    PtOrg.y = (((HTDATA *)lpData)->point).y;

    //
    // save the number of parameters for the GDI fcn concerned in DWORD.
    // This is the total size of metafile record in question less the
    // size of the GDI function
    //
    lNumDword = (lpEnhMetaRecord->nSize-8) / 4;

    switch (lpEnhMetaRecord->iType) {
    case EMR_SETWINDOWEXTEX:
    case EMR_SETWINDOWORGEX:
    case EMR_SETVIEWPORTEXTEX:
    case EMR_SETVIEWPORTORGEX:
    case EMR_SETMAPMODE:
    case EMR_SCALEVIEWPORTEXTEX:
    case EMR_SCALEWINDOWEXTEX:
    case EMR_SETMETARGN:
    case EMR_SAVEDC:
    case EMR_RESTOREDC:
    case EMR_SETWORLDTRANSFORM:
    case EMR_MODIFYWORLDTRANSFORM: {
        //
        // play all records related to transformation & font
        //
        PlayEnhMetaFileRecord(hDC, lpHandleTable,
                                   lpEnhMetaRecord, nHandles);
        bXform = TRUE;
        return TRUE;
    }
    //
    // convert the following GDI calls to path for hit testing
    //
    case EMR_RECTANGLE: {
        BeginPath(hDC);
        Rectangle(hDC, lpEnhMetaRecord->dParm[0], lpEnhMetaRecord->dParm[1],
                       lpEnhMetaRecord->dParm[2], lpEnhMetaRecord->dParm[3]);
        EndPath(hDC);
        break;
    }
    case EMR_ELLIPSE: {
        BeginPath(hDC);
        Ellipse(hDC, lpEnhMetaRecord->dParm[0], lpEnhMetaRecord->dParm[1],
                     lpEnhMetaRecord->dParm[2], lpEnhMetaRecord->dParm[3]);
        EndPath(hDC);
        break;
    }
    case EMR_MOVETOEX: {
        //
        // Remember our current position
        //
        lCurX = lpEnhMetaRecord->dParm[0];
        lCurY = lpEnhMetaRecord->dParm[1];
        return TRUE;
    }
    case EMR_LINETO: {
        BeginPath(hDC);
        MoveToEx(hDC, lCurX, lCurY, NULL);
        LineTo(hDC, lpEnhMetaRecord->dParm[0], lpEnhMetaRecord->dParm[1]);
        EndPath(hDC);
        break;
    }
    case EMR_POLYBEZIER16: {
        int         i;
        LONG        lSize;
        LONG        lPtCnt;

        lPtCnt = lpEnhMetaRecord->dParm[4];
        lSize = lPtCnt * sizeof(POINTL);

        if ((pPt = (PPOINT) LocalAlloc(LMEM_FIXED, lSize)) == NULL) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_MALLOCFAILNOHIT));
            return TRUE;
        }

        pPtTmp = pPt;

        for (i=0; i < (INT) lPtCnt; i++, pPtTmp++) {
            pPtTmp->x = (LONG)(LOWORD(lpEnhMetaRecord->dParm[i+5]));
            pPtTmp->y = (LONG)(HIWORD(lpEnhMetaRecord->dParm[i+5]));
        }

        BeginPath(hDC);
        PolyBezier(hDC, (LPPOINT)pPt, (DWORD) lPtCnt);
        EndPath(hDC);
        LocalFree(pPt);
        break;
    }
    default:
        wsprintf((LPSTR) ach, GetStringRes(IDS_FMT_NOHTST), rgMetaName[lpEnhMetaRecord->iType]);
        SetWindowText(ghTextWnd, ach);
        return TRUE;
    }   //switch

    if (bXform) {
        //
        // Set World transform to identity temporarily so that pen width
        // is not affected by world to page transformation
        //
        GetWorldTransform(hDC, &xfSave);
        ModifyWorldTransform(hDC, NULL, MWT_IDENTITY);

        //
        // Set Page transform to identity temporarily so that pen width
        // is not affected by page to device transformation
        //
        iMode = GetMapMode(hDC);

        if ((iMode == MM_ISOTROPIC) || (iMode == MM_ANISOTROPIC)) {
            GetWindowOrgEx(hDC, &ptWndOrgin);
            GetWindowExtEx(hDC, &SizeWndEx);
            GetViewportExtEx(hDC, &SizeViewEx);
            GetViewportOrgEx(hDC, &ptViewOrgin);
        }

        SetMapMode(hDC, MM_TEXT);
    }

    WidenPath(hDC);

    hRgn = PathToRegion(hDC);

    if (hRgn == 0) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_NULLRGNHTSTFAIL));
        DeleteObject(hRgn);
        return TRUE;
    }
    //DPtoLP(hDC, &PtOrg, 1);
    //SetPixel(hDC, PtOrg.x, PtOrg.y, RGB(0, 255, 0));
    //
    // test if mouse hit position is in region
    //
    bSuccess = PtInRegion(hRgn, PtOrg.x, PtOrg.y);
    //Temporily comment this out
    FillRgn(hDC, hRgn, ghbrRed);
    DeleteObject(hRgn);
    //

// Set transform back.
    //
    if (bXform) {
        SetWorldTransform(hDC, &xfSave);
        SetMapMode(hDC, iMode);

        if ((iMode == MM_ISOTROPIC) || (iMode == MM_ANISOTROPIC)) {
            SetWindowOrgEx(hDC, ptWndOrgin.x, ptWndOrgin.y, NULL);
            SetWindowExtEx(hDC, SizeWndEx.cx, SizeWndEx.cy, NULL);
            SetViewportExtEx(hDC, SizeViewEx.cx, SizeViewEx.cy, NULL);
            SetViewportOrgEx(hDC, ptViewOrgin.x, ptViewOrgin.y, NULL);
        }
    }

    if (bSuccess) {
        Beep(440, 500);
        //
        // Reporting the metafile record number.  Then reset counter.
        //
        SetDlgItemInt(ghwndCtrlPanel, DID_COUNTER, iCnt, FALSE);
        iCnt=0;
        wsprintf((LPSTR) ach, GetStringRes(IDS_FMT_HIT), rgMetaName[lpEnhMetaRecord->iType]);

        for (i=0; i < lNumDword; i++) {
            wsprintf((LPSTR) achTmp, "%ld ", lpEnhMetaRecord->dParm[i]);
            if ((strlen(ach)+strlen(achTmp))/sizeof(char) >= 128)
                break;
            strcat(ach, achTmp);
        }

        SetWindowText(ghTextWnd, ach);
        SelectObject(hDC, hObjOld);
        bXform = FALSE;
        return FALSE;
    }
    SetWindowText(ghTextWnd, GetStringRes(IDS_NOHIT));
    if (iCnt >= ((HTDATA *)lpData)->iRecord)
        iCnt = 0;
    return TRUE;

    UNREFERENCED_PARAMETER(lpHandleTable);
    UNREFERENCED_PARAMETER(nHandles);

}

/******************************Public*Routine******************************\
*
* bChooseNewFont
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

BOOL bChooseNewFont(HWND hwnd, PLOGFONT plf, COLORREF *pClrRef) {
   HDC                  hDC;
   static CHOOSEFONT    chf;
   static BOOL          bInit=TRUE;

   if (bInit) {
        bInit = FALSE;

        hDC = GetDC( hwnd );
        chf.hDC = CreateCompatibleDC( hDC );
        ReleaseDC( hwnd, hDC );

        chf.lStructSize = sizeof(CHOOSEFONT);
        chf.hwndOwner = hwnd;
        chf.lpLogFont = plf;
        chf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT;
        chf.rgbColors = *pClrRef;
        chf.lCustData = 0;
        chf.hInstance = (HANDLE)NULL;
        chf.lpszStyle = (LPSTR)NULL;
        chf.nFontType = SCREEN_FONTTYPE;
        chf.nSizeMin = 0;
        chf.nSizeMax = 0;
        chf.lpfnHook = (LPCFHOOKPROC)NULL;
        chf.lpTemplateName = (LPSTR)NULL;
   }

   if (ChooseFont( &chf ) == FALSE ) {
        DeleteDC( hDC );
return FALSE;
   }

   *pClrRef = chf.rgbColors;

   DeleteDC( hDC );
   return (TRUE);
}

/******************************Public*Routine******************************\
*
* bChooseNewColor
*
* Effects:  Returns TRUE if successful; lpdwRGB points the color selected.
*           Otherwise, FALSE.
*
* Warnings:
*
\**************************************************************************/

BOOL bChooseNewColor(HWND hwnd, LPDWORD lpdwRGB) {
    static DWORD argbCust[16] = {
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255),
        RGB(255, 255, 255), RGB(255, 255, 255)
    };
    CHOOSECOLOR cc;
    BOOL bResult;

    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = hwnd;
    cc.hInstance = ghModule;
    cc.rgbResult = *lpdwRGB;
    cc.lpCustColors = argbCust;
    cc.Flags = CC_RGBINIT | CC_SHOWHELP;
    cc.lCustData = 0;
    cc.lpfnHook = NULL;
    cc.lpTemplateName = NULL;

    bResult = ChooseColor(&cc);

    if (bResult) {
        *lpdwRGB = cc.rgbResult;
        return TRUE;
    }

    return FALSE;
}

/******************************Public*Routine******************************\
*
* hBrCreateBrush
*
* Effects: Creates a brush with the specified RGB
*
* Warnings:
*
\**************************************************************************/

HBRUSH hBrCreateBrush(HDC hDC, DWORD dwRGB)
{
    HDC hdcMem;
    HBRUSH hbr;
    HBRUSH hbrOld;
    HBITMAP hbmPat;
    HBITMAP hbmOld;

    hbr = CreateSolidBrush(dwRGB);
    hdcMem = CreateCompatibleDC(hDC);

    //
    // Minimum size for a bitmap to be used in a fill pattern is 8x8
    //
    hbmPat = CreateCompatibleBitmap(hDC, 8, 8);

    hbmOld = SelectObject(hdcMem, hbmPat);
    hbrOld = SelectObject(hdcMem, hbr);
    PatBlt(hdcMem, 0, 0, 8, 8, PATCOPY);

    //
    // Deselect hbmPat and hbr
    //
    SelectObject(hdcMem, hbmOld);
    SelectObject(hdcMem, hbrOld);

    DeleteDC(hdcMem);
    DeleteObject(hbr);

    hbr = CreatePatternBrush(hbmPat);

    DeleteObject(hbmPat);

    return hbr;
}

/******************************Public*Routine******************************\
*
* bPrintMf  Brings up the print dialog for printer setup and then
*           starts printing the enhanced metafile.
*
*           pPD     Points to a PRTDATA structure that contains the
*                   the handle for the Enh. Metafile for printing.
*
* Effects:  Returns TRUE if sucessful.  Otherwise, it is FALSE.
*           GlobalFree pPD when exits.
*
* Warnings:
*
\**************************************************************************/

BOOL bPrintMf(PPRTDATA pPD) {
    DOCINFO         DocInfo;
    HDC             hDCPrinter;
    ENHMETAHEADER   EnhMetaHdr;
    HENHMETAFILE    hEnhMf;
    TCHAR           buf[128];
    PRINTDLG        pd;
    BOOL            bSuccess;
    int             iEntries;
    PLOGPALETTE     plogPal;
    PBYTE           pjTmp;
    HPALETTE        hPal;
    BOOL            bRCSet = FALSE;

    bSuccess = TRUE;

    if (pPD->hMetaf == 0) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_NOMFTOPRT));
        goto PMF_EXIT;
        bSuccess = FALSE;
    }

    hEnhMf = CopyEnhMetaFile(pPD->hMetaf, NULL);
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize = sizeof(PRINTDLG);
    pd.hwndOwner   = ghwndMain;
    pd.Flags       = PD_RETURNDC;
    pd.hInstance   = ghModule;

    if (!PrintDlg(&pd)) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_ESCPRT));
        goto PMF_EXIT;
        bSuccess = FALSE;
    }

    if (pd.hDC == NULL) {
        SetWindowText(ghTextWnd, GetStringRes(IDS_CREPRTDCFAIL));
        goto PMF_EXIT;
        bSuccess = FALSE;
    }

    hDCPrinter = pd.hDC;
    if (GetEnhMetaFileDescription(hEnhMf, 128, (LPTSTR)buf) == 0) {
        strcpy(buf, "No Title");
    }

    memset(&DocInfo, 0, sizeof(DOCINFO));
    DocInfo.cbSize      = sizeof(DOCINFO);
    DocInfo.lpszDocName = (LPTSTR) buf;
    DocInfo.lpszOutput  = NULL;
    StartDoc(hDCPrinter, &DocInfo);
    StartPage(hDCPrinter);

    SetWindowText(ghTextWnd, GetStringRes(IDS_PRT));

    iEntries = GetEnhMetaFilePaletteEntries(hEnhMf, 0, NULL);

    if (iEntries) {
        if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                sizeof(DWORD) + sizeof(PALETTEENTRY)*iEntries )) == NULL) {
            MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
        }

        plogPal->palVersion = 0x300;
        plogPal->palNumEntries = (WORD) iEntries;
        pjTmp = (PBYTE) plogPal;
        pjTmp += 4;

        GetEnhMetaFilePaletteEntries(hEnhMf, iEntries, (PPALETTEENTRY)pjTmp);
        hPal = CreatePalette(plogPal);
        GlobalFree(plogPal);

        SelectPalette(hDCPrinter, hPal, FALSE);
        RealizePalette(hDCPrinter);
    }

    GetEnhMetaFileHeader(hEnhMf, sizeof(ENHMETAHEADER), &EnhMetaHdr);

    bRCSet = bSetRC2MatchEmfRC(hDCPrinter, EnhMetaHdr, hEnhMf, NULL);
    if (!bRCSet) {
        vErrOut("bPrintMF: failed");
    }

    if (pPD->bFit2Wnd) {
        int     iWidth, iHeight;
        RECT    rc;

        iWidth = GetDeviceCaps(hDCPrinter, HORZRES);
        iHeight = GetDeviceCaps(hDCPrinter, VERTRES);
        rc.left = rc.top = 0;
        rc.right = iWidth;
        rc.bottom = iHeight;
        bSuccess = PlayEnhMetaFile(hDCPrinter, hEnhMf, (LPRECT) &rc);
        if (!bSuccess) {
            char    text[128];

            wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
            OutputDebugString(text);
        }

    } else {
        RECT rc;

        rc.top = rc.left = 0;
        rc.right = EnhMetaHdr.rclBounds.right - EnhMetaHdr.rclBounds.left;
        rc.bottom = EnhMetaHdr.rclBounds.bottom - EnhMetaHdr.rclBounds.top;
        bSuccess = PlayEnhMetaFile(hDCPrinter, hEnhMf, (LPRECT) &rc);
        if (!bSuccess) {
            char    text[128];

            wsprintf(text, GetStringRes(IDS_FMT_PLAYEMFFAIL), GetLastError());
            OutputDebugString(text);
        }
    }

//    if (gbDB && bSuccess)
//       SwapBuffers(hDCPrinter);

    EndPage(hDCPrinter);
    EndDoc(hDCPrinter);
    SetWindowText(ghTextWnd, GetStringRes(IDS_PRTTHRDDONE));

    if (bRCSet) {
        bCleanUpRC();
    }

PMF_EXIT:

    ExitThread(0);
    return bSuccess;

}

/******************************Public*Routine******************************\
*
* bSelectDIBPal
*
* Effects: Creates a logical palette from the DIB and select it into the DC
*          and realize the palette. Saving the hPal in the ghPal
*
* Warnings: Based on Windows NT DIB support.  If PM support 16,24,32 bpp
*           we need to modify this routine.
*           Global alert! ghPal is changed here...
*
\**************************************************************************/

BOOL bSelectDIBPal(HDC hDC, LPBITMAPINFO pbmi, BOOL bCoreHdr)
{
  LOGPALETTE    *plogPal;
  UINT          uiSizPal;
  INT           i, iNumClr;
  WORD          wBitCount;

  if (bCoreHdr) {
    wBitCount = ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcBitCount;
  } else {
    wBitCount = pbmi->bmiHeader.biBitCount;
  }

  switch (wBitCount) {
    case 16:
    case 24:
    case 32:                            // Does PM supports these?
        return FALSE;
    default:
        iNumClr = (1 << wBitCount);
        break;
  }

  uiSizPal = sizeof(WORD)*2 + sizeof(PALETTEENTRY)*iNumClr;
  if ((plogPal = (LOGPALETTE *) LocalAlloc(LMEM_FIXED,uiSizPal)) == NULL) {
      MessageBox(ghwndMain, GetStringRes(IDS_ALLOCPALFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
      ghPal = NULL;
      return FALSE;
  }

  plogPal->palVersion = 0x300;
  plogPal->palNumEntries = (WORD) iNumClr;

  if (bCoreHdr) {
    for (i=0; i<iNumClr; i++) {
        plogPal->palPalEntry[i].peRed   = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtRed;
        plogPal->palPalEntry[i].peGreen = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtGreen;
        plogPal->palPalEntry[i].peBlue  = ((LPBITMAPCOREINFO)pbmi)->bmciColors[i].rgbtBlue;
        plogPal->palPalEntry[i].peFlags = PC_RESERVED;
    }
  } else {
    for (i=0; i<iNumClr; i++) {
        plogPal->palPalEntry[i].peRed   = pbmi->bmiColors[i].rgbRed;
        plogPal->palPalEntry[i].peGreen = pbmi->bmiColors[i].rgbGreen;
        plogPal->palPalEntry[i].peBlue  = pbmi->bmiColors[i].rgbBlue;
        plogPal->palPalEntry[i].peFlags = PC_RESERVED;
    }
  }

  DeleteObject(ghPal);
  ghPal = CreatePalette((LPLOGPALETTE)plogPal);
  if ((ghPal) == NULL) {
      MessageBox(ghwndMain, GetStringRes(IDS_CREPALFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
      return FALSE;
  }

  if ((GetDeviceCaps(hDC, RASTERCAPS)) & RC_PALETTE) {
    SelectPalette(hDC, ghPal, FALSE);
    RealizePalette(hDC);
  }

  GlobalFree(plogPal);

  return TRUE;
}

/******************************Public*Routine******************************\
*
* bPlgBlt
*
* Effects:  If Source DIB bpp > Destination DC's
*           use Halftone for PlgBlt.
*
* Warnings: Global Alert!
*           gbUseDIB is always TRUE now.
*
\**************************************************************************/

BOOL bPlgBlt(HDC hDC, LPPOINT rgPtsBMP)
{
    HDC                  hDCRef;
    HDC                  hDCSrn;                // hDC can be metaf DC
    HGDIOBJ              hObjOld, hBmpMem;
    BITMAP               bm;
    INT                  iBpp;
    WORD                 wBitCnt;

    hDCSrn = GetDC(ghwndDrawSurf);
    hDCRef = CreateCompatibleDC(hDC);

    if (gbUseDIB) {
        int         cx, cy, dx, dy;
        PBITMAPINFO pbmi;

        pbmi = (gDib.rgpbmi[0]);
        dx = rgPtsBMP[0].x - rgPtsBMP[1].x;
        dy = rgPtsBMP[0].y - rgPtsBMP[1].y;
        cx = (INT) sqrt( dx * dx + dy * dy );

        dx = rgPtsBMP[0].x - rgPtsBMP[2].x;
        dy = rgPtsBMP[0].y - rgPtsBMP[2].y;
        cy = (INT) sqrt( dx * dx + dy * dy );

        iBpp = GetDeviceCaps(hDC, BITSPIXEL);

        if (gDib.rgbCoreHdr[0]) {
            wBitCnt = ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcBitCount;
        } else {
            wBitCnt = pbmi->bmiHeader.biBitCount;
        }

        if (iBpp < wBitCnt) {   // Do Halftone
            SetStretchBltMode(hDCRef, HALFTONE);
            if (ghHT) {
                SelectPalette(hDCRef, ghHT, FALSE);
                SelectPalette(hDC, ghHT, FALSE);
                SelectPalette(hDCSrn, ghHT, FALSE); // hDC can be metaf DC
                RealizePalette(hDCSrn);             // always realize the srn DC

                // Don't have to realize the palette in hDCRef
                // RealizePalette(hDCRef);

                // has to be compatible with screen DC, cannot be hDCRef
                // memory DC has no bitmap by default?
                // hDC may be a metafile DC, so use hDCSrn
                hBmpMem = CreateCompatibleBitmap(hDCSrn, cx, cy);
                SelectObject(hDCRef, hBmpMem);
            } else {
                MessageBox(ghwndMain, GetStringRes(IDS_HFTONEPALNULL),
                    GetStringRes(IDS_ERROR), MB_OK);
            }
        } else {
            SetStretchBltMode(hDCRef, COLORONCOLOR);
            if (ghPal) {
                if (ghDCMetaf == hDC)
                    CopyPalette(ghPal);
                SelectPalette(hDCRef, ghPal, FALSE);
                SelectPalette(hDC, ghPal, FALSE);
                SelectPalette(hDCSrn, ghPal, FALSE); // hDC can be metaf DC
                RealizePalette(hDCSrn);             // always realize the srn DC

                // Don't have to realize the palette in hDCRef
                // RealizePalette(hDCRef);

                // has to be compatible with screen DC, cannot be hDCRef
                // memory DC has no bitmap by default?
                // hDC may be a metafile DC, so use hDCSrn
                hBmpMem = CreateCompatibleBitmap(hDCSrn, cx, cy);
                SelectObject(hDCRef, hBmpMem);
            } else {
                MessageBox(ghwndMain, GetStringRes(IDS_PALNULL),
                    GetStringRes(IDS_ERROR), MB_OK);
            }
        }

        if (gDib.rgbCoreHdr[0]) {
            StretchDIBits(hDCRef, 0,0, cx, cy,
                          0,0, ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcWidth, ((LPBITMAPCOREINFO)pbmi)->bmciHeader.bcHeight,
                          gDib.rgpjFrame[0], pbmi, DIB_RGB_COLORS, SRCCOPY);
        } else {
            StretchDIBits(hDCRef, 0,0, cx, cy,
                          0,0, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight,
                          gDib.rgpjFrame[0], pbmi, DIB_RGB_COLORS, SRCCOPY);
        }

        PlgBlt(hDC, rgPtsBMP, hDCRef, 0, 0, cx, cy,
               ghBmpMask, 0, 0);

        DeleteObject(hBmpMem);

    } else {
        hObjOld = SelectObject(hDCRef, ghBmp);

        GetObject(ghBmpMask, sizeof(BITMAP), (LPSTR)&bm);
        if (bm.bmBitsPixel != 1) {
            SetWindowText(ghTextWnd, GetStringRes(IDS_MUSTMSKMCHROMEBM));
            ghBmpMask = NULL;
        }

        GetObject(ghBmp, sizeof(BITMAP), (LPSTR)&bm);

        if (ghPal) {
            SelectPalette(hDC, ghPal, FALSE);
            RealizePalette(hDC);
            SetStretchBltMode(hDC, COLORONCOLOR);
        }
        PlgBlt(hDC, rgPtsBMP, hDCRef, 0, 0, bm.bmWidth, bm.bmHeight,
               ghBmpMask, 0, 0);

        SelectObject(hDCRef, hObjOld);
    }

    DeleteDC(hDCRef);
    ReleaseDC(ghwndDrawSurf, hDCSrn);
    return TRUE;

}

/******************************Public*Routine******************************\
*
* HPALETTE CopyPalette
*
* Effects:
*
* Warnings:
*
\**************************************************************************/

HPALETTE CopyPalette(HPALETTE hPalSrc)
{
    PLOGPALETTE     plogPal;
    PBYTE           pjTmp;
    int             iNumEntries=0;
    HPALETTE        hPal;

    if ((iNumEntries = GetPaletteEntries(hPalSrc, 0, iNumEntries, NULL)) == 0) {
        MessageBox(ghwndMain, GetStringRes(IDS_NOPALENTRYFORCPY),
            GetStringRes(IDS_ERROR), MB_OK);
        return (HPALETTE) NULL;
    }

    if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
            sizeof(DWORD) + sizeof(PALETTEENTRY)*iNumEntries )) == NULL) {
        MessageBox(ghwndMain, GetStringRes(IDS_CPYPALFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
        return (HPALETTE) NULL;
    }

    plogPal->palVersion = 0x300;
    plogPal->palNumEntries = (WORD) iNumEntries;
    pjTmp = (PBYTE) plogPal;
    pjTmp += 4;
    GetPaletteEntries(hPalSrc, 0, iNumEntries, (PPALETTEENTRY)pjTmp);
    hPal = CreatePalette(plogPal);

    GlobalFree(plogPal);

    return hPal;
}

/******************************Public*Routine******************************\
*
* iTT
*
* Effects: set the global variable gbTT if the family is true type
*
* Warnings:
*
\**************************************************************************/

int CALLBACK iTT(
    LPLOGFONT    lpLF,
    LPTEXTMETRIC lpTM,
    DWORD        dwFontType,
    LPARAM       lpData)
{

    if (lpTM->tmPitchAndFamily & TMPF_TRUETYPE) {
        //OutputDebugString("TRUETYPE\n");
        *((BOOL *)lpData) = TRUE;
    } else {
        //OutputDebugString("NON-TRUETYPE\n");
        *((BOOL *)lpData) = FALSE;
    }

#if 0
    //
    // that's equivalent
    //
    if (dwFontType & TRUETYPE_FONTTYPE) {
        //OutputDebugString("TRUETYPE\n");
        *((BOOL *)lpData) = TRUE;
    } else {
        //OutputDebugString("NON-TRUETYPE\n");
        *((BOOL *)lpData) = FALSE;
    }
#endif
    return 0;

    UNREFERENCED_PARAMETER (lpLF);
    //UNREFERENCED_PARAMETER (lpTM);
    UNREFERENCED_PARAMETER (dwFontType);

}

/******************************Public*Routine******************************\
*
* CMTMLTFMT *pLoadMltFmtFile(VOID)
*
* Effects:  Load either EPS or enh mf
*
* Warnings: CR! change this to load multiple def of picture
*
\**************************************************************************/

CMTMLTFMT *pLoadMltFmtFile(VOID)
{
    OPENFILENAME    ofn;
    char            szDirName[256];
    char            szFile[256], szFileTitle[256];
    static char     *szFilter;
    HANDLE          hFile, hMapFile;
    LPVOID          pMapFile;
    DWORD           dwFileSizeLow, dwFileSizeHigh;
    CMTMLTFMT       *pMfmt;

    pMfmt = (CMTMLTFMT*)NULL;

    BuildFilterStrs(-1);
    BuildFilterStrs(IDS_FT_EPS);
    szFilter = BuildFilterStrs(IDS_FT_EMF);

    GetSystemDirectory((LPSTR) szDirName, 256);
    strcpy(szFile, "*.eps\0");
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = GetFocus();
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = (LPSTR) NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFileTitle = szFileTitle;
    ofn.nMaxFileTitle = sizeof(szFileTitle);
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = (LPSTR) NULL;
    ofn.Flags = 0L;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "EPS";

    if (!GetOpenFileName(&ofn)) {
        goto EXIT;
    }

    if ((hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) == (HANDLE)-1) {
        goto EXIT;
    }

    dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
    if ((dwFileSizeLow == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) {
        goto EXIT;
    }

    //
    // Create a map file of the opened file
    //
    if ((hMapFile = CreateFileMapping(hFile, NULL,
                             PAGE_READONLY, 0, 0, NULL)) == (HANDLE)-1) {
        goto EXIT2;
    }

    //
    // Map a view of the whole file
    //
    if ((pMapFile = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0)) == NULL) {
        goto EXIT3;
    }

//
// CR!! In future, change this to load different def of the picture...
//
  {
    ULONG       ulSize;
    PBYTE       pjTmp;
    RECTL       rectl;

    ulSize = dwFileSizeLow+sizeof(CMTMLTFMT);
    if ((pMfmt = (CMTMLTFMT *) LocalAlloc(LMEM_FIXED, ulSize)) == NULL) {
        MessageBox(GetFocus(), GetStringRes(IDS_MALLOCFAIL),
            GetStringRes(IDS_ERROR), MB_OK);
        goto EXIT3;
    }

    pMfmt->ident = GDICOMMENT_IDENTIFIER;
    pMfmt->iComment = GDICOMMENT_MULTIFORMATS;
    pMfmt->nFormats = 1;
    pMfmt->aemrformat[0].cbData = dwFileSizeLow;
    pMfmt->aemrformat[0].offData = 11*sizeof(DWORD);

    // parse for %!PS-Adobe-3.0 EPSF keyword
    //           Enhanced Metafile signature
    // set EMRFORMAT.dSignature appropiately

    if (((ENHMETAHEADER *) pMapFile)->dSignature == ENHMETA_SIGNATURE) {

        pMfmt->aemrformat[0].dSignature = ENHMETA_SIGNATURE;
        pMfmt->aemrformat[0].nVersion = 0;                    // not for emf
        pMfmt->rclOutput.left   = ((ENHMETAHEADER *) pMapFile)->rclBounds.left;
        pMfmt->rclOutput.top    = ((ENHMETAHEADER *) pMapFile)->rclBounds.top;
        pMfmt->rclOutput.right  = ((ENHMETAHEADER *) pMapFile)->rclBounds.right;
        pMfmt->rclOutput.bottom = ((ENHMETAHEADER *) pMapFile)->rclBounds.bottom;
    }
    else    //assume it is Adobe EPS
    if (bGetEPSBounds(pMapFile, &rectl)) {

        char text[128];

        pMfmt->aemrformat[0].dSignature = 0x46535045;
        pMfmt->aemrformat[0].nVersion = 1;
        pMfmt->rclOutput.left   = rectl.left;
        pMfmt->rclOutput.top    = rectl.top;
        pMfmt->rclOutput.right  = rectl.right;
        pMfmt->rclOutput.bottom = rectl.bottom;

        wsprintf(text, "Bounds = %d %d %d %d",
                 rectl.left, rectl.top, rectl.right, rectl.bottom);
        MessageBox(GetFocus(), text, "Bounds", MB_OK);

    }
    else {
        // unknown file type
        Free(pMfmt);
        pMfmt = NULL;
        goto EXIT3;
    }

    pjTmp = (PBYTE)(((DWORD *)pMfmt->aemrformat)+4);
    while (dwFileSizeLow--) {
        *(((PBYTE)pjTmp)++) = *(((PBYTE)pMapFile)++);
    }

  }

EXIT3:
    CloseHandle(hMapFile);
EXIT2:
    CloseHandle(hFile);
EXIT:

    return pMfmt;
}

HLOCAL Free(CMTMLTFMT *pMfmt) {
    return LocalFree(pMfmt);
}

#define MYDBG 0

BOOL bIsAdobe(char *szStr)
{
    if (strcmp(szStr, "%!PS-Adobe-3.0") == 0)
        return TRUE;
    else
        return FALSE;
}

BOOL bIsEPS(char *szStr)
{
    if ((strcmp(szStr, "EPSF-3.0") == 0) ||
        (strcmp(szStr, "EPSF-2.0") == 0))
        return TRUE;
    else
        return FALSE;
}

BOOL bIsBndBox(char *szStr)
{
    if (strcmp(szStr, "%%BoundingBox:") == 0)
        return TRUE;
    else
        return FALSE;
}

BOOL bIsEOF(char *szStr)
{
    if (strcmp(szStr, "%%EOF") == 0)
        return TRUE;
    else
        return FALSE;
}

BOOL bGetEPSBounds(LPVOID lpData, RECTL *prctl)
{
    char szKeyWord[128], szValue[128];
    int  index;

    if (lpData == NULL) {
#if MYDBG
        MessageBox(GetFocus(), GetStringRes(IDS_NULLPTR),
            GetStringRes(IDS_ERROR), MB_OK);
#endif
        return FALSE;
    }

    index = 0;

    if (!bGetWord(lpData, szKeyWord, &index))
        return FALSE;

    if (!bIsAdobe(szKeyWord)) {
        MessageBox(GetFocus(), GetStringRes(IDS_NOTADOBE),
            GetStringRes(IDS_ERROR), MB_OK);
        return FALSE;
    }

    if (!bGetWord(lpData, szValue, &index))
        return FALSE;

    if (!bIsEPS(szValue)) {
        MessageBox(GetFocus(), "Not EPS!",
            GetStringRes(IDS_ERROR), MB_OK);
        return FALSE;
    }

    if (!bGoNextLine(lpData, &index))
        return FALSE;

    while ((bGetWord(lpData, szKeyWord, &index)) &&
           (!bIsBndBox(szKeyWord))) {
#if MYDBG
        MessageBox(GetFocus(), GetStringRes(IDS_SKPTOEOL),
            GetStringRes(IDS_ERROR), MB_OK);
#endif
        if (!bGoNextLine(lpData, &index)) {
            MessageBox(GetFocus(), GetStringRes(IDS_MUSTNOTEOF),
                GetStringRes(IDS_ERROR), MB_OK);
            return FALSE;
        }
    }

    if (bIsBndBox(szKeyWord)) {
        if (bGetWord(lpData, szValue, &index))
            prctl->left    = atol(szValue);
        else {
            MessageBox(GetFocus(), GetStringRes(IDS_GETBNDLFTFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            return FALSE;
        }

        if (bGetWord(lpData, szValue, &index))
            prctl->top     = atol(szValue);
        else {
            MessageBox(GetFocus(), GetStringRes(IDS_GETBNDTOPFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            return FALSE;
        }

        if (bGetWord(lpData, szValue, &index))
            prctl->right   = atol(szValue);
        else {
            MessageBox(GetFocus(), GetStringRes(IDS_GETBNDRGTFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            return FALSE;
        }

        if (bGetWord(lpData, szValue, &index))
            prctl->bottom  = atol(szValue);
        else {
            MessageBox(GetFocus(), GetStringRes(IDS_GETBNDBOTFAIL),
                GetStringRes(IDS_ERROR), MB_OK);
            return FALSE;
        }
    }

    return TRUE;

}

BOOL bGetWord(LPVOID lpData, char *str, int* pi)
{
    char *pstr;

    pstr = str;

    while (((char *)lpData)[*pi] == ' ')
        (*pi)++;

    while ((((char *)lpData)[*pi] != ' ') &&
           (((char *)lpData)[*pi] != '\n') &&
           (((char *)lpData)[*pi] != '\r')) {
        *str++ = ((char *)lpData)[(*pi)++];
    }
    *str++ = '\0';

#if MYDBG
    {
    char text[128];

    wsprintf(text, "bGetWord gets %s", pstr);
    MessageBox(GetFocus(), text, "Info", MB_OK);
    }
#endif

    return TRUE;
}

BOOL bGoNextLine(LPVOID lpData, int* pi)
{
    char tmp[128];
    int  q;

    while ((((char *)lpData)[*pi] != '\n') &&
           (((char *)lpData)[*pi] != '\r'))
        (*pi)++;

    //
    // skip them
    //
    *pi += 2;

    q = *pi;

    if ((bGetWord(lpData, tmp, &q)) && (bIsEOF(tmp)) )
        return FALSE;

    return TRUE;
}

/***************************************************************************\
* bSetAdvancedGraphics
*
* Set the HDC to advanced graphics mode to get world transforms, and other
*  features...  However, only do this if we are running on Windows NT, as
*  the other platforms don't support GM_ADVANCED.
*
\***************************************************************************/
BOOL bSetAdvancedGraphics(HDC hdc)
{
    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

    GetVersionEx (&osvi);
    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)

      if ((SetGraphicsMode(hdc, GM_ADVANCED)) == 0) {
 MessageBox(ghwndMain, GetStringRes(IDS_SETADVGRPHMDFAIL), NULL, MB_OK);
      }
    return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:    Load the resource string with the ID given, and return a
*               pointer to it.  Notice that the buffer is common memory so
*               the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}

LPTSTR   GetStringRes2 (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}

/******************************************************************************\
*
*  FUNCTION:    BuildFilterStrs (int id INPUT ONLY)
*
*  COMMENTS:    Construct a filter string by a series of LoadString calls. The
*resulting string is as follows: "%OldStr%\0%IDStr%\0%(ID+1)Str%\0\0".
*Note that each consecutive function call appends the new filter
*string on to the end of a static buffer. Call the function with
*id = -1 to reinitialize the buffer.
*
\******************************************************************************/

LPTSTRBuildFilterStrs(int id)
{
  static TCHAR buffer[1024];
  static TCHAR *pBuf;

  if (id == -1)
  {
pBuf = buffer;
  }
  else
  {
LoadString(GetModuleHandle (NULL), id, pBuf, 1024 - (pBuf-buffer));
pBuf += strlen(pBuf) + 1;
LoadString(GetModuleHandle (NULL), id + 1, pBuf, 1024 - (pBuf-buffer));
pBuf += strlen(pBuf) + 1;
  }

  *pBuf = 0;
  *(pBuf+1) = 0;
  return buffer;
}


MFEDIT.H

/******************************Module*Header*******************************\
* Module Name: mfedit.h
*
* Header file for mfedit
*
* Copyright 1996 - 1998 Microsoft Corporation
*
* Contains all the definitions, global variables, structures, etc
*
* Dependencies:
*
\**************************************************************************/
#define LIGHTGRAY               RGB(192, 192, 192)
#define DARKGRAY                RGB(128, 128, 128)
#define MAX_POINTS              256
#define MAX_POINTS_MF           3
#define MAX_POINTS_BMP          3
#define MAX_FRAME               800
#define MAX_FILE                800
#define MAX_ERR_STRING          128

//
// String ID's
//
#define IDS_INITAPPFAIL16
#define IDS_LOADACCFAIL17
#define IDS_MFDEMOCL18
#define IDS_NOMFFORCPY19
#define IDS_CNVEMFTO3X20
#define IDS_GETWMFBTSFAIL21
#define IDS_MALLOCFAIL22
#define IDS_GETWMFBTSFAIL223
#define IDS_MALLOCFAIL224
#define IDS_CPY3XMFTOCLP25
#define IDS_GETWMFBTSFAIL2X26
#define IDS_CNVFAIL27
#define IDS_CPYEMFTOCLP28
#define IDS_PST3XMF29
#define IDS_NOINFO3XMFX30

#define IDS_SAVEMF32
#define IDS_FILEOPNFAIL33
#define IDS_MAPFCREFAIL34
#define IDS_MAPVWMAPFOFAIL35
#define IDS_GET3XFRMCLPFAIL36
#define IDS_PSTEMF37
#define IDS_GETEMFFROMCLPFAIL38
#define IDS_ERROR39
#define IDS_NOMFLDFORHTST40
#define IDS_DLGABOUTCREFAIL41

#define IDS_LDMSKBM43
#define IDS_LDBM44
#define IDS_SVDRAWSFASBM45
#define IDS_LDMF46
#define IDS_MUSTESCHTSTMD47
#define IDS_REC48
#define IDS_CREMFDCFAIL49
#define IDS_ADDCMNTFAIL50

#define IDS_STOP55
#define IDS_FMT_PLAYMF56
#define IDS_CREPALFAIL57

#define IDS_NOMFFORPLAY61
#define IDS_ALDUSMF62
#define IDS_DRAWSFCLR63
#define IDS_PEN64
#define IDS_TXT65
#define IDS_RECT66
#define IDS_FILLRECT67
#define IDS_ELLIPSE68
#define IDS_FILLELLIPSE69
#define IDS_LN70
#define IDS_MSTLBCLKFORBZ71
#define IDS_MUST3CLKFORBM72
#define IDS_MUST3CLKFORXMF73
#define IDS_NOMFFORPRT74

#define IDS_CREPRTTHRDFAIL76
#define IDS_CREPNTFAIL77
#define IDS_CREPBYTEFAIL78
#define IDS_NOBMFOREMBED79
#define IDS_NOMFFOREMBED80
#define IDS_FT_DIB81
#define IDS_FM_DIB82
#define IDS_FT_RLE83
#define IDS_FM_RLE84
#define IDS_FT_EPS85
#define IDS_FM_EPS86
#define IDS_FT_EMF87
#define IDS_FM_EMF88
#define IDS_FT_WMF89
#define IDS_FM_WMF90

#define IDS_SAVEMFAS92
#define IDS_LDMFMTFILEFAIL93
#define IDS_CREMAPFILEFAIL94
#define IDS_MAPVWOFMAPFOFAIL95
#define IDS_NOTDIBBMFILE96
#define IDS_GETMFBTSXFAIL97
#define IDS_GETMFBTSXFAIL298
#define IDS_LDMSK99
#define IDS_MALLOCFAILNOHIT100

#define IDS_NULLRGNHTSTFAIL102
#define IDS_EMF103
#define IDS_NOHIT104
#define IDS_NOMFTOPRT105
#define IDS_ESCPRT106
#define IDS_CREPRTDCFAIL107
#define IDS_PRT108

#define IDS_PRTTHRDDONE111
#define IDS_ALLOCPALFAIL112

#define IDS_HFTONEPALNULL114
#define IDS_PALNULL115
#define IDS_MUSTMSKMCHROMEBM116
#define IDS_NOPALENTRYFORCPY117
#define IDS_CPYPALFAIL118

#define IDS_NULLPTR121
#define IDS_NOTADOBE122
#define IDS_SKPTOEOL123
#define IDS_MUSTNOTEOF124
#define IDS_GETBNDLFTFAIL125
#define IDS_GETBNDTOPFAIL126
#define IDS_GETBNDRGTFAIL127
#define IDS_GETBNDBOTFAIL128
#define IDS_SETADVGRPHMDFAIL129

#define IDS_FMT_HIT131
#define IDS_FMT_NOHTST132
#define IDS_FMT_PLAYEMFFAIL133
#define IDS_FMT_FILEOPNFAIL134
#define IDS_FMT_CREMAPFILEFAIL135
#define IDS_FMT_MAPVWMAPFOFAIL136
#define IDS_FMT_SETWMFBTSFAIL137
#define IDS_FMT_SETWMFBTSXFAIL138
#define IDS_FMT_PLAYMFFAIL139
#define IDS_FMT_GETMFFAIL140

//
// Menu ID's
//
#define MM_LOAD                 7001
#define MM_RECORD               7002
#define MM_PRINT                7003
#define MM_PAGESETUP            7004
#define MM_PRINTSETUP           7005
#define MM_ABOUT                7006
#define MM_CUT                  7007
#define MM_COPY                 7008
#define MM_PASTE                7009
#define MM_DEL                  7010
#define MM_LOAD_BMP             7011
#define MM_SAVE_BMP             7012
#define MM_LOAD_MASKBMP         7013
#define MM_REMOVE               7014
#define MM_INSERT               7015
#define MM_HITTEST              7016
#define MM_REPEAT               7017
#define MM_INCREASE             7018
#define MM_NORMAL               7019
#define MM_DECREASE             7020
#define MM_PEN                  7021
#define MM_BRUSH                7022
#define MM_FONT                 7023
#define MM_LEABOUT              7024
#define MM_FIT2WND              7025
#define MM_IMPORT_3X            7026
#define MM_EXPORT_3X            7027
#define MM_TTOUTLN_STROKEFILL   7028
#define MM_TTOUTLN_POLYDRAW     7029
#define MM_C_WND_MF             7030
#define MM_C_BEGIN_GP           7031
#define MM_C_END_GP             7032
#define MM_C_MLTFMTS            7033

#define SIZEOFCAPTIONTEXT20

#define ACCEL_ID                100
#define APP_ICON                 100

//
// Control Panel ID's
//
#define OD_BTN_CNT              5
#define DID_CTRLPANEL           100
#define DID_ZERO                1000
#define DID_ONE                 1001
#define DID_TWO                 1002
#define DID_THREE               1003
#define DID_FOUR                1004
#define DID_FIVE                1005
#define DID_SIX                 1006
#define DID_SEVEN               1007
#define DID_EIGHT               1008
#define DID_NINE                1009
#define DID_TEN_PLUS            1010
#define ID_OD_BTN_BASE          1011
#define DID_OPEN                1011
#define DID_RECORD              1012
#define DID_STOP                1013
#define DID_PLAY                1014
#define DID_FF                  1015
#define DID_CLEAR               1016
#define DID_COUNTER             1017
#define DID_TMP                 1018

//
// Control Panel's Owner draw bitmaps
//
#define BMID_BASED              1011
#define BMID_OPEND              1011
#define BMID_RECORDD            1012
#define BMID_STOPD              1013
#define BMID_PLAYD              1014
#define BMID_FFD                1015
#define BMID_BASEU              2011
#define BMID_OPENU              2011
#define BMID_RECORDU            2012
#define BMID_STOPU              2013
#define BMID_PLAYU              2014
#define BMID_FFU                2015

//
// Tools ID's
//
#define OD_TOOL_CNT             10
#define ID_OD_TOOL_BASE         3001
#define DID_PEN                 3001
#define DID_TEXT                3002
#define DID_RECT                3003
#define DID_FILLRECT            3004
#define DID_ELLIPSE             3005
#define DID_FILLELLIPSE         3006
#define DID_LINE                3007
#define DID_BEZIER              3008
#define DID_BMPOBJ              3009
#define DID_METAF               3010

//
// Tools Owner Draw bitmaps
//
#define BMID_TOOLBASED          3001
#define BMID_PEND               3001
#define BMID_TEXTD              3002
#define BMID_RECTD              3003
#define BMID_FILLRECTD          3004
#define BMID_ELLIPSED           3005
#define BMID_FILLELLIPSED       3006
#define BMID_LINED              3007
#define BMID_BEZIERD            3008
#define BMID_BMPOBJD            3009
#define BMID_METAFD             3010
#define BMID_TOOLBASEU          4001
#define BMID_PENU               4001
#define BMID_TEXTU              4002
#define BMID_RECTU              4003
#define BMID_FILLRECTU          4004
#define BMID_ELLIPSEU           4005
#define BMID_FILLELLIPSEU       4006
#define BMID_LINEU              4007
#define BMID_BEZIERU            4008
#define BMID_BMPOBJU            4009
#define BMID_METAFU             4010

//
// some handy macros
//
#define GET_WM_CTLCOLOR_HDC(wp, lp, msg)        (HDC)(wp)
#define GET_WM_CTLCOLOR_HWND(wp, lp, msg)       (HWND)(lp)
#define GET_WM_CTLCOLOR_TYPE(wp, lp, msg)       (WORD)(msg - WM_CTLCOLORMSGBOX)

#define META32_SIGNATURE        0x464D4520      // ' EMF'
#define ALDUS_ID0x9AC6CDD7

#define CARET_HEIGHT    16

typedef struct
  {
  DWORD   key;
  WORD          hmf;
  SMALL_RECT    bbox;
  WORD    inch;
  DWORD   reserved;
  WORD    checksum;
  } APMFILEHEADER;
typedef APMFILEHEADER * PAPMFILEHEADER;
#define APMSIZE 22

#ifndef RC_INVOKED
#pragma pack(2)
typedef struct tagMETA16HEADER
{
    WORDmtType;
    WORDmtHeaderSize;
    WORDmtVersion;
    DWORDmtSize;
    WORDmtNoObjects;
    DWORDmtMaxRecord;
    WORDmtNoParameters;
} META16HEADER, LPMETA16HEADER;
#pragma pack()

#endif // RC_INVOKED

typedef struct _playinfo {
    int iRecord;
    BOOL bPlayContinuous;
} PLAYINFO;

typedef struct _metalookup {
    LPSTR   szGDIName;
    int     iMRNumber;
} METALOOKUP;

typedef struct _htdata {
    POINT   point;
    int     iRecord;
} HTDATA;

METALOOKUP rgMetaName[] =
{
   { "                           ", 0                              },
   { "HEADER                     ", EMR_HEADER                     },
   { "POLYBEZIER                 ", EMR_POLYBEZIER                 },
   { "POLYGON                    ", EMR_POLYGON                    },
   { "POLYLINE                   ", EMR_POLYLINE                   },
   { "POLYBEZIERTO               ", EMR_POLYBEZIERTO               },
   { "POLYLINETO                 ", EMR_POLYLINETO                 },
   { "POLYPOLYLINE               ", EMR_POLYPOLYLINE               },
   { "POLYPOLYGON                ", EMR_POLYPOLYGON                },
   { "SETWINDOWEXTEX             ", EMR_SETWINDOWEXTEX             },
   { "SETWINDOWORGEX             ", EMR_SETWINDOWORGEX             },
   { "SETVIEWPORTEXTEX           ", EMR_SETVIEWPORTEXTEX           },
   { "SETVIEWPORTORGEX           ", EMR_SETVIEWPORTORGEX           },
   { "SETBRUSHORGEX              ", EMR_SETBRUSHORGEX              },
   { "EOF                        ", EMR_EOF                        },
   { "SETPIXELV                  ", EMR_SETPIXELV                  },
   { "SETMAPPERFLAGS             ", EMR_SETMAPPERFLAGS             },
   { "SETMAPMODE                 ", EMR_SETMAPMODE                 },
   { "SETBKMODE                  ", EMR_SETBKMODE                  },
   { "SETPOLYFILLMODE            ", EMR_SETPOLYFILLMODE            },
   { "SETROP2                    ", EMR_SETROP2                    },
   { "SETSTRETCHBLTMODE          ", EMR_SETSTRETCHBLTMODE          },
   { "SETTEXTALIGN               ", EMR_SETTEXTALIGN               },
   { "SETCOLORADJUSTMENT         ", EMR_SETCOLORADJUSTMENT         },
   { "SETTEXTCOLOR               ", EMR_SETTEXTCOLOR               },
   { "SETBKCOLOR                 ", EMR_SETBKCOLOR                 },
   { "OFFSETCLIPRGN              ", EMR_OFFSETCLIPRGN              },
   { "MOVETOEX                   ", EMR_MOVETOEX                   },
   { "SETMETARGN                 ", EMR_SETMETARGN                 },
   { "EXCLUDECLIPRECT            ", EMR_EXCLUDECLIPRECT            },
   { "INTERSECTCLIPRECT          ", EMR_INTERSECTCLIPRECT          },
   { "SCALEVIEWPORTEXTEX         ", EMR_SCALEVIEWPORTEXTEX         },
   { "SCALEWINDOWEXTEX           ", EMR_SCALEWINDOWEXTEX           },
   { "SAVEDC                     ", EMR_SAVEDC                     },
   { "RESTOREDC                  ", EMR_RESTOREDC                  },
   { "SETWORLDTRANSFORM          ", EMR_SETWORLDTRANSFORM          },
   { "MODIFYWORLDTRANSFORM       ", EMR_MODIFYWORLDTRANSFORM       },
   { "SELECTOBJECT               ", EMR_SELECTOBJECT               },
   { "CREATEPEN                  ", EMR_CREATEPEN                  },
   { "CREATEBRUSHINDIRECT        ", EMR_CREATEBRUSHINDIRECT        },
   { "DELETEOBJECT               ", EMR_DELETEOBJECT               },
   { "ANGLEARC                   ", EMR_ANGLEARC                   },
   { "ELLIPSE                    ", EMR_ELLIPSE                    },
   { "RECTANGLE                  ", EMR_RECTANGLE                  },
   { "ROUNDRECT                  ", EMR_ROUNDRECT                  },
   { "ARC                        ", EMR_ARC                        },
   { "CHORD                      ", EMR_CHORD                      },
   { "PIE                        ", EMR_PIE                        },
   { "SELECTPALETTE              ", EMR_SELECTPALETTE              },
   { "CREATEPALETTE              ", EMR_CREATEPALETTE              },
   { "SETPALETTEENTRIES          ", EMR_SETPALETTEENTRIES          },
   { "RESIZEPALETTE              ", EMR_RESIZEPALETTE              },
   { "REALIZEPALETTE             ", EMR_REALIZEPALETTE             },
   { "EXTFLOODFILL               ", EMR_EXTFLOODFILL               },
   { "LINETO                     ", EMR_LINETO                     },
   { "ARCTO                      ", EMR_ARCTO                      },
   { "POLYDRAW                   ", EMR_POLYDRAW                   },
   { "SETARCDIRECTION            ", EMR_SETARCDIRECTION            },
   { "SETMITERLIMIT              ", EMR_SETMITERLIMIT              },
   { "BEGINPATH                  ", EMR_BEGINPATH                  },
   { "ENDPATH                    ", EMR_ENDPATH                    },
   { "CLOSEFIGURE                ", EMR_CLOSEFIGURE                },
   { "FILLPATH                   ", EMR_FILLPATH                   },
   { "STROKEANDFILLPATH          ", EMR_STROKEANDFILLPATH          },
   { "STROKEPATH                 ", EMR_STROKEPATH                 },
   { "FLATTENPATH                ", EMR_FLATTENPATH                },
   { "WIDENPATH                  ", EMR_WIDENPATH                  },
   { "SELECTCLIPPATH             ", EMR_SELECTCLIPPATH             },
   { "ABORTPATH                  ", EMR_ABORTPATH                  },
   { "UNKNOWN                    ", 69                             },
   { "GDICOMMENT                 ", EMR_GDICOMMENT                 },
   { "FILLRGN                    ", EMR_FILLRGN                    },
   { "FRAMERGN                   ", EMR_FRAMERGN                   },
   { "INVERTRGN                  ", EMR_INVERTRGN                  },
   { "PAINTRGN                   ", EMR_PAINTRGN                   },
   { "EXTSELECTCLIPRGN           ", EMR_EXTSELECTCLIPRGN           },
   { "BITBLT                     ", EMR_BITBLT                     },
   { "STRETCHBLT                 ", EMR_STRETCHBLT                 },
   { "MASKBLT                    ", EMR_MASKBLT                    },
   { "PLGBLT                     ", EMR_PLGBLT                     },
   { "SETDIBITSTODEVICE          ", EMR_SETDIBITSTODEVICE          },
   { "STRETCHDIBITS              ", EMR_STRETCHDIBITS              },
   { "EXTCREATEFONTINDIRECTW     ", EMR_EXTCREATEFONTINDIRECTW     },
   { "EXTTEXTOUTA                ", EMR_EXTTEXTOUTA                },
   { "EXTTEXTOUTW                ", EMR_EXTTEXTOUTW                },
   { "POLYBEZIER16               ", EMR_POLYBEZIER16               },
   { "POLYGON16                  ", EMR_POLYGON16                  },
   { "POLYLINE16                 ", EMR_POLYLINE16                 },
   { "POLYBEZIERTO16             ", EMR_POLYBEZIERTO16             },
   { "POLYLINETO16               ", EMR_POLYLINETO16               },
   { "POLYPOLYLINE16             ", EMR_POLYPOLYLINE16             },
   { "POLYPOLYGON16              ", EMR_POLYPOLYGON16              },
   { "POLYDRAW16                 ", EMR_POLYDRAW16                 },
   { "CREATEMONOBRUSH            ", EMR_CREATEMONOBRUSH            },
   { "CREATEDIBPATTERNBRUSHPT    ", EMR_CREATEDIBPATTERNBRUSHPT    },
   { "EXTCREATEPEN               ", EMR_EXTCREATEPEN               },
   { "POLYTEXTOUTA               ", EMR_POLYTEXTOUTA               },
   { "POLYTEXTOUTW               ", EMR_POLYTEXTOUTW               },
   { "SETICMMODE                 ", EMR_SETICMMODE                 },
   { "CREATECOLORSPACE           ", EMR_CREATECOLORSPACE           },
   { "SETCOLORSPACE              ", EMR_SETCOLORSPACE              },
   { "DELETECOLORSPACE           ", EMR_DELETECOLORSPACE           },
#ifdef OPENGL_EMF
   { "GLSRECORD                  ", EMR_GLSRECORD                  },
   { "GLSBOUNDEDRECORD           ", EMR_GLSBOUNDEDRECORD           },
   { "PIXELFORMAT                ", EMR_PIXELFORMAT                },
#endif
};

typedef struct _PRTDATA {
    BOOL            bFit2Wnd;
    HENHMETAFILE    hMetaf;
} PRTDATA, *PPRTDATA;

PROC    gpfnGetEnhMetaFilePixelFormat = (PROC)NULL;

HANDLE  ghModule;
HWND    ghwndMain   = NULL;
HWND    ghwndClient = NULL;
HWND    ghwndDrawSurf = NULL;
HWND    ghwndCtrlPanel = NULL;
HWND    ghwndTools = NULL;
HWND    ghTextWnd = NULL;
HPALETTE ghPal = NULL;
HPALETTE ghHT = NULL;

LONG    glcyStatus;
BOOL    gbRecording = FALSE;
BOOL    gbHitTest = FALSE;

#if 0   // STRICT doesn't like this
HWND    grHwndCtrlBtn[OD_BTN_CNT];
HWND    grHwndToolBtn[OD_TOOL_CNT];
#endif
PVOID   grHwndCtrlBtn[OD_BTN_CNT];
PVOID   grHwndToolBtn[OD_TOOL_CNT];

DWORD   gdwCurCtrl = DID_STOP;
DWORD   gdwCurTool = DID_PEN;

HFONT    ghCurFont = (HFONT)NULL;
LOGFONT  glf;
COLORREF gCrText=0;                         // Black

HMENU   hMenu,      hMenuWindow;
HMENU   hChildMenu, hChildMenuWindow;
HDC     ghDCMem;
HDC     ghDCMetaf = (HDC) NULL;
HENHMETAFILE ghMetaf = NULL;
HMETAFILE ghmf=NULL;

HBITMAP ghBmp = NULL, ghBmpMask = NULL;

#if 0   // STRICT doesn't like this
HBITMAP ghBmpDn[OD_BTN_CNT], ghBmpUp[OD_BTN_CNT];
HBITMAP ghToolBmpDn[OD_TOOL_CNT], ghToolBmpUp[OD_TOOL_CNT];
#endif
PVOID   ghBmpDn[OD_BTN_CNT], ghBmpUp[OD_BTN_CNT];
PVOID   ghToolBmpDn[OD_TOOL_CNT], ghToolBmpUp[OD_TOOL_CNT];

HBRUSH  ghbrRed;
HBRUSH  ghbrCur = (HBRUSH)NULL;
HPEN    ghpnCur = (HPEN)NULL;
HBRUSH  ghbrAppBkgd;
HPEN    ghpnWide;

HWND    ghwndNext = NULL;
BOOL    gbFit2Wnd = TRUE;
BOOL    gbImport3X = FALSE;
BOOL    gbExport3X = FALSE;
BOOL    gbSFOutln = FALSE;
BOOL    gbPDOutln = FALSE;
BOOL    gbTT = FALSE;
BOOL    gb3D = FALSE;
BOOL    gbRCSet = FALSE;
BOOL    gbDebug = FALSE;
BOOL    gbUseMfPFD = TRUE;
BOOL    gbDB = FALSE;

BOOL    gbUseDIB = FALSE;

typedef struct _FileInfo{
    HANDLE      hFile;
    HANDLE      hMapFile;
    LPVOID      lpvMapView;
} FILEINFO, *PFILEINFO;

typedef struct _DIBDATA{
    ULONG           ulFiles;
    ULONG           ulFrames;
    FILEINFO        rgFileInfo[MAX_FILE];
    PBYTE           rgpjFrame[MAX_FRAME];
    PBITMAPINFO     rgpbmi[MAX_FILE];
    BOOL            rgbCoreHdr[MAX_FILE];
} DIBDATA, *PDIBDATA;

DIBDATA gDib;

typedef struct tagCMTMLTFMT {
    DWORD       ident;
    DWORD       iComment;
    RECTL       rclOutput;
    DWORD       nFormats;
    EMRFORMAT   aemrformat[1];
} CMTMLTFMT;

#define GDICOMMENT_IDENTIFIER   0x43494447
#define GDICOMMENT_MULTIFORMATS 0x40000004

#define BLACK               PALETTERGB(0,0,0)
#define WHITE               PALETTERGB(255,255,255)
#define NUM_STATIC_COLORS   (COLOR_BTNHIGHLIGHT - COLOR_SCROLLBAR + 1)

// Maximum color distance with 8-bit components
#define MAX_COL_DIST (3*256*256L)

// Number of static colors
#define STATIC_COLORS 20

// Flags used when matching colors
#define EXACT_MATCH 1
#define COLOR_USED 1

// TRUE if app wants to take over palette
static BOOL gbUseStaticColors = FALSE;

// TRUE if static system color settings have been replaced with B&W settings.
static BOOL gbSystemColorsInUse = FALSE;

// TRUE if static colors have been saved
static BOOL gbStaticSaved = FALSE;

// saved system static colors
static COLORREF gacrSave[NUM_STATIC_COLORS];

// new B&W system static colors
static COLORREF gacrBlackAndWhite[NUM_STATIC_COLORS] = {
    WHITE,  // COLOR_SCROLLBAR
    BLACK,  // COLOR_BACKGROUND
    BLACK,  // COLOR_ACTIVECAPTION
    WHITE,  // COLOR_INACTIVECAPTION
    WHITE,  // COLOR_MENU
    WHITE,  // COLOR_WINDOW
    BLACK,  // COLOR_WINDOWFRAME
    BLACK,  // COLOR_MENUTEXT
    BLACK,  // COLOR_WINDOWTEXT
    WHITE,  // COLOR_CAPTIONTEXT
    WHITE,  // COLOR_ACTIVEBORDER
    WHITE,  // COLOR_INACTIVEBORDER
    WHITE,  // COLOR_APPWORKSPACE
    BLACK,  // COLOR_HIGHLIGHT
    WHITE,  // COLOR_HIGHLIGHTTEXT
    WHITE,  // COLOR_BTNFACE
    BLACK,  // COLOR_BTNSHADOW
    BLACK,  // COLOR_GRAYTEXT
    BLACK,  // COLOR_BTNTEXT
    BLACK,  // COLOR_INACTIVECAPTIONTEXT
    BLACK   // COLOR_BTNHIGHLIGHT
    };
static INT gaiStaticIndex[NUM_STATIC_COLORS] = {
    COLOR_SCROLLBAR          ,
    COLOR_BACKGROUND         ,
    COLOR_ACTIVECAPTION      ,
    COLOR_INACTIVECAPTION    ,
    COLOR_MENU               ,
    COLOR_WINDOW             ,
    COLOR_WINDOWFRAME        ,
    COLOR_MENUTEXT           ,
    COLOR_WINDOWTEXT         ,
    COLOR_CAPTIONTEXT        ,
    COLOR_ACTIVEBORDER       ,
    COLOR_INACTIVEBORDER     ,
    COLOR_APPWORKSPACE       ,
    COLOR_HIGHLIGHT          ,
    COLOR_HIGHLIGHTTEXT      ,
    COLOR_BTNFACE            ,
    COLOR_BTNSHADOW          ,
    COLOR_GRAYTEXT           ,
    COLOR_BTNTEXT            ,
    COLOR_INACTIVECAPTIONTEXT,
    COLOR_BTNHIGHLIGHT
    };

static VOID SaveStaticEntries(HDC);
static VOID UseStaticEntries(HDC);

//
// Forward declarations.
//
BOOL InitializeApp   (void);
LONG APIENTRY MainWndProc     (HWND, UINT, DWORD, LONG);
LONG APIENTRY DrawSurfWndProc (HWND, UINT, DWORD, LONG);
BOOL CALLBACK About           (HWND, UINT, DWORD, LONG);
LONG APIENTRY TextWndProc     (HWND, UINT, DWORD, LONG);
LONG APIENTRY CtrlPanelDlgProc(HWND, UINT, DWORD, LONG);
BOOL bDrawStuff      (HDC, INT, INT, INT, INT, BOOL, BOOL, BOOL, LPSTR);
HENHMETAFILE hemfLoadMetafile(HWND);
HDC  hDCRecordMetafileAs(HWND, LPSTR);
BOOL APIENTRY bPlayRecord(HDC, LPHANDLETABLE, LPENHMETARECORD, UINT, LPVOID);
BOOL APIENTRY bDoHitTest(HDC, LPHANDLETABLE, LPENHMETARECORD, UINT, LPVOID);
BOOL bHitTest(HDC, INT, INT);
HBITMAP hBmpLoadBitmapFile(HDC, PSTR);
BOOL bGetBMP(HWND, BOOL);
BOOL bChooseNewFont(HWND, PLOGFONT, COLORREF * );
BOOL bChooseNewColor(HWND, LPDWORD);
BOOL bPrintMf(PPRTDATA);
HBRUSH hBrCreateBrush(HDC, DWORD);
BOOL bSelectDIBPal(HDC, LPBITMAPINFO, BOOL);
BOOL bFreeDibFile(PDIBDATA);
BOOL bPlgBlt(HDC, LPPOINT);
HPALETTE CopyPalette(HPALETTE hPalSrc);
int CALLBACK iTT(LPLOGFONT, LPTEXTMETRIC, DWORD, LPARAM);
CMTMLTFMT *pLoadMltFmtFile(VOID);
HLOCAL Free(CMTMLTFMT *pMfmt);
BOOL bGetEPSBounds(LPVOID, RECTL *);
BOOL bIsAdobe(char *szStr);
BOOL bIsEPS(char *szStr);
BOOL bIsBndBox(char *szStr);
BOOL bIsEOF(char *szStr);
//BOOL bGetWord(LPVOID, char *, char **);
//BOOL bGoNextLine(LPVOID, char **);
BOOL bGetWord(LPVOID, char *, int*);
BOOL bGoNextLine(LPVOID, int*);
BOOL bSetAdvancedGraphics(HDC hdc);

extern unsigned char __stdcall ComponentFromIndex(INT, INT, INT);
extern int aiDefaultOverride[20];
extern PALETTEENTRY apeDefaultPalEntry[20];

BOOL bCleanUpRC(VOID);
BOOL bSetupRC(HDC hDC, PIXELFORMATDESCRIPTOR *ppfdIn);

//
// function prototype for looking up string resources
//

LPTSTR GetStringRes (int);
LPTSTR GetStringRes2 (int);
LPTSTR BuildFilterStrs(int);


PRECOMP.H

/******************************Module*Header*******************************\
* Module Name: precomp.h
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/
#include <stdlib.h>
#include <windows.h>
#include <string.h>
#include <stdarg.h>
#include <string.h>

#ifdef JAPAN
#define _MBCS
#include <mbstring.h>
#include <mbctype.h>
//
// Maybe this needs to be the last include
//
#include <ime.h>
#define My_mbstok _mbstok// This may actually work for English as well
#else
#define My_mbstok strtok// but just to be sure...
#endif

#include <stdio.h>
#include <commdlg.h>
#include <shellapi.h>
#include <math.h>
#include <glaux.h>
#include <gl.h>
#include <glu.h>
#include "mfedit.h"


README.TXT

Palette Management Example

The MyPal sample is a simple demonstration of the Palette Management
functions. It presents the current color palette provided by the display
driver in the client area of the application.


MYPAL.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/***************************************************************************
 *                                                                         *
 *  PROGRAM     : MyPal.c                                                  *
 *                                                                         *
 *  PURPOSE     : Sets up a bar representation of the current physical     *
 *                palette and displays useful information regarding        *
 *                pixel colors and palette indices.                        *
 *                                                                         *
 *  FUNCTIONS   : WinMain() - calls initialization function,               *
 *                            processes message loop                       *
 *                                                                         *
 *                WndProc() - Window function for app. Processes           *
 *                            window messages.                             *
 *                                                                         *
 *              ShowColor() - Displays a little box on each side of the    *
 *                            caption bar displaying the pixel color at the*
 *                            mouse position.                              *
 ***************************************************************************/

#include <windows.h>
#include "mypal.h"

HANDLE          hPal;          /* Handle to the application's logical palette */
static INT      nSizeX;        /* Width of the application window             */
static INT      nSizeY;        /* Height of the application window            */
NPLOGPALETTE    pLogPal;       /* Pointer to program's logical palette        */
INT             nXBorder;      /* Width of window border                      */
INT             nXTitle;       /* Width of title bar                          */
INT             nYTitle;       /* Height of title bar                         */
BOOL            bCaptureOn;    /* Indicates if mouse capture is on            */
INT             iIndex;        /* Last index selected in palette              */
CHAR            szTitlebuf[90];/* Buffer for pixel and palette info. text     */
HDC             hDCGlobal;     /* The Screen DC                               */
INT             iNumColors;    /* Number of colors supported by device        */
INT             iRasterCaps;   /* Raster capabilities                         */
RECT            rClientRect;   /* Client rectangle coordinates                */
DWORD    dwPal[PALETTESIZE];   /* Stores palette entries for later lookup     */
INT             iGlobalXOffset;
INT             iGlobalYOffset;
INT             iYMiddle;

LONG APIENTRY WndProc(HWND hWnd, UINT iMessage, UINT wParam, LONG lParam);

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : void ShowColor(HWND hWnd, HDC hDC)                         *
 *                                                                          *
 *  PURPOSE    : Displays a little box on each side of the caption bar      *
 *               displaying the pixel color at the mouse position.          *
 *                                                                          *
 ****************************************************************************/
VOID ShowColor (
        HWND  hWnd,
        HDC   hDC)
{
     HBRUSH  hBrush, hOldBrush;

     hBrush    = CreateSolidBrush ( PALETTEINDEX(iIndex) );
     hOldBrush = SelectObject (hDC,hBrush) ;

     GetWindowRect (hWnd, (LPRECT)&rClientRect);

     PatBlt ( hDC,
              rClientRect.left + nXTitle + nXBorder + 1,
              rClientRect.top + nXBorder,
              nXTitle,
              nYTitle,
              PATCOPY);

     PatBlt(hDC,
            rClientRect.right - ( 3 * nXTitle + nXBorder + 2),
            rClientRect.top + nXBorder,
            nXTitle,
            nYTitle,
            PATCOPY);
     SelectObject (hDC, hOldBrush);
     DeleteObject (hBrush) ;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WinMain(HANDLE, HANDLE, LPSTR, int)                        *
 *                                                                          *
 *  PURPOSE    : Creates the app. window and processes the message loop.    *
 *                                                                          *
 ****************************************************************************/
int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
    )
{
     static CHAR szAppName [] = "MyPal";
     HWND        hWnd;
     WNDCLASS    wndclass;
     MSG         msg ;
     INT         xScreen;
     INT         yScreen;

     UNREFERENCED_PARAMETER( lpCmdLine );

     if (!hPrevInstance){
         wndclass.style         = CS_HREDRAW | CS_VREDRAW;
         wndclass.lpfnWndProc   = (WNDPROC) WndProc;
         wndclass.cbClsExtra    = 0;
         wndclass.cbWndExtra    = 0;
         wndclass.hInstance     = hInstance;
         wndclass.hIcon         = LoadIcon(hInstance, szAppName);
         wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
         wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
         wndclass.lpszMenuName  = szAppName;
         wndclass.lpszClassName = szAppName;

         if (!RegisterClass (&wndclass))
             return FALSE ;
     }

     /* Do some global initializations */
     xScreen     = GetSystemMetrics (SM_CXSCREEN);
     yScreen     = GetSystemMetrics (SM_CYSCREEN);
     nXBorder    = (INT)GetSystemMetrics (SM_CXFRAME);
     nXTitle     = (INT)GetSystemMetrics (SM_CXSIZE);
     nYTitle     = (INT)GetSystemMetrics (SM_CYSIZE);
     iIndex      = 0;
     bCaptureOn  = FALSE;

     hDCGlobal   = GetDC (NULL);
     iRasterCaps = GetDeviceCaps(hDCGlobal, RASTERCAPS);
     iRasterCaps = (iRasterCaps & RC_PALETTE) ? TRUE : FALSE;

     if (iRasterCaps)
         iNumColors = GetDeviceCaps(hDCGlobal, SIZEPALETTE);
     else
         iNumColors = GetDeviceCaps( hDCGlobal, NUMCOLORS);
     ReleaseDC (NULL,hDCGlobal);

     nSizeX = ((xScreen - 2*nXBorder) / PALETTESIZE) * PALETTESIZE;

     /* create the app. window */
     hWnd = CreateWindow (szAppName,
                          "My Physical Palette ",
                          WS_OVERLAPPEDWINDOW,
                          (xScreen-nSizeX) / 2 - nXBorder,
                          yScreen - ( 4 * GetSystemMetrics (SM_CYCAPTION)),
                          nSizeX + 2 * nXBorder,
                          4 * GetSystemMetrics (SM_CYCAPTION),
                          NULL,
                          NULL,
                          hInstance,
                          NULL);
     ShowWindow (hWnd, nCmdShow);
     UpdateWindow (hWnd);

     while (GetMessage (&msg, NULL, 0, 0)){
           TranslateMessage (&msg) ;
           DispatchMessage (&msg) ;
     }

     return msg.wParam ;
}

/******************************************************************************
 *                                                                            *
 *  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)                             *
 *                                                                            *
 *  PURPOSE:  Processes window messages and sets up a 256 bar representation  *
 *            of the current physical palette. Specifically, in response to:  *
 *                                                                            *
 *               WM_CREATE  -Allocates for and sets up a LOGPALETTE           *
 *                           structure, creates a logical palette the same    *
 *                           size as the physical palette and obtains a       *
 *                           handle to the logical palette.                   *
 *                                                                            *
 *               WM_DESTROY -Destroys the logical palette and shuts down app. *
 *                                                                            *
 *               WM_PAINT   -Resizes client area to hold as many vertical     *
 *                           color bars as there are physical palette entries.*
 *                           Also realises the current logical palette and    *
 *                           draws one color bar corresponding to each        *
 *                           palette entry                                    *
 *                                                                            *
 *           WM_RBUTTONDOWN -Captures the mouse and initiates the below       *
 *                           process:                                         *
 *                                                                            *
 *           WM_MOUSEMOVE   -Following a WM_RBUTTONDOWN, if the right mouse   *
 *                           key is depressed, displays info about the        *
 *                           pixel RGB value and palette index of the mouse   *
 *                           coordinates.                                     *
 *                                                                            *
 *           WM_RBUTTONUP   -Release mouse capture and terminates the above   *
 *                           process                                          *
 *                                                                            *
 *           WM_LBUTTONDOWN -Determines and displays the palette index and    *
 *                           RGB value of the bar under the mouse.            *
 *                                                                            *
 *           WM_KEYDOWN     -Allows use of the arrow keys in stepping thro'   *
 *                           palette entries.                                 *
 *                                                                            *
 *****************************************************************************/
LONG APIENTRY WndProc (
    HWND         hWnd,
    UINT     iMessage,
    UINT         wParam,
    LONG         lParam)
{
    HDC           hDC;
    PAINTSTRUCT   ps;
    INT           iLoop;
    INT           nStart;
    HBRUSH        hBrush;
    HBRUSH        hOldBrush;

    MPOINT        pt;
    static INT    nIncr;
    static DWORD  dwColor;
    static DWORD  dwLastColor;
    static INT    i, x;

    switch (iMessage) {
         case WM_DESTROY:
              /* delete the handle to the logical palette if it has any
               * color entries and quit.
               */
              if (pLogPal->palNumEntries)
                  DeleteObject (hPal);
              PostQuitMessage (0) ;
              break ;

         case WM_CREATE:
              /* Allocate enough memory for a logical palette with
               * PALETTESIZE entries and set the size and version fields
               * of the logical palette structure.
               */
              pLogPal = (NPLOGPALETTE) LocalAlloc (LMEM_FIXED,
                                                  (sizeof (LOGPALETTE) +
                                                  (sizeof (PALETTEENTRY) * (PALETTESIZE))));

          if(!pLogPal){
            MessageBox(hWnd, "<WM_CREATE> Not enough memory for palette.", NULL, MB_OK | MB_ICONHAND);
                PostQuitMessage (0) ;
            break;
          }

              pLogPal->palVersion    = 0x300;
              pLogPal->palNumEntries = PALETTESIZE;

              /* fill in intensities for all palette entry colors */
              for (iLoop = 0; iLoop < PALETTESIZE; iLoop++) {
                  *((WORD *) (&pLogPal->palPalEntry[iLoop].peRed)) = (WORD)iLoop;
                  pLogPal->palPalEntry[iLoop].peBlue  = 0;
                  pLogPal->palPalEntry[iLoop].peFlags = PC_EXPLICIT;
              }

              /*  create a logical color palette according the information
               *  in the LOGPALETTE structure.
               */
              hPal = CreatePalette ((LPLOGPALETTE) pLogPal) ;
              break;

         case WM_GETMINMAXINFO:

              ((LPRGPT)lParam)->iInfo[6] = nXBorder * 2 + PALETTESIZE;
              ((LPRGPT)lParam)->iInfo[7] = nXBorder * 2 + nYTitle*3;

              return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
              break;

         case WM_PAINT:

              /* Divide client width into equal-sized parts, one per palette
               * entry, and re-calculate client width so that it will display
               * exactly as many vertical bars as there are palette entries.
               */
               GetClientRect(hWnd,(LPRECT) &rClientRect);
               nSizeX = (rClientRect.right - rClientRect.left);
               nSizeX = (nSizeX/iNumColors) * iNumColors;

               nSizeY = rClientRect.bottom - rClientRect.top;
               GetWindowRect(hWnd,(LPRECT) &rClientRect);

              /* Adjust window width so that it can display exactly
               * as many vertical bars( of equal width) as there are palette
               * colors.
               */

              SetWindowPos( hWnd,
                            (HWND)NULL,
                            0,
                            0,
                            nSizeX + 2*nXBorder,
                            rClientRect.bottom - rClientRect.top,
                            SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

              hDC = BeginPaint(hWnd, &ps);

              /* Select the palette into the window device context and
               * make the Palette Manager map the logical palette to the
               * system palette (realize it).
               */
              SelectPalette (hDC, hPal, 1);
              RealizePalette (hDC);

              /* Calculate width of each color bar to be displayed */
              nIncr = nSizeX / iNumColors;

              /* Paint the individual bars separately on the app. window */
              for (nStart = iLoop = 0; iLoop < iNumColors; iLoop++){

                  /* Since this app. uses a logical palette, use the
                   * PALETTEINDEX macro to specify the palette entry
                   * index instead of using an explicit RGB value.
                   */
                  hBrush       = CreateSolidBrush (PALETTEINDEX (iLoop));
                  dwPal[iLoop] = GetNearestColor (hDC, PALETTEINDEX (iLoop) );
                  hOldBrush    = SelectObject (hDC,hBrush) ;
                  PatBlt (hDC, nStart, 0, nIncr, nSizeY, PATCOPY);
                  nStart       += nIncr;
                  SelectObject (hDC, hOldBrush);
                  DeleteObject (hBrush) ;
              }
              wsprintf (szTitlebuf, "MyPal Colors= %d", iNumColors);
              SetWindowText (hWnd, (LPSTR)szTitlebuf);

              EndPaint(hWnd,&ps);

              break ;

         case WM_MOUSEMOVE:

              if (wParam & MK_RBUTTON) {

          POINT pt;

#ifdef WIN16
                  /* Convert mouse position to screen coordinates */
                  pt.x = LOWORD(lParam);
                  pt.y = HIWORD(lParam);
#else
                  LONG2POINT(lParam, pt);
#endif
                  ClientToScreen(hWnd, &pt);

                  /* Get RGB value (color) of pixel under mouse coordinate */
                  dwColor = GetPixel(hDCGlobal, pt.x, pt.y);

                  /* If color value already exists in palette lookup table,
                   * obtain it's index.
                   */
                  for (i=0 ; i < iNumColors ; i++)
                      if ( dwColor == dwPal[i] )
                          break;
                  iIndex = i;

                  /* If previous color value was not identical to current one,
                   * display color boxes on either side of title bar,
                   * the R, G, B values and palette index of current color.
                   */
                  if (dwColor != dwLastColor) {
                      wsprintf ( szTitlebuf,
                                 "MyPal Colors=%d  Index=%d  R=%3u G=%3u B=%3u",
                                 iNumColors,
                                 iIndex,
                                 (WORD)(BYTE) GetRValue (dwColor),
                                 (WORD)(BYTE) GetGValue (dwColor),
                                 (WORD)(BYTE) GetBValue (dwColor));
                      SetWindowText (hWnd, (LPSTR)szTitlebuf);
                      ShowColor (hWnd, hDCGlobal);
                      dwLastColor = dwColor;
                  }
              }
              break;

         case WM_RBUTTONDOWN:

              /* Determine number of color bar under mouse, thus the index
               * of color in palette.
               */
              x = LOWORD(lParam);
              iIndex = (x / nIncr );

              wsprintf ( szTitlebuf,
                         "MyPal Colors=%d  Index=%d  PalSize=%d RasterCaps:%d",
                         iNumColors,
                         iIndex,
                         iNumColors,
                         iRasterCaps );

              SetWindowText (hWnd, (LPSTR)szTitlebuf);

              /* Set mouse capture so that subsequent WM_MOUSEMOVEs
               * (with right mouse button depressed) will allow MyPal
               * to display RGB info anywhere on the screen without losing
               * the focus.
               */
              SetCapture (hWnd);
              bCaptureOn = TRUE;
              hDCGlobal = GetDC(NULL);
              if (hPal) {
                  SelectPalette (hDCGlobal, hPal, FALSE);
                  RealizePalette (hDCGlobal);
              }
              break;

         case WM_RBUTTONUP:
              /* Stops displaying RGB and palette info and releases mouse
               * capture
               */
              ReleaseDC (NULL, hDCGlobal);
              bCaptureOn = FALSE;
              ReleaseCapture ();
              break;

         case WM_MOVE:
              /* If you have a wide column, this adds 1/2 so X is centered */
              iGlobalXOffset  = LOWORD (lParam);
              iGlobalYOffset  = HIWORD (lParam) + nXBorder;
              break;

         case WM_SIZE:
              iYMiddle = (HIWORD (lParam)/2);
              break;

         case WM_LBUTTONDOWN:
         case WM_KEYDOWN:

             if (iMessage == WM_LBUTTONDOWN){
                 /* determine which column was hit by the mouse */
                 x = LOWORD(lParam);
                 iIndex = (x / nIncr );
             }
             else{
                 /* Use arrow keys to step thro' the palette entries */
                 switch (wParam) {
                     case VK_RIGHT:
                     case VK_UP:
                                  /* go to next (higher) palette entry */
                                  iIndex++;
                                  break;
                     case VK_LEFT:
                     case VK_DOWN:
                                  /* go to previous (lower) palette entry */
                                  iIndex--;
                                  break;
                     case VK_NEXT:
                                  iIndex += 10;
                                  break;
                     case VK_PRIOR:
                                  iIndex -= 10;
                                  break;
                     case VK_HOME:
                                  /* go to first palette entry */
                                  iIndex = 0;
                                  break;
                     case VK_END:
                                  /* go to last palette entry */
                                  iIndex = iNumColors-1;
                                  break;
                     default:
                                  return 0L;
                                  break;
                 }
                 /* Make sure the palette index is within range else
                  * set it to the limiting values and give a warning beep.
                  */
                 if (iIndex < 0) {
                     iIndex = 0;
                     MessageBeep(1);
                 }
                 else{
                     if (iIndex > iNumColors-1) {
                         iIndex = iNumColors-1;
                         MessageBeep(1);
                      }
                 }

                 pt.x = (SHORT)((iIndex * nIncr) +
                        iGlobalXOffset   +
                        ((nIncr > 1) ? (nIncr / 2) : 1));
                 pt.y = (SHORT)(iYMiddle + iGlobalYOffset);

                 SetCursorPos (pt.x, pt.y);
             }

             if (TRUE == bCaptureOn) {
                 MessageBeep(1);
                 break;
             }

             /* Select & realize the palette or the colors > 0x7
              * will not match up.
              */
             hDC = GetDC(NULL);
             SelectPalette  (hDC, hPal, 1);
             RealizePalette (hDC) ;

             dwColor = GetNearestColor (hDC, PALETTEINDEX (iIndex));

             wsprintf ( szTitlebuf,
                        "MyPal Colors=%d  Index=%d  R=%3u G=%3u B=%3u",
                        iNumColors,
                        iIndex,
                        (WORD)(BYTE)GetRValue (dwColor),
                        (WORD)(BYTE)GetGValue (dwColor),
                        (WORD)(BYTE)GetBValue (dwColor)
                     );

             SetWindowText (hWnd, (LPSTR)szTitlebuf);
             ShowColor (hWnd,hDC);
             ReleaseDC(NULL, hDC);
             break;

         default:
              return DefWindowProc (hWnd, iMessage, wParam, lParam) ;

    }
    return 0L ;
}


MYPAL.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

// Porting macros
#ifdef WIN32
typedef POINTS  MPOINT;
#define LONG2POINT(l, pt)  ((pt).x = (SHORT)LOWORD(l), (pt).y = (SHORT)HIWORD(l))
#else
typedef POINT   MPOINT;
#define LONG2POINT(l, pt)  ((pt).x = (INT)LOWORD(l), (pt).y = (INT)HIWORD(l))
#endif

#define PALETTESIZE     256    /* Number of entries in the system palette     */

typedef struct tagRGPT {       /* Values for setting Min Max info             */
    INT iInfo[10];
} RGPT;
typedef RGPT FAR *LPRGPT;

extern VOID ShowColor(HWND, HDC);


README.TXT

Printing

SUMMARY
=======

The PRINTER sample does the following:

  - Shows how to print using both the CreateDC and the PrinterDlg methods
    for acquiring a printer HDC. The user is allowed to print different
    graphical objects, as well as a complete device font set. An "Abort"
    dialog box is also implemented.
  - Provides complete device capabilities for all printers and the display.
  - Provides information (levels 1 and 2) returned by a call to
    EnumPrinters.
  - Shows how to enumerate fonts for a particular DC.
  - Illustrates differences between the various mapping modes.
  - Demonstrates GDI features.

MORE INFORMATION
================

The main application window contains a menu and a toolbar. The submenus are:

Print

Calls CreateDC to get a device context for the selected printer in the
toolbar combo box, and then prints the current graphics options to this DC.

PrintDlg

Calls PrintDlg to retrieve a device context for a printer, then prints out
current graphics options to this DC.

GetDeviceCaps

Retrieves device capabilities for devices currently selected in the toolbar
combo box, and displays them in a dialog box.

EnumPrinters

Retrieves level 1 and 2 information returned by EnumPrinters and displays
this information in a dialog box.

GetPrinterDriver

Returns level 1 and 2 information returned by GetPrinterDriver (for
currently selected printer) and displays this information in a dialog box.

EnumPrinterDrivers

Returns level 1 and 2 information returned by EnumPrinterDrivers and
displays this information in a dialog box.

Refresh

Refreshes the contents for the toolbar combo box.

About

Application information dialog

Mapping Modes

User selects between different mapping modes.

Graphics

User selects different primitives to display.

Pen

User can configure size, color, and style of drawing pen.

Brush

User can configure size, color, and style of drawing brush.

Text color

User can configure color used to draw fonts.


ENUMPRT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  PROGRAM:     ENUMPRT.C
*
*  PURPOSE:     Handles display of information returned by calls to
*               EnumPrinters, EnumPrinterDrivers. Info formatted and
*               displayed in a dialog box.
*
*
*  FUNTIONS:    EnumPrintersDlgProc      - handles messages for dialog
*               DisplayEnumPrintersInfo  - retrieves printer info
*               SetEnumPrintersDlgFields - formats & displays printer info
*               ComplexEnumPrintersLine  - formats bitfield printer info
*               EnumPrinterDriversDlgProc- handles messages for dialog
*               DisplayPrinterDriversInfo- retrieves, formats, & displays
*                                            printer info
*
\******************************************************************************/

#include <windows.h>
#include <string.h>
#include <drivinit.h>
#include <stdio.h>
#include <winspool.h>
#include "common.h"
#include "enumprt.h"
#include "resource.h"

/******************************************************************************\
*
*  FUNCTION:    EnumPrintersDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Processes messages for EnumPrinters dialog box
*
\******************************************************************************/

LRESULT CALLBACK EnumPrintersDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                                      LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      BOOL bReturn;

      //
      // prompt user for EnumPrinters flags...
      //

      if (DialogBox (GetModuleHandle (NULL), (LPCTSTR) "EnumPrtOpt",
                     NULL, (DLGPROC) EnumPrintersOptionsDlgProc))
      {
        //
        // shove all the enum printer info in the list box
        //

        SetCursor (LoadCursor (NULL, IDC_WAIT));
        bReturn = DisplayEnumPrintersInfo (hwnd);
        SetCursor (LoadCursor (NULL, IDC_ARROW));

        if (!bReturn)

          EndDialog (hwnd, TRUE);

        else

          SetWindowText (hwnd, (LPCTSTR)"EnumPrinters");
      }
      else

          EndDialog (hwnd, TRUE);

      break;
    }

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_OK:

          EndDialog (hwnd, TRUE);
          break;
      }
      break;
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    EnumPrintersOptionsDlgProc (standard dlg proc INPUTS/RETURNS)
*
*  COMMENTS:    Processes messages for EnumPrtOpt dialog box
*
\******************************************************************************/

LRESULT CALLBACK EnumPrintersOptionsDlgProc (HWND   hwnd,   UINT msg,
                                             WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:

      gdwEnumFlags = 0;
      gszEnumName[0] = 0;
      break;

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_OK:

          if (gdwEnumFlags)
          {
            if (gdwEnumFlags & PRINTER_ENUM_NAME)
            {
              GetDlgItemText (hwnd, DID_EDITTEXT, (LPTSTR)gszEnumName,
                              BUFSIZE);

#ifdef FORCE_VALID_NAME
              if (!strlen (gszEnumName))
              {
                MessageBox (hwnd,
                            (LPCTSTR) GetStringRes(IDS_ASKDOMSRVNM),
                            (LPCTSTR) "", MB_OK);
                SetFocus (GetDlgItem (hwnd, DID_EDITTEXT));
                break;
              }
#endif

            }

            EndDialog (hwnd, TRUE);
          }

          else

            EndDialog (hwnd, FALSE);

          break;

        case DID_CANCEL:

          EndDialog (hwnd, FALSE);
          break;

        default:

          if (HIWORD(wParam) == BN_CLICKED)
          {
            DWORD dwControlId = (DWORD) LOWORD (wParam);

            if (gdwEnumFlags & dwControlId)
            {
              //
              // remove that flag, if PRINTER_ENUM_NAME disable edittext
              //

              gdwEnumFlags &= ~dwControlId;

              if (dwControlId & PRINTER_ENUM_NAME)
              {
                SetDlgItemText (hwnd, DID_EDITTEXT, (LPCTSTR)"");
                EnableWindow   (GetDlgItem (hwnd, DID_EDITTEXT), FALSE);
              }
            }

            else
            {
              //
              // add that flag, if PRINTER_ENUM_NAME enable edittext
              //

              gdwEnumFlags |= dwControlId;

              if (dwControlId & PRINTER_ENUM_NAME)

                EnableWindow (GetDlgItem (hwnd, DID_EDITTEXT), TRUE);
            }
          }
          break;
      }
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    DisplayEnumPrintersInfo
*
*  INPUTS:      hwnd - handle of the EnumPrinters dialog box
*
*  RETURNS:     TRUE if successful,
*               FALSE otherwise
*
\******************************************************************************/

BOOL DisplayEnumPrintersInfo (HWND hwnd)
{
    DWORD  dwBytesNeeded;
    DWORD  dwPrtRet1, dwPrtRet2;
    DWORD  dwMaxPrt;
    LPTSTR lpName = gdwEnumFlags & PRINTER_ENUM_NAME ? gszEnumName : NULL;

    LPPRINTER_INFO_1 pPrtInfo1 = NULL;
    LPPRINTER_INFO_2 pPrtInfo2 = NULL;

    BOOL   bReturn = TRUE;

    //
    // get byte count needed for buffer, alloc buffer, the enum the printers
    //
    EnumPrinters (gdwEnumFlags,   // types of printer objects to enumerate
                  lpName,         // name of printer object
                  1,              // specifies type of printer info structure
                  NULL,           // use NULL to get buffer size
                  0,              // size, in bytes, of array
                  &dwBytesNeeded, // pointer to variable with no. of bytes
                                  // copied (or required)
                  &dwPrtRet1);    // pointer to variable with no. of printer
                                  // info. structures copied
    //
    // (simple error checking, if these work assume rest will too)
    //

    // Allocate enough room for the returned data
    if (!(pPrtInfo1 = (LPPRINTER_INFO_1) LocalAlloc (LPTR, dwBytesNeeded)))
    {
        ErrMsgBox (GetStringRes(IDS_ENUMPRTLALLOCFAIL), GetStringRes2(ERR_MOD_NAME));

        return FALSE;  // Bail
    }

    if (!EnumPrinters(gdwEnumFlags,      // types of printer objects to enumerate
                      lpName,            // name of printer object
                      1,                 // specifies type of printer info structure
                      (LPBYTE)pPrtInfo1, // pointer to buffer to receive printer info
                                         // structures
                      dwBytesNeeded,     // size, in bytes, of array
                      &dwBytesNeeded,    // pointer to variable with no. of bytes
                                         // copied (or required)
                      &dwPrtRet1))       // pointer to variable with no. of printer
                                         // info. structures copied
    {
        TCHAR  tcBuffer[256];

        // Create and display our error message
        wsprintf (tcBuffer, "%s, 1, GetLastError: %d", GetStringRes2(ERR_MOD_NAME), GetLastError());
        ErrMsgBox (GetStringRes(IDS_ENUMPRT1FAIL), tcBuffer);

        // Free the buffer we allocated
        LocalFree(pPrtInfo1);

        return FALSE;  // Bail
    }

    //
    // If we don't get any printers from the Level == 1 call, there is
    //  no point in continuing... report it, free memory, and return.
    //
    if (dwPrtRet1 == 0) {

        MessageBox (ghwndMain,
            (LPCTSTR) "EnumPrinters (Level == 1) returned 0 printers",
            GetStringRes2(ERR_MOD_NAME),
            MB_OK);

        // Free the buffer we allocated
        LocalFree(pPrtInfo1);

        return FALSE;  // Bail
    }

    //
    // Call EnumPrinters again, this time with Level == 2.
    //
    // get byte count needed for buffer, alloc buffer, the enum the printers
    //
    EnumPrinters (gdwEnumFlags,     // types of printer objects to enumerate
                  lpName,           // name of printer object
                  2,                // specifies type of printer info structure
                  NULL,             // use NULL to get buffer size
                  0,                // size, in bytes, of array
                  &dwBytesNeeded,   // pointer to variable with no. of bytes
                                    // copied (or required)
                  &dwPrtRet2);      // pointer to variable with no. of printer
                                    // info. structures copied

    // Allocate enough room for the returned data
    if (!(pPrtInfo2 = (LPPRINTER_INFO_2) LocalAlloc (LPTR, dwBytesNeeded))){
        ErrMsgBox (GetStringRes(IDS_ENUMPRTLALLOCFAIL), GetStringRes2(ERR_MOD_NAME));
    } else {

if (!EnumPrinters (gdwEnumFlags,      // types of printer objects to enumerate
   lpName,            // name of printer object
   2,                 // specifies type of printer info structure
   (LPBYTE)pPrtInfo2, // pointer to buffer to receive printer info
  // structures
   dwBytesNeeded,     // size, in bytes, of array
   &dwBytesNeeded,    // pointer to variable with no. of bytes
  // copied (or required)
   &dwPrtRet2))       // pointer to variable with no. of printer
  // info. structures copied
    dwPrtRet2 = 0;
    }

    //
    //  Calling EnumPrinters with Level == 2 frequently returns 0 printers.
    //  If so display only the PRINTER_INFO_1 structures we got before.
    //

    if (dwPrtRet2 == 0)
    {
        dwMaxPrt = dwPrtRet1;
        LocalFree (pPrtInfo2);
        pPrtInfo2 = NULL;
    } else {
        dwMaxPrt = dwPrtRet1 > dwPrtRet2 ? dwPrtRet2 : dwPrtRet1;
    }

    SetEnumPrintersDlgFields (hwnd, dwMaxPrt, pPrtInfo1, pPrtInfo2);

    if (pPrtInfo2)
        LocalFree (pPrtInfo2);

if (pPrtInfo1)
        LocalFree (pPrtInfo1);

    return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    SetEnumPrintersDlgFields
*
*  INPUTS:      hwnd      - handle of the EnumPrinters dialog box
*               dwMaxPrt  - number of elements in the following two arrays
*               pdisplay_prts_info1 - ptr to an array of PRINTER_INFO_1 structs
*               pdisplay_prts_info2 - ptr to an array of PRINTER_INFO_2 structs
*
*  COMMENTS:    This function formats the info returned by EnumPrinters()
*               into readable strings and inserts them in the listbox.
*
\******************************************************************************/

void SetEnumPrintersDlgFields (HWND hwnd, DWORD dwMaxPrt,
                               LPPRINTER_INFO_1 pPrtInfo1,
                               LPPRINTER_INFO_2 pPrtInfo2)
{
char  buf[256];
WORD  i;
DWORD j;

SendDlgItemMessage (hwnd, DID_LISTBOX, LB_RESETCONTENT, 0, 0);
SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), (LPARAM)0);

for (j = 0; j < dwMaxPrt; j++) {
//
// Stick PRINTER_INFO_1  data in listbox
//

SendDlgItemMessage (hwnd, DID_LISTBOX, LB_INSERTSTRING, (UINT)-1,
(LONG) gaEnumPrt[0]);

outstr (gaEnumPrt[1], (pPrtInfo1 + j)->pDescription);
outstr (gaEnumPrt[2], (pPrtInfo1 + j)->pName);
outstr (gaEnumPrt[3], (pPrtInfo1 + j)->pComment);

//
// Stick PRINTER_INFO_2  data in listbox
//

if (pPrtInfo2 != NULL) {

SendDlgItemMessage (hwnd, DID_LISTBOX, LB_INSERTSTRING, (UINT)-1,
(LONG) gaEnumPrt[4]);

outstr (gaEnumPrt[5],  (pPrtInfo2 + j)->pServerName);
outstr (gaEnumPrt[6],  (pPrtInfo2 + j)->pPrinterName);
outstr (gaEnumPrt[7],  (pPrtInfo2 + j)->pShareName);
outstr (gaEnumPrt[8],  (pPrtInfo2 + j)->pPortName);
outstr (gaEnumPrt[9],  (pPrtInfo2 + j)->pDriverName);
outstr (gaEnumPrt[10], (pPrtInfo2 + j)->pComment);
outstr (gaEnumPrt[11], (pPrtInfo2 + j)->pLocation);

if ((pPrtInfo2 + j)->pDevMode)
{
DWORD dwFields;

outstr (gaEnumPrt[12], "");
outstr (gaEnumPrt[13], (pPrtInfo2 + j)->pDevMode->dmDeviceName);
outnum (gaEnumPrt[14], (pPrtInfo2 + j)->pDevMode->dmSpecVersion);
outnum (gaEnumPrt[15], (pPrtInfo2 + j)->pDevMode->dmDriverVersion);
outnum (gaEnumPrt[16], (pPrtInfo2 + j)->pDevMode->dmSize);
outnum (gaEnumPrt[17], (pPrtInfo2 + j)->pDevMode->dmDriverExtra);

dwFields = (pPrtInfo2 + j)->pDevMode->dmFields;
ComplexEnumPrintersLine (hwnd, gaEnumPrt[18], gaFields,
 MAX_FIELDS,
 dwFields);

if (dwFields & DM_ORIENTATION) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[19], gaOrientation,
 MAX_ORIENTATION,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmOrientation);
}

if (dwFields & DM_PAPERSIZE) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[20], gaPaperSize,
 MAX_PAPERSIZE,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmPaperSize);
}

if (dwFields & DM_PAPERLENGTH)
outnum (gaEnumPrt[21], (pPrtInfo2 + j)->pDevMode->dmPaperLength);

if (dwFields & DM_PAPERWIDTH)
outnum (gaEnumPrt[22], (pPrtInfo2 + j)->pDevMode->dmPaperWidth);

if (dwFields & DM_SCALE)
outnum (gaEnumPrt[23], (pPrtInfo2 + j)->pDevMode->dmScale);

if (dwFields & DM_COPIES)
outnum (gaEnumPrt[24], (pPrtInfo2 + j)->pDevMode->dmCopies);

if (dwFields & DM_DEFAULTSOURCE) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[25], gaDefaultSource,
 MAX_DEFAULTSOURCE,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmDefaultSource);
}

if (dwFields & DM_PRINTQUALITY) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[26], gaPrintQuality,
 MAX_PRINTQUALITY,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmPrintQuality);
}

if (dwFields & DM_COLOR) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[27], gaColor,
 MAX_COLOR,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmColor);
}

if (dwFields & DM_DUPLEX) {
ComplexEnumPrintersLine (hwnd, gaEnumPrt[28], gaDuplex,
 MAX_DUPLEX,
 (DWORD)(pPrtInfo2 + j)->pDevMode->dmDuplex);
}

if (dwFields & DM_YRESOLUTION)
outnum (gaEnumPrt[29], (DWORD)(pPrtInfo2 + j)->pDevMode->dmYResolution);

if (dwFields & DM_TTOPTION)
outnum (gaEnumPrt[30], (DWORD)(pPrtInfo2 + j)->pDevMode->dmTTOption);

if (dwFields & DM_COLLATE)
outnum (gaEnumPrt[31], (DWORD)(pPrtInfo2 + j)->pDevMode->dmCollate);

if (dwFields & DM_FORMNAME)
outstr (gaEnumPrt[32], (pPrtInfo2 + j)->pDevMode->dmFormName);
}
else
{
outstr (gaEnumPrt[12], NULL);
}

outstr (gaEnumPrt[33], (pPrtInfo2 + j)->pSepFile);
outstr (gaEnumPrt[34], (pPrtInfo2 + j)->pPrintProcessor);
outstr (gaEnumPrt[35], (pPrtInfo2 + j)->pDatatype);
outstr (gaEnumPrt[36], (pPrtInfo2 + j)->pParameters);

ComplexEnumPrintersLine (hwnd, gaEnumPrt[37], gaAttributes,
 MAX_ATTRIBUTES,
 (DWORD)(pPrtInfo2 + j)->Attributes);

for (i = 0; i < MAX_PRIORITIES; i++) {
if ((pPrtInfo2 + j)->Priority & gaPriorities[i].dwValue) {
outstr (gaEnumPrt[38], gaPriorities[i].szValue);
break;
}
}

if (i == MAX_PRIORITIES) {
outnum (gaEnumPrt[39], (pPrtInfo2 + j)->Priority);
}

outnum (gaEnumPrt[40], (pPrtInfo2 + j)->DefaultPriority);
outnum (gaEnumPrt[41], (pPrtInfo2 + j)->StartTime);
outnum (gaEnumPrt[42], (pPrtInfo2 + j)->UntilTime);

ComplexEnumPrintersLine (hwnd, gaEnumPrt[43], gaStatus,
 MAX_STATUS,
 (DWORD)(pPrtInfo2 + j)->Status);

outnum (gaEnumPrt[44], (pPrtInfo2 + j)->cJobs);
outnum (gaEnumPrt[45], (pPrtInfo2 + j)->AveragePPM);
        }
    }
}

/******************************************************************************\
*
*  FUNCTION:    ComplexEnumPrintersLine
*
*  INPUTS:      hwnd        - handle of the EnumPrinters dialog box
*               pbuf        - pointer to buffer containing a cap-type
*                              string
*               pLkUp       - pointer to a CAPSLOOKUP table
*               iMaxEntries - # of enries in table pointed at by pLkUp
*               iValue      - an integer containing 1+ bit-value flags.
*
*  COMMENTS:    This function is used to expand an int containing
*               multiple bit-values into a set of strings which are
*               inserted into the DevCapsDlg listbox. The iValue
*               parameter is checked against each iIndex entry in the
*               CAPSLOOKUP table pointed at by pLkUp, and when matches
*               are found the corresponding (lpszValue) string is
*               inserted.
*
*               The buffer pointed to by pbuf will be destroyed.
*
\******************************************************************************/

void ComplexEnumPrintersLine (HWND hwnd, char  *pstr, ENUMPRTLOOKUP *pLkUp,
                              int iMaxEntries, DWORD  dwValue)
{
  char buf [BUFSIZE];
  int  i;
  BOOL bNewLine = FALSE;

  strcpy (buf, pstr);

  for (i = 0; i < iMaxEntries; i++)

    if (dwValue & (pLkUp + i)->dwValue)
    {
      if (bNewLine)
      {
        //
        // Keep the first symbolic constant on the same line as the
        //   cap type, eg:  "TECHNOLOGY:     DT_RASDISPLAY".
        //

        strcpy (buf, BLANKS);
        strcat (buf, (pLkUp + i)->szValue);
      }
      else
      {
        //
        // Put symbolic constant on new line, eg:
        //                  "                DT_RASPRINTER".
        //

        strcat (buf, (pLkUp + i)->szValue);
        bNewLine = TRUE;
      }
      SendDlgItemMessage (hwnd, DID_LISTBOX, LB_INSERTSTRING,
                          (UINT)-1, (LONG) buf);
   }
}

/******************************************************************************\
*
*  FUNCTION:    EnumPrinterDriversDlgProc (standard dlg proc INPUTS/RETURNS)
*
*  COMMENTS:    Processes messages for EnumPrinterDrivers dialog box
*
\******************************************************************************/

LRESULT CALLBACK EnumPrinterDriversDlgProc (HWND   hwnd,   UINT   msg,
                                            WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      BOOL bReturn;

      SetCursor (LoadCursor (NULL, IDC_WAIT));
      bReturn = DisplayPrinterDriversInfo (hwnd);
      SetCursor (LoadCursor (NULL, IDC_ARROW));

      if (!bReturn)

        EndDialog (hwnd, TRUE);

      else

        SetWindowText (hwnd, (LPCTSTR) "EnumPrinterDrivers");

      break;
    }

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_OK:

          EndDialog (hwnd, TRUE);

          return 1;
      }
      break;
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    DisplayPrinterDriversInfo
*
*  INPUTS:      hwnd - handle of the EnumPrinterDrivers dialog box
*
*  RETURNS:     TRUE if successful,
*               FALSE otherwise
*
*  COMMENTS:    Retrieves EnumPrinterDrivers info, formats, & inserts
*               in listbox.
*
\******************************************************************************/

BOOL DisplayPrinterDriversInfo (HWND hwnd)
{
  DWORD         dwBytesNeeded, dwDrvRet, i;
  DRIVER_INFO_1 *pDriverInfo1;
  DRIVER_INFO_2 *pDriverInfo2;
  char          buf[BUFSIZE];
  BOOL          bReturn = TRUE;

  //
  // get byte count needed for buffer, alloc buffer, the enum the drivers
  //

  EnumPrinterDrivers ((LPTSTR) NULL, (LPTSTR) NULL, 1, NULL,
                      0, &dwBytesNeeded, &dwDrvRet);

  //
  // simple error checking, if these work assume rest will too
  //

  if (!(pDriverInfo1 = (DRIVER_INFO_1 *) LocalAlloc (LPTR, dwBytesNeeded)))
  {
    ErrMsgBox (GetStringRes(IDS_LALLOCFAIL), GetStringRes2(ERR_MOD_NAME));
    bReturn = FALSE;
    goto display_prt_drvs_info_done1;
  }

  if (!EnumPrinterDrivers ((LPTSTR) NULL, (LPTSTR) NULL, 1,
                           (LPBYTE) pDriverInfo1, dwBytesNeeded, &dwBytesNeeded,
                           &dwDrvRet))
  {
    ErrMsgBox (GetStringRes(IDS_ENUMPRTDRVRET0), GetStringRes2(ERR_MOD_NAME));
    bReturn = FALSE;
    goto display_prt_drvs_info_done2;
  }

  EnumPrinterDrivers ((LPTSTR) NULL,(LPTSTR) NULL, 2, NULL,
                      0, &dwBytesNeeded, &dwDrvRet);

  pDriverInfo2 = (DRIVER_INFO_2 *) LocalAlloc (LPTR, dwBytesNeeded);

  EnumPrinterDrivers ((LPTSTR) NULL, (LPTSTR) NULL, 2,
                      (LPBYTE) pDriverInfo2, dwBytesNeeded, &dwBytesNeeded,
                      &dwDrvRet);

  if (!dwDrvRet)
  {
    ErrMsgBox (GetStringRes(IDS_ENUMPRTDRVRET0), "");
    bReturn = FALSE;
    goto display_prt_drvs_info_done3;
  }

  //
  // insert formatted info into listbox
  //

  for (i = 0; i < dwDrvRet; i++)
  {
    sprintf (buf, gaDriverInfo[0]);
    outstr3();

    sprintf (buf, gaDriverInfo[1], (pDriverInfo1 + i)->pName);
    outstr3();

    sprintf (buf, gaDriverInfo[2]);
    outstr3();

    sprintf (buf, gaDriverInfo[3], (pDriverInfo2 + i)->cVersion);
    outstr3();

    sprintf (buf, gaDriverInfo[4], (pDriverInfo2 + i)->pName);
    outstr3();

    sprintf (buf, gaDriverInfo[5], (pDriverInfo2 + i)->pEnvironment);
    outstr3();

    sprintf (buf, gaDriverInfo[6], (pDriverInfo2 + i)->pDriverPath);
    outstr3();

    sprintf (buf, gaDriverInfo[7], (pDriverInfo2 + i)->pDataFile);
    outstr3();

    sprintf (buf, gaDriverInfo[8], (pDriverInfo2 + i)->pConfigFile);
    outstr3();

  }

display_prt_drvs_info_done3:

  LocalFree (LocalHandle (pDriverInfo2));

display_prt_drvs_info_done2:

  LocalFree (LocalHandle (pDriverInfo1));

display_prt_drvs_info_done1:

  return bReturn;
}


ENUMPRT.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                                  ENUMPRT.H
*
\******************************************************************************/

/******************************************************************************\
*                             SYMBOLIC CONSTANTS
\******************************************************************************/

#define MAX_ATTRIBUTES     4   // # of elements in gaAttributes
#define MAX_PRIORITIES     4   // # of elements in gaPriorities
#define MAX_STATUS         3   // # of elements in gaStatus
#define MAX_FIELDS         15  // # of elements in gaFields
#define MAX_ORIENTATION    2   // # of elements in gaOrientation
#define MAX_PAPERSIZE      41  // # of elements in gaPaperSize
#define MAX_DEFAULTSOURCE  16  // # of elements in gaDefaultSource
#define MAX_PRINTQUALITY   4   // # of elements in gaPrintQuality
#define MAX_COLOR          2   // # of elements in gaColor
#define MAX_DUPLEX         3   // # of elements in gaDuplex

#define ERR_MOD_NAME       IDS_ERR_ENUMPRT

#define BLANKS             "\t\t\t"

/******************************************************************************\
*                                  TYPEDEFS
\******************************************************************************/

typedef struct tagENUMPRTLOOKUP
{
  DWORD   dwValue;
  LPCSTR  szValue;

} ENUMPRTLOOKUP;

/******************************************************************************\
*                              GLOBAL VARIABLES
\******************************************************************************/

HWND  ghwndEnumPrt;
DWORD gdwEnumFlags;
char  gszEnumName[BUFSIZE];

LPSTR gaEnumPrt[] =

      { "PRINTER_INFO_1"  ,
        "  pDescription:      \t%s",
        "  pName:             \t%s",
        "  pComment:          \t%s",
        "PRINTER_INFO_2"  ,
        "  pServerName:       \t%s",  // 5
        "  pPrinterName:      \t%s",
        "  pShareName:        \t%s",
        "  pPortName:         \t%s",
        "  pDriverName:       \t%s",
        "  pComment:          \t%s",  // 10
        "  pLocation:         \t%s",
        "  pDevMode           \t%s",
        "     dmDeviceName:     \t%s",
        "     dmSpecVersion:    \t%d",
        "     dmDriverVersion:  \t%d",  // 15
        "     dmSize:           \t%d",
        "     dmDriverExtra:    \t%d",
        "     dmFields:         \t",
        "     dmOrientation:    \t",
        "     dmPaperSize:      \t",  // 20
        "     dmPaperLength:    \t%d",
        "     dmPaperWidth:     \t%d",
        "     dmScale:          \t%d",
        "     dmCopies:         \t%d",
        "     dmDefaultSource:  \t",  // 25
        "     dmPrintQuality:   \t%s",
        "     dmColor:          \t",
        "     dmDuplex:         \t",
        "     dmYResolution:    \t%d",
        "     dmTTOption:       \t%d",  // 30
        "     dmCollate:        \t&d",
        "     dmFormName:       \t%s",
        "  pSepFile:          \t%s",
        "  pPrintProcessor:   \t%s",
        "  pDatatype:         \t%s",  // 35
        "  pParameters:       \t%s",
        "  Attributes:        \t",
        "  Priority:          \t%s",
        "  Priority:          \t%ld",
        "  DefaultPriority:   \t%ld",  // 40
        "  StartTime:         \t%ld",
        "  UntilTime:         \t%ld",
        "  Status:            \t",
        "  cJobs:             \t%ld",
        "  AveragePPM:        \t%ld"    };

ENUMPRTLOOKUP gaAttributes[] =

    { { PRINTER_ATTRIBUTE_QUEUED       , "PRINTER_ATTRIBUTE_QUEUED  " },
      { PRINTER_ATTRIBUTE_DIRECT       , "PRINTER_ATTRIBUTE_DIRECT  " },
      { PRINTER_ATTRIBUTE_DEFAULT      , "PRINTER_ATTRIBUTE_DEFAULT " },
      { PRINTER_ATTRIBUTE_SHARED       , "PRINTER_ATTRIBUTE_SHARED  " } };

ENUMPRTLOOKUP gaPriorities[] =

    { { NO_PRIORITY                    , "NO_PRIORITY  " },
      { MAX_PRIORITY                   , "MAX_PRIORITY " },
      { MIN_PRIORITY                   , "MIN_PRIORITY " },
      { DEF_PRIORITY                   , "DEF_PRIORITY " } };

ENUMPRTLOOKUP gaStatus[] =

    { { PRINTER_STATUS_PAUSED          , "PRINTER_STATUS_PAUSED "          },
      { PRINTER_STATUS_ERROR           , "PRINTER_STATUS_ERROR  "          },
      { PRINTER_STATUS_PENDING_DELETION, "PRINTER_STATUS_PENDING_DELETION "},
      { 0                              , ""                                }};

ENUMPRTLOOKUP gaFields[] =

    { { DM_ORIENTATION  , "DM_ORIENTATION  " },
      { DM_PAPERSIZE    , "DM_PAPERSIZE    " },
      { DM_PAPERLENGTH  , "DM_PAPERLENGTH  " },
      { DM_PAPERWIDTH   , "DM_PAPERWIDTH   " },
      { DM_SCALE        , "DM_SCALE        " },
      { DM_COPIES       , "DM_COPIES       " },
      { DM_DEFAULTSOURCE, "DM_DEFAULTSOURCE" },
      { DM_PRINTQUALITY , "DM_PRINTQUALITY " },
      { DM_COLOR        , "DM_COLOR        " },
      { DM_DUPLEX       , "DM_DUPLEX       " },
      { DM_YRESOLUTION  , "DM_YRESOLUTION  " },
      { DM_TTOPTION     , "DM_TTOPTION     " },
      { DM_COLLATE      , "DM_COLLATE      " },
      { DM_FORMNAME     , "DM_FORMNAME     " },
      { 0               , "0               " } };

ENUMPRTLOOKUP gaOrientation[] =

    { { DMORIENT_PORTRAIT , "DMORIENT_PORTRAIT " },
      { DMORIENT_LANDSCAPE, "DMORIENT_LANDSCAPE" } };

ENUMPRTLOOKUP gaPaperSize[] =

    { { DMPAPER_FIRST             , "DMPAPER_FIRST             " },
      { DMPAPER_LETTER            , "DMPAPER_LETTER            " },
      { DMPAPER_LETTERSMALL       , "DMPAPER_LETTERSMALL       " },
      { DMPAPER_TABLOID           , "DMPAPER_TABLOID           " },
      { DMPAPER_LEDGER            , "DMPAPER_LEDGER            " },
      { DMPAPER_LEGAL             , "DMPAPER_LEGAL             " },
      { DMPAPER_STATEMENT         , "DMPAPER_STATEMENT         " },
      { DMPAPER_EXECUTIVE         , "DMPAPER_EXECUTIVE         " },
      { DMPAPER_A3                , "DMPAPER_A3                " },
      { DMPAPER_A4                , "DMPAPER_A4                " },
      { DMPAPER_A4SMALL           , "DMPAPER_A4SMALL           " },
      { DMPAPER_A5                , "DMPAPER_A5                " },
      { DMPAPER_B4                , "DMPAPER_B4                " },
      { DMPAPER_B5                , "DMPAPER_B5                " },
      { DMPAPER_FOLIO             , "DMPAPER_FOLIO             " },
      { DMPAPER_QUARTO            , "DMPAPER_QUARTO            " },
      { DMPAPER_10X14             , "DMPAPER_10X14             " },
      { DMPAPER_11X17             , "DMPAPER_11X17             " },
      { DMPAPER_NOTE              , "DMPAPER_NOTE              " },
      { DMPAPER_ENV_9             , "DMPAPER_ENV_9             " },
      { DMPAPER_ENV_10            , "DMPAPER_ENV_10            " },
      { DMPAPER_ENV_11            , "DMPAPER_ENV_11            " },
      { DMPAPER_ENV_12            , "DMPAPER_ENV_12            " },
      { DMPAPER_ENV_14            , "DMPAPER_ENV_14            " },
      { DMPAPER_CSHEET            , "DMPAPER_CSHEET            " },
      { DMPAPER_DSHEET            , "DMPAPER_DSHEET            " },
      { DMPAPER_ESHEET            , "DMPAPER_ESHEET            " },
      { DMPAPER_ENV_DL            , "DMPAPER_ENV_DL            " },
      { DMPAPER_ENV_C5            , "DMPAPER_ENV_C5            " },
      { DMPAPER_ENV_C3            , "DMPAPER_ENV_C3            " },
      { DMPAPER_ENV_C4            , "DMPAPER_ENV_C4            " },
      { DMPAPER_ENV_C6            , "DMPAPER_ENV_C6            " },
      { DMPAPER_ENV_C65           , "DMPAPER_ENV_C65           " },
      { DMPAPER_ENV_B4            , "DMPAPER_ENV_B4            " },
      { DMPAPER_ENV_B5            , "DMPAPER_ENV_B5            " },
      { DMPAPER_ENV_B6            , "DMPAPER_ENV_B6            " },
      { DMPAPER_ENV_ITALY         , "DMPAPER_ENV_ITALY         " },
      { DMPAPER_ENV_MONARCH       , "DMPAPER_ENV_MONARCH       " },
      { DMPAPER_ENV_PERSONAL      , "DMPAPER_ENV_PERSONAL      " },
      { DMPAPER_FANFOLD_STD_GERMAN, "DMPAPER_FANFOLD_STD_GERMAN" },
      { DMPAPER_FANFOLD_LGL_GERMAN, "DMPAPER_FANFOLD_LGL_GERMAN" } };

ENUMPRTLOOKUP gaDefaultSource[] =

    { { DMBIN_FIRST        , "DMBIN_FIRST        " },
      { DMBIN_UPPER        , "DMBIN_UPPER        " },
      { DMBIN_ONLYONE      , "DMBIN_ONLYONE      " },
      { DMBIN_LOWER        , "DMBIN_LOWER        " },
      { DMBIN_MIDDLE       , "DMBIN_MIDDLE       " },
      { DMBIN_MANUAL       , "DMBIN_MANUAL       " },
      { DMBIN_ENVELOPE     , "DMBIN_ENVELOPE     " },
      { DMBIN_ENVMANUAL    , "DMBIN_ENVMANUAL    " },
      { DMBIN_AUTO         , "DMBIN_AUTO         " },
      { DMBIN_TRACTOR      , "DMBIN_TRACTOR      " },
      { DMBIN_SMALLFMT     , "DMBIN_SMALLFMT     " },
      { DMBIN_LARGEFMT     , "DMBIN_LARGEFMT     " },
      { DMBIN_LARGECAPACITY, "DMBIN_LARGECAPACITY" },
      { DMBIN_CASSETTE     , "DMBIN_CASSETTE     " },
      { DMBIN_LAST         , "DMBIN_LAST         " },
      { DMBIN_USER         , "DMBIN_USER         " } };

ENUMPRTLOOKUP gaPrintQuality[] =

    { { (DWORD)DMRES_DRAFT , "DMRES_DRAFT " },
      { (DWORD)DMRES_LOW   , "DMRES_LOW   " },
      { (DWORD)DMRES_MEDIUM, "DMRES_MEDIUM" },
      { (DWORD)DMRES_HIGH  , "DMRES_HIGH  " } };

ENUMPRTLOOKUP gaColor[] =

    { { DMCOLOR_MONOCHROME, "DMCOLOR_MONOCHROME" },
      { DMCOLOR_COLOR     , "DMCOLOR_COLOR     " } };

ENUMPRTLOOKUP gaDuplex[] =

    { { DMDUP_SIMPLEX   , "DMDUP_SIMPLEX   " },
      { DMDUP_VERTICAL  , "DMDUP_VERTICAL  " },
      { DMDUP_HORIZONTAL, "DMDUP_HORIZONTAL" } };

char *gaDriverInfo[] =

    {   "DRIVER_INFO_1",
        "  pName\t\t%s",

        "DRIVER_INFO_2",
        "  cVersion\t%ld",
        "  pName\t\t%s",
        "  pEnvironment\t%s",
        "  pDriverPath\t%s",
        "  pDataFile\t%s",
        "  pConfigFile\t%s" };

/******************************************************************************\
*                              EXTERNAL VARIABLES
\******************************************************************************/

extern HWND ghwndMain;

/******************************************************************************\
*                                  MACROS
\******************************************************************************/

#define outstr(fmt,arg)  { wsprintf (buf,fmt,arg?arg:"(null)");           \
                           SendDlgItemMessage (hwnd, DID_LISTBOX,         \
                                               LB_INSERTSTRING, (UINT)-1, \
                                               (LONG) buf); }

#define outstr2(buf)     SendDlgItemMessage (hwnd, DID_LISTBOX,         \
                                             LB_INSERTSTRING, (UINT)-1, \
                                             (LONG) buf);

#define outstr3()        SendDlgItemMessage (hwnd, DID_LISTBOX,         \
                                             LB_INSERTSTRING,           \
                                             (UINT)-1, (LONG) buf);

#define outnum(fmt,arg)  { wsprintf(buf,fmt,arg);                         \
                           SendDlgItemMessage (hwnd, DID_LISTBOX,         \
                                               LB_INSERTSTRING, (UINT)-1, \
                                               (LONG) buf); }

/******************************************************************************\
*                            FUNCTION PROTOTYPES
\******************************************************************************/

LRESULT CALLBACK EnumPrintersOptionsDlgProc (HWND, UINT, WPARAM, LPARAM);

BOOL DisplayEnumPrintersInfo   (HWND);
void SetEnumPrintersDlgFields  (HWND, DWORD, LPPRINTER_INFO_1,
                                LPPRINTER_INFO_2);
void ComplexEnumPrintersLine   (HWND, char *, ENUMPRTLOOKUP *, int, DWORD);
BOOL DisplayPrinterDriversInfo (HWND);


GETCAPS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  PROGRAM:     GETCAPS.C
*
*  PURPOSE:     Handles display of information returned by call to
*               GetDeviceCaps. GetDeviceCaps is called for the
*               currently selected deivce (in the tool bar comobobox),
*               and the results are formatted and displayed in a dialog
*               box.
*
*  FUNTIONS:    GetDeviceCapsDlgProc - handles messages for dialog
*               DisplayDeviceCapsInfo- retrieves device caps info
*               TranslateDeviceCaps  - displays a capability in listbox
*               ComplexDeviceCapsLine- formats a bitfield capability
*
\******************************************************************************/

#include <windows.h>
#include <winspool.h>
#include <string.h>
#include <stdio.h>
#include <winspool.h>
#include "common.h"
#include "getcaps.h"
#include "resource.h"

/******************************************************************************\
*
*  FUNCTION:    GetDeviceCapsDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Processes messages for GetDeviceCaps dialog box
*
\******************************************************************************/

LRESULT CALLBACK   GetDeviceCapsDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                                         LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      BOOL bReturn;
      char buf[BUFSIZE];

      SendDlgItemMessage (hwnd, DID_LISTBOX, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), (LPARAM)0);

  ghwndDevCaps = hwnd;

      //
      // shove all the caps info in the list box
      //

      SetCursor (LoadCursor (NULL, IDC_WAIT));
      bReturn = DisplayDeviceCapsInfo ();
      SetCursor (LoadCursor (NULL, IDC_ARROW));

      if (!bReturn)
      {
        EndDialog (hwnd, TRUE);
      }

      //
      // set window title to reflect current device
      //

      else
      {
        sprintf (buf, "GetDeviceCaps: %s;%s;%s", gszDeviceName, gszPort,
                 gszDriverName);

        SetWindowText (hwnd, (LPCSTR) buf);
      }

      break;
    }

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_OK:

          EndDialog (hwnd, TRUE);
          return 1;
      }
      break;
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    DisplayDeviceCapsInfo
*
*  RETURNS:     TRUE if successful,
*               FALSE otherwise
*
*  COMMENTS:    Retrieves all device caps for current deivce & calls
*                 TranslateCaps to insert them in the dialog's listbox.
*
\******************************************************************************/

BOOL DisplayDeviceCapsInfo ()
{
HDC hdc;
int i, iValue;

if (!strcmp (gszDeviceName, "Display"))
{
if (!(hdc = GetDC (ghwndDevCaps)))
{
ErrMsgBox (GetStringRes(IDS_GETDCFAIL), ERR_MOD_NAME);
return FALSE;
}
}

else
{
if (!(hdc = CreateDC (gszDriverName, gszDeviceName, gszPort, NULL)))
{
char buf[BUFSIZE];

sprintf (buf, GetStringRes(IDS_FMT_CREDCFAIL),
gszDriverName, gszDeviceName, gszPort);
ErrMsgBox (buf, ERR_MOD_NAME);
return FALSE;
}
}

for (i = 0; i < MAX_DEVICE_CAPS; i++)
{
iValue = GetDeviceCaps (hdc, gaCaps[i].iValue);
TranslateDeviceCaps (i, gaCaps[i].iValue, iValue);
}

if (!strcmp (gszDeviceName, "Display"))
ReleaseDC(ghwndDevCaps, hdc);
else
DeleteDC (hdc);

return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    TranslateDeviceCaps
*
*  INPUTS:      arrayIndex - index into gaCaps[]
*               capIndex   - devcap index (eg. TECHNOLOGY, CURVECAPS)
*               iValue     - value returned by GetDeviceCaps
*
*  COMMENTS:    For simple devcaps (eg. tjose with single numeric return
*               value), appends caps value to string and inserts into
*               listbox. For "complex" caps (those returning multiple
*               bit-values) calls ComplexCapsLine which handles text
*               formattting & insertion.
*
\******************************************************************************/

void TranslateDeviceCaps (int arrayIndex, int capIndex, int iValue)
{
  char buf[BUFSIZE];

  strcpy (buf, gaCaps[arrayIndex].szValue);

  switch (capIndex)
  {
    case TECHNOLOGY:
    {
      int     i;

      for (i = 0; i < MAX_TECHNOLOGY_CAPS; i++)
        if (iValue == (gaTechnologyCaps + i)->iValue)
        {
          strcat(buf, (gaTechnologyCaps + i)->szValue);
          SendDlgItemMessage (ghwndDevCaps, DID_LISTBOX, LB_INSERTSTRING,
                              (UINT)-1, (LONG) buf);
          break;
        }

      break;
    }

    case CURVECAPS:

      ComplexDeviceCapsLine (buf, gaCurveCaps, MAX_CURVE_CAPS, iValue);
      break;

    case LINECAPS:

      ComplexDeviceCapsLine (buf, gaLineCaps, MAX_LINE_CAPS, iValue);
      break;

    case POLYGONALCAPS:

      ComplexDeviceCapsLine (buf, gaPolygonCaps, MAX_POLYGON_CAPS, iValue);
      break;

    case TEXTCAPS:

      ComplexDeviceCapsLine (buf, gaTextCaps, MAX_TEXT_CAPS, iValue);
      break;

    case CLIPCAPS:

      ComplexDeviceCapsLine (buf, gaClipCaps, MAX_CLIP_CAPS, iValue);
      break;

    case RASTERCAPS:

      ComplexDeviceCapsLine (buf, gaRasterCaps, MAX_RASTER_CAPS, iValue);
      break;

    default:

      wsprintf(buf, gaCaps[arrayIndex].szValue, iValue);

      SendDlgItemMessage (ghwndDevCaps, DID_LISTBOX, LB_INSERTSTRING,
                          (UINT)-1, (LONG) buf);
      break;
  }
}

/******************************************************************************\
*
*  FUNCTION:     ComplexDeviceCapsLine
*
*  INPUTS:       pbuf        - pointer to buffer containing a cap-type
*                              string
*                pLkUp       - pointer to a CAPSLOOKUP table
*                iMaxEntries - # of enries in table pointed at by pLkUp
*                iValue      - an integer containing 1+ bit-value flags.
*
*  COMMENTS:     This function is used to expand an int containing
*                multiple bit-values into a set of strings which are
*                inserted into the DevCapsDlg listbox. The iValue
*                parameter is checked against each iIndex entry in the
*                CAPSLOOKUP table pointed at by pLkUp, and when matches
*                are found the corresponding (lpszValue) string is
*                inserted.
*
*                The buffer pointed to by pbuf will be destroyed.
*
\******************************************************************************/

void ComplexDeviceCapsLine (char *pbuf, CAPSLOOKUP *pLkUp, int iMaxEntries,
                     int iValue)
{
  int  i;
  BOOL bNewLine = FALSE;

  for (i = 0; i < iMaxEntries; i++)

    if (iValue & (pLkUp + i)->iValue)
    {
      if (bNewLine)
      {
        //
        // Keep the first symbolic constant on the same line as the
        //   cap type, eg:  "TECHNOLOGY:     DT_RASDISPLAY".
        //

        strcpy (pbuf, BLANKS);
        strcat (pbuf, (pLkUp + i)->szValue);
      }
      else
      {
        //
        // Put symbolic constant on new line, eg:
        //                  "                DT_RASPRINTER".
        //

        strcat (pbuf, (pLkUp + i)->szValue);
        bNewLine = TRUE;
      }
      SendDlgItemMessage (ghwndDevCaps, DID_LISTBOX, LB_INSERTSTRING,
                          (UINT)-1, (LONG) pbuf);
   }
}


GETCAPS.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                               GETCAPS.H
*
\******************************************************************************/

/******************************************************************************\
*                           SYMBOLIC CONSTANTS
\******************************************************************************/

#define MAX_DEVICE_CAPS     34     // num entries in gaCaps table

#define MAX_TECHNOLOGY_CAPS 7      // num entries in gaTechnologyCaps table
#define MAX_CURVE_CAPS      10     // num entries in gaCurveCaps table
#define MAX_LINE_CAPS       8      // num entries in gaLineCaps table
#define MAX_POLYGON_CAPS    10     // num entries in gaPolygonCaps table
#define MAX_TEXT_CAPS       16     // num entries in gaTextCaps table
#define MAX_RASTER_CAPS     17     // num entries in gaRasterCaps table
#define MAX_CLIP_CAPS       3      // num entries in gaClipCaps table

#define BLANKS              "                 \t" // see gaCaps to get spacing

#define ERR_MOD_NAME       IDS_ERR_GETCAPS

/******************************************************************************\
*                                TYPEDEFS
\******************************************************************************/

typedef struct tagCAPSLOOKUP
{
  int     iValue;
  LPTSTR  szValue;

} CAPSLOOKUP;

/******************************************************************************\
*                            GLOBAL VARIABLES
\******************************************************************************/

HWND ghwndDevCaps;

CAPSLOOKUP gaCaps[] =

    { { DRIVERVERSION,   "DRIVERVERSION:   \t%ld" },
      { TECHNOLOGY,      "TECHNOLOGY:      \t"    },
      { HORZSIZE,        "HORZSIZE:        \t%ld" },
      { VERTSIZE,        "VERTSIZE:        \t%ld" },
      { HORZRES,         "HORZRES:         \t%ld" },
      { VERTRES,         "VERTRES:         \t%ld" },
      { BITSPIXEL,       "BITSPIXEL:       \t%ld" },
      { PLANES,          "PLANES:          \t%ld" },
      { NUMBRUSHES,      "NUMBRUSHES:      \t%ld" },
      { NUMPENS,         "NUMPENS:         \t%ld" },
      { NUMMARKERS,      "NUMMARKERS:      \t%ld" },
      { NUMFONTS,        "NUMFONTS:        \t%ld" },
      { NUMCOLORS,       "NUMCOLORS:       \t%ld" },
      { PDEVICESIZE,     "PDEVICESIZE:     \t%ld" },
      { CURVECAPS,       "CURVECAPS:       \t"    },
      { LINECAPS,        "LINECAPS:        \t"    },
      { POLYGONALCAPS,   "POLYGONALCAPS:   \t"    },
      { TEXTCAPS,        "TEXTCAPS:        \t"    },
      { CLIPCAPS,        "CLIPCAPS:        \t"    },
      { RASTERCAPS,      "RASTERCAPS:      \t"    },
      { ASPECTX,         "ASPECTX:         \t%ld" },
      { ASPECTY,         "ASPECTY:         \t%ld" },
      { ASPECTXY,        "ASPECTXY:        \t%ld" },
      { LOGPIXELSX,      "LOGPIXELSX:      \t%ld" },
      { LOGPIXELSY,      "LOGPIXELSY:      \t%ld" },
      { SIZEPALETTE,     "SIZEPALETTE:     \t%ld" },
      { NUMRESERVED,     "NUMRESERVED:     \t%ld" },
      { COLORRES,        "COLORRES:        \t%ld" },
      { PHYSICALWIDTH,   "PHYSICALWIDTH:   \t%ld" },
      { PHYSICALHEIGHT,  "PHYSICALHEIGHT:  \t%ld" },
      { PHYSICALOFFSETX, "PHYSICALOFFSETX: \t%ld" },
      { PHYSICALOFFSETY, "PHYSICALOFFSETY: \t%ld" },
      { SCALINGFACTORX,  "SCALINGFACTORX:  \t%ld" },
      { SCALINGFACTORY,  "SCALINGFACTORY:  \t%ld" }  };

CAPSLOOKUP gaTechnologyCaps[] =

    { { DT_PLOTTER,      "DT_PLOTTER "      },
      { DT_RASDISPLAY,   "DT_RASDISPLAY "   },
      { DT_RASPRINTER,   "DT_RASPRINTER "   },
      { DT_RASCAMERA,    "DT_RASCAMERA "    },
      { DT_CHARSTREAM,   "DT_CHARSTREAM "   },
      { DT_METAFILE,     "DT_METAFILE "     },
      { DT_DISPFILE,     "DT_DISPFILE "     } };

CAPSLOOKUP gaCurveCaps[] =

    { { CC_NONE,         "CC_NONE "         },
      { CC_CIRCLES,      "CC_CIRCLES "      },
      { CC_PIE,          "CC_PIE "          },
      { CC_CHORD,        "CC_CHORD "        },
      { CC_ELLIPSES,     "CC_ELLIPSES "     },
      { CC_WIDE,         "CC_WIDE "         },
      { CC_STYLED,       "CC_STYLED "       },
      { CC_WIDESTYLED,   "CC_WIDESTYLED "   },
      { CC_INTERIORS,    "CC_INTERIORS "    },
      { CC_ROUNDRECT,    "CC_ROUNDRECT "    } };

CAPSLOOKUP gaLineCaps[] =

    { { LC_NONE,         "LC_NONE "         },
      { LC_POLYLINE,     "LC_POLYLINE "     },
      { LC_MARKER,       "LC_MARKER "       },
      { LC_POLYMARKER,   "LC_POLYMARKER "   },
      { LC_WIDE,         "LC_WIDE "         },
      { LC_STYLED,       "LC_STYLED "       },
      { LC_WIDESTYLED,   "LC_WIDESTYLED "   },
      { LC_INTERIORS,    "LC_INTERIORS "    } };

CAPSLOOKUP gaPolygonCaps[] =

    { { PC_NONE,         "PC_NONE "         },
      { PC_POLYGON,      "PC_POLYGON "      },
      { PC_RECTANGLE,    "PC_RECTANGLE "    },
      { PC_WINDPOLYGON,  "PC_WINDPOLYGON"   },
      { PC_TRAPEZOID,    "PC_TRAPEZOID"     },
      { PC_SCANLINE,     "PC_SCANLINE"      },
      { PC_WIDE,         "PC_WIDE"          },
      { PC_STYLED,       "PC_STYLED"        },
      { PC_WIDESTYLED,   "PC_WIDESTYLED"    },
      { PC_INTERIORS,    "PC_INTERIORS"     } };

CAPSLOOKUP gaTextCaps[] =

    { { TC_OP_CHARACTER, "TC_OP_CHARACTER"  },
      { TC_OP_STROKE,    "TC_OP_STROKE"     },
      { TC_CP_STROKE,    "TC_CP_STROKE"     },
      { TC_CR_90,        "TC_CR_90"         },
      { TC_CR_ANY,       "TC_CR_ANY"        },
      { TC_SF_X_YINDEP,  "TC_SF_X_YINDEP "  },
      { TC_SA_DOUBLE,    "TC_SA_DOUBLE "    },
      { TC_SA_INTEGER,   "TC_SA_INTEGER "   },
      { TC_SA_CONTIN,    "TC_SA_CONTIN "    },
      { TC_EA_DOUBLE,    "TC_EA_DOUBLE "    },
      { TC_IA_ABLE,      "TC_IA_ABLE "      },
      { TC_UA_ABLE,      "TC_UA_ABLE "      },
      { TC_SO_ABLE,      "TC_SO_ABLE "      },
      { TC_RA_ABLE,      "TC_RA_ABLE "      },
      { TC_VA_ABLE,      "TC_VA_ABLE "      },
      { TC_RESERVED,     "TC_RESERVED "     },
      { TC_SCROLLBLT,    "TC_SCROLLBLT "     } };

CAPSLOOKUP gaRasterCaps[] =

    { { 0,               "RC_NONE "         }, // !!! RC_NONE not #def'd
      { RC_BITBLT,       "RC_BITBLT "       },
      { RC_BANDING,      "RC_BANDING "      },
      { RC_SCALING,      "RC_SCALING "      },
      { RC_BITMAP64,     "RC_BITMAP64 "     },
      { RC_GDI20_OUTPUT, "RC_GDI20_OUTPUT " },
      { RC_GDI20_STATE,  "RC_GDI20_STATE "  },
      { RC_SAVEBITMAP,   "RC_SAVEBITMAP "   },
      { RC_DI_BITMAP,    "RC_DI_BITMAP "    },
      { RC_PALETTE,      "RC_PALETTE "      },
      { RC_DIBTODEV,     "RC_DIBTODEV "     },
      { RC_BIGFONT,      "RC_BIGFONT "      },
      { RC_STRETCHBLT,   "RC_STRETCHBLT "   },
      { RC_FLOODFILL,    "RC_FLOODFILL "    },
      { RC_STRETCHDIB,   "RC_STRETCHDIB "   },
      { RC_OP_DX_OUTPUT, "RC_OP_DX_OUTPUT " },
      { RC_DEVBITS,      "RC_DEVBITS "      } };

CAPSLOOKUP gaClipCaps[] =

    { { CP_NONE,         "CP_NONE "         },
      { CP_RECTANGLE,    "CP_RECTANGLE "    },
      { CP_REGION,       "CP_REGION "       } };

/******************************************************************************\
*                          EXTERNAL VARIABLES
\******************************************************************************/

extern HWND ghwndMain;
extern char gszDeviceName [];
extern char gszDriverName [];
extern char gszPort       [];

/******************************************************************************\
*                          FUNCTION PROTOTYPES
\******************************************************************************/

BOOL DisplayDeviceCapsInfo (void);
void TranslateDeviceCaps   (int, int, int);
void ComplexDeviceCapsLine (char *, CAPSLOOKUP *, int, int);


GETPDRIV.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  PROGRAM:     GETPDRIV.C
*
*  PURPOSE:     Handles display of information returned by call to
*               GetPrinterDriver. GetPrinterDriver is called for the
*               currently selected printer (in the tool bar comobobox),
*               and the results are formatted and displayed in a dialog
*               box.
*
*  FUNTIONS:    GetPrinterDriverDlgProc - handles messages for dialog
*               DisplayPrinterDriverInfo- retrieves & displays printer
*                                           driver info
*
\******************************************************************************/

#include <windows.h>
#include <winspool.h>
#include <string.h>
#include <stdio.h>
#include <winspool.h>
#include "common.h"
#include "getpdriv.h"
#include "resource.h"

/******************************************************************************\
*
*  FUNCTION:    GetPrinterDriverDlgProc (standard dlg proc INPUTS/RETURNS)
*
*  COMMENTS:    Processes messages for getPrinterDriver dialog box
*
\******************************************************************************/

LRESULT CALLBACK GetPrinterDriverDlgProc (HWND   hwnd,   UINT msg,
                                          WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
    {
      BOOL bReturn;
      char buf[BUFSIZE];

  //
      // shove all the printer driver info in the list box
      //

      SetCursor (LoadCursor (NULL, IDC_WAIT));
      bReturn = DisplayPrinterDriverInfo (hwnd);
      SetCursor (LoadCursor (NULL, IDC_ARROW));

      if (!bReturn)
      {
        EndDialog (hwnd, TRUE);
      }

      //
      // set window title to reflect current device
      //

      else
      {
        sprintf (buf, "GetPrinterDriver: %s;%s;%s", gszDeviceName, gszPort,
                 gszDriverName);

        SetWindowText (hwnd, (LPCSTR) buf);
      }

      break;
    }

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_OK:

          EndDialog (hwnd, TRUE);
          return 1;
      }
      break;
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    DisplayPrinterDriverInfo
*
*  INPUTS:      hwnd - handle of GetPrinterDriver dialog box
*
*  RETURNS:     TRUE if successful,
*               FALSE otherwise
*
\******************************************************************************/

BOOL DisplayPrinterDriverInfo (HWND hwnd)
{
  HANDLE        hPrinter;
  DWORD         dwBytesNeeded;
  DRIVER_INFO_1 *pDriverInfo1;
  DRIVER_INFO_2 *pDriverInfo2;
  char          buf[BUFSIZE];
  char          pEnvironment[BUFSIZE] = "";
  BOOL          bReturn = TRUE;

  //
  // open selected printer & alloc buffers & get sundry info, close printer
  //

  OpenPrinter (gszDeviceName, &hPrinter, NULL);

  if (!hPrinter)
  {
    char buf[BUFSIZE];

    sprintf (buf, GetStringRes(IDS_FMT_OPNPRTFAIL), gszDeviceName);
    ErrMsgBox ((LPCSTR) buf, ERR_MOD_NAME);
    bReturn  = FALSE;
    goto display_prt_drv_info_done1;
  }

  GetPrinterDriver (hPrinter, pEnvironment, 1, NULL, 0, &dwBytesNeeded);

  //
  // simple error checking, if these work assume rest will too
  //

  if (!(pDriverInfo1 = (DRIVER_INFO_1 *) LocalAlloc (LPTR, dwBytesNeeded)))
  {
    ErrMsgBox (GetStringRes(IDS_LALLOCFAIL), ERR_MOD_NAME);
    bReturn = FALSE;
    goto display_prt_drv_info_done1;
  }

  if (!GetPrinterDriver (hPrinter, pEnvironment, 1, (LPBYTE) pDriverInfo1,
                         dwBytesNeeded, &dwBytesNeeded))
  {
    ErrMsgBox (GetStringRes(IDS_GETPRTDRVFAIL), ERR_MOD_NAME);
    bReturn = FALSE;
    goto display_prt_drv_info_done2;
  }

  GetPrinterDriver (hPrinter, pEnvironment, 2, NULL, 0, &dwBytesNeeded);
  pDriverInfo2 = (DRIVER_INFO_2 *) LocalAlloc (LPTR, dwBytesNeeded);
  GetPrinterDriver (hPrinter, pEnvironment, 2, (LPBYTE) pDriverInfo2,
                    dwBytesNeeded, &dwBytesNeeded);

  ClosePrinter (hPrinter);

  //
  // shove info in listbox
  //

  sprintf (buf, gaDrvInfo[0]);
  outstr();

  sprintf (buf, gaDrvInfo[1], pDriverInfo1->pName);
  outstr();

  sprintf (buf, gaDrvInfo[2]);
  outstr();

  sprintf (buf, gaDrvInfo[3], pDriverInfo2->cVersion);
  outstr();

  sprintf (buf, gaDrvInfo[4], pDriverInfo2->pName);
  outstr();

  sprintf (buf, gaDrvInfo[5], pDriverInfo2->pEnvironment);
  outstr();

  sprintf (buf, gaDrvInfo[6], pDriverInfo2->pDriverPath);
  outstr();

  sprintf (buf, gaDrvInfo[7], pDriverInfo2->pDataFile);
  outstr();

  sprintf (buf, gaDrvInfo[8], pDriverInfo2->pConfigFile);
  outstr();

  LocalFree (LocalHandle (pDriverInfo2));

display_prt_drv_info_done2:

  LocalFree (LocalHandle (pDriverInfo1));

display_prt_drv_info_done1:

  return bReturn;
}


GETPDRIV.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                               GETPDRIV.H
*
\******************************************************************************/

/******************************************************************************\
*                           SYMBOLIC CONSTANTS
\******************************************************************************/

#define ERR_MOD_NAME        IDS_ERR_GETPDRIV

/******************************************************************************\
*                            GLOBAL VARIABLES
\******************************************************************************/

char *gaDrvInfo[] =

    {   "DRIVER_INFO_1",
        "  pName\t\t%s",

        "DRIVER_INFO_2",
        "  cVersion\t%ld",
        "  pName\t\t%s",
        "  pEnvironment\t%s",
        "  pDriverPath\t%s",
        "  pDataFile\t%s",
        "  pConfigFile\t%s" };

/******************************************************************************\
*                          EXTERNAL VARIABLES
\******************************************************************************/

extern HWND ghwndMain;
extern char gszDeviceName [];
extern char gszDriverName [];
extern char gszPort       [];

/******************************************************************************\
*                               MACROS
\******************************************************************************/

#define outstr()  SendDlgItemMessage (hwnd, DID_LISTBOX, LB_INSERTSTRING, \
                                      (UINT)-1, (LONG) buf);

/******************************************************************************\
*                          FUNCTION PROTOTYPES
\******************************************************************************/

BOOL DisplayPrinterDriverInfo (HWND);


PAINT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  MODULE:      PAINT.C
*
*  PURPOSE:     Given an HDC and a pointer to a bounding rectangle
*               draw all graphics/fonts based on the flags set in
*               the dwGraphicsOptions global variable.
*
*  FUNTIONS:    Paint              - main painting routine
*               GetFirstGraphicSlot- computes bounding rect of 1st
*                                      graphic
*               GetNextGraphicSlot - computes bounding rect of next
*                                      graphic
*               DrawFonts          - draws enumerated fonts
*               BuildFontList      - builds a list of fonts of fonts
*                                      supported by a given DC
*               MyEnumFaces        - enumerates the font facenames
*                                      supported by a given DC
*               MyEnumCopy         - copies LOGFONT & TEXTMETRIC info
*                                      to a global variable
*               MyEnumCount        - counts total number of fonts
*                                      supported by a given DC
*               FreeFontList       - frees a (BuildFontList-) font list
*
*  COMMENTS:    Most of the font-enumeration code "lifted" from NTF.EXE
*               sample. For more complete documentation have a look a
*               that.
*
\******************************************************************************/

#include <windows.h>
#include <string.h>
#include "common.h"
#include "paint.h"
#include "resource.h"

/******************************************************************************\
*
*  FUNCTION:    Paint
*
*  INPUTS:      hdc    - device context to paint
*               lpRect - bounding rectangle of device to paint
*
*  RETURNS:     TRUE if painting went ok, or
*               FALSE if error while painting
*
\******************************************************************************/

BOOL Paint (HDC hdc, LPRECT lpRect)
{
RECT  rect;
HPEN  hpenSave;
RECTI ri;

if (gdwGraphicsOptions & DRAWAXIS)
{
//
// draw axis
//

rect = *lpRect;

hpenSave = SelectObject (hdc, GetStockObject (BLACK_PEN));

//
// if giMapMode == MM_TEXT, MM_ANISOTROPIC then (0,0) is upper left corner
//

if (giMapMode == MM_TEXT || giMapMode == MM_ANISOTROPIC)
{
MoveToEx (hdc, rect.left - glcyMenu/2, rect.bottom, NULL);
LineTo   (hdc, rect.left,  rect.bottom + glcyMenu/2);
LineTo   (hdc, rect.left + glcyMenu/2,  rect.bottom);

MoveToEx (hdc, rect.left,  rect.bottom + glcyMenu/2, NULL);
LineTo   (hdc, rect.left,  rect.top);
LineTo   (hdc, rect.right + glcyMenu/2, rect.top);

MoveToEx (hdc, rect.right, rect.top - glcyMenu/2, NULL);
LineTo   (hdc, rect.right + glcyMenu/2,  rect.top);
LineTo   (hdc, rect.right,  rect.top + glcyMenu/2);
}

//
// else (0,0) is lower left corner
//

else
{
MoveToEx (hdc, rect.left - glcyMenu/2, rect.bottom, NULL);
LineTo   (hdc, rect.left,  rect.bottom + glcyMenu/2);
LineTo   (hdc, rect.left + glcyMenu/2,  rect.bottom);

MoveToEx (hdc, rect.left,  rect.bottom + glcyMenu/2, NULL);
LineTo   (hdc, rect.left,  rect.top);
LineTo   (hdc, rect.right + glcyMenu/2, rect.top);

MoveToEx (hdc, rect.right, rect.top + glcyMenu/2, NULL);
LineTo   (hdc, rect.right + glcyMenu/2, rect.top);
LineTo   (hdc, rect.right, rect.top - glcyMenu/2);
}

SelectObject (hdc, hpenSave);
}

//
// look at bits in gdwGraphicsOptions & determine which graphics to draw
//

if (gdwGraphicsOptions & ENUMFONTS)
{
DrawFonts (hdc, lpRect);
return TRUE;
}

giDeltaX = (int) ((lpRect->right - BORDER) / NUM_GRAPHICS_XSLOTS);
giDeltaY = (int) ((lpRect->bottom - lpRect->top - BORDER)
/ NUM_GRAPHICS_YSLOTS);

GetFirstGraphicSlot (lpRect, &ri);

if (gdwGraphicsOptions & ARC)
{
Arc (hdc, ri.left, ri.top, ri.right, ri.bottom,
ri.left, ri.top, ri.right-10, ri.bottom-10);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & ELLIPSE)
{
Ellipse (hdc, ri.left, ri.top, ri.right, ri.bottom);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & LINETO)
{
int i;

for (i = PS_SOLID; i <= PS_DASHDOTDOT; i++)
{
MoveToEx (hdc, ri.left, ri.top + (i+1)*giDeltaY/7, NULL);
LineTo   (hdc, ri.right, ri.top + (i+1)*giDeltaY/7);
}
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & PIE)
{
Pie (hdc, ri.left, ri.top, ri.right, ri.bottom,
ri.left, ri.top, ri.right-10, ri.bottom-10);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & PLG_BLT)
{
HBITMAP hbm;
BITMAP  bm;
HDC     hdcMem;
POINT   ap[3];

hbm = LoadBitmap (ghInst, "printer");
hdcMem = CreateCompatibleDC (hdc);
SelectObject (hdcMem, hbm);

GetObject (hbm, sizeof(BITMAP), (LPSTR)&bm);

//
// special case here because PlgBlt requires coordinates
//   of upper left, upper right, & lower left
//

ap[0].x = (LONG) (ri.left + (ri.right - ri.left)/4);
ap[0].y = (LONG) (ri.top + (ri.bottom - ri.top)/4);
ap[1].x = (LONG) ri.right;
ap[1].y = (LONG) ri.top;
ap[2].x = (LONG) ri.left;
ap[2].y = (LONG) ri.bottom;

PlgBlt (hdc, ap, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, NULL, 0, 0);

DeleteDC(hdcMem);
DeleteObject(hbm);
GetNextGraphicSlot (&ri);

}

if (gdwGraphicsOptions & POLYBEZIER)
{
POINT ap[4];

ap[0].x  = (LONG) ri.left;    ap[0].y = (LONG) ri.top;
ap[1].x  = (LONG) ri.left;    ap[1].y = (LONG) ri.bottom;
ap[2].x  = (LONG) ri.right;   ap[2].y = (LONG) ri.top;
ap[3].x  = (LONG) ri.right;   ap[3].y = (LONG) ri.bottom;

PolyBezier (hdc, ap, 4);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & POLYGON)
{
POINT ap[5];

ap[0].x  = (LONG) ri.left;    ap[0].y = (LONG) ri.top;
ap[1].x  = (LONG) ri.right;   ap[1].y = (LONG) ri.bottom;
ap[2].x  = (LONG) ri.right;   ap[2].y = (LONG) ri.top;
ap[3].x  = (LONG) ri.left;    ap[3].y = (LONG) ri.bottom;
ap[4].x  = (LONG) ri.left;    ap[4].y = (LONG) ri.top;

Polygon (hdc, ap, 4);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & POLYLINE)
{
POINT ap[4];

ap[0].x  = (LONG) ri.left;    ap[0].y = (LONG) ri.top;
ap[1].x  = (LONG) ri.left;    ap[1].y = (LONG) ri.bottom;
ap[2].x  = (LONG) ri.right;   ap[2].y = (LONG) ri.top;
ap[3].x  = (LONG) ri.right;   ap[3].y = (LONG) ri.bottom;

Polyline (hdc, ap, 4);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & POLYPOLYGON)
{
POINT ap[8];
int ai[2] = { 4, 4 };

ap[0].x = (LONG)  ri.left;
ap[0].y = (LONG)  ri.top;
ap[1].x = (LONG) (ri.left + giDeltaX/4);
ap[1].y = (LONG)  ri.top;
ap[2].x = (LONG) (ri.left + giDeltaX/4);
ap[2].y = (LONG) (ri.top  + giDeltaY/4);
ap[3].x = (LONG)  ri.left;
ap[3].y = (LONG) (ri.top  + giDeltaY/4);
ap[4].x = (LONG) (ri.right  - 2*giDeltaX/3);
ap[4].y = (LONG) (ri.bottom - 2*giDeltaY/3);
ap[5].x = (LONG)  ri.right;
ap[5].y = (LONG) (ri.bottom - 2*giDeltaY/3);
ap[6].x = (LONG)  ri.right;
ap[6].y = (LONG)  ri.bottom;
ap[7].x = (LONG) (ri.right  - 2*giDeltaX/3);
ap[7].y = (LONG)  ri.bottom;

PolyPolygon (hdc, ap, ai, 2);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & RECTANGLE)
{
Rectangle (hdc, ri.left, ri.top, ri.right, ri.bottom);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & ROUNDRECT)
{
RoundRect (hdc, ri.left, ri.top, ri.right, ri.bottom, 15, 25);
GetNextGraphicSlot (&ri);
}

if (gdwGraphicsOptions & STRETCH_BLT)
{
HBITMAP hbm;
BITMAP  bm;
HDC     hdcMem;

hbm = LoadBitmap (ghInst, "printer");
hdcMem = CreateCompatibleDC (hdc);
SelectObject (hdcMem, hbm);

GetObject (hbm, sizeof(BITMAP), (LPSTR)&bm);

StretchBlt (hdc, ri.left, ri.top, ri.right-ri.left,
ri.bottom - ri.top, hdcMem, 0, 0,
bm.bmWidth, bm.bmHeight, SRCCOPY);

DeleteDC(hdcMem);
DeleteObject(hbm);
GetNextGraphicSlot (&ri);
}

return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    GetFirstGraphicSlot
*
*  INPUTS:      pri - pointer to a RECTI
*
\******************************************************************************/

void GetFirstGraphicSlot (LPRECT lpRect, PRECTI pri)
{
  pri->left   = BORDER;
  pri->top    = lpRect->top + BORDER;
  pri->right  = giDeltaX;
  pri->bottom = giDeltaY;

  giColumn = 1;
}

/******************************************************************************\
*
*  FUNCTION:    GetNextGraphicSlot
*
*  INPUTS:      pri - pointer to a RECTI
*
\******************************************************************************/

void GetNextGraphicSlot (PRECTI pri)
{
  if (++giColumn <= NUM_GRAPHICS_XSLOTS)
  {
    pri->left  += giDeltaX;
    pri->right += giDeltaX;
  }
  else
  {
    giColumn = 1;

    pri->left   =  BORDER;
    pri->top    += giDeltaY;
    pri->right  =  giDeltaX;
    pri->bottom += giDeltaY;
  }
}

/******************************************************************************\
*
*  FUNCTION:    DrawFonts
*
*  INPUTS:      hdc   - device context to enumerate from & draw on
*               pRect - pointer to bounding rect to draw fonts in
*
*  LOCAL VARS:  i, j       - loop variables
*               xText      - starting x position to draw text
*               yText      - starting y position to draw text
*               iMaxStrLen - length in pels of string to draw
*
\******************************************************************************/

void DrawFonts (HDC hdc, LPRECT lpRect)
{
  int      i, j, xText, yText, iMaxStrLen = 0;
  PARFONTS paf;

  paf = BuildFontList (hdc);

  xText = yText = 2;

  //
  // set the appropriate text align mode depending on
  //   whether we're drawing from top down or bottom up
  //

  if (giMapMode == MM_TEXT | giMapMode == MM_ANISOTROPIC)

    SetTextAlign (hdc, TA_TOP);

  else

    SetTextAlign (hdc, TA_BOTTOM);

  for (i = 0; i < nFaces; i++)
  {
    for (j = 0; j < (paf + i)->nFonts; j++)
    {
      HFONT      hFont, hSaveFont;
      SIZE       size;
      POINT      LogPtExtent;

      LogPtExtent.x = lpRect->right;
      LogPtExtent.y = lpRect->bottom;

      hFont = CreateFontIndirect ((paf + i)->lf + j);

      hSaveFont = SelectObject (hdc, hFont);

      TextOut (hdc, xText, yText, ((paf + i)->lf + j)->lfFaceName,
               strlen(((paf + i)->lf + j)->lfFaceName));

      GetTextExtentPoint (hdc, ((paf + i)->lf + j)->lfFaceName,
                          strlen(((paf+i)->lf+j)->lfFaceName),
                          &size);
      size.cx += 2;
      iMaxStrLen = iMaxStrLen > (int)size.cx ? iMaxStrLen: (int) size.cx;

      if (!(i == (nFaces - 1) && j == ((paf + i)->nFonts - 1)))
      {
        TEXTMETRIC *pNextTM;

        pNextTM = j < ((paf+i)->nFonts-1) ?
                          (paf+i)->tm+j+1 : (paf+i+1)->tm;

        //
        // add in the height of the face name we just drew
        //

        yText += (int) ((paf + i)->tm + j)->tmHeight;

        //
        // if the next facename will be drawn outside the bounding rect then
        //   start at first line of next column
        //

        if (yText + (int) pNextTM->tmHeight > (int) LogPtExtent.y)
        {
          yText = 2;
          xText += iMaxStrLen + 2;
          iMaxStrLen = 0;
        }
      }

      SelectObject (hdc, hSaveFont);
      DeleteObject (hFont);

      if (xText > (int) LogPtExtent.x)
      {
        if (GetDeviceCaps (hdc, TECHNOLOGY) & DT_RASDISPLAY)
        {
          //
          // If we're drawing to the screen & have run out of
          //   room then tell user how many fonts there are left
          //   (that we haven't displayed)
          //

          int   k;
          int   iFontsLeft = (paf + i)->nFonts - j - 1;
          char  buf[40];
          SIZE  size;

          for (k = i + 1; k < nFaces; k++)

            iFontsLeft += (paf + k)->nFonts;

          wsprintf (buf, GetStringRes(IDS_FMT_MOREDSPFNTNL),
                    iFontsLeft);

          GetTextExtentPoint (hdc, buf, strlen(buf), &size);

          if ((xText = lpRect->right - size.cx) < glcyMenu + 1)

            xText = glcyMenu/2 + 1;

          TextOut (hdc, xText, lpRect->bottom, buf, strlen(buf));

          goto done_enumfonts;
        }

        else
        {
          //
          // Else we're drawing to a printer & have filled up
          //   the first page. If there's any fonts left to draw
          //   then start a new page.
          //

          if (!(i == nFaces - 1 && j == (paf + i)->nFonts - 1))
          {
            EndPage   (hdc);
            xText = yText = 2;
            StartPage (hdc);
          }
        }
      }
    }
  }

done_enumfonts:

  FreeFontList (paf);
}

/*  In the callback functions for the enumerations, there is a limited
 *  ability to pass in parameters.  For that reason, declare the following
 *  global variables to be used by any of the call back functions.
 *
 *        HDC      hdcGlobal;
 *        PARFONTS parFontsGlobal;
 *        int      iFace,jFont;
 *        int      nFaces;
 *
 *
 * General call structure:
 *
 *        BuildFontList
 *            EnumFonts
 *                MyEnumCount
 *            LocalAlloc
 *            EnumFonts
 *                MyEnumFaces
 *                    EnumFonts
 *                        MyEnumCount
 *                    LocalAlloc
 *                    LocalAlloc
 *                    LocalAlloc
 *                    EnumFonts
 *                        MyEnumCopy
 */

/******************************************************************************\
*
*  FUNCTION:    BuildFontList
*
*  GLOBAL VARS: (see above)
*
\******************************************************************************/

PARFONTS BuildFontList (HDC hdcIn)
{
  nFaces = 0;

  hdcGlobal = hdcIn;

  //
  // count the total number of face names.
  //

  EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumCount, (LPARAM)&nFaces);

  //
  // allocate the pointer to the array of PArFont structures.
  //

  parFontsGlobal = (PARFONTS) LocalAlloc (LPTR, sizeof(ARFONTS) * (nFaces+1));

  //
  // step through all fonts again.  For each one fill a LOGFONT and
  //   a TEXTMETRIC stucture.
  //

  iFace = 0;
  EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumFaces, 0);

  return parFontsGlobal;
}

/******************************************************************************\
*
*  FUNCTION:    MyEnumFaces
*
*  GLOBAL VARS: (see above)
*
\******************************************************************************/

int CALLBACK MyEnumFaces (LPLOGFONT lpLogFont, LPTEXTMETRIC lpTEXTMETRICs,
                          DWORD fFontType, LPVOID  lpData)
{
  int nFonts;

  nFonts = 0;
  EnumFonts (hdcGlobal, lpLogFont->lfFaceName, (FONTENUMPROC)MyEnumCount,
             (LPARAM)&nFonts);

  parFontsGlobal[iFace].lf   = (LOGFONT *)    LocalAlloc (LPTR,
                                                          sizeof(LOGFONT) *
                                                            nFonts);
  parFontsGlobal[iFace].tm   = (TEXTMETRIC *) LocalAlloc (LPTR,
                                                          sizeof(TEXTMETRIC) *
                                                            nFonts);
  parFontsGlobal[iFace].Type = (int *)        LocalAlloc (LPTR,
                                                          sizeof(int) *
                                                            nFonts);

  if ((parFontsGlobal[iFace].lf   == NULL) ||
      (parFontsGlobal[iFace].tm   == NULL) ||
      (parFontsGlobal[iFace].Type == NULL))
  {
    ErrMsgBox (GetStringRes(IDS_LALLOCFAIL), ERR_MOD_NAME);
    return FALSE;
  }

  parFontsGlobal[iFace].nFonts = nFonts;

  jFont = 0;
  EnumFonts (hdcGlobal, lpLogFont->lfFaceName, (FONTENUMPROC)MyEnumCopy, 0);

  iFace++;

  return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    MyEnumCopy
*
*  GLOBAL VARS: (see above)
*
\******************************************************************************/

int CALLBACK MyEnumCopy (LPLOGFONT lpLogFont, LPTEXTMETRIC lpTEXTMETRICs,
                         DWORD fFontType, LPVOID  lpData)
{
  LOGFONT    *lplf;
  TEXTMETRIC *lptm;
  int        *pType;

  lplf  = parFontsGlobal[iFace].lf;
  lptm  = parFontsGlobal[iFace].tm;
  pType = parFontsGlobal[iFace].Type;

  lplf[jFont]  = *lpLogFont;
  lptm[jFont]  = *lpTEXTMETRICs;
  pType[jFont] = fFontType;

  jFont++;
  return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    MyEnumCount
*
*  GLOBAL VARS: (see above)
*
\******************************************************************************/

int CALLBACK MyEnumCount (LPLOGFONT lpLogFont, LPTEXTMETRIC lpTEXTMETRICs,
                          DWORD fFontType, LPVOID lpData)
{
  (*(LPINT)lpData)++;
  return TRUE;
}

/******************************************************************************\
*
*  FUNCTION:    FreeFontList
*
*  INPUTS:      paf - pointer to ARFONTS struct to free
*
\******************************************************************************/

void FreeFontList (PARFONTS paf)
{
  int i;

  for (i = 0; i < nFaces; i++)
  {
    LocalFree (LocalHandle ((LPSTR) ((paf + i)->lf  )));
    LocalFree (LocalHandle ((LPSTR) ((paf + i)->tm  )));
    LocalFree (LocalHandle ((LPSTR) ((paf + i)->Type)));
  }
  LocalFree (LocalHandle ((LPSTR) paf));
}


PAINT.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                                PAINT.H
*
\******************************************************************************/

/******************************************************************************\
*                           SYMBOLIC CONSTANTS
\******************************************************************************/

#define BORDER                   5

#define NUM_GRAPHICS_XSLOTS      4
#define NUM_GRAPHICS_YSLOTS      3

#define ERR_MOD_NAME              IDS_ERR_PAINT

/******************************************************************************\
*                                TYPEDEFS
\******************************************************************************/

typedef struct
{
  int left;
  int top;
  int right;
  int bottom;

} RECTI, *PRECTI;

typedef struct tagArFonts
{
  int        nFonts;
  int        cySpace;
  HDC        hdc;
  LOGFONT    *lf;
  TEXTMETRIC *tm;
  int        *Type;

} ARFONTS, *PARFONTS;

/******************************************************************************\
*                             GLOBAL VARIABLES
\******************************************************************************/

HDC      hdcGlobal;          // globals utilized by enum fonts funtions
PARFONTS parFontsGlobal;
int      iFace,jFont;
int      nFaces;

int      giDeltaX;           // vars used by Get*GraphicSlot calls
int      giDeltaY;
int      giColumn;

/******************************************************************************\
*                          EXTERNAL VARIABLES
\******************************************************************************/

extern HANDLE ghInst;
extern HWND   ghwndMain;
extern DWORD  gdwGraphicsOptions;
extern int    giMapMode;
extern LONG   glcyMenu;

/******************************************************************************\
*                            FUNCTION PROTOTYPES
\******************************************************************************/

void GetFirstGraphicSlot (LPRECT, PRECTI);
void GetNextGraphicSlot  (PRECTI);

void         DrawFonts     (HDC, LPRECT);
PARFONTS     BuildFontList (HDC);
void         FreeFontList  (PARFONTS);

int CALLBACK MyEnumCount (LPLOGFONT, LPTEXTMETRIC, DWORD, LPVOID);
int CALLBACK MyEnumCopy  (LPLOGFONT, LPTEXTMETRIC, DWORD, LPVOID);
int CALLBACK MyEnumFaces (LPLOGFONT, LPTEXTMETRIC, DWORD, LPVOID);


PRINTER.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  PROGRAM:     PRINTER.C
*
*  PURPOSE:     This is a sample application demostrating some of the new
*               printing functionality in Windows NT. This app allows the
*               user to select between various GDI graphics primitives,
*               to choose pen & brush colors, styles, and sizes, and to
*               the print these graphics to a printer. Also, the app
*               provides the user the ability to query information (reso-
*               lution, etc.) about the various printers & drivers by
*               making calls to GetDeviceCaps, etc.
*
*               Functionality for PRINTER is split into six different
*               modules as follows:
*
*                 printer.c - main event loop
*                             main window procedure
*                             about & abort dialog procedures
*                             printing thread
*
*                 paint.c   - handles all painting printers & most painting
*                             to window
*
*                 enumprt.c - manages the display of information returned
*                             from calling EnumPrinters, EnumPrinterDrivers
*
*                 devcapsx.c- manages the display of information returned
*                             from calling DeviceCapabilitiesEx
*
*                 getpdriv.c- manages the display of information returned
*                             from calling GetPrinterDriver
*
*                 getcaps.c - manages the display of information returned
*                             from calling GetDeviceCaps
*
*  FUNCTIONS:   WinMain               - initialization, create window, msg loop
*               MainWndProc           - processes main window msgs
*               AboutDlgProc          - processes About dlg msgs
*               InvalidateClient      - invalidates graphics part of client wnd
*               RefreshPrinterCombobox- updates list of printers
*               PrintThread           - printing done here
*               AbortProc             - msg loop for abort
*               AbortDlgProc          - processes abort dialog messages
*
\******************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // for _mbstrlen
#include <winspool.h>
#include <commdlg.h>
#include "common.h"
#include "resource.h"
#include "printer.h"

// for internationalization
#define My_mbslen _mbstrlen

/******************************************************************************\
*
*  FUNCTION:    WinMain (standard WinMain INPUTS/RETURNS)
*
*  COMMENTS:    Register & create main window, loop for messages
*
\******************************************************************************/

int WINAPI WinMain (HINSTANCE ghInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                    int    nCmdShow)
{
  MSG msg;

  ghInst = ghInstance;

  if (!hPrevInstance)
  {
    WNDCLASS wc;

    wc.style         = MAIN_CLASS_STYLE;
    wc.lpfnWndProc   = (WNDPROC) MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = ghInst;
    wc.hIcon         = LoadIcon (ghInst, MAKEINTRESOURCE(MAIN_ICON));
    wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = (LPCSTR) MAIN_MENU_NAME;
    wc.lpszClassName = (LPCSTR) MAIN_CLASS_NAME;

    if (!RegisterClass (&wc))
    {
      MessageBox (NULL, "WinMain(): RegisterClass() failed",
                  GetStringRes2(ERR_MOD_NAME), MB_OK | MB_ICONHAND);

      return FALSE;
    }
  }

  if (!(ghwndMain = CreateWindow ((LPCSTR) MAIN_CLASS_NAME,
                                  (LPCSTR) GetStringRes(MAIN_WND_TITLE),
                                  MAIN_WND_STYLE,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  NULL, NULL, ghInst, NULL)))
    return FALSE;

  ShowWindow (ghwndMain, nCmdShow);

  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage  (&msg);
  }

  return msg.wParam;
}

/******************************************************************************\
*
*  FUNCTION:    MainWndProc (standard window procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles main app window msg processing
*
\******************************************************************************/

LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static HMENU hMappingModesSubMenu;
  static HMENU hGraphicsSubMenu;
  static HMENU hPenWidthSubMenu;
  static HMENU hPenStyleSubMenu;
  static HMENU hBrushStyleSubMenu;
  static HWND  hwndCombobox;
  static int   iComboboxWidth;
  static LONG  lTextHeight;

  int i;

  switch (msg)
  {
    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case IDM_PRINT:
        case IDM_PRINTDLG:
        {
          DWORD  threadId;
  HANDLE hThread;

          if (!(hThread = CreateThread (NULL, 0,
                 (LPTHREAD_START_ROUTINE) PrintThread,
                             (LPVOID) wParam, 0, &threadId)))
            MessageBox (hwnd,
                        "MainWndProc(): Error creating print thread",
                        GetStringRes2(ERR_MOD_NAME), MB_OK | MB_ICONHAND);
  else
  CloseHandle(hThread);
          break;
        }

        case IDM_GETDEVICECAPS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) GetDeviceCapsDlgProc);
          break;

        case IDM_ENUMPRINTERS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) EnumPrintersDlgProc);
          break;

        case IDM_GETPRINTERDRIVER:

          if (strcmp (gszDeviceName, "Display"))

            DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                       (DLGPROC) GetPrinterDriverDlgProc);
          else

            MessageBox (hwnd, (LPCTSTR) GetStringRes(IDS_ASKSELPRT),
                        (LPCTSTR) "PRINTER.EXE:", MB_OK);

          break;

        case IDM_ENUMPRINTERDRIVERS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) EnumPrinterDriversDlgProc);
          break;

        case IDM_REFRESH:

          RefreshPrinterCombobox (hwndCombobox);
          break;

        case IDM_ABOUT:

          DialogBox (ghInst, (LPCTSTR) "About", hwnd, (DLGPROC) AboutDlgProc);
          break;

case IDM_EXIT:
DestroyWindow (hwnd);
break;

        case IDM_HIENGLISH:
        case IDM_HIMETRIC:
        case IDM_LOENGLISH:
        case IDM_LOMETRIC:
        case IDM_TWIPS:
        case IDM_ISOTROPIC:
        case IDM_ANISOTROPIC:
        case IDM_TEXT:

          //
          // Uncheck the last map mode menuitem, check the new map mode
          //   menuitem, set iMappingMode according to menu id, cause a
          //   repaint
          //

          for (i = 0; i < MAX_MAP_MODES; i++)

            if (giMapMode == gaMMLookup[i].iMapMode)
            {
              CheckMenuItem (hMappingModesSubMenu, gaMMLookup[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          CheckMenuItem (hMappingModesSubMenu, LOWORD (wParam),
                         MF_CHECKED | MF_BYCOMMAND);

          for (i = 0; i < MAX_MAP_MODES; i++)

            if (LOWORD (wParam) == gaMMLookup[i].wMenuItem)
            {
              giMapMode = gaMMLookup[i].iMapMode;
              break;
            }

          //
          // invalidate the entire client so toolbar text gets updated
          //

          InvalidateRect (hwnd, NULL, TRUE);
          break;

        case IDM_ARC:
        case IDM_ELLIPSE:
        case IDM_LINETO:
        case IDM_PIE:
        case IDM_PLGBLT:
        case IDM_POLYBEZIER:
        case IDM_POLYGON:
        case IDM_POLYLINE:
        case IDM_POLYPOLYGON:
        case IDM_RECTANGLE:
        case IDM_ROUNDRECT:
        case IDM_STRETCHBLT:
        {
          //
          // Retrieve the DWORD flag value for the particular menuitem,
          //   toggle (un/check) the menuitem, set/clear the flag in
          //   gdwGraphicsOptions, cause a repaint
          //

          DWORD dwGraphic;

          for (i = 0; i < MAX_GRAPHICS; i++)

            if (LOWORD (wParam) == gaGraphicLookup[i].wMenuItem)
            {
              dwGraphic = gaGraphicLookup[i].dwGraphic;
              break;
            }

          if (GetMenuState (hGraphicsSubMenu, LOWORD(wParam), MF_BYCOMMAND)
              & MF_CHECKED)

          {
            gdwGraphicsOptions &= ~dwGraphic;
            CheckMenuItem (hGraphicsSubMenu, LOWORD(wParam),
                            MF_UNCHECKED | MF_BYCOMMAND);
          }
          else
          {
            //
            // Clear/uncheck the ENUMFONTS flag/menuitem
            //

            gdwGraphicsOptions &= ~ENUMFONTS;
            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_UNCHECKED | MF_BYCOMMAND);

            gdwGraphicsOptions |= dwGraphic;
            CheckMenuItem (hGraphicsSubMenu, LOWORD(wParam),
                            MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;
        }

        case IDM_ALLGRAPHICS:

          //
          // Clear/uncheck the ENUMFONTS flag/menuitem, set/check all
          //   other graphics flags/menuitems, cause a repaint
          //

          CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                          MF_UNCHECKED | MF_BYCOMMAND);

          for (i = 0; i < MAX_GRAPHICS; i++)

            CheckMenuItem (hGraphicsSubMenu, IDM_ARC + i,
                           MF_CHECKED | MF_BYCOMMAND);

          gdwGraphicsOptions = ALLGRAPHICS | (gdwGraphicsOptions & DRAWAXIS);

          InvalidateClient ();
          break;

        case IDM_NOGRAPHICS:

          //
          // Clear/uncheck all graphics flags/menuitems, cause a repaint
          //

          for (i = 0; i < MAX_GRAPHICS; i++)

            CheckMenuItem (hGraphicsSubMenu, IDM_ARC + i,
                           MF_UNCHECKED | MF_BYCOMMAND);

          gdwGraphicsOptions &= (DRAWAXIS | ENUMFONTS);

          InvalidateClient ();
          break;

        case IDM_ENUMFONTS:

          //
          // Set/clear ENUMFONTS flag, toggle (un/check) menuitem, if
          //   checking IDM_ENUMFONTS then uncheck all other items,
          //   cause a repaint
          //

          if (GetMenuState (hGraphicsSubMenu, IDM_ENUMFONTS, MF_BYCOMMAND)
                & MF_CHECKED)
          {
            gdwGraphicsOptions &= DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_UNCHECKED | MF_BYCOMMAND);
          }

          else
          {
            SendMessage (hwnd, WM_COMMAND, IDM_NOGRAPHICS, 0);

            gdwGraphicsOptions = ENUMFONTS | (gdwGraphicsOptions & DRAWAXIS);

            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;

        case IDM_DRAWAXIS:

          //
          // Set/clear DRAWAXIS flag, toggle (un/check) menuitem,
          //   cause a repaint
          //

          if (GetMenuState (hGraphicsSubMenu, IDM_DRAWAXIS, MF_BYCOMMAND)
                & MF_CHECKED)
          {
            gdwGraphicsOptions &= ~DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_DRAWAXIS,
                           MF_UNCHECKED | MF_BYCOMMAND);
          }

          else
          {
            gdwGraphicsOptions |= DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_DRAWAXIS,
                           MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;

        case IDM_SETPENCOLOR:
        case IDM_SETBRUSHCOLOR:
        case IDM_TEXTCOLOR:
        {
          CHOOSECOLOR cc;

          static DWORD adwCustColors[16];

          memset ((void *) &cc, 0, sizeof (CHOOSECOLOR));

          cc.lStructSize  = sizeof (CHOOSECOLOR);
          cc.hwndOwner    = hwnd;
          cc.Flags        = CC_RGBINIT;
          cc.lpCustColors = adwCustColors;

          if (LOWORD (wParam) == IDM_SETPENCOLOR)

            cc.rgbResult = gdwPenColor;

          else if (LOWORD (wParam) == IDM_SETBRUSHCOLOR)

            cc.rgbResult = gdwBrushColor;

          else

            cc.rgbResult = gdwTextColor;

          //
          // bring up choose color common dialog
          //

          ChooseColor (&cc);

          if (LOWORD (wParam) == IDM_SETPENCOLOR)

            gdwPenColor   = cc.rgbResult;

          else if (LOWORD (wParam) == IDM_SETBRUSHCOLOR)

            gdwBrushColor = cc.rgbResult;

          else

            gdwTextColor  = cc.rgbResult;

          InvalidateClient ();
          break;
        }

        case IDM_PENWIDTH_1:
        case IDM_PENWIDTH_2:
        case IDM_PENWIDTH_3:
        case IDM_PENWIDTH_4:
        case IDM_PENWIDTH_5:
        case IDM_PENWIDTH_6:
        case IDM_PENWIDTH_7:
        case IDM_PENWIDTH_8:

          //
          // uncheck old pen width menuitem, check new one, cause a repaint
          //

          for (i = 0; i < MAX_PENWIDTHS; i++)

            if (giPenWidth == gaPenWidths[i].iPenWidth)
            {
              CheckMenuItem (hPenWidthSubMenu, gaPenWidths[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          for (i = 0; i < MAX_PENWIDTHS; i++)

            if (LOWORD(wParam) == gaPenWidths[i].wMenuItem)
            {
              CheckMenuItem (hPenWidthSubMenu, gaPenWidths[i].wMenuItem,
                             MF_CHECKED | MF_BYCOMMAND);

              giPenWidth = gaPenWidths[i].iPenWidth;

              break;
            }

          InvalidateClient ();
          break;

        case IDM_PENCOLOR_SOLID:
        case IDM_PENCOLOR_DASH:
        case IDM_PENCOLOR_DOT:
        case IDM_PENCOLOR_DASHDOT:
        case IDM_PENCOLOR_DASHDOTDOT:
        case IDM_PENCOLOR_NULL:
        case IDM_PENCOLOR_INSIDEFRAME:

          //
          // uncheck old pen style menuitem, check new one, cause a repaint
          //

          for (i = 0; i < MAX_PENSTYLES; i++)

            if (giPenStyle == gaPenStyles[i].iPenStyle)
            {
              CheckMenuItem (hPenStyleSubMenu, gaPenStyles[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          for (i = 0; i < MAX_PENSTYLES; i++)

            if (LOWORD(wParam) == gaPenStyles[i].wMenuItem)
            {
              CheckMenuItem (hPenStyleSubMenu, gaPenStyles[i].wMenuItem,
                             MF_CHECKED | MF_BYCOMMAND);

              giPenStyle = gaPenStyles[i].iPenStyle;

              break;
            }

          InvalidateClient ();
          break;

        case IDM_BRUSHSTYLE_HORIZONTAL:
        case IDM_BRUSHSTYLE_VERTICAL:
        case IDM_BRUSHSTYLE_FDIAGONAL:
        case IDM_BRUSHSTYLE_BDIAGONAL:
        case IDM_BRUSHSTYLE_CROSS:
        case IDM_BRUSHSTYLE_DIAGCROSS:

          for (i = 0; i < MAX_BRUSHSTYLES; i++) {

// Uncheck the old option
if (giBrushStyle == gaBrushStyles[i].iBrushStyle) {
CheckMenuItem (hBrushStyleSubMenu, gaBrushStyles[i].wMenuItem,
   MF_UNCHECKED | MF_BYCOMMAND);
}

// Check the new option
if (LOWORD(wParam) == gaBrushStyles[i].wMenuItem) {

CheckMenuItem (hBrushStyleSubMenu, gaBrushStyles[i].wMenuItem,
   MF_CHECKED | MF_BYCOMMAND);

giBrushStyle = gaBrushStyles[i].iBrushStyle;
}
}

          InvalidateClient ();
          break;

        case ID_COMBOBOX:

          switch (HIWORD(wParam))
          {
            case CBN_SELCHANGE:
            {
              DWORD dwIndex;
              char  buf[BUFSIZE];

              //
              // User clicked on one of the items in the toolbar combobox;
              //   figure out which item, then parse the text apart and
              //   copy it to the gszDriverName, gszDeviceName, and gszPort
              //   variables.
              //

              dwIndex = (DWORD) SendMessage ((HWND) lParam,
                                             CB_GETCURSEL, 0, 0);
              SendMessage ((HWND) lParam, CB_GETLBTEXT, dwIndex,
                           (LONG) buf);

              if (!strcmp (buf, "Display"))
              {
                strcpy (gszDeviceName, "Display");

                gszPort[0]       =
                gszDriverName[0] = '\0';
              }
              else
              {
                LPSTR   lpszSrc;
                LPSTR   lpszDst;

                for (lpszSrc = buf, lpszDst = gszDeviceName;
                    *lpszSrc && *lpszSrc != ';';    ) {
                    if (IsDBCSLeadByte(*lpszSrc)) {
                        *lpszDst++ = *lpszSrc++;
                    }
                    *lpszDst++ = *lpszSrc++;
                }
                *lpszDst = '\0';

                for (lpszSrc++, lpszDst = gszPort;
                    *lpszSrc && *lpszSrc != ';';    ) {
                    if (IsDBCSLeadByte(*lpszSrc)) {
                        *lpszDst++ = *lpszSrc++;
                    }
                    *lpszDst++ = *lpszSrc++;
                }
                *lpszDst = '\0';

                for (lpszSrc++, lpszDst = gszDriverName; *lpszSrc;    ) {
                    if (IsDBCSLeadByte(*lpszSrc)) {
                        *lpszDst++ = *lpszSrc++;
                    }
                    *lpszDst++ = *lpszSrc++;
                }
                *lpszDst = '\0';
              }
              break;
            }
          }
          break;
      }
      break;

    case WM_PAINT:
    {
      PAINTSTRUCT ps;
      RECT        rect;
      HRGN        hrgn;
      HPEN        hpen, hpenSave;
      HBRUSH      hbr;
      char        buf[BUFSIZE];
      POINT       p;

      BeginPaint (hwnd, &ps);

      //
      // paint 3d toolbar background & client size text
      //

      GetClientRect (hwnd, &rect);
      rect.bottom = 2*glcyMenu;
      FillRect (ps.hdc, &rect, GetStockObject (LTGRAY_BRUSH));
      SelectObject (ps.hdc, GetStockObject (WHITE_PEN));
      MoveToEx (ps.hdc, 0, 2*glcyMenu - 2, NULL);
      LineTo   (ps.hdc, 0, 0);
      LineTo   (ps.hdc, (int) rect.right, 0);
      hpen = CreatePen (PS_SOLID, 1, 0x808080);
      hpenSave = SelectObject (ps.hdc, hpen);
      MoveToEx (ps.hdc, 0, (int) 2*glcyMenu-1, NULL);
      LineTo   (ps.hdc, (int) rect.right - 1, (int) 2*glcyMenu-1);
      LineTo   (ps.hdc, (int) rect.right - 1, 1);
      SelectObject (ps.hdc, hpenSave);
      DeleteObject (hpen);

      GetClientRect (hwnd, &rect);

      //
      // positioning of the string based upon x,y,cx,cy of combobox
      //

      SetBkMode (ps.hdc, TRANSPARENT);

      p.x = rect.right;
      p.y = (rect.bottom - 2*glcyMenu < 0 ? 0 : rect.bottom - 2*glcyMenu);
      SetMapMode (ps.hdc, giMapMode);
      DPtoLP (ps.hdc, &p, 1);

      if ((giMapMode != MM_TEXT) && (giMapMode != MM_ANISOTROPIC))

        //
        // p.y will come out negative because we started with origin in
        //   upper left corner
        //

        p.y = -p.y;

      SetMapMode (ps.hdc, MM_TEXT);
      sprintf (buf, "cxClient = %ld (%ld)", rect.right, p.x);
      TextOut (ps.hdc, iComboboxWidth + (int) 3*glcyMenu/2, (int) glcyMenu/8,
               buf, strlen (buf));
      sprintf (buf, "cyClient = %ld (%ld)",
               rect.bottom - 2*glcyMenu < 0 ? 0 : rect.bottom - 2*glcyMenu,
               p.y);
      TextOut (ps.hdc, iComboboxWidth + (int) 3*glcyMenu/2,
               (int) (glcyMenu/8 + lTextHeight),
               buf, strlen (buf));

      //
      // paint graphics background white
      //

      rect.top    += 2*glcyMenu;
      FillRect (ps.hdc, &rect, GetStockObject (WHITE_BRUSH));

      if (rect.bottom <= 4*glcyMenu)

        //
        // we don't want to overpaint the toolbar, so just skip Paint() call
        //

        goto done_painting;

      //
      // set up a clip region so we don't draw all over our toolbar
      //

      GetClientRect (hwnd, &rect);
      rect.top += 2*glcyMenu;
      hrgn = CreateRectRgnIndirect (&rect);
      SelectClipRgn (ps.hdc, hrgn);
      DeleteObject (hrgn);

      //
      // set up view port, pens/brushes, & map mode, then paint
      //

      rect.top -= 2*glcyMenu;

      if (giMapMode == MM_TEXT || giMapMode == MM_ANISOTROPIC)

        SetViewportOrgEx (ps.hdc, glcyMenu, 3*glcyMenu, NULL);

      else

        SetViewportOrgEx (ps.hdc, glcyMenu, rect.bottom - glcyMenu, NULL);

      rect.bottom -= 4*glcyMenu;
      rect.right  -= 2*glcyMenu;

      hpen = CreatePen (giPenStyle, giPenWidth, gdwPenColor);
      SelectObject (ps.hdc, hpen);
      hbr  = CreateHatchBrush (giBrushStyle, gdwBrushColor);
      SelectObject (ps.hdc, hbr);

      SetTextColor (ps.hdc, gdwTextColor);

      SetMapMode (ps.hdc, giMapMode);
      Paint      (ps.hdc, &rect);

      DeleteObject (hpen);
      DeleteObject (hbr);

done_painting:

      EndPaint (hwnd, &ps);
      break;
    }

    case WM_CREATE:
    {
      HDC         hdc;
      TEXTMETRIC  tm;
      SIZE        size;
      HMENU       hmenu, hPenSubMenu, hBrushSubMenu;

      //
      // initialize the globals
      //

      glcyMenu = (LONG) GetSystemMetrics (SM_CYMENU);

      hmenu                = GetMenu (hwnd);
      hMappingModesSubMenu = GetSubMenu (hmenu, 2);
      hGraphicsSubMenu     = GetSubMenu (hmenu, 3);
      hPenSubMenu          = GetSubMenu (hmenu, 4);
      hPenWidthSubMenu     = GetSubMenu (hPenSubMenu, 1);
      hPenStyleSubMenu     = GetSubMenu (hPenSubMenu, 2);
      hBrushSubMenu        = GetSubMenu (hmenu, 5);
      hBrushStyleSubMenu   = GetSubMenu (hBrushSubMenu, 1);

      GetTextMetrics ((hdc = GetDC (hwnd)), &tm);
      lTextHeight = tm.tmHeight;

      //
      // create combobox to display current printers in. the width
      //   is caluculated by getting the text extent of a typical
      //   entry in the listbox.
      //

      #define ASTRING "long  printer  name;long  port  name;long  driver  name"

      GetTextExtentPoint (hdc, ASTRING, sizeof (ASTRING), &size);

      iComboboxWidth = (int) size.cx;

      ReleaseDC (hwnd, hdc);

      hwndCombobox = CreateWindow ((LPCSTR) "COMBOBOX", (LPCSTR) "",
                                   WS_CHILD | WS_VISIBLE | CBS_DROPDOWN | WS_VSCROLL,
                                   (int) glcyMenu/2,
                                   (int) glcyMenu/2 - 2,  // - 2 = fudge factor
                                   iComboboxWidth,
                                   (int) 6*glcyMenu,
                                   hwnd, NULL, ghInst, NULL);

      SetWindowLong (hwndCombobox, GWL_ID, ID_COMBOBOX);

      PostMessage (hwnd, WM_COMMAND,
                   (WPARAM) MAKELONG (IDM_REFRESH, 0),
                   (LPARAM) 0);
      PostMessage (hwnd, WM_COMMAND,
                   (WPARAM) MAKELONG (IDM_POLYPOLYGON, 0),
                   (LPARAM) 0);
      break;
    }

    case WM_DESTROY:

      PostQuitMessage (0);
      break;

    default:

      return (DefWindowProc (hwnd, msg, wParam, lParam));
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    AboutDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles "About" dialog messages
*
\******************************************************************************/

LRESULT CALLBACK AboutDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                               LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:

      return TRUE;

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case IDOK:

          EndDialog (hwnd, TRUE);

          return 1;
      }
      break;
  }
  return 0;
}

/******************************************************************************\
*
*  FUNCTION:    InvalidateClient
*
*  COMMENTS:    Eliminates the flashing of the toolbar when we redraw
*
\******************************************************************************/

void InvalidateClient ()
{
  RECT rect;

  GetClientRect (ghwndMain, &rect);

  rect.top += 2*glcyMenu;

  InvalidateRect (ghwndMain, &rect, TRUE);
}

/******************************************************************************\
*
*  FUNCTION:    RefreshPrinterCombobox
*
*  INPUTS:      hwndCombobox- handle of the toolbar combobox
*
*  COMMENTS:    The idea here is to enumerate all printers & list them in
*               then combobox in the form: "DEVICE_NAME;PORT;DRIVER_NAME".
*               Then later, when a user selects one of these, we just
*               query out the string & parse it apart, sticking the
*               appropriate parts into the DriverName, DeviceName, and
*               Port variables.
*
*               Also, the "Display" option is added to the combobox so
*               that user can get DevCaps info about it.
*
\******************************************************************************/

void RefreshPrinterCombobox (HWND hwndCombobox)
{
  DWORD            dwFlags = PRINTER_ENUM_FAVORITE | PRINTER_ENUM_LOCAL;
  LPPRINTER_INFO_2 pPrinters;
  DWORD            cbPrinters;
  DWORD            cReturned, i;
  char             buf[256];

  SendMessage (hwndCombobox, CB_RESETCONTENT, 0, 0);

  //
  // add the "Display" option to the combobox
  //

  strcpy (buf, "Display");
  SendMessage (hwndCombobox, CB_INSERTSTRING, (UINT)-1, (LONG) buf);

  //
  // get byte count needed for buffer, alloc buffer, the enum the printers
  //

  EnumPrinters (dwFlags, NULL, 2, NULL, 0, &cbPrinters,
                &cReturned);

  if (!(pPrinters = (LPPRINTER_INFO_2) LocalAlloc (LPTR, cbPrinters + 4)))
  {
    MessageBox (ghwndMain, (LPCTSTR) GetStringRes(IDS_LALLOCFAIL),
                (LPCTSTR)GetStringRes2(ERR_MOD_NAME), MB_OK | MB_ICONEXCLAMATION);
    goto done_refreshing;
  }

  if (!EnumPrinters (dwFlags, NULL, 2, (LPBYTE) pPrinters,
                     cbPrinters, &cbPrinters, &cReturned))
  {
    MessageBox (ghwndMain, (LPCTSTR) GetStringRes(IDS_ENUMPRTFAIL),
                (LPCTSTR) GetStringRes2(ERR_MOD_NAME), MB_OK | MB_ICONEXCLAMATION);
    goto done_refreshing;
  }

  if (cReturned > 0)

    for (i = 0; i < cReturned; i++)
    {
      //
      // for each printer in the PRINTER_INFO_2 array: build a string that
      //   looks like "DEVICE_NAME;PORT;DRIVER_NAME"
      //

      strcpy (buf, (pPrinters + i)->pPrinterName);
      strcat (buf, ";");
      strcat (buf, (pPrinters + i)->pPortName);
      strcat (buf, ";");
      strcat (buf, (pPrinters + i)->pDriverName);

      SendMessage (hwndCombobox, CB_INSERTSTRING, (UINT)-1, (LONG) buf);
    }

  else

    MessageBox (ghwndMain, GetStringRes(IDS_NOPRTLST), "PRINTER.EXE", MB_OK);

done_refreshing:

  SendMessage (hwndCombobox, CB_SELECTSTRING, (UINT) -1, (LONG) buf);

  PostMessage (ghwndMain, WM_COMMAND,
               (WPARAM) MAKELONG (ID_COMBOBOX, CBN_SELCHANGE),
               (LPARAM) hwndCombobox);

  LocalFree (LocalHandle (pPrinters));
}

/******************************************************************************\
*
*  FUNCTION:    PrintThread
*
*  INPUTS:      wParam - wParam of a WM_COMMAND message containing menuitem id

*
*  COMMENTS:    This is the code for the print thread created when the user
*               selects the "Print" or "PrintDlg" menuitems. A thread is used
*               here more demostration purposes only, since we really don't
*               have any background processing to do. A real app would want
*               to have alot more error checking here (e.g. check return of
*               StartDoc, StartPage...).
*
\******************************************************************************/

void PrintThread (LPVOID wParam)
{
DOCINFO di;
RECT    rect;
HPEN    hpen;
HBRUSH  hbr;

switch (LOWORD((WPARAM) wParam)) {
    case IDM_PRINT:
{
if (!strcmp (gszDeviceName, "Display"))
{
MessageBox (ghwndMain, GetStringRes(IDS_ASKSELPRT),
                    "PRINTER.EXE:", MB_OK);
return;
}
else if (!(ghdc = CreateDC (gszDriverName, gszDeviceName, gszPort, NULL)))
{
MessageBox (ghwndMain, "PrintThread(): CreateDC() failed",
                    GetStringRes2(ERR_MOD_NAME), MB_OK);
return;
}
break;
}

    case IDM_PRINTDLG:
{
PRINTDLG  pd;

//
// Initialize a PRINTDLG struct and call PrintDlg to allow user to
//   specify various printing options...
//

memset ((void *) &pd, 0, sizeof(PRINTDLG));

pd.lStructSize = sizeof(PRINTDLG);
pd.hwndOwner   = ghwndMain;
pd.Flags       = PD_RETURNDC;
pd.hInstance   = NULL;

PrintDlg(&pd);
ghdc = pd.hDC;

if (pd.hDevMode)

GlobalFree (pd.hDevMode);

if (pd.hDevNames)

GlobalFree (pd.hDevNames);

if (!ghdc)
{
MessageBox (ghwndMain, GetStringRes(IDS_PRTDLGFAIL),
                    GetStringRes2(ERR_MOD_NAME), MB_OK);
return;
}
}
}

//
// put up Abort & install the abort procedure
//

gbAbort = FALSE;
ghwndAbort = CreateDialog (ghInst, (LPCTSTR) "Abort", ghwndMain,
(DLGPROC) AbortDlgProc);
EnableWindow (ghwndMain, FALSE);
SetAbortProc (ghdc, AbortProc);

//
// create & select pen/brush
//

hpen = CreatePen (giPenStyle, giPenWidth, gdwPenColor);
SelectObject (ghdc, hpen);

hbr  = CreateHatchBrush (giBrushStyle, gdwBrushColor);
SelectObject (ghdc, hbr);

SetTextColor (ghdc, gdwTextColor);

SetMapMode (ghdc, giMapMode);

SetRect(&rect, 0,0, GetDeviceCaps (ghdc, HORZRES), GetDeviceCaps (ghdc, VERTRES));

di.cbSize      = sizeof(DOCINFO);
di.lpszDocName = GetStringRes(IDS_PRTTST);
di.lpszOutput  = NULL;

StartDoc  (ghdc, &di);

if (gdwGraphicsOptions) {

Paint (ghdc, &rect);

} else {
LPSTR pBuf = GetStringRes(IDS_BLANKPG);
TextOut (ghdc, 5, 5, (LPCTSTR) pBuf, My_mbslen(pBuf));
}

// If we didn't abort the job
if (!gbAbort)
{
EndPage   (ghdc);
EndDoc    (ghdc);

DestroyWindow (ghwndAbort);
}

// Clean up
DeleteDC  (ghdc);
DeleteObject(hpen);
DeleteObject(hbr);

EnableWindow  (ghwndMain, TRUE);
}

/******************************************************************************\
*
*  FUNCTION:    AbortProc
*
*  COMMENTS:    Standard printing abort proc
*
\******************************************************************************/

BOOL CALLBACK AbortProc (HDC hdc, int error)
{
MSG msg;

while (!gbAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {

if (!IsDialogMessage (ghwndAbort, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}

}

return !gbAbort;
}

/******************************************************************************\
*
*  FUNCTION:    AbortDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles "Abort" dialog messages
*
\******************************************************************************/

LRESULT CALLBACK AbortDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                               LPARAM lParam)
{
switch (msg) {
    case WM_INITDIALOG:
EnableMenuItem (GetSystemMenu (hwnd, FALSE), SC_CLOSE, MF_GRAYED);
return TRUE;

    case WM_COMMAND:   // There's only one
switch (LOWORD (wParam)) {
case DID_CANCEL:
MessageBeep(MB_OK);
gbAbort = TRUE;
AbortDoc (ghdc);
DestroyWindow (hwnd);
break;
}
break;
}
return 0;
}
/******************************************************************************\
*
*  FUNCTION:    GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:    Load the resource string with the ID given, and return a
*               pointer to it.  Notice that the buffer is common memory so
*               the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}

LPTSTR   GetStringRes2 (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


PRINTER.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                                PRINTER.H
*
\******************************************************************************/

#define MAX_MAP_MODES             8    // # items in gaMMLookup
#define MAX_PENWIDTHS             8    // # items in gaPenWidths
#define MAX_PENSTYLES             7    // # items in gaPenStyles
#define MAX_BRUSHSTYLES           6    // # items in gaBrushStyles

#define ERR_MOD_NAME             IDS_ERR_PRINTER

/******************************************************************************\
*                                 TYPEDEFS
\******************************************************************************/

typedef struct tagMAPMODELOOKUP
{
  WORD   wMenuItem;
  int    iMapMode;

} MAPMODELOOKUP;

typedef struct tagGRAPHICLOOKUP
{
  WORD   wMenuItem;
  DWORD  dwGraphic;

} GRAPHICLOOKUP;

typedef struct tagPENWIDTHLOOKUP
{
  WORD   wMenuItem;
  int    iPenWidth;

} PENWIDTHLOOKUP;

typedef struct tagPENSTYLELOOKUP
{
  WORD   wMenuItem;
  int    iPenStyle;

} PENSTYLELOOKUP;

typedef struct tagBRUSHSTYLELOOKUP
{
  WORD   wMenuItem;
  int    iBrushStyle;

} BRUSHSTYLELOOKUP;

typedef struct tagSTATUSBARLOOKUP
{
  WORD   wMenuItem;
  LPCSTR szText;

} STATUSBARLOOKUP;

/******************************************************************************\
*                                GLOBAL VARS
\******************************************************************************/

HANDLE  ghInst;                        // app instance handle
HWND    ghwndMain;                     // main app window handle
HWND    ghwndAbort;                    // Abort dialog handle
LONG    glcyMenu;                      // menu height
DWORD   gdwGraphicsOptions = DRAWAXIS; // current graphic options (to display)
int     giMapMode = MM_TEXT;           // current map mode
HDC     ghdc;                          // device context to print on
BOOL    gbAbort;                       // AbortProc return code

char    gszDeviceName[BUFSIZE];        // current device name
char    gszPort      [BUFSIZE];        // current port
char    gszDriverName[BUFSIZE];        // current driver name

DWORD   gdwPenColor   = 0x00ff00;      // current pen color
DWORD   gdwBrushColor = 0x0000ff;      // current brush color
DWORD   gdwTextColor =  0xff0000;      // current text color

int     giPenStyle   = PS_SOLID;       // current pen style
int     giPenWidth   = 1;              // current pen width
int     giBrushStyle = HS_HORIZONTAL;    // current brush style

MAPMODELOOKUP gaMMLookup[] =

    { { IDM_HIENGLISH  , MM_HIENGLISH   },
      { IDM_HIMETRIC   , MM_HIMETRIC    },
      { IDM_LOENGLISH  , MM_LOENGLISH   },
      { IDM_LOMETRIC   , MM_LOMETRIC    },
      { IDM_TWIPS      , MM_TWIPS       },
      { IDM_ISOTROPIC  , MM_ISOTROPIC   },
      { IDM_ANISOTROPIC, MM_ANISOTROPIC },
      { IDM_TEXT       , MM_TEXT        } };

GRAPHICLOOKUP gaGraphicLookup[] =

    { { IDM_ARC        , ARC         },
      { IDM_ELLIPSE    , ELLIPSE     },
      { IDM_LINETO     , LINETO      },
      { IDM_PIE        , PIE         },
      { IDM_PLGBLT     , PLG_BLT     },
      { IDM_POLYBEZIER , POLYBEZIER  },
      { IDM_POLYGON    , POLYGON     },
      { IDM_POLYLINE   , POLYLINE    },
      { IDM_POLYPOLYGON, POLYPOLYGON },
      { IDM_RECTANGLE  , RECTANGLE   },
      { IDM_ROUNDRECT  , ROUNDRECT   },
      { IDM_STRETCHBLT , STRETCH_BLT } };

PENWIDTHLOOKUP gaPenWidths[] =

    { { IDM_PENWIDTH_1, 1 },
      { IDM_PENWIDTH_2, 2 },
      { IDM_PENWIDTH_3, 3 },
      { IDM_PENWIDTH_4, 4 },
      { IDM_PENWIDTH_5, 5 },
      { IDM_PENWIDTH_6, 6 },
      { IDM_PENWIDTH_7, 7 },
      { IDM_PENWIDTH_8, 8 } };

PENSTYLELOOKUP gaPenStyles[] =

    { { IDM_PENCOLOR_SOLID      , PS_SOLID      },
      { IDM_PENCOLOR_DASH       , PS_DASH       },
      { IDM_PENCOLOR_DOT        , PS_DOT        },
      { IDM_PENCOLOR_DASHDOT    , PS_DASHDOT    },
      { IDM_PENCOLOR_DASHDOTDOT , PS_DASHDOTDOT },
      { IDM_PENCOLOR_NULL       , PS_NULL       },
      { IDM_PENCOLOR_INSIDEFRAME, PS_INSIDEFRAME} };

BRUSHSTYLELOOKUP gaBrushStyles[] =

    { { IDM_BRUSHSTYLE_HORIZONTAL     , HS_HORIZONTAL     },
      { IDM_BRUSHSTYLE_VERTICAL       , HS_VERTICAL       },
      { IDM_BRUSHSTYLE_FDIAGONAL      , HS_FDIAGONAL      },
      { IDM_BRUSHSTYLE_BDIAGONAL      , HS_BDIAGONAL      },
      { IDM_BRUSHSTYLE_CROSS          , HS_CROSS          },
      { IDM_BRUSHSTYLE_DIAGCROSS      , HS_DIAGCROSS      } };

/******************************************************************************\
*                            FUNCTION PROTOTYPES
\******************************************************************************/

LRESULT CALLBACK MainWndProc   (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK AboutDlgProc  (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK AbortDlgProc  (HWND, UINT, WPARAM, LPARAM);

BOOL    CALLBACK AbortProc     (HDC, int);

void    InvalidateClient       (void);
void    RefreshPrinterCombobox (HWND);
void    PrintThread            (LPVOID);


COMMON.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*                             COMMON.H
*
\******************************************************************************/

/******************************************************************************\
*                         SYMBOLIC CONSTANTS
\******************************************************************************/

#define ARC             0x00000001
#define ELLIPSE         0x00000002
#define LINETO          0x00000004
#define PIE             0x00000008
#define PLG_BLT         0x00000010
#define POLYBEZIER      0x00000020
#define POLYGON         0x00000040
#define POLYLINE        0x00000080
#define POLYPOLYGON     0x00000100
#define RECTANGLE       0x00000200
#define ROUNDRECT       0x00000400
#define STRETCH_BLT     0x00000800

#define ALLGRAPHICS     0x00000fff

#define ENUMFONTS       0x40000000
#define DRAWAXIS        0x80000000

#define MAX_GRAPHICS    12

#define BUFSIZE         256

//ERR_MOD_NAME
#define MAIN_MENU_NAME            "MENU"
#define MAIN_CLASS_NAME           "PRINTER"
#define MAIN_WND_TITLE          IDS_MAINWNDTITLE
#define MAIN_CLASS_STYLE          CS_HREDRAW | CS_VREDRAW
#define MAIN_WND_STYLE            WS_OVERLAPPEDWINDOW

/******************************************************************************\
*                            FUNCTION PROTOTYPES
\******************************************************************************/

#define ErrMsgBox(txt,title) MessageBox (ghwndMain, (LPCTSTR) txt,   \
                                         (LPCTSTR) title,            \
                                         MB_OK | MB_ICONEXCLAMATION);

/******************************************************************************\
*                            FUNCTION PROTOTYPES
\******************************************************************************/

BOOL Paint (HDC, LPRECT);

LRESULT CALLBACK DevCapsXDlgProc           (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK EnumPrintersDlgProc       (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK GetDeviceCapsDlgProc      (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK GetPrinterDriverDlgProc   (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK EnumPrinterDriversDlgProc (HWND, UINT, WPARAM, LPARAM);

LPTSTR   GetStringRes (int id);
LPTSTR   GetStringRes2 (int id);


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Printer.RC
//
#define MAIN_ICON                       1
#define IDS_ERR_PRINTER                 16
#define IDS_ERR_PAINT                   17
#define IDS_ERR_GETCAPS                 18
#define IDS_ERR_GETPDRIV                19
#define IDS_ERR_ENUMPRT                 20
#define IDS_MAINWNDTITLE                21
#define IDS_FMT_OPNPRTFAIL              22
#define IDS_LALLOCFAIL                  23
#define IDS_GETPRTDRVFAIL               24
#define IDS_ASKDOMSRVNM                 25
#define IDS_ENUMPRTLALLOCFAIL           26
#define IDS_ENUMPRT1FAIL                27
#define IDS_ENUMPRTDRVRET0              28
#define IDS_FMT_MOREDSPFNTNL            29
#define IDS_GETDCFAIL                   30
#define IDS_FMT_CREDCFAIL               31
#define IDS_ASKSELPRT                   32
#define IDS_ENUMPRTFAIL                 33
#define IDS_NOPRTLST                    34
#define IDS_PRTDLGFAIL                  35
#define IDS_PRTTST                      36
#define IDS_BLANKPG                     37
#define IDM_PRINT                       101
#define IDM_PRINTDLG                    102
#define IDM_GETDEVICECAPS               103
#define IDM_ENUMPRINTERS                104
#define IDM_GETPRINTERDRIVER            105
#define IDM_ENUMPRINTERDRIVERS          106
#define IDM_REFRESH                     107
#define IDM_ABOUT                       108
#define IDM_HIENGLISH                   201
#define IDM_HIMETRIC                    202
#define IDM_LOENGLISH                   203
#define IDM_LOMETRIC                    204
#define IDM_TWIPS                       205
#define IDM_ISOTROPIC                   206
#define IDM_ANISOTROPIC                 207
#define IDM_TEXT                        208
#define IDM_ARC                         301
#define IDM_ELLIPSE                     302
#define IDM_LINETO                      303
#define IDM_PIE                         304
#define IDM_PLGBLT                      305
#define IDM_POLYBEZIER                  306
#define IDM_POLYGON                     307
#define IDM_POLYLINE                    308
#define IDM_POLYPOLYGON                 309
#define IDM_RECTANGLE                   310
#define IDM_ROUNDRECT                   311
#define IDM_STRETCHBLT                  312
#define IDM_ALLGRAPHICS                 313
#define IDM_NOGRAPHICS                  314
#define IDM_ENUMFONTS                   315
#define IDM_DRAWAXIS                    316
#define IDM_SETPENCOLOR                 401
#define IDM_PENWIDTH_1                  402
#define IDM_PENWIDTH_2                  403
#define IDM_PENWIDTH_3                  404
#define IDM_PENWIDTH_4                  405
#define IDM_PENWIDTH_5                  406
#define IDM_PENWIDTH_6                  407
#define IDM_PENWIDTH_7                  408
#define IDM_PENWIDTH_8                  409
#define IDM_PENCOLOR_SOLID              410
#define IDM_PENCOLOR_DASH               411
#define IDM_PENCOLOR_DOT                412
#define IDM_PENCOLOR_DASHDOT            413
#define IDM_PENCOLOR_DASHDOTDOT         414
#define IDM_PENCOLOR_NULL               415
#define IDM_PENCOLOR_INSIDEFRAME        416
#define IDM_SETBRUSHCOLOR               501
#define DID_OK                          501
#define IDM_BRUSHSTYLE_HORIZONTAL       502
#define DID_LISTBOX                     502
#define IDM_BRUSHSTYLE_VERTICAL         503
#define DID_CANCEL                      503
#define IDM_BRUSHSTYLE_FDIAGONAL        504
#define DID_EDITTEXT                    504
#define IDM_BRUSHSTYLE_BDIAGONAL        505
#define IDM_BRUSHSTYLE_CROSS            506
#define IDM_BRUSHSTYLE_DIAGCROSS        507
#define IDM_TEXTCOLOR                   601
#define ID_COMBOBOX                     701
#define IDM_EXIT                        40001

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40002
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


REV.C

//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//  PROGRAM: Rev.c
//
//  PURPOSE: Helper functions for Reversi Sample Game
//
//  FUNCTIONS:
//  finalscore() - Calculate final score
//  legalcheck() - Return 1 if move legal, else return 0
//  makemove() - Capture enemy pieces
//  score() - Calculate the value of board
//  minmax() - Play human move then recursively play computer move
//
//  SPECIAL INSTRUCTIONS: N/A
//
#include <windows.h>
#include <windowsx.h>
#include <process.h>
#include <stdlib.h>
#include "reversi.h"

// declarations of function in reversi.c
VOID NEAR PASCAL paintmove(BYTE b[BoardSize], INT move, INT friendly,
    INT enemy);

// variable declarations
extern INT     moves[61];
extern INT     BestMove[max_depth+2];
extern HWND    hWin;
extern HDC     hDisp;
extern INT     depth;
extern INT     direc[];

//  Indexes for computing scores and whether or not a player has
//  any pieces on the board.  Very order dependant.

BYTE PieceFlags[] = {   0x00 ,      // Ignore sides
            0x00 ,      // Ignore blanks
            0x01 ,      // Human has a piece
            0x02 ,      // Computer has a piece
            };

//
//  The scoring tables are used to evaluate the board
//  position.  The corners of the board change value
//  according to whether a given square is occupied or
//  not.  This can be done dynamically, saving ~ 1K
//  worth of data space but costing an as of yet
//  undetermined performance hit.
//

#define B11     11    // Offsets to particular squares
#define B18     18
#define B81     81
#define B88     88

#define maskb11     0x08    // Masks used for indexing into Scoring tables.
#define maskb18     0x04
#define maskb81     0x02
#define maskb88     0x01

//
//  FUNCTION: finalscore()
//
//  PURPOSE:  Calculate final score
//

INT NEAR PASCAL finalscore(
BYTE b[],
BYTE friendly,
BYTE enemy)
{
    INT i;
    INT count=0;

    for (i=11 ; i<=88 ; i++) {
        if (b[i] == friendly)
            count++;
        else
            if (b[i] == enemy)
                count--;
    }
    if (count > 0)
        return(win + count);
    else
        if (count < 0)
            return(loss + count);
        else
            return(0);
}

//
//  FUNCTION: legalcheck()
//
//  PURPOSE:  Return 1 if move legal, else return 0
//

INT NEAR PASCAL legalcheck(
BYTE b[],
INT move,
BYTE friendly,
BYTE enemy)
{
   INT sq,d;
   INT *p;

   if (b[move] == empty) {
      p=direc;
      while ((d = *p++) != 0) {
          sq=move;
          if (b[sq += d] == enemy) {
             while(b[sq += d] == enemy)
            ;
             if (b[sq] == friendly) return(1);
          }
      }
   }
   return(0);
}

//
//  FUNCTION: makemove()
//
//  PURPOSE:  Capture enemy pieces
//

VOID NEAR PASCAL makemove(
BYTE b[],
INT move,
BYTE friendly,
BYTE enemy)
{
   INT sq,d;
   INT *p;

   if (move != PASS) {
      p=direc;
      while ((d = *p++) != 0) {
          sq=move;
          if (b[sq += d] == enemy) {
            while(b[sq += d] == enemy)
            ;
            if (b[sq] == friendly)
                while(b[sq -= d] == enemy)
                   b[sq]=friendly;
          }
      }
      b[move]=friendly;
   }
}

//
//  FUNCTION: score()
//
//  PURPOSE:  Calculate the value of board
//

INT NEAR PASCAL score(
BYTE b[],
BYTE friendly,
BYTE enemy)
{
    INT *pvalue;
    BYTE *pb;
    INT fpoints=0;
    INT epoints=0;
    INT ecount=0;
    BYTE bv;
    INT v,b11,b18,b81,b88;

    static INT value[79] = {     99, -8,  8,  6,  6,  8, -8, 99,000,
                000, -8,-24, -4, -3, -3, -4,-24, -8,000,
                000,  8, -4,  7,  4,  4,  7, -4,  8,000,
                000,  6, -3,  4,  0,  0,  4, -3,  6,000,
                000,  6, -3,  4,  0,  0,  4, -3,  6,000,
                000,  8, -4,  7,  4,  4,  7, -4,  8,000,
                000, -8,-24, -4, -3, -3, -4,-24, -8,000,
                000, 99, -8,  8,  6,  6,  8, -8, 99,infin};

    static INT value2[79]= {     99, -8,  8,  6,  6,  8, -8, 99,000,
                000, -8,-24,  0,  1,  1,  0,-24, -8,000,
                000,  8,  0,  7,  4,  4,  7,  0,  8,000,
                000,  6,  1,  4,  1,  1,  4,  1,  6,000,
                000,  6,  1,  4,  1,  1,  4,  1,  6,000,
                000,  8,  0,  7,  4,  4,  7,  0,  8,000,
                000, -8,-24,  0,  1,  1,  1,-24, -8,000,
                000, 99, -8,  8,  6,  6,  8, -8, 99,infin};

    pb = &b[11];
    b11 = *pb;
    b18 = b[18];
    b81 = b[81];
    b88 = b[88];

    if ((b11 != empty) || (b18 != empty) || (b81 != empty) || (b88 != empty)) {
    pvalue = value2;

    if (b11 == empty) {
        value2[12-11] = -8;  value2[21-11] = -8;  value2[22-11] = -24;
    } else {
        value2[12-11] = 12;  value2[21-11] = 12;  value2[22-11] = 8;
    }

    if (b18 == empty) {
        value2[17-11] = -8;  value2[28-11] = -8;  value2[27-11] = -24;
    } else {
        value2[17-11] = 12;  value2[28-11] = 12;  value2[27-11] = 8;
    }

    if (b81 == empty) {
        value2[82-11] = -8;  value2[71-11] = -8;  value2[72-11] = -24;
    } else {
        value2[82-11] = 12;  value2[71-11] = 12;  value2[72-11] = 8;
    }

    if (b88 == empty) {
        value2[87-11] = -8;  value2[78-11] = -8;  value2[77-11] = -24;
    } else {
        value2[87-11] = 12;  value2[78-11] = 12;  value2[77-11] = 8;
    }
    } else {
    pvalue = value;
    }

    while((v=*pvalue++) != infin) {
       bv = *pb++;
       if (bv == friendly)
       fpoints += v;
       else if (bv == enemy) {
           epoints += v;
       ecount++;
       }

    }
    if (!ecount)          // any enemy pieces on the board?
       return(win);       // if not, we just won!
    else
       return(fpoints-epoints);
}

//
//  FUNCTION: minmax()
//
//  PURPOSE:  Play human move then recursively play computer move
//

INT NEAR PASCAL minmax(
BYTE b[max_depth + 2][100],
INT move,
BYTE friendly,
BYTE enemy,
INT ply,
INT vmin,
INT vmax)
{
    BYTE *pCurrent, *pPrevious, *pSource, *pDest;
    INT *pMoves;
    INT *pBestMove;
    INT i;
    INT sq, value, cur_move;
    INT count=0;    // not used !

    pPrevious = &b[ply][0];
    pCurrent =  &b[ply+1][0];

    pSource = &b[ply][11];
    pDest =   &b[ply+1][11];
    for (i=11 ; i<=88 ; i++) *pDest++=*pSource++;

    pBestMove = &BestMove[ply];
    if (move == PASS) {
        if (ply == depth) {
            pMoves = moves;
            while((sq = *pMoves++) != 0) {
                if (legalcheck(pCurrent,sq,enemy,friendly))
                    return(score(pCurrent,friendly,enemy));
            }
            return(finalscore(pCurrent,friendly,enemy));
        }
    }
    else {
        if (ply == 0) {
            hDisp = GetDC(hWin);
            paintmove(pCurrent,move,friendly,enemy);
            ReleaseDC(hWin, hDisp);
        }
        else {
            makemove(pCurrent,move,friendly,enemy);
            if (ply == depth) return(score(pCurrent,friendly,enemy));
        }
    }
    pMoves = moves;
    cur_move = PASS;
    *pBestMove = PASS;
    while((sq = *pMoves++) != 0) {
        if (legalcheck(pCurrent,sq,enemy,friendly)) {
           cur_move = sq;
           value = minmax(b,cur_move,enemy,friendly,ply+1,-vmax,-vmin);
           if (value > vmin) {
              vmin = value;
              *pBestMove = cur_move;
              if (value >= vmax) goto cutoff;   // alpha-beta cutoff
           }
        }
    }
    if (cur_move == PASS) {
        if (move == PASS)        // two passes in a row mean game is over
            return(finalscore(pCurrent,friendly,enemy));
        else {
            value = minmax(b,PASS,enemy,friendly,ply+1,-vmax,-vmin);
            if (value > vmin) vmin = value;
        }
    }
cutoff:
    return(-vmin);
}


REVERSI.C

//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
//  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
//
//  PROGRAM: Reversi.c
//
//  PURPOSE: Reversi Sample Game
//
//  FUNCTIONS:
//  UpdateCursor() - Update the cursor position
//  checkdepth() - Update Skill menu items
//  clearboard() - Clear board and set starting pieces
//  RevCreate() - Called on WM_CREATE messages
//  printboard() - Draws game pieces on board
//  ClearMessageTop() - Clears message bar
//  ShowMessageTop() - Displays string in message bar
//  drawboard() - Draws game board
//  RevPaint() - Called on WM_PAINT messages
//  FlashMessageTop() - Sets up system timer for callback procedure
//  RevMessage() - Outputs flashing message at top of window
//  flashsqr() - Flashes game piece
//  RevMouseMove() - Update cursor to show legality of move
//  ShowBestMove() - Update cursor and mouse position to show best move
//  gameover() - Find a human reply to the computers move
//  paintmove() - Make a move and show the results
//  RevMenu() - Called on WM_COMMAND messages
//  RevInit() - Initializes window data and registers window
//  RevMouseClick() - Handle mouse click or keyboard space or CR
//  Next() - Find next square
//  Previous() - Find previous square
//  ShowNextMove() - Show next legal move
//  RevChar() - Called on WM_KEYDOWN messages
//  InverseMessage() - Callback - inverts message bitmap or kills timer
//  ReversiWndProc() - Processes messages for "Reversi" window
//  AboutDlgProc() - processes messages for "About" dialog box
//  WinMain() - calls initialization function, processes message loop
//
//  SPECIAL INSTRUCTIONS: N/A
//

#include <windows.h>
#include <windowsx.h>
#include <process.h>
#include <stdlib.h>
#include "reversi.h"

// Exported procedures called from other modules
LONG APIENTRY ReversiWndProc(HWND, UINT, WPARAM, LONG);
LONG APIENTRY InverseMessage(HWND, UINT, WPARAM, LONG);

// Global variables
PSTR    pDisplayMessage;
HBRUSH  hbrBlack;
HBRUSH  hbrPat;
HBRUSH  hbrWhite;
HBRUSH  hbrRed;
HBRUSH  hbrGreen;
HBRUSH  hbrBlue;
HBRUSH  hbrHuman;
HBRUSH  hbrComputer;
WNDPROC lpprocInverseMessage;
HANDLE  hInst;
HANDLE  curIllegal;
HANDLE  curLegal;
HANDLE  curThink;
HANDLE  curBlank;
BOOL    fThinking = FALSE;
BOOL    fCheated = FALSE;
INT     direc[9] = {9, 10, 11, 1, -1, -9, -10, -11, 0};
WORD     prevCheck;
BYTE    board[max_depth+2][BoardSize];
INT     fPass;
INT     flashtimes;
INT     count;
INT     MessageOn;
INT     charheight;
INT     charwidth;
INT     xscr;
CHAR    strBuf[80];
BOOL    bMouseDownInReversi = FALSE;
INT     xExt;
INT     yExt;
INT     Bx;
INT     By;
INT     ASPECT;
INT     COLOR;
INT     TXMIN;
INT     TYMIN = 45;
INT     dimension;
BOOL    ffirstmove;

CHAR    szReversi[20];
CHAR    szReversiPractice[40];
CHAR    szPass[30];
CHAR    szMustPass[30];
CHAR    szTie[30];
CHAR    szLoss[30];
CHAR    szWon[30];
CHAR    szWonPost[30];
CHAR    szLossPost[30];
CHAR    szAbout[20];
CHAR    szIllegal[70];
CHAR    szNoPass[70];
CHAR    szHelpFile[15];

HANDLE  hAccel;

POINT   MousePos;
FARPROC lpprocAbout;

INT     depth;
INT     BestMove[max_depth+2];
HDC     hDisp;
HWND    hWin;
INT     moves[61] = {11,18,81,88, 13,31,16,61,
                     38,83,68,86, 14,41,15,51,
                     48,84,58,85, 33,36,63,66,
                     34,35,43,46, 53,56,64,65,
                     24,25,42,47, 52,57,74,75,
                     23,26,32,37, 62,67,73,76,
                     12,17,21,28, 71,78,82,87,
                     22,27,72,77,
              0};

// declarations of functions in rev.c
INT NEAR PASCAL minmax(BYTE b[max_depth + 2][100], INT move, INT friendly,
    INT enemy, INT ply, INT vmin, INT vmax);
VOID NEAR PASCAL makemove(BYTE b[], INT move, INT friendly, INT enemy);
INT NEAR PASCAL legalcheck(BYTE b[], INT move, INT friendly, INT enemy);

#define LONG2POINT(l, pt)               ((pt).x = (SHORT)LOWORD(l), (pt).y = (SHORT)HIWORD(l))

//
//  FUNCTION: UpdateCursor()
//
//  PURPOSE:  Update the cursor position
//
//  COMMENTS:
//
//      To use UpdateCursor,  set the global var MousePos.x and MousePos.y
//      and make the call.  The cursor will appear at the new position
//

VOID NEAR PASCAL UpdateCursor(
HWND    hwnd)
{
  POINT curpoint;

  curpoint.x = MousePos.x;
  curpoint.y = MousePos.y;
  ClientToScreen(hwnd, (LPPOINT)&curpoint);
  SetCursorPos(curpoint.x, curpoint.y);
}

//
//  FUNCTION: checkdepth()
//
//  PURPOSE:  Update Skill menu items
//

VOID NEAR PASCAL checkdepth(
HWND hWindow,
WORD  d)
{
  HMENU hMenu;

  hMenu = GetMenu(hWindow);
  CheckMenuItem(hMenu, prevCheck, MF_UNCHECKED);
  CheckMenuItem(hMenu, d, MF_CHECKED);
  prevCheck = d;
}

//
//  FUNCTION: clearboard()
//
//  PURPOSE:  Clear board and set starting pieces
//

VOID NEAR PASCAL clearboard(
BYTE b[max_depth+2][BoardSize])
{
  register INT  i,j;
  INT           k;

  for (i=0; i<=max_depth ; i++)
      for (j=0 ; j<=99 ; j++)
          b[i][j] = edge;

    for (i=0 ; i<=max_depth ; i++)
      {
        for (j=11 ; j<=81 ; j=j+10)
            for (k=j ; k<j+8 ; k++)
                b[i][k] = empty;

        b[i][45]=computer;
        b[i][54]=computer;
        b[i][44]=human;
        b[i][55]=human;
      }
}

//
//  FUNCTION: RevCreate()
//
//  PURPOSE:  Called on WM_CREATE messages
//

VOID NEAR PASCAL RevCreate(
register HWND   hWindow)

{
  register HDC  hDC;
  TEXTMETRIC    charsize;           // characteristics of the characters

  MessageOn   = FALSE;
  hDC = GetDC(hWindow);
  GetTextMetrics(hDC, (LPTEXTMETRIC)&charsize);

  charheight = charsize.tmHeight;
  charwidth = charsize.tmAveCharWidth;

  ReleaseDC(hWindow, hDC);

  if (COLOR == TRUE)
    {
      hbrComputer = hbrBlue;
      hbrHuman = hbrRed;
    }
  else
    {
      hbrComputer = hbrBlack;
      hbrHuman = hbrWhite;
    }

  TXMIN = 45 * ASPECT;

  clearboard(board);

  fPass = PASS;   // Okay to pass on first move
  depth = 1;
  prevCheck = EASY;
  ffirstmove = TRUE;
  checkdepth(hWindow, prevCheck);
}

//
//  FUNCTION: printboard()
//
//  PURPOSE:  Draws game pieces on board
//

VOID NEAR PASCAL printboard(
BYTE b[max_depth+2][BoardSize])

{
  register INT  i,j;
  INT sq;

  for (i=0; i < 8; i++)
    {
      for (j=0; j < 8; j++)
        {
          if ((sq = (INT)b[0][i*10+j+11]) != empty)
            {
              if (sq == computer)
                  SelectObject(hDisp, hbrComputer);
              else
                  SelectObject(hDisp, hbrHuman);

              Ellipse(hDisp,
                      Bx+2*ASPECT+i*xExt,
                      By+2+j*yExt,
                      Bx+2*ASPECT + i*xExt + xExt-4*ASPECT,
                      By+2+j*yExt + yExt-4);
            }
        }
    }
}

//
//  FUNCTION: ClearMessageTop()
//
//  PURPOSE:  Clears message bar
//

VOID NEAR PASCAL ClearMessageTop(
HDC     hDC)

{
  if (MessageOn == TRUE)
    {
      flashtimes = count + 1;
      SelectObject(hDC, (COLOR) ? hbrGreen : hbrWhite);
      PatBlt(hDC, 0, 0, xscr, charheight, PATCOPY);
      MessageOn = FALSE;
    }
}

//
//  FUNCTION: ShowMessageTop()
//
//  PURPOSE:  Displays string in message bar
//

VOID NEAR PASCAL ShowMessageTop(
HDC     hDC,
PSTR    string)

{
#if defined (WIN16)
  INT   dx;
  INT   sizeRet;
  #define WIDTH(x)  sizeRet
#else
  LONG  dx;
  SIZE  sizeRet;
  #define WIDTH(x)  sizeRet.cx
#endif

  pDisplayMessage = string;
  ClearMessageTop(hDC);
  SelectObject(hDC, hbrWhite);
  PatBlt(hDC, 0, 0, xscr, charheight, PATCOPY);
  SetBkMode(hDC, TRANSPARENT);
  GetTextExtentPoint(hDC, (LPSTR)string, lstrlen(string), &sizeRet);
  dx = WIDTH(x);
  TextOut(hDC, (INT)(xscr - dx)/2, 0, (LPSTR)string, lstrlen(string));
  MessageOn = TRUE;
  SetBkMode(hDC, OPAQUE);
}

//
//  FUNCTION: drawboard()
//
//  PURPOSE:  Draws game board
//

VOID NEAR PASCAL drawboard(
BYTE b[max_depth+2][BoardSize])
{
  register INT  i;
  INT           lcx,lcy;
  register INT  xdimension;
  INT           xLineExt,yLineExt;

  yLineExt = 8 * yExt;
  xLineExt = 8 * xExt;
  xdimension = dimension * ASPECT;

  SelectObject(hDisp, hbrBlack);
  PatBlt(hDisp, Bx+2*xdimension, By+2*dimension, xLineExt, yLineExt, PATCOPY);
  //MUnrealizeObject(hbrPat);
  SelectObject(hDisp, hbrPat);
  PatBlt(hDisp, Bx, By, xLineExt, yLineExt, PATCOPY);

  SelectObject(hDisp, hbrBlack);
  for (i=Bx; i <= Bx + xLineExt; i += xExt)
      PatBlt(hDisp, i, By, ASPECT, yLineExt, PATCOPY);

  for (i=By; i <= By + yLineExt; i += yExt)
      PatBlt(hDisp, Bx, i, xLineExt, 1, PATCOPY);

  lcx = Bx+xLineExt;
  lcy = By+yLineExt;

  //MUnrealizeObject(hbrPat);
  SelectObject(hDisp, hbrPat);

  for (i=1; i < xdimension; ++i)
      PatBlt(hDisp, lcx+i, By+i/ASPECT, 1, yLineExt, PATCOPY);

  for (i=1; i < dimension; ++i)   // Fill in bottom edge of puzzle.
      PatBlt(hDisp, Bx+i*ASPECT, lcy+i, xLineExt, 1, PATCOPY);

  SelectObject(hDisp, hbrBlack);

  MoveToEx(hDisp, lcx, By, NULL);
  LineTo(hDisp, lcx+xdimension, By+dimension);
  LineTo(hDisp, lcx+xdimension, lcy+dimension);
  LineTo(hDisp, Bx+xdimension, lcy+dimension);
  LineTo(hDisp, Bx, lcy);
  MoveToEx(hDisp, lcx+xdimension, lcy+dimension, NULL);
  LineTo(hDisp, lcx, lcy);

  printboard(b);
}

//
//  FUNCTION: RevPaint()
//
//  PURPOSE:  Called on WM_PAINT messages
//

VOID NEAR PASCAL RevPaint(
HWND    hWindow,
HDC     hDC)

{
  register INT  Tx, Ty;
  INT           xLineExt, yLineExt;
  RECT          lpSize;

  hDisp = hDC;    // Since it is easy to resize we'll do it on every repaint
  hWin  = hWindow;
  SetBkMode(hDisp, OPAQUE);
  GetClientRect(hWindow, (LPRECT)&lpSize);
  xscr = Tx = lpSize.right - lpSize.left;
  Ty = lpSize.bottom - lpSize.top;

  if (Tx < Ty*ASPECT)    // Dont go below minimum size
    {
      if (Tx < TXMIN)
          Tx = TXMIN;
      xExt = Tx / (9 + 1);
      yExt = xExt / ASPECT;
    }
  else
    {
      if (Ty < TYMIN)
          Ty = TYMIN;
      yExt = Ty / (9 + 1);
      xExt = yExt * ASPECT;
    }
  yLineExt = 8 * yExt;
  xLineExt = 8 * xExt;
  dimension = yLineExt/30;

  Bx = (Tx > xLineExt) ? (Tx - xLineExt) / 2 : 0;
  By = (Ty > yLineExt) ? (Ty - yLineExt) / 2 : 0;

  drawboard(board);

  if (MessageOn)
    {
      ShowMessageTop(hDisp, pDisplayMessage);
      PatBlt(hDC, 0, 0, xscr, charheight, DSTINVERT);
    }
}

//
//  FUNCTION: FlashMessageTop()
//
//  PURPOSE:  Sets up system timer for callback procedure
//

VOID NEAR PASCAL FlashMessageTop(
HWND    hWindow)
{
  flashtimes = 0;
  count = 4;
  SetTimer(hWindow, 666, 200, (WNDPROC)lpprocInverseMessage);    /* Timer ID is 666 */
}

//
//  FUNCTION: RevMessage()
//
//  PURPOSE:  Outputs flashing message at top of window
//

VOID NEAR PASCAL RevMessage(
HWND            hWindow,
HDC             hDC,
register CHAR   *pS,
INT             n,
CHAR            *pchPostStr)
{
  register CHAR *pch;

  pch = strBuf;
  while (*pS)
      *pch++ = *pS++;

  if (n)
    {
      if (n / 10)
          *pch++ = (CHAR)(n / 10 + '0');
      *pch++ = (CHAR)(n % 10 + '0');
    }

  if (pchPostStr)
    {
      while (*pchPostStr)
          *pch++ = *pchPostStr++;
    }
  *pch = '\0';

  ShowMessageTop(hDC, strBuf);
  FlashMessageTop(hWindow);
}

//
//  FUNCTION: flashsqr()
//
//  PURPOSE:  Flashes game piece
//

VOID NEAR PASCAL flashsqr(
register HDC    hDC,
INT             x1,
INT             y1,
INT             Ex,
INT             Ey,
INT             color,
BOOL            fBlankSquare,
INT             n)

{
  register INT  i;

  if (fBlankSquare)
      SelectObject(hDC, GetStockObject(NULL_PEN));

  SetCursor(curBlank);

  for (i=0; i < n; ++i)
    {
      if (color == 1)
          color = 2;
      else
          color = 1;

      if (color == 1)
          SelectObject(hDC,hbrComputer);
      else
          SelectObject(hDC, hbrHuman);

      SetBkMode(hDC, OPAQUE);
      Ellipse(hDC, x1, y1, x1+Ex, y1+Ey);
    }

  if (fBlankSquare)
    {
      //MUnrealizeObject(hbrPat);
      SelectObject(hDC, hbrPat);
      Ellipse(hDC, x1, y1, x1+Ex, y1+Ey);
    }
  else
      SetCursor(curThink);
}

//
//  FUNCTION: RevMouseMove()
//
//  PURPOSE:  Update cursor to show legality of move
//

VOID NEAR PASCAL RevMouseMove(
POINT   point)

{
  INT     move;
  INT     Si, Sj;
  INT     yLineExt = 8 * yExt;
  INT     xLineExt = 8 * xExt;
  HANDLE  cur;

  MousePos.x = point.x;
  MousePos.y = point.y;

  if(xExt ==0 || yExt == 0)
      return;

  cur = curIllegal;

  if ((point.x > Bx) && (point.x < (Bx+xLineExt)) && (point.y > By) && (point.y < (By+yLineExt)))
    {
      Si = (point.x - Bx) / xExt;
      Sj = (point.y - By) / yExt;
      move = Si * 10 + Sj + 11;
      if (legalcheck(board[0], move, human, computer))
          cur = curLegal;
    }
  SetCursor(cur);
}

//
//  FUNCTION: ShowBestMove()
//
//  PURPOSE:  Update cursor and mouse position to show best move
//

VOID NEAR PASCAL ShowBestMove(
HWND hwnd)

{
  HDC           hdc;
  INT           sq;
  register INT  x, y;
  INT           *pMoves;
  BOOL          bDone;

  if (fPass == PASS && !ffirstmove)
      return;

  if (!fCheated)
      SetWindowText(hwnd, (LPSTR)szReversiPractice);

  fCheated = TRUE;
  SetCursor(curThink);
  fThinking = TRUE;

  if (ffirstmove)
    {
      x = 4;    // HACK: Hardcode the first move hint.
      y = 2;
    }
  else
    {
      if (depth == 1)
        {
          bDone = FALSE;
          pMoves = moves;
          sq = *pMoves;
          while (!bDone)
            {
              sq = *pMoves;
              if (legalcheck(board[0], sq, human, computer))
                  bDone = TRUE;
              else
                  pMoves++;
            }
          y = (sq - 11) % 10;
          x = (sq - 11) / 10;
        }
      else
        {
          minmax(board, BestMove[0],  computer, human, 1, -infin, infin);
          y = (BestMove[1] - 11) % 10;
          x = (BestMove[1] - 11) / 10;
        }
    }

  MousePos.x = (x * xExt) + Bx + xExt/2;
  MousePos.y = (y * yExt) + By + yExt/2;

  UpdateCursor(hwnd);

  hdc = GetDC(hwnd);

  x = x * xExt + Bx + 2 * ASPECT;
  y = y * yExt + By + 2;

  flashsqr(hdc, x, y, xExt - 4 * ASPECT, yExt - 4, computer, TRUE, 3);

  fThinking = FALSE;

  ReleaseDC(hwnd, hdc);

  RevMouseMove(MousePos);
}

//
//  FUNCTION: gameover()
//
//  PURPOSE:  Find a human reply to the computers move
//
//  COMMENTS:
//
//      As a side effect set flag fPass if the human
//      has a legal move.
//

VOID NEAR PASCAL gameover(
register HWND   hWindow,
HDC             hDC,
BYTE            b[max_depth + 2][BoardSize],
INT             r)

{
  register INT  i;
  INT           cc;
  INT           hc;
  INT           sq;
  INT           reply2;
  INT           *pMoves;

  pMoves = moves;
  fPass = PASS;
  reply2 = PASS;
  while ((sq = *pMoves++) != 0)
    {
      if (legalcheck(b[0], sq, human, computer))
          fPass = sq;
      else if (legalcheck(b[0], sq, computer, human))
          reply2 = sq;
    }

  if (fPass == PASS)
    {
      if ((r == PASS) || (reply2 == PASS))
        {
          hc = 0;
          cc = 0;
          for (i=11; i <= 88; i++)
            {
              if (b[0][i] == human)
                  hc++;
              else if (b[0][i] == computer)
                  cc++;
            }

          if (hc > cc)
              RevMessage(hWindow, hDC, szWon, hc-cc, szWonPost);
          else if (hc < cc)
              RevMessage(hWindow, hDC, szLoss, cc-hc, szLossPost);
          else
              RevMessage(hWindow, hDC, szTie, 0, NULL);
        }
      else
        {
          RevMessage(hWindow, hDC, szMustPass, 0, NULL);
        }
    }
  else if (r == PASS)
    {
      RevMessage(hWindow, hDC, szPass, 0, NULL);
    }
}

//
//  FUNCTION: paintmove()
//
//  PURPOSE:  Make a move and show the results
//

VOID NEAR PASCAL paintmove(
BYTE    b[BoardSize],
INT     move,
BYTE    friendly,
BYTE    enemy)
{
  INT           d;
  INT           sq;
  INT           *p;
  register INT  i,j;
  INT           color;

  if (move != PASS)
    {
      if (friendly == computer)
        {
          SelectObject(hDisp, hbrComputer);
          color = 1;
        }
      else
        {
          SelectObject(hDisp, hbrHuman);
          color = 2;
        }

      i = ((move - 11) / 10) * xExt + Bx + 2 * ASPECT;
      j = ((move - 11) % 10) * yExt + By + 2;
      Ellipse(hDisp, i, j, i + xExt - 4 * ASPECT, j + yExt - 4);
      flashsqr(hDisp, i, j, xExt - 4 * ASPECT, yExt - 4, color, FALSE, 4);

      p = direc;
      while ((d = *p++) != 0)
        {
          sq=move;
          if (b[sq += d] == enemy)
           {
             while(b[sq += d] == enemy)
                ;
             if (b[sq] == (BYTE)friendly)
               {
                 while(b[sq -= d] == enemy)
                   {
                     board[0][sq] = b[sq] = friendly;
                     i = ((sq - 11)/10)*xExt+Bx+2*ASPECT;
                     j = ((sq - 11)%10)*yExt+By+2;
                     Ellipse(hDisp, i, j, i + xExt-4*ASPECT, j + yExt-4);
                   }
               }
           }
        }
      b[move]=friendly;
    }
}

//
//  FUNCTION: RevMenu()
//
//  PURPOSE:  Called on WM_COMMAND messages
//

VOID NEAR PASCAL RevMenu(
register HWND   hWindow,
INT             idval)

{
  HDC           hDC;
  register INT  cmd;

  if (fThinking)
      return;

  hWin = hWindow;

  switch (idval)
    {
      case EXIT:
          PostMessage(hWindow, WM_CLOSE, 0, 0L);
          break;

      case MN_HELP_ABOUT:
          DialogBox(hInst, MAKEINTRESOURCE(3), hWindow, (WNDPROC)lpprocAbout);
          break;

      case MN_HELP_INDEX:
          //TEMPFIX WinHelp(hWindow, (LPSTR)szHelpFile, HELP_INDEX, 0L);
          break;

      case MN_HELP_USINGHELP:
          //TEMPFIX WinHelp(hWindow, (LPSTR)NULL, HELP_HELPONHELP, 0L);
          break;

      case MN_HELP_KEYBOARD:
          cmd = 0x1e;
          goto HelpCommon;

      case MN_HELP_COMMANDS:
          cmd = 0x20;
          goto HelpCommon;

      case MN_HELP_PLAYING:
          cmd = 0x21;
          goto HelpCommon;

      case MN_HELP_RULES:
          cmd = 0x22;
HelpCommon:
          //TEMPFIX WinHelp(hWindow, (LPSTR)szHelpFile, HELP_CONTEXT, (DWORD)cmd);
          break;

      case HINT:
          ShowBestMove(hWindow);
          return;
          break;

      case NEW:
          SetWindowText(hWindow , (LPSTR)szReversi);
          ffirstmove = TRUE;
          hDisp = hDC = GetDC(hWindow);
          fCheated = FALSE;
          SetBkMode(hDisp, OPAQUE);
          ClearMessageTop(hDC);
          fPass = PASS;
          clearboard(board);
          drawboard(board);
          ReleaseDC(hWindow, hDC);
          hDisp = 0;
          break;

      case EASY:
          depth = 1;                      // MUST BE AT LEAST 1.
          checkdepth(hWindow, EASY);      // KEEP HANDS OFF!
          break;

      case MEDIUM:
          depth = 2;
          checkdepth(hWindow, MEDIUM);
          break;

      case HARD:
          depth = 4;
          checkdepth(hWindow, HARD);
          break;

      case VHARD:
          depth = 6;
          checkdepth(hWindow, VHARD);
          break;

      case PASS:
          if (fPass == PASS)
            {
              hDisp = hDC = GetDC(hWindow);
              SetBkMode(hDisp, OPAQUE);
              fThinking = TRUE;
              ClearMessageTop(hDC);
              SetCursor(curThink);
              ReleaseDC(hWindow, hDC);
              hDisp = 0;
              minmax(board, PASS, human, computer, 0, -infin, infin);
              hDisp = hDC = GetDC(hWindow);
              paintmove(board[0], BestMove[0], (BYTE)computer, (BYTE)human);
              gameover(hWindow, hDC, board, BestMove[0]);
              SetCursor(curIllegal);
              fThinking = FALSE;
              ReleaseDC(hWindow, hDC);
              hDisp = 0;
            }
          else
              MessageBox(hWindow, (LPSTR)szNoPass, (LPSTR)szReversi, MB_OK | MB_ICONASTERISK);
          break;
    }
}

//
//  FUNCTION: RevInit()
//
//  PURPOSE:  Initializes window data and registers window
//

BOOL NEAR PASCAL RevInit(
HANDLE hInstance)

{
  register PWNDCLASS    pRevClass;
  HDC                   hdc;

  hbrWhite = GetStockObject(WHITE_BRUSH);
  hbrBlack = GetStockObject(BLACK_BRUSH);
  hbrPat   = GetStockObject(LTGRAY_BRUSH);

  if (!hbrPat)
      return(FALSE);
  ffirstmove = TRUE;
  hdc = GetDC((HWND)NULL);

  COLOR = GetDeviceCaps(hdc, BITSPIXEL) > 1;

  if (GetDeviceCaps(hdc, VERTRES) == 200)
      ASPECT = 2;
  else
      ASPECT = 1;
  ReleaseDC((HWND)NULL, hdc);

  hbrRed        = (HBRUSH)CreateSolidBrush(RGB(0xFF,0,0));
  hbrGreen      = (HBRUSH)CreateSolidBrush(RGB(0,0xFF,0));
  hbrBlue       = (HBRUSH)CreateSolidBrush(RGB(0,0,0xFF));

  if (!hbrRed || !hbrGreen || !hbrBlue)
      return(FALSE);

  LoadString(hInstance, 3,  (LPSTR)szReversi, 20);
  LoadString(hInstance, 4,  (LPSTR)szReversiPractice, 40);
  LoadString(hInstance, 5,  (LPSTR)szPass, 30);
  LoadString(hInstance, 6,  (LPSTR)szMustPass, 30);
  LoadString(hInstance, 7,  (LPSTR)szTie, 30);
  LoadString(hInstance, 8,  (LPSTR)szLoss, 30);
  LoadString(hInstance, 9,  (LPSTR)szWon, 30);
  LoadString(hInstance, 10, (LPSTR)szAbout, 20);
  LoadString(hInstance, 11, (LPSTR)szLossPost, 30);
  LoadString(hInstance, 12, (LPSTR)szWonPost, 30);
  LoadString(hInstance, 13, (LPSTR)szIllegal, 70);
  LoadString(hInstance, 14, (LPSTR)szNoPass, 70);
  LoadString(hInstance, 15, (LPSTR)szHelpFile, 15);

  hAccel = LoadAccelerators(hInstance, (LPSTR)"MAINACC");
  pRevClass = (PWNDCLASS)((CHAR *)LocalAlloc(LMEM_ZEROINIT, sizeof(WNDCLASS)));
  if (!pRevClass)
      return(FALSE);

  curLegal   = LoadCursor(NULL, IDC_CROSS);
  curIllegal = LoadCursor(NULL, IDC_ARROW);
  curThink   = LoadCursor(NULL, IDC_WAIT);
  curBlank   = LoadCursor(hInstance, MAKEINTRESOURCE(1));
  if (!curLegal || !curIllegal || !curThink || !curBlank)
      return(FALSE);
  pRevClass->hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(3));

  pRevClass->lpszClassName = (LPSTR)"Reversi";
  pRevClass->hbrBackground = ((COLOR) ? hbrGreen : hbrWhite);
  pRevClass->lpfnWndProc   = ReversiWndProc;
  pRevClass->lpszMenuName  = MAKEINTRESOURCE(1);
  pRevClass->hInstance    = hInstance;
  pRevClass->style         = CS_VREDRAW | CS_HREDRAW | CS_BYTEALIGNCLIENT;

  if (!RegisterClass((LPWNDCLASS)pRevClass))
    {
      LocalFree((HANDLE)pRevClass);
      return(FALSE);
    }
  LocalFree((HANDLE)pRevClass);

  return(TRUE);
}

//
//  FUNCTION: RevMouseClick()
//
//  PURPOSE:  Handle mouse click or keyboard space or CR
//

VOID NEAR PASCAL RevMouseClick(
HWND  hWnd,
POINT point)
{
  INT     move;
  INT     Si, Sj;
  INT     yLineExt = 8 * yExt;
  INT     xLineExt = 8 * xExt;
  HDC     hDC;

  MousePos.x = point.x;
  MousePos.y = point.y;

  if (xExt == 0 || yExt == 0)
      return;

  if ((point.x > Bx) && (point.x < (Bx+xLineExt)) && (point.y > By) && (point.y < (By+yLineExt)))
    {
      Si = (point.x - Bx) / xExt;
      Sj = (point.y - By) / yExt;
      move = Si * 10 + Sj + 11;
      if (legalcheck(board[0], move, human, computer))
        {
          board[0][move] = human;
          ffirstmove = FALSE;
          fThinking = TRUE;
          SetCursor(curThink);

          hDisp = hDC = GetDC(hWnd);
          ClearMessageTop(hDC);

          minmax(board, move, human, computer, 0, -infin, infin);
          makemove(board[0], move, human, computer);

          hDisp = hDC;

          paintmove(board[0], BestMove[0], computer, human);
          gameover(hWnd, hDC, board, BestMove[0]);

          ReleaseDC(hWnd, hDC);
          hDisp = 0;

          SetCursor(curIllegal);
          fThinking = FALSE;
        }
      else
          MessageBox(hWnd, (LPSTR)szIllegal, (LPSTR)szReversi, MB_OK | MB_ICONASTERISK);
   }
}

//
//  FUNCTION: Next()
//
//  PURPOSE:  Find next square
//

VOID NEAR PASCAL Next(
register INT *px,
register INT *py)

{
  (*px)++;
  if (*px > 7)
    {
      *px = 0;
      (*py)++;
      if (*py > 7)
          *py = 0;
    }
}

//
//  FUNCTION: Previous()
//
//  PURPOSE:  Find previous square
//

VOID NEAR PASCAL Previous(
register INT *px,
register INT *py)
{
  (*px)--;
  if (*px < 0)
    {
      *px = 7;
      (*py)--;
      if (*py < 0)
          *py = 7;
    }
}

//
//  FUNCTION: ShowNextMove()
//
//  PURPOSE:  Show next legal move
//

VOID NEAR PASCAL ShowNextMove(
HWND    hwnd,
BOOL    fforward)
{
  INT       x, y;
  INT       potentialmove;
  BOOL      done;

  if (fPass == PASS && !ffirstmove)   // Watch out for infinite loops
      return;

  x = (MousePos.x - Bx) / xExt;
  y = (MousePos.y - By) / yExt;

  done = FALSE;
  while (!done)
    {
      do
        {
          if (fforward)
              Next(&x, &y);
          else
              Previous(&x, &y);
        }
      while ((board[0][potentialmove = (x * 10 + y + 11)]) != empty);

      fThinking = TRUE;
      if (legalcheck(board[0], potentialmove, human, computer))
          done = TRUE;

      fThinking = FALSE;
    }

  MousePos.x = x * xExt + Bx + xExt / 2;
  MousePos.y = y * yExt + By + yExt / 2;

  UpdateCursor(hwnd);
  RevMouseMove(MousePos);
}

//
//  FUNCTION: RevChar()
//
//  PURPOSE:  Called on WM_KEYDOWN messages
//

VOID NEAR PASCAL RevChar(
HWND            hwnd,
register WORD   code)
{
  INT   a;
  POINT curpoint;

  curpoint.x = curpoint.y = 1;
  switch (code)
    {
      case 0x27:
          MousePos.x += xExt;
          break;

      case 0x28:
          MousePos.y += yExt;
          break;

      case 0x25:
          curpoint.x = (MousePos.x - Bx)/xExt;
          MousePos.x -= xExt;
          break;

      case 0x26:
          curpoint.y = (MousePos.y - By)/yExt;
          MousePos.y -= yExt;
          break;

      case 0x24:
          curpoint.y = (MousePos.y - By)/yExt;
          curpoint.x = (MousePos.x - Bx)/xExt;
          MousePos.y -= yExt;
          MousePos.x -= xExt;
          break;

      case 0x21:
          curpoint.y = (MousePos.y - By)/yExt;
          MousePos.y -= yExt;
          MousePos.x += xExt;
          break;

      case 0x23:
          curpoint.x = (MousePos.x - Bx)/xExt;
          MousePos.y += yExt;
          MousePos.x -= xExt;
          break;

      case 0x22:
          MousePos.y += yExt;
          MousePos.x += xExt;
          break;

      case 0x0020:
      case 0x000D:
          if (!fThinking)
              RevMouseClick(hwnd, MousePos);
          return;

      case 0x0009:
          if (fThinking)
              break;
          if (GetKeyState(VK_SHIFT) < 0)
              ShowNextMove(hwnd, FALSE);    // look backwards
          else
              ShowNextMove(hwnd, TRUE);     // look forwards
          return;

      default:
          return;
    }

  if (((a = ((MousePos.x - Bx) / xExt)) >7) || a <= 0)
      MousePos.x = Bx + xExt / 2;             /* wrap around horizontally */

  if (a > 8 || (curpoint.x == 0 && a == 0))

MousePos.x = (7*xExt) + Bx + xExt / 2 ;

  if ( ((a = ((MousePos.y - By) / yExt)) >7) || a <= 0)
      MousePos.y = By + yExt / 2;

  if ( a > 8 || (curpoint.y == 0 && a == 0))
      MousePos.y = (7*yExt) + By + yExt / 2;

  MousePos.x = ((MousePos.x - Bx) / xExt) * xExt + Bx + xExt / 2;
  MousePos.y = ((MousePos.y - By) / yExt) * yExt + By + yExt / 2;
  UpdateCursor(hwnd);
  RevMouseMove(MousePos);
}

//
//  FUNCTION: InverseMessage()
//
//  PURPOSE:  Callback - inverts message bitmap or kills timer
//

LONG APIENTRY InverseMessage(
register HWND   hWindow,
UINT            message,
WPARAM          wParam,
LONG            lParam)
{
  HDC   hDC;

  message;
  wParam;
  lParam;

  if (flashtimes <= count)
    {
      hDC = GetDC(hWindow);
      PatBlt(hDC, 0, 0, xscr, charheight, DSTINVERT);
      flashtimes++;
      ReleaseDC(hWindow, hDC);
    }
  else
      KillTimer(hWindow, 666);
  return(0L);
}

//
//  FUNCTION: ReversiWndProc()
//
//  PURPOSE:  Processes messages for "Reversi" window
//

LONG APIENTRY ReversiWndProc(
HWND            hWnd,
register UINT   message,
WPARAM          wParam,
LONG            lParam)
{
  HMENU         hm;
  PAINTSTRUCT   ps;
  POINT         curpoint;

  switch (message)
    {
      case WM_COMMAND:
          RevMenu(hWnd, GET_WM_COMMAND_ID(wParam, lParam));
          break;

      case WM_INITMENU:                 // disable the menu if thinking
          hm = GetMenu(hWnd);
          if (fThinking)
            {
              EnableMenuItem(hm, 0, MF_DISABLED | MF_BYPOSITION);
              EnableMenuItem(hm, 1, MF_DISABLED | MF_BYPOSITION);
            }
          else
            {
              EnableMenuItem(hm, 0, MF_ENABLED | MF_BYPOSITION);
              EnableMenuItem(hm, 1, MF_ENABLED | MF_BYPOSITION);
            }
          break;

      case WM_CREATE:
          RevCreate(hWnd);
          hWin = hWnd;
          break;

      case WM_CLOSE:
          return(DefWindowProc(hWnd, message, wParam, lParam));

      case WM_DESTROY:
          //if (MGetModuleUsage(hInst) == 1)
            {
              // check this because the green brush is used as the class
              // background brush if we are on a color system in which
              // case we don't handle the brush deletion, Windows does
              if (!COLOR)
                DeleteObject(hbrGreen);

              DeleteObject(hbrRed);
              DeleteObject(hbrBlue);
            }

          // In case WinHelp keys off hWindow, we need to do the HELP_QUIT
          // here instead of when there is just one instance of help...
          //
          //TEMPFIX WinHelp(hWnd, (LPSTR)szHelpFile, HELP_QUIT, 0L);

          PostQuitMessage(0);
          break;

      case WM_KEYDOWN:
          if (IsIconic(hWnd))
              return 0L;
          RevChar(hWnd, (WORD)wParam);
          break;

      case WM_ACTIVATE:
          if (!GetSystemMetrics(SM_MOUSEPRESENT))
            {
              if (GET_WM_ACTIVATE_STATE(wParam, lParam))
                {
                  if (GET_WM_ACTIVATE_HWND(wParam, lParam) != hWnd)
                    {
                      curpoint.x = MousePos.x;
                      curpoint.y = MousePos.y;
                      ClientToScreen(hWnd, (LPPOINT)&curpoint);
                      SetCursorPos(curpoint.x, curpoint.y);
                      RevMouseMove(MousePos);
                      ShowCursor(GET_WM_ACTIVATE_STATE(wParam, lParam));
                    }
                }
              else
                  ShowCursor(wParam);
            }
          if (wParam && (!HIWORD(lParam)))
              SetFocus(hWnd);
          break;

      case WM_PAINT:
          BeginPaint(hWnd, (LPPAINTSTRUCT)&ps);
          RevPaint(hWnd, ps.hdc);
          EndPaint(hWnd, (LPPAINTSTRUCT)&ps);
          break;

      case WM_MOUSEMOVE:
          {
          POINT pt;

          LONG2POINT(lParam, pt);  // convert LONG lParam to POINT structure
          if (!fThinking)
#ifdef ORGCODE
              RevMouseMove(MAKEPOINT(lParam));
#else
              RevMouseMove(pt);
#endif
          else
              SetCursor(curThink);
          break;
          }
      case WM_LBUTTONDOWN:
          SetCapture(hWnd);
          bMouseDownInReversi = TRUE;
          break;

      case WM_LBUTTONUP:
          {
          POINT pt;

          LONG2POINT(lParam, pt);  // convert LONG lParam to POINT structure

          ReleaseCapture();
          if (!fThinking && bMouseDownInReversi)
#ifdef ORGCODE
              RevMouseClick(hWnd, MAKEMPOINT(lParam));
#else
              RevMouseClick(hWnd, pt);
#endif
          bMouseDownInReversi = FALSE;
          break;
          }
      case WM_TIMER:
          // This should never be called.
          break;

      case WM_VSCROLL:
      case WM_HSCROLL:
              break;

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

//
//  FUNCTION: AboutDlgProc(HWND, WORD, WORD, LONG)
//
//  PURPOSE:  Processes messages for "About" dialog box
//
//  MESSAGES:
//
//      WM_INITDIALOG - initialize dialog box
//      WM_COMMAND    - Input received
//

BOOL APIENTRY AboutDlgProc(
HWND        hDlg,
WORD        message,
WORD        wParam,
LONG        lParam)

{
  if (message == WM_COMMAND)
    {
      EndDialog(hDlg, TRUE);
      return(TRUE);
    }
  if (message == WM_INITDIALOG)
      return(TRUE);
  else
      return(FALSE);
  UNREFERENCED_PARAMETER(wParam);
  UNREFERENCED_PARAMETER(lParam);
}

//
//  FUNCTION: WinMain
//
//  PURPOSE: calls initialization function, processes message loop
//

int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrev,
    LPSTR     lpCmdLine,
    int       cmdShow
) {
  HWND hWnd;
  MSG   msg;

  hInst = hInstance;
  if (!hPrev)
    {
      if (!RevInit(hInstance))
          return(FALSE);
    }
  else
    {
      if (fThinking)
          return FALSE;
#ifdef WIN16
      GetInstanceData(hPrev, (PSTR)&hbrBlack,           sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrPat,             sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrWhite,           sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrRed,             sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrBlue,            sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrGreen,           sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrComputer,        sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&hbrHuman,           sizeof(HBRUSH));
      GetInstanceData(hPrev, (PSTR)&curIllegal,         sizeof(HCURSOR));
      GetInstanceData(hPrev, (PSTR)&curLegal,           sizeof(HCURSOR));
      GetInstanceData(hPrev, (PSTR)&curThink,           sizeof(HCURSOR));
      GetInstanceData(hPrev, (PSTR)&curBlank,           sizeof(HCURSOR));
      GetInstanceData(hPrev, (PSTR)&prevCheck,          sizeof(prevCheck));
      GetInstanceData(hPrev, (PSTR)&depth,              sizeof(depth));
      GetInstanceData(hPrev, (PSTR)direc,               sizeof(direc));
      GetInstanceData(hPrev, (PSTR)moves,               sizeof(moves));
      GetInstanceData(hPrev, (PSTR)szReversi,           20);
      GetInstanceData(hPrev, (PSTR)szReversiPractice,   40);
      GetInstanceData(hPrev, (PSTR)szPass,              10);
      GetInstanceData(hPrev, (PSTR)szMustPass,          20);
      GetInstanceData(hPrev, (PSTR)szTie,               15);
      GetInstanceData(hPrev, (PSTR)szLoss,              15);
      GetInstanceData(hPrev, (PSTR)szWon,               15);
      GetInstanceData(hPrev, (PSTR)szAbout,             20);
      GetInstanceData(hPrev, (PSTR)&COLOR,              sizeof(INT));
      GetInstanceData(hPrev, (PSTR)&ASPECT,             sizeof(INT));
      GetInstanceData(hPrev, (PSTR)&hAccel,             2);
      GetInstanceData(hPrev, (PSTR)szIllegal,           70);
      GetInstanceData(hPrev, (PSTR)szNoPass,            70);
      GetInstanceData(hPrev, (PSTR)szHelpFile,          15);
#endif // WIN16
    }
  lpprocInverseMessage = (WNDPROC)MakeProcInstance((FARPROC)(LPVOID)InverseMessage, hInstance);

  TYMIN = 45;
  fThinking = FALSE;

  lpprocAbout = MakeProcInstance((FARPROC)AboutDlgProc, hInstance);
  hWnd = CreateWindow((LPSTR) "Reversi",
                fCheated ? (LPSTR)szReversiPractice : (LPSTR)szReversi,
                WS_TILEDWINDOW,
                CW_USEDEFAULT,
                0,
                (GetSystemMetrics(SM_CXSCREEN) >> 1),
                (GetSystemMetrics(SM_CYSCREEN) * 4 / 5),
                (HANDLE)NULL,
                (HANDLE)NULL,
                (HANDLE)hInstance,
                NULL);

  if (!hWnd)
      return(FALSE);

  ShowWindow(hWnd, cmdShow);
  UpdateWindow(hWnd);

  // Messaging Loop
  while (GetMessage((LPMSG)&msg, NULL, 0, 0))
    {
      if (!TranslateAccelerator(msg.hwnd, hAccel, (LPMSG)&msg))
        {
          TranslateMessage((LPMSG)&msg);
          DispatchMessage((LPMSG)&msg);
        }
    }
  return(0);
}


REVERSI.H

#define BoardSize           100
#define edge                0
#define empty               1
#define human               2
#define computer            3
#define infin               32767
#define win                 32000
#define loss                (-32000)
#define max_depth           6

#define PASS                20
#define NEW                 25
#define BSkill              40
#define EASY                41
#define MEDIUM              42
#define HARD                44
#define VHARD               46
#define HINT                50
#define EXIT                52

/* Menu ID's */

#define MN_HELP_INDEX       30
#define MN_HELP_KEYBOARD    31
#define MN_HELP_COMMANDS    32
#define MN_HELP_PLAYING     33
#define MN_HELP_RULES       34
#define MN_HELP_USINGHELP   35
#define MN_HELP_ABOUT       36


README.TXT

Dynamically Changing the Display Settings

The SetDisp sample creates a taskbar notification icon that you can use to
dynamically change your display settings. You can change resolution, color
depth, and refresh rate.

The core functions used are:

   EnumDisplaySettings -- to get the display settings that are supported by
  the video driver

   ChangeDisplaySettings -- to test and set the display  settings dynamically
  and to update the registry with these settings

   Shell_NotifyIcon -- to create and maintain the tray icon

   TrackPopupMenu   -- to implement the icon resolution context menu

Usage
------

To use the sample after building it with NMAKE, run SetDisp.EXE.  This will
display a dialog box with a menu of possible resolutions and a will create a
tray icon. If you right mouse click on the tray icon the dialog will get
the focus.  If you hide the dialog, you can bring it back up with this
technique.  If you left mouse click the tray icon, you will get the list of
available settings. To remove the tray icon, you must close the main dialog.

When SetDisp starts up, it enumerates all of the device modes that the current
or default display driver supports.  A #define controls filtering low frequency
refresh rates and limits the number of settings SetDisp reads in.  After
enumerating the settings, they are added to the menu. On NT, we can easily
obtain the current setting with ENUM_CURRENT_SETTINGS, so we set the check
mark on the menu and bold the setting that was in use when SetDisp was
started.

When you select a new setting, SetDisp test to be sure the DEVMODE selected
has one of the display attributes set.All of the DEVMODEs used in SetDisp
should have these set since they came from the system.It then tests to see if
the system believes the DEVMODE is valid. If the change requires a system
reboot, a dialog notifies the user that this is not implemented. If the screen
change can be dynamic, a resolution changed is attempted.  The user is prompted
to see if the new resolution is acceptable.  This is important since the driver
may support a setting but the monitor is not able to display it correctly.  To
ensure the user does not get stuck with a bad setting, a timer is used to
cancel the change if the user does not respond. The default time out is 10
seconds and is controlled by a #define.If the new setting is accepted, the
registry is updated.  All of this is done using ChangeDisplaySettings.

Notes
-----

On Windows 95, you cannot use EnumDisplaySettings to get the current settings.
In this sample, this causes the check mark to not be displayed and the
current setting to not be made bold. Also on Windows 95, color depth changes
require a reboot.

Of the possible returns from ChangeDisplaySettings, only success and a reboot
required are currently handled.For success, a dynamic resolution change is
done.  For a reboot required change, a message is displayed indicating that
the sample does not support a reboot.

If your driver has many resolutions, the menu may not fit on some resolutions.
This could be resolved with the use of sub menus for different settings
such as color depth or frequencies.


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SetDisp.rc
//
#define IDI_SETDISP                     102
#define IDD_SETDISP                     103
#define IDI_STATE11                     105
#define IDI_STATE12                     106
#define IDI_STATE21                     107
#define IDI_STATE22                     108
#define IDI_STATE31                     109
#define IDR_HELP                        109
#define IDI_STATE32                     110
#define IDD_VERIFYRES                   111
#define IDR_RESOLUTIONS                 112
#define IDR_MENU1                       113
#define IDM_ABOUT                       113
#define IDR_MENU                        116
#define IDR_MENU2                       117
#define IDR_ACCELERATOR1                119
#define IDR_ACCELERATOR2                120
#define IDR_ACCELERATOR3                121
#define IDR_ACCELERATOR4                122
#define IDR_ACCELERATOR5                124
#define IDD_ABOUT                       125
#define IDC_NOTIFY1                     1006
#define IDC_NOTIFY2                     1007
#define IDC_NOTIFY3                     1008
#define IDC_STATE11                     1009
#define IDC_STATE12                     1010
#define IDC_STATE21                     1011
#define IDC_STATE22                     1012
#define IDC_STATE31                     1013
#define IDC_STATE32                     1014
#define IDC_DELAY1                      1017
#define IDC_TIP11                       1018
#define IDC_TIP12                       1019
#define IDC_DELAY2                      1020
#define IDC_TIP21                       1021
#define IDC_TIP22                       1022
#define IDC_DELAY3                      1023
#define IDC_TIP31                       1024
#define IDC_TIP32                       1025
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        127
#define _APS_NEXT_COMMAND_VALUE         114
#define _APS_NEXT_CONTROL_VALUE         1021
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SETDISP.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/
/******************************************************************************\
*
*   Implementation file for SetDisp
*
*   SetDisp creates a tray Notification Icon
*   that you can use to dynamically change your srceen
*   resolution. The core APIs used are:
*
*   Shell_NotifyIcon -- to create and maintain the tray icon
*   TrackPopupMenu   -- to implement the icon resolution
*                       context menu
*   ChangeDisplaySettings -- to test and set the display
*                       settings dynamically and to update
*                       the registry with these setttings
*   EnumDisplaySettings -- to get the display settings that are
*                       supported by the video driver
*
*   KNOWN ISSUES:  On Windows 95 one cannot use the EnunDisplaySettings
*   to get the current settings.  This causes the check mark to not
*   be displayed and the current setting to not be bolded.  This
*   information could be obtained from the registry. Also on Windows 95
*   color depth changes require a reboot.  None of the possible returns
*   from ChangeDisplaySettings are currently handled execpt for
*   success and a message is displayed indicated the sample does not
*   support a reboot. If your driver has many resolutions, the menu
*   may not fit on some resolutions.  This could be resolved with
*   the use of sub menus for different settings such as color depth
*   or frequencies.
*
\******************************************************************************/

#if DBG

#define TRC(_x) OutputDebugString(TEXT(_x))

#else

#define TRC(_x)

#endif

#include "setdisp.h"
#include <windowsx.h>
#include <winuser.h>
#include "resource.h"

/**************************************************************\
*                                                              *
*  Tray Icon Structure taken from Win32\TrayNot sample         *
*                                                              *
\**************************************************************/

struct _DLGITEMS
{
DWORD dwStart;
UINT uNotify;
UINT uDelayID;
UINT uState1;
UINT uTip1;
UINT uState2;
UINT uTip2;
} g_sDlgItems [] =

//
// Data for one tray icon
//

{
{
0, IDC_NOTIFY1, IDC_DELAY1, IDC_STATE11, IDC_TIP11, IDC_STATE12, IDC_TIP12,
},
} ;

 /****************************************************************************
 *
 *  FUNCTION   : WinMain (HINSTANCE hInstance,  HINSTANCE hPrevInstance,
 *                        LPSTR lpCmdLine, int nCmdShow )
 *
 *  PURPOSE    : Creates a dialog box which implements the tray icon
 *               and which handles the resolution changes
 *
 \****************************************************************************/

int WINAPI WinMain (

  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,
  int nCmdShow )
{

    g_hinst = hInstance;

    DialogBox(hInstance, MAKEINTRESOURCE(IDD_SETDISP), NULL, SetDispDlgProc);

    if (gpDevModes)
    {
        free(gpDevModes);
    }

    return(FALSE);

}

 /****************************************************************************
 *
 *  FUNCTION   : SetDispDlgProc (HWND hDlg, UINT uMsg,
 *                                WPARAM wParam, LPARAM lParam)
 *
 *  PURPOSE    : Dialog box callback which manages the tray icon
 *               and which handles the resolution changes
 *
 \****************************************************************************/

BOOL CALLBACK SetDispDlgProc (

   HWND hDlg,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam)
{

    switch (uMsg)
    {

    case WM_INITDIALOG:
    {
BOOL bRet;

//
//Get DEVMODES
//

bRet = GetDevModes (hDlg);

        if (bRet == FALSE)
        {
            //
            // Failed to get DEVMODES so end the dialog
            //

    EndDialog(hDlg, TRUE);

    break;
        }

//
// Initialize Menu with Resolutions
//

SetResolutionMenu (hDlg);

//
// Add Tray Notify Icon
//

NotifyAdd(hDlg, 0);

//
// Update Tip to current settings
//
{

    char szTip[50];

    //
    // Set the menu text
    //

    if (IsNT())
            {
                wsprintf(szTip,"%d x %d, %d bit color at %d hz",
                         gpCurrentMode->dmPelsWidth,
                         gpCurrentMode->dmPelsHeight,
                         gpCurrentMode->dmBitsPerPel,
                         gpCurrentMode->dmDisplayFrequency);
            }
            else
            {
                szTip[0] = '\0';
            }

    //
    // For the current mode, set the tool tip
            //

    TrayMessage(hDlg, NIM_MODIFY, g_sDlgItems[0].uNotify,
                        LoadImage(g_hinst, MAKEINTRESOURCE(IDI_SETDISP),
                        IMAGE_ICON, 16, 16, 0), szTip);
}

SetForegroundWindow (hDlg);

break;

    }// WM_INITDIALOG:

    case WM_DESTROY:NotifyDelete(hDlg, 0);
break;

    case WM_COMMAND:
    {
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
    case IDCANCEL:   EndDialog(hDlg, TRUE);
     break;

    case IDABORT:    ShowWindow(hDlg, SW_HIDE);
     break;

    case IDM_ABOUT:
  DialogBox(g_hinst, MAKEINTRESOURCE(IDD_ABOUT),
    hDlg, (DLGPROC)About);
  break;

    default:

    //
    // handle MYWM_NOTIFYICON + 10 + i for diffent devmodes
    //

            if ( MYWM_NOTIFYICON + 10  <= GET_WM_COMMAND_ID(wParam, lParam) &&
 MYWM_NOTIFYICON + 11 + gnModes >= GET_WM_COMMAND_ID(wParam, lParam))
    {
UINT nNewMode;

                nNewMode = GET_WM_COMMAND_ID(wParam, lParam) - (MYWM_NOTIFYICON + 10);

//
// Only update if the chose a new setting
//

                if (gnCurrentMode != nNewMode)
                {
   ChangeResolution (nNewMode, hDlg);
}

break;

            }

} //WM_COMMAN switch

    } //WM_COMMAND

    case MYWM_NOTIFYICON:
    {
switch (lParam)
{

    case WM_LBUTTONDOWN:

 //
 // Handle popup here when user right mouse clicks tray icon
 //
 {

     //
     // Handle popup here
     //

     POINT point;

     GetCursorPos (&point);

     HandlePopupMenu (hDlg, point);

     break;

 }

    case WM_RBUTTONDOWN:
 switch (wParam)
 {

    //
    // Diplay the dialog box on Left mouse click
    // this is how the user gets the dialog back if it is hidden
    //

    case IDC_NOTIFY1:  ShowWindow (hDlg, SW_RESTORE);
       SetForegroundWindow (hDlg);
       break;

    default:
 break;

  }

    default:
 break;

}  // lParam switch

    }// MYWM_NOTIFYICON

    default:
 return(FALSE);

    }  // switch (uMsg)

    return(TRUE);

}

/****************************************************************************
*
*  FUNCTION   : GetDevModes (hwnd)
*
*  PURPOSE    : Get the display modes supported by the current driver
*
\****************************************************************************/
BOOL GetDevModes (

  HWND hwnd)
{

    BOOL bRet = TRUE;
    BOOL bAddDM;
    UINT iModeNum = 0;
    DEVMODE CurrentDM, WorkingDM;
    INT i;
    UINT nlist;
    LPDEVMODE pDM;

    //
    // setting param 1 to NULL (lpszDeviceName) uses current device on NT
    // and NULL is required on Win95
    //

    if (IsNT())
    {
//
// NT specific; returns current devmode when param 2 set to ENUM_CURRENT_SETTINGS,
//

EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &CurrentDM);
    }

    //
    // Determine how much memory we need
    //

    for (iModeNum = 0, gnModes = -1;
         bRet && (gnModes <= MAX_MODES);
         iModeNum++)
    {
        bRet = EnumDisplaySettings
                     (NULL,            // use default device
                     iModeNum,         // DEVMODEs start at 0
                     &WorkingDM);      // if successful the system
                                       // fills in the DEVMODE
                                       // structure

        //
        // For some reason my driver supports 1 hz display frequency
        // So filter out settings we do not want to display
        //

        if ( bRet &
     WorkingDM.dmPelsWidth  >= MIN_WIDTH &&
            (WorkingDM.dmDisplayFrequency >= MIN_FREQUENCY  ||
     WorkingDM.dmDisplayFrequency == 0) )

    //
    // Accept 0 frequency since windows 95 does return frequency
    //
        {
            //
            // Increment to use next DEVMODE
            //

            gnModes++;
}

    } // for loop

    if(-1 == gnModes)
    {
        MessageBox(hwnd, "No Display modes available.", "SetDisp Error", IDOK);
        return (FALSE);
    }

    //
    // We need gnModes worth of DEVMODE Structures
    //

    gpDevModes = (PDEVMODE) malloc ((size_t) (gnModes + 1) * sizeof(DEVMODE));

    if (gpDevModes == NULL)
    {
        return (FALSE);
    }

    //
    // Loop through EnumDisplaySetting to get each supported DEVMODE
    // When returns false we have them all
    //

    for (iModeNum = 0, i = 0, bRet = TRUE;
         bRet && i <= gnModes;
         iModeNum++)
    {
        bRet = EnumDisplaySettings
                     (NULL,            // use default device
                     iModeNum,         // DEVMODEs start at 0
                     &WorkingDM);      // if successful the system
                                       // fills in the DEVMODE
                                       // structure

        //
        // Miniport drivers support 1 hz display frequency representing the default
        // and older drivers support 0 hz display frequency representing the default
        // So filter out settings we do not want to display
        //

        if ( bRet &&
             WorkingDM.dmPelsWidth        >= MIN_WIDTH      &&
            (WorkingDM.dmDisplayFrequency >= MIN_FREQUENCY  ||
             WorkingDM.dmDisplayFrequency == 0              ||
             WorkingDM.dmDisplayFrequency == 1))
             // Accept 0 or 1 frequency which represent default frequencies
        {
            bAddDM = TRUE;

            //
            // We will take this one unless another default has already been
            // added at these settings
            //

            if (WorkingDM.dmDisplayFrequency == 0 ||
                WorkingDM.dmDisplayFrequency == 1)
            {
                for (nlist = 0; nlist <= gnCurrentMode; nlist++)
                {
                      pDM = gpDevModes + nlist;

                      if (pDM->dmPelsWidth  == WorkingDM.dmPelsWidth   &&
                          pDM->dmPelsHeight == WorkingDM.dmPelsHeight  &&
                          pDM->dmBitsPerPel == WorkingDM.dmBitsPerPel)
                      {
                          //
                          // If everything else is the same do not add another default
                          //

                          bAddDM = FALSE;
                      }
                } // end for nlist

            } // end if default

            if (bAddDM)
            {
                //
                // Increment to use next DEVMODE
                //

                if (i == 0)
                {
                    gpCurrentMode = gpDevModes;
                }
                else
                {
                    gpCurrentMode += 1;
                }

                EnumDisplaySettings (NULL, iModeNum, gpCurrentMode);
                i++;

            } // end if bAddDM

        } // end bRet and hz if

    } // end for EnumDisplaySettings

    //
    // Sort the array for display; default sort
    // A different compare routine could sort by different attributes
    //

    qsort((void *) gpDevModes,
          (size_t) gnModes + 1,  // add one since gnModes is zero based
          (size_t) sizeof(DEVMODE),
          ( int (_cdecl*)(const void*,const void*) )CompareDevModes);

    //
    // Set gCurrentMode to CurrentDM
    //
    if (IsNT())
    {
        for (i =0; i <= gnModes; i++)
        {

            if (i == 0)
            {
                gpCurrentMode = gpDevModes;
            }
            else
            {
                gpCurrentMode += 1;
            }

            if (0 == CompareDevModes (&CurrentDM, gpCurrentMode))
            {
                gnCurrentMode = i;
                break;
            }
        }
    }
    else
    {
        //
        // Default to the first one
        // since we error out if there is not even this one
        //

        gnCurrentMode = 0;
    }

    //
    // TODO: What if some of the DEVMODES are not supported on
    //       the physical Device?
    //

    return (TRUE);

}  //GetDevModes scope

/****************************************************************************
*
*  FUNCTION   : ChangeResolution (UINT nNewMode, HWND hwnd)
*
*  PURPOSE    : Change the resolution to the nNewMode index DEVMODE
*
\****************************************************************************/
BOOL ChangeResolution (
    UINT nNewMode,
    HWND hwnd )
{

    LONG lResult;       //Result of ChangeDisplaySettings
    INT nUserResponse;//Return from VerifyRes Dlg
    UINT nOldMode;

   /*
    ChangeDisplaySettings can set
dmBitsPerPel    Bits per pixel
dmPelsWidth    Pixel width
dmPelsHeight    Pixel height
dmDisplayFlags    Mode flags
dmDisplayFrequency  Mode frequency

        but one of thes must be set in the DEVMODE dmFields
        DM_BITSPERPEL    Use the dmBitsPerPel value.
        DM_PELSWIDTH    Use the dmPelsWidth value.
        DM_PELSHEIGHT    Use the dmPelsHeight value.
        DM_DISPLAYFLAGS    Use the dmDisplayFlags value.
DM_DISPLAYFREQENCY  Use the dmDisplayFrequency value.

    */

    //
    // First select a DevMode to use
    // to start, just cycling through them
    //

    nOldMode = gnCurrentMode;
    gnCurrentMode = nNewMode;

    //
    // Increment ptr to the new mode
    //

    gpCurrentMode = gpDevModes;
    gpCurrentMode += nNewMode;

    //
    // Verify that the DEVMODE select will change something
    //

    if (!(DM_BITSPERPEL     & gpCurrentMode->dmFields ||
          DM_PELSWIDTH     & gpCurrentMode->dmFields ||
          DM_PELSHEIGHT     & gpCurrentMode->dmFields ||
          DM_DISPLAYFLAGS & gpCurrentMode->dmFields ||
          DM_DISPLAYFREQUENCY & gpCurrentMode->dmFields ) )

    {

//
        // Note our DEVMODE returned from EnumDisplaySettings
// So we should never enter here
//

        gnCurrentMode = nOldMode;

        gpCurrentMode = gpDevModes;
        gpCurrentMode += gnCurrentMode;

        return (FALSE);
    }

    /************************************************************\
    *                                                            *
    *   Possible Flags for param 2 of ChangeDisplay Settings     *
    *    0        The graphics mode for the current            *
    *                screen will be changed dynamically.         *
    *    CDS_UPDATEREGISTRYThe graphics mode for the            *
    *                current screen will be changed dynamically  *
    *                and the graphics mode will be updated in    *
    *                the registry. The mode information is       *
    *                stored in the USER profile.                 *
    *    CDS_TESTThe system tests if the requested            *
    *                graphics mode could be set.                 *
    *                                                            *
    \************************************************************/

    //
    // First have the system test if we can set it
    //

    //
    // Param 1 is the new DEVMODE
    // Param 2 is Test Flag,
    // which asks the operating system if this is a valid setting
    // to change to -- wedon't want to make the system unusable
    //

    lResult = ChangeDisplaySettings
                ( gpCurrentMode,
                  CDS_TEST );

    if (lResult == DISP_CHANGE_RESTART)
    {
        MessageBox(hwnd, "This mode requires a reboot which\n"  \
                         "is not supported in this sample.",
 "SetDisp Message", IDOK);

        gnCurrentMode = nOldMode;
        gpCurrentMode = gpDevModes;
        gpCurrentMode += gnCurrentMode;

        return FALSE;
    }

    if (DISP_CHANGE_SUCCESSFUL ==lResult)
    {

 //
 // Param 2 is 0 to change dynamically
 // hence the app name SetDisp!
 //

         lResult = ChangeDisplaySettings
                     ( gpCurrentMode,
                       0 );
    }

    switch (lResult)
    {

    case DISP_CHANGE_SUCCESSFUL:  //The settings change was successful.
 TRC("DISP_CHANGE_SUCCESSFUL\n");
 break;

    case DISP_CHANGE_RESTART:  //The computer must be restarted in order
                                  //  for the graphics mode to work.
         TRC("DISP_CHANGE_RESTART\n");
         break;

    case DISP_CHANGE_BADFLAGS:  //An invalid set of flags was passed in.
         TRC("DISP_CHANGE_BADFLAGS\n");
         break;

    case DISP_CHANGE_FAILED:  //The display driver failed the specified
                                  //  graphics mode.
         TRC("DISP_CHANGE_FAILED\n");
         break;

    case DISP_CHANGE_BADMODE:  //The graphics mode is not supported.
         TRC("DISP_CHANGE_BADMODE\n");
         break;

    case DISP_CHANGE_NOTUPDATED:  //Windows NT only: Unable to write settings
                                  //  to the registry.
         TRC("DISP_CHANGE_NOTUPDATED\n");
         break;
    default:
 TRC("Undocumented return value!!!!\n");
 break;
    }

    //
    // Set a timer to only give the user so much
    // time to dedice incase the screen is garbled
    //

    SetTimer(hwnd,VERIFY_RESCHANGE, VERIFY_TIMEOUT, VerifyTimerProc);

    //
    // Have the user verify the new resolution and depth
    //

    nUserResponse = DialogBox(g_hinst,// handle this application
                              MAKEINTRESOURCE(IDD_VERIFYRES),// identifies dialog box template
                              NULL,               // handle to owner window
                              VerifyDlgProc);  // the dialog box procedure

    //
    // The verification is over, kill the timer
    //

    KillTimer(hwnd,VERIFY_RESCHANGE);

    if (IDYES == nUserResponse)
    {

//
// Keep resolution and update registry
//

        char szTip[50];

//
// TODO: give them three choices:
        //       Dynamic only, Registry too, or Abort
//

        lResult = ChangeDisplaySettings
                    ( gpCurrentMode,
                      CDS_UPDATEREGISTRY );  //  Flag to update registry

        switch (lResult)

        {

        case DISP_CHANGE_SUCCESSFUL:

    //
    // The settings change was successful.
    //

            TRC("DISP_CHANGE_SUCCESSFUL - reg\n");

            wsprintf(szTip,"%d x %d, %d bit color at %d hz",
                 gpCurrentMode->dmPelsWidth,
                 gpCurrentMode->dmPelsHeight,
                 gpCurrentMode->dmBitsPerPel,
                 gpCurrentMode->dmDisplayFrequency);

             {

 //
 // Update the Menu informtion, just a check mark for now
 //

                 HMENU hMenu, hMenu2;

                 hMenu = GetMenu(hwnd);

                 hMenu2 = GetSubMenu (hMenu, 0);

 //
 // Uncheck the old
 //

                 CheckMenuItem (hMenu2, nOldMode, MF_BYPOSITION | MF_UNCHECKED);

 //
 // Check the new
 //

 CheckMenuItem (hMenu2, gnCurrentMode, MF_BYPOSITION | MF_CHECKED);

             }

     //
     // Update the Notify_Icon quick tip
     //

             TrayMessage(hwnd, NIM_MODIFY, g_sDlgItems[0].uNotify,
                    LoadImage(g_hinst, MAKEINTRESOURCE(IDI_SETDISP),
                    IMAGE_ICON, 16, 16, 0), szTip);

             break;

        case DISP_CHANGE_RESTART:  //The computer must be restarted in order
                                      //  for the graphics mode to work.
             TRC("DISP_CHANGE_RESTART - reg\n");
             break;

        case DISP_CHANGE_BADFLAGS:  //An invalid set of flags was passed in.
             TRC("DISP_CHANGE_BADFLAGS - reg\n");
             break;

        case DISP_CHANGE_FAILED:  //The display driver failed the specified
                                      //  graphics mode.
             TRC("DISP_CHANGE_FAILED - reg\n");
             break;

        case DISP_CHANGE_BADMODE:  //The graphics mode is not supported.
             TRC("DISP_CHANGE_BADMODE - reg\n");
             break;

        case DISP_CHANGE_NOTUPDATED:  //Windows NT only: Unable to write settings
                                      //  to the registry.
             TRC("DISP_CHANGE_NOTUPDATED - reg\n");
             break;
        default:
              TRC("Undocumented return value!!!! - reg\n");
              break;
        }

    }

    else

    {
//
// The user chose to not keep the setting orwe timed out
        // so they may not have seen the dialog box to choose
        //
        // Change resolution back by calling
        // ChangeDisplaySettings
        // with a NULL DEVMODE which returns us to the current
        // registry settings
//

        lResult = ChangeDisplaySettings
                    ( NULL,
                      0 );  // 0 to change dynamically

        gnCurrentMode = nOldMode;

        gpCurrentMode = gpDevModes;
        gpCurrentMode += gnCurrentMode;

    }

    return (TRUE);

}

 /****************************************************************************
 *
 *  FUNCTION   : SetResolutionMenu (hwnd)
 *
 *  PURPOSE    : Create a context menu with all of the resolutions
 *               that are in the devmodes we obtained from the drive
 *
 \****************************************************************************/
BOOL SetResolutionMenu (
    HWND hwnd)
{

    HMENU hMenu, hWndMenu; //hMenuTrackPopup;
    INT i;
    BOOL bRet;
    PDEVMODE pDevMode;

    //
    // Create new Resolutions menu from the DevModes collected
    //

    hMenu = CreateMenu();

    if (!hMenu)
    {
       return (FALSE);
    }

    //
    // For each devmode add an menu item
    // TODO: make the description on BitPerPel better
    //

    for(i=0; i<= gnModes; i++)
    {

        char szRes[50];

        if (i == 0)
        {
            pDevMode = gpDevModes;
        }
        else
        {
            pDevMode += 1;
}

        //
// Set the menu text
//

        if (pDevMode->dmDisplayFrequency == 0 ||
            pDevMode->dmDisplayFrequency == 1)
{
    wsprintf(szRes,"%d x %d, %d bit color, default frequency",
                 pDevMode->dmPelsWidth,
                 pDevMode->dmPelsHeight,
                 pDevMode->dmBitsPerPel);
}
else
{
    wsprintf(szRes,"%d x %d, %d bit color at %d hz",
                 pDevMode->dmPelsWidth,
                 pDevMode->dmPelsHeight,
                 pDevMode->dmBitsPerPel,
                 pDevMode->dmDisplayFrequency);
}

        bRet = AppendMenu (hMenu, MF_STRING, MYWM_NOTIFYICON + 10 + i, szRes);

        SetMenuItemBitmaps(
  hMenu, // handle of menu
  gnCurrentMode, // menu item to receive new bitmaps
                  MF_BYPOSITION, // menu item flags
                  NULL,         // handle of unchecked bitmap
  NULL); // handle of checked bitmap
                                 // if last two are NULL the default
                                 // bitmap is used

    } // for loop to set menu

    if (!bRet)
    {

        //
        // if the last one worked they all likely worked
        // otherwise let's clean up
        //

        DestroyMenu(hMenu);

        return(FALSE);

    }
    else
    {

        if (IsNT())
        {
            //
            // Check the Current one
            //

            CheckMenuItem (hMenu, gnCurrentMode, MF_BYPOSITION | MF_CHECKED);

            //
            // Set it as the default so it is easy to go back to
            //

    SetMenuDefaultItem(hMenu, gnCurrentMode, TRUE ); //TRUE if for by Position

        }

        hWndMenu = GetMenu (hwnd);

        InsertMenu (hWndMenu, 0, MF_POPUP|MF_BYPOSITION, (DWORD)hMenu, "&Resolutions");

        return(TRUE);

    }

}

 /****************************************************************************
 *
 *  FUNCTION   : TrayMessage (HWND hDlg, DWORD dwMessage, UINT uID,
 *                            HICON hIcon, PSTR pszTip )
 *
 *  PURPOSE    : Creates, Modifies or deletes the tray icon
 *               If pszTip is not null, it uses that for the tip
 *               otherwise it sets a default tip
 *
 \****************************************************************************/

BOOL TrayMessage (

  HWND hDlg,
  DWORD dwMessage,
  UINT uID,
  HICON hIcon,
  PSTR pszTip )

{
    BOOL res;
    NOTIFYICONDATA tnd;

    //
    // Get the Tray Icon
    //

    hIcon = (HICON)LoadImage(g_hinst, MAKEINTRESOURCE(IDI_SETDISP), IMAGE_ICON,
     16, 16, 0);

    tnd.cbSize= sizeof(NOTIFYICONDATA);
    tnd.hWnd= hDlg;
    tnd.uID= uID;
    tnd.uFlags= NIF_MESSAGE|NIF_ICON|NIF_TIP;
    tnd.uCallbackMessage= MYWM_NOTIFYICON;

tnd.hIcon= hIcon;

    //
    // If there is a specific tip, set it
    // otherwise use SetDisp as the default
    //

    if (pszTip)
    {
lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip));
    }
    else
    {
lstrcpyn(tnd.szTip, "SetDisp", sizeof("SetDisp"));;
    }

    //
    //Use the Shell_NotifyIcon API to setup the tray icon
    //

    res = Shell_NotifyIcon(dwMessage, &tnd);

    if (hIcon)
    {
 DestroyIcon(hIcon);
    }

    return res;

}

 /****************************************************************************
 *
 *  FUNCTION   : NotifyDelete (HWND hDlg, UINT uIndex)
 *
 *  PURPOSE    : Deletes a tray icon based on the uIndex .
 *               In this SetDisp sample, it is only used for a single icon
 *
 \****************************************************************************/

void NotifyDelete (

  HWND hDlg,
  UINT uIndex )

{

    TrayMessage(hDlg, NIM_DELETE, g_sDlgItems[uIndex].uNotify, NULL, NULL);

}

 /****************************************************************************
 *
 *  FUNCTION   : NotifyAdd (HWND hDlg, UINT uIndex)
 *
 *  PURPOSE    : Creates tray icons based on the uIndex .
 *               In this SetDisp sample, it is only used for a single icon
 *
 \****************************************************************************/

void NotifyAdd (

  HWND hDlg,
  UINT uIndex )

{

    TrayMessage(hDlg, NIM_ADD, g_sDlgItems[uIndex].uNotify, NULL, NULL);

}

 /****************************************************************************
 *
 *  FUNCTION   : VerifyTimerProc (HWND hwnd, UINT uMsg,
 *                                UINT idEvent, DWORD dwTime )
 *
 *  PURPOSE    : Simply sends an IDNO to the Verify Dialog
 *               This is setup to give the user a limited amount of time
 *               to approve
 *
 \****************************************************************************/

VOID CALLBACK VerifyTimerProc (

  HWND hwnd,
  UINT uMsg,
  UINT idEvent,
  DWORD dwTime )
{

    SendMessage(ghVerifyDlg, WM_COMMAND, IDNO, 0);

}

 /****************************************************************************
 *
 *  FUNCTION   : VerifyTimerProc (HWND hDlg, UINT uMsg,
 *                                WPARAM wParam, LPARAM lParam)
 *
 *
 *  PURPOSE    : Ask the user if they want to keep the new resolution
 *
 \****************************************************************************/

BOOL CALLBACK VerifyDlgProc(
    HWND hDlg,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(uMsg)
    {
case WM_INITDIALOG:ghVerifyDlg = hDlg;
return(TRUE);

case WM_COMMAND:

     //
     // LOWORD added for portability
     //

     switch (LOWORD(wParam))
     {

 case IDNO:
 case IDCANCEL:ghVerifyDlg = NULL;
EndDialog(hDlg, IDNO);
return 0;

 case IDYES:ghVerifyDlg = NULL;
EndDialog(hDlg, IDYES);
return 0;
      }

default:
     break;

    }

    return(FALSE);

    UNREFERENCED_PARAMETER(lParam);

}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : HandlePopupMenu (hwnd, point)                              *
 *                                                                          *
 *  PURPOSE    : Handles the display of the "floating" popup that appears   *
 *               on a mouse click in the app's client area.                 *
 *                                                                          *
 ****************************************************************************/

VOID APIENTRY HandlePopupMenu (
    HWND  hwnd,
    POINT point)
{

    HMENU hMenu;
    HMENU hMenuTrackPopup;

    //
    // Get the menu for the windows
    //

    hMenu = GetMenu(hwnd);

    if (!hMenu)
    {
return;
    }

    //
    // Get the first menu in it which we will use for the call to
    // TrackPopup(). This could also have been created on the fly using
    // CreatePopupMenu and then we could have used InsertMenu() or
    // AppendMenu.
    //

    hMenuTrackPopup = GetSubMenu (hMenu, 0);

    //
    // Draw and track the "floating" popup
    //

    if (point.x < (long) (gpCurrentMode->dmPelsWidth - 50))
        point.x = (long) (gpCurrentMode->dmPelsWidth - 50);

    if (point.y < (long) (gpCurrentMode->dmPelsHeight - 50))
        point.y = (long) (gpCurrentMode->dmPelsHeight - 50);

    //
    // This is required when using a notify icon -- see KB article
    // PRB: Menus for Notification Icons Don't Work Correctly
    //

    SetForegroundWindow (hwnd);

    TrackPopupMenu (hMenuTrackPopup, TPM_LEFTBUTTON | TPM_RIGHTBUTTON,
                    point.x, point.y, 0, hwnd, NULL);

    //
    // This is required when using a notify icon -- see KB article
    // PRB: Menus for Notification Icons Don't Work Correctly
    //

    PostMessage (hwnd, WM_USER, 0, 0);

}

/****************************************************************************\
*
*  FUNCTION   : CompareDevModes (DEVMODE *leftDM, DEVMODE *rightDM)
*
*  PURPOSE    : Handles the display of the "floating" popup that appears
*               on a mouse click in the app's client area.
*
\****************************************************************************/
int CompareDevModes (

    DEVMODE *leftDM,
    DEVMODE *rightDM)
{

    //
    // Return 1  if left is bigger
    //        0  if equal
    //        -1 if right is bigger
    //
    // Comparison order is Width then Height then Bits then Frequency
    // This is an arbitrary order of importance. Often BitPerPels is
    // on considered the most important measure of a display setting
    //
    // return on the first one that is larger between left and right
    //

    if (leftDM->dmPelsWidth  > rightDM->dmPelsWidth)
    {
        return 1;
    }
    else if (leftDM->dmPelsWidth  < rightDM->dmPelsWidth)
    {
        return -1;
    }

    if (leftDM->dmPelsHeight > rightDM->dmPelsHeight)
    {
        return 1;
    }
    else if (leftDM->dmPelsHeight < rightDM->dmPelsHeight)
    {
        return -1;
    }

    if (leftDM->dmBitsPerPel > rightDM->dmBitsPerPel)
    {
        return 1;
    }
    else if (leftDM->dmBitsPerPel < rightDM->dmBitsPerPel)
    {
        return -1;
    }

    if (leftDM->dmDisplayFrequency > rightDM->dmDisplayFrequency)
    {
        return 1;
    }
    else if (leftDM->dmDisplayFrequency < rightDM->dmDisplayFrequency)
    {
        return -1;
    }

    //
    // All are equal
    //

    return 0;

}

/****************************************************************************\
*
*  FUNCTION: About(HWND, UINT, UINT, LONG)
*
*  PURPOSE:  Processes messages for the "About" dialog box
*
\****************************************************************************/
BOOL APIENTRY About(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{
switch (message)
{
   case WM_INITDIALOG:
  return TRUE;

   case WM_COMMAND:
 if (LOWORD(wParam) == IDOK)
 {
  EndDialog(hDlg, TRUE);
  return TRUE;
 }

 break;

   default:

return FALSE;

}

}

BOOL IsNT()
{
    OSVERSIONINFO          osvi;

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    GetVersionEx(&osvi);

    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}


SETDISP.H

/**************************************************************\
*                                                              *
*   Header file for SetDisp.c                                 *
*                                                              *
\**************************************************************/
#define STRICT

#define _INC_OLE
#include <windows.h>
#undef  _INC_OLE

/**************************************************************\
*                                                              *
*   Misc contants to control app behaviour                     *
*                                                              *
\**************************************************************/
#define MYWM_NOTIFYICON(WM_APP+100)
#define VERIFY_RESCHANGE  1

//Milliseconds  -- set to 10 seconds
#define VERIFY_TIMEOUT         10000

#define MAX_MODES              40
#define MIN_FREQUENCY          60
#define MIN_WIDTH              400

/**************************************************************\
*                                                              *
*   Global to everybody...                                     *
*                                                              *
\**************************************************************/
HINSTANCE g_hinst       = NULL;

PDEVMODE  gpDevModes    = NULL;
PDEVMODE  gpCurrentMode = NULL;

HANDLE    ghVerifyDlg   = NULL;

INT       gnModes       = -1;
UINT      gnCurrentMode =  0;  //Default to the first until
                               //we determine which it really is

/**************************************************************\
*                                                              *
*  Function Prototypes                                         *
*                                                              *
\**************************************************************/
BOOL CALLBACK SetDispDlgProc (HWND hDlg, UINT uMsg,
                               WPARAM wParam, LPARAM lParam);

VOID CALLBACK VerifyTimerProc(HWND hwnd, UINT uMsg,
                              UINT idEvent,DWORD dwTime);

BOOL CALLBACK VerifyDlgProc (HWND hDlg, UINT uMsg,
                             WPARAM wParam, LPARAM lParam);

BOOL APIENTRY About (HWND hDlg, UINT message,
                     UINT wParam, LONG lParam);

VOID APIENTRY HandlePopupMenu (HWND   hwnd, POINT point);

BOOL SetResolutionMenu (HWND hwnd);
BOOL ChangeResolution (UINT nNewMode, HWND hwnd);

BOOL GetDevModes (HWND hWnd);

int CompareDevModes (DEVMODE *leftDM, DEVMODE *rightDM);

BOOL TrayMessage (HWND hDlg, DWORD dwMessage, UINT uID,
                  HICON hIcon, PSTR pszTip );

void NotifyAdd (HWND hDlg, UINT uIndex);
void NotifyDelete (HWND hDlg, UINT uIndex);

BOOL IsNT(void);


README.TXT

Opening and Displaying Device Independent Bitmaps (DIBs)

The SHOWDIB sample illustrates how to deal with Device Independent Bitmaps
(DIBs), and implement proper palette management.


DIB.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*******************************************************************************
 *                                                                             *
 *  MODULE      : DIB.C                                                        *
 *                                                                             *
 *  DESCRIPTION : Routines for dealing with Device Independent Bitmaps.        *
 *                                                                             *
 *  FUNCTIONS   : OpenDIB()           - Opens DIB file and creates a memory DIB*
 *                                                                             *
 *                WriteDIB()          - Writes a global handle in CF_DIB format*
 *                                      to a file.                             *
 *                                                                             *
 *                DibInfo()           - Retrieves the info. block associated   *
 *                                      with a CF_DIB format memory block.     *
 *                                                                             *
 *                CreateBIPalette()   - Creates a GDI palette given a pointer  *
 *                                      to a BITMAPINFO structure.             *
 *                                                                             *
 *                CreateDibPalette()  - Creates a GDI palette given a HANDLE   *
 *                                      to a BITMAPINFO structure.             *
 *                                                                             *
 *                ReadDibBitmapInfo() - Reads a file in DIB format and returns *
 *                                      a global handle to it's BITMAPINFO     *
 *                                                                             *
 *                PaletteSize()       - Calculates the palette size in bytes   *
 *                                      of given DIB                           *
 *                                                                             *
 *                DibNumColors()      - Determines the number of colors in DIB *
 *                                                                             *
 *                BitmapFromDib()     - Creates a DDB given a global handle to *
 *                                      a block in CF_DIB format.              *
 *                                                                             *
 *                DibFromBitmap()     - Creates a DIB repr. the DDB passed in. *
 *                                                                             *
 *                DrawBitmap()        - Draws a bitmap at specified position   *
 *                                      in the DC.                             *
 *                                                                             *
 *                DibBlt()            - Draws a bitmap in CIF_DIB format using *
 *                                      SetDIBitsToDevice()                    *
 *                                                                             *
 *                StretchDibBlt()     - Draws a bitmap in CIF_DIB format using *
 *                                      StretchDIBits()                        *
 *                                                                             *
 *                lread()             - Private routine to read more than 64k  *
 *                                                                             *
 *                lwrite()            - Private routine to write more than 64k *
 *                                                                             *
 *******************************************************************************/

#include <windows.h>
#include "showdib.h"
static   HCURSOR hcurSave;

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :OpenDIB(LPSTR szFile)                                       *
 *                                                                          *
 *  PURPOSE    :Open a DIB file and create a MEMORY DIB, a memory handle    *
 *              containing BITMAPINFO, palette data and the bits.           *
 *                                                                          *
 *  RETURNS    :A handle to the DIB.                                        *
 *                                                                          *
 ****************************************************************************/
HANDLE OpenDIB (LPSTR szFile)
{
    HFILE               fh;
    BITMAPINFOHEADER    bi;
    LPBITMAPINFOHEADER  lpbi;
    DWORD               dwLen = 0;
    DWORD               dwBits;
    HANDLE              hdib;
    HANDLE              h;
    OFSTRUCT            of;

    /* Open the file and read the DIB information */
    fh = OpenFile(szFile, &of, (UINT)OF_READ);
    if (fh == -1)
        return NULL;

    hdib = ReadDibBitmapInfo(fh);
    if (!hdib)
        return NULL;
    DibInfo(hdib,&bi);

    /* Calculate the memory needed to hold the DIB */
    dwBits = bi.biSizeImage;
    dwLen  = bi.biSize + (DWORD)PaletteSize (&bi) + dwBits;

    /* Try to increase the size of the bitmap info. buffer to hold the DIB */
    h = GlobalReAlloc(hdib, dwLen, GHND);
    if (!h){
        GlobalFree(hdib);
        hdib = NULL;
    }
    else
        hdib = h;

    /* Read in the bits */
    if (hdib){

        lpbi = (VOID FAR *)GlobalLock(hdib);
        lread(fh, (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi), dwBits);
        GlobalUnlock(hdib);
    }
    _lclose(fh);

    return hdib;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WriteDIB(LPSTR szFile,HANDLE hdib)                         *
 *                                                                          *
 *  PURPOSE    : Write a global handle in CF_DIB format to a file.          *
 *                                                                          *
 *  RETURNS    : TRUE  - if successful.                                     *
 *               FALSE - otherwise                                          *
 *                                                                          *
 ****************************************************************************/
BOOL WriteDIB (
    LPSTR szFile,
    HANDLE hdib)
{
    BITMAPFILEHEADER    hdr;
    LPBITMAPINFOHEADER  lpbi;
    HFILE               fh;
    OFSTRUCT            of;

    if (!hdib)
        return FALSE;

    fh = OpenFile(szFile, &of, (UINT)OF_CREATE|OF_READWRITE);
    if (fh == -1)
        return FALSE;

    lpbi = (VOID FAR *)GlobalLock (hdib);

    /* Fill in the fields of the file header */
    hdr.bfType          = BFT_BITMAP;
    hdr.bfSize          = GlobalSize (hdib) + SIZEOF_BITMAPFILEHEADER_PACKED;
    hdr.bfReserved1     = 0;
    hdr.bfReserved2     = 0;
    hdr.bfOffBits       = (DWORD) (SIZEOF_BITMAPFILEHEADER_PACKED + lpbi->biSize +
                          PaletteSize(lpbi));

    /* Write the file header */
#ifdef  FIXDWORDALIGNMENT
    _lwrite(fh, (LPSTR)&hdr, (UINT)(SIZEOF_BITMAPFILEHEADER_PACKED));
#else
        WriteMapFileHeaderandConvertFromDwordAlignToPacked(fh, &hdr);
#endif

        /* this struct already DWORD aligned!*/
    /* Write the DIB header and the bits */
    lwrite (fh, (LPSTR)lpbi, GlobalSize (hdib));

    GlobalUnlock (hdib);
    _lclose(fh);
    return TRUE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibInfo(HANDLE hbi,LPBITMAPINFOHEADER lpbi)                *
 *                                                                          *
 *  PURPOSE    : Retrieves the DIB info associated with a CF_DIB            *
 *               format memory block.                                       *
 *                                                                          *
 *  RETURNS    : TRUE  - if successful.                                     *
 *               FALSE - otherwise                                          *
 *                                                                          *
 ****************************************************************************/
BOOL DibInfo (
    HANDLE hbi,
    LPBITMAPINFOHEADER lpbi)
{
    if (hbi){
        *lpbi = *(LPBITMAPINFOHEADER)GlobalLock (hbi);

        /* fill in the default fields */
        if (lpbi->biSize != sizeof (BITMAPCOREHEADER)){
            if (lpbi->biSizeImage == 0L)
                                lpbi->biSizeImage = WIDTHBYTES(lpbi->biWidth*lpbi->biBitCount) * lpbi->biHeight;

            if (lpbi->biClrUsed == 0L)
                                lpbi->biClrUsed = DibNumColors (lpbi);
    }
        GlobalUnlock (hbi);
        return TRUE;
    }
    return FALSE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateBIPalette(LPBITMAPINFOHEADER lpbi)                   *
 *                                                                          *
 *  PURPOSE    : Given a Pointer to a BITMAPINFO struct will create a       *
 *               a GDI palette object from the color table.                 *
 *                                                                          *
 *  RETURNS    : A handle to the palette.                                   *
 *                                                                          *
 ****************************************************************************/
HPALETTE CreateBIPalette (LPBITMAPINFOHEADER lpbi)
{
    LOGPALETTE          *pPal;
    HPALETTE            hpal = NULL;
    WORD                nNumColors;
    BYTE                red;
    BYTE                green;
    BYTE                blue;
    WORD                i;
    RGBQUAD        FAR *pRgb;

    if (!lpbi)
        return NULL;

    if (lpbi->biSize != sizeof(BITMAPINFOHEADER))
        return NULL;

    /* Get a pointer to the color table and the number of colors in it */
    pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + (WORD)lpbi->biSize);
    nNumColors = DibNumColors(lpbi);

    if (nNumColors){
        /* Allocate for the logical palette structure */
        pPal = (LOGPALETTE*)LocalAlloc(LPTR,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
        if (!pPal)
            return NULL;

        pPal->palNumEntries = nNumColors;
        pPal->palVersion    = PALVERSION;

        /* Fill in the palette entries from the DIB color table and
         * create a logical color palette.
         */
        for (i = 0; i < nNumColors; i++){
            pPal->palPalEntry[i].peRed   = pRgb[i].rgbRed;
            pPal->palPalEntry[i].peGreen = pRgb[i].rgbGreen;
            pPal->palPalEntry[i].peBlue  = pRgb[i].rgbBlue;
            pPal->palPalEntry[i].peFlags = (BYTE)0;
        }
        hpal = CreatePalette(pPal);
        LocalFree((HANDLE)pPal);
    }
    else if (lpbi->biBitCount == 24){
        /* A 24 bitcount DIB has no color table entries so, set the number of
         * to the maximum value (256).
         */
        nNumColors = MAXPALETTE;
        pPal = (LOGPALETTE*)LocalAlloc(LPTR,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
        if (!pPal)
            return NULL;

        pPal->palNumEntries = nNumColors;
        pPal->palVersion    = PALVERSION;

        red = green = blue = 0;

        /* Generate 256 (= 8*8*4) RGB combinations to fill the palette
         * entries.
         */
        for (i = 0; i < pPal->palNumEntries; i++){
            pPal->palPalEntry[i].peRed   = red;
            pPal->palPalEntry[i].peGreen = green;
            pPal->palPalEntry[i].peBlue  = blue;
            pPal->palPalEntry[i].peFlags = (BYTE)0;

            if (!(red += 32))
                if (!(green += 32))
                    blue += 64;
        }
        hpal = CreatePalette(pPal);
        LocalFree((HANDLE)pPal);
    }
    return hpal;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateDibPalette(HANDLE hbi)                               *
 *                                                                          *
 *  PURPOSE    : Given a Global HANDLE to a BITMAPINFO Struct               *
 *               will create a GDI palette object from the color table.     *
 *               (BITMAPINFOHEADER format DIBs only)                                     *
 *                                                                          *
 *  RETURNS    : A handle to the palette.                                   *
 *                                                                          *
 ****************************************************************************/
HPALETTE CreateDibPalette (HANDLE hbi)
{
    HPALETTE hpal;

    if (!hbi)
        return NULL;
    hpal = CreateBIPalette((LPBITMAPINFOHEADER)GlobalLock(hbi));
    GlobalUnlock(hbi);
    return hpal;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : ReadDibBitmapInfo(int fh)                                  *
 *                                                                          *
 *  PURPOSE    : Will read a file in DIB format and return a global HANDLE  *
 *               to it's BITMAPINFO.  This function will work with both     *
 *               "old" (BITMAPCOREHEADER) and "new" (BITMAPINFOHEADER)      *
 *               bitmap formats, but will always return a "new" BITMAPINFO  *
 *                                                                          *
 *  RETURNS    : A handle to the BITMAPINFO of the DIB in the file.         *
 *                                                                          *
 ****************************************************************************/
HANDLE ReadDibBitmapInfo (INT fh)
{
    DWORD     off;
    HANDLE    hbi = NULL;
    INT       size;
    INT       i;
    WORD      nNumColors;

    RGBQUAD FAR       *pRgb;
    BITMAPINFOHEADER   bi;
    BITMAPCOREHEADER   bc;
    LPBITMAPINFOHEADER lpbi;
    BITMAPFILEHEADER   bf;
    DWORD              dwWidth = 0;
    DWORD              dwHeight = 0;
    WORD               wPlanes, wBitCount;

    if (fh == -1)
        return NULL;
#ifdef FIXDWORDALIGNMENT
    /* Reset file pointer and read file header */
    off = _llseek(fh, 0L, (UINT)SEEK_CUR);
    if ((SIZEOF_BITMAPFILEHEADER_PACKED)  != _lread(fh, (LPSTR)&bf, (UINT)sizeof (SIZEOF_BITMAPFILEHEADER_PACKED)))
        return FALSE;
#else
        ReadBitMapFileHeaderandConvertToDwordAlign(fh, &bf, &off);
        /* at this point we have read the file into bf*/
#endif

    /* Do we have a RC HEADER? */
    if (!ISDIB (bf.bfType)) {
        bf.bfOffBits = 0L;
                _llseek(fh, off, (UINT)SEEK_SET); /*seek back to beginning of file*/
    }
    if (sizeof (bi) != _lread(fh, (LPSTR)&bi, (UINT)sizeof(bi)))
        return FALSE;

    nNumColors = DibNumColors (&bi);

    /* Check the nature (BITMAPINFO or BITMAPCORE) of the info. block
     * and extract the field information accordingly. If a BITMAPCOREHEADER,
     * transfer it's field information to a BITMAPINFOHEADER-style block
     */
    switch (size = (INT)bi.biSize){
        case sizeof (BITMAPINFOHEADER):
            break;

        case sizeof (BITMAPCOREHEADER):

            bc = *(BITMAPCOREHEADER*)&bi;

            dwWidth   = (DWORD)bc.bcWidth;
            dwHeight  = (DWORD)bc.bcHeight;
            wPlanes   = bc.bcPlanes;
            wBitCount = bc.bcBitCount;

        bi.biSize           = sizeof(BITMAPINFOHEADER);
            bi.biWidth              = dwWidth;
            bi.biHeight             = dwHeight;
            bi.biPlanes             = wPlanes;
            bi.biBitCount           = wBitCount;

            bi.biCompression        = BI_RGB;
            bi.biSizeImage          = 0;
            bi.biXPelsPerMeter      = 0;
            bi.biYPelsPerMeter      = 0;
            bi.biClrUsed            = nNumColors;
            bi.biClrImportant       = nNumColors;

            _llseek(fh, (LONG)sizeof (BITMAPCOREHEADER) - sizeof (BITMAPINFOHEADER), (UINT)SEEK_CUR);
            break;

        default:
            /* Not a DIB! */
            return NULL;
    }

    /*  Fill in some default values if they are zero */
    if (bi.biSizeImage == 0){
        bi.biSizeImage = WIDTHBYTES ((DWORD)bi.biWidth * bi.biBitCount)
                         * bi.biHeight;
    }
    if (bi.biClrUsed == 0)
        bi.biClrUsed = DibNumColors(&bi);

    /* Allocate for the BITMAPINFO structure and the color table. */
    hbi = GlobalAlloc (GHND, (LONG)bi.biSize + nNumColors * sizeof(RGBQUAD));
    if (!hbi)
        return NULL;
    lpbi = (VOID FAR *)GlobalLock (hbi);
    *lpbi = bi;

    /* Get a pointer to the color table */
    pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + bi.biSize);
    if (nNumColors){
        if (size == sizeof(BITMAPCOREHEADER)){
            /* Convert a old color table (3 byte RGBTRIPLEs) to a new
             * color table (4 byte RGBQUADs)
             */
            _lread(fh, (LPSTR)pRgb, (UINT)nNumColors * sizeof(RGBTRIPLE));

            for (i = nNumColors - 1; i >= 0; i--){
                RGBQUAD rgb;

                rgb.rgbRed      = ((RGBTRIPLE FAR *)pRgb)[i].rgbtRed;
                rgb.rgbBlue     = ((RGBTRIPLE FAR *)pRgb)[i].rgbtBlue;
                rgb.rgbGreen    = ((RGBTRIPLE FAR *)pRgb)[i].rgbtGreen;
                rgb.rgbReserved = (BYTE)0;

                pRgb[i] = rgb;
            }
        }
        else
            _lread(fh, (LPSTR)pRgb, (UINT)nNumColors * sizeof(RGBQUAD));
    }

    if (bf.bfOffBits != 0L){
        _llseek(fh, off + bf.bfOffBits, (UINT)SEEK_SET);
        }
    GlobalUnlock(hbi);
    return hbi;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  PaletteSize(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    :  Calculates the palette size in bytes. If the info. block  *
 *                is of the BITMAPCOREHEADER type, the number of colors is  *
 *                multiplied by 3 to give the palette size, otherwise the   *
 *                number of colors is multiplied by 4.                                                          *
 *                                                                          *
 *  RETURNS    :  Palette size in number of bytes.                          *
 *                                                                          *
 ****************************************************************************/
WORD PaletteSize (VOID FAR * pv)
{
    LPBITMAPINFOHEADER lpbi;
    WORD               NumColors;

    lpbi      = (LPBITMAPINFOHEADER)pv;
    NumColors = DibNumColors(lpbi);

    if (lpbi->biSize == sizeof(BITMAPCOREHEADER))
        return (WORD)(NumColors * sizeof(RGBTRIPLE));
    else
        return (WORD)(NumColors * sizeof(RGBQUAD));
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibNumColors(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    : Determines the number of colors in the DIB by looking at   *
 *               the BitCount filed in the info block.                      *
 *                                                                          *
 *  RETURNS    : The number of colors in the DIB.                           *
 *                                                                          *
 ****************************************************************************/
WORD DibNumColors (VOID FAR * pv)
{
    INT                 bits;
    LPBITMAPINFOHEADER  lpbi;
    LPBITMAPCOREHEADER  lpbc;

    lpbi = ((LPBITMAPINFOHEADER)pv);
    lpbc = ((LPBITMAPCOREHEADER)pv);

    /*  With the BITMAPINFO format headers, the size of the palette
     *  is in biClrUsed, whereas in the BITMAPCORE - style headers, it
     *  is dependent on the bits per pixel ( = 2 raised to the power of
     *  bits/pixel).
     */
    if (lpbi->biSize != sizeof(BITMAPCOREHEADER)){
        if (lpbi->biClrUsed != 0)
            return (WORD)lpbi->biClrUsed;
        bits = lpbi->biBitCount;
    }
    else
        bits = lpbc->bcBitCount;

    switch (bits){
        case 1:
                return 2;
        case 4:
                return 16;
        case 8:
                return 256;
        default:
                /* A 24 bitcount DIB has no color table */
                return 0;
    }
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibFromBitmap()                                            *
 *                                                                          *
 *  PURPOSE    : Will create a global memory block in DIB format that       *
 *               represents the Device-dependent bitmap (DDB) passed in.    *
 *                                                                          *
 *  RETURNS    : A handle to the DIB                                        *
 *                                                                          *
 ****************************************************************************/
HANDLE DibFromBitmap (
    HBITMAP      hbm,
    DWORD            biStyle,
    WORD             biBits,
    HPALETTE     hpal)
{
    BITMAP               bm;
    BITMAPINFOHEADER     bi;
    BITMAPINFOHEADER FAR *lpbi;
    DWORD                dwLen;
    HANDLE               hdib;
    HANDLE               h;
    HDC                  hdc;

    if (!hbm)
        return NULL;

    if (hpal == NULL)
        hpal = GetStockObject(DEFAULT_PALETTE);

    GetObject(hbm,sizeof(bm),(LPSTR)&bm);

    if (biBits == 0)
        biBits =  bm.bmPlanes * bm.bmBitsPixel;

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = bm.bmWidth;
    bi.biHeight             = bm.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = biBits;
    bi.biCompression        = biStyle;
    bi.biSizeImage          = 0;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = 0;
    bi.biClrImportant       = 0;

    dwLen  = bi.biSize + PaletteSize(&bi);

    hdc = GetDC(NULL);
    hpal = SelectPalette(hdc,hpal,FALSE);
         RealizePalette(hdc);

    hdib = GlobalAlloc(GHND,dwLen);

    if (!hdib){
        SelectPalette(hdc,hpal,FALSE);
        ReleaseDC(NULL,hdc);
        return NULL;
    }

    lpbi = (VOID FAR *)GlobalLock(hdib);

    *lpbi = bi;

    /*  call GetDIBits with a NULL lpBits param, so it will calculate the
     *  biSizeImage field for us
     */
    GetDIBits(hdc, hbm, 0L, (DWORD)bi.biHeight,
        (LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);

    bi = *lpbi;
    GlobalUnlock(hdib);

    /* If the driver did not fill in the biSizeImage field, make one up */
    if (bi.biSizeImage == 0){
        bi.biSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * biBits) * bm.bmHeight;

        if (biStyle != BI_RGB)
            bi.biSizeImage = (bi.biSizeImage * 3) / 2;
    }

    /*  realloc the buffer big enough to hold all the bits */
    dwLen = bi.biSize + PaletteSize(&bi) + bi.biSizeImage;
    if (h = GlobalReAlloc(hdib,dwLen,0))
        hdib = h;
    else{
        GlobalFree(hdib);
        hdib = NULL;

        SelectPalette(hdc,hpal,FALSE);
        ReleaseDC(NULL,hdc);
        return hdib;
    }

    /*  call GetDIBits with a NON-NULL lpBits param, and actualy get the
     *  bits this time
     */
    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (GetDIBits( hdc,
                   hbm,
                   0L,
                   (DWORD)bi.biHeight,
                   (LPBYTE)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi),
                   (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS) == 0){
         GlobalUnlock(hdib);
         hdib = NULL;
         SelectPalette(hdc,hpal,FALSE);
         ReleaseDC(NULL,hdc);
         return NULL;
    }

    bi = *lpbi;
    GlobalUnlock(hdib);

    SelectPalette(hdc,hpal,FALSE);
    ReleaseDC(NULL,hdc);
    return hdib;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : BitmapFromDib(HANDLE hdib, HPALETTE hpal)                  *
 *                                                                          *
 *  PURPOSE    : Will create a DDB (Device Dependent Bitmap) given a global *
 *               handle to a memory block in CF_DIB format                  *
 *                                                                          *
 *  RETURNS    : A handle to the DDB.                                       *
 *                                                                          *
 ****************************************************************************/
HBITMAP BitmapFromDib (
    HANDLE         hdib,
    HPALETTE   hpal)
{
    LPBITMAPINFOHEADER  lpbi;
    HPALETTE            hpalT;
    HDC                 hdc;
    HBITMAP             hbm;

    StartWait();

    if (!hdib)
        return NULL;

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return NULL;

    hdc = GetDC(NULL);

    if (hpal){
        hpalT = SelectPalette(hdc,hpal,FALSE);
        RealizePalette(hdc);     // GDI Bug...????
    }

    hbm = CreateDIBitmap(hdc,
                (LPBITMAPINFOHEADER)lpbi,
                (LONG)CBM_INIT,
                (LPSTR)lpbi + lpbi->biSize + PaletteSize(lpbi),
                (LPBITMAPINFO)lpbi,
                DIB_RGB_COLORS );

    if (hpal)
        SelectPalette(hdc,hpalT,FALSE);

    ReleaseDC(NULL,hdc);
    GlobalUnlock(hdib);

    EndWait();

    return hbm;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DrawBitmap(HDC hdc, int x, int y, HBITMAP hbm, DWORD rop)  *
 *                                                                          *
 *  PURPOSE    : Draws bitmap <hbm> at the specifed position in DC <hdc>    *
 *                                                                          *
 *  RETURNS    : Return value of BitBlt()                                   *
 *                                                                          *
 ****************************************************************************/
BOOL DrawBitmap (
    HDC    hdc,
    INT    x,
    INT    y,
    HBITMAP    hbm,
    DWORD          rop)
{
    HDC       hdcBits;
    BITMAP    bm;
//    HPALETTE  hpalT;
    BOOL      f;

    if (!hdc || !hbm)
        return FALSE;

    hdcBits = CreateCompatibleDC(hdc);
    GetObject(hbm,sizeof(BITMAP),(LPSTR)&bm);
    SelectObject(hdcBits,hbm);
    f = BitBlt(hdc,0,0,bm.bmWidth,bm.bmHeight,hdcBits,0,0,rop);
    DeleteDC(hdcBits);

    return f;
        UNREFERENCED_PARAMETER(y);
        UNREFERENCED_PARAMETER(x);
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibBlt( HDC hdc,                                           *
 *                       int x0, int y0,                                    *
 *                       int dx, int dy,                                    *
 *                       HANDLE hdib,                                       *
 *                       int x1, int y1,                                    *
 *                       LONG rop)                                          *
 *                                                                          *

*  PURPOSE    : Draws a bitmap in CF_DIB format, using SetDIBits to device.*
 *               taking the same parameters as BitBlt().                    *
 *                                                                          *
 *  RETURNS    : TRUE  - if function succeeds.                              *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
BOOL DibBlt (
    HDC    hdc,
    INT    x0,
    INT    y0,
    INT    dx,
    INT    dy,
    HANDLE hdib,
    INT    x1,
    INT    y1,
    LONG   rop)
{
    LPBITMAPINFOHEADER   lpbi;
//    HPALETTE           hpal,hpalT;
    LPSTR                pBuf;
//    HDC                hdcMem;
//    HBITMAP            hbm,hbmT;

    if (!hdib)
        return PatBlt(hdc,x0,y0,dx,dy,rop);

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return FALSE;

    pBuf = (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi);
    SetDIBitsToDevice (hdc, x0, y0, dx, dy,
                       x1,y1,
                       x1,
                       dy,
                       pBuf, (LPBITMAPINFO)lpbi,
                       DIB_RGB_COLORS );

    GlobalUnlock(hdib);
    return TRUE;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : StretchDibBlt( HDC hdc,                                    *
 *                              int x, int y,                               *
 *                              int dx, int dy,                             *
 *                              HANDLE hdib,                                *
 *                              int x0, int y0,                             *
 *                              int dx0, int dy0,                           *
 *                              LONG rop)                                   *
 *                                                                          *
 *  PURPOSE    : Draws a bitmap in CF_DIB format, using StretchDIBits()     *
 *               taking the same parameters as StretchBlt().                *
 *                                                                          *
 *  RETURNS    : TRUE  - if function succeeds.                              *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
BOOL StretchDibBlt (
    HDC hdc,
    INT x,
    INT y,
    INT dx,
    INT dy,
    HANDLE hdib,
    INT x0,
    INT y0,
    INT dx0,
    INT dy0,
    LONG rop)

{
    LPBITMAPINFOHEADER lpbi;
    LPSTR        pBuf;
    BOOL         f;

    if (!hdib)
        return PatBlt(hdc,x,y,dx,dy,rop);

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return FALSE;

    pBuf = (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi);

    f = StretchDIBits ( hdc,
                        x, y,
                        dx, dy,
                        x0, y0,
                        dx0, dy0,
                        pBuf, (LPBITMAPINFO)lpbi,
                        DIB_RGB_COLORS,
                        rop);

    GlobalUnlock(hdib);
    return f;
}

 /************* PRIVATE ROUTINES TO READ/WRITE MORE THAN 64K ***************/
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : lread(int fh, VOID FAR *pv, DWORD ul)                      *
 *                                                                          *
 *  PURPOSE    : Reads data in steps of 32k till all the data has been read.*
 *                                                                          *
 *  RETURNS    : 0 - If read did not proceed correctly.                     *
 *               number of bytes read otherwise.                            *
 *                                                                          *
 ****************************************************************************/
DWORD PASCAL lread (
    INT       fh,
    VOID FAR      *pv,
    DWORD             ul)
{
    DWORD     ulT = ul;
    BYTE *hp = pv;

    while (ul > (DWORD)MAXREAD) {
        if (_lread(fh, (LPSTR)hp, (UINT)MAXREAD) != MAXREAD)
                return 0;
        ul -= MAXREAD;
        hp += MAXREAD;
    }
    if (_lread(fh, (LPSTR)hp, (UINT)ul) != (UINT)ul)
        return 0;
    return ulT;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : lwrite(int fh, VOID FAR *pv, DWORD ul)                     *
 *                                                                          *
 *  PURPOSE    : Writes data in steps of 32k till all the data is written.  *
 *                                                                          *
 *  RETURNS    : 0 - If write did not proceed correctly.                    *
 *               number of bytes written otherwise.                         *
 *                                                                          *
 ****************************************************************************/
DWORD PASCAL lwrite (
    INT      fh,
    VOID FAR     *pv,
    DWORD            ul)
{
    DWORD     ulT = ul;
    BYTE *hp = pv;

    while (ul > MAXREAD) {
        if (_lwrite(fh, (LPSTR)hp, (UINT)MAXREAD) != MAXREAD)
                return 0;
        ul -= MAXREAD;
        hp += MAXREAD;
    }
    if (_lwrite(fh, (LPSTR)hp, (UINT)ul) != (UINT)ul)
        return 0;

    return ulT;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : ReadBitMapFileHeaderandConvertToDwordAlign(HFILE fh, LPBITMAPFILEHEADER pbf)
 *                                                                          *
 *  PURPOSE    : read file header (which is packed) and convert into unpacked BITMAPFILEHEADER strucutre
 *                                                                          *
 *  RETURNS    : VOID
 *                                                                          *
 ****************************************************************************/

VOID ReadBitMapFileHeaderandConvertToDwordAlign(HFILE fh, LPBITMAPFILEHEADER pbf, LPDWORD lpdwoff)
{
        DWORD off;

        off = _llseek(fh, 0L, (UINT) SEEK_CUR);
        *lpdwoff = off;

/*              BITMAPFILEHEADER STRUCUTURE is as follows
 *              BITMAPFILEHEADER
 *              WORD    bfType
 >          ....                  <     add WORD if packed here!
 *              DWORD   bfSize
 *              WORD    bfReserved1
 *              WORD    bfReserved2
 *              DWORD   bfOffBits
 *                      This is the packed format, unpacked adds a WORD after bfType
 */

        /* read in bfType*/
        _lread(fh, (LPSTR) &pbf->bfType, sizeof(WORD));
        /* read in last 3 dwords*/
        _lread(fh, (LPSTR) &pbf->bfSize, sizeof(DWORD) * 3);

}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WriteMapFileHeaderandConvertFromDwordAlignToPacked(HFILE fh, LPBITMAPFILEHEADER pbf)
 *                                                                          *
 *  PURPOSE    : write header structure (which NOT packed) and write it PACKED
 *                                                                          *
 *  RETURNS    : VOID
 *                                                                          *
 ****************************************************************************/

VOID WriteMapFileHeaderandConvertFromDwordAlignToPacked(HFILE fh, LPBITMAPFILEHEADER pbf)
{

        /* write bfType*/
    _lwrite(fh, (LPSTR)&pbf->bfType, (UINT)sizeof (WORD));
        /* now pass over extra word, and only write next 3 DWORDS!*/
        _lwrite(fh, (LPSTR)&pbf->bfSize, sizeof(DWORD) * 3);
}


DLGOPEN.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*******************************************************************************
 *                                                                             *
 *  MODULE      : DLGOPEN.C                                                    *
 *                                                                             *
 *  DESCRIPTION : Routines to display a standard File/Open and File/Save       *
 *                dialog boxes.                                                *
 *                                                                             *
 *  FUNCTIONS   : DlgOpenFile() - Displays a dialog box for opening or saving a*
 *                                file.                                        *
 *                                                                             *
 *                DlgfnOpen()   - Dialog function for the above dialog.        *
 *                                                                             *
 *                AddExt()      - Adds an extension to a filename if not       *
 *                                already present.                             *
 *                                                                             *
 *                FSearchSpec() - Checks if given string contains a wildcard   *
 *                                character.                                   *
 *                                                                             *
 *                FillListBox() - Fills listbox with files that match specs.   *
 *                                                                             *
 *                DlgCheckOkEnable() - Enables <OK> button iff there's text in *
 *                                     the edit control.                       *
 *                                                                             *
 *                NOTE : These routines require that the app. be running       *
 *                       SS = DS since they use near pointers into the stack.  *
 *                                                                             *
 *******************************************************************************/
#include <windows.h>
#include <string.h>
#include "showdib.h"

static PSTR         szExt;
static PSTR         szFileName;
static PSTR         szTitle;
static DWORD        flags;
static WORD         fOpt;

/* Forward declarations of helper functions */

static VOID  NEAR DlgCheckOkEnable(HWND hwnd, INT idEdit, UINT message);
static CHAR *NEAR FillListBox (HWND,CHAR*, UINT);
static BOOL  NEAR FSearchSpec (CHAR*);
static VOID  NEAR AddExt (CHAR*,CHAR*);

#define DLGOPEN_UNUSED   0

/* Mask to eliminate bogus style and bitcount combinations ...
 * RLE formats, if chosen should be matched with the bitcounts:
 *   RLE4 scheme should be used only for 4 bitcount DIBs.
 *   RLE8   "      "     "   "   "    "  8   "       "
 *
 * BITCOUNTMASK is indexed by DLGOPEN_RLE4 >> 4, DLGOPEN_RLE8 >> 4
 * and DLGOPEN_RLE8 >> 4
 */

static WORD BITCOUNTMASK[] = { DLGOPEN_UNUSED,
                               DLGOPEN_1BPP | DLGOPEN_8BPP | DLGOPEN_24BPP,
                               DLGOPEN_1BPP | DLGOPEN_4BPP | DLGOPEN_24BPP,
                               DLGOPEN_UNUSED,
                               0 };

/*******************************************************************************
 *                                                                             *
 *  FUNCTION   :DlgOpen(LPSTR szFile)                                          *
 *                                                                             *
 *  PURPOSE    :Display dialog box for opening files. Allow user to interact   *
 *              with dialogbox, change directories as necessary, and try to    *
 *              open file if user selects one. Automatically append            *
 *              extension to filename if necessary.                            *
 *              This routine correctly parses filenames containing KANJI       *
 *              characters.                                                    *
 *                                                                             *
 *  RETURNS    :  - Handle to the opened file if legal filename.               *
 *                - 0 if user presses <cancel>                                 *
 *                - 1 if filename entered is illegal                           *
 *                                                                             *
 *******************************************************************************/
HFILE APIENTRY DlgOpenFile (
    HWND          hwndParent,
    CHAR          *szTitleIn,
    DWORD         flagsIn,
    CHAR          *szExtIn,
    CHAR          *szFileNameIn,
    WORD          *pfOpt)
{
    INT      fh;
    FARPROC  lpProc;
    CHAR     achFile[128];
    CHAR     achExt[128];
    HANDLE   hInstance;
    WORD     w;

    if (pfOpt == NULL)
        pfOpt = &w;

    flags    = flagsIn;
    fOpt     = *pfOpt;

    lstrcpy (szFileName = achFile, szFileNameIn);
    lstrcpy (szExt = achExt, szExtIn);
    szTitle = szTitleIn;

    hInstance = (HANDLE)GetWindowLong (hwndParent, GWL_HINSTANCE);

    /* Show the dialog box */
    lpProc = MakeProcInstance ((FARPROC)DlgfnOpen, hInstance);
    fh = DialogBox (hInstance, "DlgOpenBox", hwndParent, (DLGPROC)lpProc);
    FreeProcInstance (lpProc);

    if (fh != 0){
        lstrcpy (szFileNameIn, szFileName);
        *pfOpt = fOpt;
    }
    return fh;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :DlgfnOpen (hwnd, msg, wParam, lParam)                       *
 *                                                                          *
 *  PURPOSE    :Dialog function for File/Open dialog.                       *
 *                                                                          *
 ****************************************************************************/
LONG APIENTRY DlgfnOpen (
    HWND hwnd,
    UINT msg,
    UINT wParam,
    LONG lParam)
{
    INT      result = -1;    /* Assume illegal filename initially */
    int      w;
    LPSTR    lpsz;
    WORD     f;
    OFSTRUCT of;
    RECT     rc, rcCtl;
    HWND     hwndT;
    BOOL     fEnable;
    CHAR     lpBuffer[128];

    switch (msg) {
        case WM_INITDIALOG:
            if (szTitle && *szTitle)
                SetWindowText (hwnd, szTitle);

            /* Set text on <OK> button according to mode (File/Open or File/Save) */
            if (flags & OF_SAVE) {
                LoadString(hInst, IDS_SAVESTR, lpBuffer, sizeof(lpBuffer));
                SetDlgItemText(hwnd, IDOK, lpBuffer);
            }
            if (flags & OF_OPEN) {
                LoadString(hInst, IDS_OPENSTR, lpBuffer, sizeof(lpBuffer));
                SetDlgItemText(hwnd, IDOK, lpBuffer);
            }
            if ((flags & OF_NOOPTIONS) &&
                (hwndT = GetDlgItem(hwnd,DLGOPEN_FOLDOUT)))
                EnableWindow (hwndT,FALSE);

            if (hwndT = GetDlgItem (hwnd, DLGOPEN_SMALL)) {
                GetWindowRect (hwnd,&rc);
                GetWindowRect (GetDlgItem(hwnd,DLGOPEN_SMALL),&rcCtl);

                SetWindowPos (hwnd,
                              NULL,
                              0,
                              0,
                              rcCtl.left - rc.left,
                              rc.bottom - rc.top,
                              SWP_NOZORDER | SWP_NOMOVE);
            }
            /* fill list box with filenames that match specifications, and
             * fill static field with path name.
             */
            FillListBox(hwnd,szExt, WM_INITDIALOG);

            /* If in Save mode, set the edit control with default (current)
             * file name,and select the corresponding entry in the listbox.
             */
            if ((flags & OF_SAVE) && *szFileName) {
                        SetDlgItemText (hwnd, DLGOPEN_EDIT, szFileName);
                        SendDlgItemMessage (hwnd,
                                    DLGOPEN_FILE_LISTBOX,
                                    LB_SELECTSTRING,
                                    0,
                                    (LONG)(LPSTR)szFileName);
            }
            else {
                /*  Set the edit field with the default extensions... */
                if (flags & OF_NOSHOWSPEC)
                    SetDlgItemText (hwnd, DLGOPEN_EDIT, "");
                else
                    SetDlgItemText (hwnd, DLGOPEN_EDIT, szExt);
            }
            /*  ...and select all text in the edit field */
                /* JAP added HWND cast*/
            SendMessage((HWND)GetDlgItem(hwnd, DLGOPEN_EDIT), EM_SETSEL, GET_EM_SETSEL_MPS(0, 0x7fff));

            /*  check all options that are set */
            for ( f = DLGOPEN_1BPP; f; f<<=1)
                CheckDlgButton(hwnd, (INT)FID(f), (WORD) (fOpt & f));

            break;

        case WM_COMMAND:
    switch (LOWORD(wParam)) {
                case IDOK:
                    if (IsWindowEnabled (GetDlgItem(hwnd, IDOK))) {
                        /* Get contents of edit field and add search spec. if it
                         * does not contain one.
                         */
                        GetDlgItemText (hwnd, DLGOPEN_EDIT, (LPSTR)szFileName, 128);

            lpsz = CharPrev(szFileName, szFileName + lstrlen(szFileName));
            switch (*lpsz) {
                            case '\\':
                            case '/':
                                *lpsz = 0;
                            break;
                        }
                        if (SetCurrentDirectory ((LPSTR)szFileName))
                            lstrcpy (szFileName,szExt);

                        /*  Try to open path.  If successful, fill listbox with
                         *  contents of new directory.  Otherwise, open datafile.
                         */
                        if (FSearchSpec(szFileName)) {
                            lstrcpy (szExt, FillListBox (hwnd, szFileName, WM_COMMAND));
                            if (flags & OF_NOSHOWSPEC) {
                                SetDlgItemText (hwnd, DLGOPEN_EDIT, "");
                            } else {
                                SetDlgItemText (hwnd, DLGOPEN_EDIT, szExt);
                            }
                            break;
                        }

                        /*  Make filename upper case and if it's a legal DOS
                         *  name, try to open the file.
                         */
                        CharUpper(szFileName);
                        AddExt(szFileName,szExt);
                        result = (INT)OpenFile(szFileName, &of, (WORD)flags);

                        if (result != -1) {
                            lstrcpy(szFileName,of.szPathName);
                        }
                        else if (flags & OF_MUSTEXIST) {
                            MessageBeep(0);
                            return 0L;
                        }

                        /*  Get the state of all checked options */
                        for (f = DLGOPEN_1BPP; f; f <<= 1){
                            if (IsDlgButtonChecked (hwnd, FID (f)))
                                fOpt |= f;
                            else
                                fOpt &= ~f;
                        }

                        EndDialog (hwnd, result);
                    }
                    break;

                case DLGOPEN_OPTION + DLGOPEN_RLE4:
                case DLGOPEN_OPTION + DLGOPEN_RLE8:
                case DLGOPEN_OPTION + DLGOPEN_RGB:
                    /* Mask out incompatible bitcount options and gray the
                     * appropriate radiobuttons.
                     */
                    for (f = DLGOPEN_1BPP; f <= DLGOPEN_24BPP; f <<= 1){
                        fEnable = !(f & BITCOUNTMASK [IDF(w) >> 4 ]);
                        EnableWindow (GetDlgItem (hwnd, FID(f)), fEnable);

                        /* If the radiobutton is being grayed, uncheck it and
                         * and check an "allowed" option so the bitcount group
                         * is still accessible via the keyboard
                         */
                        if (!fEnable && IsDlgButtonChecked (hwnd, FID (f))){
                            CheckDlgButton(hwnd, FID(f), FALSE);
                            CheckDlgButton(hwnd, FID(IDF(w) >> 3), TRUE);
                        }
                    }
                    break;

                case IDCANCEL:
                    /* User pressed cancel.  Just take down dialog box. */
                    EndDialog (hwnd, 0);
                    break;

                /*  User single clicked or doubled clicked in listbox -
                 *  Single click means fill edit box with selection.
                 *  Double click means go ahead and open the selection.
                 */
                case DLGOPEN_FILE_LISTBOX:
                case DLGOPEN_DIR_LISTBOX:
                    switch (GET_WM_COMMAND_CMD(wParam, lParam)) {
                        /* Single click case */
                        case LBN_SELCHANGE:
                            /* Get selection, which may be either a prefix to a
                             * new search path or a filename. DlgDirSelectEx parses
                             * selection, and appends a backslash if selection
                             * is a prefix
                             */
                            DlgDirSelectEx(hwnd, szFileName, 128, LOWORD(wParam));
            lpsz = CharPrev(szFileName, szFileName + lstrlen(szFileName));
                switch (*lpsz) {
                                case ':':
                                    lstrcat (szFileName,".");
                                    break;
                                case '\\':
                                case '/':
                                    *lpsz = 0;
                                    break;
                            }
                            SetDlgItemText(hwnd, DLGOPEN_EDIT, szFileName);
                            break;
                        /* Double click case - first click has already been
                         * processed as single click
                         */
                        case LBN_DBLCLK:
                            PostMessage (hwnd,WM_COMMAND,IDOK,0L);
                            break;
                    }
                    break;

                case DLGOPEN_EDIT:
                    DlgCheckOkEnable(hwnd, DLGOPEN_EDIT, HIWORD(lParam));
                    break;

                case DLGOPEN_FOLDOUT:
                    GetWindowRect(hwnd,&rc);
                    GetWindowRect(GetDlgItem(hwnd,DLGOPEN_BIG),&rcCtl);

                    if ((rcCtl.left <= rc.right) && (rcCtl.top <= rc.bottom))
                         GetWindowRect (GetDlgItem (hwnd, DLGOPEN_SMALL), &rcCtl);

                    SetWindowPos (hwnd,
                                  NULL,
                                  0,
                                  0,
                                  rcCtl.left - rc.left,
                                  rc.bottom - rc.top,
                                  SWP_NOZORDER | SWP_NOMOVE);
                    break;
            }
        default:
            return FALSE;
    }
    return TRUE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : static void NEAR DlgCheckOkEnable(hwnd, idEdit, message)   *
 *                                                                          *
 *  PURPOSE    : Enables the <OK> button in a dialog box iff the edit item  *
 *               contains text.                                             *
 *                                                                          *
 ****************************************************************************/
static VOID NEAR DlgCheckOkEnable(
    HWND        hwnd,
    INT idEdit,
    UINT message)
{
    if (message == EN_CHANGE) {
        EnableWindow ( GetDlgItem (hwnd, IDOK),
                       (BOOL)SendMessage (GetDlgItem (hwnd, idEdit),
                                          WM_GETTEXTLENGTH,
                                          0, 0L));
    }
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : AddExt (pch, ext)                                          *
 *                                                                          *
 *  PURPOSE    : Add an extension to a filename if none is already specified*
 *                                                                          *
 ****************************************************************************/
static VOID NEAR AddExt (
    CHAR *pch,    /* File name    */
    CHAR *ext)    /* Extension to add */
{
    CHAR acExt[20];
    CHAR *pext = acExt;

    while (*ext && *ext != '.') {
        ext = CharNext(ext);
    }
    while (*ext && *ext != ';') {
        if (IsDBCSLeadByte(*ext)) {
            *pext++ = *ext++;
        }
        *pext++ = *ext++;
    }
    *pext = 0;
    pext = acExt;

    while (*pch == '.') {
        pch++;
        if ((*pch == '.') && pch[1] == '\\')
            pch += 2;                       /* ..\ */
        if (*pch == '\\')
            pch++;                         /* .\ */
    }
    while (*pch != '\0') {
if (*pch == '.')
    return;
pch = CharNext(pch);
    }

    // *pch++ = '.';
    do
        *pch++ = *pext;
    while (*pext++ != '\0');
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : FSearchSpec (sz)                                           *
 *                                                                          *
 *  PURPOSE    : Checks to see if NULL-terminated strings contains a "*" or *
 *               a "?".                                                     *
 *                                                                          *
 *  RETURNS    : TRUE  - if the above characters are found in the string    *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
static BOOL NEAR FSearchSpec(CHAR       *sz)
{
    for (; *sz ;sz = CharNext(sz)) {
        if (*sz == '*' || *sz == '?')
            return TRUE;
    }
    return FALSE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : static char * NEAR FillListBox (hDlg,pFile, cmd)                   *
 *                                                                          *
 *  PURPOSE    : Fill list box with filenames that match specifications, and*
 *               fills the static field with the path name.                 *
 *                                                                          *
 *  RETURNS    : A pointer to the pathname.                                                           *
 *                                                                          *
 ****************************************************************************/
static CHAR * NEAR FillListBox (
    HWND  hDlg,
    CHAR  *pFile,  /* [path]{list of file wild cards, separated by ';'} */
    UINT cmd)    /* if initdialog, or WM_COMMAND*/
{
    CHAR  ach[128];
    CHAR  *pch;
    CHAR  *pDir;   /* Directory name or path */
    CHAR  pCurDir[256];

    pch  = pFile;
    pDir = ach;
    if(cmd == WM_INITDIALOG){
    while (*pch && *pch != ';')
        pch = CharNext(pch);
    while ((pch > pFile) && (*pch != '/') && (*pch != '\\'))
        pch = CharPrev(pFile, pch);
    if (pch > pFile) {
       *pch = 0;
       lstrcpy (pDir, pFile);
       pFile = pch + 1;
    }
    else {
       lstrcpy (pDir,".");
    }
    }
    else{
        /* since SetCurrentDirectory was called already, I'll use GetCurrentDirectory*/
        /* to get pDir*/
        GetCurrentDirectory(256, pCurDir);
        strcpy(pDir, pCurDir);
    }
    DlgDirList (hDlg, pDir, (INT)DLGOPEN_DIR_LISTBOX, (INT)DLGOPEN_PATH,(WORD)ATTRDIRLIST);
    SendDlgItemMessage (hDlg, DLGOPEN_FILE_LISTBOX, LB_RESETCONTENT, 0, 0L);
    SendDlgItemMessage (hDlg, DLGOPEN_FILE_LISTBOX, WM_SETREDRAW, FALSE, 0L);
    pDir = pFile;            /* save pFile to return */
    while (*pFile) {
        pch = ach;
        while (*pFile==' ')
            pFile++;
        while (*pFile && *pFile != ';') {
        if (IsDBCSLeadByte(*pFile)) {
        *pch++ = *pFile++;
        }
        *pch++ = *pFile++;
    }
        *pch = 0;
        if (*pFile)
            pFile++;
        SendDlgItemMessage (hDlg,
                            DLGOPEN_FILE_LISTBOX,
                            LB_DIR,ATTRFILELIST,
                            (LONG)(LPSTR)ach);
    }
    SendDlgItemMessage (hDlg, DLGOPEN_FILE_LISTBOX, WM_SETREDRAW, TRUE, 0L);
    InvalidateRect (GetDlgItem (hDlg, DLGOPEN_FILE_LISTBOX), NULL, TRUE);

    return pDir;
}


DRAWDIB.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*******************************************************************************
 *                                                                             *
 *  MODULE      : DrawDIB.c                                                    *
 *                                                                             *
 *  PURPOSE     : Handles most of the SHOWDIB's DIB drawing and clipboard      *
 *                operations.                                                  *
 *                                                                             *
 *  FUNCTIONS   :                                                              *
 *                PrintDIB()             -  Sets the current DIB bits to the   *
 *                                          printer DC.                        *
 *                                                                             *
 *                AppPaint()             -  Sets the DIB/bitmap bits on the    *
 *                                          screen or the given device.        *
 *                                                                             *
 *                DrawSelect()           -  Draws selected clip rectangle on   *
 *                                          the DC/screen.                     *
 *                                                                             *
 *                NormalizeRect()        -  Swaps reversed rectangle coords.   *
 *                                                                             *
 *                TrackMouse()           -  Draws rubberbanding rectangle and  *
 *                                          displays it's dimensions.          *
 *                                                                             *
 *                BandDIB()              -  Outputs DIB in bands to device.    *
 *                                                                             *
 *                SizeWindow()           -  Sizes app. window based on client  *
 *                                          dimensions and style.              *
 *                                                                             *
 *                GetRealClientRect()    -  Calculates client rectangle dimen- *
 *                                          sions if scrollbars are present.   *
 *                                                                             *
 *                SetScrollRanges()      -  Sets global scroll ranges.         *
 *                                                                             *
 *                CopyHandle()           -  Makes a copy of memory block.      *
 *                                                                             *
 *                CopyPalette()          -  Makes a copy of the GDI logical    *
 *                                          palette.                           *
 *                                                                             *
 *                CopyBitmap()           -  Copies given bitmap to another.    *
 *                                                                             *
 *                CropBitmap()           -  Crops a bitmap to the given size.  *
 *                                                                             *
 *                RenderFormat()         -  renders currently displayed DIB    *
 *                                          in CF_BITMAP or CF_DIB format.     *
 *                                                                             *
 *                RealizeDibFormat()     -  Realizes the DIB in given format.  *
 *                                                                             *
 *                ErrMsg()               -  Pops an error message to user.     *
 *                                                                             *
 *                fDialog()              -  Displays a dialog box.             *
 *                                                                             *
 *                AppAbout()             -  Shows the About.. dialog box.      *
 *                                                                             *
 *******************************************************************************/
#include <windows.h>
#include <io.h>
#include <stdio.h>
#include "showdib.h"

MPOINT                ptSize;       /* Stores DIB dimensions                   */

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  PrintDIB(HWND hWnd, HDC hDC, int x, int y, int dx, int dy)*
 *                                                                          *
 *  PURPOSE    :  Set the DIB bits to the printer DC.                       *
 *                                                                          *
 ****************************************************************************/
VOID PrintDIB (
    HWND hWnd,
    HDC hDC,
    INT x,
    INT y,
    INT dx,
    INT dy)

{
    BITMAPINFOHEADER bi;
    INT dibX,  dibY;
    INT dibDX, dibDY;

    if (!bLegitDraw)
        return;

    DibInfo (hbiCurrent, &bi);

    if (IsRectEmpty (&rcClip)){
        dibX  = 0;
        dibY  = 0;
        dibDX = (INT)bi.biWidth;
        dibDY = (INT)bi.biHeight;
    }
    else{
        dibX  = rcClip.left;
        dibY  = (INT)bi.biHeight - 1 - rcClip.bottom;
        dibDX = rcClip.right  - rcClip.left;
        dibDY = rcClip.bottom - rcClip.top;
    }

    if (hdibCurrent){
        /* Stretch the DIB to printer DC */
        StretchDibBlt ( hDC,
                        x,
                        y,
                        dx,
                        dy,
                        hdibCurrent,
                        dibX,
                        dibY,
                        dibDX,
                        dibDY,
                        SRCCOPY);
    }
    else if (achFileName[0]) {

        SetMapMode (hDC, MM_ANISOTROPIC);
        (VOID)SetViewportOrgEx (hDC, x, y, NULL);
        (VOID)SetViewportExtEx (hDC, dx, dy, NULL);

        BandDIB (hWnd, hDC, 0, 0);
    }
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  AppPaint(HWND hWnd, HDC hDC, int x, int y)                *
 *                                                                          *
 *  PURPOSE    :  Sets the DIB/bitmap bits on the screen or the given device*
 *                                                                          *
 ****************************************************************************/
VOID AppPaint (
    HWND hWnd,
    HDC hDC,
    INT x,
    INT y)
{
    HPALETTE hpalT;
    BITMAPINFOHEADER bi;
//    LPBITMAPINFOHEADER lpbi;

    (VOID)SetWindowOrgEx (hDC, x, y, NULL);
    SetBkMode (hDC, wTransparent);

    if (bLegitDraw) {
                hpalT = SelectPalette (hDC, hpalCurrent, FALSE);
                RealizePalette (hDC);

        if (hbmCurrent && !bDIBToDevice) {
            DrawBitmap (hDC, 0, 0, hbmCurrent, SRCCOPY);
        }
        else if (hdibCurrent) {
            DibInfo (hdibCurrent, &bi);
            DibBlt (hDC,
                    0,
                    0,
                    (INT)bi.biWidth,
                    (INT)bi.biHeight,
                    hdibCurrent,
                    0,
                    0,
                    SRCCOPY);
        }
        else if (achFileName[0]) {
            BandDIB (hWnd, hDC, 0, 0);
        }

        SelectPalette(hDC,hpalT,FALSE);
    }

    DrawSelect(hDC, TRUE);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  DrawSelect(HDC hdc, BOOL fDraw)                           *
 *                                                                          *
 *  PURPOSE    :  Draws the selected clip rectangle with its dimensions on  *
 *                the DC/screen                                             *
 *                                                                          *
 ****************************************************************************/
VOID DrawSelect(
    HDC hdc,
    BOOL fDraw)
{
    CHAR  sz[80];
    INT   x,y,len,dx,dy;
    HDC   hdcBits;
    HBITMAP hbm;

    if (!IsRectEmpty (&rcClip)) {

        /* If a rectangular clip region has been selected, draw it */
        PatBlt(hdc, rcClip.left,    rcClip.top,        rcClip.right-rcClip.left, 1,  DSTINVERT);
        PatBlt(hdc, rcClip.left,    rcClip.bottom, 1, -(rcClip.bottom-rcClip.top),   DSTINVERT);
        PatBlt(hdc, rcClip.right-1, rcClip.top, 1,   rcClip.bottom-rcClip.top,   DSTINVERT);
        PatBlt(hdc, rcClip.right,   rcClip.bottom-1, -(rcClip.right-rcClip.left), 1, DSTINVERT);

        /* Format the dimensions string ...*/
        sprintf( sz,
                  "%dx%d",
                  rcClip.right  - rcClip.left,
                  rcClip.bottom - rcClip.top );
     len = lstrlen(sz);

        /* ... and center it in the rectangle */
        { SIZE size;
        (VOID)GetTextExtentPoint(hdc, sz, len, &size);
        dx = size.cx; dy = size.cy;
        }
        x  =  (rcClip.right  + rcClip.left - dx) / 2;
        y  =  (rcClip.bottom + rcClip.top  - dy) / 2;

        hdcBits = CreateCompatibleDC (hdc);
        SetTextColor (hdcBits, 0xFFFFFFL);
        SetBkColor (hdcBits, 0x000000L);

        /* Output the text to the DC */
        /*if (hbm = +++CreateBitmap - Not Recommended(use CreateDIBitmap)+++ (dx, dy, 1, 1, NULL)){*/
        if (hbm = CreateBitmap(dx, dy, 1, 1, NULL)){
            hbm = SelectObject (hdcBits, hbm);
            ExtTextOut (hdcBits, 0, 0, 0, NULL, sz, len, NULL);
            BitBlt (hdc, x, y, dx, dy, hdcBits, 0, 0, SRCINVERT);
            hbm = SelectObject (hdcBits, hbm);
            DeleteObject (hbm);
        }
        DeleteDC (hdcBits);
        UNREFERENCED_PARAMETER(fDraw);
    }
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : NormalizeRect(RECT *prc)                                   *
 *                                                                          *
 *  PURPOSE    : If the rectangle coordinates are reversed, swaps them      *
 *                                                                          *
 ****************************************************************************/
VOID PASCAL NormalizeRect (RECT *prc)
{
    if (prc->right < prc->left)
        SWAP(prc->right,prc->left);
    if (prc->bottom < prc->top)
        SWAP(prc->bottom,prc->top);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : TrackMouse(HWND hwnd, POINT pt)                            *
 *                                                                          *
 *  PURPOSE    : Draws a rubberbanding rectangle and displays it's          *
 *               dimensions till the mouse button is released               *
 *                                                                          *
 ****************************************************************************/
VOID TrackMouse (
    HWND hwnd,
    MPOINT pt)
{
//    MPOINT ptBase;
    HDC   hdc;
    MSG   msg;
    MPOINT ptOrigin;
    RECT  rcClient;

    hdc = GetDC(hwnd);
    SetCapture(hwnd);

    GetClientRect(hwnd,&rcClient);

    /* Get mouse coordinates relative to origin of DIB */
    ptOrigin.x = (short int)GetScrollPos(hwnd,SB_HORZ);
    ptOrigin.y = (short int)GetScrollPos(hwnd,SB_VERT);

    pt.x += ptOrigin.x;
    pt.y += ptOrigin.y;

    /* Display the coordinates */
    (VOID)SetWindowOrgEx(hdc, ptOrigin.x, ptOrigin.y, NULL);
    DrawSelect(hdc,FALSE);

    /* Initialize clip rectangle to the point */
    rcClip.left   = pt.x;
    rcClip.top    = pt.y;
    rcClip.right  = pt.x;
    rcClip.bottom = pt.y;

    /* Eat mouse messages until a WM_LBUTTONUP is encountered. Meanwhile
     * continue to draw a rubberbanding rectangle and display it's dimensions
     */
    for (;;){
        WaitMessage();
        if (PeekMessage(&msg,NULL,WM_MOUSEFIRST,WM_MOUSELAST,PM_REMOVE)){
            DrawSelect(hdc,FALSE);

            rcClip.left   = pt.x;
            rcClip.top    = pt.y;
            rcClip.right  = LOWORD(msg.lParam) + ptOrigin.x;
            rcClip.bottom = HIWORD(msg.lParam) + ptOrigin.y;

            NormalizeRect(&rcClip);
            DrawSelect(hdc,TRUE);

            if (msg.message == WM_LBUTTONUP)
                break;
        }
        else
            continue;
    }

    ReleaseCapture();
    ReleaseDC(hwnd,hdc);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : BandDIB(HWND hWnd, HDC hDC, int x, int y)                  *
 *                                                                          *
 *  PURPOSE    : Outputs the DIB in bands to a device or the screen, using  *
 *               the maximum possible band size.                            *
 *                                                                          *
 ****************************************************************************/
VOID BandDIB (
    HWND hWnd,
    HDC hDC,
    INT x,
    INT y)
{
    HBITMAP            hBitmap, hOld ;
    HDC                hMemDC ;
    LPSTR              pBuf;
    LPBITMAPINFOHEADER lpbi;
    WORD               wRead, wActualPosition, wScansLeft  ;
    DWORD              dwMapSize;
    DWORD              dwScans;
    WORD               wBitmapHeight;
    RECT               Rect;
    HANDLE             hBuf;
    BOOL               bSuccess = FALSE;
    INT                nBandSize;
    HPALETTE           hOldMemPal;
    HPALETTE           hOldPal;
    HFILE              fh;
    OFSTRUCT           of;
    CHAR               lpBuffer[128];

    /* Open the map file and get the information out */
    fh = OpenFile(achFileName, (LPOFSTRUCT)&of, (UINT)OF_READ);

    if (fh == -1)
        return;
    lpbi = (VOID FAR *)GlobalLock(hbiCurrent);
    if (!lpbi){
        _lclose(fh);
        return;
    }

    /* Compute scan size in bytes */
    dwScans = WIDTHBYTES((DWORD)lpbi->biWidth * lpbi->biBitCount);

    wBitmapHeight = (WORD)lpbi->biHeight ;
    wScansLeft    = (WORD)lpbi->biHeight ;

    hMemDC = NULL;
    for ( nBandSize = wScansLeft;
          (WORD)nBandSize >= MINBAND || (WORD)nBandSize == wScansLeft;
          nBandSize -= BANDINCREMENT) {

        /* Attempt to maximize band size by trying to allocate a buffer
         * for the given band size. If allocation fails, try again with the
         * smaller band size.
         */
        hBuf = GlobalAlloc (GMEM_FIXED | GMEM_ZEROINIT, dwScans * nBandSize) ;
        if (!hBuf)
            continue;

        /* Show success and exit loop if we're going to set bits to device. */
        if (bDIBToDevice) {
            (int)hMemDC = 1;
            break;
        }
        else {
            /* Create a device-dependent bitmap to hold the bits */
            hBitmap = CreateCompatibleBitmap (hDC,
                                              (WORD)lpbi->biWidth,
                                              nBandSize);
            if (!hBitmap) {
                /* Try again for the next smaller band size */
                GlobalFree (hBuf);
                continue;
            }

            /* Create a memory context for the bitmap */
            if (!(hMemDC = CreateCompatibleDC (hDC))) {
                GlobalFree (hBuf);
                DeleteObject (hBitmap);
                continue;
            } else
                /* Success in creating a DC */
                break;
        }
    }
    if (!hMemDC) {

        /* We failed allocation , so give error message and quit */
        if (GetFocus () == hWnd) {
            LoadString(hInst, IDS_NOMEM, lpBuffer, sizeof(lpBuffer));
            ErrMsg (lpBuffer);
            ValidateRect (hWnd, (LPRECT) (NULL));
        } else
            MessageBeep(0);

        GlobalUnlock(hbiCurrent);
        _lclose(fh);
        return;
    }
    pBuf = GlobalLock (hBuf);

    /* Calculate number of bytes to be transferred */
    dwMapSize = dwScans * nBandSize ;

    /* Manipulate palette appropriately */
    if (!bDIBToDevice)
        hOldMemPal = SelectPalette (hMemDC, hpalCurrent, 0) ;

    /* Now get to the start of the map in the file */
    _llseek(fh, dwOffset, (UINT)SEEK_SET);

    /* we are now all set to start off */
    wActualPosition = wScansLeft ;

    Rect.left  = 0;
    Rect.right = (WORD)lpbi->biWidth;

    hOldPal = SelectPalette(hDC, hpalCurrent, 0);
    RealizePalette(hDC);

    do {
        /* Read in nBandSize scans or whatever is left */
        if (wScansLeft > (WORD)nBandSize)
            wRead = (WORD)nBandSize ;
        else
            wRead = wScansLeft ;

        Rect.bottom = wActualPosition;
        wActualPosition -= wRead ;
        Rect.top = wActualPosition;

        dwMapSize = ((DWORD) wRead) * dwScans ;

        /* Now read in the map to the global buffer */
        if (RectVisible (hDC, &Rect)) {
            lread(fh, (LPSTR)pBuf, dwMapSize);

            if (bDIBToDevice) {
                if (wRead != (WORD)SetDIBitsToDevice (hDC, x, y,
                                                (WORD)lpbi->biWidth,
                                                wBitmapHeight,
                                                0,
                                                0,
                                                wBitmapHeight - wScansLeft,
                                                wRead,
                                                pBuf,
                                                (LPBITMAPINFO)lpbi,
                                                fPalColors ?
                                                DIB_PAL_COLORS :
                                                DIB_RGB_COLORS)){
                    LoadString(hInst, IDS_CANTDRAWSCANS, lpBuffer, sizeof(lpBuffer));
                    ErrMsg(lpBuffer);
                    GlobalUnlock(hBuf);
                    GlobalFree(hBuf);
                    GlobalUnlock(hbiCurrent);
                    _lclose(fh);
                    return;
                }
            } else {
                lpbi->biHeight = wRead ;

                /* Set the DIB bits to a device-dependent format */
                if (lpbi->biHeight != (int)SetDIBits (hMemDC,
                                                       hBitmap,
                                                       0,
                                                       (WORD)lpbi->biHeight,
                                                       pBuf,
                                                       (LPBITMAPINFO)lpbi,
                                                       (DWORD) (fPalColors ?
                                                       DIB_PAL_COLORS :
                                                       DIB_RGB_COLORS))){
                    LoadString(hInst, IDS_CANTDRAWSCANS, lpBuffer, sizeof(lpBuffer));
                    ErrMsg(lpBuffer);
                    GlobalUnlock (hBuf);
                    GlobalFree (hBuf);
                    GlobalUnlock(hbiCurrent);
                    _lclose(fh);
                    return;
                }

                /* Blt own map onto the screen, remembering the point to start */
                hOld = SelectObject (hMemDC, hBitmap) ;
                if (!BitBlt (hDC, 0, wActualPosition,
                             (WORD)lpbi->biWidth,
                             (WORD)lpbi->biHeight,
                             hMemDC, 0, 0, SRCCOPY)){
                    LoadString(hInst, IDS_CANTDRAWMAP, lpBuffer, sizeof(lpBuffer));
                    ErrMsg (lpBuffer);
                    GlobalUnlock (hBuf);
                    GlobalFree (hBuf);
                    GlobalUnlock(hbiCurrent);
                    _lclose(fh);
                    return;
                }
                SelectObject (hMemDC, hOld) ;

                /* Restore the value of bitmap height */
                lpbi->biHeight = wBitmapHeight ;
            }
        }
        else {
            /* This chunk is not visible, seek over the data in the file */
            _llseek(fh, dwMapSize, (UINT)SEEK_CUR);
        }
        wScansLeft -= wRead ;
    } while (wScansLeft > 0 ) ;

    /* Delete the objects just created above */
    GlobalUnlock (hBuf);
    GlobalFree (hBuf);
    SelectPalette (hDC, hOldPal, 0);

    /* Set success flag */
    bSuccess = TRUE;

    if (!bDIBToDevice) {
        SelectPalette (hMemDC, hOldMemPal, 0);
        DeleteDC (hMemDC) ;
        DeleteObject (hBitmap) ;
    }
    GlobalUnlock(hbiCurrent);

    /* Close the file */
    _lclose(fh);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : SizeWindow(HWND hWnd)                                      *
 *                                                                          *
 *  PURPOSE    : Sizes the app. window based on client dimensions (DIB      *
 *               dimensions) and style. Sets the caption text.              *
 *                                                                          *
 ****************************************************************************/
VOID SizeWindow (HWND hWnd)
{
    CHAR  *pstr;
    CHAR  Name[60];
    RECT  Rectangle;
    RECT  rectClient;
//    INT   dx,dy;
//    MPOINT pt;
    BITMAPINFOHEADER bi;

    /* Get information about current DIB */
    DibInfo(hbiCurrent,&bi);

    /* Extract the filename from the full pathname */
    pstr = achFileName + lstrlen(achFileName);
    while ( (pstr > achFileName) && (*pstr != '\\') && (*pstr != ':') )
    pstr = CharPrev(achFileName, pstr);

    if(pstr != achFileName)
            pstr++;

    /* Format filename along with the DIB attributes */
    sprintf (Name,
              "%s (%s %dx%dx%d%s)",
              szAppName,
              pstr,
              (WORD)bi.biWidth,
              (WORD)bi.biHeight,
              (WORD)bi.biBitCount,
              bi.biCompression == BI_RGB  ? " RGB" :
              bi.biCompression == BI_RLE8 ? " RLE8" : " RLE4" );

    /* Show formatted text in the caption bar */
    SetWindowText (hWnd, Name);

    /* Store the size in ptSize, so the scroll bars will work. */
    ptSize.x = (WORD)bi.biWidth;
    ptSize.y = (WORD)bi.biHeight;

    if (IsZoomed (hWnd))
        SetScrollRanges (hWnd);
    else {
        Rectangle.left   = 0;
        Rectangle.top    = 0;
        Rectangle.right  = (WORD)bi.biWidth;
        Rectangle.bottom = (WORD)bi.biHeight;

        /* Compute the size of the window rectangle based on the given
         * client rectangle size and the window style, then size the
         * window.
         */
        AdjustWindowRect (&Rectangle, dwStyle, TRUE);
        SetWindowPos (hWnd, (HWND)NULL, 0, 0,
                      Rectangle.right  - Rectangle.left + 1,
                      Rectangle.bottom - Rectangle.top + 1,
                      SWP_NOMOVE | SWP_NOZORDER);
        GetClientRect( hWnd, &rectClient );
        // Correct for small bitmap that causes multiline menu
        if (rectClient.bottom < Rectangle.bottom) {
            Rectangle.bottom += (Rectangle.bottom - rectClient.bottom);
            SetWindowPos (hWnd, (HWND)NULL, 0, 0,
                      Rectangle.right  - Rectangle.left + 1,
                      Rectangle.bottom - Rectangle.top + 1,
                      SWP_NOMOVE | SWP_NOZORDER);
        }
    }

    InvalidateRect(hWnd,NULL,TRUE);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetRealClientRect(HWND hwnd, LPRECT lprc)                  *
 *                                                                          *
 *  PURPOSE    : Calculates the client rectangle taking scrollbars into     *
 *               consideration.                                             *
 *                                                                          *
 ****************************************************************************/
VOID GetRealClientRect (
    HWND hwnd,
    PRECT lprc)
{
    DWORD dwStyle;

    dwStyle = GetWindowLong (hwnd, GWL_STYLE);
    GetClientRect (hwnd,lprc);

    if (dwStyle & WS_HSCROLL)
        lprc->bottom += GetSystemMetrics (SM_CYHSCROLL);

    if (dwStyle & WS_VSCROLL)
        lprc->right  += GetSystemMetrics (SM_CXVSCROLL);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : SetScrollRanges(hwnd)                                      *
 *                                                                          *
 *  PURPOSE    :                                                            *
 *                                                                          *
 ****************************************************************************/
VOID SetScrollRanges(HWND hwnd)
{
    RECT       rc;
    INT        iRangeH, iRangeV, i;
    static INT iSem = 0;

    if (!iSem){
        iSem++;
        GetRealClientRect (hwnd, &rc);

        for (i = 0; i < 2; i++){
            iRangeV = ptSize.y - rc.bottom;
            iRangeH = ptSize.x - rc.right;

            if (iRangeH < 0) iRangeH = 0;
            if (iRangeV < 0) iRangeV = 0;

            if (GetScrollPos ( hwnd,
                               SB_VERT) > iRangeV ||
                               GetScrollPos (hwnd, SB_HORZ) > iRangeH)
                InvalidateRect (hwnd, NULL, TRUE);

            SetScrollRange (hwnd, SB_VERT, 0, iRangeV, TRUE);
            SetScrollRange (hwnd, SB_HORZ, 0, iRangeH, TRUE);

            GetClientRect (hwnd, &rc);
        }
        iSem--;
    }
}

/*********** THE FOLLOWING FUNCTIONS ARE FOR CLIPBOARD SUPPORT **************/
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CopyHandle (HANDLE h)                                      *
 *                                                                          *
 *  PURPOSE    : Makes a copy of the given global memory block.             *
 *                                                                          *
 *  RETURNS    : A handle to the new block.                                 *
 *                                                                          *
 ****************************************************************************/
HANDLE CopyHandle (HANDLE h)
{
    BYTE *lpCopy;
    BYTE *lp;
    HANDLE hCopy;
    DWORD  dwLen;

    dwLen = GlobalSize (h);
    if (hCopy = GlobalAlloc (GHND, dwLen)) {

        lpCopy = (BYTE *)GlobalLock (hCopy);
        lp     = (BYTE *)GlobalLock (h);
        while (dwLen--) *lpCopy++ = *lp++;
        GlobalUnlock (hCopy);
        GlobalUnlock (h);
    }
    return hCopy;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CopyPalette(HPALETTE hpal)                                 *
 *                                                                          *
 *  PURPOSE    : Makes a copy of a GDI logical palette                      *
 *                                                                          *
 *  RETURNS    : A handle to the new palette.                               *
 *                                                                          *
 ****************************************************************************/
HPALETTE CopyPalette (HPALETTE hpal)
{
    PLOGPALETTE ppal;
    WORD        nNumEntries;

    if (!hpal)
        return NULL;

    GetObject(hpal,sizeof(INT),(LPSTR)&nNumEntries);

    if (nNumEntries == 0)

return NULL;

    ppal = (PLOGPALETTE)LocalAlloc(LPTR,sizeof(LOGPALETTE) +
                nNumEntries * sizeof(PALETTEENTRY));

    if (!ppal)
        return NULL;

    ppal->palVersion    = PALVERSION;
    ppal->palNumEntries = nNumEntries;

    GetPaletteEntries(hpal,0,nNumEntries,(LPPALETTEENTRY)ppal->palPalEntry);

    hpal = CreatePalette(ppal);

    LocalFree((HANDLE)ppal);
    return hpal;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CopyBitmap (HBITMAP hbm)                                   *
 *                                                                          *
 *  PURPOSE    : Copies the given bitmap to another.                        *
 *                                                                          *
 *  RETURNS    : A handle to the new bitmap.                                *
 *                                                                          *
 ****************************************************************************/
HBITMAP CopyBitmap (HBITMAP hbm)
{
    BITMAP  bm;
    RECT    rc;

    if (!hbm)
         return NULL;

    GetObject (hbm, sizeof(BITMAP), (LPSTR)&bm);
    rc.left   = 0;
    rc.top    = 0;
    rc.right  = bm.bmWidth;
    rc.bottom = bm.bmHeight;

    return CropBitmap (hbm, &rc);
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  CropBitmap (hbm,lprect)                                   *
 *                                                                          *
 *  PURPOSE    :  Crops a bitmap to a new size specified by the lprect      *
 *                parameter.                                                *
 *                                                                          *
 *  RETURNS    :  A handle to the new bitmap.                               *
 *                                                                          *
 ****************************************************************************/
HBITMAP CropBitmap (
    HBITMAP hbm,
    PRECT prc)
{
    HDC     hMemDCsrc;
    HDC     hMemDCdst;
    HDC     hdc;
    HBITMAP hNewBm;
    BITMAP  bm;
    INT     dx,dy;

    if (!hbm)
         return NULL;

    hdc = GetDC (NULL);
    hMemDCsrc = CreateCompatibleDC (hdc);
    hMemDCdst = CreateCompatibleDC (hdc);

    GetObject (hbm, sizeof(BITMAP), (LPSTR)&bm);
    dx = prc->right  - prc->left;
    dy = prc->bottom - prc->top;

    /*hNewBm = +++CreateBitmap - Not Recommended(use CreateDIBitmap)+++ (dx, dy, bm.bmPlanes, bm.bmBitsPixel, NULL);*/
    hNewBm = CreateBitmap(dx, dy, bm.bmPlanes, bm.bmBitsPixel, NULL);
    if (hNewBm){
        SelectObject (hMemDCsrc, hbm);
        SelectObject (hMemDCdst, hNewBm);

        BitBlt (hMemDCdst,
                0,
                0,
                dx,
                dy,
                hMemDCsrc,
                prc->left,
                prc->top,
                SRCCOPY);
    }

    ReleaseDC (NULL,hdc);
    DeleteDC (hMemDCsrc);
    DeleteDC (hMemDCdst);
    return hNewBm;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : RenderFormat(int cf)                                       *
 *                                                                          *
 *  PURPOSE    : Renders the currently displayed DIB in CF_DIB or           *
 *               CF_BITMAP format.The bitmap is clipped to the current      *
 *               rcClip.                                                    *
 *                                                                          *
 *  RETURNS    : A handle to the DIB                                        *
 *                                                                          *
 ****************************************************************************/
HANDLE RenderFormat (INT cf)
{
    HANDLE  h = NULL;
    HBITMAP hbm;

    if (!bLegitDraw)
        return NULL;

    switch (cf){
        case CF_BITMAP:
            if (hbmCurrent && !IsRectEmpty (&rcClip))
                h = CropBitmap (hbmCurrent, &rcClip);
            else{
                if (hbmCurrent)
                    h = CopyBitmap (hbmCurrent);
                else if (hdibCurrent)
                    h = BitmapFromDib (hdibCurrent, hpalCurrent);
                else if (achFileName[0] && (hdibCurrent = OpenDIB (achFileName)))
                    h = BitmapFromDib (hdibCurrent, hpalCurrent);
                else
                    h = NULL;

                if (h && !IsRectEmpty (&rcClip)){
                    hbm = CropBitmap (h,&rcClip);
                    DeleteObject (h);
                    h = hbm;
                }
            }
            break;

        case CF_DIB:
            if (!IsRectEmpty (&rcClip)){
                if (hbm = RenderFormat (CF_BITMAP)){
                    h = DibFromBitmap (hbm, BI_RGB, 0, hpalCurrent);
                    DeleteObject (hbm);
                }
            }
            else{
                if (!hdibCurrent && hbmCurrent)
                    h = DibFromBitmap (hbmCurrent, BI_RGB, 0, hpalCurrent);
                else if (hdibCurrent)
                    h = CopyHandle (hdibCurrent);
                else if (achFileName[0])
                    h = OpenDIB (achFileName);
                else
                    h = NULL;
            }
            break;

        case CF_PALETTE:
            if (hpalCurrent)
                h = CopyPalette (hpalCurrent);
            break;
    }
    return h;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  RealizeDibFormat(DWORD biStyle, WORD biBits)              *
 *                                                                          *
 *  PURPOSE    :  Realize the current DIB in the specifed format            *
 *                This function is used to get a specific format of CF_DIB  *
 *                                                                          *
 *                    biStyle     DIB format      RGB or RLE                *
 *                    biBits      Bits per pixel  1,4,8,24                  *
 *                                                                          *
 *  RETURNS    :  A handle to the created DIB.                              *
 *                                                                          *
 ****************************************************************************/
HANDLE RealizeDibFormat (
    DWORD biStyle,
    WORD biBits)
{
    BITMAPINFOHEADER bi;

    if (!bLegitDraw)
        return NULL;

    DibInfo (hbiCurrent, &bi);

    /*  Do we have the requested format already? */
    if (bi.biCompression == biStyle && bi.biBitCount == biBits){
        if (!hdibCurrent)
            hdibCurrent = RenderFormat (CF_DIB);
    }
    else{
        if (!hbmCurrent)
            hbmCurrent = RenderFormat (CF_BITMAP);

        if (hbmCurrent){
            if (hdibCurrent)
                GlobalFree (hdibCurrent);

            hdibCurrent = DibFromBitmap (hbmCurrent, biStyle, biBits, hpalCurrent);
        }
    }

    return hdibCurrent;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : ErrMsg (PSTR sz,...)                                       *
 *                                                                          *
 *  PURPOSE    : Opens a Message box with a error message in it.The user can*
 *               select the OK button to continue                           *
 *                                                                          *
 *  RETURNS    : FALSE to indicate an error has occured.                    *
 *                                                                          *
 ****************************************************************************/
INT ErrMsg (PSTR sz,...)
{
    CHAR ach[128];
    va_list args;

    va_start(args, sz);

    wvsprintf (ach, sz, args);   /* Format the string */
    MessageBox (NULL, ach, NULL, MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL);
    return FALSE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : fDialog(int id,HWND hwnd,FARPROC fpfn)                     *
 *                                                                          *
 *  PURPOSE    : This function displays a dialog box                        *
 *                                                                          *
 *  RETURNS    : The exit code.                                             *
 *                                                                          *
 ****************************************************************************/
BOOL fDialog (
    INT id,
    HWND hwnd,
    FARPROC fpfn)
{
    BOOL        f;
    HANDLE      hInst;

    hInst = (HANDLE)GetWindowLong (hwnd, GWL_HINSTANCE);
    fpfn  = MakeProcInstance (fpfn, hInst);
    f = DialogBox (hInst, MAKEINTRESOURCE(id), hwnd, (DLGPROC)fpfn);
    FreeProcInstance (fpfn);
    return f;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : AppAbout( hDlg, uiMessage, wParam, lParam )                *
 *                                                                          *
 *  PURPOSE    : Dialog function for the About... dialog box                *
 *                                                                          *
 ****************************************************************************/
BOOL APIENTRY AppAbout(
    HWND         hDlg,
    UINT         uiMessage,
    UINT         wParam,
    LONG         lParam)
{
    switch (uiMessage) {
        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK)
                EndDialog (hDlg, TRUE);
            break;

        case WM_INITDIALOG:
            return TRUE;
    }
    return FALSE;
        UNREFERENCED_PARAMETER(lParam);
}


PRINT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*******************************************************************************
 *                                                                             *
 *  MODULE      : Print.c                                                      *
 *                                                                             *
 *  DESCRIPTION : Routines used for printing.                                  *
 *                                                                             *
 *  FUNCTIONS   : GetPrinterDC()   - Gets default printer from WIN.INI and     *
 *                                   creates a DC for it.                      *
 *                                                                             *
 *                InitPrinting()   - Initializes print job.                    *
 *                                                                             *
 *                TermPrinting()   - Terminates print job.                     *
 *                                                                             *
 *                PrintDlgProc()   - Dialog function for the "Cancel Printing" *
 *                                   dialog.                                   *
 *                                                                             *
 *                AbortProc()      - Peeks at message queue for messages from  *
 *                                   the print dialog.                         *
 *                                                                             *
 *******************************************************************************/

#include <windows.h>
#include <string.h>
#include <commdlg.h>
#define _MBCS
#include <mbstring.h>
#include "showdib.h"

FARPROC  lpfnAbortProc    = NULL;
FARPROC  lpfnPrintDlgProc = NULL;
HWND     hWndParent       = NULL;
HWND     hDlgPrint        = NULL;
BOOL     bError;
BOOL     bUserAbort;

BOOL APIENTRY AbortProc (HDC, SHORT);
BOOL APIENTRY PrintDlgProc (HWND, WORD, UINT, DWORD);

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : GetPrinterDC()                                             *
 *                                                                          *
 *  PURPOSE    : Read WIN.INI for default printer and create a DC for it.   *
 *                                                                          *
 *  RETURNS    : A handle to the DC if successful or NULL otherwise.        *
 *                                                                          *
 ****************************************************************************/
HDC PASCAL GetPrinterDC() {

    PRINTDLG pd;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize = sizeof(PRINTDLG);
    pd.Flags = PD_RETURNDC;
    pd.hwndOwner = hWndApp;
    pd.hInstance = (HANDLE) NULL;

    // Display the PRINT dialog box. */

    if( PrintDlg(&pd) == TRUE ) {
        return( pd.hDC );
    }
    else
        return( NULL );

}

//
// Getting the printer DC the old fashion way...
// called to enable or disable the IDM_PRINT menu item.
//
HDC PASCAL GetPrinterDC1()
{
    static CHAR szPrinter [80];
    CHAR    *szDevice, *szDriver, *szOutput;

    GetProfileString ("windows", "device", "", szPrinter, sizeof(szPrinter));

    if ((szDevice = _mbstok (szPrinter, "," )) &&
        (szDriver = _mbstok (NULL,      ", ")) &&
        (szOutput = _mbstok (NULL,      ", ")))

        return CreateDC (szDriver, szDevice, szOutput, NULL) ;

    return NULL;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : InitPrinting(HDC hDC, HWND hWnd, HANDLE hInst, LPSTR msg)  *
 *                                                                          *
 *  PURPOSE    : Makes preliminary driver calls to set up print job.        *
 *                                                                          *
 *  RETURNS    : TRUE  - if successful.                                     *
 *               FALSE - otherwise.                                         *
 *                                                                          *
 ****************************************************************************/
BOOL PASCAL InitPrinting(HDC hDC, HWND hWnd, HANDLE hInst, LPSTR msg)
{
    DOCINFO         DocInfo;

    bError     = FALSE;     /* no errors yet */
    bUserAbort = FALSE;     /* user hasn't aborted */

    hWndParent = hWnd;      /* save for Enable at Term time */

    lpfnPrintDlgProc = (DLGPROC) MakeProcInstance (PrintDlgProc, hInst);
    lpfnAbortProc    = (WNDPROC) MakeProcInstance (AbortProc, hInst);

    hDlgPrint = CreateDialog (hInst, "PRTDLG", hWndParent,
    (DLGPROC)lpfnPrintDlgProc);

    if (!hDlgPrint)
        return FALSE;

    SetWindowText (hDlgPrint, msg);
    EnableWindow (hWndParent, FALSE);        /* disable parent */

//
// Use new printing APIs...Petrus Wong 12-May-1993
//
    if (SetAbortProc(hDC, (ABORTPROC)lpfnAbortProc) <= 0) {
        bError = TRUE;
        return FALSE;
    }

    memset(&DocInfo, 0, sizeof(DOCINFO));
    DocInfo.cbSize      = sizeof(DOCINFO);
    DocInfo.lpszDocName = (LPTSTR) msg;
    DocInfo.lpszOutput  = NULL;

    if (StartDoc(hDC, &DocInfo) <= 0) {
        bError = TRUE;
        return FALSE;
    }
    bError = FALSE;

    /* might want to call the abort proc here to allow the user to
     * abort just before printing begins */
    return TRUE;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  TermPrinting(HDC hDC)                                     *
 *                                                                          *
 *  PURPOSE    :  Terminates print job.                                     *
 *                                                                          *
 ****************************************************************************/
VOID PASCAL TermPrinting(HDC hDC)
{
//
// Use new printing APIs...Petrus Wong 12-May-1993
//
    if (!bError)
        EndDoc(hDC);

    if (bUserAbort)
        AbortDoc(hDC);
    else {
        EnableWindow(hWndParent, TRUE);
        DestroyWindow(hDlgPrint);
    }

    FreeProcInstance(lpfnAbortProc);
    FreeProcInstance(lpfnPrintDlgProc);
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   :PrintDlgProc (HWND, unsigned , WORD , DWORD )               *
 *                                                                          *
 *  PURPOSE    :Dialog function for the "Cancel Printing" dialog. It sets   *
 *              the abort flag if the user presses <Cancel>.                *
 *                                                                          *
 ****************************************************************************/
BOOL APIENTRY PrintDlgProc (HWND hDlg, WORD iMessage, UINT wParam, DWORD lParam)
{
    switch (iMessage) {
    case WM_INITDIALOG:

            EnableMenuItem (GetSystemMenu (hDlg, FALSE), (WORD)SC_CLOSE, (WORD)MF_GRAYED);
            break;

    case WM_COMMAND:
            bUserAbort = TRUE;
            EnableWindow (hWndParent, TRUE);
            DestroyWindow (hDlg);
            hDlgPrint = 0;
            break;

    default:
            return FALSE;
    }
    return TRUE;
        UNREFERENCED_PARAMETER(wParam);
        UNREFERENCED_PARAMETER(lParam);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :AbortProc (HDC hPrnDC, short nCode)                         *
 *                                                                          *
 *  PURPOSE    :Checks message queue for messages from the "Cancel Printing"*
 *              dialog. If it sees a message, (this will be from a print    *
 *              cancel command), it terminates.                             *
 *                                                                          *
 *  RETURNS    :Inverse of Abort flag                                       *
 *                                                                          *
 ****************************************************************************/
BOOL APIENTRY AbortProc (HDC hPrnDC, SHORT nCode)
{
    MSG   msg;

    while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
        if (!hDlgPrint || !IsDialogMessage(hDlgPrint, &msg)) {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }
    return !bUserAbort;
        UNREFERENCED_PARAMETER(hPrnDC);
        UNREFERENCED_PARAMETER(nCode);
}


SHOWDIB.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*******************************************************************************
 *                                                                             *
 *  PROGRAM     : ShowDIB.c                                                    *
 *                                                                             *
 *  PURPOSE     : Application to illustrate the use of the GDI                 *
 *                DIB (Device Independent Bitmap) and Palette manager          *
 *                functions.                                                   *
 *                                                                             *
 *  FUNCTIONS   : WinMain ()             -  Creates the app. window and enters *
 *                                          the message loop.                  *
 *                                                                             *
 *                WndProc()              -  Processes app. window messages.    *
 *                                                                             *
 *                MenuCommand()          -  Processes menu commands.           *
 *                                                                             *
 *                FreeDIB()              -  Frees currently active objects.    *
 *                                                                             *
 *                InitDIB()              -  Reads DIB from a file and loads it.*
 *                                                                             *
 *******************************************************************************/

#include <windows.h>
#include <io.h>
#include <stdio.h>
#include "showdib.h"
#include <commdlg.h>

DIBPARAMS      DIBParams;                  /* params for the SETSCALING escape */
CHAR           achFileName[128];
DWORD          dwOffset;
NPLOGPALETTE   pLogPal;
HPALETTE       hpalSave = NULL;
HINSTANCE      hInst ;
RECT           rcClip;
static         HCURSOR hcurSave;

BOOL    fPalColors  = FALSE;          /* TRUE if the current DIB's color table   */
                                      /* contains palette indexes not rgb values */
UINT    nAnimating  = 0;              /* Palette animation count                 */
WORD    UpdateCount = 0;

BOOL    bUpdateColors = TRUE;  /* Directly update screen colors                */
BOOL    bDIBToDevice  = FALSE; /* Use SetDIBitsToDevice() to BLT data.         */
BOOL    bNoUgly       = FALSE; /* Make window black on a WM_PALETTEISCHANGING  */
BOOL    bLegitDraw    = FALSE; /* We have a valid bitmap to draw               */

CHAR    szBitmapExt[] = "*.BMP; *.DIB; *.RLE";     /* possible file extensions */
WORD    wTransparent  = TRANSPARENT;               /* Mode of DC               */
CHAR    szAppName[]   = "ShowDIB" ;                /* App. name                */

HPALETTE hpalCurrent   = NULL;         /* Handle to current palette            */
HANDLE   hdibCurrent   = NULL;         /* Handle to current memory DIB         */
HBITMAP  hbmCurrent    = NULL;         /* Handle to current memory BITMAP      */
HANDLE   hbiCurrent    = NULL;         /* Handle to current bitmap info struct */
HWND     hWndApp;                      /* Handle to app. window                */

/* Styles of app. window */
DWORD          dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                         WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME;

VOID PrintDIB (HWND hWnd, HDC hDC, INT x, INT y, INT dx, INT dy);
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WinMain(HANDLE, HANDLE, LPSTR, int)                        *
 *                                                                          *
 *  PURPOSE    : Creates the app. window and enters the message loop.       *
 *                                                                          *
 ****************************************************************************/
int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
    )
{
     HWND        hWnd ;
     WNDCLASS    wndclass ;
     MSG         msg ;
     int         xScreen, yScreen ;
     CHAR        ach[40];

     hInst = hInstance ;

     /* Initialize clip rectangle */
     SetRectEmpty(&rcClip);

     if (!hPrevInstance) {
         wndclass.style         = CS_DBLCLKS;
         wndclass.lpfnWndProc   = (WNDPROC) WndProc ;
         wndclass.cbClsExtra    = 0 ;
         wndclass.cbWndExtra    = 0 ;
         wndclass.hInstance     = hInstance ;
         wndclass.hIcon         = LoadIcon(hInst, "SHOWICON");
         wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
         wndclass.hbrBackground = GetStockObject (BLACK_BRUSH) ;
         wndclass.lpszMenuName  = szAppName ;
         wndclass.lpszClassName = szAppName ;

         if (!RegisterClass (&wndclass))
                 return FALSE ;
     }

     if (!GetProfileString("extensions", "bmp", "", ach, sizeof(ach)))
             WriteProfileString("extensions", "bmp", "showdib.exe ^.bmp");
     if (!GetProfileString("extensions", "dib", "", ach, sizeof(ach)))
             WriteProfileString("extensions", "dib", "showdib.exe ^.dib");

     /* Save the pointer to the command line */
     lstrcpy(achFileName, lpCmdLine);

     xScreen = GetSystemMetrics (SM_CXSCREEN) ;
     yScreen = GetSystemMetrics (SM_CYSCREEN) ;

     /* Create the app. window */
     hWnd = CreateWindow( szAppName,
                          szAppName,
                          dwStyle,
                          CW_USEDEFAULT,
                          0,
                          xScreen / 2,
                          yScreen / 2,
                          NULL,
                          NULL,
                          hInstance,
                          NULL) ;

     ShowWindow (hWndApp = hWnd, nCmdShow) ;

     /* Enter message loop */
     while (GetMessage (&msg, NULL, 0, 0)) {
         TranslateMessage (&msg) ;
         DispatchMessage (&msg) ;
     }

     return msg.wParam ;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WndProc (hWnd, iMessage, wParam, lParam)                   *
 *                                                                          *
 *  PURPOSE    : Processes window messages.                                 *
 *                                                                          *
 ****************************************************************************/
LONG    APIENTRY WndProc (
    HWND     hWnd ,
    UINT     iMessage ,
    UINT     wParam ,
    LONG     lParam )

{
    PAINTSTRUCT      ps;
    HDC              hDC;
    HANDLE           h;
    INT              i;
    INT              iMax;
    INT              iMin;
    INT              iPos;
    INT              dn;
    RECT             rc,Rect;
    HPALETTE         hOldPal;
    HMENU            hMenu;
    CHAR             lpBuffer[128];

    switch (iMessage) {
        case WM_DESTROY:
                /* Clean up and quit */
                FreeDib();
                PostQuitMessage(0);
                break ;

        case WM_CREATE:
                /* Allocate space for our logical palette */
                pLogPal = (NPLOGPALETTE) LocalAlloc( LMEM_FIXED,
                                                     (sizeof(LOGPALETTE) +
                                                     (sizeof(PALETTEENTRY)*(MAXPALETTE))));
        if(!pLogPal){
            LoadString(hInst, IDS_MEMLOW, lpBuffer, sizeof(lpBuffer));
            MessageBox(hWnd, lpBuffer, NULL, MB_OK | MB_ICONHAND);
            PostQuitMessage(0);
            break;
        }

// Temporary workaround.  lpCmdLine points to exe name, not first parameter
                /* If DIB initialization fails, quit */
//              if (achFileName[0] && !InitDIB(hWnd))
//                      PostQuitMessage (3) ;

                /* fall through */

        case WM_WININICHANGE:

              hMenu = GetMenu(hWnd);
              if ( hDC = GetPrinterDC1() ) {
                    EnableMenuItem( hMenu,
                                    IDM_PRINT,
                                    (RC_DIBTODEV &
                                     GetDeviceCaps(hDC, RASTERCAPS)) ?
                                     MF_ENABLED :
                                     MF_GRAYED | MF_DISABLED);
                  DeleteDC(hDC);
              }
                break;

        case WM_PALETTEISCHANGING:
                /* if SHOWDIB was not responsible for palette change and if
                 * ok to hide changes, paint app. window black.
                 */
                if (wParam != (UINT)(hWnd && bNoUgly)) {
                    GetClientRect(hWnd, &Rect);

                    hDC = GetDC(hWnd);
                    FillRect( hDC, (LPRECT) &Rect, GetStockObject(BLACK_BRUSH));
                    ReleaseDC(hWnd, hDC);
                }
                break;

        case WM_ACTIVATE:
                if (!GET_WM_ACTIVATE_STATE(wParam, lParam))  /* app. is being de-activated */
                   break;
                /* If the app. is moving to the foreground, fall through and
                 * redraw full client area with the newly realized palette,
                 * if the palette has changed.
                 */

        case WM_QUERYNEWPALETTE:
                /* If palette realization causes a palette change,
                 * we need to do a full redraw.
                 */
                if (bLegitDraw) {
                    hDC = GetDC (hWnd);
                    hOldPal = SelectPalette (hDC, hpalCurrent, 0);

                    i = RealizePalette(hDC);

                    SelectPalette (hDC, hOldPal, 0);
                    ReleaseDC (hWnd, hDC);

                    if (i) {
                        InvalidateRect (hWnd, (LPRECT) (NULL), 1);
                        UpdateCount = 0;
                        return 1;
                    } else
                        return FALSE;
                }
                else
                    return FALSE;
                break;

        case WM_PALETTECHANGED:
                /* if SHOWDIB was not responsible for palette change and if
                 * palette realization causes a palette change, do a redraw.
                 */
                 if ((HWND)wParam != hWnd){
                    if (bLegitDraw){
                        hDC = GetDC (hWnd);
                        hOldPal = SelectPalette (hDC, hpalCurrent, 0);

                        i = RealizePalette (hDC);

                        if (i){
                            if (bUpdateColors){
                                UpdateColors (hDC);
                                UpdateCount++;
                            }
                            else
                                InvalidateRect (hWnd, (LPRECT) (NULL), 1);
                        }

                        SelectPalette (hDC, hOldPal, 0);
                        ReleaseDC (hWnd, hDC);
                    }
                }
                break;

        case WM_RENDERALLFORMATS:
                /* Ensure that clipboard data can be rendered even tho'
                 * app. is being destroyed.
                 */
                SendMessage(hWnd,WM_RENDERFORMAT,CF_DIB,0L);
                SendMessage(hWnd,WM_RENDERFORMAT,CF_BITMAP,0L);
                SendMessage(hWnd,WM_RENDERFORMAT,CF_PALETTE,0L);
                break;

        case WM_RENDERFORMAT:
                /* Format data in manner specified and pass the data
                 * handle to clipboard.
                 */
                if (h = RenderFormat(wParam))
                    SetClipboardData((WORD)wParam,h);
                break;

        case WM_COMMAND:
                /* Process menu commands */
                return MenuCommand(hWnd, LOWORD(wParam));
                break;

        case WM_TIMER:
                /* Signal for palette animation */
                hDC = GetDC(hWnd);
                hOldPal = SelectPalette(hDC, hpalCurrent, 0);
                {
                    PALETTEENTRY peTemp;

                    /* Shift all palette entries left by one position and wrap
                     * around the first entry
                     */
                    peTemp = pLogPal->palPalEntry[0];
                    for (i = 0; i < (pLogPal->palNumEntries - 1); i++)
                         pLogPal->palPalEntry[i] = pLogPal->palPalEntry[i+1];
                    pLogPal->palPalEntry[i] = peTemp;
                }
                /* Replace entries in logical palette with new entries*/
                AnimatePalette(hpalCurrent, 0, pLogPal->palNumEntries, pLogPal->palPalEntry);

                SelectPalette(hDC, hOldPal, 0);
                ReleaseDC(hWnd, hDC);

                /* Decrement animation count and terminate animation
                 * if it reaches zero
                 */
                if (!(--nAnimating))
                    PostMessage(hWnd,WM_COMMAND,IDM_ANIMATE0,0L);
                break;

        case WM_PAINT:
                /* If we have updated more than once, the rest of our
                 * window is not in some level of degradation worse than
                 * our redraw...  we need to redraw the whole area
                 */
                if (UpdateCount > 1) {
                    BeginPaint(hWnd, &ps);
                    EndPaint(hWnd, &ps);
                    UpdateCount = 0;
                    InvalidateRect(hWnd, (LPRECT) (NULL), 1);
                    break;
                }

                hDC = BeginPaint(hWnd, &ps);
                AppPaint(hWnd,
                         hDC,
                         GetScrollPos(hWnd,SB_HORZ),
                         GetScrollPos(hWnd,SB_VERT) );
                EndPaint(hWnd, &ps);
                break ;

        case WM_SIZE:
            SetScrollRanges(hWnd);
            break;

        case WM_KEYDOWN:
            /* Translate keyboard messages to scroll commands */
            switch (wParam) {
                case VK_UP:
                    PostMessage (hWnd, WM_VSCROLL, SB_LINEUP,   0L);
                    break;

                case VK_DOWN:
                    PostMessage (hWnd, WM_VSCROLL, SB_LINEDOWN, 0L);
                    break;

                case VK_PRIOR:
                    PostMessage (hWnd, WM_VSCROLL, SB_PAGEUP,   0L);
                    break;

                case VK_NEXT:
                    PostMessage (hWnd, WM_VSCROLL, SB_PAGEDOWN, 0L);
                    break;

                case VK_HOME:
                    PostMessage (hWnd, WM_HSCROLL, SB_PAGEUP,   0L);
                    break;

                case VK_END:
                    PostMessage (hWnd, WM_HSCROLL, SB_PAGEDOWN, 0L);
                    break;

                case VK_LEFT:
                    PostMessage (hWnd, WM_HSCROLL, SB_LINEUP,   0L);
                    break;

                case VK_RIGHT:
                    PostMessage (hWnd, WM_HSCROLL, SB_LINEDOWN, 0L);
                    break;
            }
            break;

        case WM_KEYUP:
            switch (wParam) {
               case VK_UP:
               case VK_DOWN:
               case VK_PRIOR:
               case VK_NEXT:
                  PostMessage (hWnd, WM_VSCROLL, SB_ENDSCROLL, 0L);
                  break;

               case VK_HOME:
               case VK_END:
               case VK_LEFT:
               case VK_RIGHT:
                  PostMessage (hWnd, WM_HSCROLL, SB_ENDSCROLL, 0L);
                  break;
            }
            break;

        case WM_VSCROLL:
            /* Calculate new vertical scroll position */
            GetScrollRange (hWnd, SB_VERT, &iMin, &iMax);
            iPos = GetScrollPos (hWnd, SB_VERT);
            GetClientRect (hWnd, &rc);

            switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:
                    dn =  rc.bottom / 16 + 1;
                    break;

                case SB_LINEUP:
                    dn = -rc.bottom / 16 + 1;
                    break;

                case SB_PAGEDOWN:
                    dn =  rc.bottom / 2  + 1;
                    break;

                case SB_PAGEUP:
                    dn = -rc.bottom / 2  + 1;
                    break;

                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
                    dn = GET_WM_VSCROLL_POS(wParam, lParam)-iPos;
                    break;

                default:
                    dn = 0;
                    break;
            }
            /* Limit scrolling to current scroll range */
            if (dn = BOUND (iPos + dn, iMin, iMax) - iPos) {
                ScrollWindow (hWnd, 0, -dn, NULL, NULL);
                SetScrollPos (hWnd, SB_VERT, iPos + dn, TRUE);
            }
            break;

        case WM_HSCROLL:
            /* Calculate new horizontal scroll position */
            GetScrollRange (hWnd, SB_HORZ, &iMin, &iMax);
            iPos = GetScrollPos (hWnd, SB_HORZ);
            GetClientRect (hWnd, &rc);

            switch (GET_WM_HSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:
                    dn =  rc.right / 16 + 1;
                    break;

                case SB_LINEUP:
                    dn = -rc.right / 16 + 1;
                    break;

                case SB_PAGEDOWN:
                    dn =  rc.right / 2  + 1;
                    break;

                case SB_PAGEUP:
                    dn = -rc.right / 2  + 1;
                    break;

                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
                    dn = GET_WM_HSCROLL_POS(wParam, lParam)-iPos;
                    break;

                default:
                    dn = 0;
                    break;
            }
            /* Limit scrolling to current scroll range */
            if (dn = BOUND (iPos + dn, iMin, iMax) - iPos) {
                ScrollWindow (hWnd, -dn, 0, NULL, NULL);
                SetScrollPos (hWnd, SB_HORZ, iPos + dn, TRUE);
            }
            break;

        case WM_LBUTTONDOWN:
            /* Start rubberbanding a rect. and track it's dimensions.
             * set the clip rectangle to it's dimensions.
             */
            TrackMouse (hWnd, MAKEMPOINT(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            break;

        case WM_INITMENU:
            /* check/uncheck menu items depending on state  of related
             * flags
             */

            CheckMenuItem((HMENU)wParam, IDM_UPDATECOL,
                (bUpdateColors ? MF_CHECKED : MF_UNCHECKED));
            CheckMenuItem((HMENU)wParam, IDM_TRANSPARENT,
                (wTransparent == TRANSPARENT ? MF_CHECKED : MF_UNCHECKED));
            CheckMenuItem((HMENU)wParam, IDM_DIBSCREEN,
                (bDIBToDevice ? MF_CHECKED : MF_UNCHECKED));
            CheckMenuItem((HMENU)wParam, IDM_NOUGLY,
                (bNoUgly ? MF_CHECKED : MF_UNCHECKED));
            CheckMenuItem((HMENU)wParam, IDM_MEMORYDIB, MF_CHECKED);
            EnableMenuItem((HMENU)wParam, IDM_PASTEDIB,
                IsClipboardFormatAvailable(CF_DIB)?MF_ENABLED:MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_PASTEDDB,
                IsClipboardFormatAvailable(CF_BITMAP)?MF_ENABLED:MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_PASTEPAL,
                IsClipboardFormatAvailable(CF_PALETTE)?MF_ENABLED:MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_PRINT,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_SAVE,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_COPY,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);

            EnableMenuItem((HMENU)wParam, IDM_ANIMATE0,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_ANIMATE5,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_ANIMATE50,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_ANIMATE100,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_ANIMATE200,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_ANIMATE201,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, IDM_STEALCOL,
                bLegitDraw ? MF_ENABLED : MF_GRAYED);
            break;

        default:
            return DefWindowProc (hWnd, iMessage, wParam, lParam) ;

    }
    return 0L ;

}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : MenuCommand ( HWND hWnd, WPARAM wParam)                            *
 *                                                                          *
 *  PURPOSE    : Processes menu commands.                                   *
 *                                                                          *
 *  RETURNS    : TRUE  - if command could be processed.                     *
 *               FALSE - otherwise                                          *
 *                                                                          *
 ****************************************************************************/
BOOL MenuCommand (
    HWND hWnd,
    UINT id)

{
    BITMAPINFOHEADER bi;
    HDC              hDC;
    HANDLE           h;
    HBITMAP          hbm;
    HPALETTE         hpal;
    WORD             i;
    CHAR             Name[40];
    BOOL             bSave;
    INT              xSize, ySize, xRes, yRes, dx, dy;
    RECT             Rect;
    HFILE            fh;
    WORD             fFileOptions;
    CHAR             lpBuffer[128];

    switch (id) {
        case IDM_ABOUT:
                /* Show About .. box */
                fDialog ((INT)ABOUTBOX, hWnd,(FARPROC)AppAbout);
                break;

        case IDM_COPY:
                if (!bLegitDraw)
                    return 0L;

                /* Clean clipboard of contents */
                if (OpenClipboard(hWnd)) {
                    EmptyClipboard ();
                    SetClipboardData (CF_DIB     ,NULL);
                    SetClipboardData (CF_BITMAP  ,NULL);
                    SetClipboardData (CF_PALETTE ,NULL);
                    CloseClipboard ();
                }
                break;

        case IDM_PASTEPAL:
                if (OpenClipboard (hWnd)) {
                    if (h = GetClipboardData (CF_PALETTE)) {
                        /* Delete current palette and get the CF_PALETTE data
                         * from the clipboard
                         */
                        if (hpalCurrent)
                            DeleteObject (hpalCurrent);

                        hpalCurrent = CopyPalette (h);

                        /*
                         * If we have a bitmap realized against the old palette
                         * delete the bitmap and rebuild it using the new palette.
                         */
                        if (hbmCurrent){
                            DeleteObject (hbmCurrent);
                            hbmCurrent = NULL;

                            if (hdibCurrent)
                                hbmCurrent = BitmapFromDib (hdibCurrent, hpalCurrent);
                        }
                    }
                    CloseClipboard();
                }
                break;

        case IDM_PASTEDIB:
                if (OpenClipboard (hWnd)) {
                    if (h = GetClipboardData (CF_DIB)) {
                        /* Delete current DIB and get CF_DIB and
                         * CF_PALETTE format data from the clipboard
                         */
                        hpal = GetClipboardData (CF_PALETTE);

                        FreeDib();
                        hdibCurrent = CopyHandle (h);
                        if (hdibCurrent) {
                            bLegitDraw = TRUE;
                            lstrcpy(achFileName,"<Clipboard>");
                            hbiCurrent = hdibCurrent;

                            /* If there is a CF_PALETTE object in the
                             * clipboard, this is the palette to assume
                             * the DIB should be realized against, otherwise
                             * create a palette for it.
                             */
                            if (hpal)
                                hpalCurrent = CopyPalette (hpal);
                            else
                                hpalCurrent = CreateDibPalette (hdibCurrent);

                            SizeWindow(hWnd);
                        }
                        else {
                            bLegitDraw = FALSE;
                            LoadString(hInst, IDS_NOMEM, lpBuffer, sizeof(lpBuffer));
                            ErrMsg(lpBuffer);
                        }
                    }
                    CloseClipboard();
                }
                break;

        case IDM_PASTEDDB:
                if (OpenClipboard (hWnd)) {
                    if (hbm = GetClipboardData(CF_BITMAP)) {
                        hpal = GetClipboardData(CF_PALETTE);
                        FreeDib();

                        /*
                         * If there is a CF_PALETTE object in the
                         * clipboard, this is the palette to assume
                         * the bitmap is realized against.
                         */
                        if (hpal)
                            hpalCurrent = CopyPalette(hpal);
                        else
                            hpalCurrent = GetStockObject(DEFAULT_PALETTE);

                        hdibCurrent = DibFromBitmap(hbm,BI_RGB,0,hpalCurrent);

                        if (hdibCurrent) {
                            bLegitDraw = TRUE;
                            lstrcpy(achFileName,"<Clipboard>");
                            hbiCurrent = hdibCurrent;

                            hbmCurrent = BitmapFromDib(hdibCurrent,hpalCurrent);

                            SizeWindow(hWnd);
                        }
                        else {
                            bLegitDraw = FALSE;
                            LoadString(hInst, IDS_NOMEM, lpBuffer, sizeof(lpBuffer));
                            ErrMsg(lpBuffer);
                        }
                    }
                    CloseClipboard ();
                }
                break;

        case IDM_PRINT:
                GetWindowText(hWnd, Name, sizeof(Name));

                DibInfo(hbiCurrent, &bi);

                if (!IsRectEmpty(&rcClip))
                {
                    bi.biWidth  = rcClip.right  - rcClip.left;
                    bi.biHeight = rcClip.bottom - rcClip.top;
                }

                /* Initialise printer stuff */
                if (!(hDC = GetPrinterDC()))
                        break;

                xSize = GetDeviceCaps(hDC, HORZRES);
                ySize = GetDeviceCaps(hDC, VERTRES);
                xRes  = GetDeviceCaps(hDC, LOGPIXELSX);
                yRes  = GetDeviceCaps(hDC, LOGPIXELSY);

                /* Use half inch margins on left and right
                 * and one inch on top. Maintain the same aspect ratio.
                 */

                dx = xSize - xRes;
                dy = (INT)((LONG)dx * bi.biHeight/bi.biWidth);

                /* Fix bounding rectangle for the picture .. */
                Rect.top    = yRes;
                Rect.left   = xRes / 2;
                Rect.bottom = yRes + dy;
                Rect.right  = xRes / 2 + dx;

                /* ... and inform the driver */
                Escape(hDC, SET_BOUNDS, sizeof(RECT), (LPSTR)&Rect, NULL);

                bSave = TRUE;

                //
                // Use new Windows NT printing APIs...Petrus Wong 12-May-1993
                //
                if (InitPrinting(hDC, hWnd, hInst, Name)) {

                        StartPage(hDC);
                        PrintDIB(hWnd, hDC, xRes/2, yRes, dx, dy);

                        /* Signal to the driver to begin translating the drawing
                         * commands to printer output...
                         */
                        EndPage(hDC);
                        TermPrinting(hDC);
                }

                DeleteDC(hDC);
                break;

        case IDM_OPEN:
                {

                /* Bring up File/Open ... dialog */
                LoadString(hInst, IDS_OPENDIBPROMPT, lpBuffer, sizeof(lpBuffer));
                fh = DlgOpenFile (hWnd,
                                  lpBuffer,
                                  (LONG)OF_EXIST | OF_MUSTEXIST | OF_NOOPTIONS,
                                  szBitmapExt,
                                  achFileName,
                                  NULL
                                  );
                /*  Load up the DIB if the user did not press cancel */
                if (fh > 0) {
                   StartWait();
                   if (InitDIB (hWnd))
                       InvalidateRect (hWnd, NULL, FALSE);
                   else
                       bLegitDraw = FALSE;
                   EndWait();
                }
                break;
                }
        case IDM_SAVE:
                DibInfo(hbiCurrent,&bi);
                fFileOptions = 0;

                /* Depending on compression type for current DIB,
                 * set the appropriate bit in the fFileOptions flag
                 */
                if (bi.biCompression == BI_RGB)
                    fFileOptions |= F_RGB;
                else if (bi.biCompression == BI_RLE4)
                    fFileOptions |= F_RLE4;
                else if (bi.biCompression == BI_RLE8)
                    fFileOptions |= F_RLE8;

                /* Depending on bits/pixel type for current DIB,
                 * set the appropriate bit in the fFileOptions flag
                 */
                switch (bi.biBitCount){
                    case  1:
                        fFileOptions |= F_1BPP;
                        break;

                    case  4:
                        fFileOptions |= F_4BPP;
                        break;

                    case  8:
                        fFileOptions |= F_8BPP;
                        break;

                    case 24:
                        fFileOptions |= F_24BPP;
                }

                /* Bring up File/Save... dialog and get info. about filename,
                 * compression, and bits/pix. of DIB to be written.
                 */
                LoadString(hInst, IDS_SAVEDIBPROMPT, lpBuffer, sizeof(lpBuffer));
                fh = DlgOpenFile (hWnd,
                                  lpBuffer,
                                  (LONG)OF_EXIST | OF_SAVE | OF_NOSHOWSPEC,
                                  szBitmapExt,
                                  achFileName,
                                  &fFileOptions);

                /* Extract DIB specs. if the user did not press cancel */
                if (fh != 0){
                    if (fFileOptions & F_RGB)
                        bi.biCompression = BI_RGB;

                    if (fFileOptions & F_RLE4)
                        bi.biCompression = BI_RLE4;

                    if (fFileOptions & F_RLE8)
                        bi.biCompression = BI_RLE8;

                    if (fFileOptions & F_1BPP)
                        bi.biBitCount = 1;

                    if (fFileOptions & F_4BPP)
                        bi.biBitCount = 4;

                    if (fFileOptions & F_8BPP)
                        bi.biBitCount = 8;

                    if (fFileOptions & F_24BPP)
                        bi.biBitCount = 24;

                    /* Realize a DIB in the specified format and obtain a
                     * handle to it.
                     */
                    hdibCurrent = RealizeDibFormat(bi.biCompression,bi.biBitCount);
                    if (!hdibCurrent){
                        LoadString(hInst, IDS_CANTSAVEFILE, lpBuffer, sizeof(lpBuffer));
                        ErrMsg(lpBuffer);
                        return 0L;
                    }

                    /* Write the DIB */
                    StartWait();
                    if (!WriteDIB(achFileName,hdibCurrent)) {
                        LoadString(hInst, IDS_CANTSAVEFILE, lpBuffer, sizeof(lpBuffer));
                        ErrMsg(lpBuffer);
                    }
                    EndWait();
                }
                break;

        case IDM_EXIT:
                PostMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
                break;

        case IDM_UPDATECOL:
                /* Toggle state of "update screen colors" flag. If it is
                 * off, clear the "hide changes" flag
                 */
                bUpdateColors = !bUpdateColors;
                if (bUpdateColors)
                    bNoUgly = 0;
                break;

        case IDM_DIBSCREEN:
                bDIBToDevice = !bDIBToDevice;
                InvalidateRect(hWnd, (LPRECT) (NULL), 1);
                break;

        case IDM_MEMORYDIB:
                break;

        case IDM_NOUGLY:
                /* Toggle state of "hide changes" flag. If it is off, clear
                 * the "update screen colors" flag. This will tell SHOWDIB
                 * to paint itself black while the palette is changing.
                 */
                bNoUgly = !bNoUgly;
                if (bNoUgly)
                    bUpdateColors = 0;
                break;

        case IDM_TRANSPARENT:
                /* Toggle DC mode */
                wTransparent = (WORD) (wTransparent == TRANSPARENT ?
                    OPAQUE : TRANSPARENT);
                break;

        case IDM_ANIMATE0:
                if (!hpalSave)
                    break;

                /* Reset animation count and stop timer */
                KillTimer(hWnd, 1);
                nAnimating = 0;

                /* Restore palette which existed before animation started */
                DeleteObject(hpalCurrent);
                hpalCurrent = hpalSave;

                /* Rebuild bitmap based on newly realized information */
                hDC = GetDC (hWnd);
                SelectPalette (hDC, hpalCurrent, 0);
                RealizePalette (hDC);
                ReleaseDC (hWnd, hDC);

                if (hbmCurrent){
                    DeleteObject (hbmCurrent);
                    hbmCurrent = NULL;

                    if (hdibCurrent)
                       hbmCurrent = BitmapFromDib (hdibCurrent, hpalCurrent);
                }
                hpalSave = NULL;

                /* Force redraw with new palette for everyone */
                InvalidateRect(hWnd, NULL, TRUE);
                break;

        case IDM_STEALCOL:
        case IDM_ANIMATE5:
        case IDM_ANIMATE20:
        case IDM_ANIMATE50:
        case IDM_ANIMATE100:
        case IDM_ANIMATE200:
        case IDM_ANIMATE201:
                /* Set animation count i.e number of times animation is to
                 * take place.
                 */
                nAnimating = id;
                if (id == IDM_STEALCOL)
                        nAnimating = 0;

                /* Save current palette */
                hpalSave = CopyPalette(hpalCurrent);

                GetObject(hpalCurrent, sizeof(INT), (LPSTR)&pLogPal->palNumEntries);
                GetPaletteEntries(hpalCurrent, 0, pLogPal->palNumEntries, pLogPal->palPalEntry);

                /* Reserve all entries in the palette otherwise AnimatePalette()
                 * will not modify them
                 */
                for (i = 0; i < pLogPal->palNumEntries; i++) {
                     pLogPal->palPalEntry[i].peFlags = (BYTE)PC_RESERVED;
                }

                SetPaletteEntries(hpalCurrent, 0, pLogPal->palNumEntries, pLogPal->palPalEntry);

                /* Rebuild bitmap based on newly realized information */
                if (hbmCurrent){
                    DeleteObject (hbmCurrent);
                    hbmCurrent = NULL;

                    if (hdibCurrent)
                       hbmCurrent = BitmapFromDib (hdibCurrent, hpalCurrent);
                }

                /* Force redraw with new palette for everyone */
                InvalidateRect(hWnd, NULL, TRUE);

                /* Initiate the timer so that palette can be animated in
                 * response to a WM_TIMER message
                 */
                if (nAnimating && !SetTimer(hWnd, 1, 250, (TIMERPROC)(LPSTR) NULL))
                        nAnimating = 0;

        default:
                break;
    }

    return TRUE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : InitDIB(hWnd)                                              *
 *                                                                          *
 *  PURPOSE    : Reads a DIB from a file, obtains a handle to it's          *
 *               BITMAPINFO struct., sets up the palette and loads the DIB. *
 *                                                                          *
 *  RETURNS    : TRUE  - DIB loads ok                                       *
 *               FALSE - otherwise                                          *
 *                                                                          *
 ****************************************************************************/
INT InitDIB(HWND hWnd)
{
    HFILE              fh;
    LPBITMAPINFOHEADER lpbi;
    WORD FAR *         pw;
    INT                i;
    BITMAPINFOHEADER   bi;
    OFSTRUCT           of;
    CHAR               lpBuffer[128];

    FreeDib();

    /* Open the file and get a handle to it's BITMAPINFO */

    fh = OpenFile(achFileName, (LPOFSTRUCT)&of, (UINT)OF_READ);
    if (fh == -1) {
        LoadString(hInst, IDS_CANTOPENFILE, lpBuffer, sizeof(lpBuffer));
        ErrMsg(lpBuffer, (LPSTR)achFileName);
        return FALSE;
    }
    hbiCurrent = ReadDibBitmapInfo(fh);

    dwOffset = _llseek(fh, 0L, (UINT)SEEK_CUR);
    _lclose(fh);

    if (hbiCurrent == NULL) {
        LoadString(hInst, IDS_BADDIBFILE, lpBuffer, sizeof(lpBuffer));
        ErrMsg(lpBuffer, (LPSTR)achFileName);
        return FALSE;
    }
    DibInfo(hbiCurrent,&bi);

    /* Set up the palette */
    hpalCurrent = CreateDibPalette(hbiCurrent);
    if (hpalCurrent == NULL) {
        LoadString(hInst, IDS_CREATEPALFAIL, lpBuffer, sizeof(lpBuffer));
        ErrMsg(lpBuffer);
        return FALSE;
    }

    /*  Convert the DIB color table to palette relative indexes, so
     *  SetDIBits() and SetDIBitsToDevice() can avoid color matching.
     *  We can do this because the palette we realize is identical
     *  to the color table of the bitmap, ie the indexes match 1 to 1
     *
     *  Now that the DIB color table is palette indexes not RGB values
     *  we must use DIB_PAL_COLORS as the wUsage parameter to SetDIBits()
     */
    lpbi = (VOID FAR *)GlobalLock(hbiCurrent);
    if (lpbi->biBitCount != 24) {
        fPalColors = TRUE;

        pw = (WORD FAR *)((LPSTR)lpbi + lpbi->biSize);

        for (i=0; i<(INT)lpbi->biClrUsed; i++)
            *pw++ = (WORD)i;
    }
    GlobalUnlock(hbiCurrent);
    bLegitDraw = TRUE;

    /*  If the input bitmap is not in RGB FORMAT the banding code will
     *  not work!  we need to load the DIB bits into memory.
     *  if memory DIB, load it all NOW!  This will avoid calling the
     *  banding code.
     */
    hdibCurrent = OpenDIB(achFileName);

    /*  If the RLE could not be loaded all at once, exit gracefully NOW,
     *  to avoid calling the banding code
     */
    if ((bi.biCompression != BI_RGB) && !hdibCurrent){
        LoadString(hInst, IDS_CANTLOADRLE, lpBuffer, sizeof(lpBuffer));
        ErrMsg (lpBuffer);
        FreeDib();
        return FALSE;
    }

    if (hdibCurrent && !bDIBToDevice){
        hbmCurrent = BitmapFromDib(hdibCurrent,hpalCurrent);
        if (!hbmCurrent){
            LoadString(hInst, IDS_CANTCREATEBMP, lpBuffer, sizeof(lpBuffer));
            ErrMsg (lpBuffer);
            FreeDib();
            return FALSE;
        }
    }

    SizeWindow(hWnd);

    return TRUE;
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : FreeDib(void)                                              *
 *                                                                          *
 *  PURPOSE    : Frees all currently active bitmap, DIB and palette objects *
 *               and initializes their handles.                             *
 *                                                                          *
 ****************************************************************************/
VOID FreeDib()
{
    if (hpalCurrent)
        DeleteObject(hpalCurrent);

    if (hbmCurrent)
        DeleteObject(hbmCurrent);

    if (hdibCurrent)
        GlobalFree(hdibCurrent);

    if (hbiCurrent && hbiCurrent != hdibCurrent)
        GlobalFree(hbiCurrent);

    fPalColors  = FALSE;
    bLegitDraw  = FALSE;
    hpalCurrent = NULL;
    hdibCurrent = NULL;
    hbmCurrent  = NULL;
    hbiCurrent  = NULL;
    SetRectEmpty (&rcClip);
}


SHOWDIB.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#include <stdio.h>
/****************************************************************************
 *                                                                          *
 *  FILE        : SHOWDIB.H                                                 *
 *                                                                          *
 *  DESCRIPTION : Header/include file for ShowDIB example.                  *
 *                                                                          *
 ****************************************************************************/

typedef    POINTS       MPOINT         ;

#ifdef _WIN32
#define GET_WM_ACTIVATE_STATE(wp, lp)   LOWORD(wp)
#define GET_WM_VSCROLL_CODE(wp, lp)     LOWORD(wp)
#define GET_WM_VSCROLL_POS(wp, lp)      HIWORD(wp)
#define GET_WM_HSCROLL_CODE(wp, lp)     LOWORD(wp)
#define GET_WM_HSCROLL_POS(wp, lp)      HIWORD(wp)
#define GET_EM_SETSEL_MPS(iStart, iEnd) (UINT)(iStart), (LONG)(iEnd)
#define GET_WM_COMMAND_CMD(wp, lp)      HIWORD(wp)
#define MAKEMPOINT(l)                   (*((MPOINT *)&(l)))
#define HUGE_T
#else
#define GET_WM_ACTIVATE_STATE(wp, lp)               (wp)
#define GET_WM_HSCROLL_CODE(wp, lp)                 (wp)
#define GET_WM_HSCROLL_POS(wp, lp)                  LOWORD(lp)
#define GET_WM_VSCROLL_CODE(wp, lp)                 (wp)
#define GET_WM_VSCROLL_POS(wp, lp)                  LOWORD(lp)
#define GET_EM_SETSEL_MPS(iStart, iEnd) 0, MAKELONG(iStart, iEnd)
#define GET_WM_COMMAND_CMD(wp, lp)      HIWORD(lp)
#define MAKEMPOINT(l)                   (*((MPOINT FAR *)&(l)))
#define HUGE_T         huge
#endif

/* Macro to restrict a given value to an upper or lower boundary value */
#define BOUND(x,min,max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))

/* Macro to swap two values */
#define SWAP(x,y)   ((x)^=(y)^=(x)^=(y))

/* Macro to find the minimum of two values */
#define MIN(x,y) (((x) <= (y)) : x ? y)

/* Macros to display/remove hourglass cursor for lengthy operations */
#define StartWait() hcurSave = SetCursor(LoadCursor(NULL,IDC_WAIT))
#define EndWait()   SetCursor(hcurSave)

/* WinFlags, __WinFlags is a ABS external, thus the need for the & */
#define WinFlags    ((WORD)(&_WinFlags))

#define MINBAND         50     /* Minimum band size used by the program */
#define BANDINCREMENT   20     /* Decrement for band size while trying  */
                               /* to determine optimum band size.       */

/* Bit values for the DIB attributes flag (fFileOptions). Also used as control
 * IDs for the radiobuttons for DIB bitcount in the File/Open dialog
 */
#define F_1BPP      DLGOPEN_1BPP
#define F_4BPP      DLGOPEN_4BPP
#define F_8BPP      DLGOPEN_8BPP
#define F_24BPP     DLGOPEN_24BPP

/* Bit values for the DIB attributes flag (fFileOptions), also used as control
 * IDs for the radiobuttons for DIB compression type in the File/Open dialog
 */
#define F_RLE4      DLGOPEN_RLE4
#define F_RLE8      DLGOPEN_RLE8
#define F_RGB       DLGOPEN_RGB

#ifdef NOT_IN_STDIO
/* flags for _lseek */
#define  SEEK_CUR 1
#define  SEEK_END 2
#define  SEEK_SET 0
#endif

/* struct to be passed in for the SETDIBSCALING printer escape */
typedef struct {
        SHORT ScaleMode;
        SHORT dx, dy;
} DIBPARAMS;

/* Menu IDs */

#define IDM_UPDATECOL   1000
#define IDM_ANIMATE0    1001
#define IDM_ANIMATE5    5
#define IDM_ANIMATE20   20
#define IDM_ANIMATE50   50
#define IDM_ANIMATE100  100
#define IDM_ANIMATE200  200
#define IDM_ANIMATE201  32000

#define IDM_OPEN        1002
#define IDM_EXIT        1003
#define IDM_ABOUT       1004
#define IDM_DIBSCREEN   1005
#define IDM_NOUGLY      1006
#define IDM_STEALCOL    1009
#define IDM_PRINT       1010
#define IDM_COPY        1012
#define IDM_MEMORYDIB   1014
#define IDM_SAVE        1015
#define IDM_TRANSPARENT 1016

#define IDM_PASTEDIB    1020
#define IDM_PASTEDDB    1021
#define IDM_PASTEPAL    1022

/*String IDs*/
#define IDS_MEMLOW          650
#define IDS_NOMEM           651
#define IDS_OPENDIBPROMPT   652
#define IDS_SAVEDIBPROMPT   653
#define IDS_CANTSAVEFILE    654
#define IDS_CANTOPENFILE    655
#define IDS_BADDIBFILE      656
#define IDS_CREATEPALFAIL   657
#define IDS_CANTLOADRLE     658
#define IDS_CANTCREATEBMP   659
#define IDS_CANTDRAWSCANS   660
#define IDS_CANTDRAWMAP     661
#define IDS_SAVESTR         662
#define IDS_OPENSTR         663

#define IDI_APP         2000

#define ABOUTBOX        100

extern HWND hWndApp;                   /* The handle to the app. window     */

#define MAXREAD  32768                 /* Number of bytes to be read during */
                                       /* each read operation.              */

/* Header signatutes for various resources */
#define BFT_ICON   0x4349   /* 'IC' */
#define BFT_BITMAP 0x4d42   /* 'BM' */
#define BFT_CURSOR 0x5450   /* 'PT' */

/* macro to determine if resource is a DIB */
#define ISDIB(bft) ((bft) == BFT_BITMAP)

/* Macro to align given value to the closest DWORD (unsigned long ) */
#define ALIGNULONG(i)   ((i+3)/4*4)

/* Macro to determine to round off the given value to the closest byte */
#define WIDTHBYTES(i)   ((i+31)/32*4)

#define PALVERSION      0x300
#define MAXPALETTE      256       /* max. # supported palette entries */

/********** THE FOLLOWING ARE USED IN DLGOPEN.C  ************************/

/* IDs for controls in the DlgOpen dialog */
#define DLGOPEN_EDIT            101
#define DLGOPEN_FILE_LISTBOX    102
#define DLGOPEN_DIR_LISTBOX     103
#define DLGOPEN_PATH            104
#define DLGOPEN_TEXT            105
#define DLGOPEN_FOLDOUT         106
#define DLGOPEN_BIG             107
#define DLGOPEN_SMALL           108

#define DLGOPEN_OPTION          0xF000
#define DLGOPEN_1BPP            0x0001
#define DLGOPEN_4BPP            0x0002
#define DLGOPEN_8BPP            0x0004
#define DLGOPEN_24BPP           0x0008
#define DLGOPEN_RLE4            0x0010
#define DLGOPEN_RLE8            0x0020
#define DLGOPEN_RGB             0x0040

#define DLGOPEN_OPTION8         0x0080

/*  flags:
 *     The LOWORD is the standard FileOpen() flags (OF_*)
 *     the HIWORD can be any of the following:
 */
#define OF_MUSTEXIST    0x00010000  /* file must exist if the user hits Ok    */
#define OF_NOSHOWSPEC   0x00020000  /* DO NOT Show search spec in the edit box*/
#define OF_SHOWSPEC     0x00000000  /* Show the search spec in the edit box   */
#define OF_SAVE         0x00040000  /* Ok button will say "Save"              */
#define OF_OPEN         0x00080000  /* Ok button will say "Open"              */
#define OF_NOOPTIONS    0x00100000  /* Disable the options fold out           */

/* Attributes for DlgDirLst() */
#define ATTRFILELIST    0x0000        /* include files only          */
#define ATTRDIRLIST     0xC010        /* directories and drives ONLY */
#define CBEXTMAX        6             /* Number of bytes in "\*.txt" */

#define IDF(id)     ((id) & ~DLGOPEN_OPTION)  /* extracts flag from control ID */
#define FID(f)      ((f)  |  DLGOPEN_OPTION)  /* extracts control ID from flag */

/***************** GLOBAL VARIABLES *************************/

extern CHAR           achFileName[128]; /* File pathname                       */
extern DWORD          dwOffset;     /* Current position if DIB file pointer    */
extern RECT           rcClip;       /* Current clip rectangle.                 */
extern BOOL           fPalColors;   /* TRUE if the current DIB's color table   */
                                    /* contains palette indexes not rgb values */
extern BOOL           bDIBToDevice; /* Use SetDIBitsToDevice() to BLT data.    */
extern BOOL           bLegitDraw;   /* We have a valid bitmap to draw          */
extern WORD           wTransparent; /* Mode of DC                              */
extern CHAR           szAppName[];  /* App. name                               */
extern HPALETTE       hpalCurrent;  /* Handle to current palette               */
extern HANDLE         hdibCurrent;  /* Handle to current memory DIB            */
extern HBITMAP        hbmCurrent;   /* Handle to current memory BITMAP         */
extern HANDLE         hbiCurrent;   /* Handle to current bitmap info struct    */
extern DWORD          dwStyle;      /* Style bits of the App. window           */
extern HINSTANCE      hInst;        /* Global instance handle                  */

/***************** FUNCTION DECLARATIONS *******************/

DWORD PASCAL lread(INT fh, VOID FAR *pv, DWORD ul);
DWORD PASCAL lwrite(INT fh, VOID FAR *pv, DWORD ul);

/***********************************************************/
/* Declarations of functions used in showdib.c module      */
/***********************************************************/

LONG     APIENTRY WndProc  (HWND, UINT, UINT, LONG) ;
BOOL               MenuCommand (HWND hWnd, UINT wParam);
INT                InitDIB (HWND);
VOID               FreeDib (VOID);

/***********************************************************/
/* Declarations of functions used in dib.c module          */
/***********************************************************/

HANDLE      OpenDIB (LPSTR szFile);
BOOL        WriteDIB (LPSTR szFile,HANDLE hdib);
WORD        PaletteSize (VOID FAR * pv);
WORD        DibNumColors (VOID FAR * pv);
HPALETTE    CreateDibPalette (HANDLE hdib);
HPALETTE    CreateBIPalette (LPBITMAPINFOHEADER lpbi);
HANDLE      DibFromBitmap (HBITMAP hbm, DWORD biStyle, WORD biBits, HPALETTE hpal);
HBITMAP     BitmapFromDib (HANDLE hdib, HPALETTE hpal);
BOOL        DibBlt (HDC hdc, INT x0, INT y0, INT dx, INT dy, HANDLE hdib, INT x1, INT y1, LONG rop);
BOOL        StretchDibBlt (HDC hdc, INT x0, INT y0, INT dx, INT dy, HANDLE hdib, INT x1, INT y1, INT dx1, INT dy1, LONG rop);
BOOL        DibInfo (HANDLE hdib,LPBITMAPINFOHEADER lpbi);
HANDLE      ReadDibBitmapInfo (INT fh);
BOOL        DrawBitmap (HDC hdc, INT x, INT y, HBITMAP hbm, DWORD rop);
VOID            ReadBitMapFileHeaderandConvertToDwordAlign(HFILE fh, LPBITMAPFILEHEADER pbf, LPDWORD lpdwoff);
VOID    WriteMapFileHeaderandConvertFromDwordAlignToPacked(HFILE fh, LPBITMAPFILEHEADER pbf);

/***********************************************************/
/* Declarations of functions used in drawdib. c module     */
/***********************************************************/

VOID               PrintDIB (HWND hWnd, HDC hDC, INT x, INT y, INT dx, INT dy);
VOID               AppPaint (HWND hWnd, HDC hDC, INT x, INT y);
INT                ErrMsg (PSTR sz,...);
BOOL               fDialog (INT id, HWND hwnd, FARPROC fpfn);
VOID               BandDIB (HWND hWnd, HDC hDC, INT x, INT y);
BOOL     APIENTRY AppAbout (HWND, UINT, UINT, LONG) ;
HANDLE             CopyHandle (HANDLE h);
VOID               SizeWindow (HWND hWnd);
VOID               GetRealClientRect (HWND hwnd, PRECT lprc);
VOID               SetScrollRanges (HWND hwnd);
VOID               DrawSelect (HDC hdc, BOOL fDraw);
VOID        PASCAL NormalizeRect (RECT *prc);
VOID               TrackMouse (HWND hwnd, MPOINT pt);
HBITMAP            CopyBitmap (HBITMAP hbm);
HPALETTE           CopyPalette (HPALETTE hpal);
HBITMAP            CropBitmap (HBITMAP hbm, PRECT prc);
HANDLE             RenderFormat (INT cf);
HANDLE             RealizeDibFormat (DWORD biStyle, WORD biBits);

/***********************************************************/
/* Declarations of functions used in the print.c module    */
/***********************************************************/

BOOL    PASCAL InitPrinting (HDC hDC, HWND hWnd, HANDLE hInst, LPSTR msg);
VOID    PASCAL TermPrinting (HDC hDC);
HDC     PASCAL GetPrinterDC (VOID);
HDC     PASCAL GetPrinterDC1 (VOID);

/***********************************************************/
/* Declarations of functions used in the dlgopen.c module  */
/***********************************************************/

LONG APIENTRY DlgfnOpen(
        HWND hwnd,
    UINT msg,
    UINT wParam,
    LONG lParam
);
HFILE APIENTRY DlgOpenFile (
        HWND            hwndParent,
        CHAR            *szTitleIn,
        DWORD           flags,
        CHAR            *szExtIn,
    CHAR        *szFileNameIn,
    WORD                *pfOpt
);

/*****************************************************/
/* make new sizeof structs to cover dword alignment  */
/*****************************************************/

#define SIZEOF_BITMAPFILEHEADER_PACKED  (   \
    sizeof(WORD) +      /* bfType      */   \
    sizeof(DWORD) +     /* bfSize      */   \
    sizeof(WORD) +      /* bfReserved1 */   \
    sizeof(WORD) +      /* bfReserved2 */   \
    sizeof(DWORD))      /* bfOffBits   */


FX.C

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/
/*****************************************************************************
 * *
 *FX.C                              *
 * *
 *PURPOSE:  Routines for rendering text with stange effects                *
 * *
 * *
 *FillOut           - Converts an array of line segments into an array of *
 *                       all the points in the segments     *
 * *
 *RenderPathPoints   - Renders the outline of points returned from GetPath *
 * *
 *RenderAndFillPath  - Renders the points returned from GetPath as a       *
 *                       filled polygon *
 * *
 *TextEffect         - Draws a string of text between two guide lines      *
 * *
 *****************************************************************************/

#include <windows.h>
#include "fx.h"

// This structure is used by FillOut for passing data into LineDDA
typedef struct tagSEG {
  DWORD dwPos;
  LPPOINT lpPoints;
} PTS;

/**********************************************************************
 *                                                                    *
 * AddSegment - used with LineDDA to add points from a line segment   *
 *    to an array containing all the points on a set of line segments *
 *                                                                    *
 *********************************************************************/
VOID CALLBACK AddSegment(int x, int y, PTS *pts)
{
   pts->lpPoints[pts->dwPos].x = x;
   pts->lpPoints[pts->dwPos].y = y;
   pts->dwPos++;
}

/**********************************************************************
 *                                                                    *
 * CountPoints - used with LineDDA to determine the number of points  *
 *    needed to store all the points on a set of line segments        *
 *                                                                    *
 *********************************************************************/
VOID CALLBACK CountPoints(int x, int y, LPDWORD lpdwNumPts)
{
   (*lpdwNumPts)++;

   UNREFERENCED_PARAMETER(x);
   UNREFERENCED_PARAMETER(y);
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  FillOut(LPPOINT, LPDWORD)                               *
 *  *
 * PURPOSE:   Converts an array of line segments into an array of all *
 *            of the points comprising the line segments.             *
 *                                                                    *
 *********************************************************************/
BOOL FillOut(LPPOINT *lpPoints, LPDWORD lpdwNumPts)
{
   DWORD i;
   LPPOINT lpPts = *lpPoints;
   PTS pts;
   DWORD dwNumPts = 0;

   // Make sure we have at least two points
   if (*lpdwNumPts < 2) {
     MessageBox(NULL, "You need at least two points for a guide line.", "Not enough points!", MB_ICONSTOP);
     *lpdwNumPts = 0;
     return FALSE;
   }

   // Find out how namy points are on the segments
   for (i=0; i<*lpdwNumPts - 1; i++)
     LineDDA(lpPts[i].x, lpPts[i].y, lpPts[i+1].x, lpPts[i+1].y, (LINEDDAPROC)CountPoints, (LPARAM)&dwNumPts);

   // If there are too many points, print an anoying message so the user doesnt do it again
   if (dwNumPts > MAXFILLPOINTS) {
     MessageBox(NULL, "Make your guide lines a bit shorter please!", "Too many points!", MB_ICONSTOP);
     *lpdwNumPts = 0;
     return FALSE;  // Bail
   }

   // Allocate memory for the the points and initialize our "last point" index
   pts.lpPoints = (LPPOINT)GlobalAlloc(GPTR, dwNumPts * sizeof(POINT));
   pts.dwPos = 0;

   // Convert the segments to points
   for (i=0; i<*lpdwNumPts - 1; i++)
     LineDDA(lpPts[i].x, lpPts[i].y, lpPts[i+1].x, lpPts[i+1].y, (LINEDDAPROC)AddSegment, (LPARAM)&pts);

   // Get rid of the original array of segments...
   GlobalFree(lpPts);

   // ... and replace it with the new points
   *lpPoints = pts.lpPoints;
   *lpdwNumPts = pts.dwPos;

   // Check to see if anything hit the fan
   if (!pts.dwPos)
     return FALSE;

   return TRUE;
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  PolyDraw95(HDC, LPPOINT, LPBYTE, int)                   *
 *  *
 * PURPOSE:   Draws the points returned from a call to GetPath()      *
 *            to an HDC                                               *
 *                                                                    *
 * NOTES:     This function is similar to the Windows NT function     *
 *            PolyDraw which draws a set of line segments and B zier  *
 *            curves.  Since PolyDraw is not supported on Windows 95  *
 *            this PolyDraw95 is used instead.                        *
 *                                                                    *
 *********************************************************************/
BOOL PolyDraw95(HDC  hdc,              // handle of a device context
                CONST LPPOINT lppt,   // address of array of points
                CONST LPBYTE lpbTypes, // address of line and curve identifiers
                int  cCount)        // count of points
{
  int i;

  for (i=0; i<cCount; i++)
    switch (lpbTypes[i]) {
      case PT_MOVETO :
         MoveToEx(hdc, lppt[i].x, lppt[i].y, NULL);
         break;

      case PT_LINETO | PT_CLOSEFIGURE:
      case PT_LINETO :
         LineTo(hdc, lppt[i].x, lppt[i].y);
         break;

      case PT_BEZIERTO | PT_CLOSEFIGURE:
      case PT_BEZIERTO :
     PolyBezierTo(hdc, &lppt[i], 3);
 i+=2;
         break;
   }

   return TRUE;
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  RenderPathPoints(HDC, LPPOINT, LPBYTE, int, BOOL)      *
 *  *
 * PURPOSE:   Renders the points returned from a call to GetPath()    *
 *            by converting them back into a path and calling either  *
 *            FillPath or StrokePath to display them.                 *
 *                                                                    *
 * NOTE:      The R2_MERGEPENNOT is used here so that things like the *
 *            inside of and "a" or an "o" get painted correctly when  *
 *            using FillPath.                                         *
 *                                                                    *
 *********************************************************************/
BOOL RenderPathPoints(HDC hDC, LPPOINT lpPoints, LPBYTE lpTypes, int iNumPts, BOOL bOutline)
{
  BeginPath(hDC); // Draw into a path so that we can use FillPath()

  PolyDraw95(hDC, lpPoints, lpTypes, iNumPts);

  CloseFigure(hDC);
  EndPath(hDC);

  // Draw the path
  if (bOutline)
    StrokePath(hDC); // As an outline
  else
      {              // As solid figures
        int iROP2 = SetROP2(hDC, R2_MERGEPENNOT);
        HPEN hPen = SelectObject(hDC, GetStockObject(NULL_PEN)); // Just say "no" to outlines
        HBRUSH hBrush = SelectObject(hDC, GetStockObject(BLACK_BRUSH)); // Paint It Black

        FillPath(hDC);

        // Restore the DC to its previous state
        SetROP2(hDC, iROP2);
        SelectObject(hDC, hPen);
        SelectObject(hDC, hBrush);
      }

  return TRUE;
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  GetRealTextExtent(LPPOINT, int, LPSIZE)                 *
 *  *
 * PURPOSE:   Makes sure that the extents in a SIZE are large enough  *
 *            to bound the points in an array.                        *
 *                                                                    *
 * NOTES:     Why not just use GetTextExtentPoint32?  Well, the text  *
 *            extents returned by GetTextExtentPoint32 dont include   *
 *            the overhang of an italics character into the next      *
 *            cell.  This function expands the extents to include     *
 *            the greatest x, and y extents of the string data.       *
 *                                                                    *
 *********************************************************************/
void GetRealTextExtent(LPPOINT lpPoints, int iNumPts, LPSIZE size)
{
  int i;

  for (i=0; i<iNumPts; i++) {
    if (lpPoints[i].x > size->cx) size->cx = lpPoints[i].x;
    if (lpPoints[i].y > size->cy) size->cy = lpPoints[i].y;
  }
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  TextEffect(HDC, LPPOINT, LPPOINT, DWORD, DWORD,         *
 *                            LPSTR, BOOL)    *
 *  *
 * PURPOSE:   Draws a string of text in the currently selected font   *
 *            using two arrays of points as guide lines for orienting *
 *            the text.  The text can be displayed either as outlines *
 *            or as filled characters.                                *
 *                                                                    *
 *********************************************************************/
BOOL TextEffect(HDC hDC,            // DC to display into
                LPPOINT lpTop,  // Top guide line
                LPPOINT lpBot, // Bottom guide line
                DWORD dwTopPts, // Number of points in top guide
                DWORD dwBotPts, // Number of points in bottom guide
                LPSTR szText, // Text string to apply effects to
                BOOL bOutlineOnly)  // Print as outline or as solid text
{
   LPPOINT lpPoints;  // Path data points
   LPBYTE lpTypes;  // Path data types
   int i, iNumPts;
   SIZE size;  // Text size info
   float fXScale, fYScale;  // Scaling values
   int iTopInd, iBotInd;  // Guide array indices

   // Set to transparent so we dont get an outline around the text string
   SetBkMode(hDC, TRANSPARENT);

   // Output the text into a path
   BeginPath(hDC);
   TextOut(hDC, 0, 0, szText, strlen(szText));
   EndPath(hDC);

   // How many points are in the path
   iNumPts = GetPath(hDC, NULL, NULL, 0);
   if (iNumPts == -1) return FALSE;

   // Allocate room for the points
   lpPoints = (LPPOINT)GlobalAlloc(GPTR, sizeof(POINT) * iNumPts);
   if (!lpPoints) return FALSE;

   // Allocate room for the point types
   lpTypes = GlobalAlloc(GPTR, iNumPts);
   if (!lpTypes) {
     GlobalFree(lpPoints);
   return FALSE;
   }

   // Get the points and types from the current path
   iNumPts = GetPath(hDC, lpPoints, lpTypes, iNumPts);

   // Even more error checking
   if (iNumPts == -1) {
     GlobalFree(lpTypes);
     GlobalFree(lpPoints);
 return FALSE;
   }

   //Get extents of the text string for scaling purposes
   GetTextExtentPoint32(hDC, szText, strlen(szText), &size);

   // OK, but lets make sure our extents are big enough (handle italics fonts)
   GetRealTextExtent(lpPoints, iNumPts, &size);

   // Relocate the points in the path based on the guide lines
   for (i=0; i < iNumPts; i++) {
    // How far along is this point on the x-axis
     fXScale = (float)lpPoints[i].x / (float)size.cx;

     // What point on the top guide does this coorespond to
     iTopInd = (int)(fXScale * (dwTopPts-1));
 // What point on the bottom guide does this coorespond to
     iBotInd = (int)(fXScale * (dwBotPts-1));

     // How far along is this point on the y-axis
     fYScale = (float)lpPoints[i].y / (float)size.cy;

     // Scale the points to their new locations
     lpPoints[i].x = (int)((lpBot[iBotInd].x * fYScale) + (lpTop[iTopInd].x * (1.0f-fYScale)));
     lpPoints[i].y = (int)((lpBot[iBotInd].y * fYScale) + (lpTop[iTopInd].y * (1.0f-fYScale)));
   }

   // Draw the new path
   RenderPathPoints(hDC, lpPoints, lpTypes, iNumPts, bOutlineOnly);

   GlobalFree(lpPoints);
   GlobalFree(lpTypes);

   return TRUE;
}


FX.H

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/
/*****************************************************************************
 * *
 *FX.H                              *
 * *
 *PURPOSE:  Routines for rendering text with stange effects                *
 * *
 ****************************************************************************/

#include <windows.h>

#define MAXFILLPOINTS 64000

BOOL FillOut(LPPOINT*, LPDWORD);
BOOL RenderPathPoints(HDC, LPPOINT, LPBYTE, int, BOOL);
BOOL RenderAndFillPath(HDC, LPPOINT, LPBYTE, int);
BOOL TextEffect(HDC, LPPOINT, LPPOINT, DWORD, DWORD, LPSTR, BOOL);


GUIDE.C

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/
/*****************************************************************************
 * *
 *FX.C                              *
 * *
 *PURPOSE:  Routines for rendering text with stange effects                *
 * *
 *  GetGuideLine       - Retrieves a guide line from the user.  A guide line *
 * is just an array of line segments.    *
 * *
 *  ShowGuide   - Displays a guide line into a DC                     *
 * *
 *****************************************************************************/

#include <windows.h>
#include "guide.h"

/**********************************************************************
 *                                                                    *
 * FUNCTION:  GetGuideLine(HWND, LPPOINT, LPDWORD)                    *
 *  *
 * PURPOSE:   Gets a guide line that is entered by the user.  This is *
 *            initiated on a WM_LBUTTONDOWN.                          *
 *                                                                    *
 *********************************************************************/
BOOL GetGuideLine(HWND hWnd, LPPOINT *lpPoint, LPDWORD lpdwNumPts)
{
    MSG msg;
    HDC hDC = GetDC(hWnd);
    BOOL bFirstTime = TRUE;
    DWORD dwPos = 0;
    RECT rect;

    SetCapture(hWnd);
    GetClientRect(hWnd, &rect);

    // Allocate enough room for a reasonable line
    *lpPoint = (LPPOINT)GlobalAlloc(GPTR, MAXGUIDESEGMENTS * sizeof(POINT));

    /* Eat mouse messages until a WM_LBUTTONUP is encountered. Meanwhile
     * continue to draw a line that follows the mouse
     */
    for (;;){
        WaitMessage();
        if (PeekMessage(&msg,NULL,WM_MOUSEFIRST,WM_MOUSELAST,PM_REMOVE)) {

            // Make sure we are still tracking in our client area
            if ((LOWORD(msg.lParam) < rect.right) && (HIWORD(msg.lParam) < rect.bottom)) {

                // Set the CP to our starting position if we are starting
                if (bFirstTime) {
                  bFirstTime = FALSE;
      MoveToEx(hDC, LOWORD(msg.lParam), HIWORD(msg.lParam), NULL);
    }

                // If we dont have more points than we want...
                if (dwPos < MAXGUIDESEGMENTS) {

            // Store the point in our array
            (*lpPoint)[dwPos].x = LOWORD(msg.lParam);
            (*lpPoint)[dwPos].y = HIWORD(msg.lParam);

            // Draw from the last point to this one
            LineTo(hDC, (*lpPoint)[dwPos].x, (*lpPoint)[dwPos].y);

            // Increment our "number of points" counter
            dwPos++;
    }
            }

            // Bail when the user lets the left button up
            if (msg.message == WM_LBUTTONUP)
               break;
        }
        else
            continue;
    }

    *lpdwNumPts = dwPos;

    ReleaseDC(hWnd, hDC);

    ReleaseCapture();

    return TRUE;
}

/**********************************************************************
 *                                                                    *
 * FUNCTION:  ShowGuide(HDC, LPPOINT, DWORD)                          *
 *  *
 * PURPOSE:   Draws a guide line into a DC  *
 *                                                                    *
 *********************************************************************/
BOOL ShowGuide(HDC hDC, LPPOINT lpPoints, DWORD dwNumPts)
{
  HPEN hOldBrush;

  // Draw the line
  Polyline(hDC, lpPoints, dwNumPts);

  // Draw green circle to indicate line starting point
  hOldBrush = SelectObject(hDC, CreateSolidBrush(RGB(0,255,0)));
  Ellipse(hDC, lpPoints[0].x-5, lpPoints[0].y-5,
               lpPoints[0].x+5, lpPoints[0].y+5);

  // Draw red circle to indicate line ending point
  DeleteObject(SelectObject(hDC, CreateSolidBrush(RGB(255,0,0))));
  Ellipse(hDC, lpPoints[dwNumPts-1].x-5, lpPoints[dwNumPts-1].y-5,
               lpPoints[dwNumPts-1].x+5, lpPoints[dwNumPts-1].y+5);
  DeleteObject(SelectObject(hDC, hOldBrush));

  return TRUE;
}


GUIDE.H

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/
/*****************************************************************************
 * *
 *GUIDE.H                           *
 * *
 *PURPOSE:  Routines for reading and displaying guide lines *
 * *
 ****************************************************************************/

#define MAXGUIDESEGMENTS 1000  // Max number of segments to read from user

BOOL GetGuideLine(HWND, LPPOINT*, LPDWORD);
BOOL ShowGuide(HDC, LPPOINT, DWORD);


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by TEXTFX.RC
//
#define IDM_EXIT                        106
#define IDM_CHOOSEFONT                  108
#define IDM_RENDER                      124
#define IDM_SHOWGUIDES                  125
#define IDM_OUTLINE                     126
#define IDM_QUICKTEST                   127
#define IDM_ABOUT                       303
#define DLG_VERFIRST                    400
#define DLG_VERLAST                     404
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         128
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


TEXTFX.C

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/
/*****************************************************************************
 * *
 *   PROGRAM: TextFX.c *
 * *
 *   PURPOSE: TextFX template for Windows applications *
 * *
 *   FUNCTIONS: *
 * *
 *   GetLastErrorBox() - Report GetLastError() values as text                *
 *   WinMain() - calls initialization function, processes message loop *
 *   InitApplication() - initializes window data and registers window *
 *   InitInstance() - saves instance handle and creates main window *
 *   WndProc() - processes messages *
 *   CenterWindow() - used to center the "About" box over application window *
 *   About() - processes messages for "About" dialog box *
 * *
 *   COMMENTS: *
 *****************************************************************************/

#include <windows.h>   // required for all Windows applications
#include "resource.h"  // specific to this program
#include <time.h>
#include "textfx.h"    // specific to this program
#include "guide.h"
#include "fx.h"

#define QUICKTESTPOINTS 2      // Number of points to use for Quick Test

HINSTANCE hInst;       // current instance

char szAppName[] = "TextFX";   // The name of this application
char szTitle[]   = "TextFX Test Application"; // The title bar text

// Random number stuff for generating random points for "Quick Test" option
#define randfloat() ((float)rand() / (float)RAND_MAX)
#define random(i) ((short)(randfloat() * (float)(i)))

/**********************************************************************
 *                                                                    *
 * FUNCTION:  GetLastErrorBox(HWND, LPSTR)                            *
 *  *
 * PURPOSE:   Gets the error status and, if an error is indicated,    *
 *            converts the error number into text and displays it     *
 *            in a MessageBox.                                        *
 *                                                                    *
 *********************************************************************/
DWORD GetLastErrorBox(HWND hWnd, LPSTR lpTitle)
{
   LPVOID lpv;
   DWORD  dwRv;

   if (GetLastError() == 0) return 0;

   dwRv = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                        FORMAT_MESSAGE_FROM_SYSTEM,
                        NULL,
                        GetLastError(),
                        MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
                        (LPVOID)&lpv,
                        0,
                        NULL);

   MessageBox(hWnd, lpv, lpTitle, MB_OK);

   if(dwRv)
      LocalFree(lpv);

   SetLastError(0);
   return dwRv;
}

/*****************************************************************************
 * *
 * FUNCTION: WinMain(HINSTANCE, HINSTANCE, LPSTR, int) *
 * *
 * PURPOSE: calls initialization function, processes message loop *
 * *
 * COMMENTS: *
 * *
 *    Windows recognizes this function by name as the initial entry point *
 *    for the program.  This function calls the application initialization *
 *    routine, if no other instance of the program is running, and always *
 *    calls the instance initialization routine.  It then executes a message *
 *    retrieval and dispatch loop that is the top-level control structure *
 *    for the remainder of execution.  The loop is terminated when a WM_QUIT *
 *    message is received, at which time this function exits the application *
 *    instance by returning the value passed by PostQuitMessage(). *
 * *
 *    If this function must abort before entering the message loop, it *
 *    returns the conventional value NULL. *
 * *
 ****************************************************************************/
int APIENTRY WinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nCmdShow)
{
    MSG msg;
    HANDLE hAccelTable;
LONG lTime;

// Just for fun, lets seed the random number generator
time(&lTime);
    srand(lTime);

    if (!hPrevInstance) {       // Other instances of app running?
            if (!InitApplication(hInstance)) { // Initialize shared things
            return (FALSE);     // Exits if unable to initialize
        }
    }

    /* Perform initializations that apply to a specific instance */

    if (!InitInstance(hInstance, nCmdShow)) {
            return (FALSE);
    }

    hAccelTable = LoadAccelerators (hInstance, "TEXTFXACCEL");

    /* Acquire and dispatch messages until a WM_QUIT message is received. */

    while (GetMessage(&msg, // message structure
       NULL,   // handle of window receiving the message
       0,      // lowest message to examine
       0))     // highest message to examine
    {
        if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);// Translates virtual key codes
            DispatchMessage(&msg); // Dispatches message to window
        }
    }

    return (msg.wParam); // Returns the value from PostQuitMessage

    UNREFERENCED_PARAMETER(lpCmdLine); // This will prevent 'unused formal parameter' warnings
}

/*****************************************************************************
 * *
 *  FUNCTION: InitApplication(HINSTANCE) *
 * *
 *  PURPOSE: Initializes window data and registers window class *
 * *
 *  COMMENTS: *
 * *
 *    This function is called at initialization time only if no other *
 *    instances of the application are running.  This function performs     *
 *    initialization tasks that can be done once for any number of running *
 *    instances. *
 * *
 *    In this case, we initialize a window class by filling out a data *
 *    structure of type WNDCLASS and calling the Windows RegisterClass() *
 *    function.  Since all instances of this application use the same window *
 *    class, we only need to do this when the first instance is initialized. *
 * *
 *****************************************************************************/
BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASS  wc;

    // Fill in window class structure with parameters that describe the
    // main window.

    wc.style         = CS_OWNDC;               // Class style(s).
    wc.lpfnWndProc   = (WNDPROC)WndProc;       // Window Procedure
    wc.cbClsExtra    = 0;                      // No per-class extra data.
    wc.cbWndExtra    = 0;                      // No per-window extra data.
    wc.hInstance     = hInstance;              // Owner of this class
    wc.hIcon         = LoadIcon (hInstance, szAppName); // Icon name from .RC
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);// Cursor
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);// Default color
    wc.lpszMenuName  = "TEXTFXMENU";           // Menu name from .RC
    wc.lpszClassName = szAppName;              // Name to register as

    // Register the window class and return success/failure code.
    return (RegisterClass(&wc));
}

/*****************************************************************************
 * *
 *   FUNCTION:  InitInstance(HINSTANCE, int) *
 * *
 *   PURPOSE:  Saves instance handle and creates main window *
 * *
 *   COMMENTS: *
 * *
 *      This function is called at initialization time for every instance of *
 *      this application.  This function performs initialization tasks that *
 *      cannot be shared by multiple instances. *
 * *
 *      In this case, we save the instance handle in a static variable and *
 *      create and display the main program window. *
 * *
 ****************************************************************************/
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND            hWnd; // Main window handle.

    // Save the instance handle in static variable, which will be used in
    // many subsequence calls from this application to Windows.

    hInst = hInstance; // Store instance handle in our global variable

    // Create a main window for this application instance.

    hWnd = CreateWindow(
            szAppName,           // See RegisterClass() call.
            szTitle,             // Text for window title bar.
            WS_OVERLAPPEDWINDOW,// Window style.
            CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, // Use default positioning
            NULL,                // Overlapped windows have no parent.
            NULL,                // Use the window class menu.
            hInstance,           // This instance owns this window.
            NULL                 // We don't use any data in our WM_CREATE
    );

    // If window could not be created, return "failure"
    if (!hWnd) {
            return (FALSE);
    }

    // Make the window visible; update its client area; and return "success"
    ShowWindow(hWnd, nCmdShow); // Show the window
    UpdateWindow(hWnd);         // Sends WM_PAINT message

    return (TRUE);              // We succeeded...

}

/*****************************************************************************
 * *
 *   FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) *
 * *
 *   PURPOSE:  Processes messages *
 * *
 *   MESSAGES: *
 * *
 *   WM_COMMAND    - application menu (About dialog box) *
 *   WM_DESTROY    - destroy window *
 * *
 *   COMMENTS: *
 * *
 *   To process the IDM_ABOUT message, call MakeProcInstance() to get the *
 *   current instance address of the About() function.  Then call Dialog *
 *   box which will create the box according to the information in your *
 *   TextFX.rc file and turn control over to the About() function.  When *
 *   it returns, free the intance address. *
 * *
 ****************************************************************************/
LRESULT CALLBACK WndProc(
                HWND hWnd,         // window handle
                UINT message,      // type of message
                WPARAM uParam,     // additional information
                LPARAM lParam)     // additional information
{
int wmId, wmEvent;
    static BOOL bOn = TRUE;  // Which guide line (top = TRUE) are we on?
    static LPPOINT lpTop = NULL;
static LPPOINT lpBot = NULL;
    static DWORD dwTop = 0;
static DWORD dwBot = 0;
static BOOL bOutlineOnly = FALSE;
static BOOL bShowGuides  = FALSE;
    static HPEN hpenBlue, hpenRed;

SetLastError(0); // Set error flag to "no errors"

switch (message) {
    case WM_CREATE:  // Select an big Arial font into our DC
    {
    HDC hDC = GetDC(hWnd);
                LOGFONT lf;
HFONT hf;

    memset(&lf, 0, sizeof(LOGFONT));
    lf.lfHeight = -72;// Big fonts scale better
        strcpy((LPSTR)&(lf.lfFaceName), "arial");

    // Create and select the font
    hf = CreateFontIndirect(&lf);
    SelectObject(hDC, hf);

hpenBlue = CreatePen(PS_SOLID, 1, RGB(0,0,255));
hpenRed  = CreatePen(PS_SOLID, 1, RGB(255,0,0));
}

GetLastErrorBox(hWnd, "Error in WM_CREATE");
break;

case WM_PAINT:
            {
   PAINTSTRUCT ps;
   HDC hDC = BeginPaint(hWnd, &ps);
               RECT rect;

   // Clear the client area
               GetClientRect(hWnd, &rect);
              PatBlt(hDC, 0, 0, rect.right, rect.bottom, WHITENESS);

      // If we have something to show...
      if (dwTop && dwBot) {

       // Show the top guide line if we have one and the user wants to
       if (dwTop && bShowGuides) {
                     SelectObject(hDC, hpenBlue);
                     ShowGuide(hDC, lpTop, dwTop);
                     SelectObject(hDC, GetStockObject(BLACK_PEN));
                   }

       // If we have a bottom guide line
       if (dwBot) {

              // ...then show it if the use wants to
               if (bShowGuides) {
                 SelectObject(hDC, hpenRed);
                         ShowGuide(hDC, lpBot, dwBot);
             SelectObject(hDC, GetStockObject(BLACK_PEN));
                       }

               // Do the mapping and show the text
               TextEffect(hDC, lpTop, lpBot, dwTop, dwBot, "This is a test", bOutlineOnly);
                   }
               }

               EndPaint(hWnd, &ps);
}
            break;

case WM_LBUTTONDOWN: // Read a guide line from the user
            {
               HDC hDC = GetDC(hWnd);
               RECT rect;

               if (bOn) {
                   GetClientRect(hWnd, &rect);

                   // Clear the screen
                   PatBlt(hDC, 0, 0, rect.right, rect.bottom, WHITENESS);

                   // Get rid of the previous guide lines
                   if (lpTop)
                     GlobalFree(lpTop);
                   if (lpBot)
                     GlobalFree(lpBot);
       dwBot = 0;

   SelectObject(hDC, hpenBlue);  // Top guide line is blue
                   GetGuideLine(hWnd, &lpTop, &dwTop);
               }
               else {
                   SelectObject(hDC, hpenRed);// Bottom guide line is red
                   GetGuideLine(hWnd, &lpBot, &dwBot);
               }

               // Black as night, black as coal, I want to see the sun blotted out from the sky...
               SelectObject(hDC, GetStockObject(BLACK_PEN));
               bOn = !bOn;

   if (bOn) {

                  // Expand the line segment into points
             FillOut(&lpTop, &dwTop);
                  FillOut(&lpBot, &dwBot);

                  InvalidateRect(hWnd, NULL, FALSE);  // Draw the text via WM_PAINT
               }
            }

GetLastErrorBox(hWnd, "Error in WM_LBUTTONDOWN");
            break;

        case WM_COMMAND:
            wmId    = LOWORD(uParam);
            wmEvent = HIWORD(uParam);

            switch (wmId) {
case IDM_QUICKTEST:  // Randomly generate guide lines
    {
  RECT rect;
  int i;

  bOn = TRUE;  // Reset to top guide line

  GetClientRect(hWnd, &rect);
  if (dwTop)
    GlobalFree(lpTop);
  if (dwBot)
    GlobalFree(lpBot);

  // Allocate top guide line
  lpTop = (LPPOINT)GlobalAlloc(GPTR, sizeof(POINT) * QUICKTESTPOINTS);
  if (!lpTop) break;

  // Allocate bottom guide line
  lpBot = (LPPOINT)GlobalAlloc(GPTR, sizeof(POINT) * QUICKTESTPOINTS);
  if (!lpBot) {
    GlobalFree(lpTop);
break;
  }

  // Generate points
  for (i=0; i<QUICKTESTPOINTS; i++) {
    lpTop[i].x = random(rect.right);
                        lpTop[i].y = random(rect.bottom);
lpBot[i].x = random(rect.right);
                        lpBot[i].y = random(rect.bottom);
  }

  // Set number of points for top and bottom lines
  dwTop = QUICKTESTPOINTS;
  dwBot = QUICKTESTPOINTS;

  // Expand the line segment into points
              FillOut(&lpTop, &dwTop);
                      FillOut(&lpBot, &dwBot);

          InvalidateRect(hWnd, NULL, FALSE);  // Draw the text via WM_PAINT
}

GetLastErrorBox(hWnd, "Error in IDM_QUICKTEST");
break;

            case IDM_CHOOSEFONT: // Let the user pick a font
{
   HDC hDC = GetDC(hWnd);
                       HFONT hf;
   LOGFONT lf;
   CHOOSEFONT cf;

       ZeroMemory(&cf, sizeof(cf));
   ZeroMemory(&lf, sizeof(lf));

   cf.lStructSize = sizeof(CHOOSEFONT);
   cf.lpLogFont = &lf;
                       cf.Flags = CF_SCREENFONTS | CF_TTONLY;

   if (ChooseFont(&cf)) {
                           lf.lfHeight = -72;    // Bigger fonts look better
       hf = CreateFontIndirect(&lf);
                          DeleteObject(SelectObject(hDC, hf));
   }
        }

        // If we have text to show then re-render it
if (dwTop && dwBot)
  InvalidateRect(hWnd, NULL, FALSE);  // Draw the text via WM_PAINT

GetLastErrorBox(hWnd, "Error in IDM_CHOOSEFONT");
        break;

case IDM_SHOWGUIDES:  // User toggled guide display
                    bShowGuides = !bShowGuides;
                    CheckMenuItem(GetMenu(hWnd), IDM_SHOWGUIDES, bShowGuides ? MF_CHECKED : MF_UNCHECKED);

// If we have text to show then re-render it
if (dwTop && dwBot)
  InvalidateRect(hWnd, NULL, FALSE);  // Draw the text via WM_PAINT

GetLastErrorBox(hWnd, "Error in IDM_SHOWGUIDES");
                    break;

case IDM_OUTLINE:  // User toggled display of text as outline
                    bOutlineOnly = !bOutlineOnly;
                    CheckMenuItem(GetMenu(hWnd), IDM_OUTLINE, bOutlineOnly ? MF_CHECKED : MF_UNCHECKED);

// If we have text to show then re-render it
if (dwTop && dwBot)
  InvalidateRect(hWnd, NULL, FALSE);  // Draw the text via WM_PAINT

GetLastErrorBox(hWnd, "Error in IDM_OUTLINE");
                    break;

                case IDM_ABOUT:
                    DialogBox(hInst,          // current instance
                             "ABOUTBOX",      // dlg resource to use
                             hWnd,            // parent handle
                             (DLGPROC)About); // About() instance address

                    break;

                case IDM_EXIT:
                    DestroyWindow (hWnd);
                    break;

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

        case WM_DESTROY:  // message: window being destroyed
                PostQuitMessage(0);
                break;

        default:          // Passes it on if unproccessed
                return (DefWindowProc(hWnd, message, uParam, lParam));
}
return (0);
}

/*****************************************************************************
 * *
 *   FUNCTION: CenterWindow (HWND, HWND) *
 * *
 *   PURPOSE:  Center one window over another *
 * *
 *   COMMENTS: *
 * *
 *   Dialog boxes take on the screen position that they were designed at, *
 *   which is not always appropriate. Centering the dialog over a particular *
 *   window usually results in a better position. *
 * *
 ****************************************************************************/

BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
        RECT    rChild, rParent;
        int     wChild, hChild, wParent, hParent;
        int     wScreen, hScreen, xNew, yNew;
        HDC     hdc;

        // Get the Height and Width of the child window
        GetWindowRect (hwndChild, &rChild);
        wChild = rChild.right - rChild.left;
        hChild = rChild.bottom - rChild.top;

        // Get the Height and Width of the parent window
        GetWindowRect (hwndParent, &rParent);
        wParent = rParent.right - rParent.left;
        hParent = rParent.bottom - rParent.top;

        // Get the display limits
        hdc = GetDC (hwndChild);
        wScreen = GetDeviceCaps (hdc, HORZRES);
        hScreen = GetDeviceCaps (hdc, VERTRES);
        ReleaseDC (hwndChild, hdc);

        // Calculate new X position, then adjust for screen
        xNew = rParent.left + ((wParent - wChild) /2);
        if (xNew < 0) {
                xNew = 0;
        } else if ((xNew+wChild) > wScreen) {
                xNew = wScreen - wChild;
        }

        // Calculate new Y position, then adjust for screen
        yNew = rParent.top  + ((hParent - hChild) /2);
        if (yNew < 0) {
                yNew = 0;
        } else if ((yNew+hChild) > hScreen) {
                yNew = hScreen - hChild;
        }

        // Set it, and return
        return SetWindowPos (hwndChild, NULL,
                xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

/*****************************************************************************
 * *
 *   FUNCTION: About(HWND, UINT, WPARAM, LPARAM) *
 * *
 *   PURPOSE:  Processes messages for "About" dialog box *
 * *
 *   MESSAGES: *
 * *
 *   WM_INITDIALOG - initialize dialog box *
 *   WM_COMMAND    - Input received *
 * *
 *   COMMENTS: *
 * *
 *   Display version information from the version section of the *
 *   application resource. *
 * *
 *   Wait for user to click on "Ok" button, then close the dialog box. *
 * *
 ****************************************************************************/

LRESULT CALLBACK About(
                HWND hDlg,           // window handle of the dialog box
                UINT message,        // type of message
                WPARAM uParam,       // message-specific information
                LPARAM lParam)
{
    switch (message) {
        case WM_INITDIALOG:  // message: initialize dialog box
            // Center the dialog over the application window
            CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
            return (TRUE);

        case WM_COMMAND:                      // message: received a command
            if (LOWORD(uParam) == IDOK || LOWORD(uParam) == IDCANCEL) {
                EndDialog(hDlg, TRUE);        // Exit the dialog
                return (TRUE);
            }
            break;
    }
    return (FALSE); // Didn't process the message

    UNREFERENCED_PARAMETER(lParam); // This will prevent 'unused formal parameter' warnings
}


TEXTFX.H

/*****************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\*****************************************************************************/

BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About  (HWND, UINT, WPARAM, LPARAM);


README.TXT

Window Screen Capture

The WINCAP32 sample captures portions of the screen, specific windows, or
the entire screen and saves it to a file or prints it. The sample uses DIB
functions to do most of the capturing, printing, and saving.


COPY.C

//**********************************************************************
//
//  copy.c
//
//  Source file for Device-Independent Bitmap (DIB) API.  Provides
//  the following functions:
//
//  CopyWindowToDIB()   - Copies a window to a DIB
//  CopyScreenToDIB()   - Copies entire screen to a DIB
//  CopyWindowToBitmap()- Copies a window to a standard Bitmap
//  CopyScreenToBitmap()- Copies entire screen to a standard Bitmap
//  PaintDIB()          - Displays DIB in the specified DC
//  PaintBitmap()       - Displays bitmap in the specified DC
//
//  The following functions are called from DIBUTIL.C:
//
//  DIBToBitmap()       - Creates a bitmap from a DIB
//  BitmapToDIB()       - Creates a DIB from a bitmap
//  DIBWidth()          - Gets the width of the DIB
//  DIBHeight()         - Gets the height of the DIB
//  CreateDIBPalette()  - Gets the DIB's palette
//  GetSystemPalette()  - Gets the current palette
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#define     STRICT      // enable strict type checking

#include <WINDOWS.H>
#include "DIBUTIL.H"
#include "DIBAPI.H"

/*************************************************************************
 *
 * CopyWindowToDIB()
 *
 * Parameters:
 *
 * HWND hWnd        - specifies the window
 *
 * WORD fPrintArea  - specifies the window area to copy into the device-
 *                    independent bitmap
 *
 * Return Value:
 *
 * HDIB             - identifies the device-independent bitmap
 *
 * Description:
 *
 * This function copies the specified part(s) of the window to a device-
 * independent bitmap.
 *
 ************************************************************************/

HDIB CopyWindowToDIB(HWND hWnd, WORD fPrintArea)
{
   HDIB     hDIB = NULL;  // handle to DIB

   // check for a valid window handle

    if (!hWnd)
        return NULL;

    switch (fPrintArea)
    {
        case PW_WINDOW: // copy entire window
        {
            RECT    rectWnd;

            // get the window rectangle

            GetWindowRect(hWnd, &rectWnd);

            // get the DIB of the window by calling
            // CopyScreenToDIB and passing it the window rect

            hDIB = CopyScreenToDIB(&rectWnd);
            break;
        }

        case PW_CLIENT: // copy client area
        {
            RECT    rectClient;
            POINT   pt1, pt2;

            // get the client area dimensions

            GetClientRect(hWnd, &rectClient);

            // convert client coords to screen coords

            pt1.x = rectClient.left;
            pt1.y = rectClient.top;
            pt2.x = rectClient.right;
            pt2.y = rectClient.bottom;
            ClientToScreen(hWnd, &pt1);
            ClientToScreen(hWnd, &pt2);
            rectClient.left = pt1.x;
            rectClient.top = pt1.y;
            rectClient.right = pt2.x;
            rectClient.bottom = pt2.y;

            // get the DIB of the client area by calling
            // CopyScreenToDIB and passing it the client rect

            hDIB = CopyScreenToDIB(&rectClient);
            break;
        }

        default:    // invalid print area
            return NULL;
    }

   // return the handle to the DIB

   return hDIB;
}

/*************************************************************************
 *
 * CopyScreenToDIB()
 *
 * Parameter:
 *
 * LPRECT lpRect    - specifies the window
 *
 * Return Value:
 *
 * HDIB             - identifies the device-independent bitmap
 *
 * Description:
 *
 * This function copies the specified part of the screen to a device-
 * independent bitmap.
 *
 ************************************************************************/

HDIB CopyScreenToDIB(LPRECT lpRect)
{
    HBITMAP     hBitmap;        // handle to device-dependent bitmap
    HPALETTE    hPalette;       // handle to palette
    HDIB        hDIB = NULL;    // handle to DIB

    // get the device-dependent bitmap in lpRect by calling
    //  CopyScreenToBitmap and passing it the rectangle to grab

    hBitmap = CopyScreenToBitmap(lpRect);

    // check for a valid bitmap handle

    if (!hBitmap)
      return NULL;

    // get the current palette

    hPalette = GetSystemPalette();

    // convert the bitmap to a DIB

    hDIB = BitmapToDIB(hBitmap, hPalette);

    // clean up

    DeleteObject(hPalette);
    DeleteObject(hBitmap);

    // return handle to the packed-DIB
    return hDIB;
}

/*************************************************************************
 *
 * CopyWindowToBitmap()
 *
 * Parameters:
 *
 * HWND hWnd        - specifies the window
 *
 * WORD fPrintArea  - specifies the window area to copy into the device-
 *                    dependent bitmap
 *
 * Return Value:
 *
 * HDIB         - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function copies the specified part(s) of the window to a device-
 * dependent bitmap.
 *
 *
 ************************************************************************/

HBITMAP CopyWindowToBitmap(HWND hWnd, WORD fPrintArea)
{
    HBITMAP     hBitmap = NULL;  // handle to device-dependent bitmap

    // check for a valid window handle

    if (!hWnd)
        return NULL;

    switch (fPrintArea)
    {
        case PW_WINDOW: // copy entire window
        {
            RECT    rectWnd;

            // get the window rectangle

            GetWindowRect(hWnd, &rectWnd);

            // get the bitmap of that window by calling
            // CopyScreenToBitmap and passing it the window rect

            hBitmap = CopyScreenToBitmap(&rectWnd);
            break;
        }

        case PW_CLIENT: // copy client area
        {
            RECT rectClient;
            POINT pt1, pt2;

            // get client dimensions

            GetClientRect(hWnd, &rectClient);

            // convert client coords to screen coords

            pt1.x = rectClient.left;
            pt1.y = rectClient.top;
            pt2.x = rectClient.right;
            pt2.y = rectClient.bottom;
            ClientToScreen(hWnd, &pt1);
            ClientToScreen(hWnd, &pt2);
            rectClient.left = pt1.x;
            rectClient.top = pt1.y;
            rectClient.right = pt2.x;
            rectClient.bottom = pt2.y;

            // get the bitmap of the client area by calling
            // CopyScreenToBitmap and passing it the client rect

            hBitmap = CopyScreenToBitmap(&rectClient);
            break;
        }

        default:    // invalid print area
            return NULL;
    }

    // return handle to the bitmap

   return hBitmap;
}

/*************************************************************************
 *
 * CopyScreenToBitmap()
 *
 * Parameter:
 *
 * LPRECT lpRect    - specifies the window
 *
 * Return Value:
 *
 * HDIB             - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function copies the specified part of the screen to a device-
 * dependent bitmap.
 *
 *
 ************************************************************************/

HBITMAP CopyScreenToBitmap(LPRECT lpRect)
{
    HDC         hScrDC, hMemDC;         // screen DC and memory DC
    HBITMAP     hBitmap, hOldBitmap;    // handles to deice-dependent bitmaps
    int         nX, nY, nX2, nY2;       // coordinates of rectangle to grab
    int         nWidth, nHeight;        // DIB width and height
    int         xScrn, yScrn;           // screen resolution

    // check for an empty rectangle

    if (IsRectEmpty(lpRect))
      return NULL;

    // create a DC for the screen and create
    // a memory DC compatible to screen DC

    hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);
    hMemDC = CreateCompatibleDC(hScrDC);

    // get points of rectangle to grab

    nX = lpRect->left;
    nY = lpRect->top;
    nX2 = lpRect->right;
    nY2 = lpRect->bottom;

    // get screen resolution

    xScrn = GetDeviceCaps(hScrDC, HORZRES);
    yScrn = GetDeviceCaps(hScrDC, VERTRES);

    //make sure bitmap rectangle is visible

    if (nX < 0)
        nX = 0;
    if (nY < 0)
        nY = 0;
    if (nX2 > xScrn)
        nX2 = xScrn;
    if (nY2 > yScrn)
        nY2 = yScrn;

    nWidth = nX2 - nX;
    nHeight = nY2 - nY;

    // create a bitmap compatible with the screen DC
    hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);

    // select new bitmap into memory DC
    hOldBitmap = SelectObject(hMemDC, hBitmap);

    // bitblt screen DC to memory DC
    BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY);

    // select old bitmap back into memory DC and get handle to
    // bitmap of the screen

    hBitmap = SelectObject(hMemDC, hOldBitmap);

    // clean up

    DeleteDC(hScrDC);
    DeleteDC(hMemDC);

    // return handle to the bitmap

    return hBitmap;
}

/*************************************************************************
 *
 * PaintDIB()
 *
 * Parameters:
 *
 * HDC hDC          - DC to do output to
 *
 * LPRECT lpDCRect  - rectangle on DC to do output to
 *
 * HDIB hDIB        - handle to global memory with a DIB spec
 *                    in it followed by the DIB bits
 *
 * LPRECT lpDIBRect - rectangle of DIB to output into lpDCRect
 *
 * Return Value:
 *
 * BOOL             - TRUE if DIB was drawn, FALSE otherwise
 *
 * Description:
 *   Painting routine for a DIB.  Calls StretchDIBits() or
 *   SetDIBitsToDevice() to paint the DIB.  The DIB is
 *   output to the specified DC, at the coordinates given
 *   in lpDCRect.  The area of the DIB to be output is
 *   given by lpDIBRect.
 *
 * NOTE: This function always selects the palette as background. Before
 * calling this function, be sure your palette is selected to desired
 * priority (foreground or background).
 *
 *
 ************************************************************************/

BOOL PaintDIB(HDC hDC, LPRECT lpDCRect, HDIB hDIB, LPRECT lpDIBRect,
        HPALETTE hPal)
{
    LPSTR        lpDIBHdr;           // Pointer to BITMAPINFOHEADER
    LPSTR        lpDIBBits;          // Pointer to DIB bits
    BOOL         bSuccess=FALSE;     // Success/fail flag
    HPALETTE     hOldPal=NULL;       // Previous palette

    // Check for valid DIB handle

    if (!hDIB)
        return FALSE;

    // Lock down the DIB, and get a pointer to the beginning of the bit
    // buffer

    lpDIBHdr  = GlobalLock(hDIB);
    lpDIBBits = FindDIBBits(lpDIBHdr);

    // Select and realize our palette as background

    if (hPal)
    {
        hOldPal = SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
    }

    // Make sure to use the stretching mode best for color pictures

    SetStretchBltMode(hDC, COLORONCOLOR);

    // Determine whether to call StretchDIBits() or SetDIBitsToDevice()

    if ((RECTWIDTH(lpDCRect)  == RECTWIDTH(lpDIBRect)) &&
            (RECTHEIGHT(lpDCRect) == RECTHEIGHT(lpDIBRect)))
    {
        bSuccess = SetDIBitsToDevice(hDC, lpDCRect->left, lpDCRect->top,
                RECTWIDTH(lpDCRect), RECTHEIGHT(lpDCRect), lpDIBRect->left,
                (int)DIBHeight(lpDIBHdr) - lpDIBRect->top -
                RECTHEIGHT(lpDIBRect), 0, DIBHeight(lpDIBHdr), lpDIBBits,
                (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS);
    }
    else
        bSuccess = StretchDIBits(hDC, lpDCRect->left, lpDCRect->top,
                RECTWIDTH(lpDCRect), RECTHEIGHT(lpDCRect), lpDIBRect->left,
                lpDIBRect->top, RECTWIDTH(lpDIBRect), RECTHEIGHT(lpDIBRect),
                lpDIBBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);

    // Unlock the memory block

    GlobalUnlock(hDIB);

    // Reselect old palette

    if (hOldPal)
     SelectPalette(hDC, hOldPal, FALSE);

    // Return with success/fail flag
    return bSuccess;
}

/*************************************************************************
 *
 * PaintBitmap()
 *
 * Parameters:
 *
 * HDC hDC          - DC to do output to
 *
 * LPRECT lpDCRect  - rectangle on DC to do output to
 *
 * HBITMAP hDDB     - handle to device-dependent bitmap (DDB)
 *
 * LPRECT lpDDBRect - rectangle of DDB to output into lpDCRect
 *
 * HPALETTE hPalette - handle to the palette to use with hDDB
 *
 * Return Value:
 *
 * BOOL             - TRUE if bitmap was drawn, FLASE otherwise
 *
 * Description:
 *
 * Painting routine for a DDB.  Calls BitBlt() or
 * StretchBlt() to paint the DDB.  The DDB is
 * output to the specified DC, at the coordinates given
 * in lpDCRect.  The area of the DDB to be output is
 * given by lpDDBRect.  The specified palette is used.
 *
 * NOTE: This function always selects the palette as background. Before
 * calling this function, be sure your palette is selected to desired
 * priority (foreground or background).
 *
 ************************************************************************/

BOOL PaintBitmap(HDC hDC, LPRECT lpDCRect, HBITMAP hDDB,  LPRECT lpDDBRect,
        HPALETTE hPal)
{
    HDC         hMemDC;            // Handle to memory DC
    HBITMAP     hOldBitmap;        // Handle to previous bitmap
    HPALETTE    hOldPal1 = NULL;   // Handle to previous palette
    HPALETTE    hOldPal2 = NULL;   // Handle to previous palette
    BOOL        bSuccess = FALSE;  // Success/fail flag

    // Create a memory DC

    hMemDC = CreateCompatibleDC (hDC);

    // If this failed, return FALSE

    if (!hMemDC)
        return FALSE;

    // If we have a palette, select and realize it

    if (hPal)
    {
        hOldPal1 = SelectPalette(hMemDC, hPal, TRUE);
        hOldPal2 = SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
    }

    // Select bitmap into the memory DC

    hOldBitmap = SelectObject (hMemDC, hDDB);

    // Make sure to use the stretching mode best for color pictures

    SetStretchBltMode (hDC, COLORONCOLOR);

    // Determine whether to call StretchBlt() or BitBlt()
    if ((RECTWIDTH(lpDCRect)  == RECTWIDTH(lpDDBRect)) &&
            (RECTHEIGHT(lpDCRect) == RECTHEIGHT(lpDDBRect)))
        bSuccess = BitBlt(hDC, lpDCRect->left, lpDCRect->top,
                lpDCRect->right - lpDCRect->left,
                lpDCRect->bottom - lpDCRect->top, hMemDC, lpDDBRect->left,
                lpDDBRect->top, SRCCOPY);
    else
        bSuccess = StretchBlt(hDC, lpDCRect->left,  lpDCRect->top,
                lpDCRect->right - lpDCRect->left,
                lpDCRect->bottom - lpDCRect->top, hMemDC, lpDDBRect->left,
                lpDDBRect->top,  lpDDBRect->right - lpDDBRect->left,
                lpDDBRect->bottom - lpDDBRect->top, SRCCOPY);

    // Clean up

    SelectObject(hMemDC, hOldBitmap);

    if (hOldPal1)
        SelectPalette (hMemDC, hOldPal1, FALSE);

    if (hOldPal2)
        SelectPalette (hDC, hOldPal2, FALSE);

    DeleteDC (hMemDC);

    // Return with success/fail flag

    return bSuccess;
}


DIALOGS.C

//***********************************************************************
//
// dialogs.c
//
// Contains all the dialog procedures for WinCap Windows Screen Capture
// Program.
//
// Dialog Functions:
//
// AboutDlgProc()         // About Box
// PrintDlgProc()         // Print Options Dialog
// SavingDlgProc()        // Dialog which displays "Saving to file..."
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//***********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include "WINCAP.h"
#include "DIALOGS.h"
#include "commdlg.h"
#include "resource.h"

// Global variables which are set in main program

extern char         szAppName[20];  /// Name of app
extern HINSTANCE    ghInst;         /// Handle to instance
extern HWND         ghWndMain;      /// Handle to main window
extern BOOL         gbNowCapturing;
extern BOOLgbCaptRect;

//**********************************************************************
//
//"About" Dialog Box Window Procedure
//
// Notable features:  This dialog box draws the application's icon
// in the dialog box itself. The icon is actually stretched larger to
// fit in the specified area, which must be done manually.
// See WM_PAINT case.
//
//
//***********************************************************************

BOOL APIENTRY AboutDlgProc(HWND hWndDlg, UINT Message, WPARAM wParam, LONG
        lParam)
{
    static HBITMAP  ghAboutBmp = NULL;

    switch (Message)
    {
        // Set focus on the OK button.  Since we set focus, return FALSE

        case WM_INITDIALOG:
            SetFocus(GetDlgItem(hWndDlg, IDOK));
            ghAboutBmp = LoadBitmap(ghInst, "ABOUTBMP");
            return FALSE;

        //Closing the Dialog behaves the same as Cancel

        case WM_CLOSE:
            PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
            break;

        case WM_COMMAND:
            switch (wParam)
            {
                case IDOK:

                case IDCANCEL:
                    EndDialog(hWndDlg, FALSE);
                    if (ghAboutBmp)
                        DeleteObject(ghAboutBmp);

                    break;
            }
            break;    //End of WM_COMMAND

        // Paint our bitmap in the about box in the WM_PAINT case

        case WM_PAINT:
        {
            HBITMAP     hbm;
            HDC         hdcMem;
            PAINTSTRUCT ps;
            RECT        r1;        // Coordinates of TEXT1 control
            POINT       p1;        // Used to convert screen to client coords
            BITMAP      bm;        // For getting info about "About" bitmap

            BeginPaint(hWndDlg, &ps);

            // Place screen coords of TEXT1 in r1

            GetWindowRect(GetDlgItem(hWndDlg, IDC_TEXT1), &r1);
            p1.x = r1.left;
            p1.y = r1.top;

            ScreenToClient(hWndDlg, &p1);

            // p1 now describes the location of upper-left corner
            // for the top text in the dialog.  Let's use this
            // to make sure we don't draw our bitmap over the text

            r1.left = 12;
            r1.top = p1.y;
            r1.right = p1.x - 25;
            r1.bottom = r1.top + (r1.right - r1.left); // Make it square

            if (hdcMem = CreateCompatibleDC(ps.hdc))
            {
                GetObject(ghAboutBmp, sizeof(BITMAP), (LPVOID)&bm);
                hbm = SelectObject(hdcMem, ghAboutBmp);
                StretchBlt(ps.hdc, r1.left, r1.top, r1.right, r1.bottom, hdcMem,
                        0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
                SelectObject(hdcMem, hbm);
                DeleteDC(hdcMem);
            }

            EndPaint(hWndDlg, &ps);
            break;
        }

        default:
            return FALSE;
    }

    return TRUE;
} // End of DIALOGSMsgProc

//***********************************************************************
//
// "Print" Dialog Box Window Procedure
//
// This procedure takes care of the processing for the "Print" dialog
// box, which contains the printing options.
//
// This Dialog Box Procedure is called using DialogBoxParam.  This
// allows us to pass a parameter into the dialog box procedure -- the
// parameter that gets passed in here is a LPSTR to a structure holding
// all the options that the user specified in this dialog box.  This
// allows passing the options back to the main program WITHOUT using
// global variables.
//
//***********************************************************************

BOOL APIENTRY PrintDlgProc(HWND hWndDlg, UINT Message, WPARAM wParam,
        LPARAM lParam)
{
    static LPOPTIONSTRUCT   lpOS;

    switch (Message)
    {
        case WM_INITDIALOG:
        {

            // Because DialogBoxParam() was used to invoke this dialog box,
            // lParam contains a pointer to the OPTIONSTRUCT.
            // Place user input in this structure before returning.

            lpOS = (LPOPTIONSTRUCT)lParam;

            // Check the default button -- "BEST FIT"

            CheckRadioButton(hWndDlg, IDC_BESTFIT, IDC_STRETCHTOPAGE, IDC_BESTFIT);

            // Gray out the stuff under "SCALE"

            EnableWindow(GetDlgItem(hWndDlg, IDC_XAXIS), FALSE);
            EnableWindow(GetDlgItem(hWndDlg, IDC_YAXIS), FALSE);
            EnableWindow(GetDlgItem(hWndDlg, IDC_XTEXT), FALSE);
            EnableWindow(GetDlgItem(hWndDlg, IDC_YTEXT), FALSE);
            break;
        }

        // Closing the Dialog should behave the same as Cancel

        case WM_CLOSE:
            PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
            break;

        case WM_COMMAND:
            switch (wParam)
            {
                case IDC_BESTFIT:

                case IDC_STRETCHTOPAGE:

                case IDC_SCALE:

                    // Check the correct button

                    CheckRadioButton(hWndDlg, IDC_BESTFIT, IDC_SCALE, wParam);

                    // And enable or disable the options under "Scale",
                    // depending on whether or not the IDC_SCALE button
                    // is checked

                    EnableWindow(GetDlgItem(hWndDlg, IDC_XAXIS),
                            (BOOL)(wParam == IDC_SCALE));
                    EnableWindow(GetDlgItem(hWndDlg, IDC_YAXIS),
                            (BOOL)(wParam == IDC_SCALE));
                    EnableWindow(GetDlgItem(hWndDlg, IDC_XTEXT),
                            (BOOL)(wParam == IDC_SCALE));
                    EnableWindow(GetDlgItem(hWndDlg, IDC_YTEXT),
                            (BOOL)(wParam == IDC_SCALE));
                    break;

                case IDOK:
                {
                    char    szTmp[100];

                    // Save the user's selection into the OPTIONSTRUCT

                    if (!lpOS)
                    {
                        EndDialog(hWndDlg, FALSE);
                        break;
                    }

                    if (IsDlgButtonChecked(hWndDlg, IDC_BESTFIT))
                        lpOS->iOption = IDC_BESTFIT;

                    if (IsDlgButtonChecked(hWndDlg, IDC_STRETCHTOPAGE))
                        lpOS->iOption = IDC_STRETCHTOPAGE;

                    if (IsDlgButtonChecked(hWndDlg, IDC_SCALE))
                        lpOS->iOption = IDC_SCALE;

                    if (GetDlgItemText(hWndDlg, IDC_XAXIS, (LPSTR)szTmp, 100))
                        lpOS->iXScale = atoi(szTmp);

                    if (GetDlgItemText(hWndDlg, IDC_YAXIS, (LPSTR)szTmp, 100))
                        lpOS->iYScale = atoi(szTmp);

                    EndDialog(hWndDlg, TRUE);
                    break;
                }

                case IDCANCEL:
                    EndDialog(hWndDlg, FALSE);
                    break;
            } // End of WM_COMMAND

            break;

        default:
            return FALSE;
    }

    return TRUE;
}

//***********************************************************************
//
// "Saving file to..." Dialog Box Window Procedure
//
// This is a modeless dialog box which is called when we save the bitmap
// to a file (so the user dosen't think his machine has hung).
//
//***********************************************************************

BOOL APIENTRY SavingDlgProc(HWND hDlg, UINT message, WPARAM wParam,
        LPARAM lParam)
{
    switch (message)
    {
        case WM_SETFOCUS:
            MessageBeep(0);
            break;

        case WM_INITDIALOG:

            // Set the text of the filename in the dialog box.  This dialog
            // should be called with DialogBoxParam, and the parameter should
            // be a pointer to the filename.  It shows up as the lParam here.

            SetDlgItemText(hDlg, IDC_FILETEXT, (LPSTR)lParam);
            return TRUE;
            break;

        case WM_DESTROY:
            return TRUE;
            break;

        default:
            return FALSE;
    }
}

//***********************************************************************
//
// Selection Help Dialog Box Window Procedure
//
// This is a modeless dialog box which displays how to select a window to
// be captured
//
//***********************************************************************
BOOL APIENTRY SelectDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM
        lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
        {
            HGLOBAL     hMem;
            char        *szText;

            hMem = GlobalAlloc(GHND, 200);
            szText = (char *)GlobalLock(hMem);
            if(szText == NULL)
                return TRUE;

            LoadString(ghInst, IDS_SELECT2, szText, 200);
            SetDlgItemText(hDlg, IDC_STATIC_SELECT2, szText);
            LoadString(ghInst, IDS_SELECT3, szText, 200);
            SetDlgItemText(hDlg, IDC_STATIC_SELECT3, szText);

            GlobalUnlock(hMem);
            GlobalFree(hMem);

            return TRUE;
        }

        case WM_COMMAND:
            if ((wParam) != IDCANCEL)
                return FALSE;
            else
            {
                ShowWindow(hDlg, SW_HIDE);
                gbNowCapturing = FALSE;
                return TRUE;
            }

        case WM_SYSCOMMAND:
            if(wParam == SC_CLOSE)
            {
                ShowWindow(hDlg, SW_HIDE);
                gbNowCapturing = FALSE;
                return TRUE;
            }
            else
                return FALSE;

        case WM_DESTROY:
            return TRUE;

        default:
            return FALSE;
    }
}

//***********************************************************************
//
// Selection Help Dialog Box Window Procedure
//
// This is a modeless dialog box which displays how to select a window to
// be captured
//
//***********************************************************************
BOOL APIENTRY RectangleDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM
        lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
        {
            HGLOBAL     hMem;
            char        *szText;

            hMem = GlobalAlloc(GHND, 200);
            szText = (char *)GlobalLock(hMem);
            if(szText == NULL)
                return TRUE;

            LoadString(ghInst, IDS_RECT2, szText, 200);
            SetDlgItemText(hDlg, IDC_STATIC_RECT2, szText);
            LoadString(ghInst, IDS_RECT3, szText, 200);
            SetDlgItemText(hDlg, IDC_STATIC_RECT3, szText);

            GlobalUnlock(hMem);
            GlobalFree(hMem);

            return TRUE;
        }

        case WM_COMMAND:
            if ((wParam) != IDCANCEL)
                return FALSE;
            else
            {
                ShowWindow(hDlg, SW_HIDE);
                gbNowCapturing = FALSE;
gbCaptRect = FALSE;
                return TRUE;
            }

        case WM_SYSCOMMAND:
            if(wParam == SC_CLOSE)
            {
                ShowWindow(hDlg, SW_HIDE);
                gbNowCapturing = FALSE;
                return TRUE;
            }
            else
                return FALSE;

        case WM_DESTROY:
            return TRUE;

        default:
            return FALSE;
    }
}


DIALOGS.H

#define IDC_SELECTTEXT              101
#define IDC_ENTIRESCREEN            311
#define IDC_PARTIALSCREEN           312
#define IDC_CLIENTAREAONLY          302
#define IDC_ENTIREWINDOW            301
#define IDC_CAPTIONBARONLY          303
#define IDC_MENUBARONLY             304
#define IDC_SINGLEWINDOW            310
#define IDC_STATUSBAR               103
#define IDC_OPTIONSBUTTON           110
#define IDC_ICONAREA                102
#define IDC_VERSION                 111
#define IDC_COPYRIGHT               114
#define IDC_PRINTER                 115
#define IDC_FILE                    116
#define IDC_BESTFIT                 119
#define IDC_STRETCHTOPAGE           120
#define IDC_SCALE                   121
#define IDC_XAXIS                   122
#define IDC_YAXIS                   201
#define IDC_DIRECTORIES             204
#define IDC_EDITFILE                502
#define IDC_GROUPBOX1               117
#define IDC_BOX2                    118
#define IDC_BOX1                    123
#define IDC_BOX3                    124
#define IDC_FILETEXT                104
#define IDC_SELECTPRINTER           105
#define IDC_BOX4                    109
#define IDC_DIVIDINGLINE            106
#define IDC_PRINTTEXT1              401
#define IDC_PRINTTEXT2              402
#define IDC_PERCENTAGE              403
#define IDC_TITLE                   125
#define IDC_TITLE2                  128
#define IDC_TITLE1                  129
#define IDC_ABOUT                   107
#define IDC_YTEXT                   202
#define IDC_XTEXT                   200
#define IDC_FOLD                    108
#define DLGOPEN_EDIT                610
#define DLGOPEN_FILE_LISTBOX        620
#define DLGOPEN_DIR_LISTBOX         630
#define DLGOPEN_PATH                640
#define DLGOPEN_TEXT                650
#define DLGOPEN_FOLDOUT             660
#define DLGOPEN_BIG                 670
#define DLGOPEN_SMALL               680

#define IDC_BITMAP1                 126
#define IDC_BITMAP2                 131
#define IDC_TEXT1                   525
#define OPENING                     100


DIBAPI.H

/*    PortTool v2.2     dibapi.h          */

/*
 *  dibapi.h
 *
 *  ** Win32 Version **
 *
 *  Copyright 1991-1998 Microsoft Corporation. All rights reserved
 *
 *  Header file for Device-Independent Bitmap (DIB) API.  Provides
 *  function prototypes and constants for the following functions:
 *
 *  BitmapToDIB()        - Creates a DIB from a bitmap
 *  ChangeBitmapFormat() - Changes a bitmap to a specified DIB format
 *  ChangeDIBFormat()    - Changes a DIB's BPP and/or compression format
 *  CopyScreenToBitmap() - Copies entire screen to a standard Bitmap
 *  CopyScreenToDIB()    - Copies entire screen to a DIB
 *  CopyWindowToBitmap() - Copies a window to a standard Bitmap
 *  CopyWindowToDIB()    - Copies a window to a DIB
 *  CreateDIBPalette()   - Creates a palette from a DIB
 *  CreateDIB()          - Creates a new DIB
 *  DestroyDIB()         - Deletes DIB when finished using it
 *  DIBError()           - Displays message box with error message
 *  DIBHeight()          - Gets the DIB height
 *  DIBNumColors()       - Calculates number of colors in the DIB's color table
 *  DIBToBitmap()        - Creates a bitmap from a DIB
 *  DIBWidth()           - Gets the DIB width
 *  FindDIBBits()        - Sets pointer to the DIB bits
 *  GetSystemPalette()   - Gets the current palette
 *  LoadDIB()            - Loads a DIB from a file
 *  PaintBitmap()        - Displays standard bitmap in the specified DC
 *  PaintDIB()           - Displays DIB in the specified DC
 *  PalEntriesOnDevice() - Gets the number of palette entries
 *  PaletteSize()        - Calculates the buffer size required by a palette
 *  PrintDIB()           - Prints the specified DIB
 *  PrintScreen()        - Prints the entire screen
 *  PrintWindow()        - Prints all or part of a window
 *  SaveDIB()            - Saves the specified dib in a file
 *
 * See the file DIBAPI.TXT for more information about these functions.
 *
 */

/* Handle to a DIB */
#define HDIB HANDLE

/* Print Area selection */
#define PW_WINDOW        1
#define PW_CLIENT        2

/* Print Options selection */
#define PW_BESTFIT       1
#define PW_STRETCHTOPAGE 2
#define PW_SCALE         3

/* DIB Macros*/

// WIDTHBYTES performs DWORD-aligning of DIB scanlines.  The "bits"
// parameter is the bit count for the scanline (biWidth * biBitCount),
// and this macro returns the number of DWORD-aligned bytes needed
// to hold those bits.

#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4)

/* Error constants */
enum {
      ERR_MIN = 0,                     // All error #s >= this value
      ERR_NOT_DIB = 0,                 // Tried to load a file, NOT a DIB!
      ERR_MEMORY,                      // Not enough memory!
      ERR_READ,                        // Error reading file!
      ERR_LOCK,                        // Error on a GlobalLock()!
      ERR_OPEN,                        // Error opening a file!
      ERR_CREATEPAL,                   // Error creating palette.
      ERR_GETDC,                       // Couldn't get a DC.
      ERR_CREATEDDB,                   // Error create a DDB.
      ERR_STRETCHBLT,                  // StretchBlt() returned failure.
      ERR_STRETCHDIBITS,               // StretchDIBits() returned failure.
      ERR_SETDIBITSTODEVICE,           // SetDIBitsToDevice() failed.
      ERR_STARTDOC,                    // Error calling StartDoc().
      ERR_NOGDIMODULE,                 // Couldn't find GDI module in memory.
      ERR_SETABORTPROC,                // Error calling SetAbortProc().
      ERR_STARTPAGE,                   // Error calling StartPage().
      ERR_NEWFRAME,                    // Error calling NEWFRAME escape.
      ERR_ENDPAGE,                     // Error calling EndPage().
      ERR_ENDDOC,                      // Error calling EndDoc().
      ERR_SETDIBITS,                   // Error calling SetDIBits().
      ERR_FILENOTFOUND,                // Error opening file in GetDib()
      ERR_INVALIDHANDLE,               // Invalid Handle
      ERR_DIBFUNCTION,                 // Error on call to DIB function
      ERR_MAX                          // All error #s < this value
     };

/* Function prototypes */

HDIB BitmapToDIB (HBITMAP hBitmap, HPALETTE hPal);
HDIB ChangeBitmapFormat (HBITMAPhBitmap,
                                   WORD     wBitCount,
                                   DWORD    dwCompression,
                                   HPALETTE hPal);
HDIB ChangeDIBFormat (HDIB hDIB, WORD wBitCount,
                                DWORD dwCompression);
HBITMAP CopyScreenToBitmap (LPRECT);
HDIB CopyScreenToDIB (LPRECT);
HBITMAP CopyWindowToBitmap (HWND, WORD);
HDIB CopyWindowToDIB (HWND, WORD);
HPALETTE CreateDIBPalette (HDIB);
HDIB CreateDIB(DWORD, DWORD, WORD);
WORD DestroyDIB (HDIB);
void DIBError (int ErrNo);
DWORD DIBHeight (LPSTR lpDIB);
WORD DIBNumColors (LPSTR lpDIB);
HBITMAP DIBToBitmap (HDIB hDIB, HPALETTE hPal);
DWORD DIBWidth (LPSTR lpDIB);
LPSTR FindDIBBits (LPSTR lpDIB);
HPALETTE GetSystemPalette (void);
HDIB LoadDIB (LPSTR);
BOOL PaintBitmap (HDC, LPRECT, HBITMAP, LPRECT, HPALETTE);
BOOL PaintDIB (HDC, LPRECT, HDIB, LPRECT, HPALETTE);
int PalEntriesOnDevice (HDC hDC);
WORD PaletteSize (LPSTR lpDIB);
WORD PrintDIB (HDIB, WORD, WORD, WORD, LPSTR);
WORD PrintScreen (LPRECT, WORD, WORD, WORD, LPSTR);
WORD PrintWindow (HWND, WORD, WORD, WORD, WORD, LPSTR);
WORD SaveDIB (HDIB, LPSTR);


DIBDLL.H

/*    PortTool v2.2     dibdll.h          */

/*
 *  dibdll.h
 *
 *  Copyright 1991-1998 Microsoft Corporation. All rights reserved
 *
 *  Header file for dllinit.c, print.c, and dibapi.rc.  It includes
 *  the declaration for ghDLLInst, a global variable which stores the
 *  DLL's instance handle.  It also defines constants for the DLL's
 *  printing dialog.
 */

/* Defines for the DLL's printing dialog. */

#define IDC_BOX1                    123
#define IDC_PRINTTEXT1              401
#define IDC_PRINTTEXT2              402
#define IDC_PERCENTAGE              403

#define IDS_PRINTMSG                601
#define IDS_BANDNMBR                602
#define IDS_SENDINGBAND             603
#define IDS_ABORTSTRING             604


DIBUTIL.C

//**********************************************************************
//
//  dibutil.c
//
//  Source file for Device-Independent Bitmap (DIB) API.  Provides
//  the following functions:
//
//  CreateDIB()         - Creates new DIB
//  FindDIBBits()       - Sets pointer to the DIB bits
//  DIBWidth()          - Gets the width of the DIB
//  DIBHeight()         - Gets the height of the DIB
//  PaletteSize()       - Calculates the buffer size required by a palette
//  DIBNumColors()      - Calculates number of colors in the DIB's color table
//  CreateDIBPalette()  - Creates a palette from a DIB
//  DIBToBitmap()       - Creates a bitmap from a DIB
//  BitmapToDIB()       - Creates a DIB from a bitmap
//  PalEntriesOnDevice()- Gets the number of palette entries of a device
//  GetSystemPalette()  - Returns a handle to the current system palette
//  AllocRoomForDIB()   - Allocates memory for a DIB
//  ChangeDIBFormat()   - Changes a DIB's BPP and/or compression format
//  ChangeBitmapFormat()- Changes a bitmap to a DIB with specified BPP and
//                        compression format
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <assert.h>
#include "dibapi.h"
#include "dibutil.h"
#include <stdio.h>

/*************************************************************************
 *
 * CreateDIB()
 *
 * Parameters:
 *
 * DWORD dwWidth    - Width for new bitmap, in pixels
 * DWORD dwHeight   - Height for new bitmap
 * WORD  wBitCount  - Bit Count for new DIB (1, 4, 8, or 24)
 *
 * Return Value:
 *
 * HDIB             - Handle to new DIB
 *
 * Description:
 *
 * This function allocates memory for and initializes a new DIB by
 * filling in the BITMAPINFOHEADER, allocating memory for the color
 * table, and allocating memory for the bitmap bits.  As with all
 * HDIBs, the header, colortable and bits are all in one contiguous
 * memory block.  This function is similar to the CreateBitmap()
 * Windows API.
 *
 * The colortable and bitmap bits are left uninitialized (zeroed) in the
 * returned HDIB.
 *
 *
 ************************************************************************/

HDIB CreateDIB(DWORD dwWidth, DWORD dwHeight, WORD wBitCount)
{
    BITMAPINFOHEADER    bi;             // bitmap header
    LPBITMAPINFOHEADER  lpbi;           // pointer to BITMAPINFOHEADER
    DWORD               dwLen;          // size of memory block
    HDIB                hDIB;
    DWORD               dwBytesPerLine; // Number of bytes per scanline

    // Make sure bits per pixel is valid

    if (wBitCount <= 1)
        wBitCount = 1;
    else if (wBitCount <= 4)
        wBitCount = 4;
    else if (wBitCount <= 8)
        wBitCount = 8;
    else if (wBitCount <= 24)
        wBitCount = 24;
    else
        wBitCount = 4;  // set default value to 4 if parameter is bogus

    // initialize BITMAPINFOHEADER

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = dwWidth;         // fill in width from parameter
    bi.biHeight = dwHeight;       // fill in height from parameter
    bi.biPlanes = 1;              // must be 1
    bi.biBitCount = wBitCount;    // from parameter
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;           // 0's here mean "default"
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store the DIB.  This
    // block should be big enough to hold the BITMAPINFOHEADER, the color
    // table, and the bits

    dwBytesPerLine = WIDTHBYTES(wBitCount * dwWidth);
    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + (dwBytesPerLine * dwHeight);

    // alloc memory block to store our bitmap

    hDIB = GlobalAlloc(GHND, dwLen);

    // major bummer if we couldn't get memory block

    if (!hDIB)
        return NULL;

    // lock memory and get pointer to it

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // use our bitmap info structure to fill in first part of
    // our DIB with the BITMAPINFOHEADER

    *lpbi = bi;

    // Since we don't know what the colortable and bits should contain,
    // just leave these blank.  Unlock the DIB and return the HDIB.

    GlobalUnlock(hDIB);

    //return handle to the DIB

    return hDIB;
}

/*************************************************************************
 *
 * FindDIBBits()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * LPSTR            - pointer to the DIB bits
 *
 * Description:
 *
 * This function calculates the address of the DIB's bits and returns a
 * pointer to the DIB bits.
 *
 ************************************************************************/

LPSTR FindDIBBits(LPSTR lpDIB)
{
   return (lpDIB + *(LPDWORD)lpDIB + PaletteSize(lpDIB));
}

/*************************************************************************
 *
 * DIBWidth()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * DWORD            - width of the DIB
 *
 * Description:
 *
 * This function gets the width of the DIB from the BITMAPINFOHEADER
 * width field if it is a Windows 3.0-style DIB or from the BITMAPCOREHEADER
 * width field if it is an OS/2-style DIB.
 *
 ************************************************************************/

DWORD DIBWidth(LPSTR lpDIB)
{
    LPBITMAPINFOHEADER   lpbmi;  // pointer to a Win 3.0-style DIB
    LPBITMAPCOREHEADER   lpbmc;  // pointer to an OS/2-style DIB

    // point to the header (whether Win 3.0 and OS/2)

    lpbmi = (LPBITMAPINFOHEADER)lpDIB;
    lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    // return the DIB width if it is a Win 3.0 DIB

    if (lpbmi->biSize == sizeof(BITMAPINFOHEADER))
        return lpbmi->biWidth;
    else  // it is an OS/2 DIB, so return its width
        return (DWORD)lpbmc->bcWidth;
}

/*************************************************************************
 *
 * DIBHeight()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * DWORD            - height of the DIB
 *
 * Description:
 *
 * This function gets the height of the DIB from the BITMAPINFOHEADER
 * height field if it is a Windows 3.0-style DIB or from the BITMAPCOREHEADER
 * height field if it is an OS/2-style DIB.
 *
 ************************************************************************/

DWORD DIBHeight(LPSTR lpDIB)
{
   LPBITMAPINFOHEADER   lpbmi;  // pointer to a Win 3.0-style DIB
   LPBITMAPCOREHEADER   lpbmc;  // pointer to an OS/2-style DIB

   // point to the header (whether OS/2 or Win 3.0

   lpbmi = (LPBITMAPINFOHEADER)lpDIB;
   lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    // return the DIB height if it is a Win 3.0 DIB
    if (lpbmi->biSize == sizeof(BITMAPINFOHEADER))
        return lpbmi->biHeight;
    else  // it is an OS/2 DIB, so return its height
        return (DWORD)lpbmc->bcHeight;
}

/*************************************************************************
 *
 * PaletteSize()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - size of the color palette of the DIB
 *
 * Description:
 *
 * This function gets the size required to store the DIB's palette by
 * multiplying the number of colors by the size of an RGBQUAD (for a
 * Windows 3.0-style DIB) or by the size of an RGBTRIPLE (for an OS/2-
 * style DIB).
 *
 ************************************************************************/

WORD PaletteSize(LPSTR lpDIB)
{
    // calculate the size required by the palette
    if (IS_WIN30_DIB (lpDIB))
        return (DIBNumColors(lpDIB) * sizeof(RGBQUAD));
    else
        return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}

/*************************************************************************
 *
 * DIBNumColors()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - number of colors in the color table
 *
 * Description:
 *
 * This function calculates the number of colors in the DIB's color table
 * by finding the bits per pixel for the DIB (whether Win3.0 or OS/2-style
 * DIB). If bits per pixel is 1: colors=2, if 4: colors=16, if 8: colors=256,
 * if 24, no colors in color table.
 *
 ************************************************************************/

WORD DIBNumColors(LPSTR lpDIB)
{
    WORD wBitCount;  // DIB bit count

    // If this is a Windows-style DIB, the number of colors in the
    // color table can be less than the number of bits per pixel
    // allows for (i.e. lpbi->biClrUsed can be set to some value).
    // If this is the case, return the appropriate value.

    if (IS_WIN30_DIB(lpDIB))
    {
        DWORD dwClrUsed;

        dwClrUsed = ((LPBITMAPINFOHEADER)lpDIB)->biClrUsed;
        if (dwClrUsed)

        return (WORD)dwClrUsed;
    }

    // Calculate the number of colors in the color table based on
    // the number of bits per pixel for the DIB.

    if (IS_WIN30_DIB(lpDIB))
        wBitCount = ((LPBITMAPINFOHEADER)lpDIB)->biBitCount;
    else
        wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;

    // return number of colors based on bits per pixel

    switch (wBitCount)
    {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
    }
}

/*************************************************************************
 *
 * CreateDIBPalette()
 *
 * Parameter:
 *
 * HDIB hDIB        - specifies the DIB
 *
 * Return Value:
 *
 * HPALETTE         - specifies the palette
 *
 * Description:
 *
 * This function creates a palette from a DIB by allocating memory for the
 * logical palette, reading and storing the colors from the DIB's color table
 * into the logical palette, creating a palette from this logical palette,
 * and then returning the palette's handle. This allows the DIB to be
 * displayed using the best possible colors (important for DIBs with 256 or
 * more colors).
 *
 ************************************************************************/

HPALETTE CreateDIBPalette(HDIB hDIB)
{
    LPLOGPALETTE        lpPal;          // pointer to a logical palette
    HANDLE              hLogPal;        // handle to a logical palette
    HPALETTE            hPal = NULL;    // handle to a palette
    int                 i, wNumColors;  // loop index, number of colors in color table
    LPSTR               lpbi;           // pointer to packed-DIB
    LPBITMAPINFO        lpbmi;          // pointer to BITMAPINFO structure (Win3.0)
    LPBITMAPCOREINFO    lpbmc;          // pointer to BITMAPCOREINFO structure (OS/2)
    BOOL                bWinStyleDIB;   // Win3.0 DIB?

    // if handle to DIB is invalid, return NULL

    if (!hDIB)
        return NULL;

    // lock DIB memory block and get a pointer to it

    lpbi = GlobalLock(hDIB);

    // get pointer to BITMAPINFO (Win 3.0)

    lpbmi = (LPBITMAPINFO)lpbi;

    // get pointer to BITMAPCOREINFO (OS/2 1.x)

    lpbmc = (LPBITMAPCOREINFO)lpbi;

    // get the number of colors in the DIB

    wNumColors = DIBNumColors(lpbi);

    // is this a Win 3.0 DIB?

    bWinStyleDIB = IS_WIN30_DIB(lpbi);
    if (wNumColors)
    {
        // allocate memory block for logical palette

        hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) +
                sizeof(PALETTEENTRY) * wNumColors);

        // if not enough memory, clean up and return NULL

        if (!hLogPal)
        {
            GlobalUnlock(hDIB);
            return NULL;
        }

        // lock memory block and get pointer to it

        lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);

        // set version and number of palette entries

        lpPal->palVersion = PALVERSION;
        lpPal->palNumEntries = wNumColors;

        // store RGB triples (if Win 3.0 DIB) or RGB quads (if OS/2 DIB)
        // into palette

        for (i = 0; i < wNumColors; i++)
        {
            if (bWinStyleDIB)
            {
                lpPal->palPalEntry[i].peRed = lpbmi->bmiColors[i].rgbRed;
                lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;
                lpPal->palPalEntry[i].peBlue = lpbmi->bmiColors[i].rgbBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
            else
            {
                lpPal->palPalEntry[i].peRed = lpbmc->bmciColors[i].rgbtRed;
                lpPal->palPalEntry[i].peGreen = lpbmc->bmciColors[i].rgbtGreen;
                lpPal->palPalEntry[i].peBlue = lpbmc->bmciColors[i].rgbtBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
        }

        // create the palette and get handle to it

        hPal = CreatePalette(lpPal);

        // if error getting handle to palette, clean up and return NULL

        if (!hPal)
        {
            GlobalUnlock(hLogPal);
            GlobalFree(hLogPal);
            return NULL;
        }
    }

    // clean up

    GlobalUnlock(hLogPal);
    GlobalFree(hLogPal);
    GlobalUnlock(hDIB);

    // return handle to DIB's palette
    return hPal;
}

/*************************************************************************
 *
 * DIBToBitmap()
 *
 * Parameters:
 *
 * HDIB hDIB        - specifies the DIB to convert
 *
 * HPALETTE hPal    - specifies the palette to use with the bitmap
 *
 * Return Value:
 *
 * HBITMAP          - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function creates a bitmap from a DIB using the specified palette.
 * If no palette is specified, default is used.
 *
 * NOTE:
 *
 * The bitmap returned from this funciton is always a bitmap compatible
 * with the screen (e.g. same bits/pixel and color planes) rather than
 * a bitmap with the same attributes as the DIB.  This behavior is by
 * design, and occurs because this function calls CreateDIBitmap to
 * do its work, and CreateDIBitmap always creates a bitmap compatible
 * with the hDC parameter passed in (because it in turn calls
 * CreateCompatibleBitmap).
 *
 * So for instance, if your DIB is a monochrome DIB and you call this
 * function, you will not get back a monochrome HBITMAP -- you will
 * get an HBITMAP compatible with the screen DC, but with only 2
 * colors used in the bitmap.
 *
 * If your application requires a monochrome HBITMAP returned for a
 * monochrome DIB, use the function SetDIBits().
 *
 * Also, the DIBpassed in to the function is not destroyed on exit. This
 * must be done later, once it is no longer needed.
 *
 ************************************************************************/

HBITMAP DIBToBitmap(HDIB hDIB, HPALETTE hPal)
{
    LPSTR       lpDIBHdr, lpDIBBits;  // pointer to DIB header, pointer to DIB bits
    HBITMAP     hBitmap;            // handle to device-dependent bitmap
    HDC         hDC;                    // handle to DC
    HPALETTE    hOldPal = NULL;    // handle to a palette

    // if invalid handle, return NULL

    if (!hDIB)
        return NULL;

    // lock memory block and get a pointer to it

    lpDIBHdr = GlobalLock(hDIB);

    // get a pointer to the DIB bits

    lpDIBBits = FindDIBBits(lpDIBHdr);

    // get a DC

    hDC = GetDC(NULL);
    if (!hDC)
    {
        // clean up and return NULL

        GlobalUnlock(hDIB);
        return NULL;
    }

    // select and realize palette

    if (hPal)
        hOldPal = SelectPalette(hDC, hPal, FALSE);

    RealizePalette(hDC);

    // create bitmap from DIB info. and bits
    hBitmap = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)lpDIBHdr, CBM_INIT,
            lpDIBBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS);

    // restore previous palette
    if (hOldPal)
        SelectPalette(hDC, hOldPal, FALSE);

    // clean up
    ReleaseDC(NULL, hDC);
    GlobalUnlock(hDIB);

    // return handle to the bitmap
    return hBitmap;
}

/*************************************************************************
 *
 * BitmapToDIB()
 *
 * Parameters:
 *
 * HBITMAP hBitmap  - specifies the bitmap to convert
 *
 * HPALETTE hPal    - specifies the palette to use with the bitmap
 *
 * Return Value:
 *
 * HDIB             - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function creates a DIB from a bitmap using the specified palette.
 *
 ************************************************************************/

HDIB BitmapToDIB(HBITMAP hBitmap, HPALETTE hPal)
{
    BITMAP              bm;         // bitmap structure
    BITMAPINFOHEADER    bi;         // bitmap header
    LPBITMAPINFOHEADER  lpbi;       // pointer to BITMAPINFOHEADER
    DWORD               dwLen;      // size of memory block
    HANDLE              hDIB, h;    // handle to DIB, temp handle
    HDC                 hDC;        // handle to DC
    WORD                biBits;     // bits per pixel

    // check if bitmap handle is valid

    if (!hBitmap)
        return NULL;

    // fill in BITMAP structure, return NULL if it didn't work

    if (!GetObject(hBitmap, sizeof(bm), (LPSTR)&bm))
        return NULL;

    // if no palette is specified, use default palette

    if (hPal == NULL)
        hPal = GetStockObject(DEFAULT_PALETTE);

    // calculate bits per pixel

    biBits = bm.bmPlanes * bm.bmBitsPixel;

    // make sure bits per pixel is valid

    if (biBits <= 1)
        biBits = 1;
    else if (biBits <= 4)
        biBits = 4;
    else if (biBits <= 8)
        biBits = 8;
    else // if greater than 8-bit, force to 24-bit
        biBits = 24;

    // initialize BITMAPINFOHEADER

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bm.bmWidth;
    bi.biHeight = bm.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = biBits;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store BITMAPINFO

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi);

    // get a DC

    hDC = GetDC(NULL);

    // select and realize our palette

    hPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    // alloc memory block to store our bitmap

    hDIB = GlobalAlloc(GHND, dwLen);

    // if we couldn't get memory block

    if (!hDIB)
    {
      // clean up and return NULL

      SelectPalette(hDC, hPal, TRUE);
      RealizePalette(hDC);
      ReleaseDC(NULL, hDC);
      return NULL;
    }

    // lock memory and get pointer to it

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    /// use our bitmap info. to fill BITMAPINFOHEADER

    *lpbi = bi;

    // call GetDIBits with a NULL lpBits param, so it will calculate the
    // biSizeImage field for us

    GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
        DIB_RGB_COLORS);

    // get the info. returned by GetDIBits and unlock memory block

    bi = *lpbi;
    GlobalUnlock(hDIB);

    // if the driver did not fill in the biSizeImage field, make one up
    if (bi.biSizeImage == 0)
        bi.biSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * biBits) * bm.bmHeight;

    // realloc the buffer big enough to hold all the bits

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + bi.biSizeImage;

    if (h = GlobalReAlloc(hDIB, dwLen, 0))
        hDIB = h;
    else
    {
        // clean up and return NULL

        GlobalFree(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    // lock memory block and get pointer to it */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // call GetDIBits with a NON-NULL lpBits param, and actualy get the
    // bits this time

    if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi +
            (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi), (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS) == 0)
    {
        // clean up and return NULL

        GlobalUnlock(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    bi = *lpbi;

    // clean up
    GlobalUnlock(hDIB);
    SelectPalette(hDC, hPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    // return handle to the DIB
    return hDIB;
}

/*************************************************************************
 *
 * PalEntriesOnDevice()
 *
 * Parameter:
 *
 * HDC hDC          - device context
 *
 * Return Value:
 *
 * int              - number of palette entries on device
 *
 * Description:
 *
 * This function gets the number of palette entries on the specified device
 *
 ************************************************************************/

int PalEntriesOnDevice(HDC hDC)
{
    int nColors;  // number of colors

    // Find out the number of colors on this device.

    nColors = (1 << (GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES)));

    assert(nColors);
    return nColors;
}

/*************************************************************************
 *
 * GetSystemPalette()
 *
 * Parameters:
 *
 * None
 *
 * Return Value:
 *
 * HPALETTE         - handle to a copy of the current system palette
 *
 * Description:
 *
 * This function returns a handle to a palette which represents the system
 * palette.  The system RGB values are copied into our logical palette using
 * the GetSystemPaletteEntries function.
 *
 ************************************************************************/

HPALETTE GetSystemPalette(void)
{
    HDC hDC;                // handle to a DC
    static HPALETTE hPal = NULL;   // handle to a palette
    HANDLE hLogPal;         // handle to a logical palette
    LPLOGPALETTE lpLogPal;  // pointer to a logical palette
    int nColors;            // number of colors

    // Find out how many palette entries we want.

    hDC = GetDC(NULL);

    if (!hDC)
        return NULL;

    nColors = PalEntriesOnDevice(hDC);   // Number of palette entries

    // Allocate room for the palette and lock it.

    hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + nColors *
            sizeof(PALETTEENTRY));

    // if we didn't get a logical palette, return NULL

    if (!hLogPal)
        return NULL;

    // get a pointer to the logical palette

    lpLogPal = (LPLOGPALETTE)GlobalLock(hLogPal);

    // set some important fields

    lpLogPal->palVersion = PALVERSION;
    lpLogPal->palNumEntries = nColors;

    // Copy the current system palette into our logical palette

    GetSystemPaletteEntries(hDC, 0, nColors,
            (LPPALETTEENTRY)(lpLogPal->palPalEntry));

    // Go ahead and create the palette.  Once it's created,
    // we no longer need the LOGPALETTE, so free it.

    hPal = CreatePalette(lpLogPal);

    // clean up

    GlobalUnlock(hLogPal);
    GlobalFree(hLogPal);
    ReleaseDC(NULL, hDC);

    return hPal;
}

/*************************************************************************
 *
 * AllocRoomForDIB()
 *
 * Parameters:
 *
 * BITMAPINFOHEADER - bitmap info header stucture
 *
 * HBITMAP          - handle to the bitmap
 *
 * Return Value:
 *
 * HDIB             - handle to memory block
 *
 * Description:
 *
 *  This routine takes a BITMAPINOHEADER, and returns a handle to global
 *  memory which can contain a DIB with that header.  It also initializes
 *  the header portion of the global memory.  GetDIBits() is used to determine
 *  the amount of room for the DIB's bits.  The total amount of memory
 *  needed = sizeof(BITMAPINFOHEADER) + size of color table + size of bits.
 *
 ************************************************************************/

HANDLE AllocRoomForDIB(BITMAPINFOHEADER bi, HBITMAP hBitmap)
{
    DWORD               dwLen;
    HANDLE              hDIB;
    HDC                 hDC;
    LPBITMAPINFOHEADER  lpbi;
    HANDLE              hTemp;

    // Figure out the size needed to hold the BITMAPINFO structure
    // (which includes the BITMAPINFOHEADER and the color table).

    dwLen = bi.biSize + PaletteSize((LPSTR) &bi);
    hDIB  = GlobalAlloc(GHND,dwLen);

    // Check that DIB handle is valid

    if (!hDIB)
        return NULL;

    // Set up the BITMAPINFOHEADER in the newly allocated global memory,
    // then call GetDIBits() with lpBits = NULL to have it fill in the
    // biSizeImage field for us.

    lpbi  = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    *lpbi = bi;

    hDC   = GetDC(NULL);

    GetDIBits(hDC, hBitmap, 0, (UINT) bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS);
    ReleaseDC(NULL, hDC);

    // If the driver did not fill in the biSizeImage field,
    // fill it in -- NOTE: this is a bug in the driver!

    if (lpbi->biSizeImage == 0)
        lpbi->biSizeImage = WIDTHBYTES((DWORD)lpbi->biWidth *
                lpbi->biBitCount) * lpbi->biHeight;

    // Get the size of the memory block we need

    dwLen = lpbi->biSize + PaletteSize((LPSTR) &bi) + lpbi->biSizeImage;

    // Unlock the memory block

    GlobalUnlock(hDIB);

    // ReAlloc the buffer big enough to hold all the bits

    if (hTemp = GlobalReAlloc(hDIB,dwLen,0))
        return hTemp;
    else
    {
        // Else free memory block and return failure

        GlobalFree(hDIB);
        return NULL;
    }
}

/*************************************************************************
 *
 * ChangeDIBFormat()
 *
 * Parameter:
 *
 * HDIB             - handle to packed-DIB in memory
 *
 * WORD             - desired bits per pixel
 *
 * DWORD            - desired compression format
 *
 * Return Value:
 *
 * HDIB             - handle to the new DIB if successful, else NULL
 *
 * Description:
 *
 * This function will convert the bits per pixel and/or the compression
 * format of the specified DIB. Note: If the conversion was unsuccessful,
 * we return NULL. The original DIB is left alone. Don't use code like the
 * following:
 *
 *    hMyDIB = ChangeDIBFormat(hMyDIB, 8, BI_RLE4);
 *
 * The conversion will fail, but hMyDIB will now be NULL and the original
 * DIB will now hang around in memory. We could have returned the old
 * DIB, but we wanted to allow the programmer to check whether this
 * conversion succeeded or failed.
 *
 ************************************************************************/

HDIB ChangeDIBFormat(HDIB hDIB, WORD wBitCount, DWORD dwCompression)
{
    HDC                hDC;             // Handle to DC
    HBITMAP            hBitmap;         // Handle to bitmap
    BITMAP             Bitmap;          // BITMAP data structure
    BITMAPINFOHEADER   bi;              // Bitmap info header
    LPBITMAPINFOHEADER lpbi;            // Pointer to bitmap info
    HDIB               hNewDIB = NULL;  // Handle to new DIB
    HPALETTE           hPal, hOldPal;   // Handle to palette, prev pal
    WORD               DIBBPP, NewBPP;  // DIB bits per pixel, new bpp
    DWORD              DIBComp, NewComp;// DIB compression, new compression

    // Check for a valid DIB handle

    if (!hDIB)
        return NULL;

    // Get the old DIB's bits per pixel and compression format

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    DIBBPP = ((LPBITMAPINFOHEADER)lpbi)->biBitCount;
    DIBComp = ((LPBITMAPINFOHEADER)lpbi)->biCompression;
    GlobalUnlock(hDIB);

    // Validate wBitCount and dwCompression
    // They must match correctly (i.e., BI_RLE4 and 4 BPP or
    // BI_RLE8 and 8BPP, etc.) or we return failure
    if (wBitCount == 0)
    {
        NewBPP = DIBBPP;
        if ((dwCompression == BI_RLE4 && NewBPP == 4) ||
                (dwCompression == BI_RLE8 && NewBPP == 8) ||
                (dwCompression == BI_RGB))
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 1 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;

NewComp = BI_RGB;
    }
    else if (wBitCount == 4)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE4)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 8)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE8)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 24 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else
        return NULL;

    // Save the old DIB's palette

    hPal = CreateDIBPalette(hDIB);
    if (!hPal)
        return NULL;

    // Convert old DIB to a bitmap

    hBitmap = DIBToBitmap(hDIB, hPal);
    if (!hBitmap)
    {
        DeleteObject(hPal);
        return NULL;
    }

    // Get info about the bitmap
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);

    // Fill in the BITMAPINFOHEADER appropriately

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = Bitmap.bmWidth;
    bi.biHeight             = Bitmap.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = NewBPP;
    bi.biCompression        = NewComp;
    bi.biSizeImage          = 0;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = 0;
    bi.biClrImportant       = 0;

    // Go allocate room for the new DIB

    hNewDIB = AllocRoomForDIB(bi, hBitmap);
    if (!hNewDIB)
        return NULL;

    // Get a pointer to the new DIB

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hNewDIB);

    // Get a DC and select/realize our palette in it

    hDC  = GetDC(NULL);
    hOldPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    // Call GetDIBits and get the new DIB bits

    if (!GetDIBits(hDC, hBitmap, 0, (UINT) lpbi->biHeight,
            (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi),
            (LPBITMAPINFO)lpbi, DIB_RGB_COLORS))
    {
        GlobalUnlock(hNewDIB);
        GlobalFree(hNewDIB);
        hNewDIB = NULL;
    }

    // Clean up and return

    SelectPalette(hDC, hOldPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    // Unlock the new DIB's memory block
    if (hNewDIB)
        GlobalUnlock(hNewDIB);

    DeleteObject(hBitmap);
    DeleteObject(hPal);

    return hNewDIB;
}

/*************************************************************************
 *
 * ChangeBitmapFormat()
 *
 * Parameter:
 *
 * HBITMAP          - handle to a bitmap
 *
 * WORD             - desired bits per pixel
 *
 * DWORD            - desired compression format
 *
 * HPALETTE         - handle to palette
 *
 * Return Value:
 *
 * HDIB             - handle to the new DIB if successful, else NULL
 *
 * Description:
 *
 * This function will convert a bitmap to the specified bits per pixel
 * and compression format. The bitmap and it's palette will remain
 * after calling this function.
 *
 ************************************************************************/

HDIB ChangeBitmapFormat(HBITMAP hBitmap, WORD wBitCount, DWORD dwCompression,
        HPALETTE hPal)
{
    HDC                hDC;          // Screen DC
    HDIB               hNewDIB=NULL; // Handle to new DIB
    BITMAP             Bitmap;       // BITMAP data structure
    BITMAPINFOHEADER   bi;           // Bitmap info. header
    LPBITMAPINFOHEADER lpbi;         // Pointer to bitmap header
    HPALETTE           hOldPal=NULL; // Handle to palette
    WORD               NewBPP;       // New bits per pixel
    DWORD              NewComp;      // New compression format

    // Check for a valid bitmap handle

    if (!hBitmap)
        return NULL;

    // Validate wBitCount and dwCompression
    // They must match correctly (i.e., BI_RLE4 and 4 BPP or
    // BI_RLE8 and 8BPP, etc.) or we return failure

    if (wBitCount == 0)
    {
        NewComp = dwCompression;
        if (NewComp == BI_RLE4)
            NewBPP = 4;
        else if (NewComp == BI_RLE8)
            NewBPP = 8;
        else // Not enough info */
            return NULL;
    }
    else if (wBitCount == 1 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else if (wBitCount == 4)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE4)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 8)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE8)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 24 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else
        return NULL;

    // Get info about the bitmap

    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);

    // Fill in the BITMAPINFOHEADER appropriately

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = Bitmap.bmWidth;
    bi.biHeight             = Bitmap.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = NewBPP;
    bi.biCompression        = NewComp;
    bi.biSizeImage          = 0;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = 0;
    bi.biClrImportant       = 0;

    // Go allocate room for the new DIB

    hNewDIB = AllocRoomForDIB(bi, hBitmap);
    if (!hNewDIB)
        return NULL;

    // Get a pointer to the new DIB

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hNewDIB);

    // If we have a palette, get a DC and select/realize it

    if (hPal)
    {
        hDC  = GetDC(NULL);
        hOldPal = SelectPalette(hDC, hPal, FALSE);
        RealizePalette(hDC);
    }

    // Call GetDIBits and get the new DIB bits

    if (!GetDIBits(hDC, hBitmap, 0, (UINT) lpbi->biHeight, (LPSTR)lpbi +
            (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi), (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS))
    {
        GlobalUnlock(hNewDIB);
        GlobalFree(hNewDIB);
        hNewDIB = NULL;
    }

    // Clean up and return

    if (hOldPal)
    {
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
    }

    // Unlock the new DIB's memory block

    if (hNewDIB)
        GlobalUnlock(hNewDIB);

    return hNewDIB;
}


DIBUTIL.H

/*    PortTool v2.2     dibutil.h          */

/*
 *  dibutil.h
 *
 *  Copyright 1991-1998 Microsoft Corporation. All rights reserved.
 *
 *  Header file for Device-Independent Bitmap (DIB) API.  Provides
 *  function prototypes and constants for the following functions:
 *
 *  AllocRoomForDIB()   - Allocates memory for a DIB
 *
 */

/* DIB constants */
#define PALVERSION   0x300

/* DIB macros */
#define IS_WIN30_DIB(lpbi)  ((*(LPDWORD)(lpbi)) == sizeof(BITMAPINFOHEADER))
#define RECTWIDTH(lpRect)     ((lpRect)->right - (lpRect)->left)
#define RECTHEIGHT(lpRect)    ((lpRect)->bottom - (lpRect)->top)

/* function prototypes */
HANDLE          AllocRoomForDIB(BITMAPINFOHEADER bi, HBITMAP hBitmap);


DLGOPEN.C

//**************************************************************************
//
// MODULE  : DLGOPEN.C
//
// Routine to display a standard COMMDLG File/Save dialog box.
//
// Defines the following functions:
//
// GetFileName()     - Calls up common dialog file/save, and returns
//                     selected file
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**************************************************************************/

#define     STRICT      // enable strict type checking

#include <windows.h>
#include "wincap.h"
#include "dialogs.h"
#include "resource.h"
#include "commdlg.h"  // For common dialogs
#include "dlgs.h"     // For common dialog hook proc

#define MAXFILENAMELEN 144

extern HANDLE ghInst;
extern UINT   guiFileOKMsg;       // for common dialog FILEOKSTRING

BOOL APIENTRY FileSaveHookProc (HWND hDlg, WORD msg, WORD wParam, LONG lParam);

//************************************************************************
// Function:  GetFileName (HWND, LPSTR, LPDWORD)
//
//   Purpose:  Prompts user for a filename through the use of a Windows 3.1
//             FileOpen common dialog box.
//
//Parameters:  HWND hWndOwner      Owner who is calling this funciton
//
//             LPSTR szFileName    Buffer where selected filename will
//                                 be placed.  Must be at least MAXFILENAMELEN
//                                 characters large
//
//             LPDWORD dwFlags     HIWORD is the bits per pixel, LOWORD
//                                 is compression type.
//
//   Returns:  TRUE if a filename is selected.
//             FALSE if no filename is selected.
//
//  Comments:  Filename is put into the string passed to the routine.
//             If a filename is not selected, NULL is returned.
//
//************************************************************************/

BOOL GetFileName (HWND hWndOwner, LPSTR szFileName, LPDWORD dwFlags)
{
    OPENFILENAME   of;
    DWORD          flags;
    static char    szTitle[50];         // Dialog Box title
    static char    szFile[MAXFILENAMELEN];         // File name
    DWORD          dw = 0;               // Local copy of flags for validation
    BOOL           bReturn;             // Return code
    DWORD          dwLocalOpt;
    CHAR           lpBuffer[128];       // Holds strings retrieved from resources

    // Parameter validation -- try to recover gracefully if LPWORD
    // is NULL, so we don't die when we try to do a *wFlags = x below

    if (dwFlags == NULL)
        dwFlags = &dw;

    dwLocalOpt = *dwFlags;  // Make local copy of options

    // Initialize the OPENFILENAME members

    lstrcpy(szFile, "*.BMP");

    LoadString(ghInst, IDS_SAVEASTITLE, (LPSTR)szTitle, 50);
    LoadString(ghInst, IDS_SAVEFILTER, lpBuffer, sizeof(lpBuffer));
    flags = OFN_ENABLEHOOK | OFN_ENABLETEMPLATE | OFN_HIDEREADONLY |
            OFN_OVERWRITEPROMPT;

    of.lStructSize       = sizeof (OPENFILENAME);
    of.hwndOwner         = hWndOwner;
    of.hInstance         = ghInst;
    of.lpstrFilter       = lpBuffer;
    of.lpstrCustomFilter = NULL;
    of.nMaxCustFilter    = 0L;
    of.nFilterIndex      = 1L;
    of.lpstrFile         = (LPSTR)szFile;
    of.nMaxFile          = MAXFILENAMELEN;
    of.lpstrFileTitle    = NULL;
    of.nMaxFileTitle     = 0;
    of.lpstrInitialDir   = NULL;
    of.lpstrTitle        = szTitle;
    of.Flags             = flags;
    of.nFileOffset       = 0;
    of.nFileExtension    = 0;
    of.lpstrDefExt       = "bmp";
    of.lpfnHook          = (LPOFNHOOKPROC)FileSaveHookProc;
    of.lpTemplateName    = (LPSTR)"FILESAVE";

    // Place our flags in the lCustData parameter

    of.lCustData         = dwLocalOpt;

    // Call the GetSaveFilename function

    if (GetSaveFileName (&of))
    {
        // User pressed OK, continue on

        bReturn = TRUE;

        // Copy filename out

        lstrcpy (szFileName, of.lpstrFile);

        // Copy flags out

        *dwFlags = of.lCustData;

    }
    else
    {
        bReturn = FALSE;
        szFileName[0] = '\0';
        *dwFlags = 0;
    }

    return bReturn;
}

/*************************************************************************

  Function:  FileSaveHookProc (HWND, WORD, WORD, LONG)

   Purpose:  Hook procedure for FileSave common dialog box.

   Returns:  TRUE if message was processed.
             FALSE if the system needs to process the message.

  Comments:

*************************************************************************/

BOOL APIENTRY FileSaveHookProc (HWND hDlg, WORD msg, WORD wParam, LONG lParam)
{
    HWND                    hGroup;
    RECT                    rect, DlgRect;
    static LPOPENFILENAME   lpOpenFn;
    static DWORD            dwFlags;

    if (wParam == guiFileOKMsg)
    {
        WORD    biStyle, biBits;

        if (SendDlgItemMessage (hDlg, IDD_RGB, BM_GETCHECK, 0, 0L))
            biStyle = IDD_RGB;

        else if (SendDlgItemMessage (hDlg, IDD_RLE4, BM_GETCHECK, 0, 0L))
            biStyle = IDD_RLE4;

        else
            biStyle = IDD_RLE8;

        if (SendDlgItemMessage (hDlg, IDD_1, BM_GETCHECK, 0, 0L))
            biBits = 1;

        else if (SendDlgItemMessage (hDlg, IDD_4, BM_GETCHECK, 0, 0L))
            biBits = 4;

        else if (SendDlgItemMessage (hDlg, IDD_8, BM_GETCHECK, 0, 0L))
            biBits = 8;

        else
            biBits = 24;

        // Copy result of user input to our lCustData field, which
        // will pass it back to the calling function.

        lpOpenFn->lCustData = MAKELONG(biStyle, biBits);
        return FALSE;
    }

    switch (msg)
    {
        case WM_INITDIALOG:

            //  Get the memory handle stored in the extra words.
            //  From this, insert the filename, and the file format

            // Retrieve flags which we passed in in lCustData

            lpOpenFn = (LPOPENFILENAME)lParam;
            dwFlags = lpOpenFn->lCustData;

            // Our dwFlags field contains the following:
            //
            // HIWORD: Bits per pixel specification.  Should be set to
            //    one of the following: 1, 4, 8, or 24
            //
            // LOWORD: Compression specification:  Should be set to one of
            //    the following:   IDD_RGB, IDD_RLE4, IDD_RLE8

            switch (LOWORD(dwFlags))
            {
                case IDD_RGB:
                    SendDlgItemMessage (hDlg, IDD_RGB,  BM_SETCHECK, 1, 0L);
                    SendMessage (hDlg, WM_COMMAND, IDD_RGB, 0L);
                    break;

                case IDD_RLE4:
                    SendDlgItemMessage (hDlg, IDD_RLE4, BM_SETCHECK, 1, 0L);
                    SendMessage (hDlg, WM_COMMAND, IDD_RLE4, 0L);
                    break;

                case IDD_RLE8:

                default:
                    SendDlgItemMessage (hDlg, IDD_RLE8, BM_SETCHECK, 1, 0L);
                    SendMessage (hDlg, WM_COMMAND, IDD_RLE8, 0L);
                    break;
            }

            switch (HIWORD(dwFlags))
            {
                case 4:
                    SendDlgItemMessage (hDlg, IDD_4,   BM_SETCHECK, 1, 0L);
                    break;

                case 8:
                    SendDlgItemMessage (hDlg, IDD_8,   BM_SETCHECK, 1, 0L);
                    break;

                case 24:
                    SendDlgItemMessage (hDlg, IDD_24,  BM_SETCHECK, 1, 0L);
                    break;

                case 1:

                default:
                    SendDlgItemMessage (hDlg, IDD_1,   BM_SETCHECK, 1, 0L);
                    break;
            }

            // Return 1 so Windows sets focus on a control in our dialog.

            return 1;

        case WM_COMMAND:
            switch (wParam)
            {

            case IDD_FILETYPE:
                hGroup = GetDlgItem (hDlg, IDD_FILETYPEGROUP);
                GetWindowRect (hGroup, &rect);
                GetWindowRect (hDlg, &DlgRect);
                SetWindowPos (hDlg,0, DlgRect.left, DlgRect.top,
                        (DlgRect.right-DlgRect.left),
                        (rect.bottom+(rect.left-DlgRect.left)-DlgRect.top),
                        SWP_NOMOVE | SWP_NOZORDER);
                EnableWindow (GetDlgItem (hDlg, IDD_FILETYPE), 0);
                SetFocus (hGroup);
                break;

            case IDD_RLE4:
                if (SendDlgItemMessage (hDlg, IDD_1, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_1, BM_SETCHECK, 0,0L);
                if (SendDlgItemMessage (hDlg, IDD_8, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_8, BM_SETCHECK, 0,0L);
                if (SendDlgItemMessage (hDlg, IDD_24, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_24, BM_SETCHECK, 0,0L);

                EnableWindow (GetDlgItem(hDlg, IDD_4), 1);
                SendDlgItemMessage (hDlg, IDD_4, BM_SETCHECK, 1, 0L);
                EnableWindow (GetDlgItem(hDlg, IDD_1), 0);
                EnableWindow (GetDlgItem(hDlg, IDD_8), 0);
                EnableWindow (GetDlgItem(hDlg, IDD_24), 0);
                break;

            case IDD_RLE8:
                if (SendDlgItemMessage (hDlg, IDD_1, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_1, BM_SETCHECK, 0,0L);
                if (SendDlgItemMessage (hDlg, IDD_4, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_4, BM_SETCHECK, 0,0L);
                if (SendDlgItemMessage (hDlg, IDD_24, BM_GETCHECK, 0, 0L))
                    SendDlgItemMessage (hDlg, IDD_24, BM_SETCHECK, 0,0L);

                EnableWindow (GetDlgItem(hDlg, IDD_8), 1);
                SendDlgItemMessage (hDlg, IDD_8, BM_SETCHECK, 1, 0L);
                EnableWindow (GetDlgItem(hDlg, IDD_1), 0);
                EnableWindow (GetDlgItem(hDlg, IDD_4), 0);
                EnableWindow (GetDlgItem(hDlg, IDD_24), 0);
                break;

            case IDD_RGB:
                EnableWindow (GetDlgItem(hDlg, IDD_1), 1);
                EnableWindow (GetDlgItem(hDlg, IDD_4), 2);
                EnableWindow (GetDlgItem(hDlg, IDD_8), 3);
                EnableWindow (GetDlgItem(hDlg, IDD_24), 1);
                break;

            break;
        }

        default:
            break;
    }
    return FALSE;
}


DLLINIT.C

// -----------------------------------------------------------------
// File name:  DLLINIT.C
//
// This is the DLL's initialization source file.  It contains DllEntry,
// the Win32 DLL's entry and exit point.
//
// Written by Microsoft Product Support Services, Windows Developer Support.
//
// Copyright 1991-1998 Microsoft Corporation.  All rights reserved.
//
// -----------------------------------------------------------------

#define     STRICT      // enable strict type checking

#include <windows.h>
#include "dibdll.h"

#pragma data_seg("._WINCAP")
    HWND ghWndMain = 0;         // Handle to main window -- used to post msgs
#pragma data_seg()

HANDLE ghDLLInst = 0;   // Handle to the DLL's instance.
WORD nLangID;

// entry point for DLL loading and unloading

BOOL WINAPI DllMain (HANDLE hModule, DWORD dwFunction, LPVOID lpNot)
{
    ghDLLInst = hModule;

    switch (dwFunction)
    {
        case DLL_PROCESS_ATTACH:
            nLangID = PRIMARYLANGID(GetUserDefaultLangID());
            break;

        case DLL_PROCESS_DETACH:

               // When we are finally going away, do something here

        default:
            break;
    }
    return TRUE;
}


ERRORS.C

//
// Errors.c
//
// Contains error messages for DIBAPI.DLL
//
// These error messages all have constants associated with
// them, contained in dibapi.h.
//
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//

#define     STRICT      // enable strict type checking

#include <windows.h>
#include "dibapi.h"

extern WORD nLangID;
static char *szErrors[] =
{
    "Not a Windows DIB file!",
    "Couldn't allocate memory!",
    "Error reading file!",
    "Error locking memory!",
    "Error opening file!",
    "Error creating palette!",
    "Error getting a DC!",
    "Error creating Device Dependent Bitmap",
    "StretchBlt() failed!",
    "StretchDIBits() failed!",
    "SetDIBitsToDevice() failed!",
    "Printer: StartDoc failed!",
    "Printing: GetModuleHandle() couldn't find GDI!",
    "Printer: SetAbortProc failed!",
    "Printer: StartPage failed!",
    "Printer: NEWFRAME failed!",
    "Printer: EndPage failed!",
    "Printer: EndDoc failed!",
    "SetDIBits() failed!",
    "File Not Found!",
    "Invalid Handle",
    "General Error on call to DIB function"
};

static char *szErrorsJ[] =
{
    "Windows DIB  t @ C                 B",
    "         m                             B",
    " t @ C             G   [",
    "           b N  G   [",
    " t @ C     I [ v    G   [",
    " p   b g       G   [",
    " f o C X  R   e L X g       G   [",
    " f o C X     r b g } b v       G   [",
    "StretchBlt()      s         B",
    "StretchDIBits()      s         B",
    "SetDIBitsToDevice()      s         B",
    " v     ^: StartDoc      s         B",
    "    : GetModuleHandle() GDI                        B",
    " v     ^: SetAbortProc      s         B",
    " v     ^: StartPage      s         B",
    " v     ^: NEWFRAME      s         B",
    " v     ^: EndPage      s         B",
    " v     ^: EndDoc      s         B",
    "SetDIBits()      s         B",
    " t @ C                         B",
    "       n   h  ",
    "DIB  t @   N V         o           G   ["
};

void DIBError(int ErrNo)
{
    if (nLangID == LANG_JAPANESE) {
        if ((ErrNo < ERR_MIN) || (ErrNo >= ERR_MAX))
            MessageBox(NULL, "   m   G   [ B", NULL, MB_OK | MB_ICONHAND);
        else
            MessageBox(NULL, szErrorsJ[ErrNo], NULL, MB_OK | MB_ICONHAND);
    }
    else {
        if ((ErrNo < ERR_MIN) || (ErrNo >= ERR_MAX))
            MessageBox(NULL, "Undefined Error!", NULL, MB_OK | MB_ICONHAND);
        else
            MessageBox(NULL, szErrors[ErrNo], NULL, MB_OK | MB_ICONHAND);
    }
}


FILE.C

//*******************************************************************
//
//  file.c
//
//  Source file for Device-Independent Bitmap (DIB) API.  Provides
//  the following functions:
//
//  SaveDIB()           - Saves the specified dib in a file
//  LoadDIB()           - Loads a DIB from a file
//  DestroyDIB()        - Deletes DIB when finished using it
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//*******************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <io.h>
#include <direct.h>
#include <stdlib.h>
#include "dibutil.h"
#include "dibapi.h"

// Dib Header Marker - used in writing DIBs to files

#define DIB_HEADER_MARKER   ((WORD) ('M' << 8) | 'B')

/*********************************************************************
 *
 * Local Function Prototypes
 *
 *********************************************************************/

HANDLE ReadDIBFile(HANDLE);
BOOL SaveDIBFile(void);
BOOL WriteDIB(LPSTR, HANDLE);

/*************************************************************************
 *
 * LoadDIB()
 *
 * Loads the specified DIB from a file, allocates memory for it,
 * and reads the disk file into the memory.
 *
 *
 * Parameters:
 *
 * LPSTR lpFileName - specifies the file to load a DIB from
 *
 * Returns: A handle to a DIB, or NULL if unsuccessful.
 *
 * NOTE: The DIB API were not written to handle OS/2 DIBs; This
 * function will reject any file that is not a Windows DIB.
 *
 *************************************************************************/

HDIB LoadDIB(LPSTR lpFileName)
{
    HDIB        hDIB;
    HANDLE      hFile;

    // Set the cursor to a hourglass, in case the loading operation
    // takes more than a sec, the user will know what's going on.

    SetCursor(LoadCursor(NULL, IDC_WAIT));

    if ((hFile = CreateFile(lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL)) != INVALID_HANDLE_VALUE)
    {
        hDIB = ReadDIBFile(hFile);
        CloseHandle(hFile);
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        return hDIB;
    }
    else
    {
        DIBError(ERR_FILENOTFOUND);
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        return NULL;
    }
}

/*************************************************************************
 *
 * SaveDIB()
 *
 * Saves the specified DIB into the specified file name on disk.  No
 * error checking is done, so if the file already exists, it will be
 * written over.
 *
 * Parameters:
 *
 * HDIB hDib - Handle to the dib to save
 *
 * LPSTR lpFileName - pointer to full pathname to save DIB under
 *
 * Return value: 0 if successful, or one of:
 *        ERR_INVALIDHANDLE
 *        ERR_OPEN
 *        ERR_LOCK
 *
 *************************************************************************/

WORD SaveDIB(HDIB hDib, LPSTR lpFileName)
{
    BITMAPFILEHEADER    bmfHdr;     // Header for Bitmap file
    LPBITMAPINFOHEADER  lpBI;       // Pointer to DIB info structure
    HANDLE              fh;         // file handle for opened file
    DWORD               dwDIBSize;
    DWORD               dwWritten;

    if (!hDib)
        return ERR_INVALIDHANDLE;

    fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (fh == INVALID_HANDLE_VALUE)
        return ERR_OPEN;

    // Get a pointer to the DIB memory, the first of which contains
    // a BITMAPINFO structure

    lpBI = (LPBITMAPINFOHEADER)GlobalLock(hDib);
    if (!lpBI)
    {
        CloseHandle(fh);
        return ERR_LOCK;
    }

    // Check to see if we're dealing with an OS/2 DIB.  If so, don't
    // save it because our functions aren't written to deal with these
    // DIBs.

    if (lpBI->biSize != sizeof(BITMAPINFOHEADER))
    {
        GlobalUnlock(hDib);
        CloseHandle(fh);
        return ERR_NOT_DIB;
    }

    // Fill in the fields of the file header

    // Fill in file type (first 2 bytes must be "BM" for a bitmap)

    bmfHdr.bfType = DIB_HEADER_MARKER;  // "BM"

    // Calculating the size of the DIB is a bit tricky (if we want to
    // do it right).  The easiest way to do this is to call GlobalSize()
    // on our global handle, but since the size of our global memory may have
    // been padded a few bytes, we may end up writing out a few too
    // many bytes to the file (which may cause problems with some apps,
    // like HC 3.0).
    //
    // So, instead let's calculate the size manually.
    //
    // To do this, find size of header plus size of color table.  Since the
    // first DWORD in both BITMAPINFOHEADER and BITMAPCOREHEADER conains
    // the size of the structure, let's use this.

    // Partial Calculation

    dwDIBSize = *(LPDWORD)lpBI + PaletteSize((LPSTR)lpBI);

    // Now calculate the size of the image

    // It's an RLE bitmap, we can't calculate size, so trust the biSizeImage
    // field

    if ((lpBI->biCompression == BI_RLE8) || (lpBI->biCompression == BI_RLE4))
        dwDIBSize += lpBI->biSizeImage;
    else
    {
        DWORD dwBmBitsSize;  // Size of Bitmap Bits only

        // It's not RLE, so size is Width (DWORD aligned) * Height

        dwBmBitsSize = WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount)) *
                lpBI->biHeight;

        dwDIBSize += dwBmBitsSize;

        // Now, since we have calculated the correct size, why don't we
        // fill in the biSizeImage field (this will fix any .BMP files which
        // have this field incorrect).

        lpBI->biSizeImage = dwBmBitsSize;
    }

    // Calculate the file size by adding the DIB size to sizeof(BITMAPFILEHEADER)

    bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);
    bmfHdr.bfReserved1 = 0;
    bmfHdr.bfReserved2 = 0;

    // Now, calculate the offset the actual bitmap bits will be in
    // the file -- It's the Bitmap file header plus the DIB header,
    // plus the size of the color table.

    bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize +
            PaletteSize((LPSTR)lpBI);

    // Write the file header

    WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);

    // Write the DIB header and the bits -- use local version of
    // MyWrite, so we can write more than 32767 bytes of data

    WriteFile(fh, (LPSTR)lpBI, dwDIBSize, &dwWritten, NULL);

    GlobalUnlock(hDib);
    CloseHandle(fh);

    if (dwWritten == 0)
        return ERR_OPEN; // oops, something happened in the write
    else
        return 0; // Success code
}

/*************************************************************************
 *
 * DestroyDIB ()
 *
 * Purpose:  Frees memory associated with a DIB
 *
 * Returns:  Nothing
 *
 *************************************************************************/

WORD DestroyDIB(HDIB hDib)
{
    GlobalFree(hDib);
    return 0;
}

//************************************************************************
//
// Auxiliary Functions which the above procedures use
//
//************************************************************************

/*************************************************************************
 *
 * Function:  ReadDIBFile (int)
 *
 *  Purpose:  Reads in the specified DIB file into a global chunk of
 *            memory.
 *
 *  Returns:  A handle to a dib (hDIB) if successful.
 *            NULL if an error occurs.
 *
 * Comments:  BITMAPFILEHEADER is stripped off of the DIB.  Everything
 *            from the end of the BITMAPFILEHEADER structure on is
 *            returned in the global memory handle.
 *
 *
 * NOTE: The DIB API were not written to handle OS/2 DIBs, so this
 * function will reject any file that is not a Windows DIB.
 *
 *************************************************************************/

HANDLE ReadDIBFile(HANDLE hFile)
{
    BITMAPFILEHEADER    bmfHeader;
    DWORD               dwBitsSize;
    UINT                nNumColors;   // Number of colors in table
    HANDLE              hDIB;
    HANDLE              hDIBtmp;      // Used for GlobalRealloc() //MPB
    LPBITMAPINFOHEADER  lpbi;
    DWORD               offBits;
    DWORD               dwRead;

    // get length of DIB in bytes for use when reading

    dwBitsSize = GetFileSize(hFile, NULL);

    // Allocate memory for header & color table. We'll enlarge this
    // memory as needed.

    hDIB = GlobalAlloc(GMEM_MOVEABLE, (DWORD)(sizeof(BITMAPINFOHEADER) +
            256 * sizeof(RGBQUAD)));

    if (!hDIB)
        return NULL;

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    if (!lpbi)
    {
        GlobalFree(hDIB);
        return NULL;
    }

    // read the BITMAPFILEHEADER from our file

    if (!ReadFile(hFile, (LPSTR)&bmfHeader, sizeof (BITMAPFILEHEADER),
            &dwRead, NULL))
        goto ErrExit;

    if (sizeof (BITMAPFILEHEADER) != dwRead)
        goto ErrExit;

    if (bmfHeader.bfType != 0x4d42)  // 'BM'
        goto ErrExit;

    // read the BITMAPINFOHEADER

    if (!ReadFile(hFile, (LPSTR)lpbi, sizeof(BITMAPINFOHEADER), &dwRead,
            NULL))
        goto ErrExit;

    if (sizeof(BITMAPINFOHEADER) != dwRead)
        goto ErrExit;

    // Check to see that it's a Windows DIB -- an OS/2 DIB would cause
    // strange problems with the rest of the DIB API since the fields
    // in the header are different and the color table entries are
    // smaller.
    //
    // If it's not a Windows DIB (e.g. if biSize is wrong), return NULL.

    if (lpbi->biSize == sizeof(BITMAPCOREHEADER))
        goto ErrExit;

    // Now determine the size of the color table and read it.  Since the
    // bitmap bits are offset in the file by bfOffBits, we need to do some
    // special processing here to make sure the bits directly follow
    // the color table (because that's the format we are susposed to pass
    // back)

    if (!(nNumColors = (UINT)lpbi->biClrUsed))
    {
        // no color table for 24-bit, default size otherwise

        if (lpbi->biBitCount != 24)
            nNumColors = 1 << lpbi->biBitCount; // standard size table
    }

    // fill in some default values if they are zero

    if (lpbi->biClrUsed == 0)
        lpbi->biClrUsed = nNumColors;

    if (lpbi->biSizeImage == 0)
    {
        lpbi->biSizeImage = ((((lpbi->biWidth * (DWORD)lpbi->biBitCount) +
                31) & ~31) >> 3) * lpbi->biHeight;
    }

    // get a proper-sized buffer for header, color table and bits

    GlobalUnlock(hDIB);
    hDIBtmp = GlobalReAlloc(hDIB, lpbi->biSize + nNumColors *
            sizeof(RGBQUAD) + lpbi->biSizeImage, 0);

    if (!hDIBtmp) // can't resize buffer for loading
        goto ErrExitNoUnlock; //MPB
    else
        hDIB = hDIBtmp;

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // read the color table

    ReadFile (hFile, (LPSTR)(lpbi) + lpbi->biSize,
            nNumColors * sizeof(RGBQUAD), &dwRead, NULL);

    // offset to the bits from start of DIB header

    offBits = lpbi->biSize + nNumColors * sizeof(RGBQUAD);

    // If the bfOffBits field is non-zero, then the bits might *not* be
    // directly following the color table in the file.  Use the value in
    // bfOffBits to seek the bits.

    if (bmfHeader.bfOffBits != 0L)
        SetFilePointer(hFile, bmfHeader.bfOffBits, NULL, FILE_BEGIN);

    if (ReadFile(hFile, (LPSTR)lpbi + offBits, lpbi->biSizeImage, &dwRead,
            NULL))
        goto OKExit;

ErrExit:
    GlobalUnlock(hDIB);

ErrExitNoUnlock:
    GlobalFree(hDIB);
    return NULL;

OKExit:
    GlobalUnlock(hDIB);
    return hDIB;
}


HOOK.C

//***********************************************************************
//
// hook.c
//
// Source file for Keyboard Hook function and support procedures.  Defines
// the following exported functions:
//
// InstallHook()       - Installs/Removes keyboard hook for app's hotkeys
// KeyboardHook()      - Keyboard Hook Procedure
//
// This file should go into a DLL.
//
// This file, although not officially containing DIB functions, is part
// of the DIBAPI.DLL because:
//   1) System-wide Hook Procedures in 3.1 need to be in a DLL, and
//   2) I didn't want the user to have 2 DLLs to worry about for WINCAP.
//
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//***********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include "wincap.h"

// Globals for this module

extern HWND     ghWndMain;      // Handle to main window -- used to post msgs

static HHOOK    hHook;          // A handle to our installed hook
static BOOL     bHookInstalled; // TRUE if hook has been installed

// External variables

extern HANDLE ghDLLInst;   // Handle to the DLL's instance.  Set in DllMain.

// Local function.  Although this function is exported from our DLL (Windows
// needs to call it directly), no other app needs to call this, so we can
// just prototype it here.

LRESULT CALLBACK KeyboardHook (int nCode, WORD wParam, DWORD lParam );

//**********************************************************************
// InstallHook()
//
// Installs/Removes Filter function for the WH_KEYBOARD hook.
//
// Parameters:
// HWND hWnd      Handle to main window to receive posted messages.  See
//                KeyboardHook() for more info on how it works.
//
// BOOL bCode     TRUE to hook, FALSE to unhook
//
// Returns:
// 1 if successful, 0 if not.
//
//**********************************************************************

int APIENTRY InstallHook (HWND hWnd, BOOL bCode )
{

    int nReturn = 1;

    ghWndMain = hWnd;  // Store app's window handle in DLL global variable

    // Make sure that we are installing/removing in the proper order

    if (bCode == bHookInstalled)
        return 0;

    if (bCode)
    {
        hHook = (HHOOK)SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyboardHook,
            ghDLLInst, 0);

        if (!hHook)
            return 0;

        bHookInstalled = TRUE;
    }
    else
    {
        nReturn = UnhookWindowsHookEx(hHook);
        bHookInstalled = FALSE;
    }
    return nReturn;
}

//**********************************************************************
//
// KeyboardHook()
//
// This is the Keyboard Hook function which windows will call every
// time it gets a keyboard message.  In this function, we check to
// see if the key pressed was Ctrl+Alt+F[8,9,10], and if it is, we post
// the proper message to our main window which will do the right
// thing.
//
// Note that the window handle that we post from was set by a call to
// InstallHook() above.
//
// Parameters/return value:
//
// Standard 3.1 KeyboardProc.  See docs for "KeyboardProc".
//
//*********************************************************************

LRESULT CALLBACK KeyboardHook (int nCode, WORD wParam, DWORD lParam )
{

    if ( nCode >= 0 )
    {

        // Check to see if it's a key we're looking for

        if (GetKeyState(VK_SHIFT) < 0 && GetKeyState(VK_CONTROL) < 0)
        {
            switch (wParam)
            {

//      MENUITEM    "&Active Window \aCtrl+Shift+F9",  IDM_ACTIVEWINDOW
//      MENUITEM    "&Desktop\aCtrl+Shift+F10",       IDM_DESKTOP

                case VK_F9:
                    if (HIWORD(lParam) & 0x8000)
                        PostMessage(ghWndMain, WM_COMMAND, IDM_ACTIVEWINDOW, 0L);
                    return 1;

                case VK_F10:

                    if (HIWORD(lParam) & 0x8000)
                        PostMessage(ghWndMain, WM_COMMAND, IDM_DESKTOP, 0L);
                    return 1;
            }
        }
   }

   //
   // If we haven't returned 1 by the time we get here, then we
   // need to pass on the message to CallNextHookEx.
   //
   return (int)CallNextHookEx(hHook, nCode, wParam, lParam);
}


PRINT.C

//********************************************************************
//
//  print.c
//
//  Source file for Device-Independent Bitmap (DIB) API.  Provides
//  the following functions:
//
//  PrintWindow()       - Prints all or part of a window
//  PrintScreen()       - Prints the entire screen
//  PrintDIB()          - Prints the specified DIB
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <string.h>
#include "dibdll.h"     // Header for printing dialog & DLL instance handle
#include "dibapi.h"     // Header for DIB functions
#include "dibutil.h"    // Auxiliary functions

extern HANDLE   ghDLLInst;      // Global handle to DLL's instance

/***************************************************************
 * Typedefs
 **************************************************************/

// Structure used for Banding

typedef struct
{
   BOOL bGraphics;
   BOOL bText;
   RECT GraphicsRect;
} BANDINFOSTRUCT;

/****************************************************************
 * Variables
 ***************************************************************/

HWND hDlgAbort;                    // Handle to Abort Dialog
char szPrintDlg[] = "PRINTING";    // Name of Print dialog from .RC
BOOL bAbort = FALSE;               // Abort a print operation?
char gszDevice[50];                // Keeps track out device (e.g. "HP LaserJet")
char gszOutput[50];                // Output device (e.g. "LPT1:")

/***************************************************************
 * Function prototypes for functions local to this module
 **************************************************************/

BOOL CALLBACK PrintAbortProc(HDC, int);
int CALLBACK PrintAbortDlg(HWND, UINT, WPARAM, LPARAM);
WORD PrintBand(HDC, LPRECT, LPRECT, BOOL, BOOL, LPBITMAPINFOHEADER, LPSTR);
HDC GetPrinterDC(void);
void CalculatePrintRect(HDC, LPRECT, WORD, DWORD, DWORD);

/**********************************************************************
 *
 * PrintWindow()
 *
 *
 * Description:
 *
 * This function prints the specified window on the default
 * printer.
 *
 * Parameters:
 *
 * HWND hWnd       - Specifies the window to print.  The window must
 *                   not be iconic and must be topmost on the display.
 *
 * WORD fPrintArea - Specifies the area of the window to print.  Must be
 *                   one of PW_ALL, PW_CLIENT, PW_CAPTION,  or PW_MENUBAR
 *
 * WORD fPrintOpt  - Print options (one of PW_BESTFIT, PW_STRETCHTOPAGE, or
 *                   PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors if PW_SCALE is specified
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 * Return Value:
 *      ERR_DIBFUNCTION or any return value from PrintDIB
 *
 **********************************************************************/

WORD PrintWindow(HWND hWnd, WORD fPrintArea, WORD fPrintOpt, WORD wXScale,
        WORD wYScale, LPSTR szJobName)
{
    HDIB    hDib;          // Handle to the DIB
    WORD    wReturn;       // our return value

    // Parameter validation

    if (!hWnd)
        return ERR_INVALIDHANDLE;  // Invalid Window

    // Copy the Window to a DIB and print it.

    hDib = CopyWindowToDIB(hWnd, fPrintArea);
    if (!hDib)
        return ERR_DIBFUNCTION; // CopyWindowToDIB failed!

    wReturn = PrintDIB(hDib, fPrintOpt, wXScale, wYScale, szJobName);

    // Call DestroyDIB to free the memory the dib takes up.

    DestroyDIB(hDib);
    return wReturn;   // return the value from PrintDIB
}

/**********************************************************************
 *
 * PrintScreen()
 *
 *
 * Description:
 *
 * This function prints the specified portion of the display screen on the
 * default printer using the print options specified.  The print
 * options are listed in dibapi.h.
 *
 * Parameters:
 *
 * LPRECT rRegion  - Specifies the region of the screen (in screen
 *                   coordinates) to print
 *
 * WORD fPrintOpt  - Print options  (PW_BESTFIT, PW_STRETCHTOPAGE, or PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors if PW_SCALE is specified
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 *
 * Return Value:
 *      ERR_DIBFUNCTION or any return value from PrintDIB
 *
 **********************************************************************/

WORD PrintScreen(LPRECT rRegion, WORD fPrintOpt, WORD wXScale, WORD wYScale,
        LPSTR szJobName)
{
    HDIB     hDib;          // A Handle to our DIB
    WORD     wReturn;       // Return value

    // Copy the screen contained in the specified rectangle to a DIB

    hDib = CopyScreenToDIB(rRegion);

    if (!hDib)
        return ERR_DIBFUNCTION;   // CopyScreenToDIB failed!

    wReturn = PrintDIB(hDib, fPrintOpt, wXScale, wYScale, szJobName);
    DestroyDIB(hDib);
    return wReturn; // Return the value that PrintDIB returned
}

/**********************************************************************
 *
 * PrintDIB()
 *
 * Description:
 *
 * This routine prints the specified DIB.  The actual printing is done
 * in the PrintBand() routine (see below), this procedure drives the
 * printing operation.  PrintDIB() has the code to handle both banding
 * and non-banding printers.  A banding printer can be distinguished by
 * the GetDeviceCaps() API (see the code below).  On banding devices,
 * must repeatedly call the NEXTBAND escape to get the next banding
 * rectangle to print into.  If the device supports the BANDINFO escape,
 * it should be used to determine whether the band "wants" text or
 * graphics (or both).  On non-banding devices, we can ignore all this
 * and call PrintBand() on the entire page.
 *
 * Parameters:
 *
 * HDIB hDib       - Handle to dib to be printed
 *
 * WORD fPrintOpt  - tells which print option to use (PW_BESTFIT,
 *                   PW_STRETCHTOPAGE, OR PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors (integers) for
 *                   printed output if the PW_SCALE option is used.
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 *
 * Return Value:  (see errors.h for description)
 *
 * One of: ERR_INVALIDHANDLE
 *         ERR_LOCK
 *         ERR_SETABORTPROC
 *         ERR_STARTDOC
 *         ERR_NEWFRAME
 *         ERR_ENDDOC
 *         ERR_GETDC
 *         ERR_STRETCHDIBITS
 *
 ********************************************************************/

WORD PrintDIB(HDIB hDib, WORD fPrintOpt, WORD wXScale, WORD wYScale,
        LPSTR szJobName)
{
    HDC                 hPrnDC;         // DC to the printer
    RECT                rect;           // Rect structure used for banding
    LPSTR               lpBits;         // pointer to the DIB bits
    LPBITMAPINFOHEADER  lpDIBHdr;       // Pointer to DIB header
    int                 nBandCount = 0; // used for print dialog box to count bands
    WORD                wErrorCode = 0; // Error code to return
    RECT                rPrintRect;     // specifies the area on the printer
                                        // (in printer coordinates) which we
                                        // want the DIB to go to
    char                szBuffer[70];   // Buffer to hold message for "Printing" dlg box
    char                szJobNameTrunc[35];     // szJobName truncated to 31
                                        // characters, since STARTDOC can't
                                        // accept a string longer than 31
    DOCINFO             DocInfo;        // structure for StartDoc
    int                 nTemp;          // used to check banding capability
    CHAR                lpBuffer[128];  // Buffer for strings retrieved from resources

    // Paramter validation

    if (!hDib)
        return ERR_INVALIDHANDLE;

    // Get pointer to DIB header

    lpDIBHdr = (LPBITMAPINFOHEADER)GlobalLock(hDib);
    if (!lpDIBHdr) // Check that we have a valid pointer
        return ERR_LOCK;
    lpBits = FindDIBBits((LPSTR)lpDIBHdr); // Find pointer to DIB bits

    if (hPrnDC = GetPrinterDC())
    {
        SetStretchBltMode(hPrnDC, COLORONCOLOR);

        // Determine rPrintRect (printer area to print to) from the
        // fPrintOpt.  Fill in rPrintRect.left and .top from wXScale and
        // wYScale just in case we use PW_SCALE (see the function
        // CalculatePrintRect).

        rPrintRect.left = wXScale;
        rPrintRect.top = wYScale;
        CalculatePrintRect(hPrnDC, &rPrintRect, fPrintOpt, lpDIBHdr->biWidth,
                         lpDIBHdr->biHeight);

        // Initialize the abort procedure.

        hDlgAbort = CreateDialog(ghDLLInst, szPrintDlg, GetFocus(),
                (DLGPROC)PrintAbortDlg);

        // ISet the text inside the dialog to the name of our print job

        lstrcpy(szJobNameTrunc, szJobName);
        szJobNameTrunc[31] = '\0';           // Truncate string to 31 chars
        LoadString(ghDLLInst, IDS_PRINTMSG, lpBuffer, sizeof(lpBuffer));
        wsprintf(szBuffer, lpBuffer, (LPSTR)szJobNameTrunc);
        SetDlgItemText(hDlgAbort, IDC_PRINTTEXT1, (LPSTR)szBuffer);

        // Set global variable bAbort to FALSE.  This will get set to TRUE
        // in our PrintAbortDlg() procedure if the user selects the
        // CANCEL button in our dialog box

        bAbort = FALSE;

        // set up the Abort Procedure

        if (SetAbortProc(hPrnDC, (ABORTPROC)PrintAbortProc) < 0)
                return ERR_SETABORTPROC;

        // start print job

        ZeroMemory(&DocInfo, sizeof(DOCINFO));
        DocInfo.cbSize = sizeof(DOCINFO);
        DocInfo.lpszDocName = (LPTSTR)szJobNameTrunc;
        DocInfo.lpszOutput = NULL;

        if (StartDoc(hPrnDC, &DocInfo) <= 0)
        {
            // Oops, something happened, let's clean up here and return

             DestroyWindow(hDlgAbort);   // Remove abort dialog box
             DeleteDC(hPrnDC);
             GlobalUnlock(hDib);
             return ERR_STARTDOC;
        }

// Note: the following banding code applies to Windows 3.1.  With the new
//       printing architecture of Win32, send out both the graphics and
//       text in one band (like a non-banding device).  This code is used
//       for Win32s since Win32s depends on Windows 3.1 printing architecture.
//
        // Check if need to do banding.  If we do, loop through
        // each band in the page, calling NEXTBAND and BANDINFO
        // (if supported) calling PrintBand() on the band.  Else,
        // call PrintBand() with the entire page as our clipping
        // rectangle!

        // If Wincap32 is running on Win32s, then use banding

        nTemp = NEXTBAND;
        if (Escape(hPrnDC, QUERYESCSUPPORT, sizeof(int), (LPSTR)&nTemp, NULL) &&
                (GetVersion() & 0x80000000) && (LOWORD(GetVersion()) == 3))
        {
            BOOL                bBandInfoDevice;
            BANDINFOSTRUCT      biBandInfo;         // Used for banding

            // Fill in initial values for our BandInfo Structure to
            // tell driver we can want to do graphics and text, and
            // also which area we want the graphics to go in.

            biBandInfo.bGraphics = TRUE;
            biBandInfo.bText = TRUE;
            biBandInfo.GraphicsRect = rPrintRect;

            // Check if device supports the BANDINFO escape.

            nTemp = BANDINFO;
            bBandInfoDevice = Escape(hPrnDC, QUERYESCSUPPORT, sizeof(int),
                    (LPSTR)&nTemp, NULL);

            // Do each band -- Call Escape() with NEXTBAND, then the
            // rect structure returned is the area where we are to
            // print in.  This loop exits when the rect area is empty.

            while (Escape(hPrnDC, NEXTBAND, 0, NULL, (LPSTR)&rect) && !
                IsRectEmpty(&rect))
            {
                char szTmpBuf[100];

                // Do the BANDINFO, if needed.

                if (bBandInfoDevice)
                    Escape(hPrnDC, BANDINFO, sizeof(BANDINFOSTRUCT), (LPSTR)&
                            biBandInfo, (LPSTR)&biBandInfo);
                LoadString(ghDLLInst, IDS_BANDNMBR, lpBuffer, sizeof(lpBuffer));
                wsprintf(szTmpBuf, lpBuffer, ++nBandCount);
                SetDlgItemText(hDlgAbort, IDC_PERCENTAGE, (LPSTR)szTmpBuf);

                // Call PrintBand() to do actual output into band.
                // Pass in our band-info flags to tell what sort
                // of data to output into the band.  Note that on
                // non-banding devices, we pass in the default bandinfo
                // stuff set above (i.e. bText=TRUE, bGraphics=TRUE).

                wErrorCode = PrintBand(hPrnDC, &rPrintRect, &rect,
                        biBandInfo.bText, biBandInfo.bGraphics, lpDIBHdr,
                        lpBits);
            }
        }
        else
        {
            // Print the whole page -- non-banding device.

            if (StartPage(hPrnDC) <= 0)
                return ERR_STARTPAGE;

            rect = rPrintRect;
            LoadString(ghDLLInst, IDS_SENDINGBAND, lpBuffer, sizeof(lpBuffer));
            SetDlgItemText(hDlgAbort, IDC_PERCENTAGE, lpBuffer);
            wErrorCode = PrintBand(hPrnDC, &rPrintRect, &rect, TRUE, TRUE,
                    lpDIBHdr, lpBits);

            // Non-banding devices need a NEWFRAME

            if (EndPage(hPrnDC) <= 0)
                return ERR_ENDPAGE;
        }

        // End the print operation.  Only send the ENDDOC if
        //  we didn't abort or error.

        if (!bAbort)
        {
            // We errored out on ENDDOC, but don't return here - we still
            // need to close the dialog box, free proc instances, etc.

            if (EndDoc(hPrnDC) <= 0)
                wErrorCode = ERR_ENDDOC;

            DestroyWindow(hDlgAbort);
        }

        // All done, clean up.

        DeleteDC(hPrnDC);
    }
    else
        wErrorCode = ERR_GETDC;   // Couldn't get Printer DC!

    GlobalUnlock(hDib);
    return wErrorCode;
}

// *******************************************************************
// Auxiliary Functions
//     -- Local to this module only
// *******************************************************************

/*********************************************************************
 *
 * CalculatePrintRect()
 *
 * Given fPrintOpt and a size of the DIB, return the area on the
 * printer where the image should go (in printer coordinates).  If
 * fPrintOpt is PW_SCALE, then lpPrintRect.left and .top should
 * contain WORDs which specify the scaling factor for the X and
 * Y directions, respecively.
 *
 ********************************************************************/

void CalculatePrintRect(HDC hDC, LPRECT lpPrintRect, WORD fPrintOpt,
        DWORD cxDIB, DWORD cyDIB)
{
    int  cxPage, cyPage, cxInch, cyInch;

    if (!hDC)
        return;

    // Get some info from printer driver

    cxPage = GetDeviceCaps(hDC, HORZRES);   // Width of printr page - pixels
    cyPage = GetDeviceCaps(hDC, VERTRES);   // Height of printr page - pixels
    cxInch = GetDeviceCaps(hDC, LOGPIXELSX);// Printer pixels per inch - X
    cyInch = GetDeviceCaps(hDC, LOGPIXELSY);// Printer pixels per inch - Y

    switch (fPrintOpt)
    {

        // Best Fit case -- create a rectangle which preserves
        // the DIB's aspect ratio, and fills the page horizontally.

        // The formula in the "->bottom" field below calculates the Y
        // position of the printed bitmap, based on the size of the
        // bitmap, the width of the page, and the relative size of
        // a printed pixel (cyInch / cxInch).

        case PW_BESTFIT:
            lpPrintRect->top = 0;
            lpPrintRect->left = 0;
            lpPrintRect->bottom = (int)(((double)cyDIB * cxPage * cyInch) /
                    ((double)cxDIB * cxInch));
            lpPrintRect->right = cxPage;
            break;

        // Scaling option -- lpPrintRect's top/left contain
        // multipliers to multiply the DIB's height/width by.

        case PW_SCALE:
        {
            int     cxMult, cyMult;

            cxMult = lpPrintRect->left;
            cyMult = lpPrintRect->top;
            lpPrintRect->top = 0;
            lpPrintRect->left = 0;
            lpPrintRect->bottom = (int)(cyDIB * cyMult);
            lpPrintRect->right = (int)(cxDIB * cxMult);
            break;
        }

        // Stretch To Page case -- create a rectangle
        // which covers the entire printing page (note that this
        // is also the default).

        case PW_STRETCHTOPAGE:

        default:
            lpPrintRect->top = 0;
            lpPrintRect->left = 0;
            lpPrintRect->bottom = cyPage;
            lpPrintRect->right = cxPage;
            break;
    }
}

/*********************************************************************
 *
 * PrintBand()
 *
 * This routine does ALL output to the printer.  It is called from
 * the PrintDIB() routine.  It is called for both banding and non-
 * banding printing devices.  lpRectClip contains the rectangular
 * area we should do our output into (i.e. we should clip our output
 * to this area).  The flags fDoText and fDoGraphics should be set
 * appropriately (if we want any text output to the rectangle, set
 * fDoText to true).  Normally these flags are returned on banding
 * devices which support the BANDINFO escape.
 *
 ********************************************************************/

WORD PrintBand(HDC hDC, LPRECT lpRectOut, LPRECT lpRectClip, BOOL fDoText,
        BOOL fDoGraphics, LPBITMAPINFOHEADER lpDIBHdr, LPSTR lpDIBBits)
{
    RECT    rect;           // Temporary rectangle
    double  dblXScaling,    // X and Y scaling factors
            dblYScaling;
    WORD    wReturn = 0;    // Return code

    if (fDoGraphics)
    {
        dblXScaling = ((double)lpRectOut->right - lpRectOut->left) / (double)
                lpDIBHdr->biWidth;
        dblYScaling = ((double)lpRectOut->bottom - lpRectOut->top) / (double)
                lpDIBHdr->biHeight;

        // Now we set up a temporary rectangle -- this rectangle
        // holds the coordinates on the paper where our bitmap
        // WILL be output.  We can intersect this rectangle with
        // the lpClipRect to see what we NEED to output to this
        // band.  Then, we determine the coordinates in the DIB
        // to which this rectangle corresponds (using dbl?Scaling).

        IntersectRect(&rect, lpRectOut, lpRectClip);
        if (!IsRectEmpty(&rect))
        {
            RECT    rectIn;

            rectIn.left = (int)((rect.left - lpRectOut->left) / dblXScaling +
                    0.5
                       );
            rectIn.top = (int)((rect.top - lpRectOut->top) / dblYScaling + 0.5);
            rectIn.right = (int)(rectIn.left + (rect.right - rect.left) /
                    dblXScaling + 0.5);
            rectIn.bottom = (int)(rectIn.top + (rect.bottom - rect.top) /
                    dblYScaling + 0.5);
            if (!StretchDIBits(hDC, rect.left, rect.top,
                    rect.right - rect.left, rect.bottom - rect.top,
                    rectIn.left, (int)(lpDIBHdr->biHeight) -
                    rectIn.top - (rectIn.bottom - rectIn.top),
                    rectIn.right - rectIn.left, rectIn.bottom - rectIn.top,
                    lpDIBBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS,
                    SRCCOPY))
                wReturn = ERR_STRETCHDIBITS; // StretchDIBits() failed!
        }
    }

    return wReturn;
}

/***********************************************************************
 *
 * GetPrinterDC()
 *
 * Uses PrinDlg common dialog for printer selection and creates a dc.
 * Returns NULL on error.
 *
 ***********************************************************************/

HDC GetPrinterDC() {

    PRINTDLG pd;

    ZeroMemory(&pd, sizeof(pd));

    pd.lStructSize = sizeof(PRINTDLG);
    pd.Flags = PD_RETURNDC;

    if (PrintDlg(&pd) == TRUE)
    {
        DEVNAMES    *pDevNames = GlobalLock(pd.hDevNames);

        lstrcpy((LPSTR)gszDevice,
                (LPSTR)((char *)pDevNames+pDevNames->wDeviceOffset));

        if(!lstrcmpi((LPSTR)((char *)pDevNames+pDevNames->wDeviceOffset),
                (LPSTR)((char *)pDevNames+pDevNames->wOutputOffset)))
            lstrcpy((LPSTR)gszOutput, "net:");
        else
            lstrcpy((LPSTR)gszOutput,
                    (LPSTR)((char *)pDevNames+pDevNames->wOutputOffset));

        GlobalUnlock(pd.hDevNames);
        return pd.hDC;
    }

    else
        return NULL;
}

/**********************************************************************
 * PrintAbortProc()
 *
 * Abort procedure - contains the message loop while printing is
 * in progress.  By using a PeekMessage() loop, multitasking
 * can occur during printing.
 *
 **********************************************************************/

BOOL CALLBACK PrintAbortProc(HDC hDC, int code)
{
    MSG  msg;

    while (!bAbort && PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        if (!IsDialogMessage(hDlgAbort, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

    return !bAbort;
}

/***********************************************************************
 *
 * PrintAbortDlg()
 *
 *
 * This is the Dialog Procedure which will handle the "Now Printing"
 * dialog box.  When the user presses the "Cancel" button, the
 * global variable bAbort is set to TRUE, which causes the
 * PrintAbortProc to exit, which in turn causes the printing
 * operation to terminate.
 *
 ***********************************************************************/

int CALLBACK PrintAbortDlg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_INITDIALOG:
        {
            char szBuffer[100];
            CHAR msgBuffer[128];

            // Fill in the text which specifies where this bitmap
            // is going ("on HP LaserJet on LPT1", for example)
            LoadString(ghDLLInst, IDS_ABORTSTRING, msgBuffer, sizeof(msgBuffer));
            wsprintf(szBuffer, msgBuffer, (LPSTR)gszDevice,
                    (LPSTR)gszOutput);
            SetDlgItemText(hWnd, IDC_PRINTTEXT2, (LPSTR)szBuffer);
            SetFocus(GetDlgItem(hWnd, IDCANCEL));
            return TRUE;     // Return TRUE because we called SetFocus()
        }

        case WM_COMMAND:
            bAbort = TRUE;
            DestroyWindow(hWnd);
            return TRUE;
            break;
    }

    return FALSE;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by WINCAP.RC
//
#define IDS_RECT2                       2
#define IDS_RECT3                       3
#define IDS_SELECT2                     5
#define IDS_SELECT3                     6
#define IDS_MAINWINDOWTITLE7
#define IDS_WINCAPRUNNING               8
#define IDS_NOIMAGE                     9
#define IDS_VWFULLSCRN                  10
#define IDS_SAVEBMP                     11
#define IDS_CAPTEDALREADY               12
#define IDS_CANTSAVE                    13
#define IDS_SAVEERROR                   14
#define IDS_ENTIRESCRN                  15
#define IDS_USERSELECT                  16
#define IDS_CANTCAPWNDW                 17
#define IDS_WNDWNOTVIS                  18
#define IDS_INTERROR                    19
#define IDS_SAVEFILTER                  20
#define IDD_SELECT                      102
#define IDC_STATIC_RECT2                1002
#define IDC_STATIC_RECT3                1003
#define IDC_STATIC_SELECT2              1004
#define IDC_STATIC_SELECT3              1005
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         101
#define _APS_NEXT_CONTROL_VALUE         1006
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


WEPCODE.C

// -----------------------------------------------------------------
// File name:  WEPCODE.C
//
// This is the source file that contains the DLL's Windows Exit
// Procedure (WEP).
//
// Description of functions:
//
//    WEP         -     The Windows Exit Procedure.  Every DLL must have
//                      one of these.
//
//
// Written by Microsoft Product Support Services, Windows Developer Support.
//
// Copyright 1991-1998 Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------

#define     STRICT      // enable strict type checking

#include <windows.h>

// -----------------------------------------------------------------
//
// WEP()
//
// Purpose:  Called by Windows just before Windows unloads the DLL.
//
// Parameters:  int  nParameter     -  This is a dummy parameter.
//                                     Windows expects this parameter
//                                     so be sure to include it.
//
// Return Value:  Always returns 1 to indicate success.
//
// Comments:  Every DLL MUST have a WEP.  WEP, as defined here, is the
//            proper way to define a WEP; do not do anything else
//            except return 1 because the WEP is called on the Windows
//            Kernel's stack, which is very small.
//
// -----------------------------------------------------------------

int APIENTRY WEP (int nParameter)
{
    return (1);
}


WINCAP.C

//***********************************************************************
//
// Wincap.c
//
// Windows Screen Capture Utility
// Version 3.20
//
// Description:
// ------------
//
// Captures portions of the screen, specific windows, or the entire screen
// and saves it to a file or prints it.  Uses DIBAPI functions to do most
// of the capture/printing/saving work.  See the file DIBAPI.TXT for a
// description of the DIB api functions.
//
// Changes from first version:
//
// - Updated to use 3.1 Common Dialogs and 3.1 hook functions
// - New user interface displays window which was captured in client
//      area of Wincap, complete with scroll bars
// - Hot new hotkeys allow more versatile capturing of windows (e.g. you
//      can now capture windows with menu items pulled down)
// - New APIs to draw bitmaps and DIBs on the screen
// - Nifty startup bitmap
// - All DIB API functions are now in a DLL, which can be used
//      by any application
//
// Changes from version 3.1 to 3.2:
// - Window selection changed due to how SetCapture works under Win32
// - The window to be selected for capture is highlighted as the cursor is moved
// - Option to copy DIB, DDB, and Palette to the clipboard
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright 1991-1998 Microsoft Corporation. All rights reserved.
//***********************************************************************

#define STRICT
#include <windows.h>
#include <string.h>
#include "commdlg.h"
#include "wincap.h"
#include "resource.h"
#include "dialogs.h"
#include "dibapi.h"

char szAppName[20];     // Name of application - used in dialog boxes

// Global variables

HINSTANCE   ghInst;             // Handle to instance
HWND        ghWndMain;          // Handle to main window

HOOKPROC    lpfnKeyHook;        // Used in keyboard hook
HOOKPROC    lpfnOldHook;        // Used for keyboard hook
HWND        hModelessDlg;       // Handle to modeless "Saving to file..."
                                // dialog box

BOOL        bStartup=TRUE;      // Startup flag for WM_PAINT/logo
BOOL        bViewFull=FALSE;    // Full view flag
HBITMAP     ghbmLogo;           // Handle to logo bitmap
HBITMAP     ghBitmap=NULL;      // Handle to captured bitmap
HPALETTE    ghPal=NULL;         // Handle to our bitmap's palette
char        gszWindowText[100]; // Text which tells what we captured

BOOL        gbLButtonDown=FALSE;
BOOL        gbSave = FALSE;
HWND        ghWndCapture=NULL;
BOOL        gbNowCapturing = FALSE;
BOOL        gbCaptRect = FALSE;
HWND        hSelectDlg;         // help dialog for window selection
HWND        hRectangleDlg;      // help dialog for rectangle selection
UINT        guiFileOKMsg;       // for common dialog FILEOKSTRING

#define WM_DOCAPTURE WM_USER+101 // Used for screen capture messages

// Macro to swap two values

#define SWAP(x,y)   ((x)^=(y)^=(x)^=(y))
#define SCROLL_RATIO    4

BOOL CenterWindow (HWND hwndChild, HWND hwndParent);

//************************************************************************
//
// WinMain()
//
// Entry point of the Application.
//
//************************************************************************

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine,
        int nCmdShow)
{
    MSG msg;
    WNDCLASS wndclass;
    HWND hWnd;
CHAR lpBuffer[256];

    strcpy(szAppName, "WinCap");     // Name of our App
    hModelessDlg = NULL;             // Set handle to modeless dialog to NULL
                                     // because we haven't created it yet

    hSelectDlg = hRectangleDlg = NULL;

LoadString(ghInst, IDS_MAINWINDOWTITLE, lpBuffer, sizeof(lpBuffer));
    if (!FindWindow(szAppName, lpBuffer))
    {
        wndclass.style = 0;
        wndclass.lpfnWndProc = (WNDPROC)WndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = LoadIcon(hInstance, "WINCAP");
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

        // Use black for background for better contrast

        wndclass.hbrBackground = GetStockObject(GRAY_BRUSH);

        wndclass.lpszMenuName = (LPSTR)"MAINMENU";
        wndclass.lpszClassName = (LPSTR)szAppName;

        if (!RegisterClass(&wndclass))
            return FALSE;

        ghInst = hInstance;  // Set Global variable

        // Create a main window for this application instance.

        hWnd = CreateWindow(szAppName, lpBuffer, WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT, 450, 345, NULL, NULL, hInstance,            // This instance owns this window
                NULL);

        ghWndMain = hWnd;      // Set global variable

        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);

        // Set up the Keyboard hook for our hotkeys

        InstallHook(hWnd, TRUE);  // Function resides in DIBAPI32.DLL

        // Create our full-screen view class

        wndclass.style = 0;
        wndclass.lpfnWndProc = (WNDPROC)FullViewWndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInstance;
        wndclass.hIcon = NULL;
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = GetStockObject(GRAY_BRUSH);
        wndclass.lpszMenuName = (LPSTR)NULL;
        wndclass.lpszClassName = (LPSTR)"ViewClass";

        if (!RegisterClass(&wndclass))
            return FALSE;

    }

    // Let's make this a single-instance app -- we can get into hotkey
    // conflicts (e.g. windows won't know which instance of WINCAP to
    // send the message to).

    else
    {
        LoadString(ghInst, IDS_WINCAPRUNNING, lpBuffer, sizeof(lpBuffer));
MessageBeep(0);
        MessageBox(NULL, lpBuffer, szAppName, MB_OK | MB_ICONHAND);
        return FALSE;
    }

    // Polling messages from event queue -- we have a modeless dialog
    // box, so we have to take care of the messages here also

    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (hModelessDlg == NULL || !IsDialogMessage(hModelessDlg, &msg) ||
                hSelectDlg || !IsDialogMessage(hSelectDlg, &msg) ||
                hRectangleDlg || !IsDialogMessage(hRectangleDlg, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return msg.wParam;
}

//**********************************************************************
//
// WndProc()
//
// This is our main window procedure.  It receives all the messages destined
// for our application's main window.
//
// When we capture a window, we capture it into a Device-Dependent bitmap,
// and at the same time, we get a copy of the current system palette.  This
// makes displaying the bitmap on the screen very fast.  And when we want
// to print or save the captured window, we need to use this palette to
// convert the DDB to a DIB.
//
//*********************************************************************

long WINAPI WndProc(HWND hWnd, UINT wMessage, WPARAM wParam, LPARAM lParam)
{

    // The gbNowCapturing variable is set to TRUE if we are in the middle of
    // printing.  This takes care of the case when the user presses the hotkey
    // during capturing

    static BOOL     bCapturedYet = FALSE;  // TRUE if window contains captured screen
    HWND            hViewWnd;              // Handle to our view window
    static WORD     wCaptureCommand;
    static HCURSOR  hOldCursor;
    static HWND     hBrowseWnd;
    CHAR            lpBuffer[128];
    CHAR            lpBuffer2[128];

    switch (wMessage)
    {

        case WM_CREATE:
            if (!hSelectDlg)
                hSelectDlg = CreateDialog(ghInst, "Select", hWnd,
                        SelectDlgProc);

            if (!hRectangleDlg)
                hRectangleDlg = CreateDialog(ghInst, "Rectangle", hWnd,
                        RectangleDlgProc);

            ghbmLogo = LoadBitmap(ghInst, "STARTBMP");
            guiFileOKMsg = RegisterWindowMessage( (LPCSTR)FILEOKSTRING );
            break;

        // Gray out menu items if we haven't captured anything yet.

        case WM_INITMENU:
            EnableMenuItem(GetMenu(hWnd), IDM_SAVE, MF_BYCOMMAND |
                    (bCapturedYet ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
            EnableMenuItem(GetMenu(hWnd), IDM_PRINT, MF_BYCOMMAND |
                    (bCapturedYet ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
            EnableMenuItem(GetMenu(hWnd), IDM_EDITCOPY, MF_BYCOMMAND |
                    (bCapturedYet ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
            return 0;

        // The WM_PALETTECHANGED message informs all windows that the window
        // with input focus has realized its logical palette, thereby changing
        // the system palette. This message allows a window without input focus
        // that uses a color palette to realize its logical palettes and update
        // its client area.
        //
        // This message is sent to all windows, including the one that changed
        // the system palette and caused this message to be sent. The wParam of
        // this message contains the handle of the window that caused the system
        // palette to change. To avoid an infinite loop, care must be taken to
        // check that the wParam of this message does not match the window's
        // handle.

        case WM_PALETTECHANGED:
        {
            HDC         hDC;      // Handle to device context
            HPALETTE    hOldPal;  // Handle to previous logical palette

            // Before processing this message, make sure we
            // are indeed using a palette

            if (ghPal)
            {
                // If this application did not change the palette, select
                // and realize this application's palette

                if (wParam != (WPARAM)hWnd)
                {
                    // Need the window's DC for SelectPalette/RealizePalette

                    hDC = GetDC(hWnd);

                    // Select and realize our palette

                    hOldPal = SelectPalette(hDC, ghPal, FALSE);
                    RealizePalette(hDC);

                    // WHen updating the colors for an inactive window,
                    // UpdateColors can be called because it is faster than
                    // redrawing the client area (even though the results are
                    // not as good)

                    UpdateColors(hDC);

                    // Clean up

                    if (hOldPal)
                       SelectPalette(hDC, hOldPal, FALSE);

                    ReleaseDC(hWnd, hDC);
                }
            }
            break;
        }

        // The WM_QUERYNEWPALETTE message informs a window that it is about to
        // receive input focus. In response, the window receiving focus should
        // realize its palette as a foreground palette and update its client
        // area. If the window realizes its palette, it should return TRUE;
        // otherwise, it should return FALSE.

        case WM_QUERYNEWPALETTE:
        {
            HDC         hDC;      // Handle to device context
            HPALETTE    hOldPal;  // Handle to previous logical palette

            // Before processing this message, make sure we
            // are indeed using a palette

            if (ghPal)
            {
                 // Need the window's DC for SelectPalette/RealizePalette

                 hDC = GetDC(hWnd);

                 // Select and realize our palette

                 hOldPal = SelectPalette(hDC, ghPal, FALSE);
                 RealizePalette(hDC);

                 // Redraw the entire client area

                 InvalidateRect(hWnd, NULL, TRUE);
                 UpdateWindow(hWnd);

                 // Clean up

                 if (hOldPal)
                    SelectPalette(hDC, hOldPal, FALSE);

                 ReleaseDC(hWnd, hDC);

                 // Message processed, return TRUE

                 return TRUE;
             }

            // Message not processed, return FALSE

            return FALSE;
        }

        case WM_MOVE:
        {
            if (gbNowCapturing)
            {
                if (gbCaptRect)
                    CenterWindow(hRectangleDlg, hWnd);
                else
                    CenterWindow(hSelectDlg, hWnd);
            }

            return 0;
        }

        case WM_SIZE:
        {
            static BOOL     bSizing=FALSE;

            if (gbNowCapturing)
            {
                if (gbCaptRect)
                    CenterWindow(hRectangleDlg, hWnd);
                else
                    CenterWindow(hSelectDlg, hWnd);
            }

            // Check if we are already sizing

            if (bSizing)
                return 0l;

            bSizing = TRUE;
            DoSize(hWnd);
            bSizing = FALSE;
            break;
        }

        case WM_HSCROLL:
        case WM_VSCROLL:
            DoScroll(hWnd, wMessage, (int)HIWORD(wParam), (int)LOWORD(wParam));
            break;

        case WM_PAINT:
            DoPaint(hWnd);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDM_ABOUT:
                    DialogBox(ghInst, (LPSTR)"About", hWnd, AboutDlgProc);
                    break;

                case IDM_SAVE:
                    SaveMe();
                    break;

                case IDM_PRINT:
                    PrintMe();
                    break;

                case IDM_VIEWFULL:
                    if (!bViewFull && (ghBitmap || bStartup))
                    {
                        HDC     hDC;
                        int ScreenX, ScreenY;

                        hDC = CreateDC("DISPLAY", NULL, NULL, NULL);

                        ScreenX = GetDeviceCaps(hDC, HORZRES);

                        ScreenY = GetDeviceCaps(hDC, VERTRES);
                        DeleteDC(hDC);

                        hViewWnd = CreateWindow((LPSTR)"ViewClass", (LPSTR)NULL,
                                WS_POPUP | WS_VISIBLE, 0, 0, ScreenX, ScreenY,
                                hWnd, NULL, ghInst, NULL);

                        ShowWindow(hViewWnd, SW_SHOW);
                        UpdateWindow(hViewWnd);
                    }
                    else {
                        LoadString(ghInst, IDS_NOIMAGE, lpBuffer, sizeof(lpBuffer));
                        LoadString(ghInst, IDS_VWFULLSCRN, lpBuffer2, sizeof(lpBuffer2));
                        MessageBox(hWnd, lpBuffer, lpBuffer2, MB_OK);
                    }
                    break;

                case IDM_EDITCOPY: // Copy DIB, bitmap, & palette to clipboard
                    if (OpenClipboard(hWnd))
                    {
                        HDIB        hDib;
                        HBITMAP     hBitmap;
                        HPALETTE    hPal;

                        EmptyClipboard();

                        if (ghBitmap)
                        {
                            // Once these are added to the clipboard, the
                            // clipboard owns the data.

                            if (hDib = BitmapToDIB(ghBitmap, ghPal))
                                SetClipboardData(CF_DIB, hDib);

                            // Make a copy of the bitmap so the clipboard can
                            // own it.

                            if (hBitmap = DIBToBitmap(hDib, ghPal))
                                SetClipboardData(CF_BITMAP, hBitmap);

                            // Make a copy of the palette so the clipboard can
                            // own it.

                            if (hPal = GetStockObject(DEFAULT_PALETTE))
                                SetClipboardData(CF_PALETTE, hPal);
                        }

                        CloseClipboard();
                    }
                    break;

                case IDM_VIEWCLEAR:
                {
                    WORD        wRet;

                    if (gbSave)
                    {
                        LoadString(ghInst, IDS_SAVEBMP, lpBuffer, sizeof(lpBuffer));
                        wRet = MessageBox(hWnd, lpBuffer,
                                szAppName, MB_ICONEXCLAMATION | MB_YESNOCANCEL);

                        if (wRet == IDYES)
                            SaveMe();
                        else if( wRet == IDCANCEL)
                            break;
                    }

                    gbSave = FALSE;

                    // If we are just displaying logo, don't
                    // display it anymore

                    if (bStartup)
                        bStartup = FALSE;

                    // Delete captured bitmap if we have one

                    if (ghBitmap)
                    {
                        DeleteObject(ghBitmap);
                        ghBitmap = NULL;
                    }

                    // Delete our captured bitmap's palette if we have one

                    if (ghPal)
                    {
                        DeleteObject(ghPal);
                        ghPal = NULL;
                    }

                    // Now update display to reflect fact that we
                    // nuked the captured bitmap or don't want to
                    // look at the cool logo

                    InvalidateRect(hWnd, NULL, TRUE);
                    UpdateWindow(hWnd);

                    bCapturedYet = FALSE;  // Gray out "File.Save" menu item
                    break;
                }

                case IDM_CAPTRECT:
                    gbCaptRect = TRUE;

                case IDM_CAPTWINDOW:  // make sure lParam == IDM_CURSORSELECT
                case IDM_CAPTCLIENT:  // make sure lParam == IDM_CURSORSELECT
                    lParam = IDM_CURSORSELECT;

                case IDM_ACTIVEWINDOW:
                case IDM_DESKTOP:

                // User selected one of the window capture items

                // Check to see that we aren't already in the middle of
                // capturing.  This could happen if the user presses our hotkey
                // in the middle of one of our dialog boxes.

                if (gbNowCapturing)
                {
                    LoadString(ghInst, IDS_CAPTEDALREADY, lpBuffer, sizeof(lpBuffer));
                    MessageBox(NULL, lpBuffer,
                            szAppName, MB_OK | MB_ICONEXCLAMATION);
                }
                else
                {
                    // User wants to capture screen.  One problem we may
                    // run into here is that we might have a popup menu pulled
                    // down in our own application when we get to this point
                    // (because of the hotkey feature of this app).
                    //
                    // Normally, we'd just enter a message loop after calling
                    // ShowWindow(SW_HIDE) to take care of any menu messages
                    // which may have been posted as we hide our application's
                    // window, and *then* call our screen capture routine.
                    // But unfortunately, we can't do that here and be 100% safe.
                    //
                    // If we *have* been sent here on a hotkey, and a menu of
                    // our own app is currently down, then we are currently running
                    // inside of a PeekMessage() loop in the Windows Menu
                    // manager. We should *not* enter another PeekMessage loop,
                    // but should return from this message case right away.
                    // The Windows Menu manager code relies on checking messages
                    // in it's queue via it's own PeekMessage loop, and if we
                    // entered one, it would confuse the menu manager.
                    //
                    // So what we do instead is just post ourselves a private
                    // message, and when we get this message (see below), then
                    // do the screen capture.
                    //

                    // Commence screen capture!

                    gbNowCapturing = TRUE;

                    if (wParam == IDM_DESKTOP)
                        ShowWindow(hWnd, SW_HIDE);

                    // Allow this message case to return right away.  We'll
                    // capture screen down below.

                    if (lParam != IDM_CURSORSELECT)
                        PostMessage(hWnd, WM_DOCAPTURE, wParam, 0L);
                    else if (gbCaptRect)
                    {
                        CenterWindow(hRectangleDlg, hWnd);
                        ShowWindow(hRectangleDlg, SW_SHOW);
                    }
                    else
                    {
                        CenterWindow(hSelectDlg, hWnd);
                        ShowWindow(hSelectDlg, SW_SHOW);
                    }

                    wCaptureCommand = (WORD)wParam;
                }
                break;

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                default:
                    return DefWindowProc(hWnd, wMessage, wParam, lParam);
                    break;
            } //switch (LOWORD(wParam))

            break; // case WM_COMMAND:

        // Message case for doing screen capture.  This message is posted
        // to us from the IDM_CAPT* code above.  wParam should be equal to
        // the ID of the message which we got sent here for (it is used in
        // the call to DoCapture()).

        case WM_DOCAPTURE:
        {
            // We're going to capture a new screen, get rid of
            // previous captured image

            if (ghBitmap)
            {
                DeleteObject(ghBitmap);
                ghBitmap = NULL;
            }

            if (ghPal)
            {
                DeleteObject(ghPal);
                ghPal = NULL;
            }

            SetForegroundWindow(ghWndCapture);

            // Save captured screen as bitmap

            DoCapture(hWnd, (WORD)wParam);

            SetForegroundWindow(hWnd);

            // Un-hide Window

            ShowWindow(hWnd, SW_SHOW);

            if (IsIconic(hWnd))
                PostMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE,  0L);

            if (!gbCaptRect)
                gbNowCapturing = FALSE;

            bCapturedYet = TRUE; // Enable "File.Save" menu item
            gbSave = TRUE;
            break;
        }

        case WM_LBUTTONDOWN:
        {

            if (!gbNowCapturing)
                break;

            ShowWindow(hWnd, SW_HIDE);  // Hide main app's window
            if (gbCaptRect)
                ShowWindow(hRectangleDlg, SW_HIDE);
            else
                ShowWindow(hSelectDlg, SW_HIDE);
            if (gbCaptRect)
            {
                hOldCursor = SetCursor(LoadCursor(NULL, IDC_CROSS));
                PostMessage(hWnd, WM_DOCAPTURE, wCaptureCommand, 0L);
            }
            else
                hOldCursor = SetCursor(LoadCursor(ghInst, "SELECT"));
            gbLButtonDown = TRUE;
            SetCapture(ghWndMain);
            break;
        }

        case WM_LBUTTONUP:
        {
            POINTS      pts;
            POINT       pt;

            if (!(gbNowCapturing && gbLButtonDown))
                break;

            ReleaseCapture();
            gbLButtonDown = FALSE;
            SetCursor(hOldCursor);

            if (gbCaptRect)
            {
                gbCaptRect = FALSE;
                gbNowCapturing = FALSE;
                break;
            }

            FrameWindow(hBrowseWnd);
            hBrowseWnd = NULL;

            pts = MAKEPOINTS(lParam);
            pt.x = pts.x;
            pt.y = pts.y;

            // Convert to screen coordinates

            ClientToScreen(ghWndMain, &pt);

            // Get Window that we clicked on

            ghWndCapture = WindowFromPoint(pt);

            // If it's not a valid window, just return NULL

            if (!ghWndCapture)
            {
               SetCursor(hOldCursor);
               gbLButtonDown = gbNowCapturing = FALSE;
               break;
            }

            PostMessage(hWnd, WM_DOCAPTURE, wCaptureCommand, 0L);
            break;

        }

        case WM_MOUSEMOVE:
        {
            POINT       pt;
            HWND        hWndCurrent;

            if (!(gbNowCapturing && gbLButtonDown))
                break;

            if (gbCaptRect)
                break;

            // get the current mouse position

            GetCursorPos(&pt);

            // Get Window that we clicked on

            hWndCurrent = WindowFromPoint(pt);

            // if the cursor is still moving in the current window, no need
            // to highlight it again

            if (hWndCurrent != hBrowseWnd)
            {
                FrameWindow(hBrowseWnd);
                FrameWindow(hWndCurrent);
                hBrowseWnd = hWndCurrent;
                if (hWndCurrent)
                    UpdateWindow(hWndCurrent);
            }

            break;
        }

        case WM_CLOSE:
        {
            WORD        wRet;

            if (gbSave)
            {
                LoadString(ghInst, IDS_SAVEBMP, lpBuffer, sizeof(lpBuffer));
                wRet = MessageBox(hWnd, lpBuffer,
                        szAppName, MB_ICONEXCLAMATION | MB_YESNOCANCEL);

                if (wRet == IDYES)
                    SaveMe();
                else if( wRet == IDCANCEL)
                    break;
            }

            if (IsWindow(hSelectDlg))
                DestroyWindow(hSelectDlg);
            if (IsWindow(hRectangleDlg))
                DestroyWindow(hRectangleDlg);

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

        case WM_DESTROY:  // Clean up
            InstallHook(hWnd, FALSE);  // Remove keyboard hook
            DeleteObject(ghbmLogo);

            if (ghBitmap)
                DeleteObject(ghBitmap);
            if (ghPal)
                DeleteObject(ghPal);

            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, wMessage, wParam, lParam);
    }
    return 0L;
}

//**********************************************************************
//
// FullViewWndProc()
//
// This is our full-screen popup window procedure. It is used to display an
// image using the entire screen. Clicking the left mouse button restores
// to the main app window.
//
//
//*********************************************************************

long APIENTRY FullViewWndProc(HWND hWnd, UINT wMessage, WPARAM wParam,
        LPARAM  lParam)
{
    switch (wMessage)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT     ps;
            HDC             hMemDC;
            BITMAP          bm;
            HBITMAP         hOldBm;
            RECT            rect, rectClient;
            int             x, y;

            BeginPaint(hWnd, &ps);

            // Check to see if we are displaying a bitmap

            if (!ghBitmap)
            {

                // No bitmap yet, are we in start mode?

                if (bStartup)
                {
                    GetClientRect(hWnd, &rectClient);

                    hMemDC = CreateCompatibleDC(ps.hdc);

                    // Select our logo bitmap

                    hOldBm = SelectObject(hMemDC, ghbmLogo);
                    GetObject(ghbmLogo, sizeof(BITMAP), (VOID *)&bm);

                    x = (rectClient.right - bm.bmWidth) / 2;
                    y = (rectClient.bottom - bm.bmHeight) / 2;

                    // Now bitblt our logo to client area
                    BitBlt(ps.hdc, x, y, bm.bmWidth, bm.bmHeight,
                          hMemDC, 0, 0, SRCCOPY);

                    // Clean up
                    SelectObject(hMemDC,hOldBm);
                    DeleteDC(hMemDC);
                }
            }
            else
            {
                // Get info for captured bitmap

                GetObject(ghBitmap, sizeof(BITMAP), (LPSTR)&bm);

                // Fill in src/dest rectangle with width and height

// of captured bitmap

                rect.left = 0;
                rect.top = 0;
                rect.right = bm.bmWidth;
                rect.bottom = bm.bmHeight;

                // Paint the captured bitmap in the client area

                PaintBitmap(ps.hdc, &rect, ghBitmap, &rect, ghPal);
            }

            EndPaint(hWnd, &ps);
            break;
         }

        case WM_KEYDOWN:
        case WM_LBUTTONDOWN:
            DestroyWindow(hWnd);
            break;

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

//**********************************************************************
//
// SaveMe()
//
// This procedure calls up the common dialog "File.Save" box, then
// saves the current hBitmap as a DIB in the specified file, in the
// file format specified by the user in the dialog.
//
//*********************************************************************

void SaveMe()
{
    char    szFileBuf[255];     // Buffer to hold returned file name
    DWORD   dwFlags;            // used to pass in / get out file type
    HDC     hDC;                // HDC for getting info
    int     iBits;              // Bits per pixel of the display adapter
    DWORD   dCompression;       // Compression that the user specifies
    WORD    wBitCount;          // Bits per pixel that the user specifies
    WORD    wCompression;       // Compression
    HDIB    hDIB;               // A handle to our dib
    BOOL    bResult;            // Result of dialog box - TRUE if OK was pressed
    CHAR    lpBuffer[128];      // Buffer to hold string message retrieved from resources
    CHAR    lpBuffer2[128];     // Buffer to hold string message retrieved from resources

    // Set up default compression to display in dialog box

    wCompression = IDD_RGB;

    // Depending on bits/pixel type for current display,
    // set the appropriate bit in the fFileOptions flag
    //
    // NOTE that we don't just assign wBitCount to iBits.  The reason
    // for this is some displays aren't 1,4,8 or 24 bits.  Some are
    // 15 bits, which isn't valid for a DIB, so in this case, we would
    // set the bits to 24.

    hDC = CreateDC("DISPLAY",NULL,NULL,NULL);
    iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
    DeleteDC(hDC);

    if (iBits <= 1)
        wBitCount = 1;

    else if (iBits <= 4)
        wBitCount = 4;

    else if (iBits <= 8)
        wBitCount = 8;

    else if (iBits <= 24)
        wBitCount = 24;

    // Our dwFlags parameter to GetFileName is made up of the
    // bits per pixel in the HIWORD (1, 2, 4, or 24), and the compression
    // type in the LOWORD (IDD_RGB, IDD_RLE4, or IDD_RLE8).

    dwFlags = MAKELONG(wCompression, wBitCount);

    // Bring up File/Save... dialog and get info. about filename,
    // compression, and bits/pix. of DIB to be written.

    bResult = GetFileName(ghWndMain, (LPSTR)szFileBuf, &dwFlags);

    // Extract DIB specs and save to file (if the user did not
    // press cancel)

    if (bResult)
    {
        switch(LOWORD(dwFlags))
        {
            case IDD_RLE4:
                dCompression = BI_RLE4;
                break;

            case IDD_RLE8:
                dCompression = BI_RLE8;
                break;

            case IDD_RGB:
            default:
                dCompression = BI_RGB;
                break;
        }

        // First, call up a modeless dialog box which tells that we are
        // saving this to a file...

        if (!hModelessDlg)
        {
            hModelessDlg = CreateDialogParam(ghInst, (LPSTR)"Saving",
                    ghWndMain,SavingDlgProc, (DWORD)(LPSTR)szFileBuf);
        }

        // Now, write out the DIB in the proper format.  The following
        // API ChangeBitmapFormat() will convert the specified bitmap
        // to a DIB with the specified Bit Count, Compression and
        // Palette.  Remember that the HIWORD of dwFlags specifies the
        // bits per pixel.

        hDIB = ChangeBitmapFormat(ghBitmap, HIWORD(dwFlags),
                dCompression, ghPal);

        if (hDIB)
        {
            if (SaveDIB(hDIB, szFileBuf))
            {
                LoadString(ghInst, IDS_CANTSAVE, lpBuffer, sizeof(lpBuffer));
                LoadString(ghInst, IDS_SAVEERROR, lpBuffer2, sizeof(lpBuffer2));
                MessageBox(NULL, lpBuffer, lpBuffer2, MB_ICONEXCLAMATION);
                gbSave = TRUE;
            }
            else
                gbSave = FALSE;

            DestroyDIB(hDIB);
        }

        DestroyWindow(hModelessDlg);
        hModelessDlg = NULL;
    }

}

//*********************************************************************
//
// PrintMe()
//
// This procedure calls up the "File.Print" dialog, then prints the
// current hBitmap as a DIB on the default printer.
//
//*********************************************************************

void PrintMe()
{
    static OPTIONSTRUCT opts;
    int                 iReturn;
    HDIB                hDIB;
    WORD                wReturn;

    // Display "Print Options" Box

    iReturn = DialogBoxParam(ghInst, (LPSTR)"Print", ghWndMain, PrintDlgProc,
            (LONG)(LPSTR)&opts);

    if (iReturn)
    {
        // User pressed "OK" -- do the printing

        hDIB = BitmapToDIB(ghBitmap, ghPal);

        if (hDIB)
        {

            // Print the dib using PrintDIB() API

            if (opts.iOption == IDC_STRETCHTOPAGE)
                wReturn = PrintDIB(hDIB, PW_STRETCHTOPAGE, 0, 0,
                        (LPSTR)gszWindowText);

            else if (opts.iOption == IDC_SCALE)
                wReturn = PrintDIB(hDIB, (WORD)PW_SCALE, (WORD)opts.iXScale,
                        (WORD)opts.iYScale, (LPSTR)gszWindowText);
            else
                wReturn = PrintDIB(hDIB, PW_BESTFIT, 0, 0,
                        (LPSTR)gszWindowText);

            if (wReturn)
                DIBError(wReturn);

            DestroyDIB(hDIB);
        }
    }
}

//*********************************************************************
//
// DoCapture()
//
// This procedure gets called when the user wants to capture the
// screen.  The wCommand parameter tells us which capture operation
// we want to perform.
//
//*********************************************************************

void DoCapture(HWND hWnd, WORD wCommand)
{
    HBITMAP     hBitmap;        // Handle to our temporary bitmap
    HPALETTE    hPal;           // Handle to our palette
    CHAR        lpBuffer[128];  // Buffer for string retrieved from resources

    switch (wCommand)
    {
        // Copy Entire screen to DIB

        case IDM_DESKTOP:
        {
            RECT rScreen;       // Rect containing entire screen coordinates
            HDC hDC;            // DC to screen
            MSG msg;            // Message for the PeekMessage()
            CHAR lpBuffer[128]; // Buffer for string retrieved from resources

            hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
            rScreen.left = rScreen.top = 0;
            rScreen.right = GetDeviceCaps(hDC, HORZRES);
            rScreen.bottom = GetDeviceCaps(hDC, VERTRES);

            // Delete our DC

            DeleteDC(hDC);

            LoadString(ghInst, IDS_ENTIRESCRN, lpBuffer, sizeof(lpBuffer));
            strcpy(gszWindowText, lpBuffer);

            // Wait until everybody repaints themselves

            while (PeekMessage(&msg,NULL,0,0,PM_REMOVE) != 0)
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            hBitmap = CopyScreenToBitmap(&rScreen);

            if (hBitmap)
                hPal = GetSystemPalette();
            else
                hPal = NULL;
        }
        break;

        // copy user-selected portion of screen to DIB

        case IDM_CAPTRECT:
        {
            RECT rRubberBand;       // Region to capture (screen coordinates)

            // Allow user to "rubberband" a section of the screen for
            // us to capture

            RubberBandScreen(&rRubberBand);
            LoadString(ghInst, IDS_USERSELECT, lpBuffer, sizeof(lpBuffer));
            strcpy(gszWindowText, lpBuffer);

            hBitmap = CopyScreenToBitmap(&rRubberBand);

            if (hBitmap)
                hPal = GetSystemPalette();
            else
                hPal = NULL;
        }
        break;

        case IDM_CAPTWINDOW:
        case IDM_CAPTCLIENT:
        case IDM_ACTIVEWINDOW:
        {
            HWND    hWndSelect;       // The current active window
            HWND    hWndDesktop;      // Window to desktop
            MSG     msg;              // For our peekmessage loop

            // Just capture the current active window, whatever it is

            if (wCommand == IDM_ACTIVEWINDOW)
                hWndSelect = GetForegroundWindow();

            // Allow the user to click on a single window to capture

            else
                hWndSelect = ghWndCapture;

            // If they try to capture the desktop window, then just
            // capture the entire screen.

            hWndDesktop = GetDesktopWindow();

            if (hWndSelect == hWndDesktop)
            {
                RECT    rScreen;    // contains entire screen coordinates
                HDC     hDC;        // DC to screen
                MSG     msg;        // Message for the PeekMessage()

                hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
                rScreen.left = rScreen.top = 0;
                rScreen.right = GetDeviceCaps(hDC, HORZRES);
                rScreen.bottom = GetDeviceCaps(hDC, VERTRES);

                /* Delete our DC */
                DeleteDC(hDC);

                LoadString(ghInst, IDS_ENTIRESCRN, lpBuffer, sizeof(lpBuffer));
                strcpy(gszWindowText, lpBuffer);

                // Wait until everybody repaints themselves

                while (PeekMessage(&msg,NULL,0,0,PM_REMOVE) != 0)
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }

                ghBitmap = CopyScreenToBitmap(&rScreen);

                if (ghBitmap)
                    ghPal = GetSystemPalette();
                else
                    ghPal = NULL;

                ghWndCapture = NULL;
                return;
            }

            // Check to see that the hWnd is not NULL

            if (!hWndSelect)
            {
                LoadString(ghInst, IDS_CANTCAPWNDW, lpBuffer, sizeof(lpBuffer));
                MessageBox(NULL, lpBuffer, szAppName,
                        MB_ICONEXCLAMATION | MB_OK);

                hBitmap = NULL;
                hPal = NULL;
                break;
            }

            // Make sure it's not a hidden window.  Hmm, capturing a hidden
            // window would certainly be a cool trick, wouldn't it?

            if (!IsWindowVisible(hWndSelect))
            {
                LoadString(ghInst, IDS_WNDWNOTVIS, lpBuffer, sizeof(lpBuffer));
                MessageBox(NULL, lpBuffer,
                        szAppName, MB_ICONEXCLAMATION | MB_OK);

                ghWndCapture = NULL;
                hBitmap = NULL;
                hPal = NULL;
                break;
            }

            // Move window which was selected to top of Z-order for
            // the capture, and make it redraw itself

            SetWindowPos(hWndSelect, NULL, 0, 0, 0, 0,
                    SWP_DRAWFRAME | SWP_NOSIZE | SWP_NOMOVE);

            UpdateWindow(hWndSelect);

            // Wait until everybody repaints themselves

            while (PeekMessage(&msg,NULL,0,0,PM_REMOVE) != 0)
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            // Get the caption

            GetWindowText(hWndSelect, gszWindowText, 100);

            // Capture the screen -- if we selected CLIENT only capture, then
            // select that open when calling our API.  Otherwise, get the
            // entire window.

            hBitmap = CopyWindowToBitmap(hWndSelect,
                    (WORD)((wCommand == IDM_CAPTCLIENT) ? (PW_CLIENT) :
                    (PW_WINDOW)));

            if (hBitmap)
                hPal = GetSystemPalette();
            else
                hPal = NULL;

            break;
        }

        //Oops, something went wrong

        default:
            LoadString(ghInst, IDS_INTERROR, lpBuffer, sizeof(lpBuffer));
            MessageBox(NULL, lpBuffer, szAppName,
                    MB_ICONHAND | MB_OK);
            break;
    }

    if (hBitmap)
    {
        ghBitmap = hBitmap;
        hBitmap = NULL;
    }
    if (hPal)
    {
        ghPal = hPal;
        hPal = NULL;
    }

    // Now, paint our bitmap in the client area

    if (bStartup)
        bStartup = FALSE;

    InvalidateRect(hWnd, NULL, FALSE);
    UpdateWindow(hWnd);
    ghWndCapture = NULL;
}

//**********************************************************************
//
// RubberBandScreen()
//
// This function allows the user to rubber-band a portion of the screen.
// When the left button is released, the rect that the user selected
// (in screen coordinates) is returned in lpRect.
//
//*********************************************************************

void RubberBandScreen(LPRECT lpRect)
{
    POINT   pt;                 // Temporary POINT
    MSG     msg;                // Used in our PeekMessage() loop
    POINT   ptOrigin;           // Point where the user pressed left mouse button down
    RECT    rcClip;             // Current selection
    HDC     hScreenDC;          // DC to the screen (so we can draw on it)
    BOOL    bCapturing = FALSE; // TRUE if we are rubber-banding
    POINTS  pts;
    BOOL    bLButtonUp = FALSE;

    hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);

    // Eat mouse messages until a WM_LBUTTONUP is encountered. Meanwhile
    // continue to draw a rubberbanding rectangle and display it's dimensions

    for (;;)
    {
        WaitMessage();
        if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
        {
            // If the message is a WM_LBUTTONDOWN, begin drawing the
            // rubber-band box.

            if (msg.message == WM_RBUTTONDOWN)
            {
                // User pressed left button, initialize selection
                // Set origin to current mouse position (in window coords)

                pts = MAKEPOINTS(msg.lParam);

                ptOrigin.x = pts.x;
                ptOrigin.y = pts.y;

                // Convert to screen coordinates

                ClientToScreen(ghWndMain, &ptOrigin);

                // rcClip is the current rectangle that the user
                // has selected.  Since user just pressed left button down,
                // initialize this rect very small

                rcClip.left = rcClip.right = ptOrigin.x;
                rcClip.top = rcClip.bottom = ptOrigin.y;
                NormalizeRect(&rcClip);     // Make sure it is a normal rect
                DrawSelect(hScreenDC, TRUE, &rcClip); // Draw the rubber-band box
                bCapturing = TRUE;
            }

            else if (msg.message == WM_LBUTTONUP)
            {
                if (!bCapturing)
                    break;
                else
                {
                    bLButtonUp = TRUE;
                    PostMessage(ghWndMain, WM_RBUTTONUP, msg.wParam, msg.lParam);
                }
            }

            // Any messages that make it into the next statement are mouse
            // messages, and we are capturing, so let's update the rubber-band
            // box

            if (bCapturing)
            {
                DrawSelect(hScreenDC, FALSE, &rcClip);  // erase old rubber-band
                rcClip.left = ptOrigin.x;   // Update rect with new mouse info
                rcClip.top = ptOrigin.y;

                pts = MAKEPOINTS(msg.lParam);

                pt.x = pts.x;
                pt.y = pts.y;

                // Convert to screen coordinates

                ClientToScreen(ghWndMain, &pt);
                rcClip.right = pt.x;
                rcClip.bottom = pt.y;
                NormalizeRect(&rcClip);
                DrawSelect(hScreenDC, TRUE, &rcClip); // new rubber-band
            }

            // If the message is WM_LBUTTONUP, then we stop the selection
            // process.

            if (msg.message == WM_RBUTTONUP)
            {
                DrawSelect(hScreenDC, FALSE, &rcClip);    // erase rubber-band
                if (bLButtonUp)
                    PostMessage(ghWndMain, WM_LBUTTONUP, msg.wParam, msg.lParam);
                break;
            }
        }
        else
            continue;
    }
    DeleteDC(hScreenDC);

    // Assign rect user selected to lpRect parameter

    if (!IsRectEmpty(&rcClip))
        CopyRect(lpRect, &rcClip);

    PostMessage(ghWndMain, WM_LBUTTONUP, msg.wParam, msg.lParam);
}

//***************************************************************************
//
// DrawSelect
//
// Draws the selected clip rectangle with its dimensions on the DC
//
//***************************************************************************

void DrawSelect(HDC hdc, BOOL fDraw, LPRECT lprClip)
{
    char sz[80];
    DWORD dw;
    int x, y, len, dx, dy;
    HDC hdcBits;
    HBITMAP hbm;
    RECT rcClip;
    SIZE sExtent;

    rcClip = *lprClip;
    if (!IsRectEmpty(&rcClip))
    {

        // If a rectangular clip region has been selected, draw it

        PatBlt(hdc, rcClip.left, rcClip.top, rcClip.right - rcClip.left, 1,
                DSTINVERT);
        PatBlt(hdc, rcClip.left, rcClip.bottom, 1, -(rcClip.bottom-rcClip.top),
                DSTINVERT);
        PatBlt(hdc, rcClip.right - 1, rcClip.top, 1, rcClip.bottom - rcClip.top,
                DSTINVERT);
        PatBlt(hdc, rcClip.right, rcClip.bottom - 1, -(rcClip.right -
                rcClip.left), 1, DSTINVERT);

        // Format the dimensions string ...

        wsprintf(sz, "%dx%d", rcClip.right - rcClip.left, rcClip.bottom -
               rcClip.top);
        len = lstrlen(sz);

        // ... and center it in the rectangle

        dw = GetTextExtentPoint(hdc, sz, len, &sExtent);
        dx = sExtent.cx;
        dy = sExtent.cy;
        x = (rcClip.right + rcClip.left - dx) / 2;
        y = (rcClip.bottom + rcClip.top - dy) / 2;
        hdcBits = CreateCompatibleDC(hdc);
        SetTextColor(hdcBits, 0xFFFFFFL);
        SetBkColor(hdcBits, 0x000000L);

        // Output the text to the DC

        if (hbm = CreateBitmap(dx, dy, 1, 1, NULL))
        {
            hbm = SelectObject(hdcBits, hbm);
            ExtTextOut(hdcBits, 0, 0, 0, NULL, sz, len, NULL);
            BitBlt(hdc, x, y, dx, dy, hdcBits, 0, 0, SRCINVERT);
            hbm = SelectObject(hdcBits, hbm);
            DeleteObject(hbm);
        }
        DeleteDC(hdcBits);
    }
}

//*************************************************************************
//
//  FUNCTION   : NormalizeRect(RECT *prc)
//
//  PURPOSE    : If the rectangle coordinates are reversed, swaps them.
//               This is used to make sure that the first coordinate of
//               our rect is the upper left, and the second is lower right.
//
//*************************************************************************

void WINAPI NormalizeRect(LPRECT prc)
{
    if (prc->right  < prc->left) SWAP(prc->right,  prc->left);
    if (prc->bottom < prc->top)  SWAP(prc->bottom, prc->top);
}

//***************************************************************************
//
// Function: DoSize
//
// Purpose:  Called by WndProc() on WM_SIZE
//
//           When the window is sized -- set up the scroll bars.
//
//           The window will be repainted if the new size, combined
//           with the current scroll bar positions would create blank
//           space at the left or bottom of the window.
//
//***************************************************************************

void DoSize(HWND hWnd)
{
    BITMAP      bm;                     // Bitmap info structure
    int         cxBitmap=0, cyBitmap=0; // Bitmap width and height
    int         cxScroll, cyScroll;     // Scroll positions
    RECT        rect;                   // Client rectangle

    // repaint if displaying bitmap

    if (ghBitmap)
    {
        // Get info about bitmap

        GetObject(ghBitmap, sizeof(BITMAP), (LPSTR)&bm);

        // Get the width and height of the bitmap

        cxBitmap = bm.bmWidth;
        cyBitmap = bm.bmHeight;

        // Find out the dimensions of the window, and the current thumb
        // positions

        GetClientRect(hWnd, &rect);

        cxScroll = GetScrollPos (hWnd, SB_HORZ);
        cyScroll = GetScrollPos (hWnd, SB_VERT);

        // If current thumb positions would cause blank space
        // at right or bottom of client area, repaint

        if (cxScroll + rect.right > cxBitmap ||
                cyScroll + rect.bottom > cyBitmap)
            InvalidateRect(hWnd, NULL, FALSE);

        // Make sure scroll bars are updated

        SetupScrollBars(hWnd, (WORD)cxBitmap, (WORD)cyBitmap);
    }
    else if (bStartup)
        InvalidateRect(hWnd, NULL, TRUE);
}

//***************************************************************************
//
// Function: ReallyGetClientRect
//
// Purpose:  Gets the rectangular area of the client rect including
//           the area underneath visible scroll bars.  Stolen from
//           ShowDIB.
//
//***************************************************************************

void ReallyGetClientRect(HWND hWnd, LPRECT lpRect)
{
    DWORD   dwWinStyle;

    dwWinStyle = GetWindowLong (hWnd, GWL_STYLE);

    GetClientRect (hWnd, lpRect);

    if (dwWinStyle & WS_HSCROLL)
        lpRect->bottom += (GetSystemMetrics (SM_CYHSCROLL) - 1);

    if (dwWinStyle & WS_VSCROLL)
        lpRect->right  += (GetSystemMetrics (SM_CXVSCROLL) - 1);
}

//***************************************************************************
//
// Function: SetupScrollBars
//
// Purpose:  Sets up scroll bars.
//
//***************************************************************************

void SetupScrollBars(HWND hWnd, WORD cxBitmap, WORD cyBitmap)
{
    RECT        rect;                       // Client Rectangle
    BOOL        bNeedScrollBars=FALSE;      // Need Scroll bars?
    unsigned    cxWindow, cyWindow;         // Width and height of client area
    int         cxRange=0, cyRange=0;       // Range needed for horz and vert

    // Do some initialization

    ReallyGetClientRect(hWnd, &rect);

    cxWindow = rect.right - rect.left;
    cyWindow = rect.bottom - rect.top;

    // Now determine if we need the scroll bars

    if ((cxWindow < (unsigned)cxBitmap) || (cyWindow < (unsigned)cyBitmap))
        bNeedScrollBars = TRUE;

    // Setup the scroll bar ranges.  We want to be able to
    // scroll the window so that all the bitmap can appear
    // within the client area.  Take into account that
    // if the opposite scroll bar is activated, it eats
    // up some client area.

    if (bNeedScrollBars)
    {
        cyRange = (unsigned)cyBitmap - cyWindow - 1 +
                GetSystemMetrics (SM_CYHSCROLL);
        cxRange = (unsigned)cxBitmap - cxWindow - 1 +
                GetSystemMetrics (SM_CXVSCROLL);
    }

    // Set the ranges we've calculated (0 to 0 means invisible scrollbar)

    SetScrollRange(hWnd, SB_VERT, 0, cyRange, TRUE);
    SetScrollRange(hWnd, SB_HORZ, 0, cxRange, TRUE);
}

//**********************************************************************
//
// Function:   DoScroll()
//
// Purpose:    Called by ChildWndProc() on WM_HSCROLL and WM_VSCROLL.
//             Window needs to be scrolled (user has clicked on one
//             of the scroll bars.
//
//             Does scrolling in both horiziontal and vertical directions.
//             Note that the variables are all named as if we were
//             doing a horizontal scroll.  However, if we're doing a
//             vertical scroll, they are initialized to the appropriate
//             values for a vertical scroll.
//
//             If we scroll by one (i.e. user clicks on one of the
//             scrolling arrows), we scroll the window by 1/SCROLL_RATIO
//             of the client area.  In other words, if SCROLL_RATION==4,
//             then we move the client area over a 1/4 of the width/height
//             of the screen.
//
//             If the user is paging up/down we move a full client area's
//             worth.
//
//             If the user moves the thumb to an absolute position, we
//             just move there.
//
//             ScrollWindow/re-painting do the actual work of scrolling.
//
//**********************************************************************

void DoScroll(HWND hWnd, int message, int wPos, int wScrollType)
{
    int  xBar;          // Where scrollbar is now.
    int  nMin;          // Minumum scroll bar value.
    int  nMax;          // Maximum scroll bar value.
    int  dx;            // How much to move.
    int  nOneUnit;      // # of pixels for LINEUP/LINEDOWN
    int  cxClient;      // Width of client area.
    int  nHorzOrVert;   // Doing the horizontal or vertical?
    RECT rect;          // Client area.

    GetClientRect (hWnd, &rect);

    if (message == WM_HSCROLL)
    {
        nHorzOrVert = SB_HORZ;
        cxClient    = rect.right - rect.left;
    }
    else
    {
        nHorzOrVert = SB_VERT;
        cxClient    = rect.bottom - rect.top;
    }

    // On a SB_LINEUP/SB_LINEDOWN we will move the DIB by
    //  1/SCROLL_RATIO of the client area (i.e. if SCROLL_RATIO
    //  is 4, it will scroll the DIB a quarter of the client
    //  area's height or width.

    nOneUnit = cxClient / SCROLL_RATIO;
    if (!nOneUnit)
        nOneUnit = 1;

    xBar = GetScrollPos (hWnd, nHorzOrVert);
    GetScrollRange (hWnd, nHorzOrVert, &nMin, &nMax);

    switch (wScrollType)
    {
        case SB_LINEDOWN:             // One line right.
            dx = nOneUnit;
            break;

        case SB_LINEUP:               // One line left.
            dx = -nOneUnit;
            break;

        case SB_PAGEDOWN:             // One page right.
            dx = cxClient;
            break;

        case SB_PAGEUP:               // One page left.
            dx = -cxClient;
            break;

        case SB_THUMBPOSITION:        // Absolute position.
            dx = wPos - xBar;
            break;

        default:                      // No change.
            dx = 0;
            break;
    }

    if (dx)
    {
        xBar += dx;

        if (xBar < nMin)
        {
            dx  -= xBar - nMin;
            xBar = nMin;
        }

        if (xBar > nMax)
        {
            dx  -= xBar - nMax;
            xBar = nMax;
        }

        if (dx)
        {
            SetScrollPos (hWnd, nHorzOrVert, xBar, TRUE);

            if (nHorzOrVert == SB_HORZ)
                ScrollWindow (hWnd, -dx, 0, NULL, NULL);
            else
                ScrollWindow (hWnd, 0, -dx, NULL, NULL);

            UpdateWindow (hWnd);
        }
    }
}

//****************************************************************************
//
// Function: DoPaint()
//
// Purpose:  Called by WndProc. Does painting for client area.
//
//
//***************************************************************************

void DoPaint(HWND hWnd)
{
    HDC             hDC, hMemDC;         // Handle to DC, memory DC
    PAINTSTRUCT     ps;                  // Painting structure
    BITMAP          bm;                  // BITMAP structure
    HBITMAP         hOldBm;              // Handle to previous bitmap
    RECT            rectClient, rectDDB; // Client and bitmap rectangles
    int             xScroll, yScroll;    // Scroll positions
    int             x, y;                // Logo origin

    // Begin painting

    hDC = BeginPaint(hWnd, &ps);

    // Check to see if we are displaying a bitmap

    if (!ghBitmap)
    {
        // No bitmap yet, are we in start mode?

        if (bStartup)
        {
            GetClientRect(hWnd, &rectClient);

            hMemDC = CreateCompatibleDC(ps.hdc);

            // Select our logo bitmap

            hOldBm = SelectObject(hMemDC, ghbmLogo);

            GetObject(ghbmLogo, sizeof(BITMAP), (VOID *)&bm);

            x = (rectClient.right - bm.bmWidth) / 2;
            y = (rectClient.bottom - bm.bmHeight) / 2;

            // Now bitblt our logo to client area

            BitBlt(ps.hdc, x, y, bm.bmWidth, bm.bmHeight, hMemDC, 0, 0,
                    SRCCOPY);

            // Clean up
            SelectObject(hMemDC,hOldBm);
            DeleteDC(hMemDC);
        }
        else
        {
            // Turn off scroll bars in case they were on

                SetScrollRange (hWnd, SB_VERT, 0, 0, TRUE);
            SetScrollRange (hWnd, SB_HORZ, 0, 0, TRUE);
        }
    }
    else // We are displaying a bitmap
    {
        // Get bitmap info

        GetObject(ghBitmap, sizeof(BITMAP), (LPSTR)&bm);

        // Get scroll bar positions

        xScroll  = GetScrollPos  (hWnd, SB_HORZ);
        yScroll  = GetScrollPos  (hWnd, SB_VERT);

        // Set up the scroll bars appropriately.

        SetupScrollBars(hWnd, (WORD)bm.bmWidth, (WORD)bm.bmHeight);

        // Set up the necessary rectangles -- i.e. the rectangle
        //  we're rendering into, and the rectangle in the bitmap

        GetClientRect (hWnd, &rectClient);

        rectDDB.left   = xScroll;
        rectDDB.top    = yScroll;
        rectDDB.right  = xScroll + rectClient.right - rectClient.left;
        rectDDB.bottom = yScroll + rectClient.bottom - rectClient.top;

        if (rectDDB.right > bm.bmWidth)
        {
            int dx;

            dx = bm.bmWidth - rectDDB.right;

            rectDDB.right     += dx;
            rectClient.right  += dx;
        }

        if (rectDDB.bottom > bm.bmHeight)
        {
            int dy;

            dy = bm.bmHeight - rectDDB.bottom;

            rectDDB.bottom    += dy;
            rectClient.bottom += dy;
        }

        // Go do the actual painting.

        PaintBitmap(hDC, &rectClient, ghBitmap, &rectDDB, ghPal);
    }

    EndPaint(hWnd, &ps);
}

//****************************************************************************
//
// Function: FrameWindow()
//
// Purpose:  Highlight the window frame
//
//
//***************************************************************************

void FrameWindow(HWND hWnd)
{
    HDC     hdc;
    RECT    rc;

#define DINV    3

    if (!IsWindow(hWnd))
        return;

    hdc = GetWindowDC(hWnd);
    GetWindowRect(hWnd, &rc);
    OffsetRect(&rc, -rc.left, -rc.top);

    if (!IsRectEmpty(&rc))
    {
        PatBlt(hdc, rc.left, rc.top, rc.right-rc.left, DINV, DSTINVERT);
        PatBlt(hdc, rc.left, rc.bottom-DINV, DINV, -(rc.bottom-rc.top-2*DINV),
                DSTINVERT);
        PatBlt(hdc, rc.right-DINV, rc.top+DINV, DINV, rc.bottom-rc.top-2*DINV,
                DSTINVERT);
        PatBlt(hdc, rc.right, rc.bottom-DINV, -(rc.right-rc.left), DINV,
                DSTINVERT);
    }

    ReleaseDC(hWnd, hdc);
}

//****************************************************************************
//
// FUNCTION: CenterWindow (HWND, HWND)
//
// PURPOSE:  Center one window over another
//
// COMMENTS:
//
//      Dialog boxes take on the screen position that they were designed at,
//      which is not always appropriate. Centering the dialog over a particular
//      window usually results in a better position.
//
//***************************************************************************

BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
    RECT    rChild, rParent;
    int     wChild, hChild, wParent, hParent;
    int     wScreen, hScreen, xNew, yNew;
    HDC     hdc;

    // Get the Height and Width of the child window
    GetWindowRect (hwndChild, &rChild);
    wChild = rChild.right - rChild.left;
    hChild = rChild.bottom - rChild.top;

    // Get the Height and Width of the parent window
    GetWindowRect (hwndParent, &rParent);
    wParent = rParent.right - rParent.left;
    hParent = rParent.bottom - rParent.top;

    // Get the display limits
    hdc = GetDC (hwndChild);
    wScreen = GetDeviceCaps (hdc, HORZRES);
    hScreen = GetDeviceCaps (hdc, VERTRES);
    ReleaseDC (hwndChild, hdc);

    // Calculate new X position, then adjust for screen
    xNew = rParent.left + ((wParent - wChild) /2);
    if (xNew < 0)
        xNew = 0;
    else if ((xNew+wChild) > wScreen)
        xNew = wScreen - wChild;

    // Calculate new Y position, then adjust for screen
    yNew = rParent.top  + ((hParent - hChild) /2);
    if (yNew < 0)
        yNew = 0;
    else if ((yNew+hChild) > hScreen)
        yNew = hScreen - hChild;

    // Set it, and return
    return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0,
            SWP_NOSIZE | SWP_NOZORDER);
}


WINCAP.H

/*    PortTool v2.2     wincap.h          */

/* Header file for wincap.c */
/* Copyright 1991-1998 Microsoft Corporation. All rights reserved. */

//
// constants for menu items
//

#define IDM_SAVE            110
#define IDM_PRINT           120
#define IDM_EXIT            130
#define IDM_ABOUT           140
#define IDM_EDITCOPY        180
#define IDM_CAPTWINDOW      210
#define IDM_CAPTCLIENT      220
#define IDM_CAPTRECT        230
#define IDM_CAPTUREHIDE     240
#define IDM_ACTIVEWINDOW    250
#define IDM_DESKTOP         260
#define IDM_VIEWFULL        310
#define IDM_VIEWRESTORED    320
#define IDM_VIEWCLEAR       330
#define IDM_CURSORSELECT    340
#define IDM_SELECTRECT      350

// Constants for string table

#define IDS_SAVEASTITLE      1

#define OPTION_FILE        0x0001
#define OPTION_PRINTER     0x0002

// For common dialog file/save

#define IDD_FILETYPE        101
#define IDD_RGB             102
#define IDD_RLE4            103
#define IDD_RLE8            104
#define IDD_PM              105
#define IDD_1               106
#define IDD_4               107
#define IDD_8               108
#define IDD_24              109
#define IDD_FILETYPEGROUP   110
#define IDD_BPP             111

// Stuff for "File.Print" dialog

typedef struct tagOPTIONS {
    int iOption;       // Print Options
    int iXScale;       // X scale factor (if iOption is DLGPRINT_SCALE)
    int iYScale;       // Y scale factor ('')
    } OPTIONSTRUCT, *LPOPTIONSTRUCT;

/*
 * function prototypes
 */

void cwCenter(HWND, int);
LONG WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI InstallHook (HWND hWnd, BOOL bCode );
BOOL WINAPI HelpDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY SavingDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL GetFileName (HWND hWndOwner, LPSTR szFileName, LPDWORD dwFlags);
void DrawIndent(HWND hDlg, int ID, int iType);
void StretchIconToWindow(HWND hWndDlg, LPSTR szIconName);
void DoCapture(HWND hWnd, WORD wCommand);
void RubberBandScreen(LPRECT lpRect);
void DrawSelect( HDC hdc, BOOL fDraw, LPRECT lprClip);
void WINAPI NormalizeRect (LPRECT prc);
LONG APIENTRY FullViewWndProc(HWND, UINT, WPARAM, LPARAM);
void SaveMe(void);
void PrintMe(void);
void DoSize(HWND);
void ReallyGetClientRect(HWND, LPRECT);
void SetupScrollBars(HWND, WORD, WORD);
void DoScroll(HWND, int, int, int);
void DoPaint(HWND);
BOOL CALLBACK SelectDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK RectangleDlgProc(HWND, UINT, WPARAM, LPARAM);
void FrameWindow(HWND);
BOOL CenterWindow (HWND hwndChild, HWND hwndParent);


README.TXT

PlgBlt Demonstration

SUMMARY
=======

The PlgBlt function can copy a bit image onto an arbitrary parallelogram.
Applications can use this function to easily rotate or shear bitmaps. The
PLGBLT sample is an easy-to-use demonstration of how this can be done.

MORE INFORMATION
================

The program presents a dialog box that displays the input parameters to the
PlgBlt function. By choosing the "New Src" or "New Mask" button, the user
can select a new bitmap for use as the source bitmap or as the monochrome
mask bitmap. The client area of the window is divided into three regions.
The region on the left contains the result of the PlgBlt operation. The
region in the middle provides the source HDC, and the region on the right
provides the mask bitmap for the PlgBlt operation.

In each of the three regions, there is a "direct manipulation object." This
object may be picked up and moved by clicking the left mouse button in the
upper-left corner and dragging. The three objects are restricted in their
response to user actions to correctly reflect the parameters to the PlgBlt
function. The object in the mask region may be moved only. The object in the
source region may be moved or sized. The object in the destination region
may be moved, sized, sheared, or rotated. Please see the WXFORM sample for
more information on how this direct manipulation is accomplished.


BITMAP.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/* bitmap.h  */

/* function prototypes */
VOID DrawBitmap (HDC, HBITMAP);
HBITMAP GetBitmap (HDC, HANDLE, BOOL);


PLGBLT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  plgblt.c -- sample program demonstrating the new PlgBlt() API.
*
*  design:  There is one main window with one dialog box stretched to fill
*   the top of it.  The parameters for the plgblt painted into the main
*   window are stored in the entry fields of this dialog box.  The user
*   may change these values and see the effect on the blt.
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <math.h>
#include <stdio.h>
#include "plgblt.h"
#include "track.h"
#include "bitmap.h"

/* global variables. */
PTrackObject ptoDest, ptoSrc, ptoMask = NULL;
HDC          hdcDest, hdcSrc, hdcMask;
HBITMAP               hbmSrc, hbmMask = NULL;

HANDLE hInst;
HWND   hwndMain, hwndDlg;

int nSpin;

#define  BACKGROUNDBRUSH GetStockObject(LTGRAY_BRUSH)

/**************************************************************************\
*
*  function:  WinMain()
*
*  input parameters:  c.f. generic sample
*
\**************************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    MSG   msg;
    RECT rect;
    HANDLE haccel;

    UNREFERENCED_PARAMETER( lpCmdLine );

    //
    // Detect platform and exit gracefully if not Windows NT.
    //

    {
      OSVERSIONINFO osvi;
      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

      GetVersionEx (&osvi);
      if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) {
        MessageBox (NULL,
          GetStringRes (IDS_NTONLY),
          "PlgBlt", MB_OK | MB_ICONSTOP);
        return 0;
      }
    }

    /* Check for previous instance.  If none, then register class. */
    if (!hPrevInstance) {
        WNDCLASS  wc;

        wc.style = 0;
        wc.lpfnWndProc = (WNDPROC)MainWndProc;

        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(hInstance, "plgbltIcon");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = BACKGROUNDBRUSH;
        wc.lpszMenuName =  "plgbltMenu";
        wc.lpszClassName = "plgblt";

        if (!RegisterClass(&wc)) return (FALSE);
    }  /* class registered o.k. */

    /* Create the main window.  Return false if CreateWindow() fails */
    hInst = hInstance;

    hwndMain = CreateWindow(
        "plgblt",
        GetStringRes (IDS_WINDOWTITLE),
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (!hwndMain) return (FALSE);

    /* create the top dialog as a child of the main window. */
    hwndDlg = CreateDialog (hInst, "plgbltDlg", hwndMain, (DLGPROC)DlgProc);

    /* Send main window a WM_SIZE message so that it will size the top
     *  dialog correctly.
     */
    GetClientRect (hwndMain, &rect);
    SendMessage (hwndMain, WM_SIZE, 0, (rect.right - rect.left));
    ShowWindow (hwndDlg, SW_SHOW);
    ShowWindow(hwndMain, nCmdShow);

    /* get global handle to the menu */

    /* Load the accelerator table that provides clipboard support. */
    haccel = LoadAccelerators (hInst, "bltAccel");

    /* Loop getting messages and dispatching them. */
    while (GetMessage(&msg,NULL, 0,0)) {
      if (!TranslateAccelerator(hwndMain, haccel, &msg))
      if (!IsDialogMessage (hwndDlg, &msg)){
        DispatchMessage(&msg);
      }

      /* if no messages, and we are spinning, then post spin message. */
      if (!PeekMessage (&msg, hwndMain, 0,0, PM_NOREMOVE) && nSpin)
        PostMessage (hwndMain, WM_SPIN, 0,0);
    }

    /* Return the value from PostQuitMessage */
    return (msg.wParam);
}

/**************************************************************************\
*
*  function:  MainWndProc()
*
*  input parameters:  normal window procedure parameters.
*
*  There are 6 different HDCs used for the main window (in addition to the
*   temporary one returned from BeginPaint).  There are two for each of the
*   three thirds of the window.  The first one contains the bitmap.  The
*   second one is for the track object and is stored in the TRACKOBJECT
*   structure.
*
*  global variables:
*   hwndDlg - dialog with entry fields containing parameters.
*   ptoDest, ptoSrc, ptoMask - pointers to the direct manipulation objects
*   hdcDest, hdcSrc, hdcMask - HDCs for the 3 sub regions of the window.
*   hbmSrc, hbmMask          - bitmap handles for source and mask.
\**************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int miniWidth;
static RECT rect;
static HANDLE hPenGrid, hPenSeparator;

  switch (message) {

    /**********************************************************************\
    *  WM_CREATE
    *
    * Get three new HDCs, then create three new track objects.
    *  Each track object has different allowed tracking modes.
    *  Finally create two pens for drawing later on.
    \**********************************************************************/
    case WM_CREATE:
        hdcSrc  = GetDC (hwnd);
        hdcDest = GetDC (hwnd);
        hdcMask = GetDC (hwnd);

        ptoDest = doTrackObject (NULL, TROB_NEW, hwnd,0);
        ptoDest->allowedModes = TMALL;
        ptoSrc  = doTrackObject (NULL, TROB_NEW, hwnd,0);
        ptoSrc->allowedModes = TMMOVE | TMSIZEXY;
        ptoMask = doTrackObject (NULL, TROB_NEW, hwnd,0);
        ptoMask->allowedModes = TMMOVE;

        hPenGrid      = CreatePen (PS_SOLID, 1, GRIDCOLOR);
        hPenSeparator = CreatePen (PS_SOLID, 2*SEPARATORWIDTH, (COLORREF) 0x01000000);

        { HMENU hMenu;
        hMenu = GetMenu (hwnd);
        CheckMenuItem(hMenu, IDM_SPINTOPLEFT, MF_CHECKED);
        CheckMenuItem(hMenu, IDM_SPINOFF    , MF_CHECKED);
        nSpin = FALSE;
        }

    break;

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Complement of WM_CREATE.  Free up all of the HDCs, send all of the
    *  track objects their delete messages, delete the pens,
    *  then call PostQuitMessage.
    \**********************************************************************/
    case WM_DESTROY:
        ReleaseDC (hwnd, hdcSrc );
        ReleaseDC (hwnd, hdcDest);
        ReleaseDC (hwnd, hdcMask);
        doTrackObject (ptoDest, TROB_DELETE, hwnd,0);
        doTrackObject (ptoSrc , TROB_DELETE, hwnd,0);
        doTrackObject (ptoMask, TROB_DELETE, hwnd,0);

        DeleteObject(hPenGrid);
        DeleteObject(hPenSeparator);

        PostQuitMessage(0);
    break;

    /**********************************************************************\
    *  WM_SIZE
    *
    * Stretch the top dialog to fill the width of the main window.
    * Adjust the viewport origins of the 6 HDCs.
    * Set the clip regions of the 6 HDCs.
    \**********************************************************************/
    case WM_SIZE: {
        HRGN hrgn;

        SetWindowPos (hwndDlg, NULL, 0,0, LOWORD(lParam), DIALOGHEIGHT, 0);

        GetClientRect (hwndMain, &rect);
        miniWidth = rect.right/3;

        SetViewportOrgEx (hdcDest,      0,           DIALOGHEIGHT, NULL);
        SetViewportOrgEx (ptoDest->hdc, 0,           DIALOGHEIGHT, NULL);
        SetViewportOrgEx (hdcSrc,       miniWidth,   DIALOGHEIGHT, NULL);
        SetViewportOrgEx (ptoSrc->hdc,  miniWidth,   DIALOGHEIGHT, NULL);
        SetViewportOrgEx (hdcMask,      2*miniWidth, DIALOGHEIGHT, NULL);
        SetViewportOrgEx (ptoMask->hdc, 2*miniWidth, DIALOGHEIGHT, NULL);

        ptoDest->rectClip.left    = 0;
        ptoDest->rectClip.top     = DIALOGHEIGHT;
        ptoDest->rectClip.right   = miniWidth-2*SEPARATORWIDTH;
        ptoDest->rectClip.bottom  = rect.bottom;
        hrgn = CreateRectRgnIndirect (&ptoDest->rectClip);
        SelectClipRgn (hdcDest,      hrgn);
        SelectClipRgn (ptoDest->hdc, hrgn);
        DeleteObject (hrgn);

        ptoSrc->rectClip.left    = miniWidth;
        ptoSrc->rectClip.top     = DIALOGHEIGHT;
        ptoSrc->rectClip.right   = 2*miniWidth-2*SEPARATORWIDTH;
        ptoSrc->rectClip.bottom  = rect.bottom;
        hrgn = CreateRectRgnIndirect (&ptoSrc->rectClip);
        SelectClipRgn (hdcSrc,       hrgn);
        SelectClipRgn (ptoSrc->hdc,  hrgn);
        DeleteObject (hrgn);

        ptoMask->rectClip.left    = 2*miniWidth;
        ptoMask->rectClip.top     = DIALOGHEIGHT;
        ptoMask->rectClip.right   = 3*miniWidth;
        ptoMask->rectClip.bottom  = rect.bottom;
        hrgn = CreateRectRgnIndirect (&ptoMask->rectClip);
        SelectClipRgn (hdcMask,      hrgn);
        SelectClipRgn (ptoMask->hdc, hrgn);
        DeleteObject (hrgn);

        SendMessage (hwndDlg, WM_PUTUPLPOINTS, (DWORD)hdcDest, (LONG)ptoDest);
        SendMessage (hwndDlg, WM_PUTUPSRCRECT, (DWORD)hdcSrc,  (LONG)ptoSrc);
        SendMessage (hwndDlg, WM_PUTUPMASKPT,  (DWORD)hdcMask, (LONG)ptoMask);

        /* repaint the whole window. */
        InvalidateRect (hwnd, NULL, TRUE);
    } break;

    /**********************************************************************\
    *  WM_PAINT
    *
    * miniWidth, rect -- set by WM_SIZE message.
    *
    * First shift the viewport origin down so that 0,0 is the top left
    *  most visible point (out from underneath the top dialog).  Second,
    *  draw the grid with wider lines on the axes.  Finally, read the
    *  values out of the top dialog, do elementary validation, and then
    *  try to call plgblt() with the values.
    \**********************************************************************/
     case WM_PAINT: {
        HDC hdc;
        PAINTSTRUCT ps;

        hdc = BeginPaint(hwnd, &ps);

        /* Draw Separator lines for the three miniareas */
        SelectObject(hdc, hPenSeparator);
        MoveToEx (hdc,   miniWidth-SEPARATORWIDTH,0, NULL);
        LineTo   (hdc,   miniWidth-SEPARATORWIDTH, rect.bottom);
        MoveToEx (hdc, 2*miniWidth-SEPARATORWIDTH,0, NULL);
        LineTo   (hdc, 2*miniWidth-SEPARATORWIDTH, rect.bottom);

        /* Grid the HDCs */
        SelectObject(hdcSrc, hPenGrid);
        DrawGrids (hdcSrc, miniWidth, rect.bottom);
        SelectObject(hdcMask, hPenGrid);
        DrawGrids (hdcMask, miniWidth, rect.bottom);

        /* Draw bitmaps if any, then draw track objects over them. */
        if (hbmSrc)  DrawBitmap (hdcSrc, hbmSrc);
        if (hbmMask) DrawBitmap (hdcMask, hbmMask);
        doTrackObject (ptoSrc , TROB_PAINT, hwnd, 0);
        doTrackObject (ptoMask, TROB_PAINT, hwnd, 0);

        /* paint the left third of the window. */
        SendMessage (hwnd, WM_PLGBLT, 0,0);

        EndPaint (hwnd, &ps);
    } return FALSE;

    /**********************************************************************\
    *  WM_PLGBLT
    *
    * WM_USER message.  This paints the left third of the window.  It
    *  is called on the WM_PAINT message.  It is separated out here because
    *  it is common for just the plgblt() to need to be called and not the
    *  whole window painted.
    \**********************************************************************/
    case WM_PLGBLT: {
        POINT lpPoint[3];
        int XSrc, YSrc, nWidth, nHeight, XMask, YMask;
        BOOL success;
        RECT cliprect;

        doTrackObject (ptoSrc , TROB_PAINT, hwnd, 0);
        doTrackObject (ptoMask, TROB_PAINT, hwnd, 0);

        GetClipBox (hdcDest, &cliprect);
        FillRect (hdcDest, &cliprect,
                  (HBRUSH) GetClassLong (hwnd, GCL_HBRBACKGROUND));
        SelectObject(hdcDest, hPenGrid);

        DrawGrids (hdcDest, miniWidth, rect.bottom);
        if (IsWindow(hwndDlg)) {

          /* Grab points out of the dialog entry fields. */
          lpPoint[0].x = GetDlgItemInt(hwndDlg, DID_P1X, &success, TRUE);
          lpPoint[0].y = GetDlgItemInt(hwndDlg, DID_P1Y, &success, TRUE);
          lpPoint[1].x = GetDlgItemInt(hwndDlg, DID_P2X, &success, TRUE);
          lpPoint[1].y = GetDlgItemInt(hwndDlg, DID_P2Y, &success, TRUE);
          lpPoint[2].x = GetDlgItemInt(hwndDlg, DID_P3X, &success, TRUE);
          lpPoint[2].y = GetDlgItemInt(hwndDlg, DID_P3Y, &success, TRUE);
          XSrc = GetDlgItemInt(hwndDlg, DID_XSRC, &success, TRUE);
          YSrc = GetDlgItemInt(hwndDlg, DID_YSRC, &success, TRUE);
          nWidth = GetDlgItemInt(hwndDlg, DID_WIDTH, &success, TRUE);
          nHeight = GetDlgItemInt(hwndDlg, DID_HEIGHT, &success, TRUE);
          XMask = GetDlgItemInt(hwndDlg, DID_XMASK, &success, TRUE);
          YMask = GetDlgItemInt(hwndDlg, DID_YMASK, &success, TRUE);

          /**********************************************************/
          /**********************************************************/
          PlgBlt (hdcDest, lpPoint,
                  hdcSrc, XSrc, YSrc, nWidth, nHeight,
                  hbmMask, XMask, YMask);
          /**********************************************************/
          /**********************************************************/
        }
        doTrackObject (ptoSrc , TROB_PAINT, hwnd, 0);
        doTrackObject (ptoMask, TROB_PAINT, hwnd, 0);
    } break;

    /**********************************************************************\
    *  WM_LBUTTONDOWN & WM_RBUTTONDOWN
    * On button down messages, hittest on the track object, and if
    *  it returns true, then send these messages to the track object.
    \**********************************************************************/
    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
      if (doTrackObject(ptoDest, TROB_HITTEST, hwnd, lParam))
         doTrackObject(ptoDest, message, hwnd, lParam);
      else if (doTrackObject(ptoSrc, TROB_HITTEST, hwnd, lParam))
         doTrackObject(ptoSrc, message, hwnd, lParam);
      else if (doTrackObject(ptoMask, TROB_HITTEST, hwnd, lParam))
         doTrackObject(ptoMask, message, hwnd, lParam);
    break;

    /**********************************************************************\
    *  WM_LBUTTONUP & WM_RBUTTONDOWN & MW_MOUSEMOVE
    * If the track object is in a "tracking mode" then send it these messages.
    *  If the transform dialog is not minimized, fill it with numbers.
    *  If the mouse dialog is not minimized, fill it with numbers.
    \**********************************************************************/
    case WM_RBUTTONUP:
    case WM_LBUTTONUP:
      /* user action complete.  Force plgblt() update. */
      PostMessage (hwndMain, WM_PLGBLT, 0,0);
    case WM_MOUSEMOVE:
      if (ptoDest->Mode) {
        doTrackObject(ptoDest, message, hwnd, lParam);
        SendMessage (hwndDlg, WM_PUTUPLPOINTS, (DWORD) hdcDest, (LONG) ptoDest);
      }
      if (ptoSrc->Mode) {
        doTrackObject(ptoSrc, message, hwnd, lParam);
        SendMessage (hwndDlg, WM_PUTUPSRCRECT, (DWORD) hdcSrc, (LONG) ptoSrc);
      }

      if (ptoMask->Mode) {
        doTrackObject(ptoMask, message, hwnd, lParam);
        SendMessage (hwndDlg, WM_PUTUPMASKPT, (DWORD) hdcMask, (LONG) ptoMask);
      }

    break;

    /**********************************************************************\
    *  WM_SETFOCUS
    *
    * The main window should never have the focus.  Set it back
    *  to the top dialog.
    \**********************************************************************/
    case WM_SETFOCUS: SetFocus (hwndDlg);
        return 0;

    /**********************************************************************\
    *  Menu item support.
    *
    \**********************************************************************/
    case WM_COMMAND:
      switch (LOWORD(wParam)) {
        HBITMAP hbmCompat, hbmOld;
        HDC hdcCompat;

        /******************************************************************\
        *  WM_COMMAND, IDM_COPY
        *
        * Create a new bitmap, copy the destination HDC bits into it,
        *  and send the new bitmap to the clipboard.
        \******************************************************************/
        case IDM_COPY: {
          int X[4],Y[4];
          int nWidth, nHeight;
          int Xmin, Ymin, Xmax, Ymax;
          BOOL success;
          int i;

          for (i = 0; i<3 ; i++) {
            X[i] = GetDlgItemInt(hwndDlg, DID_P1X + 2*i, &success, TRUE);
            Y[i] = GetDlgItemInt(hwndDlg, DID_P1Y + 2*i, &success, TRUE);
          }

          X[3] = (X[1] - X[0]) + X[2];
          Y[3] = (Y[2] - Y[0]) + Y[1];

          Xmin = Xmax = X[0];
          Ymin = Ymax = Y[0];

          for (i = 1; i<4 ; i++) {
            Xmin = (X[i] < Xmin) ? X[i] : Xmin;
            Ymin = (Y[i] < Ymin) ? Y[i] : Ymin;
            Xmax = (X[i] > Xmax) ? X[i] : Xmax;
            Ymax = (Y[i] > Ymax) ? Y[i] : Ymax;
          }

          nWidth = Xmax - Xmin;
          nHeight = Ymax - Ymin;

          hdcCompat = CreateCompatibleDC(hdcDest);
          hbmCompat = CreateCompatibleBitmap (hdcDest, nWidth, nHeight);
          hbmOld = SelectObject(hdcCompat,hbmCompat);

          BitBlt (hdcCompat, 0,0,nWidth, nHeight, hdcDest, Xmin,Ymin, SRCCOPY );

          SelectObject(hdcCompat,hbmOld);
          DeleteDC(hdcCompat);

          OpenClipboard (hwnd);
          SetClipboardData (CF_BITMAP,hbmCompat);
          CloseClipboard ();

          DeleteObject (hbmCompat);

        } break;

        /******************************************************************\
        *  WM_COMMAND, IDM_PASTE
        *
        * Get bitmap handle from clipboard, create a new bitmap, draw
        *  the clipboard bitmap into the new one, and store the new
        *  handle in the global hbmSrc.
        \******************************************************************/
        case IDM_PASTE: {
          HBITMAP hbm;
          BITMAP bm;

          OpenClipboard (hwnd);
          if ( hbm = GetClipboardData (CF_BITMAP)) {
            DeleteObject (hbmSrc);

            GetObject (hbm, sizeof(BITMAP), &bm);

            hdcCompat = CreateCompatibleDC(hdcDest);
            hbmCompat = CreateCompatibleBitmap (hdcDest, bm.bmWidth, bm.bmHeight);
            hbmOld = SelectObject(hdcCompat,hbmCompat);

            DrawBitmap (hdcCompat, hbm);

            SelectObject(hdcCompat,hbmOld);
            DeleteDC(hdcCompat);

            hbmSrc = hbmCompat;

            InvalidateRect (hwnd, &ptoSrc->rectClip, TRUE);
            InvalidateRect (hwnd, &ptoDest->rectClip, TRUE);
          }
          CloseClipboard ();
        } break;

        /******************************************************************\
        *  WM_COMMAND, IDM_BOTH
        *
        * Post a COPY and PASTE command message to this window so that with
        *  one key stroke the user can copy the DEST image into the clipboard,
        *  paste it down into the SRC hdc and cause the blt.
        \******************************************************************/
        case IDM_BOTH:

          PostMessage (hwnd, WM_COMMAND, MAKELONG (IDM_COPY , 1), 0);
          PostMessage (hwnd, WM_COMMAND, MAKELONG (IDM_PASTE, 1), 0);

        break;

        /******************************************************************\
        *  WM_COMMAND, IDM_MODE_*
        *
        * manage mutually exclusive menu.
        *  call SetStretchBltMode() for the global destination hdc.
        \******************************************************************/
        case IDM_MODE_BLACKONWHITE:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_MODE_BLACKONWHITE, MF_CHECKED);
          CheckMenuItem(hMenu, IDM_MODE_COLORONCOLOR, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_WHITEONBLACK, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_HALFTONE    , MF_UNCHECKED);

          SetStretchBltMode (hdcDest,BLACKONWHITE);
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

          } return 0;

        case IDM_MODE_COLORONCOLOR:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_MODE_BLACKONWHITE, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_COLORONCOLOR, MF_CHECKED);
          CheckMenuItem(hMenu, IDM_MODE_WHITEONBLACK, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_HALFTONE    , MF_UNCHECKED);

          SetStretchBltMode (hdcDest,COLORONCOLOR);
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

          } return 0;

        case IDM_MODE_WHITEONBLACK:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_MODE_BLACKONWHITE, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_COLORONCOLOR, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_WHITEONBLACK, MF_CHECKED);
          CheckMenuItem(hMenu, IDM_MODE_HALFTONE    , MF_UNCHECKED);

          SetStretchBltMode (hdcDest,WHITEONBLACK);
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

          } return 0;

        case IDM_MODE_HALFTONE    :
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_MODE_BLACKONWHITE, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_COLORONCOLOR, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_WHITEONBLACK, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_MODE_HALFTONE    , MF_CHECKED);

          SetStretchBltMode (hdcDest,HALFTONE);
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

          } return 0;

        /******************************************************************\
        *  WM_COMMAND, IDM_SPIN*
        *
        * manage mutually exclusive menu.
        *
        \******************************************************************/
        case IDM_SPINOFF:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_UNCHECKED);

          nSpin = FALSE;
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

          } return 0;

        case IDM_SPIN5 :
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_UNCHECKED);

          nSpin = 5;

          } return 0;

        case IDM_SPIN10:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_UNCHECKED);

          nSpin = 10;

          } return 0;

        case IDM_SPIN30:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_UNCHECKED);

          nSpin = 30;

          } return 0;

        case IDM_SPIN60:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_UNCHECKED);

          nSpin = 60;

          } return 0;

        case IDM_SPIN90:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINOFF, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN5  , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN10 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN30 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN60 , MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPIN90 , MF_CHECKED);

          nSpin = 90;

          } return 0;

        case IDM_FLIPONCE:
          nSpin = 90;
          SendMessage (hwndMain, WM_SPIN, 0,0);
          nSpin = FALSE;
          return 0;

        case IDM_SPINTOPLEFT:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINTOPLEFT, MF_CHECKED);
          CheckMenuItem(hMenu, IDM_SPINCENTER , MF_UNCHECKED);

          } return 0;

        case IDM_SPINCENTER:
          { HMENU hMenu;
          hMenu = GetMenu (hwnd);

          CheckMenuItem(hMenu, IDM_SPINTOPLEFT, MF_UNCHECKED);
          CheckMenuItem(hMenu, IDM_SPINCENTER , MF_CHECKED);

          } return 0;

        /******************************************************************\
        *  WM_COMMAND, DID_NEW*
        *
        * menu equivalents for buttons on top dialog.  Just pass along
        *  WM_COMMAND messages to the dialog.
        *
        \******************************************************************/
        case DID_NEWSRC:
        case DID_NEWMASK:
          SendMessage (hwndDlg, message, wParam, lParam);
        return 0;

        /******************************************************************\
        *  WM_COMMAND, IDM_ABOUT
        *
        \******************************************************************/
        case IDM_ABOUT:
          DialogBox (hInst, "aboutBox", hwnd, (DLGPROC)About);
        return 0;

      }  /* end switch */

    break;  /* end wm_command */

    /******************************************************************\
    *  WM_SPIN
    *
    * Set up a transform to modify the destination points.
    *  (Note that the transform in hdcDest remains the identity.)
    *  Loop through, reblitting to the transformed points.
    *  Erase behind the old bitmap by keeping track of the region uncovered.
    *
    \******************************************************************/
    case WM_SPIN: {

      XFORM  x;
      HDC hdc;
      float M11, M12, M21, M22;
      int nSteps, i;

      POINT pivot;
      POINT lpPoint[3];
      POINT lpRgnErase[4], lpRgnBmp[4];
      HRGN hrgnErase, hrgnBmp;
      HMENU hMenu;
      int XSrc, YSrc, nWidth, nHeight, XMask, YMask;
      BOOL success;

      /* validate the dialog on top with the parameters in it. */
      if (!IsWindow(hwndDlg)) return 0;

      /* Grab points out of the dialog entry fields. */
      lpPoint[0].x = GetDlgItemInt(hwndDlg, DID_P1X, &success, TRUE);
      lpPoint[0].y = GetDlgItemInt(hwndDlg, DID_P1Y, &success, TRUE);
      lpPoint[1].x = GetDlgItemInt(hwndDlg, DID_P2X, &success, TRUE);
      lpPoint[1].y = GetDlgItemInt(hwndDlg, DID_P2Y, &success, TRUE);
      lpPoint[2].x = GetDlgItemInt(hwndDlg, DID_P3X, &success, TRUE);
      lpPoint[2].y = GetDlgItemInt(hwndDlg, DID_P3Y, &success, TRUE);
      XSrc = GetDlgItemInt(hwndDlg, DID_XSRC, &success, TRUE);
      YSrc = GetDlgItemInt(hwndDlg, DID_YSRC, &success, TRUE);
      nWidth = GetDlgItemInt(hwndDlg, DID_WIDTH, &success, TRUE);
      nHeight = GetDlgItemInt(hwndDlg, DID_HEIGHT, &success, TRUE);
      XMask = GetDlgItemInt(hwndDlg, DID_XMASK, &success, TRUE);
      YMask = GetDlgItemInt(hwndDlg, DID_YMASK, &success, TRUE);

      /* get an HDC we can use to play with transforms. */
      hdc = GetDC (hwnd);
      SetGraphicsMode (hdc, GM_ADVANCED);

      /* check menu check to pivot on top-left corner, or pivot on center. */

hMenu = GetMenu (hwnd);

      if (GetMenuState(hMenu, IDM_SPINCENTER, MF_BYCOMMAND) & MF_CHECKED) {
        pivot.x = (lpPoint[1].x +lpPoint[2].x)/2;
        pivot.y = (lpPoint[1].y +lpPoint[2].y)/2;
      } else {
        pivot.x = lpPoint[0].x;
        pivot.y = lpPoint[0].y;
      }

      /* nSpin contains values reflecting the number of degrees per step.
       *  fill in the number of steps required (360 / nSpin), and fill in
       *  the precomputed transformation matrices.
       */
      switch (nSpin) {

        case 5:
          nSteps = 72;
          M11 =                   M22 = (float)0.9961946980917;
          M12 = (float)-0.08715574274766; M21 = (float)0.08715574274766;
          break;
        case 10:
          nSteps = 36;
          M11 =                   M22 = (float)0.984808;
          M12 = (float)-0.173648; M21 = (float)0.173648;
          break;
        case 30:
          nSteps = 12;
          M11 =                   M22 = (float)0.866025;
          M12 = (float)-0.5     ; M21 = (float)0.5;
          break;
        case 60:
          nSteps = 6;
          M11 =                   M22 = (float)0.5     ;
          M12 = (float)-0.866025; M21 = (float)0.866025;
          break;
        case 90:
          nSteps = 4;
          M11 =                   M22 = (float)0.0;
          M12 = (float)-1.0     ; M21 = (float)1.0;
          break;
        default:
          MessageBox (hwnd,
                      GetStringRes (IDS_INVALID),
                      NULL, MB_ICONHAND);
          return 0;

      } /* end switch nSpin */

      /* translate objects from pivot point to origin. */
      x.eM11 = (float)1.0;
      x.eM12 = (float)0.0;
      x.eM21 = (float)0.0;
      x.eM22 = (float)1.0;
      x.eDx = (float)-pivot.x;
      x.eDy = (float)-pivot.y;
      ModifyWorldTransform (hdc, &x, MWT_RIGHTMULTIPLY);

      /* rotate object about origin. */
      x.eM11 = M11;
      x.eM12 = M12;
      x.eM21 = M21;
      x.eM22 = M22;
      x.eDx = (float)0.0;
      x.eDy = (float)0.0;
      ModifyWorldTransform (hdc, &x, MWT_RIGHTMULTIPLY);

      /* translate objects back to pivot point. */
      x.eM11 = (float)1.0;
      x.eM12 = (float)0.0;
      x.eM21 = (float)0.0;
      x.eM22 = (float)1.0;
      x.eDx = (float)pivot.x;
      x.eDy = (float)pivot.y;
      ModifyWorldTransform (hdc, &x, MWT_RIGHTMULTIPLY);

      /* fill in initial region for erasure... the region containing bmp */
      lpRgnErase[0] = lpPoint[0];
      lpRgnErase[1] = lpPoint[1];
      lpRgnErase[3] = lpPoint[2];
      lpRgnErase[2].x =  lpPoint[1].x - lpPoint[0].x;
      lpRgnErase[2].x += lpPoint[2].x - lpPoint[0].x;
      lpRgnErase[2].x += lpPoint[0].x;
      lpRgnErase[2].y =  lpPoint[1].y - lpPoint[0].y;
      lpRgnErase[2].y += lpPoint[2].y - lpPoint[0].y;
      lpRgnErase[2].y += lpPoint[0].y;

      /* loop through transforming the points on each step. */
      for (i= 0; i<nSteps; i++) {

        hrgnErase = CreatePolygonRgn (lpRgnErase, 4, ALTERNATE);

        /* TRANSFORM the lpPoint[] destination extent points */
        LPtoDP (hdc, lpPoint, 3);

        /* create a region containing the new bitmap extents */
        lpRgnBmp[0] = lpPoint[0];
        lpRgnBmp[1] = lpPoint[1];
        lpRgnBmp[3] = lpPoint[2];
        lpRgnBmp[2].x =  lpPoint[1].x - lpPoint[0].x;
        lpRgnBmp[2].x += lpPoint[2].x - lpPoint[0].x;
        lpRgnBmp[2].x += lpPoint[0].x;
        lpRgnBmp[2].y =  lpPoint[1].y - lpPoint[0].y;
        lpRgnBmp[2].y += lpPoint[2].y - lpPoint[0].y;
        lpRgnBmp[2].y += lpPoint[0].y;
        hrgnBmp = CreatePolygonRgn (lpRgnBmp, 4, ALTERNATE);

        /* subtract the new bitmap extents region from the erasure region. */
        CombineRgn (hrgnErase, hrgnErase, hrgnBmp, RGN_DIFF);

        /* while we are here, get points ready for the next loop erasure */
        lpRgnErase[0] = lpPoint[0];
        lpRgnErase[1] = lpPoint[1];
        lpRgnErase[3] = lpPoint[2];
        lpRgnErase[2].x =  lpPoint[1].x - lpPoint[0].x;
        lpRgnErase[2].x += lpPoint[2].x - lpPoint[0].x;
        lpRgnErase[2].x += lpPoint[0].x;
        lpRgnErase[2].y =  lpPoint[1].y - lpPoint[0].y;
        lpRgnErase[2].y += lpPoint[2].y - lpPoint[0].y;
        lpRgnErase[2].y += lpPoint[0].y;

        /**********************************************************/
        PlgBlt (hdcDest, lpPoint,
                hdcSrc, XSrc, YSrc, nWidth, nHeight,
                hbmMask, XMask, YMask);
        /**********************************************************/

        /* need to flush graphics buffer, or regions are erased
         *  before the bitmap is drawn.
         */
        GdiFlush();

        /* erase the newly uncovered region. */
        FillRgn (hdcDest, hrgnErase, BACKGROUNDBRUSH );
        DeleteObject (hrgnErase);
        DeleteObject (hrgnBmp);
      } /* end for loop */

      /* because of roundoff error, the 'nSteps'th rotation will not
       *  always bring the parallelogram around to the 0th position.
       *  So we special case the last position, and do one more erase.
       *  Try commenting this out, and see the little glitches left
       *  on the screen if this comment is unclear.
       */
      lpRgnErase[0] = lpPoint[0];
      lpRgnErase[1] = lpPoint[1];
      lpRgnErase[3] = lpPoint[2];
      lpRgnErase[2].x =  lpPoint[1].x - lpPoint[0].x;
      lpRgnErase[2].x += lpPoint[2].x - lpPoint[0].x;
      lpRgnErase[2].x += lpPoint[0].x;
      lpRgnErase[2].y =  lpPoint[1].y - lpPoint[0].y;
      lpRgnErase[2].y += lpPoint[2].y - lpPoint[0].y;
      lpRgnErase[2].y += lpPoint[0].y;
      hrgnErase = CreatePolygonRgn (lpRgnErase, 4, ALTERNATE);
      lpPoint[0].x = GetDlgItemInt(hwndDlg, DID_P1X, &success, TRUE);
      lpPoint[0].y = GetDlgItemInt(hwndDlg, DID_P1Y, &success, TRUE);
      lpPoint[1].x = GetDlgItemInt(hwndDlg, DID_P2X, &success, TRUE);
      lpPoint[1].y = GetDlgItemInt(hwndDlg, DID_P2Y, &success, TRUE);
      lpPoint[2].x = GetDlgItemInt(hwndDlg, DID_P3X, &success, TRUE);
      lpPoint[2].y = GetDlgItemInt(hwndDlg, DID_P3Y, &success, TRUE);
      lpRgnBmp[0] = lpPoint[0];
      lpRgnBmp[1] = lpPoint[1];
      lpRgnBmp[3] = lpPoint[2];
      lpRgnBmp[2].x =  lpPoint[1].x - lpPoint[0].x;
      lpRgnBmp[2].x += lpPoint[2].x - lpPoint[0].x;
      lpRgnBmp[2].x += lpPoint[0].x;
      lpRgnBmp[2].y =  lpPoint[1].y - lpPoint[0].y;
      lpRgnBmp[2].y += lpPoint[2].y - lpPoint[0].y;
      lpRgnBmp[2].y += lpPoint[0].y;
      hrgnBmp = CreatePolygonRgn (lpRgnBmp, 4, ALTERNATE);
      CombineRgn (hrgnErase, hrgnErase, hrgnBmp, RGN_DIFF);
      FillRgn (hdcDest, hrgnErase, BACKGROUNDBRUSH );
      DeleteObject (hrgnErase);
      DeleteObject (hrgnBmp);

      ReleaseDC (hwnd, hdc);

    } return 0; /* end WM_SPIN message */

  } /* end switch */
  return (DefWindowProc(hwnd, message, wParam, lParam));
}

/**************************************************************************\
*
*  function:  DlgProc()
*
*  input parameters:  normal window procedure parameters.
*
*  Respond to user button presses by getting new bitmaps or by sending
*   the main window a WM_PLGBLT message.  Also handle special user messages
*   for updating the entry fields with the contents of the direct manipulation
*   objects.
*
*  global variables:
*   hwndMain - the main window.  also the parent of this dialog
*   ptoDest, ptoSrc, ptoMask - pointers to the direct manipulation objects
*   hdcDest, hdcSrc, hdcMask - HDCs for the 3 sub regions of the window.
*   hbmSrc, hbmMask          - bitmap handles for source and mask.
\**************************************************************************/
LRESULT CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

  switch (message) {
    /**********************************************************************\
    *  WM_INITDIALOG
    *
    * Fill the entry fields with sensible original values.
    \**********************************************************************/
    case WM_INITDIALOG:
        SetDlgItemText(hwnd, DID_P1X     , "0");
        SetDlgItemText(hwnd, DID_P1Y     , "0");
        SetDlgItemText(hwnd, DID_P2X     , "0");
        SetDlgItemText(hwnd, DID_P2Y     , "0");
        SetDlgItemText(hwnd, DID_P3X     , "0");
        SetDlgItemText(hwnd, DID_P3Y     , "0");
        SetDlgItemText(hwnd, DID_XSRC    , "0");
        SetDlgItemText(hwnd, DID_YSRC    , "0");
        SetDlgItemText(hwnd, DID_WIDTH   , "0");
        SetDlgItemText(hwnd, DID_HEIGHT  , "0");
        SetDlgItemText(hwnd, DID_XMASK   , "0");
        SetDlgItemText(hwnd, DID_YMASK   , "0");
    return TRUE;

    /**********************************************************************\
    *  WM_PUTUPLPOINTS
    *
    * wParam -  HDC with the needed world transform.
    * lParam -  Pointer to the track object.
    *
    * Fill the entry fields for the array of 3 dest parallelogram points.
    *  Conditionally change the first point depending on tracking mode.
    \**********************************************************************/
    case WM_PUTUPLPOINTS: {
        POINT p, origin;
        PTrackObject pto;
        HDC hdc;

        hdc = (HDC) wParam;
        pto = (PTrackObject) lParam;
        GetViewportOrgEx (hdc, &origin);

        if (pto->Mode & TMMOVE) {
          p.x = pto->rect.left;
          p.y = pto->rect.top;
          LPtoDP (pto->hdc, &p, 1);
          p.x -= origin.x; p.y -= origin.y;

          SetDlgItemInt(hwnd, DID_P1X, p.x, TRUE);
          SetDlgItemInt(hwnd, DID_P1Y, p.y, TRUE);

        }

        p.x = pto->rect.right;
        p.y = pto->rect.top;
        LPtoDP (pto->hdc, &p, 1);
        p.x -= origin.x; p.y -= origin.y;

        SetDlgItemInt(hwnd, DID_P2X, p.x, TRUE);
        SetDlgItemInt(hwnd, DID_P2Y, p.y, TRUE);

        p.x = pto->rect.left;
        p.y = pto->rect.bottom;
        LPtoDP (pto->hdc, &p, 1);
        p.x -= origin.x; p.y -= origin.y;
        SetDlgItemInt(hwnd, DID_P3X, p.x, TRUE);
        SetDlgItemInt(hwnd, DID_P3Y, p.y, TRUE);

    } return FALSE;

    /**********************************************************************\
    *  WM_PUTUPSRCRECT
    *
    * wParam -  HDC with the needed world transform.
    * lParam -  Pointer to the track object.
    *
    * Fill the entry fields for the source rectangle points.
    *  Conditionally change <x,y> or <width,height> depending on tracking mode.
    \**********************************************************************/
    case WM_PUTUPSRCRECT: {
        POINT p1,p2, origin;
        PTrackObject pto;
        HDC hdc;

        hdc = (HDC) wParam;
        pto = (PTrackObject) lParam;
        GetViewportOrgEx (hdc, &origin);

        p1.x = pto->rect.left;
        p1.y = pto->rect.top;
        LPtoDP (pto->hdc, &p1, 1);

        p2.x = pto->rect.right;
        p2.y = pto->rect.bottom;
        LPtoDP (pto->hdc, &p2, 1);
        p2.x -= p1.x; p2.y -= p1.y;

        p1.x -= origin.x; p1.y -= origin.y;

        if (!(pto->Mode & TMSIZEXY)) {
          SetDlgItemInt(hwnd, DID_XSRC, p1.x, TRUE);
          SetDlgItemInt(hwnd, DID_YSRC, p1.y, TRUE);
        }

        if (!(pto->Mode & TMMOVE)) {
          SetDlgItemInt(hwnd, DID_WIDTH,  p2.x, TRUE);
          SetDlgItemInt(hwnd, DID_HEIGHT, p2.y, TRUE);
        }
    } return FALSE;

    /**********************************************************************\
    *  WM_PUTUPMASKPT
    *
    * wParam -  HDC with the needed world transform.
    * lParam -  Pointer to the track object.
    *
    * Fill the entry fields for the mask location point.
    \**********************************************************************/
    case WM_PUTUPMASKPT: {
        POINT p1, origin;
        PTrackObject pto;
        HDC hdc;

        hdc = (HDC) wParam;
        pto = (PTrackObject) lParam;
        GetViewportOrgEx (hdc, &origin);

        p1.x = pto->rect.left;
        p1.y = pto->rect.top;
        LPtoDP (pto->hdc, &p1, 1);
        p1.x -= origin.x; p1.y -= origin.y;

        SetDlgItemInt(hwnd, DID_XMASK, p1.x, TRUE);
        SetDlgItemInt(hwnd, DID_YMASK, p1.y, TRUE);

    } return FALSE;

    /**********************************************************************\
    *  WM_COMMAND, DID_DRAW
    *
    * Draw button hit - send main window message to call PlgBlt().
    \**********************************************************************/
    case WM_COMMAND: {
      HBITMAP hbm;

      if (LOWORD(wParam) == DID_DRAW) {
          SendMessage (hwndMain, WM_PLGBLT, 0,0);

    /**********************************************************************\
    *  WM_COMMAND, DID_NEWSRC
    *
    * Try to get a new source bitmap.  Then
    *  invalidate two sub windows so that we force a repaint.
    \**********************************************************************/
      } else if (LOWORD(wParam) == DID_NEWSRC) {
        if ( hbm = GetBitmap (hdcSrc, hInst, FALSE)) {
          DeleteObject (hbmSrc);
          hbmSrc = hbm;
          InvalidateRect (hwndMain, &ptoSrc->rectClip, TRUE);
          InvalidateRect (hwndMain, &ptoDest->rectClip, TRUE);
        }

    /**********************************************************************\
    *  WM_COMMAND, DID_Mask
    *
    * Try to get a new mask bitmap.  Then
    *  invalidate two sub windows so that we force a repaint.
    \**********************************************************************/
      } else if (LOWORD(wParam) == DID_NEWMASK) {
        if ( hbm = GetBitmap (hdcMask,  hInst, TRUE)) {
          DeleteObject (hbmMask);
          hbmMask = hbm;
          InvalidateRect (hwndMain, &ptoMask->rectClip, TRUE);
          InvalidateRect (hwndMain, &ptoDest->rectClip, TRUE);
        }
      }

    } return FALSE; /* end WM_COMMAND */

  } /* end switch */
  return 0;
}

#define TICKSPACE     20

/**************************************************************************\
*
*  function:  DrawGrids()
*
*  input parameters:
*   hdc - Device context to draw into.
*   width, height - size of the rectangle to fill with grids.
*
*  global variables:  none.
*
\**************************************************************************/
VOID DrawGrids (HDC hdc, int width, int height)
{
int i;

    /* Draw vertical lines. Double at the axis */
    for (i = 0; i<= width; i+=TICKSPACE){
      MoveToEx (hdc, i, 0, NULL);
      LineTo (hdc, i, height);
    }
    MoveToEx (hdc, 1, 0, NULL);
    LineTo (hdc, 1, height);

    /* Draw horizontal lines. Double at the axis */
    for (i = 0; i<= height; i+=TICKSPACE){
      MoveToEx (hdc, 0,i, NULL);
      LineTo (hdc, width,i);
    }
    MoveToEx (hdc, 0, 1, NULL);
    LineTo (hdc, width,1);

  return;
}

/****************************************************************************
    FUNCTION: About
****************************************************************************/
LRESULT CALLBACK About(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  if ((message == WM_COMMAND) && (LOWORD(wParam) == IDOK)) {
    EndDialog (hwnd, TRUE);
    return TRUE;
  }
  if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) {
    EndDialog (hwnd, TRUE);
    return TRUE;
  }
  return FALSE;
}

/******************************************************************************\
*
*  FUNCTION:    GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:    Load the resource string with the ID given, and return a
*               pointer to it.  Notice that the buffer is common memory so
*               the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


PLGBLT.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/* plgblt.h - header file for the PlgBlt() demonstration program. */

/* function prototypes for the window procedures. */
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DlgProc    (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About      (HWND, UINT, WPARAM, LPARAM);

VOID DrawGrids (HDC, int, int);
LPTSTR  GetStringRes (int);

/* Top dialog item IDs */
#define  DID_P1X       101
#define  DID_P1Y       102
#define  DID_P2X       103
#define  DID_P2Y       104
#define  DID_P3X       105
#define  DID_P3Y       106
#define  DID_XSRC      110
#define  DID_YSRC      111
#define  DID_WIDTH     112
#define  DID_HEIGHT    113
#define  DID_XMASK     114
#define  DID_YMASK     115
#define  DID_NEWSRC    160
#define  DID_NEWMASK   161

#define  DID_DRAW    200

#define IDM_COPY            300
#define IDM_PASTE           301
#define IDM_BOTH            302
#define IDM_SPINOFF         310
#define IDM_FLIPONCE        311
#define IDM_SPIN5           312
#define IDM_SPIN10          313
#define IDM_SPIN30          314
#define IDM_SPIN60          315
#define IDM_SPIN90          316
#define IDM_SPINTOPLEFT     317
#define IDM_SPINCENTER      318
#define IDM_MODE_BLACKONWHITE    320
#define IDM_MODE_COLORONCOLOR    321
#define IDM_MODE_WHITEONBLACK    322
#define IDM_MODE_HALFTONE        323
#define IDM_ABOUT           330

/* string table ids's */
#define IDS_NTONLY        16
#define IDS_WINDOWTITLE   17
#define IDS_INVALID       18
#define IDS_MONOCHROME    19

/* Misc. defines for size, color, and appearance of drawing. */
#define GRIDCOLOR  (COLORREF) 0x01000006
#define DIALOGHEIGHT  90
#define SEPARATORWIDTH 4
#define MAXCHARS      32
#define INC            5

/* other user messages sent to the main window or the dialog */
#define WM_PUTUPLPOINTS  WM_USER+20
#define WM_PUTUPSRCRECT  WM_USER+21
#define WM_PUTUPMASKPT   WM_USER+22
#define WM_PLGBLT        WM_USER+23
#define WM_SPIN          WM_USER+24


BITMAP.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  bitmap.c -- support for reading in and drawing bitmaps.
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include "plgblt.h"

/**************************************************************************\
*
*  function:  DrawBitmap()
*
*  input parameters:  HDC, HBITMAP
*
*  Draw the bitmap into the hdc.  Source rectangle computed to include the
*   whole bitmap.  Destination location is 0,0.
*
*  global variables: none.
*
\**************************************************************************/
VOID DrawBitmap (HDC hdc, HBITMAP hbm)
{
    BOOL f;
    HDC hdcBits;
    BITMAP bm;

    hdcBits = CreateCompatibleDC(hdc);
    GetObject (hbm, sizeof(BITMAP), &bm);
    SelectObject(hdcBits,hbm);
    f = BitBlt(hdc,0,0,bm.bmWidth, bm.bmHeight,hdcBits,0,0,SRCCOPY);
    DeleteDC(hdcBits);
}

/**************************************************************************\
*
*  function:  GetBitmap()
*
*  input parameters:
*   hdc - hdc to make the bitmap compatible with.
*   hInst - instance handle
*
*  Put up a common dialog box to open a new *.BMP file.
*   Once this is complete, open the file, read in the information,
*   and create a compatible bitmap.
*
*  returns:  handle to the bitmap iff successful.  NULL otherwise.
*
\**************************************************************************/
HBITMAP GetBitmap (HDC hdc, HANDLE hInst, BOOL monochrome)
{
    HBITMAP hbm;
    PBITMAPFILEHEADER pbmfh;
    PBITMAPINFOHEADER pbmih;
    PBYTE             pBits;
    int fh;
    int bfOffBits;
    int nbytes;
    OPENFILENAME  of;
    char buffer [MAX_PATH];

    buffer[0] = 0;

    /* set up the OPENFILE structure,
     *  then use the appropriate common dialog
     */
    of.lStructSize       = sizeof (OPENFILENAME);
    of.hwndOwner         = NULL;
    of.hInstance         = hInst;
    of.lpstrCustomFilter = NULL;
    of.nMaxCustFilter    = 0;
    of.nFilterIndex      = 0;
    of.lpstrFile         = buffer;
    of.nMaxFile          = MAX_PATH;
    of.lpstrFileTitle    = NULL;
    of.nMaxFileTitle     = 0;
    of.lpstrInitialDir   = "c:\\nt\\windows";
    of.lpstrTitle        = NULL;
    of.Flags             = OFN_HIDEREADONLY;
    of.nFileOffset       = 0;
    of.nFileExtension    = 0;
    of.lpstrDefExt       = NULL;
    of.lCustData         = 0;
    of.lpfnHook          = NULL;
    of.lpTemplateName    = NULL;

    if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE)
      of.lpstrFilter       = "        (*.bmp)\000 *.BMP\000\000";
    else
      of.lpstrFilter       = "Bitmaps\000 *.BMP\000\000";

    if (!GetOpenFileName (&of)) return NULL;

    /* Try to open the file.  If successful, then allocate space for it,
     *  and read in all of the bytes.
     */
    fh = _lopen (buffer, OF_READ);
    if (fh == -1) return NULL;
    nbytes = GetFileSize ((HANDLE) fh, NULL);

    /* The contents of the file are read in here in three parts.  First
     *  the bitmap file header is read, then the bitmap header along with
     *  the color table, then finally the actual bit data.  I.e. from
     *  a total of nbytes...
     *    1.  sizeof (BITMAPFILEHEADER)
     *    2.  bfOffBits- sizeof (BITMAPFILEHEADER)
     *    3.  (nbytes - bfOffBits)
     */

    /* read in the bitmap file header.  save the offset to bits. */
    if (!(pbmfh = (PBITMAPFILEHEADER)LocalAlloc (LPTR, sizeof (BITMAPFILEHEADER))))
        return NULL;
    _lread (fh, (LPSTR)pbmfh, sizeof (BITMAPFILEHEADER));
    bfOffBits=pbmfh->bfOffBits;

    /* read in the bitmap info header and the color table right after it.
     * both BITMAPINFOHEADER and BITMAPINFO needed for CreateDIBitmap()
     */
    if (!(pbmih = (PBITMAPINFOHEADER)LocalAlloc (LPTR, bfOffBits- sizeof (BITMAPFILEHEADER))))
        return NULL;
    _lread (fh, (LPSTR)pbmih, bfOffBits- sizeof (BITMAPFILEHEADER));

    /* finally read in the bit data. */
    if (!(pBits = (PBYTE)LocalAlloc (LPTR, (nbytes - bfOffBits)))) return NULL;
    _lread (fh, (LPSTR)pBits, (nbytes - bfOffBits));

    /* in the case of desiring a monochrome bitmap (input parameter),
     *  verify that is what we got.  If it is, then use CreateBitmap,
     *  else use a device independent bitmap.
     */
    if (monochrome) {
      if (pbmih->biBitCount != 1) {
        MessageBox (NULL,
          GetStringRes (IDS_MONOCHROME),
          NULL, MB_APPLMODAL | MB_ICONSTOP | MB_OK);
        hbm = NULL;
      } else {
BITMAP bmpInf;
memset( &bmpInf, 0, sizeof(bmpInf) );
bmpInf.bmWidth = pbmih->biWidth;
bmpInf.bmHeight = pbmih->biHeight;
bmpInf.bmWidthBytes = ((pbmih->biWidth+31)&(~31))/8;
bmpInf.bmPlanes = pbmih->biPlanes;
bmpInf.bmBitsPixel = pbmih->biBitCount;
bmpInf.bmBits = pBits;

        hbm = CreateBitmapIndirect ( &bmpInf );
      }
    } else  /* bitmap is NOT monochrome, use DIB. */
      hbm = CreateDIBitmap (hdc, pbmih, CBM_INIT,
                          pBits, (PBITMAPINFO) pbmih, DIB_RGB_COLORS);

    /* hbm is set... free up memory, and return */
    LocalFree (LocalHandle ((LPSTR)pBits));
    LocalFree (LocalHandle ((LPSTR)pbmih));
    LocalFree (LocalHandle ((LPSTR)pbmfh));
    _lclose (fh);

    return hbm;
}


TRACK.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  track.c -- support for direct manipulation of parallelogram object.
\**************************************************************************/

#include <windows.h>
#include <math.h>
#include "track.h"

#define EPSILON  (float) 0.0001
#define RECTSIZE  60

/**************************************************************************\
*
*  function:  doTrackObject()
*
*  input parameters:
*   pto -  pointer to a track object.
*   msg -  message selecting what action to take.  Values may include WM_*'s
*           (see case statements below for more information.)
*   hwnd - Window handle for the window the track object exists within.
*   lParam - Usually fourth param to window proc. varies based on msg.
*
*  global variables:  none.
*
*  coordinate spaces:  There are three coordinate spaces of interest here,
*   and this routine is frequently switching between them...
*
*           WORLD                   DEVICE                  SCREEN
*
*      object coordinates       input mouse pos       used w/ SetCursorPos()
*         (pto->rect)          (lParam for WM_*)
*
*             ----->  LPtoDP() ---->    ----> ClientToScreen() -->
*             <-----  DPtoLP() <----    <---- ScreenToClient() <--
*
*   in addition, the HDC has an offset origin.  Device coordinates for the
*   mouse (lParam) never take this into account, but it is necessary to
*   translate them in order to get direct manipulation right.
*
\**************************************************************************/
PTrackObject doTrackObject(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
{
  if ((pto == NULL) && (msg != TROB_NEW))  return NULL;

  switch (msg) {

    /**********************************************************************\
    *  TROB_NEW
    *
    * Allocate new PTrackObject structure.  Fill in default values
    *  for the fields of the structure.  Set up the HDC correctly.
    * return - pointer to the new object.
    \**********************************************************************/
    case  TROB_NEW: {
        PTrackObject  pto;

        /* with LPTR returned value is a pointer. */
        pto = (PTrackObject) LocalAlloc (LPTR, sizeof (TrackObject));

        /* initialize the HDC and other fields. */
        pto->hdc = GetDC (hwnd);
        SetGraphicsMode (pto->hdc, GM_ADVANCED);
        SetROP2(pto->hdc, R2_NOT);
        SelectObject (pto->hdc, GetStockObject (NULL_BRUSH));
        pto->Mode = TMNONE;
        pto->allowedModes = TMMOVE | TMSIZEXY;

        GetWorldTransform (pto->hdc, &(pto->xfmChange));

        /* initialize the size. */
        pto->rect.top = pto->rect.left = 0;
        pto->rect.bottom = pto->rect.right = RECTSIZE;

        return (pto);
    }

    /**********************************************************************\
    *  TROB_DELETE
    *
    * Complement of TROB_NEW.  Free up the memory allocated for the object.
    \**********************************************************************/
    case  TROB_DELETE:
        doTrackObject (pto, TROB_PAINT, hwnd, lParam);
        ReleaseDC (hwnd, pto->hdc);
        LocalFree (LocalHandle ((LPSTR)pto));
    return NULL;

    /**********************************************************************\
    *  TROB_PAINT
    *
    * Paint the object into its hdc.  Called half the time to erase
    *  the object, and half the time to redraw it.
    \**********************************************************************/
    case TROB_PAINT: {
        MoveToEx (pto->hdc, pto->rect.right, pto->rect.top, NULL);
        LineTo (pto->hdc,  pto->rect.left, pto->rect.top);
        LineTo (pto->hdc,  pto->rect.left, pto->rect.bottom);

        if (pto->allowedModes & TMSIZEXY) {
          LineTo (pto->hdc,  pto->rect.right, pto->rect.bottom);
          LineTo (pto->hdc,  pto->rect.right, pto->rect.top);
        }

        if (pto->allowedModes & TMROTATE) {
          MoveToEx (pto->hdc, pto->rect.left, pto->rect.bottom/ 4, NULL);
          AngleArc (pto->hdc, pto->rect.left, pto->rect.top,
                 (DWORD) pto->rect.bottom/ 4, (float) 270.0, (float) 90.0);
        }

    } return NULL;

    /**********************************************************************\
    *  TROB_HITTEST
    *
    * Check the point sent in in the lParam to see if it lays within
    *  the bounds of the objects defining rectangle.
    * return - pointer to the object iff the point is in rectangle,
    *  otherwise return NULL.
    \**********************************************************************/
    case TROB_HITTEST:{
        POINT  mouWorld;
        mouWorld.x = LOWORD(lParam);
        mouWorld.y = HIWORD(lParam);

        DPtoLP (pto->hdc, &mouWorld, 1);

        if (PtInRect (&pto->rect, mouWorld))  return pto;
        else  return NULL;
    }

    /**********************************************************************\
    *  WM_LBUTTONDOWN &  WM_RBUTTONDOWN
    *
    * Capture the mouse, set the tracking mode depending on the mouse
    *  location in world coordinates, reset the mouse position.
    *
    \**********************************************************************/
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN: {
      POINT  newmouScreen;
      POINT  mouWorld;

      mouWorld.x = LOWORD(lParam);
      mouWorld.y = HIWORD(lParam);
      DPtoLP (pto->hdc, &mouWorld, 1);

      /* upper left hand corner. right button is no-op. */
      if ((mouWorld.x <= (pto->rect.right  / 2)) &&
          (mouWorld.y <= (pto->rect.bottom / 2))) {
          if (msg == WM_RBUTTONDOWN) return NULL;
          pto->Mode = TMMOVE;
          newmouScreen.x = pto->rect.left;
          newmouScreen.y = pto->rect.top;

      /* lower left hand corner */
      } else if ((mouWorld.x <= (pto->rect.right  / 2)) &&
          (mouWorld.y > (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARY : TMSIZEY;
          newmouScreen.x = pto->rect.left;
          newmouScreen.y = pto->rect.bottom;

      /* upper right hand corner */
      } else if ((mouWorld.x > (pto->rect.right  / 2)) &&
          (mouWorld.y <= (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARX : TMSIZEX;
          newmouScreen.x = pto->rect.right;
          newmouScreen.y = pto->rect.top;

      /* lower right hand corner */
      } else if ((mouWorld.x > (pto->rect.right  / 2)) &&
          (mouWorld.y > (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMROTATE : TMSIZEXY;
          newmouScreen.x = pto->rect.right;
          newmouScreen.y = pto->rect.bottom;
      }

      if (! (pto->Mode & pto->allowedModes)) {
        pto->Mode = TMNONE;
        return NULL;
      }

      SetCapture(hwnd);
      LPtoDP (pto->hdc, &newmouScreen, 1);
      ClientToScreen (hwnd, &newmouScreen);
      SetCursorPos (newmouScreen.x,newmouScreen.y);

      GetWorldTransform (pto->hdc, &pto->xfmDown);
    } return NULL;

    /**********************************************************************\
    *  WM_MOUSEMOVE
    *
    * this is where almost all of the interesting calculation is done.
    *  First clip the mouse location to be in rectClip, then
    *  call MouseMove() to handle the different tracking modes.
    \**********************************************************************/
    case WM_MOUSEMOVE: {
      if ((short) LOWORD(lParam) < (short)pto->rectClip.left)
        lParam = MAKELONG ((WORD)pto->rectClip.left, HIWORD(lParam));

      if (LOWORD(lParam) > (WORD)pto->rectClip.right)
        lParam = MAKELONG ((WORD)pto->rectClip.right, HIWORD(lParam));

      if ((short) HIWORD(lParam) < (short)pto->rectClip.top)
        lParam = MAKELONG (LOWORD(lParam), (WORD)pto->rectClip.top);

      if (HIWORD(lParam) > (WORD)pto->rectClip.bottom)
        lParam = MAKELONG (LOWORD(lParam),(WORD)pto->rectClip.bottom);

      MouseMove (pto, msg, hwnd, lParam);

    } return NULL;

    /**********************************************************************\
    *  WM_RBUTTONUP & WM_LBUTTONUP
    *
    * simply release the mouse capture, and set the mode to TMNONE.
    \**********************************************************************/
    case WM_RBUTTONUP:
    case WM_LBUTTONUP: {
      if (pto->Mode) {
         ReleaseCapture();
         pto->Mode = TMNONE;
      }
    } return NULL;

  }  /* end switch(msg) */
}

/**************************************************************************\
*  function:  MouseMove()
*
*  input parameters:
*   pto -  pointer to a track object.
*   msg -  not used.
*   hwnd - Window handle for the window the track object exists within.
*   lParam - Usually fourth param to window proc. varies based on msg.
*
*  The tracking behavior which the user observers when moving the mouse
*   is based on the current tracking mode of the object.  This is usually
*   determined on the mouse down event (c.f. TM*).  First erase the old
*   object, then figure out the change to the transform matrix, finally
*   change the world transform matrix and redraw the object.
*
*  Tranform:
*    (    eM11        eM12        0   )
*    (    eM21        eM22        0   )
*    (    eDx         eDy         1   )
*
*   xDevice = (xWorld * eM11) + (yWorld * eM21) + eDx
*   yDevice = (xWorld * eM12) + (yWorld * eM22) + eDy
*
*   In this routine the Device (mouse location) and World (rectangle corner)
*   points are known.  Therefore, the two equations above are solved for
*   the desired matrix entry value (e.g. eM11, 1M12, ... eDy).  The tracking
*   mode determines which one of these entries may be changed.  E.g. scaling
*   in X modifies eM11 while shearing in X modifies eM12.  So rather than
*   using the world transform to map from world to device points, we are
*   back-computing the proper contents of the world transform.
*
\**************************************************************************/
VOID MouseMove(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
{
POINT  mouWorld, mouDevice, orgDevice;

    UNREFERENCED_PARAMETER(msg);

    doTrackObject(pto, TROB_PAINT, hwnd, lParam);
    mouDevice.x = mouWorld.x = LOWORD(lParam);
    mouDevice.y = mouWorld.y = HIWORD(lParam);

    SetWorldTransform(pto->hdc, &pto->xfmDown);
    DPtoLP (pto->hdc, &mouWorld, 1);

    /* offset the mouse device point for the viewport's origin. */
    GetViewportOrgEx (pto->hdc, &orgDevice);
    mouDevice.x -= orgDevice.x;
    mouDevice.y -= orgDevice.y;

    GetWorldTransform(pto->hdc, &pto->xfmChange);

    switch (pto->Mode) {
      /*******************************************************\
      *    (     1         xShear       0   )
      *    (     0           1          0   )
      *    (     0           0          1   )
      *
      * xWorld = rect.left == 0;
      \*******************************************************/
      case TMSHEARX: {
        pto->xfmChange.eM12 = (float) mouDevice.y;
        pto->xfmChange.eM12 -=pto->xfmChange.eDy;
        pto->xfmChange.eM12 /=(float) pto->rect.right ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (   yShear        1          0   )
      *    (     0           0          1   )
      *
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMSHEARY: {
        pto->xfmChange.eM21 = (float) mouDevice.x;
        pto->xfmChange.eM21 -=pto->xfmChange.eDx;
        pto->xfmChange.eM21 /=(float) pto->rect.bottom ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);

      } break;

      /*******************************************************\
      *    (   cos(a)      -sin(a)      0   )
      *    (   sin(a)       cos(a)      0   )
      *    (     0           0          1   )
      *
      * a == rotation angle.  Since mouse in in lower right,
      *  we need to shift this back 45 degrees (assuming that
      *  straight down is 0 degrees).  Thus we actually compute
      *  cos(a) = cos(b - 45) = cos(b)sin(45) + cos(45)sin(45)
      *  where b is angle from the origin to the mouse (x,y)
      *  cos(45) = sin(45) ~= 0.707107
      *  cos(b) = y/r    sin(b) = x/r
      *
      \*******************************************************/
      case TMROTATE: {
        float r;

        /* translate back to the origin. */
        pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;
        SetWorldTransform (pto->hdc, &pto->xfmChange);

        /* rotate about the origin. */
        r = (float) sqrt( (double)(mouWorld.x * mouWorld.x) +
                          (double)(mouWorld.y * mouWorld.y));

        pto->xfmChange.eM11 = (float) mouWorld.y / r;
        pto->xfmChange.eM11 += (float) mouWorld.x / r;
        pto->xfmChange.eM11 *= (float) 0.707107;
        pto->xfmChange.eM22 = pto->xfmChange.eM11;

        pto->xfmChange.eM12 = (float) mouWorld.y / r;
        pto->xfmChange.eM12 -= (float) mouWorld.x / r;
        pto->xfmChange.eM12 *= (float) 0.707107;
        pto->xfmChange.eM21 = -pto->xfmChange.eM12;

        pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;

        ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);

        /* translate back to the original offset. */
        pto->xfmChange.eM11 =
        pto->xfmChange.eM22 = (float) 1.0;
        pto->xfmChange.eM12 =
        pto->xfmChange.eM21 = (float) 0.0;

        pto->xfmChange.eDx = pto->xfmDown.eDx;
        pto->xfmChange.eDy = pto->xfmDown.eDy;
        ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);
        GetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (  Size X         0          0   )
      *    (     0        Size Y        0   )
      *    (     0           0          1   )
      *
      \*******************************************************/
      case TMSIZEXY: {
        pto->xfmChange.eM11 = (float) mouDevice.x;
        pto->xfmChange.eM11 -=pto->xfmChange.eDx;
        pto->xfmChange.eM11 -=((float) pto->rect.bottom*pto->xfmChange.eM21);
        pto->xfmChange.eM11 /=(float) pto->rect.right ;
        if (fabs(pto->xfmChange.eM11) < EPSILON)  // HACK.  system bug ?
           pto->xfmChange.eM11 = EPSILON;

        pto->xfmChange.eM22 = (float) mouDevice.y;
        pto->xfmChange.eM22 -=pto->xfmChange.eDy;
        pto->xfmChange.eM22 -=((float) pto->rect.right*pto->xfmChange.eM12);
        pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
        if (fabs(pto->xfmChange.eM22) < EPSILON)  // HACK.  system bug ?
           pto->xfmChange.eM22 = EPSILON;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (  Size X         0          0   )
      *    (     0           1          0   )
      *    (     0           0          1   )
      *
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMSIZEX: {
        pto->xfmChange.eM11 = (float) mouDevice.x;
        pto->xfmChange.eM11 -=pto->xfmChange.eDx;
        pto->xfmChange.eM11 /=(float) pto->rect.right ;
        if (fabs(pto->xfmChange.eM11) < EPSILON)  // HACK.  system bug ?
           pto->xfmChange.eM11 = EPSILON;

        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (     0        Size Y        0   )
      *    (     0           0          1   )
      *
      * xWorld = rect.left == 0;
      \*******************************************************/
      case TMSIZEY: {
        pto->xfmChange.eM22 = (float) mouDevice.y;
        pto->xfmChange.eM22 -=pto->xfmChange.eDy;
        pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
        if (fabs(pto->xfmChange.eM22) < EPSILON)  // HACK.  system bug ?
           pto->xfmChange.eM22 = EPSILON;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (     0           1          0   )
      *    (   Move x      Move y       1   )
      *
      * xWorld = rect.left == 0;
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMMOVE: {
        pto->xfmChange.eDx = (float) mouDevice.x ;
        pto->xfmChange.eDy = (float) mouDevice.y ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;
    } /* end switch */

    doTrackObject(pto, TROB_PAINT, hwnd, lParam);

    return;
 }


TRACK.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/* track.h - header file for doTrackObject support */

/* special messages for doTrackobject(). */
#define TROB_NEW         WM_USER+1
#define TROB_DELETE      WM_USER+2
#define TROB_PAINT       WM_USER+3
#define TROB_HITTEST     WM_USER+5

/* Different "Track modes." */
#define TMNONE    0x0000
#define TMMOVE    0x0001
#define TMSIZEX   0x0002
#define TMSIZEY   0x0004
#define TMSIZEXY  0x0006
#define TMSHEARX  0x0010
#define TMSHEARY  0x0020
#define TMROTATE  0x0008
#define TMALL     0x00ff

/* structure for the track object.  */
typedef struct tagTrackObject{
    RECT   rect;
    XFORM  xfmChange;   /* World coordinate transform      */
    XFORM  xfmDown;     /* transform when the mouse down happens. */
    HDC    hdc;
    int    Mode;        /* TMROTATE, TMSIZEXY, TMMOVE, ... */
    int    allowedModes;
    RECT   rectClip;
    struct tagTrackObject* Next;
} TrackObject, *PTrackObject;

/* function prototypes. */
PTrackObject doTrackObject(PTrackObject, int, HWND, LONG);
VOID MouseMove(PTrackObject, int, HWND, LONG);


README.TXT

World Coordinate Transformation

SUMMARY
=======

The WXFORM sample provides a demonstration of the new world-coordinate
transformation. This sample displays a rectangle in world coordinates and a
matrix containing the transform values. Users can directly manipulate the
rectangle and see the effect on the transformation, or they can set the
transformation and see the effect on the rectangle.

MORE INFORMATION
================

The program begins by setting the viewport origin to the center of the
client area. It then draws a rectangle in world-coordinate space from the
point (0, 0) to the point (100, 100). You can directly manipulate this
rectangle by using the left and right mouse buttons. Specific actions are
described more fully in the "Direct Manipulation Help" dialog box.

There is a second dialog box titled "World Transform." This shows the values
of the eM11, eM12, eM21, eM22, eDx, and eDy fields in the XFORM structure
retrieved by calling the GetWorldTransform function. By choosing the buttons
on this dialog box, the user can cause the program to call the
SetWorldTransform function.

There are three coordinate systems of interest in this sample. The first one
is the world-coordinate system, which is new to Win32. These points are
ultimately mapped to the second coordinate system, device coordinates,
before being painted in the window. This program must also use a third
coordinate system, screen coordinates, for certain interactions with the
mouse pointer.

There is a third dialog box titled "Mouse Position" that shows the location
of the cursor in all three coordinate systems. The device coordinates are
relative to the upper-left corner of the client area. They are not relative
to the viewport origin.


WXFORM.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/**************************************************************************\
*  wxform.c -- sample program demonstrating the new "World Transform."
*
*  design:  There are a few global handles or pointers in this application,
*   and different routines to operate on them.  The obvious case of this
*   is the three window handles and their associated window procedures.
*   There is also a unique pointer to a track object and a routine to
*   operate on it (i.e. doTrackObject).  All communication is accomplished
*   by sending messages between these procedures.  Each window procedure,
*   and the track object procedure, operate on some set of messages which
*   include some of the standard Windows messages, and also miscellaneous
*   "WM_USER" messages (c.f. wxform.h).
\**************************************************************************/

#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <limits.h>
#include "wxform.h"

//
// function prototype for looking up string resources
//

LPTSTR GetStringRes (int);

/**************************************************************************\
*
*  function:  WinMain()
*
*  input parameters:  c.f. generic sample
*
\**************************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    MSG   msg;
    HICON hicon;

    UNREFERENCED_PARAMETER( lpCmdLine );

    //
    // Detect platform and exit gracefully if not Windows NT.
    //

    {
      OSVERSIONINFO osvi;
      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);

      GetVersionEx (&osvi);
      if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) {
        MessageBox (NULL,
          GetStringRes (IDS_NTONLY),
          "wxform", MB_OK | MB_ICONSTOP);
        return 0;
      }
    }

    /* Check for previous instance.  If none, then register class. */
    if (!hPrevInstance) {
        WNDCLASS  wc;

        wc.style = 0;
        wc.lpfnWndProc = (WNDPROC)MainWndProc;

        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(hInstance, "TransformIcon");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
        wc.lpszMenuName =  NULL;
        wc.lpszClassName = "wxform";

        if (!RegisterClass(&wc)) return (FALSE);
    }  /* class registered o.k. */

    /* Create the main window.  Return false if CreateWindow() fails */
    hInst = hInstance;

    hwndMain = CreateWindow(
        "wxform",
        GetStringRes (IDS_WINDOWTITLE),
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (!hwndMain) return (FALSE);
    ShowWindow(hwndMain, nCmdShow);
    UpdateWindow(hwndMain);

    /* create a new track object and paint it for the first time. */
    ptoRect = doTrackObject(NULL, TROB_NEW, hwndMain, 0);
    doTrackObject(ptoRect, TROB_PAINT, hwndMain, 0);

    /* load and display dialog for the world transform matrix.
     *  then fill its entry fields.  Also, get the HICON from the
     *  main window and fill it into the dialog's class structure
     *  for this application.
     */
    hwndTransform = CreateDialog(hInst, "TransformDlg",
                                 hwndMain, (DLGPROC)TransformDlgProc);
    hicon = (HICON) GetClassLong (hwndMain, GCL_HICON);
    SetClassLong (hwndTransform, GCL_HICON, (LONG)hicon);
    showTransform = TRUE;
    SendMessage (hwndTransform, WM_PUTUPFLOATS, 0, (LONG) &ptoRect->xfmChange);

    /* load and display the dialog for the mouse position.
     *  minimize it initially.
     */
    hwndMouse = CreateDialog(hInst, "MouseDlg",
                             hwndMain, (DLGPROC)MouseDlgProc);
    ShowWindow (hwndMouse, SW_SHOWMINIMIZED);
    showMouse = FALSE;

    /* load and display the dialog with the direct manipulation help.
     *  minimize it initially.   (Don't need a unique window procedure.)
     */
    hwndHelp = CreateDialog(hInst, "helpDlg", hwndMain, NULL);
    ShowWindow (hwndHelp, SW_SHOWMINIMIZED);

    /* Loop getting messages and dispatching them. */
    while (GetMessage(&msg,NULL, 0,0)) {
      if (!IsDialogMessage (hwndTransform, &msg))
      if (!IsDialogMessage (hwndMouse, &msg))
      if (!IsDialogMessage (hwndHelp, &msg)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    return (msg.wParam);
}

/**************************************************************************\
*
*  function:  MainWndProc()
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   hwndTransform,
*   hwndMouse - information dialog box window handles.
*   showTransform,
*   showMouse - Booleans recording the retore/minimize state of the dialogs.
*   ptoRect   - pointer to track object in middle of screen.
*
\**************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HANDLE hPenGrid;

  switch (message) {

    /**********************************************************************\
    *  WM_CREATE
    *
    * create a pen for later use.
    \**********************************************************************/
    case WM_CREATE:
      hPenGrid  = CreatePen (PS_SOLID, 1, GRIDCOLOR);
    break;

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Complement of WM_CREATE.  send the track object the delete messages,
    *  then call PostQuitMessage.
    \**********************************************************************/
    case WM_DESTROY:
      DeleteObject(hPenGrid);
      doTrackObject(ptoRect, TROB_DELETE, hwnd, lParam);
      PostQuitMessage(0);
    break;

    /**********************************************************************\
    *  WM_SIZE
    *
    * Invalidate the whole window because we reset the origin on paint
    *  messages according to the size.  Also, send the track object a
    *  message so that it will also change its HDC's viewport origin.
    \**********************************************************************/
    case WM_SIZE:
        InvalidateRect (hwnd, NULL, TRUE);
        doTrackObject (ptoRect, TROB_CENTER, hwnd, lParam);
    break;

    /**********************************************************************\
    *  WM_PAINT
    *
    * First invalidate the whole window (forces the object to be painted
    *  fresh, and thus it won't XOR its old self out).  Then draw the
    *  grid and finally draw the object.
    \**********************************************************************/
    case WM_PAINT : {
      PAINTSTRUCT ps;
      HDC hdc;
      RECT  rect;
      POINT point;
      int i;

      InvalidateRect (hwnd, NULL, TRUE);

      hdc = BeginPaint(hwnd, &ps);

      CenterOrigin (hwnd, hdc);
      GetClientRect (hwnd, &rect);
      GetViewportOrgEx(hdc, &point);
      OffsetRect(&rect, -point.x, -point.y );

      /* Draw vertical lines.  Draw three at the origin. */
      SelectObject(hdc, hPenGrid);
      for (i = 0; i<= rect.right; i+=TICKSPACE){
        MoveToEx (hdc, i, rect.top, NULL);
        LineTo (hdc, i, rect.bottom);
        MoveToEx (hdc, -i, rect.top, NULL);
        LineTo (hdc, -i, rect.bottom);
      }
      MoveToEx (hdc, -1, rect.top, NULL);
      LineTo (hdc, -1, rect.bottom);
      MoveToEx (hdc, 1, rect.top, NULL);
      LineTo (hdc, 1, rect.bottom);

      /* Draw horizontal lines.  Draw three at the origin. */
      for (i = 0; i<= rect.bottom; i+=TICKSPACE){
        MoveToEx (hdc, rect.left, i, NULL);
        LineTo (hdc, rect.right, i);
        MoveToEx (hdc, rect.left, -i, NULL);
        LineTo (hdc, rect.right, -i);
      }
      MoveToEx (hdc, rect.left, -1, NULL);
      LineTo (hdc, rect.right, -1);
      MoveToEx (hdc, rect.left, 1, NULL);
      LineTo (hdc, rect.right, 1);

      doTrackObject(ptoRect, TROB_PAINT, hwnd, lParam);

      EndPaint (hwnd, &ps);
    } break;

    /**********************************************************************\
    *  WM_LBUTTONDOWN & WM_RBUTTONDOWN
    * On button down messages, hittest on the track object, and if
    *  it returns true, then send these messages to the track object.
    \**********************************************************************/
    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
      if (doTrackObject(ptoRect, TROB_HITTEST, hwnd, lParam))
         doTrackObject(ptoRect, message, hwnd, lParam);
    break;

    /**********************************************************************\
    *  WM_LBUTTONUP & WM_RBUTTONDOWN & MW_MOUSEMOVE
    * If the track object is in a "tracking mode" then send it these messages.
    *  If the transform dialog is not minimized, fill it with numbers.
    *  If the mouse dialog is not minimized, fill it with numbers.
    \**********************************************************************/
    case WM_RBUTTONUP:
    case WM_LBUTTONUP:
    case WM_MOUSEMOVE:
      if (ptoRect->Mode) {
        doTrackObject(ptoRect, message, hwnd, lParam);
        if (showTransform)
          SendMessage (hwndTransform, WM_PUTUPFLOATS, 0,
                       (LONG) &ptoRect->xfmChange);
      }

      if (showMouse)
        SendMessage (hwndMouse, WM_PUTUPFLOATS, (DWORD) hwnd, lParam);

    break;

  } /* end switch */
  return (DefWindowProc(hwnd, message, wParam, lParam));
}

/**************************************************************************\
*
*  function:  TransformDlgProc()
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   showTransform  - TRUE if window is restored, FALSE if minimized.
*       maintain the value in this routine for other windows' use.
*   ptoRect - pointer to the track object.
*   showMouse, hwndMain.
*
*  nonstandard messages:
*   WM_PUTUPFLOATS - fill the entry fields with the contents of an XFORM.
\**************************************************************************/
LRESULT CALLBACK TransformDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
XFORM  xform;
char buffer[MAXCHARS];

  switch (message) {

    /**********************************************************************\
    *  WM_INITDIALOG
    *
    * Fill the entry fields with sensible original values.
    \**********************************************************************/
    case WM_INITDIALOG:
      SetDlgItemText(hwnd, IDD_13, "0");
      SetDlgItemText(hwnd, IDD_23, "0");
      SetDlgItemText(hwnd, IDD_33, "1");
    return TRUE;

    /******************************************************************\
    *  WM_PUTUPFLOATS
    *
    *  lParam - pointer to an XFORM structure.
    *   fill the entry fields with the XFORM values.
    \******************************************************************/
    case WM_PUTUPFLOATS: {
      PXFORM pxform;
      pxform = (PXFORM) lParam;

      sprintf (buffer, FORMATFLOAT,pxform->eM11);
      SetDlgItemText(hwnd, IDD_EM11, buffer);
      sprintf (buffer, FORMATFLOAT,pxform->eM12);
      SetDlgItemText(hwnd, IDD_EM12, buffer);
      sprintf (buffer, FORMATFLOAT,pxform->eDx);
      SetDlgItemText(hwnd, IDD_EDX, buffer);

      sprintf (buffer, FORMATFLOAT,pxform->eM21);
      SetDlgItemText(hwnd, IDD_EM21, buffer);
      sprintf (buffer, FORMATFLOAT,pxform->eM22);
      SetDlgItemText(hwnd, IDD_EM22, buffer);
      sprintf (buffer, FORMATFLOAT,pxform->eDy);
      SetDlgItemText(hwnd, IDD_EDY, buffer);

    } return FALSE;

    /******************************************************************\
    *  WM_SIZE
    *
    *  toggle the global variable keeping track of the iconized state
    *   of this window.
    \******************************************************************/
    case WM_SIZE :
      if (wParam == SIZEICONIC)
        showTransform = FALSE;
      else {
        showTransform = TRUE;
        SendMessage (hwnd, WM_PUTUPFLOATS, 0, (LONG) &ptoRect->xfmChange);
      }
    return FALSE;

    case WM_COMMAND:
      /******************************************************************\
      *  WM_COMMAND,  IDD_SETXFORM
      *
      *  take the values from the entry field, fill them into an XFORM
      *   structure and then send the track object the message to use
      *   these values.  Finally, reformat and repaint the entry fields.
      \******************************************************************/
      if (LOWORD(wParam) == IDD_SETXFORM) {
        GetDlgItemText(hwnd, IDD_EM11, buffer, MAXCHARS);
        xform.eM11 = (float) atof (buffer);
        GetDlgItemText(hwnd, IDD_EM12, buffer, MAXCHARS);
        xform.eM12 = (float) atof (buffer);
        GetDlgItemText(hwnd, IDD_EDX, buffer, MAXCHARS);
        xform.eDx = (float) atof (buffer);

        GetDlgItemText(hwnd, IDD_EM21, buffer, MAXCHARS);
        xform.eM21 = (float) atof (buffer);
        GetDlgItemText(hwnd, IDD_EM22, buffer, MAXCHARS);
        xform.eM22 = (float) atof (buffer);
        GetDlgItemText(hwnd, IDD_EDY, buffer, MAXCHARS);
        xform.eDy = (float) atof (buffer);

        // HACK.  The WM_SIZE here is used to flush the GDI buffer in order
        //  to eliminate a very strange bug whereby DPtoLP() doesn't work.
        if (showMouse) SendMessage (hwndMain, WM_SIZE, 0,0);

        doTrackObject (ptoRect, TROB_SETXFORM, hwnd, (LONG) &xform);
        SendMessage (hwnd, WM_PUTUPFLOATS, 0, (LONG) &xform);

      /******************************************************************\
      *  WM_COMMAND,  IDD_IDENTITY
      *
      *  fill a local XFORM structure with the identity matrix.  Now
      *   send the track object the message to use these values.
      *   Finally, reformat and repaint the entry fields.
      \******************************************************************/
      } else if (LOWORD(wParam) == IDD_IDENTITY) {
        xform.eM11 =
        xform.eM22 =  (float) 1.0;
        xform.eDx  =
        xform.eDy  =
        xform.eM12 =
        xform.eM21 =  (float) 0.0;

        // HACK.  The WM_SIZE here is used to flush the GDI buffer in order
        //  to eliminate a very strange bug whereby DPtoLP() doesn't work.
        if (showMouse) SendMessage (hwndMain, WM_SIZE, 0,0);

        doTrackObject (ptoRect, TROB_SETXFORM, hwnd, (LONG) &xform);
        SendMessage (hwnd, WM_PUTUPFLOATS, 0, (LONG) &xform);
      } /* end WM_COMMAND */
    return FALSE;

  } /* end switch */
  return FALSE;
}

/**************************************************************************\
*
*  function:  MouseDlgProc()
*
*  input parameters:  normal window procedure parameters.
*
*  global variables:
*   showMouse  -- TRUE if window is restored, FALSE if minimized.
*       maintain the value in this routine for other windows' use.
*   ptoRect - pointer to the track object.  Needed for DPtoLP()
*
*  nonstandard messages:
*   WM_PUTUPFLOATS - fill the entry fields with the mouse position.
*
\**************************************************************************/
LRESULT CALLBACK MouseDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
char buffer[MAXCHARS];

  switch (message) {

    /******************************************************************\
    *  WM_PUTUPFLOATS
    *
    *  wParam - contains the hwnd for the main window.
    *  lParam - contains the mouse position in device coordinates.
    *           (c.f. WM_MOUSEMOVE)
    \******************************************************************/
    case WM_PUTUPFLOATS: {
      POINT pScreen, pWorld;
      HWND hwndMain;

      hwndMain = (HWND) wParam;
      pScreen.x = pWorld.x = LOWORD(lParam);
      pScreen.y = pWorld.y  = HIWORD(lParam);

      sprintf (buffer, "%d", pScreen.x);
      SetDlgItemText(hwnd, IDD_DEVICEX, buffer);
      sprintf (buffer, "%d", pScreen.y);
      SetDlgItemText(hwnd, IDD_DEVICEY, buffer);

      ClientToScreen (hwndMain, &pScreen);
      sprintf (buffer, "%d", pScreen.x);
      SetDlgItemText(hwnd, IDD_SCREENX, buffer);
      sprintf (buffer, "%d", pScreen.y);
      SetDlgItemText(hwnd, IDD_SCREENY, buffer);

      DPtoLP (ptoRect->hdc, &pWorld, 1);
      sprintf (buffer, FORMATFLOAT, (float) pWorld.x);
      SetDlgItemText(hwnd, IDD_WORLDX , buffer);
      sprintf (buffer, FORMATFLOAT, (float) pWorld.y);
      SetDlgItemText(hwnd, IDD_WORLDY , buffer);

    } return FALSE;

    /******************************************************************\
    *  WM_SIZE
    *
    *  toggle the global variable keeping track of the iconized state
    *   of this window.
    \******************************************************************/
    case WM_SIZE :
      if (wParam == SIZEICONIC)
        showMouse = FALSE;
      else
        showMouse = TRUE;
    return FALSE;

  }
  return FALSE;
}

/**************************************************************************\
*  function:  CenterOrigin()
*
*  input parameters:
*   hwnd - window with client we want the center of.
*   hdc - device context which we set the Viewport origin of.
*
\**************************************************************************/
VOID CenterOrigin (HWND hwnd, HDC hdc)
{
RECT  rect;
POINT center;

    GetClientRect (hwnd, &rect);
    center.x = rect.right / 2;
    center.y = rect.bottom /2;

    SetViewportOrgEx (hdc, center.x, center.y, NULL);
    return;
}

/**************************************************************************\
*
*  function:  doTrackObject()
*
*  input parameters:
*   pto -  pointer to a track object.
*   msg -  message selecting what action to take.  Values may include WM_*'s
*           (see case statements below for more information.)
*   hwnd - Window handle for the window the track object exists within.
*   lParam - Usually fourth param to window proc. varies based on msg.
*
*  global variables:  none.
*
*  coordinate spaces:  There are three coordinate spaces of interest here,
*   and this routine is frequently switching between them...
*
*           WORLD                   DEVICE                  SCREEN
*
*      object coordinates       input mouse pos       used w/ SetCursorPos()
*         (pto->rect)          (lParam for WM_*)
*
*             ----->  LPtoDP() ---->    ----> ClientToScreen() -->
*             <-----  DPtoLP() <----    <---- ScreenToClient() <--
*
*   in addition, the HDC has an offset origin.  Device coordinates for the
*   mouse (lParam) never take this into account, but it is necessary to
*   translate them in order to get direct manipulation right.
*
\**************************************************************************/
PTrackObject doTrackObject(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
{
  if ((pto == NULL) && (msg != TROB_NEW))  return NULL;

  switch (msg) {

    /**********************************************************************\
    *  TROB_NEW
    *
    * Allocate new PTrackObject structure.  Fill in default values
    *  for the fields of the structure.  Set up the HDC correctly.
    * return - pointer to the new object.
    \**********************************************************************/
    case  TROB_NEW: {
        PTrackObject  pto;

        /* with LPTR returned value is a pointer. */
        pto = (PTrackObject) LocalAlloc (LPTR, sizeof (TrackObject));

        /* initialize the HDC and other fields. */
        pto->hdc = GetDC(hwnd);
        SetGraphicsMode (pto->hdc, GM_ADVANCED);
        SetROP2(pto->hdc, R2_NOT);
        SelectObject (pto->hdc, GetStockObject (NULL_BRUSH));
        SelectObject(pto->hdc, CreatePen (PS_SOLID, 2, (COLORREF) 0x01000009));
        pto->Mode = TMNONE;
        doTrackObject (pto, TROB_CENTER, hwnd, lParam);
        GetWorldTransform (pto->hdc, &(pto->xfmChange));

        /* initialize the size. */
        pto->rect.top = pto->rect.left = 0;
        pto->rect.bottom = pto->rect.right = TICKSPACE*5;

        return (pto);
    }

    /**********************************************************************\
    *  TROB_DELETE
    *
    * Delete the pen that we created, release the DC,
    *  free up the memory allocated for the object.
    \**********************************************************************/
    case  TROB_DELETE:
        DeleteObject (SelectObject (pto->hdc, GetStockObject (BLACK_PEN)));
        doTrackObject (pto, TROB_PAINT, hwnd, lParam);
        ReleaseDC (hwnd, pto->hdc);
        LocalFree (LocalHandle ((LPSTR)pto));
    return NULL;

    /**********************************************************************\
    *  TROB_CENTER
    *
    * Called in order to reset the view port origin in the track objects
    *  hdc whenever the client window changes size.  This hdc is thus kept
    *  synchronized with the hdc that the axes are painted into.
    \**********************************************************************/
    case TROB_CENTER: {
        CenterOrigin (hwnd, pto->hdc);
        return (pto);
    }

    /**********************************************************************\
    *  TROB_PAINT
    *
    * Paint the object into its hdc.  Called half the time to erase
    *  the object, and half the time to redraw it.
    \**********************************************************************/
    case TROB_PAINT: {
        Rectangle (pto->hdc, pto->rect.left+1, pto->rect.top+1,
                             pto->rect.left+INC, pto->rect.top+INC);

        Rectangle (pto->hdc, pto->rect.left, pto->rect.top,
                             pto->rect.right, pto->rect.bottom);
    } return NULL;

    /**********************************************************************\
    *  TROB_SETXFORM
    *
    * lParam - pointer to the new transform.
    *  set the new transform into the HDC, then update xfmChange.
    \**********************************************************************/
    case TROB_SETXFORM: {
        doTrackObject (pto, TROB_PAINT, hwnd, lParam);
        SetWorldTransform(pto->hdc, (PXFORM) lParam);
        GetWorldTransform(pto->hdc, &pto->xfmChange);
        doTrackObject (pto, TROB_PAINT, hwnd, lParam);
    } return NULL;

    /**********************************************************************\
    *  TROB_HITTEST
    *
    * Check the point sent in in the lParam to see if it lays within
    *  the bounds of the objects defining rectangle.
    * return - pointer to the object iff the point is in rectangle,
    *  otherwise return NULL.
    \**********************************************************************/
    case TROB_HITTEST:{
        POINT  mouWorld;
        mouWorld.x = LOWORD(lParam);
        mouWorld.y = HIWORD(lParam);

        DPtoLP (pto->hdc, &mouWorld, 1);

        if (PtInRect (&pto->rect, mouWorld))  return pto;
        else  return NULL;
    }

    /**********************************************************************\
    *  WM_LBUTTONDOWN &  WM_RBUTTONDOWN
    *
    * Capture the mouse, set the tracking mode depending on the mouse
    *  location in world coordinates, reset the mouse position.
    *
    \**********************************************************************/
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN: {
      POINT  newmouScreen;
      POINT  mouWorld;

      mouWorld.x = LOWORD(lParam);
      mouWorld.y = HIWORD(lParam);
      DPtoLP (pto->hdc, &mouWorld, 1);

      /* upper left hand corner. right button is no-op. */
      if ((mouWorld.x <= (pto->rect.right  / 2)) &&
          (mouWorld.y <= (pto->rect.bottom / 2))) {
          if (msg == WM_RBUTTONDOWN) return NULL;
          pto->Mode = TMMOVE;
          newmouScreen.x = pto->rect.left;
          newmouScreen.y = pto->rect.top;

      /* lower left hand corner */
      } else if ((mouWorld.x <= (pto->rect.right  / 2)) &&
          (mouWorld.y > (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARY : TMSIZEY;
          newmouScreen.x = pto->rect.left;
          newmouScreen.y = pto->rect.bottom;

      /* upper right hand corner */
      } else if ((mouWorld.x > (pto->rect.right  / 2)) &&
          (mouWorld.y <= (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARX : TMSIZEX;
          newmouScreen.x = pto->rect.right;
          newmouScreen.y = pto->rect.top;

      /* lower right hand corner */
      } else if ((mouWorld.x > (pto->rect.right  / 2)) &&
          (mouWorld.y > (pto->rect.bottom / 2))) {

          pto->Mode = (msg == WM_RBUTTONDOWN) ? TMROTATE : TMSIZEXY;
          newmouScreen.x = pto->rect.right;
          newmouScreen.y = pto->rect.bottom;
      }

      SetCapture(hwnd);
      LPtoDP (pto->hdc, &newmouScreen, 1);
      ClientToScreen (hwnd, &newmouScreen);
      SetCursorPos (newmouScreen.x,newmouScreen.y);

      GetWorldTransform (pto->hdc, &pto->xfmDown);
    } return NULL;

    /**********************************************************************\
    *  WM_MOUSEMOVE
    *
    * this is where almost all of the interesting calculation is done.
    *  First clip the mouse location to be in client rectangle, then
    *  call MouseMove() to handle the different tracking modes.
    \**********************************************************************/
    case WM_MOUSEMOVE: {
      RECT  rect;
      GetClientRect (hwnd, &rect);

      if ((short) LOWORD(lParam) < (short)rect.left)
        lParam = MAKELONG ((WORD)rect.left, HIWORD(lParam));

      if (LOWORD(lParam) > (WORD)rect.right)
        lParam = MAKELONG ((WORD)rect.right, HIWORD(lParam));

      if ((short) HIWORD(lParam) < (short)rect.top)
        lParam = MAKELONG (LOWORD(lParam), (WORD)rect.top);

      if (HIWORD(lParam) > (WORD)rect.bottom)
        lParam = MAKELONG (LOWORD(lParam),(WORD)rect.bottom);

      MouseMove (pto, msg, hwnd, lParam);

    } return NULL;

    /**********************************************************************\
    *  WM_RBUTTONUP & WM_LBUTTONUP
    *
    * simply release the mouse capture, and set the mode to TMNONE.
    \**********************************************************************/
    case WM_RBUTTONUP:
    case WM_LBUTTONUP: {
      if (pto->Mode) {
         ReleaseCapture();
         pto->Mode = TMNONE;
      }
    } return NULL;

  }  /* end switch(msg) */
}

/**************************************************************************\
*  function:  MouseMove()
*
*  input parameters:
*   pto -  pointer to a track object.
*   msg -  not used.
*   hwnd - Window handle for the window the track object exists within.
*   lParam - Usually fourth param to window proc. varies based on msg.
*
*  The tracking behavior which the user observers when moving the mouse
*   is based on the current tracking mode of the object.  This is usually
*   determined on the mouse down event (c.f. TM*).  First erase the old
*   object, then figure out the change to the transform matrix, finally
*   change the world transform matrix and redraw the object.
*
*  Tranform:
*    (    eM11        eM12        0   )
*    (    eM21        eM22        0   )
*    (    eDx         eDy         1   )
*
*   xDevice = (xWorld * eM11) + (yWorld * eM21) + eDx
*   yDevice = (xWorld * eM12) + (yWorld * eM22) + eDy
*
*   In this routine the Device (mouse location) and World (rectangle corner)
*   points are known.  Therefore, the two equations above are solved for
*   the desired matrix entry value (e.g. eM11, 1M12, ... eDy).  The tracking
*   mode determines which one of these entries may be changed.  E.g. scaling
*   in X modifies eM11 while shearing in X modifies eM12.  So rather than
*   using the world transform to map from world to device points, we are
*   back-computing the proper contents of the world transform.

*
\**************************************************************************/
VOID MouseMove(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
{
POINT  mouWorld, mouDevice, orgDevice;

    UNREFERENCED_PARAMETER(msg);

    doTrackObject(pto, TROB_PAINT, hwnd, lParam);
    mouDevice.x = mouWorld.x = LOWORD(lParam);
    mouDevice.y = mouWorld.y = HIWORD(lParam);

    SetWorldTransform(pto->hdc, &pto->xfmDown);
    DPtoLP (pto->hdc, &mouWorld, 1);

    /* offset the mouse device point for the viewport's origin. */
    GetViewportOrgEx (pto->hdc, &orgDevice);
    mouDevice.x -= orgDevice.x;
    mouDevice.y -= orgDevice.y;

    GetWorldTransform(pto->hdc, &pto->xfmChange);

    switch (pto->Mode) {
      /*******************************************************\
      *    (     1         xShear       0   )
      *    (     0           1          0   )
      *    (     0           0          1   )
      *
      * xWorld = rect.left == 0;
      \*******************************************************/
      case TMSHEARX: {
        pto->xfmChange.eM12 = (float) mouDevice.y;
        pto->xfmChange.eM12 -=pto->xfmChange.eDy;
        pto->xfmChange.eM12 /=(float) pto->rect.right ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (   yShear        1          0   )
      *    (     0           0          1   )
      *
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMSHEARY: {
        pto->xfmChange.eM21 = (float) mouDevice.x;
        pto->xfmChange.eM21 -=pto->xfmChange.eDx;
        pto->xfmChange.eM21 /=(float) pto->rect.bottom ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);

      } break;

      /*******************************************************\
      *    (   cos(a)      -sin(a)      0   )
      *    (   sin(a)       cos(a)      0   )
      *    (     0           0          1   )
      *
      * a == rotation angle.  Since mouse in in lower right,
      *  we need to shift this back 45 degrees (assuming that
      *  straight down is 0 degrees).  Thus we actually compute
      *  cos(a) = cos(b - 45) = cos(b)sin(45) + cos(45)sin(45)
      *  where b is angle from the origin to the mouse (x,y)
      *  cos(45) = sin(45) ~= 0.707107
      *  cos(b) = y/r    sin(b) = x/r
      *
      \*******************************************************/
      case TMROTATE: {
        float r;

        /* translate back to the origin. */
        pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;
        SetWorldTransform (pto->hdc, &pto->xfmChange);

        /* rotate about the origin. */
        r = (float) sqrt( (double)(mouWorld.x * mouWorld.x) +
                          (double)(mouWorld.y * mouWorld.y));

        pto->xfmChange.eM11 = (float) mouWorld.y / r;
        pto->xfmChange.eM11 += (float) mouWorld.x / r;
        pto->xfmChange.eM11 *= (float) 0.707107;
        pto->xfmChange.eM22 = pto->xfmChange.eM11;

        pto->xfmChange.eM12 = (float) mouWorld.y / r;
        pto->xfmChange.eM12 -= (float) mouWorld.x / r;
        pto->xfmChange.eM12 *= (float) 0.707107;
        pto->xfmChange.eM21 = -pto->xfmChange.eM12;

        pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;

        ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);

        /* translate back to the original offset. */
        pto->xfmChange.eM11 =
        pto->xfmChange.eM22 = (float) 1.0;
        pto->xfmChange.eM12 =
        pto->xfmChange.eM21 = (float) 0.0;

        pto->xfmChange.eDx = pto->xfmDown.eDx;
        pto->xfmChange.eDy = pto->xfmDown.eDy;
        ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);
        GetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (  Size X         0          0   )
      *    (     0        Size Y        0   )
      *    (     0           0          1   )
      *
      \*******************************************************/
      case TMSIZEXY: {
        pto->xfmChange.eM11 = (float) mouDevice.x;
        pto->xfmChange.eM11 -=pto->xfmChange.eDx;
        pto->xfmChange.eM11 -=((float) pto->rect.bottom * pto->xfmChange.eM21);
        pto->xfmChange.eM11 /=(float) pto->rect.right ;

        pto->xfmChange.eM22 = (float) mouDevice.y;
        pto->xfmChange.eM22 -=pto->xfmChange.eDy;
        pto->xfmChange.eM22 -=((float) pto->rect.right  * pto->xfmChange.eM12);
        pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (  Size X         0          0   )
      *    (     0           1          0   )
      *    (     0           0          1   )
      *
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMSIZEX: {
        pto->xfmChange.eM11 = (float) mouDevice.x;
        pto->xfmChange.eM11 -=pto->xfmChange.eDx;
        pto->xfmChange.eM11 /=(float) pto->rect.right ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (     0        Size Y        0   )
      *    (     0           0          1   )
      *
      * xWorld = rect.left == 0;
      \*******************************************************/
      case TMSIZEY: {
        pto->xfmChange.eM22 = (float) mouDevice.y;
        pto->xfmChange.eM22 -=pto->xfmChange.eDy;
        pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;

      /*******************************************************\
      *    (     1           0          0   )
      *    (     0           1          0   )
      *    (   Move x      Move y       1   )
      *
      * xWorld = rect.left == 0;
      * yWorld = rect.top == 0;
      \*******************************************************/
      case TMMOVE: {
        pto->xfmChange.eDx = (float) mouDevice.x ;
        pto->xfmChange.eDy = (float) mouDevice.y ;
        SetWorldTransform (pto->hdc, &pto->xfmChange);
      } break;
    } /* end switch */

    doTrackObject(pto, TROB_PAINT, hwnd, lParam);

    return;
 }

/******************************************************************************\
*
*  FUNCTION:    GetStringRes (int id INPUT ONLY)
*
*  COMMENTS:    Load the resource string with the ID given, and return a
*               pointer to it.  Notice that the buffer is common memory so
*               the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR   GetStringRes (int id)
{
  static TCHAR buffer[MAX_PATH];

  buffer[0]=0;
  LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  return buffer;
}


WXFORM.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*  wxform.h -- header file for World transform sample.  */

LRESULT CALLBACK MainWndProc     (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK TransformDlgProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK MouseDlgProc    (HWND, UINT, WPARAM, LPARAM);

/* dialog id's for the Transform dialog */
#define IDD_EM11        101
#define IDD_EM12        102
#define IDD_EDX         103
#define IDD_EM21        104
#define IDD_EM22        105
#define IDD_EDY         106
#define IDD_13          107
#define IDD_23          108
#define IDD_33          109
#define IDD_SETXFORM    110
#define IDD_IDENTITY    111

/* dialog id's for the Mouse dialog */
#define IDD_SCREENX     201
#define IDD_SCREENY     202
#define IDD_DEVICEX     203
#define IDD_DEVICEY     204
#define IDD_WORLDX      205
#define IDD_WORLDY      206

/* string table ids's */
#define IDS_NTONLY        16
#define IDS_WINDOWTITLE   17

/* special messages for doTrackobject(). */
#define TROB_NEW         WM_USER+1
#define TROB_DELETE      WM_USER+2
#define TROB_PAINT       WM_USER+3
#define TROB_HITTEST     WM_USER+5
#define TROB_CENTER      WM_USER+6
#define TROB_SETXFORM    WM_USER+7

/* special user message for two of the dialogs */
#define WM_PUTUPFLOATS  WM_USER+1

/* Different "Track modes." */
#define TMNONE    0x0000
#define TMMOVE    0x0001
#define TMSIZEX   0x0002
#define TMSIZEY   0x0004
#define TMSIZEXY  0x0006
#define TMSHEARX  0x0010
#define TMSHEARY  0x0020
#define TMROTATE  0x0008

/* misc. defines for drawing and formating */
#define TICKSPACE 20
#define MAXCHARS  32
#define INC   5
#define FORMATFLOAT    "%1.2f"
#define GRIDCOLOR (COLORREF) 0x01000006

/* structure for the track object.  c.f. ptoRect */
typedef struct tagTrackObject{
    RECT   rect;
    XFORM  xfmChange;   /* World coordinate transform      */
    XFORM  xfmDown;     /* transform when the mouse down happens. */
    HDC    hdc;
    int    Mode;        /* TMROTATE, TMSIZEXY, TMMOVE, ... */
    struct tagTrackObject* Next;
} TrackObject, *PTrackObject;

/* function prototypes. */
PTrackObject doTrackObject(PTrackObject, int, HWND, LONG);
VOID MouseMove(PTrackObject, int, HWND, LONG);
VOID CenterOrigin (HWND, HDC);

/* Global variables. */
BOOL showTransform, showMouse, showHelp;
HWND hwndTransform, hwndMouse, hwndHelp;
PTrackObject ptoRect = NULL;
HANDLE hInst;
HWND   hwndMain;


README.TXT

Microsoft Image Color Management Version 2.0               readme.txt

           For Windows 98 or Windows NT 5.0
                   November, 1997

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

This readme file contains the following information:

             1. Target Platform for ICM 2.0
     2. Contents

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

1. Target Platform for ICM 2.0
-------------------------------
Image Color Management (ICM) 2.0 is for developing software for
Windows  98, and for version 5.0 of Windows NT  (both unreleased).
The platforms currently available for testing software developed
using ICM are the Beta 2 and later releases of Windows 98 and the
Beta 1 and later releases of Windows NT version 5. For information
about receiving a beta version of Windows 98, please contact:

Memreq@microsoft.com

2. Contents
-------------

When you installed the ICM 2.0 component, the following files
were placed in the folders under the installation root folder that
you specified:

[Installation root folder]
|
+--bin
|     Bitmap.exe             : Utility for changing bitmap headers
|     icmview.exe            : Sample image viewer showing color management
|     Icmtest.icm            : Test profile designed to dramtically change colors
|     rgbtest.icm            : Test profiles designed to mess up colors
|     sRGB-GBR.icm           : Test profile design to mess up colors
|     srgbspac.icm           : sRGB profile
|
+--include
|     icm.h                  : ICM 2.0 C/C++ header file
|     icmui.dlg              : If you want to hook out ICM UI dialog and replace it with your own
|
+--lib
|     icmui.lib              : ICM 2.0 User Interface static link library
|     mscms.lib              : ICM 2.0 static link library
|
+--samples
|   +--Graphics
|      +--ICM20
|             readme.txt      : This readme file
|        +--icmview            : SAMPLE APPLICATION
|             appinit.c        : Initialization source code
|             appinit.h        : Initialization header
|             cderr.h          : Error handling header
|             child.c          : Child windows source
|             child.h          : Child windows header
|             debug.c          : Debug routines source
|             debug.h          : Debug routines header
|             dialogs.c        : Dialog boxes source
|             dialogs.h        : Dialog boxes header
|             dibinfo.c        : DIB information source
|             dibinfo.h        : DIB information header
|             dibs.c           : DIB handling source
|             dibs.h           : DIB handling header
|             icmview.c        : Entry point source
|             icmview.h        : Entry point header
|             icmview.def      : Def file for application
|             icmview.ico      : Icon for application
|             ICMView.rc       : Resource file
|             makefile         : Generic make file for application
|             print.c          : Printing routines source
|             print.h          : Printing routines header
|             readme.txt       : Readme file about ICMView
|             regutil.c        : Registry utilities source
|             regutil.h        : Registry utilities header
|             resource.h       : Resource header file
|             small.ico        : Small icon for application
|

NOTE: The ICMView sample requires the BITMAPV5HEADER structure
to compile. This structure should be defined in the WINGDI.H
file used in the compilation process. If this structure is not
defined, you will need to obtain an updated version of the
Microsoft Windows Platform SDK build environment.


README.TXT

ICMVIEW.EXE : ICM capable image viewer

This application uses ICM both inside and outside the DC ICM functions to
manipulate the display and printing of Windows BMP and DIB format images.
Typically, an application would just use one or the other; the preferred and
encouraged method is outside the DC.

To run the application, type "ICMVIEW".  Use File/Open to open images, then use
the secondary mouse button to bring up the properties of the selected image.


APPINIT.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    INIT.H
//
//  PURPOSE:
//    Header file for INIT.C.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// String literals and defines for Registry and most Recent files.
#define APP_REG     __TEXT("Software\\Microsoft\\ICMView")
#define APP_COORD   __TEXT("WinCoord")
#define APP_FLAGS   __TEXT("Flags")
#define APP_RECENT  __TEXT("RecentFile")

#define MAX_RECENT  4

//
// Function prototypes
//
BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
BOOL SetSettings(LPRECT, DWORD, HANDLE);
BOOL AddRecentFile(HWND, LPTSTR);


CDERR.H

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    CDERR.H
//
//  PURPOSE:
//    Common dialogs error include file
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros
#define CDERR_DIALOGFAILURE   0xFFFF

#define CDERR_GENERALCODES     0x0000
#define CDERR_STRUCTSIZE       0x0001
#define CDERR_INITIALIZATION   0x0002
#define CDERR_NOTEMPLATE       0x0003
#define CDERR_NOHINSTANCE      0x0004
#define CDERR_LOADSTRFAILURE   0x0005
#define CDERR_FINDRESFAILURE   0x0006
#define CDERR_LOADRESFAILURE   0x0007
#define CDERR_LOCKRESFAILURE   0x0008
#define CDERR_MEMALLOCFAILURE  0x0009
#define CDERR_MEMLOCKFAILURE   0x000A
#define CDERR_NOHOOK           0x000B

#define PDERR_PRINTERCODES     0x1000
#define PDERR_SETUPFAILURE     0x1001
#define PDERR_PARSEFAILURE     0x1002
#define PDERR_RETDEFFAILURE    0x1003
#define PDERR_LOADDRVFAILURE   0x1004
#define PDERR_GETDEVMODEFAIL   0x1005
#define PDERR_INITFAILURE      0x1006
#define PDERR_NODEVICES        0x1007
#define PDERR_NODEFAULTPRN     0x1008
#define PDERR_DNDMMISMATCH     0x1009
#define PDERR_CREATEICFAILURE  0x100A
#define PDERR_PRINTERNOTFOUND  0x100B

#define CFERR_CHOOSEFONTCODES  0x2000
#define CFERR_NOFONTS          0x2001

#define FNERR_FILENAMECODES    0x3000
#define FNERR_SUBCLASSFAILURE  0x3001
#define FNERR_INVALIDFILENAME  0x3002
#define FNERR_BUFFERTOOSMALL   0x3003

#define FRERR_FINDREPLACECODES 0x4000
#define CCERR_CHOOSECOLORCODES 0x5000

// General STRUCTS && TYPEDEFS

// Function prototypes


CHILD.C

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    CHILD.C
//
//  PURPOSE:
//    Routines for child windows.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// These pragmas allow for the maximum warning level to be set
// without getting bombarded by warnings from the Windows SDK
// include files.
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include <CommCtrl.h>
#include <STDLIB.H>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings

// C RunTime Header Files

// Local Header Files
#include "icmview.h"
#include "resource.h"
#include "child.h"
#include "DibInfo.H"
#include "dialogs.h"
#include "Dibs.h"
#include "print.h"
#include "debug.h"

// Pre-processor definitions and macros

// Typedefs and structs

typedef struct TAG_PROGRESSPARAM
{
    BOOL    bCancel;
    HWND    hWnd;
    HWND    hDialog;
    DWORD   dwStartTick;
} PROGRESSPARAM, *PPROGRESSPARAM;

// External global data

// Private Global DataPrivate data

// Function prototypes
void ChildWndPaint (HWND hWnd);
BOOL ChildWndQueryNewPalette (HWND hWnd, HWND hWndFrame);
BOOL WINAPI TransformProgress(ULONG ulMax, ULONG ulCurrent, ULONG ulCallbackData);
BOOL CALLBACK TransformProgressProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
void SizeScrollBars(HWND hWnd, LPDIBINFO lpDIBInfo, UINT uiWindowHeight, UINT uiWindowWidth);
void InitializeScrollBars(HWND hWnd, LPDIBINFO lpDIBInfo);
BOOL ScrollChildWindow(HWND hWnd, int nScrollBar, WORD wScrollCode);

//////////////////////////////////////////////////////////////////////////
//  Function:  ChildWndProc
//
//  Description:
//    Child window procedure.
//
//  Parameters:
//    HWND    handle of window
//    UINT    message identifier
//    WPARAM  first message parameter
//    LPARAM  second message parameter
//
//  Returns:
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////

LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    LPDIBINFO   lpDIBInfo;
    int wmId, wmEvent;

    // Initialize variables
    lpDIBInfo = GetDIBInfoPtr(hWnd);
    switch (uiMsg)
    {
        case WM_COMMAND:
            wmId    = LOWORD(wParam);
            wmEvent = HIWORD(wParam);

            switch (wmId)
            {
                case ID_FILE_SAVEAS:
                    SaveDIBToFileDialog(hWnd, lpDIBInfo);
                    break;

                case IDM_FILE_PRINT_SETUP:
                    CreateDIBPropSheet(hWnd, ghInst, DIB_PROPSHEET_PRINT, lpDIBInfo->lpszImageFileName);
                    SetFocus(ghAppWnd);
                    break;

                case IDM_FILE_PRINT:
                    PrintDialog(hWnd, ghInst, lpDIBInfo);
                    break;

                case IDM_FILE_DISPLAY:
                    CreateDIBPropSheet(hWnd, ghInst, DIB_PROPSHEET_DISPLAY, lpDIBInfo->lpszImageFileName);
                    InvalidateRect(hWnd, NULL, FALSE);
                    break;

                case IDM_FILE_CLOSE:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);
                    break;

                case IDM_FILE_ICM20:
                case IDM_FILE_ICM10:
                case IDM_FILE_CONFIGURE_ICM:
                    {
                        HGLOBAL     hDIBInfo;
                        BOOL        bFreed;

                        switch (wmId)
                        {
                            case IDM_FILE_ICM20:
                                if (!CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20))  // Don't redundantly transform DIB
                                {
                                    SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
                                    SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), (ICMVFLAGS_ICM20), TRUE);
                                }
                                break;

                            case IDM_FILE_ICM10:
                                SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_ICM20, FALSE);
                                break;

                            case IDM_FILE_CONFIGURE_ICM:
                                ColorMatchUI(hWnd, lpDIBInfo);
                                break;

                            default:
                                break;
                        }
                        hDIBInfo = GlobalHandle(lpDIBInfo);
                        bFreed = GlobalUnlock(hDIBInfo);
                        InvalidateRect(hWnd, NULL, FALSE);
                    }
                    break;

                default:
                    return(DefMDIChildProc(hWnd, uiMsg, wParam, lParam));
                    break;
            }
            break;

        case WM_CONTEXTMENU:
            {
                HMENU hMenu;
                hMenu = InitImageMenu(hWnd);
                if (hMenu)
                {
                    TrackPopupMenu (hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, LOWORD(lParam), HIWORD(lParam), 0, hWnd, NULL);
                    DestroyMenu(hMenu);
                }
                return(0);
            }
            break;

        case WM_PAINT:
            ChildWndPaint(hWnd);
            return(0); // We have processed the message
            break;

        case WM_CLOSE:
            SendMessage(ghWndMDIClient, WM_MDIDESTROY, (WPARAM)(HWND)hWnd, 0);
            DestroyWindow(hWnd);
            SetFocus(ghAppWnd);
            return(1L);

        case WM_DESTROY:
            fFreeDIBInfo(GetDIBInfoHandle(hWnd), TRUE);
            SetWindowLong(hWnd, GWL_DIBINFO, (LONG)NULL);
            break;

        case WM_MDIACTIVATE:
            if (0 != wParam)
            {
                SendMessage (hWnd, MYWM_QUERYNEWPALETTE, (WPARAM) GetParent(ghWndMDIClient), 0L);
            }
            break;

        case WM_SIZE:
            // May need to show or hide scroll bars.
            SizeScrollBars(hWnd, lpDIBInfo, HIWORD(lParam), LOWORD(lParam));

            // This makes sure that we redraw
            // all the window if we are stretching to window
            // and the size changes.
            InvalidateRect(hWnd, NULL, FALSE);
            break;

        case WM_VSCROLL:
            ScrollChildWindow(hWnd, SB_VERT, LOWORD(wParam));
            return FALSE;

        case WM_HSCROLL:
            ScrollChildWindow(hWnd, SB_HORZ, LOWORD(wParam));
            return FALSE;

        case MYWM_QUERYNEWPALETTE:
            return ChildWndQueryNewPalette (hWnd, (HWND) wParam);
    }
    return DefMDIChildProc(hWnd, uiMsg, wParam, lParam);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  CreateNewImageWindow
//
//  Description:
//    Creates a new window for a new image.  As this function is the
//    startup function for a new thread, some administrative duties
//    are required.  The following actions are taken when the new
//    thread is created:
//      -A new window is created.
//                      -A DIBINFO structure is created and associated with the
//       window via a SetWindowLong call.
//                      -The contents of the DIBINFO structure associated with the
//       main application window are copied into the new window's
//       DIBINFO structure.
//
//  Parameters:
//    DWORD   Actually an LPTSTR to the file name of the image.
//
//  Returns:
//    DWORD to be returned by implicit CloseThread call
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////

DWORD CreateNewImageWindow(HANDLE hDIBInfo)
{
    // Local variables
    int             nWidth;
    int             nHeight;
    HWND            hwndImage;
    HANDLE          hAccel;
    RECT            rcWindow;
    RECT            rcClient;
    LPDIBINFO       lpDIBInfo;

    //  Initialize variables
    ASSERT(ghWndMDIClient != NULL);  // If it's NULL, won't get a window for thread
    ASSERT(NULL != hDIBInfo);
    if (NULL == hDIBInfo)
    {
        return(FALSE);
    }
    lpDIBInfo = GlobalLock(hDIBInfo);

    // Determine the necessary window size to hold the DIB.
    rcWindow.left   = 0;
    rcWindow.top    = 0;
    rcWindow.right  = (int) lpDIBInfo->uiDIBWidth;
    rcWindow.bottom = (int) lpDIBInfo->uiDIBHeight;

    if (!AdjustWindowRectEx (&rcWindow,
                             MDIS_ALLCHILDSTYLES|WS_CHILD|WS_SYSMENU|WS_CAPTION|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX,
                             FALSE, WS_EX_MDICHILD))
    {
        DebugMsg(__TEXT("CreateNewImageWindow:  AdjustWindowRectEx failed, LastError = %lu\r\n"), GetLastError());
        return(ERROR_INVALID_PARAMETER);
    }

    // Make sure child window is not larger than parent's client area.
    GetClientRect(ghWndMDIClient, &rcClient);
    nWidth  = __min(rcClient.right, rcWindow.right - rcWindow.left);
    nHeight = __min(rcClient.bottom, rcWindow.bottom - rcWindow.top);

    // Create child window; class declaration leaves room for pointer to DIBINFO.
    hwndImage = CreateWindowEx(WS_EX_MDICHILD, CHILD_CLASSNAME,
                               lpDIBInfo->lpszImageFileName,
                               MDIS_ALLCHILDSTYLES | WS_CHILD|WS_SYSMENU|WS_CAPTION|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX,
                               0, 0, nWidth, nHeight, ghWndMDIClient, NULL, ghInst, 0);

    if (hwndImage != NULL)
    {
        // Obtain handle to main window's ICMINFO
        lpDIBInfo->hWndOwner = hwndImage;  // Establish ownership of DIBINFO structure
        SetWindowLong(hwndImage, GWL_DIBINFO, (LONG)hDIBInfo);

        // Initial scroll information.
        InitializeScrollBars(hwndImage, lpDIBInfo);

        ShowWindow(hwndImage, SW_NORMAL);
        UpdateWindow(hwndImage);

        SendMessage(hwndImage, MYWM_QUERYNEWPALETTE, (WPARAM) ghAppWnd, 0L);
    }
    else
    {
        DebugMsg(__TEXT("CreateNewImageWindow:  hwndImage = NULL, LastError = %lu\r\n"), GetLastError);
        return(ERROR_INVALID_PARAMETER);
    }

    // Unlock handle
    GlobalUnlock(hDIBInfo);
    lpDIBInfo = NULL;

    hAccel = LoadAccelerators(ghInst, __TEXT("ICMVIEW"));
    ASSERT(hAccel);
    return(ERROR_SUCCESS);
}   // End of CreateNewImageWindow

///////////////////////////////////////////////////////////////////////
//
// Function:   ChildWndPaint
//
// Purpose:    Called by ChildWndProc() on WM_PAINT.  Does all paints
//             for this MDI child window.
//
//             Reads position of scroll bars to find out what part
//             of the DIB to display.
//
//             Checks the stretching flag in the DIBINFO structure for
//             this window to see if we are stretching to the window
//             (if we're iconic, we always stretch to a tiny bitmap).
//
//             Selects/Realizes the palette as a background palette.
//             The palette was realized as the foreground palette in
//             ChildWndQueryNewPalette() if this is the active window.
//
//
// Parms:      hWnd == Handle to window being painted.
//
///////////////////////////////////////////////////////////////////////

void ChildWndPaint(HWND hWnd)
{
    HDC                 hDC;
    RECT                rectClient;
    RECT                rectDDB;
    BOOL                bStretch;
    HGLOBAL             hDIBInfo;
    LPDIBINFO           lpDIBInfo;
    HANDLE              hDIB;
    LPTSTR              lpszTargetProfile;
    DWORD               dwLCSIntent;
    HCURSOR             hCur;
    PAINTSTRUCT         ps;
    PROGRESSPARAM       ProgressParam;
    LPBITMAPINFOHEADER  lpBi;

    // Initialize variables
    lpDIBInfo = NULL;
    lpBi = NULL;
    hDIBInfo = NULL;
    hDIB = NULL;
    lpszTargetProfile = NULL;

    START_WAIT_CURSOR(hCur);
    hDC = BeginPaint (hWnd, &ps);
    hDIBInfo = GetDIBInfoHandle(hWnd);

    if ( (NULL != hDC)
         &&
         (NULL != hDIBInfo)
         &&
         (RECTWIDTH(&ps.rcPaint) != 0)
         &&
         (RECTHEIGHT(&ps.rcPaint) !=0)
       )
    {
        lpDIBInfo = (LPDIBINFO)GlobalLock(hDIBInfo);

        //Check if we're using ICM outside of the DC. If so, it might be necessary to
        // create a color transform.
        if ( CHECK_DWFLAG(lpDIBInfo->dwICMFlags,ICMVFLAGS_ICM20) && CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ENABLE_ICM))
        {
            // Outside DC selected
            hDIB = lpDIBInfo->hDIBTransformed;
            if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_CREATE_TRANSFORM) || (hDIB == NULL))
            {
                if (NULL != lpDIBInfo->hDIBTransformed)  // Free old transformed bits
                {
                    GlobalFree(lpDIBInfo->hDIBTransformed);
                }
                // Set up for transform
                if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_PROOFING))
                {
                    lpszTargetProfile = lpDIBInfo->lpszTargetProfile;
                }
                // Initialize Progress Param structure.
                ProgressParam.bCancel = FALSE;
                ProgressParam.hWnd = hWnd;
                ProgressParam.hDialog = NULL;
                ProgressParam.dwStartTick = GetTickCount();

                ConvertIntent(lpDIBInfo->dwRenderIntent, ICC_TO_LCS, &dwLCSIntent);
                lpDIBInfo->hDIBTransformed = TransformDIBOutsideDC(lpDIBInfo->hDIB,
                                                                   lpDIBInfo->bmFormat,
                                                                   lpDIBInfo->lpszMonitorProfile,
                                                                   lpszTargetProfile,
                                                                   USE_BITMAP_INTENT, NULL, //TransformProgress,
                                                                   (ULONG) &ProgressParam);
                // May need to destroy progress dialog.
                if (IsWindow(ProgressParam.hDialog))
                    DestroyWindow(ProgressParam.hDialog);

                if (NULL == lpDIBInfo->hDIBTransformed)
                {
                    ErrMsg(hWnd, __TEXT("DIB Transform failed.  Disabling ICM"));
                    hDIB = lpDIBInfo->hDIB;
                    SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_ENABLE_ICM, FALSE);
                }
                else
                {
                    SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, FALSE);
                    hDIB = lpDIBInfo->hDIBTransformed;
                }
            }
        }
        else  // Inside DC selected
        {
            hDIB = lpDIBInfo->hDIB;
        }

        if (NULL != hDIB)
        {
            lpBi = GlobalLock(hDIB);
        }
        if (NULL == lpBi)
        {
            DebugMsg(__TEXT("CHILD.C : ChildWndPaint : NULL lpBi\r\n"));
            goto ABORTPAINT;
        }
        bStretch = lpDIBInfo->bStretch;

        // Set up the necessary rectangles -- i.e. the rectangle
        //  we're rendering into, and the rectangle in the DIB.
        if (bStretch)
        {
            GetClientRect(hWnd, &rectClient);
            rectDDB.left   = 0;
            rectDDB.top    = 0;
            rectDDB.right  = BITMAPWIDTH(lpBi);
            rectDDB.bottom = abs(BITMAPHEIGHT(lpBi));
        }
        else
        {
            int xScroll;
            int yScroll;

            xScroll  = GetScrollPos(hWnd, SB_HORZ);
            yScroll  = GetScrollPos(hWnd, SB_VERT);
            CopyRect(&rectClient, &ps.rcPaint);

            rectDDB.left   = xScroll + rectClient.left;
            rectDDB.top    = yScroll + rectClient.top;
            rectDDB.right  = rectDDB.left + RECTWIDTH(&rectClient);
            rectDDB.bottom = rectDDB.top + RECTHEIGHT(&rectClient);

            if (rectDDB.right > BITMAPWIDTH(lpBi))
            {
                int dx;

                dx = BITMAPWIDTH(lpBi) - rectDDB.right;
                rectDDB.right     += dx;
                rectClient.right  += dx;
            }

            if (rectDDB.bottom > abs(BITMAPHEIGHT(lpBi)))
            {
                int dy;

                dy = abs(BITMAPHEIGHT(lpBi)) - rectDDB.bottom;
                rectDDB.bottom    += dy;
                rectClient.bottom += dy;
            }
        }
        DIBPaint (hDC, &rectClient, hDIB, &rectDDB, lpDIBInfo);

        // Draw the clipboard selection rubber-band.
        SetWindowOrgEx (hDC, GetScrollPos(hWnd, SB_HORZ), GetScrollPos (hWnd, SB_VERT), NULL);
    }
    else
    {
        if (NULL == hDC)
        {
            END_WAIT_CURSOR(hCur);
            DebugMsg(__TEXT("ChildWndPaint : NULL hDC\r\n"));
            return;
        }
    }

    ABORTPAINT:
    EndPaint (hWnd, &ps);
    END_WAIT_CURSOR(hCur);

    if (hDIB && lpBi)
    {
        GlobalUnlock(hDIB);
    }
    if (lpDIBInfo)
    {
        GlobalUnlock(hDIBInfo);
    }
}

///////////////////////////////////////////////////////////////////////
//
// Function:   ChildWndQueryNewPalette
//
// Purpose:    Called by ChildWndProc() on WM_QUERYNEWPALETTE.
//
//             We get this message when an MDI child is getting
//             focus (by hocus pocus in FRAME.C, and by passing
//             this message when we get WM_MDIACTIVATE).  Normally
//             this message is passed only to the top level window(s)
//             of an application.
//
//             We want this window to have the foreground palette when this
//             happens, so we select and realize the palette as
//             a foreground palette (of the frame Window).  Then make
//             sure the window repaints, if necessary.
//
// Parms:      hWnd      == Handle to window getting WM_QUERYNEWPALETTE.
//             hWndFrame == Handle to the frame window (i.e. the top-level
//                            window of this app.
//
///////////////////////////////////////////////////////////////////////

BOOL ChildWndQueryNewPalette (HWND hWnd, HWND hWndFrame)
{
    HPALETTE  hOldPal;
    HDC       hDC;
    HGLOBAL   hDIBInfo;
    LPDIBINFO lpDIBInfo;
    int       nColorsChanged;

    hDIBInfo = GetDIBInfoHandle (hWnd);

    if (!hDIBInfo)
        return FALSE;

    lpDIBInfo = (LPDIBINFO) GlobalLock (hDIBInfo);

    if (!lpDIBInfo->hPal)
    {
        GlobalUnlock (hDIBInfo);
        return FALSE;
    }

    // We're going to make our palette the foreground palette for
    //  this application.  Window's palette manager expects the
    //  top-level window of the application to have the palette,
    //  so, we get a DC for the frame here!

    hDC     = GetDC (hWndFrame);
    hOldPal = SelectPalette (hDC, lpDIBInfo->hPal, FALSE);

    nColorsChanged = RealizePalette (hDC);
    InvalidateRect (hWnd, NULL, FALSE);

    if (hOldPal)
        SelectPalette (hDC, hOldPal, FALSE);

    ReleaseDC (hWndFrame, hDC);

    GlobalUnlock (hDIBInfo);

    return (nColorsChanged != 0);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  TransformProgress
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL WINAPI TransformProgress(ULONG ulMax, ULONG ulCurrent, ULONG ulCallbackData)
{
    PPROGRESSPARAM  pProgressParam = (PPROGRESSPARAM) ulCallbackData;

    DebugMsg(__TEXT("TransformProgress:  ulCurrent = %d, ulMax = %d\r\n"), ulCurrent, ulMax);

    if (!IsWindow(pProgressParam->hDialog))
    {
        DWORD   dwTick = GetTickCount();

        // May need to create progress dialog for translate bitmap bits.
        // Create dialog only if translation takes awhile to do.
        if ( (dwTick - pProgressParam->dwStartTick > 1000L) && ((double)ulCurrent/ulMax < 0.33))
        {
            // Created dialog must be destroyed by TranslateBitmapBits caller.
            pProgressParam->hDialog = CreateDialogParam(NULL, MAKEINTRESOURCE(IDD_TRANSLATE),
                                                        pProgressParam->hWnd, TransformProgressProc,
                                                        (LPARAM) ulCallbackData);
            ShowWindow(pProgressParam->hDialog, SW_SHOWNORMAL);
            UpdateWindow(pProgressParam->hDialog);
            SendDlgItemMessage(pProgressParam->hDialog, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELONG(0, ulMax));
            SendDlgItemMessage(pProgressParam->hDialog, IDC_PROGRESS, PBM_SETPOS, ulCurrent, 0);
        }
    }
    else
    {
        // Update dialog's progress bar.
        SendDlgItemMessage(pProgressParam->hDialog, IDC_PROGRESS, PBM_SETPOS, ulCurrent, 0);
    }

    return !pProgressParam->bCancel;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  TransformProgressProc
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL CALLBACK TransformProgressProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    switch (uMsg)
    {
        case WM_INITDIALOG:
            ASSERT(lParam != 0);
            SetWindowLong(hDlg, GWL_USERDATA, lParam);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDCANCEL:
                    {
                        PPROGRESSPARAM  pProgressParam = (PPROGRESSPARAM) GetWindowLong(hDlg, GWL_USERDATA);

                        pProgressParam->bCancel = TRUE;
                    }
                    break;
            }
            break;
    }

    return FALSE;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  SizeScrollBars
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void SizeScrollBars(HWND hWnd, LPDIBINFO lpDIBInfo, UINT uiWindowHeight, UINT uiWindowWidth)
{
    static BOOL    bAdjusting = FALSE;

    // Only size for validte windows and dib info.
    // Don't SizeScrollBars if in process of adjusting them.
    if ( IsWindow(hWnd)
         &&
         (NULL != lpDIBInfo)
         &&
         !bAdjusting
       )
    {
        int         nScrollHeight = GetSystemMetrics (SM_CXVSCROLL);
        int         nScrollWidth = GetSystemMetrics (SM_CYHSCROLL);
        SCROLLINFO  VertScrollInfo;
        SCROLLINFO  HorzScrollInfo;

        // Make sure that we don't get into an infinite loop when updating scroll bars.
        bAdjusting = TRUE;

        // Get current vertical scroll info.
        VertScrollInfo.cbSize = sizeof(SCROLLINFO);
        VertScrollInfo.fMask = SIF_ALL;
        GetScrollInfo(hWnd, SB_VERT, &VertScrollInfo);

        // Get current horizontal scroll info.
        HorzScrollInfo.cbSize = sizeof(SCROLLINFO);
        HorzScrollInfo.fMask = SIF_ALL;
        GetScrollInfo(hWnd, SB_HORZ, &HorzScrollInfo);

        // Only adjust if not stretching to fit.
        // Turn off scroll bars if stretching to fit.
        if (!lpDIBInfo->bStretch)
        {
            // Adjust window width and height to account for current scroll bars.
            if ((int) VertScrollInfo.nPage <= VertScrollInfo.nMax)
            {
                // INVARIANT:  Vertical scroll bar exists.

                // modify width to account for scroll bars width.
                uiWindowWidth += nScrollWidth;
            }
            if ((int) HorzScrollInfo.nPage <= HorzScrollInfo.nMax)
            {
                // INVARIANT:   Horizantal scroll bar exists.

                // Modify height to account for scroll bars height.
                uiWindowHeight += nScrollHeight;
            }

            // Adjust width and height based on what will happen.
            if ((int) uiWindowHeight < VertScrollInfo.nMax)
            {
                uiWindowWidth -= nScrollWidth;
                if ((int) uiWindowWidth < HorzScrollInfo.nMax)
                {
                    uiWindowHeight -= nScrollHeight;
                }
            }
            else if ((int) uiWindowWidth < HorzScrollInfo.nMax)
            {
                uiWindowHeight -= nScrollHeight;
                if ((int) uiWindowHeight < VertScrollInfo.nMax)
                {
                    uiWindowWidth -= nScrollWidth;
                }
            }

            // If width or height equals bitmap, need to make it larger
            // so that scroll bars will not appear.
            if ((int) uiWindowWidth == HorzScrollInfo.nMax)
            {
                ++uiWindowWidth;
            }
            if ((int) uiWindowHeight == VertScrollInfo.nMax)
            {
                ++uiWindowHeight;
            }
        }
        else
        {
            uiWindowHeight = VertScrollInfo.nMax + 1;
            uiWindowWidth = HorzScrollInfo.nMax + 1;
        }

        // Update vertical scroll info.
        VertScrollInfo.fMask = SIF_PAGE;
        VertScrollInfo.nPage = uiWindowHeight;
        SetScrollInfo(hWnd, SB_VERT, &VertScrollInfo, FALSE);

        // Update horizontal scroll info.
        HorzScrollInfo.fMask = SIF_PAGE;
        HorzScrollInfo.nPage = uiWindowWidth;
        SetScrollInfo(hWnd, SB_HORZ, &HorzScrollInfo, FALSE);

        bAdjusting = FALSE;
    }
}

//////////////////////////////////////////////////////////////////////////
//  Function:  InitializeScrollBars
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void InitializeScrollBars(HWND hWnd, LPDIBINFO lpDIBInfo)
{
    RECT        rClient;
    SCROLLINFO  ScrollInfo;

    // Get windows client size.
    GetClientRect(hWnd, &rClient);

    // If client size is equel to dib size, then add one to client size
    // so that we don't show scroll bars.
    // However, if client size is less than dib size, subtract scroll bar
    // size form client size so that page size will be correct when
    // scroll bars are shown.
    if ((UINT) rClient.bottom == lpDIBInfo->uiDIBHeight)
        ++rClient.bottom;
    else if ((UINT) rClient.bottom < lpDIBInfo->uiDIBHeight)
        rClient.bottom -= GetSystemMetrics (SM_CYHSCROLL);
    if ((UINT) rClient.right == lpDIBInfo->uiDIBWidth)
        ++rClient.right;
    else if ((UINT) rClient.right < lpDIBInfo->uiDIBWidth)
        rClient.right -= GetSystemMetrics (SM_CXVSCROLL);

    // Initialize vertical scroll bar.
    ScrollInfo.cbSize = sizeof(SCROLLINFO);
    ScrollInfo.fMask = SIF_ALL;
    ScrollInfo.nMin = 0;
    ScrollInfo.nMax = lpDIBInfo->uiDIBHeight;
    ScrollInfo.nPage = rClient.bottom;
    ScrollInfo.nPos = 0;
    SetScrollInfo(hWnd, SB_VERT, &ScrollInfo, TRUE);

    // Initialize vertical scroll bar.
    ScrollInfo.nMax = lpDIBInfo->uiDIBWidth;
    ScrollInfo.nPage = rClient.right;
    SetScrollInfo(hWnd, SB_HORZ, &ScrollInfo, TRUE);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  ScrollChildWindow
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL ScrollChildWindow(HWND hWnd, int nScrollBar, WORD wScrollCode)
{
    int         nPosition;
    int         nHorzScroll = 0;
    int         nVertScroll = 0;
    SCROLLINFO  ScrollInfo;

    // Get current scroll information.

ScrollInfo.cbSize = sizeof(SCROLLINFO);
    ScrollInfo.fMask = SIF_ALL;
    GetScrollInfo(hWnd, nScrollBar, &ScrollInfo);
    nPosition = ScrollInfo.nPos;

    // Modify scroll information based on requested
    // scroll action.
    switch (wScrollCode)
    {
        case SB_LINEDOWN:
            ScrollInfo.nPos++;
            break;

        case SB_LINEUP:
            ScrollInfo.nPos--;
            break;

        case SB_PAGEDOWN:
            ScrollInfo.nPos += ScrollInfo.nPage;
            break;

        case SB_PAGEUP:
            ScrollInfo.nPos -= ScrollInfo.nPage;
            break;

        case SB_TOP:
            ScrollInfo.nPos = ScrollInfo.nMin;
            break;

        case SB_BOTTOM:
            ScrollInfo.nPos = ScrollInfo.nMax;
            break;

            // Don't do anything.
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
            ScrollInfo.nPos = ScrollInfo.nTrackPos;
            break;

        case SB_ENDSCROLL:
            default:
            return FALSE;
    }

    // Make sure that scroll position is in range.
    if (0 > ScrollInfo.nPos)
        ScrollInfo.nPos = 0;
    else if (ScrollInfo.nMax - (int) ScrollInfo.nPage + 1 < ScrollInfo.nPos)
        ScrollInfo.nPos = ScrollInfo.nMax  - ScrollInfo.nPage + 1;

    // Set new scroll position.
    ScrollInfo.fMask = SIF_POS;
    SetScrollInfo(hWnd, nScrollBar, &ScrollInfo, TRUE);

    // Scroll window.
    if (SB_VERT == nScrollBar)
        nVertScroll = nPosition - ScrollInfo.nPos;
    else
        nHorzScroll = nPosition - ScrollInfo.nPos;

    ScrollWindowEx(hWnd, nHorzScroll, nVertScroll, NULL, NULL,
                   NULL, NULL, SW_INVALIDATE);

    return TRUE;
}


CHILD.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    Child.H
//
//  PURPOSE:
//    Include file for CHILD.C.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//
// local definitions
#define GetCurrentMDIWnd() (HWND)(SendMessage(ghWndMDIClient, WM_MDIGETACTIVE, 0, 0L))

// For use in creating children windows
#define CHILD_CLASSNAME __TEXT("ICMVIEWCHILD")
#define CHILD_CBWNDEXTRA  (sizeof(LPDIBINFO))
#define ICMVIEW_CBWNDEXTRA (sizeof(LPDIBINFO))
#define FIRSTCHILD  1
#define WINDOWMENU_POS  2
#define MYWM_QUERYNEWPALETTE  WM_USER+1

//
// Public functions
//
LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD CreateNewImageWindow(HANDLE hDIBInfo);


DEBUG.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DEBUG.C
//
//  PURPOSE:
//    Debugging routines.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include <icm.h>

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings
#pragma warning(default:4514)   // Unreferenced inline function has been removed

// C RunTime Header Files
#include <stdio.h>
#include <TCHAR.H>
#include <stdlib.h>

// Local Header Files
#include "icmview.h"
#include "dibinfo.h"
#define I_AM_DEBUG
#include "Debug.h"
#undef I_AM_DEBUG

// local definitions

// default settings

// external functions

// external data

// public data

// private data

// public functions

// private functions

//////////////////////////////////////////////////////////////////////////
//  Function:  _Assert
//
//  Description:
//    Replacement assertion function
//
//  Parameters:
//    LPSTR    Name of file
//    UINT      Line number@@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void _Assert(LPSTR strFile, UINT uiLine)
{
    // Local variables
    char  stAssert[255];

    //  Initialize variables
    wsprintfA(stAssert, "Assertion failed %s, line %u\r\n", strFile, uiLine);
    OutputDebugStringA(stAssert);
} // End of function _Assert

///////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: DebugMsg
//
//  PURPOSE:
//    To provide a printf type DebugMsg function.
//
//  PARAMETERS:
//    LPTSTR  Format string
//    <variable arguments>  printf-style arguments
//
//  RETURN VALUE:
//    void
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////

void DebugMsg (LPTSTR lpszMessage,...)
{
#ifdef _DEBUG
    va_list VAList;
    TCHAR   szMsgBuf[256];

    // Pass the variable parameters to wvsprintf to be formated.
    va_start(VAList, lpszMessage);
    wvsprintf(szMsgBuf, lpszMessage, VAList);
    va_end(VAList);

    ASSERT(lstrlen((LPTSTR)szMsgBuf) < MAX_DEBUG_STRING);
    OutputDebugString(szMsgBuf);
#endif
    lpszMessage = lpszMessage;  // Eliminates 'unused formal parameter' warning
}

void DebugMsgA (LPSTR lpszMessage,...)
{
#ifdef _DEBUG
    va_list VAList;
    char    szMsgBuf[256];

    // Pass the variable parameters to wvsprintf to be formated.
    va_start(VAList, lpszMessage);
    wvsprintfA(szMsgBuf, lpszMessage, VAList);
    va_end(VAList);

    ASSERT(strlen((LPSTR)szMsgBuf) < MAX_DEBUG_STRING);
    OutputDebugStringA(szMsgBuf);
#endif
    lpszMessage = lpszMessage;  // Eliminates 'unused formal parameter' warning
}

///////////////////////////////////////////////////////////////////////////
//
//  FUNCTION: ErrMsg
//
//  PURPOSE:
//    To provide a printf type error message box.
//
//  PARAMETERS:
//    LPTSTR  wsprintf-style format string
//    ...     formatting information
//
//  RETURN VALUE:
//    Value from MessageBox function; 0 if failure, non-zero
//    otherwise.  For specific values, see MessageBox.
//
//  COMMENTS:
//
///////////////////////////////////////////////////////////////////////////
int ErrMsg (HWND hwndOwner, LPTSTR lpszMessage,...)
{
    va_list VAList;
    TCHAR   szMsgBuf[256];

    // Pass the variable parameters to wvsprintf to be formated.
    va_start(VAList, lpszMessage);
    wvsprintf(szMsgBuf, lpszMessage, VAList);
    va_end(VAList);

    ASSERT(lstrlen((LPTSTR)szMsgBuf) < MAX_DEBUG_STRING);
    return(MessageBox(hwndOwner, (LPCTSTR)szMsgBuf, (LPCTSTR)__TEXT("Error"), MB_ICONSTOP|MB_APPLMODAL));
}

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpMemory
//
//  Description:
//    Dumps values at specified memory.
//
//  Parameters:
//    LPVOID    Pointer to memory
//    UINT      Size of each element, i.e. 8 or 16
//    UINT      Number of elements to dump.
//
//  Returns:
//    void
//
//  Comments:
//    -Need to add validation of uiElementSize.
//    -uiElementSize * uiNumElements should not exceed the size
//     of the buffer; otherwise, wacky and wonderful events will
//     take place.
//
//////////////////////////////////////////////////////////////////////////
void DumpMemory(LPBYTE lpbMem, UINT uiElementSize, UINT uiNumElements)
{
    // Local variables
    UINT  uiIdx;

    uiElementSize = uiElementSize; // Eliminates 'unused formal parameters'

    // Initialize variables
    for (uiIdx = 0; uiIdx < uiNumElements; uiIdx++)
    {
        if (uiIdx == 0 || ((uiIdx % 16) == 0))
        {
            DebugMsg(__TEXT("\r\n0x%08X\t"), (DWORD)(lpbMem + (uiIdx)));
        }
        DebugMsg(__TEXT("%02x "), (WORD)*(lpbMem + uiIdx));
    }
} // End of function DumpMemory

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpProfile
//
//  Description:
//    Dumps the PROFILE structure provided.
//
//  Parameters:
//    PPROFILE  Pointer to the profile.
//
//  Returns:
//    void
//
//  Comments:
//////////////////////////////////////////////////////////////////////////
void DumpProfile(PPROFILE pProfile)
{
    // Local variables
    TCHAR   stProfileType[MAX_PATH];

    // Initialize variables
    if (pProfile == NULL)
    {
        DebugMsg(__TEXT("DEBUG.C : DumpProfile : NULL pProfile\r\n"));
        return;
    }

    // Set type string
    _tcscpy(stProfileType, __TEXT("UNKNOWN"));
    if (PROFILE_FILENAME == pProfile->dwType)
    {
        _tcscpy(stProfileType, __TEXT("FILE"));
    }
    if (PROFILE_MEMBUFFER == pProfile->dwType)
    {
        _tcscpy(stProfileType, __TEXT("MEMBUFFER"));
    }

    DebugMsg(__TEXT("***** PROFILE *****\r\n"));
    DebugMsg(__TEXT("pProfile        0x%08lX\r\n"), pProfile);
    DebugMsg(__TEXT("dwType          %s\r\n"), stProfileType);
    DebugMsg(__TEXT("pProfileData    0x%08lX\r\n"), pProfile->pProfileData);
    if (PROFILE_FILENAME == pProfile->dwType)
    {
        DebugMsg(__TEXT("Filename        %s\r\n"), pProfile->pProfileData);
    }
    DebugMsg(__TEXT("cbDataSize      %ld\r\n\r\n"), pProfile->cbDataSize);
} // End of function DumpMemory

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpRectangle
//
//  Description:
//    Dumps the coordinates of a rectangle structure
//
//  Parameters:
//    LPTSTR    Comment
//    LPRECT    Rectangle to dump
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void DumpRectangle(LPTSTR lpszDesc, LPRECT lpRect)
{
    DebugMsg(__TEXT("%s:  %ld, %ld, %ld, %ld\r\n"), lpszDesc, lpRect->left, lpRect->top, lpRect->right,
             lpRect->bottom);
}   // End of function DumpRectangle

//////////////////////////////////////////////////////////////////////////
//  Function:  SafeFree
//
//  Description:
//    Debug Free routine which checks lock counts and return codes.
//
//  Parameters:
//    @@@
//
//  Returns:
//    HGLOBAL
//
//  Comments:
//    This function assumes that the HGLOBAL object has been unlocked;
//    if not, the item will still be freed, but a warning message will
//    be displayed.  This is useful for tracking down items which have
//    been locked without being unlocked.
//
//    The last error value will be preserved if no error occurs in this
//    function.  If an error does occur, it will be passed to the calling
//    function.
//
//////////////////////////////////////////////////////////////////////////
HGLOBAL SafeFree(LPTSTR lpszFile, UINT uiLine, HGLOBAL hMemory)
{
    // Local variables
    UINT      uiLockCount, uiFlags;
    DWORD     dwLastError;
    HGLOBAL   hFreed;                         // Return from GlobalFree
    TCHAR   szName[MAX_PATH], szExt[MAX_PATH];

    //  Initialize variables
    _tsplitpath(lpszFile, NULL, NULL, szName, szExt);
    wsprintf(lpszFile,__TEXT("%s%s"), szName, szExt);
    if (NULL == hMemory)
    {
        DebugMsg(__TEXT("%s(%lu) : SafeFree:  NULL hMem!!!  This will cause an exception!!!!   Fix it!!!\r\n"), lpszFile, uiLine);
        DebugBreak();
        return(NULL);
    }
    SetLastError(0);

    hFreed = GlobalFree(hMemory);
    if (NULL != hFreed) // unsuccessful free
    {
        dwLastError = GetLastError();
        uiFlags = GlobalFlags(hMemory);
        uiLockCount = uiFlags & GMEM_LOCKCOUNT;
        uiFlags = HIBYTE(LOWORD(uiFlags));
        DebugMsg(__TEXT("SafeFree : <%s(%lu)> failed\tLastError = %ld, Flags = %lu, Lock Count = %lu\r\n"),
                 lpszFile, uiLine, dwLastError, uiFlags, uiLockCount);
    }
    return(hFreed);
}   // End of function SafeFree

//////////////////////////////////////////////////////////////////////////
//  Function:  SafeUnlock
//
//  Description:
//    Unlocks handle and displays the lock count and flags information.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL SafeUnlock(LPTSTR lpszFile, UINT uiLine, HGLOBAL hMemory)
{
    // Local variables
    BOOL    bUnlocked;
    UINT    uiLockCount,   uiFlags;
    DWORD   dwLastError;
    TCHAR   szName[MAX_PATH], szExt[MAX_PATH];

    //  Initialize variables
    _tsplitpath(lpszFile, NULL, NULL, szName, szExt);
    wsprintf(lpszFile, __TEXT("%s%s"), szName, szExt);

    if (NULL == hMemory)
    {
        DebugMsg(__TEXT("%s(%lu) : SafeUnlock:  NULL hMem\r\n"), lpszFile, uiLine);
        return(0);
    }
    SetLastError(0);

    bUnlocked = GlobalUnlock(hMemory);
    dwLastError = GetLastError();
    uiFlags = GlobalFlags(hMemory);
    uiLockCount = uiFlags & GMEM_LOCKCOUNT;
    if (0 != dwLastError)
    {
        uiFlags = HIBYTE(LOWORD(uiFlags));
    }
    DebugMsg(__TEXT("SafeUnlock : <%s(%lu)>\tGlobalUnlock(0x%08lX) returned %4d w/LastError = %4ld, Lock Count = %4lu, Flags = %4lu\r\n"),
             lpszFile, uiLine, hMemory, bUnlocked, dwLastError, uiLockCount, uiFlags);
    return(bUnlocked);
}   // End of function SafeUnlock

//////////////////////////////////////////////////////////////////////////
//  Function:  SafeLock
//
//  Description:
//    Locks memory and reports lock count.
//
//  Parameters:
//    @@@
//
//  Returns:
//    LPVOID
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPVOID SafeLock(LPTSTR lpszFile, UINT uiLine, HGLOBAL hMemory)
{
    // Local variables
    LPVOID  lpMem;
    UINT    uiLockCount, uiFlags;
    DWORD   dwLastError;
    TCHAR   szName[MAX_PATH], szExt[MAX_PATH];

    //  Initialize variables
    _tsplitpath(lpszFile, NULL, NULL, szName, szExt);
    wsprintf(lpszFile, __TEXT("%s%s"), szName, szExt);
    if (NULL == hMemory)
    {
        DebugMsg(__TEXT("%s(%lu) : SafeLock:  NULL hMem\r\n"), lpszFile, uiLine);
        return((LPVOID)NULL);
    }
    SetLastError(0);

    lpMem = GlobalLock(hMemory);
    dwLastError = GetLastError();
    uiFlags = GlobalFlags(hMemory);
    uiLockCount = uiFlags & GMEM_LOCKCOUNT;
    uiFlags = HIBYTE(LOWORD(uiFlags));

    DebugMsg(__TEXT("SafeLock : <%s(%lu)>\tGlobalLock(0x%08lX) returned 0x%08lX w/LastError = %ld, Lock Count = %lu, Flags = %lu\r\n"),
             lpszFile, uiLine, hMemory, lpMem, dwLastError, uiLockCount, uiFlags);
    return(lpMem);
}   // End of function SafeLock

//////////////////////////////////////////////////////////////////////////
//  Function:  FormatLastError
//
//  Description:
//    Allocates and formats a string of LastError's text-equivalent message.
//    If the caller requests, the message will be displayed and the memory
//    will be deallocated.
//
//  Parameters:
//    LPTSTR  Name of file where error occured
//    UINT    Line of file at which point error occured
//    DWORD Numeric value of LastError
//    UINT  Display and free indicator
//            LASTERROR_NOALLOC  Display via DebugMsg and deallocate
//            LASTERROR_ALLOC    Return pointer to allocated string
//
//  Returns:
//    BOOL
//
//  Comments:
//    Caller must free memory returned by this function.
//
//////////////////////////////////////////////////////////////////////////
LPTSTR FormatLastError(LPTSTR lpszFile, UINT uiLine, UINT uiOutput, DWORD dwLastError)
{
    // Local variables
    LPTSTR  lpszLastErrorMsg;
    LPTSTR  lpszDebugMsg;
    DWORD   cbBytes;

    //  Initialize variables
    lpszLastErrorMsg = NULL;
    cbBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
                            | FORMAT_MESSAGE_FROM_SYSTEM,
                            NULL,
                            dwLastError,
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
                            (LPTSTR) &lpszLastErrorMsg,
                            0,
                            NULL );

    if (0 == cbBytes) // Error occured in FormatMessage
    {
        SetLastError(dwLastError); // restore the last error to time of call
    }

    // Create the error message
    lpszDebugMsg = GlobalAlloc(GPTR, ((cbBytes + 512) * sizeof(TCHAR)));
    ASSERT(NULL != lpszDebugMsg);
    wsprintf(lpszDebugMsg, __TEXT("<%s(%lu)>:\r\n\tLastError(ld, lu) = (%ld, %lu)\r\n\t\t%s\r\n"),
             lpszFile,
             uiLine,
             dwLastError,
             dwLastError,
             NULL != lpszLastErrorMsg ? lpszLastErrorMsg : __TEXT("UNKNOWN LASTERROR"));

    // Free the buffer memory if requested
    if (LASTERROR_NOALLOC == uiOutput)
    {
        DebugMsg(lpszDebugMsg);
        GlobalFree(lpszDebugMsg);
    }

    // Always free the string created by FormatMessage
    if (NULL != lpszLastErrorMsg)
    {
        GlobalFree(lpszLastErrorMsg);
    }
    return(lpszDebugMsg);
}   // End of function FormatLastError

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpBITMAPFILEHEADER
//
//  Description:
//    Dumps the contents of a BITMAPFILEHEADER.
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void DumpBITMAPFILEHEADER(LPBITMAPFILEHEADER lpBmpFileHeader)
{
    // Local variables
    DebugMsg(__TEXT("////////////////// BITMAPFILEHEADER ///////////////////\r\n"));
    DebugMsg(__TEXT("sizeof(BITMAPFILEHEADER)    %ld\r\n"), sizeof(BITMAPFILEHEADER));
    DebugMsg(__TEXT("bfType                      0x%04x\r\n"), lpBmpFileHeader->bfType);
    DebugMsg(__TEXT("bfSize                      %ld\r\n"), lpBmpFileHeader->bfSize);
    DebugMsg(__TEXT("bfReserved1                 %d\r\n"), lpBmpFileHeader->bfReserved1);
    DebugMsg(__TEXT("bfReserved2                 %d\r\n"), lpBmpFileHeader->bfReserved1);
    DebugMsg(__TEXT("bfOffBits                   %ld\r\n"), lpBmpFileHeader->bfOffBits);
    DebugMsg(__TEXT("///////////////////////////////////////////////////////\r\n"));

    //  Initialize variables

}   // End of function DumpBITMAPFILEHEADER

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpBITMAPINFOHEADER
//
//  Description:
//    Dumps a BITMAPINFO header structure.
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void DumpBmpHeader(LPVOID lpvBmpHeader)
{
    // Local variables
    LPBITMAPV5HEADER  lpBmpV5Header;
    TCHAR             stNumText[MAX_PATH];

    //  Initialize variables
    lpBmpV5Header = (LPBITMAPV5HEADER)lpvBmpHeader;

    switch (lpBmpV5Header->bV5Size)
    {
        case sizeof(BITMAPCOREHEADER):
            _tcscpy(stNumText,__TEXT("BITMAPCOREHEADER"));
            break;

        case sizeof(BITMAPINFOHEADER):
            _tcscpy(stNumText, __TEXT("BITMAPINFOHEADER"));
            break;

        case sizeof(BITMAPV4HEADER):
            _tcscpy(stNumText, __TEXT("BITMAPV4HEADER"));
            break;

        case sizeof(BITMAPV5HEADER):
            _tcscpy(stNumText,__TEXT("BITMAPV5HEADER"));
            break;

        default:
            _tcscpy(stNumText, __TEXT("UNKNOWN HEADER SIZE"));
            break;
    }
    DebugMsg(__TEXT("/////////////////// %s /////////////////////\r\n"), stNumText);
    DebugMsg(__TEXT("HeaderSize        %ld\r\n"), lpBmpV5Header->bV5Size);
    DebugMsg(__TEXT("Width             %lu\r\n"), lpBmpV5Header->bV5Width);
    DebugMsg(__TEXT("Height            %lu\r\n"), lpBmpV5Header->bV5Height);
    DebugMsg(__TEXT("Planes            %d\r\n"), lpBmpV5Header->bV5Planes);
    DebugMsg(__TEXT("BitCount          %d\r\n"), lpBmpV5Header->bV5BitCount);
    if ( sizeof(BITMAPCOREHEADER)== lpBmpV5Header->bV5Size) goto End;

    switch (lpBmpV5Header->bV5Compression)
    {
        case BI_RGB:
            _tcscpy(stNumText, __TEXT("BI_RGB"));
            break;

        case BI_RLE8:
            _tcscpy(stNumText, __TEXT("BI_RLE8"));
            break;

        case BI_RLE4:
            _tcscpy(stNumText,__TEXT("BI_RLE4"));
            break;

        case BI_BITFIELDS:
            _tcscpy(stNumText, __TEXT("BI_BITFIELDS"));
            break;

        default:
            _tcscpy(stNumText,__TEXT("Unknown Compression"));
            break;
    }
    DebugMsg(__TEXT("Compression       %s\r\n"), stNumText);
    DebugMsg(__TEXT("SizeImage         %ld\r\n"), lpBmpV5Header->bV5SizeImage);
    DebugMsg(__TEXT("XPelsPerMeter     %ld\r\n"), lpBmpV5Header->bV5XPelsPerMeter);
    DebugMsg(__TEXT("YPelsPerMeter     %ld\r\n"), lpBmpV5Header->bV5YPelsPerMeter);
    DebugMsg(__TEXT("ClrUsed           %ld\r\n"), lpBmpV5Header->bV5ClrUsed);
    DebugMsg(__TEXT("ClrImportant      %ld\r\n"), lpBmpV5Header->bV5ClrImportant);
    if (sizeof(BITMAPINFOHEADER) == lpBmpV5Header->bV5Size) goto End;

    DebugMsg(__TEXT("Red Mask          0x%08lx\r\n"), lpBmpV5Header->bV5RedMask);
    DebugMsg(__TEXT("Green Mask        0x%08lx\r\n"), lpBmpV5Header->bV5GreenMask);
    DebugMsg(__TEXT("Blue Mask         0x%08lx\r\n"), lpBmpV5Header->bV5BlueMask);
    DebugMsg(__TEXT("Alpha Mask        0x%08lx\r\n"), lpBmpV5Header->bV5AlphaMask);

    DebugMsg(__TEXT("CS Type           "));
    switch (lpBmpV5Header->bV5CSType)
    {
        case PROFILE_LINKED:
            DebugMsg(__TEXT("LINKED\r\n"));
            break;

        case PROFILE_EMBEDDED:
            DebugMsg(__TEXT("EMBEDDED\r\n"));
            break;

        default:
            DebugMsg(__TEXT("0x%08lx\r\n"), lpBmpV5Header->bV5CSType);
            break;
    }

    DebugMsg(__TEXT("Gamma Red         %ld\r\n"), lpBmpV5Header->bV5GammaRed);
    DebugMsg(__TEXT("Gamma Green       %ld\r\n"), lpBmpV5Header->bV5GammaGreen);
    DebugMsg(__TEXT("Gamma Blue        %ld\r\n"), lpBmpV5Header->bV5GammaBlue);
    if (sizeof(BITMAPV4HEADER) == lpBmpV5Header->bV5Size) goto End;

    DebugMsg(__TEXT("Intent            %ld\r\n"), lpBmpV5Header->bV5Intent);
    DebugMsg(__TEXT("ProfileData       %ld\r\n"), lpBmpV5Header->bV5ProfileData);
    DebugMsg(__TEXT("ProfileSize       %ld\r\n"), lpBmpV5Header->bV5ProfileSize);
    DebugMsg(__TEXT("Reserved          %ld\r\n"), lpBmpV5Header->bV5Reserved);

    End:
    DebugMsg(__TEXT("///////////////////////////////////////////////////////\r\n"));
}   // End of function DumpBITMAPINFOHEADER

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpLogColorSpace
//
//  Description:
//    Dumps a LOGCOLORSPACE structure.
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////

void DumpLogColorSpace(LPLOGCOLORSPACE pColorSpace)
{
    DebugMsg(__TEXT("/////////////////// LOGCOLORSPACE /////////////////////\r\n"));
    DebugMsg(__TEXT("lcsSignature                       %#lx\r\n"), pColorSpace->lcsSignature);
    DebugMsg(__TEXT("lcsVersion                         %#lx\r\n"), pColorSpace->lcsVersion);
    DebugMsg(__TEXT("lcsSize                            %#lx\r\n"), pColorSpace->lcsSize);
    DebugMsg(__TEXT("lcsCSType                          %#lx\r\n"), pColorSpace->lcsCSType);
    DebugMsg(__TEXT("lcsIntent                          %#lx\r\n"), pColorSpace->lcsIntent);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzRed.ciexyzX      %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzRed.ciexyzX);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzRed.ciexyzY      %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzRed.ciexyzY);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzRed.ciexyzZ      %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzRed.ciexyzZ);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzGreen.ciexyzX    %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzGreen.ciexyzX);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzGreen.ciexyzY    %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzGreen.ciexyzY);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzGreen.ciexyzZ    %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzGreen.ciexyzZ);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzBlue.ciexyzX     %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzBlue.ciexyzX);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzBlue.ciexyzY     %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzBlue.ciexyzY);
    DebugMsg(__TEXT("lcsEndpoints.ciexyzBlue.ciexyzZ     %#lx\r\n"), pColorSpace->lcsEndpoints.ciexyzBlue.ciexyzZ);
    DebugMsg(__TEXT("lcsGammaRed                        %#lx\r\n"), pColorSpace->lcsGammaRed);
    DebugMsg(__TEXT("lcsGammaGreen                      %#lx\r\n"), pColorSpace->lcsGammaGreen);
    DebugMsg(__TEXT("lcsGammaBlue                       %#lx\r\n"), pColorSpace->lcsGammaBlue);
    DebugMsg(__TEXT("lcsFilename                        %s\r\n"), pColorSpace->lcsFilename);
    DebugMsg(__TEXT("///////////////////////////////////////////////////////\r\n"));
}

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpCOLORMATCHSETUP
//
//  Description:
//    Dumps COLORMATCHSETUP structure
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void DumpCOLORMATCHSETUP(LPCOLORMATCHSETUP lpCM)
{
    // Local variables

    //  ASSERTs and parameter validations
    DebugMsg(__TEXT("***** COLORMATCHSETUP 0x%08lx\r\n"), lpCM);
    DebugMsg(__TEXT("dwSize                        %ld\r\n"),     lpCM->dwSize);
    DebugMsg(__TEXT("dwVersion                     0x%08lx\r\n"), lpCM->dwVersion);
    DebugMsg(__TEXT("dwFlags                       0x%08lx\r\n"), lpCM->dwFlags);
    DebugMsg(__TEXT("hwndOwner                     0x%08lx\r\n"), lpCM->hwndOwner);

    DebugMsg(__TEXT("pSourceName                   0x%08lX <%s>\r\n"), lpCM->pSourceName   , lpCM->pSourceName    ? lpCM->pSourceName    : __TEXT("NULL PTR"));
    DebugMsg(__TEXT("pDisplayName                  0x%08lX <%s>\r\n"), lpCM->pDisplayName   , lpCM->pDisplayName    ? lpCM->pDisplayName    : __TEXT("NULL PTR"));
    DebugMsg(__TEXT("pPrinterName                  0x%08lX <%s>\r\n"), lpCM->pPrinterName   , lpCM->pPrinterName    ? lpCM->pPrinterName    : __TEXT("NULL PTR"));

    DebugMsg(__TEXT("dwRenderIntent                %ld\r\n"),     lpCM->dwRenderIntent);
    DebugMsg(__TEXT("dwProofingIntent              %ld\r\n"),     lpCM->dwProofingIntent);

    DebugMsg(__TEXT("pMonitorProfile               0x%08lX <%s>\r\n"), lpCM->pMonitorProfile   , lpCM->pMonitorProfile    ? lpCM->pMonitorProfile    : __TEXT("NULL PTR"));
    DebugMsg(__TEXT("ccMonitorProfile              %ld\r\n"),     lpCM->ccMonitorProfile);

    DebugMsg(__TEXT("pPrinterProfile               0x%08lX <%s>\r\n"), lpCM->pPrinterProfile   , lpCM->pPrinterProfile    ? lpCM->pPrinterProfile    : __TEXT("NULL PTR"));
    DebugMsg(__TEXT("ccPrinterProfile              %ld\r\n"),     lpCM->ccPrinterProfile);

    DebugMsg(__TEXT("pTargetProfile                0x%08lX <%s>\r\n"), lpCM->pTargetProfile   , lpCM->pTargetProfile    ? lpCM->pTargetProfile    : __TEXT("NULL PTR"));
    DebugMsg(__TEXT("ccTargetProfile               %ld\r\n"),     lpCM->ccTargetProfile);

    DebugMsg(__TEXT("lpfnHook                      0x%08lx\r\n"), lpCM->lpfnHook);
    DebugMsg(__TEXT("lParam                        0x%08lx\r\n"), lpCM->lParam);
    DebugMsg(__TEXT("***** COLORMATCHSETUP 0x%08lx\r\n"), lpCM);
} // End of function DumpCOLORMATCHSETUP


DEBUG.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DEBUG.H
//
//  PURPOSE:
//    Include file for DEBUG.C
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//
#ifndef _ICM_H_
    #include "icm.h"
#endif

#ifdef DBG
    #define _DEBUG
#endif

// General pre-processor macros
// Constants used by ICM_Debug functions
#define MAX_DEBUG_STRING    256

// Constants used to set unitialized values
#define UNINIT_BYTE     0x17
#define UNINIT_DWORD    0x17171717

// ASSERT macro to display problem information in DEBUG build
#ifdef _DEBUG
    #define ASSERT(exp)               \
    if(exp)                           \
    {                                 \
      NULL;                           \
    }                                 \
    else                              \
    {                                 \
      _Assert(__FILE__, __LINE__);    \
    }
#else
    #define ASSERT(exp)   NULL
#endif

#ifdef DEBUG_MEMORY
    #ifndef I_AM_DEBUG
        #define GlobalFree(hMem)   SafeFree(__FILE__, __LINE__, hMem)
        #define GlobalUnlock(hMem) SafeUnlock(__FILE__, __LINE__, hMem)
        #define GlobalLock(hMem)   SafeLock(__FILE__, __LINE__, hMem)
    #endif
#endif

// Used by FormatLastError to determine if string should be allocated
// and returned or just displayed and freed.
#define LASTERROR_ALLOC      1
#define LASTERROR_NOALLOC    2

#define DISPLAY_LASTERROR(ui,dw) FormatLastError(__FILE__, __LINE__, ui, dw)

// General STRUCTS && TYPEDEFS

// Function prototypes
void    _Assert(LPSTR lpszFile, UINT uLine);
void    DebugMsg (LPTSTR sz,...);
void    DebugMsgA (LPSTR lpszMessage,...);
int     ErrMsg (HWND hwndOwner, LPTSTR sz,...);
void    DumpMemory(LPBYTE lpbMem, UINT uiElementSize, UINT uiNumElements);
void    DumpRectangle(LPTSTR lpszDesc, LPRECT lpRect);
void    DumpProfile(PPROFILE pProfile);
void    DumpBmpHeader(LPVOID lpvBmpHeader);
void    DumpBITMAPFILEHEADER(LPBITMAPFILEHEADER lpBmpFileHeader);
void    DumpLogColorSpace(LPLOGCOLORSPACE pColorSpace);
void    DumpCOLORMATCHSETUP(LPCOLORMATCHSETUP lpCM);
HGLOBAL SafeFree(LPTSTR lpszFile, UINT uLine, HGLOBAL hMemory);
BOOL    SafeUnlock(LPTSTR lpszFile, UINT uLine, HGLOBAL hMemory);
LPVOID  SafeLock(LPTSTR lpszFile, UINT uiLine, HGLOBAL hMemory);
LPSTR   FormatLastError(LPSTR lpszFile, UINT uiLine, UINT uiOutput, DWORD dwLastError);


DIALOGS.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIALOGS.C
//
//  DESCRIPTION:
//
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS:
//

//
// Pre-processor directives
//

#define REGISTRY_CURRENT_DISPLAY "System\\CurrentControlSet\\Services\\Class\\DISPLAY\\0000"
#define TOGGLE_BOOLEAN(b) b=!b

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include <commdlg.h>
#include <commctrl.h>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings
#pragma warning(default:4514)   // Unreferenced inline function has been removed

// C-runtime header files
#include <TCHAR.H>
#include <stdlib.h>

// Local header files
#include "icmview.h"
#include "resource.h"
#include "DibInfo.H"
#include "Dialogs.h"
#include "CDErr.h"
#include "print.h"
#include "child.h"
#include "Dibs.H"
#include "AppInit.h"
#include "Debug.h"

// Private structures/typedefs
typedef struct tagDIBPROPSHEETINFO
{
    UINT            uiPageNum;          // Page number of propsheet
    HGLOBAL         hDIBInfo;           // Handle to DIBINFO structure
} DIBPROPSHEETINFO, *LPDIBPROPSHEETINFO;

// Private function prototypes
void            ProcessCDError(DWORD dwErrorCode, HWND hWnd);
int CALLBACK    EnumICMProfileCallback(LPCTSTR lpszFileName, LPARAM lParam);
LPTSTR          GetOpenImageName(HWND hWnd , PBOOL pbUserCancel);
void            DlgDIBInfoPaint(HWND  hDlg, LPDIBINFO lpDIBInfo);
BOOL            SaveDIBInfoDlgPage(HWND hDlg, LPDIBINFO lpDIBInfo, UINT uiPageNum);
LPTSTR          GetDlgItemString(HWND hDlg, int iControlId, LPTSTR lpszCurrentString);
DWORD           SetColorMatchUIFlags(DWORD dwDIFlags);
BOOL WINAPI     ColorSetupApply(PCOLORMATCHSETUP pcmSetup, LPARAM lParam);
VOID            ApplyColorSettings(LPDIBINFO lpDIBInfo, PCOLORMATCHSETUP pCMSetup);
BOOL CALLBACK PrintDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Global external variables

// Global private variables
//UINT  guiNumProfiles = 0;
BOOL  gbOpenCanceled;

/////////////////////////////////////////////////////////////////////////////
//
//   FUNCTION: ProcessCDError(DWORD)
//
//   PURPOSE:  Processes errors from the common dialog functions.
//
//   COMMENTS:
//
//       This function is called whenever a common dialog function
//       fails.  The CommonDialogExtendedError() value is passed to
//       the function which maps the error value to a string table.
//       The string is loaded and displayed for the user.
//
//   RETURN VALUES:
//       void.
//
/////////////////////////////////////////////////////////////////////////////
void ProcessCDError(DWORD dwErrorCode, HWND hWnd)
{
    WORD  wStringID;
    TCHAR  buf[256];

    switch (dwErrorCode)
    {
        case CDERR_DIALOGFAILURE:   wStringID=IDS_DIALOGFAILURE;   break;
        case CDERR_STRUCTSIZE:      wStringID=IDS_STRUCTSIZE;      break;
        case CDERR_INITIALIZATION:  wStringID=IDS_INITIALIZATION;  break;
        case CDERR_NOTEMPLATE:      wStringID=IDS_NOTEMPLATE;      break;
        case CDERR_NOHINSTANCE:     wStringID=IDS_NOHINSTANCE;     break;
        case CDERR_LOADSTRFAILURE:  wStringID=IDS_LOADSTRFAILURE;  break;
        case CDERR_FINDRESFAILURE:  wStringID=IDS_FINDRESFAILURE;  break;
        case CDERR_LOADRESFAILURE:  wStringID=IDS_LOADRESFAILURE;  break;
        case CDERR_LOCKRESFAILURE:  wStringID=IDS_LOCKRESFAILURE;  break;
        case CDERR_MEMALLOCFAILURE: wStringID=IDS_MEMALLOCFAILURE; break;
        case CDERR_MEMLOCKFAILURE:  wStringID=IDS_MEMLOCKFAILURE;  break;
        case CDERR_NOHOOK:          wStringID=IDS_NOHOOK;          break;
        case PDERR_SETUPFAILURE:    wStringID=IDS_SETUPFAILURE;    break;
        case PDERR_PARSEFAILURE:    wStringID=IDS_PARSEFAILURE;    break;
        case PDERR_RETDEFFAILURE:   wStringID=IDS_RETDEFFAILURE;   break;
        case PDERR_LOADDRVFAILURE:  wStringID=IDS_LOADDRVFAILURE;  break;
        case PDERR_GETDEVMODEFAIL:  wStringID=IDS_GETDEVMODEFAIL;  break;
        case PDERR_INITFAILURE:     wStringID=IDS_INITFAILURE;     break;
        case PDERR_NODEVICES:       wStringID=IDS_NODEVICES;       break;
        case PDERR_NODEFAULTPRN:    wStringID=IDS_NODEFAULTPRN;    break;
        case PDERR_DNDMMISMATCH:    wStringID=IDS_DNDMMISMATCH;    break;
        case PDERR_CREATEICFAILURE: wStringID=IDS_CREATEICFAILURE; break;
        case PDERR_PRINTERNOTFOUND: wStringID=IDS_PRINTERNOTFOUND; break;
        case CFERR_NOFONTS:         wStringID=IDS_NOFONTS;         break;
        case FNERR_SUBCLASSFAILURE: wStringID=IDS_SUBCLASSFAILURE; break;
        case FNERR_INVALIDFILENAME: wStringID=IDS_INVALIDFILENAME; break;
        case FNERR_BUFFERTOOSMALL:  wStringID=IDS_BUFFERTOOSMALL;  break;

        case 0:   //User may have hit CANCEL or we got a *very* random error
            return;

        default:
            wStringID=IDS_UNKNOWNERROR;
    }

    LoadString(NULL, wStringID, buf, sizeof(buf));
    MessageBox(hWnd, buf, NULL, MB_OK);
    return;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  GetOpenImageName
//
//  Description:
//    Invokes common dialog function to open a file and opens it, returning
//    the LPHANDLE to the opened file (NULL if failure) and setting the
//    LPTSTR parameter to the FULLY QUALIFIED name of the file which was opened.
//    If the user cancels out of the dialog, the bUserCancelled variable is
//    set to TRUE, allowing the calling function to discriminate between this
//    action and an actual failure of the open dialog calls.
//
//  Parameters:
//    HWND      Handle to the associated window
//    *BOOL     Indicates if user cancelled out of dialog.
//
//  Returns:
//    LPTSTR     Pointer to the buffer to hold the filename.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////

LPTSTR GetOpenImageName(HWND hWnd , PBOOL pbUserCancel)
{
    // Local variables
    OPENFILENAME    OpenFileName;   // Structure for common dialog File/Open
    TCHAR           szFilter[]=__TEXT("Images(*.BMP,*.DIB)\0*.BMP;*.DIB\0All Files(*.*)\0*.*\0\0");
    TCHAR           szFile[MAX_PATH];
    LPTSTR          lpszFileName;

    // Initialize variables
    szFile[0] = __TEXT('\0');
    OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = hWnd;
    OpenFileName.hInstance         = (HANDLE) ghInst;
    OpenFileName.lpstrFilter       = szFilter;
    OpenFileName.lpstrCustomFilter = (LPTSTR) NULL;
    OpenFileName.nMaxCustFilter    = 0L;
    OpenFileName.nFilterIndex      = 1L;
    OpenFileName.lpstrFile         = szFile;
    OpenFileName.nMaxFile          = sizeof(szFile);
    OpenFileName.lpstrFileTitle    = NULL;
    OpenFileName.nMaxFileTitle     = 0;
    OpenFileName.lpstrInitialDir   = NULL;
    OpenFileName.lpstrTitle        = __TEXT("ICMVIEW:  Open Image");
    OpenFileName.nFileOffset       = 0;
    OpenFileName.nFileExtension    = 0;
    OpenFileName.lpstrDefExt       = NULL;  // No default extension
    OpenFileName.lCustData         = 0;

    OpenFileName.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST |
                         OFN_FILEMUSTEXIST | OFN_HIDEREADONLY |
                         OFN_EXPLORER | OFN_LONGNAMES;

    if (GetOpenFileName(&OpenFileName))
    {
        *pbUserCancel = FALSE;
        // We have a valid filename, let's copy it into the buffer
        lpszFileName = GlobalAlloc(GPTR, (lstrlen(OpenFileName.lpstrFile)+1));
        if (NULL == lpszFileName)
        {
            return(NULL);
        }
        _tcscpy(lpszFileName,OpenFileName.lpstrFile);
    }
    else // User didn't select a file
    {
        *pbUserCancel = TRUE;
        ProcessCDError(CommDlgExtendedError(), hWnd );
        return (NULL);
    }
    return(lpszFileName);
}   // End of GetOpenImageName

//////////////////////////////////////////////////////////////////////////
//  Function:  fOpenNewImage
//
//  Description:
//    Performs all tasks associated with opening a new file.  This includes
//    creating the File Open common dialog, and if a file was successfully
//    opened, creating a new thread and window to handle the selected image.
//
//  Parameters:
//    HWND    Handle to associated window.
//
//  Returns:
//    BOOL    Indicates if a file was successfully opened.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////

BOOL fOpenNewImage(HWND hWnd, LPTSTR lpszFileName)
{
    // Local variables
    BOOL        bUserCancelled;   // TRUE if user cancels open dialog
    HGLOBAL     hDIBInfo, hMem;
    BOOL        bRC;
    HCURSOR     hCur;

    // If lpszFileName is NULL, get file name from user.
    if (NULL == lpszFileName)
    {
        lpszFileName = GetOpenImageName(hWnd, &bUserCancelled);
        if (lpszFileName == NULL)
        {
            if (!bUserCancelled)
            {
                ErrMsg(hWnd, __TEXT("fOpenNewImage:  lpszFileName == NULL"));
            }
            return(FALSE);
        }
    }

    START_WAIT_CURSOR(hCur);
    hDIBInfo = ReadDIBFile(lpszFileName);
    END_WAIT_CURSOR(hCur);
    if (hDIBInfo)
    {
        LPDIBINFO lpDIBInfo, lpGlobalDIBInfo;

        // Add file to recent files list.
        AddRecentFile(hWnd, lpszFileName);

        // Copy ICM information from global LPDIBINFO
        lpDIBInfo = GlobalLock(hDIBInfo);
        lpGlobalDIBInfo = GetDIBInfoPtr(ghAppWnd);
        fDuplicateICMInfo(lpDIBInfo, lpGlobalDIBInfo);

        // Now copy default options
        lpDIBInfo->bStretch          = lpGlobalDIBInfo->bStretch;
        lpDIBInfo->dwStretchBltMode  = lpGlobalDIBInfo->dwStretchBltMode;
        lpDIBInfo->dwPrintOption     = lpGlobalDIBInfo->dwPrintOption;

        CreateNewImageWindow(hDIBInfo);
        GlobalUnlock(GlobalHandle(lpGlobalDIBInfo));
        GlobalUnlock(GlobalHandle(lpDIBInfo));
        bRC = TRUE;
    }
    else
    {
        ErrMsg(hWnd, __TEXT("Unable to open file %s"), lpszFileName);
        bRC = (FALSE);
    }
    hMem = GlobalHandle(lpszFileName);
    GlobalFree(hMem);
    return(bRC);
}   // End of fOpenNewImage

//////////////////////////////////////////////////////////////////////////
//  Function:  CreateDIBPropSheet
//
//  Description:
//    Creates the property sheet used to describe a DIB.
//
//  Parameters:
//    HWND        Handle to the window which owns the property sheet
//    HINSTANCE   Instance handle
//
//  Returns:
//    int   Return value from the call to Win32 API PropertySheet
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
int CreateDIBPropSheet(HWND hwndOwner, HINSTANCE hInst, int nStartPage, LPTSTR lpszCaption)
{
    // Local variables
    PROPSHEETPAGE     PropSheetPage[(DIB_PROPSHEET_MAX+1)];
    PROPSHEETHEADER   PropSheetHdr;
    DIBPROPSHEETINFO  DIBPropSheetInfo[(DIB_PROPSHEET_MAX+1)];
    int               iPropSheet;  // Return code for PropertySheet call

    // Initialize vars
    SetLastError(0);
    ASSERT((DIB_PROPSHEET_MIN <= nStartPage) && (DIB_PROPSHEET_MAX >= nStartPage));
    if ((DIB_PROPSHEET_MIN > nStartPage) && (DIB_PROPSHEET_MAX < nStartPage))
    {
        nStartPage = DIB_PROPSHEET_DEFAULT;
    }

    // Initialize PROPERTYSHEETHEADER
    PropSheetHdr.dwSize = sizeof(PROPSHEETHEADER);
    PropSheetHdr.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
    PropSheetHdr.hwndParent = hwndOwner;
    PropSheetHdr.hInstance = hInst;
    PropSheetHdr.pszIcon = NULL;
    PropSheetHdr.pszCaption = lpszCaption;
    PropSheetHdr.nPages = sizeof(PropSheetPage) / sizeof(PROPSHEETPAGE);
    PropSheetHdr.ppsp = (LPCPROPSHEETPAGE)&PropSheetPage;
    PropSheetHdr.nStartPage = nStartPage;

    // Initialize DIB Display Property Sheet
    PropSheetPage[0].dwSize = sizeof(PROPSHEETPAGE);
    PropSheetPage[0].dwFlags = PSP_USETITLE;
    PropSheetPage[0].hInstance = hInst;
    PropSheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DISPLAY);
    PropSheetPage[0].pszIcon = NULL;
    PropSheetPage[0].pfnDlgProc = DlgDIBPropSheet;
    PropSheetPage[0].pszTitle = __TEXT("Display");
    DIBPropSheetInfo[0].uiPageNum = DIB_PROPSHEET_DISPLAY;
    DIBPropSheetInfo[0].hDIBInfo = GetDIBInfoHandle(hwndOwner);
    PropSheetPage[0].lParam = (LPARAM)(LPDIBPROPSHEETINFO)&DIBPropSheetInfo[0];

    // Initialize DIB Print Property Sheet
    PropSheetPage[1].dwSize = sizeof(PROPSHEETPAGE);
    PropSheetPage[1].dwFlags = PSP_USETITLE;
    PropSheetPage[1].hInstance = hInst;
    PropSheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_PRINT);
    PropSheetPage[1].pszIcon = NULL;
    PropSheetPage[1].pfnDlgProc = DlgDIBPropSheet;
    PropSheetPage[1].pszTitle = __TEXT("Printer");
    DIBPropSheetInfo[1].uiPageNum = DIB_PROPSHEET_PRINT;
    DIBPropSheetInfo[1].hDIBInfo = GetDIBInfoHandle(hwndOwner);
    PropSheetPage[1].lParam = (LPARAM)(LPDIBPROPSHEETINFO)&DIBPropSheetInfo[1];

    // Create the property sheet and return
    iPropSheet = PropertySheet(&PropSheetHdr);
    return(iPropSheet);
} // End of function CreateDIBPropSheet

//////////////////////////////////////////////////////////////////////////
//  Function:  DlgDIBPropSheet
//
//  Description:
//    Routine to handle the DIB Display properties property sheet.
//
//  Parameters:
//    @@@
//
//  Returns:
//    @@@
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
BOOL APIENTRY DlgDIBPropSheet(HWND hwndPSPage, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    // Local variables
    HGLOBAL           hDIBInfo;
    LPDIBINFO         lpDIBInfo;
    static  UINT      uiPageNum = (UINT)UNINIT_DWORD;
    static  LPDIBINFO lpTempDIBInfo = NULL;
    static  HWND      hwndOwner = NULL;
    static  HWND      hwndPSPagePropSheet = NULL;
    HDC     hDC;
    LPTSTR   lpszInitialProfile;
    LPDIBPROPSHEETINFO  lpDIBPropSheetInfo;

    // Init variables
    hDC = NULL;
    lpszInitialProfile = NULL;
    switch (uiMsg)
    {
        case WM_INITDIALOG:
            // Save the page number associated w/this page
            lpDIBPropSheetInfo = (LPDIBPROPSHEETINFO) ((LPPROPSHEETPAGE)lParam)->lParam;
            uiPageNum = lpDIBPropSheetInfo->uiPageNum;
            ASSERT(uiPageNum >= DIB_PROPSHEET_MIN && uiPageNum <= DIB_PROPSHEET_MAX);
            SetWindowLong(hwndPSPage, GWL_ID, uiPageNum);
            hDIBInfo = lpDIBPropSheetInfo->hDIBInfo;
            lpDIBInfo = GlobalLock(hDIBInfo);
            hwndOwner = lpDIBInfo->hWndOwner;
            lpTempDIBInfo = fDuplicateDIBInfo(lpTempDIBInfo, lpDIBInfo);

            if (uiPageNum == DIB_PROPSHEET_DISPLAY)
            {
                ASSERT(lpDIBInfo);

                // Copy the DIBINFO structure for the image to a temporary DIBINFO
                // structure.  If the user presses "OK" or "Accept" to close the
                // property sheet, the temporary DIBINFO will be copied back into the
                // image's DIBINFO to reflect any possible changes.
                ASSERT(lpTempDIBInfo);
                lpszInitialProfile = lpTempDIBInfo->lpszMonitorProfile;
            }
            else if (uiPageNum == DIB_PROPSHEET_PRINT)
            {
                lpszInitialProfile = lpTempDIBInfo->lpszPrinterProfile;
            }

            GlobalUnlock(hDIBInfo);
            break;

        case WM_DESTROY:
            ASSERT(NULL != hwndOwner);
            if (NULL != hwndOwner)
            {
                InvalidateRect(      hwndOwner,       NULL,       FALSE);
            }
            if (lpTempDIBInfo)
            {
                fFreeDIBInfo(GlobalHandle(lpTempDIBInfo), FALSE);
            }
            lpTempDIBInfo = NULL;
            break;

        case WM_NOTIFY:
            switch (((LPNMHDR)lParam)->code)  // type of notification message
            {
                case PSN_SETACTIVE:
                    // Initialize the controls
                    uiPageNum = GetWindowLong(hwndPSPage,GWL_ID);
                    if (uiPageNum == DIB_PROPSHEET_PRINT)
                    {
                        PopulatePrinterCombobox(hwndPSPage, IDC_PRINT_PRINTERLIST, lpTempDIBInfo->lpszPrinterName);
                    }

                    // Update the dialog based upon the contents of lpDIBInfo
                    DlgDIBInfoPaint(hwndPSPage, lpTempDIBInfo);
                    SetDlgMsgResult(hwndPSPage, uiMsg, FALSE);
                    break;

                case PSN_KILLACTIVE:
                    SaveDIBInfoDlgPage(hwndPSPage, lpTempDIBInfo, uiPageNum);
                    SetDlgMsgResult(hwndPSPage, uiMsg, FALSE);
                    break;

                case PSN_APPLY:
                    lpDIBInfo = GetDIBInfoPtr(hwndOwner);
                    if (NULL == fDuplicateDIBInfo(lpDIBInfo, lpTempDIBInfo ))
                    {
                        ErrMsg(hwndPSPage, __TEXT("fDuplicateDIBInfo:  Failed to copy DIBINFO"));
                    }
                    ASSERT(lpDIBInfo != NULL);
                    GlobalUnlock(GlobalHandle(lpDIBInfo));
                    SetDlgMsgResult(hwndPSPage, uiMsg, PSNRET_NOERROR);
                    break;

                case PSN_RESET:
                    fFreeDIBInfo(lpTempDIBInfo, FALSE);
                    lpTempDIBInfo = NULL;
                    SetDlgMsgResult(hwndPSPage, uiMsg, FALSE);
                    break;

                default:
                    break;
            }

        case WM_COMMAND:
            {
                switch (LOWORD(wParam))
                {
                    case IDC_DISPLAY_STRETCH:
                        {
                            BOOL  bChecked;

                            bChecked = IsDlgButtonChecked(hwndPSPage, IDC_DISPLAY_STRETCH);
                            EnableWindow(GetDlgItem(hwndPSPage, IDC_DISPLAY_ANDSCAN), bChecked);
                            EnableWindow(GetDlgItem(hwndPSPage, IDC_DISPLAY_DELETESCAN), bChecked);
                            EnableWindow(GetDlgItem(hwndPSPage, IDC_DISPLAY_ORSCAN), bChecked);
                        }
                        break;

                    case IDC_PRINT_IMAGE:
                        {
                            HCURSOR hCur;
                            HGLOBAL hDIBInfo, hTempDIBInfo;

                            START_WAIT_CURSOR(hCur);
                            hDIBInfo = GetDIBInfoHandle(hwndOwner);
                            SaveDIBInfoDlgPage(hwndPSPage, lpTempDIBInfo, uiPageNum);

                            // Use current settings
                            hTempDIBInfo = GlobalHandle(lpTempDIBInfo);
                            SetWindowLong(hwndOwner, GWL_DIBINFO, (LONG)hTempDIBInfo);

                            PrintImage(hwndOwner);

                            //Restore settings
                            SetWindowLong(hwndOwner, GWL_DIBINFO, (LONG)hDIBInfo);
                            END_WAIT_CURSOR(hCur);
                        }
                        break;

                    default:
                        break;
                }

            }
        default:
            break;
    }
    return(FALSE);  // FALSE means let the system property sheet code take over
} // End of function DlgDIBPropSheet

//////////////////////////////////////////////////////////////////////////
//  Function:  DlgDIBInfoPaint
//
//  Description:
//    Update the dialog based upon the contents of lpDIBInfo.  This includes
//    determining which property sheet page is visible, and using this
//    information to populate the device profile ComboBox for the proper
//    device type.
//
//  Parameters:
//    HWND      Handle to the dialog.
//    LPDIBINFO Pointer to DIBINFO structure.
//    LPICMINFO Pointer to ICMINFO structure.
//
//  Returns:
//    void
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
void DlgDIBInfoPaint(HWND  hDlg, LPDIBINFO lpDIBInfo)
{
    // Local variables
    UINT    uiPageNum;        // ID's which property sheet page is visible

    // Initialize variables
    ASSERT(lpDIBInfo != NULL);
    uiPageNum = GetWindowLong(hDlg, GWL_ID);
    ASSERT(uiPageNum <= DIB_PROPSHEET_MAX);

    // Update the page-specific elements first
    switch (uiPageNum)
    {
        case DIB_PROPSHEET_DISPLAY:

            // Display the display method information
            CheckDlgButton(hDlg, IDC_DISPLAY_STRETCH, lpDIBInfo->bStretch);

            // Enable/Disable stretch mode buttons
            EnableWindow(GetDlgItem(hDlg, IDC_DISPLAY_ANDSCAN), lpDIBInfo->bStretch);
            EnableWindow(GetDlgItem(hDlg, IDC_DISPLAY_DELETESCAN), lpDIBInfo->bStretch);
            EnableWindow(GetDlgItem(hDlg, IDC_DISPLAY_ORSCAN), lpDIBInfo->bStretch);

            // Select stretch mode if necessary
            ASSERT(lpDIBInfo->dwStretchBltMode >= STRETCH_ANDSCANS);
            ASSERT(lpDIBInfo->dwStretchBltMode <= STRETCH_DELETESCANS);
            switch ((int)(lpDIBInfo->dwStretchBltMode))
            {
                case STRETCH_ANDSCANS:
                    CheckRadioButton(hDlg, IDC_DISPLAY_ANDSCAN, IDC_DISPLAY_ORSCAN, IDC_DISPLAY_ANDSCAN);
                    break;
                case STRETCH_DELETESCANS:
                    CheckRadioButton(hDlg, IDC_DISPLAY_ANDSCAN, IDC_DISPLAY_ORSCAN, IDC_DISPLAY_DELETESCAN);
                    break;
                case STRETCH_ORSCANS:
                    CheckRadioButton(hDlg, IDC_DISPLAY_ANDSCAN, IDC_DISPLAY_ORSCAN, IDC_DISPLAY_ORSCAN);
                    break;
                default:
                    CheckRadioButton(hDlg, IDC_DISPLAY_ANDSCAN, IDC_DISPLAY_ORSCAN, IDC_DISPLAY_ANDSCAN);
            }
            break;

        case DIB_PROPSHEET_PRINT:
            // Select print size
            {
                int iPrintSize = IDC_PRINT_BESTFIT;

                if (lpDIBInfo->dwPrintOption == ICMV_PRINT_ACTUALSIZE)
                {
                    iPrintSize = IDC_PRINT_ACTUALSIZE;
                }
                CheckRadioButton(hDlg, IDC_PRINT_ACTUALSIZE, IDC_PRINT_BESTFIT, iPrintSize);
            }
            break;

        default:
            break;
    }

} // End of function DlgDIBInfoPaint

//////////////////////////////////////////////////////////////////////////
//  Function:  SaveDIBInfoDlgPage
//
//  Description:
//    Saves the current page of the property sheet to the specified DIBINFO structure.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL  TRUE upon success, FALSE otherwise
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL SaveDIBInfoDlgPage(HWND hDlg, LPDIBINFO lpDIBInfo, UINT uiPageNum)
{
    switch (uiPageNum)
    {
        case DIB_PROPSHEET_DISPLAY:
            lpDIBInfo->bStretch = IsDlgButtonChecked(hDlg, IDC_DISPLAY_STRETCH);
            if (IsDlgButtonChecked(hDlg, IDC_DISPLAY_ANDSCAN))
                lpDIBInfo->dwStretchBltMode = STRETCH_ANDSCANS;
            else if (IsDlgButtonChecked(hDlg, IDC_DISPLAY_DELETESCAN))
                lpDIBInfo->dwStretchBltMode = STRETCH_DELETESCANS;
            else if (IsDlgButtonChecked(hDlg, IDC_DISPLAY_ORSCAN))
                lpDIBInfo->dwStretchBltMode = STRETCH_ORSCANS;

            if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20))
            {
                SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
            }
            break;

        case DIB_PROPSHEET_PRINT:
            {
                LPTSTR  pszTemp;

                pszTemp = GetDlgItemString(hDlg, IDC_PRINT_PRINTERLIST, NULL);
                if ( (NULL != pszTemp)
                     &&
                     (_tcscmp(pszTemp, __TEXT("No printers installed")))
                   )
                {
                    if (lstrcmpi(pszTemp, lpDIBInfo->lpszPrinterName))
                    {
                        HDC     hPrinterDC;
                        TCHAR   szProfile[MAX_PATH];
                        DWORD   dwSize = MAX_PATH;

                        GlobalFree(lpDIBInfo->lpszPrinterName);
                        lpDIBInfo->lpszPrinterName = pszTemp;
                        if (NULL != lpDIBInfo->pDevMode)
                            GlobalFree(GlobalHandle(lpDIBInfo->pDevMode));
                        lpDIBInfo->pDevMode = GetDefaultPrinterDevMode(lpDIBInfo->lpszPrinterName);

                        hPrinterDC = CreateDC(NULL, lpDIBInfo->lpszPrinterName, NULL, lpDIBInfo->pDevMode);
                        if (NULL != hPrinterDC)
                        {
                            if (GetICMProfile(hPrinterDC, &dwSize, szProfile))
                                UpdateString(&lpDIBInfo->lpszPrinterProfile, szProfile);
                            DeleteDC(hPrinterDC);
                        }
                    }
                }
                else
                {
                    if (NULL != lpDIBInfo->lpszPrinterName)
                    {
                        GlobalFree(lpDIBInfo->lpszPrinterName);
                        lpDIBInfo->lpszPrinterName = NULL;
                    }

                    if (NULL != pszTemp)
                    {
                        GlobalFree(pszTemp);
                    }
                }

                if (IsDlgButtonChecked(hDlg, IDC_PRINT_ACTUALSIZE))
                {
                    lpDIBInfo->dwPrintOption = ICMV_PRINT_ACTUALSIZE;
                }
                else
                {
                    lpDIBInfo->dwPrintOption = ICMV_PRINT_BESTFIT;
                }
            }
            break;

        default:
            DebugMsg(__TEXT("DIALOGS.C : SaveDIBInfoDlgPage : Invalid uiPageNum\r\n"));
            return(FALSE);
            break;
    }
    return(TRUE);
}   // End of function SaveDIBInfoDlgPage

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDlgItemString
//
//  Description:
//    If the specified control identifier text differs from the string
//    passed in, the string will be reallocated to the proper size and
//    the currently displayed item will be copied into the string.
//
//  Parameters:
//    @@@
//
//  Returns:
//    LPTSTR
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPTSTR GetDlgItemString(HWND hDlg, int iControlId, LPTSTR lpszCurrentString)
{
    // Local variables
    TCHAR    szEditString[MAX_PATH + 1];

    //  Initialize variables
    if (lpszCurrentString == NULL)
    {
        lpszCurrentString = GlobalAlloc(GPTR, sizeof(TCHAR));
        _tcscpy(lpszCurrentString, __TEXT(""));
    }

    GetDlgItemText(hDlg, iControlId, szEditString, MAX_PATH);
    {
        if (_tcscmp(szEditString, lpszCurrentString) != 0)
        {
            // Edit control differs from current string
            HGLOBAL hNewString, hCurrentString;

            hCurrentString = GlobalHandle(lpszCurrentString);
            GlobalUnlock(hCurrentString);
            hNewString = GlobalReAlloc(hCurrentString, (lstrlen(szEditString)+1) *sizeof(TCHAR), GMEM_MOVEABLE);
            lpszCurrentString = GlobalLock(hNewString);

            _tcscpy(lpszCurrentString, szEditString);
        }
    }
    return(lpszCurrentString);
}   // End of function GetDlgItemString

//////////////////////////////////////////////////////////////////////////
//  Function:  ColorMatchUI
//
//  Description:
//    Fills in COLORMATCHSETUP structure and calls SetupColorMatching, the new ICM 2.0 UI.
//
//  Parameters:
//    HWND  Owner window; NULL if dialog to have no owner.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////

BOOL ColorMatchUI(HWND hwndOwner, LPVOID lpvDIBInfo)
{
    // Local variables
    COLORMATCHSETUP     CMSetup;
    BOOL                bSetup;
    TCHAR               stPrinterProfile[MAX_PATH];

TCHAR               stMonitorProfile[MAX_PATH];
    TCHAR               stTargetProfile[MAX_PATH];
    LPDIBINFO           lpDIBInfo;
    DWORD               dwICMFlags;
    LPBITMAPV5HEADER    lpbi;

    //  ASSERTs and parameter validations
    ASSERT((NULL != hwndOwner) && (NULL != lpvDIBInfo));
    if ((NULL == hwndOwner) || (NULL == lpvDIBInfo))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    //  Initialize variables
#ifdef _DEBUG
    memset((PBYTE)&CMSetup, UNINIT_BYTE, sizeof(COLORMATCHSETUP));
#endif

    lpDIBInfo = (LPDIBINFO)lpvDIBInfo;
    dwICMFlags = lpDIBInfo->dwICMFlags;

    // Fill in required information
    // HACK for differnect versions of ICMUI.
    CMSetup.dwSize    = sizeof(COLORMATCHSETUP);
    CMSetup.dwVersion = COLOR_MATCH_VERSION;

    // Set ICM Flags
    CMSetup.dwFlags   = SetColorMatchUIFlags(lpDIBInfo->dwICMFlags) | CMS_USEAPPLYCALLBACK;
    CMSetup.hwndOwner = hwndOwner;

    // Fill in source name
    CMSetup.pSourceName   =  NULL;
    lpbi = (LPBITMAPV5HEADER)GlobalLock(lpDIBInfo->hDIB);
    if (IS_BITMAPV5HEADER(lpbi))
    {
        switch (lpbi->bV5CSType)
        {
            case PROFILE_LINKED:
                CMSetup.pSourceName = (LPCTSTR)GETPROFILEDATA(lpbi);
                break;

            case PROFILE_EMBEDDED:
                CMSetup.pSourceName = &(__TEXT("Embedded Profile"));
                break;

            default:
                break;
        }

    }
    GlobalUnlock(lpDIBInfo->hDIB);

    // Fill in device names
    CMSetup.pDisplayName  =  lpDIBInfo->lpszMonitorName;
    CMSetup.pPrinterName  =  lpDIBInfo->lpszPrinterName;

    // Fill in profile names.  Make local copies of the values within
    // the DIBINFO structure, as they may have been allocated to the
    // size of the actual strings.

    stPrinterProfile[0] = (TCHAR)'\0';
    if (lpDIBInfo->lpszPrinterProfile)
    {
        _tcscpy((LPTSTR)stPrinterProfile, lpDIBInfo->lpszPrinterProfile);
    }
    CMSetup.pPrinterProfile = (LPTSTR)&stPrinterProfile;
    CMSetup.ccPrinterProfile = MAX_PATH;

    stMonitorProfile[0] = (TCHAR)'\0';
    if (lpDIBInfo->lpszMonitorProfile)
    {
        _tcscpy((LPTSTR)stMonitorProfile, lpDIBInfo->lpszMonitorProfile);
    }
    CMSetup.pMonitorProfile = (LPTSTR)&stMonitorProfile;
    CMSetup.ccMonitorProfile = MAX_PATH;

    stTargetProfile[0] = (TCHAR)'\0';
    if (lpDIBInfo->lpszTargetProfile)
    {
        _tcscpy((LPTSTR)stTargetProfile, lpDIBInfo->lpszTargetProfile);
    }
    CMSetup.pTargetProfile = (LPTSTR)&stTargetProfile;
    CMSetup.ccTargetProfile = MAX_PATH;

    // Set up rendering intents
    CMSetup.dwRenderIntent = lpDIBInfo->dwRenderIntent;
    CMSetup.dwProofingIntent = lpDIBInfo->dwProofingIntent;

    // Set up for apply callback.
    CMSetup.lpfnApplyCallback = ColorSetupApply;
    CMSetup.lParamApplyCallback = (LPARAM) lpDIBInfo;

    // Clear unused items
    CMSetup.lpfnHook = NULL;
    CMSetup.lParam = (LPARAM)NULL;

    // Save ICM state before call
    dwICMFlags = lpDIBInfo->dwICMFlags;

    // Call the function to create the actual dialog
    SetLastError(0);
    bSetup = SetupColorMatching(&CMSetup);

    // Save information from dialog
    if (!bSetup)
    {
        if (ERROR_SUCCESS == GetLastError()) // User cancelled the dialog
        {
            return(TRUE);
        }
        else  // Something unexpected happened
        {
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        }
    }

    ApplyColorSettings(lpDIBInfo, &CMSetup);

    return(bSetup);
} // End of function ColorMatchUI

//////////////////////////////////////////////////////////////////////////
//  Function:  SetColorMatchUIFlags
//
//  Description:
//    Function which converts a DIBINFO's dwFlags to a COLORMATCHSETUP flag.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
DWORD SetColorMatchUIFlags(DWORD dwDIFlags)
{
    // Local variables
    DWORD       dwCMFlags;      // COLORMATCHSETUP flags

    //  ASSERTs and parameter validations

    //  Initialize variables
    dwCMFlags = CMS_SETRENDERINTENT | CMS_SETPROOFINTENT | CMS_SETTARGETPROFILE
                | CMS_SETMONITORPROFILE | CMS_SETPRINTERPROFILE;

    if (!CHECK_DWFLAG(dwDIFlags, ICMVFLAGS_ENABLE_ICM))
    {
        dwCMFlags |= CMS_DISABLEICM;
    }

    if (CHECK_DWFLAG(dwDIFlags, ICMVFLAGS_PROOFING))
    {
        dwCMFlags |= CMS_ENABLEPROOFING;
    }
    return(dwCMFlags);
} // End of function SetColorMatchUIFlags

///////////////////////////////////////////////////////////////////////
//
// Function:   SaveDIBToFileDialog
//
// Purpose:    Gets file name and saves DIB to file.
//
// Parms:
//
///////////////////////////////////////////////////////////////////////

void SaveDIBToFileDialog(HWND hWnd, LPDIBINFO lpDIBInfo)
{
    TCHAR           szFileName[MAX_PATH];
    DWORD           dwSaveAs[3] = {LCS_sRGB, LCS_sRGB, LCS_sRGB};
    OPENFILENAME    OpenFileName;
    PBITMAPV5HEADER pBitmap;

    // Validate parameters.
    if (NULL == lpDIBInfo)
        return;

    // Save bitmap.
    pBitmap = (PBITMAPV5HEADER) GlobalLock(lpDIBInfo->hDIB);
    if (NULL != pBitmap)
    {
        // Initialize OPENFILENAME structure for getting save as file name.
        lstrcpy(szFileName, lpDIBInfo->lpszImageFileName);
        memset(&OpenFileName, 0, sizeof(OPENFILENAME));
        OpenFileName.lStructSize = sizeof(OPENFILENAME);
        OpenFileName.hwndOwner = hWnd;
        OpenFileName.lpstrFile = szFileName;
        OpenFileName.nMaxFile = MAX_PATH;
        OpenFileName.lpstrTitle = __TEXT("Save Bitmap As");
        OpenFileName.Flags = OFN_CREATEPROMPT | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
        OpenFileName.lpstrDefExt = __TEXT("bmp");

        if ( (sizeof(BITMAPINFOHEADER) < pBitmap->bV5Size)
             &&
             (LCS_CALIBRATED_RGB == pBitmap->bV5CSType)
           )
        {
            // INVARIANT:  Bitmap is a calibrated RGB bitmap.

            // Can save as sRGB or calibrated bitmap.
            OpenFileName.lpstrFilter = __TEXT("sRGB Bitmap (*.bmp)\0*.bmp\0Calibrated RGB Bitmap\0 (*.bmp)\0*.bmp\0");
            dwSaveAs[1] = LCS_CALIBRATED_RGB;
        }
        else if ( (sizeof(BITMAPINFOHEADER) < pBitmap->bV5Size)
                  &&
                  ( (PROFILE_LINKED == pBitmap->bV5CSType)
                    ||
                    (PROFILE_EMBEDDED == pBitmap->bV5CSType)
                  )
                )
        {
            // INVARIANT:  Bitmap is either a LINKed or MBEDed bitmap.

            // Can save as sRGB, linked or embeded,
            OpenFileName.lpstrFilter = __TEXT("sRGB Bitmap (*.bmp)\0*.bmp\0Linked Bitmap (*.bmp)\0*.bmp\0Embedded Bitmap (*.bmp)\0*.bmp\0\0");
            dwSaveAs[1] = PROFILE_LINKED;
            dwSaveAs[2] = PROFILE_EMBEDDED;
        }
        else
        {
            // Can only save as sRGB bitmap.
            OpenFileName.lpstrFilter = __TEXT("sRGB Bitmap (*.bmp)\0*.bmp\0\0");
        }

        // No longer need hDIB in this routine.
        GlobalUnlock(lpDIBInfo->hDIB);

        if (GetSaveFileName(&OpenFileName))
        {
            // INVARIANT:  User specified file and choice OK.

            // Save DIB.
            SaveDIBToFile(hWnd, OpenFileName.lpstrFile, lpDIBInfo, dwSaveAs[OpenFileName.nFilterIndex -1]);
        }

    }
} // End of function SaveDIBToFileDialog

///////////////////////////////////////////////////////////////////////
//
// Function:   GetProfileSaveName
//
// Purpose:    Gets file name to save profile to.
//
// Parms:
//
///////////////////////////////////////////////////////////////////////

BOOL GetProfileSaveName(HWND hWnd, LPSTR* ppszFileName, DWORD dwSize)
{
    OPENFILENAMEA   OpenFileName;

    // Initialize OPENFILENAME structure for getting save as file name.
    memset(&OpenFileName, 0, sizeof(OPENFILENAME));
    OpenFileName.lStructSize = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner = hWnd;
    OpenFileName.lpstrFile = *ppszFileName;
    OpenFileName.nMaxFile = dwSize;
    OpenFileName.lpstrTitle = "Save Profile As";
    OpenFileName.Flags = OFN_CREATEPROMPT | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
    OpenFileName.lpstrFilter = "ICC Color Profile (*.icm, *.icc)\0*.icm;*.icc\0\0";
    OpenFileName.lpstrDefExt = "icm";

    return GetSaveFileNameA(&OpenFileName);
}

///////////////////////////////////////////////////////////////////////
//
// Function:   ColorSetupApply
//
// Purpose:    Applies current color setup dialog values.
//
// Parms:
//
///////////////////////////////////////////////////////////////////////

BOOL WINAPI ColorSetupApply(PCOLORMATCHSETUP pcmSetup, LPARAM lParam)
{
    ApplyColorSettings((LPDIBINFO)lParam, pcmSetup);
    return TRUE;
}

///////////////////////////////////////////////////////////////////////
//
// Function:   ApplyColorSettings
//
// Purpose:    Applies color settings to dib info.
//
// Parms:
//
///////////////////////////////////////////////////////////////////////

VOID ApplyColorSettings(LPDIBINFO lpDIBInfo, PCOLORMATCHSETUP pCMSetup)
{
    DWORD   dwICMFlags = lpDIBInfo->dwICMFlags;

    // Check if ICM and/or proofing is enabled
    SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_PROOFING, CHECK_DWFLAG(pCMSetup->dwFlags, CMS_ENABLEPROOFING));
    SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_ENABLE_ICM, (!CHECK_DWFLAG(pCMSetup->dwFlags, CMS_DISABLEICM)));
    if ((dwICMFlags != lpDIBInfo->dwICMFlags) && (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20)))
    {
        SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
    }

    // Update Intents
    if ((lpDIBInfo->dwRenderIntent != pCMSetup->dwRenderIntent) || (lpDIBInfo->dwProofingIntent != pCMSetup->dwProofingIntent))
    {
        SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
    }
    lpDIBInfo->dwRenderIntent = pCMSetup->dwRenderIntent;
    lpDIBInfo->dwProofingIntent = pCMSetup->dwProofingIntent;

    // Update DIBINFO profile strings if CMSetup strings have changed
    if (0 != _tcscmp(__TEXT(""), pCMSetup->pMonitorProfile))
    {
        UpdateString(&(lpDIBInfo->lpszMonitorProfile), pCMSetup->pMonitorProfile);
        SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
    }

    if (0 != _tcscmp(__TEXT(""), pCMSetup->pPrinterProfile))
    {
        UpdateString(&(lpDIBInfo->lpszPrinterProfile), pCMSetup->pPrinterProfile);
    }

    if (0 != _tcscmp(__TEXT(""), pCMSetup->pTargetProfile))
    {
        UpdateString(&(lpDIBInfo->lpszTargetProfile),  pCMSetup->pTargetProfile);
        SetDWFlags(&(lpDIBInfo->dwICMFlags), ICMVFLAGS_CREATE_TRANSFORM, TRUE);
    }

    InvalidateRect(lpDIBInfo->hWndOwner, NULL, FALSE);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  PrintDialog
//
//  Description:
//    Displays printing dialog box.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL PrintDialog(HWND hWnd, HINSTANCE hInst, LPDIBINFO lpDIBInfo)
{
    PRINTDLG    Print;

    memset(&Print, 0, sizeof(PRINTDLG));
    Print.lStructSize = sizeof(PRINTDLG);
    Print.hwndOwner = hWnd;
    Print.hDevMode = GlobalHandle(lpDIBInfo->pDevMode);

    if (PrintDlg(&Print))
    {
        lpDIBInfo->pDevMode = (PDEVMODE) GlobalLock(Print.hDevMode);
        if (lstrcmpi(lpDIBInfo->lpszPrinterName, lpDIBInfo->pDevMode->dmDeviceName))
        {
            HDC     hPrinterDC;
            TCHAR   szProfile[MAX_PATH];
            DWORD   dwSize = MAX_PATH;

            UpdateString(&lpDIBInfo->lpszPrinterName, lpDIBInfo->pDevMode->dmDeviceName);
            hPrinterDC = CreateDC(NULL, lpDIBInfo->lpszPrinterName, NULL, lpDIBInfo->pDevMode);
            if (NULL != hPrinterDC)
            {
                if (GetICMProfile(hPrinterDC, &dwSize, szProfile))
                    UpdateString(&lpDIBInfo->lpszPrinterProfile, szProfile);
                DeleteDC(hPrinterDC);
            }
        }
        PrintImage(hWnd);
    }

    return TRUE;
}


DIALOGS.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIALOGS.H
//
//  PURPOSE:
//    Include file for DIALOGS.C
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros
#define DIB_PROPSHEET_MIN      0
#define DIB_PROPSHEET_DISPLAY  0
#define DIB_PROPSHEET_PRINT    1
#define DIB_PROPSHEET_MAX      1
#define DIB_PROPSHEET_DEFAULT  DIB_PROPSHEET_DISPLAY

// General STRUCTS && TYPEDEFS

// Function prototypes
BOOL fOpenNewImage(HWND hWnd, LPTSTR lpszFileName);
BOOL CALLBACK DlgSetICMOptionsForDevice(HWND  hwndDlg, UINT  uMsg, WPARAM  wParam, LPARAM  lParam);
int CreateDIBPropSheet(HWND hwndOwner, HINSTANCE hInst, int nStartPage, LPTSTR lpszCaption);
BOOL APIENTRY DlgDIBPropSheet(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam);
BOOL ColorMatchUI(HWND hwndOwner, LPVOID lpvDIBInfo);
void SaveDIBToFileDialog(HWND hWnd, LPDIBINFO lpDIBInfo);
BOOL GetProfileSaveName(HWND hWnd, LPSTR* ppszFileName, DWORD dwSize);
BOOL PrintDialog(HWND hWnd, HINSTANCE hInst, LPDIBINFO lpDIBInfo);


DIBINFO.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIBINFO.C
//
//  PURPOSE:
//
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings

// C RunTime Header Files
#include <tchar.h>

// Local Header Files
#include "icmview.h"
#include "dibinfo.h"
#include "dibs.h"
#include "regutil.h"
#include "print.h"
#include "debug.h"

// local definitions
#ifndef ICM_DONE_OUTSIDEDC
    #define ICM_DONE_OUTSIDEDC  4
#endif

// default settings

// external functions

// external data

// public data

// private data

// public functions

// private functions

//////////////////////////////////////////////////////////////////////////
//  Function:  fReadDIBInfo
//
//  Description:
//    Will read a file in DIB format and return a global HANDLE
//    to it's BITMAPINFO.  This function will work with both
//    "old" (BITMAPCOREHEADER) and "new" (BITMAPINFOHEADER)
//    bitmap formats, but will always return a "new" BITMAPINFO
//
//  Parameters:
//    LPTSTR     Pointer to string containing the filename of the image.
//    LPDIBINFO Pointer to DIBINFO structure.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL fReadDIBInfo(LPTSTR lpszFileName, LPDIBINFO lpDIBInfo)
{
    // Local variables
    HANDLE                hDIBFile;
    HANDLE                hDIB = NULL;
    LPBITMAPINFOHEADER    lpBmpInfoHdr = NULL;

    //  Initialize variables
    if ( (NULL == lpszFileName) || (NULL == lpDIBInfo) )
    {
        DebugMsg(__TEXT("fReadDIBInfo:  NULL parameter.\r\n"));
        return FALSE;
    }

    // Set the filename
    lpDIBInfo->lpszImageFileName = GlobalAlloc(GPTR,(lstrlen(lpszFileName)+1) * sizeof(TCHAR));
    if (lpDIBInfo->lpszImageFileName)
    {
        _tcscpy(lpDIBInfo->lpszImageFileName, lpszFileName);
    }

    // Open image file
    hDIBFile = CreateFile(lpszFileName,
                          GENERIC_READ,
                          FILE_SHARE_READ,
                          NULL,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL,
                          (HANDLE)NULL);

    if (INVALID_HANDLE_VALUE == hDIBFile)
    {
        return FALSE;
    }

    // Read DIB from file.
    hDIB = ReadDIBFromFile(hDIBFile);
    CloseHandle(hDIBFile);

    // Make sure that DIB file read was successful.
    if (NULL == hDIB)
    {
        DebugMsg(__TEXT("fReadDIBInfo:  Failed to read DIB file.\r\n"));
        return FALSE;
    }

    // Get pointer to DIB.
    lpBmpInfoHdr = (LPBITMAPINFOHEADER) GlobalLock(hDIB);
    if (NULL == lpBmpInfoHdr)
    {
        GlobalFree(hDIB);
        return FALSE;
    }

    // Set values in DIBINFO structure
    if (sizeof(BITMAPCOREHEADER) == lpBmpInfoHdr->biSize)
    {
        LPBITMAPCOREHEADER  lpCoreHdr;

        lpCoreHdr = (LPBITMAPCOREHEADER) lpBmpInfoHdr;
        lpDIBInfo->uiDIBWidth   = (DWORD) lpCoreHdr->bcWidth;
        lpDIBInfo->uiDIBHeight  = (DWORD) lpCoreHdr->bcHeight;
        lpDIBInfo->dwDIBBits = lpCoreHdr->bcBitCount;
    }
    else
    {
        lpDIBInfo->dwDIBBits    = (DWORD)lpBmpInfoHdr->biBitCount;
        lpDIBInfo->uiDIBWidth  = abs(lpBmpInfoHdr->biWidth);
        lpDIBInfo->uiDIBHeight = abs(lpBmpInfoHdr->biHeight);
    }

    // Set bmFormat.  Since the app only supports RGB's, pixel depth is enough
    lpDIBInfo->bmFormat = (DWORD)-1;
    switch (lpDIBInfo->dwDIBBits)
    {
        case 1:
            //lpDIBInfo->bmFormat = BM_1GRAY;
            lpDIBInfo->bmFormat = 0;
            break;
        case 16:
            // Should either be 555 or 565 bitmap.
            // Check mask if BI_BITFILEDS.
            if ( (BI_BITFIELDS == lpBmpInfoHdr->biCompression)
                 &&
                 (0x7E0 == *((LPDWORD)(lpBmpInfoHdr + 1) +1))
               )
            {
                lpDIBInfo->bmFormat = BM_565RGB;
            }
            else
            {
                lpDIBInfo->bmFormat = BM_x555RGB;
            }
            break;
        case 24:
            lpDIBInfo->bmFormat = BM_RGBTRIPLETS;  // RGB Triplets -- most significant byte is R
            break;
        case 32:
            lpDIBInfo->bmFormat = BM_xRGBQUADS;
            break;
        case 4:
        case 8:
            lpDIBInfo->bmFormat = 0;
            break;
        default:
            DebugMsg(__TEXT("fReadDIBInfo : Unknown dwDIBBits value.\r\n"));
            break;
    }
    lpDIBInfo->hDIB = hDIB;
    lpDIBInfo->dwStretchBltMode = ICMV_STRETCH_DEFAULT;
    lpDIBInfo->bStretch = FALSE;
    lpDIBInfo->hPal = CreateDIBPalette(hDIB);

    // Unlock bmp info.
    GlobalUnlock(hDIB);
    return TRUE;
}   // End of function fReadDIBInfo

//
// Functions for DIBINFO structure
//
///////////////////////////////////////////////////////////////////////
//
// Function:  GetDIBInfoHandle
//
// Purpose:   Encapsulates the getting and setting of the
//            WW_DIB_HINFO value for a window since we are
//            storing a handle, and handles change from 16
//            bits in WIN16 to 32 bits in WIN32.
//
// Parms:     hWnd     == Window to retrieve the DIBINFO handle from.
//
// Returns:   The previous value.
//
///////////////////////////////////////////////////////////////////////

HGLOBAL GetDIBInfoHandle (HWND hWnd)
{
    return (HGLOBAL)GetWindowLong(hWnd, GWL_DIBINFO);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDIBInfoPtr
//
//  Description:
//    Gets a pointer to the DIBINFO structure of the window.
//
//  Parameters:
//    HWND    Handle to a window
//
//  Returns:
//    LPDIBINFO  Pointer to DIBINFO structure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPDIBINFO GetDIBInfoPtr(HWND hWnd)
{
    // Local variables
    HGLOBAL     hDIBInfo;       // Handle to DIBINFO structure
    LPDIBINFO   lpDIBInfo;      // Pointer to DIBINFO structure

    //  Initialize variables
    lpDIBInfo = NULL;

    hDIBInfo = GetDIBInfoHandle(hWnd);
    if (hDIBInfo != NULL)
    {
        lpDIBInfo = GlobalLock(hDIBInfo);
        //lpDIBInfo = GlobalLock(hDIBInfo);
    }

    return(lpDIBInfo);
}   // End of function GetDIBInfoPtr

//////////////////////////////////////////////////////////////////////////
//  Function:  CreateDIBInfo
//
//  Description:
//    Initializes the window/thread by setting the ICMINFO values to the
//    current global values.  Then, the extra window information is set to
//    point to the ICMINFO structure so that any operation using this
//    window can obtain necesary information to manipulate the image.
//
//  Parameters:
//    none.
//
//  Returns:
//    HANDLE to DIBINFO structure.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////

HGLOBAL CreateDIBInfo(void)
{
    // Local variables
    HGLOBAL   hDIBInfo;           // Handle to ICMINFO structure
    LPDIBINFO lpDIBInfo;          // Pointer to ICMINFO structure

    // Allocate DIBINFO structure and get a pointer to it
    hDIBInfo = GlobalAlloc(GHND, sizeof(DIBINFO));
    if ((HGLOBAL)NULL != hDIBInfo)
    {
        if (NULL != (lpDIBInfo = GlobalLock(hDIBInfo)))
        {
            InitDIBInfo(lpDIBInfo);
        }
        else
        {
            DebugMsg(__TEXT("DIBS.C : CreateDIBInfo : Failed to lock DIBINFO\r\n"));
        }
    }
    else
    {
        DebugMsg(__TEXT("DIBS.C : CreateDIBInfo : Global alloc failed\r\n"));
        return(NULL);
    }
    GlobalUnlock(hDIBInfo);
    if (NULL == lpDIBInfo)
    {
        GlobalFree(hDIBInfo);
        hDIBInfo = NULL;
    }
    return(hDIBInfo);
}

///////////////////////////////////////////////////////////////////////////////
//  Function:  fDuplicateDIBInfo
//
//  Description:
//    Copies the source DIBINFO into the target DIBINFO.
//
//  Parameters:
//    LPDIBINFO Target DIBINFO to recieve the contents of the source DIBINFO.
//    LPDIBINFO Source DIBINFO to be copied into the target DIBINFO.
//
//  Returns:
//    void
//
//  Comments:
//
///////////////////////////////////////////////////////////////////////////////
LPDIBINFO fDuplicateDIBInfo(LPDIBINFO lpDIDest, LPDIBINFO lpDISrc)
{
    // Local variables

    // Initialize variables
    if (lpDISrc == NULL)
    {
        return(NULL);
    }
    if (lpDIDest == NULL)
    {
        lpDIDest = (LPDIBINFO)GlobalLock(CreateDIBInfo());
    }
    if (lpDIDest == (LPDIBINFO)NULL)
    {
        return(NULL);
    }

    // Now, copy the body of the DIBINFO structure
    lpDIDest->lpszImageFileName = CopyString(lpDISrc->lpszImageFileName);
    CopyRect((LPRECT)&lpDIDest->rcClip, (LPRECT)&lpDISrc->rcClip);

    lpDIDest->hWndOwner         = lpDISrc->hWndOwner;
    lpDIDest->hDIB              = lpDISrc->hDIB;
    lpDIDest->hDIBTransformed   = lpDISrc->hDIBTransformed;
    lpDIDest->hPal              = lpDISrc->hPal;
    lpDIDest->dwDIBBits         = lpDISrc->dwDIBBits;
    lpDIDest->uiDIBWidth        = lpDISrc->uiDIBWidth;
    lpDIDest->uiDIBHeight       = lpDISrc->uiDIBHeight;
    lpDIDest->bmFormat          = lpDISrc->bmFormat;

    lpDIDest->bStretch          = lpDISrc->bStretch;
    lpDIDest->dwStretchBltMode  = lpDISrc->dwStretchBltMode;
    lpDIDest->dwPrintOption     = lpDISrc->dwPrintOption;
    lpDIDest->dwXScale          = lpDISrc->dwXScale;
    lpDIDest->dwYScale          = lpDISrc->dwYScale;

    // Copy DEVMODE.
    if (NULL != lpDISrc->pDevMode)
    {
        HANDLE  hDevMode = GlobalHandle(lpDISrc->pDevMode);
        DWORD   dwSize = GlobalSize(hDevMode);

        lpDIDest->pDevMode = (PDEVMODE) GlobalLock(GlobalAlloc(GHND, dwSize));
        if (NULL != lpDIDest->pDevMode)
        {
            memcpy(lpDIDest->pDevMode, lpDISrc->pDevMode, dwSize);
        }
    }

    // Copy the ICM information now
    fDuplicateICMInfo(lpDIDest, lpDISrc);

    // Made it this far--return pointer to DIBINFO
    return(lpDIDest);
} // End of function fDuplicateDIBInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  fDuplicateICMInfo
//
//  Description:
//    Safely copies source ICM information in a DIBINFO structure to
//    the target DIBINFO.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL fDuplicateICMInfo(LPDIBINFO lpDIDest, LPDIBINFO lpDISrc)
{
    // Local variables

    //  Initialize variables
    if (NULL == lpDIDest)
    {
        DebugMsg(__TEXT("DIBS.C : fDuplicateICMInfo : NULL Target\r\n"));
        return(FALSE);
    }
    if (NULL == lpDISrc)
    {
        DebugMsg(__TEXT("DIBS.C : fDuplicateICMInfo : NULL Source\r\n"));
        return(FALSE);
    }

    lpDIDest->dwICMFlags = lpDISrc->dwICMFlags;
    lpDIDest->hLCS = lpDISrc->hLCS;

    // Copy strings.
    UpdateString(&(lpDIDest->lpszMonitorName)   ,lpDISrc->lpszMonitorName);
    UpdateString(&(lpDIDest->lpszMonitorProfile),lpDISrc->lpszMonitorProfile);
    UpdateString(&(lpDIDest->lpszPrinterName   ),lpDISrc->lpszPrinterName);
    UpdateString(&(lpDIDest->lpszPrinterProfile),lpDISrc->lpszPrinterProfile);
    UpdateString(&(lpDIDest->lpszTargetProfile) ,lpDISrc->lpszTargetProfile);

    // Copy intents
    lpDIDest->dwRenderIntent = lpDIDest->dwRenderIntent;
    lpDIDest->dwProofingIntent = lpDIDest->dwProofingIntent;

}   // End of function fDuplicateICMInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  FreeDIBInfo
//
//  Description:
//    Frees the DIBINFO structure and its members.
//
//  Parameters:
//    HGLOBAL   Handle to DIBINFO structure@@@
//
//  Returns:
//    void
//
//  Comments:
//    This function will also deallocate the association ICMINFO
//    structure which is contained within the DIBINFO structure.
//
//////////////////////////////////////////////////////////////////////////
BOOL fFreeDIBInfo(HGLOBAL hDIBInfo, BOOL bFreeDIBHandles)
{
    // Local variables
    LPDIBINFO lpDIBInfo;
    HGLOBAL   hFreed;
    DWORD     dwLastError;

    // Initialize variables
    if (hDIBInfo == NULL)
    {
        return(TRUE);
    }

    // Obtain DIBINFO pointer
    lpDIBInfo = GlobalLock(hDIBInfo);
    if (lpDIBInfo == NULL)
    {
        return(FALSE);
    }
    // Have the pointer, let's free its members
    if (lpDIBInfo->lpszImageFileName)
        hFreed = GlobalFree(lpDIBInfo->lpszImageFileName);
    if (lpDIBInfo->lpszMonitorName)
        hFreed = GlobalFree(lpDIBInfo->lpszMonitorName);
    if (lpDIBInfo->lpszMonitorProfile)
        hFreed = GlobalFree(lpDIBInfo->lpszMonitorProfile);
    if (lpDIBInfo->lpszPrinterName)
        hFreed = GlobalFree(lpDIBInfo->lpszPrinterName);
    if (lpDIBInfo->lpszPrinterProfile)
        hFreed = GlobalFree(lpDIBInfo->lpszPrinterProfile);
    if (lpDIBInfo->lpszTargetProfile)
        hFreed = GlobalFree(lpDIBInfo->lpszTargetProfile);

    // Preserve last error if necessary
    SetLastError(0);

    if (bFreeDIBHandles)
    {
        if (NULL != lpDIBInfo->hDIB)
            GlobalFree(lpDIBInfo->hDIB);
        if (NULL != lpDIBInfo->hDIBTransformed)
            GlobalFree(lpDIBInfo->hDIBTransformed);
    }
    GlobalUnlock(hDIBInfo);
    if (NULL != (GlobalFree(hDIBInfo))) // unsuccessful free
    {
        dwLastError = GetLastError();
        DebugMsg(__TEXT("DIBS.C : fFreeDIBInfo : GlobalFree failed, LastError = %ld\r\n"), dwLastError);
        return(FALSE);
    }
    return(TRUE);
} // End of function fFreeDIBInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  InitDIBInfo
//
//  Description:
//    Given a pointer to a DIBINFO structure, this function will place
//    default values in all of its members.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL  Success / Failure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL InitDIBInfo(LPDIBINFO lpDIBInfo)
{
    // Local variables

    //  Initialize variables
    if (NULL == lpDIBInfo)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        DebugMsg(__TEXT("DIBS.C : InitDIBInfo : lpDIBInfo == NULL\r\n"));
        return(FALSE);
    }
#ifdef _DEBUG
    memset(lpDIBInfo, UNINIT_BYTE, sizeof(DIBINFO));
#endif

    lpDIBInfo->hWndOwner = NULL;
    lpDIBInfo->lpszImageFileName = NULL;
    lpDIBInfo->hDIB = NULL;
    lpDIBInfo->hDIBTransformed = NULL;
    lpDIBInfo->hPal = NULL;

    // Image attributes
    lpDIBInfo->dwDIBBits = 0;
    lpDIBInfo->uiDIBWidth   =   0;
    lpDIBInfo->uiDIBHeight = 0;
    lpDIBInfo->bmFormat = (DWORD)-1;

    // Display options
    SetRect((LPRECT)&(lpDIBInfo->rcClip), 0, 0, 0, 0);
    lpDIBInfo->dwStretchBltMode = ICMV_STRETCH_DEFAULT;
    lpDIBInfo->bStretch = FALSE;

    // Printing Options
    lpDIBInfo->dwPrintOption = ICMV_PRINT_DEFAULTSIZE;
    lpDIBInfo->dwXScale = 0;
    lpDIBInfo->dwYScale = 0;
    lpDIBInfo->pDevMode = NULL;

    // ICM Attributes
    lpDIBInfo->dwICMFlags = ICMVFLAGS_DEFAULT_ICMFLAGS;
    lpDIBInfo->hLCS = NULL;
    lpDIBInfo->lpszMonitorName = NULL;
    lpDIBInfo->lpszMonitorProfile = NULL;
    lpDIBInfo->lpszPrinterName = NULL;
    lpDIBInfo->lpszPrinterProfile = NULL;
    lpDIBInfo->lpszTargetProfile = NULL;
    lpDIBInfo->dwRenderIntent = ICMV_RENDER_INTENT_DEFAULT;
    lpDIBInfo->dwProofingIntent = ICMV_PROOFING_INTENT_DEFAULT;

    return(TRUE);
}   // End of function InitDIBInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDefaultICMInfo
//
//  Description:
//    Initializes the global DIBINFO structure with default profiles.
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL GetDefaultICMInfo(void)
{
    // Local variables
    LPDIBINFO   lpDIBInfo;
    HDC                 hDC;
    BOOL                bRC;
    LPSTR           lpszDefaultProfile;

    //  Initialize variables
    bRC = TRUE;
    lpszDefaultProfile = NULL;
    lpDIBInfo = GetDIBInfoPtr(ghAppWnd); // Lock info for writing

    // Get display DC
    hDC = GetDC(ghAppWnd);
    lpDIBInfo->lpszMonitorName = GetRegistryString(HKEY_LOCAL_MACHINE,
                                                   __TEXT("System\\CurrentControlSet\\Services\\Class\\Monitor\\0000"),
                                                   __TEXT("DriverDesc"));
    lpDIBInfo->lpszMonitorProfile = GetDefaultICMProfile(hDC);

    if (NULL == lpDIBInfo->lpszMonitorProfile)
    {
        DWORD   dwSize;

        GetStandardColorSpaceProfile(NULL, LCS_WINDOWS_COLOR_SPACE, NULL, &dwSize);
        if (0 != dwSize)
        {
            lpszDefaultProfile = GlobalAlloc(GPTR, dwSize);
            if (GetStandardColorSpaceProfile(NULL, LCS_WINDOWS_COLOR_SPACE, lpszDefaultProfile, &dwSize))
            {
                GetBaseFilename(lpszDefaultProfile, &(lpDIBInfo->lpszMonitorProfile));
            }
            else
            {
                DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
                bRC = FALSE;
                lpDIBInfo->lpszMonitorProfile = NULL;
            }
        }
        else
        {
            lpDIBInfo->lpszMonitorProfile = GlobalAlloc(GPTR, (lstrlen(DEFAULT_ICM_PROFILE) +1 ) * sizeof(TCHAR));
            _tcscpy(lpDIBInfo->lpszMonitorProfile, DEFAULT_ICM_PROFILE);
        }

        DebugMsg(__TEXT("Display DC didn't get profile.  Using <%s>\r\n"), lpDIBInfo->lpszMonitorProfile);
    }
    DebugMsg(__TEXT("GetDefaultICMInfo:  Monitor profile <%s>\r\n"), lpDIBInfo->lpszMonitorProfile);
    ReleaseDC(ghAppWnd, hDC);

    if (bRC)
    {
        // Get printer name and DC
        lpDIBInfo->lpszPrinterName = GetDefaultPrinterName();
        if (lpDIBInfo->lpszPrinterName != NULL)
        {
            hDC = GetPrinterDC(lpDIBInfo->lpszPrinterName, lpDIBInfo->pDevMode);
            if (hDC != NULL)
            {
                lpDIBInfo->lpszPrinterProfile = GetDefaultICMProfile(hDC);
                lpDIBInfo->lpszTargetProfile= lpDIBInfo->lpszPrinterProfile;
                DebugMsg(__TEXT("GetDefaultICMInfo:  Printer profile <%s>\r\n"), lpDIBInfo->lpszPrinterProfile ?
                         lpDIBInfo->lpszPrinterProfile : __TEXT("NULL"));
                DeleteDC(hDC);
                bRC = TRUE;
            }
        }
        GlobalUnlock(GlobalHandle(lpDIBInfo));
    }

    if (NULL != lpszDefaultProfile)
    {
        GlobalFree(lpszDefaultProfile);
    }
    return(bRC);
}   // End of function GetDefaultICMInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  DumpDIBINFO
//
//  Description:
//    Dumps DIBInfo structure
//
//  Parameters:
//    @@@
//
//  Returns:
//    void
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
void DumpDIBINFO(LPTSTR lpszMsg, LPDIBINFO lpDIBInfo)
{
    // Local variables

    //  Initialize variables
    DebugMsg(__TEXT("\r\n******************** DumpDIBINFO ********************\r\n"));
    DebugMsg(__TEXT("***** %s \r\n"), lpszMsg);
    if (lpDIBInfo == NULL)
    {
        DebugMsg(__TEXT("lpDIBInfo                    NULL\r\n\r\n"));
        return;
    }

    DebugMsg(__TEXT("lpDIBInfo                   0x%08lx\r\n"), lpDIBInfo);
    if (lpDIBInfo->lpszImageFileName != NULL)
    {
        DebugMsg(__TEXT("lpszImageFileName           %s\r\n"), lpDIBInfo-> lpszImageFileName);
    }
    else
    {
        DebugMsg(__TEXT("lpszImageFileName           <NULL PTR>\r\n"));
    }

    DebugMsg(__TEXT("hDIB                        0x%08lx\r\n"), lpDIBInfo-> hDIB);
    DebugMsg(__TEXT("hDIBTransformed             0x%08lx\r\n"), lpDIBInfo-> hDIBTransformed);
    DebugMsg(__TEXT("hPal                        0x%08lx\r\n"), lpDIBInfo-> hPal);
    DebugMsg(__TEXT("dwDIBBits                   %lu\r\n"), lpDIBInfo-> dwDIBBits);
    DebugMsg(__TEXT("uiDIBWidth                  %lu\r\n"), lpDIBInfo-> uiDIBWidth);
    DebugMsg(__TEXT("uiDIBHeight                 %lu\r\n"), lpDIBInfo-> uiDIBHeight);
    DebugMsg(__TEXT("bmFormat                    %ld\r\n"), (DWORD)(lpDIBInfo->bmFormat));
    DumpRectangle(__TEXT("rcClip                      "), (LPRECT)&(lpDIBInfo->rcClip));
    DebugMsg(__TEXT("dwStretchBltMode            %lu\r\n"), lpDIBInfo-> dwStretchBltMode);
    DebugMsg(__TEXT("bStretch                    %lu\r\n"), lpDIBInfo-> bStretch);
    DebugMsg(__TEXT("dwPrintOption               %lu\r\n"), lpDIBInfo-> dwPrintOption);
    DebugMsg(__TEXT("dwXScale                    %lu\r\n"), lpDIBInfo-> dwXScale);
    DebugMsg(__TEXT("dwYScale                    %lu\r\n\r\n"), lpDIBInfo-> dwYScale);
    DebugMsg(__TEXT("lpszMonitorName             0x%08lX <%s>\r\n"), lpDIBInfo->lpszMonitorName   , lpDIBInfo->lpszMonitorName    ? lpDIBInfo->lpszMonitorName    : __TEXT("NULL"));
    DebugMsg(__TEXT("lpszMonitorProfile          0x%08lX <%s>\r\n"), lpDIBInfo->lpszMonitorProfile, lpDIBInfo->lpszMonitorProfile ? lpDIBInfo->lpszMonitorProfile : __TEXT("NULL"));
    DebugMsg(__TEXT("lpszPrinterName             0x%08lX <%s>\r\n"), lpDIBInfo->lpszPrinterName   , lpDIBInfo->lpszPrinterName    ? lpDIBInfo->lpszPrinterName    : __TEXT("NULL"));
    DebugMsg(__TEXT("lpszPrinterProfile          0x%08lX <%s>\r\n"), lpDIBInfo->lpszPrinterProfile, lpDIBInfo->lpszPrinterProfile ? lpDIBInfo->lpszPrinterProfile : __TEXT("NULL"));
    DebugMsg(__TEXT("lpszTargetProfile           0x%08lX <%s>\r\n\r\n"), lpDIBInfo->lpszTargetProfile, lpDIBInfo->lpszTargetProfile ? lpDIBInfo->lpszTargetProfile : __TEXT("NULL"));
    DebugMsg(__TEXT("dwICMFlags                  %ld\r\n"), lpDIBInfo->dwICMFlags);
    DebugMsg(__TEXT("dwRenderIntent              %ld\r\n"), lpDIBInfo->dwRenderIntent);
    DebugMsg(__TEXT("dwProofingIntent            %ld\r\n"), lpDIBInfo->dwProofingIntent);
    DebugMsg(__TEXT("^^^^^^^^^^DumpDIBINFO   0x%08lx^^^^^^^^^^^^^^\r\n\r\n\r\n"), lpDIBInfo);
}   // End of function DumpDIBINFO

//////////////////////////////////////////////////////////////////////////
//  Function:  SetupDC
//
//  Description:
//    Sets up DC for drawing based on DIBINFO.  This consolidates code for both
//    printing and drawing to the screen.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL SetupDC(HDC hDC, LPDIBINFO lpDIBInfo, HPALETTE *phOldPalette, HDC *phDCPrinter)
{
    int     iICMMode;
    BOOL    bRC;
    TCHAR   stFullProfile[MAX_PATH];

    // Initialize variables.
    *phOldPalette = NULL;
    *phDCPrinter = NULL;

    // Select/Realize our palette.  Make it a background palette, so that
    // it doesn't mess up foreground palette when background windows repaint.
    if (NULL != lpDIBInfo->hPal)
    {
        *phOldPalette = SelectPalette (hDC, lpDIBInfo->hPal, TRUE);
        if (NULL == *phOldPalette)
        {
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        }
    }

    //Only do ICM pre-processing if "Inside DC" is selected.
    if (!CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20))
    {
        if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ENABLE_ICM))
        {
            // build the FULL pathname to the profile.
            wsprintf(stFullProfile,__TEXT("%s\\%s"), gstProfilesDir, lpDIBInfo->lpszMonitorProfile);

            if (SetICMProfile(hDC, stFullProfile))
            {
                iICMMode = SetICMMode(hDC, ICM_ON);
                if (0 == iICMMode)
                {
                    DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
                    SetDWFlags((LPDWORD)&(lpDIBInfo->dwICMFlags), ICMVFLAGS_ENABLE_ICM, FALSE);
                }
                else
                {
                    if ((CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_PROOFING)) && (NULL != lpDIBInfo->lpszTargetProfile))
                    {
                        *phDCPrinter = GetPrinterDC(lpDIBInfo->lpszPrinterName, lpDIBInfo->pDevMode);
                        if (NULL != *phDCPrinter)
                        {
                            wsprintf(stFullProfile,__TEXT("%s\\%s"), gstProfilesDir, lpDIBInfo->lpszTargetProfile);
                            bRC = SetICMProfile(*phDCPrinter, stFullProfile);
                            if (bRC)
                            {
                                iICMMode = SetICMMode(*phDCPrinter, ICM_ON);
                                if (0 != iICMMode)
                                {
                                    bRC = ColorMatchToTarget(hDC, *phDCPrinter, CS_ENABLE);
                                    if (!bRC)
                                    {
                                        DebugMsg(__TEXT("DIBPaint:  ColorMatchToTarget w/profile %s FAILED\r\n"), stFullProfile);
                                        DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
                                    }
                                }
                                else
                                {
                                    DebugMsg(__TEXT("DIBPaint:  SetICMMode (%s, %s) FAILED!\r\n"), lpDIBInfo->lpszPrinterName, stFullProfile);
                                    DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
                                }
                            }
                            else
                            {
                                DebugMsg(__TEXT("DIBPaint:  SetICMProfile w/profile %s FAILED\r\n"), stFullProfile);
                                DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
                            }
                        }
                        else
                        {
                            DebugMsg(__TEXT("DIBPaint:  GetPrinterDC() w/printer %s FAILED\r\n"), lpDIBInfo->lpszPrinterName);
                            DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
                        }
                    }

                    if (NULL != lpDIBInfo->hPal)
                    {
                        WORD    wEntries;

                        GetObject(lpDIBInfo->hPal, sizeof(wEntries), &wEntries);
                        //if(!ColorCorrectPalette(hDC, lpDIBInfo->hPal, 0, (DWORD)wEntries))
                        //{
                        //    DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
                        //}
                    }
                }
            }
            else
            {
                DebugMsg(__TEXT("DIBPaint : SetICMProfile(%s)FAILED\r\n"), stFullProfile);
                DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
            }
        }
        else
        {
            iICMMode = SetICMMode(hDC, ICM_OFF);
        }
    }
    else
    {
        iICMMode = SetICMMode(hDC, ICM_DONE_OUTSIDEDC);
    }

    return TRUE;
}


DIBINFO.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIBINFO.H
//
//  PURPOSE:
//
//
//  PLATFORMS:
//    Windows 95, Windows NT,
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros

// Default stretch mode
#define ICMV_STRETCH_DEFAULT                STRETCH_DELETESCANS

// LCS Intent default
#define ICMV_LCSINTENT_DEFAULT              LCS_GM_IMAGES

// ICM rendering intents
#define ICMV_RENDER_INTENT_DEFAULT      INTENT_PERCEPTUAL
#define ICMV_PROOFING_INTENT_DEFAULT    INTENT_ABSOLUTE_COLORIMETRIC

// Flags for use in the dwPrintOption member of DIBINFO
#define ICMV_PRINT_ACTUALSIZE               0x00000001
#define ICMV_PRINT_BESTFIT                  0x00000002
#define ICMV_PRINT_DEFAULTSIZE              ICMV_PRINT_ACTUALSIZE

// Flags for use in the dwICMFlags field of the ICMINFO structure
#define ICMVFLAGS_ENABLE_ICM                0x00000001
#define ICMVFLAGS_PROOFING                  0x00000002
#define ICMVFLAGS_ICM20                     0x00000004
#define ICMVFLAGS_CREATE_TRANSFORM          0x00000008
#define ICMVFLAGS_DEFAULT_ICMFLAGS          (ICMVFLAGS_ICM20 | ICMVFLAGS_ENABLE_ICM)

// General STRUCTS && TYPEDEFS
typedef struct
{
    // Handle to window which owns this structure
    HWND          hWndOwner;

    // Handles to access information
    LPTSTR        lpszImageFileName;    // Name of image to open/display
    HGLOBAL       hDIB;                 // Handle to the DIB spec
    HGLOBAL       hDIBTransformed;      // Handle to the DDB or DIBSection.
    HPALETTE      hPal;

    // Image attributes
    DWORD         dwDIBBits;            // Bits per pixel
    UINT          uiDIBWidth;           // Print width of the DIB
    UINT          uiDIBHeight;          // Print height of the DIB
    BMFORMAT      bmFormat;             // Bitmap format used by TranslateBitmapBits

    // Display options
    RECT          rcClip;               // Clipboard cut rectangle.
    DWORD         dwStretchBltMode;     // Mode to use for StretchBlt calls
                                        // when painting.
    BOOL          bStretch;             // True = stretch to window

    // Printing options
    DWORD         dwPrintOption;        // See defines below
    DWORD         dwXScale;             // X Scale Edit control value
    DWORD         dwYScale;             // Y Scale Edit control value
    PDEVMODE      pDevMode;

    // ICM Control structure
    HCOLORSPACE   hLCS;
    LPTSTR        lpszMonitorName;
    LPTSTR        lpszMonitorProfile;
    LPTSTR        lpszPrinterName;
    LPTSTR        lpszPrinterProfile;
    LPTSTR        lpszTargetProfile;
    DWORD         dwICMFlags;
    DWORD         dwRenderIntent;
    DWORD         dwProofingIntent;
} DIBINFO, FAR *LPDIBINFO;

// Function prototypes
HGLOBAL   CreateDIBInfo(void);
BOOL      GetDefaultICMInfo(void);
LPDIBINFO GetDIBInfoPtr(HWND hWnd);
HGLOBAL   GetDIBInfoHandle (HWND hWnd);

BOOL      fFreeDIBInfo(HGLOBAL hDIBInfo, BOOL bFreeDIBHandles);LPTSTR    GetDefaultICMProfile(HDC hDC);
LPDIBINFO fDuplicateDIBInfo(LPDIBINFO lpDISource, LPDIBINFO lpDITarget);
BOOL      fDuplicateICMInfo(LPDIBINFO lpDIDest, LPDIBINFO lpDISrc);
BOOL      InitDIBInfo(LPDIBINFO lpDIBINFO);
void      CopyDIBInfo(LPDIBINFO lpDITarget, LPDIBINFO lpDISource);
BOOL      fReadDIBInfo(LPTSTR lpszFileName, LPDIBINFO lpDIBInfo);
HANDLE    GetDIBHandleFromDIBInfo(HANDLE hDIBInfo);
void      DumpDIBINFO(LPTSTR lpszMsg, LPDIBINFO lpDIBInfo);

BOOL SetupDC(HDC hDC, LPDIBINFO lpDIBInfo, HPALETTE *phOldPalette, HDC *phDCPrinter);


DIBS.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIBS.C
//
//  PURPOSE:
//    DIB routines for the ICMVIEW application.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
//#include <CommCtrl.h>
#include <icm.h>

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings
#pragma warning(default:4514)   // Unreferenced inline function has been removed

// C RunTime Header Files
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <TCHAR.H>

// Local Header Files
#include "icmview.h"  // specific to this program
#include "child.h"
#include "dibinfo.h"
#include "dibs.h"      // specific to this file
#include "dialogs.h"
#include "debug.h"
#include "print.h"
#include "regutil.h"
#include "resource.h"

// local definitions
DWORD NumColorsInDIB(LPBITMAPINFOHEADER lpbi);
LONG      DIBHeight (LPBYTE lpDIB);
LONG      DIBWidth (LPBYTE lpDIB);
LPLOGCOLORSPACE GetColorSpaceFromBitmap(LPBITMAPINFOHEADER lpBitmapInfo, DWORD dwIntent, LPBOOL pbDeleteProfile);
HPROFILE  OpenColorProfileFromFile(LPTSTR lpszProfileName);
BOOL TranslateColorTable(HTRANSFORM hColorTransform, PCOLOR paInputColors, DWORD nColors, COLORTYPE ctInput, PCOLOR paOutputColors, COLORTYPE ctOutput, int biBitCount);

// default settings

// external functions

// external data

// public data

// private data

// public functions

// private functions

///////////////////////////////////////////////////////////////////////
//
// Function:   DIBPaint
//
// Purpose:    Painting routine for a DIB.  Calls StretchDIBits() or
//             SetDIBitsToDevice() to paint the DIB.  The DIB is
//             output to the specified DC, at the coordinates given
//             in lpDCRect.  The area of the DIB to be output is
//             given by lpDIBRect.  The specified palette is used.
//
// Parms:      hDC       == DC to do output to.
//             lpDCRect  == Rectangle on DC to do output to.
//             hDIB      == Handle to global memory with a DIB spec
//                          in it (either a BITMAPINFO or BITMAPCOREINFO
//                          followed by the DIB bits).
//             lpDIBRect == Rect of DIB to output into lpDCRect.
//             hPal      == Palette to be used.
//
///////////////////////////////////////////////////////////////////////

void DIBPaint (HDC hDC, LPRECT lpDCRect, HGLOBAL hDIB,LPRECT lpDIBRect,
               LPDIBINFO lpDIBInfo)
{
    int                 iScanLines;
    HDC                 hDCPrinter;
    BOOL                bRC;
    LPBYTE              lpbDIBBits;
    HPALETTE            hOldPal;
    LPBITMAPINFOHEADER  lpDIBHdr;
    LPBITMAPINFOHEADER  lpbi;

    // Initialize variables
    if (!hDIB)
    {
        return;
    }
    ASSERT(hDC != NULL);
    hDCPrinter = NULL;
    SetLastError(0);
    hOldPal = NULL;
    lpbi = NULL;

    // Lock down DIB, get a pointer to the beginning of the bit buffer.
    lpDIBHdr  = GlobalLock(hDIB);
    if (NULL == lpDIBHdr )
    {
        goto Release;
    }
    lpbDIBBits = FindDIBBits(lpDIBHdr);
    if (NULL == lpbDIBBits)
    {
        goto Release;
    }

    // If size > BITMAPINFOHEADER header then
    // need to convert to BITMAPINFOHEADER.
    lpbi = lpDIBHdr;
#ifdef OSR2
    if (sizeof(BITMAPINFOHEADER) < lpDIBHdr->biSize)
    {
        DWORD dwColorTableSize;
        DWORD dwHeaderDataSize;

        // Allocate Bitmapinfo memory.
        dwHeaderDataSize = sizeof(BITMAPINFOHEADER) + (lpDIBHdr->biCompression == BI_BITFIELDS ? 12 : 0);
        dwColorTableSize = NumColorsInDIB(lpDIBHdr) * sizeof(RGBQUAD);
        lpbi = (LPBITMAPINFOHEADER) GlobalAlloc(GPTR, dwHeaderDataSize + dwColorTableSize);
        if (NULL == lpbi)
        {
            goto Release;
        }

        // Convert header data into bitmapinfo header.
        memcpy(lpbi, lpDIBHdr, dwHeaderDataSize);
        lpbi->biSize = sizeof(BITMAPINFOHEADER);

        // Copy color table if any.
        if (0 != dwColorTableSize)
            memcpy((LPBYTE)lpbi + dwHeaderDataSize, (LPBYTE)lpDIBHdr + lpDIBHdr->biSize, dwColorTableSize);
    }
#endif

    SetupDC(hDC, lpDIBInfo, &hOldPal, &hDCPrinter);

    // Determine whether to call StretchDIBits() or SetDIBitsToDevice().
    if (!(lpDIBInfo->bStretch))
    {
        iScanLines = SetDIBitsToDevice (hDC,          // hDC
                                        lpDCRect->left,               // dest upper-left x
                                        lpDCRect->top,                // dest upper-left y
                                        RECTWIDTH (lpDCRect),         // src width
                                        RECTHEIGHT (lpDCRect),        // src height
                                        lpDIBRect->left,              // src lower-left x
                                        (int) DIBHeight((LPBYTE)lpDIBHdr) -
                                        lpDIBRect->top -
                                        RECTHEIGHT (lpDIBRect),       // src lower-left y
                                        0,                            // nStartScan
                                        (UINT) DIBHeight((LPBYTE)lpDIBHdr),   // nNumScans
                                        lpbDIBBits,                   // lpBits
                                        (LPBITMAPINFO) lpbi,      // lpBitsInfo
                                        DIB_RGB_COLORS);              // wUsage
    }
    else
    {
        // Use the specified stretch mode
        SetStretchBltMode (hDC, (int)lpDIBInfo->dwStretchBltMode);
        iScanLines = StretchDIBits (hDC,                          // hDC
                                    lpDCRect->left,               // dest upper-left x
                                    lpDCRect->top,                // dest upper-left y
                                    RECTWIDTH (lpDCRect),         // src width
                                    RECTHEIGHT (lpDCRect),        // src height
                                    lpDIBRect->left,              // src lower-left x
                                    lpDIBRect->top,               // src lower-left y
                                    RECTWIDTH (lpDIBRect),        // nStartScan
                                    RECTHEIGHT (lpDIBRect),       // nNumScans
                                    lpbDIBBits,                   // lpBits
                                    (LPBITMAPINFO)lpbi,                   // lpBitsInfo
                                    DIB_RGB_COLORS,               // wUsage
                                    SRCCOPY);                     // dwROP
    }
    if (DIBHeight((LPBYTE)lpDIBHdr) != iScanLines)
    {
        DebugMsg(__TEXT("DIBS.C : DIBPaint:  iScanLines expected %ld, returned %ld\r\n"), DIBHeight((LPBYTE)lpDIBHdr), iScanLines);
    }

    // Fix up the palette.
    if (NULL == SelectPalette (hDC, hOldPal, TRUE))
    {
        DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
    }

    Release:
    if (lpbi != lpDIBHdr)
        GlobalFree((HANDLE)lpbi);
    if (NULL != lpDIBHdr)
    {
        GlobalUnlock(hDIB);
    }
    if (hDCPrinter)
    {
        bRC = ColorMatchToTarget(hDC, hDCPrinter, CS_DISABLE);
        if (0 == bRC)
        {
            DebugMsg(__TEXT("DIBS.C : DIBPaint : ColorMatchToTarget failed to DISABLE transform.\r\n"));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
        }
        bRC = ColorMatchToTarget(hDC, hDCPrinter, CS_DELETE_TRANSFORM);
        if (0 == bRC)
        {
            DebugMsg(  __TEXT(  "DIBS.C : DIBPaint : ColorMatchToTarget failed to DELETE transform.\r\n"));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
        }
        bRC = DeleteDC(hDCPrinter);
        if (0 == bRC)
        {
            DebugMsg(__TEXT("DIBS.C : DIBPaint : Failed to delete printer DC\r\n"));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
        }
    }
}

///////////////////////////////////////////////////////////////////////
//
// Function:   CreateDIBPalette
//
// Purpose:    Given a handle to a DIB, constructs a logical palette,
//             and returns a handle to this palette.
//
//             Stolen almost verbatim from ShowDIB.
//
// Parms:      hDIB == HANDLE to global memory with a DIB header
//                     (either BITMAPINFOHEADER or BITMAPCOREHEADER)
//
///////////////////////////////////////////////////////////////////////
HPALETTE CreateDIBPalette(HANDLE hDIB)
{
    LPLOGPALETTE          lpPal;
    HGLOBAL               hLogPal;
    HPALETTE              hPal = NULL;
    UINT                  i;
    DWORD                 dwNumColors;
    LPBITMAPINFOHEADER    lpBmpInfoHdr;
    LPBITMAPINFO          lpbmi;
    LPBITMAPCOREINFO      lpbmc;
    BOOL                  bWinStyleDIB;

    if (!hDIB)
    {
        return NULL;
    }

    lpBmpInfoHdr = GlobalLock (hDIB);
    lpbmi        = (LPBITMAPINFO)lpBmpInfoHdr;
    lpbmc        = (LPBITMAPCOREINFO)lpBmpInfoHdr;
    dwNumColors  = NumColorsInDIB(lpBmpInfoHdr);
    bWinStyleDIB = IS_WIN30_DIB(lpBmpInfoHdr);

    if (0 != dwNumColors)
    {
        hLogPal = GlobalAlloc (GHND, sizeof (LOGPALETTE) + sizeof (PALETTEENTRY) * dwNumColors);
        if (!hLogPal)
        {
            GlobalUnlock(hDIB);
            return NULL;
        }

        lpPal = (LPLOGPALETTE) GlobalLock (hLogPal);
        lpPal->palVersion    = PALVERSION;
        lpPal->palNumEntries = (WORD)dwNumColors;

        for (i = 0;  i < dwNumColors;  i++)
        {
            if (bWinStyleDIB)
            {
                lpPal->palPalEntry[i].peRed   = lpbmi->bmiColors[i].rgbRed;
                lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;
                lpPal->palPalEntry[i].peBlue  = lpbmi->bmiColors[i].rgbBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
            else
            {
                lpPal->palPalEntry[i].peRed   = lpbmc->bmciColors[i].rgbtRed;
                lpPal->palPalEntry[i].peGreen = lpbmc->bmciColors[i].rgbtGreen;
                lpPal->palPalEntry[i].peBlue  = lpbmc->bmciColors[i].rgbtBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
        }

        hPal = CreatePalette (lpPal);
        GlobalUnlock(hLogPal);
        GlobalFree(hLogPal);
    }
    GlobalUnlock(hDIB);
    return(hPal);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDefaultICMProfile
//
//  Description:
//    Uses GetICMProfile to retrieve the filename of the default ICM
//    profile for the DC.
//
//  Parameters:
//    @@@
//
//  Returns:
//    LPTSTR
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPTSTR GetDefaultICMProfile(HDC hDC)
{
    // Local variables
    LPTSTR    lpszProfileName;
    BOOL      bProfile;
    DWORD     dwProfileLen;
    TCHAR     stProfileName[MAX_PATH+1];
    HGLOBAL   hFree;

    //  Initialize variables
    lpszProfileName = NULL;
    dwProfileLen = 0;
    stProfileName[0] = __TEXT('\0');

    // Query for size of profile name string
    bProfile = GetICMProfile(hDC, &dwProfileLen, NULL);
    if (bProfile)
    {
        DebugMsg(__TEXT("GetDefaultICMProfile:  GetICMProfile returned TRUE on query, dwProfileLen = %ld\r\n"), dwProfileLen);
        return(FALSE);
    }

    if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
    {
        DebugMsg(__TEXT("GetDefaultICMProfile:  GetICMProfile set unexpected LastError %ld on query, dwProfileLen = %ld\r\n"),
                 GetLastError(), dwProfileLen);
        DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        dwProfileLen = MAX_PATH;
    }
    else
    {
        if (0 == dwProfileLen)
        {
            DebugMsg(__TEXT("GetDefaultICMProfile:  GetICMProfile returned FALSE on query, DID NOT SET dwProfileLen\r\n"));
            return(FALSE);
        }
    }

    // Fill in lpszProfileName with actual profile filename
    lpszProfileName = GlobalAlloc(GPTR, (dwProfileLen+1) * sizeof(TCHAR));
    if (lpszProfileName != NULL)
    {
        bProfile = GetICMProfile(hDC, &dwProfileLen, lpszProfileName);
        if (!bProfile)
        {
            DebugMsg(__TEXT("GetDefaultICMProfile:  GetICMProfile FAILED\r\n"));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
            hFree = GlobalFree(lpszProfileName);
            return(NULL);
        }
        else  // Successfully id'd default profile
        {
            TCHAR   szName[MAX_PATH], szExt[MAX_PATH];

            DebugMsg(__TEXT("Full profile name:  <%s>\r\n"), lpszProfileName);
            _tsplitpath(lpszProfileName, NULL, NULL, szName, szExt);
            wsprintf(lpszProfileName, __TEXT("%s%s"), szName, szExt);
        }
    }
    else
    {
        DebugMsg(__TEXT("GetDefaultICMProfile:  Unable to allocate lpszProfileName.\r\n"));
    }
    return(lpszProfileName);
} // End of function GetDefaultICMProfile

//////////////////////////////////////////////////////////////////////////
//  Function:  TransformDIBOutsideDC
//
//  Description:
//    Transforms the provided hDIB using the provided profile names.
//
//  Parameters:
//    HANDLE      Handle to DIB to process
//    LPTSTR      Destination profile
//    LPTSTR      Target profile
//
//  Returns:
//    HANDLE to transformed bits; NULL upon failure.
//
//  Comments:
//    Uses ICM functions
//        CreateColorTransform
//        TranslateBitmapBits
//        TranslateColors
//
//////////////////////////////////////////////////////////////////////////
HANDLE TransformDIBOutsideDC(HANDLE hDIB, BMFORMAT bmInput, LPTSTR lpszDestProfile,
                             LPTSTR lpszTargetProfile, DWORD dwIntent, PBMCALLBACKFN pBMCallback,
                             ULONG ulCallbackData)
{
    // Local variables
    HANDLE          hDIBTransformed;    // Handle to transformed DIB
    HPROFILE        hDestProfile, hTargetProfile;
    HTRANSFORM      hTransform;
    LPLOGCOLORSPACE lpLogColorSpace;
    PVOID           pSrcBits, pDestBits;
    LPBITMAPINFOHEADER  lpbSrcDIBHdr, lpbDestDIBHdr;
    BOOL            bTranslated, bProfileClosed, bDeleted, bDeleteProfile;
    DWORD           dwCopySize;

    //  Initialize variables
    ASSERT(NULL != hDIB);
    ASSERT(NULL != lpszDestProfile);
    hDIBTransformed = NULL;
    SetLastError(0);
    hTransform = NULL;
    bTranslated = FALSE;

    hDestProfile   = OpenColorProfileFromFile(lpszDestProfile );
    hTargetProfile = OpenColorProfileFromFile(lpszTargetProfile);

    if (NULL == hDestProfile)
    {
        DebugMsg(__TEXT("TransformDIBOutsideDC : NULL dest file\r\n"));
        return(NULL);
    }

    // Get bits from original DIB
    lpbSrcDIBHdr = GlobalLock(hDIB);

    pSrcBits = (PVOID)FindDIBBits(lpbSrcDIBHdr);

    // Get LPLOGCOLORSPACE for transform
    lpLogColorSpace = GetColorSpaceFromBitmap(lpbSrcDIBHdr, dwIntent, &bDeleteProfile);

    if (NULL != lpLogColorSpace)
    {
        //Create the transform to use
        SetLastError(0);
        hTransform = CreateColorTransform(lpLogColorSpace, hDestProfile, hTargetProfile, ENABLE_GAMUT_CHECKING | NORMAL_MODE | 0x80000000);

        if (NULL != hTransform)
        {
            // Allocate for new DIB
            hDIBTransformed = GlobalAlloc(GHND, GlobalSize(hDIB));
            lpbDestDIBHdr = GlobalLock(hDIBTransformed);

            switch (BITCOUNT(lpbSrcDIBHdr))
            {
                DWORD   dwSrcOffBytes, dwDestOffBytes;

                case 1: // BM_1GRAY:
                case 4:
                case 8:
                    ASSERT((((LPBITMAPV5HEADER)lpbSrcDIBHdr)->bV5ClrUsed) <= (DWORD)( 1 << ((LPBITMAPV5HEADER)lpbSrcDIBHdr)->bV5BitCount));
                    // Copy entire DIB.  Color table will be replaced by TranslateColors
                    dwCopySize = GlobalSize(hDIB);
                    memset(lpbDestDIBHdr, 0x17, dwCopySize);
                    memcpy(lpbDestDIBHdr, lpbSrcDIBHdr, dwCopySize);

                    dwSrcOffBytes = *(LPDWORD)lpbSrcDIBHdr;
                    dwDestOffBytes   = *(LPDWORD)lpbDestDIBHdr;

                    pSrcBits  = (PBYTE)lpbSrcDIBHdr  + dwSrcOffBytes;
                    pDestBits = (PBYTE)lpbDestDIBHdr + dwDestOffBytes;

                    // Needed to use different translation if BITMAPCORE bitmap.
                    if (dwSrcOffBytes >= sizeof(BITMAPINFOHEADER))
                    {
                        bTranslated = TranslateColorTable(hTransform,
                                                          (PCOLOR)pSrcBits, // paInputColors,
                                                          ((LPBITMAPV5HEADER)lpbSrcDIBHdr)->bV5ClrUsed, // nColors,
                                                          COLOR_RGB, // ctInput,
                                                          (PCOLOR)pDestBits, // paOutputColors,
                                                          COLOR_RGB,  // ctOutput)
                                                          lpbSrcDIBHdr->biBitCount);
                    }
                    else
                    {
                        bTranslated = TranslateBitmapBits(hTransform,
                                                          pSrcBits,
                                                          BM_RGBTRIPLETS,
                                                          NumColorsInDIB(lpbSrcDIBHdr),
                                                          1,
                                                          0,
                                                          pDestBits,
                                                          BM_RGBTRIPLETS,
                                                          0,
                                                          NULL,
                                                          0);
                    }

                    if (0 == bTranslated)
                    {
                        DebugMsg(__TEXT("TransformDIBOutsideDC : TranslateColors failed, :"));
                        DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
                    }
                    break;

                case 16: // BM_RGBTRIPLETS:
                case 24: // BM_xRGBQUADS:
                case 32: // BM_x555RGB:

                    // Copy header from original DIB to new DIB
                    memcpy(lpbDestDIBHdr, lpbSrcDIBHdr, sizeof(BITMAPV5HEADER));
                    pDestBits = (PVOID)FindDIBBits(lpbDestDIBHdr);

                    bTranslated = TranslateBitmapBits(hTransform,
                                                      pSrcBits,
                                                      bmInput,
                                                      BITMAPWIDTH(lpbSrcDIBHdr),
                                                      abs(BITMAPHEIGHT(lpbSrcDIBHdr)),
                                                      0,
                                                      pDestBits,
                                                      bmInput,
                                                      0,
                                                      pBMCallback,
                                                      ulCallbackData);

                    if (0 == bTranslated)
                    {
                        DebugMsg(__TEXT("TransformDIBOutsideDC : TranslateBitmapBits failed:\r\n"));
                        DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
                    }
                    break;

                default:  // 8bpp
                    DebugMsg(__TEXT("TransformDIBOutsideDC : Unrecognized format\r\n"));
                    bTranslated = FALSE;
                    break;
            }
            bDeleted = DeleteColorTransform(hTransform);
            if (0 == bDeleted)
            {
                DebugMsg(__TEXT("TransformDIBOutsideDC : DeleteColorTransform failed, : "));
                DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
            }
        }
        else
        {
            ErrMsg(NULL, __TEXT("TransformDIBOutsideDC : CreateColorTransform failed"));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        }

        if (bDeleteProfile)
        {
            DeleteFile(lpLogColorSpace->lcsFilename);
        }

        GlobalFree((HANDLE)lpLogColorSpace);
    }
    else // Failed to get LOGCOLORSPACE
    {
        ErrMsg(NULL, __TEXT("TransformDIBOutsideDC : Failed to get LOGCOLORSPACE"));
    }

    if (NULL != hDestProfile)
    {
        bProfileClosed = CloseColorProfile(hDestProfile);
        if (0 == bProfileClosed)
        {
            DebugMsg(__TEXT("TransformDIBOutsideDC : Failed to close hDestProfile, "));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        }
    }

    if (NULL != hTargetProfile)
    {
        bProfileClosed = CloseColorProfile(hTargetProfile);
        if (0 == bProfileClosed)
        {
            DebugMsg(__TEXT("TransformDIBOutsideDC : Failed to close hTargetProfile, "));
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
        }
    }
    if (NULL != hDIBTransformed)
    {
        GlobalUnlock(hDIBTransformed);
    }
    if ((NULL == hTransform) || (0 == bTranslated))
    {
        if (NULL != hDIBTransformed)
        {
            GlobalFree(hDIBTransformed);
            hDIBTransformed = NULL;
        }
        lpbDestDIBHdr = NULL;
        pDestBits = NULL;
    }
    return(hDIBTransformed);
}   // End of function TransformDIBOutsideDC

//////////////////////////////////////////////////////////////////////////
//  Function:  OpenColorProfileFromFile
//
//  Description:
//    Creates a color profile based upon the parameters passed.
//
//  Parameters:
//    LPTSTR  Profile name
//
//  Returns:
//    HPROFILE Handle to PROFILE structure, NULL if failure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
HPROFILE OpenColorProfileFromFile(LPTSTR lpszProfileName)
{
    // Local variables
    PPROFILE  pProfile;
    HPROFILE  hProfile;
    DWORD     cbDataSize;
    DWORD     dwProfileSize;
    TCHAR     stFullProfile[MAX_PATH];
    BOOL      bValid;

    //  Initialize variables
    if (NULL == lpszProfileName)
    {
        return(NULL);
    }

    // Add COLOR dir path if profile name does not contain a path.
    if ( (NULL == _tcschr(lpszProfileName, __TEXT(':')))
         &&
         (NULL == _tcschr(lpszProfileName, __TEXT('\\')))
       )
    {
        wsprintf(stFullProfile, __TEXT("%s\\%s"), gstProfilesDir, lpszProfileName);
    }
    else
    {
        lstrcpy(stFullProfile, lpszProfileName);
    }
    dwProfileSize = sizeof(PROFILE);
    cbDataSize = (lstrlen(stFullProfile) * sizeof(TCHAR)) + sizeof(TCHAR); // String plus NULL
    pProfile = GlobalAlloc(GPTR, (dwProfileSize + cbDataSize));
#ifdef _DEBUG
    memset(pProfile, UNINIT_BYTE, (cbDataSize+dwProfileSize));
#endif

    pProfile->dwType = PROFILE_FILENAME;
    pProfile->cbDataSize = cbDataSize;
    pProfile->pProfileData = (PVOID)((LPBYTE)pProfile + dwProfileSize);
    _tcscpy(pProfile->pProfileData, stFullProfile);
    hProfile = OpenColorProfile(pProfile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
    if (NULL  == hProfile)
    {
        ErrMsg(NULL,__TEXT("Unable to open color profile <%s>"), stFullProfile);
        DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
    }
    else
    {
        // Validate the profile
        bValid = IsColorProfileValid(hProfile, &bValid);
        if (0 == bValid)
        {
            ErrMsg(NULL,__TEXT("Color profile %s is not valid"), stFullProfile);
            DISPLAY_LASTERROR(LASTERROR_NOALLOC, GetLastError());
            CloseColorProfile(hProfile);
            hProfile = NULL;
        }
    }
    // Cleanup
    GlobalFree(GlobalHandle(pProfile));
    return(hProfile);
}   // End of function OpenColorProfileFromDisk

///////////////////////////////////////////////////////////////////////
//
// Function:   GetColorSpaceFromBitmap
//
// Purpose:    Creates a LOGCOLORSPACE based on information in bitmap.
//
//
// Parms:      lpBitmapHeader   Pointer to bitmap header and info.
//             dwIntent         Color Space Intent.
//             pbDeleteProfile  Flag, on return, will indicate if the profile needs
//                              to be deleted before LOGCOLORSPACE is freed.
//
//
///////////////////////////////////////////////////////////////////////

LPLOGCOLORSPACE GetColorSpaceFromBitmap(LPBITMAPINFOHEADER lpBitmapInfo, DWORD dwIntent,
                                        LPBOOL pbDeleteProfile)
{
    LPLOGCOLORSPACE lpColorSpace = NULL;
    PBITMAPV5HEADER lpBitmapV5 = (PBITMAPV5HEADER) lpBitmapInfo;

    // Validate parameters.
    ASSERT(NULL != lpBitmapInfo);
    if ( (NULL == lpBitmapInfo)
         ||
         (NULL == pbDeleteProfile)
         ||
         ( (sizeof(BITMAPCOREHEADER) != lpBitmapInfo->biSize)
           &&
           (sizeof(BITMAPINFOHEADER) != lpBitmapInfo->biSize)
           &&
           (sizeof(BITMAPV4HEADER) != lpBitmapInfo->biSize)
           &&
           (sizeof(BITMAPV5HEADER) != lpBitmapInfo->biSize)
         )
       )
    {
        return NULL;
    }

    // Initalize delete flag.
    *pbDeleteProfile = FALSE;

    // Allocate LOGCOLORSPACE.
    lpColorSpace = (LPLOGCOLORSPACE) GlobalAlloc(GPTR, sizeof(LOGCOLORSPACE));
    if (NULL == lpColorSpace)
    {
        return NULL;
    }

    // Initialize color space.
    lpColorSpace->lcsSignature = 'PSOC';  // The signature should always be 'PSOC'.
    lpColorSpace->lcsVersion = 0x400;
    lpColorSpace->lcsSize = sizeof(LOGCOLORSPACE);

    // If BITMAPCOREHEADER or BITMAPINFOHEADER, bitmap has no information
    // about color space; use sRGB.
    if (sizeof(BITMAPINFOHEADER) >= lpBitmapInfo->biSize)
    {
        // Set color space to default values.

        lpColorSpace->lcsCSType = LCS_sRGB;

        // Set endpoints to sRGB values.

        lpColorSpace->lcsEndpoints.ciexyzRed.ciexyzX = __FXPT2DOT30(.64);
        lpColorSpace->lcsEndpoints.ciexyzRed.ciexyzY = __FXPT2DOT30(.33);
        lpColorSpace->lcsEndpoints.ciexyzRed.ciexyzZ = __FXPT2DOT30(.03);

        lpColorSpace->lcsEndpoints.ciexyzGreen.ciexyzX = __FXPT2DOT30(.3);
        lpColorSpace->lcsEndpoints.ciexyzGreen.ciexyzY = __FXPT2DOT30(.6);
        lpColorSpace->lcsEndpoints.ciexyzGreen.ciexyzZ = __FXPT2DOT30(.1);

        lpColorSpace->lcsEndpoints.ciexyzBlue.ciexyzX   =   __FXPT2DOT30(  .15);
        lpColorSpace->lcsEndpoints.ciexyzBlue.ciexyzY = __FXPT2DOT30(.06);
        lpColorSpace->lcsEndpoints.ciexyzBlue.ciexyzZ = __FXPT2DOT30(.79);

        // Just so that if monitor has 2.2 gamma, the over all gamma is 1.0.
        lpColorSpace->lcsGammaRed = __FXPT16DOT16(0.45);
        lpColorSpace->lcsGammaGreen = __FXPT16DOT16(0.45);
        lpColorSpace->lcsGammaBlue = __FXPT16DOT16(0.45);
    }
    else
    {
        // Copy information from portion that is similar between BITMAPV4HEADERs and
        // BITMAPV5HEADERs.
        memcpy(&lpColorSpace->lcsEndpoints, &lpBitmapV5->bV5Endpoints, sizeof(CIEXYZTRIPLE));
        lpColorSpace->lcsGammaRed = lpBitmapV5->bV5GammaRed;
        lpColorSpace->lcsGammaGreen = lpBitmapV5->bV5GammaGreen;
        lpColorSpace->lcsGammaBlue = lpBitmapV5->bV5GammaBlue;

        // BITMAPV4HEADERs do not have complete color space information,
        // we need to assume some things.
        if (sizeof(BITMAPV4HEADER) == lpBitmapInfo->biSize)
        {
            // Fill in default values for fields that do not
            // have equivalents in BITMAPV4HEADER.
            lpColorSpace->lcsCSType = lpBitmapV5->bV5CSType;
            lpColorSpace->lcsIntent = LCS_GM_IMAGES;
        }
        else

{
            // BITMAPV5HEADERs have complete color space information.
            // No assumptions to make.
            lpColorSpace->lcsIntent = lpBitmapV5->bV5Intent;

            // Look to see if no, linked, or embedded profile.
            switch (lpBitmapV5->bV5CSType)
            {
                case 'MBED':
                    // Need to create profile file and reference
                    // profile in the color space.
                    lpColorSpace->lcsCSType = LCS_CALIBRATED_RGB;

                    // Make sure that profile data offset is valid and not zero length.
                    if ( (lpBitmapV5->bV5Size > lpBitmapV5->bV5ProfileData)
                         ||
                         (0L == lpBitmapV5->bV5ProfileSize)
                       )
                    {
                        GlobalFree((HANDLE)lpColorSpace);
                        return NULL;
                    }

                    // Create unique temp name.
                    {
                        DWORD   dwWritten;
                        TCHAR   szTempPath[MAX_PATH];
                        HANDLE  hFile;

                        GetTempPath(MAX_PATH, szTempPath);
                        if (!GetTempFileName(szTempPath, __TEXT("ICM"), 0, lpColorSpace->lcsFilename))
                        {
                            GlobalFree((HANDLE)lpColorSpace);
                            return NULL;
                        }

                        // Create temp profile that contains the embedded profile.
                        hFile = CreateFile(lpColorSpace->lcsFilename, GENERIC_READ | GENERIC_WRITE,
                                           0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
                        if (INVALID_HANDLE_VALUE == hFile)
                        {
                            GlobalFree((HANDLE)lpColorSpace);
                            return NULL;
                        }

                        // Write embedded profile to disk.
                        WriteFile(hFile, GETPROFILEDATA(lpBitmapV5), lpBitmapV5->bV5ProfileSize,
                                  &dwWritten, NULL);

                        // No longer need file open.
                        CloseHandle(hFile);

                        // Need to indicate to caller that this file needs to be deleted
                        // before LOGCOLORSPACE is freed.
                        *pbDeleteProfile = TRUE;
                    }
                    break;

                case 'LINK':
                    // Need to reference profile.
                    lpColorSpace->lcsCSType = LCS_CALIBRATED_RGB;
                    lstrcpyn(lpColorSpace->lcsFilename, (LPTSTR) GETPROFILEDATA(lpBitmapV5), MAX_PATH);
                    break;

                default:
                    // Just use color space type in the bitmap.
                    lpColorSpace->lcsCSType = lpBitmapV5->bV5CSType;
                    break;
            }
        }
    }
    // Set intent to default or to users selection.
    if (0xffffffffL == dwIntent)
    {
        lpColorSpace->lcsIntent = LCS_GM_IMAGES;
    }
    else
    {
        lpColorSpace->lcsIntent = dwIntent;
    }

    return(lpColorSpace);
}

//
// Functions for extracting information from DIBs
//

//////////////////////////////////////////////////////////////////////////
//  Function:  NumColorsInDIB
//
//  Description:
//    Determines the number of colors in the DIB by looking at the
//    BitCount field in the info block.
//
//  Parameters:
//    LPBINTMAPV5HEADER Pointer to BITMAPINFO structure
//
//  Returns:
//    DWORD   Number of colors in the DIB
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
DWORD NumColorsInDIB(LPBITMAPINFOHEADER lpbi)
{
    WORD    wBitCount;
    DWORD   dwColors = 0;

    // If this is a Windows style DIB, the number of colors in the
    //  color table can be less than the number of bits per pixel
    //  allows for (i.e. lpbi->biClrUsed can be set to some value).
    //  If this is the case, return the appropriate value.
    if ( (sizeof(BITMAPINFOHEADER) <= lpbi->biSize)
         &&
         (0 != lpbi->biClrUsed)
       )
    {
        dwColors = lpbi->biClrUsed;
    }
    else
    {
        // Calculate the number of colors in the color table based on
        //  the number of bits per pixel for the DIB.
        wBitCount = BITCOUNT(lpbi);
        if (MAX_BPP_COLOR_TABLE >= wBitCount)
        {
            dwColors = 1 << wBitCount;  // Colors = 2^BitCount
        }
    }

    return dwColors;
}   // End of function NumColorsInDIB

//////////////////////////////////////////////////////////////////////////
//  Function:  PaletteSize
//
//  Description:
//    Calculates the palette size in bytes.
//
//  Parameters:
//    @@@
//
//  Returns:
//    DWORD   Palette size in bytes.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
DWORD PaletteSize(LPBITMAPINFOHEADER lpbi)
{
    DWORD   dwSize = 0L;

    if (sizeof(BITMAPINFOHEADER) <= lpbi->biSize)
    {
        dwSize = NumColorsInDIB(lpbi) * sizeof(RGBQUAD);

        if ( (lpbi->biCompression == BI_BITFIELDS)
             &&
             (sizeof(BITMAPV4HEADER) > lpbi->biSize)
           )
        {
            dwSize = 3 * sizeof(DWORD);
        }
    }
    else
    {
        dwSize = NumColorsInDIB(lpbi) * sizeof (RGBTRIPLE);
    }

    return dwSize;
}   // End of function PaletteSize

///////////////////////////////////////////////////////////////////////
//
// Function:   FindDIBBits
//
// Purpose:    Given a pointer to a DIB, returns a pointer to the
//             DIB's bitmap bits.
//
// Parms:      lpbi == pointer to DIB header (either BITMAPINFOHEADER
//                       or BITMAPCOREHEADER)
//
///////////////////////////////////////////////////////////////////////

LPBYTE FindDIBBits(LPBITMAPINFOHEADER lpbi)
{
    ASSERT(NULL != lpbi);
    return ((LPBYTE)lpbi + *(LPDWORD)lpbi + PaletteSize(lpbi) + PROFILESIZE(lpbi));
}

///////////////////////////////////////////////////////////////////////
//
// Function:   FindDIBBits
//
// Purpose:    Given a pointer to a DIB, returns a pointer to the
//             DIB's bitmap bits.
//
// Parms:      lpbi == pointer to DIB header (either BITMAPINFOHEADER
//                       or BITMAPCOREHEADER)
//
///////////////////////////////////////////////////////////////////////

LPBYTE FindColorTable(LPBITMAPINFOHEADER lpbi)
{
    ASSERT(NULL != lpbi);
    return ((LPBYTE)lpbi + *(LPDWORD)lpbi + PaletteSize(lpbi) + PROFILESIZE(lpbi));
}

///////////////////////////////////////////////////////////////////////
//
// Function:   DIBHeight
//
// Purpose:    Given a pointer to a DIB, returns the ABSOLUTE VALUE of
//             its height.  Note that it returns a DWORD (since a Win30
//             DIB can have a DWORD in its height field), but under
//             Win30, the high order word isn't used!
//
// Parms:      lpDIB == pointer to DIB header (either BITMAPINFOHEADER
//                       or BITMAPCOREHEADER)
//
///////////////////////////////////////////////////////////////////////

LONG DIBHeight (LPBYTE lpDIB)
{
    LPBITMAPINFOHEADER lpbmi;
    LPBITMAPCOREHEADER lpbmc;

    lpbmi = (LPBITMAPINFOHEADER) lpDIB;
    lpbmc = (LPBITMAPCOREHEADER) lpDIB;
    if (lpbmi->biSize >= sizeof (BITMAPINFOHEADER))
    {
        return labs(lpbmi->biHeight); // biHeight can now be negative! 8/9/93
    }
    else
    {
        return (LONG) lpbmc->bcHeight;
    }
}

///////////////////////////////////////////////////////////////////////
//
// Function:   DIBWidth
//
// Purpose:    Given a pointer to a DIB, returns its width.  Note
//             that it returns a DWORD (since a Win30 DIB can have
//             a DWORD in its width field), but under Win30, the
//             high order word isn't used!
//
// Parms:      lpDIB == pointer to DIB header (either BITMAPINFOHEADER
//                       or BITMAPCOREHEADER)
//
///////////////////////////////////////////////////////////////////////

LONG DIBWidth (LPBYTE lpDIB)
{
    LPBITMAPINFOHEADER lpbmi;
    LPBITMAPCOREHEADER lpbmc;

    lpbmi = (LPBITMAPINFOHEADER) lpDIB;
    lpbmc = (LPBITMAPCOREHEADER) lpDIB;

    if (lpbmi->biSize >= sizeof (BITMAPINFOHEADER))
    {
        return lpbmi->biWidth;
    }
    else
    {
        return (LONG) lpbmc->bcWidth;
    }
}

//
//  Functions for reading DIBs
//

//////////////////////////////////////////////////////////////////////////
//  Function:  ReadDIBFile
//
//  Description:
//     Open a DIB file and create
//          -a memory DIB
//          -a memory handle containing BITMAPINFO, palette, and the bits
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    HGLOBAL Handle to DIB memory; NULL upon failure.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
HGLOBAL ReadDIBFile(LPTSTR lpszFileName)
{
    // Local variables
    HGLOBAL             hDIBInfo;
    LPDIBINFO           lpDIBInfo;
    BOOL                bGotDIBInfo;

    //  Initialize variables
    hDIBInfo = CreateDIBInfo();
    if (hDIBInfo == NULL)
    {
        return(NULL);
    }
    lpDIBInfo = GlobalLock(hDIBInfo);
    if (lpDIBInfo == (LPDIBINFO)NULL)
    {
        GlobalFree(hDIBInfo);
        return(NULL);
    }
    bGotDIBInfo =  fReadDIBInfo(lpszFileName, lpDIBInfo);
    GlobalUnlock(hDIBInfo);
    if (0 == bGotDIBInfo)  // failed to open file
    {
        fFreeDIBInfo(hDIBInfo, TRUE);
        hDIBInfo = NULL;
    }
    return(hDIBInfo);
}   // End of function ReadDIBFile

//////////////////////////////////////////////////////////////////////////
//  Function:   ReadDIBFromFile
//
//  Description:
//      Reads in the bitmap information.
//
//  Parameters:
//      hFile   Handle to bitmap file.
//
//  Returns:  Handle to DIB Bitmap.  NULL on error.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
HANDLE ReadDIBFromFile(HANDLE hFile)
{
    UINT                    nNumColors;
    DWORD                   offBits;
    DWORD                   dwRead;
    DWORD                   dwImageBytes;
    DWORD                   dwSizeImage;
    HANDLE                  hDIB;
    BITMAPFILEHEADER        BmpFileHeader;
    LPBITMAPINFOHEADER      lpbi;

    // Make sure non-null file handle.

    if (NULL == hFile)
    {
        return NULL;
    }

    // Read in Bitmap file header.
    if (0L != SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
    {
        return NULL;
    }
    ReadFile(hFile, &BmpFileHeader, sizeof(BITMAPFILEHEADER), &dwRead, NULL);
    if (sizeof(BITMAPFILEHEADER) != dwRead)
    {
        return NULL;
    }

    // Make sure that the file is a bitmap file.
    if (BFT_BITMAP != BmpFileHeader.bfType)
    {
        return NULL;
    }

    // Detemine size of bitmap header.
    ReadFile(hFile, &dwImageBytes, sizeof(DWORD), &dwRead, NULL);
    if (sizeof(DWORD) != dwRead)
    {
        return NULL;
    }

    // Allocate memory for header & color table.
    // We'll enlarge this memory as needed.
    hDIB = GlobalAlloc(GHND, dwImageBytes + (256L * sizeof(RGBQUAD)));
    if (!hDIB)
    {
        return NULL;
    }
    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    if (!lpbi)
    {
        GlobalFree(hDIB);
        return NULL;
    }

    // Back up to begining of bitmap header and read bitmap header.
    SetFilePointer(hFile, sizeof(BITMAPFILEHEADER), NULL, FILE_BEGIN);
    ReadFile(hFile, lpbi, dwImageBytes, &dwRead, NULL);
    if (dwRead != dwImageBytes)
    {
        GlobalUnlock(hDIB);
        GlobalFree(hDIB);
        return NULL;
    }

    // Dump debug info about type of bitmap opened.
#ifdef MAXDEBUG
    DumpBmpHeader((LPVOID)lpbi);
#endif

    // Check to see that it's a Windows DIB or an OS/2 DIB.
    // It assumed that anything that is not a OS/2 DIB is a Windows DIB.
    // This at least allows the opening of newer bitmap types, although GDI
    // may not handle them so we may not be able to display them.
    // More critical checking could be done by checking against
    // BITMAPV4HEADER and BITMAPV5HEADER sizes.

    if (lpbi->biSize >= sizeof(BITMAPINFOHEADER))
    {
        DWORD   dwProfileData = 0;
        DWORD   dwColorTableSize;
        HANDLE  hTemp;

        // Now determine the size of the color table and read it.  Since the
        // bitmap bits are offset in the file by bfOffBits, we need to do some
        // special processing here to make sure the bits directly follow
        // the color table (because that's the format we are susposed to pass
        // back)

        // no color table for 24-bit, default size otherwise
        nNumColors = (UINT)lpbi->biClrUsed;
        if (0 == nNumColors)
        {
            if (lpbi->biBitCount <= MAX_BPP_COLOR_TABLE)
            {
                nNumColors = 1 << lpbi->biBitCount;             // standard size table
            }
        }

        // Fill in some default values if they are zero
        if (0 == lpbi->biClrUsed)
        {
            lpbi->biClrUsed = nNumColors;
        }

        if (0 == lpbi->biSizeImage)
        {
            // Calculate size using DWORD alignment
            dwSizeImage = (((lpbi->biWidth * lpbi->biBitCount + 31) & ~31)
                           >> 3) * abs(lpbi->biHeight);
        }
        else
        {
            dwSizeImage = lpbi->biSizeImage;
        }

        // get a proper-sized buffer for header, color table and bits
        // Figure out the size of the bitmap AND it's color table.
        dwColorTableSize = PaletteSize(lpbi);

        // Calculate profile data size;
        // zero if not BITMAPV5HEADER, else in use bV5ProfileSize.
        dwProfileData =  PROFILESIZE(lpbi);

        // Resize DIB buffer.
        dwImageBytes = lpbi->biSize + dwSizeImage + dwColorTableSize + dwProfileData;
        GlobalUnlock(hDIB);
        hTemp = GlobalReAlloc(hDIB, dwImageBytes, GHND);
        DebugMsg(__TEXT("ReadDIBFromFile:  Allocating %lu bytes for header, color table, and image\r\n"), dwImageBytes);
        if (NULL == hTemp) // can't resize buffer for loading
        {
            GlobalFree(hDIB);
            return NULL;
        }

        hDIB = hTemp;
        lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

        // read the color table, if any, into buffer starting past Bitmap header.
        if (0 != dwColorTableSize)
        {
            ReadFile(hFile, (LPBYTE)lpbi + lpbi->biSize, dwColorTableSize, &dwRead, NULL);
            if (dwColorTableSize != dwRead)
            {
                GlobalUnlock(hDIB);
                GlobalFree(hDIB);
                return NULL;
            }
        }

        // Load profile data, if any.
        if (0 != dwProfileData)
        {
            // Move to profile data.
            SetFilePointer(hFile, sizeof(BITMAPFILEHEADER)
                           + ((LPBITMAPV5HEADER)lpbi)->bV5ProfileData, NULL, FILE_BEGIN);

            // Read the profile data in after the header and color table.
            ReadFile(hFile, (LPBYTE)lpbi + lpbi->biSize + dwColorTableSize, dwProfileData, &dwRead, NULL);
            if (dwProfileData != dwRead)
            {
                GlobalUnlock(hDIB);
                GlobalFree(hDIB);
                return NULL;
            }

            // Need to change the offset in the header.
            ((LPBITMAPV5HEADER)lpbi)->bV5ProfileData = lpbi->biSize + dwColorTableSize;
        }

        // offset to the bits from start of DIB header
        offBits = lpbi->biSize + dwColorTableSize + dwProfileData;
        DumpBmpHeader((LPVOID)lpbi);
    }
    else
    {
        // It's an OS/2 DIB, the color table is an array of RGBTRIPLEs.
        HANDLE                          hTemp;
        LPBITMAPCOREHEADER      lpbc = (LPBITMAPCOREHEADER) lpbi;

        // Gotta back up to beginning of color table.
        SetFilePointer(hFile, sizeof (BITMAPFILEHEADER) + sizeof (BITMAPCOREHEADER), NULL, FILE_BEGIN);

        // Now determine the size of the color table and read it.  Since the
        // bitmap bits are offset in the file by bfOffBits, we need to do some
        // special processing here to make sure the bits directly follow
        // the color table (because that's the format we are susposed to pass
        // back)

        if (lpbc->bcBitCount <= MAX_BPP_COLOR_TABLE)
        {
            nNumColors = 1 << lpbc->bcBitCount;    // standard size table
        }
        else
        {
            nNumColors = 0;
        }

        // Determine the size of the image
        dwSizeImage = (((lpbc->bcWidth * lpbc->bcBitCount + 31) & ~31) >> 3)
                      * lpbc->bcHeight;

        // get a proper-sized buffer for header, color table and bits
        GlobalUnlock(hDIB);
        hTemp = GlobalReAlloc(hDIB, lpbc->bcSize + (nNumColors * sizeof(RGBTRIPLE))
                              + dwSizeImage, GHND);
        if (!hTemp) // can't resize buffer for loading
        {
            GlobalFree(hDIB);
            return NULL;
        }

        hDIB = hTemp;
        lpbc = (LPBITMAPCOREHEADER) GlobalLock(hDIB);
        lpbi = (LPBITMAPINFOHEADER) lpbc;

        // read the color table
        ReadFile(hFile, (LPBYTE)lpbc + lpbc->bcSize, nNumColors * sizeof(RGBTRIPLE), &dwRead, NULL);
        if (nNumColors * sizeof(RGBTRIPLE) != dwRead)
        {
            GlobalUnlock(hDIB);
            GlobalFree(hDIB);
            return NULL;
        }

        // offset to the bits from start of DIB header
        offBits = lpbc->bcSize + nNumColors * sizeof(RGBTRIPLE);
    }

    // If the bfOffBits field is non-zero, then the bits might *not* be
    // directly following the color table in the file.  Use the value in
    // bfOffBits to seek the bits.

    if (BmpFileHeader.bfOffBits != 0L)
    {
        SetFilePointer(hFile, BmpFileHeader.bfOffBits, NULL, FILE_BEGIN);
    }

    // Read the actual bits
    ReadFile(hFile, (LPBYTE)lpbi + offBits, dwSizeImage, &dwRead, NULL);
    if (dwRead != dwSizeImage)
    {
        GlobalUnlock(hDIB);
        GlobalFree(hDIB);
        return NULL;
    }
    GlobalUnlock(hDIB);
    return hDIB;
}  // End of function ReadDIBFromFile

//////////////////////////////////////////////////////////////////////////
//  Function:  TranslateColorTable
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL TranslateColorTable(HTRANSFORM hColorTransform,
                         PCOLOR    paInputColors,
                         DWORD   nColors,
                         COLORTYPE     ctInput,
                         PCOLOR    paOutputColors,
                         COLORTYPE   ctOutput,
                         int         biBitCount
                        )
{
    //  Initialize variables
    if (ctInput == COLOR_RGB)
    {
        return(TranslateBitmapBits(hColorTransform,
                                   (PVOID)paInputColors,
                                   BM_xRGBQUADS,
                                   1<<biBitCount,
                                   1,
                                   0,
                                   (PVOID)paOutputColors,
                                   BM_xRGBQUADS,
                                   0,
                                   NULL,
                                   0));
    }
    else
    {
        return(TranslateColors(hColorTransform, paInputColors, nColors, ctInput, paOutputColors, ctOutput));
    }
}   // End of function TranslateColorTable

//////////////////////////////////////////////////////////////////////////
//  Function:  SaveDIBToFile
//
//  Description:
//
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL SaveDIBToFile(HWND hWnd, LPCTSTR lpszFileName, LPDIBINFO lpDIBInfo, DWORD dwType)
{
    BOOL                bResult = FALSE;
    HANDLE              hFile;
    HANDLE              hDIBTransformed = NULL;
    PBITMAPINFOHEADER   pBitmapInfo;

    // Validate Parameters.
    if ( (NULL == lpszFileName)
         ||
         (NULL == lpDIBInfo)
         ||
         (NULL == lpDIBInfo->hDIB)
         ||
         ( (LCS_sRGB != dwType)
           &&
           (LCS_CALIBRATED_RGB != dwType)
           &&
           (PROFILE_LINKED != dwType)
           &&
           (PROFILE_EMBEDDED != dwType)
         )
       )
    {
        return FALSE;
    }
    pBitmapInfo = (PBITMAPINFOHEADER) GlobalLock(lpDIBInfo->hDIB);

    // Open file handle.
    hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE != hFile)
    {
        DWORD               dwBytes;
        DWORD               dwProfileSize;
        PBYTE               pProfileData;
        LPSTR               lpszProfileName;
        BITMAPV5HEADER      BitmapHeader;
        BITMAPFILEHEADER    FileHeader;

        // Initialize variables.
        dwProfileSize = PROFILESIZE(pBitmapInfo);
        pProfileData = GETPROFILEDATA(pBitmapInfo);
        lpszProfileName = NULL;
        memset(&BitmapHeader, 0, sizeof(BITMAPV5HEADER));
        memset(&FileHeader, 0, sizeof(BITMAPFILEHEADER));

        // Convert to proper type.
        if (dwType != BITMAPCSTYPE(pBitmapInfo))
        {
            if (PROFILE_LINKED == dwType)
            {
                ASSERT(PROFILE_EMBEDDED == BITMAPCSTYPE(pBitmapInfo));

                // Going from Embedded profile to linked.
                // Save embedded profile data to file.

                // Create file name from manufacture and model name of profile header.
                lpszProfileName = (LPSTR) GlobalAlloc(GPTR, 1024);

                // Get save name and save profile data.
                if (GetProfileSaveName(hWnd, &lpszProfileName, 1024))
                {
                    HANDLE  hProfile;

                    hProfile = CreateFileA(lpszProfileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                                           FILE_ATTRIBUTE_NORMAL, NULL);
                    if (INVALID_HANDLE_VALUE != hProfile)
                    {
                        WriteFile(hProfile, pProfileData, dwProfileSize, &dwBytes, NULL);
                        CloseHandle(hProfile);
                    }

                    // Change profile variables to point to linked name.
                    pProfileData = (PBYTE) lpszProfileName;
                    dwProfileSize = strlen(lpszProfileName);
                }
            }
            else if (PROFILE_EMBEDDED == dwType)
            {
                DWORD       dwSize = 0;
                PROFILE     Profile;
                HPROFILE    hProfile;

                ASSERT(PROFILE_EMBEDDED == BITMAPCSTYPE(pBitmapInfo));

                // Going from linked profile to Embedded.
                // Embed Linked profile data.

                // Open profile.
                Profile.dwType = PROFILE_FILENAME;
                Profile.pProfileData = pProfileData;
                Profile.cbDataSize = dwProfileSize;
                hProfile = OpenColorProfile(&Profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);

                // Get profile data form handle.
                if (NULL != hProfile)
                {
                    GetColorProfileFromHandle(hProfile, NULL, &dwSize);
                    lpszProfileName = (LPSTR) GlobalAlloc(GPTR, dwSize);
                    GetColorProfileFromHandle(hProfile, (PBYTE) lpszProfileName, &dwSize);

                    CloseColorProfile(hProfile);
                }

                // Change profile variables to point at data to embed.
                pProfileData = (PBYTE) lpszProfileName;
                dwProfileSize = dwSize;
            }
            else if (LCS_sRGB == dwType)
            {
                DWORD       dwSize;
                LPTSTR      pszSRGB;

                // Need to translate from current type to sRGB.

                // Open sRGB profile.
                GetStandardColorSpaceProfile(NULL, LCS_sRGB, NULL, &dwSize);
                pszSRGB = (LPTSTR) GlobalAlloc(GPTR, dwSize);
                GetStandardColorSpaceProfile(NULL, LCS_sRGB, pszSRGB, &dwSize);

                if (NULL != pszSRGB)
                {
                    // Translate DIB.
                    hDIBTransformed = TransformDIBOutsideDC(lpDIBInfo->hDIB,
                                                            lpDIBInfo->bmFormat,
                                                            pszSRGB,
                                                            NULL,
                                                            USE_BITMAP_INTENT,
                                                            NULL,
                                                            0);

                    // Clean up.
                    GlobalFree((HANDLE)pszSRGB);

                    // Change bitmap info variable to point at tranlated DIB.
                    pBitmapInfo = (PBITMAPINFOHEADER) GlobalLock(hDIBTransformed);
                }
            }
        }

        if (NULL != pBitmapInfo)
        {
            // Create file header and write it to the file.
            FileHeader.bfType = BFT_BITMAP;
            FileHeader.bfSize = sizeof(BITMAPFILEHEADER);
            FileHeader.bfSize += sizeof(BITMAPV5HEADER);
            FileHeader.bfSize += NumColorsInDIB(pBitmapInfo) * sizeof(RGBQUAD);
            FileHeader.bfSize += BITMAPSIZE(pBitmapInfo);
            FileHeader.bfSize += dwProfileSize;
            FileHeader.bfOffBits = sizeof(BITMAPFILEHEADER);
            FileHeader.bfOffBits += sizeof(BITMAPV5HEADER);
            FileHeader.bfOffBits += NumColorsInDIB(pBitmapInfo) * sizeof(RGBQUAD);
            WriteFile(hFile, &FileHeader, sizeof(BITMAPFILEHEADER), &dwBytes, NULL);

            // Create bitmap header and write it to file.
            BitmapHeader.bV5Size = sizeof(BITMAPV5HEADER);
            BitmapHeader.bV5Width = BITMAPWIDTH(pBitmapInfo);
            BitmapHeader.bV5Height = BITMAPHEIGHT(pBitmapInfo);
            BitmapHeader.bV5Planes = 1;
            BitmapHeader.bV5BitCount = (WORD) BITCOUNT(pBitmapInfo);
            BitmapHeader.bV5Compression = BITMAPCOMPRESSION(pBitmapInfo);
            BitmapHeader.bV5SizeImage = BITMAPIMAGESIZE(pBitmapInfo);
            BitmapHeader.bV5ClrUsed = BITMAPCLRUSED(pBitmapInfo);
            BitmapHeader.bV5ClrImportant = BITMAPCLRIMPORTANT(pBitmapInfo);
            BitmapHeader.bV5RedMask = BITMAPREDMASK(pBitmapInfo);
            BitmapHeader.bV5GreenMask = BITMAPGREENMASK(pBitmapInfo);
            BitmapHeader.bV5BlueMask = BITMAPBLUEMASK(pBitmapInfo);
            BitmapHeader.bV5CSType = dwType;
            if (sizeof(BITMAPV4HEADER) <= *(LPDWORD)pBitmapInfo)
            {
                memcpy(&BitmapHeader.bV5Endpoints, &((PBITMAPV4HEADER)pBitmapInfo)->bV4Endpoints,
                       sizeof(CIEXYZTRIPLE) + sizeof(DWORD) * 3);
            }
            BitmapHeader.bV5Intent = BITMAPINTENT(pBitmapInfo);
            BitmapHeader.bV5ProfileData = sizeof(BITMAPV5HEADER);
            BitmapHeader.bV5ProfileData += NumColorsInDIB(pBitmapInfo) * sizeof(RGBQUAD);
            BitmapHeader.bV5ProfileData += BITMAPSIZE(pBitmapInfo);
            BitmapHeader.bV5ProfileSize = dwProfileSize;
            WriteFile(hFile, &BitmapHeader, sizeof(BITMAPV5HEADER), &dwBytes, NULL);

            // Write color table.
            if (!IS_BITMAPCOREHEADER(pBitmapInfo))
            {
                PBYTE   pColorTable;
                DWORD   dwColorTableSize;

                dwColorTableSize = NumColorsInDIB(pBitmapInfo) * sizeof(RGBQUAD);
                pColorTable = (PBYTE)pBitmapInfo + *(LPDWORD)pBitmapInfo;
                if ( IS_BITMAPINFOHEADER(pBitmapInfo)
                     &&
                     (BI_BITFIELDS == BITMAPCOMPRESSION(pBitmapInfo))
                   )
                {
                    dwColorTableSize += sizeof(DWORD) * 3;
                }

                WriteFile(hFile, pColorTable, dwColorTableSize, &dwBytes, NULL);
            }
            else
            {
                PBYTE   pColorTable;
                DWORD   dwCount;

RGBQUAD ColorTable[256];

                pColorTable = (PBYTE)pBitmapInfo + *(LPDWORD)pBitmapInfo;
                memset(ColorTable, 0, sizeof(ColorTable));
                for (dwCount = 0; dwCount < NumColorsInDIB(pBitmapInfo); dwCount++)
                    memcpy(ColorTable + dwCount, pColorTable + dwCount, sizeof(RGBTRIPLE));

                WriteFile(hFile, ColorTable, NumColorsInDIB(pBitmapInfo) * sizeof(RGBQUAD), &dwBytes, NULL);
            }

            // Save bitmap data.
            WriteFile(hFile, FindDIBBits(pBitmapInfo), BITMAPSIZE(pBitmapInfo), &dwBytes, NULL);

            // Save profile data.
            if (0 != dwProfileSize)
            {
                WriteFile(hFile, pProfileData, dwProfileSize, &dwBytes, NULL);
            }

            bResult = TRUE;
        }

        // Clean up.
        CloseHandle(hFile);
        if (NULL != lpszProfileName)
            GlobalFree((HANDLE)lpszProfileName);
        if (NULL != hDIBTransformed)
        {
            GlobalUnlock(hDIBTransformed);
            GlobalFree(hDIBTransformed);
        }

        // If failed, delete file.
        if (!bResult)
            DeleteFile(lpszFileName);
    }

    //Clean up.
    GlobalUnlock(lpDIBInfo->hDIB);

    return bResult;
}


DIBS.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    DIBS.C
//
//  PURPOSE:
//    Include file for DIBS.H
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

#include <math.h>

//
// General pre-processor macros
//

// Calculates fixed poit from floating point.
#define __FXPTMANTISSA(d, f)  ( (DWORD)d << f )
#define __FXPTFRACTION(d, f)  ( (DWORD)ldexp((d - (DWORD)d), f) )
#define __FXPT32(d, f)      ( __FXPTMANTISSA(d, f) | __FXPTFRACTION(d, f) )

#define __FXPT2DOT30(d)   __FXPT32(d, 30)
#define __FXPT16DOT16(d)  __FXPT32(d, 16)

// Max number of color bits that will have a full color table.
#define MAX_BPP_COLOR_TABLE             8

// Macro to swap two values
#define SWAP(x,y)   ((x)^=(y)^=(x)^=(y))

#define ABS(x)          (((x) >= 0) ? (x) : (-(x)))

#define PALVERSION      0x300
#define MAXPALETTE      256     // max. # supported palette entries

#define RECTWIDTH(lpRect)     ((lpRect)->right - (lpRect)->left)
#define RECTHEIGHT(lpRect)    ((lpRect)->bottom - (lpRect)->top)

// Header signatutes for various resources
#define BFT_BITMAP 0x4d42   // 'BM'

// Intent flag to indicate to use what ever the bitmap header intent is.
#define USE_BITMAP_INTENT       0xffffffff

// macro to determine if resource is a DIB
#define ISDIB(bft) ((bft) == BFT_BITMAP)

// Universial macros to find bitmap's width and height.
#define BITMAPWIDTH(lpbi)           (*(LPDWORD)lpbi >= sizeof(BITMAPINFOHEADER) ? lpbi->biWidth :  \
                                    ((LPBITMAPCOREHEADER)lpbi)->bcWidth)
#define BITMAPHEIGHT(lpbi)          (*(LPDWORD)lpbi >= sizeof(BITMAPINFOHEADER) ? lpbi->biHeight :  \
                                    ((LPBITMAPCOREHEADER)lpbi)->bcHeight)
#define BITMAPCOMPRESSION(lpbi)     (*(LPDWORD)lpbi >= sizeof(BITMAPINFOHEADER) ? lpbi->biCompression : BI_RGB)
#define BITMAPIMAGESIZE(lpbi)       (*(LPDWORD)lpbi >= sizeof(BITMAPINFOHEADER) ? lpbi->biSizeImage : 0)
#define BITMAPCSTYPE(lpbi)          (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER) ? ((PBITMAPV4HEADER)lpbi)->bV4CSType : LCS_sRGB)
#define BITMAPCLRUSED(lpbi)         (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER) ? ((PBITMAPV4HEADER)lpbi)->bV4ClrUsed : 0)
#define BITMAPCLRIMPORTANT(lpbi)    (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER) ? ((PBITMAPV4HEADER)lpbi)->bV4ClrImportant : 0)
#define BITMAPREDMASK(lpbi)         ( (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER)) || (BITMAPCOMPRESSION(lpbi) == BI_BITFIELDS) ?         \
                                    ((PBITMAPV4HEADER)lpbi)->bV4RedMask : 0)
#define BITMAPGREENMASK(lpbi)       ( (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER)) || (BITMAPCOMPRESSION(lpbi) == BI_BITFIELDS) ?         \
                                    ((PBITMAPV4HEADER)lpbi)->bV4GreenMask : 0)
#define BITMAPBLUEMASK(lpbi)        ( (*(LPDWORD)lpbi >= sizeof(BITMAPV4HEADER)) || (BITMAPCOMPRESSION(lpbi) == BI_BITFIELDS) ?         \
                                    ((PBITMAPV4HEADER)lpbi)->bV4BlueMask : 0)
#define BITMAPINTENT(lpbi)          (*(LPDWORD)lpbi >= sizeof(BITMAPV5HEADER) ? ((PBITMAPV5HEADER)lpbi)->bV5Intent : LCS_GM_IMAGES)

// Macro to determine bitcount for all bitmap types.
#define BITCOUNT(lpbi)      (*(LPDWORD)lpbi >= sizeof(BITMAPINFOHEADER) ? ((LPBITMAPINFOHEADER)lpbi)->biBitCount :  \
                            ((LPBITMAPCOREHEADER)lpbi)->bcBitCount)

// Macro to determine to the DWORD aligned stride of a bitmap.
#define WIDTHBYTES(lpbi)   (((BITMAPWIDTH(lpbi) * BITCOUNT(lpbi) + 31) & ~31) >> 3)

// Macro to calculate size of bitmap.
#define BITMAPSIZE(lpbi)    ( 0 != BITMAPIMAGESIZE(lpbi) ? BITMAPIMAGESIZE(lpbi) : WIDTHBYTES(lpbi) * abs(BITMAPHEIGHT(lpbi)))

// Macros to query Bitmap header version
#define IS_BITMAPCOREHEADER(lpbi) (sizeof(BITMAPCOREHEADER) == *(LPDWORD)lpbi)
#define IS_BITMAPINFOHEADER(lpbi) (sizeof(BITMAPINFOHEADER) == *(LPDWORD)lpbi)
#define IS_BITMAPV4HEADER(lpbi)   (sizeof(BITMAPV4HEADER)   == *(LPDWORD)lpbi)
#define IS_BITMAPV5HEADER(lpbi)   (sizeof(BITMAPV5HEADER)   == *(LPDWORD)lpbi)

// Macro to determine the size of profile data.
#define PROFILESIZE(lpbi)   (*(LPDWORD)lpbi < sizeof(BITMAPV5HEADER) ? 0 :  \
                            ((LPBITMAPV5HEADER)lpbi)->bV5ProfileSize)

// Macro to that returns pointer to DIB profile data.
#define GETPROFILEDATA(lpbi)    (IS_BITMAPV5HEADER(lpbi) ? (LPBYTE)lpbi + ((LPBITMAPV5HEADER)lpbi)->bV5ProfileData : NULL)

// Given a pointer to a DIB header, return TRUE if is a Windows 3.0 style
//  DIB, false if otherwise (PM style DIB).  Assume it is a Windows DIB if
// size (the first DWORD) at least eqal to BITMAPINFOHEADER size.

#define IS_WIN30_DIB(lpbi)  ((*(LPDWORD) (lpbi)) >= sizeof (BITMAPINFOHEADER))

// General STRUCTS && TYPEDEFS

// Function prototypes
void      DIBPaint (HDC hDC, LPRECT lpDCRect, HGLOBAL hDIB,LPRECT lpDIBRect, LPDIBINFO lpDIBInfo);
HGLOBAL   ReadDIBFile(LPTSTR lpszFileName);
DWORD     PaletteSize(LPBITMAPINFOHEADER lpbi);
HANDLE    ReadDIBFromFile(HANDLE hFile);
HPALETTE  CreateDIBPalette(HANDLE hDIB);
LPBYTE    FindDIBBits(LPBITMAPINFOHEADER lpbi);
HANDLE    TransformDIBOutsideDC(HANDLE hDIB, BMFORMAT bmInput, LPTSTR lpszDestProfile,
                                LPTSTR lpszTargetProfile, DWORD dwIntent, PBMCALLBACKFN pBMCallback,
                                ULONG ulCallbackData);
BOOL      SaveDIBToFile(HWND hWnd, LPCTSTR lpszFileName, LPDIBINFO lpDIBInfo, DWORD dwType);


ICMVIEW.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    ICMVIEW.C
//
//  PURPOSE:
//
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings

// C RunTime Header Files
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>

// Local Header Files
#include "Resource.h"

#define   ICMVIEW_INTERNAL
#include "ICMView.h"
#undef    ICMVIEW_INTERNAL

#include "Print.H"
#include "DibInfo.H"
#include "Dialogs.H"
#include "AppInit.H"
#include "child.h"
#include "Debug.h"

// Global Variables:
HINSTANCE   ghInst;                         // Global instance handle
TCHAR       gstTitle[MAX_STRING];           // The title bar text
HWND        ghAppWnd;                       // Handle to application window
HWND        ghWndMDIClient;
DWORD       gdwLastError;                   // Used to track last error value
TCHAR       gstProfilesDir[MAX_PATH];       // System directory for ICM profiles

// Foward declarations of functions included in this code module:
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);

//
//  FUNCTION: WinMain(HANDLE, HANDLE, LPTSTR, int)
//
//  PURPOSE: Entry point for the application.
//
//  COMMENTS:
//
//      This function initializes the application and processes the
//      message loop.
//
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    HANDLE hAccelTable;

    if (!hPrevInstance)
    {
        // Perform instance initialization:
        if (!InitApplication(hInstance))
        {
            return (FALSE);
        }
    }

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow))
    {
        return (FALSE);
    }

    hAccelTable = LoadAccelerators (hInstance, APPNAME);
    ASSERT(hAccelTable);

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if ((!TranslateMDISysAccel (ghWndMDIClient, &msg))
            && (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg)))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (msg.wParam);
    lpCmdLine; // This will prevent 'unused formal parameter' warnings
}

//
//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for the main window.
//
//  MESSAGES:
//
//      WM_COMMAND - process the application menu
//      WM_PAINT - Paint the main window
//      WM_DESTROY - post a quit message and return
//      WM_DISPLAYCHANGE - message sent to Plug & Play systems when the display changes
//      WM_RBUTTONDOWN - Right mouse click -- put up context menu here if appropriate
//      WM_NCRBUTTONUP - User has clicked the right button on the application's system menu
//
//
LRESULT CALLBACK WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    int         wmId, wmEvent;
    POINT       pnt;
    HMENU       hMenu;
    int         iDlg;

    // Init variables
    iDlg = 0;

    switch (uiMsg)
    {

        case WM_INITMENUPOPUP:
            InitImageMenu(hwnd);
            break;

        case WM_CREATE:
            {
                CLIENTCREATESTRUCT ccs;

                // Find window menu where children will be listed
                ccs.hWindowMenu  = GetSubMenu (GetMenu (hwnd), WINDOWMENU_POS);
                ccs.idFirstChild = FIRSTCHILD;

                // Create the MDI client window, which will fill the client area
                ghWndMDIClient = CreateWindow (
                                              __TEXT("MDICLIENT"),
                                              NULL,
                                              WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL,
                                              0,
                                              0,
                                              0,
                                              0,
                                              hwnd,
                                              (HMENU) 0xCAC,
                                              ghInst,
                                              (LPTSTR)&ccs);

                ShowWindow (ghWndMDIClient, SW_SHOW);
                break;
            }

        case WM_COMMAND:
            wmId    = LOWORD(wParam); // Remember, these are...
            wmEvent = HIWORD(wParam); // ...different for Win32!

            //Parse the menu selections:
            switch (wmId)
            {
                case IDM_HELP_ABOUT:
                    DialogBox(ghInst, __TEXT("AboutBox"), hwnd, (DLGPROC)About);
                    break;

                case IDM_FILE_EXIT:
                    DestroyWindow (hwnd);
                    break;

                case IDM_FILE_OPEN:
                    fOpenNewImage(hwnd, NULL);
                    break;

                case IDM_WINDOW_CASCADE:
                    SendMessage(ghWndMDIClient, WM_MDICASCADE, 0, 0);;
                    break;

                case IDM_WINDOW_TILE_HORIZONTAL:
                    SendMessage(ghWndMDIClient, WM_MDITILE, (WPARAM)MDITILE_HORIZONTAL, 0);;
                    break;

                case IDM_WINDOW_TILE_VERTICAL:
                    SendMessage(ghWndMDIClient, WM_MDITILE, (WPARAM)MDITILE_VERTICAL, 0);;
                    break;

                case IDM_WINDOW_ARRANGE:
                    SendMessage(ghWndMDIClient, WM_MDIICONARRANGE, 0, 0);
                    break;

                case IDM_FILE_RECENT:
                case IDM_FILE_RECENT1:
                case IDM_FILE_RECENT2:
                case IDM_FILE_RECENT3:
                    {
                        HANDLE  hRecent;
                        LPTSTR  *ppszRecent;
                        LPTSTR  lpszFileName;

                        // Get handle to recent file list table.
                        hRecent = GetProp(hwnd, APP_RECENT);
                        if (NULL != hRecent)
                        {
                            // Get pointer to recent file array.
                            ppszRecent = (LPTSTR*) GlobalLock(hRecent);
                            ASSERT(NULL != ppszRecent);

                            // Copy file name into buffer.
                            lpszFileName = GlobalAlloc(GPTR,(lstrlen(ppszRecent[ wmId - IDM_FILE_RECENT]) +1) * sizeof(TCHAR));

                            // Open the file.
                            if (lpszFileName != NULL)
                            {
                                // Copy recent file name to file name buffer.
                                _tcscpy(lpszFileName, ppszRecent[ wmId - IDM_FILE_RECENT]);
                                GlobalUnlock(hRecent);

                                // Open new image of recent file.
                                fOpenNewImage(hwnd, lpszFileName);
                            }
                        }
                    }
                    break;

                default:
                    {
                        HWND  hwndActiveChild;

                        hwndActiveChild = GetCurrentMDIWnd();
                        ASSERT(hwndActiveChild != NULL);
                        if (hwndActiveChild)
                        {
                            PostMessage(hwndActiveChild, uiMsg, wmId, wmEvent);
                        }
                        break;
                    }
            }
            break;

        case WM_RBUTTONDOWN: // RightClick in windows client area...
            pnt.x = LOWORD(lParam);
            pnt.y = HIWORD(lParam);
            ClientToScreen(hwnd, (LPPOINT) &pnt);
            hMenu = GetSubMenu (GetMenu (hwnd), 2);
            if (hMenu)
            {
                TrackPopupMenu (hMenu, 0, pnt.x, pnt.y, 0, hwnd, NULL);
            }
            else  // Couldn't find the menu...
            {
                MessageBeep(0);
            }
            break;

        case WM_DISPLAYCHANGE: // Only comes through on plug'n'play systems
            {
                SIZE szScreen;
                BOOL fChanged = (BOOL)wParam;

                szScreen.cx = LOWORD(lParam);
                szScreen.cy = HIWORD(lParam);
            }
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            return 1L;

        case WM_DESTROY:
            {
                DWORD             dwFlags = 0L;
                HANDLE            hRecent;
                WINDOWPLACEMENT   WndPlacement;

                // Store application settings.
                dwFlags |= IsZoomed(hwnd) ? IVF_MAXIMIZED : 0L;
                WndPlacement.length = sizeof(WINDOWPLACEMENT);
                GetWindowPlacement(hwnd, &WndPlacement);
                hRecent = GetProp(hwnd, APP_RECENT);
                SetSettings(&WndPlacement.rcNormalPosition, dwFlags, hRecent);

                // Remove recent file attachment and free memory.
                RemoveProp(hwnd, APP_RECENT);
                GlobalFree(hRecent);
            }
            PostQuitMessage(0);
            break;

        case WM_QUERYNEWPALETTE:
            {
                HWND hwndActive;

                hwndActive = GetCurrentMDIWnd();
                if (NULL != hwndActive)
                {
                    return SendMessage(hwndActive, MYWM_QUERYNEWPALETTE, (WPARAM) hwnd, 0L);
                }
                return FALSE;
            }

        case WM_SIZE:
            InvalidateRect(hwnd, NULL, TRUE);
            break;
    }
    return DefFrameProc(hwnd,ghWndMDIClient, uiMsg, wParam, lParam);
}

//
//  FUNCTION: About(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for "About" dialog box
//              This version allows greater flexibility over the contents of the 'About' box,
//              by pulling out values from the 'Version' resource.
//
//  MESSAGES:
//
//      WM_INITDIALOG - initialize dialog box
//      WM_COMMAND    - Input received
//
//
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static  HFONT hfontDlg;         // Font for dialog text
    static  HFONT hFinePrint;       // Font for 'fine print' in dialog
    DWORD   dwVerInfoSize;          // Size of version information block
    LPTSTR   lpVersion;              // String pointer to 'version' text
    DWORD   dwVerHnd=0;             // An 'ignored' parameter, always '0'
    UINT    uVersionLen;
    int     iRootLen;
    BOOL    bRetCode;
    int     i;
    TCHAR   szFullPath[256];
    TCHAR   szResult[256];
    TCHAR   szGetName[256];
    DWORD   dwVersion;
    TCHAR   szVersion[40];
    DWORD   dwResult;

    wParam = wParam; //Eliminate 'unused formal parameter' warning
    lParam = lParam; //Eliminate 'unused formal parameter' warning

    switch (message)
    {
        case WM_INITDIALOG:
            ShowWindow (hDlg, SW_HIDE);
            hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, __TEXT(""));
            hFinePrint = CreateFont(11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, __TEXT(""));
            CenterWindow (hDlg, GetWindow (hDlg, GW_OWNER));
            GetModuleFileName (ghInst, szFullPath, sizeof(szFullPath));

            // Now let's dive in and pull out the version information:
            dwVerInfoSize = GetFileVersionInfoSize(szFullPath, &dwVerHnd);
            if (dwVerInfoSize)
            {
                LPTSTR   lpstrVffInfo;
                HANDLE  hMem;
                hMem = GlobalAlloc(GMEM_MOVEABLE, dwVerInfoSize);
                lpstrVffInfo  = GlobalLock(hMem);
                GetFileVersionInfo(szFullPath, dwVerHnd, dwVerInfoSize, lpstrVffInfo);

                // The below 'hex' value looks a little confusing, but
                // essentially what it is, is the hexidecimal representation
                // of a couple different values that represent the language
                // and character set that we are wanting string values for.
                // 040904E4 is a very common one, because it means:
                //   US English, Windows MultiLingual characterset
                // Or to pull it all apart:
                // 04//////        = SUBLANG_ENGLISH_USA
                // --09////        = LANG_ENGLISH
                // ////04E4 = 1252 = Codepage for Windows:Multilingual
                _tcscpy(szGetName, __TEXT("\\StringFileInfo\\040904E4\\"));
                iRootLen = lstrlen(szGetName); // Save this position

                // Set the title of the dialog:
                lstrcat (szGetName, __TEXT("ProductName"));
                bRetCode = VerQueryValue((LPVOID)lpstrVffInfo, (LPTSTR)szGetName, (LPVOID)&lpVersion, (UINT *)&uVersionLen);
                _tcscpy(szResult, __TEXT("About "));
                lstrcat(szResult, lpVersion);
                SetWindowText (hDlg, szResult);

                // Walk through the dialog items that we want to replace:
                for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++)
                {
                    GetDlgItemText(hDlg, i, szResult, sizeof(szResult));
                    szGetName[iRootLen] = __TEXT('\0');
                    lstrcat (szGetName, szResult);
                    uVersionLen   = 0;
                    lpVersion     = NULL;
                    bRetCode      =  VerQueryValue((LPVOID)lpstrVffInfo, (LPTSTR)szGetName, (LPVOID)&lpVersion, (UINT *)&uVersionLen);

                    if ( bRetCode && uVersionLen && lpVersion)
                    {
                        // Replace dialog item text with version info
                        _tcscpy(szResult, lpVersion);
                        SetDlgItemText(hDlg, i, szResult);
                    }
                    else
                    {
                        dwResult = GetLastError();
                        wsprintf (szResult,__TEXT("Error %lu"), dwResult);
                        SetDlgItemText (hDlg, i, szResult);
                    }
                    SendMessage (GetDlgItem (hDlg, i), WM_SETFONT, (UINT)((i==DLG_VERLAST)?hFinePrint:hfontDlg), TRUE);
                } // for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++)
                GlobalUnlock(hMem);
                GlobalFree(hMem);
            }
            else
            {
                // No version information available.
            } // if (dwVerInfoSize)

            SendMessage (GetDlgItem (hDlg, IDC_LABEL), WM_SETFONT, (WPARAM)hfontDlg,(LPARAM)TRUE);

            // We are  using GetVersion rather then GetVersionEx
            // because earlier versions of Windows NT and Win32s
            // didn't include GetVersionEx:
            dwVersion = GetVersion();
            if (dwVersion < 0x80000000)
            {
                // Windows NT
                wsprintf (szVersion,__TEXT("Microsoft Windows NT %u.%u (Build: %u)"), (DWORD)(LOBYTE(LOWORD(dwVersion))), (DWORD)(HIBYTE(LOWORD(dwVersion))),(DWORD)(HIWORD(dwVersion)) );
            }
            else
            {
                if (LOBYTE(LOWORD(dwVersion)) < 4)
                {
                    // Win32s
                    wsprintf (szVersion, __TEXT("Microsoft Win32s %u.%u (Build: %u)"), (DWORD)(LOBYTE(LOWORD(dwVersion))), (DWORD)(HIBYTE(LOWORD(dwVersion))),(DWORD)(HIWORD(dwVersion) & ~0x8000) );
                }
                else
                {
                    // Windows 95
                    wsprintf (szVersion, __TEXT("Microsoft Windows 95 %u.%u"), (DWORD)(LOBYTE(LOWORD(dwVersion))), (DWORD)(HIBYTE(LOWORD(dwVersion))) );
                }
            }
            SetWindowText (GetDlgItem(hDlg, IDC_OSVERSION), szVersion);
            ShowWindow (hDlg, SW_SHOW);
            return (TRUE);

        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, TRUE);
                DeleteObject (hfontDlg);
                DeleteObject (hFinePrint);
                return (TRUE);
            }
            break;
    }
    return FALSE;
}

//
//   FUNCTION: CenterWindow(HWND, HWND)
//
//   PURPOSE: Centers one window over another.
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
//              This function will center one window over another ensuring that
//              the placement of the window is within the 'working area', meaning
//              that it is both within the display limits of the screen, and not
//              obscured by the tray or other framing elements of the desktop.
BOOL CenterWindow (HWND hwndChild, HWND hwndParent)
{
    RECT    rChild, rParent, rWorkArea;
    int     wChild, hChild, wParent, hParent;
    int     xNew, yNew;
    BOOL    bResult;

    // Get the Height and Width of the child window
    GetWindowRect (hwndChild, &rChild);
    wChild = rChild.right - rChild.left;
    hChild = rChild.bottom - rChild.top;

    // Get the Height and Width of the parent window
    GetWindowRect (hwndParent, &rParent);
    wParent = rParent.right - rParent.left;
    hParent = rParent.bottom - rParent.top;

    // Get the limits of the 'workarea'
    bResult = SystemParametersInfo(
                                  SPI_GETWORKAREA,
                                  sizeof(RECT),
                                  &rWorkArea,
                                  0);
    if (!bResult)
    {
        rWorkArea.left = rWorkArea.top = 0;
        rWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
        rWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
    }
    // Calculate new X position, then adjust for workarea
    xNew = rParent.left + ((wParent - wChild) / 2);
    if (xNew < rWorkArea.left)
    {
        xNew = rWorkArea.left;
    }
    else if ((xNew + wChild) > rWorkArea.right)
    {
        xNew = rWorkArea.right - wChild;
    }
    // Calcualte new Y position, then adjust for workarea
    yNew = rParent.top + ((hParent - hChild) /2);
    if (yNew < rWorkArea.top)
    {
        yNew = rWorkArea.top;
    }
    else if ((yNew + hChild) > rWorkArea.bottom)
    {
        yNew = rWorkArea.bottom - hChild;
    }

    //Set it and return
    return(SetWindowPos(hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER));
}

//////////////////////////////////////////////////////////////////////////
//  Function:  CopyString
//
//  Description:
//    Allocates space and copies one LPTSTR into a new one.  Allocates
//    exactly as much memory as necessary.
//
//  Parameters:
//    LPTSTR    Source string to be copied.
//
//  Returns:
//    LPTSTR  Pointer to copy of string; NULL if failure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPTSTR CopyString(LPTSTR  lpszSrc)
{
    // Local variables
    LPTSTR    lpszDest;
    int       iStrLen;

    //  Initialize variables
    lpszDest = NULL;

    if (lpszSrc == NULL)
    {
        DebugMsg(__TEXT("ICMVIEW.C : CopyString : lpszSrc == NULL\r\n"));
        return(NULL);
    }
    iStrLen = ((int)(lstrlen(lpszSrc) +1) * sizeof(TCHAR));
    lpszDest = GlobalAlloc(GPTR, iStrLen);
    _tcscpy(lpszDest, lpszSrc);
    return(lpszDest);
}   // End of function CopyString

//////////////////////////////////////////////////////////////////////////
//  Function:  UpdateString
//
//  Description:
//    Replaces target string with source string.  Frees target first
//    if necessary.
//
//  Parameters:
//    LPTSTR    Destination string
//    LPTSTR    Source string
//
//  Returns:
//    LPTSTR    Destination string.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL UpdateString(LPTSTR *lpszDest, LPTSTR lpszSrc)
{
    // Local variables
    HGLOBAL hMem, hDest;
    LPTSTR  lpszNew;

    //  Initialize variables
    if (NULL == lpszSrc)
    {
        DebugMsg(__TEXT("ICMVIEW.C : UpdateString : NULL source\r\n"));
        *lpszDest = NULL;
        return(FALSE);
    }

    if (NULL != *lpszDest)
    {
        hDest = GlobalHandle(*lpszDest);
        GlobalUnlock(hDest);
        GlobalFree(hDest);
    }
    hMem = GlobalAlloc(GPTR, (1 + lstrlen(lpszSrc))* sizeof(TCHAR));
    if (NULL == hMem)
    {
        DebugMsg(__TEXT("UpdateString : GlobalAlloc failed\r\n"));
        return(FALSE);
    }
    lpszNew = GlobalLock(hMem);
    _tcscpy(lpszNew, lpszSrc);
    *lpszDest = lpszNew;
    return(TRUE);
}   // End of function UpdateString

BOOL ConvertIntent(DWORD dwOrig, DWORD dwDirection, LPDWORD lpdwXlate)
{
    DWORD   adwIntents[MAX_ICC_INTENT + 1] = { LCS_GM_IMAGES, LCS_GM_GRAPHICS, LCS_GM_BUSINESS, LCS_GM_GRAPHICS};
    int     idx;

    ASSERT((ICC_TO_LCS == dwDirection) || (LCS_TO_ICC == dwDirection));
    *lpdwXlate = (DWORD)-1;

    switch (dwDirection)
    {
        case LCS_TO_ICC:
            for (idx = MAX_ICC_INTENT+1; idx >0; idx--)
            {
                if (adwIntents[idx] == dwOrig)
                {
                    *lpdwXlate = idx;
                    return(TRUE);
                }
            }
            break;

        case ICC_TO_LCS:
            if (dwOrig <= MAX_ICC_INTENT)
            {
                *lpdwXlate = adwIntents[dwOrig];
                return(TRUE);
            }
        default:
            SetLastError(ERROR_INVALID_PARAMETER);
            return(FALSE);
            break;
    }
    return(FALSE);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  SetDWFlags
//
//  Description:
//    Sets flag values in a DWORD flag.
//
//  Parameters:
//    LPDWORD Pointer to flag variable to be changed.
//    DWORD   Value to be set/cleared.
//    BOOL    Indicates if value is to be set or cleared.
//
//  Returns:
//    DWORD   Value of flag variable prior to call.
//
//  Comments:
//
//////////////////////////////////////////////////////////////////////////
DWORD SetDWFlags(LPDWORD lpdwFlag, DWORD dwBitValue, BOOL bSet)
{
    // Initialize variables
    if (bSet)
    {
        (*lpdwFlag) |= dwBitValue;
    }
    else
    {
        (*lpdwFlag) &= (~dwBitValue);
    }
    return(*lpdwFlag);
} // End of function SetDWFlags

//////////////////////////////////////////////////////////////////////////
//  Function:  InitImageMenu
//
//  Description:
//    Handles the WM_CONTEXTMENU message
//
//  Parameters:
//    @@@
//
//  Returns:
//    LRESULT
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
HMENU InitImageMenu(HWND hwnd)
{
    // Local variables
    HMENU       hMenu, hActiveMenu;
    UINT        uiMenuFlag;
    LPDIBINFO   lpDIBInfo;
    HWND        hwndImage;

    //  Initialize variables
    lpDIBInfo = NULL;

    hwndImage = GetCurrentMDIWnd();
    if (hwndImage)  // we have an active child window
    {
        lpDIBInfo = GetDIBInfoPtr(hwndImage);
        if (!lpDIBInfo)
        {
            return(NULL);
        }
    }

    if (hwnd == ghAppWnd) // dealing with main app window
    {
        hMenu = GetMenu(hwnd);
        hActiveMenu = GetSubMenu(hMenu, IsZoomed(hwndImage) ? 1 : 0);
        uiMenuFlag = (hwndImage != NULL) ? MF_ENABLED : MF_GRAYED;
        EnableMenuItem(hActiveMenu, IDM_FILE_CLOSE, uiMenuFlag);
        EnableMenuItem(hActiveMenu, IDM_FILE_PRINT_SETUP, uiMenuFlag);
        EnableMenuItem(hActiveMenu, IDM_FILE_PRINT, uiMenuFlag);
        EnableMenuItem(hActiveMenu, IDM_FILE_DISPLAY, uiMenuFlag);
        EnableMenuItem(hActiveMenu, IDM_FILE_CONFIGURE_ICM, uiMenuFlag);
        EnableMenuItem(hActiveMenu, ID_FILE_SAVEAS, uiMenuFlag);
    }
    else  // dealing with child window--must init context menu
    {
        hMenu = LoadMenu(ghInst, __TEXT("ImageContext"));
        hActiveMenu = GetSubMenu(hMenu, 0);
    }

    // Check EITHER ICM 2.0 or ICM 1.0 (Inside DC or Outside DC)
    if (hActiveMenu && lpDIBInfo)
    {
        uiMenuFlag = (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20) ? MF_CHECKED : MF_UNCHECKED);
        CheckMenuItem(hActiveMenu, IDM_FILE_ICM20, uiMenuFlag);
        CheckMenuItem(hActiveMenu, IDM_FILE_ICM10, uiMenuFlag == MF_CHECKED ? MF_UNCHECKED : MF_CHECKED);
    }

    if (lpDIBInfo)
        GlobalUnlock(GlobalHandle(lpDIBInfo));

    return(hActiveMenu);
}   // End of function InitImageMenu

//////////////////////////////////////////////////////////////////////////
//  Function:  GetBaseFilename
//
//  Description:
//
//
//  Parameters:
//      @@@
//
//  Returns:
//      BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL GetBaseFilename(LPTSTR lpszFilename, LPTSTR *lpszBaseFilename)
{
    // Local variables
    TCHAR   stFile[MAX_PATH], stExt[MAX_PATH];

    //  ASSERTs and parameter validations
    ASSERT(NULL != lpszFilename);
    if (NULL == lpszFilename)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    //  Initialize variables
    _tsplitpath(lpszFilename, NULL, NULL, stFile, stExt);
    (*lpszBaseFilename) = (LPTSTR)GlobalAlloc(GPTR, ((_tcslen(stFile) + _tcslen(stExt) + 1 ) * sizeof(TCHAR)));
    if (NULL != *lpszBaseFilename)
    {
        _stprintf(*lpszBaseFilename, __TEXT("%s%s"), stFile, stExt);
    }
    //  Cleanup any allocated resources
    return(NULL != *(lpszBaseFilename));
} // End of function GetBaseFilename


ICMVIEW.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    ICMVIEW.H
//
//  PURPOSE:
//    Include file for ICMVIEW.C
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros
#define APPNAMEA    "ICMVIEW"
#define APPNAME __TEXT("ICMVIEW")

#ifndef MAX_STRING
    #define MAX_STRING    256
#endif

//
// Constants for use by Windows ICM APIs
//    These *should* be in WINGDI.H
//
//  API:  ColorMatchToTarget.
#ifndef CS_ENABLE
    #define CS_ENABLE           1
    #define CS_DISABLE          2
    #define CS_DELETE_TRANSFORM 3
#endif

// Values for BITMAPV5HEADER field bV5CSType
#ifndef PROFILE_LINKED
    #define PROFILE_LINKED          'LINK'
    #define PROFILE_EMBEDDED        'MBED'
#endif

// Makes it easier to determine appropriate code paths:
#if defined (WIN32)
    #define IS_WIN32 TRUE
#else
    #define IS_WIN32 FALSE
#endif

#define IS_NT      (0 != (IS_WIN32 && ((BOOL)(GetVersion() < 0x80000000))) )
#define IS_WIN32S  (0 != (IS_WIN32 && (BOOL)(!(IS_NT) && (LOBYTE(LOWORD(GetVersion()))<4))))
#define IS_WIN95   (0 != ((BOOL)(!(IS_NT) && !(IS_WIN32S)) && IS_WIN32))

// Support macros
#ifndef C1_GAMMA_RAMP
    #define C1_GAMMA_RAMP 0x00000020
#endif

#ifndef CAPS1
    #define CAPS1   94
#endif

#define SUPPORT_GAMMA_RAMPS(hDC) (BOOL)((GetDeviceCaps(hDC, CAPS1) & C1_GAMMA_RAMP) != 0)

// Default profile to use
#define DEFAULT_ICM_PROFILE __TEXT("sRGB Color Space Profile.ICM")

// Window extra bytes
#define GWL_DIBINFO GWL_USERDATA

// DWORD Flag macros
#define CHECK_DWFLAG(dwFlag,dwBits)((BOOL)((dwFlag & dwBits) != 0))
#define ENABLE_DWFLAG(dwFlag,dwBits)   (dwFlag |= dwBits)
#define CLEAR_DWFLAG(dwFlag,dwBits) (dwFlag &= ~dwBits)
#define SET_DWFlag(dwFlag, dwBits, bSet) (bSet ? (dwFlag |= dwBits) : (dwFlag &= ~dwFlag))

#define START_WAIT_CURSOR(hCur) hCur = SetCursor(LoadCursor(NULL,IDC_WAIT));
#define END_WAIT_CURSOR(hCur) SetCursor(hCur);

#define IVF_MAXIMIZED   0x1L

// Constants for use in converting ICC Intents to LCS Intents
#define MAX_ICC_INTENT  INTENT_ABSOLUTE_COLORIMETRIC
#define ICC_TO_LCS      0
#define LCS_TO_ICC      1

// General STRUCTS && TYPEDEFS
#ifndef ICMVIEW_INTERNAL

// Global variables for the application.
extern  HINSTANCE   ghInst;                 // Global instance handle
extern  TCHAR       gstTitle[];             // The title bar text
extern  HWND        ghAppWnd;               // Handle to application window
extern  HWND        ghWndMDIClient;
extern  DWORD       gdwLastError;           // Used to track LastError value
extern  TCHAR       gstProfilesDir[MAX_PATH];       // System directory for ICM profiles

#endif

// Function prototypes
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LPTSTR  CopyString(LPTSTR lpszSrc);
BOOL    UpdateString(LPTSTR *lpszDest, LPTSTR lpszSrc);
BOOL    CenterWindow(HWND, HWND);
BOOL    ConvertIntent(DWORD dwOrig, DWORD dwDirection, LPDWORD lpdwXlate);
DWORD   SetDWFlags(LPDWORD lpdwFlag, DWORD dwBitValue, BOOL bSet);
HMENU   InitImageMenu(HWND hWnd);
BOOL    GetBaseFilename(LPTSTR lpszFilename, LPTSTR *lpszBaseFilename);


PRINT.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    Print.c
//
//  PURPOSE:
//    Illustrates the 'minimum' functionality of a well-behaved Win32 application.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include <commdlg.h>
#include <winspool.h>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings
#pragma warning(default:4514)   // Unreferenced inline function has been removed

// C RunTime Header Files
#include <TCHAR.H>

// Local Header Files
#include "IcmView.h"
#include "Child.h"
#include "dibinfo.h"
#include "Dibs.h"

#include "Debug.h"

#include "Print.h"
#include "RegUtil.h"
#include "Resource.h"

// local definitions

#ifndef ICM_DONE_OUTSIDEDC
    #define ICM_DONE_OUTSIDEDC  4
#endif

// default settings

// external functions

// external data

// public data

// private data
BOOL      gbUserAbort;
FARPROC   glpfnPrintDlgProc;
FARPROC   glpfnAbortProc;
HWND      ghDlgPrint;
HWND      ghWndParent;
LOGFONT   gLogFont = { 0, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, __TEXT("Arial")};

// private functions
BOOL FreeMemory(HANDLE hInfo);
LPBYTE GetMemory(LPHANDLE lphInfo, DWORD dwSize);
DWORD SPL_EnumPrinters(DWORD dwType, LPTSTR lpszName, DWORD dwLevel, LPHANDLE lphPrinterInfo);
BOOL PrintDIB (HANDLE hDIB, HDC hDC, int xOrigin, int yOrigin, int xSize, int ySize, BOOL bStretch);
static HDC PASCAL InitPrinting(HWND hWnd, LPTSTR lpszFriendlyName, PDEVMODE pDevMode);
static void PASCAL TermPrinting(HDC hDC);
BOOL FAR PASCAL PrintDlgProc (HWND hDlg, unsigned iMessage, WORD wParam, DWORD lParam);
BOOL FAR PASCAL AbortProc (HDC hPrnDC, short nCode);

extern DWORD NumColorsInDIB(LPBITMAPINFOHEADER lpbi);

// public functions

/////////////////////////////////////////////////////////////////////
//  Function:  SelectPrinter
//
//  Description:
//    Uses the Print common dialog box to provide the user with the
//    opportunity to select and set up a particular printer.
//
//  Parameters:
//    hWnd    Handle to the parent window.
//
//  Returns:
//    HDC to requested printer if successful; NULL otherwise.
//
// Comments:
//   If this function returns NULL, the caller should check
//   the latest COMMDLG error by calling CommDlgExtendedError().
//
/////////////////////////////////////////////////////////////////////

HDC SelectPrinter(HWND hWnd)
{
    // Local variables
    BOOL      bPrintDlg;        // Return code from PrintDlg function
    PRINTDLG  pd;               // Printer dialog structure
    DWORD     dwError;
    HDC       hDC;              // DC to printer

    //  Initialize variables
    hDC = NULL;
#ifdef _DEBUG
    memset(&pd, UNINIT_BYTE, sizeof(PRINTDLG));
#endif

    /* Initialize the PRINTDLG members. */

    pd.lStructSize = sizeof(PRINTDLG);
    pd.hwndOwner = hWnd;
    pd.Flags = PD_RETURNDC | PD_PRINTSETUP;
    pd.hDevMode = (HANDLE) NULL;
    pd.hDevNames = (HANDLE) NULL;
    pd.hDC = (HDC) NULL;
    pd.nFromPage = 1;
    pd.nToPage = 1;
    pd.nMinPage = 0;
    pd.nMaxPage = 0;
    pd.nCopies = 1;
    pd.hInstance = (HANDLE) NULL;
    pd.lCustData = 0L;
    pd.lpfnPrintHook = (LPPRINTHOOKPROC) NULL;
    pd.lpfnSetupHook = (LPSETUPHOOKPROC) NULL;
    pd.lpPrintTemplateName = (LPTSTR) NULL;
    pd.lpSetupTemplateName = (LPTSTR)  NULL;
    pd.hPrintTemplate = (HANDLE) NULL;
    pd.hSetupTemplate = (HANDLE) NULL;

    // Display the PRINT dialog box.
    bPrintDlg = PrintDlg(&pd);
    if (!bPrintDlg)  // Either no changes, or a common dialog error occured
    {
        dwError = CommDlgExtendedError();
        if (dwError != 0)
        {
            return(NULL);
        }
    }
    else // Passed call, set up DC
    {
        ASSERT(pd.hDC != NULL); // HDC should never be NULL if we passed the call
        hDC = pd.hDC;
    }
    return(hDC);
}   // End of SelectPrinter

// private functions

//////////////////////////////////////////////////////////////////////////
//  Function:  GetPrinterDC
//
//  Description:
//    Obtains a DC from the specified friendly name.
//
//  Parameters:
//    @@@
//
//  Returns:
//    HDC
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
HDC GetPrinterDC(LPTSTR lpszFriendlyName, PDEVMODE pDevMode)
{
    HDC     hDC;
    BOOL    bFreeDevMode = FALSE;

    //  Initialize variables
    hDC = NULL;

    if (lpszFriendlyName != NULL)
    {
        // Make sure that we have a devmode.
        if (NULL == pDevMode)
        {
            pDevMode = GetDefaultPrinterDevMode(lpszFriendlyName);
            bFreeDevMode = TRUE;
        }

        // Now get a DC for the printer
        hDC = CreateDC(NULL, lpszFriendlyName, NULL, pDevMode);

        // Free devmode if created in routine.
        if (bFreeDevMode)
        {
            GlobalFree((HANDLE)pDevMode);
        }
    }
    else
    {
        DebugMsg(__TEXT("GetPrinterDC:  lpszFriendlyName == NULL"));
    }

    return hDC;
}   // End of function GetPrinterDC

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDefaultPrinterName
//
//  Description:
//    Obtains the name of the default printer.
//
//  Parameters:
//    none
//
//  Returns:
//    LPTSTR   Name of printer, or NULL if failed.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPTSTR GetDefaultPrinterName(void)
{
    // Local variables
    LPTSTR     lpszDefaultPrinter = NULL;

    if (IS_WIN95)
    {

        lpszDefaultPrinter = GetRegistryString(HKEY_CURRENT_CONFIG,
                                               __TEXT("SYSTEM\\CurrentControlSet\\Control\\Print\\Printers"),
                                               __TEXT("Default"));
    }
    else if (IS_NT)
    {
        TCHAR szTemp[MAX_PATH];
        LPTSTR lpszTemp;

        // Get Default printer name.
        GetProfileString(__TEXT("windows"), __TEXT("device"), __TEXT(""),
                         szTemp, sizeof(szTemp));

        if (lstrlen(szTemp) == 0)
        {
            // INVARIANT:  no default printer.
            return(NULL);
        }

        // Terminate at first comma, just want printer name.
        lpszTemp = _tcschr(szTemp, ',');
        if (lpszTemp != NULL)
        {
            *lpszTemp = '\x0';
        }
        lpszDefaultPrinter = CopyString((LPTSTR)szTemp);
    }
    return(lpszDefaultPrinter);
}   // End of function GetDefaultPrinterName

//////////////////////////////////////////////////////////////////////////
//  Function:  PopulatePrinterCombobox
//
//  Description:
//    Enumerates all printers into a ComboBox based upon the provided flags.
//
//  Parameters:
//    @@@
//
//  Returns:
//    DWORD Number of printers enumerated.  -1 indicates failure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
DWORD PopulatePrinterCombobox(HWND hDlg, int iControlId, LPTSTR lpszCurrentPrinter)
{
    // Local variables
    DWORD             dwReturnCode;
    DWORD             dwIndex;
    DWORD             dwNumPRN;
    LPPRINTER_INFO_2  lpPrinterInfo2;
    HGLOBAL           hFree;

    //  Initialize variables
    lpPrinterInfo2 = NULL;

    // Initialize ComboBox
    SendDlgItemMessage(hDlg, iControlId, EM_LIMITTEXT, (WPARAM)MAX_PATH, 0L);
    SendDlgItemMessage(hDlg,  iControlId, CB_RESETCONTENT, (WPARAM)0, (LPARAM)0L);

    dwNumPRN = SPL_EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 2, (LPVOID)&lpPrinterInfo2);
    EnableWindow(GetDlgItem(hDlg, iControlId), (dwNumPRN != 0));
    if (dwNumPRN <= 0)
    {
        dwReturnCode = SendDlgItemMessage(hDlg, iControlId, CB_ADDSTRING, 0, (LPARAM)(LPTSTR)__TEXT("No printers installed"));
        dwReturnCode = SendDlgItemMessage(hDlg, iControlId, WM_SETTEXT, 0, (LPARAM)(LPTSTR)__TEXT("No printers installed"));
        return (dwNumPRN);  // No printers to deal with.  -1 if EnumPrinters call failed.
    }

    if (lpPrinterInfo2 != NULL) // Got array of PRINTER_INFO structures
    {
        for (dwIndex =0; dwIndex < dwNumPRN; dwIndex++)
        {
            dwReturnCode = SendDlgItemMessage(hDlg, iControlId, CB_ADDSTRING, 0, (LPARAM)(LPTSTR)lpPrinterInfo2[dwIndex].pPrinterName);
            if (dwReturnCode == CB_ERR)
            {
                return((DWORD)-1);
            }

            // If this is the current printer, load it into edit control
            if (lstrcmpi((LPTSTR)lpPrinterInfo2[dwIndex].pPrinterName, lpszCurrentPrinter) == 0)
            {
                dwReturnCode = SendDlgItemMessage(hDlg, iControlId, WM_SETTEXT, 0, (LPARAM)(LPTSTR)lpPrinterInfo2[dwIndex].pPrinterName);
                if (dwReturnCode == CB_ERR)
                {
                    return((DWORD)-1);
                }  // CB_ERR
            }  // current printer
        } // for (dwIndex = 0 ...
    }  // if (lpPrinterInfo2 != NULL)
    else
    {
        return((DWORD)-1);
    }

    // Free memory
    hFree = GlobalFree(lpPrinterInfo2);
    return(dwNumPRN);
}   // End of function PopulatePrinterCombobox

////////////////////////////////////////////////////////////////////////////
//
// FunctionName: SPL_EnumPrinters()
//
// Purpose:
//
// Parameters:
//    None.
//
// Return Value:
//
// Comments:
//
////////////////////////////////////////////////////////////////////////////

DWORD SPL_EnumPrinters(DWORD dwType, LPTSTR lpszName, DWORD dwLevel, LPVOID *lpvPrinterInfo)

{
    DWORD        dwSize;
    DWORD        dwPrinters;
    DWORD        dwNeeded    = 0;
    DWORD        dwErrorCode = 0;
    BOOL         bReturnCode;
    BOOL         bRC         = FALSE;
    LPBYTE       lpInfo      = NULL;

    // Enumerate Printers.
    bReturnCode = EnumPrinters(dwType, lpszName, dwLevel, NULL, 0, &dwSize, &dwPrinters);

    // If Return Code is TRUE, there is nothing to enumerate.
    if (bReturnCode)
    {
        DebugMsg(__TEXT("EnumPrinter():  No printers found\r\n"));
        return(0);
    }

    // Since Return Code is FALSE, check LastError.
    // If LastError is any thing other than allocate size error, flag and exit.
    dwErrorCode = GetLastError();
    if (dwErrorCode != ERROR_INSUFFICIENT_BUFFER)
    {
        return((DWORD)-1);
    }

    // Loop until we have size right.
    while (!bRC)
    {
        if (NULL != (lpInfo = (LPBYTE)GlobalAlloc(GPTR, dwSize)))
        {
#ifdef _DEBUG
            memset(lpInfo, UNINIT_BYTE, dwSize);
#endif
            // Enumerate
            bRC = EnumPrinters(dwType, lpszName, dwLevel, lpInfo, dwSize, &dwNeeded, &dwPrinters);
            if (!bRC)
            {
                dwErrorCode = GetLastError();

                // If anything other than allocate size error, flag and exit.
                if (dwErrorCode != ERROR_INSUFFICIENT_BUFFER)
                {
                    return((DWORD)-1);
                }
                else
                {
                    GlobalFree(lpInfo);
                    lpInfo = NULL;
                    dwSize = dwNeeded;
                }
            } // if (!bRC)
            else  // EnumPrinters returned success
            {
                *lpvPrinterInfo = lpInfo;  // Save pointer to PRINTER_INFO structure
            }
        }
        else
        {
            return((DWORD)-1);
        }
    }
    return(dwPrinters);
}  // End of function SPL_EnumPrinters

////////////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  PrintDIB(HANDLE hDIB, HDC hDC, int x, int y, int dx, int dy)
//
//  PURPOSE:  Set the DIB bits to the printer DC.
//
////////////////////////////////////////////////////////////////////////////
BOOL PrintDIB (HANDLE hDIB, HDC hDC, int xOrigin, int yOrigin, int xSize, int ySize, BOOL bStretch)
{
    int                 iBits;
    HCURSOR             hCurSave;
    LPBITMAPINFOHEADER  lpDIB;
    LPBITMAPINFOHEADER  lpbi;

    // Initailize variables
    START_WAIT_CURSOR(hCurSave);  // put up busy cursor

    lpDIB = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // If size > BITMAPINFOHEADER header then
    // need to convert to BITMAPINFOHEADER.
    lpbi = lpDIB;
#ifdef OSR2
    if (sizeof(BITMAPINFOHEADER) < lpDIB->biSize)
    {
        DWORD dwColorTableSize;
        DWORD dwHeaderDataSize;

        // Allocate Bitmapinfo memory.
        dwHeaderDataSize = sizeof(BITMAPINFOHEADER) + (lpDIB->biCompression == BI_BITFIELDS ? 12 : 0);
        dwColorTableSize = NumColorsInDIB(lpDIB) * sizeof(RGBQUAD);
        lpbi = (LPBITMAPINFOHEADER) GlobalAlloc(GPTR, dwHeaderDataSize + dwColorTableSize);
        if (NULL == lpbi)
        {
            return FALSE;
        }

        // Convert header data into bitmapinfo header.
        memcpy(lpbi, lpDIB, dwHeaderDataSize);
        lpbi->biSize = sizeof(BITMAPINFOHEADER);

        // Copy color table if any.
        if (0 != dwColorTableSize)
            memcpy((LPBYTE)lpbi + dwHeaderDataSize, (LPBYTE)lpDIB + lpDIB->biSize, dwColorTableSize);
    }
#endif

    if (bStretch)
    {
        iBits = StretchDIBits(hDC,
                              xOrigin, yOrigin,
                              xSize, ySize,
                              0, 0,
                              BITMAPWIDTH(lpbi), abs(BITMAPHEIGHT(lpbi)),
                              FindDIBBits(lpDIB),
                              (LPBITMAPINFO)lpbi,
                              DIB_RGB_COLORS,
                              SRCCOPY);
    }
    else
    {
        iBits  =   SetDIBitsToDevice (hDC,                                  // hDC
                                      xOrigin,                              // DestX
                                      yOrigin,                              // DestY
                                      BITMAPWIDTH(lpbi),                    // nDestWidth
                                      abs(BITMAPHEIGHT(lpbi)),              // nDestHeight
                                      0,                                    // SrcX
                                      0,                                    // SrcY
                                      0,                                    // nStartScan
                                      abs(BITMAPHEIGHT(lpbi)),              // nNumScans
                                      FindDIBBits(lpDIB),
                                      (LPBITMAPINFO) lpbi,                  // lpBitsInfo
                                      DIB_RGB_COLORS);                      // wUsage
    }
    END_WAIT_CURSOR(hCurSave);   // restore cursor
    if (lpbi != lpDIB)
    {
        GlobalFree((HANDLE)lpbi);
    }
    GlobalUnlock(hDIB);
    return((iBits != 0) && (iBits != GDI_ERROR));
}

//////////////////////////////////////////////////////////////////////////
//  Function:  PrintImage
//
//  Description:
//    Prints the image to the printer specified within the DIBINFO structure.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL PrintImage(HWND hWnd)
{
    // Local variables
    LPBITMAPINFOHEADER lpBi;
    int               xSize, ySize, xRes, yRes, dx, dy;
    LPDIBINFO         lpDIBInfo;
    HDC               hDC;
    HANDLE            hDIB;
    int               iICMMode, iPrevICMMode;
    int               iXOrigin, iYOrigin;
    BOOL              bRC;                    // general return code
    BOOL              bStretch;     // TRUE if to use StretchDIBits,
                                    // FALSE if to use SetDIBitsToDevice
    TCHAR             stPrintMsg[MAX_PATH*2];
    HANDLE            hDIBPrinter;
    DWORD             dwLCSIntent;

    //  Initialize variables
    hDIBPrinter = NULL;
    lpDIBInfo = GetDIBInfoPtr(hWnd);
    if (NULL == lpDIBInfo)
    {
        SetLastError(ERROR_INVALID_DATA);;
        return(FALSE);
    }
    hDIB = lpDIBInfo->hDIB;
    if (!ConvertIntent(lpDIBInfo->dwRenderIntent, ICC_TO_LCS, &dwLCSIntent))
    {
        ErrMsg(hWnd, __TEXT("Invalid Intent.  Aborting Print process"));
    }

    if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20)
        && (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ENABLE_ICM)))
    {
        // ICM On, using outside DC, a.k.a. ICM20
        DebugMsg(__TEXT("PrintImage:  Outside DC, ICM Enabled\r\n"));

        // Create transform of the original bits to the printer
        ASSERT(NULL != lpDIBInfo->lpszPrinterProfile);
        hDIBPrinter  = TransformDIBOutsideDC(lpDIBInfo->hDIB,
                                             lpDIBInfo->bmFormat,
                                             lpDIBInfo->lpszPrinterProfile,
                                             NULL,
                                             USE_BITMAP_INTENT, NULL, 0);
        if (NULL == hDIBPrinter)
        {
            ErrMsg(hWnd, __TEXT("PrintImage:  Unable to transform DIB"));
            return(FALSE);
        }
        else // Transform worked
        {
            hDIB = hDIBPrinter;
        }
    }
    ASSERT(NULL != hDIB);
    lpBi = GlobalLock(hDIB);

    // Initialize printing
    hDC = InitPrinting(hWnd, lpDIBInfo->lpszPrinterName, lpDIBInfo->pDevMode);
    if (NULL != hDC)
    {
        // Get addresses of dialog procs
        glpfnPrintDlgProc = (FARPROC)&PrintDlgProc;
        glpfnAbortProc = (FARPROC)&AbortProc;

        // Create the printing dialog
        ghDlgPrint = CreateDialog(ghInst, MAKEINTRESOURCE(IDD_PRINTING), ghWndParent, (DLGPROC)glpfnPrintDlgProc);

        EnableWindow( ghWndParent, FALSE);  // Disable Parent
        CenterWindow(ghDlgPrint, ghWndParent);
        wsprintf(stPrintMsg, __TEXT("Printing image\r\n\r\n%s\r\n\r\nto\r\n\r\n%s"), lpDIBInfo->lpszImageFileName, lpDIBInfo->lpszPrinterName);
        SetWindowText(GetDlgItem(ghDlgPrint, IDC_PRINT_FILENAME), (LPTSTR)stPrintMsg);
        ShowWindow(ghDlgPrint, SW_SHOW);

        // Set the abort procedure
        if (SetAbortProc(hDC, (ABORTPROC)glpfnAbortProc) == SP_ERROR)
        {
            ErrMsg(hWnd, __TEXT("InitPrinting:  SetAbortProc FAILED, LastError = %ld"), GetLastError());
        }

        // Use the printable region of the printer to determine
        // the margins.
        iXOrigin = GetDeviceCaps(hDC, PHYSICALOFFSETX);
        iYOrigin = GetDeviceCaps(hDC, PHYSICALOFFSETY);

        // Obtain info about printer resolution
        xSize = GetDeviceCaps(hDC, HORZRES);
        ySize = GetDeviceCaps(hDC, VERTRES);
        xRes  = GetDeviceCaps(hDC, LOGPIXELSX);
        yRes  = GetDeviceCaps(hDC, LOGPIXELSY);

        // Stretch to best fit, if necessary.  Maintain the same aspect ratio.
        if (CHECK_DWFLAG(lpDIBInfo->dwPrintOption,ICMV_PRINT_BESTFIT))
        {
            bStretch = TRUE;
            dy = ySize - (yRes/2);
            dx = ((int)((long)dy * BITMAPWIDTH(lpBi)/abs(BITMAPHEIGHT(lpBi)) ));

            // Make sure the image still fits.
            if (dx > xSize)
            {
                dx = xSize - xRes/2;
                dy = ((int)((long)dx * abs(BITMAPHEIGHT(lpBi))/BITMAPWIDTH(lpBi)));
            }
        }
        else // Actual size
        {
            bStretch = FALSE;
            dx = BITMAPWIDTH(lpBi);
            dy = abs(BITMAPHEIGHT(lpBi));
        }

        // Set ICM mode according to properties set in ICMINFO structure
        iICMMode = ICM_OFF;
        if (CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ENABLE_ICM))
        {
            if (!CHECK_DWFLAG(lpDIBInfo->dwICMFlags, ICMVFLAGS_ICM20))
            {
                TCHAR   stProfile[MAX_PATH];

                // ICM enabled, using ICM10--inside DC

                iICMMode = ICM_ON;
                wsprintf(stProfile, __TEXT("%s\\%s"), gstProfilesDir, lpDIBInfo->lpszPrinterProfile);
                bRC = SetICMProfile(hDC, (LPTSTR)stProfile);
                DebugMsg(__TEXT("PrintImage:  Inside DC using profile \"%s\".\r\n"), stProfile);
                if (!bRC)
                {
                    DebugMsg(__TEXT("Print.C, PrintImage:  SetICMProfile() FAILED!!\r\n"));
                }
            }
            else
            {
                iICMMode = ICM_DONE_OUTSIDEDC;
            }
        }
        iPrevICMMode = SetICMMode(hDC, iICMMode);  // Explicitly set ICMMode--don't count on any behavior
        if (0 == iPrevICMMode)
        {
            DebugMsg(__TEXT("PRINT.C : PrintImage : SetICMMode(%d) FAILED \r\n"), iICMMode);
        }
        PrintDIB(hDIB, hDC, iXOrigin, iYOrigin, dx, dy, bStretch);

        // Terminate printing and delete the printer DC
        TermPrinting(hDC);
        DeleteDC(hDC);
    }

    // Delete DIB transform for printer if necessary
    GlobalUnlock(hDIB);
    if (NULL != hDIBPrinter)
    {
        GlobalFree(hDIBPrinter);
    }

    GlobalUnlock(GlobalHandle(lpDIBInfo));

    return TRUE;
}   // End of function PrintImage

// //////////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  PrintDlgProc (hWnd, unsigned , WORD , DWORD )
//
//  PURPOSE:  Dialog function for the "Cancel Printing" dialog. It sets
//              the abort flag if the user presses <Cancel>.
//
// //////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL PrintDlgProc (HWND hDlg, unsigned iMessage, WORD wParam, DWORD lParam)
{

    lParam = lParam;  // Eliminates 'unused formal parameter' warning
    wParam = wParam;  // Eliminates 'unused formal parameter' warning

    switch (iMessage)
    {
        case WM_INITDIALOG:
            EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE, MF_GRAYED);
            break;

        case WM_COMMAND:
            gbUserAbort = TRUE;
            EnableWindow (ghWndParent, TRUE);
            DestroyWindow (hDlg);
            ghDlgPrint = 0;
            break;

        default:
            return(FALSE);
    }
    return(TRUE);
}

// //////////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  AbortProc (HDC hPrnDC, short nCode)
//
//  PURPOSE:  Checks message queue for messages from the "Cancel Printing"
//              dialog. If it sees a message, (this will be from a print
//              cancel command), it terminates.
//
//  RETURNS:  Inverse of Abort flag
//
// //////////////////////////////////////////////////////////////////////////
BOOL FAR PASCAL AbortProc (HDC hPrnDC, short nCode)
{
    MSG   msg;

    nCode = nCode;  // Eliminates 'unused formal paramater' warning
    hPrnDC = hPrnDC;  // Eliminates 'unused formal paramater' warning
    while (!gbUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (!ghDlgPrint || !IsDialogMessage(ghDlgPrint, &msg))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }
    return(!gbUserAbort);
}

//////////////////////////////////////////////////////////////////////////
//  Function:  InitPrinting
//
//  Description:
//    Sets up print job.
//
//  Parameters:
//    @@@
//
//  Returns:
//    HDC PASCAL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
static HDC PASCAL InitPrinting(HWND hWnd, LPTSTR lpszFriendlyName, PDEVMODE pDevMode)
{
    // Local variables
    HDC       hDC;
    DOCINFO   diDocInfo;      // Document infor for StartDoc call
    BOOL      bRetVal;

    //  Initialize variables
    hDC = NULL;
    bRetVal = TRUE;
    gbUserAbort = FALSE;
    ghWndParent = hWnd;
    SetLastError(0);

    hDC = GetPrinterDC(lpszFriendlyName, pDevMode);
    if (hDC == NULL)
    {
        DebugMsg(__TEXT("InitPrinting : GetPrinterDC returned NULL\r\n"));
        return(NULL);
    }

    // Fill in the DOCINFO structure
    diDocInfo.cbSize = sizeof(DOCINFO);
    diDocInfo.lpszDocName = lpszFriendlyName;
    diDocInfo.lpszOutput = NULL;
    diDocInfo.lpszDatatype = NULL;
    diDocInfo.fwType = 0;

    // Start the document
    if (StartDoc(hDC, &diDocInfo)== SP_ERROR)
    {
        ErrMsg(hWnd, __TEXT("InitPrinting:  StartDoc FAILED"));
        bRetVal = FALSE;
        goto exit;
    }

    // Start the page
    if (StartPage(hDC) == SP_ERROR)
    {
        DISPLAY_LASTERROR(LASTERROR_NOALLOC,GetLastError());
        ErrMsg(hWnd, __TEXT("InitPrinting:  StartPage FAILED"));
        AbortDoc(hDC);
        bRetVal = FALSE;
    }

    exit:
    if (bRetVal == FALSE)
    {
        EnableWindow(   ghWndParent,    TRUE);
        DestroyWindow(ghDlgPrint);
    }
    return(hDC);
}   // End of function InitPrinting

//////////////////////////////////////////////////////////////////////////
//  Function:  TermPrinting
//
//  Description:
//    Terminates the print job.
//
//  Parameters:
//    @@@
//
//  Returns:
//    void PASCAL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
static void PASCAL TermPrinting(HDC hDC)
{
    // Local variables
    if (EndPage(hDC) == SP_ERROR)
    {
        ErrMsg(NULL, __TEXT("TermPrinting:  EndPage FAILED"));
    }
    if (EndDoc(hDC) == SP_ERROR)
    {
        ErrMsg(NULL, __TEXT("TermPrinting:  EndDoc FAILED"));
    }

    // Dstroy the dialog
    EnableWindow(ghWndParent, TRUE);
    DestroyWindow(ghDlgPrint);
}   // End of function TermPrinting

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDefaultPrinterDC
//
//  Description:
//    Returns a DC for the default printer.
//
//  Parameters:
//    @@@
//
//  Returns:
//    HDC
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
HDC GetDefaultPrinterDC()
{
    HDC     hDC;
    LPTSTR  lpszPrinterName;

    //  Initialize variables
    hDC = NULL;

    lpszPrinterName = GetDefaultPrinterName();
    if (lpszPrinterName != NULL)
    {
        hDC = GetPrinterDC(lpszPrinterName, NULL);
        GlobalFree(lpszPrinterName);
    }
    else
    {
        DebugMsg(__TEXT("GetDefaultPrinterDC:  Could not obtain default printer name.\r\n"));
    }

    return hDC;
}   // End of function GetDefaultPrinterDC

//////////////////////////////////////////////////////////////////////////
//  Function:  GetDefaultPrinterDevMode
//
//  Description:
//    Returns a printer's default devmode.
//
//  Parameters:
//    @@@
//
//  Returns:
//    PDEVMODE
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
PDEVMODE GetDefaultPrinterDevMode(LPTSTR lpszPrinterName)
{
    LONG        lDevModeSize;
    HANDLE      hDevMode;
    PDEVMODE    pDevMode = NULL;

    lDevModeSize = DocumentProperties(NULL, NULL, lpszPrinterName, NULL, NULL, 0);
    if (lDevModeSize > 0)
    {
        hDevMode = GlobalAlloc(GHND, lDevModeSize);
        pDevMode = (PDEVMODE) GlobalLock(hDevMode);
        DocumentProperties(NULL, NULL, lpszPrinterName, pDevMode, NULL, DM_OUT_BUFFER);
    }
    else
    {
        DebugMsg(__TEXT("GetDefaultPrinterDevMode:  Could not obtain printer's devmode.\r\n"));
    }

    return pDevMode;
}


PRINT.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    PRINT.H
//
//  PURPOSE:
//    Include file for PRINT.C
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros
#define ENUM_ERROR         0x80000000
#define ERROR_ENUMPRINTERS 0x80000001

// General STRUCTS && TYPEDEFS

// Function prototypes
HDC   SelectPrinter(HWND hWnd);
LPTSTR GetDefaultPrinterName(void);
DWORD PopulatePrinterCombobox(HWND hDlg, int iControlId, LPTSTR lpszCurrentPrinter);
HDC   GetPrinterDC(LPTSTR lpszFriendlyName, PDEVMODE pDevMode);
BOOL  PrintImage(HWND HWnd);
HDC   GetDefaultPrinterDC();
PDEVMODE GetDefaultPrinterDevMode(LPTSTR lpszPrinterName);


APPINIT.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1996  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    INIT.C
//
//  PURPOSE:
//    Initializes ICMVIEW.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>
#include <commctrl.h>
#include "icm.h"

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings

// C RunTime Header Files
#include <stdlib.h>
#include <tchar.h>

// Local Header Files
#include "AppInit.h"
#include "icmview.h"
#include "child.h"
#include "DibInfo.H"
#include "debug.h"
#include "resource.h"

#define RECENT_MENU         0
#define RECENT_POSITION     11

// local definitions
BOOL RegisterICMViewClasses(HINSTANCE);
BOOL CreateGlobalDIBInfo(void);
BOOL GetSettings(LPRECT, LPDWORD, HANDLE);
BOOL UpdateRecentFiles(HWND, HANDLE);
int RegisterCMMProc(LPCTSTR lpszFileName);

// default settings

// external functions

// external data

// public data

// private data

//
// Public functions
//

//////////////////////////////////////////////////////////////////////////
//  Function:  InitApplication
//
//  Description:
//    Initializes window data and registers window class.
//
//  Parameters:
//    HINSTANCE   Instance handle.
//
//  Returns:
//    BOOL    Success indicator
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL  InitApplication(HINSTANCE hInstance)
{
    //  Initialize variables
    SetLastError(0);

    return RegisterICMViewClasses(hInstance);
}   // End of function InitApplication

//////////////////////////////////////////////////////////////////////////
//  Function:  InitInstance
//
//  Description:
//    Initializes this instance and saves an instance handle.
//
//  Parameters:
//    HINSTANCE Instance handle
//    int       Integer identifying initial display mode@@@
//
//  Returns:
//    BOOL    Success indicator.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    // Local variables
    HDC       hDisplay = GetDC(NULL);
    int       nScreenWidth = GetDeviceCaps(hDisplay, HORZRES);
    int       nScreenHeight = GetDeviceCaps(hDisplay, VERTRES);
    HWND      hWnd;
    RECT      rCoord;
    DWORD     dwFlags = 0L;
    HANDLE    hRecent = NULL;
    DWORD     dwPathSize;

    // No longer need display DC.
    if (hDisplay)
        ReleaseDC(NULL, hDisplay);

    //  Initialize variables
    ghInst = hInstance; // Store instance handle in our global variable

    // Get stored coordinates from last run.
    GetSettings(&rCoord, &dwFlags, &hRecent);
    if ( ((rCoord.left + rCoord.right)/2 < 0)
         ||
         ((rCoord.left + rCoord.right)/2 > nScreenWidth)
         ||
         ((rCoord.top + rCoord.bottom)/2 < 0)
         ||
         ((rCoord.top + rCoord.bottom)/2 > nScreenHeight)
         ||
         ( (double)(rCoord.right - rCoord.left)/(double)(nScreenHeight) * 1.1
           >= (double)(nScreenWidth)/(double)(rCoord.bottom - rCoord.top) )
       )
    {
        SetRect(&rCoord, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
    }
    else
    {
        // Set right and bottom to width and height.
        rCoord.right -= rCoord.left;
        rCoord.bottom -= rCoord.top;
    }

    // Create the window using stored coordinates.
    _tcscpy(gstTitle, APPNAME);
    hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, APPNAME, gstTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                          rCoord.left, rCoord.top, rCoord.right, rCoord.bottom, NULL, NULL, hInstance, NULL);

    if (!hWnd)  // Couldn't create main window, exit w/FAILURE code
    {
        DebugMsg(__TEXT("InitInstance:  CreateWindow failed.\r\n"));
        return (FALSE);
    }
    ghAppWnd = hWnd;  // Save main application window handle

    // Attach recent files to main window and display them.
    SetProp(hWnd, APP_RECENT, hRecent);
    UpdateRecentFiles(hWnd, hRecent);

    // Store profiles directory
    dwPathSize = MAX_PATH;
    GetColorDirectory(NULL, gstProfilesDir, &dwPathSize);

    if (!CreateGlobalDIBInfo())
    {
        DebugMsg(__TEXT("InitInstance:  CreateGlobalDIBInfo failed\r\n"));
        return(FALSE);
    }
    InitCommonControls();

    // Figure out how the window should be shown.
    if ((SW_NORMAL == nCmdShow) && (IVF_MAXIMIZED & dwFlags))
    {
        nCmdShow = SW_MAXIMIZE;
    }

    // Show window.
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return (TRUE);
}   // End of function InitInstance

//
// Private functions
//

//////////////////////////////////////////////////////////////////////////
//  Function:  RegisterICMViewClasses
//
//  Description:
//    Registers the window class
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//    This function and its usage is only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this
//    function so that the application will get 'well formed' small icons
//    associated with it.
//
//////////////////////////////////////////////////////////////////////////
BOOL RegisterICMViewClasses(HINSTANCE hInstance)
{
    // Local variables
    BOOL          bAppClass, bChildClass;   // Booleans indicate if classes registered successfully
    WNDCLASSEX    wcex;

    //  Initialize variables
    bAppClass = bChildClass = FALSE;
    wcex.style         = 0;
    wcex.lpfnWndProc   = (WNDPROC)WndProc;
    wcex.cbClsExtra    = 0;
    wcex.cbWndExtra    = ICMVIEW_CBWNDEXTRA;
    wcex.hInstance     = hInstance;
    wcex.hIcon         = LoadIcon (hInstance, APPNAME);
    wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

    // Set menu depending upon Operating System
    wcex.lpszMenuName = APPNAME;
    wcex.lpszClassName = APPNAME;

    // Added elements for Windows 95:
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.hIconSm = LoadIcon(wcex.hInstance, __TEXT("SMALL"));

    // Register App Class
    bAppClass = RegisterClassEx(&wcex);

    if (bAppClass)
    {
        // Register the child class
        wcex.style         = CS_SAVEBITS;
        wcex.lpfnWndProc   = ChildWndProc;
        wcex.lpszMenuName  = (LPCTSTR)NULL;
        wcex.cbWndExtra    = CHILD_CBWNDEXTRA;
        wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        wcex.lpszClassName = CHILD_CLASSNAME;
        wcex.hIcon         = NULL;
        bChildClass = RegisterClassEx(&wcex);
    }
    return    (   bAppClass    &&    bChildClass);
}   // End of function RegisterICMViewClasses

//////////////////////////////////////////////////////////////////////////
//  Function:  CreateGlobalDIBInfo
//
//  Description:
//    Creates and initializes the DIBINFO structure for the global ICMVIEW application window.
//
//  Parameters:
//    @@@
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL CreateGlobalDIBInfo(void)
{
    // Local variables
    HGLOBAL     hDIBInfo;
    LPDIBINFO   lpDIBInfo;

    //  Initialize variables
    hDIBInfo = CreateDIBInfo();
    if (NULL == hDIBInfo)
    {
        DebugMsg(__TEXT("CreateGlobalDIBInfo:  CreateDIBInfo failed.\r\n"));
        return(FALSE);
    }
    lpDIBInfo = GlobalLock(hDIBInfo);
    lpDIBInfo->hWndOwner = ghAppWnd;
    GlobalUnlock(hDIBInfo);
    SetWindowLong(ghAppWnd, GWL_DIBINFO, (LONG)hDIBInfo);
    GetDefaultICMInfo();
    return(TRUE);
}   // End of function CreateGlobalDIBInfo

//////////////////////////////////////////////////////////////////////////
//  Function:  GetSettings
//
//  Description:
//    Grabs stored settings from registry.
//
//  Parameters:
//    lpRect    Pointer to rect of window position and size.
//    phRecent  Pointer to handle of an array of most recent files.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL GetSettings(LPRECT lpRect, LPDWORD pdwFlags, LPHANDLE phRecent)
{
    HKEY    hKey;
    LONG    lResult;
    DWORD   dwSize;
    DWORD   dwType;

    // Initialize coordinates and recent file list in case we fail.
    if (NULL != phRecent)
    {
        // Free and re-allocate.
        if (NULL != *phRecent)
        {
            GlobalFree(*phRecent);
        }

        // Allocate string table.
        *phRecent = GlobalAlloc(GHND, (sizeof(LPTSTR) + MAX_PATH * sizeof(TCHAR)) * MAX_RECENT);
    }
    if (NULL != lpRect)
    {
        SetRect(lpRect, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT);
    }

    // Open top level registry to application settings.
    lResult = RegOpenKeyEx(HKEY_CURRENT_USER, APP_REG, 0L, KEY_ALL_ACCESS, &hKey);
    if (ERROR_SUCCESS == lResult)
    {
        // INVARIANT:  Application registry key exists.
        // If lpRect is non-null, attempt to get the stored window coord.
        if (NULL != lpRect)
        {
            dwSize = sizeof(RECT);
            RegQueryValueEx(hKey, APP_COORD, NULL, &dwType, (LPBYTE)lpRect, &dwSize);
        }

        // Get stored state flags.
        dwSize = sizeof(DWORD);
        RegQueryValueEx(hKey, APP_FLAGS, NULL, &dwType, (LPBYTE)pdwFlags, &dwSize);

        // If ppszRecent is non-null, attempt to get the stored recent file list.
        if (NULL != phRecent)
        {
            TCHAR    szReg[32];
            DWORD   dwCount;
            LPTSTR  *ppszRecent;

            // Make sure alloc succeded from above.
            if (NULL == *phRecent)
            {
                RegCloseKey(hKey);
                return FALSE;
            }
            ppszRecent = (LPTSTR*)GlobalLock(*phRecent);

            // Get each of the recent files.
            for (dwCount = 0; dwCount < MAX_RECENT; dwCount++)
            {
                // Build the key name to the nth recent file.
                wsprintf(szReg, __TEXT("%s%d"), APP_RECENT, dwCount);

                // Get the size of the recent file string.
                dwSize = 0;
                lResult = RegQueryValueEx(hKey, szReg, NULL, &dwType, NULL, &dwSize);

                // Alloc the string and attempt to get the recent file.
                if ((ERROR_SUCCESS == lResult) && (0 != dwSize))
                {
                    ASSERT(dwSize <= MAX_PATH);
                    ppszRecent[dwCount] = (LPTSTR) ((LPBYTE)(ppszRecent + MAX_RECENT) + MAX_PATH
                                                    * sizeof(TCHAR) * dwCount);
                    if (NULL != ppszRecent[dwCount])
                    {
                        RegQueryValueEx(hKey, szReg, NULL, &dwType, (LPBYTE)ppszRecent[dwCount], &dwSize);
                    }
                }
            }
        }
        RegCloseKey(hKey);
        GlobalUnlock(*phRecent);
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  SetSettings
//
//  Description:
//    Store settings into registry.
//
//  Parameters:
//    lpRect          Pointer to rect of window position and size.
//    ppszRecent      Pointer to an array of most recent files.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL SetSettings(LPRECT lpRect, DWORD dwFlags, HANDLE hRecent)
{
    HKEY    hKey;
    LONG    lResult;
    DWORD   dwType;
    LPTSTR  *ppszRecent = NULL;

    // Open top level registry to application settings.
    lResult = RegCreateKeyEx(HKEY_CURRENT_USER, APP_REG, 0L, NULL,
                             REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
                             &hKey, &dwType);
    if (ERROR_SUCCESS == lResult)
    {
        // INVARIANT:  Application registry key exists or has been created.
        // If lpRect is non-null, attempt to store the window coord.
        if (NULL != lpRect)
        {
            RegSetValueEx(hKey, APP_COORD, 0, REG_BINARY, (LPBYTE)lpRect, sizeof(RECT));
        }

        // Store ICMView state flags.
        RegSetValueEx(hKey, APP_FLAGS, 0, REG_DWORD, (LPBYTE)&dwFlags, sizeof(dwFlags));

        // If ppszRecent is non-null, attempt to store recent file list.
        if (NULL != hRecent)
        {
            ppszRecent = (LPTSTR*) GlobalLock(hRecent);
        }
        if (NULL != ppszRecent)
        {
            TCHAR    szReg[32];
            DWORD   dwCount;

            // Set each of the recent files.
            for (dwCount = 0; dwCount < MAX_RECENT; dwCount++)
            {
                // Build the key name to the nth recent file.
                wsprintf(szReg, __TEXT("%s%d"), APP_RECENT, dwCount);

                // Set the the recent file string.
                if (NULL != ppszRecent[dwCount])
                {
                    RegSetValueEx(hKey, szReg, 0, REG_SZ, (LPBYTE)ppszRecent[dwCount],
                                  lstrlen(ppszRecent[dwCount]) * sizeof(TCHAR) );
                }
            }
            GlobalUnlock(hRecent);
        }
        RegCloseKey(hKey);
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  AddRecentFile
//
//  Description:
//    Adds and displays recent file strins.
//
//  Parameters:
//    hWnd                    Window that was menu and prop for recent file list.
//    lpszFileName    File name string to add to recent files list.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
BOOL AddRecentFile(HWND hWnd, LPTSTR lpszFileName)
{
    int             nFind;
    int             nCount;
    HANDLE  hRecent;
    LPTSTR  *ppszRecent;

    // Make sure valid paramters.
    if (!IsWindow(hWnd) || (NULL == lpszFileName) ||(lstrlen(lpszFileName) == 0))
    {
        return FALSE;
    }

    // Get Recent file list.
    hRecent = GetProp(hWnd, APP_RECENT);
    if (NULL == hRecent)
    {
        return FALSE;
    }

    // Get pointer to string array.
    ppszRecent = (LPTSTR*) GlobalLock(hRecent);
    ASSERT(ppszRecent != NULL);

    // Search array for an occurence of the string
    // we are adding.
    for (nFind = 0; (nFind < MAX_RECENT) && (NULL != ppszRecent[nFind])
        && lstrcmpi(ppszRecent[nFind], lpszFileName); nFind++);

    // Make sure that nFind element is valid string.
    // nFind - 1 should be non null or not indexed.
    if ( (nFind < MAX_RECENT) && (NULL == ppszRecent[nFind]))
    {
        ppszRecent[nFind] = (LPTSTR)((LPBYTE)(ppszRecent + MAX_RECENT) + MAX_PATH * sizeof(TCHAR) * nFind);
    }

    // Move strings from nFind to zero down one to make room
    // to add string to top of list.
    for (nCount = __min(nFind, MAX_RECENT -1); nCount > 0; nCount--)
        _tcscpy(ppszRecent[nCount], ppszRecent[nCount -1]);

    // Add file to first position.
    _tcscpy(ppszRecent[0], lpszFileName);

    // Unlock array.
    GlobalUnlock(hRecent);

    // Display strings.
    UpdateRecentFiles(hWnd, hRecent);

    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
//  Function:  UpdateRecentFiles
//
//  Description:
//    Displays recent files.
//
//  Parameters:
//
//    hWnd            Window that was menu and prop for recent file list.
//    hRecent         Handle to an array of most recent files.
//
//  Returns:
//    BOOL
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////

BOOL UpdateRecentFiles(HWND hWnd, HANDLE hRecent)
{
    int                    nMenuAdjust;
    int                    nCount;
    TCHAR                  szTemp[MAX_PATH + 4];
    HWND                   hwndActiveChild;
    HMENU                  hMenu;
    LPTSTR                 *ppszRecent;
    MENUITEMINFO          ItemInfo;

    // Validate paramters.
    if ( !IsWindow(hWnd) || (NULL == hRecent) )
    {
        return FALSE;
    }

    // Get current active MDI window and check to see
    // if it is maximized.  Need to add 1 to menu if it
    // is maximized.
    hwndActiveChild = GetCurrentMDIWnd();
    nMenuAdjust = 0;
    if (IsZoomed(hwndActiveChild))
    {
        nMenuAdjust = 1;
    }

    // Get file menu.
    hMenu = GetMenu(hWnd);
    ASSERT(hMenu != NULL);
    hMenu = GetSubMenu(hMenu, RECENT_MENU + nMenuAdjust);
    ASSERT(hMenu != NULL);
    ASSERT(GetMenuItemCount(hMenu) > RECENT_POSITION);

    // Get pointer to recent file array.
    ppszRecent = (LPTSTR*) GlobalLock(hRecent);
    ASSERT(ppszRecent != NULL);

    // Add each string in recent file list to menu.
    // Replace menu items until separator, then insert them.
    for (nCount = 0; nCount < MAX_RECENT; nCount++)
    {
        // Only add strings that are not null or zero length.
        if ( (NULL != ppszRecent[nCount]) && (lstrlen(ppszRecent[nCount]) != 0) )
        {
            // Build recent file menu string.
            wsprintf(szTemp, __TEXT("&%d %s"), nCount +1, ppszRecent[nCount]);

            // Determine if replacing item or inserting.
            memset(&ItemInfo, 0, sizeof(MENUITEMINFO));
            ItemInfo.cbSize = sizeof(MENUITEMINFO);
            ItemInfo.fMask = MIIM_TYPE;
            GetMenuItemInfo(hMenu, RECENT_POSITION + nCount, TRUE, &ItemInfo);
            if (MFT_SEPARATOR == ItemInfo.fType)
            {
                // Insert item. MIIM_ID
                ItemInfo.fMask = MIIM_TYPE | MIIM_ID;
                ItemInfo.wID = IDM_FILE_RECENT + nCount;
                ItemInfo.fType = MFT_STRING;
                ItemInfo.dwTypeData = szTemp;
                ItemInfo.cch = lstrlen(ItemInfo.dwTypeData);
                InsertMenuItem(hMenu, RECENT_POSITION + nCount, TRUE, &ItemInfo);
            }
            else
            {
                // Replace menu item.
                ItemInfo.fMask = MIIM_TYPE | MIIM_STATE;
                ItemInfo.fState = MFS_ENABLED;
                ItemInfo.fType = MFT_STRING;
                ItemInfo.dwTypeData = szTemp;
                ItemInfo.cch = lstrlen(ItemInfo.dwTypeData);
                SetMenuItemInfo(hMenu, RECENT_POSITION + nCount, TRUE, &ItemInfo);
            }
        }
    }

    // Unlock recent array.
    GlobalUnlock(hRecent);
    return TRUE;
}


REGUTIL.C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    REGUTIL.C
//
//  PURPOSE:
//    Registry access functions.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// Windows Header Files:
#pragma warning(disable:4001)   // Single-line comment warnings
#pragma warning(disable:4115)   // Named type definition in parentheses
#pragma warning(disable:4201)   // Nameless struct/union warning
#pragma warning(disable:4214)   // Bit field types other than int warnings
#pragma warning(disable:4514)   // Unreferenced inline function has been removed

// Windows Header Files:
#include <Windows.h>
#include <WindowsX.h>

// Restore the warnings--leave the single-line comment warning OFF
#pragma warning(default:4115)   // Named type definition in parentheses
#pragma warning(default:4201)   // Nameless struct/union warning
#pragma warning(default:4214)   // Bit field types other than int warnings

// C RunTime Header Files

// Local Header Files

// local definitions

// default settings

// external functions

// external data

// public data

// private data

// public functions

//////////////////////////////////////////////////////////////////////////
//  Function:  GetRegistryString
//
//  Description:
//    Retrieves the string associated with the specified key in the registry.
//
//  Parameters:
//    @@@
//
//  Returns:
//    LPTSTR   Pointer to registry string.  NULL upon failure.
//
//  Comments:
//
//
//////////////////////////////////////////////////////////////////////////
LPTSTR GetRegistryString(HKEY hKeyClass, LPTSTR lpszSubKey, LPTSTR lpszValueName)
{
    // Local variables
    HKEY          hKey;                 // Registry key
    LPTSTR         lpszKeyValue;         // Buffer for key name
    DWORD         dwKeySize;            // Size of key value
    DWORD         dwKeyDataType;        // Type of data stored in key
    LONG          lRC;                  // Return code

    //  Initialize variables
    dwKeyDataType = 0;
    dwKeySize = 0;
    hKey = NULL;

    lRC = RegOpenKey(hKeyClass, lpszSubKey, &hKey);
    if (lRC != ERROR_SUCCESS)
    {
        return(NULL);
    }

    // Got key, get value.  First, get the size of the key.
    lRC = RegQueryValueEx(hKey, lpszValueName, NULL, NULL, NULL, &dwKeySize);
    if (lRC != ERROR_SUCCESS)
    {
        return(NULL);
    }
    if (dwKeySize <= 1)  // Registry will return "" if no printers installed
    {
        return(NULL);
    }

    lpszKeyValue = GlobalAlloc(GPTR, (++dwKeySize));
    if (lpszKeyValue == NULL)
    {
        return(NULL);
    }

    lRC = RegQueryValueEx(hKey, lpszValueName, NULL, &dwKeyDataType, (LPBYTE)lpszKeyValue, &dwKeySize);
    return(lpszKeyValue);
}   // End of function GetRegistryString


REGUTIL.H

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright  1994-1997  Microsoft Corporation.  All Rights Reserved.
//
//  FILE:
//    REGUTIL.H
//
//  PURPOSE:
//    Registry utility functions.
//
//  PLATFORMS:
//    Windows 95, Windows NT
//
//  SPECIAL INSTRUCTIONS: N/A
//

// General pre-processor macros

// General STRUCTS && TYPEDEFS

// Function prototypes
LPTSTR GetRegistryString(HKEY hKeyClass, LPTSTR lpszSubKey, LPTSTR lpszValueName);


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by ICMView.rc
//
#define IDM_FILE_CLOSE                  100
#define IDM_FILE_EXIT                   101
#define IDM_FILE_OPEN                   102
#define IDM_FILE_PRINT                  104
#define IDM_FILE_RECENT                 105
#define IDM_FILE_RECENT1                106
#define IDM_FILE_RECENT2                107
#define IDM_FILE_RECENT3                108
#define IDM_HELP_ABOUT                  109
#define IDM_HELPCONTENTS                110
#define IDM_HELPTOPICS                  111
#define IDD_TRANSLATE                   111
#define IDM_WINDOW_ARRANGE              119
#define IDM_WINDOW_CASCADE              120
#define IDM_WINDOW_TILE_HORIZONTAL      121
#define IDM_WINDOW_TILE_VERTICAL        122
#define DLG_VERFIRST                    400
#define IDC_COMPANY                     400
#define IDC_FILEDESC                    401
#define IDC_PRODVER                     402
#define IDC_COPYRIGHT                   403
#define IDC_OSVERSION                   404
#define IDC_TRADEMARK                   405
#define DLG_VERLAST                     406
#define IDC_LABEL                       406
#define IDD_DISPLAY                     1000
#define IDC_DISPLAY_STRETCH             1001
#define IDC_DISPLAY_ANDSCAN             1002
#define IDC_DISPLAY_DELETESCAN          1003
#define IDC_DISPLAY_ORSCAN              1004
#define IDC_PRINT_IMAGE                 1053
#define IDC_PROGRESS                    1054
#define IDD_PRINT                       2000
#define IDC_PRINT_FILENAME              2001
#define IDC_PRINT_PRINTERLIST           2002
#define IDC_PRINT_ACTUALSIZE            2003
#define IDC_PRINT_BESTFIT               2004
#define IDD_PRINTING                    3000
#define IDS_BUFFERTOOSMALL              10000
#define IDS_CREATEICFAILURE             10001
#define IDS_DIALOGFAILURE               10002
#define IDS_DNDMMISMATCH                10003
#define IDS_FILTERSTRING                10004
#define IDS_FINDRESFAILURE              10005
#define IDS_GETDEVMODEFAIL              10006
#define IDS_INITFAILURE                 10007
#define IDS_INITIALIZATION              10008
#define IDS_INVALIDFILENAME             10009
#define IDS_LOADDRVFAILURE              10010
#define IDS_LOADRESFAILURE              10011
#define IDS_LOADSTRFAILURE              10012
#define IDS_LOCKRESFAILURE              10013
#define IDS_MEMALLOCFAILURE             10014
#define IDS_MEMLOCKFAILURE              10015
#define IDS_NODEFAULTPRN                10016
#define IDS_NODEVICES                   10017
#define IDS_NOFONTS                     10018
#define IDS_NOHINSTANCE                 10019
#define IDS_NOHOOK                      10020
#define IDS_NOTEMPLATE                  10021
#define IDS_PARSEFAILURE                10022
#define IDS_PRINTERNOTFOUND             10023
#define IDS_RETDEFFAILURE               10024
#define IDS_SETUPFAILURE                10025
#define IDS_STRUCTSIZE                  10026
#define IDS_SUBCLASSFAILURE             10027
#define IDS_UNKNOWNERROR                10028
#define ID_TEST                         40038
#define IDM_FILE_ICM10                  40056
#define IDM_FILE_ICM20                  40057
#define IDM_FILE_CONFIGURE_ICM          40060
#define IDM_FILE_DISPLAY                40061
#define ID_FILE_SAVEAS                  40065
#define IDM_FILE_PRINT_SETUP            40066
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
        #define _APS_NO_MFC                     1
        #define _APS_NEXT_RESOURCE_VALUE        112
        #define _APS_NEXT_COMMAND_VALUE         40067
        #define _APS_NEXT_CONTROL_VALUE         1055
        #define _APS_NEXT_SYMED_VALUE           105
    #endif
#endif


README.TXT

Sample joystick application.


JOYTOY.C

/* joytoy.c - WinMain() and WndProc() for JOYTOY, along with
 *      initialization and support code.
 *
 * JOYTOY is a Windows with Multimedia application that illustrates
 *  how to use the joystick services. When run, it presents the user
 *  with a crosshair cursor. When the joystick or the mouse is moved,
 *  the cursor follows. When a joystick button is pressed, JOYTOY
 *  produces a sound and draws a bullet hole icon onto the screen.
 *
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 *  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
 *  A PARTICULAR PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
 */

#include <windows.h>
#include <mmsystem.h>
#include "joytoy.h"

char szAppName[] = "JoyToy";
HANDLE hSound1, hSound2;
HANDLE hHole;
LPSTR lpSound1, lpSound2;

/* WinMain - Entry point for JOYTOY.
 */
int PASCAL WinMain(hInstance,hPrevInstance,lpszCmdLine,cmdShow)
HINSTANCE hInstance,hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
    MSG msg;

    /* Initialize the application.
     */
    if(! InitJoyToy(hInstance,hPrevInstance))
        return FALSE;

    /* Standard Windows message processing loop.  We don't drop out of
     * this loop until the user quits the application.
     */
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    /* Unlock and free resources allocated by InitJoyToy().
     */
    UnlockResource(hSound1);
    UnlockResource(hSound2);
    FreeResource(hSound1);
    FreeResource(hSound2);
    FreeResource(hHole);

    return (msg.wParam);
}

/* DrawSight - Takes the new joystick position and moves the
 *  mouse cursor accordingly.
 *
 * Params:  lParam - Specifies the new joystick position. The
 *  high-order word is the y position and the low-order word is
 *  the x position.
 *
 * Returns: void
 */
void DrawSight(DWORD lParam)
{
    WORD x, y;
    POINT pt;

    /* Get the current cursor position in screen coordinates.
     */
    GetCursorPos(&pt);

    /* Joystick positions are expressed in a coordinate system with the
     * origin in the upper left corner and with coordinate ranges from
     * 0 to 65535. Take the 5 most significant bits, so the position is
     * expressed in the range of 0 to 31.
     */
    x = LOWORD(lParam) >> 11;
    y = HIWORD(lParam) >> 11;

    /* If the joystick is to the left of the center position, then move
     * the cursor position to the left. Otherwise, if the joystick is to
     * the right of the center position, then move the cursor position
     * to the right.
     */
    if(x <= 12)
        pt.x = pt.x + x - 17;
    else if(x >= 20)
        pt.x = pt.x + x - 15;

    /* If the joystick is below the center position, then move the cursor
     * position down. Otherwise, if the joystick is above the center
     * position, then move the cursor position up.
     */
    if(y <= 12)
        pt.y = pt.y + y - 17;
    else if(y >= 20)
        pt.y = pt.y + y - 15;

    /* Set the new cursor position.
     */
    SetCursorPos(pt.x, pt.y);
}

/* DrawHole - Draws an icon representing a hole in the given window at the
 * current cursor position.
 *
 * Params:  hWnd - Specifies the handle to the window to draw in.
 *
 * Returns: void
 */
void DrawHole(HWND hWnd)
{
    HDC hDC;
    POINT pt;

    /* Get the current cursor position.
     */
    GetCursorPos(&pt);

    /* Get a DC, draw the icon, release DC.
     */
    hDC = GetDC(hWnd);
    DrawIcon(hDC, pt.x - 16, pt.y - 16, hHole);
    ReleaseDC(hWnd, hDC);
}

/* WndProc - Main window procedure function.
 */
LRESULT FAR PASCAL WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_CREATE:
            /* Capture the joystick. If this fails, beep and display
             * error, then quit.
             */
            if(joySetCapture(hWnd, JOYSTICKID1, 0, FALSE))
            {
                MessageBeep(MB_ICONEXCLAMATION);
                MessageBox(hWnd, "Couldn't capture the joystick", NULL,
                           MB_OK | MB_ICONEXCLAMATION);
                return -1;
            }
            break;

        case WM_ACTIVATE:
        case WM_ACTIVATEAPP:
            /* If app becomes inactive (wParam == 0), then we want to quit.
             */
            if(wParam)
                break;

        case WM_LBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        case WM_KEYDOWN:
        case WM_CHAR:
            /* Also, any keystrokes or mouse buttons cause us to quit.
             */
            PostMessage(hWnd,WM_CLOSE,0,0L);
            break;

        case WM_ERASEBKGND:
            /* Process this message to keep Windows from erasing background.
             */
            return 0l;

        case MM_JOY1BUTTONDOWN :
            /* Joystick button pressed. Detect which button, play appropriate
             * sound, then draw bullet hole. To play sound, pass
             * sndPlaySound() a pointer to an in-memory WAVE file and
             * specify the SND_MEMORY flag. Specify SND_LOOP and SND_ASYNC
             * so that sound keeps playing until it's turned off after
             * joystick button is released.
             */
            if (wParam & JOY_BUTTON1CHG)
            {
                sndPlaySound(lpSound1, SND_LOOP | SND_ASYNC | SND_MEMORY);
                DrawHole(hWnd);
            }
            else if (wParam & JOY_BUTTON2CHG)
            {
                sndPlaySound(lpSound2, SND_LOOP | SND_ASYNC | SND_MEMORY);
                DrawHole(hWnd);
            }
            break;

        case MM_JOY1BUTTONUP :
            /* Stop playing looped sound.
             */
            if (wParam & JOY_BUTTON1)
                sndPlaySound(lpSound1, SND_LOOP | SND_ASYNC | SND_MEMORY);
            else if (wParam & JOY_BUTTON2)
                sndPlaySound(lpSound2, SND_LOOP | SND_ASYNC | SND_MEMORY);
            else
                sndPlaySound(NULL, 0);
            break;

        case MM_JOY1MOVE :
            /* If joystick buttons are pressed, draw bullet holes
             * while joystick is moved.
             */
            if(wParam & (JOY_BUTTON1 | JOY_BUTTON2))
                DrawHole(hWnd);

            /* Redraw the crosshair in its new position.
             */
            DrawSight(lParam);
            break;

        case WM_DESTROY:
            /* We're shutting down. Release capture on the joystick,
             * make sure any sounds that are playing are stopped.
             */
            joyReleaseCapture(JOYSTICKID1);
            sndPlaySound(NULL, 0);
            PostQuitMessage(0);
            break;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

/* InitJoyToy - Application initialization routine.
 *
 * Params:  hInstance - App's instance handle.
 *
 * Returns: TRUE if initialization is successful, FALSE otherwise.
 */
BOOL InitJoyToy(HANDLE hInstance, HANDLE hPrevInstance)
{
    WNDCLASS wc;
    HWND     hWnd;

    /* Make sure there is a joystick device installed.
     */
    if (!joyGetNumDevs())
    {
        MessageBox(NULL, "There are no joystick devices installed. Exiting.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }

    /* Load bullet hole icon.
     */
    hHole = LoadIcon(hInstance, "HoleIcon");

    if (hPrevInstance == NULL) {
        /* Setup and register a window class for our main window.
         */
        wc.hCursor          = LoadCursor(NULL, IDC_CROSS);
        wc.hIcon            = NULL;
        wc.lpszMenuName     = NULL;
        wc.lpszClassName    = szAppName;
        wc.hbrBackground    = GetStockObject(BLACK_BRUSH);
        wc.hInstance        = hInstance;
        wc.style            = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc      = WndProc;
        wc.cbClsExtra       = 0;
        wc.cbWndExtra       = 0;

        if(! RegisterClass(&wc))
            return FALSE;
    }

    /* Create a full-screen window with no title bar or scroll bars.
     */
    hWnd = CreateWindow(szAppName, szAppName, WS_POPUP | WS_VISIBLE, 0, 0,
        GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
        NULL, NULL, hInstance, NULL);

    if (hWnd != NULL) {
        /* Load and lock sound resources.
         */
        hSound1 = LoadResource(hInstance, FindResource(hInstance, "SOUND1", "WAVE"));
        hSound2 = LoadResource(hInstance, FindResource(hInstance, "SOUND2", "WAVE"));
        lpSound1 = LockResource(hSound1);
        lpSound2 = LockResource(hSound2);
    }
    return hWnd != NULL;
}


JOYTOY.H

/*  joytoy.h - Header file for JOYTOY sample application.
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 *  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
 *  A PARTICULAR PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
 */

/* Function prototypes.
 */
LRESULT FAR PASCAL WndProc(HWND, UINT, WPARAM, LPARAM ) ;
BOOL InitJoyToy(HANDLE hInstance, HANDLE hPrevInstance);
void DrawSight(DWORD lParam);
void DrawHole(HWND hWnd);


README.TXT

Windows NT OpenGL(R) Release Notes

1.  OpenGL Functionality Notes
2.  Compiler warnings and include files
3.  Redistributable components for Windows 95
4.  Sample Applications

1.  Functionality Notes
-----------------------

The OpenGL API is supported on a variety of graphics hardware; the
software in this release provides support for graphics hardware including
basic emulation on any video adapter that is supported with the operating
system. This release of OpenGL will run on all supported hardware under
Windows NT 4.00 and Windows 95, including VGA and Super VGA 16 color mode
displays, and, for WIndows NT, including all RISC platforms.

The runtime libraries for Windows NT are also available bundled with the
NT workstation and server products.  The runtime libraries for Windows 95
are not bundled with the Windows 95 operating system, but application
developers may freely redistribute from this SDK along with their applications
to other Windows95 systems.  In addition, the Windows 95 libraries have been
bundled with the Windows95 operating system in the OEM system release 2, so
OEM Windows 95 systems shipping later in 1996 will begin appearing with the
OpenGL runtime libraries included.

To achieve good shaded rendering with OpenGL applications, you should use a
color graphics mode with 256 or more colors.  Reasonable shading is
possible for most OpenGL applications with 256 colors.

The Windows NT 4.0 and Windows 95 releases of OpenGL includes new functionality
and performance enhancements.  These include:

1) A complete implementation of OpenGL 1.1.  OpenGL 1.1 contains several
functions, including vertex array, polygon offset, logic ops, and several new
functions for handling textures.  The vertex array and texture calls are
particularly significant, as they may enable order of magnitude performance
improvements in some applications.

2) Overlay planes extensions.  These Microsoft OpenGL extensions permit
applications to manage and render into overlay planes where supported
in the graphics hardware.  This permits applications to bring up dialog boxes
and other UI features without overwriting 3D renderings.

3) Extended metafile support.  Applications may encapsulate OpenGL calls and
data in GDI extended metafiles.  This, together with NT 4.0 print spooler
enhancements for remote metafile rendering, makes it possible to print OpenGL
graphics at high resolution on the print server.  This feature is limited to
the Windows NT release.

4) Performance.  The software renderer has been tuned for this release.
Performance tuning has been carried out for the front end of the OpenGL
pipeline as well as for rendering particular primitives, especially
anti-aliased lines and texturing.  Software rendering is generally 2-4x
faster.

2.  Compiler warnings and include files
---------------------------------------

When developing or porting OpenGL source code for Windows NT, you may
notice certain compiler warning messages related to floating point type
conversions.

Microsoft has recognized "acceptable" type-conversion warnings and has
provided a means to ignore them in the provided OpenGL sample source files.
See the included "glos.h" header file.  When using this file, be certain
to include it at an analogous location in your source file.

3. Redistributable components for Windows 95
--------------------------------------------

The OpenGL redistributable components for Windows 95 are in the
\MSTOOLS\OPENGL95 directory.  Under this are the following subdirectories:

- DLL contains the runtime dynamic-link libraries for OpenGL and GLU.
  We recommend either of two methods for redistributing these libraries
  with your application on Windows 95 (for Windows NT, the libraries are
  bundled with the operating system and should not be redistributed):
  1) In your setup program, install these libraries in the
     application directory along with your application.  This gives you
     greater control over the version of OpenGL that your application
     will link to (an issue if other applications install other versions
     of the library), but also gives you greater responsibility for
     updating your customers' libraries if and when that is required
     to address defects, add functionality, etc.
  2) In your setup program, install these libraries in the windows
     system directory.  If you do this, you should use the Win32 setup
     API call VerInstallFile to help prevent installing an older version
     of the libraries over another application's installation of a more
     recent version of the libraries.

- MGA contains the binaries for Matrox MGA Impression Plus graphics card.
  It includes 3D DDI support for enhanced OpenGL performance in 1024x768
  at 256 color mode or 800x600 at 16bpp mode on WIndows 95.  (3D DDI is
  not supported in this release of Windows NT.)  Note that 4MB of video
  memory is required for these modes.  No 32bpp accelerated modes are
  supported at this time.  (In addition, index color mode fails buffer
  invariance conformance tests.)

- SCRSAVE contains 4 screen savers for Windows95 systems.  These should
  be copied into the windows system directory of your system.

4.  Sample Applications
-----------------------

a)  Samples included

    BOOKSample code from "OpenGL Programming Guide"

    DEMOSAUXDEMO  -  AUX Library demonstration.

BACKTRCE -  BackTrace sample ported from Silicon Graphics Irix/OpenGL.

GENGL -  A Generic Windows NT OpenGL sample application.  This is
    a great starting point for new programmers learning OpenGL.

                PGONOFFS -  Polygon offsets for efficient translation of objects.

STONEHNG -  Stonehenge application ported from SGI workstation.

                VASPHERE -  Using vertex arrays for improved performance.

    GLAUX       Source code for our AUXLIB implementation.

    SAMPLESOpenGL Sample code from SGI.  See SAMPLES\ABOUT.TXT for details.

    SCRSAVESource code for new OpenGL screen savers.

b)  Disclaimer for Sample Applications that contain SGI copyright notice

ALL RIGHTS RESERVED
Permission to use, copy, modify, and distribute this software for
any purpose and without fee is hereby granted, provided that the above
copyright notice appear in all copies and that both the copyright notice
and this permission notice appear in supporting documentation, and that
the name of Silicon Graphics, Inc. not be used in advertising
or publicity pertaining to distribution of the software without specific,
written prior permission.

THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.

US Government Users Restricted Rights
Use, duplication, or disclosure by the Government is subject to
restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
(c)(1)(ii) of the Rights in Technical Data and Computer Software
clause at DFARS 252.227-7013 and/or in similar or successor
clauses in the FAR or the DOD or NASA FAR Supplement.
Unpublished-- rights reserved under the copyright laws of the
United States.  Contractor/manufacturer is Silicon Graphics,
Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.

OpenGL(R) is a trademark of Silicon Graphics, Inc.
Windows is a registered trademark of Microsoft Corporation.


ACCANTI.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  accanti.c
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include "jitter.h"

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize lighting and other values.
 */
void myinit(void)
{
    GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_position[] = { 0.0, 0.0, 10.0, 1.0 };
    GLfloat lm_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 50.0);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel (GL_FLAT);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearAccum(0.0, 0.0, 0.0, 0.0);
}

void displayObjects(void)
{
    GLfloat torus_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
    GLfloat cube_diffuse[] = { 0.0, 0.7, 0.7, 1.0 };
    GLfloat sphere_diffuse[] = { 0.7, 0.0, 0.7, 1.0 };
    GLfloat octa_diffuse[] = { 0.7, 0.4, 0.4, 1.0 };

    glPushMatrix ();
    glRotatef (30.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.80, 0.35, 0.0);
    glRotatef (100.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, torus_diffuse);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.50, 0.0);
    glRotatef (45.0, 0.0, 0.0, 1.0);
    glRotatef (45.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, cube_diffuse);
    auxSolidCube (1.5);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.60, 0.0);
    glRotatef (30.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.70, -0.90, 0.25);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, octa_diffuse);
    auxSolidOctahedron (1.0);
    glPopMatrix ();

    glPopMatrix ();
}

#define ACSIZE  8

void CALLBACK display(void)
{
    GLint viewport[4];
    int jitter;

    glGetIntegerv (GL_VIEWPORT, viewport);

    glClear(GL_ACCUM_BUFFER_BIT);
    for (jitter = 0; jitter < ACSIZE; jitter++) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glPushMatrix ();
/*  Note that 4.5 is the distance in world space between
 *  left and right and bottom and top.
 *  This formula converts fractional pixel movement to
 *  world coordinates.
 */
        glTranslatef (j8[jitter].x*4.5/viewport[2],
            j8[jitter].y*4.5/viewport[3], 0.0);
        displayObjects ();
        glPopMatrix ();
        glAccum(GL_ACCUM, 1.0/ACSIZE);
        glFlush();
        auxSwapBuffers();
    }
    glAccum (GL_RETURN, 1.0);
    glFlush();
    auxSwapBuffers();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-2.25, 2.25, -2.25*h/w, 2.25*h/w, -10.0, 10.0);
    else
    glOrtho (-2.25*w/h, 2.25*w/h, -2.25, 2.25, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGB
            | AUX_ACCUM | AUX_DEPTH16);
    auxInitPosition (0, 0, 250, 250);
    auxInitWindow ("Jittering - Orthographic");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ACCNOT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  accnot.c
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize lighting and other values.
 */

void myinit(void)
{
    GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_position[] = { 0.0, 0.0, 10.0, 1.0 };
    GLfloat lm_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 50.0);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    GLfloat torus_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
    GLfloat cube_diffuse[] = { 0.0, 0.7, 0.7, 1.0 };
    GLfloat sphere_diffuse[] = { 0.7, 0.0, 0.7, 1.0 };
    GLfloat octa_diffuse[] = { 0.7, 0.4, 0.4, 1.0 };

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glShadeModel (GL_FLAT);
    glPushMatrix ();
    glRotatef (30.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.80, 0.35, 0.0);
    glRotatef (100.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, torus_diffuse);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.50, 0.0);
    glRotatef (45.0, 0.0, 0.0, 1.0);
    glRotatef (45.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, cube_diffuse);
    auxSolidCube (1.5);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.60, 0.0);
    glRotatef (30.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.70, -0.90, 0.25);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, octa_diffuse);
    auxSolidOctahedron (1.0);
    glPopMatrix ();

    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if(!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho (-2.25, 2.25, -2.25*h/w, 2.25*h/w, -10.0, 10.0);
    else
        glOrtho (-2.25*w/h, 2.25*w/h, -2.25, 2.25, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB
            | AUX_ACCUM | AUX_DEPTH16);
    auxInitPosition (0, 0, 250, 250);
    auxInitWindow ("Auxillary 3-D Models");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ACCPERSP.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  accpersp.c
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glaux.h>
#include "jitter.h"

void myinit(void);
void accFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus);
void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus);
void displayObjects(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

#define PI_ 3.14159265358979323846

/*  accFrustum()
 *  The first 6 arguments are identical to the glFrustum() call.
 *
 *  pixdx and pixdy are anti-alias jitter in pixels.
 *  Set both equal to 0.0 for no anti-alias jitter.
 *  eyedx and eyedy are depth-of field jitter in pixels.
 *  Set both equal to 0.0 for no depth of field effects.
 *
 *  focus is distance from eye to plane in focus.
 *  focus must be greater than, but not equal to 0.0.
 *
 *  Note that accFrustum() calls glTranslatef().  You will
 *  probably want to insure that your ModelView matrix has been
 *  initialized to identity before calling accFrustum().
 */
void accFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble xwsize, ywsize;
    GLdouble dx, dy;
    GLint viewport[4];

    glGetIntegerv (GL_VIEWPORT, viewport);

    xwsize = right - left;
    ywsize = top - bottom;

    dx = -(pixdx*xwsize/(GLdouble) viewport[2] + eyedx*znear/focus);
    dy = -(pixdy*ywsize/(GLdouble) viewport[3] + eyedy*znear/focus);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum (left + dx, right + dx, bottom + dy, top + dy, znear, zfar);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (-eyedx, -eyedy, 0.0);
}

/*  accPerspective()
 *
 *  The first 4 arguments are identical to the gluPerspective() call.
 *  pixdx and pixdy are anti-alias jitter in pixels.
 *  Set both equal to 0.0 for no anti-alias jitter.
 *  eyedx and eyedy are depth-of field jitter in pixels.
 *  Set both equal to 0.0 for no depth of field effects.
 *
 *  focus is distance from eye to plane in focus.
 *  focus must be greater than, but not equal to 0.0.
 *
 *  Note that accPerspective() calls accFrustum().
 */
void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble fov2,left,right,bottom,top;

    fov2 = ((fovy*PI_) / 180.0) / 2.0;

    top = znear / (cos(fov2) / sin(fov2));
    bottom = -top;

    right = top * aspect;
    left = -right;

    accFrustum (left, right, bottom, top, znear, zfar,
    pixdx, pixdy, eyedx, eyedy, focus);
}

/*  Initialize lighting and other values.
 */
void myinit(void)
{
    GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_position[] = { 0.0, 0.0, 10.0, 1.0 };
    GLfloat lm_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 50.0);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel (GL_FLAT);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearAccum(0.0, 0.0, 0.0, 0.0);
}

void displayObjects(void)
{
    GLfloat torus_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
    GLfloat cube_diffuse[] = { 0.0, 0.7, 0.7, 1.0 };
    GLfloat sphere_diffuse[] = { 0.7, 0.0, 0.7, 1.0 };
    GLfloat octa_diffuse[] = { 0.7, 0.4, 0.4, 1.0 };

    glPushMatrix ();
    glTranslatef (0.0, 0.0, -5.0);
    glRotatef (30.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.80, 0.35, 0.0);
    glRotatef (100.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, torus_diffuse);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.50, 0.0);
    glRotatef (45.0, 0.0, 0.0, 1.0);
    glRotatef (45.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, cube_diffuse);
    auxSolidCube (1.5);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.60, 0.0);
    glRotatef (30.0, 1.0, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.70, -0.90, 0.25);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, octa_diffuse);
    auxSolidOctahedron (1.0);
    glPopMatrix ();

    glPopMatrix ();
}

#define ACSIZE  8

void CALLBACK display(void)
{
    GLint viewport[4];
    int jitter;

    glGetIntegerv (GL_VIEWPORT, viewport);

    if(!viewport[3]) return;

    glClear(GL_ACCUM_BUFFER_BIT);
    for (jitter = 0; jitter < ACSIZE; jitter++) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        accPerspective (50.0,
            (GLdouble) viewport[2]/(GLdouble) viewport[3],
            1.0, 15.0, j8[jitter].x, j8[jitter].y,
            0.0, 0.0, 1.0);
        displayObjects ();
        glAccum(GL_ACCUM, 1.0/ACSIZE);
        glFlush();
        auxSwapBuffers();
    }
    glAccum (GL_RETURN, 1.0);
    glFlush();
    auxSwapBuffers();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGB
            | AUX_ACCUM | AUX_DEPTH16);
    auxInitPosition (0, 0, 250, 250);
    auxInitWindow ("Scene Antialiasing");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ACCUM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  accum.c
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void loaddxdy(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLsizei width, height;

void myinit(void)
{
    GLfloat ambient[] = { 0.4, 0.4, 0.4, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 2.0, 2.0, 0.0 };
    GLfloat mat_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
    GLfloat mat_specular[] = { 0.9, 0.9, 0.9, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };
    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);

    glClearAccum(0.0, 0.0, 0.0, 0.0);
}

#define ACSIZE 16

GLfloat dx[ACSIZE], dy[ACSIZE];

GLfloat jitter3[3][2] = {
    {0.5, 0.5},
    {1.35899e-05, 0.230369},
    {0.000189185, 0.766878},
};

GLfloat jitter11[11][2] = {
    {0.5, 0.5}, {0.406537, 0.135858},
    {0.860325, 0.968558}, {0.680141, 0.232877},
    {0.775694, 0.584871}, {0.963354, 0.309056},
    {0.593493, 0.864072}, {0.224334, 0.415055},
    {0.0366643, 0.690884}, {0.139685, 0.0313988},
    {0.319861, 0.767097},
};

GLfloat jitter16[16][2] = {
    {0.4375, 0.4375}, {0.1875, 0.5625},
    {0.9375, 1.1875}, {0.4375, 0.9375-1},
    {0.6875, 0.5625}, {0.1875, 0.0625},
    {0.6875, 0.3125}, {0.1875, 0.3125},
    {0.4375, 0.1875}, {0.9375-1, 0.4375},
    {0.6875, 0.8125}, {0.4375, 0.6875},
    {0.6875, 0.0625}, {0.9375, 0.9375},
    {1.1875, 0.8125}, {0.9375, 0.6875},
};

GLfloat jitter29[29][2] = {
    {0.5, 0.5}, {0.498126, 0.141363},
    {0.217276, 0.651732}, {0.439503, 0.954859},
    {0.734171, 0.836294}, {0.912454, 0.79952},
    {0.406153, 0.671156}, {0.0163892, 0.631994},
    {0.298064, 0.843476}, {0.312025, 0.0990405},
    {0.98135, 0.965697}, {0.841999, 0.272378},
    {0.559348, 0.32727}, {0.809331, 0.638901},
    {0.632583, 0.994471}, {0.00588314, 0.146344},
    {0.713365, 0.437896}, {0.185173, 0.246584},
    {0.901735, 0.474544}, {0.366423, 0.296698},
    {0.687032, 0.188184}, {0.313256, 0.472999},
    {0.543195, 0.800044}, {0.629329, 0.631599},
    {0.818263, 0.0439354}, {0.163978, 0.00621497},
    {0.109533, 0.812811}, {0.131325, 0.471624},
    {0.0196755, 0.331813},
};

GLfloat jitter90[90][2] = {
    {0.5, 0.5}, {0.784289, 0.417355},
    {0.608691, 0.678948}, {0.546538, 0.976002},
    {0.972245, 0.270498}, {0.765121, 0.189392},
    {0.513193, 0.743827}, {0.123709, 0.874866},
    {0.991334, 0.745136}, {0.56342, 0.0925047},
    {0.662226, 0.143317}, {0.444563, 0.928535},
    {0.248017, 0.981655}, {0.100115, 0.771923},
    {0.593937, 0.559383}, {0.392095, 0.225932},
    {0.428776, 0.812094}, {0.510615, 0.633584},
    {0.836431, 0.00343328}, {0.494037, 0.391771},
    {0.617448, 0.792324}, {0.688599, 0.48914},
    {0.530421, 0.859206}, {0.0742278, 0.665344},
    {0.979388, 0.626835}, {0.183806, 0.479216},
    {0.151222, 0.0803998}, {0.476489, 0.157863},
    {0.792675, 0.653531}, {0.0990416, 0.267284},
    {0.776667, 0.303894}, {0.312904, 0.296018},
    {0.288777, 0.691008}, {0.460097, 0.0436075},
    {0.594323, 0.440751}, {0.876296, 0.472043},
    {0.0442623, 0.0693901}, {0.355476, 0.00442787},
    {0.391763, 0.361327}, {0.406994, 0.696053},
    {0.708393, 0.724992}, {0.925807, 0.933103},
    {0.850618, 0.11774}, {0.867486, 0.233677},
    {0.208805, 0.285484}, {0.572129, 0.211505},
    {0.172931, 0.180455}, {0.327574, 0.598031},
    {0.685187, 0.372379}, {0.23375, 0.878555},
    {0.960657, 0.409561}, {0.371005, 0.113866},
    {0.29471, 0.496941}, {0.748611, 0.0735321},
    {0.878643, 0.34504}, {0.210987, 0.778228},
    {0.692961, 0.606194}, {0.82152, 0.8893},
    {0.0982095, 0.563104}, {0.214514, 0.581197},
    {0.734262, 0.956545}, {0.881377, 0.583548},
    {0.0560485, 0.174277}, {0.0729515, 0.458003},
    {0.719604, 0.840564}, {0.325388, 0.7883},
    {0.26136, 0.0848927}, {0.393754, 0.467505},
    {0.425361, 0.577672}, {0.648594, 0.0248658},
    {0.983843, 0.521048}, {0.272936, 0.395127},
    {0.177695, 0.675733}, {0.89175, 0.700901},
    {0.632301, 0.908259}, {0.782859, 0.53611},
    {0.0141421, 0.855548}, {0.0437116, 0.351866},
    {0.939604, 0.0450863}, {0.0320883, 0.962943},
    {0.341155, 0.895317}, {0.952087, 0.158387},
    {0.908415, 0.820054}, {0.481435, 0.281195},
    {0.675525, 0.25699}, {0.585273, 0.324454},
    {0.156488, 0.376783}, {0.140434, 0.977416},
    {0.808155, 0.77305}, {0.282973, 0.188937},
};

void loaddxdy(void)
{
    long i;
    for (i = 0; i < ACSIZE; i++) {
        dx[i] = jitter16[i][0]*10/width;
        dy[i] = jitter16[i][1]*10/height;
    }
}

void CALLBACK display(void)
{
    int i;

    glClear(GL_ACCUM_BUFFER_BIT);
    loaddxdy();
    for (i = 0; i < (ACSIZE); i++) {
        //printf("Pass %d\n", i);
        glPushMatrix();
        glTranslatef(dx[i], dy[i], 0.0);
        glRotatef(45.0, 1.0, 1.0, 1.0);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
        auxSolidTeapot(1.0);
        glPopMatrix();
        glAccum(GL_ACCUM, 1.0/(ACSIZE));
        glFlush();
        auxSwapBuffers();
    }
    //printf("final job\n");
    glAccum(GL_RETURN, 1.0);
    //printf("done\n");
    glFlush();
    auxSwapBuffers();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if(!h) return;
    width = w; height = h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho(-3.0, 3.0, -3.0*(GLfloat) h/(GLfloat) w,
                3.0*(GLfloat) h/(GLfloat) w, -15.0, 15.0);
    else
        glOrtho(-3.0*(GLfloat) w/(GLfloat) h,
                3.0*(GLfloat) w/(GLfloat) h, -3.0, 3.0, -15.0, 15.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGB
            | AUX_ACCUM | AUX_DEPTH16);
    auxInitPosition (0, 0, 300, 300);
    auxInitWindow ("Accumulation Buffer");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


AIM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  aim.c
 *  This program calculates the fovy (field of view angle
 *  in the y direction), by using trigonometry, given the
 *  size of an object and its size.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
GLdouble calculateAngle (double size, double distance);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

void myinit (void) {
    glShadeModel (GL_FLAT);
}

/*  Clear the screen.  Set the current color to white.
 *  Draw the wire frame cube and sphere.
 */
void CALLBACK display (void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);

    glLoadIdentity ();
/*  glTranslatef() as viewing transformation    */
    glTranslatef (0.0, 0.0, -5.0);
    auxWireCube(2.0);
    auxWireSphere(1.0);
    glFlush();
}

#define PI  3.1415926535

/*  atan2 () is a system math routine which calculates
 *  the arctangent of an angle, given length of the
 *  opposite and adjacent sides of a right triangle.
 *  atan2 () is not an OpenGL routine.
 */
GLdouble calculateAngle (double size, double distance)
{
    GLdouble radtheta, degtheta;

    radtheta = 2.0 * atan2 (size/2.0, distance);
    degtheta = (180.0 * radtheta) / PI;
    //printf ("degtheta is %lf\n", degtheta);
    return ((GLdouble) degtheta);
}

/*  Called when the window is first opened and whenever
 *  the window is reconfigured (moved or resized).
 */
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    GLdouble theta;

    if(!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    theta = calculateAngle (2.0, 5.0);
    gluPerspective(theta, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Field of View Angle");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ALPHA.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  alpha.c
 *  This program draws several overlapping filled polygons
 *  to demonstrate the effect order has on alpha blending results.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize alpha blending function.
 */
void myinit(void)
{
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glShadeModel (GL_FLAT);
    glClearColor (0.0, 0.0, 0.0, 0.0);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glColor4f (1.0, 1.0, 0.0, 0.75);
    glRectf (0.0, 0.0, 0.5, 1.0);

    glColor4f (0.0, 1.0, 1.0, 0.75);
    glRectf (0.0, 0.0, 1.0, 0.5);
/*  draw colored polygons in reverse order in upper right  */
    glColor4f (0.0, 1.0, 1.0, 0.75);
    glRectf (0.5, 0.5, 1.0, 1.0);

    glColor4f (1.0, 1.0, 0.0, 0.75);
    glRectf (0.5, 0.5, 1.0, 1.0);

    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if(!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    gluOrtho2D (0.0, 1.0, 0.0, 1.0*(GLfloat)h/(GLfloat)w);
    else
    gluOrtho2D (0.0, 1.0*(GLfloat)w/(GLfloat)h, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Alpha Blending");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ALPHA3D.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  alpha3D.c
 *  This program demonstrates how to intermix opaque and
 *  alpha blended polygons in the same scene, by using glDepthMask.
 *  Pressing the left mouse button toggles the eye position.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK toggleEye (AUX_EVENTREC *event);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

void myinit(void)
{
    GLfloat mat_ambient[] = { 0.0, 0.0, 0.0, 0.15 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 0.15 };
    GLfloat mat_shininess[] = { 15.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

GLboolean eyePosition = GL_FALSE;

void CALLBACK toggleEye (AUX_EVENTREC *event)
{
    if (eyePosition)
    eyePosition = GL_FALSE;
    else
    eyePosition = GL_TRUE;
}

void CALLBACK display(void)
{
    GLfloat position[] = { 0.0, 0.0, 1.0, 1.0 };
    GLfloat mat_torus[] = { 0.75, 0.75, 0.0, 1.0 };
    GLfloat mat_cylinder[] = { 0.0, 0.75, 0.75, 0.15 };

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLightfv (GL_LIGHT0, GL_POSITION, position);
    glPushMatrix ();
    if (eyePosition)
        gluLookAt (0.0, 0.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    else
        gluLookAt (0.0, 0.0, -9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    glPushMatrix ();
    glTranslatef (0.0, 0.0, 1.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_torus);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glEnable (GL_BLEND);
    glDepthMask (GL_FALSE);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_cylinder);
    glTranslatef (0.0, 0.0, -1.0);
    auxSolidCylinder (1.0, 2.0);
    glDepthMask (GL_TRUE);
    glDisable (GL_BLEND);
    glPopMatrix ();

    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if(!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(30.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Intermixing Opaque and Alpha Blending");
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, toggleEye);
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ANTI.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  anti.c
 *  This program draws antialiased lines in RGBA mode.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize antialiasing for RGBA mode, including alpha
 *  blending, hint, and line width.  Print out implementation
 *  specific info on line width granularity and width.
 */
void myinit(void)
{
    GLfloat values[2];
    glGetFloatv (GL_LINE_WIDTH_GRANULARITY, values);
    //printf ("GL_LINE_WIDTH_GRANULARITY value is %3.1f\n", values[0]);

    glGetFloatv (GL_LINE_WIDTH_RANGE, values);

    //printf ("GL_LINE_WIDTH_RANGE values are %3.1f %3.1f\n",
    //values[0], values[1]);

    glEnable (GL_LINE_SMOOTH);
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
    glLineWidth (1.5);

    glShadeModel(GL_FLAT);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

/*  display() draws an icosahedron with a large alpha value, 1.0.
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor4f (1.0, 1.0, 1.0, 1.0);
    auxWireIcosahedron(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (GLfloat) w/(GLfloat) h, 3.0, 5.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
    glTranslatef (0.0, 0.0, -4.0);  /*  move object into view   */
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Antialiased Lines Using RGBA");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ANTINDX.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  antiindex.c
 *  The program draws a wireframe icosahedron with
 *  antialiased lines, in color index mode.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

#define RAMPSIZE 16
#define RAMPSTART 32

/*  Initialize antialiasing for color index mode,
 *  including loading a grey color ramp starting
 *  at RAMPSTART, which must be a multiple of 16.
 */
void myinit(void)
{
    int i;

    for (i = 0; i < RAMPSIZE; i++) {
    GLfloat shade;
    shade = (GLfloat) i/(GLfloat) RAMPSIZE;
    auxSetOneColor(RAMPSTART+(GLint)i, shade, shade, shade);
    }

    glEnable (GL_LINE_SMOOTH);
    glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
    glLineWidth (1.5);

    glClearIndex ((GLfloat) RAMPSTART);
    glShadeModel(GL_FLAT);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

/*  display() draws an icosahedron.
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glIndexi(RAMPSTART);
    auxWireIcosahedron(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (GLfloat) w/(GLfloat) h, 3.0, 5.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
    glTranslatef (0.0, 0.0, -4.0);  /*  move object into view   */
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  color index display mode, depth buffer,
 *  and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_INDEX | AUX_DEPTH16);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Antialiased Lines Using Color Index");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ANTIPNDX.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  antipindex.c
 *  The program draws antialiased points,
 *  in color index mode.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

#define RAMPSIZE 16
#define RAMPSTART 32

/*  Initialize point antialiasing for color index mode,
 *  including loading a grey color ramp starting at
 *  RAMPSTART, which must be a multiple of 16.
 */
void myinit(void)
{
    int i;

    for (i = 0; i < RAMPSIZE; i++) {
    GLfloat shade;
    shade = (GLfloat) i/(GLfloat) RAMPSIZE;
    auxSetOneColor (RAMPSTART+(GLint)i, shade, shade, shade);
    }
    glEnable (GL_POINT_SMOOTH);
    glHint (GL_POINT_SMOOTH_HINT, GL_FASTEST);
    glPointSize (3.0);
    glClearIndex ((GLfloat) RAMPSTART);
}

/*  display() draws several points.  */

void CALLBACK display(void)
{
    int i;

    glClear(GL_COLOR_BUFFER_BIT);
    glIndexi (RAMPSTART);
    glBegin (GL_POINTS);
    for (i = 1; i < 10; i++) {
        glVertex2f ((GLfloat) i * 10.0, (GLfloat) i * 10.0);
    }
    glEnd ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w < h)
    glOrtho (0.0, 100.0, 0.0,
        100.0*(GLfloat) h/(GLfloat) w, -1.0, 1.0);
    else
    glOrtho (0.0, 100.0*(GLfloat) w/(GLfloat) h,
        0.0, 100.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  color index display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_INDEX);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Points");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ANTIPNT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  antipoint.c
 *  The program draws antialiased points, in RGBA mode.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize point anti-aliasing for RGBA mode, including alpha
 *  blending, hint, and point size.  These points are 3.0 pixels big.
 */
void myinit(void)
{
    glEnable (GL_POINT_SMOOTH);
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glHint (GL_POINT_SMOOTH_HINT, GL_DONT_CARE);
    glPointSize (3.0);

    glClearColor(0.0, 0.0, 0.0, 0.0);
}

/*  display() draws several points.
 */
void CALLBACK display(void)
{
    int i;

    glClear (GL_COLOR_BUFFER_BIT);
    glColor4f (1.0, 1.0, 1.0, 1.0);
    glBegin (GL_POINTS);
    for (i = 1; i < 10; i++) {
        glVertex2f ((GLfloat) i * 10.0, (GLfloat) i * 10.0);
    }
    glEnd ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w < h)
    glOrtho (0.0, 100.0,
        0.0, 100.0*(GLfloat) h/(GLfloat) w, -1.0, 1.0);
    else
    glOrtho (0.0, 100.0*(GLfloat) w/(GLfloat) h,
        0.0, 100.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Points");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


ANTIPOLY.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  antipoly.c
 *  This program draws filled polygons with antialiased
 *  edges.  The special GL_SRC_ALPHA_SATURATE blending
 *  function is used.
 *  Pressing the left mouse button turns the antialiasing
 *  on and off.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK toggleSmooth (AUX_EVENTREC *event);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLboolean polySmooth;

void myinit(void)
{
    GLfloat mat_ambient[] = { 0.0, 0.0, 0.0, 1.00 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.00 };
    GLfloat mat_shininess[] = { 15.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);
    glEnable (GL_BLEND);
    glCullFace (GL_BACK);
    glEnable (GL_CULL_FACE);
    glEnable (GL_POLYGON_SMOOTH);
    polySmooth = GL_TRUE;

    glClearColor (0.0, 0.0, 0.0, 0.0);
}

void CALLBACK toggleSmooth (AUX_EVENTREC *event)
{
    if (polySmooth) {
    polySmooth = GL_FALSE;
    glDisable (GL_BLEND);
    glDisable (GL_POLYGON_SMOOTH);
    glEnable (GL_DEPTH_TEST);
    }
    else {
    polySmooth = GL_TRUE;
    glEnable (GL_BLEND);
    glEnable (GL_POLYGON_SMOOTH);
    glDisable (GL_DEPTH_TEST);
    }
}

/*  Note:  polygons must be drawn from front to back
 *  for proper blending.
 */
void CALLBACK display(void)
{
    GLfloat position[] = { 0.0, 0.0, 1.0, 0.0 };
    GLfloat mat_cube1[] = { 0.75, 0.75, 0.0, 1.0 };
    GLfloat mat_cube2[] = { 0.0, 0.75, 0.75, 1.0 };

    if (polySmooth)
    glClear (GL_COLOR_BUFFER_BIT);
    else
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glTranslatef (0.0, 0.0, -8.0);
    glLightfv (GL_LIGHT0, GL_POSITION, position);

    glBlendFunc (GL_SRC_ALPHA_SATURATE, GL_ONE);

    glPushMatrix ();
    glRotatef (30.0, 1.0, 0.0, 0.0);
    glRotatef (60.0, 0.0, 1.0, 0.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_cube1);
    auxSolidCube (1.0);
    glPopMatrix ();

    glTranslatef (0.0, 0.0, -2.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_cube2);
    glRotatef (30.0, 0.0, 1.0, 0.0);
    glRotatef (60.0, 1.0, 0.0, 0.0);
    auxSolidCube (1.0);

    glPopMatrix ();

    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    h = (h == 0) ? 1 : h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(30.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_ALPHA | AUX_DEPTH16);
    auxInitPosition (0, 0, 200, 200);
    auxInitWindow ("Antialiased Edges");
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, toggleSmooth);
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


BEZCURVE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  bezcurve.c
 *  This program uses evaluators to draw a Bezier curve.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLfloat ctrlpoints[4][3] = {
    { -4.0, -4.0, 0.0}, { -2.0, 4.0, 0.0},
    {2.0, -4.0, 0.0}, {4.0, 4.0, 0.0}};

void myinit(void)
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);
    glEnable(GL_MAP1_VERTEX_3);
    glShadeModel(GL_FLAT);
}

void CALLBACK display(void)
{
    int i;

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINE_STRIP);
    for (i = 0; i <= 30; i++)
        glEvalCoord1f((GLfloat) i/30.0);
    glEnd();
    /* The following code displays the control points as dots. */
    glPointSize(5.0);
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_POINTS);
    for (i = 0; i < 4; i++)
        glVertex3fv(&ctrlpoints[i][0]);
    glEnd();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho(-5.0, 5.0, -5.0*(GLfloat)h/(GLfloat)w,
        5.0*(GLfloat)h/(GLfloat)w, -5.0, 5.0);
    else
    glOrtho(-5.0*(GLfloat)w/(GLfloat)h,
        5.0*(GLfloat)w/(GLfloat)h, -5.0, 5.0, -5.0, 5.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Bezier Curves Using Evaluators");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


BEZMESH.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  bezsurf.c
 *  This program renders a lighted, filled Bezier surface,
 *  using two-dimensional evaluators.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void initlights(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLfloat ctrlpoints[4][4][3] = {
    {{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
    {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
    {{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
    {0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
    {{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
    {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
    {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
    {0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};

void initlights(void)
{
    GLfloat ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat position[] = { 0.0, 0.0, 2.0, 1.0 };
    GLfloat mat_diffuse[] = { 0.6, 0.6, 0.6, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glRotatef(85.0, 1.0, 1.0, 1.0);
    glEvalMesh2(GL_FILL, 0, 20, 0, 20);
    glPopMatrix();
    glFlush();
}

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glEnable (GL_DEPTH_TEST);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
        0, 1, 12, 4, &ctrlpoints[0][0][0]);
    glEnable(GL_MAP2_VERTEX_3);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
    initlights();   /* for lighted version only */
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
        4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
    else
    glOrtho(-4.0*(GLfloat)w/(GLfloat)h,
        4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Lighted and Filled Bezier Surface");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


BEZSURF.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  bezmesh.c
 *  This program renders a wireframe Bezier surface,
 *  using two-dimensional evaluators.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLfloat ctrlpoints[4][4][3] = {
    {{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
    {{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
{0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
    {{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
{0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
    {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};

void CALLBACK display(void)
{
    int i, j;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    glPushMatrix ();
    glRotatef(85.0, 1.0, 1.0, 1.0);
    for (j = 0; j <= 8; j++) {
glBegin(GL_LINE_STRIP);
        for (i = 0; i <= 30; i++)
    glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
glEnd();
glBegin(GL_LINE_STRIP);
        for (i = 0; i <= 30; i++)
    glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
glEnd();
    }
    glPopMatrix ();
    glFlush();
}

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
    0, 1, 12, 4, &ctrlpoints[0][0][0]);
    glEnable(GL_MAP2_VERTEX_3);
    glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
    4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
    else
glOrtho(-4.0*(GLfloat)w/(GLfloat)h,
    4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Wireframe Bezier Surface");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CHECKER.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  checker.c
 *  This program texture maps a checkerboard image onto
 *  two rectangles.  This program clamps the texture, if
 *  the texture coordinates fall outside 0.0 and 1.0.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void makeCheckImage(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Create checkerboard texture */
#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageWidth][checkImageHeight][3];

void makeCheckImage(void)
{
    int i, j, c;

    for (i = 0; i < checkImageWidth; i++) {
    for (j = 0; j < checkImageHeight; j++) {
        c = ((((i&0x8)==0)^((j&0x8))==0))*255;
        checkImage[i][j][0] = (GLubyte) c;
        checkImage[i][j][1] = (GLubyte) c;
        checkImage[i][j][2] = (GLubyte) c;
    }
    }
}

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    makeCheckImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, checkImageWidth,
    checkImageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
    &checkImage[0][0][0]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glEnable(GL_TEXTURE_2D);
    glShadeModel(GL_FLAT);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
    glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
    glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
    glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

    glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
    glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
    glTexCoord2f(1.0, 1.0); glVertex3f(2.41421, 1.0, -1.41421);
    glTexCoord2f(1.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
    glEnd();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.6);
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Texture Map");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CHECKER2.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  checker2.c
 *  This program texture maps a checkerboard image onto
 *  two rectangles.  This program repeats the texture, if
 *  the texture coordinates fall outside 0.0 and 1.0.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void makeCheckImage(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Create checkerboard texture */
#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageWidth][checkImageHeight][3];

void makeCheckImage(void)
{
    int i, j, c;

    for (i = 0; i < checkImageWidth; i++) {
    for (j = 0; j < checkImageHeight; j++) {
        c = ((((i&0x8)==0)^((j&0x8))==0))*255;
        checkImage[i][j][0] = (GLubyte) c;
        checkImage[i][j][1] = (GLubyte) c;
        checkImage[i][j][2] = (GLubyte) c;
    }
    }
}

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    makeCheckImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, checkImageWidth,
    checkImageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
    &checkImage[0][0][0]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glEnable(GL_TEXTURE_2D);
    glShadeModel(GL_FLAT);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
    glTexCoord2f(0.0, 3.0); glVertex3f(-2.0, 1.0, 0.0);
    glTexCoord2f(3.0, 3.0); glVertex3f(0.0, 1.0, 0.0);
    glTexCoord2f(3.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

    glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
    glTexCoord2f(0.0, 3.0); glVertex3f(1.0, 1.0, 0.0);
    glTexCoord2f(3.0, 3.0); glVertex3f(2.41421, 1.0, -1.41421);
    glTexCoord2f(3.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
    glEnd();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.6);
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Texture Map");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CHESS.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  chess.c
 *  This program texture maps a checkerboard image onto
 *  two rectangles.  The texture coordinates for the
 *  rectangles are 0.0 to 3.0.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <math.h>

void myinit(void);
void makeCheckImage(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageWidth][checkImageHeight][3];

void makecheckimage(void)
{
    int i, j, c;

    for (i = 0; i < checkImageWidth; i++) {
    for (j = 0; j < checkImageHeight; j++) {
        c = ((((i&0x8)==0)^((j&0x8))==0))*255;
        checkImage[i][j][0] = (GLubyte) c;
        checkImage[i][j][1] = (GLubyte) c;
        checkImage[i][j][2] = (GLubyte) c;
    }
    }
}

void myinit(void)
{
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    makecheckimage();
    glTexImage2D(GL_TEXTURE_2D, 0, 3,
    checkImageWidth, checkImageHeight, 0,
    GL_RGB, GL_UNSIGNED_BYTE, &checkImage[0][0][0]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glEnable(GL_TEXTURE_2D);
    glShadeModel(GL_FLAT);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
    glTexCoord2f(0.0, 3.0); glVertex3f(-2.0, 1.0, 0.0);
    glTexCoord2f(3.0, 3.0); glVertex3f(0.0, 1.0, 0.0);
    glTexCoord2f(3.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

    glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
    glTexCoord2f(0.0, 3.0); glVertex3f(1.0, 1.0, 0.0);
    glTexCoord2f(3.0, 3.0); glVertex3f(2.41421, 1.0, -1.41421);
    glTexCoord2f(3.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
    glEnd();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.6);
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Texture Map");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CLIP.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  clip.c
 *  This program demonstrates arbitrary clipping planes.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

void CALLBACK display(void)
{
    GLdouble eqn[4] = {0.0, 1.0, 0.0, 0.0};
    GLdouble eqn2[4] = {1.0, 0.0, 0.0, 0.0};

    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f (1.0, 1.0, 1.0);
    glPushMatrix();
    glTranslatef (0.0, 0.0, -5.0);

/*    clip lower half -- y < 0      */
    glClipPlane (GL_CLIP_PLANE0, eqn);
    glEnable (GL_CLIP_PLANE0);
/*    clip left half -- x < 0       */
    glClipPlane (GL_CLIP_PLANE1, eqn2);
    glEnable (GL_CLIP_PLANE1);

    glRotatef (90.0, 1.0, 0.0, 0.0);
    auxWireSphere(1.0);
    glPopMatrix();
    glFlush();
}

void myinit (void) {
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Arbitrary Clipping Planes");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


COLORMAT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  colormat.c
 *  After initialization, the program will be in
 *  ColorMaterial mode.  Pressing the mouse buttons
 *  will change the color of the diffuse reflection.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK changeRedDiffuse (void);
void CALLBACK changeGreenDiffuse (void);
void CALLBACK changeBlueDiffuse (void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLfloat diffuseMaterial[4] = { 0.5, 0.5, 0.5, 1.0 };

/*  Initialize values for material property, light source,
 *  lighting model, and depth buffer.
 */
void myinit(void)
{
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuseMaterial);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 25.0);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    glColorMaterial(GL_FRONT, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
}

void CALLBACK changeRedDiffuse (void)
{
    diffuseMaterial[0] += 0.1;
    if (diffuseMaterial[0] > 1.0)
    diffuseMaterial[0] = 0.0;
    glColor4fv(diffuseMaterial);
}

void CALLBACK changeGreenDiffuse (void)
{
    diffuseMaterial[1] += 0.1;
    if (diffuseMaterial[1] > 1.0)
    diffuseMaterial[1] = 0.0;
    glColor4fv(diffuseMaterial);
}

void CALLBACK changeBlueDiffuse (void)
{
    diffuseMaterial[2] += 0.1;
    if (diffuseMaterial[2] > 1.0)
    diffuseMaterial[2] = 0.0;
    glColor4fv(diffuseMaterial);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    auxSolidSphere(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
        1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
        1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("ColorMaterial Mode");
    myinit();
    auxKeyFunc(AUX_R, changeRedDiffuse);
    auxKeyFunc(AUX_r, changeRedDiffuse);
    auxKeyFunc(AUX_G, changeGreenDiffuse);
    auxKeyFunc(AUX_g, changeGreenDiffuse);
    auxKeyFunc(AUX_B, changeBlueDiffuse);
    auxKeyFunc(AUX_b, changeBlueDiffuse);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CONE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 * cone.c
 * This program demonstrates the use of the GL lighting model.
 * A sphere is drawn using a grey material characteristic.
 * A single light source illuminates the object.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize material property, light source, and lighting model.
 */
void myinit(void)
{
    GLfloat mat_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat mat_diffuse[] = { 0.8, 0.8, 0.8, 1.0 };
/*  mat_specular and mat_shininess are NOT default values   */
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };

    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glTranslatef (0.0, -1.0, 0.0);
    glRotatef (250.0, 1.0, 0.0, 0.0);
    auxSolidCone(1.0, 2.0);
    glPopMatrix();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
                1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
        glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
                1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CUBE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  cube.c
 *  Draws a 3-D cube, viewed with perspective, stretched
 *  along the y-axis.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Clear the screen.  Set the current color to white.
 *  Draw the wire frame cube.
 */
void CALLBACK display (void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    glLoadIdentity ();  /*  clear the matrix    */
    glTranslatef (0.0, 0.0, -5.0);  /*  viewing transformation  */
    glScalef (1.0, 2.0, 1.0);   /*  modeling transformation */
    auxWireCube(1.0);   /*  draw the cube   */
    glFlush();
}

void myinit (void) {
    glShadeModel (GL_FLAT);
}

/*  Called when the window is first opened and whenever
 *  the window is reconfigured (moved or resized).
 */
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glMatrixMode (GL_PROJECTION);   /*  prepare for and then  */
    glLoadIdentity ();  /*  define the projection  */
    glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);    /*  transformation  */
    glMatrixMode (GL_MODELVIEW);    /*  back to modelview matrix    */
    glViewport (0, 0, w, h);    /*  define the viewport */
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Perspective 3-D Cube");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


CURVE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  curve.c
 *  This program uses the Utility Library NURBS routines to
 *  draw a one-dimensional NURBS curve.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLUnurbsObj *theNurb;

void myinit(void)
{
    glShadeModel (GL_FLAT);
    theNurb = gluNewNurbsRenderer();
    gluNurbsProperty (theNurb, GLU_SAMPLING_TOLERANCE, 10.0);
}

/*  This routine draws a B-spline curve.  Try a different
 *  knot sequence for a Bezier curve.  For example,
    GLfloat knots[8] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
 */

void CALLBACK display(void)
{
    GLfloat ctlpoints[4][3] = {{-.75, -.75, 0.0},
    {-.5, .75, 0.0}, {.5, .75, 0.0}, {.75, -.75, 0.0}};
    GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    gluBeginCurve(theNurb);
    gluNurbsCurve(theNurb,
        8, knots,
        3,
        &ctlpoints[0][0],
        4,
        GL_MAP1_VERTEX_3);
    gluEndCurve(theNurb);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    gluOrtho2D (-1.0, 1.0, -1.0 * (GLfloat) h/(GLfloat) w,
        1.0 * (GLfloat) h/(GLfloat) w);
    else
    gluOrtho2D (-1.0 * (GLfloat) w/(GLfloat) h,
        1.0 * (GLfloat) w/(GLfloat) h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("NURBS Curve");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


DEPTHCUE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  depthcue.c
 *  This program draws a wireframe model, which uses
 *  intensity (brightness) to give clues to distance.
 *  Fog is used to achieve this effect.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize linear fog for depth cueing.
 */
void myinit(void)
{
    GLfloat fogColor[4] = {0.0, 0.0, 0.0, 1.0};

    glEnable(GL_FOG);
    glFogi (GL_FOG_MODE, GL_LINEAR);
    glHint (GL_FOG_HINT, GL_NICEST);  /*  per pixel   */
    glFogf (GL_FOG_START, 3.0);
    glFogf (GL_FOG_END, 5.0);
    glFogfv (GL_FOG_COLOR, fogColor);
    glClearColor(0.0, 0.0, 0.0, 1.0);

    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_FLAT);
}

/*  display() draws an icosahedron.
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    auxWireIcosahedron(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (GLfloat) w/(GLfloat) h, 3.0, 5.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
    glTranslatef (0.0, 0.0, -4.0);  /*  move object into view   */
}

/*  Main Loop
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Distance Using Fog");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


DISK.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  disk.c
 *  This program demonstrates the use of the quadrics
 *  Utility Library routines to draw circles and arcs.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLUquadricObj * quadObj;

/*  Clear the screen.  For each triangle, set the current
 *  color and modify the modelview matrix.
 */
void CALLBACK display(void)
{
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glClear (GL_COLOR_BUFFER_BIT);

    glPushMatrix();
    gluQuadricDrawStyle (quadObj, GLU_FILL);
    glColor3f (1.0, 1.0, 1.0);
    glTranslatef (10.0, 10.0, 0.0);
    gluDisk (quadObj, 0.0, 5.0, 10, 2);
    glPopMatrix();

    glPushMatrix();
    glColor3f (1.0, 1.0, 0.0);
    glTranslatef (20.0, 20.0, 0.0);
    gluPartialDisk (quadObj, 0.0, 5.0, 10, 3, 30.0, 120.0);
    glPopMatrix();

    glPushMatrix();
    gluQuadricDrawStyle (quadObj, GLU_SILHOUETTE);
    glColor3f (0.0, 1.0, 1.0);
    glTranslatef (30.0, 30.0, 0.0);
    gluPartialDisk (quadObj, 0.0, 5.0, 10, 3, 135.0, 270.0);
    glPopMatrix();

    glPushMatrix();
    gluQuadricDrawStyle (quadObj, GLU_LINE);
    glColor3f (1.0, 0.0, 1.0);
    glTranslatef (40.0, 40.0, 0.0);
    gluDisk (quadObj, 2.0, 5.0, 10, 10);
    glPopMatrix();
    glFlush();
}

void myinit (void) {
    quadObj = gluNewQuadric ();
    glShadeModel(GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (0.0, 50.0,
        0.0, 50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0);
    else
    glOrtho (0.0, 50.0*(GLfloat)w/(GLfloat)h,
        0.0, 50.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Quadrics");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


DOF.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  dof.c
 *  This program demonstrates use of the accumulation buffer to
 *  create an out-of-focus depth-of-field effect.  The teapots
 *  are drawn several times into the accumulation buffer.  The
 *  viewing volume is jittered, except at the focal point, where
 *  the viewing volume is at the same position, each time.  In
 *  this case, the gold teapot remains in focus.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glaux.h>
#include "jitter.h"

void myinit(void);
void accFrustum(GLdouble left, GLdouble right, GLdouble bottom,
    GLdouble top, GLdouble znear, GLdouble zfar, GLdouble pixdx,
    GLdouble pixdy, GLdouble eyedx, GLdouble eyedy, GLdouble focus);
void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus);
void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

#define PI_ 3.14159265358979323846

/*  accFrustum()
 *  The first 6 arguments are identical to the glFrustum() call.
 *
 *  pixdx and pixdy are anti-alias jitter in pixels.
 *  Set both equal to 0.0 for no anti-alias jitter.
 *  eyedx and eyedy are depth-of field jitter in pixels.
 *  Set both equal to 0.0 for no depth of field effects.
 *
 *  focus is distance from eye to plane in focus.
 *  focus must be greater than, but not equal to 0.0.
 *
 *  Note that accFrustum() calls glTranslatef().  You will
 *  probably want to insure that your ModelView matrix has been
 *  initialized to identity before calling accFrustum().
 */
void accFrustum(GLdouble left, GLdouble right, GLdouble bottom,
    GLdouble top, GLdouble znear, GLdouble zfar, GLdouble pixdx,
    GLdouble pixdy, GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble xwsize, ywsize;
    GLdouble dx, dy;
    GLint viewport[4];

    glGetIntegerv (GL_VIEWPORT, viewport);

    xwsize = right - left;
    ywsize = top - bottom;

    dx = -(pixdx*xwsize/(GLdouble) viewport[2] + eyedx*znear/focus);
    dy = -(pixdy*ywsize/(GLdouble) viewport[3] + eyedy*znear/focus);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum (left + dx, right + dx, bottom + dy, top + dy, znear, zfar);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (-eyedx, -eyedy, 0.0);
}

/*  accPerspective()
 *
 *  The first 4 arguments are identical to the gluPerspective() call.
 *  pixdx and pixdy are anti-alias jitter in pixels.
 *  Set both equal to 0.0 for no anti-alias jitter.
 *  eyedx and eyedy are depth-of field jitter in pixels.
 *  Set both equal to 0.0 for no depth of field effects.
 *
 *  focus is distance from eye to plane in focus.
 *  focus must be greater than, but not equal to 0.0.
 *
 *  Note that accPerspective() calls accFrustum().
 */
void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble znear, GLdouble zfar, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble fov2,left,right,bottom,top;

    fov2 = ((fovy*PI_) / 180.0) / 2.0;

    top = znear / (cos(fov2) / sin(fov2));
    bottom = -top;

    right = top * aspect;
    left = -right;

    accFrustum (left, right, bottom, top, znear, zfar,
    pixdx, pixdy, eyedx, eyedy, focus);
}

void myinit(void)
{
    GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };

    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat local_view[] = { 0.0 };

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearAccum(0.0, 0.0, 0.0, 0.0);
}

void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine)
{
    float mat[3];

    glPushMatrix();
    glTranslatef (x, y, z);
    mat[0] = ambr; mat[1] = ambg; mat[2] = ambb;
    glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
    mat[0] = difr; mat[1] = difg; mat[2] = difb;
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
    mat[0] = specr; mat[1] = specg; mat[2] = specb;
    glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
    glMaterialf (GL_FRONT, GL_SHININESS, shine*128.0);
    auxSolidTeapot(0.5);
    glPopMatrix();
}

/*  display() draws 5 teapots into the accumulation buffer
 *  several times; each time with a jittered perspective.
 *  The focal point is at z = 5.0, so the gold teapot will
 *  stay in focus.  The amount of jitter is adjusted by the
 *  magnitude of the accPerspective() jitter; in this example, 0.33.
 *  In this example, the teapots are drawn 8 times.  See jitter.h
 */
void CALLBACK display(void)
{
    int jitter;
    GLint viewport[4];

    glGetIntegerv (GL_VIEWPORT, viewport);
    if (!viewport[3]) return;
    glClear(GL_ACCUM_BUFFER_BIT);

    for (jitter = 0; jitter < 8; jitter++) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    accPerspective (45.0,
        (GLdouble) viewport[2]/(GLdouble) viewport[3],
        1.0, 15.0, 0.0, 0.0,
        0.33*j8[jitter].x, 0.33*j8[jitter].y, 5.0);
/*  ruby, gold, silver, emerald, and cyan teapots   */
    renderTeapot (-1.1, -0.5, -4.5, 0.1745, 0.01175, 0.01175,
        0.61424, 0.04136, 0.04136, 0.727811, 0.626959, 0.626959, 0.6);
    renderTeapot (-0.5, -0.5, -5.0, 0.24725, 0.1995, 0.0745,
        0.75164, 0.60648, 0.22648, 0.628281, 0.555802, 0.366065, 0.4);
    renderTeapot (0.2, -0.5, -5.5, 0.19225, 0.19225, 0.19225,
        0.50754, 0.50754, 0.50754, 0.508273, 0.508273, 0.508273, 0.4);
    renderTeapot (1.0, -0.5, -6.0, 0.0215, 0.1745, 0.0215,
        0.07568, 0.61424, 0.07568, 0.633, 0.727811, 0.633, 0.6);
    renderTeapot (1.8, -0.5, -6.5, 0.0, 0.1, 0.06, 0.0, 0.50980392,
        0.50980392, 0.50196078, 0.50196078, 0.50196078, .25);
    glAccum (GL_ACCUM, 0.125);
    glFlush();
    auxSwapBuffers();
    }

    glAccum (GL_RETURN, 1.0);
    glFlush();
    auxSwapBuffers();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGB
            | AUX_ACCUM | AUX_DEPTH16);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Depth-of-Field");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


DOFNOT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  dofnot.c
 *  This program demonstrates the same scene as dof.c, but
 *  without use of the accumulation buffer, so everything
 *  is in focus.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glaux.h>

void myinit(void);
void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

void myinit(void)
{
    GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };

    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat local_view[] = { 0.0 };

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    glClearColor(0.0, 0.0, 0.0, 0.0);
}

void renderTeapot (GLfloat x, GLfloat y, GLfloat z,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine)
{
    float mat[3];

    glPushMatrix();
    glTranslatef (x, y, z);
    mat[0] = ambr; mat[1] = ambg; mat[2] = ambb;
    glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
    mat[0] = difr; mat[1] = difg; mat[2] = difb;
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
    mat[0] = specr; mat[1] = specg; mat[2] = specb;
    glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
    glMaterialf (GL_FRONT, GL_SHININESS, shine*128.0);
    auxSolidTeapot(0.5);
    glPopMatrix();
}

/*  display() draws 5 teapots into the accumulation buffer
 *  several times; each time with a jittered perspective.
 *  The focal point is at z = 5.0, so the gold teapot will
 *  stay in focus.  The amount of jitter is adjusted by the
 *  magnitude of the accPerspective() jitter; in this example, 0.33.
 *  In this example, the teapots are drawn 8 times.  See jitter.h
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
/*  ruby, gold, silver, emerald, and cyan teapots   */
    renderTeapot (-1.1, -0.5, -4.5, 0.1745, 0.01175, 0.01175,
        0.61424, 0.04136, 0.04136, 0.727811, 0.626959, 0.626959, 0.6);
    renderTeapot (-0.5, -0.5, -5.0, 0.24725, 0.1995, 0.0745,
        0.75164, 0.60648, 0.22648, 0.628281, 0.555802, 0.366065, 0.4);
    renderTeapot (0.2, -0.5, -5.5, 0.19225, 0.19225, 0.19225,
        0.50754, 0.50754, 0.50754, 0.508273, 0.508273, 0.508273, 0.4);
    renderTeapot (1.0, -0.5, -6.0, 0.0215, 0.1745, 0.0215,
        0.07568, 0.61424, 0.07568, 0.633, 0.727811, 0.633, 0.6);
    renderTeapot (1.8, -0.5, -6.5, 0.0, 0.1, 0.06, 0.0, 0.50980392,
        0.50980392, 0.50196078, 0.50196078, 0.50196078, .25);

    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (45.0, (GLfloat) w/(GLfloat) h,
        1.0, 15.0);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Depth-of-Field");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


DOUBLE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  double.c
 *  This program demonstrates double buffering for
 *  flicker-free animation.  The left and middle mouse
 *  buttons start and stop the spinning motion of the square.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK spinDisplay (void);
void CALLBACK startIdleFunc (AUX_EVENTREC *event);
void CALLBACK stopIdleFunc (AUX_EVENTREC *event);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

static GLfloat spin = 0.0;

void CALLBACK display(void)
{
    glClear (GL_COLOR_BUFFER_BIT);

    glPushMatrix ();
    glRotatef (spin, 0.0, 0.0, 1.0);
    glRectf (-25.0, -25.0, 25.0, 25.0);
    glPopMatrix ();

    glFlush();
    auxSwapBuffers();
}

void CALLBACK spinDisplay (void)
{
    spin = spin + 2.0;
    if (spin > 360.0)
    spin = spin - 360.0;
    display();
}

void CALLBACK startIdleFunc (AUX_EVENTREC *event)
{
    auxIdleFunc(spinDisplay);
}

void CALLBACK stopIdleFunc (AUX_EVENTREC *event)
{
    auxIdleFunc(0);
}

void myinit (void)
{
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glColor3f (1.0, 1.0, 1.0);
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w,
        50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0);
    else
    glOrtho (-50.0*(GLfloat)w/(GLfloat)h,
        50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_DOUBLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Double Buffering");
    myinit ();
    auxReshapeFunc (myReshape);
    auxIdleFunc (spinDisplay);
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, startIdleFunc);
    auxMouseFunc (AUX_RIGHTBUTTON, AUX_MOUSEDOWN, stopIdleFunc);
    auxMainLoop(display);
    return(0);
}


DRAWF.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  drawf.c
 *  Draws the bitmapped letter F on the screen (several times).
 *  This demonstrates use of the glBitmap() call.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLubyte rasters[24] = {
    0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00,
    0xff, 0x00, 0xff, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00,
    0xff, 0xc0, 0xff, 0xc0};

void myinit(void)
{
    glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
    glClearColor (0.0, 0.0, 0.0, 0.0);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    glRasterPos2i (20.5, 20.5);
    glBitmap (10, 12, 0.0, 0.0, 12.0, 0.0, rasters);
    glBitmap (10, 12, 0.0, 0.0, 12.0, 0.0, rasters);
    glBitmap (10, 12, 0.0, 0.0, 12.0, 0.0, rasters);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho (0, w, 0, h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Bitmap");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


FEEDBACK.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  feedback.c
 *  This program demonstrates use of OpenGL feedback.  First,
 *  a lighting environment is set up and a few lines are drawn.
 *  Then feedback mode is entered, and the same lines are
 *  drawn.  The results in the feedback buffer are printed.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void drawGeometry (GLenum mode);
void print3DcolorVertex (GLint size, GLint *count, GLfloat *buffer);
void printBuffer(GLint size, GLfloat *buffer);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize lighting.
 */
void myinit(void)
{
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
}

/*  Draw a few lines and two points, one of which will
 *  be clipped.  If in feedback mode, a passthrough token
 *  is issued between the each primitive.
 */
void drawGeometry (GLenum mode)
{
    glBegin (GL_LINE_STRIP);
    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (30.0, 30.0, 0.0);
    glVertex3f (50.0, 60.0, 0.0);
    glVertex3f (70.0, 40.0, 0.0);
    glEnd ();
    if (mode == GL_FEEDBACK)
    glPassThrough (1.0);
    glBegin (GL_POINTS);
    glVertex3f (-100.0, -100.0, -100.0);    /*  will be clipped  */
    glEnd ();
    if (mode == GL_FEEDBACK)
    glPassThrough (2.0);
    glBegin (GL_POINTS);
    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (50.0, 50.0, 0.0);
    glEnd ();
}

/*  Write contents of one vertex to stdout. */
void print3DcolorVertex (GLint size,
    GLint *count, GLfloat *buffer)
{
    int i;

    //printf ("  ");
    for (i = 0; i < 7; i++) {
    //printf ("%4.2f ", buffer[size-(*count)]);
    *count = *count - 1;
    }
    //printf ("\n");
}

/*  Write contents of entire buffer.  (Parse tokens!)   */
void printBuffer(GLint size, GLfloat *buffer)
{
    GLint count;
    GLfloat token;

    count = size;
    while (count) {
    token = buffer[size-count]; count--;
    if (token == GL_PASS_THROUGH_TOKEN) {
        //printf ("GL_PASS_THROUGH_TOKEN\n");
        //printf ("  %4.2f\n", buffer[size-count]);
        count--;
    }
    else if (token == GL_POINT_TOKEN) {
        //printf ("GL_POINT_TOKEN\n");
        print3DcolorVertex (size, &count, buffer);
    }
    else if (token == GL_LINE_TOKEN) {
        //printf ("GL_LINE_TOKEN\n");
        print3DcolorVertex (size, &count, buffer);
        print3DcolorVertex (size, &count, buffer);
    }
    else if (token == GL_LINE_RESET_TOKEN) {
        //printf ("GL_LINE_RESET_TOKEN\n");
        print3DcolorVertex (size, &count, buffer);
        print3DcolorVertex (size, &count, buffer);
    }
    }
}

void CALLBACK display(void)
{
    GLfloat feedBuffer[1024];
    GLint size;

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glOrtho (0.0, 100.0, 0.0, 100.0, 0.0, 1.0);

    glClearColor (0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawGeometry (GL_RENDER);

    glFeedbackBuffer (1024, GL_3D_COLOR, feedBuffer);
    (void) glRenderMode (GL_FEEDBACK);
    drawGeometry (GL_FEEDBACK);

    size = glRenderMode (GL_RENDER);
    printBuffer (size, feedBuffer);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Feedback");
    myinit ();
    auxMainLoop(display);
    return(0);
}


FOG.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  fog.c
 *  This program draws 5 red teapots, each at a different
 *  z distance from the eye, in different types of fog.
 *  Pressing the left mouse button chooses between 3 types of
 *  fog:  exponential, exponential squared, and linear.
 *  In this program, there is a fixed density value, as well
 *  as fixed start and end values for the linear fog.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void renderRedTeapot (GLfloat x, GLfloat y, GLfloat z);
void CALLBACK cycleFog (AUX_EVENTREC *event);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLint fogMode;

void CALLBACK cycleFog (AUX_EVENTREC *event)
{
    if (fogMode == GL_EXP) {
    fogMode = GL_EXP2;
    //printf ("Fog mode is GL_EXP2\n");
    }
    else if (fogMode == GL_EXP2) {
    fogMode = GL_LINEAR;
    //printf ("Fog mode is GL_LINEAR\n");
    glFogf (GL_FOG_START, 1.0);
    glFogf (GL_FOG_END, 5.0);
    }
    else if (fogMode == GL_LINEAR) {
    fogMode = GL_EXP;
    //printf ("Fog mode is GL_EXP\n");
    }
    glFogi (GL_FOG_MODE, fogMode);
}

/*  Initialize z-buffer, projection matrix, light source,
 *  and lighting model.  Do not specify a material property here.
 */
void myinit(void)
{
    GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };
    GLfloat local_view[] = { 0.0 };

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_FOG);
    {
    GLfloat fogColor[4] = {0.5, 0.5, 0.5, 1.0};

    fogMode = GL_EXP;
    glFogi (GL_FOG_MODE, fogMode);
    glFogfv (GL_FOG_COLOR, fogColor);
    glFogf (GL_FOG_DENSITY, 0.35);
    glHint (GL_FOG_HINT, GL_DONT_CARE);
    glClearColor(0.5, 0.5, 0.5, 1.0);
    }
}

void renderRedTeapot (GLfloat x, GLfloat y, GLfloat z)
{
    float mat[3];

    glPushMatrix();
    glTranslatef (x, y, z);
    mat[0] = 0.1745; mat[1] = 0.01175; mat[2] = 0.01175;
    glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
    mat[0] = 0.61424; mat[1] = 0.04136; mat[2] = 0.04136;
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
    mat[0] = 0.727811; mat[1] = 0.626959; mat[2] = 0.626959;
    glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
    glMaterialf (GL_FRONT, GL_SHININESS, 0.6*128.0);
    auxSolidTeapot(1.0);
    glPopMatrix();
}

/*  display() draws 5 teapots at different z positions.
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    renderRedTeapot (-4.0, -0.5, -1.0);
    renderRedTeapot (-2.0, -0.5, -2.0);
    renderRedTeapot (0.0, -0.5, -3.0);
    renderRedTeapot (2.0, -0.5, -4.0);
    renderRedTeapot (4.0, -0.5, -5.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= (h*3))
    glOrtho (-6.0, 6.0, -2.0*((GLfloat) h*3)/(GLfloat) w,
        2.0*((GLfloat) h*3)/(GLfloat) w, 0.0, 10.0);
    else
    glOrtho (-6.0*(GLfloat) w/((GLfloat) h*3),
        6.0*(GLfloat) w/((GLfloat) h*3), -2.0, 2.0, 0.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 450, 150);
    auxInitWindow ("Fog");
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, cycleFog);
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


FOGINDEX.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  fogindex.c
 *  This program demonstrates fog in color index mode.
 *  Three cones are drawn at different z values in a linear
 *  fog.  32 contiguous colors (from 16 to 47) are loaded
 *  with a color ramp.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize color map and fog.  Set screen clear color
 *  to end of color ramp.
 */
#define NMCOLORS 32
#define RAMPSTART 16

void myinit(void)
{
    int i;

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    for (i = 0; i < NMCOLORS; i++) {
    GLfloat shade;
    shade = (GLfloat) (NMCOLORS-i)/(GLfloat) NMCOLORS;
    auxSetOneColor (16 + i, shade, shade, shade);
    }
    glEnable(GL_FOG);

    glFogi (GL_FOG_MODE, GL_LINEAR);
    glFogi (GL_FOG_INDEX, NMCOLORS);
    glFogf (GL_FOG_START, 0.0);
    glFogf (GL_FOG_END, 4.0);
    glHint (GL_FOG_HINT, GL_NICEST);
    glClearIndex((GLfloat) (NMCOLORS+RAMPSTART-1));
}

/*  display() renders 3 cones at different z positions.
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix ();
    glTranslatef (-1.0, -1.0, -1.0);
    glRotatef (-90.0, 1.0, 0.0, 0.0);
    glIndexi (RAMPSTART);
    auxSolidCone(1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.0, -1.0, -2.25);
    glRotatef (-90.0, 1.0, 0.0, 0.0);
    glIndexi (RAMPSTART);
    auxSolidCone(1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (1.0, -1.0, -3.5);
    glRotatef (-90.0, 1.0, 0.0, 0.0);
    glIndexi (RAMPSTART);
    auxSolidCone(1.0, 2.0);
    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w,
        2.0*(GLfloat)h/(GLfloat)w, 0.0, 10.0);
    else
    glOrtho (-2.0*(GLfloat)w/(GLfloat)h,
        2.0*(GLfloat)w/(GLfloat)h, -2.0, 2.0, 0.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_INDEX | AUX_DEPTH16);
    auxInitPosition (0, 0, 200, 200);
    auxInitWindow ("Fog Using Color Index");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


FONT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  font.c
 *
 *  Draws some text in a bitmapped font.  Uses glBitmap()
 *  and other pixel routines.  Also demonstrates use of
 *  display lists.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void makeRasterFont(void);
void printString(char *s);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

GLubyte rasters[][13] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36},
{0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00, 0x00},
{0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18},
{0x00, 0x00, 0x0e, 0x1b, 0xdb, 0x6e, 0x30, 0x18, 0x0c, 0x76, 0xdb, 0xd8, 0x70},
{0x00, 0x00, 0x7f, 0xc6, 0xcf, 0xd8, 0x70, 0x70, 0xd8, 0xcc, 0xcc, 0x6c, 0x38},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x0c, 0x0e},
{0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c},
{0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30},
{0x00, 0x00, 0x00, 0x00, 0x99, 0x5a, 0x3c, 0xff, 0x3c, 0x5a, 0x99, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x00, 0x00},
{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03},
{0x00, 0x00, 0x3c, 0x66, 0xc3, 0xe3, 0xf3, 0xdb, 0xcf, 0xc7, 0xc3, 0x66, 0x3c},
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x38, 0x18},
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0xe7, 0x7e},
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0x07, 0x03, 0x03, 0xe7, 0x7e},
{0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xcc, 0x6c, 0x3c, 0x1c, 0x0c},
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x03, 0x03, 0xff},
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x03, 0x7f, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06},
{0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60},
{0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x03, 0xc3, 0xc3, 0x7e},
{0x00, 0x00, 0x3f, 0x60, 0xcf, 0xdb, 0xd3, 0xdd, 0xc3, 0x7e, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18},
{0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
{0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
{0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc},
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff},
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e},
{0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3},
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3},
{0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3},
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e},
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
{0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c},
{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e},
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff},
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
{0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff},
{0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c},
{0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60},
{0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18},
{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x30, 0x70},
{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0x7f, 0x03, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
{0x00, 0x00, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x03, 0x03, 0x03, 0x03, 0x03},
{0x00, 0x00, 0x7f, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x33, 0x1e},
{0x7e, 0xc3, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0},
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00},
{0x38, 0x6c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x00},
{0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xf0, 0xd8, 0xcc, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0},
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78},
{0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xfe, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00},
{0xc0, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00},
{0x03, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xfe, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xfe, 0x03, 0x03, 0x7e, 0xc0, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x00},
{0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xdb, 0xc3, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
{0xc0, 0x60, 0x60, 0x30, 0x18, 0x3c, 0x66, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0xff, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x0f, 0x18, 0x18, 0x18, 0x38, 0xf0, 0x38, 0x18, 0x18, 0x18, 0x0f},
{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
{0x00, 0x00, 0xf0, 0x18, 0x18, 0x18, 0x1c, 0x0f, 0x1c, 0x18, 0x18, 0x18, 0xf0},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x8f, 0xf1, 0x60, 0x00, 0x00, 0x00}
};

GLuint fontOffset;

void makeRasterFont(void)
{
    GLuint i;
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    fontOffset = glGenLists (128);
    for (i = 32; i < 127; i++) {
    glNewList(i+fontOffset, GL_COMPILE);
    glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, rasters[i-32]);
    glEndList();
    }
}

void myinit(void)
{
    glShadeModel (GL_FLAT);
    makeRasterFont();
}

void printString(char *s)
{
    glPushAttrib (GL_LIST_BIT);
    glListBase(fontOffset);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte *) s);
    glPopAttrib ();
}

/* Everything above this line could be in a library that defines a font.
 * To make it work, you've got to call makeRasterFont() before you start
 * making calls to printString().
 */
void CALLBACK display(void)
{
    GLfloat white[3] = { 1.0, 1.0, 1.0 };
    int i, j;
    char teststring[33];

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3fv(white);
    for (i = 32; i < 127; i += 32) {
    glRasterPos2i(20, 200 - 18*i/32);
    for (j = 0; j < 32; j++)
        teststring[j] = (char) (i+j);
    teststring[32] = 0;
    printString(teststring);
    }
    glRasterPos2i(20, 100);
    printString("The quick brown fox jumps");
    glRasterPos2i(20, 82);
    printString("over a lazy dog.");
    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho (0.0, w, 0.0, h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Display Lists");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


GLOS.H

// GLOS.H
//
// This is an OS specific header file

#include <windows.h>

// disable data conversion warnings

#pragma warning(disable : 4244)     // MIPS
#pragma warning(disable : 4136)     // X86
#pragma warning(disable : 4051)     // ALPHA
#pragma warning(disable : 4305)


LIGHT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  light.c
 *  This program demonstrates the use of the OpenGL lighting
 *  model.  A sphere is drawn using a grey material characteristic.
 *  A single light source illuminates the object.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);

/*  Initialize material property, light source, lighting model,
 *  and depth buffer.
 */
void myinit(void)
{
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    auxSolidSphere(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
        1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
        1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


LINELIST.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  linelist.c
 *  This program demonstrates using display lists to call
 *  different line stipples.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);

#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \
    glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();

GLuint offset;

void myinit (void)
{
/*  background to be cleared to black   */
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);

    offset = glGenLists (3);
    glNewList (offset, GL_COMPILE);
    glDisable (GL_LINE_STIPPLE);
    glEndList ();
    glNewList (offset+1, GL_COMPILE);
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x0F0F);
    glEndList ();
    glNewList (offset+2, GL_COMPILE);
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x1111);
    glEndList ();
}

void CALLBACK display(void)
{
    glClear (GL_COLOR_BUFFER_BIT);

/*  draw all lines in white */
    glColor3f (1.0, 1.0, 1.0);

    glCallList (offset);
    drawOneLine (50.0, 125.0, 350.0, 125.0);
    glCallList (offset+1);
    drawOneLine (50.0, 100.0, 350.0, 100.0);
    glCallList (offset+2);
    drawOneLine (50.0, 75.0, 350.0, 75.0);
    glCallList (offset+1);
    drawOneLine (50.0, 50.0, 350.0, 50.0);
    glCallList (offset);
    drawOneLine (50.0, 25.0, 350.0, 25.0);
    glFlush ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 150);
    auxInitWindow ("Display Lists");
    myinit ();
    auxMainLoop(display);
    return(0);
}


LINES.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  lines.c
 *  This program demonstrates different line stipples and widths.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);

#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \
    glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();

void myinit (void) {
    /*  background to be cleared to black   */
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);
}

void CALLBACK display(void)
{
    int i;

    glClear (GL_COLOR_BUFFER_BIT);
/*  draw all lines in white */
    glColor3f (1.0, 1.0, 1.0);

/*  in 1st row, 3 lines drawn, each with a different stipple    */
    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0x0101);  /*  dotted  */
    drawOneLine (50.0, 125.0, 150.0, 125.0);
    glLineStipple (1, 0x00FF);  /*  dashed  */
    drawOneLine (150.0, 125.0, 250.0, 125.0);
    glLineStipple (1, 0x1C47);  /*  dash/dot/dash   */
    drawOneLine (250.0, 125.0, 350.0, 125.0);

/*  in 2nd row, 3 wide lines drawn, each with different stipple */
    glLineWidth (5.0);
    glLineStipple (1, 0x0101);
    drawOneLine (50.0, 100.0, 150.0, 100.0);
    glLineStipple (1, 0x00FF);
    drawOneLine (150.0, 100.0, 250.0, 100.0);
    glLineStipple (1, 0x1C47);
    drawOneLine (250.0, 100.0, 350.0, 100.0);
    glLineWidth (1.0);

/*  in 3rd row, 6 lines drawn, with dash/dot/dash stipple,  */
/*  as part of a single connect line strip          */
    glLineStipple (1, 0x1C47);
    glBegin (GL_LINE_STRIP);
    for (i = 0; i < 7; i++)
    glVertex2f (50.0 + ((GLfloat) i * 50.0), 75.0);
    glEnd ();

/*  in 4th row, 6 independent lines drawn,  */
/*  with dash/dot/dash stipple          */
    for (i = 0; i < 6; i++) {
    drawOneLine (50.0 + ((GLfloat) i * 50.0),
        50.0, 50.0 + ((GLfloat)(i+1) * 50.0), 50.0);
    }

/*  in 5th row, 1 line drawn, with dash/dot/dash stipple    */
/*  and repeat factor of 5          */
    glLineStipple (5, 0x1C47);
    drawOneLine (50.0, 25.0, 350.0, 25.0);
    glFlush ();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 150);
    auxInitWindow ("Lines");
    myinit ();
    auxMainLoop(display);
    return(0);
}


LIST.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  list.c
 *  This program demonstrates how to make and execute a
 *  display list.  Note that attributes, such as current
 *  color and matrix, are changed.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawLine (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLuint listName = 1;

void myinit (void)
{
    glNewList (listName, GL_COMPILE);
    glColor3f (1.0, 0.0, 0.0);
    glBegin (GL_TRIANGLES);
    glVertex2f (0.0, 0.0);
    glVertex2f (1.0, 0.0);
    glVertex2f (0.0, 1.0);
    glEnd ();
    glTranslatef (1.5, 0.0, 0.0);
    glEndList ();
    glShadeModel (GL_FLAT);
}

void drawLine (void)
{
    glBegin (GL_LINES);
    glVertex2f (0.0, 0.5);
    glVertex2f (15.0, 0.5);
    glEnd ();
}

void CALLBACK display(void)
{
    GLuint i;

    glClear (GL_COLOR_BUFFER_BIT);
    glColor3f (0.0, 1.0, 0.0);
    glPushMatrix();
    for (i = 0; i < 10; i++)
        glCallList (listName);
    drawLine ();
    glPopMatrix();
    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        gluOrtho2D (0.0, 2.0, -0.5 * (GLfloat) h/(GLfloat) w,
                1.5 * (GLfloat) h/(GLfloat) w);
    else
        gluOrtho2D (0.0, 2.0 * (GLfloat) w/(GLfloat) h, -0.5, 1.5);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 50);
    auxInitWindow ("Display List");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


LIST2.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  list2.c
 *  This program demonstrates glGenList() and glPushAttrib().
 *  The matrix and color are restored, before the line is drawn.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawLine (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLuint listName;

void myinit (void)
{
    GLfloat color_vector[3] = {1.0, 0.0, 0.0};

    listName = glGenLists (1);
    glNewList (listName, GL_COMPILE);
    glPushAttrib (GL_CURRENT_BIT);
    glColor3fv (color_vector);
    glBegin (GL_TRIANGLES);
    glVertex2f (0.0, 0.0);
    glVertex2f (1.0, 0.0);
    glVertex2f (0.0, 1.0);
    glEnd ();
    glTranslatef (1.5, 0.0, 0.0);
    glPopAttrib ();
    glEndList ();
    glShadeModel (GL_FLAT);
}

void drawLine (void)
{
    glBegin (GL_LINES);
    glVertex2f (0.0, 0.5);
    glVertex2f (15.0, 0.5);
    glEnd ();
}

void CALLBACK display(void)
{
    GLuint i;
    GLfloat new_color[3] = {0.0, 1.0, 0.0};

    glClear (GL_COLOR_BUFFER_BIT);
    glColor3fv (new_color);
    glPushMatrix ();
    for (i = 0; i < 10; i++)
    glCallList (listName);
    glPopMatrix ();
    drawLine ();
    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    gluOrtho2D (0.0, 2.0, -0.5 * (GLfloat) h/(GLfloat) w,
        1.5 * (GLfloat) h/(GLfloat) w);
    else
    gluOrtho2D (0.0, 2.0 * (GLfloat) w/(GLfloat) h, -0.5, 1.5);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 50);
    auxInitWindow ("Display List");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


MAPLIGHT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  maplight.c
 *  This program demonstrates the use of the GL lighting model.
 *  A sphere is drawn using a magenta diffuse reflective and
 *  white specular material property.
 *  A single light source illuminates the object.  This program
 *  illustrates lighting in color map mode.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize material property, light source, and lighting model.
 */
void myinit(void)
{
    GLint i;

    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
    GLfloat mat_colormap[] = { 16.0, 48.0, 79.0 };
    GLfloat mat_shininess[] = { 10.0 };

    glMaterialfv(GL_FRONT, GL_COLOR_INDEXES, mat_colormap);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    for (i = 0; i < 32; i++) {
    auxSetOneColor (16 + i, 1.0 * (i/32.0), 0.0, 1.0 * (i/32.0));
    auxSetOneColor (48 + i, 1.0, 1.0 * (i/32.0), 1.0);
    }
    glClearIndex(0);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    auxSolidSphere(1.0);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
        1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
        1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar, color
 *  index display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_INDEX | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Lighting in Color Map Mode");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


MATERIAL.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 * material.c
 * This program demonstrates the use of the GL lighting model.
 * Several objects are drawn using different material characteristics.
 * A single light source illuminates the objects.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize z-buffer, projection matrix, light source,
 *  and lighting model.  Do not specify a material property here.
 */
void myinit(void)
{
    GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 3.0, 2.0, 0.0 };
    GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
    GLfloat local_view[] = { 0.0 };

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glClearColor(0.0, 0.1, 0.1, 0.0);
}

/*  Draw twelve spheres in 3 rows with 4 columns.
 *  The spheres in the first row have materials with no ambient reflection.
 *  The second row has materials with significant ambient reflection.
 *  The third row has materials with colored ambient reflection.
 *
 *  The first column has materials with blue, diffuse reflection only.
 *  The second column has blue diffuse reflection, as well as specular
 *  reflection with a low shininess exponent.
 *  The third column has blue diffuse reflection, as well as specular
 *  reflection with a high shininess exponent (a more concentrated highlight).
 *  The fourth column has materials which also include an emissive component.
 *
 *  glTranslatef() is used to move spheres to their appropriate locations.
 */

void CALLBACK display(void)
{
    GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
    GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };
    GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat no_shininess[] = { 0.0 };
    GLfloat low_shininess[] = { 5.0 };
    GLfloat high_shininess[] = { 100.0 };
    GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/*  draw sphere in first row, first column
 *  diffuse reflection only; no ambient or specular
 */
    glPushMatrix();
    glTranslatef (-3.75, 3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in first row, second column
 *  diffuse and specular reflection; low shininess; no ambient
 */
    glPushMatrix();
    glTranslatef (-1.25, 3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in first row, third column
 *  diffuse and specular reflection; high shininess; no ambient
 */
    glPushMatrix();
    glTranslatef (1.25, 3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in first row, fourth column
 *  diffuse reflection; emission; no ambient or specular reflection
 */
    glPushMatrix();
    glTranslatef (3.75, 3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in second row, first column
 *  ambient and diffuse reflection; no specular
 */
    glPushMatrix();
    glTranslatef (-3.75, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in second row, second column
 *  ambient, diffuse and specular reflection; low shininess
 */
    glPushMatrix();
    glTranslatef (-1.25, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in second row, third column
 *  ambient, diffuse and specular reflection; high shininess
 */
    glPushMatrix();
    glTranslatef (1.25, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in second row, fourth column
 *  ambient and diffuse reflection; emission; no specular
 */
    glPushMatrix();
    glTranslatef (3.75, 0.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in third row, first column
 *  colored ambient and diffuse reflection; no specular
 */
    glPushMatrix();
    glTranslatef (-3.75, -3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in third row, second column
 *  colored ambient, diffuse and specular reflection; low shininess
 */
    glPushMatrix();
    glTranslatef (-1.25, -3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in third row, third column
 *  colored ambient, diffuse and specular reflection; high shininess
 */
    glPushMatrix();
    glTranslatef (1.25, -3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
    auxSolidSphere(1.0);
    glPopMatrix();

/*  draw sphere in third row, fourth column
 *  colored ambient and diffuse reflection; emission; no specular
 */
    glPushMatrix();
    glTranslatef (3.75, -3.0, 0.0);
    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
    glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
    glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
    auxSolidSphere(1.0);
    glPopMatrix();

    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= (h * 2))
    glOrtho (-6.0, 6.0, -3.0*((GLfloat)h*2)/(GLfloat)w,
        3.0*((GLfloat)h*2)/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-6.0*(GLfloat)w/((GLfloat)h*2),
        6.0*(GLfloat)w/((GLfloat)h*2), -3.0, 3.0, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 600, 450);
    auxInitWindow ("Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


MIPMAP.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  mipmap.c
 *  This program demonstrates using mipmaps for texture maps.
 *  To overtly show the effect of mipmaps, each mipmap reduction
 *  level has a solidly colored, contrasting texture image.
 *  Thus, the quadrilateral which is drawn is drawn with several
 *  different colors.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void makeImages(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLubyte mipmapImage32[32][32][3];
GLubyte mipmapImage16[16][16][3];
GLubyte mipmapImage8[8][8][3];
GLubyte mipmapImage4[4][4][3];
GLubyte mipmapImage2[2][2][3];
GLubyte mipmapImage1[1][1][3];

void makeImages(void)
{
    int i, j;

    for (i = 0; i < 32; i++) {
    for (j = 0; j < 32; j++) {
        mipmapImage32[i][j][0] = 255;
        mipmapImage32[i][j][1] = 255;
        mipmapImage32[i][j][2] = 0;
    }
    }
    for (i = 0; i < 16; i++) {
    for (j = 0; j < 16; j++) {
        mipmapImage16[i][j][0] = 255;
        mipmapImage16[i][j][1] = 0;
        mipmapImage16[i][j][2] = 255;
    }
    }
    for (i = 0; i < 8; i++) {
    for (j = 0; j < 8; j++) {
        mipmapImage8[i][j][0] = 255;
        mipmapImage8[i][j][1] = 0;
        mipmapImage8[i][j][2] = 0;
    }
    }
    for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++) {
        mipmapImage4[i][j][0] = 0;
        mipmapImage4[i][j][1] = 255;
        mipmapImage4[i][j][2] = 0;
    }
    }
    for (i = 0; i < 2; i++) {
    for (j = 0; j < 2; j++) {
        mipmapImage2[i][j][0] = 0;
        mipmapImage2[i][j][1] = 0;
        mipmapImage2[i][j][2] = 255;
    }
    }
    mipmapImage1[0][0][0] = 255;
    mipmapImage1[0][0][1] = 255;
    mipmapImage1[0][0][2] = 255;
}

void myinit(void)
{
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glShadeModel(GL_FLAT);

    glTranslatef(0.0, 0.0, -3.6);
    makeImages();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, 32, 32, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage32[0][0][0]);
    glTexImage2D(GL_TEXTURE_2D, 1, 3, 16, 16, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage16[0][0][0]);
    glTexImage2D(GL_TEXTURE_2D, 2, 3, 8, 8, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage8[0][0][0]);
    glTexImage2D(GL_TEXTURE_2D, 3, 3, 4, 4, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage4[0][0][0]);
    glTexImage2D(GL_TEXTURE_2D, 4, 3, 2, 2, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage2[0][0][0]);
    glTexImage2D(GL_TEXTURE_2D, 5, 3, 1, 1, 0,
         GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage1[0][0][0]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_NEAREST_MIPMAP_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glEnable(GL_TEXTURE_2D);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
    glTexCoord2f(0.0, 8.0); glVertex3f(-2.0, 1.0, 0.0);
    glTexCoord2f(8.0, 8.0); glVertex3f(2000.0, 1.0, -6000.0);
    glTexCoord2f(8.0, 0.0); glVertex3f(2000.0, -1.0, -6000.0);
    glEnd();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Texture Map Using Mipmap");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


MODEL.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  model.c
 *  This program demonstrates the use of OpenGL modeling
 *  transformations.  Four triangles are drawn, each with
 *  a different transformation.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void draw_triangle(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

void draw_triangle(void)
{
    glBegin(GL_LINE_LOOP);
    glVertex2f(0.0, 25.0);
    glVertex2f(25.0, -25.0);
    glVertex2f(-25.0, -25.0);
    glEnd();
}

/*  Clear the screen.  For each triangle, set the current
 *  color and modify the modelview matrix.
 */
void CALLBACK display(void)
{
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glClear (GL_COLOR_BUFFER_BIT);

    glLoadIdentity ();
    glColor3f (1.0, 1.0, 1.0);
    draw_triangle ();

    glEnable (GL_LINE_STIPPLE);
    glLineStipple (1, 0xF0F0);
    glLoadIdentity ();
    glTranslatef (-20.0, 0.0, 0.0);
    draw_triangle ();

    glLineStipple (1, 0xF00F);
    glLoadIdentity ();
    glScalef (1.5, 0.5, 1.0);
    draw_triangle ();

    glLineStipple (1, 0x8888);
    glLoadIdentity ();
    glRotatef (90.0, 0.0, 0.0, 1.0);
    draw_triangle ();
    glDisable (GL_LINE_STIPPLE);

    glFlush();
}

void myinit (void)
{
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w,
        50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0);
    else
    glOrtho (-50.0*(GLfloat)w/(GLfloat)h,
        50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Modeling Transformations");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


MOVELGHT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  movelight.c
 *  This program demonstrates when to issue lighting and
 *  transformation commands to render a model with a light
 *  which is moved by a modeling transformation (rotate or
 *  translate).  The light position is reset after the modeling
 *  transformation is called.  The eye position does not change.
 *
 *  A sphere is drawn using a grey material characteristic.
 *  A single light source illuminates the object.
 *
 *  Interaction:  pressing the left or middle mouse button
 *  alters the modeling transformation (x rotation) by 30 degrees.
 *  The scene is then redrawn with the light in a new position.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK movelight (AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

static int spin = 0;

void CALLBACK movelight (AUX_EVENTREC *event)
{
    spin = (spin + 30) % 360;
}

void myinit (void)
{
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

/*  Here is where the light position is reset after the modeling
 *  transformation (glRotated) is called.  This places the
 *  light at a new position in world coordinates.  The cube
 *  represents the position of the light.
 */
void CALLBACK display(void)
{
    GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 };

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix ();
    glTranslatef (0.0, 0.0, -5.0);

    glPushMatrix ();
    glRotated ((GLdouble) spin, 1.0, 0.0, 0.0);
    glRotated (0.0, 1.0, 0.0, 0.0);
    glLightfv (GL_LIGHT0, GL_POSITION, position);

    glTranslated (0.0, 0.0, 1.5);
    glDisable (GL_LIGHTING);
    glColor3f (0.0, 1.0, 1.0);
    auxWireCube (0.1);
    glEnable (GL_LIGHTING);
    glPopMatrix ();

    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();
    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Moving Light");
    myinit();
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, movelight);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


NURBS.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  nurbs.c
 *  This program shows a NURBS (Non-uniform rational B-splines)
 *  surface, shaped like a heart.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

#define S_NUMPOINTS 13
#define S_ORDER     3
#define S_NUMKNOTS  (S_NUMPOINTS + S_ORDER)
#define T_NUMPOINTS 3
#define T_ORDER     3
#define T_NUMKNOTS  (T_NUMPOINTS + T_ORDER)
#define SQRT2    1.41421356237309504880

/* initialized local data */

GLfloat sknots[S_NUMKNOTS] =
    {-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0,
      4.0,  5.0,  6.0, 7.0, 8.0, 9.0, 9.0, 9.0};
GLfloat tknots[T_NUMKNOTS] = {1.0, 1.0, 1.0, 2.0, 2.0, 2.0};

GLfloat ctlpoints[S_NUMPOINTS][T_NUMPOINTS][4] = {
{   {4.,2.,2.,1.},{4.,1.6,2.5,1.},{4.,2.,3.0,1.}    },
{   {5.,4.,2.,1.},{5.,4.,2.5,1.},{5.,4.,3.0,1.} },
{   {6.,5.,2.,1.},{6.,5.,2.5,1.},{6.,5.,3.0,1.} },
{   {SQRT2*6.,SQRT2*6.,SQRT2*2.,SQRT2},
    {SQRT2*6.,SQRT2*6.,SQRT2*2.5,SQRT2},
    {SQRT2*6.,SQRT2*6.,SQRT2*3.0,SQRT2}  },
{   {5.2,6.7,2.,1.},{5.2,6.7,2.5,1.},{5.2,6.7,3.0,1.}   },
{   {SQRT2*4.,SQRT2*6.,SQRT2*2.,SQRT2},
    {SQRT2*4.,SQRT2*6.,SQRT2*2.5,SQRT2},
    {SQRT2*4.,SQRT2*6.,SQRT2*3.0,SQRT2}  },
{   {4.,5.2,2.,1.},{4.,4.6,2.5,1.},{4.,5.2,3.0,1.}  },
{   {SQRT2*4.,SQRT2*6.,SQRT2*2.,SQRT2},
    {SQRT2*4.,SQRT2*6.,SQRT2*2.5,SQRT2},
    {SQRT2*4.,SQRT2*6.,SQRT2*3.0,SQRT2}  },
{   {2.8,6.7,2.,1.},{2.8,6.7,2.5,1.},{2.8,6.7,3.0,1.}   },
{   {SQRT2*2.,SQRT2*6.,SQRT2*2.,SQRT2},
    {SQRT2*2.,SQRT2*6.,SQRT2*2.5,SQRT2},
    {SQRT2*2.,SQRT2*6.,SQRT2*3.0,SQRT2}  },
{   {2.,5.,2.,1.},{2.,5.,2.5,1.},{2.,5.,3.0,1.} },
{   {3.,4.,2.,1.},{3.,4.,2.5,1.},{3.,4.,3.0,1.} },
{   {4.,2.,2.,1.},{4.,1.6,2.5,1.},{4.,2.,3.0,1.}    }
};

GLUnurbsObj *theNurb;

/*  Initialize material property, light source, lighting model,
 *  and depth buffer.
 */
void myinit(void)
{
    GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_diffuse[] = { 1.0, 0.2, 1.0, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };

    GLfloat light0_position[] = { 1.0, 0.1, 1.0, 0.0 };
    GLfloat light1_position[] = { -1.0, 0.1, 1.0, 0.0 };

    GLfloat lmodel_ambient[] = { 0.3, 0.3, 0.3, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
    glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHT1);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_AUTO_NORMAL);

    theNurb = gluNewNurbsRenderer();

    glNewList (1, GL_COMPILE);
    gluBeginSurface(theNurb);
    gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
    gluNurbsSurface(theNurb,
        S_NUMKNOTS, sknots,
        T_NUMKNOTS, tknots,
        4 * T_NUMPOINTS,
        4,
        &ctlpoints[0][0][0],
        S_ORDER, T_ORDER,
        GL_MAP2_VERTEX_4);
    gluEndSurface(theNurb);
    glEndList ();

}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);

    glPushMatrix();
    glTranslatef (4., 4.5, 2.5);
    glRotatef (220.0, 1., 0., 0.);
    glRotatef (115.0, 0., 1., 0.);
    glTranslatef (-4., -4.5, -2.5);
    glCallList (1);
    glPopMatrix();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-2.0, 2.0, -2.0, 2.0, 0.8, 10.0);
    gluLookAt(7.0,4.5,4.0, 4.5,4.5,2.0, 6.0,-3.0,2.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("NURBS Surface");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PICKDPTH.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  pickdepth.c
 *  Picking is demonstrated in this program.  In
 *  rendering mode, three overlapping rectangles are
 *  drawn.  When the left mouse button is pressed,
 *  selection mode is entered with the picking matrix.
 *  Rectangles which are drawn under the cursor position
 *  are "picked."  Pay special attention to the depth
 *  value range, which is returned.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void drawRects(GLenum mode);
void processHits (GLint hits, GLuint buffer[]);
void CALLBACK pickRects(AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_FLAT);
    glDepthRange (0.0, 1.0);    /*  The default z mapping   */
}

/*  The three rectangles are drawn.  In selection mode,
 *  each rectangle is given the same name.  Note that
 *  each rectangle is drawn with a different z value.
 */
void drawRects(GLenum mode)
{
    if (mode == GL_SELECT)
    glLoadName (1);
    glBegin (GL_QUADS);
    glColor3f (1.0, 1.0, 0.0);
    glVertex3i (2, 0, 0);
    glVertex3i (2, 6, 0);
    glVertex3i (6, 6, 0);
    glVertex3i (6, 0, 0);
    glColor3f (0.0, 1.0, 1.0);
    glVertex3i (3, 2, -1);
    glVertex3i (3, 8, -1);
    glVertex3i (8, 8, -1);
    glVertex3i (8, 2, -1);
    glColor3f (1.0, 0.0, 1.0);
    glVertex3i (0, 2, -2);
    glVertex3i (0, 7, -2);
    glVertex3i (5, 7, -2);
    glVertex3i (5, 2, -2);
    glEnd ();
}

/*  processHits() prints out the contents of the
 *  selection array.
 */
void processHits (GLint hits, GLuint buffer[])
{
    int i;
    GLint names, *ptr;

    //printf ("hits = %d\n", hits);
    ptr = (GLuint *) buffer;
    for (i = 0; i < hits; i++) {    /*  for each hit  */
        names = *ptr;
        //printf (" number of names for hit = %d\n", names); ptr++;
        //printf ("  z1 is %u;", *ptr); ptr++;
        //printf (" z2 is %u\n", *ptr); ptr++;
        //printf ("   the name is ");
/*
        for (j = 0; j < names; j++) {
            printf ("%d ", *ptr); ptr++;
        }
*/
        //printf ("\n");
    }
}

/*  pickRects() sets up selection mode, name stack,
 *  and projection matrix for picking.  Then the objects
 *  are drawn.
 */
#define BUFSIZE 512

void CALLBACK pickRects(AUX_EVENTREC *event)
{
    GLuint selectBuf[BUFSIZE];
    GLint hits;
    GLint viewport[4];
    int x, y;

    x = event->data[AUX_MOUSEX];
    y = event->data[AUX_MOUSEY];
    glGetIntegerv (GL_VIEWPORT, viewport);

    glSelectBuffer (BUFSIZE, selectBuf);
    (void) glRenderMode (GL_SELECT);

    glInitNames();
    glPushName((unsigned)-1);

    glMatrixMode (GL_PROJECTION);
    glPushMatrix ();
    glLoadIdentity ();
/*  create 5x5 pixel picking region near cursor location    */
    gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y),
    5.0, 5.0, viewport);
    glOrtho (0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
    drawRects (GL_SELECT);
    glPopMatrix ();
    glFlush ();

    hits = glRenderMode (GL_RENDER);
    //processHits (hits, selectBuf);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawRects (GL_RENDER);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho (0.0, 8.0, 0.0, 8.0, 0.0, 2.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Picking Rectangles");
    myinit ();
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, pickRects);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PICKLINE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  pickline.c
 *  Picking is demonstrated here.  Press the left mouse button
 *  is enter picking mode.  You get two hits if you press the
 *  mouse, while the cursor is where the lines intersect.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void drawLine(GLenum mode);
void CALLBACK pickLine(AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

void drawLine(GLenum mode)
{
    if (mode == GL_SELECT)
    glLoadName (1);
    glBegin (GL_LINES);
    glColor3f (1.0, 1.0, 1.0);
    glVertex3f (30.0, 30.0, 0.0);
    glVertex3f (50.0, 60.0, 0.0);
    glEnd ();

    if (mode == GL_SELECT)
    glLoadName (2);
    glBegin (GL_LINES);
    glColor3f (1.0, 1.0, 1.0);
    glVertex3f (50.0, 60.0, 0.0);
    glVertex3f (70.0, 40.0, 0.0);
    glEnd ();
}

/*  pickline() is called when the mouse is pressed.
 *  The projection matrix is reloaded to include the
 *  picking matrix.  The line is "redrawn" during
 *  selection mode, and names are sent to the buffer.
 */
#define BUFSIZE 512

void CALLBACK pickLine(AUX_EVENTREC *event)
{
    GLuint selectBuf[BUFSIZE];
    GLint hits;
    GLint viewport[4];
    int x, y;

    x = event->data[AUX_MOUSEX];
    y = event->data[AUX_MOUSEY];
    glGetIntegerv (GL_VIEWPORT, viewport);

    glSelectBuffer (BUFSIZE, selectBuf);
    (void) glRenderMode (GL_SELECT);

    glInitNames();
    glPushName((GLuint)-1);

    glMatrixMode (GL_PROJECTION);
    glPushMatrix ();
    glLoadIdentity ();
    gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y),
    5.0, 5.0, viewport);
    gluOrtho2D (0.0, (GLdouble) viewport[2],
    0.0, (GLdouble) viewport[3]);
    drawLine (GL_SELECT);
    glPopMatrix ();
    glFlush ();

    hits = glRenderMode (GL_RENDER);
    //printf ("hits is %d\n", hits);
}

void myinit (void)
{
    glShadeModel (GL_FLAT);
    glClearColor (0.0, 0.0, 0.0, 0.0);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    drawLine (GL_RENDER);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Picking Lines");
    myinit ();
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, pickLine);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PICKSQR.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  picksquare.c
 *  Use of multiple names and picking are demonstrated.
 *  A 3x3 grid of squares is drawn.  When the left mouse
 *  button is pressed, all squares under the cursor position
 *  have their color changed.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void processHits (GLint hits, GLuint buffer[]);
void CALLBACK pickSquares(AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

int board[3][3];    /*  amount of color for each square */

/*  Clear color value for every square on the board     */
void myinit(void)
{
    int i, j;
    for (i = 0; i < 3; i++)
    for (j = 0; j < 3; j ++)
        board[i][j] = 0;
    glClearColor (0.0, 0.0, 0.0, 0.0);
}

/*  The nine squares are drawn.  In selection mode, each
 *  square is given two names:  one for the row and the
 *  other for the column on the grid.  The color of each
 *  square is determined by its position on the grid, and
 *  the value in the board[][] array.
 */
void drawSquares(GLenum mode)
{
    GLuint i, j;
    for (i = 0; i < 3; i++) {
    if (mode == GL_SELECT)
        glLoadName (i);
    for (j = 0; j < 3; j ++) {
        if (mode == GL_SELECT)
        glPushName (j);
        glColor3f ((GLfloat) i/3.0, (GLfloat) j/3.0,
            (GLfloat) board[i][j]/3.0);
        glRecti (i, j, i+1, j+1);
        if (mode == GL_SELECT)
        glPopName ();
    }
    }
}

/*  processHits() prints out the contents of the
 *  selection array.
 */
void processHits (GLint hits, GLuint buffer[])
{
    int i, j;
    GLint ii, jj, names, *ptr;

    //printf ("hits = %d\n", hits);
    ptr = (GLuint *) buffer;
    for (i = 0; i < hits; i++) {    /*  for each hit  */
    names = *ptr;
    //printf (" number of names for this hit = %d\n", names);
    ptr++;
    //printf ("  z1 is %u;", *ptr);
    ptr++;
    //printf (" z2 is %u\n", *ptr);
    ptr++;
    //printf ("   names are ");
    for (j = 0; j < names; j++) {   /*  for each name */
        //printf ("%d ", *ptr);
        if (j == 0) /*  set row and column  */
        ii = *ptr;
        else if (j == 1)
        jj = *ptr;
        ptr++;
    }
    //printf ("\n");
    board[ii][jj] = (board[ii][jj] + 1) % 3;
    }
}

/*  pickSquares() sets up selection mode, name stack,
 *  and projection matrix for picking.  Then the
 *  objects are drawn.
 */
#define BUFSIZE 512

void CALLBACK pickSquares(AUX_EVENTREC *event)
{
    GLuint selectBuf[BUFSIZE];
    GLint hits;
    GLint viewport[4];
    int x, y;

    x = event->data[AUX_MOUSEX];
    y = event->data[AUX_MOUSEY];
    glGetIntegerv (GL_VIEWPORT, viewport);

    glSelectBuffer (BUFSIZE, selectBuf);
    (void) glRenderMode (GL_SELECT);

    glInitNames();
    glPushName((unsigned)-1);

    glMatrixMode (GL_PROJECTION);
    glPushMatrix ();
    glLoadIdentity ();
/*  create 5x5 pixel picking region near cursor location    */
    gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y),
    5.0, 5.0, viewport);
    gluOrtho2D (0.0, 3.0, 0.0, 3.0);
    drawSquares (GL_SELECT);

    glMatrixMode (GL_PROJECTION);
    glPopMatrix ();
    glFlush ();

    hits = glRenderMode (GL_RENDER);
    processHits (hits, selectBuf);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    drawSquares (GL_RENDER);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D (0.0, 3.0, 0.0, 3.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 100, 100);
    auxInitWindow ("Picking Squares");
    myinit ();
    auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, pickSquares);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PLANE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  plane.c
 *  This program demonstrates the use of local versus
 *  infinite lighting on a flat plane.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawPlane(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize material property, light source, and lighting model.
 */
void myinit(void)
{
    GLfloat mat_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
/*   mat_specular and mat_shininess are NOT default values  */
    GLfloat mat_diffuse[] = { 0.4, 0.4, 0.4, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 15.0 };

    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };

    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void drawPlane(void)
{
    glBegin (GL_QUADS);
    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (-1.0, -1.0, 0.0);
    glVertex3f (0.0, -1.0, 0.0);
    glVertex3f (0.0, 0.0, 0.0);
    glVertex3f (-1.0, 0.0, 0.0);

    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (0.0, -1.0, 0.0);
    glVertex3f (1.0, -1.0, 0.0);
    glVertex3f (1.0, 0.0, 0.0);
    glVertex3f (0.0, 0.0, 0.0);

    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (0.0, 0.0, 0.0);
    glVertex3f (1.0, 0.0, 0.0);
    glVertex3f (1.0, 1.0, 0.0);
    glVertex3f (0.0, 1.0, 0.0);

    glNormal3f (0.0, 0.0, 1.0);
    glVertex3f (0.0, 0.0, 0.0);
    glVertex3f (0.0, 1.0, 0.0);
    glVertex3f (-1.0, 1.0, 0.0);
    glVertex3f (-1.0, 0.0, 0.0);
    glEnd();
}

void CALLBACK display (void)
{
    GLfloat infinite_light[] = { 1.0, 1.0, 1.0, 0.0 };
    GLfloat local_light[] = { 1.0, 1.0, 1.0, 1.0 };

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glTranslatef (-1.5, 0.0, 0.0);
    glLightfv (GL_LIGHT0, GL_POSITION, infinite_light);
    drawPlane ();
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (1.5, 0.0, 0.0);
    glLightfv (GL_LIGHT0, GL_POSITION, local_light);
    drawPlane ();
    glPopMatrix ();
    glFlush ();
}

void CALLBACK myReshape (GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport (0, 0, w, h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    if (w <= h)
    glOrtho (-1.5, 1.5, -1.5*(GLdouble)h/(GLdouble)w,
        1.5*(GLdouble)h/(GLdouble)w, -10.0, 10.0);
    else
    glOrtho (-1.5*(GLdouble)w/(GLdouble)h,
        1.5*(GLdouble)w/(GLdouble)h, -1.5, 1.5, -10.0, 10.0);
    glMatrixMode (GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 200);
    auxInitWindow ("Local vs Infinite Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PLANET.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  planet.c
 *  This program shows how to composite modeling transformations
 *  to draw translated and rotated models.
 *  Interaction:  pressing the left, right, up, and down
 *  arrow keys alters the rotation of the planet around the sun.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawPlane(void);
void CALLBACK dayAdd (void);
void CALLBACK daySubtract (void);
void CALLBACK yearAdd (void);
void CALLBACK yearSubtract (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

static int year = 0, day = 0;

void CALLBACK dayAdd (void)
{
    day = (day + 10) % 360;
}

void CALLBACK daySubtract (void)
{
    day = (day - 10) % 360;
}

void CALLBACK yearAdd (void)
{
    year = (year + 5) % 360;
}

void CALLBACK yearSubtract (void)
{
    year = (year - 5) % 360;
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f (1.0, 1.0, 1.0);
    glPushMatrix();
/*  draw sun    */
    auxWireSphere(1.0);
/*  draw smaller planet */
    glRotatef ((GLfloat) year, 0.0, 1.0, 0.0);
    glTranslatef (2.0, 0.0, 0.0);
    glRotatef ((GLfloat) day, 0.0, 1.0, 0.0);
    auxWireSphere(0.2);
    glPopMatrix();
    glFlush();
}

void myinit (void) {
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (0.0, 0.0, -5.0);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Composite Modeling Transformations");
    myinit ();
    auxKeyFunc (AUX_LEFT, yearSubtract);
    auxKeyFunc (AUX_RIGHT, yearAdd);
    auxKeyFunc (AUX_UP, dayAdd);
    auxKeyFunc (AUX_DOWN, daySubtract);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


PLANETUP.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  planetup.c
 *  The planets (from planet.c) have been rotated so their
 *  polar regions are north/south.
 *  Interaction:  pressing the left, right, up, and down
 *  arrow keys alters the rotation of the planet around the sun.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawPlane(void);
void CALLBACK dayAdd (void);
void CALLBACK daySubtract (void);
void CALLBACK yearAdd (void);
void CALLBACK yearSubtract (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

static int year = 0, day = 0;

void CALLBACK dayAdd (void)
{
    day = (day + 10) % 360;
}

void CALLBACK daySubtract (void)
{
    day = (day - 10) % 360;
}

void CALLBACK yearAdd (void)
{
    year = (year + 5) % 360;
}

void CALLBACK yearSubtract (void)
{
    year = (year - 5) % 360;
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f (1.0, 1.0, 1.0);
    glPushMatrix();
/*  draw sun    */
    glPushMatrix();
    glRotatef (90.0, 1.0, 0.0, 0.0);  /*  rotate it upright  */
    auxWireSphere(1.0);
    glPopMatrix();
/*  draw smaller planet */
    glRotatef ((GLfloat) year, 0.0, 1.0, 0.0);
    glTranslatef (2.0, 0.0, 0.0);
    glRotatef ((GLfloat) day, 0.0, 1.0, 0.0);
    glRotatef (90.0, 1.0, 0.0, 0.0);  /*  rotate it upright  */
    auxWireSphere(0.2);
    glPopMatrix();
    glFlush();
}

void myinit (void) {
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (0.0, 0.0, -5.0);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Composite Modeling Transformations");
    myinit ();
    auxKeyFunc (AUX_LEFT, yearSubtract);
    auxKeyFunc (AUX_RIGHT, yearAdd);
    auxKeyFunc (AUX_UP, dayAdd);
    auxKeyFunc (AUX_DOWN, daySubtract);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


POLYS.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  polys.c
 *  This program demonstrates polygon stippling.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);

void CALLBACK display(void)
{
    GLubyte fly[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60, 0x04, 0x60, 0x06, 0x20,
0x04, 0x30, 0x0C, 0x20, 0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22, 0x44, 0x01, 0x80, 0x22,
0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x66, 0x01, 0x80, 0x66,
0x33, 0x01, 0x80, 0xCC, 0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0, 0x03, 0x31, 0x8c, 0xc0,
0x03, 0x33, 0xcc, 0xc0, 0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08, 0x10, 0x63, 0xC6, 0x08,
0x10, 0x30, 0x0c, 0x08, 0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08};

    GLubyte halftone[] = {
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA,
0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55};

    glClear (GL_COLOR_BUFFER_BIT);

/*  draw all polygons in white  */
    glColor3f (1.0, 1.0, 1.0);

/*  draw one solid, unstippled rectangle,   */
/*  then two stippled rectangles        */
    glRectf (25.0, 25.0, 125.0, 125.0);
    glEnable (GL_POLYGON_STIPPLE);
    glPolygonStipple (fly);
    glRectf (125.0, 25.0, 225.0, 125.0);
    glPolygonStipple (halftone);
    glRectf (225.0, 25.0, 325.0, 125.0);
    glDisable (GL_POLYGON_STIPPLE);

    glFlush ();
}

void myinit (void)
{
/*  clear background to black   */
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 350, 150);
    auxInitWindow ("Polygon Stippling");
    myinit ();
    auxMainLoop(display);
    return(0);
}


ROBOT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  robot.c
 *  This program shows how to composite modeling transformations
 *  to draw translated and rotated hierarchical models.
 *  Interaction:  pressing the arrow keys alters the rotation
 *  of robot arm.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void drawPlane(void);
void CALLBACK elbowAdd (void);
void CALLBACK elbowSubtract (void);
void CALLBACK shoulderAdd (void);
void CALLBACK shoulderSubtract (void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

static int shoulder = 0, elbow = 0;

void CALLBACK elbowAdd (void)
{
    elbow = (elbow + 5) % 360;
}

void CALLBACK elbowSubtract (void)
{
    elbow = (elbow - 5) % 360;
}

void CALLBACK shoulderAdd (void)
{
    shoulder = (shoulder + 5) % 360;
}

void CALLBACK shoulderSubtract (void)
{
    shoulder = (shoulder - 5) % 360;
}

void CALLBACK display(void)
{

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);

    glPushMatrix();
    glTranslatef (-1.0, 0.0, 0.0);
    glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0);
    glTranslatef (1.0, 0.0, 0.0);
    auxWireBox(2.0, 0.4, 1.0);

    glTranslatef (1.0, 0.0, 0.0);
    glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
    glTranslatef (1.0, 0.0, 0.0);
    auxWireBox(2.0, 0.4, 1.0);

    glPopMatrix();
    glFlush();
}

void myinit (void)
{
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (0.0, 0.0, -5.0);  /* viewing transform  */
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Composite Modeling Transformations");

    myinit ();

    auxKeyFunc (AUX_LEFT, shoulderSubtract);
    auxKeyFunc (AUX_RIGHT, shoulderAdd);
    auxKeyFunc (AUX_UP, elbowAdd);
    auxKeyFunc (AUX_DOWN, elbowSubtract);
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


SCCLRLT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  sccolorlight.c
 *  This program demonstrates the use of a colored
 *  (magenta, in this example) light source.  Objects
 *  are drawn using a grey material characteristic.
 *  A single light source illuminates the objects.
 */
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize material property and light source.
 */
void myinit(void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 0.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 0.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix ();
    glRotatef (20.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.75, 0.5, 0.0);
    glRotatef (90.0, 1.0, 0.0, 0.0);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.5, 0.0);
    glRotatef (270.0, 1.0, 0.0, 0.0);
    auxSolidCone (1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.0, -1.0);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
        2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
        2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Colored Light Source");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


SCENE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  scene.c
 *  This program demonstrates the use of the GL lighting model.
 *  Objects are drawn using a grey material characteristic.
 *  A single light source illuminates the objects.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize material property and light source.
 */
void myinit (void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv (GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv (GL_LIGHT0, GL_POSITION, light_position);

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display (void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glRotatef (20.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.75, 0.5, 0.0);
    glRotatef (90.0, 1.0, 0.0, 0.0);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.5, 0.0);
    glRotatef (270.0, 1.0, 0.0, 0.0);
    auxSolidCone (1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.0, -1.0);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPopMatrix ();
    glFlush ();
}

void CALLBACK myReshape (GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport (0, 0, w, h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    if (w <= h)
    glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
        2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
        2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
    glMatrixMode (GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Lighting");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop (display);
    return(0);
}


SCENEBMB.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  scenebamb.c
 *  This program demonstrates use of a blue ambient light
 *  source.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize light source and lighting.
 */
void myinit(void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 1.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glRotatef (20.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.75, 0.5, 0.0);
    glRotatef (90.0, 1.0, 0.0, 0.0);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.5, 0.0);
    glRotatef (270.0, 1.0, 0.0, 0.0);
    auxSolidCone (1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.0, -1.0);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
        2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
        2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Blue Ambient Light Source");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


SCENEFLT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  sceneflat.c
 *  This program draws lighted objects with flat shading.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize light source and shading model (GL_FLAT).
 */
void myinit(void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel (GL_FLAT);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glRotatef (20.0, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glTranslatef (-0.75, 0.5, 0.0);
    glRotatef (90.0, 1.0, 0.0, 0.0);
    auxSolidTorus (0.275, 0.85);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (-0.75, -0.5, 0.0);
    glRotatef (270.0, 1.0, 0.0, 0.0);
    auxSolidCone (1.0, 2.0);
    glPopMatrix ();

    glPushMatrix ();
    glTranslatef (0.75, 0.0, -1.0);
    auxSolidSphere (1.0);
    glPopMatrix ();

    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-2.5, 2.5, -2.5*(GLfloat)h/(GLfloat)w,
        2.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-2.5*(GLfloat)w/(GLfloat)h,
        2.5*(GLfloat)w/(GLfloat)h, -2.5, 2.5, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Flat Shading");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


SELECT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  select.c
 *  This is an illustration of the selection mode and
 *  name stack, which detects whether objects which collide
 *  with a viewing volume.  First, four triangles and a
 *  rectangular box representing a viewing volume are drawn
 *  (drawScene routine).  The green triangle and yellow
 *  triangles appear to lie within the viewing volume, but
 *  the red triangle appears to lie outside it.  Then the
 *  selection mode is entered (selectObjects routine).
 *  Drawing to the screen ceases.  To see if any collisions
 *  occur, the four triangles are called.  In this example,
 *  the green triangle causes one hit with the name 1, and
 *  the yellow triangles cause one hit with the name 3.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <stdio.h>

void myinit(void);
void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2,
    GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z);
void drawViewVolume (GLfloat x1, GLfloat x2, GLfloat y1,
    GLfloat y2, GLfloat z1, GLfloat z2);
void drawScene (void);
void processHits (GLint hits, GLuint buffer[]);
void selectObjects(void);
void CALLBACK display(void);

/*  draw a triangle with vertices at (x1, y1), (x2, y2)
 *  and (x3, y3) at z units away from the origin.
 */
void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2,
    GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z)
{
    glBegin (GL_TRIANGLES);
    glVertex3f (x1, y1, z);
    glVertex3f (x2, y2, z);
    glVertex3f (x3, y3, z);
    glEnd ();
}

/*  draw a rectangular box with these outer x, y, and z values  */
void drawViewVolume (GLfloat x1, GLfloat x2, GLfloat y1,
    GLfloat y2, GLfloat z1, GLfloat z2)
{
    glColor3f (1.0, 1.0, 1.0);
    glBegin (GL_LINE_LOOP);
    glVertex3f (x1, y1, -z1);
    glVertex3f (x2, y1, -z1);
    glVertex3f (x2, y2, -z1);
    glVertex3f (x1, y2, -z1);
    glEnd ();

    glBegin (GL_LINE_LOOP);
    glVertex3f (x1, y1, -z2);
    glVertex3f (x2, y1, -z2);
    glVertex3f (x2, y2, -z2);
    glVertex3f (x1, y2, -z2);
    glEnd ();

    glBegin (GL_LINES); /*  4 lines */
    glVertex3f (x1, y1, -z1);
    glVertex3f (x1, y1, -z2);
    glVertex3f (x1, y2, -z1);
    glVertex3f (x1, y2, -z2);
    glVertex3f (x2, y1, -z1);
    glVertex3f (x2, y1, -z2);
    glVertex3f (x2, y2, -z1);
    glVertex3f (x2, y2, -z2);
    glEnd ();
}

/*  drawScene() draws 4 triangles and a wire frame
 *  which represents the viewing volume.
 */
void drawScene (void)
{
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (40.0, 4.0/3.0, 0.01, 100.0);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    gluLookAt (7.5, 7.5, 12.5, 2.5, 2.5, -5.0, 0.0, 1.0, 0.0);
    glColor3f (0.0, 1.0, 0.0);  /*  green triangle  */
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
    glColor3f (1.0, 0.0, 0.0);  /*  red triangle    */
    drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
    glColor3f (1.0, 1.0, 0.0);  /*  yellow triangles    */
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0);
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0);
    drawViewVolume (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
}

/*  processHits() prints out the contents of the selection array.
 */
void processHits (GLint hits, GLuint buffer[])
{
    int i;
    GLint names, *ptr;

    //printf ("hits = %d\n", hits);
    ptr = (GLuint *) buffer;
    for (i = 0; i < hits; i++) {    /*  for each hit  */
        names = *ptr;
        //printf (" number of names for hit = %d\n", names); ptr++;
        //printf ("  z1 is %u;", *ptr); ptr++;
        //printf (" z2 is %u\n", *ptr); ptr++;
        //printf ("   the name is ");
/*
        for (j = 0; j < names; j++) {
            //printf ("%d ", *ptr); ptr++;
        }
*/
        //printf ("\n");
    }
}

/*  selectObjects() "draws" the triangles in selection mode,
 *  assigning names for the triangles.  Note that the third
 *  and fourth triangles share one name, so that if either
 *  or both triangles intersects the viewing/clipping volume,
 *  only one hit will be registered.
 */
#define BUFSIZE 512

void selectObjects(void)
{
    GLuint selectBuf[BUFSIZE];
    GLint hits;

    glSelectBuffer (BUFSIZE, selectBuf);
    (void) glRenderMode (GL_SELECT);

    glInitNames();
    glPushName((unsigned)-1);

    glPushMatrix ();
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glOrtho (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    glLoadName(1);
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
    glLoadName(2);
    drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
    glLoadName(3);
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0);
    drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0);
    glPopMatrix ();
    glFlush ();

    hits = glRenderMode (GL_RENDER);
    processHits (hits, selectBuf);
}

void myinit (void)
{
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_FLAT);
}

void CALLBACK display(void)
{
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawScene ();
    selectObjects ();
    glFlush();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, depth buffer, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 200, 200);
    auxInitWindow ("Selection Mode");
    myinit ();
    auxMainLoop(display);
    return(0);
}


SIMPLE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  simple.c
 *  This program draws a white rectangle on a black background.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glaux.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Simple OpengGL Sample");

    glClearColor (0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex2f(-0.5, -0.5);
        glVertex2f(-0.5, 0.5);
        glVertex2f(0.5, 0.5);
        glVertex2f(0.5, -0.5);
    glEnd();
    glFlush();
    _sleep (10000);
    return(0);
}


SMOOTH.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 * smooth.c
 * This program demonstrates smooth shading.
 * A smooth shaded polygon is drawn in a 2-D projection.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void triangle(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  GL_SMOOTH is actually the default shading model.  */
void myinit (void)
{
    glShadeModel (GL_SMOOTH);
}

void triangle(void)
{
    glBegin (GL_TRIANGLES);
    glColor3f (1.0, 0.0, 0.0);
    glVertex2f (5.0, 5.0);
    glColor3f (0.0, 1.0, 0.0);
    glVertex2f (25.0, 5.0);
    glColor3f (0.0, 0.0, 1.0);
    glVertex2f (5.0, 25.0);
    glEnd ();
}

void CALLBACK display(void)
{
    glClear (GL_COLOR_BUFFER_BIT);
    triangle ();
    glFlush ();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    gluOrtho2D (0.0, 30.0, 0.0, 30.0 * (GLfloat) h/(GLfloat) w);
    else
    gluOrtho2D (0.0, 30.0 * (GLfloat) w/(GLfloat) h, 0.0, 30.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Smooth Shading");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


SPHERE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  sphere.c
 *  This program draws a wire frame sphere.  It uses
 *  glTranslatef() as a viewing transformation.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

void CALLBACK display (void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    glPushMatrix();
/*  glTranslatef() as viewing transformation    */
    glTranslatef (0.0, 0.0, -5.0);
    auxWireSphere(1.0);
    glPopMatrix();
    glFlush();
}

void myinit (void)
{
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Sphere");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


STENCIL.C

/* Copyright (c) Mark J. Kilgard, 1994. */

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
/*  stencil.c
 *  This program draws two rotated tori in a window.
 *  A diamond in the center of the window masks out part
 *  of the scene.  Within this mask, a different model
 *  (a sphere) is drawn in a different color.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

#define YELLOWMAT   1
#define BLUEMAT 2

void myinit (void)
{
    GLfloat yellow_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
    GLfloat yellow_specular[] = { 1.0, 1.0, 1.0, 1.0 };

    GLfloat blue_diffuse[] = { 0.1, 0.1, 0.7, 1.0 };
    GLfloat blue_specular[] = { 0.1, 1.0, 1.0, 1.0 };

    GLfloat position_one[] = { 1.0, 1.0, 1.0, 0.0 };

    glNewList(YELLOWMAT, GL_COMPILE);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, yellow_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, yellow_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 64.0);
    glEndList();

    glNewList(BLUEMAT, GL_COMPILE);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, blue_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, blue_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 45.0);
    glEndList();

    glLightfv(GL_LIGHT0, GL_POSITION, position_one);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    glClearStencil(0x0);
    glEnable(GL_STENCIL_TEST);

}

/*  Draw a sphere in a diamond-shaped section in the
 *  middle of a window with 2 tori.
 */
GLsizei width, height;

void CALLBACK display(void)
{
#if 0
    if (!h) return;
    glViewport(0, 0, w, h);
#endif

    glClear(GL_STENCIL_BUFFER_BIT);
/* create a diamond shaped stencil area */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-3.0, 3.0, -3.0, 3.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glStencilFunc (GL_ALWAYS, 0x1, 0x1);
    glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glBegin(GL_QUADS);
glVertex3f (-1.0, 0.0, 0.0);
glVertex3f (0.0, 1.0, 0.0);
glVertex3f (1.0, 0.0, 0.0);
glVertex3f (0.0, -1.0, 0.0);
    glEnd();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (GLfloat) width/(GLfloat) height, 3.0, 7.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -5.0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/* draw blue sphere where the stencil is 1 */
    glStencilFunc (GL_EQUAL, 0x1, 0x1);
    glCallList (BLUEMAT);
    auxSolidSphere (0.5);

/* draw the tori where the stencil is not 1 */
    glStencilFunc (GL_NOTEQUAL, 0x1, 0x1);
    glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
    glPushMatrix();
glRotatef (45.0, 0.0, 0.0, 1.0);
glRotatef (45.0, 0.0, 1.0, 0.0);
glCallList (YELLOWMAT);
        auxSolidTorus (0.275, 0.85);
glPushMatrix();
    glRotatef (90.0, 1.0, 0.0, 0.0);
            auxSolidTorus (0.275, 0.85);
glPopMatrix();
    glPopMatrix();

    glFlush();
}

/*  Whenever the window is reshaped, redefine the
 *  coordinate system and redraw the stencil area.
 */
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);

    width = w;
    height = h;

    glClear(GL_STENCIL_BUFFER_BIT);
/* create a diamond shaped stencil area */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-3.0, 3.0, -3.0, 3.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glStencilFunc (GL_ALWAYS, 0x1, 0x1);
    glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glBegin(GL_QUADS);
glVertex3f (-1.0, 0.0, 0.0);
glVertex3f (0.0, 1.0, 0.0);
glVertex3f (1.0, 0.0, 0.0);
glVertex3f (0.0, -1.0, 0.0);
    glEnd();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (GLfloat) w/(GLfloat) h, 3.0, 7.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -5.0);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB
    | AUX_DEPTH16 | AUX_STENCIL);
    auxInitPosition (0, 0, 400, 400);
    auxInitWindow ("Stencil Test");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


STROKE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  stroke.c
 *  This program demonstrates some characters of a
 *  stroke (vector) font.  The characters are represented
 *  by display lists, which are given numbers which
 *  correspond to the ASCII values of the characters.
 *  Use of glCallLists() is demonstrated.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

typedef struct charpoint {
    GLfloat   x, y;
    int    type;
} CP;

void myinit(void);
void drawLetter(CP *l);
void printStrokedString(char *s);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

#define PT 1
#define STROKE 2
#define END 3

CP Adata[] = {
    { 0, 0, PT}, {0, 9, PT}, {1, 10, PT}, {4, 10, PT},
    {5, 9, PT}, {5, 0, STROKE}, {0, 5, PT}, {5, 5, END}
};

CP Edata[] = {
    {5, 0, PT}, {0, 0, PT}, {0, 10, PT}, {5, 10, STROKE},
    {0, 5, PT}, {4, 5, END}
};

CP Pdata[] = {
    {0, 0, PT}, {0, 10, PT},  {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
    {4, 5, PT}, {0, 5, END}
};

CP Rdata[] = {
    {0, 0, PT}, {0, 10, PT},  {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
    {4, 5, PT}, {0, 5, STROKE}, {3, 5, PT}, {5, 0, END}
};

CP Sdata[] = {
    {0, 1, PT}, {1, 0, PT}, {4, 0, PT}, {5, 1, PT}, {5, 4, PT},
    {4, 5, PT}, {1, 5, PT}, {0, 6, PT}, {0, 9, PT}, {1, 10, PT},
    {4, 10, PT}, {5, 9, END}
};

/*  drawLetter() interprets the instructions from the array
 *  for that letter and renders the letter with line segments.
 */
void drawLetter(CP *l)
{
    glBegin(GL_LINE_STRIP);
    while (1) {
    switch (l->type) {
        case PT:
        glVertex2fv(&l->x);
        break;
        case STROKE:
        glVertex2fv(&l->x);
        glEnd();
        glBegin(GL_LINE_STRIP);
        break;
        case END:
        glVertex2fv(&l->x);
        glEnd();
        glTranslatef(8.0, 0.0, 0.0);
        return;
    }
    l++;
    }
}

/*  Create a display list for each of 6 characters  */
void myinit (void)
{
    GLuint base;

    glShadeModel (GL_FLAT);

    base = glGenLists (128);
    glListBase(base);
    glNewList(base+'A', GL_COMPILE); drawLetter(Adata); glEndList();
    glNewList(base+'E', GL_COMPILE); drawLetter(Edata); glEndList();
    glNewList(base+'P', GL_COMPILE); drawLetter(Pdata); glEndList();
    glNewList(base+'R', GL_COMPILE); drawLetter(Rdata); glEndList();
    glNewList(base+'S', GL_COMPILE); drawLetter(Sdata); glEndList();
    glNewList(base+' ', GL_COMPILE); glTranslatef(8.0, 0.0, 0.0); glEndList();
}

char *test1 = "A SPARE SERAPE APPEARS AS";
char *test2 = "APES PREPARE RARE PEPPERS";

void printStrokedString(char *s)
{
    GLsizei len = strlen(s);
    glCallLists(len, GL_BYTE, (GLbyte *)s);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    glTranslatef(10.0, 30.0, 0.0);
    printStrokedString(test1);
    glPopMatrix();
    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    glTranslatef(10.0, 13.0, 0.0);
    printStrokedString(test2);
    glPopMatrix();
    glFlush();
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 440, 120);
    auxInitWindow ("Stroke Font");
    myinit ();
    auxMainLoop(display);
    return(0);
}


SURFACE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  surface.c
 *  This program draws a NURBS surface in the shape of a
 *  symmetrical hill.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void init_surface(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLfloat ctlpoints[4][4][3];

GLUnurbsObj *theNurb;

/*
 *  Initializes the control points of the surface to a small hill.
 *  The control points range from -3 to +3 in x, y, and z
 */
void init_surface(void)
{
    int u, v;
    for (u = 0; u < 4; u++) {
    for (v = 0; v < 4; v++) {
        ctlpoints[u][v][0] = 2.0*((GLfloat)u - 1.5);
        ctlpoints[u][v][1] = 2.0*((GLfloat)v - 1.5);

        if ( (u == 1 || u == 2) && (v == 1 || v == 2))
        ctlpoints[u][v][2] = 3.0;
        else
        ctlpoints[u][v][2] = -3.0;
    }
    }
}

/*  Initialize material property and depth buffer.
 */
void myinit(void)
{
    GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 100.0 };

    glClearColor (0.0, 0.0, 0.0, 1.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    init_surface();

    theNurb = gluNewNurbsRenderer();
    gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
    gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
}

void CALLBACK display(void)
{
    GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
    glRotatef(330.0, 1.,0.,0.);
    glScalef (0.5, 0.5, 0.5);

    gluBeginSurface(theNurb);
    gluNurbsSurface(theNurb,
        8, knots,
        8, knots,
        4 * 3,
        3,
        &ctlpoints[0][0][0],
        4, 4,
        GL_MAP2_VERTEX_3);
    gluEndSurface(theNurb);

    glPopMatrix();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (0.0, 0.0, -5.0);
}

/*  Main Loop
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("NURBS Surface");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TEA.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  tea.c
 *  This program demonstrates two-sided lighting and compares
 *  it with one-sided lighting.  Three teapots are drawn, with
 *  a clipping plane to expose the interior of the objects.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize light source.
 */
void myinit(void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    GLdouble eqn[4] = {1.0, 0.0, -1.0, 1.0};
    GLfloat two_side_on[] = { GL_TRUE };
    GLfloat two_side_off[] = { GL_FALSE };
    GLfloat mat_diffuse[] = { 0.8, 0.8, 0.8, 1.0 };
    GLfloat back_diffuse[] = { 0.8, 0.2, 0.8, 1.0 };

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix ();
    glClipPlane (GL_CLIP_PLANE0, eqn);  /*  slice objects   */
    glEnable (GL_CLIP_PLANE0);

    glPushMatrix ();
    glTranslatef (0.0, 2.0, 0.0);
    auxSolidTeapot(1.0);    /*  one-sided lighting  */
    glPopMatrix ();

    /*  two-sided lighting, but same material   */
    glLightModelf (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);
    glPushMatrix ();
    glTranslatef (0.0, 0.0, 0.0);
    auxSolidTeapot(1.0);
    glPopMatrix ();

    /*  two-sided lighting, two different materials */
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv (GL_BACK, GL_DIFFUSE, back_diffuse);
    glPushMatrix ();
    glTranslatef (0.0, -2.0, 0.0);
    auxSolidTeapot(1.0);
    glPopMatrix ();

    glLightModelf (GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
    glDisable (GL_CLIP_PLANE0);
    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
        4.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-4.0*(GLfloat)w/(GLfloat)h,
        4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Two Sided Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TEAAMB.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  teaambient.c
 *  This program renders three lighted, shaded teapots, with
 *  different ambient values.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize light source and lighting model.
 */
void myinit(void)
{
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
/*  light_position is NOT default value */
    GLfloat light_position[] = { 1.0, 0.0, 0.0, 0.0 };
    GLfloat global_ambient[] = { 0.75, 0.75, 0.75, 1.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void)
{
    GLfloat low_ambient[] = { 0.1, 0.1, 0.1, 1.0 };
    GLfloat more_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
    GLfloat most_ambient[] = { 1.0, 1.0, 1.0, 1.0 };

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /*  material has small ambient reflection   */
    glMaterialfv(GL_FRONT, GL_AMBIENT, low_ambient);
    glMaterialf(GL_FRONT, GL_SHININESS, 40.0);
    glPushMatrix ();
    glTranslatef (0.0, 2.0, 0.0);
    auxSolidTeapot(1.0);
    glPopMatrix ();

    /*  material has moderate ambient reflection    */
    glMaterialfv(GL_FRONT, GL_AMBIENT, more_ambient);
    glPushMatrix ();
    glTranslatef (0.0, 0.0, 0.0);
    auxSolidTeapot(1.0);
    glPopMatrix ();

    /*  material has large ambient reflection   */
    glMaterialfv(GL_FRONT, GL_AMBIENT, most_ambient);
    glPushMatrix ();
    glTranslatef (0.0, -2.0, 0.0);
    auxSolidTeapot(1.0);
    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
        4.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
    else
    glOrtho (-4.0*(GLfloat)w/(GLfloat)h,
        4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Ambient Lighting");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TEAPOTS.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  teapots.c
 *  This program demonstrates lots of material properties.
 *  A single light source illuminates the objects.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void renderTeapot (GLfloat x, GLfloat y,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

/*  Initialize depth buffer, projection matrix, light source,
 *  and lighting model.  Do not specify a material property here.
 */
void myinit(void)
{
    GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position[] = { 0.0, 3.0, 3.0, 0.0 };

    GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat local_view[] = { 0.0 };

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glFrontFace (GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
}

/*  Move object into position.  Use 3rd through 12th parameters
 *  to specify the material property.  Draw a teapot.
 */
void renderTeapot (GLfloat x, GLfloat y,
    GLfloat ambr, GLfloat ambg, GLfloat ambb,
    GLfloat difr, GLfloat difg, GLfloat difb,
    GLfloat specr, GLfloat specg, GLfloat specb, GLfloat shine)
{
    float mat[4];

    glPushMatrix();
    glTranslatef (x, y, 0.0);
    mat[0] = ambr; mat[1] = ambg; mat[2] = ambb; mat[3] = 1.0;
    glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
    mat[0] = difr; mat[1] = difg; mat[2] = difb;
    glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
    mat[0] = specr; mat[1] = specg; mat[2] = specb;
    glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
    glMaterialf (GL_FRONT, GL_SHININESS, shine*128.0);
    auxSolidTeapot(1.0);
    glPopMatrix();
}

/*  First column:  emerald, jade, obsidian, pearl, ruby, turquoise
 *  2nd column:  brass, bronze, chrome, copper, gold, silver
 *  3rd column:  black, cyan, green, red, white, yellow plastic
 *  4th column:  black, cyan, green, red, white, yellow rubber
 */
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    renderTeapot (2.0, 17.0, 0.0215, 0.1745, 0.0215,
    0.07568, 0.61424, 0.07568, 0.633, 0.727811, 0.633, 0.6);
    renderTeapot (2.0, 14.0, 0.135, 0.2225, 0.1575,
    0.54, 0.89, 0.63, 0.316228, 0.316228, 0.316228, 0.1);
    renderTeapot (2.0, 11.0, 0.05375, 0.05, 0.06625,
    0.18275, 0.17, 0.22525, 0.332741, 0.328634, 0.346435, 0.3);
    renderTeapot (2.0, 8.0, 0.25, 0.20725, 0.20725,
    1, 0.829, 0.829, 0.296648, 0.296648, 0.296648, 0.088);
    renderTeapot (2.0, 5.0, 0.1745, 0.01175, 0.01175,
    0.61424, 0.04136, 0.04136, 0.727811, 0.626959, 0.626959, 0.6);
    renderTeapot (2.0, 2.0, 0.1, 0.18725, 0.1745,
    0.396, 0.74151, 0.69102, 0.297254, 0.30829, 0.306678, 0.1);
    renderTeapot (6.0, 17.0, 0.329412, 0.223529, 0.027451,
    0.780392, 0.568627, 0.113725, 0.992157, 0.941176, 0.807843,
    0.21794872);
    renderTeapot (6.0, 14.0, 0.2125, 0.1275, 0.054,
    0.714, 0.4284, 0.18144, 0.393548, 0.271906, 0.166721, 0.2);
    renderTeapot (6.0, 11.0, 0.25, 0.25, 0.25,
    0.4, 0.4, 0.4, 0.774597, 0.774597, 0.774597, 0.6);
    renderTeapot (6.0, 8.0, 0.19125, 0.0735, 0.0225,
    0.7038, 0.27048, 0.0828, 0.256777, 0.137622, 0.086014, 0.1);
    renderTeapot (6.0, 5.0, 0.24725, 0.1995, 0.0745,
    0.75164, 0.60648, 0.22648, 0.628281, 0.555802, 0.366065, 0.4);
    renderTeapot (6.0, 2.0, 0.19225, 0.19225, 0.19225,
    0.50754, 0.50754, 0.50754, 0.508273, 0.508273, 0.508273, 0.4);
    renderTeapot (10.0, 17.0, 0.0, 0.0, 0.0, 0.01, 0.01, 0.01,
    0.50, 0.50, 0.50, .25);
    renderTeapot (10.0, 14.0, 0.0, 0.1, 0.06, 0.0, 0.50980392, 0.50980392,
    0.50196078, 0.50196078, 0.50196078, .25);
    renderTeapot (10.0, 11.0, 0.0, 0.0, 0.0,
    0.1, 0.35, 0.1, 0.45, 0.55, 0.45, .25);
    renderTeapot (10.0, 8.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0,
    0.7, 0.6, 0.6, .25);
    renderTeapot (10.0, 5.0, 0.0, 0.0, 0.0, 0.55, 0.55, 0.55,
    0.70, 0.70, 0.70, .25);
    renderTeapot (10.0, 2.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0,
    0.60, 0.60, 0.50, .25);
    renderTeapot (14.0, 17.0, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01,
    0.4, 0.4, 0.4, .078125);
    renderTeapot (14.0, 14.0, 0.0, 0.05, 0.05, 0.4, 0.5, 0.5,
    0.04, 0.7, 0.7, .078125);
    renderTeapot (14.0, 11.0, 0.0, 0.05, 0.0, 0.4, 0.5, 0.4,
    0.04, 0.7, 0.04, .078125);
    renderTeapot (14.0, 8.0, 0.05, 0.0, 0.0, 0.5, 0.4, 0.4,
    0.7, 0.04, 0.04, .078125);
    renderTeapot (14.0, 5.0, 0.05, 0.05, 0.05, 0.5, 0.5, 0.5,
    0.7, 0.7, 0.7, .078125);
    renderTeapot (14.0, 2.0, 0.05, 0.05, 0.0, 0.5, 0.5, 0.4,
    0.7, 0.7, 0.04, .078125);
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (0.0, 16.0, 0.0, 16.0*(GLfloat)h/(GLfloat)w,
        -10.0, 10.0);
    else
    glOrtho (0.0, 16.0*(GLfloat)w/(GLfloat)h, 0.0, 16.0,
        -10.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

/*  Main Loop
 *  Open window with initial window size, title bar,
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 600);
    auxInitWindow ("Material Properties");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TEXGEN.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  texgen.c
 *  This program draws a texture mapped teapot with
 *  automatically generated texture coordinates.  The
 *  texture is rendered as stripes on the teapot.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void makeStripeImage(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

#define stripeImageWidth 32
GLubyte stripeImage[3*stripeImageWidth];

void makeStripeImage(void)
{
    int j;

    for (j = 0; j < stripeImageWidth; j++) {
        stripeImage[3*j] = (j<=4) ? 255 : 0;
        stripeImage[3*j+1] = (j>4) ? 255 : 0;
        stripeImage[3*j+2] = 0;
    }
}

/* glTexGen stuff: */

GLfloat sgenparams[] = {1.0, 1.0, 1.0, 0.0};

void myinit(void)
{
    glClearColor (0.0, 0.0, 0.0, 0.0);

    makeStripeImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage1D(GL_TEXTURE_1D, 0, 3, stripeImageWidth, 0,
         GL_RGB, GL_UNSIGNED_BYTE, stripeImage);

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    glTexGenfv(GL_S, GL_OBJECT_PLANE, sgenparams);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_1D);
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glFrontFace(GL_CW);
    glCullFace(GL_BACK);
    glMaterialf (GL_FRONT, GL_SHININESS, 64.0);
}

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix ();
    glRotatef(45.0, 0.0, 0.0, 1.0);
    auxSolidTeapot(2.0);
    glPopMatrix ();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho (-3.5, 3.5, -3.5*(GLfloat)h/(GLfloat)w,
        3.5*(GLfloat)h/(GLfloat)w, -3.5, 3.5);
    else
    glOrtho (-3.5*(GLfloat)w/(GLfloat)h,
        3.5*(GLfloat)w/(GLfloat)h, -3.5, 3.5, -3.5, 3.5);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 200, 200);
    auxInitWindow ("Texture Map");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TEXSURF.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*  texturesurf.c
 *  This program uses evaluators to generate a curved
 *  surface and automatically generated texture coordinates.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <math.h>

void myinit(void);
void makeImage(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLfloat ctrlpoints[4][4][3] = {
    {{ -1.5, -1.5, 4.0}, { -0.5, -1.5, 2.0},
    {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
    {{ -1.5, -0.5, 1.0}, { -0.5, -0.5, 3.0},
    {0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
    {{ -1.5, 0.5, 4.0}, { -0.5, 0.5, 0.0},
    {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
    {{ -1.5, 1.5, -2.0}, { -0.5, 1.5, -2.0},
    {0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};

GLfloat texpts[2][2][2] = {{{0.0, 0.0}, {0.0, 1.0}},
            {{1.0, 0.0}, {1.0, 1.0}}};

void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    glEvalMesh2(GL_FILL, 0, 20, 0, 20);
    glFlush();
}

#define imageWidth 64
#define imageHeight 64
GLubyte image[3*imageWidth*imageHeight];

void makeImage(void)
{
    int i, j;
    float ti, tj;

    for (i = 0; i < imageWidth; i++) {
    ti = 2.0*3.14159265*i/imageWidth;
    for (j = 0; j < imageHeight; j++) {
        tj = 2.0*3.14159265*j/imageHeight;

        image[3*(imageHeight*i+j)] = (GLubyte) 127*(1.0+sin(ti));
        image[3*(imageHeight*i+j)+1] = (GLubyte) 127*(1.0+cos(2*tj));
        image[3*(imageHeight*i+j)+2] = (GLubyte) 127*(1.0+cos(ti+tj));
    }
    }
}

void myinit(void)
{
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
        0, 1, 12, 4, &ctrlpoints[0][0][0]);
    glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2,
        0, 1, 4, 2, &texpts[0][0][0]);
    glEnable(GL_MAP2_TEXTURE_COORD_2);
    glEnable(GL_MAP2_VERTEX_3);
    glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
    makeImage();
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth, imageHeight, 0,
         GL_RGB, GL_UNSIGNED_BYTE, image);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_NORMALIZE);
    glShadeModel (GL_FLAT);
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
    glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w,
        4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
    else
    glOrtho(-4.0*(GLfloat)w/(GLfloat)h,
        4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(85.0, 1.0, 1.0, 1.0);
}

int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 300, 300);
    auxInitWindow ("Evaluators");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


TRIM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

/*
 *  trim.c
 *  This program draws a NURBS surface in the shape of a
 *  symmetrical hill, using both a NURBS curve and pwl
 *  (piecewise linear) curve to trim part of the surface.
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void);
void init_surface(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

GLfloat ctlpoints[4][4][3];

GLUnurbsObj *theNurb;

/*
 *  Initializes the control points of the surface to a small hill.
 *  The control points range from -3 to +3 in x, y, and z
 */
void init_surface(void)
{
    int u, v;
    for (u = 0; u < 4; u++) {
    for (v = 0; v < 4; v++) {
        ctlpoints[u][v][0] = 2.0*((GLfloat)u - 1.5);
        ctlpoints[u][v][1] = 2.0*((GLfloat)v - 1.5);

        if ( (u == 1 || u == 2) && (v == 1 || v == 2))
        ctlpoints[u][v][2] = 3.0;
        else
        ctlpoints[u][v][2] = -3.0;
    }
    }
}

/*  Initialize material property and depth buffer.
 */
void myinit(void)
{
    GLfloat mat_diffuse[] = { 0.6, 0.6, 0.6, 1.0 };
    GLfloat mat_specular[] = { 0.9, 0.9, 0.9, 1.0 };
    GLfloat mat_shininess[] = { 128.0 };

    glClearColor (0.0, 0.0, 0.0, 1.0);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    init_surface();

    theNurb = gluNewNurbsRenderer();
    gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 50.0);
    gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
}

void CALLBACK display(void)
{
    GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
    GLfloat edgePt[5][2] = /* counter clockwise */
    {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.0}};
    GLfloat curvePt[4][2] = /* clockwise */
    {{0.25, 0.5}, {0.25, 0.75}, {0.75, 0.75}, {0.75, 0.5}};
    GLfloat curveKnots[8] =
    {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
    GLfloat pwlPt[4][2] = /* clockwise */
    {{0.75, 0.5}, {0.5, 0.25}, {0.25, 0.5}};

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glRotatef(330.0, 1.,0.,0.);
    glScalef (0.5, 0.5, 0.5);

    gluBeginSurface(theNurb);
    gluNurbsSurface(theNurb,
        8, knots,
        8, knots,
        4 * 3,
        3,
        &ctlpoints[0][0][0],
        4, 4,
        GL_MAP2_VERTEX_3);
    gluBeginTrim (theNurb);
    gluPwlCurve (theNurb, 5, &edgePt[0][0], 2, GLU_MAP1_TRIM_2);
    gluEndTrim (theNurb);
    gluBeginTrim (theNurb);
    gluNurbsCurve (theNurb, 8, curveKnots, 2,
        &curvePt[0][0], 4, GLU_MAP1_TRIM_2);
    gluPwlCurve (theNurb, 3, &pwlPt[0][0], 2, GLU_MAP1_TRIM_2);
    gluEndTrim (theNurb);
    gluEndSurface(theNurb);

    glPopMatrix();
    glFlush();
}

void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    if (!h) return;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (0.0, 0.0, -5.0);
}

/*  Main Loop
 */
int main(int argc, char** argv)
{
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("NURBS Surface");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
    return(0);
}


AUXDEMO.C

/*
 * (c) Copyright 1993 - 1998 Microsoft Corporation
 *
 *    bgammon.c
 *
 *    start easy, just have pieces...
 *
 */
#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLvoidinitialize(GLvoid);
GLvoidCALLBACK drawScene(GLvoid);
GLvoid CALLBACK resize(GLsizei, GLsizei);
GLvoiddrawLight(GLvoid);
void polarView( GLdouble, GLdouble, GLdouble, GLdouble);

GLfloat latitude, longitude, radius;

void _CRTAPI1 main(void)
{
    initialize();

    auxMainLoop( drawScene );
}

GLvoid CALLBACK resize( GLsizei width, GLsizei height )
{
    GLfloat aspect;

    glViewport( 0, 0, width, height );

    aspect = (GLfloat) width / height;

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective( 45.0, aspect, 3.0, 7.0 );
    glMatrixMode( GL_MODELVIEW );
}

GLvoid initialize(GLvoid)
{
    GLfloatmaxObjectSize, aspect;
    GLdoublenear_plane, far_plane;
    GLsizeiwidth, height;

    GLfloatambientProperties[] = {0.7, 0.7, 0.7, 1.0};
    GLfloatdiffuseProperties[] = {0.8, 0.8, 0.8, 1.0};
    GLfloatspecularProperties[] = {1.0, 1.0, 1.0, 1.0};

    width = 1024.0;
    height = 768.0;

    auxInitPosition( width/4, height/4, width/2, height/2);

    auxInitDisplayMode( AUX_RGB | AUX_DEPTH | AUX_DOUBLE );

    auxInitWindow( "AUX Library Demo" );

    auxIdleFunc( drawScene );

    auxReshapeFunc( resize );

    glClearColor( 0.0, 0.0, 0.0, 1.0 );
    glClearDepth( 1.0 );

    glEnable(GL_DEPTH_TEST);

    glEnable(GL_LIGHTING);

    glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
    glLightfv( GL_LIGHT0, GL_SPECULAR, specularProperties);
    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0);

    glEnable( GL_LIGHT0 );

    glMatrixMode( GL_PROJECTION );
    aspect = (GLfloat) width / height;
    gluPerspective( 45.0, aspect, 3.0, 7.0 );
    glMatrixMode( GL_MODELVIEW );

    near_plane = 3.0;
    far_plane = 7.0;
    maxObjectSize = 3.0;
    radius = near_plane + maxObjectSize/2.0;

    latitude = 0.0;
    longitude = 0.0;

}

void polarView(GLdouble radius, GLdouble twist, GLdouble latitude,
       GLdouble longitude)
{
    glTranslated(0.0, 0.0, -radius);
    glRotated( -twist, 0.0, 0.0, 1.0 );
    glRotated( -latitude, 1.0, 0.0, 0.0);
    glRotated( longitude, 0.0, 0.0, 1.0);

}

GLvoid CALLBACK drawScene(GLvoid)
{
    static GLfloatwhiteAmbient[] = {0.3, 0.3, 0.3, 1.0};
    static GLfloatredAmbient[] = {0.3, 0.1, 0.1, 1.0};
    static GLfloatgreenAmbient[] = {0.1, 0.3, 0.1, 1.0};
    static GLfloatblueAmbient[] = {0.1, 0.1, 0.3, 1.0};
    static GLfloatwhiteDiffuse[] = {1.0, 1.0, 1.0, 1.0};
    static GLfloatredDiffuse[] = {1.0, 0.0, 0.0, 1.0};
    static GLfloatgreenDiffuse[] = {0.0, 1.0, 0.0, 1.0};
    static GLfloatblueDiffuse[] = {0.0, 0.0, 1.0, 1.0};
    static GLfloatwhiteSpecular[] = {1.0, 1.0, 1.0, 1.0};
    static GLfloatredSpecular[] = {1.0, 0.0, 0.0, 1.0};
    static GLfloatgreenSpecular[] = {0.0, 1.0, 0.0, 1.0};
    static GLfloatblueSpecular[] = {0.0, 0.0, 1.0, 1.0};

    static GLfloatlightPosition0[] = {1.0, 1.0, 1.0, 1.0};
    static GLfloatangle = 0.0;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glPushMatrix();

    latitude += 4.0;
    longitude += 2.5;

    polarView( radius, 0, latitude, longitude );

    glPushMatrix();
        angle += 6.0;
        glRotatef(angle, 1.0, 0.0, 1.0);
        glTranslatef( 0.0, 1.5, 0.0);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPosition0);
        drawLight();
    glPopMatrix();

    glPushAttrib(GL_LIGHTING_BIT);

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, redAmbient);
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, redDiffuse);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, whiteSpecular);
        glMaterialf(GL_FRONT, GL_SHININESS, 100.0);

        auxSolidCone( 0.3, 0.6 );

    glPopAttrib();
    auxWireSphere(1.5);

    glPushAttrib(GL_LIGHTING_BIT);

        glMaterialfv(GL_BACK, GL_AMBIENT, greenAmbient);
        glMaterialfv(GL_BACK, GL_DIFFUSE, greenDiffuse);
        glMaterialfv(GL_FRONT, GL_AMBIENT, blueAmbient);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, blueDiffuse);
        glMaterialfv(GL_FRONT, GL_SPECULAR, blueSpecular);
        glMaterialf(GL_FRONT, GL_SHININESS, 50.0);

        glPushMatrix();
            glTranslatef(0.8, -0.65, 0.0);
            glRotatef(30.0, 1.0, 0.5, 1.0);
            auxSolidCylinder( 0.3, 0.6 );
            glPopMatrix();

        glPopAttrib();

    glPopMatrix();

    auxSwapBuffers();
}

GLvoid drawLight(GLvoid)
{
    glPushAttrib(GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);
    glColor3f(1.0, 1.0, 1.0);
    auxSolidDodecahedron(0.1);
    glPopAttrib();
}


BACKTRAC.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>
#include <GL/gl.h>
extern "C" {
#include <GL/glaux.h>
};

#ifdef GLX_MOTIF
#include <GL/glx.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/CascadeB.h>
#include <Xm/Frame.h>
#include <Xm/MainW.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
#include <Xm/ToggleB.h>
#include <GLwMDrawA.h>
#include <sys/time.h>
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "scene.hxx"
#include "cbacks.hxx"
#include "menu.h"

LONG APIENTRY MyWndProc(HWND, UINT, UINT, LONG);
VOID SubclassWindow (HWND, WNDPROC);
void vCustomizeWnd(void);

extern light lights[];

extern GLboolean bAutoMotion(void);
extern void draw(void);

#ifdef GLX_MOTIF
static Display *display;
XtAppContext app_context;
Widget glw;
const int max_args = 20;
#endif

int quick_moves = 0;
int auto_motion = 0;

#ifdef GLX_MOTIF
XVisualInfo *find_visual()
{
  int attr[256], i, stencil, dbuffer;
  XVisualInfo *vi;

  i = 0;
  attr[i++] = GLX_RGBA;
  attr[i++] = GLX_RED_SIZE;
  attr[i++] = 1;
  attr[i++] = GLX_GREEN_SIZE;
  attr[i++] = 1;
  attr[i++] = GLX_BLUE_SIZE;
  attr[i++] = 1;
  attr[i++] = GLX_DEPTH_SIZE;
  attr[i++] = 1;
  dbuffer = i;
  attr[i++] = GLX_DOUBLEBUFFER;
  stencil = i;
  attr[i++] = GLX_STENCIL_SIZE;
  attr[i++] = 1;
  attr[i++] = (int)None;

  vi = glXChooseVisual(display, DefaultScreen(display), attr);
  if (vi == NULL) {
    //fprintf(stderr, "Unable to find visual with stencil buffer.\n");
    //fprintf(stderr, "(Things won't look quite as good).\n");
    attr[stencil] = (int)None;
    vi = glXChooseVisual(display, DefaultScreen(display), attr);
    if (vi == NULL) {
      //fprintf(stderr, "Unable to find double-buffered visual.\n");
      //fprintf(stderr, "(Things will look even worse).\n");
      attr[dbuffer] = (int)None;
      vi = glXChooseVisual(display, DefaultScreen(display), attr);
      //if (vi == NULL) fprintf(stderr, "Can't find visual at all.\n");
    }
  }
  return vi;
}
#endif

#ifdef GLX_MOTIF
Widget create_widgets(Widget parent)
{
  Widget main_window;
  Widget menu_bar;
  Widget menu_pane;
  Widget button;
  Widget cascade;
  Widget frame;
  XVisualInfo *vi;

  Arg args[max_args];
  int argcount;

  char buffer[128];

  int i;

  main_window = XmCreateMainWindow(parent, "main1", NULL, 0);
  XtManageChild(main_window);

  menu_bar = XmCreateMenuBar(main_window, "menu_bar", NULL, 0);
  XtManageChild(menu_bar);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", NULL, 0);
  button = XmCreatePushButton(menu_pane, "Exit", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback, (XtCallbackProc)exitCB, NULL);

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "File", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNradioBehavior, True); argcount++;
  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args, argcount);
  XtSetArg(args[0], XmNset, TRUE);
  for (i = 0; i < nindices; i++) {
    if (i == def_refraction_index) argcount = 1;
    else argcount = 0;
    button = XmCreateToggleButton(menu_pane, (char *)indices[i].name,
args, argcount);
    XtManageChild(button);
    XtAddCallback(button, XmNvalueChangedCallback,
  (XtCallbackProc)refractionCB,
  (XtPointer)(&indices[i].index));
  }
  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Material", args, 1);
  XtManageChild(cascade);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", NULL, 0);

  XtSetArg(args[0], XmNset, draw_square);
  button = XmCreateToggleButton(menu_pane, "Draw Square", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_square);

  XtSetArg(args[0], XmNset, draw_shadows);
  button = XmCreateToggleButton(menu_pane, "Draw Shadows", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_shadows);

  XtSetArg(args[0], XmNset, draw_refraction);
  button = XmCreateToggleButton(menu_pane, "Draw Refraction", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_refraction);

  XtSetArg(args[0], XmNset, draw_sphere);
  button = XmCreateToggleButton(menu_pane, "Draw Sphere", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_sphere);

  XtSetArg(args[0], XmNset, draw_lights);
  button = XmCreateToggleButton(menu_pane, "Draw Lights", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_lights);

#ifdef TEXTURE
  XtSetArg(args[0], XmNset, draw_texture);
  button = XmCreateToggleButton(menu_pane, "Texture Map", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)drawSomethingCB, &draw_texture);
#endif

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Draw", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNradioBehavior, True); argcount++;
  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args, argcount);
  XtSetArg(args[0], XmNset, TRUE);
  for (i = 0; i < npossible_divisions; i++) {
    if (i == def_divisions_index) argcount = 1;
    else argcount = 0;
    sprintf(buffer, "%d", possible_divisions[i]);
    button = XmCreateToggleButton(menu_pane, buffer, args, argcount);
    XtManageChild(button);
    XtAddCallback(button, XmNvalueChangedCallback,
  (XtCallbackProc)subdivisionCB, &possible_divisions[i]);
  }
  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Subdivision", args, 1);
  XtManageChild(cascade);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args, argcount);
  button = XmCreatePushButton(menu_pane, "Reset Position", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)resetLightsCB, NULL);

  XtSetArg(args[0], XmNset, TRUE);
  for (i = 0; i < nlights; i++) {
    button = XmCreateToggleButton(menu_pane, lights[i].name, args,
  lights[i].on ? 1 : 0);
    XtManageChild(button);
    XtAddCallback(button, XmNvalueChangedCallback,
  (XtCallbackProc)light_onCB, &lights[i]);
  }
  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Lights", args, 1);
  XtManageChild(cascade);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args,
   argcount);
  XtSetArg(args[0], XmNset, quick_moves);
  button = XmCreateToggleButton(menu_pane, "Quick Motion",
args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)intToggleCB, &quick_moves);

  XtSetArg(args[0], XmNset, auto_motion);
  button = XmCreateToggleButton(menu_pane, "Rotate Automatically",
args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)autoMotionCB, NULL);

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Motion", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNmarginWidth, 0); argcount++;
  XtSetArg(args[argcount], XmNmarginHeight, 0); argcount++;
  XtSetArg(args[argcount], XmNshadowThickness, 1); argcount++;
  XtSetArg(args[argcount], XmNshadowType, XmSHADOW_OUT); argcount++;
  frame = XmCreateFrame(main_window, "frame", args, argcount);
  XtManageChild(frame);

  argcount = 0;
  vi = find_visual();
  if (vi) {
    XtSetArg(args[argcount], GLwNvisualInfo, vi); argcount++;
  }
  else {
    XtSetArg(args[argcount], GLwNrgba, TRUE); argcount++;
    XtSetArg(args[argcount], GLwNdepthSize, 1); argcount++;
    XtSetArg(args[argcount], GLwNdoublebuffer, TRUE); argcount++;
  }
  XtSetArg(args[argcount], XmNbottomAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNtopAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNleftAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNrightAttachment, XmATTACH_FORM); argcount++;
  glw = GLwCreateMDrawingArea(frame, "glwidget", args, argcount);
  XtManageChild(glw);
  XtAddCallback(glw, GLwNginitCallback, (XtCallbackProc)initCB, 0);
  XtAddCallback(glw, GLwNexposeCallback, (XtCallbackProc)exposeCB, 0);
  XtAddCallback(glw, GLwNresizeCallback, (XtCallbackProc)resizeCB, 0);
  XtAddCallback(glw, GLwNinputCallback, (XtCallbackProc)inputCB, 0);

  return main_window;
}
#endif

void idle_func (void)
{
    bAutoMotion();
}

void null_func (void)
{
}

int CRTAPI1 main(int argc, char **argv)
{
    auxInitDisplayMode(AUX_RGB | AUX_STENCIL | AUX_DOUBLE );
    auxInitPosition(100, 100, 300, 300);
    auxInitWindow("BackTrace");
    vCustomizeWnd();
    scene_load_texture((char *)def_texfile);

    vInit();
    auxReshapeFunc(vResize);

    //
    // we've a choice of using accelerator or auxKeyFunc
    // the use of auxKeyFunc here is solely for demonstration purpose
    //
    auxKeyFunc(AUX_SPACE, vQuickMove);
    auxKeyFunc(AUX_RETURN, vResetLights);
    auxKeyFunc(AUX_p, vAutoMotion);

    auxKeyFunc(AUX_q, vDrawAll);
    auxKeyFunc(AUX_w, vDrawSquare);
    auxKeyFunc(AUX_e, vDrawShadow);
    auxKeyFunc(AUX_r, vDrawRefraction);
    auxKeyFunc(AUX_t, vDrawSphere);
    auxKeyFunc(AUX_y, vDrawLight);
    auxKeyFunc(AUX_u, vDrawTexture);

    auxKeyFunc(AUX_a, vRefractionAIR);
    auxKeyFunc(AUX_s, vRefractionICE);
    auxKeyFunc(AUX_d, vRefractionWATER);
    auxKeyFunc(AUX_f, vRefractionZincGLASS);
    auxKeyFunc(AUX_g, vRefractionLightGLASS);
    auxKeyFunc(AUX_h, vRefractionHeavyGLASS);

    auxKeyFunc(AUX_1, vSubdivision10);
    auxKeyFunc(AUX_2, vSubdivision20);
    auxKeyFunc(AUX_3, vSubdivision30);
    auxKeyFunc(AUX_4, vSubdivision40);

    auxKeyFunc(AUX_R, vRLight_on);
    auxKeyFunc(AUX_G, vGLight_on);
    auxKeyFunc(AUX_B, vBLight_on);

    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN, vMouseDown);
    auxMouseFunc(AUX_MIDDLEBUTTON, AUX_MOUSEDOWN, vMouseDown);
    auxMouseFunc(AUX_RIGHTBUTTON, AUX_MOUSEDOWN, vMouseDown);

    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEUP, vLeftMouseUp);
    auxMouseFunc(AUX_MIDDLEBUTTON, AUX_MOUSEUP, vMiddleMouseUp);
    auxMouseFunc(AUX_RIGHTBUTTON, AUX_MOUSEUP, vRightMouseUp);

    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSELOC, vMouseMove);
    auxMouseFunc(AUX_MIDDLEBUTTON, AUX_MOUSELOC, vMouseMove);
    auxMouseFunc(AUX_RIGHTBUTTON, AUX_MOUSELOC, vMouseMove);

    auxMainLoop(draw);
    if (auto_motion) auxIdleFunc (idle_func);
    else auxIdleFunc (null_func);
    return 0;
}

#ifdef GLX_MOTIF
void main(int argc, char **argv)
{
  Widget app_shell;
  Arg args[max_args];
  int argcount;

  scene_load_texture((char *)def_texfile);

  XtToolkitInitialize();
  app_context = XtCreateApplicationContext();
  display = XtOpenDisplay(app_context, NULL, argv[0],
                          "XMdemos", NULL, 0, &argc, argv);
  if (!display) {
    XtWarning("Can't open display.");
    exit(0);
  }

  argcount = 0;
  XtSetArg(args[argcount], XmNmaxAspectX, 1); argcount++;
  XtSetArg(args[argcount], XmNmaxAspectY, 1); argcount++;
  XtSetArg(args[argcount], XmNminAspectX, 1); argcount++;
  XtSetArg(args[argcount], XmNminAspectY, 1); argcount++;
  app_shell =
    XtAppCreateShell(argv[0], "XMdemos", applicationShellWidgetClass,
                     display, args, argcount);

  create_widgets(app_shell);

  XtRealizeWidget(app_shell);

  XtAppMainLoop(app_context);

}
#endif

/**************************************************************************\
*
*  function:  MyWndProc
*
*  input parameters:  normal window procedure parameters.
*
\**************************************************************************/
LONG APIENTRY MyWndProc(HWND hwnd, UINT message, UINT wParam, LONG lParam)
{
  WNDPROC     pfnOldProc;
  static UINT uiTmID = 0;
  int wmId, wmEvent;

  pfnOldProc = (WNDPROC) GetWindowLong (hwnd, GWL_USERDATA);

  switch (message) {

    case WM_COMMAND: {
        HMENU hmenu;

        hmenu = GetMenu(auxGetHWND());
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        switch (wmId) {
            case IDM_EXIT      : vExit(); break;
            case IDM_AIR       :
            case IDM_ICE       :
            case IDM_WATER     :
            case IDM_ZINC      :
            case IDM_LIGHT     :
            case IDM_HEAVY     : vRefraction(wmId - IDM_AIR); break;

            case IDM_SQUARE    :
            {
                if (draw_square)
                    CheckMenuItem(hmenu, IDM_SQUARE, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_SQUARE, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_square);
                break;
            }
            case IDM_SHADOW    :
            {
                if (draw_shadows)
                    CheckMenuItem(hmenu, IDM_SHADOW, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_SHADOW, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_shadows);
                break;
            }
            case IDM_REFRACTION:
            {
                if (draw_refraction)
                    CheckMenuItem(hmenu, IDM_REFRACTION, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_REFRACTION, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_refraction);
                break;
            }
            case IDM_SPHERE    :
            {
                if (draw_sphere)
                    CheckMenuItem(hmenu, IDM_SPHERE, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_SPHERE, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_sphere);
                break;
            }
            case IDM_LIGHTS    :
            {
                if (draw_lights)
                    CheckMenuItem(hmenu, IDM_LIGHTS, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_LIGHTS, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_lights);
                break;
            }
            case IDM_TEXTURE   :
            {
                if (draw_texture)
                    CheckMenuItem(hmenu, IDM_TEXTURE, MF_BYCOMMAND | MF_UNCHECKED);
                else
                    CheckMenuItem(hmenu, IDM_TEXTURE, MF_BYCOMMAND | MF_CHECKED);
                vDrawStuff(&draw_texture);
                break;
            }
            case IDM_ALL       : vDrawAll(); break;
            case IDM_10        :
            case IDM_20        :
            case IDM_30        :
            case IDM_40        : vSubdivision(wmId - IDM_10); break;

            case IDM_RESETLGT  : vResetLights(); break;

            case IDM_RED       :
            case IDM_GREEN     :
            case IDM_BLUE      : vLight_on(wmId - IDM_RED); break;

            case IDM_QUICK     : vQuickMove(); break;
            case IDM_AUTO      : vAutoMotion(); break;
            default:
                break;
        }
    }

    case WM_USER:
      if (auto_motion) auxIdleFunc (idle_func);
      else auxIdleFunc (null_func);

        return 0;

    case WM_DESTROY:
        if (uiTmID != 0)
            KillTimer(hwnd, uiTmID);
        return (pfnOldProc)(hwnd, message, wParam, lParam);

    default:
        return (pfnOldProc)(hwnd, message, wParam, lParam);

  } /* end switch */

  return 0;
}

/**************************************************************************\
*  function:  SubclassWindow
*
*  input parameters:
*   hwnd            - window handle to be subclassed,
*   SubclassWndProc - the new window procedure.
*
\**************************************************************************/
VOID SubclassWindow (HWND hwnd, WNDPROC SubclassWndProc)
{
  LONG pfnOldProc;

  pfnOldProc = GetWindowLong (hwnd, GWL_WNDPROC);

  SetWindowLong (hwnd, GWL_USERDATA, (LONG) pfnOldProc);
  SetWindowLong (hwnd, GWL_WNDPROC,  (LONG) SubclassWndProc);
}

/******************************Public*Routine******************************\
*
* vCustomizeWnd
*
* Effects: Subclass the window created by the toolkit
*          Add menu bar to the window
*          Setup timer
*
* History:
*  01-Dec-1993
*
\**************************************************************************/

void vCustomizeWnd(void)
{
    HWND    hWnd;

    if ((hWnd = auxGetHWND()) == NULL) {
OutputDebugString("auxGetHWND() failed\n");
return;
    }

    SubclassWindow (hWnd, (WNDPROC) MyWndProc);
    SendMessage(hWnd, WM_USER, 0L, 0L);
    SetMenu(hWnd, LoadMenu(GetModuleHandle(NULL), "Backtrac"));
    DrawMenuBar(hWnd);

    return;
}


CBACKS.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#ifdef GLX_MOTIF
#include <X11/keysym.h>

#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/ToggleB.h>
#include <GL/glx.h>
#include <GLwMDrawA.h>
#include <sys/time.h>
#endif

#include <GL/glu.h>
#include <GL/gl.h>
extern "C" {
#include <GL/glaux.h>
}

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "scene.hxx"
#include "cbacks.hxx"
#include "menu.h"

#ifdef GLX_MOTIF
extern Widget glw;
extern XtAppContext app_context;
GLXContext glx_con1005text;
#endif

extern light lights[];
extern int quick_moves;
extern int auto_motion;
static float dtheta[nlights];
static float last_motion_update;

//struct timeval starttime;
SYSTEMTIME starttime;

static int button_down;
static int winx, winy;
GLint mouse_x, mouse_y;

static int name_selected;

#ifdef GLX_MOTIF
static XtWorkProcId workproc = NULL;
#endif

const float time_fudge = .0001;

inline
float current_time()
{
  //struct timeval time;
  SYSTEMTIME time;

  //gettimeofday(&time, NULL);
  GetSystemTime(&time);

  //return ((double)(time.tv_sec - starttime.tv_sec) +
  //  (double)(time.tv_usec - starttime.tv_usec) / 1000000.0);

  return ((float)(time.wSecond - starttime.wSecond) +
        (float)(time.wMilliseconds - starttime.wMilliseconds) / (float)1000.0);

}

inline float rand(float min, float max)
{
  double r;
  r = (double)rand() / (double)RAND_MAX;
  return (float)(min + r * (max - min));
}

/******************************Public*Routine******************************\
*
* void draw(void)
*
* History:
*  29-Nov-1993                      Replaced with wgl and aux calls
*
\**************************************************************************/

void CALLBACK draw(void)
{

#ifdef GLX_MOTIF
  GLwDrawingAreaMakeCurrent(glw, glx_context);
//#else
  //wglMakeCurrent(auxGetHDC(), auxGetHGLRC());
#endif

  scene_draw();

#ifdef GLX_MOTIF
  GLwDrawingAreaSwapBuffers(glw);
#else
  // Win32's SwapBuffers takes HDC
  // SwapBuffers(auxGetHDC);
  auxSwapBuffers();
#endif

}

/******************************Public*Routine******************************\
*
* vQuickMove(void)
*
* //void intToggleCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  set or reset the global flag quick_moves
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vQuickMove(void)
{
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

#ifdef GLX_MOTIF
  int *data;
  XmToggleButtonCallbackStruct *ptr;

  ptr = (XmToggleButtonCallbackStruct *)call_data;
  data = (int *)client_data;
  *data = ptr->set;
#endif

  quick_moves = quick_moves ? 0 : 1;

  if (quick_moves)
    CheckMenuItem(hmenu, IDM_QUICK, MF_BYCOMMAND | MF_CHECKED);
  else
    CheckMenuItem(hmenu, IDM_QUICK, MF_BYCOMMAND | MF_UNCHECKED);

  // This redraw may or may not be needed - do it to be safe
  draw();
}

/******************************Public*Routine******************************\
*
* vResetLights(void)
*
* // void resetLightsCB(Widget w)
*
* Effects:
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vResetLights(void)
{
  scene_reset_lights();
  draw();
}

/******************************Public*Routine******************************\
*
* vAutoMotion(void)
*
* // void autoMotionCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  !!! turns on/off the timer?
*           (Re)set the global auto_moves
*
* Warnings:
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vAutoMotion(void)
{
  int i;
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

#ifdef GLX_MOTIF
  XmToggleButtonCallbackStruct *ptr;

  ptr = (XmToggleButtonCallbackStruct *)call_data;

  auto_motion = ptr->set;
#endif

  auto_motion = auto_motion ? 0 : 1;
  SendMessage(auxGetHWND(), WM_USER, 0L, 0L);

  if (auto_motion) {
    CheckMenuItem(hmenu, IDM_AUTO, MF_BYCOMMAND | MF_CHECKED);

    for (i = 0; i < nlights; i++)
        dtheta[i] = rand(-1, 1);
    last_motion_update = current_time();
  } else {
    CheckMenuItem(hmenu, IDM_AUTO, MF_BYCOMMAND | MF_UNCHECKED);
  }

#ifdef GLX_MOTIF
  if (auto_motion) {
    workproc = XtAppAddWorkProc(app_context, drawWP, NULL);
    for (i = 0; i < nlights; i++) dtheta[i] = rand(-1, 1);
    last_motion_update = current_time();
  } else {
    XtRemoveWorkProc(workproc);
  }
#endif

}

/******************************Public*Routine******************************\
*
* vInit(void)
*
* // void initCB(Widget w)
*
* Effects:
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void vInit(void)
{
  RECT  rect;
  HMENU hmenu;
  HWND  hWnd;
  int   i;

#ifdef GLX_MOTIF
  Arg args[1];
  XVisualInfo *vi;

  XtSetArg(args[0], GLwNvisualInfo, &vi);
  XtGetValues(w, args, 1);

  // !!! creating indirect context, make current
  //
  glx_context = glXCreateContext(XtDisplay(w), vi, 0, GL_FALSE);
  GLwDrawingAreaMakeCurrent(w, glx_context);
#endif

#ifdef TESTTEST
  PIXELFORMATDESCRIPTOR pfd;
  HDC                   hDC;
  HGLRC                 hRC;
  INT                   iPixelFormat;

  hDC = auxGetHDC();

  iPixelFormat = GetPixelFormat(hDC);
  DescribePixelFormat(hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

  if ((pfd.dwFlags & PFD_SUPPORT_GDI) == 0) {

    OutputDebugString("PFD_SUPPORT_GDI not supported!\n");

    pfd.dwFlags |= PFD_SUPPORT_GDI;

  }

  if (!SetPixelFormat(hDC, iPixelFormat, &pfd))
    OutputDebugString("PFD_SUPPORT_GDI still not supported!\n");

  //
  // Maynot need this
  //
  hRC = wglCreateContext(hDC);
  if (!wglMakeCurrent(hDC, hRC))
    OutputDebugString("MakeCurrentFailed!\n");
#endif

  scene_init();

  //gettimeofday(&starttime, NULL);
  //srand(starttime.tv_usec);

  GetSystemTime(&starttime);
  srand(starttime.wMilliseconds);

  hWnd = auxGetHWND();
  GetClientRect(hWnd, &rect);

  glViewport(0, 0, rect.right, rect.bottom);
  glClear(GL_COLOR_BUFFER_BIT);

  //
  // this is for initizating the menu items
  //
  hmenu = GetMenu(hWnd);

  for (i=0; i < nlights; i++) {
    if (lights[i].on)
      CheckMenuItem(hmenu, IDM_RED+i, MF_BYCOMMAND | MF_CHECKED);
    else
      CheckMenuItem(hmenu, IDM_RED+i, MF_BYCOMMAND | MF_UNCHECKED);
  }

  if (draw_square)
    CheckMenuItem(hmenu, IDM_SQUARE, MF_BYCOMMAND | MF_CHECKED);
  if (draw_shadows)
    CheckMenuItem(hmenu, IDM_SHADOW, MF_BYCOMMAND | MF_CHECKED);
  if (draw_refraction)
    CheckMenuItem(hmenu, IDM_REFRACTION, MF_BYCOMMAND | MF_CHECKED);
  if (draw_lights)
    CheckMenuItem(hmenu, IDM_LIGHTS, MF_BYCOMMAND | MF_CHECKED);
  if (draw_sphere)
    CheckMenuItem(hmenu, IDM_SPHERE, MF_BYCOMMAND | MF_CHECKED);
  if (draw_texture)
    CheckMenuItem(hmenu, IDM_TEXTURE, MF_BYCOMMAND | MF_CHECKED);

  CheckMenuItem(hmenu, IDM_AIR+def_refraction_index, MF_BYCOMMAND | MF_CHECKED);
  CheckMenuItem(hmenu, IDM_10+def_divisions_index, MF_BYCOMMAND | MF_CHECKED);

}

/******************************Public*Routine******************************\
*
* vExpose
*
* // !!! void exposeCB(Widget w)
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void vExpose(int x, int y)
{
  draw();
}

/******************************Public*Routine******************************\
*
* vResize
*
* // void resizeCB(Widget w, XtPointer client_data, XtPointer call)
*
* Effects:  changes globals winx, winy and aspect
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vResize(GLsizei width, GLsizei height)
{
#ifdef GLX_MOTIF
  GLwDrawingAreaCallbackStruct *call_data;
  call_data = (GLwDrawingAreaCallbackStruct *)call;

  GLwDrawingAreaMakeCurrent(w, glx_context);
  winx = call_data->width;
  winy = call_data->height;
#endif

  winx = width;
  winy = height;

  glViewport(0, 0, winx, winy);

  aspect = (GLfloat)winx / (GLfloat)winy;
}

#ifdef GLX_MOTIF
//
// !!! mouse down/up function??
//
void inputCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  int picked;
  GLwDrawingAreaCallbackStruct *call;

  char buffer[5];
  int bufsize = 5;
  KeySym key;
  XComposeStatus compose;

  static int mousex, mousey;
  /* Just to confuse everybody, I've made these go from 0-1. */
  float dmousex, dmousey;
  float r1, r2;

  call = (GLwDrawingAreaCallbackStruct *)call_data;

  GLwDrawingAreaMakeCurrent(w, glx_context);
  switch(call->event->type) {
  case ButtonPress:
    button_down = call->event->xbutton.button;
    mousex = call->event->xbutton.x;
    mousey = call->event->xbutton.y;
    picked = scene_pick(mousex, mousey);
    if (picked >= name_lights) name_selected = picked;
    break;
  case ButtonRelease:
    if (quick_moves)
      scene_move_update(name_selected, button_down == Button2,
button_down == Button3, button_down = Button1);
    button_down = 0;
    break;
  case MotionNotify:
    if (button_down == Button1) {
      /* This is the "default" mouse button - moves things in theta
       * since this is easy and computationally cheap */
      dmousex = (double)(call->event->xmotion.x - mousex) / (double)winx;
      scene_move(name_selected, 0, 0, dmousex, quick_moves ? 0 : 1);
    } else if (button_down == Button2) {
      /* Change the radius - figue out the component of the mouse motion
       * that's going toward the center of the screen */
      mousex = (winx / 2) - mousex;
      mousey = (winy / 2) - mousey;
      r1 = sqrt((float)(mousex*mousex) / (float)(winx*winx) +
(float)(mousey*mousey) / (float)(winy*winy));
      mousex = call->event->xmotion.x;
      mousey = call->event->xmotion.y;
      mousex = (winx / 2) - mousex;
      mousey = (winy / 2) - mousey;
      r2 = sqrt((float)(mousex*mousex) / (float)(winx*winx) +
(float)(mousey*mousey) / (float)(winy*winy));
      scene_move(name_selected, r2 - r1, 0, 0, quick_moves ? 0 : 1);
    } else if (button_down == Button3) {
      /* Change phi - this is expensive */
      dmousex = (double)(call->event->xmotion.x - mousex) / (double)winx;
      scene_move(name_selected, 0, dmousex, 0, quick_moves ? 0 : 1);
    }
    mousex = call->event->xmotion.x;
    mousey = call->event->xmotion.y;
    break;
  case KeyPress:
    XLookupString(&(call->event->xkey), buffer, bufsize, &key, &compose);
    if (key == XK_Escape) exit(0);
    break;
  default:
    break;
  }

  draw();
}
#endif

/******************************Public*Routine******************************\
*
* vDrawAll(void)
*
* //void drawAllCB(Widget w)
*
* Effects:
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vDrawAll(void)
{
  HMENU hmenu;
  int       i;
  static int fAll=0;

  hmenu = GetMenu(auxGetHWND());

  fAll = fAll ? 0 : 1;

  if (fAll) {
    for (i=0; i < 6; i++)
       CheckMenuItem(hmenu, IDM_SQUARE+i, MF_BYCOMMAND | MF_CHECKED);

    CheckMenuItem(hmenu, IDM_ALL, MF_BYCOMMAND | MF_CHECKED);
    draw_square = 1;
    draw_shadows = 1;
    draw_refraction = 1;
    draw_sphere = 1;
    draw_lights = 1;

  } else {
    CheckMenuItem(hmenu, IDM_ALL, MF_BYCOMMAND | MF_UNCHECKED);
  }

  draw();
}

/******************************Public*Routine******************************\
*
* //void drawSomethingCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  functions for auxKeyFunc can't have parameters so we have
*           these similar functions here.
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vDrawSquare(void)
{
#ifdef GLX_MOTIF
  XmToggleButtonCallbackStruct *ptr;
  int *data;
  int i;

  ptr = (XmToggleButtonCallbackStruct *)call_data;
  data = (int *)client_data;
  *data = ptr->set;
#endif
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_square = draw_square ? 0 : 1;
  if (draw_square)
      CheckMenuItem(hmenu, IDM_SQUARE, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_SQUARE, MF_BYCOMMAND | MF_UNCHECKED);

  draw();
}

void CALLBACK vDrawShadow(void)
{
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_shadows = draw_shadows ? 0 : 1;
  if (draw_shadows)
      CheckMenuItem(hmenu, IDM_SHADOW, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_SHADOW, MF_BYCOMMAND | MF_UNCHECKED);
  draw();
}

void CALLBACK vDrawRefraction(void)
{
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_refraction = draw_refraction ? 0 : 1;
  if (draw_refraction)
      CheckMenuItem(hmenu, IDM_REFRACTION, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_REFRACTION, MF_BYCOMMAND | MF_UNCHECKED);
  draw();
}
void CALLBACK vDrawSphere(void)
{
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_sphere = draw_sphere ? 0 : 1;
  if (draw_sphere)
      CheckMenuItem(hmenu, IDM_SPHERE, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_SPHERE, MF_BYCOMMAND | MF_UNCHECKED);
  draw();
}
void CALLBACK vDrawLight(void)
{
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_lights = draw_lights ? 0 : 1;
  if (draw_lights)
      CheckMenuItem(hmenu, IDM_LIGHTS, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_LIGHTS, MF_BYCOMMAND | MF_UNCHECKED);
  draw();
}
void CALLBACK vDrawTexture(void)
{
  HMENU hmenu;

  hmenu = GetMenu(auxGetHWND());

  draw_texture = draw_texture ? 0 : 1;
  if (draw_texture)
      CheckMenuItem(hmenu, IDM_TEXTURE, MF_BYCOMMAND | MF_CHECKED);
  else
      CheckMenuItem(hmenu, IDM_TEXTURE, MF_BYCOMMAND | MF_UNCHECKED);
  draw();
}

void vDrawStuff(int *what)
{
  *what = *what ? 0 : 1;
  draw();
}

/******************************Public*Routine******************************\
*
* //void refractionCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  functions for auxKeyFunc can't have parameters so we have
*           these similar functions here.
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vRefractionAIR(void)
{
#ifdef GLX_MOTIF
  XmToggleButtonCallbackStruct *ptr;
  GLfloat refraction;

  ptr = (XmToggleButtonCallbackStruct *)call_data;
  if (!ptr->set) return;
  refraction = *((GLfloat *)client_data);
#endif

  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+0, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[0].index;
  refraction_change(refraction);
  draw();
}

void CALLBACK vRefractionICE(void)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+1, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[1].index;
  refraction_change(refraction);
  draw();
}

void CALLBACK vRefractionWATER(void)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+2, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[2].index;
  refraction_change(refraction);
  draw();
}

void CALLBACK vRefractionZincGLASS(void)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+3, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[3].index;
  refraction_change(refraction);
  draw();
}
void CALLBACK vRefractionLightGLASS(void)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+4, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[4].index;
  refraction_change(refraction);
  draw();
}
void CALLBACK vRefractionHeavyGLASS(void)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+5, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[5].index;
  refraction_change(refraction);
  draw();
}

void vRefraction(int type)
{
  GLfloat refraction;
  HMENU   hmenu;
  int         i;

  hmenu = GetMenu(auxGetHWND());
  for (i = 0; i < nindices; i++)
    CheckMenuItem(hmenu, IDM_AIR+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_AIR+type, MF_BYCOMMAND | MF_CHECKED);
  refraction = indices[type].index;
  refraction_change(refraction);
  draw();
}

/******************************Public*Routine******************************\
*
* vSubdivision
*
* //void subdivisionCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  functions for auxKeyFunc can't have parameters so we have
*           these similar functions here.
* History:
*  30-Nov-1993
*
\**************************************************************************/

void CALLBACK vSubdivision10(void)
{
#ifdef GLX_MOTIF
  XmToggleButtonCallbackStruct *ptr;
  int subdivisions;

  ptr = (XmToggleButtonCallbackStruct *)call_data;
  if (!ptr->set) return;
  subdivisions = *((int *)client_data);
#endif

  HMENU hmenu;
  int       i;

  hmenu = GetMenu(auxGetHWND());

  for (i = 0; i < npossible_divisions; i++)
    CheckMenuItem(hmenu, IDM_10+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_10, MF_BYCOMMAND | MF_CHECKED);
  divisions_change(possible_divisions[0]);

  draw();
}

void CALLBACK vSubdivision20(void)
{
  HMENU hmenu;
  int       i;

  hmenu = GetMenu(auxGetHWND());

  for (i = 0; i < npossible_divisions; i++)
    CheckMenuItem(hmenu, IDM_10+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_20, MF_BYCOMMAND | MF_CHECKED);
  divisions_change(possible_divisions[1]);

  draw();
}

void CALLBACK vSubdivision30(void)
{
  HMENU hmenu;
  int       i;

  hmenu = GetMenu(auxGetHWND());

  for (i = 0; i < npossible_divisions; i++)
    CheckMenuItem(hmenu, IDM_10+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_30, MF_BYCOMMAND | MF_CHECKED);
  divisions_change(possible_divisions[2]);

  draw();
}

void CALLBACK vSubdivision40(void)
{
  HMENU hmenu;
  int       i;

  hmenu = GetMenu(auxGetHWND());

  for (i = 0; i < npossible_divisions; i++)
    CheckMenuItem(hmenu, IDM_10+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_40, MF_BYCOMMAND | MF_CHECKED);
  divisions_change(possible_divisions[3]);

  draw();
}

void vSubdivision(int which)
{
  HMENU hmenu;
  int       i;

  hmenu = GetMenu(auxGetHWND());

  for (i = 0; i < npossible_divisions; i++)
    CheckMenuItem(hmenu, IDM_10+i, MF_BYCOMMAND | MF_UNCHECKED);

  CheckMenuItem(hmenu, IDM_10+which, MF_BYCOMMAND | MF_CHECKED);

  divisions_change(possible_divisions[which]);
  draw();
}

/******************************Public*Routine******************************\
*
* v[RGB]Light_on
*
* //void light_onCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects:  functions for auxKeyFunc can't have parameters so we have
*           these similar functions here.
* History:
*  30-Nov-1993
*
\**************************************************************************/
#define RED_LIGHT   0
#define GREEN_LIGHT 1
#define BLUE_LIGHT  2

void CALLBACK vRLight_on(void)
{
#ifdef GLX_MOTIF
  XmToggleButtonCallbackStruct *ptr;

  ptr = (XmToggleButtonCallbackStruct *)call_data;
  lights_onoff((light *)client_data - lights, ptr->set);
#endif

  int fOn;
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

  fOn = lights[RED_LIGHT].on ? 0 : 1;
  if (fOn)
    CheckMenuItem(hmenu, IDM_RED, MF_BYCOMMAND | MF_CHECKED);
  else
    CheckMenuItem(hmenu, IDM_RED, MF_BYCOMMAND | MF_UNCHECKED);
  lights_onoff(RED_LIGHT, fOn);
  draw();
}

void CALLBACK vGLight_on(void)
{
  int fOn;
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

  fOn = lights[GREEN_LIGHT].on ? 0 : 1;
  if (fOn)
    CheckMenuItem(hmenu, IDM_GREEN, MF_BYCOMMAND | MF_CHECKED);
  else
    CheckMenuItem(hmenu, IDM_GREEN, MF_BYCOMMAND | MF_UNCHECKED);
  lights_onoff(GREEN_LIGHT, fOn);
  draw();
}

void CALLBACK vBLight_on(void)
{
  int fOn;
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

  fOn = lights[BLUE_LIGHT].on ? 0 : 1;
  if (fOn)
    CheckMenuItem(hmenu, IDM_BLUE, MF_BYCOMMAND | MF_CHECKED);
  else
    CheckMenuItem(hmenu, IDM_BLUE, MF_BYCOMMAND | MF_UNCHECKED);
  lights_onoff(BLUE_LIGHT, fOn);
  draw();
}

void vLight_on(int which)
{
  int fOn;
  HMENU   hmenu;

  hmenu = GetMenu(auxGetHWND());

  fOn = lights[which].on ? 0 : 1;

  if (fOn)
    CheckMenuItem(hmenu, IDM_RED+which, MF_BYCOMMAND | MF_CHECKED);
  else
    CheckMenuItem(hmenu, IDM_RED+which, MF_BYCOMMAND | MF_UNCHECKED);

  lights_onoff(which, fOn);
  draw();
}

/******************************Public*Routine******************************\
*
* vExit
*
* //void exitCB(Widget w, XtPointer client_data, XtPointer call_data)
*
* Effects: !!! this is redundant unless we do stuff other than auxQuit
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

void vExit(void)
{
#ifdef GLX_MOTIF
  exit(0);
#endif

    auxQuit();
}

/******************************Public*Routine******************************\
*
* bAutoMotion(void)
*
* // Boolean drawWP(XtPointer data)
*
* Effects:  !!! for WM_TIMER ?
*
* History:
*  30-Nov-1993
*
\**************************************************************************/

GLboolean bAutoMotion(void)
{
  float t, dt;
  int i;

  t = current_time();
  dt = t - last_motion_update;
  dt = (dt < 0) ? -dt : dt;

  if (dt < time_fudge) {

    //wsprintf(text, "dt = %lx\n", dt);
    //OutputDebugString(text);
    return FALSE;
  }

  for (i = 0; i < nlights; i++) {
    scene_move(name_lights + i, 0, 0, dtheta[i] * dt, 1);
  }

  last_motion_update = t;

  draw();
  return FALSE;
}

void CALLBACK vMouseDown(AUX_EVENTREC *event)
{
  int    picked;

  mouse_x = event->data[AUX_MOUSEX];
  mouse_y = event->data[AUX_MOUSEY];

  picked = scene_pick(mouse_x, mouse_y);
  if (picked >= name_lights)
     name_selected = picked;

  return;
}

void CALLBACK vLeftMouseUp(AUX_EVENTREC *event)
{
  if (quick_moves)
    scene_move_update(name_selected, 0,     // dr
                     0,     // dphi
                                     1);    // dtheta

  return;
}

void CALLBACK vMiddleMouseUp(AUX_EVENTREC *event)
{
  if (quick_moves)
    scene_move_update(name_selected, 1,     // dr
                     0,     // dphi
                                     0);    // dtheta

  return;
}

void CALLBACK vRightMouseUp(AUX_EVENTREC *event)
{
  if (quick_moves)
    scene_move_update(name_selected, 0,     // dr
                     1,     // dphi
                                     0);    // dtheta

  return;
}

void CALLBACK vMouseMove(AUX_EVENTREC *event)
{
  float dmousex;
  float r1, r2;
  GLint button = event->data[AUX_MOUSESTATUS];
  GLint x = event->data[AUX_MOUSEX];
  GLint y = event->data[AUX_MOUSEY];

  switch( button ) {
    case AUX_LEFTBUTTON:
      /* This is the "default" mouse button - moves things in theta
       * since this is easy and computationally cheap */
      dmousex = (float)((double)(x - mouse_x) / (double)winx);
      scene_move(name_selected, 0, 0, dmousex, quick_moves ? 0 : 1);
      break;

    case AUX_MIDDLEBUTTON:
      /* Change the radius - figue out the component of the mouse motion
       * that's going toward the center of the screen */
      mouse_x = (winx / 2) - mouse_x;
      mouse_y = (winy / 2) - mouse_y;
      r1 = (float)sqrt((double)(mouse_x*mouse_x) / (double)(winx*winx) +
    (double)(mouse_y*mouse_y) / (double)(winy*winy));
      mouse_x = x;
      mouse_y = y;
      mouse_x = (winx / 2) - mouse_x;
      mouse_y = (winy / 2) - mouse_y;
      r2 = (float)sqrt((double)(mouse_x*mouse_x) / (double)(winx*winx) +
  (double)(mouse_y*mouse_y) / (double)(winy*winy));
      scene_move(name_selected, r2 - r1, 0, 0, quick_moves ? 0 : 1);
      break;

    case AUX_RIGHTBUTTON :
      /* Change phi - this is expensive */
      dmousex = (float)((double)(x - mouse_x) / (double)winx);
      scene_move(name_selected, 0, dmousex, 0, quick_moves ? 0 : 1);
      break;
    }

    mouse_x = x;
    mouse_y = y;
}

//
// hack these since cfront generates these...
//
PVOID __nw(unsigned int ui)
{
    return LocalAlloc(LMEM_FIXED, ui);
}

VOID __dl(PVOID pv)
{

    LocalFree(pv);
    return;
}

PVOID __vec_new(void *p, int x, int y, void *q)
{
    return LocalAlloc(LMEM_FIXED, x*y);
}


MENU.H

#define IDM_EXIT        1000

#define IDM_AIR         1001
#define IDM_ICE         1002
#define IDM_WATER       1003
#define IDM_ZINC        1004
#define IDM_LIGHT       1005
#define IDM_HEAVY       1006

#define IDM_SQUARE      1007
#define IDM_SHADOW      1008
#define IDM_REFRACTION  1009
#define IDM_SPHERE      1010
#define IDM_LIGHTS      1011
#define IDM_TEXTURE     1012
#define IDM_ALL         1013

#define IDM_10          1014
#define IDM_20          1015
#define IDM_30          1016
#define IDM_40          1017

#define IDM_RESETLGT    1018

#define IDM_RED         1019
#define IDM_GREEN       1020
#define IDM_BLUE        1021

#define IDM_QUICK       1022
#define IDM_AUTO        1023


POINT.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/gl.h>

#include <math.h>
#include <stdio.h>

#define POINT_EXTERN
#include "point.hxx"

// Change this back to inline
Point Point::rotate_abouty(GLfloat c, GLfloat s)
{
  val.pt[0] = c*pt[0] + s*pt[2];
  val.pt[1] = pt[1];
  val.pt[2] = -s*pt[0] + c*pt[2];
  return val;
}

void Point::refract_self(Point light, Point N, GLfloat I)
{
  GLfloat t;
  Point dlight;

  dlight = refract_direction(light, N, I);
  t = -pt[2] / dlight.pt[2];
  pt[0] = pt[0] + dlight.pt[0]*t;
  pt[1] = pt[1] + dlight.pt[1]*t;
  pt[2] = 0;
}

Point Point::refract_direction(Point light, Point N, GLfloat I)
{
  GLfloat cos1, sin1, cos2, sin2, m;
  GLfloat dlight[3], dN[3], axis[3];

  /* dlight = (light - *this).unit() * -1.0; */
  dlight[0] = pt[0] - light.pt[0];
  dlight[1] = pt[1] - light.pt[1];
  dlight[2] = pt[2] - light.pt[2];
  m = (GLfloat)sqrt((double)(dlight[0]*dlight[0] + dlight[1]*dlight[1] + dlight[2]*dlight[2]));
  dlight[0] /= m;
  dlight[1] /= m;
  dlight[2] /= m;

  // dN = N * -1.0;
  dN[0] = -N.pt[0];
  dN[1] = -N.pt[1];
  dN[2] = -N.pt[2];

  // cos1 = dN.dot(dlight);
  cos1 = dN[0]*dlight[0] + dN[1]*dlight[1] + dN[2]*dlight[2];

  if (1.0 - cos1*cos1 < point_fudge) {
    val = dN;
    return val;
  }

  // axis = ((dN * dlight) * dN).unit();
  val.pt[0] = dN[1]*dlight[2] - dlight[1]*dN[2];
  val.pt[1] = dN[2]*dlight[0] - dlight[2]*dN[0];
  val.pt[2] = dN[0]*dlight[1] - dN[1]*dlight[0];
  axis[0] = val.pt[1]*dN[2] - dN[1]*val.pt[2];
  axis[1] = val.pt[2]*dN[0] - dN[2]*val.pt[0];
  axis[2] = val.pt[0]*dN[1] - val.pt[1]*dN[0];
  m = (GLfloat)sqrt((double)(axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]));
  axis[0] /= m;
  axis[1] /= m;
  axis[2] /= m;

  if (axis[0]*axis[0] > point_fudge)
    sin1 = (dlight[0] - dN[0] * cos1) / axis[0];
  else if (axis[1]*axis[1] > point_fudge)
    sin1 = (dlight[1] - dN[1] * cos1) / axis[1];
  else sin1 = dlight[2] - dN[2] * cos1;

  sin2 = sin1 / I;
  cos2 = (sin1*sin1 < 1.0) ? (GLfloat)sqrt((double)(1.0 - sin2*sin2)) : (GLfloat)0;

  dlight[0] = dN[0]*cos2 + axis[0]*sin2;
  dlight[1] = dN[1]*cos2 + axis[1]*sin2;
  dlight[2] = dN[2]*cos2 + axis[2]*sin2;

  /* I'm not sure this is quite legal */
  if (dlight[2] > 0.0) dlight[2] = -dlight[2];

  val = dlight;

  return val;
}

void Point::print()
{
  print("%f %f %f\n");
}

void Point::print(const char *format)
{
  //printf(format, this->pt[0], this->pt[1], this->pt[2], 1.0);
}


SCENE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

extern "C" {
#include <GL/glaux.h>
}
#include <GL/glu.h>
#include <GL/gl.h>

#ifdef GLX_MOTIF
#include <GL/glx.h>
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#include "Unitdisk.hxx"
#include "scene.hxx"

const GLfloat I[16] = {
                        1, 0, 0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1
                      };

Color white;
Color black;

const double M_2PI      = 2.0 * M_PI;
const float scene_fudge = .000001;

// Lights are native to the xz plane and are rotated into position -
// shadows and refraction will not have to be changed if lights are
// just rotating about the z axis

light lights[] = {
                  {{1, 0, 0, 1}, {0, 0, 0, 0}, {1, 0, 0, 0},
                     {1, 0, 0, 0,  0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1},
                        "Red", 1},
                  {{0, 1, 0, 1}, {0, 0, 0, 0}, {0, 1, 0, 0},
                     {1, 0, 0, 0,  0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1},
                        "Green", 1},
                  {{0, 0, 1, 1}, {0, 0, 0, 0}, {0, 0, 1, 0},
                     {1, 0, 0, 0,  0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1},
                        "Blue", 1}
                 };

GLfloat light_init_position[nlights][4] = {
                                           {1.5, 0, 2.5, 1},
                                           {1, 0, 3, 1},
                                           {2, 0, 3, 1}
                                          };

GLfloat light_init_rotation[nlights] = {135, 0, 90};
GLfloat light_rotation[nlights];

Color world_ambient(.25, .25, .25);

GLfloat index = indices[def_refraction_index].index;

GLfloat square_ambient[4]  = {.25, .25, .25, 1};
GLfloat square_diffuse[4]  = {1, 1, 1, 1};
GLfloat square_specular[4] = {0, 0, 0, 1};

const GLfloat fov          = 45.0;
GLfloat aspect             = 1.0;
GLfloat eyep[3]            = {-6, 0, 6};
GLfloat lookp[3]           = {0, 0, 1};

#ifdef GLX_MOTIF
static GLXContext glx_context;
#endif

const int max_args = 20;

static int list_square;
static int lists_shadows;
static int lists_refraction;
static int lists_lights    = 5;
static int list_sphere     = 4;
static int list_spheredisk = 9;
static int list_lights_on  = 6;
static int list_lights_off = 7;
static int list_light_draw = 8;

int draw_square     = 1;
int draw_shadows    = 1;
int draw_refraction = 1;
int draw_sphere     = 1;
int draw_lights     = 1;
int draw_texture    = 0;

int possible_divisions[] = {10, 20, 30, 40};

// Sphere is stored as floats - more efficient
GLfloat *spherepts = NULL;
int nspherepts     = 0;
int spherediv      = 0;
Point sphere_position = {0, 0, 1, 1};
GLfloat sphere_size   = .5;
const GLfloat sphere_ambient[4]  = {0, 0, 0, 0};
const GLfloat sphere_specular[4] = {0, 0, 0, 0};
Unitdisk sphere_disk;
static void sphere_build();
static void sphere_list_init();
static void sphere_draw();

static void square_list_init();

static void lights_init_onoff();
static void lights_init_position();
static void lights_init_position(int i);
static void lights_list_init();
static void lights_list_init(int n);

static void light_draw_list_init();

Unitdisk disks[nlights];
int diskdiv = possible_divisions[def_divisions_index];
static void disk_build();
static void disk_build(int disk);

Unitdisk shadows[nlights];
static void shadow_list_init();
static void shadow_list_init(int n);
static void shadow_draw(int n);

Unitdisk refraction[nlights];
static void refraction_list_init();
static void refraction_list_init(int n);

static void shadow_refraction_full_build();
static void shadow_refraction_full_build(int n);

void scene_init();

#ifdef MYDEBUG
void lists_init();
void lights_init();
int  lights_move(int light, float dr, float dphi, float dtheta,
       int update);

void lights_move_update(int light, int dr, int dphi, int dtheta);
#else
static void lists_init();
static void lights_init();
static int lights_move(int light, float dr, float dphi, float dtheta,
       int update);
static void lights_move_update(int light, int dr, int dphi, int dtheta);
#endif

void scene_draw();

void texture_init();
AUX_RGBImageRec *teximage = NULL;

inline float sign(float a)
{
  // This is badly written so let's not call it too often, 'k?
  return (a > 0) ? (float)1 : (a < 0) ? (float) -1 : (float) 0;
}

inline double degrees(double a)
{
  return (a * 180.0 / M_PI);
}

inline double radians(double a)
{
  return (a * M_PI / 180.0);
}

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

inline double radians_clamp(double a)
{
  while (a < 0.0) a += M_2PI;
  while (a > M_2PI) a -= M_2PI;
  return a;
}

void scene_init()
{
  int i;

  white.c[0] = white.c[1] = white.c[2] = white.c[3] = 1;
  black.c[0] = black.c[1] = black.c[2] = 0;
  black.c[3] = 1;

  lists_init();

  for (i = 0; i < nlights; i++) {
    lights[i].pos = light_init_position[i];
    light_rotation[i] = light_init_rotation[i];
    lights_init_position(i);
  }

  divisions_change(possible_divisions[def_divisions_index]);

  lights_init_onoff();
  lights_init();
  lights_init_position();

  texture_init();

  glClearStencil(0);

  // This is for profiling
  // exit(0);
}

static void scene_project()
{
  glMatrixMode(GL_PROJECTION);
  gluPerspective(fov, aspect, 0.01, 20.0);
  gluLookAt(eyep[0], eyep[1], eyep[2], lookp[0], lookp[1], lookp[2],
            1, 0, 0);
}

static void scene_rasterize()
{
  int i;

  glLoadName(name_square);
  if (draw_square) {
    if (draw_texture) glEnable(GL_TEXTURE_2D);
    glCallList(list_square);
    glDisable(GL_TEXTURE_2D);
  }
  if (draw_shadows)
    for (i = 0; i < nlights; i++)
      if (lights[i].on) {
glPushMatrix();
glRotatef(-light_rotation[i], 0, 0, 1);
glCallList(lists_shadows + i);
glPopMatrix();
      }
  if (draw_refraction)
    for (i = 0; i < nlights; i++)
      if (lights[i].on) {
glPushMatrix();
glRotatef(-light_rotation[i], 0, 0, 1);
glCallList(lists_refraction + i);
glPopMatrix();
      }

  glLoadName(name_sphere);
  /* Drawing the sphere here makes the sphere visible through itself when we
   * do the refraction redraw hack -- for now, just don't draw it */
  //  if (draw_sphere) glCallList(list_sphere);

  for (i = 0; i < nlights; i++)
    if (draw_lights) glCallList(lists_lights + i);
}

/* This draws an image of the scene seen through the sphere */
static void scene_draw_refracted()
{
  int i;

  if (!draw_sphere) return;

  /* Draw an image of the sphere into the stencil plane  -
   * must do this every time in case the lights have moved in front
   * of it */
  glEnable(GL_STENCIL_TEST);
  glClearStencil(0);
  glClear(GL_STENCIL_BUFFER_BIT);
  glStencilFunc(GL_ALWAYS, 0x1, 0x1);
  glStencilOp(GL_REPLACE, GL_KEEP, GL_REPLACE);

  glColorMask(0, 0, 0, 0);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  scene_project();

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glCallList(list_sphere);
  glDisable(GL_CULL_FACE);
  glDisable(GL_DEPTH_TEST);

  glColorMask(1, 1, 1, 1);

  /* Set up a transform with a wider field of view  - this is inaccurate
   * but I don't have time to do it right */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fov * index, aspect, 0.01, 20.0);
  gluLookAt(eyep[0], eyep[1], eyep[2], lookp[0], lookp[1], lookp[2],
            1, 0, 0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* Set up the stencil stuff which will be used to draw the image */
  glStencilFunc(GL_NOTEQUAL, 0x0, 0xffffffff);
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

  /* Draw the image, gambling that we'll never see anything but the
   * floor through the table */
  glLoadName(name_sphere);
  if (draw_texture) glEnable(GL_TEXTURE_2D);
  if (draw_square) glCallList(list_square);
  if (draw_shadows)
    for (i = 0; i < nlights; i++)
      if (lights[i].on) {
glPushMatrix();
glRotatef(-light_rotation[i], 0, 0, 1);
glCallList(lists_shadows + i);
glPopMatrix();
      }
  if (draw_refraction)
    for (i = 0; i < nlights; i++)
      if (lights[i].on) {
glPushMatrix();
glRotatef(-light_rotation[i], 0, 0, 1);
glCallList(lists_refraction + i);
glPopMatrix();
      }
  glDisable(GL_TEXTURE_2D);

  /* Draw the sphere to make it look like it
   * has some substance */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  scene_project();
  glCallList(list_spheredisk);

  glDisable(GL_STENCIL_TEST);
}

void scene_draw()
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  scene_project();

  /* Should draw an image of the square into the stencil buffer
   * to make sure that refractions which are not on the square do not get
   * drawn, but it can wait. */

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  scene_rasterize();

  scene_draw_refracted();

}

const int pick_maxz = 0xffffffff;

int scene_pick(GLdouble x, GLdouble y)
{
  GLuint buffer[128];
  GLint vp[4], nhits, nnames;
  GLuint minz, hit = name_background;
  GLint i, j;

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  glGetIntegerv(GL_VIEWPORT, vp);

  glSelectBuffer(128, buffer);
  glRenderMode(GL_SELECT);

  // Where is this supposed to go, anyway?
  gluPickMatrix(x, vp[3] - y, 1, 1, vp);

  scene_project();

  glMatrixMode(GL_MODELVIEW);

  glInitNames();
  glPushName(name_background);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  scene_rasterize();
  glFlush();
  nhits = glRenderMode(GL_RENDER);

  minz = pick_maxz;
  for (i = j = 0; j < nhits; j++) {
    nnames = buffer[i];
    i++;
    if (buffer[i] < minz) {
      minz = buffer[i];
      hit = buffer[i + 1 + nnames];
    }
    i++;
    i += nnames + 1;
  }
  if (minz == pick_maxz) return name_background;
  else return hit;
}

void scene_reset_lights()
{
  int i;
  for (i = 0; i < nlights; i++) {
    lights[i].pos = light_init_position[i];
    light_rotation[i] = light_init_rotation[i];
  }
  lights_init_position();
  lights_list_init();
}

static void square_list_init()
{
  GLfloat x, y, inc;
  int i, j;
  glNewList(list_square, GL_COMPILE);
  glLoadName(name_square);
  glNormal3f(0, 0, 1);
  glEnable(GL_LIGHTING);
  glCallList(list_lights_on);
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, square_ambient);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, square_diffuse);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, square_specular);
  inc = (GLfloat) (10.0 / diskdiv);
  glEnable(GL_CULL_FACE);

  for (i = 0, y = -5.0; i < diskdiv; i++, y += inc) {
    glBegin(GL_TRIANGLE_STRIP);
    for (j = 0, x = -5.0; j <= diskdiv; j++, x += inc) {
      glTexCoord2f(x, y + inc);
      glVertex2f(x, y + inc);
      glTexCoord2f(x, y);
      glVertex2f(x, y);
    }
    glEnd();
  }

  glDisable(GL_CULL_FACE);
  glCallList(list_lights_off);
  glDisable(GL_LIGHTING);
  glEndList();
}

static void spheredisk_list_init()
{
  glNewList(list_spheredisk, GL_COMPILE);
  glEnable(GL_BLEND);
  glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
  glEnable(GL_LIGHTING);
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
  glMaterialfv(GL_AMBIENT, GL_FRONT_AND_BACK, sphere_ambient);
  glMaterialfv(GL_SPECULAR, GL_FRONT_AND_BACK, sphere_specular);
  glCallList(list_lights_on);
  sphere_disk.draw();
  glCallList(list_lights_off);
  glDisable(GL_COLOR_MATERIAL);
  glDisable(GL_LIGHTING);
  glDisable(GL_BLEND);
  glEndList();
}

void lights_onoff(int light, int val)
{
  lights[light].on = val;
  lights_init_onoff();
  lights_list_init(light);
  square_list_init();
}

void refraction_change(GLfloat refraction)
{
  if (refraction == index) return;
  index = refraction;
  shadow_refraction_full_build();
  refraction_list_init();
}

void divisions_change(int divisions)
{
  Point eye, look;

  if (divisions != spherediv) {
    spherediv = divisions;

    light_draw_list_init();
    lights_list_init();

    sphere_disk.set_divisions(spherediv, spherediv);
    sphere_disk.fill_points();
    sphere_disk.set_colors(white);
    sphere_disk.scale_alpha_by_z();
    eye = eyep;
    look = lookp;
    sphere_disk.face_direction((eye - look).unit());
    sphere_disk.copy_normals_from_points();
    sphere_disk.scale_translate(sphere_size, sphere_position);
    sphere_build();
    sphere_list_init();

    diskdiv = divisions;
    disk_build();
    shadow_refraction_full_build();
    square_list_init();
    spheredisk_list_init();
    shadow_list_init();
    refraction_list_init();
  }
}

int scene_move(int name, float dr, float dphi, float dtheta, int update)
{
  switch(name) {
  case name_background:
    return 0;
  case name_square:
    return 0;
  case name_sphere:
    return 0;
  default:
    if (name < name_lights || name > name_lights + nlights) return 0;
    return lights_move(name - name_lights, dr, dphi, dtheta, update);
  }
}

void scene_move_update(int name, int dr, int dphi, int dtheta)
{
  switch(name) {
  case name_background:
    break;
  case name_square:
    break;
  case name_sphere:
    break;
  default:
    if (name < name_lights || name > name_lights + nlights) break;
    lights_move_update(name - name_lights, dr, dphi, dtheta);
    break;
  }
}

#ifdef MYDEBUG
void lights_init_onoff()
#else
static void lights_init_onoff()
#endif
{
  int i;

  glNewList(list_lights_on, GL_COMPILE);
  for (i = 0; i < nlights; i++)
    if (lights[i].on) glEnable(GL_LIGHT0 + i);
    else glDisable(GL_LIGHT0 + i);
  glEndList();

  glNewList(list_lights_off, GL_COMPILE);
  for (i = 0; i < nlights; i++) glDisable(GL_LIGHT0 + i);
  glEndList();
}

#ifdef MYDEBUG
void lights_init_position()
#else
static void lights_init_position()
#endif
{
  int i;

  for (i = 0; i < nlights; i++) lights_init_position(i);

}

static void lights_init_position(int i)
{
  Point l, d;

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  l = lights[i].pos;
  l.pt[0] = (GLfloat)(lights[i].pos.pt[0] * cos((double)radians(light_rotation[i])));
  l.pt[1] = (GLfloat)(lights[i].pos.pt[0] * -sin((double)radians(light_rotation[i])));
  d = (sphere_position - l).unit();
  glLightfv(GL_LIGHT0 + i, GL_POSITION, l.pt);
  glLightfv(GL_LIGHT0 + i, GL_SPOT_DIRECTION, d.pt);
}

static void lights_list_init()
{
  int i;
  for (i = 0; i < nlights; i++) lights_list_init(i);
}

static void lights_list_init(int n)
{
  Color c;

  glNewList(lists_lights + n, GL_COMPILE);
  if (lights[n].on) {
    glLoadName(name_lights + n);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
    glCallList(list_lights_on);

    c = lights[n].diffuse;
    glMaterialfv(GL_BACK, GL_AMBIENT, c.c);
    glMaterialfv(GL_BACK, GL_DIFFUSE, black.c);
    glMaterialfv(GL_BACK, GL_SPECULAR, black.c);

    glMaterialfv(GL_FRONT, GL_AMBIENT, (c * .75).c);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, white.c);
    glMaterialfv(GL_FRONT, GL_SPECULAR, white.c);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glRotatef(-light_rotation[n], 0, 0, 1);
    glTranslatef(lights[n].pos.pt[0], lights[n].pos.pt[1],
 lights[n].pos.pt[2]);
    glRotatef((GLfloat)-degrees(atan2((double)(lights[n].pos.pt[2] - sphere_position.pt[2]),
     (double)(lights[n].pos.pt[0]))), 0, 1, 0);
    glCallList(list_light_draw);
    glPopMatrix();

    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
    glDisable(GL_LIGHTING);
    glCallList(list_lights_off);
    glDisable(GL_DEPTH_TEST);
  } else {
    /* 5.0.1 for Elans seems to object strongly to replacing
     * empty display lists, so will put some stupid command
     * in here */
    glColor3f(0, 0, 0);
  }
  glEndList();
}

static void light_draw_list_init()
{
  float c, s;
  int t;

  glNewList(list_light_draw, GL_COMPILE);
  glEnable(GL_NORMALIZE);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glScalef(.25, .15, .15);
  glBegin(GL_QUAD_STRIP);
  for (t = 0; t <= spherediv; t++) {
    c = (float) cos((double)(M_2PI * (float)t / (float)spherediv));
    s = (float) sin((double)(M_2PI * (float)t / (float)spherediv));
    glNormal3f((GLfloat).25, (GLfloat)(.968*s), (GLfloat)(.968*c));
    glVertex3f((GLfloat)0, (GLfloat)s, (GLfloat)c);
    glVertex3f((GLfloat)1, (GLfloat)(.75*s), (GLfloat)(.75*c));
  }
  glEnd();
  glNormal3f(1, 0, 0);
  glBegin(GL_TRIANGLE_STRIP);
  for (t = 0; t <= spherediv; t++) {
    c = (float)cos((double)(M_2PI * (float)t / (float)spherediv));
    s = (float)sin((double)(M_2PI * (float)t / (float)spherediv));
    glVertex3f((GLfloat)1, (GLfloat)(.75*s), (GLfloat)(.75*c));
    glVertex3f(1, 0, 0);
  }
  glEnd();
  glPopMatrix();
  glDisable(GL_NORMALIZE);
  glEndList();
}

#ifdef MYDEBUG
void lights_init()
#else
static void lights_init()
#endif
{
  int i;

  for (i = 0; i < nlights; i++) {
    glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lights[i].diffuse);
    glLightfv(GL_LIGHT0 + i, GL_SPECULAR, black.c);
    glLightfv(GL_LIGHT0 + i, GL_AMBIENT, black.c);
    glLightf(GL_LIGHT0 + i, GL_SPOT_EXPONENT, 4);
    glLightf(GL_LIGHT0 + i, GL_SPOT_CUTOFF, 90);
  }

  glLightfv(GL_LIGHT0 + nlights, GL_DIFFUSE, black.c);
  glLightfv(GL_LIGHT0 + nlights, GL_SPECULAR, black.c);
  glLightfv(GL_LIGHT0 + nlights, GL_AMBIENT, world_ambient.c);
  glEnable(GL_LIGHT0 + nlights);

  /* GL_LIGHT0 + nlights + 1 willl eventually be used to draw the
   * refractions - stay tuned. */
  glLightfv(GL_LIGHT0 + nlights + 1, GL_DIFFUSE, black.c);
  glLightfv(GL_LIGHT0 + nlights + 1, GL_SPECULAR, black.c);
  glLightfv(GL_LIGHT0 + nlights + 1, GL_AMBIENT, white.c);
}

#ifdef MYDEBUG
int lights_move(int light, float dr, float dphi, float dtheta,
#else
static int lights_move(int light, float dr, float dphi, float dtheta,

#endif
                       int update)
{
  float cphi, sphi, x, y;
  Point l, dl;

  if (!(dr || dphi || dtheta)) return 0;

  l = lights[light].pos - sphere_position;

  if (dr) {
    dl = l + l*dr;
    if (dl.mag() > sphere_size) l = dl;
  }

  if (dphi) {
    cphi = (float)cos((double)dphi);
    sphi = (float)sin((double)dphi);
    y = -l.pt[0]*sphi + l.pt[2]*cphi;

    /* This hack keeps with light from getting below the sphere -
     * the projection sections would completely freak if this ever
     * happened  - sphere_size is multiplied by two as a fudge factor*/
    if (y < 2.0*sphere_size) {
      dphi = (float)atan2((double)(l.pt[2] - 2.0*sphere_size), (double)l.pt[0]);
      cphi = (float)cos((double)dphi);
      sphi = (float)sin((double)dphi);

    }
    x = l.pt[0];
    l.pt[0] = x*cphi + l.pt[2]*sphi;
    l.pt[2] = -x*sphi + l.pt[2]*cphi;
  }

  if (dtheta) {
    light_rotation[light] += (GLfloat)degrees((double)dtheta);
    light_rotation[light] =  (GLfloat)degrees_clamp((double)light_rotation[light]);
  }

  lights[light].pos = l + sphere_position;
  lights[light].pos.pt[3] = 1;

  lights_init_position(light);
  lights_list_init(light);

  if (update) lights_move_update(light, dr ? 1 : 0, dphi ? 1 : 0,
 dtheta ? 1 : 0);
  return 1;
}

#ifdef MYDEBUG
void lights_move_update(int light, int dr, int dphi,
#else
static void lights_move_update(int light, int dr, int dphi,
#endif
       int dtheta)
{
  if (dr) {
    disk_build(light);
    shadow_refraction_full_build(light);
    shadow_list_init(light);
    refraction_list_init(light);
  } else if (dphi) {
    shadow_refraction_full_build(light);
    shadow_list_init(light);
    refraction_list_init(light);
  } else if (dtheta) {
  }

}

#ifdef MYDEBUG
int get_lists(int size)
#else
static int get_lists(int size)
#endif
{
  int i;
  i = glGenLists(size);
  if (size && !i) {
    //fprintf(stderr, "Unable to allocate %d display lists.\n");
    exit(1);
  }
  return i;
}

#ifdef MYDEBUG
void lists_init()
#else
static void lists_init()
#endif
{
  list_square = get_lists(1);
  lists_shadows = get_lists(nlights);
  lists_refraction = get_lists(nlights);
  lists_lights = get_lists(nlights);
  list_sphere = get_lists(1);
  list_spheredisk = get_lists(1);
  list_lights_on = get_lists(1);
  list_lights_off = get_lists(1);
  list_light_draw = get_lists(1);
//  sphere_build();
}

static inline int sphere_npoints()
{
  return (spherediv+1)*spherediv*3;
}

void sphere_build()
{
  int nspherepts;
  int r, t, index;
  float c, s;

  delete spherepts;
  nspherepts = sphere_npoints();
  if (nspherepts == 0) return;
  spherepts = new GLfloat[nspherepts];

  index = 0;
  for (r = 0; r <= spherediv; r++) {
    spherepts[index++] = (GLfloat)sin((double)(M_PI * (float)r / (float)spherediv));
    spherepts[index++] = 0;
    spherepts[index++] = (GLfloat)-cos((double)(M_PI * (float)r / (float)spherediv));
  }
  for (t = 1; t < spherediv; t++) {
    c = (float)cos((double)(2.0 * M_PI * (float)t / (float)spherediv));
    s = (float)sin((double)(2.0 * M_PI * (float)t / (float)spherediv));
    for (r = 0; r <= spherediv; r++) {
      spherepts[index++] = c*spherepts[r*3];
      spherepts[index++] = s*spherepts[r*3];
      spherepts[index++] = spherepts[r*3 + 2];
    }
  }

}

void sphere_list_init()
{
  glNewList(list_sphere, GL_COMPILE);
  sphere_disk.draw_by_perimeter();
  glEndList();
}

void sphere_draw()
{
  int r, t, p1, p2;

  for (t = 1; t < spherediv; t++) {
    glBegin(GL_QUAD_STRIP);
    p1 = (t - 1) * (spherediv + 1);
    p2 = t * (spherediv + 1);
    for (r = 0; r <= spherediv; r++, p1++, p2++) {
      glNormal3fv(&spherepts[p1*3]);
      glVertex3fv(&spherepts[p1*3]);
      glNormal3fv(&spherepts[p2*3]);
      glVertex3fv(&spherepts[p2*3]);
    }
    glEnd();
  }

  glBegin(GL_QUAD_STRIP);
  p1 = (spherediv + 1) * (spherediv - 1);
  p2 = 0;
  for (r = 0; r <= spherediv; r++, p1++, p2++) {
    glNormal3fv(&spherepts[p1*3]);
    glVertex3fv(&spherepts[p1*3]);
    glNormal3fv(&spherepts[p2*3]);
    glVertex3fv(&spherepts[p2*3]);
  }
  glEnd();
}

static void disk_build()
{
  int i;
  for (i = 0; i < nlights; i++) disk_build(i);
}

static void disk_build(int disk)
{
  Point light;
  light = lights[disk].pos;

  disks[disk].free_points_normals();
  disks[disk].free_colors();

  disks[disk].set_divisions(diskdiv, diskdiv);
  disks[disk].set_angle((float)(2.0 *
acos((double)(sphere_size / light.dist(sphere_position)))));
  disks[disk].fill_points();
}

static void shadow_list_init()
{
  int i;
  for (i = 0; i < nlights; i++) shadow_list_init(i);
}

static void shadow_list_init(int n)
{
  Color c(square_ambient[0], square_ambient[1], square_ambient[2]);

  c *= world_ambient;

  glNewList(lists_shadows + n, GL_COMPILE);
  glColorMask(lights[n].shadow_mask[0], lights[n].shadow_mask[1],
      lights[n].shadow_mask[2], lights[n].shadow_mask[3]);
  glDisable(GL_DEPTH_TEST);
  glColor3fv(c.c);
  shadows[n].draw_by_perimeter(0, 0, 1);
  glColorMask(1, 1, 1, 1);
  glEndList();
}

static void refraction_list_init()
{
  int i;
  for (i = 0; i < nlights; i++) refraction_list_init(i);
}

static void refraction_list_init(int n) {
  /* This could be loads simpler if it weren't for the texture mapping -
   * that's where all this weirdness with GL_LIGHT0 + nlights + 1 comes
   * in */
  glNewList(lists_refraction + n, GL_COMPILE);

  glEnable(GL_LIGHTING);
  glCallList(list_lights_off);
  /* This is white ambient light */
  glEnable(GL_LIGHT0 + nlights + 1);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, black.c);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, black.c);
  glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
  glEnable(GL_COLOR_MATERIAL);

  glBlendFunc(GL_ONE, GL_ONE);
  glEnable(GL_BLEND);

  glDisable(GL_DEPTH_TEST);
  refraction[n].draw();

  glDisable(GL_BLEND);

  glDisable(GL_COLOR_MATERIAL);
  glDisable(GL_LIGHT0 + nlights + 1);
  glDisable(GL_LIGHTING);

  glEndList();
}

static void shadow_refraction_full_build()
{
  int i;
  for (i = 0; i < nlights; i++) shadow_refraction_full_build(i);
}

/* This entire function is written a bit oddly... */
static void shadow_refraction_full_build(int n)
{
  Color c;
  float dist_light;
  Point dlight, zaxis;

  /* Make sure that we're starting over from scratch */
  shadows[n].free_points_normals();
  shadows[n].free_colors();
  refraction[n].free_points_normals();
  refraction[n].free_colors();

  dlight = lights[n].pos - sphere_position;
  dist_light = dlight.mag();
  dlight.unitize();
  zaxis.pt[0] = 0;
  zaxis.pt[1] = 0;
  zaxis.pt[2] = 1;

  shadows[n].set_divisions(disks[n].get_rdivisions(),
   disks[n].get_tdivisions());
  refraction[n].set_divisions(disks[n].get_rdivisions(),
      disks[n].get_tdivisions());

  shadows[n].alloc_points();
  shadows[n].face_direction(dlight, disks[n]);
  shadows[n].scale_translate(sphere_size, sphere_position);

  c = square_diffuse;
  c *= lights[n].diffuse;

  refraction[n].copy_points(disks[n]);
  refraction[n].set_colors(c);
  refraction[n].scale_colors_by_z();

  refraction[n].scale(sphere_size);
  refraction[n].refract_normals(zaxis * dist_light, index);
  refraction[n].face_direction(dlight);

  refraction[n].project_borrow_points(shadows[n]);
  refraction[n].free_normals();
  shadows[n].project(lights[n].pos);
  if (index != 1.0) refraction[n].scale_colors_by_darea(shadows[n]);
}

int scene_load_texture(char *texfile)
{
#ifdef TEXTURE
  teximage = auxRGBImageLoad(texfile);
#else
  teximage = NULL;
#endif

  if (teximage == NULL) return 0;
  else return 1;
}

void texture_init()
{
  if (teximage == NULL) return;

  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, teximage->sizeX, teximage->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, teximage->data);

  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glRotatef(90, 0, 0, 1);
  /* This scales the texture so that it fits on the square */
  glTranslatef(.5, .5, 0);
  glScalef(2, 2, 1);
  glMatrixMode(GL_MODELVIEW);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
 GL_NEAREST_MIPMAP_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
 GL_LINEAR);
}


UNITDISK.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "Unitdisk.hxx"

const float fudge = .000001;
const float M_2PI = 2.0 * M_PI;

Unitdisk::Unitdisk()
{
  rdivisions = 3;
  tdivisions = 10;
  points = normals = NULL;
  colors = NULL;
  points_size = normals_size = colors_size = 0;
  angle = M_PI;
  zaxis[0] = 0;
  zaxis[1] = 0;
  zaxis[2] = 1;

  still_in_xy = 1;

  sintable = costable = NULL;
}

Unitdisk::~Unitdisk()
{
}

void Unitdisk::draw()
{
  if (points == NULL) return;
  glNormal3f(0, 0, 1);
  if (colors == NULL) draw_nocolors();
  else if (normals == NULL) draw_colors_nonormals();
  else draw_colors_normals();
}

void Unitdisk::draw_nocolors()
{
  int r, t, p1, p2;
  int has_n;

  has_n = (normals != NULL);

  for (t = 1; t < tdivisions; t++) {
    glBegin(GL_QUAD_STRIP);
    p1 = t * (rdivisions + 1);
    p2 = (t - 1) * (rdivisions + 1);
    for (r = 0; r <= rdivisions; r++, p1++, p2++) {
      if (has_n) glNormal3fv(normals[p1].pt);
#ifdef TEXTURE
      glTexCoord2fv(points[p1].pt);
#endif
      glVertex3fv(points[p1].pt);
      if (has_n) glNormal3fv(normals[p2].pt);
#ifdef TEXTURE
      glTexCoord2fv(points[p2].pt);
#endif
      glVertex3fv(points[p2].pt);
    }
    glEnd();
  }

  glBegin(GL_QUAD_STRIP);
  p1 = 0;
  p2 = (rdivisions + 1) * (tdivisions - 1);
  for (r = 0; r <= rdivisions; r++, p1++, p2++) {
    if (has_n) glNormal3fv(normals[p1].pt);
#ifdef TEXTURE
    glTexCoord2fv(points[p1].pt);
#endif
    glVertex3fv(points[p1].pt);
    if (has_n) glNormal3fv(normals[p2].pt);
#ifdef TEXTURE
    glTexCoord2fv(points[p2].pt);
#endif
    glVertex3fv(points[p2].pt);
  }
  glEnd();
}

void Unitdisk::draw_colors_nonormals()
{
  int r, t, p1, p2;

  for (t = 1; t < tdivisions; t++) {
    glBegin(GL_QUAD_STRIP);
    p1 = t * (rdivisions + 1);
    p2 = (t - 1) * (rdivisions + 1);
    for (r = 0; r <= rdivisions; r++, p1++, p2++) {
      glColor4fv(colors[p1].c);
#ifdef TEXTURE
      glTexCoord2fv(points[p1].pt);
#endif
      glVertex3fv(points[p1].pt);
      glColor4fv(colors[p2].c);
#ifdef TEXTURE
      glTexCoord2fv(points[p2].pt);
#endif
      glVertex3fv(points[p2].pt);
    }
    glEnd();
  }

  glBegin(GL_QUAD_STRIP);
  p1 = 0;
  p2 = (rdivisions + 1) * (tdivisions - 1);
  for (r = 0; r <= rdivisions; r++, p1++, p2++) {
    glColor4fv(colors[p1].c);
#ifdef TEXTURE
    glTexCoord2fv(points[p1].pt);
#endif
    glVertex3fv(points[p1].pt);
    glColor4fv(colors[p2].c);
#ifdef TEXTURE
    glTexCoord2fv(points[p2].pt);
#endif
    glVertex3fv(points[p2].pt);
  }
  glEnd();
}

void Unitdisk::draw_colors_normals()
{
  int r, t, p1, p2;

  for (t = 1; t < tdivisions; t++) {
    glBegin(GL_QUAD_STRIP);
    p1 = t * (rdivisions + 1);
    p2 = (t - 1) * (rdivisions + 1);
    for (r = 0; r <= rdivisions; r++, p1++, p2++) {
      glColor4fv(colors[p1].c);
      glNormal3fv(normals[p1].pt);
#ifdef TEXTURE
      glTexCoord2fv(points[p1].pt);
#endif
      glVertex3fv(points[p1].pt);
      glColor4fv(colors[p2].c);
      glNormal3fv(normals[p2].pt);
#ifdef TEXTURE
      glTexCoord2fv(points[p2].pt);
#endif
      glVertex3fv(points[p2].pt);
    }
    glEnd();
  }

  glBegin(GL_QUAD_STRIP);
  p1 = 0;
  p2 = (rdivisions + 1) * (tdivisions - 1);
  for (r = 0; r <= rdivisions; r++, p1++, p2++) {
    glColor4fv(colors[p1].c);
    glNormal3fv(normals[p1].pt);
#ifdef TEXTURE
    glTexCoord2fv(points[p1].pt);
#endif
    glVertex3fv(points[p1].pt);
    glColor4fv(colors[p2].c);
    glNormal3fv(normals[p2].pt);
#ifdef TEXTURE
    glTexCoord2fv(points[p2].pt);
#endif
    glVertex3fv(points[p2].pt);
  }
  glEnd();
}

void Unitdisk::draw_by_perimeter(int pass_colors, int pass_norms,
 int pass_tex)
{
  int i, index, r = rdivisions + 1;

  if (points == NULL) return;
  if (pass_colors && colors == NULL) {
    //fprintf(stderr, "Warning:  No colors to draw in Unitdisk.c++");
    pass_colors = 0;
  }
  if (pass_norms && normals == NULL) {
    //fprintf(stderr, "Warning:  No normals to draw in Unitdisk.c++");
    pass_norms = 0;
  }
  glBegin(GL_POLYGON);
  for (i = 0, index = rdivisions; i < tdivisions; i++, index += r) {
    if (pass_colors) glColor4fv(colors[index].c);
    if (pass_norms) glNormal3fv(normals[index].pt);
#ifdef TEXTURE
    if (pass_tex) glTexCoord2fv(points[index].pt);
#endif
    glVertex3fv(points[index].pt);
  }
  glEnd();

}

void Unitdisk::set_angle(float new_angle)
{
  angle = new_angle;
}

GLfloat Unitdisk::get_angle()
{
  return angle;
}

GLfloat Unitdisk::get_radius()
{
  return (GLfloat)cos((double)((M_PI - angle) / 2.0));
}

void Unitdisk::set_divisions(int new_rdivisions, int new_tdivisions)
{
  if (tdivisions != new_tdivisions) {
    delete sintable;
    delete costable;
    sintable = costable = NULL;
  }
  if (tdivisions != new_tdivisions || rdivisions != new_rdivisions) {
    rdivisions = new_rdivisions;
    tdivisions = new_tdivisions;
    free_points();
    free_normals();
    free_colors();
  }
}

int Unitdisk::get_rdivisions()
{
  return rdivisions;
}

int Unitdisk::get_tdivisions()
{
  return tdivisions;
}

void Unitdisk::alloc_points()
{
  int npoints = get_npoints();
  if (npoints > points_size) {
    delete points;
    points = new Point[npoints];
    points_size = npoints;
  }
}

void Unitdisk::alloc_normals()
{
  int npoints = get_npoints();
  if (npoints > normals_size) {
    delete normals;
    normals = new Point[npoints];
    normals_size = npoints;
  }
}

void Unitdisk::alloc_points_normals()
{
  alloc_points();
  alloc_normals();
}

void Unitdisk::free_points()
{
  delete points;
  points = NULL;
  points_size = 0;
}

void Unitdisk::free_normals()
{
  delete normals;
  normals = NULL;
  normals_size = 0;
}

void Unitdisk::free_points_normals()
{
  free_points();
  free_normals();
}

void Unitdisk::fill_points()
{
  alloc_points();
  fill_either(points);
}

void Unitdisk::fill_normals()
{
  alloc_normals();
  fill_either(normals);
}

void Unitdisk::fill_points_normals()
{
  alloc_points();
  alloc_normals();
  fill_either(points);
  fill_either(normals);
}

void Unitdisk::copy_points(Unitdisk src)
{
  set_divisions(src.rdivisions, src.tdivisions);
  alloc_points();
  copy_either(points, src.points);
}

void Unitdisk::copy_normals(Unitdisk src)
{
  set_divisions(src.rdivisions, src.tdivisions);
  alloc_normals();
  copy_either(normals, src.normals);
}

void Unitdisk::copy_either(Point *dpt, Point *spt) {
  int i, npoints;
  npoints = get_npoints();
  for (i = 0; i < npoints; i++, dpt++, spt++) {
    dpt->pt[0] = spt->pt[0];
    dpt->pt[1] = spt->pt[1];
    dpt->pt[2] = spt->pt[2];
  }
}

void Unitdisk::copy_normals_from_points(Unitdisk src) {
  set_divisions(src.rdivisions, src.tdivisions);
  alloc_normals();
  copy_either(normals, src.points);
}

void Unitdisk::copy_normals_from_points() {
  copy_normals_from_points(*this);
}

void Unitdisk::fill_either(Point *what) {
  int t, r;
  int i;

  fill_either_strip1(what);
  if (sintable == NULL) fill_trig_tables();
  i = rdivisions + 1;
  for (t = 1; t < tdivisions; t++) {
    for (r = 0; r <= rdivisions; r++) {
      what[i].pt[0] = costable[t] * what[r].pt[0];
      what[i].pt[1] = sintable[t] * what[r].pt[0];
      what[i].pt[2] = what[r].pt[2];
      i++;
    }
  }
}

void Unitdisk::fill_points_strip1()
{
  alloc_points();
  fill_either_strip1(points);
}

void Unitdisk::fill_either_strip1(Point *what) {
  float radius, rinc;
  int r;

  rinc = get_radius() / (float)rdivisions;
  radius = 0.0;
  for (r = 0; r <= rdivisions; r++, radius += rinc) {
    what[r].pt[0] = radius;
    what[r].pt[1] = 0;
    // Round-off error avoidance hack
    what[r].pt[2] = (GLfloat)(1.0 - what[r].pt[0]*what[r].pt[0]);
    if (what[r].pt[2] > 0.0) what[r].pt[2] = (GLfloat)sqrt((double)what[r].pt[2]);
    else what[r].pt[2] = 0.0;
  }
}

void Unitdisk::translate(Point trans)
{
  int i, npoints;
  npoints = get_npoints();
  for (i = 0; i < npoints; i++) {
    points[i].pt[0] += trans.pt[0];
    points[i].pt[1] += trans.pt[1];
    points[i].pt[2] += trans.pt[2];
  }
}

void Unitdisk::scale(float s)
{
  int i, npoints;
  npoints = get_npoints();
  for (i = 0; i < npoints; i++) {
    points[i].pt[0] *= s;
    points[i].pt[1] *= s;
    points[i].pt[2] *= s;
  }
}

void Unitdisk::scale_translate(float s, Point trans)
{
  int i, npoints;
  npoints = get_npoints();
  for (i = 0; i < npoints; i++) {
    points[i].pt[0] = points[i].pt[0]*s + trans.pt[0];
    points[i].pt[1] = points[i].pt[1]*s + trans.pt[1];
    points[i].pt[2] = points[i].pt[2]*s + trans.pt[2];
  }
}

int Unitdisk::get_npoints()
{
  return (rdivisions + 1) * tdivisions;
}

void Unitdisk::project()
{
  int i, npoints;

  if (normals == NULL) {
    //fprintf(stderr, "Warning:  No normals defined when project() called.\n");
    fill_normals();
  }
  if (points == NULL) fill_points();
  npoints = get_npoints();
  for (i = 0; i < npoints; i++) {
    /* I'm not sure quite what the justification for this is, but it
     * seems to work */
    if (normals[i].pt[2] < 0.0) normals[i].pt[2] = -normals[i].pt[2];
    points[i] = points[i].project_direction(normals[i]);
  }
}

void Unitdisk::project(Point projpt)
{
  int i, npoints = get_npoints();
  float x, y, z;
  Point *pt;

  if (points == NULL) fill_points();
  x = projpt.pt[0];
  y = projpt.pt[1];
  z = projpt.pt[2];
  pt = points;
  for (i = 0; i < npoints; i++, pt++) pt->project_self(x, y, z);
}

void Unitdisk::project_borrow_points(Unitdisk src)
{
  int i, npoints = get_npoints();
  Point *pt, *spt, *sn;
  spt = src.points;
  sn = normals;
  alloc_points();
  pt = points;
  for (i = 0; i < npoints; i++, pt++, spt++, sn++) {
    /* I'm not sure quite what the justification for this is, but it
     * seems to work */
    if (normals[i].pt[2] < 0.0) normals[i].pt[2] = -normals[i].pt[2];
    pt->compute_projected(spt->pt[0], spt->pt[1], spt->pt[2],
  sn->pt[0], sn->pt[1], sn->pt[2]);
  }
}

void Unitdisk::refract_normals(Point light, GLfloat I)
{
  Point dlight;
  float cos1, sin1, cos2, sin2;
  int use_normals;
  int r, t, i;

  if (points == NULL) {
    //fprintf(stderr, "Attempting to refract without points.\n");
    fill_normals();
  }

  use_normals = (normals != NULL);
  alloc_normals();

  /* Do the theta = 0 diagonal */
  for (r = 0; r <= rdivisions; r++) {
    /* Find the original normal - use the unit of the points if there are
     * no normals */
    if (!use_normals) normals[r] = points[r].unit();

    /* Compute the direction to the light */
    dlight = (light - points[r]).unit();

    /* Compute the cosine and the sine of the original angle */
    cos1 = dlight.dot(normals[r]);
    sin1 = (float)(1.0 - cos1*cos1);
    if (sin1 <= 0.0) continue;
    sin1 = (float)sqrt((double)sin1);

    /* Compute the cosine and the sine of the new angle */
    sin2 = sin1 / I;
    cos2 = (float)sqrt((double)(1.0 - sin2*sin2));

    /* Rotate the normal by the new sine and cosine */
    normals[r] = normals[r].rotate_abouty(cos2, -sin2);
  }

  /* Copy the rest of the rows from the current row */
  i = rdivisions + 1;
  if (sintable == NULL) fill_trig_tables();
  for (t = 1; t < tdivisions; t++) {
    for (r = 0; r <= rdivisions; r++) {
      normals[i].pt[0] = costable[t] * normals[r].pt[0];
      normals[i].pt[1] = sintable[t] * normals[r].pt[0];
      normals[i].pt[2] = normals[r].pt[2];
      i++;
    }
  }

}

void Unitdisk::face_direction(Point d)
{
  face_direction(d, *this);
}

void Unitdisk::face_direction(Point d, Unitdisk src)
{
  Point *spt, *dpt, *sn, *dn;
  float sin1, cos1;
  float x;
  int npoints, i;

  if (d.pt[1]) {
    /*fprintf(stderr,
    "Internal error:  Can't face in direction not in xz plane.");*/
    return;
  }

  cos1 = d.pt[2];
  sin1 = d.pt[0];

  if (sin1 * sin1 > fudge) {
    spt = src.points;
    dpt = points;
    sn = src.normals;
    dn = normals;
    /* Change this to be seperate loops for points&&normals, points, normals
     * (faster than testing every iteration */
    npoints = get_npoints();
    for (i = 0; i < npoints; i++) {
      if (points != NULL) {
x = spt->pt[0];
dpt->pt[0] = x*cos1 + spt->pt[2]*sin1;
dpt->pt[1] = spt->pt[1];
dpt->pt[2] = -x*sin1 + spt->pt[2]*cos1;
spt++; dpt++;
      }
      if (normals != NULL) {
x = sn->pt[0];
dn->pt[0] = x*cos1 + sn->pt[2]*sin1;
dn->pt[1] = sn->pt[1];
dn->pt[2] = -x*sin1 + sn->pt[2]*cos1;
sn++; dn++;
      }
    }
  } else if (points != NULL && points != src.points) {
    copy_points(src);
    if (normals != NULL) copy_normals(src);
  }
}

void Unitdisk::alloc_colors()
{
  int ncolors = get_npoints();
  if (ncolors > colors_size) {
    delete colors;
    colors = new Color[ncolors];
    colors_size = ncolors;
  }
}

void Unitdisk::map_normals_to_colors()
{
  int t, r;
  int i;

  if (normals == NULL) fill_normals();
  alloc_colors();
  i = 0;
  for (t = 1; t <= tdivisions; t++) {
    for (r = 0; r <= rdivisions; r++) {
      colors[i] *= normals[r].pt[2];
      colors[i].c[3] = 1;
      i++;
    }
  }
}

void Unitdisk::map_z_to_colors()
{
  int i, npoints = get_npoints();

  if (points == NULL) {
    //fprintf(stderr, "Warning:  no points defined in map_z_to_colors()\n");
    fill_points();
  }

  alloc_colors();
  for (i = 0; i < npoints; i++) {
    colors[i] = points[i].pt[2];
    colors[i].c[3] = 1;
  }
}

void Unitdisk::scale_alpha_by_z()
{
  int i, npoints = get_npoints();

  if (colors == NULL) alloc_colors();
  if (points == NULL) {
    alloc_points();
    fill_points();
  }
  for (i = 0; i < npoints; i++) colors[i].c[3] *= points[i].pt[2];
}

void Unitdisk::scale_colors_by_z()
{
  int i, npoints = get_npoints();

  if (colors == NULL) alloc_colors();
  if (points == NULL) {
    alloc_points();
    fill_points();
  }
  for (i = 0; i < npoints; i++) colors[i] *= points[i].pt[2];
}

void Unitdisk::scale_colors_by_normals(Point light)
{
  scale_colors_by_normals(light, *this);
}

void Unitdisk::scale_colors_by_normals(Point light, Unitdisk src_normals)
{
  scale_colors_by_either(light, src_normals.normals);
}

void Unitdisk::scale_colors_by_points(Point light, Unitdisk src_points)
{
  scale_colors_by_either(light, src_points.points);
}

void Unitdisk::scale_colors_by_either(Point light, Point *what)
{
  int t, r;
  int i;
  if (what == NULL) {
    //fprintf(stderr, "Scaling colors to NULL pointer.\n");
    return;
  }
  /*if (light.pt[0] || light.pt[1] || light.pt[2] < 0.0) {
    fprintf(stderr, "Light not on z axis in scale_colors_by_normals.\n");
  }*/

  alloc_colors();
  for (r = 0; r <= rdivisions; r++)
    colors[r] *= what[r].dot(light);
  i = rdivisions + 1;
  for (t = 1; t < tdivisions; t++) {
    for (r = 0; r <= rdivisions; r++) {
      colors[i] = colors[r];
      i++;
    }
  }
}

void Unitdisk::set_colors(Color c)
{
  Color *dst;
  int i, npoints = get_npoints();
  alloc_colors();
  dst = colors;
  for (i = 0; i < npoints; i++, dst++) {
    dst->c[0] = c.c[0];
    dst->c[1] = c.c[1];
    dst->c[2] = c.c[2];
    dst->c[3] = c.c[3];
  }
}

void Unitdisk::add_colors(Color c)
{
  int i, npoints = get_npoints();
  alloc_colors();
  for (i = 0; i < npoints; i++) colors[i] += c;
}

void Unitdisk::free_colors()
{
  delete colors;
  colors = NULL;
  colors_size = 0;
}

inline float Unitdisk::area_triangle(Point a, Point b, Point c)
{
  return (float)(((a.pt[0]*b.pt[1] + b.pt[0]*c.pt[1] + c.pt[0]*a.pt[1]) -
   (a.pt[1]*b.pt[0] + b.pt[1]*c.pt[0] + c.pt[1]*a.pt[0])) * .5);
}

inline float Unitdisk::area_triangle(GLfloat *a, GLfloat *b,
     GLfloat *c)
{
  return (float)(((a[0]*b[1] + b[0]*c[1] + c[0]*a[1]) -
   (a[1]*b[0] + b[1]*c[0] + c[1]*a[0])) * .5);
}

inline float Unitdisk::area_2triangle(GLfloat *a, GLfloat *b,
     GLfloat *c)
{
  return ((a[0]*b[1] + b[0]*c[1] + c[0]*a[1]) -
  (a[1]*b[0] + b[1]*c[0] + c[1]*a[0]));
}

void Unitdisk::scale_colors_by_darea(Unitdisk disk)
{
  int pt1, pt2, pt3;
  int t, r, i;
  int npoints = get_npoints();
  float *rproducts1, *tproducts1, *rproducts2, *tproducts2;
  float area1, area2;

  rproducts1 = new float[npoints];
  tproducts1 = new float[npoints];
  rproducts2 = new float[npoints];
  tproducts2 = new float[npoints];

  /* Compute the products of the segments which make up the disk -
   * these will later be used in the area calculations */
  i = 0;
  for (t = 0; t < tdivisions; t++) {
    for (r = 0; r < rdivisions; r++) {
      pt1 = i;
      pt2 = i + 1;
      rproducts1[i] = (points[pt1].pt[0]*points[pt2].pt[1] -
       points[pt1].pt[1]*points[pt2].pt[0]);
      rproducts2[i] = (disk.points[pt1].pt[0]*disk.points[pt2].pt[1] -
       disk.points[pt1].pt[1]*disk.points[pt2].pt[0]);
      pt2 = ((t+1)%tdivisions)*(rdivisions + 1) + r;
      tproducts1[i] = (points[pt1].pt[0]*points[pt2].pt[1] -
       points[pt1].pt[1]*points[pt2].pt[0]);
      tproducts2[i] = (disk.points[pt1].pt[0]*disk.points[pt2].pt[1] -
       disk.points[pt1].pt[1]*disk.points[pt2].pt[0]);
      i++;
    }
    pt1 = i;
    pt2 = ((t+1)%tdivisions)*(rdivisions + 1) + r;
    tproducts1[i] = (points[pt1].pt[0]*points[pt2].pt[1] -
     points[pt1].pt[1]*points[pt2].pt[0]);
    tproducts2[i] = (disk.points[pt1].pt[0]*disk.points[pt2].pt[1] -
     disk.points[pt1].pt[1]*disk.points[pt2].pt[0]);
    i++;
  }

  /* Compute the area at the center of the disk */
  area1 = area2 = 0.0;
  r = 1;
  for (t = 0; t <= tdivisions; t++) {
    pt1 = (t%tdivisions)*(rdivisions+1) + r;
    area1 += tproducts1[pt1];
    area2 += tproducts2[pt1];
  }
  if (area1 != 0.0) area1 = (float)fabs((double)(area2 / area1));
  for (t = 0; t < tdivisions; t++) {
    colors[t*(rdivisions+1)] *= area1;
  }

  for (t = 0; t < tdivisions; t++) {
    for (r = 1; r < rdivisions; r++) {
      pt1 = (t ? t-1 : tdivisions-1)*(rdivisions+1) + r - 1;
      pt3 = t*(rdivisions + 1) + r - 1;
      pt2 = ((t+1) % tdivisions)*(rdivisions+1) + r - 1;
      area1 = rproducts1[pt1] + rproducts1[pt1 + 1];
      area1 += tproducts1[pt1 + 2] + tproducts1[pt3 + 2];
      area1 -= rproducts1[pt2 + 1] + rproducts1[pt2];
      area1 -= tproducts1[pt3] + tproducts1[pt1];
      area2 = rproducts2[pt1] + rproducts2[pt1 + 1];
      area2 += tproducts2[pt1 + 2] + tproducts2[pt3 + 2];
      area2 -= rproducts2[pt2 + 1] + rproducts2[pt2];
      area2 -= tproducts2[pt3] + tproducts2[pt1];
      if (area1 != 0.0) area1 = (float)fabs((double)(area2 / area1));
      colors[pt3 + 1] *= area1;
    }
  }

  /* Compute the area around the outside of the disk */
  r = rdivisions;
  for (t = 0; t < tdivisions; t++) {
    pt1 = (t ? t-1 : tdivisions-1)*(rdivisions+1) + r - 1;
    pt3 = t*(rdivisions + 1) + r - 1;
    pt2 = ((t+1) % tdivisions)*(rdivisions+1) + r - 1;
    area1 = rproducts1[pt1];
    area1 += tproducts1[pt1 + 1] + tproducts1[pt3 + 1];
    area1 -= rproducts1[pt2];
    area1 -= tproducts1[pt1] + tproducts1[pt3];
    area2 = rproducts2[pt1];
    area2 += tproducts2[pt1 + 1] + tproducts2[pt3 + 1];
    area2 -= rproducts2[pt2];
    area2 -= tproducts2[pt1] + tproducts2[pt3];
    if (area1 != 0.0) area1 = (float)fabs((double)(area2 / area1));
    colors[pt3 + 1] *= area1;
  }

  delete rproducts1;
  delete tproducts1;
  delete rproducts2;
  delete tproducts2;
}

void Unitdisk::fill_trig_tables()
{
  int t;

  delete sintable;
  delete costable;
  sintable = new float[tdivisions];
  costable = new float[tdivisions];
  for (t = 0; t < tdivisions; t++) {
    costable[t] = (float)cos((double)(M_2PI * (float)t / (float)tdivisions));
    sintable[t] = (float)sin((double)(M_2PI * (float)t / (float)tdivisions));
  }
}


GENGL.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#include "gengl.h"

CHAR szAppName[]="OpenGLGeneric";

HGLRC hRC;

extern BOOL draw_cone;
extern BOOL draw_pyramid;
extern HPALETTE ghpalOld, ghPalette;

/* forward declarations of helper functions in this module */
HWND   WINAPI InitializeWindow (HINSTANCE, int);
LONG   WINAPI CommandHandler (HWND, WPARAM, LPARAM);
LONG   WINAPI MainWndProc (HWND, UINT, WPARAM, LPARAM);

/* entry point of this executable */
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG        msg;
    HWND       hWnd;

    /* previous instances do not exist in Win32 */
    if (hPrevInstance)
        return 0;

    if (!(hWnd = InitializeWindow (hInstance, nCmdShow)))
        return FALSE;

    /* main window message loop */
    while (GetMessage (&msg, NULL, 0, 0))
    {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
    }

    /* return success of application */
    return TRUE;
}

HWND WINAPI InitializeWindow (HINSTANCE hInstance, int nCmdShow)
    {
    WNDCLASS   wc;
    HWND       hWnd;

    /* Register the frame class */
    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC)MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon (hInstance, szAppName);
    wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = szAppName;
    wc.lpszClassName = szAppName;

    if (!RegisterClass (&wc) )
        return FALSE;

    /* Create the frame */
    hWnd = CreateWindow (szAppName,
             "Generic OpenGL Sample",
     WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
             CW_USEDEFAULT,
             CW_USEDEFAULT,
             CW_USEDEFAULT,
             CW_USEDEFAULT,
             NULL,
             NULL,
             hInstance,
             NULL);

    /* make sure window was created */
    if (!hWnd)
        return FALSE;

    SendMessage(hWnd, WM_INIT, 0, 0L);

    /* show and update main window */
    ShowWindow (hWnd, nCmdShow);

    UpdateWindow (hWnd);

    return hWnd;
}

/* main window procedure */
LONG WINAPI MainWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
    LONG    lRet = 1;

    switch (uMsg)
    {
    case WM_CREATE:
        {
    HDC  hDC;

            hDC = GetDC(hWnd);
    bSetupPixelFormat(hDC);

            hRC = wglCreateContext( hDC );
            wglMakeCurrent( hDC, hRC );
}
break;

    case WM_PAINT:
        {
    HDChDC;
    PAINTSTRUCTps;

    hDC = BeginPaint(hWnd, &ps);

    draw_scene(hWnd);

    EndPaint(hWnd, &ps);
        }
break;

    case WM_INIT:
        initialize(hWnd);
        break;

    case WM_SIZE:
        resize(hWnd);
            break;

        // The WM_QUERYNEWPALETTE message informs a window that it is about to
        // receive input focus. In response, the window receiving focus should
        // realize its palette as a foreground palette and update its client
        // area. If the window realizes its palette, it should return TRUE;
        // otherwise, it should return FALSE.

        case WM_QUERYNEWPALETTE:
        {
            HDC     hDC;

            if(ghPalette)
            {
                hDC = GetDC(hWnd);

                // Select and realize the palette

                ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
                RealizePalette(hDC);

                // Redraw the client area

                InvalidateRect(hWnd, NULL, TRUE);
                UpdateWindow(hWnd);

                if(ghpalOld)
                    SelectPalette(hDC, ghpalOld, FALSE);

                ReleaseDC(hWnd, hDC);

                return TRUE;
            }

            return FALSE;
        }

        // The WM_PALETTECHANGED message informs all windows that the window
        // with input focus has realized its logical palette, thereby changing
        // the system palette. This message allows a window without input focus
        // that uses a color palette to realize its logical palettes and update
        // its client area.
        //
        // This message is sent to all windows, including the one that changed
        // the system palette and caused this message to be sent. The wParam of
        // this message contains the handle of the window that caused the system
        // palette to change. To avoid an infinite loop, care must be taken to
        // check that the wParam of this message does not match the window's
        // handle.

        case WM_PALETTECHANGED:
        {
            HDC         hDC;

            // Before processing this message, make sure we
            // are indeed using a palette

            if (ghPalette)
            {
                // If this application did not change the palette, select
                // and realize this application's palette

                if (wParam != (WPARAM)hWnd)
                {
                    // Need the window's DC for SelectPalette/RealizePalette

                    hDC = GetDC(hWnd);

                    // Select and realize our palette

                    ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
                    RealizePalette(hDC);

                    // WHen updating the colors for an inactive window,
                    // UpdateColors can be called because it is faster than
                    // redrawing the client area (even though the results are
                    // not as good)

                    UpdateColors(hDC);

                    // Clean up

                    if (ghpalOld)
                       SelectPalette(hDC, ghpalOld, FALSE);

                    ReleaseDC(hWnd, hDC);
                }
            }
            break;
        }

    case WM_COMMAND:
            lRet = CommandHandler (hWnd, wParam, lParam);
            break;

       case WM_CLOSE:
        {
        HGLRC hRC;
        HDC  hDC;

                /* release and free the device context and rendering context */
        hRC = wglGetCurrentContext();
        hDC = wglGetCurrentDC();

        wglMakeCurrent(NULL, NULL);

        if (hRC)
        wglDeleteContext(hRC);
        if (hDC)
            ReleaseDC(hWnd, hDC);

                /* call destroy window to cleanup and go away */
                DestroyWindow (hWnd);
        }
        break;

       case WM_DESTROY:
        {
        HGLRC hRC;
        HDC  hDC;

                /* release and free the device context and rendering context */
        hRC = wglGetCurrentContext();
        hDC = wglGetCurrentDC();

        wglMakeCurrent(NULL, NULL);

        if (hRC)
        wglDeleteContext(hRC);
        if (hDC)
            ReleaseDC(hWnd, hDC);

                PostQuitMessage (0);
        }
        break;

    default:
            /* pass all unhandled messages to DefWindowProc */
            lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
        break;
    }

    /* return 1 if handled message, 0 if not */
    return lRet;
}

/* handle all WM_COMMAND messages here */
LONG WINAPI CommandHandler (
    HWND    hWnd,
    WPARAM  wParam,
    LPARAM  lParam)
{
    HMENU   hMenu;

    switch (LOWORD(wParam))
    {
    case IDM_EXIT:
            /* exit application */
            PostMessage (hWnd, WM_CLOSE, 0, 0L);
        break;

    case IDM_CONE:
    hMenu = GetMenu(hWnd);
    draw_cone = !draw_cone;
    CheckMenuItem(hMenu,IDM_CONE,(draw_cone?MF_CHECKED:MF_UNCHECKED));
    PostMessage (hWnd, WM_PAINT, 0, 0L);
break;

    case IDM_PYRAMID:
    hMenu = GetMenu(hWnd);
    draw_pyramid = !draw_pyramid;
    CheckMenuItem(hMenu,IDM_PYRAMID,(draw_pyramid?MF_CHECKED:MF_UNCHECKED));
    PostMessage (hWnd, WM_PAINT, 0, 0L);
break;

    default:
            return FALSE;
    }
    return TRUE;
}


GENGL.H

#include "glos.h"
#include <gl.h>
#include <glu.h>
#include "genglrc.h"

#define WM_INITWM_USER

GLvoid initialize(HWND);
GLvoid draw_scene(HWND);
GLvoid resize(HWND);
BOOL bSetupPixelFormat(HDC);


GENGLRC.H

#define IDM_EXIT100
#define IDM_CONE200
#define IDM_PYRAMID201


RENDER.C

#include "gengl.h"

HPALETTE ghpalOld, ghPalette = (HPALETTE) 0;

BOOL draw_cone = FALSE;
BOOL draw_pyramid = FALSE;

GLvoid drawCone(GLvoid);
GLvoid drawPyramid(GLvoid);

GLfloat radius;
RECToldrect;

unsigned char threeto8[8] = {
    0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};

unsigned char twoto8[4] = {
    0, 0x55, 0xaa, 0xff
};

unsigned char oneto8[2] = {
    0, 255
};

static int defaultOverride[13] = {
    0, 3, 24, 27, 64, 67, 88, 173, 181, 236, 247, 164, 91
};

static PALETTEENTRY defaultPalEntry[20] = {
    { 0,   0,   0,    0 },
    { 0x80,0,   0,    0 },
    { 0,   0x80,0,    0 },
    { 0x80,0x80,0,    0 },
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 },

    { 192, 220, 192,  0 },
    { 166, 202, 240,  0 },
    { 255, 251, 240,  0 },
    { 160, 160, 164,  0 },

    { 0x80,0x80,0x80, 0 },
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }
};

unsigned char
ComponentFromIndex(int i, UINT nbits, UINT shift)
{
    unsigned char val;

    val = (unsigned char) (i >> shift);
    switch (nbits) {

    case 1:
        val &= 0x1;
        return oneto8[val];

    case 2:
        val &= 0x3;
        return twoto8[val];

    case 3:
        val &= 0x7;
        return threeto8[val];

    default:
        return 0;
    }
}

void
CreateRGBPalette(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pfd;
    LOGPALETTE *pPal;
    int n, i;

    n = GetPixelFormat(hDC);
    DescribePixelFormat(hDC, n, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if (pfd.dwFlags & PFD_NEED_PALETTE) {
        n = 1 << pfd.cColorBits;
        pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                n * sizeof(PALETTEENTRY));
        pPal->palVersion = 0x300;
        pPal->palNumEntries = n;
        for (i=0; i<n; i++) {
            pPal->palPalEntry[i].peRed =
                    ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
            pPal->palPalEntry[i].peGreen =
                    ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
            pPal->palPalEntry[i].peBlue =
                    ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
            pPal->palPalEntry[i].peFlags = 0;
        }

        /* fix up the palette to include the default GDI palette */
        if ((pfd.cColorBits == 8)                           &&
            (pfd.cRedBits   == 3) && (pfd.cRedShift   == 0) &&
            (pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) &&
            (pfd.cBlueBits  == 2) && (pfd.cBlueShift  == 6)
           ) {
            for (i = 1 ; i <= 12 ; i++)
                pPal->palPalEntry[defaultOverride[i]] = defaultPalEntry[i];
        }

        ghPalette = CreatePalette(pPal);
        LocalFree(pPal);

        ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
        n = RealizePalette(hDC);
    }
}

BOOL bSetupPixelFormat(HDC hDC)
{
    static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),// size of this pfd
1,// version number
PFD_DRAW_TO_WINDOW |// support window
  PFD_SUPPORT_OPENGL |// support OpenGL
  PFD_DOUBLEBUFFER,// double buffered
PFD_TYPE_RGBA,// RGBA type
24,// 24-bit color depth
0, 0, 0, 0, 0, 0,// color bits ignored
0,// no alpha buffer
0,// shift bit ignored
0,// no accumulation buffer
0, 0, 0, 0, // accum bits ignored
32,// 32-bit z-buffer
0,// no stencil buffer
0,// no auxiliary buffer
PFD_MAIN_PLANE,// main layer
0,// reserved
0, 0, 0// layer masks ignored
    };
    int pixelformat;

    if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 )
    {
        MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK);
        return FALSE;
    }

    if (SetPixelFormat(hDC, pixelformat, &pfd) == FALSE)
    {
        MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);
        return FALSE;
    }

    CreateRGBPalette(hDC);

    return TRUE;
}

GLvoid initialize(HWND hWnd)
{
    GLfloatmaxObjectSize, aspect;
    GLdoublenear_plane, far_plane;

    GetClientRect(hWnd, &oldrect);

    glClearColor( 0.0, 0.0, 0.0, 1.0 );
    glClearDepth( 1.0 );

    glEnable(GL_DEPTH_TEST);

    glMatrixMode( GL_PROJECTION );
    aspect = (GLfloat) oldrect.right / oldrect.bottom;;
    gluPerspective( 45.0, aspect, 3.0, 7.0 );
    glMatrixMode( GL_MODELVIEW );

    near_plane = 3.0;
    far_plane = 7.0;
    maxObjectSize = 3.0;
    radius = near_plane + maxObjectSize/2.0;
}

GLvoid draw_scene(HWND hWnd)
{
    HDC hDC;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glPushMatrix();
    glTranslatef(0.0, 0.0, -radius);

if(draw_cone)
    drawCone();

if(draw_pyramid)
    drawPyramid();

    glPopMatrix();
    glFinish();

    hDC = wglGetCurrentDC();
    SwapBuffers(hDC);
}

GLvoid resize(HWND hWnd)
{
    RECTrect;

    GetClientRect(hWnd, &rect);

    glViewport(0, 0, rect.right, rect.bottom);

    if((oldrect.right > rect.right) || (oldrect.bottom > rect.bottom))
PostMessage (hWnd, WM_PAINT, 0, 0L);

    oldrect.right = rect.right;
    oldrect.bottom = rect.bottom;
}

GLvoid drawCone(GLvoid)
{
    GLUquadricObj *quadObj;

    glColor3f(0.0, 1.0, 0.0);

    glPushMatrix();
glTranslatef(-1.0, 0.0, 0.0);
        quadObj = gluNewQuadric();
        gluQuadricDrawStyle(quadObj, GLU_FILL);
        gluQuadricNormals(quadObj, GLU_SMOOTH);
        gluCylinder(quadObj, 1.0, 0.0, 1.0, 20, 10);
        gluDeleteQuadric(quadObj);
    glPopMatrix();

}

GLvoid drawPyramid(GLvoid)
{

    glPushMatrix();
glTranslatef(1.0, 0.0, 0.0);
glBegin(GL_TRIANGLE_FAN);
    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(0.0, 1.0, 0.0);
    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(-1.0, 0.0, 0.0);
    glColor3f(0.0, 0.0, 1.0);
    glVertex3f(0.0, 0.0, 1.0);
    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(1.0, 0.0, 0.0);
glEnd();
    glPopMatrix();
}


README.TXT

Using Polygon Offsets for Efficient Object Translation

The PGONOFFS sample demonstrates the use of the polygon-offset call available
in OpenGL 1.1. Using this call, the depth values of the fragments generated by
the rasterization of the polygon may be shifted away or towards the origin,
as an affine function of the window coordinate depth slope of the polygon.

In this sample, a quadric (cylinder, sphere or disk) is drawn normally and
with a polygon-offset value. A trackball is used to view the object from
various angles, by dragging it with a mouse. Additionally, the following
key inputs are recognized:

 Key        Description
 ---        -----------
 left/right Decrease/increase the units in the glPolygonOffset() call.
 up/down    Increase/decrease the factors in the glPolygonOffset() call.
 a/A        Decrease/increase the number of stacks in the quadric.
 c          Toggle on/off culling.
 f          Cycle through the various quadrics.
 g/G        Decrease/increase the radius of the quadric.
 h/H        Decrease/increase the height of the quadric.
 i          Set initial values for units and factors.
 t          Toggle on/off texturing.
 z/Z        Translate the object in Z.
 1/2/3      Enable GL_POLYGON_OFFSET_FILL/GL_POLYGON_OFFSET_LINE/
            GL_POLYGON_OFFSET_POINT.
 0          Toggle on/off smooth shading.

Note
----

PGONOFFS will not run on an implementation of OpenGL version 1.0. The OpenGL
for Windows 95 redistributables included on the Win32 SDK are an
implementation of OpenGL version 1.0.


GLOS.H

// GLOS.H
//
// This is an OS specific header file

#include <windows.h>

// disable data conversion warnings

#pragma warning(disable : 4305)
#pragma warning(disable : 4244)     // MIPS
#pragma warning(disable : 4136)     // X86
#pragma warning(disable : 4309)     // X86
#pragma warning(disable : 4051)     // ALPHA


TOFFSET.C

/******************************Module*Header*******************************\
* Module Name: toffset.c
*
* Program demonstrating the Polygon Offset call.
*
* Draws a quadric, first normally and then with polygon offset.
*
* Copyright 1994 - 1998 Microsoft Corporation
*
\**************************************************************************/

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993, 1994 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <gl\glaux.h>
#include "trackbal.h"

#define PI 3.141592654
#define BLACK 0
#define GRAY 128
#define WHITE 255
#define RD 0xA40000FF
#define WT 0xFFFFFFFF
#define brickImageWidth 16
#define brickImageHeight 16

//static void CALLBACK ErrorHandler(unsigned long which);
static void Init(void );

static void CALLBACK Key_Left(void );
static void CALLBACK Key_Right(void );
static void CALLBACK Key_Up(void );
static void CALLBACK Key_Down(void );
static void CALLBACK Key_a(void );
static void CALLBACK Key_A(void );
static void CALLBACK Key_b(void );
static void CALLBACK Key_c(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_g(void );
static void CALLBACK Key_G(void );
static void CALLBACK Key_h(void );
static void CALLBACK Key_H(void );
static void CALLBACK Key_i(void );
static void CALLBACK Key_j(void );
static void CALLBACK Key_J(void );
static void CALLBACK Key_l(void );
static void CALLBACK Key_L(void );
static void CALLBACK Key_k(void );
static void CALLBACK Key_K(void );
static void CALLBACK Key_o(void );
static void CALLBACK Key_p(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_S(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_z(void );
static void CALLBACK Key_Z(void );
static void CALLBACK Key_0(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );

static void CALLBACK Reshape(int width,int height);
static void CALLBACK Draw( void );
static unsigned long Args(int argc,char **argv );

GLenum rgb, doubleBuffer;

GLint wWidth = 300, wHeight = 300;

GLenum doDither = GL_TRUE;
GLenum shade = GL_TRUE;
GLenum texture = GL_TRUE;

BOOL bPolyOffset = TRUE;

float factor=-1.0f, units=0.0f;

float inc = 1.0f;
int polygonMode = GL_LINE;
GLenum polyFace = GL_FRONT;
float zTrans = -2.7;
BOOL bCullFace = FALSE;
BOOL bHiddenLine;

GLint radius1, radius2;
GLdouble angle1, angle2;
GLint slices, stacks;
GLint height;
GLint whichQuadric;
GLUquadricObj *quadObj;

GLubyte brickImage[brickImageWidth*brickImageHeight] = {
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD
};
char *texFileName = 0;

static void UpdateInfo()
{
    HWND hwnd = auxGetHWND();
    char buf[100];

    sprintf( buf, "Factor = %4.1f, Units = %4.1f", factor, units );
    SendMessage( hwnd, WM_SETTEXT, 0, (LPARAM)buf );
}

static void SetMaterial( bBlack )
{
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float black_mat_shininess[] = {0.0};
    static float black_mat_specular[] = {0.0, 0.0, 0.0, 0.0};
    static float black_mat_diffuse[] = {0.0, 0.0, 0.0, 0.0};
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float no_ambient[] = {0.0, 0.0, 0.0, 0.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_no_ambient[] = {0.0, 0.0, 0.0, 0.0};

    if( !bBlack ) {
        glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
        glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
        glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
        glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
        glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    } else {
        glMaterialfv(GL_FRONT, GL_SHININESS, black_mat_shininess);
        glMaterialfv(GL_FRONT, GL_SPECULAR, black_mat_specular);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, black_mat_diffuse);
        glMaterialfv(GL_BACK, GL_SHININESS, black_mat_shininess);
        glMaterialfv(GL_BACK, GL_SPECULAR, black_mat_specular);
        glMaterialfv(GL_BACK, GL_DIFFUSE, black_mat_diffuse);
        glLightfv(GL_LIGHT0, GL_AMBIENT, no_ambient);
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_no_ambient);
    }
}

static void Init(void)
{
    static GLint colorIndexes[3] = {0, 200, 255};
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};
    static float decal[] = {GL_DECAL};
    static float modulate[] = {GL_MODULATE};
    static float repeat[] = {GL_REPEAT};
    static float nearest[] = {GL_NEAREST};
    AUX_RGBImageRec *image;

    if (!rgb) {
        auxSetGreyRamp();
    }
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    bHiddenLine = FALSE;
    SetMaterial( bHiddenLine );

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    if (texFileName) {
        image = auxRGBImageLoad(texFileName);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    } else {
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, 4, brickImageWidth, brickImageHeight,
             0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)brickImage);
    }

    quadObj = gluNewQuadric();

    radius1 = 10;
    radius2 = 5;
    angle1 = 90;
    angle2 = 180;
    slices = 16;
    stacks = 10;
    height = 20;

    glCullFace( GL_BACK );

    UpdateInfo();
}

static void SetDistance( void )
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1, 1, -1, 1, 1, 10);
    // This defines how far away we're looking from
    glTranslated( 0, 0, zTrans );
}

static void CALLBACK Reshape(int width, int height)
{
    trackball_Resize( width, height );

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1, 1, -1, 1, 1, 10);
    // This defines how far away we're looking from
    glTranslated( 0, 0, zTrans );
}

static void CALLBACK Key_Left (void)
{
    units -= inc;
}

static void CALLBACK Key_Right (void)
{
    units += inc;
}

static void CALLBACK Key_Up (void)
{
    factor += inc;
}

static void CALLBACK Key_Down (void)
{
    factor -= inc;
}

static void CALLBACK Key_a (void)
{
    if (stacks > 1)
        stacks--;
}

static void CALLBACK Key_A (void)
{
    stacks++;
}

static void CALLBACK Key_b (void)
{
    bHiddenLine = !bHiddenLine;
    SetMaterial( bHiddenLine );
}

static void CALLBACK Key_c (void)
{
    bCullFace = !bCullFace;
    if( bCullFace )
        glEnable( GL_CULL_FACE );
    else
        glDisable( GL_CULL_FACE );
}

static void CALLBACK Key_f (void)
{
whichQuadric = whichQuadric >= 3 ? 0 : whichQuadric + 1;
}

static void CALLBACK Key_g (void)
{
    if (radius1 > 0)
        radius1 -= 1;
}

static void CALLBACK Key_G (void)
{
    radius1 += 1;
}

static void CALLBACK Key_h (void)
{
    if (height > 0)
        height -= 2;
}

static void CALLBACK Key_H (void)
{
    height += 2;
}

static void CALLBACK Key_i (void)
{
    factor = -1.0f;
    units = 0.0f;
}

static void CALLBACK Key_j (void)
{
    if (radius2 > 0)
        radius2 -= 1;
}

static void CALLBACK Key_J (void)
{
    radius2 += 1;
}

static void CALLBACK Key_k (void)
{
angle1 -= 5;
}

static void CALLBACK Key_K (void)
{
angle1 += 5;
}

static void CALLBACK Key_l (void)
{
angle2 -= 5;
}

static void CALLBACK Key_L (void)
{
angle2 += 5;
}

static void CALLBACK Key_o (void)
{
    bPolyOffset = !bPolyOffset;
}

static void CALLBACK Key_p (void)
{
switch (polyFace) {
  case GL_BACK:
    polyFace = GL_FRONT;
    break;
  case GL_FRONT:
    polyFace = GL_FRONT_AND_BACK;
    break;
  case GL_FRONT_AND_BACK:
    polyFace = GL_BACK;
    break;
}
}

static void CALLBACK Key_s (void)
{
    if (slices > 3)
        slices--;
}

static void CALLBACK Key_S (void)
{
    slices++;
}

static void CALLBACK Key_t (void)
{
    texture = !texture;
    if (texture) {
        gluQuadricTexture(quadObj, GL_TRUE);
        glEnable(GL_TEXTURE_2D);
    } else {
        gluQuadricTexture(quadObj, GL_FALSE);
        glDisable(GL_TEXTURE_2D);
    }
}

static void CALLBACK Key_z (void)
{
    zTrans += 0.1f;
    SetDistance();
}

static void CALLBACK Key_Z (void)
{
    zTrans -= 0.1f;
    SetDistance();
}

static void CALLBACK Key_0 (void)
{
    shade = !shade;
    if (shade) {
        glShadeModel(GL_SMOOTH);
        gluQuadricNormals(quadObj, GLU_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
        gluQuadricNormals(quadObj, GLU_FLAT);
    }
}

static void CALLBACK Key_1 (void)
{
    polygonMode = GL_FILL;
}

static void CALLBACK Key_2 (void)
{
    polygonMode = GL_LINE;
}

static void CALLBACK Key_3 (void)
{
    polygonMode = GL_POINT;
}

static void DrawObject( void )
{
    switch (whichQuadric) {
      case 0:
gluCylinder(quadObj, radius1/10.0, radius2/10.0, height/10.0,
    slices, stacks);
break;
      case 1:
gluSphere(quadObj, radius1/10.0, slices, stacks);
break;
      case 2:
gluPartialDisk(quadObj, radius2/10.0, radius1/10.0, slices,
       stacks, angle1, angle2);
break;
      case 3:
gluDisk(quadObj, radius2/10.0, radius1/10.0, slices, stacks);
break;
    }
}

static void CALLBACK Draw( void )
{
    float matRot[4][4];

    UpdateInfo();
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    trackball_CalcRotMatrix( matRot );
    glMultMatrixf( &(matRot[0][0]) );

    if( whichQuadric == 0 ) // cylinder
glTranslatef(0, 0, -height/20.0);

    // Draw object normally

    DrawObject();

    // Draw object again with polygon offset

    // Set polygon mode for offset faces
    glPolygonMode( GL_FRONT_AND_BACK, polygonMode );

    if( bPolyOffset ) {
        switch( polygonMode ) {
            case GL_FILL:
                glEnable( GL_POLYGON_OFFSET_FILL );
                break;
            case GL_LINE:
                glEnable( GL_POLYGON_OFFSET_LINE );
                break;
            case GL_POINT:
                glEnable( GL_POLYGON_OFFSET_POINT );
                break;
        }
    }

    glPolygonOffset( factor, units );
    glColor3f(1.0, 0.0, 0.0);
    glDisable( GL_LIGHTING );
    DrawObject();
    glEnable( GL_LIGHTING );
    // restore modes
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

    if( bPolyOffset ) {
        glDisable( GL_POLYGON_OFFSET_FILL );
        glDisable( GL_POLYGON_OFFSET_LINE );
        glDisable( GL_POLYGON_OFFSET_POINT );
    }

    glFlush();

    if (doubleBuffer) {
auxSwapBuffers();
    }
}

static unsigned long Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_TRUE;

    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-ci") == 0) {
            rgb = GL_FALSE;
        } else if (strcmp(argv[i], "-rgb") == 0) {
            rgb = GL_TRUE;
        } else if (strcmp(argv[i], "-sb") == 0) {
            doubleBuffer = GL_FALSE;
        } else if (strcmp(argv[i], "-db") == 0) {
            doubleBuffer = GL_TRUE;
        } else if (strcmp(argv[i], "-f") == 0) {
            if (i+1 >= argc || argv[i+1][0] == '-') {
                //printf("-f (No file name).\n");
                return GL_FALSE;
            } else {
                texFileName = argv[++i];
            }
        } else {
            //printf("%s (Bad option).\n", argv[i]);
            return GL_FALSE;
        }
    }
    return GL_TRUE;
}

void  _CRTAPI1 main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
       auxQuit();
    }

    auxInitPosition(0, 0, wWidth, wHeight);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Polygon Offset Demo") == GL_FALSE) {
        auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);

    auxKeyFunc( AUX_LEFT, Key_Left );
    auxKeyFunc( AUX_RIGHT, Key_Right );
    auxKeyFunc( AUX_UP, Key_Up );
    auxKeyFunc( AUX_DOWN, Key_Down );
    auxKeyFunc( AUX_a, Key_a );
    auxKeyFunc( AUX_A, Key_A );
    auxKeyFunc( AUX_b, Key_b );
    auxKeyFunc( AUX_c, Key_c );
    auxKeyFunc( AUX_f, Key_f );
    auxKeyFunc( AUX_g, Key_g );
    auxKeyFunc( AUX_G, Key_G );
    auxKeyFunc( AUX_h, Key_h );
    auxKeyFunc( AUX_H, Key_H );
    auxKeyFunc( AUX_i, Key_i );
    auxKeyFunc( AUX_j, Key_j );
    auxKeyFunc( AUX_J, Key_J );
    auxKeyFunc( AUX_k, Key_k );
    auxKeyFunc( AUX_K, Key_K );
    auxKeyFunc( AUX_l, Key_l );
    auxKeyFunc( AUX_L, Key_L );
    auxKeyFunc( AUX_o, Key_o );
    auxKeyFunc( AUX_p, Key_p );
    auxKeyFunc( AUX_s, Key_s );
    auxKeyFunc( AUX_S, Key_S );
    auxKeyFunc( AUX_t, Key_t );
    auxKeyFunc( AUX_z, Key_z );
    auxKeyFunc( AUX_Z, Key_Z );
    auxKeyFunc( AUX_0, Key_0 );
    auxKeyFunc( AUX_1, Key_1 );
    auxKeyFunc( AUX_2, Key_2 );
    auxKeyFunc( AUX_3, Key_3 );

    auxMouseFunc( AUX_LEFTBUTTON, AUX_MOUSEDOWN, trackball_MouseDown );
    auxMouseFunc( AUX_LEFTBUTTON, AUX_MOUSEUP, trackball_MouseUp );

    trackball_Init( wWidth, wHeight );

    auxIdleFunc( Draw );
    auxMainLoop( Draw );
}


TRACKBAL.C

/*
 * Trackball code:
 *
 * Implementation of a virtual trackball.
 * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
 *   the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
 *
 * Vector manip code:
 *
 * Original code from:
 * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
 *
 * Much mucking with by:
 * Gavin Bell
 *
 * Shell hacking courtesy of:
 * Reptilian Inhaleware
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include "trackbal.h"

/*
 * globals
 */
static GLenum (*MouseDownFunc)(int, int, GLenum) = NULL;
static GLenum (*MouseUpFunc)(int, int, GLenum)   = NULL;
static HWND ghwnd;
GLint giWidth, giHeight;
LONG    glMouseDownX, glMouseDownY;
BOOL    gbLeftMouse = FALSE;
BOOL    gbSpinning = FALSE;
float   curquat[4], lastquat[4];

/*
 * This size should really be based on the distance from the center of
 * rotation to the point on the object underneath the mouse.  That
 * point would then track the mouse as closely as possible.  This is a
 * simple example, though, so that is left as an Exercise for the
 * Programmer.
 */
#define TRACKBALLSIZE  (0.8f)

/*
 * Local function prototypes (not defined in trackball.h)
 */
static float tb_project_to_sphere(float, float, float);
static void normalize_quat(float [4]);

void
trackball_Init( GLint width, GLint height )
{
    ghwnd = auxGetHWND();
    giWidth = width;
    giHeight = height;

    trackball_calc_quat( curquat, 0.0f, 0.0f, 0.0f, 0.0f );
}

void
trackball_Resize( GLint width, GLint height )
{
    giWidth = width;
    giHeight = height;
}

GLenum
#ifdef _USE_TK
trackball_MouseDown( int mouseX, int mouseY, GLenum button )
#else
trackball_MouseDown( AUX_EVENTREC *event)
#endif
{
    SetCapture(ghwnd);
    glMouseDownX = event->data[AUX_MOUSEX];
    glMouseDownY = event->data[AUX_MOUSEY];
    gbLeftMouse = TRUE;
    return GL_TRUE;
}

GLenum
#ifdef _USE_TK
trackball_MouseUp( int mouseX, int mouseY, GLenum button )
#else
trackball_MouseUp( AUX_EVENTREC *event )
#endif
{
    ReleaseCapture();
    gbLeftMouse = FALSE;
    return GL_TRUE;
}

/* these 4 not used yet */
void
trackball_MouseDownEvent( int mouseX, int mouseY, GLenum button )
{
}

void
trackball_MouseUpEvent( int mouseX, int mouseY, GLenum button )
{
}

void
trackball_MouseDownFunc(GLenum (*Func)(int, int, GLenum))
{
    MouseDownFunc = Func;
}

void
trackball_MouseUpFunc(GLenum (*Func)(int, int, GLenum))
{
    MouseUpFunc = Func;
}

void
trackball_CalcRotMatrix( GLfloat matRot[4][4] )
{
    POINT pt;

    if (gbLeftMouse)
    {
        auxGetMouseLoc( &pt.x, &pt.y );

        // If mouse has moved since button was pressed, change quaternion.

            if (pt.x != glMouseDownX || pt.y != glMouseDownY)
            {
#if 1
    /* negate all params for proper operation with glTranslate(-z)
     */
                trackball_calc_quat(lastquat,
                          -(2.0f * ( giWidth - glMouseDownX ) / giWidth - 1.0f),
                          -(2.0f * glMouseDownY / giHeight - 1.0f),
                          -(2.0f * ( giWidth - pt.x ) / giWidth - 1.0f),
                          -(2.0f * pt.y / giHeight - 1.0f)
                         );
#else
// now out-of-date
                trackball_calc_quat(lastquat,
                          2.0f * ( Width - glMouseDownX ) / Width - 1.0f,
                          2.0f * glMouseDownY / Height - 1.0f,
                          2.0f * ( Width - pt.x ) / Width - 1.0f,
                          2.0f * pt.y / Height - 1.0f );
#endif

                gbSpinning = TRUE;
            }
            else
                gbSpinning = FALSE;

            glMouseDownX = pt.x;
            glMouseDownY = pt.y;
    }

    if (gbSpinning)
        trackball_add_quats(lastquat, curquat, curquat);

    trackball_build_rotmatrix(matRot, curquat);
}

void
vzero(float *v)
{
    v[0] = 0.0f;
    v[1] = 0.0f;
    v[2] = 0.0f;
}

void
vset(float *v, float x, float y, float z)
{
    v[0] = x;
    v[1] = y;
    v[2] = z;
}

void
vsub(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] - src2[0];
    dst[1] = src1[1] - src2[1];
    dst[2] = src1[2] - src2[2];
}

void
vcopy(const float *v1, float *v2)
{
    register int i;
    for (i = 0 ; i < 3 ; i++)
        v2[i] = v1[i];
}

void
vcross(const float *v1, const float *v2, float *cross)
{
    float temp[3];

    temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
    temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
    temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
    vcopy(temp, cross);
}

float
vlength(const float *v)
{
    return (float) sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}

void
vscale(float *v, float div)
{
    v[0] *= div;
    v[1] *= div;
    v[2] *= div;
}

void
vnormal(float *v)
{
    vscale(v,1.0f/vlength(v));
}

float
vdot(const float *v1, const float *v2)
{
    return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}

void
vadd(const float *src1, const float *src2, float *dst)
{
    dst[0] = src1[0] + src2[0];
    dst[1] = src1[1] + src2[1];
    dst[2] = src1[2] + src2[2];
}

/*
 * Ok, simulate a track-ball.  Project the points onto the virtual
 * trackball, then figure out the axis of rotation, which is the cross
 * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
 * Note:  This is a deformed trackball-- is a trackball in the center,
 * but is deformed into a hyperbolic sheet of rotation away from the
 * center.  This particular function was chosen after trying out
 * several variations.
 *
 * It is assumed that the arguments to this routine are in the range
 * (-1.0 ... 1.0)
 */
void
trackball_calc_quat(float q[4], float p1x, float p1y, float p2x, float p2y)
{
    float a[3]; /* Axis of rotation */
    float phi;  /* how much to rotate about axis */
    float p1[3], p2[3], d[3];
    float t;

    if (p1x == p2x && p1y == p2y) {
/* Zero rotation */
        vzero(q);
q[3] = 1.0f;
        return;
    }

    /*
     * First, figure out z-coordinates for projection of P1 and P2 to
     * deformed sphere
     */
    vset(p1,p1x,p1y,tb_project_to_sphere(TRACKBALLSIZE,p1x,p1y));
    vset(p2,p2x,p2y,tb_project_to_sphere(TRACKBALLSIZE,p2x,p2y));

    /*
     *  Now, we want the cross product of P1 and P2
     */
    vcross(p2,p1,a);

    /*
     *  Figure out how much to rotate around that axis.
     */
    vsub(p1,p2,d);
    t = vlength(d) / (2.0f*TRACKBALLSIZE);

    /*
     * Avoid problems with out-of-control values...
     */
    if (t > 1.0f) t = 1.0f;
    if (t < -1.0f) t = -1.0f;
    phi = 2.0f * (float) asin(t);

    trackball_axis_to_quat(a,phi,q);
}

/*
 *  Given an axis and angle, compute quaternion.
 */
void
trackball_axis_to_quat(float a[3], float phi, float q[4])
{
    vnormal(a);
    vcopy(a,q);
    vscale(q,(float) sin(phi/2.0f));
    q[3] = (float) cos(phi/2.0f);
}

/*
 * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
 * if we are away from the center of the sphere.
 */
static float
tb_project_to_sphere(float r, float x, float y)
{
    float d, t, z;

    d = (float) sqrt(x*x + y*y);
    if (d < r * 0.70710678118654752440f) {    /* Inside sphere */
z = (float) sqrt(r*r - d*d);
    } else {           /* On hyperbola */
        t = r / 1.41421356237309504880f;
        z = t*t / d;
    }
    return z;
}

/*
 * Given two rotations, e1 and e2, expressed as quaternion rotations,
 * figure out the equivalent single rotation and stuff it into dest.
 *
 * This routine also normalizes the result every RENORMCOUNT times it is
 * called, to keep error from creeping in.
 *
 * NOTE: This routine is written so that q1 or q2 may be the same
 * as dest (or each other).
 */

#define RENORMCOUNT 97

void
trackball_add_quats(float q1[4], float q2[4], float dest[4])
{
    static int count=0;
    float t1[4], t2[4], t3[4];
    float tf[4];

    vcopy(q1,t1);
    vscale(t1,q2[3]);

    vcopy(q2,t2);
    vscale(t2,q1[3]);

    vcross(q2,q1,t3);
    vadd(t1,t2,tf);
    vadd(t3,tf,tf);
    tf[3] = q1[3] * q2[3] - vdot(q1,q2);

    dest[0] = tf[0];
    dest[1] = tf[1];
    dest[2] = tf[2];
    dest[3] = tf[3];

    if (++count > RENORMCOUNT) {
        count = 0;
        normalize_quat(dest);
    }
}

/*
 * Quaternions always obey:  a^2 + b^2 + c^2 + d^2 = 1.0
 * If they don't add up to 1.0, dividing by their magnitued will
 * renormalize them.
 *
 * Note: See the following for more information on quaternions:
 *
 * - Shoemake, K., Animating rotation with quaternion curves, Computer
 *   Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
 * - Pletinckx, D., Quaternion calculus as a basic tool in computer
 *   graphics, The Visual Computer 5, 2-13, 1989.
 */
static void
normalize_quat(float q[4])
{
    int i;
    float mag;

    mag = (q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
    for (i = 0; i < 4; i++) q[i] /= mag;
}

/*
 * Build a rotation matrix, given a quaternion rotation.
 *
 */
void
trackball_build_rotmatrix(float m[4][4], float q[4])
{
    m[0][0] = 1.0f - 2.0f * (q[1] * q[1] + q[2] * q[2]);
    m[0][1] = 2.0f * (q[0] * q[1] - q[2] * q[3]);
    m[0][2] = 2.0f * (q[2] * q[0] + q[1] * q[3]);
    m[0][3] = 0.0f;

    m[1][0] = 2.0f * (q[0] * q[1] + q[2] * q[3]);
    m[1][1]= 1.0f - 2.0f * (q[2] * q[2] + q[0] * q[0]);
    m[1][2] = 2.0f * (q[1] * q[2] - q[0] * q[3]);
    m[1][3] = 0.0f;

    m[2][0] = 2.0f * (q[2] * q[0] - q[1] * q[3]);
    m[2][1] = 2.0f * (q[1] * q[2] + q[0] * q[3]);
    m[2][2] = 1.0f - 2.0f * (q[1] * q[1] + q[0] * q[0]);
    m[2][3] = 0.0f;

    m[3][0] = 0.0f;
    m[3][1] = 0.0f;
    m[3][2] = 0.0f;
    m[3][3] = 1.0f;
}


TRACKBAL.H

/*
 * trackball.h
 * A virtual trackball implementation
 * Written by Gavin Bell for Silicon Graphics, November 1988.
 */

/*
 * Initialize trackball in win32 environment
 */
extern void
trackball_Init( GLint width, GLint height );

extern void
trackball_Resize( GLint width, GLint height );

extern GLenum
trackball_MouseDown( AUX_EVENTREC *event );

extern GLenum
trackball_MouseUp( AUX_EVENTREC *event );

/* These next Mouse fns are required if both the trackbal and user
 * need mouse events.  Otherwise, can just supply above two functions
 * to tk to call
 */

/*
 * Mouse functions called directly on events
 */
extern void
trackball_MouseDownEvent( int mouseX, int mouseY, GLenum button );

extern void
trackball_MouseUpEvent( int mouseX, int mouseY, GLenum button );

/*
 * Functions to register mouse event callbacks
 */
extern void
trackball_MouseDownFunc(GLenum (*)(int, int, GLenum));

extern void
trackball_MouseUpFunc(GLenum (*)(int, int, GLenum));

/*
 * Calculate rotation matrix based on mouse movement
 */
void
trackball_CalcRotMatrix( GLfloat matRot[4][4] );

/*
 * Pass the x and y coordinates of the last and current positions of
 * the mouse, scaled so they are from (-1.0 ... 1.0).
 *
 * if ox,oy is the window's center and sizex,sizey is its size, then
 * the proper transformation from screen coordinates (sc) to world
 * coordinates (wc) is:
 * wcx = (2.0 * (scx-ox)) / (float)sizex - 1.0
 * wcy = (2.0 * (scy-oy)) / (float)sizey - 1.0
 *
 * The resulting rotation is returned as a quaternion rotation in the
 * first paramater.
 */
void
trackball_calc_quat(float q[4], float p1x, float p1y, float p2x, float p2y);

/*
 * Given two quaternions, add them together to get a third quaternion.
 * Adding quaternions to get a compound rotation is analagous to adding
 * translations to get a compound translation.  When incrementally
 * adding rotations, the first argument here should be the new
 * rotation, the second and third the total rotation (which will be
 * over-written with the resulting new total rotation).
 */
void
trackball_add_quats(float *q1, float *q2, float *dest);

/*
 * A useful function, builds a rotation matrix in Matrix based on
 * given quaternion.
 */
void
trackball_build_rotmatrix(float m[4][4], float q[4]);

/*
 * This function computes a quaternion based on an axis (defined by
 * the given vector) and an angle about which to rotate.  The angle is
 * expressed in radians.  The result is put into the third argument.
 */
void
trackball_axis_to_quat(float a[3], float phi, float q[4]);


ATMOSPHE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#define ATMOSPHERE_EXTERN
#include "atmosphe.h"

const Weather clear("Clear", 0.);
const Weather foggy("Foggy", .04, .5, white, Color(.6, .6, 1), white);
const Weather very_foggy("Very Foggy", .25, .5, white, white, white);
const Weather rainy("Rainy", .01, 1., black, Color(.35, .35, .35), black);

Weather weathers[nweathers] = {clear, foggy, very_foggy, rainy};

const float root3_2 = sqrt(3.) / 2.;

inline float clamp(float a, float min = 0, float max = 1)
{
  if (a < min) return min;
  else if (a > max) return max;
  else return a;
}

Weather::Weather()
{
  strcpy(name, "No name");
  fog_density = 0;
  fog_color = white;
  fog_spread = 1;
  sun_brightness = 1;
  light_sun = GL_LIGHT0;
  light_ambient = GL_LIGHT1;
  sky_top = blue;
  sky_bottom = white;
}

Weather::Weather(const char *n, GLfloat fd, GLfloat fs, Color fc,
 Color s1, Color s2, GLfloat sb,
 GLenum ls, GLenum la)
{
  strcpy(name, n);
  fog_density = fd;
  fog_color = fc;
  fog_spread = fs;
  sun_brightness = sb;
  light_sun = ls;
  light_ambient = la;
  sky_top = s1;
  sky_bottom = s2;
}

Weather::~Weather()
{
}

Weather Weather::operator=(Weather a)
{
  strcpy(name, a.name);

  fog_density = a.fog_density;
  fog_color = a.fog_color;
  fog_spread = a.fog_spread;

  sun_brightness = a.sun_brightness;

  light_sun = a.light_sun;
  light_ambient = a.light_ambient;

  sky_top = a.sky_top;
  sky_bottom = a.sky_bottom;

  return *this;
}

void Weather::apply(Point sun)
{
  Color c;

  if (fog_density != 0) {
    glFogf(GL_FOG_DENSITY, fog_density);
    glFogfv(GL_FOG_COLOR, fog_color.c);
    glFogi(GL_FOG_MODE, GL_EXP2);
  }
  glLightfv(light_sun, GL_AMBIENT, black.c);
  c = white;
  c *= sun_brightness;
  glLightfv(light_sun, GL_DIFFUSE, c.c);
  glLightfv(light_sun, GL_SPECULAR, white.c);
  if (sun.pt[2] >= 0.0) glEnable(light_sun);
  else glDisable(light_sun);

  c = white;
  c *= .25;
  glLightfv(light_ambient, GL_AMBIENT, c.c);
  glLightfv(light_ambient, GL_DIFFUSE, black.c);
  glLightfv(light_ambient, GL_SPECULAR, black.c);
  glEnable(light_ambient);
}

void Weather::draw_sky(Point sun)
{
  Point p;
  float c;
  Color bottom, top;

  if (sun.pt[2] >= .5) c = 1.;
  else if (sun.pt[2] >= -.5)
    c = sqrt(1. - sun.pt[2]*sun.pt[2])*.5 + sun.pt[2]*root3_2;
  else c = 0;

  bottom = sky_bottom * c;
  top = sky_top * c;

  /* This is drawn as a far-away quad */
  glBegin(GL_QUADS);
  glColor3fv(bottom.c);
  glVertex3f(-1, -1, -1);
  glVertex3f(1, -1, -1);
  glColor3fv(top.c);
  glVertex3f(1, 1, -1);
  glVertex3f(-1, 1, -1);
  glEnd();
}

GLfloat Weather::shadow_blur()
{
  return clamp(fog_density * 10.);
}


ATMOSPHE.H

#ifndef ATMOSPHERE_H
#define ATMOSPHERE_H

#ifndef ATMOSPHERE_EXTERN
#define ATMOSPHERE_EXTERN extern
#endif

#include "Color.h"
#include "Point.h"

const int weather_max_name = 64;
class Weather {
 public:
  Weather();
  Weather(const char *n, GLfloat fd, GLfloat fs = 1, Color fc = white,
  Color s1 = blue, Color s2 = white,
  GLfloat sb = 1,
  GLenum ls = GL_LIGHT0, GLenum la = GL_LIGHT1);
  ~Weather();

  Weather operator=(Weather a);

  /* The sun position is used to determine how light / dark it is */
  void apply(Point sun);
  void draw_sky(Point sun);

  /* This is how much to blur shadows due to haze or fog */
  GLfloat shadow_blur();

  char name[weather_max_name];

  GLfloat fog_density;
  Color fog_color;
  /* Fog spread relates the height to the density of the fog -- 1.0
   * leads to uniform fog */
  GLfloat fog_spread;

  Color sky_top;
  Color sky_bottom;

  GLenum light_sun, light_ambient;
  GLfloat sun_brightness;

 private:
};
const int nweathers = 4;
extern Weather weathers[nweathers];
const int def_weather_index = 0;

#undef ATMOSPHERE_EXTERN

#endif


CALLBACK.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#ifdef X11
#include <X11/Intrinsic.h>
#include <X11/keysym.h>
#include <Xm/Xm.h>

#include <GL/glx.h>
#include <GLwMDrawA.h>
#endif

#include <GL/glu.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef X11
#include <sys/time.h>
#else
#include <time.h>
#endif

#ifdef WIN32
#include "stonehen.h"
#endif

#include "atmosphe.h"
#include "scene.h"

#include "callback.h"

int cb_demo_mode = 0;
float demo_time;

#ifdef X11
extern Widget glw;
extern XtAppContext app_context;
GLXContext glx_context;
#endif

static int needs_wp = 0;
#ifdef X11
static XtWorkProcId workproc = NULL;
#else
static int workproc = 0;
#endif

static int winx, winy;

static int button_down = 0;
int mousex, mousey;
/* What's moving */
GLint target;

/* Location of the telescope */
GLfloat tx, ty;

/* Movements of the camera position to be applied at the next redraw */
float rot_pendx = 0, rot_pendz = 0, trans_pend = 0;

/* How fast the camera is moving */
float trans_speed = 0;

/* This is how fast demo time moves relative to real time */
GLfloat time_scale = 0;

TimeDate last_update;

float last_time = 0;
const float time_fudge = 1000;
inline unsigned long current_time()
{
#ifdef X11
  struct timeval time;
  gettimeofday(&time, NULL);
  return (time.tv_sec * 1000000 + time.tv_usec);
#else
  return (GetTickCount() * 1000);
#endif
}

inline float clamp(float x, float min, float max)
{
  if (x < min) return min;
  else if (x > max) return max;
  else return x;
}

static void add_workproc(Widget w)
{
  needs_wp++;
#ifdef X11
  if (workproc == NULL)
    workproc = XtAppAddWorkProc(app_context, drawWP, NULL);
#endif
}

static void remove_workproc(Widget w)
{
  needs_wp--;
  if (needs_wp == 0) {
#ifdef X11
    XtRemoveWorkProc(workproc);
    workproc = NULL;
#else
    workproc = 0;
#endif
  } else if (needs_wp < 0) {
    //fprintf(stderr, "Internal Error:  No workproc to remove!\n");
    needs_wp = 0;
    workproc = NULL;
  }
}

static void reset_viewer()
{
  scene_viewer_center();
}

#ifdef X11
void intToggleCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  int *data;
  XmToggleButtonCallbackStruct *ptr;
  ptr = (XmToggleButtonCallbackStruct *)call_data;
  data = (int *)client_data;
  *data = ptr->set;
  // This redraw may or may not be needed - do it to be safe
  drawWP(NULL);
}

void initCB(Widget w)
{
  Arg args[1];
  XVisualInfo *vi;

  glw = w;

  XtSetArg(args[0], GLwNvisualInfo, &vi);
  XtGetValues(w, args, 1);

  glx_context = glXCreateContext(XtDisplay(w), vi, 0, GL_FALSE);
  GLwDrawingAreaMakeCurrent(w, glx_context);

  scene_init();

  last_update.read_time();

  resetViewerCB(NULL, NULL, NULL);
}

void exposeCB(Widget w)
{
  drawWP(NULL);
}
#endif

void resizeCB(Widget w, XtPointer client_data, XtPointer call)
{
#ifdef X11
  GLwDrawingAreaCallbackStruct *call_data;

  GLwDrawingAreaMakeCurrent(w, glx_context);

  call_data = (GLwDrawingAreaCallbackStruct *)call;

  winx = call_data->width;
  winy = call_data->height;
#else
  RECT rect;
  GetClientRect(w, &rect);

  winx = WINDSIZEX(rect);
  winy = WINDSIZEY(rect);
#endif

  glViewport(0, 0, winx, winy);

  aspect = (GLfloat)winx / (GLfloat)winy;
}

void inputCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  GLwDrawingAreaCallbackStruct *call;

  int bufsize = 5;
#ifdef X11
  char buffer[5];
  KeySym key;
  XComposeStatus compose;
#endif

  float dx, dy, r1, r2;

#ifdef X11
  GLwDrawingAreaMakeCurrent(w, glx_context);
#endif

  call = (GLwDrawingAreaCallbackStruct *)call_data;

  switch(call->event->type) {
  case ButtonPress:
    last_time = current_time();
    button_down = call->event->xbutton.button;
    mousex = call->event->xbutton.x;
    mousey = call->event->xbutton.y;
    /* Determine if the target should be the camera position
     * or the telescope */
    if (use_telescope) {
      scene_get_position_telescope(&tx, &ty);
      scene_get_radius_telescope(&r1);
      dx = (tx + .5) - ((GLfloat)(winx - mousex)/(GLfloat)winx);
      dy = (ty + .5) - ((GLfloat)(winy - mousey)/(GLfloat)winy);
      r2 = sqrt(dx*dx + dy*dy);
      if (r2 < r1) target = name_telescope;
      else target = name_background;
    } else target = name_background;
    add_workproc(w);
    break;
  case ButtonRelease:
#ifdef X11
    if (call->event->xbutton.button == Button3) {
      /* Use Button3 to stop */
      if (trans_speed) remove_workproc(w);
      trans_speed = 0;
    }
#endif
    remove_workproc(w);
    button_down = 0;
    break;
  case MotionNotify:
    switch(button_down) {
    case Button1:
      /* Use Button1 to control the way in which the viewer is looking
       * or to move the telescope around */
      if (target == name_background) {
dx = (float)(call->event->xmotion.x - mousex) / (float)winx;
dy = (float)(call->event->xmotion.y - mousey) / (float)winy;
rot_pendx -= dy * fov;
rot_pendz -= dx * fov;
      } else {
dx = (float)(mousex - call->event->xmotion.x) / (float)winx;
dy = (float)(mousey - call->event->xmotion.y) / (float)winy;
tx += dx;
ty += dy;
tx = clamp(tx, -.5, .5);
ty = clamp(ty, -.5, .5);
scene_position_telescope(tx, ty);
      }
      break;
    case Button2:
      /* Use Button2 to change speed */
      dx = (float)(mousex - call->event->xmotion.x) /
(float)winx;
      if (dx && !trans_speed) add_workproc(w);
#ifdef WIN32
      else if (!dx && trans_speed) remove_workproc(w);
#endif
      trans_speed += dx;
      break;
    }
    mousex = call->event->xmotion.x;
    mousey = call->event->xmotion.y;
    break;

#ifdef X11 // We can handle our own keys...
  case KeyPress:
    XLookupString(&(call->event->xkey), buffer, bufsize, &key, &compose);
    if (key == XK_Escape) exit(0);
    break;
#endif

  default:
    break;
  }
}

const float speed_t = .5;
const float speed_r = 15.;
const float speed_rx = 10.;
void demo_mode_update(float dt)
{
  float t;

  if (!cb_demo_mode) return;

  dt /= 1000000;
  demo_time += dt;

  t = demo_time;

  if (t < 10.5) {
    trans_speed = speed_t;
    return;
  } else t -= 10.5;

  if (t < 1) {
    trans_speed = 0;
    rot_pendz = 0;
    return;
  } else t -= 1;

  if (t < 3.) {
    trans_speed = 0.;
    rot_pendz = dt * speed_r;
    return;
  } else t -= 3.;

  if (t < 2.) {
    rot_pendx = -dt * speed_rx;
    return;
  } else t -= 2.;

  if (t < 2.) {
    return;
  } else t -= 2.;

  if (t < 2.) {
    rot_pendx = dt * speed_rx;
    return;
  } else t -= 2.;

  if (t < 3.) {
    trans_speed = 0.;
    rot_pendz = dt * speed_r;
    return;
  } else t -= 3.;

  if (t < 30.) {
    trans_speed  = speed_t;
    rot_pendz = 0;
    return;
  } else t -= 30.;

  if (t < 1.) {
    trans_speed = 0;
    return;
  } else t -= 1.;

  if (t < 1.3) {
    rot_pendz = -dt * speed_r;
    return;
  } else t -= 1.3;

  // Pan back to see entire thing
  if (t < 23) {
    trans_speed = speed_t;
    return;
  } else t -= 23;

  // Hold before starting over
  if (t < 20) {
    trans_speed = 0;
    return;
  }
  else t -= 20;

  demo_time = 0.;
  reset_viewer();
}

Boolean drawWP(Widget w)
{
  /* Right now, there's two completely independent time measurements:
   * one for the time of day in the demo and one for changing the camera
   * position */
  TimeDate t, dt;
  float elapsed_time, time;
#ifdef WIN32
  HDC hDC;
#endif

  if (time_scale != 0.0) {
    t.read_time();
    dt = (t - last_update) * time_scale;
    scene_inc_time(dt);
    last_update = t;
  }

  time = current_time();
  if (time - last_time > time_fudge) {
    elapsed_time = time - last_time;
    demo_mode_update(elapsed_time);
    trans_pend = trans_speed * (elapsed_time / 1000000);
    last_time = time;
  }

  scene_viewer_rotatex(rot_pendx);
  scene_viewer_rotatez(rot_pendz);
  scene_viewer_translate(trans_pend);
  rot_pendx = 0;
  rot_pendz = 0;
  trans_pend = 0;

#ifdef X11
  GLwDrawingAreaMakeCurrent(glw, glx_context);
  scene_render();
  /* This is a total hack */
//  if (!use_antialias)
GLwDrawingAreaSwapBuffers(glw);
#else
  hDC = GetDC(w);
  scene_render();
  SwapBuffers(hDC);
  ReleaseDC(w, hDC);
#endif

  return FALSE;
}

#ifdef X11
void weatherCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  Weather *data;
  XmToggleButtonCallbackStruct *ptr;
  ptr = (XmToggleButtonCallbackStruct *)call_data;
  if (ptr->set) scene_set_weather(*((Weather *)client_data));
  drawWP(NULL);
}
#endif

void checkTimeMenuItem(HMENU hmenu, UINT idCheckItem)
{
    CheckMenuItem(hmenu, IDM_CURRENTTIME, MF_UNCHECKED);
    CheckMenuItem(hmenu, IDM_10AM, MF_UNCHECKED);
    CheckMenuItem(hmenu, IDM_NOON, MF_UNCHECKED);
    CheckMenuItem(hmenu, IDM_4PM, MF_UNCHECKED);
    CheckMenuItem(hmenu, idCheckItem, MF_CHECKED);
}

void currentTimeCB(Widget w)
{
  scene_set_time(TimeDate().read_time());
  drawWP(w);
}

void time10amCB(Widget w)
{
  scene_set_time(TimeDate(10, 0));
  drawWP(w);
}

void time12pmCB(Widget w)
{
  scene_set_time(TimeDate(12, 0));
  drawWP(w);
}

void time4pmCB(Widget w)
{
  scene_set_time(TimeDate(16, 0));
  drawWP(w);
}

void timeSpeedCB(Widget w, XtPointer client_data, XtPointer call_data)
{

#ifdef X11
  if (!((XmToggleButtonCallbackStruct *)call_data)->set) return;
  time_scale = (int)client_data;
#endif
  if (time_scale == 0.0) remove_workproc(w);
  else add_workproc(w);
  last_update.read_time();
}

void demo_modeCB(Widget w, XtPointer client_data, XtPointer call_data)
{
#ifdef X11
  int val  = ((XmToggleButtonCallbackStruct *)call_data)->set;
#else
  int val = cb_demo_mode;
#endif
  if (!val) {
    remove_workproc(w);
    resetViewerCB(w, NULL, NULL);
    last_time = current_time();
  } else {
    reset_viewer();
    add_workproc(w);
    trans_speed = 0;
    demo_time = 0;
    rot_pendx = -5;
  }
  drawWP(w);
}

void resetViewerCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  trans_speed = 0;
  rot_pendx = rot_pendz = trans_pend = 0;
  rot_pendx = -5;
  reset_viewer();
  return;
}

void exitCB(Widget w, XtPointer client_data, XtPointer call_data)
{
  exit(0);
}


CALLBACK.H

#ifndef CALLBACKS_H
#define CALLBACKS_H

extern int cb_demo_mode;

void intToggleCB(Widget w, XtPointer client_data, XtPointer call_data);

void initCB(Widget w);
void exposeCB(Widget w);
void resizeCB(Widget w, XtPointer client_data, XtPointer call);
void inputCB(Widget w, XtPointer client_data, XtPointer call_data);

#ifdef X11
Boolean drawWP(XtPointer data);
#else
BOOL drawWP(HWND hWnd);
#endif

void weatherCB(Widget w, XtPointer client_data, XtPointer call_data);

void checkTimeMenuItem(HMENU hmenu, UINT idCheckItem);

void currentTimeCB(Widget w);
void time10amCB(Widget w);
void time12pmCB(Widget w);
void time4pmCB(Widget w);

/* This sets how fast time in the demo moves as opposed to
 * real time -- client_data is secretly an int */
void timeSpeedCB(Widget w, XtPointer client_data, XtPointer call_data);

void demo_modeCB(Widget w, XtPointer client_data, XtPointer call_data);
void resetViewerCB(Widget w, XtPointer client_data, XtPointer call_data);

void exitCB(Widget w, XtPointer client_data, XtPointer call_data);

#endif


COLOR.H

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#ifndef COLOR_H
#define COLOR_H

class Color {
 public:
  inline Color() {};
  inline Color(GLfloat r, GLfloat g, GLfloat b, GLfloat a = 1.0);

  inline Color operator=(GLfloat *a);
  inline Color operator=(GLfloat a);
  inline Color operator+(Color a);
  inline Color operator+=(Color a);
  inline Color operator*(Color a);
  inline Color operator*(GLfloat a);
  inline Color operator*=(Color a);
  inline Color operator*=(GLfloat *a);
  inline Color operator*=(GLfloat a);

  inline GLfloat& operator[](int index);

  inline Color clamp();

  inline void glcolor();

  inline void print();
  inline void print(const char *format);

  GLfloat c[4];
};

const Color white(1., 1., 1., 1.), black(0., 0., 0., 1.);
const Color red(1, 0, 0), green(0, 1, 0), blue(0, 0, 1);

inline Color::Color(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
  c[0] = r;
  c[1] = g;
  c[2] = b;
  c[3] = a;
}

inline Color Color::operator=(GLfloat a)
{
  c[0] = c[1] = c[2] = c[3] = a;
  return *this;
}

inline Color Color::operator=(GLfloat *a)
{
  c[0] = a[0];
  c[1] = a[1];
  c[2] = a[2];
  c[3] = a[3];
  return *this;
}

inline Color Color::operator+(Color a)
{
  Color val;
  val.c[0] = c[0] + a.c[0];
  val.c[1] = c[1] + a.c[1];
  val.c[2] = c[2] + a.c[2];
  val.c[3] = c[3] + a.c[3];
  return val;
}

inline Color Color::operator+=(Color a)
{
  c[0] += a.c[0];
  c[1] += a.c[1];
  c[2] += a.c[2];
  c[3] += a.c[3];
  return *this;
}

inline Color Color::operator*(Color a)
{
  Color val;
  val.c[0] = c[0] * a.c[0];
  val.c[1] = c[1] * a.c[1];
  val.c[2] = c[2] * a.c[2];
  val.c[3] = c[3] * a.c[3];
  return val;
}

inline Color Color::operator*(GLfloat a)
{
  Color val;
  val.c[0] = c[0] * a;
  val.c[1] = c[1] * a;
  val.c[2] = c[2] * a;
  val.c[3] = c[3] * a;
  return val;
}

inline Color Color::operator*=(Color a)
{
  c[0] *= a.c[0];
  c[1] *= a.c[1];
  c[2] *= a.c[2];
  return *this;
}

inline Color Color::operator*=(GLfloat *a)
{
  c[0] *= a[0];
  c[1] *= a[1];
  c[2] *= a[2];
  return *this;
}

inline Color Color::operator*=(GLfloat a)
{
  c[0] *= a;
  c[1] *= a;
  c[2] *= a;
  c[3] *= a;

  return *this;
}

inline GLfloat& Color::operator[](int index)
{
  return c[index];
}

inline Color Color::clamp()
{
  Color val;
  val.c[0] = c[0] < 0.0 ? 0.0 : (c[0] > 1.0 ? 1.0 : c[0]);
  val.c[1] = c[1] < 0.0 ? 0.0 : (c[1] > 1.0 ? 1.0 : c[1]);
  val.c[2] = c[2] < 0.0 ? 0.0 : (c[2] > 1.0 ? 1.0 : c[2]);
  val.c[3] = c[3] < 0.0 ? 0.0 : (c[3] > 1.0 ? 1.0 : c[3]);
  return val;
}

inline void Color::glcolor()
{
  glColor4fv(c);
}

inline void Color::print()
{
  print("%f %f %f %f\n");
}

inline void Color::print(const char *format)
{
  printf(format, c[0], c[1], c[2], c[3]);
}

#endif


ELLIPSE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <stdio.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Ellipse.h"

EllipseSt::EllipseSt()
{
  Point sar_dim, lin_dim;

  sar_dim.set(.3, .5, 1.2);
  lin_dim.set(.3, 1.2, .2);

  sarcens[0].set_dimensions(sar_dim);
  lintels[0].set_dimensions(lin_dim);
  sarcens[0].translate(5, 0, sar_dim.pt[2]);
  lintels[0].translate(4.9, 0, 2.*sar_dim.pt[2] + lin_dim.pt[2]);
  copy_zero();

  sarcens[0].rotate_self_aboutz(17);
  sarcens[1].rotate_self_aboutz(33);
  lintels[0].rotate_self_aboutz(25);

  sarcens[2].rotate_self_aboutz(-17);
  sarcens[3].rotate_self_aboutz(-33);
  lintels[1].rotate_self_aboutz(-25);

  sarcens[4].rotate_self_aboutz(197);
  sarcens[5].rotate_self_aboutz(213);
  lintels[2].rotate_self_aboutz(205);

  sarcens[6].rotate_self_aboutz(-197);
  sarcens[7].rotate_self_aboutz(-213);
  lintels[3].rotate_self_aboutz(-205);
}

void EllipseSt::erode(float p)
{
  int i;

  for (i = 0; i < nsarcens; i++) sarcens[i].erode(p);
  for (i = 0; i < nlintels; i++) lintels[i].erode(p);
}

void EllipseSt::draw()
{
  glPushMatrix();
  glScalef(1, 1.1, 1);
  draw_sarcens();
  draw_lintels();
  glPopMatrix();
}

void EllipseSt::draw_sarcens()
{
  int i;
  for (i = 0; i < nsarcens; i++) sarcens[i].draw();
}

void EllipseSt::draw_lintels()
{
  int i;
  for (i = 0; i < nlintels; i++) lintels[i].draw();
}

void EllipseSt::draw_shadow(Point dlight, GLfloat blur,
  Color color, Color diffuse)
{
  draw_sarcens_shadows(dlight, blur, color, diffuse);
  draw_lintels_shadows(dlight, blur, color, diffuse);
}

void EllipseSt::draw_sarcens_shadows(Point dlight, GLfloat blur,
   Color color, Color diffuse)
{
  int i;
  for (i = 0; i < nsarcens; i++)
sarcens[i].draw_shadow(dlight, blur, color, diffuse);
}

void EllipseSt::draw_lintels_shadows(Point dlight, GLfloat blur,
   Color color, Color diffuse)
{
  int i;
  for (i = 0; i < nlintels; i++)
lintels[i].draw_shadow(dlight, blur, color, diffuse);
}

void EllipseSt::copy_zero()
{
  int i;

  for (i = 1; i < nsarcens; i++) sarcens[i] = sarcens[0];
  for (i = 1; i < nlintels; i++) lintels[i] = lintels[0];
}


ELLIPSE.H

#ifndef ELLIPSE_H
#define ELLIPSE_H

#include "Stone.h"

const int nsarcens = 8;
const int nlintels = 4;

class EllipseSt {
 public:
  EllipseSt();
  ~EllipseSt() {};

  void erode(float p);

  void draw();
  void draw_shadow(Point dlight, GLfloat blur = 0.0,
                   Color color = black, Color diffuse = black);

 private:
  GLfloat r1, r2;

  Stone sarcens[nsarcens], lintels[nlintels];

  void copy_zero();

  void draw_sarcens();
  void draw_lintels();

  void draw_sarcens_shadows(Point dlight, GLfloat blur,
                            Color color, Color diffuse);
  void draw_lintels_shadows(Point dlight, GLfloat blur,
                            Color color, Color diffuse);
};

#endif


MAIN.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>
#include <GL/glx.h>

#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/CascadeB.h>
#include <Xm/Frame.h>
#include <Xm/MainW.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
#include <Xm/ToggleB.h>
#include <GLwMDrawA.h>

#include <stdio.h>
#include <stdlib.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "atmosphe.h"
#include "scene.h"
#include "callback.h"

static Display *display;
XtAppContext app_context;
Widget glw;

const int max_args = 20;

XVisualInfo *find_visual()
{
  int attr[256], i, stencil, dbuffer, accum;
  XVisualInfo *vi;

  i = 0;
  attr[i++] = GLX_RGBA;
  attr[i++] = GLX_RED_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_GREEN_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_BLUE_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_DEPTH_SIZE;  attr[i++] = 1;
  dbuffer = i;
  attr[i++] = GLX_DOUBLEBUFFER;
  stencil = i;
  attr[i++] = GLX_STENCIL_SIZE;
  attr[i++] = 1;
  accum = i;
  attr[i++] = GLX_ACCUM_RED_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_ACCUM_BLUE_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_ACCUM_GREEN_SIZE;  attr[i++] = 1;
  attr[i++] = GLX_ACCUM_ALPHA_SIZE;  attr[i++] = 1;
  attr[i++] = (int)None;

  vi = glXChooseVisual(display, DefaultScreen(display), attr);
  if (vi == NULL) {
    //fprintf(stderr, "Unable to find visual with accumulation buffer.\n");
    //fprintf(stderr, "(Fancy fog won't work).\n");
    attr[accum] = (int)None;
    vi = glXChooseVisual(display, DefaultScreen(display), attr);
    if (vi == NULL) {
      //fprintf(stderr, "Unable to find visual with stencil buffer.\n");
      //fprintf(stderr, "(Things won't look quite as good).\n");
      attr[stencil] = (int)None;
      vi = glXChooseVisual(display, DefaultScreen(display), attr);
      if (vi == NULL) {
//fprintf(stderr, "Unable to find double-buffered visual.\n");
//fprintf(stderr, "(Things will look even worse).\n");
attr[dbuffer] = (int)None;
vi = glXChooseVisual(display, DefaultScreen(display), attr);
//if (vi == NULL) fprintf(stderr, "Can't find visual at all.\n");
      }
    }
  }
  return vi;
}

Widget create_widgets(Widget parent)
{
  Widget main_window;
  Widget menu_bar;
  Widget menu_pane;
  Widget button;
  Widget cascade;
  Widget frame;
  XVisualInfo *vi;

  Arg args[max_args];
  int argcount;

#ifdef X11
  char buffer[128];
#endif

  int i;

  main_window = XmCreateMainWindow(parent, "main1", NULL, 0);
  XtManageChild(main_window);

  menu_bar = XmCreateMenuBar(main_window, "menu_bar", NULL, 0);
  XtManageChild(menu_bar);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", NULL, 0);
  button = XmCreatePushButton(menu_pane, "Exit", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback, (XtCallbackProc)exitCB, NULL);

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "File", args, 1);
  XtManageChild(cascade);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", NULL, 0);
  XtSetArg(args[0], XmNset, use_lighting);
  button = XmCreateToggleButton(menu_pane, "Lighting", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)intToggleCB, &use_lighting);
  XtSetArg(args[0], XmNset, draw_shadows);
  button = XmCreateToggleButton(menu_pane, "Shadows", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)intToggleCB, &draw_shadows);

  XtSetArg(args[0], XmNset, use_normal_fog);
  button = XmCreateToggleButton(menu_pane, "Fog", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)intToggleCB, &use_normal_fog);

#ifdef TEXTURE
  XtSetArg(args[0], XmNset, use_textures);
  button = XmCreateToggleButton(menu_pane, "Texture Map", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
                (XtCallbackProc)intToggleCB, &use_textures);
#endif

  XtSetArg(args[0], XmNset, use_telescope);
  button = XmCreateToggleButton(menu_pane, "Telescope", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)intToggleCB, &use_telescope);

  XtSetArg(args[0], XmNset, use_antialias);
  button = XmCreateToggleButton(menu_pane, "Antialias", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)intToggleCB, &use_antialias);

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "SPFX", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNradioBehavior, True); argcount++;
  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args, argcount);
  XtSetArg(args[0], XmNset, TRUE);
  for (i = 0; i < nweathers; i++) {
    if (i == def_weather_index) argcount = 1;
    else argcount = 0;
    button = XmCreateToggleButton(menu_pane, (char *)weathers[i].name,
  args, argcount);
    XtManageChild(button);
    XtAddCallback(button, XmNvalueChangedCallback,
  (XtCallbackProc)weatherCB,
  (XtPointer)(&weathers[i]));
  }
  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Weather", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNradioBehavior, True); argcount++;
  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", args, argcount);
  button = XmCreatePushButton(menu_pane, "Current Time", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)currentTimeCB, NULL);
  button = XmCreatePushButton(menu_pane, "10 a.m.", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)time10amCB, NULL);
  button = XmCreatePushButton(menu_pane, "Noon", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)time12pmCB, NULL);
  button = XmCreatePushButton(menu_pane, "4 p.m.", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)time4pmCB, NULL);

  XtSetArg(args[0], XmNset, 1); argcount = 1;
  button = XmCreateToggleButton(menu_pane, "Hold Time", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)timeSpeedCB, (XtPointer)0);
  button = XmCreateToggleButton(menu_pane, "Scale = 1:1", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)timeSpeedCB, (XtPointer)1);
  button = XmCreateToggleButton(menu_pane, "Scane = 1:10", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)timeSpeedCB, (XtPointer)10);
  button = XmCreateToggleButton(menu_pane, "Scane = 1:100", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)timeSpeedCB, (XtPointer)100);
  button = XmCreateToggleButton(menu_pane, "Scane = 1:500", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)timeSpeedCB, (XtPointer)500);

  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Time", args, 1);
  XtManageChild(cascade);

  menu_pane = XmCreatePulldownMenu(menu_bar, "menu_pane", NULL, 0);
  XtSetArg(args[0], XmNset, cb_demo_mode); argcount = 1;
  button = XmCreateToggleButton(menu_pane, "Demo Mode", args, 1);
  XtManageChild(button);
  XtAddCallback(button, XmNvalueChangedCallback,
(XtCallbackProc)demo_modeCB, (XtPointer)0);
  button = XmCreatePushButton(menu_pane, "Reset viewer", NULL, 0);
  XtManageChild(button);
  XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc)resetViewerCB, NULL);
  XtSetArg(args[0], XmNsubMenuId, menu_pane);
  cascade = XmCreateCascadeButton(menu_bar, "Misc", args, 1);
  XtManageChild(cascade);

  argcount = 0;
  XtSetArg(args[argcount], XmNmarginWidth, 0); argcount++;
  XtSetArg(args[argcount], XmNmarginHeight, 0); argcount++;
  XtSetArg(args[argcount], XmNshadowThickness, 1); argcount++;
  XtSetArg(args[argcount], XmNshadowType, XmSHADOW_OUT); argcount++;
  frame = XmCreateFrame(main_window, "frame", args, argcount);
  XtManageChild(frame);

  argcount = 0;
  vi = find_visual();
  if (vi) {
    XtSetArg(args[argcount], GLwNvisualInfo, vi); argcount++;
  }
  else {
    XtSetArg(args[argcount], GLwNrgba, TRUE); argcount++;
    XtSetArg(args[argcount], GLwNdepthSize, 1); argcount++;
    XtSetArg(args[argcount], GLwNdoublebuffer, TRUE); argcount++;
  }
  XtSetArg(args[argcount], XmNbottomAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNtopAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNleftAttachment, XmATTACH_FORM); argcount++;
  XtSetArg(args[argcount], XmNrightAttachment, XmATTACH_FORM); argcount++;
  glw = GLwCreateMDrawingArea(frame, "glwidget", args, argcount);
  XtManageChild(glw);
  XtAddCallback(glw, GLwNginitCallback, (XtCallbackProc)initCB, 0);
  XtAddCallback(glw, GLwNexposeCallback, (XtCallbackProc)exposeCB, 0);
  XtAddCallback(glw, GLwNresizeCallback, (XtCallbackProc)resizeCB, 0);
  XtAddCallback(glw, GLwNinputCallback, (XtCallbackProc)inputCB, 0);

  return main_window;
}

void _cdecl main(int argc, char **argv)
{
  Widget app_shell;
  Arg args[max_args];
  int argcount;

  XtToolkitInitialize();
  app_context = XtCreateApplicationContext();
  display = XtOpenDisplay(app_context, NULL, argv[0],
                          "XMdemos", NULL, 0, &argc, argv);
  if (!display) {
    XtWarning("Can't open display.");
    exit(0);
  }

  argcount = 0;
  XtSetArg(args[argcount], XmNmaxAspectX, 1); argcount++;
  XtSetArg(args[argcount], XmNmaxAspectY, 1); argcount++;
  XtSetArg(args[argcount], XmNminAspectX, 1); argcount++;
  XtSetArg(args[argcount], XmNminAspectY, 1); argcount++;
  app_shell =
    XtAppCreateShell(argv[0], "XMdemos", applicationShellWidgetClass,
                     display, args, argcount);

  create_widgets(app_shell);

  XtRealizeWidget(app_shell);

  XtAppMainLoop(app_context);

}


POINT.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/gl.h>

#include <math.h>
#include <stdio.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#define POINT_EXTERN
#include "Point.h"

Point Point::rotate_abouty(GLfloat c, GLfloat s)
{
  val.pt[0] = c*pt[0] + s*pt[2];
  val.pt[1] = pt[1];
  val.pt[2] = -s*pt[0] + c*pt[2];
  return val;
}

Point Point::rotate_aboutz(GLfloat c, GLfloat s)
{
  val.pt[0] = c*pt[0] - s*pt[1];
  val.pt[1] = s*pt[0] + c*pt[1];
  val.pt[2] = pt[2];
  return val;
}

void Point::refract_self(Point light, Point N, GLfloat I)
{
  GLfloat t;
  Point dlight;

  dlight = refract_direction(light, N, I);
  t = -pt[2] / dlight.pt[2];
  pt[0] = pt[0] + dlight.pt[0]*t;
  pt[1] = pt[1] + dlight.pt[1]*t;
  pt[2] = 0;
}

Point Point::refract_direction(Point light, Point N, GLfloat I)
{
  GLfloat cos1, sin1, cos2, sin2, m;
  GLfloat dlight[3], dN[3], axis[3];

  /* dlight = (light - *this).unit() * -1.0; */
  dlight[0] = pt[0] - light.pt[0];
  dlight[1] = pt[1] - light.pt[1];
  dlight[2] = pt[2] - light.pt[2];
  m = sqrt(dlight[0]*dlight[0] + dlight[1]*dlight[1] + dlight[2]*dlight[2]);
  dlight[0] /= m;
  dlight[1] /= m;
  dlight[2] /= m;

  // dN = N * -1.0;
  dN[0] = -N.pt[0];
  dN[1] = -N.pt[1];
  dN[2] = -N.pt[2];

  // cos1 = dN.dot(dlight);
  cos1 = dN[0]*dlight[0] + dN[1]*dlight[1] + dN[2]*dlight[2];

  if (1.0 - cos1*cos1 < point_fudge) {
    val = dN;
    return val;
  }

  // axis = ((dN * dlight) * dN).unit();
  val.pt[0] = dN[1]*dlight[2] - dlight[1]*dN[2];
  val.pt[1] = dN[2]*dlight[0] - dlight[2]*dN[0];
  val.pt[2] = dN[0]*dlight[1] - dN[1]*dlight[0];
  axis[0] = val.pt[1]*dN[2] - dN[1]*val.pt[2];
  axis[1] = val.pt[2]*dN[0] - dN[2]*val.pt[0];
  axis[2] = val.pt[0]*dN[1] - val.pt[1]*dN[0];
  m = sqrt(axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]);
  axis[0] /= m;
  axis[1] /= m;
  axis[2] /= m;

  if (axis[0]*axis[0] > point_fudge)
    sin1 = (dlight[0] - dN[0] * cos1) / axis[0];
  else if (axis[1]*axis[1] > point_fudge)
    sin1 = (dlight[1] - dN[1] * cos1) / axis[1];
  else sin1 = dlight[2] - dN[2] * cos1;

  sin2 = sin1 / I;
  cos2 = (sin1*sin1 < 1.0) ? sqrt(1.0 - sin2*sin2) : 0;

  dlight[0] = dN[0]*cos2 + axis[0]*sin2;
  dlight[1] = dN[1]*cos2 + axis[1]*sin2;
  dlight[2] = dN[2]*cos2 + axis[2]*sin2;

  /* I'm not sure this is quite legal */
  if (dlight[2] > 0.0) dlight[2] = -dlight[2];

  val = dlight;

  return val;
}

void Point::print()
{
  print("%f %f %f\n");
}

void Point::print(const char *format)
{
  printf(format, this->pt[0], this->pt[1], this->pt[2], 1.0);
}


POINT.H

#ifndef POINT_H
#define POINT_H

#ifndef POINT_EXTERN
#define POINT_EXTERN extern
#endif

#include <math.h>

const float point_fudge = .000001;

class Point {
 public:
  inline Point operator=(Point a);
  inline Point operator=(GLfloat *a);
  inline Point operator+(Point a);
  inline Point operator+=(Point a);
  inline Point operator-(Point a);
  // This takes a cross-product
  inline Point operator*(Point b);
  inline Point operator*(GLfloat b);
  inline Point operator*=(GLfloat b);
  inline Point operator/(GLfloat b);
  inline Point operator/=(GLfloat b);
  inline GLfloat& operator[](int index);

  inline void set(GLfloat x, GLfloat y, GLfloat z, GLfloat w = 1.0);

  inline GLfloat dist(Point b);
  inline GLfloat dot(Point b);
  inline GLfloat dot(GLfloat x, GLfloat y, GLfloat z);
  inline GLfloat mag();
  inline GLfloat magsquared();
  inline Point unit();
  inline void unitize();

  inline Point scale(Point p);

  // Angle is in RADIANS
  inline Point rotate(Point axis, GLfloat angle);
  inline Point rotate(Point axis, GLfloat c, GLfloat s);
  inline void rotate_self(Point axis, GLfloat c, GLfloat s);
  Point rotate_abouty(GLfloat c, GLfloat s);
  Point rotate_aboutz(GLfloat c, GLfloat s);

  // Returns point projected through proj_pt into XY plane
  // Does nothing if proj_pt - *this is parallel to XY plane
  inline Point project(Point proj_pt);
  inline void project_self(Point proj_pt);
  inline void Point::project_self(GLfloat px, GLfloat py, GLfloat pz);
  inline Point project_direction(Point direction);
  inline Point Point::project_direction(GLfloat x, GLfloat y, GLfloat z);
  // This projects (px, py, pz) into this in direction (dx, dy, dz)
  inline void Point::compute_projected(GLfloat px, GLfloat py, GLfloat pz,
 GLfloat x, GLfloat y, GLfloat z);

  // Returns point projected through light and refracted into XY
  // plane.
  // N is normal at point (ie normal at *this)
  // I is the index of refraction
  inline Point refract(Point light, Point N, GLfloat I);
  void refract_self(Point light, Point N, GLfloat I);
  Point refract_direction(Point light, Point N, GLfloat I);

  inline void glvertex();
  inline void glnormal();

  void print();
  void print(const char *format);

  GLfloat pt[4];
 private:
};

POINT_EXTERN Point val;

#define DOT(a, b) (a.pt[0]*b.pt[0] + a.pt[1]*b.pt[1] + a.pt[2]*b.pt[2])
#define THIS_DOT(b) (pt[0]*b.pt[0] + pt[1]*b.pt[1] + pt[2]*b.pt[2])

inline Point Point::operator=(Point a)
{
  pt[0] = a.pt[0];
  pt[1] = a.pt[1];
  pt[2] = a.pt[2];
  pt[3] = a.pt[3];
  return *this;
}

inline Point Point::operator=(GLfloat *a)
{
  pt[0] = a[0];
  pt[1] = a[1];
  pt[2] = a[2];
  pt[3] = 1;
  return *this;
}

inline Point Point::operator+(Point a)
{
  val.pt[0] = pt[0] + a.pt[0];
  val.pt[1] = pt[1] + a.pt[1];
  val.pt[2] = pt[2] + a.pt[2];
  return val;
}

inline Point Point::operator+=(Point a)
{
  pt[0] += a.pt[0];
  pt[1] += a.pt[1];
  pt[2] += a.pt[2];
  return *this;
}

inline Point Point::operator-(Point a)
{
  val.pt[0] = pt[0] - a.pt[0];
  val.pt[1] = pt[1] - a.pt[1];
  val.pt[2] = pt[2] - a.pt[2];
  return val;
}

inline Point Point::operator*(Point b)
{
  val.pt[0] = pt[1]*b.pt[2] - b.pt[1]*pt[2];
  val.pt[1] = pt[2]*b.pt[0] - b.pt[2]*pt[0];
  val.pt[2] = pt[0]*b.pt[1] - pt[1]*b.pt[0];
  return val;
}

inline Point Point::operator*(GLfloat b)
{
  val.pt[0] = pt[0] * b;
  val.pt[1] = pt[1] * b;
  val.pt[2] = pt[2] * b;
  return val;
}

inline Point Point::operator*=(GLfloat b)
{
  pt[0] *= b;
  pt[1] *= b;
  pt[2] *= b;
  return *this;
}

inline Point Point::operator/(GLfloat b)
{
  val.pt[0] = pt[0] / b;
  val.pt[1] = pt[1] / b;
  val.pt[2] = pt[2] / b;
  return val;
}

inline Point Point::operator/=(GLfloat b)
{
  pt[0] /= b;
  pt[1] /= b;
  pt[2] /= b;
  return *this;
}

inline GLfloat& Point::operator[](int index)
{
  return pt[index];
}

inline void Point::set(GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
  pt[0] = x;
  pt[1] = y;
  pt[2] = z;
  pt[3] = w;
}

inline GLfloat Point::dist(Point b)
{
  return (*this - b).mag();
}

inline GLfloat Point::dot(Point b)
{
  return pt[0]*b.pt[0] + pt[1]*b.pt[1] + pt[2]*b.pt[2];
}

inline GLfloat Point::dot(GLfloat x, GLfloat y, GLfloat z)
{
  return pt[0]*x + pt[1]*y + pt[2]*z;
}

inline GLfloat Point::mag()
{
  return sqrt(pt[0]*pt[0] + pt[1]*pt[1] +
              pt[2]*pt[2]);
}

inline GLfloat Point::magsquared()
{
  return pt[0]*pt[0] + pt[1]*pt[1] + pt[2]*pt[2];
}

inline Point Point::unit()
{
  GLfloat m = sqrt(pt[0]*pt[0] + pt[1]*pt[1] + pt[2]*pt[2]);
  val.pt[0] = pt[0] / m;
  val.pt[1] = pt[1] / m;
  val.pt[2] = pt[2] / m;
  return val;
}

inline void Point::unitize()
{
  GLfloat m = sqrt(pt[0]*pt[0] + pt[1]*pt[1] + pt[2]*pt[2]);
  pt[0] /= m;
  pt[1] /= m;
  pt[2] /= m;
}

inline Point Point::scale(Point p)
{
  val.pt[0] = pt[0] * p.pt[0];
  val.pt[1] = pt[1] * p.pt[1];
  val.pt[2] = pt[2] * p.pt[2];
  return val;
}

inline Point Point::rotate(Point axis, GLfloat angle)
{
  return rotate(axis, cos(angle), sin(angle));
}

inline Point Point::rotate(Point axis, GLfloat c, GLfloat s)
{
  float x = axis.pt[0], y = axis.pt[1], z = axis.pt[2], t = 1.0 - c;
  float tx, ty;

  tx = t*x;
  /* Taking advantage of inside info that this is a common case */
  if (y == 0.0) {
    val.pt[0] = pt[0]*(tx*x + c) + pt[1]*(-s*z) + pt[2]*(tx*z);
    val.pt[1] = pt[0]*(s*z) + pt[1]*c + pt[2]*(-s*x);
    val.pt[2] = pt[0]*(tx*z) + pt[1]*s*x + pt[2]*(t*z*z + c);
  } else {
    ty = t*y;
    val.pt[0] = pt[0]*(tx*x + c) + pt[1]*(tx*y - s*z) +
      pt[2]*(tx*z + s*y);
    val.pt[1] = pt[0]*(tx*y + s*z) + pt[1]*(ty*y + c) +
      pt[2]*(ty*z - s*x);
    val.pt[2] = pt[0]*(tx*z - s*y) + pt[1]*(ty*z + s*x) +
      pt[2]*(t*z*z + c);
  }
  return val;
}

inline void Point::rotate_self(Point axis, GLfloat c, GLfloat s)
{
  float Px, Py, Pz;
  float x = axis.pt[0], y = axis.pt[1], z = axis.pt[2], t = 1.0 - c;
  float tx, ty;

  tx = t*x;
  Px = pt[0];
  Py = pt[1];
  Pz = pt[2];
  /* Taking advantage of inside info that this is a common case */
  if (!y) {
    pt[0] = Px*(tx*x + c) +Py*(-s*z) + Pz*(tx*z);
    pt[1] = Px*(s*z) + Py*c + Pz*(-s*x);
    pt[2] = Px*(tx*z) + Py*s*x + Pz*(t*z*z + c);
  } else {
    ty = t*y;
    pt[0] = Px*(tx*x + c) + Py*(tx*y - s*z) +
      Pz*(tx*z + s*y);
    pt[1] = Px*(tx*y + s*z) + Py*(ty*y + c) +
      Pz*(ty*z - s*x);
    pt[2] = Px*(tx*z - s*y) + Py*(ty*z + s*x) +
      Pz*(t*z*z + c);
  }
}

inline void Point::glvertex()
{
  glVertex3fv(pt);
}

inline void Point::glnormal()
{
  glNormal3fv(pt);
}

inline Point Point::project(Point proj_pt)
{
  GLfloat dirx = pt[0] - proj_pt.pt[0],
      diry = pt[1] - proj_pt.pt[1],
      dirz = pt[2] - proj_pt.pt[2];
  GLfloat t;

  if (fabs(dirz) < point_fudge) val = *this;
  else {
    t = -proj_pt.pt[2] / dirz;
    val.pt[0] = proj_pt.pt[0] + dirx*t;
    val.pt[1] = proj_pt.pt[1] + diry*t;
    val.pt[2] = 0.0;
  }
  return val;
}

// This naively assumes that proj_pt[z] != this->pt[z]
inline void Point::project_self(Point proj_pt)
{
  GLfloat dirx = pt[0] - proj_pt.pt[0],
      diry = pt[1] - proj_pt.pt[1],
      dirz = pt[2] - proj_pt.pt[2];
  GLfloat t;

  t = -proj_pt.pt[2] / dirz;
  pt[0] = proj_pt.pt[0] + dirx*t;
  pt[1] = proj_pt.pt[1] + diry*t;
  pt[2] = 0.0;
}

inline void Point::project_self(GLfloat px, GLfloat py, GLfloat pz)
{
  GLfloat dirx = pt[0] - px,
  diry = pt[1] - py,
  dirz = pt[2] - pz, t;

  t = -pz / dirz;
  pt[0] = px + dirx*t;
  pt[1] = py + diry*t;
  pt[2] = 0.0;
}

inline Point Point::project_direction(Point direction) {
  GLfloat t;

  t = -pt[2] / direction.pt[2];
  val.pt[0] = pt[0] + direction.pt[0]*t;
  val.pt[1] = pt[1] + direction.pt[1]*t;
  val.pt[2] = 0;
  return val;
}

inline Point Point::project_direction(GLfloat x, GLfloat y, GLfloat z)
{
  GLfloat t;

  t = -pt[2] / z;
  val.pt[0] = pt[0] + x*t;
  val.pt[1] = pt[1] + y*t;
  val.pt[2] = 0;
  return val;
}

inline void Point::compute_projected(GLfloat px, GLfloat py, GLfloat pz,
     GLfloat dx, GLfloat dy, GLfloat dz)
{
  GLfloat t = -pz / dz;
  pt[0] = px + dx*t;
  pt[1] = py + dy*t;
  pt[2] = 0;
}

#undef POINT_EXTERN

#endif


RGBIMAGE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/gl.h>
#include "stonehen.h"

#define IMAGIC      0x01da
#define IMAGIC_SWAP 0xda01

#define SWAP_SHORT_BYTES(x) ((((x) & 0xff) << 8) | (((x) & 0xff00) >> 8))
#define SWAP_LONG_BYTES(x) (((((x) & 0xff) << 24) | (((x) & 0xff00) << 8)) | \
                            ((((x) & 0xff0000) >> 8) | (((x) & 0xff000000) >> 24)))

typedef struct _rawImageRec {
    unsigned short imagic;
    unsigned short type;
    unsigned short dim;
    unsigned short sizeX, sizeY, sizeZ;
    unsigned long min, max;
    unsigned long wasteBytes;
    char name[80];
    unsigned long colorMap;
    FILE *file;
    unsigned char *tmp, *tmpR, *tmpG, *tmpB;
    unsigned long rleEnd;
    unsigned long *rowStart;
    unsigned long *rowSize;
} rawImageRec;

static rawImageRec *RawImageOpen(char *fileName)
{
    rawImageRec *raw;
    unsigned long *rowStart, *rowSize, ulTmp;
    int x;

    raw = (rawImageRec *)malloc(sizeof(rawImageRec));
    if (raw == NULL) {
//fprintf(stderr, "Out of memory!\n");
exit(-1);
    }
    if ((raw->file = fopen(fileName, "rb")) == NULL) {
perror(fileName);
exit(-1);
    }

    fread(raw, 1, 12, raw->file);

    if (raw->imagic == IMAGIC_SWAP) {
        raw->type = SWAP_SHORT_BYTES(raw->type);
        raw->dim = SWAP_SHORT_BYTES(raw->dim);
        raw->sizeX = SWAP_SHORT_BYTES(raw->sizeX);
        raw->sizeY = SWAP_SHORT_BYTES(raw->sizeY);
        raw->sizeZ = SWAP_SHORT_BYTES(raw->sizeZ);
    }
    raw->tmp = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpR = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpG = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpB = (unsigned char *)malloc(raw->sizeX*256);
    if (raw->tmp == NULL || raw->tmpR == NULL || raw->tmpG == NULL ||
raw->tmpB == NULL) {
//fprintf(stderr, "Out of memory!\n");
exit(-1);
    }

    if ((raw->type & 0xFF00) == 0x0100) {
x = raw->sizeY * raw->sizeZ * sizeof(long);
raw->rowStart = (unsigned long *)malloc(x);
raw->rowSize = (unsigned long *)malloc(x);
if (raw->rowStart == NULL || raw->rowSize == NULL) {
    //fprintf(stderr, "Out of memory!\n");
    exit(-1);
}
raw->rleEnd = 512 + (2 * x);
fseek(raw->file, 512, SEEK_SET);
fread(raw->rowStart, 1, x, raw->file);
fread(raw->rowSize, 1, x, raw->file);
        if (raw->imagic == IMAGIC_SWAP) {
            x /= sizeof(long);
            rowStart = raw->rowStart;
            rowSize = raw->rowSize;
            while (x--) {
                ulTmp = *rowStart;
                *rowStart++ = SWAP_LONG_BYTES(ulTmp);
                ulTmp = *rowSize;
                *rowSize++ = SWAP_LONG_BYTES(ulTmp);
            }
        }
    }
    return raw;
}

static void RawImageClose(rawImageRec *raw)
{

    fclose(raw->file);
    free(raw->tmp);
    free(raw->tmpR);
    free(raw->tmpG);
    free(raw->tmpB);
    free(raw);
}

static void RawImageGetRow(rawImageRec *raw, unsigned char *buf, int y, int z)
{
    unsigned char *iPtr, *oPtr, pixel;
    int count;

    if ((raw->type & 0xFF00) == 0x0100) {
fseek(raw->file, raw->rowStart[y+z*raw->sizeY], SEEK_SET);
fread(raw->tmp, 1, (unsigned int)raw->rowSize[y+z*raw->sizeY],
      raw->file);

iPtr = raw->tmp;
oPtr = buf;
while (1) {
    pixel = *iPtr++;
    count = (int)(pixel & 0x7F);
    if (!count) {
return;
    }
    if (pixel & 0x80) {
while (count--) {
    *oPtr++ = *iPtr++;
}
    } else {
pixel = *iPtr++;
while (count--) {
    *oPtr++ = pixel;
}
    }
}
    } else {
fseek(raw->file, 512+(y*raw->sizeX)+(z*raw->sizeX*raw->sizeY),
      SEEK_SET);
fread(buf, 1, raw->sizeX, raw->file);
    }
}

static void RawImageGetData(rawImageRec *raw, RGBImageRec *final)
{
    unsigned char *ptr;
    int i, j;

    final->data = (unsigned char *)malloc((raw->sizeX+1)*(raw->sizeY+1)*4);
    if (final->data == NULL) {
//fprintf(stderr, "Out of memory!\n");
exit(-1);
    }

    ptr = final->data;
    for (i = 0; i < raw->sizeY; i++) {
RawImageGetRow(raw, raw->tmpR, i, 0);
RawImageGetRow(raw, raw->tmpG, i, 1);
RawImageGetRow(raw, raw->tmpB, i, 2);
for (j = 0; j < raw->sizeX; j++) {
    *ptr++ = *(raw->tmpR + j);
    *ptr++ = *(raw->tmpG + j);
    *ptr++ = *(raw->tmpB + j);
}
    }
}

RGBImageRec *RGBImageLoad(char *fileName)
{
    rawImageRec *raw;
    RGBImageRec *final;

    raw = RawImageOpen(fileName);
    final = (RGBImageRec *)malloc(sizeof(RGBImageRec));
    if (final == NULL) {
//fprintf(stderr, "Out of memory!\n");
exit(-1);
    }
    final->sizeX = raw->sizeX;
    final->sizeY = raw->sizeY;
    RawImageGetData(raw, final);
    RawImageClose(raw);
    return final;
}


RING.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <math.h>
#include <stdio.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Ring.h"

inline float radians(float a) {return a * M_PI / 180.0;};

Ring::Ring()
{
  Point sar_dim, lin_dim;

  radius = 10;

  nstones = 30;

  sar_dim.set(.2, .5, 1);
  sarcen.set_dimensions(sar_dim);
  sarcen.translate(radius, 0, sar_dim.pt[2]);

  angle = 360.0 / (float)nstones;

  lin_dim.set(.2, .99 * tan(2.0 * M_PI / (float)nstones) *
      (radius - sar_dim.pt[0]) / 2.0, .2);
  lintel.set_dimensions(lin_dim);
  lintel.translate(radius, 0, 2.*sar_dim.pt[2] + lin_dim.pt[2]);
}

 Ring::~Ring()
{
}

void Ring::erode(float p)
{
  sarcen.erode(p);
  lintel.erode(p);
}

void Ring::draw()
{
  draw_sarcens();
  draw_lintels();
}

void Ring::draw_sarcens()
{
  int i;
  for (i = 0; i < nstones; i++) {
    glPushMatrix();
    glRotatef(i * angle, 0, 0, 1);
    sarcen.draw();
    glPopMatrix();
  }
}

void Ring::draw_lintels()
{
  int i;
  glPushMatrix();
  glRotatef(angle / 2.0, 0, 0, 1);
  for (i = 0; i < nstones; i++) {
    glPushMatrix();
    glRotatef(i * angle, 0, 0, 1);
    lintel.draw();
    glPopMatrix();
  }
  glPopMatrix();
}

void Ring::draw_shadow(Point dlight, GLfloat blur,
       Color color, Color diffuse)
{
  draw_sarcens_shadows(dlight, blur, color, diffuse);
  draw_lintels_shadows(dlight, blur, color, diffuse);
}

void Ring::draw_sarcens_shadows(Point dlight, GLfloat blur,
Color color, Color diffuse)
{
  int i;
  Stone proto;

  proto = sarcen;
  for (i = 0; i < nstones; i++) {
    proto.rotate_self_aboutz(angle);
    proto.draw_shadow(dlight, blur, color, diffuse);
  }
}

void Ring::draw_lintels_shadows(Point dlight, GLfloat blur,
Color color, Color diffuse)
{
  int i;
  Stone proto;

  proto = lintel;

  proto.rotate_self_aboutz(angle / 2.0);

  for (i = 0; i < nstones; i++) {
    proto.rotate_self_aboutz(angle);
    proto.draw_shadow(dlight, blur, color, diffuse);
  }
}


RING.H

#ifndef RING_H
#define RING_H

#include "Stone.h"

class Ring {
 public:
  Ring();
  ~Ring();

  void erode(float p);

  void draw();
  void draw_shadow(Point dlight, GLfloat blur = 0.0,
   Color color = black, Color diffuse = black);

 private:
  GLfloat radius, angle;
  int nstones;

  Stone sarcen;
  Stone lintel;

  void draw_sarcens();
  void draw_lintels();

  void draw_sarcens_shadows(Point dlight, GLfloat blur,
    Color color, Color diffuse);
  void draw_lintels_shadows(Point dlight, GLfloat blur,
    Color color, Color diffuse);
};

#endif


ROUNDWAL.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <math.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Roundwal.h"

Roundwall::Roundwall()
{
  height = 2;
  radius = 20;
  divisions = 20;
  sint = cost = NULL;
}

void Roundwall::draw()
{
  float texx = 0;
  int i;
  if (sint == NULL) compute_tables();
  glBegin(GL_QUAD_STRIP);
  for (i = 0; i <= divisions; i++, texx += 1.) {
    glTexCoord2f(texx, 0);
    glNormal3f(-cost[i], -sint[i], 0);
    glVertex3f(radius * cost[i], radius * sint[i], 0);
    glTexCoord2f(texx, 1);
    glVertex3f(radius * cost[i], radius * sint[i], height);
  }
  glEnd();
}

void Roundwall::set_divisions(int d)
{
  if (divisions != d) delete_tables();
  divisions = d;
}

void Roundwall::delete_tables()
{
  delete sint;
  delete cost;
  sint = cost = NULL;
}

void Roundwall::compute_tables()
{
  int i;
  float t, dt;

  delete_tables();
  cost = new float[divisions + 1];
  sint = new float[divisions + 1];

  dt = 2. * M_PI / divisions;
  for (i = 0, t = 0.; i <= divisions; i++, t += dt) {
    cost[i] = cos(t);
    sint[i] = sin(t);
  }
}


ROUNDWAL.H

#ifndef ROUNDWALL_H
#define ROUNDWALL_H

class Roundwall {
 public:
  Roundwall();
  ~Roundwall() {};

  void draw();

  void set_divisions(int d);

  float height;
  float radius;

 private:
  int divisions;
  float *sint, *cost;

  void delete_tables();
  void compute_tables();
};

#endif


SCENE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdlib.h>
#include <stdio.h>

#include <GL/glu.h>
#include <GL/glaux.h>

/*
#ifdef X11
#include <GL/glx.h>
extern "C" {
#include <tk.h>
};
#endif
*/

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Point.h"
#include "Ring.h"
#include "Roundwal.h"
#include "Ellipse.h"
#include "Telescop.h"

#define SCENE_EXTERN
#include "scene.h"

GLfloat mat_view[16];
GLfloat view_rotx = 0;
GLfloat fov = 45.0, aspect = 1.0;
static Point eyep = {0, 0, .5};
static Point lookp = {0.05, 1, .25};

TimeDate current_time;

static int list_ground;
static int list_texture_ground;
static int list_trees;
static int list_texture_trees;
static int list_ring;
static int list_ellipse;
static int list_texture_stones;
static int list_shadows;
static int list_telescope;
static int list_texture_telescope;
int draw_ground = 1;
int draw_trees = 0;
int draw_ring = 1;
int draw_ellipse = 1;
int draw_shadows = 0;

int use_lighting = 1;
int use_textures = 0;
int texture_hack = 0;//HACK HACK HACK - only texture map the stone
int use_normal_fog = 0;
int use_fancy_fog = 0;
int use_telescope = 0;
int use_antialias = 0;

static void scene_identity();
static void scene_project(GLfloat f = fov, float dx = 0, float dy = 0);
static void scene_draw(int rend = 1);
static void scene_render_telescope();

static void draw_background();

Point sun_position = {0., .707, .707, 0.};
Color ambient(.25, .25, .25, 1.);
static void lights_init();

static void lists_init();

static void ground_list_init();
static void ground_draw();

Roundwall trees;
static void trees_list_init();
static void trees_draw();

Ring ring;
static void ring_list_init();
static void ring_draw();

EllipseSt ellipse;
static void ellipse_list_init();
static void ellipse_draw();

static void shadows_list_init();
static void shadows_draw();

Weather weather;

Telescope telescope;
GLfloat magnif = .5;
static void telescope_list_init();
static void telescope_draw();

/* Read the back buffer into the accum buffer, adding in the appropriate
 * alpha values */
static void fog_read_image();
/* Add the accum buffer to the back buffer */
static void fog_write_image();

inline float clamp(float x, float min, float max)
{
  if (x < min) return min;
  else if (x > max) return max;
  else return x;
}

void scene_init()
{

  scene_identity();
  scene_viewer_center();

  glEnable(GL_CULL_FACE);
  glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

  glEnable(GL_NORMALIZE);

  /* Initial time will be four in the afternoon */
  scene_set_time(TimeDate(16, 0));
  scene_set_weather(weathers[def_weather_index]);

  lights_init();
  lists_init();
}

inline double time_minutes(int hour, int minute, int second)
{
  return (double)hour*60.0 + (double)minute + (double)second / 60.0;
}

static void scene_time_changed()
{
  sun_position = current_time.sun_direction();

  weather.apply(sun_position);

  lights_init();
  shadows_list_init();
}

void scene_set_time(TimeDate t)
{
  current_time = t;
  scene_time_changed();
}

void scene_inc_time(TimeDate t)
{
  current_time += t;
  scene_time_changed();
}

/* This is a hack -- has to be called several times to get the antialiasing
 * to work */
static void scene_inner_render(GLfloat dx = 0, GLfloat dy = 0)
{
  /* This draws layered fog if the use_fancy_fog flag is on --
   * it's going to be slow on anything but high-end stuff */
  if (use_fancy_fog && weather.fog_density != 0.) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_FOG);
    draw_background();
    scene_project(fov, dx, dy);
    glClear(GL_DEPTH_BUFFER_BIT);
    if (use_lighting) glEnable(GL_LIGHTING);
    scene_draw();
    if (use_lighting) glDisable(GL_LIGHTING);
    fog_read_image();
    glDisable(GL_FOG);
  } else
    if (use_normal_fog && weather.fog_density != 0.) glEnable(GL_FOG);
    else glDisable(GL_FOG);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* This is the part where we actually draw the image */
  glClear(GL_DEPTH_BUFFER_BIT);
  draw_background();
  scene_project(fov, dx, dy);
  glClear(GL_DEPTH_BUFFER_BIT);
  if (use_lighting) glEnable(GL_LIGHTING);
  scene_draw();
  if (use_lighting) glDisable(GL_LIGHTING);

  if (use_fancy_fog && weather.fog_density != 0.) {
    fog_write_image();
  }

  if (use_telescope) scene_render_telescope();
}

void scene_render()
{
  GLint vp[4];

  scene_inner_render();

  if (!use_antialias) return;

  if (use_fancy_fog) {
    //fprintf(stderr, "Cannot antialias while using fancy fog.\n");
    return;
  }

  glGetIntegerv(GL_VIEWPORT, vp);
  glAccum(GL_LOAD, .5);

  scene_inner_render(2. / (float)vp[2], 2. / (float)vp[3]);
  glAccum(GL_ACCUM, .5);

/*
  scene_inner_render(-2. / (float)vp[2], -2. / (float)vp[3]);
  glAccum(GL_ACCUM, .25);
*/
  glAccum(GL_RETURN, 1);
/*
  glDrawBuffer(GL_BACK);
  glFlush();
*/
}

static void scene_render_telescope()
{
  telescope.draw_setup(fov, aspect);

  /* Don't fog the telescope - moisture makes it rust.
   * Seriously, it's in a strange coordinate system and fog will look
   * bad on it. */
  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_FOG);
  if (use_textures) {
    glCallList(list_texture_telescope);
    glEnable(GL_TEXTURE_2D);
  }
  glCallList(list_telescope);
  glPopAttrib();

  if (use_lighting) glEnable(GL_LIGHTING);
  glEnable(GL_STENCIL_TEST);
  glClearStencil(0);
  glClear(GL_STENCIL_BUFFER_BIT);
  glStencilFunc(GL_ALWAYS, 0x1, 0x1);
  glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

  glDisable(GL_CULL_FACE);
  telescope.draw_lens();
  glEnable(GL_CULL_FACE);

  telescope.draw_takedown();

  if (use_lighting) glDisable(GL_LIGHTING);

  glStencilFunc(GL_NOTEQUAL, 0x0, 0xffffffff);
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

  scene_identity();

  draw_background();

  glMatrixMode(GL_PROJECTION);
  glTranslatef(telescope.xpos / magnif, -telescope.ypos / magnif, 0);
  scene_project(fov * magnif);

  glClear(GL_DEPTH_BUFFER_BIT);

  /* Pushing the lighting bit used to do really bad things, but
   * hopefully they've all gone away */
  glPushAttrib(GL_LIGHTING_BIT);
  lights_init();
  if (use_lighting) glEnable(GL_LIGHTING);
  scene_draw();
  if (use_lighting) glDisable(GL_LIGHTING);
  glPopAttrib();

  glDisable(GL_STENCIL_TEST);
}

static void scene_identity()
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

static void scene_project(GLfloat f, float dx, float dy)
{
  glMatrixMode(GL_PROJECTION);
  glOrtho(-1 - dx, 1, -1 - dy, 1, 0, -1);
  gluPerspective(f, aspect, 0.01, 40.0);
  glMatrixMode(GL_MODELVIEW);
  glRotatef(view_rotx, 1, 0, 0);
  gluLookAt(eyep.pt[0], eyep.pt[1], eyep.pt[2],
    lookp.pt[0], lookp.pt[1], lookp.pt[2],
            0, 0, 1);
  glMultMatrixf(mat_view);
lights_init();
}

/* scene_draw() just draws the geometry - it's used for rendering and
 * picking. */
static void scene_draw(int rend)
{
  if (draw_ground) {
    if (rend) {
      if (use_textures) {
glCallList(list_texture_ground);
glEnable(GL_TEXTURE_2D);
      } else glEnable(GL_COLOR_MATERIAL);
    }
    glCallList(list_ground);

    if (rend) {
      glDisable(GL_TEXTURE_2D);
      glDisable(GL_COLOR_MATERIAL);
    }
  }

  if (draw_shadows) {
    if (use_textures && rend && !draw_ground) {
      glCallList(list_texture_ground);
      glEnable(GL_TEXTURE_2D);
    }
    glCallList(list_shadows);
    if (use_textures && rend) glDisable(GL_TEXTURE_2D);
  }

  if (draw_trees) {
    if (use_textures && rend) {
      glCallList(list_texture_trees);
      glEnable(GL_TEXTURE_2D);
    }
    glCallList(list_trees);
    if (use_textures && rend) glDisable(GL_TEXTURE_2D);
  }

  glClear(GL_DEPTH_BUFFER_BIT);

  if (draw_ring) {
    if (rend) {
      if (use_textures || texture_hack) {
glCallList(list_texture_stones);
glEnable(GL_TEXTURE_2D);
      }
      glEnable(GL_COLOR_MATERIAL);
      glColor3f(.5, .5, .5);
    }
    glCallList(list_ring);
    if (rend) {
      if (use_textures || texture_hack) glDisable(GL_TEXTURE_2D);
      glDisable(GL_COLOR_MATERIAL);
    }
  }

  if (draw_ellipse) {
    if (use_textures && rend) {
      // Hack to avoid doing something expensive twice in a row
      if (!draw_ring) glCallList(list_texture_stones);
      glEnable(GL_TEXTURE_2D);
    }
    glCallList(list_ellipse);
    if (use_textures && rend) glDisable(GL_TEXTURE_2D);
  }
}

static void draw_background()
{
  weather.draw_sky(sun_position);
}

void scene_viewer_center()
{
  glPushMatrix();
  glLoadIdentity();
  glGetFloatv(GL_MODELVIEW_MATRIX, mat_view);
  glPopMatrix();
  view_rotx = 0;
}

void scene_viewer_rotate_worldz(GLfloat degrees)
{
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glRotatef(degrees, 0, 0, 1);
  glMultMatrixf(mat_view);
  glGetFloatv(GL_MODELVIEW_MATRIX, mat_view);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
}

void scene_viewer_rotatez(GLfloat degrees)
{
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glRotatef(degrees, 0, 0, 1);
  glMultMatrixf(mat_view);
  glGetFloatv(GL_PROJECTION_MATRIX, mat_view);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
}

void scene_viewer_rotatex(GLfloat degrees)
{
  view_rotx += degrees;
  view_rotx = clamp(view_rotx, -60, 60);
  scene_identity();
  scene_project();
  lights_init();
}

void scene_viewer_translate(GLfloat dist)
{
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glTranslatef(0, dist, 0);
  glMultMatrixf(mat_view);
  glGetFloatv(GL_PROJECTION_MATRIX, mat_view);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  scene_identity();
  scene_project();
  lights_init();
}

void scene_position_telescope(GLfloat x, GLfloat y)
{
  telescope.xpos = x;
  telescope.ypos = y;
}

void scene_get_position_telescope(GLfloat *x, GLfloat *y)
{
  *x = telescope.xpos;
  *y = telescope.ypos;
}

void scene_get_radius_telescope(GLfloat *r)
{
  *r = telescope.get_radius();
}

void scene_set_weather(Weather w)
{
  weather = w;
  weather.apply(sun_position);
  shadows_list_init();
}

static int get_lists(int size)
{
  int i;
  i = glGenLists(size);
  if (size && !i) {
    //fprintf(stderr, "Unable to allocate %d display lists.\n");
    exit(1);
  }
  return i;
}

static void lights_init()
{
  glLightfv(GL_LIGHT0, GL_POSITION, sun_position.pt);

  /* This light gives a diffuse coefficient when the sun is off -
   * it's used for drawing shadows */
  glLightfv(GL_LIGHT1, GL_AMBIENT, black.c);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, black.c);
  glLightfv(GL_LIGHT1, GL_SPECULAR, black.c);
}

#ifdef TEXTURE
static void textures_list_init()
{
  AUX_RGBImageRec *teximage = NULL;

  teximage = auxRGBImageLoad((char *)texfile_stones);
  glNewList(list_texture_stones, GL_COMPILE);
  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, teximage->sizeX, teximage->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, teximage->data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
 GL_NEAREST_MIPMAP_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
 GL_LINEAR);
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glEndList();

  /* tk is obnoxious and doesn't seem to provide any mechanism for this */
  free(teximage->data);
  free(teximage);

  teximage = auxRGBImageLoad((char *)texfile_ground);
  glNewList(list_texture_ground, GL_COMPILE);
  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, teximage->sizeX, teximage->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, teximage->data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
  GL_NEAREST_MIPMAP_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
  GL_LINEAR);
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glScalef(100, 100, 1);
  glMatrixMode(GL_MODELVIEW);
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glEndList();

  free(teximage->data);
  free(teximage);

  /* Figure out some way to get an alpha component out of the tk --
   * otherwise we're really hosed */
  teximage = auxRGBImageLoad((char *)texfile_trees);
  glNewList(list_texture_trees, GL_COMPILE);
  /* In the final scenerio we probably won't want to mipmap this, but it's
   * not square and I don't feel like bothering to scale it */
  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, teximage->sizeX, teximage->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, teximage->data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
  GL_NEAREST_MIPMAP_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
  GL_LINEAR);
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glEndList();

  free(teximage->data);
  free(teximage);

  teximage = auxRGBImageLoad((char *)texfile_telescope);
  glNewList(list_texture_telescope, GL_COMPILE);
  glTexImage2D(GL_TEXTURE_2D, 0, 3, teximage->sizeX, teximage->sizeY,
       0, GL_RGB, GL_UNSIGNED_BYTE, teximage->data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEndList();

  free(teximage->data);
  free(teximage);
}
#endif

static void lists_init()
{
  list_ground = get_lists(1);
  list_texture_ground = get_lists(1);
  list_trees = get_lists(1);
  list_texture_trees = get_lists(1);
  list_ring = get_lists(1);
  list_ellipse = get_lists(1);
  list_texture_stones = get_lists(1);
  list_shadows = get_lists(1);
  list_telescope = get_lists(1);
  list_texture_telescope = get_lists(1);

  ground_list_init();
  trees_list_init();
  shadows_list_init();
  ring_list_init();
  ellipse_list_init();
#ifdef TEXTURE
  textures_list_init();
#endif
  telescope_list_init();
}

static void ground_list_init()
{
  glNewList(list_ground, GL_COMPILE);
  ground_draw();
  glEndList();
}

static void ground_draw()
{
  glColor3f(0, .75, 0);

  glLoadName(name_ground);

  glNormal3f(0, 0, 1);

  glPushMatrix();
  /* Making something this big would confuse the zbuffer, but we're
   * clearing that AFTER drawing this, so it's ok */
  glScalef(100, 100, 1);
  glBegin(GL_QUADS);
  glTexCoord2f(0, 0);
  glVertex2f(-1, -1);
  glTexCoord2f(1, 0);
  glVertex2f(1, -1);
  glTexCoord2f(1, 1);
  glVertex2f(1, 1);
  glTexCoord2f(0, 1);
  glVertex2f(-1, 1);
  glEnd();
  glPopMatrix();
}

static void trees_list_init()
{
  glNewList(list_trees, GL_COMPILE);
  trees_draw();
  glEndList();
}

static void trees_draw()
{
  glEnable(GL_COLOR_MATERIAL);
  glColor3f(0, .5, 0);

  glLoadName(name_trees);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  trees.draw();
  glDisable(GL_BLEND);

  glDisable(GL_COLOR_MATERIAL);
}

static void ring_list_init()
{
  glNewList(list_ring, GL_COMPILE);
  ring_draw();
  glEndList();
}

static void ring_draw()
{
  glLoadName(name_ring);

  glEnable(GL_DEPTH_TEST);
  ring.erode(.1);
  ring.draw();
  glDisable(GL_DEPTH_TEST);
}

static void ellipse_list_init()
{
  glNewList(list_ellipse, GL_COMPILE);
  ellipse_draw();
  glEndList();
}

static void ellipse_draw()
{
  glEnable(GL_COLOR_MATERIAL);
  glColor3f(.5, .5, .5);

  glEnable(GL_DEPTH_TEST);

  glLoadName(name_ellipse);

  ellipse.erode(.1);
  ellipse.draw();

  glDisable(GL_DEPTH_TEST);

  glDisable(GL_COLOR_MATERIAL);
}

static void shadows_list_init()
{
  glNewList(list_shadows, GL_COMPILE);
  shadows_draw();
  glEndList();
}

static void shadows_draw()
{
  Color grass(0, .75, 0);

  glPushAttrib(GL_ENABLE_BIT);

  /* Turn the sun off */
  glDisable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, grass.c);
  glColor3fv((grass * .5).c);

  glDisable(GL_CULL_FACE);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  ring.draw_shadow(sun_position, weather.shadow_blur(),
   grass * .5, grass);
  ellipse.draw_shadow(sun_position, weather.shadow_blur(),
      grass * .5, grass);
  glPopAttrib();
}

static void telescope_list_init()
{
  glNewList(list_telescope, GL_COMPILE);
  telescope_draw();
  glEndList();
}

static void telescope_draw()
{
  glLoadName(name_telescope);
  glEnable(GL_COLOR_MATERIAL);
  glDisable(GL_CULL_FACE);
  glClear(GL_DEPTH_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);
  telescope.draw_body();
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glDisable(GL_COLOR_MATERIAL);
}

static void fog_read_image()
{
  glPushMatrix();
  glLoadIdentity();

  /* This creates an alpha gradient across the image */
/*  glColorMask(0, 0, 0, 1);
  glBegin(GL_QUADS);
  glColor4f(1, 1, 1, 1);
  glVertex2f(-1, -1);
  glVertex2f(1, -1);
  glColor4f(1, 1, 1, 0);
  glVertex2f(1, 1);
  glVertex2f(-1, 1);
  glEnd();
  glColorMask(1, 1, 1, 1);
*/
  glDrawBuffer(GL_BACK);
  glReadBuffer(GL_BACK);
  glAccum(GL_LOAD, 1);

  glPopMatrix();
}

static void fog_write_image()
{
  glDrawBuffer(GL_BACK);

  /* Put this back in once we're done testing */
//  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glAccum(GL_RETURN, 1);
  glDisable(GL_BLEND);
}


SCENE.H

#ifndef SCENE_H
#define SCENE_H

#ifndef SCENE_EXTERN
#define SCENE_EXTERN extern
#endif

#include "TimeDate.h"
#include "atmosphe.h"

void scene_init();

void scene_set_time(TimeDate t);
void scene_inc_time(TimeDate t);

void scene_render();

void scene_viewer_center();
/* This rotates about the origin in the world coordinate system */
void scene_viewer_rotate_worldz(GLfloat degrees);
/* This rotates about the z axis */
void scene_viewer_rotatez(GLfloat degrees);
void scene_viewer_rotatex(GLfloat degrees);
/* This translates in y */
void scene_viewer_translate(GLfloat dist);

void scene_position_telescope(GLfloat x, GLfloat y);
void scene_get_position_telescope(GLfloat *x, GLfloat *y);
void scene_get_radius_telescope(GLfloat *r);

void scene_set_weather(Weather w);

const GLint name_background = 0;
const GLint name_ground = 1;
const GLint name_trees = 2;
const GLint name_ring = 3;
const GLint name_ellipse = 4;
const GLint name_telescope = 5;

extern int draw_ground;
extern int draw_trees;
extern int draw_ring;
extern int draw_ellipse;
extern int draw_shadows;

extern int use_lighting;
extern int use_textures;
extern int use_normal_fog;
extern int use_fancy_fog;
extern int use_telescope;
extern int use_antialias;

extern GLfloat fov, aspect;

const char texfile_ground[] = DATADIR "grass.rgb";
const char texfile_trees[] = DATADIR "treewall.rgb";
const char texfile_stones[] = DATADIR "marble.rgb";
const char texfile_telescope[] = DATADIR "cv.rgb";

extern Weather weather;

#undef SCENE_EXTERN
#endif


SETPIXEL.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/gl.h>
#include "stonehen.h"
#include "setpixel.h"

#define BLACK   PALETTERGB(0,0,0)
#define WHITE   PALETTERGB(255,255,255)
#define NUM_STATIC_COLORS   (COLOR_BTNHIGHLIGHT - COLOR_SCROLLBAR + 1)

static COLORREF gacrSave[NUM_STATIC_COLORS];

static COLORREF gacrBlackAndWhite[NUM_STATIC_COLORS] = {
    WHITE,  // COLOR_SCROLLBAR
    BLACK,  // COLOR_BACKGROUND
    BLACK,  // COLOR_ACTIVECAPTION
    WHITE,  // COLOR_INACTIVECAPTION
    WHITE,  // COLOR_MENU
    WHITE,  // COLOR_WINDOW
    BLACK,  // COLOR_WINDOWFRAME
    BLACK,  // COLOR_MENUTEXT
    BLACK,  // COLOR_WINDOWTEXT
    WHITE,  // COLOR_CAPTIONTEXT
    WHITE,  // COLOR_ACTIVEBORDER
    WHITE,  // COLOR_INACTIVEBORDER
    WHITE,  // COLOR_APPWORKSPACE
    BLACK,  // COLOR_HIGHLIGHT
    WHITE,  // COLOR_HIGHLIGHTTEXT
    WHITE,  // COLOR_BTNFACE
    BLACK,  // COLOR_BTNSHADOW
    BLACK,  // COLOR_GRAYTEXT
    BLACK,  // COLOR_BTNTEXT
    BLACK,  // COLOR_INACTIVECAPTIONTEXT
    BLACK   // COLOR_BTNHIGHLIGHT
    };
static INT gaiStaticIndex[NUM_STATIC_COLORS] = {
    COLOR_SCROLLBAR          ,
    COLOR_BACKGROUND         ,
    COLOR_ACTIVECAPTION      ,
    COLOR_INACTIVECAPTION    ,
    COLOR_MENU               ,
    COLOR_WINDOW             ,
    COLOR_WINDOWFRAME        ,
    COLOR_MENUTEXT           ,
    COLOR_WINDOWTEXT         ,
    COLOR_CAPTIONTEXT        ,
    COLOR_ACTIVEBORDER       ,
    COLOR_INACTIVEBORDER     ,
    COLOR_APPWORKSPACE       ,
    COLOR_HIGHLIGHT          ,
    COLOR_HIGHLIGHTTEXT      ,
    COLOR_BTNFACE            ,
    COLOR_BTNSHADOW          ,
    COLOR_GRAYTEXT           ,
    COLOR_BTNTEXT            ,
    COLOR_INACTIVECAPTIONTEXT,
    COLOR_BTNHIGHLIGHT
    };

HPALETTE    ghpalOld, ghPalette = (HPALETTE) 0;
BOOL        bUseStaticColors = FALSE;
BOOL        bSystemColorsInUse = FALSE;
BOOL        bStaticSaved = FALSE;

unsigned char threeto8[8] = {
    0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};

unsigned char twoto8[4] = {
    0, 0x55, 0xaa, 0xff
};

unsigned char oneto8[2] = {
    0, 255
};

unsigned char
ComponentFromIndex(UCHAR i, UINT nbits, UINT shift)
{
    unsigned char val;

    val = i >> shift;
    switch (nbits) {

    case 1:
        val &= 0x1;
        return oneto8[val];

    case 2:
        val &= 0x3;
        return twoto8[val];

    case 3:
        val &= 0x7;
        return threeto8[val];

    default:
        return 0;
    }
}

void
CreateRGBPalette(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd, *ppfd;
    LOGPALETTE *pPal;
    int n, i;

    ppfd = &pfd;
    n = GetPixelFormat(hdc);
    DescribePixelFormat(hdc, n, sizeof(PIXELFORMATDESCRIPTOR), ppfd);

    if (ppfd->dwFlags & PFD_NEED_PALETTE) {
        n = 1 << ppfd->cColorBits;
        pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
                n * sizeof(PALETTEENTRY));
        pPal->palVersion = 0x300;
        pPal->palNumEntries = n;
        for (i=0; i<n; i++) {
            pPal->palPalEntry[i].peRed =
                    ComponentFromIndex(i, ppfd->cRedBits, ppfd->cRedShift);
            pPal->palPalEntry[i].peGreen =
                    ComponentFromIndex(i, ppfd->cGreenBits, ppfd->cGreenShift);
            pPal->palPalEntry[i].peBlue =
                    ComponentFromIndex(i, ppfd->cBlueBits, ppfd->cBlueShift);
            pPal->palPalEntry[i].peFlags = 0;
        }

        if (256 == n)
        {
            if (bUseStaticColors = ppfd->dwFlags & PFD_NEED_SYSTEM_PALETTE)
            {
                // Prepare to take over the system's static colors

                for (i = 0; i < n; i++)
                    pPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
            }
        }

        ghPalette = CreatePalette(pPal);
        LocalFree(pPal);

        //ghpalOld = SelectPalette(hdc, ghPalette, FALSE);
        //n = RealizePalette(hdc);
        n = RealizePaletteNow(hdc, ghPalette, FALSE);
    }
}

BOOL bSetupPixelFormat(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd, *ppfd;
    int pixelformat;

    ppfd = &pfd;

    ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR);
    ppfd->nVersion = 1;
    ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    ppfd->dwLayerMask = PFD_MAIN_PLANE;

    ppfd->iPixelType = PFD_TYPE_RGBA;
    ppfd->cColorBits = 24;

    ppfd->cDepthBits = 16;//GLX_DEPTH_SIZE

    ppfd->cRedBits = 1;//GLX_RED_SIZE
    ppfd->cRedShift = 0;
    ppfd->cGreenBits = 1;//GLX_GREEN_SIZE
    ppfd->cGreenShift = 0;
    ppfd->cBlueBits = 1;//GLX_BLUE_SIZE
    ppfd->cBlueShift = 0;
    ppfd->cAlphaBits = 0;
    ppfd->cAlphaShift = 0;
    ppfd->cAccumBits = 0;//ACCUM NOT SUPPORTED
    ppfd->cAccumRedBits = 0; //GLX_ACCUM_RED_SIZE
    ppfd->cAccumGreenBits = 0;//GLX_ACCUM_GREEN_SIZE
    ppfd->cAccumBlueBits = 0; //GLX_ACCUM_BLUE_SIZE
    ppfd->cAccumAlphaBits = 0;//GLX_ACCUM_ALPHA_SIZE

    ppfd->cStencilBits = 4;//GLX_STENCIL_SIZE
    ppfd->cAuxBuffers = 0;
    ppfd->bReserved = 0;
    ppfd->dwVisibleMask =
    ppfd->dwDamageMask = 0;

    pixelformat = ChoosePixelFormat(hdc, ppfd);

    if ( (pixelformat = ChoosePixelFormat(hdc, ppfd)) == 0 )
    {
        MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK);
        return FALSE;
    }

    if (SetPixelFormat(hdc, pixelformat, ppfd) == FALSE)
    {
        MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);
        return FALSE;
    }

    CreateRGBPalette(hdc);

    return TRUE;
}

long RealizePaletteNow( HDC hdc, HPALETTE hPal, BOOL bForceBackground )
{
    long Result = -1;

    // If static system color usage is set, prepare to take over the
    // system palette.

    if ( bUseStaticColors )
    {
    // If foreground, take over the static colors.  If background, release
    // the static colors.
    //
    // Note: even though SaveStaticEntries is always called, it only saves
    // the static colors if we don't currently possess them.  Once saved,
    // they cannot be resaved until RestoreStaticEntries is called.  This
    // prevents a quirk in the palette messaging that may cause us to try
    // to save a copy of the B&W settings as the original settings.

        SaveStaticEntries( hdc );
        SetSystemPaletteUse( hdc, bForceBackground ? SYSPAL_STATIC
                                                  : SYSPAL_NOSTATIC );

    // Rerealize the palette.
    //
    // If set to TRUE, bForceBackground will force the palette to be realized
    // as a background palette, regardless of focus.

        if ( UnrealizeObject( hPal ) &&
                NULL != (ghpalOld = SelectPalette( hdc, hPal, bForceBackground )) )
        {
            Result = RealizePalette( hdc );
        }

    // If foreground, setup the B&W static colors.  If background, restore
    // the original static colors.

        if ( bForceBackground )
            RestoreStaticEntries( hdc );
        else
            UseStaticEntries( hdc );
    }
    else
    {
        if ( NULL != (ghpalOld = SelectPalette( hdc, hPal, FALSE )) )
            Result = RealizePalette( hdc );
    }

    return( Result );
}

static VOID SaveStaticEntries(HDC hdc)
{
    int i;

    if ( !bSystemColorsInUse )
    {
        for (i = COLOR_SCROLLBAR; i <= COLOR_BTNHIGHLIGHT; i++)
            gacrSave[i - COLOR_SCROLLBAR] = GetSysColor(i);

        bStaticSaved = TRUE;
    }
}

static VOID UseStaticEntries(HDC hdc)
{
    SetSysColors(NUM_STATIC_COLORS, gaiStaticIndex, gacrBlackAndWhite);
    bSystemColorsInUse = TRUE;

    PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);
}

static VOID RestoreStaticEntries(HDC hdc)
{
    if ( bStaticSaved )
    {
        SetSysColors(NUM_STATIC_COLORS, gaiStaticIndex, gacrSave);
        bSystemColorsInUse = FALSE;

        PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);
    }
}


SETPIXEL.H

BOOL bSetupPixelFormat(HDC);
long RealizePaletteNow( HDC, HPALETTE, BOOL );
static VOID SaveStaticEntries(HDC);
static VOID UseStaticEntries(HDC);
static VOID RestoreStaticEntries(HDC);


STONE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Stone.h"

const GLfloat root2 = 1.4142136;
const GLfloat root3 = sqrt(3.);
const GLfloat rt_half = .70710678;

inline float radians(float a) {return M_PI * a / 180.0; }

inline float sign(float a) {return (a > 0. ? 1 : (a < 0. ? -1 : 0)); }

Stone::Stone()
{
  translation.set(0, 0, 0);
  rotation = 0;
  dimensions.set(1, 1, 1);

  erosion = 0;

  points_valid = transforms_valid = 0;
}

Stone::~Stone()
{
}

Stone Stone::operator=(Stone a)
{
  int i;

  translation = a.translation;
  rotation = a.rotation;
  dimensions = a.dimensions;

  erosion = a.erosion;

  if (a.points_valid && a.transforms_valid) {
    for (i = 0; i < 24; i++) points[i] = a.points[i];
    points_valid = transforms_valid = 1;
  } else points_valid = transforms_valid = 0;

  return *this;
}

void Stone::set_dimensions(GLfloat x, GLfloat y, GLfloat z)
{
  dimensions.set(x, y, z);
  points_valid = 0;
}

void Stone::set_dimensions(Point p)
{
  dimensions = p;
  points_valid = 0;
}

void Stone::erode(float p)
{
  erosion += p;
  points_valid = 0;
}

void Stone::translate(GLfloat x, GLfloat y, GLfloat z)
{
  translation.pt[0] += x;
  translation.pt[1] += y;
  translation.pt[2] += z;
  points_valid = 0;
}

void Stone::translate(Point p)
{
  translation += p;
  points_valid = 0;
}

void Stone::rotate_self_aboutz(GLfloat angle)
{
  rotation += angle;
  points_valid = 0;
}

void Stone::draw()
{
  compute_points();

  glPushMatrix();
  glRotatef(rotation, 0, 0, 1);
  glTranslatef(translation.pt[0], translation.pt[1], translation.pt[2]);
  glScalef(dimensions.pt[0], dimensions.pt[1], dimensions.pt[2]);

  draw_faces();
  draw_edges();
  draw_corners();

  glPopMatrix();
}

void Stone::compute_points()
{
  GLfloat e = 1.0 - erosion;

  if (points_valid) return;

  points[0].set(1, e, e);
  points[1].set(e, 1, e);
  points[2].set(e, e, 1);

  points[3].set(-e, e, 1);
  points[4].set(-e, 1, e);
  points[5].set(-1, e, e);

  points[6].set(e, -e, 1);
  points[7].set(e, -1, e);
  points[8].set(1, -e, e);

  points[9 ].set(e, e, -1);
  points[10].set(e, 1, -e);
  points[11].set(1, e, -e);

  points[12].set(-1, -e, e);
  points[13].set(-e, -1, e);
  points[14].set(-e, -e, 1);

  points[15].set(1, -e, -e);
  points[16].set(e, -1, -e);
  points[17].set(e, -e, -1);

  points[18].set(-1, e, -e);
  points[19].set(-e, 1, -e);
  points[20].set(-e, e, -1);

  points[21].set(-e, -e, -1);
  points[22].set(-e, -1, -e);
  points[23].set(-1, -e, -e);

  points_valid = 1;
  transforms_valid = 0;
}

inline Point Stone::trans_rot_point(Point p, float c, float s)
{
  Point val;
  val = p + translation;
  val = val.rotate_aboutz(c, s);
  return val;
}

inline Point Stone::transform_point(Point p, float c, float s)
{
  Point val;
  val = p.scale(dimensions);
  val += translation;
  val = val.rotate_aboutz(c, s);
  return val;
}

void Stone::transform_points()
{
  float c, s;
  int i;

  if (points_valid && transforms_valid) return;
  points_valid = 0;
  compute_points();

  c = cos(radians(rotation));
  s = sin(radians(rotation));

  for (i = 0; i < 24; i++) points[i] = transform_point(points[i], c, s);

  points_valid = 0;
  transforms_valid = 1;
}

void Stone::draw_faces(int flat)
{
  draw_faces(points, flat);
}

void Stone::draw_faces(Point *p, int flat)
{
  glBegin(GL_QUADS);

  glNormal3f(1, 0, 0);
  if (flat) glTexCoord2fv(p[15].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[15].pt);
  if (flat) glTexCoord2fv(p[11].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[11].pt);
  if (flat) glTexCoord2fv(p[0].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[0].pt);
  if (flat) glTexCoord2fv(p[8].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[8].pt);

  glNormal3f(-1, 0, 0);
  if (flat) glTexCoord2fv(p[12].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[12].pt);
  if (flat) glTexCoord2fv(p[5].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[5].pt);
  if (flat) glTexCoord2fv(p[18].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[18].pt);
  if (flat) glTexCoord2fv(p[23].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[23].pt);

  glNormal3f(0, 1, 0);
  if (flat) glTexCoord2fv(p[4].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[4].pt);
  if (flat) glTexCoord2fv(p[1].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[1].pt);
  if (flat) glTexCoord2fv(p[10].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[10].pt);
  if (flat) glTexCoord2fv(p[19].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[19].pt);

  glNormal3f(0, -1, 0);
  if (flat) glTexCoord2fv(p[22].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[22].pt);
  if (flat) glTexCoord2fv(p[16].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[16].pt);
  if (flat) glTexCoord2fv(p[7].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[7].pt);
  if (flat) glTexCoord2fv(p[13].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[13].pt);

  glNormal3f(0, 0, 1);
  if (flat) glTexCoord2fv(p[14].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[14].pt);
  if (flat) glTexCoord2fv(p[6].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[6].pt);
  if (flat) glTexCoord2fv(p[2].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[2].pt);
  if (flat) glTexCoord2fv(p[3].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[3].pt);

  glNormal3f(0, 0, -1);
  if (flat) glTexCoord2fv(p[20].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[20].pt);
  if (flat) glTexCoord2fv(p[9].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[9].pt);
  if (flat) glTexCoord2fv(p[17].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[17].pt);
  if (flat) glTexCoord2fv(p[21].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[21].pt);

  glEnd();
}

void Stone::draw_edges(int flat)
{
  draw_edges(points, flat);
}

void Stone::draw_edges(Point *p, int flat)
{
  Point n1, n2;

  glBegin(GL_QUADS);

  n1.set(0, 1, 0);
  n2.set(1, 0, 0);
  draw_edge(n1, n2, p, 10, 1, 0, 11, flat);

  n1.set(1, 0, 0);
  n2.set(0, -1, 0);
  draw_edge(n1, n2, p, 15, 8, 7, 16, flat);

  n1.set(1, 0, 0);
  n2.set(0, 0, 1);
  draw_edge(n1, n2, p, 8, 0, 2, 6, flat);

  n1.set(1, 0, 0);
  n2.set(0, 0, -1);
  draw_edge(n1, n2, p, 11, 15, 17, 9, flat);

  n1.set(0, 1, 0);
  n2.set(-1, 0, 0);
  draw_edge(n1, n2, p, 4, 19, 18, 5, flat);

  n1.set(-1, 0, 0);
  n2.set(0, -1, 0);
  draw_edge(n1, n2, p, 12, 23, 22, 13, flat);

  n1.set(-1, 0, 0);
  n2.set(0, 0, 1);
  draw_edge(n1, n2, p, 5, 12, 14, 3, flat);

  n2.set(-1, 0, 0);
  n1.set(0, 0, -1);
  draw_edge(n1, n2, p, 20, 21, 23, 18, flat);

  n1.set(0, 1, 0);
  n2.set(0, 0, 1);
  draw_edge(n1, n2, p, 1, 4, 3, 2, flat);

  n1.set(0, 1, 0);
  n2.set(0, 0, -1);
  draw_edge(n1, n2, p, 19, 10, 9, 20, flat);

  n1.set(0, -1, 0);
  n2.set(0, 0, 1);
  draw_edge(n1, n2, p, 13, 7, 6, 14, flat);

  n1.set(0, -1, 0);
  n2.set(0, 0, -1);
  draw_edge(n1, n2, p, 16, 22, 21, 17, flat);

  glEnd();
}

void Stone::draw_edge(Point n1, Point n2, Point *p,
      int a, int b, int c, int d, int flat)
{
  glNormal3fv(n1.pt);
  if (flat) glTexCoord2fv(p[a].pt);
  else glTexCoord2f(0, 0);
  glVertex3fv(p[a].pt);
  if (flat) glTexCoord2fv(p[b].pt);
  else glTexCoord2f(0, 1);
  glVertex3fv(p[b].pt);
  glNormal3fv(n2.pt);
  if (flat) glTexCoord2fv(p[c].pt);
  else glTexCoord2f(1, 1);
  glVertex3fv(p[c].pt);
  if (flat) glTexCoord2fv(p[d].pt);
  else glTexCoord2f(1, 0);
  glVertex3fv(p[d].pt);
}

void Stone::draw_corners(Point *p, int flat)
{
  GLfloat e = 1.0 - erosion;

  glBegin(GL_TRIANGLES);

  if (flat) glTexCoord2fv(p[0].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(1, 0, 0);
  glVertex3fv(p[0].pt);
  if (flat) glTexCoord2fv(p[1].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, 1, 0);
  glVertex3fv(p[1].pt);
  if (flat) glTexCoord2fv(p[2].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(0, 0, 1);
  glVertex3fv(p[2].pt);

  if (flat) glTexCoord2fv(p[3].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(0, 0, 1);
  glVertex3fv(p[3].pt);
  if (flat) glTexCoord2fv(p[4].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, 1, 0);
  glVertex3fv(p[4].pt);
  if (flat) glTexCoord2fv(p[5].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(-1, 0, 0);
  glVertex3fv(p[5].pt);

  if (flat) glTexCoord2fv(p[6].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(0, 0, 1);
  glVertex3fv(p[6].pt);
  if (flat) glTexCoord2fv(p[7].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, -1, 0);
  glVertex3fv(p[7].pt);
  if (flat) glTexCoord2fv(p[8].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(1, 0, 0);
  glVertex3fv(p[8].pt);

  if (flat) glTexCoord2fv(p[9].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(0, 0, -1);
  glVertex3fv(p[9].pt);
  if (flat) glTexCoord2fv(p[10].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, 1, 0);
  glVertex3fv(p[10].pt);
  if (flat) glTexCoord2fv(p[11].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(1, 0, 0);
  glVertex3fv(p[11].pt);

  if (flat) glTexCoord2fv(p[12].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(-1, 0, 0);
  glVertex3fv(p[12].pt);
  if (flat) glTexCoord2fv(p[13].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, -1, 0);
  glVertex3fv(p[13].pt);
  if (flat) glTexCoord2fv(p[14].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(0, 0, 1);
  glVertex3fv(p[14].pt);

  if (flat) glTexCoord2fv(p[15].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(1, 0, 0);
  glVertex3fv(p[15].pt);
  if (flat) glTexCoord2fv(p[16].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, -1, 0);
  glVertex3fv(p[16].pt);
  if (flat) glTexCoord2fv(p[17].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(0, 0, -1);
  glVertex3fv(p[17].pt);

  if (flat) glTexCoord2fv(p[18].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(-1, 0, 0);
  glVertex3fv(p[18].pt);
  if (flat) glTexCoord2fv(p[19].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, 1, 0);
  glVertex3fv(p[19].pt);
  if (flat) glTexCoord2fv(p[20].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(0, 0, -1);
  glVertex3fv(p[20].pt);

  if (flat) glTexCoord2fv(p[21].pt);
  else glTexCoord2f(0, 0);
  glNormal3f(0, 0, -1);
  glVertex3fv(p[21].pt);
  if (flat) glTexCoord2fv(p[22].pt);
  else glTexCoord2f(1, 0);
  glNormal3f(0, -1, 0);
  glVertex3fv(p[22].pt);
  if (flat) glTexCoord2fv(p[23].pt);
  else glTexCoord2f(.5, 1);
  glNormal3f(-1, 0, 0);
  glVertex3fv(p[23].pt);

  glEnd();
}

void Stone::draw_shadow(Point dlight)
{
  Point p[24];
  int i;

  transform_points();
  for (i = 0; i < 24; i++) {
    p[i] = points[i];
    if (p[i].pt[2] < 0.0) p[i].pt[2] = 0.0;
    p[i] = p[i].project_direction(dlight);
  }
  draw_faces(p, 1);
  draw_edges(p, 1);
  draw_corners(p, 1);
}

void Stone::draw_shadow(Point dlight, GLfloat blur,
Color color, Color diffuse)
{
  float b = 1.0 + blur;
  float c, s;
  Point p[8], pp[8];
  Point d, n;
  Color colorp, diffusep;
  int draw[6];
  int i;

  if (blur == 0.0) {
    draw_shadow(dlight);
    return;
  }

  colorp = color;
  color.c[3] = 1;
  colorp.c[3] = 0;
  diffusep = diffuse;
  diffuse.c[3] = 1;
  diffusep.c[3] = 0;

  /* We're being slightly cowardly here and ignoring the erosion - it
   * shouldn't make a big difference */
  p[0].set(-1, -1, -1);
  pp[0] = p[0];
  p[1].set(1, -1, -1);
  pp[1] = p[1];
  p[2].set(1, 1, -1);
  pp[2] = p[2];
  p[3].set(-1, 1, -1);
  pp[3] = p[3];
  p[4].set(-1, -1, 1);
  pp[4] = p[4];
  p[5].set(1, -1, 1);
  pp[5] = p[5];
  p[6].set(1, 1, 1);
  pp[6] = p[6];
  p[7].set(-1, 1, 1);
  pp[7] = p[7];

  c = cos(radians(rotation));
  s = sin(radians(rotation));
  for (i = 0; i < 8; i++) {
    p[i] = transform_point(p[i], c, s);

    /* This is a complete and utter hack - the net effect is that
     * points which are higher are displaced further when the penumbra
     * is drawn */
    d = dimensions;
    d.pt[0] += blur * (pp[i].pt[2] + translation.pt[2]);
    d.pt[1] += blur * (pp[i].pt[2] + translation.pt[2]);
    d.pt[2] += blur * (pp[i].pt[2] + translation.pt[2]);
    pp[i] = pp[i].scale(d);
    pp[i] = trans_rot_point(pp[i], c, s);
    if (p[i].pt[2] < 0.0) p[i].pt[2] = 0.0;

    p[i] = p[i].project_direction(dlight);
    if (pp[i].pt[2] < 0.0) pp[i].pt[2] = 0.0;
    pp[i] = pp[i].project_direction(dlight);
  }

  /* Compute whether or not we should draw various parts of the shadow
   * based upon the normal direction */
  if (dlight.pt[2] > 0.0) dlight *= -1.0;
  n.set(0, 0, -1);
  n = n.rotate_aboutz(c, s);
  draw[0] = (dlight.dot(n) < 0.0);
  n.set(0, -1, 0);
  n = n.rotate_aboutz(c, s);
  draw[1] = (dlight.dot(n) < 0.0);
  n.set(1, 0, 0);
  n = n.rotate_aboutz(c, s);
  draw[2] = (dlight.dot(n) < 0.0);
  n.set(0, 1, 0);
  n = n.rotate_aboutz(c, s);
  draw[3] = (dlight.dot(n) < 0.0);
  n.set(-1, 0, 0);
  n = n.rotate_aboutz(c, s);
  draw[4] = (dlight.dot(n) < 0.0);
  n.set(0, 0, 1);
  n = n.rotate_aboutz(c, s);
  draw[5] = (dlight.dot(n) < 0.0);

  /* This part draws the "real" shadow */
  glColor4fv(color.c);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);

  glBegin(GL_QUADS);

  if (draw[0]) {
    glVertex2fv(p[0].pt);
    glVertex2fv(p[1].pt);
    glVertex2fv(p[2].pt);
    glVertex2fv(p[3].pt);
  }

  if (draw[1]) {
    glVertex2fv(p[0].pt);
    glVertex2fv(p[1].pt);
    glVertex2fv(p[5].pt);
    glVertex2fv(p[4].pt);
  }

  if (draw[2]) {
    glVertex2fv(p[1].pt);
    glVertex2fv(p[2].pt);
    glVertex2fv(p[6].pt);
    glVertex2fv(p[5].pt);
  }

  if (draw[3]) {
    glVertex2fv(p[2].pt);
    glVertex2fv(p[3].pt);
    glVertex2fv(p[7].pt);
    glVertex2fv(p[6].pt);
  }

  if (draw[4]) {
    glVertex2fv(p[3].pt);
    glVertex2fv(p[0].pt);
    glVertex2fv(p[4].pt);
    glVertex2fv(p[7].pt);
  }

  if (draw[5]) {
    glVertex2fv(p[4].pt);
    glVertex2fv(p[5].pt);
    glVertex2fv(p[6].pt);
    glVertex2fv(p[7].pt);
  }

  glEnd();

  /* This part draws the penumbra */

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glBegin(GL_QUADS);

  /* Top */
  if (draw[5]) {
    if (!draw[4]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[4].pt);
      glVertex2fv(p[5].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[5].pt);
      glVertex2fv(pp[4].pt);
    }

    if (!draw[2]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[5].pt);
      glVertex2fv(p[6].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[6].pt);
      glVertex2fv(pp[5].pt);
    }

    if (!draw[3]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[6].pt);
      glVertex2fv(p[7].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[7].pt);
      glVertex2fv(pp[6].pt);
    }

    if (!draw[4]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[7].pt);
      glVertex2fv(p[4].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[4].pt);
      glVertex2fv(pp[7].pt);
    }
  }
  /* End of Top */

  /* Sides */
  if ((draw[1] || draw[4]) && !(draw[1] && draw[4])) {
    glColor4fv(color.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
    glVertex2fv(p[0].pt);
    glVertex2fv(p[4].pt);
    glColor4fv(colorp.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
    glVertex2fv(pp[4].pt);
    glVertex2fv(pp[0].pt);
  }

  if ((draw[1] || draw[2]) && !(draw[1] && draw[2])) {
    glColor4fv(color.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
    glVertex2fv(p[1].pt);
    glVertex2fv(p[5].pt);
    glColor4fv(colorp.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
    glVertex2fv(pp[5].pt);
    glVertex2fv(pp[1].pt);
  }

  if ((draw[3] || draw[2]) && !(draw[3] && draw[2])) {
    glColor4fv(color.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
    glVertex2fv(p[2].pt);
    glVertex2fv(p[6].pt);
    glColor4fv(colorp.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
    glVertex2fv(pp[6].pt);
    glVertex2fv(pp[2].pt);
  }

  if ((draw[3] || draw[4]) && !(draw[3] && draw[4])) {
    glColor4fv(color.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
    glVertex2fv(p[3].pt);
    glVertex2fv(p[7].pt);
    glColor4fv(colorp.c);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
    glVertex2fv(pp[7].pt);
    glVertex2fv(pp[3].pt);
  }
  /* End of Sides */

  /* Bottom */
  if (draw[0]) {
    if (!draw[1]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, diffuse.c);
      glVertex2fv(p[0].pt);
      glVertex2fv(p[1].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, diffusep.c);
      glVertex2fv(pp[1].pt);
      glVertex2fv(pp[0].pt);
    }

    if (!draw[2]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[1].pt);
      glVertex2fv(p[2].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[2].pt);
      glVertex2fv(pp[1].pt);
    }

    if (!draw[3]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[2].pt);
      glVertex2fv(p[3].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[3].pt);
      glVertex2fv(pp[2].pt);
    }

    if (!draw[4]) {
      glColor4fv(color.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse.c);
      glVertex2fv(p[3].pt);
      glVertex2fv(p[0].pt);
      glColor4fv(colorp.c);
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffusep.c);
      glVertex2fv(pp[0].pt);
      glVertex2fv(pp[3].pt);
    }
  }
  /* End of Bottom */

  glEnd();

  glDisable(GL_BLEND);

}


STONE.H

#ifndef STONE_H
#define STONE_H

#include "Color.h"
#include "Point.h"

class Stone {
 public:
  Stone();
  ~Stone();
  Stone operator=(Stone a);

  void set_dimensions(GLfloat x, GLfloat y, GLfloat z);
  void set_dimensions(Point p);
  Point get_dimensions() {return dimensions;};

  /* p = 0 --> sharp corners, p == 1 --> completely rounded corners */
  void erode(float p);
  float get_erosion() {return erosion;};

  void translate(GLfloat x, GLfloat y, GLfloat z);
  void translate(Point p);

  /* Angle in degrees */
  void rotate_self_aboutz(GLfloat angle);

  void draw();

  void draw_shadow(Point dlight);
  void draw_shadow(Point dlight, GLfloat blur, Color color, Color diffuse);

 private:
  Point translation;
  GLfloat rotation;
  /* dimensions contains the length, width, and height of the stone */
  Point dimensions;

  GLfloat erosion;

  Point points[24];
  int points_valid;
  void compute_points();

  int transforms_valid;
  inline Point trans_rot_point(Point p, float c, float s);
  inline Point transform_point(Point p, float c, float s);
  void transform_points();

  void draw_faces(int flat = 0);
  void draw_faces(Point *p, int flat = 0);

  void draw_edges(int flat = 0);
  void draw_edges(Point *p, int flat = 0);
  void draw_edge(Point n1, Point n2, Point *p, int a, int b, int c, int d,
 int flat = 0);

  void draw_corners(int flat = 0) {draw_corners(points, flat);};
  void draw_corners(Point *p, int flat = 0);
};

#endif


STONEHEN.CXX

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#include "glos.h"

#include <GL/gl.h>
#include <GL/glu.h>

#include "stonehen.h"
#include <stdio.h>
#include <stdlib.h>

#include "atmosphe.h"
#include "scene.h"
#include "callback.h"
#include "setpixel.h"

extern int use_lighting;
extern int draw_shadows;
extern int use_normal_fog;
extern int use_textures;
extern int texture_hack;// HACK HACK - only texture map stones for now
extern int use_telescope;
extern int use_antialias;
extern int cb_demo_mode;

BOOL Con;

BOOL bTimeScale[5];
extern GLfloat time_scale;
extern HPALETTE ghPalette, ghpalOld;

GLfloat TimeScale[] = {0, 1, 10, 100, 500};

char szAppName[] = "Stonehenge";

extern TimeDate last_update;

int curr_weather;

HGLRC hRC;

int  iTimer;

/* forward declarations of helper functions in this module */
HWND   WINAPI InitializeWindow (HINSTANCE, int);
VOID   WINAPI InitializeMenu (HWND, HMENU);
LONG   WINAPI CommandHandler (HWND, UINT, LONG);
LONG   WINAPI MainWndProc (HWND, UINT, UINT, LONG);

/* entry point of this executable */
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG        msg;
    HWND       hWnd;

    /* previous instances do not exist in Win32 */
    if (hPrevInstance)
    return 0;

    if (!(hWnd = InitializeWindow (hInstance, nCmdShow)))
        return FALSE;

    /* main window message loop */
    while (TRUE) {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) break;
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        } else {
            PostMessage(hWnd, WM_PAINT, NULL, NULL);
        }
    }

    /* return success of application */
    return TRUE;
}

HWND WINAPI InitializeWindow (HINSTANCE hInstance, int nCmdShow)
    {
    WNDCLASS   wc;
    HWND       hWnd;
    RECT       rect;

    /* Register the frame class */
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon (hInstance, szAppName);
    wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = szAppName;
    wc.lpszClassName = szAppName;

    if (!RegisterClass (&wc) )
    return FALSE;

    rect.left = 100;
    rect.top  = 100;
    rect.right = 652;
    rect.bottom = 500;

    AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
    /* Create the frame */
    hWnd = CreateWindow (szAppName,
             "OpenGL Stonehenge Demo",
     WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
             rect.left,
             rect.top,
             WINDSIZEX(rect),
             WINDSIZEY(rect),
             NULL,
             NULL,
             hInstance,
             NULL);

    /* make sure window was created */
    if (!hWnd)
    return FALSE;

    InitializeMenu(hWnd, (HMENU) GetMenu(hWnd));

    SendMessage(hWnd, WM_INIT, NULL, NULL);

    /* show and update main window */
    ShowWindow (hWnd, nCmdShow);

    UpdateWindow (hWnd);

    return hWnd;
}

/* main window procedure */
LONG WINAPI MainWndProc (
    HWND    hWnd,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
    LONG    lRet = 1;

    switch (uMsg)
    {
    case WM_CREATE:
        {
    HDC    hDC;

    /* This is equivalent to the find_visual routine */
            hDC = GetDC(hWnd);
    bSetupPixelFormat(hDC);

            hRC = wglCreateContext( hDC );
            Con = wglMakeCurrent( hDC, hRC );

    ReleaseDC(hWnd, hDC);

    // iTimer = SetTimer(hWnd, 1, 1000, NULL);
}
break;

    case WM_PAINT:
        {
HDChDC;
PAINTSTRUCTps;

hDC = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);

drawWP(hWnd);

        }
break;

    case WM_INIT:
{
    scene_init();

    last_update.read_time();

    resetViewerCB(NULL, NULL, NULL);

}
break;

        // The WM_QUERYNEWPALETTE message informs a window that it is about to
        // receive input focus. In response, the window receiving focus should
        // realize its palette as a foreground palette and update its client
        // area. If the window realizes its palette, it should return TRUE;
        // otherwise, it should return FALSE.

        case WM_QUERYNEWPALETTE:
        {
            HDC     hDC;

            if(ghPalette)
            {
                hDC = GetDC(hWnd);

                // Select and realize the palette

                //ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
                RealizePaletteNow(hDC, ghPalette, FALSE);

                // Redraw the client area

                InvalidateRect(hWnd, NULL, TRUE);
                UpdateWindow(hWnd);

                if(ghpalOld)
                    SelectPalette(hDC, ghpalOld, FALSE);

                ReleaseDC(hWnd, hDC);

                return TRUE;
            }

            return FALSE;
        }

        // The WM_PALETTECHANGED message informs all windows that the window
        // with input focus has realized its logical palette, thereby changing
        // the system palette. This message allows a window without input focus
        // that uses a color palette to realize its logical palettes and update
        // its client area.
        //
        // This message is sent to all windows, including the one that changed
        // the system palette and caused this message to be sent. The wParam of
        // this message contains the handle of the window that caused the system
        // palette to change. To avoid an infinite loop, care must be taken to
        // check that the wParam of this message does not match the window's
        // handle.

        case WM_PALETTECHANGED:
        {
            HDC         hDC;

            // Before processing this message, make sure we
            // are indeed using a palette

            if (ghPalette)
            {
                // If this application did not change the palette, select
                // and realize this application's palette

                if (uParam != (WPARAM)hWnd)
                {
                    // Need the window's DC for SelectPalette/RealizePalette

                    hDC = GetDC(hWnd);

                    // Select and realize our palette

                    //ghpalOld = SelectPalette(hDC, ghPalette, FALSE);
                    RealizePaletteNow(hDC, ghPalette, FALSE);

                    // WHen updating the colors for an inactive window,
                    // UpdateColors can be called because it is faster than
                    // redrawing the client area (even though the results are
                    // not as good)

                    UpdateColors(hDC);

                    // Clean up

                    if (ghpalOld)
                       SelectPalette(hDC, ghpalOld, FALSE);

                    ReleaseDC(hWnd, hDC);
                }
            }
            break;
        }

    case WM_SIZE:
        {

 resizeCB(hWnd, NULL, NULL);

        }
        break;

    case WM_COMMAND:
        /* handle all command messages in a localized function */
         lRet = CommandHandler (hWnd, uParam, lParam);
        break;

    case WM_LBUTTONDOWN:
{
GLwDrawingAreaCallbackStruct *call_data;

call_data = (GLwDrawingAreaCallbackStruct *) LocalAlloc(0, sizeof(GLwDrawingAreaCallbackStruct));
call_data->event = (XEvent *) LocalAlloc(0, sizeof(XEvent));

call_data->event->type = ButtonPress;
call_data->event->xbutton.button = Button1;
call_data->event->xbutton.x = LOWORD(lParam);
call_data->event->xbutton.y = HIWORD(lParam);

inputCB(hWnd, NULL, (LPVOID)call_data);
LocalFree(call_data->event);
LocalFree(call_data);
}
break;

    case WM_RBUTTONDOWN:
{
GLwDrawingAreaCallbackStruct *call_data;

call_data = (GLwDrawingAreaCallbackStruct *) LocalAlloc(0, sizeof(GLwDrawingAreaCallbackStruct));
call_data->event = (XEvent *) LocalAlloc(0, sizeof(XEvent));

call_data->event->type = ButtonPress;
call_data->event->xbutton.button = Button2;
call_data->event->xbutton.x = LOWORD(lParam);
call_data->event->xbutton.y = HIWORD(lParam);

inputCB(hWnd, NULL, (LPVOID)call_data);
LocalFree(call_data->event);
LocalFree(call_data);
}
break;

    case WM_LBUTTONUP:
{
GLwDrawingAreaCallbackStruct *call_data;

call_data = (GLwDrawingAreaCallbackStruct *) LocalAlloc(0, sizeof(GLwDrawingAreaCallbackStruct));
call_data->event = (XEvent *) LocalAlloc(0, sizeof(XEvent));

call_data->event->type = ButtonRelease;
call_data->event->xbutton.button = Button1;

inputCB(hWnd, NULL, (LPVOID)call_data);
LocalFree(call_data->event);
LocalFree(call_data);
}
break;

    case WM_RBUTTONUP:
{
GLwDrawingAreaCallbackStruct *call_data;

call_data = (GLwDrawingAreaCallbackStruct *) LocalAlloc(0, sizeof(GLwDrawingAreaCallbackStruct));
call_data->event = (XEvent *) LocalAlloc(0, sizeof(XEvent));

call_data->event->type = ButtonRelease;
call_data->event->xbutton.button = Button2;

inputCB(hWnd, NULL, (LPVOID)call_data);
LocalFree(call_data->event);
LocalFree(call_data);
}
break;

    case WM_MOUSEMOVE:
{
GLwDrawingAreaCallbackStruct *call_data;

call_data = (GLwDrawingAreaCallbackStruct *) LocalAlloc(0, sizeof(GLwDrawingAreaCallbackStruct));
call_data->event = (XEvent *) LocalAlloc(0, sizeof(XEvent));

call_data->event->type = MotionNotify;
call_data->event->xmotion.x = LOWORD(lParam);
call_data->event->xmotion.y = HIWORD(lParam);

inputCB(hWnd, NULL, (LPVOID)call_data);
LocalFree(call_data->event);
LocalFree(call_data);
}
break;

    case WM_CLOSE:
        {

HGLRC hRC;

if(hRC = wglGetCurrentContext())
    wglDeleteContext(hRC);

//KillTimer(iTimer);

        /* call destroy window to cleanup and go away */
        DestroyWindow (hWnd);
        }
        break;

    case WM_DESTROY:
        {
wglDeleteContext(wglGetCurrentContext());
        PostQuitMessage (0);
        }
        break;

    default:
        /* pass all unhandled messages to DefWindowProc */
        lRet = DefWindowProc (hWnd, uMsg, uParam, lParam);
        break;
    }

    /* return 1 if handled message, 0 if not */
    return lRet;
}

/* handle all WM_COMMAND messages here */
LONG WINAPI CommandHandler (
    HWND    hWnd,
    UINT    uParam,
    LONG    lParam)
{
    int    i;
    HMENU   hMenu;

    switch (LOWORD(uParam))
    {
    case IDM_EXIT:
        /* exit application */
        PostMessage (hWnd, WM_CLOSE, 0, 0L);
        break;

    case IDM_LIGHTING:
hMenu = GetMenu(hWnd);
use_lighting = !use_lighting;
CheckMenuItem(hMenu,IDM_LIGHTING,(use_lighting?MF_CHECKED:MF_UNCHECKED));
break;

    case IDM_SHADOWS:
hMenu = GetMenu(hWnd);
draw_shadows = !draw_shadows;
CheckMenuItem(hMenu,IDM_SHADOWS,(draw_shadows?MF_CHECKED:MF_UNCHECKED));
break;

    case IDM_FOG:
hMenu = GetMenu(hWnd);
use_normal_fog = !use_normal_fog;
CheckMenuItem(hMenu,IDM_FOG,(use_normal_fog?MF_CHECKED:MF_UNCHECKED));
break;

#ifdef TEXTURE
    case IDM_TEXTUREMAP:
hMenu = GetMenu(hWnd);
/* HACK HACK HACK
 * This would be right, but for now I only want to map the stones, nothing
 * else, so I have a new variable - texture_hack

use_textures = !use_textures;
CheckMenuItem(hMenu,IDM_TEXTUREMAP,(use_textures?MF_CHECKED:MF_UNCHECKED));
 */
texture_hack = !texture_hack;
CheckMenuItem(hMenu,IDM_TEXTUREMAP,(texture_hack?MF_CHECKED:MF_UNCHECKED));
break;
#endif

    case IDM_TELESCOPE:
hMenu = GetMenu(hWnd);
use_telescope = !use_telescope;
CheckMenuItem(hMenu,IDM_TELESCOPE,(use_telescope?MF_CHECKED:MF_UNCHECKED));
break;

    case IDM_ANTIALIAS:
hMenu = GetMenu(hWnd);
use_antialias = !use_antialias;
CheckMenuItem(hMenu,IDM_ANTIALIAS,(use_antialias?MF_CHECKED:MF_UNCHECKED));
break;

    case IDM_CLEAR:
    case IDM_FOGGY:
    case IDM_VERYFOGGY:
    case IDM_RAINY:
hMenu = GetMenu(hWnd);
/* If this value is already checked, we do nothing */
if(curr_weather != (LOWORD(uParam) - IDM_CLEAR))
{
    CheckMenuItem(hMenu, curr_weather + IDM_CLEAR, MF_UNCHECKED);

    CheckMenuItem(hMenu, LOWORD(uParam), MF_CHECKED);
    curr_weather = LOWORD(uParam) - IDM_CLEAR;
    scene_set_weather(weathers[curr_weather]);
}

break;

    case IDM_CURRENTTIME:
currentTimeCB(hWnd);
        checkTimeMenuItem(GetMenu(hWnd), LOWORD(uParam));
break;

    case IDM_10AM:
time10amCB(hWnd);
        checkTimeMenuItem(GetMenu(hWnd), LOWORD(uParam));
break;

    case IDM_NOON:
time12pmCB(hWnd);
        checkTimeMenuItem(GetMenu(hWnd), LOWORD(uParam));
break;

    case IDM_4PM:
time4pmCB(hWnd);
        checkTimeMenuItem(GetMenu(hWnd), LOWORD(uParam));
break;

    case IDM_HOLDTIME:
    case IDM_SCALE1:
    case IDM_SCALE10:
    case IDM_SCALE100:
    case IDM_SCALE500:
hMenu = GetMenu(hWnd);
/* If this value is already checked, we do nothing */
if(!bTimeScale[LOWORD(uParam) - IDM_HOLDTIME])
{
    for(i=0;!bTimeScale[i];i++);

    time_scale = TimeScale[LOWORD(uParam) - IDM_HOLDTIME];

    CheckMenuItem(hMenu, IDM_HOLDTIME + i, MF_UNCHECKED);
    bTimeScale[i] = FALSE;

    CheckMenuItem(hMenu, LOWORD(uParam), MF_CHECKED);
    bTimeScale[LOWORD(uParam) - IDM_HOLDTIME] = TRUE;

    timeSpeedCB(hWnd, NULL, NULL);
}
break;

    case IDM_DEMOMODE:
hMenu = GetMenu(hWnd);

cb_demo_mode = !cb_demo_mode;
CheckMenuItem(hMenu,IDM_DEMOMODE,(cb_demo_mode?MF_CHECKED:MF_UNCHECKED));
demo_modeCB(hWnd, NULL, NULL);
break;

    case IDM_RESET:
resetViewerCB(hWnd, NULL, NULL);
break;

    default:
        return FALSE;
    }
    return TRUE;
}

VOID WINAPI InitializeMenu (HWND hWnd, HMENU hMenu)
{

    curr_weather = def_weather_index;

    CheckMenuItem(hMenu, IDM_CLEAR + curr_weather, MF_CHECKED);

    CheckMenuItem(hMenu, IDM_LIGHTING, MF_CHECKED);

    bTimeScale[0] = TRUE;
    bTimeScale[1] = FALSE;
    bTimeScale[2] = FALSE;
    bTimeScale[3] = FALSE;
    bTimeScale[4] = FALSE;

    CheckMenuItem(hMenu, IDM_4PM, MF_CHECKED);

    CheckMenuItem(hMenu, IDM_HOLDTIME, MF_CHECKED);

    cb_demo_mode = FALSE;
}


STONEHEN.H

#include "stonerc.h"

#pragma warning (disable : 4244)

#define M_PI 3.1415926

typedef HWND Widget;
typedef VOID *XtPointer;
typedef BOOL Boolean;
typedef UINT Dimension;

#define WINDSIZEX(Rect)   (Rect.right  - Rect.left)
#define WINDSIZEY(Rect)   (Rect.bottom - Rect.top)

#define WM_INITWM_USER

typedef struct
{
    intbutton;
    int x;
    int y;
}XButton;

typedef struct
{
    intx;
    inty;
}XMotion;

typedef struct
{
    int    type;
    XButton xbutton;
    XMotion xmotion;
} XEvent;

typedef struct
{
    int     reason;
    XEvent  *event;
    Dimension width, height;/* for resize callback */
} GLwDrawingAreaCallbackStruct;

/*
** RGB Image Structure
*/

typedef struct _RGBImageRec {
    GLint sizeX, sizeY;
    unsigned char *data;
} RGBImageRec;

#define Button11
#define Button22

#define ButtonPress1
#define ButtonRelease2
#defineMotionNotify3

RGBImageRec *RGBImageLoad(char *);

#define TK_RGBImageRec RGBImageRec
#define tkRGBImageLoad RGBImageLoad


STONERC.H

#define IDM_EXIT100
#define IDM_LIGHTING200
#define IDM_SHADOWS201
#define IDM_FOG202
#define IDM_TEXTUREMAP203
#define IDM_TELESCOPE204
#define IDM_ANTIALIAS205
#define IDM_CLEAR300
#define IDM_FOGGY301
#define IDM_VERYFOGGY302
#define IDM_RAINY303
#define IDM_CURRENTTIME400
#define IDM_10AM401
#define IDM_NOON402
#define IDM_4PM403
#define IDM_HOLDTIME410
#define IDM_SCALE1411
#define IDM_SCALE10412
#define IDM_SCALE100413
#define IDM_SCALE500414
#define IDM_DEMOMODE500
#define IDM_RESET501


TELESCOP.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#include <math.h>
#include <stdio.h>

#ifdef WIN32
#include "stonehen.h"
#endif

#include "Color.h"
#include "Telescop.h"

inline float radians(float a) {return M_PI * a / 180.0;}

Telescope::Telescope(GLfloat x, GLfloat y)
{
  xpos = x;
  ypos = y;

  divisions = 20;
  radius = .1;

  disk = gluNewQuadric();
  cylinder = gluNewQuadric();
  gluQuadricNormals(disk, GLU_FLAT);
  gluQuadricNormals(cylinder, GLU_SMOOTH);
  gluQuadricTexture(disk, GL_TRUE);
  gluQuadricTexture(cylinder, GL_TRUE);

}

Telescope::~Telescope()
{
  gluDeleteQuadric(disk);
  gluDeleteQuadric(cylinder);
}

void Telescope::draw_setup(GLfloat fov, GLfloat aspect, int perspective)
{
  GLfloat near_plane;

  /* Worry about the aspect ratio later */
  near_plane = .5 / tan(radians(fov / 2.0));

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  if (perspective) gluPerspective(fov, aspect, near_plane - .01, 10.0);
  /*else
    fprintf(stderr,
    "Warning: Drawing telescope using orthographic projection.\n");
  gluLookAt(0, 0, -near_plane,
    0, 0, 1,
    0, 1, 0);*/

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glTranslatef(xpos, ypos, 0);
}

void Telescope::draw_fake()
{
  glBegin(GL_QUADS);
  glColor3f(1, 1, 1);
  glVertex3f(-radius, -radius, .01);
  glVertex3f(radius, -radius, .01);
  glVertex3f(radius, radius, .01);
  glVertex3f(-radius, radius, .01);
  glEnd();
}

void Telescope::draw_body()
{
  Color c;

  glPushMatrix();

  glColor3f(1, 1, 0);
  gluDisk(disk, radius, 1.1 * radius, divisions, 1);
  gluCylinder(cylinder, 1.1 * radius, 1.1 * radius, .1 * radius, divisions, 1);
  glTranslatef(0, 0, .1*radius);
  gluDisk(disk, radius, 1.1 * radius, divisions, 1);

  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_TEXTURE_2D);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white.c);
  glColor3f(0, 0, 0);
  gluCylinder(cylinder, 1.05 * radius, .95 * radius, .25, divisions, 1);
  glPopAttrib();

  /* Would just do this with a push / pop, but that seems to be broken.
   * glGetMaterialfv also seems to be broken, so we can't use that either. */
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, black.c);

  glTranslatef(0, 0, .25);
  glColor3f(1, 1, 0);
  gluDisk(disk, .95 * radius, 1.05 * radius, divisions, 1);
  gluCylinder(cylinder, 1.05 * radius, 1.05 * radius, .1 * radius,
      divisions, 1);
  glTranslatef(0, 0, .1*radius);
  gluDisk(disk, .95 * radius, 1.05 * radius, divisions, 1);

  glPopMatrix();
}

void Telescope::draw_takedown()
{
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

void Telescope::draw_lens()
{
  gluDisk(disk, 0, radius, divisions, 1);
}

void Telescope::set_divisions(int d)
{
  // Someday we'll put all the quadric stuff in display lists...
  divisions = d;
}

int Telescope::get_divisions()
{
  return divisions;
}

void Telescope::set_radius(GLfloat r)
{
  // Someday this might have to update some display lists
  radius = r;
}

GLfloat Telescope::get_radius()
{
  return radius;
}


TELESCOP.H

#ifndef TELESCOPE_H
#define TELESCOPE_H

class Telescope {
 public:
  Telescope(GLfloat x = 0, GLfloat y = 0);
  ~Telescope();

  /* This draws the "outside" of the telescope - the fov and aspect are
   * needed since it is drawn in eye coordinates */
  void draw_setup(GLfloat fov, GLfloat aspect = 1.0, int perspective = 1);
  void draw_fake();
  void draw_body();
  void draw_takedown();

  /* This just draws the lens - usually it will be used to draw the lens
   * into the stencil buffer */
  void draw_lens();

  /* How finally to divide things as we're drawing */
  void set_divisions(int d);
  int get_divisions();

  /* This is the radius of the lens - the rest of the dimensions depend
   * upon it */
  void set_radius(GLfloat r);
  GLfloat get_radius();

  /* Positions are in eye coordinates and go from [0, 1] */
  GLfloat xpos, ypos;
 private:
  int divisions;

  GLfloat radius;

  GLUquadricObj *disk;
  GLUquadricObj *cylinder;
};

#endif


TIMEDATE.CXX

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1996 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <GL/glu.h>

#ifdef X11
#include <GL/glx.h>
#endif

#ifdef WIN32
#include "stonehen.h"
#endif

#include "TimeDate.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef X11
#include <sys/time.h>
#else
#include <time.h>
#endif

inline float sign(float a) {return a > 0.0 ? 1. : (a < 0.0 ? -1. : 0.);}

inline float degrees(float a) {return a * 180.0 / M_PI;}
inline float radians(float a) {return a * M_PI / 180.0;}

inline float arcsin(float a) {return atan2(a, sqrt( (float) 1.0-a*a));}

/* Thirty days hath September... */
const int mday[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

TimeDate::TimeDate(int hour, int minute, int second, long usecond)
{
  read_time();
  t.tm_hour = hour;
  t.tm_min = minute;
  t.tm_sec = second;
  usec = usecond;
}

TimeDate TimeDate::operator=(TimeDate a)
{
  usec = a.usec;
  t = a.t;
  return *this;
}

TimeDate TimeDate::operator+(TimeDate a)
{
  TimeDate td;
  td = *this + a.t;
  td.usec = usec + a.usec;
  return td;
}

TimeDate TimeDate::operator+(struct tm t2)
{
  TimeDate td;
  td.usec = 0;
  td.t.tm_sec = t.tm_sec + t2.tm_sec;
  td.t.tm_min = t.tm_min + t2.tm_min;
  td.t.tm_hour = t.tm_hour + t2.tm_hour;
  td.t.tm_mday = t.tm_mday + t2.tm_mday;
  td.t.tm_mon = t.tm_mon + t2.tm_mon;
  td.t.tm_year = t.tm_year + t2.tm_year;
  td.t.tm_wday = t.tm_wday + t2.tm_wday;
  td.t.tm_yday = t.tm_yday + t2.tm_yday;
  return td;
}

TimeDate TimeDate::operator+=(TimeDate a)
{
  /* This is slower than it needs to be */
  *this = *this + a.t;
  return *this;
}

TimeDate TimeDate::operator-(TimeDate a)
{
  TimeDate td;
  td.usec = usec - a.usec;
  td.t.tm_sec = t.tm_sec - a.t.tm_sec;
  td.t.tm_min = t.tm_min - a.t.tm_min;
  td.t.tm_hour = t.tm_hour - a.t.tm_hour;
  td.t.tm_mday = t.tm_mday - a.t.tm_mday;
  td.t.tm_mon = t.tm_mon - a.t.tm_mon;
  td.t.tm_year = t.tm_year - a.t.tm_year;
  td.t.tm_wday = t.tm_wday - a.t.tm_wday;
  td.t.tm_yday = t.tm_yday - a.t.tm_yday;
  return td;
}

TimeDate TimeDate::operator*(float f)
{
  TimeDate td;
  td.usec = (long)((float)usec * f);
  td.t.tm_sec = (int)((float)t.tm_sec * f);
  td.t.tm_min = (int)((float)t.tm_min * f);
  td.t.tm_hour = (int)((float)t.tm_hour * f);
  td.t.tm_mday = (int)((float)t.tm_mday * f);
  td.t.tm_mon = (int)((float)t.tm_mon * f);
  td.t.tm_year = (int)((float)t.tm_year * f);
  td.t.tm_wday = (int)((float)t.tm_wday * f);
  td.t.tm_yday = (int)((float)t.tm_yday * f);
  return td;
}

TimeDate TimeDate::read_time()
{

#ifdef X11
  struct timeval tv;
#else
  LPSYSTEMTIME lpst;
#endif

  time_t cal_time;
  struct tm *tmp;

  cal_time = time(NULL);
  tmp = localtime(&cal_time);

#ifdef X11
  gettimeofday(&tv);
  usec = tv.tv_usec;
#else
  lpst = (LPSYSTEMTIME) malloc(sizeof(SYSTEMTIME));
  GetSystemTime(lpst);
  usec = 1000 * lpst->wMilliseconds;
#endif

  t = *tmp;
  return *this;
}

void TimeDate::print()
{
  puts(asctime(&t));
}

/* The sun_direction routine comes from an awk program by Stuart Levy
 * (slevy@geom.umn.edu) */

/* Day number of March 21st, roughly the vernal equinox */
const float equinox = mday[0] + mday[1] + 21;

/*
 *        Date     E.T. [degrees]
 *        Jan 1    0.82
 *        Jan 31   3.35
 *        Mar 1    3.08
 *        Mar 31   1.02
 *        Apr 30  -0.71
 *        May 30  -0.62
 *        Jun 29   0.87
 *        Jul 29   1.60
 *        Aug 28   0.28
 *        Sep 27  -2.28
 *        Oct 27  -4.03
 *        Nov 26  -3.15
 *        Dec 26   0.19
 */
const float et[] = {
  .82, 3.35, 3.08, 1.02, -.71, -.62, .87,
  1.60, .28, -2.28, -4.03, -3.15, .19, 2.02};

const float DEG = 180. / M_PI;

Point TimeDate::sun_direction(float lon, float lat)
{
  float yearday, eti, etoffset, ET, HA, LON, decl, altitude, azimuth;
  int etindex;
  Point dir;
  long tmp_usec;
  int m;

  tmp_usec = usec;
  usec = 0;
  *this = correct_smaller();

  /*
   *   hour angle of the sun (time since local noon):
   *       HA = (time - noon) * 15 degrees/hour
   *          + (observer''s east longitude - long. of time zone''s
   *       central meridian
   *                           POSITIVE east, NEGATIVE west)
   *          - (15 degrees if time above was daylight savings time, else 0)
   *          - "equation of time" (depends on date, correcting the
   *   hour angle for
   *               the earth''s elliptical orbit and the slope of the ecliptic)
   */
  yearday = (float)t.tm_mday +
    ((float)t.tm_hour + (float)t.tm_min / 60.0) / 24.0;
  for (m = 0; m < t.tm_mon; m++) yearday += mday[m];

  /*
   * Index into equation-of-time table, tabulated at 30-day intervals
   * We''ve added an extra entry at the end of the et table, corresponding
   * to January 25th of the following year, to make interpolation work
   * throughout the year.
   * Use simple linear interpolation.
   */
  eti = (yearday - 1.) / 30.;
#ifdef X11
  etoffset = eti - trunc(eti);
  etindex = (int)trunc(eti) + 1;
#else
  etoffset = eti - (int)eti;
  etindex = (int)eti + 1;
#endif
  ET = et[etindex - 1] + etoffset*(et[etindex+1 - 1] - et[etindex - 1]);
  /* The 90. puts us in the Central time zone */
  HA = ((float)t.tm_hour + (float)t.tm_min/60. - 12.)*15. +
    lon + 90. - ET;

  /*
   *    Sun''s declination:
   *        sun''s longitude = (date - March 21) * 360 degrees / 365.25 days
   *                [This ignores the earth''s elliptical orbit...]
   */
  LON = (yearday - equinox) * 360. / 365.25;
  decl = DEG * arcsin( sin(LON/DEG) * sin(23.4/DEG) );

  /*
   *    Then a spherical triangle calculation to convert the Sun''s
   *    hour angle and declination, and the observer''s latitude,
   *    to give the Sun''s altitude and azimuth (angle east from north).
   */
  altitude = DEG * arcsin( sin(decl/DEG)*sin(lat/DEG)
  + cos(decl/DEG)*cos(lat/DEG)*cos(HA/DEG) );
  azimuth = DEG * atan2( -cos(decl/DEG)*sin(HA/DEG),
sin(decl/DEG)*cos(lat/DEG)
- cos(decl/DEG)*cos(HA/DEG)*sin(lat/DEG) );

  /*
  printf("On %d/%d at %d:%02d, lat %g, lon %g\n",
 t.tm_mon + 1, t.tm_mday + 1, t.tm_hour, t.tm_min, lat, lon);
  printf("HA %.1f ET %.1f Sun lon %.1f decl %.1f alt %.1f az %.1f\n",
 HA,ET,LON,decl,altitude,azimuth);
  */

  dir.pt[2] = sin(radians(altitude));
  dir.pt[0] = cos(radians(azimuth));
  dir.pt[1] = sin(radians(azimuth));
  dir.pt[3] = 0;
  dir.unitize();
  // dir.print();

  usec = tmp_usec;

  return dir;
}

TimeDate TimeDate::correct_bigger()
{
  TimeDate td;
  int days;

  td.t = t;
  td.usec = usec;

  td.t.tm_sec += (int)(td.usec / 1000000);
  td.usec %= 1000000;
  td.t.tm_min += td.t.tm_sec / 60;
  td.t.tm_sec %= 60;
  td.t.tm_hour += td.t.tm_min / 60;
  td.t.tm_min %= 60;

  if (td.t.tm_hour > 23) {
    days = td.t.tm_hour / 24;
    td.t.tm_hour %= 24;
    td.t.tm_mday += days;
    td.t.tm_wday = (td.t.tm_wday + days) % 7;
    td.t.tm_yday = (td.t.tm_yday + days) % 365;
    while (td.t.tm_mday > mday[td.t.tm_mon]) {
      td.t.tm_mday -= mday[td.t.tm_mon++];
      if (td.t.tm_mon >= 12) {
td.t.tm_year += td.t.tm_mon / 12;
td.t.tm_mon %= 12;
      }
    }
  }
  return td;
}

TimeDate TimeDate::correct_smaller()
{
  TimeDate td;

  td.t = t;
  td.usec = usec;

  while (td.usec < 0) {
    td.t.tm_sec--;
    td.usec += 1000000;
  }
  while (td.t.tm_sec < 0) {
    td.t.tm_min--;
    td.t.tm_sec += 60;
  }
  while (td.t.tm_min < 0) {
    td.t.tm_hour--;
    td.t.tm_min += 60;
  }
  while (td.t.tm_hour < 0) {
    td.t.tm_mday--;
    if (td.t.tm_wday) td.t.tm_wday--;
    else td.t.tm_wday = 6;
    td.t.tm_yday--;
    td.t.tm_hour += 24;
  }
  if (td.t.tm_mon < 0 || td.t.tm_year < 0 || td.t.tm_yday < 0)  {
    //fprintf(stderr, "Warning:  day < 0 in TimeDate.c++.\n");
    td.t.tm_mon = td.t.tm_year = td.t.tm_yday = 0;
  }
  return td.correct_bigger();
}


TIMEDATE.H

#ifndef TIMEDATE_H
#define TIMEDATE_H

#include "Point.h"

#include <time.h>

class TimeDate {
 public:
  TimeDate() {};
  TimeDate(int hour, int minute = 0, int second = 0, long usecond = 0);
  TimeDate(struct tm t2) {t = t2;};

  TimeDate operator=(TimeDate a);
  TimeDate operator+(TimeDate a);
  TimeDate operator+(struct tm t2);
  TimeDate operator+=(TimeDate a);
  TimeDate operator-(TimeDate a);
  TimeDate operator*(float f);

  /* This reads the current clock time and returns itself */
  TimeDate read_time();

  void print();

  Point sun_direction(float lon = -93, float lat = 45);
 private:
  /* Call this if the time can only have gotten bigger */
  TimeDate correct_bigger();
  /* Call this if the time could have gotten bigger or smaller */
  TimeDate correct_smaller();

  struct tm t;
  /* This won't be quite accurate down to microseconds, but should be
   * reasonably close */
  long usec;
};

#endif


README.TXT

Using Vertex Arrays for Improved Performance

The VASPHERE sample demonstrates the use of vertex-arrays, available
in OpenGL 1.1. A significant performance improvement can be gained
if objects in the scene are drawn using vertex-arrays instead of
other primitives. In this sample, a lit revolving sphere is drawn
using one of the following methods

 - Vertex-arrays
 - Triangles
 - Triangle-Strips

In addition, either immediate mode or display-list mode are used. Cycle
through the three primitives by pressing the space-bar, and toggle between
immediate-mode and display-list mode by pressing the 'd' key.
The window title shows the triangle/sec figure.

Note
----

VASPHERE will not run on an implementation of OpenGL version 1.0. The OpenGL
for Windows 95 redistributables included on the Win32 SDK are an
implementation of version 1.0.


VASPHERE.C

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>

#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>

#define PI ((float)3.14159265358979323846)

#define WIDTH 512
#define HEIGHT 512

#define COMPUTE_SPHERE_VERTICES(rings, sections) \
    (((rings)+1)*(sections)+2)
#define COMPUTE_SPHERE_TRIANGLES(rings, sections) \
    (((rings)+1)*(sections)*2)
#define RINGS 50
#define SECTIONS 50
#define SVERTICES COMPUTE_SPHERE_VERTICES(RINGS, SECTIONS)
#define STRIANGLES COMPUTE_SPHERE_TRIANGLES(RINGS, SECTIONS)

#define RGB_COLOR(red, green, blue) \
    (((DWORD)(BYTE)(red) << 0) | \
     ((DWORD)(BYTE)(green) << 8) | \
     ((DWORD)(BYTE)(blue) << 16))

#define FRANDOM(x) (((float)rand() / RAND_MAX) * (x))

#define DROT 10.0f

#define NLISTS 3

typedef struct
{
    float fX, fY, fZ;
    float fNx, fNy, fNz;
    DWORD dwColor;
} VERTEX;

typedef struct
{
    int iV1;
    int iV2;
    int iV3;
} TRIANGLE;

typedef struct _SPHERE
{
    GLfloat fXr, fYr, fZr;
    GLfloat fDXr, fDYr, fDZr;
} SPHERE;

SPHERE sphere;

BOOL fSingle = FALSE;
BOOL fDisplayList = TRUE;

int iCurrentType = 0;
GLint dlLists[NLISTS];
char *pszListType[NLISTS] =
{
    "Vertex Array", "Direct Triangles", "Direct Strips"
};

void DrawVertexArray(void);
void DrawTriangles(void);
void DrawStrips(void);
void (*pfnDraw[NLISTS])(void) =
{
    DrawVertexArray, DrawTriangles, DrawStrips
};

VERTEX vtxSphere[SVERTICES];
TRIANGLE triSphere[STRIANGLES];

void *Malloc(size_t cb)
{
    void *pv;

    pv = malloc(cb);
    if (pv == NULL)
    {
        printf("Unable to alloc %d bytes\n", cb);
        exit(1);
    }
    return pv;
}

void GenerateSphere(float fRadius, int iRings, int iSections,
                    VERTEX *pvtx, TRIANGLE *ptri)
{
    float fTheta, fPhi;             /* Angles used to sweep around sphere */
    float fDTheta, fDPhi;           /* Angle between each section and ring */
    float fX, fY, fZ, fV, fRSinTheta;  /* Temporary variables */
    int   i, j, n, m;               /* counters */
    int   iVert, iTri;              /* Internal vertex and triangle count */

    iTri = COMPUTE_SPHERE_TRIANGLES(iRings, iSections);
    iVert = COMPUTE_SPHERE_VERTICES(iRings, iSections);

    /*
     * Generate vertices at the top and bottom points.
     */
    pvtx[0].fX = 0.0f;
    pvtx[0].fY = fRadius;
    pvtx[0].fZ = 0.0f;
    pvtx[0].fNx = 0.0f;
    pvtx[0].fNy = 1.0f;
    pvtx[0].fNz = 0.0f;
    pvtx[0].dwColor = RGB_COLOR(0, 0, 255);
    pvtx[iVert - 1].fX = 0.0f;
    pvtx[iVert - 1].fY = -fRadius;
    pvtx[iVert - 1].fZ = 0.0f;
    pvtx[iVert - 1].fNx = 0.0f;
    pvtx[iVert - 1].fNy = -1.0f;
    pvtx[iVert - 1].fNz = 0.0f;
    pvtx[iVert - 1].dwColor = RGB_COLOR(0, 255, 0);

    /*
     * Generate vertex points for rings
     */
    fDTheta = PI / (float) (iRings + 2);
    fDPhi = 2.0f * PI / (float) iSections;
    n = 1; /* vertex being generated, begins at 1 to skip top point */
    fTheta = fDTheta;

    for (i = 0; i <= iRings; i++)
    {
        fY = (float)(fRadius * cos(fTheta)); /* y is the same for each ring */
        fV = fTheta / PI; /* v is the same for each ring */
        fRSinTheta = (float)(fRadius * sin(fTheta));
        fPhi = 0.0f;

        for (j = 0; j < iSections; j++)
        {
            fX = (float)(fRSinTheta * sin(fPhi));
            fZ = (float)(fRSinTheta * cos(fPhi));
            pvtx[n].fX = fX;
            pvtx[n].fZ = fZ;
            pvtx[n].fY = fY;
            pvtx[n].fNx = fX / fRadius;
            pvtx[n].fNy = fY / fRadius;
            pvtx[n].fNz = fZ / fRadius;
            if (n & 1)
            {
                pvtx[n].dwColor = RGB_COLOR(0, 0, 255);
            }
            else
            {
                pvtx[n].dwColor = RGB_COLOR(0, 255, 0);
            }
            fPhi += fDPhi;
            n++;
        }

        fTheta += fDTheta;
    }

    /*
     * Generate triangles for top and bottom caps.
     */
    for (i = 0; i < iSections; i++)
    {
        ptri[i].iV1 = 0;
        ptri[i].iV2 = i + 1;
        ptri[i].iV3 = 1 + ((i + 1) % iSections);
        ptri[iTri - iSections + i].iV1 = iVert - 1;
        ptri[iTri - iSections + i].iV2 = iVert - 2 - i;
        ptri[iTri - iSections + i].iV3 = iVert - 2 - ((1 + i) % iSections);
    }

    /*
     * Generate triangles for the rings
     */
    m = 1; /* first vertex in current ring,begins at 1 to skip top point*/
    n = iSections; /* triangle being generated, skip the top cap */

    for (i = 0; i < iRings; i++)
    {
        for (j = 0; j < iSections; j++)
        {
            ptri[n].iV1 = m + j;
            ptri[n].iV2 = m + iSections + j;
            ptri[n].iV3 = m + iSections + ((j + 1) % iSections);
            ptri[n + 1].iV1 = ptri[n].iV1;
            ptri[n + 1].iV2 = ptri[n].iV3;
            ptri[n + 1].iV3 = m + ((j + 1) % iSections);
            n += 2;
        }

        m += iSections;
    }
}

void InitSphere(void)
{
    GenerateSphere(1.0f, RINGS, SECTIONS, vtxSphere, triSphere);

    glVertexPointer(3, GL_FLOAT, sizeof(VERTEX), &vtxSphere[0].fX);
    glNormalPointer(GL_FLOAT, sizeof(VERTEX), &vtxSphere[0].fNx);
    glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(VERTEX), &vtxSphere[0].dwColor);

    srand(time(NULL));
    sphere.fXr = 0.0f;
    sphere.fYr = 0.0f;
    sphere.fZr = 0.0f;
    sphere.fDXr = DROT - FRANDOM(2 * DROT);
    sphere.fDYr = DROT - FRANDOM(2 * DROT);
    sphere.fDZr = DROT - FRANDOM(2 * DROT);
}

void UpdateSphere(void)
{
    sphere.fXr += sphere.fDXr;
    sphere.fYr += sphere.fDYr;
    sphere.fZr += sphere.fDZr;
}

void DrawVertexArray(void)
{
    glDrawElements(GL_TRIANGLES, STRIANGLES*3, GL_UNSIGNED_INT, triSphere);
}

void Vertex(int iVert)
{
    VERTEX *pvtx;

    pvtx = &vtxSphere[iVert];
    glColor3ubv((GLubyte *)&pvtx->dwColor);
    glNormal3fv(&pvtx->fNx);
    glVertex3fv(&pvtx->fX);
}

void DrawTriangles(void)
{
    int iVert, *pidx;

    glBegin(GL_TRIANGLES);
    pidx = (int *)&triSphere[0];
    for (iVert = 0; iVert < STRIANGLES*3; iVert++)
    {
        Vertex(*pidx++);
    }
    glEnd();
}

void DrawStrips(void)
{
    int iIdxBase;
    int iRing, iSection;

    // Triangle fans for top and bottom caps
    glBegin(GL_TRIANGLE_FAN);

    Vertex(0);
    iIdxBase = 1;
    for (iSection = 0; iSection <= SECTIONS; iSection++)
    {
        Vertex(iIdxBase+(iSection % SECTIONS));
    }

    glEnd();

    glBegin(GL_TRIANGLE_FAN);

    Vertex(SVERTICES-1);
    iIdxBase = SVERTICES-SECTIONS-1;
    for (iSection = SECTIONS; iSection >= 0 ; iSection--)
    {
        Vertex(iIdxBase+(iSection % SECTIONS));
    }

    glEnd();

    // Triangle strips for each ring
    iIdxBase = 1;
    for (iRing = 0; iRing < RINGS; iRing++)
    {
        glBegin(GL_TRIANGLE_STRIP);

        for (iSection = 0; iSection <= SECTIONS; iSection++)
        {
            Vertex(iIdxBase+(iSection % SECTIONS));
            Vertex(iIdxBase+(iSection % SECTIONS)+SECTIONS);
        }

        glEnd();

        iIdxBase += SECTIONS;
    }
}

void Init(void)
{
    float fv4[4];
    int iv1[1];
    int i;
    GLint dlBase;

    InitSphere();

    fv4[0] = 0.05f;
    fv4[1] = 0.05f;
    fv4[2] = 0.05f;
    fv4[3] = 1.0f;
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fv4);

    fv4[0] = 0.0f;
    fv4[1] = 1.0f;
    fv4[2] = 1.0f;
    fv4[3] = 0.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, fv4);
    fv4[0] = 0.9f;
    fv4[1] = 0.9f;
    fv4[2] = 0.9f;
    fv4[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_DIFFUSE, fv4);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

    fv4[0] = 0.6f;
    fv4[1] = 0.6f;
    fv4[2] = 0.6f;
    fv4[3] = 1.0f;
    glMaterialfv(GL_FRONT, GL_SPECULAR, fv4);
    iv1[0] = 40;
    glMaterialiv(GL_FRONT, GL_SHININESS, iv1);

    glEnable(GL_CULL_FACE);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);

    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, 1, .01, 15);
    gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
    glMatrixMode(GL_MODELVIEW);

    dlBase = glGenLists(NLISTS);
    for (i = 0; i < NLISTS; i++)
    {
        dlLists[i] = dlBase+i;
        glNewList(dlLists[i], GL_COMPILE);
        pfnDraw[i]();
        glEndList();
    }
}

DWORD dwTotalMillis = 0;
int iTotalTriangles = 0;

void ResetTotals(void)
{
    dwTotalMillis = 0;
    iTotalTriangles = 0;
}

void CALLBACK Redraw(void)
{
    DWORD dwMillis;
    char szMsg[80];

    dwMillis = GetTickCount();

    glClear(GL_COLOR_BUFFER_BIT);

    glLoadIdentity();
    glRotatef(sphere.fXr, 1.0f, 0.0f, 0.0f);
    glRotatef(sphere.fYr, 0.0f, 1.0f, 0.0f);
    glRotatef(sphere.fZr, 0.0f, 0.0f, 1.0f);

    if (fDisplayList)
    {
        glCallList(dlLists[iCurrentType]);
    }
    else
    {
        pfnDraw[iCurrentType]();
    }

    UpdateSphere();

    if (fSingle)
    {
        glFlush();
    }
    else
    {
        auxSwapBuffers();
    }

    dwMillis = GetTickCount()-dwMillis;

    dwTotalMillis += dwMillis;
    iTotalTriangles += STRIANGLES;

    if (dwTotalMillis > 1000)
    {
        sprintf(szMsg, "%s%s: %.3lf tri/sec",
                fDisplayList ? "Display List " : "",
                pszListType[iCurrentType],
                (double)iTotalTriangles*1000.0/dwTotalMillis);
        SetWindowText(auxGetHWND(), szMsg);

        ResetTotals();
    }
}

void CALLBACK Reshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
}

void CALLBACK Keyd(void)
{
    fDisplayList = !fDisplayList;
    ResetTotals();
}

void CALLBACK KeySPACE(void)
{
    iCurrentType = (iCurrentType+1) % NLISTS;
    ResetTotals();
}

void _CRTAPI1 main(int argc, char **argv)
{
    GLenum eMode;

    while (--argc > 0)
    {
        argv++;

        if (!strcmp(*argv, "-sb"))
        {
            fSingle = TRUE;
        }
    }

    auxInitPosition(10, 10, WIDTH, HEIGHT);
    eMode = AUX_RGB;
    if (!fSingle)
    {
        eMode |= AUX_DOUBLE;
    }
    auxInitDisplayMode(eMode);
    auxInitWindow("Vertex Array/Direct Comparison");

    auxReshapeFunc(Reshape);
    auxIdleFunc(Redraw);

    auxKeyFunc(AUX_d, Keyd);
    auxKeyFunc(AUX_SPACE, KeySPACE);

    Init();
    auxMainLoop(Redraw);
}


3D.H

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
voiderror(char *);
voiddiff3(GLdouble [3], GLdouble [3], GLdouble [3]);
voidadd3(GLdouble [3], GLdouble [3], GLdouble [3]);
voidscalarmult(GLdouble, GLdouble [3], GLdouble [3]);
GLdoubledot3(GLdouble [3], GLdouble [3]);
GLdoublelength3(GLdouble [3]);
GLdoubledist3(GLdouble [3], GLdouble [3]);
voidcopy3(GLdouble [3], GLdouble [3]);
voidcrossprod(GLdouble [3], GLdouble [3], GLdouble [3]);
voidnormalize(GLdouble [3]);
voidprint3(GLdouble [3]);
voidprintmat3(GLdouble [3][3]);
voididentifymat3(GLdouble [3][3]);
voidcopymat3(GLdouble *, GLdouble *);
voidxformvec3(GLdouble [3], GLdouble [3][3], GLdouble [3]);

void m_resetmatrixstack();
void m_xformpt(GLdouble [3], GLdouble [3], GLdouble [3], GLdouble [3]);
void m_xformptonly(GLdouble [3], GLdouble [3]);
void m_pushmatrix();
void m_popmatrix();
void m_shear(GLdouble, GLdouble, GLdouble);
void m_translate(GLdouble, GLdouble, GLdouble);
void m_scale(GLdouble, GLdouble, GLdouble);
void m_rotate(GLdouble, char);


FONT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <gl/glaux.h>
#include "tk.h"

#define static

static GLuint bitmapBase;
void APIENTRY auxDrawStrAW(LPCSTR str, BOOL bUnicode);

void auxCreateFont(void)
{
    bitmapBase = glGenLists(256);
    if (tkCreateBitmapFont(bitmapBase) == GL_FALSE) {
        auxQuit();
    }
}

void APIENTRY auxDrawStrA(LPCSTR str)
{
    auxDrawStrAW(str, FALSE);
}

void APIENTRY auxDrawStrW(LPCWSTR str)
{
    auxDrawStrAW((LPCSTR)str, TRUE);
}

void APIENTRY auxDrawStrAW(LPCSTR str, BOOL bUnicode)
{

    glPushAttrib(GL_LIST_BIT);
    glListBase(bitmapBase);
    if (bUnicode)
        glCallLists(wcslen((LPWSTR) str), GL_UNSIGNED_SHORT, str);
    else
glCallLists(strlen(str), GL_UNSIGNED_BYTE, str);
    glPopAttrib();
}


GLAUX.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <windows.h>
#include <GL/gl.h>
#include <gl/glaux.h>
#include "tk.h"

#define static

#if defined(__cplusplus) || defined(c_plusplus)
#define class c_class
#endif

static struct {
    int keyField;
    void (*KeyFunc)(void);
} keyTable[200];

static struct {
    int mouseField;
    void (*MouseFunc)(AUX_EVENTREC *);
} mouseDownTable[20], mouseUpTable[20], mouseLocTable[20];

static int keyTableCount = 0;
static int mouseDownTableCount = 0;
static int mouseUpTableCount = 0;
static int mouseLocTableCount = 0;
static GLenum displayModeType = 0;
GLenum APIENTRY auxInitWindowAW(LPCSTR title, BOOL bUnicode);

static void DefaultHandleReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho((GLdouble)0.0, (GLdouble)w, (GLdouble)0.0, (GLdouble)h, (GLdouble)-1.0, (GLdouble)1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void DefaultHandleExpose(int w, int h)
{
}

static GLenum MouseLoc(int x, int y, GLenum button)
{
    AUX_EVENTREC info;
    GLenum flag;
    int i;

    flag = GL_FALSE;
    for (i = 0; i < mouseLocTableCount; i++) {
        if ((int)(button & AUX_LEFTBUTTON) == mouseLocTable[i].mouseField) {
    info.event = AUX_MOUSELOC;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_LEFTBUTTON;
    (*mouseLocTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_RIGHTBUTTON) == mouseLocTable[i].mouseField) {
    info.event = AUX_MOUSELOC;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_RIGHTBUTTON;
    (*mouseLocTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_MIDDLEBUTTON) == mouseLocTable[i].mouseField) {
    info.event = AUX_MOUSELOC;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_MIDDLEBUTTON;
    (*mouseLocTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
    }
    return flag;
}

static GLenum MouseUp(int x, int y, GLenum button)
{
    AUX_EVENTREC info;
    GLenum flag;
    int i;

    flag = GL_FALSE;
    for (i = 0; i < mouseUpTableCount; i++) {
        if ((int)(button & AUX_LEFTBUTTON) == mouseUpTable[i].mouseField) {
    info.event = AUX_MOUSEUP;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_LEFTBUTTON;
    (*mouseUpTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_RIGHTBUTTON) == mouseUpTable[i].mouseField) {
    info.event = AUX_MOUSEUP;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_RIGHTBUTTON;
    (*mouseUpTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_MIDDLEBUTTON) == mouseUpTable[i].mouseField) {
    info.event = AUX_MOUSEUP;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_MIDDLEBUTTON;
    (*mouseUpTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
    }
    return flag;
}

static GLenum MouseDown(int x, int y, GLenum button)
{
    AUX_EVENTREC info;
    GLenum flag;
    int i;

    flag = GL_FALSE;
    for (i = 0; i < mouseDownTableCount; i++) {
        if ((int)(button & AUX_LEFTBUTTON) == mouseDownTable[i].mouseField) {
    info.event = AUX_MOUSEDOWN;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_LEFTBUTTON;
    (*mouseDownTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_RIGHTBUTTON) == mouseDownTable[i].mouseField) {
    info.event = AUX_MOUSEDOWN;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_RIGHTBUTTON;
    (*mouseDownTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
        if ((int)(button & AUX_MIDDLEBUTTON) == mouseDownTable[i].mouseField) {
    info.event = AUX_MOUSEDOWN;
    info.data[AUX_MOUSEX] = x;
    info.data[AUX_MOUSEY] = y;
    info.data[AUX_MOUSESTATUS] = AUX_MIDDLEBUTTON;
    (*mouseDownTable[i].MouseFunc)(&info);
    flag |= GL_TRUE;
}
    }
    return flag;
}

static GLenum KeyDown(int key, GLenum status)
{
    GLenum flag;
    int i;

    flag = GL_FALSE;
    if (keyTableCount) {
for (i = 0; i < keyTableCount; i++) {
    if (key == keyTable[i].keyField) {
(*keyTable[i].KeyFunc)();
flag |= GL_TRUE;
    }
}
    }
    return flag;
}

void auxExposeFunc(AUXEXPOSEPROC Func)
{
    tkExposeFunc(Func);
}

void auxReshapeFunc(AUXRESHAPEPROC Func)
{
    tkExposeFunc((AUXEXPOSEPROC) Func);
    tkReshapeFunc(Func);
}

void auxIdleFunc(AUXIDLEPROC Func)
{
    tkIdleFunc(Func);
}

void auxKeyFunc(int key, AUXKEYPROC Func)
{
    keyTable[keyTableCount].keyField = key;
    keyTable[keyTableCount++].KeyFunc = Func;
}

void auxMouseFunc(int mouse, int mode, AUXMOUSEPROC Func)
{
    if (mode == AUX_MOUSEDOWN) {
mouseDownTable[mouseDownTableCount].mouseField = mouse;
mouseDownTable[mouseDownTableCount++].MouseFunc = Func;
    } else if (mode == AUX_MOUSEUP) {
mouseUpTable[mouseUpTableCount].mouseField = mouse;
mouseUpTable[mouseUpTableCount++].MouseFunc = Func;
    } else if (mode == AUX_MOUSELOC) {
mouseLocTable[mouseLocTableCount].mouseField = mouse;
mouseLocTable[mouseLocTableCount++].MouseFunc = Func;
    }
}

void auxMainLoop(AUXMAINPROC Func)
{
    tkDisplayFunc(Func);
    tkExec();
}

void auxInitPosition(int x, int y, int width, int height)
{
    tkInitPosition(x, y, width, height);
}

void auxInitDisplayMode(GLenum type)
{
    displayModeType = type;
    tkInitDisplayMode(type);
}

void auxInitDisplayModePolicy(GLenum type)
{
    tkInitDisplayModePolicy(type);
}

GLenum auxInitDisplayModeID(GLint id)
{
    return tkInitDisplayModeID(id);
}

GLenum APIENTRY auxInitWindowA(LPCSTR title)
{
    return auxInitWindowAW(title,FALSE);
}

GLenum APIENTRY auxInitWindowW(LPCWSTR title)
{
    return auxInitWindowAW((LPCSTR)title,TRUE);
}

GLenum APIENTRY auxInitWindowAW(LPCSTR title, BOOL bUnicode)
{
    int useDoubleAsSingle = 0;

    if (tkInitWindowAW((char *)title, bUnicode) == GL_FALSE) {
if (AUX_WIND_IS_SINGLE(displayModeType)) {
    tkInitDisplayMode(displayModeType | AUX_DOUBLE);
    if (tkInitWindowAW((char *)title, bUnicode) == GL_FALSE) {
return GL_FALSE;    /*  curses, foiled again*/
            }
            MESSAGEBOX(GetFocus(), "Can't initialize a single buffer visual. "
                                   "Will use a double buffer visual instead, "
                                   "only drawing into the front buffer.",
                                   "Warning", MB_OK);
    displayModeType = displayModeType | AUX_DOUBLE;
    useDoubleAsSingle = 1;
}
    }
    tkReshapeFunc(DefaultHandleReshape);
    tkExposeFunc(DefaultHandleExpose);
    tkMouseUpFunc(MouseUp);
    tkMouseDownFunc(MouseDown);
    tkMouseMoveFunc(MouseLoc);
    tkKeyDownFunc(KeyDown);
    auxKeyFunc(AUX_ESCAPE, auxQuit);
    glClearColor((GLclampf)0.0, (GLclampf)0.0, (GLclampf)0.0, (GLclampf)1.0);
    glClearIndex((GLfloat)0.0);
    glLoadIdentity();
    if (useDoubleAsSingle)
glDrawBuffer(GL_FRONT);
    return GL_TRUE;
}

void auxCloseWindow(void)
{
    tkCloseWindow();
    keyTableCount = 0;
    mouseDownTableCount = 0;
    mouseUpTableCount = 0;
    mouseLocTableCount = 0;
}

void auxQuit(void)
{
    tkQuit();
}

void auxSwapBuffers(void)
{
    tkSwapBuffers();
}

HWND auxGetHWND(void)
{
    return tkGetHWND();
}

HDC auxGetHDC(void)
{
    return tkGetHDC();
}

HGLRC auxGetHGLRC(void)
{
    return tkGetHRC();
}

GLenum auxGetDisplayModePolicy(void)
{
    return tkGetDisplayModePolicy();
}

GLint auxGetDisplayModeID(void)
{
    return tkGetDisplayModeID();
}

GLenum auxGetDisplayMode(void)
{
    return tkGetDisplayMode();
}

void auxSetOneColor(int index, float r, float g, float b)
{
    tkSetOneColor(index, r, g, b);
}

void auxSetFogRamp(int density, int startIndex)
{
    tkSetFogRamp(density, startIndex);
}

void auxSetGreyRamp(void)
{
    tkSetGreyRamp();
}

void auxSetRGBMap(int size, float *rgb)
{
    tkSetRGBMap(size, rgb);
}

int auxGetColorMapSize(void)
{
    return tkGetColorMapSize();
}

void auxGetMouseLoc(int *x, int *y)
{
    tkGetMouseLoc(x, y);
}


IMAGE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <gl/glaux.h>
#include "tk.h"

#define static

AUX_RGBImageRec * APIENTRY auxRGBImageLoadA(LPCSTR filename)
{
    return (AUX_RGBImageRec *)tkRGBImageLoadAW((char *)filename, FALSE);
}

AUX_RGBImageRec * APIENTRY auxRGBImageLoadW(LPCWSTR filename)
{
    return (AUX_RGBImageRec *)tkRGBImageLoadAW((char *)filename, TRUE);
}

AUX_RGBImageRec * APIENTRY auxDIBImageLoadA(LPCSTR filename)
{
    return (AUX_RGBImageRec *)tkDIBImageLoadAW((char *)filename, FALSE);
}

AUX_RGBImageRec * APIENTRY auxDIBImageLoadW(LPCWSTR filename)
{
    return (AUX_RGBImageRec *)tkDIBImageLoadAW((char *)filename, TRUE);
}


SHAPES.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <gl/glaux.h>
#include "3d.h"

#define static

#define SPHEREWIRE      0
#define CUBEWIRE        1
#define BOXWIRE         2
#define TORUSWIRE       3
#define CYLINDERWIRE    4
#define ICOSAWIRE       5
#define OCTAWIRE        6
#define TETRAWIRE       7
#define DODECAWIRE      8
#define CONEWIRE        9
#define SPHERESOLID     10
#define CUBESOLID       11
#define BOXSOLID        12
#define TORUSSOLID      13
#define CYLINDERSOLID   14
#define ICOSASOLID      15
#define OCTASOLID       16
#define TETRASOLID      17
#define DODECASOLID     18
#define CONESOLID       19

#define PI ((GLdouble)3.1415926535897)

/*      structure for each geometric object     */
typedef struct model {
    GLuint list;        /*  display list to render object   */
    struct model *ptr;  /*  pointer to next object      */
    int numParam;       /*  # of parameters             */
    GLdouble *params;   /*  array with parameters       */
} MODEL, *MODELPTR;

/*      array of linked lists--used to keep track of display lists
 *      for each different type of geometric object.
 */
static MODELPTR lists[25] = {
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL
};

GLuint findList (int index, GLdouble *paramArray, int size);
int compareParams (GLdouble *oneArray, GLdouble *twoArray, int size);
GLuint makeModelPtr (int index, GLdouble *sizeArray, int count);

static void drawbox(GLdouble, GLdouble, GLdouble,
        GLdouble, GLdouble, GLdouble, GLenum);
static void doughnut(GLdouble, GLdouble, GLint, GLint, GLenum);
static void icosahedron(GLdouble *, GLdouble, GLenum);
static void octahedron(GLdouble *, GLdouble, GLenum);
static void tetrahedron(GLdouble *, GLdouble, GLenum);
static void subdivide(int, GLdouble *, GLdouble *, GLdouble *,
        GLdouble *, GLdouble, GLenum, int);
static void drawtriangle(int, int, int,
        GLdouble *, GLdouble, GLenum, int);
static void recorditem(GLdouble *, GLdouble *, GLdouble *,
        GLdouble *, GLdouble, GLenum, int);
static void initdodec(void);
static void dodecahedron(GLdouble *, GLdouble, GLenum);
static void pentagon(int, int, int, int, int, GLenum);

/*  Render wire frame or solid sphere.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireSphere (GLdouble radius)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (SPHEREWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (SPHEREWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_LINE);
            gluSphere (quadObj, radius, 16, 16);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidSphere (GLdouble radius)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (SPHERESOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (SPHERESOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_FILL);
            gluQuadricNormals (quadObj, GLU_SMOOTH);
            gluSphere (quadObj, radius, 16, 16);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid cube.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireCube (GLdouble size)
{
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = size;
    displayList = findList (CUBEWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (CUBEWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            drawbox(-size/(GLdouble)2., size/(GLdouble)2., -size/(GLdouble)2., size/(GLdouble)2.,
                -size/(GLdouble)2., size/(GLdouble)2., GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidCube (GLdouble size)
{
    GLdouble *sizeArray;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = size;
    displayList = findList (CUBESOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (CUBESOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            drawbox(-size/(GLdouble)2., size/(GLdouble)2., -size/(GLdouble)2., size/(GLdouble)2.,
                -size/(GLdouble)2., size/(GLdouble)2., GL_QUADS);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid cube.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireBox (GLdouble width, GLdouble height, GLdouble depth)
{
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 3);
    tmp = sizeArray;
    *tmp++ = width;
    *tmp++ = height;
    *tmp++ = depth;
    displayList = findList (BOXWIRE, sizeArray, 3);

    if (displayList == 0) {
        glNewList(makeModelPtr (BOXWIRE, sizeArray, 3),
            GL_COMPILE_AND_EXECUTE);
            drawbox(-width/(GLdouble)2., width/(GLdouble)2., -height/(GLdouble)2., height/(GLdouble)2.,
                -depth/(GLdouble)2., depth/(GLdouble)2., GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidBox (GLdouble width, GLdouble height, GLdouble depth)
{
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 3);
    tmp = sizeArray;
    *tmp++ = width;
    *tmp++ = height;
    *tmp++ = depth;
    displayList = findList (BOXSOLID, sizeArray, 3);

    if (displayList == 0) {
        glNewList(makeModelPtr (BOXSOLID, sizeArray, 3),
            GL_COMPILE_AND_EXECUTE);
            drawbox(-width/(GLdouble)2., width/(GLdouble)2., -height/(GLdouble)2., height/(GLdouble)2.,
                -depth/(GLdouble)2., depth/(GLdouble)2., GL_QUADS);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid tori.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireTorus (GLdouble innerRadius, GLdouble outerRadius)
{
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = innerRadius;
    *tmp++ = outerRadius;
    displayList = findList (TORUSWIRE, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (TORUSWIRE, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            doughnut(innerRadius, outerRadius, 5, 10, GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidTorus (GLdouble innerRadius, GLdouble outerRadius)
{
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = innerRadius;
    *tmp++ = outerRadius;
    displayList = findList (TORUSSOLID, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (TORUSSOLID, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            doughnut(innerRadius, outerRadius, 8, 15, GL_QUADS);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid cylinders.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireCylinder (GLdouble radius, GLdouble height)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = radius;
    *tmp++ = height;
    displayList = findList (CYLINDERWIRE, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (CYLINDERWIRE, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            glPushMatrix ();
            glRotatef ((GLfloat)90.0, (GLfloat)1.0, (GLfloat)0.0, (GLfloat)0.0);
            glTranslatef ((GLfloat)0.0, (GLfloat)0.0, (GLfloat)-1.0);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_LINE);
            gluCylinder (quadObj, radius, radius, height, 12, 2);
            glPopMatrix ();
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidCylinder (GLdouble radius, GLdouble height)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = radius;
    *tmp++ = height;
    displayList = findList (CYLINDERWIRE, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (CYLINDERWIRE, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            glPushMatrix ();
            glRotatef ((GLfloat)90.0, (GLfloat)1.0, (GLfloat)0.0, (GLfloat)0.0);
            glTranslatef ((GLfloat)0.0, (GLfloat)0.0, (GLfloat)-1.0);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_FILL);
            gluQuadricNormals (quadObj, GLU_SMOOTH);
            gluCylinder (quadObj, radius, radius, height, 12, 2);
            glPopMatrix ();
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid icosahedra.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireIcosahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (ICOSAWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (ICOSAWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            icosahedron (center, radius, GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidIcosahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (ICOSASOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (ICOSASOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            icosahedron (center, radius, GL_TRIANGLES);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid octahedra.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireOctahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (OCTAWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (OCTAWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            octahedron (center, radius, GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidOctahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (OCTASOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (OCTASOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            octahedron (center, radius, GL_TRIANGLES);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid tetrahedra.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireTetrahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (TETRAWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (TETRAWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            tetrahedron (center, radius, GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidTetrahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (TETRASOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (TETRASOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            tetrahedron (center, radius, GL_TRIANGLES);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid dodecahedra.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireDodecahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (DODECAWIRE, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (DODECAWIRE, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            dodecahedron (center, radius/(GLdouble)1.73, GL_LINE_LOOP);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidDodecahedron (GLdouble radius)
{
    GLdouble *sizeArray;
    GLuint displayList;
    GLdouble center[3] = {(GLdouble)0.0, (GLdouble)0.0, (GLdouble)0.0};

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 1);
    *sizeArray = radius;
    displayList = findList (DODECASOLID, sizeArray, 1);

    if (displayList == 0) {
        glNewList(makeModelPtr (DODECASOLID, sizeArray, 1),
            GL_COMPILE_AND_EXECUTE);
            dodecahedron (center, radius/(GLdouble)1.73, GL_POLYGON);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/*  Render wire frame or solid cones.  If no display list with
 *  the current model size exists, create a new display list.
 */
void auxWireCone (GLdouble base, GLdouble height)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = base;
    *tmp++ = height;
    displayList = findList (CONEWIRE, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (CONEWIRE, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_LINE);
            gluCylinder (quadObj, base, (GLdouble)0.0, height, 15, 10);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

void auxSolidCone (GLdouble base, GLdouble height)
{
    GLUquadricObj *quadObj;
    GLdouble *sizeArray, *tmp;
    GLuint displayList;

    sizeArray = (GLdouble *) malloc (sizeof (GLdouble) * 2);
    tmp = sizeArray;
    *tmp++ = base;
    *tmp++ = height;
    displayList = findList (CONEWIRE, sizeArray, 2);

    if (displayList == 0) {
        glNewList(makeModelPtr (CONEWIRE, sizeArray, 2),
            GL_COMPILE_AND_EXECUTE);
            quadObj = gluNewQuadric ();
            gluQuadricDrawStyle (quadObj, GLU_FILL);
            gluQuadricNormals (quadObj, GLU_SMOOTH);
            gluCylinder (quadObj, base, (GLdouble)0.0, height, 15, 10);
        glEndList();
    }
    else {
        glCallList(displayList);
        free (sizeArray);
    }
}

/* Routines to build 3 dimensional solids, including:
 *
 * drawbox, doughnut, icosahedron,
 * octahedron, tetrahedron, dodecahedron.
 */

/* drawbox:
 *
 * draws a rectangular box with the given x, y, and z ranges.
 * The box is axis-aligned.
 */
void drawbox(GLdouble x0, GLdouble x1, GLdouble y0, GLdouble y1,
        GLdouble z0, GLdouble z1, GLenum type)
{
    static GLdouble n[6][3] = {
        {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0},
        {0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0}
    };
    static GLint faces[6][4] = {
        { 0, 1, 2, 3 }, { 3, 2, 6, 7 }, { 7, 6, 5, 4 },
        { 4, 5, 1, 0 }, { 5, 6, 2, 1 }, { 7, 4, 0, 3 }
    };
    GLdouble v[8][3], tmp;
    GLint i;

    if (x0 > x1) {
        tmp = x0; x0 = x1; x1 = tmp;
    }
    if (y0 > y1) {
        tmp = y0; y0 = y1; y1 = tmp;
    }
    if (z0 > z1) {
        tmp = z0; z0 = z1; z1 = tmp;
    }
    v[0][0] = v[1][0] = v[2][0] = v[3][0] = x0;
    v[4][0] = v[5][0] = v[6][0] = v[7][0] = x1;
    v[0][1] = v[1][1] = v[4][1] = v[5][1] = y0;
    v[2][1] = v[3][1] = v[6][1] = v[7][1] = y1;
    v[0][2] = v[3][2] = v[4][2] = v[7][2] = z0;
    v[1][2] = v[2][2] = v[5][2] = v[6][2] = z1;

    for (i = 0; i < 6; i++) {
        glBegin(type);
        glNormal3dv(&n[i][0]);
        glVertex3dv(&v[faces[i][0]][0]);
        glNormal3dv(&n[i][0]);
        glVertex3dv(&v[faces[i][1]][0]);
        glNormal3dv(&n[i][0]);
        glVertex3dv(&v[faces[i][2]][0]);
        glNormal3dv(&n[i][0]);
        glVertex3dv(&v[faces[i][3]][0]);
        glEnd();
    }
}

/* doughnut:
 *
 * draws a doughnut, centered at (0, 0, 0) whose axis is aligned with
 * the z-axis.  The doughnut's major radius is R, and minor radius is r.
 */

void doughnut(GLdouble r, GLdouble R, GLint nsides, GLint rings, GLenum type)
{
    int i, j;
    GLdouble    theta, phi, theta1, phi1;
    GLdouble    p0[03], p1[3], p2[3], p3[3];
    GLdouble    n0[3], n1[3], n2[3], n3[3];

    for (i = 0; i < rings; i++) {
        theta = (GLdouble)i*(GLdouble)2.0*PI/rings;
        theta1 = (GLdouble)(i+1)*(GLdouble)2.0*PI/rings;
        for (j = 0; j < nsides; j++) {
            phi = (GLdouble)j*(GLdouble)2.0*PI/nsides;
            phi1 = (GLdouble)(j+1)*(GLdouble)2.0*PI/nsides;

            p0[0] = cos(theta)*(R + r*cos(phi));
            p0[1] = -sin(theta)*(R + r*cos(phi));
            p0[2] = r*sin(phi);

            p1[0] = cos(theta1)*(R + r*cos(phi));
            p1[1] = -sin(theta1)*(R + r*cos(phi));
            p1[2] = r*sin(phi);

            p2[0] = cos(theta1)*(R + r*cos(phi1));
            p2[1] = -sin(theta1)*(R + r*cos(phi1));
            p2[2] = r*sin(phi1);

            p3[0] = cos(theta)*(R + r*cos(phi1));
            p3[1] = -sin(theta)*(R + r*cos(phi1));
            p3[2] = r*sin(phi1);

            n0[0] = cos(theta)*(cos(phi));
            n0[1] = -sin(theta)*(cos(phi));
            n0[2] = sin(phi);

            n1[0] = cos(theta1)*(cos(phi));
            n1[1] = -sin(theta1)*(cos(phi));
            n1[2] = sin(phi);

            n2[0] = cos(theta1)*(cos(phi1));
            n2[1] = -sin(theta1)*(cos(phi1));
            n2[2] = sin(phi1);

            n3[0] = cos(theta)*(cos(phi1));
            n3[1] = -sin(theta)*(cos(phi1));
            n3[2] = sin(phi1);

            m_xformpt(p0, p0, n0, n0);
            m_xformpt(p1, p1, n1, n1);
            m_xformpt(p2, p2, n2, n2);
            m_xformpt(p3, p3, n3, n3);

            glBegin(type);
                glNormal3dv(n3);
                glVertex3dv(p3);
                glNormal3dv(n2);
                glVertex3dv(p2);
                glNormal3dv(n1);
                glVertex3dv(p1);
                glNormal3dv(n0);
                glVertex3dv(p0);
            glEnd();
        }
    }
}

/* octahedron data: The octahedron produced is centered
 * at the origin and has radius 1.0
 */
static GLdouble odata[6][3] = {
  {1.0, 0.0, 0.0},
  {-1.0, 0.0, 0.0},
  {0.0, 1.0, 0.0},
  {0.0, -1.0, 0.0},
  {0.0, 0.0, 1.0},
  {0.0, 0.0, -1.0}
};

static int ondex[8][3] = {
    {0, 4, 2}, {1, 2, 4}, {0, 3, 4}, {1, 4, 3},
    {0, 2, 5}, {1, 5, 2}, {0, 5, 3}, {1, 3, 5}
};

/* tetrahedron data: */

#define T       1.73205080756887729

static GLdouble tdata[4][3] = {
    {T, T, T}, {T, -T, -T}, {-T, T, -T}, {-T, -T, T}
};

static int tndex[4][3] = {
    {0, 1, 3}, {2, 1, 0}, {3, 2, 0}, {1, 2, 3}
};

/* icosahedron data: These numbers are rigged to
 * make an icosahedron of radius 1.0
 */

#define X .525731112119133606
#define Z .850650808352039932

static GLdouble idata[12][3] = {
  {-X, 0, Z},
  {X, 0, Z},
  {-X, 0, -Z},
  {X, 0, -Z},
  {0, Z, X},
  {0, Z, -X},
  {0, -Z, X},
  {0, -Z, -X},
  {Z, X, 0},
  {-Z, X, 0},
  {Z, -X, 0},
  {-Z, -X, 0}
};

static int index[20][3] = {
    {0, 4, 1},    {0, 9, 4},
    {9, 5, 4},    {4, 5, 8},
    {4, 8, 1},    {8, 10, 1},
    {8, 3, 10},    {5, 3, 8},
    {5, 2, 3},    {2, 7, 3},
    {7, 10, 3},    {7, 6, 10},
    {7, 11, 6},    {11, 0, 6},
    {0, 1, 6},    {6, 1, 10},
    {9, 0, 11},    {9, 11, 2},
    {9, 2, 5},    {7, 2, 11},
};

/* icosahedron:
 *
 * Draws an icosahedron with center at p0 having the
 * given radius.
 */

static void icosahedron(GLdouble p0[3], GLdouble radius, GLenum shadeType)
{
    int i;

    for (i = 0; i < 20; i++)
        drawtriangle(i, 0, 1, p0, radius, shadeType, 0);
}

/* octahedron:
 *
 * Draws an octahedron with center at p0 having the
 * given radius.
 */
static void octahedron(GLdouble p0[3], GLdouble radius, GLenum shadeType)
{
    int i;

    for (i = 0; i < 8; i++)
        drawtriangle(i, 1, 1, p0, radius, shadeType, 0);
}

/* tetrahedron:
 *
 * Draws an tetrahedron with center at p0 having the
 * given radius.
 */

static void tetrahedron(GLdouble p0[3], GLdouble radius, GLenum shadeType)
{
    int i;

    for (i = 0; i < 4; i++)
        drawtriangle(i, 2, 1, p0, radius, shadeType, 0);
}

static void subdivide(int depth, GLdouble *v0, GLdouble *v1, GLdouble *v2,
        GLdouble p0[3], GLdouble radius, GLenum shadeType, int avnormal)
{
    GLdouble w0[3], w1[3], w2[3];
    GLdouble l;
    int i, j, k, n;

    for (i = 0; i < depth; i++)
        for (j = 0; i + j < depth; j++) {
            k = depth - i - j;
            for (n = 0; n < 3; n++) {
                w0[n] = (i*v0[n] + j*v1[n] + k*v2[n])/depth;
                w1[n] = ((i+1)*v0[n] + j*v1[n] + (k-1)*v2[n])/depth;
                w2[n] = (i*v0[n] + (j+1)*v1[n] + (k-1)*v2[n])/depth;
            }
            l = sqrt(w0[0]*w0[0] + w0[1]*w0[1] + w0[2]*w0[2]);
            w0[0] /= l; w0[1] /= l; w0[2] /= l;
            l = sqrt(w1[0]*w1[0] + w1[1]*w1[1] + w1[2]*w1[2]);
            w1[0] /= l; w1[1] /= l; w1[2] /= l;
            l = sqrt(w2[0]*w2[0] + w2[1]*w2[1] + w2[2]*w2[2]);
            w2[0] /= l; w2[1] /= l; w2[2] /= l;
            recorditem(w1, w0, w2, p0, radius, shadeType, avnormal);
        }
    for (i = 0; i < depth-1; i++)
        for (j = 0; i + j < depth-1; j++) {
            k = depth - i - j;
            for (n = 0; n < 3; n++) {
                w0[n] = ((i+1)*v0[n] + (j+1)*v1[n] + (k-2)*v2[n])/depth;
                w1[n] = ((i+1)*v0[n] + j*v1[n] + (k-1)*v2[n])/depth;
                w2[n] = (i*v0[n] + (j+1)*v1[n] + (k-1)*v2[n])/depth;
            }
            l = sqrt(w0[0]*w0[0] + w0[1]*w0[1] + w0[2]*w0[2]);
            w0[0] /= l; w0[1] /= l; w0[2] /= l;
            l = sqrt(w1[0]*w1[0] + w1[1]*w1[1] + w1[2]*w1[2]);
            w1[0] /= l; w1[1] /= l; w1[2] /= l;
            l = sqrt(w2[0]*w2[0] + w2[1]*w2[1] + w2[2]*w2[2]);
            w2[0] /= l; w2[1] /= l; w2[2] /= l;
            recorditem(w0, w1, w2, p0, radius, shadeType, avnormal);
        }
}

static void drawtriangle(int i, int geomType, int depth,
        GLdouble p0[3], GLdouble radius, GLenum shadeType, int avnormal)
{
    GLdouble *x0, *x1, *x2;

    switch (geomType) {
        case 0: /* icosahedron */
            x0 = &idata[index[i][0]][0];
            x1 = &idata[index[i][1]][0];
            x2 = &idata[index[i][2]][0];
            break;
        case 1: /* octahedron */
            x0 = &odata[ondex[i][0]][0];
            x1 = &odata[ondex[i][1]][0];
            x2 = &odata[ondex[i][2]][0];
            break;
        case 2: /* tetrahedron */
            x0 = &tdata[tndex[i][0]][0];
            x1 = &tdata[tndex[i][1]][0];
            x2 = &tdata[tndex[i][2]][0];
            break;
    }
    subdivide(depth, x0, x1, x2, p0, radius, shadeType, avnormal);
}

static void recorditem(GLdouble *n1, GLdouble *n2, GLdouble *n3,
        GLdouble center[3], GLdouble radius, GLenum shadeType, int avnormal)
{
    GLdouble p1[3], p2[3], p3[3], q0[3], q1[3], n11[3], n22[3], n33[3];
    int i;

    for (i = 0; i < 3; i++) {
        p1[i] = n1[i]*radius + center[i];
        p2[i] = n2[i]*radius + center[i];
        p3[i] = n3[i]*radius + center[i];
    }
    if (avnormal == 0) {
        diff3(p1, p2, q0);
        diff3(p2, p3, q1);
        crossprod(q0, q1, q1);
        normalize(q1);
        m_xformpt(p1, p1, q1, n11);

m_xformptonly(p2, p2);
        m_xformptonly(p3, p3);

        glBegin (shadeType);
        glNormal3dv(n11);
        glVertex3dv(p1);
        glVertex3dv(p2);
        glVertex3dv(p3);
        glEnd();
        return;
    }
    m_xformpt(p1, p1, n1, n11);
    m_xformpt(p2, p2, n2, n22);
    m_xformpt(p3, p3, n3, n33);

    glBegin (shadeType);
    glNormal3dv(n11);
    glVertex3dv(p1);
    glNormal3dv(n22);
    glVertex3dv(p2);
    glNormal3dv(n33);
    glVertex3dv(p3);
    glEnd();
}

static GLdouble dodec[20][3];

static void initdodec()
{
    GLdouble alpha, beta;

    alpha = sqrt((double)2.0/((double)3.0 + sqrt((double)5.0)));
    beta = (double)1.0 + sqrt((double)6.0/((double)3.0 + sqrt((double)5.0)) - (double)2.0 + (double)2.0*sqrt((double)2.0/((double)3.0 +
                                                            sqrt((double)5.0))));
    dodec[0][0] = -alpha; dodec[0][1] = 0; dodec[0][2] = beta;
    dodec[1][0] = alpha; dodec[1][1] = 0; dodec[1][2] = beta;
    dodec[2][0] = -1; dodec[2][1] = -1; dodec[2][2] = -1;
    dodec[3][0] = -1; dodec[3][1] = -1; dodec[3][2] = 1;
    dodec[4][0] = -1; dodec[4][1] = 1; dodec[4][2] = -1;
    dodec[5][0] = -1; dodec[5][1] = 1; dodec[5][2] = 1;
    dodec[6][0] = 1; dodec[6][1] = -1; dodec[6][2] = -1;
    dodec[7][0] = 1; dodec[7][1] = -1; dodec[7][2] = 1;
    dodec[8][0] = 1; dodec[8][1] = 1; dodec[8][2] = -1;
    dodec[9][0] = 1; dodec[9][1] = 1; dodec[9][2] = 1;
    dodec[10][0] = beta; dodec[10][1] = alpha; dodec[10][2] = 0;
    dodec[11][0] = beta; dodec[11][1] = -alpha; dodec[11][2] = 0;
    dodec[12][0] = -beta; dodec[12][1] = alpha; dodec[12][2] = 0;
    dodec[13][0] = -beta; dodec[13][1] = -alpha; dodec[13][2] = 0;
    dodec[14][0] = -alpha; dodec[14][1] = 0; dodec[14][2] = -beta;
    dodec[15][0] = alpha; dodec[15][1] = 0; dodec[15][2] = -beta;
    dodec[16][0] = 0; dodec[16][1] = beta; dodec[16][2] = alpha;
    dodec[17][0] = 0; dodec[17][1] = beta; dodec[17][2] = -alpha;
    dodec[18][0] = 0; dodec[18][1] = -beta; dodec[18][2] = alpha;
    dodec[19][0] = 0; dodec[19][1] = -beta; dodec[19][2] = -alpha;
}

/* dodecahedron:
 *
 * Draws an dodecahedron with center at 0.0. The radius
 * is sqrt(3).
 */
static void dodecahedron(GLdouble center[3], GLdouble sc, GLenum type)
{
    static int inited = 0;

    if ( inited == 0) {
        inited = 1;
        initdodec();
    }
    m_pushmatrix();
    m_translate(center[0], center[1], center[2]);
    m_scale(sc, sc, sc);
    pentagon(0, 1, 9, 16, 5, type);
    pentagon(1, 0, 3, 18, 7, type);
    pentagon(1, 7, 11, 10, 9, type);
    pentagon(11, 7, 18, 19, 6, type);
    pentagon(8, 17, 16, 9, 10, type);
    pentagon(2, 14, 15, 6, 19, type);
    pentagon(2, 13, 12, 4, 14, type);
    pentagon(2, 19, 18, 3, 13, type);
    pentagon(3, 0, 5, 12, 13, type);
    pentagon(6, 15, 8, 10, 11, type);
    pentagon(4, 17, 8, 15, 14, type);
    pentagon(4, 12, 5, 16, 17, type);
    m_popmatrix();
}

static void pentagon(int a, int b, int c, int d, int e, GLenum shadeType)
{
    GLdouble n0[3], d1[3], d2[3], d3[3], d4[3], d5[3], nout[3];

    diff3(&dodec[a][0], &dodec[b][0], d1);
    diff3(&dodec[b][0], &dodec[c][0], d2);
    crossprod(d1, d2, n0);
    normalize(n0);
    m_xformpt(&dodec[a][0], d1, n0, nout);
    m_xformptonly(&dodec[b][0], d2);
    m_xformptonly(&dodec[c][0], d3);
    m_xformptonly(&dodec[d][0], d4);
    m_xformptonly(&dodec[e][0], d5);

    glBegin (shadeType);
    glNormal3dv(nout);
    glVertex3dv(d1);
    glVertex3dv(d2);
    glVertex3dv(d3);
    glVertex3dv(d4);
    glVertex3dv(d5);
    glEnd();
}

/*      linked lists--display lists for each different
 *      type of geometric objects.  The linked list is
 *      searched, until an object of the requested
 *      size is found.  If no geometric object of that size
 *      has been previously made, a new one is created.
 */
GLuint findList (int index, GLdouble *paramArray, int size)
{
    MODELPTR endList;
    int found = 0;

    endList = lists[index];
    while (endList != NULL) {
        if (compareParams (endList->params, paramArray, size))
            return (endList->list);
        endList = endList->ptr;
    }
/*  if not found, return 0 and calling routine should
 *  make a new list
 */
    return (0);
}

int compareParams (GLdouble *oneArray, GLdouble *twoArray, int size)
{
    int i;
    int matches = 1;

    for (i = 0; (i < size) && matches; i++) {
        if (*oneArray++ != *twoArray++)
            matches = 0;
    }
    return (matches);
}

GLuint makeModelPtr (int index, GLdouble *sizeArray, int count)
{
    MODELPTR newModel;

    newModel = (MODELPTR) malloc (sizeof (MODEL));
    newModel->list = glGenLists (1);
    newModel->numParam = count;
    newModel->params = sizeArray;
    newModel->ptr = lists[index];
    lists[index] = newModel;

    return (newModel->list);
}


TEAPOT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <windows.h>
#include <GL/gl.h>
#include <gl/glaux.h>
#include "teapot.h"

#define static

long GRD;

#define TEAPOTSOLID 0
#define TEAPOTWIRE 1

static GLuint teapots[2] = {0, 0};

static float tex[2][2][2] = {{{0, 0},{1, 0}},{{0, 1},{1, 1}}};

static void solidTeapot(long grid, GLdouble scale)
{
    float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3];
    long i, j, k, l;

    if (grid < 2) grid = 7;
    GRD = grid;
    teapots[TEAPOTSOLID] = glGenLists (1);
    glNewList(teapots[TEAPOTSOLID], GL_COMPILE);
    glPushMatrix ();
    glRotatef ((GLfloat)270.0, (GLfloat)1.0, (GLfloat)0.0, (GLfloat)0.0);
    glScalef ((GLdouble)0.5*scale, (GLdouble)0.5*scale, (GLdouble)0.5*scale);
    glTranslatef ((GLfloat)0.0, (GLfloat)0.0, (GLfloat)-1.5);
    for (i = 0; i < 10; i++) {
for (j = 0; j < 4; j++)
    for (k = 0; k < 4; k++)
for (l = 0; l < 3; l++) {
    p[j][k][l] = cpdata[patchdata[i][j*4+k]][l];
    q[j][k][l] = cpdata[patchdata[i][j*4+(3-k)]][l];
    if (l == 1) q[j][k][l] *= (float)-1.0;
    if (i < 6) {
r[j][k][l] = cpdata[patchdata[i][j*4+(3-k)]][l];
if (l == 0) r[j][k][l] *= (float)-1.0;
s[j][k][l] = cpdata[patchdata[i][j*4+k]][l];
if (l == 0) s[j][k][l] *= (float)-1.0;
if (l == 1) s[j][k][l] *= (float)-1.0;
    }
}
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, &tex[0][0][0]);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &p[0][0][0]);
glEnable(GL_MAP2_VERTEX_3); glEnable(GL_MAP2_TEXTURE_COORD_2);
glMapGrid2f(GRD, (GLfloat)0.0, (GLfloat)1.0, GRD, (GLfloat)0.0, (GLfloat)1.0);
glEvalMesh2(GL_FILL, 0, GRD, 0, GRD);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &q[0][0][0]);
glEvalMesh2(GL_FILL, 0, GRD, 0, GRD);
if (i < 6) {
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &r[0][0][0]);
    glEvalMesh2(GL_FILL, 0, GRD, 0, GRD);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &s[0][0][0]);
    glEvalMesh2(GL_FILL, 0, GRD, 0, GRD);
}
    }
    glDisable(GL_MAP2_VERTEX_3); glDisable(GL_MAP2_TEXTURE_COORD_2);
    glPopMatrix ();
    glEndList();
}

static void wireTeapot(long grid, GLdouble scale)
{
    float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3];
    long i, j, k, l;

    if (grid < 2) grid = 7;
    GRD = grid;
    teapots[TEAPOTWIRE] = glGenLists (1);
    glNewList(teapots[TEAPOTWIRE], GL_COMPILE);
    glPushMatrix ();
    glRotatef ((GLfloat)270.0, (GLfloat)1.0, (GLfloat)0.0, (GLfloat)0.0);
    glScalef ((GLdouble)0.5*scale, (GLdouble)0.5*scale, (GLdouble)0.5*scale);
    glTranslatef ((GLfloat)0.0, (GLfloat)0.0, (GLfloat)-1.5);
    for (i = 0; i < 10; i++) {
for (j = 0; j < 4; j++)
    for (k = 0; k < 4; k++)
for (l = 0; l < 3; l++) {
    p[j][k][l] = cpdata[patchdata[i][j*4+k]][l];
    q[j][k][l] = cpdata[patchdata[i][j*4+(3-k)]][l];
    if (l == 1) q[j][k][l] *= (float)-1.0;
    if (i < 6) {
r[j][k][l] = cpdata[patchdata[i][j*4+(3-k)]][l];
if (l == 0) r[j][k][l] *= (float)-1.0;
s[j][k][l] = cpdata[patchdata[i][j*4+k]][l];
if (l == 0) s[j][k][l] *= (float)-1.0;
if (l == 1) s[j][k][l] *= (float)-1.0;
    }
}
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, &tex[0][0][0]);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &p[0][0][0]);
glEnable(GL_MAP2_VERTEX_3); glEnable(GL_MAP2_TEXTURE_COORD_2);
glMapGrid2f(GRD, (GLfloat)0.0, (GLfloat)1.0, GRD, (GLfloat)0.0, (GLfloat)1.0);
glEvalMesh2(GL_LINE, 0, GRD, 0, GRD);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &q[0][0][0]);
glEvalMesh2(GL_LINE, 0, GRD, 0, GRD);
if (i < 6) {
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &r[0][0][0]);
    glEvalMesh2(GL_LINE, 0, GRD, 0, GRD);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &s[0][0][0]);
    glEvalMesh2(GL_LINE, 0, GRD, 0, GRD);
}
    }
    glDisable(GL_MAP2_VERTEX_3); glDisable(GL_MAP2_TEXTURE_COORD_2);
    glPopMatrix ();
    glEndList();
}

void auxSolidTeapot(GLdouble scale)
{
    if (glIsList(teapots[TEAPOTSOLID]) == 0)
solidTeapot (14, scale);
    glCallList(teapots[TEAPOTSOLID]);
}

void auxWireTeapot(GLdouble scale)
{
    if (glIsList(teapots[TEAPOTWIRE]) == 0)
wireTeapot (10, scale);
    glCallList(teapots[TEAPOTWIRE]);
}


TEAPOT.H

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
/* Rim, body, lid, and bottom data must be reflected in x and y;
 * handle and spout data across the y axis only.
 */
#include "glos.h"

long patchdata[][16] = {
    {102,103,104,105,4,5,6,7,8,9,10,11,12,13,14,15},   /* rim */
    {12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27},   /* body */
    {24,25,26,27,29,30,31,32,33,34,35,36,37,38,39,40},   /* body */
    {96,96,96,96,97,98,99,100,101,101,101,101,0,1,2,3,},   /* lid */
    {0,1,2,3,106,107,108,109,110,111,112,113,114,115,116,117},   /* lid */
    {118,118,118,118,124,122,119,121,123,126,125,120,40,39,38,37}, /* bottom */
    {41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56},   /* handle */
    {53,54,55,56,57,58,59,60,61,62,63,64,28,65,66,67},   /* handle */
    {68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83},   /* spout */
    {80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95}   /* spout */
};

float cpdata[][3] = {
{0.2,0,2.7},{0.2,-0.112,2.7},{0.112,-0.2,2.7},{0,-0.2,2.7},{1.3375,0,2.53125},
{1.3375,-0.749,2.53125},{0.749,-1.3375,2.53125},{0,-1.3375,2.53125},
{1.4375,0,2.53125},{1.4375,-0.805,2.53125},{0.805,-1.4375,2.53125},
{0,-1.4375,2.53125},{1.5,0,2.4},{1.5,-0.84,2.4},{0.84,-1.5,2.4},{0,-1.5,2.4},
{1.75,0,1.875},{1.75,-0.98,1.875},{0.98,-1.75,1.875},{0,-1.75,1.875},{2,0,1.35},
{2,-1.12,1.35},{1.12,-2,1.35},{0,-2,1.35},{2,0,0.9},{2,-1.12,0.9},{1.12,-2,0.9},
{0,-2,0.9},{-2,0,0.9},{2,0,0.45},{2,-1.12,0.45},{1.12,-2,0.45},{0,-2,0.45},
{1.5,0,0.225},{1.5,-0.84,0.225},{0.84,-1.5,0.225},{0,-1.5,0.225},{1.5,0,0.15},
{1.5,-0.84,0.15},{0.84,-1.5,0.15},{0,-1.5,0.15},{-1.6,0,2.025},{-1.6,-0.3,2.025},
{-1.5,-0.3,2.25},{-1.5,0,2.25},{-2.3,0,2.025},{-2.3,-0.3,2.025},{-2.5,-0.3,2.25},
{-2.5,0,2.25},{-2.7,0,2.025},{-2.7,-0.3,2.025},{-3,-0.3,2.25},{-3,0,2.25},
{-2.7,0,1.8},{-2.7,-0.3,1.8},{-3,-0.3,1.8},{-3,0,1.8},{-2.7,0,1.575},
{-2.7,-0.3,1.575},{-3,-0.3,1.35},{-3,0,1.35},{-2.5,0,1.125},{-2.5,-0.3,1.125},
{-2.65,-0.3,0.9375},{-2.65,0,0.9375},{-2,-0.3,0.9},{-1.9,-0.3,0.6},{-1.9,0,0.6},
{1.7,0,1.425},{1.7,-0.66,1.425},{1.7,-0.66,0.6},{1.7,0,0.6},{2.6,0,1.425},
{2.6,-0.66,1.425},{3.1,-0.66,0.825},{3.1,0,0.825},{2.3,0,2.1},{2.3,-0.25,2.1},
{2.4,-0.25,2.025},{2.4,0,2.025},{2.7,0,2.4},{2.7,-0.25,2.4},{3.3,-0.25,2.4},
{3.3,0,2.4},{2.8,0,2.475},{2.8,-0.25,2.475},{3.525,-0.25,2.49375},
{3.525,0,2.49375},{2.9,0,2.475},{2.9,-0.15,2.475},{3.45,-0.15,2.5125},
{3.45,0,2.5125},{2.8,0,2.4},{2.8,-0.15,2.4},{3.2,-0.15,2.4},{3.2,0,2.4},
{0,0,3.15},{0.8,0,3.15},{0.8,-0.45,3.15},{0.45,-0.8,3.15},{0,-0.8,3.15},
{0,0,2.85},{1.4,0,2.4},{1.4,-0.784,2.4},{0.784,-1.4,2.4},{0,-1.4,2.4},
{0.4,0,2.55},{0.4,-0.224,2.55},{0.224,-0.4,2.55},{0,-0.4,2.55},{1.3,0,2.55},
{1.3,-0.728,2.55},{0.728,-1.3,2.55},{0,-1.3,2.55},{1.3,0,2.4},{1.3,-0.728,2.4},
{0.728,-1.3,2.4},{0,-1.3,2.4},{0,0,0},{1.425,-0.798,0},{1.5,0,0.075},{1.425,0,0},
{0.798,-1.425,0},{0,-1.5,0.075},{0,-1.425,0},{1.5,-0.84,0.075},{0.84,-1.5,0.075}
};


TK.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tk.h"
#include "windows.h"

#if(WINVER < 0x0400)
// Ordinarily not defined for versions before 4.00.
#define COLOR_3DDKSHADOW        21
#define COLOR_3DLIGHT           22
#define COLOR_INFOTEXT          23
#define COLOR_INFOBK            24
#endif

#define static

#if defined(__cplusplus) || defined(c_plusplus)
#define class c_class
#endif

#if DBG
#define TKASSERT(x)                                     \
if ( !(x) ) {                                           \
    PrintMessage("%s(%d) Assertion failed %s\n",        \
        __FILE__, __LINE__, #x);                        \
}
#else
#define TKASSERT(x)
#endif  /* DBG */

/******************************************************************************/

static struct _WINDOWINFO {
    int x, y;
    int width, height;
    GLenum type;
    GLenum dmPolicy;
    int ipfd;
    BOOL bDefPos;
} windInfo = {
    0, 0, 100, 100, TK_INDEX | TK_SINGLE, TK_MINIMUM_CRITERIA, 0, TRUE
};

static HWND     tkhwnd     = NULL;
static HDC      tkhdc      = NULL;
static HGLRC    tkhrc      = NULL;
static HPALETTE tkhpalette = NULL;
static OSVERSIONINFO tkOSVerInfo;
GLboolean tkPopupEnable = TRUE;

// Fixed palette support.

#define BLACK   PALETTERGB(0,0,0)
#define WHITE   PALETTERGB(255,255,255)
#define MAX_STATIC_COLORS   (COLOR_INFOBK - COLOR_SCROLLBAR + 1)
static int tkNumStaticColors = MAX_STATIC_COLORS;

// TRUE if app wants to take over palette
static BOOL tkUseStaticColors = FALSE;

// TRUE if static system color settings have been replaced with B&W settings.
static BOOL tkSystemColorsInUse = FALSE;

// TRUE if original static colors saved
static BOOL tkStaticColorsSaved = FALSE;

// saved system static colors (initialize with default colors)
static COLORREF gacrSave[MAX_STATIC_COLORS];

// new B&W system static colors
static COLORREF gacrBlackAndWhite[] = {
    WHITE,  // COLOR_SCROLLBAR
    BLACK,  // COLOR_BACKGROUND
    BLACK,  // COLOR_ACTIVECAPTION
    WHITE,  // COLOR_INACTIVECAPTION
    WHITE,  // COLOR_MENU
    WHITE,  // COLOR_WINDOW
    BLACK,  // COLOR_WINDOWFRAME
    BLACK,  // COLOR_MENUTEXT
    BLACK,  // COLOR_WINDOWTEXT
    WHITE,  // COLOR_CAPTIONTEXT
    WHITE,  // COLOR_ACTIVEBORDER
    WHITE,  // COLOR_INACTIVEBORDER
    WHITE,  // COLOR_APPWORKSPACE
    BLACK,  // COLOR_HIGHLIGHT
    WHITE,  // COLOR_HIGHLIGHTTEXT
    WHITE,  // COLOR_BTNFACE
    BLACK,  // COLOR_BTNSHADOW
    BLACK,  // COLOR_GRAYTEXT
    BLACK,  // COLOR_BTNTEXT
    BLACK,  // COLOR_INACTIVECAPTIONTEXT
    BLACK,  // COLOR_BTNHIGHLIGHT
    BLACK,  // COLOR_3DDKSHADOW
    WHITE,  // COLOR_3DLIGHT
    BLACK,  // COLOR_INFOTEXT
    WHITE   // COLOR_INFOBK
    };
static INT gaiStaticIndex[] = {
    COLOR_SCROLLBAR          ,
    COLOR_BACKGROUND         ,
    COLOR_ACTIVECAPTION      ,
    COLOR_INACTIVECAPTION    ,
    COLOR_MENU               ,
    COLOR_WINDOW             ,
    COLOR_WINDOWFRAME        ,
    COLOR_MENUTEXT           ,
    COLOR_WINDOWTEXT         ,
    COLOR_CAPTIONTEXT        ,
    COLOR_ACTIVEBORDER       ,
    COLOR_INACTIVEBORDER     ,
    COLOR_APPWORKSPACE       ,
    COLOR_HIGHLIGHT          ,
    COLOR_HIGHLIGHTTEXT      ,
    COLOR_BTNFACE            ,
    COLOR_BTNSHADOW          ,
    COLOR_GRAYTEXT           ,
    COLOR_BTNTEXT            ,
    COLOR_INACTIVECAPTIONTEXT,
    COLOR_BTNHIGHLIGHT       ,
    COLOR_3DDKSHADOW         ,
    COLOR_3DLIGHT            ,
    COLOR_INFOTEXT           ,
    COLOR_INFOBK
    };

static BOOL GrabStaticEntries(HDC);
static BOOL ReleaseStaticEntries(HDC);

#define RESTORE_FROM_REGISTRY   1
#if RESTORE_FROM_REGISTRY
// Registry names for the system colors.
CHAR *gaszSysClrNames[] = {
    "Scrollbar",      // COLOR_SCROLLBAR              0
    "Background",     // COLOR_BACKGROUND             1   (also COLOR_DESKTOP)
    "ActiveTitle",    // COLOR_ACTIVECAPTION          2
    "InactiveTitle",  // COLOR_INACTIVECAPTION        3
    "Menu",           // COLOR_MENU                   4
    "Window",         // COLOR_WINDOW                 5
    "WindowFrame",    // COLOR_WINDOWFRAME            6
    "MenuText",       // COLOR_MENUTEXT               7
    "WindowText",     // COLOR_WINDOWTEXT             8
    "TitleText",      // COLOR_CAPTIONTEXT            9
    "ActiveBorder",   // COLOR_ACTIVEBORDER          10
    "InactiveBorder", // COLOR_INACTIVEBORDER        11
    "AppWorkspace",   // COLOR_APPWORKSPACE          12
    "Hilight",        // COLOR_HIGHLIGHT             13
    "HilightText",    // COLOR_HIGHLIGHTTEXT         14
    "ButtonFace",     // COLOR_BTNFACE               15   (also COLOR_3DFACE)
    "ButtonShadow",   // COLOR_BTNSHADOW             16   (also COLOR_3DSHADOW)
    "GrayText",       // COLOR_GRAYTEXT              17
    "ButtonText",     // COLOR_BTNTEXT               18
    "InactiveTitleText", // COLOR_INACTIVECAPTIONTEXT   19
    "ButtonHilight",  // COLOR_BTNHIGHLIGHT          20   (also COLOR_3DHILIGHT)
    "ButtonDkShadow", // COLOR_3DDKSHADOW            21
    "ButtonLight",    // COLOR_3DLIGHT               22
    "InfoText",       // COLOR_INFOTEXT              23
    "InfoWindow"      // COLOR_INFOBK                24
};

static BOOL GetRegistrySysColors(COLORREF *, int);
#endif

static void (*ExposeFunc)(int, int)              = NULL;
static void (*ReshapeFunc)(GLsizei, GLsizei)     = NULL;
static void (*DisplayFunc)(void)                 = NULL;
static GLenum (*KeyDownFunc)(int, GLenum)        = NULL;
static GLenum (*MouseDownFunc)(int, int, GLenum) = NULL;
static GLenum (*MouseUpFunc)(int, int, GLenum)   = NULL;
static GLenum (*MouseMoveFunc)(int, int, GLenum) = NULL;
static void (*IdleFunc)(void)                    = NULL;

static char     *lpszClassName = "tkLibWClass";
static WCHAR    *lpszClassNameW = L"tkLibWClass";

static long tkWndProc(HWND hWnd, UINT message, DWORD wParam, LONG lParam);
static unsigned char ComponentFromIndex(int i, int nbits, int shift );
static void PrintMessage( const char *Format, ... );
static PALETTEENTRY *FillRgbPaletteEntries( PIXELFORMATDESCRIPTOR *Pfd, PALETTEENTRY *Entries, UINT Count );
static HPALETTE CreateCIPalette( HDC Dc );
static HPALETTE CreateRGBPalette( HDC hdc );
static void DestroyThisWindow( HWND Window );
static void CleanUp( void );
static void DelayPaletteRealization( void );
static long RealizePaletteNow( HDC Dc, HPALETTE Palette, BOOL bForceBackground );
static void ForceRedraw( HWND Window );
static BOOL FindPixelFormat(HDC hdc, GLenum type);
static int FindBestPixelFormat(HDC hdc, GLenum type, PIXELFORMATDESCRIPTOR *ppfd);
static int FindExactPixelFormat(HDC hdc, GLenum type, PIXELFORMATDESCRIPTOR *ppfd);
static BOOL IsPixelFormatValid(HDC hdc, int ipfd, PIXELFORMATDESCRIPTOR *ppfd);
static int PixelFormatDescriptorFromDc( HDC Dc, PIXELFORMATDESCRIPTOR *Pfd );
static void *AllocateMemory( size_t Size );
static void *AllocateZeroedMemory( size_t Size );
static void FreeMemory( void *Chunk );

/*
 *  Prototypes for the debugging functions go here
 */

#define DBGFUNC 0
#if DBGFUNC

static void DbgPrintf( const char *Format, ... );
static void pwi( void );
static void pwr(RECT *pr);
static void ShowPixelFormat(HDC hdc);

#endif

static float colorMaps[] = {
    0.000000F, 1.000000F, 0.000000F, 1.000000F, 0.000000F, 1.000000F,
    0.000000F, 1.000000F, 0.333333F, 0.776471F, 0.443137F, 0.556863F,
    0.443137F, 0.556863F, 0.219608F, 0.666667F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.039216F, 0.078431F, 0.117647F, 0.156863F,
    0.200000F, 0.239216F, 0.278431F, 0.317647F, 0.356863F, 0.400000F,
    0.439216F, 0.478431F, 0.517647F, 0.556863F, 0.600000F, 0.639216F,
    0.678431F, 0.717647F, 0.756863F, 0.800000F, 0.839216F, 0.878431F,
    0.917647F, 0.956863F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 0.000000F, 0.000000F,
    1.000000F, 1.000000F, 0.000000F, 0.000000F, 1.000000F, 1.000000F,
    0.333333F, 0.443137F, 0.776471F, 0.556863F, 0.443137F, 0.219608F,
    0.556863F, 0.666667F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.039216F, 0.078431F, 0.117647F, 0.156863F, 0.200000F, 0.239216F,
    0.278431F, 0.317647F, 0.356863F, 0.400000F, 0.439216F, 0.478431F,
    0.517647F, 0.556863F, 0.600000F, 0.639216F, 0.678431F, 0.717647F,
    0.756863F, 0.800000F, 0.839216F, 0.878431F, 0.917647F, 0.956863F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.141176F, 0.282353F, 0.427451F,
    0.568627F, 0.713726F, 0.854902F, 1.000000F, 0.000000F, 0.141176F,
    0.282353F, 0.427451F, 0.568627F, 0.713726F, 0.854902F, 1.000000F,
    0.000000F, 0.141176F, 0.282353F, 0.427451F, 0.568627F, 0.713726F,
    0.854902F, 1.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 0.333333F, 0.443137F,
    0.443137F, 0.219608F, 0.776471F, 0.556863F, 0.556863F, 0.666667F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.666667F, 0.333333F,
    0.666667F, 0.333333F, 0.666667F, 0.333333F, 0.039216F, 0.078431F,
    0.117647F, 0.156863F, 0.200000F, 0.239216F, 0.278431F, 0.317647F,
    0.356863F, 0.400000F, 0.439216F, 0.478431F, 0.517647F, 0.556863F,
    0.600000F, 0.639216F, 0.678431F, 0.717647F, 0.756863F, 0.800000F,
    0.839216F, 0.878431F, 0.917647F, 0.956863F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F, 0.000000F,
    0.000000F, 0.000000F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F, 0.247059F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.498039F,
    0.498039F, 0.498039F, 0.498039F, 0.498039F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F, 0.749020F,
    0.749020F, 0.749020F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
    1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F, 1.000000F,
};

/* Default Palette */
float auxRGBMap[20][3] = {
    { 0.0F, 0.0F, 0.0F },                               /* 0: black */
    { 0x80/255.0F, 0.0F, 0.0F },                        /* 1: Half red */
    { 0.0F, 0x80/255.0F, 0.0F },                        /* 2: Half green */
    { 0x80/255.0F, 0x80/255.0F, 0.0F },                 /* 3: Half yellow */
    { 0.0F, 0.0F, 0x80/255.0F },                        /* 4: Half blue */
    { 0x80/255.0F, 0.0F, 0x80/255.0F },                 /* 5: Half magenta */
    { 0.0F, 0x80/255.0F, 0x80/255.0F },                 /* 6: Half cyan */
    { 0xC0/255.0F, 0xC0/255.0F, 0xC0/255.0F },          /* 7: Light gray */
    { 0xC0/255.0F, 0xDC/255.0F, 0xC0/255.0F },          /* 8: Green gray */
    { 0xA6/255.0F, 0xCA/255.0F, 0xF0/255.0F },          /* 9: Half gray */
    { 1.0F, 0xFB/255.0F, 0xF0/255.0F },                 /* 10: Pale */
    { 0xA0/255.0F, 0xA0/255.0F, 0xA4/255.0F },          /* 11: Med gray */
    { 0x80/255.0F, 0x80/255.0F, 0x80/255.0F },          /* 12: Dark gray */
    { 1.0F, 0.0F, 0.0F },                               /* 13: red */
    { 0.0F, 1.0F, 0.0F },                               /* 14: green */
    { 1.0F, 1.0F, 0.0F },                               /* 15: yellow */
    { 0.0F, 0.0F, 1.0F },                               /* 16: blue */
    { 1.0F, 0.0F, 1.0F },                               /* 17: magenta */
    { 0.0F, 1.0F, 1.0F },                               /* 18: cyan */
    { 1.0F, 1.0F, 1.0F },                               /* 19: white */
};

/***************************************************************
 *                                                             *
 *  Exported Functions go here                                 *
 *                                                             *
 ***************************************************************/

void tkErrorPopups(GLboolean bEnable)
{
    tkPopupEnable = bEnable;
}

void tkCloseWindow(void)
{
    DestroyThisWindow(tkhwnd);
}

void tkExec(void)
{
    MSG Message;

    /*
     *  WM_SIZE gets delivered before we get here!
     */

    if (ReshapeFunc)
    {
        RECT ClientRect;

        GetClientRect(tkhwnd, &ClientRect);
        (*ReshapeFunc)(ClientRect.right, ClientRect.bottom);
    }

    while (GL_TRUE)
    {
        /*
         *  Process all pending messages
         */

        if (IdleFunc) {
            while (PeekMessage(&Message, NULL, 0, 0, PM_NOREMOVE) == TRUE) {
                if (GetMessage(&Message, NULL, 0, 0) ) {
                    TranslateMessage(&Message);
                    DispatchMessage(&Message);
                } else {
                    /*
                     *  Nothing else to do here, just return
                     */

                    return;
                }
            }

            /*
             *  If an idle function was defined, call it
             */

            if (IdleFunc) {
                (*IdleFunc)();
            }
        } else {
            if (GetMessage(&Message, NULL, 0, 0)) {
                TranslateMessage(&Message);
                DispatchMessage(&Message);
            } else {
                return;
            }
        }
    }
}

void tkExposeFunc(void (*Func)(int, int))
{
    ExposeFunc = Func;
}

void tkReshapeFunc(void (*Func)(GLsizei, GLsizei))
{
    ReshapeFunc = Func;
}

void tkDisplayFunc(void (*Func)(void))
{
    DisplayFunc = Func;
}

void tkKeyDownFunc(GLenum (*Func)(int, GLenum))
{
    KeyDownFunc = Func;
}

void tkMouseDownFunc(GLenum (*Func)(int, int, GLenum))
{
    MouseDownFunc = Func;
}

void tkMouseUpFunc(GLenum (*Func)(int, int, GLenum))
{
    MouseUpFunc = Func;
}

void tkMouseMoveFunc(GLenum (*Func)(int, int, GLenum))
{
    MouseMoveFunc = Func;
}

void tkIdleFunc(void (*Func)(void))
{
    IdleFunc = Func;
}

void tkInitPosition(int x, int y, int width, int height)
{
    if (x == CW_USEDEFAULT)
    {
        x = 0;
        y = 0;
        windInfo.bDefPos = TRUE;
    }
    else
        windInfo.bDefPos = FALSE;

    windInfo.x = x + GetSystemMetrics(SM_CXFRAME);
    windInfo.y = y + GetSystemMetrics(SM_CYCAPTION)
                 - GetSystemMetrics(SM_CYBORDER)
                 + GetSystemMetrics(SM_CYFRAME);
    windInfo.width = width;
    windInfo.height = height;
}

void tkInitDisplayMode(GLenum type)
{
    windInfo.type = type;
}

void tkInitDisplayModePolicy(GLenum type)
{
    windInfo.dmPolicy = type;
}

GLenum tkInitDisplayModeID(GLint ipfd)
{
    windInfo.ipfd = ipfd;
    return GL_TRUE;
}

// Initialize a window, create a rendering context for that window
GLenum tkInitWindow(char *title)
{
    TKASSERT( NULL==tkhwnd      );
    TKASSERT( NULL==tkhdc       );
    TKASSERT( NULL==tkhrc       );
    TKASSERT( NULL==tkhpalette  );

    return tkInitWindowAW(title, FALSE);
}

GLenum tkInitWindowAW(char *title, BOOL bUnicode)
{
    WNDCLASS wndclass;
    RECT     WinRect;
    HANDLE   hInstance;
    ATOM     aRegister;
    GLenum   Result = GL_FALSE;
    BOOL     bGetVersionExRet;

    hInstance = GetModuleHandle(NULL);

    tkOSVerInfo.dwOSVersionInfoSize = sizeof(tkOSVerInfo);
    bGetVersionExRet = GetVersionEx(&tkOSVerInfo);
    TKASSERT(bGetVersionExRet);
    if ( tkOSVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
         tkOSVerInfo.dwMajorVersion == 3 &&
         (tkOSVerInfo.dwMinorVersion == 5 || tkOSVerInfo.dwMinorVersion == 51) )
        tkNumStaticColors = COLOR_BTNHIGHLIGHT - COLOR_SCROLLBAR + 1;
    else
        tkNumStaticColors = COLOR_INFOBK - COLOR_SCROLLBAR + 1;

    // Must not define CS_PARENTDC style.
    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = (WNDPROC)tkWndProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
    wndclass.lpszMenuName  = NULL;

    if (bUnicode)
        wndclass.lpszClassName = (LPCSTR)lpszClassNameW;
    else
        wndclass.lpszClassName = (LPCSTR)lpszClassName;

    if (bUnicode)
    {
        aRegister = RegisterClassW((CONST WNDCLASSW *)&wndclass);
    }
    else
    {
        aRegister = RegisterClass(&wndclass);
    }

    /*
     *  If the window failed to register, then there's no
     *  need to continue further.
     */

    if(0 == aRegister)
    {
        PrintMessage("Failed to register window class\n");
        return(Result);
    }

    /*
     *  Make window large enough to hold a client area as large as windInfo
     */

    WinRect.left   = windInfo.x;
    WinRect.right  = windInfo.x + windInfo.width;
    WinRect.top    = windInfo.y;
    WinRect.bottom = windInfo.y + windInfo.height;

    AdjustWindowRect(&WinRect, WS_OVERLAPPEDWINDOW, FALSE);

    /*
     *  Must use WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles.
     */

    if (bUnicode)
    {
        tkhwnd = CreateWindowW(
                    (LPCWSTR)lpszClassNameW,
                    (LPCWSTR)title,
                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                    (windInfo.bDefPos) ? CW_USEDEFAULT : WinRect.left,
                    (windInfo.bDefPos) ? CW_USEDEFAULT : WinRect.top,
                    WinRect.right - WinRect.left,
                    WinRect.bottom - WinRect.top,
                    NULL,
                    NULL,
                    hInstance,
                    NULL);
    }
    else
    {
        tkhwnd = CreateWindow(
                    lpszClassName,
                    title,
                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                    (windInfo.bDefPos) ? CW_USEDEFAULT : WinRect.left,
                    (windInfo.bDefPos) ? CW_USEDEFAULT : WinRect.top,
                    WinRect.right - WinRect.left,
                    WinRect.bottom - WinRect.top,
                    NULL,
                    NULL,
                    hInstance,
                    NULL);
    }

    if ( NULL != tkhwnd )
    {
        // If default window positioning used, find out window position and fix
        // up the windInfo position info.

        if (windInfo.bDefPos)
        {
            GetWindowRect(tkhwnd, &WinRect);
            windInfo.x = WinRect.left + GetSystemMetrics(SM_CXFRAME);
            windInfo.y = WinRect.top  + GetSystemMetrics(SM_CYCAPTION)
                         - GetSystemMetrics(SM_CYBORDER)
                         + GetSystemMetrics(SM_CYFRAME);
        }

        tkhdc = GetDC(tkhwnd);

        if ( NULL != tkhdc )
        {
            ShowWindow(tkhwnd, SW_SHOWDEFAULT);

            if ( FindPixelFormat(tkhdc, windInfo.type) )
            {
                /*
                 *  Create a Rendering Context
                 */

                tkhrc = wglCreateContext(tkhdc);

                if ( NULL != tkhrc )
                {
                    /*
                     *  Make it Current
                     */

                    if ( wglMakeCurrent(tkhdc, tkhrc) )
                    {
                        Result = GL_TRUE;
                    }
                    else
                    {
                        PrintMessage("wglMakeCurrent Failed\n");
                    }
                }
                else
                {
                    PrintMessage("wglCreateContext Failed\n");
                }
            }
        }
        else
        {
            PrintMessage("Could not get an HDC for window 0x%08lX\n", tkhwnd );
        }
    }
    else
    {
        PrintMessage("create window failed\n");
    }

    if ( GL_FALSE == Result )
    {
        DestroyThisWindow(tkhwnd);  // Something Failed, Destroy this window
    }
    return( Result );
}

/******************************************************************************/

/*
 * You cannot just call DestroyWindow() here.  The programs do not expect
 * tkQuit() to return;  DestroyWindow() just sends a WM_DESTROY message
 */

void tkQuit(void)
{
    DestroyThisWindow(tkhwnd);
    ExitProcess(0);
}

/******************************************************************************/

void tkSetOneColor(int index, float r, float g, float b)
{
    PALETTEENTRY PalEntry;
    HPALETTE Palette;

    if ( NULL != (Palette = CreateCIPalette( tkhdc )) )
    {
        if ( tkUseStaticColors && ( index == 0 || index == 255 ) )
            return;

        PalEntry.peRed   = (BYTE)(r*(float)255.0 + (float)0.5);
        PalEntry.peGreen = (BYTE)(g*(float)255.0 + (float)0.5);
        PalEntry.peBlue  = (BYTE)(b*(float)255.0 + (float)0.5);
        PalEntry.peFlags = ( tkUseStaticColors ) ? PC_NOCOLLAPSE : 0;

        // This is a workaround for a GDI palette "feature".  If any of
        // the static colors are repeated in the palette, those colors
        // will map to the first occurance.  So, for our case where there
        // are only two static colors (black and white), if a white
        // color appears anywhere in the palette other than in the last
        // entry, the static white will remap to the first white.  This
        // destroys the nice one-to-one mapping we are trying to achieve.
        //
        // There are two ways to workaround this.  The first is to
        // simply not allow a pure white anywhere but in the last entry.
        // Such requests are replaced with an attenuated white of
        // (0xFE, 0xFE, 0xFE).
        //
        // The other way is to mark these extra whites with PC_RESERVED
        // which will cause GDI to skip these entries when mapping colors.
        // This way the app gets the actual colors requested, but can
        // have side effects on other apps.
        //
        // Both solutions are included below.  The PC_RESERVED solution is
        // the one currently enabled.  It may have side effects, but taking
        // over the static colors as we are is a really big side effect that
        // should swamp out the effects of using PC_RESERVED.

        if ( tkUseStaticColors )
        {
            if ( PalEntry.peRed   == 0xFF &&
                 PalEntry.peGreen == 0xFF &&
                 PalEntry.peBlue  == 0xFF )
            {
            #define USE_PC_RESERVED_WORKAROUND  1
            #if USE_PC_RESERVED_WORKAROUND
                PalEntry.peFlags |= PC_RESERVED;
            #else
                PalEntry.peRed   =
                PalEntry.peGreen =
                PalEntry.peBlue  = 0xFE;
            #endif
            }
        }

        SetPaletteEntries( Palette, index, 1, &PalEntry);

        DelayPaletteRealization();
    }
}

void tkSetFogRamp(int density, int startIndex)
{
    HPALETTE CurrentPal;
    PALETTEENTRY *pPalEntry;
    UINT n, i, j, k, intensity, fogValues, colorValues;

    if ( NULL != (CurrentPal = CreateCIPalette(tkhdc)) )
    {
        n = GetPaletteEntries( CurrentPal, 0, 0, NULL );

        pPalEntry = AllocateMemory( n * sizeof(PALETTEENTRY) );

        if ( NULL != pPalEntry)
        {
            fogValues = 1 << density;
            colorValues = 1 << startIndex;
            for (i = 0; i < colorValues; i++) {
                for (j = 0; j < fogValues; j++) {
                    k = i * fogValues + j;

                    intensity = i * fogValues + j * colorValues;
                    //mf: not sure what they're trying to do here
                    //intensity = (intensity << 8) | intensity; ???

                // This is a workaround for a GDI palette "feature".  If any of
                // the static colors are repeated in the palette, those colors
                // will map to the first occurance.  So, for our case where there
                // are only two static colors (black and white), if a white
                // color appears anywhere in the palette other than in the last
                // entry, the static white will remap to the first white.  This
                // destroys the nice one-to-one mapping we are trying to achieve.
                //
                // There are two ways to workaround this.  The first is to
                // simply not allow a pure white anywhere but in the last entry.
                // Such requests are replaced with an attenuated white of
                // (0xFE, 0xFE, 0xFE).
                //
                // The other way is to mark these extra whites with PC_RESERVED
                // which will cause GDI to skip these entries when mapping colors.
                // This way the app gets the actual colors requested, but can
                // have side effects on other apps.
                //
                // Both solutions are included below.  The PC_RESERVED solution is
                // the one currently enabled.  It may have side effects, but taking
                // over the static colors as we are is a really big side effect that
                // should swamp out the effects of using PC_RESERVED.

                #if USE_PC_RESERVED_WORKAROUND
                    if (intensity > 0xFF)
                        intensity = 0xFF;
                #else
                    if (intensity >= 0xFF)
                        intensity = ( tkUseStaticColors && k != 255) ? 0xFE : 0xFF;
                #endif

                    pPalEntry[k].peRed =
                    pPalEntry[k].peGreen =
                    pPalEntry[k].peBlue = (BYTE) intensity;
                    pPalEntry[k].peFlags = ( tkUseStaticColors && k != 0 && k != 255 )
                                           ? PC_NOCOLLAPSE : 0;

                #if USE_PC_RESERVED_WORKAROUND
                    if (tkUseStaticColors && intensity == 0xFF
                        && k != 0 && k!= 255)
                        pPalEntry[k].peFlags |= PC_RESERVED;
                #endif
                }
            }

            SetPaletteEntries(CurrentPal, 0, n, pPalEntry);
            FreeMemory( pPalEntry );

            DelayPaletteRealization();
        }
    }
}

void tkSetGreyRamp(void)
{
    HPALETTE CurrentPal;
    PALETTEENTRY *Entries;
    UINT Count, i;
    float intensity;

    if ( NULL != (CurrentPal = CreateCIPalette( tkhdc )) )
    {
        Count   = GetPaletteEntries( CurrentPal, 0, 0, NULL );
        Entries = AllocateMemory( Count * sizeof(PALETTEENTRY) );

        if ( NULL != Entries )
        {
            for (i = 0; i < Count; i++)
            {
                intensity = (float)(((double)i / (double)(Count-1)) * (double)255.0 + (double)0.5);
                Entries[i].peRed =
                Entries[i].peGreen =
                Entries[i].peBlue = (BYTE) intensity;
                Entries[i].peFlags = ( tkUseStaticColors && i != 0 && i != 255 )
                                     ? PC_NOCOLLAPSE : 0;
            }
            SetPaletteEntries( CurrentPal, 0, Count, Entries );
            FreeMemory( Entries );

            DelayPaletteRealization();
        }
    }
}

void tkSetRGBMap( int Size, float *Values )
{
    HPALETTE CurrentPal;
    PIXELFORMATDESCRIPTOR Pfd, *pPfd;
    PALETTEENTRY *Entries;
    UINT Count;

    if ( NULL != (CurrentPal = CreateCIPalette( tkhdc )) )
    {
        pPfd = &Pfd;

        if ( PixelFormatDescriptorFromDc( tkhdc, pPfd ) )
        {
            Count    = 1 << pPfd->cColorBits;
            Entries  = AllocateMemory( Count * sizeof(PALETTEENTRY) );

            if ( NULL != Entries )
            {
                FillRgbPaletteEntries( pPfd, Entries, Count );
                SetPaletteEntries( CurrentPal, 0, Count, Entries );
                FreeMemory(Entries);

                RealizePaletteNow( tkhdc, tkhpalette, FALSE );
            }
        }
    }
}

/******************************************************************************/

void tkSwapBuffers(void)
{
    SwapBuffers(tkhdc);
}

/******************************************************************************/

GLint tkGetColorMapSize(void)
{
    CreateCIPalette( tkhdc );

    if ( NULL == tkhpalette )
        return( 0 );

    return( GetPaletteEntries( tkhpalette, 0, 0, NULL ) );
}

void tkGetMouseLoc(int *x, int *y)
{
    POINT Point;

    *x = 0;
    *y = 0;

    GetCursorPos(&Point);

    /*
     *  GetCursorPos returns screen coordinates,
     *  we want window coordinates
     */

    *x = Point.x - windInfo.x;
    *y = Point.y - windInfo.y;
}

HWND tkGetHWND(void)
{
    return tkhwnd;
}

HDC tkGetHDC(void)
{
    return tkhdc;
}

HGLRC tkGetHRC(void)
{
    return tkhrc;
}

GLenum tkGetDisplayModePolicy(void)
{
    return windInfo.dmPolicy;
}

GLint tkGetDisplayModeID(void)
{
    return windInfo.ipfd;
}

GLenum tkGetDisplayMode(void)
{
    return windInfo.type;
}

/***********************************************************************
 *                                                                     *
 *  The Following functions are for our own use only. (ie static)      *
 *                                                                     *
 ***********************************************************************/

static long
tkWndProc(HWND hWnd, UINT message, DWORD wParam, LONG lParam)
{
    int key;
    PAINTSTRUCT paint;
    HDC hdc;
    PIXELFORMATDESCRIPTOR pfd;

    switch (message) {

    case WM_USER:
        if ( RealizePaletteNow( tkhdc, tkhpalette, FALSE ) > 0 )
        {
            ForceRedraw( hWnd );
        }
        return(0);

    case WM_SIZE:
        windInfo.width  = LOWORD(lParam);
        windInfo.height = HIWORD(lParam);

        if (ReshapeFunc)
        {
            (*ReshapeFunc)(windInfo.width, windInfo.height);

            ForceRedraw( hWnd );
        }
        return (0);

    case WM_MOVE:
        windInfo.x = LOWORD(lParam);
        windInfo.y = HIWORD(lParam);
        return (0);

    case WM_PAINT:
        /*
         *  Validate the region even if there are no DisplayFunc.
         *  Otherwise, USER will not stop sending WM_PAINT messages.
         */

        hdc = BeginPaint(tkhwnd, &paint);

        if (DisplayFunc)
        {
            (*DisplayFunc)();
        }

        EndPaint(tkhwnd, &paint);
        return (0);

    case WM_QUERYNEWPALETTE:

    // We don't actually realize the palette here (we do it at WM_ACTIVATE
    // time), but we need the system to think that we have so that a
    // WM_PALETTECHANGED message is generated.

        return (1);

    case WM_PALETTECHANGED:

    // Respond to this message only if the window that changed the palette
    // is not this app's window.

    // We are not the foreground window, so realize palette in the
    // background.  We cannot call RealizePaletteNow to do this because
    // we should not do any of the tkUseStaticColors processing while
    // in background.

        if ( hWnd != (HWND) wParam )
        {
            if ( !tkSystemColorsInUse &&
                 NULL != tkhpalette &&
                 NULL != SelectPalette( tkhdc, tkhpalette, TRUE ) )
                RealizePalette( tkhdc );
        }

        return (0);

    case WM_SYSCOLORCHANGE:

    // If the system colors have changed and we have a palette
    // for an RGB surface then we need to recompute the static
    // color mapping because they might have been changed in
    // the process of changing the system colors.

        if (tkhdc != NULL && tkhpalette != NULL &&
            PixelFormatDescriptorFromDc(tkhdc, &pfd) &&
            (pfd.dwFlags & PFD_NEED_PALETTE) &&
            pfd.iPixelType == PFD_TYPE_RGBA)
        {
            HPALETTE hpalTmp;

            hpalTmp = tkhpalette;
            tkhpalette = NULL;
            if (CreateRGBPalette(tkhdc) != NULL)
            {
                DeleteObject(hpalTmp);
                ForceRedraw(hWnd);
            }
            else
            {
                tkhpalette = hpalTmp;
            }
        }
        break;

    case WM_ACTIVATE:

    // If the window is going inactive, the palette must be realized to
    // the background.  Cannot depend on WM_PALETTECHANGED to be sent since
    // the window that comes to the foreground may or may not be palette
    // managed.

        if ( LOWORD(wParam) == WA_INACTIVE )
        {
            if ( NULL != tkhpalette )
            {
            // Realize as a background palette.  Need to call
            // RealizePaletteNow rather than RealizePalette directly to
            // because it may be necessary to release usage of the static
            // system colors.

                if ( RealizePaletteNow( tkhdc, tkhpalette, TRUE ) > 0 )
                    ForceRedraw( hWnd );
            }
        }

    // Window is going active.  If we are not iconized, realize palette
    // to the foreground.  If management of the system static colors is
    // needed, RealizePaletteNow will take care of it.

        else if ( HIWORD(wParam) == 0 )
        {
            if ( NULL != tkhpalette )
            {
                if ( RealizePaletteNow( tkhdc, tkhpalette, FALSE ) > 0 )
                    ForceRedraw( hWnd );

                return (1);
            }
        }

    // Allow DefWindowProc() to finish the default processing (which includes
    // changing the keyboard focus).

        break;

    case WM_MOUSEMOVE:

        if (MouseMoveFunc)
        {
            GLenum mask;

            mask = 0;
            if (wParam & MK_LBUTTON) {
                mask |= TK_LEFTBUTTON;
            }
            if (wParam & MK_MBUTTON) {
                mask |= TK_MIDDLEBUTTON;
            }
            if (wParam & MK_RBUTTON) {
                mask |= TK_RIGHTBUTTON;
            }

            if ((*MouseMoveFunc)( LOWORD(lParam), HIWORD(lParam), mask ))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_LBUTTONDOWN:

        SetCapture(hWnd);

        if (MouseDownFunc)
        {
            if ( (*MouseDownFunc)(LOWORD(lParam), HIWORD(lParam),
                 TK_LEFTBUTTON) )
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_LBUTTONUP:

        ReleaseCapture();

        if (MouseUpFunc)
        {
            if ((*MouseUpFunc)(LOWORD(lParam), HIWORD(lParam), TK_LEFTBUTTON))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_MBUTTONDOWN:

        SetCapture(hWnd);

        if (MouseDownFunc)
        {
            if ((*MouseDownFunc)(LOWORD(lParam), HIWORD(lParam),
                    TK_MIDDLEBUTTON))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_MBUTTONUP:

        ReleaseCapture();

        if (MouseUpFunc)
        {
            if ((*MouseUpFunc)(LOWORD(lParam), HIWORD(lParam),
                TK_MIDDLEBUTTON))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_RBUTTONDOWN:

        SetCapture(hWnd);

        if (MouseDownFunc)
        {
            if ((*MouseDownFunc)(LOWORD(lParam), HIWORD(lParam),
                TK_RIGHTBUTTON))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_RBUTTONUP:

        ReleaseCapture();

        if (MouseUpFunc)
        {
            if ((*MouseUpFunc)(LOWORD(lParam), HIWORD(lParam),
                TK_RIGHTBUTTON))
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_KEYDOWN:
        switch (wParam) {
        case VK_SPACE:          key = TK_SPACE;         break;
        case VK_RETURN:         key = TK_RETURN;        break;
        case VK_ESCAPE:         key = TK_ESCAPE;        break;
        case VK_LEFT:           key = TK_LEFT;          break;
        case VK_UP:             key = TK_UP;            break;
        case VK_RIGHT:          key = TK_RIGHT;         break;
        case VK_DOWN:           key = TK_DOWN;          break;
        default:                key = GL_FALSE;         break;
        }

        if (key && KeyDownFunc)
        {
            GLenum mask;

            mask = 0;
            if (GetKeyState(VK_CONTROL)) {
                mask |= TK_CONTROL;
            }

            if (GetKeyState(VK_SHIFT)) {

                mask |= TK_SHIFT;
            }

            if ( (*KeyDownFunc)(key, mask) )
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_CHAR:
        if (('0' <= wParam && wParam <= '9') ||
            ('a' <= wParam && wParam <= 'z') ||
            ('A' <= wParam && wParam <= 'Z')) {

            key = wParam;
        } else {
            key = GL_FALSE;
        }

        if (key && KeyDownFunc) {
            GLenum mask;

            mask = 0;

            if (GetKeyState(VK_CONTROL)) {
                mask |= TK_CONTROL;
            }

            if (GetKeyState(VK_SHIFT)) {
                mask |= TK_SHIFT;
            }

            if ( (*KeyDownFunc)(key, mask) )
            {
                ForceRedraw( hWnd );
            }
        }
        return (0);

    case WM_CLOSE:
        DestroyWindow(tkhwnd);
        return(0);

    case WM_DESTROY:
        CleanUp();
        PostQuitMessage(TRUE);
        return 0;
    }
    return(DefWindowProc( hWnd, message, wParam, lParam));
}

#if RESTORE_FROM_REGISTRY
/******************************Public*Routine******************************\
* GetRegistrySysColors
*
* Reads the Control Panel's color settings from the registry and stores
* those values in pcr.  If we fail to get any value, then the corresponding
* entry in pcr is not modified.
*
* History:
*  12-Apr-1995 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static BOOL GetRegistrySysColors(COLORREF *pcr, int nColors)
{
    BOOL bRet = FALSE;
    long lRet;
    HKEY hkSysColors = (HKEY) NULL;
    int i;
    DWORD dwDataType;
    char achColor[64];
    DWORD cjColor;

    TKASSERT(nColors <= tkNumStaticColors);

// Open the key for the system color settings.

    lRet = RegOpenKeyExA(HKEY_CURRENT_USER,
                         "Control Panel\\Colors",
                         0,
                         KEY_QUERY_VALUE,
                         &hkSysColors);

    if ( lRet != ERROR_SUCCESS )
    {
        goto GetRegistrySysColors_exit;
    }

// Read each system color value.  The names are stored in the global
// array of char *, gaszSysClrNames.

    for (i = 0; i < nColors; i++)
    {
        cjColor = sizeof(achColor);
        lRet = RegQueryValueExA(hkSysColors,
                                (LPSTR) gaszSysClrNames[i],
                                (LPDWORD) NULL,
                                &dwDataType,
                                (LPBYTE) achColor,
                                &cjColor);

        TKASSERT(lRet != ERROR_MORE_DATA);

        if ( lRet == ERROR_SUCCESS && dwDataType == REG_SZ )
        {
            DWORD r, g, b;

            sscanf(achColor, "%ld %ld %ld", &r, &g, &b);
            pcr[i] = RGB(r, g, b);
        }
    }

    bRet = TRUE;

GetRegistrySysColors_exit:
    if (hkSysColors)
        RegCloseKey(hkSysColors);

    return bRet;
}
#endif

/******************************Public*Routine******************************\
* GrabStaticEntries
*
* Support routine for RealizePaletteNow to manage the static system color
* usage.
*
* This function will save the current static system color usage state.
* It will fail if:
*
*   1.  TK is not in "sys color in use state but system palette is in
*       SYSPAL_NOSTATIC mode.  This means that another app still possesses
*       the static system colors.  This this happens, GrabStaticEntries
*       will post a message to cause TK to try again (by calling
*       DelayPaletteRealization).
*
* Side effect:
*   If system colors are changed, then WM_SYSCOLORCHANGE message is
*   broadcast to all top level windows.
*
*   DelayPaletteRealization may be called in case 2 above, resulting in
*   a WM_USER message being posted to our message queue.
*
* Returns:
*   TRUE if successful, FALSE otherwise (see above).
*
* History:
*  26-Apr-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static BOOL GrabStaticEntries(HDC hdc)
{
    int i;
    BOOL bRet = FALSE;

// Do nothing if sys colors already in use.

    if ( !tkSystemColorsInUse )
    {
    // Take possession only if no other app has the static colors.
    // How can we tell?  If the return from SetSystemPaletteUse is
    // SYSPAL_STATIC, then no other app has the statics.  If it is
    // SYSPAL_NOSTATIC, someone else has them and we must fail.
    //
    // SetSystemPaletteUse is properly synchronized internally
    // so that it is atomic.
    //
    // Because we are relying on SetSystemPaletteUse to synchronize TK,
    // it is important to observe the following order for grabbing and
    // releasing:
    //
    //      Grab        call SetSystemPaletteUse and check for SYSPAL_STATIC
    //                  save sys color settings
    //                  set new sys color settings
    //
    //      Release     restore sys color settings
    //                  call SetSystemPaletteUse

        if ( SetSystemPaletteUse( hdc, SYSPAL_NOSTATIC ) == SYSPAL_STATIC )
        {
        // Save current sys color settings.

            for (i = COLOR_SCROLLBAR; i <= COLOR_BTNHIGHLIGHT; i++)
                gacrSave[i - COLOR_SCROLLBAR] = GetSysColor(i);

        // Set b&w sys color settings.  Put TK in "sys colors in use" state.

            SetSysColors(tkNumStaticColors, gaiStaticIndex, gacrBlackAndWhite);
            tkSystemColorsInUse = TRUE;

        // Inform all other top-level windows of the system color change.

            PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);

            bRet = TRUE;
        }

    // Sleep a little and then post message to try palette realization again
    // later.

        else
        {
            Sleep(0L);
            DelayPaletteRealization();
        }
    }
    else
        bRet = TRUE;

    return bRet;
}

/******************************Public*Routine******************************\
* ReleaseStaticEntries
*
* Support routine for RealizePaletteNow to manage the static system color
* usage.
*
* This function will reset the current static system color usage state.
* It will fail if:
*
*   1.  TK is not in a "sys colors in use" state.  If we are in this case,
*       then the static system colors do not need to be released.
*
* Side effect:
*   If system colors are changed, then WM_SYSCOLORCHANGE message is
*   broadcast to all top level windows.
*
* Returns:
*   TRUE if successful, FALSE otherwise (see above).
*
* History:
*  21-Jul-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static BOOL ReleaseStaticEntries(HDC hdc)
{
    BOOL bRet = FALSE;

// Do nothing if sys colors not in use.

    if ( tkSystemColorsInUse )
    {
#if RESTORE_FROM_REGISTRY
    // Replace saved system colors with registry values.  We do it now
    // rather than earlier because someone may have changed registry while
    // TK app was running in the foreground (very unlikely, but it could
    // happen).
    //
    // Also, we still try to save current setting in GrabStaticEntries so
    // that if for some reason we fail to grab one or more of the colors
    // from the registry, we can still fall back on what we grabbed via
    // GetSysColors (even though there is a chance its the wrong color).

        GetRegistrySysColors(gacrSave, tkNumStaticColors);
#endif

    // Restore the saved system color settings.

        SetSysColors(tkNumStaticColors, gaiStaticIndex, gacrSave);

    // Return the system palette to SYSPAL_STATIC.

        SetSystemPaletteUse( hdc, SYSPAL_STATIC );

    // Inform all other top-level windows of the system color change.

        PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);

    // Reset the "sys colors in use" state and return success.

        tkSystemColorsInUse = FALSE;
        bRet = TRUE;
    }

    return bRet;
}

// Default palette entry flags
#define PALETTE_FLAGS PC_NOCOLLAPSE

// Gamma correction factor * 10
#define GAMMA_CORRECTION 14

// Maximum color distance with 8-bit components
#define MAX_COL_DIST (3*256*256L)

// Number of static colors
#define STATIC_COLORS 20

// Flags used when matching colors
#define EXACT_MATCH 1
#define COLOR_USED 1

// Conversion tables for n bits to eight bits

#if GAMMA_CORRECTION == 10
// These tables are corrected for a gamma of 1.0
static unsigned char abThreeToEight[8] =
{
    0, 0111 >> 1, 0222 >> 1, 0333 >> 1, 0444 >> 1, 0555 >> 1, 0666 >> 1, 0377
};
static unsigned char abTwoToEight[4] =
{
    0, 0x55, 0xaa, 0xff
};
static unsigned char abOneToEight[2] =
{
    0, 255
};
#else
// These tables are corrected for a gamma of 1.4
static unsigned char abThreeToEight[8] =
{
    0, 63, 104, 139, 171, 200, 229, 255
};
static unsigned char abTwoToEight[4] =
{
    0, 116, 191, 255
};
static unsigned char abOneToEight[2] =
{
    0, 255
};
#endif

// Table which indicates which colors in a 3-3-2 palette should be
// replaced with the system default colors
#if GAMMA_CORRECTION == 10
static int aiDefaultOverride[STATIC_COLORS] =
{
    0, 4, 32, 36, 128, 132, 160, 173, 181, 245,
    247, 164, 156, 7, 56, 63, 192, 199, 248, 255
};
#else
static int aiDefaultOverride[STATIC_COLORS] =
{
    0, 3, 24, 27, 64, 67, 88, 173, 181, 236,
    247, 164, 91, 7, 56, 63, 192, 199, 248, 255
};
#endif

static unsigned char
ComponentFromIndex(int i, int nbits, int shift)
{
    unsigned char val;

    TKASSERT(nbits >= 1 && nbits <= 3);

    val = i >> shift;
    switch (nbits)
    {
    case 1:
        return abOneToEight[val & 1];

    case 2:
        return abTwoToEight[val & 3];

    case 3:
        return abThreeToEight[val & 7];
    }
}

// System default colors
static PALETTEENTRY apeDefaultPalEntry[STATIC_COLORS] =
{
    { 0,   0,   0,    0 },
    { 0x80,0,   0,    0 },
    { 0,   0x80,0,    0 },
    { 0x80,0x80,0,    0 },
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 },

    { 192, 220, 192,  0 },
    { 166, 202, 240,  0 },
    { 255, 251, 240,  0 },
    { 160, 160, 164,  0 },

    { 0x80,0x80,0x80, 0 },
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }
};

/******************************Public*Routine******************************\
*
* UpdateStaticMapping
*
* Computes the best match between the current system static colors
* and a 3-3-2 palette
*
* History:
*  Tue Aug 01 18:18:12 1995-by-Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

static void
UpdateStaticMapping(PALETTEENTRY *pe332Palette)
{
    HPALETTE hpalStock;
    int iStatic, i332;
    int iMinDist, iDist;
    int iDelta;
    int iMinEntry;
    PALETTEENTRY *peStatic, *pe332;

    hpalStock = GetStockObject(DEFAULT_PALETTE);

    // The system should always have one of these
    TKASSERT(hpalStock != NULL);
    // Make sure there's the correct number of entries
    TKASSERT(GetPaletteEntries(hpalStock, 0, 0, NULL) == STATIC_COLORS);

    // Get the current static colors
    GetPaletteEntries(hpalStock, 0, STATIC_COLORS, apeDefaultPalEntry);

    // Zero the flags in the static colors because they are used later
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Zero the flags in the incoming palette because they are used later
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = 0;
        pe332++;
    }

    // Try to match each static color exactly
    // This saves time by avoiding the least-squares match for each
    // exact match
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            if (peStatic->peRed == pe332->peRed &&
                peStatic->peGreen == pe332->peGreen &&
                peStatic->peBlue == pe332->peBlue)
            {
                TKASSERT(pe332->peFlags != COLOR_USED);

                peStatic->peFlags = EXACT_MATCH;
                pe332->peFlags = COLOR_USED;

aiDefaultOverride[iStatic] = i332;

                break;
            }

            pe332++;
        }

        peStatic++;
    }

    // Match each static color as closely as possible to an entry
    // in the 332 palette by minimized the square of the distance
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        // Skip colors already matched exactly
        if (peStatic->peFlags == EXACT_MATCH)
        {
            peStatic++;
            continue;
        }

        iMinDist = MAX_COL_DIST+1;
#if DBG
        iMinEntry = -1;
#endif

        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            // Skip colors already used
            if (pe332->peFlags == COLOR_USED)
            {
                pe332++;
                continue;
            }

            // Compute Euclidean distance squared
            iDelta = pe332->peRed-peStatic->peRed;
            iDist = iDelta*iDelta;
            iDelta = pe332->peGreen-peStatic->peGreen;
            iDist += iDelta*iDelta;
            iDelta = pe332->peBlue-peStatic->peBlue;
            iDist += iDelta*iDelta;

            if (iDist < iMinDist)
            {
                iMinDist = iDist;
                iMinEntry = i332;
            }

            pe332++;
        }

        TKASSERT(iMinEntry != -1);

        // Remember the best match
        aiDefaultOverride[iStatic] = iMinEntry;
        pe332Palette[iMinEntry].peFlags = COLOR_USED;

        peStatic++;
    }

    // Zero the flags in the static colors because they may have been
    // set.  We want them to be zero so the colors can be remapped
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Reset the 332 flags because we may have set them
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = PALETTE_FLAGS;
        pe332++;
    }

#if 0
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        PrintMessage("Static color %2d maps to %d\n",
                     iStatic, aiDefaultOverride[iStatic]);
    }
#endif
}

/******************************Public*Routine******************************\
* FillRgbPaletteEntries
*
* Fills a PALETTEENTRY array with values required for a logical rgb palette.
* If tkSetStaticColorUsage has been called with TRUE, the static system
* colors will be overridden.  Otherwise, the PALETTEENTRY array will be
* fixed up to contain the default static system colors.
*
* History:
*  26-Apr-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static PALETTEENTRY *
FillRgbPaletteEntries(  PIXELFORMATDESCRIPTOR *Pfd,
                        PALETTEENTRY *Entries,
                        UINT Count
                     )
{
    PALETTEENTRY *Entry;
    UINT i;

    if ( NULL != Entries )
    {
        for ( i = 0, Entry = Entries ; i < Count ; i++, Entry++ )
        {
            Entry->peRed   = ComponentFromIndex(i, Pfd->cRedBits,
                                    Pfd->cRedShift);
            Entry->peGreen = ComponentFromIndex(i, Pfd->cGreenBits,
                                    Pfd->cGreenShift);
            Entry->peBlue  = ComponentFromIndex(i, Pfd->cBlueBits,
                                    Pfd->cBlueShift);
            Entry->peFlags = PALETTE_FLAGS;
        }

        if ( 256 == Count)
        {
        // If app set static system color usage for fixed palette support,
        // setup to take over the static colors.  Otherwise, fixup the
        // static system colors.

            if ( tkUseStaticColors )
            {
            // Black and white already exist as the only remaining static
            // colors.  Let those remap.  All others should be put into
            // the palette (i.e., set PC_NOCOLLAPSE).

                Entries[0].peFlags = 0;
                Entries[255].peFlags = 0;
            }
            else
            {
            // The defaultOverride array is computed assuming a 332
            // palette where red has zero shift, etc.

                if ( (3 == Pfd->cRedBits)   && (0 == Pfd->cRedShift)   &&
                     (3 == Pfd->cGreenBits) && (3 == Pfd->cGreenShift) &&
                     (2 == Pfd->cBlueBits)  && (6 == Pfd->cBlueShift) )
                {
                    UpdateStaticMapping(Entries);

                    for ( i = 0 ; i < STATIC_COLORS ; i++)
                    {
                        Entries[aiDefaultOverride[i]] = apeDefaultPalEntry[i];
                    }
                }
            }
        }
    }
    return( Entries );
}

static HPALETTE
CreateRGBPalette( HDC Dc )
{
    PIXELFORMATDESCRIPTOR Pfd, *pPfd;
    LOGPALETTE *LogPalette;
    UINT Count;

    if ( NULL == tkhpalette )
    {
        pPfd = &Pfd;

        if ( PixelFormatDescriptorFromDc( Dc, pPfd ) )
        {
            /*
             *  Make sure we need a palette
             */

            if ( (pPfd->iPixelType == PFD_TYPE_RGBA) &&
                 (pPfd->dwFlags & PFD_NEED_PALETTE) )
            {
                /*
                 *  Note how palette is to be realized.  Take over the
                 *  system colors if either the pixel format requires it
                 *  or the app wants it.
                 */
                tkUseStaticColors = ( pPfd->dwFlags & PFD_NEED_SYSTEM_PALETTE )
                                    || TK_USE_FIXED_332_PAL(windInfo.type);

                Count       = 1 << pPfd->cColorBits;
                LogPalette  = AllocateMemory( sizeof(LOGPALETTE) +
                                Count * sizeof(PALETTEENTRY));

                if ( NULL != LogPalette )
                {
                    LogPalette->palVersion    = 0x300;
                    LogPalette->palNumEntries = Count;

                    FillRgbPaletteEntries( pPfd,
                                           &LogPalette->palPalEntry[0],
                                           Count );

                    tkhpalette = CreatePalette(LogPalette);
                    FreeMemory(LogPalette);

                    RealizePaletteNow( Dc, tkhpalette, FALSE );
                }
            }
        }
    }
    return( tkhpalette );
}

static HPALETTE
CreateCIPalette( HDC Dc )
{
    PIXELFORMATDESCRIPTOR Pfd;
    LOGPALETTE *LogicalPalette;
    HPALETTE StockPalette;
    UINT PaletteSize, StockPaletteSize, EntriesToCopy;

    if ( (Dc != NULL) && (NULL == tkhpalette) )
    {
        if ( PixelFormatDescriptorFromDc( Dc, &Pfd ) )
        {
            if ( Pfd.iPixelType == PFD_TYPE_COLORINDEX )
            {
                /*
                 *  Note how palette is to be realized (Is this the correct place to do this?)
                 */
                tkUseStaticColors = ( Pfd.dwFlags & PFD_NEED_SYSTEM_PALETTE )
                                    || TK_USE_FIXED_332_PAL(windInfo.type);

                /*
                 *  Limit the size of the palette to 256 colors.
                 *  Why? Because this is what was decided.
                 */

                PaletteSize = (Pfd.cColorBits >= 8) ? 256 : (1 << Pfd.cColorBits);

                LogicalPalette = AllocateZeroedMemory( sizeof(LOGPALETTE) +
                                        (PaletteSize * sizeof(PALETTEENTRY)) );

                if ( NULL != LogicalPalette )
                {
                    LogicalPalette->palVersion    = 0x300;
                    LogicalPalette->palNumEntries = PaletteSize;

                    StockPalette     = GetStockObject(DEFAULT_PALETTE);
                    StockPaletteSize = GetPaletteEntries( StockPalette, 0, 0, NULL );

                    /*
                     *  start by copying default palette into new one
                     */

                    EntriesToCopy = StockPaletteSize < PaletteSize ?
                                        StockPaletteSize : PaletteSize;

                    GetPaletteEntries( StockPalette, 0, EntriesToCopy,
                                        LogicalPalette->palPalEntry );

                    /*
                     *  If we are taking possession of the system colors,
                     *  must guarantee that 0 and 255 are black and white
                     *  (respectively).
                     */

                    if ( tkUseStaticColors && PaletteSize == 256 )
                    {
                        int i;

                        LogicalPalette->palPalEntry[0].peRed =
                        LogicalPalette->palPalEntry[0].peGreen =
                        LogicalPalette->palPalEntry[0].peBlue = 0x00;

                        LogicalPalette->palPalEntry[255].peRed =
                        LogicalPalette->palPalEntry[255].peGreen =
                        LogicalPalette->palPalEntry[255].peBlue = 0xFF;

                        LogicalPalette->palPalEntry[0].peFlags =
                        LogicalPalette->palPalEntry[255].peFlags = 0;

                        /*
                         *  All other entries should be remappable,
                         *  so mark them as PC_NOCOLLAPSE.
                         */
                        for ( i = 1; i < 255; i++ )
                            LogicalPalette->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
                    }

                    tkhpalette = CreatePalette(LogicalPalette);

                    FreeMemory(LogicalPalette);

                    RealizePaletteNow( Dc, tkhpalette, FALSE );
                }
            }
        }
    }
    return( tkhpalette );
}

static BOOL
FindPixelFormat(HDC hdc, GLenum type)
{
    PIXELFORMATDESCRIPTOR pfd;
    int PfdIndex;
    BOOL Result = FALSE;

    if ( TK_MINIMUM_CRITERIA == windInfo.dmPolicy )
        PfdIndex = FindBestPixelFormat(hdc, type, &pfd);
    else if ( TK_EXACT_MATCH == windInfo.dmPolicy )
        PfdIndex = FindExactPixelFormat(hdc, type, &pfd);
    else if ( IsPixelFormatValid(hdc, windInfo.ipfd, &pfd) )
        PfdIndex = windInfo.ipfd;
    else
        PfdIndex = 0;

    if ( PfdIndex )
    {
        if ( SetPixelFormat(hdc, PfdIndex, &pfd) )
        {
            /*
             *  If this pixel format requires a palette do it now.
             *  In colorindex mode, create a logical palette only
             *  if the application needs to modify it.
             */

            CreateRGBPalette( hdc );
            Result = TRUE;
        }
        else
        {
            PrintMessage("SetPixelFormat failed\n");
        }
    }
    else
    {
        PrintMessage("Selecting a pixel format failed\n");
    }
    return(Result);
}

static int
FindBestPixelFormat(HDC hdc, GLenum type, PIXELFORMATDESCRIPTOR *ppfd)
{
    PIXELFORMATDESCRIPTOR pfd;

    pfd.nSize       = sizeof(pfd);
    pfd.nVersion    = 1;
    pfd.dwFlags     = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;

    if (TK_IS_DOUBLE(type))
        pfd.dwFlags |= PFD_DOUBLEBUFFER;

    if (TK_IS_INDEX(type)) {
        pfd.iPixelType = PFD_TYPE_COLORINDEX;
        pfd.cColorBits = 8;
    } else {
        pfd.iPixelType = PFD_TYPE_RGBA;
        pfd.cColorBits = 24;
    }

    if (TK_HAS_ALPHA(type))
        pfd.cAlphaBits = 8;
    else
        pfd.cAlphaBits = 0;

    if (TK_HAS_ACCUM(type))
        pfd.cAccumBits = pfd.cColorBits + pfd.cAlphaBits;
    else
        pfd.cAccumBits = 0;

    if (TK_HAS_DEPTH(type)) {
        if (TK_IS_DEPTH16(type))
            pfd.cDepthBits = 16;
        else
            pfd.cDepthBits = 32;
    } else {
        pfd.cDepthBits = 0;
    }

    if (TK_HAS_STENCIL(type))
        pfd.cStencilBits = 4;
    else
        pfd.cStencilBits = 0;

    pfd.cAuxBuffers = 0;
    pfd.iLayerType  = PFD_MAIN_PLANE;
    *ppfd = pfd;

    return ( ChoosePixelFormat(hdc, &pfd) );
}

static int
FindExactPixelFormat(HDC hdc, GLenum type, PIXELFORMATDESCRIPTOR *ppfd)
{
    int i, MaxPFDs, Score, BestScore, BestPFD;
    PIXELFORMATDESCRIPTOR pfd;

    i = 1;
    BestPFD = BestScore = 0;
    do
    {
        MaxPFDs = DescribePixelFormat(hdc, i, sizeof(pfd), &pfd);
        if ( MaxPFDs <= 0 )
            return ( 0 );

        Score = 0;
        if ( !( ( pfd.dwFlags & PFD_DRAW_TO_WINDOW ) &&
                ( pfd.dwFlags & PFD_SUPPORT_OPENGL ) ) )
            continue;
        if ( pfd.iLayerType != PFD_MAIN_PLANE )
            continue;
        if ( ( pfd.iPixelType == PFD_TYPE_RGBA ) && ( TK_IS_INDEX(type) ) )
            continue;
        if ( ( pfd.iPixelType == PFD_TYPE_COLORINDEX ) && ( TK_IS_RGB(type) ) )
            continue;
        if ( ( pfd.dwFlags & PFD_DOUBLEBUFFER ) && ( TK_IS_SINGLE(type) ) )
            continue;
        if ( !( pfd.dwFlags & PFD_DOUBLEBUFFER ) && ( TK_IS_DOUBLE(type) ) )
            continue;

/* If accum requested then accum rgb size must be > 0 */
/* If alpha requested then alpha size must be > 0 */
/* if accum & alpha requested then accum alpha size must be > 0 */
        if ( TK_IS_RGB(type) )
        {
            if ( TK_HAS_ACCUM(type) )
            {
                if (  pfd.cAccumBits <= 0 )
                    continue;
            }
            else
            {
                if ( pfd.cAccumBits > 0 )
                    continue;
            }

            if ( TK_HAS_ALPHA(type) )
            {
                if ( pfd.cAlphaBits <= 0 )
                    continue;
                if ( TK_HAS_ACCUM(type) && pfd.cAccumAlphaBits <= 0 )
                    continue;
            }
            else
            {
                if ( pfd.cAlphaBits > 0 )
                    continue;
            }
        }

        if ( TK_HAS_DEPTH(type) )
        {
            if ( pfd.cDepthBits <= 0 )
                continue;
        }
        else
        {
            if ( pfd.cDepthBits > 0 )
                continue;
        }

        if ( TK_HAS_STENCIL(type) )
        {
            if ( pfd.cStencilBits <= 0 )
                continue;
        }
        else
        {
            if ( pfd.cStencilBits > 0 )
                continue;
        }

        Score = pfd.cColorBits;

        if (Score > BestScore)
        {
            BestScore = Score;
            BestPFD = i;
            *ppfd = pfd;
        }
    } while (++i <= MaxPFDs);

    return ( BestPFD );
}

static BOOL IsPixelFormatValid(HDC hdc, int ipfd, PIXELFORMATDESCRIPTOR *ppfd)
{
    if ( ipfd > 0 )
    {
        if ( ipfd <= DescribePixelFormat(hdc, ipfd, sizeof(*ppfd), ppfd) )
        {
            if ( ( ppfd->dwFlags & PFD_DRAW_TO_WINDOW ) &&
                 ( ppfd->dwFlags & PFD_SUPPORT_OPENGL ) )
            {
                return ( TRUE );
            }
        }
    }
    return ( FALSE );
}

static void
PrintMessage( const char *Format, ... )
{
    va_list ArgList;
    char Buffer[256];

    va_start(ArgList, Format);
    vsprintf(Buffer, Format, ArgList);
    va_end(ArgList);

    MESSAGEBOX(GetFocus(), Buffer, "Error", MB_OK);
}

static void
DelayPaletteRealization( void )
{
    MSG Message;

    TKASSERT(NULL!=tkhwnd);

    /*
     *  Add a WM_USER message to the queue, if there isn't one there already.
     */

    if (!PeekMessage(&Message, tkhwnd, WM_USER, WM_USER, PM_NOREMOVE) )
    {
        PostMessage( tkhwnd, WM_USER, 0, 0);
    }
}

/******************************Public*Routine******************************\
* RealizePaletteNow
*
* Select the given palette in background or foreground mode (as specified
* by the bForceBackground flag), and realize the palette.
*
* If static system color usage is set, the system colors are replaced.
*
* History:
*  26-Apr-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static long
RealizePaletteNow( HDC Dc, HPALETTE Palette, BOOL bForceBackground )
{
    long Result = -1;
    BOOL bHaveSysPal = TRUE;

    TKASSERT( NULL!=Dc      );
    TKASSERT( NULL!=Palette );

// If static system color usage is set, prepare to take over the
// system palette.

    if ( tkUseStaticColors )
    {
    // If foreground, take over the static colors.  If background, release
    // the static colors.

        if ( !bForceBackground )
        {
        // If GrabStaticEntries succeeds, then it is OK to take over the
        // static colors.  If not, then GrabStaticEntries will have
        // posted a WM_USER message for us to try again later.

            bHaveSysPal = GrabStaticEntries( Dc );
        }
        else
        {
        // If we are currently using the system colors (tkSystemColorsInUse)
        // and RealizePaletteNow was called with bForceBackground set, we
        // are being deactivated and must release the static system colors.

            ReleaseStaticEntries( Dc );
        }

    // Rerealize the palette.
    //
    // If set to TRUE, bForceBackground will force the palette to be realized
    // as a background palette, regardless of focus.  This will happen anyway
    // if the TK window does not have the keyboard focus.

        if ( (bForceBackground || bHaveSysPal) &&
             UnrealizeObject( Palette ) &&
             NULL != SelectPalette( Dc, Palette, bForceBackground ) )
        {
            Result = RealizePalette( Dc );
        }
    }
    else
    {
        if ( NULL != SelectPalette( Dc, Palette, FALSE ) )
        {
            Result = RealizePalette( Dc );
        }
    }

    return( Result );
}

static void
ForceRedraw( HWND Window )
{
    MSG Message;

    if (!PeekMessage(&Message, Window, WM_PAINT, WM_PAINT, PM_NOREMOVE) )
    {
        InvalidateRect( Window, NULL, FALSE );
    }
}

static int
PixelFormatDescriptorFromDc( HDC Dc, PIXELFORMATDESCRIPTOR *Pfd )
{
    int PfdIndex;

    if ( 0 < (PfdIndex = GetPixelFormat( Dc )) )
    {
        if ( 0 < DescribePixelFormat( Dc, PfdIndex, sizeof(*Pfd), Pfd ) )
        {
            return(PfdIndex);
        }
        else
        {
            PrintMessage("Could not get a description of pixel format %d\n",
                PfdIndex );
        }
    }
    else
    {
        PrintMessage("Could not get pixel format for Dc 0x%08lX\n", Dc );
    }
    return( 0 );
}

static void
DestroyThisWindow( HWND Window )
{
    if ( NULL != Window )
    {
        DestroyWindow( Window );
    }
}

/*
 *  This Should be called in response to a WM_DESTROY message
 */

static void
CleanUp( void )
{
    HPALETTE hStock;

// Cleanup the palette.

    if ( NULL != tkhpalette )
    {
    // If static system color usage is set, restore the system colors.

        if ( tkUseStaticColors )
        {
            RealizePaletteNow( tkhdc, GetStockObject(DEFAULT_PALETTE), TRUE );
        }
        else
        {
            if ( hStock = GetStockObject( DEFAULT_PALETTE ) )
                SelectPalette( tkhdc, hStock, FALSE );
        }

        DeleteObject( tkhpalette );
    }

// Cleanup the RC.

    if ( NULL != tkhrc )
    {
        wglMakeCurrent( tkhdc, NULL );  // Release first...
        wglDeleteContext( tkhrc );      // then delete.
    }

// Cleanup the DC.

    if ( NULL != tkhdc )
    {
        ReleaseDC( tkhwnd, tkhdc );
    }

// Be really nice and reset global values.

    tkhwnd        = NULL;
    tkhdc         = NULL;
    tkhrc         = NULL;
    tkhpalette    = NULL;

    ExposeFunc    = NULL;
    ReshapeFunc   = NULL;
    IdleFunc      = NULL;
    DisplayFunc   = NULL;
    KeyDownFunc   = NULL;
    MouseDownFunc = NULL;
    MouseUpFunc   = NULL;
    MouseMoveFunc = NULL;
}

static void *
AllocateMemory( size_t Size )
{
    return( LocalAlloc( LMEM_FIXED, Size ) );
}

static void *
AllocateZeroedMemory( size_t Size )
{
    return( LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, Size ) );
}

static void
FreeMemory( void *Chunk )
{
    TKASSERT( NULL!=Chunk );

    LocalFree( Chunk );
}

/*******************************************************************
 *                                                                 *
 *  Debugging functions go here                                    *
 *                                                                 *
 *******************************************************************/

#if DBGFUNC

static void
DbgPrintf( const char *Format, ... )
{
    va_list ArgList;
    char Buffer[256];

    va_start(ArgList, Format);
    vsprintf(Buffer, Format, ArgList);
    va_end(ArgList);

    printf("%s", Buffer );
    fflush(stdout);
}

static void
pwi( void )
{
    DbgPrintf("windInfo: x %d, y %d, w %d, h %d\n", windInfo.x, windInfo.y, windInfo.width, windInfo.height);
}

static void
pwr(RECT *pr)
{
    DbgPrintf("Rect: left %d, top %d, right %d, bottom %d\n", pr->left, pr->top, pr->right, pr->bottom);
}

static void
ShowPixelFormat(HDC hdc)
{
    PIXELFORMATDESCRIPTOR pfd, *ppfd;
    int format;

    ppfd   = &pfd;
    format = PixelFormatDescriptorFromDc( hdc, ppfd );

    DbgPrintf("Pixel format %d\n", format);
    DbgPrintf("  dwFlags - 0x%x", ppfd->dwFlags);
        if (ppfd->dwFlags & PFD_DOUBLEBUFFER) DbgPrintf("PFD_DOUBLEBUFFER ");
        if (ppfd->dwFlags & PFD_STEREO) DbgPrintf("PFD_STEREO ");
        if (ppfd->dwFlags & PFD_DRAW_TO_WINDOW) DbgPrintf("PFD_DRAW_TO_WINDOW ");
        if (ppfd->dwFlags & PFD_DRAW_TO_BITMAP) DbgPrintf("PFD_DRAW_TO_BITMAP ");
        if (ppfd->dwFlags & PFD_SUPPORT_GDI) DbgPrintf("PFD_SUPPORT_GDI ");
        if (ppfd->dwFlags & PFD_SUPPORT_OPENGL) DbgPrintf("PFD_SUPPORT_OPENGL ");
        if (ppfd->dwFlags & PFD_GENERIC_FORMAT) DbgPrintf("PFD_GENERIC_FORMAT ");
        if (ppfd->dwFlags & PFD_NEED_PALETTE) DbgPrintf("PFD_NEED_PALETTE ");
        if (ppfd->dwFlags & PFD_NEED_SYSTEM_PALETTE) DbgPrintf("PFD_NEED_SYSTEM_PALETTE ");
        DbgPrintf("\n");
    DbgPrintf("  iPixelType - %d", ppfd->iPixelType);
        if (ppfd->iPixelType == PFD_TYPE_RGBA) DbgPrintf("PGD_TYPE_RGBA\n");
        if (ppfd->iPixelType == PFD_TYPE_COLORINDEX) DbgPrintf("PGD_TYPE_COLORINDEX\n");
    DbgPrintf("  cColorBits - %d\n", ppfd->cColorBits);
    DbgPrintf("  cRedBits - %d\n", ppfd->cRedBits);
    DbgPrintf("  cRedShift - %d\n", ppfd->cRedShift);
    DbgPrintf("  cGreenBits - %d\n", ppfd->cGreenBits);
    DbgPrintf("  cGreenShift - %d\n", ppfd->cGreenShift);
    DbgPrintf("  cBlueBits - %d\n", ppfd->cBlueBits);
    DbgPrintf("  cBlueShift - %d\n", ppfd->cBlueShift);
    DbgPrintf("  cAlphaBits - %d\n", ppfd->cAlphaBits);
    DbgPrintf("  cAlphaShift - 0x%x\n", ppfd->cAlphaShift);
    DbgPrintf("  cAccumBits - %d\n", ppfd->cAccumBits);
    DbgPrintf("  cAccumRedBits - %d\n", ppfd->cAccumRedBits);
    DbgPrintf("  cAccumGreenBits - %d\n", ppfd->cAccumGreenBits);
    DbgPrintf("  cAccumBlueBits - %d\n", ppfd->cAccumBlueBits);
    DbgPrintf("  cAccumAlphaBits - %d\n", ppfd->cAccumAlphaBits);
    DbgPrintf("  cDepthBits - %d\n", ppfd->cDepthBits);
    DbgPrintf("  cStencilBits - %d\n", ppfd->cStencilBits);
    DbgPrintf("  cAuxBuffers - %d\n", ppfd->cAuxBuffers);
    DbgPrintf("  iLayerType - %d\n", ppfd->iLayerType);
    DbgPrintf("  bReserved - %d\n", ppfd->bReserved);
    DbgPrintf("  dwLayerMask - 0x%x\n", ppfd->dwLayerMask);
    DbgPrintf("  dwVisibleMask - 0x%x\n", ppfd->dwVisibleMask);
    DbgPrintf("  dwDamageMask - 0x%x\n", ppfd->dwDamageMask);

}

#endif  /* DBG */


TK.H

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

/*
** Window Types
*/

#define TK_RGB0
#define TK_INDEX1
#define TK_SINGLE0
#define TK_DOUBLE2
#define TK_DIRECT0
#define TK_INDIRECT4
#define TK_ACCUM8
#define TK_ALPHA16
#define TK_DEPTH        32      /* 32-bit depth buffer */
#define TK_STENCIL      64
#define TK_DEPTH16      256     /* 16-bit depth buffer */
#define TK_FIXED_332_PAL    512

/*
** Display Mode Selection Criteria
*/

enum {
    TK_USE_ID = 1,
    TK_EXACT_MATCH,
    TK_MINIMUM_CRITERIA
};

/*
** Window Masks
*/

#define TK_IS_RGB(x)(((x) & TK_INDEX) == 0)
#define TK_IS_INDEX(x)(((x) & TK_INDEX) != 0)
#define TK_IS_SINGLE(x)(((x) & TK_DOUBLE) == 0)
#define TK_IS_DOUBLE(x)(((x) & TK_DOUBLE) != 0)
#define TK_HAS_ACCUM(x)(((x) & TK_ACCUM) != 0)
#define TK_HAS_ALPHA(x)(((x) & TK_ALPHA) != 0)
#define TK_HAS_DEPTH(x)         (((x) & (TK_DEPTH | TK_DEPTH16)) != 0)
#define TK_HAS_STENCIL(x)(((x) & TK_STENCIL) != 0)
#define TK_IS_DIRECT(x)(((x) & TK_INDIRECT) == 0)
#define TK_IS_INDIRECT(x)       (((x) & TK_INDIRECT) != 0)
#define TK_USE_FIXED_332_PAL(x) (((x) & TK_FIXED_332_PAL) != 0)
#define TK_IS_DEPTH16(x)        (((x) & TK_DEPTH16) != 0)

/*
** Event Status
*/

#defineTK_LEFTBUTTON1
#defineTK_RIGHTBUTTON2
#defineTK_MIDDLEBUTTON4
#defineTK_SHIFT1
#defineTK_CONTROL2

/*
** Key Codes
*/

#define TK_RETURN0x0D
#define TK_ESCAPE0x1B
#define TK_SPACE0x20
#define TK_LEFT0x25
#define TK_UP0x26
#define TK_RIGHT0x27
#define TK_DOWN0x28
#define TK_A'A'
#define TK_B'B'
#define TK_C'C'
#define TK_D'D'
#define TK_E'E'
#define TK_F'F'
#define TK_G'G'
#define TK_H'H'
#define TK_I'I'
#define TK_J'J'
#define TK_K'K'
#define TK_L'L'
#define TK_M'M'
#define TK_N'N'
#define TK_O'O'
#define TK_P'P'
#define TK_Q'Q'
#define TK_R'R'
#define TK_S'S'
#define TK_T'T'
#define TK_U'U'
#define TK_V'V'
#define TK_W'W'
#define TK_X'X'
#define TK_Y'Y'
#define TK_Z'Z'
#define TK_a'a'
#define TK_b'b'
#define TK_c'c'
#define TK_d'd'
#define TK_e'e'
#define TK_f'f'
#define TK_g'g'
#define TK_h'h'
#define TK_i'i'
#define TK_j'j'
#define TK_k'k'
#define TK_l'l'
#define TK_m'm'
#define TK_n'n'
#define TK_o'o'
#define TK_p'p'
#define TK_q'q'
#define TK_r'r'
#define TK_s's'
#define TK_t't'
#define TK_u'u'
#define TK_v'v'
#define TK_w'w'
#define TK_x'x'
#define TK_y'y'
#define TK_z'z'
#define TK_0'0'
#define TK_1'1'
#define TK_2'2'
#define TK_3'3'
#define TK_4'4'
#define TK_5'5'
#define TK_6'6'
#define TK_7'7'
#define TK_8'8'
#define TK_9'9'

/*
** Color Macros - Default Windows Logical palette
*/

enum {
    TK_BLACK = 0,
    TK_RED = 13,
    TK_GREEN,
    TK_YELLOW,
    TK_BLUE,
    TK_MAGENTA,
    TK_CYAN,
    TK_WHITE
};

extern float auxRGBMap[20][3];
#define tkRGBMap auxRGBMap

#define TK_SETCOLOR(x, y) (TK_IS_RGB((x)) ? \
           glColor3fv(auxRGBMap[(y)]) : glIndexf((y)))

/*
** RGB Image Structure
*/

typedef struct _TK_RGBImageRec {
    GLint sizeX, sizeY;
    unsigned char *data;
} TK_RGBImageRec;

/*
** Prototypes
*/

extern void tkInitDisplayMode(GLenum);
extern void tkInitPosition(int, int, int, int);
extern void tkInitDisplayModePolicy(GLenum);
extern GLenum tkInitDisplayModeID(GLint);
extern GLenum tkInitWindow(char *);
extern GLenum tkInitWindowAW(char *, BOOL);
extern void tkCloseWindow(void);
extern void tkQuit(void);

extern void tkExec(void);
extern void tkExposeFunc(void (*)(int, int));
extern void tkReshapeFunc(void (*)(GLsizei, GLsizei));
extern void tkDisplayFunc(void (*)(void));
extern void tkKeyDownFunc(GLenum (*)(int, GLenum));
extern void tkMouseDownFunc(GLenum (*)(int, int, GLenum));
extern void tkMouseUpFunc(GLenum (*)(int, int, GLenum));
extern void tkMouseMoveFunc(GLenum (*)(int, int, GLenum));
extern void tkIdleFunc(void (*)(void));

extern void tkSwapBuffers(void);

extern GLint tkGetColorMapSize(void);
extern void tkGetMouseLoc(int *, int *);
extern GLint tkGetDisplayModeID(void);
extern GLenum tkGetDisplayModePolicy(void);
extern GLenum tkGetDisplayMode(void);

extern void tkSetOneColor(int, float, float, float);
extern void tkSetFogRamp(int, int);
extern void tkSetGreyRamp(void);
extern void tkSetRGBMap(int, float *);

extern TK_RGBImageRec *tkRGBImageLoad(char *);
extern TK_RGBImageRec *tkRGBImageLoadAW(char *, BOOL);

extern TK_RGBImageRec *tkDIBImageLoad(char *);
extern TK_RGBImageRec *tkDIBImageLoadAW(char *, BOOL);

extern GLenum tkCreateStrokeFont(GLuint);
extern GLenum tkCreateOutlineFont(GLuint);
extern GLenum tkCreateFilledFont(GLuint);
extern GLenum tkCreateBitmapFont(GLuint);
extern void tkDrawStr(GLuint, char *);

/* Windows specific routines */
extern HWND  tkGetHWND(void);
extern HDC   tkGetHDC(void);
extern HGLRC tkGetHRC(void);

/* Error popup control */
extern void tkErrorPopups(GLboolean);
extern GLboolean tkPopupEnable;
#define MESSAGEBOX(a, b, c, d)  if (tkPopupEnable) MessageBoxA((a), (b), (c), (d))


TKDIB.C

/******************************Module*Header*******************************\
* Module Name: dib.c
*
* (Brief description)
*
* Created: 21-Feb-1994 23:12:58
* Author: Gilman Wong [gilmanw]
*
* Copyright (c) 1994 Microsoft Corporation
*
* (General description of its use)
*
* Dependencies:
*
*   (#defines)
*   (#includes)
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "tk.h"

#define static

#define BFT_BITMAP  0x4d42  // 'BM' -- indicates structure is BITMAPFILEHEADER

// struct BITMAPFILEHEADER {
//      WORD  bfType
//      DWORD bfSize
//      WORD  bfReserved1
//      WORD  bfReserved2
//      DWORD bfOffBits
// }
#define OFFSET_bfType       0
#define OFFSET_bfSize       2
#define OFFSET_bfReserved1  6
#define OFFSET_bfReserved2  8
#define OFFSET_bfOffBits    10
#define SIZEOF_BITMAPFILEHEADER 14

// Read a WORD-aligned DWORD.  Needed because BITMAPFILEHEADER has
// WORD-alignment.
#define READDWORD(pv)   ( (DWORD)((PWORD)(pv))[0]               \
                          | ((DWORD)((PWORD)(pv))[1] << 16) )   \

// Computes the number of BYTES needed to contain n number of bits.
#define BITS2BYTES(n)   ( ((n) + 7) >> 3 )

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibNumColors(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    : Determines the number of colors in the DIB by looking at   *
 *               the BitCount filed in the info block.                      *
 *                                                                          *
 *  RETURNS    : The number of colors in the DIB.                           *
 *                                                                          *
 * Stolen from SDK ShowDIB example.                                         *
 ****************************************************************************/

WORD DibNumColors(VOID FAR * pv)
{
    WORD                bits;
    BITMAPINFOHEADER UNALIGNED *lpbi;
    BITMAPCOREHEADER UNALIGNED *lpbc;

    lpbi = ((LPBITMAPINFOHEADER)pv);
    lpbc = ((LPBITMAPCOREHEADER)pv);

    /*  With the BITMAPINFO format headers, the size of the palette
     *  is in biClrUsed, whereas in the BITMAPCORE - style headers, it
     *  is dependent on the bits per pixel ( = 2 raised to the power of
     *  bits/pixel).
     *
     *  Because of the way we use this call, BITMAPINFOHEADER may be out
     *  of alignment if it follows a BITMAPFILEHEADER.  So use the macro
     *  to safely access DWORD fields.
     */
    if (READDWORD(&lpbi->biSize) != sizeof(BITMAPCOREHEADER)){
        if (READDWORD(&lpbi->biClrUsed) != 0)
        {
            return (WORD) READDWORD(&lpbi->biClrUsed);
        }
        bits = lpbi->biBitCount;
    }
    else
        bits = lpbc->bcBitCount;

    switch (bits){
        case 1:
            return 2;
        case 4:
            return 16;
        case 8:
            return 256;
        default:
            /* A 24 bitcount DIB has no color table */
            return 0;
    }
}

/******************************Public*Routine******************************\
* tkDIBImageLoad
*
* ANSI version stub.  Only here for orthogonality with tkRGBImageLoad.
*
* History:
*  22-Feb-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

TK_RGBImageRec *tkDIBImageLoadAW(char *fileName, BOOL bUnicode);

TK_RGBImageRec *tkDIBImageLoad(char *fileName)
{
    return tkDIBImageLoadAW(fileName, FALSE);
}

/******************************Public*Routine******************************\
* tkDIBImageLoadAW
*
* Loads a DIB file (specified as either an ANSI or Unicode filename,
* depending on the bUnicode flag) and converts it into a TK image format.
*
* The technique used is based on CreateDIBSection and SetDIBits.
* CreateDIBSection is used to create a DIB with a format easily converted
* into the TK image format (packed 24BPP RGB).  The only conversion
* required is swapping R and B in each RGB triplet (see history below)
* The resulting bitmap is selected into a memory DC.
*
* The DIB file is mapped into memory and SetDIBits called to initialize
* the memory DC bitmap.  It is during this step that GDI converts the
* arbitrary DIB file format to RGB format.
*
* Finally, the RGB data in the DIB section is read out and repacked
* as 24BPP 'BGR'.
*
* Returns:
*   Pointer to TK_RGBImageRec.  If an error occurs, a diagnostic error
*   message is put into the error stream and tkQuit() is called,
*   terminating the app.
*
* History:
*  - 22-Feb-1994 -by- Gilman Wong [gilmanw]
*    Wrote it.
*
*  - 01-May-1995 : [marcfo]
*    Don't quit if can't open DIB file - return NULL.
*
*  - 27-Jul-1995 : [marcfo]
*    Changed CreateDIBSection to create an RGB mapping, so this would work
*    on win95.  Swapping of R and B required, to generate BGR (R=low byte)
*    for GL.  If the GL 'BGR' (R=high byte) extension becomes part of api
*    in OpenGL 2.0, then we can avoid swap and do a memcpy to grab the bits.
*
\**************************************************************************/

TK_RGBImageRec *tkDIBImageLoadAW(char *fileName, BOOL bUnicode)
{
    TK_RGBImageRec *final = (TK_RGBImageRec *) NULL; // Ptr to TK image struct
                                                     // to return.  Non-NULL
                                                     // only for success.

    WORD             wNumColors;    // Number of colors in color table
    BITMAPFILEHEADER *pbmf;         // Ptr to file header
    BITMAPINFOHEADER UNALIGNED *pbmihFile;
    BITMAPCOREHEADER UNALIGNED *pbmchFile; // Ptr to file's core header (if it exists)
    PVOID            pvBitsFile;    // Ptr to bitmap bits in file
    PBYTE            pjBitsRGB;     // Ptr to 24BPP RGB image in DIB section
    PBYTE            pjTKBits = (PBYTE) NULL;   // Ptr to final TK image bits
    PBYTE            pjSrc;         // Ptr to image file used for conversion
    PBYTE            pjDst;         // Ptr to TK image used for conversion

    // These need to be cleaned up when we exit:
    HANDLE     hFile = INVALID_HANDLE_VALUE;        // File handle
    HANDLE     hMap = (HANDLE) NULL;                // Mapping object handle
    PVOID      pvFile = (PVOID) NULL;               // Ptr to mapped file
    HDC        hdcMem = (HDC) NULL;                 // 24BPP mem DC
    HBITMAP    hbmRGB = (HBITMAP) NULL;             // 24BPP RGB bitmap
    BITMAPINFO *pbmiSource = (BITMAPINFO *) NULL;   // Ptr to source BITMAPINFO
    BITMAPINFO *pbmiRGB = (BITMAPINFO *) NULL;      // Ptr to file's BITMAPINFO

    int i, j;
    int padBytes;

// Map the DIB file into memory.

    hFile = bUnicode ?
            CreateFileW((LPWSTR) fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, 0, 0) :
            CreateFileA((LPSTR) fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, 0, 0);

    if (hFile == INVALID_HANDLE_VALUE)
        goto tkDIBLoadImage_cleanup;

    hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMap)
        goto tkDIBLoadImage_cleanup;

    pvFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
    if (!pvFile)
        goto tkDIBLoadImage_cleanup;

// Check the file header.  If the BFT_BITMAP magic number is there,
// then the file format is a BITMAPFILEHEADER followed immediately
// by either a BITMAPINFOHEADER or a BITMAPCOREHEADER.  The bitmap
// bits, in this case, are located at the offset bfOffBits from the
// BITMAPFILEHEADER.
//
// Otherwise, this may be a raw BITMAPINFOHEADER or BITMAPCOREHEADER
// followed immediately with the color table and the bitmap bits.

    pbmf = (BITMAPFILEHEADER *) pvFile;

    if ( pbmf->bfType == BFT_BITMAP )
    {
        pbmihFile = (BITMAPINFOHEADER *) ((PBYTE) pbmf + SIZEOF_BITMAPFILEHEADER);

    // BITMAPFILEHEADER is WORD aligned, so use safe macro to read DWORD
    // bfOffBits field.

        pvBitsFile = (PVOID *) ((PBYTE) pbmf
                                + READDWORD((PBYTE) pbmf + OFFSET_bfOffBits));
    }
    else
    {
        pbmihFile = (BITMAPINFOHEADER *) pvFile;

    // Determination of where the bitmaps bits are needs to wait until we
    // know for sure whether we have a BITMAPINFOHEADER or a BITMAPCOREHEADER.
    }

// Determine the number of colors in the DIB palette.  This is non-zero
// only for 8BPP or less.

    wNumColors = DibNumColors(pbmihFile);

// Create a BITMAPINFO (with color table) for the DIB file.  Because the
// file may not have one (BITMAPCORE case) and potential alignment problems,
// we will create a new one in memory we allocate.
//
// We distinguish between BITMAPINFO and BITMAPCORE cases based upon
// BITMAPINFOHEADER.biSize.

    pbmiSource = (BITMAPINFO *)
        LocalAlloc(LMEM_FIXED, sizeof(BITMAPINFO)
                               + wNumColors * sizeof(RGBQUAD));
    if (!pbmiSource)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    // Note: need to use safe READDWORD macro because pbmihFile may
    // have only WORD alignment if it follows a BITMAPFILEHEADER.

    switch (READDWORD(&pbmihFile->biSize))
    {
    case sizeof(BITMAPINFOHEADER):

    // Convert WORD-aligned BITMAPINFOHEADER to aligned BITMAPINFO.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = READDWORD(&pbmihFile->biWidth);
        pbmiSource->bmiHeader.biHeight        = READDWORD(&pbmihFile->biHeight);
        pbmiSource->bmiHeader.biPlanes        = pbmihFile->biPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmihFile->biBitCount;
        pbmiSource->bmiHeader.biCompression   =
                                        READDWORD(&pbmihFile->biCompression);
        pbmiSource->bmiHeader.biSizeImage     =
                                        READDWORD(&pbmihFile->biSizeImage);
        pbmiSource->bmiHeader.biXPelsPerMeter =
                                        READDWORD(&pbmihFile->biXPelsPerMeter);
        pbmiSource->bmiHeader.biYPelsPerMeter =
                                        READDWORD(&pbmihFile->biYPelsPerMeter);
        pbmiSource->bmiHeader.biClrUsed       =
                                        READDWORD(&pbmihFile->biClrUsed);
        pbmiSource->bmiHeader.biClrImportant  =
                                        READDWORD(&pbmihFile->biClrImportant);

    // Copy color table.  It immediately follows the BITMAPINFOHEADER.

        memcpy((PVOID) &pbmiSource->bmiColors[0], (PVOID) (pbmihFile + 1),
               wNumColors * sizeof(RGBQUAD));

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBitsFile)
            pvBitsFile = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBQUAD));
        break;

    case sizeof(BITMAPCOREHEADER):
        pbmchFile = (BITMAPCOREHEADER *) pbmihFile;

    // Convert BITMAPCOREHEADER to BITMAPINFOHEADER.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = (DWORD) pbmchFile->bcWidth;
        pbmiSource->bmiHeader.biHeight        = (DWORD) pbmchFile->bcHeight;
        pbmiSource->bmiHeader.biPlanes        = pbmchFile->bcPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmchFile->bcBitCount;
        pbmiSource->bmiHeader.biCompression   = BI_RGB;
        pbmiSource->bmiHeader.biSizeImage     = 0;
        pbmiSource->bmiHeader.biXPelsPerMeter = 0;
        pbmiSource->bmiHeader.biYPelsPerMeter = 0;
        pbmiSource->bmiHeader.biClrUsed       = wNumColors;
        pbmiSource->bmiHeader.biClrImportant  = wNumColors;

    // Convert RGBTRIPLE color table into RGBQUAD color table.

        {
            RGBQUAD *rgb4 = pbmiSource->bmiColors;
            RGBTRIPLE *rgb3 = (RGBTRIPLE *) (pbmchFile + 1);

            for (i = 0; i < wNumColors; i++)
            {
                rgb4->rgbRed   = rgb3->rgbtRed  ;
                rgb4->rgbGreen = rgb3->rgbtGreen;
                rgb4->rgbBlue  = rgb3->rgbtBlue ;
                rgb4->rgbReserved = 0;

                rgb4++;
                rgb3++;
            }
        }

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBitsFile)
            pvBitsFile = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBTRIPLE));
        break;

    default:
        MESSAGEBOX(GetFocus(), "Unknown DIB file format.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Fill in default values (for fields that can have defaults).

    if (pbmiSource->bmiHeader.biSizeImage == 0)
        pbmiSource->bmiHeader.biSizeImage =
            BITS2BYTES( (DWORD) pbmiSource->bmiHeader.biWidth *
                                pbmiSource->bmiHeader.biBitCount ) *
                                pbmiSource->bmiHeader.biHeight;
    if (pbmiSource->bmiHeader.biClrUsed == 0)
        pbmiSource->bmiHeader.biClrUsed = wNumColors;

// Create memory DC.

    hdcMem = CreateCompatibleDC(NULL);
    if (!hdcMem) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Create a 24BPP RGB DIB section and select it into the memory DC.

    pbmiRGB = (BITMAPINFO *)
              LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, sizeof(BITMAPINFO) );
    if (!pbmiRGB)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    pbmiRGB->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
    pbmiRGB->bmiHeader.biWidth         = pbmiSource->bmiHeader.biWidth;
    pbmiRGB->bmiHeader.biHeight        = pbmiSource->bmiHeader.biHeight;
    pbmiRGB->bmiHeader.biPlanes        = 1;
    pbmiRGB->bmiHeader.biBitCount      = 24;
    pbmiRGB->bmiHeader.biCompression   = BI_RGB;
    pbmiRGB->bmiHeader.biSizeImage     = pbmiRGB->bmiHeader.biWidth
                                         * abs(pbmiRGB->bmiHeader.biHeight) * 3;

    hbmRGB = CreateDIBSection(hdcMem, pbmiRGB, DIB_RGB_COLORS,
                              (PVOID *) &pjBitsRGB, NULL, 0);

    if (!hbmRGB)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    if (!SelectObject(hdcMem, hbmRGB))
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Slam the DIB file image into the memory DC.  GDI will do the work of
// translating whatever format the DIB file has into RGB format.

    if (!SetDIBits(hdcMem, hbmRGB, 0, pbmiSource->bmiHeader.biHeight,
                   pvBitsFile, pbmiSource, DIB_RGB_COLORS))
    {
        MESSAGEBOX(GetFocus(), "Image file conversion error.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }
    GdiFlush();     // make sure that SetDIBits executes

// Convert to TK image format (packed RGB format).
// Allocate with malloc to be consistent with tkRGBImageLoad (i.e., app
// can deallocate with free()).

    pjTKBits = (PBYTE) malloc(pbmiRGB->bmiHeader.biSizeImage);
    if (!pjTKBits)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    pjSrc = pjBitsRGB;
    pjDst = pjTKBits;
    // src lines end on LONG boundary - so need to skip over any padding bytes
    padBytes = pbmiSource->bmiHeader.biWidth % sizeof(LONG);
    for (i = 0; i < pbmiSource->bmiHeader.biHeight; i++)
    {
        for (j = 0; j < pbmiSource->bmiHeader.biWidth; j++)
        {
            // swap R and B
            *pjDst++ = pjSrc[2];
            *pjDst++ = pjSrc[1];
            *pjDst++ = pjSrc[0];
            pjSrc += 3;
        }
        pjSrc += padBytes;
    }

// Allocate and initialize the TK_RGBImageRec.
// Allocate with malloc to be consistent with tkRGBImageLoad (i.e., app
// can deallocate with free()).

    final = (TK_RGBImageRec *)malloc(sizeof(TK_RGBImageRec));
    if (final == NULL) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }
    // If we get to here, we have suceeded!
    final->sizeX = pbmiSource->bmiHeader.biWidth;
    final->sizeY = pbmiSource->bmiHeader.biHeight;
    final->data = pjTKBits;

// Cleanup objects.

tkDIBLoadImage_cleanup:
    {
        if (hdcMem)
            DeleteDC(hdcMem);

        if (hbmRGB)
            DeleteObject(hbmRGB);

        if (pbmiRGB)
            LocalFree(pbmiRGB);

        if (pbmiSource)
            LocalFree(pbmiSource);

        if (pvFile)
            UnmapViewOfFile(pvFile);

        if (hMap)
            CloseHandle(hMap);

        if (hFile != INVALID_HANDLE_VALUE)
            CloseHandle(hFile);
    }

// Check for error.

    if (!final)
    {
        if (pjTKBits)
            free(pjTKBits);

        if ( (hFile == INVALID_HANDLE_VALUE) || !hMap || !pvFile )
        {
            CHAR ach[256];

            bUnicode ? wsprintf(ach, "Failed to open DIB file %ws.\n", fileName) :
                       wsprintf(ach, "Failed to open DIB file %s.\n", fileName);

            MESSAGEBOX(GetFocus(), ach, "Error", MB_OK);
        }
    }

    return final;
}


TKFONT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include "tk.h"

#define static

enum {
    FONT_BEGIN = 1,
    FONT_NEXT,
    FONT_END,
    FONT_ADVANCE
};
#define MAX_STROKES        256
#define END_OF_LIST        256
#define BITMAP_END_OF_LIST 0

#define STROKE_SCALE ((GLfloat) 1.71)
#define OUTLINE_SCALE ((GLfloat) 0.023)
#define FILLED_SCALE ((GLfloat) 0.023)

GLint strokeFont[][1+MAX_STROKES*3] = {
    {
1,
FONT_BEGIN, 0, 4,
FONT_NEXT, 2, 2,
FONT_END, 4, 4,
FONT_BEGIN, 2, 8,
FONT_END, 2, 2,
FONT_ADVANCE, 6, 0
    },
    {
2,
FONT_BEGIN, 0, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_NEXT, 2, 6,
FONT_NEXT, 3, 5,
FONT_END, 4, 6,
FONT_BEGIN, 3, 5,
FONT_NEXT, 3, 3,
FONT_END, 4, 2,
FONT_BEGIN, 3, 3,
FONT_NEXT, 2, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
3,
FONT_BEGIN, 0, 0,
FONT_NEXT, 1, 1,
FONT_NEXT, 1, 5,
FONT_NEXT, 2, 6,
FONT_NEXT, 4, 6,
FONT_NEXT, 5, 5,
FONT_NEXT, 4, 4,
FONT_END, 1, 4,
FONT_BEGIN, 4, 4,
FONT_NEXT, 5, 3,
FONT_NEXT, 4, 2,
FONT_END, 1, 2,
FONT_ADVANCE, 7, 0
    },
    {
4,
FONT_BEGIN, 0, 4,
FONT_NEXT, 2, 6,
FONT_END, 4, 4,
FONT_ADVANCE, 6, 0
    },
    {
5,
FONT_BEGIN, 0, 5,
FONT_NEXT, 4, 5,
FONT_END, 4, 4,
FONT_ADVANCE, 6, 0
    },
    {
6,
FONT_BEGIN, 1, 4,
FONT_END, 3, 4,
FONT_BEGIN, 3, 6,
FONT_NEXT, 2, 6,
FONT_NEXT, 1, 5,
FONT_NEXT, 1, 3,
FONT_NEXT, 2, 2,
FONT_END, 3, 2,
FONT_ADVANCE, 6, 0
    },
    {
7,
FONT_BEGIN, 1, 2,
FONT_END, 1, 6,
FONT_BEGIN, 0, 6,
FONT_END, 4, 6,
FONT_BEGIN, 3, 6,
FONT_END, 3, 2,
FONT_ADVANCE, 6, 0
    },
    {
8,
FONT_BEGIN, 0, 2,
FONT_END, 2, 4,
FONT_BEGIN, 0, 7,
FONT_NEXT, 0, 6,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
10,
FONT_ADVANCE, 0, -9
    },
    {
11,
FONT_BEGIN, 0, 2,
FONT_NEXT, 1, 1,
FONT_NEXT, 2, 2,
FONT_NEXT, 2, 7,
FONT_NEXT, 3, 8,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
12,
FONT_BEGIN, 0, 3,
FONT_END, 4, 3,
FONT_BEGIN, 4, 6,
FONT_END, 0, 6,
FONT_BEGIN, 2, 8,
FONT_END, 2, 4,
FONT_ADVANCE, 6, 0
    },
    {
14,
FONT_BEGIN, 1, 3,
FONT_NEXT, 2, 4,
FONT_NEXT, 2, 5,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 3,
FONT_END, 2, 4,
FONT_BEGIN, 2, 5,
FONT_NEXT, 1, 6,
FONT_NEXT, 0, 5,
FONT_NEXT, 0, 4,
FONT_END, 1, 3,
FONT_ADVANCE, 6, 0
    },
    {
15,
FONT_BEGIN, 0, 3,
FONT_NEXT, 0, 4,
FONT_NEXT, 1, 5,
FONT_NEXT, 3, 5,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 6,
FONT_NEXT, 2, 8,
FONT_END, 1, 8,
FONT_BEGIN, 4, 4,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
16,
FONT_BEGIN, 4, 7,
FONT_NEXT, 1, 7,
FONT_NEXT, 0, 6,
FONT_NEXT, 0, 4,
FONT_NEXT, 1, 3,
FONT_END, 4, 3,
FONT_ADVANCE, 6, 0
    },
    {
17,
FONT_BEGIN, 0, 3,
FONT_NEXT, 3, 3,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 6,
FONT_NEXT, 3, 7,
FONT_END, 0, 7,
FONT_ADVANCE, 6, 0
    },
    {
18,
FONT_BEGIN, 0, 4,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 7,
FONT_NEXT, 3, 7,
FONT_NEXT, 4, 6,
FONT_END, 4, 4,
FONT_ADVANCE, 6, 0
    },
    {
19,
FONT_BEGIN, 0, 7,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 4,
FONT_NEXT, 3, 4,
FONT_NEXT, 4, 5,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
20,
FONT_BEGIN, 0, 8,
FONT_NEXT, 2, 2,
FONT_END, 4, 8,
FONT_BEGIN, 3, 6,
FONT_END, 1, 6,
FONT_ADVANCE, 6, 0
    },
    {
21,
FONT_BEGIN, 0, 2,
FONT_NEXT, 4, 2,
FONT_NEXT, 4, 5,
FONT_END, 1, 5,
FONT_BEGIN, 0, 8,
FONT_NEXT, 4, 8,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
22,
FONT_BEGIN, 0, 4,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 7,
FONT_NEXT, 3, 7,
FONT_NEXT, 4, 6,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 3,
FONT_NEXT, 1, 3,
FONT_END, 0, 4,
FONT_BEGIN, 1, 4,
FONT_END, 3, 6,
FONT_BEGIN, 1, 6,
FONT_END, 3, 4,
FONT_ADVANCE, 6, 0
    },
    {
23,
FONT_BEGIN, 2, 0,
FONT_NEXT, 0, 2,
FONT_NEXT, 4, 6,
FONT_END, 2, 8,
FONT_BEGIN, 0, 6,
FONT_END, 4, 6,
FONT_BEGIN, 0, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
24,
FONT_BEGIN, 0, 0,
FONT_END, 4, 0,
FONT_ADVANCE, 6, 0
    },
    {
25,
FONT_BEGIN, 2, 3,
FONT_NEXT, 4, 5,
FONT_END, 2, 7,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
26,
FONT_BEGIN, 0, 8,
FONT_NEXT, 1, 9,
FONT_NEXT, 2, 9,
FONT_NEXT, 2, 8,
FONT_NEXT, 3, 8,
FONT_END, 4, 9,
FONT_ADVANCE, 6, 0
    },
    {
27,
FONT_BEGIN, 0, 1,
FONT_END, 4, 7,
FONT_BEGIN, 4, 5,
FONT_END, 0, 5,
FONT_BEGIN, 0, 3,
FONT_END, 4, 3,
FONT_ADVANCE, 6, 1
    },
    {
28,
FONT_BEGIN, 1, 2,
FONT_END, 3, 2,
FONT_BEGIN, 3, 4,
FONT_NEXT, 1, 6,
FONT_END, 3, 8,
FONT_ADVANCE, 6, 0
    },
    {
29,
FONT_BEGIN, 1, 2,
FONT_END, 3, 2,
FONT_BEGIN, 1, 4,
FONT_NEXT, 3, 6,
FONT_END, 1, 8,
FONT_ADVANCE, 6, 0
    },
    {
30,
FONT_BEGIN, 0, 3,
FONT_END, 4, 3,
FONT_BEGIN, 4, 5,
FONT_END, 0, 5,
FONT_BEGIN, 0, 7,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
31,
FONT_BEGIN, 0, 6,
FONT_NEXT, 2, 4,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
32,
FONT_ADVANCE, 6, 0
    },
    {
33,
FONT_BEGIN, 2, 1,
FONT_END, 2, 2,
FONT_BEGIN, 2, 4,
FONT_END, 2, 8,
FONT_ADVANCE, 6, 0
    },
    {
34,
FONT_BEGIN, 1, 7,
FONT_END, 1, 9,
FONT_BEGIN, 3, 9,
FONT_END, 3, 7,
FONT_ADVANCE, 6, 0
    },
    {
35,
FONT_BEGIN, 1, 2,
FONT_END, 1, 7,
FONT_BEGIN, 3, 7,
FONT_END, 3, 2,
FONT_BEGIN, 4, 3,
FONT_END, 0, 3,
FONT_BEGIN, 0, 6,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
36,
FONT_BEGIN, 2, 1,
FONT_END, 2, 9,
FONT_BEGIN, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 5,
FONT_NEXT, 3, 5,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 2,
FONT_NEXT, 3, 1,
FONT_NEXT, 1, 1,
FONT_END, 0, 2,
FONT_ADVANCE, 6, -1
    },
    {
37,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 4, 7,
FONT_END, 4, 8,
FONT_BEGIN, 1, 8,
FONT_NEXT, 0, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 7,
FONT_END, 1, 8,
FONT_BEGIN, 4, 3,
FONT_NEXT, 5, 3,
FONT_NEXT, 5, 2,
FONT_NEXT, 4, 2,
FONT_END, 5, 2,
FONT_ADVANCE, 8, 1
    },
    {
38,
FONT_BEGIN, 4, 4,
FONT_NEXT, 2, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 4,
FONT_NEXT, 2, 6,
FONT_NEXT, 2, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 6,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
39,
FONT_BEGIN, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 1, 9,
FONT_NEXT, 2, 9,
FONT_NEXT, 2, 8,
FONT_END, 1, 8,
FONT_ADVANCE, 6, 1
    },
    {
40,
FONT_BEGIN, 4, 2,
FONT_NEXT, 2, 4,
FONT_NEXT, 2, 6,
FONT_END, 4, 8,
FONT_ADVANCE, 6, 0
    },
    {
41,
FONT_BEGIN, 0, 2,
FONT_NEXT, 2, 4,
FONT_NEXT, 2, 6,
FONT_END, 0, 8,
FONT_ADVANCE, 6, 0
    },
    {
42,
FONT_BEGIN, 2, 2,
FONT_END, 2, 8,
FONT_BEGIN, 0, 7,
FONT_END, 4, 3,
FONT_BEGIN, 4, 5,
FONT_END, 0, 5,
FONT_BEGIN, 0, 3,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
43,
FONT_BEGIN, 2, 3,
FONT_END, 2, 7,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
44,
FONT_BEGIN, 0, 1,
FONT_NEXT, 1, 2,
FONT_END, 1, 3,
FONT_ADVANCE, 6, 0
    },
    {
45,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
46,
FONT_BEGIN, 1, 2,
FONT_END, 2, 2,
FONT_ADVANCE, 6, 0
    },
    {
47,
FONT_BEGIN, 0, 3,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
48,
FONT_BEGIN, 0, 3,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
49,
FONT_BEGIN, 1, 2,
FONT_END, 3, 2,
FONT_BEGIN, 2, 2,
FONT_NEXT, 2, 8,
FONT_END, 1, 7,
FONT_ADVANCE, 6, 0
    },
    {
50,
FONT_BEGIN, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 6,
FONT_NEXT, 0, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
51,
FONT_BEGIN, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 5,
FONT_END, 2, 5,
FONT_BEGIN, 3, 5,
FONT_NEXT, 4, 6,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_END, 0, 7,
FONT_ADVANCE, 6, 0
    },
    {
52,
FONT_BEGIN, 3, 2,
FONT_NEXT, 3, 8,
FONT_NEXT, 0, 5,
FONT_NEXT, 0, 4,
FONT_END, 4, 4,
FONT_ADVANCE, 6, 0
    },
    {
53,
FONT_BEGIN, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 5,
FONT_NEXT, 3, 6,
FONT_NEXT, 0, 6,
FONT_NEXT, 0, 8,
FONT_END, 4, 8,
FONT_ADVANCE, 6, 0
    },
    {
54,
FONT_BEGIN, 0, 5,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 5,
FONT_NEXT, 0, 5,
FONT_NEXT, 0, 6,
FONT_NEXT, 2, 8,
FONT_END, 3, 8,
FONT_ADVANCE, 6, 0
    },
    {
55,
FONT_BEGIN, 0, 8,
FONT_NEXT, 4, 8,
FONT_NEXT, 2, 4,
FONT_END, 2, 2,
FONT_ADVANCE, 6, 0
    },
    {
56,
FONT_BEGIN, 1, 5,
FONT_NEXT, 0, 4,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 5,
FONT_NEXT, 1, 5,
FONT_NEXT, 0, 6,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 6,
FONT_END, 3, 5,
FONT_ADVANCE, 6, 0
    },
    {
57,
FONT_BEGIN, 1, 2,
FONT_NEXT, 2, 2,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
58,
FONT_BEGIN, 0, 3,
FONT_END, 1, 3,
FONT_BEGIN, 1, 6,
FONT_END, 0, 6,
FONT_ADVANCE, 6, 0
    },
    {
59,
FONT_BEGIN, 0, 1,
FONT_NEXT, 1, 2,
FONT_END, 1, 3,
FONT_BEGIN, 1, 6,
FONT_END, 0, 6,
FONT_ADVANCE, 6, 0
    },
    {
60,
FONT_BEGIN, 3, 3,
FONT_NEXT, 1, 5,
FONT_END, 3, 7,
FONT_ADVANCE, 6, 0
    },
    {
61,
FONT_BEGIN, 0, 4,
FONT_END, 4, 4,
FONT_BEGIN, 4, 6,
FONT_END, 0, 6,
FONT_ADVANCE, 6, 0
    },
    {
62,
FONT_BEGIN, 1, 7,
FONT_NEXT, 3, 5,
FONT_END, 1, 3,
FONT_ADVANCE, 6, 0
    },
    {
63,
FONT_BEGIN, 1, 2,
FONT_END, 2, 2,
FONT_BEGIN, 2, 4,
FONT_NEXT, 2, 5,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_END, 0, 7,
FONT_ADVANCE, 6, 0
    },
    {
64,
FONT_BEGIN, 3, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 4,
FONT_NEXT, 2, 4,
FONT_NEXT, 2, 6,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
65,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_END, 4, 2,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
66,
FONT_BEGIN, 0, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 4,
FONT_NEXT, 3, 5,
FONT_END, 0, 5,
FONT_BEGIN, 3, 5,
FONT_NEXT, 4, 6,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 0, 8,
FONT_END, 0, 2,
FONT_ADVANCE, 6, 0
    },
    {
67,
FONT_BEGIN, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_END, 4, 3,
FONT_ADVANCE, 6, 0
    },
    {
68,
FONT_BEGIN, 0, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 7,
FONT_NEXT, 3, 8,
FONT_END, 0, 8,
FONT_BEGIN, 1, 8,
FONT_END, 1, 2,
FONT_ADVANCE, 6, 0
    },
    {
69,
FONT_BEGIN, 4, 2,
FONT_NEXT, 0, 2,
FONT_NEXT, 0, 8,
FONT_END, 4, 8,
FONT_BEGIN, 3, 5,
FONT_END, 0, 5,
FONT_ADVANCE, 6, 0
    },
    {
70,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 8,
FONT_END, 4, 8,
FONT_BEGIN, 3, 5,
FONT_END, 0, 5,
FONT_ADVANCE, 6, 0
    },
    {
71,
FONT_BEGIN, 3, 4,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
72,
FONT_BEGIN, 0, 2,
FONT_END, 0, 8,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_BEGIN, 4, 8,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
73,
FONT_BEGIN, 1, 2,
FONT_END, 3, 2,
FONT_BEGIN, 2, 2,
FONT_END, 2, 8,
FONT_BEGIN, 1, 8,
FONT_END, 3, 8,
FONT_ADVANCE, 6, 0
    },
    {
74,
FONT_BEGIN, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_END, 4, 8,
FONT_ADVANCE, 6, 0
    },
    {
75,
FONT_BEGIN, 0, 2,
FONT_END, 0, 8,
FONT_BEGIN, 4, 8,
FONT_NEXT, 1, 5,
FONT_END, 4, 2,
FONT_ADVANCE, 8, 0
    },
    {
76,
FONT_BEGIN, 0, 8,
FONT_NEXT, 0, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
77,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 8,
FONT_NEXT, 2, 6,
FONT_NEXT, 4, 8,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
78,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 8,
FONT_NEXT, 4, 2,
FONT_END, 4, 8,
FONT_ADVANCE, 6, 0
    },
    {
79,
FONT_BEGIN, 0, 3,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
80,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 6,
FONT_NEXT, 3, 5,
FONT_END, 0, 5,
FONT_ADVANCE, 6, 0
    },
    {
81,
FONT_BEGIN, 0, 3,
FONT_NEXT, 0, 7,
FONT_NEXT, 1, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 4,
FONT_NEXT, 2, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_BEGIN, 2, 5,
FONT_END, 4, 3,
FONT_ADVANCE, 6, 0
    },
    {
82,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 8,
FONT_NEXT, 3, 8,
FONT_NEXT, 4, 7,
FONT_NEXT, 4, 6,
FONT_NEXT, 3, 5,
FONT_END, 0, 5,
FONT_BEGIN, 1, 5,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
83,
FONT_BEGIN, 4, 7,
FONT_NEXT, 3, 8,
FONT_NEXT, 1, 8,
FONT_NEXT, 0, 7,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 5,
FONT_NEXT, 3, 5,
FONT_NEXT, 4, 4,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
84,
FONT_BEGIN, 0, 8,
FONT_END, 4, 8,
FONT_BEGIN, 2, 8,
FONT_END, 2, 2,
FONT_ADVANCE, 6, 0
    },
    {
85,
FONT_BEGIN, 0, 3,
FONT_END, 0, 8,
FONT_BEGIN, 4, 8,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
86,
FONT_BEGIN, 0, 8,
FONT_NEXT, 0, 5,
FONT_NEXT, 2, 2,
FONT_NEXT, 4, 5,
FONT_END, 4, 8,
FONT_ADVANCE, 6, 0
    },
    {
87,
FONT_BEGIN, 0, 2,
FONT_END, 0, 8,
FONT_BEGIN, 4, 8,
FONT_NEXT, 4, 2,
FONT_NEXT, 2, 4,
FONT_END, 0, 2,
FONT_ADVANCE, 6, 0
    },
    {
88,
FONT_BEGIN, 0, 2,
FONT_END, 4, 8,
FONT_BEGIN, 0, 8,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
89,
FONT_BEGIN, 2, 2,
FONT_NEXT, 2, 5,
FONT_END, 0, 8,
FONT_BEGIN, 4, 8,
FONT_END, 2, 5,
FONT_ADVANCE, 6, 0
    },
    {
90,
FONT_BEGIN, 0, 8,
FONT_NEXT, 4, 8,
FONT_NEXT, 0, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
91,
FONT_BEGIN, 3, 1,
FONT_NEXT, 1, 1,
FONT_NEXT, 1, 9,
FONT_END, 3, 9,
FONT_ADVANCE, 6, 0
    },
    {
92,
FONT_BEGIN, 0, 7,
FONT_END, 4, 3,
FONT_ADVANCE, 6, 0
    },
    {
93,
FONT_BEGIN, 1, 9,
FONT_NEXT, 3, 9,
FONT_NEXT, 3, 1,
FONT_END, 1, 1,
FONT_ADVANCE, 6, 0
    },
    {
94,
FONT_BEGIN, 2, 2,
FONT_END, 2, 8,
FONT_BEGIN, 0, 6,
FONT_NEXT, 2, 8,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
95,
FONT_BEGIN, 2, 3,
FONT_NEXT, 0, 5,
FONT_END, 2, 7,
FONT_BEGIN, 0, 5,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
96,
FONT_BEGIN, 3, 8,
FONT_NEXT, 2, 8,
FONT_NEXT, 2, 9,
FONT_NEXT, 3, 9,
FONT_NEXT, 3, 8,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
97,
FONT_BEGIN, 1, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_NEXT, 4, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 4,
FONT_END, 4, 4,
FONT_ADVANCE, 6, 0
    },
    {
98,
FONT_BEGIN, 0, 8,
FONT_NEXT, 0, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 4, 5,
FONT_NEXT, 3, 6,
FONT_END, 0, 6,
FONT_ADVANCE, 6, 0
    },
    {
99,
FONT_BEGIN, 4, 5,
FONT_NEXT, 3, 6,
FONT_NEXT, 1, 6,
FONT_NEXT, 0, 5,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
100,
FONT_BEGIN, 4, 8,
FONT_NEXT, 4, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
101,
FONT_BEGIN, 3, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_NEXT, 3, 4,
FONT_END, 0, 4,
FONT_ADVANCE, 6, 0
    },
    {
102,
FONT_BEGIN, 1, 2,
FONT_NEXT, 1, 7,
FONT_NEXT, 2, 8,
FONT_NEXT, 3, 8,
FONT_END, 4, 7,
FONT_BEGIN, 2, 5,
FONT_END, 0, 5,
FONT_ADVANCE, 6, 0
    },
    {
103,
FONT_BEGIN, 1, 0,
FONT_NEXT, 3, 0,
FONT_NEXT, 4, 1,
FONT_NEXT, 4, 5,
FONT_NEXT, 3, 6,
FONT_NEXT, 1, 6,
FONT_NEXT, 0, 5,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
104,
FONT_BEGIN, 0, 2,
FONT_END, 0, 8,
FONT_BEGIN, 0, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
105,
FONT_BEGIN, 2, 2,
FONT_END, 2, 5,
FONT_BEGIN, 2, 6,
FONT_END, 2, 7,
FONT_ADVANCE, 6, 0
    },
    {
106,
FONT_BEGIN, 0, 1,
FONT_NEXT, 1, 0,
FONT_NEXT, 3, 0,
FONT_NEXT, 4, 1,
FONT_END, 4, 5,
FONT_BEGIN, 4, 6,
FONT_END, 4, 7,
FONT_ADVANCE, 6, 0
    },
    {
107,
FONT_BEGIN, 0, 2,
FONT_END, 0, 8,
FONT_BEGIN, 4, 6,
FONT_NEXT, 2, 4,
FONT_END, 0, 4,
FONT_BEGIN, 2, 4,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
108,
FONT_BEGIN, 1, 2,
FONT_END, 1, 8,
FONT_ADVANCE, 6, 0
    },
    {
109,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 6,
FONT_NEXT, 2, 5,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_END, 4, 2,
FONT_BEGIN, 2, 2,
FONT_END, 2, 5,
FONT_ADVANCE, 6, 0
    },
    {
110,
FONT_BEGIN, 0, 2,
FONT_NEXT, 0, 6,
FONT_NEXT, 1, 5,
FONT_NEXT, 2, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
111,
FONT_BEGIN, 0, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 1, 2,
FONT_END, 0, 3,
FONT_ADVANCE, 6, 0
    },
    {
112,
FONT_BEGIN, 0, 0,
FONT_NEXT, 0, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 2,
FONT_END, 0, 2,
FONT_ADVANCE, 6, 0
    },
    {
113,
FONT_BEGIN, 4, 2,
FONT_NEXT, 1, 2,
FONT_NEXT, 0, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_NEXT, 3, 6,
FONT_NEXT, 4, 5,
FONT_END, 4, 0,
FONT_ADVANCE, 6, 0
    },
    {
114,
FONT_BEGIN, 0, 2,
FONT_END, 0, 6,
FONT_BEGIN, 0, 4,
FONT_NEXT, 2, 6,
FONT_NEXT, 3, 6,
FONT_END, 4, 5,
FONT_ADVANCE, 6, 0
    },
    {
115,
FONT_BEGIN, 0, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_NEXT, 3, 4,
FONT_NEXT, 1, 4,
FONT_NEXT, 0, 5,
FONT_NEXT, 1, 6,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
116,
FONT_BEGIN, 0, 6,
FONT_END, 4, 6,
FONT_BEGIN, 2, 8,
FONT_NEXT, 2, 3,
FONT_NEXT, 3, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
117,
FONT_BEGIN, 0, 6,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
118,
FONT_BEGIN, 0, 6,
FONT_NEXT, 0, 4,
FONT_NEXT, 2, 2,
FONT_NEXT, 4, 4,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
119,
FONT_BEGIN, 0, 6,
FONT_NEXT, 0, 3,
FONT_NEXT, 1, 2,
FONT_NEXT, 2, 3,
FONT_NEXT, 3, 2,
FONT_NEXT, 4, 3,
FONT_END, 4, 6,
FONT_ADVANCE, 6, 0
    },
    {
120,
FONT_BEGIN, 0, 2,
FONT_END, 4, 6,
FONT_BEGIN, 0, 6,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
121,
FONT_BEGIN, 0, 0,
FONT_NEXT, 4, 4,
FONT_END, 4, 6,
FONT_BEGIN, 0, 6,
FONT_NEXT, 0, 4,
FONT_END, 2, 2,
FONT_ADVANCE, 6, 0
    },
    {
122,
FONT_BEGIN, 0, 6,
FONT_NEXT, 4, 6,
FONT_NEXT, 0, 2,
FONT_END, 4, 2,
FONT_ADVANCE, 6, 0
    },
    {
123,
FONT_BEGIN, 4, 9,
FONT_NEXT, 3, 8,
FONT_NEXT, 3, 6,
FONT_NEXT, 2, 5,
FONT_NEXT, 3, 4,
FONT_NEXT, 3, 2,
FONT_END, 4, 1,
FONT_ADVANCE, 6, 0
    },
    {
124,
FONT_BEGIN, 2, 9,
FONT_END, 2, 0,
FONT_ADVANCE, 6, 0
    },
    {
125,
FONT_BEGIN, 2, 2,
FONT_NEXT, 2, 3,
FONT_NEXT, 0, 5,
FONT_NEXT, 2, 7,
FONT_END, 2, 8,
FONT_BEGIN, 2, 7,
FONT_NEXT, 4, 5,
FONT_END, 2, 3,
FONT_ADVANCE, 6, 0
    },
    {
126,
FONT_BEGIN, 0, 9,
FONT_NEXT, 1, 8,
FONT_NEXT, 1, 6,
FONT_NEXT, 2, 5,
FONT_NEXT, 1, 4,
FONT_NEXT, 1, 2,
FONT_END, 0, 1,
FONT_ADVANCE, 6, 0
    },
    {
END_OF_LIST
    }
};

GLint outlineFont[][1+MAX_STROKES*3] = {
    {
32,
FONT_ADVANCE, 250, 0
    },
    {
33,
FONT_BEGIN, 234, 559,
FONT_NEXT, 236, 605,
FONT_NEXT, 229, 642,
FONT_NEXT, 211, 667,
FONT_NEXT, 182, 676,
FONT_NEXT, 156, 668,
FONT_NEXT, 140, 648,
FONT_NEXT, 130, 592,
FONT_NEXT, 131, 568,
FONT_NEXT, 134, 534,
FONT_NEXT, 146, 447,
FONT_NEXT, 158, 355,
FONT_NEXT, 163, 315,
FONT_NEXT, 167, 284,
FONT_NEXT, 176, 176,
FONT_END, 189, 176,
FONT_BEGIN, 219, 3,
FONT_NEXT, 232, 18,
FONT_NEXT, 238, 42,
FONT_NEXT, 233, 62,
FONT_NEXT, 222, 79,
FONT_NEXT, 204, 91,
FONT_NEXT, 183, 96,
FONT_NEXT, 162, 91,
FONT_NEXT, 145, 80,
FONT_NEXT, 134, 63,
FONT_NEXT, 130, 42,
FONT_NEXT, 135, 18,
FONT_NEXT, 148, 2,
FONT_END, 183, -9,
FONT_ADVANCE, 333, 0
    },
    {
34,
FONT_BEGIN, 308, 482,
FONT_NEXT, 318, 543,
FONT_NEXT, 327, 599,
FONT_NEXT, 331, 635,
FONT_NEXT, 317, 665,
FONT_NEXT, 289, 676,
FONT_NEXT, 260, 665,
FONT_NEXT, 246, 635,
FONT_NEXT, 249, 599,
FONT_NEXT, 258, 543,
FONT_NEXT, 268, 482,
FONT_NEXT, 278, 431,
FONT_END, 299, 431,
FONT_BEGIN, 139, 482,
FONT_NEXT, 149, 543,
FONT_NEXT, 158, 599,
FONT_NEXT, 162, 635,
FONT_NEXT, 148, 665,
FONT_NEXT, 120, 676,
FONT_NEXT, 91, 665,
FONT_NEXT, 77, 635,
FONT_NEXT, 80, 599,
FONT_NEXT, 89, 543,
FONT_NEXT, 99, 482,
FONT_NEXT, 109, 431,
FONT_END, 130, 431,
FONT_ADVANCE, 408, 0
    },
    {
35,
FONT_BEGIN, 371, 271,
FONT_NEXT, 391, 405,
FONT_NEXT, 496, 405,
FONT_NEXT, 496, 460,
FONT_NEXT, 399, 460,
FONT_NEXT, 429, 662,
FONT_NEXT, 371, 662,
FONT_NEXT, 341, 460,
FONT_NEXT, 208, 460,
FONT_NEXT, 239, 662,
FONT_NEXT, 181, 662,
FONT_NEXT, 150, 460,
FONT_NEXT, 32, 460,
FONT_NEXT, 32, 405,
FONT_NEXT, 142, 405,
FONT_NEXT, 121, 271,
FONT_NEXT, 5, 271,
FONT_NEXT, 5, 216,
FONT_NEXT, 112, 216,
FONT_NEXT, 79, 0,
FONT_NEXT, 137, 0,
FONT_NEXT, 170, 216,
FONT_NEXT, 304, 216,
FONT_NEXT, 273, 0,
FONT_NEXT, 331, 0,
FONT_NEXT, 362, 216,
FONT_NEXT, 471, 216,
FONT_END, 471, 271,
FONT_BEGIN, 313, 271,
FONT_NEXT, 179, 271,
FONT_NEXT, 200, 405,
FONT_END, 333, 405,
FONT_ADVANCE, 500, 0
    },
    {
36,
FONT_BEGIN, 425, 611,
FONT_NEXT, 387, 634,
FONT_NEXT, 345, 649,
FONT_NEXT, 264, 664,
FONT_NEXT, 264, 727,
FONT_NEXT, 230, 727,
FONT_NEXT, 230, 664,
FONT_NEXT, 185, 658,
FONT_NEXT, 148, 646,
FONT_NEXT, 93, 611,
FONT_NEXT, 61, 563,
FONT_NEXT, 52, 511,
FONT_NEXT, 55, 476,
FONT_NEXT, 67, 445,
FONT_NEXT, 106, 392,
FONT_NEXT, 163, 349,
FONT_NEXT, 230, 310,
FONT_NEXT, 230, 28,
FONT_NEXT, 166, 37,
FONT_NEXT, 117, 66,
FONT_NEXT, 81, 114,
FONT_NEXT, 59, 181,
FONT_NEXT, 44, 181,
FONT_NEXT, 44, 51,
FONT_NEXT, 70, 35,
FONT_NEXT, 110, 19,
FONT_NEXT, 162, 5,
FONT_NEXT, 230, 0,
FONT_NEXT, 230, -87,
FONT_NEXT, 264, -87,
FONT_NEXT, 264, 0,
FONT_NEXT, 338, 16,
FONT_NEXT, 399, 49,
FONT_NEXT, 441, 100,
FONT_NEXT, 453, 134,
FONT_NEXT, 457, 174,
FONT_NEXT, 453, 211,
FONT_NEXT, 444, 243,
FONT_NEXT, 408, 295,
FONT_NEXT, 348, 341,
FONT_NEXT, 264, 391,
FONT_NEXT, 264, 637,
FONT_NEXT, 305, 628,
FONT_NEXT, 348, 607,
FONT_NEXT, 385, 566,
FONT_NEXT, 399, 537,
FONT_NEXT, 410, 500,
FONT_END, 425, 500,
FONT_BEGIN, 187, 435,
FONT_NEXT, 155, 464,
FONT_NEXT, 133, 495,
FONT_NEXT, 126, 532,
FONT_NEXT, 128, 555,
FONT_NEXT, 141, 586,
FONT_NEXT, 171, 616,
FONT_NEXT, 196, 628,
FONT_NEXT, 229, 637,
FONT_END, 229, 407,
FONT_BEGIN, 308, 264,
FONT_NEXT, 344, 234,
FONT_NEXT, 369, 197,
FONT_NEXT, 378, 151,
FONT_NEXT, 374, 116,
FONT_NEXT, 364, 89,
FONT_NEXT, 333, 53,
FONT_NEXT, 295, 35,
FONT_NEXT, 264, 28,
FONT_END, 264, 293,
FONT_ADVANCE, 500, 0
    },
    {
37,
FONT_BEGIN, 622, 365,
FONT_NEXT, 583, 349,
FONT_NEXT, 514, 293,
FONT_NEXT, 466, 218,
FONT_NEXT, 453, 177,
FONT_NEXT, 449, 137,
FONT_NEXT, 455, 86,
FONT_NEXT, 475, 42,
FONT_NEXT, 513, 11,
FONT_NEXT, 572, 0,
FONT_NEXT, 615, 6,
FONT_NEXT, 654, 25,
FONT_NEXT, 688, 53,
FONT_NEXT, 717, 88,
FONT_NEXT, 757, 173,
FONT_NEXT, 772, 261,
FONT_NEXT, 768, 295,

FONT_NEXT, 760, 321,
FONT_NEXT, 731, 354,
FONT_NEXT, 695, 368,
FONT_END, 663, 371,
FONT_BEGIN, 703, 332,
FONT_NEXT, 726, 314,
FONT_NEXT, 740, 287,
FONT_NEXT, 746, 254,
FONT_NEXT, 733, 183,
FONT_NEXT, 700, 110,
FONT_NEXT, 650, 53,
FONT_NEXT, 620, 36,
FONT_NEXT, 589, 30,
FONT_NEXT, 552, 39,
FONT_NEXT, 533, 60,
FONT_NEXT, 525, 106,
FONT_NEXT, 527, 128,
FONT_NEXT, 534, 160,
FONT_NEXT, 564, 238,
FONT_NEXT, 611, 308,
FONT_NEXT, 641, 330,
FONT_END, 676, 339,
FONT_BEGIN, 595, 676,
FONT_NEXT, 536, 630,
FONT_NEXT, 493, 614,
FONT_NEXT, 438, 608,
FONT_NEXT, 404, 610,
FONT_NEXT, 381, 616,
FONT_NEXT, 352, 635,
FONT_NEXT, 325, 653,
FONT_NEXT, 305, 659,
FONT_NEXT, 276, 662,
FONT_NEXT, 232, 655,
FONT_NEXT, 192, 639,
FONT_NEXT, 156, 613,
FONT_NEXT, 124, 581,
FONT_NEXT, 78, 504,
FONT_NEXT, 65, 463,
FONT_NEXT, 61, 425,
FONT_NEXT, 64, 390,
FONT_NEXT, 72, 361,
FONT_NEXT, 102, 319,
FONT_NEXT, 140, 296,
FONT_NEXT, 178, 289,
FONT_NEXT, 223, 295,
FONT_NEXT, 264, 314,
FONT_NEXT, 299, 343,
FONT_NEXT, 329, 379,
FONT_NEXT, 369, 463,
FONT_NEXT, 384, 548,
FONT_NEXT, 377, 593,
FONT_NEXT, 401, 585,
FONT_NEXT, 443, 583,
FONT_NEXT, 478, 585,
FONT_NEXT, 507, 592,
FONT_NEXT, 548, 611,
FONT_NEXT, 550, 609,
FONT_NEXT, 201, -13,
FONT_NEXT, 249, -13,
FONT_END, 634, 676,
FONT_BEGIN, 172, 326,
FONT_NEXT, 153, 339,
FONT_NEXT, 141, 362,
FONT_NEXT, 137, 397,
FONT_NEXT, 150, 468,
FONT_NEXT, 184, 545,
FONT_NEXT, 230, 606,
FONT_NEXT, 255, 625,
FONT_NEXT, 281, 632,
FONT_NEXT, 299, 622,
FONT_NEXT, 315, 611,
FONT_NEXT, 350, 600,
FONT_NEXT, 359, 553,
FONT_NEXT, 345, 469,
FONT_NEXT, 309, 396,
FONT_NEXT, 257, 343,
FONT_NEXT, 228, 328,
FONT_END, 199, 323,
FONT_ADVANCE, 833, 0
    },
    {
38,
FONT_BEGIN, 287, 596,
FONT_NEXT, 304, 623,
FONT_NEXT, 329, 639,
FONT_NEXT, 359, 644,
FONT_NEXT, 389, 637,
FONT_NEXT, 414, 620,
FONT_NEXT, 431, 594,
FONT_NEXT, 438, 560,
FONT_NEXT, 423, 504,
FONT_NEXT, 389, 462,
FONT_NEXT, 350, 432,
FONT_NEXT, 321, 416,
FONT_NEXT, 293, 483,
FONT_END, 281, 557,
FONT_BEGIN, 495, 426,
FONT_NEXT, 495, 405,
FONT_NEXT, 540, 395,
FONT_NEXT, 554, 381,
FONT_NEXT, 559, 359,
FONT_NEXT, 548, 310,
FONT_NEXT, 524, 259,
FONT_NEXT, 494, 213,
FONT_NEXT, 468, 178,
FONT_NEXT, 396, 277,
FONT_NEXT, 336, 384,
FONT_NEXT, 387, 409,
FONT_NEXT, 437, 444,
FONT_NEXT, 475, 492,
FONT_NEXT, 486, 521,
FONT_NEXT, 491, 556,
FONT_NEXT, 480, 606,
FONT_NEXT, 450, 644,
FONT_NEXT, 407, 667,
FONT_NEXT, 355, 676,
FONT_NEXT, 319, 672,
FONT_NEXT, 288, 661,
FONT_NEXT, 240, 624,
FONT_NEXT, 211, 573,
FONT_NEXT, 202, 519,
FONT_NEXT, 211, 452,
FONT_NEXT, 237, 377,
FONT_NEXT, 199, 355,
FONT_NEXT, 146, 320,
FONT_NEXT, 95, 274,
FONT_NEXT, 57, 215,
FONT_NEXT, 42, 144,
FONT_NEXT, 46, 99,
FONT_NEXT, 59, 64,
FONT_NEXT, 78, 36,
FONT_NEXT, 101, 16,
FONT_NEXT, 155, -7,
FONT_NEXT, 207, -13,
FONT_NEXT, 286, -4,
FONT_NEXT, 347, 19,
FONT_NEXT, 394, 49,
FONT_NEXT, 429, 78,
FONT_NEXT, 456, 51,
FONT_NEXT, 494, 21,
FONT_NEXT, 542, -3,
FONT_NEXT, 599, -13,
FONT_NEXT, 635, -9,
FONT_NEXT, 667, 4,
FONT_NEXT, 713, 42,
FONT_NEXT, 740, 80,
FONT_NEXT, 747, 93,
FONT_NEXT, 750, 100,
FONT_NEXT, 735, 111,
FONT_NEXT, 697, 73,
FONT_NEXT, 672, 62,
FONT_NEXT, 639, 58,
FONT_NEXT, 585, 70,
FONT_NEXT, 540, 98,
FONT_NEXT, 507, 129,
FONT_NEXT, 491, 150,
FONT_NEXT, 528, 200,
FONT_NEXT, 550, 232,
FONT_NEXT, 568, 261,
FONT_NEXT, 591, 300,
FONT_NEXT, 617, 346,
FONT_NEXT, 638, 375,
FONT_NEXT, 661, 392,
FONT_NEXT, 711, 405,
FONT_END, 711, 426,
FONT_BEGIN, 146, 238,
FONT_NEXT, 176, 283,
FONT_NEXT, 214, 317,
FONT_NEXT, 252, 343,
FONT_NEXT, 322, 219,
FONT_NEXT, 404, 104,
FONT_NEXT, 356, 69,
FONT_NEXT, 316, 49,
FONT_NEXT, 285, 41,
FONT_NEXT, 263, 39,
FONT_NEXT, 212, 50,
FONT_NEXT, 171, 80,
FONT_NEXT, 144, 125,
FONT_END, 134, 180,
FONT_ADVANCE, 778, 0
    },
    {
39,
FONT_BEGIN, 136, 450,
FONT_NEXT, 173, 483,
FONT_NEXT, 204, 528,
FONT_NEXT, 218, 580,
FONT_NEXT, 208, 630,
FONT_NEXT, 186, 659,
FONT_NEXT, 159, 672,
FONT_NEXT, 137, 676,
FONT_NEXT, 98, 663,
FONT_NEXT, 84, 645,
FONT_NEXT, 79, 618,
FONT_NEXT, 85, 593,
FONT_NEXT, 100, 578,
FONT_NEXT, 141, 568,
FONT_NEXT, 156, 570,
FONT_NEXT, 165, 572,
FONT_NEXT, 174, 568,
FONT_NEXT, 179, 558,
FONT_NEXT, 175, 539,
FONT_NEXT, 162, 513,
FONT_NEXT, 137, 483,
FONT_NEXT, 97, 452,
FONT_END, 106, 433,
FONT_ADVANCE, 333, 0
    },
    {
40,
FONT_BEGIN, 239, 638,
FONT_NEXT, 191, 596,
FONT_NEXT, 148, 549,
FONT_NEXT, 113, 499,
FONT_NEXT, 85, 444,
FONT_NEXT, 64, 386,
FONT_NEXT, 52, 325,
FONT_NEXT, 48, 262,
FONT_NEXT, 53, 169,
FONT_NEXT, 70, 96,
FONT_NEXT, 96, 35,
FONT_NEXT, 130, -20,
FONT_NEXT, 203, -108,
FONT_NEXT, 247, -148,
FONT_NEXT, 292, -177,
FONT_NEXT, 304, -161,
FONT_NEXT, 247, -116,
FONT_NEXT, 205, -63,
FONT_NEXT, 175, -4,
FONT_NEXT, 155, 56,
FONT_NEXT, 136, 174,
FONT_NEXT, 134, 225,
FONT_NEXT, 134, 269,
FONT_NEXT, 138, 356,
FONT_NEXT, 150, 428,
FONT_NEXT, 168, 488,
FONT_NEXT, 191, 537,
FONT_NEXT, 246, 609,
FONT_NEXT, 304, 660,
FONT_END, 295, 676,
FONT_ADVANCE, 333, 0
    },
    {
41,
FONT_BEGIN, 93, -140,
FONT_NEXT, 141, -98,
FONT_NEXT, 184, -51,
FONT_NEXT, 219, 0,
FONT_NEXT, 247, 54,
FONT_NEXT, 268, 112,
FONT_NEXT, 280, 173,
FONT_NEXT, 285, 237,
FONT_NEXT, 279, 329,
FONT_NEXT, 262, 402,
FONT_NEXT, 236, 463,
FONT_NEXT, 203, 519,
FONT_NEXT, 129, 606,
FONT_NEXT, 85, 646,
FONT_NEXT, 41, 676,
FONT_NEXT, 29, 660,
FONT_NEXT, 85, 614,
FONT_NEXT, 127, 561,
FONT_NEXT, 157, 502,
FONT_NEXT, 177, 442,
FONT_NEXT, 196, 324,
FONT_NEXT, 198, 273,
FONT_NEXT, 199, 230,
FONT_NEXT, 194, 142,
FONT_NEXT, 182, 70,
FONT_NEXT, 164, 10,
FONT_NEXT, 141, -39,
FONT_NEXT, 86, -111,
FONT_NEXT, 29, -161,
FONT_END, 38, -177,
FONT_ADVANCE, 333, 0
    },
    {
42,
FONT_BEGIN, 76, 362,
FONT_NEXT, 103, 351,
FONT_NEXT, 129, 358,
FONT_NEXT, 143, 371,
FONT_NEXT, 164, 394,
FONT_NEXT, 192, 422,
FONT_NEXT, 210, 435,
FONT_NEXT, 240, 456,
FONT_NEXT, 241, 449,
FONT_NEXT, 240, 405,
FONT_NEXT, 230, 365,
FONT_NEXT, 219, 331,
FONT_NEXT, 214, 304,
FONT_NEXT, 225, 275,
FONT_NEXT, 249, 265,
FONT_NEXT, 277, 278,
FONT_NEXT, 288, 307,
FONT_NEXT, 274, 352,
FONT_NEXT, 264, 389,
FONT_NEXT, 260, 449,
FONT_NEXT, 260, 456,
FONT_NEXT, 267, 453,
FONT_NEXT, 307, 425,
FONT_NEXT, 338, 391,
FONT_NEXT, 366, 362,
FONT_NEXT, 396, 350,
FONT_NEXT, 421, 359,
FONT_NEXT, 432, 386,
FONT_NEXT, 428, 404,
FONT_NEXT, 419, 416,
FONT_NEXT, 384, 428,
FONT_NEXT, 334, 439,
FONT_NEXT, 276, 465,
FONT_NEXT, 268, 470,
FONT_NEXT, 304, 489,
FONT_NEXT, 336, 502,
FONT_NEXT, 388, 513,
FONT_NEXT, 420, 525,
FONT_NEXT, 428, 537,
FONT_NEXT, 431, 557,
FONT_NEXT, 427, 574,
FONT_NEXT, 417, 585,
FONT_NEXT, 395, 593,
FONT_NEXT, 368, 581,
FONT_NEXT, 341, 554,
FONT_NEXT, 309, 520,
FONT_NEXT, 267, 488,
FONT_NEXT, 260, 484,
FONT_NEXT, 260, 510,
FONT_NEXT, 273, 582,
FONT_NEXT, 282, 613,
FONT_NEXT, 287, 637,
FONT_NEXT, 279, 661,
FONT_NEXT, 269, 671,
FONT_NEXT, 253, 676,
FONT_NEXT, 231, 671,
FONT_NEXT, 220, 659,
FONT_NEXT, 216, 637,
FONT_NEXT, 228, 581,
FONT_NEXT, 237, 548,
FONT_NEXT, 241, 499,
FONT_NEXT, 241, 486,
FONT_NEXT, 185, 523,
FONT_NEXT, 152, 557,
FONT_NEXT, 129, 581,
FONT_NEXT, 102, 591,
FONT_NEXT, 85, 586,
FONT_NEXT, 75, 576,
FONT_NEXT, 69, 557,
FONT_NEXT, 72, 538,
FONT_NEXT, 83, 526,
FONT_NEXT, 120, 511,
FONT_NEXT, 170, 500,
FONT_NEXT, 223, 478,
FONT_NEXT, 234, 471,
FONT_NEXT, 172, 441,
FONT_NEXT, 120, 428,
FONT_NEXT, 82, 415,
FONT_NEXT, 72, 402,
FONT_END, 69, 383,
FONT_ADVANCE, 500, 0
    },
    {
43,
FONT_BEGIN, 30, 286,
FONT_NEXT, 30, 220,
FONT_NEXT, 249, 220,
FONT_NEXT, 249, 0,
FONT_NEXT, 315, 0,
FONT_NEXT, 315, 220,
FONT_NEXT, 534, 220,
FONT_NEXT, 534, 286,
FONT_NEXT, 315, 286,
FONT_NEXT, 315, 506,
FONT_NEXT, 249, 506,
FONT_END, 249, 286,
FONT_ADVANCE, 564, 0
    },
    {
44,
FONT_BEGIN, 113, -124,
FONT_NEXT, 150, -91,
FONT_NEXT, 181, -46,
FONT_NEXT, 195, 6,
FONT_NEXT, 185, 56,
FONT_NEXT, 163, 85,
FONT_NEXT, 136, 98,
FONT_NEXT, 114, 102,
FONT_NEXT, 75, 89,
FONT_NEXT, 61, 71,
FONT_NEXT, 56, 44,
FONT_NEXT, 62, 19,
FONT_NEXT, 77, 4,
FONT_NEXT, 118, -6,
FONT_NEXT, 133, -4,
FONT_NEXT, 142, -2,
FONT_NEXT, 151, -6,
FONT_NEXT, 156, -16,
FONT_NEXT, 152, -35,
FONT_NEXT, 139, -61,
FONT_NEXT, 114, -91,
FONT_NEXT, 74, -122,
FONT_END, 83, -141,
FONT_ADVANCE, 250, 0
    },
    {
45,
FONT_BEGIN, 39, 194,
FONT_NEXT, 285, 194,
FONT_NEXT, 285, 257,
FONT_END, 39, 257,
FONT_ADVANCE, 333, 0
    },
    {
46,
FONT_BEGIN, 160, 2,
FONT_NEXT, 175, 18,
FONT_NEXT, 181, 43,
FONT_NEXT, 176, 64,
FONT_NEXT, 164, 83,
FONT_NEXT, 146, 95,
FONT_NEXT, 125, 100,
FONT_NEXT, 103, 95,
FONT_NEXT, 86, 83,
FONT_NEXT, 74, 65,
FONT_NEXT, 70, 43,
FONT_NEXT, 75, 18,
FONT_NEXT, 89, 1,
FONT_END, 125, -11,
FONT_ADVANCE, 250, 0
    },
    {
47,
FONT_BEGIN, 220, 676,
FONT_NEXT, -9, -14,
FONT_NEXT, 59, -14,
FONT_END, 287, 676,
FONT_ADVANCE, 278, 0
    },
    {
48,
FONT_BEGIN, 292, 639,
FONT_NEXT, 324, 609,
FONT_NEXT, 347, 566,
FONT_NEXT, 363, 516,
FONT_NEXT, 377, 409,
FONT_NEXT, 379, 364,
FONT_NEXT, 380, 331,
FONT_NEXT, 379, 294,
FONT_NEXT, 377, 247,
FONT_NEXT, 363, 141,
FONT_NEXT, 347, 92,
FONT_NEXT, 324, 50,
FONT_NEXT, 292, 22,
FONT_NEXT, 250, 12,
FONT_NEXT, 207, 22,
FONT_NEXT, 175, 50,
FONT_NEXT, 152, 91,
FONT_NEXT, 137, 141,
FONT_NEXT, 122, 246,
FONT_NEXT, 120, 294,
FONT_NEXT, 120, 331,
FONT_NEXT, 120, 364,
FONT_NEXT, 122, 409,
FONT_NEXT, 137, 516,
FONT_NEXT, 152, 566,
FONT_NEXT, 175, 609,
FONT_NEXT, 207, 639,
FONT_END, 250, 650,
FONT_BEGIN, 187, 666,
FONT_NEXT, 137, 638,
FONT_NEXT, 97, 597,
FONT_NEXT, 68, 547,
FONT_NEXT, 33, 434,
FONT_NEXT, 26, 379,
FONT_NEXT, 24, 331,
FONT_NEXT, 33, 226,
FONT_NEXT, 68, 113,
FONT_NEXT, 97, 63,
FONT_NEXT, 137, 22,
FONT_NEXT, 187, -5,
FONT_NEXT, 250, -14,
FONT_NEXT, 312, -5,
FONT_NEXT, 362, 22,
FONT_NEXT, 402, 63,
FONT_NEXT, 432, 113,
FONT_NEXT, 466, 226,
FONT_NEXT, 476, 331,
FONT_NEXT, 473, 379,
FONT_NEXT, 466, 434,
FONT_NEXT, 432, 547,
FONT_NEXT, 402, 597,
FONT_NEXT, 362, 638,
FONT_NEXT, 312, 666,
FONT_END, 250, 676,
FONT_ADVANCE, 500, 0
    },
    {
49,
FONT_BEGIN, 394, 0,
FONT_NEXT, 394, 15,
FONT_NEXT, 347, 17,
FONT_NEXT, 318, 26,
FONT_NEXT, 303, 44,
FONT_NEXT, 299, 74,
FONT_NEXT, 299, 674,
FONT_NEXT, 291, 676,
FONT_NEXT, 111, 585,
FONT_NEXT, 111, 571,
FONT_NEXT, 143, 583,
FONT_NEXT, 161, 590,
FONT_NEXT, 179, 593,
FONT_NEXT, 198, 588,
FONT_NEXT, 208, 577,
FONT_NEXT, 213, 546,
FONT_NEXT, 213, 93,
FONT_NEXT, 206, 49,
FONT_NEXT, 187, 27,
FONT_NEXT, 157, 17,
FONT_NEXT, 118, 15,
FONT_END, 118, 0,
FONT_ADVANCE, 500, 0
    },
    {
50,
FONT_BEGIN, 462, 142,
FONT_NEXT, 435, 105,
FONT_NEXT, 413, 85,
FONT_NEXT, 367, 76,
FONT_NEXT, 128, 76,
FONT_NEXT, 296, 252,
FONT_NEXT, 334, 295,
FONT_NEXT, 376, 351,
FONT_NEXT, 410, 419,
FONT_NEXT, 424, 496,
FONT_NEXT, 419, 535,
FONT_NEXT, 408, 571,
FONT_NEXT, 367, 628,
FONT_NEXT, 309, 663,
FONT_NEXT, 243, 676,
FONT_NEXT, 176, 666,
FONT_NEXT, 115, 633,
FONT_NEXT, 88, 606,
FONT_NEXT, 64, 571,
FONT_NEXT, 45, 528,
FONT_NEXT, 31, 477,
FONT_NEXT, 52, 472,
FONT_NEXT, 70, 515,
FONT_NEXT, 97, 557,
FONT_NEXT, 137, 589,
FONT_NEXT, 195, 602,
FONT_NEXT, 235, 597,
FONT_NEXT, 267, 586,
FONT_NEXT, 310, 549,
FONT_NEXT, 332, 502,
FONT_NEXT, 338, 459,
FONT_NEXT, 329, 400,
FONT_NEXT, 305, 336,
FONT_NEXT, 265, 270,
FONT_NEXT, 208, 201,
FONT_NEXT, 30, 12,
FONT_NEXT, 30, 0,
FONT_NEXT, 420, 0,
FONT_END, 475, 137,
FONT_ADVANCE, 500, 0
    },
    {
51,
FONT_BEGIN, 197, 330,
FONT_NEXT, 230, 326,
FONT_NEXT, 283, 308,
FONT_NEXT, 321, 276,
FONT_NEXT, 345, 239,
FONT_NEXT, 356, 202,
FONT_NEXT, 359, 176,
FONT_NEXT, 351, 120,
FONT_NEXT, 326, 70,
FONT_NEXT, 285, 35,
FONT_NEXT, 225, 22,
FONT_NEXT, 184, 30,
FONT_NEXT, 148, 50,
FONT_NEXT, 115, 69,
FONT_NEXT, 82, 78,
FONT_NEXT, 56, 71,
FONT_NEXT, 46, 60,
FONT_NEXT, 43, 43,
FONT_NEXT, 56, 12,
FONT_NEXT, 89, -5,
FONT_NEXT, 126, -13,
FONT_NEXT, 154, -14,
FONT_NEXT, 210, -10,
FONT_NEXT, 263, 1,
FONT_NEXT, 310, 21,
FONT_NEXT, 351, 47,
FONT_NEXT, 384, 80,
FONT_NEXT, 409, 120,
FONT_NEXT, 425, 165,
FONT_NEXT, 431, 216,
FONT_NEXT, 427, 261,
FONT_NEXT, 417, 298,
FONT_NEXT, 383, 351,
FONT_NEXT, 341, 383,
FONT_NEXT, 304, 401,
FONT_NEXT, 346, 433,
FONT_NEXT, 375, 466,
FONT_NEXT, 391, 502,
FONT_NEXT, 397, 541,
FONT_NEXT, 390, 579,
FONT_NEXT, 367, 623,
FONT_NEXT, 319, 660,
FONT_NEXT, 284, 671,
FONT_NEXT, 241, 676,
FONT_NEXT, 187, 669,
FONT_NEXT, 132, 644,
FONT_NEXT, 83, 594,
FONT_NEXT, 62, 558,
FONT_NEXT, 45, 514,
FONT_NEXT, 60, 510,
FONT_NEXT, 76, 538,
FONT_NEXT, 105, 573,
FONT_NEXT, 148, 603,
FONT_NEXT, 208, 616,
FONT_NEXT, 256, 606,
FONT_NEXT, 290, 583,
FONT_NEXT, 311, 549,
FONT_NEXT, 318, 512,
FONT_NEXT, 304, 450,
FONT_NEXT, 269, 403,
FONT_NEXT, 216, 368,
FONT_NEXT, 152, 343,
FONT_END, 153, 330,
FONT_ADVANCE, 500, 0
    },
    {
52,
FONT_BEGIN, 370, 231,
FONT_NEXT, 370, 676,
FONT_NEXT, 326, 676,
FONT_NEXT, 12, 231,
FONT_NEXT, 12, 167,
FONT_NEXT, 293, 167,
FONT_NEXT, 293, 0,
FONT_NEXT, 370, 0,
FONT_NEXT, 370, 167,
FONT_NEXT, 472, 167,
FONT_END, 472, 231,
FONT_BEGIN, 52, 231,
FONT_NEXT, 290, 571,
FONT_NEXT, 292, 571,
FONT_END, 292, 231,
FONT_ADVANCE, 500, 0
    },
    {
53,
FONT_BEGIN, 377, 583,
FONT_NEXT, 391, 584,
FONT_NEXT, 400, 592,
FONT_NEXT, 438, 681,
FONT_NEXT, 429, 688,
FONT_NEXT, 411, 669,
FONT_NEXT, 383, 662,
FONT_NEXT, 174, 662,
FONT_NEXT, 65, 425,
FONT_NEXT, 64, 415,
FONT_NEXT, 71, 412,
FONT_NEXT, 161, 399,
FONT_NEXT, 254, 365,
FONT_NEXT, 294, 337,
FONT_NEXT, 326, 300,
FONT_NEXT, 348, 255,
FONT_NEXT, 356, 201,
FONT_NEXT, 352, 158,
FONT_NEXT, 343, 121,
FONT_NEXT, 310, 66,
FONT_NEXT, 266, 33,
FONT_NEXT, 217, 23,
FONT_NEXT, 72, 85,
FONT_NEXT, 48, 80,
FONT_NEXT, 37, 69,
FONT_NEXT, 32, 46,
FONT_NEXT, 40, 21,
FONT_NEXT, 65, 2,
FONT_NEXT, 104, -10,
FONT_NEXT, 158, -14,
FONT_NEXT, 214, -10,
FONT_NEXT, 266, 4,
FONT_NEXT, 312, 26,
FONT_NEXT, 351, 55,
FONT_NEXT, 382, 90,
FONT_NEXT, 406, 131,
FONT_NEXT, 420, 176,
FONT_NEXT, 426, 225,
FONT_NEXT, 423, 281,
FONT_NEXT, 415, 323,
FONT_NEXT, 398, 359,
FONT_NEXT, 372, 395,
FONT_NEXT, 321, 439,
FONT_NEXT, 263, 468,
FONT_NEXT, 139, 498,
FONT_END, 181, 583,
FONT_ADVANCE, 500, 0
    },
    {
54,
FONT_BEGIN, 358, 673,
FONT_NEXT, 279, 647,
FONT_NEXT, 209, 609,
FONT_NEXT, 149, 560,
FONT_NEXT, 100, 502,
FONT_NEXT, 64, 436,
FONT_NEXT, 41, 365,
FONT_NEXT, 34, 291,
FONT_NEXT, 38, 217,
FONT_NEXT, 51, 154,
FONT_NEXT, 72, 102,
FONT_NEXT, 99, 59,
FONT_NEXT, 132, 27,
FONT_NEXT, 169, 4,
FONT_NEXT, 210, -10,
FONT_NEXT, 254, -14,
FONT_NEXT, 314, -8,
FONT_NEXT, 362, 11,
FONT_NEXT, 399, 40,
FONT_NEXT, 427, 74,
FONT_NEXT, 459, 150,
FONT_NEXT, 468, 218,
FONT_NEXT, 464, 265,
FONT_NEXT, 454, 307,
FONT_NEXT, 437, 343,
FONT_NEXT, 415, 373,
FONT_NEXT, 357, 414,
FONT_NEXT, 284, 428,
FONT_NEXT, 247, 425,
FONT_NEXT, 217, 417,
FONT_NEXT, 187, 403,
FONT_NEXT, 152, 383,
FONT_NEXT, 181, 475,
FONT_NEXT, 207, 519,
FONT_NEXT, 240, 560,
FONT_NEXT, 281, 597,
FONT_NEXT, 329, 628,
FONT_NEXT, 384, 652,
FONT_NEXT, 448, 668,
FONT_END, 446, 684,
FONT_BEGIN, 278, 377,
FONT_NEXT, 307, 364,
FONT_NEXT, 331, 344,
FONT_NEXT, 349, 319,
FONT_NEXT, 371, 254,
FONT_NEXT, 378, 179,
FONT_NEXT, 369, 105,
FONT_NEXT, 347, 54,
FONT_NEXT, 312, 23,
FONT_NEXT, 269, 14,
FONT_NEXT, 215, 28,
FONT_NEXT, 191, 46,
FONT_NEXT, 169, 72,
FONT_NEXT, 152, 105,
FONT_NEXT, 138, 147,
FONT_NEXT, 130, 197,
FONT_NEXT, 127, 256,
FONT_NEXT, 128, 294,
FONT_NEXT, 133, 321,
FONT_NEXT, 147, 350,
FONT_NEXT, 191, 375,
FONT_END, 242, 382,
FONT_ADVANCE, 500, 0
    },
    {
55,
FONT_BEGIN, 79, 662,
FONT_NEXT, 63, 618,
FONT_NEXT, 20, 515,
FONT_NEXT, 37, 507,
FONT_NEXT, 59, 541,
FONT_NEXT, 83, 567,
FONT_NEXT, 112, 582,
FONT_NEXT, 153, 588,
FONT_NEXT, 370, 588,
FONT_NEXT, 172, -8,
FONT_NEXT, 237, -8,
FONT_NEXT, 449, 646,
FONT_END, 449, 662,
FONT_ADVANCE, 500, 0
    },
    {
56,
FONT_BEGIN, 285, 258,
FONT_NEXT, 333, 216,
FONT_NEXT, 360, 174,
FONT_NEXT, 369, 126,
FONT_NEXT, 360, 78,
FONT_NEXT, 337, 43,
FONT_NEXT, 302, 21,
FONT_NEXT, 259, 14,
FONT_NEXT, 204, 25,
FONT_NEXT, 165, 56,
FONT_NEXT, 140, 101,
FONT_NEXT, 132, 156,
FONT_NEXT, 140, 215,
FONT_NEXT, 160, 258,
FONT_NEXT, 186, 289,
FONT_END, 212, 312,
FONT_BEGIN, 68, 88,
FONT_NEXT, 104, 36,
FONT_NEXT, 164, 0,
FONT_NEXT, 202, -11,
FONT_NEXT, 246, -14,
FONT_NEXT, 302, -10,
FONT_NEXT, 347, 4,
FONT_NEXT, 381, 24,
FONT_NEXT, 407, 48,
FONT_NEXT, 437, 102,
FONT_NEXT, 445, 150,
FONT_NEXT, 439, 199,
FONT_NEXT, 417, 249,
FONT_NEXT, 370, 305,
FONT_NEXT, 335, 336,
FONT_NEXT, 290, 371,
FONT_NEXT, 333, 395,
FONT_NEXT, 377, 428,
FONT_NEXT, 410, 473,
FONT_NEXT, 424, 534,
FONT_NEXT, 412, 588,
FONT_NEXT, 378, 633,
FONT_NEXT, 324, 664,
FONT_NEXT, 249, 676,
FONT_NEXT, 183, 665,
FONT_NEXT, 123, 636,
FONT_NEXT, 79, 587,
FONT_NEXT, 66, 556,
FONT_NEXT, 62, 521,
FONT_NEXT, 69, 467,
FONT_NEXT, 92, 423,
FONT_NEXT, 131, 381,
FONT_NEXT, 186, 332,
FONT_NEXT, 128, 286,
FONT_NEXT, 87, 246,
FONT_NEXT, 63, 202,
FONT_END, 56, 146,
FONT_BEGIN, 142, 583,
FONT_NEXT, 161, 615,
FONT_NEXT, 194, 638,
FONT_NEXT, 243, 648,
FONT_NEXT, 292, 638,
FONT_NEXT, 327, 614,
FONT_NEXT, 348, 579,
FONT_NEXT, 355, 539,
FONT_NEXT, 345, 481,
FONT_NEXT, 322, 440,
FONT_NEXT, 291, 410,
FONT_NEXT, 261, 389,
FONT_NEXT, 226, 412,
FONT_NEXT, 185, 447,
FONT_NEXT, 150, 492,
FONT_END, 136, 547,
FONT_ADVANCE, 500, 0
    },
    {
57,
FONT_BEGIN, 149, -9,
FONT_NEXT, 228, 20,
FONT_NEXT, 297, 61,
FONT_NEXT, 354, 113,
FONT_NEXT, 399, 174,
FONT_NEXT, 432, 243,
FONT_NEXT, 452, 317,
FONT_NEXT, 459, 396,
FONT_NEXT, 454, 455,
FONT_NEXT, 442, 510,
FONT_NEXT, 422, 557,
FONT_NEXT, 395, 598,
FONT_NEXT, 363, 631,
FONT_NEXT, 326, 655,
FONT_NEXT, 284, 670,
FONT_NEXT, 240, 676,
FONT_NEXT, 193, 671,
FONT_NEXT, 152, 657,
FONT_NEXT, 116, 634,
FONT_NEXT, 86, 605,
FONT_NEXT, 62, 569,
FONT_NEXT, 44, 528,
FONT_NEXT, 30, 435,
FONT_NEXT, 39, 369,
FONT_NEXT, 69, 305,
FONT_NEXT, 94, 278,
FONT_NEXT, 125, 256,
FONT_NEXT, 163, 242,
FONT_NEXT, 210, 237,
FONT_NEXT, 285, 250,
FONT_NEXT, 357, 292,
FONT_NEXT, 359, 290,
FONT_NEXT, 354, 271,
FONT_NEXT, 343, 239,
FONT_NEXT, 324, 197,
FONT_NEXT, 296, 149,
FONT_NEXT, 256, 101,
FONT_NEXT, 204, 56,
FONT_NEXT, 138, 21,
FONT_NEXT, 56, -2,
FONT_END, 59, -22,
FONT_BEGIN, 358, 332,
FONT_NEXT, 346, 315,
FONT_NEXT, 302, 291,
FONT_NEXT, 250, 280,
FONT_NEXT, 214, 285,
FONT_NEXT, 185, 299,
FONT_NEXT, 146, 348,
FONT_NEXT, 127, 410,
FONT_NEXT, 122, 473,
FONT_NEXT, 126, 534,
FONT_NEXT, 144, 591,
FONT_NEXT, 176, 632,
FONT_NEXT, 200, 643,
FONT_NEXT, 229, 648,
FONT_NEXT, 272, 639,
FONT_NEXT, 305, 616,
FONT_NEXT, 329, 583,
FONT_NEXT, 345, 543,
FONT_NEXT, 359, 458,
FONT_NEXT, 361, 421,
FONT_NEXT, 362, 394,
FONT_END, 362, 355,
FONT_ADVANCE, 500, 0
    },
    {
58,
FONT_BEGIN, 171, 361,
FONT_NEXT, 186, 377,
FONT_NEXT, 192, 402,
FONT_NEXT, 187, 423,
FONT_NEXT, 175, 442,
FONT_NEXT, 157, 454,
FONT_NEXT, 136, 459,
FONT_NEXT, 114, 454,
FONT_NEXT, 97, 442,
FONT_NEXT, 85, 424,
FONT_NEXT, 81, 402,
FONT_NEXT, 86, 377,
FONT_NEXT, 100, 360,
FONT_END, 136, 348,
FONT_BEGIN, 171, 2,
FONT_NEXT, 186, 18,
FONT_NEXT, 192, 43,
FONT_NEXT, 187, 64,
FONT_NEXT, 175, 83,
FONT_NEXT, 157, 95,
FONT_NEXT, 136, 100,
FONT_NEXT, 114, 95,
FONT_NEXT, 97, 83,
FONT_NEXT, 85, 65,
FONT_NEXT, 81, 43,
FONT_NEXT, 86, 18,
FONT_NEXT, 100, 1,
FONT_END, 136, -11,
FONT_ADVANCE, 278, 0
    },
    {
59,
FONT_BEGIN, 138, -123,
FONT_NEXT, 175, -90,
FONT_NEXT, 206, -45,
FONT_NEXT, 219, 6,
FONT_NEXT, 209, 56,
FONT_NEXT, 186, 85,
FONT_NEXT, 160, 98,
FONT_NEXT, 139, 102,
FONT_NEXT, 125, 100,
FONT_NEXT, 105, 93,
FONT_NEXT, 87, 76,
FONT_NEXT, 80, 44,
FONT_NEXT, 85, 20,
FONT_NEXT, 99, 5,
FONT_NEXT, 142, -6,
FONT_NEXT, 157, -4,
FONT_NEXT, 166, -2,
FONT_NEXT, 175, -6,
FONT_NEXT, 180, -16,
FONT_NEXT, 176, -35,
FONT_NEXT, 163, -61,
FONT_NEXT, 138, -91,
FONT_NEXT, 98, -122,
FONT_END, 107, -141,
FONT_BEGIN, 171, 361,
FONT_NEXT, 186, 377,
FONT_NEXT, 192, 402,
FONT_NEXT, 187, 423,
FONT_NEXT, 175, 442,
FONT_NEXT, 157, 454,
FONT_NEXT, 136, 459,
FONT_NEXT, 114, 454,
FONT_NEXT, 97, 442,
FONT_NEXT, 85, 424,
FONT_NEXT, 81, 402,
FONT_NEXT, 86, 377,
FONT_NEXT, 100, 360,
FONT_END, 136, 348,
FONT_ADVANCE, 278, 0
    },
    {
60,
FONT_BEGIN, 111, 253,
FONT_NEXT, 536, 446,
FONT_NEXT, 536, 514,
FONT_NEXT, 28, 284,
FONT_NEXT, 28, 222,
FONT_NEXT, 536, -8,
FONT_END, 536, 60,
FONT_ADVANCE, 564, 0
    },
    {
61,
FONT_BEGIN, 30, 386,
FONT_NEXT, 30, 320,
FONT_NEXT, 534, 320,
FONT_END, 534, 386,
FONT_BEGIN, 30, 186,
FONT_NEXT, 30, 120,
FONT_NEXT, 534, 120,
FONT_END, 534, 186,
FONT_ADVANCE, 564, 0
    },
    {
62,
FONT_BEGIN, 28, -8,
FONT_NEXT, 536, 222,
FONT_NEXT, 536, 284,
FONT_NEXT, 28, 514,
FONT_NEXT, 28, 446,
FONT_NEXT, 453, 253,
FONT_END, 28, 60,
FONT_ADVANCE, 564, 0
    },
    {
63,
FONT_BEGIN, 257, 220,
FONT_NEXT, 276, 267,
FONT_NEXT, 302, 309,
FONT_NEXT, 339, 354,
FONT_NEXT, 363, 385,
FONT_NEXT, 387, 426,
FONT_NEXT, 406, 469,
FONT_NEXT, 414, 510,
FONT_NEXT, 412, 544,
FONT_NEXT, 406, 571,
FONT_NEXT, 378, 618,
FONT_NEXT, 352, 639,
FONT_NEXT, 316, 657,
FONT_NEXT, 274, 671,
FONT_NEXT, 231, 676,
FONT_NEXT, 176, 667,
FONT_NEXT, 123, 641,
FONT_NEXT, 83, 597,
FONT_NEXT, 72, 567,
FONT_NEXT, 68, 532,
FONT_NEXT, 78, 487,
FONT_NEXT, 93, 474,
FONT_NEXT, 118, 469,
FONT_NEXT, 135, 473,
FONT_NEXT, 148, 483,
FONT_NEXT, 157, 510,
FONT_NEXT, 119, 585,
FONT_NEXT, 127, 608,
FONT_NEXT, 149, 627,
FONT_NEXT, 179, 641,
FONT_NEXT, 212, 646,
FONT_NEXT, 261, 634,
FONT_NEXT, 295, 605,
FONT_NEXT, 315, 564,
FONT_NEXT, 322, 519,
FONT_NEXT, 310, 444,
FONT_NEXT, 285, 370,
FONT_NEXT, 257, 306,
FONT_NEXT, 249, 286,
FONT_NEXT, 239, 251,
FONT_NEXT, 227, 164,
FONT_END, 244, 164,
FONT_BEGIN, 273, 4,
FONT_NEXT, 286, 19,
FONT_NEXT, 292, 43,
FONT_NEXT, 287, 63,
FONT_NEXT, 276, 80,
FONT_NEXT, 258, 92,
FONT_NEXT, 237, 97,
FONT_NEXT, 216, 92,
FONT_NEXT, 199, 81,
FONT_NEXT, 188, 64,
FONT_NEXT, 184, 43,
FONT_NEXT, 189, 19,
FONT_NEXT, 202, 3,
FONT_END, 237, -8,
FONT_ADVANCE, 444, 0
    },
    {
64,
FONT_BEGIN, 554, 306,
FONT_NEXT, 524, 240,
FONT_NEXT, 489, 200,
FONT_NEXT, 455, 187,
FONT_NEXT, 430, 191,
FONT_NEXT, 410, 206,
FONT_NEXT, 396, 232,
FONT_NEXT, 392, 271,
FONT_NEXT, 395, 314,
FONT_NEXT, 406, 352,
FONT_NEXT, 442, 412,
FONT_NEXT, 487, 450,
FONT_NEXT, 529, 464,
FONT_NEXT, 548, 459,
FONT_NEXT, 561, 447,
FONT_NEXT, 569, 426,
FONT_END, 572, 399,
FONT_BEGIN, 601, 39,
FONT_NEXT, 550, 28,
FONT_NEXT, 490, 25,
FONT_NEXT, 427, 31,
FONT_NEXT, 371, 48,
FONT_NEXT, 321, 75,
FONT_NEXT, 279, 111,
FONT_NEXT, 245, 153,
FONT_NEXT, 220, 200,
FONT_NEXT, 205, 251,
FONT_NEXT, 200, 305,
FONT_NEXT, 205, 383,
FONT_NEXT, 222, 451,
FONT_NEXT, 248, 510,
FONT_NEXT, 283, 558,
FONT_NEXT, 324, 595,
FONT_NEXT, 372, 623,
FONT_NEXT, 424, 639,
FONT_NEXT, 481, 645,
FONT_NEXT, 545, 638,
FONT_NEXT, 603, 619,
FONT_NEXT, 652, 591,
FONT_NEXT, 693, 555,
FONT_NEXT, 726, 513,
FONT_NEXT, 749, 469,
FONT_NEXT, 764, 424,
FONT_NEXT, 769, 380,
FONT_NEXT, 760, 311,
FONT_NEXT, 734, 245,
FONT_NEXT, 693, 195,
FONT_NEXT, 667, 181,
FONT_NEXT, 639, 176,
FONT_NEXT, 613, 183,
FONT_NEXT, 603, 202,
FONT_NEXT, 603, 240,
FONT_NEXT, 668, 494,
FONT_NEXT, 599, 494,
FONT_NEXT, 589, 456,
FONT_NEXT, 565, 491,
FONT_NEXT, 546, 503,
FONT_NEXT, 518, 508,
FONT_NEXT, 470, 500,
FONT_NEXT, 430, 480,
FONT_NEXT, 396, 450,
FONT_NEXT, 368, 414,
FONT_NEXT, 332, 335,
FONT_NEXT, 323, 298,
FONT_NEXT, 321, 268,
FONT_NEXT, 323, 230,
FONT_NEXT, 332, 200,
FONT_NEXT, 358, 163,
FONT_NEXT, 390, 147,
FONT_NEXT, 417, 144,
FONT_NEXT, 457, 154,
FONT_NEXT, 492, 174,
FONT_NEXT, 517, 196,
FONT_NEXT, 532, 214,
FONT_NEXT, 534, 214,
FONT_NEXT, 538, 197,
FONT_NEXT, 553, 173,
FONT_NEXT, 581, 152,
FONT_NEXT, 623, 143,
FONT_NEXT, 665, 149,
FONT_NEXT, 702, 165,
FONT_NEXT, 734, 191,
FONT_NEXT, 760, 223,
FONT_NEXT, 796, 300,
FONT_NEXT, 809, 381,
FONT_NEXT, 802, 442,
FONT_NEXT, 783, 498,
FONT_NEXT, 753, 548,
FONT_NEXT, 714, 591,
FONT_NEXT, 665, 627,
FONT_NEXT, 609, 653,
FONT_NEXT, 547, 670,
FONT_NEXT, 481, 676,
FONT_NEXT, 408, 669,
FONT_NEXT, 340, 649,
FONT_NEXT, 278, 618,
FONT_NEXT, 224, 576,
FONT_NEXT, 179, 524,
FONT_NEXT, 145, 462,
FONT_NEXT, 123, 392,
FONT_NEXT, 116, 315,
FONT_NEXT, 121, 262,
FONT_NEXT, 137, 207,
FONT_NEXT, 165, 152,
FONT_NEXT, 205, 100,
FONT_NEXT, 257, 54,
FONT_NEXT, 322, 18,
FONT_NEXT, 399, -6,
FONT_NEXT, 491, -14,
FONT_NEXT, 547, -10,
FONT_NEXT, 601, 3,
FONT_NEXT, 700, 43,
FONT_END, 688, 73,
FONT_ADVANCE, 921, 0
    },
    {
65,
FONT_BEGIN, 677, 23,
FONT_NEXT, 656, 36,
FONT_NEXT, 637, 62,
FONT_NEXT, 616, 106,
FONT_NEXT, 367, 674,
FONT_NEXT, 347, 674,
FONT_NEXT, 139, 183,
FONT_NEXT, 104, 104,
FONT_NEXT, 84, 67,
FONT_NEXT, 66, 41,
FONT_NEXT, 39, 23,
FONT_NEXT, 15, 19,
FONT_NEXT, 15, 0,
FONT_NEXT, 213, 0,
FONT_NEXT, 213, 19,
FONT_NEXT, 171, 24,
FONT_NEXT, 152, 36,
FONT_NEXT, 145, 61,
FONT_NEXT, 153, 99,
FONT_NEXT, 199, 216,
FONT_NEXT, 461, 216,
FONT_NEXT, 502, 120,
FONT_NEXT, 521, 57,
FONT_NEXT, 515, 35,
FONT_NEXT, 499, 24,
FONT_NEXT, 451, 19,
FONT_NEXT, 451, 0,
FONT_NEXT, 706, 0,
FONT_END, 706, 19,
FONT_BEGIN, 331, 532,
FONT_NEXT, 447, 257,
FONT_END, 216, 257,
FONT_ADVANCE, 722, 0
    },
    {
66,
FONT_BEGIN, 66, 637,
FONT_NEXT, 95, 624,
FONT_NEXT, 109, 597,
FONT_NEXT, 113, 553,
FONT_NEXT, 113, 109,
FONT_NEXT, 109, 65,
FONT_NEXT, 96, 38,
FONT_NEXT, 67, 23,
FONT_NEXT, 17, 19,
FONT_NEXT, 17, 0,
FONT_NEXT, 351, 0,
FONT_NEXT, 425, 6,
FONT_NEXT, 482, 22,
FONT_NEXT, 525, 45,
FONT_NEXT, 555, 73,
FONT_NEXT, 574, 103,
FONT_NEXT, 586, 132,
FONT_NEXT, 593, 176,
FONT_NEXT, 589, 212,
FONT_NEXT, 579, 244,
FONT_NEXT, 542, 294,
FONT_NEXT, 488, 327,
FONT_NEXT, 426, 347,
FONT_NEXT, 426, 349,
FONT_NEXT, 465, 360,
FONT_NEXT, 509, 383,
FONT_NEXT, 544, 425,
FONT_NEXT, 555, 455,
FONT_NEXT, 559, 493,
FONT_NEXT, 551, 544,
FONT_NEXT, 530, 584,
FONT_NEXT, 499, 614,
FONT_NEXT, 460, 635,
FONT_NEXT, 374, 657,
FONT_NEXT, 297, 662,
FONT_NEXT, 17, 662,
FONT_END, 17, 643,
FONT_BEGIN, 276, 325,
FONT_NEXT, 319, 322,
FONT_NEXT, 377, 311,
FONT_NEXT, 414, 295,
FONT_NEXT, 447, 270,
FONT_NEXT, 469, 232,
FONT_NEXT, 478, 180,
FONT_NEXT, 472, 135,
FONT_NEXT, 458, 101,
FONT_NEXT, 436, 75,
FONT_NEXT, 409, 58,
FONT_NEXT, 348, 40,
FONT_NEXT, 291, 37,
FONT_NEXT, 254, 38,
FONT_NEXT, 231, 43,
FONT_NEXT, 218, 55,
FONT_NEXT, 215, 78,

FONT_END, 215, 326,
FONT_BEGIN, 218, 615,
FONT_NEXT, 237, 624,
FONT_NEXT, 255, 624,
FONT_NEXT, 282, 625,
FONT_NEXT, 339, 620,
FONT_NEXT, 396, 600,
FONT_NEXT, 420, 582,
FONT_NEXT, 439, 558,
FONT_NEXT, 452, 527,
FONT_NEXT, 457, 487,
FONT_NEXT, 451, 447,
FONT_NEXT, 437, 417,
FONT_NEXT, 394, 381,
FONT_NEXT, 344, 368,
FONT_NEXT, 310, 366,
FONT_NEXT, 215, 365,
FONT_END, 215, 595,
FONT_ADVANCE, 667, 0
    },
    {
67,
FONT_BEGIN, 611, 676,
FONT_NEXT, 590, 676,
FONT_NEXT, 580, 656,
FONT_NEXT, 565, 647,
FONT_NEXT, 544, 643,
FONT_NEXT, 519, 648,
FONT_NEXT, 481, 659,
FONT_NEXT, 429, 670,
FONT_NEXT, 368, 676,
FONT_NEXT, 304, 670,
FONT_NEXT, 242, 653,
FONT_NEXT, 184, 625,
FONT_NEXT, 133, 587,
FONT_NEXT, 90, 538,
FONT_NEXT, 56, 479,
FONT_NEXT, 35, 410,
FONT_NEXT, 28, 331,
FONT_NEXT, 35, 244,
FONT_NEXT, 57, 172,
FONT_NEXT, 91, 112,
FONT_NEXT, 134, 65,
FONT_NEXT, 185, 29,
FONT_NEXT, 241, 4,
FONT_NEXT, 301, -10,
FONT_NEXT, 362, -14,
FONT_NEXT, 426, -9,
FONT_NEXT, 481, 5,
FONT_NEXT, 528, 25,
FONT_NEXT, 566, 48,
FONT_NEXT, 595, 71,
FONT_NEXT, 616, 92,
FONT_NEXT, 628, 106,
FONT_NEXT, 633, 113,
FONT_NEXT, 615, 131,
FONT_NEXT, 583, 102,
FONT_NEXT, 536, 69,
FONT_NEXT, 471, 41,
FONT_NEXT, 389, 30,
FONT_NEXT, 342, 34,
FONT_NEXT, 297, 46,
FONT_NEXT, 256, 68,
FONT_NEXT, 219, 100,
FONT_NEXT, 188, 141,
FONT_NEXT, 164, 192,
FONT_NEXT, 149, 255,
FONT_NEXT, 144, 329,
FONT_NEXT, 151, 422,
FONT_NEXT, 170, 491,
FONT_NEXT, 197, 540,
FONT_NEXT, 227, 575,
FONT_NEXT, 261, 601,
FONT_NEXT, 299, 620,
FONT_NEXT, 377, 636,
FONT_NEXT, 420, 632,
FONT_NEXT, 459, 621,
FONT_NEXT, 523, 582,
FONT_NEXT, 569, 524,
FONT_NEXT, 597, 451,
FONT_END, 620, 451,
FONT_ADVANCE, 667, 0
    },
    {
68,
FONT_BEGIN, 300, 0,
FONT_NEXT, 404, 8,
FONT_NEXT, 489, 32,
FONT_NEXT, 556, 69,
FONT_NEXT, 607, 114,
FONT_NEXT, 643, 166,
FONT_NEXT, 667, 221,
FONT_NEXT, 681, 276,
FONT_NEXT, 685, 329,
FONT_NEXT, 677, 407,
FONT_NEXT, 656, 475,
FONT_NEXT, 622, 532,
FONT_NEXT, 575, 579,
FONT_NEXT, 517, 615,
FONT_NEXT, 449, 641,
FONT_NEXT, 372, 656,
FONT_NEXT, 286, 662,
FONT_NEXT, 16, 662,
FONT_NEXT, 16, 643,
FONT_NEXT, 62, 636,
FONT_NEXT, 88, 623,
FONT_NEXT, 101, 597,
FONT_NEXT, 104, 553,
FONT_NEXT, 104, 109,
FONT_NEXT, 100, 65,
FONT_NEXT, 88, 39,
FONT_NEXT, 61, 24,
FONT_NEXT, 16, 19,
FONT_END, 16, 0,
FONT_BEGIN, 211, 613,
FONT_NEXT, 225, 621,
FONT_NEXT, 253, 625,
FONT_NEXT, 335, 620,
FONT_NEXT, 397, 606,
FONT_NEXT, 444, 584,
FONT_NEXT, 483, 555,
FONT_NEXT, 526, 505,
FONT_NEXT, 555, 449,
FONT_NEXT, 571, 390,
FONT_NEXT, 576, 328,
FONT_NEXT, 573, 276,
FONT_NEXT, 565, 231,
FONT_NEXT, 536, 159,
FONT_NEXT, 494, 108,
FONT_NEXT, 445, 73,
FONT_NEXT, 392, 52,
FONT_NEXT, 341, 41,
FONT_NEXT, 297, 37,
FONT_NEXT, 266, 37,
FONT_NEXT, 236, 38,
FONT_NEXT, 218, 44,
FONT_NEXT, 208, 57,
FONT_NEXT, 206, 78,
FONT_END, 206, 586,
FONT_ADVANCE, 722, 0
    },
    {
69,
FONT_BEGIN, 569, 169,
FONT_NEXT, 533, 105,
FONT_NEXT, 489, 65,
FONT_NEXT, 430, 44,
FONT_NEXT, 350, 38,
FONT_NEXT, 300, 38,
FONT_NEXT, 263, 38,
FONT_NEXT, 237, 40,
FONT_NEXT, 219, 43,
FONT_NEXT, 203, 56,
FONT_NEXT, 201, 80,
FONT_NEXT, 201, 328,
FONT_NEXT, 355, 326,
FONT_NEXT, 405, 321,
FONT_NEXT, 436, 308,
FONT_NEXT, 454, 279,
FONT_NEXT, 465, 231,
FONT_NEXT, 488, 231,
FONT_NEXT, 488, 463,
FONT_NEXT, 465, 463,
FONT_NEXT, 455, 418,
FONT_NEXT, 439, 388,
FONT_NEXT, 408, 372,
FONT_NEXT, 355, 368,
FONT_NEXT, 201, 368,
FONT_NEXT, 201, 590,
FONT_NEXT, 203, 609,
FONT_NEXT, 209, 619,
FONT_NEXT, 234, 624,
FONT_NEXT, 369, 624,
FONT_NEXT, 437, 620,
FONT_NEXT, 481, 604,
FONT_NEXT, 506, 572,
FONT_NEXT, 521, 519,
FONT_NEXT, 546, 519,
FONT_NEXT, 543, 662,
FONT_NEXT, 12, 662,
FONT_NEXT, 12, 643,
FONT_NEXT, 52, 637,
FONT_NEXT, 79, 625,
FONT_NEXT, 94, 599,
FONT_NEXT, 99, 553,
FONT_NEXT, 99, 109,
FONT_NEXT, 94, 63,
FONT_NEXT, 79, 36,
FONT_NEXT, 52, 23,
FONT_NEXT, 12, 19,
FONT_NEXT, 12, 0,
FONT_NEXT, 552, 0,
FONT_END, 597, 169,
FONT_ADVANCE, 611, 0
    },
    {
70,
FONT_BEGIN, 479, 463,
FONT_NEXT, 456, 463,
FONT_NEXT, 446, 416,
FONT_NEXT, 428, 387,
FONT_NEXT, 396, 372,
FONT_NEXT, 346, 368,
FONT_NEXT, 201, 368,
FONT_NEXT, 201, 590,
FONT_NEXT, 202, 607,
FONT_NEXT, 207, 618,
FONT_NEXT, 233, 624,
FONT_NEXT, 369, 624,
FONT_NEXT, 437, 620,
FONT_NEXT, 481, 604,
FONT_NEXT, 506, 572,
FONT_NEXT, 521, 519,
FONT_NEXT, 546, 519,
FONT_NEXT, 543, 662,
FONT_NEXT, 12, 662,
FONT_NEXT, 12, 643,
FONT_NEXT, 52, 637,
FONT_NEXT, 79, 625,
FONT_NEXT, 94, 599,
FONT_NEXT, 99, 553,
FONT_NEXT, 99, 120,
FONT_NEXT, 95, 70,
FONT_NEXT, 83, 40,
FONT_NEXT, 57, 24,
FONT_NEXT, 12, 19,
FONT_NEXT, 12, 0,
FONT_NEXT, 292, 0,
FONT_NEXT, 292, 19,
FONT_NEXT, 247, 23,
FONT_NEXT, 219, 37,
FONT_NEXT, 205, 63,
FONT_NEXT, 201, 109,
FONT_NEXT, 201, 328,
FONT_NEXT, 346, 326,
FONT_NEXT, 393, 322,
FONT_NEXT, 425, 309,
FONT_NEXT, 444, 281,
FONT_NEXT, 456, 231,
FONT_END, 479, 231,
FONT_ADVANCE, 556, 0
    },
    {
71,
FONT_BEGIN, 454, 354,
FONT_NEXT, 454, 336,
FONT_NEXT, 496, 331,
FONT_NEXT, 523, 320,
FONT_NEXT, 537, 295,
FONT_NEXT, 542, 247,
FONT_NEXT, 542, 85,
FONT_NEXT, 530, 60,
FONT_NEXT, 499, 42,
FONT_NEXT, 455, 30,
FONT_NEXT, 405, 26,
FONT_NEXT, 349, 31,
FONT_NEXT, 299, 46,
FONT_NEXT, 254, 71,
FONT_NEXT, 217, 105,
FONT_NEXT, 187, 147,
FONT_NEXT, 164, 197,
FONT_NEXT, 150, 255,
FONT_NEXT, 146, 320,
FONT_NEXT, 152, 406,
FONT_NEXT, 170, 476,
FONT_NEXT, 197, 531,
FONT_NEXT, 231, 572,
FONT_NEXT, 269, 602,
FONT_NEXT, 310, 622,
FONT_NEXT, 388, 636,
FONT_NEXT, 436, 631,
FONT_NEXT, 477, 619,
FONT_NEXT, 539, 577,
FONT_NEXT, 581, 522,
FONT_NEXT, 607, 465,
FONT_NEXT, 630, 465,
FONT_NEXT, 622, 676,
FONT_NEXT, 600, 676,
FONT_NEXT, 591, 659,
FONT_NEXT, 577, 649,
FONT_NEXT, 553, 643,
FONT_NEXT, 525, 648,
FONT_NEXT, 489, 659,
FONT_NEXT, 440, 670,
FONT_NEXT, 374, 676,
FONT_NEXT, 298, 668,
FONT_NEXT, 231, 647,
FONT_NEXT, 172, 614,
FONT_NEXT, 123, 571,
FONT_NEXT, 84, 520,
FONT_NEXT, 55, 464,
FONT_NEXT, 38, 403,
FONT_NEXT, 32, 341,
FONT_NEXT, 37, 255,
FONT_NEXT, 53, 189,
FONT_NEXT, 79, 137,
FONT_NEXT, 115, 93,
FONT_NEXT, 174, 43,
FONT_NEXT, 245, 10,
FONT_NEXT, 320, -9,
FONT_NEXT, 396, -14,
FONT_NEXT, 480, -5,
FONT_NEXT, 558, 16,
FONT_NEXT, 616, 40,
FONT_NEXT, 633, 50,
FONT_NEXT, 639, 58,
FONT_NEXT, 639, 259,
FONT_NEXT, 643, 300,
FONT_NEXT, 657, 322,
FONT_NEXT, 679, 332,
FONT_NEXT, 709, 336,
FONT_END, 709, 354,
FONT_ADVANCE, 722, 0
    },
    {
72,
FONT_BEGIN, 297, 0,
FONT_NEXT, 297, 19,
FONT_NEXT, 250, 25,
FONT_NEXT, 223, 41,
FONT_NEXT, 211, 67,
FONT_NEXT, 209, 109,
FONT_NEXT, 209, 315,
FONT_NEXT, 512, 315,
FONT_NEXT, 512, 120,
FONT_NEXT, 510, 73,
FONT_NEXT, 499, 42,
FONT_NEXT, 473, 25,
FONT_NEXT, 424, 19,
FONT_NEXT, 424, 0,
FONT_NEXT, 702, 0,
FONT_NEXT, 702, 19,
FONT_NEXT, 655, 25,
FONT_NEXT, 628, 41,
FONT_NEXT, 616, 67,
FONT_NEXT, 614, 109,
FONT_NEXT, 614, 553,
FONT_NEXT, 617, 596,
FONT_NEXT, 629, 622,
FONT_NEXT, 656, 636,
FONT_NEXT, 702, 643,
FONT_NEXT, 702, 662,
FONT_NEXT, 424, 662,
FONT_NEXT, 424, 643,
FONT_NEXT, 468, 636,
FONT_NEXT, 495, 623,
FONT_NEXT, 508, 597,
FONT_NEXT, 512, 553,
FONT_NEXT, 512, 359,
FONT_NEXT, 209, 359,
FONT_NEXT, 209, 553,
FONT_NEXT, 212, 596,
FONT_NEXT, 224, 622,
FONT_NEXT, 251, 636,
FONT_NEXT, 297, 643,
FONT_NEXT, 297, 662,
FONT_NEXT, 19, 662,
FONT_NEXT, 19, 643,
FONT_NEXT, 63, 636,
FONT_NEXT, 90, 623,
FONT_NEXT, 103, 597,
FONT_NEXT, 107, 553,
FONT_NEXT, 107, 120,
FONT_NEXT, 105, 73,
FONT_NEXT, 94, 42,
FONT_NEXT, 68, 25,
FONT_NEXT, 19, 19,
FONT_END, 19, 0,
FONT_ADVANCE, 722, 0
    },
    {
73,
FONT_BEGIN, 315, 0,
FONT_NEXT, 315, 19,
FONT_NEXT, 268, 22,
FONT_NEXT, 238, 35,
FONT_NEXT, 221, 62,
FONT_NEXT, 217, 109,
FONT_NEXT, 217, 553,
FONT_NEXT, 222, 600,
FONT_NEXT, 238, 626,
FONT_NEXT, 268, 638,
FONT_NEXT, 315, 643,
FONT_NEXT, 315, 662,
FONT_NEXT, 18, 662,
FONT_NEXT, 18, 643,
FONT_NEXT, 68, 637,
FONT_NEXT, 97, 624,
FONT_NEXT, 111, 597,
FONT_NEXT, 115, 553,
FONT_NEXT, 115, 109,
FONT_NEXT, 111, 65,
FONT_NEXT, 98, 38,
FONT_NEXT, 69, 23,
FONT_NEXT, 18, 19,
FONT_END, 18, 0,
FONT_ADVANCE, 333, 0
    },
    {
74,
FONT_BEGIN, 281, 597,
FONT_NEXT, 294, 623,
FONT_NEXT, 322, 637,
FONT_NEXT, 370, 643,
FONT_NEXT, 370, 662,
FONT_NEXT, 83, 662,
FONT_NEXT, 83, 643,
FONT_NEXT, 131, 636,
FONT_NEXT, 159, 622,
FONT_NEXT, 172, 596,
FONT_NEXT, 176, 553,
FONT_NEXT, 176, 90,
FONT_NEXT, 173, 57,
FONT_NEXT, 165, 37,
FONT_NEXT, 153, 27,
FONT_NEXT, 137, 24,
FONT_NEXT, 117, 37,
FONT_NEXT, 107, 66,
FONT_NEXT, 92, 94,
FONT_NEXT, 77, 104,
FONT_NEXT, 56, 108,
FONT_NEXT, 36, 103,
FONT_NEXT, 22, 90,
FONT_NEXT, 10, 59,
FONT_NEXT, 15, 36,
FONT_NEXT, 32, 11,
FONT_NEXT, 59, -6,
FONT_NEXT, 110, -14,
FONT_NEXT, 124, -14,
FONT_NEXT, 147, -11,
FONT_NEXT, 204, 11,
FONT_NEXT, 231, 35,
FONT_NEXT, 255, 70,
FONT_NEXT, 271, 118,
FONT_NEXT, 278, 183,
FONT_END, 278, 553,
FONT_ADVANCE, 389, 0
    },
    {
75,
FONT_BEGIN, 444, 641,
FONT_NEXT, 465, 636,
FONT_NEXT, 477, 627,
FONT_NEXT, 481, 612,
FONT_NEXT, 472, 584,
FONT_NEXT, 459, 567,
FONT_NEXT, 438, 543,
FONT_NEXT, 406, 511,
FONT_NEXT, 361, 470,
FONT_NEXT, 301, 416,
FONT_NEXT, 226, 348,
FONT_NEXT, 226, 553,
FONT_NEXT, 229, 597,
FONT_NEXT, 242, 623,
FONT_NEXT, 270, 637,
FONT_NEXT, 318, 643,
FONT_NEXT, 318, 662,
FONT_NEXT, 34, 662,
FONT_NEXT, 34, 643,
FONT_NEXT, 76, 637,
FONT_NEXT, 104, 625,
FONT_NEXT, 119, 599,
FONT_NEXT, 124, 553,
FONT_NEXT, 124, 120,
FONT_NEXT, 120, 70,
FONT_NEXT, 108, 40,
FONT_NEXT, 80, 24,
FONT_NEXT, 34, 19,
FONT_NEXT, 34, 0,
FONT_NEXT, 316, 0,
FONT_NEXT, 316, 19,
FONT_NEXT, 269, 24,
FONT_NEXT, 241, 39,
FONT_NEXT, 229, 65,
FONT_NEXT, 226, 109,
FONT_NEXT, 226, 296,
FONT_NEXT, 252, 317,
FONT_NEXT, 358, 212,
FONT_NEXT, 392, 175,
FONT_NEXT, 435, 126,
FONT_NEXT, 472, 78,
FONT_NEXT, 483, 58,
FONT_NEXT, 488, 44,
FONT_NEXT, 484, 31,
FONT_NEXT, 472, 24,
FONT_NEXT, 451, 20,
FONT_NEXT, 418, 19,
FONT_NEXT, 418, 0,
FONT_NEXT, 723, 0,
FONT_NEXT, 723, 19,
FONT_NEXT, 683, 24,
FONT_NEXT, 647, 45,
FONT_NEXT, 609, 79,
FONT_NEXT, 566, 127,
FONT_NEXT, 333, 377,
FONT_NEXT, 523, 565,
FONT_NEXT, 568, 606,
FONT_NEXT, 603, 628,
FONT_NEXT, 635, 638,
FONT_NEXT, 675, 643,
FONT_NEXT, 675, 662,
FONT_NEXT, 413, 662,
FONT_END, 413, 643,
FONT_ADVANCE, 722, 0
    },
    {
76,
FONT_BEGIN, 12, 662,
FONT_NEXT, 12, 643,
FONT_NEXT, 52, 637,
FONT_NEXT, 79, 625,
FONT_NEXT, 94, 599,
FONT_NEXT, 99, 553,
FONT_NEXT, 99, 109,
FONT_NEXT, 94, 63,
FONT_NEXT, 79, 36,
FONT_NEXT, 52, 23,
FONT_NEXT, 12, 19,
FONT_NEXT, 12, 0,
FONT_NEXT, 550, 0,
FONT_NEXT, 598, 174,
FONT_NEXT, 573, 174,
FONT_NEXT, 551, 130,
FONT_NEXT, 527, 97,
FONT_NEXT, 501, 73,
FONT_NEXT, 472, 57,
FONT_NEXT, 399, 41,
FONT_NEXT, 302, 39,
FONT_NEXT, 251, 39,
FONT_NEXT, 220, 44,
FONT_NEXT, 205, 56,
FONT_NEXT, 201, 80,
FONT_NEXT, 201, 553,
FONT_NEXT, 205, 599,
FONT_NEXT, 222, 626,
FONT_NEXT, 250, 638,
FONT_NEXT, 294, 643,
FONT_END, 294, 662,
FONT_ADVANCE, 611, 0
    },
    {
77,
FONT_BEGIN, 664, 662,
FONT_NEXT, 443, 157,
FONT_NEXT, 212, 662,
FONT_NEXT, 14, 662,
FONT_NEXT, 14, 643,
FONT_NEXT, 60, 638,
FONT_NEXT, 89, 625,
FONT_NEXT, 104, 599,
FONT_NEXT, 109, 553,
FONT_NEXT, 109, 147,
FONT_NEXT, 107, 110,
FONT_NEXT, 104, 82,
FONT_NEXT, 89, 44,
FONT_NEXT, 59, 25,
FONT_NEXT, 12, 19,
FONT_NEXT, 12, 0,
FONT_NEXT, 247, 0,
FONT_NEXT, 247, 19,
FONT_NEXT, 201, 25,
FONT_NEXT, 172, 45,
FONT_NEXT, 157, 83,
FONT_NEXT, 153, 147,
FONT_NEXT, 153, 546,
FONT_NEXT, 155, 546,
FONT_NEXT, 404, 0,
FONT_NEXT, 418, 0,
FONT_NEXT, 672, 569,
FONT_NEXT, 674, 569,
FONT_NEXT, 674, 120,
FONT_NEXT, 670, 70,
FONT_NEXT, 657, 40,
FONT_NEXT, 630, 24,
FONT_NEXT, 583, 19,
FONT_NEXT, 583, 0,
FONT_NEXT, 863, 0,
FONT_NEXT, 863, 19,
FONT_NEXT, 822, 23,
FONT_NEXT, 795, 36,
FONT_NEXT, 780, 63,
FONT_NEXT, 776, 109,
FONT_NEXT, 776, 553,
FONT_NEXT, 780, 599,
FONT_NEXT, 795, 625,
FONT_NEXT, 822, 637,
FONT_NEXT, 863, 643,
FONT_END, 863, 662,
FONT_ADVANCE, 889, 0
    },
    {
78,
FONT_BEGIN, 472, 662,
FONT_NEXT, 472, 643,
FONT_NEXT, 515, 637,
FONT_NEXT, 545, 620,
FONT_NEXT, 562, 582,
FONT_NEXT, 566, 553,
FONT_NEXT, 568, 515,
FONT_NEXT, 568, 181,
FONT_NEXT, 566, 181,
FONT_NEXT, 183, 662,
FONT_NEXT, 12, 662,
FONT_NEXT, 12, 643,
FONT_NEXT, 60, 634,
FONT_NEXT, 82, 617,
FONT_NEXT, 109, 588,
FONT_NEXT, 109, 147,
FONT_NEXT, 107, 110,
FONT_NEXT, 104, 82,
FONT_NEXT, 89, 44,
FONT_NEXT, 59, 25,
FONT_NEXT, 12, 19,
FONT_NEXT, 12, 0,
FONT_NEXT, 247, 0,
FONT_NEXT, 247, 19,
FONT_NEXT, 201, 25,
FONT_NEXT, 172, 45,
FONT_NEXT, 157, 83,
FONT_NEXT, 153, 147,
FONT_NEXT, 153, 537,
FONT_NEXT, 155, 537,
FONT_NEXT, 595, -11,
FONT_NEXT, 612, -11,
FONT_NEXT, 612, 515,
FONT_NEXT, 613, 553,
FONT_NEXT, 617, 583,
FONT_NEXT, 635, 620,
FONT_NEXT, 665, 636,
FONT_NEXT, 707, 643,
FONT_END, 707, 662,
FONT_ADVANCE, 722, 0
    },
    {
79,
FONT_BEGIN, 427, -8,
FONT_NEXT, 488, 9,
FONT_NEXT, 544, 38,
FONT_NEXT, 592, 78,
FONT_NEXT, 632, 127,
FONT_NEXT, 662, 186,
FONT_NEXT, 681, 254,
FONT_NEXT, 688, 330,
FONT_NEXT, 680, 410,
FONT_NEXT, 659, 481,
FONT_NEXT, 627, 540,
FONT_NEXT, 586, 589,
FONT_NEXT, 536, 627,
FONT_NEXT, 481, 654,
FONT_NEXT, 422, 670,
FONT_NEXT, 361, 676,
FONT_NEXT, 299, 670,
FONT_NEXT, 240, 654,
FONT_NEXT, 185, 627,
FONT_NEXT, 136, 589,
FONT_NEXT, 94, 540,
FONT_NEXT, 62, 481,
FONT_NEXT, 41, 410,
FONT_NEXT, 34, 330,
FONT_NEXT, 40, 254,
FONT_NEXT, 59, 186,
FONT_NEXT, 89, 127,
FONT_NEXT, 129, 78,
FONT_NEXT, 177, 38,
FONT_NEXT, 233, 9,
FONT_NEXT, 294, -8,
FONT_END, 361, -14,
FONT_BEGIN, 319, 26,
FONT_NEXT, 280, 39,
FONT_NEXT, 244, 61,
FONT_NEXT, 212, 94,
FONT_NEXT, 186, 136,
FONT_NEXT, 165, 189,
FONT_NEXT, 152, 254,
FONT_NEXT, 148, 331,
FONT_NEXT, 152, 409,
FONT_NEXT, 166, 475,
FONT_NEXT, 188, 528,
FONT_NEXT, 216, 571,
FONT_NEXT, 248, 602,
FONT_NEXT, 284, 623,
FONT_NEXT, 322, 636,
FONT_NEXT, 361, 640,
FONT_NEXT, 399, 636,
FONT_NEXT, 437, 623,
FONT_NEXT, 473, 602,
FONT_NEXT, 505, 571,
FONT_NEXT, 533, 528,
FONT_NEXT, 555, 475,
FONT_NEXT, 569, 409,
FONT_NEXT, 574, 331,
FONT_NEXT, 569, 254,
FONT_NEXT, 556, 189,
FONT_NEXT, 535, 136,
FONT_NEXT, 509, 94,
FONT_NEXT, 477, 61,
FONT_NEXT, 441, 39,
FONT_NEXT, 402, 26,
FONT_END, 361, 22,
FONT_ADVANCE, 722, 0
    },
    {
80,
FONT_BEGIN, 59, 635,
FONT_NEXT, 85, 622,
FONT_NEXT, 97, 596,
FONT_NEXT, 100, 553,
FONT_NEXT, 100, 120,
FONT_NEXT, 98, 74,
FONT_NEXT, 89, 44,
FONT_NEXT, 64, 27,
FONT_NEXT, 16, 19,
FONT_NEXT, 16, 0,
FONT_NEXT, 296, 0,
FONT_NEXT, 296, 19,
FONT_NEXT, 248, 23,
FONT_NEXT, 220, 37,
FONT_NEXT, 205, 64,
FONT_NEXT, 202, 109,
FONT_NEXT, 202, 291,
FONT_NEXT, 269, 288,
FONT_NEXT, 340, 291,
FONT_NEXT, 398, 302,
FONT_NEXT, 444, 320,
FONT_NEXT, 481, 347,
FONT_NEXT, 497, 363,
FONT_NEXT, 517, 391,
FONT_NEXT, 534, 430,
FONT_NEXT, 542, 482,
FONT_NEXT, 536, 526,
FONT_NEXT, 520, 563,
FONT_NEXT, 495, 594,
FONT_NEXT, 462, 619,
FONT_NEXT, 379, 651,
FONT_NEXT, 280, 662,
FONT_NEXT, 16, 662,
FONT_END, 16, 643,
FONT_BEGIN, 204, 610,
FONT_NEXT, 211, 620,
FONT_NEXT, 243, 625,
FONT_NEXT, 291, 622,
FONT_NEXT, 354, 606,
FONT_NEXT, 384, 588,
FONT_NEXT, 409, 562,
FONT_NEXT, 426, 527,
FONT_NEXT, 433, 480,
FONT_NEXT, 427, 429,
FONT_NEXT, 411, 392,
FONT_NEXT, 388, 365,
FONT_NEXT, 361, 347,
FONT_NEXT, 304, 330,
FONT_NEXT, 262, 328,
FONT_NEXT, 202, 331,
FONT_END, 202, 591,
FONT_ADVANCE, 556, 0
    },
    {
81,
FONT_BEGIN, 638, -153,
FONT_NEXT, 586, -138,
FONT_NEXT, 505, -90,
FONT_NEXT, 426, -7,
FONT_NEXT, 468, 2,
FONT_NEXT, 512, 20,
FONT_NEXT, 556, 47,
FONT_NEXT, 597, 83,
FONT_NEXT, 633, 130,
FONT_NEXT, 662, 186,
FONT_NEXT, 681, 252,
FONT_NEXT, 688, 330,
FONT_NEXT, 680, 410,
FONT_NEXT, 659, 481,
FONT_NEXT, 627, 540,
FONT_NEXT, 586, 589,
FONT_NEXT, 536, 627,
FONT_NEXT, 481, 654,
FONT_NEXT, 422, 670,
FONT_NEXT, 361, 676,
FONT_NEXT, 299, 670,
FONT_NEXT, 240, 654,
FONT_NEXT, 185, 626,
FONT_NEXT, 136, 588,
FONT_NEXT, 94, 540,
FONT_NEXT, 62, 480,
FONT_NEXT, 41, 410,
FONT_NEXT, 34, 330,
FONT_NEXT, 40, 255,
FONT_NEXT, 57, 191,
FONT_NEXT, 82, 138,
FONT_NEXT, 114, 94,
FONT_NEXT, 151, 58,
FONT_NEXT, 189, 31,
FONT_NEXT, 265, -1,
FONT_NEXT, 312, -56,
FONT_NEXT, 379, -113,
FONT_NEXT, 462, -151,
FONT_NEXT, 555, -172,
FONT_NEXT, 654, -178,
FONT_NEXT, 701, -177,
FONT_END, 701, -159,
FONT_BEGIN, 399, 636,
FONT_NEXT, 437, 623,
FONT_NEXT, 473, 602,
FONT_NEXT, 505, 570,
FONT_NEXT, 533, 528,
FONT_NEXT, 555, 475,
FONT_NEXT, 569, 409,
FONT_NEXT, 574, 330,
FONT_NEXT, 569, 253,
FONT_NEXT, 556, 189,
FONT_NEXT, 535, 136,
FONT_NEXT, 509, 93,
FONT_NEXT, 477, 61,
FONT_NEXT, 441, 39,
FONT_NEXT, 402, 26,
FONT_NEXT, 361, 22,
FONT_NEXT, 319, 26,
FONT_NEXT, 280, 39,
FONT_NEXT, 244, 61,
FONT_NEXT, 212, 93,
FONT_NEXT, 186, 136,
FONT_NEXT, 165, 189,
FONT_NEXT, 152, 253,
FONT_NEXT, 148, 330,
FONT_NEXT, 152, 409,
FONT_NEXT, 166, 475,
FONT_NEXT, 188, 528,
FONT_NEXT, 216, 570,
FONT_NEXT, 248, 602,
FONT_NEXT, 284, 623,
FONT_NEXT, 322, 636,
FONT_END, 361, 640,
FONT_ADVANCE, 722, 0
    },
    {
82,
FONT_BEGIN, 608, 33,
FONT_NEXT, 572, 66,
FONT_NEXT, 366, 319,
FONT_NEXT, 415, 330,
FONT_NEXT, 475, 356,
FONT_NEXT, 503, 377,
FONT_NEXT, 525, 405,
FONT_NEXT, 541, 441,
FONT_NEXT, 547, 487,
FONT_NEXT, 539, 538,
FONT_NEXT, 519, 579,
FONT_NEXT, 490, 610,
FONT_NEXT, 453, 632,
FONT_NEXT, 370, 656,
FONT_NEXT, 293, 662,
FONT_NEXT, 17, 662,
FONT_NEXT, 17, 643,
FONT_NEXT, 59, 636,
FONT_NEXT, 85, 623,
FONT_NEXT, 98, 597,
FONT_NEXT, 102, 553,
FONT_NEXT, 102, 120,
FONT_NEXT, 100, 73,
FONT_NEXT, 89, 43,
FONT_NEXT, 64, 26,
FONT_NEXT, 17, 19,
FONT_NEXT, 17, 0,
FONT_NEXT, 294, 0,
FONT_NEXT, 294, 19,
FONT_NEXT, 246, 25,
FONT_NEXT, 219, 40,
FONT_NEXT, 206, 67,
FONT_NEXT, 204, 109,
FONT_NEXT, 204, 306,
FONT_NEXT, 260, 308,
FONT_NEXT, 498, 0,
FONT_NEXT, 659, 0,
FONT_END, 659, 19,
FONT_BEGIN, 206, 606,
FONT_NEXT, 215, 617,
FONT_NEXT, 233, 623,
FONT_NEXT, 266, 625,
FONT_NEXT, 310, 622,
FONT_NEXT, 367, 606,
FONT_NEXT, 394, 590,
FONT_NEXT, 416, 566,
FONT_NEXT, 432, 533,
FONT_NEXT, 438, 489,
FONT_NEXT, 431, 441,
FONT_NEXT, 413, 406,
FONT_NEXT, 386, 380,
FONT_NEXT, 352, 362,
FONT_NEXT, 276, 346,
FONT_NEXT, 204, 343,
FONT_END, 204, 589,
FONT_ADVANCE, 667, 0
    },
    {
83,
FONT_BEGIN, 426, 676,
FONT_NEXT, 418, 654,
FONT_NEXT, 408, 645,
FONT_NEXT, 391, 642,
FONT_NEXT, 367, 647,
FONT_NEXT, 334, 659,
FONT_NEXT, 252, 676,
FONT_NEXT, 185, 665,
FONT_NEXT, 127, 633,
FONT_NEXT, 86, 580,
FONT_NEXT, 75, 546,
FONT_NEXT, 71, 506,
FONT_NEXT, 82, 441,
FONT_NEXT, 114, 390,
FONT_NEXT, 163, 349,
FONT_NEXT, 227, 310,
FONT_NEXT, 276, 280,
FONT_NEXT, 314, 254,
FONT_NEXT, 343, 231,
FONT_NEXT, 363, 210,
FONT_NEXT, 385, 172,
FONT_NEXT, 390, 133,
FONT_NEXT, 382, 92,
FONT_NEXT, 360, 57,
FONT_NEXT, 322, 31,
FONT_NEXT, 270, 22,
FONT_NEXT, 223, 27,
FONT_NEXT, 183, 43,
FONT_NEXT, 124, 92,
FONT_NEXT, 86, 151,
FONT_NEXT, 65, 199,
FONT_NEXT, 42, 199,
FONT_NEXT, 72, -13,
FONT_NEXT, 94, -13,
FONT_NEXT, 101, 9,
FONT_NEXT, 125, 20,
FONT_NEXT, 151, 14,
FONT_NEXT, 185, 3,
FONT_NEXT, 227, -9,
FONT_NEXT, 280, -14,
FONT_NEXT, 330, -10,
FONT_NEXT, 373, 2,
FONT_NEXT, 409, 20,
FONT_NEXT, 439, 44,
FONT_NEXT, 478, 102,
FONT_NEXT, 491, 167,
FONT_NEXT, 476, 235,
FONT_NEXT, 438, 290,
FONT_NEXT, 385, 336,
FONT_NEXT, 324, 375,
FONT_NEXT, 209, 448,
FONT_NEXT, 171, 491,
FONT_NEXT, 157, 542,
FONT_NEXT, 165, 582,
FONT_NEXT, 188, 611,
FONT_NEXT, 221, 629,
FONT_NEXT, 258, 635,
FONT_NEXT, 295, 630,
FONT_NEXT, 328, 619,
FONT_NEXT, 384, 579,
FONT_NEXT, 422, 524,
FONT_NEXT, 444, 463,
FONT_NEXT, 469, 463,
FONT_END, 447, 676,
FONT_ADVANCE, 556, 0
    },
    {
84,
FONT_BEGIN, 401, 24,
FONT_NEXT, 372, 39,
FONT_NEXT, 359, 66,
FONT_NEXT, 356, 109,
FONT_NEXT, 356, 620,
FONT_NEXT, 410, 620,
FONT_NEXT, 478, 615,
FONT_NEXT, 522, 597,
FONT_NEXT, 550, 558,
FONT_NEXT, 569, 492,
FONT_NEXT, 593, 492,
FONT_NEXT, 587, 662,
FONT_NEXT, 23, 662,
FONT_NEXT, 17, 492,
FONT_NEXT, 41, 492,
FONT_NEXT, 59, 557,
FONT_NEXT, 87, 596,
FONT_NEXT, 131, 615,
FONT_NEXT, 200, 620,
FONT_NEXT, 254, 620,
FONT_NEXT, 254, 120,
FONT_NEXT, 252, 73,
FONT_NEXT, 241, 43,
FONT_NEXT, 213, 26,
FONT_NEXT, 160, 19,
FONT_NEXT, 160, 0,
FONT_NEXT, 452, 0,
FONT_END, 452, 19,
FONT_ADVANCE, 611, 0
    },
    {
85,
FONT_BEGIN, 473, 662,
FONT_NEXT, 473, 643,
FONT_NEXT, 517, 636,
FONT_NEXT, 546, 617,
FONT_NEXT, 562, 579,
FONT_NEXT, 567, 515,
FONT_NEXT, 567, 245,
FONT_NEXT, 565, 192,
FONT_NEXT, 560, 154,
FONT_NEXT, 544, 109,
FONT_NEXT, 517, 76,
FONT_NEXT, 480, 51,
FONT_NEXT, 434, 35,
FONT_NEXT, 380, 30,
FONT_NEXT, 322, 36,
FONT_NEXT, 279, 52,
FONT_NEXT, 248, 77,
FONT_NEXT, 227, 107,
FONT_NEXT, 208, 174,
FONT_NEXT, 206, 233,
FONT_NEXT, 206, 553,
FONT_NEXT, 209, 596,
FONT_NEXT, 221, 622,
FONT_NEXT, 249, 636,
FONT_NEXT, 297, 643,
FONT_NEXT, 297, 662,
FONT_NEXT, 14, 662,
FONT_NEXT, 14, 643,
FONT_NEXT, 60, 637,
FONT_NEXT, 87, 623,
FONT_NEXT, 100, 597,
FONT_NEXT, 104, 553,
FONT_NEXT, 104, 241,
FONT_NEXT, 104, 212,
FONT_NEXT, 107, 175,
FONT_NEXT, 117, 133,
FONT_NEXT, 135, 91,
FONT_NEXT, 164, 50,
FONT_NEXT, 208, 17,
FONT_NEXT, 270, -6,
FONT_NEXT, 352, -14,
FONT_NEXT, 437, -6,
FONT_NEXT, 501, 16,
FONT_NEXT, 547, 49,
FONT_NEXT, 578, 90,
FONT_NEXT, 597, 134,
FONT_NEXT, 606, 178,
FONT_NEXT, 611, 254,
FONT_NEXT, 611, 515,
FONT_NEXT, 614, 578,
FONT_NEXT, 629, 615,
FONT_NEXT, 657, 634,
FONT_NEXT, 705, 643,
FONT_END, 705, 662,
FONT_ADVANCE, 722, 0
    },
    {
86,
FONT_BEGIN, 492, 662,
FONT_NEXT, 492, 643,
FONT_NEXT, 542, 634,
FONT_NEXT, 558, 621,
FONT_NEXT, 565, 597,
FONT_NEXT, 556, 556,
FONT_NEXT, 549, 538,
FONT_NEXT, 546, 528,
FONT_NEXT, 399, 161,
FONT_NEXT, 248, 499,
FONT_NEXT, 218, 566,
FONT_NEXT, 210, 590,
FONT_NEXT, 207, 606,
FONT_NEXT, 210, 624,
FONT_NEXT, 223, 635,
FONT_NEXT, 246, 640,
FONT_NEXT, 282, 643,
FONT_NEXT, 282, 662,
FONT_NEXT, 16, 662,
FONT_NEXT, 16, 643,
FONT_NEXT, 46, 638,
FONT_NEXT, 71, 623,
FONT_NEXT, 94, 592,
FONT_NEXT, 122, 538,
FONT_NEXT, 368, -11,
FONT_NEXT, 383, -11,
FONT_NEXT, 605, 550,
FONT_NEXT, 625, 596,
FONT_NEXT, 644, 623,
FONT_NEXT, 666, 637,
FONT_NEXT, 697, 643,
FONT_END, 697, 662,
FONT_ADVANCE, 722, 0
    },
    {
87,
FONT_BEGIN, 734, 662,
FONT_NEXT, 734, 643,
FONT_NEXT, 781, 633,
FONT_NEXT, 797, 620,
FONT_NEXT, 803, 597,
FONT_NEXT, 787, 525,
FONT_NEXT, 662, 186,
FONT_NEXT, 530, 527,
FONT_NEXT, 510, 574,
FONT_NEXT, 503, 605,
FONT_NEXT, 510, 626,
FONT_NEXT, 529, 637,
FONT_NEXT, 580, 643,
FONT_NEXT, 580, 662,
FONT_NEXT, 313, 662,
FONT_NEXT, 313, 643,
FONT_NEXT, 346, 640,
FONT_NEXT, 371, 628,
FONT_NEXT, 391, 601,
FONT_NEXT, 414, 553,
FONT_NEXT, 447, 471,
FONT_NEXT, 340, 189,
FONT_NEXT, 196, 565,
FONT_NEXT, 185, 609,
FONT_NEXT, 188, 625,
FONT_NEXT, 200, 635,
FONT_NEXT, 220, 640,
FONT_NEXT, 250, 643,
FONT_NEXT, 250, 662,
FONT_NEXT, 5, 662,
FONT_NEXT, 5, 643,
FONT_NEXT, 40, 634,
FONT_NEXT, 65, 613,
FONT_NEXT, 86, 578,
FONT_NEXT, 108, 526,
FONT_NEXT, 125, 480,
FONT_NEXT, 147, 421,
FONT_NEXT, 199, 277,
FONT_NEXT, 254, 123,
FONT_NEXT, 279, 51,
FONT_NEXT, 301, -11,
FONT_NEXT, 316, -11,
FONT_NEXT, 470, 412,
FONT_NEXT, 630, -11,
FONT_NEXT, 645, -11,
FONT_NEXT, 745, 288,
FONT_NEXT, 853, 582,
FONT_NEXT, 864, 606,
FONT_NEXT, 879, 624,
FONT_NEXT, 900, 635,
FONT_NEXT, 932, 643,
FONT_END, 932, 662,
FONT_ADVANCE, 944, 0
    },
    {
88,
FONT_BEGIN, 458, 662,
FONT_NEXT, 458, 643,
FONT_NEXT, 505, 637,
FONT_NEXT, 521, 628,
FONT_NEXT, 528, 610,
FONT_NEXT, 516, 579,
FONT_NEXT, 488, 542,
FONT_NEXT, 375, 404,
FONT_NEXT, 333, 463,
FONT_NEXT, 291, 523,
FONT_NEXT, 265, 565,
FONT_NEXT, 251, 592,
FONT_NEXT, 248, 611,
FONT_NEXT, 252, 628,
FONT_NEXT, 266, 637,
FONT_NEXT, 290, 641,
FONT_NEXT, 324, 643,
FONT_NEXT, 324, 662,
FONT_NEXT, 22, 662,
FONT_NEXT, 22, 643,
FONT_NEXT, 60, 637,
FONT_NEXT, 96, 618,
FONT_NEXT, 116, 600,
FONT_NEXT, 139, 573,
FONT_NEXT, 168, 535,
FONT_NEXT, 203, 486,
FONT_NEXT, 312, 326,
FONT_NEXT, 155, 133,
FONT_NEXT, 128, 100,
FONT_NEXT, 106, 74,
FONT_NEXT, 73, 41,
FONT_NEXT, 44, 24,
FONT_NEXT, 10, 19,
FONT_NEXT, 10, 0,
FONT_NEXT, 243, 0,
FONT_NEXT, 243, 19,
FONT_NEXT, 190, 26,
FONT_NEXT, 173, 37,
FONT_NEXT, 167, 56,
FONT_NEXT, 172, 73,
FONT_NEXT, 185, 95,
FONT_NEXT, 219, 140,
FONT_NEXT, 338, 288,
FONT_NEXT, 433, 148,
FONT_NEXT, 464, 98,
FONT_NEXT, 478, 72,
FONT_NEXT, 484, 53,
FONT_NEXT, 479, 35,
FONT_NEXT, 464, 25,
FONT_NEXT, 440, 21,
FONT_NEXT, 407, 19,
FONT_NEXT, 407, 0,
FONT_NEXT, 704, 0,
FONT_NEXT, 704, 19,
FONT_NEXT, 670, 24,
FONT_NEXT, 643, 37,
FONT_NEXT, 619, 58,
FONT_NEXT, 593, 93,
FONT_NEXT, 401, 367,
FONT_NEXT, 547, 549,
FONT_NEXT, 592, 599,
FONT_NEXT, 627, 627,
FONT_NEXT, 660, 638,
FONT_NEXT, 696, 643,
FONT_END, 696, 662,
FONT_ADVANCE, 722, 0
    },
    {
89,
FONT_BEGIN, 484, 662,
FONT_NEXT, 484, 643,
FONT_NEXT, 530, 638,
FONT_NEXT, 546, 628,
FONT_NEXT, 553, 610,
FONT_NEXT, 539, 573,
FONT_NEXT, 396, 347,
FONT_NEXT, 248, 569,
FONT_NEXT, 231, 612,
FONT_NEXT, 235, 628,
FONT_NEXT, 249, 637,
FONT_NEXT, 271, 641,
FONT_NEXT, 302, 643,
FONT_NEXT, 302, 662,
FONT_NEXT, 22, 662,
FONT_NEXT, 22, 643,
FONT_NEXT, 51, 637,
FONT_NEXT, 81, 619,
FONT_NEXT, 99, 600,
FONT_NEXT, 121, 573,
FONT_NEXT, 149, 535,
FONT_NEXT, 184, 486,
FONT_NEXT, 315, 294,
FONT_NEXT, 315, 120,
FONT_NEXT, 313, 72,
FONT_NEXT, 301, 41,
FONT_NEXT, 271, 24,
FONT_NEXT, 246, 20,
FONT_NEXT, 214, 19,
FONT_NEXT, 214, 0,
FONT_NEXT, 520, 0,
FONT_NEXT, 520, 19,
FONT_NEXT, 465, 23,
FONT_NEXT, 434, 37,
FONT_NEXT, 420, 64,
FONT_NEXT, 417, 109,
FONT_NEXT, 417, 303,
FONT_NEXT, 565, 529,
FONT_NEXT, 589, 564,
FONT_NEXT, 610, 590,
FONT_NEXT, 646, 624,
FONT_NEXT, 675, 638,
FONT_NEXT, 703, 643,
FONT_END, 703, 662,
FONT_ADVANCE, 722, 0
    },
    {
90,
FONT_BEGIN, 556, 123,
FONT_NEXT, 528, 79,
FONT_NEXT, 481, 49,
FONT_NEXT, 446, 40,
FONT_NEXT, 402, 38,
FONT_NEXT, 145, 38,
FONT_NEXT, 577, 647,
FONT_NEXT, 577, 662,
FONT_NEXT, 51, 662,
FONT_NEXT, 31, 491,
FONT_NEXT, 57, 491,
FONT_NEXT, 68, 540,
FONT_NEXT, 92, 582,
FONT_NEXT, 112, 599,
FONT_NEXT, 140, 612,
FONT_NEXT, 177, 621,
FONT_NEXT, 225, 624,
FONT_NEXT, 446, 624,
FONT_NEXT, 9, 15,
FONT_NEXT, 9, 0,
FONT_NEXT, 573, 0,
FONT_NEXT, 597, 176,
FONT_END, 574, 176,
FONT_ADVANCE, 611, 0
    },
    {
91,
FONT_BEGIN, 299, 662,
FONT_NEXT, 88, 662,
FONT_NEXT, 88, -156,
FONT_NEXT, 299, -156,
FONT_NEXT, 299, -131,
FONT_NEXT, 213, -131,
FONT_NEXT, 184, -125,
FONT_NEXT, 170, -110,

FONT_NEXT, 164, -79,
FONT_NEXT, 164, 593,
FONT_NEXT, 169, 620,
FONT_NEXT, 182, 632,
FONT_NEXT, 209, 637,
FONT_END, 299, 637,
FONT_ADVANCE, 333, 0
    },
    {
92,
FONT_BEGIN, 219, -14,
FONT_NEXT, 287, -14,
FONT_NEXT, 58, 676,
FONT_END, -9, 676,
FONT_ADVANCE, 278, 0
    },
    {
93,
FONT_BEGIN, 34, -156,
FONT_NEXT, 245, -156,
FONT_NEXT, 245, 662,
FONT_NEXT, 34, 662,
FONT_NEXT, 34, 637,
FONT_NEXT, 120, 637,
FONT_NEXT, 148, 630,
FONT_NEXT, 162, 615,
FONT_NEXT, 169, 585,
FONT_NEXT, 169, -87,
FONT_NEXT, 163, -115,
FONT_NEXT, 150, -127,
FONT_NEXT, 124, -131,
FONT_END, 34, -131,
FONT_ADVANCE, 333, 0
    },
    {
94,
FONT_BEGIN, 235, 586,
FONT_NEXT, 378, 297,
FONT_NEXT, 446, 297,
FONT_NEXT, 265, 662,
FONT_NEXT, 205, 662,
FONT_NEXT, 24, 297,
FONT_END, 92, 297,
FONT_ADVANCE, 469, 0
    },
    {
95,
FONT_BEGIN, 500, -75,
FONT_NEXT, 0, -75,
FONT_NEXT, 0, -125,
FONT_END, 500, -125,
FONT_ADVANCE, 500, 0
    },
    {
96,
FONT_BEGIN, 196, 658,
FONT_NEXT, 159, 625,
FONT_NEXT, 128, 580,
FONT_NEXT, 115, 529,
FONT_NEXT, 124, 478,
FONT_NEXT, 146, 449,
FONT_NEXT, 173, 436,
FONT_NEXT, 196, 433,
FONT_NEXT, 234, 445,
FONT_NEXT, 248, 463,
FONT_NEXT, 254, 491,
FONT_NEXT, 247, 515,
FONT_NEXT, 232, 531,
FONT_NEXT, 192, 541,
FONT_NEXT, 177, 539,
FONT_NEXT, 168, 537,
FONT_NEXT, 158, 540,
FONT_NEXT, 154, 551,
FONT_NEXT, 157, 569,
FONT_NEXT, 170, 595,
FONT_NEXT, 195, 625,
FONT_NEXT, 236, 657,
FONT_END, 227, 676,
FONT_ADVANCE, 333, 0
    },
    {
97,
FONT_BEGIN, 281, 88,
FONT_NEXT, 271, 77,
FONT_NEXT, 253, 66,
FONT_NEXT, 226, 55,
FONT_NEXT, 191, 48,
FONT_NEXT, 165, 53,
FONT_NEXT, 144, 69,
FONT_NEXT, 130, 93,
FONT_NEXT, 125, 126,
FONT_NEXT, 125, 128,
FONT_NEXT, 129, 157,
FONT_NEXT, 149, 192,
FONT_NEXT, 169, 210,
FONT_NEXT, 197, 229,
FONT_NEXT, 236, 249,
FONT_NEXT, 287, 268,
FONT_END, 287, 123,
FONT_BEGIN, 423, 53,
FONT_NEXT, 397, 47,
FONT_NEXT, 380, 51,
FONT_NEXT, 371, 65,
FONT_NEXT, 368, 105,
FONT_NEXT, 368, 300,
FONT_NEXT, 365, 350,
FONT_NEXT, 349, 402,
FONT_NEXT, 331, 425,
FONT_NEXT, 305, 443,
FONT_NEXT, 268, 455,
FONT_NEXT, 219, 460,
FONT_NEXT, 178, 456,
FONT_NEXT, 144, 448,
FONT_NEXT, 94, 419,
FONT_NEXT, 65, 383,
FONT_NEXT, 56, 349,
FONT_NEXT, 65, 321,
FONT_NEXT, 79, 309,
FONT_NEXT, 99, 305,
FONT_NEXT, 131, 318,
FONT_NEXT, 144, 348,
FONT_NEXT, 139, 387,
FONT_NEXT, 145, 407,
FONT_NEXT, 162, 423,
FONT_NEXT, 210, 436,
FONT_NEXT, 234, 433,
FONT_NEXT, 259, 421,
FONT_NEXT, 279, 396,
FONT_NEXT, 287, 353,
FONT_NEXT, 287, 292,
FONT_NEXT, 179, 248,
FONT_NEXT, 100, 206,
FONT_NEXT, 73, 183,
FONT_NEXT, 53, 158,
FONT_NEXT, 41, 128,
FONT_NEXT, 37, 94,
FONT_NEXT, 45, 49,
FONT_NEXT, 69, 16,
FONT_NEXT, 103, -4,
FONT_NEXT, 143, -10,
FONT_NEXT, 183, -4,
FONT_NEXT, 222, 14,
FONT_NEXT, 288, 63,
FONT_NEXT, 297, 21,
FONT_NEXT, 315, -1,
FONT_NEXT, 353, -10,
FONT_NEXT, 387, -4,
FONT_NEXT, 412, 11,
FONT_NEXT, 442, 40,
FONT_END, 442, 66,
FONT_ADVANCE, 444, 0
    },
    {
98,
FONT_BEGIN, 165, 357,
FONT_NEXT, 192, 380,
FONT_NEXT, 224, 393,
FONT_NEXT, 253, 397,
FONT_NEXT, 284, 392,
FONT_NEXT, 311, 379,
FONT_NEXT, 350, 333,
FONT_NEXT, 372, 270,
FONT_NEXT, 380, 201,
FONT_NEXT, 375, 145,
FONT_NEXT, 357, 86,
FONT_NEXT, 340, 61,
FONT_NEXT, 317, 40,
FONT_NEXT, 287, 26,
FONT_NEXT, 249, 22,
FONT_NEXT, 223, 24,
FONT_NEXT, 191, 32,
FONT_NEXT, 164, 47,
FONT_NEXT, 153, 70,
FONT_END, 153, 322,
FONT_BEGIN, 73, 40,
FONT_NEXT, 86, 28,
FONT_NEXT, 127, 8,
FONT_NEXT, 178, -6,
FONT_NEXT, 224, -10,
FONT_NEXT, 287, -4,
FONT_NEXT, 340, 16,
FONT_NEXT, 382, 44,
FONT_NEXT, 415, 80,
FONT_NEXT, 439, 119,
FONT_NEXT, 455, 161,
FONT_NEXT, 468, 238,
FONT_NEXT, 457, 317,
FONT_NEXT, 424, 389,
FONT_NEXT, 399, 417,
FONT_NEXT, 368, 440,
FONT_NEXT, 332, 454,
FONT_NEXT, 290, 460,
FONT_NEXT, 237, 450,
FONT_NEXT, 196, 428,
FONT_NEXT, 167, 401,
FONT_NEXT, 155, 379,
FONT_NEXT, 153, 379,
FONT_NEXT, 153, 681,
FONT_NEXT, 148, 683,
FONT_NEXT, 3, 639,
FONT_NEXT, 3, 623,
FONT_NEXT, 25, 624,
FONT_NEXT, 50, 620,
FONT_NEXT, 63, 609,
FONT_NEXT, 69, 573,
FONT_END, 69, 54,
FONT_ADVANCE, 500, 0
    },
    {
99,
FONT_BEGIN, 340, 89,
FONT_NEXT, 302, 69,
FONT_NEXT, 255, 62,
FONT_NEXT, 195, 75,
FONT_NEXT, 147, 114,
FONT_NEXT, 114, 174,
FONT_NEXT, 102, 253,
FONT_NEXT, 106, 302,
FONT_NEXT, 116, 342,
FONT_NEXT, 133, 373,
FONT_NEXT, 152, 397,
FONT_NEXT, 195, 423,
FONT_NEXT, 231, 431,
FONT_NEXT, 262, 427,
FONT_NEXT, 280, 418,
FONT_NEXT, 297, 383,
FONT_NEXT, 303, 361,
FONT_NEXT, 320, 329,
FONT_NEXT, 334, 318,
FONT_NEXT, 352, 315,
FONT_NEXT, 386, 329,
FONT_NEXT, 398, 359,
FONT_NEXT, 387, 392,
FONT_NEXT, 355, 429,
FONT_NEXT, 306, 450,
FONT_NEXT, 249, 460,
FONT_NEXT, 168, 444,
FONT_NEXT, 130, 425,
FONT_NEXT, 96, 398,
FONT_NEXT, 67, 363,
FONT_NEXT, 44, 320,
FONT_NEXT, 30, 270,
FONT_NEXT, 25, 213,
FONT_NEXT, 29, 158,
FONT_NEXT, 42, 111,
FONT_NEXT, 63, 73,
FONT_NEXT, 88, 42,
FONT_NEXT, 149, 2,
FONT_NEXT, 212, -10,
FONT_NEXT, 269, -2,
FONT_NEXT, 322, 25,
FONT_NEXT, 369, 74,
FONT_NEXT, 412, 147,
FONT_END, 398, 156,
FONT_ADVANCE, 444, 0
    },
    {
100,
FONT_BEGIN, 328, 77,
FONT_NEXT, 308, 58,
FONT_NEXT, 281, 46,
FONT_NEXT, 251, 42,
FONT_NEXT, 201, 53,
FONT_NEXT, 157, 88,
FONT_NEXT, 139, 116,
FONT_NEXT, 125, 152,
FONT_NEXT, 116, 195,
FONT_NEXT, 113, 247,
FONT_NEXT, 116, 293,
FONT_NEXT, 124, 333,
FONT_NEXT, 154, 390,
FONT_NEXT, 194, 422,
FONT_NEXT, 237, 432,
FONT_NEXT, 271, 425,
FONT_NEXT, 303, 407,
FONT_NEXT, 327, 376,
FONT_NEXT, 340, 332,
FONT_END, 340, 102,
FONT_BEGIN, 452, 58,
FONT_NEXT, 432, 68,
FONT_NEXT, 425, 87,
FONT_NEXT, 424, 114,
FONT_NEXT, 424, 681,
FONT_NEXT, 419, 683,
FONT_NEXT, 272, 639,
FONT_NEXT, 272, 623,
FONT_NEXT, 296, 624,
FONT_NEXT, 321, 620,
FONT_NEXT, 334, 609,
FONT_NEXT, 340, 573,
FONT_NEXT, 340, 417,
FONT_NEXT, 290, 449,
FONT_NEXT, 234, 460,
FONT_NEXT, 192, 454,
FONT_NEXT, 154, 439,
FONT_NEXT, 119, 416,
FONT_NEXT, 88, 386,
FONT_NEXT, 43, 307,
FONT_NEXT, 31, 262,
FONT_NEXT, 27, 214,
FONT_NEXT, 33, 145,
FONT_NEXT, 49, 92,
FONT_NEXT, 74, 53,
FONT_NEXT, 103, 25,
FONT_NEXT, 164, -4,
FONT_NEXT, 209, -10,
FONT_NEXT, 257, -4,
FONT_NEXT, 294, 11,
FONT_NEXT, 320, 32,
FONT_NEXT, 338, 54,
FONT_NEXT, 340, 54,
FONT_NEXT, 340, -7,
FONT_NEXT, 344, -10,
FONT_NEXT, 491, 42,
FONT_END, 491, 58,
FONT_ADVANCE, 500, 0
    },
    {
101,
FONT_BEGIN, 386, 131,
FONT_NEXT, 355, 97,
FONT_NEXT, 311, 70,
FONT_NEXT, 253, 59,
FONT_NEXT, 202, 68,
FONT_NEXT, 153, 101,
FONT_NEXT, 132, 130,
FONT_NEXT, 114, 168,
FONT_NEXT, 101, 216,
FONT_NEXT, 94, 277,
FONT_NEXT, 405, 277,
FONT_NEXT, 398, 322,
FONT_NEXT, 386, 360,
FONT_NEXT, 348, 417,
FONT_NEXT, 294, 449,
FONT_NEXT, 229, 460,
FONT_NEXT, 163, 447,
FONT_NEXT, 129, 430,
FONT_NEXT, 97, 406,
FONT_NEXT, 68, 373,
FONT_NEXT, 45, 330,
FONT_NEXT, 30, 277,
FONT_NEXT, 25, 214,
FONT_NEXT, 28, 163,
FONT_NEXT, 38, 119,
FONT_NEXT, 54, 80,
FONT_NEXT, 76, 48,
FONT_NEXT, 104, 23,
FONT_NEXT, 135, 5,
FONT_NEXT, 172, -7,
FONT_NEXT, 212, -10,
FONT_NEXT, 262, -5,
FONT_NEXT, 304, 11,
FONT_NEXT, 368, 61,
FONT_NEXT, 406, 117,
FONT_NEXT, 417, 140,
FONT_NEXT, 424, 157,
FONT_END, 408, 164,
FONT_BEGIN, 113, 364,
FONT_NEXT, 137, 399,
FONT_NEXT, 168, 418,
FONT_NEXT, 207, 424,
FONT_NEXT, 254, 412,
FONT_NEXT, 282, 384,
FONT_NEXT, 303, 309,
FONT_END, 97, 309,
FONT_ADVANCE, 444, 0
    },
    {
102,
FONT_BEGIN, 186, 450,
FONT_NEXT, 186, 566,
FONT_NEXT, 195, 624,
FONT_NEXT, 213, 646,
FONT_NEXT, 246, 655,
FONT_NEXT, 340, 580,
FONT_NEXT, 369, 590,
FONT_NEXT, 379, 603,
FONT_NEXT, 383, 622,
FONT_NEXT, 375, 645,
FONT_NEXT, 354, 665,
FONT_NEXT, 322, 678,
FONT_NEXT, 283, 683,
FONT_NEXT, 223, 675,
FONT_NEXT, 178, 654,
FONT_NEXT, 146, 624,
FONT_NEXT, 125, 587,
FONT_NEXT, 105, 510,
FONT_NEXT, 103, 476,
FONT_NEXT, 103, 450,
FONT_NEXT, 21, 450,
FONT_NEXT, 21, 418,
FONT_NEXT, 103, 418,
FONT_NEXT, 103, 104,
FONT_NEXT, 99, 58,
FONT_NEXT, 86, 31,
FONT_NEXT, 60, 19,
FONT_NEXT, 20, 15,
FONT_NEXT, 20, 0,
FONT_NEXT, 280, 0,
FONT_NEXT, 280, 15,
FONT_NEXT, 226, 21,
FONT_NEXT, 199, 37,
FONT_NEXT, 188, 64,
FONT_NEXT, 187, 104,
FONT_NEXT, 187, 418,
FONT_NEXT, 309, 418,
FONT_END, 309, 450,
FONT_ADVANCE, 333, 0
    },
    {
103,
FONT_BEGIN, 110, -49,
FONT_NEXT, 147, -2,
FONT_NEXT, 210, -12,
FONT_NEXT, 251, -14,
FONT_NEXT, 310, -15,
FONT_NEXT, 360, -18,
FONT_NEXT, 399, -24,
FONT_NEXT, 424, -38,
FONT_NEXT, 433, -64,
FONT_NEXT, 420, -102,
FONT_NEXT, 384, -133,
FONT_NEXT, 326, -154,
FONT_NEXT, 248, -161,
FONT_NEXT, 176, -155,
FONT_NEXT, 130, -138,
FONT_NEXT, 105, -115,
FONT_END, 98, -90,
FONT_BEGIN, 158, 384,
FONT_NEXT, 176, 412,
FONT_NEXT, 200, 427,
FONT_NEXT, 227, 432,
FONT_NEXT, 256, 426,
FONT_NEXT, 279, 410,
FONT_NEXT, 310, 361,
FONT_NEXT, 325, 307,
FONT_NEXT, 329, 270,
FONT_NEXT, 327, 245,
FONT_NEXT, 318, 213,
FONT_NEXT, 296, 185,
FONT_NEXT, 256, 174,
FONT_NEXT, 229, 177,
FONT_NEXT, 206, 189,
FONT_NEXT, 174, 228,
FONT_NEXT, 157, 282,
FONT_END, 152, 342,
FONT_BEGIN, 470, 427,
FONT_NEXT, 393, 427,
FONT_NEXT, 338, 437,
FONT_NEXT, 287, 454,
FONT_NEXT, 241, 460,
FONT_NEXT, 182, 450,
FONT_NEXT, 126, 421,
FONT_NEXT, 85, 369,
FONT_NEXT, 73, 334,
FONT_NEXT, 69, 293,
FONT_NEXT, 75, 251,
FONT_NEXT, 93, 216,
FONT_NEXT, 122, 186,
FONT_NEXT, 162, 163,
FONT_NEXT, 132, 136,
FONT_NEXT, 110, 115,
FONT_NEXT, 84, 85,
FONT_NEXT, 74, 67,
FONT_NEXT, 73, 54,
FONT_NEXT, 77, 35,
FONT_NEXT, 89, 21,
FONT_NEXT, 126, 1,
FONT_NEXT, 57, -57,
FONT_NEXT, 36, -89,
FONT_NEXT, 28, -121,
FONT_NEXT, 41, -159,
FONT_NEXT, 78, -190,
FONT_NEXT, 134, -211,
FONT_NEXT, 205, -218,
FONT_NEXT, 282, -208,
FONT_NEXT, 366, -176,
FONT_NEXT, 403, -153,
FONT_NEXT, 433, -124,
FONT_NEXT, 453, -89,
FONT_NEXT, 461, -49,
FONT_NEXT, 457, -22,
FONT_NEXT, 440, 13,
FONT_NEXT, 404, 43,
FONT_NEXT, 375, 53,
FONT_NEXT, 340, 58,
FONT_NEXT, 211, 64,
FONT_NEXT, 160, 69,
FONT_NEXT, 140, 77,
FONT_NEXT, 133, 91,
FONT_NEXT, 139, 109,
FONT_NEXT, 155, 129,
FONT_NEXT, 175, 146,
FONT_NEXT, 193, 154,
FONT_NEXT, 247, 149,
FONT_NEXT, 295, 156,
FONT_NEXT, 347, 182,
FONT_NEXT, 389, 227,
FONT_NEXT, 401, 258,
FONT_NEXT, 406, 296,
FONT_NEXT, 404, 328,
FONT_NEXT, 400, 352,
FONT_NEXT, 387, 388,
FONT_END, 470, 388,
FONT_ADVANCE, 500, 0
    },
    {
104,
FONT_BEGIN, 456, 22,
FONT_NEXT, 438, 34,
FONT_NEXT, 429, 58,
FONT_NEXT, 427, 102,
FONT_NEXT, 427, 301,
FONT_NEXT, 424, 342,
FONT_NEXT, 411, 395,
FONT_NEXT, 396, 420,
FONT_NEXT, 374, 440,
FONT_NEXT, 343, 454,
FONT_NEXT, 303, 460,
FONT_NEXT, 260, 454,
FONT_NEXT, 221, 437,
FONT_NEXT, 187, 411,
FONT_NEXT, 159, 378,
FONT_NEXT, 157, 378,
FONT_NEXT, 157, 680,
FONT_NEXT, 152, 683,
FONT_NEXT, 10, 639,
FONT_NEXT, 10, 623,
FONT_NEXT, 29, 624,
FONT_NEXT, 54, 620,
FONT_NEXT, 67, 609,
FONT_NEXT, 73, 573,
FONT_NEXT, 73, 102,
FONT_NEXT, 70, 58,
FONT_NEXT, 61, 33,
FONT_NEXT, 41, 21,
FONT_NEXT, 9, 15,
FONT_NEXT, 9, 0,
FONT_NEXT, 225, 0,
FONT_NEXT, 225, 15,
FONT_NEXT, 193, 19,
FONT_NEXT, 172, 31,
FONT_NEXT, 160, 56,
FONT_NEXT, 157, 102,
FONT_NEXT, 157, 343,
FONT_NEXT, 187, 373,
FONT_NEXT, 216, 392,
FONT_NEXT, 266, 406,
FONT_NEXT, 301, 398,
FONT_NEXT, 325, 378,
FONT_NEXT, 338, 344,
FONT_NEXT, 343, 300,
FONT_NEXT, 343, 102,
FONT_NEXT, 339, 56,
FONT_NEXT, 327, 31,
FONT_NEXT, 306, 19,
FONT_NEXT, 275, 15,
FONT_NEXT, 275, 0,
FONT_NEXT, 487, 0,
FONT_END, 487, 15,
FONT_ADVANCE, 500, 0
    },
    {
105,
FONT_BEGIN, 253, 0,
FONT_NEXT, 253, 15,
FONT_NEXT, 212, 21,
FONT_NEXT, 190, 35,
FONT_NEXT, 180, 61,
FONT_NEXT, 179, 102,
FONT_NEXT, 179, 457,
FONT_NEXT, 175, 460,
FONT_NEXT, 20, 405,
FONT_NEXT, 20, 390,
FONT_NEXT, 60, 394,
FONT_NEXT, 83, 386,
FONT_NEXT, 91, 368,
FONT_NEXT, 95, 334,
FONT_NEXT, 95, 102,
FONT_NEXT, 91, 55,
FONT_NEXT, 78, 30,
FONT_NEXT, 54, 18,
FONT_NEXT, 16, 15,
FONT_END, 16, 0,
FONT_BEGIN, 89, 599,
FONT_NEXT, 105, 586,
FONT_NEXT, 128, 581,
FONT_NEXT, 148, 584,
FONT_NEXT, 165, 595,
FONT_NEXT, 176, 611,
FONT_NEXT, 180, 632,
FONT_NEXT, 165, 668,
FONT_NEXT, 148, 678,
FONT_NEXT, 128, 683,
FONT_NEXT, 105, 677,
FONT_NEXT, 89, 664,
FONT_END, 78, 632,
FONT_ADVANCE, 278, 0
    },
    {
106,
FONT_BEGIN, 108, -102,
FONT_NEXT, 102, -146,
FONT_NEXT, 87, -174,
FONT_NEXT, 59, -184,
FONT_NEXT, -30, -124,
FONT_NEXT, -58, -135,
FONT_NEXT, -70, -162,
FONT_NEXT, -63, -187,
FONT_NEXT, -42, -204,
FONT_NEXT, -12, -215,
FONT_NEXT, 23, -218,
FONT_NEXT, 67, -214,
FONT_NEXT, 103, -201,
FONT_NEXT, 133, -180,
FONT_NEXT, 156, -153,
FONT_NEXT, 184, -84,
FONT_NEXT, 193, 0,
FONT_NEXT, 193, 457,
FONT_NEXT, 188, 460,
FONT_NEXT, 32, 406,
FONT_NEXT, 32, 390,
FONT_NEXT, 74, 394,
FONT_NEXT, 97, 386,
FONT_NEXT, 105, 368,
FONT_NEXT, 109, 334,
FONT_END, 109, -45,
FONT_BEGIN, 103, 599,
FONT_NEXT, 119, 586,
FONT_NEXT, 142, 581,
FONT_NEXT, 162, 584,
FONT_NEXT, 179, 595,
FONT_NEXT, 190, 611,
FONT_NEXT, 194, 632,
FONT_NEXT, 179, 668,
FONT_NEXT, 162, 678,
FONT_NEXT, 142, 683,
FONT_NEXT, 119, 677,
FONT_NEXT, 103, 664,
FONT_END, 92, 632,
FONT_ADVANCE, 278, 0
    },
    {
107,
FONT_BEGIN, 241, 0,
FONT_NEXT, 241, 15,
FONT_NEXT, 221, 16,
FONT_NEXT, 181, 26,
FONT_NEXT, 169, 42,
FONT_NEXT, 166, 67,
FONT_NEXT, 166, 248,
FONT_NEXT, 168, 248,
FONT_NEXT, 306, 64,
FONT_NEXT, 321, 43,
FONT_NEXT, 327, 31,
FONT_NEXT, 319, 18,
FONT_NEXT, 306, 15,
FONT_NEXT, 287, 15,
FONT_NEXT, 287, 0,
FONT_NEXT, 505, 0,
FONT_NEXT, 505, 15,
FONT_NEXT, 482, 18,
FONT_NEXT, 455, 28,
FONT_NEXT, 424, 49,
FONT_NEXT, 388, 88,
FONT_NEXT, 235, 282,
FONT_NEXT, 264, 309,
FONT_NEXT, 309, 350,
FONT_NEXT, 347, 381,
FONT_NEXT, 377, 404,
FONT_NEXT, 402, 419,
FONT_NEXT, 442, 433,
FONT_NEXT, 480, 435,
FONT_NEXT, 480, 450,
FONT_NEXT, 276, 450,
FONT_NEXT, 276, 436,
FONT_NEXT, 301, 434,
FONT_NEXT, 316, 430,
FONT_NEXT, 326, 417,
FONT_NEXT, 320, 401,
FONT_NEXT, 303, 383,
FONT_NEXT, 168, 263,
FONT_NEXT, 166, 265,
FONT_NEXT, 166, 681,
FONT_NEXT, 162, 683,
FONT_NEXT, 7, 639,
FONT_NEXT, 7, 623,
FONT_NEXT, 37, 625,
FONT_NEXT, 60, 621,
FONT_NEXT, 74, 611,
FONT_NEXT, 80, 592,
FONT_NEXT, 82, 564,
FONT_NEXT, 82, 82,
FONT_NEXT, 79, 50,
FONT_NEXT, 69, 32,
FONT_NEXT, 46, 22,
FONT_NEXT, 7, 15,
FONT_END, 7, 0,
FONT_ADVANCE, 500, 0
    },
    {
108,
FONT_BEGIN, 257, 0,
FONT_NEXT, 257, 15,
FONT_NEXT, 220, 18,
FONT_NEXT, 197, 28,
FONT_NEXT, 185, 49,
FONT_NEXT, 182, 84,
FONT_NEXT, 182, 681,
FONT_NEXT, 178, 683,
FONT_NEXT, 19, 639,
FONT_NEXT, 19, 623,
FONT_NEXT, 53, 625,
FONT_NEXT, 76, 621,
FONT_NEXT, 90, 611,
FONT_NEXT, 96, 592,
FONT_NEXT, 98, 564,
FONT_NEXT, 98, 87,
FONT_NEXT, 94, 53,
FONT_NEXT, 81, 31,
FONT_NEXT, 57, 20,
FONT_NEXT, 21, 15,
FONT_END, 21, 0,
FONT_ADVANCE, 278, 0
    },
    {
109,
FONT_BEGIN, 80, 45,
FONT_NEXT, 66, 25,
FONT_NEXT, 43, 16,
FONT_NEXT, 16, 15,
FONT_NEXT, 16, 0,
FONT_NEXT, 238, 0,
FONT_NEXT, 238, 15,
FONT_NEXT, 190, 23,
FONT_NEXT, 175, 38,
FONT_NEXT, 170, 67,
FONT_NEXT, 170, 349,
FONT_NEXT, 178, 360,
FONT_NEXT, 201, 380,
FONT_NEXT, 237, 399,
FONT_NEXT, 285, 408,
FONT_NEXT, 317, 400,
FONT_NEXT, 339, 380,
FONT_NEXT, 350, 347,
FONT_NEXT, 354, 303,
FONT_NEXT, 354, 86,
FONT_NEXT, 349, 47,
FONT_NEXT, 336, 27,
FONT_NEXT, 315, 18,
FONT_NEXT, 286, 15,
FONT_NEXT, 286, 0,
FONT_NEXT, 510, 0,
FONT_NEXT, 510, 15,
FONT_NEXT, 481, 17,
FONT_NEXT, 458, 26,
FONT_NEXT, 443, 50,
FONT_NEXT, 438, 95,
FONT_NEXT, 438, 347,
FONT_NEXT, 481, 390,
FONT_NEXT, 511, 403,
FONT_NEXT, 549, 408,
FONT_NEXT, 590, 397,
FONT_NEXT, 612, 372,
FONT_NEXT, 622, 298,
FONT_NEXT, 622, 87,
FONT_NEXT, 618, 49,
FONT_NEXT, 607, 29,
FONT_NEXT, 587, 19,
FONT_NEXT, 556, 15,
FONT_NEXT, 556, 0,
FONT_NEXT, 775, 0,
FONT_NEXT, 775, 15,
FONT_NEXT, 749, 17,
FONT_NEXT, 723, 26,
FONT_NEXT, 711, 40,
FONT_NEXT, 706, 76,
FONT_NEXT, 706, 282,
FONT_NEXT, 702, 341,
FONT_NEXT, 686, 398,
FONT_NEXT, 650, 442,
FONT_NEXT, 622, 455,
FONT_NEXT, 588, 460,
FONT_NEXT, 532, 450,
FONT_NEXT, 485, 427,
FONT_NEXT, 449, 399,
FONT_NEXT, 427, 376,
FONT_NEXT, 414, 406,
FONT_NEXT, 393, 433,
FONT_NEXT, 362, 452,
FONT_NEXT, 320, 460,
FONT_NEXT, 281, 454,
FONT_NEXT, 244, 438,
FONT_NEXT, 166, 383,
FONT_NEXT, 166, 458,
FONT_NEXT, 159, 460,
FONT_NEXT, 19, 415,
FONT_NEXT, 19, 398,
FONT_NEXT, 51, 402,
FONT_NEXT, 74, 393,
FONT_NEXT, 82, 374,
FONT_NEXT, 86, 338,
FONT_END, 86, 85,
FONT_ADVANCE, 778, 0
    },
    {
110,
FONT_BEGIN, 76, 53,
FONT_NEXT, 66, 31,
FONT_NEXT, 47, 19,
FONT_NEXT, 18, 15,
FONT_NEXT, 18, 0,
FONT_NEXT, 230, 0,
FONT_NEXT, 230, 15,
FONT_NEXT, 197, 19,
FONT_NEXT, 177, 29,
FONT_NEXT, 167, 45,
FONT_NEXT, 164, 67,
FONT_NEXT, 164, 348,
FONT_NEXT, 216, 390,
FONT_NEXT, 263, 405,
FONT_NEXT, 299, 398,
FONT_NEXT, 323, 379,
FONT_NEXT, 336, 348,
FONT_NEXT, 340, 308,
FONT_NEXT, 340, 99,
FONT_NEXT, 335, 54,
FONT_NEXT, 322, 29,
FONT_NEXT, 302, 18,
FONT_NEXT, 277, 15,
FONT_NEXT, 277, 0,
FONT_NEXT, 485, 0,
FONT_NEXT, 485, 15,
FONT_NEXT, 454, 20,
FONT_NEXT, 436, 31,
FONT_NEXT, 426, 51,
FONT_NEXT, 424, 81,
FONT_NEXT, 424, 310,
FONT_NEXT, 420, 351,
FONT_NEXT, 412, 385,
FONT_NEXT, 382, 431,
FONT_NEXT, 344, 453,
FONT_NEXT, 307, 460,
FONT_NEXT, 266, 453,
FONT_NEXT, 230, 436,
FONT_NEXT, 161, 379,
FONT_NEXT, 161, 458,
FONT_NEXT, 154, 460,
FONT_NEXT, 16, 415,
FONT_NEXT, 16, 398,
FONT_NEXT, 45, 402,
FONT_NEXT, 68, 393,
FONT_NEXT, 76, 374,
FONT_NEXT, 80, 338,
FONT_END, 80, 90,
FONT_ADVANCE, 500, 0
    },
    {
111,
FONT_BEGIN, 197, 455,
FONT_NEXT, 154, 441,
FONT_NEXT, 117, 419,
FONT_NEXT, 86, 390,
FONT_NEXT, 62, 355,
FONT_NEXT, 43, 316,
FONT_NEXT, 29, 228,
FONT_NEXT, 33, 179,
FONT_NEXT, 45, 133,
FONT_NEXT, 64, 93,
FONT_NEXT, 90, 58,
FONT_NEXT, 122, 29,
FONT_NEXT, 159, 8,
FONT_NEXT, 201, -6,
FONT_NEXT, 248, -10,
FONT_NEXT, 301, -4,
FONT_NEXT, 346, 13,
FONT_NEXT, 385, 39,
FONT_NEXT, 416, 71,
FONT_NEXT, 456, 149,
FONT_NEXT, 470, 228,
FONT_NEXT, 465, 279,
FONT_NEXT, 453, 325,
FONT_NEXT, 433, 365,
FONT_NEXT, 406, 398,
FONT_NEXT, 373, 424,
FONT_NEXT, 335, 444,
FONT_NEXT, 292, 455,
FONT_END, 245, 460,
FONT_BEGIN, 221, 26,
FONT_NEXT, 189, 47,
FONT_NEXT, 164, 80,
FONT_NEXT, 145, 119,
FONT_NEXT, 124, 204,
FONT_NEXT, 119, 276,
FONT_NEXT, 128, 344,
FONT_NEXT, 154, 393,
FONT_NEXT, 191, 422,
FONT_NEXT, 237, 432,
FONT_NEXT, 269, 427,
FONT_NEXT, 298, 413,
FONT_NEXT, 322, 390,
FONT_NEXT, 343, 362,
FONT_NEXT, 370, 287,
FONT_NEXT, 380, 199,
FONT_NEXT, 377, 150,
FONT_NEXT, 368, 110,
FONT_NEXT, 356, 78,
FONT_NEXT, 340, 54,
FONT_NEXT, 301, 26,
FONT_END, 262, 18,
FONT_ADVANCE, 500, 0
    },
    {
112,
FONT_BEGIN, 73, -160,
FONT_NEXT, 64, -180,
FONT_NEXT, 43, -194,
FONT_NEXT, 5, -200,
FONT_NEXT, 5, -217,
FONT_NEXT, 247, -217,
FONT_NEXT, 247, -199,
FONT_NEXT, 202, -197,
FONT_NEXT, 176, -186,
FONT_NEXT, 162, -163,
FONT_NEXT, 159, -124,
FONT_NEXT, 159, 33,
FONT_NEXT, 206, -1,
FONT_NEXT, 260, -10,
FONT_NEXT, 308, -4,
FONT_NEXT, 351, 13,
FONT_NEXT, 387, 40,
FONT_NEXT, 417, 74,
FONT_NEXT, 456, 156,
FONT_NEXT, 470, 245,
FONT_NEXT, 466, 292,
FONT_NEXT, 456, 334,
FONT_NEXT, 420, 402,
FONT_NEXT, 368, 445,
FONT_NEXT, 305, 460,
FONT_NEXT, 252, 451,
FONT_NEXT, 212, 432,
FONT_NEXT, 181, 406,
FONT_NEXT, 161, 383,
FONT_NEXT, 159, 385,
FONT_NEXT, 159, 458,
FONT_NEXT, 153, 460,
FONT_NEXT, 9, 409,
FONT_NEXT, 9, 393,
FONT_NEXT, 37, 394,
FONT_NEXT, 57, 390,
FONT_NEXT, 69, 380,
FONT_NEXT, 73, 362,
FONT_NEXT, 75, 337,
FONT_END, 75, -131,
FONT_BEGIN, 171, 360,
FONT_NEXT, 196, 381,
FONT_NEXT, 227, 395,
FONT_NEXT, 257, 400,
FONT_NEXT, 297, 392,
FONT_NEXT, 328, 374,
FONT_NEXT, 350, 347,
FONT_NEXT, 366, 316,
FONT_NEXT, 381, 253,
FONT_NEXT, 383, 229,
FONT_NEXT, 384, 215,
FONT_NEXT, 380, 156,
FONT_NEXT, 369, 111,
FONT_NEXT, 353, 77,
FONT_NEXT, 334, 53,
FONT_NEXT, 294, 27,
FONT_NEXT, 261, 22,
FONT_NEXT, 221, 29,
FONT_NEXT, 189, 46,
FONT_NEXT, 167, 67,
FONT_NEXT, 159, 88,
FONT_END, 159, 334,
FONT_ADVANCE, 500, 0
    },
    {
113,
FONT_BEGIN, 308, 450,
FONT_NEXT, 247, 460,
FONT_NEXT, 201, 455,
FONT_NEXT, 159, 440,
FONT_NEXT, 121, 417,
FONT_NEXT, 88, 387,
FONT_NEXT, 61, 351,
FONT_NEXT, 41, 308,
FONT_NEXT, 28, 262,
FONT_NEXT, 24, 212,
FONT_NEXT, 27, 159,
FONT_NEXT, 37, 113,
FONT_NEXT, 52, 75,
FONT_NEXT, 72, 44,
FONT_NEXT, 124, 3,
FONT_NEXT, 188, -10,
FONT_NEXT, 234, -3,
FONT_NEXT, 278, 14,
FONT_NEXT, 314, 38,
FONT_NEXT, 336, 62,
FONT_NEXT, 341, 62,
FONT_NEXT, 341, -124,
FONT_NEXT, 337, -157,
FONT_NEXT, 324, -180,
FONT_NEXT, 297, -194,
FONT_NEXT, 252, -200,
FONT_NEXT, 252, -217,
FONT_NEXT, 488, -217,
FONT_NEXT, 488, -203,
FONT_NEXT, 459, -196,
FONT_NEXT, 439, -187,
FONT_NEXT, 428, -170,
FONT_NEXT, 425, -141,
FONT_NEXT, 425, 456,
FONT_NEXT, 421, 460,
FONT_NEXT, 414, 457,
FONT_END, 360, 425,
FONT_BEGIN, 333, 88,
FONT_NEXT, 318, 73,
FONT_NEXT, 288, 60,
FONT_NEXT, 238, 51,
FONT_NEXT, 206, 55,
FONT_NEXT, 180, 67,
FONT_NEXT, 140, 110,
FONT_NEXT, 117, 169,
FONT_NEXT, 110, 238,
FONT_NEXT, 119, 319,
FONT_NEXT, 147, 380,
FONT_NEXT, 189, 418,
FONT_NEXT, 245, 432,
FONT_NEXT, 276, 428,
FONT_NEXT, 300, 420,
FONT_NEXT, 328, 393,
FONT_NEXT, 339, 360,
FONT_NEXT, 341, 333,
FONT_END, 341, 127,
FONT_ADVANCE, 500, 0
    },
    {
114,
FONT_BEGIN, 72, 51,
FONT_NEXT, 61, 32,
FONT_NEXT, 39, 22,
FONT_NEXT, 5, 15,
FONT_NEXT, 5, 0,
FONT_NEXT, 245, 0,
FONT_NEXT, 245, 15,
FONT_NEXT, 210, 17,
FONT_NEXT, 183, 27,
FONT_NEXT, 166, 49,
FONT_NEXT, 160, 90,
FONT_NEXT, 160, 315,
FONT_NEXT, 167, 341,
FONT_NEXT, 185, 368,
FONT_NEXT, 207, 388,
FONT_NEXT, 228, 397,
FONT_NEXT, 258, 379,
FONT_NEXT, 274, 367,
FONT_NEXT, 297, 362,
FONT_NEXT, 325, 374,
FONT_NEXT, 335, 406,
FONT_NEXT, 331, 428,
FONT_NEXT, 320, 445,
FONT_NEXT, 303, 456,
FONT_NEXT, 281, 460,
FONT_NEXT, 252, 455,
FONT_NEXT, 224, 441,
FONT_NEXT, 195, 413,
FONT_NEXT, 162, 369,
FONT_NEXT, 160, 369,
FONT_NEXT, 160, 458,
FONT_NEXT, 155, 460,
FONT_NEXT, 7, 406,
FONT_NEXT, 7, 390,
FONT_NEXT, 41, 394,
FONT_NEXT, 64, 386,
FONT_NEXT, 72, 368,
FONT_NEXT, 76, 334,
FONT_END, 76, 84,
FONT_ADVANCE, 333, 0
    },
    {
115,
FONT_BEGIN, 311, 451,
FONT_NEXT, 300, 451,
FONT_NEXT, 283, 440,
FONT_NEXT, 247, 450,
FONT_NEXT, 191, 460,
FONT_NEXT, 128, 448,
FONT_NEXT, 84, 420,
FONT_NEXT, 59, 380,
FONT_NEXT, 51, 338,
FONT_NEXT, 60, 288,
FONT_NEXT, 86, 250,
FONT_NEXT, 123, 219,
FONT_NEXT, 168, 192,
FONT_NEXT, 226, 159,
FONT_NEXT, 264, 127,
FONT_NEXT, 278, 85,
FONT_NEXT, 270, 49,
FONT_NEXT, 250, 27,
FONT_NEXT, 224, 15,
FONT_NEXT, 197, 12,
FONT_NEXT, 163, 15,
FONT_NEXT, 137, 26,
FONT_NEXT, 100, 62,
FONT_NEXT, 79, 107,
FONT_NEXT, 68, 152,
FONT_NEXT, 52, 152,
FONT_NEXT, 52, -4,
FONT_NEXT, 65, -4,
FONT_NEXT, 73, 5,
FONT_NEXT, 87, 8,
FONT_NEXT, 138, -1,
FONT_NEXT, 203, -10,
FONT_NEXT, 252, -3,
FONT_NEXT, 299, 21,
FONT_NEXT, 334, 62,
FONT_NEXT, 348, 119,
FONT_NEXT, 341, 158,
FONT_NEXT, 324, 189,
FONT_NEXT, 264, 237,
FONT_NEXT, 156, 301,
FONT_NEXT, 133, 318,
FONT_NEXT, 120, 337,
FONT_NEXT, 113, 372,
FONT_NEXT, 117, 393,
FONT_NEXT, 130, 414,
FONT_NEXT, 153, 430,
FONT_NEXT, 189, 437,
FONT_NEXT, 221, 432,
FONT_NEXT, 252, 413,
FONT_NEXT, 279, 376,
FONT_NEXT, 300, 314,
FONT_END, 315, 314,
FONT_ADVANCE, 389, 0
    },
    {
116,
FONT_BEGIN, 154, 450,
FONT_NEXT, 154, 566,
FONT_NEXT, 147, 579,
FONT_NEXT, 122, 544,
FONT_NEXT, 102, 516,
FONT_NEXT, 57, 465,
FONT_NEXT, 26, 441,
FONT_NEXT, 13, 425,
FONT_NEXT, 17, 418,
FONT_NEXT, 70, 418,
FONT_NEXT, 70, 117,
FONT_NEXT, 77, 54,
FONT_NEXT, 96, 16,
FONT_NEXT, 126, -5,
FONT_NEXT, 162, -10,
FONT_NEXT, 196, -5,
FONT_NEXT, 228, 11,
FONT_NEXT, 257, 35,
FONT_NEXT, 279, 66,
FONT_NEXT, 266, 77,
FONT_NEXT, 243, 54,
FONT_NEXT, 205, 42,
FONT_NEXT, 175, 51,
FONT_NEXT, 160, 75,
FONT_NEXT, 154, 132,
FONT_NEXT, 154, 418,
FONT_NEXT, 255, 418,
FONT_END, 255, 450,
FONT_ADVANCE, 278, 0
    },
    {
117,
FONT_BEGIN, 444, 53,
FONT_NEXT, 425, 64,
FONT_NEXT, 418, 82,
FONT_NEXT, 417, 107,
FONT_NEXT, 417, 450,
FONT_NEXT, 259, 450,
FONT_NEXT, 259, 433,
FONT_NEXT, 300, 427,
FONT_NEXT, 322, 415,
FONT_NEXT, 331, 396,
FONT_NEXT, 333, 370,
FONT_NEXT, 333, 135,
FONT_NEXT, 328, 100,
FONT_NEXT, 317, 84,
FONT_NEXT, 274, 57,
FONT_NEXT, 230, 48,
FONT_NEXT, 190, 57,
FONT_NEXT, 167, 80,
FONT_NEXT, 157, 106,
FONT_NEXT, 155, 124,
FONT_NEXT, 155, 450,
FONT_NEXT, 9, 450,
FONT_NEXT, 9, 436,
FONT_NEXT, 51, 425,
FONT_NEXT, 65, 407,
FONT_NEXT, 71, 372,
FONT_NEXT, 71, 120,
FONT_NEXT, 74, 83,
FONT_NEXT, 83, 54,
FONT_NEXT, 115, 15,
FONT_NEXT, 154, -5,
FONT_NEXT, 190, -10,
FONT_NEXT, 208, -9,
FONT_NEXT, 234, -4,
FONT_NEXT, 264, 9,
FONT_NEXT, 295, 33,
FONT_NEXT, 338, 76,
FONT_NEXT, 338, -7,
FONT_NEXT, 340, -9,
FONT_NEXT, 479, 36,
FONT_END, 479, 50,
FONT_ADVANCE, 500, 0
    },
    {
118,
FONT_BEGIN, 338, 450,
FONT_NEXT, 338, 435,
FONT_NEXT, 370, 426,
FONT_NEXT, 385, 402,
FONT_NEXT, 381, 383,
FONT_NEXT, 373, 355,
FONT_NEXT, 345, 280,
FONT_NEXT, 280, 114,
FONT_NEXT, 178, 370,
FONT_NEXT, 169, 405,
FONT_NEXT, 172, 419,
FONT_NEXT, 183, 428,
FONT_NEXT, 215, 435,
FONT_NEXT, 215, 450,
FONT_NEXT, 19, 450,
FONT_NEXT, 19, 435,
FONT_NEXT, 48, 427,
FONT_NEXT, 74, 398,
FONT_NEXT, 90, 364,
FONT_NEXT, 110, 319,
FONT_NEXT, 156, 212,
FONT_NEXT, 200, 106,
FONT_NEXT, 217, 63,
FONT_NEXT, 230, 33,
FONT_NEXT, 239, 7,
FONT_NEXT, 246, -7,
FONT_NEXT, 257, -14,
FONT_NEXT, 269, 0,
FONT_NEXT, 284, 36,
FONT_NEXT, 412, 357,
FONT_NEXT, 427, 393,
FONT_NEXT, 440, 417,
FONT_NEXT, 455, 429,
FONT_NEXT, 477, 435,
FONT_END, 477, 450,
FONT_ADVANCE, 500, 0
    },
    {
119,
FONT_BEGIN, 571, 435,
FONT_NEXT, 602, 425,
FONT_NEXT, 611, 416,
FONT_NEXT, 615, 402,
FONT_NEXT, 610, 374,
FONT_NEXT, 598, 338,
FONT_NEXT, 508, 116,
FONT_NEXT, 428, 330,
FONT_NEXT, 411, 371,
FONT_NEXT, 407, 398,
FONT_NEXT, 411, 415,
FONT_NEXT, 423, 425,
FONT_NEXT, 465, 435,
FONT_NEXT, 465, 450,
FONT_NEXT, 262, 450,
FONT_NEXT, 262, 435,
FONT_NEXT, 288, 429,
FONT_NEXT, 308, 413,
FONT_NEXT, 327, 376,
FONT_NEXT, 338, 347,
FONT_NEXT, 351, 310,
FONT_NEXT, 260, 111,
FONT_NEXT, 224, 205,
FONT_NEXT, 190, 290,
FONT_NEXT, 164, 358,
FONT_NEXT, 157, 383,
FONT_NEXT, 155, 401,
FONT_NEXT, 158, 417,
FONT_NEXT, 169, 427,
FONT_NEXT, 201, 435,
FONT_NEXT, 201, 450,
FONT_NEXT, 21, 450,
FONT_NEXT, 21, 435,
FONT_NEXT, 49, 420,
FONT_NEXT, 60, 401,
FONT_NEXT, 74, 372,
FONT_NEXT, 209, 30,
FONT_NEXT, 217, 8,
FONT_NEXT, 224, -5,
FONT_NEXT, 235, -14,
FONT_NEXT, 244, -5,
FONT_NEXT, 260, 25,
FONT_NEXT, 372, 265,
FONT_NEXT, 463, 29,
FONT_NEXT, 471, 7,
FONT_NEXT, 477, -6,
FONT_NEXT, 487, -14,
FONT_NEXT, 498, -5,

FONT_NEXT, 505, 10,
FONT_NEXT, 515, 35,
FONT_NEXT, 653, 381,
FONT_NEXT, 671, 419,
FONT_NEXT, 694, 435,
FONT_NEXT, 694, 450,
FONT_END, 571, 450,
FONT_ADVANCE, 722, 0
    },
    {
120,
FONT_BEGIN, 311, 60,
FONT_NEXT, 318, 41,
FONT_NEXT, 309, 20,
FONT_NEXT, 278, 15,
FONT_NEXT, 278, 0,
FONT_NEXT, 479, 0,
FONT_NEXT, 479, 15,
FONT_NEXT, 445, 23,
FONT_NEXT, 423, 41,
FONT_NEXT, 397, 75,
FONT_NEXT, 269, 271,
FONT_NEXT, 352, 391,
FONT_NEXT, 379, 417,
FONT_NEXT, 403, 429,
FONT_NEXT, 433, 435,
FONT_NEXT, 433, 450,
FONT_NEXT, 275, 450,
FONT_NEXT, 275, 435,
FONT_NEXT, 301, 431,
FONT_NEXT, 311, 424,
FONT_NEXT, 315, 412,
FONT_NEXT, 313, 402,
FONT_NEXT, 304, 386,
FONT_NEXT, 284, 356,
FONT_NEXT, 248, 304,
FONT_NEXT, 210, 362,
FONT_NEXT, 194, 391,
FONT_NEXT, 188, 412,
FONT_NEXT, 192, 424,
FONT_NEXT, 202, 431,
FONT_NEXT, 231, 435,
FONT_NEXT, 231, 450,
FONT_NEXT, 24, 450,
FONT_NEXT, 24, 435,
FONT_NEXT, 64, 428,
FONT_NEXT, 83, 410,
FONT_NEXT, 110, 375,
FONT_NEXT, 204, 231,
FONT_NEXT, 90, 66,
FONT_NEXT, 68, 39,
FONT_NEXT, 51, 24,
FONT_NEXT, 17, 15,
FONT_NEXT, 17, 0,
FONT_NEXT, 162, 0,
FONT_NEXT, 162, 15,
FONT_NEXT, 136, 17,
FONT_NEXT, 122, 33,
FONT_NEXT, 142, 74,
FONT_NEXT, 221, 197,
FONT_END, 302, 73,
FONT_ADVANCE, 500, 0
    },
    {
121,
FONT_BEGIN, 340, 450,
FONT_NEXT, 340, 435,
FONT_NEXT, 376, 428,
FONT_NEXT, 388, 407,
FONT_NEXT, 380, 379,
FONT_NEXT, 365, 339,
FONT_NEXT, 287, 117,
FONT_NEXT, 172, 370,
FONT_NEXT, 162, 406,
FONT_NEXT, 167, 421,
FONT_NEXT, 182, 430,
FONT_NEXT, 220, 435,
FONT_NEXT, 220, 450,
FONT_NEXT, 14, 450,
FONT_NEXT, 14, 436,
FONT_NEXT, 39, 428,
FONT_NEXT, 65, 404,
FONT_NEXT, 179, 158,
FONT_NEXT, 194, 125,
FONT_NEXT, 215, 83,
FONT_NEXT, 233, 43,
FONT_NEXT, 241, 20,
FONT_NEXT, 239, 8,
FONT_NEXT, 233, -11,
FONT_NEXT, 214, -63,
FONT_NEXT, 185, -113,
FONT_NEXT, 167, -129,
FONT_NEXT, 149, -134,
FONT_NEXT, 118, -125,
FONT_NEXT, 78, -116,
FONT_NEXT, 51, -123,
FONT_NEXT, 36, -136,
FONT_NEXT, 30, -160,
FONT_NEXT, 34, -181,
FONT_NEXT, 47, -199,
FONT_NEXT, 70, -213,
FONT_NEXT, 103, -218,
FONT_NEXT, 154, -206,
FONT_NEXT, 197, -169,
FONT_NEXT, 235, -106,
FONT_NEXT, 273, -18,
FONT_NEXT, 427, 390,
FONT_NEXT, 439, 414,
FONT_NEXT, 451, 427,
FONT_NEXT, 475, 435,
FONT_END, 475, 450,
FONT_ADVANCE, 500, 0
    },
    {
122,
FONT_BEGIN, 400, 139,
FONT_NEXT, 388, 89,
FONT_NEXT, 369, 55,
FONT_NEXT, 334, 36,
FONT_NEXT, 307, 31,
FONT_NEXT, 272, 30,
FONT_NEXT, 134, 30,
FONT_NEXT, 403, 435,
FONT_NEXT, 403, 450,
FONT_NEXT, 56, 450,
FONT_NEXT, 53, 332,
FONT_NEXT, 71, 332,
FONT_NEXT, 76, 368,
FONT_NEXT, 88, 396,
FONT_NEXT, 112, 413,
FONT_NEXT, 155, 420,
FONT_NEXT, 293, 420,
FONT_NEXT, 27, 15,
FONT_NEXT, 27, 0,
FONT_NEXT, 404, 0,
FONT_END, 418, 135,
FONT_ADVANCE, 444, 0
    },
    {
123,
FONT_BEGIN, 187, -82,
FONT_NEXT, 196, -114,
FONT_NEXT, 210, -139,
FONT_NEXT, 228, -156,
FONT_NEXT, 280, -176,
FONT_NEXT, 350, -181,
FONT_NEXT, 350, -170,
FONT_NEXT, 305, -154,
FONT_NEXT, 277, -129,
FONT_NEXT, 263, -94,
FONT_NEXT, 259, -47,
FONT_NEXT, 259, 121,
FONT_NEXT, 255, 168,
FONT_NEXT, 242, 204,
FONT_NEXT, 215, 230,
FONT_NEXT, 168, 248,
FONT_NEXT, 168, 250,
FONT_NEXT, 215, 267,
FONT_NEXT, 242, 294,
FONT_NEXT, 255, 330,
FONT_NEXT, 259, 378,
FONT_NEXT, 259, 546,
FONT_NEXT, 263, 592,
FONT_NEXT, 277, 627,
FONT_NEXT, 305, 652,
FONT_NEXT, 350, 669,
FONT_NEXT, 350, 680,
FONT_NEXT, 280, 675,
FONT_NEXT, 228, 655,
FONT_NEXT, 210, 637,
FONT_NEXT, 196, 612,
FONT_NEXT, 187, 581,
FONT_NEXT, 185, 541,
FONT_NEXT, 185, 363,
FONT_NEXT, 181, 320,
FONT_NEXT, 168, 289,
FONT_NEXT, 142, 266,
FONT_NEXT, 100, 250,
FONT_NEXT, 142, 233,
FONT_NEXT, 168, 209,
FONT_NEXT, 181, 178,
FONT_NEXT, 185, 136,
FONT_END, 185, -41,
FONT_ADVANCE, 480, 0
    },
    {
124,
FONT_BEGIN, 133, -14,
FONT_NEXT, 133, 676,
FONT_NEXT, 67, 676,
FONT_END, 67, -14,
FONT_ADVANCE, 200, 0
    },
    {
125,
FONT_BEGIN, 292, 580,
FONT_NEXT, 283, 612,
FONT_NEXT, 269, 637,
FONT_NEXT, 251, 655,
FONT_NEXT, 199, 675,
FONT_NEXT, 130, 680,
FONT_NEXT, 130, 669,
FONT_NEXT, 174, 652,
FONT_NEXT, 202, 627,
FONT_NEXT, 216, 592,
FONT_NEXT, 221, 546,
FONT_NEXT, 221, 378,
FONT_NEXT, 224, 330,
FONT_NEXT, 237, 294,
FONT_NEXT, 264, 268,
FONT_NEXT, 312, 251,
FONT_NEXT, 312, 249,
FONT_NEXT, 264, 231,
FONT_NEXT, 237, 204,
FONT_NEXT, 224, 168,
FONT_NEXT, 221, 121,
FONT_NEXT, 221, -47,
FONT_NEXT, 216, -94,
FONT_NEXT, 202, -129,
FONT_NEXT, 174, -154,
FONT_NEXT, 130, -170,
FONT_NEXT, 130, -181,
FONT_NEXT, 199, -177,
FONT_NEXT, 251, -157,
FONT_NEXT, 269, -139,
FONT_NEXT, 283, -114,
FONT_NEXT, 292, -83,
FONT_NEXT, 295, -42,
FONT_NEXT, 295, 136,
FONT_NEXT, 298, 178,
FONT_NEXT, 311, 209,
FONT_NEXT, 337, 233,
FONT_NEXT, 380, 250,
FONT_NEXT, 337, 266,
FONT_NEXT, 311, 289,
FONT_NEXT, 298, 320,
FONT_NEXT, 295, 363,
FONT_END, 295, 540,
FONT_ADVANCE, 480, 0
    },
    {
126,
FONT_BEGIN, 428, 273,
FONT_NEXT, 405, 257,
FONT_NEXT, 378, 251,
FONT_NEXT, 330, 263,
FONT_NEXT, 276, 287,
FONT_NEXT, 219, 309,
FONT_NEXT, 165, 319,
FONT_NEXT, 120, 311,
FONT_NEXT, 85, 291,
FONT_NEXT, 40, 233,
FONT_NEXT, 76, 183,
FONT_NEXT, 107, 232,
FONT_NEXT, 130, 248,
FONT_NEXT, 160, 255,
FONT_NEXT, 222, 244,
FONT_NEXT, 275, 220,
FONT_NEXT, 324, 196,
FONT_NEXT, 377, 187,
FONT_NEXT, 418, 194,
FONT_NEXT, 451, 214,
FONT_NEXT, 502, 273,
FONT_END, 466, 323,
FONT_ADVANCE, 541, 0
    },
    {
END_OF_LIST
    }
};

GLint filledFont[][1+MAX_STROKES*3] = {
    {
32,
FONT_ADVANCE, 250, 0
    },
    {
33,
FONT_BEGIN, 236, 605,
FONT_NEXT, 234, 559,
FONT_NEXT, 229, 642,
FONT_NEXT, 189, 176,
FONT_NEXT, 211, 667,
FONT_END, 182, 676,
FONT_BEGIN, 182, 676,
FONT_NEXT, 189, 176,
FONT_END, 176, 176,
FONT_BEGIN, 182, 676,
FONT_NEXT, 176, 176,
FONT_NEXT, 156, 668,
FONT_NEXT, 167, 284,
FONT_END, 163, 315,
FONT_BEGIN, 156, 668,
FONT_NEXT, 163, 315,
FONT_END, 158, 355,
FONT_BEGIN, 156, 668,
FONT_NEXT, 158, 355,
FONT_END, 146, 447,
FONT_BEGIN, 156, 668,
FONT_NEXT, 146, 447,
FONT_NEXT, 140, 648,
FONT_NEXT, 134, 534,
FONT_NEXT, 130, 592,
FONT_END, 131, 568,
FONT_BEGIN, 238, 42,
FONT_NEXT, 232, 18,
FONT_NEXT, 233, 62,
FONT_END, 222, 79,
FONT_BEGIN, 222, 79,
FONT_NEXT, 232, 18,
FONT_END, 219, 3,
FONT_BEGIN, 222, 79,
FONT_NEXT, 219, 3,
FONT_NEXT, 204, 91,
FONT_NEXT, 183, -9,
FONT_NEXT, 183, 96,
FONT_END, 162, 91,
FONT_BEGIN, 162, 91,
FONT_NEXT, 183, -9,
FONT_END, 148, 2,
FONT_BEGIN, 162, 91,
FONT_NEXT, 148, 2,
FONT_NEXT, 145, 80,
FONT_NEXT, 135, 18,
FONT_NEXT, 134, 63,
FONT_END, 130, 42,
FONT_ADVANCE, 333, 0
    },
    {
34,
FONT_BEGIN, 162, 635,
FONT_NEXT, 158, 599,
FONT_NEXT, 148, 665,
FONT_NEXT, 149, 543,
FONT_END, 139, 482,
FONT_BEGIN, 148, 665,
FONT_NEXT, 139, 482,
FONT_NEXT, 120, 676,
FONT_NEXT, 130, 431,
FONT_END, 109, 431,
FONT_BEGIN, 120, 676,
FONT_NEXT, 109, 431,
FONT_NEXT, 91, 665,
FONT_NEXT, 99, 482,
FONT_END, 89, 543,
FONT_BEGIN, 91, 665,
FONT_NEXT, 89, 543,
FONT_NEXT, 77, 635,
FONT_END, 80, 599,
FONT_BEGIN, 331, 635,
FONT_NEXT, 327, 599,
FONT_NEXT, 317, 665,
FONT_NEXT, 318, 543,
FONT_END, 308, 482,
FONT_BEGIN, 317, 665,
FONT_NEXT, 308, 482,
FONT_NEXT, 289, 676,
FONT_NEXT, 299, 431,
FONT_END, 278, 431,
FONT_BEGIN, 289, 676,
FONT_NEXT, 278, 431,
FONT_NEXT, 260, 665,
FONT_NEXT, 268, 482,
FONT_END, 258, 543,
FONT_BEGIN, 260, 665,
FONT_NEXT, 258, 543,
FONT_NEXT, 246, 635,
FONT_END, 249, 599,
FONT_ADVANCE, 408, 0
    },
    {
35,
FONT_BEGIN, 32, 405,
FONT_NEXT, 32, 460,
FONT_NEXT, 142, 405,
FONT_NEXT, 150, 460,
FONT_END, 137, 0,
FONT_BEGIN, 142, 405,
FONT_NEXT, 137, 0,
FONT_NEXT, 121, 271,
FONT_NEXT, 79, 0,
FONT_END, 112, 216,
FONT_BEGIN, 121, 271,
FONT_NEXT, 112, 216,
FONT_NEXT, 5, 271,
FONT_END, 5, 216,
FONT_BEGIN, 239, 662,
FONT_NEXT, 208, 460,
FONT_NEXT, 181, 662,
FONT_NEXT, 200, 405,
FONT_END, 179, 271,
FONT_BEGIN, 181, 662,
FONT_NEXT, 179, 271,
FONT_NEXT, 150, 460,
FONT_NEXT, 170, 216,
FONT_END, 137, 0,
FONT_BEGIN, 170, 216,
FONT_NEXT, 179, 271,
FONT_NEXT, 304, 216,
FONT_NEXT, 313, 271,
FONT_NEXT, 273, 0,
FONT_END, 331, 0,
FONT_BEGIN, 331, 0,
FONT_NEXT, 313, 271,
FONT_END, 333, 405,
FONT_BEGIN, 331, 0,
FONT_NEXT, 333, 405,
FONT_NEXT, 341, 460,
FONT_END, 208, 460,
FONT_BEGIN, 208, 460,
FONT_NEXT, 333, 405,
FONT_END, 200, 405,
FONT_BEGIN, 208, 460,
FONT_END, 200, 405,
FONT_BEGIN, 429, 662,
FONT_NEXT, 399, 460,
FONT_NEXT, 371, 662,
FONT_NEXT, 391, 405,
FONT_END, 371, 271,
FONT_BEGIN, 371, 662,
FONT_NEXT, 371, 271,
FONT_NEXT, 341, 460,
FONT_NEXT, 362, 216,
FONT_END, 331, 0,
FONT_BEGIN, 496, 460,
FONT_NEXT, 496, 405,
FONT_NEXT, 399, 460,
FONT_END, 391, 405,
FONT_BEGIN, 471, 271,
FONT_NEXT, 471, 216,
FONT_NEXT, 371, 271,
FONT_END, 362, 216,
FONT_ADVANCE, 500, 0
    },
    {
36,
FONT_BEGIN, 264, 727,
FONT_NEXT, 264, 664,
FONT_NEXT, 230, 727,
FONT_NEXT, 264, 637,
FONT_END, 264, 391,
FONT_BEGIN, 230, 727,
FONT_NEXT, 264, 391,
FONT_END, 264, 293,
FONT_BEGIN, 230, 727,
FONT_NEXT, 264, 293,
FONT_END, 264, 28,
FONT_BEGIN, 230, 727,
FONT_NEXT, 264, 28,
FONT_END, 264, 0,
FONT_BEGIN, 230, 727,
FONT_NEXT, 264, 0,
FONT_END, 264, -87,
FONT_BEGIN, 230, 727,
FONT_NEXT, 264, -87,
FONT_END, 230, -87,
FONT_BEGIN, 230, 727,
FONT_NEXT, 230, -87,
FONT_NEXT, 230, 664,
FONT_END, 230, 310,
FONT_BEGIN, 230, 310,
FONT_NEXT, 230, -87,
FONT_NEXT, 230, 28,
FONT_NEXT, 230, 0,
FONT_NEXT, 166, 37,
FONT_NEXT, 162, 5,
FONT_NEXT, 117, 66,
FONT_NEXT, 110, 19,
FONT_NEXT, 81, 114,
FONT_NEXT, 70, 35,
FONT_NEXT, 59, 181,
FONT_NEXT, 44, 51,
FONT_END, 44, 181,
FONT_BEGIN, 425, 611,
FONT_NEXT, 425, 500,
FONT_NEXT, 387, 634,
FONT_NEXT, 410, 500,
FONT_END, 399, 537,
FONT_BEGIN, 387, 634,
FONT_NEXT, 399, 537,
FONT_END, 385, 566,
FONT_BEGIN, 387, 634,
FONT_NEXT, 385, 566,
FONT_NEXT, 345, 649,
FONT_NEXT, 348, 607,
FONT_END, 305, 628,
FONT_BEGIN, 345, 649,
FONT_NEXT, 305, 628,
FONT_NEXT, 264, 664,
FONT_END, 264, 637,
FONT_BEGIN, 457, 174,
FONT_NEXT, 453, 134,
FONT_NEXT, 453, 211,
FONT_END, 444, 243,
FONT_BEGIN, 444, 243,
FONT_NEXT, 453, 134,
FONT_END, 441, 100,
FONT_BEGIN, 444, 243,
FONT_NEXT, 441, 100,
FONT_NEXT, 408, 295,
FONT_NEXT, 399, 49,
FONT_NEXT, 348, 341,
FONT_NEXT, 378, 151,
FONT_END, 369, 197,
FONT_BEGIN, 348, 341,
FONT_NEXT, 369, 197,
FONT_END, 344, 234,
FONT_BEGIN, 348, 341,
FONT_NEXT, 344, 234,
FONT_NEXT, 264, 391,
FONT_NEXT, 308, 264,
FONT_END, 264, 293,
FONT_BEGIN, 264, 28,
FONT_NEXT, 295, 35,
FONT_NEXT, 264, 0,
FONT_NEXT, 333, 53,
FONT_NEXT, 338, 16,
FONT_NEXT, 364, 89,
FONT_END, 374, 116,
FONT_BEGIN, 338, 16,
FONT_NEXT, 374, 116,
FONT_NEXT, 399, 49,
FONT_END, 378, 151,
FONT_BEGIN, 126, 532,
FONT_NEXT, 133, 495,
FONT_NEXT, 106, 392,
FONT_NEXT, 155, 464,
FONT_NEXT, 163, 349,
FONT_NEXT, 187, 435,
FONT_END, 229, 407,
FONT_BEGIN, 163, 349,
FONT_NEXT, 229, 407,
FONT_NEXT, 230, 310,
FONT_NEXT, 229, 637,
FONT_NEXT, 230, 664,
FONT_END, 185, 658,
FONT_BEGIN, 185, 658,
FONT_NEXT, 229, 637,
FONT_END, 196, 628,
FONT_BEGIN, 185, 658,
FONT_NEXT, 196, 628,
FONT_END, 171, 616,
FONT_BEGIN, 185, 658,
FONT_NEXT, 171, 616,
FONT_NEXT, 148, 646,
FONT_NEXT, 141, 586,
FONT_NEXT, 93, 611,
FONT_NEXT, 128, 555,
FONT_END, 126, 532,
FONT_BEGIN, 93, 611,
FONT_NEXT, 126, 532,
FONT_END, 106, 392,
FONT_BEGIN, 93, 611,
FONT_NEXT, 106, 392,
FONT_END, 67, 445,
FONT_BEGIN, 93, 611,
FONT_NEXT, 67, 445,
FONT_NEXT, 61, 563,
FONT_NEXT, 55, 476,
FONT_END, 52, 511,
FONT_ADVANCE, 500, 0
    },
    {
37,
FONT_BEGIN, 102, 319,
FONT_NEXT, 137, 397,
FONT_NEXT, 140, 296,
FONT_NEXT, 141, 362,
FONT_END, 153, 339,
FONT_BEGIN, 140, 296,
FONT_NEXT, 153, 339,
FONT_END, 172, 326,
FONT_BEGIN, 140, 296,
FONT_NEXT, 172, 326,
FONT_NEXT, 178, 289,
FONT_NEXT, 199, 323,
FONT_NEXT, 223, 295,
FONT_NEXT, 228, 328,
FONT_END, 257, 343,
FONT_BEGIN, 223, 295,
FONT_NEXT, 257, 343,
FONT_NEXT, 264, 314,
FONT_END, 299, 343,
FONT_BEGIN, 299, 343,
FONT_NEXT, 257, 343,
FONT_END, 309, 396,
FONT_BEGIN, 299, 343,
FONT_NEXT, 309, 396,
FONT_NEXT, 329, 379,
FONT_NEXT, 345, 469,
FONT_NEXT, 369, 463,
FONT_NEXT, 359, 553,
FONT_NEXT, 352, 635,
FONT_NEXT, 350, 600,
FONT_NEXT, 325, 653,
FONT_NEXT, 315, 611,
FONT_NEXT, 305, 659,
FONT_NEXT, 299, 622,
FONT_NEXT, 276, 662,
FONT_NEXT, 281, 632,
FONT_END, 255, 625,
FONT_BEGIN, 276, 662,
FONT_NEXT, 255, 625,
FONT_NEXT, 232, 655,
FONT_NEXT, 230, 606,
FONT_NEXT, 192, 639,
FONT_NEXT, 184, 545,
FONT_NEXT, 156, 613,
FONT_NEXT, 150, 468,
FONT_NEXT, 124, 581,
FONT_NEXT, 137, 397,
FONT_END, 102, 319,
FONT_BEGIN, 124, 581,
FONT_NEXT, 102, 319,
FONT_NEXT, 78, 504,
FONT_NEXT, 72, 361,
FONT_NEXT, 65, 463,
FONT_NEXT, 64, 390,
FONT_END, 61, 425,
FONT_BEGIN, 201, -13,
FONT_NEXT, 550, 609,
FONT_NEXT, 249, -13,
FONT_END, 595, 676,
FONT_BEGIN, 595, 676,
FONT_NEXT, 550, 609,
FONT_NEXT, 536, 630,
FONT_NEXT, 548, 611,
FONT_END, 507, 592,
FONT_BEGIN, 536, 630,
FONT_NEXT, 507, 592,
FONT_NEXT, 493, 614,
FONT_NEXT, 478, 585,
FONT_NEXT, 438, 608,
FONT_NEXT, 443, 583,
FONT_END, 401, 585,
FONT_BEGIN, 438, 608,
FONT_NEXT, 401, 585,
FONT_NEXT, 404, 610,
FONT_END, 381, 616,
FONT_BEGIN, 381, 616,
FONT_NEXT, 401, 585,
FONT_END, 377, 593,
FONT_BEGIN, 381, 616,
FONT_NEXT, 377, 593,
FONT_NEXT, 352, 635,
FONT_END, 369, 463,
FONT_BEGIN, 369, 463,
FONT_NEXT, 377, 593,
FONT_END, 384, 548,
FONT_BEGIN, 369, 463,
FONT_END, 384, 548,
FONT_BEGIN, 249, -13,
FONT_NEXT, 595, 676,
FONT_END, 634, 676,
FONT_BEGIN, 772, 261,
FONT_NEXT, 757, 173,
FONT_NEXT, 768, 295,
FONT_END, 760, 321,
FONT_BEGIN, 760, 321,
FONT_NEXT, 757, 173,
FONT_END, 746, 254,
FONT_BEGIN, 760, 321,
FONT_NEXT, 746, 254,
FONT_NEXT, 731, 354,
FONT_NEXT, 740, 287,
FONT_END, 726, 314,
FONT_BEGIN, 731, 354,
FONT_NEXT, 726, 314,
FONT_NEXT, 695, 368,
FONT_NEXT, 703, 332,
FONT_END, 676, 339,
FONT_BEGIN, 695, 368,
FONT_NEXT, 676, 339,
FONT_NEXT, 663, 371,
FONT_NEXT, 641, 330,
FONT_NEXT, 622, 365,
FONT_NEXT, 611, 308,
FONT_NEXT, 583, 349,
FONT_NEXT, 564, 238,
FONT_NEXT, 514, 293,
FONT_NEXT, 534, 160,
FONT_END, 527, 128,
FONT_BEGIN, 514, 293,
FONT_NEXT, 527, 128,
FONT_END, 525, 106,
FONT_BEGIN, 514, 293,
FONT_NEXT, 525, 106,
FONT_END, 513, 11,
FONT_BEGIN, 514, 293,
FONT_NEXT, 513, 11,
FONT_NEXT, 466, 218,
FONT_NEXT, 475, 42,
FONT_END, 455, 86,
FONT_BEGIN, 466, 218,
FONT_NEXT, 455, 86,
FONT_NEXT, 453, 177,
FONT_END, 449, 137,
FONT_BEGIN, 525, 106,
FONT_NEXT, 533, 60,
FONT_NEXT, 513, 11,
FONT_NEXT, 552, 39,
FONT_NEXT, 572, 0,
FONT_NEXT, 589, 30,
FONT_NEXT, 615, 6,
FONT_NEXT, 620, 36,
FONT_END, 650, 53,
FONT_BEGIN, 615, 6,
FONT_NEXT, 650, 53,
FONT_NEXT, 654, 25,
FONT_END, 688, 53,
FONT_BEGIN, 688, 53,
FONT_NEXT, 650, 53,
FONT_END, 700, 110,
FONT_BEGIN, 688, 53,
FONT_NEXT, 700, 110,
FONT_NEXT, 717, 88,
FONT_NEXT, 733, 183,
FONT_END, 746, 254,
FONT_BEGIN, 717, 88,
FONT_NEXT, 746, 254,
FONT_END, 757, 173,
FONT_ADVANCE, 833, 0
    },
    {
38,
FONT_BEGIN, 42, 144,
FONT_NEXT, 57, 215,
FONT_NEXT, 46, 99,
FONT_END, 59, 64,
FONT_BEGIN, 59, 64,
FONT_NEXT, 57, 215,
FONT_NEXT, 78, 36,
FONT_NEXT, 95, 274,
FONT_NEXT, 101, 16,
FONT_END, 134, 180,
FONT_BEGIN, 134, 180,
FONT_NEXT, 95, 274,
FONT_NEXT, 146, 238,
FONT_NEXT, 146, 320,
FONT_NEXT, 176, 283,
FONT_NEXT, 199, 355,
FONT_NEXT, 214, 317,
FONT_NEXT, 237, 377,
FONT_NEXT, 252, 343,
FONT_END, 240, 624,
FONT_BEGIN, 240, 624,
FONT_NEXT, 237, 377,
FONT_NEXT, 211, 573,
FONT_NEXT, 211, 452,
FONT_END, 202, 519,
FONT_BEGIN, 321, 416,
FONT_NEXT, 350, 432,
FONT_NEXT, 336, 384,
FONT_END, 387, 409,
FONT_BEGIN, 387, 409,
FONT_NEXT, 350, 432,
FONT_END, 389, 462,
FONT_BEGIN, 387, 409,
FONT_NEXT, 389, 462,
FONT_END, 423, 504,
FONT_BEGIN, 387, 409,
FONT_NEXT, 423, 504,
FONT_NEXT, 437, 444,
FONT_NEXT, 438, 560,
FONT_NEXT, 450, 644,
FONT_NEXT, 431, 594,
FONT_NEXT, 407, 667,
FONT_NEXT, 414, 620,
FONT_END, 389, 637,
FONT_BEGIN, 407, 667,
FONT_NEXT, 389, 637,
FONT_NEXT, 355, 676,
FONT_NEXT, 359, 644,
FONT_END, 329, 639,
FONT_BEGIN, 355, 676,
FONT_NEXT, 329, 639,
FONT_NEXT, 319, 672,
FONT_NEXT, 304, 623,
FONT_NEXT, 288, 661,
FONT_NEXT, 287, 596,
FONT_NEXT, 240, 624,
FONT_NEXT, 281, 557,
FONT_NEXT, 252, 343,
FONT_NEXT, 293, 483,
FONT_END, 321, 416,
FONT_BEGIN, 252, 343,
FONT_NEXT, 321, 416,
FONT_NEXT, 322, 219,
FONT_NEXT, 336, 384,
FONT_END, 396, 277,
FONT_BEGIN, 322, 219,
FONT_NEXT, 396, 277,
FONT_NEXT, 404, 104,
FONT_NEXT, 429, 78,
FONT_END, 394, 49,
FONT_BEGIN, 404, 104,
FONT_NEXT, 394, 49,
FONT_NEXT, 356, 69,
FONT_NEXT, 347, 19,
FONT_NEXT, 316, 49,
FONT_NEXT, 286, -4,
FONT_NEXT, 285, 41,
FONT_NEXT, 207, -13,
FONT_NEXT, 263, 39,
FONT_END, 212, 50,
FONT_BEGIN, 212, 50,
FONT_NEXT, 207, -13,
FONT_NEXT, 171, 80,
FONT_NEXT, 155, -7,
FONT_NEXT, 144, 125,
FONT_NEXT, 101, 16,
FONT_END, 134, 180,
FONT_BEGIN, 491, 556,
FONT_NEXT, 486, 521,
FONT_NEXT, 480, 606,
FONT_NEXT, 475, 492,
FONT_NEXT, 450, 644,
FONT_END, 437, 444,
FONT_BEGIN, 495, 405,
FONT_NEXT, 495, 426,
FONT_NEXT, 540, 395,
FONT_NEXT, 617, 346,
FONT_NEXT, 554, 381,
FONT_NEXT, 591, 300,
FONT_NEXT, 559, 359,
FONT_NEXT, 568, 261,
FONT_END, 550, 232,
FONT_BEGIN, 559, 359,
FONT_NEXT, 550, 232,
FONT_NEXT, 548, 310,
FONT_NEXT, 528, 200,
FONT_NEXT, 524, 259,
FONT_NEXT, 491, 150,
FONT_NEXT, 494, 213,
FONT_END, 468, 178,
FONT_BEGIN, 468, 178,
FONT_NEXT, 491, 150,
FONT_END, 456, 51,
FONT_BEGIN, 468, 178,
FONT_NEXT, 456, 51,
FONT_NEXT, 396, 277,
FONT_END, 429, 78,
FONT_BEGIN, 711, 426,
FONT_NEXT, 711, 405,
FONT_NEXT, 495, 426,
FONT_NEXT, 661, 392,
FONT_END, 638, 375,
FONT_BEGIN, 495, 426,
FONT_NEXT, 638, 375,
FONT_END, 617, 346,
FONT_BEGIN, 750, 100,
FONT_NEXT, 747, 93,
FONT_NEXT, 735, 111,
FONT_NEXT, 740, 80,
FONT_END, 713, 42,
FONT_BEGIN, 735, 111,
FONT_NEXT, 713, 42,
FONT_NEXT, 697, 73,
FONT_NEXT, 667, 4,
FONT_NEXT, 672, 62,
FONT_END, 639, 58,
FONT_BEGIN, 639, 58,
FONT_NEXT, 667, 4,
FONT_END, 635, -9,
FONT_BEGIN, 639, 58,
FONT_NEXT, 635, -9,
FONT_NEXT, 585, 70,
FONT_NEXT, 599, -13,
FONT_END, 542, -3,
FONT_BEGIN, 585, 70,
FONT_NEXT, 542, -3,
FONT_NEXT, 540, 98,
FONT_NEXT, 494, 21,
FONT_NEXT, 507, 129,
FONT_END, 491, 150,
FONT_BEGIN, 491, 150,
FONT_NEXT, 494, 21,
FONT_END, 456, 51,
FONT_BEGIN, 491, 150,
FONT_END, 456, 51,
FONT_ADVANCE, 778, 0
    },
    {
39,
FONT_BEGIN, 106, 433,
FONT_NEXT, 97, 452,
FONT_NEXT, 136, 450,
FONT_NEXT, 137, 483,
FONT_END, 162, 513,
FONT_BEGIN, 136, 450,
FONT_NEXT, 162, 513,
FONT_NEXT, 173, 483,
FONT_NEXT, 175, 539,
FONT_END, 179, 558,
FONT_BEGIN, 173, 483,
FONT_NEXT, 179, 558,
FONT_NEXT, 204, 528,
FONT_END, 186, 659,
FONT_BEGIN, 186, 659,
FONT_NEXT, 179, 558,
FONT_NEXT, 159, 672,
FONT_NEXT, 174, 568,
FONT_END, 165, 572,
FONT_BEGIN, 159, 672,
FONT_NEXT, 165, 572,
FONT_END, 156, 570,
FONT_BEGIN, 159, 672,
FONT_NEXT, 156, 570,
FONT_NEXT, 137, 676,
FONT_NEXT, 141, 568,
FONT_END, 100, 578,
FONT_BEGIN, 137, 676,
FONT_NEXT, 100, 578,
FONT_NEXT, 98, 663,
FONT_NEXT, 85, 593,
FONT_NEXT, 84, 645,
FONT_END, 79, 618,
FONT_BEGIN, 218, 580,
FONT_NEXT, 204, 528,
FONT_NEXT, 208, 630,
FONT_END, 186, 659,
FONT_ADVANCE, 333, 0
    },
    {
40,
FONT_BEGIN, 48, 262,
FONT_NEXT, 52, 325,
FONT_NEXT, 53, 169,
FONT_NEXT, 64, 386,
FONT_NEXT, 70, 96,
FONT_NEXT, 85, 444,
FONT_NEXT, 96, 35,
FONT_NEXT, 113, 499,
FONT_NEXT, 130, -20,
FONT_NEXT, 134, 225,
FONT_END, 136, 174,
FONT_BEGIN, 130, -20,
FONT_NEXT, 136, 174,
FONT_END, 155, 56,
FONT_BEGIN, 130, -20,
FONT_NEXT, 155, 56,
FONT_END, 175, -4,
FONT_BEGIN, 130, -20,
FONT_NEXT, 175, -4,
FONT_NEXT, 203, -108,
FONT_NEXT, 205, -63,
FONT_NEXT, 247, -148,
FONT_NEXT, 247, -116,
FONT_NEXT, 292, -177,
FONT_END, 304, -161,
FONT_BEGIN, 304, 660,
FONT_NEXT, 246, 609,
FONT_NEXT, 295, 676,
FONT_END, 239, 638,
FONT_BEGIN, 239, 638,
FONT_NEXT, 246, 609,
FONT_END, 191, 537,
FONT_BEGIN, 239, 638,
FONT_NEXT, 191, 537,
FONT_NEXT, 191, 596,
FONT_END, 148, 549,
FONT_BEGIN, 148, 549,
FONT_NEXT, 191, 537,
FONT_END, 168, 488,
FONT_BEGIN, 148, 549,
FONT_NEXT, 168, 488,
FONT_END, 150, 428,
FONT_BEGIN, 148, 549,
FONT_NEXT, 150, 428,
FONT_END, 138, 356,
FONT_BEGIN, 148, 549,
FONT_NEXT, 138, 356,
FONT_NEXT, 113, 499,
FONT_NEXT, 134, 269,
FONT_END, 134, 225,
FONT_ADVANCE, 333, 0
    },
    {
41,
FONT_BEGIN, 29, -161,
FONT_NEXT, 86, -111,
FONT_NEXT, 38, -177,
FONT_END, 93, -140,
FONT_BEGIN, 93, -140,
FONT_NEXT, 86, -111,
FONT_NEXT, 141, -98,
FONT_NEXT, 141, -39,
FONT_END, 164, 10,
FONT_BEGIN, 141, -98,
FONT_NEXT, 164, 10,
FONT_END, 182, 70,
FONT_BEGIN, 141, -98,
FONT_NEXT, 182, 70,
FONT_NEXT, 184, -51,
FONT_NEXT, 194, 142,
FONT_END, 199, 230,
FONT_BEGIN, 184, -51,
FONT_NEXT, 199, 230,
FONT_NEXT, 219, 0,
FONT_END, 203, 519,
FONT_BEGIN, 203, 519,
FONT_NEXT, 199, 230,
FONT_END, 198, 273,
FONT_BEGIN, 203, 519,
FONT_NEXT, 198, 273,
FONT_END, 196, 324,
FONT_BEGIN, 203, 519,
FONT_NEXT, 196, 324,
FONT_END, 177, 442,
FONT_BEGIN, 203, 519,
FONT_NEXT, 177, 442,
FONT_NEXT, 129, 606,
FONT_NEXT, 157, 502,
FONT_END, 127, 561,
FONT_BEGIN, 129, 606,
FONT_NEXT, 127, 561,
FONT_NEXT, 85, 646,
FONT_NEXT, 85, 614,
FONT_NEXT, 41, 676,
FONT_END, 29, 660,
FONT_BEGIN, 285, 237,
FONT_NEXT, 280, 173,
FONT_NEXT, 279, 329,
FONT_NEXT, 268, 112,
FONT_NEXT, 262, 402,
FONT_NEXT, 247, 54,
FONT_NEXT, 236, 463,
FONT_NEXT, 219, 0,
FONT_END, 203, 519,
FONT_ADVANCE, 333, 0
    },
    {
42,
FONT_BEGIN, 69, 557,
FONT_NEXT, 75, 576,
FONT_NEXT, 72, 538,
FONT_END, 83, 526,
FONT_BEGIN, 83, 526,
FONT_NEXT, 75, 576,
FONT_END, 85, 586,
FONT_BEGIN, 83, 526,
FONT_NEXT, 85, 586,
FONT_END, 102, 591,
FONT_BEGIN, 83, 526,
FONT_NEXT, 102, 591,
FONT_NEXT, 120, 511,
FONT_NEXT, 129, 581,
FONT_END, 152, 557,
FONT_BEGIN, 120, 511,
FONT_NEXT, 152, 557,
FONT_NEXT, 170, 500,
FONT_NEXT, 185, 523,
FONT_NEXT, 223, 478,
FONT_END, 234, 471,
FONT_BEGIN, 234, 471,
FONT_NEXT, 185, 523,
FONT_END, 241, 486,
FONT_BEGIN, 234, 471,
FONT_NEXT, 241, 486,
FONT_END, 240, 456,
FONT_BEGIN, 234, 471,
FONT_NEXT, 240, 456,
FONT_END, 210, 435,
FONT_BEGIN, 234, 471,
FONT_NEXT, 210, 435,
FONT_NEXT, 172, 441,
FONT_NEXT, 192, 422,
FONT_END, 164, 394,
FONT_BEGIN, 172, 441,
FONT_NEXT, 164, 394,
FONT_NEXT, 120, 428,
FONT_NEXT, 143, 371,
FONT_END, 129, 358,
FONT_BEGIN, 120, 428,
FONT_NEXT, 129, 358,
FONT_END, 103, 351,
FONT_BEGIN, 120, 428,
FONT_NEXT, 103, 351,
FONT_NEXT, 82, 415,
FONT_NEXT, 76, 362,
FONT_NEXT, 72, 402,
FONT_END, 69, 383,
FONT_BEGIN, 214, 304,
FONT_NEXT, 219, 331,
FONT_NEXT, 225, 275,
FONT_NEXT, 230, 365,
FONT_END, 240, 405,
FONT_BEGIN, 225, 275,
FONT_NEXT, 240, 405,
FONT_NEXT, 249, 265,
FONT_NEXT, 241, 449,
FONT_NEXT, 241, 499,
FONT_END, 241, 486,
FONT_BEGIN, 241, 486,
FONT_NEXT, 241, 449,
FONT_END, 240, 456,
FONT_BEGIN, 241, 486,
FONT_END, 240, 456,
FONT_BEGIN, 216, 637,
FONT_NEXT, 220, 659,
FONT_NEXT, 228, 581,
FONT_NEXT, 231, 671,
FONT_NEXT, 237, 548,
FONT_END, 241, 499,
FONT_BEGIN, 241, 499,
FONT_NEXT, 231, 671,
FONT_END, 253, 676,
FONT_BEGIN, 241, 499,
FONT_NEXT, 253, 676,
FONT_NEXT, 249, 265,
FONT_END, 260, 449,
FONT_BEGIN, 260, 449,
FONT_NEXT, 253, 676,
FONT_NEXT, 260, 456,
FONT_END, 260, 484,
FONT_BEGIN, 260, 484,
FONT_NEXT, 253, 676,
FONT_NEXT, 260, 510,
FONT_NEXT, 269, 671,
FONT_NEXT, 273, 582,
FONT_NEXT, 279, 661,
FONT_NEXT, 282, 613,
FONT_END, 287, 637,
FONT_BEGIN, 288, 307,
FONT_NEXT, 277, 278,
FONT_NEXT, 274, 352,
FONT_NEXT, 249, 265,
FONT_NEXT, 264, 389,
FONT_END, 260, 449,
FONT_BEGIN, 431, 557,
FONT_NEXT, 428, 537,
FONT_NEXT, 427, 574,
FONT_NEXT, 420, 525,
FONT_NEXT, 417, 585,
FONT_NEXT, 388, 513,
FONT_NEXT, 395, 593,
FONT_END, 368, 581,
FONT_BEGIN, 368, 581,
FONT_NEXT, 388, 513,
FONT_END, 336, 502,
FONT_BEGIN, 368, 581,
FONT_NEXT, 336, 502,
FONT_NEXT, 341, 554,
FONT_END, 309, 520,
FONT_BEGIN, 309, 520,
FONT_NEXT, 336, 502,
FONT_END, 304, 489,
FONT_BEGIN, 309, 520,
FONT_NEXT, 304, 489,
FONT_NEXT, 267, 488,
FONT_NEXT, 268, 470,
FONT_END, 267, 453,
FONT_BEGIN, 267, 488,
FONT_NEXT, 267, 453,
FONT_NEXT, 260, 484,
FONT_END, 260, 456,
FONT_BEGIN, 432, 386,
FONT_NEXT, 421, 359,
FONT_NEXT, 428, 404,
FONT_END, 419, 416,
FONT_BEGIN, 419, 416,
FONT_NEXT, 421, 359,
FONT_END, 396, 350,
FONT_BEGIN, 419, 416,
FONT_NEXT, 396, 350,
FONT_NEXT, 384, 428,
FONT_NEXT, 366, 362,
FONT_NEXT, 334, 439,
FONT_NEXT, 338, 391,
FONT_END, 307, 425,
FONT_BEGIN, 334, 439,
FONT_NEXT, 307, 425,
FONT_NEXT, 276, 465,
FONT_NEXT, 267, 453,
FONT_END, 268, 470,
FONT_ADVANCE, 500, 0
    },
    {
43,
FONT_BEGIN, 315, 506,
FONT_NEXT, 315, 286,
FONT_NEXT, 249, 506,
FONT_NEXT, 315, 220,
FONT_END, 315, 0,
FONT_BEGIN, 249, 506,
FONT_NEXT, 315, 0,
FONT_END, 249, 0,
FONT_BEGIN, 249, 506,
FONT_NEXT, 249, 0,
FONT_NEXT, 249, 286,
FONT_NEXT, 249, 220,
FONT_NEXT, 30, 286,
FONT_END, 30, 220,
FONT_BEGIN, 534, 286,
FONT_NEXT, 534, 220,
FONT_NEXT, 315, 286,
FONT_END, 315, 220,
FONT_ADVANCE, 564, 0
    },
    {
44,
FONT_BEGIN, 83, -141,
FONT_NEXT, 74, -122,
FONT_NEXT, 113, -124,
FONT_NEXT, 114, -91,
FONT_END, 139, -61,
FONT_BEGIN, 113, -124,
FONT_NEXT, 139, -61,
FONT_NEXT, 150, -91,
FONT_NEXT, 152, -35,
FONT_END, 156, -16,
FONT_BEGIN, 150, -91,
FONT_NEXT, 156, -16,
FONT_NEXT, 181, -46,
FONT_END, 163, 85,
FONT_BEGIN, 163, 85,
FONT_NEXT, 156, -16,
FONT_NEXT, 136, 98,
FONT_NEXT, 151, -6,
FONT_END, 142, -2,
FONT_BEGIN, 136, 98,
FONT_NEXT, 142, -2,
FONT_END, 133, -4,
FONT_BEGIN, 136, 98,
FONT_NEXT, 133, -4,
FONT_NEXT, 114, 102,
FONT_NEXT, 118, -6,
FONT_END, 77, 4,
FONT_BEGIN, 114, 102,
FONT_NEXT, 77, 4,
FONT_NEXT, 75, 89,
FONT_NEXT, 62, 19,
FONT_NEXT, 61, 71,
FONT_END, 56, 44,
FONT_BEGIN, 195, 6,
FONT_NEXT, 181, -46,
FONT_NEXT, 185, 56,
FONT_END, 163, 85,
FONT_ADVANCE, 250, 0
    },
    {
45,
FONT_BEGIN, 285, 257,
FONT_NEXT, 285, 194,
FONT_NEXT, 39, 257,
FONT_END, 39, 194,
FONT_ADVANCE, 333, 0
    },
    {
46,
FONT_BEGIN, 181, 43,
FONT_NEXT, 175, 18,
FONT_NEXT, 176, 64,
FONT_END, 164, 83,
FONT_BEGIN, 164, 83,
FONT_NEXT, 175, 18,
FONT_END, 160, 2,
FONT_BEGIN, 164, 83,
FONT_NEXT, 160, 2,
FONT_NEXT, 146, 95,
FONT_NEXT, 125, -11,
FONT_NEXT, 125, 100,
FONT_END, 103, 95,
FONT_BEGIN, 103, 95,
FONT_NEXT, 125, -11,
FONT_END, 89, 1,
FONT_BEGIN, 103, 95,
FONT_NEXT, 89, 1,
FONT_NEXT, 86, 83,
FONT_NEXT, 75, 18,
FONT_NEXT, 74, 65,
FONT_END, 70, 43,
FONT_ADVANCE, 250, 0
    },
    {
47,
FONT_BEGIN, 287, 676,
FONT_NEXT, 59, -14,
FONT_NEXT, 220, 676,
FONT_END, -9, -14,
FONT_ADVANCE, 278, 0
    },
    {
48,
FONT_BEGIN, 476, 331,
FONT_NEXT, 466, 226,
FONT_NEXT, 473, 379,
FONT_END, 466, 434,
FONT_BEGIN, 466, 434,

FONT_NEXT, 466, 226,
FONT_NEXT, 432, 547,
FONT_NEXT, 432, 113,
FONT_NEXT, 402, 597,
FONT_NEXT, 402, 63,
FONT_NEXT, 362, 638,
FONT_NEXT, 377, 409,
FONT_END, 363, 516,
FONT_BEGIN, 362, 638,
FONT_NEXT, 363, 516,
FONT_END, 347, 566,
FONT_BEGIN, 362, 638,
FONT_NEXT, 347, 566,
FONT_NEXT, 312, 666,
FONT_NEXT, 324, 609,
FONT_END, 292, 639,
FONT_BEGIN, 312, 666,
FONT_NEXT, 292, 639,
FONT_NEXT, 250, 676,
FONT_NEXT, 250, 650,
FONT_NEXT, 187, 666,
FONT_NEXT, 207, 639,
FONT_END, 175, 609,
FONT_BEGIN, 187, 666,
FONT_NEXT, 175, 609,
FONT_NEXT, 137, 638,
FONT_NEXT, 152, 566,
FONT_END, 137, 516,
FONT_BEGIN, 137, 638,
FONT_NEXT, 137, 516,
FONT_NEXT, 97, 597,
FONT_NEXT, 122, 409,
FONT_END, 120, 364,
FONT_BEGIN, 97, 597,
FONT_NEXT, 120, 364,
FONT_END, 120, 331,
FONT_BEGIN, 97, 597,
FONT_NEXT, 120, 331,
FONT_END, 120, 294,
FONT_BEGIN, 97, 597,
FONT_NEXT, 120, 294,
FONT_END, 97, 63,
FONT_BEGIN, 97, 597,
FONT_NEXT, 97, 63,
FONT_NEXT, 68, 547,
FONT_NEXT, 68, 113,
FONT_NEXT, 33, 434,
FONT_NEXT, 33, 226,
FONT_NEXT, 26, 379,
FONT_END, 24, 331,
FONT_BEGIN, 120, 294,
FONT_NEXT, 122, 246,
FONT_NEXT, 97, 63,
FONT_END, 137, 22,
FONT_BEGIN, 137, 22,
FONT_NEXT, 122, 246,
FONT_END, 137, 141,
FONT_BEGIN, 137, 22,
FONT_NEXT, 137, 141,
FONT_END, 152, 91,
FONT_BEGIN, 137, 22,
FONT_NEXT, 152, 91,
FONT_END, 175, 50,
FONT_BEGIN, 137, 22,
FONT_NEXT, 175, 50,
FONT_NEXT, 187, -5,
FONT_NEXT, 207, 22,
FONT_NEXT, 250, -14,
FONT_NEXT, 250, 12,
FONT_END, 292, 22,
FONT_BEGIN, 250, -14,
FONT_NEXT, 292, 22,
FONT_NEXT, 312, -5,
FONT_NEXT, 324, 50,
FONT_END, 347, 92,
FONT_BEGIN, 312, -5,
FONT_NEXT, 347, 92,
FONT_NEXT, 362, 22,
FONT_NEXT, 363, 141,
FONT_END, 377, 247,
FONT_BEGIN, 362, 22,
FONT_NEXT, 377, 247,
FONT_NEXT, 402, 63,
FONT_NEXT, 379, 294,
FONT_END, 380, 331,
FONT_BEGIN, 402, 63,
FONT_NEXT, 380, 331,
FONT_END, 379, 364,
FONT_BEGIN, 402, 63,
FONT_NEXT, 379, 364,
FONT_END, 377, 409,
FONT_ADVANCE, 500, 0
    },
    {
49,
FONT_BEGIN, 299, 674,
FONT_NEXT, 299, 74,
FONT_NEXT, 291, 676,
FONT_NEXT, 213, 93,
FONT_END, 213, 546,
FONT_BEGIN, 291, 676,
FONT_NEXT, 213, 546,
FONT_END, 208, 577,
FONT_BEGIN, 291, 676,
FONT_NEXT, 208, 577,
FONT_END, 198, 588,
FONT_BEGIN, 291, 676,
FONT_NEXT, 198, 588,
FONT_END, 179, 593,
FONT_BEGIN, 291, 676,
FONT_NEXT, 179, 593,
FONT_NEXT, 111, 585,
FONT_NEXT, 161, 590,
FONT_END, 143, 583,
FONT_BEGIN, 111, 585,
FONT_NEXT, 143, 583,
FONT_END, 111, 571,
FONT_BEGIN, 394, 15,
FONT_NEXT, 394, 0,
FONT_NEXT, 347, 17,
FONT_NEXT, 118, 0,
FONT_NEXT, 318, 26,
FONT_END, 303, 44,
FONT_BEGIN, 303, 44,
FONT_NEXT, 118, 0,
FONT_END, 187, 27,
FONT_BEGIN, 303, 44,
FONT_NEXT, 187, 27,
FONT_NEXT, 299, 74,
FONT_NEXT, 206, 49,
FONT_END, 213, 93,
FONT_BEGIN, 118, 15,
FONT_NEXT, 157, 17,
FONT_NEXT, 118, 0,
FONT_END, 187, 27,
FONT_ADVANCE, 500, 0
    },
    {
50,
FONT_BEGIN, 475, 137,
FONT_NEXT, 420, 0,
FONT_NEXT, 462, 142,
FONT_END, 435, 105,
FONT_BEGIN, 435, 105,
FONT_NEXT, 420, 0,
FONT_NEXT, 413, 85,
FONT_NEXT, 30, 0,
FONT_NEXT, 367, 76,
FONT_END, 128, 76,
FONT_BEGIN, 128, 76,
FONT_NEXT, 30, 0,
FONT_END, 30, 12,
FONT_BEGIN, 128, 76,
FONT_NEXT, 30, 12,
FONT_END, 208, 201,
FONT_BEGIN, 128, 76,
FONT_NEXT, 208, 201,
FONT_END, 265, 270,
FONT_BEGIN, 128, 76,
FONT_NEXT, 265, 270,
FONT_NEXT, 296, 252,
FONT_NEXT, 305, 336,
FONT_END, 329, 400,
FONT_BEGIN, 296, 252,
FONT_NEXT, 329, 400,
FONT_NEXT, 334, 295,
FONT_NEXT, 338, 459,
FONT_NEXT, 367, 628,
FONT_NEXT, 332, 502,
FONT_NEXT, 309, 663,
FONT_NEXT, 310, 549,
FONT_END, 267, 586,
FONT_BEGIN, 309, 663,
FONT_NEXT, 267, 586,
FONT_NEXT, 243, 676,
FONT_NEXT, 235, 597,
FONT_NEXT, 176, 666,
FONT_NEXT, 195, 602,
FONT_END, 137, 589,
FONT_BEGIN, 176, 666,
FONT_NEXT, 137, 589,
FONT_NEXT, 115, 633,
FONT_NEXT, 97, 557,
FONT_NEXT, 88, 606,
FONT_NEXT, 70, 515,
FONT_NEXT, 64, 571,
FONT_NEXT, 52, 472,
FONT_NEXT, 45, 528,
FONT_END, 31, 477,
FONT_BEGIN, 424, 496,
FONT_NEXT, 410, 419,
FONT_NEXT, 419, 535,
FONT_END, 408, 571,
FONT_BEGIN, 408, 571,
FONT_NEXT, 410, 419,
FONT_END, 376, 351,
FONT_BEGIN, 408, 571,
FONT_NEXT, 376, 351,
FONT_NEXT, 367, 628,
FONT_END, 334, 295,
FONT_ADVANCE, 500, 0
    },
    {
51,
FONT_BEGIN, 153, 330,
FONT_NEXT, 152, 343,
FONT_NEXT, 197, 330,
FONT_NEXT, 216, 368,
FONT_NEXT, 230, 326,
FONT_NEXT, 269, 403,
FONT_NEXT, 283, 308,
FONT_END, 304, 401,
FONT_BEGIN, 304, 401,
FONT_NEXT, 269, 403,
FONT_END, 304, 450,
FONT_BEGIN, 304, 401,
FONT_NEXT, 304, 450,
FONT_END, 318, 512,
FONT_BEGIN, 304, 401,
FONT_NEXT, 318, 512,
FONT_NEXT, 346, 433,
FONT_END, 319, 660,
FONT_BEGIN, 319, 660,
FONT_NEXT, 318, 512,
FONT_END, 311, 549,
FONT_BEGIN, 319, 660,
FONT_NEXT, 311, 549,
FONT_NEXT, 284, 671,
FONT_NEXT, 290, 583,
FONT_END, 256, 606,
FONT_BEGIN, 284, 671,
FONT_NEXT, 256, 606,
FONT_NEXT, 241, 676,
FONT_NEXT, 208, 616,
FONT_NEXT, 187, 669,
FONT_NEXT, 148, 603,
FONT_NEXT, 132, 644,
FONT_NEXT, 105, 573,
FONT_NEXT, 83, 594,
FONT_NEXT, 76, 538,
FONT_NEXT, 62, 558,
FONT_NEXT, 60, 510,
FONT_END, 45, 514,
FONT_BEGIN, 43, 43,
FONT_NEXT, 46, 60,
FONT_NEXT, 56, 12,
FONT_NEXT, 56, 71,
FONT_END, 82, 78,
FONT_BEGIN, 56, 12,
FONT_NEXT, 82, 78,
FONT_NEXT, 89, -5,
FONT_NEXT, 115, 69,
FONT_NEXT, 126, -13,
FONT_NEXT, 148, 50,
FONT_NEXT, 154, -14,
FONT_NEXT, 184, 30,
FONT_NEXT, 210, -10,
FONT_NEXT, 225, 22,
FONT_NEXT, 263, 1,
FONT_NEXT, 285, 35,
FONT_NEXT, 310, 21,
FONT_NEXT, 326, 70,
FONT_NEXT, 351, 47,
FONT_NEXT, 351, 120,
FONT_END, 359, 176,
FONT_BEGIN, 351, 47,
FONT_NEXT, 359, 176,
FONT_NEXT, 383, 351,
FONT_END, 341, 383,
FONT_BEGIN, 341, 383,
FONT_NEXT, 359, 176,
FONT_END, 356, 202,
FONT_BEGIN, 341, 383,
FONT_NEXT, 356, 202,
FONT_END, 345, 239,
FONT_BEGIN, 341, 383,
FONT_NEXT, 345, 239,
FONT_END, 321, 276,
FONT_BEGIN, 341, 383,
FONT_NEXT, 321, 276,
FONT_NEXT, 304, 401,
FONT_END, 283, 308,
FONT_BEGIN, 431, 216,
FONT_NEXT, 425, 165,
FONT_NEXT, 427, 261,
FONT_END, 417, 298,
FONT_BEGIN, 417, 298,
FONT_NEXT, 425, 165,
FONT_END, 409, 120,
FONT_BEGIN, 417, 298,
FONT_NEXT, 409, 120,
FONT_NEXT, 383, 351,
FONT_NEXT, 384, 80,
FONT_END, 351, 47,
FONT_BEGIN, 397, 541,
FONT_NEXT, 391, 502,
FONT_NEXT, 390, 579,
FONT_NEXT, 375, 466,
FONT_NEXT, 367, 623,
FONT_NEXT, 346, 433,
FONT_END, 319, 660,
FONT_ADVANCE, 500, 0
    },
    {
52,
FONT_BEGIN, 292, 571,
FONT_NEXT, 293, 167,
FONT_NEXT, 292, 231,
FONT_NEXT, 12, 167,
FONT_NEXT, 52, 231,
FONT_NEXT, 12, 231,
FONT_NEXT, 290, 571,
FONT_NEXT, 326, 676,
FONT_NEXT, 292, 571,
FONT_END, 293, 167,
FONT_BEGIN, 293, 167,
FONT_NEXT, 326, 676,
FONT_NEXT, 293, 0,
FONT_END, 370, 0,
FONT_BEGIN, 370, 0,
FONT_NEXT, 326, 676,
FONT_NEXT, 370, 167,
FONT_END, 370, 231,
FONT_BEGIN, 370, 231,
FONT_NEXT, 326, 676,
FONT_END, 370, 676,
FONT_BEGIN, 370, 231,
FONT_END, 370, 676,
FONT_BEGIN, 472, 231,
FONT_NEXT, 472, 167,
FONT_NEXT, 370, 231,
FONT_END, 370, 167,
FONT_ADVANCE, 500, 0
    },
    {
53,
FONT_BEGIN, 64, 415,
FONT_NEXT, 65, 425,
FONT_NEXT, 71, 412,
FONT_NEXT, 139, 498,
FONT_NEXT, 161, 399,
FONT_END, 254, 365,
FONT_BEGIN, 254, 365,
FONT_NEXT, 139, 498,
FONT_END, 263, 468,
FONT_BEGIN, 254, 365,
FONT_NEXT, 263, 468,
FONT_NEXT, 294, 337,
FONT_NEXT, 321, 439,
FONT_NEXT, 326, 300,
FONT_END, 348, 255,
FONT_BEGIN, 348, 255,
FONT_NEXT, 321, 439,
FONT_NEXT, 356, 201,
FONT_NEXT, 372, 395,
FONT_END, 351, 55,
FONT_BEGIN, 356, 201,
FONT_NEXT, 351, 55,
FONT_NEXT, 352, 158,
FONT_END, 343, 121,
FONT_BEGIN, 343, 121,
FONT_NEXT, 351, 55,
FONT_END, 312, 26,
FONT_BEGIN, 343, 121,
FONT_NEXT, 312, 26,
FONT_NEXT, 310, 66,
FONT_NEXT, 266, 4,
FONT_NEXT, 266, 33,
FONT_END, 217, 23,
FONT_BEGIN, 217, 23,
FONT_NEXT, 266, 4,
FONT_END, 214, -10,
FONT_BEGIN, 217, 23,
FONT_NEXT, 214, -10,
FONT_NEXT, 72, 85,
FONT_NEXT, 158, -14,
FONT_END, 104, -10,
FONT_BEGIN, 72, 85,
FONT_NEXT, 104, -10,
FONT_END, 65, 2,
FONT_BEGIN, 72, 85,
FONT_NEXT, 65, 2,
FONT_NEXT, 48, 80,
FONT_NEXT, 40, 21,
FONT_NEXT, 37, 69,
FONT_END, 32, 46,
FONT_BEGIN, 438, 681,
FONT_NEXT, 400, 592,
FONT_NEXT, 429, 688,
FONT_END, 411, 669,
FONT_BEGIN, 411, 669,
FONT_NEXT, 400, 592,
FONT_NEXT, 383, 662,
FONT_NEXT, 391, 584,
FONT_END, 377, 583,
FONT_BEGIN, 383, 662,
FONT_NEXT, 377, 583,
FONT_NEXT, 174, 662,
FONT_NEXT, 181, 583,
FONT_END, 139, 498,
FONT_BEGIN, 174, 662,
FONT_NEXT, 139, 498,
FONT_END, 65, 425,
FONT_BEGIN, 426, 225,
FONT_NEXT, 420, 176,
FONT_NEXT, 423, 281,
FONT_END, 415, 323,
FONT_BEGIN, 415, 323,
FONT_NEXT, 420, 176,
FONT_END, 406, 131,
FONT_BEGIN, 415, 323,
FONT_NEXT, 406, 131,
FONT_NEXT, 398, 359,
FONT_NEXT, 382, 90,
FONT_NEXT, 372, 395,
FONT_END, 351, 55,
FONT_ADVANCE, 500, 0
    },
    {
54,
FONT_BEGIN, 448, 668,
FONT_NEXT, 384, 652,
FONT_NEXT, 446, 684,
FONT_END, 358, 673,
FONT_BEGIN, 358, 673,
FONT_NEXT, 384, 652,
FONT_END, 329, 628,
FONT_BEGIN, 358, 673,
FONT_NEXT, 329, 628,
FONT_NEXT, 279, 647,
FONT_NEXT, 281, 597,
FONT_END, 240, 560,
FONT_BEGIN, 279, 647,
FONT_NEXT, 240, 560,
FONT_NEXT, 209, 609,
FONT_NEXT, 207, 519,
FONT_NEXT, 149, 560,
FONT_NEXT, 181, 475,
FONT_END, 152, 383,
FONT_BEGIN, 149, 560,
FONT_NEXT, 152, 383,
FONT_END, 147, 350,
FONT_BEGIN, 149, 560,
FONT_NEXT, 147, 350,
FONT_NEXT, 100, 502,
FONT_NEXT, 133, 321,
FONT_END, 128, 294,
FONT_BEGIN, 100, 502,
FONT_NEXT, 128, 294,
FONT_END, 127, 256,
FONT_BEGIN, 100, 502,
FONT_NEXT, 127, 256,
FONT_END, 99, 59,
FONT_BEGIN, 100, 502,
FONT_NEXT, 99, 59,
FONT_NEXT, 64, 436,
FONT_NEXT, 72, 102,
FONT_END, 51, 154,
FONT_BEGIN, 64, 436,
FONT_NEXT, 51, 154,
FONT_NEXT, 41, 365,
FONT_NEXT, 38, 217,
FONT_END, 34, 291,
FONT_BEGIN, 468, 218,
FONT_NEXT, 459, 150,
FONT_NEXT, 464, 265,
FONT_END, 454, 307,
FONT_BEGIN, 454, 307,
FONT_NEXT, 459, 150,
FONT_END, 427, 74,
FONT_BEGIN, 454, 307,
FONT_NEXT, 427, 74,
FONT_NEXT, 437, 343,
FONT_END, 415, 373,
FONT_BEGIN, 415, 373,
FONT_NEXT, 427, 74,
FONT_END, 399, 40,
FONT_BEGIN, 415, 373,
FONT_NEXT, 399, 40,
FONT_NEXT, 357, 414,
FONT_NEXT, 378, 179,
FONT_END, 371, 254,
FONT_BEGIN, 357, 414,
FONT_NEXT, 371, 254,
FONT_END, 349, 319,
FONT_BEGIN, 357, 414,
FONT_NEXT, 349, 319,
FONT_NEXT, 284, 428,
FONT_NEXT, 331, 344,
FONT_END, 307, 364,
FONT_BEGIN, 284, 428,
FONT_NEXT, 307, 364,
FONT_END, 278, 377,
FONT_BEGIN, 284, 428,
FONT_NEXT, 278, 377,
FONT_NEXT, 247, 425,
FONT_NEXT, 242, 382,
FONT_NEXT, 217, 417,
FONT_NEXT, 191, 375,
FONT_NEXT, 187, 403,
FONT_NEXT, 147, 350,
FONT_END, 152, 383,
FONT_BEGIN, 127, 256,
FONT_NEXT, 130, 197,
FONT_NEXT, 99, 59,
FONT_END, 132, 27,
FONT_BEGIN, 132, 27,
FONT_NEXT, 130, 197,
FONT_END, 138, 147,
FONT_BEGIN, 132, 27,
FONT_NEXT, 138, 147,
FONT_END, 152, 105,
FONT_BEGIN, 132, 27,
FONT_NEXT, 152, 105,
FONT_NEXT, 169, 4,
FONT_NEXT, 169, 72,
FONT_END, 191, 46,
FONT_BEGIN, 169, 4,
FONT_NEXT, 191, 46,
FONT_NEXT, 210, -10,
FONT_NEXT, 215, 28,
FONT_NEXT, 254, -14,
FONT_NEXT, 269, 14,
FONT_END, 312, 23,
FONT_BEGIN, 254, -14,
FONT_NEXT, 312, 23,
FONT_NEXT, 314, -8,
FONT_NEXT, 347, 54,
FONT_NEXT, 362, 11,
FONT_NEXT, 369, 105,
FONT_END, 378, 179,
FONT_BEGIN, 362, 11,
FONT_NEXT, 378, 179,
FONT_END, 399, 40,
FONT_ADVANCE, 500, 0
    },
    {
55,
FONT_BEGIN, 449, 662,
FONT_NEXT, 449, 646,
FONT_NEXT, 79, 662,
FONT_END, 370, 588,
FONT_BEGIN, 370, 588,
FONT_NEXT, 449, 646,
FONT_END, 237, -8,
FONT_BEGIN, 370, 588,
FONT_NEXT, 237, -8,
FONT_END, 172, -8,
FONT_BEGIN, 37, 507,
FONT_NEXT, 20, 515,
FONT_NEXT, 59, 541,
FONT_NEXT, 63, 618,
FONT_END, 79, 662,
FONT_BEGIN, 59, 541,
FONT_NEXT, 79, 662,
FONT_NEXT, 83, 567,
FONT_END, 112, 582,
FONT_BEGIN, 112, 582,
FONT_NEXT, 79, 662,
FONT_NEXT, 153, 588,
FONT_END, 370, 588,
FONT_ADVANCE, 500, 0
    },
    {
56,
FONT_BEGIN, 136, 547,
FONT_NEXT, 150, 492,
FONT_NEXT, 131, 381,
FONT_NEXT, 185, 447,
FONT_NEXT, 186, 332,
FONT_END, 212, 312,
FONT_BEGIN, 212, 312,
FONT_NEXT, 185, 447,
FONT_END, 226, 412,
FONT_BEGIN, 212, 312,
FONT_NEXT, 226, 412,
FONT_END, 261, 389,
FONT_BEGIN, 212, 312,
FONT_NEXT, 261, 389,
FONT_NEXT, 285, 258,
FONT_END, 290, 371,
FONT_BEGIN, 290, 371,
FONT_NEXT, 261, 389,
FONT_END, 291, 410,
FONT_BEGIN, 290, 371,
FONT_NEXT, 291, 410,
FONT_END, 322, 440,
FONT_BEGIN, 290, 371,
FONT_NEXT, 322, 440,
FONT_NEXT, 333, 395,
FONT_NEXT, 345, 481,
FONT_END, 355, 539,
FONT_BEGIN, 333, 395,
FONT_NEXT, 355, 539,
FONT_NEXT, 377, 428,
FONT_NEXT, 348, 579,
FONT_NEXT, 378, 633,
FONT_END, 324, 664,
FONT_BEGIN, 324, 664,
FONT_NEXT, 348, 579,
FONT_END, 327, 614,
FONT_BEGIN, 324, 664,
FONT_NEXT, 327, 614,
FONT_END, 292, 638,
FONT_BEGIN, 324, 664,
FONT_NEXT, 292, 638,
FONT_NEXT, 249, 676,
FONT_NEXT, 243, 648,
FONT_NEXT, 183, 665,
FONT_NEXT, 194, 638,
FONT_END, 161, 615,
FONT_BEGIN, 183, 665,
FONT_NEXT, 161, 615,
FONT_NEXT, 123, 636,
FONT_NEXT, 142, 583,
FONT_END, 136, 547,
FONT_BEGIN, 123, 636,
FONT_NEXT, 136, 547,
FONT_END, 131, 381,
FONT_BEGIN, 123, 636,
FONT_NEXT, 131, 381,
FONT_END, 92, 423,
FONT_BEGIN, 123, 636,
FONT_NEXT, 92, 423,
FONT_NEXT, 79, 587,
FONT_NEXT, 69, 467,
FONT_NEXT, 66, 556,
FONT_END, 62, 521,
FONT_BEGIN, 56, 146,
FONT_NEXT, 63, 202,
FONT_NEXT, 68, 88,
FONT_NEXT, 87, 246,
FONT_NEXT, 104, 36,
FONT_NEXT, 128, 286,
FONT_NEXT, 132, 156,
FONT_END, 140, 215,
FONT_BEGIN, 140, 215,
FONT_NEXT, 128, 286,
FONT_NEXT, 160, 258,
FONT_END, 186, 289,
FONT_BEGIN, 186, 289,
FONT_NEXT, 128, 286,
FONT_END, 186, 332,
FONT_BEGIN, 186, 289,
FONT_NEXT, 186, 332,
FONT_END, 212, 312,
FONT_BEGIN, 285, 258,
FONT_NEXT, 290, 371,
FONT_NEXT, 333, 216,
FONT_NEXT, 335, 336,
FONT_NEXT, 360, 174,
FONT_END, 369, 126,
FONT_BEGIN, 369, 126,
FONT_NEXT, 335, 336,
FONT_END, 370, 305,
FONT_BEGIN, 369, 126,
FONT_NEXT, 370, 305,
FONT_END, 381, 24,
FONT_BEGIN, 369, 126,
FONT_NEXT, 381, 24,
FONT_END, 347, 4,
FONT_BEGIN, 369, 126,
FONT_NEXT, 347, 4,
FONT_NEXT, 360, 78,
FONT_END, 337, 43,
FONT_BEGIN, 337, 43,
FONT_NEXT, 347, 4,
FONT_END, 302, -10,
FONT_BEGIN, 337, 43,
FONT_NEXT, 302, -10,
FONT_NEXT, 302, 21,
FONT_END, 259, 14,
FONT_BEGIN, 259, 14,
FONT_NEXT, 302, -10,
FONT_END, 246, -14,
FONT_BEGIN, 259, 14,
FONT_NEXT, 246, -14,
FONT_NEXT, 204, 25,
FONT_NEXT, 202, -11,
FONT_NEXT, 165, 56,
FONT_NEXT, 164, 0,
FONT_NEXT, 140, 101,
FONT_NEXT, 104, 36,
FONT_END, 132, 156,
FONT_BEGIN, 445, 150,
FONT_NEXT, 437, 102,
FONT_NEXT, 439, 199,
FONT_END, 417, 249,
FONT_BEGIN, 417, 249,
FONT_NEXT, 437, 102,
FONT_END, 407, 48,
FONT_BEGIN, 417, 249,
FONT_NEXT, 407, 48,
FONT_NEXT, 370, 305,
FONT_END, 381, 24,
FONT_BEGIN, 424, 534,
FONT_NEXT, 410, 473,
FONT_NEXT, 412, 588,
FONT_END, 378, 633,
FONT_BEGIN, 378, 633,
FONT_NEXT, 410, 473,
FONT_END, 377, 428,
FONT_BEGIN, 378, 633,
FONT_END, 377, 428,
FONT_ADVANCE, 500, 0
    },
    {
57,
FONT_BEGIN, 459, 396,
FONT_NEXT, 452, 317,
FONT_NEXT, 454, 455,
FONT_END, 442, 510,
FONT_BEGIN, 442, 510,
FONT_NEXT, 452, 317,
FONT_END, 432, 243,
FONT_BEGIN, 442, 510,
FONT_NEXT, 432, 243,
FONT_NEXT, 422, 557,
FONT_NEXT, 399, 174,
FONT_NEXT, 395, 598,
FONT_NEXT, 354, 113,
FONT_END, 362, 355,
FONT_BEGIN, 395, 598,
FONT_NEXT, 362, 355,
FONT_NEXT, 363, 631,
FONT_NEXT, 362, 394,
FONT_END, 361, 421,
FONT_BEGIN, 363, 631,
FONT_NEXT, 361, 421,
FONT_END, 359, 458,
FONT_BEGIN, 363, 631,
FONT_NEXT, 359, 458,
FONT_END, 345, 543,
FONT_BEGIN, 363, 631,
FONT_NEXT, 345, 543,
FONT_NEXT, 326, 655,
FONT_NEXT, 329, 583,
FONT_END, 305, 616,
FONT_BEGIN, 326, 655,
FONT_NEXT, 305, 616,
FONT_NEXT, 284, 670,
FONT_NEXT, 272, 639,
FONT_NEXT, 240, 676,
FONT_NEXT, 229, 648,
FONT_NEXT, 193, 671,
FONT_NEXT, 200, 643,
FONT_END, 176, 632,
FONT_BEGIN, 193, 671,
FONT_NEXT, 176, 632,
FONT_NEXT, 152, 657,
FONT_NEXT, 144, 591,
FONT_NEXT, 116, 634,
FONT_NEXT, 126, 534,
FONT_END, 122, 473,
FONT_BEGIN, 116, 634,
FONT_NEXT, 122, 473,
FONT_END, 94, 278,
FONT_BEGIN, 116, 634,
FONT_NEXT, 94, 278,
FONT_NEXT, 86, 605,
FONT_NEXT, 69, 305,
FONT_NEXT, 62, 569,
FONT_NEXT, 39, 369,
FONT_NEXT, 44, 528,
FONT_END, 30, 435,
FONT_BEGIN, 56, -2,
FONT_NEXT, 138, 21,
FONT_NEXT, 59, -22,
FONT_END, 149, -9,
FONT_BEGIN, 149, -9,
FONT_NEXT, 138, 21,
FONT_END, 204, 56,
FONT_BEGIN, 149, -9,
FONT_NEXT, 204, 56,
FONT_NEXT, 228, 20,
FONT_NEXT, 256, 101,
FONT_END, 296, 149,
FONT_BEGIN, 228, 20,
FONT_NEXT, 296, 149,
FONT_NEXT, 297, 61,
FONT_NEXT, 324, 197,
FONT_END, 343, 239,
FONT_BEGIN, 297, 61,
FONT_NEXT, 343, 239,
FONT_NEXT, 354, 113,
FONT_NEXT, 354, 271,
FONT_END, 359, 290,
FONT_BEGIN, 354, 113,
FONT_NEXT, 359, 290,
FONT_NEXT, 362, 355,
FONT_END, 358, 332,
FONT_BEGIN, 358, 332,
FONT_NEXT, 359, 290,
FONT_END, 357, 292,
FONT_BEGIN, 358, 332,
FONT_NEXT, 357, 292,
FONT_NEXT, 346, 315,
FONT_NEXT, 285, 250,
FONT_NEXT, 302, 291,
FONT_END, 250, 280,
FONT_BEGIN, 250, 280,
FONT_NEXT, 285, 250,
FONT_END, 210, 237,
FONT_BEGIN, 250, 280,
FONT_NEXT, 210, 237,
FONT_NEXT, 214, 285,
FONT_END, 185, 299,
FONT_BEGIN, 185, 299,
FONT_NEXT, 210, 237,
FONT_END, 163, 242,
FONT_BEGIN, 185, 299,
FONT_NEXT, 163, 242,
FONT_NEXT, 146, 348,
FONT_NEXT, 125, 256,
FONT_NEXT, 127, 410,
FONT_END, 122, 473,
FONT_BEGIN, 122, 473,
FONT_NEXT, 125, 256,
FONT_END, 94, 278,
FONT_BEGIN, 122, 473,
FONT_END, 94, 278,
FONT_ADVANCE, 500, 0
    },
    {
58,
FONT_BEGIN, 192, 43,
FONT_NEXT, 186, 18,
FONT_NEXT, 187, 64,
FONT_END, 175, 83,
FONT_BEGIN, 175, 83,
FONT_NEXT, 186, 18,
FONT_END, 171, 2,
FONT_BEGIN, 175, 83,
FONT_NEXT, 171, 2,
FONT_NEXT, 157, 95,
FONT_NEXT, 136, -11,
FONT_NEXT, 136, 100,
FONT_END, 114, 95,
FONT_BEGIN, 114, 95,
FONT_NEXT, 136, -11,
FONT_END, 100, 1,
FONT_BEGIN, 114, 95,
FONT_NEXT, 100, 1,
FONT_NEXT, 97, 83,
FONT_NEXT, 86, 18,
FONT_NEXT, 85, 65,
FONT_END, 81, 43,
FONT_BEGIN, 192, 402,
FONT_NEXT, 186, 377,
FONT_NEXT, 187, 423,
FONT_END, 175, 442,
FONT_BEGIN, 175, 442,
FONT_NEXT, 186, 377,
FONT_END, 171, 361,
FONT_BEGIN, 175, 442,
FONT_NEXT, 171, 361,
FONT_NEXT, 157, 454,
FONT_NEXT, 136, 348,
FONT_NEXT, 136, 459,
FONT_END, 114, 454,
FONT_BEGIN, 114, 454,
FONT_NEXT, 136, 348,
FONT_END, 100, 360,
FONT_BEGIN, 114, 454,
FONT_NEXT, 100, 360,
FONT_NEXT, 97, 442,
FONT_NEXT, 86, 377,
FONT_NEXT, 85, 424,
FONT_END, 81, 402,
FONT_ADVANCE, 278, 0
    },
    {
59,
FONT_BEGIN, 107, -141,
FONT_NEXT, 98, -122,
FONT_NEXT, 138, -123,
FONT_NEXT, 138, -91,
FONT_END, 163, -61,
FONT_BEGIN, 138, -123,
FONT_NEXT, 163, -61,
FONT_NEXT, 175, -90,
FONT_NEXT, 176, -35,
FONT_END, 180, -16,
FONT_BEGIN, 175, -90,
FONT_NEXT, 180, -16,
FONT_NEXT, 206, -45,
FONT_END, 186, 85,
FONT_BEGIN, 186, 85,
FONT_NEXT, 180, -16,
FONT_NEXT, 160, 98,
FONT_NEXT, 175, -6,
FONT_END, 166, -2,
FONT_BEGIN, 160, 98,
FONT_NEXT, 166, -2,
FONT_END, 157, -4,
FONT_BEGIN, 160, 98,
FONT_NEXT, 157, -4,
FONT_NEXT, 139, 102,
FONT_NEXT, 142, -6,
FONT_END, 99, 5,
FONT_BEGIN, 139, 102,
FONT_NEXT, 99, 5,
FONT_NEXT, 125, 100,
FONT_END, 105, 93,
FONT_BEGIN, 105, 93,
FONT_NEXT, 99, 5,
FONT_NEXT, 87, 76,
FONT_NEXT, 85, 20,
FONT_END, 80, 44,
FONT_BEGIN, 219, 6,
FONT_NEXT, 206, -45,
FONT_NEXT, 209, 56,
FONT_END, 186, 85,
FONT_BEGIN, 192, 402,
FONT_NEXT, 186, 377,
FONT_NEXT, 187, 423,
FONT_END, 175, 442,
FONT_BEGIN, 175, 442,
FONT_NEXT, 186, 377,
FONT_END, 171, 361,
FONT_BEGIN, 175, 442,
FONT_NEXT, 171, 361,
FONT_NEXT, 157, 454,
FONT_NEXT, 136, 348,
FONT_NEXT, 136, 459,
FONT_END, 114, 454,
FONT_BEGIN, 114, 454,
FONT_NEXT, 136, 348,
FONT_END, 100, 360,
FONT_BEGIN, 114, 454,
FONT_NEXT, 100, 360,
FONT_NEXT, 97, 442,
FONT_NEXT, 86, 377,
FONT_NEXT, 85, 424,
FONT_END, 81, 402,
FONT_ADVANCE, 278, 0
    },
    {
60,
FONT_BEGIN, 536, 514,
FONT_NEXT, 536, 446,
FONT_NEXT, 28, 284,
FONT_NEXT, 111, 253,
FONT_NEXT, 28, 222,
FONT_END, 536, -8,
FONT_BEGIN, 536, -8,
FONT_NEXT, 111, 253,
FONT_END, 536, 60,
FONT_BEGIN, 536, -8,
FONT_END, 536, 60,
FONT_ADVANCE, 564, 0
    },
    {
61,
FONT_BEGIN, 534, 186,
FONT_NEXT, 534, 120,
FONT_NEXT, 30, 186,
FONT_END, 30, 120,
FONT_BEGIN, 534, 386,
FONT_NEXT, 534, 320,
FONT_NEXT, 30, 386,
FONT_END, 30, 320,
FONT_ADVANCE, 564, 0
    },
    {
62,
FONT_BEGIN, 28, 446,
FONT_NEXT, 28, 514,
FONT_NEXT, 453, 253,
FONT_NEXT, 536, 222,
FONT_END, 28, -8,
FONT_BEGIN, 453, 253,
FONT_NEXT, 28, -8,
FONT_END, 28, 60,
FONT_BEGIN, 536, 222,
FONT_NEXT, 28, 514,
FONT_END, 536, 284,
FONT_ADVANCE, 564, 0
    },
    {
63,
FONT_BEGIN, 68, 532,
FONT_NEXT, 72, 567,
FONT_NEXT, 78, 487,
FONT_NEXT, 83, 597,
FONT_NEXT, 93, 474,
FONT_END, 118, 469,
FONT_BEGIN, 118, 469,
FONT_NEXT, 83, 597,
FONT_END, 119, 585,
FONT_BEGIN, 118, 469,
FONT_NEXT, 119, 585,
FONT_NEXT, 135, 473,
FONT_END, 148, 483,
FONT_BEGIN, 148, 483,
FONT_NEXT, 119, 585,
FONT_END, 157, 510,
FONT_BEGIN, 148, 483,
FONT_END, 157, 510,
FONT_BEGIN, 414, 510,
FONT_NEXT, 406, 469,
FONT_NEXT, 412, 544,
FONT_END, 406, 571,
FONT_BEGIN, 406, 571,
FONT_NEXT, 406, 469,
FONT_NEXT, 378, 618,
FONT_NEXT, 387, 426,
FONT_END, 363, 385,
FONT_BEGIN, 378, 618,
FONT_NEXT, 363, 385,
FONT_NEXT, 352, 639,
FONT_NEXT, 339, 354,
FONT_NEXT, 316, 657,
FONT_NEXT, 322, 519,
FONT_END, 315, 564,
FONT_BEGIN, 316, 657,
FONT_NEXT, 315, 564,
FONT_NEXT, 274, 671,
FONT_NEXT, 295, 605,
FONT_END, 261, 634,
FONT_BEGIN, 274, 671,
FONT_NEXT, 261, 634,
FONT_NEXT, 231, 676,
FONT_NEXT, 212, 646,
FONT_NEXT, 176, 667,
FONT_NEXT, 179, 641,
FONT_END, 149, 627,
FONT_BEGIN, 176, 667,
FONT_NEXT, 149, 627,
FONT_NEXT, 123, 641,
FONT_NEXT, 127, 608,
FONT_END, 119, 585,
FONT_BEGIN, 123, 641,
FONT_NEXT, 119, 585,
FONT_END, 83, 597,
FONT_BEGIN, 227, 164,
FONT_NEXT, 239, 251,
FONT_NEXT, 244, 164,
FONT_NEXT, 249, 286,
FONT_NEXT, 257, 220,
FONT_NEXT, 257, 306,
FONT_NEXT, 276, 267,
FONT_NEXT, 285, 370,
FONT_NEXT, 302, 309,
FONT_NEXT, 310, 444,
FONT_END, 322, 519,
FONT_BEGIN, 302, 309,
FONT_NEXT, 322, 519,
FONT_END, 339, 354,
FONT_BEGIN, 292, 43,
FONT_NEXT, 286, 19,
FONT_NEXT, 287, 63,
FONT_END, 276, 80,
FONT_BEGIN, 276, 80,
FONT_NEXT, 286, 19,
FONT_END, 273, 4,
FONT_BEGIN, 276, 80,
FONT_NEXT, 273, 4,
FONT_NEXT, 258, 92,
FONT_NEXT, 237, -8,
FONT_NEXT, 237, 97,
FONT_END, 216, 92,
FONT_BEGIN, 216, 92,
FONT_NEXT, 237, -8,
FONT_END, 202, 3,
FONT_BEGIN, 216, 92,
FONT_NEXT, 202, 3,
FONT_NEXT, 199, 81,
FONT_NEXT, 189, 19,
FONT_NEXT, 188, 64,
FONT_END, 184, 43,
FONT_ADVANCE, 444, 0
    },
    {
64,
FONT_BEGIN, 321, 268,
FONT_NEXT, 323, 298,
FONT_NEXT, 323, 230,
FONT_END, 332, 200,
FONT_BEGIN, 332, 200,
FONT_NEXT, 323, 298,
FONT_END, 332, 335,
FONT_BEGIN, 332, 200,
FONT_NEXT, 332, 335,
FONT_NEXT, 358, 163,
FONT_NEXT, 368, 414,
FONT_NEXT, 390, 147,
FONT_NEXT, 392, 271,
FONT_END, 396, 232,
FONT_BEGIN, 390, 147,
FONT_NEXT, 396, 232,
FONT_END, 410, 206,
FONT_BEGIN, 390, 147,
FONT_NEXT, 410, 206,
FONT_NEXT, 417, 144,
FONT_NEXT, 430, 191,
FONT_END, 455, 187,
FONT_BEGIN, 417, 144,
FONT_NEXT, 455, 187,
FONT_NEXT, 457, 154,
FONT_NEXT, 489, 200,
FONT_NEXT, 492, 174,
FONT_END, 517, 196,
FONT_BEGIN, 517, 196,
FONT_NEXT, 489, 200,
FONT_END, 524, 240,
FONT_BEGIN, 517, 196,
FONT_NEXT, 524, 240,
FONT_NEXT, 532, 214,
FONT_END, 534, 214,
FONT_BEGIN, 534, 214,
FONT_NEXT, 524, 240,
FONT_END, 553, 173,
FONT_BEGIN, 534, 214,
FONT_NEXT, 553, 173,
FONT_END, 538, 197,
FONT_BEGIN, 668, 494,
FONT_NEXT, 603, 240,
FONT_NEXT, 599, 494,
FONT_NEXT, 603, 202,
FONT_END, 581, 152,
FONT_BEGIN, 599, 494,
FONT_NEXT, 581, 152,
FONT_NEXT, 589, 456,
FONT_NEXT, 572, 399,
FONT_NEXT, 565, 491,
FONT_NEXT, 569, 426,
FONT_END, 561, 447,
FONT_BEGIN, 565, 491,
FONT_NEXT, 561, 447,
FONT_NEXT, 546, 503,
FONT_NEXT, 548, 459,
FONT_END, 529, 464,
FONT_BEGIN, 546, 503,
FONT_NEXT, 529, 464,
FONT_NEXT, 518, 508,
FONT_NEXT, 487, 450,
FONT_NEXT, 470, 500,
FONT_NEXT, 442, 412,
FONT_NEXT, 430, 480,
FONT_NEXT, 406, 352,
FONT_NEXT, 396, 450,
FONT_NEXT, 395, 314,
FONT_NEXT, 368, 414,
FONT_END, 392, 271,
FONT_BEGIN, 116, 315,
FONT_NEXT, 123, 392,
FONT_NEXT, 121, 262,
FONT_END, 137, 207,
FONT_BEGIN, 137, 207,
FONT_NEXT, 123, 392,
FONT_END, 145, 462,
FONT_BEGIN, 137, 207,
FONT_NEXT, 145, 462,
FONT_NEXT, 165, 152,
FONT_NEXT, 179, 524,
FONT_NEXT, 200, 305,
FONT_END, 205, 383,
FONT_BEGIN, 205, 383,
FONT_NEXT, 179, 524,
FONT_NEXT, 222, 451,
FONT_NEXT, 224, 576,
FONT_NEXT, 248, 510,
FONT_NEXT, 278, 618,
FONT_NEXT, 283, 558,
FONT_END, 324, 595,
FONT_BEGIN, 324, 595,
FONT_NEXT, 278, 618,
FONT_END, 340, 649,
FONT_BEGIN, 324, 595,
FONT_NEXT, 340, 649,
FONT_NEXT, 372, 623,
FONT_NEXT, 408, 669,
FONT_NEXT, 424, 639,
FONT_END, 481, 645,
FONT_BEGIN, 481, 645,
FONT_NEXT, 408, 669,
FONT_END, 481, 676,
FONT_BEGIN, 481, 645,
FONT_NEXT, 481, 676,
FONT_NEXT, 545, 638,
FONT_NEXT, 547, 670,
FONT_NEXT, 603, 619,
FONT_NEXT, 609, 653,
FONT_NEXT, 652, 591,
FONT_NEXT, 665, 627,
FONT_NEXT, 693, 555,
FONT_NEXT, 714, 591,
FONT_NEXT, 726, 513,
FONT_END, 749, 469,
FONT_BEGIN, 749, 469,
FONT_NEXT, 714, 591,
FONT_END, 753, 548,
FONT_BEGIN, 749, 469,
FONT_NEXT, 753, 548,
FONT_NEXT, 764, 424,
FONT_END, 769, 380,
FONT_BEGIN, 769, 380,
FONT_NEXT, 753, 548,
FONT_END, 783, 498,
FONT_BEGIN, 769, 380,
FONT_NEXT, 783, 498,
FONT_END, 760, 223,
FONT_BEGIN, 769, 380,
FONT_NEXT, 760, 223,
FONT_NEXT, 760, 311,
FONT_END, 734, 245,
FONT_BEGIN, 734, 245,
FONT_NEXT, 760, 223,
FONT_END, 734, 191,
FONT_BEGIN, 734, 245,
FONT_NEXT, 734, 191,
FONT_NEXT, 693, 195,
FONT_NEXT, 702, 165,
FONT_END, 665, 149,
FONT_BEGIN, 693, 195,
FONT_NEXT, 665, 149,
FONT_NEXT, 667, 181,
FONT_END, 639, 176,
FONT_BEGIN, 639, 176,
FONT_NEXT, 665, 149,
FONT_END, 623, 143,
FONT_BEGIN, 639, 176,
FONT_NEXT, 623, 143,
FONT_NEXT, 613, 183,
FONT_NEXT, 581, 152,
FONT_END, 603, 202,
FONT_BEGIN, 700, 43,
FONT_NEXT, 601, 3,
FONT_NEXT, 688, 73,
FONT_END, 601, 39,
FONT_BEGIN, 601, 39,
FONT_NEXT, 601, 3,
FONT_NEXT, 550, 28,
FONT_NEXT, 547, -10,
FONT_NEXT, 490, 25,
FONT_NEXT, 491, -14,
FONT_END, 399, -6,
FONT_BEGIN, 490, 25,
FONT_NEXT, 399, -6,
FONT_NEXT, 427, 31,
FONT_END, 371, 48,
FONT_BEGIN, 371, 48,
FONT_NEXT, 399, -6,
FONT_END, 322, 18,
FONT_BEGIN, 371, 48,
FONT_NEXT, 322, 18,
FONT_NEXT, 321, 75,
FONT_NEXT, 257, 54,
FONT_NEXT, 279, 111,
FONT_END, 245, 153,
FONT_BEGIN, 245, 153,
FONT_NEXT, 257, 54,
FONT_END, 205, 100,
FONT_BEGIN, 245, 153,
FONT_NEXT, 205, 100,
FONT_NEXT, 220, 200,
FONT_END, 205, 251,
FONT_BEGIN, 205, 251,
FONT_NEXT, 205, 100,
FONT_NEXT, 200, 305,
FONT_END, 165, 152,
FONT_BEGIN, 809, 381,
FONT_NEXT, 796, 300,
FONT_NEXT, 802, 442,
FONT_END, 783, 498,
FONT_BEGIN, 783, 498,
FONT_NEXT, 796, 300,
FONT_END, 760, 223,
FONT_BEGIN, 783, 498,
FONT_END, 760, 223,
FONT_BEGIN, 524, 240,
FONT_NEXT, 554, 306,
FONT_NEXT, 553, 173,
FONT_NEXT, 572, 399,
FONT_END, 581, 152,

FONT_ADVANCE, 921, 0
    },
    {
65,
FONT_BEGIN, 451, 19,
FONT_NEXT, 499, 24,
FONT_NEXT, 451, 0,
FONT_END, 637, 62,
FONT_BEGIN, 637, 62,
FONT_NEXT, 499, 24,
FONT_END, 515, 35,
FONT_BEGIN, 637, 62,
FONT_NEXT, 515, 35,
FONT_NEXT, 616, 106,
FONT_NEXT, 521, 57,
FONT_NEXT, 367, 674,
FONT_NEXT, 502, 120,
FONT_END, 461, 216,
FONT_BEGIN, 367, 674,
FONT_NEXT, 461, 216,
FONT_END, 447, 257,
FONT_BEGIN, 367, 674,
FONT_NEXT, 447, 257,
FONT_END, 331, 532,
FONT_BEGIN, 367, 674,
FONT_NEXT, 331, 532,
FONT_NEXT, 347, 674,
FONT_END, 139, 183,
FONT_BEGIN, 139, 183,
FONT_NEXT, 331, 532,
FONT_END, 216, 257,
FONT_BEGIN, 139, 183,
FONT_NEXT, 216, 257,
FONT_END, 199, 216,
FONT_BEGIN, 139, 183,
FONT_NEXT, 199, 216,
FONT_END, 153, 99,
FONT_BEGIN, 139, 183,
FONT_NEXT, 153, 99,
FONT_END, 145, 61,
FONT_BEGIN, 139, 183,
FONT_NEXT, 145, 61,
FONT_NEXT, 104, 104,
FONT_END, 84, 67,
FONT_BEGIN, 84, 67,
FONT_NEXT, 145, 61,
FONT_NEXT, 66, 41,
FONT_NEXT, 15, 0,
FONT_NEXT, 39, 23,
FONT_END, 15, 19,
FONT_BEGIN, 706, 19,
FONT_NEXT, 706, 0,
FONT_NEXT, 677, 23,
FONT_NEXT, 451, 0,
FONT_NEXT, 656, 36,
FONT_END, 637, 62,
FONT_BEGIN, 216, 257,
FONT_NEXT, 447, 257,
FONT_NEXT, 199, 216,
FONT_END, 461, 216,
FONT_BEGIN, 213, 19,
FONT_NEXT, 213, 0,
FONT_NEXT, 171, 24,
FONT_NEXT, 15, 0,
FONT_NEXT, 152, 36,
FONT_END, 145, 61,
FONT_ADVANCE, 722, 0
    },
    {
66,
FONT_BEGIN, 17, 19,
FONT_NEXT, 67, 23,
FONT_NEXT, 17, 0,
FONT_END, 215, 78,
FONT_BEGIN, 215, 78,
FONT_NEXT, 67, 23,
FONT_END, 96, 38,
FONT_BEGIN, 215, 78,
FONT_NEXT, 96, 38,
FONT_END, 109, 65,
FONT_BEGIN, 215, 78,
FONT_NEXT, 109, 65,
FONT_END, 113, 109,
FONT_BEGIN, 215, 78,
FONT_NEXT, 113, 109,
FONT_END, 113, 553,
FONT_BEGIN, 215, 78,
FONT_NEXT, 113, 553,
FONT_END, 109, 597,
FONT_BEGIN, 215, 78,
FONT_NEXT, 109, 597,
FONT_NEXT, 215, 326,
FONT_END, 215, 365,
FONT_BEGIN, 215, 365,
FONT_NEXT, 109, 597,
FONT_NEXT, 215, 595,
FONT_NEXT, 95, 624,
FONT_NEXT, 17, 662,
FONT_NEXT, 66, 637,
FONT_END, 17, 643,
FONT_BEGIN, 215, 326,
FONT_NEXT, 215, 365,
FONT_NEXT, 276, 325,
FONT_NEXT, 310, 366,
FONT_NEXT, 319, 322,
FONT_NEXT, 344, 368,
FONT_NEXT, 377, 311,
FONT_NEXT, 394, 381,
FONT_NEXT, 414, 295,
FONT_NEXT, 426, 347,
FONT_NEXT, 447, 270,
FONT_END, 469, 232,
FONT_BEGIN, 469, 232,
FONT_NEXT, 426, 347,
FONT_END, 488, 327,
FONT_BEGIN, 469, 232,
FONT_NEXT, 488, 327,
FONT_NEXT, 478, 180,
FONT_NEXT, 482, 22,
FONT_NEXT, 472, 135,
FONT_NEXT, 425, 6,
FONT_NEXT, 458, 101,
FONT_END, 436, 75,
FONT_BEGIN, 436, 75,
FONT_NEXT, 425, 6,
FONT_NEXT, 409, 58,
FONT_NEXT, 351, 0,
FONT_NEXT, 348, 40,
FONT_NEXT, 17, 0,
FONT_NEXT, 291, 37,
FONT_END, 254, 38,
FONT_BEGIN, 254, 38,
FONT_NEXT, 17, 0,
FONT_NEXT, 231, 43,
FONT_END, 218, 55,
FONT_BEGIN, 218, 55,
FONT_NEXT, 17, 0,
FONT_END, 215, 78,
FONT_BEGIN, 218, 55,
FONT_END, 215, 78,
FONT_BEGIN, 215, 595,
FONT_NEXT, 17, 662,
FONT_NEXT, 218, 615,
FONT_END, 237, 624,
FONT_BEGIN, 237, 624,
FONT_NEXT, 17, 662,
FONT_NEXT, 255, 624,
FONT_END, 282, 625,
FONT_BEGIN, 282, 625,
FONT_NEXT, 17, 662,
FONT_END, 297, 662,
FONT_BEGIN, 282, 625,
FONT_NEXT, 297, 662,
FONT_NEXT, 339, 620,
FONT_NEXT, 374, 657,
FONT_NEXT, 396, 600,
FONT_END, 420, 582,
FONT_BEGIN, 420, 582,
FONT_NEXT, 374, 657,
FONT_NEXT, 439, 558,
FONT_NEXT, 460, 635,
FONT_NEXT, 452, 527,
FONT_END, 457, 487,
FONT_BEGIN, 457, 487,
FONT_NEXT, 460, 635,
FONT_END, 465, 360,
FONT_BEGIN, 457, 487,
FONT_NEXT, 465, 360,
FONT_NEXT, 451, 447,
FONT_NEXT, 426, 349,
FONT_NEXT, 437, 417,
FONT_END, 394, 381,
FONT_BEGIN, 394, 381,
FONT_NEXT, 426, 349,
FONT_END, 426, 347,
FONT_BEGIN, 394, 381,
FONT_END, 426, 347,
FONT_BEGIN, 559, 493,
FONT_NEXT, 555, 455,
FONT_NEXT, 551, 544,
FONT_NEXT, 544, 425,
FONT_NEXT, 530, 584,
FONT_NEXT, 509, 383,
FONT_NEXT, 499, 614,
FONT_NEXT, 465, 360,
FONT_END, 460, 635,
FONT_BEGIN, 593, 176,
FONT_NEXT, 586, 132,
FONT_NEXT, 589, 212,
FONT_END, 579, 244,
FONT_BEGIN, 579, 244,
FONT_NEXT, 586, 132,
FONT_END, 574, 103,
FONT_BEGIN, 579, 244,
FONT_NEXT, 574, 103,
FONT_NEXT, 542, 294,
FONT_NEXT, 555, 73,
FONT_END, 525, 45,
FONT_BEGIN, 542, 294,
FONT_NEXT, 525, 45,
FONT_NEXT, 488, 327,
FONT_END, 482, 22,
FONT_ADVANCE, 667, 0
    },
    {
67,
FONT_BEGIN, 620, 451,
FONT_NEXT, 597, 451,
FONT_NEXT, 611, 676,
FONT_END, 590, 676,
FONT_BEGIN, 590, 676,
FONT_NEXT, 597, 451,
FONT_END, 569, 524,
FONT_BEGIN, 590, 676,
FONT_NEXT, 569, 524,
FONT_NEXT, 580, 656,
FONT_END, 565, 647,
FONT_BEGIN, 565, 647,
FONT_NEXT, 569, 524,
FONT_END, 523, 582,
FONT_BEGIN, 565, 647,
FONT_NEXT, 523, 582,
FONT_NEXT, 544, 643,
FONT_END, 519, 648,
FONT_BEGIN, 519, 648,
FONT_NEXT, 523, 582,
FONT_END, 459, 621,
FONT_BEGIN, 519, 648,
FONT_NEXT, 459, 621,
FONT_NEXT, 481, 659,
FONT_END, 429, 670,
FONT_BEGIN, 429, 670,
FONT_NEXT, 459, 621,
FONT_END, 420, 632,
FONT_BEGIN, 429, 670,
FONT_NEXT, 420, 632,
FONT_NEXT, 368, 676,
FONT_NEXT, 377, 636,
FONT_END, 299, 620,
FONT_BEGIN, 368, 676,
FONT_NEXT, 299, 620,
FONT_NEXT, 304, 670,
FONT_END, 242, 653,
FONT_BEGIN, 242, 653,
FONT_NEXT, 299, 620,
FONT_END, 261, 601,
FONT_BEGIN, 242, 653,
FONT_NEXT, 261, 601,
FONT_END, 227, 575,
FONT_BEGIN, 242, 653,
FONT_NEXT, 227, 575,
FONT_NEXT, 184, 625,
FONT_NEXT, 197, 540,
FONT_END, 170, 491,
FONT_BEGIN, 184, 625,
FONT_NEXT, 170, 491,
FONT_NEXT, 133, 587,
FONT_NEXT, 151, 422,
FONT_END, 144, 329,
FONT_BEGIN, 133, 587,
FONT_NEXT, 144, 329,
FONT_END, 134, 65,
FONT_BEGIN, 133, 587,
FONT_NEXT, 134, 65,
FONT_END, 91, 112,
FONT_BEGIN, 133, 587,
FONT_NEXT, 91, 112,
FONT_NEXT, 90, 538,
FONT_NEXT, 57, 172,
FONT_NEXT, 56, 479,
FONT_NEXT, 35, 244,
FONT_NEXT, 35, 410,
FONT_END, 28, 331,
FONT_BEGIN, 633, 113,
FONT_NEXT, 628, 106,
FONT_NEXT, 615, 131,
FONT_NEXT, 616, 92,
FONT_END, 595, 71,
FONT_BEGIN, 615, 131,
FONT_NEXT, 595, 71,
FONT_NEXT, 583, 102,
FONT_NEXT, 566, 48,
FONT_NEXT, 536, 69,
FONT_NEXT, 528, 25,
FONT_NEXT, 471, 41,
FONT_NEXT, 481, 5,
FONT_END, 426, -9,
FONT_BEGIN, 471, 41,
FONT_NEXT, 426, -9,
FONT_NEXT, 389, 30,
FONT_NEXT, 362, -14,
FONT_NEXT, 342, 34,
FONT_NEXT, 301, -10,
FONT_NEXT, 297, 46,
FONT_NEXT, 241, 4,
FONT_NEXT, 256, 68,
FONT_END, 219, 100,
FONT_BEGIN, 219, 100,
FONT_NEXT, 241, 4,
FONT_END, 185, 29,
FONT_BEGIN, 219, 100,
FONT_NEXT, 185, 29,
FONT_NEXT, 188, 141,
FONT_END, 164, 192,
FONT_BEGIN, 164, 192,
FONT_NEXT, 185, 29,
FONT_END, 134, 65,
FONT_BEGIN, 164, 192,
FONT_NEXT, 134, 65,
FONT_NEXT, 149, 255,
FONT_END, 144, 329,
FONT_ADVANCE, 667, 0
    },
    {
68,
FONT_BEGIN, 16, 643,
FONT_NEXT, 16, 662,
FONT_NEXT, 62, 636,
FONT_END, 88, 623,
FONT_BEGIN, 88, 623,
FONT_NEXT, 16, 662,
FONT_END, 206, 586,
FONT_BEGIN, 88, 623,
FONT_NEXT, 206, 586,
FONT_NEXT, 101, 597,
FONT_NEXT, 206, 78,
FONT_NEXT, 104, 553,
FONT_END, 104, 109,
FONT_BEGIN, 104, 109,
FONT_NEXT, 206, 78,
FONT_NEXT, 100, 65,
FONT_END, 88, 39,
FONT_BEGIN, 88, 39,
FONT_NEXT, 206, 78,
FONT_NEXT, 61, 24,
FONT_NEXT, 16, 0,
FONT_END, 16, 19,
FONT_BEGIN, 685, 329,
FONT_NEXT, 681, 276,
FONT_NEXT, 677, 407,
FONT_NEXT, 667, 221,
FONT_NEXT, 656, 475,
FONT_NEXT, 643, 166,
FONT_NEXT, 622, 532,
FONT_NEXT, 607, 114,
FONT_NEXT, 575, 579,
FONT_NEXT, 576, 328,
FONT_END, 571, 390,
FONT_BEGIN, 575, 579,
FONT_NEXT, 571, 390,
FONT_NEXT, 517, 615,
FONT_NEXT, 555, 449,
FONT_END, 526, 505,
FONT_BEGIN, 517, 615,
FONT_NEXT, 526, 505,
FONT_END, 483, 555,
FONT_BEGIN, 517, 615,
FONT_NEXT, 483, 555,
FONT_NEXT, 449, 641,
FONT_NEXT, 444, 584,
FONT_NEXT, 372, 656,
FONT_NEXT, 397, 606,
FONT_END, 335, 620,
FONT_BEGIN, 372, 656,
FONT_NEXT, 335, 620,
FONT_NEXT, 286, 662,
FONT_NEXT, 253, 625,
FONT_NEXT, 16, 662,
FONT_NEXT, 225, 621,
FONT_END, 211, 613,
FONT_BEGIN, 16, 662,
FONT_NEXT, 211, 613,
FONT_END, 206, 586,
FONT_BEGIN, 206, 78,
FONT_NEXT, 208, 57,
FONT_NEXT, 16, 0,
FONT_NEXT, 218, 44,
FONT_END, 236, 38,
FONT_BEGIN, 16, 0,
FONT_NEXT, 236, 38,
FONT_END, 266, 37,
FONT_BEGIN, 16, 0,
FONT_NEXT, 266, 37,
FONT_END, 297, 37,
FONT_BEGIN, 16, 0,
FONT_NEXT, 297, 37,
FONT_NEXT, 300, 0,
FONT_NEXT, 341, 41,
FONT_END, 392, 52,
FONT_BEGIN, 300, 0,
FONT_NEXT, 392, 52,
FONT_NEXT, 404, 8,
FONT_NEXT, 445, 73,
FONT_NEXT, 489, 32,
FONT_NEXT, 494, 108,
FONT_END, 536, 159,
FONT_BEGIN, 489, 32,
FONT_NEXT, 536, 159,
FONT_NEXT, 556, 69,
FONT_NEXT, 565, 231,
FONT_END, 573, 276,
FONT_BEGIN, 556, 69,
FONT_NEXT, 573, 276,
FONT_NEXT, 607, 114,
FONT_END, 576, 328,
FONT_ADVANCE, 722, 0
    },
    {
69,
FONT_BEGIN, 12, 19,
FONT_NEXT, 52, 23,
FONT_NEXT, 12, 0,
FONT_NEXT, 79, 36,
FONT_NEXT, 201, 80,
FONT_NEXT, 94, 63,
FONT_END, 99, 109,
FONT_BEGIN, 201, 80,
FONT_NEXT, 99, 109,
FONT_END, 99, 553,
FONT_BEGIN, 201, 80,
FONT_NEXT, 99, 553,
FONT_END, 94, 599,
FONT_BEGIN, 201, 80,
FONT_NEXT, 94, 599,
FONT_NEXT, 201, 328,
FONT_NEXT, 201, 368,
FONT_NEXT, 355, 326,
FONT_NEXT, 355, 368,
FONT_NEXT, 405, 321,
FONT_NEXT, 408, 372,
FONT_NEXT, 436, 308,
FONT_NEXT, 439, 388,
FONT_NEXT, 454, 279,
FONT_NEXT, 455, 418,
FONT_NEXT, 465, 231,
FONT_NEXT, 465, 463,
FONT_NEXT, 488, 231,
FONT_END, 488, 463,
FONT_BEGIN, 597, 169,
FONT_NEXT, 552, 0,
FONT_NEXT, 569, 169,
FONT_END, 533, 105,
FONT_BEGIN, 533, 105,
FONT_NEXT, 552, 0,
FONT_NEXT, 489, 65,
FONT_END, 430, 44,
FONT_BEGIN, 430, 44,
FONT_NEXT, 552, 0,
FONT_END, 12, 0,
FONT_BEGIN, 430, 44,
FONT_NEXT, 12, 0,
FONT_NEXT, 350, 38,
FONT_END, 300, 38,
FONT_BEGIN, 300, 38,
FONT_NEXT, 12, 0,
FONT_NEXT, 263, 38,
FONT_END, 237, 40,
FONT_BEGIN, 237, 40,
FONT_NEXT, 12, 0,
FONT_NEXT, 219, 43,
FONT_END, 203, 56,
FONT_BEGIN, 203, 56,
FONT_NEXT, 12, 0,
FONT_END, 201, 80,
FONT_BEGIN, 203, 56,
FONT_END, 201, 80,
FONT_BEGIN, 12, 643,
FONT_NEXT, 12, 662,
FONT_NEXT, 52, 637,
FONT_END, 79, 625,
FONT_BEGIN, 79, 625,
FONT_NEXT, 12, 662,
FONT_END, 201, 590,
FONT_BEGIN, 79, 625,
FONT_NEXT, 201, 590,
FONT_NEXT, 94, 599,
FONT_END, 201, 368,
FONT_BEGIN, 546, 519,
FONT_NEXT, 521, 519,
FONT_NEXT, 543, 662,
FONT_NEXT, 506, 572,
FONT_END, 481, 604,
FONT_BEGIN, 543, 662,
FONT_NEXT, 481, 604,
FONT_END, 437, 620,
FONT_BEGIN, 543, 662,
FONT_NEXT, 437, 620,
FONT_NEXT, 12, 662,
FONT_NEXT, 369, 624,
FONT_END, 234, 624,
FONT_BEGIN, 12, 662,
FONT_NEXT, 234, 624,
FONT_END, 209, 619,
FONT_BEGIN, 12, 662,
FONT_NEXT, 209, 619,
FONT_END, 203, 609,
FONT_BEGIN, 12, 662,
FONT_NEXT, 203, 609,
FONT_END, 201, 590,
FONT_ADVANCE, 611, 0
    },
    {
70,
FONT_BEGIN, 12, 643,
FONT_NEXT, 12, 662,
FONT_NEXT, 52, 637,
FONT_END, 79, 625,
FONT_BEGIN, 79, 625,
FONT_NEXT, 12, 662,
FONT_END, 201, 590,
FONT_BEGIN, 79, 625,
FONT_NEXT, 201, 590,
FONT_NEXT, 94, 599,
FONT_NEXT, 201, 368,
FONT_END, 201, 328,
FONT_BEGIN, 94, 599,
FONT_NEXT, 201, 328,
FONT_END, 201, 109,
FONT_BEGIN, 94, 599,
FONT_NEXT, 201, 109,
FONT_NEXT, 99, 553,
FONT_END, 99, 120,
FONT_BEGIN, 99, 120,
FONT_NEXT, 201, 109,
FONT_NEXT, 95, 70,
FONT_END, 83, 40,
FONT_BEGIN, 83, 40,
FONT_NEXT, 201, 109,
FONT_NEXT, 57, 24,
FONT_NEXT, 205, 63,
FONT_END, 12, 0,
FONT_BEGIN, 57, 24,
FONT_NEXT, 12, 0,
FONT_END, 12, 19,
FONT_BEGIN, 546, 519,
FONT_NEXT, 521, 519,
FONT_NEXT, 543, 662,
FONT_NEXT, 506, 572,
FONT_END, 481, 604,
FONT_BEGIN, 543, 662,
FONT_NEXT, 481, 604,
FONT_END, 437, 620,
FONT_BEGIN, 543, 662,
FONT_NEXT, 437, 620,
FONT_NEXT, 12, 662,
FONT_NEXT, 369, 624,
FONT_END, 233, 624,
FONT_BEGIN, 12, 662,
FONT_NEXT, 233, 624,
FONT_END, 207, 618,
FONT_BEGIN, 12, 662,
FONT_NEXT, 207, 618,
FONT_END, 202, 607,
FONT_BEGIN, 12, 662,
FONT_NEXT, 202, 607,
FONT_END, 201, 590,
FONT_BEGIN, 479, 463,
FONT_NEXT, 479, 231,
FONT_NEXT, 456, 463,
FONT_NEXT, 456, 231,
FONT_NEXT, 446, 416,
FONT_NEXT, 444, 281,
FONT_NEXT, 428, 387,
FONT_NEXT, 425, 309,
FONT_NEXT, 396, 372,
FONT_NEXT, 393, 322,
FONT_NEXT, 346, 368,
FONT_NEXT, 346, 326,
FONT_NEXT, 201, 368,
FONT_END, 201, 328,
FONT_BEGIN, 292, 19,
FONT_NEXT, 292, 0,
FONT_NEXT, 247, 23,
FONT_NEXT, 12, 0,
FONT_NEXT, 219, 37,
FONT_END, 205, 63,
FONT_ADVANCE, 556, 0
    },
    {
71,
FONT_BEGIN, 630, 465,
FONT_NEXT, 607, 465,
FONT_NEXT, 622, 676,
FONT_END, 600, 676,
FONT_BEGIN, 600, 676,
FONT_NEXT, 607, 465,
FONT_END, 581, 522,
FONT_BEGIN, 600, 676,
FONT_NEXT, 581, 522,
FONT_NEXT, 591, 659,
FONT_END, 577, 649,
FONT_BEGIN, 577, 649,
FONT_NEXT, 581, 522,
FONT_END, 539, 577,
FONT_BEGIN, 577, 649,
FONT_NEXT, 539, 577,
FONT_NEXT, 553, 643,
FONT_END, 525, 648,
FONT_BEGIN, 525, 648,
FONT_NEXT, 539, 577,
FONT_END, 477, 619,
FONT_BEGIN, 525, 648,
FONT_NEXT, 477, 619,
FONT_NEXT, 489, 659,
FONT_END, 440, 670,
FONT_BEGIN, 440, 670,
FONT_NEXT, 477, 619,
FONT_END, 436, 631,
FONT_BEGIN, 440, 670,
FONT_NEXT, 436, 631,
FONT_NEXT, 374, 676,
FONT_NEXT, 388, 636,
FONT_END, 310, 622,
FONT_BEGIN, 374, 676,
FONT_NEXT, 310, 622,
FONT_NEXT, 298, 668,
FONT_NEXT, 269, 602,
FONT_NEXT, 231, 647,
FONT_NEXT, 231, 572,
FONT_NEXT, 172, 614,
FONT_NEXT, 197, 531,
FONT_END, 170, 476,
FONT_BEGIN, 172, 614,
FONT_NEXT, 170, 476,
FONT_NEXT, 123, 571,
FONT_NEXT, 152, 406,
FONT_END, 146, 320,
FONT_BEGIN, 123, 571,
FONT_NEXT, 146, 320,
FONT_END, 115, 93,
FONT_BEGIN, 123, 571,
FONT_NEXT, 115, 93,
FONT_NEXT, 84, 520,
FONT_NEXT, 79, 137,
FONT_NEXT, 55, 464,
FONT_NEXT, 53, 189,
FONT_NEXT, 38, 403,
FONT_NEXT, 37, 255,
FONT_END, 32, 341,
FONT_BEGIN, 454, 336,
FONT_NEXT, 454, 354,
FONT_NEXT, 496, 331,
FONT_END, 523, 320,
FONT_BEGIN, 523, 320,
FONT_NEXT, 454, 354,
FONT_END, 643, 300,
FONT_BEGIN, 523, 320,
FONT_NEXT, 643, 300,
FONT_END, 639, 259,
FONT_BEGIN, 523, 320,
FONT_NEXT, 639, 259,
FONT_NEXT, 537, 295,
FONT_NEXT, 639, 58,
FONT_END, 633, 50,
FONT_BEGIN, 537, 295,
FONT_NEXT, 633, 50,
FONT_END, 616, 40,
FONT_BEGIN, 537, 295,
FONT_NEXT, 616, 40,
FONT_NEXT, 542, 247,
FONT_NEXT, 558, 16,
FONT_NEXT, 542, 85,
FONT_END, 530, 60,
FONT_BEGIN, 530, 60,
FONT_NEXT, 558, 16,
FONT_END, 480, -5,
FONT_BEGIN, 530, 60,
FONT_NEXT, 480, -5,
FONT_NEXT, 499, 42,
FONT_END, 455, 30,
FONT_BEGIN, 455, 30,
FONT_NEXT, 480, -5,
FONT_END, 396, -14,
FONT_BEGIN, 455, 30,
FONT_NEXT, 396, -14,
FONT_NEXT, 405, 26,
FONT_END, 349, 31,
FONT_BEGIN, 349, 31,
FONT_NEXT, 396, -14,
FONT_END, 320, -9,
FONT_BEGIN, 349, 31,
FONT_NEXT, 320, -9,
FONT_NEXT, 299, 46,
FONT_NEXT, 245, 10,
FONT_NEXT, 254, 71,
FONT_END, 217, 105,
FONT_BEGIN, 217, 105,
FONT_NEXT, 245, 10,
FONT_END, 174, 43,
FONT_BEGIN, 217, 105,
FONT_NEXT, 174, 43,
FONT_NEXT, 187, 147,
FONT_END, 164, 197,
FONT_BEGIN, 164, 197,
FONT_NEXT, 174, 43,
FONT_END, 115, 93,
FONT_BEGIN, 164, 197,
FONT_NEXT, 115, 93,
FONT_NEXT, 150, 255,
FONT_END, 146, 320,
FONT_BEGIN, 709, 354,
FONT_NEXT, 709, 336,
FONT_NEXT, 454, 354,
FONT_NEXT, 679, 332,
FONT_END, 657, 322,
FONT_BEGIN, 454, 354,
FONT_NEXT, 657, 322,
FONT_END, 643, 300,
FONT_ADVANCE, 722, 0
    },
    {
72,
FONT_BEGIN, 19, 643,
FONT_NEXT, 19, 662,
FONT_NEXT, 63, 636,
FONT_END, 90, 623,
FONT_BEGIN, 90, 623,
FONT_NEXT, 19, 662,
FONT_END, 212, 596,
FONT_BEGIN, 90, 623,
FONT_NEXT, 212, 596,
FONT_END, 209, 553,
FONT_BEGIN, 90, 623,
FONT_NEXT, 209, 553,
FONT_NEXT, 103, 597,
FONT_NEXT, 209, 359,
FONT_END, 209, 315,
FONT_BEGIN, 103, 597,
FONT_NEXT, 209, 315,
FONT_END, 209, 109,
FONT_BEGIN, 103, 597,
FONT_NEXT, 209, 109,
FONT_NEXT, 107, 553,
FONT_END, 107, 120,
FONT_BEGIN, 107, 120,
FONT_NEXT, 209, 109,
FONT_NEXT, 105, 73,
FONT_END, 94, 42,
FONT_BEGIN, 94, 42,
FONT_NEXT, 209, 109,
FONT_NEXT, 68, 25,
FONT_NEXT, 211, 67,
FONT_END, 19, 0,
FONT_BEGIN, 68, 25,
FONT_NEXT, 19, 0,
FONT_END, 19, 19,
FONT_BEGIN, 297, 662,
FONT_NEXT, 297, 643,
FONT_NEXT, 19, 662,
FONT_NEXT, 251, 636,
FONT_END, 224, 622,
FONT_BEGIN, 19, 662,
FONT_NEXT, 224, 622,
FONT_END, 212, 596,
FONT_BEGIN, 424, 643,
FONT_NEXT, 424, 662,
FONT_NEXT, 468, 636,
FONT_END, 495, 623,
FONT_BEGIN, 495, 623,
FONT_NEXT, 424, 662,
FONT_END, 617, 596,
FONT_BEGIN, 495, 623,
FONT_NEXT, 617, 596,
FONT_END, 614, 553,
FONT_BEGIN, 495, 623,
FONT_NEXT, 614, 553,
FONT_NEXT, 508, 597,
FONT_NEXT, 614, 109,
FONT_NEXT, 512, 553,
FONT_NEXT, 512, 120,
FONT_NEXT, 512, 359,
FONT_NEXT, 512, 315,
FONT_NEXT, 209, 359,
FONT_END, 209, 315,
FONT_BEGIN, 702, 662,
FONT_NEXT, 702, 643,
FONT_NEXT, 424, 662,
FONT_NEXT, 656, 636,
FONT_END, 629, 622,
FONT_BEGIN, 424, 662,
FONT_NEXT, 629, 622,
FONT_END, 617, 596,
FONT_BEGIN, 424, 19,
FONT_NEXT, 473, 25,
FONT_NEXT, 424, 0,
FONT_END, 616, 67,
FONT_BEGIN, 616, 67,
FONT_NEXT, 473, 25,
FONT_NEXT, 614, 109,
FONT_NEXT, 499, 42,
FONT_END, 510, 73,
FONT_BEGIN, 614, 109,
FONT_NEXT, 510, 73,
FONT_END, 512, 120,
FONT_BEGIN, 702, 19,
FONT_NEXT, 702, 0,
FONT_NEXT, 655, 25,
FONT_NEXT, 424, 0,
FONT_NEXT, 628, 41,
FONT_END, 616, 67,
FONT_BEGIN, 297, 19,
FONT_NEXT, 297, 0,
FONT_NEXT, 250, 25,
FONT_NEXT, 19, 0,
FONT_NEXT, 223, 41,
FONT_END, 211, 67,
FONT_ADVANCE, 722, 0
    },
    {
73,
FONT_BEGIN, 18, 643,
FONT_NEXT, 18, 662,
FONT_NEXT, 68, 637,
FONT_END, 97, 624,
FONT_BEGIN, 97, 624,
FONT_NEXT, 18, 662,
FONT_END, 222, 600,
FONT_BEGIN, 97, 624,
FONT_NEXT, 222, 600,
FONT_END, 217, 553,
FONT_BEGIN, 97, 624,
FONT_NEXT, 217, 553,
FONT_NEXT, 111, 597,
FONT_NEXT, 217, 109,
FONT_NEXT, 115, 553,
FONT_END, 115, 109,
FONT_BEGIN, 115, 109,
FONT_NEXT, 217, 109,
FONT_NEXT, 111, 65,
FONT_END, 98, 38,
FONT_BEGIN, 98, 38,
FONT_NEXT, 217, 109,
FONT_END, 221, 62,
FONT_BEGIN, 98, 38,
FONT_NEXT, 221, 62,
FONT_NEXT, 69, 23,
FONT_NEXT, 18, 0,
FONT_END, 18, 19,
FONT_BEGIN, 315, 662,
FONT_NEXT, 315, 643,
FONT_NEXT, 18, 662,
FONT_NEXT, 268, 638,
FONT_END, 238, 626,
FONT_BEGIN, 18, 662,
FONT_NEXT, 238, 626,
FONT_END, 222, 600,
FONT_BEGIN, 315, 19,
FONT_NEXT, 315, 0,
FONT_NEXT, 268, 22,
FONT_NEXT, 18, 0,
FONT_NEXT, 238, 35,
FONT_END, 221, 62,
FONT_ADVANCE, 333, 0
    },
    {
74,
FONT_BEGIN, 83, 643,
FONT_NEXT, 83, 662,
FONT_NEXT, 131, 636,
FONT_END, 159, 622,
FONT_BEGIN, 159, 622,
FONT_NEXT, 83, 662,
FONT_END, 281, 597,
FONT_BEGIN, 159, 622,
FONT_NEXT, 281, 597,
FONT_END, 278, 553,
FONT_BEGIN, 159, 622,
FONT_NEXT, 278, 553,
FONT_NEXT, 172, 596,
FONT_NEXT, 278, 183,
FONT_END, 271, 118,
FONT_BEGIN, 172, 596,
FONT_NEXT, 271, 118,
FONT_END, 255, 70,
FONT_BEGIN, 172, 596,
FONT_NEXT, 255, 70,
FONT_END, 231, 35,
FONT_BEGIN, 172, 596,
FONT_NEXT, 231, 35,
FONT_NEXT, 176, 553,
FONT_NEXT, 204, 11,
FONT_NEXT, 176, 90,
FONT_END, 173, 57,
FONT_BEGIN, 173, 57,
FONT_NEXT, 204, 11,
FONT_END, 147, -11,
FONT_BEGIN, 173, 57,
FONT_NEXT, 147, -11,
FONT_NEXT, 165, 37,
FONT_END, 153, 27,
FONT_BEGIN, 153, 27,
FONT_NEXT, 147, -11,
FONT_NEXT, 137, 24,
FONT_NEXT, 124, -14,
FONT_NEXT, 117, 37,
FONT_NEXT, 110, -14,
FONT_NEXT, 107, 66,
FONT_NEXT, 59, -6,
FONT_NEXT, 92, 94,
FONT_END, 77, 104,
FONT_BEGIN, 77, 104,
FONT_NEXT, 59, -6,
FONT_NEXT, 56, 108,
FONT_NEXT, 32, 11,
FONT_NEXT, 36, 103,
FONT_END, 22, 90,
FONT_BEGIN, 22, 90,
FONT_NEXT, 32, 11,
FONT_END, 15, 36,
FONT_BEGIN, 22, 90,
FONT_NEXT, 15, 36,
FONT_END, 10, 59,
FONT_BEGIN, 370, 662,
FONT_NEXT, 370, 643,
FONT_NEXT, 83, 662,
FONT_NEXT, 322, 637,
FONT_END, 294, 623,
FONT_BEGIN, 83, 662,
FONT_NEXT, 294, 623,
FONT_END, 281, 597,
FONT_ADVANCE, 389, 0
    },
    {
75,
FONT_BEGIN, 34, 643,
FONT_NEXT, 34, 662,
FONT_NEXT, 76, 637,
FONT_END, 104, 625,
FONT_BEGIN, 104, 625,
FONT_NEXT, 34, 662,
FONT_END, 229, 597,
FONT_BEGIN, 104, 625,
FONT_NEXT, 229, 597,
FONT_END, 226, 553,
FONT_BEGIN, 104, 625,
FONT_NEXT, 226, 553,
FONT_NEXT, 119, 599,
FONT_NEXT, 226, 348,
FONT_END, 226, 296,
FONT_BEGIN, 119, 599,
FONT_NEXT, 226, 296,
FONT_END, 226, 109,
FONT_BEGIN, 119, 599,
FONT_NEXT, 226, 109,
FONT_NEXT, 124, 553,
FONT_END, 124, 120,
FONT_BEGIN, 124, 120,
FONT_NEXT, 226, 109,
FONT_NEXT, 120, 70,
FONT_END, 108, 40,
FONT_BEGIN, 108, 40,
FONT_NEXT, 226, 109,
FONT_END, 229, 65,
FONT_BEGIN, 108, 40,
FONT_NEXT, 229, 65,
FONT_NEXT, 80, 24,
FONT_NEXT, 34, 0,
FONT_END, 34, 19,
FONT_BEGIN, 318, 662,
FONT_NEXT, 318, 643,
FONT_NEXT, 34, 662,
FONT_NEXT, 270, 637,
FONT_END, 242, 623,
FONT_BEGIN, 34, 662,
FONT_NEXT, 242, 623,
FONT_END, 229, 597,
FONT_BEGIN, 413, 643,
FONT_NEXT, 413, 662,
FONT_NEXT, 444, 641,
FONT_END, 465, 636,
FONT_BEGIN, 465, 636,
FONT_NEXT, 413, 662,
FONT_END, 568, 606,
FONT_BEGIN, 465, 636,
FONT_NEXT, 568, 606,
FONT_NEXT, 477, 627,
FONT_NEXT, 523, 565,
FONT_NEXT, 481, 612,
FONT_END, 472, 584,
FONT_BEGIN, 472, 584,
FONT_NEXT, 523, 565,
FONT_END, 333, 377,
FONT_BEGIN, 472, 584,
FONT_NEXT, 333, 377,
FONT_NEXT, 459, 567,
FONT_END, 438, 543,
FONT_BEGIN, 438, 543,
FONT_NEXT, 333, 377,
FONT_NEXT, 406, 511,
FONT_END, 361, 470,
FONT_BEGIN, 361, 470,
FONT_NEXT, 333, 377,
FONT_NEXT, 301, 416,
FONT_NEXT, 252, 317,
FONT_NEXT, 226, 348,
FONT_END, 226, 296,
FONT_BEGIN, 675, 662,
FONT_NEXT, 675, 643,
FONT_NEXT, 413, 662,
FONT_NEXT, 635, 638,
FONT_END, 603, 628,
FONT_BEGIN, 413, 662,
FONT_NEXT, 603, 628,
FONT_END, 568, 606,
FONT_BEGIN, 723, 19,
FONT_NEXT, 723, 0,
FONT_NEXT, 683, 24,
FONT_NEXT, 418, 0,
FONT_NEXT, 647, 45,
FONT_END, 609, 79,
FONT_BEGIN, 609, 79,
FONT_NEXT, 418, 0,
FONT_END, 472, 24,
FONT_BEGIN, 609, 79,
FONT_NEXT, 472, 24,
FONT_END, 484, 31,
FONT_BEGIN, 609, 79,
FONT_NEXT, 484, 31,
FONT_NEXT, 566, 127,
FONT_NEXT, 488, 44,
FONT_END, 483, 58,
FONT_BEGIN, 566, 127,
FONT_NEXT, 483, 58,
FONT_NEXT, 333, 377,
FONT_NEXT, 472, 78,
FONT_END, 435, 126,
FONT_BEGIN, 333, 377,
FONT_NEXT, 435, 126,
FONT_END, 392, 175,
FONT_BEGIN, 333, 377,
FONT_NEXT, 392, 175,
FONT_END, 358, 212,
FONT_BEGIN, 333, 377,
FONT_NEXT, 358, 212,
FONT_END, 252, 317,
FONT_BEGIN, 418, 19,
FONT_NEXT, 451, 20,
FONT_NEXT, 418, 0,
FONT_END, 472, 24,
FONT_BEGIN, 316, 19,
FONT_NEXT, 316, 0,
FONT_NEXT, 269, 24,
FONT_NEXT, 34, 0,
FONT_NEXT, 241, 39,
FONT_END, 229, 65,
FONT_ADVANCE, 722, 0
    },
    {
76,
FONT_BEGIN, 12, 19,
FONT_NEXT, 52, 23,
FONT_NEXT, 12, 0,
FONT_NEXT, 79, 36,
FONT_NEXT, 201, 80,
FONT_NEXT, 94, 63,
FONT_END, 99, 109,
FONT_BEGIN, 201, 80,
FONT_NEXT, 99, 109,
FONT_END, 99, 553,
FONT_BEGIN, 201, 80,
FONT_NEXT, 99, 553,
FONT_END, 94, 599,
FONT_BEGIN, 201, 80,
FONT_NEXT, 94, 599,
FONT_NEXT, 201, 553,
FONT_NEXT, 79, 625,
FONT_NEXT, 205, 599,
FONT_END, 12, 662,
FONT_BEGIN, 12, 662,
FONT_NEXT, 79, 625,
FONT_END, 52, 637,
FONT_BEGIN, 12, 662,
FONT_NEXT, 52, 637,
FONT_END, 12, 643,
FONT_BEGIN, 598, 174,
FONT_NEXT, 550, 0,
FONT_NEXT, 573, 174,
FONT_END, 551, 130,
FONT_BEGIN, 551, 130,
FONT_NEXT, 550, 0,
FONT_NEXT, 527, 97,
FONT_END, 501, 73,
FONT_BEGIN, 501, 73,
FONT_NEXT, 550, 0,
FONT_NEXT, 472, 57,
FONT_END, 399, 41,
FONT_BEGIN, 399, 41,
FONT_NEXT, 550, 0,
FONT_END, 12, 0,
FONT_BEGIN, 399, 41,
FONT_NEXT, 12, 0,
FONT_NEXT, 302, 39,
FONT_END, 251, 39,
FONT_BEGIN, 251, 39,
FONT_NEXT, 12, 0,
FONT_NEXT, 220, 44,
FONT_END, 205, 56,
FONT_BEGIN, 205, 56,
FONT_NEXT, 12, 0,
FONT_END, 201, 80,
FONT_BEGIN, 205, 56,
FONT_END, 201, 80,
FONT_BEGIN, 294, 662,
FONT_NEXT, 294, 643,
FONT_NEXT, 12, 662,
FONT_NEXT, 250, 638,
FONT_END, 222, 626,
FONT_BEGIN, 12, 662,
FONT_NEXT, 222, 626,
FONT_END, 205, 599,
FONT_ADVANCE, 611, 0
    },
    {
77,
FONT_BEGIN, 14, 643,
FONT_NEXT, 14, 662,
FONT_NEXT, 60, 638,
FONT_END, 89, 625,
FONT_BEGIN, 89, 625,
FONT_NEXT, 14, 662,
FONT_END, 212, 662,
FONT_BEGIN, 89, 625,
FONT_NEXT, 212, 662,
FONT_END, 155, 546,
FONT_BEGIN, 89, 625,
FONT_NEXT, 155, 546,
FONT_END, 153, 546,
FONT_BEGIN, 89, 625,
FONT_NEXT, 153, 546,
FONT_NEXT, 104, 599,
FONT_END, 109, 553,
FONT_BEGIN, 109, 553,
FONT_NEXT, 153, 546,
FONT_END, 153, 147,
FONT_BEGIN, 109, 553,
FONT_NEXT, 153, 147,
FONT_NEXT, 109, 147,
FONT_END, 107, 110,
FONT_BEGIN, 107, 110,
FONT_NEXT, 153, 147,
FONT_NEXT, 104, 82,
FONT_END, 89, 44,
FONT_BEGIN, 89, 44,
FONT_NEXT, 153, 147,
FONT_END, 157, 83,
FONT_BEGIN, 89, 44,
FONT_NEXT, 157, 83,
FONT_NEXT, 59, 25,
FONT_NEXT, 172, 45,
FONT_END, 12, 0,
FONT_BEGIN, 59, 25,
FONT_NEXT, 12, 0,
FONT_END, 12, 19,
FONT_BEGIN, 863, 662,
FONT_NEXT, 863, 643,
FONT_NEXT, 664, 662,
FONT_NEXT, 822, 637,
FONT_END, 795, 625,
FONT_BEGIN, 664, 662,
FONT_NEXT, 795, 625,
FONT_END, 780, 599,
FONT_BEGIN, 664, 662,
FONT_NEXT, 780, 599,
FONT_END, 776, 553,
FONT_BEGIN, 664, 662,
FONT_NEXT, 776, 553,
FONT_END, 776, 109,
FONT_BEGIN, 664, 662,
FONT_NEXT, 776, 109,
FONT_END, 674, 569,
FONT_BEGIN, 664, 662,
FONT_NEXT, 674, 569,
FONT_END, 672, 569,
FONT_BEGIN, 664, 662,
FONT_NEXT, 672, 569,
FONT_END, 418, 0,
FONT_BEGIN, 664, 662,
FONT_NEXT, 418, 0,
FONT_NEXT, 443, 157,
FONT_END, 212, 662,
FONT_BEGIN, 212, 662,
FONT_NEXT, 418, 0,
FONT_END, 404, 0,
FONT_BEGIN, 212, 662,
FONT_NEXT, 404, 0,
FONT_END, 155, 546,
FONT_BEGIN, 583, 19,
FONT_NEXT, 630, 24,
FONT_NEXT, 583, 0,
FONT_END, 780, 63,
FONT_BEGIN, 780, 63,
FONT_NEXT, 630, 24,
FONT_NEXT, 776, 109,
FONT_NEXT, 657, 40,
FONT_END, 670, 70,
FONT_BEGIN, 776, 109,
FONT_NEXT, 670, 70,
FONT_END, 674, 120,
FONT_BEGIN, 776, 109,
FONT_NEXT, 674, 120,
FONT_END, 674, 569,
FONT_BEGIN, 863, 19,
FONT_NEXT, 863, 0,
FONT_NEXT, 822, 23,
FONT_NEXT, 583, 0,
FONT_NEXT, 795, 36,
FONT_END, 780, 63,
FONT_BEGIN, 247, 19,
FONT_NEXT, 247, 0,
FONT_NEXT, 201, 25,
FONT_NEXT, 12, 0,
FONT_END, 172, 45,
FONT_ADVANCE, 889, 0
    },
    {
78,
FONT_BEGIN, 12, 643,
FONT_NEXT, 12, 662,
FONT_NEXT, 60, 634,
FONT_NEXT, 183, 662,
FONT_NEXT, 82, 617,
FONT_END, 109, 588,
FONT_BEGIN, 109, 588,
FONT_NEXT, 183, 662,
FONT_END, 155, 537,
FONT_BEGIN, 109, 588,
FONT_NEXT, 155, 537,
FONT_END, 153, 537,
FONT_BEGIN, 109, 588,
FONT_NEXT, 153, 537,
FONT_END, 153, 147,
FONT_BEGIN, 109, 588,
FONT_NEXT, 153, 147,
FONT_NEXT, 109, 147,
FONT_END, 107, 110,
FONT_BEGIN, 107, 110,
FONT_NEXT, 153, 147,
FONT_NEXT, 104, 82,
FONT_END, 89, 44,
FONT_BEGIN, 89, 44,
FONT_NEXT, 153, 147,
FONT_END, 157, 83,
FONT_BEGIN, 89, 44,
FONT_NEXT, 157, 83,
FONT_NEXT, 59, 25,
FONT_NEXT, 172, 45,
FONT_END, 12, 0,
FONT_BEGIN, 59, 25,
FONT_NEXT, 12, 0,
FONT_END, 12, 19,
FONT_BEGIN, 472, 643,
FONT_NEXT, 472, 662,
FONT_NEXT, 515, 637,
FONT_END, 545, 620,
FONT_BEGIN, 545, 620,
FONT_NEXT, 472, 662,
FONT_END, 617, 583,
FONT_BEGIN, 545, 620,
FONT_NEXT, 617, 583,
FONT_END, 613, 553,
FONT_BEGIN, 545, 620,
FONT_NEXT, 613, 553,
FONT_END, 612, 515,
FONT_BEGIN, 545, 620,
FONT_NEXT, 612, 515,
FONT_NEXT, 562, 582,
FONT_END, 566, 553,
FONT_BEGIN, 566, 553,
FONT_NEXT, 612, 515,
FONT_END, 612, -11,
FONT_BEGIN, 566, 553,
FONT_NEXT, 612, -11,
FONT_NEXT, 568, 515,
FONT_NEXT, 595, -11,
FONT_NEXT, 568, 181,
FONT_END, 566, 181,
FONT_BEGIN, 566, 181,
FONT_NEXT, 595, -11,
FONT_END, 155, 537,
FONT_BEGIN, 566, 181,
FONT_NEXT, 155, 537,
FONT_END, 183, 662,
FONT_BEGIN, 707, 662,

FONT_NEXT, 707, 643,
FONT_NEXT, 472, 662,
FONT_NEXT, 665, 636,
FONT_END, 635, 620,
FONT_BEGIN, 472, 662,
FONT_NEXT, 635, 620,
FONT_END, 617, 583,
FONT_BEGIN, 247, 19,
FONT_NEXT, 247, 0,
FONT_NEXT, 201, 25,
FONT_NEXT, 12, 0,
FONT_END, 172, 45,
FONT_ADVANCE, 722, 0
    },
    {
79,
FONT_BEGIN, 148, 331,
FONT_NEXT, 152, 254,
FONT_NEXT, 129, 78,
FONT_NEXT, 165, 189,
FONT_NEXT, 177, 38,
FONT_NEXT, 186, 136,
FONT_END, 212, 94,
FONT_BEGIN, 177, 38,
FONT_NEXT, 212, 94,
FONT_NEXT, 233, 9,
FONT_NEXT, 244, 61,
FONT_END, 280, 39,
FONT_BEGIN, 233, 9,
FONT_NEXT, 280, 39,
FONT_NEXT, 294, -8,
FONT_NEXT, 319, 26,
FONT_NEXT, 361, -14,
FONT_NEXT, 361, 22,
FONT_END, 402, 26,
FONT_BEGIN, 361, -14,
FONT_NEXT, 402, 26,
FONT_NEXT, 427, -8,
FONT_NEXT, 441, 39,
FONT_END, 477, 61,
FONT_BEGIN, 427, -8,
FONT_NEXT, 477, 61,
FONT_NEXT, 488, 9,
FONT_NEXT, 509, 94,
FONT_END, 535, 136,
FONT_BEGIN, 488, 9,
FONT_NEXT, 535, 136,
FONT_NEXT, 544, 38,
FONT_NEXT, 556, 189,
FONT_END, 569, 254,
FONT_BEGIN, 544, 38,
FONT_NEXT, 569, 254,
FONT_NEXT, 592, 78,
FONT_NEXT, 574, 331,
FONT_NEXT, 586, 589,
FONT_NEXT, 569, 409,
FONT_NEXT, 536, 627,
FONT_NEXT, 555, 475,
FONT_END, 533, 528,
FONT_BEGIN, 536, 627,
FONT_NEXT, 533, 528,
FONT_NEXT, 481, 654,
FONT_NEXT, 505, 571,
FONT_END, 473, 602,
FONT_BEGIN, 481, 654,
FONT_NEXT, 473, 602,
FONT_NEXT, 422, 670,
FONT_NEXT, 437, 623,
FONT_END, 399, 636,
FONT_BEGIN, 422, 670,
FONT_NEXT, 399, 636,
FONT_NEXT, 361, 676,
FONT_NEXT, 361, 640,
FONT_NEXT, 299, 670,
FONT_NEXT, 322, 636,
FONT_END, 284, 623,
FONT_BEGIN, 299, 670,
FONT_NEXT, 284, 623,
FONT_NEXT, 240, 654,
FONT_NEXT, 248, 602,
FONT_END, 216, 571,
FONT_BEGIN, 240, 654,
FONT_NEXT, 216, 571,
FONT_NEXT, 185, 627,
FONT_NEXT, 188, 528,
FONT_END, 166, 475,
FONT_BEGIN, 185, 627,
FONT_NEXT, 166, 475,
FONT_NEXT, 136, 589,
FONT_NEXT, 152, 409,
FONT_END, 148, 331,
FONT_BEGIN, 136, 589,
FONT_NEXT, 148, 331,
FONT_END, 129, 78,
FONT_BEGIN, 136, 589,
FONT_NEXT, 129, 78,
FONT_NEXT, 94, 540,
FONT_NEXT, 89, 127,
FONT_NEXT, 62, 481,
FONT_NEXT, 59, 186,
FONT_NEXT, 41, 410,
FONT_NEXT, 40, 254,
FONT_END, 34, 330,
FONT_BEGIN, 688, 330,
FONT_NEXT, 681, 254,
FONT_NEXT, 680, 410,
FONT_NEXT, 662, 186,
FONT_NEXT, 659, 481,
FONT_NEXT, 632, 127,
FONT_NEXT, 627, 540,
FONT_NEXT, 592, 78,
FONT_END, 586, 589,
FONT_ADVANCE, 722, 0
    },
    {
80,
FONT_BEGIN, 16, 643,
FONT_NEXT, 16, 662,
FONT_NEXT, 59, 635,
FONT_END, 85, 622,
FONT_BEGIN, 85, 622,
FONT_NEXT, 16, 662,
FONT_END, 202, 591,
FONT_BEGIN, 85, 622,
FONT_NEXT, 202, 591,
FONT_NEXT, 97, 596,
FONT_NEXT, 202, 331,
FONT_END, 202, 291,
FONT_BEGIN, 97, 596,
FONT_NEXT, 202, 291,
FONT_END, 202, 109,
FONT_BEGIN, 97, 596,
FONT_NEXT, 202, 109,
FONT_NEXT, 100, 553,
FONT_END, 100, 120,
FONT_BEGIN, 100, 120,
FONT_NEXT, 202, 109,
FONT_NEXT, 98, 74,
FONT_END, 89, 44,
FONT_BEGIN, 89, 44,
FONT_NEXT, 202, 109,
FONT_NEXT, 64, 27,
FONT_NEXT, 205, 64,
FONT_END, 16, 0,
FONT_BEGIN, 64, 27,
FONT_NEXT, 16, 0,
FONT_END, 16, 19,
FONT_BEGIN, 542, 482,
FONT_NEXT, 534, 430,
FONT_NEXT, 536, 526,
FONT_END, 520, 563,
FONT_BEGIN, 520, 563,
FONT_NEXT, 534, 430,
FONT_END, 517, 391,
FONT_BEGIN, 520, 563,
FONT_NEXT, 517, 391,
FONT_NEXT, 495, 594,
FONT_NEXT, 497, 363,
FONT_END, 481, 347,
FONT_BEGIN, 495, 594,
FONT_NEXT, 481, 347,
FONT_NEXT, 462, 619,
FONT_NEXT, 444, 320,
FONT_END, 433, 480,
FONT_BEGIN, 462, 619,
FONT_NEXT, 433, 480,
FONT_END, 426, 527,
FONT_BEGIN, 462, 619,
FONT_NEXT, 426, 527,
FONT_NEXT, 379, 651,
FONT_NEXT, 409, 562,
FONT_END, 384, 588,
FONT_BEGIN, 379, 651,
FONT_NEXT, 384, 588,
FONT_END, 354, 606,
FONT_BEGIN, 379, 651,
FONT_NEXT, 354, 606,
FONT_NEXT, 280, 662,
FONT_NEXT, 291, 622,
FONT_END, 243, 625,
FONT_BEGIN, 280, 662,
FONT_NEXT, 243, 625,
FONT_NEXT, 16, 662,
FONT_NEXT, 211, 620,
FONT_END, 204, 610,
FONT_BEGIN, 16, 662,
FONT_NEXT, 204, 610,
FONT_END, 202, 591,
FONT_BEGIN, 202, 331,
FONT_NEXT, 262, 328,
FONT_NEXT, 202, 291,
FONT_END, 269, 288,
FONT_BEGIN, 269, 288,
FONT_NEXT, 262, 328,
FONT_END, 304, 330,
FONT_BEGIN, 269, 288,
FONT_NEXT, 304, 330,
FONT_NEXT, 340, 291,
FONT_NEXT, 361, 347,
FONT_END, 388, 365,
FONT_BEGIN, 340, 291,
FONT_NEXT, 388, 365,
FONT_NEXT, 398, 302,
FONT_NEXT, 411, 392,
FONT_END, 427, 429,
FONT_BEGIN, 398, 302,
FONT_NEXT, 427, 429,
FONT_NEXT, 444, 320,
FONT_END, 433, 480,
FONT_BEGIN, 296, 19,
FONT_NEXT, 296, 0,
FONT_NEXT, 248, 23,
FONT_NEXT, 16, 0,
FONT_NEXT, 220, 37,
FONT_END, 205, 64,
FONT_ADVANCE, 556, 0
    },
    {
81,
FONT_BEGIN, 34, 330,
FONT_NEXT, 41, 410,
FONT_NEXT, 40, 255,
FONT_END, 57, 191,
FONT_BEGIN, 57, 191,
FONT_NEXT, 41, 410,
FONT_END, 62, 480,
FONT_BEGIN, 57, 191,
FONT_NEXT, 62, 480,
FONT_NEXT, 82, 138,
FONT_NEXT, 94, 540,
FONT_NEXT, 114, 94,
FONT_NEXT, 136, 588,
FONT_END, 148, 330,
FONT_BEGIN, 114, 94,
FONT_NEXT, 148, 330,
FONT_NEXT, 151, 58,
FONT_NEXT, 152, 253,
FONT_END, 165, 189,
FONT_BEGIN, 151, 58,
FONT_NEXT, 165, 189,
FONT_END, 186, 136,
FONT_BEGIN, 151, 58,
FONT_NEXT, 186, 136,
FONT_NEXT, 189, 31,
FONT_NEXT, 212, 93,
FONT_END, 244, 61,
FONT_BEGIN, 189, 31,
FONT_NEXT, 244, 61,
FONT_NEXT, 265, -1,
FONT_NEXT, 280, 39,
FONT_NEXT, 312, -56,
FONT_NEXT, 319, 26,
FONT_END, 361, 22,
FONT_BEGIN, 312, -56,
FONT_NEXT, 361, 22,
FONT_NEXT, 379, -113,
FONT_NEXT, 402, 26,
FONT_NEXT, 426, -7,
FONT_NEXT, 441, 39,
FONT_NEXT, 468, 2,
FONT_NEXT, 477, 61,
FONT_END, 509, 93,
FONT_BEGIN, 468, 2,
FONT_NEXT, 509, 93,
FONT_NEXT, 512, 20,
FONT_NEXT, 535, 136,
FONT_NEXT, 556, 47,
FONT_NEXT, 556, 189,
FONT_END, 569, 253,
FONT_BEGIN, 556, 47,
FONT_NEXT, 569, 253,
FONT_END, 574, 330,
FONT_BEGIN, 556, 47,
FONT_NEXT, 574, 330,
FONT_NEXT, 597, 83,
FONT_NEXT, 586, 589,
FONT_END, 627, 540,
FONT_BEGIN, 597, 83,
FONT_NEXT, 627, 540,
FONT_NEXT, 633, 130,
FONT_NEXT, 659, 481,
FONT_NEXT, 662, 186,
FONT_NEXT, 680, 410,
FONT_NEXT, 681, 252,
FONT_END, 688, 330,
FONT_BEGIN, 574, 330,
FONT_NEXT, 569, 409,
FONT_NEXT, 586, 589,
FONT_END, 536, 627,
FONT_BEGIN, 536, 627,
FONT_NEXT, 569, 409,
FONT_END, 555, 475,
FONT_BEGIN, 536, 627,
FONT_NEXT, 555, 475,
FONT_END, 533, 528,
FONT_BEGIN, 536, 627,
FONT_NEXT, 533, 528,
FONT_NEXT, 481, 654,
FONT_NEXT, 505, 570,
FONT_END, 473, 602,
FONT_BEGIN, 481, 654,
FONT_NEXT, 473, 602,
FONT_NEXT, 422, 670,
FONT_NEXT, 437, 623,
FONT_END, 399, 636,
FONT_BEGIN, 422, 670,
FONT_NEXT, 399, 636,
FONT_NEXT, 361, 676,
FONT_NEXT, 361, 640,
FONT_NEXT, 299, 670,
FONT_NEXT, 322, 636,
FONT_END, 284, 623,
FONT_BEGIN, 299, 670,
FONT_NEXT, 284, 623,
FONT_NEXT, 240, 654,
FONT_NEXT, 248, 602,
FONT_END, 216, 570,
FONT_BEGIN, 240, 654,
FONT_NEXT, 216, 570,
FONT_NEXT, 185, 626,
FONT_NEXT, 188, 528,
FONT_END, 166, 475,
FONT_BEGIN, 185, 626,
FONT_NEXT, 166, 475,
FONT_NEXT, 136, 588,
FONT_NEXT, 152, 409,
FONT_END, 148, 330,
FONT_BEGIN, 701, -159,
FONT_NEXT, 701, -177,
FONT_NEXT, 638, -153,
FONT_NEXT, 654, -178,
FONT_END, 555, -172,
FONT_BEGIN, 638, -153,
FONT_NEXT, 555, -172,
FONT_NEXT, 586, -138,
FONT_END, 505, -90,
FONT_BEGIN, 505, -90,
FONT_NEXT, 555, -172,
FONT_END, 462, -151,
FONT_BEGIN, 505, -90,
FONT_NEXT, 462, -151,
FONT_NEXT, 426, -7,
FONT_END, 379, -113,
FONT_ADVANCE, 722, 0
    },
    {
82,
FONT_BEGIN, 17, 643,
FONT_NEXT, 17, 662,
FONT_NEXT, 59, 636,
FONT_END, 85, 623,
FONT_BEGIN, 85, 623,
FONT_NEXT, 17, 662,
FONT_END, 204, 589,
FONT_BEGIN, 85, 623,
FONT_NEXT, 204, 589,
FONT_NEXT, 98, 597,
FONT_NEXT, 204, 343,
FONT_END, 204, 306,
FONT_BEGIN, 98, 597,
FONT_NEXT, 204, 306,
FONT_END, 204, 109,
FONT_BEGIN, 98, 597,
FONT_NEXT, 204, 109,
FONT_NEXT, 102, 553,
FONT_END, 102, 120,
FONT_BEGIN, 102, 120,
FONT_NEXT, 204, 109,
FONT_NEXT, 100, 73,
FONT_END, 89, 43,
FONT_BEGIN, 89, 43,
FONT_NEXT, 204, 109,
FONT_NEXT, 64, 26,
FONT_NEXT, 206, 67,
FONT_END, 17, 0,
FONT_BEGIN, 64, 26,
FONT_NEXT, 17, 0,
FONT_END, 17, 19,
FONT_BEGIN, 204, 306,
FONT_NEXT, 204, 343,
FONT_NEXT, 260, 308,
FONT_NEXT, 276, 346,
FONT_END, 352, 362,
FONT_BEGIN, 260, 308,
FONT_NEXT, 352, 362,
FONT_NEXT, 366, 319,
FONT_NEXT, 386, 380,
FONT_END, 413, 406,
FONT_BEGIN, 366, 319,
FONT_NEXT, 413, 406,
FONT_NEXT, 415, 330,
FONT_NEXT, 431, 441,
FONT_END, 438, 489,
FONT_BEGIN, 415, 330,
FONT_NEXT, 438, 489,
FONT_NEXT, 475, 356,
FONT_END, 453, 632,
FONT_BEGIN, 453, 632,
FONT_NEXT, 438, 489,
FONT_END, 432, 533,
FONT_BEGIN, 453, 632,
FONT_NEXT, 432, 533,
FONT_END, 416, 566,
FONT_BEGIN, 453, 632,
FONT_NEXT, 416, 566,
FONT_NEXT, 370, 656,
FONT_NEXT, 394, 590,
FONT_END, 367, 606,
FONT_BEGIN, 370, 656,
FONT_NEXT, 367, 606,
FONT_NEXT, 293, 662,
FONT_NEXT, 310, 622,
FONT_END, 266, 625,
FONT_BEGIN, 293, 662,
FONT_NEXT, 266, 625,
FONT_NEXT, 17, 662,
FONT_NEXT, 233, 623,
FONT_END, 215, 617,
FONT_BEGIN, 17, 662,
FONT_NEXT, 215, 617,
FONT_END, 206, 606,
FONT_BEGIN, 17, 662,
FONT_NEXT, 206, 606,
FONT_END, 204, 589,
FONT_BEGIN, 659, 19,
FONT_NEXT, 659, 0,
FONT_NEXT, 608, 33,
FONT_NEXT, 498, 0,
FONT_NEXT, 572, 66,
FONT_END, 366, 319,
FONT_BEGIN, 366, 319,
FONT_NEXT, 498, 0,
FONT_END, 260, 308,
FONT_BEGIN, 366, 319,
FONT_END, 260, 308,
FONT_BEGIN, 547, 487,
FONT_NEXT, 541, 441,
FONT_NEXT, 539, 538,
FONT_NEXT, 525, 405,
FONT_NEXT, 519, 579,
FONT_NEXT, 503, 377,
FONT_NEXT, 490, 610,
FONT_NEXT, 475, 356,
FONT_END, 453, 632,
FONT_BEGIN, 294, 19,
FONT_NEXT, 294, 0,
FONT_NEXT, 246, 25,
FONT_NEXT, 17, 0,
FONT_NEXT, 219, 40,
FONT_END, 206, 67,
FONT_ADVANCE, 667, 0
    },
    {
83,
FONT_BEGIN, 71, 506,
FONT_NEXT, 75, 546,
FONT_NEXT, 82, 441,
FONT_NEXT, 86, 580,
FONT_NEXT, 114, 390,
FONT_NEXT, 127, 633,
FONT_END, 157, 542,
FONT_BEGIN, 114, 390,
FONT_NEXT, 157, 542,
FONT_NEXT, 163, 349,
FONT_NEXT, 171, 491,
FONT_END, 209, 448,
FONT_BEGIN, 163, 349,
FONT_NEXT, 209, 448,
FONT_NEXT, 227, 310,
FONT_END, 276, 280,
FONT_BEGIN, 276, 280,
FONT_NEXT, 209, 448,
FONT_NEXT, 314, 254,
FONT_NEXT, 324, 375,
FONT_NEXT, 343, 231,
FONT_END, 363, 210,
FONT_BEGIN, 363, 210,
FONT_NEXT, 324, 375,
FONT_NEXT, 385, 172,
FONT_NEXT, 385, 336,
FONT_NEXT, 390, 133,
FONT_NEXT, 409, 20,
FONT_END, 373, 2,
FONT_BEGIN, 390, 133,
FONT_NEXT, 373, 2,
FONT_NEXT, 382, 92,
FONT_END, 360, 57,
FONT_BEGIN, 360, 57,
FONT_NEXT, 373, 2,
FONT_END, 330, -10,
FONT_BEGIN, 360, 57,
FONT_NEXT, 330, -10,
FONT_NEXT, 322, 31,
FONT_NEXT, 280, -14,
FONT_NEXT, 270, 22,
FONT_NEXT, 227, -9,
FONT_NEXT, 223, 27,
FONT_NEXT, 185, 3,
FONT_NEXT, 183, 43,
FONT_NEXT, 151, 14,
FONT_NEXT, 124, 92,
FONT_NEXT, 125, 20,
FONT_END, 101, 9,
FONT_BEGIN, 124, 92,
FONT_NEXT, 101, 9,
FONT_NEXT, 86, 151,
FONT_NEXT, 94, -13,
FONT_END, 72, -13,
FONT_BEGIN, 86, 151,
FONT_NEXT, 72, -13,
FONT_NEXT, 65, 199,
FONT_END, 42, 199,
FONT_BEGIN, 469, 463,
FONT_NEXT, 444, 463,
FONT_NEXT, 447, 676,
FONT_END, 426, 676,
FONT_BEGIN, 426, 676,
FONT_NEXT, 444, 463,
FONT_END, 422, 524,
FONT_BEGIN, 426, 676,
FONT_NEXT, 422, 524,
FONT_NEXT, 418, 654,
FONT_NEXT, 384, 579,
FONT_NEXT, 408, 645,
FONT_END, 391, 642,
FONT_BEGIN, 391, 642,
FONT_NEXT, 384, 579,
FONT_NEXT, 367, 647,
FONT_NEXT, 328, 619,
FONT_NEXT, 334, 659,
FONT_END, 252, 676,
FONT_BEGIN, 252, 676,
FONT_NEXT, 328, 619,
FONT_END, 295, 630,
FONT_BEGIN, 252, 676,
FONT_NEXT, 295, 630,
FONT_END, 258, 635,
FONT_BEGIN, 252, 676,
FONT_NEXT, 258, 635,
FONT_END, 221, 629,
FONT_BEGIN, 252, 676,
FONT_NEXT, 221, 629,
FONT_NEXT, 185, 665,
FONT_NEXT, 188, 611,
FONT_END, 165, 582,
FONT_BEGIN, 185, 665,
FONT_NEXT, 165, 582,
FONT_NEXT, 127, 633,
FONT_END, 157, 542,
FONT_BEGIN, 491, 167,
FONT_NEXT, 478, 102,
FONT_NEXT, 476, 235,
FONT_NEXT, 439, 44,
FONT_NEXT, 438, 290,
FONT_NEXT, 409, 20,
FONT_END, 385, 336,
FONT_ADVANCE, 556, 0
    },
    {
84,
FONT_BEGIN, 17, 492,
FONT_NEXT, 23, 662,
FONT_NEXT, 41, 492,
FONT_END, 59, 557,
FONT_BEGIN, 59, 557,
FONT_NEXT, 23, 662,
FONT_NEXT, 87, 596,
FONT_END, 131, 615,
FONT_BEGIN, 131, 615,
FONT_NEXT, 23, 662,
FONT_NEXT, 200, 620,
FONT_END, 254, 620,
FONT_BEGIN, 254, 620,
FONT_NEXT, 23, 662,
FONT_END, 356, 620,
FONT_BEGIN, 254, 620,
FONT_NEXT, 356, 620,
FONT_END, 356, 109,
FONT_BEGIN, 254, 620,
FONT_NEXT, 356, 109,
FONT_NEXT, 254, 120,
FONT_END, 252, 73,
FONT_BEGIN, 252, 73,
FONT_NEXT, 356, 109,
FONT_NEXT, 241, 43,
FONT_END, 213, 26,
FONT_BEGIN, 213, 26,
FONT_NEXT, 356, 109,
FONT_END, 359, 66,
FONT_BEGIN, 213, 26,
FONT_NEXT, 359, 66,
FONT_END, 160, 0,
FONT_BEGIN, 213, 26,
FONT_NEXT, 160, 0,
FONT_END, 160, 19,
FONT_BEGIN, 593, 492,
FONT_NEXT, 569, 492,
FONT_NEXT, 587, 662,
FONT_NEXT, 550, 558,
FONT_END, 522, 597,
FONT_BEGIN, 587, 662,
FONT_NEXT, 522, 597,
FONT_END, 478, 615,
FONT_BEGIN, 587, 662,
FONT_NEXT, 478, 615,
FONT_NEXT, 23, 662,
FONT_NEXT, 410, 620,
FONT_END, 356, 620,
FONT_BEGIN, 452, 19,
FONT_NEXT, 452, 0,
FONT_NEXT, 401, 24,
FONT_NEXT, 160, 0,
FONT_NEXT, 372, 39,
FONT_END, 359, 66,
FONT_ADVANCE, 611, 0
    },
    {
85,
FONT_BEGIN, 14, 643,
FONT_NEXT, 14, 662,
FONT_NEXT, 60, 637,
FONT_END, 87, 623,
FONT_BEGIN, 87, 623,
FONT_NEXT, 14, 662,
FONT_END, 209, 596,
FONT_BEGIN, 87, 623,
FONT_NEXT, 209, 596,
FONT_END, 206, 553,
FONT_BEGIN, 87, 623,
FONT_NEXT, 206, 553,
FONT_NEXT, 100, 597,
FONT_NEXT, 206, 233,
FONT_END, 164, 50,
FONT_BEGIN, 100, 597,
FONT_NEXT, 164, 50,
FONT_NEXT, 104, 553,
FONT_NEXT, 135, 91,
FONT_END, 117, 133,
FONT_BEGIN, 104, 553,
FONT_NEXT, 117, 133,
FONT_END, 107, 175,
FONT_BEGIN, 104, 553,
FONT_NEXT, 107, 175,
FONT_END, 104, 212,
FONT_BEGIN, 104, 553,
FONT_NEXT, 104, 212,
FONT_END, 104, 241,
FONT_BEGIN, 297, 662,
FONT_NEXT, 297, 643,
FONT_NEXT, 14, 662,
FONT_NEXT, 249, 636,
FONT_END, 221, 622,
FONT_BEGIN, 14, 662,
FONT_NEXT, 221, 622,
FONT_END, 209, 596,
FONT_BEGIN, 473, 643,
FONT_NEXT, 473, 662,
FONT_NEXT, 517, 636,
FONT_NEXT, 629, 615,
FONT_END, 614, 578,
FONT_BEGIN, 517, 636,
FONT_NEXT, 614, 578,
FONT_NEXT, 546, 617,
FONT_NEXT, 611, 515,
FONT_NEXT, 562, 579,
FONT_NEXT, 611, 254,
FONT_END, 606, 178,
FONT_BEGIN, 562, 579,
FONT_NEXT, 606, 178,
FONT_END, 597, 134,
FONT_BEGIN, 562, 579,
FONT_NEXT, 597, 134,
FONT_NEXT, 567, 515,
FONT_NEXT, 578, 90,
FONT_NEXT, 567, 245,
FONT_END, 565, 192,
FONT_BEGIN, 565, 192,
FONT_NEXT, 578, 90,
FONT_END, 547, 49,
FONT_BEGIN, 565, 192,
FONT_NEXT, 547, 49,
FONT_NEXT, 560, 154,
FONT_END, 544, 109,
FONT_BEGIN, 544, 109,
FONT_NEXT, 547, 49,
FONT_END, 501, 16,
FONT_BEGIN, 544, 109,
FONT_NEXT, 501, 16,
FONT_NEXT, 517, 76,
FONT_END, 480, 51,
FONT_BEGIN, 480, 51,
FONT_NEXT, 501, 16,
FONT_END, 437, -6,
FONT_BEGIN, 480, 51,
FONT_NEXT, 437, -6,
FONT_NEXT, 434, 35,
FONT_NEXT, 352, -14,
FONT_NEXT, 380, 30,
FONT_END, 322, 36,
FONT_BEGIN, 322, 36,
FONT_NEXT, 352, -14,
FONT_END, 270, -6,
FONT_BEGIN, 322, 36,
FONT_NEXT, 270, -6,
FONT_NEXT, 279, 52,
FONT_END, 248, 77,
FONT_BEGIN, 248, 77,
FONT_NEXT, 270, -6,
FONT_END, 208, 17,
FONT_BEGIN, 248, 77,
FONT_NEXT, 208, 17,
FONT_NEXT, 227, 107,
FONT_END, 208, 174,
FONT_BEGIN, 208, 174,
FONT_NEXT, 208, 17,
FONT_NEXT, 206, 233,
FONT_END, 164, 50,
FONT_BEGIN, 705, 662,
FONT_NEXT, 705, 643,
FONT_NEXT, 473, 662,
FONT_NEXT, 657, 634,
FONT_END, 629, 615,
FONT_ADVANCE, 722, 0
    },
    {
86,
FONT_BEGIN, 282, 662,
FONT_NEXT, 282, 643,
FONT_NEXT, 16, 662,
FONT_NEXT, 246, 640,
FONT_END, 223, 635,
FONT_BEGIN, 16, 662,
FONT_NEXT, 223, 635,
FONT_END, 210, 624,
FONT_BEGIN, 16, 662,
FONT_NEXT, 210, 624,
FONT_END, 207, 606,
FONT_BEGIN, 16, 662,
FONT_NEXT, 207, 606,
FONT_END, 71, 623,
FONT_BEGIN, 16, 662,
FONT_NEXT, 71, 623,
FONT_END, 46, 638,
FONT_BEGIN, 16, 662,
FONT_NEXT, 46, 638,
FONT_END, 16, 643,
FONT_BEGIN, 549, 538,
FONT_NEXT, 556, 556,
FONT_NEXT, 546, 528,
FONT_NEXT, 383, -11,
FONT_NEXT, 399, 161,
FONT_END, 248, 499,
FONT_BEGIN, 248, 499,
FONT_NEXT, 383, -11,
FONT_END, 368, -11,
FONT_BEGIN, 248, 499,
FONT_NEXT, 368, -11,
FONT_END, 122, 538,
FONT_BEGIN, 248, 499,
FONT_NEXT, 122, 538,
FONT_NEXT, 218, 566,
FONT_END, 210, 590,
FONT_BEGIN, 210, 590,
FONT_NEXT, 122, 538,
FONT_NEXT, 207, 606,
FONT_NEXT, 94, 592,
FONT_END, 71, 623,
FONT_BEGIN, 492, 643,
FONT_NEXT, 492, 662,
FONT_NEXT, 542, 634,
FONT_NEXT, 625, 596,
FONT_NEXT, 558, 621,
FONT_NEXT, 605, 550,
FONT_NEXT, 565, 597,
FONT_END, 556, 556,
FONT_BEGIN, 556, 556,
FONT_NEXT, 605, 550,
FONT_END, 383, -11,
FONT_BEGIN, 556, 556,
FONT_END, 383, -11,
FONT_BEGIN, 697, 662,
FONT_NEXT, 697, 643,
FONT_NEXT, 492, 662,
FONT_NEXT, 666, 637,
FONT_END, 644, 623,
FONT_BEGIN, 492, 662,
FONT_NEXT, 644, 623,
FONT_END, 625, 596,
FONT_ADVANCE, 722, 0
    },
    {
87,
FONT_BEGIN, 125, 480,
FONT_NEXT, 108, 526,
FONT_NEXT, 147, 421,
FONT_END, 185, 609,
FONT_BEGIN, 185, 609,
FONT_NEXT, 108, 526,
FONT_END, 86, 578,
FONT_BEGIN, 185, 609,
FONT_NEXT, 86, 578,
FONT_END, 65, 613,
FONT_BEGIN, 185, 609,
FONT_NEXT, 65, 613,
FONT_END, 40, 634,
FONT_BEGIN, 185, 609,
FONT_NEXT, 40, 634,
FONT_NEXT, 5, 662,
FONT_END, 5, 643,
FONT_BEGIN, 313, 643,
FONT_NEXT, 313, 662,
FONT_NEXT, 346, 640,
FONT_END, 371, 628,
FONT_BEGIN, 371, 628,
FONT_NEXT, 313, 662,
FONT_END, 503, 605,
FONT_BEGIN, 371, 628,
FONT_NEXT, 503, 605,
FONT_NEXT, 391, 601,
FONT_END, 414, 553,
FONT_BEGIN, 414, 553,
FONT_NEXT, 503, 605,
FONT_NEXT, 447, 471,
FONT_NEXT, 470, 412,
FONT_END, 316, -11,
FONT_BEGIN, 447, 471,
FONT_NEXT, 316, -11,
FONT_NEXT, 340, 189,
FONT_END, 196, 565,
FONT_BEGIN, 196, 565,
FONT_NEXT, 316, -11,
FONT_END, 301, -11,
FONT_BEGIN, 196, 565,
FONT_NEXT, 301, -11,
FONT_END, 279, 51,
FONT_BEGIN, 196, 565,
FONT_NEXT, 279, 51,
FONT_END, 254, 123,
FONT_BEGIN, 196, 565,
FONT_NEXT, 254, 123,
FONT_END, 199, 277,
FONT_BEGIN, 196, 565,
FONT_NEXT, 199, 277,
FONT_END, 147, 421,
FONT_BEGIN, 196, 565,
FONT_NEXT, 147, 421,
FONT_END, 185, 609,
FONT_BEGIN, 580, 662,
FONT_NEXT, 580, 643,
FONT_NEXT, 313, 662,
FONT_NEXT, 529, 637,
FONT_END, 510, 626,
FONT_BEGIN, 313, 662,
FONT_NEXT, 510, 626,
FONT_END, 503, 605,
FONT_BEGIN, 734, 643,
FONT_NEXT, 734, 662,
FONT_NEXT, 781, 633,
FONT_NEXT, 864, 606,
FONT_END, 853, 582,
FONT_BEGIN, 781, 633,
FONT_NEXT, 853, 582,
FONT_NEXT, 797, 620,
FONT_END, 803, 597,
FONT_BEGIN, 803, 597,
FONT_NEXT, 853, 582,
FONT_END, 745, 288,
FONT_BEGIN, 803, 597,
FONT_NEXT, 745, 288,
FONT_NEXT, 787, 525,
FONT_END, 662, 186,
FONT_BEGIN, 662, 186,
FONT_NEXT, 745, 288,
FONT_END, 645, -11,
FONT_BEGIN, 662, 186,
FONT_NEXT, 645, -11,
FONT_NEXT, 530, 527,
FONT_NEXT, 630, -11,
FONT_END, 470, 412,
FONT_BEGIN, 530, 527,
FONT_NEXT, 470, 412,
FONT_NEXT, 510, 574,
FONT_END, 503, 605,
FONT_BEGIN, 932, 662,
FONT_NEXT, 932, 643,
FONT_NEXT, 734, 662,
FONT_NEXT, 900, 635,
FONT_END, 879, 624,
FONT_BEGIN, 734, 662,
FONT_NEXT, 879, 624,
FONT_END, 864, 606,
FONT_BEGIN, 250, 662,
FONT_NEXT, 250, 643,
FONT_NEXT, 5, 662,
FONT_NEXT, 220, 640,
FONT_END, 200, 635,
FONT_BEGIN, 5, 662,
FONT_NEXT, 200, 635,
FONT_END, 188, 625,
FONT_BEGIN, 5, 662,
FONT_NEXT, 188, 625,
FONT_END, 185, 609,
FONT_ADVANCE, 944, 0
    },
    {
88,
FONT_BEGIN, 22, 643,
FONT_NEXT, 22, 662,
FONT_NEXT, 60, 637,
FONT_END, 96, 618,
FONT_BEGIN, 96, 618,
FONT_NEXT, 22, 662,
FONT_END, 248, 611,
FONT_BEGIN, 96, 618,
FONT_NEXT, 248, 611,
FONT_NEXT, 116, 600,
FONT_END, 139, 573,
FONT_BEGIN, 139, 573,
FONT_NEXT, 248, 611,
FONT_NEXT, 168, 535,
FONT_END, 203, 486,
FONT_BEGIN, 203, 486,
FONT_NEXT, 248, 611,
FONT_END, 251, 592,
FONT_BEGIN, 203, 486,
FONT_NEXT, 251, 592,
FONT_END, 265, 565,
FONT_BEGIN, 203, 486,
FONT_NEXT, 265, 565,
FONT_END, 291, 523,
FONT_BEGIN, 203, 486,
FONT_NEXT, 291, 523,
FONT_NEXT, 312, 326,
FONT_NEXT, 333, 463,
FONT_END, 338, 288,
FONT_BEGIN, 312, 326,
FONT_NEXT, 338, 288,
FONT_END, 219, 140,
FONT_BEGIN, 312, 326,
FONT_NEXT, 219, 140,
FONT_NEXT, 155, 133,
FONT_NEXT, 185, 95,
FONT_END, 172, 73,
FONT_BEGIN, 155, 133,
FONT_NEXT, 172, 73,
FONT_END, 167, 56,
FONT_BEGIN, 155, 133,
FONT_NEXT, 167, 56,
FONT_NEXT, 128, 100,
FONT_END, 106, 74,
FONT_BEGIN, 106, 74,
FONT_NEXT, 167, 56,
FONT_NEXT, 73, 41,
FONT_NEXT, 10, 0,
FONT_NEXT, 44, 24,
FONT_END, 10, 19,
FONT_BEGIN, 324, 662,
FONT_NEXT, 324, 643,
FONT_NEXT, 22, 662,
FONT_NEXT, 290, 641,
FONT_END, 266, 637,
FONT_BEGIN, 22, 662,
FONT_NEXT, 266, 637,
FONT_END, 252, 628,
FONT_BEGIN, 22, 662,
FONT_NEXT, 252, 628,
FONT_END, 248, 611,
FONT_BEGIN, 458, 643,
FONT_NEXT, 458, 662,
FONT_NEXT, 505, 637,
FONT_NEXT, 592, 599,
FONT_NEXT, 521, 628,
FONT_END, 528, 610,
FONT_BEGIN, 528, 610,
FONT_NEXT, 592, 599,
FONT_END, 547, 549,
FONT_BEGIN, 528, 610,
FONT_NEXT, 547, 549,
FONT_NEXT, 516, 579,
FONT_NEXT, 401, 367,
FONT_NEXT, 488, 542,
FONT_END, 375, 404,
FONT_BEGIN, 375, 404,
FONT_NEXT, 401, 367,
FONT_END, 338, 288,
FONT_BEGIN, 375, 404,
FONT_NEXT, 338, 288,
FONT_END, 333, 463,
FONT_BEGIN, 696, 662,
FONT_NEXT, 696, 643,
FONT_NEXT, 458, 662,
FONT_NEXT, 660, 638,
FONT_END, 627, 627,
FONT_BEGIN, 458, 662,
FONT_NEXT, 627, 627,
FONT_END, 592, 599,
FONT_BEGIN, 704, 19,
FONT_NEXT, 704, 0,
FONT_NEXT, 670, 24,
FONT_NEXT, 407, 0,
FONT_NEXT, 643, 37,
FONT_END, 619, 58,
FONT_BEGIN, 619, 58,
FONT_NEXT, 407, 0,
FONT_END, 464, 25,
FONT_BEGIN, 619, 58,
FONT_NEXT, 464, 25,
FONT_NEXT, 593, 93,
FONT_NEXT, 479, 35,
FONT_END, 484, 53,
FONT_BEGIN, 593, 93,
FONT_NEXT, 484, 53,
FONT_NEXT, 401, 367,
FONT_NEXT, 478, 72,
FONT_END, 464, 98,
FONT_BEGIN, 401, 367,
FONT_NEXT, 464, 98,
FONT_END, 433, 148,
FONT_BEGIN, 401, 367,
FONT_NEXT, 433, 148,
FONT_END, 338, 288,
FONT_BEGIN, 407, 19,
FONT_NEXT, 440, 21,
FONT_NEXT, 407, 0,
FONT_END, 464, 25,
FONT_BEGIN, 243, 19,
FONT_NEXT, 243, 0,
FONT_NEXT, 190, 26,
FONT_NEXT, 10, 0,
FONT_NEXT, 173, 37,
FONT_END, 167, 56,
FONT_ADVANCE, 722, 0
    },
    {
89,
FONT_BEGIN, 302, 662,
FONT_NEXT, 302, 643,
FONT_NEXT, 22, 662,
FONT_NEXT, 271, 641,
FONT_END, 249, 637,
FONT_BEGIN, 22, 662,
FONT_NEXT, 249, 637,
FONT_END, 235, 628,
FONT_BEGIN, 22, 662,
FONT_NEXT, 235, 628,
FONT_END, 231, 612,
FONT_BEGIN, 22, 662,
FONT_NEXT, 231, 612,
FONT_END, 81, 619,
FONT_BEGIN, 22, 662,
FONT_NEXT, 81, 619,
FONT_END, 51, 637,
FONT_BEGIN, 22, 662,
FONT_NEXT, 51, 637,
FONT_END, 22, 643,
FONT_BEGIN, 484, 643,
FONT_NEXT, 484, 662,
FONT_NEXT, 530, 638,
FONT_NEXT, 646, 624,
FONT_END, 610, 590,
FONT_BEGIN, 530, 638,
FONT_NEXT, 610, 590,
FONT_NEXT, 546, 628,
FONT_NEXT, 589, 564,
FONT_NEXT, 553, 610,
FONT_NEXT, 565, 529,
FONT_NEXT, 539, 573,
FONT_NEXT, 417, 303,
FONT_NEXT, 396, 347,
FONT_NEXT, 417, 109,
FONT_END, 313, 72,
FONT_BEGIN, 396, 347,
FONT_NEXT, 313, 72,
FONT_END, 315, 120,
FONT_BEGIN, 396, 347,
FONT_NEXT, 315, 120,
FONT_END, 315, 294,
FONT_BEGIN, 396, 347,
FONT_NEXT, 315, 294,
FONT_NEXT, 248, 569,
FONT_NEXT, 184, 486,
FONT_NEXT, 231, 612,
FONT_NEXT, 149, 535,
FONT_END, 121, 573,
FONT_BEGIN, 231, 612,
FONT_NEXT, 121, 573,
FONT_END, 99, 600,
FONT_BEGIN, 231, 612,
FONT_NEXT, 99, 600,
FONT_END, 81, 619,
FONT_BEGIN, 703, 662,
FONT_NEXT, 703, 643,
FONT_NEXT, 484, 662,
FONT_NEXT, 675, 638,
FONT_END, 646, 624,
FONT_BEGIN, 520, 19,
FONT_NEXT, 520, 0,
FONT_NEXT, 465, 23,
FONT_NEXT, 214, 0,
FONT_NEXT, 434, 37,
FONT_END, 420, 64,
FONT_BEGIN, 420, 64,
FONT_NEXT, 214, 0,
FONT_END, 271, 24,
FONT_BEGIN, 420, 64,
FONT_NEXT, 271, 24,
FONT_END, 301, 41,
FONT_BEGIN, 420, 64,
FONT_NEXT, 301, 41,
FONT_NEXT, 417, 109,
FONT_END, 313, 72,
FONT_BEGIN, 214, 19,
FONT_NEXT, 246, 20,
FONT_NEXT, 214, 0,
FONT_END, 271, 24,
FONT_ADVANCE, 722, 0
    },
    {
90,
FONT_BEGIN, 577, 662,
FONT_NEXT, 577, 647,
FONT_NEXT, 51, 662,
FONT_NEXT, 446, 624,
FONT_END, 225, 624,
FONT_BEGIN, 51, 662,
FONT_NEXT, 225, 624,
FONT_END, 177, 621,
FONT_BEGIN, 51, 662,
FONT_NEXT, 177, 621,
FONT_END, 140, 612,
FONT_BEGIN, 51, 662,
FONT_NEXT, 140, 612,
FONT_END, 112, 599,
FONT_BEGIN, 51, 662,
FONT_NEXT, 112, 599,
FONT_END, 92, 582,
FONT_BEGIN, 51, 662,
FONT_NEXT, 92, 582,
FONT_END, 68, 540,
FONT_BEGIN, 51, 662,
FONT_NEXT, 68, 540,
FONT_END, 57, 491,
FONT_BEGIN, 51, 662,
FONT_NEXT, 57, 491,
FONT_END, 31, 491,
FONT_BEGIN, 597, 176,
FONT_NEXT, 573, 0,
FONT_NEXT, 574, 176,
FONT_END, 556, 123,
FONT_BEGIN, 556, 123,
FONT_NEXT, 573, 0,
FONT_NEXT, 528, 79,
FONT_END, 481, 49,
FONT_BEGIN, 481, 49,
FONT_NEXT, 573, 0,
FONT_NEXT, 446, 40,
FONT_NEXT, 9, 0,
FONT_NEXT, 402, 38,
FONT_END, 145, 38,
FONT_BEGIN, 145, 38,
FONT_NEXT, 9, 0,
FONT_END, 9, 15,
FONT_BEGIN, 145, 38,
FONT_NEXT, 9, 15,
FONT_END, 446, 624,
FONT_BEGIN, 145, 38,
FONT_NEXT, 446, 624,
FONT_END, 577, 647,
FONT_ADVANCE, 611, 0
    },
    {
91,
FONT_BEGIN, 299, 662,
FONT_NEXT, 299, 637,
FONT_NEXT, 88, 662,
FONT_NEXT, 209, 637,
FONT_END, 182, 632,
FONT_BEGIN, 88, 662,
FONT_NEXT, 182, 632,
FONT_END, 169, 620,
FONT_BEGIN, 88, 662,
FONT_NEXT, 169, 620,
FONT_END, 164, 593,
FONT_BEGIN, 88, 662,
FONT_NEXT, 164, 593,
FONT_END, 164, -79,
FONT_BEGIN, 88, 662,
FONT_NEXT, 164, -79,
FONT_NEXT, 88, -156,
FONT_NEXT, 170, -110,
FONT_END, 184, -125,
FONT_BEGIN, 88, -156,
FONT_NEXT, 184, -125,
FONT_END, 213, -131,
FONT_BEGIN, 88, -156,
FONT_NEXT, 213, -131,
FONT_NEXT, 299, -156,
FONT_END, 299, -131,
FONT_ADVANCE, 333, 0
    },
    {
92,
FONT_BEGIN, 287, -14,
FONT_NEXT, 219, -14,
FONT_NEXT, 58, 676,
FONT_END, -9, 676,
FONT_ADVANCE, 278, 0
    },
    {
93,
FONT_BEGIN, 34, 637,
FONT_NEXT, 34, 662,
FONT_NEXT, 120, 637,
FONT_END, 148, 630,
FONT_BEGIN, 148, 630,
FONT_NEXT, 34, 662,
FONT_END, 245, 662,
FONT_BEGIN, 148, 630,
FONT_NEXT, 245, 662,
FONT_NEXT, 162, 615,
FONT_END, 169, 585,
FONT_BEGIN, 169, 585,
FONT_NEXT, 245, 662,
FONT_END, 245, -156,
FONT_BEGIN, 169, 585,
FONT_NEXT, 245, -156,
FONT_NEXT, 169, -87,
FONT_END, 163, -115,
FONT_BEGIN, 163, -115,
FONT_NEXT, 245, -156,
FONT_NEXT, 150, -127,
FONT_NEXT, 34, -156,
FONT_NEXT, 124, -131,
FONT_END, 34, -131,
FONT_ADVANCE, 333, 0
    },
    {
94,
FONT_BEGIN, 446, 297,
FONT_NEXT, 378, 297,
FONT_NEXT, 265, 662,
FONT_NEXT, 235, 586,
FONT_NEXT, 205, 662,
FONT_NEXT, 92, 297,
FONT_END, 24, 297,
FONT_ADVANCE, 469, 0
    },
    {
95,
FONT_BEGIN, 500, -75,
FONT_NEXT, 500, -125,
FONT_NEXT, 0, -75,
FONT_END, 0, -125,
FONT_ADVANCE, 500, 0
    },
    {
96,
FONT_BEGIN, 236, 657,
FONT_NEXT, 195, 625,
FONT_NEXT, 227, 676,
FONT_END, 196, 658,
FONT_BEGIN, 196, 658,
FONT_NEXT, 195, 625,
FONT_NEXT, 159, 625,
FONT_NEXT, 170, 595,
FONT_END, 157, 569,
FONT_BEGIN, 159, 625,
FONT_NEXT, 157, 569,
FONT_NEXT, 128, 580,
FONT_NEXT, 154, 551,
FONT_END, 146, 449,
FONT_BEGIN, 128, 580,
FONT_NEXT, 146, 449,
FONT_END, 124, 478,
FONT_BEGIN, 128, 580,
FONT_NEXT, 124, 478,
FONT_END, 115, 529,
FONT_BEGIN, 254, 491,
FONT_NEXT, 248, 463,
FONT_NEXT, 247, 515,
FONT_NEXT, 234, 445,

FONT_NEXT, 232, 531,
FONT_NEXT, 196, 433,
FONT_NEXT, 192, 541,
FONT_NEXT, 173, 436,
FONT_NEXT, 177, 539,
FONT_END, 168, 537,
FONT_BEGIN, 168, 537,
FONT_NEXT, 173, 436,
FONT_END, 146, 449,
FONT_BEGIN, 168, 537,
FONT_NEXT, 146, 449,
FONT_NEXT, 158, 540,
FONT_END, 154, 551,
FONT_ADVANCE, 333, 0
    },
    {
97,
FONT_BEGIN, 56, 349,
FONT_NEXT, 65, 383,
FONT_NEXT, 65, 321,
FONT_END, 79, 309,
FONT_BEGIN, 79, 309,
FONT_NEXT, 65, 383,
FONT_END, 94, 419,
FONT_BEGIN, 79, 309,
FONT_NEXT, 94, 419,
FONT_NEXT, 99, 305,
FONT_END, 131, 318,
FONT_BEGIN, 131, 318,
FONT_NEXT, 94, 419,
FONT_END, 139, 387,
FONT_BEGIN, 131, 318,
FONT_NEXT, 139, 387,
FONT_END, 144, 348,
FONT_BEGIN, 37, 94,
FONT_NEXT, 41, 128,
FONT_NEXT, 45, 49,
FONT_NEXT, 53, 158,
FONT_NEXT, 69, 16,
FONT_NEXT, 73, 183,
FONT_END, 100, 206,
FONT_BEGIN, 69, 16,
FONT_NEXT, 100, 206,
FONT_NEXT, 103, -4,
FONT_END, 125, 126,
FONT_BEGIN, 125, 126,
FONT_NEXT, 100, 206,
FONT_NEXT, 125, 128,
FONT_END, 129, 157,
FONT_BEGIN, 129, 157,
FONT_NEXT, 100, 206,
FONT_NEXT, 149, 192,
FONT_END, 169, 210,
FONT_BEGIN, 169, 210,
FONT_NEXT, 100, 206,
FONT_END, 179, 248,
FONT_BEGIN, 169, 210,
FONT_NEXT, 179, 248,
FONT_NEXT, 197, 229,
FONT_END, 236, 249,
FONT_BEGIN, 236, 249,
FONT_NEXT, 179, 248,
FONT_NEXT, 287, 268,
FONT_NEXT, 287, 292,
FONT_NEXT, 287, 123,
FONT_NEXT, 287, 353,
FONT_NEXT, 288, 63,
FONT_END, 297, 21,
FONT_BEGIN, 297, 21,
FONT_NEXT, 287, 353,
FONT_NEXT, 305, 443,
FONT_NEXT, 279, 396,
FONT_NEXT, 268, 455,
FONT_NEXT, 259, 421,
FONT_NEXT, 219, 460,
FONT_NEXT, 234, 433,
FONT_END, 210, 436,
FONT_BEGIN, 219, 460,
FONT_NEXT, 210, 436,
FONT_NEXT, 178, 456,
FONT_NEXT, 162, 423,
FONT_NEXT, 144, 448,
FONT_NEXT, 145, 407,
FONT_END, 139, 387,
FONT_BEGIN, 144, 448,
FONT_NEXT, 139, 387,
FONT_END, 94, 419,
FONT_BEGIN, 287, 123,
FONT_NEXT, 288, 63,
FONT_NEXT, 281, 88,
FONT_NEXT, 222, 14,
FONT_NEXT, 271, 77,
FONT_END, 253, 66,
FONT_BEGIN, 253, 66,
FONT_NEXT, 222, 14,
FONT_NEXT, 226, 55,
FONT_END, 191, 48,
FONT_BEGIN, 191, 48,
FONT_NEXT, 222, 14,
FONT_END, 183, -4,
FONT_BEGIN, 191, 48,
FONT_NEXT, 183, -4,
FONT_NEXT, 165, 53,
FONT_NEXT, 143, -10,
FONT_NEXT, 144, 69,
FONT_END, 130, 93,
FONT_BEGIN, 130, 93,
FONT_NEXT, 143, -10,
FONT_END, 103, -4,
FONT_BEGIN, 130, 93,
FONT_NEXT, 103, -4,
FONT_END, 125, 126,
FONT_BEGIN, 368, 300,
FONT_NEXT, 368, 105,
FONT_NEXT, 365, 350,
FONT_NEXT, 353, -10,
FONT_NEXT, 349, 402,
FONT_NEXT, 315, -1,
FONT_NEXT, 331, 425,
FONT_END, 305, 443,
FONT_BEGIN, 305, 443,
FONT_NEXT, 315, -1,
FONT_END, 297, 21,
FONT_BEGIN, 305, 443,
FONT_END, 297, 21,
FONT_BEGIN, 442, 66,
FONT_NEXT, 442, 40,
FONT_NEXT, 423, 53,
FONT_NEXT, 412, 11,
FONT_NEXT, 397, 47,
FONT_NEXT, 387, -4,
FONT_NEXT, 380, 51,
FONT_NEXT, 353, -10,
FONT_NEXT, 371, 65,
FONT_END, 368, 105,
FONT_ADVANCE, 444, 0
    },
    {
98,
FONT_BEGIN, 3, 623,
FONT_NEXT, 3, 639,
FONT_NEXT, 25, 624,
FONT_END, 50, 620,
FONT_BEGIN, 50, 620,
FONT_NEXT, 3, 639,
FONT_END, 148, 683,
FONT_BEGIN, 50, 620,
FONT_NEXT, 148, 683,
FONT_NEXT, 63, 609,
FONT_END, 69, 573,
FONT_BEGIN, 69, 573,
FONT_NEXT, 148, 683,
FONT_END, 127, 8,
FONT_BEGIN, 69, 573,
FONT_NEXT, 127, 8,
FONT_END, 86, 28,
FONT_BEGIN, 69, 573,
FONT_NEXT, 86, 28,
FONT_END, 73, 40,
FONT_BEGIN, 69, 573,
FONT_NEXT, 73, 40,
FONT_END, 69, 54,
FONT_BEGIN, 153, 379,
FONT_NEXT, 155, 379,
FONT_NEXT, 153, 322,
FONT_END, 165, 357,
FONT_BEGIN, 165, 357,
FONT_NEXT, 155, 379,
FONT_END, 167, 401,
FONT_BEGIN, 165, 357,
FONT_NEXT, 167, 401,
FONT_NEXT, 192, 380,
FONT_NEXT, 196, 428,
FONT_NEXT, 224, 393,
FONT_NEXT, 237, 450,
FONT_NEXT, 253, 397,
FONT_END, 284, 392,
FONT_BEGIN, 284, 392,
FONT_NEXT, 237, 450,
FONT_END, 290, 460,
FONT_BEGIN, 284, 392,
FONT_NEXT, 290, 460,
FONT_NEXT, 311, 379,
FONT_NEXT, 332, 454,
FONT_NEXT, 350, 333,
FONT_NEXT, 368, 440,
FONT_NEXT, 372, 270,
FONT_END, 380, 201,
FONT_BEGIN, 380, 201,
FONT_NEXT, 368, 440,
FONT_END, 399, 417,
FONT_BEGIN, 380, 201,
FONT_NEXT, 399, 417,
FONT_END, 382, 44,
FONT_BEGIN, 380, 201,
FONT_NEXT, 382, 44,
FONT_NEXT, 375, 145,
FONT_NEXT, 340, 16,
FONT_NEXT, 357, 86,
FONT_END, 340, 61,
FONT_BEGIN, 340, 61,
FONT_NEXT, 340, 16,
FONT_NEXT, 317, 40,
FONT_NEXT, 287, -4,
FONT_NEXT, 287, 26,
FONT_END, 249, 22,
FONT_BEGIN, 249, 22,
FONT_NEXT, 287, -4,
FONT_END, 224, -10,
FONT_BEGIN, 249, 22,
FONT_NEXT, 224, -10,
FONT_NEXT, 223, 24,
FONT_NEXT, 178, -6,
FONT_NEXT, 191, 32,
FONT_END, 164, 47,
FONT_BEGIN, 164, 47,
FONT_NEXT, 178, -6,
FONT_END, 127, 8,
FONT_BEGIN, 164, 47,
FONT_NEXT, 127, 8,
FONT_NEXT, 153, 70,
FONT_NEXT, 148, 683,
FONT_NEXT, 153, 322,
FONT_END, 153, 379,
FONT_BEGIN, 153, 379,
FONT_NEXT, 148, 683,
FONT_END, 153, 681,
FONT_BEGIN, 153, 379,
FONT_END, 153, 681,
FONT_BEGIN, 468, 238,
FONT_NEXT, 455, 161,
FONT_NEXT, 457, 317,
FONT_END, 424, 389,
FONT_BEGIN, 424, 389,
FONT_NEXT, 455, 161,
FONT_END, 439, 119,
FONT_BEGIN, 424, 389,
FONT_NEXT, 439, 119,
FONT_END, 415, 80,
FONT_BEGIN, 424, 389,
FONT_NEXT, 415, 80,
FONT_NEXT, 399, 417,
FONT_END, 382, 44,
FONT_ADVANCE, 500, 0
    },
    {
99,
FONT_BEGIN, 25, 213,
FONT_NEXT, 30, 270,
FONT_NEXT, 29, 158,
FONT_END, 42, 111,
FONT_BEGIN, 42, 111,
FONT_NEXT, 30, 270,
FONT_END, 44, 320,
FONT_BEGIN, 42, 111,
FONT_NEXT, 44, 320,
FONT_NEXT, 63, 73,
FONT_NEXT, 67, 363,
FONT_NEXT, 88, 42,
FONT_NEXT, 96, 398,
FONT_NEXT, 102, 253,
FONT_END, 106, 302,
FONT_BEGIN, 106, 302,
FONT_NEXT, 96, 398,
FONT_NEXT, 116, 342,
FONT_NEXT, 130, 425,
FONT_NEXT, 133, 373,
FONT_END, 152, 397,
FONT_BEGIN, 152, 397,
FONT_NEXT, 130, 425,
FONT_END, 168, 444,
FONT_BEGIN, 152, 397,
FONT_NEXT, 168, 444,
FONT_NEXT, 195, 423,
FONT_END, 231, 431,
FONT_BEGIN, 231, 431,
FONT_NEXT, 168, 444,
FONT_END, 249, 460,
FONT_BEGIN, 231, 431,
FONT_NEXT, 249, 460,
FONT_NEXT, 262, 427,
FONT_END, 280, 418,
FONT_BEGIN, 280, 418,
FONT_NEXT, 249, 460,
FONT_END, 306, 450,
FONT_BEGIN, 280, 418,
FONT_NEXT, 306, 450,
FONT_NEXT, 297, 383,
FONT_END, 303, 361,
FONT_BEGIN, 303, 361,
FONT_NEXT, 306, 450,
FONT_NEXT, 320, 329,
FONT_END, 334, 318,
FONT_BEGIN, 334, 318,
FONT_NEXT, 306, 450,
FONT_NEXT, 352, 315,
FONT_NEXT, 355, 429,
FONT_NEXT, 386, 329,
FONT_NEXT, 387, 392,
FONT_END, 398, 359,
FONT_BEGIN, 412, 147,
FONT_NEXT, 369, 74,
FONT_NEXT, 398, 156,
FONT_END, 340, 89,
FONT_BEGIN, 340, 89,
FONT_NEXT, 369, 74,
FONT_END, 322, 25,
FONT_BEGIN, 340, 89,
FONT_NEXT, 322, 25,
FONT_NEXT, 302, 69,
FONT_NEXT, 269, -2,
FONT_NEXT, 255, 62,
FONT_NEXT, 212, -10,
FONT_NEXT, 195, 75,
FONT_NEXT, 149, 2,
FONT_NEXT, 147, 114,
FONT_NEXT, 88, 42,
FONT_NEXT, 114, 174,
FONT_END, 102, 253,
FONT_ADVANCE, 444, 0
    },
    {
100,
FONT_BEGIN, 113, 247,
FONT_NEXT, 116, 195,
FONT_NEXT, 103, 25,
FONT_NEXT, 125, 152,
FONT_END, 139, 116,
FONT_BEGIN, 103, 25,
FONT_NEXT, 139, 116,
FONT_END, 157, 88,
FONT_BEGIN, 103, 25,
FONT_NEXT, 157, 88,
FONT_NEXT, 164, -4,
FONT_NEXT, 201, 53,
FONT_NEXT, 209, -10,
FONT_NEXT, 251, 42,
FONT_NEXT, 257, -4,
FONT_NEXT, 281, 46,
FONT_NEXT, 294, 11,
FONT_NEXT, 308, 58,
FONT_NEXT, 320, 32,
FONT_NEXT, 328, 77,
FONT_NEXT, 338, 54,
FONT_END, 340, 54,
FONT_BEGIN, 340, 54,
FONT_NEXT, 328, 77,
FONT_END, 340, 102,
FONT_BEGIN, 340, 54,
FONT_NEXT, 340, 102,
FONT_NEXT, 340, -7,
FONT_NEXT, 340, 332,
FONT_NEXT, 340, 417,
FONT_NEXT, 327, 376,
FONT_NEXT, 290, 449,
FONT_NEXT, 303, 407,
FONT_END, 271, 425,
FONT_BEGIN, 290, 449,
FONT_NEXT, 271, 425,
FONT_NEXT, 234, 460,
FONT_NEXT, 237, 432,
FONT_END, 194, 422,
FONT_BEGIN, 234, 460,
FONT_NEXT, 194, 422,
FONT_NEXT, 192, 454,
FONT_NEXT, 154, 390,
FONT_NEXT, 154, 439,
FONT_END, 119, 416,
FONT_BEGIN, 119, 416,
FONT_NEXT, 154, 390,
FONT_END, 124, 333,
FONT_BEGIN, 119, 416,
FONT_NEXT, 124, 333,
FONT_END, 116, 293,
FONT_BEGIN, 119, 416,
FONT_NEXT, 116, 293,
FONT_NEXT, 88, 386,
FONT_NEXT, 113, 247,
FONT_END, 103, 25,
FONT_BEGIN, 88, 386,
FONT_NEXT, 103, 25,
FONT_END, 74, 53,
FONT_BEGIN, 88, 386,
FONT_NEXT, 74, 53,
FONT_NEXT, 43, 307,
FONT_NEXT, 49, 92,
FONT_END, 33, 145,
FONT_BEGIN, 43, 307,
FONT_NEXT, 33, 145,
FONT_NEXT, 31, 262,
FONT_END, 27, 214,
FONT_BEGIN, 272, 623,
FONT_NEXT, 272, 639,
FONT_NEXT, 296, 624,
FONT_END, 321, 620,
FONT_BEGIN, 321, 620,
FONT_NEXT, 272, 639,
FONT_END, 419, 683,
FONT_BEGIN, 321, 620,
FONT_NEXT, 419, 683,
FONT_NEXT, 334, 609,
FONT_END, 340, 573,
FONT_BEGIN, 340, 573,
FONT_NEXT, 419, 683,
FONT_END, 344, -10,
FONT_BEGIN, 340, 573,
FONT_NEXT, 344, -10,
FONT_END, 340, -7,
FONT_BEGIN, 340, 573,
FONT_NEXT, 340, -7,
FONT_END, 340, 417,
FONT_BEGIN, 491, 58,
FONT_NEXT, 491, 42,
FONT_NEXT, 452, 58,
FONT_NEXT, 344, -10,
FONT_NEXT, 432, 68,
FONT_END, 425, 87,
FONT_BEGIN, 425, 87,
FONT_NEXT, 344, -10,
FONT_NEXT, 424, 114,
FONT_NEXT, 419, 683,
FONT_END, 424, 681,
FONT_ADVANCE, 500, 0
    },
    {
101,
FONT_BEGIN, 424, 157,
FONT_NEXT, 417, 140,
FONT_NEXT, 408, 164,
FONT_NEXT, 406, 117,
FONT_NEXT, 386, 131,
FONT_NEXT, 368, 61,
FONT_NEXT, 355, 97,
FONT_NEXT, 304, 11,
FONT_NEXT, 311, 70,
FONT_END, 253, 59,
FONT_BEGIN, 253, 59,
FONT_NEXT, 304, 11,
FONT_END, 262, -5,
FONT_BEGIN, 253, 59,
FONT_NEXT, 262, -5,
FONT_END, 212, -10,
FONT_BEGIN, 253, 59,
FONT_NEXT, 212, -10,
FONT_NEXT, 202, 68,
FONT_NEXT, 172, -7,
FONT_NEXT, 153, 101,
FONT_NEXT, 135, 5,
FONT_NEXT, 132, 130,
FONT_NEXT, 104, 23,
FONT_NEXT, 114, 168,
FONT_END, 101, 216,
FONT_BEGIN, 101, 216,
FONT_NEXT, 104, 23,
FONT_END, 76, 48,
FONT_BEGIN, 101, 216,
FONT_NEXT, 76, 48,
FONT_NEXT, 94, 277,
FONT_END, 68, 373,
FONT_BEGIN, 68, 373,
FONT_NEXT, 76, 48,
FONT_END, 54, 80,
FONT_BEGIN, 68, 373,
FONT_NEXT, 54, 80,
FONT_NEXT, 45, 330,
FONT_NEXT, 38, 119,
FONT_NEXT, 30, 277,
FONT_NEXT, 28, 163,
FONT_END, 25, 214,
FONT_BEGIN, 303, 309,
FONT_NEXT, 94, 277,
FONT_NEXT, 97, 309,
FONT_NEXT, 68, 373,
FONT_END, 97, 406,
FONT_BEGIN, 97, 309,
FONT_NEXT, 97, 406,
FONT_NEXT, 113, 364,
FONT_NEXT, 129, 430,
FONT_NEXT, 137, 399,
FONT_NEXT, 163, 447,
FONT_NEXT, 168, 418,
FONT_END, 207, 424,
FONT_BEGIN, 207, 424,
FONT_NEXT, 163, 447,
FONT_END, 229, 460,
FONT_BEGIN, 207, 424,
FONT_NEXT, 229, 460,
FONT_NEXT, 254, 412,
FONT_END, 282, 384,
FONT_BEGIN, 282, 384,
FONT_NEXT, 229, 460,
FONT_END, 294, 449,
FONT_BEGIN, 282, 384,
FONT_NEXT, 294, 449,
FONT_NEXT, 303, 309,
FONT_NEXT, 348, 417,
FONT_END, 386, 360,
FONT_BEGIN, 303, 309,
FONT_NEXT, 386, 360,
FONT_END, 398, 322,
FONT_BEGIN, 303, 309,
FONT_NEXT, 398, 322,
FONT_NEXT, 94, 277,
FONT_END, 405, 277,
FONT_ADVANCE, 444, 0
    },
    {
102,
FONT_BEGIN, 383, 622,
FONT_NEXT, 379, 603,
FONT_NEXT, 375, 645,
FONT_NEXT, 369, 590,
FONT_NEXT, 354, 665,
FONT_NEXT, 340, 580,
FONT_NEXT, 322, 678,
FONT_NEXT, 246, 655,
FONT_NEXT, 283, 683,
FONT_END, 223, 675,
FONT_BEGIN, 223, 675,
FONT_NEXT, 246, 655,
FONT_END, 213, 646,
FONT_BEGIN, 223, 675,
FONT_NEXT, 213, 646,
FONT_NEXT, 178, 654,
FONT_NEXT, 195, 624,
FONT_END, 186, 566,
FONT_BEGIN, 178, 654,
FONT_NEXT, 186, 566,
FONT_END, 186, 450,
FONT_BEGIN, 178, 654,
FONT_NEXT, 186, 450,
FONT_END, 99, 58,
FONT_BEGIN, 178, 654,
FONT_NEXT, 99, 58,
FONT_END, 103, 104,
FONT_BEGIN, 178, 654,
FONT_NEXT, 103, 104,
FONT_NEXT, 146, 624,
FONT_END, 125, 587,
FONT_BEGIN, 125, 587,
FONT_NEXT, 103, 104,
FONT_NEXT, 105, 510,
FONT_END, 103, 476,
FONT_BEGIN, 103, 476,
FONT_NEXT, 103, 104,
FONT_NEXT, 103, 450,
FONT_NEXT, 103, 418,
FONT_NEXT, 21, 450,
FONT_END, 21, 418,
FONT_BEGIN, 309, 450,
FONT_NEXT, 309, 418,
FONT_NEXT, 186, 450,
FONT_NEXT, 187, 418,
FONT_END, 187, 104,
FONT_BEGIN, 186, 450,
FONT_NEXT, 187, 104,
FONT_NEXT, 99, 58,
FONT_END, 86, 31,
FONT_BEGIN, 86, 31,
FONT_NEXT, 187, 104,
FONT_END, 188, 64,
FONT_BEGIN, 86, 31,
FONT_NEXT, 188, 64,
FONT_END, 20, 0,
FONT_BEGIN, 86, 31,
FONT_NEXT, 20, 0,
FONT_NEXT, 60, 19,
FONT_END, 20, 15,
FONT_BEGIN, 280, 15,
FONT_NEXT, 280, 0,
FONT_NEXT, 226, 21,
FONT_NEXT, 20, 0,
FONT_NEXT, 199, 37,
FONT_END, 188, 64,
FONT_ADVANCE, 333, 0
    },
    {
103,
FONT_BEGIN, 69, 293,
FONT_NEXT, 73, 334,
FONT_NEXT, 75, 251,
FONT_NEXT, 85, 369,
FONT_NEXT, 93, 216,
FONT_END, 122, 186,
FONT_BEGIN, 122, 186,
FONT_NEXT, 85, 369,
FONT_END, 126, 421,
FONT_BEGIN, 122, 186,
FONT_NEXT, 126, 421,
FONT_END, 152, 342,
FONT_BEGIN, 122, 186,
FONT_NEXT, 152, 342,
FONT_END, 157, 282,
FONT_BEGIN, 122, 186,
FONT_NEXT, 157, 282,
FONT_NEXT, 162, 163,
FONT_NEXT, 174, 228,
FONT_END, 175, 146,
FONT_BEGIN, 162, 163,
FONT_NEXT, 175, 146,
FONT_END, 155, 129,
FONT_BEGIN, 162, 163,
FONT_NEXT, 155, 129,
FONT_NEXT, 132, 136,
FONT_NEXT, 139, 109,
FONT_END, 133, 91,
FONT_BEGIN, 132, 136,
FONT_NEXT, 133, 91,
FONT_END, 126, 1,
FONT_BEGIN, 132, 136,
FONT_NEXT, 126, 1,
FONT_NEXT, 110, 115,
FONT_NEXT, 89, 21,
FONT_NEXT, 84, 85,
FONT_NEXT, 77, 35,
FONT_NEXT, 74, 67,
FONT_END, 73, 54,
FONT_BEGIN, 175, 146,
FONT_NEXT, 174, 228,
FONT_NEXT, 193, 154,
FONT_NEXT, 206, 189,
FONT_END, 229, 177,
FONT_BEGIN, 193, 154,
FONT_NEXT, 229, 177,
FONT_NEXT, 247, 149,
FONT_NEXT, 256, 174,
FONT_NEXT, 295, 156,
FONT_NEXT, 296, 185,
FONT_END, 318, 213,
FONT_BEGIN, 295, 156,
FONT_NEXT, 318, 213,
FONT_NEXT, 347, 182,
FONT_NEXT, 327, 245,
FONT_END, 329, 270,
FONT_BEGIN, 347, 182,
FONT_NEXT, 329, 270,
FONT_NEXT, 338, 437,
FONT_NEXT, 325, 307,
FONT_NEXT, 287, 454,
FONT_NEXT, 310, 361,
FONT_END, 279, 410,
FONT_BEGIN, 287, 454,
FONT_NEXT, 279, 410,
FONT_NEXT, 241, 460,
FONT_NEXT, 256, 426,
FONT_END, 227, 432,
FONT_BEGIN, 241, 460,
FONT_NEXT, 227, 432,
FONT_NEXT, 182, 450,
FONT_NEXT, 200, 427,
FONT_END, 176, 412,
FONT_BEGIN, 182, 450,
FONT_NEXT, 176, 412,
FONT_NEXT, 126, 421,
FONT_NEXT, 158, 384,
FONT_END, 152, 342,
FONT_BEGIN, 28, -121,
FONT_NEXT, 36, -89,
FONT_NEXT, 41, -159,
FONT_NEXT, 57, -57,
FONT_NEXT, 78, -190,
FONT_NEXT, 98, -90,
FONT_END, 105, -115,
FONT_BEGIN, 78, -190,
FONT_NEXT, 105, -115,
FONT_END, 130, -138,
FONT_BEGIN, 78, -190,
FONT_NEXT, 130, -138,
FONT_NEXT, 134, -211,
FONT_NEXT, 176, -155,
FONT_NEXT, 205, -218,
FONT_NEXT, 248, -161,
FONT_NEXT, 282, -208,
FONT_NEXT, 326, -154,
FONT_NEXT, 366, -176,
FONT_NEXT, 384, -133,
FONT_NEXT, 403, -153,
FONT_NEXT, 420, -102,
FONT_NEXT, 433, -124,
FONT_NEXT, 433, -64,
FONT_NEXT, 440, 13,
FONT_END, 404, 43,
FONT_BEGIN, 404, 43,
FONT_NEXT, 433, -64,
FONT_END, 424, -38,
FONT_BEGIN, 404, 43,
FONT_NEXT, 424, -38,
FONT_END, 399, -24,
FONT_BEGIN, 404, 43,
FONT_NEXT, 399, -24,
FONT_NEXT, 375, 53,
FONT_NEXT, 360, -18,
FONT_NEXT, 340, 58,
FONT_NEXT, 310, -15,
FONT_NEXT, 211, 64,
FONT_NEXT, 251, -14,
FONT_END, 210, -12,
FONT_BEGIN, 211, 64,
FONT_NEXT, 210, -12,
FONT_NEXT, 160, 69,
FONT_NEXT, 147, -2,
FONT_NEXT, 140, 77,
FONT_NEXT, 126, 1,
FONT_END, 133, 91,
FONT_BEGIN, 147, -2,
FONT_NEXT, 110, -49,
FONT_NEXT, 126, 1,
FONT_END, 57, -57,
FONT_BEGIN, 57, -57,
FONT_NEXT, 110, -49,
FONT_END, 98, -90,
FONT_BEGIN, 57, -57,
FONT_END, 98, -90,
FONT_BEGIN, 461, -49,
FONT_NEXT, 453, -89,
FONT_NEXT, 457, -22,
FONT_END, 440, 13,
FONT_BEGIN, 440, 13,
FONT_NEXT, 453, -89,
FONT_END, 433, -124,
FONT_BEGIN, 440, 13,
FONT_END, 433, -124,
FONT_BEGIN, 470, 427,
FONT_NEXT, 470, 388,
FONT_NEXT, 393, 427,
FONT_NEXT, 387, 388,
FONT_NEXT, 338, 437,
FONT_END, 347, 182,
FONT_BEGIN, 347, 182,
FONT_NEXT, 387, 388,
FONT_NEXT, 389, 227,
FONT_NEXT, 400, 352,
FONT_NEXT, 401, 258,
FONT_NEXT, 404, 328,
FONT_END, 406, 296,
FONT_ADVANCE, 500, 0
    },
    {
104,
FONT_BEGIN, 157, 680,
FONT_NEXT, 157, 378,
FONT_NEXT, 152, 683,
FONT_NEXT, 157, 343,
FONT_END, 157, 102,
FONT_BEGIN, 152, 683,
FONT_NEXT, 157, 102,
FONT_END, 70, 58,
FONT_BEGIN, 152, 683,
FONT_NEXT, 70, 58,
FONT_END, 73, 102,
FONT_BEGIN, 152, 683,
FONT_NEXT, 73, 102,
FONT_END, 73, 573,
FONT_BEGIN, 152, 683,
FONT_NEXT, 73, 573,
FONT_END, 67, 609,
FONT_BEGIN, 152, 683,
FONT_NEXT, 67, 609,
FONT_END, 54, 620,
FONT_BEGIN, 152, 683,
FONT_NEXT, 54, 620,
FONT_NEXT, 10, 639,
FONT_NEXT, 29, 624,
FONT_END, 10, 623,
FONT_BEGIN, 427, 301,
FONT_NEXT, 427, 102,
FONT_NEXT, 424, 342,
FONT_NEXT, 339, 56,
FONT_NEXT, 411, 395,
FONT_END, 396, 420,
FONT_BEGIN, 396, 420,
FONT_NEXT, 339, 56,
FONT_NEXT, 374, 440,
FONT_NEXT, 343, 102,
FONT_NEXT, 343, 454,
FONT_NEXT, 343, 300,
FONT_END, 338, 344,
FONT_BEGIN, 343, 454,
FONT_NEXT, 338, 344,
FONT_NEXT, 303, 460,
FONT_NEXT, 325, 378,
FONT_END, 301, 398,
FONT_BEGIN, 303, 460,
FONT_NEXT, 301, 398,
FONT_NEXT, 260, 454,
FONT_NEXT, 266, 406,
FONT_END, 216, 392,
FONT_BEGIN, 260, 454,
FONT_NEXT, 216, 392,
FONT_NEXT, 221, 437,
FONT_END, 187, 411,
FONT_BEGIN, 187, 411,
FONT_NEXT, 216, 392,
FONT_END, 187, 373,
FONT_BEGIN, 187, 411,
FONT_NEXT, 187, 373,
FONT_NEXT, 159, 378,
FONT_NEXT, 157, 343,
FONT_END, 157, 378,
FONT_BEGIN, 487, 15,
FONT_NEXT, 487, 0,
FONT_NEXT, 456, 22,
FONT_NEXT, 275, 0,
FONT_NEXT, 438, 34,
FONT_END, 429, 58,
FONT_BEGIN, 429, 58,
FONT_NEXT, 275, 0,
FONT_END, 327, 31,
FONT_BEGIN, 429, 58,
FONT_NEXT, 327, 31,
FONT_NEXT, 427, 102,
FONT_END, 339, 56,
FONT_BEGIN, 275, 15,
FONT_NEXT, 306, 19,
FONT_NEXT, 275, 0,
FONT_END, 327, 31,
FONT_BEGIN, 225, 15,
FONT_NEXT, 225, 0,
FONT_NEXT, 193, 19,
FONT_NEXT, 9, 0,
FONT_NEXT, 172, 31,
FONT_END, 160, 56,
FONT_BEGIN, 160, 56,
FONT_NEXT, 9, 0,
FONT_END, 61, 33,
FONT_BEGIN, 160, 56,
FONT_NEXT, 61, 33,
FONT_NEXT, 157, 102,
FONT_END, 70, 58,
FONT_BEGIN, 9, 15,
FONT_NEXT, 41, 21,
FONT_NEXT, 9, 0,
FONT_END, 61, 33,
FONT_ADVANCE, 500, 0
    },
    {
105,
FONT_BEGIN, 179, 457,
FONT_NEXT, 179, 102,
FONT_NEXT, 175, 460,
FONT_NEXT, 91, 55,
FONT_END, 95, 102,
FONT_BEGIN, 175, 460,
FONT_NEXT, 95, 102,
FONT_END, 95, 334,
FONT_BEGIN, 175, 460,
FONT_NEXT, 95, 334,
FONT_END, 91, 368,
FONT_BEGIN, 175, 460,
FONT_NEXT, 91, 368,
FONT_END, 83, 386,
FONT_BEGIN, 175, 460,
FONT_NEXT, 83, 386,
FONT_END, 60, 394,
FONT_BEGIN, 175, 460,
FONT_NEXT, 60, 394,
FONT_NEXT, 20, 405,
FONT_END, 20, 390,
FONT_BEGIN, 16, 15,
FONT_NEXT, 54, 18,
FONT_NEXT, 16, 0,
FONT_END, 180, 61,
FONT_BEGIN, 180, 61,
FONT_NEXT, 54, 18,
FONT_END, 78, 30,
FONT_BEGIN, 180, 61,
FONT_NEXT, 78, 30,
FONT_NEXT, 179, 102,
FONT_END, 91, 55,
FONT_BEGIN, 253, 15,
FONT_NEXT, 253, 0,
FONT_NEXT, 212, 21,
FONT_NEXT, 16, 0,
FONT_NEXT, 190, 35,
FONT_END, 180, 61,
FONT_BEGIN, 180, 632,
FONT_NEXT, 176, 611,
FONT_NEXT, 165, 668,
FONT_NEXT, 165, 595,
FONT_NEXT, 148, 678,
FONT_NEXT, 148, 584,
FONT_NEXT, 128, 683,
FONT_NEXT, 128, 581,
FONT_NEXT, 105, 677,
FONT_NEXT, 105, 586,
FONT_NEXT, 89, 664,
FONT_NEXT, 89, 599,
FONT_END, 78, 632,
FONT_ADVANCE, 278, 0
    },
    {
106,
FONT_BEGIN, 32, 390,
FONT_NEXT, 32, 406,
FONT_NEXT, 74, 394,
FONT_NEXT, 188, 460,
FONT_NEXT, 97, 386,
FONT_END, 105, 368,
FONT_BEGIN, 105, 368,
FONT_NEXT, 188, 460,
FONT_END, 184, -84,
FONT_BEGIN, 105, 368,
FONT_NEXT, 184, -84,
FONT_NEXT, 109, 334,
FONT_NEXT, 156, -153,
FONT_END, 133, -180,
FONT_BEGIN, 109, 334,
FONT_NEXT, 133, -180,
FONT_NEXT, 109, -45,
FONT_END, 108, -102,
FONT_BEGIN, 108, -102,
FONT_NEXT, 133, -180,
FONT_END, 103, -201,
FONT_BEGIN, 108, -102,
FONT_NEXT, 103, -201,
FONT_NEXT, 102, -146,
FONT_NEXT, 67, -214,
FONT_NEXT, 87, -174,
FONT_END, 59, -184,
FONT_BEGIN, 59, -184,
FONT_NEXT, 67, -214,
FONT_END, 23, -218,
FONT_BEGIN, 59, -184,
FONT_NEXT, 23, -218,
FONT_NEXT, -30, -124,
FONT_NEXT, -12, -215,
FONT_END, -42, -204,
FONT_BEGIN, -30, -124,
FONT_NEXT, -42, -204,
FONT_NEXT, -58, -135,
FONT_NEXT, -63, -187,
FONT_END, -70, -162,
FONT_BEGIN, 193, 457,
FONT_NEXT, 193, 0,
FONT_NEXT, 188, 460,
FONT_END, 184, -84,
FONT_BEGIN, 194, 632,
FONT_NEXT, 190, 611,
FONT_NEXT, 179, 668,
FONT_NEXT, 179, 595,
FONT_NEXT, 162, 678,
FONT_NEXT, 162, 584,
FONT_NEXT, 142, 683,
FONT_NEXT, 142, 581,
FONT_NEXT, 119, 677,
FONT_NEXT, 119, 586,
FONT_NEXT, 103, 664,
FONT_NEXT, 103, 599,
FONT_END, 92, 632,
FONT_ADVANCE, 278, 0
    },
    {
107,
FONT_BEGIN, 166, 681,
FONT_NEXT, 166, 265,
FONT_NEXT, 162, 683,
FONT_NEXT, 166, 248,
FONT_END, 166, 67,
FONT_BEGIN, 162, 683,
FONT_NEXT, 166, 67,
FONT_END, 79, 50,
FONT_BEGIN, 162, 683,
FONT_NEXT, 79, 50,
FONT_END, 82, 82,
FONT_BEGIN, 162, 683,
FONT_NEXT, 82, 82,
FONT_END, 82, 564,
FONT_BEGIN, 162, 683,
FONT_NEXT, 82, 564,
FONT_END, 80, 592,
FONT_BEGIN, 162, 683,
FONT_NEXT, 80, 592,
FONT_END, 74, 611,
FONT_BEGIN, 162, 683,
FONT_NEXT, 74, 611,
FONT_END, 60, 621,
FONT_BEGIN, 162, 683,
FONT_NEXT, 60, 621,
FONT_NEXT, 7, 639,
FONT_NEXT, 37, 625,
FONT_END, 7, 623,
FONT_BEGIN, 276, 436,
FONT_NEXT, 276, 450,
FONT_NEXT, 301, 434,
FONT_END, 316, 430,
FONT_BEGIN, 316, 430,
FONT_NEXT, 276, 450,
FONT_END, 377, 404,
FONT_BEGIN, 316, 430,
FONT_NEXT, 377, 404,
FONT_NEXT, 326, 417,
FONT_NEXT, 347, 381,
FONT_END, 309, 350,
FONT_BEGIN, 326, 417,
FONT_NEXT, 309, 350,
FONT_NEXT, 320, 401,
FONT_END, 303, 383,
FONT_BEGIN, 303, 383,
FONT_NEXT, 309, 350,
FONT_END, 264, 309,
FONT_BEGIN, 303, 383,
FONT_NEXT, 264, 309,
FONT_NEXT, 168, 263,
FONT_NEXT, 235, 282,
FONT_END, 168, 248,
FONT_BEGIN, 168, 263,
FONT_NEXT, 168, 248,
FONT_NEXT, 166, 265,
FONT_END, 166, 248,
FONT_BEGIN, 480, 450,
FONT_NEXT, 480, 435,
FONT_NEXT, 276, 450,
FONT_NEXT, 442, 433,
FONT_END, 402, 419,
FONT_BEGIN, 276, 450,
FONT_NEXT, 402, 419,
FONT_END, 377, 404,
FONT_BEGIN, 505, 15,
FONT_NEXT, 505, 0,
FONT_NEXT, 482, 18,
FONT_NEXT, 287, 0,
FONT_NEXT, 455, 28,
FONT_END, 424, 49,
FONT_BEGIN, 424, 49,
FONT_NEXT, 287, 0,
FONT_END, 319, 18,
FONT_BEGIN, 424, 49,
FONT_NEXT, 319, 18,
FONT_NEXT, 388, 88,
FONT_NEXT, 327, 31,
FONT_NEXT, 235, 282,
FONT_NEXT, 321, 43,
FONT_END, 306, 64,
FONT_BEGIN, 235, 282,
FONT_NEXT, 306, 64,
FONT_END, 168, 248,
FONT_BEGIN, 287, 15,
FONT_NEXT, 306, 15,
FONT_NEXT, 287, 0,
FONT_END, 319, 18,
FONT_BEGIN, 241, 15,
FONT_NEXT, 241, 0,
FONT_NEXT, 221, 16,
FONT_NEXT, 7, 0,
FONT_NEXT, 181, 26,
FONT_END, 169, 42,
FONT_BEGIN, 169, 42,
FONT_NEXT, 7, 0,
FONT_NEXT, 166, 67,
FONT_NEXT, 69, 32,
FONT_END, 79, 50,
FONT_BEGIN, 7, 15,
FONT_NEXT, 46, 22,
FONT_NEXT, 7, 0,
FONT_END, 69, 32,
FONT_ADVANCE, 500, 0
    },
    {
108,
FONT_BEGIN, 182, 681,
FONT_NEXT, 182, 84,
FONT_NEXT, 178, 683,
FONT_NEXT, 94, 53,
FONT_END, 98, 87,
FONT_BEGIN, 178, 683,
FONT_NEXT, 98, 87,
FONT_END, 98, 564,
FONT_BEGIN, 178, 683,
FONT_NEXT, 98, 564,
FONT_END, 96, 592,
FONT_BEGIN, 178, 683,
FONT_NEXT, 96, 592,
FONT_END, 90, 611,
FONT_BEGIN, 178, 683,
FONT_NEXT, 90, 611,
FONT_END, 76, 621,
FONT_BEGIN, 178, 683,
FONT_NEXT, 76, 621,
FONT_NEXT, 19, 639,
FONT_NEXT, 53, 625,
FONT_END, 19, 623,
FONT_BEGIN, 257, 15,
FONT_NEXT, 257, 0,
FONT_NEXT, 220, 18,
FONT_NEXT, 21, 0,
FONT_NEXT, 197, 28,
FONT_END, 185, 49,
FONT_BEGIN, 185, 49,
FONT_NEXT, 21, 0,
FONT_END, 81, 31,
FONT_BEGIN, 185, 49,
FONT_NEXT, 81, 31,
FONT_NEXT, 182, 84,
FONT_END, 94, 53,
FONT_BEGIN, 21, 15,
FONT_NEXT, 57, 20,
FONT_NEXT, 21, 0,
FONT_END, 81, 31,
FONT_ADVANCE, 278, 0
    },
    {
109,
FONT_BEGIN, 166, 458,
FONT_NEXT, 166, 383,
FONT_NEXT, 159, 460,
FONT_NEXT, 80, 45,
FONT_END, 86, 85,
FONT_BEGIN, 159, 460,
FONT_NEXT, 86, 85,
FONT_END, 86, 338,
FONT_BEGIN, 159, 460,
FONT_NEXT, 86, 338,
FONT_END, 82, 374,
FONT_BEGIN, 159, 460,
FONT_NEXT, 82, 374,
FONT_END, 74, 393,
FONT_BEGIN, 159, 460,
FONT_NEXT, 74, 393,
FONT_NEXT, 19, 415,
FONT_NEXT, 51, 402,
FONT_END, 19, 398,
FONT_BEGIN, 706, 282,
FONT_NEXT, 706, 76,
FONT_NEXT, 702, 341,
FONT_NEXT, 618, 49,
FONT_NEXT, 686, 398,
FONT_NEXT, 622, 87,
FONT_NEXT, 650, 442,
FONT_END, 622, 455,
FONT_BEGIN, 622, 455,
FONT_NEXT, 622, 87,
FONT_END, 622, 298,
FONT_BEGIN, 622, 455,
FONT_NEXT, 622, 298,
FONT_END, 612, 372,
FONT_BEGIN, 622, 455,
FONT_NEXT, 612, 372,
FONT_NEXT, 588, 460,
FONT_NEXT, 590, 397,
FONT_END, 549, 408,
FONT_BEGIN, 588, 460,
FONT_NEXT, 549, 408,
FONT_NEXT, 532, 450,
FONT_NEXT, 511, 403,
FONT_NEXT, 485, 427,
FONT_NEXT, 481, 390,
FONT_NEXT, 449, 399,
FONT_NEXT, 438, 347,
FONT_NEXT, 427, 376,
FONT_NEXT, 438, 95,
FONT_END, 349, 47,
FONT_BEGIN, 427, 376,
FONT_NEXT, 349, 47,
FONT_NEXT, 414, 406,
FONT_NEXT, 354, 86,
FONT_NEXT, 393, 433,
FONT_END, 362, 452,
FONT_BEGIN, 362, 452,
FONT_NEXT, 354, 86,
FONT_END, 354, 303,
FONT_BEGIN, 362, 452,
FONT_NEXT, 354, 303,
FONT_END, 350, 347,
FONT_BEGIN, 362, 452,
FONT_NEXT, 350, 347,
FONT_NEXT, 320, 460,
FONT_NEXT, 339, 380,
FONT_END, 317, 400,
FONT_BEGIN, 320, 460,
FONT_NEXT, 317, 400,
FONT_NEXT, 281, 454,
FONT_NEXT, 285, 408,
FONT_END, 237, 399,
FONT_BEGIN, 281, 454,
FONT_NEXT, 237, 399,
FONT_NEXT, 244, 438,
FONT_END, 166, 383,
FONT_BEGIN, 166, 383,
FONT_NEXT, 237, 399,
FONT_END, 201, 380,
FONT_BEGIN, 166, 383,
FONT_NEXT, 201, 380,
FONT_END, 178, 360,
FONT_BEGIN, 166, 383,
FONT_NEXT, 178, 360,
FONT_END, 170, 349,
FONT_BEGIN, 166, 383,
FONT_NEXT, 170, 349,
FONT_END, 170, 67,
FONT_BEGIN, 166, 383,
FONT_NEXT, 170, 67,
FONT_NEXT, 80, 45,
FONT_END, 66, 25,
FONT_BEGIN, 66, 25,
FONT_NEXT, 170, 67,
FONT_END, 16, 0,
FONT_BEGIN, 66, 25,
FONT_NEXT, 16, 0,
FONT_NEXT, 43, 16,
FONT_END, 16, 15,
FONT_BEGIN, 775, 15,
FONT_NEXT, 775, 0,
FONT_NEXT, 749, 17,
FONT_NEXT, 556, 0,
FONT_NEXT, 723, 26,
FONT_END, 711, 40,
FONT_BEGIN, 711, 40,
FONT_NEXT, 556, 0,
FONT_NEXT, 706, 76,
FONT_NEXT, 607, 29,
FONT_END, 618, 49,
FONT_BEGIN, 556, 15,
FONT_NEXT, 587, 19,
FONT_NEXT, 556, 0,
FONT_END, 607, 29,
FONT_BEGIN, 510, 15,
FONT_NEXT, 510, 0,
FONT_NEXT, 481, 17,
FONT_NEXT, 286, 0,
FONT_NEXT, 458, 26,
FONT_END, 443, 50,
FONT_BEGIN, 443, 50,
FONT_NEXT, 286, 0,
FONT_END, 336, 27,
FONT_BEGIN, 443, 50,
FONT_NEXT, 336, 27,
FONT_NEXT, 438, 95,
FONT_END, 349, 47,
FONT_BEGIN, 286, 15,
FONT_NEXT, 315, 18,
FONT_NEXT, 286, 0,
FONT_END, 336, 27,
FONT_BEGIN, 238, 15,
FONT_NEXT, 238, 0,
FONT_NEXT, 190, 23,
FONT_NEXT, 16, 0,
FONT_NEXT, 175, 38,
FONT_END, 170, 67,
FONT_ADVANCE, 778, 0
    },
    {
110,
FONT_BEGIN, 161, 458,
FONT_NEXT, 161, 379,
FONT_NEXT, 154, 460,
FONT_NEXT, 76, 53,
FONT_END, 80, 90,
FONT_BEGIN, 154, 460,
FONT_NEXT, 80, 90,
FONT_END, 80, 338,
FONT_BEGIN, 154, 460,
FONT_NEXT, 80, 338,
FONT_END, 76, 374,
FONT_BEGIN, 154, 460,
FONT_NEXT, 76, 374,
FONT_END, 68, 393,
FONT_BEGIN, 154, 460,
FONT_NEXT, 68, 393,
FONT_NEXT, 16, 415,
FONT_NEXT, 45, 402,
FONT_END, 16, 398,
FONT_BEGIN, 424, 310,
FONT_NEXT, 424, 81,
FONT_NEXT, 420, 351,
FONT_NEXT, 335, 54,
FONT_NEXT, 412, 385,
FONT_END, 382, 431,
FONT_BEGIN, 382, 431,
FONT_NEXT, 335, 54,
FONT_END, 340, 99,
FONT_BEGIN, 382, 431,
FONT_NEXT, 340, 99,
FONT_NEXT, 344, 453,
FONT_NEXT, 340, 308,
FONT_END, 336, 348,
FONT_BEGIN, 344, 453,
FONT_NEXT, 336, 348,
FONT_NEXT, 307, 460,
FONT_NEXT, 323, 379,
FONT_END, 299, 398,
FONT_BEGIN, 307, 460,

FONT_NEXT, 299, 398,
FONT_NEXT, 266, 453,
FONT_NEXT, 263, 405,
FONT_NEXT, 230, 436,
FONT_NEXT, 216, 390,
FONT_NEXT, 161, 379,
FONT_NEXT, 164, 348,
FONT_END, 164, 67,
FONT_BEGIN, 161, 379,
FONT_NEXT, 164, 67,
FONT_NEXT, 76, 53,
FONT_END, 66, 31,
FONT_BEGIN, 66, 31,
FONT_NEXT, 164, 67,
FONT_END, 18, 0,
FONT_BEGIN, 66, 31,
FONT_NEXT, 18, 0,
FONT_NEXT, 47, 19,
FONT_END, 18, 15,
FONT_BEGIN, 485, 15,
FONT_NEXT, 485, 0,
FONT_NEXT, 454, 20,
FONT_NEXT, 277, 0,
FONT_NEXT, 436, 31,
FONT_END, 426, 51,
FONT_BEGIN, 426, 51,
FONT_NEXT, 277, 0,
FONT_NEXT, 424, 81,
FONT_NEXT, 322, 29,
FONT_END, 335, 54,
FONT_BEGIN, 277, 15,
FONT_NEXT, 302, 18,
FONT_NEXT, 277, 0,
FONT_END, 322, 29,
FONT_BEGIN, 230, 15,
FONT_NEXT, 230, 0,
FONT_NEXT, 197, 19,
FONT_NEXT, 18, 0,
FONT_NEXT, 177, 29,
FONT_END, 167, 45,
FONT_BEGIN, 167, 45,
FONT_NEXT, 18, 0,
FONT_END, 164, 67,
FONT_BEGIN, 167, 45,
FONT_END, 164, 67,
FONT_ADVANCE, 500, 0
    },
    {
111,
FONT_BEGIN, 470, 228,
FONT_NEXT, 456, 149,
FONT_NEXT, 465, 279,
FONT_END, 453, 325,
FONT_BEGIN, 453, 325,
FONT_NEXT, 456, 149,
FONT_END, 416, 71,
FONT_BEGIN, 453, 325,
FONT_NEXT, 416, 71,
FONT_NEXT, 433, 365,
FONT_END, 406, 398,
FONT_BEGIN, 406, 398,
FONT_NEXT, 416, 71,
FONT_END, 385, 39,
FONT_BEGIN, 406, 398,
FONT_NEXT, 385, 39,
FONT_NEXT, 373, 424,
FONT_NEXT, 380, 199,
FONT_END, 370, 287,
FONT_BEGIN, 373, 424,
FONT_NEXT, 370, 287,
FONT_NEXT, 335, 444,
FONT_NEXT, 343, 362,
FONT_END, 322, 390,
FONT_BEGIN, 335, 444,
FONT_NEXT, 322, 390,
FONT_NEXT, 292, 455,
FONT_NEXT, 298, 413,
FONT_END, 269, 427,
FONT_BEGIN, 292, 455,
FONT_NEXT, 269, 427,
FONT_NEXT, 245, 460,
FONT_NEXT, 237, 432,
FONT_NEXT, 197, 455,
FONT_NEXT, 191, 422,
FONT_NEXT, 154, 441,
FONT_NEXT, 154, 393,
FONT_NEXT, 117, 419,
FONT_NEXT, 128, 344,
FONT_END, 119, 276,
FONT_BEGIN, 117, 419,
FONT_NEXT, 119, 276,
FONT_END, 90, 58,
FONT_BEGIN, 117, 419,
FONT_NEXT, 90, 58,
FONT_NEXT, 86, 390,
FONT_NEXT, 64, 93,
FONT_NEXT, 62, 355,
FONT_NEXT, 45, 133,
FONT_NEXT, 43, 316,
FONT_NEXT, 33, 179,
FONT_END, 29, 228,
FONT_BEGIN, 90, 58,
FONT_NEXT, 119, 276,
FONT_NEXT, 122, 29,
FONT_NEXT, 124, 204,
FONT_END, 145, 119,
FONT_BEGIN, 122, 29,
FONT_NEXT, 145, 119,
FONT_NEXT, 159, 8,
FONT_NEXT, 164, 80,
FONT_END, 189, 47,
FONT_BEGIN, 159, 8,
FONT_NEXT, 189, 47,
FONT_NEXT, 201, -6,
FONT_NEXT, 221, 26,
FONT_NEXT, 248, -10,
FONT_NEXT, 262, 18,
FONT_NEXT, 301, -4,
FONT_NEXT, 301, 26,
FONT_END, 340, 54,
FONT_BEGIN, 301, -4,
FONT_NEXT, 340, 54,
FONT_NEXT, 346, 13,
FONT_NEXT, 356, 78,
FONT_END, 368, 110,
FONT_BEGIN, 346, 13,
FONT_NEXT, 368, 110,
FONT_NEXT, 385, 39,
FONT_NEXT, 377, 150,
FONT_END, 380, 199,
FONT_ADVANCE, 500, 0
    },
    {
112,
FONT_BEGIN, 159, 458,
FONT_NEXT, 159, 385,
FONT_NEXT, 153, 460,
FONT_NEXT, 159, 334,
FONT_END, 159, 88,
FONT_BEGIN, 153, 460,
FONT_NEXT, 159, 88,
FONT_END, 159, 33,
FONT_BEGIN, 153, 460,
FONT_NEXT, 159, 33,
FONT_END, 159, -124,
FONT_BEGIN, 153, 460,
FONT_NEXT, 159, -124,
FONT_END, 73, -160,
FONT_BEGIN, 153, 460,
FONT_NEXT, 73, -160,
FONT_END, 75, -131,
FONT_BEGIN, 153, 460,
FONT_NEXT, 75, -131,
FONT_END, 75, 337,
FONT_BEGIN, 153, 460,
FONT_NEXT, 75, 337,
FONT_END, 73, 362,
FONT_BEGIN, 153, 460,
FONT_NEXT, 73, 362,
FONT_END, 69, 380,
FONT_BEGIN, 153, 460,
FONT_NEXT, 69, 380,
FONT_END, 57, 390,
FONT_BEGIN, 153, 460,
FONT_NEXT, 57, 390,
FONT_NEXT, 9, 409,
FONT_NEXT, 37, 394,
FONT_END, 9, 393,
FONT_BEGIN, 470, 245,
FONT_NEXT, 456, 156,
FONT_NEXT, 466, 292,
FONT_END, 456, 334,
FONT_BEGIN, 456, 334,
FONT_NEXT, 456, 156,
FONT_NEXT, 420, 402,
FONT_NEXT, 417, 74,
FONT_NEXT, 368, 445,
FONT_NEXT, 384, 215,
FONT_END, 383, 229,
FONT_BEGIN, 368, 445,
FONT_NEXT, 383, 229,
FONT_END, 381, 253,
FONT_BEGIN, 368, 445,
FONT_NEXT, 381, 253,
FONT_END, 366, 316,
FONT_BEGIN, 368, 445,
FONT_NEXT, 366, 316,
FONT_NEXT, 305, 460,
FONT_NEXT, 350, 347,
FONT_END, 328, 374,
FONT_BEGIN, 305, 460,
FONT_NEXT, 328, 374,
FONT_END, 297, 392,
FONT_BEGIN, 305, 460,
FONT_NEXT, 297, 392,
FONT_NEXT, 252, 451,
FONT_NEXT, 257, 400,
FONT_END, 227, 395,
FONT_BEGIN, 252, 451,
FONT_NEXT, 227, 395,
FONT_NEXT, 212, 432,
FONT_NEXT, 196, 381,
FONT_NEXT, 181, 406,
FONT_NEXT, 171, 360,
FONT_NEXT, 161, 383,
FONT_NEXT, 159, 334,
FONT_END, 159, 385,
FONT_BEGIN, 159, 88,
FONT_NEXT, 167, 67,
FONT_NEXT, 159, 33,
FONT_NEXT, 189, 46,
FONT_NEXT, 206, -1,
FONT_NEXT, 221, 29,
FONT_NEXT, 260, -10,
FONT_NEXT, 261, 22,
FONT_END, 294, 27,
FONT_BEGIN, 260, -10,
FONT_NEXT, 294, 27,
FONT_NEXT, 308, -4,
FONT_NEXT, 334, 53,
FONT_NEXT, 351, 13,
FONT_NEXT, 353, 77,
FONT_END, 369, 111,
FONT_BEGIN, 351, 13,
FONT_NEXT, 369, 111,
FONT_END, 380, 156,
FONT_BEGIN, 351, 13,
FONT_NEXT, 380, 156,
FONT_NEXT, 387, 40,
FONT_NEXT, 384, 215,
FONT_END, 417, 74,
FONT_BEGIN, 5, -200,
FONT_NEXT, 43, -194,
FONT_NEXT, 5, -217,
FONT_END, 159, -124,
FONT_BEGIN, 159, -124,
FONT_NEXT, 43, -194,
FONT_END, 64, -180,
FONT_BEGIN, 159, -124,
FONT_NEXT, 64, -180,
FONT_END, 73, -160,
FONT_BEGIN, 247, -199,
FONT_NEXT, 247, -217,
FONT_NEXT, 202, -197,
FONT_NEXT, 5, -217,
FONT_NEXT, 176, -186,
FONT_END, 162, -163,
FONT_BEGIN, 162, -163,
FONT_NEXT, 5, -217,
FONT_END, 159, -124,
FONT_BEGIN, 162, -163,
FONT_END, 159, -124,
FONT_ADVANCE, 500, 0
    },
    {
113,
FONT_BEGIN, 110, 238,
FONT_NEXT, 117, 169,
FONT_NEXT, 72, 44,
FONT_END, 124, 3,
FONT_BEGIN, 124, 3,
FONT_NEXT, 117, 169,
FONT_END, 140, 110,
FONT_BEGIN, 124, 3,
FONT_NEXT, 140, 110,
FONT_END, 180, 67,
FONT_BEGIN, 124, 3,
FONT_NEXT, 180, 67,
FONT_NEXT, 188, -10,
FONT_NEXT, 206, 55,
FONT_NEXT, 234, -3,
FONT_NEXT, 238, 51,
FONT_NEXT, 278, 14,
FONT_NEXT, 288, 60,
FONT_NEXT, 314, 38,
FONT_NEXT, 318, 73,
FONT_END, 333, 88,
FONT_BEGIN, 314, 38,
FONT_NEXT, 333, 88,
FONT_NEXT, 336, 62,
FONT_END, 341, 62,
FONT_BEGIN, 341, 62,
FONT_NEXT, 333, 88,
FONT_END, 341, 127,
FONT_BEGIN, 341, 62,
FONT_NEXT, 341, 127,
FONT_NEXT, 341, -124,
FONT_NEXT, 341, 333,
FONT_NEXT, 360, 425,
FONT_NEXT, 339, 360,
FONT_END, 328, 393,
FONT_BEGIN, 360, 425,
FONT_NEXT, 328, 393,
FONT_NEXT, 308, 450,
FONT_NEXT, 300, 420,
FONT_NEXT, 247, 460,
FONT_NEXT, 276, 428,
FONT_END, 245, 432,
FONT_BEGIN, 247, 460,
FONT_NEXT, 245, 432,
FONT_NEXT, 201, 455,
FONT_NEXT, 189, 418,
FONT_NEXT, 159, 440,
FONT_NEXT, 147, 380,
FONT_NEXT, 121, 417,
FONT_NEXT, 119, 319,
FONT_NEXT, 88, 387,
FONT_NEXT, 110, 238,
FONT_END, 72, 44,
FONT_BEGIN, 88, 387,
FONT_NEXT, 72, 44,
FONT_NEXT, 61, 351,
FONT_NEXT, 52, 75,
FONT_NEXT, 41, 308,
FONT_NEXT, 37, 113,
FONT_NEXT, 28, 262,
FONT_NEXT, 27, 159,
FONT_END, 24, 212,
FONT_BEGIN, 425, 456,
FONT_NEXT, 425, -141,
FONT_NEXT, 421, 460,
FONT_NEXT, 337, -157,
FONT_NEXT, 414, 457,
FONT_NEXT, 341, -124,
FONT_END, 360, 425,
FONT_BEGIN, 252, -200,
FONT_NEXT, 297, -194,
FONT_NEXT, 252, -217,
FONT_END, 425, -141,
FONT_BEGIN, 425, -141,
FONT_NEXT, 297, -194,
FONT_END, 324, -180,
FONT_BEGIN, 425, -141,
FONT_NEXT, 324, -180,
FONT_END, 337, -157,
FONT_BEGIN, 488, -203,
FONT_NEXT, 488, -217,
FONT_NEXT, 459, -196,
FONT_NEXT, 252, -217,
FONT_NEXT, 439, -187,
FONT_END, 428, -170,
FONT_BEGIN, 428, -170,
FONT_NEXT, 252, -217,
FONT_END, 425, -141,
FONT_BEGIN, 428, -170,
FONT_END, 425, -141,
FONT_ADVANCE, 500, 0
    },
    {
114,
FONT_BEGIN, 160, 458,
FONT_NEXT, 160, 369,
FONT_NEXT, 155, 460,
FONT_NEXT, 160, 315,
FONT_END, 160, 90,
FONT_BEGIN, 155, 460,
FONT_NEXT, 160, 90,
FONT_END, 72, 51,
FONT_BEGIN, 155, 460,
FONT_NEXT, 72, 51,
FONT_END, 76, 84,
FONT_BEGIN, 155, 460,
FONT_NEXT, 76, 84,
FONT_END, 76, 334,
FONT_BEGIN, 155, 460,
FONT_NEXT, 76, 334,
FONT_END, 72, 368,
FONT_BEGIN, 155, 460,
FONT_NEXT, 72, 368,
FONT_END, 64, 386,
FONT_BEGIN, 155, 460,
FONT_NEXT, 64, 386,
FONT_NEXT, 7, 406,
FONT_NEXT, 41, 394,
FONT_END, 7, 390,
FONT_BEGIN, 335, 406,
FONT_NEXT, 325, 374,
FONT_NEXT, 331, 428,
FONT_END, 320, 445,
FONT_BEGIN, 320, 445,
FONT_NEXT, 325, 374,
FONT_END, 297, 362,
FONT_BEGIN, 320, 445,
FONT_NEXT, 297, 362,
FONT_NEXT, 303, 456,
FONT_END, 281, 460,
FONT_BEGIN, 281, 460,
FONT_NEXT, 297, 362,
FONT_END, 274, 367,
FONT_BEGIN, 281, 460,
FONT_NEXT, 274, 367,
FONT_NEXT, 252, 455,
FONT_NEXT, 258, 379,
FONT_END, 228, 397,
FONT_BEGIN, 252, 455,
FONT_NEXT, 228, 397,
FONT_NEXT, 224, 441,
FONT_NEXT, 207, 388,
FONT_NEXT, 195, 413,
FONT_NEXT, 185, 368,
FONT_NEXT, 162, 369,
FONT_NEXT, 167, 341,
FONT_END, 160, 315,
FONT_BEGIN, 162, 369,
FONT_NEXT, 160, 315,
FONT_END, 160, 369,
FONT_BEGIN, 245, 15,
FONT_NEXT, 245, 0,
FONT_NEXT, 210, 17,
FONT_NEXT, 5, 0,
FONT_NEXT, 183, 27,
FONT_END, 166, 49,
FONT_BEGIN, 166, 49,
FONT_NEXT, 5, 0,
FONT_END, 61, 32,
FONT_BEGIN, 166, 49,
FONT_NEXT, 61, 32,
FONT_NEXT, 160, 90,
FONT_END, 72, 51,
FONT_BEGIN, 5, 15,
FONT_NEXT, 39, 22,
FONT_NEXT, 5, 0,
FONT_END, 61, 32,
FONT_ADVANCE, 333, 0
    },
    {
115,
FONT_BEGIN, 51, 338,
FONT_NEXT, 59, 380,
FONT_NEXT, 60, 288,
FONT_NEXT, 84, 420,
FONT_NEXT, 86, 250,
FONT_NEXT, 113, 372,
FONT_END, 120, 337,
FONT_BEGIN, 86, 250,
FONT_NEXT, 120, 337,
FONT_NEXT, 123, 219,
FONT_NEXT, 133, 318,
FONT_END, 156, 301,
FONT_BEGIN, 123, 219,
FONT_NEXT, 156, 301,
FONT_NEXT, 168, 192,
FONT_END, 226, 159,
FONT_BEGIN, 226, 159,
FONT_NEXT, 156, 301,
FONT_NEXT, 264, 127,
FONT_NEXT, 264, 237,
FONT_NEXT, 278, 85,
FONT_NEXT, 299, 21,
FONT_NEXT, 270, 49,
FONT_NEXT, 252, -3,
FONT_NEXT, 250, 27,
FONT_NEXT, 203, -10,
FONT_NEXT, 224, 15,
FONT_END, 197, 12,
FONT_BEGIN, 197, 12,
FONT_NEXT, 203, -10,
FONT_END, 138, -1,
FONT_BEGIN, 197, 12,
FONT_NEXT, 138, -1,
FONT_NEXT, 163, 15,
FONT_END, 137, 26,
FONT_BEGIN, 137, 26,
FONT_NEXT, 138, -1,
FONT_END, 87, 8,
FONT_BEGIN, 137, 26,
FONT_NEXT, 87, 8,
FONT_NEXT, 100, 62,
FONT_END, 79, 107,
FONT_BEGIN, 79, 107,
FONT_NEXT, 87, 8,
FONT_END, 73, 5,
FONT_BEGIN, 79, 107,
FONT_NEXT, 73, 5,
FONT_NEXT, 68, 152,
FONT_NEXT, 65, -4,
FONT_NEXT, 52, 152,
FONT_END, 52, -4,
FONT_BEGIN, 315, 314,
FONT_NEXT, 300, 314,
FONT_NEXT, 311, 451,
FONT_END, 300, 451,
FONT_BEGIN, 300, 451,
FONT_NEXT, 300, 314,
FONT_NEXT, 283, 440,
FONT_NEXT, 279, 376,
FONT_NEXT, 247, 450,
FONT_NEXT, 252, 413,
FONT_END, 221, 432,
FONT_BEGIN, 247, 450,
FONT_NEXT, 221, 432,
FONT_NEXT, 191, 460,
FONT_NEXT, 189, 437,
FONT_NEXT, 128, 448,
FONT_NEXT, 153, 430,
FONT_END, 130, 414,
FONT_BEGIN, 128, 448,
FONT_NEXT, 130, 414,
FONT_END, 117, 393,
FONT_BEGIN, 128, 448,
FONT_NEXT, 117, 393,
FONT_NEXT, 84, 420,
FONT_END, 113, 372,
FONT_BEGIN, 348, 119,
FONT_NEXT, 334, 62,
FONT_NEXT, 341, 158,
FONT_END, 324, 189,
FONT_BEGIN, 324, 189,
FONT_NEXT, 334, 62,
FONT_END, 299, 21,
FONT_BEGIN, 324, 189,
FONT_NEXT, 299, 21,
FONT_END, 264, 237,
FONT_ADVANCE, 389, 0
    },
    {
116,
FONT_BEGIN, 13, 425,
FONT_NEXT, 26, 441,
FONT_NEXT, 17, 418,
FONT_NEXT, 57, 465,
FONT_NEXT, 70, 418,
FONT_NEXT, 102, 516,
FONT_END, 96, 16,
FONT_BEGIN, 70, 418,
FONT_NEXT, 96, 16,
FONT_END, 77, 54,
FONT_BEGIN, 70, 418,
FONT_NEXT, 77, 54,
FONT_END, 70, 117,
FONT_BEGIN, 154, 566,
FONT_NEXT, 154, 450,
FONT_NEXT, 147, 579,
FONT_NEXT, 154, 418,
FONT_END, 154, 132,
FONT_BEGIN, 147, 579,
FONT_NEXT, 154, 132,
FONT_END, 126, -5,
FONT_BEGIN, 147, 579,
FONT_NEXT, 126, -5,
FONT_NEXT, 122, 544,
FONT_NEXT, 96, 16,
FONT_END, 102, 516,
FONT_BEGIN, 255, 450,
FONT_NEXT, 255, 418,
FONT_NEXT, 154, 450,
FONT_END, 154, 418,
FONT_BEGIN, 279, 66,
FONT_NEXT, 257, 35,
FONT_NEXT, 266, 77,
FONT_END, 243, 54,
FONT_BEGIN, 243, 54,
FONT_NEXT, 257, 35,
FONT_END, 228, 11,
FONT_BEGIN, 243, 54,
FONT_NEXT, 228, 11,
FONT_NEXT, 205, 42,
FONT_NEXT, 196, -5,
FONT_NEXT, 175, 51,
FONT_NEXT, 162, -10,
FONT_NEXT, 160, 75,
FONT_NEXT, 126, -5,
FONT_END, 154, 132,
FONT_ADVANCE, 278, 0
    },
    {
117,
FONT_BEGIN, 9, 436,
FONT_NEXT, 9, 450,
FONT_NEXT, 51, 425,
FONT_NEXT, 155, 450,
FONT_NEXT, 65, 407,
FONT_NEXT, 155, 124,
FONT_END, 154, -5,
FONT_BEGIN, 65, 407,
FONT_NEXT, 154, -5,
FONT_NEXT, 71, 372,
FONT_NEXT, 115, 15,
FONT_END, 83, 54,
FONT_BEGIN, 71, 372,
FONT_NEXT, 83, 54,
FONT_END, 74, 83,
FONT_BEGIN, 71, 372,
FONT_NEXT, 74, 83,
FONT_END, 71, 120,
FONT_BEGIN, 338, -7,
FONT_NEXT, 338, 76,
FONT_NEXT, 340, -9,
FONT_END, 333, 370,
FONT_BEGIN, 333, 370,
FONT_NEXT, 338, 76,
FONT_NEXT, 333, 135,
FONT_END, 328, 100,
FONT_BEGIN, 328, 100,
FONT_NEXT, 338, 76,
FONT_END, 295, 33,
FONT_BEGIN, 328, 100,
FONT_NEXT, 295, 33,
FONT_NEXT, 317, 84,
FONT_END, 274, 57,
FONT_BEGIN, 274, 57,
FONT_NEXT, 295, 33,
FONT_END, 264, 9,
FONT_BEGIN, 274, 57,
FONT_NEXT, 264, 9,
FONT_NEXT, 230, 48,
FONT_NEXT, 234, -4,
FONT_END, 208, -9,
FONT_BEGIN, 230, 48,
FONT_NEXT, 208, -9,
FONT_NEXT, 190, 57,
FONT_NEXT, 190, -10,
FONT_NEXT, 167, 80,
FONT_NEXT, 154, -5,
FONT_NEXT, 157, 106,
FONT_END, 155, 124,
FONT_BEGIN, 479, 50,
FONT_NEXT, 479, 36,
FONT_NEXT, 444, 53,
FONT_NEXT, 340, -9,
FONT_NEXT, 425, 64,
FONT_END, 418, 82,
FONT_BEGIN, 418, 82,
FONT_NEXT, 340, -9,
FONT_NEXT, 417, 107,
FONT_NEXT, 333, 370,
FONT_END, 331, 396,
FONT_BEGIN, 417, 107,
FONT_NEXT, 331, 396,
FONT_NEXT, 417, 450,
FONT_NEXT, 322, 415,
FONT_NEXT, 259, 450,
FONT_NEXT, 300, 427,
FONT_END, 259, 433,
FONT_ADVANCE, 500, 0
    },
    {
118,
FONT_BEGIN, 215, 450,
FONT_NEXT, 215, 435,
FONT_NEXT, 19, 450,
FONT_NEXT, 183, 428,
FONT_END, 172, 419,
FONT_BEGIN, 19, 450,
FONT_NEXT, 172, 419,
FONT_END, 169, 405,
FONT_BEGIN, 19, 450,
FONT_NEXT, 169, 405,
FONT_END, 48, 427,
FONT_BEGIN, 19, 450,
FONT_NEXT, 48, 427,
FONT_END, 19, 435,
FONT_BEGIN, 338, 435,
FONT_NEXT, 338, 450,
FONT_NEXT, 370, 426,
FONT_NEXT, 427, 393,
FONT_NEXT, 385, 402,
FONT_NEXT, 412, 357,
FONT_NEXT, 381, 383,
FONT_NEXT, 284, 36,
FONT_NEXT, 373, 355,
FONT_END, 345, 280,
FONT_BEGIN, 345, 280,
FONT_NEXT, 284, 36,
FONT_NEXT, 280, 114,
FONT_NEXT, 269, 0,
FONT_NEXT, 178, 370,
FONT_NEXT, 257, -14,
FONT_END, 246, -7,
FONT_BEGIN, 178, 370,
FONT_NEXT, 246, -7,
FONT_END, 239, 7,
FONT_BEGIN, 178, 370,
FONT_NEXT, 239, 7,
FONT_END, 230, 33,
FONT_BEGIN, 178, 370,
FONT_NEXT, 230, 33,
FONT_END, 217, 63,
FONT_BEGIN, 178, 370,
FONT_NEXT, 217, 63,
FONT_END, 200, 106,
FONT_BEGIN, 178, 370,
FONT_NEXT, 200, 106,
FONT_END, 156, 212,
FONT_BEGIN, 178, 370,
FONT_NEXT, 156, 212,
FONT_NEXT, 169, 405,
FONT_NEXT, 110, 319,
FONT_END, 90, 364,
FONT_BEGIN, 169, 405,
FONT_NEXT, 90, 364,
FONT_END, 74, 398,
FONT_BEGIN, 169, 405,
FONT_NEXT, 74, 398,
FONT_END, 48, 427,
FONT_BEGIN, 477, 450,
FONT_NEXT, 477, 435,
FONT_NEXT, 338, 450,
FONT_NEXT, 455, 429,
FONT_END, 440, 417,
FONT_BEGIN, 338, 450,
FONT_NEXT, 440, 417,
FONT_END, 427, 393,
FONT_ADVANCE, 500, 0
    },
    {
119,
FONT_BEGIN, 201, 450,
FONT_NEXT, 201, 435,
FONT_NEXT, 21, 450,
FONT_NEXT, 169, 427,
FONT_END, 158, 417,
FONT_BEGIN, 21, 450,
FONT_NEXT, 158, 417,
FONT_END, 155, 401,
FONT_BEGIN, 21, 450,
FONT_NEXT, 155, 401,
FONT_END, 49, 420,
FONT_BEGIN, 21, 450,
FONT_NEXT, 49, 420,
FONT_END, 21, 435,
FONT_BEGIN, 262, 435,
FONT_NEXT, 262, 450,
FONT_NEXT, 288, 429,
FONT_END, 308, 413,
FONT_BEGIN, 308, 413,
FONT_NEXT, 262, 450,
FONT_END, 407, 398,
FONT_BEGIN, 308, 413,
FONT_NEXT, 407, 398,
FONT_NEXT, 327, 376,
FONT_NEXT, 372, 265,
FONT_NEXT, 338, 347,
FONT_END, 351, 310,
FONT_BEGIN, 351, 310,
FONT_NEXT, 372, 265,
FONT_END, 260, 25,
FONT_BEGIN, 351, 310,
FONT_NEXT, 260, 25,
FONT_NEXT, 260, 111,
FONT_END, 224, 205,
FONT_BEGIN, 224, 205,
FONT_NEXT, 260, 25,
FONT_END, 244, -5,
FONT_BEGIN, 224, 205,
FONT_NEXT, 244, -5,
FONT_END, 235, -14,
FONT_BEGIN, 224, 205,
FONT_NEXT, 235, -14,
FONT_END, 224, -5,
FONT_BEGIN, 224, 205,
FONT_NEXT, 224, -5,
FONT_NEXT, 190, 290,
FONT_NEXT, 217, 8,
FONT_END, 209, 30,
FONT_BEGIN, 190, 290,
FONT_NEXT, 209, 30,
FONT_END, 74, 372,
FONT_BEGIN, 190, 290,
FONT_NEXT, 74, 372,
FONT_NEXT, 164, 358,
FONT_END, 157, 383,
FONT_BEGIN, 157, 383,
FONT_NEXT, 74, 372,
FONT_NEXT, 155, 401,
FONT_NEXT, 60, 401,
FONT_END, 49, 420,
FONT_BEGIN, 465, 450,
FONT_NEXT, 465, 435,
FONT_NEXT, 262, 450,
FONT_NEXT, 423, 425,
FONT_END, 411, 415,
FONT_BEGIN, 262, 450,
FONT_NEXT, 411, 415,
FONT_END, 407, 398,
FONT_BEGIN, 571, 435,
FONT_NEXT, 571, 450,
FONT_NEXT, 602, 425,
FONT_NEXT, 671, 419,
FONT_END, 653, 381,
FONT_BEGIN, 602, 425,
FONT_NEXT, 653, 381,
FONT_NEXT, 611, 416,
FONT_END, 615, 402,
FONT_BEGIN, 615, 402,
FONT_NEXT, 653, 381,
FONT_NEXT, 610, 374,
FONT_NEXT, 515, 35,
FONT_NEXT, 598, 338,
FONT_END, 508, 116,
FONT_BEGIN, 508, 116,
FONT_NEXT, 515, 35,
FONT_END, 505, 10,
FONT_BEGIN, 508, 116,
FONT_NEXT, 505, 10,
FONT_NEXT, 428, 330,
FONT_NEXT, 498, -5,
FONT_END, 487, -14,
FONT_BEGIN, 428, 330,
FONT_NEXT, 487, -14,
FONT_END, 477, -6,
FONT_BEGIN, 428, 330,
FONT_NEXT, 477, -6,
FONT_END, 471, 7,
FONT_BEGIN, 428, 330,
FONT_NEXT, 471, 7,
FONT_END, 463, 29,
FONT_BEGIN, 428, 330,
FONT_NEXT, 463, 29,
FONT_END, 372, 265,
FONT_BEGIN, 428, 330,
FONT_NEXT, 372, 265,
FONT_NEXT, 411, 371,
FONT_END, 407, 398,
FONT_BEGIN, 694, 450,
FONT_NEXT, 694, 435,
FONT_NEXT, 571, 450,
FONT_END, 671, 419,
FONT_ADVANCE, 722, 0
    },
    {
120,
FONT_BEGIN, 24, 435,
FONT_NEXT, 24, 450,
FONT_NEXT, 64, 428,
FONT_NEXT, 188, 412,
FONT_NEXT, 83, 410,
FONT_END, 110, 375,
FONT_BEGIN, 110, 375,
FONT_NEXT, 188, 412,
FONT_END, 194, 391,
FONT_BEGIN, 110, 375,
FONT_NEXT, 194, 391,
FONT_NEXT, 204, 231,
FONT_NEXT, 210, 362,
FONT_END, 221, 197,
FONT_BEGIN, 204, 231,
FONT_NEXT, 221, 197,
FONT_END, 142, 74,
FONT_BEGIN, 204, 231,
FONT_NEXT, 142, 74,
FONT_NEXT, 90, 66,
FONT_NEXT, 122, 33,
FONT_NEXT, 68, 39,
FONT_END, 51, 24,
FONT_BEGIN, 51, 24,
FONT_NEXT, 122, 33,
FONT_END, 17, 0,
FONT_BEGIN, 51, 24,
FONT_NEXT, 17, 0,
FONT_END, 17, 15,
FONT_BEGIN, 231, 450,
FONT_NEXT, 231, 435,
FONT_NEXT, 24, 450,
FONT_NEXT, 202, 431,
FONT_END, 192, 424,
FONT_BEGIN, 24, 450,
FONT_NEXT, 192, 424,
FONT_END, 188, 412,
FONT_BEGIN, 275, 435,
FONT_NEXT, 275, 450,
FONT_NEXT, 301, 431,
FONT_END, 311, 424,
FONT_BEGIN, 311, 424,
FONT_NEXT, 275, 450,
FONT_END, 379, 417,
FONT_BEGIN, 311, 424,
FONT_NEXT, 379, 417,
FONT_END, 352, 391,
FONT_BEGIN, 311, 424,
FONT_NEXT, 352, 391,
FONT_NEXT, 315, 412,
FONT_END, 313, 402,
FONT_BEGIN, 313, 402,
FONT_NEXT, 352, 391,
FONT_END, 269, 271,
FONT_BEGIN, 313, 402,
FONT_NEXT, 269, 271,
FONT_NEXT, 304, 386,
FONT_END, 284, 356,
FONT_BEGIN, 284, 356,
FONT_NEXT, 269, 271,
FONT_NEXT, 248, 304,
FONT_NEXT, 221, 197,
FONT_END, 210, 362,
FONT_BEGIN, 433, 450,
FONT_NEXT, 433, 435,
FONT_NEXT, 275, 450,
FONT_NEXT, 403, 429,
FONT_END, 379, 417,
FONT_BEGIN, 278, 15,
FONT_NEXT, 309, 20,
FONT_NEXT, 278, 0,
FONT_END, 397, 75,
FONT_BEGIN, 397, 75,
FONT_NEXT, 309, 20,
FONT_END, 318, 41,
FONT_BEGIN, 397, 75,
FONT_NEXT, 318, 41,
FONT_NEXT, 269, 271,
FONT_NEXT, 311, 60,
FONT_END, 302, 73,
FONT_BEGIN, 269, 271,
FONT_NEXT, 302, 73,
FONT_END, 221, 197,
FONT_BEGIN, 479, 15,
FONT_NEXT, 479, 0,
FONT_NEXT, 445, 23,
FONT_NEXT, 278, 0,
FONT_NEXT, 423, 41,
FONT_END, 397, 75,
FONT_BEGIN, 162, 15,
FONT_NEXT, 162, 0,
FONT_NEXT, 136, 17,
FONT_NEXT, 17, 0,
FONT_END, 122, 33,
FONT_ADVANCE, 500, 0
    },
    {
121,
FONT_BEGIN, 220, 450,
FONT_NEXT, 220, 435,
FONT_NEXT, 14, 450,
FONT_NEXT, 182, 430,
FONT_END, 167, 421,
FONT_BEGIN, 14, 450,
FONT_NEXT, 167, 421,
FONT_END, 162, 406,
FONT_BEGIN, 14, 450,
FONT_NEXT, 162, 406,
FONT_END, 39, 428,
FONT_BEGIN, 14, 450,
FONT_NEXT, 39, 428,
FONT_END, 14, 436,
FONT_BEGIN, 340, 435,
FONT_NEXT, 340, 450,
FONT_NEXT, 376, 428,
FONT_NEXT, 439, 414,
FONT_END, 427, 390,
FONT_BEGIN, 376, 428,
FONT_NEXT, 427, 390,
FONT_NEXT, 388, 407,
FONT_NEXT, 273, -18,
FONT_NEXT, 380, 379,
FONT_END, 365, 339,
FONT_BEGIN, 365, 339,
FONT_NEXT, 273, -18,
FONT_NEXT, 287, 117,
FONT_END, 172, 370,
FONT_BEGIN, 172, 370,
FONT_NEXT, 273, -18,
FONT_END, 241, 20,
FONT_BEGIN, 172, 370,
FONT_NEXT, 241, 20,
FONT_END, 233, 43,
FONT_BEGIN, 172, 370,
FONT_NEXT, 233, 43,
FONT_END, 215, 83,
FONT_BEGIN, 172, 370,
FONT_NEXT, 215, 83,
FONT_END, 194, 125,
FONT_BEGIN, 172, 370,
FONT_NEXT, 194, 125,
FONT_END, 179, 158,
FONT_BEGIN, 172, 370,
FONT_NEXT, 179, 158,
FONT_END, 65, 404,
FONT_BEGIN, 172, 370,
FONT_NEXT, 65, 404,
FONT_NEXT, 162, 406,
FONT_END, 39, 428,
FONT_BEGIN, 475, 450,
FONT_NEXT, 475, 435,
FONT_NEXT, 340, 450,
FONT_NEXT, 451, 427,
FONT_END, 439, 414,
FONT_BEGIN, 30, -160,
FONT_NEXT, 36, -136,
FONT_NEXT, 34, -181,
FONT_END, 47, -199,
FONT_BEGIN, 47, -199,
FONT_NEXT, 36, -136,
FONT_END, 51, -123,
FONT_BEGIN, 47, -199,
FONT_NEXT, 51, -123,
FONT_NEXT, 70, -213,
FONT_NEXT, 78, -116,
FONT_NEXT, 103, -218,
FONT_NEXT, 118, -125,
FONT_END, 149, -134,
FONT_BEGIN, 103, -218,
FONT_NEXT, 149, -134,
FONT_NEXT, 154, -206,
FONT_NEXT, 167, -129,
FONT_END, 185, -113,
FONT_BEGIN, 154, -206,
FONT_NEXT, 185, -113,
FONT_NEXT, 197, -169,
FONT_NEXT, 214, -63,
FONT_END, 233, -11,
FONT_BEGIN, 197, -169,
FONT_NEXT, 233, -11,
FONT_NEXT, 235, -106,
FONT_NEXT, 239, 8,
FONT_END, 241, 20,
FONT_BEGIN, 235, -106,
FONT_NEXT, 241, 20,
FONT_END, 273, -18,
FONT_ADVANCE, 500, 0
    },
    {
122,
FONT_BEGIN, 403, 450,
FONT_NEXT, 403, 435,
FONT_NEXT, 56, 450,
FONT_NEXT, 293, 420,
FONT_END, 155, 420,
FONT_BEGIN, 56, 450,
FONT_NEXT, 155, 420,
FONT_END, 112, 413,
FONT_BEGIN, 56, 450,
FONT_NEXT, 112, 413,
FONT_END, 88, 396,
FONT_BEGIN, 56, 450,
FONT_NEXT, 88, 396,
FONT_END, 76, 368,
FONT_BEGIN, 56, 450,
FONT_NEXT, 76, 368,
FONT_END, 71, 332,
FONT_BEGIN, 56, 450,
FONT_NEXT, 71, 332,
FONT_END, 53, 332,
FONT_BEGIN, 418, 135,
FONT_NEXT, 404, 0,
FONT_NEXT, 400, 139,
FONT_END, 388, 89,
FONT_BEGIN, 388, 89,
FONT_NEXT, 404, 0,
FONT_NEXT, 369, 55,
FONT_END, 334, 36,
FONT_BEGIN, 334, 36,
FONT_NEXT, 404, 0,
FONT_NEXT, 307, 31,
FONT_NEXT, 27, 0,
FONT_NEXT, 272, 30,
FONT_END, 134, 30,
FONT_BEGIN, 134, 30,
FONT_NEXT, 27, 0,
FONT_END, 27, 15,
FONT_BEGIN, 134, 30,
FONT_NEXT, 27, 15,
FONT_END, 293, 420,
FONT_BEGIN, 134, 30,
FONT_NEXT, 293, 420,
FONT_END, 403, 435,
FONT_ADVANCE, 444, 0
    },
    {
123,
FONT_BEGIN, 100, 250,
FONT_NEXT, 142, 266,
FONT_NEXT, 142, 233,
FONT_END, 168, 209,
FONT_BEGIN, 168, 209,
FONT_NEXT, 142, 266,
FONT_END, 168, 248,
FONT_BEGIN, 168, 209,
FONT_NEXT, 168, 248,
FONT_NEXT, 181, 178,
FONT_NEXT, 215, 230,
FONT_NEXT, 185, 136,
FONT_NEXT, 210, -139,
FONT_END, 196, -114,
FONT_BEGIN, 185, 136,
FONT_NEXT, 196, -114,
FONT_END, 187, -82,
FONT_BEGIN, 185, 136,
FONT_NEXT, 187, -82,
FONT_END, 185, -41,
FONT_BEGIN, 185, 541,
FONT_NEXT, 187, 581,
FONT_NEXT, 185, 363,
FONT_NEXT, 196, 612,
FONT_END, 210, 637,
FONT_BEGIN, 185, 363,
FONT_NEXT, 210, 637,
FONT_END, 215, 267,
FONT_BEGIN, 185, 363,
FONT_NEXT, 215, 267,
FONT_NEXT, 181, 320,
FONT_NEXT, 168, 250,
FONT_NEXT, 168, 289,
FONT_END, 142, 266,
FONT_BEGIN, 142, 266,
FONT_NEXT, 168, 250,
FONT_END, 168, 248,
FONT_BEGIN, 142, 266,
FONT_END, 168, 248,
FONT_BEGIN, 350, 680,
FONT_NEXT, 350, 669,
FONT_NEXT, 280, 675,
FONT_NEXT, 305, 652,
FONT_END, 277, 627,
FONT_BEGIN, 280, 675,
FONT_NEXT, 277, 627,
FONT_NEXT, 228, 655,
FONT_NEXT, 263, 592,
FONT_END, 259, 546,
FONT_BEGIN, 228, 655,
FONT_NEXT, 259, 546,
FONT_END, 259, 378,
FONT_BEGIN, 228, 655,
FONT_NEXT, 259, 378,
FONT_END, 255, 330,
FONT_BEGIN, 228, 655,
FONT_NEXT, 255, 330,
FONT_END, 242, 294,
FONT_BEGIN, 228, 655,
FONT_NEXT, 242, 294,
FONT_END, 215, 267,
FONT_BEGIN, 228, 655,
FONT_NEXT, 215, 267,
FONT_END, 210, 637,
FONT_BEGIN, 259, 121,
FONT_NEXT, 259, -47,
FONT_NEXT, 255, 168,
FONT_NEXT, 228, -156,
FONT_NEXT, 242, 204,
FONT_END, 215, 230,
FONT_BEGIN, 215, 230,
FONT_NEXT, 228, -156,
FONT_END, 210, -139,
FONT_BEGIN, 215, 230,
FONT_END, 210, -139,
FONT_BEGIN, 350, -170,
FONT_NEXT, 350, -181,
FONT_NEXT, 305, -154,
FONT_NEXT, 280, -176,
FONT_NEXT, 277, -129,
FONT_NEXT, 228, -156,
FONT_NEXT, 263, -94,
FONT_END, 259, -47,
FONT_ADVANCE, 480, 0
    },
    {
124,
FONT_BEGIN, 133, 676,
FONT_NEXT, 133, -14,
FONT_NEXT, 67, 676,
FONT_END, 67, -14,
FONT_ADVANCE, 200, 0
    },
    {
125,
FONT_BEGIN, 130, 669,
FONT_NEXT, 130, 680,
FONT_NEXT, 174, 652,
FONT_NEXT, 199, 675,
FONT_NEXT, 202, 627,
FONT_END, 216, 592,
FONT_BEGIN, 216, 592,
FONT_NEXT, 199, 675,
FONT_END, 251, 655,
FONT_BEGIN, 216, 592,
FONT_NEXT, 251, 655,
FONT_NEXT, 221, 546,
FONT_NEXT, 237, 294,
FONT_END, 224, 330,
FONT_BEGIN, 221, 546,
FONT_NEXT, 224, 330,
FONT_END, 221, 378,
FONT_BEGIN, 295, 540,
FONT_NEXT, 295, 363,
FONT_NEXT, 292, 580,
FONT_NEXT, 264, 268,
FONT_NEXT, 283, 612,
FONT_END, 269, 637,
FONT_BEGIN, 269, 637,
FONT_NEXT, 264, 268,
FONT_NEXT, 251, 655,
FONT_END, 237, 294,
FONT_BEGIN, 380, 250,
FONT_NEXT, 337, 233,
FONT_NEXT, 337, 266,
FONT_END, 311, 289,
FONT_BEGIN, 311, 289,
FONT_NEXT, 337, 233,
FONT_END, 312, 251,
FONT_BEGIN, 311, 289,
FONT_NEXT, 312, 251,
FONT_END, 264, 268,
FONT_BEGIN, 311, 289,
FONT_NEXT, 264, 268,
FONT_NEXT, 298, 320,
FONT_END, 295, 363,
FONT_BEGIN, 221, 121,
FONT_NEXT, 224, 168,
FONT_NEXT, 221, -47,
FONT_NEXT, 237, 204,
FONT_NEXT, 251, -157,
FONT_NEXT, 264, 231,
FONT_NEXT, 269, -139,
FONT_END, 283, -114,
FONT_BEGIN, 283, -114,
FONT_NEXT, 264, 231,
FONT_NEXT, 292, -83,
FONT_END, 295, -42,
FONT_BEGIN, 295, -42,
FONT_NEXT, 264, 231,
FONT_NEXT, 295, 136,
FONT_END, 298, 178,
FONT_BEGIN, 298, 178,
FONT_NEXT, 264, 231,
FONT_NEXT, 311, 209,
FONT_NEXT, 312, 249,
FONT_NEXT, 337, 233,
FONT_END, 312, 251,
FONT_BEGIN, 130, -170,
FONT_NEXT, 174, -154,
FONT_NEXT, 130, -181,
FONT_END, 199, -177,
FONT_BEGIN, 199, -177,
FONT_NEXT, 174, -154,
FONT_END, 202, -129,
FONT_BEGIN, 199, -177,
FONT_NEXT, 202, -129,
FONT_END, 216, -94,
FONT_BEGIN, 199, -177,
FONT_NEXT, 216, -94,
FONT_NEXT, 251, -157,
FONT_END, 221, -47,
FONT_ADVANCE, 480, 0
    },
    {
126,
FONT_BEGIN, 502, 273,
FONT_NEXT, 451, 214,
FONT_NEXT, 466, 323,
FONT_END, 428, 273,
FONT_BEGIN, 428, 273,
FONT_NEXT, 451, 214,
FONT_END, 418, 194,
FONT_BEGIN, 428, 273,
FONT_NEXT, 418, 194,
FONT_NEXT, 405, 257,
FONT_NEXT, 377, 187,
FONT_NEXT, 378, 251,
FONT_END, 330, 263,
FONT_BEGIN, 330, 263,
FONT_NEXT, 377, 187,
FONT_END, 324, 196,
FONT_BEGIN, 330, 263,
FONT_NEXT, 324, 196,
FONT_NEXT, 276, 287,
FONT_NEXT, 275, 220,
FONT_NEXT, 219, 309,
FONT_NEXT, 222, 244,
FONT_END, 160, 255,
FONT_BEGIN, 219, 309,
FONT_NEXT, 160, 255,
FONT_NEXT, 165, 319,
FONT_END, 120, 311,
FONT_BEGIN, 120, 311,
FONT_NEXT, 160, 255,
FONT_END, 130, 248,
FONT_BEGIN, 120, 311,
FONT_NEXT, 130, 248,
FONT_END, 107, 232,
FONT_BEGIN, 120, 311,
FONT_NEXT, 107, 232,
FONT_NEXT, 85, 291,
FONT_NEXT, 76, 183,
FONT_END, 40, 233,
FONT_ADVANCE, 541, 0
    },
    {
END_OF_LIST
    }
};

// Character 0 may not be defined for the bitmap font because it is used as

// BITMAP_END_OF_LIST.  We could use END_OF_LIST (which is 256), but that
// gets truncated to zero anyway, so lets be clean and use BITMAP_END_OF_LIST.

GLubyte bitmapFont[][1+13] = {
    {
32,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    },
    {
33,
0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18
    },
    {
34,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x36, 0x36, 0x36, 0x36
    },
    {
35,
0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66,
0x66, 0xff, 0x66, 0x66, 0x00, 0x00
    },
    {
36,
0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f,
0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18
    },
    {
37,
0x00, 0x00, 0x0e, 0x1b, 0xdb, 0x6e, 0x30,
0x18, 0x0c, 0x76, 0xdb, 0xd8, 0x70
    },
    {
38,
0x00, 0x00, 0x7f, 0xc6, 0xcf, 0xd8, 0x70,
0x70, 0xd8, 0xcc, 0xcc, 0x6c, 0x38
    },
    {
39,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x1c, 0x0c, 0x0e
    },
    {
40,
0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x18, 0x0c
    },
    {
41,
0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c,
0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30
    },
    {
42,
0x00, 0x00, 0x00, 0x00, 0x99, 0x5a, 0x3c,
0xff, 0x3c, 0x5a, 0x99, 0x00, 0x00
    },
    {
43,
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff,
0xff, 0x18, 0x18, 0x18, 0x00, 0x00
    },
    {
44,
0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    },
    {
45,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    },
    {
46,
0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    },
    {
47,
0x00, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18,
0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03
    },
    {
48,
0x00, 0x00, 0x3c, 0x66, 0xc3, 0xe3, 0xf3,
0xdb, 0xcf, 0xc7, 0xc3, 0x66, 0x3c
    },
    {
49,
0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x78, 0x38, 0x18
    },
    {
50,
0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30,
0x18, 0x0c, 0x06, 0x03, 0xe7, 0x7e
    },
    {
51,
0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07,
0x7e, 0x07, 0x03, 0x03, 0xe7, 0x7e
    },
    {
52,
0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
0xff, 0xcc, 0x6c, 0x3c, 0x1c, 0x0c
    },
    {
53,
0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07,
0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xff
    },
    {
54,
0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc7,
0xfe, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e
    },
    {
55,
0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x18,
0x0c, 0x06, 0x03, 0x03, 0x03, 0xff
    },
    {
56,
0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7,
0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e
    },
    {
57,
0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x03,
0x7f, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e
    },
    {
58,
0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00,
0x38, 0x38, 0x00, 0x00, 0x00, 0x00
    },
    {
59,
0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00,
0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00
    },
    {
60,
0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60,
0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06
    },
    {
61,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00
    },
    {
62,
0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06,
0x03, 0x06, 0x0c, 0x18, 0x30, 0x60
    },
    {
63,
0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18,
0x0c, 0x06, 0x03, 0xc3, 0xc3, 0x7e
    },
    {
64,
0x00, 0x00, 0x3f, 0x60, 0xcf, 0xdb, 0xd3,
0xdd, 0xc3, 0x7e, 0x00, 0x00, 0x00
    },
    {
65,
0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff,
0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18
    },
    {
66,
0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7,
0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe
    },
    {
67,
0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e
    },
    {
68,
0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3,
0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc
    },
    {
69,
0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0,
0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff
    },
    {
70,
0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff
    },
    {
71,
0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf,
0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e
    },
    {
72,
0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3
    },
    {
73,
0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x7e
    },
    {
74,
0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06
    },
    {
75,
0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0,
0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3
    },
    {
76,
0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0
    },
    {
77,
0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3
    },
    {
78,
0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf,
0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3
    },
    {
79,
0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3,
0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e
    },
    {
80,
0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe
    },
    {
81,
0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3,
0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c
    },
    {
82,
0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0,
0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe
    },
    {
83,
0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07,
0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e
    },
    {
84,
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0xff
    },
    {
85,
0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3,
0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3
    },
    {
86,
0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66,
0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3
    },
    {
87,
0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb,
0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3
    },
    {
88,
0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c,
0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3
    },
    {
89,
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3
    },
    {
90,
0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30,
0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff
    },
    {
91,
0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x3c
    },
    {
92,
0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c,
0x18, 0x18, 0x30, 0x30, 0x60, 0x60
    },
    {
93,
0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c,
0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c
    },
    {
94,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18
    },
    {
95,
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    },
    {
96,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x38, 0x30, 0x70
    },
    {
97,
0x00, 0x00, 0x7f, 0xc3, 0xc3, 0x7f, 0x03,
0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00
    },
    {
98,
0x00, 0x00, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3,
0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0
    },
    {
99,
0x00, 0x00, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0,
0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00
    },
    {
100,
0x00, 0x00, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3,
0x7f, 0x03, 0x03, 0x03, 0x03, 0x03
    },
    {
101,
0x00, 0x00, 0x7f, 0xc0, 0xc0, 0xfe, 0xc3,
0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00
    },
    {
102,
0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30,
0xfc, 0x30, 0x30, 0x30, 0x33, 0x1e
    },
    {
103,
0x7e, 0xc3, 0x03, 0x03, 0x7f, 0xc3, 0xc3,
0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00
    },
    {
104,
0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0
    },
    {
105,
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x00, 0x00, 0x18, 0x00
    },
    {
106,
0x38, 0x6c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x00
    },
    {
107,
0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xf0, 0xd8,
0xcc, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0
    },
    {
108,
0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x78
    },
    {
109,
0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
0xdb, 0xfe, 0x00, 0x00, 0x00, 0x00
    },
    {
110,
0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00
    },
    {
111,
0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6,
0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00
    },
    {
112,
0xc0, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0xc3,
0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00
    },
    {
113,
0x03, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3,
0xc3, 0x7f, 0x00, 0x00, 0x00, 0x00
    },
    {
114,
0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xe0, 0xfe, 0x00, 0x00, 0x00, 0x00
    },
    {
115,
0x00, 0x00, 0xfe, 0x03, 0x03, 0x7e, 0xc0,
0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00
    },
    {
116,
0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0x30,
0x30, 0xfc, 0x30, 0x30, 0x30, 0x00
    },
    {
117,
0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6,
0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00
    },
    {
118,
0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66,
0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00
    },
    {
119,
0x00, 0x00, 0xc3, 0xe7, 0xff, 0xdb, 0xc3,
0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00
    },
    {
120,
0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c,
0x66, 0xc3, 0x00, 0x00, 0x00, 0x00
    },
    {
121,
0xc0, 0x60, 0x60, 0x30, 0x18, 0x3c, 0x66,
0x66, 0xc3, 0x00, 0x00, 0x00, 0x00
    },
    {
122,
0x00, 0x00, 0xff, 0x60, 0x30, 0x18, 0x0c,
0x06, 0xff, 0x00, 0x00, 0x00, 0x00
    },
    {
123,
0x00, 0x00, 0x0f, 0x18, 0x18, 0x18, 0x38,
0xf0, 0x38, 0x18, 0x18, 0x18, 0x0f
    },
    {
124,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0x18
    },
    {
125,
0x00, 0x00, 0xf0, 0x18, 0x18, 0x18, 0x1c,
0x0f, 0x1c, 0x18, 0x18, 0x18, 0xf0
    },
    {
126,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x8f, 0xf1, 0x60, 0x00, 0x00, 0x00
    },
    {
        BITMAP_END_OF_LIST
    }
};

GLenum tkCreateStrokeFont(GLuint fontBase)
{
    GLint mode, i, j;

    for (i = 0; strokeFont[i][0] != END_OF_LIST; i++) {
glNewList(fontBase+(unsigned int)strokeFont[i][0], GL_COMPILE);
for (j = 1; mode = strokeFont[i][j]; j += 3) {
    if (mode == FONT_BEGIN) {
glBegin(GL_LINE_STRIP);
glVertex2f((float)strokeFont[i][j+1]*STROKE_SCALE,
   (float)strokeFont[i][j+2]*STROKE_SCALE);
    } else if (mode == FONT_NEXT) {
glVertex2f((float)strokeFont[i][j+1]*STROKE_SCALE,
   (float)strokeFont[i][j+2]*STROKE_SCALE);
    } else if (mode == FONT_END) {
glVertex2f((float)strokeFont[i][j+1]*STROKE_SCALE,
   (float)strokeFont[i][j+2]*STROKE_SCALE);
glEnd();
    } else if (mode == FONT_ADVANCE) {
glTranslatef((float)strokeFont[i][j+1]*STROKE_SCALE,
     (float)strokeFont[i][j+2]*STROKE_SCALE, (GLfloat) 0.0);
break;
    }
}
glEndList();
    }
    return GL_TRUE;
}

GLenum tkCreateOutlineFont(GLuint fontBase)
{
    GLint mode, i, j;

    for (i = 0; outlineFont[i][0] != END_OF_LIST; i++) {
glNewList(fontBase+(unsigned int)outlineFont[i][0], GL_COMPILE);
for (j = 1; mode = outlineFont[i][j]; j += 3) {
    if (mode == FONT_BEGIN) {
glBegin(GL_LINE_STRIP);
glVertex2f((float)outlineFont[i][j+1]*OUTLINE_SCALE,
   (float)outlineFont[i][j+2]*OUTLINE_SCALE);
    } else if (mode == FONT_NEXT) {
glVertex2f((float)outlineFont[i][j+1]*OUTLINE_SCALE,
   (float)outlineFont[i][j+2]*OUTLINE_SCALE);
    } else if (mode == FONT_END) {
glVertex2f((float)outlineFont[i][j+1]*OUTLINE_SCALE,
   (float)outlineFont[i][j+2]*OUTLINE_SCALE);
glEnd();
    } else if (mode == FONT_ADVANCE) {
glTranslatef((float)outlineFont[i][j+1]*OUTLINE_SCALE,
     (float)outlineFont[i][j+2]*OUTLINE_SCALE, (GLfloat) 0.0);
break;
    }
}
glEndList();
    }
    return GL_TRUE;
}

GLenum tkCreateFilledFont(GLuint fontBase)
{
    GLint mode, i, j;

    for (i = 0; filledFont[i][0] != END_OF_LIST; i++) {
glNewList(fontBase+(unsigned int)filledFont[i][0], GL_COMPILE);
for (j = 1; mode = filledFont[i][j]; j += 3) {
    if (mode == FONT_BEGIN) {
glBegin(GL_TRIANGLE_STRIP);
glVertex2f((float)filledFont[i][j+1]*FILLED_SCALE,
   (float)filledFont[i][j+2]*FILLED_SCALE);
    } else if (mode == FONT_NEXT) {
glVertex2f((float)filledFont[i][j+1]*FILLED_SCALE,
   (float)filledFont[i][j+2]*FILLED_SCALE);
    } else if (mode == FONT_END) {
glVertex2f((float)filledFont[i][j+1]*FILLED_SCALE,
   (float)filledFont[i][j+2]*FILLED_SCALE);
glEnd();
    } else if (mode == FONT_ADVANCE) {
glTranslatef((float)filledFont[i][j+1]*FILLED_SCALE,
     (float)filledFont[i][j+2]*FILLED_SCALE, (GLfloat) 0.0);
break;
    }
}
glEndList();
    }
    return GL_TRUE;
}

GLenum tkCreateBitmapFont(GLuint fontBase)
{
    GLint i;

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    for (i = 0; bitmapFont[i][0] != BITMAP_END_OF_LIST; i++) {
glNewList(fontBase+(unsigned int)bitmapFont[i][0], GL_COMPILE);
glBitmap(8, 13, (GLfloat) 0.0, (GLfloat) 2.0, (GLfloat) 10.0, (GLfloat) 0.0, &bitmapFont[i][1]);
glEndList();
    }
    return GL_TRUE;
}

void tkDrawStr(GLuint base, char *str)
{

    glPushAttrib(GL_LIST_BIT);
    glListBase(base);
    glCallLists(strlen(str), GL_UNSIGNED_BYTE, (unsigned char *)str);
    glPopAttrib();
}


TKIMAGE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "tk.h"

#define static

#define IMAGIC      0x01da
#define IMAGIC_SWAP 0xda01

#define SWAP_SHORT_BYTES(x) ((((x) & 0xff) << 8) | (((x) & 0xff00) >> 8))
#define SWAP_LONG_BYTES(x) (((((x) & 0xff) << 24) | (((x) & 0xff00) << 8)) | \
                            ((((x) & 0xff0000) >> 8) | (((x) & 0xff000000) >> 24)))

typedef struct _rawImageRec {
    unsigned short imagic;
    unsigned short type;
    unsigned short dim;
    unsigned short sizeX, sizeY, sizeZ;
    unsigned long min, max;
    unsigned long wasteBytes;
    char name[80];
    unsigned long colorMap;
    HANDLE file;
    unsigned char *tmp, *tmpR, *tmpG, *tmpB;
    unsigned long rleEnd;
    unsigned long *rowStart;
    long *rowSize;
} rawImageRec;

static void RawImageClose(rawImageRec *raw);

static rawImageRec *RawImageOpenAW(char *fileName, BOOL bUnicode)
{
    rawImageRec *raw;
    unsigned long *rowStart, *rowSize, ulTmp;
    int x;
    DWORD dwBytesRead;

    raw = (rawImageRec *)malloc(sizeof(rawImageRec));
    if (raw == NULL) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        return NULL;
    }

    raw->file = bUnicode ? CreateFileW((LPWSTR) fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0) :
                           CreateFileA((LPSTR) fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
    if (raw->file == INVALID_HANDLE_VALUE) {
        char ach[256];

        bUnicode ? wsprintf(ach, "Failed to open image file %ws.\n", fileName) :
                   wsprintf(ach, "Failed to open image file %s.\n", fileName);

        MESSAGEBOX(GetFocus(), ach, "Error", MB_OK);

        free( raw );
        return NULL;
    }

    ReadFile(raw->file, (LPVOID) raw, 12, &dwBytesRead, (LPOVERLAPPED) NULL);

    if (raw->imagic == IMAGIC_SWAP) {
        raw->type = SWAP_SHORT_BYTES(raw->type);
        raw->dim = SWAP_SHORT_BYTES(raw->dim);
        raw->sizeX = SWAP_SHORT_BYTES(raw->sizeX);
        raw->sizeY = SWAP_SHORT_BYTES(raw->sizeY);
        raw->sizeZ = SWAP_SHORT_BYTES(raw->sizeZ);
    } else if (raw->imagic != IMAGIC) {
        // magic number is absent - conclude file is invalid (?)
        MESSAGEBOX(GetFocus(), "Invalid rgb file.", "Error", MB_OK);
        RawImageClose( raw );
        return NULL;
    }

    raw->tmp = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpR = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpG = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpB = (unsigned char *)malloc(raw->sizeX*256);
    if (raw->tmp == NULL || raw->tmpR == NULL || raw->tmpG == NULL ||
        raw->tmpB == NULL) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        RawImageClose( raw );
        return NULL;
    }

    if ((raw->type & 0xFF00) == 0x0100) {
        x = raw->sizeY * raw->sizeZ * sizeof(long);
        raw->rowStart = (unsigned long *)malloc(x);
        raw->rowSize = (long *)malloc(x);
        if (raw->rowStart == NULL || raw->rowSize == NULL) {
            MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
            RawImageClose( raw );
            return NULL;
        }
        raw->rleEnd = 512 + (2 * x);
        SetFilePointer(raw->file, 512, NULL, FILE_BEGIN);
        ReadFile(raw->file, (LPVOID) raw->rowStart, x, &dwBytesRead,
                 (LPOVERLAPPED) NULL);
        ReadFile(raw->file, (LPVOID) raw->rowSize, x, &dwBytesRead,
                 (LPOVERLAPPED) NULL);
        if (raw->imagic == IMAGIC_SWAP) {
            x /= sizeof(long);
            rowStart = raw->rowStart;
            rowSize = raw->rowSize;
            while (x--) {
                ulTmp = *rowStart;
                *rowStart++ = SWAP_LONG_BYTES(ulTmp);
                ulTmp = *rowSize;
                *rowSize++ = SWAP_LONG_BYTES(ulTmp);
            }
        }
    }
    return raw;
}

static void RawImageClose(rawImageRec *raw)
{
    if( !raw )
        return;
    CloseHandle(raw->file);
    if( raw->tmp ) free(raw->tmp);
    if( raw->tmpR ) free(raw->tmpR);
    if( raw->tmpG ) free(raw->tmpG);
    if( raw->tmpB ) free(raw->tmpB);
    free(raw);
}

static void RawImageGetRow(rawImageRec *raw, unsigned char *buf, int y, int z)
{
    unsigned char *iPtr, *oPtr, pixel;
    int count;
    DWORD dwBytesRead;

    if ((raw->type & 0xFF00) == 0x0100) {
        SetFilePointer(raw->file, raw->rowStart[y+z*raw->sizeY], NULL, FILE_BEGIN);
        ReadFile(raw->file, (LPVOID) raw->tmp,
                 (unsigned int)raw->rowSize[y+z*raw->sizeY], &dwBytesRead,
                 (LPOVERLAPPED) NULL);

        iPtr = raw->tmp;
        oPtr = buf;
        while (1) {
            pixel = *iPtr++;
            count = (int)(pixel & 0x7F);
            if (!count) {
                return;
            }
            if (pixel & 0x80) {
                while (count--) {
                    *oPtr++ = *iPtr++;
                }
            } else {
                pixel = *iPtr++;
                while (count--) {
                    *oPtr++ = pixel;
                }
            }
        }
    } else {
        SetFilePointer(raw->file, 512+(y*raw->sizeX)+(z*raw->sizeX*raw->sizeY),
                       NULL, FILE_BEGIN);
        ReadFile(raw->file, (LPVOID) buf, raw->sizeX, &dwBytesRead,
                 (LPOVERLAPPED) NULL);
    }
}

static void RawImageGetData(rawImageRec *raw, TK_RGBImageRec *final)
{
    unsigned char *ptr;
    int i, j;

    final->data = (unsigned char *)malloc((raw->sizeX+1)*(raw->sizeY+1)*4);
    if (final->data == NULL) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        return;
    }

    ptr = final->data;
    for (i = 0; i < raw->sizeY; i++) {
        RawImageGetRow(raw, raw->tmpR, i, 0);
        RawImageGetRow(raw, raw->tmpG, i, 1);
        RawImageGetRow(raw, raw->tmpB, i, 2);
        for (j = 0; j < raw->sizeX; j++) {
            *ptr++ = *(raw->tmpR + j);
            *ptr++ = *(raw->tmpG + j);
            *ptr++ = *(raw->tmpB + j);
        }
    }
}

TK_RGBImageRec *tkRGBImageLoad(char *fileName)
{
    return tkRGBImageLoadAW(fileName, FALSE);
}

TK_RGBImageRec *tkRGBImageLoadAW(char *fileName, BOOL bUnicode)
{
    rawImageRec *raw;
    TK_RGBImageRec *final;

    if( !(raw = RawImageOpenAW(fileName, bUnicode)) )
        return NULL;

    final = (TK_RGBImageRec *)malloc(sizeof(TK_RGBImageRec));
    if (final == NULL) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        RawImageClose(raw);
        return NULL;
    }
    final->sizeX = raw->sizeX;
    final->sizeY = raw->sizeY;
    RawImageGetData(raw, final);
    RawImageClose(raw);
    return final;
}


VECT3D.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
/* Routines to manipulate 3 dimensional vectors.  All these routines
 * should work even if the input and output vectors are the same.
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/gl.h>
#include "3d.h"
#include "tk.h"

#define static

void (*errfunc)(char *) = 0;

void seterrorfunc(void (*func)(char *))
{
    errfunc = func;
}

void error(char *s)
{
    if (errfunc)
        (*errfunc)(s);
    else {
        MESSAGEBOX(GetFocus(), s, "Error", MB_OK);
        exit(1);
    }
}

void diff3(GLdouble p[3], GLdouble q[3], GLdouble diff[3])
{
    diff[0] = p[0] - q[0];
    diff[1] = p[1] - q[1];
    diff[2] = p[2] - q[2];
}

void add3(GLdouble p[3], GLdouble q[3], GLdouble sum[3])
{
    sum[0] = p[0] + q[0];
    sum[1] = p[1] + q[1];
    sum[2] = p[2] + q[2];
}

void scalarmult(GLdouble s, GLdouble v[3], GLdouble vout[3])
{
    vout[0] = v[0]*s;
    vout[1] = v[1]*s;
    vout[2] = v[2]*s;
}

GLdouble dot3(GLdouble p[3], GLdouble q[3])
{
    return p[0]*q[0] + p[1]*q[1] + p[2]*q[2];
}

GLdouble length3(GLdouble v[3])
{
    return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
}

GLdouble dist3(GLdouble p[3], GLdouble q[3])
{
    GLdouble d[3];

    diff3(p, q, d);
    return length3(d);
}

void copy3(GLdouble old[3], GLdouble new[3])
{
    new[0] = old[0], new[1] = old[1], new[2] = old[2];
}

void crossprod(GLdouble v1[3], GLdouble v2[3], GLdouble prod[3])
{
    GLdouble p[3];      /* in case prod == v1 or v2 */

    p[0] = v1[1]*v2[2] - v2[1]*v1[2];
    p[1] = v1[2]*v2[0] - v2[2]*v1[0];
    p[2] = v1[0]*v2[1] - v2[0]*v1[1];
    prod[0] = p[0]; prod[1] = p[1]; prod[2] = p[2];
}

void normalize(GLdouble v[3])
{
    GLdouble d;

    d = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    if (d == (GLdouble)0.0) {
        error("normalize: zero length vector");
        v[0] = d = (GLdouble)1.0;
    }
    d = (GLdouble)1/d;
    v[0] *= d; v[1] *= d; v[2] *= d;
}

void identifymat3(GLdouble m[3][3])
{
    int i, j;

    for (i=0; i<3; i++)
        for (j=0; j<3; j++)
            m[i][j] = (i == j) ? (GLdouble)1.0 : (GLdouble)0.0;
}

void copymat3(GLdouble *to, GLdouble *from)
{
    int i;

    for (i=0; i<9; i++) {
        *to++ = *from++;
    }
}

void xformvec3(GLdouble v[3], GLdouble m[3][3], GLdouble vm[3])
{
    GLdouble result[3]; /* in case v == vm */
    int i;

    for (i=0; i<3; i++) {
        result[i] = v[0]*m[0][i] + v[1]*m[1][i] + v[2]*m[2][i];
    }
    for (i=0; i<3; i++) {
        vm[i] = result[i];
    }
}

long samepoint(GLdouble p1[3], GLdouble p2[3])
{
    if (p1[0] == p2[0] && p1[1] == p2[1] && p1[2] == p2[2])
        return 1;
    return 0;
}

void perpnorm(GLdouble p1[3], GLdouble p2[3], GLdouble p3[3], GLdouble n[3])
{
    GLdouble d1[3], d2[3];

    diff3(p2, p1, d1);
    diff3(p2, p3, d2);
    crossprod(d1, d2, n);
    normalize(n);
}


XFORM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <windows.h>
#include <math.h>
#include <stdio.h>
#include <GL/gl.h>
#include "3d.h"

#define static

#define STACKDEPTH 10

typedef struct {
    GLdoublemat[4][4];
    GLdoublenorm[3][3];
} mat_t;

static mat_t matstack[STACKDEPTH] = {
    {{{1.0, 0.0, 0.0, 0.0},
    {0.0, 1.0, 0.0, 0.0},
    {0.0, 0.0, 1.0, 0.0},
    {0.0, 0.0, 0.0, 1.0}},
    {{1.0, 0.0, 0.0},
    {0.0, 1.0, 0.0},
    {0.0, 0.0, 1.0}}}
};
static int identitymat = 1;

static int mattop = 0;

void m_xformpt(GLdouble pin[3], GLdouble pout[3],
    GLdouble nin[3], GLdouble nout[3])
{
    inti;
    GLdoubleptemp[3], ntemp[3];
    mat_t*m = &matstack[mattop];

    if (identitymat) {
for (i = 0; i < 3; i++) {
    pout[i] = pin[i];
    nout[i] = nin[i];
}
return;
    }
    for (i = 0; i < 3; i++) {
ptemp[i] = pin[0]*m->mat[0][i] +
   pin[1]*m->mat[1][i] +
   pin[2]*m->mat[2][i] +
   m->mat[3][i];
ntemp[i] = nin[0]*m->norm[0][i] +
   nin[1]*m->norm[1][i] +
   nin[2]*m->norm[2][i];
    }
    for (i = 0; i < 3; i++) {
pout[i] = ptemp[i];
nout[i] = ntemp[i];
    }
    normalize(nout);
}

void m_xformptonly(GLdouble pin[3], GLdouble pout[3])
{
    inti;
    GLdoubleptemp[3];
    mat_t*m = &matstack[mattop];

    if (identitymat) {
for (i = 0; i < 3; i++) {
    pout[i] = pin[i];
}
return;
    }
     for (i = 0; i < 3; i++) {
ptemp[i] = pin[0]*m->mat[0][i] +
   pin[1]*m->mat[1][i] +
   pin[2]*m->mat[2][i] +
   m->mat[3][i];
    }
    for (i = 0; i < 3; i++) {
pout[i] = ptemp[i];
    }
}

void m_pushmatrix(void)
{
    if (mattop < STACKDEPTH-1) {
matstack[mattop+1] = matstack[mattop];
mattop++;
    } else
error("m_pushmatrix: stack overflow\n");
}

void m_popmatrix(void)
{
    if (mattop > 0)
mattop--;
    else
error("m_popmatrix: stack underflow\n");
}

void m_translate(GLdouble x, GLdouble y, GLdouble z)
{
    inti;
    mat_t*m = &matstack[mattop];

    identitymat = 0;
    for (i = 0; i < 4; i++)
m->mat[3][i] = x*m->mat[0][i] +
 y*m->mat[1][i] +
 z*m->mat[2][i] +
 m->mat[3][i];
}

void m_scale(GLdouble x, GLdouble y, GLdouble z)
{
    inti;
    mat_t*m = &matstack[mattop];

    identitymat = 0;
    for (i = 0; i < 3; i++) {
m->mat[0][i] *= x;
m->mat[1][i] *= y;
m->mat[2][i] *= z;
    }
    for (i = 0; i < 3; i++) {
m->norm[0][i] /= x;
m->norm[1][i] /= y;
m->norm[2][i] /= z;
    }
}


ABOUT.TXT

OpenGL Samples

The OPENGL\SAMPLES directory contains a number of short samples. The following
are summaries for the operation of each of these samples.

TACCUM - Accumulation test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb Single buffer mode.
   -db Double buffer mode.
 - Keys:
   ESC Quit
   1   Use filled polygon mode.
   2   Use outlined polygon mode.

TBITMAP1 - Bitmap test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgbRGBA mode.
   -ci  Color index mode.
   -sb  Single buffer mode.
   -db  Double buffer mode.
 - Keys:
   ESC  Quit.

TBITMAP2 - Bitmap test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb RGBA mode.
   -ci  Color index mode.
   -sb  single buffer mode.
   -db  Double buffer mode.
 - Keys:
   ESC  Quit.
   1    Toggle display list mode.
   2    Toggle color animation mode.

TCOPY - Pixel copy test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb  Single buffer mode.
   -db  Double buffer mode.
   -f   <file name> RGB image file.
 - Keys:
   ESC  Quit.
   Z    Increase zoom factor.
   z    Decrease zoom factor.
 - Mouse input:
   Left Copy location.

TDEPTH - Z buffer test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb RGBA mode.
   -ci  Color index mode.
   -sb  Single buffer mode.
   -db  Double buffer mode.
 - Keys:
   ESC  Quit.
   1    Toggle anti-aliased mode.
   2    Toggle stipple mode.

TEVAL - Evaluator test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC Quit.
   LEFT  Rotate.
   RIGHT Rotate.
   UP    Rotate.
   DOWN  Rotate.
   1     Toggle dimensions.
   2     Toggle dimensions.
   e     Use eval mode.
   m     Use mesh mode.
   f     Toggle polygon mode.
   p     Toggle point mode.
   c     Toggle color mode.
   t     Toggle texture mode.
   l     Toggle lighting mode.

TFOG - Fog test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC Quit.
   LEFT Rotate.
   RIGHT Rotate.
   UP    Rotate.
   DOWN  Rotate.
   D     Increase fog density.
   d     Decrease fog density.

TLINE - Line test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgbRGBA mode.
   -ciColor index mode.
   -sbSingle buffer mode.
   -dbDouble buffer mode.
 - Keys:
ESCQuit.
   W    Increase line width.
   w    Decrease line width.
   1    Toggle stipple mode.
   2    Toggle anti-aliased mode.

TLOGO - Demo.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC Quit.
   LEFT Rotate.
   RIGHT Rotate
   UP    Move clipping plane.
   DOWN  Move clipping plane.
   Z     Translate.
   z     Translate.
   1     Use GL_POINT polygon mode.
   2     Use GL_LINE polygon mode.
   3     Use GL_FILL polygon mode.
   p     Toggle polygon fill modes.
   4     Use GL_NICEST for GL_POLYGON_SMOOTH_HINT.
   5     Use anti-aliased polygon mode.
   6     Use aliased polygon mode.
   8     Toggle dither mode.
   9     Toggle stipple polygon mode.
   0     Toggle flat/smooth shading mode.
   q     Disable cull mode.
   w     Use front face cull mode.
   e     Use back face cull mode.
   r     Use clockwise front face mode.
   t     Use counter-clockwise front face mode.
   y     Use MSB first stipple pattern.
   u     Use LSB first stipple pattern.
   a     Use brick texture map.
   s     Use checker texture map.
   d     Disable texture map.
   f     Use decal texture environment mode.
   g     Use modulate texture environment mode.

TNURB - Nurb test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC   Quit.
   LEFT  Rotate.
   RIGHT Rotate.
   UP    Rotate.
   DOWN  Rotate.

TOLYMPIC - Olymipic rings demo.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC   Quit.
   SPACE Restart demo.

TPOINT - Point test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC   Quit.
   LEFT  Translate.
   RIGHT Translate.
   UP    Translate.
   DOWN  Translate.
   W     Increase point width.
   w     Decrease point width.
   1     Toggle anti-aliased mode.

TPRIM - Primitive test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb RGBA mode.
   -ci  Color index mode.
   -sb  Single buffer mode.
   -db  Double buffer mode.
 - Keys:
   ESC  Quit.
   1    Toggle flat/smooth shade mode.
   2    Toggle outlined/filled polygon mode.
   3    Toggle color mask mode.

TQUAD - Quadric test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
   -f    <file name> Texture file.
 - Keys:
   ESC   Quit.
   LEFT  Rotate.
   RIGHT Rotate.
   UP    Rotate.
   DOWN  Rotate.
   X     Rotate.
   x     Rotate.
   1     Use GLU_FILL draw style.
   2     Use GLU_POINT draw style.
   3     Use GLU_LINE draw style.
   4     Use GLU_SILHOUETTE draw style.
   0     Toggle flat/smooth shade mode.
   f     Cycle through quadrics.
   d     Toggle orientation.
   A     Increase number of stacks.
   a     Decrease number of stacks.
   S     Increase number of slices.
   s     Decrease number of slices.
   G     Increase radius1.
   g     Decrease radius1.
   J     Increase radius2.
   j     Decrease radius2.
   H     Increase height.
   h     Decrease height.
   K     Increase angle1.
   k     Decrease angle1.
   L     Increase angle2.
   l     Decrease angle2.
   z     Toggle texture mode.
   q     Disable cull mode.
   w     Use front face cull mode.
   e     Use back face cull mode.
   r     Use clockwise front face mode.
   t     Use counter-clockwise front face mode.
   y     Toggle dither mode.

TSELECT - Selection test.
 - RGBA, SB.
 - Keys:
   ESC    Quit.
   LEFT   Rotate.
   RIGHT  Rotate.
   Z      Increase zoom factor.
   z      Decrease zoom factor.
   d      Zoom at current Mouse location.
   f      Print feedback information.
   l      Toggle outlined/filled polygon mode.
 - Mouse:
   Left   Recolor selected triangle.
   Center Enlarge selected triangle.
   Right  Delete selected triangle.

TSPEED - Speed test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb RGBA mode.
   -ci  Color index mode.
   -sb  Single buffer mode.
   -db  Double buffer mode.
 - Keys:
   ESC  Quit.
   a    Toggle anti-aliased mode.
   d    Toggle z buffering mode.
   f    Toggle fog mode.
   F    Toggle fog hint mode.
   s    Toggle flat/smooth shading mode.
   t    Toggle texturing mode.

TSPHERE - Spheremap test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
   -f    <file name> Texture file.
   -3    Use RGB components.
   -4    Use RGBA components.
 - Keys:
   ESC   Quit.
   LEFT  Rotate about the y axis.
   RIGHT Rotate about the y axis.
   UP    Rotate about the x axis.
   DOWN  Rotate about the x axis.
   a     Toggle auto rotate mode.
   c     Toggle between cylinder or cube object.
   t     Use torus object.
   d     Use decal texture mode.
   m     Use modulate texture mode.
   l     Toggle lighted mode.
   f     Toggle fog mode.
   0     Use nearest magification filter.
   1     Use linear magification.
   2     Use nearest minification filter.
   3     Use linear minification filter.
   4     Use nearest-mipmap-nearest minification filter.
   5     Use nearest-mipmap-linear minification filter.
   6     Use linear-mipmap-nearest minification filter.
   7     Use linear-mipmap-linear minification filter.

TSTAR - Demo.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC   Quit.
   SPACE Toggle weird movement mode.
   t     Toggle turbo mode.

TSTENCIL - Stencil test.
 - RGBA, SB.

TSTRETCH - Texture test.
 - RGBA, SB.
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
   -f    <file name> Texture file.
 - Keys:
   ESC   Quit.
   SPACE Start animation.
 - Mouse:
   Left  Added stretch point.

TTEXTURE - Texture test.
 - RGBA, SB/DB (SB default).
 - Command line options:
   -sb   Single buffer mode.
   -db   Double buffer mode.
   -f    <file name> Texture file.
 - Keys:
   ESC   Quit.
   LEFT  Rotate.
   RIGHT Rotate.
   UP    Rotate.
   DOWN  Rotate.
   T     Translate.
   t     Translate.
   s     Toggle sphere map mode.
   0     Use nearest magification filter.
   1     Use linear magification filter.
   2     Use nearest minification filter.
   3     Use linear minification filter.
   4     Use nearest-mipmap-nearest minification filter.
   5     Use nearest-mipmap-linear minification filter.
   6     Use linear-mipmap-nearest minification filter.
   7     Use linear-mipmap-linear minification filter.

TTRI - Triangle test.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
 - Keys:
   ESC   Quit.
   LEFT  Translate.
   RIGHT Translate.
   Z     Increase zoom factor.
   z     Decrease zoom factor.
   1     Use point polygon mode.
   2     Use line polygon mode.
   3     Use filled polygon mode.
   4     Use point primitive.
   5     Use line-loop primitive.
   6     Use polygon primitive.
   7     Toggle cull mode.
   8     Use clockwise/counter-clockwise front face mode.
   9     Toggle front/back face cull mode.
   v     Toggle show verticies mode.
   s     Toggle flat/smooth shade mode.
   h     Toggle hide bottom triangle mode.
   o     Toggle outline mode.
   m     Toggle dither mode.
   0     Toggle anti-aliased mode.

TWAVE - Demo.
 - RGBA/CI (RGBA default), SB/DB (SB default).
 - Command line options:
   -rgb  RGBA mode.
   -ci   Color index mode.
   -sb   Single buffer mode.
   -db   Double buffer mode.
   -grid <x> <y>   Number of grids.
   -size <number>  Size of grid.
   -wave <number>  Height of wave (floating point number).
   -frames <count> Number of frames.
 - Keys:
   ESC   Quit.
   c     Toggle contouring mode.
   s     Toggle flat/smooth shade mode.
   l     Toggle lighting mode.
   d     Toggle depth checking mode.
   SPACE Toggle step/animation mode.
   n     Single step in step mode.
   a     Toggle spin mode.


TACCUM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer, directRender;
GLint thing1, thing2;

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearAccum(0.0, 0.0, 0.0, 0.0);

    thing1 = glGenLists(1);
    glNewList(thing1, GL_COMPILE);
    glColor3f(1.0, 0.0, 0.0);
    glRectf(-1.0, -1.0, 1.0, 0.0);
    glEndList();

    thing2 = glGenLists(1);
    glNewList(thing2, GL_COMPILE);
    glColor3f(0.0, 1.0, 0.0);
    glRectf(0.0, -1.0, 1.0, 1.0);
    glEndList();
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void CALLBACK Key_1(void)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

static void CALLBACK Key_2(void)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

static void CALLBACK Draw(void)
{

    glPushMatrix();

    glScalef(0.8, 0.8, 1.0);

    glClear(GL_COLOR_BUFFER_BIT);
    glCallList(thing1);
    glAccum(GL_LOAD, 0.5);

    glClear(GL_COLOR_BUFFER_BIT);
    glCallList(thing2);
    glAccum(GL_ACCUM, 0.5);

    glAccum(GL_RETURN, 1.0);

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB | AUX_ACCUM;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Accum Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxMainLoop(Draw);
}


TBITMAP1.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define OPENGL_WIDTH 24
#define OPENGL_HEIGHT 13

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;

float boxA[3] = {
    0, 0, 0
};
float boxB[3] = {
    -100, 0, 0
};
float boxC[3] = {
    100, 0, 0
};
float boxD[3] = {
    0, 95, 0
};
float boxE[3] = {
    0, -105, 0
};
GLubyte OpenGL_bits1[] = {
   0x00, 0x03, 0x00,
   0x7f, 0xfb, 0xff,
   0x7f, 0xfb, 0xff,
   0x00, 0x03, 0x00,
   0x3e, 0x8f, 0xb7,
   0x63, 0xdb, 0xb0,
   0x63, 0xdb, 0xb7,
   0x63, 0xdb, 0xb6,
   0x63, 0x8f, 0xf3,
   0x63, 0x00, 0x00,
   0x63, 0x00, 0x00,
   0x63, 0x00, 0x00,
   0x3e, 0x00, 0x00,
};
GLubyte OpenGL_bits2[] = {
   0x00, 0x00, 0x00,
   0xff, 0xff, 0x01,
   0xff, 0xff, 0x01,
   0x00, 0x00, 0x00,
   0xf9, 0xfc, 0x01,
   0x8d, 0x0d, 0x00,
   0x8d, 0x0d, 0x00,
   0x8d, 0x0d, 0x00,
   0xcc, 0x0d, 0x00,
   0x0c, 0x4c, 0x0a,
   0x0c, 0x4c, 0x0e,
   0x8c, 0xed, 0x0e,
   0xf8, 0x0c, 0x00,
};
GLubyte logo_bits[] = {
   0x00, 0x66, 0x66,
   0xff, 0x66, 0x66,
   0x00, 0x00, 0x00,
   0xff, 0x3c, 0x3c,
   0x00, 0x42, 0x40,
   0xff, 0x42, 0x40,
   0x00, 0x41, 0x40,
   0xff, 0x21, 0x20,
   0x00, 0x2f, 0x20,
   0xff, 0x20, 0x20,
   0x00, 0x10, 0x90,
   0xff, 0x10, 0x90,
   0x00, 0x0f, 0x10,
   0xff, 0x00, 0x00,
   0x00, 0x66, 0x66,
   0xff, 0x66, 0x66,
};

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearIndex(0.0);
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Draw(void)
{
    float mapI[2], mapIA[2], mapIR[2];

    glClear(GL_COLOR_BUFFER_BIT);

    mapI[0] = 0.0;
    mapI[1] = 1.0;
    mapIR[0] = 0.0;
    mapIR[1] = 0.0;
    mapIA[0] = 1.0;
    mapIA[1] = 1.0;

    glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 2, mapIR);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 2, mapI);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 2, mapI);
    glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 2, mapIA);
    glPixelTransferi(GL_MAP_COLOR, GL_TRUE);

    AUX_SETCOLOR(windType, AUX_WHITE);
    glRasterPos3fv(boxA);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 24);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 8);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 2);
    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBitmap(16, 12, 8.0, 0.0, 0.0, 0.0, logo_bits);

    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    AUX_SETCOLOR(windType, AUX_WHITE);
    glRasterPos3fv(boxB);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    AUX_SETCOLOR(windType, AUX_YELLOW);
    glRasterPos3fv(boxC);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    AUX_SETCOLOR(windType, AUX_CYAN);
    glRasterPos3fv(boxD);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    AUX_SETCOLOR(windType, AUX_RED);
    glRasterPos3fv(boxE);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    glFlush();

    if (doubleBuffer) {
        auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
        auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    windType = (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Bitmap Test") == GL_FALSE) {
        auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxMainLoop(Draw);
}


TCOPY.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_Z(void );
static void CALLBACK Key_z(void );
static GLenum CALLBACK Mouse_left(struct _AUX_EVENTREC *event);
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;
GLint windW, windH;

char *fileName = 0;
AUX_RGBImageRec *xximage;
float point[3];
float zoom;
GLint x, y;

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);

    x = 0;
    y = windH;
    zoom = 1.8;
}

static void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;

    glViewport(0, 0, windW, windH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, windW, 0, windH);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_Z(void)
{
    zoom += 0.2;
}

static void CALLBACK Key_z(void)
{
    zoom -= 0.2;
    if (zoom < 0.2) {
        zoom = 0.2;
    }
}

static GLenum CALLBACK Mouse_left(AUX_EVENTREC *event)
{

    x = event->data[AUX_MOUSEX];
    y = event->data[AUX_MOUSEY];
    return GL_TRUE;
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    point[0] = (windW / 2) - (xximage->sizeX / 2);
    point[1] = (windH / 2) - (xximage->sizeY / 2);
    point[2] = 0;
    glRasterPos3fv(point);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelZoom(1.0, 1.0);
    glDrawPixels(xximage->sizeX, xximage->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         xximage->data);

    point[0] = (float)x;
    point[1] = windH - (float)y;
    point[2] = 0.0;
    glRasterPos3fv(point);

    glPixelZoom(zoom, zoom);
    glCopyPixels((windW/2)-(xximage->sizeX/2),
         (windH/2)-(xximage->sizeY/2),
         xximage->sizeX, xximage->sizeY, GL_COLOR);

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-f (No file name).\n");
        return GL_FALSE;
        } else {
        fileName = argv[++i];
        }
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    if (fileName == 0) {
    //printf("No image file.\n");
    auxQuit();
    }

    xximage = auxRGBImageLoad(fileName);

    windW = 300;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Copy Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_Z, Key_Z);
    auxKeyFunc(AUX_z, Key_z);
    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN, Mouse_left);
    auxMainLoop(Draw);
}


TDEPTH.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define CI_OFFSET_1 32
#define CI_OFFSET_2 48

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, directRender;

GLenum antiAlias, stipple;
GLubyte stippleBits[32*4] = {
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
};

static void Init(void)
{
    GLint i;

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearIndex(0.0);

    if (!rgb) {
    for (i = 0; i < 16; i++) {
        auxSetOneColor(i+CI_OFFSET_1, 0.0, 0.0, i/15.0);
        auxSetOneColor(i+CI_OFFSET_2, 0.0, i/15.0, 0.0);
    }
    }

    antiAlias = GL_FALSE;
    stipple = GL_FALSE;
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -1.0, 1.0, -0.5, 1000.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_1(void)
{
    antiAlias = !antiAlias;
}

static void CALLBACK Key_2(void)
{
    stipple = !stipple;
}

static void CALLBACK Draw(void)
{
    GLint ci1, ci2;
    float blue[3] = { 0.0, 0.0, 1.0 };
    float green[3] = { 0.0, 1.0, 0.0 };

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    if (antiAlias) {
    ci1 = CI_OFFSET_1;
    ci2 = CI_OFFSET_2;
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_BLEND);
    glEnable(GL_POLYGON_SMOOTH);
    glDisable(GL_DEPTH_TEST);
    } else {
    ci1 = AUX_BLUE;
    ci2 = AUX_GREEN;
    glDisable(GL_BLEND);
    glDisable(GL_POLYGON_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    }

    if (stipple) {
    glPolygonStipple(stippleBits);
    glEnable(GL_POLYGON_STIPPLE);
    } else {
    glDisable(GL_POLYGON_STIPPLE);
    }

    glBegin(GL_TRIANGLES);
    (rgb) ? glColor3fv(blue) : glIndexi(ci1);
    glVertex3f( 0.9, -0.9, -30.0);
    glVertex3f( 0.9,  0.9, -30.0);
    glVertex3f(-0.9,  0.0, -30.0);
    (rgb) ? glColor3fv(green) : glIndexi(ci2);
    glVertex3f(-0.9, -0.9, -40.0);
    glVertex3f(-0.9,  0.9, -40.0);
    glVertex3f( 0.9,  0.0, -25.0);
    glEnd();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;
    directRender = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-dr") == 0) {
        directRender = GL_TRUE;
    } else if (strcmp(argv[i], "-ir") == 0) {
        directRender = GL_FALSE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;
    auxInitDisplayMode(type);

    if (auxInitWindow("Depth Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxMainLoop(Draw);
}


TEVAL.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <gl\glaux.h>

#define VORDER 10
#define CORDER 10
#define TORDER 3

#define VMAJOR_ORDER 2
#define VMINOR_ORDER 3

#define CMAJOR_ORDER 2
#define CMINOR_ORDER 2

#define TMAJOR_ORDER 2
#define TMINOR_ORDER 2

#define VDIM 4
#define CDIM 4
#define TDIM 2

#define ONE_D 1
#define TWO_D 2

#define EVAL 3
#define MESH 4

static void Init(void );
static void DrawPoints1(void );
static void DrawPoints2(void );
static void DrawMapEval1(float du);
static void DrawMapEval2(float du,float dv);
static void RenderEval(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_left(void );
static void CALLBACK Key_right(void );
static void CALLBACK Key_up(void );
static void CALLBACK Key_down(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_e(void );
static void CALLBACK Key_m(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_p(void );
static void CALLBACK Key_c(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_l(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;

float rotX = 0.0, rotY = 0.0, translateZ = -1.0;

GLenum arrayType = ONE_D;
GLenum colorType = GL_FALSE;
GLenum textureType = GL_FALSE;
GLenum polygonFilled = GL_FALSE;
GLenum lighting = GL_FALSE;
GLenum mapPoint = GL_FALSE;
GLenum mapType = EVAL;

double point1[10*4] = {
    -0.5, 0.0, 0.0, 1.0,
    -0.4, 0.5, 0.0, 1.0,
    -0.3,-0.5, 0.0, 1.0,
    -0.2, 0.5, 0.0, 1.0,
    -0.1,-0.5, 0.0, 1.0,
    0.0, 0.5, 0.0, 1.0,
    0.1,-0.5, 0.0, 1.0,
    0.2, 0.5, 0.0, 1.0,
    0.3,-0.5, 0.0, 1.0,
    0.4, 0.0, 0.0, 1.0,
};
double cpoint1[10*4] = {
    0.0, 0.0, 1.0, 1.0,
    0.3, 0.0, 0.7, 1.0,
    0.6, 0.0, 0.3, 1.0,
    1.0, 0.0, 0.0, 1.0,
    1.0, 0.3, 0.0, 1.0,
    1.0, 0.6, 0.0, 1.0,
    1.0, 1.0, 0.0, 1.0,
    1.0, 1.0, 0.5, 1.0,
    1.0, 1.0, 1.0, 1.0,
};
double tpoint1[11*4] = {
    0.0, 0.0, 0.0, 1.0,
    0.0, 0.1, 0.0, 1.0,
    0.0, 0.2, 0.0, 1.0,
    0.0, 0.3, 0.0, 1.0,
    0.0, 0.4, 0.0, 1.0,
    0.0, 0.5, 0.0, 1.0,
    0.0, 0.6, 0.0, 1.0,
    0.0, 0.7, 0.0, 1.0,
    0.0, 0.8, 0.0, 1.0,
    0.0, 0.9, 0.0, 1.0,
};
double point2[2*3*4] = {
    -0.5, -0.5,  0.5, 1.0,
     0.0,  1.0,  0.5, 1.0,
     0.5, -0.5,  0.5, 1.0,
    -0.5,  0.5, -0.5, 1.0,
     0.0, -1.0, -0.5, 1.0,
     0.5,  0.5, -0.5, 1.0,
};
double cpoint2[2*2*4] = {
    0.0, 0.0, 0.0, 1.0,
    0.0, 0.0, 1.0, 1.0,
    0.0, 1.0, 0.0, 1.0,
    1.0, 1.0, 1.0, 1.0,
};
double tpoint2[2*2*2] = {
    0.0, 0.0, 0.0, 1.0,
    1.0, 0.0, 1.0, 1.0,
};
float textureImage[4*2*4] =  {
    1.0, 1.0, 1.0, 1.0,
    1.0, 0.0, 0.0, 1.0,
    1.0, 0.0, 0.0, 1.0,
    1.0, 1.0, 1.0, 1.0,
    1.0, 1.0, 1.0, 1.0,
    1.0, 0.0, 0.0, 1.0,
    1.0, 0.0, 0.0, 1.0,
    1.0, 1.0, 1.0, 1.0,
};

static void Init(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {1.0, 1.0, 1.0, 1.0};
    static float position[] = {0.0, 0.0, -150.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.7, 0.7, 0.7, 1.0};
    static float front_mat_diffuse[] = {1.0, 0.2, 1.0, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};
    static float decal[] = {GL_DECAL};
    static float modulate[] = {GL_MODULATE};
    static float repeat[] = {GL_REPEAT};
    static float clamp[] = {GL_CLAMP};
    static float nr[] = {GL_NEAREST};
    static float ln[] = {GL_LINEAR};

    glFrontFace(GL_CCW);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glMap1d(GL_MAP1_VERTEX_4, 0.0, 1.0, VDIM, VORDER, point1);
    glMap1d(GL_MAP1_COLOR_4, 0.0, 1.0, CDIM, CORDER, cpoint1);

    glMap2d(GL_MAP2_VERTEX_4, 0.0, 1.0, VMINOR_ORDER*VDIM, VMAJOR_ORDER, 0.0,
        1.0, VDIM, VMINOR_ORDER, point2);
    glMap2d(GL_MAP2_COLOR_4, 0.0, 1.0, CMINOR_ORDER*CDIM, CMAJOR_ORDER, 0.0,
        1.0, CDIM, CMINOR_ORDER, cpoint2);
    glMap2d(GL_MAP2_TEXTURE_COORD_2, 0.0, 1.0, TMINOR_ORDER*TDIM,
        TMAJOR_ORDER, 0.0, 1.0, TDIM, TMINOR_ORDER, tpoint2);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nr);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nr);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, 2, 4, 0, GL_RGBA, GL_FLOAT,
         (GLvoid *)textureImage);
}

static void DrawPoints1(void)
{
    GLint i;

    glColor3f(0.0, 1.0, 0.0);
    glPointSize(2);
    glBegin(GL_POINTS);
    for (i = 0; i < VORDER; i++) {
        glVertex4dv(&point1[i*4]);
    }
    glEnd();
}

static void DrawPoints2(void)
{
    GLint i, j;

    glColor3f(1.0, 0.0, 1.0);
    glPointSize(2);
    glBegin(GL_POINTS);
    for (i = 0; i < VMAJOR_ORDER; i++) {
        for (j = 0; j < VMINOR_ORDER; j++) {
        glVertex4dv(&point2[i*4*VMINOR_ORDER+j*4]);
        }
    }
    glEnd();
}

static void DrawMapEval1(float du)
{
    float u;

    glColor3f(1.0, 0.0, 0.0);
    glBegin(GL_LINE_STRIP);
    for (u = 0.0; u < 1.0; u += du) {
        glEvalCoord1d(u);
    }
    glEvalCoord1d(1.0);
    glEnd();
}

static void DrawMapEval2(float du, float dv)
{
    float u, v, tmp;

    glColor3f(1.0, 0.0, 0.0);
    for (v = 0.0; v < 1.0; v += dv) {
    glBegin(GL_QUAD_STRIP);
        for (u = 0.0; u <= 1.0; u += du) {
        glEvalCoord2d(u,v);
        tmp = (v + dv < 1.0) ? (v + dv) : 1.0;
        glEvalCoord2d(u, tmp);
        }
        glEvalCoord2d(1.0, v);
        glEvalCoord2d(1.0, v+dv);
    glEnd();
    }
}

static void RenderEval(void)
{

    if (colorType) {
    glEnable(GL_MAP1_COLOR_4);
    glEnable(GL_MAP2_COLOR_4);
    } else {
    glDisable(GL_MAP1_COLOR_4);
    glDisable(GL_MAP2_COLOR_4);
    }

    if (textureType) {
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_MAP2_TEXTURE_COORD_2);
    } else {
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_MAP2_TEXTURE_COORD_2);
    }

    if (polygonFilled) {
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    } else {
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }

    glShadeModel(GL_SMOOTH);

    switch (mapType) {
      case EVAL:
    switch (arrayType) {
      case ONE_D:
        glDisable(GL_MAP2_VERTEX_4);
        glEnable(GL_MAP1_VERTEX_4);
        DrawPoints1();
        DrawMapEval1(0.1/VORDER);
        break;
      case TWO_D:
        glDisable(GL_MAP1_VERTEX_4);
        glEnable(GL_MAP2_VERTEX_4);
        DrawPoints2();
        DrawMapEval2(0.1/VMAJOR_ORDER,0.1/VMINOR_ORDER);
        break;
    }
    break;
      case MESH:
    switch (arrayType) {
      case ONE_D:
        DrawPoints1();
        glDisable(GL_MAP2_VERTEX_4);
        glEnable (GL_MAP1_VERTEX_4);
        glColor3f(0.0, 0.0, 1.0);
        glMapGrid1d(40, 0.0, 1.0);
        if (mapPoint) {
        glPointSize(2);
        glEvalMesh1(GL_POINT, 0, 40);
        } else {
        glEvalMesh1(GL_LINE, 0, 40);
        }
        break;
      case TWO_D:
        DrawPoints2();
        glDisable(GL_MAP1_VERTEX_4);
        glEnable(GL_MAP2_VERTEX_4);
        glColor3f(0.0, 0.0, 1.0);
        glMapGrid2d(20, 0.0, 1.0, 20, 0.0, 1.0);
        if (mapPoint) {
        glPointSize(2);
        glEvalMesh2(GL_POINT, 0, 20, 0, 20);
        } else if (polygonFilled) {
        glEvalMesh2(GL_FILL, 0, 20, 0, 20);
        } else {
        glEvalMesh2(GL_LINE, 0, 20, 0, 20);
        }
        break;
    }
    break;
    }
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -1.0, 1.0, -0.5, 10.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_left(void)
{
    rotY -= 30;
}

static void CALLBACK Key_right(void)
{
    rotY += 30;
}

static void CALLBACK Key_up(void)
{
    rotX -= 30;
}

static void CALLBACK Key_down(void)
{
    rotX += 30;
}

static void CALLBACK Key_1(void)
{
    arrayType = ONE_D;
}

static void CALLBACK Key_2(void)
{
    arrayType = TWO_D;
}

static void CALLBACK Key_e(void)
{
    mapType = EVAL;
}

static void CALLBACK Key_m(void)
{
    mapType = MESH;
}

static void CALLBACK Key_f(void)
{
    polygonFilled = !polygonFilled;
}

static void CALLBACK Key_p(void)
{
    mapPoint = !mapPoint;
}

static void CALLBACK Key_c(void)
{
    colorType = !colorType;
}

static void CALLBACK Key_t(void)
{
    textureType = !textureType;
}

static void CALLBACK Key_l(void)
{
    lighting =! lighting;
    if (lighting) {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glEnable(GL_AUTO_NORMAL);
    } else {
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);
        glDisable(GL_AUTO_NORMAL);
    }
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(0.0, 0.0 , translateZ);
    glRotatef(rotX, 1, 0, 0);
    glRotatef(rotY, 0, 1, 0);
    RenderEval();

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB | AUX_DEPTH16;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Evaluator Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_left);
    auxKeyFunc(AUX_RIGHT, Key_right);
    auxKeyFunc(AUX_UP, Key_up);
    auxKeyFunc(AUX_DOWN, Key_down);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_e, Key_e);
    auxKeyFunc(AUX_m, Key_m);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_p, Key_p);
    auxKeyFunc(AUX_c, Key_c);
    auxKeyFunc(AUX_t, Key_t);
    auxKeyFunc(AUX_l, Key_l);
    auxMainLoop(Draw);
}


TFOG.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

static void Build_lists(void );
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_D(void );
static void CALLBACK Key_d(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer;

double plane[4] = {
    1.0, 0.0, -1.0, 0.0
};
float rotX = 5.0, rotY = -5.0, zTranslate = -65.0;
float fogDensity = 0.02;
GLint cubeList = 1;

float scp[18][3] = {
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    },
    {
    0.707107, 0.707107, 0.000000
    },
    {
    0.707107, 0.707107, 5.000000
    },
    {
    0.000000, 1.000000, 0.000000
    },
    {
    0.000000, 1.000000, 5.000000
    },
    {
    -0.707107, 0.707107, 0.000000
    },
    {
    -0.707107, 0.707107, 5.000000
    },
    {
    -1.000000, 0.000000, 0.000000
    },
    {
    -1.000000, 0.000000, 5.000000
    },
    {
    -0.707107, -0.707107, 0.000000
    },
    {
    -0.707107, -0.707107, 5.000000
    },
    {
    0.000000, -1.000000, 0.000000
    },
    {
    0.000000, -1.000000, 5.000000
    },
    {
    0.707107, -0.707107, 0.000000
    },
    {
    0.707107, -0.707107, 5.000000
    },
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    },
};

static void Build_lists(void)
{

    glNewList(cubeList, GL_COMPILE);
    glBegin(GL_TRIANGLE_STRIP);
       glNormal3fv(scp[0]); glVertex3fv(scp[0]);
       glNormal3fv(scp[0]); glVertex3fv(scp[1]);
       glNormal3fv(scp[2]); glVertex3fv(scp[2]);
       glNormal3fv(scp[2]); glVertex3fv(scp[3]);
       glNormal3fv(scp[4]); glVertex3fv(scp[4]);
       glNormal3fv(scp[4]); glVertex3fv(scp[5]);
       glNormal3fv(scp[6]); glVertex3fv(scp[6]);
       glNormal3fv(scp[6]); glVertex3fv(scp[7]);
       glNormal3fv(scp[8]); glVertex3fv(scp[8]);
       glNormal3fv(scp[8]); glVertex3fv(scp[9]);
       glNormal3fv(scp[10]); glVertex3fv(scp[10]);
       glNormal3fv(scp[10]); glVertex3fv(scp[11]);
       glNormal3fv(scp[12]); glVertex3fv(scp[12]);
       glNormal3fv(scp[12]); glVertex3fv(scp[13]);
       glNormal3fv(scp[14]); glVertex3fv(scp[14]);
       glNormal3fv(scp[14]); glVertex3fv(scp[15]);
       glNormal3fv(scp[16]); glVertex3fv(scp[16]);
       glNormal3fv(scp[16]); glVertex3fv(scp[17]);
    glEnd();
    glEndList();
}

static void Init(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {1.0, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 0.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.0, 0.0, 0.0, 1.0};
    static float front_mat_diffuse[] = {0.0, 1.0, 0.0, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.0, 0.0, 1.0, 1.0};
    static float back_mat_diffuse[] = {1.0, 0.0, 0.0, 1.0};
    static float lmodel_ambient[] = {0.0, 0.0, 0.0, 1.0};
    static float fog_color[] = {0.8, 0.8, 0.8, 1.0};

    glFrontFace(GL_CW);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);

    glEnable(GL_FOG);
    glFogi(GL_FOG_MODE, GL_EXP);
    glFogf(GL_FOG_DENSITY, fogDensity);
    if (rgb) {
    glFogfv(GL_FOG_COLOR, fog_color);
    glClearColor(0.8, 0.8, 0.8, 1.0);
    } else {
    glFogi(GL_FOG_INDEX, 1<<5);
    auxSetFogRamp(5, 3);
    glClearIndex(128);
    }

    Build_lists();
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, 1.0, 1.0, 200.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_UP(void)
{
    rotX -= 5;
}

static void CALLBACK Key_DOWN(void)
{
    rotX += 5;
}

static void CALLBACK Key_LEFT(void)
{
    rotY -= 5;
}

static void CALLBACK Key_RIGHT(void)
{
    rotY += 5;
}

static void CALLBACK Key_D(void)
{
    if (rgb) {
        fogDensity *= 1.10;
        glFogf(GL_FOG_DENSITY, fogDensity);
    }
}

static void CALLBACK Key_d(void)
{
    if (rgb) {
        fogDensity /= 1.10;
        glFogf(GL_FOG_DENSITY, fogDensity);
    }
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(0, 0, zTranslate);
    glRotatef(rotY, 0,1,0);
    glRotatef(rotX, 1,0,0);
    glScalef(1.0, 1.0, 10.0);

    glCallList(cubeList);

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Fog Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_D, Key_D);
    auxKeyFunc(AUX_d, Key_d);
    auxMainLoop(Draw);
}


TLINE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define CI_OFFSET 16

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_W(void );
static void CALLBACK Key_w(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;

GLenum mode1, mode2;
GLint size;
float pntA[3] = {
    -160.0, 0.0, 0.0
};
float pntB[3] = {
    -130.0, 0.0, 0.0
};
float pntC[3] = {
    -40.0, -50.0, 0.0
};
float pntD[3] = {
    30.0, 60.0, 0.0
};

static void Init(void)
{
    GLint i;

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glLineStipple(1, 0xF0E0);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    if (!rgb) {
    for (i = 0; i < 16; i++) {
        auxSetOneColor(i+CI_OFFSET, i/15.0, i/15.0, 0.0);
    }
    }

    mode1 = GL_FALSE;
    mode2 = GL_FALSE;
    size = 1;
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_1(void)
{
    mode1 = !mode1;
}

static void CALLBACK Key_2(void)
{
    mode2 = !mode2;
}

static void CALLBACK Key_W(void)
{
    size++;
}

static void CALLBACK Key_w(void)
{
    size--;
}

static void CALLBACK Draw(void)
{
    GLint ci, i;

    glClear(GL_COLOR_BUFFER_BIT);

    glLineWidth(size);

    if (mode1) {
    glEnable(GL_LINE_STIPPLE);
    } else {
    glDisable(GL_LINE_STIPPLE);
    }

    if (mode2) {
    ci = CI_OFFSET;
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    } else {
    ci = AUX_YELLOW;
    glDisable(GL_LINE_SMOOTH);
    glDisable(GL_BLEND);
    }

    glPushMatrix();

    for (i = 0; i < 360; i += 5) {
    glRotatef(5.0, 0,0,1);

    (rgb) ? glColor3f(1.0, 1.0, 0.0) : glIndexi(ci);
    glBegin(GL_LINE_STRIP);
        glVertex3fv(pntA);
        glVertex3fv(pntB);
    glEnd();

    glPointSize(1);

    AUX_SETCOLOR(windType, AUX_GREEN);
    glBegin(GL_POINTS);
        glVertex3fv(pntA);
        glVertex3fv(pntB);
    glEnd();
    }

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    windType |= (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Line Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_W, Key_W);
    auxKeyFunc(AUX_w, Key_w);
    auxMainLoop(Draw);
}


TLOGO.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define PI 3.141592654

#define BLACK 0
#define GRAY 128
#define WHITE 255
#define BL 0x00
#define WH 0xFF
#define RD 0xA40000FF
#define WT 0xFFFFFFFF

#define CHECKIMAGEWIDTH 8
#define CHECKIMAGEHEIGHT 8
#define BRICKIMAGEWIDTH 16
#define BRICKIMAGEHEIGHT 16

static void SetUpAntiAliasedGrayScale(void );
static void BendForward(void );
static void BendLeft(void );
static void BendRight(void );
static void BuildSingleCylinder(void );
static void BuildDoubleCylinder(void );
static void BuildElbow(void );
static void BuildLogo(void );
static void BuildLists(void );
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_Z(void );
static void CALLBACK Key_z(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void CALLBACK Key_p(void );
static void CALLBACK Key_4(void );
static void CALLBACK Key_5(void );
static void CALLBACK Key_6(void );
static void CALLBACK Key_8(void );
static void CALLBACK Key_9(void );
static void CALLBACK Key_0(void );
static void CALLBACK Key_q(void );
static void CALLBACK Key_w(void );
static void CALLBACK Key_e(void );
static void CALLBACK Key_r(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_y(void );
static void CALLBACK Key_u(void );
static void CALLBACK Key_a(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_g(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, directRender;

float black[3] = {0.0, 0.0, 0.0};
float white[3] = {1.0, 1.0, 1.0};
float gray[3] = {0.5, 0.5, 0.5};
float blue[3] = {0.0, 0.0, 1.0};
GLint colorIndexes[3] = {0, 200, 255};

GLenum polyMode;
GLenum dithering;
GLenum shade;
GLenum doStipple;

double plane[4] = {1.0, 0.0, -1.0, 0.0};
float xRotation = 30.0, yRotation = 30.0;
float zTranslation = -15.0;

GLint singleCylinder;
GLint doubleCylinder;
GLint elbow, logo;

GLubyte checkImage[3*CHECKIMAGEWIDTH*CHECKIMAGEHEIGHT] = {
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
};
GLuint brickImage[BRICKIMAGEWIDTH*BRICKIMAGEHEIGHT] = {
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD
};

GLubyte *image = checkImage;
GLint imageHeight = CHECKIMAGEHEIGHT;
GLint imageWidth = CHECKIMAGEWIDTH;

float decal[] = {
    GL_DECAL,
};
float modulate[] = {
    GL_MODULATE,
};
float repeat[] = {
    GL_REPEAT,
};
float nearest[] = {
    GL_NEAREST,
};

GLubyte stipple[4*32] = {
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,

    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,

    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,

    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
};

float tscp[18][2] = {
    {
    0.0, 0.0
    },
    {
    1.0, 0.0
    },
    {
    0.0, 0.125
    },
    {
    1.0, 0.125
    },
    {
    0.0, 0.250
    },
    {
    1.0, 0.25
    },
    {
    0.0, 0.375
    },
    {
    1.0, 0.375
    },
    {
    0.0, 0.50
    },
    {
    1.0, 0.50
    },
    {
    0.0, 0.625
    },
    {
    1.0, 0.625
    },
    {
    0.0, 0.75
    },
    {
    1.0, 0.75
    },
    {
    0.0, 0.875
    },
    {
    1.0, 0.875
    },
    {
    0.0, 1.0
    },
    {
    1.0, 1.0
    }
};
float scp[18][3] = {
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    },
    {
    0.707107, 0.707107, 0.000000
    },
    {
    0.707107, 0.707107, 5.000000
    },
    {
    0.000000, 1.000000, 0.000000
    },
    {
    0.000000, 1.000000, 5.000000
    },
    {
    -0.707107, 0.707107, 0.000000
    },
    {
    -0.707107, 0.707107, 5.000000
    },
    {
    -1.000000, 0.000000, 0.000000
    },
    {
    -1.000000, 0.000000, 5.000000
    },
    {
    -0.707107, -0.707107, 0.000000
    },
    {
    -0.707107, -0.707107, 5.000000
    },
    {
    0.000000, -1.000000, 0.000000
    },
    {
    0.000000, -1.000000, 5.000000
    },
    {
    0.707107, -0.707107, 0.000000
    },
    {
    0.707107, -0.707107, 5.000000
    },
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    }
};
float dcp[18][3] = {
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 7.000000
    },
    {
    0.707107, 0.707107, 0.000000
    },
    {
    0.707107, 0.707107, 7.000000
    },
    {
    0.000000, 1.000000, 0.000000
    },
    {
    0.000000, 1.000000, 7.000000
    },
    {
    -0.707107, 0.707107, 0.000000
    },
    {
    -0.707107, 0.707107, 7.000000
    },
    {
    -1.000000, 0.000000, 0.000000
    },
    {
    -1.000000, 0.000000, 7.000000
    },
    {
    -0.707107, -0.707107, 0.000000
    },
    {
    -0.707107, -0.707107, 7.000000
    },
    {
    0.000000, -1.000000, 0.000000
    },
    {
    0.000000, -1.000000, 7.000000
    },
    {
    0.707107, -0.707107, 0.000000
    },
    {
    0.707107, -0.707107, 7.000000
    },
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 7.000000
    }
};
float ep[7][9][3] = {
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.707107, 0.000000
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.707107, 0.000000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.707107, 0.000000
    },
    {
        0.000000, -1.000000, 0.000000
    },
    {
        0.707107, -0.707107, 0.000000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.034074, 0.258819
    },
    {
        0.707107, 0.717087, 0.075806
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.717087, 0.075806
    },
    {
        -1.000000, 0.034074, 0.258819
    },
    {
        -0.707107, -0.648939, 0.441832
    },
    {
        0.000000, -0.931852, 0.517638
    },
    {
        0.707107, -0.648939, 0.441832
    },
    {
        1.000000, 0.034074, 0.258819
    }
    },
    {
    {
        1.000000, 0.133975, 0.500000
    },
    {
        0.707107, 0.746347, 0.146447
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.746347, 0.146447
    },
    {
        -1.000000, 0.133975, 0.500000
    },
    {
        -0.707107, -0.478398, 0.853553
    },
    {
        0.000000, -0.732051, 1.000000
    },
    {
        0.707107, -0.478398, 0.853553
    },
    {
        1.000000, 0.133975, 0.500000
    }
    },
    {
    {
        1.000000, 0.292893, 0.707107
    },
    {
        0.707107, 0.792893, 0.207107
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.792893, 0.207107
    },
    {
        -1.000000, 0.292893, 0.707107
    },
    {
        -0.707107, -0.207107, 1.207107
    },
    {
        0.000000, -0.414214, 1.414214
    },
    {
        0.707107, -0.207107, 1.207107
    },
    {
        1.000000, 0.292893, 0.707107
    }
    },
    {
    {
        1.000000, 0.500000, 0.866025
    },
    {
        0.707107, 0.853553, 0.253653
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.853553, 0.253653
    },
    {
        -1.000000, 0.500000, 0.866025
    },
    {
        -0.707107, 0.146447, 1.478398
    },
    {
        0.000000, 0.000000, 1.732051
    },
    {
        0.707107, 0.146447, 1.478398
    },
    {
        1.000000, 0.500000, 0.866025
    }
    },
    {
    {
        1.000000, 0.741181, 0.965926
    },
    {
        0.707107, 0.924194, 0.282913
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.924194, 0.282913
    },
    {
        -1.000000, 0.741181, 0.965926
    },
    {
        -0.707107, 0.558168, 1.648939
    },
    {
        0.000000, 0.482362, 1.931852
    },
    {
        0.707107, 0.558168, 1.648939
    },
    {
        1.000000, 0.741181, 0.965926
    }
    },
    {
    {
        1.000000, 1.000000, 1.000000
    },
    {
        0.707107, 1.000000, 0.292893
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 1.000000, 0.292893
    },
    {
        -1.000000, 1.000000, 1.000000
    },
    {
        -0.707107, 1.000000, 1.707107
    },
    {
        0.000000, 1.000000, 2.000000
    },
    {
        0.707107, 1.000000, 1.707107
    },
    {
        1.000000, 1.000000, 1.000000
    }
    }
};
float en[7][9][3] = {
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.707107, 0.000000
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.707107, 0.000000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.707107, 0.000000
    },
    {
        0.000000, -1.000000, 0.000000
    },
    {
        0.707107, -0.707107, 0.000000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.683013, -0.183013
    },
    {
        0.000000, 0.965926, -0.258819
    },
    {
        -0.707107, 0.683013, -0.183013
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.683013, 0.183013
    },
    {
        0.000000, -0.965926, 0.258819
    },
    {
        0.707107, -0.683013, 0.183013
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.612372, -0.353553
    },
    {
        0.000000, 0.866025, -0.500000
    },
    {
        -0.707107, 0.612372, -0.353553
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.612372, 0.353553
    },
    {
        0.000000, -0.866025, 0.500000
    },
    {
        0.707107, -0.612372, 0.353553
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.500000, -0.500000
    },
    {
        0.000000, 0.707107, -0.707107
    },
    {
        -0.707107, 0.500000, -0.500000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.500000, 0.500000
    },
    {
        0.000000, -0.707107, 0.707107
    },
    {
        0.707107, -0.500000, 0.500000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.353553, -0.612372
    },
    {
        0.000000, 0.500000, -0.866025
    },
    {
        -0.707107, 0.353553, -0.612372
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.353553, 0.612372
    },
    {
        0.000000, -0.500000, 0.866025
    },
    {
        0.707107, -0.353553, 0.612372
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.183013, -0.683013
    },
    {
        0.000000, 0.258819, -0.965926
    },
    {
        -0.707107, 0.183013, -0.683013
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.183013, 0.683013
    },
    {
        0.000000, -0.258819, 0.965926
    },
    {
        0.707107, -0.183013, 0.683013
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.000000, -0.707107
    },
    {
        0.000000, 0.000000, -1.000000
    },
    {
        -0.707107, 0.000000, -0.707107
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, 0.000000, 0.707107
    },
    {
        0.000000, 0.000000, 1.000000
    },
    {
        0.707107, 0.000000, 0.707107
    },
    {
        1.000000, 0.000000, 0.000000
    }
    }
};
float tep[7][9][2] = {
    {
    {
        0,     0.0
    },
    {
        0.125, 0.0
    },
    {
        0.25,  0.0
    },
    {
        0.375, 0.0
    },
    {
        0.5,   0.0
    },
    {
        0.625, 0.0
    },
    {
        0.75,  0.0
    },
    {
        0.875, 0.0
    },
    {
        1.0,   0.0
    }
    },
    {
    {
        0,     0.16667
    },
    {
        0.125, 0.16667
    },
    {
        0.25,  0.16667
    },
    {
        0.375, 0.16667
    },
    {
        0.5,   0.16667
    },
    {
        0.625, 0.16667
    },
    {
        0.75,  0.16667
    },
    {
        0.875, 0.16667
    },
    {
        1.0,   0.16667
    }
    },
    {
    {
        0,     0.33333
    },
    {
        0.125, 0.33333
    },
    {
        0.25,  0.33333
    },
    {
        0.375, 0.33333
    },
    {
        0.5,   0.33333
    },
    {
        0.625, 0.33333
    },
    {
        0.75,  0.33333
    },
    {
        0.875, 0.33333
    },
    {
        1.0,   0.33333
    }
    },
    {
    {
        0,     0.5
    },
    {
        0.125, 0.5
    },
    {
        0.25,  0.5
    },
    {
        0.375, 0.5
    },
    {
        0.5,   0.5
    },
    {
        0.625, 0.5
    },
    {
        0.75,  0.5
    },
    {
        0.875, 0.5
    },
    {
        1.0,   0.5
    }
    },
    {
    {
        0,     0.6667
    },
    {
        0.125, 0.6667
    },
    {
        0.25,  0.6667
    },
    {
        0.375, 0.6667
    },
    {
        0.5,   0.6667
    },
    {
        0.625, 0.6667
    },
    {
        0.75,  0.6667
    },
    {
        0.875, 0.6667
    },
    {
        1.0,   0.6667
    }
    },
    {
    {
        0,     0.83333
    },
    {
        0.125, 0.83333
    },
    {
        0.25,  0.83333
    },
    {
        0.375, 0.83333
    },
    {
        0.5,   0.83333
    },
    {
        0.625, 0.83333
    },
    {
        0.75,  0.83333
    },
    {
        0.875, 0.83333
    },
    {
        1.0,   0.83333
    }
    },
    {
    {
        0,     1.0
    },
    {
        0.125, 1.0
    },
    {
        0.25,  1.0
    },
    {
        0.375, 1.0
    },
    {
        0.5,   1.0
    },
    {
        0.625, 1.0
    },
    {
        0.75,  1.0
    },
    {
        0.875, 1.0
    },
    {
        1.0,   1.0
    }
    }
};

static void SetUpAntiAliasedGrayScale(void)
{
    float color;
    GLint i, j;

    for (i = 0; i < 16; i++) {
    color = (2 * i + 1) / 32.0;
    for (j = 0; j < 16; j++) {
        auxSetOneColor(i*16+j, color*j/15.0, color*j/15.0, color*j/15.0);
    }
    }
}

static void BendForward(void)
{

    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BendLeft(void)
{

    glRotatef(-90.0, 0, 0, 1);
    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BendRight(void)
{

    glRotatef(90.0, 0, 0, 1);
    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BuildSingleCylinder(void)
{

    glNewList(singleCylinder, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
       glNormal3fv(scp[0]); glTexCoord2fv(tscp[0]); glVertex3fv(scp[0]);
       glNormal3fv(scp[0]); glTexCoord2fv(tscp[1]); glVertex3fv(scp[1]);
       glNormal3fv(scp[2]); glTexCoord2fv(tscp[2]); glVertex3fv(scp[2]);
       glNormal3fv(scp[2]); glTexCoord2fv(tscp[3]); glVertex3fv(scp[3]);
       glNormal3fv(scp[4]); glTexCoord2fv(tscp[4]); glVertex3fv(scp[4]);
       glNormal3fv(scp[4]); glTexCoord2fv(tscp[5]); glVertex3fv(scp[5]);
       glNormal3fv(scp[6]); glTexCoord2fv(tscp[6]); glVertex3fv(scp[6]);
       glNormal3fv(scp[6]); glTexCoord2fv(tscp[7]); glVertex3fv(scp[7]);
       glNormal3fv(scp[8]); glTexCoord2fv(tscp[8]); glVertex3fv(scp[8]);
       glNormal3fv(scp[8]); glTexCoord2fv(tscp[9]); glVertex3fv(scp[9]);
       glNormal3fv(scp[10]); glTexCoord2fv(tscp[10]); glVertex3fv(scp[10]);
       glNormal3fv(scp[10]); glTexCoord2fv(tscp[11]); glVertex3fv(scp[11]);
       glNormal3fv(scp[12]); glTexCoord2fv(tscp[12]); glVertex3fv(scp[12]);
       glNormal3fv(scp[12]); glTexCoord2fv(tscp[13]); glVertex3fv(scp[13]);
       glNormal3fv(scp[14]); glTexCoord2fv(tscp[14]); glVertex3fv(scp[14]);
       glNormal3fv(scp[14]); glTexCoord2fv(tscp[15]); glVertex3fv(scp[15]);
       glNormal3fv(scp[16]); glTexCoord2fv(tscp[16]); glVertex3fv(scp[16]);
       glNormal3fv(scp[16]); glTexCoord2fv(tscp[17]); glVertex3fv(scp[17]);
    glEnd();

    glEndList();
}

static void BuildDoubleCylinder(void)
{

    glNewList(doubleCylinder, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(dcp[0]); glTexCoord2fv(tscp[0]); glVertex3fv(dcp[0]);
    glNormal3fv(dcp[0]); glTexCoord2fv(tscp[1]); glVertex3fv(dcp[1]);
    glNormal3fv(dcp[2]); glTexCoord2fv(tscp[2]); glVertex3fv(dcp[2]);
    glNormal3fv(dcp[2]); glTexCoord2fv(tscp[3]); glVertex3fv(dcp[3]);
    glNormal3fv(dcp[4]); glTexCoord2fv(tscp[4]); glVertex3fv(dcp[4]);
    glNormal3fv(dcp[4]); glTexCoord2fv(tscp[5]); glVertex3fv(dcp[5]);
    glNormal3fv(dcp[6]); glTexCoord2fv(tscp[6]); glVertex3fv(dcp[6]);
    glNormal3fv(dcp[6]); glTexCoord2fv(tscp[7]); glVertex3fv(dcp[7]);
    glNormal3fv(dcp[8]); glTexCoord2fv(tscp[8]); glVertex3fv(dcp[8]);
    glNormal3fv(dcp[8]); glTexCoord2fv(tscp[9]); glVertex3fv(dcp[9]);
    glNormal3fv(dcp[10]); glTexCoord2fv(tscp[10]); glVertex3fv(dcp[10]);
    glNormal3fv(dcp[10]); glTexCoord2fv(tscp[11]); glVertex3fv(dcp[11]);
    glNormal3fv(dcp[12]); glTexCoord2fv(tscp[12]); glVertex3fv(dcp[12]);
    glNormal3fv(dcp[12]); glTexCoord2fv(tscp[13]); glVertex3fv(dcp[13]);
    glNormal3fv(dcp[14]); glTexCoord2fv(tscp[14]); glVertex3fv(dcp[14]);
    glNormal3fv(dcp[14]); glTexCoord2fv(tscp[15]); glVertex3fv(dcp[15]);
    glNormal3fv(dcp[16]); glTexCoord2fv(tscp[16]); glVertex3fv(dcp[16]);
    glNormal3fv(dcp[16]); glTexCoord2fv(tscp[17]); glVertex3fv(dcp[17]);
    glEnd();

    glEndList();
}

static void BuildElbow(void)
{

    glNewList(elbow, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[0][0]); glTexCoord2fv(tep[0][0]); glVertex3fv(ep[0][0]);
    glNormal3fv(en[1][0]); glTexCoord2fv(tep[1][0]); glVertex3fv(ep[1][0]);
    glNormal3fv(en[0][1]); glTexCoord2fv(tep[0][1]); glVertex3fv(ep[0][1]);
    glNormal3fv(en[1][1]); glTexCoord2fv(tep[1][1]); glVertex3fv(ep[1][1]);
    glNormal3fv(en[0][2]); glTexCoord2fv(tep[0][2]); glVertex3fv(ep[0][2]);
    glNormal3fv(en[1][2]); glTexCoord2fv(tep[1][2]); glVertex3fv(ep[1][2]);
    glNormal3fv(en[0][3]); glTexCoord2fv(tep[0][3]); glVertex3fv(ep[0][3]);
    glNormal3fv(en[1][3]); glTexCoord2fv(tep[1][3]); glVertex3fv(ep[1][3]);
    glNormal3fv(en[0][4]); glTexCoord2fv(tep[0][4]); glVertex3fv(ep[0][4]);
    glNormal3fv(en[1][4]); glTexCoord2fv(tep[1][4]); glVertex3fv(ep[1][4]);
    glNormal3fv(en[0][5]); glTexCoord2fv(tep[0][5]); glVertex3fv(ep[0][5]);
    glNormal3fv(en[1][5]); glTexCoord2fv(tep[1][5]); glVertex3fv(ep[1][5]);
    glNormal3fv(en[0][6]); glTexCoord2fv(tep[0][6]); glVertex3fv(ep[0][6]);
    glNormal3fv(en[1][6]); glTexCoord2fv(tep[1][6]); glVertex3fv(ep[1][6]);
    glNormal3fv(en[0][7]); glTexCoord2fv(tep[0][7]); glVertex3fv(ep[0][7]);
    glNormal3fv(en[1][7]); glTexCoord2fv(tep[1][7]); glVertex3fv(ep[1][7]);
    glNormal3fv(en[0][8]); glTexCoord2fv(tep[0][8]); glVertex3fv(ep[0][8]);
    glNormal3fv(en[1][8]); glTexCoord2fv(tep[1][8]); glVertex3fv(ep[1][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[1][0]); glTexCoord2fv(tep[1][0]); glVertex3fv(ep[1][0]);
    glNormal3fv(en[2][0]); glTexCoord2fv(tep[2][0]); glVertex3fv(ep[2][0]);
    glNormal3fv(en[1][1]); glTexCoord2fv(tep[1][1]); glVertex3fv(ep[1][1]);
    glNormal3fv(en[2][1]); glTexCoord2fv(tep[2][1]); glVertex3fv(ep[2][1]);
    glNormal3fv(en[1][2]); glTexCoord2fv(tep[1][2]); glVertex3fv(ep[1][2]);
    glNormal3fv(en[2][2]); glTexCoord2fv(tep[2][2]); glVertex3fv(ep[2][2]);
    glNormal3fv(en[1][3]); glTexCoord2fv(tep[1][3]); glVertex3fv(ep[1][3]);
    glNormal3fv(en[2][3]); glTexCoord2fv(tep[2][3]); glVertex3fv(ep[2][3]);
    glNormal3fv(en[1][4]); glTexCoord2fv(tep[1][4]); glVertex3fv(ep[1][4]);
    glNormal3fv(en[2][4]); glTexCoord2fv(tep[2][4]); glVertex3fv(ep[2][4]);
    glNormal3fv(en[1][5]); glTexCoord2fv(tep[1][5]); glVertex3fv(ep[1][5]);
    glNormal3fv(en[2][5]); glTexCoord2fv(tep[2][5]); glVertex3fv(ep[2][5]);
    glNormal3fv(en[1][6]); glTexCoord2fv(tep[1][6]); glVertex3fv(ep[1][6]);
    glNormal3fv(en[2][6]); glTexCoord2fv(tep[2][6]); glVertex3fv(ep[2][6]);
    glNormal3fv(en[1][7]); glTexCoord2fv(tep[1][7]); glVertex3fv(ep[1][7]);
    glNormal3fv(en[2][7]); glTexCoord2fv(tep[2][7]); glVertex3fv(ep[2][7]);
    glNormal3fv(en[1][8]); glTexCoord2fv(tep[1][8]); glVertex3fv(ep[1][8]);
    glNormal3fv(en[2][8]); glTexCoord2fv(tep[2][8]); glVertex3fv(ep[2][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[2][0]); glTexCoord2fv(tep[2][0]); glVertex3fv(ep[2][0]);
    glNormal3fv(en[3][0]); glTexCoord2fv(tep[3][0]); glVertex3fv(ep[3][0]);
    glNormal3fv(en[2][1]); glTexCoord2fv(tep[2][1]); glVertex3fv(ep[2][1]);
    glNormal3fv(en[3][1]); glTexCoord2fv(tep[3][1]); glVertex3fv(ep[3][1]);
    glNormal3fv(en[2][2]); glTexCoord2fv(tep[2][2]); glVertex3fv(ep[2][2]);
    glNormal3fv(en[3][2]); glTexCoord2fv(tep[3][2]); glVertex3fv(ep[3][2]);
    glNormal3fv(en[2][3]); glTexCoord2fv(tep[2][3]); glVertex3fv(ep[2][3]);
    glNormal3fv(en[3][3]); glTexCoord2fv(tep[3][3]); glVertex3fv(ep[3][3]);
    glNormal3fv(en[2][4]); glTexCoord2fv(tep[2][4]); glVertex3fv(ep[2][4]);
    glNormal3fv(en[3][4]); glTexCoord2fv(tep[3][4]); glVertex3fv(ep[3][4]);
    glNormal3fv(en[2][5]); glTexCoord2fv(tep[2][5]); glVertex3fv(ep[2][5]);
    glNormal3fv(en[3][5]); glTexCoord2fv(tep[3][5]); glVertex3fv(ep[3][5]);
    glNormal3fv(en[2][6]); glTexCoord2fv(tep[2][6]); glVertex3fv(ep[2][6]);
    glNormal3fv(en[3][6]); glTexCoord2fv(tep[3][6]); glVertex3fv(ep[3][6]);
    glNormal3fv(en[2][7]); glTexCoord2fv(tep[2][7]); glVertex3fv(ep[2][7]);
    glNormal3fv(en[3][7]); glTexCoord2fv(tep[3][7]); glVertex3fv(ep[3][7]);
    glNormal3fv(en[2][8]); glTexCoord2fv(tep[2][8]); glVertex3fv(ep[2][8]);
    glNormal3fv(en[3][8]); glTexCoord2fv(tep[3][8]); glVertex3fv(ep[3][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[3][0]); glTexCoord2fv(tep[3][0]); glVertex3fv(ep[3][0]);
    glNormal3fv(en[4][0]); glTexCoord2fv(tep[4][0]); glVertex3fv(ep[4][0]);
    glNormal3fv(en[3][1]); glTexCoord2fv(tep[3][1]); glVertex3fv(ep[3][1]);
    glNormal3fv(en[4][1]); glTexCoord2fv(tep[4][1]); glVertex3fv(ep[4][1]);
    glNormal3fv(en[3][2]); glTexCoord2fv(tep[3][2]); glVertex3fv(ep[3][2]);
    glNormal3fv(en[4][2]); glTexCoord2fv(tep[4][2]); glVertex3fv(ep[4][2]);
    glNormal3fv(en[3][3]); glTexCoord2fv(tep[3][3]); glVertex3fv(ep[3][3]);
    glNormal3fv(en[4][3]); glTexCoord2fv(tep[4][3]); glVertex3fv(ep[4][3]);
    glNormal3fv(en[3][4]); glTexCoord2fv(tep[3][4]); glVertex3fv(ep[3][4]);
    glNormal3fv(en[4][4]); glTexCoord2fv(tep[4][4]); glVertex3fv(ep[4][4]);
    glNormal3fv(en[3][5]); glTexCoord2fv(tep[3][5]); glVertex3fv(ep[3][5]);
    glNormal3fv(en[4][5]); glTexCoord2fv(tep[4][5]); glVertex3fv(ep[4][5]);
    glNormal3fv(en[3][6]); glTexCoord2fv(tep[3][6]); glVertex3fv(ep[3][6]);
    glNormal3fv(en[4][6]); glTexCoord2fv(tep[4][6]); glVertex3fv(ep[4][6]);
    glNormal3fv(en[3][7]); glTexCoord2fv(tep[3][7]); glVertex3fv(ep[3][7]);
    glNormal3fv(en[4][7]); glTexCoord2fv(tep[4][7]); glVertex3fv(ep[4][7]);
    glNormal3fv(en[3][8]); glTexCoord2fv(tep[3][8]); glVertex3fv(ep[3][8]);
    glNormal3fv(en[4][8]); glTexCoord2fv(tep[4][8]); glVertex3fv(ep[4][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[4][0]); glTexCoord2fv(tep[4][0]); glVertex3fv(ep[4][0]);
    glNormal3fv(en[5][0]); glTexCoord2fv(tep[5][0]); glVertex3fv(ep[5][0]);
    glNormal3fv(en[4][1]); glTexCoord2fv(tep[4][1]); glVertex3fv(ep[4][1]);
    glNormal3fv(en[5][1]); glTexCoord2fv(tep[5][1]); glVertex3fv(ep[5][1]);
    glNormal3fv(en[4][2]); glTexCoord2fv(tep[4][2]); glVertex3fv(ep[4][2]);
    glNormal3fv(en[5][2]); glTexCoord2fv(tep[5][2]); glVertex3fv(ep[5][2]);
    glNormal3fv(en[4][3]); glTexCoord2fv(tep[4][3]); glVertex3fv(ep[4][3]);
    glNormal3fv(en[5][3]); glTexCoord2fv(tep[5][3]); glVertex3fv(ep[5][3]);

glNormal3fv(en[4][4]); glTexCoord2fv(tep[4][4]); glVertex3fv(ep[4][4]);
    glNormal3fv(en[5][4]); glTexCoord2fv(tep[5][4]); glVertex3fv(ep[5][4]);
    glNormal3fv(en[4][5]); glTexCoord2fv(tep[4][5]); glVertex3fv(ep[4][5]);
    glNormal3fv(en[5][5]); glTexCoord2fv(tep[5][5]); glVertex3fv(ep[5][5]);
    glNormal3fv(en[4][6]); glTexCoord2fv(tep[4][6]); glVertex3fv(ep[4][6]);
    glNormal3fv(en[5][6]); glTexCoord2fv(tep[5][6]); glVertex3fv(ep[5][6]);
    glNormal3fv(en[4][7]); glTexCoord2fv(tep[4][7]); glVertex3fv(ep[4][7]);
    glNormal3fv(en[5][7]); glTexCoord2fv(tep[5][7]); glVertex3fv(ep[5][7]);
    glNormal3fv(en[4][8]); glTexCoord2fv(tep[4][8]); glVertex3fv(ep[4][8]);
    glNormal3fv(en[5][8]); glTexCoord2fv(tep[5][8]); glVertex3fv(ep[5][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[5][0]); glTexCoord2fv(tep[5][0]); glVertex3fv(ep[5][0]);
    glNormal3fv(en[6][0]); glTexCoord2fv(tep[6][0]); glVertex3fv(ep[6][0]);
    glNormal3fv(en[5][1]); glTexCoord2fv(tep[5][1]); glVertex3fv(ep[5][1]);
    glNormal3fv(en[6][1]); glTexCoord2fv(tep[6][1]); glVertex3fv(ep[6][1]);
    glNormal3fv(en[5][2]); glTexCoord2fv(tep[5][2]); glVertex3fv(ep[5][2]);
    glNormal3fv(en[6][2]); glTexCoord2fv(tep[6][2]); glVertex3fv(ep[6][2]);
    glNormal3fv(en[5][3]); glTexCoord2fv(tep[5][3]); glVertex3fv(ep[5][3]);
    glNormal3fv(en[6][3]); glTexCoord2fv(tep[6][3]); glVertex3fv(ep[6][3]);
    glNormal3fv(en[5][4]); glTexCoord2fv(tep[5][4]); glVertex3fv(ep[5][4]);
    glNormal3fv(en[6][4]); glTexCoord2fv(tep[6][4]); glVertex3fv(ep[6][4]);
    glNormal3fv(en[5][5]); glTexCoord2fv(tep[5][5]); glVertex3fv(ep[5][5]);
    glNormal3fv(en[6][5]); glTexCoord2fv(tep[6][5]); glVertex3fv(ep[6][5]);
    glNormal3fv(en[5][6]); glTexCoord2fv(tep[5][6]); glVertex3fv(ep[5][6]);
    glNormal3fv(en[6][6]); glTexCoord2fv(tep[6][6]); glVertex3fv(ep[6][6]);
    glNormal3fv(en[5][7]); glTexCoord2fv(tep[5][7]); glVertex3fv(ep[5][7]);
    glNormal3fv(en[6][7]); glTexCoord2fv(tep[6][7]); glVertex3fv(ep[6][7]);
    glNormal3fv(en[5][8]); glTexCoord2fv(tep[5][8]); glVertex3fv(ep[5][8]);
    glNormal3fv(en[6][8]); glTexCoord2fv(tep[6][8]); glVertex3fv(ep[6][8]);
    glEnd();

    glEndList();
}

static void BuildLogo(void)
{

    glNewList(logo, GL_COMPILE);

    glTranslatef(5.5, -3.5, 4.5);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);

    glEndList();
}

static void BuildLists(void)
{

    singleCylinder = glGenLists(1);
    doubleCylinder = glGenLists(1);
    elbow = glGenLists(1);
    logo = glGenLists(1);

    BuildSingleCylinder();
    BuildDoubleCylinder();
    BuildElbow();
    BuildLogo();
}

static void Init(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glFrontFace(GL_CW);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);

    glEnable(GL_CLIP_PLANE0);

    if (rgb) {
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, CHECKIMAGEWIDTH, CHECKIMAGEHEIGHT, 0,
             GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)checkImage);
    glEnable(GL_TEXTURE_2D);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);
    } else {
    auxSetGreyRamp();
    /*
    if (doubleBuffer) {
        colorIndexes[1] = 10;
        colorIndexes[2] = 15;
    } */

    glMaterialiv(GL_FRONT_AND_BACK, GL_COLOR_INDEXES, colorIndexes);
    }

    BuildLists();

    dithering = GL_TRUE;
    shade = GL_TRUE;
    doStipple = GL_FALSE;
    polyMode = GL_BACK;
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90, 1.0, 1.0, 200.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_LEFT(void)
{
    yRotation += 0.5;
}

static void CALLBACK Key_RIGHT(void)
{
    yRotation -= 0.5;
}

static void CALLBACK Key_UP(void)
{
    plane[3] += 2.0;
}

static void CALLBACK Key_DOWN(void)
{
    plane[3] -= 2.0;
}

static void CALLBACK Key_Z(void)
{
    zTranslation -= 1.0;
}

static void CALLBACK Key_z(void)
{
    zTranslation += 1.0;
}

static void CALLBACK Key_1(void)
{
    glPolygonMode(polyMode, GL_POINT);
}

static void CALLBACK Key_2(void)
{
    glPolygonMode(polyMode, GL_LINE);
}

static void CALLBACK Key_3(void)
{
    glPolygonMode(polyMode, GL_FILL);
}

static void CALLBACK Key_p(void)
{
    switch (polyMode) {
      case GL_BACK:
        polyMode = GL_FRONT;
        break;
      case GL_FRONT:
        polyMode = GL_FRONT_AND_BACK;
        break;
      case GL_FRONT_AND_BACK:
        polyMode = GL_BACK;
        break;
    }
}

static void CALLBACK Key_4(void)
{
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
}

static void CALLBACK Key_5(void)
{
    glEnable(GL_POLYGON_SMOOTH);
    if (rgb) {
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        glEnable(GL_BLEND);
        glDisable(GL_DEPTH_TEST);
    } else {
        SetUpAntiAliasedGrayScale();
    }
}

static void CALLBACK Key_6(void)
{
    glDisable(GL_POLYGON_SMOOTH);
    if (rgb) {
        glBlendFunc(GL_ONE, GL_ZERO);
        glDisable(GL_BLEND);
        glEnable(GL_DEPTH_TEST);
    } else {
        auxSetGreyRamp();
    }
}

static void CALLBACK Key_8(void)
{
    dithering = !dithering;
    (dithering) ? glEnable(GL_DITHER) : glDisable(GL_DITHER);
}

static void CALLBACK Key_9(void)
{
    doStipple = !doStipple;
    if (doStipple) {
        glPolygonStipple(stipple);
        glEnable(GL_POLYGON_STIPPLE);
    } else {
        glDisable(GL_POLYGON_STIPPLE);
    }
}

static void CALLBACK Key_0(void)
{
    shade = !shade;
    (shade) ? glShadeModel(GL_SMOOTH) : glShadeModel(GL_FLAT);
}

static void CALLBACK Key_q(void)
{
    glDisable(GL_CULL_FACE);
}

static void CALLBACK Key_w(void)
{
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
}

static void CALLBACK Key_e(void)
{
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
}

static void CALLBACK Key_r(void)
{
    glFrontFace(GL_CW);
}

static void CALLBACK Key_t(void)
{
    glFrontFace(GL_CCW);
}

static void CALLBACK Key_y(void)
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_LSB_FIRST, 0);
    glPolygonStipple(stipple);
}

static void CALLBACK Key_u(void)
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_LSB_FIRST, 1);
    glPolygonStipple(stipple);
}

static void CALLBACK Key_a(void)
{
    glEnable(GL_TEXTURE_2D);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, BRICKIMAGEWIDTH,
             BRICKIMAGEHEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,
             (GLvoid *)brickImage);
}

static void CALLBACK Key_s(void)
{
    glEnable(GL_TEXTURE_2D);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, CHECKIMAGEWIDTH,
             CHECKIMAGEHEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE,
             (GLvoid *)checkImage);
}

static void CALLBACK Key_d(void)
{
    glDisable(GL_TEXTURE_2D);
}

static void CALLBACK Key_f(void)
{
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
}

static void CALLBACK Key_g(void)
{
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, modulate);
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(0, 0, zTranslation);
    glRotatef(30.0, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);
    glClipPlane(GL_CLIP_PLANE0, plane);
    glCallList(logo);

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;
    directRender = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-dr") == 0) {
        directRender = GL_TRUE;
    } else if (strcmp(argv[i], "-ir") == 0) {
        directRender = GL_FALSE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;
    auxInitDisplayMode(type);

    if (auxInitWindow("Logo Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_Z, Key_Z);
    auxKeyFunc(AUX_z, Key_z);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxKeyFunc(AUX_p, Key_p);
    auxKeyFunc(AUX_4, Key_4);
    auxKeyFunc(AUX_5, Key_5);
    auxKeyFunc(AUX_6, Key_6);
    auxKeyFunc(AUX_8, Key_8);
    auxKeyFunc(AUX_9, Key_9);
    auxKeyFunc(AUX_0, Key_0);
    auxKeyFunc(AUX_q, Key_q);
    auxKeyFunc(AUX_w, Key_w);
    auxKeyFunc(AUX_e, Key_e);
    auxKeyFunc(AUX_r, Key_r);
    auxKeyFunc(AUX_t, Key_t);
    auxKeyFunc(AUX_y, Key_y);
    auxKeyFunc(AUX_u, Key_u);
    auxKeyFunc(AUX_a, Key_a);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_g, Key_g);
    auxMainLoop(Draw);
}


TNURB.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <gl\glaux.h>

#define INREAL float

#define S_NUMPOINTS 13
#define S_ORDER     3
#define S_NUMKNOTS  (S_NUMPOINTS + S_ORDER)
#define T_NUMPOINTS 3
#define T_ORDER     3
#define T_NUMKNOTS  (T_NUMPOINTS + T_ORDER)
#define SQRT_TWO    1.41421356237309504880

typedef INREAL Point[4];

static void CALLBACK ErrorCallback(GLenum which);
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;

GLenum expectedError;
GLint rotX = 40, rotY = 40;
INREAL sknots[S_NUMKNOTS] = {
    -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0,
    4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 9.0, 9.0
};
INREAL tknots[T_NUMKNOTS] = {
    1.0, 1.0, 1.0, 2.0, 2.0, 2.0
};
Point ctlpoints[S_NUMPOINTS][T_NUMPOINTS] = {
    {
    {
        4.0, 2.0, 2.0, 1.0
    },
    {
        4.0, 1.6, 2.5, 1.0
    },
    {
        4.0, 2.0, 3.0, 1.0
    }
    },
    {
    {
        5.0, 4.0, 2.0, 1.0
    },
    {
        5.0, 4.0, 2.5, 1.0
    },
    {
        5.0, 4.0, 3.0, 1.0
    }
    },
    {
    {
        6.0, 5.0, 2.0, 1.0
    },
    {
        6.0, 5.0, 2.5, 1.0
    },
    {
        6.0, 5.0, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        5.2, 6.7, 2.0, 1.0
    },
    {
        5.2, 6.7, 2.5, 1.0
    },
    {
        5.2, 6.7, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        4.0, 5.2, 2.0, 1.0
    },
    {
        4.0, 4.6, 2.5, 1.0
    },
    {
        4.0, 5.2, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        2.8, 6.7, 2.0, 1.0
    },
    {
        2.8, 6.7, 2.5, 1.0
    },
    {
        2.8, 6.7, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        2.0, 5.0, 2.0, 1.0
    },
    {
        2.0, 5.0, 2.5, 1.0
    },
    {
        2.0, 5.0, 3.0, 1.0
    }
    },
    {
    {
        3.0, 4.0, 2.0, 1.0
    },
    {
        3.0, 4.0, 2.5, 1.0
    },
    {
        3.0, 4.0, 3.0, 1.0
    }
    },
    {
    {
        4.0, 2.0, 2.0, 1.0
    },
    {
        4.0, 1.6, 2.5, 1.0
    },
    {
        4.0, 2.0, 3.0, 1.0
    }
    }
};
GLUnurbsObj *theNurbs;

static void CALLBACK ErrorCallback(GLenum which)
{

    if (which != expectedError) {
    fprintf(stderr, "Unexpected error occured (%d):\n", which);
    fprintf(stderr, "    %s\n", gluErrorString(which));
    }
}

static void Init(void)
{

    theNurbs = gluNewNurbsRenderer();
    //gluNurbsCallback(theNurbs, GLU_ERROR, ErrorCallback);

    gluNurbsProperty(theNurbs, GLU_SAMPLING_TOLERANCE, 15.0);
    gluNurbsProperty(theNurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_PATCH);

    expectedError = GLU_INVALID_ENUM;
    gluNurbsProperty(theNurbs, (GLenum)~0, 15.0);
    expectedError = GLU_NURBS_ERROR13;
    gluEndSurface(theNurbs);
    expectedError = 0;

    glColor3f(1.0, 1.0, 1.0);
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-2.0, 2.0, -2.0, 2.0, 0.8, 10.0);
    gluLookAt(7.0, 4.5, 4.0, 4.5, 4.5, 2.5, 6.0, -3.0, 2.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_DOWN(void)
{
    rotX -= 5;
}

static void CALLBACK Key_UP(void)
{
    rotX += 5;
}

static void CALLBACK Key_LEFT(void)
{
    rotY -= 5;
}

static void CALLBACK Key_RIGHT(void)
{
    rotY += 5;
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(4.0, 4.5, 2.5);
    glRotatef(rotY, 1, 0, 0);
    glRotatef(rotX, 0, 1, 0);
    glTranslatef(-4.0, -4.5, -2.5);

    gluBeginSurface(theNurbs);
    gluNurbsSurface(theNurbs, S_NUMKNOTS, sknots, T_NUMKNOTS, tknots,
            4*T_NUMPOINTS, 4, &ctlpoints[0][0][0], S_ORDER,
            T_ORDER, GL_MAP2_VERTEX_4);
    gluEndSurface(theNurbs);

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("NURBS Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxMainLoop(Draw);
}


TPOINT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define CI_RED AUX_RED
#define CI_ANTI_ALIAS_GREEN 16
#define CI_ANTI_ALIAS_YELLOW 32
#define CI_ANTI_ALIAS_RED 48

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_1(void );
static void CALLBACK Key_W(void );
static void CALLBACK Key_w(void );
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;
GLint windW, windH;

GLenum mode;
GLint size;
float point[3] = {
    1.0, 1.0, 0.0
};

static void Init(void)
{
    GLint i;

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glBlendFunc(GL_SRC_ALPHA, GL_ZERO);

    if (!rgb) {
    for (i = 0; i < 16; i++) {
        auxSetOneColor(i+CI_ANTI_ALIAS_RED, i/15.0, 0.0, 0.0);
        auxSetOneColor(i+CI_ANTI_ALIAS_YELLOW, i/15.0, i/15.0, 0.0);
        auxSetOneColor(i+CI_ANTI_ALIAS_GREEN, 0.0, i/15.0, 0.0);
    }
    }

    mode = GL_FALSE;
    size = 1;
}

static void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-windW/2, windW/2, -windH/2, windH/2);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_1(void)
{
    mode = !mode;
}

static void CALLBACK Key_W(void)
{
    size++;
}

static void CALLBACK Key_w(void)
{
    size--;
    if (size < 1) {
        size = 1;
    }
}

static void CALLBACK Key_LEFT(void)
{
    point[0] -= 0.25;
}

static void CALLBACK Key_RIGHT(void)
{
    point[0] += 0.25;
}

static void CALLBACK Key_UP(void)
{
    point[1] += 0.25;
}

static void CALLBACK Key_DOWN(void)
{
    point[1] -= 0.25;
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    AUX_SETCOLOR(windType, AUX_YELLOW);
    glBegin(GL_LINE_STRIP);
    glVertex2f(-windW/2, 0);
    glVertex2f(windW/2, 0);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glVertex2f(0, -windH/2);
    glVertex2f(0, windH/2);
    glEnd();

    if (mode) {
    glEnable(GL_BLEND);
    glEnable(GL_POINT_SMOOTH);
    } else {
    glDisable(GL_BLEND);
    glDisable(GL_POINT_SMOOTH);
    }

    glPointSize(size);
    if (mode) {
    (rgb) ? glColor3f(1.0, 0.0, 0.0) : glIndexf(CI_ANTI_ALIAS_RED);
    } else {
    (rgb) ? glColor3f(1.0, 0.0, 0.0) : glIndexf(CI_RED);
    }
    glBegin(GL_POINTS);
    glVertex3fv(point);
    glEnd();

    glDisable(GL_POINT_SMOOTH);

    glPointSize(1);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glBegin(GL_POINTS);
    glVertex3fv(point);
    glEnd();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    windW = 300;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    windType |= (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Point Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_W, Key_W);
    auxKeyFunc(AUX_w, Key_w);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxMainLoop(Draw);
}


TPRIM.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define PIXEL_CENTER(x) ((long)(x) + 0.5)

#define GAP 10
#define ROWS 3
#define COLS 4

#define OPENGL_WIDTH 48
#define OPENGL_HEIGHT 13

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void RotateColorMask(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void Viewport(GLint row, GLint column);
static void Point(void );
static void Lines(void );
static void LineStrip(void );
static void LineLoop(void );
static void Bitmap(void );
static void Triangles(void );
static void TriangleStrip(void );
static void TriangleFan(void );
static void Rect(void );
static void xPolygon(void );
static void Quads(void );
static void QuadStrip(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;
GLint windW, windH;

GLenum mode1, mode2;
GLint boxW, boxH;
GLubyte OpenGL_bits[] = {
   0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
   0x7f, 0xfb, 0xff, 0xff, 0xff, 0x01,
   0x7f, 0xfb, 0xff, 0xff, 0xff, 0x01,
   0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
   0x3e, 0x8f, 0xb7, 0xf9, 0xfc, 0x01,
   0x63, 0xdb, 0xb0, 0x8d, 0x0d, 0x00,
   0x63, 0xdb, 0xb7, 0x8d, 0x0d, 0x00,
   0x63, 0xdb, 0xb6, 0x8d, 0x0d, 0x00,
   0x63, 0x8f, 0xf3, 0xcc, 0x0d, 0x00,
   0x63, 0x00, 0x00, 0x0c, 0x4c, 0x0a,
   0x63, 0x00, 0x00, 0x0c, 0x4c, 0x0e,
   0x63, 0x00, 0x00, 0x8c, 0xed, 0x0e,
   0x3e, 0x00, 0x00, 0xf8, 0x0c, 0x00,
};

static void Init(void)
{

    mode1 = GL_TRUE;
    mode2 = GL_TRUE;
}

static void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;
}

static void RotateColorMask(void)
{
    static GLint rotation = 0;

    rotation = (rotation + 1) & 0x3;
    switch (rotation) {
      case 0:
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glIndexMask( 0xff );
    break;
      case 1:
    glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
    glIndexMask(0xFE);
    break;
      case 2:
    glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE);
    glIndexMask(0xFD);
    break;
      case 3:
    glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
    glIndexMask(0xFB);
    break;
    }
}

static void CALLBACK Key_1(void)
{
    mode1 = !mode1;
}

static void CALLBACK Key_2(void)
{
    mode2 = !mode2;
}

static void CALLBACK Key_3(void)
{
    RotateColorMask();
}

static void Viewport(GLint row, GLint column)
{
    GLint x, y;

    boxW = (windW - (COLS + 1) * GAP) / COLS;
    boxH = (windH - (ROWS + 1) * GAP) / ROWS;

    x = GAP + column * (boxW + GAP);
    y = GAP + row * (boxH + GAP);

    glViewport(x, y, boxW, boxH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-boxW/2, boxW/2, -boxH/2, boxH/2, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_SCISSOR_TEST);
    glScissor(x, y, boxW, boxH);
}

static void Point(void)
{
    GLint i;

    glBegin(GL_POINTS);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(0, 0);
    for (i = 1; i < 8; i++) {
        GLint j = i * 2;
        AUX_SETCOLOR(windType, i + AUX_RED - 1);
        glVertex2i(-j, -j);
        glVertex2i(-j, 0);
        glVertex2i(-j, j);
        glVertex2i(0, j);
        glVertex2i(j, j);
        glVertex2i(j, 0);
        glVertex2i(j, -j);
        glVertex2i(0, -j);
    }
    glEnd();
}

static void Lines(void)
{
    GLint i;

    glPushMatrix();

    glTranslatef(-12, 0, 0);
    for (i = 1; i < 8; i++) {
    AUX_SETCOLOR(windType, i + AUX_RED - 1);
    glBegin(GL_LINES);
        glVertex2i(-boxW/4, -boxH/4);
        glVertex2i(boxW/4, boxH/4);
    glEnd();
    glTranslatef(4, 0, 0);
    }

    glPopMatrix();

    glBegin(GL_LINES);
    glVertex2i(0, 0);
    glEnd();
}

static void LineStrip(void)
{

    glBegin(GL_LINE_STRIP);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(-boxH/4));
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(boxH/4));
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(boxH/4));
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(-boxH/4));
    glEnd();

    glBegin(GL_LINE_STRIP);
    glVertex2i(0, 0);
    glEnd();
}

static void LineLoop(void)
{

    glBegin(GL_LINE_LOOP);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(-boxH/4));
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(boxH/4));
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(boxH/4));
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(-boxH/4));
    glEnd();

    glEnable(GL_LOGIC_OP);
    glLogicOp(GL_XOR);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    AUX_SETCOLOR(windType, AUX_MAGENTA);
    glBegin(GL_LINE_LOOP);
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(-boxH/8));
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(boxH/8));
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(boxH/8+5));
    glVertex2f(PIXEL_CENTER(boxW/8), PIXEL_CENTER(boxH/8+5));
    glEnd();
    glDisable(GL_LOGIC_OP);
    glDisable(GL_BLEND);

    AUX_SETCOLOR(windType, AUX_GREEN);
    glBegin(GL_POINTS);
    glVertex2i(0, 0);
    glEnd();

    glBegin(GL_LINE_LOOP);
    glVertex2i(0, 0);
    glEnd();
}

static void Bitmap(void)
{

    glBegin(GL_LINES);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/2, 0);
    glVertex2i(boxW/2, 0);
    glVertex2i(0, -boxH/2);
    glVertex2i(0, boxH/2);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(0, -3);
    glVertex2i(0, -3+OPENGL_HEIGHT);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(0, -3);
    glVertex2i(OPENGL_WIDTH, -3);
    glEnd();

    AUX_SETCOLOR(windType, AUX_GREEN);

    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glRasterPos2i(0, 0);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, 0, 3, 0.0, 0.0, OpenGL_bits);
}

static void Triangles(void)
{

    glBegin(GL_TRIANGLES);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/8, -boxH/16);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(boxW/8, -boxH/16);

    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/8, boxH/16);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(boxW/8, boxH/16);
    glEnd();

    glBegin(GL_TRIANGLES);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void TriangleStrip(void)
{

    glBegin(GL_TRIANGLE_STRIP);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/4, boxH/4);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(0, -boxH/4);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(0, boxH/4);
    AUX_SETCOLOR(windType, AUX_CYAN);
    glVertex2i(boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_YELLOW);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    glBegin(GL_TRIANGLE_STRIP);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void TriangleFan(void)
{
    GLint vx[8][2];
    GLint x0, y0, x1, y1, x2, y2, x3, y3;
    GLint i;

    y0 = -boxH/4;
    y1 = y0 + boxH/2/3;
    y2 = y1 + boxH/2/3;
    y3 = boxH/4;
    x0 = -boxW/4;
    x1 = x0 + boxW/2/3;
    x2 = x1 + boxW/2/3;
    x3 = boxW/4;

    vx[0][0] = x0; vx[0][1] = y1;
    vx[1][0] = x0; vx[1][1] = y2;
    vx[2][0] = x1; vx[2][1] = y3;
    vx[3][0] = x2; vx[3][1] = y3;
    vx[4][0] = x3; vx[4][1] = y2;
    vx[5][0] = x3; vx[5][1] = y1;
    vx[6][0] = x2; vx[6][1] = y0;
    vx[7][0] = x1; vx[7][1] = y0;

    glBegin(GL_TRIANGLE_FAN);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(0, 0);
    for (i = 0; i < 8; i++) {
        AUX_SETCOLOR(windType, AUX_WHITE-i);
        glVertex2iv(vx[i]);
    }
    glEnd();

    glBegin(GL_TRIANGLE_FAN);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void Rect(void)
{

    AUX_SETCOLOR(windType, AUX_GREEN);
    glRecti(-boxW/4, -boxH/4, boxW/4, boxH/4);
}

static void xPolygon(void)
{
    GLint vx[8][2];
    GLint x0, y0, x1, y1, x2, y2, x3, y3;
    GLint i;

    y0 = -boxH/4;
    y1 = y0 + boxH/2/3;
    y2 = y1 + boxH/2/3;
    y3 = boxH/4;
    x0 = -boxW/4;
    x1 = x0 + boxW/2/3;
    x2 = x1 + boxW/2/3;
    x3 = boxW/4;

    vx[0][0] = x0; vx[0][1] = y1;
    vx[1][0] = x0; vx[1][1] = y2;
    vx[2][0] = x1; vx[2][1] = y3;
    vx[3][0] = x2; vx[3][1] = y3;
    vx[4][0] = x3; vx[4][1] = y2;
    vx[5][0] = x3; vx[5][1] = y1;
    vx[6][0] = x2; vx[6][1] = y0;
    vx[7][0] = x1; vx[7][1] = y0;

    glBegin(GL_POLYGON);
    for (i = 0; i < 8; i++) {
        AUX_SETCOLOR(windType, AUX_WHITE-i);
        glVertex2iv(vx[i]);
    }
    glEnd();

    glBegin(GL_POLYGON);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glEnd();
}

static void Quads(void)
{

    glBegin(GL_QUADS);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/8, -boxH/16);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(boxW/8, -boxH/16);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(boxW/4, -boxH/4);

    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/8, boxH/16);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(boxW/8, boxH/16);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    glBegin(GL_QUADS);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glVertex2i(-100, 100);
    glEnd();
}

static void QuadStrip(void)
{

    glBegin(GL_QUAD_STRIP);
    AUX_SETCOLOR(windType, AUX_GREEN);
    glVertex2i(-boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_RED);
    glVertex2i(-boxW/4, boxH/4);
    AUX_SETCOLOR(windType, AUX_BLUE);
    glVertex2i(0, -boxH/4);
    AUX_SETCOLOR(windType, AUX_WHITE);
    glVertex2i(0, boxH/4);
    AUX_SETCOLOR(windType, AUX_CYAN);
    glVertex2i(boxW/4, -boxH/4);
    AUX_SETCOLOR(windType, AUX_YELLOW);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    glBegin(GL_QUAD_STRIP);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glVertex2i(-100, 100);
    glEnd();
}

static void CALLBACK Draw(void)
{

    glViewport(0, 0, windW, windH);
    glDisable(GL_SCISSOR_TEST);

    glPushAttrib(GL_COLOR_BUFFER_BIT);

    glColorMask(1, 1, 1, 1);
    glIndexMask((GLuint)~0);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glPopAttrib();

    if (mode1) {
    glShadeModel(GL_SMOOTH);
    } else {
    glShadeModel(GL_FLAT);
    }

    if (mode2) {
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    } else {
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }

    Viewport(0, 0); Point();
    Viewport(0, 1); Lines();
    Viewport(0, 2); LineStrip();
    Viewport(0, 3); LineLoop();

    Viewport(1, 0); Bitmap();

    Viewport(1, 1); TriangleFan();
    Viewport(1, 2); Triangles();
    Viewport(1, 3); TriangleStrip();

    Viewport(2, 0); Rect();
    Viewport(2, 1); xPolygon();
    Viewport(2, 2); Quads();
    Viewport(2, 3); QuadStrip();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    windW = 600;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    windType |= (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Primitive Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxMainLoop(Draw);
}


TQUAD.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define PI 3.141592654
#define BLACK 0
#define GRAY 128
#define WHITE 255
#define RD 0xA40000FF
#define WT 0xFFFFFFFF
#define brickImageWidth 16
#define brickImageHeight 16

static void CALLBACK ErrorHandler(GLenum which);
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_X(void );
static void CALLBACK Key_x(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void CALLBACK Key_4(void );
static void CALLBACK Key_0(void );
static void CALLBACK Key_A(void );
static void CALLBACK Key_a(void );
static void CALLBACK Key_S(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_G(void );
static void CALLBACK Key_g(void );
static void CALLBACK Key_J(void );
static void CALLBACK Key_j(void );
static void CALLBACK Key_H(void );
static void CALLBACK Key_h(void );
static void CALLBACK Key_K(void );
static void CALLBACK Key_k(void );
static void CALLBACK Key_L(void );
static void CALLBACK Key_l(void );
static void CALLBACK Key_z(void );
static void CALLBACK Key_q(void );
static void CALLBACK Key_w(void );
static void CALLBACK Key_e(void );
static void CALLBACK Key_r(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_y(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer;

float black[3] = {
    0.0, 0.0, 0.0
};
float blue[3] =  {
    0.0, 0.0, 1.0
};
float gray[3] =  {
    0.5, 0.5, 0.5
};
float white[3] = {
    1.0, 1.0, 1.0
};

GLenum doDither = GL_TRUE;
GLenum shade = GL_TRUE;
GLenum texture = GL_TRUE;

float xRotation = 30.0, yRotation = 30.0, zRotation = 0.0;
GLint radius1, radius2;
GLdouble angle1, angle2;
GLint slices, stacks;
GLint height;
GLint orientation = GLU_OUTSIDE;
GLint whichQuadric;
GLUquadricObj *quadObj;

GLuint brickImage[brickImageWidth*brickImageHeight] = {
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD
};
char *texFileName = 0;

static void CALLBACK ErrorHandler(GLenum which)
{

    fprintf(stderr, "Quad Error: %s\n", gluErrorString(which));
}

static void Init(void)
{
    static GLint colorIndexes[3] = {0, 200, 255};
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};
    static float decal[] = {GL_DECAL};
    static float modulate[] = {GL_MODULATE};
    static float repeat[] = {GL_REPEAT};
    static float nearest[] = {GL_NEAREST};
    AUX_RGBImageRec *image;

    if (!rgb) {
    auxSetGreyRamp();
    }
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
    if (!rgb) {
    glMaterialiv( GL_FRONT_AND_BACK, GL_COLOR_INDEXES, colorIndexes);
    }

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    if (texFileName) {
    image = auxRGBImageLoad(texFileName);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    } else {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, brickImageWidth, brickImageHeight,
             0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)brickImage);
    }

    quadObj = gluNewQuadric();
    //gluQuadricCallback(quadObj, GLU_ERROR, ErrorHandler);

    radius1 = 10;
    radius2 = 5;
    angle1 = 90;
    angle2 = 180;
    slices = 16;
    stacks = 10;
    height = 20;
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1, 1, -1, 1, 1, 10);
    gluLookAt(2, 2, 2, 0, 0, 0, 0, 0, 1);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_LEFT(void)
{
    yRotation += 5;
}

static void CALLBACK Key_RIGHT(void)
{
    yRotation -= 5;
}

static void CALLBACK Key_UP(void)
{
    xRotation += 5;
}

static void CALLBACK Key_DOWN(void)
{
    xRotation -= 5;
}

static void CALLBACK Key_X(void)
{
    zRotation += 5;
}

static void CALLBACK Key_x(void)
{
    zRotation -= 5;
}

static void CALLBACK Key_1(void)
{
    gluQuadricDrawStyle(quadObj, GLU_FILL);
}

static void CALLBACK Key_2(void)
{
    gluQuadricDrawStyle(quadObj, GLU_POINT);
}

static void CALLBACK Key_3(void)
{
    gluQuadricDrawStyle(quadObj, GLU_LINE);
}

static void CALLBACK Key_4(void)
{
    gluQuadricDrawStyle(quadObj, GLU_SILHOUETTE);
}

static void CALLBACK Key_0(void)
{
    shade = !shade;
    if (shade) {
        glShadeModel(GL_SMOOTH);
        gluQuadricNormals(quadObj, GLU_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
        gluQuadricNormals(quadObj, GLU_FLAT);
    }
}

static void CALLBACK Key_A(void)
{
    stacks++;
}

static void CALLBACK Key_a(void)
{
    if (stacks > 1)
        stacks--;
}

static void CALLBACK Key_S(void)
{
    slices++;
}

static void CALLBACK Key_s(void)
{
    if (slices > 3)
        slices--;
}

static void CALLBACK Key_d(void)
{
    switch(orientation) {
      case GLU_OUTSIDE:
        orientation = GLU_INSIDE;
        break;
      case GLU_INSIDE:
      default:
        orientation = GLU_OUTSIDE;
        break;
    }
    gluQuadricOrientation(quadObj, orientation);
}

static void CALLBACK Key_f(void)
{
    whichQuadric = (whichQuadric + 1) & 0x03;
}

static void CALLBACK Key_G(void)
{
    radius1 += 1;
}

static void CALLBACK Key_g(void)
{
    if (radius1 > 0)
        radius1 -= 1;
}

static void CALLBACK Key_J(void)
{
    radius2 += 1;
}

static void CALLBACK Key_j(void)
{
    if (radius2 > 0)
        radius2 -= 1;
}

static void CALLBACK Key_H(void)
{
    height += 2;
}

static void CALLBACK Key_h(void)
{
    if (height > 0)
        height -= 2;
}

static void CALLBACK Key_K(void)
{
    angle1 += 5;
}

static void CALLBACK Key_k(void)
{
    angle1 -= 5;
}

static void CALLBACK Key_L(void)
{
    angle2 += 5;
}

static void CALLBACK Key_l(void)
{
    angle2 -= 5;
}

static void CALLBACK Key_z(void)
{
        texture = !texture;
    if (texture) {
        gluQuadricTexture(quadObj, GL_TRUE);
        glEnable(GL_TEXTURE_2D);
    } else {
        gluQuadricTexture(quadObj, GL_FALSE);
        glDisable(GL_TEXTURE_2D);
    }
}

static void CALLBACK Key_q(void)
{
    glDisable(GL_CULL_FACE);
}

static void CALLBACK Key_w(void)
{
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
}

static void CALLBACK Key_e(void)
{
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
}

static void CALLBACK Key_r(void)
{
    glFrontFace(GL_CW);
}

static void CALLBACK Key_t(void)
{
    glFrontFace(GL_CCW);
}

static void CALLBACK Key_y(void)
{
    doDither = !doDither;
    (doDither) ? glEnable(GL_DITHER) : glDisable(GL_DITHER);
}

static void CALLBACK Draw(void)
{

    glLoadIdentity();
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);
    glRotatef(zRotation, 0, 0, 1);

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glColor3f(1.0, 1.0, 1.0);
    switch (whichQuadric) {
      case 0:
    glTranslatef(0, 0, -height/20.0);
    gluCylinder(quadObj, radius1/10.0, radius2/10.0, height/10.0,
            slices, stacks);
    break;
      case 1:
    gluSphere(quadObj, radius1/10.0, slices, stacks);
    break;
      case 2:
    gluPartialDisk(quadObj, radius2/10.0, radius1/10.0, slices,
               stacks, angle1, angle2);
    break;
      case 3:
    gluDisk(quadObj, radius2/10.0, radius1/10.0, slices, stacks);
    break;
    }

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-f (No file name).\n");
        return GL_FALSE;
        } else {
        texFileName = argv[++i];
        }
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Quad Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_X, Key_X);
    auxKeyFunc(AUX_x, Key_x);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxKeyFunc(AUX_4, Key_4);
    auxKeyFunc(AUX_0, Key_0);
    auxKeyFunc(AUX_A, Key_A);
    auxKeyFunc(AUX_a, Key_a);
    auxKeyFunc(AUX_S, Key_S);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_G, Key_G);
    auxKeyFunc(AUX_g, Key_g);
    auxKeyFunc(AUX_J, Key_J);
    auxKeyFunc(AUX_j, Key_j);
    auxKeyFunc(AUX_H, Key_H);
    auxKeyFunc(AUX_h, Key_h);
    auxKeyFunc(AUX_K, Key_K);
    auxKeyFunc(AUX_k, Key_k);
    auxKeyFunc(AUX_L, Key_L);
    auxKeyFunc(AUX_l, Key_l);
    auxKeyFunc(AUX_z, Key_z);
    auxKeyFunc(AUX_Q, Key_q);
    auxKeyFunc(AUX_w, Key_w);
    auxKeyFunc(AUX_e, Key_e);
    auxKeyFunc(AUX_r, Key_r);
    auxKeyFunc(AUX_t, Key_t);
    auxKeyFunc(AUX_y, Key_y);
    auxMainLoop(Draw);
}


TSELECT.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <gl\glaux.h>

#define MAXOBJS 10000
#define MAXSELECT 100
#define MAXFEED 300
#define SOLID 1
#define LINE 2
#define POINT 3

static void InitObjects(GLint num);
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void Render(GLenum mode);
static GLint DoSelect(GLint x, GLint y);
static void RecolorTri(GLint h);
static void DeleteTri(GLint h);
static void GrowTri(GLint h);
static GLenum CALLBACK Mouse_left(struct _AUX_EVENTREC *event);
static GLenum CALLBACK Mouse_middle(struct _AUX_EVENTREC *event);
static GLenum CALLBACK Mouse_right(struct _AUX_EVENTREC *event);
static void CALLBACK Draw(void );
static void DrawZoom(GLint x, GLint y);
static void DumpFeedbackVert(GLint *i, GLint n);
static void DrawFeedback(GLint n);
static void DoFeedback(void );
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_Z(void );
static void CALLBACK Key_z(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_l(void );

GLint windW, windH;

GLuint selectBuf[MAXSELECT];
GLfloat feedBuf[MAXFEED];
GLint vp[4];
float zRotation = 90.0;
float zoom = 1.0;
GLint objectCount;
GLint numObjects;
struct object {
    float v1[2];
    float v2[2];
    float v3[2];
    float color[3];
} objects[MAXOBJS];
GLenum linePoly = GL_FALSE;

static void InitObjects(GLint num)
{
    GLint i;
    float x, y;

    if (num > MAXOBJS) {
    num = MAXOBJS;
    }
    if (num < 1) {
    num = 1;
    }
    objectCount = num;

    srand((unsigned int)time(NULL));
    for (i = 0; i < num; i++) {
    x = (rand() % 300) - 150;
    y = (rand() % 300) - 150;

    objects[i].v1[0] = x + (rand() % 50) - 25;
    objects[i].v2[0] = x + (rand() % 50) - 25;
    objects[i].v3[0] = x + (rand() % 50) - 25;
    objects[i].v1[1] = y + (rand() % 50) - 25;
    objects[i].v2[1] = y + (rand() % 50) - 25;
    objects[i].v3[1] = y + (rand() % 50) - 25;
    objects[i].color[0] = ((rand() % 100) + 50) / 150.0;
    objects[i].color[1] = ((rand() % 100) + 50) / 150.0;
    objects[i].color[2] = ((rand() % 100) + 50) / 150.0;
    }
}

static void Init(void)
{

    numObjects = 10;
    InitObjects(numObjects);
    glGetIntegerv(GL_VIEWPORT, vp);
}

static void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;
}

static void Render(GLenum mode)
{
    GLint i;

    for (i = 0; i < objectCount; i++) {
    if (mode == GL_SELECT) {
        glLoadName(i);
    }
    glColor3fv(objects[i].color);
    glBegin(GL_POLYGON);
        glVertex2fv(objects[i].v1);
        glVertex2fv(objects[i].v2);
        glVertex2fv(objects[i].v3);
    glEnd();
    }
}

static GLint DoSelect(GLint x, GLint y)
{
    GLint hits;

    glSelectBuffer(MAXSELECT, selectBuf);
    (void)glRenderMode(GL_SELECT);
    glInitNames();
    glPushName((GLuint)~0);

    glPushMatrix();

    glViewport(0, 0, windW, windH);
    glGetIntegerv(GL_VIEWPORT, vp);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPickMatrix(x, windH-y, 4, 4, vp);
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0, 0, 1);

    Render(GL_SELECT);

    glPopMatrix();

    hits = glRenderMode(GL_RENDER);
    if (hits <= 0) {
    return -1;
    }

    return selectBuf[(hits-1)*4+3];
}

static void RecolorTri(GLint h)
{

    objects[h].color[0] = ((rand() % 100) + 50) / 150.0;
    objects[h].color[1] = ((rand() % 100) + 50) / 150.0;
    objects[h].color[2] = ((rand() % 100) + 50) / 150.0;
}

static void DeleteTri(GLint h)
{

    objects[h] = objects[objectCount-1];
    objectCount--;
}

static void GrowTri(GLint h)
{
    float v[2];
    float *oldV;
    GLint i;

    v[0] = objects[h].v1[0] + objects[h].v2[0] + objects[h].v3[0];
    v[1] = objects[h].v1[1] + objects[h].v2[1] + objects[h].v3[1];
    v[0] /= 3;
    v[1] /= 3;

    for (i = 0; i < 3; i++) {
    switch (i) {
      case 0:
        oldV = objects[h].v1;
        break;
      case 1:
        oldV = objects[h].v2;
        break;
      case 2:
        oldV = objects[h].v3;
        break;
    }
    oldV[0] = 1.5 * (oldV[0] - v[0]) + v[0];
    oldV[1] = 1.5 * (oldV[1] - v[1]) + v[1];
    }
}

static GLenum CALLBACK Mouse_left(AUX_EVENTREC *event)
{
    GLint mouseX, mouseY;
    GLint hit;

    mouseX = event->data[AUX_MOUSEX];
    mouseY = event->data[AUX_MOUSEY];
    hit = DoSelect((GLint)mouseX, (GLint)mouseY);
    if (hit != -1) {
        RecolorTri(hit);
    }
    return GL_FALSE;
}

static GLenum CALLBACK Mouse_middle(AUX_EVENTREC *event)
{
    GLint mouseX, mouseY;
    GLint hit;

    mouseX = event->data[AUX_MOUSEX];
    mouseY = event->data[AUX_MOUSEY];
    hit = DoSelect((GLint)mouseX, (GLint)mouseY);
    if (hit != -1) {
        GrowTri(hit);
    }
    return GL_FALSE;
}

static GLenum CALLBACK Mouse_right(AUX_EVENTREC *event)
{
    GLint mouseX, mouseY;
    GLint hit;

    mouseX = event->data[AUX_MOUSEX];
    mouseY = event->data[AUX_MOUSEY];
    hit = DoSelect((GLint)mouseX, (GLint)mouseY);
    if (hit != -1) {
        DeleteTri(hit);
    }
    return GL_FALSE;
}

static void CALLBACK Draw(void)
{

    glPushMatrix();

    glViewport(0, 0, windW, windH);
    glGetIntegerv(GL_VIEWPORT, vp);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0, 0, 1);

    Render(GL_RENDER);

    glPopMatrix();

    glFlush();
}

static void DrawZoom(GLint x, GLint y)
{

    glPushMatrix();

    glViewport(0, 0, windW, windH);
    glGetIntegerv(GL_VIEWPORT, vp);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPickMatrix(x, windH-y, 4, 4, vp);
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0, 0, 1);

    Render(GL_RENDER);

    glPopMatrix();
}

static void DumpFeedbackVert(GLint *i, GLint n)
{
    GLint index;

    index = *i;
    if (index+7 > n) {
    *i = n;
    //printf("  ???\n");
    return;
    }
/*
    printf("  (%g %g %g), color = (%4.2f %4.2f %4.2f)\n",
       feedBuf[index],
       feedBuf[index+1],
       feedBuf[index+2],
       feedBuf[index+3],
       feedBuf[index+4],
       feedBuf[index+5]);
*/
    index += 7;
    *i = index;
}

static void DrawFeedback(GLint n)
{
    GLint i;
    GLint verts;

    //printf("Feedback results (%d floats):\n", n);
    for (i = 0; i < n; i++) {
    switch ((GLint)feedBuf[i]) {
      case GL_POLYGON_TOKEN:
        //printf("Polygon");
        i++;
        if (i < n) {
        verts = (GLint)feedBuf[i];
        i++;
        //printf(": %d vertices", verts);
        } else {
        verts = 0;
        }
        //printf("\n");
        while (verts) {
        DumpFeedbackVert(&i, n);
        verts--;
        }
        i--;
        break;
      case GL_LINE_TOKEN:
        //printf("Line:\n");
        i++;
        DumpFeedbackVert(&i, n);
        DumpFeedbackVert(&i, n);
        i--;
        break;
      case GL_LINE_RESET_TOKEN:
        //printf("Line Reset:\n");
        i++;
        DumpFeedbackVert(&i, n);
        DumpFeedbackVert(&i, n);
        i--;
        break;
      default:
        //printf("%9.2f\n", feedBuf[i]);
        break;
    }
    }
/*
    if (i == MAXFEED) {
    printf("...\n");
    }
    printf("\n");
*/
}

static void DoFeedback(void)
{
    GLint x;

    glFeedbackBuffer(MAXFEED, GL_3D_COLOR, feedBuf);
    (void)glRenderMode(GL_FEEDBACK);

    glPushMatrix();

    glViewport(0, 0, windW, windH);
    glGetIntegerv(GL_VIEWPORT, vp);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0, 0, 1);

    Render(GL_FEEDBACK);

    glPopMatrix();

    x = glRenderMode(GL_RENDER);
    if (x == -1) {
    x = MAXFEED;
    }

    DrawFeedback((GLint)x);
}

static void CALLBACK Key_LEFT(void)
{
    zRotation += 0.5;
}

static void CALLBACK Key_RIGHT(void)
{
    zRotation -= 0.5;
}

static void CALLBACK Key_Z(void)
{
    zoom /= 0.75;
}

static void CALLBACK Key_z(void)
{
    zoom *= 0.75;
}

static void CALLBACK Key_f(void)
{
    DoFeedback();
}

static void CALLBACK Key_d(void)
{
    GLint mouseX, mouseY;

    auxGetMouseLoc(&mouseX, &mouseY);
    DrawZoom((GLint)mouseX, (GLint)mouseY);
}

static void CALLBACK Key_l(void)
{
    linePoly = !linePoly;
    if (linePoly) {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    } else {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
}

void main(int argc, char **argv)
{
    GLenum type;

    windW = 300;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    type = AUX_RGB | AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Select Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_Z, Key_Z);
    auxKeyFunc(AUX_z, Key_z);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_l, Key_l);
    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN, Mouse_left);
    auxMouseFunc(AUX_MIDDLEBUTTON, AUX_MOUSEDOWN, Mouse_middle);
    auxMouseFunc(AUX_RIGHTBUTTON, AUX_MOUSEDOWN, Mouse_right);
    auxMainLoop(Draw);
}


TSPEED.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#ifdef __unix
#include <sys/times.h>
#include <sys/param.h>
#endif
#ifdef WIN32
#include <time.h>
#endif

#define GAP 10
#define ROWS 1
#define COLS 4

static void CALLBACK SetWindSize(int width,int height);
static void CALLBACK Key_a(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_F(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_t(void );
static void Viewport(GLint row, GLint column);
static double Now(void );
static void Report(const char *msg,float elapsed);
static void Points(void );
static void Lines(void );
static void Triangles(void );
static void Rects(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;
GLint windW, windH;

GLint boxW, boxH;

GLenum antialiasing = GL_FALSE;
GLenum depthTesting = GL_FALSE;
GLenum fogging = GL_FALSE, niceFogging = GL_FALSE;
GLenum lighting = GL_FALSE;
GLenum shading = GL_FALSE;
GLenum texturing = GL_FALSE;

GLint repeatCount = 100;
GLint loopCount = 10;

GLubyte texture[4*3] = {
    0xFF, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0xFF, 0,
};

static void CALLBACK SetWindSize(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;
}

static void CALLBACK Key_a(void)
{
    antialiasing = !antialiasing;
}

static void CALLBACK Key_d(void)
{
    depthTesting = !depthTesting;
}

static void CALLBACK Key_f(void)
{
    fogging = !fogging;
}

static void CALLBACK Key_F(void)
{
    niceFogging = !niceFogging;
}

static void CALLBACK Key_s(void)
{
    shading = !shading;
}

static void CALLBACK Key_t(void)
{
    texturing = !texturing;
}

static void Viewport(GLint row, GLint column)
{
    GLint x, y;

    boxW = (windW - (COLS + 1) * GAP) / COLS;
    boxH = (windH - (ROWS + 1) * GAP) / ROWS;

    x = GAP + column * (boxW + GAP);
    y = GAP + row * (boxH + GAP);

    glViewport(x, y, boxW, boxH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-boxW/2, boxW/2, -boxH/2, boxH/2);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_SCISSOR_TEST);
    glScissor(x, y, boxW, boxH);
}

static double Now(void)
{
#ifdef __unix
    struct tms tm;
    clock_t clk;

    clk = times(&tm);
    return (double)clk / (double)HZ;
#else
#ifdef WIN32
    long sec;

    sec = time(NULL);
    return(sec);
#else
    return 0;
#endif
#endif
}

static void Report(const char *msg, float elapsed)
{

    if (elapsed == 0.0) {
    printf("%s per second: Unknown, elapsed time is zero\n", msg);
    } else {
    printf("%s per second: %g\n", msg, repeatCount*loopCount/elapsed);
    }
}

static void Points(void)
{
    GLint i, j;
    float v1[3];
    double start;

    start = Now();
    for (i = 0; i < repeatCount; i++) {
    v1[0] = 10;
    v1[1] = 10;
    v1[2] = 10;
    glBegin(GL_POINTS);
        for (j = 0; j < loopCount; j++) {
        glVertex2fv(v1);
        }
    glEnd();
    }
    glFinish();
    //Report("Points", Now()-start);
}

static void Lines(void)
{
    GLint i, j;
    float v1[3], v2[3];
    double start;

    start = Now();
    for (i = 0; i < repeatCount; i++) {
    v1[0] = 10;
    v1[1] = 10;
    v1[2] = 10;
    v2[0] = 20;
    v2[1] = 20;
    v2[2] = 10;
    glBegin(GL_LINES);
        for (j = 0; j < loopCount; j++) {
        glVertex2fv(v1);
        glVertex2fv(v2);
        }
    glEnd();
    }
    glFinish();
    //Report("Lines", Now()-start);
}

static void Triangles(void)
{
    GLint i, j;
    float v1[3], v2[3], v3[3], t1[2], t2[2], t3[2];
    double start;

    start = Now();

    v1[0] = 10;
    v1[1] = 10;
    v1[2] = 10;
    v2[0] = 20;
    v2[1] = 20;
    v2[2] = 10;
    v3[0] = 10;
    v3[1] = 20;
    v3[2] = 10;

    t1[0] = 0;
    t1[1] = 0;
    t2[0] = 1;
    t2[1] = 1;
    t3[0] = 0;
    t3[1] = 1;

    for (i = 0; i < repeatCount; i++) {
    glBegin(GL_TRIANGLES);
        for (j = 0; j < loopCount; j++) {
        if (texturing) {
            glTexCoord2fv(t1);
        }
        glVertex2fv(v1);
        if (texturing) {
            glTexCoord2fv(t2);
        }
        glVertex2fv(v2);
        if (texturing) {
            glTexCoord2fv(t3);
        }
        glVertex2fv(v3);
        }
    glEnd();
    }
    glFinish();
    //Report("Triangles", Now()-start);
}

static void Rects(void)
{
    GLint i, j;
    float v1[2], v2[2];
    double start;

    start = Now();
    for (i = 0; i < repeatCount; i++) {
    v1[0] = 10;
    v1[1] = 10;
    v2[0] = 20;
    v2[1] = 20;
    for (j = 0; j < loopCount; j++) {
        glRectfv(v1, v2);
    }
    }
    glFinish();
    //Report("Rects", Now()-start);
}

static void CALLBACK Draw(void)
{
    glDisable(GL_SCISSOR_TEST);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    AUX_SETCOLOR(windType, AUX_YELLOW);

    if (antialiasing) {
    glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
    glEnable(GL_BLEND);

    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_POLYGON_SMOOTH);
    }
    if (depthTesting) {
    glEnable(GL_DEPTH_TEST);
    }
    if (fogging) {
    glEnable(GL_FOG);
    glHint(GL_FOG_HINT, (niceFogging) ? GL_NICEST : GL_FASTEST);
    }
    if (lighting) {
    static GLfloat ambient[4] = {1, 0.5, 0.5, 0};

    glEnable(GL_NORMALIZE);
    glNormal3f(1.0, 1.0, 1.0);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    }
    (shading) ? glShadeModel(GL_SMOOTH) : glShadeModel(GL_FLAT);
    if (texturing) {
    static GLfloat modulate[1] = {GL_DECAL};
    static GLfloat clamp[1] = {GL_CLAMP};
    static GLfloat linear[1] = {GL_LINEAR};

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE,
             (GLvoid *)texture);
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, modulate);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear);
    glEnable(GL_TEXTURE_2D);
    }

    Viewport(0, 0); Points();
    Viewport(0, 1); Lines();
    Viewport(0, 2); Triangles();
    Viewport(0, 3); Rects();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    windW = 600;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    windType = AUX_DEPTH16;
    windType |= (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Speed Test") == GL_FALSE) {
    auxQuit();
    }

    auxExposeFunc((AUXEXPOSEPROC)SetWindSize);
    auxReshapeFunc((AUXRESHAPEPROC)SetWindSize);
    auxKeyFunc(AUX_a, Key_a);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_F, Key_F);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_t, Key_t);
    auxMainLoop(Draw);
}


TSPHERE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define FALSE 0
#define TRUE  1
#define PI    3.14159265358979323846

extern void BuildCylinder(int numEdges);
extern void BuildTorus(float rc,int numc,float rt,int numt);
extern void BuildCage(void );
extern void BuildCube(void );
extern void BuildLists(void );
extern void SetDeepestColor(void );
extern void SetDefaultSettings(void );
extern unsigned char *AlphaPadImage(int bufSize,unsigned char *inData,int alpha);
extern void Init(void );
extern void ReInit(void );
extern void CALLBACK Draw(void );
extern void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_a(void );
static void CALLBACK Key_c(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_m(void );
static void CALLBACK Key_l(void );
static void CALLBACK Key_f(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_0(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void CALLBACK Key_4(void );
static void CALLBACK Key_5(void );
static void CALLBACK Key_6(void );
static void CALLBACK Key_7(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;
int W = 400, H = 400;

char *imageFileName = 0;
AUX_RGBImageRec *image;

int numComponents;

float *minFilter, *magFilter, *sWrapMode, *tWrapMode;
float decal[] = {GL_DECAL};
float modulate[] = {GL_MODULATE};
float repeat[] = {GL_REPEAT};
float clamp[] = {GL_CLAMP};
float nearest[] = {GL_NEAREST};
float linear[] = {GL_LINEAR};
float nearest_mipmap_nearest[] = {GL_NEAREST_MIPMAP_NEAREST};
float nearest_mipmap_linear[] = {GL_NEAREST_MIPMAP_LINEAR};
float linear_mipmap_nearest[] = {GL_LINEAR_MIPMAP_NEAREST};
float linear_mipmap_linear[] = {GL_LINEAR_MIPMAP_LINEAR};
GLint sphereMap[] = {GL_SPHERE_MAP};

float xRotation = 0.0, yRotation = 0.0;
float zTranslate = -4.0;
GLenum autoRotate = TRUE;
GLenum deepestColor = AUX_GREEN;
GLenum isLit = TRUE;
GLenum isFogged = FALSE;
float *textureEnvironment = modulate;

struct MipMap {
    int width, height;
    unsigned char *data;
};

int cube, cage, cylinder, torus, genericObject;

float c[6][4][4][3] = {
    {
    {
        {
        1.0, 1.0, -1.0
        },
        {
        0.0, 1.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
        {
        1.0, 0.0, -1.0
        },
    },
    {
        {
        0.0, 1.0, -1.0
        },
        {
        -1.0, 1.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
    },
    {
        {
        0.0,  0.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        -1.0, -1.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
    },
    {
        {
        1.0, 0.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
        {
        1.0, -1.0, -1.0
        },
    },
    },
    {
    {
        {
        1.0, 1.0, 1.0
        },
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 0.0, 0.0
        },
        {
        1.0, 0.0, 1.0
        },
    },
    {
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 1.0, -1.0
        },
        {
        1.0, 0.0, -1.0
        },
        {
        1.0, 0.0, 0.0
        },
    },
    {
        {
        1.0, 0.0, -1.0
        },
        {
        1.0, -1.0, -1.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, 0.0, 0.0
        },
    },
    {
        {
        1.0, 0.0, 0.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        1.0, 0.0, 1.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
    },
    {
        {
        0.0, 1.0, 1.0
        },
        {
        1.0, 1.0, 1.0
        },
        {
        1.0, 0.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    {
        {
        1.0, 0.0, 1.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    {
        {
        0.0, -1.0, 1.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, -1.0
        },
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 0.0, 0.0
        },
        {
        -1.0, 0.0, -1.0
        },
    },
    {
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 1.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    {
        {
        -1.0, 0.0, 1.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    {
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, -1.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, 1.0
        },
        {
        -1.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 1.0
        },
    },
    {
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 1.0, -1.0
        },
        {
        0.0, 1.0, -1.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    {
        {
        0.0, 1.0, -1.0
        },
        {
        1.0, 1.0, -1.0
        },
        {
        1.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    {
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    },
    {
    {
        {
        -1.0, -1.0, -1.0
        },
        {
        -1.0, -1.0, 0.0
        },
        {
        0.0, -1.0, 0.0
        },
        {
        0.0, -1.0, -1.0
        },
    },
    {
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    {
        {
        0.0, -1.0, 1.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    {
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, -1.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    }
};

float n[6][3] = {
    {
    0.0, 0.0, -1.0
    },
    {
    1.0, 0.0, 0.0
    },
    {
    0.0, 0.0, 1.0
    },
    {
    -1.0, 0.0, 0.0
    },
    {
    0.0, 1.0, 0.0
    },
    {
    0.0, -1.0, 0.0
    }
};

GLfloat identity[16] = {
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
};

void BuildCylinder(int numEdges)
{
    int i, top = 1.0, bottom = -1.0;
    float x[100], y[100], angle;

    for (i = 0; i <= numEdges; i++) {
    angle = i * 2.0 * PI / numEdges;
    x[i] = cos(angle);
    y[i] = sin(angle);
    }

    glNewList(cylinder, GL_COMPILE);
    glBegin(GL_TRIANGLE_STRIP);
    for (i = 0; i <= numEdges; i++) {
        glNormal3f(x[i], y[i], 0.0);
        glVertex3f(x[i], y[i], bottom);
        glVertex3f(x[i], y[i], top);
    }
    glEnd();
    glBegin(GL_TRIANGLE_FAN);
    glNormal3f(0.0, 0.0, 1.0);
    glVertex3f(0.0, 0.0, top);
    for (i = 0; i <= numEdges; i++) {
        glVertex3f(x[i], -y[i], top);
    }
    glEnd();
    glBegin(GL_TRIANGLE_FAN);
    glNormal3f(0.0, 0.0, -1.0);
    glVertex3f(0.0, 0.0, bottom);
    for (i = 0; i <= numEdges; i++) {
        glVertex3f(x[i], y[i], bottom);
    }
    glEnd();
    glEndList();
}

void BuildTorus(float rc, int numc, float rt, int numt)
{
    int i, j, k;
    double s, t;
    double x, y, z;
    double pi, twopi;

    pi = 3.14159265358979323846;
    twopi = 2.0 * pi;

    glNewList(torus, GL_COMPILE);
    for (i = 0; i < numc; i++) {
    glBegin(GL_QUAD_STRIP);
        for (j = 0; j <= numt; j++) {
        for (k = 0; k <= 1; k++) {
        s = (i + k) % numc + 0.5;
        t = j % numt;

        x = cos(t*twopi/numt) * cos(s*twopi/numc);
        y = sin(t*twopi/numt) * cos(s*twopi/numc);
        z = sin(s*twopi/numc);
        glNormal3f(x, y, z);

        x = (rt + rc * cos(s*twopi/numc)) * cos(t*twopi/numt);
        y = (rt + rc * cos(s*twopi/numc)) * sin(t*twopi/numt);
        z = rc * sin(s*twopi/numc);
        glVertex3f(x, y, z);
        }
        }
    glEnd();
    }
    glEndList();
}

void BuildCage(void)
{
    int i;
    float inc;
    float right, left, top, bottom, front, back;

    front  = 0.0;
    back   = -8.0;

    left   = -4.0;
    bottom = -4.0;
    right  = 4.0;
    top    = 4.0;

    inc = 2.0 * 4.0 * 0.1;

    glNewList(cage, GL_COMPILE);
    for (i = 0; i < 10; i++) {

    /*
    ** Back
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top,    back);
        glVertex3f(left+i*inc, bottom, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom+i*inc, back);
        glVertex3f(left,  bottom+i*inc, back);
    glEnd();

    /*
    ** Front
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top,    front);
        glVertex3f(left+i*inc, bottom, front);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom+i*inc, front);
        glVertex3f(left,  bottom+i*inc, front);
    glEnd();

    /*
    ** Left
    */
    glBegin(GL_LINES);
        glVertex3f(left, bottom+i*inc, front);
        glVertex3f(left, bottom+i*inc, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(left, top,    back+i*inc);
        glVertex3f(left, bottom, back+i*inc);
    glEnd();

    /*
    ** Right
    */
    glBegin(GL_LINES);
        glVertex3f(right, top-i*inc, front);
        glVertex3f(right, top-i*inc, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, top,    back+i*inc);
        glVertex3f(right, bottom, back+i*inc);
    glEnd();

    /*
    ** Top
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top, front);
        glVertex3f(left+i*inc, top, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, top, back+i*inc);
        glVertex3f(left,  top, back+i*inc);
    glEnd();

    /*
    ** Bottom
    */
    glBegin(GL_LINES);
        glVertex3f(right-i*inc, bottom, front);
        glVertex3f(right-i*inc, bottom, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom, back+i*inc);
        glVertex3f(left,  bottom, back+i*inc);
    glEnd();
    }
    glEndList();
}

void BuildCube(void)
{
    int i, j;

    glNewList(cube, GL_COMPILE);
    for (i = 0; i < 6; i++) {
    for (j = 0; j < 4; j++) {
        glNormal3fv(n[i]);
        glBegin(GL_POLYGON);
        glVertex3fv(c[i][j][0]);
        glVertex3fv(c[i][j][1]);
        glVertex3fv(c[i][j][2]);
        glVertex3fv(c[i][j][3]);
        glEnd();
    }
    }
    glEndList();
}

void BuildLists(void)
{

    cube = glGenLists(1);
    BuildCube();

    cage = glGenLists(2);
    BuildCage();

    cylinder = glGenLists(3);
    BuildCylinder(60);

    torus = glGenLists(4);
    BuildTorus(0.65, 20, .85, 65);

    genericObject = torus;
}

void SetDeepestColor(void)
{
    int redBits, greenBits, blueBits;

    glGetIntegerv(GL_RED_BITS, &redBits);
    glGetIntegerv(GL_GREEN_BITS, &greenBits);
    glGetIntegerv(GL_BLUE_BITS, &blueBits);

    deepestColor = (redBits >= greenBits) ? AUX_RED : AUX_GREEN;
    deepestColor = (deepestColor >= (GLenum)blueBits) ? deepestColor : AUX_BLUE;
}

void SetDefaultSettings(void)
{

    magFilter = nearest;
    minFilter = nearest;
    sWrapMode = repeat;
    tWrapMode = repeat;
    textureEnvironment = modulate;
    autoRotate = TRUE;
}

unsigned char *AlphaPadImage(int bufSize, unsigned char *inData, int alpha)
{
    unsigned char *outData, *out_ptr, *in_ptr;
    int i;

    outData = malloc(bufSize * 4);
    out_ptr = outData;
    in_ptr = inData;

    for (i = 0; i < bufSize; i++) {
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = alpha;
    }

    free (inData);
    return outData;
}

void Init(void)
{
    float ambient[] = {0.0, 0.0, 0.0, 1.0};
    float diffuse[] = {0.0, 1.0, 0.0, 1.0};
    float specular[] = {1.0, 1.0, 1.0, 1.0};
    float position[] = {2.0, 2.0,  0.0, 1.0};
    float fog_color[] = {0.0, 0.0, 0.0, 1.0};
    float mat_ambient[] = {0.0, 0.0, 0.0, 1.0};
    float mat_shininess[] = {90.0};
    float mat_specular[] = {1.0, 1.0, 1.0, 1.0};
    float mat_diffuse[] = {1.0, 1.0, 1.0, 1.0};
    float lmodel_ambient[] = {0.0, 0.0, 0.0, 1.0};
    float lmodel_twoside[] = {GL_TRUE};
    float lmodel_local_viewer[] = {GL_FALSE};

    SetDeepestColor();
    SetDefaultSettings();

    if (numComponents == 4) {
    image = auxRGBImageLoad(imageFileName);
    image->data = AlphaPadImage(image->sizeX*image->sizeY,
                                    image->data, 128);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, numComponents,
              image->sizeX, image->sizeY,
              GL_RGBA, GL_UNSIGNED_BYTE, image->data);
    } else {
    image = auxRGBImageLoad(imageFileName);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, numComponents,
              image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    }

    glFogf(GL_FOG_DENSITY, 0.125);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_START, 4.0);
    glFogf(GL_FOG_END, 9.0);
    glFogfv(GL_FOG_COLOR, fog_color);

    glDepthFunc(GL_LEQUAL);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glShadeModel(GL_SMOOTH);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glViewport(0, 0, W, H);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glFrontFace(GL_CW);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    glEnable(GL_TEXTURE_2D);
    glTexGeniv(GL_S, GL_TEXTURE_GEN_MODE, sphereMap);
    glTexGeniv(GL_T, GL_TEXTURE_GEN_MODE, sphereMap);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tWrapMode);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, textureEnvironment);

    BuildLists();
}

void ReInit(void)
{

    if (genericObject == torus) {
    glEnable(GL_DEPTH_TEST);
    } else  {
    glDisable(GL_DEPTH_TEST);
    }
    if (isFogged) {
    textureEnvironment = modulate;
    }

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, textureEnvironment);
}

void CALLBACK Draw(void)
{

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-0.2, 0.2, -0.2, 0.2, 0.15, 9.0);
    glMatrixMode(GL_MODELVIEW);

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    if (isFogged) {
    glEnable(GL_FOG);
    glColor3fv(auxRGBMap[deepestColor]);
    } else {
    glColor3fv(auxRGBMap[AUX_WHITE]);
    }
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glDisable(GL_TEXTURE_2D);
    glCallList(cage);

    glPushMatrix();
    glTranslatef(0.0, 0.0, zTranslate);
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);

    if (isLit == TRUE) {
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    }

    glEnable(GL_TEXTURE_2D);
    if (isFogged) {
    glDisable(GL_FOG);
    }
    glPolygonMode(GL_FRONT, GL_FILL);
    glColor3fv(auxRGBMap[deepestColor]);
    glCallList(genericObject);

    glPopMatrix();
    glFlush();

    if (autoRotate) {
    xRotation += .75;
    yRotation += .375;
    }
    auxSwapBuffers();
}

void CALLBACK Reshape(int width, int height)
{

    W = width;
    H = height;
    ReInit();
}

static void CALLBACK Key_LEFT(void)
{
    yRotation -= 0.5;
    autoRotate = FALSE;
    ReInit();
}

static void CALLBACK Key_RIGHT(void)
{
    yRotation += 0.5;
    autoRotate = FALSE;
    ReInit();
}

static void CALLBACK Key_UP(void)
{
    xRotation -= 0.5;
    autoRotate = FALSE;
    ReInit();
}

static void CALLBACK Key_DOWN(void)
{
    xRotation += 0.5;
    autoRotate = FALSE;
    ReInit();
}

static void CALLBACK Key_a(void)
{
    autoRotate = !autoRotate;
    ReInit();
}

static void CALLBACK Key_c(void)
{
    genericObject = (genericObject == cube) ? cylinder : cube;
    ReInit();
}

static void CALLBACK Key_d(void)
{
    textureEnvironment = decal;
    ReInit();
}

static void CALLBACK Key_m(void)
{
    textureEnvironment = modulate;
    ReInit();
}

static void CALLBACK Key_l(void)
{
    isLit = !isLit;
    ReInit();
}

static void CALLBACK Key_f(void)
{
    isFogged = !isFogged;
    ReInit();
}

static void CALLBACK Key_t(void)
{
    genericObject = torus;
    ReInit();
}

static void CALLBACK Key_0(void)
{
    magFilter = nearest;
    ReInit();
}

static void CALLBACK Key_1(void)
{
    magFilter = linear;
    ReInit();
}

static void CALLBACK Key_2(void)
{
    minFilter = nearest;
    ReInit();
}

static void CALLBACK Key_3(void)
{
    minFilter = linear;
    ReInit();
}

static void CALLBACK Key_4(void)
{
    minFilter = nearest_mipmap_nearest;
    ReInit();
}

static void CALLBACK Key_5(void)
{
    minFilter = nearest_mipmap_linear;
    ReInit();
}

static void CALLBACK Key_6(void)
{
    minFilter = linear_mipmap_nearest;
    ReInit();
}

static void CALLBACK Key_7(void)
{
    minFilter = linear_mipmap_linear;
    ReInit();
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    numComponents = 4;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-f (No file name).\n");
        return GL_FALSE;
        } else {
        imageFileName = argv[++i];
        }
    } else if (strcmp(argv[i], "-4") == 0) {
        numComponents = 4;
    } else if (strcmp(argv[i], "-3") == 0) {
        numComponents = 3;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    if (imageFileName == 0) {
    //printf("No image file.\n");
    auxQuit();
    }

    auxInitPosition(0, 0, W, H);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Texture Test") == GL_FALSE) {
        auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_a, Key_a);
    auxKeyFunc(AUX_c, Key_c);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_m, Key_m);
    auxKeyFunc(AUX_l, Key_l);
    auxKeyFunc(AUX_f, Key_f);
    auxKeyFunc(AUX_t, Key_t);
    auxKeyFunc(AUX_0, Key_0);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxKeyFunc(AUX_4, Key_4);
    auxKeyFunc(AUX_5, Key_5);
    auxKeyFunc(AUX_6, Key_6);
    auxKeyFunc(AUX_7, Key_7);
    auxIdleFunc(Draw);

    auxMainLoop(NULL);
}


TSTAR.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "GL/gl.h"
#include "GL/glu.h"
#include <gl\glaux.h>

#define PI 3.141592657

extern float Sin(float angle);
extern float Cos(float angle);
extern void NewStar(GLint n, GLint d);
extern void RotatePoint(float *x,float *y,float rotation);
extern void MoveStars(void );
extern GLenum StarPoint(GLint n);
extern void ShowStars(void );
static void Init(void );
extern void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_SPACE(void );
static void CALLBACK Key_t(void );
extern void CALLBACK Idle(void );
static GLenum Args(int argc,char **argv );

enum {
    NORMAL = 0,
    WEIRD = 1
};

enum {
    STREAK = 0,
    CIRCLE = 1
};

#define MAXSTARS 400
#define MAXPOS 10000
#define MAXWARP 10
#define MAXANGLES 6000

typedef struct _starRec {
    GLint type;
    float x[2], y[2], z[2];
    float offsetX, offsetY, offsetR, rotation;
} starRec;

GLenum doubleBuffer;
GLint windW, windH;

GLenum flag = NORMAL;
GLint starCount = MAXSTARS / 2;
float speed = 1.0;
GLint nitro = 0;
starRec stars[MAXSTARS];
float sinTable[MAXANGLES];

float Sin(float angle)
{

    return (sinTable[(GLint)angle % MAXANGLES]);
}

float Cos(float angle)
{

    return (sinTable[((GLint)angle+(MAXANGLES/4))%MAXANGLES]);
}

void NewStar(GLint n, GLint d)
{

    if (rand()%4 == 0) {
    stars[n].type = CIRCLE;
    } else {
    stars[n].type = STREAK;
    }
    stars[n].x[0] = (float)(rand() % MAXPOS - MAXPOS / 2);
    stars[n].y[0] = (float)(rand() % MAXPOS - MAXPOS / 2);
    stars[n].z[0] = (float)(rand() % MAXPOS + d);
    if (rand()%4 == 0 && flag == WEIRD) {
    stars[n].offsetX = (float)(rand() % 100 - 100 / 2);
    stars[n].offsetY = (float)(rand() % 100 - 100 / 2);
    stars[n].offsetR = (float)(rand() % 25 - 25 / 2);
    } else {
    stars[n].offsetX = 0.0;
    stars[n].offsetY = 0.0;
    stars[n].offsetR = 0.0;
    }
    stars[n].rotation = 0.0;
}

void RotatePoint(float *x, float *y, float rotation)
{
    float tmpX, tmpY;

    tmpX = *x * Cos(rotation) - *y * Sin(rotation);
    tmpY = *y * Cos(rotation) + *x * Sin(rotation);
    *x = tmpX;
    *y = tmpY;
}

void MoveStars(void)
{
    float offset;
    GLint n;

    offset = speed * 60.0;

    for (n = 0; n < starCount; n++) {
        stars[n].x[1] = stars[n].x[0];
        stars[n].y[1] = stars[n].y[0];
        stars[n].z[1] = stars[n].z[0];
        stars[n].x[0] += stars[n].offsetX;
        stars[n].y[0] += stars[n].offsetY;
        stars[n].z[0] -= offset;
        stars[n].rotation += stars[n].offsetR;
        if (stars[n].rotation > MAXANGLES) {
            stars[n].rotation = 0.0;
        } else if (stars[n].rotation < 0.0) {
            stars[n].rotation = (float)MAXANGLES;
        }
    }
}

GLenum StarPoint(GLint n)
{
    float x0, y0, x1, y1, width;
    GLint i;

    x0 = stars[n].x[0] * windW / stars[n].z[0];
    y0 = stars[n].y[0] * windH / stars[n].z[0];
    RotatePoint(&x0, &y0, stars[n].rotation);
    x0 += windW / 2.0;
    y0 += windH / 2.0;

    if (x0 >= 0.0 && x0 < windW && y0 >= 0.0 && y0 < windH) {
    if (stars[n].type == STREAK) {
        x1 = stars[n].x[1] * windW / stars[n].z[1];
        y1 = stars[n].y[1] * windH / stars[n].z[1];
        RotatePoint(&x1, &y1, stars[n].rotation);
        x1 += windW / 2.0;
        y1 += windH / 2.0;

        glLineWidth(MAXPOS/100.0/stars[n].z[0]+1.0);
        glColor3f(1.0, (MAXWARP-speed)/MAXWARP, (MAXWARP-speed)/MAXWARP);
        if (fabs(x0-x1) < 1.0 && fabs(y0-y1) < 1.0) {
        glBegin(GL_POINTS);
            glVertex2f(x0, y0);
        glEnd();
        } else {
        glBegin(GL_LINES);
            glVertex2f(x0, y0);
            glVertex2f(x1, y1);
        glEnd();
        }
    } else {
        width = MAXPOS / 10.0 / stars[n].z[0] + 1.0;
        glColor3f(1.0, 0.0, 0.0);
        glBegin(GL_POLYGON);
        for (i = 0; i < 8; i++) {
            float x = x0 + width * Cos((float)i*MAXANGLES/8.0);
            float y = y0 + width * Sin((float)i*MAXANGLES/8.0);
            glVertex2f(x, y);
        };
        glEnd();
    }
    return GL_TRUE;
    } else {
    return GL_FALSE;
    }
}

void ShowStars(void)
{
    GLint n;

    glClear(GL_COLOR_BUFFER_BIT);

    for (n = 0; n < starCount; n++) {
    if (stars[n].z[0] > speed || (stars[n].z[0] > 0.0 && speed < MAXWARP)) {
        if (StarPoint(n) == GL_FALSE) {
        NewStar(n, MAXPOS);
        }
    } else {
        NewStar(n, MAXPOS);
    }
    }
}

static void Init(void)
{
    float angle;
    GLint n;

    srand((unsigned int)time(NULL));

    for (n = 0; n < MAXSTARS; n++) {
    NewStar(n, 100);
    }

    angle = 0.0;
    for (n = 0; n < MAXANGLES ; n++) {
    sinTable[n] = sin(angle);
        angle += PI / (MAXANGLES / 2.0);
    }

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glDisable(GL_DITHER);
}

void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;

    glViewport(0, 0, windW, windH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-0.5, windW+0.5, -0.5, windH+0.5);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_SPACE(void)
{
    flag = (flag == NORMAL) ? WEIRD : NORMAL;
}

static void CALLBACK Key_t(void)
{
    nitro = 1;
}

void CALLBACK Idle(void)
{

    MoveStars();
    ShowStars();
    if (nitro > 0) {
    speed = (float)(nitro / 10) + 1.0;
    if (speed > MAXWARP) {
        speed = MAXWARP;
    }
    if (++nitro > MAXWARP*10) {
        nitro = -nitro;
    }
    } else if (nitro < 0) {
    nitro++;
    speed = (float)(-nitro / 10) + 1.0;
    if (speed > MAXWARP) {
        speed = MAXWARP;
    }
    }

    glFlush();
    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    windW = 300;
    windH = 300;
    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Stars") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_SPACE, Key_SPACE);
    auxKeyFunc(AUX_t, Key_t);
    auxIdleFunc(Idle);
    auxMainLoop(NULL);
}


TSTENCIL.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <gl\glaux.h>

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Draw(void );

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glClearStencil(0);
    glStencilMask(1);
    glEnable(GL_STENCIL_TEST);
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-5.0, 5.0, -5.0, 5.0, -5.0, 5.0);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

    glStencilFunc(GL_ALWAYS, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    glColor3ub(200, 0, 0);
    glBegin(GL_POLYGON);
    glVertex3i(-4, -4, 0);
    glVertex3i( 4, -4, 0);
    glVertex3i( 0,  4, 0);
    glEnd();

    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_INCR, GL_KEEP, GL_DECR);

    glColor3ub(0, 200, 0);
    glBegin(GL_POLYGON);
    glVertex3i(3, 3, 0);
    glVertex3i(-3, 3, 0);
    glVertex3i(-3, -3, 0);
    glVertex3i(3, -3, 0);
    glEnd();

    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glColor3ub(0, 0, 200);
    glBegin(GL_POLYGON);
    glVertex3i(3, 3, 0);
    glVertex3i(-3, 3, 0);
    glVertex3i(-3, -3, 0);
    glVertex3i(3, -3, 0);
    glEnd();

    glFlush();
}

void main(int argc, char **argv)
{
    GLenum type;

    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB | AUX_SINGLE | AUX_STENCIL;

    auxInitDisplayMode(type);

    if (auxInitWindow("Stencil Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxMainLoop(Draw);
}


TSTRETCH.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gl\glaux.h>

#define STEPCOUNT 40
#define FALSE 0
#define TRUE 1
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))

enum {
    OP_NOOP = 0,
    OP_STRETCH,
    OP_DRAWPOINT,
    OP_DRAWIMAGE
};

typedef struct _cRec {
    float x, y;
} cRec;

typedef struct _vertexRec {
    float x, y;
    float dX, dY;
    float tX, tY;
} vertexRec;

extern void DrawImage(void );
extern void DrawPoint(void );
extern void InitVList(void );
extern void ScaleImage(int sizeX,int sizeY);
extern void SetPoint(int x,int y);
extern void Stretch(void );
static void CALLBACK Key_SPACE(void );
static GLenum CALLBACK Mouse_left(struct _AUX_EVENTREC *event);
extern void CALLBACK Animate(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;
int imageSizeX, imageSizeY;
char *fileName = 0;
AUX_RGBImageRec *image;
cRec cList[50];
vertexRec vList[5];
int cCount, cIndex[2], cStep;
GLenum op = OP_NOOP;

void DrawImage(void)
{

    glRasterPos2i(0, 0);
    glDrawPixels(image->sizeX, image->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         image->data);

    auxSwapBuffers();

    glRasterPos2i(0, 0);
    glDrawPixels(image->sizeX, image->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         image->data);
}

void DrawPoint(void)
{
    int i;

    glColor3f(1.0, 0.0, 1.0);
    glPointSize(3.0);
    glBegin(GL_POINTS);
    for (i = 0; i < cCount; i++) {
        glVertex2f(cList[i].x, cList[i].y);
    }
    glEnd();

    auxSwapBuffers();
}

void InitVList(void)
{

    vList[0].x = 0.0;
    vList[0].y = 0.0;
    vList[0].dX = 0.0;
    vList[0].dY = 0.0;
    vList[0].tX = 0.0;
    vList[0].tY = 0.0;

    vList[1].x = (float)imageSizeX;
    vList[1].y = 0.0;
    vList[1].dX = 0.0;
    vList[1].dY = 0.0;
    vList[1].tX = 1.0;
    vList[1].tY = 0.0;

    vList[2].x = (float)imageSizeX;
    vList[2].y = (float)imageSizeY;
    vList[2].dX = 0.0;
    vList[2].dY = 0.0;
    vList[2].tX = 1.0;
    vList[2].tY = 1.0;

    vList[3].x = 0.0;
    vList[3].y = (float)imageSizeY;
    vList[3].dX = 0.0;
    vList[3].dY = 0.0;
    vList[3].tX = 0.0;
    vList[3].tY = 1.0;

    vList[4].x = cList[0].x;
    vList[4].y = cList[0].y;
    vList[4].dX = (cList[1].x - cList[0].x) / STEPCOUNT;
    vList[4].dY = (cList[1].y - cList[0].y) / STEPCOUNT;
    vList[4].tX = cList[0].x / (float)imageSizeX;
    vList[4].tY = cList[0].y / (float)imageSizeY;
}

void ScaleImage(int sizeX, int sizeY)
{
    GLubyte *buf;

    buf = (GLubyte *)malloc(3*sizeX*sizeY);
    gluScaleImage(GL_RGB, image->sizeX, image->sizeY, GL_UNSIGNED_BYTE,
                  image->data, sizeX, sizeY, GL_UNSIGNED_BYTE, buf);
    free(image->data);
    image->data = buf;
    image->sizeX = sizeX;
    image->sizeY = sizeY;
}

void SetPoint(int x, int y)
{

    cList[cCount].x = (float)x;
    cList[cCount].y = (float)y;
    cCount++;
}

void Stretch(void)
{

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[0].tX, vList[0].tY);
    glVertex2f(vList[0].x, vList[0].y);
    glTexCoord2f(vList[1].tX, vList[1].tY);
    glVertex2f(vList[1].x, vList[1].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[1].tX, vList[1].tY);
    glVertex2f(vList[1].x, vList[1].y);
    glTexCoord2f(vList[2].tX, vList[2].tY);
    glVertex2f(vList[2].x, vList[2].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[2].tX, vList[2].tY);
    glVertex2f(vList[2].x, vList[2].y);
    glTexCoord2f(vList[3].tX, vList[3].tY);
    glVertex2f(vList[3].x, vList[3].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[3].tX, vList[3].tY);
    glVertex2f(vList[3].x, vList[3].y);
    glTexCoord2f(vList[0].tX, vList[0].tY);
    glVertex2f(vList[0].x, vList[0].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    auxSwapBuffers();

    if (++cStep < STEPCOUNT) {
    vList[4].x += vList[4].dX;
    vList[4].y += vList[4].dY;
    } else {
    cIndex[0] = cIndex[1];
    cIndex[1] = cIndex[1] + 1;
    if (cIndex[1] == cCount) {
        cIndex[1] = 0;
    }
    vList[4].dX = (cList[cIndex[1]].x - cList[cIndex[0]].x) / STEPCOUNT;
    vList[4].dY = (cList[cIndex[1]].y - cList[cIndex[0]].y) / STEPCOUNT;
    cStep = 0;
    }
}

static void CALLBACK Key_SPACE(void)
{
    if (cCount > 1) {
        InitVList();
        cIndex[0] = 0;
        cIndex[1] = 1;
        cStep = 0;
        glEnable(GL_TEXTURE_2D);
        op = OP_STRETCH;
    }
}

static GLenum CALLBACK Mouse_left(AUX_EVENTREC *event)
{
    GLint mouseX;
    GLint mouseY;

    mouseX = event->data[AUX_MOUSEX];
    mouseY = event->data[AUX_MOUSEY];
    if (op == OP_STRETCH) {
    glDisable(GL_TEXTURE_2D);
    cCount = 0;
    op = OP_DRAWIMAGE;
    } else {
    SetPoint(mouseX, imageSizeY-mouseY);
    op = OP_DRAWPOINT;
    }
    return GL_TRUE;
}

void CALLBACK Animate(void)
{

    switch (op) {
      case OP_STRETCH:
    Stretch();
    break;
      case OP_DRAWPOINT:
    DrawPoint();
    break;
      case OP_DRAWIMAGE:
    DrawImage();
    break;
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-f (No file name).\n");
        return GL_FALSE;
        } else {
        fileName = argv[++i];
        }
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    if (fileName == 0) {
    //printf("No image file.\n");
    auxQuit();
    }

    image = auxRGBImageLoad(fileName);

    imageSizeX = (int)pow(2.0, (float)((int)(log(image->sizeX)/log(2.0))));
    imageSizeY = (int)pow(2.0, (float)((int)(log(image->sizeY)/log(2.0))));

    auxInitPosition(0, 0, imageSizeX, imageSizeY);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Stretch") == GL_FALSE) {
        auxQuit();
    }

    glViewport(0, 0, imageSizeX, imageSizeY);
    gluOrtho2D(0, imageSizeX, 0, imageSizeY);
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);

    ScaleImage(imageSizeX, imageSizeY);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image->sizeX, image->sizeY, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)image->data);

    cCount = 0;
    cIndex[0] = 0;
    cIndex[1] = 0;
    cStep = 0;
    op = OP_DRAWIMAGE;

    auxKeyFunc(AUX_SPACE, Key_SPACE);
    auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN, Mouse_left);
    auxIdleFunc(Animate);
    auxMainLoop(NULL);
}


TTEXTURE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <gl\glaux.h>

static void BuildCube(void );
static void BuildLists(void );
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_UP(void );
static void CALLBACK Key_DOWN(void );
static void CALLBACK Key_T(void );
static void CALLBACK Key_t(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_0(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void CALLBACK Key_4(void );
static void CALLBACK Key_5(void );
static void CALLBACK Key_6(void );
static void CALLBACK Key_7(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum doubleBuffer;

char *texFileName = 0;
AUX_RGBImageRec *image;

float *minFilter, *magFilter, *sWrapMode, *tWrapMode;
float decal[] = {GL_DECAL};
float modulate[] = {GL_MODULATE};
float repeat[] = {GL_REPEAT};
float clamp[] = {GL_CLAMP};
float nr[] = {GL_NEAREST};
float ln[] = {GL_LINEAR};
float nr_mipmap_nr[] = {GL_NEAREST_MIPMAP_NEAREST};
float nr_mipmap_ln[] = {GL_NEAREST_MIPMAP_LINEAR};
float ln_mipmap_nr[] = {GL_LINEAR_MIPMAP_NEAREST};
float ln_mipmap_ln[] = {GL_LINEAR_MIPMAP_LINEAR};
GLint sphereMap[] = {GL_SPHERE_MAP};

GLenum doSphere = GL_FALSE;
float xRotation = 0.0, yRotation = 0.0, zTranslate = -3.125;

GLint cube;
float c[6][4][3] = {
    {
    {
        1.0, 1.0, -1.0
    },
    {
        -1.0, 1.0, -1.0
    },
    {
        -1.0, -1.0, -1.0
    },
    {
        1.0, -1.0, -1.0
    }
    },
    {
    {
        1.0, 1.0, 1.0
    },
    {
        1.0, 1.0, -1.0
    },
    {
        1.0, -1.0, -1.0
    },
    {
        1.0, -1.0, 1.0
    }
    },
    {
    {
        -1.0, 1.0, 1.0
    },
    {
        1.0, 1.0, 1.0
    },
    {
        1.0, -1.0, 1.0
    },
    {
        -1.0, -1.0, 1.0
    }
    },
    {
    {
        -1.0, 1.0, -1.0
    },
    {
        -1.0, 1.0, 1.0
    },
    {
        -1.0, -1.0, 1.0
    },
    {
        -1.0, -1.0, -1.0
    }
    },
    {
    {
        -1.0, 1.0, 1.0
    },
    {
        -1.0, 1.0, -1.0
    },
    {
        1.0, 1.0, -1.0
    },
    {
        1.0, 1.0, 1.0
    }
    },
    {
    {
        -1.0, -1.0, -1.0
    },
    {
        -1.0, -1.0, 1.0
    },
    {
        1.0, -1.0, 1.0
    },
    {
        1.0, -1.0, -1.0
    }
    }
};
static float n[6][3] = {
    {
    0.0, 0.0, -1.0
    },
    {
    1.0, 0.0, 0.0
    },
    {
    0.0, 0.0, 1.0
    },
    {
    -1.0, 0.0, 0.0
    },
    {
    0.0, 1.0, 0.0
    },
    {
    0.0, -1.0, 0.0
    }
};
static float t[6][4][2] = {
    {
    {
        1.1,  1.1
    },
    {
        -0.1, 1.1
    },
    {
        -0.1, -0.1
    },
    {
        1.1,  -0.1
    }
    },
    {
    {
        1.1,  1.1
    },
    {
        -0.1, 1.1
    },
    {
        -0.1, -0.1
    },
    {
        1.1,  -0.1
    }
    },
    {
    {
        -0.1,  1.1
    },
    {
        1.1, 1.1
    },
    {
        1.1, -0.1
    },
    {
        -0.1,  -0.1
    }
    },
    {
    {
        1.1,  1.1
    },
    {
        -0.1, 1.1
    },
    {
        -0.1, -0.1
    },
    {
        1.1,  -0.1
    }
    },
    {
    {
        1.1,  1.1
    },
    {
        -0.1, 1.1
    },
    {
        -0.1, -0.1
    },
    {
        1.1,  -0.1
    }
    },
    {
    {
        1.1,  1.1
    },
    {
        -0.1, 1.1
    },
    {
        -0.1, -0.1
    },
    {
        1.1,  -0.1
    }
    },
};

static void BuildCube(void)
{
    GLint i;

    glNewList(cube, GL_COMPILE);
    for (i = 0; i < 6; i++) {
    glBegin(GL_POLYGON);
        glNormal3fv(n[i]); glTexCoord2fv(t[i][0]); glVertex3fv(c[i][0]);
        glNormal3fv(n[i]); glTexCoord2fv(t[i][1]); glVertex3fv(c[i][1]);
        glNormal3fv(n[i]); glTexCoord2fv(t[i][2]); glVertex3fv(c[i][2]);
        glNormal3fv(n[i]); glTexCoord2fv(t[i][3]); glVertex3fv(c[i][3]);
    glEnd();
    }
    glEndList();
}

static void BuildLists(void)
{

    cube = glGenLists(1);
    BuildCube();
}

static void Init(void)
{

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glEnable(GL_TEXTURE_2D);

    glFrontFace(GL_CCW);
    glCullFace(GL_FRONT);
    glEnable(GL_CULL_FACE);

    BuildLists();

    glClearColor(0.0, 0.0, 0.0, 0.0);

    magFilter = nr;
    minFilter = nr;
    sWrapMode = repeat;
    tWrapMode = repeat;
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(145.0, 1.0, 0.01, 1000);
    glMatrixMode(GL_MODELVIEW);
}

static void CALLBACK Key_LEFT(void)
{
    yRotation -= 0.5;
}

static void CALLBACK Key_RIGHT(void)
{
    yRotation += 0.5;
}

static void CALLBACK Key_UP(void)
{
    xRotation -= 0.5;
}

static void CALLBACK Key_DOWN(void)
{
    xRotation += 0.5;
}

static void CALLBACK Key_T(void)
{
    zTranslate += 0.25;
}

static void CALLBACK Key_t(void)
{
    zTranslate -= 0.25;
}

static void CALLBACK Key_s(void)
{
    doSphere = !doSphere;
    if (doSphere) {
        glTexGeniv(GL_S, GL_TEXTURE_GEN_MODE, sphereMap);
        glTexGeniv(GL_T, GL_TEXTURE_GEN_MODE, sphereMap);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
    } else {
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
    }
}

static void CALLBACK Key_0(void)
{
    magFilter = nr;
}

static void CALLBACK Key_1(void)
{
    magFilter = ln;
}

static void CALLBACK Key_2(void)
{
    minFilter = nr;
}

static void CALLBACK Key_3(void)
{
    minFilter = ln;
}

static void CALLBACK Key_4(void)
{
    minFilter = nr_mipmap_nr;
}

static void CALLBACK Key_5(void)
{
    minFilter = nr_mipmap_ln;
}

static void CALLBACK Key_6(void)
{
    minFilter = ln_mipmap_nr;
}

static void CALLBACK Key_7(void)
{
    minFilter = ln_mipmap_ln;
}

static void CALLBACK Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);

    glPushMatrix();

    glTranslatef(0.0, 0.0, zTranslate);
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);
    glCallList(cube);

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-f (No file name).\n");
        return GL_FALSE;
        } else {
        texFileName = argv[++i];
        }
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    if (texFileName == 0) {
    //printf("No image file.\n");
    auxQuit();
    }

    image = auxRGBImageLoad(texFileName);

    auxInitPosition(0, 0, 300, 300);

    type = AUX_RGB;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Texture Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_UP, Key_UP);
    auxKeyFunc(AUX_DOWN, Key_DOWN);
    auxKeyFunc(AUX_T, Key_T);
    auxKeyFunc(AUX_t, Key_t);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_0, Key_0);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxKeyFunc(AUX_4, Key_4);
    auxKeyFunc(AUX_5, Key_5);
    auxKeyFunc(AUX_6, Key_6);
    auxKeyFunc(AUX_7, Key_7);
    auxMainLoop(Draw);
}


TTRI.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gl\glaux.h>

#define SOLID 1
#define LINE 2
#define POINT 3

static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_LEFT(void );
static void CALLBACK Key_RIGHT(void );
static void CALLBACK Key_Z(void );
static void CALLBACK Key_z(void );
static void CALLBACK Key_1(void );
static void CALLBACK Key_2(void );
static void CALLBACK Key_3(void );
static void CALLBACK Key_4(void );
static void CALLBACK Key_5(void );
static void CALLBACK Key_6(void );
static void CALLBACK Key_7(void );
static void CALLBACK Key_8(void );
static void CALLBACK Key_9(void );
static void CALLBACK Key_v(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_h(void );
static void CALLBACK Key_o(void );
static void CALLBACK Key_m(void );
static void CALLBACK Key_0(void );
static void BeginPrim(void );
static void EndPrim(void );
static void CALLBACK Draw(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer, windType;
GLint windW, windH;

GLenum dithering = GL_TRUE;
GLenum showVerticies = GL_TRUE;
GLenum hideBottomTriangle = GL_FALSE;
GLenum outline = GL_TRUE;
GLenum culling = GL_FALSE;
GLenum winding = GL_FALSE;
GLenum face = GL_FALSE;
GLenum state = SOLID;
GLenum aaMode = GL_FALSE;
GLenum shade = GL_TRUE;

GLint color1, color2, color3;

float zRotation = 90.0;
float zoom = 1.0;

float boxA[3] = {-100, -100, 0};
float boxB[3] = { 100, -100, 0};
float boxC[3] = { 100,  100, 0};
float boxD[3] = {-100,  100, 0};

float p0[3] = {-125,-80, 0};
float p1[3] = {-125, 80, 0};
float p2[3] = { 172,  0, 0};

static void Init(void)
{
    float r, g, b;
    float percent1, percent2;
    GLint i, j;

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glLineStipple(1, 0xF0F0);

    glEnable(GL_SCISSOR_TEST);

    if (!rgb) {
    for (j = 0; j <= 12; j++) {
        if (j <= 6) {
        percent1 = j / 6.0;
        r = 1.0 - 0.8 * percent1;
        g = 0.2 + 0.8 * percent1;
        b = 0.2;
        } else {
        percent1 = (j - 6) / 6.0;
        r = 0.2;
        g = 1.0 - 0.8 * percent1;
        b = 0.2 + 0.8 * percent1;
        }
        auxSetOneColor(j+18, r, g, b);
        for (i = 0; i < 16; i++) {
        percent2 = i / 15.0;
        auxSetOneColor(j*16+1+32, r*percent2, g*percent2, b*percent2);
        }
    }
    color1 = 18;
    color2 = 24;
    color3 = 30;
    }
}

static void CALLBACK Reshape(int width, int height)
{

    windW = (GLint)width;
    windH = (GLint)height;
}

static void CALLBACK Key_LEFT(void)
{
    zRotation += 0.5;
}

static void CALLBACK Key_RIGHT(void)
{
    zRotation -= 0.5;
}

static void CALLBACK Key_Z(void)
{
    zoom *= 0.75;
}

static void CALLBACK Key_z(void)
{
    zoom /= 0.75;
    if (zoom > 10) {
        zoom = 10;
    }
}

static void CALLBACK Key_1(void)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
}

static void CALLBACK Key_2(void)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

static void CALLBACK Key_3(void)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

static void CALLBACK Key_4(void)
{
    state = POINT;
}

static void CALLBACK Key_5(void)
{
    state = LINE;
}

static void CALLBACK Key_6(void)
{
    state = SOLID;
}

static void CALLBACK Key_7(void)
{
    culling = !culling;
}

static void CALLBACK Key_8(void)
{
    winding = !winding;
}

static void CALLBACK Key_9(void)
{
    face = !face;
}

static void CALLBACK Key_v(void)
{
    showVerticies = !showVerticies;
}

static void CALLBACK Key_s(void)
{
    shade = !shade;
    (shade) ? glShadeModel(GL_SMOOTH) : glShadeModel(GL_FLAT);
}

static void CALLBACK Key_h(void)
{
    hideBottomTriangle = !hideBottomTriangle;
}

static void CALLBACK Key_o(void)
{
    outline = !outline;
}

static void CALLBACK Key_m(void)
{
    dithering = !dithering;
}

static void CALLBACK Key_0(void)
{
    aaMode = !aaMode;
    if (aaMode) {
        glEnable(GL_POLYGON_SMOOTH);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        if (!rgb) {
        color1 = 32;
        color2 = 128;
        color3 = 224;
        }
    } else {
        glDisable(GL_POLYGON_SMOOTH);
        glDisable(GL_BLEND);
        if (!rgb) {
        color1 = 18;
        color2 = 24;
        color3 = 30;
        }
    }
}

static void BeginPrim(void)
{

    switch (state) {
      case SOLID:
    glBegin(GL_POLYGON);
    break;
      case LINE:
    glBegin(GL_LINE_LOOP);
    break;
      case POINT:
    glBegin(GL_POINTS);
    break;
    }
}

static void EndPrim(void)
{

    glEnd();
}

static void CALLBACK Draw(void)
{
    float scaleX, scaleY;

    glViewport(0, 0, windW, windH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);

    glScissor(0, 0, windW, windH);

    (culling) ? glEnable(GL_CULL_FACE) : glDisable(GL_CULL_FACE);
    (winding) ? glFrontFace(GL_CCW) : glFrontFace(GL_CW);
    (face) ? glCullFace(GL_FRONT) : glCullFace(GL_BACK);

    (dithering) ? glEnable(GL_DITHER) : glDisable(GL_DITHER);

    glClear(GL_COLOR_BUFFER_BIT);

    AUX_SETCOLOR(windType, AUX_GREEN);
    glBegin(GL_LINE_LOOP);
    glVertex3fv(boxA);
    glVertex3fv(boxB);
    glVertex3fv(boxC);
    glVertex3fv(boxD);
    glEnd();

    if (!hideBottomTriangle) {
    glPushMatrix();

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0, 0, 1);

    AUX_SETCOLOR(windType, AUX_BLUE);
    BeginPrim();
        glVertex3fv(p0);
        glVertex3fv(p1);
        glVertex3fv(p2);
    EndPrim();

    if (showVerticies) {
        (rgb) ? glColor3fv(auxRGBMap[AUX_RED]) : glIndexf(color1);
        glRectf(p0[0]-2, p0[1]-2, p0[0]+2, p0[1]+2);
        (rgb) ? glColor3fv(auxRGBMap[AUX_GREEN]) : glIndexf(color2);
        glRectf(p1[0]-2, p1[1]-2, p1[0]+2, p1[1]+2);
        (rgb) ? glColor3fv(auxRGBMap[AUX_BLUE]) : glIndexf(color3);
        glRectf(p2[0]-2, p2[1]-2, p2[0]+2, p2[1]+2);
    }

    glPopMatrix();
    }

    scaleX = (float)(windW - 20) / 2 / 175 * (175 - 100) + 10;
    scaleY = (float)(windH - 20) / 2 / 175 * (175 - 100) + 10;

    glViewport(scaleX, scaleY, windW-2*scaleX, windH-2*scaleY);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-100, 100, -100, 100);
    glMatrixMode(GL_MODELVIEW);

    glScissor(scaleX, scaleY, windW-2*scaleX, windH-2*scaleY);

    glPushMatrix();

    glScalef(zoom, zoom, zoom);
    glRotatef(zRotation, 0,0,1);

    glPointSize(10);
    glLineWidth(5);
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_STIPPLE);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    AUX_SETCOLOR(windType, AUX_RED);
    BeginPrim();
    (rgb) ? glColor3fv(auxRGBMap[AUX_RED]) : glIndexf(color1);
    glVertex3fv(p0);
    (rgb) ? glColor3fv(auxRGBMap[AUX_GREEN]) : glIndexf(color2);
    glVertex3fv(p1);
    (rgb) ? glColor3fv(auxRGBMap[AUX_BLUE]) : glIndexf(color3);
    glVertex3fv(p2);
    EndPrim();

    glPointSize(1);
    glLineWidth(1);
    glDisable(GL_POINT_SMOOTH);
    glDisable(GL_LINE_STIPPLE);
    glBlendFunc(GL_ONE, GL_ZERO);

    if (outline) {
    AUX_SETCOLOR(windType, AUX_WHITE);
    glBegin(GL_LINE_LOOP);
        glVertex3fv(p0);
        glVertex3fv(p1);
        glVertex3fv(p2);
    glEnd();
    }

    glPopMatrix();

    glFlush();

    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    windW = 600;
    windH = 300;
    auxInitPosition(0, 0, windW, windH);

    windType |= (rgb) ? AUX_RGB : AUX_INDEX;
    windType |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(windType);

    if (auxInitWindow("Triangle Test") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_LEFT, Key_LEFT);
    auxKeyFunc(AUX_RIGHT, Key_RIGHT);
    auxKeyFunc(AUX_Z, Key_Z);
    auxKeyFunc(AUX_z, Key_z);
    auxKeyFunc(AUX_1, Key_1);
    auxKeyFunc(AUX_2, Key_2);
    auxKeyFunc(AUX_3, Key_3);
    auxKeyFunc(AUX_4, Key_4);
    auxKeyFunc(AUX_5, Key_5);
    auxKeyFunc(AUX_6, Key_6);
    auxKeyFunc(AUX_7, Key_7);
    auxKeyFunc(AUX_8, Key_8);
    auxKeyFunc(AUX_9, Key_9);
    auxKeyFunc(AUX_v, Key_v);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_h, Key_h);
    auxKeyFunc(AUX_o, Key_o);
    auxKeyFunc(AUX_m, Key_m);
    auxKeyFunc(AUX_0, Key_0);
    auxMainLoop(Draw);
}


TWAVE.C

/*
 * (c) Copyright 1993, Silicon Graphics, Inc.
 *               1993-1995 Microsoft Corporation
 *
 * ALL RIGHTS RESERVED
 *
 * Please refer to OpenGL/readme.txt for additional information
 *
 */

#include "glos.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <gl\glaux.h>

#define PI 3.14159265358979323846

#define GETCOORD(frame, x, y) (&(theMesh.coords[frame*theMesh.numCoords+(x)+(y)*(theMesh.widthX+1)]))
#define GETFACET(frame, x, y) (&(theMesh.facets[frame*theMesh.numFacets+(x)+(y)*theMesh.widthX]))

static void CALLBACK Animate(void );
static void SetColorMap(void );
static void InitMesh(void );
static void InitMaterials(void );
static void InitTexture(void );
static void Init(void );
static void CALLBACK Reshape(int width,int height);
static void CALLBACK Key_c(void );
static void CALLBACK Key_s(void );
static void CALLBACK Key_l(void );
static void CALLBACK Key_d(void );
static void CALLBACK Key_SPACE(void );
static void CALLBACK Key_n(void );
static void CALLBACK Key_a(void );
static GLenum Args(int argc,char **argv );

GLenum rgb, doubleBuffer;

GLint colorIndexes1[3];
GLint colorIndexes2[3];
GLenum clearMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;

GLenum smooth = GL_TRUE;
GLenum lighting = GL_TRUE;
GLenum depth = GL_TRUE;
GLenum stepMode = GL_FALSE;
GLenum spinMode = GL_FALSE;
GLint contouring = 0;

GLint widthX, widthY;
GLint checkerSize;
float height;

GLint frames, curFrame = 0, nextFrame = 0;

struct facet {
    float color[3];
    float normal[3];
};
struct coord {
    float vertex[3];
    float normal[3];
};
struct mesh {
    GLint widthX, widthY;
    GLint numFacets;
    GLint numCoords;
    GLint frames;
    struct coord *coords;
    struct facet *facets;
} theMesh;

GLubyte contourTexture1[] = {
    255, 255, 255, 255,
    255, 255, 255, 255,
    255, 255, 255, 255,
    127, 127, 127, 127,
};
GLubyte contourTexture2[] = {
    255, 255, 255, 255,
    255, 127, 127, 127,
    255, 127, 127, 127,
    255, 127, 127, 127,
};

static void CALLBACK Animate(void)
{
    struct coord *coord;
    struct facet *facet;
    float *lastColor;
    float *thisColor;
    GLint i, j;

    glClear(clearMask);

    if (nextFrame || !stepMode) {
    curFrame++;
    }
    if (curFrame >= theMesh.frames) {
    curFrame = 0;
    }

    if ((nextFrame || !stepMode) && spinMode) {
    glRotatef(5.0, 0.0, 0.0, 1.0);
    }
    nextFrame = 0;

    for (i = 0; i < theMesh.widthX; i++) {
    glBegin(GL_QUAD_STRIP);
    lastColor = NULL;
    for (j = 0; j < theMesh.widthY; j++) {
        facet = GETFACET(curFrame, i, j);
        if (!smooth && lighting) {
        glNormal3fv(facet->normal);
        }
        if (lighting) {
        if (rgb) {
            thisColor = facet->color;
            glColor3fv(facet->color);
        } else {
            thisColor = facet->color;
            glMaterialfv(GL_FRONT_AND_BACK, GL_COLOR_INDEXES,
                 facet->color);
        }
        } else {
        if (rgb) {
            thisColor = facet->color;
            glColor3fv(facet->color);
        } else {
            thisColor = facet->color;
            glIndexf(facet->color[1]);
        }
        }

        if (!lastColor || (thisColor[0] != lastColor[0] && smooth)) {
        if (lastColor) {
            glEnd();
            glBegin(GL_QUAD_STRIP);
        }
        coord = GETCOORD(curFrame, i, j);
        if (smooth && lighting) {
            glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        coord = GETCOORD(curFrame, i+1, j);
        if (smooth && lighting) {
            glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);
        }

        coord = GETCOORD(curFrame, i, j+1);
        if (smooth && lighting) {
        glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        coord = GETCOORD(curFrame, i+1, j+1);
        if (smooth && lighting) {
        glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        lastColor = thisColor;
    }
    glEnd();
    }

    glFlush();
    if (doubleBuffer) {
    auxSwapBuffers();
    }
}

static void SetColorMap(void)
{
    static float green[3] = {0.2, 1.0, 0.2};
    static float red[3] = {1.0, 0.2, 0.2};
    float *color, percent;
    GLint *indexes, entries, i, j;

    entries = auxGetColorMapSize();

    colorIndexes1[0] = 1;
    colorIndexes1[1] = 1 + (GLint)((entries - 1) * 0.3);
    colorIndexes1[2] = (GLint)((entries - 1) * 0.5);
    colorIndexes2[0] = 1 + (GLint)((entries - 1) * 0.5);
    colorIndexes2[1] = 1 + (GLint)((entries - 1) * 0.8);
    colorIndexes2[2] = entries - 1;

    for (i = 0; i < 2; i++) {
    switch (i) {
      case 0:
        color = green;
        indexes = colorIndexes1;
        break;
      case 1:
        color = red;
        indexes = colorIndexes2;
        break;
    }

    for (j = indexes[0]; j < indexes[1]; j++) {
        percent = 0.2 + 0.8 * (j - indexes[0]) /
              (float)(indexes[1] - indexes[0]);
        auxSetOneColor(j, percent*color[0], percent*color[1],
               percent*color[2]);
    }
    for (j=indexes[1]; j<=indexes[2]; j++) {
        percent = (j - indexes[1]) / (float)(indexes[2] - indexes[1]);
        auxSetOneColor(j, percent*(1-color[0])+color[0],
               percent*(1-color[1])+color[1],
               percent*(1-color[2])+color[2]);
    }
    }
}

static void InitMesh(void)
{
    struct coord *coord;
    struct facet *facet;
    float dp1[3], dp2[3];
    float *pt1, *pt2, *pt3;
    float angle, d, x, y;
    GLint numFacets, numCoords, frameNum, i, j;

    theMesh.widthX = widthX;
    theMesh.widthY = widthY;
    theMesh.frames = frames;

    numFacets = widthX * widthY;
    numCoords = (widthX + 1) * (widthY + 1);

    theMesh.numCoords = numCoords;
    theMesh.numFacets = numFacets;

    theMesh.coords = (struct coord *)malloc(frames*numCoords*
                        sizeof(struct coord));
    theMesh.facets = (struct facet *)malloc(frames*numFacets*
                        sizeof(struct facet));
    if (theMesh.coords == NULL || theMesh.facets == NULL) {
    //printf("Out of memory.\n");
    auxQuit();
    }

    for (frameNum = 0; frameNum < frames; frameNum++) {
    for (i = 0; i <= widthX; i++) {
        x = i / (float)widthX;
        for (j = 0; j <= widthY; j++) {
        y = j / (float)widthY;

        d = sqrt(x*x+y*y);
        if (d == 0.0) {
            d = 0.0001;
        }
        angle = 2 * PI * d + (2 * PI / frames * frameNum);

        coord = GETCOORD(frameNum, i, j);

        coord->vertex[0] = x - 0.5;
        coord->vertex[1] = y - 0.5;
        coord->vertex[2] = (height - height * d) * cos(angle);

        coord->normal[0] = -(height / d) * x * ((1 - d) * 2 * PI *
                   sin(angle) + cos(angle));
        coord->normal[1] = -(height / d) * y * ((1 - d) * 2 * PI *
                   sin(angle) + cos(angle));
        coord->normal[2] = -1;

        d = 1.0 / sqrt(coord->normal[0]*coord->normal[0]+
                   coord->normal[1]*coord->normal[1]+1);
        coord->normal[0] *= d;
        coord->normal[1] *= d;
        coord->normal[2] *= d;
        }
    }
    for (i = 0; i < widthX; i++) {
        for (j = 0; j < widthY; j++) {
        facet = GETFACET(frameNum, i, j);
        if (((i/checkerSize)%2)^(j/checkerSize)%2) {
            if (rgb) {
            facet->color[0] = 1.0;
            facet->color[1] = 0.2;
            facet->color[2] = 0.2;
            } else {
            facet->color[0] = colorIndexes1[0];
            facet->color[1] = colorIndexes1[1];
            facet->color[2] = colorIndexes1[2];
            }
        } else {
            if (rgb) {
            facet->color[0] = 0.2;
            facet->color[1] = 1.0;
            facet->color[2] = 0.2;
            } else {
            facet->color[0] = colorIndexes2[0];
            facet->color[1] = colorIndexes2[1];
            facet->color[2] = colorIndexes2[2];
            }
        }
        pt1 = GETCOORD(frameNum, i, j)->vertex;
        pt2 = GETCOORD(frameNum, i, j+1)->vertex;
        pt3 = GETCOORD(frameNum, i+1, j+1)->vertex;

        dp1[0] = pt2[0] - pt1[0];
        dp1[1] = pt2[1] - pt1[1];
        dp1[2] = pt2[2] - pt1[2];

        dp2[0] = pt3[0] - pt2[0];
        dp2[1] = pt3[1] - pt2[1];
        dp2[2] = pt3[2] - pt2[2];

        facet->normal[0] = dp1[1] * dp2[2] - dp1[2] * dp2[1];
        facet->normal[1] = dp1[2] * dp2[0] - dp1[0] * dp2[2];
        facet->normal[2] = dp1[0] * dp2[1] - dp1[1] * dp2[0];

        d = 1.0 / sqrt(facet->normal[0]*facet->normal[0]+
                   facet->normal[1]*facet->normal[1]+
                   facet->normal[2]*facet->normal[2]);

        facet->normal[0] *= d;
        facet->normal[1] *= d;
        facet->normal[2] *= d;
        }
    }
    }
}

static void InitMaterials(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {60.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {60.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};

    glMatrixMode(GL_PROJECTION);
    gluPerspective(450, 1.0, 0.5, 10.0);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
    if (rgb) {
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
    }

    if (rgb) {
    glEnable(GL_COLOR_MATERIAL);
    } else {
    SetColorMap();
    }
}

static void InitTexture(void)
{

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glShadeModel(GL_SMOOTH);

    glFrontFace(GL_CW);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    InitMaterials();
    InitTexture();
    InitMesh();

    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0, 0.4, -1.8);
    glScalef(2.0, 2.0, 2.0);
    glRotatef(-35.0, 1.0, 0.0, 0.0);
    glRotatef(35.0, 0.0, 0.0, 1.0);
}

static void CALLBACK Reshape(int width, int height)
{

    glViewport(0, 0, (GLint)width, (GLint)height);
}

static void CALLBACK Key_c(void)
{
    contouring++;
    if (contouring == 1) {
        static GLfloat map[4] = {0, 0, 20, 0};

        glTexImage2D(GL_TEXTURE_2D, 0, 3, 4, 4, 0, GL_LUMINANCE,
             GL_UNSIGNED_BYTE, (GLvoid *)contourTexture1);
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_S, GL_OBJECT_PLANE, map);
        glTexGenfv(GL_T, GL_OBJECT_PLANE, map);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
    } else if (contouring == 2) {
        static GLfloat map[4] = {0, 0, 20, 0};

        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glPushMatrix();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTexGenfv(GL_S, GL_EYE_PLANE, map);
        glTexGenfv(GL_T, GL_EYE_PLANE, map);
        glPopMatrix();
    } else {
        contouring = 0;
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_2D);
    }
}

static void CALLBACK Key_s(void)
{
    smooth = !smooth;
    if (smooth) {
        glShadeModel(GL_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
    }
}

static void CALLBACK Key_l(void)
{
    lighting = !lighting;
    if (lighting) {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        if (rgb) {
        glEnable(GL_COLOR_MATERIAL);
        }
    } else {
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);
        if (rgb) {
        glDisable(GL_COLOR_MATERIAL);
        }
    }
}

static void CALLBACK Key_d(void)
{
    depth = !depth;
    if (depth) {
        glEnable(GL_DEPTH_TEST);
        clearMask |= GL_DEPTH_BUFFER_BIT;
    } else {
        glDisable(GL_DEPTH_TEST);
        clearMask &= ~GL_DEPTH_BUFFER_BIT;
    }
}

static void CALLBACK Key_SPACE(void)
{
    stepMode = !stepMode;

    if (stepMode) {
        auxIdleFunc(0);
/*      tkDisplayFunc(Animate); */
    } else {
        auxIdleFunc(Animate);
/*      tkDisplayFunc(0);*/
    }
}

static void CALLBACK Key_n(void)
{
    if (stepMode) {
        nextFrame = 1;
    }
}

static void CALLBACK Key_a(void)
{
    spinMode = !spinMode;
}

static GLenum Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_TRUE;

    frames = 10;
    widthX = 10;
    widthY = 10;
    checkerSize = 2;
    height = 0.2;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-grid") == 0) {
        if (i+2 >= argc || argv[i+1][0] == '-' || argv[i+2][0] == '-') {
        //printf("-grid (No numbers).\n");
        return GL_FALSE;
        } else {
        widthX = atoi(argv[++i]);
        widthY = atoi(argv[++i]);
        }
    } else if (strcmp(argv[i], "-size") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-checker (No number).\n");
        return GL_FALSE;
        } else {
        checkerSize = atoi(argv[++i]);
        }
    } else if (strcmp(argv[i], "-wave") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-wave (No number).\n");
        return GL_FALSE;
        } else {
        height = atof(argv[++i]);
        }
    } else if (strcmp(argv[i], "-frames") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        //printf("-frames (No number).\n");
        return GL_FALSE;
        } else {
        frames = atoi(argv[++i]);
        }
    } else {
        //printf("%s (Bad option).\n", argv[i]);
        return GL_FALSE;
    }
    }
    return GL_TRUE;
}

void main(int argc, char **argv)
{
    GLenum type;

    if (Args(argc, argv) == GL_FALSE) {
    auxQuit();
    }

    auxInitPosition(0, 0, 300, 300);

    type = AUX_DEPTH16;
    type |= (rgb) ? AUX_RGB : AUX_INDEX;
    type |= (doubleBuffer) ? AUX_DOUBLE : AUX_SINGLE;

    auxInitDisplayMode(type);

    if (auxInitWindow("Wave Demo") == GL_FALSE) {
    auxQuit();
    }

    Init();

    auxExposeFunc((AUXEXPOSEPROC)Reshape);
    auxReshapeFunc((AUXRESHAPEPROC)Reshape);
    auxKeyFunc(AUX_c, Key_c);
    auxKeyFunc(AUX_s, Key_s);
    auxKeyFunc(AUX_l, Key_l);
    auxKeyFunc(AUX_d, Key_d);
    auxKeyFunc(AUX_SPACE, Key_SPACE);
    auxKeyFunc(AUX_n, Key_n);
    auxKeyFunc(AUX_a, Key_a);
    auxIdleFunc(Animate);
    auxMainLoop(Animate);
}


README.TXT

Sample screen savers that use OpenGL.


GENDROP.C

/******************************Module*Header*******************************\
* Module Name: gendrop.c
*
* The Splash style of the 3D Flying Objects screen saver.
*
* Simulation of a drop of water falling into a pool of water.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
#include <GL\gl.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"
#include "mesh.h"

#define FLOAT_SMALL (1e-6)

#define DROPPREC   10

// Remember from pre-calc:
//      x = r cos th
//      y = r sin th
// to convert from polar to rect, and that
//      x = x' cos th - y' sin th
//      y = x' sin th + y' cos th
// to rotate axes.
//
// Also, note that the equation for a lemniscate is:
//      r = sqrt(sin 2*th)
//

static POINT3D *circle;
static POINT3D *drop;
static POINT3D *curves;
static MESH waterMesh;
static MESH waterInmesh;
static MESH waterOutmesh;
static MESH waterBorderMesh;
static MESH *drops;
static int iPrec;
static float fRadiusFact = 0.35f;

static GLfloat light0Pos[] = {100.0f, 100.0f, 100.0f, 0.0f};
static dropList[DROPPREC];

void genCurves()
{
    int i;
    double angle;
    double step = -PI / (float)(iPrec - 1);
    double start = PI / 2.0;
    double rotSin = sin(PI / 4.0);
    double rotCos = cos(PI / 4.0);
    double aFract = 0.0;
    double bFract = 1.0;
    double fractInc = 1.0 / (double)(iPrec - 1);
    POINT3D *pt = curves;

    for (i = 0, angle = start; i < iPrec; i++, angle += step) {
        circle[i].x = (float) (0.5 * cos(angle));
        circle[i].y = (float) (0.5 * sin(angle));
    }

    step = (-PI / 4.0) / (float)(iPrec - 1);
    start = PI / 4.0;

    for (i = 0, angle = start; i < iPrec; i++, angle += step) {
        double x, y, r;
        double xrot, yrot;
        double sinVal;

        sinVal = sin(2.0 * angle);
        if (sinVal < 0.0)
            sinVal = -sinVal;

        r = 1.5 * sqrt(sinVal);
        x = r * cos(angle);
        y = r * sin(angle);

        xrot = x * rotCos - y * rotSin;
        yrot = x * rotSin + y * rotCos - 1.0;

        drop[i].x = (float) xrot;
        drop[i].y = (float) yrot;
    }

    for (i = 0; i < DROPPREC; i++) {
        int j;

        for (j = 0; j < iPrec; j++, pt++) {
            pt->x = (float) (aFract * circle[j].x +
                             bFract * drop[j].x);

            pt->y = (float) (aFract * circle[j].y +
                             bFract * drop[j].y);

            pt->z = 0.0f;
        }
        aFract += fractInc;
        bFract -= fractInc;
    }
}

#define NORMS(x, y) waterMesh.norms[((x) * iPrec) + y]
#define BNORMS(x, y) waterBorderMesh.norms[((x) * iPrec) + y]
#define INGRID(x, y)  waterInmesh.pts[((x) * iPrec) + y]
#define OUTGRID(x, y)  waterOutmesh.pts[((x) * iPrec) + y]
#define GRID(x, y)  waterMesh.pts[((x) * iPrec) + y]
#define BGRID(x, y)  waterBorderMesh.pts[((x) * iPrec) + y]

void genWater(double freq, double damp, double mag, double w, double minr)
{
    int i;
    int j;
    double r;
    double theta;
    double thetaInc = (2.0 * PI) / (float)iPrec;
    double posInc = 1.0 / (float)iPrec;
    int facecount;
    double xCenter = 0.0;
    double zCenter = 0.0;
    POINT3D norm;
    static BOOL first = TRUE;

    if (first) {
        for (i = 0, r = 0.0; i < iPrec; i++, r += posInc) {
            for (j = 0, theta = 0.0; j < iPrec; j++, theta += thetaInc) {
                float x, z;
                float dx, dz;
                float rr;

                x = (float) cos(theta);
                z = (float) sin(theta);

                dx = x - (float) xCenter;
                dz = z - (float) zCenter;

                rr = (float) sqrt((dx * dx) + (dz * dz));
                dx /= rr;
                dz /= rr;
                dx *= i / (float)(iPrec - 1);
                dz *= i / (float)(iPrec - 1);
                GRID(i, j).x = dx + (float) xCenter;
                GRID(i, j).z = dz + (float) zCenter;

                INGRID(i, j).y = 0.0f;
                OUTGRID(i, j).y = 0.0f;
            }
        }
    }

    for (i = (iPrec - 1), r = 1.0; i >= 0; i--, r -= posInc) {
        float val;

        if (i == 0) {
            if (minr != 0.0)
                val = (float) (-mag * cos(w + (r * freq)) * exp((-damp * r)/2.0));
            else
                val =  INGRID(0, 0).y * 0.95f;
        } else
            val = OUTGRID(i - 1, 0).y * 0.95f;

        for (j = 0; j < iPrec; j++)
            OUTGRID(i, j).y = val;
    }

    for (i = 0, r = 0.0; i < iPrec; i++, r += posInc) {
        for (j = 0; j < iPrec; j++) {
            if (i == iPrec-1)
                INGRID(i, j).y = -OUTGRID(i, j).y;
            else
                INGRID(i, j).y = INGRID(i + 1, j).y * 0.95f;
        }
    }

    waterMesh.numFaces = 0;
    waterBorderMesh.numFaces = 0;

    for (i = 0; i < iPrec; i++) {
        for (j = 0; j < iPrec; j++) {
            NORMS(i, j).x = 0.0f;
            NORMS(i, j).y = 0.0f;
            NORMS(i, j).z = 0.0f;
        }
    }

    for (i = 0, r = 0.0; i < iPrec; i++, r += posInc) {
        for (j = 0, theta = 0.0; j < iPrec; j++, theta += thetaInc) {
            GRID(i, j).y = OUTGRID(i, j).y + INGRID(i, j).y;

            if (i == (iPrec - 1)) {
                GRID(i, j).y = 0.0f;

                BGRID(0, j).x = GRID(i, j).x;
                BGRID(0, j).z = GRID(i, j).z;
                BGRID(0, j).y = GRID(i, j).y;

                BGRID(1, j).x = GRID(i, j).x;
                BGRID(1, j).z = GRID(i, j).z;
                BGRID(1, j).y = -0.5f;
            }
        }
    }

    for (i = 0; i < 2; i++) {
        for (j = 0; j < iPrec; j++) {
            BNORMS(i, j).x = 0.0f;
            BNORMS(i, j).y = 0.0f;
            BNORMS(i, j).z = 0.0f;
        }
    }

    for (facecount = 0, i = 0; i < (iPrec - 1); i++) {
        for (j = 0; j < iPrec; j++) {
            int k, l;

            k = i + 1;

            if (j == (iPrec - 1))
                l = 0;
            else
                l = j + 1;

            ss_calcNorm(&norm, &GRID(k, j), &GRID(i, j), &GRID(i, l));

            if (norm.x > -FLOAT_SMALL && norm.x < FLOAT_SMALL &&
                norm.y > -FLOAT_SMALL && norm.y < FLOAT_SMALL &&
                norm.z > -FLOAT_SMALL && norm.z < FLOAT_SMALL)
                ss_calcNorm(&norm, &GRID(i, l), &GRID(k, l), &GRID(k, j));

            waterMesh.faces[facecount].material = 0;
            waterMesh.faces[facecount].norm = norm;

            NORMS(i, j).x += norm.x;
            NORMS(i, j).y += norm.y;
            NORMS(i, j).z += norm.z;

            NORMS(k, j).x += norm.x;
            NORMS(k, j).y += norm.y;
            NORMS(k, j).z += norm.z;

            NORMS(i, l).x += norm.x;
            NORMS(i, l).y += norm.y;
            NORMS(i, l).z += norm.z;

            NORMS(k, l).x += norm.x;
            NORMS(k, l).y += norm.y;
            NORMS(k, l).z += norm.z;

            waterMesh.faces[facecount].p[0] = (k * iPrec) + j;
            waterMesh.faces[facecount].p[1] = (i * iPrec) + j;
            waterMesh.faces[facecount].p[2] = (k * iPrec) + l;
            waterMesh.faces[facecount].p[3] = (i * iPrec) + l;
            waterMesh.numFaces++;
            facecount++;
        }
    }

    waterMesh.numPoints = iPrec * iPrec;

    for (facecount = 0, i = 0; i < 1; i++) {
        for (j = 0; j < iPrec; j++) {
            int k, l;

            k = i + 1;

            if (j == (iPrec - 1))
                l = 0;
            else
                l = j + 1;

            ss_calcNorm(&norm, &BGRID(k, j), &BGRID(i, j), &BGRID(i, l));

            waterBorderMesh.faces[facecount].material = 0;
            waterBorderMesh.faces[facecount].norm = norm;

// Setting SMOOTH_BORDER will render the border (the sides of the "pool")
// with smooth shading.  This effect is good at higher tesselations, but
// doesn't really look that good for low tesselations.
//
// A possible enhancement for later: use smooth shading if tesselation
// exceeds some threshold.  Should we just pick some arbitrary threshold?
// Make it a setup option?  Things look pretty good now, so don't bother?

#if SMOOTH_BORDER
            BNORMS(i, j).x += norm.x;
            BNORMS(i, j).y += norm.y;
            BNORMS(i, j).z += norm.z;

            if (i) {
                BNORMS(i-1, j).x += norm.x;
                BNORMS(i-1, j).y += norm.y;
                BNORMS(i-1, j).z += norm.z;
            }
            if (j) {
                BNORMS(i, j-1).x += norm.x;
                BNORMS(i, j-1).y += norm.y;
                BNORMS(i, j-1).z += norm.z;
            }

            BNORMS(k, j).x += norm.x;
            BNORMS(k, j).y += norm.y;
            BNORMS(k, j).z += norm.z;

            BNORMS(i, l).x += norm.x;
            BNORMS(i, l).y += norm.y;
            BNORMS(i, l).z += norm.z;
#else
            BNORMS(i, j) = norm;

            if (i)
                BNORMS(i-1, j) = norm;
            if (j)
                BNORMS(i, j-1) = norm;

            BNORMS(k, j) = norm;
            BNORMS(i, l) = norm;
#endif

            waterBorderMesh.faces[facecount].p[0] = (k * iPrec) + j;
            waterBorderMesh.faces[facecount].p[1] = (i * iPrec) + j;
            waterBorderMesh.faces[facecount].p[2] = (k * iPrec) + l;
            waterBorderMesh.faces[facecount].p[3] = (i * iPrec) + l;
            waterBorderMesh.numFaces++;
            facecount++;
        }
    }
    waterBorderMesh.numPoints = 2 * iPrec;

    ss_normalizeNorms(waterBorderMesh.norms, waterBorderMesh.numPoints);
    ss_normalizeNorms(waterMesh.norms, waterMesh.numPoints);

    first = FALSE;
}

void initDropScene()
{
    int i;

    iPrec = (int)(fTesselFact * 10.5);
    if (iPrec < 4)
        iPrec = 4;

    if (fTesselFact > fRadiusFact)
        fRadiusFact = fTesselFact;

    circle = (POINT3D *)SaverAlloc(iPrec * sizeof(POINT3D));
    drop = (POINT3D *)SaverAlloc(iPrec * sizeof(POINT3D));
    curves = (POINT3D *)SaverAlloc(DROPPREC * iPrec * sizeof(POINT3D));
    drops = (MESH *)SaverAlloc(DROPPREC * sizeof(MESH));

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.5, 1.5, -1.5, 1.5, 0.0, 3.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0f, -1.5f);

    glDisable(GL_CULL_FACE);

    newMesh(&waterInmesh, iPrec * iPrec, iPrec * iPrec + iPrec);
    newMesh(&waterOutmesh, iPrec * iPrec, iPrec * iPrec + iPrec);
    newMesh(&waterMesh, iPrec * iPrec, iPrec * iPrec + iPrec);
    newMesh(&waterBorderMesh, iPrec, 2 * iPrec);
    genCurves();

    for (i = 0; i < DROPPREC; i++)
        revolveSurface(&drops[i], &curves[i * iPrec], iPrec);
    for (i = 0; i < DROPPREC; i++) {
        GLuint id = 0x10 + i;
        dropList[i] = id;
        MakeList(id, &drops[i]);
    }
    for (i = 0; i < DROPPREC; i++) {
        delMesh(&drops[i]);
    }

    SaverFree(circle);
    SaverFree(drop);
    SaverFree(curves);
    SaverFree(drops);
}

void delDropScene()
{
    delMesh(&waterMesh);
    delMesh(&waterInmesh);
    delMesh(&waterOutmesh);
    delMesh(&waterBorderMesh);
}

void updateDropScene(int flags)
{
    static double zrot = 0.0;
    static double yrot = 0.0;
    static double mxrot = 0.0;
    static double myrot = 0.0;
    static double mzrot = 0.0;
    static double mxrotInc = 0.0;
    static double myrotInc = 0.1;
    static double zrotInc = 3.0;
    static double yrotInc = 1.5;
    static double mzrotInc = 0.0;
    static double ypos = 1.0;
    static int dropnum = 0;
    static double radius = 0.3;
    static double damp = 1.0;
    static double mag = 0.0;
    static double w = 1.0;
    static double freq = 1.0;
    static double dist;
    static double minr = 0.0;
    static int h = 0;
    RGBA color;

    glPushMatrix();

    zrot += zrotInc;
    if (zrot >= 45.0) {
        zrot = 45.0;
        zrotInc = -(2.0 + ((float)rand() / (float)RAND_MAX) * 3.0);
    } else if (zrot <= -45.0) {
        zrot = -45.0;
        zrotInc = 2.0 + ((float)rand() / (float)RAND_MAX) * 3.0;
    }

    yrot += yrotInc;
    if (yrot >= 10.0) {
        yrot = 10.0;
        yrotInc = -(1.0 + ((float)rand() / (float)RAND_MAX) * 2.0);
    } else if (zrot <= -10.0) {
        yrot = -10.0;
        yrotInc = 1.0 + ((float)rand() / (float)RAND_MAX) * 2.0;
    }

    if ((ypos + 0.5 < -radius) && (mag < 0.05)) {
        radius = (float)rand() / (6.0 * (float)RAND_MAX) + 0.1;
        ypos = 1.0;
        dropnum = 0;
    }

    dist = (ypos + 0.5);

    if ((dist > -radius / 2.0) && (dist < radius / 2.0)) {
        if (dist <= 0.0)
            dist = radius / 2.0;
        else
            dist = (radius / 2.0) - dist;
        freq = (0.25 * PI) / dist;
        if (freq < 0.2)
            freq = 0.2;

        minr = radius;

        damp = 20.0;
        mag = (0.35 / fRadiusFact) + 0.2 * dist;

        w = 0;
    } else {
        minr -= 0.05;
        if (minr < 0.0)
            minr = 0.0;

        mag = mag * 0.95;
        if (minr == 0.0) {
            w -= (PI / 6.0);
            mag *= 0.75;
        }
        if (damp > 0.0)
            damp -= 1.0;
    }

    genWater(freq, damp, mag, w, minr);

    glRotatef((GLfloat) zrot, 0.0f, 0.0f, 1.0f);
    glRotatef(30.0f, 1.0f, 0.0f, 0.0f);

    glPushMatrix();
    glTranslatef(0.0f, -0.5f, 0.0f);
    glRotatef((GLfloat) (myrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);

    if (bColorCycle) {
        ss_HsvToRgb((float)h, 1.0f, 1.0f, &color );

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                     (GLfloat *) &color);
        h++;
        h %= 360;
    } else {
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                     (GLfloat *) &Material[6].kd);
    }

    updateObject(&waterMesh, bSmoothShading);

    if (bSmoothShading)
        glShadeModel(GL_FLAT);

    if (!bColorCycle)
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                     (GLfloat *) &Material[2].kd);

    updateObject2(&waterBorderMesh, FALSE);
    glPopMatrix();

    if (bSmoothShading)
        glShadeModel(GL_SMOOTH);

    if (dist > -radius) {

        if (!bColorCycle)
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                         (GLfloat *) &Material[6].kd);
        glTranslatef(0.0f, (GLfloat) ypos, 0.0f);
        glScalef((GLfloat) radius, (GLfloat) radius, (GLfloat) radius);
        glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
        glEnable(GL_NORMALIZE);
        glCallList(dropList[dropnum]);
        glDisable(GL_NORMALIZE);

    }

    myrot += myrotInc;

    ypos -= 0.08;
    dropnum = (int) ((DROPPREC - 1) - (ypos * (DROPPREC - 1)));
    if (dropnum > (DROPPREC - 1))
        dropnum = DROPPREC - 1;

    glPopMatrix();
}


GENEXPLD.C

/******************************Module*Header*******************************\
* Module Name: genexpld.c
*
* The Explode style of the 3D Flying Objects screen saver.
*
* Simulation of a sphere that occasionally explodes.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <GL\gl.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"

#define RADIUS         0.3
#define STEPS    30
#define MAXPREC20

static MATRIX *faceMat;
static float *xstep;
static float *ystep;
static float *zstep;
static float *xrot;
static float *yrot;
static float *zrot;
static MESH explodeMesh;
static int iPrec = 10;

static BOOL bOpenGL11;

// Data type accepted by glInterleavedArrays
typedef struct _POINT_N3F_V3F {
    POINT3D normal;
    POINT3D vertex;
} POINT_N3F_V3F;

static POINT_N3F_V3F *pN3V3;

static GLfloat matl1Diffuse[] = {1.0f, 0.8f, 0.0f, 1.0f};
static GLfloat matl2Diffuse[] = {0.8f, 0.8f, 0.8f, 1.0f};
static GLfloat matlSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f};
static GLfloat light0Pos[] = {100.0f, 100.0f, 100.0f, 0.0f};

void genExplode()
{
    int i;
    POINT3D circle[MAXPREC+1];
    double angle;
    double step = -PI / (float)(iPrec - 1);
    double start = PI / 2.0;

    for (i = 0, angle = start; i < iPrec; i++, angle += step) {
        circle[i].x = (float) (RADIUS * cos(angle));
        circle[i].y = (float) (RADIUS * sin(angle));
        circle[i].z = 0.0f;
    }

    revolveSurface(&explodeMesh, circle, iPrec);

    for (i = 0; i < explodeMesh.numFaces; i++) {
        ss_matrixIdent(&faceMat[i]);
        xstep[i] = (float)(((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));
        ystep[i] = (float)(((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));
        zstep[i] = (float)(((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));
        xrot[i] = 0.0f;
        yrot[i] = 0.0f;
        zrot[i] = 0.0f;
    }
}

void initExplodeScene()
{
    iPrec = (int)(fTesselFact * 10.5);
    if (iPrec < 5)
        iPrec = 5;
    if (iPrec > MAXPREC)
        iPrec = MAXPREC;

    faceMat = (MATRIX *)SaverAlloc((iPrec * iPrec) *
     (4 * 4 * sizeof(float)));
    xstep = SaverAlloc(iPrec * iPrec * sizeof(float));
    ystep = SaverAlloc(iPrec * iPrec * sizeof(float));
    zstep = SaverAlloc(iPrec * iPrec * sizeof(float));
    xrot = SaverAlloc(iPrec * iPrec * sizeof(float));
    yrot = SaverAlloc(iPrec * iPrec * sizeof(float));
    zrot = SaverAlloc(iPrec * iPrec * sizeof(float));

    genExplode();

    // Find out the OpenGL version that we are running on.
    bOpenGL11 = ss_fOnGL11();

    // Setup the data arrays.
    pN3V3 = SaverAlloc(explodeMesh.numFaces * 4 * sizeof(POINT_N3F_V3F));

    // If we are running on OpenGL 1.1, use the new vertex array functions.
    if (bOpenGL11) {
        glInterleavedArrays(GL_N3F_V3F, 0, pN3V3);
    }

    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, matl1Diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, matlSpecular);
    glMaterialf(GL_FRONT, GL_SHININESS, 100.0f);

    glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, matl2Diffuse);
    glMaterialfv(GL_BACK, GL_SPECULAR, matlSpecular);
    glMaterialf(GL_BACK, GL_SHININESS, 60.0f);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-0.33, 0.33, -0.33, 0.33, 0.3, 3.0);

    glTranslatef(0.0f, 0.0f, -1.5f);
}

void delExplodeScene()
{
    delMesh(&explodeMesh);

    SaverFree(faceMat);
    SaverFree(xstep);
    SaverFree(ystep);
    SaverFree(zstep);
    SaverFree(xrot);
    SaverFree(yrot);
    SaverFree(zrot);
    SaverFree(pN3V3);
}

void updateExplodeScene(int flags)
{
    static double mxrot = 0.0;
    static double myrot = 0.0;
    static double mzrot = 0.0;
    static double mxrotInc = 0.0;
    static double myrotInc = 0.1;
    static double mzrotInc = 0.0;
    static float maxR;
    static float r = 0.0f;
    static float rotZ = 0.0f;
    static int count = 0;
    static int direction = 1;
    static int restCount = 0;
    static float lightSpin = 0.0f;
    static float spinDelta = 5.0f;
    static int h = 0;
    static RGBA color;
    int i;
    MFACE *faces;
    POINT_N3F_V3F *pn3v3;

    if( gbBounce ) {
        // floating window bounced off an edge
        if (mxrotInc) {
            mxrotInc = 0.0;
            myrotInc = 0.1;
        } else if (myrotInc) {
            myrotInc = 0.0;
            mzrotInc = 0.1;
        } else if (mzrotInc) {
            mzrotInc = 0.0;
            mxrotInc = 0.1;
        }
        gbBounce = FALSE;
    }

    mxrot += mxrotInc;
    myrot += myrotInc;
    mzrot += mzrotInc;

    if (bColorCycle || h == 0) {
        ss_HsvToRgb((float)h, 1.0f, 1.0f, &color);

        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &color);

        h++;
        h %= 360;
    }

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glRotatef(-lightSpin, 0.0f, 1.0f, 0.0f);
    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
    lightSpin += spinDelta;
    if ((lightSpin > 90.0) || (lightSpin < 0.0))
        spinDelta = -spinDelta;
    glPopMatrix();

    if (!bOpenGL11) {
        glBegin(GL_QUADS);
    }

    for(
        i = 0, faces = explodeMesh.faces, pn3v3 = pN3V3;
        i < explodeMesh.numFaces;
i++, faces++, pn3v3 += 4
       ) {
        int a, b, c, d;
        int j;
        POINT3D vector;

        ss_matrixIdent(&faceMat[i]);
        ss_matrixRotate(&faceMat[i], xrot[i], yrot[i], zrot[i]);

        if (restCount)
            ;
        else {
            xrot[i] += (xstep[i]);
            yrot[i] += (ystep[i]);
            zrot[i] += (zstep[i]);
        }

        a = faces->p[0];
        b = faces->p[1];
        c = faces->p[3];
        d = faces->p[2];

        memcpy(&pn3v3[0].vertex, (explodeMesh.pts + a), sizeof(POINT3D));
        memcpy(&pn3v3[1].vertex, (explodeMesh.pts + b), sizeof(POINT3D));
        memcpy(&pn3v3[2].vertex, (explodeMesh.pts + c), sizeof(POINT3D));
        memcpy(&pn3v3[3].vertex, (explodeMesh.pts + d), sizeof(POINT3D));

        vector.x = pn3v3[0].vertex.x;
        vector.y = pn3v3[0].vertex.y;
        vector.z = pn3v3[0].vertex.z;

        for (j = 0; j < 4; j++) {
            pn3v3[j].vertex.x -= vector.x;
            pn3v3[j].vertex.y -= vector.y;
            pn3v3[j].vertex.z -= vector.z;
            ss_xformPoint((POINT3D *)&pn3v3[j].vertex, (POINT3D *)&pn3v3[j].vertex, &faceMat[i]);
            pn3v3[j].vertex.x += vector.x + (vector.x * r);
            pn3v3[j].vertex.y += vector.y + (vector.y * r);
            pn3v3[j].vertex.z += vector.z + (vector.z * r);
        }
        if (bSmoothShading) {
            memcpy(&pn3v3[0].normal, (explodeMesh.norms + a), sizeof(POINT3D));
            memcpy(&pn3v3[1].normal, (explodeMesh.norms + b), sizeof(POINT3D));
            memcpy(&pn3v3[2].normal, (explodeMesh.norms + c), sizeof(POINT3D));
            memcpy(&pn3v3[3].normal, (explodeMesh.norms + d), sizeof(POINT3D));

            for (j = 0; j < 4; j++)
                ss_xformNorm((POINT3D *)&pn3v3[j].normal, (POINT3D *)&pn3v3[j].normal, &faceMat[i]);
        } else {
            memcpy(&pn3v3[0].normal, &faces->norm, sizeof(POINT3D));
            ss_xformNorm((POINT3D *)&pn3v3[0].normal, (POINT3D *)&pn3v3[0].normal, &faceMat[i]);
            memcpy(&pn3v3[1].normal, &pn3v3[0].normal, sizeof(POINT3D));
            memcpy(&pn3v3[2].normal, &pn3v3[0].normal, sizeof(POINT3D));
            memcpy(&pn3v3[3].normal, &pn3v3[0].normal, sizeof(POINT3D));
        }

        if (!bOpenGL11) {
            if (bSmoothShading) {
                glNormal3fv((GLfloat *)&pn3v3[0].normal);
                glVertex3fv((GLfloat *)&pn3v3[0].vertex);
                glNormal3fv((GLfloat *)&pn3v3[1].normal);
                glVertex3fv((GLfloat *)&pn3v3[1].vertex);
                glNormal3fv((GLfloat *)&pn3v3[2].normal);
                glVertex3fv((GLfloat *)&pn3v3[2].vertex);
                glNormal3fv((GLfloat *)&pn3v3[3].normal);
                glVertex3fv((GLfloat *)&pn3v3[3].vertex);
            } else {
                glNormal3fv((GLfloat *)&pn3v3[0].normal);
                glVertex3fv((GLfloat *)&pn3v3[0].vertex);
                glVertex3fv((GLfloat *)&pn3v3[1].vertex);
                glVertex3fv((GLfloat *)&pn3v3[2].vertex);
                glVertex3fv((GLfloat *)&pn3v3[3].vertex);
            }
        }
    }

    if (bOpenGL11) {
        glDrawArrays(GL_QUADS, 0, explodeMesh.numFaces * 4);
    } else {
        glEnd();
    }

    if (restCount) {
        restCount--;
        goto resting;
    }

    if (direction) {
        maxR = r;
        r += (float) (0.3 * pow((double)(STEPS - count) / (double)STEPS, 4.0));
    } else {
        r -= (float) (maxR / (double)(STEPS));
    }

    count++;
    if (count > STEPS) {
        direction ^= 1;
        count = 0;

        if (direction == 1) {
            restCount = 10;
            r = 0.0f;

            for (i = 0; i < explodeMesh.numFaces; i++) {
                ss_matrixIdent(&faceMat[i]);
                xstep[i] = (float) (((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));
                ystep[i] = (float) (((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));
                zstep[i] = (float) (((float)(rand() & 0x3) * PI) / ((float)STEPS + 1.0));

                xrot[i] = 0.0f;
                yrot[i] = 0.0f;
                zrot[i] = 0.0f;
            }
        }
    }

resting:
    ;
}


GENLEM.C

/******************************Module*Header*******************************\
* Module Name: genlem.c
*
* The Twist style of the 3D Flying Objects screen saver.
*
* Solid model of a 3D lemniscate.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
#include <GL\gl.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"
#include "mesh.h"

#define ROT_PREC    10
#define NORMS(x, y) lemMesh.norms[((x) * iPrec) + y]
#define GRID(x, y)  lemMesh.pts[((x) * iPrec) + y]

static MESH lemMesh;
static POINT3D basis[ROT_PREC];
static double zrot = 0.2;
static int iPrec = 32;
static double *lemX;
static double *lemY;
static double *lemXT;
static double *lemYT;

static void getLem(double index, double max, double *angle, double *r)
{
    double a, sina;

    a = (index * PI) / (max - 1.0);
    if (a >= PI)
        a -= PI;
    if (a > PI / 2.0) {
        *angle = (2.0 * PI) - a;
        sina = sin( 2.0 * *angle );
        if( sina < 0.0 )
            sina = 0.0; // protect against sqrt fpe
        *r = 0.5 * sqrt(sina);
    } else {
        *angle = a;
        sina = sin( 2.0 * *angle );
        if( sina < 0.0 )
            sina = 0.0;
        *r = 0.5 * sqrt(sina);
    }
}

static void initLemCoords(int iMax)
{
    int i;
    double max = (double)iMax;
    double angle;
    double r;

    for (i = 0; i < iMax; i++) {
        getLem((double)i, (double)iPrec, &angle, &r);
        lemX[i] = r * cos(angle);
        lemY[i] = r * sin(angle);

        getLem((double)i + 0.00001, (double)iPrec, &angle, &r);
        lemXT[i] = r * cos(angle);
        lemYT[i] = r * sin(angle);

    }
}

void genLemniscate(void)
{
    int i;
    int j;
    double posInc = 2.0 / (float)iPrec;
    int facecount = 0;
    int ptcount = 0;
    POINT3D norm;
    static float twistFact = 0.0f;
    static float twistFactAdd = 0.05f;
    POINT3D a[ROT_PREC];
    POINT3D b[ROT_PREC];
    MATRIX matrix;
    MESH *mesh = &lemMesh;

    mesh->numPoints = 0;
    mesh->numFaces = 0;
    for (i = 0; i < (iPrec - 1) * (ROT_PREC - 1); i++)
        mesh->norms[i] = ss_ptZero;

    for (i = 0; i < (iPrec - 1); i++) {
        double x1, y1, x2, y2;
        double len;
        double sinAngle;
        double rotZ;
        int id[4];

        x1 = lemX[i];
        y1 = lemY[i];
        x2 = lemXT[i];
        y2 = lemYT[i];

        x2 -= x1;
        y2 -= y1;

        len = sqrt(x2 * x2 + y2 * y2);
        if (len > 0.0)
            sinAngle = y2 / len;
        else
            sinAngle = 0.0;
        if (y2 < 0.0)
            sinAngle = -sinAngle;
        rotZ = asin(sinAngle);
        if (x2 < 0.0)
            rotZ = PI - rotZ;
        if (y2 < 0.0)
            rotZ = -rotZ;
        if (rotZ < 0.0)
            rotZ = 2.0 * PI + rotZ;

        ss_matrixIdent(&matrix);
        ss_matrixRotate(&matrix, 0.0, 0.0, -rotZ);
        ss_matrixTranslate(&matrix, x1, y1,
                         twistFact * cos((2.0 * PI * (float)i) / ((float)iPrec - 1)));

        for (j = 0; j < ROT_PREC; j++)
            ss_xformPoint(&a[j], &basis[j], &matrix);

        x1 = lemX[i+1];
        y1 = lemY[i+1];

        x2 = lemXT[i+1];
        y2 = lemYT[i+1];

        x2 -= x1;
        y2 -= y1;

        len = sqrt(x2 * x2 + y2 * y2);

        if (len > 0.0)
            sinAngle = y2 / len;
        else
            sinAngle = 0.0;
        if (y2 < 0.0)
            sinAngle = -sinAngle;
        rotZ = asin(sinAngle);
        if (x2 < 0.0)
            rotZ = PI - rotZ;
        if (y2 < 0.0)
            rotZ = -rotZ;
        if (rotZ < 0.0)
            rotZ = 2.0 * PI + rotZ;

        ss_matrixIdent(&matrix);
        ss_matrixRotate(&matrix, 0.0, 0.0, -rotZ);
        ss_matrixTranslate(&matrix, x1, y1,
                         twistFact * cos((2.0 * PI * ((float)i + 1.0)) / ((float)iPrec - 1)));

        for (j = 0; j < ROT_PREC; j++)
            ss_xformPoint(&b[j], &basis[j], &matrix);

        memcpy(&mesh->pts[ptcount], &a, sizeof(POINT3D) * (ROT_PREC - 1));
        ptcount += (ROT_PREC - 1);
        mesh->numPoints += (ROT_PREC - 1);

        for (j = 0; j < (ROT_PREC - 1); j++) {
            int k;
            int jj;

            if (j == (ROT_PREC - 2))
                jj = 0;
            else
                jj = j + 1;

            ss_calcNorm(&norm, &b[j + 1], &b[j], &a[j]);

            mesh->faces[facecount].material = 3;
            mesh->faces[facecount].norm = norm;
            if (i == iPrec - 2) {
                id[0] = mesh->faces[facecount].p[0] = j;
                id[1] = mesh->faces[facecount].p[1] = jj;
            } else {
                id[0] = mesh->faces[facecount].p[0] = ptcount + j;
                id[1] = mesh->faces[facecount].p[1] = ptcount + jj;
            }
            id[2] = mesh->faces[facecount].p[2] = ptcount - (ROT_PREC - 1) + j;
            id[3] = mesh->faces[facecount].p[3] = ptcount - (ROT_PREC - 1) + jj;

            for (k = 0; k < 4; k++) {
                POINT3D *pn = &mesh->norms[id[k]];

                pn->x += norm.x;
                pn->y += norm.y;
                pn->z += norm.z;
            }
            mesh->numFaces++;
            facecount++;
        }
    }

    ss_normalizeNorms(lemMesh.norms, lemMesh.numPoints);

    if (twistFact >= 1.0f)
        twistFactAdd = -0.01f;
    else if (twistFact <= -1.0f)
        twistFactAdd = 0.01f;
    twistFact += twistFactAdd;

}

void initLemScene()
{
    int i;
    RGBA lightAmbient = {0.0f, 0.0f, 0.0f, 1.0f};

    iPrec = (int)(fTesselFact * 32.5);
    if (iPrec < 5)
        iPrec = 5;

    lemX = SaverAlloc(sizeof(double) * iPrec);
    lemY = SaverAlloc(sizeof(double) * iPrec);
    lemXT = SaverAlloc(sizeof(double) * iPrec);
    lemYT = SaverAlloc(sizeof(double) * iPrec);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.5, 1.5, -1.5, 1.5, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);

    newMesh(&lemMesh, (ROT_PREC - 1) * (iPrec - 1) ,
            (ROT_PREC - 1) * (iPrec - 1));
    for (i = 0; i < ROT_PREC; i++) {
        basis[i].x = 0.0f;
        basis[i].y = (float) (0.15 * cos((i * 2.0 * PI) / (ROT_PREC - 1.0)));
        basis[i].z = (float) (0.15 * sin((i * 2.0 * PI) / (ROT_PREC - 1.0)));
    }

    initLemCoords(iPrec);

    glFrontFace(GL_CW);
    glEnable(GL_CULL_FACE);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (GLfloat *) &lightAmbient);

    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                 (GLfloat *) &Material[3].kd);
}

void delLemScene()
{
    delMesh(&lemMesh);

    SaverFree(lemX);
    SaverFree(lemY);
    SaverFree(lemXT);
    SaverFree(lemYT);
}

void updateLemScene(int flags)
{
    static double mxrot = 0.0;
    static double myrot = 0.0;
    static double mzrot = 0.0;
    static double mxrotInc = 0.0;
    static double myrotInc = 0.1;
    static double zrotInc = 0.1;
    static double mzrotInc = 0.0;
    static int h = 0;
    RGBA color;
    MATRIX model;

    mxrot += mxrotInc;
    myrot += myrotInc;
    mzrot += mzrotInc;

    if( gbBounce ) {
        // floating window bounced off an edge
        if (mxrotInc) {
            mxrotInc = 0.0;
            myrotInc = 0.1;
        } else if (myrotInc) {
            myrotInc = 0.0;
            mzrotInc = 0.1;
        } else if (mzrotInc) {
            mzrotInc = 0.0;
            mxrotInc = 0.1;
        }
        gbBounce = FALSE;
    }

    zrot += zrotInc;
    if (zrot >= PI / 4.0) {
        zrot = PI / 4.0;
        zrotInc = -0.03;
    } else if (zrot <= -PI / 4.0) {
        zrot = -PI / 4.0;
        zrotInc = 0.03;
    }

    genLemniscate();

    if (bColorCycle) {
        ss_HsvToRgb((float)h, 1.0f, 1.0f, &color );

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &color);

        h++;
        h %= 360;
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.5, 1.5, -1.5, 1.5, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);
    glRotatef((GLfloat) (zrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
    glRotatef(50.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(50.0f, 0.0f, 0.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0f, -0.5f, 0.0f);
    glRotatef((GLfloat) (mxrot * (180.0 / PI)), 1.0f, 0.0f, 0.0f);
    glRotatef((GLfloat) (myrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
    glRotatef((GLfloat) (mzrot * (180.0 / PI)), 0.0f, 0.0f, 1.0f);

    ss_matrixIdent(&model);
    ss_matrixRotate(&model, mxrot, myrot, mzrot);
    ss_matrixTranslate(&model, 0.0, -0.5, 0.0);

    updateObject(&lemMesh, bSmoothShading);
}


GENSTRIP.C

/******************************Module*Header*******************************\
* Module Name: genstrip.c
*
* The Ribbon and 2 Ribbon styles of the 3D Flying Objects screen saver.
*
* Animation of 1 or 2 quad strips floating about.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <GL\gl.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"
#include "mesh.h"

static MESH stripMesh;
static int iPrec = 40;

void genStrip()
{
    static int counter = 0;
    int i;
    int facecount;
    // Use Hermite basis, pg 488, FVD
    static float M[4][4] = {{2.0f, -2.0f, 1.0f, 1.0f},
                            {-3.0f, 3.0f, -2.0f, -1.0f},
                            {0.0f, 0.0f, 1.0f, 0.0f},
                            {1.0f, 0.0f, 0.0f, 0.0f}};
    float xx[4], yy[4], zz[4];
    float cx[4], cy[4], cz[4];
    float d = 1.0f / (float) iPrec;
    float t = 0.0f;
    float t2, t3;
    POINT3D p1 = {-0.5f, 0.0f, 0.0f};
    POINT3D p2 = {0.5f, 0.0f, 0.0f};
    POINT3D v1 = {1.5f, 1.5f, 0.0f};
    POINT3D v2 = {0.0f, 3.0f, 0.0f};
    POINT3D norm;
    float sinVal;
    float angle;
    float angleStep = (float) (PI / iPrec);
    static float rotA = 0.0f;
    static float rotB = (float) (PI / 2.0);
    static float sideSin = 0.0f;
    float rotStepA = (float) (PI / (2.0 * iPrec));
    float rotStepB = (float) (PI / (4.0 * iPrec));
    MESH *mesh = &stripMesh;

#define NORMS(x, y) stripMesh.norms[((x) * iPrec) + y]
#define GRID(x, y)  stripMesh.pts[((x) * iPrec) + y]

    v1.x = (float) (4.0 * cos(rotA));
    v1.y = (float) (4.0 * sin(rotA));

    p2.x = (float) (0.5 * sin(rotB));
//    p2.y = (float) (0.5 * sin(rotB));

    rotA += rotStepA;
    rotB += rotStepB;
    counter++;
    if (counter >= (2 * iPrec)) {
        rotStepA = -rotStepA;
        counter = 0;
    }

    angle = sideSin;
    sideSin += (float) (PI / 80.0);

    xx[0] = p1.x;
    xx[1] = p2.x;
    xx[2] = v1.x;
    xx[3] = v2.x;

    yy[0] = p1.y;
    yy[1] = p2.y;
    yy[2] = v1.y;
    yy[3] = v2.y;

    zz[0] = p1.z;
    zz[1] = p2.z;
    zz[2] = v1.z;
    zz[3] = v2.z;

    for (i = 0; i < 4; i++) {
        cx[i] = xx[0] * M[i][0] + xx[1] * M[i][1] +
                xx[2] * M[i][2] + xx[3] * M[i][3];
        cy[i] = yy[0] * M[i][0] + yy[1] * M[i][1] +
                yy[2] * M[i][2] + yy[3] * M[i][3];
        cz[i] = zz[0] * M[i][0] + zz[1] * M[i][1] +
                zz[2] * M[i][2] + zz[3] * M[i][3];
    }

    for (i = 0; i < iPrec; i++) {
        float x, y;

        t += d;
        t2 = t * t;
        t3 = t2 * t;

        x = cx[0] * t3 + cx[1] * t2 + cx[2] * t + cx[3];
        y = cy[0] * t3 + cy[1] * t2 + cy[2] * t + cy[3];

        sinVal = (float) (sin(angle) / 5.0);
        if (sinVal < 0.0)
            sinVal = -sinVal;
        angle += angleStep;

        GRID(0, i).x = x;
        GRID(0, i).z = y;
        GRID(0, i).y = 0.25f;    // extrusion // + sinVal;
        GRID(1, i).x = x;
        GRID(1, i).z = y;
        GRID(1, i).y = -0.25f;   // - sinVal;
    }

    stripMesh.numFaces = 0;

    for (i = 0; i < 2 * iPrec; i++)
        mesh->norms[i] = ss_ptZero;

    for (facecount = 0, i = 0; i < (iPrec - 1); i++) {

        ss_calcNorm(&norm, &GRID(0, i + 1), &GRID(0, i), &GRID(1, i));
        stripMesh.faces[facecount].material = 0;
        stripMesh.faces[facecount].norm = norm;

        NORMS(0, i).x += norm.x;
        NORMS(0, i).y += norm.y;
        NORMS(0, i).z += norm.z;
        NORMS(1, i).x += norm.x;
        NORMS(1, i).y += norm.y;
        NORMS(1, i).z += norm.z;

        if (i != (iPrec - 1)) {
            NORMS(0, i+1).x += norm.x;
            NORMS(0, i+1).y += norm.y;
            NORMS(0, i+1).z += norm.z;
            NORMS(1, i+1).x += norm.x;
            NORMS(1, i+1).y += norm.y;
            NORMS(1, i+1).z += norm.z;
        }

        stripMesh.faces[facecount].p[0] = i;
        stripMesh.faces[facecount].p[1] = iPrec + i;
        stripMesh.faces[facecount].p[2] = i + 1;
        stripMesh.faces[facecount].p[3] = iPrec + i + 1;
        stripMesh.numFaces++;
        facecount++;
    }

    stripMesh.numPoints = 2 * iPrec;

    ss_normalizeNorms(stripMesh.norms, stripMesh.numPoints);
}

void initStripScene()
{
    iPrec = (int)(fTesselFact * 40.5);
    if (iPrec < 4)
        iPrec = 4;

    newMesh(&stripMesh, iPrec, 2 * iPrec);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.1, 1.1, -1.1, 1.1, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);
    glRotatef(50.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(50.0f, 0.0f, 1.0f, 0.0f);
    glRotatef(12.0f, 0.0f, 0.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
}

void delStripScene()
{
    delMesh(&stripMesh);
}

void updateStripScene(int flags)
{
    static double mxrot = 0.0;
    static double myrot = 0.0;
    static double mzrot = 0.0;
    static double mxrotInc = 0.0;
    static double myrotInc = 0.1;
    static double mzrotInc = 0.0;
    static int h = 0;
    RGBA color;

    if( gbBounce ) {
        // floating window bounced off an edge
        if (mxrotInc) {
            mxrotInc = 0.0;
            myrotInc = 0.1;
        } else if (myrotInc) {
            myrotInc = 0.0;
            mzrotInc = 0.1;
        } else if (mzrotInc) {
            mzrotInc = 0.0;
            mxrotInc = 0.1;
        }
        gbBounce = FALSE;
    }

    glLoadIdentity();
    glRotatef((GLfloat) (mxrot * (180.0 / PI)), 1.0f, 0.0f, 0.0f);
    glRotatef((GLfloat) (myrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
    glRotatef((GLfloat) (mzrot * (180.0 / PI)), 0.0f, 0.0f, 1.0f);

    genStrip();

    if (bColorCycle) {
        ss_HsvToRgb((float)h, 1.0f, 1.0f, &color );

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                     (GLfloat *) &color);

        h++;
        h %= 360;
    } else {
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                     (GLfloat *) &Material[1].kd);
    }

    updateObject(&stripMesh, bSmoothShading);

    if (flags & 0x4) {
        glLoadIdentity();
        glTranslatef(0.05f, 0.0f, 0.0f);
        glRotatef((GLfloat) (myrot * (180.0 / PI)), 1.0f, 0.0f, 0.0f);
        glRotatef((GLfloat) (mxrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
        glRotatef((GLfloat) (mzrot * (180.0 / PI)), 0.0f, 0.0f, 1.0f);

        if (bColorCycle) {
            color.r = 1.0f - color.r;
            color.g = 1.0f - color.g;
            color.b = 1.0f - color.b;

            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                         (GLfloat *) &color);
        } else {
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                         (GLfloat *) &Material[2].kd);
        }

        updateObject(&stripMesh, bSmoothShading);
    }

    mxrot += mxrotInc;
    myrot += myrotInc;
    mzrot += mzrotInc;
}


GENTEX.C

/******************************Module*Header*******************************\
* Module Name: gentex.c
*
* The Textured Flag style of the 3D Flying Objects screen saver.
*
* Texture maps .BMP files onto a simulation of a flag waving in the breeze.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
#include <GL\gl.h>
#include <GL\glu.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"

static float winTotalwidth = (float)0.75;
static float winTotalheight = (float)0.75 * (float)0.75;

#define MAX_FRAMES 20

// IPREC is the number of faces in the mesh that models the flag.

#define IPREC   15

static int Frames = 10;
static MESH winMesh[MAX_FRAMES];
static float sinAngle = (float)0.0;
static float xTrans = (float)0.0;
static int curMatl = 0;

// Material properties.

static RGBA matlBrightSpecular = {1.0f, 1.0f, 1.0f, 1.0f};
static RGBA matlDimSpecular    = {0.5f, 0.5f, 0.5f, 1.0f};
static RGBA matlNoSpecular     = {0.0f, 0.0f, 0.0f, 0.0f};

// Lighting properties.

static GLfloat light0Pos[] = {20.0f, 5.0f, 20.0f, 0.0f};
static GLfloat light1Pos[] = {-20.0f, 5.0f, 0.0f, 0.0f};
static RGBA light1Ambient  = {0.0f, 0.0f, 0.0f, 0.0f};
static RGBA light1Diffuse  = {0.4f, 0.4f, 0.4f, 1.0f};
static RGBA light1Specular = {0.0f, 0.0f, 0.0f, 0.0f};

static RGBA flagColors[] = {{1.0f, 1.0f, 1.0f, 1.0f},
                            {0.94f, 0.37f, 0.13f, 1.0f},    // red
                           };

// Default texture resource

TEX_RES gTexRes = { TEX_BMP, IDB_DEFTEX };

/******************************Public*Routine******************************\
* iPtInList
*
* Add a vertex and its normal to the mesh.  If the vertex already exists,
* add in the normal to the existing normal (we to accumulate the average
* normal at each vertex).  Normalization of the normals is the
* responsibility of the caller.
*
\**************************************************************************/

static int iPtInList(MESH *mesh, int start,
                     POINT3D *p, POINT3D *norm, BOOL blend)
{
    int i;
    POINT3D *pts = mesh->pts + start;

    if (blend) {
        for (i = start; i < mesh->numPoints; i++, pts++) {
            if ((pts->x == p->x) && (pts->y == p->y) && (pts->z == p->z)) {
                mesh->norms[i].x += norm->x;
                mesh->norms[i].y += norm->y;
                mesh->norms[i].z += norm->z;
                return i;
            }
        }
    } else {
        i = mesh->numPoints;
    }

    mesh->pts[i] = *p;
    mesh->norms[i] = *norm;
    mesh->numPoints++;
    return i;
}

/******************************Public*Routine******************************\
* getZpos
*
* Get the z-position (depth) of the "wavy" flag component at the given x.
*
* The function used to model the wave is:
*
*        1/2
*   z = x    * sin((2*PI*x + sinAngle) / 4)
*
* The shape of the wave varies from frame to frame by changing the
* phase, sinAngle.
*
\**************************************************************************/

float getZpos(float x)
{
    float xAbs = x - xTrans;
    float angle = sinAngle + ((float) (2.0 * PI) * (xAbs / winTotalwidth));

    xAbs = winTotalwidth - xAbs;
//    xAbs += (winTotalwidth / 2.0);

    return (float)((sin((double)angle) / 4.0) *
                   sqrt((double)(xAbs / winTotalwidth )));
}

/******************************Public*Routine******************************\
* genTex
*
* Generate a mesh representing a frame of the flag.  The phase, sinAngle,
* is a global variable.
*
\**************************************************************************/

void genTex(MESH *winMesh)
{
    POINT3D pos;
    POINT3D pts[4];
    float w, h;
    int i;

    newMesh(winMesh, IPREC * IPREC, IPREC * IPREC);

// Width and height of each face

    w = (winTotalwidth) / (float)(IPREC + 1);
    h = winTotalheight;

// Generate the mesh data.  At equally spaced intervals along the x-axis,
// we compute the z-position of the flag surface.

    pos.y = (float) 0.0;
    pos.z = (float) 0.0;

    for (i = 0, pos.x = xTrans; i < IPREC; i++, pos.x += w) {
        int faceCount = winMesh->numFaces;

        pts[0].x = (float)pos.x;
        pts[0].y = (float)(pos.y);
        pts[0].z = getZpos(pos.x);

        pts[1].x = (float)pos.x;
        pts[1].y = (float)(pos.y + h);
        pts[1].z = getZpos(pos.x);

        pts[2].x = (float)(pos.x + w);
        pts[2].y = (float)(pos.y);
        pts[2].z = getZpos(pos.x + w);

        pts[3].x = (float)(pos.x + w);
        pts[3].y = (float)(pos.y + h);
        pts[3].z = getZpos(pos.x + w);

    // Compute the face normal.

        ss_calcNorm(&winMesh->faces[faceCount].norm, pts + 2, pts + 1, pts);

    // Add the face to the mesh.

        winMesh->faces[faceCount].material = 0;
        winMesh->faces[faceCount].p[0] = iPtInList(winMesh, 0, pts,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[1] = iPtInList(winMesh, 0, pts + 1,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[2] = iPtInList(winMesh, 0, pts + 2,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[3] = iPtInList(winMesh, 0, pts + 3,
            &winMesh->faces[faceCount].norm, TRUE);

        winMesh->numFaces++;
    }

// Normalize the vertex normals in the mesh.

    ss_normalizeNorms(winMesh->norms, winMesh->numPoints);
}

/******************************Public*Routine******************************\
* initTexScene
*
* Initialize the screen saver.
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/

void initTexScene()
{
    int i;
    float angleDelta;
    TEXTURE tex;
    float aspectRatio;

    // Initialize the transform.

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-0.25, 1.0, -0.25, 1.0, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);

    // Initialize and turn on lighting.

    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);

    glLightfv(GL_LIGHT1, GL_AMBIENT, (GLfloat *) &light1Ambient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, (GLfloat *) &light1Diffuse);
    glLightfv(GL_LIGHT1, GL_SPECULAR, (GLfloat *) &light1Specular);
    glLightfv(GL_LIGHT1, GL_POSITION, light1Pos);
    glEnable(GL_LIGHT1);
    glDisable(GL_DEPTH_TEST);

    // Leave OpenGL in a state ready to accept the model view transform (we
    // are going to have the flag vary its orientation from frame to frame).

    glMatrixMode(GL_MODELVIEW);

    // Define orientation of polygon faces.

    glFrontFace(GL_CW);
    //    glEnable(GL_CULL_FACE);

    Frames = (int)((float)(MAX_FRAMES / 2) * fTesselFact);

    // Load user texture - if that fails load default texture resource

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    if( ss_LoadTextureFile( &gTexFile, &tex ) ||
        ss_LoadTextureResource( &gTexRes, &tex) )
    {
        glEnable(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

        ss_SetTexture( &tex );

    // Correct aspect ratio of flag to match image.
    //
    // The 1.4 is a correction factor to account for the length of the
    // curve that models the surface ripple of the waving flag.  This
    // factor is the length of the curve at zero phase.  It would be
    // more accurate to determine the length of the curve at each phase,
    // but this is a sufficient approximation for our purposes.

        aspectRatio = ((float) tex.height / (float) tex.width)
                      * (float) 1.4;

        if (aspectRatio < (float) 1.0) {
            winTotalwidth  = (float)0.75;
            winTotalheight = winTotalwidth * aspectRatio;
        } else {
            winTotalheight = (float) 0.75;
            winTotalwidth  = winTotalheight / aspectRatio;
        };
    }

    if (Frames < 5)
        Frames = 5;
    if (Frames > MAX_FRAMES)
        Frames = MAX_FRAMES;

    // Generate the geometry data (stored in the array of mesh structures),
    // for each frame of the animation.  The shape of the flag is varied by
    // changing the global variable sinAngle.

    angleDelta = (float)(2.0 * PI) / (float)Frames;
    sinAngle = (float) 0.0;

    for (i = 0; i < Frames; i++) {
        genTex(&winMesh[i]);
        sinAngle += angleDelta;
    }
}

/******************************Public*Routine******************************\
* delTexScene
*
* Cleanup the data associated with this screen saver.
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/

void delTexScene()
{
    int i;

    for (i = 0; i < Frames; i++)
        delMesh(&winMesh[i]);
}

/******************************Public*Routine******************************\
* updateTexScene
*
* Generate a scene by taking one of the meshes and rendering it with
* OpenGL.
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/

void updateTexScene(int flags)
{
    MESH *mesh;
    MFACE *faces;
    int i;
    static double mxrot = 23.0;
    static double myrot = 23.0;
    static double mzrot = 5.7;
    static double mxrotInc = 0.0;
    static double myrotInc = 3.0;
    static double mzrotInc = 0.0;
    static int h = 0;
    static int frameNum = 0;
    POINT3D *pp;
    POINT3D *pn;
    int lastC, lastD;
    int aOffs, bOffs, cOffs, dOffs;
    int a, b;
    GLfloat s = (GLfloat) 0.0;
    GLfloat ds;

// In addition to having the flag wave (an effect acheived by switching
// meshes from frame to frame), the flag changes its orientation from
// frame to frame.  This is done by applying a model view transform.

    glLoadIdentity();
    glRotatef((float)mxrot, 1.0f, 0.0f, 0.0f);
    glRotatef((float)myrot, 0.0f, 1.0f, 0.0f);
    glRotatef((float)mzrot, 0.0f, 0.0f, 1.0f);

// Divide the texture into IPREC slices.  ds is the texture coordinate
// delta we apply as we move along the x-axis.

    ds = (GLfloat)1.0 / (GLfloat)IPREC;

// Setup the material property of the flag.  The material property, light
// properties, and polygon orientation will interact with the texture.

    curMatl = 0;
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &flagColors[0]);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &matlBrightSpecular);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, (float) 60.0);

// Pick the mesh for the current frame.

    mesh = &winMesh[frameNum];

// Take the geometry data is the mesh and convert it to a single OpenGL
// quad strip.  If smooth shading is required, use the vertex normals stored
// in the mesh.  Otherwise, use the face normals.
//
// As we define each vertex, we also define a corresponding vertex and
// texture coordinate.

    glBegin(GL_QUAD_STRIP);

    pp = mesh->pts;
    pn = mesh->norms;

    for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1];
         i < mesh->numFaces; i++, faces++) {

        a = faces->p[0];
        b = faces->p[1];

        if (!bSmoothShading) {
            // Since flag is a single quad strip, this isn't needed.
            // But lets keep it in case we ever change to a more
            // complex model (ie., one that uses more than one quad
            // strip).
            #if 0
            if ((a != lastC) || (b != lastD)) {
                glNormal3fv((GLfloat *)&(faces - 1)->norm);

                glTexCoord2f(s, (float) 0.0);
                glVertex3fv((GLfloat *)((char *)pp +
                            (lastC << 3) + (lastC << 2)));
                glTexCoord2f(s, (float) 1.0);
                glVertex3fv((GLfloat *)((char *)pp +
                            (lastD << 3) + (lastD << 2)));
                s += ds;
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }
            #endif

            if (faces->material != curMatl) {
                curMatl = faces->material;
                glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
                             (GLfloat *) &matlNoSpecular);
                glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                             (GLfloat *) &flagColors[curMatl]);
            }

            glNormal3fv((GLfloat *)&faces->norm);
            glTexCoord2f(s, (float) 0.0);
            glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2)));
            glTexCoord2f(s, (float) 1.0);
            glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2)));
            s += ds;
        } else {
            // Since flag is a single quad strip, this isn't needed.
            // But lets keep it in case we ever change to a more
            // complex model (ie., one that uses more than one quad
            // strip).
            #if 0
            if ((a != lastC) || (b != lastD)) {
                cOffs = (lastC << 3) + (lastC << 2);
                dOffs = (lastD << 3) + (lastD << 2);

                glTexCoord2f(s, (float) 0.0);
                glNormal3fv((GLfloat *)((char *)pn + cOffs));
                glVertex3fv((GLfloat *)((char *)pp + cOffs));
                glTexCoord2f(s, (float) 1.0);
                glNormal3fv((GLfloat *)((char *)pn + dOffs));
                glVertex3fv((GLfloat *)((char *)pp + dOffs));
                s += ds;
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }
            #endif

            aOffs = (a << 3) + (a << 2);
            bOffs = (b << 3) + (b << 2);

            if (faces->material != curMatl) {
                curMatl = faces->material;
                glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
                             (GLfloat *) &matlNoSpecular);
                glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                             (GLfloat *) &flagColors[curMatl]);
            }

            glTexCoord2f(s, (float) 0.0);
            glNormal3fv((GLfloat *)((char *)pn + aOffs));
            glVertex3fv((GLfloat *)((char *)pp + aOffs));
            glTexCoord2f(s, (float) 1.0);
            glNormal3fv((GLfloat *)((char *)pn + bOffs));
            glVertex3fv((GLfloat *)((char *)pp + bOffs));
            s += ds;
        }

        lastC = faces->p[2];
        lastD = faces->p[3];
    }

    if (!bSmoothShading) {
        glNormal3fv((GLfloat *)&(faces - 1)->norm);
        glTexCoord2f(s, (float) 0.0);
        glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2)));
        glTexCoord2f(s, (float) 1.0);
        glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2)));
    } else {
        cOffs = (lastC << 3) + (lastC << 2);
        dOffs = (lastD << 3) + (lastD << 2);

        glTexCoord2f(s, (float) 0.0);
        glNormal3fv((GLfloat *)((char *)pn + cOffs));
        glVertex3fv((GLfloat *)((char *)pp + cOffs));
        glTexCoord2f(s, (float) 1.0);
        glNormal3fv((GLfloat *)((char *)pn + dOffs));
        glVertex3fv((GLfloat *)((char *)pp + dOffs));
    }

    glEnd();

// Transfer the image to the floating OpenGL window.

// Determine the flag orientation for the next frame.
// What we are doing is an oscillating rotation about the y-axis
// (mxrotInc and mzrotInc are currently 0).

    mxrot += mxrotInc;
    myrot += myrotInc;
    mzrot += mzrotInc;

    if ((myrot < -65.0) || (myrot > 25.0))
        myrotInc = -myrotInc;

    frameNum++;
    if (frameNum >= Frames)
        frameNum = 0;
}


GENWIN.C

/******************************Module*Header*******************************\
* Module Name: genwin.c
*
* The Windows Logo style of the 3D Flying Objects screen saver.
*
* Animated 3D model of the Microsoft (R) Windows NT (TM) flag logo.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
#include <GL\gl.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"

#define WIN_TOP_BORDER      (float)0.1
#define WIN_RIGHT_BORDER    WIN_TOP_BORDER
#define WIN_CROSSBAR        (0.6522f * WIN_TOP_BORDER)
#define WIN_NUMPIECES       7
#define WIN_NUMCOLUMNS      6
#define WIN_GAP             (WIN_TOP_BORDER / 8.0f)
#define WIN_GAP_X           (2.0f * WIN_GAP)
#define WIN_HEIGHT          ((WIN_GAP * 6.0f) + \
                                (WIN_NUMPIECES * WIN_TOP_BORDER))
#define WIN_WIDTH           (0.7024f * WIN_HEIGHT)
#define WIN_THICKNESS       WIN_CROSSBAR

#define WIN_TOTALWIDTH      (WIN_TOP_BORDER * 1.1f * (float)WIN_NUMCOLUMNS + \
                             WIN_WIDTH)

#define BLOCK_TOP            0x0001
#define BLOCK_BOTTOM         0x0002
#define BLOCK_LEFT           0x0004
#define BLOCK_RIGHT          0x0008
#define BLOCK_FRONT          0x0010
#define BLOCK_BACK           0x0020
#define BLOCK_ALL            0x003f
#define DELTA_BLEND          0x2000
#define NO_BLEND             0x1000

#define CUBE_FACES          6
#define CUBE_POINTS         8

#define MAX_FRAMES 20

#define MAXPREC 15
#define S_IPREC 3

static int Frames = 10;
static MESH winMesh[MAX_FRAMES];
static MESH winStreamer[MAX_FRAMES];
static float sinAngle = 0.0f;
static float xTrans = 0.2f;
static int curMatl = 0;
static int iPrec = 10;

static RGBA matlBrightSpecular = {1.0f, 1.0f, 1.0f, 1.0f};
static RGBA matlDimSpecular = {0.5f, 0.5f, 0.5f, 1.0f};
static RGBA matlNoSpecular = {0.0f, 0.0f, 0.0f, 0.0f};
static GLfloat light0Pos[] = {20.0f, -10.0f, 20.0f, 0.0f};

static RGBA light1Ambient  = {0.0f, 0.0f, 0.0f, 0.0f};
static RGBA light1Diffuse  = {0.4f, 0.4f, 0.4f, 1.0f};
static RGBA light1Specular = {0.0f, 0.0f, 0.0f, 0.0f};
static GLfloat light1Pos[] = {-20.0f, 5.0f, 0.0f, 0.0f};

static RGBA winColors[] = {{0.3f, 0.3f, 0.3f, 1.0f},
                           {0.94f, 0.37f, 0.13f, 1.0f},    // red
                           {0.22f, 0.42f, 0.78f, 1.0f},    // blue
                           {0.35f, 0.71f, 0.35f, 1.0f},    // green
                           {0.95f, 0.82f, 0.12f, 1.0f}};   // yellow

static int iPtInList(MESH *mesh, int start,
                     POINT3D *p, POINT3D *norm, BOOL blend)
{
    int i;
    POINT3D *pts = mesh->pts + start;

    if (blend) {
        for (i = start; i < mesh->numPoints; i++, pts++) {
            if ((pts->x == p->x) && (pts->y == p->y) && (pts->z == p->z)) {
                mesh->norms[i].x += norm->x;
                mesh->norms[i].y += norm->y;
                mesh->norms[i].z += norm->z;
                return i;
            }
        }
    } else {
        i = mesh->numPoints;
    }

    mesh->pts[i] = *p;
    mesh->norms[i] = *norm;
    mesh->numPoints++;
    return i;
}

float getZPos(float x)
{
    float xAbs = x - xTrans;
    float angle = (float) (sinAngle + ((2.0 * PI) * (xAbs / WIN_TOTALWIDTH)));

    xAbs += (WIN_TOTALWIDTH / 2.0f);
    xAbs = WIN_TOTALWIDTH - xAbs;

    return (float)((sin((double)angle) / 4.0) *
                   sqrt((double)(xAbs / WIN_TOTALWIDTH )));
}

void AddFace(MESH *mesh, int startBlend, POINT3D *pos, float w, float h)
{
#define FACE_VERTEX(i) \
    iPtInList(mesh, startBlend, pts + i, &mesh->faces[faceCount].norm, TRUE)

    int faceCount = mesh->numFaces;
    int numPts = mesh->numPoints;
    POINT3D *pts = mesh->pts + numPts;
    float zLeft = getZPos(pos->x);
    float zRight = getZPos(pos->x + w);

    pts->x = (float)pos->x;
    pts->y = (float)pos->y;
    pts->z = zLeft;
    pts++;

    pts->x = (float)pos->x;
    pts->y = (float)(pos->y + h);
    pts->z = zLeft;
    pts++;

    pts->x = (float)(pos->x + w);
    pts->y = (float)(pos->y + h);
    pts->z = zRight;
    pts++;

    pts->x = (float)(pos->x + w);
    pts->y = (float)pos->y;
    pts->z = zRight;

    pts -= 3;

    mesh->faces[faceCount].material = curMatl;
    ss_calcNorm(&mesh->faces[faceCount].norm, pts + 2, pts + 1, pts);
    mesh->faces[faceCount].p[3] = FACE_VERTEX(0);
    mesh->faces[faceCount].p[2] = FACE_VERTEX(1);
    mesh->faces[faceCount].p[1] = FACE_VERTEX(2);
    mesh->faces[faceCount].p[0] = FACE_VERTEX(3);
    mesh->numFaces++;
}

#define BLOCK_VERTEX(face, i)\
{\
    if (flags & DELTA_BLEND) {\
        mesh->faces[faceCount].p[face] = \
            iPtInList(mesh, blendStart, &pts[i], &norms[((i & 0x2) >> 1)],\
                      bBlend);\
    } else\
        mesh->faces[faceCount].p[face] = \
            iPtInList(mesh, blendStart, &pts[i],\
                      &mesh->faces[faceCount].norm, bBlend);\
}

#define DELTA_FACT  (float)10.0

void AddBlock(MESH *mesh, int blendStart, POINT3D *pos,
              float w, float h, float d, ULONG flags)
{

    POINT3D pts[8];
    POINT3D ptsL[8];
    POINT3D ptsR[8];
    POINT3D norms[2];
    POINT3D posPrev;
    float zLeft = getZPos(pos->x);
    float zRight = getZPos(pos->x + w);
    int faceCount = mesh->numFaces;
    BOOL bBlend = ((flags & NO_BLEND) == 0);

    flags |= DELTA_BLEND;

    pts[0].x = (float)pos->x;
    pts[0].y = (float)(pos->y + h);
    pts[0].z = zLeft;

    pts[1].x = (float)pos->x;
    pts[1].y = (float)(pos->y + h);
    pts[1].z = zLeft + d;

    pts[2].x = (float)(pos->x + w);
    pts[2].y = (float)(pos->y + h);
    pts[2].z = zRight + d;

    pts[3].x = (float)(pos->x + w);
    pts[3].y = (float)(pos->y + h);
    pts[3].z = zRight;

    pts[4].x = (float)pos->x;
    pts[4].y = (float)pos->y;
    pts[4].z = zLeft;

    pts[5].x = (float)pos->x;
    pts[5].y = (float)pos->y;
    pts[5].z = zLeft + d;

    pts[6].x = (float)(pos->x + w);
    pts[6].y = (float)pos->y;
    pts[6].z = zRight + d;

    pts[7].x = (float)(pos->x + w);
    pts[7].y = (float)pos->y;
    pts[7].z = zRight;

    if (flags & DELTA_BLEND) {
float prevW = w;
        posPrev = *pos;

        w /= DELTA_FACT;
        zRight = getZPos(pos->x + w);

        ptsL[0].x = (float)pos->x;
        ptsL[0].y = (float)(pos->y + h);
        ptsL[0].z = zLeft;

        ptsL[1].x = (float)pos->x;
        ptsL[1].y = (float)(pos->y + h);
        ptsL[1].z = zLeft + d;

        ptsL[2].x = (float)(pos->x + w);
        ptsL[2].y = (float)(pos->y + h);
        ptsL[2].z = zRight + d;

        ptsL[3].x = (float)(pos->x + w);
        ptsL[3].y = (float)(pos->y + h);
        ptsL[3].z = zRight;

        ptsL[4].x = (float)pos->x;
        ptsL[4].y = (float)pos->y;
        ptsL[4].z = zLeft;

        ptsL[5].x = (float)pos->x;
        ptsL[5].y = (float)pos->y;
        ptsL[5].z = zLeft + d;

        ptsL[6].x = (float)(pos->x + w);
        ptsL[6].y = (float)pos->y;
        ptsL[6].z = zRight + d;

        ptsL[7].x = (float)(pos->x + w);
        ptsL[7].y = (float)pos->y;
        ptsL[7].z = zRight;

        pos->x += (prevW - w);
        zLeft = getZPos(pos->x);
        zRight = getZPos(pos->x + w);

        ptsR[0].x = (float)pos->x;
        ptsR[0].y = (float)(pos->y + h);
        ptsR[0].z = zLeft;

        ptsR[1].x = (float)pos->x;
        ptsR[1].y = (float)(pos->y + h);
        ptsR[1].z = zLeft + d;

        ptsR[2].x = (float)(pos->x + w);
        ptsR[2].y = (float)(pos->y + h);
        ptsR[2].z = zRight + d;

        ptsR[3].x = (float)(pos->x + w);
        ptsR[3].y = (float)(pos->y + h);
        ptsR[3].z = zRight;

        ptsR[4].x = (float)pos->x;
        ptsR[4].y = (float)pos->y;
        ptsR[4].z = zLeft;

        ptsR[5].x = (float)pos->x;
        ptsR[5].y = (float)pos->y;
        ptsR[5].z = zLeft + d;

        ptsR[6].x = (float)(pos->x + w);
        ptsR[6].y = (float)pos->y;
        ptsR[6].z = zRight + d;

        ptsR[7].x = (float)(pos->x + w);
        ptsR[7].y = (float)pos->y;
        ptsR[7].z = zRight;

        *pos = posPrev;

    }

    if (flags & BLOCK_TOP) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[0], &pts[1], &pts[2]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[0], &ptsL[1], &ptsL[2]);
            ss_calcNorm(&norms[1], &ptsR[0], &ptsR[1], &ptsR[2]);
        }
        BLOCK_VERTEX(0, 0);
        BLOCK_VERTEX(1, 1);
        BLOCK_VERTEX(2, 2);
        BLOCK_VERTEX(3, 3);
        faceCount++;
        mesh->numFaces++;
    }

    if (flags & BLOCK_BOTTOM) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[4], &pts[7], &pts[6]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[4], &ptsL[7], &ptsL[6]);
            ss_calcNorm(&norms[1], &ptsR[4], &ptsR[7], &ptsR[6]);
        }
        BLOCK_VERTEX(0, 4);
        BLOCK_VERTEX(1, 7);
        BLOCK_VERTEX(2, 6);
        BLOCK_VERTEX(3, 5);
        faceCount++;
        mesh->numFaces++;
    }

    if (flags & BLOCK_LEFT) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[1], &pts[0], &pts[4]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[1], &ptsL[0], &ptsL[4]);
            ss_calcNorm(&norms[1], &ptsR[1], &ptsR[0], &ptsR[4]);
        }
        BLOCK_VERTEX(0, 1);
        BLOCK_VERTEX(1, 0);
        BLOCK_VERTEX(2, 4);
        BLOCK_VERTEX(3, 5);
        faceCount++;
        mesh->numFaces++;
    }

    if (flags & BLOCK_RIGHT) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[3], &pts[2], &pts[6]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[3], &ptsL[2], &ptsL[6]);
            ss_calcNorm(&norms[1], &ptsR[3], &ptsR[2], &ptsR[6]);
        }
        BLOCK_VERTEX(0, 3);
        BLOCK_VERTEX(1, 2);
        BLOCK_VERTEX(2, 6);
        BLOCK_VERTEX(3, 7);
        faceCount++;
        mesh->numFaces++;
    }

    if (flags & BLOCK_FRONT) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[0], &pts[3], &pts[7]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[0], &ptsL[3], &ptsL[7]);
            ss_calcNorm(&norms[1], &ptsR[0], &ptsR[3], &ptsR[7]);
        }
        BLOCK_VERTEX(0, 0);
        BLOCK_VERTEX(1, 3);
        BLOCK_VERTEX(2, 7);
        BLOCK_VERTEX(3, 4);
        faceCount++;
        mesh->numFaces++;
    }

    if (flags & BLOCK_BACK) {
        mesh->faces[faceCount].material = curMatl;
        ss_calcNorm(&mesh->faces[faceCount].norm, &pts[1], &pts[5], &pts[6]);
        if (flags & DELTA_BLEND) {
            ss_calcNorm(&norms[0], &ptsL[1], &ptsL[5], &ptsL[6]);
            ss_calcNorm(&norms[1], &ptsR[1], &ptsR[5], &ptsR[6]);
        }
        BLOCK_VERTEX(0, 1);
        BLOCK_VERTEX(1, 5);
        BLOCK_VERTEX(2, 6);
        BLOCK_VERTEX(3, 2);
        mesh->numFaces++;
    }

}

void genWin(MESH *winMesh, MESH *winStreamer)
{
    POINT3D pos, posCenter;
    float w, h, d;
    float wMax, hMax;
    float xpos;
    int i, j, prec;
    int startBlend;

    newMesh(winMesh, CUBE_FACES * iPrec * 20, CUBE_POINTS * iPrec * 20);

    //
    // create window frame
    //

    w = (WIN_WIDTH - WIN_TOP_BORDER) / (float)iPrec;
    h = (float)WIN_TOP_BORDER;
    d = (float)WIN_THICKNESS;

    // draw top and bottom portions

    pos.y = 0.0f;
    pos.z = 0.0f;

    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_TOP);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BOTTOM);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_FRONT);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BACK);
    pos.x = xTrans;
    AddBlock(winMesh, 0, &pos, w, h, d, BLOCK_LEFT | NO_BLEND);

    pos.y = WIN_HEIGHT - WIN_TOP_BORDER;
    pos.z = 0.0f;

    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_TOP);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BOTTOM);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_FRONT);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < iPrec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BACK);
    pos.x = xTrans;
    AddBlock(winMesh, 0, &pos, w, h, d, BLOCK_LEFT | NO_BLEND);

    // draw middle horizontal portions

    prec = (iPrec / 2);
    w = (WIN_WIDTH - WIN_TOP_BORDER - WIN_CROSSBAR) / 2.0f;
    w /= (float)prec;
    h = WIN_CROSSBAR;
    pos.y = (WIN_HEIGHT - WIN_CROSSBAR) / 2.0f;
    pos.z = 0.0f;

    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_TOP);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BOTTOM);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_FRONT);
    for (i = 0, pos.x = xTrans, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BACK);

    xpos = pos.x + WIN_CROSSBAR;

    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_TOP);
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BOTTOM);
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_FRONT);
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < prec; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BACK);

    pos.x = xTrans;
    AddBlock(winMesh, 0, &pos, w, h, d, BLOCK_LEFT | NO_BLEND);

    // Draw thick right-hand edge of frame

    pos.x = xpos = xTrans + WIN_WIDTH - WIN_RIGHT_BORDER;
    pos.y = 0.0f;
    pos.z = 0.0f;
    w = WIN_RIGHT_BORDER / (float)S_IPREC;
    h = WIN_HEIGHT;

    AddBlock(winMesh, winMesh->numPoints, &pos, w, h, d, BLOCK_LEFT);

    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < S_IPREC; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_FRONT);
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < S_IPREC; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BACK);
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < S_IPREC; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_TOP);
    pos.y = WIN_HEIGHT;
    for (i = 0, pos.x = xpos, startBlend = winMesh->numPoints;
         i < S_IPREC; i++, pos.x += w)
        AddBlock(winMesh, startBlend, &pos, w, h, d, BLOCK_BOTTOM);

    pos.y = 0.0f;
    pos.x = xTrans + WIN_WIDTH - w;
    AddBlock(winMesh, winMesh->numPoints, &pos, w, h, d, BLOCK_RIGHT);

    // draw middle-vertical portion of frame

    pos.x = xTrans + (WIN_WIDTH - WIN_RIGHT_BORDER) / 2.0f - (WIN_CROSSBAR / 2.0f);
    pos.y = WIN_TOP_BORDER;
    pos.z = 0.0f;
    w = WIN_CROSSBAR;
    h = WIN_HEIGHT - 2.0f * WIN_TOP_BORDER;
    AddBlock(winMesh, 0, &pos, w, h, d, BLOCK_ALL | NO_BLEND);

    //
    // add the panels
    //

    w = (WIN_WIDTH - WIN_RIGHT_BORDER - WIN_CROSSBAR) / 2.0f;
    h = (WIN_HEIGHT - 2.0f * WIN_TOP_BORDER - WIN_CROSSBAR) / 2.0f;

    w /= (float)(iPrec / 2);

    curMatl = 2;
    pos.x = xTrans;
    pos.y = WIN_TOP_BORDER;
    for (i = 0, startBlend = winMesh->numPoints; i < iPrec / 2; i++) {
        AddFace(winMesh, startBlend, &pos, w, h);
        pos.x += w;
    }
    curMatl = 4;
    pos.x += WIN_CROSSBAR;
    for (i = 0, startBlend = winMesh->numPoints; i < iPrec / 2; i++) {
        AddFace(winMesh, startBlend, &pos, w, h);
        pos.x += w;
    }

    curMatl = 1;
    pos.x = xTrans;
    pos.y = WIN_TOP_BORDER + h + WIN_CROSSBAR;
    for (i = 0, startBlend = winMesh->numPoints; i < iPrec / 2; i++) {
        AddFace(winMesh, startBlend, &pos, w, h);
        pos.x += w;
    }
    curMatl = 3;
    pos.x += WIN_CROSSBAR;
    for (i = 0, startBlend = winMesh->numPoints; i < iPrec / 2; i++) {
        AddFace(winMesh, startBlend, &pos, w, h);
        pos.x += w;
    }

    ss_normalizeNorms(winMesh->norms, winMesh->numPoints);

    newMesh(winStreamer, CUBE_FACES * WIN_NUMPIECES * WIN_NUMCOLUMNS,
            CUBE_POINTS * WIN_NUMPIECES * WIN_NUMCOLUMNS);

    h = hMax = WIN_TOP_BORDER;
    w = wMax = WIN_TOP_BORDER * 1.1f;

    posCenter.x = pos.x = xTrans - wMax - WIN_GAP_X;
    posCenter.y = pos.y = 0.0f;

    for (i = 0; i < WIN_NUMCOLUMNS; i++) {
        for (j = 0; j < WIN_NUMPIECES; j++) {
            if (((j % 3) == 0) || (i == 0))
                curMatl = 0;
            else if (j < 3)
                curMatl = 2;
            else
                curMatl = 1;
            AddBlock(winStreamer, 0, &pos, w, h, d, BLOCK_ALL);
            pos.y += (hMax + WIN_GAP);
        }

        posCenter.x -= (wMax + WIN_GAP_X);
        posCenter.y = 0.0f;

        h = h * 0.8f;
        w = w * 0.8f;

        pos.x = posCenter.x;
        pos.y = posCenter.y;

        pos.x += (wMax - w) / 2.0f;
        pos.y += (hMax - h) / 2.0f;
    }
    ss_normalizeNorms(winStreamer->norms, winStreamer->numPoints);
}

void initWinScene()
{
    int i;
    float angleDelta;

    iPrec = (int)(fTesselFact * 10.5);
    if (iPrec < 5)
        iPrec = 5;
    if (iPrec > MAXPREC)
        iPrec = MAXPREC;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -0.75, 1.25, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);
    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);

    glLightfv(GL_LIGHT1, GL_AMBIENT, (GLfloat *) &light1Ambient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, (GLfloat *) &light1Diffuse);
    glLightfv(GL_LIGHT1, GL_SPECULAR, (GLfloat *) &light1Specular);
    glLightfv(GL_LIGHT1, GL_POSITION, light1Pos);
    glEnable(GL_LIGHT1);

    glMatrixMode(GL_MODELVIEW);

    glFrontFace(GL_CCW);
    glEnable(GL_CULL_FACE);

    Frames = (int)((float)(MAX_FRAMES / 2) * fTesselFact);

    if (Frames < 5)
        Frames = 5;
    if (Frames > MAX_FRAMES)
        Frames = MAX_FRAMES;

    angleDelta = (float) ((2.0 * PI) / Frames);
    sinAngle = 0.0f;

    for (i = 0; i < Frames; i++) {
        genWin(&winMesh[i], &winStreamer[i]);
        sinAngle += angleDelta;
    }
}

void delWinScene()
{
    int i;

    for (i = 0; i < Frames; i++) {
        delMesh(&winMesh[i]);
        delMesh(&winStreamer[i]);
    }
}

void updateWinScene(int flags)
{
    MESH *mesh;
    MFACE *faces;
    int i;
    static double mxrot = 23.0;
    static double myrot = 23.0;
    static double mzrot = 5.7;
    static double mxrotInc = 0.0;
    static double myrotInc = 3.0;
    static double mzrotInc = 0.0;
    static int h = 0;
    static int frameNum = 0;
    POINT3D *pp;
    POINT3D *pn;
    int lastC, lastD;
    int aOffs, bOffs, cOffs, dOffs;
    int a, b;

    if (bColorCycle) {
        ss_HsvToRgb((float)h, 1.0f, 1.0f, &winColors[0] );

        h++;
        h %= 360;
    }

    glLoadIdentity();
    glRotatef((GLfloat) mxrot, 1.0f, 0.0f, 0.0f);
    glRotatef((GLfloat) myrot, 0.0f, 1.0f, 0.0f);
    glRotatef((GLfloat) mzrot, 0.0f, 0.0f, 1.0f);

    curMatl = 0;
    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (GLfloat *) &winColors[0]);
    glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat *) &matlBrightSpecular);
    glMaterialf(GL_FRONT, GL_SHININESS, 60.0f);

    mesh = &winMesh[frameNum];

    glBegin(GL_QUAD_STRIP);

    pp = mesh->pts;
    pn = mesh->norms;

    for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1];
         i < mesh->numFaces; i++, faces++) {

        a = faces->p[0];
        b = faces->p[1];

if (!bSmoothShading) {
            if ((a != lastC) || (b != lastD)) {
                glNormal3fv((GLfloat *)&(faces - 1)->norm);

                glVertex3fv((GLfloat *)((char *)pp +
                            (lastC << 3) + (lastC << 2)));
                glVertex3fv((GLfloat *)((char *)pp +
                            (lastD << 3) + (lastD << 2)));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            if (faces->material != curMatl) {
                curMatl = faces->material;
                glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
                             (GLfloat *) &matlNoSpecular);
                glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                             (GLfloat *) &winColors[curMatl]);
            }

            glNormal3fv((GLfloat *)&faces->norm);
            glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2)));
            glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2)));
        } else {
            if ((a != lastC) || (b != lastD)) {
                cOffs = (lastC << 3) + (lastC << 2);
                dOffs = (lastD << 3) + (lastD << 2);

                glNormal3fv((GLfloat *)((char *)pn + cOffs));
                glVertex3fv((GLfloat *)((char *)pp + cOffs));
                glNormal3fv((GLfloat *)((char *)pn + dOffs));
                glVertex3fv((GLfloat *)((char *)pp + dOffs));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            aOffs = (a << 3) + (a << 2);
            bOffs = (b << 3) + (b << 2);

            if (faces->material != curMatl) {
                curMatl = faces->material;
                glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
                             (GLfloat *) &matlNoSpecular);
                glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                             (GLfloat *) &winColors[curMatl]);
            }

            glNormal3fv((GLfloat *)((char *)pn + aOffs));
            glVertex3fv((GLfloat *)((char *)pp + aOffs));
            glNormal3fv((GLfloat *)((char *)pn + bOffs));
            glVertex3fv((GLfloat *)((char *)pp + bOffs));
        }

        lastC = faces->p[3];
        lastD = faces->p[2];
    }

    if (!bSmoothShading) {
        glNormal3fv((GLfloat *)&(faces - 1)->norm);
        glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2)));
        glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2)));
    } else {
        cOffs = (lastC << 3) + (lastC << 2);
        dOffs = (lastD << 3) + (lastD << 2);

        glNormal3fv((GLfloat *)((char *)pn + cOffs));
        glVertex3fv((GLfloat *)((char *)pp + cOffs));
        glNormal3fv((GLfloat *)((char *)pn + dOffs));
        glVertex3fv((GLfloat *)((char *)pp + dOffs));
    }

    glEnd();

    glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat *) &matlDimSpecular);

    glBegin(GL_QUADS);

    mesh = &winStreamer[frameNum];

    for (i = 0, faces = mesh->faces;
         i < mesh->numFaces; i++, faces++) {
        int a, b, c, d;

        a = faces->p[0];
        b = faces->p[1];
        c = faces->p[2];
        d = faces->p[3];

        if (faces->material != curMatl) {
            curMatl = faces->material;
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                         (GLfloat *)&winColors[curMatl]);
        }

        glNormal3fv((GLfloat *)&faces->norm);
        glVertex3fv((GLfloat *)(mesh->pts + a));
        glVertex3fv((GLfloat *)(mesh->pts + b));
        glVertex3fv((GLfloat *)(mesh->pts + c));
        glVertex3fv((GLfloat *)(mesh->pts + d));
    }

    glEnd();

    mxrot += mxrotInc;
    myrot += myrotInc;
    mzrot += mzrotInc;

    if ((myrot < -45.0) || (myrot > 45.0))
        myrotInc = -myrotInc;

    frameNum++;
    if (frameNum >= Frames)
        frameNum = 0;
}


MESH.C

/******************************Module*Header*******************************\
* Module Name: mesh.c
*
* Routines to create a mesh representation of a 3D object and to turn it
* into an OpenGL description.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/
#include <stdlib.h>
#include <windows.h>
#include <GL\gl.h>
#include <string.h>
#include <math.h>
#include "ss3dfo.h"
#include "mesh.h"

#define ZERO_EPS    0.00000001

/******************************Public*Routine******************************\
* newMesh
*
* Allocate memory for the mesh structure to accomodate the specified number
* of points and faces.
*
\**************************************************************************/

void newMesh(MESH *mesh, int numFaces, int numPts)
{
    mesh->numFaces = 0;
    mesh->numPoints = 0;

    if (numPts) {
        mesh->pts = SaverAlloc((LONG)numPts * (LONG)sizeof(POINT3D));
        mesh->norms = SaverAlloc((LONG)numPts * (LONG)sizeof(POINT3D));
    }
    mesh->faces = SaverAlloc((LONG)numFaces * (LONG)sizeof(MFACE));
}

/******************************Public*Routine******************************\
* delMesh
*
* Delete the allocated portions of the MESH structure.
*
\**************************************************************************/

void delMesh(MESH *mesh)
{
    SaverFree(mesh->pts);
    SaverFree(mesh->norms);
    SaverFree(mesh->faces);
}

/******************************Public*Routine******************************\
* iPtInList
*
* Add a vertex and its normal to the mesh.  If the vertex already exists,
* add in the normal to the existing normal (we to accumulate the average
* normal at each vertex).  Normalization of the normals is the
* responsibility of the caller.
*
\**************************************************************************/

static int iPtInList(MESH *mesh, POINT3D *p, POINT3D *norm, int start)
{
    int i;
    POINT3D *pts = mesh->pts + start;

    for (i = start; i < mesh->numPoints; i++, pts++)
    {
    // If the vertices are within ZERO_EPS of each other, then its the same
    // vertex.

        if ( fabs(pts->x - p->x) < ZERO_EPS &&
             fabs(pts->y - p->y) < ZERO_EPS &&
             fabs(pts->z - p->z) < ZERO_EPS )
        {
            mesh->norms[i].x += norm->x;
            mesh->norms[i].y += norm->y;
            mesh->norms[i].z += norm->z;
            return i;
        }
    }

    mesh->pts[i] = *p;
    mesh->norms[i] = *norm;
    mesh->numPoints++;
    return i;
}

/******************************Public*Routine******************************\
* revolveSurface
*
* Takes the set of points in curve and fills the mesh structure with a
* surface of revolution.  The surface consists of quads made up of the
* points in curve rotated about the y-axis.  The number of increments
* in the revolution is determined by the steps parameter.
*
\**************************************************************************/

#define MAXPREC 40

void revolveSurface(MESH *mesh, POINT3D *curve, int steps)
{
    int i;
    int j;
    int facecount = 0;
    double rotation = 0.0;
    double rotInc;
    double cosVal;
    double sinVal;
    int stepsSqr;
    POINT3D norm;
    POINT3D a[MAXPREC + 1];
    POINT3D b[MAXPREC + 1];

    if (steps > MAXPREC)
        steps = MAXPREC;
    rotInc = (2.0 * PI) / (double)(steps - 1);
    stepsSqr = steps * steps;
    newMesh(mesh, stepsSqr, 4 * stepsSqr);

    for (j = 0; j < steps; j++, rotation += rotInc) {
        cosVal = cos(rotation);
        sinVal = sin(rotation);
        for (i = 0; i < steps; i++) {
            a[i].x = (float) (curve[i].x * cosVal + curve[i].z * sinVal);
            a[i].y = (float) (curve[i].y);
            a[i].z = (float) (curve[i].z * cosVal - curve[i].x * sinVal);
        }

        cosVal = cos(rotation + rotInc);
        sinVal = sin(rotation + rotInc);
        for (i = 0; i < steps; i++) {
            b[i].x = (float) (curve[i].x * cosVal + curve[i].z * sinVal);
            b[i].y = (float) (curve[i].y);
            b[i].z = (float) (curve[i].z * cosVal - curve[i].x * sinVal);
        }

        for (i = 0; i < (steps - 1); i++) {
            ss_calcNorm(&norm, &b[i + 1], &b[i], &a[i]);
            if ((norm.x * norm.x) + (norm.y * norm.y) + (norm.z * norm.z) < 0.9)
                ss_calcNorm(&norm, &a[i], &a[i+1], &b[i + 1]);
            mesh->faces[facecount].material = j & 7;
            mesh->faces[facecount].norm = norm;
            mesh->faces[facecount].p[0] = iPtInList(mesh, &b[i], &norm, 0);
            mesh->faces[facecount].p[1] = iPtInList(mesh, &a[i], &norm, 0);
            mesh->faces[facecount].p[2] = iPtInList(mesh, &b[i + 1], &norm, 0);
            mesh->faces[facecount].p[3] = iPtInList(mesh, &a[i + 1], &norm, 0);
            mesh->numFaces++;
            facecount++;
        }
    }

    ss_normalizeNorms(mesh->norms, mesh->numPoints);
}

/******************************Public*Routine******************************\
* updateObject
*
* Takes the mesh structure and converts the data into OpenGL immediate
* mode commands.
*
\**************************************************************************/

void updateObject(MESH *mesh, BOOL bSmooth)
{
    int i;
    int a, b;
    int aOffs, bOffs, cOffs, dOffs;
    MFACE *faces;
    POINT3D *pp;
    POINT3D *pn;
    int lastC, lastD;

    pp = mesh->pts;
    pn = mesh->norms;

    glBegin(GL_QUAD_STRIP);
    for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1];
         i < mesh->numFaces; i++, faces++) {

        a = faces->p[0];
        b = faces->p[1];

        if (!bSmooth) {
            if ((a != lastC) || (b != lastD)) {
                glNormal3fv((GLfloat *)&(faces - 1)->norm);

                glVertex3fv((GLfloat *)((char *)pp +
                            (lastC << 3) + (lastC << 2)));
                glVertex3fv((GLfloat *)((char *)pp +
                            (lastD << 3) + (lastD << 2)));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            glNormal3fv((GLfloat *)&faces->norm);
            glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2)));
            glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2)));
        } else {
            if ((a != lastC) || (b != lastD)) {
                cOffs = (lastC << 3) + (lastC << 2);
                dOffs = (lastD << 3) + (lastD << 2);

                glNormal3fv((GLfloat *)((char *)pn + cOffs));
                glVertex3fv((GLfloat *)((char *)pp + cOffs));
                glNormal3fv((GLfloat *)((char *)pn + dOffs));
                glVertex3fv((GLfloat *)((char *)pp + dOffs));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            aOffs = (a << 3) + (a << 2);
            bOffs = (b << 3) + (b << 2);

            glNormal3fv((GLfloat *)((char *)pn + aOffs));
            glVertex3fv((GLfloat *)((char *)pp + aOffs));
            glNormal3fv((GLfloat *)((char *)pn + bOffs));
            glVertex3fv((GLfloat *)((char *)pp + bOffs));
        }

        lastC = faces->p[2];
        lastD = faces->p[3];
    }

    if (!bSmooth) {
        glNormal3fv((GLfloat *)&(faces - 1)->norm);
        glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2)));
        glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2)));
    } else {
        cOffs = (lastC << 3) + (lastC << 2);
        dOffs = (lastD << 3) + (lastD << 2);

        glNormal3fv((GLfloat *)((char *)pn + cOffs));
        glVertex3fv((GLfloat *)((char *)pp + cOffs));
        glNormal3fv((GLfloat *)((char *)pn + dOffs));
        glVertex3fv((GLfloat *)((char *)pp + dOffs));
    }

    glEnd();
}

/******************************Public*Routine******************************\
* updateObject
*
* This is a special case that handles a mesh structure that represents
* a strip that is a 1 high loop.
*
* Takes the mesh structure and converts the data into OpenGL immediate
* mode commands.
*
\**************************************************************************/

void updateObject2(MESH *mesh, BOOL bSmooth)
{
    int i;
    int a, b;
    int aOffs, bOffs, cOffs, dOffs;
    MFACE *faces;
    POINT3D *pp;
    POINT3D *pn;
    int lastC, lastD;

    pp = mesh->pts;
    pn = mesh->norms;

    glBegin(GL_QUAD_STRIP);
    for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1];
         i < mesh->numFaces; i++, faces++) {

        a = faces->p[0];
        b = faces->p[1];

        if (!bSmooth) {
            glNormal3fv((GLfloat *)&faces->norm);
            glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2)));
            glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2)));
        } else {
            aOffs = (a << 3) + (a << 2);
            bOffs = (b << 3) + (b << 2);

            glNormal3fv((GLfloat *)((char *)pn + aOffs));
            glVertex3fv((GLfloat *)((char *)pp + aOffs));
            glNormal3fv((GLfloat *)((char *)pn + bOffs));
            glVertex3fv((GLfloat *)((char *)pp + bOffs));
        }

        lastC = faces->p[2];
        lastD = faces->p[3];
    }

    if (!bSmooth) {
        glNormal3fv((GLfloat *)&(mesh->faces)->norm);
        glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2)));
        glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2)));
    } else {
        cOffs = (lastC << 3) + (lastC << 2);
        dOffs = (lastD << 3) + (lastD << 2);

        glNormal3fv((GLfloat *)((char *)pn + cOffs));
        glVertex3fv((GLfloat *)((char *)pp + cOffs));
        glNormal3fv((GLfloat *)((char *)pn + dOffs));
        glVertex3fv((GLfloat *)((char *)pp + dOffs));
    }

    glEnd();
}

/******************************Public*Routine******************************\
* MakeList
*
* Takes the mesh structure and converts the data into OpenGL display
* list.
*
\**************************************************************************/

void MakeList(GLuint listID, MESH *mesh)
{
    int i;
    int a, b;
    int aOffs, bOffs, cOffs, dOffs;
    MFACE *faces;
    BOOL bSmooth;
    POINT3D *pp;
    POINT3D *pn;
    GLint shadeModel;
    int lastC, lastD;

    glGetIntegerv(GL_SHADE_MODEL, &shadeModel);

    bSmooth = (shadeModel == GL_SMOOTH);

    glNewList(listID, GL_COMPILE);

    pp = mesh->pts;
    pn = mesh->norms;

    glBegin(GL_QUAD_STRIP);
    for (i = 0, faces = mesh->faces, lastC = faces->p[0], lastD = faces->p[1];
         i < mesh->numFaces; i++, faces++) {

        a = faces->p[0];
        b = faces->p[1];

        if (!bSmooth) {

            if ((a != lastC) || (b != lastD)) {
                glNormal3fv((GLfloat *)&((faces - 1)->norm));

                glVertex3fv((GLfloat *)((char *)pp +
                            (lastC << 3) + (lastC << 2)));
                glVertex3fv((GLfloat *)((char *)pp +
                            (lastD << 3) + (lastD << 2)));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            glNormal3fv((GLfloat *)&faces->norm);
            glVertex3fv((GLfloat *)((char *)pp + (a << 3) + (a << 2)));
            glVertex3fv((GLfloat *)((char *)pp + (b << 3) + (b << 2)));
        } else {
            if ((a != lastC) || (b != lastD)) {
                cOffs = (lastC << 3) + (lastC << 2);
                dOffs = (lastD << 3) + (lastD << 2);

                glNormal3fv((GLfloat *)((char *)pn + cOffs));
                glVertex3fv((GLfloat *)((char *)pp + cOffs));
                glNormal3fv((GLfloat *)((char *)pn + dOffs));
                glVertex3fv((GLfloat *)((char *)pp + dOffs));
                glEnd();
                glBegin(GL_QUAD_STRIP);
            }

            aOffs = (a << 3) + (a << 2);
            bOffs = (b << 3) + (b << 2);

            glNormal3fv((GLfloat *)((char *)pn + aOffs));
            glVertex3fv((GLfloat *)((char *)pp + aOffs));
            glNormal3fv((GLfloat *)((char *)pn + bOffs));
            glVertex3fv((GLfloat *)((char *)pp + bOffs));
        }

        lastC = faces->p[2];
        lastD = faces->p[3];
    }

    if (!bSmooth) {
        glNormal3fv((GLfloat *)&((faces - 1)->norm));
        glVertex3fv((GLfloat *)((char *)pp + (lastC << 3) + (lastC << 2)));
        glVertex3fv((GLfloat *)((char *)pp + (lastD << 3) + (lastD << 2)));
    } else {
        cOffs = (lastC << 3) + (lastC << 2);
        dOffs = (lastD << 3) + (lastD << 2);

        glNormal3fv((GLfloat *)((char *)pn + cOffs));
        glVertex3fv((GLfloat *)((char *)pp + cOffs));
        glNormal3fv((GLfloat *)((char *)pn + dOffs));
        glVertex3fv((GLfloat *)((char *)pp + dOffs));
    }

    glEnd();

    glEndList();
}


MESH.H

/******************************Module*Header*******************************\
* Module Name: mesh.h
*
* Declaration of the mesh routines.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

extern void newMesh(MESH *mesh, int numFaces, int numPts);
extern void delMesh(MESH *mesh);
extern void revolveSurface(MESH *mesh, POINT3D *curve, int steps);
extern void updateObject(MESH *mesh, BOOL bSmooth);
extern void updateObject2(MESH *mesh, BOOL bSmooth);
extern void MakeList(GLuint listID, MESH *mesh);


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by ss3dfo.rc
//
#define IDI_ICON1                       103
#define IDI_ICON2                       104
#define IDC_SLIDER1                     1013
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         101
#define _APS_NEXT_CONTROL_VALUE         1014
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

#include "commctrl.h"
#include "ss3dfo.h"
#include "sscommon.h"
#include "sscommon.rc"


SS3DFO.C

/******************************Module*Header*******************************\
* Module Name: ss3dfo.c
*
* Dispatcher and dialog box for the OpenGL-based 3D Flying Objects screen
* saver.
*
* Created: 18-May-1994 14:54:59
*
* Copyright (c) 1994 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys\timeb.h>
#include <time.h>
#include <commdlg.h>
#include <commctrl.h>
#include "ss3dfo.h"

//#define SS_DEBUG 1

// Global strings.
#define GEN_STRING_SIZE 64

static SSContext gssc;
BOOL gbBounce = FALSE; // floating window bounce off side

// Global message loop variables.
MATERIAL Material[16];
#ifdef MEMDEBUG
ULONG totalMem = 0;
#endif

// Global screen saver settings.

void (*updateSceneFunc)(int); // current screen saver update function
void (*delSceneFunc)(void);         // current screen saver deletion function
BOOL bColorCycle = FALSE;           // color cycling flag
BOOL bSmoothShading = TRUE;         // smooth shading flag
UINT uSize = 100;                   // object size
float fTesselFact = 1.0f;           // object tessalation
int UpdateFlags = 0;                // extra data sent to update function
int Type = 0;                       // screen saver index (into function arrays)

// Texture file information
TEXFILE gTexFile = {0};

// Lighting properties.

static const RGBA lightAmbient   = {0.21f, 0.21f, 0.21f, 1.0f};
static const RGBA light0Ambient  = {0.0f, 0.0f, 0.0f, 1.0f};
static const RGBA light0Diffuse  = {0.7f, 0.7f, 0.7f, 1.0f};
static const RGBA light0Specular = {1.0f, 1.0f, 1.0f, 1.0f};
static const GLfloat light0Pos[]      = {100.0f, 100.0f, 100.0f, 0.0f};

// Material properties.

static RGBA matlColors[7] = {{1.0f, 0.0f, 0.0f, 1.0f},
                             {0.0f, 1.0f, 0.0f, 1.0f},
                             {0.0f, 0.0f, 1.0f, 1.0f},
                             {1.0f, 1.0f, 0.0f, 1.0f},
                             {0.0f, 1.0f, 1.0f, 1.0f},
                             {1.0f, 0.0f, 1.0f, 1.0f},
                             {0.235f, 0.0f, 0.78f, 1.0f},
                            };

extern void updateStripScene(int);
extern void updateDropScene(int);
extern void updateLemScene(int);
extern void updateExplodeScene(int);
extern void updateWinScene(int);
extern void updateTexScene(int);
extern void initStripScene(void);
extern void initDropScene(void);
extern void initLemScene(void);
extern void initExplodeScene(void);
extern void initWinScene(void);
extern void initTexScene(void);
extern void delStripScene(void);
extern void delDropScene(void);
extern void delLemScene(void);
extern void delExplodeScene(void);
extern void delWinScene(void);
extern void delTexScene(void);

typedef void (*PTRUPDATE)(int);
typedef void (*ptrdel)();
typedef void (*ptrinit)();

// Each screen saver style puts its hook functions into the function
// arrays below.  A consistent ordering of the functions is required.

static PTRUPDATE updateFuncs[] =
    {updateWinScene, updateExplodeScene,updateStripScene, updateStripScene,
     updateDropScene, updateLemScene, updateTexScene};
static ptrdel delFuncs[] =
    {delWinScene, delExplodeScene, delStripScene, delStripScene,
     delDropScene, delLemScene, delTexScene};
static ptrinit initFuncs[] =
    {initWinScene, initExplodeScene, initStripScene, initStripScene,
     initDropScene, initLemScene, initTexScene};
static int idsStyles[] =
    {IDS_LOGO, IDS_EXPLODE, IDS_RIBBON, IDS_2RIBBON,
     IDS_SPLASH, IDS_TWIST, IDS_FLAG};

#define MAX_TYPE    ( sizeof(initFuncs) / sizeof(ptrinit) - 1 )

// Each screen saver style can choose which dialog box controls it wants
// to use.  These flags enable each of the controls.  Controls not choosen
// will be disabled.

#define OPT_COLOR_CYCLE     0x00000001
#define OPT_SMOOTH_SHADE    0x00000002
#define OPT_TESSEL          0x00000008
#define OPT_SIZE            0x00000010
#define OPT_TEXTURE         0x00000020
#define OPT_STD             ( OPT_COLOR_CYCLE | OPT_SMOOTH_SHADE | OPT_TESSEL | OPT_SIZE )

static ULONG gflConfigOpt[] = {
     OPT_STD,               // Windows logo
     OPT_STD,               // Explode
     OPT_STD,               // Strip
     OPT_STD,               // Strip
     OPT_STD,               // Drop
     OPT_STD,               // Twist (lemniscate)
     OPT_SMOOTH_SHADE | OPT_TESSEL | OPT_SIZE | OPT_TEXTURE  // Texture mapped flag
};

static void updateDialogControls(HWND hDlg);

#ifdef MEMDEBUG
void xprintf(char *str, ...)
{
    va_list ap;
    char buffer[256];

    va_start(ap, str);
    vsprintf(buffer, str, ap);

    OutputDebugString(buffer);
    va_end(ap);
}
#endif

void *SaverAlloc(ULONG size)
{
    void *mPtr;

    mPtr = malloc(size);
#ifdef MEMDEBUG
    totalMem += size;
    xprintf("malloc'd %x, size %d\n", mPtr, size);
#endif
    return mPtr;
}

void SaverFree(void *pMem)
{
#ifdef MEMDEBUG
    totalMem -= _msize(pMem);
    xprintf("free %x, size = %d, total = %d\n", pMem, _msize(pMem), totalMem);
#endif
    free(pMem);
}

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static void getIniSettings()
{
    int    options;
    int    optMask = 1;
    TCHAR  szDefaultBitmap[MAX_PATH];
    int    tessel=0;

    LoadString(hMainInstance, IDS_GENNAME, szScreenSaver, sizeof(szScreenSaver) / sizeof(TCHAR));

    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        options = ss_GetRegistryInt( IDS_OPTIONS, -1 );
        if (options >= 0)
        {
            bSmoothShading = ((options & optMask) != 0);
            optMask <<= 1;
            bColorCycle = ((options & optMask) != 0);
            UpdateFlags = (bColorCycle << 1);
        }

        Type = ss_GetRegistryInt( IDS_OBJTYPE, 0 );

        // Sanity check Type.  Don't want to index into function arrays
        // with a bad index!
        Type = min(Type, MAX_TYPE);

        // Set flag so that updateStripScene will do two strips instead
        // of one.

        if (Type == 3)
            UpdateFlags |= 0x4;

        tessel = ss_GetRegistryInt( IDS_TESSELATION, 100 );
        SS_CLAMP_TO_RANGE2( tessel, 0, 200 );

        if (tessel <= 100)
            fTesselFact  = (float)tessel / 100.0f;
        else
            fTesselFact = 1.0f + (((float)tessel - 100.0f) / 100.0f);

        uSize = ss_GetRegistryInt( IDS_SIZE, 50 );
        SS_CLAMP_TO_RANGE2( uSize, 0, 100 );

        // Determine the default .bmp file

        ss_GetDefaultBmpFile( szDefaultBitmap );

        // Is there a texture specified in the registry that overrides the
        // default?

        ss_GetRegistryString( IDS_TEXTURE, szDefaultBitmap, gTexFile.szPathName,
                              MAX_PATH);

        gTexFile.nOffset = ss_GetRegistryInt( IDS_TEXTURE_FILE_OFFSET, 0 );
    }
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
*
* History:
*  10-May-1994 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

static void saveIniSettings(HWND hDlg)
{
    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        int pos, options;
        int optMask = 1;

        bSmoothShading = IsDlgButtonChecked(hDlg, DLG_SETUP_SMOOTH);
        bColorCycle = IsDlgButtonChecked(hDlg, DLG_SETUP_CYCLE);
        options = bColorCycle;
        options <<= 1;
        options |= bSmoothShading;
        ss_WriteRegistryInt( IDS_OPTIONS, options );

        Type = SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_GETCURSEL,
                                  0, 0);
        ss_WriteRegistryInt( IDS_OBJTYPE, Type );

        pos = ss_GetTrackbarPos( hDlg, DLG_SETUP_TESSEL );
        ss_WriteRegistryInt( IDS_TESSELATION, pos );

        pos = ss_GetTrackbarPos( hDlg, DLG_SETUP_SIZE );
        ss_WriteRegistryInt( IDS_SIZE, pos );

        ss_WriteRegistryString( IDS_TEXTURE, gTexFile.szPathName );
        ss_WriteRegistryInt( IDS_TEXTURE_FILE_OFFSET, gTexFile.nOffset );
    }
}

/******************************Public*Routine******************************\
* setupDialogControls
*
* Setup the dialog controls initially.
*
\**************************************************************************/

static void
setupDialogControls(HWND hDlg)
{
    int pos;

    InitCommonControls();

    if ( gflConfigOpt[Type] & OPT_TESSEL )
    {
        if (fTesselFact <= 1.0f)
            pos = (int)(fTesselFact * 100.0f);
        else
            pos = 100 + (int) ((fTesselFact - 1.0f) * 100.0f);

        ss_SetupTrackbar( hDlg, DLG_SETUP_TESSEL, 0, 200, 1, 10, pos );
    }

    if ( gflConfigOpt[Type] & OPT_SIZE )
    {
        ss_SetupTrackbar( hDlg, DLG_SETUP_SIZE, 0, 100, 1, 10, uSize );
    }

    updateDialogControls( hDlg );
}

/******************************Public*Routine******************************\
* updateDialogControls
*
* Update the dialog controls based on the current global state.
*
\**************************************************************************/

static void
updateDialogControls(HWND hDlg)
{
    CheckDlgButton(hDlg, DLG_SETUP_SMOOTH, bSmoothShading);
    CheckDlgButton(hDlg, DLG_SETUP_CYCLE, bColorCycle);

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_SMOOTH),
                 gflConfigOpt[Type] & OPT_SMOOTH_SHADE );
    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_CYCLE),
                 gflConfigOpt[Type] & OPT_COLOR_CYCLE );

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TESSEL),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MIN),
                 gflConfigOpt[Type] & OPT_TESSEL);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MAX),
                 gflConfigOpt[Type] & OPT_TESSEL);

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_SIZE),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE_MIN),
                 gflConfigOpt[Type] & OPT_SIZE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_SIZE_MAX),
                 gflConfigOpt[Type] & OPT_SIZE);

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TEXTURE),
                 gflConfigOpt[Type] & OPT_TEXTURE);
}

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

/******************************Public*Routine******************************\
* ScreenSaverConfigureDialog
*
* Processes messages for the configuration dialog box.
*
\**************************************************************************/

BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{
    int wTmp;
    TCHAR szString[GEN_STRING_SIZE];

    switch (message) {
        case WM_INITDIALOG:
            getIniSettings();

            setupDialogControls(hDlg);

            for (wTmp = 0; wTmp <= MAX_TYPE; wTmp++) {
                LoadString(hMainInstance, idsStyles[wTmp], szString, GEN_STRING_SIZE);
                SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_ADDSTRING, 0,
                                   (LPARAM) szString);
            }
            SendDlgItemMessage(hDlg, DLG_SETUP_TYPES, CB_SETCURSEL, Type, 0);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DLG_SETUP_TYPES:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                            Type = SendDlgItemMessage(hDlg, DLG_SETUP_TYPES,
                                                      CB_GETCURSEL, 0, 0);
                            updateDialogControls(hDlg);
                            break;
                        default:
                            break;
                    }
                    return FALSE;

                case DLG_SETUP_TEXTURE:
                    ss_SelectTextureFile( hDlg, &gTexFile );
                    break;

                case IDOK:
                    saveIniSettings(hDlg);
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;

                case DLG_SETUP_SMOOTH:
                case DLG_SETUP_CYCLE:
                default:
                    break;
            }
            return TRUE;
            break;

        default:
            return 0;
    }
    return 0;
}

/******************************Public*Routine******************************\
* SetFloaterInfo
*
* Set the size and motion of the floating window
* It stays square in shape
*
\**************************************************************************/

static void
SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild )
{
    float sizeFact;
    float sizeScale;
    int size;
    ISIZE *pChildSize = &pChild->size;
    MOTION_INFO *pMotion = &pChild->motionInfo;

    sizeScale = (float)uSize / 100.0f; // 0..1
    sizeFact = 0.25f + (0.30f * sizeScale);
    size = (int) (sizeFact * ( ((float)(pParentSize->width + pParentSize->height)) / 2.0f ));
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->width );
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->height );

    pChildSize->width = pChildSize->height = size;

    // Floater motion
    pMotion->posInc.x = .01f * (float) size;
    if( pMotion->posInc.x < 1.0f )
        pMotion->posInc.x = 1.0f;
    pMotion->posInc.y = pMotion->posInc.x;
    pMotion->posIncVary.x = .4f * pMotion->posInc.x;
    pMotion->posIncVary.y = pMotion->posIncVary.x;
}

/******************************Public*Routine******************************\
* initMaterial
*
* Initialize the material properties.
*
\**************************************************************************/

void initMaterial(int id, float r, float g, float b, float a)
{
    Material[id].ka.r = r;
    Material[id].ka.g = g;
    Material[id].ka.b = b;
    Material[id].ka.a = a;

    Material[id].kd.r = r;
    Material[id].kd.g = g;
    Material[id].kd.b = b;
    Material[id].kd.a = a;

    Material[id].ks.r = 1.0f;
    Material[id].ks.g = 1.0f;
    Material[id].ks.b = 1.0f;
    Material[id].ks.a = 1.0f;

    Material[id].specExp = 128.0f;
}

/******************************Public*Routine******************************\
* _3dfo_Init
*
\**************************************************************************/

static void
_3dfo_Init(void *data)
{
    int i;

    for (i = 0; i < 7; i++)
        initMaterial(i, matlColors[i].r, matlColors[i].g,
                     matlColors[i].b, matlColors[i].a);

    // Set the OpenGL clear color to black.

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
#ifdef SS_DEBUG
    glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
#endif

    // Enable the z-buffer.

    glEnable(GL_DEPTH_TEST);

    // Select the shading model.

    if (bSmoothShading)
        glShadeModel(GL_SMOOTH);
    else
        glShadeModel(GL_FLAT);

    // Setup the OpenGL matrices.

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Setup the lighting.

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (GLfloat *) &lightAmbient);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
    glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat *) &light0Ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat *) &light0Diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, (GLfloat *) &light0Specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    // Setup the material properties.

    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *) &Material[0].ks);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, (GLfloat *) &Material[0].specExp);

    // call specific objects init func
    (*initFuncs[Type])();
    updateSceneFunc = updateFuncs[Type];
}

/******************************Public*Routine******************************\
* _3dfo_Draw
*
\**************************************************************************/

static void
_3dfo_Draw(void *data)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    (*updateSceneFunc)(UpdateFlags);
}

/******************************Public*Routine******************************\
* _3dfo_FloaterBounce
*
\**************************************************************************/

static void
_3dfo_FloaterBounce(void *data)
{
    gbBounce = TRUE;
}

/******************************Public*Routine******************************\
* ss_Init
*
* Screen saver entry point.  Pre-GL initialization
*
\**************************************************************************/

SSContext *
ss_Init(void)
{
    getIniSettings();

    // Make sure the selected texture file is OK.

    if ( gflConfigOpt[Type] & OPT_TEXTURE )
    {
        // Verify texture file
        ss_DisableTextureErrorMsgs();
        ss_VerifyTextureFile( &gTexFile );
    }

    // set callbacks

    ss_InitFunc( _3dfo_Init );
    ss_UpdateFunc( _3dfo_Draw );

    // set configuration info to return

    gssc.bDoubleBuf = TRUE;
    gssc.depthType = SS_DEPTH16;

    gssc.bFloater = TRUE;
    gssc.floaterInfo.bMotion = TRUE;
    gssc.floaterInfo.ChildSizeFunc = SetFloaterInfo;
    ss_FloaterBounceFunc( _3dfo_FloaterBounce );

    return &gssc;
}

/**************************************************************************\
* ConfigInit
*
* Dialog box version of ss_Init.  Used for setting up any gl drawing on the
* dialog.
*
\**************************************************************************/
BOOL
ss_ConfigInit( HWND hDlg )
{
    return TRUE;
}


SS3DFO.H

/******************************Module*Header*******************************\
* Module Name: ssopengl.h
*
* Global header for the 3D Flying Objects screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include "sscommon.h"

// redefine PI as a double for all 3dfo code
#undef PI
#define PI PI_D

#define PALETTE_PER_MATL    32
#define PALETTE_PER_DIFF    26
#define PALETTE_PER_SPEC    6
#define MATL_MAX            7

typedef struct strFACE {
    POINT3D p[4];
    POINT3D n[4];
    POINT3D fn;
    int idMatl;
} FACE;

typedef struct strMFACE {
    int p[4];
    int material;
    POINT3D norm;
} MFACE;

typedef struct strMESH {
    int numFaces;
    int numPoints;
    POINT3D *pts;
    POINT3D *norms;
    MFACE *faces;
    GLint listID;
} MESH;

extern BOOL bSmoothShading;
extern BOOL bFalseColor;
extern BOOL bColorCycle;
extern float fTesselFact;
extern TEXFILE gTexFile;
extern BOOL gbBounce;

extern MATERIAL Material[];
extern int NumLights;

extern void newMesh(MESH *, int numFaces, int numPts);
extern void delMesh(MESH *);
extern void revolveSurface(MESH *, POINT3D *curve, int steps);

extern void *SaverAlloc(ULONG);
extern void SaverFree(void *);

// Resource constants

#define IDS_SAVERNAME           1002
#define IDS_FALSECOLOR          1004
#define IDS_SMOOTHSHADING       1005
#define IDS_OPTIONS             1007
#define IDS_OBJTYPE             1008
#define IDS_SCREENSAVERTITLE    1020

#define IDS_LOGO                1100
#define IDS_EXPLODE             1101
#define IDS_RIBBON              1102
#define IDS_2RIBBON             1103
#define IDS_SPLASH              1104
#define IDS_TWIST               1105
#define IDS_FLAG                1106

#define DLG_SETUP_HELP          2001
#define DLG_SETUP_TYPES         2002
#define DLG_SETUP_BITMAP        2003
#define DLG_SETUP_FCOLOR        2004
#define DLG_SETUP_SMOOTH        2005
#define DLG_SETUP_CYCLE         2006
#define DLG_SETUP_ABOUT         2007
#define DLG_SETUP_TESSEL        2008
#define DLG_SETUP_SIZE          2009
#define DLG_SETUP_TEXTURE       2010
#define IDC_STATIC_TESS         2011
#define IDC_STATIC_TESS_MIN     2012
#define IDC_STATIC_TESS_MAX     2013
#define IDC_STATIC_SIZE         2014
#define IDC_STATIC_SIZE_MIN     2015
#define IDC_STATIC_SIZE_MAX     2016

#define IDB_DEFTEX              3000

#define SHELP_CONTENTS          01
#define SHELP_SHAPES            02
#define SHELP_PASSWORD          03
#define SHELP_COLOR             04
#define SHELP_MISC              05
#define SHELP_OVERVIEW          06


CLEAR.CXX

/******************************Module*Header*******************************\
* Module Name: clear.cxx
*
* Window clearing functions
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <sys/timeb.h>
#include <GL/gl.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <math.h>
#include "ssintrnl.hxx"
#include "util.hxx"
#include "clear.hxx"

#define SS_CLEAR_BASE_DIV 32
#define SS_CLEAR_BASE_SIZE 16

/******************************Public*Routine******************************\
* ss_RectWipeClear
*
* Clears by drawing top, bottom, left, right rectangles that shrink in size
* towards the center.
*
* Calibration is used to try to maintain an ideal clear time.
*
\**************************************************************************/

int
ss_RectWipeClear( int width, int height, int repCount )
{
    int i, j, xinc, yinc, numDivs;
    int xmin, xmax, ymin, ymax;
    int w, h;
    BOOL bCalibrate = FALSE;
    double elapsed;
    static double idealTime = 0.7;
    SS_TIMER timer;

    xinc = 1;
    yinc = 1;
    numDivs = height; // assumes height <= width

    xmin = ymin = 0;
    xmax = width-1;
    ymax = height-1;

    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );

    glEnable( GL_SCISSOR_TEST );

    if( repCount == 0 ) {
        bCalibrate = TRUE;
        repCount = 1;
        timer.Start();
    }

    for( i = 0; i < (numDivs/2); i ++ ) {
      w = xmax - xmin + 1;
      h = ymax - ymin + 1;

      for( j = 0; j < repCount; j ++ ) {
        // bottom
        glScissor( xmin, ymin, w, 1 );
        glClear( GL_COLOR_BUFFER_BIT );

        // left
        glScissor( xmin, ymin, 1, h );
        glClear( GL_COLOR_BUFFER_BIT );

        // right
        glScissor( xmax, ymin, 1, h );
        glClear( GL_COLOR_BUFFER_BIT );

        // top
        glScissor( xmin, ymax, w, 1 );
        glClear( GL_COLOR_BUFFER_BIT );

        glFlush(); // to eliminate 'bursts'
      }

      xmin += xinc;
      xmax -= xinc;
      ymin += yinc;
      ymax -= yinc;
    }

    if( bCalibrate ) {
        elapsed = timer.Stop();

        // try to maintain ideal clear time
        if( elapsed < idealTime ) {
            // increase repCount to slow down the clear
            if( elapsed == 0.0 )
                repCount = 10;  // just in case
            else
                repCount = (int) ((idealTime / elapsed) + 0.5);
        }
    }

    glDisable( GL_SCISSOR_TEST );

    return repCount;
}

#define SS_CLEAR_BASE_DIV 32

/******************************Public*Routine******************************\
*
* SS_DIGITAL_DISSOLVE_CLEAR constructor
*
\**************************************************************************/

SS_DIGITAL_DISSOLVE_CLEAR::
SS_DIGITAL_DISSOLVE_CLEAR()
{
    rectBuf = NULL;
    rectBufSize = 0;
    rectSize = SS_CLEAR_BASE_SIZE;
}

/******************************Public*Routine******************************\
*
* SS_DIGITAL_DISSOLVE_CLEAR destructor
*
\**************************************************************************/

SS_DIGITAL_DISSOLVE_CLEAR::
~SS_DIGITAL_DISSOLVE_CLEAR()
{
    if( rectBuf )
        LocalFree( rectBuf );
}

/******************************Public*Routine******************************\
* CalibrateClear
*
* Try to calibrate the clear so it takes the specified time
\**************************************************************************/

int SS_DIGITAL_DISSOLVE_CLEAR::
CalibrateClear( int width, int height, float fClearTime )
{
    float factor;
    int idealNRects;
    int nRects;
    int baseSize;
    SS_TIMER timer;
    float elapsed;

    baseSize = (width < height ? width : height) / SS_CLEAR_BASE_DIV;
    if( baseSize == 0 )
        baseSize = 1;

    timer.Start();

#ifdef AUTO_CALIBRATE
    // Clear a small region (quarter-screen) and extrapolate
    Clear( width >> 1, height >> 1, baseSize );
#else
    Clear( width, height, baseSize );
#endif

    elapsed = timer.Stop();
#ifdef AUTO_CALIBRATE
    // extrapolate to full screen time
    // this approximation resulted in clears being somewhat less than ideal
    elapsed *= 4.0f;
#endif

    // Adjust size of rects for ideal clear time

    if( elapsed <= 0.0f ) {
        rectSize = 1;
        return rectSize;
    }

    nRects = RectangleCount( width, height, baseSize );
    factor = fClearTime / elapsed;
    idealNRects = (int) (factor * (float)nRects);
    rectSize = (int) (sqrt( (double)(width*height) / (double)idealNRects ) + 0.5);
    if( rectSize == 0 )
        rectSize = 1;

    return rectSize;
}

/******************************Public*Routine******************************\
*
* SS_DIGITAL_DISSOLVE_CLEAR::Clear
*
* Clears by drawing random rectangles
*
\**************************************************************************/

BOOL SS_DIGITAL_DISSOLVE_CLEAR::
Clear( int width, int height )
{
    return Clear( width, height, rectSize );
}

BOOL SS_DIGITAL_DISSOLVE_CLEAR::
Clear( int width, int height, int size )
{
    BOOL *pRect;
    BOOL bCalibrate = FALSE;
    int count, nRects;
    int i, xdim, ydim;
    static float idealTime = 2.0f;

    if( (size <= 0) || !width || !height )
        return FALSE;

    // determine xdim, ydim from size
    xdim = SS_ROUND_UP( (float)width / (float)size );
    ydim = SS_ROUND_UP( (float)height / (float) size );

    // figure out how many rects needed
    count = nRects = xdim * ydim;

    // make sure enough room
    if( !ValidateBufSize( nRects ) )
        return FALSE;

    // reset the rect array to uncleared

    pRect = rectBuf;
    for( i = 0; i < count; i ++, pRect++ )
        *pRect = FALSE;

    // Clear random rectangles

    glEnable( GL_SCISSOR_TEST );

    while( count ) {
        // pick a random rect
        i = ss_iRand( nRects );

        if( rectBuf[i] ) {
            // This rect has already been cleared - find an empty one
            // Scan up and down from x,y, looking at the array linearly

            int up, down;
            BOOL searchUp;

            up = down = i;

            pRect = rectBuf;
            while( *(pRect + i) ) {
                if( searchUp ) {
                    // search up side
                    if( up < (nRects-1) ) {
                        up++;
                    }
                    i = up;
                } else {
                    // search down side
                    if( down > 0 ) {
                        down--;
                    }
                    i = down;
                }
                searchUp = !searchUp;
            }
        }

        // clear the x,y rect
        glScissor( (i % xdim)*size, (i / xdim)*size, size, size );
        glClear( GL_COLOR_BUFFER_BIT );
        glFlush();

        rectBuf[i] = TRUE; // mark as taken
        count--;
    }

    glDisable( GL_SCISSOR_TEST );

    return TRUE;
}

/******************************Public*Routine******************************\
* RectangleCount
*
\**************************************************************************/

int SS_DIGITAL_DISSOLVE_CLEAR::
RectangleCount( int width, int height, int size )
{
    return  SS_ROUND_UP( (float)width / (float)size ) *
            SS_ROUND_UP( (float)height / (float) size );
}

/******************************Public*Routine******************************\
* ValidateBufSize
*
\**************************************************************************/

BOOL SS_DIGITAL_DISSOLVE_CLEAR::
ValidateBufSize( int nRects )
{
    if( nRects > rectBufSize ) {
        // need a bigger rect buf
        BOOL *r = (BOOL *) LocalAlloc( LMEM_FIXED, sizeof(BOOL) * nRects );
        if( !r )
            return FALSE;
        if( rectBuf )
            LocalFree( rectBuf );
        rectBuf = r;
        rectBufSize = nRects;
    }
    return TRUE;
}

/******************************Public*Routine******************************\
* DrawGdiRect
*
* Clears the rect with the brush
\**************************************************************************/

void
DrawGdiRect( HDC hdc, HBRUSH hbr, RECT *pRect )
{
    if( pRect == NULL )
        return;

    FillRect( hdc, pRect, hbr );
    GdiFlush();
}

#ifdef SS_INITIAL_CLEAR
/*-----------------------------------------------------------------------
|
|    RectWipeClear(width, height):
|       - Does a rectangular wipe (or clear) by drawing in a sequence
|         of rectangles using Gdi
|       MOD: add calibrator capability to adjust speed for different
|            architectures
|       MOD: this can be further optimized by caching the brush
|
-----------------------------------------------------------------------*/
void
ss_GdiRectWipeClear( HWND hwnd, int width, int height )
{
    HDC hdc;
    HBRUSH hbr;
    RECT rect;
    int i, j, xinc, yinc, numDivs = 500;
    int xmin, xmax, ymin, ymax;
    int repCount = 10;

    xinc = 1;
    yinc = 1;
    numDivs = height;
    xmin = ymin = 0;
    xmax = width;
    ymax = height;

    hdc = GetDC( hwnd );

    hbr = CreateSolidBrush( RGB( 0, 0, 0 ) );

    for( i = 0; i < (numDivs/2 - 1); i ++ ) {
      for( j = 0; j < repCount; j ++ ) {
        rect.left = xmin; rect.top = ymin;
        rect.right = xmax; rect.bottom = ymin + yinc;
        FillRect( hdc, &rect, hbr );
        rect.top = ymax - yinc;
        rect.bottom = ymax;
        FillRect( hdc, &rect, hbr );
        rect.top = ymin + yinc;
        rect.right = xmin + xinc; rect.bottom = ymax - yinc;
        FillRect( hdc, &rect, hbr );
        rect.left = xmax - xinc; rect.top = ymin + yinc;
        rect.right = xmax; rect.bottom = ymax - yinc;
        FillRect( hdc, &rect, hbr );
      }

      xmin += xinc;
      xmax -= xinc;
      ymin += yinc;
      ymax -= yinc;
    }

    // clear last square in middle

    rect.left = xmin; rect.top = ymin;
    rect.right = xmax; rect.bottom = ymax;
    FillRect( hdc, &rect, hbr );

    DeleteObject( hbr );

    ReleaseDC( hwnd, hdc );

    GdiFlush();
}
#endif // SS_INITIAL_CLEAR


COLOR.C

#include <windows.h>
#include <math.h>
#include <GL/gl.h>
#include "sscommon.h"

/******************************Public*Routine******************************\
* HsvToRgb
*
* HSV to RGB color space conversion.  From pg. 593 of Foley & van Dam.
*
\**************************************************************************/

void
ss_HsvToRgb(float h, float s, float v, RGBA *color )
{
    float i, f, p, q, t;

    // set alpha value, so caller doesn't have to worry about undefined value
    color->a = 1.0f;

    if (s == 0.0f)     // assume h is undefined
        color->r = color->g = color->b = v;
    else {
        if (h >= 360.0f)
            h = 0.0f;
        h = h / 60.0f;
        i = (float) floor(h);
        f = h - i;
        p = v * (1.0f - s);
        q = v * (1.0f - (s * f));
        t = v * (1.0f - (s * (1.0f - f)));
        switch ((int)i) {
        case 0:
            color->r = v;
            color->g = t;
            color->b = p;
            break;
        case 1:
            color->r = q;
            color->g = v;
            color->b = p;
            break;
        case 2:
            color->r = p;
            color->g = v;
            color->b = t;
            break;
        case 3:
            color->r = p;
            color->g = q;
            color->b = v;
            break;
        case 4:
            color->r = t;
            color->g = p;
            color->b = v;
            break;
        case 5:
            color->r = v;
            color->g = p;
            color->b = q;
            break;
        default:
            break;
        }
    }
}


DIALOG.C

/******************************Module*Header*******************************\
* Module Name: dialog.c
*
* Dialog helper functions
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <commdlg.h>
#include <commctrl.h>
#include "sscommon.h"

#define BUF_SIZE 30
static TCHAR  szSectName[BUF_SIZE];
static TCHAR  szItemName[BUF_SIZE];
static TCHAR  szFname[BUF_SIZE];
static TCHAR  szTmp[BUF_SIZE];
static HINSTANCE hInstance = 0;

/******************************Public*Routine******************************\
* ss_RegistrySetup
*
* Setup for registry access
*
\**************************************************************************/

BOOL ss_RegistrySetup( HINSTANCE hinst, int section, int file )
{
    if( LoadString(hInstance, section, szSectName, BUF_SIZE) &&
        LoadString(hInstance, file, szFname, BUF_SIZE) )
    {
        hInstance = hinst;
        return TRUE;
    }
    return FALSE;
}

/******************************Public*Routine******************************\
* ss_GetRegistryInt
*
* Retrieve integer value from registry
*
\**************************************************************************/

int  ss_GetRegistryInt( int name, int iDefault )
{
    if( LoadString( hInstance, name, szItemName, BUF_SIZE ) ) {
        return GetPrivateProfileInt(szSectName, szItemName, iDefault, szFname);
    }
    return 0;
}

/******************************Public*Routine******************************\
* ss_GetRegistryString
*
* Retrieve string from registry
*
\**************************************************************************/

void ss_GetRegistryString( int name, LPTSTR lpDefault, LPTSTR lpDest,
                           int bufSize )
{
    if( LoadString( hInstance, name, szItemName, BUF_SIZE ) ) {
        GetPrivateProfileString(szSectName, szItemName, lpDefault, lpDest,
                                bufSize, szFname);
    }
}

/******************************Public*Routine******************************\
* ss_WriteRegistryInt
*
* Write integer value to registry
*
\**************************************************************************/

void ss_WriteRegistryInt( int name, int iVal )
{
    if( LoadString(hInstance, name, szItemName, BUF_SIZE) ) {
        wsprintf(szTmp, TEXT("%ld"), iVal);
        WritePrivateProfileString(szSectName, szItemName, szTmp, szFname);
    }
}

/******************************Public*Routine******************************\
* ss_WriteRegistryString
*
* Write string value to registry
*
\**************************************************************************/

void ss_WriteRegistryString( int name, LPTSTR lpString )
{
    if( LoadString(hInstance, name, szItemName, BUF_SIZE) ) {
        WritePrivateProfileString(szSectName, szItemName, lpString, szFname);
    }
}

/******************************Public*Routine******************************\
* GetTrackbarPos
*
* Get the current position of a common control trackbar
\**************************************************************************/

int
ss_GetTrackbarPos( HWND hDlg, int item )
{
    return
        SendDlgItemMessage(
            hDlg,
            item,
            TBM_GETPOS,
            0,
            0
        );
}

/******************************Public*Routine******************************\
* SetupTrackbar
*
* Setup a common control trackbar
\**************************************************************************/

void
ss_SetupTrackbar( HWND hDlg, int item, int lo, int hi, int lineSize,
                  int pageSize, int pos )
{
    SendDlgItemMessage(
        hDlg,
        item,
        TBM_SETRANGE,
        (WPARAM) TRUE,
        (LPARAM) MAKELONG( lo, hi )
    );
    SendDlgItemMessage(
        hDlg,
        item,
        TBM_SETPOS,
        (WPARAM) TRUE,
        (LPARAM) pos
    );
    SendDlgItemMessage(
        hDlg,
        item,
        TBM_SETPAGESIZE,
        (WPARAM) 0,
        (LPARAM) pageSize
    );
    SendDlgItemMessage(
        hDlg,
        item,
        TBM_SETLINESIZE,
        (WPARAM) 0,
        (LPARAM) lineSize
    );
}


DLGDRAW.CXX

/******************************Module*Header*******************************\
* Module Name: dlgdraw.c
*
* For gl drawing in dialog boxes
*
* Copyright 1995 - 1998 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "ssintrnl.hxx"
#include "dlgdraw.hxx"

// Define this if want each TEX_BUTTON to be a separate window.  This is
// necessary if the main dialog window has WS_CLIP_CHILDREN, but so far this
// doesn't seem to be the case.

static void CalcGLViewport( HWND hwndParent, HWND hwndChild, IPOINT2D *pOrigin, ISIZE *pSize );

//materials for varying intensities

enum{
    MAT_INTENSITY_LOW = 0,
    MAT_INTENSITY_MID,
    MAT_INTENSITY_HIGH,
    MAT_COUNT
};

MATERIAL gMat[MAT_COUNT] = {
    {{0.3f, 0.3f, 0.3f}, {0.6f, 0.6f, 0.6f}, {0.2f, 0.2f, 0.2f}, 0.3f },
    {{0.2f, 0.2f, 0.2f}, {0.8f, 0.8f, 0.8f}, {0.2f, 0.2f, 0.2f}, 0.3f },
    {{0.2f, 0.2f, 0.2f}, {1.0f, 1.0f, 1.0f}, {0.2f, 0.2f, 0.2f}, 0.3f }
};

float colorBlack[3] = {0.0f, 0.0f, 0.0f};

/**************************************************************************\
* SS_TEX_BUTTON constructor
*
* This allows drawing GL textures on a button
*
* For optimum performance, GL is configured on the main dialog window, and
* 'viewported' to the button.
* Defining SS_MULTIWINDOW results in the texture being drawn in the actual
* button window.
*
* Note: this only works for buttons on the main dialog window for now.
\**************************************************************************/

SS_TEX_BUTTON::SS_TEX_BUTTON( HWND hDlg, HWND hDlgBtn )
{
    PSSW psswParent = gpss->sswTable.PsswFromHwnd( hDlg );
    SS_ASSERT( psswParent, "SS_TEX_BUTTON constructor: NULL psswParent\n" );

    // The parent needs to have an hrc context, since we will be using it
    // for drawing.

    SS_GL_CONFIG GLc = { 0, 0, NULL };
    if( !psswParent->ConfigureForGL( &GLc ) ) {
        SS_WARNING( "SS_TEX_BUTTON constructor: ConfigureForGL failed\n" );
        return;
    }

#ifdef SS_MULTIWINDOW
    // Each button is a separate GL window, using its parents hrc
    pssw = new SSW( psswParent, hDlgBtn );

    SS_ASSERT( pssw, "SS_TEX_BUTTON constructor: pssw alloc failure\n" );

    // Configure the pssw for GL

    GLc.pfFlags = 0;
    GLc.hrc = psswParent->GetHRC();
    GLc.pStretch = NULL;

    if( ! pssw->ConfigureForGL( &GLc ) ) {
        SS_WARNING( "SS_TEX_BUTTON constructor: ConfigureForGL failed\n" );
        return;
    }
#else
    // Make the button a 'subwindow' of the parent
    pssw = NULL;

    // Calculate the viewport to draw to

    CalcGLViewport( hDlg, hDlgBtn, &origin, &size );
#endif

    // Init various GL stuff
    InitGL();

    pCurTex = NULL;
    bEnabled = TRUE;
}

/**************************************************************************\
* SS_TEX_BUTTON destructor
*
\**************************************************************************/

SS_TEX_BUTTON::~SS_TEX_BUTTON()
{
    if( pssw )
        delete pssw;
}

/**************************************************************************\
* InitGL
*
\**************************************************************************/

void
SS_TEX_BUTTON::InitGL()
{
    float ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
    float diffuse[] = {0.7f, 0.7f, 0.7f, 1.0f};
    float position[] = {0.0f, 0.0f, -150.0f, 1.0f};
    float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};

    // lighting, for intensity levels

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHT0);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);

    glCullFace( GL_BACK );
    glEnable(GL_CULL_FACE);
    glFrontFace( GL_CW );
    glShadeModel( GL_FLAT );

    glColor3f( 1.0f, 1.0f, 1.0f );
    gluOrtho2D( -1.0, 1.0, -1.0, 1.0 );

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}

/**************************************************************************\
* SetTexture
*
* Set a current texture for the button
*
* Note this is a pointer to a texture, so any texture memory management is
* done by the caller.
\**************************************************************************/

void
SS_TEX_BUTTON::SetTexture( TEXTURE *pTex )
{
    pCurTex = pTex;
}

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

void
SS_TEX_BUTTON::Draw( TEXTURE *pTex )
{
    if( pTex != NULL ) {
        glEnable(GL_TEXTURE_2D);

        ss_SetTexture( pTex ); // doesn't look at iPalRot yet
        // Set the texture palette if it exists
        if( pTex->pal && pTex->iPalRot )
            ss_SetTexturePalette( pTex, pTex->iPalRot );
    }
    // else white rectangle will be drawn

    if( bEnabled )
        intensity = DLG_INTENSITY_HIGH;
    else
        intensity = DLG_INTENSITY_LOW;

    switch( intensity ) {
        case DLG_INTENSITY_LOW:
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glEnable(GL_LIGHTING);
            glColor3f( 0.5f, 0.5f, 0.5f );
            break;
        case DLG_INTENSITY_MID:
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glEnable(GL_LIGHTING);
            glColor3f( 0.7f, 0.7f, 0.7f );
            break;
        case DLG_INTENSITY_HIGH:
        default:
            glColor3f( 1.0f, 1.0f, 1.0f );
            glDisable(GL_LIGHTING);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    }

    // Set the viewport
#ifdef SS_MULTIWINDOW
    glViewport( 0, 0, pssw->size.width, pssw->size.height );
#else
    glViewport( origin.x, origin.y, size.width, size.height );
#endif

    glBegin( GL_QUADS );
        glTexCoord2f( 0.0f, 1.0f );
        glVertex2f( -1.0f, 1.0f );
        glTexCoord2f( 1.0f, 1.0f );
        glVertex2f(  1.0f,  1.0f );
        glTexCoord2f( 1.0f, 0.0f );
        glVertex2f(  1.0f, -1.0f );
        glTexCoord2f( 0.0f, 0.0f );
        glVertex2f(  -1.0f, -1.0f );
    glEnd();

    glDisable( GL_TEXTURE_2D);

    glFlush();
}

void
SS_TEX_BUTTON::Draw()
{
    Draw( pCurTex );
}

/**************************************************************************\
* CalcGLViewport
*
* Calculate viewport for the child window
*
\**************************************************************************/

static void
CalcGLViewport( HWND hwndParent, HWND hwndChild, IPOINT2D *pOrigin, ISIZE *pSize )
{
    RECT childRect, parentRect;

    // Get size of the child window

    GetClientRect( hwndChild, &childRect );
    pSize->width = childRect.right;
    pSize->height = childRect.bottom;

    // Calc origin of the child window wrt its parents client area
    // Note that the y-coord must be inverted for GL

    // Map the child client rect to the parent client coords
    MapWindowPoints( hwndChild, hwndParent, (POINT *) &childRect, 2 );
    pOrigin->x = childRect.left;
    // invert y coord
    GetClientRect( hwndParent, &parentRect );
    pOrigin->y = parentRect.bottom - childRect.bottom;
}


FASTDIB.C

/******************************Module*Header*******************************\
* Module Name: fastdib.c
*
* CreateCompatibleDIB implementation.
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "sscommon.h"

BOOL APIENTRY GetDIBTranslationVector(HDC hdcMem, HPALETTE hpal, BYTE *pbVector);
static BOOL bFillBitmapInfo(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi);
static BOOL bFillColorTable(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi);
static UINT MyGetSystemPaletteEntries(HDC hdc, UINT iStartIndex, UINT nEntries,
                                      LPPALETTEENTRY lppe);
static BOOL bComputeLogicalToSurfaceMap(HDC hdc, HPALETTE hpal,
                                        BYTE *pajVector);

/******************************Public*Routine******************************\
* CreateCompatibleDIB
*
* Create a DIB section with an optimal format w.r.t. the specified hdc.
*
* If DIB <= 8bpp, then the DIB color table is initialized based on the
* specified palette.  If the palette handle is NULL, then the system
* palette is used.
*
* Note: The hdc must be a direct DC (not an info or memory DC).
*
* Note: On palettized displays, if the system palette changes the
*       UpdateDIBColorTable function should be called to maintain
*       the identity palette mapping between the DIB and the display.
*
* Returns:
*   Valid bitmap handle if successful, NULL if error.
\**************************************************************************/

//HBITMAP APIENTRY
HBITMAP
SSDIB_CreateCompatibleDIB(HDC hdc, HPALETTE hpal, ULONG ulWidth, ULONG ulHeight,
                    PVOID *ppvBits)
{
    HBITMAP hbmRet = (HBITMAP) NULL;
    BYTE aj[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) * 255)];
    BITMAPINFO *pbmi = (BITMAPINFO *) aj;

    //
    // Validate hdc.
    //

    if ( GetObjectType(hdc) != OBJ_DC )
    {
        SS_DBGPRINT("CreateCompatibleDIB: not OBJ_DC\n");
        return hbmRet;
    }

    memset(aj, 0, sizeof(aj));
    if ( bFillBitmapInfo(hdc, hpal, pbmi) )
    {
        //
        // Change bitmap size to match specified dimensions.
        //

        pbmi->bmiHeader.biWidth = ulWidth;
        pbmi->bmiHeader.biHeight = ulHeight;
        if (pbmi->bmiHeader.biCompression == BI_RGB)
        {
            pbmi->bmiHeader.biSizeImage = 0;
        }
        else
        {
            if ( pbmi->bmiHeader.biBitCount == 16 )
                pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 2;
            else if ( pbmi->bmiHeader.biBitCount == 32 )
                pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 4;
            else
                pbmi->bmiHeader.biSizeImage = 0;
        }
        pbmi->bmiHeader.biClrUsed = 0;
        pbmi->bmiHeader.biClrImportant = 0;

        //
        // Create the DIB section.  Let Win32 allocate the memory and return
        // a pointer to the bitmap surface.
        //

        hbmRet = CreateDIBSection(hdc, pbmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
        GdiFlush();

        if ( !hbmRet )
        {
            SS_DBGPRINT("CreateCompatibleDIB: CreateDIBSection failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("CreateCompatibleDIB: bFillBitmapInfo failed\n");
    }

    return hbmRet;
}

/******************************Public*Routine******************************\
* UpdateDIBColorTable
*
* Synchronize the DIB color table to the specified palette hpal.
* If hpal is NULL, then use the system palette.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
SSDIB_UpdateColorTable(HDC hdcMem, HDC hdc, HPALETTE hpal)
{
    BOOL bRet = FALSE;
    HBITMAP hbm;
    DIBSECTION ds;
    BYTE aj[(sizeof(RGBQUAD) + sizeof(PALETTEENTRY)) * 256];
    LPPALETTEENTRY lppe = (LPPALETTEENTRY) aj;
    LPRGBQUAD prgb = (LPRGBQUAD) (lppe + 256);
    ULONG cColors;

    //
    // Validate hdc.
    //

    if ( GetObjectType(hdc) != OBJ_DC )
    {
        SS_DBGPRINT("UpdateDIBColorTable: not OBJ_DC\n");
        return bRet;
    }
    if ( GetObjectType(hdcMem) != OBJ_MEMDC )
    {
        SS_DBGPRINT("UpdateDIBColorTable: not OBJ_MEMDC\n");
        return bRet;
    }

    //
    // Get the bitmap handle out of the memdc.
    //

    hbm = GetCurrentObject(hdcMem, OBJ_BITMAP);

    //
    // Validate bitmap (must be DIB section).
    //

    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds)) &&
         ds.dsBm.bmBits )
    {
        //
        // Get palette entries from specified palette or system palette.
        //

        cColors = 1 << ds.dsBmih.biBitCount;

        if ( hpal ? GetPaletteEntries(hpal, 0, cColors, lppe)
                  : MyGetSystemPaletteEntries(hdc, 0, cColors, lppe)
           )
        {
            UINT i;

            //
            // Convert to RGBQUAD.
            //

            for (i = 0; i < cColors; i++)
            {
                prgb[i].rgbRed      = lppe[i].peRed;
                prgb[i].rgbGreen    = lppe[i].peGreen;
                prgb[i].rgbBlue     = lppe[i].peBlue;
                prgb[i].rgbReserved = 0;
            }

            //
            // Set the DIB color table.
            //

            bRet = (BOOL) SetDIBColorTable(hdcMem, 0, cColors, prgb);

            if (!bRet)
            {
                SS_DBGPRINT("UpdateDIBColorTable: SetDIBColorTable failed\n");
            }
        }
        else
        {
            SS_DBGPRINT("UpdateDIBColorTable: MyGetSystemPaletteEntries failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("UpdateDIBColorTable: GetObject failed\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* GetCompatibleDIBInfo
*
* Copies pointer to bitmap origin to ppvBase and bitmap stride to plStride.
* Win32 DIBs can be created bottom-up (the default) with the origin at the
* lower left corner or top-down with the origin at the upper left corner.
* If the bitmap is top-down, *plStride is positive; if bottom-up, *plStride
* us negative.
*
* Also, because of restrictions on the alignment of scan lines the width
* the bitmap is often not the same as the stride (stride is the number of
* bytes between vertically adjacent pixels).
*
* The ppvBase and plStride value returned will allow you to address any
* given pixel (x, y) in the bitmap as follows:
*
* PIXEL *ppix;
*
* ppix = (PIXEL *) (((BYTE *)*ppvBase) + (y * *plStride) + (x * sizeof(PIXEL)));
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
GetCompatibleDIBInfo(HBITMAP hbm, PVOID *ppvBase, LONG *plStride)
{
    BOOL bRet = FALSE;
    DIBSECTION ds;

    //
    // Call GetObject to return a DIBSECTION.  If successful, the
    // bitmap is a DIB section and we can retrieve the pointer to
    // the bitmap bits and other parameters.
    //

    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds))
         && ds.dsBm.bmBits )
    {
        // For backwards compatibility with Get/SetBitmapBits, GDI does
        // not accurately report the bitmap pitch in bmWidthBytes.  It
        // always computes bmWidthBytes assuming WORD-aligned scanlines
        // regardless of the platform.
        //
        // Therefore, if the platform is WinNT, which uses DWORD-aligned
        // scanlines, adjust the bmWidthBytes value.

        {
            OSVERSIONINFO osvi;

            osvi.dwOSVersionInfoSize = sizeof(osvi);
            if (GetVersionEx(&osvi))
            {
                if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT )
                {
                    ds.dsBm.bmWidthBytes = (ds.dsBm.bmWidthBytes + 3) & ~3;
                }
            }
            else
            {
                SS_DBGPRINT1("GetCompatibleDIBInfo: GetVersionEx failed with %d\n", GetLastError());
                return bRet;
            }
        }

        //
        // If biHeight is positive, then the bitmap is a bottom-up DIB.
        // If biHeight is negative, then the bitmap is a top-down DIB.
        //

        if ( ds.dsBmih.biHeight > 0 )
        {
            *ppvBase  = (PVOID) (((int) ds.dsBm.bmBits) + (ds.dsBm.bmWidthBytes * (ds.dsBm.bmHeight - 1)));
            *plStride = (ULONG) (-ds.dsBm.bmWidthBytes);
        }
        else
        {
            *ppvBase  = ds.dsBm.bmBits;
            *plStride = ds.dsBm.bmWidthBytes;
        }

        bRet = TRUE;
    }
    else
    {
        SS_DBGPRINT("GetCompatibleDIBInfo: cannot get pointer to DIBSECTION bmBits\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* GetDIBTranslationVector
*
* Copies the translation vector that maps colors in the specified palette,
* hpal, to the DIB selected into the specified DC, hdcMem.
*
* Effects:
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
GetDIBTranslationVector(HDC hdcMem, HPALETTE hpal, BYTE *pbVector)
{
    BOOL bRet = FALSE;
    HBITMAP hbm;
    DIBSECTION ds;

    //
    // Validate parameters.
    //

    if ( GetObjectType(hdcMem) != OBJ_MEMDC ||
         GetObjectType(hpal) != OBJ_PAL ||
         !pbVector )
    {
        SS_DBGPRINT("GetDIBTranslationVector: bad parameter\n");
        return bRet;
    }

    //
    // The function bComputeLogicalToSurfaceMap cannot handle palettes
    // greater than 256 entries.
    //

    if ( GetPaletteEntries(hpal, 0, 1, NULL) > 256 )
    {
        SS_DBGPRINT("GetDIBTranslationVector: palette too big\n");
        return bRet;
    }

    //
    // The DIB must have a color table.
    //

    hbm = GetCurrentObject(hdcMem, OBJ_BITMAP);
    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds))
         && (ds.dsBmih.biBitCount <= 8) )
    {
        bRet = bComputeLogicalToSurfaceMap(hdcMem, hpal, pbVector);
    }
    else
    {
        SS_DBGPRINT("GetDIBTranslationVector: not a DIB section\n");
        return bRet;
    }

    return bRet;
}

//////////////////// Below here are internal-only routines ////////////////////

/******************************Public*Routine******************************\
* bFillBitmapInfo
*
* Fills in the fields of a BITMAPINFO so that we can create a bitmap
* that matches the format of the display.
*
* This is done by creating a compatible bitmap and calling GetDIBits
* to return the color masks.  This is done with two calls.  The first
* call passes in biBitCount = 0 to GetDIBits which will fill in the
* base BITMAPINFOHEADER data.  The second call to GetDIBits (passing
* in the BITMAPINFO filled in by the first call) will return the color
* table or bitmasks, as appropriate.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

static BOOL
bFillBitmapInfo(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi)
{
    HBITMAP hbm;
    BOOL    bRet = FALSE;

    //
    // Create a dummy bitmap from which we can query color format info
    // about the device surface.
    //

    if ( (hbm = CreateCompatibleBitmap(hdc, 1, 1)) != NULL )
    {
        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        //
        // Call first time to fill in BITMAPINFO header.
        //

        GetDIBits(hdc, hbm, 0, 0, NULL, pbmi, DIB_RGB_COLORS);

        if ( pbmi->bmiHeader.biBitCount <= 8 )
        {
            bRet = bFillColorTable(hdc, hpal, pbmi);
        }
        else
        {
            if ( pbmi->bmiHeader.biCompression == BI_BITFIELDS )
            {
                //
                // Call a second time to get the color masks.
                // It's a GetDIBits Win32 "feature".
                //

                GetDIBits(hdc, hbm, 0, pbmi->bmiHeader.biHeight, NULL, pbmi,
                          DIB_RGB_COLORS);
            }

            bRet = TRUE;
        }

        DeleteObject(hbm);
    }
    else
    {
        SS_DBGPRINT("bFillBitmapInfo: CreateCompatibleBitmap failed\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* bFillColorTable
*
* Initialize the color table of the BITMAPINFO pointed to by pbmi.  Colors
* are set to the current system palette.
*
* Note: call only valid for displays of 8bpp or less.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

static BOOL
bFillColorTable(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi)
{
    BOOL bRet = FALSE;
    BYTE aj[sizeof(PALETTEENTRY) * 256];
    LPPALETTEENTRY lppe = (LPPALETTEENTRY) aj;
    RGBQUAD *prgb = (RGBQUAD *) &pbmi->bmiColors[0];
    ULONG cColors;

    cColors = 1 << pbmi->bmiHeader.biBitCount;
    if ( cColors <= 256 )
    {
        if ( hpal ? GetPaletteEntries(hpal, 0, cColors, lppe)
                  : MyGetSystemPaletteEntries(hdc, 0, cColors, lppe) )
        {
            UINT i;

            for (i = 0; i < cColors; i++)
            {
                prgb[i].rgbRed      = lppe[i].peRed;
                prgb[i].rgbGreen    = lppe[i].peGreen;
                prgb[i].rgbBlue     = lppe[i].peBlue;
                prgb[i].rgbReserved = 0;
            }

            bRet = TRUE;
        }
        else
        {
            SS_DBGPRINT("bFillColorTable: MyGetSystemPaletteEntries failed\n");
        }
    }

    return bRet;
}

/******************************Public*Routine******************************\
* MyGetSystemPaletteEntries
*
* Internal version of GetSystemPaletteEntries.
*
* GetSystemPaletteEntries fails on some 4bpp devices.  This version
* will detect the 4bpp case and supply the hardcoded 16-color VGA palette.
* Otherwise, it will pass the call on to GDI's GetSystemPaletteEntries.
*
* It is expected that this call will only be called in the 4bpp and 8bpp
* cases as it is not necessary for OpenGL to query the system palette
* for > 8bpp devices.
\**************************************************************************/

static PALETTEENTRY gapeVgaPalette[16] =
{
    { 0,   0,   0,    0 },
    { 0x80,0,   0,    0 },
    { 0,   0x80,0,    0 },
    { 0x80,0x80,0,    0 },
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0x80,0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 },
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }
};

static UINT
MyGetSystemPaletteEntries(HDC hdc, UINT iStartIndex, UINT nEntries,
                          LPPALETTEENTRY lppe)
{
    int nDeviceBits;

    nDeviceBits = GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES);

    //
    // Some 4bpp displays will fail the GetSystemPaletteEntries call.
    // So if detected, return the hardcoded table.
    //

    if ( nDeviceBits == 4 )
    {
        if ( lppe )
        {
            nEntries = min(nEntries, (16 - iStartIndex));

            memcpy(lppe, &gapeVgaPalette[iStartIndex],
                   nEntries * sizeof(PALETTEENTRY));
        }
        else
            nEntries = 16;

        return nEntries;
    }
    else
    {
        return GetSystemPaletteEntries(hdc, iStartIndex, nEntries, lppe);
    }
}

/******************************Public*Routine******************************\
* bComputeLogicalToSurfaceMap
*
* Copy logical palette to surface palette translation vector to the buffer
* pointed to by pajVector.  The logical palette is specified by hpal.  The
* surface is specified by hdc.
*
* Note: The hdc may identify either a direct (display) dc or a DIB memory dc.
* If hdc is a display dc, then the surface palette is the system palette.
* If hdc is a memory dc, then the surface palette is the DIB color table.
\**************************************************************************/

static BOOL bComputeLogicalToSurfaceMap(HDC hdc, HPALETTE hpal, BYTE *pajVector)
{
    BOOL bRet = FALSE;
    HPALETTE hpalSurf;
    ULONG cEntries, cSysEntries;
    DWORD dwDcType = GetObjectType(hdc);
    LPPALETTEENTRY lppeTmp, lppeEnd;

    BYTE aj[sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 512) + (sizeof(RGBQUAD) * 256)];
    LOGPALETTE *ppal = (LOGPALETTE *) aj;
    LPPALETTEENTRY lppeSurf = &ppal->palPalEntry[0];
    LPPALETTEENTRY lppe = lppeSurf + 256;
    RGBQUAD *prgb = (RGBQUAD *) (lppe + 256);

    //
    // Determine number of colors in each palette.
    //

    cEntries = GetPaletteEntries(hpal, 0, 1, NULL);
    if ( dwDcType == OBJ_DC )
        cSysEntries = MyGetSystemPaletteEntries(hdc, 0, 1, NULL);
    else
        cSysEntries = 256;

    //
    // Get the logical palette entries.
    //

    cEntries = GetPaletteEntries(hpal, 0, cEntries, lppe);

    //
    // Get the surface palette entries.
    //

    if ( dwDcType == OBJ_DC )
    {
        cSysEntries = MyGetSystemPaletteEntries(hdc, 0, cSysEntries, lppeSurf);

        lppeTmp = lppeSurf;
        lppeEnd = lppeSurf + cSysEntries;

        for (; lppeTmp < lppeEnd; lppeTmp++)
            lppeTmp->peFlags = 0;
    }
    else
    {
        RGBQUAD *prgbTmp;

        //
        // First get RGBQUADs from DIB color table...
        //

        cSysEntries = GetDIBColorTable(hdc, 0, cSysEntries, prgb);

        //
        // ...then convert RGBQUADs into PALETTEENTRIES.
        //

        prgbTmp = prgb;
        lppeTmp = lppeSurf;
        lppeEnd = lppeSurf + cSysEntries;

        while ( lppeTmp < lppeEnd )
        {
            lppeTmp->peRed   = prgbTmp->rgbRed;
            lppeTmp->peGreen = prgbTmp->rgbGreen;
            lppeTmp->peBlue  = prgbTmp->rgbBlue;
            lppeTmp->peFlags = 0;

            lppeTmp++;
            prgbTmp++;

        }
    }

    //
    // Construct a translation vector by using GetNearestPaletteIndex to
    // map each entry in the logical palette to the surface palette.
    //

    if ( cEntries && cSysEntries )
    {
        //
        // Create a temporary logical palette that matches the surface
        // palette retrieved above.
        //

        ppal->palVersion = 0x300;
        ppal->palNumEntries = (USHORT) cSysEntries;

        if ( hpalSurf = CreatePalette(ppal) )
        {
            //
            // Translate each logical palette entry into a surface palette
            // index.
            //

            lppeTmp = lppe;
            lppeEnd = lppe + cEntries;

            for ( ; lppeTmp < lppeEnd; lppeTmp++, pajVector++)
            {
                *pajVector = (BYTE) GetNearestPaletteIndex(
                                        hpalSurf,
                                        RGB(lppeTmp->peRed,
                                            lppeTmp->peGreen,
                                            lppeTmp->peBlue)
                                        );
            }

            bRet = TRUE;

            DeleteObject(hpalSurf);
        }
        else
        {
            SS_DBGPRINT("bComputeLogicalToSurfaceMap: CreatePalette failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("bComputeLogicalToSurfaceMap: failed to get pal info\n");
    }

    return bRet;
}


GLSCRNSV.CXX

/******************************Module*Header*******************************\
* Module Name: glscrnsv.c
*
* Companion file to scrnsave.c.  Hooks out any changes in functionality
* defined as GL_SCRNSAVE in scrnsave.c, and does general intialization.
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include "scrnsave.h"

#include "glscrnsv.h"
#include "ssintrnl.hxx"
#include "sswindow.hxx"
#include "glscrnsv.hxx"
#include "sswproc.hxx"

static UINT (*KeyDownFunc)(int)        = NULL;

// Global ptr to screen saver instance
SCRNSAVE *gpss = NULL;

// Global strings.
#define GEN_STRING_SIZE 64
TCHAR szScreenSaverTitle[GEN_STRING_SIZE];
extern TCHAR szClassName[];  // from sswindow.cxx
LPCTSTR pszWindowClass = TEXT("WindowsScreenSaverClass");  // main class name
TCHAR szClassName[] = TEXT("ScreenSaverClass"); // child class name

// forward declarations of internal fns
static BOOL RegisterMainClass( WNDPROC wndProc, HBRUSH hbrBg, HCURSOR hCursor );
static BOOL RegisterChildClass();
static BOOL AttemptResolutionSwitch( int width, int height, ISIZE *pNewSize );

// externs
extern void InitRealScreenSave(); // scrnsave.cxx
extern LRESULT WINAPI
    RealScreenSaverProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
extern VOID UnloadPwdDLL(VOID);
extern BOOL GLScreenSaverConfigureDialog( HWND hDlg, UINT msg, WPARAM wParam,
                              LPARAM lParam ); // sswproc.cxx

#ifdef SS_INITIAL_CLEAR
static void InitialClear( PSSW *pssw );
#endif

#ifdef SS_WIN95
#define SS_WIN95_MULTIWINDOW_WORKAROUND 1
#define SS_WIN95_DESKTOP_REDRAW_FIX 1
static BOOL bPowerSaveEnabled();
#endif

/**************************************************************************\
* GLDoScreenSave
*
* Runs the screen saver in the specified mode
*
* GL version of DoScreenSave in scrnsave.c
*
* Does basic init, creates initial set of windows, and starts the message
* loop, which runs until terminated by some event.
*
\**************************************************************************/

static int
GLDoScreenSave( int winType, LPARAM lParam )
{
    MSG msg;

    // Create screen saver instance - this calls ss_Init()
    SCRNSAVE ss( winType, lParam );

    // Setup all the windows and start the message loop

    if( ss.SetupInitialWindows() )
    {
        // Send message to main window to start the drawing timer
#ifdef SS_DELAYED_START_KLUGE
        // Kluge to work around 'window-not-ready' problem in child
        // preview mode - trigger off of WM_PAINT instead
        if( ! SS_DELAY_START(winType) )
            SendMessage( ss.psswMain->hwnd, SS_WM_START, 0, 0 );
#else
        SendMessage( ss.psswMain->hwnd, SS_WM_START, 0, 0 );
#endif

        while( GetMessage( &msg, NULL, 0, 0 ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    }

    // We're done - screen saver exiting.

    // free password-handling DLL if loaded
    UnloadPwdDLL();

    return msg.wParam;
}

/**************************************************************************\
* SCRNSAVE constructors
*
\**************************************************************************/

SCRNSAVE::SCRNSAVE( int typeArg, LPARAM lParam )
{
    type = typeArg;
    initParam = lParam;
    Init();
}

SCRNSAVE::SCRNSAVE( int typeArg )
{
    type = typeArg;
    initParam = 0;
    Init();
}

void
SCRNSAVE::Init()
{
    psswMain    = NULL;
    psswGL      = NULL;
    bResSwitch  = FALSE;
    pssc        = NULL;
    pssPal      = NULL;
    flags       = 0;
#ifdef SS_DEBUG
    bDoTiming   = type == SS_TYPE_NORMAL ? TRUE : FALSE;
#endif

    // Global ptr to the screen saver instance
    gpss = this;

    // Platform detections

    ss_QueryOSVersion();

    // Initialize randomizer

    ss_RandInit();

    // Create multi-purpose black bg brush

    hbrBg = (HBRUSH) GetStockObject( BLACK_BRUSH );

    // Call client ss's init function, to get ptr to its configuration
    // request

    if( type == SS_TYPE_CONFIG ) {
        // This case handled differently
        return;
    }

    pssc = ss_Init();
    SS_ASSERT( pssc, "SCRNSAVE constructor failure\n" );

    // Set GL config structure from pssc

    GLc.pfFlags = 0;
    GLc.hrc = 0;
    GLc.pStretch = NULL;
    switch( pssc->depthType ) {
        case SS_DEPTH16 :
            GLc.pfFlags |= SS_DEPTH16_BIT;
            break;
        case SS_DEPTH32 :
            GLc.pfFlags |= SS_DEPTH32_BIT;
            break;
    }
    if( pssc->bDoubleBuf )
        GLc.pfFlags |= SS_DOUBLEBUF_BIT;
    if( pssc->bStretch )
        GLc.pStretch = &pssc->stretchInfo;

}

/**************************************************************************\
* SetupInitialWindows
*
* Create / Configure all required windows.
*
\**************************************************************************/

BOOL
SCRNSAVE::SetupInitialWindows()
{
    // Create the windows

    if( ! CreateInitialWindows() ) {
        SS_WARNING( "SCRNSAVE:: Couldn't create windows\n" );
        return FALSE;
    }

    // Initial window clear

#ifdef SS_INITIAL_MAIN_WINDOW_CLEAR
    if( type == SS_TYPE_PREVIEW ) {
    // Make sure the screen is cleared to black before we start drawing
    // anything, as sometimes the background WM_PAINT doesn't get to us right
    // away.  This is only a problem in preview mode
       psswMain->GdiClear();
    }
#endif

    // Configure and Init the windows, if applicable

#ifdef SS_DELAYED_START_KLUGE
    // delay start for some configurations
    if( ! SS_DELAY_START(type) ) {
        SendMessage( psswMain->hwnd, SS_WM_INITGL, 0, 0 );
    }
#else
    SendMessage( psswMain->hwnd, SS_WM_INITGL, 0, 0 );
#endif // SS_DELAYED_START_KLUGE

    return TRUE;
}

/**************************************************************************\
* CreateInitialWindows
*
* Create the intitial set of windows.
*
\**************************************************************************/

BOOL
SCRNSAVE::CreateInitialWindows()
{
    PSSW    pssw;

    if( !pssc )
        return FALSE;

    // Handle any request for resolution change

#define SS_RESOLUTION_SWITCH 1
#ifdef SS_RESOLUTION_SWITCH
    if( pssc->bStretch && (type == SS_TYPE_FULLSCREEN)
        && ss_fOnWin95() ) {

        STRETCH_INFO *pStretch = &pssc->stretchInfo;
        ISIZE newSize;

        // Try and change screen resolution to match stretch size
        bResSwitch = AttemptResolutionSwitch( pStretch->baseWidth,
                                               pStretch->baseHeight, &newSize );
        // Is stretching still necessary if resolution changed ?
        if( bResSwitch ) {
            if( (newSize.width == pStretch->baseWidth) &&
                (newSize.height == pStretch->baseHeight) )
                // exact match, no stretching now necessary
                pssc->bStretch = FALSE;
        }
    }
#endif

    // Currently the bitmaps used in stretch mode don't support palette
    // messages, so disable any stretching when in PREVIEW mode (where we
    // need to support palette interaction).
    // actually this is only a consideration in 8-bit mode...

    if( (type == SS_TYPE_PREVIEW) && pssc->bStretch )
        pssc->bStretch = FALSE;

    // Create the main ss window

    if( ! CreateMainWindow() )
        return FALSE;

#ifdef SS_INITIAL_CLEAR
    // If main window is transparent, can do an initial clear here before
    // any other windows are created or palettes modified
    // This is bogus on NT, as system switches to secure desktop when screen
    // saver kicks in automatically.
    InitialClear( pssw );
#endif

    // For now, simple window environment is described by pssc, so things
    // like bFloater and bStretch are mutually exclusive.

    SS_GL_CONFIG *pGLc = &gpss->GLc;

    if( pssc->bFloater ) {

        if( !(pssw = CreateChildWindow( &pssc->floaterInfo )) )
            return FALSE;

        pssw->pGLc = pGLc;
        psswGL = pssw; // support old-style
    } else {
        psswMain->pGLc = pGLc;
        psswGL = psswMain; // support old-style
    }

    return TRUE;
}

/**************************************************************************\
* NormalWindowScreenSaverProc
*
* Highest level window proc, used only in normal window (/w) mode.
*
\**************************************************************************/

LRESULT WINAPI
NormalWindowScreenSaverProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
   switch( uMsg )
   {
      case WM_SETTEXT:
         // RealScreenSaverProc won't allow this - bypass it
         return ScreenSaverProc( hWnd, uMsg, wParam, lParam );

      case WM_CHAR:
            if( KeyDownFunc ) {
                int key = wParam;
                (*KeyDownFunc)(key);
            }
            break;

      case WM_KEYDOWN:
            if( wParam == VK_ESCAPE ) {
                PostMessage( hWnd, WM_CLOSE, 0, 0l );
                break;
            } else if( KeyDownFunc ) {
                (*KeyDownFunc)(wParam);
                return 0;
            }
            return 0; // ??

      default:
         break;
   }

   return RealScreenSaverProc( hWnd, uMsg, wParam, lParam );
}

/**************************************************************************\
* DoScreenSave
*
* Hooked out version of DoScreenSave in standard scrnsave.c
*
\**************************************************************************/

int
DoScreenSave( HWND hwndParent )
{
    return GLDoScreenSave( hwndParent ? SS_TYPE_PREVIEW : SS_TYPE_FULLSCREEN,
                           (LPARAM) hwndParent );
}

/**************************************************************************\
* DoWindowedScreenSave
*
* Called when screen saver invoked with /w (window mode) parameter
*
\**************************************************************************/

int
DoWindowedScreenSave( LPCTSTR szArgs )
{
    return GLDoScreenSave( SS_TYPE_NORMAL, (LPARAM) szArgs );
}

/**************************************************************************\
* DoConfigBox
*
* Hooked out version of DoConfigBox in standard scrnsave.c
*
\**************************************************************************/

int
DoConfigBox( HWND hwndParent )
{
    // let the consumer register any special controls for the dialog
    if( !RegisterDialogClasses( hMainInstance ) )
        return FALSE;

    // Create screen saver instance
    SCRNSAVE ss( SS_TYPE_CONFIG );

    int retVal = DialogBox( hMainInstance,
                           MAKEINTRESOURCE( DLG_SCRNSAVECONFIGURE ),
                           hwndParent, (DLGPROC)GLScreenSaverConfigureDialog );

    return retVal;
}

/**************************************************************************\
* CreateMainWindow
*
* Creates main screen saver window based on the window type
*
\**************************************************************************/

BOOL
SCRNSAVE::CreateMainWindow()
{
    WNDPROC  wndProc;
    int      nCx, nCy;
    ISIZE    size;
    UINT     uStyle = 0;
    UINT     uExStyle = 0;
    IPOINT2D pos;
    LPCTSTR  pszWindowTitle;
    HCURSOR  hCursor = NULL;
    HBRUSH   hbrBgMain;
    PSSW     pssw;
    HWND    hwndParent = NULL;

    wndProc = RealScreenSaverProc;

    switch( type ) {
        case SS_TYPE_FULLSCREEN:
          {
            HWND hOther;

            nCx = GetSystemMetrics( SM_CXSCREEN );
            nCy = GetSystemMetrics( SM_CYSCREEN );
#ifdef SS_DEBUG
//#define SS_FULLSCREEN_DEBUG 1
#ifdef SS_FULLSCREEN_DEBUG
            // Reduce window size so we can see debugger
            nCx /= 2;
            nCy /= 2;
#endif
#endif
            uStyle = WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
            uExStyle = WS_EX_TOPMOST;

            pszWindowTitle = TEXT("Screen Saver"); // MUST differ from preview

            // if there is another NORMAL screen save instance, switch to it
            hOther = FindWindow( szClassName, pszWindowTitle );

            if( hOther && IsWindow( hOther ) )
            {
               SetForegroundWindow( hOther );
               return FALSE;
            }

            InitRealScreenSave();
          }
          break;

        case SS_TYPE_PREVIEW:
          {
            RECT rcParent;

            hwndParent = (HWND) initParam;

            GetClientRect( hwndParent, &rcParent );

            fChildPreview = TRUE;
            nCx = rcParent.right;
            nCy = rcParent.bottom;
            uStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN;

            pszWindowTitle = TEXT("Preview");   // MUST differ from full screen
          }
          break;

        case SS_TYPE_NORMAL:

          {
            // We set fChildPreview even when we're running in a normal window,
            // as this flag is used in scrnsave.c to differentiate from full
            // screen.
            fChildPreview = TRUE;

            // init size to half screen by default
            nCx = GetSystemMetrics( SM_CXSCREEN ) >> 1;
            nCy = GetSystemMetrics( SM_CYSCREEN ) >> 1;

            if( initParam ) {
                // get size of window from args
                LPCTSTR szArgs = (LPCTSTR) initParam;
            }

            LoadString(hMainInstance, IDS_DESCRIPTION, szScreenSaverTitle,
               sizeof(szScreenSaverTitle) / sizeof(TCHAR));
            pszWindowTitle = szScreenSaverTitle; // MUST differ from preview

            uStyle = WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
                   WS_CLIPSIBLINGS;

            hCursor = LoadCursor( NULL, IDC_ARROW );

            // Wrap RealScreenSaverProc
            wndProc = NormalWindowScreenSaverProc;
          }
          break;

        default:
          break;
    }

    size.width = nCx;
    size.height = nCy;
#ifdef SS_INITIAL_CLEAR
    hbrBgMain = NULL;
#else
    hbrBgMain = hbrBg;
#endif

    pos.x = pos.y = 0;

    // Create SSW window wrapper

    pssw = new SSW( NULL,       // parent
                    &size,
                    &pos,
                    FALSE,      // bMotion
                    NULL       // ChildSizeFunc
                  );
    if( !pssw )
        return FALSE;

    // Initialize the window class and create the window

    if( !RegisterMainClass
        (
            wndProc,
            hbrBgMain,
            hCursor
        ) ||

        !pssw->CreateSSWindow
        (
            hMainInstance,
            uStyle,
            uExStyle,
            szScreenSaverTitle,
            wndProc,
            pszWindowClass,
            hwndParent     // mf: ! hwndParentOverride
        ) )
    {
        delete pssw;
        return FALSE;
    }

    if( type != SS_TYPE_PREVIEW )
#ifndef SS_DEBUG
        SetForegroundWindow( pssw->hwnd );
#else
    {
        if( !SetForegroundWindow( pssw->hwnd ) )
            SS_DBGPRINT( "Main_Proc: SetForegroundWindow failed\n" );
    }
#endif

    // Always configure the main window for gdi
    pssw->ConfigureForGdi();

    psswMain = pssw;
    return TRUE;
}

/**************************************************************************\
* CreateChildWindow
*
* Creates a child window of the parent window
*
* This is a kind of wrapper-constructor
\**************************************************************************/

PSSW
SCRNSAVE::CreateChildWindow( FLOATER_INFO *pFloater )
{
    pFloater->bSubWindow = FALSE; // default is no logical subwin's

#ifdef SS_WIN95_MULTIWINDOW_WORKAROUND
    if( ss_fOnWin95() && ss_fFullScreenMode() && bPowerSaveEnabled() ) {
        // Make the floater a logical sub-window of the main window, not a
        // separate window.  This works around a USER bug where the power
        // save feature was not getting activated for multi-windowed screen
        // savers
        SS_DBGINFO( "SCRNSAVE::CreateChildWindow : Using logical sub-windows\n" );
        pFloater->bSubWindow = TRUE;
    }
#endif // SS_WIN95_MULTIWINDOW_WORKAROUND

    // Register child window class
    // This only has to be done once, since so far, all child window
    // classes are the same
    if( !pFloater->bSubWindow && !RegisterChildClass() )
        return NULL;

    return CreateChildWindow( psswMain, pFloater );
}

PSSW
SCRNSAVE::CreateChildWindow( PSSW psswParent, FLOATER_INFO *pFloater )
{
    UINT     uStyle = 0;
    UINT     uExStyle = 0;
    PSSW     pssw;

    uStyle = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

    // Size and position are NULL here, as the SSW will call the size
    // function callback in pFloater to get these and other values

    // Create the SSW window wrapper

    pssw = new SSW( psswParent,      // parent
                    NULL,            // size
                    NULL,            // position
                    pFloater->bMotion,
                    pFloater->ChildSizeFunc
                  );
    if( !pssw )
        return NULL;

    if( pFloater->bSubWindow )
        // Don't need to create win32 window
        return pssw;

    // Create a window

    if( !pssw->CreateSSWindow
        (
            hMainInstance,
            uStyle,
            0,                      // uExStyle
            szScreenSaverTitle ,
            SS_ScreenSaverProc,
            szClassName,
            NULL                    // hwndParentOverride
        ) )
    {
        delete pssw;
        return NULL;
    }

    return pssw;
}

/**************************************************************************\
* RegisterMainClass
*
* Registers class of the main SS window
\**************************************************************************/

static BOOL
RegisterMainClass( WNDPROC wndProc, HBRUSH hbrBg, HCURSOR hCursor )
{
    WNDCLASS cls;

    cls.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_OWNDC;
    cls.lpfnWndProc = wndProc;
    cls.cbClsExtra = 0;
    cls.cbWndExtra = 0;
    cls.hInstance = hMainInstance;
    cls.hIcon = LoadIcon( hMainInstance, MAKEINTATOM( ID_APP ) );
    cls.hCursor = hCursor;
    cls.hbrBackground = hbrBg;
    cls.lpszMenuName = (LPTSTR)NULL;
    cls.lpszClassName = (LPTSTR)pszWindowClass;
    return RegisterClass(&cls);
}

/**************************************************************************\
* RegisterChildClass
*
* Registers class of a standard child window
\**************************************************************************/

static BOOL
RegisterChildClass()
{
    static BOOL bRegistered = FALSE;

    if( bRegistered )
        return TRUE;

    WNDCLASS cls;

    cls.style = CS_VREDRAW | CS_HREDRAW;
    cls.lpfnWndProc = SS_ScreenSaverProc;
    cls.cbClsExtra = 0;
    cls.cbWndExtra = 0;
    cls.hInstance = hMainInstance;
    cls.hIcon = NULL;
    cls.hCursor = NULL;
    cls.hbrBackground = NULL;
    cls.lpszMenuName = (LPTSTR)NULL;
    cls.lpszClassName = (LPTSTR)szClassName;

    if( !RegisterClass(&cls) )
        return FALSE;

    // success
    bRegistered = TRUE;
    return TRUE;
}

/******************************Public*Routine******************************\
* AttemptResolutionSwitch
*
* Try doing resolution switching to match or get close to the desired size.
*
\**************************************************************************/

static BOOL
AttemptResolutionSwitch( int width, int height, ISIZE *pNewSize )
{
    BOOL bChanged = FALSE;

    // Try doing resolution switching to match or get close to the
    // desired width and height

    // Try switching to requested size
    if( 0 ) {
        // for now force failure of user request and try standard 640x480
        bChanged = TRUE;
    } else {
        // Can't switch to requested size, try for best match
        width = 640;
        height = 480;
        // If screen already this size or less, leave be
        if( (GetSystemMetrics( SM_CXSCREEN ) <= width) &&
            (GetSystemMetrics( SM_CYSCREEN ) <= height) )
            return FALSE;

        // use this when trying for best match
        // ss_QueryDisplaySettings();

        if( ss_ChangeDisplaySettings( width, height, 0 ) )
            bChanged = TRUE;
    }

    if( bChanged ) {
        pNewSize->width = width;
        pNewSize->height = height;
    }
    return bChanged;
}

#ifdef SS_INITIAL_CLEAR
static void
InitialClear( PSSW *pssw )
{
    ss_GdiRectWipeClear( pssw->hwnd, pssw->size.width, pssw->size.height );
}
#endif // SS_INITIAL_CLEAR

/**************************************************************************\
* CloseWindows
*
* Close down any open windows.
*
* This sends a WM_CLOSE message to the top-level window if it is still open.  If
* the window has any children, they are also closed.  For each window, the
* SSW destructor is called.
\**************************************************************************/

void
SCRNSAVE::CloseWindows()
{
    if( psswMain ) {
        if( psswMain->bOwnWindow )
            DestroyWindow( psswMain->hwnd );
        else
            delete psswMain;
    }
}

/**************************************************************************\
* SCRNSAVE destructor
*
\**************************************************************************/

SCRNSAVE::~SCRNSAVE()
{
    // Close any open windows (there might be some open if errors occurred)
    CloseWindows();

#ifdef SS_WIN95_DESKTOP_REDRAW_FIX
    // On win95, sometimes the desktop is drawn incorrectly after we have
    // taken over the palette
    if( flags & SS_PALETTE_TAKEOVER ) {
        SS_DBGINFO( "SCRNSAVE destructor: Redrawing desktop\n" );
        ss_RedrawDesktop();
    }
#endif

    if( bResSwitch ) {
        // Restore previous display settings
        ChangeDisplaySettings(NULL, CDS_FULLSCREEN);
    }

    gpss = NULL;
}

#ifdef SS_WIN95_MULTIWINDOW_WORKAROUND

// Macro to convert string to integer
#ifdef UNICODE
#define sztoi( pszStr ) _wtoi( (wchar_t *) (pszStr) )
#else
#define sztoi( pszStr ) atoi( (char *) (pszStr) )
#endif

/**************************************************************************\
* GetRegValueBool
*
* Get registry value and convert to BOOL
\**************************************************************************/

#define SS_REG_BUF_SIZE 30

static BOOL
bGetRegValueBool( HKEY hKey, LPTSTR szValue, BOOL *pbEnabled )
{
    TCHAR szBuf[SS_REG_BUF_SIZE];
    DWORD dataSize = SS_REG_BUF_SIZE;
    DWORD dwType;

    // Get the value

    if( ! ( RegQueryValueEx(
              hKey,
              szValue,
              (LPDWORD) NULL,
              (LPDWORD) &dwType,
              (LPBYTE) szBuf,
              (LPDWORD) &dataSize ) == ERROR_SUCCESS
          )
      )
        return FALSE;

    // Convert value to BOOL (the expected type is REG_SZ)

    switch( dwType ) {
        case REG_SZ :
            // Convert string to BOOL
            *pbEnabled = (BOOL) sztoi( szBuf );
            break;

        case REG_DWORD :
            // Handle numerical value
            *pbEnabled = (BOOL) *( (LPDWORD) szBuf );
            break;

        default:
            SS_WARNING( "GetRegValueBool : Unexpected type\n" );
            return FALSE;
    }

    return TRUE;
}

/**************************************************************************\
* bPowerSaveEnabled
*
* Detect if power saving is enabled by checking registry values.
*
\**************************************************************************/

static BOOL
bPowerSaveEnabled()
{
    BOOL bLowPowerActive;
    BOOL bPowerOffActive;
    HKEY hKey;

    // Open desktop key

    if( ! ( RegOpenKeyEx(
              HKEY_CURRENT_USER,
              (LPCTSTR) TEXT( "Control Panel\\desktop"),
              0,
              KEY_QUERY_VALUE,
              &hKey  ) == ERROR_SUCCESS
          )
      )
    {
        return FALSE;  // Can't open desktop key
    }

    // Check values - power saving is enabled if either ScreenSaveLowPowerActive
    // or ScreenSavePowerOffActive is enabled.

    if( (
          bGetRegValueBool(
            hKey,
            (LPTSTR) TEXT("ScreenSaveLowPowerActive"),
            &bLowPowerActive )
          &&
          bLowPowerActive
        )
        ||
        (
          bGetRegValueBool(
            hKey,
            (LPTSTR) TEXT("ScreenSavePowerOffActive"),
            &bPowerOffActive )
          &&
          bPowerOffActive
        )
      )
    {
        return TRUE;
    }

    return FALSE;
}
#endif // SS_WIN95_MULTIWINDOW_WORKAROUND


GLSCRNSV.H

/******************************Module*Header*******************************\
* Module Name: glscrnsv.h
*
* Include for modified scrnsave.c
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __glscrnsv_h__
#define __glscrnsv_h__

#include "sscommon.hxx"
#include "sswproc.hxx"

#ifdef __cplusplus
extern "C" {
#endif

extern int DoScreenSave( HWND hwndParent );
extern int DoWindowedScreenSave( LPCTSTR szArgs );
extern int DoConfigBox( HWND hwndParent );

#ifdef __cplusplus
}
#endif

#endif // __glscrnsv_h__


MATERIAL.C

/******************************Module*Header*******************************\
* Module Name: material.c
*
* Material selection functions.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>
#include <GL/gl.h>

#include "sscommon.h"

//#define NUM_TEA_MATERIALS 24

#define NUM_GOOD_MATERIALS 16  // 'good' ones among the 24 tea materials

//#define NUM_TEX_MATERIALS 4  // materials for texture

int goodMaterials[NUM_GOOD_MATERIALS] = {
        EMERALD, JADE, PEARL, RUBY, TURQUOISE, BRASS, BRONZE,
        COPPER, GOLD, SILVER, CYAN_PLASTIC, WHITE_PLASTIC, YELLOW_PLASTIC,
        CYAN_RUBBER, GREEN_RUBBER, WHITE_RUBBER };

/*  materials:  emerald, jade, obsidian, pearl, ruby, turquoise
 *              brass, bronze, chrome, copper, gold, silver
 *              black, cyan, green, red, white, yellow plastic
 *              black, cyan, green, red, white, yellow rubber

    description: ambient(RGB), diffuse(RGB), specular(RGB), shininess
 *
 */
// 'tea' materials, from aux teapots program
static GLfloat teaMaterialData[NUM_TEA_MATERIALS][10] =
{
     0.0215f, 0.1745f, 0.0215f,
        0.07568f, 0.61424f, 0.07568f, 0.633f, 0.727811f, 0.633f, 0.6f,
     0.135f, 0.2225f, 0.1575f,
        0.54f, 0.89f, 0.63f, 0.316228f, 0.316228f, 0.316228f, 0.1f,
     0.05375f, 0.05f, 0.06625f, // XX
        0.18275f, 0.17f, 0.22525f, 0.332741f, 0.328634f, 0.346435f, 0.3f,
     0.25f, 0.20725f, 0.20725f,
        1.0f, 0.829f, 0.829f, 0.296648f, 0.296648f, 0.296648f, 0.088f,
     0.1745f, 0.01175f, 0.01175f,
        0.61424f, 0.04136f, 0.04136f, 0.727811f, 0.626959f, 0.626959f, 0.6f,
     0.1f, 0.18725f, 0.1745f,
        0.396f, 0.74151f, 0.69102f, 0.297254f, 0.30829f, 0.306678f, 0.1f,
     0.329412f, 0.223529f, 0.027451f,
        0.780392f, 0.568627f, 0.113725f, 0.992157f, 0.941176f, 0.807843f,
        0.21794872f,
     0.2125f, 0.1275f, 0.054f,
        0.714f, 0.4284f, 0.18144f, 0.393548f, 0.271906f, 0.166721f, 0.2f,
     0.25f, 0.25f, 0.25f,  // XX
        0.4f, 0.4f, 0.4f, 0.774597f, 0.774597f, 0.774597f, 0.6f,
     0.19125f, 0.0735f, 0.0225f,
        0.7038f, 0.27048f, 0.0828f, 0.256777f, 0.137622f, 0.086014f, 0.1f,
     0.24725f, 0.1995f, 0.0745f,
        0.75164f, 0.60648f, 0.22648f, 0.628281f, 0.555802f, 0.366065f, 0.4f,
     0.19225f, 0.19225f, 0.19225f,
        0.50754f, 0.50754f, 0.50754f, 0.508273f, 0.508273f, 0.508273f, 0.4f,
     0.0f, 0.0f, 0.0f, 0.01f, 0.01f, 0.01f,
        0.50f, 0.50f, 0.50f, .25f,
     0.0f, 0.1f, 0.06f, 0.0f, 0.50980392f, 0.50980392f,
        0.50196078f, 0.50196078f, 0.50196078f, .25f,
     0.0f, 0.0f, 0.0f,
        0.1f, 0.35f, 0.1f, 0.45f, 0.55f, 0.45f, .25f,
     0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, // XX
        0.7f, 0.6f, 0.6f, .25f,
     0.0f, 0.0f, 0.0f, 0.55f, 0.55f, 0.55f,
        0.70f, 0.70f, 0.70f, .25f,
     0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f,
        0.60f, 0.60f, 0.50f, .25f,
     0.02f, 0.02f, 0.02f, 0.01f, 0.01f, 0.01f, // XX
        0.4f, 0.4f, 0.4f, .078125f,
     0.0f, 0.05f, 0.05f, 0.4f, 0.5f, 0.5f,
        0.04f, 0.7f, 0.7f, .078125f,
     0.0f, 0.05f, 0.0f, 0.4f, 0.5f, 0.4f,
        0.04f, 0.7f, 0.04f, .078125f,
     0.05f, 0.0f, 0.0f, 0.5f, 0.4f, 0.4f,
        0.7f, 0.04f, 0.04f, .078125f,
     0.05f, 0.05f, 0.05f, 0.5f, 0.5f, 0.5f,
        0.7f, 0.7f, 0.7f, .078125f,
     0.05f, 0.05f, 0.0f, 0.5f, 0.5f, 0.4f,
        0.7f, 0.7f, 0.04f, .078125f
};

// generally white materials for texturing

static GLfloat texMaterialData[NUM_TEX_MATERIALS][10] = {
// bright white
     0.2f, 0.2f, 0.2f,
        1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f,
// less bright white
     0.2f, 0.2f, 0.2f,
        0.9f, 0.9f, 0.9f, 0.9f, 0.9f, 0.9f, 0.5f,
// warmish white
     0.3f, 0.2f, 0.2f,
        1.0f, 0.9f, 0.8f, 1.0f, 0.9f, 0.8f, 0.5f,
// coolish white
     0.2f, 0.2f, 0.3f,
        0.8f, 0.9f, 1.0f, 0.8f, 0.9f, 1.0f, 0.5f
};

MATERIAL Material[NUM_TEA_MATERIALS + NUM_TEX_MATERIALS];

// pure black material

MATERIAL ss_BlackMat = {0.0f};

/**************************************************************************\
* InitMaterials
*
* Initialize MATERIAL structures with data
* - Set alpha to 0.5 for all materials
*
\**************************************************************************/

static void
InitMaterials( MATERIAL *pm, float *pd, int count )
{
    int i;

    for( i = 0; i < count; i++, pm++ ) {
        pm->ka.r = *pd++;
        pm->ka.g = *pd++;
        pm->ka.b = *pd++;
        pm->ka.a = 0.5f;
        pm->kd.r = *pd++;
        pm->kd.g = *pd++;
        pm->kd.b = *pd++;
        pm->kd.a = 0.5f;
        pm->ks.r = *pd++;
        pm->ks.g = *pd++;
        pm->ks.b = *pd++;
        pm->ks.a = 0.5f;
        pm->specExp = *pd++;
    }
}

/**************************************************************************\
* ss_InitMaterials
*
* Initialize all materials
*
\**************************************************************************/

void ss_InitMaterials()
{
    ss_InitTeaMaterials();
    ss_InitTexMaterials();
}

/**************************************************************************\
* ss_InitTeaMaterials
*
* Initialize Material structure with data in TeaMaterialData
*
\**************************************************************************/

void ss_InitTeaMaterials()
{
    InitMaterials( Material, teaMaterialData[0], NUM_TEA_MATERIALS );
}

/**************************************************************************\
* ss_InitTexMaterials
*
* Initialize Material structure with data in TexMaterialData (tex materials
* follow the tea materials in Material)
*
\**************************************************************************/

void ss_InitTexMaterials()
{
    InitMaterials( Material+NUM_TEA_MATERIALS, texMaterialData[0],
                   NUM_TEX_MATERIALS );
}

/**************************************************************************\
* ss_SetMaterial
*
* Set specified material with MATERIAL structure
*
\**************************************************************************/

void ss_SetMaterial( MATERIAL *pMat )
{
    glMaterialfv( GL_FRONT, GL_AMBIENT, (GLfloat*) &pMat->ka );
    glMaterialfv( GL_BACK, GL_AMBIENT, (GLfloat*) &pMat->ka );
    glMaterialfv( GL_FRONT, GL_DIFFUSE, (GLfloat *) &pMat->kd );
    glMaterialfv( GL_BACK, GL_DIFFUSE, (GLfloat *) &pMat->kd );
    glMaterialfv( GL_FRONT, GL_SPECULAR, (GLfloat *) &pMat->ks );
    glMaterialfv( GL_BACK, GL_SPECULAR, (GLfloat *) &pMat->ks );
    glMaterialf ( GL_FRONT, GL_SHININESS, pMat->specExp*128.0f);
    glMaterialf ( GL_BACK, GL_SHININESS, pMat->specExp*128.0f);
}

/**************************************************************************\
* ss_SetMaterialIndex
*
* Set material using its index name
*
\**************************************************************************/

void ss_SetMaterialIndex( int index )
{
    if( (index < 0) || (index >= (NUM_TEA_MATERIALS+NUM_TEX_MATERIALS)) )
        return;
    ss_SetMaterial( &Material[index] );
}

/**************************************************************************\
* ss_CreateMaterialGradient
*
* Given 2 materials and a count, create an incremental material, such
* that adding it 'count' times to the first material will generate the
* 2nd material.
*
\**************************************************************************/
void
ss_CreateMaterialGradient( MATERIAL *matInc, MATERIAL *startMat,
                        MATERIAL *endMat, int transCount )
{
    FLOAT fTransCount;

    if( !transCount ) {
        *matInc = *endMat;
        return;
    }

    /*
     * Add fudge factor to avoid round-off error, which could result in
     * invalid material values.
     */
#define FMAT_GRAD_FUDGE 1.0001f
    fTransCount = (FLOAT) transCount * FMAT_GRAD_FUDGE;

    matInc->ka.r = (endMat->ka.r - startMat->ka.r) / fTransCount;
    matInc->ka.g = (endMat->ka.g - startMat->ka.g) / fTransCount;
    matInc->ka.b = (endMat->ka.b - startMat->ka.b) / fTransCount;
    matInc->ks.r = (endMat->ks.r - startMat->ks.r) / fTransCount;
    matInc->ks.g = (endMat->ks.g - startMat->ks.g) / fTransCount;
    matInc->ks.b = (endMat->ks.b - startMat->ks.b) / fTransCount;
    matInc->kd.r = (endMat->kd.r - startMat->kd.r) / fTransCount;
    matInc->kd.g = (endMat->kd.g - startMat->kd.g) / fTransCount;
    matInc->kd.b = (endMat->kd.b - startMat->kd.b) / fTransCount;
    matInc->specExp = (endMat->specExp - startMat->specExp) / fTransCount;
}

/**************************************************************************\
* ss_TransitionMaterial
*
* Given a material, add to it the incremental material, and set the
* result as the current material
*
\**************************************************************************/
void
ss_TransitionMaterial( MATERIAL *transMat, MATERIAL *transMatInc )
{
    transMat->ka.r += transMatInc->ka.r;
    transMat->ka.g += transMatInc->ka.g;
    transMat->ka.b += transMatInc->ka.b;
    transMat->kd.r += transMatInc->kd.r;
    transMat->kd.g += transMatInc->kd.g;
    transMat->kd.b += transMatInc->kd.b;
    transMat->ks.r += transMatInc->ks.r;
    transMat->ks.g += transMatInc->ks.g;
    transMat->ks.b += transMatInc->ks.b;
    transMat->specExp += transMatInc->specExp;

    ss_SetMaterial( transMat );
}

/**************************************************************************\
* ss_RandomTeaMaterial
*
* Select a random TeaMaterial
* Return ptr to material
*
\**************************************************************************/
MATERIAL *ss_RandomTeaMaterial( BOOL bSet )
{
    int index;
    MATERIAL *pMat;

    index =  goodMaterials[ ss_iRand(NUM_GOOD_MATERIALS) ];
    pMat = &Material[ index ];
    if( bSet )
        ss_SetMaterial( pMat );
    return pMat;
}

/**************************************************************************\
* ss_RandomTeaMaterialIndex
*
* Select a random TeaMaterial
* Return material index
*
\**************************************************************************/
int ss_RandomTeaMaterialIndex( BOOL bSet )
{
    int index;

    index =  goodMaterials[ ss_iRand(NUM_GOOD_MATERIALS) ];
    if( bSet )
        ss_SetMaterial( &Material[ index ] );
    return index;
}

/**************************************************************************\
* ss_RandomTexMaterial
*
* Select a random TexMaterial
* Return ptr to material
*
\**************************************************************************/
MATERIAL *ss_RandomTexMaterial( BOOL bSet )
{
    int index;
    MATERIAL *pMat;

    index =  NUM_TEA_MATERIALS + ss_iRand(NUM_TEX_MATERIALS);
    pMat = &Material[ index ];
    if( bSet )
        ss_SetMaterial( pMat );
    return pMat;
}

/**************************************************************************\
* ss_RandomTexMaterialIndex
*
* Select a random TexMaterial
*
\**************************************************************************/
int ss_RandomTexMaterialIndex( BOOL bSet )
{
    int index;

    index =  NUM_TEA_MATERIALS + ss_iRand(NUM_TEX_MATERIALS);
    if( bSet )
        ss_SetMaterial( &Material[ index ] );
    return index;
}


MATH.C

/******************************Module*Header*******************************\
* Module Name: math.c
*
* Misc. useful math utility functions.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <GL\gl.h>
#include <math.h>
#include "sscommon.h"

#define ZERO_EPS    0.00000001

POINT3D ss_ptZero = {0.0f, 0.0f, 0.0f};

void ss_xformPoint(POINT3D *ptOut, POINT3D *ptIn, MATRIX *mat)
{
    double x, y, z;

    x = (ptIn->x * mat->M[0][0]) + (ptIn->y * mat->M[0][1]) +
        (ptIn->z * mat->M[0][2]) + mat->M[0][3];

    y = (ptIn->x * mat->M[1][0]) + (ptIn->y * mat->M[1][1]) +
        (ptIn->z * mat->M[1][2]) + mat->M[1][3];

    z = (ptIn->x * mat->M[2][0]) + (ptIn->y * mat->M[2][1]) +
        (ptIn->z * mat->M[2][2]) + mat->M[2][3];

    ptOut->x = (float) x;
    ptOut->y = (float) y;
    ptOut->z = (float) z;
}

void ss_xformNorm(POINT3D *ptOut, POINT3D *ptIn, MATRIX *mat)
{
    double x, y, z;
    double len;

    x = (ptIn->x * mat->M[0][0]) + (ptIn->y * mat->M[0][1]) +
        (ptIn->z * mat->M[0][2]);

    y = (ptIn->x * mat->M[1][0]) + (ptIn->y * mat->M[1][1]) +
        (ptIn->z * mat->M[1][2]);

    z = (ptIn->x * mat->M[2][0]) + (ptIn->y * mat->M[2][1]) +
        (ptIn->z * mat->M[2][2]);

    len = (x * x) + (y * y) + (z * z);
    if (len >= ZERO_EPS)
        len = 1.0 / sqrt(len);
    else
        len = 1.0;

    ptOut->x = (float) (x * len);
    ptOut->y = (float) (y * len);
    ptOut->z = (float) (z * len);
    return;
}

void ss_matrixIdent(MATRIX *mat)
{
    mat->M[0][0] = 1.0f; mat->M[0][1] = 0.0f;
    mat->M[0][2] = 0.0f; mat->M[0][3] = 0.0f;

    mat->M[1][0] = 0.0f; mat->M[1][1] = 1.0f;
    mat->M[1][2] = 0.0f; mat->M[1][3] = 0.0f;

    mat->M[2][0] = 0.0f; mat->M[2][1] = 0.0f;
    mat->M[2][2] = 1.0f; mat->M[2][3] = 0.0f;

    mat->M[3][0] = 0.0f; mat->M[3][1] = 0.0f;
    mat->M[3][2] = 0.0f; mat->M[3][3] = 1.0f;
}

void ss_matrixRotate(MATRIX *m, double xTheta, double yTheta, double zTheta)
{
    float xScale, yScale, zScale;
    float sinX, cosX;
    float sinY, cosY;
    float sinZ, cosZ;

    xScale = m->M[0][0];
    yScale = m->M[1][1];
    zScale = m->M[2][2];
    sinX = (float) sin(xTheta);
    cosX = (float) cos(xTheta);
    sinY = (float) sin(yTheta);
    cosY = (float) cos(yTheta);
    sinZ = (float) sin(zTheta);
    cosZ = (float) cos(zTheta);

    m->M[0][0] = (float) ((cosZ * cosY) * xScale);
    m->M[0][1] = (float) ((cosZ * -sinY * -sinX + sinZ * cosX) * yScale);
    m->M[0][2] = (float) ((cosZ * -sinY * cosX + sinZ * sinX) * zScale);

    m->M[1][0] = (float) (-sinZ * cosY * xScale);
    m->M[1][1] = (float) ((-sinZ * -sinY * -sinX + cosZ * cosX) * yScale);
    m->M[1][2] = (float) ((-sinZ * -sinY * cosX + cosZ * sinX) * zScale);

    m->M[2][0] = (float) (sinY * xScale);
    m->M[2][1] = (float) (cosY * -sinX * yScale);
    m->M[2][2] = (float) (cosY * cosX * zScale);
}

void ss_matrixTranslate(MATRIX *m, double xTrans, double yTrans,
                     double zTrans)
{
    m->M[0][3] = (float) xTrans;
    m->M[1][3] = (float) yTrans;
    m->M[2][3] = (float) zTrans;
}

void ss_matrixMult( MATRIX *m1, MATRIX *m2, MATRIX *m3 )
{
    int i, j;

    for( j = 0; j < 4; j ++ ) {
    for( i = 0; i < 4; i ++ ) {
    m1->M[j][i] = m2->M[j][0] * m3->M[0][i] +
  m2->M[j][1] * m3->M[1][i] +
  m2->M[j][2] * m3->M[2][i] +
  m2->M[j][3] * m3->M[3][i];
}
    }
}

void ss_calcNorm(POINT3D *norm, POINT3D *p1, POINT3D *p2, POINT3D *p3)
{
    float crossX, crossY, crossZ;
    float abX, abY, abZ;
    float acX, acY, acZ;
    float sqrLength;
    float invLength;

    abX = p2->x - p1->x;       // calculate p2 - p1
    abY = p2->y - p1->y;
    abZ = p2->z - p1->z;

    acX = p3->x - p1->x;       // calculate p3 - p1
    acY = p3->y - p1->y;
    acZ = p3->z - p1->z;

    crossX = (abY * acZ) - (abZ * acY);    // get cross product
    crossY = (abZ * acX) - (abX * acZ);    // (p2 - p1) X (p3 - p1)
    crossZ = (abX * acY) - (abY * acX);

    sqrLength = (crossX * crossX) + (crossY * crossY) +
                 (crossZ * crossZ);

    if (sqrLength > ZERO_EPS)
        invLength = (float) (1.0 / sqrt(sqrLength));
    else
        invLength = 1.0f;

    norm->x = crossX * invLength;
    norm->y = crossY * invLength;
    norm->z = crossZ * invLength;
}

void ss_normalizeNorm( POINT3D *n )
{
    float len;

    len = (n->x * n->x) + (n->y * n->y) + (n->z * n->z);
    if (len > ZERO_EPS)
        len = (float) (1.0 / sqrt(len));
    else
        len = 1.0f;

    n->x *= len;
    n->y *= len;
    n->z *= len;
}

void ss_normalizeNorms(POINT3D *p, ULONG cPts)
{
    float len;
    ULONG i;

    for (i = 0; i < cPts; i++, p++) {
        len = (p->x * p->x) + (p->y * p->y) + (p->z * p->z);
        if (len > ZERO_EPS)
            len = (float) (1.0 / sqrt(len));
        else
            len = 1.0f;

        p->x *= len;
        p->y *= len;
        p->z *= len;
    }
}


MATNAME.H

/******** materials ********************************/

// 24 tea materials (from teapots app)

#define NUM_TEA_MATERIALS 24

enum {
    EMERALD = 0,
    JADE,
    OBSIDIAN,
    PEARL,
    RUBY,
    TURQUOISE,
    BRASS,
    BRONZE,
    CHROME,
    COPPER,
    GOLD,
    SILVER,
    BLACK_PLASTIC,
    CYAN_PLASTIC,
    GREEN_PLASTIC,
    RED_PLASTIC,
    WHITE_PLASTIC,
    YELLOW_PLASTIC,
    BLACK_RUBBER,
    CYAN_RUBBER,
    GREEN_RUBBER,
    RED_RUBBER,
    WHITE_RUBBER,
    YELLOW_RUBBER
};

// 'white' materials, for texturing

#define NUM_TEX_MATERIALS 4

enum {
    BRIGHT_WHITE = NUM_TEA_MATERIALS,
    WHITE,
    WARM_WHITE,
    COOL_WHITE
};


PALETTE.CXX

/******************************Module*Header*******************************\
* Module Name: palette.cxx
*
* Palette processing functions
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <GL/gl.h>
#include "ssintrnl.hxx"
#include "palette.hxx"

#if(WINVER < 0x0400)
// Ordinarily not defined for versions before 4.00.
#define COLOR_3DDKSHADOW        21
#define COLOR_3DLIGHT           22
#define COLOR_INFOTEXT          23
#define COLOR_INFOBK            24
#endif

#define TKASSERT(x) SS_ASSERT( x, "palette processing failure\n" )

/******************************************************************************/

// Fixed palette support.

#define BLACK   PALETTERGB(0,0,0)
#define WHITE   PALETTERGB(255,255,255)
#define MAX_STATIC_COLORS   (COLOR_INFOBK - COLOR_SCROLLBAR + 1)
static int gNumStaticColors = MAX_STATIC_COLORS;

// TRUE if static system color settings have been replaced with B&W settings.

// TRUE if original static colors saved
static BOOL tkStaticColorsSaved = FALSE;

// saved system static colors (initialize with default colors)
static COLORREF gacrSave[MAX_STATIC_COLORS];

// new B&W system static colors
static COLORREF gacrBlackAndWhite[] = {
    WHITE,  // COLOR_SCROLLBAR
    BLACK,  // COLOR_BACKGROUND
    BLACK,  // COLOR_ACTIVECAPTION
    WHITE,  // COLOR_INACTIVECAPTION
    WHITE,  // COLOR_MENU
    WHITE,  // COLOR_WINDOW
    BLACK,  // COLOR_WINDOWFRAME
    BLACK,  // COLOR_MENUTEXT
    BLACK,  // COLOR_WINDOWTEXT
    WHITE,  // COLOR_CAPTIONTEXT
    WHITE,  // COLOR_ACTIVEBORDER
    WHITE,  // COLOR_INACTIVEBORDER
    WHITE,  // COLOR_APPWORKSPACE
    BLACK,  // COLOR_HIGHLIGHT
    WHITE,  // COLOR_HIGHLIGHTTEXT
    WHITE,  // COLOR_BTNFACE
    BLACK,  // COLOR_BTNSHADOW
    BLACK,  // COLOR_GRAYTEXT
    BLACK,  // COLOR_BTNTEXT
    BLACK,  // COLOR_INACTIVECAPTIONTEXT
    BLACK,  // COLOR_BTNHIGHLIGHT
    BLACK,  // COLOR_3DDKSHADOW
    WHITE,  // COLOR_3DLIGHT
    BLACK,  // COLOR_INFOTEXT
    WHITE   // COLOR_INFOBK
    };
static INT gaiStaticIndex[] = {
    COLOR_SCROLLBAR          ,
    COLOR_BACKGROUND         ,
    COLOR_ACTIVECAPTION      ,
    COLOR_INACTIVECAPTION    ,
    COLOR_MENU               ,
    COLOR_WINDOW             ,
    COLOR_WINDOWFRAME        ,
    COLOR_MENUTEXT           ,
    COLOR_WINDOWTEXT         ,
    COLOR_CAPTIONTEXT        ,
    COLOR_ACTIVEBORDER       ,
    COLOR_INACTIVEBORDER     ,
    COLOR_APPWORKSPACE       ,
    COLOR_HIGHLIGHT          ,
    COLOR_HIGHLIGHTTEXT      ,
    COLOR_BTNFACE            ,
    COLOR_BTNSHADOW          ,
    COLOR_GRAYTEXT           ,
    COLOR_BTNTEXT            ,
    COLOR_INACTIVECAPTIONTEXT,
    COLOR_BTNHIGHLIGHT       ,
    COLOR_3DDKSHADOW         ,
    COLOR_3DLIGHT            ,
    COLOR_INFOTEXT           ,
    COLOR_INFOBK
    };

#define RESTORE_FROM_REGISTRY   1
#if RESTORE_FROM_REGISTRY
// Registry names for the system colors.
static CHAR *gaszSysClrNames[] = {
    "Scrollbar",      // COLOR_SCROLLBAR              0
    "Background",     // COLOR_BACKGROUND             1   (also COLOR_DESKTOP)
    "ActiveTitle",    // COLOR_ACTIVECAPTION          2
    "InactiveTitle",  // COLOR_INACTIVECAPTION        3
    "Menu",           // COLOR_MENU                   4
    "Window",         // COLOR_WINDOW                 5
    "WindowFrame",    // COLOR_WINDOWFRAME            6
    "MenuText",       // COLOR_MENUTEXT               7
    "WindowText",     // COLOR_WINDOWTEXT             8
    "TitleText",      // COLOR_CAPTIONTEXT            9
    "ActiveBorder",   // COLOR_ACTIVEBORDER          10
    "InactiveBorder", // COLOR_INACTIVEBORDER        11
    "AppWorkspace",   // COLOR_APPWORKSPACE          12
    "Hilight",        // COLOR_HIGHLIGHT             13
    "HilightText",    // COLOR_HIGHLIGHTTEXT         14
    "ButtonFace",     // COLOR_BTNFACE               15   (also COLOR_3DFACE)
    "ButtonShadow",   // COLOR_BTNSHADOW             16   (also COLOR_3DSHADOW)
    "GrayText",       // COLOR_GRAYTEXT              17
    "ButtonText",     // COLOR_BTNTEXT               18
    "InactiveTitleText", // COLOR_INACTIVECAPTIONTEXT   19
    "ButtonHilight",  // COLOR_BTNHIGHLIGHT          20   (also COLOR_3DHILIGHT)
    "ButtonDkShadow", // COLOR_3DDKSHADOW            21
    "ButtonLight",    // COLOR_3DLIGHT               22
    "InfoText",       // COLOR_INFOTEXT              23
    "InfoWindow"      // COLOR_INFOBK                24
};

static BOOL GetRegistrySysColors(COLORREF *, int);
#endif

unsigned char ss_ComponentFromIndex(int i, int nbits, int shift );
static int ss_PixelFormatDescriptorFromDc( HDC hdc, PIXELFORMATDESCRIPTOR *Pfd );

/******************************************************************************/

#if RESTORE_FROM_REGISTRY
/******************************Public*Routine******************************\
* GetRegistrySysColors
*
* Reads the Control Panel's color settings from the registry and stores
* those values in pcr.  If we fail to get any value, then the corresponding
* entry in pcr is not modified.
\**************************************************************************/

static BOOL GetRegistrySysColors(COLORREF *pcr, int nColors)
{
    BOOL bRet = FALSE;
    long lRet;
    HKEY hkSysColors = (HKEY) NULL;
    int i;
    DWORD dwDataType;
    char achColor[64];
    DWORD cjColor;

    TKASSERT(nColors <= gNumStaticColors);

// Open the key for the system color settings.

    lRet = RegOpenKeyExA(HKEY_CURRENT_USER,
                         "Control Panel\\Colors",
                         0,
                         KEY_QUERY_VALUE,
                         &hkSysColors);

    if ( lRet != ERROR_SUCCESS )
    {
        goto GetRegistrySysColors_exit;
    }

// Read each system color value.  The names are stored in the global
// array of char *, gaszSysClrNames.

    for (i = 0; i < nColors; i++)
    {
        cjColor = sizeof(achColor);
        lRet = RegQueryValueExA(hkSysColors,
                                (LPSTR) gaszSysClrNames[i],
                                (LPDWORD) NULL,
                                &dwDataType,
                                (LPBYTE) achColor,
                                &cjColor);

        TKASSERT(lRet != ERROR_MORE_DATA);

        if ( lRet == ERROR_SUCCESS && dwDataType == REG_SZ )
        {
            DWORD r, g, b;

            sscanf(achColor, "%ld %ld %ld", &r, &g, &b);
            pcr[i] = RGB(r, g, b);
        }
    }

    bRet = TRUE;

GetRegistrySysColors_exit:
    if (hkSysColors)
        RegCloseKey(hkSysColors);

    return bRet;
}
#endif

/******************************Public*Routine******************************\
* GrabStaticEntries
*
* Support routine for Realize to manage the static system color
* usage.
*
* This function will save the current static system color usage state.
* It will fail if:
*
*   1.  TK is not in "sys color in use state but system palette is in
*       SYSPAL_NOSTATIC mode.  This means that another app still possesses
*       the static system colors.  If this happens <TBD>
*
* Side effect:
*   If system colors are changed, then WM_SYSCOLORCHANGE message is
*   broadcast to all top level windows.
*
* Returns:
*   TRUE if successful, FALSE otherwise (see above).
\**************************************************************************/

BOOL
SS_PAL::GrabStaticEntries()
{
    int i;
    BOOL bRet = FALSE;

    // Do nothing if sys colors already in use.

    if ( !bSystemColorsInUse )
    {
    // Take possession only if no other app has the static colors.
    // How can we tell?  If the return from SetSystemPaletteUse is
    // SYSPAL_STATIC, then no other app has the statics.  If it is
    // SYSPAL_NOSTATIC, someone else has them and we must fail.
    //
    // SetSystemPaletteUse is properly synchronized internally
    // so that it is atomic.
    //
    // Because we are relying on SetSystemPaletteUse to synchronize TK,
    // it is important to observe the following order for grabbing and
    // releasing:
    //
    //      Grab        call SetSystemPaletteUse and check for SYSPAL_STATIC
    //                  save sys color settings
    //                  set new sys color settings
    //
    //      Release     restore sys color settings
    //                  call SetSystemPaletteUse

// potential pitfall here, if a 'bad' app has not released the static
// colors on deactivation.
        if ( SetSystemPaletteUse( hdc, SYSPAL_NOSTATIC ) == SYSPAL_STATIC )
        {
        // Save current sys color settings.

            for (i = COLOR_SCROLLBAR; i <= COLOR_BTNHIGHLIGHT; i++)
                gacrSave[i - COLOR_SCROLLBAR] = GetSysColor(i);

            bSystemColorsInUse = TRUE;

            // Set b&w sys color settings.

            SetSysColors(gNumStaticColors, gaiStaticIndex, gacrBlackAndWhite);

            // Inform all other top-level windows of the system color change.

            PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);

            bRet = TRUE;
        } else {
            // handle case where can't get sys colors
        }
    }
    else
        bRet = TRUE;

    return bRet;
}

/******************************Public*Routine******************************\
* ReleaseStaticEntries
*
* Support routine for Realize to manage the static system color
* usage.
*
* This function will reset the current static system color usage state.
* It will fail if:
*
*   1.  TK is not in a "sys colors in use" state.  If we are in this case,
*       then the static system colors do not need to be released.
*
* Side effect:
*   If system colors are changed, then WM_SYSCOLORCHANGE message is
*   broadcast to all top level windows.
*
* Returns:
*   TRUE if successful, FALSE otherwise (see above).
\**************************************************************************/

BOOL
SS_PAL::ReleaseStaticEntries()
{
    BOOL bRet = FALSE;

    // Do nothing if sys colors not in use.

    if ( bSystemColorsInUse )
    {
#if RESTORE_FROM_REGISTRY
    // Replace saved system colors with registry values.  We do it now
    // rather than earlier because someone may have changed registry while
    // TK app was running in the foreground (very unlikely, but it could
    // happen).
    //
    // Also, we still try to save current setting in GrabStaticEntries so
    // that if for some reason we fail to grab one or more of the colors
    // from the registry, we can still fall back on what we grabbed via
    // GetSysColors (even though there is a chance its the wrong color).

        GetRegistrySysColors(gacrSave, gNumStaticColors);
#endif

        // Do this now, since SetSysColors() generates WM_SYSCOLORCHANGE,
        // which can cause this routine to be re-entered
        // back to here.
        bSystemColorsInUse = FALSE;

        // Return the system palette to SYSPAL_STATIC.

        SetSystemPaletteUse( hdc, SYSPAL_STATIC );

        // Restore the saved system color settings.

        SetSysColors(gNumStaticColors, gaiStaticIndex, gacrSave);

        // Inform all other top-level windows of the system color change.

        PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);

        // Reset the "sys colors in use" state and return success.

        bSystemColorsInUse = FALSE;
        bRet = TRUE;
    }

    return bRet;
}

// Gamma correction factor * 10
#define GAMMA_CORRECTION 14

// Maximum color distance with 8-bit components
#define MAX_COL_DIST (3*256*256L)

// Number of static colors
#define STATIC_COLORS 20

// Flags used when matching colors
#define EXACT_MATCH 1
#define COLOR_USED 1

// Conversion tables for n bits to eight bits

#if GAMMA_CORRECTION == 10
// These tables are corrected for a gamma of 1.0
static unsigned char abThreeToEight[8] =
{
    0, 0111 >> 1, 0222 >> 1, 0333 >> 1, 0444 >> 1, 0555 >> 1, 0666 >> 1, 0377
};
static unsigned char abTwoToEight[4] =
{
    0, 0x55, 0xaa, 0xff
};
static unsigned char abOneToEight[2] =
{
    0, 255
};
#else
// These tables are corrected for a gamma of 1.4
static unsigned char abThreeToEight[8] =
{
    0, 63, 104, 139, 171, 200, 229, 255
};
static unsigned char abTwoToEight[4] =
{
    0, 116, 191, 255
};
static unsigned char abOneToEight[2] =
{
    0, 255
};
#endif

// Table which indicates which colors in a 3-3-2 palette should be
// replaced with the system default colors
#if GAMMA_CORRECTION == 10
static int aiDefaultOverride[STATIC_COLORS] =
{
    0, 4, 32, 36, 128, 132, 160, 173, 181, 245,
    247, 164, 156, 7, 56, 63, 192, 199, 248, 255
};
#else
static int aiDefaultOverride[STATIC_COLORS] =
{
    0, 3, 24, 27, 64, 67, 88, 173, 181, 236,
    247, 164, 91, 7, 56, 63, 192, 199, 248, 255
};
#endif

unsigned char
ss_ComponentFromIndex(int i, int nbits, int shift)
{
    unsigned char val;

    TKASSERT(nbits >= 1 && nbits <= 3);

    val = i >> shift;
    switch (nbits)
    {
    case 1:
        return abOneToEight[val & 1];

    case 2:
        return abTwoToEight[val & 3];

    case 3:
        return abThreeToEight[val & 7];
    }
    return 0;
}

// System default colors
static PALETTEENTRY apeDefaultPalEntry[STATIC_COLORS] =
{
    { 0,   0,   0,    0 },
    { 0x80,0,   0,    0 },
    { 0,   0x80,0,    0 },
    { 0x80,0x80,0,    0 },
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 },

    { 192, 220, 192,  0 },
    { 166, 202, 240,  0 },
    { 255, 251, 240,  0 },
    { 160, 160, 164,  0 },

    { 0x80,0x80,0x80, 0 },
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }
};

/******************************Public*Routine******************************\
*
* UpdateStaticMapping
*
* Computes the best match between the current system static colors
* and a 3-3-2 palette
*
* History:
*  Tue Aug 01 18:18:12 1995-by-Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

static void
UpdateStaticMapping(PALETTEENTRY *pe332Palette)
{
    HPALETTE hpalStock;
    int iStatic, i332;
    int iMinDist, iDist;
    int iDelta;
    int iMinEntry;
    PALETTEENTRY *peStatic, *pe332;

    hpalStock = (HPALETTE) GetStockObject(DEFAULT_PALETTE);

    // The system should always have one of these
    TKASSERT(hpalStock != NULL);
    // Make sure there's the correct number of entries
    TKASSERT(GetPaletteEntries(hpalStock, 0, 0, NULL) == STATIC_COLORS);

    // Get the current static colors
    GetPaletteEntries(hpalStock, 0, STATIC_COLORS, apeDefaultPalEntry);

    // Zero the flags in the static colors because they are used later
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Zero the flags in the incoming palette because they are used later
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = 0;
        pe332++;
    }

    // Try to match each static color exactly
    // This saves time by avoiding the least-squares match for each
    // exact match
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            if (peStatic->peRed == pe332->peRed &&
                peStatic->peGreen == pe332->peGreen &&
                peStatic->peBlue == pe332->peBlue)
            {
                TKASSERT(pe332->peFlags != COLOR_USED);

                peStatic->peFlags = EXACT_MATCH;
                pe332->peFlags = COLOR_USED;
                aiDefaultOverride[iStatic] = i332;

                break;
            }

            pe332++;
        }

        peStatic++;
    }

    // Match each static color as closely as possible to an entry
    // in the 332 palette by minimized the square of the distance
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        // Skip colors already matched exactly
        if (peStatic->peFlags == EXACT_MATCH)
        {
            peStatic++;
            continue;
        }

        iMinDist = MAX_COL_DIST+1;
#if DBG
        iMinEntry = -1;
#endif

        pe332 = pe332Palette;
        for (i332 = 0; i332 < 256; i332++)
        {
            // Skip colors already used
            if (pe332->peFlags == COLOR_USED)
            {
                pe332++;
                continue;
            }

            // Compute Euclidean distance squared
            iDelta = pe332->peRed-peStatic->peRed;
            iDist = iDelta*iDelta;
            iDelta = pe332->peGreen-peStatic->peGreen;
            iDist += iDelta*iDelta;
            iDelta = pe332->peBlue-peStatic->peBlue;
            iDist += iDelta*iDelta;

            if (iDist < iMinDist)
            {
                iMinDist = iDist;
                iMinEntry = i332;
            }

            pe332++;
        }

        TKASSERT(iMinEntry != -1);

        // Remember the best match
        aiDefaultOverride[iStatic] = iMinEntry;
        pe332Palette[iMinEntry].peFlags = COLOR_USED;

        peStatic++;
    }

    // Zero the flags in the static colors because they may have been
    // set.  We want them to be zero so the colors can be remapped
    peStatic = apeDefaultPalEntry;
    for (iStatic = 0; iStatic < STATIC_COLORS; iStatic++)
    {
        peStatic->peFlags = 0;
        peStatic++;
    }

    // Reset the 332 flags because we may have set them
    pe332 = pe332Palette;
    for (i332 = 0; i332 < 256; i332++)
    {
        pe332->peFlags = PC_NOCOLLAPSE;
        pe332++;
    }
}

/******************************Public*Routine******************************\
* FlushPalette
*
* Because of Win 3.1 compatibility, GDI palette mapping always starts
* at zero and stops at the first exact match.  So if there are duplicates,
* the higher colors aren't mapped to--which is often a problem if we
* are trying to make to any of the upper 10 static colors.  To work around
* this, we flush the palette to all black.
*
\**************************************************************************/

void
SS_PAL::Flush()
{
    LOGPALETTE *pPal;
    HPALETTE hpalBlack, hpalOld;
    int i;

    if( nEntries == 256 )
    {
        pPal = (LOGPALETTE *) LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT,
                         sizeof(LOGPALETTE) + nEntries * sizeof(PALETTEENTRY));

        if (pPal)
        {
        pPal->palVersion = 0x300;
        pPal->palNumEntries = nEntries;

            // Mark everything PC_NOCOLLAPSE and PC_RESERVED to force every
            // thing into the palette.  Colors are already black because
            // we zero initialized during memory allocation.

            for (i = 0; i < nEntries; i++)
            {
                pPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE | PC_RESERVED;
            }

            hpalBlack = CreatePalette(pPal);
            LocalFree(pPal);

            hpalOld = SelectPalette(hdc, hpalBlack, FALSE);
            RealizePalette(hdc);

            SelectPalette(hdc, hpalOld, FALSE);
            DeleteObject(hpalBlack);
        }
    }
}

/******************************Public*Routine******************************\
* Realize
*
* Select the given palette in background or foreground mode (as specified
* by the bForceBackground flag), and realize the palette.
*
* If static system color usage is set, the system colors are replaced.
\**************************************************************************/

long
SS_PAL::Realize( HWND hwndArg, HDC hdcArg, BOOL bForceBackground )
{
    // cache:
    hwnd = hwndArg;
    hdc = hdcArg;

    if( bTakeOver ) {
        // Easy case
        SetSystemPaletteUse(hdc, SYSPAL_NOSTATIC);
        SelectPalette(hdc, hPal, bForceBackground );
        RealizePalette(hdc);
        return 1;
    }

    if( bFlush ) {
        Flush();
        bFlush = FALSE;
    }

    return Realize( bForceBackground );
}

long
SS_PAL::Realize( BOOL bForceBackground )
{
    long Result = -1;
    BOOL bHaveSysPal = TRUE;

    SS_DBGLEVEL2( SS_LEVEL_INFO, "SS_PAL::Realize: %d for %d\n", bForceBackground, hwnd );

    // If static system color usage is set, prepare to take over the
    // system palette.

    if( bUseStatic )
    {
        // If foreground, take over the static colors.  If background, release
        // the static colors.

        if ( !bForceBackground )
        {
            // If GrabStaticEntries succeeds, then it is OK to take over the
            // static colors.  If not <mf:TBD>

            bHaveSysPal = GrabStaticEntries();
        }
        else
        {
            // If we are currently using the system colors (bSystemColorsInUse)
            // and Realize was called with bForceBackground set, we
            // are being deactivated and must release the static system colors.

            ReleaseStaticEntries();
        }

        // Rerealize the palette.
        //
        // If set to TRUE, bForceBackground will force the palette to be
        // realized as a background palette, regardless of focus.  This
        // will happen anyway if the TK window does not have the keyboard focus.

        if ( (bForceBackground || bHaveSysPal) &&
             UnrealizeObject( hPal ) &&
             NULL != SelectPalette( hdc, hPal, bForceBackground ) )
        {
            Result = RealizePalette( hdc );
        }

        // If some rude app still has the system colors and we're in the
        // foreground, make the best of it.
        if( !bForceBackground && !bHaveSysPal ) {
            if( UnrealizeObject( hPal ) &&
                NULL != SelectPalette( hdc, hPal, TRUE ) )
            {
                Result = RealizePalette( hdc );
            }
        }
    }
    else
    {
        if ( NULL != SelectPalette( hdc, hPal, FALSE ) )
        {
            Result = RealizePalette( hdc );
        }
    }

    return( Result );
}

/******************************Public*Routine******************************\
* SS_PAL constructor
*
* This creates the palette, but does not select or realize it
*
\**************************************************************************/

SS_PAL::SS_PAL( HDC hdcArg, PIXELFORMATDESCRIPTOR *ppfd, BOOL bTakeOverPalette )
{
    hwnd = 0;
    hPal = 0;
    bUseStatic = FALSE;
    bSystemColorsInUse = FALSE;
    pfd = *ppfd;  // this is for palette purposes only (other fields may not apply)
    hdc = hdcArg;
    bTakeOver = bTakeOverPalette;  // mf: for now, when this is set, it means
        // the screen saver is running in full screen mode - implying that
        // interaction with other apps not necessary

    if( bTakeOver ) {
        bFlush = FALSE;
        bUseStatic = TRUE;
    } else {
        bFlush = TRUE;
        bUseStatic = ppfd->dwFlags & PFD_NEED_SYSTEM_PALETTE;
    }

    if( bUseStatic )
        // save current static palette usage so we can restore it
        uiOldStaticUse = GetSystemPaletteUse( hdc );

    paletteManageProc = NullPaletteManageProc;

    // Now create the palette and return
    hPal = MakeRGBPalette();
    SS_ASSERT( hPal, "SS_PAL constructor failure\n" );
}

/******************************Public*Routine******************************\
* SS_PAL destructor

\**************************************************************************/

SS_PAL::~SS_PAL()
{
    if( bUseStatic )
    {
        if( uiOldStaticUse )
            SetSystemPaletteUse(hdc, uiOldStaticUse);

        PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);
    }
    if( hPal ) {
        SelectPalette( hdc, (HPALETTE) GetStockObject(DEFAULT_PALETTE), TRUE );
        DeleteObject( hPal );
    }
}

/******************************Public*Routine******************************\
* ReCreateRGBPalette
*
*
\**************************************************************************/

void
SS_PAL::ReCreateRGBPalette()
{
    if( bTakeOver )
        return;

    HPALETTE hPalTmp = hPal;
    hPal = MakeRGBPalette();
    if( hPal ) {
        DeleteObject( hPalTmp );
        bFlush = TRUE;
    }
}

/******************************Public*Routine******************************\
* MakeRGBPalette
*
* Creates an HPALETTE with values required for a logical rgb palette.
* If bUseStatic is TRUE, the static system
* colors will be overridden.  Otherwise, the PALETTEENTRY array will be
* fixed up to contain the default static system colors.
*
\**************************************************************************/

HPALETTE
SS_PAL::MakeRGBPalette()
{
    LOGPALETTE *pPal;
    HPALETTE hpal;
    int count, i;
    PIXELFORMATDESCRIPTOR *ppfd = &pfd;

    count = 1 << ppfd->cColorBits;
    nEntries = count;
    pPal = (PLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE) +
            count * sizeof(PALETTEENTRY));
    if( !pPal )
        return (HPALETTE) 0;

    pPal->palVersion = 0x300;
    pPal->palNumEntries = count;

    PALETTEENTRY *pEntry = pPal->palPalEntry;

    for ( i = 0; i < count ; i++, pEntry++ )
    {
        pEntry->peRed   = ss_ComponentFromIndex(i, ppfd->cRedBits,
                                ppfd->cRedShift);
        pEntry->peGreen = ss_ComponentFromIndex(i, ppfd->cGreenBits,
                                ppfd->cGreenShift);
        pEntry->peBlue  = ss_ComponentFromIndex(i, ppfd->cBlueBits,
                                ppfd->cBlueShift);
        pEntry->peFlags = PC_NOCOLLAPSE;
    }

    if( count == 256 )
    {
    // If app set static system color usage for fixed palette support,
    // setup to take over the static colors.  Otherwise, fixup the
    // static system colors.

        if ( bUseStatic )
        {
        // Black and white already exist as the only remaining static
        // colors.  Let those remap.  All others should be put into
        // the palette (i.e., set PC_NOCOLLAPSE).

            pPal->palPalEntry[0].peFlags = 0;
            pPal->palPalEntry[255].peFlags = 0;
        }
        else
        {
        // The defaultOverride array is computed assuming a 332
        // palette where red has zero shift, etc.

            if ( (3 == ppfd->cRedBits)   && (0 == ppfd->cRedShift)   &&
                 (3 == ppfd->cGreenBits) && (3 == ppfd->cGreenShift) &&
                 (2 == ppfd->cBlueBits)  && (6 == ppfd->cBlueShift) )
            {
                pEntry = pPal->palPalEntry;
                UpdateStaticMapping( pEntry );

                for ( i = 0 ; i < STATIC_COLORS ; i++)
                {
                    pEntry[aiDefaultOverride[i]] = apeDefaultPalEntry[i];
                }
            }
        }
    }

    hpal = CreatePalette(pPal);
    LocalFree(pPal);
    return hpal;
}


SCRNSAVE.CXX

//----------------------------------------------------------------------------
//
// SCRNSAVE.C      --    skeleton for screen saver application
//
//----------------------------------------------------------------------------

#define GL_SCRNSAVE 1

#define WIN31
#include <windows.h>
#include <windowsx.h>
#include "scrnsave.h"
#include <regstr.h>
#include    <imm.h>
#ifdef GL_SCRNSAVE
#include "glscrnsv.h"
#endif

#define DBG_MSGS    0

const TCHAR szScreenSaverKey[] = REGSTR_PATH_SCREENSAVE;
TCHAR szPasswordActiveValue[] = REGSTR_VALUE_USESCRPASSWORD;
const TCHAR szPasswordValue[] = REGSTR_VALUE_SCRPASSWORD;
TCHAR szPwdDLL[] = TEXT("PASSWORD.CPL");
CHAR szFnName[] = "VerifyScreenSavePwd";        // Proc name, must be ANSI
TCHAR szImmDLL[] = TEXT("IMM32.DLL");
CHAR szImmFnc[] = "ImmAssociateContext";        // Proc name, must be ANSI

typedef BOOL (FAR PASCAL * VERIFYPWDPROC) (HWND);
typedef BOOL (FAR PASCAL * IMMASSOCPROC) (HWND,HIMC);

//----------------------------------------------------------------------------
// variables declared in SCRNSAVE.H
HINSTANCE hMainInstance = 0;
HWND hMainWindow = 0;
BOOL fChildPreview = FALSE;

//----------------------------------------------------------------------------
// other globals
POINT ptMouse;
BOOL fClosing = FALSE;
BOOL fCheckingPassword = FALSE;
HINSTANCE hInstPwdDLL = NULL;
VERIFYPWDPROC VerifyPassword = NULL;
static BOOL preview_like_fullscreen = FALSE;
HINSTANCE hInstImm = NULL;
IMMASSOCPROC ImmFnc = NULL;
HIMC hPrevImc = (HIMC)0L;

static BOOL fOnWin95 = FALSE;  //TRUE if on Chicago, FALSE if on Cairo

//----------------------------------------------------------------------------
DWORD dwWakeThreshold = 4;  //default to slight movement
DWORD dwPasswordDelay = 0;
DWORD dwBlankTime = 0;
#define MAX_PASSWORD_DELAY_IN_SECONDS (60)

//----------------------------------------------------------------------------
// forward declarations of internal fns
#ifndef GL_SCRNSAVE
// These are hooked out to glscrnsv.cxx
static int DoScreenSave( HWND hParent );
static int DoConfigBox( HWND hParent );
#endif
static int DoSaverPreview( LPCTSTR szUINTHandle );
static int DoChangePw( LPCTSTR szUINTHandle );
static BOOL DoPasswordCheck( HWND hParent );
VOID LoadPwdDLL(VOID);
VOID UnloadPwdDLL(VOID);

//----------------------------------------------------------------------------
// helper for time
static DWORD
GetElapsedTime(DWORD from, DWORD to)
{
    return (to >= from)? (to - from) : (1 + to + (((DWORD)-1) - from));
}

//----------------------------------------------------------------------------
// helper to convert text to unsigned int
static UINT
atoui( LPCTSTR szUINT )
{
   UINT uValue = 0;

   while( ( *szUINT >= TEXT('0') ) && ( *szUINT <= TEXT('9') ) )
      uValue = ( ( uValue * 10 ) + ( *szUINT++ - TEXT('0') ) );

   return uValue;
}

//----------------------------------------------------------------------------
// Local reboot and hotkey control (on Win95)
static void
HogMachine( BOOL value )
{
    BOOL dummy;

    //
    // NT is always secure, therefore we don't need to call this on Cairo/NT
    //
    if (fOnWin95) {
        SystemParametersInfo( SPI_SCREENSAVERRUNNING, value, &dummy, 0 );
    }
}

//----------------------------------------------------------------------------
// entry point
int PASCAL
WinMainN( HINSTANCE hInst, HINSTANCE hPrev, LPTSTR szCmdLine, int nCmdShow )
{
   LPCTSTR pch = szCmdLine;
   HWND hParent = 0;
   OSVERSIONINFO osvi;

   hMainInstance = hInst;

   osvi.dwOSVersionInfoSize = sizeof(osvi);
   fOnWin95 = (GetVersionEx(&osvi) &&
                osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);

   _try
   {
      for(;;) switch( *pch )
      {
          case TEXT('S'):
          case TEXT('s'):
              return DoScreenSave( NULL );

#ifdef GL_SCRNSAVE
          case TEXT('W'):
          case TEXT('w'):
              do pch++; while( *pch == TEXT(' ') );  // size parameters
              return DoWindowedScreenSave( pch );
#endif

          case TEXT('L'):
          case TEXT('l'):
              // special switch for tests such as WinBench
              // this is NOT a hack to make bechmarks look good
              // it's a hack to allow you to benchmark a screen saver
              // many bechmarking apps require the whole screen in foreground
              // which makes it hard to measure how a screensaver adds CPU load
              // you must provide a parent window (just like preview mode)
              preview_like_fullscreen = TRUE;
          case TEXT('P'):
          case TEXT('p'):
              do pch++; while( *pch == TEXT(' ') );  // skip to the good stuff
              return DoSaverPreview( pch );

          case TEXT('A'):
          case TEXT('a'):
              if (!fOnWin95)
                  return -1;
              do pch++; while( *pch == TEXT(' ') );  // skip to the good stuff
              return DoChangePw( pch );

          case TEXT('C'):
          case TEXT('c'):
              return DoConfigBox( GetForegroundWindow() );

          case TEXT('\0'):
              return DoConfigBox( NULL );

          case TEXT(' '):
          case TEXT('-'):
          case TEXT('/'):
              pch++;   // skip spaces and common switch prefixes
              break;

          default:
              return -1;
      }
   }
   _except(UnhandledExceptionFilter(GetExceptionInformation()))
   {
      // don't leave local reboot and hotkeys disabled on Win95
      HogMachine( FALSE );
   }

}

//----------------------------------------------------------------------------
// default screen-saver proc, declared in SCRNSAVE.H
// intended to be called by the consumer's ScreenSaverProc where
// DefWindowProc would normally be called
LRESULT WINAPI
DefScreenSaverProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
#if DBG_MSGS
    TCHAR szBuff[80];

    wsprintf( szBuff, TEXT("*** DefSSP received:\t0x%04lx 0x%08lx 0x%08lx\n"), uMsg, wParam, lParam );
    OutputDebugString(szBuff);
#endif

   if( !fChildPreview && !fClosing )
   {
      switch( uMsg )
      {
         case WM_CLOSE:
            //
            // Only do password check if on Windows 95.  WinNT (Cairo) has
            // the password check built into the security desktop for
            // C2 compliance.
            //
            if (fOnWin95) {
                if( !DoPasswordCheck( hWnd ) )
                {
                    GetCursorPos( &ptMouse );  // re-establish
                    return FALSE;
                }
            }
#ifdef GL_SCRNSAVE
            // We need to know when we're being terminated, so we can do
            // various clean-up stuff
            SendMessage( hWnd, SS_WM_CLOSING, 0, 0 );
#endif
            break;

         case SCRM_VERIFYPW:
            if (fOnWin95)
                return ( VerifyPassword? (LRESULT)VerifyPassword( hWnd ) : 1L );
            break;

         default:
         {
            POINT ptMove, ptCheck;

            if( fCheckingPassword )
                break;

            switch( uMsg )
            {
                case WM_SHOWWINDOW:
                    if( (BOOL)wParam )
                    SetCursor( NULL );
                    break;

                case WM_SETCURSOR:
                    SetCursor( NULL );
                    return TRUE;

                case WM_MOUSEMOVE:
                    GetCursorPos( &ptCheck );
                    if( ( ptMove.x = ptCheck.x - ptMouse.x ) && ( ptMove.x < 0 ) )
                        ptMove.x *= -1;
                    if( ( ptMove.y = ptCheck.y - ptMouse.y ) && ( ptMove.y < 0 ) )
                        ptMove.y *= -1;
                    if( ((DWORD)ptMove.x + (DWORD)ptMove.y) > dwWakeThreshold )
                    {
                        PostMessage( hWnd, WM_CLOSE, 0, 0l );
                        ptMouse = ptCheck;
                    }
                    break;

                case WM_ACTIVATEAPP:
                    if( wParam ) break;
                case WM_LBUTTONDOWN:
                case WM_MBUTTONDOWN:
                case WM_RBUTTONDOWN:
                case WM_KEYDOWN:
                case WM_SYSKEYDOWN:
                    PostMessage( hWnd, WM_CLOSE, 0, 0l );
                    break;
            }
         }
      }
   }

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

//----------------------------------------------------------------------------
// This window procedure takes care of important stuff before calling the
// consumer's ScreenSaverProc.
LRESULT WINAPI
RealScreenSaverProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
   switch( uMsg )
   {
      case WM_CREATE:
         // screen saver does not need the IME
         if ((hInstImm = GetModuleHandle(szImmDLL)) &&
             (ImmFnc = (IMMASSOCPROC)GetProcAddress(hInstImm,szImmFnc)))
             hPrevImc = ImmFnc(hWnd, (HIMC)NULL);

         // establish the mouse position
         GetCursorPos( &ptMouse );

         if( !fChildPreview )
            SetCursor( NULL );

         break;

      case WM_DESTROY:
         // screen saver does not need the IME
         if( hInstImm && ImmFnc && hPrevImc )
            ImmFnc(hWnd, hPrevImc);

         PostQuitMessage( 0 );
         break;

      case WM_SETTEXT:
         // we need to be able to use FindWindow() to find running instances
         // of full-screen windows screen savers
         // NOTE: USER slams our title in during WM_NCCREATE by calling the
         // defproc for WM_SETTEXT directly, so the initial title will get
         // there.  If this ever changes, we can simply set a bypass flag
         // during WM_NCCREATE processing.
         return FALSE;

      case WM_SYSCOMMAND:
         if (!fChildPreview)
         {
            switch (wParam)
            {
               case SC_NEXTWINDOW:       // no Alt-tabs
               case SC_PREVWINDOW:       // no shift-alt-tabs
               case SC_SCREENSAVE:       // no more screensavers
                  return FALSE;
            }
         }
         break;

      case WM_HELP:
      case WM_CONTEXTMENU:
         if( fChildPreview )
         {
            // if we're in preview mode, pump the help stuff to our owner
            HWND hParent = GetParent( hWnd );

            if( hParent && IsWindow( hParent ) )
               PostMessage( hParent, uMsg, (WPARAM)hParent, lParam );
               return TRUE;
         }
         break;

      case WM_TIMER:
         if( fClosing )
            return FALSE;
         Sleep( 0 );
         break;

      case WM_MOUSEMOVE:
      case WM_LBUTTONDOWN:
      case WM_MBUTTONDOWN:
      case WM_RBUTTONDOWN:
      case WM_KEYDOWN:
      case WM_SYSKEYDOWN:
         if( fClosing )
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
         break;

      case WM_PAINT:
         if( fClosing )
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
         if( !fChildPreview )
            SetCursor( NULL );
         break;
   }

   return ScreenSaverProc( hWnd, uMsg, wParam, lParam );
}

#ifdef GL_SCRNSAVE
void
#else
static void
#endif
InitRealScreenSave()
{

   LoadPwdDLL();
}

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

#ifndef GL_SCRNSAVE

static int
DoScreenSave( HWND hParent )
{
   LPCTSTR pszWindowClass = TEXT("WindowsScreenSaverClass");
   LPCTSTR pszWindowTitle;

   WNDCLASS cls;
   MSG      msg;
   UINT     uStyle;
   UINT     uExStyle;
   int      nCx, nCy;

   cls.hCursor        = NULL;
   cls.hIcon          = LoadIcon( hMainInstance, MAKEINTATOM( ID_APP ) );
   cls.lpszMenuName   = NULL;
   cls.lpszClassName  = pszWindowClass;
   cls.hbrBackground  = GetStockObject( BLACK_BRUSH );
   cls.hInstance      = hMainInstance;
   cls.style          = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_OWNDC;
   cls.lpfnWndProc    = RealScreenSaverProc;
   cls.cbWndExtra     = 0;
   cls.cbClsExtra     = 0;

   if( hParent )
   {
      RECT rcParent;
      GetClientRect( hParent, &rcParent );
      nCx = rcParent.right;
      nCy = rcParent.bottom;
      uStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN;
      uExStyle = 0;

      fChildPreview = TRUE;
      pszWindowTitle = TEXT("Preview");      // MUST differ from full screen
   }
   else
   {
      HWND hOther;

      nCx = GetSystemMetrics( SM_CXSCREEN );
      nCy = GetSystemMetrics( SM_CYSCREEN );
      uStyle = WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
      uExStyle = WS_EX_TOPMOST;

      pszWindowTitle = TEXT("Screen Saver"); // MUST differ from preview

      // if there is another NORMAL screen save instance, switch to it
      hOther = FindWindow( pszWindowClass, pszWindowTitle );

      if( hOther && IsWindow( hOther ) )
      {
         SetForegroundWindow( hOther );
         return 0;
      }

      InitRealScreenSave();
   }

   if( RegisterClass( &cls ) )
   {
      hMainWindow = CreateWindowEx( uExStyle, pszWindowClass, pszWindowTitle,
                        uStyle, 0, 0, nCx, nCy, hParent, (HMENU)NULL,
                        hMainInstance, (LPVOID)NULL );
   }

   if( hMainWindow )
   {
      if( !fChildPreview )
         SetForegroundWindow( hMainWindow );

      while( GetMessage( &msg, NULL, 0, 0 ) )
      {
         TranslateMessage( &msg );
         DispatchMessage( &msg );
      }
   }

   // free password-handling DLL if loaded
   UnloadPwdDLL();

   return msg.wParam;
}
#endif

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

static int
DoSaverPreview( LPCTSTR szUINTHandle )
{
   // get parent handle from string
   HWND hParent = (HWND)atoui( szUINTHandle );

   // only preview on a valid parent window (NOT full screen)
   return ( (hParent && IsWindow( hParent ))? DoScreenSave( hParent ) : -1 );
}

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

#ifndef GL_SCRNSAVE

static int
DoConfigBox( HWND hParent )
{
   // let the consumer register any special controls for the dialog
   if( !RegisterDialogClasses( hMainInstance ) )
      return FALSE;

   return DialogBox( hMainInstance, MAKEINTRESOURCE( DLG_SCRNSAVECONFIGURE ),
                     hParent, (DLGPROC)ScreenSaverConfigureDialog );
}
#endif

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

static int
DoChangePw( LPCTSTR szUINTHandle )
{
   // get parent handle from string
   HWND hParent = (HWND)atoui( szUINTHandle );

   if( !hParent || !IsWindow( hParent ) )
      hParent = GetForegroundWindow();

   // allow the library to be hooked
   ScreenSaverChangePassword( hParent );
   return 0;
}

static const TCHAR szMprDll[] = TEXT("MPR.DLL");       // not to be localized
static const TCHAR szProviderName[] = TEXT("SCRSAVE"); // not to be localized

#ifdef UNICODE
static const CHAR szPwdChangePW[] = "PwdChangePasswordW"; // not to be localized
#else
static const CHAR szPwdChangePW[] = "PwdChangePasswordA"; // not to be localized
#endif

// bogus prototype
typedef DWORD (FAR PASCAL *PWCHGPROC)( LPCTSTR, HWND, DWORD, LPVOID );

void WINAPI
ScreenSaverChangePassword( HWND hParent )
{
   HINSTANCE mpr = LoadLibrary( szMprDll );

   if( mpr )
   {
      PWCHGPROC pwd = (PWCHGPROC)GetProcAddress( mpr, szPwdChangePW );

      if( pwd )
         pwd( szProviderName, hParent, 0, NULL );

      FreeLibrary( mpr );
   }
}

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

static BOOL
DoPasswordCheck( HWND hParent )
{
   // don't reenter and don't check when we've already decided
   if( fCheckingPassword || fClosing )
      return FALSE;

   if( VerifyPassword )
   {
      static DWORD lastcheck = (DWORD)-1;
      DWORD curtime = GetTickCount();
      MSG msg;

      if (dwPasswordDelay &&
         (GetElapsedTime(dwBlankTime, curtime) < dwPasswordDelay))
      {
         fClosing = TRUE;
         goto _didcheck;
      }

      // no rapid checking...
      if ((lastcheck != (DWORD)-1) &&
         (GetElapsedTime(lastcheck, curtime) < 200))
      {
         goto _didcheck;
      }

      // do the check
      fCheckingPassword = TRUE;

#ifdef GL_SCRNSAVE
      // Put ss in idle mode during password dialog processing
      SendMessage( hParent, SS_WM_IDLE, SS_IDLE_ON, 0L );
#endif

      // flush WM_TIMER messages before putting up the dialog
      PeekMessage( &msg, hParent, WM_TIMER, WM_TIMER, PM_REMOVE | PM_NOYIELD );
      PeekMessage( &msg, hParent, WM_TIMER, WM_TIMER, PM_REMOVE | PM_NOYIELD );

      // call the password verify proc
      fClosing = (BOOL)SendMessage( hParent, SCRM_VERIFYPW, 0, 0L );

      fCheckingPassword = FALSE;

#ifdef GL_SCRNSAVE
      // Restore normal display mode
      SendMessage( hParent, SS_WM_IDLE, SS_IDLE_OFF, 0L );
#endif

      if (!fClosing)
         SetCursor(NULL);

      // curtime may be outdated by now
      lastcheck = GetTickCount();
   }
   else
   {
      // passwords disabled or unable to load handler DLL, always allow exit
      fClosing = TRUE;
   }

_didcheck:
   return fClosing;
}

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

int _stdcall
DummyEntry( void )
{
    int i;
    STARTUPINFO si;
    LPTSTR pszCmdLine = GetCommandLine();

    if ( *pszCmdLine == TEXT('\"')) {
        /*
         * Scan, and skip over, subsequent characters until
         * another double-quote or a null is encountered.
         */
        while (*(pszCmdLine = CharNext(pszCmdLine)) &&
              (*pszCmdLine != TEXT('\"')) );
        /*
         * If we stopped on a double-quote (usual case), skip
         * over it.
         */
        if ( *pszCmdLine == TEXT('\"') )
            pszCmdLine++;
    }
    else {
        while ((UINT)*pszCmdLine > (UINT)TEXT(' '))
            pszCmdLine = CharNext(pszCmdLine);
    }

    /*
     * Skip past any white space preceeding the second token.
     */
    while (*pszCmdLine && ((UINT)*pszCmdLine <= (UINT)TEXT(' '))) {
        pszCmdLine = CharNext(pszCmdLine);
    }

    si.dwFlags = 0;
    GetStartupInfo(&si);

    i = WinMainN(GetModuleHandle(NULL), NULL, pszCmdLine,
        si.dwFlags & STARTF_USESHOWWINDOW ? si.wShowWindow : SW_SHOWDEFAULT);

    ExitProcess(i);
    return i;   // We never comes here.
}

//----------------------------------------------------------------------------
// main() entry point to satisfy old NT screen savers
void _cdecl main( int argc, char *argv[] ) {
    DummyEntry();
}

//----------------------------------------------------------------------------
// WinMain() entry point to satisfy old NT screen savers
int PASCAL WinMain( HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow ) {
    DummyEntry();
    return 0;

    // reference unreferenced parameters
    (void)hInst;
    (void)hPrev;
    (void)szCmdLine;
    (void)nCmdShow;
}

VOID LoadPwdDLL(VOID)
{
    HKEY hKey;

    if (!fOnWin95)
        return;

    if (hInstPwdDLL)
        UnloadPwdDLL();

    // look in registry to see if password turned on, otherwise don't
    // bother to load password handler DLL
    if (RegOpenKey(HKEY_CURRENT_USER,szScreenSaverKey,&hKey) ==
        ERROR_SUCCESS)
    {
        DWORD dwVal,dwSize=sizeof(dwVal);

        if ((RegQueryValueEx(hKey,szPasswordActiveValue,
            NULL,NULL,(BYTE *) &dwVal,&dwSize) == ERROR_SUCCESS)
            && dwVal)
        {

            // try to load the DLL that contains password proc.
            hInstPwdDLL = LoadLibrary(szPwdDLL);
            if (hInstPwdDLL)
            {
                VerifyPassword = (VERIFYPWDPROC) GetProcAddress(hInstPwdDLL,
                    szFnName);

                if( VerifyPassword )
                    HogMachine( TRUE );
                else
                    UnloadPwdDLL();
            }
        }

        RegCloseKey(hKey);
    }

}

VOID UnloadPwdDLL(VOID)
{
    if (!fOnWin95)
        return;

    if (hInstPwdDLL)
    {
        FreeLibrary(hInstPwdDLL);
        hInstPwdDLL = NULL;

        if( VerifyPassword )
        {
            VerifyPassword = NULL;
            HogMachine( FALSE );
        }
    }
}

//----------------------------------------------------------------------------
// compatbility stuff  (to make porting easier)
TCHAR szAppName[ APPNAMEBUFFERLEN ];
TCHAR szName[ TITLEBARNAMELEN ];
TCHAR szIniFile[ MAXFILELEN ];
TCHAR szScreenSaver[ 22 ];
TCHAR szHelpFile[ MAXFILELEN ];
TCHAR szNoHelpMemory[ BUFFLEN ];

// Quick fix for old screen savers that don't know about context
// sensitive help
UINT  MyHelpMessage = WM_HELP;


SSA8.C

/******************************Module*Header*******************************\
* Module Name: ssa8.c
*
* Operations on .a8 files
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sscommon.h"

#define ESCAPE 0
#define ESC_ENDLINE 0
#define ESC_ENDBITMAP 1
#define ESC_DELTA 2
#define ESC_RANDOM 3
#define RANDOM_COUNT(c) ((c)-(ESC_RANDOM-1))

#define COUNT_SIZE 256
#define MAXRUN (COUNT_SIZE-1)
#define MAXRND (RANDOM_COUNT(COUNT_SIZE)-1)

typedef unsigned char *HwMem;
typedef unsigned long HwMemSize;
#define HW_UNALIGNED UNALIGNED

static HwMemSize HwuRld(HwMem src, HwMem dest, HwMemSize stride)
{
    unsigned short code;
    unsigned char run, esc;
    size_t len;
    HwMem s;
    HwMem f;

    s = src;
    f = dest;
    for (;;)
    {
        code = *((unsigned short HW_UNALIGNED *)s)++;
        run = code & 0xff;
        esc = code >> 8;
        if (run == ESCAPE)
        {
            if (esc == ESC_ENDBITMAP)
            {
                break;
            }
            else if (esc == ESC_DELTA)
            {
                len = *((unsigned short HW_UNALIGNED *)s)++;
                while (len-- > 0)
                {
                    *f = 0;
                    f += stride;
                }
            }
            else if (esc >= ESC_RANDOM)
            {
                len = RANDOM_COUNT(esc);
                while (len-- > 0)
                {
                    *f = *s++;
                    f += stride;
                }
            }
        }
        else
        {
            while (run-- > 0)
            {
                *f = esc;
                f += stride;
            }
        }
    }

    return s-src;
}

static HwMemSize HwuRldTo32(HwMem src, HwMem dest, HwMemSize stride,
                            DWORD *translate)
{
    unsigned short code;
    unsigned char run, esc;
    size_t len;
    HwMem s;
    DWORD *f, tran;

    s = src;
    f = (DWORD *)dest;
    for (;;)
    {
        code = *((unsigned short HW_UNALIGNED *)s)++;
        run = code & 0xff;
        esc = code >> 8;
        if (run == ESCAPE)
        {
            if (esc == ESC_ENDBITMAP)
            {
                break;
            }
            else if (esc == ESC_DELTA)
            {
                len = *((unsigned short HW_UNALIGNED *)s)++;
                while (len-- > 0)
                {
                    *f = translate[0];
                    f += stride;
                }
            }
            else if (esc >= ESC_RANDOM)
            {
                len = RANDOM_COUNT(esc);
                while (len-- > 0)
                {
                    *f = translate[*s++];
                    f += stride;
                }
            }
        }
        else
        {
            tran = translate[esc];
            while (run-- > 0)
            {
                *f = tran;
                f += stride;
            }
        }
    }

    return s-src;
}

#define ALPHA_SIGNATURE 0xa0a1a2a3
#define COMPRESS_NONE 0
#define COMPRESS_RLE  1

BOOL ss_A8ImageLoad(void *pvResource, TEXTURE *ptex)
{
    DWORD compress;
    DWORD size;
    DWORD *pal;
    DWORD *pdwA8;

    pdwA8 = (DWORD *)pvResource;

    // Check data signature for alpha texture format
    if (*pdwA8 != ALPHA_SIGNATURE)
    {
        return FALSE;
    }
    pdwA8++;

    ptex->width = *pdwA8++;
    ptex->height = *pdwA8++;

    // Make sure depth is 8bpp
    if (*pdwA8 != 8)
    {
        return FALSE;
    }
    pdwA8++;

    size = ptex->width*ptex->height;

    // Compression type
    compress = *pdwA8++;
    // Compressed data size only if compressed, not used
    pdwA8++;

    // Remember pointer to palette data
    pal = pdwA8;
    pdwA8 += 256;

    if (ss_PalettedTextureEnabled())
    {
        // Allocate data for final image
        ptex->data = malloc(size);
        if (ptex->data == NULL)
        {
            return FALSE;
        }

        ptex->pal_size = 256;
        ptex->pal = malloc(ptex->pal_size*sizeof(RGBQUAD));
        if (ptex->pal == NULL)
        {
            free(ptex->data);
            return FALSE;
        }
        memcpy(ptex->pal, pal, ptex->pal_size*sizeof(RGBQUAD));

        // Unpack 8bpp data into final image
        if (compress == COMPRESS_NONE)
        {
            memcpy(ptex->data, pdwA8, size);
        }
        else
        {
            HwuRld((HwMem)pdwA8, ptex->data, 1);
        }

        ptex->format = GL_COLOR_INDEX;
        ptex->components = GL_COLOR_INDEX8_EXT;
    }
    else
    {
        // Allocate data for final image
        ptex->data = malloc(size*sizeof(DWORD));
        if (ptex->data == NULL)
        {
            return FALSE;
        }

        ptex->pal_size = 0;
        ptex->pal = NULL;

        // Unpack 8bpp data into final image
        if (compress == COMPRESS_NONE)
        {
            DWORD i;
            BYTE *src;
            DWORD *dst;

            src = (BYTE *)pdwA8;
            dst = (DWORD *)ptex->data;
            for (i = 0; i < size; i++)
            {
                *dst++ = pal[*src++];
            }
        }
        else
        {
            HwuRldTo32((HwMem)pdwA8, ptex->data, 1, pal);
        }

        ptex->format = GL_BGRA_EXT;
        ptex->components = 4;
    }

    return TRUE;
}


SSCOMMON.H

/******************************Module*Header*******************************\
* Module Name: sscommon.h
*
* Defines and externals for screen saver common shell
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#ifndef __sscommon_h__
#define __sscommon_h__

#include <GL\gl.h>
#include <GL\glaux.h>
#include <assert.h>
#include "matname.h"  // material names

#ifdef __cplusplus
extern "C" {
#endif

#include "ssdebug.h"

#define FAILURE             0
#define SUCCESS             1

// Maximum texture bitmap dimensions.

#define TEX_WIDTH_MAX   1280
#define TEX_HEIGHT_MAX  1024

#define PI 3.14159265358979323846f
// double version of PI
#define PI_D 3.14159265358979323846264338327950288419716939937510
#define ONE_OVER_PI (1.0f / PI)
#define ROOT_TWO 1.414213562373f

#define GEN_STRING_SIZE 64

// texture quality level
enum {
    TEXQUAL_DEFAULT = 0,
    TEXQUAL_HIGH
};

typedef struct _point2d {
    GLfloat x;
    GLfloat y;
} POINT2D;

typedef struct _ipoint2d {
    int x;
    int y;
} IPOINT2D;

typedef struct _point3d {
    GLfloat x;
    GLfloat y;
    GLfloat z;
} POINT3D;

typedef struct _ipoint3d {
    int x;
    int y;
    int z;
} IPOINT3D;

typedef struct _texpoint2d {
    GLfloat s;
    GLfloat t;
} TEX_POINT2D;

typedef struct _isize {
    int width;
    int height;
} ISIZE;

typedef struct _fsize {
    GLfloat width;
    GLfloat height;
} FSIZE;

typedef struct _glrect {
    int x, y;
    int width, height;
} GLRECT;

// texture data
typedef struct {
    int     width;
    int     height;
    GLenum  format;
    GLsizei components;
    float   origAspectRatio; // original width/height aspect ratio
    unsigned char *data;
    GLuint  texObj;          // texture object
    int     pal_size;
    int     iPalRot;         // current palette rotation (not used yet)
    RGBQUAD *pal;
} TEXTURE, *HTEXTURE;

#ifndef GL_EXT_paletted_texture
#define GL_COLOR_INDEX1_EXT                   0x80E2
#define GL_COLOR_INDEX2_EXT                   0x80E3
#define GL_COLOR_INDEX4_EXT                   0x80E4
#define GL_COLOR_INDEX8_EXT                   0x80E5
#define GL_COLOR_INDEX12_EXT                  0x80E6
#define GL_COLOR_INDEX16_EXT                  0x80E7
typedef void (APIENTRY * PFNGLCOLORTABLEEXTPROC)
    (GLenum target, GLenum internalFormat, GLsizei width, GLenum format,
     GLenum type, const GLvoid *data);
typedef void (APIENTRY * PFNGLCOLORSUBTABLEEXTPROC)
    (GLenum target, GLsizei start, GLsizei count, GLenum format,
     GLenum type, GLvoid *data);
#endif

// texture resource

#define RT_RGB          99
#define RT_MYBMP        100
#define RT_A8           101

// texture resource types
enum {
    TEX_UNKNOWN = 0,
    TEX_RGB,
    TEX_BMP,
    TEX_A8
};

typedef struct {
    int     type;
    int     name;
} TEX_RES;

typedef struct _MATRIX {
    GLfloat M[4][4];
} MATRIX;

typedef struct strRGBA {
    GLfloat r;
    GLfloat g;
    GLfloat b;
    GLfloat a;
} RGBA;

typedef struct {
    BYTE r;
    BYTE g;
    BYTE b;
} RGB8;

typedef struct {
    BYTE r;
    BYTE g;
    BYTE b;
    BYTE a;
} RGBA8;

// DlgDraw

enum {
    DLG_INTENSITY_LOW = 0,
    DLG_INTENSITY_MID,
    DLG_INTENSITY_HIGH,
};

// Callback function defines
typedef void (CALLBACK* SSINITPROC)( void *);
typedef void (CALLBACK* SSRESHAPEPROC)(int, int, void *);
typedef void (CALLBACK* SSREPAINTPROC)( LPRECT, void *);
typedef void (CALLBACK* SSUPDATEPROC)( void *);
typedef void (CALLBACK* SSFINISHPROC)( void *);
typedef void (CALLBACK* SSFLOATERFAILPROC)(void *);
typedef void (CALLBACK* SSFLOATERBOUNCEPROC)( void *);

// Defines for pixel format (internal use)
#define SS_DOUBLEBUF_BIT    (1 << 0)
#define SS_DEPTH16_BIT      (1 << 1)
#define SS_DEPTH32_BIT      (1 << 2)
#define SS_ALPHA_BIT        (1 << 3)
#define SS_BITMAP_BIT       (1 << 4)
#define SS_NO_SYSTEM_PALETTE_BIT       (1 << 5)
#define SS_GENERIC_UNACCELERATED_BIT   (1 << 6)

#define SS_HAS_DOUBLEBUF(x) ((x) & SS_DOUBLEBUF_BIT)
#define SS_HAS_DEPTH16(x)((x) & SS_DEPTH16_BIT)
#define SS_HAS_DEPTH32(x)((x) & SS_DEPTH32_BIT)
#define SS_HAS_ALPHA(x)     ((x) & SS_ALPHA_BIT)
#define SS_HAS_BITMAP(x)    ((x) & SS_BITMAP_BIT)

// Depth types
enum {
    SS_DEPTH_NONE = 0,
    SS_DEPTH16,
    SS_DEPTH32,
};

// ss context structures

// Note: all *_INFO structures are requests between common and client ss

typedef struct {
    POINT2D    posInc;          // base position increment
    POINT2D    posIncVary;      // +/- variation for posInc on bounce
} MOTION_INFO;

// internal motion structure
typedef struct {
    POINT2D    pos;             // position
    POINT2D    posInc;          // base position increment
    POINT2D    posIncVary;      // +/- variation for posInc on bounce
    POINT2D    posIncCur;       // Current position increment
} MOTION;

// Size and motion attributes of the OpenGL window that floats around.
typedef struct {
    ISIZE      size;
    IPOINT2D    pos;             // position
    MOTION_INFO     motionInfo;
} CHILD_INFO;

typedef void (CALLBACK* SSCHILDSIZEPROC)( ISIZE *, CHILD_INFO * );

// Size and motion attributes of the OpenGL window that floats around.
typedef struct {
    BOOL       bMotion;         // if floater should move or not
    BOOL       bSubWindow;      // If floater is logical sub-window
    SSCHILDSIZEPROC   ChildSizeFunc; // Callback to set size based on parent
} FLOATER_INFO;

// SS_BITMAP already defined in winuser.h
#if defined( SS_BITMAP )
#undef SS_BITMAP
#endif

typedef struct {
    HDC     hdc;
    ISIZE   size;
    HBITMAP hbm;
    HBITMAP hbmOld; // ? necessary ?
} SS_BITMAP;

// Attributes for StretchBlt mode
typedef struct {
    BOOL       bRatioMode;      // use ratio or base width
    float      widthRatio;      // e.g. 2 means use base of (window width / 2)
    float      heightRatio;
    int        baseWidth;
    int        baseHeight;
    SS_BITMAP  ssbm;
} STRETCH_INFO;

typedef struct {
    BOOL bFloater;
    FLOATER_INFO floaterInfo;
    BOOL bStretch;
    STRETCH_INFO stretchInfo;
    BOOL bDoubleBuf;
    int  depthType;
} SSContext, SSC, *PSSC;

typedef struct _MATERIAL {
    RGBA ka;
    RGBA kd;
    RGBA ks;
    GLfloat specExp;
} MATERIAL;

// texture file info

typedef struct {
    int     nOffset;  // filename offset into pathname
    TCHAR   szPathName[MAX_PATH];  // texture pathname
} TEXFILE;

// texture file processing messages

typedef struct {
    TCHAR   szWarningMsg[MAX_PATH];
    TCHAR   szBitmapSizeMsg[MAX_PATH];
    TCHAR   szBitmapInvalidMsg[MAX_PATH];
    TCHAR   szSelectAnotherBitmapMsg[MAX_PATH];
    TCHAR   szTextureDialogTitle[GEN_STRING_SIZE];
    TCHAR   szTextureFilter[2*GEN_STRING_SIZE];
    TCHAR   szBmp[GEN_STRING_SIZE];
    TCHAR   szDotBmp[GEN_STRING_SIZE];
} TEX_STRINGS;

// Resource constants common to all screen savers

#define IDS_COPYRIGHT           9001
#define IDS_GENNAME             9003
#define IDS_INIFILE             9006
#define IDS_HELPFILE            9009
#define IDS_WARNING             9014
#define IDS_ERROR               9015
#define IDS_BITMAP_SIZE         9016
#define IDS_BITMAP_INVALID      9017
#define IDS_SELECT_ANOTHER_BITMAP 9018
#define IDS_START_FAILED        9019
#define IDS_TEXTUREFILTER       9021
#define IDS_TEXTUREDIALOGTITLE  9022
#define IDS_BMP                 9023
#define IDS_DOTBMP              9024
#define IDS_STARDOTBMP          9025
#define IDS_RGB                 9030
#define IDS_DOTRGB              9031
#define IDS_STARDOTRGB          9032
#define IDS_TEXTURE             9126
#define IDS_TEXTURE_FILE_OFFSET 9127
#define IDS_SIZE                9129
#define IDS_TESSELATION         9130

// Useful macros

#define SS_MAX( a, b ) \
    ( a > b ? a : b )

#define SS_MIN( a, b ) \
    ( a < b ? a : b )

// macro to round up floating values
#define SS_ROUND_UP( fval ) \
    ( (((fval) - (FLOAT)(int)(fval)) > 0.0f) ? (int) ((fval)+1.0f) : (int) (fval) )

// macros to clamp a value within a range
#define SS_CLAMP_TO_RANGE( a, lo, hi ) ( (a < lo) ? lo : ((a > hi) ? hi : a) )
#define SS_CLAMP_TO_RANGE2( a, lo, hi ) \
    ( a = (a < lo) ? lo : ((a > hi) ? hi : a) )

// degree<->radian macros
#define ONE_OVER_180 (1.0f / 180.0f)
#define SS_DEG_TO_RAD( a ) ( (a*PI) * ONE_OVER_180 )
#define SS_RAD_TO_DEG( a ) ( (a*180.0f) * ONE_OVER_PI )

extern MATERIAL TeaMaterial[], TexMaterial[], ss_BlackMat;

// window handling

extern void ss_InitFunc(SSINITPROC);
extern void ss_ReshapeFunc(SSRESHAPEPROC);
extern void ss_RepaintFunc(SSREPAINTPROC);
extern void ss_UpdateFunc(SSUPDATEPROC);
extern void ss_FinishFunc(SSFINISHPROC);
extern void ss_FloaterBounceFunc(SSFLOATERBOUNCEPROC);

// This function *must* be defined by the screen saver
extern SSContext* ss_Init(void);
extern BOOL ss_ConfigInit( HWND hDlg );

extern void ss_DataPtr( void * );

extern BOOL ss_SetWindowAspectRatio( FLOAT aspect );
extern void ss_RandomWindowPos( void );

extern HWND ss_GetHWND();
extern HWND ss_GetGLHWND();
extern void ss_GetScreenSize( ISIZE *size );

extern HBITMAP
SSDIB_CreateCompatibleDIB(HDC hdc, HPALETTE hpal, ULONG ulWidth, ULONG ulHeight,
                    PVOID *ppvBits);
extern BOOL APIENTRY SSDIB_UpdateColorTable(HDC hdcMem, HDC hdc, HPALETTE hpal);

// Palette manage procs
extern LONG MainPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
extern LONG PaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
extern LONG FullScreenPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
extern LONG NullPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

// material processing

extern void ss_InitTeaMaterials();
extern void ss_InitTexMaterials();
extern void ss_InitMaterials();
extern void ss_SetMaterial( MATERIAL *pMat );
extern void ss_SetMaterialIndex( int index );
extern MATERIAL *ss_RandomTeaMaterial( BOOL bSet );
extern int  ss_RandomTeaMaterialIndex( BOOL bSet );
extern MATERIAL *ss_RandomTexMaterial( BOOL bSet );
extern int  ss_RandomTexMaterialIndex( BOOL bSet );
extern void ss_CreateMaterialGradient( MATERIAL *matInc, MATERIAL *startMat,
                        MATERIAL *endMat, int transCount );
extern void ss_TransitionMaterial( MATERIAL *transMat, MATERIAL *transMatInc );

// color

extern void ss_HsvToRgb(float h, float s, float v, RGBA *color );

// clear

extern int ss_RectWipeClear( int width, int height, int repCount );
extern int ss_DigitalDissolveClear( int width, int height, int size );

// utility

extern void ss_RandInit( void );
extern int ss_iRand( int max );
extern int ss_iRand2( int min, int max );
extern FLOAT ss_fRand( FLOAT min, FLOAT max );
extern BOOL ss_ChangeDisplaySettings( int width, int height, int bitDepth );
extern void ss_QueryDisplaySettings( void );
extern void ss_QueryOSVersion( void );
extern void ss_QueryGLVersion( void );
extern BOOL ss_fOnWin95( void );
extern BOOL ss_fOnNT35( void );
extern BOOL ss_fOnGL11( void );
extern BOOL ss_fPreviewMode( void );
extern BOOL ss_fFullScreenMode( void );
extern BOOL ss_fWindowMode( void );
extern BOOL ss_fConfigMode( void );
extern BOOL ss_RedrawDesktop( void );

// texture file processing

extern int  ss_LoadBMPTextureFile( LPCTSTR pszBmpfile, TEXTURE *pTex );
extern int  ss_LoadTextureFile( TEXFILE *texFile, TEXTURE *pTex );
extern int  ss_LoadTextureResource( TEX_RES *pTexRes, TEXTURE *pTex );
extern BOOL ss_CopyTexture( TEXTURE *pTexDst, TEXTURE *pTexSrc );
extern BOOL ss_SetTextureTransparency( TEXTURE *pTex, float alpha, BOOL bSet );
extern void ss_DisableTextureErrorMsgs();
extern void ss_SetTexture( TEXTURE *pTex );
extern void ss_SetTexturePalette( TEXTURE *pTex, int index );
extern void ss_DeleteTexture( TEXTURE *pTex );
extern BOOL ss_LoadTextureResourceStrings();
extern BOOL ss_DIBImageLoad(PVOID pvFile, TEXTURE *ptex);
extern BOOL ss_RGBImageLoad(PVOID pvFile, TEXTURE *ptex);
extern BOOL ss_A8ImageLoad(PVOID pvFile, TEXTURE *ptex);
extern BOOL ss_VerifyTextureFile( TEXFILE *ptf );
extern BOOL ss_SelectTextureFile( HWND hDlg, TEXFILE *ptf );
extern void ss_GetDefaultBmpFile( LPTSTR pszBmpFile );
extern void ss_InitAutoTexture( TEX_POINT2D *pTexRep );

// texture objects

extern BOOL ss_TextureObjectsEnabled( void );

// Paletted texture support
extern BOOL ss_PalettedTextureEnabled(void);
extern BOOL ss_QueryPalettedTextureEXT(void);

// math functions

extern POINT3D ss_ptZero;
extern void ss_xformPoint(POINT3D *ptOut, POINT3D *ptIn, MATRIX *);
extern void ss_xformNorm(POINT3D *ptOut, POINT3D *ptIn, MATRIX *);
extern void ss_matrixIdent(MATRIX *);
extern void ss_matrixRotate(MATRIX *m, double xTheta, double yTheta, double zTheta);
extern void ss_matrixTranslate(MATRIX *, double xTrans, double yTrans, double zTrans);
extern void ss_matrixMult( MATRIX *m1, MATRIX *m2, MATRIX *m3 );
extern void ss_calcNorm(POINT3D *norm, POINT3D *p1, POINT3D *p2, POINT3D *p3);
extern void ss_normalizeNorm(POINT3D *);
extern void ss_normalizeNorms(POINT3D *, ULONG);

// registry functions

extern BOOL ss_RegistrySetup( HINSTANCE hinst, int section, int file );
extern int  ss_GetRegistryInt( int name, int iDefault );
extern void ss_GetRegistryString( int name, LPTSTR lpDefault, LPTSTR lpDest, int bufSize );
extern void ss_WriteRegistryInt( int name, int iVal );
extern void ss_WriteRegistryString( int name, LPTSTR lpString );

// dialog helper functions

extern int ss_GetTrackbarPos( HWND hDlg, int item );
extern void ss_SetupTrackbar( HWND hDlg, int item, int lo, int hi, int lineSize, int pageSize, int pos );

extern BOOL gbTextureObjects; // from texture.c

#ifdef __cplusplus
}
#endif

#endif // __sscommon_h__


SSDEBUG.H

/******************************Module*Header*******************************\
* Module Name: ssdebug.h
*
* Debugging stuff
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __ssdebug_h__
#define __ssdebug_h__

ULONG DbgPrint(PCH Format, ...);

#define SS_LEVEL_ERROR 1L
#define SS_LEVEL_INFO  2L
#define SS_LEVEL_ENTRY 8L

#if DBG

extern long ssDebugMsg;
extern long ssDebugLevel;

#define SS_DBGPRINT( str )          DbgPrint("SS: " str )
#define SS_DBGPRINT1( str, a )      DbgPrint("SS: " str, a )
#define SS_DBGPRINT2( str, a, b )   DbgPrint("SS: " str, a, b )

#define SS_WARNING(str)             DbgPrint("SS: " str )
#define SS_WARNING1(str,a)          DbgPrint("SS: " str,a)
#define SS_WARNING2(str,a,b)        DbgPrint("SS: " str,a,b)

#define SS_RIP(str)                 {SS_WARNING(str); DebugBreak();}
#define SS_RIP1(str,a)              {SS_WARNING1(str,a); DebugBreak();}
#define SS_RIP2(str,a,b)            {SS_WARNING2(str,a,b); DebugBreak();}

#define SS_ASSERT(expr,str)            if(!(expr)) SS_RIP(str)
#define SS_ASSERT1(expr,str,a)         if(!(expr)) SS_RIP1(str,a)
#define SS_ASSERT2(expr,str,a,b)       if(!(expr)) SS_RIP2(str,a,b)

#define SS_ALLOC_FAILURE(str) \
    DbgPrint( "%s : Memory allocation failure\n", str );

#define SS_DBGMSG( str )         if( ssDebugMsg ) SS_DBGPRINT( str )
#define SS_DBGMSG1( str, a )     if( ssDebugMsg ) SS_DBGPRINT1( str, a )
#define SS_DBGMSG2( str, a, b )  if( ssDebugMsg ) SS_DBGPRINT2( str, a, b )

//
// Use SS_DBGLEVEL for general purpose debug messages gated by an
// arbitrary warning level.
//
#define SS_DBGLEVEL(n,str)         if (ssDebugLevel >= (n)) SS_DBGPRINT(str)
#define SS_DBGLEVEL1(n,str,a)      if (ssDebugLevel >= (n)) SS_DBGPRINT1(str,a)
#define SS_DBGLEVEL2(n,str,a,b)    if (ssDebugLevel >= (n)) SS_DBGPRINT2(str,a,b)

#define SS_ERROR(str)              SS_DBGLEVEL( SS_LEVEL_ERROR, str )
#define SS_ERROR1(str,a)           SS_DBGLEVEL1( SS_LEVEL_ERROR, str, a )
#define SS_ERROR2(str,a)           SS_DBGLEVEL2( SS_LEVEL_ERROR, str, a, b )

#define SS_DBGINFO(str)            SS_DBGLEVEL( SS_LEVEL_INFO, str )
#define SS_DBGINFO1(str,a)         SS_DBGLEVEL1( SS_LEVEL_INFO, str, a )
#define SS_DBGINFO2(str,a,b)       SS_DBGLEVEL2( SS_LEVEL_INFO, str, a, b )

#else

#define SS_DBGPRINT( str )
#define SS_DBGPRINT1( str, a )
#define SS_DBGPRINT2( str, a, b )

#define SS_WARNING(str)
#define SS_WARNING1(str,a)
#define SS_WARNING2(str,a,b)

#define SS_RIP(str)
#define SS_RIP1(str,a)
#define SS_RIP2(str,a,b)

#define SS_ASSERT(expr,str)         assert( expr )
#define SS_ASSERT1(expr,str,a)      assert( expr )
#define SS_ASSERT2(expr,str,a,b)    assert( expr )

#define SS_ALLOC_FAILURE(str)

#define SS_DBGMSG( str )
#define SS_DBGMSG1( str, a )
#define SS_DBGMSG2( str, a, b )

#define SS_DBGLEVEL(n,str)
#define SS_DBGLEVEL1(n,str,a)
#define SS_DBGLEVEL2(n,str,a,b)

#define SS_ERROR(str)
#define SS_ERROR1(str,a)
#define SS_ERROR2(str,a)

#define SS_DBGINFO(str)
#define SS_DBGINFO1(str,a)
#define SS_DBGINFO2(str,a)

#endif // DBG

#endif // __ssdebug_h__


SSDIB.C

/******************************Module*Header*******************************\
* Module Name: ssdib.c
*
* Operations on .bmp files
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sscommon.h"

#define BFT_BITMAP  0x4d42  // 'BM' -- indicates structure is BITMAPFILEHEADER

#define OFFSET_bfType       0
#define OFFSET_bfSize       2
#define OFFSET_bfReserved1  6
#define OFFSET_bfReserved2  8
#define OFFSET_bfOffBits    10
#define SIZEOF_BITMAPFILEHEADER 14

// Read a WORD-aligned DWORD.  Needed because BITMAPFILEHEADER has
// WORD-alignment.
#define READDWORD(pv)   ( (DWORD)((PWORD)(pv))[0]               \
                          | ((DWORD)((PWORD)(pv))[1] << 16) )   \

// Computes the number of BYTES needed to contain n number of bits.
#define BITS2BYTES(n)   ( ((n) + 7) >> 3 )

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibNumColors(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    : Determines the number of colors in the DIB by looking at   *
 *               the BitCount filed in the info block.                      *
 *                                                                          *
 *  RETURNS    : The number of colors in the DIB.                           *
 *                                                                          *
 * Stolen from SDK ShowDIB example.                                         *
 ****************************************************************************/

static WORD DibNumColors(VOID FAR * pv)
{
    WORD                bits;
    BITMAPINFOHEADER UNALIGNED *lpbi;
    BITMAPCOREHEADER UNALIGNED *lpbc;

    lpbi = ((LPBITMAPINFOHEADER)pv);
    lpbc = ((LPBITMAPCOREHEADER)pv);

    /*  With the BITMAPINFO format headers, the size of the palette
     *  is in biClrUsed, whereas in the BITMAPCORE - style headers, it
     *  is dependent on the bits per pixel ( = 2 raised to the power of
     *  bits/pixel).
     *
     *  Because of the way we use this call, BITMAPINFOHEADER may be out
     *  of alignment if it follows a BITMAPFILEHEADER.  So use the macro
     *  to safely access DWORD fields.
     */
    if (READDWORD(&lpbi->biSize) != sizeof(BITMAPCOREHEADER)){
        if (READDWORD(&lpbi->biClrUsed) != 0)
        {
            return (WORD) READDWORD(&lpbi->biClrUsed);
        }
        bits = lpbi->biBitCount;
    }
    else
        bits = lpbc->bcBitCount;

    switch (bits){
        case 1:
            return 2;
        case 4:
            return 16;
        case 8:
            return 256;
        default:
            /* A 24 bitcount DIB has no color table */
            return 0;
    }
}

/******************************Public*Routine******************************\
* ss_DIBImageLoad
*
* Hacked form of tk_DIBImageLoad(), for reading a .bmp file from a resource
*
* Loads a DIB file (specified as either an ANSI or Unicode filename,
* depending on the bUnicode flag) and converts it into a TK image format.
*
* The technique used is based on CreateDIBSection and SetDIBits.
* CreateDIBSection is used to create a DIB with a format easily converted
* into the TK image format (packed 24BPP RGB).  The only conversion
* required is swapping R and B in each RGB triplet (see history below)
* The resulting bitmap is selected into a memory DC.
*
* The DIB file is mapped into memory and SetDIBits called to initialize
* the memory DC bitmap.  It is during this step that GDI converts the
* arbitrary DIB file format to RGB format.
*
* Finally, the RGB data in the DIB section is read out and repacked
* as 24BPP 'BGR'.
*
* Returns:
*   BOOL.  If an error occurs, a diagnostic error
*   message is put into the error stream and tkQuit() is called,
*   terminating the app.
*
\**************************************************************************/

BOOL ss_DIBImageLoad(PVOID pv, TEXTURE *ptex)
{
    BOOL             fSuccess = FALSE;
    WORD             wNumColors;    // Number of colors in color table
    BITMAPFILEHEADER *pbmf;         // Ptr to file header
    BITMAPINFOHEADER UNALIGNED *pbmihFile;
    BITMAPCOREHEADER UNALIGNED *pbmchFile; // Ptr to file's core header (if it exists)
    PVOID            pvBits;    // Ptr to bitmap bits in file
    PBYTE            pjBitsRGB;     // Ptr to 24BPP RGB image in DIB section
    PBYTE            pjTKBits = (PBYTE) NULL;   // Ptr to final TK image bits
    PBYTE            pjSrc;         // Ptr to image file used for conversion
    PBYTE            pjDst;         // Ptr to TK image used for conversion

    // These need to be cleaned up when we exit:
    HDC        hdcMem = (HDC) NULL;                 // 24BPP mem DC
    HBITMAP    hbmRGB = (HBITMAP) NULL;             // 24BPP RGB bitmap
    BITMAPINFO *pbmiSource = (BITMAPINFO *) NULL;   // Ptr to source BITMAPINFO
    BITMAPINFO *pbmiRGB = (BITMAPINFO *) NULL;      // Ptr to file's BITMAPINFO

    int i, j;
    int padBytes;

// Otherwise, this may be a raw BITMAPINFOHEADER or BITMAPCOREHEADER
// followed immediately with the color table and the bitmap bits.

    pbmf = (BITMAPFILEHEADER *) pv;

    if ( pbmf->bfType == BFT_BITMAP )
    {
        pbmihFile = (BITMAPINFOHEADER *) ((PBYTE) pbmf + SIZEOF_BITMAPFILEHEADER);

    // BITMAPFILEHEADER is WORD aligned, so use safe macro to read DWORD
    // bfOffBits field.

        pvBits = (PVOID *) ((PBYTE) pbmf
                                + READDWORD((PBYTE) pbmf + OFFSET_bfOffBits));
    }
    else
    {
        pbmihFile = (BITMAPINFOHEADER *) pv;

    // Determination of where the bitmaps bits are needs to wait until we
    // know for sure whether we have a BITMAPINFOHEADER or a BITMAPCOREHEADER.
    }

// Determine the number of colors in the DIB palette.  This is non-zero
// only for 8BPP or less.

    wNumColors = DibNumColors(pbmihFile);

// Create a BITMAPINFO (with color table) for the DIB file.  Because the
// file may not have one (BITMAPCORE case) and potential alignment problems,
// we will create a new one in memory we allocate.
//
// We distinguish between BITMAPINFO and BITMAPCORE cases based upon
// BITMAPINFOHEADER.biSize.

    pbmiSource = (BITMAPINFO *)
        LocalAlloc(LMEM_FIXED, sizeof(BITMAPINFO)
                               + wNumColors * sizeof(RGBQUAD));
    if (!pbmiSource)
    {
        goto tkDIBLoadImage_cleanup;
    }

    // Note: need to use safe READDWORD macro because pbmihFile may
    // have only WORD alignment if it follows a BITMAPFILEHEADER.

    switch (READDWORD(&pbmihFile->biSize))
    {
    case sizeof(BITMAPINFOHEADER):

    // Convert WORD-aligned BITMAPINFOHEADER to aligned BITMAPINFO.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = READDWORD(&pbmihFile->biWidth);
        pbmiSource->bmiHeader.biHeight        = READDWORD(&pbmihFile->biHeight);
        pbmiSource->bmiHeader.biPlanes        = pbmihFile->biPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmihFile->biBitCount;
        pbmiSource->bmiHeader.biCompression   =
                                        READDWORD(&pbmihFile->biCompression);
        pbmiSource->bmiHeader.biSizeImage     =
                                        READDWORD(&pbmihFile->biSizeImage);
        pbmiSource->bmiHeader.biXPelsPerMeter =
                                        READDWORD(&pbmihFile->biXPelsPerMeter);
        pbmiSource->bmiHeader.biYPelsPerMeter =
                                        READDWORD(&pbmihFile->biYPelsPerMeter);
        pbmiSource->bmiHeader.biClrUsed       =
                                        READDWORD(&pbmihFile->biClrUsed);
        pbmiSource->bmiHeader.biClrImportant  =
                                        READDWORD(&pbmihFile->biClrImportant);

    // Copy color table.  It immediately follows the BITMAPINFOHEADER.

        memcpy((PVOID) &pbmiSource->bmiColors[0], (PVOID) (pbmihFile + 1),
               wNumColors * sizeof(RGBQUAD));

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBits)
            pvBits = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBQUAD));
        break;

    case sizeof(BITMAPCOREHEADER):
        pbmchFile = (BITMAPCOREHEADER *) pbmihFile;

    // Convert BITMAPCOREHEADER to BITMAPINFOHEADER.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = (DWORD) pbmchFile->bcWidth;
        pbmiSource->bmiHeader.biHeight        = (DWORD) pbmchFile->bcHeight;
        pbmiSource->bmiHeader.biPlanes        = pbmchFile->bcPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmchFile->bcBitCount;
        pbmiSource->bmiHeader.biCompression   = BI_RGB;
        pbmiSource->bmiHeader.biSizeImage     = 0;
        pbmiSource->bmiHeader.biXPelsPerMeter = 0;
        pbmiSource->bmiHeader.biYPelsPerMeter = 0;
        pbmiSource->bmiHeader.biClrUsed       = wNumColors;
        pbmiSource->bmiHeader.biClrImportant  = wNumColors;

    // Convert RGBTRIPLE color table into RGBQUAD color table.

        {
            RGBQUAD *rgb4 = pbmiSource->bmiColors;
            RGBTRIPLE *rgb3 = (RGBTRIPLE *) (pbmchFile + 1);

            for (i = 0; i < wNumColors; i++)
            {
                rgb4->rgbRed   = rgb3->rgbtRed  ;
                rgb4->rgbGreen = rgb3->rgbtGreen;
                rgb4->rgbBlue  = rgb3->rgbtBlue ;
                rgb4->rgbReserved = 0;

                rgb4++;
                rgb3++;
            }
        }

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBits)
            pvBits = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBTRIPLE));
        break;

    default:
        goto tkDIBLoadImage_cleanup;
    }

// Fill in default values (for fields that can have defaults).

    if (pbmiSource->bmiHeader.biSizeImage == 0)
        pbmiSource->bmiHeader.biSizeImage =
            BITS2BYTES( (DWORD) pbmiSource->bmiHeader.biWidth *
                                pbmiSource->bmiHeader.biBitCount ) *
                                pbmiSource->bmiHeader.biHeight;
    if (pbmiSource->bmiHeader.biClrUsed == 0)
        pbmiSource->bmiHeader.biClrUsed = wNumColors;

// Create memory DC.

    hdcMem = CreateCompatibleDC(NULL);
    if (!hdcMem) {
        goto tkDIBLoadImage_cleanup;
    }

// Create a 24BPP RGB DIB section and select it into the memory DC.

    pbmiRGB = (BITMAPINFO *)
              LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, sizeof(BITMAPINFO) );
    if (!pbmiRGB)
    {
        goto tkDIBLoadImage_cleanup;
    }

    pbmiRGB->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
    pbmiRGB->bmiHeader.biWidth         = pbmiSource->bmiHeader.biWidth;
    pbmiRGB->bmiHeader.biHeight        = pbmiSource->bmiHeader.biHeight;
    pbmiRGB->bmiHeader.biPlanes        = 1;
    pbmiRGB->bmiHeader.biBitCount      = 24;
    pbmiRGB->bmiHeader.biCompression   = BI_RGB;
    pbmiRGB->bmiHeader.biSizeImage     = pbmiRGB->bmiHeader.biWidth
                                         * abs(pbmiRGB->bmiHeader.biHeight) * 3;

    hbmRGB = CreateDIBSection(hdcMem, pbmiRGB, DIB_RGB_COLORS,
                              (PVOID *) &pjBitsRGB, NULL, 0);

    if (!hbmRGB)
    {
        goto tkDIBLoadImage_cleanup;
    }

    if (!SelectObject(hdcMem, hbmRGB))
    {
        goto tkDIBLoadImage_cleanup;
    }

// Slam the DIB file image into the memory DC.  GDI will do the work of
// translating whatever format the DIB file has into RGB format.

    if (!SetDIBits(hdcMem, hbmRGB, 0, pbmiSource->bmiHeader.biHeight,
                   pvBits, pbmiSource, DIB_RGB_COLORS))
    {
        goto tkDIBLoadImage_cleanup;
    }
    GdiFlush();     // make sure that SetDIBits executes

// Convert to TK image format (packed RGB format).
// Allocate with malloc to be consistent with tkRGBImageLoad (i.e., app
// can deallocate with free()).

    pjTKBits = (PBYTE) malloc(pbmiRGB->bmiHeader.biSizeImage);
    if (!pjTKBits)
    {
        goto tkDIBLoadImage_cleanup;
    }

    pjSrc = pjBitsRGB;
    pjDst = pjTKBits;
    // src lines end on LONG boundary - so need to skip over any padding bytes
    padBytes = pbmiSource->bmiHeader.biWidth % sizeof(LONG);
    for (i = 0; i < pbmiSource->bmiHeader.biHeight; i++)
    {
        for (j = 0; j < pbmiSource->bmiHeader.biWidth; j++)
        {
            // swap R and B
            *pjDst++ = pjSrc[2];
            *pjDst++ = pjSrc[1];
            *pjDst++ = pjSrc[0];
            pjSrc += 3;
        }
        pjSrc += padBytes;
    }

// Initialize the texture structure

    // If we get to here, we have suceeded!
    ptex->width = pbmiSource->bmiHeader.biWidth;
    ptex->height = pbmiSource->bmiHeader.biHeight;
    ptex->format = GL_RGB;
    ptex->components = 3;
    ptex->data = pjTKBits;
    ptex->pal_size = 0;
    ptex->pal = NULL;

    fSuccess = TRUE;

// Cleanup objects.

tkDIBLoadImage_cleanup:
    {
        if (hdcMem)
            DeleteDC(hdcMem);

        if (hbmRGB)
            DeleteObject(hbmRGB);

        if (pbmiRGB)
            LocalFree(pbmiRGB);

        if (pbmiSource)
            LocalFree(pbmiSource);
    }

// Check for error.

    if (!fSuccess)
    {
        if (pjTKBits)
            free(pjTKBits);
    }

    return fSuccess;
}

/******************************Public*Routine******************************\
*
* bVerifyDIB
*
* Stripped down version of tkDIBImageLoadAW that verifies that a bitmap
* file is valid and, if so, returns the bitmap dimensions.
*
* Returns:
*   TRUE if valid bitmap file; otherwise, FALSE.
*
\**************************************************************************/

BOOL
bVerifyDIB(LPTSTR pszFileName, ISIZE *pSize )
{
    BOOL bRet = FALSE;
    BITMAPFILEHEADER *pbmf;         // Ptr to file header
    BITMAPINFOHEADER *pbmihFile;    // Ptr to file's info header (if it exists)
    BITMAPCOREHEADER *pbmchFile;    // Ptr to file's core header (if it exists)

    // These need to be cleaned up when we exit:
    HANDLE     hFile = INVALID_HANDLE_VALUE;        // File handle
    HANDLE     hMap = (HANDLE) NULL;                // Mapping object handle
    PVOID      pvFile = (PVOID) NULL;               // Ptr to mapped file

// Map the DIB file into memory.

    hFile = CreateFile((LPTSTR) pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
    if (hFile == INVALID_HANDLE_VALUE)
        goto bVerifyDIB_cleanup;

    hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMap)
        goto bVerifyDIB_cleanup;

    pvFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
    if (!pvFile)
        goto bVerifyDIB_cleanup;

// Check the file header.  If the BFT_BITMAP magic number is there,
// then the file format is a BITMAPFILEHEADER followed immediately
// by either a BITMAPINFOHEADER or a BITMAPCOREHEADER.  The bitmap
// bits, in this case, are located at the offset bfOffBits from the
// BITMAPFILEHEADER.
//
// Otherwise, this may be a raw BITMAPINFOHEADER or BITMAPCOREHEADER
// followed immediately with the color table and the bitmap bits.

    pbmf = (BITMAPFILEHEADER *) pvFile;

    if ( pbmf->bfType == BFT_BITMAP )
        pbmihFile = (BITMAPINFOHEADER *) ((PBYTE) pbmf + SIZEOF_BITMAPFILEHEADER);
    else
        pbmihFile = (BITMAPINFOHEADER *) pvFile;

// Get the width and height from whatever header we have.
//
// We distinguish between BITMAPINFO and BITMAPCORE cases based upon
// BITMAPINFOHEADER.biSize.

    // Note: need to use safe READDWORD macro because pbmihFile may
    // have only WORD alignment if it follows a BITMAPFILEHEADER.

    switch (READDWORD(&pbmihFile->biSize))
    {
    case sizeof(BITMAPINFOHEADER):

        if( pSize != NULL ) {
            pSize->width  = READDWORD(&pbmihFile->biWidth);
            pSize->height = READDWORD(&pbmihFile->biHeight);
        }
        bRet = TRUE;

        break;

    case sizeof(BITMAPCOREHEADER):
        pbmchFile = (BITMAPCOREHEADER *) pbmihFile;

    // Convert BITMAPCOREHEADER to BITMAPINFOHEADER.

        if( pSize != NULL ) {
            pSize->width  = (DWORD) pbmchFile->bcWidth;
            pSize->height = (DWORD) pbmchFile->bcHeight;
        }
        bRet = TRUE;

        break;

    default:
        break;
    }

bVerifyDIB_cleanup:

    if (pvFile)
        UnmapViewOfFile(pvFile);

    if (hMap)
        CloseHandle(hMap);

    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return bRet;
}


SSIMAGE.C

/******************************Module*Header*******************************\
* Module Name: ssimage.c
*
* Operations on .rgb files
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sscommon.h"

#define IMAGIC      0x01da
#define IMAGIC_SWAP 0xda01

#define SWAP_SHORT_BYTES(x) ((((x) & 0xff) << 8) | (((x) & 0xff00) >> 8))
#define SWAP_LONG_BYTES(x) (((((x) & 0xff) << 24) | (((x) & 0xff00) << 8)) | \
                            ((((x) & 0xff0000) >> 8) | (((x) & 0xff000000) >> 24)))

typedef struct _rawImageRec {
    unsigned short imagic;
    unsigned short type;
    unsigned short dim;
    unsigned short sizeX, sizeY, sizeZ;
    unsigned long min, max;
    unsigned long wasteBytes;
    char name[80];
    unsigned long colorMap;
    HANDLE file;
    unsigned char *tmp, *tmpR, *tmpG, *tmpB;
    unsigned long rleEnd;
    unsigned long *rowStart;
    long *rowSize;
    // !!! Hack to stick in a pointer to the resource data - shouldn't be
    // a problem, since rgb files always have 512 byte header
    unsigned char *data;
} rawImageRec;

static void RawImageClose(rawImageRec *raw);

/**************************************************************************\
*
* Hacked form of tk_RGBImageLoad(), for reading a .rgb file from a resource
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
static rawImageRec *RawImageOpen( PVOID pv )
{
    rawImageRec *raw;
    unsigned long *rowStart, *rowSize, ulTmp;
    int x;

    raw = (rawImageRec *)malloc(sizeof(rawImageRec));
    if (raw == NULL) {
        return 0;
    }

    // Make a copy of the resource header, since we may be doing some byte
    // swapping, and resources are read-only
    *raw = *((rawImageRec *) pv);

    if (raw->imagic == IMAGIC_SWAP) {
        raw->type = SWAP_SHORT_BYTES(raw->type);
        raw->dim = SWAP_SHORT_BYTES(raw->dim);
        raw->sizeX = SWAP_SHORT_BYTES(raw->sizeX);
        raw->sizeY = SWAP_SHORT_BYTES(raw->sizeY);
        raw->sizeZ = SWAP_SHORT_BYTES(raw->sizeZ);
    }

    raw->tmp = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpR = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpG = (unsigned char *)malloc(raw->sizeX*256);
    raw->tmpB = (unsigned char *)malloc(raw->sizeX*256);
    if (raw->tmp == NULL || raw->tmpR == NULL || raw->tmpG == NULL ||
        raw->tmpB == NULL) {
        RawImageClose(raw);
        return 0;
    }

    if ((raw->type & 0xFF00) == 0x0100) {
        x = raw->sizeY * raw->sizeZ * sizeof(long);
        raw->rowStart = (unsigned long *)malloc(x);
        raw->rowSize = (long *)malloc(x);
        if (raw->rowStart == NULL || raw->rowSize == NULL) {
            RawImageClose(raw);
            return 0;
        }
        raw->rleEnd = 512 + (2 * x);

        raw->data = ((unsigned char *) pv);
        RtlCopyMemory( raw->rowStart, raw->data + 512, x );
        RtlCopyMemory( raw->rowSize, raw->data + 512 + x, x );

        if (raw->imagic == IMAGIC_SWAP) {
            x /= sizeof(long);
            rowStart = raw->rowStart;
            rowSize = (unsigned long *) raw->rowSize;
            while (x--) {
                ulTmp = *rowStart;
                *rowStart++ = SWAP_LONG_BYTES(ulTmp);
                ulTmp = *rowSize;
                *rowSize++ = SWAP_LONG_BYTES(ulTmp);
            }
        }
    }
    return raw;
}

static void RawImageClose(rawImageRec *raw)
{
    if( !raw )
        return;
    if( raw->tmp ) free(raw->tmp);
    if( raw->tmpR ) free(raw->tmpR);
    if( raw->tmpG ) free(raw->tmpG);
    if( raw->tmpB ) free(raw->tmpB);
    free(raw);
}

static void RawImageGetRow(rawImageRec *raw, unsigned char *buf, int y, int z)
{
    unsigned char *iPtr, *oPtr, pixel;
    int count;

    if ((raw->type & 0xFF00) == 0x0100) {
        RtlCopyMemory(raw->tmp, raw->data + raw->rowStart[y+z*raw->sizeY],
                 (unsigned int)raw->rowSize[y+z*raw->sizeY] );
        iPtr = raw->tmp;
        oPtr = buf;
        while (1) {
            pixel = *iPtr++;
            count = (int)(pixel & 0x7F);
            if (!count) {
                return;
            }
            if (pixel & 0x80) {
                while (count--) {
                    *oPtr++ = *iPtr++;
                }
            } else {
                pixel = *iPtr++;
                while (count--) {
                    *oPtr++ = pixel;
                }
            }
        }
    } else {
        iPtr = raw->data + 512 + (y*raw->sizeX)+(z*raw->sizeX*raw->sizeY);
        RtlCopyMemory( buf, iPtr, raw->sizeX );
    }
}

static void RawImageGetData(rawImageRec *raw, TEXTURE *ptex)
{
    unsigned char *ptr;
    int i, j;

    ptex->data = (unsigned char *)malloc((raw->sizeX+1)*(raw->sizeY+1)*4);
    if (ptex->data == NULL) {
        return;
    }

    ptr = ptex->data;
    for (i = 0; i < raw->sizeY; i++) {
        RawImageGetRow(raw, raw->tmpR, i, 0);
        RawImageGetRow(raw, raw->tmpG, i, 1);
        RawImageGetRow(raw, raw->tmpB, i, 2);
        for (j = 0; j < raw->sizeX; j++) {
            *ptr++ = *(raw->tmpR + j);
            *ptr++ = *(raw->tmpG + j);
            *ptr++ = *(raw->tmpB + j);
        }
    }
}

BOOL ss_RGBImageLoad( PVOID pv, TEXTURE *ptex )
{
    rawImageRec *raw;

    if( !(raw = RawImageOpen( pv )) )
        return FALSE;

    ptex->width = raw->sizeX;
    ptex->height = raw->sizeY;
    ptex->format = GL_RGB;
    ptex->components = 3;
    ptex->pal_size = 0;
    ptex->pal = NULL;
    RawImageGetData(raw, ptex);
    RawImageClose(raw);
    return TRUE;
}

/******************************Public*Routine******************************\
*
* bVerifyRGB
*
* Stripped down version of tkRGBImageLoadAW that verifies that an rgb
* file is valid and, if so, returns the bitmap dimensions.
*
* Returns:
*   TRUE if valid rgb file; otherwise, FALSE.
*
\**************************************************************************/

BOOL
bVerifyRGB(LPTSTR pszFileName, ISIZE *pSize )
{
    rawImageRec *raw;
    DWORD dwBytesRead;
    BOOL bRet = FALSE;

    raw = (rawImageRec *)
          LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, sizeof(rawImageRec) );

    if (raw == NULL) {
        goto bVerifyRGB_cleanup;
    }

    raw->file = CreateFile((LPTSTR) pszFileName, GENERIC_READ, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, 0, 0);

    if (raw->file == INVALID_HANDLE_VALUE) {
        goto bVerifyRGB_cleanup;
    }

    ReadFile(raw->file, (LPVOID) raw, 12, &dwBytesRead, (LPOVERLAPPED) NULL);

    if( raw->imagic == IMAGIC_SWAP ) {
        raw->sizeX = SWAP_SHORT_BYTES(raw->sizeX);
        raw->sizeY = SWAP_SHORT_BYTES(raw->sizeY);
        bRet = TRUE;
    } else if( raw->imagic == IMAGIC)
        bRet = TRUE;

bVerifyRGB_cleanup:

    if( bRet && pSize ) {
        pSize->width = raw->sizeX;
        pSize->height = raw->sizeY;
    }

    if( raw->file )
        CloseHandle( raw->file );

    if( raw )
        LocalFree( raw );

    return bRet;
}


SSINIT.CXX

/******************************Module*Header*******************************\
* Module Name: ssinit.cxx
*
* Main code for common screen saver functions.
*
* Copyright (c) 1994 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include "ssintrnl.hxx"

void     *gDataPtr = NULL; // data ptr used with callbacks

// function protos
void (*gReshapeFunc)(int, int, void *)      = NULL;
void (*gRepaintFunc)(LPRECT, void *)      = NULL;
void (*gUpdateFunc)( void *)           = NULL;
void (*gInitFunc)( void *)             = NULL;
void (*gFinishFunc)( void *)           = NULL;
void (*gFloaterBounceFunc)( void *)      = NULL;

// Debug stuff
#if DBG
#ifdef SS_DEBUG
long ssDebugMsg = 1;
long ssDebugLevel = SS_LEVEL_INFO;
#else
long ssDebugMsg = 0;
long ssDebugLevel = SS_LEVEL_ERROR;
#endif
#endif

// Callback functions:

/******************************Public*Routine******************************\
* ss_InitFunc
*
\**************************************************************************/

void
ss_InitFunc(SSINITPROC Func)
{
    gInitFunc = Func;
}

/******************************Public*Routine******************************\
* ss_ReshapeFunc
*
\**************************************************************************/

void
ss_ReshapeFunc(SSRESHAPEPROC Func)
{
    gReshapeFunc = Func;
    if( gpss->psswGL )
        gpss->psswGL->ReshapeFunc = gReshapeFunc;
}

/******************************Public*Routine******************************\
* ss_RepaintFunc
*
\**************************************************************************/

void
ss_RepaintFunc(SSREPAINTPROC Func)
{
    gRepaintFunc = Func;
    if( gpss->psswGL )
        gpss->psswGL->RepaintFunc = gRepaintFunc;
}

/******************************Public*Routine******************************\
* ss_UpdateFunc
*
\**************************************************************************/

void
ss_UpdateFunc(SSUPDATEPROC Func)
{
    gUpdateFunc = Func;
    if( gpss->psswGL )
        gpss->psswGL->UpdateFunc = gUpdateFunc;
}

/******************************Public*Routine******************************\
* ss_FinishFunc
*
\**************************************************************************/

void
ss_FinishFunc(SSFINISHPROC Func)
{
    gFinishFunc = Func;
    if( gpss->psswGL )
        gpss->psswGL->FinishFunc = gFinishFunc;
}

/******************************Public*Routine******************************\
* ss_FloaterBounceFunc
*
\**************************************************************************/

void
ss_FloaterBounceFunc(SSFLOATERBOUNCEPROC Func)
{
    gFloaterBounceFunc = Func;
    if( gpss->psswGL )
        gpss->psswGL->FloaterBounceFunc = gFloaterBounceFunc;
}

/******************************Public*Routine******************************\
* ss_DataPtr
*
* Sets data ptr to be sent with callbacks
*
\**************************************************************************/

void
ss_DataPtr( void *data )
{
    gDataPtr = data;
    if( gpss->psswGL )
        gpss->psswGL->DataPtr = gDataPtr;
}

/******************************Public*Routine******************************\
* RandomWindowPos
*
* Sets a new random window position and direction.
*
\**************************************************************************/

void
ss_RandomWindowPos()
{
    if( gpss->psswGL )
        gpss->psswGL->RandomWindowPos();
}

/******************************Public*Routine******************************\
* ss_SetWindowAspectRatio
*
* Resize the window to conform to the supplied aspect ratio.  We do this by
* maintaining the existing width, and adjusting the height.
*
* Window resize seems to be executed synchronously, so gl should be able to
* immediately validate its buffer dimensions (we count on it).
*
* Returns TRUE if new height is different from last, else FALSE.
\**************************************************************************/

BOOL
ss_SetWindowAspectRatio( FLOAT aspect )
{
    if( gpss->psswGL )
        return gpss->psswGL->SetAspectRatio( aspect );
    return FALSE;
}

/******************************Public*Routine******************************\
* ss_GetScreenSize
*
* Returns size of screen saver window
*
\**************************************************************************/

void
ss_GetScreenSize( ISIZE *size )
{
    if( gpss->psswMain )
        *size = gpss->psswMain->size;
}

/******************************Public*Routine******************************\
* ss_GetHWND
*
* Return HWND of the main window
\**************************************************************************/

HWND
ss_GetHWND()
{
    if( gpss->psswMain )
        return gpss->psswMain->hwnd;
    return NULL;
}

/******************************Public*Routine******************************\
* ss_GetGLHWND
*
* Return HWND of the GL window
\**************************************************************************/

HWND
ss_GetGLHWND()
{
    if( gpss->psswGL )
        return gpss->psswGL->hwnd;
    return NULL;
}

/******************************Public*Routine******************************\
* ss_GetMainPSSW
*
* Return PSSW of top level window
\**************************************************************************/

PSSW
ss_GetMainPSSW()
{
    if( gpss->psswMain )
        return gpss->psswMain;
    return NULL;
}


SSUTIL.CXX

/******************************Module*Header*******************************\
* Module Name: ssutil.cxx
*
* Screen-saver utility functions
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include "ssintrnl.hxx"

#include "ssutil.hxx"

static OSVERSIONINFO gosvi = {0};
static BOOL gbGLv1_1 = FALSE;  // GL version 1.1 boolean

/******************************Public*Routine******************************\
* SSU_ChoosePixelFormat
*
* Local implementation of ChoosePixelFormat
*
* Choose pixel format based on flags.
* This allows us a little a more control than just calling ChoosePixelFormat
\**************************************************************************/

static int
SSU_ChoosePixelFormat( HDC hdc, int flags )
{
    int MaxPFDs;
    int iBest = 0;
    PIXELFORMATDESCRIPTOR pfd;

    // Always choose native pixel depth
    int cColorBits =
                GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES);

    BOOL bDoubleBuf = flags & SS_DOUBLEBUF_BIT;

    int cDepthBits = 0;
    if( SS_HAS_DEPTH16(flags) )
        cDepthBits = 16;
    else if( SS_HAS_DEPTH32(flags) )
        cDepthBits = 32;

    int i = 1;
    do
    {
        MaxPFDs = DescribePixelFormat(hdc, i, sizeof(pfd), &pfd);
        if ( MaxPFDs <= 0 )
            return 0;

        if( ! (pfd.dwFlags & PFD_SUPPORT_OPENGL) )
            continue;

        if( flags & SS_BITMAP_BIT ) {
            // need bitmap pixel format
            if( ! (pfd.dwFlags & PFD_DRAW_TO_BITMAP) )
                continue;
        } else {
            // need window pixel format
            if( ! (pfd.dwFlags & PFD_DRAW_TO_WINDOW) )
                continue;
            // a window can be double buffered...
            if( ( bDoubleBuf && !(pfd.dwFlags & PFD_DOUBLEBUFFER) ) ||
                ( !bDoubleBuf && (pfd.dwFlags & PFD_DOUBLEBUFFER) ) )
                continue;
        }

        if ( pfd.iPixelType != PFD_TYPE_RGBA )
            continue;
        if( pfd.cColorBits != cColorBits )
            continue;

        if( (flags & SS_GENERIC_UNACCELERATED_BIT) &&
            ((pfd.dwFlags & (PFD_GENERIC_FORMAT|PFD_GENERIC_ACCELERATED))
    != PFD_GENERIC_FORMAT) )
            continue;

        if( (flags & SS_NO_SYSTEM_PALETTE_BIT) &&
            (pfd.dwFlags & PFD_NEED_SYSTEM_PALETTE) )
            continue;

        if( cDepthBits ) {
            if( pfd.cDepthBits < cDepthBits )
                continue;
        } else {
            // No depth buffer required, but use it if nothing better
            if( pfd.cDepthBits ) {
                iBest = i;
                continue;
            }
        }

        // We have found something useful
        return i;

    } while (++i <= MaxPFDs);

    if( iBest )
        // not an exact match, but good enough
        return iBest;

    // If we reach here, we have failed to find a suitable pixel format.
    // See if the system can find us one.

    memset( &pfd, 0, sizeof( PIXELFORMATDESCRIPTOR ) );
    pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR );
    pfd.cColorBits = cColorBits;
    pfd.cDepthBits = cDepthBits;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.dwFlags = PFD_SUPPORT_OPENGL;
    if( bDoubleBuf )
        pfd.dwFlags |= PFD_DOUBLEBUFFER;
    if( flags & SS_BITMAP_BIT )
        pfd.dwFlags |= PFD_DRAW_TO_BITMAP;
    else
        pfd.dwFlags |= PFD_DRAW_TO_WINDOW;

    if( (flags & SS_GENERIC_UNACCELERATED_BIT) ||
        (flags & SS_NO_SYSTEM_PALETTE_BIT) )
        // If either of these flags are set, we should be safe specifying a
        // 'slow' pixel format that supports bitmap drawing
        pfd.dwFlags |= PFD_DRAW_TO_BITMAP;

    SS_WARNING( "SSU_ChoosePixelFormat failed, calling ChoosePIxelFormat\n" );

    return ChoosePixelFormat( hdc, &pfd );
}

/******************************Public*Routine******************************\
* SSU_SetupPixelFormat
*
* Choose pixel format according to supplied flags.  If ppfd is non-NULL,
* call DescribePixelFormat with it.
*
\**************************************************************************/

BOOL
SSU_SetupPixelFormat(HDC hdc, int flags, PIXELFORMATDESCRIPTOR *ppfd )
{
    int pixelFormat;
    int nTryAgain = 4;

    do{
        if( (pixelFormat = SSU_ChoosePixelFormat(hdc, flags)) &&
            SetPixelFormat(hdc, pixelFormat, NULL) ) {
            SS_DBGLEVEL1( SS_LEVEL_INFO,
               "SSU_SetupPixelFormat: Setting pixel format %d\n", pixelFormat );
            if( ppfd )
                DescribePixelFormat(hdc, pixelFormat,
                                sizeof(PIXELFORMATDESCRIPTOR), ppfd);
            return TRUE; // Success
        }
        // Failed to set pixel format.  Try again after waiting a bit (win95
        // bug with full screen dos box)
        Sleep( 1000 ); // Wait a second between attempts
    } while( nTryAgain-- );

    return FALSE;
}

/******************************Public*Routine******************************\
* SSU_bNeedPalette
*
\**************************************************************************/

BOOL
SSU_bNeedPalette( PIXELFORMATDESCRIPTOR *ppfd )
{
    if (ppfd->dwFlags & PFD_NEED_PALETTE)
        return TRUE;
    else
        return FALSE;
}

/******************************Public*Routine******************************\
* SSU_PixelFormatDescriptorFromDc
*
\**************************************************************************/

int
SSU_PixelFormatDescriptorFromDc( HDC hdc, PIXELFORMATDESCRIPTOR *Pfd )
{
    int PfdIndex;

    if ( 0 < (PfdIndex = GetPixelFormat( hdc )) )
    {
        if ( 0 < DescribePixelFormat( hdc, PfdIndex, sizeof(*Pfd), Pfd ) )
        {
            return(PfdIndex);
        }
    }
    return 0;
}

/******************************Public*Routine******************************\
* ss_ChangeDisplaySettings
*
* Try changing display settings.
* If bitDepth is 0, use current bit depth
*
\**************************************************************************/

BOOL
ss_ChangeDisplaySettings( int width, int height, int bitDepth )
{
    int change;
    DEVMODE dm = {0};

dm.dmSize       = sizeof(dm);
    dm.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT;
dm.dmPelsWidth  = width;
dm.dmPelsHeight = height;

    if( bitDepth != 0 ) {
    dm.dmFields |= DM_BITSPERPEL;
    dm.dmBitsPerPel = bitDepth;
    }

//    change = ChangeDisplaySettings(&dm, CDS_TEST);
    change = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);

    if( change == DISP_CHANGE_SUCCESSFUL )
        return TRUE;
    else
        return FALSE;
}

/******************************Public*Routine******************************\
* ss_QueryDisplaySettings
*
* Find out what diplay resolutions are available.
*
\**************************************************************************/

void
ss_QueryDisplaySettings( void )
{
    int i = 0;
    DEVMODE devMode = {0};

    while( EnumDisplaySettings( NULL, i, &devMode ) ) {
        i++;
    }
}

/******************************Public*Routine******************************\
* ss_QueryGLVersion
*
* Find out what GL version is being loaded.  If it's 1.1, set various
* global capabilities.
*
\**************************************************************************/

void
ss_QueryGLVersion( void )
{
    // Get GL version

    if( strstr( (char *) glGetString(GL_VERSION), "1.1") ) {
        gbGLv1_1 = TRUE;
        gbTextureObjects = TRUE;
    } else {
        gbGLv1_1 = FALSE;
        gbTextureObjects = FALSE;
    }
#ifdef SS_DEBUG
    if( !gbTextureObjects )
        DbgPrint( "ss_QueryGLVersion: Texture Objects disabled\n" );
#endif
}

/******************************Public*Routine******************************\
* ss_fOnGL11
*
* True if running on OpenGL v.1.1x
*
\**************************************************************************/

BOOL
ss_fOnGL11( void )
{
    return gbGLv1_1;
}

/******************************Public*Routine******************************\
* ss_QueryOSVersion
*
* Query the OS version
*
\**************************************************************************/

void
ss_QueryOSVersion( void )
{
    gosvi.dwOSVersionInfoSize = sizeof(gosvi);
    GetVersionEx(&gosvi);
}

/******************************Public*Routine******************************\
* ss_fOnNT35
*
* True if running on NT version 3.51 or less
*
\**************************************************************************/

BOOL
ss_fOnNT35( void )
{
    static fOnNT35;
    static bInited = FALSE;

    if( !bInited ) {
        if( !gosvi.dwOSVersionInfoSize )
            ss_QueryOSVersion();
        fOnNT35 =
        (
            (gosvi.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
            (gosvi.dwMajorVersion == 3 && gosvi.dwMinorVersion <= 51)
        );
        bInited = TRUE;
    }
    return fOnNT35;
}

/******************************Public*Routine******************************\
* ss_fOnWin95
*
* True if running on Windows 95
*
\**************************************************************************/

BOOL
ss_fOnWin95( void )
{
    static fOnWin95;
    static bInited = FALSE;

    if( !bInited ) {
        if( !gosvi.dwOSVersionInfoSize )
            ss_QueryOSVersion();
        fOnWin95 = ( gosvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS );
        bInited = TRUE;
    }
    return fOnWin95;
}

/******************************Public*Routine******************************\
* ss_fPreviewMode
*
* True if running in Display setting's child preview window
*
\**************************************************************************/

BOOL
ss_fPreviewMode( void )
{
    return gpss->type == SS_TYPE_PREVIEW;
}

/******************************Public*Routine******************************\
* ss_fFullScreenMode
*
* True if running full screen (/s option)
*
\**************************************************************************/

BOOL
ss_fFullScreenMode( void )
{
    return gpss->type == SS_TYPE_FULLSCREEN;
}

BOOL
ss_fConfigMode( void )
{
    return gpss->type == SS_TYPE_CONFIG;
}

BOOL
ss_fWindowMode( void )
{
    return gpss->type == SS_TYPE_NORMAL;
}

/******************************Public*Routine******************************\
* ss_RedrawDesktop
*
* Causes the entire desktop to be redrawn
*
\**************************************************************************/

BOOL
ss_RedrawDesktop( void )
{
    return RedrawWindow( NULL, NULL, NULL, RDW_ERASE | RDW_INVALIDATE |
                            RDW_ERASENOW | RDW_UPDATENOW | RDW_ALLCHILDREN );
}


SSWINDOW.CXX

/******************************Module*Header*******************************\
* Module Name: sswindow.cxx
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include "scrnsave.h"

#include "glscrnsv.h"
#include "ssintrnl.hxx"
#include "sswindow.hxx"
#include "ssutil.hxx"

static void (__stdcall *glAddSwapHintRect)(GLint, GLint, GLint, GLint);

// externs from ssinit.cxx
extern void *gDataPtr;
extern void (*gReshapeFunc)(int, int, void *);
extern void (*gRepaintFunc)( LPRECT, void *);
extern void (*gUpdateFunc)( void *);
extern void (*gInitFunc)( void *);
extern void (*gFinishFunc)( void *);
extern void (*gFloaterBounceFunc)( void *);

// forwards
static void GetWindowSize( HWND hwnd, ISIZE *pSize );
static void ss_QueryAddSwapHintRect();
static void DrawGdiDeltaRect( HDC hdc, HBRUSH hbr, RECT *pRect1, RECT *pRect2 );
static void DrawGLDeltaRect( GLRECT *pRect1, GLRECT *pRect2 );

/**************************************************************************\
* SSW constructor
*
\**************************************************************************/

SSW::SSW( PSSW psswParentArg, ISIZE *pSize, IPOINT2D *pPos, BOOL bMotion,
          SSCHILDSIZEPROC ChildSizeFuncArg )
{
    // Basic initialization

    Reset();

    // Initialization based on constructor parameters

    psswParent = psswParentArg;
    ChildSizeFunc = ChildSizeFuncArg;

    if( pSize )
        size = *pSize;
    if( pPos )
        pos = *pPos;
    else
        pos.x = pos.y = 0;

    if( bMotion && psswParent ) {
        // Allocate motion structure
        pMotion = (MOTION *)
                LocalAlloc( LMEM_ZEROINIT | LMEM_FIXED, sizeof(MOTION) );
        // If pMotion is NULL, then motion is disabled
    }

    // Call back to the client screen saver to determine size and motion
    // characteristics of child based on its parent size.
    GetChildInfo(); // this can set pos and size

    if( pMotion ) {
        if( pPos == NULL ) {
            // Set a random window pos
            pos.x = ss_iRand2( 0, (psswParent->size.width - size.width) );
            pos.y = ss_iRand2( 0, (psswParent->size.height - size.height) );

            // Set the motion parameters
            ResetMotion();
        }
        // Have to make sure parent has an hdc so it can draw background when
        // this child moves
        if( !psswParent->hdc )
            psswParent->hdc = GetDC( psswParent->hwnd );
    }

    if( psswParent ) {
        // Need to add this pssw to its parent's list
        psswParent->AddChild( this );
        // Default is to be subWindow of parent
        psswParent->iSubWindow++;  // increment reference count
    }
}

/**************************************************************************\
* SSW constructor
*
* Used when wrapping an SSW around an already existing window
* (as when drawing GL on dialog buttons)
\**************************************************************************/

SSW::SSW( PSSW psswParentArg, HWND hwndArg )
{
    Reset();

    psswParent = psswParentArg;
    if( psswParent )
        // Need to add this pssw to its parent's list
        psswParent->AddChild( this );

    hwnd = hwndArg;
    if( !hwnd ) {
        SS_ERROR( "SSW::SSW : NULL hwnd\n" );
        return;
    }

    bOwnWindow = FALSE;
    // Get the window size
    GetWindowSize( hwnd, &size );

    gpss->sswTable.Register( hwnd, this );
}

/**************************************************************************\
* Reset
*
* Reset parameters to default init state
\**************************************************************************/

void
SSW::Reset()
{
    // Basic initialization

    bOwnWindow = TRUE;
    iSubWindow = 0;
    bValidateBg = FALSE;
    wFlags = 0;
    hwnd = 0;
    hdc = 0;
    hrc = 0;
    pos.x = pos.y = 0;
    size.width = size.height = 0;
    psswParent =    NULL;
    psswSibling =   NULL;
    psswChildren =  NULL;
    bDoubleBuf =    FALSE;
    pStretch =      NULL;
    pMotion =       NULL;
    pGLc =          NULL;

    InitFunc =      NULL;
    UpdateFunc =    NULL;
    ReshapeFunc =   NULL;
    RepaintFunc =   NULL;
    FloaterBounceFunc = NULL;
    FinishFunc =    NULL;
    ChildSizeFunc = NULL;
    DataPtr =       NULL;
}

/**************************************************************************\
* SSW destructor
*
* This can be called when a window is closed, or by the ss client
*
\**************************************************************************/

SSW::~SSW()
{
    // If this window has any children, they will have to be terminated too

    if( psswChildren ) {
        PSSW psswChild = psswChildren;
        while( psswChild ) {
            // Delete first child in list
            if( psswChild->hwnd && bOwnWindow ) {
                // We created this window, we must destroy it
    DestroyWindow( psswChild->hwnd );
            } else {
                delete psswChild;
            }
            // Next child is now first child in list
            psswChild = psswChildren;
        }
    }

    if( psswParent )
        // Need to remove this pssw from its parent's list
        psswParent->RemoveChild( this );

    if( hwnd ) {
        // Remove from SSWTable
        gpss->sswTable.Remove( hwnd );
    } else {
        // subWindow
        if( psswParent ) {
            SS_ASSERT1( (psswParent->iSubWindow > 0),
                 "Invalid subWindow reference count for pssw=0x%x\n", this );
            psswParent->iSubWindow--;  // decrement subWindow reference count
        }
    }

    // Clean up GL

    if( hrc ) {
        // FinishFunc still needs gl
        if( FinishFunc )
            (*FinishFunc)( DataPtr );

        wglMakeCurrent( NULL, NULL );
        if( ! (wFlags & SS_HRC_PROXY_BIT) )
            wglDeleteContext( hrc );
    }

    // Clean up any bitmaps

    if( pStretch ) {
        SS_BITMAP *pssbm = &pStretch->ssbm;

        DeleteObject(SelectObject(pssbm->hdc, pssbm->hbmOld));
        DeleteDC(pssbm->hdc);
    }

    //  Release the dc
    if( hdc ) {
        HWND hwndForHdc = hwnd ? hwnd : psswParent ? psswParent->hwnd : NULL;
        ReleaseDC(hwndForHdc, hdc);
    }
}

/**************************************************************************\
* AddChild
*
* Add the supplied child SSW to this SSW.
\**************************************************************************/

void
SSW::AddChild( PSSW psswChild )
{
    if( !psswChildren ) {
        psswChildren = psswChild;
        return;
    }

    // Else travel along the sibling chain of psswChildren and deposit
    // psswChild at the end

    PSSW pssw = psswChildren;
    while( pssw->psswSibling )
        pssw = pssw->psswSibling;
    pssw->psswSibling = psswChild;
}

/**************************************************************************\
* RemoveChild
*
* Remove this child from the parent's list
*
* Whoever calls this needs to update SSW_TABLE too...
\**************************************************************************/

BOOL
SSW::RemoveChild( PSSW psswChild )
{
    if( !psswChildren ) {
        // Something wrong - this window has no children
        SS_ERROR( "SSW::RemoveChild : no children\n" );
        return FALSE;
    }

    PSSW psswPrev;
    PSSW pssw = psswChildren;

    while( pssw != NULL ) {
        if( pssw == psswChild ) {
            // found it !
            if( psswChild == psswChildren )
                // The child being removed is the first in the list
                psswChildren = psswChild->psswSibling;
            else
                psswPrev->psswSibling = pssw->psswSibling;
            return TRUE;
        }
        // Move up the pointers
        psswPrev = pssw;
        pssw = psswPrev->psswSibling;
    }

    SS_ERROR( "SSW::RemoveChild : child not found\n" );
    return FALSE;
}

/**************************************************************************\
* GetWindowSize
*
\**************************************************************************/

static void
GetWindowSize( HWND hwnd, ISIZE *pSize )
{
    RECT clientRect;

    GetClientRect( hwnd, &clientRect );

    pSize->width = clientRect.right - clientRect.left + 1;
    pSize->height = clientRect.bottom - clientRect.top + 1;
}

/**************************************************************************\
* CreateSSWindow
*
* Create OpenGL floater window.  This window floats on top of the screen
* saver window, bouncing off each of the screen edges.
*
\**************************************************************************/

BOOL
SSW::CreateSSWindow(HINSTANCE hMainInstance,  UINT uStyle, UINT uExStyle ,
                  LPCTSTR pszWindowTitle, WNDPROC wndProcArg, LPCTSTR pszClassName, HWND hwndParentOverride )
{
    IPOINT2D startPos;
    HWND hwndParent;

    if( hwndParentOverride )
        hwndParent = hwndParentOverride;
    else
        hwndParent =  psswParent ? psswParent->hwnd : NULL;

    wndProc = wndProcArg;

    if( !pMotion )
        startPos = pos;
    else {
        // Initialize start position off screen to work around win95 screen
        // validation bug
        startPos.x = pos.x - psswParent->size.width;
        startPos.y = pos.y - psswParent->size.height;
    }

    hwnd = CreateWindowEx(
                                 uExStyle,
                                 pszClassName,
                                 pszWindowTitle,
                                 uStyle,
                                 startPos.x,
                                 startPos.y,
                                 size.width,     // width
                                 size.height,    // height
                                 hwndParent,
                                 NULL,               // menu
                                 hMainInstance,
                                 (LPVOID) this
                                );

    if (!hwnd) {
        SS_WARNING( "SSW::CreateSSWindow : CreateWindowEx failure\n" );
        return FALSE;
    }

    // This window is on its own now
    if( psswParent ) {
        SS_ASSERT1( (psswParent->iSubWindow > 0),
                 "Invalid subWindow reference count for pssw=0x%x\n", this );
        psswParent->iSubWindow--;  // decrement subWindow reference count
    }

    ShowWindow(hwnd, SW_SHOW);

    return TRUE;
}

/**************************************************************************\
* GetChildInfo
*
* Call the window's ChildSizeFunc
\**************************************************************************/

void
SSW::GetChildInfo( )
{
    if( !ChildSizeFunc )
        return;

    CHILD_INFO childInfo;

    // Call the client's SizeFunc to get required info

    (*ChildSizeFunc)( &psswParent->size, &childInfo );

    // Pull required values into pssw and validate them

    size = childInfo.size;
    ValidateChildSize();

    if( !pMotion ) {
        pos = childInfo.pos;
        bValidateChildPos();
    } else {
        pMotion->posInc = childInfo.motionInfo.posInc;
        pMotion->posIncVary = childInfo.motionInfo.posIncVary;
        pMotion->posIncCur = pMotion->posInc;
    }
}

/**************************************************************************\
* ConfigureForGdi
*
* Creates an hdc for the window
*
\**************************************************************************/

BOOL
SSW::ConfigureForGdi()
{
    if( hdc )
        // already configured
        return TRUE;

    // Figure window to get hdc from
    HWND hwndForHdc = hwnd ? hwnd : psswParent ? psswParent->hwnd : NULL;

    if( !hwndForHdc || !(hdc = GetDC(hwndForHdc)) ) {
        SS_WARNING( "SSW::ConfigureForGdi failed\n" );
        return FALSE;
    }
    return TRUE;
}

/**************************************************************************\
* ConfigureForGL
*
* Creates a GL rendering context for the specified window
*
\**************************************************************************/

BOOL
SSW::ConfigureForGL( SS_GL_CONFIG *pGLcArg )
{
    pGLc = pGLcArg;
    return ConfigureForGL();
}

BOOL
SSW::ConfigureForGL()
{
    if( hrc )
        return TRUE;

    if( ConfigureForGdi() &&
        (hrc = hrcSetupGL()) )
        return TRUE;

    SS_WARNING( "SSW::ConfigureForGL failed\n" );
    return FALSE;
}

/**************************************************************************\
* hrcSetupGL
*
* Setup OpenGL.
*
\**************************************************************************/

#define NULL_RC ((HGLRC) 0)

HGLRC
SSW::hrcSetupGL()
{
    if( !pGLc )
        return NULL_RC;

    HGLRC hrc;
    HDC hgldc;
    int pfFlags = pGLc->pfFlags;
    PIXELFORMATDESCRIPTOR pfd = {0};

    pStretch = pGLc->pStretch;
    if( pStretch ) {
        if( NeedStretchedWindow() ) {
            // Only need single buffered pixel format
            pfFlags &= ~SS_DOUBLEBUF_BIT;
            pfFlags |= SS_BITMAP_BIT; // yup, BOTH window and bitmap need this
        } else
            // Turn off stretching
            pStretch = NULL;
    }

    // If preview mode or config mode, don't allow pixel formats that need
    // the system palette, as this will create much ugliness.
    if( ss_fPreviewMode() || ss_fConfigMode() )
        pfFlags |= SS_NO_SYSTEM_PALETTE_BIT;

    // If config mode, force a non-accelerated pixel format, as WNDOBJ's
    // seem to have problems with MCD, ICD
    if( ss_fConfigMode() )
        pfFlags |= SS_GENERIC_UNACCELERATED_BIT;

    bDoubleBuf = SS_HAS_DOUBLEBUF( pfFlags );

    if( !SSU_SetupPixelFormat( hdc, pfFlags, &pfd ) )
        return NULL_RC;

    // Update pGLc->pfFlags based on pfd returned
    // (for now, the only ones we care about are the generic/accelerated flags)
    if(  (pfd.dwFlags & (PFD_GENERIC_FORMAT|PFD_GENERIC_ACCELERATED))
 == PFD_GENERIC_FORMAT )
        pGLc->pfFlags |= SS_GENERIC_UNACCELERATED_BIT;

    if( SSU_bNeedPalette( &pfd ) ) {
        // Note: even if bStretch, need to set up palette here so they match
        if( !gpss->pssPal ) {
            SS_PAL *pssPal;
            BOOL bTakeOverPalette = ss_fFullScreenMode() ? TRUE : FALSE;

            // The global palette has not been created yet - do it
            // SS_PAL creation requires pixel format descriptor for color bit
            // information, etc. (the pfd is cached in SS_PAL, since for
            // palette purposes it is the same for all windows)
            pssPal = new SS_PAL( hdc, &pfd, bTakeOverPalette );
            if( !pssPal )
                return NULL_RC;
            // Set approppriate palette manage proc
            if( ss_fFullScreenMode() )
                pssPal->paletteManageProc = FullScreenPaletteManageProc;
            else
                // use regular palette manager proc
                pssPal->paletteManageProc = PaletteManageProc;
            gpss->pssPal = pssPal;
        }
        // Realize the global palette in this window
        // assume we're realizing in foreground
        HWND hwndPal = hwnd ? hwnd : psswParent ? psswParent->hwnd : NULL;
        if( hwndPal )
            gpss->pssPal->Realize( hwndPal, hdc, FALSE );
    }

    if( pStretch ) {
        // Stretch blt mode: For every frame, we'll be doing a StretchBlt
        // from a DIB to the screen.  Need to set up a compatible memdc.
        SS_BITMAP *pssbm;

        pssbm = &pStretch->ssbm;

        pssbm->hdc = CreateCompatibleDC(hdc);
        if( !pssbm->hdc )
            return NULL_RC;
        ResizeStretch(); // this creates the DIB Section
        pfFlags = 0;
        pfFlags |= SS_BITMAP_BIT;
        if( !SSU_SetupPixelFormat( pssbm->hdc, pfFlags, &pfd ) ) {
            return NULL_RC;
        }
        // this ppfd's palette bits must match the window's !!
        // If window needs palette, so does bitmap...
        if( gpss->pssPal ) {
            SS_PAL *pssPal = gpss->pssPal;
            extern void ssw_UpdateDIBColorTable( HDC, HDC );

            ssw_UpdateDIBColorTable( pssbm->hdc, hdc );
        }
        hgldc = pssbm->hdc;
    } else {
        hgldc = hdc;
    }

    if( pGLc->hrc ) {
        // Use the supplied hrc
        hrc = pGLc->hrc;
        // Set flag so we don't delete this borrowed hrc when the SSW terminates
        wFlags |= SS_HRC_PROXY_BIT;
    } else
        // Create a new hrc
        hrc = wglCreateContext(hgldc);

    if( !hrc || !wglMakeCurrent(hgldc, hrc) ) {
        SS_WARNING( "SSW::hrcSetupGL : hrc context failure\n" );
        return NULL_RC;
    }

    if( !hwnd && (bDoubleBuf || pStretch) ) {

        // enable scissoring
        glEnable( GL_SCISSOR_TEST );

        if( !(pGLc->pfFlags & SS_GENERIC_UNACCELERATED_BIT) ) {
            // MCD or ICD, possible hardware implementation - we maintain
            // a lastRect to handle SwapBuffer issues
            lastRect.x = lastRect.y = lastRect.width = lastRect.height = 0;
        }
    }

    SS_DBGLEVEL2( SS_LEVEL_INFO,
        "SSW::hrcSetupGL: wglMakeCurrent( hrc=0x%x, hwnd=0x%x )\n", hrc, hwnd );

// Note that these queries are based on a single gl window screen saver.  In
// a more complicated scenario, these capabilities could be queried on a
// per-window basis (since support could vary with pixel formats).
    // Query the GL version - sets support for any new (e.g. 1.1) functionality
    ss_QueryGLVersion();

    // Query paletted texture extension
    ss_QueryPalettedTextureEXT();

    // Query the AddSwapHintRect WIN extension
    ss_QueryAddSwapHintRect();

    // Pull in any Func's that were already defined (for compatibility with
    // old mechanism)

    InitFunc = gInitFunc;
    UpdateFunc = gUpdateFunc;
    ReshapeFunc = gReshapeFunc;
    RepaintFunc = gRepaintFunc;
    FloaterBounceFunc = gFloaterBounceFunc;
    FinishFunc = gFinishFunc;
    DataPtr = gDataPtr;

    return hrc;
}

/**************************************************************************\
* MakeCurrent
*
* Call wglMakeCurrent for this window's hrc.  Note: an ss client may have
* more than one hrc (e.g. pipes), in which case it is the client's
* responsibility to make current.
\**************************************************************************/

void
SSW::MakeCurrent()
{
    if( ! wglMakeCurrent( hdc, hrc ) )
        SS_WARNING( "SSW::MakeCurrent : wglMakeCurrent failure\n" );
}

/**************************************************************************\
* InitGL
*
* Call the window's GL Init Func
*
* Priority is raised to expedite any initialization (e.g. loading and
* processing textures can take a while.
*
* A Reshape msg is sent to the client ss, as this is required for setting
* glViewport, etc.
\**************************************************************************/

void
SSW::InitGL()
{
    PSSW psswChild = psswChildren;

    // Configure the window for GL if pGLc non-NULL
    if( pGLc && (! ConfigureForGL()) ) {
        // This is fatal for this window - if it is the main window,
        // the ss will terminate
        if( hwnd )
            PostMessage( hwnd, WM_CLOSE, 0, 0l );
        return;
    }

    // If window configured for GL, hrc will have been set...

    // Call the InitFunc
    if( hrc && InitFunc ) {
        DWORD oldPriority;

        // Bump up priority during initialization phase
        oldPriority = GetPriorityClass( GetCurrentProcess() );
        SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS );

        SS_DBGLEVEL1( SS_LEVEL_INFO,
                "SSW::InitGL: Calling client GLInit for 0x%x\n", hwnd );

        (*InitFunc)( DataPtr );

        // restore original priority
        SetPriorityClass( GetCurrentProcess(), oldPriority );
    }

    /* Send another Reshape, since initial one triggered by window
     * creation would have been received before GL init'd
     */
    Reshape();

    // Next, init any child windows.  This has to be done after the parent
    // window initialization.

    while( psswChild ) {
        if( psswChild->hwnd )
            SendMessage( psswChild->hwnd, SS_WM_INITGL, 0, 0 );
        else
            // Must be a logical sub-window
            psswChild->InitGL();
        psswChild = psswChild->psswSibling;
    }
}

/**************************************************************************\
* ResizeStretch
*
* Resize the compatible bitmap for stretch blt mode
*
* There are 2 sizing modes.  If bRatioMode, then we set the bitmap size
* by dividing the window dimensions by the supplied ratios.  In this case,
* the base* values set a lower limit for the bitmap dimensions.  Otherwise, the
* base* values determine the bitmap dimensions.
*
\**************************************************************************/

void
SSW::ResizeStretch()
{
    HBITMAP hbmNew;
    PVOID pvBits;
    int width, height;
    int cwidth, cheight; // client area size
    SS_BITMAP *pssbm = &pStretch->ssbm;

    cwidth = size.width;
    cheight = size.height;

    if( pStretch->bRatioMode ) {
        width = (int) ( (float)cwidth / pStretch->widthRatio );
        height = (int) ( (float)cheight / pStretch->heightRatio );
        if( width < pStretch->baseWidth )
            width = pStretch->baseWidth;
        if( height < pStretch->baseHeight )
            height = pStretch->baseHeight ;
    } else {
        width = pStretch->baseWidth;
        height = pStretch->baseHeight;
    }

    // Limit width, height to window dimensions
    if( width > cwidth )
        width = cwidth;
    if( height > cheight )
        height = cheight;

    // If same size, get out
    if( (width == pssbm->size.width) && (height == pssbm->size.height) )
        return;

    pssbm->size.width = width;
    pssbm->size.height = height;

    // Use system palette
    hbmNew = SSDIB_CreateCompatibleDIB(hdc, NULL, width, height, &pvBits);

    if (hbmNew)
    {
        if (pssbm->hbm != (HBITMAP) 0)
        {
            SelectObject( pssbm->hdc, pssbm->hbmOld );
            DeleteObject( pssbm->hbm );
        }

        pssbm->hbm = hbmNew;
        pssbm->hbmOld = (HBITMAP) SelectObject( pssbm->hdc, pssbm->hbm );
    }
}

/**************************************************************************\
* Resize
*
* Resize wrapper
*
* Called in response to WM_SIZE.
*
\**************************************************************************/

void
SSW::Resize( int width, int height )
{
    size.width  = width;
    size.height = height;

    if( pStretch ) {
        // May have to resize associated bitmap
        ResizeStretch();
    }

    if( psswChildren ) {
        // May need to resize any children
        PSSW pssw = psswChildren;
        while( pssw ) {
            // Get new size/motion for the floater
            pssw->GetChildInfo();
            pssw->SetSSWindowPos();
            if( !pssw->hwnd ) {
                // Handle sub-window case
                // Need to call Reshape, since win32 system won't send WM_SIZE.
                pssw->Reshape();
            }
            pssw = pssw->psswSibling;
        }
    }

    Reshape();
}

/**************************************************************************\
* Repaint
*
* Repaint wrapper
*
* Called in response to WM_PAINT.
*
\**************************************************************************/

#define NULL_UPDATE_RECT( pRect ) \
     (  ((pRect)->left == 0) && \
        ((pRect)->right == 0) && \
        ((pRect)->top == 0) && \
        ((pRect)->bottom == 0) )

void
SSW::Repaint( BOOL bCheckUpdateRect )
{
    if( !hwnd )
        return;

    RECT rect, *pRect = NULL;

    if( bCheckUpdateRect ) {
        GetUpdateRect( hwnd, &rect, FALSE );
        // Above supposed to return NULL if rect is all 0's,
        // but this doesn't happen
        if( NULL_UPDATE_RECT( &rect ) )
            return;
        pRect = &rect;
    }

    if( RepaintFunc )
        (*RepaintFunc)( pRect, DataPtr );
}

/**************************************************************************\
* NeedStretchedWindow
*
* Check if stretch mode is necessary
*
\**************************************************************************/

BOOL
SSW::NeedStretchedWindow()
{
    if( (pStretch->baseWidth >= size.width) &&
        (pStretch->baseHeight >= size.height) ) {
        return FALSE;
    }
    return TRUE;
}

/**************************************************************************\
* SetSSWindowPos
*
* Set new size and position for an SSW
*
\**************************************************************************/

void
SSW::SetSSWindowPos()
{
    SetSSWindowPos( 0 );
}

void
SSW::SetSSWindowPos( int flags )
{
    if( hwnd ) {
        SetWindowPos(hwnd, 0, pos.x, pos.y,
                 size.width, size.height,
                 SWP_NOCOPYBITS | SWP_NOZORDER | SWP_NOACTIVATE | flags );
    // Note: If flags does not specify SWP_NOREDRAW, this generates a WM_PAINT
    // msg for the entire window region
    } else if( psswParent && !pStretch ) {
        // Set viewport for new position
        // (if pStretch, shouldn't need to do anything, as viewport should
        // have been already set to the bitmap)
        int posy = GLPosY();
        glViewport( pos.x, posy, size.width, size.height );
        glScissor( pos.x, posy, size.width, size.height );
        glAddSwapHintRect( pos.x, posy, size.width, size.height );
    }
}

/******************************Public*Routine******************************\
* SetAspectRatio
*
* Resize a child window to conform to the supplied aspect ratio.  We do this by
* maintaining the existing width, and adjusting the height.
*
* Window resize seems to be executed synchronously, so gl should be able to
* immediately validate its buffer dimensions (we count on it).
*
* Returns TRUE if new height is different from last, else FALSE.
\**************************************************************************/

BOOL
SSW::SetAspectRatio( FLOAT fAspect )
{
    if( !psswParent )
        return FALSE;

    int oldHeight;
    UINT uFlags = 0;

    // Check for zero fAspect
    if( fAspect == 0.0f ) fAspect = 1.0f;

    oldHeight = size.height;
    // Set the new height, based on desired aspect ratio
    size.height = (int) ((FLOAT) size.width / fAspect);
    // make sure new height not TOO big!
    ValidateChildSize();

    if( size.height == oldHeight )
        // no change
        return FALSE;

    // make sure current position still valid when height changes
    if( !bValidateChildPos() ) {
        // current position OK, don't need to move it
        uFlags |= SWP_NOMOVE;
    }
    SetSSWindowPos( uFlags );

    // ! remember not to call the client's ReshapeFunc here, as SetAspectRatio
    // may be called by the client in response to a window resize, resulting in
    // an infinite loop
    // but if there is no size change (above) we return, avoiding the
    // infinite loop
    if( !hwnd )
        // Need to call Reshape, since win32 system won't send WM_SIZE.  Need
        // to fix to avoid possible infinite loops
        Reshape();

    return TRUE;
}

/**************************************************************************\
* CalcNextWindowPos
*
* Calculate the next position for a moving window, using the pMotion data.
* If the new position would cause the window to bounce off the
* edge of its parent, return TRUE.
*
\**************************************************************************/

BOOL
SSW::CalcNextWindowPos()
{
    POINT2D *fpos = &pMotion->pos;
    IPOINT2D *ipos = &pos;
    POINT2D *posInc = &pMotion->posInc;
    POINT2D *posIncV = &pMotion->posIncVary;
    POINT2D *posIncCur = &pMotion->posIncCur;
    BOOL bounce = FALSE;

    if( !psswParent )
        return FALSE;

    // Compute the next window position.

    fpos->x += posIncCur->x;
    fpos->y += posIncCur->y;
    ipos->x = (int) fpos->x;
    ipos->y = (int) fpos->y;

    if ( (ipos->x + size.width) > psswParent->size.width) {
        // Right hit
        ipos->x = psswParent->size.width - size.width;

fpos->x = (float) ipos->x;
        posIncCur->x =
            - ss_fRand( posInc->x - posIncV->x, posInc->x + posIncV->x );
        if( posIncCur->x > -0.5f ) posIncCur->x = -0.5f;
        bounce = TRUE;
    } else if (ipos->x < 0) {
        // Left hit
        ipos->x = 0;
        fpos->x = 0.0f;
        posIncCur->x =
            ss_fRand( posInc->x - posIncV->x, posInc->x + posIncV->x );
        if( posIncCur->x < 0.5f ) posIncCur->x = 0.5f;
        bounce = TRUE;
    }

    if ( (ipos->y + size.height) > psswParent->size.height) {
        // Bottom hit
        ipos->y = psswParent->size.height - size.height;
        fpos->y = (float) (ipos->y);
        posIncCur->y =
            - ss_fRand( posInc->y - posIncV->y, posInc->y + posIncV->y );
        if( posIncCur->y > -0.5f ) posIncCur->y = -0.5f;
        bounce = TRUE;
    } else if (ipos->y < 0) {
        // Top hit
        ipos->y = 0;
        fpos->y = 0.0f;
        posIncCur->y =
            ss_fRand( posInc->y - posIncV->y, posInc->y + posIncV->y );
        if( posIncCur->y < 0.5f ) posIncCur->y = 0.5f;
        bounce = TRUE;
    }
    return bounce;
}

/**************************************************************************\
* MoveSSWindow
*
* This is the function that moves the OpenGL window around, causing it to
* bounce around.  Each time the window is moved, the contents of the
* window are updated from the hidden (or back) buffer by SwapBuffers().
*
* The bRedrawBg flag determines whether the area that was covered by the old
* position should be updated by the parent window.
*
\**************************************************************************/

void
SSW::MoveSSWindow( BOOL bRedrawBg )
{
    BOOL bounce;
    int flags = SWP_NOSIZE;

    // Synchronize with OpenGL.  Flush OpenGL commands and wait for completion.

    glFinish();

    // Move the window

    bounce = CalcNextWindowPos();

    if( bounce && FloaterBounceFunc )
        // The window bounced off one of the sides
        // ! This function should *not* be used for rendering - for
        // for informational purposes only (.e.g. changing the spin of a
        // rotating object).
        (*FloaterBounceFunc)( DataPtr );

    if( !bRedrawBg )
        flags |= SWP_NOREDRAW;
    SetSSWindowPos( flags );
}

/**************************************************************************\
* UpdateWindow
*
* Update the window
*
* Ccurrently this assumes all windows are being animated (i.e. not showing
*   a static image)
*
* Things *must* happen in the order defined here, so they work on generic as
* well as hardware implementations.
* Note: Move must happen after SwapBuf, and will cause some encroaching on
* the current display, as the parent window repaints after the move.  Therefore
* apps must take care to leave an empty border around their rendered image,
* equal to the maximum window move delta.
*
\**************************************************************************/

void
SSW::UpdateWindow()
{
    // update any children first

    PSSW pssw = psswChildren;
    while( pssw ) {
        pssw->UpdateWindow();
        pssw = pssw->psswSibling;
    }

    // If this window is a subWindow in a non-generic implementation, the
    // background of the parent may be invalid (this may eventually be
    // useful for regular windows, which is why we don't && !hwnd)
    if( psswParent &&
        psswParent->bValidateBg &&
        pGLc &&
        !(pGLc->pfFlags & SS_GENERIC_UNACCELERATED_BIT) )
    {
        // Clear the entire parent window
        // mf: I think this is only needed for double-buffered schemes, since
        // the windowing system should repaint the front buffer in this case.
        glDisable( GL_SCISSOR_TEST );
        glClear( GL_COLOR_BUFFER_BIT );
        glEnable( GL_SCISSOR_TEST );
        psswParent->bValidateBg = FALSE;
    }

    if( !UpdateFunc )
        return;

    // bDoubleBuf and pStretch should be mutually exclusive...

    if( bDoubleBuf || pStretch ) {
        UpdateDoubleBufWin();
    } else {
        if( pMotion )
            MoveSSWindow( TRUE );
        (*UpdateFunc)( DataPtr );
    }
}

/**************************************************************************\
* UpdateDoubleBufWin
*
* This is used when moving a double buffered window around.  It will
* work for all configurations.
*
\**************************************************************************/

void
SSW::UpdateDoubleBufWin()
{
    if( !hwnd ) {
        UpdateDoubleBufSubWin();
        return;
    }

    RECT updateRect;

    // Move the window

    if( pMotion ) {
        // Save child update rect before move
        GetSSWindowRect( &updateRect );
        // Move window, without repainting exposed area
        MoveSSWindow( FALSE );
    }

    // Update the back buffer

    (*UpdateFunc)( DataPtr );

    if( pMotion ) {
        // (pMotion will be NULL if this window has no parent)
        if( hwnd ) {
            // Paint the exposed area with bg brush (the current image will
            // be partially erased momentarily, until the SwapBuffers() call
            // comes through)
            // (This rect should be clipped to our new window position...)
            DrawGdiRect( psswParent->hdc, gpss->hbrBg, &updateRect );
        } else {
// currently this path not possible, since if !hwnd, we use one of the
// UpdateDoubleBufSubWin* functions
            SS_WARNING( "SSW::UpdateDoubleBufWin: no hwnd\n" );
            // sub-window case : need to do our own clipping
            RECT newRect;
            GetSSWindowRect( &newRect );
            DrawGdiDeltaRect( psswParent->hdc, gpss->hbrBg, &updateRect, &newRect );
        }
    }

    // Swap to the new window position
    SwapSSBuffers();
}

/**************************************************************************\
* UpdateDoubleBufSubWin
*
* Used for generic double buffered gl sub-windows.
*
\**************************************************************************/

void
SSW::UpdateDoubleBufSubWin()
{
    GLRECT curRect, newRect;

    // AddSwapHintRect for current position
    glAddSwapHintRect( pos.x, GLPosY(), size.width, size.height );

    if( pMotion ) {

        // Save current rect
        curRect.x = pos.x;
        curRect.y = GLPosY();
        curRect.width = size.width;
        curRect.height = size.height;

        // Move window, without repainting exposed area
        MoveSSWindow( FALSE );

        // Get new rect
        newRect.x = pos.x;
        newRect.y = GLPosY();
        newRect.width = size.width;
        newRect.height = size.height;

        DrawGLDeltaRect( &curRect, &newRect );

        // Have to consider previous rect for ICD or MCD
        if( !(pGLc->pfFlags & SS_GENERIC_UNACCELERATED_BIT) ) {
            DrawGLDeltaRect( &lastRect, &newRect );
            lastRect = curRect;
        }

        // Reset scissor to new rect (this *was* set by MoveSSWindow, but
        // DrawGLDeltaRect sets scissor to do its clearing
        glScissor( newRect.x, newRect.y, newRect.width, newRect.height );
    }

    // Update the back buffer

    (*UpdateFunc)( DataPtr );

    // Swap to the new window position
    SwapSSBuffers();
}

/******************************Public*Routine******************************\
* RandomWindowPos
*
* Sets a new random window position and motion
*
\**************************************************************************/

void
SSW::RandomWindowPos()
{
    if( psswParent ) {
        if( !hwnd ) {
            // sub-window : manually clear old window rect
            if( bDoubleBuf ) {
                glClear( GL_COLOR_BUFFER_BIT );
            } else {
                RECT oldRect;
                GetSSWindowRect( &oldRect );
                DrawGdiRect( psswParent->hdc, gpss->hbrBg, &oldRect );
            }
        }

        // Calc and set new position
        pos.x = ss_iRand2( 0, (psswParent->size.width - size.width) );
        pos.y = ss_iRand2( 0, (psswParent->size.height - size.height) );
        SetSSWindowPos( SWP_NOSIZE );

        // Reset motion
        if( pMotion )
            ResetMotion();
    }
}

/**************************************************************************\
* ResetMotion
*
* Calculate a random position and motion vector for the floater window
* Note that a floating point position is maintained for DDA window movement
*
\**************************************************************************/

void
SSW::ResetMotion()
{
    if( !psswParent || !pMotion )
        // Only child windows can be reset
        return;

    // Set floating point pos also, for DDA
    pMotion->pos.x = (float) pos.x;
    pMotion->pos.y = (float) pos.y;

    // also reset the window motion directions

    if( ss_iRand(2) )  // 0 or 1
        pMotion->posIncCur.x = - pMotion->posIncCur.x;
    if( ss_iRand(2) )
        pMotion->posIncCur.y = - pMotion->posIncCur.y;
}

/**************************************************************************\
* ValidateChildSize
*
* Make sure it's not bigger than its parent
*
\**************************************************************************/

void
SSW::ValidateChildSize()
{
    if( !psswParent )
        return;

    SS_CLAMP_TO_RANGE2( size.width, 0, psswParent->size.width );
    SS_CLAMP_TO_RANGE2( size.height, 0, psswParent->size.height );
}

/**************************************************************************\
* bValidateChildPos
*
* Make sure that with the current window position, none of the floating
* window extends beyond the parent window.
*
\**************************************************************************/

BOOL
SSW::bValidateChildPos()
{
    BOOL bRet = FALSE;

    if( !psswParent )
        return FALSE;

    if ( (pos.x + size.width) > psswParent->size.width) {
        pos.x = psswParent->size.width - size.width;
        bRet = TRUE;
    }

    if ( (pos.y + size.height) > psswParent->size.height) {
        pos.y = psswParent->size.height - size.height;
        bRet = TRUE;
    }
    return bRet;
}

/**************************************************************************\
* GetSSWindowRect
*
* Return window position and size in supplied RECT structure
*
* this rect is relative to the parent
\**************************************************************************/

void
SSW::GetSSWindowRect( LPRECT lpRect )
{
    lpRect->left = pos.x;
    lpRect->top = pos.y;
    lpRect->right = pos.x + size.width;
    lpRect->bottom = pos.y + size.height;
}

/**************************************************************************\
* GLPosY
*
* Return y-coord of window position in GL coordinates (a win32 window position
* (starts from top left, while GL starts from bottom left)
*
\**************************************************************************/

int
SSW::GLPosY()
{
    if( !psswParent )
        return 0;

    return psswParent->size.height - size.height - pos.y;
}

/**************************************************************************\
* SwapStretchBuffers
*
* Swaps from the stretch buffer to the GL window, using StretchBlt
*
\**************************************************************************/

void
SSW::SwapStretchBuffers()
{
    SS_BITMAP *pssbm = &pStretch->ssbm;

    if( (size.width == pssbm->size.width) &&
        (size.height == pssbm->size.height) ) // buffers same size
    {
        BitBlt(hdc, 0, 0, size.width, size.height,
               pssbm->hdc, 0, 0, SRCCOPY);
    }
    else
    {
        StretchBlt(hdc, 0, 0, size.width, size.height,
                   pssbm->hdc, 0, 0, pssbm->size.width, pssbm->size.height,
                   SRCCOPY);
    }
    GdiFlush();
}

/**************************************************************************\
* SwapBuffers
*
* Wrapper for SwapBuffers / SwapStretchBuffers
*
\**************************************************************************/

void
SSW::SwapSSBuffers()
{
    if( pStretch )
        SwapStretchBuffers();
    else if( bDoubleBuf )
        SwapBuffers( hdc );
}

/**************************************************************************\
* Reshape
*
* Reshape wrapper

* Sends reshape msg to screen saver
* This is the size of the surface that gl renders onto, which can be a bitmap.
*
\**************************************************************************/

void
SSW::Reshape()
{
    // Point to size of window, or bitmap if it has one
    ISIZE *pSize = &size;
    if( pStretch )
        pSize = &pStretch->ssbm.size;

    // If the window has an hrc, set default viewport

    if( hrc ) {
        if( hwnd )
            glViewport( 0, 0, pSize->width, pSize->height );
        else if ( psswParent ) {
            // sub-window (only 1 level of sub-windowing supported)

            // clear entire window to black
            glDisable( GL_SCISSOR_TEST );
            glClear( GL_COLOR_BUFFER_BIT );
            glEnable( GL_SCISSOR_TEST );

            // Convert win32 y-coord to GL
            glViewport( pos.x, GLPosY(), pSize->width, pSize->height );
        }
    }

    if( ReshapeFunc ) {
        (*ReshapeFunc)( pSize->width, pSize->height, DataPtr );
    }
}

/******************************Public*Routine******************************\
* GdiClear
*
* Clears window using Gdi FillRect
\**************************************************************************/

void
SSW::GdiClear()
{
    if( !hdc )
        return;

    RECT rect;

    GetSSWindowRect( &rect );

    FillRect( hdc, &rect, gpss->hbrBg );
    GdiFlush();
}

/******************************Public*Routine******************************\
* MyAddSwapHintRect
*
\**************************************************************************/

static void _stdcall
MyAddSwapHintRect(GLint xs, GLint ys, GLint xe, GLint ye)
{
    return;
}

/******************************Public*Routine******************************\
* QueryAddSwapHintRectWIN
*
\**************************************************************************/

static void
ss_QueryAddSwapHintRect()
{
    glAddSwapHintRect = (PFNGLADDSWAPHINTRECTWINPROC)
        wglGetProcAddress("glAddSwapHintRectWIN");
    if (glAddSwapHintRect == NULL) {
        glAddSwapHintRect = MyAddSwapHintRect;
    }
}

/******************************Public*Routine******************************\
* DrawGdiDeltaRect
*
* Draw the exposed area by transition from rect1 to rect2
\**************************************************************************/

static void
DrawGdiDeltaRect( HDC hdc, HBRUSH hbr, RECT *pRect1, RECT *pRect2 )
{
    if( (pRect1 == NULL) || (pRect2 == NULL) ) {
        SS_WARNING( "DrawGdiDeltaRect : one or both rects are NULL\n" );
        return;
    }

    // Draw 2 rects

    RECT rect;

    // Rect exposed in x-direction:

    rect.top = pRect1->top;
    rect.bottom = pRect1->bottom;
    if( pRect2->left > pRect1->left ) {
        // moving right
        rect.left = pRect1->left;
        rect.right = pRect2->left;
    } else {
        // moving left
        rect.left = pRect2->right;
        rect.right = pRect1->right;
    }
    FillRect( hdc, &rect, hbr );

    // Rect exposed in y-direction:

    rect.left = pRect1->left;
    rect.right = pRect1->right;
    if( pRect2->bottom > pRect1->bottom ) {
        // moving down
        rect.top = pRect1->top;
        rect.bottom = pRect2->top;
    } else {
        // moving up
        rect.top = pRect2->bottom;
        rect.bottom = pRect1->bottom;
    }
    FillRect( hdc, &rect, hbr );

    GdiFlush();
}

/******************************Public*Routine******************************\
* DrawGLDeltaRect
*
* Draw the exposed area by transition from rect1 to rect2
\**************************************************************************/

static void
DrawGLDeltaRect( GLRECT *pRect1, GLRECT *pRect2 )
{
    if( (pRect1 == NULL) || (pRect2 == NULL) ) {
        SS_WARNING( "DrawGLDeltaRect : one or both rects are NULL\n" );
        return;
    }

    // Draw 2 rects :

//mf: !!! this assumes rect1 and rect2 have same dimensions !

    GLRECT rect;

    // Rect exposed in x-direction:

    rect.height = pRect1->height;
    rect.y = pRect1->y;

    if( pRect2->x > pRect1->x ) {
        // moving right
        rect.width = pRect2->x - pRect1->x;
        rect.x = pRect1->x;
    } else {
        // moving left
        rect.width = pRect1->x - pRect2->x;
        rect.x = pRect2->x + pRect2->width;
    }

    glScissor( rect.x, rect.y, rect.width, rect.height );
    glClear( GL_COLOR_BUFFER_BIT );

    // Rect exposed in y-direction:

    rect.width = pRect1->width;
    rect.x = pRect1->x;

    if( pRect2->y > pRect1->y ) {
        // moving up
        rect.height = pRect2->y - pRect1->y;
        rect.y = pRect1->y;
    } else {
        // moving down
        rect.height = pRect1->y - pRect2->y;
        rect.y = pRect2->y + pRect2->height;
    }

    glScissor( rect.x, rect.y, rect.width, rect.height );
    glClear( GL_COLOR_BUFFER_BIT );
}


SSWPROC.CXX

/******************************Module*Header*******************************\
* Module Name: sswproc.cxx
*
* Window procedure functions.
*
* Copyright 1996 - 1998 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>

#include "ssintrnl.hxx"
#include "sswproc.hxx"
#include "palette.hxx"
#include "clear.hxx"

// forward declarations of internal functions

static void ss_TimerProc();
LONG SS_ScreenSaverProc(HWND, UINT, WPARAM, LPARAM);

LONG
FullScreenPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

static void
ssw_RelayMessageToChildren( PSSW pssw, UINT msg, WPARAM wParam, LPARAM lParam);

static void ssw_RealizePalette( PSSW pssw, BOOL bBackground );
static void ssw_DeletePalette( PSSW pssw );

/**************************************************************************\
* ScreenSaverProc
*
* Processes messages for the top level screen saver window.
*
* Unhandled msgs are sent to DefScreenSaverProc
\**************************************************************************/

LONG
ScreenSaverProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static BOOL bInited = FALSE;
    static UINT idTimer = 0;
    // Draw timer time-out interval
#ifdef SS_DEBUG
    static UINT uiTimeOut = 2;  // Let it rip !
#else
    static UINT uiTimeOut = 16; // Cap at ~60 fps
#endif
    static BOOL bSuspend = FALSE;
#ifdef SS_WIN95_TIMER_HACK
    static BOOL bIdle = FALSE;
#endif
    PSSW pssw;

    switch (message)
    {
        case WM_CREATE:
        case WM_ERASEBKGND:
        case SS_WM_INITGL:
            return SS_ScreenSaverProc( hwnd, message, wParam, lParam);

        case WM_ACTIVATE:
            if ( LOWORD(wParam) == WA_INACTIVE ) {
                SS_DBGMSG( "Main_Proc: WM_ACTIVATE inactive\n" );
                gpss->bInForeground = FALSE;
            } else {
                SS_DBGMSG( "Main_Proc: WM_ACTIVATE active\n" );
                gpss->bInForeground = TRUE;
            }

            // fall thru

        case WM_QUERYNEWPALETTE:
        case WM_PALETTECHANGED:
        case WM_SYSCOLORCHANGE:
        case SS_WM_PALETTE:
            return( MainPaletteManageProc( hwnd, message, wParam, lParam ) );

        case SS_WM_START:
            SS_DBGMSG( "Main_Proc: SS_WM_START\n" );
            // This is the main GL startup point. The global animation timer
            // is started, and SS_WM_START is relayed to the window chain.

// for 'delayed background paint' problem in preview mode - in
// floater type ss's, the main window gets this delayed bg paint thing that
// makes the floater obvious.  By relenquishing our time slice here, the
// problem goes away...
            if( gpss->type == SS_TYPE_PREVIEW ) {
                Sleep(0);
            }

            // Initialize the animation timer - it should start up once we
            // return from here

            idTimer = 1;
            SetTimer(hwnd, idTimer, uiTimeOut, 0);

#ifdef SS_DEBUG
            // Start timer for calculating update rate
            gpss->timer.Start();
#endif
            if( !bInited )
                bInited = TRUE;

            // Process SS_WM_START for the window chain
            return SS_ScreenSaverProc( hwnd, message, wParam, lParam);

#ifdef SS_WIN95_TIMER_HACK
        case SS_WM_IDLE :
            if( wParam == SS_IDLE_ON )
                bIdle = TRUE;
            else if( wParam == SS_IDLE_OFF )
                bIdle = FALSE;
            break;
#endif

        case WM_DESTROY:
            if (idTimer) {
                KillTimer(hwnd, idTimer);
                idTimer = 0;
            }

            // Destroy any children of the top level window

            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            PSSW psswChild;
            psswChild = pssw->psswChildren;

            while( psswChild ) {
                if( psswChild->hwnd )
                    DestroyWindow( psswChild->hwnd );
                psswChild = pssw->psswSibling;
            }

            // Handle any palette stuff

// Before deleting this top level window, we need to use its
// still-valid dc to things like restore SystemPaletteUse mode.  Ideally this
// should be done after ~SSW has dumped GL, but ~SSW also releases the DC.
// If this is a problem, we can create a new function SSW::DeleteGL that just
// gets rid of the GL part

            if( gpss->pssPal )
                ssw_DeletePalette( pssw );

            // Dump the main pssw
            delete pssw;

            // All pssw's have now been deleted - remove global ptr to top of
            // window chain.
            gpss->psswMain = NULL;

            PostQuitMessage(0);
            return 0;

        case SS_WM_CLOSING:
            // This message is sent when the screen saver receives a WM_CLOSE
            // msg, *after* any password protection routines.
            // For now, only sent in /s mode
            SS_DBGMSG( "Main_Proc: SS_WM_CLOSING\n" );

            if( gpss->bResSwitch ) {
                // pssw->GdiClear();
                // mf: untested for child window case
                // Restore previous display settings
                // Note that this is also checked for in ~SCRNSAVE, in
                // case this message is not hit.
                ChangeDisplaySettings(NULL, CDS_FULLSCREEN);
                gpss->bResSwitch = FALSE;
            }
            return 0;

        case WM_SETFOCUS:
            SS_DBGMSG( "Main_Proc: WM_FOCUS\n" );
            // this catches some of the win95 passwd dialog problems, where
            // we don't get repaint msgs when dialogs end
            if( ss_fOnWin95() && ss_fFullScreenMode() ) {
                pssw = gpss->sswTable.PsswFromHwnd( hwnd );
                pssw->Repaint( FALSE );
            }
            break;

#if DBG
        case WM_SHOWWINDOW:
            SS_DBGMSG( "Main_Proc: WM_SHOWWINDOW\n" );
            break;
#endif

        case WM_PAINT:
            SS_DBGMSG( "Main_Proc: WM_PAINT\n" );

            pssw = gpss->sswTable.PsswFromHwnd( hwnd );
            pssw->Repaint( TRUE );

#ifdef SS_DO_PAINT
            // We do the painting rather than letting the system do it
            hdc = BeginPaint(hwnd, &ps);

            // This is case where bg brush is NULL and we have to do repaint
            // We only do it after bInited, as this will avoid the first
            // WM_PAINT for the entire window.
            if( bInited )
                DrawGdiRect( hdc, gpss->hbrBg, &ps.rcPaint );
            EndPaint(hwnd, &ps);
#endif // SS_DO_PAINT

#ifdef SS_DELAYED_START_KLUGE
            if( !bInited && SS_DELAY_START(gpss->type) ) {
                bInited = TRUE;

                // Do initial GL configuration
                PostMessage( hwnd, SS_WM_INITGL, 0, 0 );
                // Start drawing
                PostMessage( hwnd, SS_WM_START, 0, 0 );
            }
#endif // SS_DELAYED_START_KLUGE

            if( pssw->iSubWindow ) {
                // If this window has sub windows, mark the bg for validation,
                // since for hardware double buffered schemes, Swapbuffers
                // may swap in ugly garbage.
                pssw->bValidateBg = TRUE;
            }

#ifdef SS_DO_PAINT
            return 0; // painting has been handled by us
#endif // SS_DO_PAINT

            break;

        case WM_SIZE:
            // Suspend drawing if minimized
            if( wParam == SIZE_MINIMIZED )
                bSuspend = TRUE;
            else  // either SIZE_RESTORED or SIZE_MAXIMIZED
                bSuspend = FALSE;

            return SS_ScreenSaverProc( hwnd, message, wParam, lParam);

        case WM_MOVE:
            SS_DBGMSG( "Main_Proc: WM_MOVE\n" );
            // See note for WM_PAINT for subWindows
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            //mf: kluge for non-floater workaround : see above
            if( pssw->iSubWindow )
                pssw->bValidateBg = TRUE;

            break;

        case WM_TIMER:
            if( bSuspend )
                return 0;

#ifdef SS_WIN95_TIMER_HACK
            if( bIdle ) {
                // We are in an idle state, and don't want to flood the queue
                // with WM_TIMER mesages.  So we kill the timer, do our
                // drawing, then start another timer.

                // Kill current timer
                if (idTimer)
                    KillTimer(hwnd, idTimer);
                else
                    // unlikely, but what the hay
                    return 0;
            }
#endif

            ss_TimerProc();

#ifdef SS_WIN95_TIMER_HACK
            if( bIdle ) {
                // Start another animation timer after we've done drawing
                SetTimer(hwnd, idTimer, uiTimeOut, 0);
            }
#endif
            return 0;
    }

    return DefScreenSaverProc(hwnd, message, wParam, lParam);
}

/**************************************************************************\
* SS_ScreenSaverProc
*
* Wndproc for child windows, and some messages from top-level window
*
* Unhandled msgs are sent to DefWindowProc
\**************************************************************************/

LONG
SS_ScreenSaverProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    PSSW pssw;

    switch (message)
    {
        case WM_CREATE:
            SS_DBGMSG1( "SS_Proc: WM_CREATE for 0x%x\n", hwnd );

            pssw = (PSSW) ( ((LPCREATESTRUCT)lParam)->lpCreateParams );
            gpss->sswTable.Register( hwnd, pssw );

            pssw->size.width  = ((LPCREATESTRUCT)lParam)->cx;
            pssw->size.height = ((LPCREATESTRUCT)lParam)->cy;
            pssw->hwnd = hwnd;
            break;

        case SS_WM_INITGL:
            SS_DBGMSG1( "SS_Proc: SS_WM_INITGL for 0x%x\n", hwnd );

            pssw = gpss->sswTable.PsswFromHwnd( hwnd );
            pssw->InitGL();

            break;

#ifdef SS_MULTI_WINDOW_TIMERS
// Enable this section and fill it in if windows start their own animation
// timers.  For now, there is just one timer on the main window.
        case SS_WM_START:
            SS_DBGMSG1( "SS_Proc: SS_WM_START for 0x%x\n", hwnd );

            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            // Send SS_WM_START to any children of this window
            ssw_RelayMessageToChildren( pssw, SS_WM_START, 0, 0 );

            // Nothing really to do here yet...

            break;
#endif

        case SS_WM_PALETTE:
            return( MainPaletteManageProc( hwnd, message, wParam, lParam ) );

        case WM_DESTROY:
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            SS_DBGMSG1( "SS_Proc: WM_DESTROY for 0x%x\n", hwnd );
            // Kill off any children of this window first

            PSSW psswChild;
            psswChild = pssw->psswChildren;

            while( psswChild ) {
                if( psswChild->hwnd )
                    DestroyWindow( psswChild->hwnd );
                else
                    delete psswChild;
                psswChild = pssw->psswSibling;
            }
            // Delete the pssw - this does all necessary cleanup

            delete pssw;
            break;

        case WM_ERASEBKGND:
            SS_DBGMSG1( "SS_Proc: WM_ERASEBKGRND for 0x%x\n", hwnd );
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );
            pssw->Repaint( FALSE );
            // Don't process this message
            return DefWindowProc(hwnd, message, wParam, lParam);

        case WM_PAINT:
            // We get this msg every time window moves, since SWP_NOCOPYBITS is
            // specified with the window move.
            hdc = BeginPaint(hwnd, &ps);
            EndPaint(hwnd, &ps);
            break;

        case WM_SIZE:
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );
            pssw->Resize( LOWORD(lParam), HIWORD(lParam) );
            break;

        // these msg's are never received by the child window ?
        case WM_ACTIVATE:
        case WM_QUERYNEWPALETTE:
        case WM_PALETTECHANGED:
            return( MainPaletteManageProc( hwnd, message, wParam, lParam ) );

        case WM_SYSCOMMAND:
        case WM_SETCURSOR:
        case WM_ACTIVATEAPP:
        case WM_MOUSEMOVE:
        case WM_LBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDOWN:
        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );
            return DefScreenSaverProc(pssw->psswParent->hwnd, message, wParam, lParam);

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

#ifdef SS_DEBUG
/**************************************************************************\
* PrintUpdateRate
*
* Print number of updates per second in the title bar
*
\**************************************************************************/

static void
PrintUpdateRate( double elapsed, long updateCount )
{
    char buf[100];
    double updateRate;

    if( elapsed == 0.0 )
        updateRate = 0.0;
    else
        updateRate = updateCount / elapsed;

    sprintf( buf, "Updates per second = %4.1f", updateRate );
    SendMessage(gpss->psswMain->hwnd, WM_SETTEXT, 0, (LPARAM)buf);
}
#endif

/**************************************************************************\
* ss_TimerProc
*
* Every time a timer event fires off, update all active windows
*
\**************************************************************************/

static void
ss_TimerProc()
{
    static int busy = FALSE;
#ifdef SS_DEBUG
    static long updateCount = 0;
    static double updateInterval = 2.0;
    SS_TIMER *pTimer = &gpss->timer;
#endif

    if (busy)
        return;
    busy = TRUE;

    gpss->psswMain->UpdateWindow();

#ifdef SS_DEBUG
    updateCount++;
    if( gpss->bDoTiming &&
        (( (double) pTimer->ElapsedTime() ) >= updateInterval) )
    {
        double elapsed = pTimer->Stop();
        PrintUpdateRate( elapsed, updateCount );
        updateCount = 0;
        pTimer->Start();
    }
#endif

    busy = FALSE;
}

/**************************************************************************\
* RelayMessageToChildren
*
* Pass along the message to any child windows
\**************************************************************************/

static void
ssw_RelayMessageToChildren( PSSW pssw, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PSSW psswChild = pssw->psswChildren;

    while( psswChild ) {
        if( psswChild->hwnd )
            SendMessage( psswChild->hwnd, msg, wParam, lParam );
        psswChild = psswChild->psswSibling;
    }
}

/**************************************************************************\
* UpdateDIBColorTable
*
* Wrapper for SSDIB_UpdateColorTable.
*
* This controls the hPal parameter for SSDIB_UpdateColorTable.
*
\**************************************************************************/

void
ssw_UpdateDIBColorTable( HDC hdcbm, HDC hdcwin )
{
    SS_PAL *pssPal = gpss->pssPal;

    if( !pssPal )
        return;
    HPALETTE hpal =  pssPal->hPal;

    SSDIB_UpdateColorTable( hdcbm, hdcwin, hpal );
}

/**************************************************************************\
* RealizePalette
*
\**************************************************************************/

static void
ssw_RealizePalette( PSSW pssw, BOOL bBackground )
{
    // assumed pssPal valid if get here
    SS_PAL *pssPal = gpss->pssPal;

    if( !pssw->hrc ) {
        // Can assume this window doesn't need to worry about palettes, but
        // if any of its children are subWindows, it will have to take care
        // of it *for* them.
        if( ! pssw->iSubWindow )
            return; // no hrc and no subWindow children
    }
    pssPal->Realize( pssw->hwnd, pssw->hdc, bBackground );

    if( pssw->pStretch && pssw->pStretch->ssbm.hdc ) {
        SS_BITMAP *pssbm = &pssw->pStretch->ssbm;
        ssw_UpdateDIBColorTable( pssbm->hdc, pssw->hdc );
    }
}

/**************************************************************************\
* ssw_DeletePalette
*
\**************************************************************************/

static void
ssw_DeletePalette( PSSW pssw )
{
    SS_PAL *pssPal = gpss->pssPal;

    if( pssPal->bTakeOver ) {
        // We took over the system palette - make a note of this
        // for any special ss termination conditions.
        gpss->flags |= SS_PALETTE_TAKEOVER;
    }
    pssPal->SetDC( pssw->hdc );
    delete pssPal;
    gpss->pssPal = NULL;
}

/**************************************************************************\
* PaletteManage Procs
\**************************************************************************/

/* palette related msgs's:
    - WM_ACTIVATE:
        The WM_ACTIVATE message is sent when a window is being activated or
        deactivated. This message is sent first to the window procedure of
        the top-level window being deactivated; it is then sent to the
        window procedure of the top-level window being activated.

    - WM_QUERYNEWPALETTE:
        The WM_QUERYNEWPALETTE message informs a window that it is about
        to receive the keyboard focus, giving the window the opportunity
        to realize its logical palette when it receives the focus.

        If the window realizes its logical palette, it must return TRUE;
        otherwise, it must return FALSE.

    - WM_PALETTECHANGED:
        The WM_PALETTECHANGED message is sent to all top-level and overlapped
        windows after the window with the keyboard focus has realized its
        logical palette, thereby changing the system palette. This message
        enables a window that uses a color palette but does not have the
        keyboard focus to realize its logical palette and update its client
        area.

        This message must be sent to all top-level and overlapped windows,
        including the one that changed the system palette. If any child
        windows use a color palette, this message must be passed on to them
        as well.
        To avoid creating an infinite loop, a window that receives this
        message must not realize its palette, unless it determines that
        wParam does not contain its own window handle.

    - WM_SYSCOLORCHANGE:
        The WM_SYSCOLORCHANGE message is sent to all top-level windows when
        a change is made to a system color setting.

    - SS_WM_PALETTE:
        Internal msg.  Uses:
        - In fullscreen mode, we send this from Main wndproc to main
          window's children on WM_ACTIVATE.
        - When this is received in SS_ScreenSaverProc, if fullscreen,
          it does:
                    UnrealizeObject( pssPal->hPal );
                    RealizePalette( hdc );
          otherwise, it is passed to PaletteManageProc, where
          Realize is called (for 'floater' windows to realize
          their palettes).
        - It is also sent by DelayPaletteRealization() when it can't get
          the system palette.

*/

/**************************************************************************\
* MainPaletteManageProc
*
* Top-level palette management proc.

* Returns immediately if no palette set - otherwise calls through
* paletteManageProc function pointer
*
\**************************************************************************/

LONG
MainPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if( !gpss->pssPal )
        // No palette management required
        return 0;

    // else call approppriate palette manage proc
    return (*gpss->pssPal->paletteManageProc)(hwnd, message, wParam, lParam);
}

LONG
NullPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return 0;
}

/**************************************************************************\
* FullScreenPaletteManageProc
*
* Processes messages relating to palette management in full screen mode.
*
\**************************************************************************/

LONG
FullScreenPaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    SS_PAL *pssPal;
    PSSW pssw;

    switch (message)
    {
        case WM_ACTIVATE:

#if SS_DEBUG
            if ( LOWORD(wParam) == WA_INACTIVE )
                SS_DBGMSG1( "FullScreen_PMProc: WM_ACTIVATE : inactive for 0x%x\n",
                           hwnd );
            else
                SS_DBGMSG1( "FullScreen_PMProc: WM_ACTIVATE : active for 0x%x\n",
                           hwnd );
#endif

            // !! This msg is only sent to main top level window
            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            pssPal = gpss->pssPal;
            if ( pssPal->bUseStatic ) {
                HDC hdc = pssw->hdc; // hdc *always* valid for top-level pssw
                // Note: wParam = 0 when window going *inactive*
                SetSystemPaletteUse( hdc, wParam ? SYSPAL_NOSTATIC
                                                : pssPal->uiOldStaticUse);
            }

            // Send SS_WM_PALETTE msg to main window
            SendMessage( hwnd, SS_WM_PALETTE, wParam, 0);
            break;

        case SS_WM_PALETTE:

            SS_DBGMSG1( "FullScreen_PMProc: SS_WM_PALETTE for 0x%x\n", hwnd );

            pssw = gpss->sswTable.PsswFromHwnd( hwnd );

            ssw_RelayMessageToChildren( pssw, SS_WM_PALETTE, wParam, 0 );

            HDC hdc;
            if( hdc = pssw->hdc )
            {
                pssPal = gpss->pssPal;

// this should call thru ssw_RealizePalette for bitmap case ? (for now
// don't need to, since we take over palette...)
                // This resets the logical palette, causing remapping of
                // logical palette to system palette
                UnrealizeObject( pssPal->hPal );
                RealizePalette( hdc );
            }
            break;
    }
    return 0;
}

/**************************************************************************\
* PaletteManageProc
*
* Processes messages relating to palette management for the general case.
*
* Note: this msg handling strategy is based loosely on the tk, so any changes
* there should be reflected here
\**************************************************************************/

LONG
PaletteManageProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // One global palette for all windows
    if( !gpss->pssPal )
        return 0;

    switch (message)
    {
      case WM_ACTIVATE:

        SendMessage( hwnd, SS_WM_PALETTE, gpss->bInBackground(), 0);

        // Allow DefWindowProc() to finish the default processing (which
        // includes changing the keyboard focus).

        break;

      case WM_QUERYNEWPALETTE:

        SS_DBGMSG1( "Palette_Proc: WM_QUERYNEWPALETTE for 0x%x\n", hwnd );
        // We don't actually realize palette here (we do it at WM_ACTIVATE
        // time), but we need the system to think that we have so that a
        // WM_PALETTECHANGED message is generated.

        // This is the only msg preview mode gets wrt palettes !
        if( !ss_fPreviewMode() )
            return (1);

        // We are in preview mode - realize the palette
        SendMessage( hwnd, SS_WM_PALETTE, gpss->bInBackground(), 0);
        break;

      case WM_PALETTECHANGED:

        SS_DBGMSG1( "Palette_Proc: WM_PALETTECHANGED for 0x%x\n", hwnd );
        // Respond to this message only if the window that changed the palette
        // is not this app's window.

        // We are not the foreground window, so realize palette in the
        // background.  We cannot call Realize to do this because
        // we should not do any of the gbUseStaticColors processing while
        // in background.

        // Actually, we *can* be the fg window, so don't realize if
        // we're in foreground

        if( (hwnd != (HWND) wParam) && gpss->bInBackground() )
            SendMessage( hwnd, SS_WM_PALETTE, TRUE, 0);

        break;

      case WM_SYSCOLORCHANGE:

        // If the system colors have changed and we have a palette
        // for an RGB surface then we need to recompute the static
        // color mapping because they might have been changed in
        // the process of changing the system colors.

          SS_DBGMSG1( "Palette_Proc: WM_SYSCOLORCHANGE for 0x%x\n", hwnd );
          gpss->pssPal->ReCreateRGBPalette();
          SendMessage( hwnd, SS_WM_PALETTE, gpss->bInBackground(), 0);
          break;

      case SS_WM_PALETTE:

          SS_DBGMSG2( "Palette_Proc: SS_WM_PALETTE for 0x%x, bg = %d\n",
                          hwnd, wParam );

          // Realize palette for this window and its children
          // wParam = TRUE if realize as bg

          PSSW pssw = gpss->sswTable.PsswFromHwnd( hwnd );
          ssw_RelayMessageToChildren( pssw, message, wParam, lParam );
          ssw_RealizePalette( pssw, wParam );
          break;
    }
    return 0;
}

/******************************Public*Routine******************************\
* GLScreenSaverConfigureDialog
*
* This is a wrapper for ScreenSaverConfigureDialog, which is the main dialog
* proc for all the GL screen savers in config mode.
*
* We call the client's ss_ConfigInit() routine on the first WM_PAINT, since
* the dialog will have focus at this point (can realize palette) and all
* buttons should have been created.
\**************************************************************************/

BOOL
GLScreenSaverConfigureDialog( HWND hDlg, UINT msg, WPARAM wParam,
                              LPARAM lParam )
{
    static BOOL bInited = 0;

    switch( msg ) {
        case WM_INITDIALOG :
          {
            SS_DBGMSG( "GLScreenSaverConfigureDialog: WM_INITDIALOG\n" );
            // Create wrapper pssw for the dialog box

            PSSW pssw;
            pssw = new SSW( NULL,       // ssw parent
                            hDlg
                          );
            SS_ASSERT( pssw, "GLScreenSaverConfigureDialog : alloc failure for psswMain\n" );

            gpss->psswMain = pssw;

            // Load any resource strings common to all the dialogs
            BOOL bStringsLoaded = ss_LoadTextureResourceStrings();
            // If this doesn't work, things are seriously wrong and we
            // shouldn't continue
            SS_ASSERT( bStringsLoaded, "GLScreenSaverConfigureDialog : failure loading common resource strings\n" );
          }
          break;

        case WM_PAINT:

            if( !bInited ) {
                // Call client's ss_ConfigInit()
                if( !ss_ConfigInit( hDlg ) ) {
                    SS_WARNING( "ConfigInit failed\n" );
                    // Send WM_CLOSE to the dialog - this will enable any
                    // cleanup code to be called by the client
                    SendMessage( hDlg, WM_CLOSE, 0, 0l );
                }
                bInited = TRUE;
            }
            break;

        case WM_ACTIVATE:
        case WM_QUERYNEWPALETTE:
        case WM_PALETTECHANGED:
        case SS_WM_PALETTE:
            return( MainPaletteManageProc( hDlg, msg, wParam, lParam ) );

    }
    return ScreenSaverConfigureDialog( hDlg, msg, wParam, lParam );
}

/******************************Public*Routine******************************\
* SSW_TABLE constructor
*
\**************************************************************************/

SSW_TABLE::SSW_TABLE()
{
    nEntries = 0;
}

/******************************Public*Routine******************************\
* Register
*
* Register a HWND/PSSW pair.
\**************************************************************************/

void
SSW_TABLE::Register( HWND hwnd, PSSW pssw )
{
    SSW_TABLE_ENTRY *pEntry;

    // Check if already in table
    if( PsswFromHwnd( hwnd ) )
        return;

    // put hwnd/pssw pair in the table
    pEntry = &sswTable[nEntries];
    pEntry->hwnd = hwnd;
    pEntry->pssw = pssw;
    nEntries++;
}

/******************************Public*Routine******************************\
* PsswFromHwnd
*
* Return PSSW for the HWND

\**************************************************************************/

PSSW
SSW_TABLE::PsswFromHwnd( HWND hwnd )
{
    int count = nEntries;
    SSW_TABLE_ENTRY *pEntry = sswTable;

    while( count-- ) {
        if( pEntry->hwnd == hwnd )
            return pEntry->pssw;
        pEntry++;
    }
    return NULL;
}

/******************************Public*Routine******************************\
* Remove
*
* Remove HWND/PSSW entry from table
\**************************************************************************/

BOOL
SSW_TABLE::Remove( HWND hwnd )
{
    SSW_TABLE_ENTRY *pEntry = sswTable;

    // Locate the hwnd/pssw pair

    for( int count = 0 ; count < nEntries ; count++, pEntry++ ) {
        if( pEntry->hwnd == hwnd )
            break;
    }

    if( count == nEntries )
        // couldn't find it in the table
        return FALSE;

    // Remove entry / shuffle up other entries
    for( int i = count; i < nEntries-1; i ++ ) {
        sswTable[i] = sswTable[i+1];
    }

    nEntries--;
    return TRUE;
}


TEXTURE.C

/******************************Module*Header*******************************\
* Module Name: texture.c
*
* Texture handling functions
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>
#include <scrnsave.h>
#include <commdlg.h>
#include <GL/gl.h>

#include "scrnsave.h"  // for hMainInstance
#include "sscommon.h"
#include "texture.h"

static int ProcessTexture( TEXTURE *pTex );
static int ProcessTkTexture( AUX_RGBImageRec *image, TEXTURE *pTex );
static int VerifyTextureFile( TEXFILE *pTexFile );
static int GetTexFileType( TEXFILE *pTexFile );

static TEX_STRINGS gts = {0};
BOOL gbTextureObjects = FALSE;
static BOOL gbPalettedTexture = FALSE;
static PFNGLCOLORTABLEEXTPROC pfnColorTableEXT;
static PFNGLCOLORSUBTABLEEXTPROC pfnColorSubTableEXT;

static BOOL gbEnableErrorMsgs = FALSE;

/******************************Public*Routine******************************\
*
* ss_LoadTextureResourceStrings
*
* Load various messages and strings that are used in processing textures,
* into global TEX_STRINGS structure
*
\**************************************************************************/

BOOL
ss_LoadTextureResourceStrings()
{
    LPTSTR pszStr;

    // title for choose texture File dialog
    LoadString(hMainInstance, IDS_TEXTUREDIALOGTITLE, gts.szTextureDialogTitle,
                GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_BMP, gts.szBmp, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_DOTBMP, gts.szDotBmp, GEN_STRING_SIZE);
    LoadString(hMainInstance, IDS_TEXTUREFILTER, gts.szTextureFilter,
                GEN_STRING_SIZE);
    pszStr = &gts.szTextureFilter[lstrlen(gts.szTextureFilter)+1];
    LoadString(hMainInstance, IDS_STARDOTBMP, pszStr, GEN_STRING_SIZE);
    pszStr += lstrlen(pszStr);
    *pszStr++ = TEXT(';');
    LoadString(hMainInstance, IDS_STARDOTRGB, pszStr, GEN_STRING_SIZE);
    pszStr += lstrlen(pszStr);
    pszStr++;
    *pszStr = TEXT('\0');

    LoadString(hMainInstance, IDS_WARNING, gts.szWarningMsg, MAX_PATH);
    LoadString(hMainInstance, IDS_SELECT_ANOTHER_BITMAP,
                gts.szSelectAnotherBitmapMsg, MAX_PATH );

    LoadString(hMainInstance, IDS_BITMAP_INVALID,
                gts.szBitmapInvalidMsg, MAX_PATH );
    LoadString(hMainInstance, IDS_BITMAP_SIZE,
                gts.szBitmapSizeMsg, MAX_PATH );

    // assumed here that all above calls loaded properly
    return TRUE;
}

/******************************Public*Routine******************************\
*
*
\**************************************************************************/

void
ss_DisableTextureErrorMsgs()
{
    gbEnableErrorMsgs = FALSE;
}

/******************************Public*Routine******************************\
*
* ss_LoadBMPTextureFile
*
* Loads a BMP file and prepares it for GL usage
*
\**************************************************************************/

int
ss_LoadBMPTextureFile( LPCTSTR pszBmpfile, TEXTURE *pTex )
{
    AUX_RGBImageRec *image = (AUX_RGBImageRec *) NULL;

#ifdef UNICODE
    image = auxDIBImageLoad( (LPCWSTR) pszBmpfile);
#else
    image = auxDIBImageLoad( (LPCSTR) pszBmpfile);
#endif

    if( !image )  {
        return 0;
    }
    return ProcessTkTexture( image, pTex );
}

/******************************Public*Routine******************************\
*
* ss_LoadTextureFile
*
* Loads a BMP file and prepares it for GL usage
*
\**************************************************************************/

int
ss_LoadTextureFile( TEXFILE *pTexFile, TEXTURE *pTex )
{
    AUX_RGBImageRec *image = (AUX_RGBImageRec *) NULL;
    LPTSTR pszBmpfile = pTexFile->szPathName;
    int type;

    // Verify file / set type

    if( !(type = VerifyTextureFile( pTexFile )) )
        return 0;

    if( type == TEX_BMP ) {
#ifdef UNICODE
        image = auxDIBImageLoad( (LPCWSTR) pszBmpfile);
#else
        image = auxDIBImageLoad( (LPCSTR) pszBmpfile);
#endif
    } else {
#ifdef UNICODE
        image = auxRGBImageLoad( (LPCWSTR) pszBmpfile);
#else
        image = auxRGBImageLoad( (LPCSTR) pszBmpfile);
#endif
    }

    if( !image )  {
        return 0;
    }
    return ProcessTkTexture( image, pTex );
}

/******************************Public*Routine******************************\
*
* ss_LoadTextureResource
*
* Loads a BMP or RGB texture resource and prepares it for GL usage
*
\**************************************************************************/

int
ss_LoadTextureResource( TEX_RES *pTexRes, TEXTURE *pTex )
{
    HMODULE ghmodule;
    HRSRC hr;
    HGLOBAL hg;
    LPVOID pv;
    LPCTSTR lpType;
    BOOL fLoaded = FALSE;

    ghmodule = GetModuleHandle(NULL);
    switch(pTexRes->type)
    {
    case TEX_RGB:
        lpType = MAKEINTRESOURCE(RT_RGB);
        break;
    case TEX_BMP:
        lpType = MAKEINTRESOURCE(RT_MYBMP);
        break;
    case TEX_A8:
        lpType = MAKEINTRESOURCE(RT_A8);
        break;
    }

    hr = FindResource(ghmodule, MAKEINTRESOURCE(pTexRes->name), lpType);
    if (hr == NULL)
    {
        goto EH_NotFound;
    }
    hg = LoadResource(ghmodule, hr);
    if (hg == NULL)
    {
        goto EH_FreeResource;
    }
    pv = (PSZ)LockResource(hg);
    if (pv == NULL)
    {
        goto EH_FreeResource;
    }

    switch(pTexRes->type)
    {
    case TEX_RGB:
        fLoaded = ss_RGBImageLoad( pv, pTex );
        break;
    case TEX_BMP:
        fLoaded = ss_DIBImageLoad( pv, pTex );
        break;
    case TEX_A8:
        fLoaded = ss_A8ImageLoad( pv, pTex );
        break;
    }

 EH_FreeResource:
    FreeResource(hr);
 EH_NotFound:

    if( !fLoaded )  {
        return 0;
    }

    return ProcessTexture( pTex );
}

/******************************Public*Routine******************************\
*
* ValidateTextureSize
*
* - Scales the texture to powers of 2
*
\**************************************************************************/

static BOOL
ValidateTextureSize( TEXTURE *pTex )
{
    double xPow2, yPow2;
    int ixPow2, iyPow2;
    int xSize2, ySize2;
    GLint glMaxTexDim;

    if( (pTex->width <= 0) || (pTex->height <= 0) ) {
        SS_WARNING( "ValidateTextureSize : invalid texture dimensions\n" );
        return FALSE;
    }

    pTex->origAspectRatio = (float) pTex->height / (float) pTex->width;

    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);
    if( glMaxTexDim <= 0 )
        return FALSE;

    if( pTex->format != GL_COLOR_INDEX ) {

        // We limit the max dimension here for performance reasons
        glMaxTexDim = min(256, glMaxTexDim);

        if (pTex->width <= glMaxTexDim)
            xPow2 = log((double)pTex->width) / log((double)2.0);
        else
            xPow2 = log((double)glMaxTexDim) / log((double)2.0);

        if (pTex->height <= glMaxTexDim)
            yPow2 = log((double)pTex->height) / log((double)2.0);
        else
            yPow2 = log((double)glMaxTexDim) / log((double)2.0);

        ixPow2 = (int)xPow2;
        iyPow2 = (int)yPow2;

        // Always scale to higher nearest power
        if (xPow2 != (double)ixPow2)
            ixPow2++;
        if (yPow2 != (double)iyPow2)
            iyPow2++;

        xSize2 = 1 << ixPow2;
        ySize2 = 1 << iyPow2;

        if (xSize2 != pTex->width ||
            ySize2 != pTex->height)
        {
            BYTE *pData;

            pData = (BYTE *) malloc(xSize2 * ySize2 * pTex->components * sizeof(BYTE));
            if (!pData) {
                SS_WARNING( "ValidateTextureSize : can't alloc pData\n" );
                return FALSE;
            }

            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

            if( gluScaleImage(pTex->format, pTex->width, pTex->height,
                      GL_UNSIGNED_BYTE, pTex->data,
                      xSize2, ySize2, GL_UNSIGNED_BYTE,
                      pData) )
            {
                // glu failure
                SS_WARNING( "ValidateTextureSize : gluScaleImage failure\n" );
                return FALSE;
            }

            // set the new width,height,data
            pTex->width = xSize2;
            pTex->height = ySize2;
            free(pTex->data);
            pTex->data = pData;
        }
    } else {  // paletted texture case
        // paletted texture: must be power of 2 - but might need to enforce
        // here if not done in a8 load.  Also have to check against
        // GL_MAX_TEXTURE_SIZE.  Could then clip it to power of 2 size
    }
    return TRUE;
}

/******************************Public*Routine******************************\
*
* SetDefaultTextureParams
*
\**************************************************************************/

static void
SetDefaultTextureParams()
{
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}

/******************************Public*Routine******************************\
*
* ProcessTexture
*
* - Verifies texture size
* - Fills out TEXTURE structure with required data
* - Creates a texture object if extension exists
*
\**************************************************************************/

static int
ProcessTexture( TEXTURE *pTex )
{
    // Enforce proper texture size (power of 2, etc.)

    if( !ValidateTextureSize( pTex ) )
        return 0;

    // if texturing objects available, init the object
    if( gbTextureObjects ) {
        glGenTextures( 1, &pTex->texObj );
        glBindTexture( GL_TEXTURE_2D, pTex->texObj );

        // Default attributes for texObj
        SetDefaultTextureParams();

        glTexImage2D( GL_TEXTURE_2D, 0, pTex->components,
                      pTex->width, pTex->height, 0, pTex->format,
                      GL_UNSIGNED_BYTE, pTex->data );

        if (gbPalettedTexture && pTex->pal != NULL)
        {
            pfnColorTableEXT(GL_TEXTURE_2D, GL_RGBA, pTex->pal_size,
                             GL_BGRA_EXT, GL_UNSIGNED_BYTE, pTex->pal);
        }
    } else
        pTex->texObj = 0;

    return 1;
}

/******************************Public*Routine******************************\
*
* ProcessTkTexture
*
* Simple wrapper for ProcessTexture which fills out a TEXTURE
* from a AUX_RGBImageRec
*
* Frees the ImageRec if ProcessTexture succeeds
*
\**************************************************************************/

static int
ProcessTkTexture( AUX_RGBImageRec *image, TEXTURE *pTex )
{

    pTex->width = image->sizeX;
    pTex->height = image->sizeY;
    pTex->format = GL_RGB;
    pTex->components = 3;
    pTex->data = image->data;
    pTex->pal_size = 0;
    pTex->pal = NULL;

    if( ProcessTexture( pTex ) )
    {
        free(image);
        return 1;
    }
    else
    {
        return 0;
    }
}

/******************************Public*Routine******************************\
*
* ss_SetTexture
*
\**************************************************************************/

void
ss_SetTexture( TEXTURE *pTex )
{
    if( pTex == NULL )
        return;

    if( gbTextureObjects && pTex->texObj ) {
        glBindTexture( GL_TEXTURE_2D, pTex->texObj );
        return;
    }

    glTexImage2D( GL_TEXTURE_2D, 0, pTex->components,
                  pTex->width, pTex->height, 0, pTex->format,
                  GL_UNSIGNED_BYTE, pTex->data );

    if (gbPalettedTexture && pTex->pal != NULL)
    {
        pfnColorTableEXT(GL_TEXTURE_2D, GL_RGBA, pTex->pal_size,
                         GL_BGRA_EXT, GL_UNSIGNED_BYTE, pTex->pal);
    }
}

/******************************Public*Routine******************************\
*
* ss_CopyTexture
*
* Make a copy of a texture.
*
\**************************************************************************/

BOOL
ss_CopyTexture( TEXTURE *pTexDst, TEXTURE *pTexSrc )
{
    int size;

    if( (pTexDst == NULL) || (pTexSrc == NULL) )
        return FALSE;

    *pTexDst = *pTexSrc;

    if( gbTextureObjects && pTexSrc->texObj ) {
        glGenTextures( 1, &pTexDst->texObj );
    }

    // copy image data

    size = pTexSrc->width * pTexSrc->height;
    if( pTexSrc->components != GL_COLOR_INDEX8_EXT )
        size *= pTexSrc->components; // since data format always UNSIGNED_BYTE

    pTexDst->data = (unsigned char *) malloc( size );
    if( pTexDst->pal == NULL )
        return FALSE;
    memcpy( pTexDst->data, pTexSrc->data, size );

    // copy palette data

    if( gbPalettedTexture && pTexSrc->pal != NULL )
    {
        size = pTexSrc->pal_size*sizeof(RGBQUAD);
        pTexDst->pal = (RGBQUAD *) malloc(size);
        if( pTexDst->pal == NULL )
        {
            free(pTexDst->data);
            return FALSE;
        }
        memcpy( pTexDst->pal, pTexSrc->pal, size );
    }

    if( gbTextureObjects ) {
        glBindTexture( GL_TEXTURE_2D, pTexDst->texObj );

        // Default attributes for texObj
        SetDefaultTextureParams();

        glTexImage2D( GL_TEXTURE_2D, 0, pTexDst->components,
                      pTexDst->width, pTexDst->height, 0, pTexDst->format,
                      GL_UNSIGNED_BYTE, pTexDst->data );

        if( gbPalettedTexture && (pTexDst->pal != NULL) )
        {
            pfnColorTableEXT(GL_TEXTURE_2D, GL_RGBA, pTexDst->pal_size,
                             GL_BGRA_EXT, GL_UNSIGNED_BYTE, pTexDst->pal);
        }
    }
    return TRUE;
}

/******************************Public*Routine******************************\
*
* ss_SetTexturePalette
*
* Set a texture's palette according to the supplied index. This index
* indicates the start of the palette, which then wraps around if necessary.
* Of course this only works on paletted textures.
*
\**************************************************************************/

void
ss_SetTexturePalette( TEXTURE *pTex, int index )
{
    if( pTex == NULL )
        return;

    if( gbTextureObjects )
        ss_SetTexture( pTex );

    if( gbPalettedTexture && pTex->pal != NULL )
    {
        int start, count;

        start = index & (pTex->pal_size - 1);
        count = pTex->pal_size - start;
        pfnColorSubTableEXT(GL_TEXTURE_2D, 0, count, GL_BGRA_EXT,
                            GL_UNSIGNED_BYTE, pTex->pal + start);
        if (start != 0)
        {
            pfnColorSubTableEXT(GL_TEXTURE_2D, count, start, GL_BGRA_EXT,
                                GL_UNSIGNED_BYTE, pTex->pal);
        }
    }
}

/******************************Public*Routine******************************\
*
* SetTextureAlpha
*
* Set a constant alpha value for the texture
* Again, don't overwrite any existing 0 alpha values, as explained in
* ss_SetTextureTransparency
*
\**************************************************************************/

static void
SetTextureAlpha( TEXTURE *pTex, float fAlpha )
{
    int i;
    unsigned char *pData = pTex->data;
    RGBA8 *pColor = (RGBA8 *) pTex->data;
    BYTE bAlpha = (BYTE) (fAlpha * 255.0f);

    if( pTex->components != 4 )
        return;

    for( i = 0; i < pTex->width*pTex->height; i ++, pColor++ ) {
        if( pColor->a != 0 )
            pColor->a = bAlpha;
    }
}

/******************************Public*Routine******************************\
*
* ConvertTextureToRGBA
*
* Convert RGB texture to RGBA
*
\**************************************************************************/

static void
ConvertTextureToRGBA( TEXTURE *pTex, float fAlpha )
{
    unsigned char *pNewData;
    int count = pTex->width * pTex->height;
    unsigned char *src, *dst;
    BYTE bAlpha = (BYTE) (fAlpha * 255.0f);
    int i;

    pNewData = (unsigned char *) LocalAlloc(LMEM_FIXED, count * sizeof(RGBA8));
    if( !pNewData )
        return;

    src = pTex->data;
    dst = pNewData;
    // Note: the color ordering is ABGR, where R is lsb
    for( i = 0; i < count; i ++ ) {
        *((RGB8 *)dst) = *((RGB8 *)src);
        dst += sizeof(RGB8);
        src += sizeof(RGB8);
        *dst++ = bAlpha;
    }
    LocalFree( pTex->data );
    pTex->data = pNewData;
    pTex->components = 4;
    pTex->format = GL_RGBA;
}

/******************************Public*Routine******************************\
*
* ss_SetTextureTransparency
*
* Set transparency for a texture by adding or modifying the alpha data.
* Transparency value must be between 0.0 (opaque) and 1.0 (fully transparent)
* If the texture data previously had no alpha, add it in.
* If bSet is TRUE, make this the current texture.
*
* Note: Currently fully transparent pixels (alpha=0) will not be altered, since
* it is assumed these should be permanently transparent (could make this an
* option? - bPreserveTransparentPixels )
*
\**************************************************************************/

BOOL
ss_SetTextureTransparency( TEXTURE *pTex, float fTransp, BOOL bSet )
{
    int i;
    float fAlpha;

    if( pTex == NULL )
        return FALSE;

    SS_CLAMP_TO_RANGE2( fTransp, 0.0f, 1.0f );
    fAlpha = 1 - fTransp;

    if( pTex->format == GL_COLOR_INDEX )
    {
        // just need to modify the palette
            RGBQUAD *pPal = pTex->pal;
            BYTE bAlpha = (BYTE) (fAlpha * 255.0f);

            if( !pPal )
                return FALSE;

            for( i = 0; i < pTex->pal_size; i ++, pPal++ ) {
                if( pPal->rgbReserved != 0 )
                    pPal->rgbReserved = bAlpha;
            }

            // need to send down the new palette for texture objects
            if( gbTextureObjects && gbPalettedTexture )
            {
                glBindTexture( GL_TEXTURE_2D, pTex->texObj );
                pfnColorTableEXT(GL_TEXTURE_2D, GL_RGBA, pTex->pal_size,
                                 GL_BGRA_EXT, GL_UNSIGNED_BYTE, pTex->pal);
            }
    }
    else {
        // Need to setup new texture data
        if( pTex->components != 4 ) {
            // Make room for alpha component
            ConvertTextureToRGBA( pTex, fAlpha );
        } else {
            // Set alpha component
            SetTextureAlpha( pTex, fAlpha );
        }
        // Send down new data if texture objects
        if( gbTextureObjects )
        {
            glBindTexture( GL_TEXTURE_2D, pTex->texObj );
            glTexImage2D( GL_TEXTURE_2D, 0, pTex->components,
                          pTex->width, pTex->height, 0, pTex->format,
                          GL_UNSIGNED_BYTE, pTex->data );
        }
    }

    if( bSet )
        ss_SetTexture( pTex );

    return TRUE;
}

/******************************Public*Routine******************************\
*
* ss_DeleteTexture
*
\**************************************************************************/

void
ss_DeleteTexture( TEXTURE *pTex )
{
    if( pTex == NULL )
        return;

    if( gbTextureObjects && pTex->texObj ) {
        glDeleteTextures( 1, &pTex->texObj );
        pTex->texObj = 0;
    }
    if (pTex->pal != NULL)
    {
        free(pTex->pal);
    }
    if( pTex->data )
        free( pTex->data );
}

/******************************Public*Routine******************************\
*
* ss_TextureObjectsEnabled
*
* Returns BOOL set by ss_QueryGLVersion (Texture Objects only supported on
* GL v.1.1 or greater)
*
\**************************************************************************/

BOOL
ss_TextureObjectsEnabled( void )
{
    return gbTextureObjects;
}

/******************************Public*Routine******************************\
*
* ss_PalettedTextureEnabled
*
* Returns result from ss_QueryPalettedTextureEXT
*
\**************************************************************************/

BOOL
ss_PalettedTextureEnabled( void )
{
    return gbPalettedTexture;
}

/******************************Public*Routine******************************\
*
* ss_QueryPalettedTextureEXT
*
* Queries the OpenGL implementation to see if paletted texture is supported
* Typically called once at app startup.
*
\**************************************************************************/

BOOL
ss_QueryPalettedTextureEXT( void )
{
    PFNGLGETCOLORTABLEPARAMETERIVEXTPROC pfnGetColorTableParameterivEXT;
    int size;

    pfnColorTableEXT = (PFNGLCOLORTABLEEXTPROC)
        wglGetProcAddress("glColorTableEXT");
    if (pfnColorTableEXT == NULL)
        return FALSE;
    pfnColorSubTableEXT = (PFNGLCOLORSUBTABLEEXTPROC)
        wglGetProcAddress("glColorSubTableEXT");
    if (pfnColorSubTableEXT == NULL)
        return FALSE;

    // Check color table size
    pfnGetColorTableParameterivEXT = (PFNGLGETCOLORTABLEPARAMETERIVEXTPROC)
        wglGetProcAddress("glGetColorTableParameterivEXT");
    if (pfnGetColorTableParameterivEXT == NULL)
        return FALSE;
    // For now, the only paletted textures supported in this lib are TEX_A8,
    // with 256 color table entries.  Make sure the device supports this.
    pfnColorTableEXT(GL_PROXY_TEXTURE_2D, GL_RGBA, 256,
                     GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL );
    pfnGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D,
                                    GL_COLOR_TABLE_WIDTH_EXT, &size );
    if( size != 256 )
        // The device does not support a color table size of 256, so we don't
        // enable paletted textures in general.
        return FALSE;

    return gbPalettedTexture=TRUE;
}

/******************************Public*Routine******************************\
*
* ss_VerifyTextureFile
*
* Validates texture bmp or rgb file, by checking for valid pathname and
* correct format.
*
\**************************************************************************/

BOOL
ss_VerifyTextureFile( TEXFILE *ptf )
{
    // Make sure the selected texture file is OK.

    TCHAR szFileName[MAX_PATH];
    PTSTR pszString;
    TCHAR szString[MAX_PATH];

    lstrcpy(szFileName, ptf->szPathName);

    if ( SearchPath(NULL, szFileName, NULL, MAX_PATH,
                     ptf->szPathName, &pszString)
       )
    {
        ptf->nOffset = pszString - ptf->szPathName;
        return TRUE;
    }
    else
    {
        lstrcpy(ptf->szPathName, szFileName);    // restore

        if( !ss_fOnWin95() && gbEnableErrorMsgs )
        {
            wsprintf(szString, gts.szSelectAnotherBitmapMsg, ptf->szPathName);
            MessageBox(NULL, szString, gts.szWarningMsg, MB_OK);
        }
        return FALSE;
    }
}

/******************************Public*Routine******************************\
*
* ss_SelectTextureFile
*
* Use the common dialog GetOpenFileName to get the name of a bitmap file
* for use as a texture.  This function will not return until the user
* either selects a valid bitmap or cancels.  If a valid bitmap is selected
* by the user, the global array szPathName will have the full path
* to the bitmap file and the global value nOffset will have the
* offset from the beginning of szPathName to the pathless file name.
*
* If the user cancels, szPathName and nOffset will remain
* unchanged.
*
\**************************************************************************/

BOOL
ss_SelectTextureFile( HWND hDlg, TEXFILE *ptf )
{
    OPENFILENAME ofn;
    TCHAR dirName[MAX_PATH];
    TEXFILE newTexFile;
    LPTSTR pszFileName = newTexFile.szPathName;
    TCHAR origPathName[MAX_PATH];
    PTSTR pszString;
    BOOL bTryAgain, bFileSelected;

    gbEnableErrorMsgs = TRUE;

    // Make a copy of the original file path name, so we can tell if
    // it changed or not
    lstrcpy( origPathName, ptf->szPathName );

    // Make dialog look nice by parsing out the initial path and
    // file name from the full pathname.  If this isn't done, then
    // dialog has a long ugly name in the file combo box and
    // directory will end up with the default current directory.

    if (ptf->nOffset) {
    // Separate the directory and file names.

        lstrcpy(dirName, ptf->szPathName);
        dirName[ptf->nOffset-1] = L'\0';
        lstrcpy(pszFileName, &ptf->szPathName[ptf->nOffset]);
    }
    else {
    // If nOffset is zero, then szPathName is not a full path.
    // Attempt to make it a full path by calling SearchPath.

        if ( SearchPath(NULL, ptf->szPathName, NULL, MAX_PATH,
                         dirName, &pszString) )
        {
        // Successful.  Go ahead a change szPathName to the full path
        // and compute the filename offset.

            lstrcpy(ptf->szPathName, dirName);
            ptf->nOffset = pszString - dirName;

        // Break the filename and directory paths apart.

            dirName[ptf->nOffset-1] = TEXT('\0');
            lstrcpy(pszFileName, pszString);
        }

    // Give up and use the Windows system directory.

        else
        {
            GetWindowsDirectory(dirName, MAX_PATH);
            lstrcpy(pszFileName, ptf->szPathName);
        }
    }

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hDlg;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = gts.szTextureFilter;
    ofn.lpstrCustomFilter = (LPTSTR) NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = pszFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFileTitle = (LPTSTR) NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = dirName;
    ofn.lpstrTitle = gts.szTextureDialogTitle;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = gts.szBmp;
    ofn.lCustData = 0;
    ofn.lpfnHook = (LPOFNHOOKPROC) NULL;
    ofn.lpTemplateName = (LPTSTR) NULL;

    do {
    // Invoke the common file dialog.  If it succeeds, then validate
    // the bitmap file.  If not valid, make user try again until either
    // they pick a good one or cancel the dialog.

        bTryAgain = FALSE;

        if ( bFileSelected = GetOpenFileName(&ofn) ) {

            newTexFile.nOffset = ofn.nFileOffset;
            if( VerifyTextureFile( &newTexFile ) ) {
                // copy in new file and offset
                *ptf = newTexFile;
            }
            else {
                bTryAgain = TRUE;
            }
        }

    // If need to try again, recompute dir and file name so dialog
    // still looks nice.

        if (bTryAgain && ofn.nFileOffset) {
            lstrcpy(dirName, pszFileName);
            dirName[ofn.nFileOffset-1] = L'\0';
            lstrcpy(pszFileName, &pszFileName[ofn.nFileOffset]);
        }

    } while (bTryAgain);

    gbEnableErrorMsgs = FALSE;

    if( bFileSelected ) {
        if( lstrcmpi( origPathName, ptf->szPathName ) )
            // a different file was selected
            return TRUE;
    }
    return FALSE;
}

/******************************Public*Routine******************************\
*
* ss_GetDefaultBmpFile
*
* Determine a default bitmap file to use for texturing, if none
* exists yet in the registry.
*
* Put default in BmpFile parameter.   DotBmp parameter is the bitmap
* extension (usually .bmp).
*
* We have to synthesise the name from the ProductType registry entry.
* Currently, this can be WinNT, LanmanNT, or Server.  If it is
* WinNT, the bitmap is winnt.bmp.  If it is LanmanNT or Server,
* the bitmap is lanmannt.bmp.
*
\**************************************************************************/

void
ss_GetDefaultBmpFile( LPTSTR pszBmpFile )
{
    HKEY   hkey;
    LONG   cjDefaultBitmap = MAX_PATH;

    if( ss_fOnWin95() )
        // There is no 'nice' bmp file on standard win95 installations
        lstrcpy( pszBmpFile, TEXT("") );
    else {
        if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                 (LPCTSTR) TEXT("System\\CurrentControlSet\\Control\\ProductOptions"),
                 0,
                 KEY_QUERY_VALUE,
                 &hkey) == ERROR_SUCCESS )
        {

            if ( RegQueryValueEx(hkey,
                                  TEXT("ProductType"),
                                  (LPDWORD) NULL,

(LPDWORD) NULL,
                                  (LPBYTE) pszBmpFile,
                                  (LPDWORD) &cjDefaultBitmap) == ERROR_SUCCESS
                 && (cjDefaultBitmap / sizeof(TCHAR) + 4) <= MAX_PATH )
                lstrcat( pszBmpFile, gts.szDotBmp );
            else
                lstrcpy( pszBmpFile, TEXT("winnt.bmp") );

            RegCloseKey(hkey);
        }
        else
            lstrcpy( pszBmpFile, TEXT("winnt.bmp") );

    // If its not winnt.bmp, then its lanmannt.bmp.  (This would be a lot
    // cleaner both in the screen savers and for usersrv desktop bitmap
    // initialization if the desktop bitmap name were stored in the
    // registry).

        if ( lstrcmpi( pszBmpFile, TEXT("winnt.bmp") ) != 0 )
            lstrcpy( pszBmpFile, TEXT("lanmannt.bmp") );
    }
}

/******************************Public*Routine******************************\
*
* VerifyTextureFile
*
* Verify that a bitmap or rgb file is valid
*
* Returns:
*   File type (RGB or BMP) if valid file; otherwise, 0.
*
\**************************************************************************/

static int
VerifyTextureFile( TEXFILE *pTexFile )
{
    int type;
    ISIZE size;
    BOOL bValid;
    TCHAR szString[2 * MAX_PATH]; // May contain a pathname

    // check for 0 offset and null strings
    if( (pTexFile->nOffset == 0) || (*pTexFile->szPathName == 0) )
        return 0;

    type = GetTexFileType( pTexFile );

    switch( type ) {
        case TEX_BMP:
            bValid = bVerifyDIB( pTexFile->szPathName, &size );
            break;
        case TEX_RGB:
            bValid = bVerifyRGB( pTexFile->szPathName, &size );
            break;
        case TEX_UNKNOWN:
        default:
            bValid = FALSE;
    }

    if( !bValid ) {
        if( gbEnableErrorMsgs ) {
            wsprintf(szString, gts.szSelectAnotherBitmapMsg, pTexFile->szPathName);
            MessageBox(NULL, szString, gts.szWarningMsg, MB_OK);
        }
        return 0;
    }

    // Check size ?

    if ( (size.width > TEX_WIDTH_MAX)     ||
         (size.height > TEX_HEIGHT_MAX) )
    {
        if( gbEnableErrorMsgs )
        {
            wsprintf(szString, gts.szBitmapSizeMsg,
                      TEX_WIDTH_MAX, TEX_HEIGHT_MAX);
            MessageBox(NULL, szString, gts.szWarningMsg, MB_OK);
        }
        return 0;
    }

    return type;
}

/******************************Public*Routine******************************\
*
* ss_InitAutoTexture
*
* Generate texture coordinates automatically.
* If pTexRep is not NULL, use it to set the repetition of the generated
* texture.
*
\**************************************************************************/

void
ss_InitAutoTexture( TEX_POINT2D *pTexRep )
{
    GLfloat sgenparams[] = {1.0f, 0.0f, 0.0f, 0.0f};
    GLfloat tgenparams[] = {0.0f, 1.0f, 0.0f, 0.0f};

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    if( pTexRep )
        sgenparams[0] = pTexRep->s;
    glTexGenfv(GL_S, GL_OBJECT_PLANE, sgenparams );

    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    if( pTexRep )
        tgenparams[0] = pTexRep->t;
    glTexGenfv(GL_T, GL_OBJECT_PLANE, tgenparams );

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable( GL_TEXTURE_2D );
}

/******************************Public*Routine******************************\
*
* GetTexFileType
*
* Determine if a texture file is rgb or bmp, based on extension.  This is
* good enough, as the open texture dialog only shows files with these
* extensions.
*
\**************************************************************************/

static int
GetTexFileType( TEXFILE *pTexFile )
{
    LPTSTR pszStr;

#ifdef UNICODE
    pszStr = wcsrchr( pTexFile->szPathName + pTexFile->nOffset,
             (USHORT) L'.' );
#else
    pszStr = strrchr( pTexFile->szPathName + pTexFile->nOffset,
             (USHORT) L'.' );
#endif
    if( !pszStr || (lstrlen(++pszStr) == 0) )
        return TEX_UNKNOWN;

    if( !lstrcmpi( pszStr, TEXT("bmp") ) )
        return TEX_BMP;
    else if( !lstrcmpi( pszStr, TEXT("rgb") ) )
        return TEX_RGB;
    else
        return TEX_UNKNOWN;
}


TEXTURE.H

/******************************Module*Header*******************************\
* Module Name: texture.h
*
* Local texture processing functions
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __texture_h__
#define __texture_h__

#include "sscommon.h"

#ifdef __cplusplus
extern "C" {
#endif

extern BOOL bVerifyDIB(LPTSTR pszFileName, ISIZE *pSize );
extern BOOL bVerifyRGB(LPTSTR pszFileName, ISIZE *pSize );

#ifdef __cplusplus
}
#endif

#endif // __texture_h__


UTIL.CXX

/******************************Module*Header*******************************\
* Module Name: util.cxx
*
* Misc. utility functions
*
* Copyright 1996 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <sys/timeb.h>
#include <GL/gl.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <math.h>
#include "ssintrnl.hxx"
#include "util.hxx"

/******************************Public*Routine******************************\
* SS_TIME class
*
\**************************************************************************/

void
SS_TIME::Update()
{
    struct _timeb time;

    _ftime( &time );
    seconds = time.time + time.millitm/1000.0;
}

void
SS_TIME::Zero()
{
    seconds = 0.0;
}

double
SS_TIME::Seconds()
{
    return seconds;
}

SS_TIME
SS_TIME::operator+( SS_TIME addTime )
{
    return( *this += addTime );
}

SS_TIME
SS_TIME::operator-( SS_TIME subTime )
{
    return( *this -= subTime );
}

SS_TIME
SS_TIME::operator+=( SS_TIME addTime )
{
    seconds += addTime.seconds;
    return *this;
}

SS_TIME
SS_TIME::operator-=( SS_TIME subTime )
{
    seconds -= subTime.seconds;
    return *this;
}

/******************************Public*Routine******************************\
* SS_TIMER class
*
\**************************************************************************/

void
SS_TIMER::Start()
{
    startTime.Update();
}

SS_TIME
SS_TIMER::Stop()
{
    elapsed.Update();
    return elapsed - startTime;
}

void
SS_TIMER::Reset()
{
    elapsed.Zero();
}

SS_TIME
SS_TIMER::ElapsedTime()
{
    elapsed.Update();
    return elapsed - startTime;
}

/******************************Public*Routine******************************\
* ss_Rand
*
* Generates integer random number 0..(max-1)
*
\**************************************************************************/

int ss_iRand( int max )
{
    return (int) ( max * ( ((float)rand()) / ((float)(RAND_MAX+1)) ) );
}

/******************************Public*Routine******************************\
* ss_Rand2
*
* Generates integer random number min..max
*
\**************************************************************************/

int ss_iRand2( int min, int max )
{
    if( min == max )
        return min;
    else if( max < min ) {
        int temp = min;
        min = max;
        max = temp;
    }

    return min + (int) ( (max-min+1) * ( ((float)rand()) / ((float)(RAND_MAX+1)) ) );
}

/******************************Public*Routine******************************\
* ss_fRand
*
* Generates float random number min...max
*
\**************************************************************************/

FLOAT ss_fRand( FLOAT min, FLOAT max )
{
    FLOAT diff;

    diff = max - min;
    return min + ( diff * ( ((float)rand()) / ((float)(RAND_MAX)) ) );
}

/******************************Public*Routine******************************\
* ss_RandInit
*
* Initializes the randomizer
*
\**************************************************************************/

void ss_RandInit( void )
{
    struct _timeb time;

    _ftime( &time );
    srand( time.millitm );

    for( int i = 0; i < 10; i ++ )
        rand();
}

#if DBG
/******************************Public*Routine******************************\
* DbgPrint
*
* Formatted string output to the debugger.
*
* History:
*  26-Jan-1996 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

ULONG
DbgPrint(PCH DebugMessage, ...)
{
    va_list ap;
    char buffer[256];

    va_start(ap, DebugMessage);

    vsprintf(buffer, DebugMessage, ap);

    OutputDebugStringA(buffer);

    va_end(ap);

    return(0);
}
#endif


GEOM.C

/**********************************Module**********************************\
*
* geom.c
*
* 3D FlowerBox screen saver
* Geometry routines
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

// Sphere radius
#define RADIUS 1

// Values to map a 2D point onto a 3D plane
// Base point and axes to map X and Y coordinates onto
typedef struct _PLANE_MAP
{
    PT3 base, x_axis, y_axis;
} PLANE_MAP;

// Data area used by the current geometry
// Base points and generated points
PT3 pts[MAXPTS], npts[MAXPTS];
// Scaling factor for spherical projection
FLT vlen[MAXPTS];
// Normals
PT3 normals[MAXPTS];
// Vertex data indices
int index[MAXPTS*2];
// Triangle strip sizes
int strip_size[MAXSIDES*MAXSUBDIV];

void InitCube(GEOMETRY *geom);
void InitTetra(GEOMETRY *geom);
void InitPyramids(GEOMETRY *geom);
void InitCylinder(GEOMETRY *geom);
void InitSpring(GEOMETRY *geom);

GEOMETRY cube_geom = {InitCube};
GEOMETRY tetra_geom = {InitTetra};
GEOMETRY pyramids_geom = {InitPyramids};
GEOMETRY cylinder_geom = {InitCylinder};
GEOMETRY spring_geom = {InitSpring};

GEOMETRY *geom_table[] =
{
    &cube_geom,
    &tetra_geom,
    &pyramids_geom,
    &cylinder_geom,
    &spring_geom
};

extern BOOL bOgl11;
extern BOOL bCheckerOn;

/******************************Public*Routine******************************\
*
* InitVlen
*
* Precomputes scaling factor for spherical projection
*
\**************************************************************************/

void InitVlen(GEOMETRY *geom, int npts, PT3 *pts)
{
    FLT d;
    FLT *vl;

    vl = vlen;
    while (npts-- > 0)
    {
        d = V3Len(pts);

        // Don't allow really close points because this leads to
        // numeric instability and really large objects
        assert(d > 0.01f);

        // Geometries are created with size one, filling the area
        // from -.5 to .5.  This leads to distances generally less
        // than one, which leaves off half of the interesting morphing
        // effects due to the projection
        // Scaling up the scaling factor allows the values to
        // be both above and below one
        d *= geom->init_sf;

        assert(d > 0.0001f);

        *vl++ = (RADIUS-d)/d;

#if 0
        dprintf(("Distance is %f, vl %f\n", d, *(vl-1)));
#endif

        pts++;
    }
}

/******************************Public*Routine******************************\
*
* MapToSide
*
* Takes x,y coordinates in the range 0-1 and maps them onto the given
* side plane for the current geometry
*
\**************************************************************************/

void MapToSide(PLANE_MAP *map, FLT x, FLT y, PT3 *pt)
{
    pt->x = x*map->x_axis.x+y*map->y_axis.x+map->base.x;
    pt->y = x*map->x_axis.y+y*map->y_axis.y+map->base.y;
    pt->z = x*map->x_axis.z+y*map->y_axis.z+map->base.z;

}

void DrawWithVArrays (GEOMETRY *geom)
{
    int side, ss, idc, k;
    unsigned int *idx;

    for (side = 0; side < geom->nsides; side++) {
        geom->sides[side].dBuf = (GLuint *) LocalAlloc (LMEM_FIXED,
                                                        sizeof (GLuint) *
                                                        3 * MAXPTS * 2);
        k = 0;
        idx = geom->sides[side].strip_index;
        for (ss = 0; ss < geom->sides[side].nstrips; ss++) {
            if (geom->sides[side].strip_size[ss] < 3) continue;
            for (idc = 2; idc < geom->sides[side].strip_size[ss]; idc++) {
                if (!(idc % 2)) { //even
                    geom->sides[side].dBuf[k++] = *(idx+idc-2);
                    geom->sides[side].dBuf[k++] = *(idx+idc-1);
                } else {
                    geom->sides[side].dBuf[k++] = *(idx+idc-1);
                    geom->sides[side].dBuf[k++] = *(idx+idc-2);
                }
                geom->sides[side].dBuf[k++] = *(idx+idc);
            }
            idx += geom->sides[side].strip_size[ss];
        }
        geom->sides[side].num_eles = k;
    }
    glNormalPointer (GL_FLOAT, sizeof (PT3),
                     (GLfloat *)&(geom->normals[0].x));
    glVertexPointer (3, GL_FLOAT, sizeof (PT3),
                     (GLfloat *)&(geom->npts[0].x));
    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_NORMAL_ARRAY);

    glDisableClientState (GL_COLOR_ARRAY);
    glDisableClientState (GL_INDEX_ARRAY);
    glDisableClientState (GL_EDGE_FLAG_ARRAY);
    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
}
/******************************Public*Routine******************************\
*
* InitCube
*
* Initialize the cube's geometry
*
\**************************************************************************/

#define CUBE_SIDES 6

PLANE_MAP cube_planes[CUBE_SIDES] =
{
    -0.5f, -0.5f,  0.5f,  1.0f,  0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
     0.5f, -0.5f, -0.5f, -1.0f,  0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, -1.0f,  0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  1.0f,  0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f
};

#define CUBE_IDX(side, x, y) ((side)*side_pts+(x)*(config.subdiv+1)+(y))

void InitCube(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    unsigned int *sz, *idx;
    int side_pts;

    side_pts = (config.subdiv+1)*(config.subdiv+1);

    geom->nsides = CUBE_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.1f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 2.0f;

    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    for (side = 0; side < geom->nsides; side++)
    {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;

        for (x = 0; x < config.subdiv; x++)
        {
            *sz++ = (config.subdiv+1)*2;

            for (y = 0; y < config.subdiv+1; y++)
            {
                *idx++ = CUBE_IDX(side, x, y);
                *idx++ = CUBE_IDX(side, x+1, y);
            }
        }
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            for (y = 0; y < config.subdiv+1; y++)
            {
                MapToSide(&cube_planes[side],
                          (FLT)x/config.subdiv, (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}

/******************************Public*Routine******************************\
*
* InitTetra
*
* Initialize the tetrahedron's geometry
*
\**************************************************************************/

#define TETRA_SIDES 4

#define SQRT3 1.73205f
#define SQRT3_2 (SQRT3/2.0f)
#define SQRT3_3 (SQRT3/3.0f)
#define SQRT3_6 (SQRT3/6.0f)
#define SQRT3_12 (SQRT3/12.0f)

#define TETRA_BASE (-SQRT3/8.0f)

PLANE_MAP tetra_planes[TETRA_SIDES] =
{
    -0.5f, TETRA_BASE, SQRT3_6,
    1.0f, 0.0f, 0.0f, 0.0f, SQRT3_2, -SQRT3_6,

    0.0f, TETRA_BASE, -SQRT3_3,
    -0.5f, 0.0f, SQRT3_2, 0.25f, SQRT3_2, SQRT3_12,

    0.5f, TETRA_BASE, SQRT3_6,
    -0.5f, 0.0f, -SQRT3_2, -0.25f, SQRT3_2, SQRT3_12,

    0.5f, TETRA_BASE, SQRT3_6,
    -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -SQRT3_2
};

void InitTetra(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int side_pts;
    int base_pt;
    int row_pts;
    FLT fx;

    side_pts = (config.subdiv+2)*(config.subdiv+1)/2;

    geom->nsides = TETRA_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 3.75f;

    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    base_pt = 0;
    for (side = 0; side < geom->nsides; side++)
    {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;

        for (x = 0; x < config.subdiv; x++)
        {
            row_pts = config.subdiv-x+1;
            *sz++ = row_pts*2-1;

            *idx++ = base_pt;
            for (y = 0; y < row_pts-1; y++)
            {
                *idx++ = base_pt+row_pts+y;
                *idx++ = base_pt+1+y;
            }

            base_pt += row_pts;
        }

        base_pt++;
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            fx = (FLT)x/config.subdiv;
            for (y = 0; y < config.subdiv-x+1; y++)
            {
                MapToSide(&tetra_planes[side],
                          fx+(FLT)y/(config.subdiv*2),
                          (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}

/******************************Public*Routine******************************\
*
* InitPyramids
*
* Initializes double pyramid geometry
*
\**************************************************************************/

#define PYRAMIDS_SIDES 8

PLANE_MAP pyramids_planes[PYRAMIDS_SIDES] =
{
    -0.5f, 0.0f,  0.5f,  1.0f, 0.0f,  0.0f,  0.0f,  0.5f, -0.5f,
     0.5f, 0.0f,  0.5f, -1.0f, 0.0f,  0.0f,  0.0f, -0.5f, -0.5f,
     0.5f, 0.0f,  0.5f,  0.0f, 0.0f, -1.0f, -0.5f,  0.5f,  0.0f,
     0.5f, 0.0f, -0.5f,  0.0f, 0.0f,  1.0f, -0.5f, -0.5f,  0.0f,
     0.5f, 0.0f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f,  0.5f,  0.5f,
    -0.5f, 0.0f, -0.5f,  1.0f, 0.0f,  0.0f,  0.0f, -0.5f,  0.5f,
    -0.5f, 0.0f, -0.5f,  0.0f, 0.0f,  1.0f,  0.5f,  0.5f,  0.0f,
    -0.5f, 0.0f,  0.5f,  0.0f, 0.0f, -1.0f,  0.5f, -0.5f,  0.0f
};

void InitPyramids(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int side_pts;
    int base_pt;
    int row_pts;
    FLT fx;

    side_pts = (config.subdiv+2)*(config.subdiv+1)/2;

    geom->nsides = PYRAMIDS_SIDES;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -1.1f;
    geom->max_sf = 5.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 3.0f;

    // Generate triangle strip data
    sz = &strip_size[0];
    idx = &index[0];
    base_pt = 0;

    for (side = 0; side < geom->nsides; side++) {
        geom->sides[side].nstrips = config.subdiv;
        geom->sides[side].strip_size = sz;
        geom->sides[side].strip_index = idx;

        for (x = 0; x < config.subdiv; x++) {
            row_pts = config.subdiv-x+1;
            *sz++ = row_pts*2-1;

            *idx++ = base_pt;
            for (y = 0; y < row_pts-1; y++) {
                *idx++ = base_pt+row_pts+y;
                *idx++ = base_pt+1+y;
            }

            base_pt += row_pts;
        }

        base_pt++;
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    for (side = 0; side < geom->nsides; side++)
    {
        for (x = 0; x < config.subdiv+1; x++)
        {
            fx = (FLT)x/config.subdiv;
            for (y = 0; y < config.subdiv-x+1; y++)
            {
                MapToSide(&pyramids_planes[side],
                          fx+(FLT)y/(config.subdiv*2),
                          (FLT)y/config.subdiv,
                          pt);
                pt++;
            }
        }
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*side_pts;
}

/******************************Public*Routine******************************\
*
* InitCylinder
*
* Initializes the cylinder geometry
*
\**************************************************************************/

void InitCylinder(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    int base_pt;
    int row_pts;
    FLT fx, fz;
    double ang;

    geom->nsides = 1;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -2.5f;
    geom->max_sf = 8.5f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 2.1f;

    // Generate triangle strip data
    // If version 1.1 then allocate the index buffer for glDrawElements
    sz = &strip_size[0];
    idx = &index[0];
    side = 0;
    geom->sides[side].nstrips = config.subdiv;
    geom->sides[side].strip_size = sz;
    geom->sides[side].strip_index = idx;

    row_pts = config.subdiv+1;
    base_pt = 0;
    for (x = 0; x < config.subdiv; x++) {
        *sz++ = row_pts*2;

        for (y = 0; y < row_pts; y++) {
            // Wrap around at the edge so the cylinder normals
            // are properly averaged
            if (x == config.subdiv-1) {
                *idx++ = y;
            }
            else {
                *idx++ = base_pt+row_pts+y;
            }
            *idx++ = base_pt+y;
        }

        base_pt += row_pts;
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    ang = 0;
    for (x = 0; x < config.subdiv; x++)
    {
        fx = (FLT)cos(ang)*0.5f;
        fz = (FLT)sin(ang)*0.5f;
        for (y = 0; y < config.subdiv+1; y++)
        {
            pt->x = fx;
            pt->y = (FLT)y/config.subdiv-0.5f;
            pt->z = fz;
            pt++;
        }
        ang += (2*PI)/config.subdiv;
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*(config.subdiv+1)*config.subdiv;
}

/******************************Public*Routine******************************\
*
* InitSpring
*
* Initializes the spring geometry
*
\**************************************************************************/

#define SPRING_RADIUS 0.1f
#define SPRING_CENTER (0.5f-SPRING_RADIUS)

void InitSpring(GEOMETRY *geom)
{
    int side, x, y;
    PT3 *pt;
    int *sz, *idx;
    double ang_center, ang_surf;
    FLT cs, sn;
    FLT rad;
    PLANE_MAP plane;
    int spin_pts;
    int row_pts;

    geom->nsides = 1;
    geom->pts = &pts[0];
    geom->npts = &npts[0];
    geom->normals = &normals[0];

    geom->min_sf = -2.2f;
    geom->max_sf = 0.2f;
    geom->sf_inc = 0.05f;
    geom->init_sf = 1.0f;

    // Generate triangle strip data
    // If version 1.1 then allocate the index buffer for glDrawElements
    sz = &strip_size[0];
    idx = &index[0];
    side = 0;
    geom->sides[side].nstrips = config.subdiv;
    geom->sides[side].strip_size = sz;
    geom->sides[side].strip_index = idx;

    row_pts = config.subdiv;
    spin_pts = 4*config.subdiv+1;
    for (x = 0; x < config.subdiv; x++) {
        *sz++ = spin_pts*2;

        for (y = 0; y < spin_pts; y++) {
            *idx++ = x+row_pts*y;
            // Wrap around at the edge so the cylindrical surface
            // of the tube is seamless.  Without this the normal
            // averaging would be incorrect and a seam would be visible
            if (x == config.subdiv-1) {
                *idx++ = row_pts*y;
            }
            else {
                *idx++ = x+row_pts*y+1;
            }
        }
    }

    assert(sz-strip_size <= DIMA(strip_size));
    assert(idx-index <= DIMA(index));

    // Generate base vertices
    pt = geom->pts;
    ang_center = 0;
    plane.y_axis.x = 0.0f;
    plane.y_axis.y = SPRING_RADIUS;
    plane.y_axis.z = 0.0f;
    plane.x_axis.y = 0.0f;
    for (x = 0; x < spin_pts; x++)
    {
        cs = (FLT)cos(ang_center);
        sn = (FLT)sin(ang_center);
        rad = 0.5f-(FLT)x/(spin_pts-1)*(SPRING_CENTER/2);
        plane.base.x = cs*rad;
        plane.base.y = -0.5f+(FLT)x/(spin_pts-1);
        plane.base.z = sn*rad;
        plane.x_axis.x = cs*SPRING_RADIUS;
        plane.x_axis.z = sn*SPRING_RADIUS;

        ang_surf = 0;
        for (y = 0; y < config.subdiv; y++)
        {
            MapToSide(&plane,
                      (FLT)cos(ang_surf), (FLT)sin(ang_surf),
                      pt);
            pt++;
            ang_surf += (2*PI)/config.subdiv;
        }

        ang_center += (4*PI)/(spin_pts-1);
    }

    assert(pt-pts <= DIMA(pts));

    geom->total_pts = geom->nsides*spin_pts*config.subdiv;
}

/******************************Public*Routine******************************\
*
* DrawGeom
*
* Draw the current geometry
*
\**************************************************************************/

void DrawGeom(GEOMETRY *geom)
{
    int side, strip, k;
    int *idx, idc, idxv;

    if (config.smooth_colors)
    {
        glShadeModel(GL_SMOOTH);
    }
    else
    {
        glShadeModel(GL_FLAT);
    }

    if (config.color_pick == ID_COL_SINGLE)
    {
        glMaterialfv(config.two_sided, GL_DIFFUSE, solid_cols);
    }

    if (!(bOgl11 && !bCheckerOn)) {
        for (side = 0; side < geom->nsides; side++) {
            if (config.color_pick == ID_COL_PER_SIDE) {
                glMaterialfv(config.two_sided, GL_DIFFUSE, side_cols[side]);
            }

            idx = geom->sides[side].strip_index;
            for (strip = 0; strip < geom->sides[side].nstrips; strip++) {
                glBegin(GL_TRIANGLE_STRIP);

                for (idc = 0; idc < geom->sides[side].strip_size[strip];
                     idc++) {
                    idxv = *idx++;

                    assert(idxv >=0 && idxv < geom->total_pts);

                    if (config.color_pick == ID_COL_CHECKER) {
                        if (config.triangle_colors) {
                            glMaterialfv(config.two_sided, GL_DIFFUSE,
                                         checker_cols[side][(idc+1)/2+strip &
                                                           1]);
                        }
                        else {
                            glMaterialfv(config.two_sided, GL_DIFFUSE,
                                         checker_cols[side][idc/2+strip & 1]);
                        }
                    }

                    glNormal3fv((GLfloat *)&geom->normals[idxv]);
                    glVertex3fv((GLfloat *)&geom->npts[idxv]);
                }

                glEnd();
            }
        }
    } else {
        k = 0;
        for (side = 0; side < geom->nsides; side++) {
            if (config.color_pick == ID_COL_PER_SIDE)
                glMaterialfv(config.two_sided, GL_DIFFUSE, side_cols[side]);

            glDrawElements (GL_TRIANGLES, geom->sides[side].num_eles,
                            GL_UNSIGNED_INT, &(geom->sides[side].dBuf[0]));
            k += geom->sides[side].num_eles;
        }
    }
}

/******************************Public*Routine******************************\
*
* ComputeAveragedNormals
*
* Compute face-averaged normals for each vertex
*
\**************************************************************************/

void ComputeAveragedNormals(GEOMETRY *geom)
{
    int side, strip;
    int *sz;
    int *idx, idx1, idx2, idx3;
    int tc, idc;
    PT3 v1, v2, n1;

    memset(geom->normals, 0, sizeof(PT3)*geom->total_pts);

    for (side = 0; side < geom->nsides; side++)
    {
        idx = geom->sides[side].strip_index;
        sz = geom->sides[side].strip_size;
        for (strip = 0; strip < geom->sides[side].nstrips; strip++)
        {
            idx1 = *idx++;
            idx2 = *idx++;

            assert(idx1 >= 0 && idx1 < geom->total_pts &&
                   idx2 >= 0 && idx2 < geom->total_pts);

            tc = (*sz++)-2;
            for (idc = 0; idc < tc; idc++)
            {
                idx3 = *idx++;

                assert(idx3 >= 0 && idx3 < geom->total_pts);

                V3Sub(&geom->npts[idx3], &geom->npts[idx1], &v1);
                V3Sub(&geom->npts[idx2], &geom->npts[idx1], &v2);
                V3Cross(&v1, &v2, &n1);
                // Triangle strip ordering causes half of the triangles
                // to be oriented oppositely from the others
                // Those triangles need to have their normals flipped
                // so the whole triangle strip has consistent normals
                if ((idc & 1) == 0)
                {
                    n1.x = -n1.x;
                    n1.y = -n1.y;
                    n1.z = -n1.z;
                }

                V3Add(&geom->normals[idx1], &n1, &geom->normals[idx1]);
                V3Add(&geom->normals[idx2], &n1, &geom->normals[idx2]);
                V3Add(&geom->normals[idx3], &n1, &geom->normals[idx3]);

                idx1 = idx2;
                idx2 = idx3;
            }
        }
    }
}

/******************************Public*Routine******************************\
*
* UpdatePts
*
* Project the point array through a sphere according to the given scale factor
*
\**************************************************************************/

void UpdatePts(GEOMETRY *geom, FLT sf)
{
    int pt;
    FLT f, *vl;
    PT3 *v;
    PT3 *p;

    vl = vlen;
    p = &geom->pts[0];
    v = &geom->npts[0];
    for (pt = 0; pt < geom->total_pts; pt++)
    {
        f = (*vl++)*sf+1;
        v->x = p->x*f;
        v->y = p->y*f;
        v->z = p->z*f;
        p++;
        v++;
    }

    ComputeAveragedNormals(geom);
}


GEOM.H

/**********************************Module**********************************\
*
* geom.h
*
* 3D FlowerBox screen saver
* Geometry header file
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __GEOM_H__
#define __GEOM_H__

// Geometry of a shape

// A side of a shape
typedef struct _SIDE
{
    // Number of triangle strips in this side
    int nstrips;
    // Number of vertices per strip
    int *strip_size;
    // Indices for each point in the triangle strips
    unsigned int *strip_index;

    // The number of elements for glDrawElements call
    int num_eles;
    // Index buffer for glDrawElements
    GLuint *dBuf;
} SIDE;

typedef struct _GEOMETRY
{
    void (*init)(struct _GEOMETRY *geom);

    // Number of sides
    int nsides;
    // Sides
    SIDE sides[MAXSIDES];

    // Data for each vertex in the shape
    PT3 *pts, *npts;
    PT3 *normals;

    // Total number of vertices
    int total_pts;

    // Scaling control
    FLT min_sf, max_sf, sf_inc;

    // Initial scale factor setup control
    FLT init_sf;

} GEOMETRY;

#define GEOM_CUBE       0
#define GEOM_TETRA      1
#define GEOM_PYRAMIDS   2

extern GEOMETRY *geom_table[];

void InitVlen(GEOMETRY *geom, int npts, PT3 *pts);
void UpdatePts(GEOMETRY *geom, FLT sf);
void DrawGeom(GEOMETRY *geom);
extern void DrawWithVArrays (GEOMETRY *geom);

#endif // __GEOM_H__


PRECOMP.H

/**********************************Module**********************************\
*
* precomp.h
*
* Precompiled header file
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>

#include <windows.h>
#include <math.h>
#include <commctrl.h>
#include <scrnsave.h>

#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>

#include <sscommon.h>

#include "rsrc.h"
#include "ssflwbox.h"
#include "geom.h"


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by ssflwbox.rc
//

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1002
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


RSRC.H

/**********************************Module**********************************\
*
* rsrc.h
*
* Resource definitions
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __RSRC_H__
#define __RSRC_H__

#define ID_COMPLEXITY              401
#define ID_IMAGE_SIZE              402

#define ID_COL_PICK_FIRST          ID_COL_CHECKER
#define ID_COL_CHECKER             500
#define ID_COL_PER_SIDE            501
#define ID_COL_SINGLE              502
#define ID_COL_PICK_LAST           ID_COL_SINGLE
#define ID_COL_PICK_COUNT          (ID_COL_PICK_LAST-ID_COL_PICK_FIRST+1)

#define ID_COL_SMOOTH              550
#define ID_COL_TRIANGLE            551
#define ID_COL_CYCLE               552

#define ID_SPIN                    600
#define ID_BLOOM                   601
#define ID_TWO_SIDED               602

#define ID_GEOM                    650

#define IDS_CONFIG_SMOOTH_COLORS        1000
#define IDS_CONFIG_TRIANGLE_COLORS      1001
#define IDS_CONFIG_CYCLE_COLORS         1002
#define IDS_CONFIG_SPIN                 1003
#define IDS_CONFIG_BLOOM                1004
#define IDS_CONFIG_SUBDIV               1005
#define IDS_CONFIG_COLOR_PICK           1006
#define IDS_CONFIG_IMAGE_SIZE           1007
#define IDS_CONFIG_GEOM                 1008
#define IDS_CONFIG_TWO_SIDED            1009

#define IDS_GEOM_FIRST                  IDS_GEOM_CUBE
#define IDS_GEOM_CUBE                   1025
#define IDS_GEOM_TETRA                  1026
#define IDS_GEOM_PYRAMIDS               1027
#define IDS_GEOM_CYLINDER               1028
#define IDS_GEOM_SPRING                 1029
#define IDS_GEOM_LAST                   IDS_GEOM_SPRING
#define IDS_GEOM_COUNT                  (IDS_GEOM_LAST-IDS_GEOM_FIRST+1)

#define IDS_INI_SECTION                 1051

#endif // __RSRC_H__


SSFLWBOX.C

/**********************************Module**********************************\
*
* ssflwbox.c
*
* 3D FlowerBox screen saver
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

// Minimum and maximum image sizes
#define MINIMAGESIZE 10
#define MAXIMAGESIZE 100

// Color tables for checkboard, per-side and single color modes
GLfloat base_checker_cols[MAXSIDES][NCCOLS][4] =
{
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f,
    1.0f, 0.0f, 0.0f, 1.0f
};
GLfloat checker_cols[MAXSIDES][NCCOLS][4];

GLfloat base_side_cols[MAXSIDES][4] =
{
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f
};
GLfloat side_cols[MAXSIDES][4];

GLfloat base_solid_cols[4] =
{
    1.0f, 1.0f, 1.0f, 1.0f
};
GLfloat solid_cols[4];

// Current geometry
GEOMETRY *cur_geom;

// Set when a rendering context is available
BOOL gbGlInit = FALSE;

// Common library context
SSContext gssc;

// Spin rotations
double xr = 0, yr = 0, zr = 0;
// Scale factor and increment
FLT sf;
FLT sfi;
// Color cycling hue phase
FLT phase = 0.0f;

// Default configuration
CONFIG config =
{
    TRUE, FALSE, FALSE, TRUE, TRUE, MAXSUBDIV, ID_COL_PER_SIDE,
    (MAXIMAGESIZE+MINIMAGESIZE)/2, GEOM_CUBE, GL_FRONT
};

// A slider range
typedef struct _RANGE
{
    int min_val;
    int max_val;
    int step;
    int page_step;
} RANGE;

RANGE complexity_range = {MINSUBDIV, MAXSUBDIV, 1, 2};
RANGE image_size_range = {MINIMAGESIZE, MAXIMAGESIZE, 1, 10};

// True if the current OpenGL version is 1.1
BOOL bOgl11;

// True if checkered mode is on
BOOL bCheckerOn;

/******************************Public*Routine******************************\
*
* dprintf
*
* Debug output printf
*
\**************************************************************************/

#if DBG
void dprintf_out(char *fmt, ...)
{
    va_list args;
    char dbg[256];

    va_start(args, fmt);
    vsprintf(dbg, fmt, args);
    va_end(args);
    OutputDebugStringA(dbg);
}
#endif

/******************************Public*Routine******************************\
*
* assert_failed
*
* Assertion failure handler
*
\**************************************************************************/

#if DBG
void assert_failed(char *file, int line, char *msg)
{
    dprintf(("Assertion failed %s(%d): %s\n", file, line, msg));
    DebugBreak();
}
#endif

/******************************Public*Routine******************************\
*
* V3Len
*
* Vector length
*
\**************************************************************************/

FLT V3Len(PT3 *v)
{
    return (FLT)sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}

/******************************Public*Routine******************************\
*
* ComputeHsvColors
*
* Compute a smooth range of colors depending on the current color mode
*
\**************************************************************************/

void ComputeHsvColors(void)
{
    GLfloat *cols;
    int ncols;
    FLT ang, da;
    int hex;
    FLT fhex, frac;
    FLT p, q, t;
    FLT sat, val;

    switch(config.color_pick)
    {
    case ID_COL_CHECKER:
        ncols = MAXSIDES*NCCOLS;
        cols = &checker_cols[0][0][0];
        break;
    case ID_COL_PER_SIDE:
        ncols = MAXSIDES;
        cols = &side_cols[0][0];
        break;
    case ID_COL_SINGLE:
        ncols = 1;
        cols = &solid_cols[0];
        break;
    }

    ang = phase;
    da = (FLT)((2*PI)/ncols);
    val = sat = 1.0f;

    while (ncols > 0)
    {
        fhex = (FLT)(6*ang/(2*PI));
        hex = (int)fhex;
        frac = fhex-hex;
        hex = hex % 6;

p = val*(1-sat);
q = val*(1-sat*frac);
t = val*(1-sat*(1-frac));

switch(hex)
{
case 0:
            cols[0] = val;
            cols[1] = t;
            cols[2] = p;
    break;
case 1:
            cols[0] = q;
            cols[1] = val;
            cols[2] = p;
    break;
case 2:
            cols[0] = p;
            cols[1] = val;
            cols[2] = t;
    break;
case 3:
            cols[0] = p;
            cols[1] = q;
            cols[2] = val;
    break;
case 4:
            cols[0] = t;
            cols[1] = p;
            cols[2] = val;
            break;
case 5:
            cols[0] = val;
            cols[1] = p;
            cols[2] = q;
    break;
}

        ang += da;
        cols += 4;
        ncols--;
    }
}

/******************************Public*Routine******************************\
*
* Draw
*
* Draw everything
*
\**************************************************************************/

void Draw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();

    glRotated(xr, 1, 0, 0);
    glRotated(yr, 0, 1, 0);
    glRotated(zr, 0, 0, 1);

    DrawGeom(cur_geom);

    glFlush();
}

/******************************Public*Routine******************************\
*
* Update
*
* Update all varying values, called by the common library
*
\**************************************************************************/

void Update(void *data)
{
    if (config.spin)
    {
        xr += 3;
        yr += 2;
    }

    if (config.bloom)
    {
        sf += sfi;
        if (sf > cur_geom->max_sf ||
            sf < cur_geom->min_sf)
        {
            sfi = -sfi;
        }
        UpdatePts(cur_geom, sf);
    }

    if (config.cycle_colors)
    {
        ComputeHsvColors();
        phase += (FLT)(2.5*PI/180.);
    }

    Draw();
}

// String storage
TCHAR geom_names[IDS_GEOM_COUNT][20];

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
\**************************************************************************/

static void
getIniSettings()
{
    // Get registry settings

    if( ! ss_RegistrySetup( GetModuleHandle(NULL), IDS_INI_SECTION,
                            IDS_INIFILE ) )
        return;

    config.smooth_colors =
        ss_GetRegistryInt( IDS_CONFIG_SMOOTH_COLORS, config.smooth_colors );
    config.triangle_colors =
        ss_GetRegistryInt( IDS_CONFIG_TRIANGLE_COLORS, config.triangle_colors );
    config.cycle_colors =
        ss_GetRegistryInt( IDS_CONFIG_CYCLE_COLORS, config.cycle_colors );
    config.spin =
        ss_GetRegistryInt( IDS_CONFIG_SPIN, config.spin );
    config.bloom =
        ss_GetRegistryInt( IDS_CONFIG_BLOOM, config.bloom );
    config.subdiv =
        ss_GetRegistryInt( IDS_CONFIG_SUBDIV, config.subdiv );
    config.color_pick =
        ss_GetRegistryInt( IDS_CONFIG_COLOR_PICK, config.color_pick );
    config.image_size =
        ss_GetRegistryInt( IDS_CONFIG_IMAGE_SIZE, config.image_size );
    config.geom =
        ss_GetRegistryInt( IDS_CONFIG_GEOM, config.geom );
    config.two_sided =
        ss_GetRegistryInt( IDS_CONFIG_TWO_SIDED, config.two_sided );
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
*
\**************************************************************************/

static void
saveIniSettings()
{
    if( ! ss_RegistrySetup( GetModuleHandle(NULL), IDS_INI_SECTION,
                            IDS_INIFILE ) )
        return;

    ss_WriteRegistryInt( IDS_CONFIG_SMOOTH_COLORS, config.smooth_colors );

    ss_WriteRegistryInt( IDS_CONFIG_TRIANGLE_COLORS, config.triangle_colors );
    ss_WriteRegistryInt( IDS_CONFIG_CYCLE_COLORS, config.cycle_colors );
    ss_WriteRegistryInt( IDS_CONFIG_SPIN, config.spin );
    ss_WriteRegistryInt( IDS_CONFIG_BLOOM, config.bloom );
    ss_WriteRegistryInt( IDS_CONFIG_SUBDIV, config.subdiv );
    ss_WriteRegistryInt( IDS_CONFIG_COLOR_PICK, config.color_pick );
    ss_WriteRegistryInt( IDS_CONFIG_IMAGE_SIZE, config.image_size );
    ss_WriteRegistryInt( IDS_CONFIG_GEOM, config.geom );
    ss_WriteRegistryInt( IDS_CONFIG_TWO_SIDED, config.two_sided );
}

/******************************Public*Routine******************************\
*
* NewConfig
*
* Set up a new configuration
*
\**************************************************************************/

void NewConfig(CONFIG *cnf)
{
    // Set new config
    config = *cnf;

    // Save to ini file
    saveIniSettings();

    // Reset colors
    memcpy(checker_cols, base_checker_cols, sizeof(checker_cols));
    memcpy(side_cols, base_side_cols, sizeof(side_cols));
    memcpy(solid_cols, base_solid_cols, sizeof(solid_cols));

    // Reset geometry
    cur_geom = geom_table[config.geom];
    cur_geom->init(cur_geom);
    if (bOgl11 && !bCheckerOn) DrawWithVArrays (cur_geom);

    assert(cur_geom->total_pts <= MAXPTS);

    InitVlen(cur_geom, cur_geom->total_pts, cur_geom->pts);
    sf = 0.0f;
    sfi = cur_geom->sf_inc;
    UpdatePts(cur_geom, sf);

    // Reset OpenGL parameters according to configuration
    // Only done if GL has been initialized
    if (gbGlInit)
    {
        GLfloat fv4[4];

        if (config.two_sided == GL_FRONT_AND_BACK)
        {
            glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
            glDisable(GL_CULL_FACE);
        }
        else
        {
            glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
            glEnable(GL_CULL_FACE);
        }

        fv4[0] = fv4[1] = fv4[2] = .8f;
        fv4[3] = 1.0f;
        glMaterialfv(config.two_sided, GL_SPECULAR, fv4);
        glMaterialf(config.two_sided, GL_SHININESS, 30.0f);
    }
}

/******************************Public*Routine******************************\
*
* RegisterDialogClasses
*
* Standard screensaver hook
*
\**************************************************************************/

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

// Temporary configuration for when the configuration dialog is active
// If the dialog is ok'ed then this becomes the current configuration,
// otherwise it is discarded
CONFIG temp_config;

/******************************Public*Routine******************************\
*
* ScreenSaverConfigureDialog
*
* Standard screensaver hook
*
\**************************************************************************/

BOOL CALLBACK ScreenSaverConfigureDialog(HWND hdlg, UINT msg,
                                         WPARAM wpm, LPARAM lpm)
{
    HWND hCtrl;
    int i;

    switch(msg)
    {
    case WM_INITDIALOG:

        InitCommonControls();

        getIniSettings();

        temp_config = config;

        CheckRadioButton(hdlg, ID_COL_PICK_FIRST, ID_COL_PICK_LAST,
                         config.color_pick);
        CheckDlgButton(hdlg, ID_COL_SMOOTH, config.smooth_colors);
        CheckDlgButton(hdlg, ID_COL_TRIANGLE, config.triangle_colors);
        CheckDlgButton(hdlg, ID_COL_CYCLE, config.cycle_colors);
        CheckDlgButton(hdlg, ID_SPIN, config.spin);
        CheckDlgButton(hdlg, ID_BLOOM, config.bloom);
        CheckDlgButton(hdlg, ID_TWO_SIDED,
                       config.two_sided == GL_FRONT_AND_BACK);

        ss_SetupTrackbar( hdlg, ID_COMPLEXITY, MINSUBDIV, MAXSUBDIV,
                          complexity_range.step,
                          complexity_range.page_step,
                          config.subdiv);

        ss_SetupTrackbar( hdlg, ID_IMAGE_SIZE, MINIMAGESIZE, MAXIMAGESIZE,
                          image_size_range.step,
                          image_size_range.page_step,
                          config.image_size);

        hCtrl = GetDlgItem(hdlg, ID_GEOM);
        SendMessage(hCtrl, CB_RESETCONTENT, 0, 0);
        for (i = 0; i < IDS_GEOM_COUNT; i++)
        {
            LoadString( hMainInstance, i+IDS_GEOM_FIRST, geom_names[i],
                        sizeof(geom_names)/IDS_GEOM_COUNT );
            SendMessage(hCtrl, CB_ADDSTRING, 0, (LPARAM)geom_names[i]);
        }
        SendMessage(hCtrl, CB_SETCURSEL, config.geom, 0);

        SetFocus(GetDlgItem(hdlg, ID_COMPLEXITY));
        return FALSE;

    case WM_COMMAND:
        switch(LOWORD(wpm))
        {
        case ID_COL_CHECKER:
        case ID_COL_PER_SIDE:
        case ID_COL_SINGLE:
            temp_config.color_pick = LOWORD(wpm);
            break;

        case ID_COL_SMOOTH:
            temp_config.smooth_colors = !temp_config.smooth_colors;
            break;
        case ID_COL_TRIANGLE:
            temp_config.triangle_colors = !temp_config.triangle_colors;
            break;
        case ID_COL_CYCLE:
            temp_config.cycle_colors = !temp_config.cycle_colors;
            break;

        case ID_SPIN:
            temp_config.spin = !temp_config.spin;
            break;
        case ID_BLOOM:
            temp_config.bloom = !temp_config.bloom;
            break;
        case ID_TWO_SIDED:
            temp_config.two_sided =
                temp_config.two_sided == GL_FRONT_AND_BACK ? GL_FRONT :
                GL_FRONT_AND_BACK;
            break;

        case IDOK:
            temp_config.subdiv =
                ss_GetTrackbarPos(hdlg, ID_COMPLEXITY);
            temp_config.image_size =
                ss_GetTrackbarPos(hdlg, ID_IMAGE_SIZE);
            temp_config.geom =
                SendMessage(GetDlgItem(hdlg, ID_GEOM), CB_GETCURSEL, 0, 0);
            NewConfig(&temp_config);
            // Fall through
        case IDCANCEL:
            EndDialog(hdlg, LOWORD(wpm));
            break;
        }
        return TRUE;

    }

    return FALSE;
}

/******************************Public*Routine******************************\
*
* Init
*
* Drawing initialization, called by common library
*
\**************************************************************************/

void Init(void *data)
{
    GLfloat fv4[4];

    gbGlInit = TRUE;

    bOgl11 = ss_fOnGL11();

    if (config.color_pick == ID_COL_CHECKER) bCheckerOn = TRUE;
    else bCheckerOn = FALSE;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, 1, 2, 5); // for object range -1.5 to 1.5
    gluLookAt(0, 0, 3.5, 0, 0, 0, 0, 1, 0);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_DEPTH_TEST);
    glClearDepth(1);

    glCullFace(GL_BACK);

    fv4[0] = 2.0f;
    fv4[1] = 2.0f;
    fv4[2] = 10.0f;
    fv4[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, fv4);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glEnable(GL_NORMALIZE);

    // Make default configuration current
    NewConfig(&config);
}

/******************************Public*Routine******************************\
* SetFloaterInfo
*
* Set the size and motion of the floating window
*
\**************************************************************************/
void
SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild )
{
    float sizeFact;
    float sizeScale;
    int size;
    ISIZE *pChildSize = &pChild->size;
    MOTION_INFO *pMotion = &pChild->motionInfo;

    sizeScale = (float)config.image_size / 100.0f;
    sizeFact = 0.25f + (0.5f * sizeScale);     // range 25-75%
    size = (int) (sizeFact * ( ((float)(pParentSize->width + pParentSize->height )) / 2.0f ));
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->width );
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->height );

    pChildSize->width = pChildSize->height = size;

    // Floater motion
    pMotion->posInc.x = .005f * (float) size;
    if( pMotion->posInc.x < 1.0f )
        pMotion->posInc.x = 1.0f;
    pMotion->posInc.y = pMotion->posInc.x;
    pMotion->posIncVary.x = .4f * pMotion->posInc.x;
    pMotion->posIncVary.y = pMotion->posIncVary.x;
}

/******************************Public*Routine******************************\
*
* FloaterFail
*
* Called when the floating window cannot be created
*
\**************************************************************************/
void FloaterFail(void *data)
{
    HINSTANCE hinst;
    TCHAR error_str[20];
    TCHAR start_failed[80];

    hinst = GetModuleHandle(NULL);
    if (LoadString(hinst, IDS_ERROR,
                   error_str, sizeof(error_str)) &&
        LoadString(hinst, IDS_START_FAILED,
                   start_failed, sizeof(start_failed)))
    {
        MessageBox(NULL, start_failed, error_str, MB_OK);
    }
}

/******************************Public*Routine******************************\
*
* ss_Init
*
* Screensaver initialization routine, called at startup by common library
*
\**************************************************************************/

SSContext *ss_Init(void)
{
    getIniSettings();

    ss_InitFunc(Init);
    ss_UpdateFunc(Update);

    gssc.bFloater = TRUE;
    gssc.floaterInfo.bMotion = TRUE;
    gssc.floaterInfo.ChildSizeFunc = SetFloaterInfo;

    gssc.bDoubleBuf = TRUE;
    gssc.depthType = SS_DEPTH16;

    return &gssc;
}

/**************************************************************************\
* ConfigInit
*
* Dialog box version of ss_Init.  Used for setting up any gl drawing on the
* dialog.
*
\**************************************************************************/
BOOL
ss_ConfigInit( HWND hDlg )
{
    return TRUE;
}


SSFLWBOX.H

/**********************************Module**********************************\
*
* ssflwbox.h
*
* 3D FlowerBox screen saver
* Base header file
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __SSFLWBOX_H__
#define __SSFLWBOX_H__

#ifndef PI
#define PI 3.14159265358979323846
#endif

// Minimum and maximum number of side subdivisions
#define MINSUBDIV 2
#define MAXSUBDIV 10

// Maximum values allowed
#define MAXSIDES 8
#define MAXSPTS ((MAXSUBDIV+1)*(MAXSUBDIV+1))
#define MAXPTS (MAXSIDES*MAXSPTS)
#define MAXSFACES (MAXSUBDIV*MAXSUBDIV)
#define MAXFACES (MAXSIDES*MAXSFACES)
#define MAXFPTS 4

// Number of colors used in checkerboarding
#define NCCOLS 2

// Allow floating point type configurability
typedef GLfloat FLT;
typedef struct
{
    FLT x, y, z;
} PT3;

// Configurable options
typedef struct _CONFIG
{
    BOOL smooth_colors;
    BOOL triangle_colors;
    BOOL cycle_colors;
    BOOL spin;
    BOOL bloom;
    int subdiv;
    int color_pick;
    int image_size;
    int geom;
    int two_sided;
} CONFIG;

extern CONFIG config;

extern GLfloat checker_cols[MAXSIDES][NCCOLS][4];
extern GLfloat side_cols[MAXSIDES][4];
extern GLfloat solid_cols[4];

#if defined(assert)
#undef assert
#endif
#if DBG
#define dprintf(args) dprintf_out args
void dprintf_out(char *fmt, ...);
#define assert(e) if (!(e)) assert_failed(__FILE__, __LINE__, #e); else 0
void assert_failed(char *file, int line, char *msg);
#else
#define dprintf(args)
#define assert(e)
#endif

#define DIMA(a) (sizeof(a)/sizeof(a[0]))

/******************************Public*Routine******************************\
*
* Basic vector math macros
*
* History:
*  Wed Jul 19 14:49:49 1995-by-Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

#define V3Sub(a, b, r) \
    ((r)->x = (a)->x-(b)->x, (r)->y = (a)->y-(b)->y, (r)->z = (a)->z-(b)->z)
#define V3Add(a, b, r) \
    ((r)->x = (a)->x+(b)->x, (r)->y = (a)->y+(b)->y, (r)->z = (a)->z+(b)->z)
#define V3Cross(a, b, r) \
    ((r)->x = (a)->y*(b)->z-(b)->y*(a)->z,\
     (r)->y = (a)->z*(b)->x-(b)->z*(a)->x,\
     (r)->z = (a)->x*(b)->y-(b)->x*(a)->y)
extern FLT V3Len(PT3 *v);

#endif // __SSFLWBOX_H__


DIALOG.CXX

/******************************Module*Header*******************************\
* Module Name: dialog.cxx
*
* Dialog box functions
*
* Copyright 1995 - 1998 Microsoft Corporation
\**************************************************************************/

#include "pch.c"
#pragma hdrstop

#include <commdlg.h>
#include <commctrl.h>
#include <scrnsave.h>
#include "mazedlg.h"

#include "sscommon.hxx"

// Global string buffers for message box.

// local funtions

static void updateDialogControls(HWND hDlg);

HWND    ghDlg; // main dialog window handle
int     giSize;
int     giImageQual;
BOOL    gbTurboMode;
int     gnBitsPerPixel;

static void DrawTexture( int surface );
static void CleanUp( HWND hwnd );

// Texture information for each surface: used in both configure and run mode

TEX_INFO gTexInfo[NUM_SURFACES] = {0};

// default surface texture cache
static TEXTURE gDefTex[NUM_DEF_SURFACE_TEXTURES] = {0};

// Per-surface texture information relevant only to configure mode

typedef struct {
    TEXTURE userTex;
    int  iDefTex;
    int  defTexIDS;
    int  fileIDS;
    int  offsetIDS;
    int  dlgSpinTexID;
    int  iPalRot;  // texture palette rotation index
    SS_TEX_BUTTON *pTexBtn;  // GL texture drawing button thing
} TEX_DLG;

static TEX_DLG gTex[NUM_SURFACES] = {
    {
      {0},
      BRICK_TEXTURE,
      IDS_DEF_WALL_TEXTURE,
      IDS_WALL_TEXTURE_FILE,
      IDS_WALL_TEXTURE_OFFSET,
      DLG_SPIN_WALLS,
      NULL
    },
    {
      {0},
      WOOD_TEXTURE,
      IDS_DEF_FLOOR_TEXTURE,
      IDS_FLOOR_TEXTURE_FILE,
      IDS_FLOOR_TEXTURE_OFFSET,
      DLG_SPIN_FLOOR,
      NULL
    },
    {
      {0},
      CASTLE_TEXTURE,
      IDS_DEF_CEILING_TEXTURE,
      IDS_CEILING_TEXTURE_FILE,
      IDS_CEILING_TEXTURE_OFFSET,
      DLG_SPIN_CEILING,
      NULL
    }
};

static UINT idTimer = 0;

static BOOL CALLBACK TextureConfigureDialog(HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam);

/******************************Public*Routine******************************\
* getIniSettings
*
* - Get the screen saver configuration options from .INI file/registry.
* - Called by both dialog box and screen saver
*
\**************************************************************************/

void
getIniSettings()
{
    int    option;
    int    i;

    // Load resources

    LoadString(hMainInstance, IDS_GENNAME, szScreenSaver,
               sizeof(szScreenSaver) / sizeof(TCHAR));

    // Get registry settings

    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        // get wall/floor/ceiling texture enables
        // For now, texturing always on
        for( i = 0; i < NUM_SURFACES; i++ )
            gTexInfo[i].bTex = TRUE;

        option = ss_GetRegistryInt( IDS_DEFAULT_TEXTURE_ENABLE, (1 << NUM_SURFACES)-1 );
        for( i = 0; i < NUM_SURFACES; i++, option >>= 1 )
            gTexInfo[i].bDefTex = option & 1;

        // get default texture indices

        for( i = 0; i < NUM_SURFACES; i++ ) {
            gTexInfo[i].iDefTex =
                    ss_GetRegistryInt( gTex[i].defTexIDS, gTex[i].iDefTex );
            SS_CLAMP_TO_RANGE2( gTexInfo[i].iDefTex, 0,
                                                NUM_DEF_SURFACE_TEXTURES-1 );
        }

        // get user texture files

        for( i = 0; i < NUM_SURFACES; i++ ) {
            ss_GetRegistryString( gTex[i].fileIDS, 0,
                                  gTexInfo[i].texFile.szPathName, MAX_PATH);
            gTexInfo[i].texFile.nOffset = ss_GetRegistryInt( gTex[i].offsetIDS, 0 );
        }

        // get overlay
        maze_options.top_view = ss_GetRegistryInt( IDS_OVERLAY, 0 );

        // Get rat population
        maze_options.nrats = ss_GetRegistryInt(IDS_NRATS, 1);

        // get image quality

        giImageQual = ss_GetRegistryInt( IDS_IMAGEQUAL, IMAGEQUAL_DEFAULT );
        SS_CLAMP_TO_RANGE2( giImageQual, IMAGEQUAL_DEFAULT, IMAGEQUAL_HIGH );

        // get size

        giSize = ss_GetRegistryInt( IDS_SIZE, 0 );
        SS_CLAMP_TO_RANGE2( giSize, MIN_SLIDER, MAX_SLIDER );

        // get turbo mode
        gbTurboMode = ss_GetRegistryInt( IDS_TURBOMODE, 1 );
    }
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
\**************************************************************************/

static void
saveIniSettings(HWND hDlg)
{
    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        int i, option = 0;

        // write enables

        for( i = NUM_SURFACES-1, option = 0; i >= 0; i--, option <<= 1 )
            option |= gTexInfo[i].bDefTex & 1;
        ss_WriteRegistryInt( IDS_DEFAULT_TEXTURE_ENABLE, option >>= 1 );

        // Write default texture indices

        for( i = 0; i < NUM_SURFACES; i++ ) {
            ss_WriteRegistryInt( gTex[i].defTexIDS, gTexInfo[i].iDefTex );
        }

        // write user texture files

        for( i = 0; i < NUM_SURFACES; i++ ) {
            ss_WriteRegistryString( gTex[i].fileIDS,
                                    gTexInfo[i].texFile.szPathName );
            ss_WriteRegistryInt( gTex[i].offsetIDS, gTexInfo[i].texFile.nOffset);
        }

        // write size
        ss_WriteRegistryInt( IDS_SIZE,
                    ss_GetTrackbarPos(hDlg, DLG_SLIDER_SIZE) );

        // write overlay enable
        ss_WriteRegistryInt( IDS_OVERLAY, maze_options.top_view );

        // Write rat population
        ss_WriteRegistryInt( IDS_NRATS, maze_options.nrats );

        // write image quality
        ss_WriteRegistryInt( IDS_IMAGEQUAL, giImageQual );

        // turbot mod
        ss_WriteRegistryInt( IDS_TURBOMODE, gbTurboMode );
    }
}

/******************************Public*Routine******************************\
* setupDialogControls
*
* Do initial setup of dialog controls.
\**************************************************************************/

static void
setupDialogControls(HWND hDlg)
{
    int i;
    int idsImageQual;
    TCHAR szStr[GEN_STRING_SIZE];

    InitCommonControls();

    // setup size slider

    ss_SetupTrackbar( hDlg, DLG_SLIDER_SIZE, MIN_SLIDER, MAX_SLIDER, 1, 9,
                      giSize );

    // setup default texture spins

    for( i = 0; i < NUM_SURFACES; i ++ ) {
        SendDlgItemMessage( hDlg, gTex[i].dlgSpinTexID, UDM_SETRANGE, 0,
                            MAKELONG(NUM_DEF_SURFACE_TEXTURES-1, 0) );
        SendDlgItemMessage( hDlg, gTex[i].dlgSpinTexID, UDM_SETPOS, 0,
                            MAKELONG(gTexInfo[i].iDefTex, 0) );
    }

    // setup image quality combo box

    idsImageQual = IDS_IMAGEQUAL_DEFAULT;
    for( i = 0; i < IMAGEQUAL_COUNT; i++, idsImageQual++ ) {
        LoadString(hMainInstance, idsImageQual, szStr,
                    GEN_STRING_SIZE);
        SendDlgItemMessage(hDlg, DLG_COMBO_IMAGEQUAL, CB_ADDSTRING, 0,
                           (LPARAM) szStr);
    }
    SendDlgItemMessage(hDlg, DLG_COMBO_IMAGEQUAL, CB_SETCURSEL,
                       giImageQual, 0);

    // Disable Quality box when running on > 16 bits per pixel (since it only
    // affects dithering)
    gnBitsPerPixel = GetDeviceCaps( GetDC(hDlg), BITSPIXEL );
    EnableWindow( GetDlgItem(hDlg, DLG_COMBO_IMAGEQUAL), gnBitsPerPixel <= 16 );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_IMAGEQUAL), gnBitsPerPixel <= 16 );

    // set palette rotation index for each surface for a8 textures
    for( i = 0; i < NUM_SURFACES; i ++ )
        gTex[i].iPalRot = ss_iRand(0xff);

    // set state of other controls

    CheckDlgButton(hDlg, DLG_CHECK_OVERLAY, maze_options.top_view );

    CheckDlgButton(hDlg, DLG_CHECK_TURBOMODE, gbTurboMode );

    updateDialogControls(hDlg);
}

/******************************Public*Routine******************************\
* updateDialogControls
*
* Updates dialog controls according to current state
\**************************************************************************/

static void
updateDialogControls(HWND hDlg)
{
    int i;
    BOOL bDither;

    static int dlgSpinTexDef[NUM_SURFACES] = {
        DLG_SPIN_WALLS,
        DLG_SPIN_FLOOR,
        DLG_SPIN_CEILING};

    for( i = 0; i < NUM_SURFACES; i ++ ) {
        EnableWindow( GetDlgItem(hDlg, gTex[i].dlgSpinTexID), gTexInfo[i].bDefTex );
    }

    EnableWindow( GetDlgItem(hDlg, DLG_SLIDER_SIZE), !gbTurboMode );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_SIZE), !gbTurboMode );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_MIN), !gbTurboMode );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_MAX), !gbTurboMode );

    // Dithering looks bad with turbo mode on and 8 bits/pixel
    bDither = (gnBitsPerPixel <= 16) && !gbTurboMode;
    EnableWindow( GetDlgItem(hDlg, DLG_COMBO_IMAGEQUAL), bDither );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_IMAGEQUAL), bDither );
}

/******************************Public*Routine******************************\
* updateGLState
*
\**************************************************************************/

static void
updateGLState()
{
    if( giImageQual == IMAGEQUAL_DEFAULT )
        glDisable( GL_DITHER );
    else
        glEnable( GL_DITHER );
}

/******************************Public*Routine******************************\
* ValidateTexture
*
*
\**************************************************************************/

static TEXTURE *
ValidateTexture( int surface )
{
    TEXTURE *pTex = NULL;
    TEX_INFO *pTexInfo = &gTexInfo[surface];
    extern TEX_RES gTexResSurf[]; // from glmaze.c

    if( !pTexInfo->bDefTex ) {
        // Try to draw user texture
        pTex = &gTex[surface].userTex;
        if( !pTex->data ) {
            // Load user texture - this can fail!
            // If nOffset = 0, we assume user has never specified a texture,
            // so we silently switch to the default texture.
            // If the load of user texture fails, we use default texture
            if( (pTexInfo->texFile.nOffset == 0) ||
                (! ss_LoadTextureFile( &pTexInfo->texFile, pTex )) ) {

                pTexInfo->bDefTex = TRUE;  // draw default texture resource

                // Enable the spin control
                EnableWindow( GetDlgItem(ghDlg, gTex[surface].dlgSpinTexID),
                              TRUE );
            }
        }
    }

    if( pTexInfo->bDefTex ) {
        // Draw default texture resource
        pTex = &gDefTex[pTexInfo->iDefTex];
        if( !pTex->data ) {
            if( !ss_LoadTextureResource( &gTexResSurf[pTexInfo->iDefTex], pTex ))
                pTex = NULL;
        }
    }

    return pTex;
}

/******************************Public*Routine******************************\
* DrawTextures
*
* Draw all the textures
*
\**************************************************************************/

static void
DrawTextures()
{
    for( int i = 0; i < NUM_SURFACES; i++ ) {
        DrawTexture( i );
    }
}

/******************************Public*Routine******************************\
* DrawTexture
*
* Draw appropriate texture in supplied window (e.g. a button)
* - Use TEX_INFO state to determine texture
* - Load texture if not in cache
*
\**************************************************************************/

static void
DrawTexture( int surface )
{
    TEXTURE *pTex;

    // Make sure valid texture loaded for this surface
    if( ! (pTex = ValidateTexture( surface )) )
        return;

    SS_TEX_BUTTON *pTexBtn = gTex[surface].pTexBtn;
    // pTexBtn never NULL, else ss_ConfigInit fails

    // If palette rotation for this texture, slam in the current rotation,
    // to be used by pTexBtn->Draw
    if( pTex->pal )
        pTex->iPalRot = gTex[surface].iPalRot;

    pTexBtn->Draw( pTex );
}

/******************************Public*Routine******************************\
* RegisterDialogClasses
*
\**************************************************************************/

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

/******************************Public*Routine******************************\
* HandlePreviewDraw
*
\**************************************************************************/

static BOOL
HandlePreviewDraw( UINT idCtl, LPDRAWITEMSTRUCT lpdis )
{
    int surface = DLG_PREVIEW_TO_SURFACE( idCtl );

    if( lpdis->itemAction == ODA_DRAWENTIRE ) {
        DrawTexture( surface );
        return TRUE;
    }
    return FALSE;
}

/******************************Public*Routine******************************\
* HandleTexButtonNewTexture
*
* - Handle when a user selects a new texture
*
* If the new texture fails to load (unlikely, since we validate a good chunk
* of it with the selection dialog), then the current default texture is
* used.
\**************************************************************************/

static void
HandleTexButtonNewTexture( int surface )
{
    TEXTURE *pTex = &gTex[surface].userTex;

    // Delete the old texture
    ss_DeleteTexture( pTex );

    // Load up the new texture
    if( ! ss_LoadTextureFile( &gTexInfo[surface].texFile, pTex ) )
        gTexInfo[surface].bDefTex = TRUE;
}

/******************************Public*Routine******************************\
* MazeDlgTimerProc
*
* Runs off of WM_TIMER message.  Used for any gl animation in the dialog
*
\**************************************************************************/

static void
MazeDlgTimerProc()
{
    extern TEX_RES gTexResSurf[]; // from glmaze.c

    if( ss_PalettedTextureEnabled() ) {
        TEX_INFO *pti = gTexInfo;
        int i;

        // rotate texture palettes for surfaces with a8 palettes

        for( i = 0; i < NUM_SURFACES; i++, pti++ ) {
            if( pti->bTex &&
                pti->bDefTex &&
                ( gTexResSurf[pti->iDefTex].type == TEX_A8 ) )
            {
                // Increment palette rotation for this surface
                gTex[i].iPalRot++;
                // Draw texture associated with the surface
                DrawTexture( i );
            }
        }
    }
}

/******************************Public*Routine******************************\
* ConfigInit
*
* Do Initialization for Config mode
*
* This is the config equivalent of ss_Init
*
* Setup SS_TEX_BUTTON wrappers for each of the 3 texture preview buttons.
\**************************************************************************/

BOOL
ss_ConfigInit( HWND hDlg )
{
    SS_DBGLEVEL1( SS_LEVEL_INFO, "ConfigInit for %d\n", hDlg );

    // Create GL texture buttons to draw the surface previews

    SS_TEX_BUTTON *pTexBtn;

    for( int i = 0; i < NUM_SURFACES; i++ ) {
        pTexBtn = new SS_TEX_BUTTON( hDlg,
                            GetDlgItem(hDlg, DLG_SURFACE_TO_PREVIEW(i)) );
        if( !pTexBtn ) {
            return FALSE;
        }
        gTex[i].pTexBtn = pTexBtn;
    }

    updateGLState();

    // Start a timer for animating texture palettes
    idTimer = 1;
    SetTimer(hDlg, idTimer, 16, 0);

    // Note: no textures are loaded here, they are 'demand-loaded'
    return TRUE;
}

/******************************Public*Routine******************************\
* ScreenSaverConfigureDialog
*
* Processes messages for the configuration dialog box.
*
\**************************************************************************/

BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{
    int wTmp, surface;
    static BOOL bInited = 0;

    switch (message) {
        case WM_INITDIALOG:
            getIniSettings();
            setupDialogControls(hDlg);
            // cache the window handle
            ghDlg = hDlg;
            return TRUE;

        case WM_TIMER:
            MazeDlgTimerProc();
            return 0;
            break;

        case WM_DRAWITEM:
            switch( (UINT) wParam ) {
                case DLG_PREVIEW_WALLS:
                case DLG_PREVIEW_FLOOR:
                case DLG_PREVIEW_CEILING:
                    if( HandlePreviewDraw( (UINT) wParam,
                                             (LPDRAWITEMSTRUCT) lParam ) )
                        return TRUE;
                default:
                    break;
            }
            break;

        case WM_VSCROLL:
            switch(LOWORD(wParam))
            {
                case SB_THUMBPOSITION:
                    // get new value
                    wTmp = HIWORD(wParam);
                    {
                        int id;
                        HWND hwndScroll = (HWND) lParam;

                        id = GetDlgCtrlID( hwndScroll );
                        surface = DLG_SPIN_TEX_TO_SURFACE( id );
                        gTexInfo[surface].iDefTex = wTmp;
                        DrawTexture( surface );
                    }
                    break;
                default:
                    break;
            }
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {

                case DLG_BUTTON_WALLS_TEX:
                case DLG_BUTTON_FLOOR_TEX:
                case DLG_BUTTON_CEILING_TEX:
                    surface = DLG_BUTTON_TEX_TO_SURFACE( LOWORD(wParam) );
                    if( DialogBoxParam(
                        hMainInstance,
                        (LPTSTR) MAKEINTRESOURCE( DLG_TEXTURE_CONFIGURE ),
                        hDlg, (DLGPROC) TextureConfigureDialog, surface ) ) {
                    // If anything changed here, we have to draw it, as it
                    // seems when choose texture dialog box terminates, we
                    // don't get any DRAW_ITEM messages.
                    // Draw preview area
                        DrawTexture( surface );
                    }
                    break;

                case DLG_CHECK_OVERLAY:
                    maze_options.top_view = !maze_options.top_view;
                    CheckDlgButton(hDlg, DLG_CHECK_OVERLAY,
                                   maze_options.top_view );
                    break;

                case DLG_CHECK_TURBOMODE:
                    gbTurboMode = !gbTurboMode;
                    CheckDlgButton(hDlg, DLG_CHECK_TURBOMODE, gbTurboMode );
                    break;

                case DLG_COMBO_IMAGEQUAL:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                          {
                            int oldImageQual = giImageQual;
                            giImageQual =
                                SendDlgItemMessage(hDlg, DLG_COMBO_IMAGEQUAL,
                                                      CB_GETCURSEL, 0, 0);
                            if( giImageQual != oldImageQual ) {
                                // change has occurred - redraw any gl objects
                                updateGLState();
                                DrawTextures();
                            }
                          }
                          break;
                        default:
                            return FALSE;
                    }
                    break;

                case IDOK:
                    saveIniSettings(hDlg);
                    // fall thru...

                case IDCANCEL:
                    CleanUp( hDlg );
                    EndDialog(hDlg, FALSE);
                    break;

                default:
                    return 0;
                    break;
            }
            updateDialogControls(hDlg);
            return TRUE;
            break;

        default:
            return 0;
    }
    return 0;
}

/******************************Public*Routine******************************\
* updateTextureConfigControls
*
* Updates dialog controls according to current state
*
\**************************************************************************/

static void
updateTextureConfigControls(HWND hDlg, BOOL bDefTex )
{
    CheckDlgButton(hDlg, IDC_RADIO_TEX_DEFAULT, bDefTex );
    CheckDlgButton(hDlg, IDC_RADIO_TEX_CHOOSE, !bDefTex );

    EnableWindow(GetDlgItem(hDlg, DLG_BUTTON_TEX_CHOOSE ), !bDefTex );
}

/******************************Public*Routine******************************\
* TextureConfigureDialog
*
* Processes messages for the texture configure dialog box.
*
* - Call EndDialog with TRUE if texture changed, otherwise FALSE
*
\**************************************************************************/

static BOOL CALLBACK
TextureConfigureDialog(HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam)
{
    // static state ok here, cuz only one of these dialogs can be active
    static int surface;
    static BOOL bDefTex; // temporary state

    switch (message) {
        case WM_INITDIALOG:
            // Cache some initial values
            surface = (int) lParam;
            bDefTex = gTexInfo[surface].bDefTex;
            updateTextureConfigControls(hDlg, bDefTex );
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {

                case DLG_BUTTON_TEX_CHOOSE:
                    if( ss_SelectTextureFile( hDlg,
                                         &gTexInfo[surface].texFile ) ) {
                        // New user texture was selected
                        HandleTexButtonNewTexture( surface );

                        // For now, let's end the dialog right here if a
                        // new one chosen - otherwise we'd have to make the
                        // new texture discardable if user entered Cancel
                        EndDialog(hDlg, TRUE );
                    }
                    break;

                case IDC_RADIO_TEX_DEFAULT:
                    bDefTex = TRUE;
                    break;

                case IDC_RADIO_TEX_CHOOSE:
                    bDefTex = FALSE;
                    break;

                case IDOK:
                    // save state
                    gTexInfo[surface].bDefTex = bDefTex;
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;

                default:
                    return 0;
                    break;
            }
            updateTextureConfigControls(hDlg, bDefTex );
            return TRUE;
            break;

        default:
            return 0;
    }
    return 0;
}

/******************************Public*Routine******************************\
* CleanUp
*
\**************************************************************************/

static void
CleanUp( HWND hwnd )
{
    int i;

    if (idTimer) {
        KillTimer(hwnd, idTimer);
        idTimer = 0;
    }

    // delete any textures and tex buttons created

    for( i = 0; i < NUM_SURFACES; i ++ ) {
        if( gTex[i].userTex.data )
            ss_DeleteTexture( &gTex[i].userTex );
        if( gDefTex[i].data )
            ss_DeleteTexture( &gDefTex[i] );
        if( gTex[i].pTexBtn )
            delete gTex[i].pTexBtn;
    }

}


FIXED.H

#ifndef __FIXED_H__
#define __FIXED_H__

#include <math.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifndef PI
#define PI (3.1415926535897932384626433832795028841971693993751)
#endif

/* Default table size for precomputed sincos table */
#define FA_TABLE_SIZE 360

/*
   Flags for initialization
   FA_CARTESIAN_Y       - Y axis is positive up
 */
#define FA_DEFAULT      0
#define FA_CARTESIAN_Y  1

#if defined(FX_DOUBLE) || defined(FX_SINGLE)

#ifdef FX_DOUBLE
typedef double FxValue;

#define FX_MAX_VALUE (1e100)
#define FX_MIN_VALUE (1e-10)
#else
typedef float FxValue;

#define FX_MAX_VALUE (1e38f)
#define FX_MIN_VALUE (1e-7f)
#endif

#define FxVal(i) ((FxValue)(i))
#define FxInt(v) ((int)(v))
#define FxFltVal(f) ((FxValue)(f))
#define FxFlt(v) ((double)(v))
#define FxPromote(v) (v)
#define FxDemote(v) (v)

#define FxMul(a, b) ((a)*(b))
#define FxDemotedMul(a, b) FxMul(a, b)
#define FxDiv(a, b) ((a)/(b))
#define FxDemotedDiv(a, b) FxDiv(a, b)
#define FxMulToInt(a, b) FxInt((a)*(b))
#define FxDivToInt(a, b) ((int)((a)/(b)))
#define FxMulDiv(a, m, d) (((a)*(m))/(d))
#define FxSqrt(v) ((FxValue)sqrt((double)(v)))
#define FxDemotedSqrt(v) FxSqrt(v)

typedef FxValue FaAngle;

#define FaAng(a) (a)

#define FaSin(v) ((FxValue)-sin((double)(v)))
#define FaCos(v) ((FxValue)cos((double)(v)))

#define FaAdd(a, d) ((a)+(d))
FaAngle FaNorm(FaAngle a);
#define FaDeg(da) ((da)*(FxValue)(PI/180.0))
#define FaRad(ra) (ra)
#define FaAngVal(aa) (aa)
#define FaFltDegVal(a) ((a)*180.0/PI)
#define FaFltRadVal(a) (a)

#define FxInitialize(table_size, flags) ((flags) == FA_DEFAULT)
#define FxEnd()

#else

/* If integer sqrt isn't interesting, define FX_PRECISE_SQRT
   and the floating point sqrt will be used */

#ifndef FX_SHIFT
#define FX_SHIFT 10
#endif
#define FX_MULT (1L << FX_SHIFT)

typedef long FxValue;

#define FX_MAX_VALUE (0x7fffffff)
#define FX_MIN_VALUE (1)

#define FxVal(i) FxPromote((FxValue)(i))
#define FxInt(v) ((int)FxDemote(v))
#define FxFltVal(f) ((FxValue)((f)*(double)FX_MULT))
#define FxFlt(v) (((double)(v))/(double)FX_MULT)
#define FxPromote(v) ((v) << FX_SHIFT)
#define FxDemote(v) ((v) >> FX_SHIFT)

#if FX_SHIFT != 16
/* These can overflow if the shift and numbers are too large */
#define FxMul(a, b) FxDemote((a)*(b))
#define FxDiv(a, b) (FxPromote(a)/(b))
#define FxMulToInt(a, b) FxInt(FxDemote((a)*(b)))
#define FxMulDiv(a, m, d) (((a)*(m))/(d))
#else
/* For FX_SHIFT == 16 and certain platforms, assembly routines are
   provided which do 64-bit intermediate math, preserving accuracy
   There is still a danger of overflow if the results don't fit in
   32 bits, though */
FxValue FxMul(FxValue a, FxValue b);
FxValue FxDiv(FxValue a, FxValue b);
int FxMulToInt(FxValue a, FxValue b);
FxValue FxMulDiv(FxValue a, FxValue m, FxValue d);
#endif

#ifndef FX_PRECISE_SQRT
FxValue FxSqrt(FxValue v);
/* Computing the square root of a demoted value leaves it out of
   adjustment by sqrt(FX_MULT) so shift by FX_SHIFT/2 to
   restore fixed point
   FX_SHIFT should be even for this to work */
#define FxDemotedSqrt(v) (FxSqrt(FxDemote(v)) << (FX_SHIFT/2))
#else
#define FxSqrt(v) FxFltVal(sqrt(FxFlt(v)))
#define FxDemotedSqrt(v) FxSqrt(v)
#endif

#define FxDemotedMul(a, b) (FxDemote(a)*(b))
#define FxDemotedDiv(a, b) FxPromote((a)/(b))
#define FxDivToInt(a, b) ((int)((a)/(b)))

/* One unit of angle is the table quantum
   One unit of angle equals 360/_fa_table_size degrees */
typedef FxValue FaAngle;

extern int _fa_table_size;

extern FxValue *_fa_sines;
extern FxValue *_fa_cosines;

#define FaAng(a) FxDemote(a)
#define FaSin(a) _fa_sines[(int)FaAng(a)]
#define FaCos(a) _fa_cosines[(int)FaAng(a)]

FaAngle FaAdd(FaAngle a, FaAngle d);
FaAngle FaBisectingAngle(FaAngle f, FaAngle t);
#define FaNorm(a) FaAdd(FxVal(0), a)
#define FaDeg(da) \
    FaNorm(FxMulDiv(FxVal(da), _fa_table_size, 360))
#define FaRad(ra) FaNorm(FxFltVal((ra)*_fa_table_size/PI2))
#define FaAngVal(aa) FaNorm(FxVal(aa))
#define FaFltDegVal(ang) FxFltVal(FxMulDiv(ang, 360, _fa_table_size))
#define FaFltRadVal(ang) (FxFltVal(ang)*PI2/_fa_table_size)

BOOL FxInitialize(int table_size, ULONG flags);
void FxEnd(void);

#endif

typedef struct _FxPt2
{
    FxValue x, y;
} FxPt2;
typedef FxPt2 FxVec2;

typedef struct _FxBox2
{
    FxPt2 min, max;
} FxBox2;

typedef struct _FxPt3
{
    FxValue x, y, z;
} FxPt3;
typedef FxPt3 FxVec3;

typedef struct _FxBox3
{
    FxPt3 min, max;
} FxBox3;

typedef struct _FxPt4
{
    FxValue x, y, z, w;
} FxPt4;
typedef FxPt4 FxVec4;

void FxBBox2Empty(FxBox2 *bb);
void FxBBox2AddPt(FxBox2 *bb, FxPt2 *pt);

void FxBBox3Empty(FxBox3 *bb);
void FxBBox3AddPt(FxBox3 *bb, FxPt3 *pt);

#define FxV2Set(v, xv, yv) \
    ((v)->x = (xv), (v)->y = (yv))
#define FxV2Add(a, b, r) \
    ((r)->x = (a)->x+(b)->x, (r)->y = (a)->y+(b)->y)
#define FxV2Sub(a, b, r) \
    ((r)->x = (a)->x-(b)->x, (r)->y = (a)->y-(b)->y)
#define FxV2Dot(a, b) \
    (FxMul((a)->x, (b)->x)+FxMul((a)->y, (b)->y))
#define FxV2Neg(v, r) \
    ((r)->x = -(v)->x, (r)->y = -(v)->y)
#define FxV2NegV(v) FxV2Neg(v, v)
#define FxV2NormV(v) FxV2Norm(v, v)

FxValue FxV2Len(FxVec2 *v);
FxValue FxV2Norm(FxVec2 *v, FxVec2 *r);

#define FxvV2Set(v, xv, yv) FxV2Set(&(v), xv, yv)
#define FxvV2Add(a, b, r) FxV2Add(&(a), &(b), &(r))
#define FxvV2Sub(a, b, r) FxV2Sub(&(a), &(b), &(r))
#define FxvV2Dot(a, b) FxV2Dot(&(a), &(b))
#define FxvV2Neg(v, r) FxV2Neg(&(v), &(r))
#define FxvV2NegV(v) FxV2NegV(&(v))
#define FxvV2Len(v) FxV2Len(&(v))
#define FxvV2Norm(v, r) FxV2Norm(&(v), &(r))
#define FxvV2NormV(v) FxV2NormV(&(v))

#define FxV3Set(v, xv, yv, zv) \
    ((v)->x = (xv), (v)->y = (yv), (v)->z = (zv))
#define FxV3Add(a, b, r) \
    ((r)->x = (a)->x+(b)->x, (r)->y = (a)->y+(b)->y, (r)->z = (a)->z+(b)->z)
#define FxV3Sub(a, b, r) \
    ((r)->x = (a)->x-(b)->x, (r)->y = (a)->y-(b)->y, (r)->z = (a)->z-(b)->z)
#define FxV3Dot(a, b) \
    (FxMul((a)->x, (b)->x)+FxMul((a)->y, (b)->y)+FxMul((a)->z, (b)->z))
#define FxV3Neg(v, r) \
    ((r)->x = -(v)->x, (r)->y = -(v)->y, (r)->z = -(v)->z)
#define FxV3NegV(v) FxV3Neg(v, v)
#define FxV3Cross(a, b, r) \
    ((r)->x = (a)->y*(b)->z-(b)->y*(a)->z,\
     (r)->y = (a)->z*(b)->x-(b)->z*(a)->x,\
     (r)->z = (a)->x*(b)->y-(b)->x*(a)->y)
#define FxV3NormV(v) FxV3Norm(v, v)

FxValue FxV3Len(FxVec3 *v);
FxValue FxV3Norm(FxVec3 *v, FxVec3 *r);

#define FxvV3Set(v, xv, yv, zv) FxV3Set(&(v), xv, yv, zv)
#define FxvV3Add(a, b, r) FxV3Add(&(a), &(b), &(r))
#define FxvV3Sub(a, b, r) FxV3Sub(&(a), &(b), &(r))
#define FxvV3Dot(a, b) FxV3Dot(&(a), &(b))
#define FxvV3Neg(v, r) FxV3Neg(&(v), &(r))
#define FxvV3NegV(v) FxV3NegV(&(v))
#define FxvV3Cross(a, b, r) FxV3Cross(&(a), &(b), &(r))
#define FxvV3Len(v) FxV3Len(&(v))
#define FxvV3Norm(v, r) FxV3Norm(&(v), &(r))
#define FxvV3NormV(v) FxV3NormV(&(v))

typedef FxValue FxMatrix2[2][2];
typedef FxValue FxMatrix3[3][3];
typedef FxValue FxMatrix4[4][4];

typedef FxValue FxTMatrix2[2][3];
typedef FxValue FxTMatrix3[3][4];

void FxT2Ident(FxTMatrix2 m);
void FxT2Mul(FxTMatrix2 a, FxTMatrix2 b, FxTMatrix2 r);
void FxT2Vec2(FxTMatrix2 m, int n, FxVec2 *f, FxVec2 *t);

void FxT3Ident(FxTMatrix3 m);
void FxT3Mul(FxTMatrix3 a, FxTMatrix3 b, FxTMatrix3 r);
void FxT3Vec3(FxTMatrix3 m, int n, FxVec3 *f, FxVec3 *t);

#ifdef __cplusplus
}
#endif

#endif


GENMAZE.C

#include "pch.c"
#pragma hdrstop

/*
  Maze generation based on Kruskal's algorithm

  Consider all possible paths as sets of connected cells.  Initially
  all cells are unconnected and all walls are present.  Pick a wall
  at random and determine if the cells on either side of the wall are
  connected by any possible path.  If they are, try again.  If not,
  knock out the wall and union the two path sets together.
  */
#include <stdlib.h>
#include <memory.h>

#include "genmaze.h"

typedef struct _MazeCell
{
    /* Pointer to the head of the set of cells reachable from this one.
       Easily identifies an entire set for comparison */
    struct _MazeCell *set;
    /* Pointer to the next cell in this connected set */
    struct _MazeCell *next;
} MazeCell;

typedef struct _MazeWall
{
    BYTE wall;
    int x, y;
} MazeWall;

/*
  Union two connection sets together by setting all elements in one
  to the set of the other and then appending it to the end of the
  set list
  */
static void ConnectSets(MazeCell *set, MazeCell *add)
{
    /* Locate end of set */
    while (set->next != NULL)
    {
        set = set->next;
    }

    /* Change to point to head of set */
    add = add->set;

    /* Append new cells */
    set->next = add;

    /* Change new cells' set identity */
    while (add != NULL)
    {
        add->set = set->set;
        add = add->next;
    }
}

/*
  Determine whether the two cells are already connected.  With the
  existing data structures this is a trivial comparison of set
  identities
  */
#define SetsAreConnected(a, b) \
    ((a)->set == (b)->set)

/* Locate a cell in a known array */
#define CellAt(x, y) (cells+(x)+(y)*width)

/* Locate a cell in the output array */
#define MazeAt(x, y) (maze+(x)+(y)*(width+1))

/*
  Generate a maze by deleting walls at random
  Width and height are the counts of cells

  Result is stored in the walls array, formatted as (width+1)*(height+1)
  cells with MAZE_WALL_HORZ | MAZE_WALL_VERT set appropriately.
  X coordinates are one byte apart, so maze is treated like
  maze[height+1][width+1]
  */
BOOL GenerateMaze(int width, int height, BYTE *maze)
{
    MazeCell *cells;
    MazeWall *walls;
    int ncells;
    int nwalls;
    int i, x, y;
    MazeCell *ca, *cb;
    MazeWall *w, wt;

    ncells = width*height;
    cells = (MazeCell *)malloc(sizeof(MazeCell)*ncells);
    if (cells == NULL)
    {
        return FALSE;
    }

    nwalls = (width-1)*height+(height-1)*width;
    walls = (MazeWall *)malloc(sizeof(MazeWall)*nwalls);
    if (walls == NULL)
    {
        free(cells);
        return FALSE;
    }

    /* Initialize all cells to be unique sets */
    ca = cells;
    for (i = 0; i < ncells; i++)
    {
        ca->set = ca;
        ca->next = NULL;
        ca++;
    }

    /* Add all internal horizontal and vertical walls.
       All edge walls will be present in the final maze so
       they aren't considered here */
    w = walls;
    for (x = 1; x < width; x++)
    {
        for (y = 0; y < height; y++)
        {
            w->wall = MAZE_WALL_VERT;
            w->x = x;
            w->y = y;
            w++;
        }
    }
    for (y = 1; y < height; y++)
    {
        for (x = 0; x < width; x++)
        {
            w->wall = MAZE_WALL_HORZ;
            w->x = x;
            w->y = y;
            w++;
        }
    }

    /* Randomize the wall array */
    for (i = nwalls-1; i > 0; i--)
    {
        w = walls+(rand() % i);
        wt = *w;
        *w = walls[i];
        walls[i] = wt;
    }

    /* Now walk the random list of walls, knocking walls out to
       join cells together */
    w = walls;
    for (i = 0; i < nwalls; i++)
    {
        /* Determine the two cells separated by the current wall */
        ca = CellAt(w->x, w->y);
        if (w->wall == MAZE_WALL_HORZ)
        {
            cb = CellAt(w->x, w->y-1);
        }
        else
        {
            cb = CellAt(w->x-1, w->y);
        }

        /* If the two cells aren't connected, connect them by knocking
           out the wall */
        if (!SetsAreConnected(ca, cb))
        {
            ConnectSets(ca, cb);
            w->wall = 0;
        }

        w++;
    }

    /* Initialize output to empty */
    memset(maze, 0, sizeof(BYTE)*(width+1)*(height+1));

    /* Set all edge walls in the output */
    for (x = 0; x < width; x++)
    {
        *MazeAt(x, 0) |= MAZE_WALL_HORZ;
        *MazeAt(x, height) |= MAZE_WALL_HORZ;
    }
    for (y = 0; y < height; y++)
    {
        *MazeAt(0, y) |= MAZE_WALL_VERT;
        *MazeAt(width, y) |= MAZE_WALL_VERT;
    }

    /* Copy remaining walls into the output array */
    w = walls;
    for (i = 0; i < nwalls; i++)
    {
        *MazeAt(w->x, w->y) |= w->wall;
        w++;
    }

    free(cells);
    free(walls);

    return TRUE;
}


GENMAZE.H

#ifndef __GENMAZE_H__
#define __GENMAZE_H__

#define MAZE_WALL_VERT 1
#define MAZE_WALL_HORZ 2

BOOL GenerateMaze(int width, int height, BYTE *walls);

#endif


GLMAZE.C

#include "pch.c"
#pragma hdrstop

#include <time.h>
#include "mazedlg.h"
#include <assert.h>

MazeView vw;
MazeSolution sol;

MazeOptions maze_options =
{
    FALSE,          // depth_test
    RENDER_FLAT,    // render mode (WALLS)
    RENDER_FLAT,    // render mode (FLOOR)
    RENDER_FLAT,    // render mode (CEILING)
    FALSE,           // frame_count
    FALSE,          // top_view
    TRUE,           // eye_view
    FALSE,          // single_step
    FALSE,          // all_alpha
    FALSE,          // bDither
    1               // nrats
};

float gfAspect = 1.0f;  // aspect ratio

#define SST_NEW_MAZE    0
#define SST_MAZE_GROW   1
#define SST_SOLVING     2
#define SST_MAZE_SHRINK 3
#define SST_ROTATE      4

int solve_state = SST_NEW_MAZE;

// Table of textures used.  The first entries contain 1 texture for each main
// surface, followed by textures used for various objects
TEXTURE all_textures[NUM_TEXTURES];

// Environment associated with the texture (repetition, palette rotation,..)
// For now, have one for each of the textures in all_textures.  But, this
// could be a per-object thing.
TEX_ENV gTexEnv[NUM_TEXTURES];

// texture environment mode
int gTexEnvMode;

// Texture resources for the main surfaces
TEX_RES gTexResSurf[NUM_DEF_SURFACE_TEXTURES] = {
    {TEX_BMP, IDB_BRICK},       // default textures
    {TEX_BMP, IDB_WOOD},
    {TEX_BMP, IDB_CASTLE},
    {TEX_A8,  IDB_CURL4},       // mandelbrot textures
    {TEX_A8,  IDB_BHOLE4},
    {TEX_A8,  IDB_SNOWFLAK},
    {TEX_A8,  IDB_SWIRLX4}
};

// Texture resources for objects
TEX_RES gTexResObject[NUM_OBJECT_TEXTURES] = {
    {TEX_A8,  IDB_START},
    {TEX_A8,  IDB_END},
    {TEX_A8,  IDB_RAT},
    {TEX_A8,  IDB_AD},
    {TEX_BMP, IDB_COVER}
};
SSContext gssc;

void maze_Init( void *data );
void RotateTexturePalettes( TEX_ENV *pTexEnv, int count, int iRot );

enum {
    TIMER_START = 0,
    TIMER_STOP
};

void Draw(void)
{
    GLbitfield mask;

    mask = 0;
    if (maze_options.depth_test)
    {
        mask |= GL_DEPTH_BUFFER_BIT;
    }
    if (maze_options.render[FLOOR] == RENDER_NONE ||
        maze_options.render[CEILING] == RENDER_NONE ||
        maze_options.render[WALLS] == RENDER_NONE ||
        maze_height < 1.0f)
    {
        mask |= GL_COLOR_BUFFER_BIT;
    }
    if (mask != 0)
    {
        glClear(mask);
    }

    // Rotate the palettes of any paletted texures by 1
    RotateTexturePalettes( gTexEnv, NUM_TEXTURES, 1 );

    if (maze_options.eye_view)
    {
        DrawMaze(&vw);
    }

    if (maze_options.top_view)
    {
        DrawTopView(&vw);
    }

    glFlush();
}

MazeGoal maze_goals[MAX_GOALS];
int ngoals;

Object special_obj[MAX_SPECIALS];

#define MAX_LETTERS 3
Object letters_obj[MAX_LETTERS];

typedef struct _Rat
{
    Object obj;
    MazeView vw;
    MazeSolution sol;
} Rat;
Rat rats[MAX_RATS];

void NewMazeList(void)
{
    glNewList(maze_walls_list, GL_COMPILE);
    DrawMazeWalls();
    glEndList();
}

void UpdateRatPosition(Rat *rat)
{
    MoveObject(&rat->obj, rat->vw.pos.x, rat->vw.pos.y);
    // Invert the angle because view angles move opposite to
    // object angles
    rat->obj.ang = FaAdd(0, -rat->vw.ang);
}

// Simple routines to pick cells at random while guaranteeing
// that those cells are unoccupied

static int rnd_cells[MAZE_CELLS];
static int nrnd_cells;

void InitRandomCells(void)
{
    int i;

    nrnd_cells = MAZE_CELLS;
    for (i = 0; i < nrnd_cells; i++)
    {
        rnd_cells[i] = i;
    }
}

void PickRandomCell(IntPt2 *pos)
{
    int idx, t;

#if DBG
    if (nrnd_cells == 0)
    {
        MessageBox(GetDesktopWindow(), TEXT("Out of random cells"),
                   NULL, MB_OK);
        pos->x = 0;
        pos->y = 0;
        return;
    }
#endif

    idx = rand() % nrnd_cells;
    nrnd_cells--;
    t = rnd_cells[idx];
    rnd_cells[idx] = rnd_cells[nrnd_cells];
    pos->x = t % MAZE_GRID;
    pos->y = t / MAZE_GRID;
}

void RemoveRandomCell(IntPt2 *pos)
{
    int i, idx;

    for (i = 0; i < nrnd_cells; i++)
    {
        idx = rnd_cells[i];
        if ((idx % MAZE_GRID) == pos->x &&
            (idx / MAZE_GRID) == pos->y)
        {
            nrnd_cells--;
            rnd_cells[i] = rnd_cells[nrnd_cells];
            return;
        }
    }
}

void NewMaze(void)
{
    IntPt2 cell, start_cell;
    int i, nspecials, nads;
    static BOOL firstMaze = TRUE;

    // If not in full screen mode, move the maze window around after it's solved
    if( !gbTurboMode && !firstMaze )
        ss_RandomWindowPos();

    InitRandomCells();

    if (!InitMaze(&start_cell, maze_goals, &ngoals))
    {
        printf("InitMaze failed\n");
        exit(1);
    }

    RemoveRandomCell(&start_cell);
    cell.x = maze_goals[GOAL_END].clx;
    cell.y = maze_goals[GOAL_END].cly;
    RemoveRandomCell(&cell);

    nspecials = rand() % MAX_SPECIALS;
    for (i = 0; i < nspecials; i++)
    {
        PickRandomCell(&cell);
        maze_goals[ngoals].clx = cell.x;
        maze_goals[ngoals].cly = cell.y;
        maze_goals[ngoals].user = &special_obj[i];

        special_obj[i].w = FMAZE_CELL_SIZE/4;
        special_obj[i].h = FxFltVal(.25);
        special_obj[i].z = special_obj[i].h;
        special_obj[i].col = 15;
        special_obj[i].draw_style = DRAW_SPECIAL;
        special_obj[i].draw_arg = rand() % SPECIAL_ARG_COUNT;
        special_obj[i].ang = FaDeg(0);
        special_obj[i].user1 = (rand() % 6)+2;
        special_obj[i].user2 = rand() % 5;
        special_obj[i].user3 = 0;
        PlaceObject(&special_obj[i],
                    CellToMfx(maze_goals[ngoals].clx)+FMAZE_CELL_SIZE/2,
                    CellToMfx(maze_goals[ngoals].cly)+FMAZE_CELL_SIZE/2);
        ngoals++;
    }
    while (i < MAX_SPECIALS)
    {
        special_obj[i].cell = NULL;
        i++;
    }

    nads = (rand() % (MAX_LETTERS*2))-MAX_LETTERS+1;
    for (i = 0; i < nads; i++)
    {
        PickRandomCell(&cell);

        letters_obj[i].w = FMAZE_CELL_SIZE/3;
        letters_obj[i].h = FxFltVal(.33);
        letters_obj[i].z = FxFltVal(.5);
        letters_obj[i].col = 15;
        letters_obj[i].draw_style = DRAW_POLYGON;
        letters_obj[i].pTexEnv = &gTexEnv[ TEX_AD ];
        letters_obj[i].ang = FaDeg(0);
        PlaceObject(&letters_obj[i],
                    CellToMfx(cell.x)+FMAZE_CELL_SIZE/2,
                    CellToMfx(cell.y)+FMAZE_CELL_SIZE/2);
    }
    while (i < MAX_LETTERS)
    {
        letters_obj[i].cell = NULL;
        i++;
    }

    for (i = 0; i < maze_options.nrats; i++)
    {
        PickRandomCell(&cell);

        rats[i].obj.w = FMAZE_CELL_SIZE/4;
        rats[i].obj.h = FxFltVal(.125);
        rats[i].obj.z = rats[i].obj.h;
        rats[i].obj.col = 16;
        rats[i].obj.draw_style = DRAW_POLYGON;
        rats[i].obj.pTexEnv = &gTexEnv[ TEX_RAT ];
        SolveMazeStart(&rats[i].vw, &maze_cells[0][0], MAZE_GRID, MAZE_GRID,
                       &cell, SOL_DIR_LEFT,
                       NULL, 0,
                       (rand() % 1000) > 500 ? SOL_TURN_LEFT : SOL_TURN_RIGHT,
                       &rats[i].sol);
        // Need to force this to NULL when a new maze is generated so
        // that moving doesn't try to use old maze data
        rats[i].obj.cell = NULL;
        UpdateRatPosition(&rats[i]);
    }

    NewMazeList();

    SolveMazeStart(&vw, &maze_cells[0][0], MAZE_GRID, MAZE_GRID,
                   &start_cell, SOL_DIR_RIGHT,
                   maze_goals, ngoals,
                   (rand() % 1000) > 500 ? SOL_TURN_LEFT : SOL_TURN_RIGHT,
                   &sol);

    solve_state = SST_MAZE_GROW;
    firstMaze = FALSE;
}

/**************************************************************************\
* Step
*
* Main draw proc
*
\**************************************************************************/

static MazeGoal *found_goal = NULL;
static int rot_step;

void Step(void *data)
{
    int i;
    MazeGoal *goal;

    switch(solve_state)
    {
    case SST_NEW_MAZE:
        view_rot = 0;
        maze_height = 0.0f;
        NewMaze();
        break;

    case SST_SOLVING:
        for (i = 0; i < maze_options.nrats; i++)
        {
            SolveMazeStep(&rats[i].vw, &rats[i].sol);
            UpdateRatPosition(&rats[i]);
        }

        goal = SolveMazeStep(&vw, &sol);
        if (goal == &maze_goals[GOAL_END])
        {
            solve_state = SST_MAZE_SHRINK;
        }
        else if (goal != NULL)
        {
            solve_state = SST_ROTATE;
            found_goal = goal;
            rot_step = 0;
        }
        break;

    case SST_MAZE_GROW:
        maze_height += .025f;
        if (maze_height >= 1.0f)
        {
            solve_state = SST_SOLVING;
        }
        break;

    case SST_MAZE_SHRINK:
        maze_height -= .025f;
        if (maze_height <= 0.0f)
        {
            solve_state = SST_NEW_MAZE;
        }
        break;

    case SST_ROTATE:
        view_rot += 10.0;
        if (++rot_step == 18)
        {
            Object *sp_obj;

            sp_obj = (Object *)found_goal->user;
            RemoveObject(sp_obj);

            solve_state = SST_SOLVING;
            ngoals--;
            if (found_goal < maze_goals+ngoals)
            {
                memmove(found_goal, found_goal+1,
                        sizeof(MazeGoal)*(ngoals-(found_goal-maze_goals)));
            }
            SolveMazeSetGoals(&sol, maze_goals, ngoals);
            found_goal = NULL;

            if (view_rot >= 360.0)
            {
                view_rot = 0.0;
            }
            else
            {
                view_rot = 180.0;
            }
        }
        break;
    }

    Draw();

    for (i = 0; i < MAX_SPECIALS; i++)
    {
        // Increment rotations of any specials still present in the maze
        if (special_obj[i].cell != NULL)
        {
            special_obj[i].ang = FaAdd(special_obj[i].ang,
                                       FaDeg(special_obj[i].user1));
            special_obj[i].user3 += special_obj[i].user2;
        }
    }
}

void UpdateModes(void)
{
    if (maze_options.depth_test)
    {
        glEnable(GL_DEPTH_TEST);
    }
    else
    {
        glDisable(GL_DEPTH_TEST);
    }
}

/**************************************************************************\
* Reshape
*
*       - called on resize, expose
*       - always called on app startup
*
\**************************************************************************/

void
Reshape(int width, int height, void *data )
{
    gfAspect = height == 0 ? 1.0f: (float) width / (float) height;
}

/******************************Public*Routine******************************\
* VerifyTextureFiles
*
* Check that any user specified textures are valid
* - MUST be called at ss_Init, or any error msgs will not display properly
*
\**************************************************************************/

void VerifyTextureFiles(void)
{
    int i;

    ss_DisableTextureErrorMsgs();

    for (i = 0; i < NUM_SURFACES; i++) {
        if( gTexInfo[i].bTex && !gTexInfo[i].bDefTex ) {
            if( !ss_VerifyTextureFile( &gTexInfo[i].texFile ) )
                // use default texture
                gTexInfo[i].bDefTex = TRUE;
        }
    }
}

/******************************Public*Routine******************************\
* CalcRep
*
* Figure out repetion based on size.
*
* - Use 128 pixels as a unit repetition reference
* - Size is always a power of 2
*
\**************************************************************************/

static int
CalcRep( int size )
{
    double pow2;
    int pow2ref = 8;
    int rep;

    pow2 = log((double)size) / log((double)2.0);
    rep = 1 + pow2ref - (int)pow2;
    return rep;
}

// tradeoff
#define MAX_TEX_DIM 128
//#define MAX_TEX_DIM 256

/******************************Public*Routine******************************\
* CalcTexRep
*
* Figure out texture repetition based on texture size.
*
* - mf: ? double for walls/ceiling ?
*
\**************************************************************************/

static void
CalcTexRep( TEXTURE *pTex, IPOINT2D *pTexRep )
{
    if( pTex->width >= MAX_TEX_DIM )
        pTexRep->x = 1;
    else
        pTexRep->x = CalcRep( pTex->width );

    if( pTex->height >= MAX_TEX_DIM )
        pTexRep->y = 1;
    else
        pTexRep->y = CalcRep( pTex->height );
}

/******************************Public*Routine******************************\
* LoadTextures
*
* Load textures from .bmp files or from embedded resources, using
* global texture info structure.
*
* For now, this also sets render modes, since if texturing is off for a
* surface, we always use RENDER_FLAT
*
\**************************************************************************/

static void
LoadTextures(void)
{
    int i;
    TEXTURE *pTex = all_textures;
    TEX_ENV *pTexEnv = gTexEnv;
    TEX_RES *pTexRes;

    assert( (NUM_SURFACES + NUM_OBJECT_TEXTURES) == NUM_TEXTURES );

    // load surface textures (wall, floor, ceiling)

    for (i = 0; i < NUM_SURFACES; i++, pTex++, pTexEnv++) {
        maze_options.render[i] = RENDER_FLAT;
        if( gTexInfo[i].bTex ) {
            pTexEnv->pTex = pTex;
            pTexEnv->bTransp = FALSE;

            // Load user or default texture for the surface

            if( !gTexInfo[i].bDefTex &&
                ss_LoadTextureFile( &gTexInfo[i].texFile, pTex )) {
            } else
            {
                // Load default resource texture

                pTexRes = &gTexResSurf[ gTexInfo[i].iDefTex ];
                if( !ss_LoadTextureResource( pTexRes, pTex ) )
                    continue;

                if( ss_PalettedTextureEnabled() &&
                    (pTexRes->type == TEX_A8) )
                {
                    pTexEnv->bPalRot = TRUE;
                    pTexEnv->iPalRot = ss_iRand( 0xff );
                } else
                    pTexEnv->bPalRot = FALSE;
            }

            maze_options.render[i] = RENDER_TEXTURED;

            // Figure out texture repetition
            CalcTexRep( pTex, &pTexEnv->texRep );
            // We would have to set texture parameters per object here,
            // but we just use default ones already set by ss_LoadTexture*
        }
    }

    // load object textures

    pTexRes = gTexResObject;
    for( i = 0; i < NUM_OBJECT_TEXTURES; i++, pTex++, pTexEnv++, pTexRes++ ) {
        if( ss_LoadTextureResource( pTexRes, pTex ) )
            pTexEnv->pTex = pTex;
        else
            pTexEnv->pTex = NULL;

        pTexEnv->bTransp = FALSE;

        // For now we don't do palrot's on any of the object textures
        pTexEnv->bPalRot = FALSE;
        // No repetition
        pTexEnv->texRep.x = pTexEnv->texRep.y = 1;
    }

    // Set transparency for some of the textures
    ss_SetTextureTransparency( gTexEnv[TEX_START].pTex, 0.42f, FALSE );
    ss_SetTextureTransparency( gTexEnv[TEX_END].pTex, 0.4f, FALSE );
    ss_SetTextureTransparency( gTexEnv[TEX_AD].pTex, 0.4f, FALSE );

    // Enable transparency for some of the texture environments
    gTexEnv[TEX_START].bTransp = TRUE;
    gTexEnv[TEX_END].bTransp = TRUE;
    gTexEnv[TEX_AD].bTransp = TRUE;
}

void UseTextureEnv( TEX_ENV *pTexEnv )
{
    static HTEXTURE hCurTex = (HTEXTURE) -1;
    HTEXTURE hTex = pTexEnv->pTex;

    // We cache the current texture for 'no texture object' case

    if( !ss_TextureObjectsEnabled() &&
        (hCurTex == hTex) )
        return; // same texture, no need to send it down

    // Make this texture current

    ss_SetTexture( hTex );

    // Set texture palette if necessary

    if( pTexEnv->bPalRot &&
        !ss_TextureObjectsEnabled() &&
        ss_PalettedTextureEnabled() ) {

        // We need to send down the (rotated) palette
        ss_SetTexturePalette( hTex, pTexEnv->iPalRot );
    }

    hCurTex = hTex;
}

/******************************Public*Routine******************************\
* RotateTexturePalettes
*
* If paletted texturing is enabled, go through the supplied list of texture
* environments, and if any have a palette, increment its rotation by the
* supplied iRot value.
*
\**************************************************************************/

void
RotateTexturePalettes( TEX_ENV *pTexEnv, int count, int iRot )
{
    if( !ss_PalettedTextureEnabled() || !pTexEnv )
        return;

    for( ; count; count--, pTexEnv++ ) {

        if( !pTexEnv->pTex || !pTexEnv->pTex->pal )
            continue;

        if( pTexEnv->bPalRot ) {
            // increment palette rotation
            pTexEnv->iPalRot += iRot;
            if( pTexEnv->iPalRot >= pTexEnv->pTex->pal_size )
                pTexEnv->iPalRot = 0;
            // Only send down the new palette if texture objects are enabled,
            // since otherwise it will be sent down when texture is
            // 'made current'
            if( ss_TextureObjectsEnabled() ) {
                ss_SetTexturePalette( pTexEnv->pTex, pTexEnv->iPalRot );
            }
        }
    }
}

/******************************Public*Routine******************************\
* InitStretchInfo
*
\**************************************************************************/

static void
InitStretchInfo( STRETCH_INFO *pStretch )
{
    pStretch->baseWidth = 320;
    pStretch->baseHeight = 200;
    pStretch->bRatioMode = FALSE;
}

/******************************Public*Routine******************************\
* SetFloaterInfo
*
* Set the size and position of the floating child window
*
\**************************************************************************/

static void
SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild )
{
    float hdtvAspect = 9.0f / 16.0f;
    ISIZE *pChildSize = &pChild->size;

    // Set width according to user-specified size
    // (giSize range is 0..100)
    // set min size as 1/3 parent width
    pChildSize->width =
            (int) ((0.333f + 2.0f*giSize/300.0f) * pParentSize->width);

    // Scale height for hdtv aspect ratio
    pChildSize->height = (int) (hdtvAspect * pChildSize->width + 0.5f);
    // Ensure height not too big
    SS_CLAMP_TO_RANGE2( pChildSize->height, 0, pParentSize->height );

    pChild->pos.x = (pParentSize->width - pChildSize->width) / 2;
    pChild->pos.y = (pParentSize->height - pChildSize->height) / 2;
}

/******************************Public*Routine******************************\
* ss_Init
*
* Initialize - called on first entry into ss.
* Called BEFORE gl is initialized!
* Just do basic stuff here, like set up callbacks, verify dialog stuff, etc.
*
* Fills global SSContext structure with required data, and returns ptr
* to it.
*
\**************************************************************************/

SSContext *
ss_Init( void )
{
    getIniSettings();

    VerifyTextureFiles();

    // Set callbacks

    ss_InitFunc( maze_Init );
    ss_UpdateFunc( Step );
    ss_ReshapeFunc( Reshape );

    gssc.depthType = (maze_options.depth_test) ? SS_DEPTH16 : SS_DEPTH_NONE;

    // Currently stretch and floater mutex
    gssc.bDoubleBuf = TRUE; // will get turned off if stretch used
    if( gbTurboMode ) {
        // We stretch out the drawing area to the full size of the main window
        gssc.bFloater = FALSE;
        gssc.bStretch = TRUE;
        InitStretchInfo( &gssc.stretchInfo );
    } else {
        // A static centered floating window is created in the main window
        FLOATER_INFO *pFloater = &gssc.floaterInfo;

        gssc.bFloater = TRUE;
        gssc.bStretch = FALSE;
        pFloater->bMotion = FALSE;
        pFloater->ChildSizeFunc = SetFloaterInfo;
    }

    return &gssc;
}

/******************************Public*Routine******************************\
* maze_Init
*
* Initializes OpenGL state
*
\**************************************************************************/
void
maze_Init( void *data )
{
    float fv4[4];

    if (!FxInitialize(FA_TABLE_SIZE, 0))
    {
        printf("FxInit failed\n");
        exit(1);
    }

    glShadeModel( GL_FLAT );
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    glClearDepth(1);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    if( (giImageQual == IMAGEQUAL_DEFAULT) || gbTurboMode ) {
        maze_options.bDither = FALSE;
        glDisable( GL_DITHER );
    } else {
        maze_options.bDither = TRUE;
        glEnable( GL_DITHER );
    }

    // Load textures and set render modes
    LoadTextures();

    fv4[0] = MAZE_SIZE/2.0f;
    fv4[1] = MAZE_SIZE/2.0f;
    fv4[2] = 10.0f;
    fv4[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, fv4);
    glEnable(GL_LIGHT0);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Check which texture environment function to use for objects
    if( ss_fOnGL11() )
        gTexEnvMode = GL_REPLACE;
    else
        gTexEnvMode = GL_MODULATE;

    maze_walls_list = glGenLists(1);

    UpdateModes();
}


GLMAZE.H

#ifndef __GLMAZE_H__
#define __GLMAZE_H__

#include "sscommon.h"
#include "maze_std.h"

#ifdef __cplusplus
extern "C" {
#endif

enum {
    TEX_WALL  = 0,
    TEX_FLOOR,
    TEX_CEILING,
    TEX_START,
    TEX_END,
    TEX_RAT,
    TEX_AD,
    TEX_COVER,
    NUM_TEXTURES
};

void UseTextureEnv( TEX_ENV *pTexEnv );

#define MAX_RATS 10

extern MazeOptions maze_options;
extern TEX_RES gTexResSurf[];

#ifdef __cplusplus
}
#endif

#endif


MAZE.H

#ifndef __MAZE_H__
#define __MAZE_H__

#include "maze_std.h"

#define MAZE_GRID 12
#define MAZE_CELLS (MAZE_GRID*MAZE_GRID)
#define MAZE_ARRAY (MAZE_GRID+1)
#define MAZE_ARRAY_CELLS (MAZE_ARRAY*MAZE_ARRAY)
#define N_MAZE_PTS MAZE_ARRAY_CELLS
#define N_MAZE_WALLS (2*MAZE_ARRAY_CELLS)

#define MAZE_WALL_LEFT          0x0004
#define MAZE_WALL_RIGHT         0x0008
#define MAZE_WALL_UP            0x0010
#define MAZE_WALL_DOWN          0x0020
#define MAZE_CONTENTS           0x0040

// Partial flags must be shifted versions of the standard flags
#define MAZE_WALL_LEFT_PARTIAL  0x0080
#define MAZE_WALL_RIGHT_PARTIAL 0x0100
#define MAZE_WALL_UP_PARTIAL    0x0200
#define MAZE_WALL_DOWN_PARTIAL  0x0400
#define MAZE_PARTIAL_SHIFT      5

#define WIDX_LEFT       0
#define WIDX_RIGHT      1
#define WIDX_UP         2
#define WIDX_DOWN       3

#define MAZE_CELL_SIZE 1
#define MAZE_SIZE (MAZE_GRID*MAZE_CELL_SIZE)
#define FMAZE_CELL_SIZE FxVal(MAZE_CELL_SIZE)
#define FMAZE_SIZE FxVal(MAZE_SIZE)

#define MfxToCell(mfx) FxInt(mfx)
#define CellToMfx(cl) FxVal(cl)

struct _Cell;

#define DRAW_POLYGON    0
#define DRAW_SPECIAL    1

#define SPECIAL_ARG_ICOSAHEDRON         0
#define SPECIAL_ARG_OCTAHEDRON          1
#define SPECIAL_ARG_DODECAHEDRON        2
#define SPECIAL_ARG_TETRAHEDRON         3
#define SPECIAL_ARG_COUNT               4

typedef struct _Object
{
    FxPt2 p;
    FxValue z;
    FaAngle ang;
    FxValue w, h;
    int col;
    int draw_style;
    int draw_arg;
    TEX_ENV *pTexEnv;  // ptr to texture environment
    int user1, user2, user3;
    struct _Object *next;
    struct _Cell *cell;
} Object;

typedef unsigned short WallFlags;

typedef struct _Cell
{
    WallFlags can_see;
    WallFlags unseen;
    Object *contents;
    struct _Wall *walls[4];
} Cell;

extern Cell maze_cells[MAZE_GRID][MAZE_GRID];

extern float maze_height;
extern double view_rot;
extern int maze_walls_list;

extern int gTexEnvMode;

#define MAX_GOALS       10
#define MAX_SPECIALS    (MAX_GOALS-1)

#define GOAL_END        0
#define GOAL_SPECIALS   1

BOOL InitMaze(IntPt2 *start_cell, struct _MazeGoal *goals, int *ngoals);

void PlaceObject(Object *obj, FxValue x, FxValue y);
void RemoveObject(Object *obj);
void MoveObject(Object *obj, FxValue x, FxValue y);

void DrawMaze(MazeView *vw);
void DrawMazeWalls(void);
void DrawTopView(MazeView *vw);

#endif


MAZE_STD.H

#ifndef __MAZE_STD_H__
#define __MAZE_STD_H__

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "fixed.h"
#include "genmaze.h"
#include "sscommon.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct _IntPt2
{
    int x, y;
} IntPt2;

#define DIR_RIGHT 0
#define DIR_UP 1
#define DIR_LEFT 2
#define DIR_DOWN 3
#define DIR_COUNT 4

typedef struct
{
    TEXTURE  *pTex;   // ptr to texture
    IPOINT2D texRep;  // texture repetition in s and t directions
    BOOL     bPalRot; // whether should rotate texture palette
    int      iPalRot; // current palette rotation start point
    BOOL     bTransp; // transparency on/off for RGBA textures
} TEX_ENV;

typedef struct _MazeView
{
    FxPt2 pos;
    FaAngle ang;
} MazeView;

#define RENDER_NONE     0
#define RENDER_FLAT     1
#define RENDER_SMOOTH   2
#define RENDER_TEXTURED 3
#define RENDER_COUNT    4

// surfaces
enum {
    WALLS = 0,
    FLOOR,
    CEILING,
    NUM_SURFACES
};

// default surface textures
enum {
    BRICK_TEXTURE = 0,
    WOOD_TEXTURE,
    CASTLE_TEXTURE,
    CURL4_TEXTURE,
    BHOLE4_TEXTURE,
    SNOWFLAK_TEXTURE,
    SWIRLX4_TEXTURE,
    NUM_DEF_SURFACE_TEXTURES
};

// textured objects
enum {
    START = 0,
    END,
    RAT,
    AD,
    COVER,
    NUM_OBJECT_TEXTURES
};

enum {
    IMAGEQUAL_DEFAULT = 0,
    IMAGEQUAL_HIGH,
    IMAGEQUAL_COUNT
};

typedef struct _MazeOptions
{
    BOOL depth_test;
    int render[NUM_SURFACES];
    BOOL frame_count;
    BOOL top_view;
    BOOL eye_view;
    BOOL single_step;
    BOOL all_alpha;
    BOOL bDither;
    int nrats;
} MazeOptions;

extern MazeOptions maze_options;

#ifdef __cplusplus
}
#endif

#endif // __MAZE_STD_H__


MAZEDLG.H

/******************************Module*Header*******************************\
* Module Name: mazedlg.h
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __dialog_h__
#define __dialog_h__

#include "dlgs.h"
#include "sscommon.h"
#include "maze_std.h"

#ifdef __cplusplus
extern "C" {
#endif

#define IDS_SCREENSAVERTITLE    1020
#define IDS_SAVERNAME           1002

// structure to pull in texture info from registry
typedef struct {
    BOOL    bTex;       // texture enable/disable
    BOOL    bDefTex;    // default texture enable/disable
    int     iDefTex;    // default texture index
    TEXFILE texFile;    // user texture file
} TEX_INFO;

// registry strings

#define IDS_TEXTURE_ENABLE              1100
#define IDS_DEFAULT_TEXTURE_ENABLE      1101

#define IDS_WALL_TEXTURE_FILE           1110
#define IDS_FLOOR_TEXTURE_FILE          1111
#define IDS_CEILING_TEXTURE_FILE        1112

#define IDS_WALL_TEXTURE_OFFSET         1120
#define IDS_FLOOR_TEXTURE_OFFSET        1121
#define IDS_CEILING_TEXTURE_OFFSET      1122

#define IDS_DEF_WALL_TEXTURE            1130
#define IDS_DEF_FLOOR_TEXTURE           1131
#define IDS_DEF_CEILING_TEXTURE         1132

#define IDS_OVERLAY                     1200

#define IDS_TURBOMODE                   1250

#define IDS_IMAGEQUAL                   1300

#define IDS_NRATS                       1400

// Dialog box defines

// Imagequal combobox strings
#define IDS_IMAGEQUAL_DEFAULT     1301
#define IDS_IMAGEQUAL_HIGH        1302

#define MIN_SLIDER              0
#define MAX_SLIDER              100

// Size slider
#define DLG_SLIDER_SIZE         2000
#define IDC_STATIC_SIZE         2001
#define IDC_STATIC_MIN          2002
#define IDC_STATIC_MAX          2003

// Maze overly check box
#define DLG_CHECK_OVERLAY       2008

// Turbo mode check box
#define DLG_CHECK_TURBOMODE     2009

// Buttons to choose textures
#define DLG_BUTTON_WALLS_TEX    3100
#define DLG_BUTTON_FLOOR_TEX    3101
#define DLG_BUTTON_CEILING_TEX  3102
#define DLG_BUTTON_TEX_TO_SURFACE(n)    ( (n) - DLG_BUTTON_WALLS_TEX )

// Previews for surfaces (now just texture, but could show like colors)
#define DLG_PREVIEW_WALLS        3200
#define DLG_PREVIEW_FLOOR        3201
#define DLG_PREVIEW_CEILING      3202
#define DLG_PREVIEW_TO_SURFACE(n)    ( (n) - DLG_PREVIEW_WALLS )
#define DLG_SURFACE_TO_PREVIEW(n)    ( (n) + DLG_PREVIEW_WALLS )

// Spin controls for texture previews
//mf: add TEX suffix
#define DLG_SPIN_WALLS           3300
#define DLG_SPIN_FLOOR           3301
#define DLG_SPIN_CEILING         3302
#define DLG_SPIN_TEX_TO_SURFACE(n)    ( (n) - DLG_SPIN_WALLS )

// Resource bitmap id's
#define IDB_BRICK                100
#define IDB_WOOD                 101
#define IDB_CASTLE               102
#define IDB_START                103
#define IDB_END                  104
#define IDB_RAT                  105
#define IDB_AD                   106
#define IDB_COVER                107

#define IDB_CURL4                120
#define IDB_BHOLE4               121
#define IDB_SNOWFLAK             125
#define IDB_SWIRLX4               127

// Image quality box
#define DLG_COMBO_IMAGEQUAL      3500
#define IDC_STATIC_IMAGEQUAL     3501

// Choose texture dialog box

#define DLG_TEXTURE_CONFIGURE   4000

#define IDC_RADIO_TEX_DEFAULT   4010
#define IDC_RADIO_TEX_CHOOSE    4011
#define DLG_BUTTON_TEX_CHOOSE   4012

extern void getIniSettings();
extern TEX_INFO gTexInfo[];
extern int      giSize; // window size
extern BOOL     gbTurboMode;  // turbo mode enable/disable
extern int      giImageQual;  // Image quality (dithering)

#ifdef __cplusplus
}
#endif

#endif // __dialog_h__


PCH.C

#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>

#include "sscommon.h"
#include "maze_std.h"
#include "glmaze.h"
#include "maze.h"
#include "slvmaze.h"


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by maze.rc
//
#include "windows.h"
#include "scrnsave.h"
// Note: vc4 embeds sscommon.rc into maze.rc
#include "sscommon.rc"
#include "mazedlg.h"
#include "commctrl.h"

#define IDD_DIALOG1                     101
#define IDB_BITMAP1                     102
#define IDC_CHECK1                      1000
#define IDC_CHECK2                      1001
#define IDC_CHECK3                      1002
#define IDC_SCROLLBAR1                  1003
#define IDC_CHECK4                      1005
#define IDC_RADIO1                      1011
#define IDC_RADIO2                      1012
#define IDC_BUTTON1                     1013
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1014
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SLVMAZE.C

#include "pch.c"
#pragma hdrstop

static int left_turn[SOL_DIRS] =
{
    SOL_DIR_DOWN, SOL_DIR_LEFT, SOL_DIR_UP, SOL_DIR_RIGHT
};

static int right_turn[SOL_DIRS] =
{
    SOL_DIR_UP, SOL_DIR_RIGHT, SOL_DIR_DOWN, SOL_DIR_LEFT
};

static BYTE dir_wall[SOL_DIRS] =
{
    MAZE_WALL_LEFT, MAZE_WALL_UP, MAZE_WALL_RIGHT, MAZE_WALL_DOWN
};

static int dir_cloff[SOL_DIRS][2] =
{
    -1,0, 0,-1, 1,0, 0,1
};

static FaAngle dir_ang[SOL_DIRS];
static FxPt2 dir_off[SOL_DIRS];

/* We want to traverse one quarter of a circle in the given number of
   steps.  The distance is the arc length which is r*pi/2.  Divide that
   by the number of steps to get the distance each step should travel */
#define ARC_STEP 5
#define ARC_STEPS (90/ARC_STEP)

#define REVERSE_STEP (2*ARC_STEP)
#define REVERSE_STEPS (180/REVERSE_STEP)

static void SetView(MazeSolution *sol, MazeView *vw)
{
    vw->ang = dir_ang[sol->dir];
    vw->pos.x = CellToMfx(sol->clx)+dir_off[sol->dir].x;
    vw->pos.y = CellToMfx(sol->cly)+dir_off[sol->dir].y;
}

void SolveMazeSetGoals(MazeSolution *sol, MazeGoal *goals, int ngoals)
{
    sol->goals = goals;
    sol->ngoals = ngoals;
}

void SolveMazeStart(MazeView *vw,
                    Cell *maze, int w, int h,
                    IntPt2 *start, int start_dir,
                    MazeGoal *goals, int ngoals,
                    int turn_to,
                    MazeSolution *sol)
{
    dir_ang[SOL_DIR_LEFT] = FaDeg(180);
    dir_ang[SOL_DIR_UP] = FaDeg(90);
    dir_ang[SOL_DIR_RIGHT] = FaDeg(0);
    dir_ang[SOL_DIR_DOWN] = FaDeg(270);

    dir_off[SOL_DIR_LEFT].x = CellToMfx(1)-FX_MIN_VALUE;
    dir_off[SOL_DIR_LEFT].y = CellToMfx(1)/2;
    dir_off[SOL_DIR_UP].x = CellToMfx(1)/2;
    dir_off[SOL_DIR_UP].y = CellToMfx(1)-FX_MIN_VALUE;
    dir_off[SOL_DIR_RIGHT].x = FxVal(0);
    dir_off[SOL_DIR_RIGHT].y = CellToMfx(1)/2;
    dir_off[SOL_DIR_DOWN].x = CellToMfx(1)/2;
    dir_off[SOL_DIR_DOWN].y = FxVal(0);

    sol->clx = start->x;
    sol->cly = start->y;
    sol->dir = start_dir;
    sol->maze = maze;
    sol->w = w;
    sol->h = h;
    sol->ani_state = ANI_STATE_NONE;

    switch(turn_to)
    {
    case SOL_TURN_RIGHT:
        sol->turn_to = left_turn;
        sol->turn_away = right_turn;
        sol->dir_sign = 1;
        break;

    case SOL_TURN_LEFT:
        sol->turn_to = right_turn;
        sol->turn_away = left_turn;
        sol->dir_sign = -1;
        break;
    }

    SolveMazeSetGoals(sol, goals, ngoals);

    SetView(sol, vw);
}

#define MazeAt(x, y) (sol->maze+(x)+(y)*(sol->w))

MazeGoal *SolveMazeStep(MazeView *vw, MazeSolution *sol)
{
    Cell *cell;
    int i, dir, turn_to;

    if (sol->ani_state != ANI_STATE_NONE)
    {
        if (--sol->ani_count == 0)
        {
            sol->ani_state = ANI_STATE_NONE;
            SetView(sol, vw);
        }
    }

    switch(sol->ani_state)
    {
    case ANI_STATE_TURN_TO:
        vw->pos.x += FxMulDiv(FaCos(vw->ang),
                              FxFltVal(PI*MAZE_CELL_SIZE/2),
                              FxVal(ARC_STEPS*2));
        vw->pos.y += FxMulDiv(FaSin(vw->ang),
                              FxFltVal(PI*MAZE_CELL_SIZE/2),
                              FxVal(ARC_STEPS*2));
        vw->ang = FaAdd(vw->ang, sol->dir_sign*FaDeg(ARC_STEP));
        return NULL;

    case ANI_STATE_TURN_AWAY:
        vw->pos.x += FxMulDiv(FaCos(vw->ang),
                              FxFltVal(PI*MAZE_CELL_SIZE/2),
                              FxVal(ARC_STEPS*2));
        vw->pos.y += FxMulDiv(FaSin(vw->ang),
                              FxFltVal(PI*MAZE_CELL_SIZE/2),
                              FxVal(ARC_STEPS*2));
        vw->ang = FaAdd(vw->ang, sol->dir_sign * -FaDeg(ARC_STEP));
        return NULL;

    case ANI_STATE_FORWARD:
        vw->pos.x += FxMulDiv(FaCos(vw->ang), MAZE_CELL_SIZE, ARC_STEPS);
        vw->pos.y += FxMulDiv(FaSin(vw->ang), MAZE_CELL_SIZE, ARC_STEPS);
        return NULL;

    case ANI_STATE_REVERSE:
        vw->ang = FaAdd(vw->ang, sol->dir_sign*FaDeg(REVERSE_STEP));
        return NULL;
    }

    for (i = 0; i < sol->ngoals; i++)
    {
        if (sol->clx == sol->goals[i].clx &&
            sol->cly == sol->goals[i].cly)
        {
            return &sol->goals[i];
        }
    }

    cell = MazeAt(sol->clx, sol->cly);

    dir = sol->dir;
    for (i = 0; i < SOL_DIRS-1; i++)
    {
        turn_to = sol->turn_to[dir];
        if ((dir_wall[turn_to] & cell->can_see) == 0)
        {
            /* No wall present when turned, so turn that way */
            sol->clx += dir_cloff[turn_to][0];
            sol->cly += dir_cloff[turn_to][1];
            sol->dir = turn_to;

            sol->ani_count = ARC_STEPS;
            switch(i)
            {
            case 0:
                sol->ani_state = ANI_STATE_TURN_TO;
                break;

            case 1:
                sol->ani_state = ANI_STATE_FORWARD;
                break;

            case 2:
                sol->ani_state = ANI_STATE_TURN_AWAY;
                break;
            }
            break;
        }
        else
        {
            /* Wall present, turn away and check again */
            dir = sol->turn_away[dir];
        }
    }

    if (i == SOL_DIRS-1)
    {
        /* Dead end.  Turn around */
        dir = sol->turn_to[sol->turn_to[sol->dir]];
        sol->clx += dir_cloff[dir][0];
        sol->cly += dir_cloff[dir][1];
        sol->dir = dir;

        sol->ani_state = ANI_STATE_REVERSE;
        sol->ani_count = REVERSE_STEPS;
    }

    return NULL;
}


SLVMAZE.H

#ifndef __SLVMAZE_H__
#define __SLVMAZE_H__

#define ANI_STATE_NONE          0
#define ANI_STATE_TURN_TO       1
#define ANI_STATE_TURN_AWAY     2
#define ANI_STATE_FORWARD       3
#define ANI_STATE_REVERSE       4

#define SOL_DIR_LEFT    0
#define SOL_DIR_UP      1
#define SOL_DIR_RIGHT   2
#define SOL_DIR_DOWN    3
#define SOL_DIRS        4

#define SOL_TURN_LEFT   0
#define SOL_TURN_RIGHT  1

typedef struct _MazeGoal
{
    int clx, cly;
    void *user;
} MazeGoal;

typedef struct _MazeSolution
{
    int clx, cly;
    int dir;
    Cell *maze;
    int w, h;
    MazeGoal *goals;
    int ngoals;
    int ani_state;
    int ani_count;
    int *turn_to;
    int *turn_away;
    int dir_sign;
} MazeSolution;

void SolveMazeStart(MazeView *vw,
                    Cell *maze, int w, int h,
                    IntPt2 *start, int start_dir,
                    MazeGoal *goals, int ngoals,
                    int turn_to,
                    MazeSolution *sol);
void SolveMazeSetGoals(MazeSolution *sol, MazeGoal *goals, int ngoals);
MazeGoal *SolveMazeStep(MazeView *vw, MazeSolution *sol);

#endif


SSMAZE.C

#include "pch.c"
#pragma hdrstop
#include "maze_std.h"

#define VIEW_ANG 90

float maze_height;
double view_rot;
int maze_walls_list;

extern TEX_ENV gTexEnv[];

typedef struct _FxRay2
{
    FxPt2 p;
    FxVec2 d;
} FxRay2;

BYTE maze_desc[MAZE_ARRAY][MAZE_ARRAY];

Cell maze_cells[MAZE_GRID][MAZE_GRID];
#define CellAt(x, y) (&maze_cells[y][x])

typedef struct _Wall
{
    FxPt2 f, t;
    int col;
    TEX_ENV *pTexEnv;  // points to texture environment
} Wall;

FxPt2 fmaze_pts[N_MAZE_PTS];
Wall maze[N_MAZE_WALLS];
int nwalls;

typedef struct _WallHit
{
    Cell *cell;
    int cx, cy;
    WallFlags flag;
} WallHit;

void AddObject(Object *obj, Cell *cell)
{
    obj->next = cell->contents;
    cell->contents = obj;
    obj->cell = cell;
}

void PlaceObject(Object *obj, FxValue x, FxValue y)
{
    Cell *cell;
    int cx, cy;

    cx = MfxToCell(x);
    cy = MfxToCell(y);
    cell = CellAt(cx, cy);

    obj->p.x = x;
    obj->p.y = y;

    AddObject(obj, cell);
}

void RemoveObject(Object *obj)
{
    Object *o, *op;

    if (obj->cell != NULL)
    {
        op = NULL;
        for (o = obj->cell->contents; o != obj; o = o->next)
        {
            op = o;
        }

        if (op == NULL)
        {
            obj->cell->contents = obj->next;
        }
        else
        {
            op->next = obj->next;
        }

        obj->cell = NULL;
    }
}

void MoveObject(Object *obj, FxValue x, FxValue y)
{
    int cx, cy;
    Cell *cell;

    obj->p.x = x;
    obj->p.y = y;

    cx = MfxToCell(x);
    cy = MfxToCell(y);
    cell = CellAt(cx, cy);

    if (cell == obj->cell)
    {
        return;
    }

    RemoveObject(obj);
    AddObject(obj, cell);
}

Object start_obj, end_obj;

BOOL InitMaze(IntPt2 *start_cell, MazeGoal *goals, int *ngoals)
{
    int i, j, n;
    FxPt2 p;

    if (!GenerateMaze(MAZE_GRID, MAZE_GRID, &maze_desc[0][0]))
    {
        return FALSE;
    }

    p.y = FxVal(0);
    n = 0;
    for (i = 0; i < MAZE_ARRAY; i++)
    {
        p.x = FxVal(0);
        for (j = 0; j < MAZE_ARRAY; j++)
        {
            fmaze_pts[n].x = p.x;
            fmaze_pts[n++].y = p.y;
            p.x += FMAZE_CELL_SIZE;
        }
        p.y += FMAZE_CELL_SIZE;
    }

    nwalls = 0;
    for (i = 0; i < MAZE_ARRAY; i++)
    {
        for (j = 0; j < MAZE_ARRAY; j++)
        {
            if (i < MAZE_ARRAY-1 && j < MAZE_ARRAY-1)
            {
                maze_cells[i][j].can_see = 0;
                maze_cells[i][j].contents = NULL;
                memset(maze_cells[i][j].walls, 0, 4*sizeof(Wall *));
            }

            if (maze_desc[i][j] & MAZE_WALL_HORZ)
            {
                if (j == MAZE_ARRAY-1)
                {
                    printf("MAZE_WALL_HORZ at right edge\n");
                    return FALSE;
                }

                maze[nwalls].f = fmaze_pts[i*MAZE_ARRAY+j];
                maze[nwalls].t = fmaze_pts[i*MAZE_ARRAY+j+1];
                maze[nwalls].col = (i+j+1) & 1;
                maze[nwalls].pTexEnv = &gTexEnv[TEX_WALL];

                if (i > 0)
                {
                    maze_cells[i-1][j].can_see |= MAZE_WALL_DOWN;
                    maze_cells[i-1][j].walls[WIDX_DOWN] = &maze[nwalls];
                }
                if (i < MAZE_ARRAY-1)
                {
                    maze_cells[i][j].can_see |= MAZE_WALL_UP;
                    maze_cells[i][j].walls[WIDX_UP] = &maze[nwalls];
                }

                nwalls++;
            }

            if (maze_desc[i][j] & MAZE_WALL_VERT)
            {
                if (i == MAZE_ARRAY-1)
                {
                    printf("MAZE_WALL_VERT at bottom edge\n");
                    return FALSE;
                }

                maze[nwalls].f = fmaze_pts[i*MAZE_ARRAY+j];
                maze[nwalls].t = fmaze_pts[(i+1)*MAZE_ARRAY+j];
                maze[nwalls].col = (i+j) & 1;
                maze[nwalls].pTexEnv = &gTexEnv[TEX_WALL];

                if (j > 0)
                {
                    maze_cells[i][j-1].can_see |= MAZE_WALL_RIGHT;
                    maze_cells[i][j-1].walls[WIDX_RIGHT] = &maze[nwalls];
                }
                if (j < MAZE_ARRAY-1)
                {
                    maze_cells[i][j].can_see |= MAZE_WALL_LEFT;
                    maze_cells[i][j].walls[WIDX_LEFT] = &maze[nwalls];
                }

                nwalls++;
            }
        }
    }

    // Always place the start on the left and
    // the end on the right.  This guarantees that there'll be
    // some traversing of the maze for the solution
    // Since the maze generator guarantees that the entire maze is
    // fully connected, the solution can always be found

    start_cell->x = 0;
    start_cell->y = rand() % MAZE_GRID;

    *ngoals = 1;
    goals[0].clx = MAZE_GRID-1;
    goals[0].cly = rand() % MAZE_GRID;

    start_obj.w = FMAZE_CELL_SIZE/6;
    start_obj.h = FxFltVal(.166);
    start_obj.z = FxFltVal(.5);
    start_obj.col = 12;
    start_obj.draw_style = DRAW_POLYGON;
    start_obj.pTexEnv = &gTexEnv[ TEX_START ];
    start_obj.ang = FaDeg(0);
    PlaceObject(&start_obj,
                CellToMfx(start_cell->x)+FMAZE_CELL_SIZE/2,
                CellToMfx(start_cell->y)+FMAZE_CELL_SIZE/2);

    end_obj.w = FMAZE_CELL_SIZE/6;
    end_obj.h = FxFltVal(.166);
    end_obj.z = FxFltVal(.5);
    end_obj.col = 10;
    end_obj.draw_style = DRAW_POLYGON;
    end_obj.pTexEnv = &gTexEnv[ TEX_END ];
    end_obj.ang = FaDeg(0);
    PlaceObject(&end_obj,
                CellToMfx(goals[0].clx)+FMAZE_CELL_SIZE/2,
                CellToMfx(goals[0].cly)+FMAZE_CELL_SIZE/2);

    // Reset some of the walls' textures to the OpenGL cover
    // for some variety
    i = (rand() % 5)+1;
    while (i-- > 0)
    {
        j = rand() % nwalls;
        maze[j].pTexEnv = &gTexEnv[TEX_COVER];
    }

    return TRUE;
}

#define PO_WALL 0
#define PO_PARTIAL 1
#define PO_COUNT 2

typedef struct _PaintWall
{
    Wall *wall;
} PaintWall;

typedef struct _PaintPartial
{
    Object *obj;
} PaintPartial;

typedef struct _PaintObject
{
    int type;
    union
    {
        PaintWall wall;
        PaintPartial partial;
    } u;
    FxValue depth;
    struct _PaintObject *closer;
} PaintObject;

#define N_PAINT_OBJECTS (4*MAZE_CELLS)
PaintObject paint[N_PAINT_OBJECTS];
int npaint;

void WallCoords(int x, int y, WallFlags flag, FxPt2 *f, FxPt2 *t)
{
    t->x = f->x = CellToMfx(x);
    t->y = f->y = CellToMfx(y);
    if (flag & MAZE_WALL_LEFT)
    {
        t->y += FMAZE_CELL_SIZE;
    }
    else if (flag & MAZE_WALL_UP)
    {
        t->x += FMAZE_CELL_SIZE;
    }
    else if (flag & MAZE_WALL_RIGHT)
    {
        f->x += FMAZE_CELL_SIZE;
        t->x = f->x;
        t->y += FMAZE_CELL_SIZE;
    }
    else if (flag & MAZE_WALL_DOWN)
    {
        f->y += FMAZE_CELL_SIZE;
        t->y = f->y;
        t->x += FMAZE_CELL_SIZE;
    }
}

void AddPaintWall(Cell *cell, int widx)
{
    PaintWall *pw;

    if (npaint == N_PAINT_OBJECTS)
    {
        printf("Paint list full\n");
        return;
    }

    pw = &paint[npaint].u.wall;
    paint[npaint].type = PO_WALL;
    npaint++;

    pw->wall = cell->walls[widx];
}

void AddPaintWalls(Cell *cell, WallFlags wf)
{
    if (wf & MAZE_WALL_LEFT)
    {
        AddPaintWall(cell, WIDX_LEFT);
    }
    if (wf & MAZE_WALL_RIGHT)
    {
        AddPaintWall(cell, WIDX_RIGHT);
    }
    if (wf & MAZE_WALL_DOWN)
    {
        AddPaintWall(cell, WIDX_DOWN);
    }
    if (wf & MAZE_WALL_UP)
    {
        AddPaintWall(cell, WIDX_UP);
    }
}

void AddPaintPartial(Object *obj)
{
    PaintPartial *pp;

    if (npaint == N_PAINT_OBJECTS)
    {
        printf("Paint list full\n");
        return;
    }

    pp = &paint[npaint].u.partial;
    paint[npaint].type = PO_PARTIAL;
    npaint++;

    pp->obj = obj;
}

void AddCell(int x, int y, WallFlags wf)
{
    Cell *cell;
    Object *obj;

    wf |= MAZE_CONTENTS;
    cell = CellAt(x, y);
    if ((cell->unseen & wf) == 0)
    {
        return;
    }

    AddPaintWalls(cell, (WallFlags)(wf & cell->unseen));

    if (cell->unseen & MAZE_CONTENTS)
    {
        for (obj = cell->contents; obj; obj = obj->next)
        {
            AddPaintPartial(obj);
        }
    }

    cell->unseen &= ~wf;
}

void TraceCells(FxPt2 *ip, FxVec2 *dp, WallHit *hit)
{
    int cx, cy;
    int sgnx, sgny;
    FxVec2 dg, dst;
    FxPt2 fp, g;
    WallFlags xwf, ywf, iwf, xpf, ypf;
    FxValue sx, sy;

    cx = MfxToCell(ip->x);
    cy = MfxToCell(ip->y);

    fp = *ip;

#ifdef TRACEDEB
    printf("pt %ld,%ld dp %ld,%ld\n", fp.x, fp.y, dp.x, dp.y);
#endif

    if (dp->x < 0)
    {
        g.x = CellToMfx(cx)-FX_MIN_VALUE;
        dg.x = -FMAZE_CELL_SIZE;
        sgnx = -1;
        xwf = MAZE_WALL_LEFT;
        xpf = MAZE_WALL_LEFT_PARTIAL;
    }
    else
    {
        g.x = CellToMfx(cx+1);
        dg.x = FMAZE_CELL_SIZE;
        sgnx = 1;
        xwf = MAZE_WALL_RIGHT;
        xpf = MAZE_WALL_RIGHT_PARTIAL;
        if (dp->x == 0)
        {
            xwf |= MAZE_WALL_LEFT;
            xpf |= MAZE_WALL_LEFT_PARTIAL;
        }
    }
    if (dp->y < 0)
    {
        g.y = CellToMfx(cy)-FX_MIN_VALUE;
        dg.y = -FMAZE_CELL_SIZE;
        sgny = -1;
        ywf = MAZE_WALL_UP;
        ypf = MAZE_WALL_UP_PARTIAL;
    }
    else
    {
        g.y = CellToMfx(cy+1);
        dg.y = FMAZE_CELL_SIZE;
        sgny = 1;
        ywf = MAZE_WALL_DOWN;
        ypf = MAZE_WALL_DOWN_PARTIAL;
        if (dp->y == 0)
        {
            ywf |= MAZE_WALL_UP;
            ypf |= MAZE_WALL_UP_PARTIAL;
        }
    }

    for (;;)
    {
        AddCell(cx, cy, (WallFlags)(xwf | ywf));

        dst.x = (g.x-fp.x)*sgnx;
        dst.y = (g.y-fp.y)*sgny;
        sx = FxMul(dst.x, dp->y);
        if (sx < 0)
        {
            sx = -sx;
        }
        sy = FxMul(dst.y, dp->x);
        if (sy < 0)
        {
            sy = -sy;
        }

#ifdef TRACEDEB
        printf("dx %ld, sx %ld, dy %ld, sy %ld\n", dst.x, sx, dst.y, sy);
#endif

        if (sx <= sy)
        {
            if ((maze_cells[cy][cx].can_see & xwf) &&
                (maze_cells[cy][cx].can_see & xpf) == 0)
            {
                iwf = xwf;
                break;
            }

            fp.x = g.x;
            fp.y += FxDiv(sx, dp->x)*sgnx*sgny;
            if (fp.y == g.y)
            {
                if ((maze_cells[cy][cx].can_see & ywf) &&
                    (maze_cells[cy][cx].can_see & ypf) == 0)
                {
                    iwf = ywf;
                    break;
                }
                cy += sgny;
                g.y += dg.y;
            }
            cx += sgnx;
            g.x += dg.x;
        }
        else
        {
            if ((maze_cells[cy][cx].can_see & ywf) &&
                (maze_cells[cy][cx].can_see & ypf) == 0)
            {
                iwf = ywf;
                break;
            }

            fp.y = g.y;
            fp.x += FxDiv(sy, dp->y)*sgnx*sgny;
            if (fp.x == g.x)
            {
                if ((maze_cells[cy][cx].can_see & xwf) &&
                    (maze_cells[cy][cx].can_see & xpf) == 0)
                {
                    iwf = xwf;
                    break;
                }
                cx += sgnx;
                g.x += dg.x;
            }
            cy += sgny;
            g.y += dg.y;
        }
    }
    hit->cell = CellAt(cx, cy);
    hit->cx = cx;
    hit->cy = cy;
    hit->flag = iwf;
}

void TraceView(MazeView *vw)
{
    FaAngle acc;
    FxVec2 vcc;
    WallHit hit;
    int rc;

    acc = FaAdd(vw->ang, FaDeg(VIEW_ANG)/2);

    for (rc = 0; rc < VIEW_ANG; rc++)
    {
        vcc.x = FaCos(acc);
        vcc.y = FaSin(acc);

        TraceCells(&vw->pos, &vcc, &hit);

        acc = FaAdd(acc, -FaDeg(1));
    }
}

static void WallCompute(PaintObject *po, MazeView *vw,
                        FxValue cs, FxValue sn)
{
    FxPt2 mid;
    Wall *wall;

    wall = po->u.wall.wall;

    // Compute depth at midpoint of wall
    // Eye coordinate depth increases along the X so
    // we only need to transform it

    mid.x = (wall->f.x+wall->t.x)/2-vw->pos.x;
    mid.y = (wall->f.y+wall->t.y)/2-vw->pos.y;

    po->depth = FxMul(mid.x, cs)+FxMul(mid.y, sn);
}

static void PartialCompute(PaintObject *po, MazeView *vw,
                           FxValue cs, FxValue sn)
{
    PaintPartial *pp;
    FxPt2 c;

    pp = &po->u.partial;

    // Compute depth at center of partial

    c.x = pp->obj->p.x-vw->pos.x;
    c.y = pp->obj->p.y-vw->pos.y;

    po->depth = FxMul(c.x, cs)+FxMul(c.y, sn);
}

typedef void (*PoComputeFn)(PaintObject *po, MazeView *vw,
                            FxValue cs, FxValue sn);
static PoComputeFn PoCompute[PO_COUNT] =
{
    WallCompute,
    PartialCompute
};

static float colors[17][3] =
{
    0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.5f,
    0.0f, 0.5f, 0.0f,
    0.0f, 0.5f, 0.5f,
    0.5f, 0.0f, 0.0f,
    0.5f, 0.0f, 0.5f,
    0.5f, 0.5f, 0.0f,
    0.5f, 0.5f, 0.5f,
    0.75f, 0.75f, 0.75f,
    0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 0.0f,
    1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f,
    1.0f, 1.0f, 1.0f,
    0.75f, 0.39f, 0.0f
};

#define WALL_SET 0
#define FLOOR_SET 1
#define CEILING_SET 2

static float *smooth_sets[3][2][4] =
{
    &colors[1][0], &colors[2][0], &colors[4][0], &colors[7][0],
    &colors[2][0], &colors[1][0], &colors[7][0], &colors[4][0],
    &colors[10][0], &colors[2][0], &colors[4][0], &colors[6][0],
    &colors[10][0], &colors[2][0], &colors[4][0], &colors[6][0],
    &colors[9][0], &colors[1][0], &colors[2][0], &colors[3][0],
    &colors[9][0], &colors[1][0], &colors[2][0], &colors[3][0]
};

static float *flat_sets[3][2][4] =
{
    &colors[8][0], &colors[8][0], &colors[8][0], &colors[8][0],
    &colors[15][0], &colors[15][0], &colors[15][0], &colors[15][0],
    &colors[2][0], &colors[2][0], &colors[2][0], &colors[2][0],
    &colors[2][0], &colors[2][0], &colors[2][0], &colors[2][0],
    &colors[9][0], &colors[9][0], &colors[9][0], &colors[9][0],
    &colors[9][0], &colors[9][0], &colors[9][0], &colors[9][0]
};

void SetAlphaCol(GLfloat *fv3)
{
    if (maze_options.all_alpha)
    {
        GLfloat fv4[4];

        fv4[0] = fv3[0];
        fv4[1] = fv3[1];
        fv4[2] = fv3[2];
        fv4[3] = 0.5f;
        glColor4fv(fv4);
    }
    else
    {
        glColor3fv(fv3);
    }
}

static void WallDraw(PaintObject *po, MazeView *vw)
{
    Wall *wall;
    float fx, fy, tx, ty, cx, cy, nx, ny;
    float **col_set;
    int reps;
    int rept;
    GLenum old_env;

    wall = po->u.wall.wall;
    reps = wall->pTexEnv->texRep.x;
    rept = wall->pTexEnv->texRep.y;

    fx = (float)FxFlt(wall->f.x);
    fy = (float)FxFlt(wall->f.y);
    tx = (float)FxFlt(wall->t.x);
    ty = (float)FxFlt(wall->t.y);
    nx = -(ty-fy);
    ny = (tx-fx);
    cx = (float)FxFlt(vw->pos.x);
    cy = (float)FxFlt(vw->pos.y);

    col_set = &flat_sets[WALL_SET][wall->col][0];
    switch(maze_options.render[WALLS])
    {
    case RENDER_NONE:
        return;
    case RENDER_SMOOTH:
        col_set = &smooth_sets[WALL_SET][wall->col][0];
        break;
    case RENDER_FLAT:
    case RENDER_TEXTURED:
        break;
    }

    // Compute dot product with wall normal to determine
    // wall direction.  We need to know the wall direction
    // in order to ensure that the wall texture faces the
    // correct direction
    UseTextureEnv(wall->pTexEnv);

    if (wall->pTexEnv->bTransp)
    {
        if (!maze_options.all_alpha)
        {
            glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &old_env);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, gTexEnvMode);
            glEnable(GL_BLEND);
        }
    }

    glBegin(GL_POLYGON);
    if ((fx-cx)*nx+(fy-cy)*ny > 0)
    {
        glTexCoord2d(0, 0);
        SetAlphaCol(col_set[0]);
        glVertex3f(fx, fy, 0.0f);
        glTexCoord2d(reps, 0);
        SetAlphaCol(col_set[1]);
        glVertex3f(tx, ty, 0.0f);
        glTexCoord2d(reps, rept);
        SetAlphaCol(col_set[2]);
        glVertex3f(tx, ty, maze_height);
        glTexCoord2d(0, rept);
        SetAlphaCol(col_set[3]);
        glVertex3f(fx, fy, maze_height);
    }
    else
    {
        glTexCoord2d(reps, 0);
        SetAlphaCol(col_set[0]);
        glVertex3f(fx, fy, 0.0f);
        glTexCoord2d(0, 0);
        SetAlphaCol(col_set[1]);
        glVertex3f(tx, ty, 0.0f);
        glTexCoord2d(0, rept);
        SetAlphaCol(col_set[2]);
        glVertex3f(tx, ty, maze_height);
        glTexCoord2d(reps, rept);
        SetAlphaCol(col_set[3]);
        glVertex3f(fx, fy, maze_height);
    }
    glEnd();

    if (wall->pTexEnv->bTransp)
    {
        if (!maze_options.all_alpha)
        {
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, old_env);
            glDisable(GL_BLEND);
        }
    }
}

void (APIENTRY *convex_solids[SPECIAL_ARG_COUNT])(GLdouble radius) =
{
    auxSolidIcosahedron,
    auxSolidOctahedron,
    auxSolidDodecahedron,
    auxSolidTetrahedron
};

static void PartialDraw(PaintObject *po, MazeView *vw)
{
    PaintPartial *pp;
    float w, h, cx, cy, cz, vx, vy, fx, fy, fz, tx, ty, tz;
    float cs, sn;
    GLenum old_env;

    pp = &po->u.partial;

    w = (float)FxFlt(pp->obj->w);
    h = (float)FxFlt(pp->obj->h);

    // Partials are billboarded so we want it to always be
    // perpendicular to the view direction

    cs = (float)FxFlt(FaCos(vw->ang));
    sn = (float)FxFlt(FaSin(vw->ang));
    vx = -sn*w;
    vy = cs*w;

    cx = (float)FxFlt(pp->obj->p.x);
    cy = (float)FxFlt(pp->obj->p.y);
    cz = (float)FxFlt(pp->obj->z);

    fx = cx-vx;
    fy = cy-vy;
    fz = (cz-h)*maze_height;
    tx = cx+vx;
    ty = cy+vy;
    tz = (cz+h)*maze_height;

    if (maze_options.render[WALLS] == RENDER_TEXTURED)
    {
        glDisable(GL_TEXTURE_2D);
    }

    switch(pp->obj->draw_style)
    {
    case DRAW_POLYGON:
        glEnable(GL_TEXTURE_2D);
        if (!maze_options.all_alpha)
        {
            glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &old_env);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, gTexEnvMode);
            glEnable(GL_BLEND);
        }
        UseTextureEnv( pp->obj->pTexEnv );
        SetAlphaCol(colors[15]);
        glBegin(GL_POLYGON);
        glNormal3f(cs, sn, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(fx, fy, fz);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(tx, ty, fz);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(tx, ty, tz);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(fx, fy, tz);
        glEnd();
        glDisable(GL_TEXTURE_2D);
        if (!maze_options.all_alpha)
        {
            glDisable(GL_BLEND);
            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, old_env);
        }
        break;

    case DRAW_SPECIAL:
        SetAlphaCol(colors[pp->obj->col]);

        glEnable(GL_AUTO_NORMAL);
        glEnable(GL_NORMALIZE);
        glEnable(GL_LIGHTING);
        glEnable(GL_CULL_FACE);
        glEnable(GL_DITHER);
        glPushMatrix();

        glTranslated(cx, cy, cz*maze_height);
        glScaled(1.0, 1.0, maze_height);
        glRotated(FaFltDegVal(pp->obj->ang), 0, 0, 1);
        glRotated(pp->obj->user3, 0, 1, 0);
        // Must use convex objects since depth testing can be off
        convex_solids[pp->obj->draw_arg](w);

        glPopMatrix();
        if( !maze_options.bDither )
            glDisable(GL_DITHER);
        glDisable(GL_CULL_FACE);
        glDisable(GL_LIGHTING);
        glDisable(GL_AUTO_NORMAL);
        glDisable(GL_NORMALIZE);
        break;
    }

    if (maze_options.render[WALLS] == RENDER_TEXTURED)
    {
        glEnable(GL_TEXTURE_2D);
    }
}

typedef void (*PoDrawFn)(PaintObject *po, MazeView *vw);
static PoDrawFn PoDraw[PO_COUNT] =
{
    WallDraw,
    PartialDraw
};

void RenderZPlane(int render, TEX_ENV *pTexEnv, int set, float zval)
{
    float **col_set;
    int reps = pTexEnv->texRep.x;
    int rept = pTexEnv->texRep.y;

    switch(render)
    {
    case RENDER_NONE:
        break;
    case RENDER_TEXTURED:
        UseTextureEnv(pTexEnv);
        glEnable(GL_TEXTURE_2D);
        // Fall through
    case RENDER_FLAT:
    case RENDER_SMOOTH:
        col_set = &flat_sets[set][0][0];
        if (render == RENDER_SMOOTH)
        {
            col_set = &smooth_sets[set][0][0];
        }

        glBegin(GL_POLYGON);

        // Switch texture orientation dependent on surface type
        if( set == CEILING_SET ) {
            glTexCoord2f((float)reps*MAZE_SIZE, 0.0f);
            glColor3fv(col_set[0]);
            glVertex3f(0.0f, 0.0f, zval);
            glTexCoord2f(0.0f, 0.0f);
            glColor3fv(col_set[1]);
            glVertex3f((float)MAZE_SIZE, 0.0f, zval);
            glTexCoord2f(0.0f, (float)rept*MAZE_SIZE);
            glColor3fv(col_set[2]);
            glVertex3f((float)MAZE_SIZE, (float)MAZE_SIZE, zval);
            glTexCoord2f((float)reps*MAZE_SIZE, (float)rept*MAZE_SIZE);
            glColor3fv(col_set[3]);
            glVertex3f(0.0f, (float)MAZE_SIZE, zval);
        } else {
            glTexCoord2f(0.0f, 0.0f);
            glColor3fv(col_set[0]);
            glVertex3f(0.0f, 0.0f, zval);
            glTexCoord2f((float)reps*MAZE_SIZE, 0.0f);
            glColor3fv(col_set[1]);
            glVertex3f((float)MAZE_SIZE, 0.0f, zval);
            glTexCoord2f((float)reps*MAZE_SIZE, (float)rept*MAZE_SIZE);
            glColor3fv(col_set[2]);
            glVertex3f((float)MAZE_SIZE, (float)MAZE_SIZE, zval);
            glTexCoord2f(0.0f, (float)rept*MAZE_SIZE);
            glColor3fv(col_set[3]);
            glVertex3f(0.0f, (float)MAZE_SIZE, zval);
        }

        glEnd();

        if (render == RENDER_TEXTURED)
        {
            glDisable(GL_TEXTURE_2D);
        }
        break;
    }
}

void Render(MazeView *vw)
{
    FxValue cs, sn;
    PaintObject *sorted, *so, *pso;
    PaintObject *po;
    int i;
    FxPt2 at;
    BOOL special;
    float viewHeight;

    cs = FaCos(vw->ang);
    sn = FaSin(vw->ang);

    at.x = vw->pos.x+cs;
    at.y = vw->pos.y+sn;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glRotated(view_rot, 0, 0, 1);
    gluPerspective(VIEW_ANG, 1, .01, 100);
    viewHeight = 0.5f;
    gluLookAt(FxFlt(vw->pos.x), FxFlt(vw->pos.y), viewHeight,
              FxFlt(at.x), FxFlt(at.y), viewHeight,
              0, 0, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    RenderZPlane(maze_options.render[FLOOR], &gTexEnv[TEX_FLOOR], FLOOR_SET, 0.0f);
    RenderZPlane(maze_options.render[CEILING], &gTexEnv[TEX_CEILING], CEILING_SET, 1.0f);

    sorted = NULL;
    special = FALSE;
    for (i = 0, po = paint; i < npaint; i++, po++)
    {
        if (po->type == PO_PARTIAL &&
            po->u.partial.obj->draw_style == DRAW_SPECIAL)
        {
            special = TRUE;
        }

        PoCompute[po->type](po, vw, cs, sn);

        for (so = sorted, pso = NULL; so; pso = so, so = so->closer)
        {
            if (so->depth <= po->depth)
            {
                break;
            }
        }
        if (pso == NULL)
        {
            sorted = po;
        }
        else
        {
            pso->closer = po;
        }
        po->closer = so;
    }

    if (maze_options.render[WALLS] == RENDER_TEXTURED)
    {
        glEnable(GL_TEXTURE_2D);
    }

    for (so = sorted; so; so = so->closer)
    {
        PoDraw[so->type](so, vw);
    }

    if (maze_options.render[WALLS] == RENDER_TEXTURED)
    {
        glDisable(GL_TEXTURE_2D);
    }
}

void InitPaint(void)
{
    int i, j;

    npaint = 0;
    for (i = 0; i < MAZE_GRID; i++)
    {
        for (j = 0; j < MAZE_GRID; j++)
        {
            maze_cells[i][j].unseen = maze_cells[i][j].can_see | MAZE_CONTENTS;
        }
    }
}

void DrawMaze(MazeView *vw)
{
    InitPaint();
    TraceView(vw);
    Render(vw);
}

void DrawMazeWalls(void)
{
    int w;
    Wall *wall;

    wall = maze;

    glColor3f(1.0f, 1.0f, 1.0f);

    glBegin(GL_LINES);
    for (w = 0; w < nwalls; w++)
    {
        glVertex2f((float)FxFltVal(wall->f.x), (float)FxFltVal(wall->f.y));
        glVertex2f((float)FxFltVal(wall->t.x), (float)FxFltVal(wall->t.y));
        wall++;
    }
    glEnd();
}

#define SQRT2_2 0.707107f

void DrawTopView(MazeView *vw)
{
    int c;
    Cell *cell;
    Object *obj;
    float vx, vy, cx, cy, width, ang;
    extern float gfAspect;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D( -MAZE_SIZE/2.0, MAZE_SIZE/2.0,
            -MAZE_SIZE/2.0/gfAspect, MAZE_SIZE/2.0/gfAspect );
    glMatrixMode(GL_MODELVIEW);

    glPushMatrix();

    ang = (float)FaFltDegVal(vw->ang)+90.0f;
    glRotatef(ang, 0.0f, 0.0f, 1.0f);
    vx = (float)FxFltVal(vw->pos.x);
    vy = (float)FxFltVal(vw->pos.y);
    glTranslatef(-vx, -vy, 0.0f);

#define AA_LINES 1
#ifdef AA_LINES
    // Turn on antialiased lines
    glEnable( GL_BLEND );
    glEnable( GL_LINE_SMOOTH );
    glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
#endif

    glCallList(maze_walls_list);

#ifdef AA_LINES
    glDisable( GL_BLEND );
    glDisable( GL_LINE_SMOOTH );
#endif

    // Objects aren't put in the walls display list so that they
    // can move around

    cell = &maze_cells[0][0];
    for (c = 0; c < MAZE_CELLS; c++)
    {
        for (obj = cell->contents; obj != NULL; obj = obj->next)
        {
            cx = (float)FxFltVal(obj->p.x);
            cy = (float)FxFltVal(obj->p.y);
            width = (float)FxFltVal(obj->w);

            glColor3fv(colors[obj->col]);

            glPushMatrix();
            glTranslatef(cx, cy, 0.0f);
            glRotated(FaFltDegVal(obj->ang), 0, 0, 1);
            glBegin(GL_POLYGON);
            glVertex2f(width, 0.0f);
            glVertex2f(-width*SQRT2_2, width*0.5f);
            glVertex2f(-width*SQRT2_2, -width*0.5f);
            glEnd();
            glPopMatrix();
        }

        cell++;
    }

    glPopMatrix();

    // Draw self
    glColor3f(0.0f, 0.0f, 1.0f);
    width = MAZE_CELL_SIZE/4.0f;
    glBegin(GL_POLYGON);
    glVertex2f(0.0f, width);
    glVertex2f(width*0.5f, -width*SQRT2_2);
    glVertex2f(-width*0.5f, -width*SQRT2_2);
    glEnd();
}


DIALOG.C

/******************************Module*Header*******************************\
* Module Name: sspipes.c
*
* Message loop and dialog box for the OpenGL-based 3D Pipes screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys\timeb.h>
#include <time.h>
#include <commctrl.h>
#include "sscommon.h"
#include "sspipes.h"
#include "dlgs.h"
#include "dialog.h"

BOOL bFlexMode;
BOOL bMultiPipes;

// ulJointType controls the style of the elbows.

ULONG ulJointType = JOINT_ELBOW;

// ulSurfStyle determines whether the pipe surfaces are textured.

ULONG ulSurfStyle = SURFSTYLE_SOLID;

// ulTexQuality control the texture quality.

ULONG ulTexQuality = TEXQUAL_DEFAULT;

// fTesselFact controls the how finely the surface is tesselated.  It
// varies from very course (0.0) to very fine (2.0).

float fTesselFact = 1.0f;

// If ulSurfStyle indicates a textured surface, szTexPathname specifies
// the bitmap chosen as the texture.

// Texture file(s)
TEXFILE gTexFile[MAX_TEXTURES] = {0};
int gnTextures = 0;

static void updateDialogControls(HWND hDlg);

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
\**************************************************************************/

void
getIniSettings()
{
    int    tessel;

    // Load resources

    LoadString(hMainInstance, IDS_GENNAME, szScreenSaver,
               sizeof(szScreenSaver) / sizeof(TCHAR));

    // Load resource strings for texture processing

    ss_LoadTextureResourceStrings();

    // Get registry settings

    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        ulJointType = ss_GetRegistryInt( IDS_JOINTTYPE, JOINT_ELBOW );

        ulSurfStyle = ss_GetRegistryInt( IDS_SURFSTYLE, SURFSTYLE_SOLID );

        ulTexQuality = ss_GetRegistryInt( IDS_TEXQUAL, TEXQUAL_DEFAULT );

        tessel = ss_GetRegistryInt( IDS_TESSELATION, 0 );
        SS_CLAMP_TO_RANGE2( tessel, 0, 200 );
        fTesselFact  = (float)tessel / 100.0f;

        bFlexMode = ss_GetRegistryInt( IDS_FLEX, 0 );

        bMultiPipes = ss_GetRegistryInt( IDS_MULTIPIPES, 0 );

        // Get any textures

#ifndef NEW_TEXTURE
        // Just get one texture with old registry names
        ss_GetRegistryString( IDS_TEXTURE, 0, gTexFile[0].szPathName, MAX_PATH);
        gTexFile[0].nOffset = ss_GetRegistryInt( IDS_TEXTURE_FILE_OFFSET, 0 );
        gnTextures = 1;
#else
        gnTextures = ss_GetRegistryInt( IDS_TEXTURE_COUNT, 0 );
        SS_CLAMP_TO_RANGE2( gnTextures, 0, MAX_TEXTURES );

        idsTexture = IDS_TEXTURE0;
        idsTexOffset = IDS_TEXOFFSET0;
        for( i = 0; i < gnTextures; i++, idsTexture++, idsTexOffset++ ) {
            ss_GetRegistryString( idsTexture, 0, gTexFile[i].szPathName,
                                  MAX_PATH);
            gTexFile[i].nOffset = ss_GetRegistryInt( idsTexOffset, 0 );
        }
#endif
    }
}

/**************************************************************************\
* ConfigInit
*
\**************************************************************************/
BOOL
ss_ConfigInit( HWND hDlg )
{
    return TRUE;
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
*
\**************************************************************************/

static void saveIniSettings(HWND hDlg)
{
    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        ss_WriteRegistryInt( IDS_JOINTTYPE, ulJointType );
        ss_WriteRegistryInt( IDS_SURFSTYLE, ulSurfStyle );
        ss_WriteRegistryInt( IDS_TEXQUAL, ulTexQuality );
        ss_WriteRegistryInt( IDS_TESSELATION,
                    ss_GetTrackbarPos(hDlg, DLG_SETUP_TESSEL) );
        ss_WriteRegistryInt( IDS_FLEX, bFlexMode );
        ss_WriteRegistryInt( IDS_MULTIPIPES, bMultiPipes );
#ifndef NEW_TEXTURE
        ss_WriteRegistryString( IDS_TEXTURE, gTexFile[0].szPathName );
        ss_WriteRegistryInt( IDS_TEXTURE_FILE_OFFSET, gTexFile[0].nOffset );
#else
        idsTexture = IDS_TEXTURE0;
        idsTexOffset = IDS_TEXOFFSET0;
        for( i = 0; i < gnTextures; i++, idsTexture++, idsTexOffset++ ) {
            ss_WriteRegistryString( idsTexture, gTexFile[i].szPathName );
            ss_WriteRegistryInt( idsTexOffset, gTexFile[i].nOffset );
        }
#endif
    }
}

/******************************Public*Routine******************************\
* setupDialogControls
*
* Do initial setup of dialog controls.
\**************************************************************************/

static void
setupDialogControls(HWND hDlg)
{
    int pos;
    int wTmp;
    TCHAR szStr[GEN_STRING_SIZE];
    int idsJointType;

    InitCommonControls();

    pos = (int)(fTesselFact * 100.0f);
    ss_SetupTrackbar( hDlg, DLG_SETUP_TESSEL, 0, 200, 1, 10, pos );

    // setup jointType combo box
    idsJointType = IDS_JOINT_ELBOW;
    for (wTmp = 0; wTmp < NUM_JOINTTYPES; wTmp++, idsJointType++) {
        LoadString(hMainInstance, idsJointType, szStr,
                    GEN_STRING_SIZE);
        SendDlgItemMessage(hDlg, DLG_COMBO_JOINTTYPE, CB_ADDSTRING, 0,
                           (LPARAM) szStr);
    }
    SendDlgItemMessage(hDlg, DLG_COMBO_JOINTTYPE, CB_SETCURSEL,
                       ulJointType, 0);

    updateDialogControls( hDlg );
}

/******************************Public*Routine******************************\
* updateDialogControls
*
* Setup the dialog controls based on the current global state.
*
\**************************************************************************/

static void updateDialogControls(HWND hDlg)
{
    BOOL bTexture = (ulSurfStyle ==  SURFSTYLE_TEX);

    CheckDlgButton( hDlg, IDC_RADIO_SOLID, ulSurfStyle == SURFSTYLE_SOLID );
    CheckDlgButton( hDlg, IDC_RADIO_TEX  , bTexture );
#ifdef NEED_THESE_LATER
    CheckDlgButton( hDlg, IDC_RADIO_WIREFRAME, ulSurfStyle == SURFSTYLE_WIREFRAME   );

    CheckDlgButton( hDlg, IDC_RADIO_TEXQUAL_DEFAULT ,
                   bTexture && ulTexQuality == TEXQUAL_DEFAULT);
    CheckDlgButton( hDlg, IDC_RADIO_TEXQUAL_HIGH,
                   bTexture && ulTexQuality == TEXQUAL_HIGH);
#endif

    CheckDlgButton( hDlg, IDC_RADIO_NORMAL, !bFlexMode);
    CheckDlgButton( hDlg, IDC_RADIO_FLEX, bFlexMode);

    CheckDlgButton( hDlg, IDC_RADIO_SINGLE_PIPE, !bMultiPipes);
    CheckDlgButton( hDlg, IDC_RADIO_MULTIPLE_PIPES, bMultiPipes);

    EnableWindow( GetDlgItem(hDlg, DLG_COMBO_JOINTTYPE), !bFlexMode);
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_JOINTTYPE), !bFlexMode);

    EnableWindow( GetDlgItem(hDlg, DLG_SETUP_TEXTURE), bTexture );
    EnableWindow( GetDlgItem(hDlg, IDC_RADIO_TEXQUAL_DEFAULT), bTexture );
    EnableWindow( GetDlgItem(hDlg, IDC_RADIO_TEXQUAL_HIGH), bTexture );
    EnableWindow( GetDlgItem(hDlg, IDC_STATIC_TEXQUAL_GRP), bTexture );

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TESSEL), TRUE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MIN), TRUE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_MAX), TRUE);
    EnableWindow(GetDlgItem(hDlg, IDC_STATIC_TESS_GRP), TRUE);
}

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

/******************************Public*Routine******************************\
* ScreenSaverConfigureDialog
*
* Screen saver setup dialog box procedure.
\**************************************************************************/

BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{
    int optMask = 1;

    switch (message)
    {
        case WM_INITDIALOG:
            getIniSettings();
            setupDialogControls(hDlg);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDC_RADIO_SOLID:
                case IDC_RADIO_TEX:
                case IDC_RADIO_WIREFRAME:
                    ulSurfStyle = IDC_TO_SURFSTYLE(LOWORD(wParam));
                    break;

                case IDC_RADIO_TEXQUAL_DEFAULT:
                case IDC_RADIO_TEXQUAL_HIGH:
                    ulTexQuality = IDC_TO_TEXQUAL(LOWORD(wParam));
                    break;

                case IDC_RADIO_NORMAL:
                    bFlexMode = FALSE;
                    break;
                case IDC_RADIO_FLEX:
                    bFlexMode = TRUE;
                    break;

                case IDC_RADIO_SINGLE_PIPE:
                    bMultiPipes = FALSE;
                    break;
                case IDC_RADIO_MULTIPLE_PIPES:
                    bMultiPipes = TRUE;
                    break;

                case DLG_SETUP_TEXTURE:
                    // Run choose texture dialog
                    ss_SelectTextureFile( hDlg, &gTexFile[0] );
                    break;

                case DLG_COMBO_JOINTTYPE:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                            ulJointType =
                                SendDlgItemMessage(hDlg, DLG_COMBO_JOINTTYPE,
                                                      CB_GETCURSEL, 0, 0);
                            break;
                        default:
                            return FALSE;
                    }
                    break;

                case IDOK:
                    saveIniSettings(hDlg);
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;

                default:
                    break;
            }
            updateDialogControls(hDlg);
            return TRUE;

        default:
            return 0;
    }
    return 0;
}


DIALOG.H

/******************************Module*Header*******************************\
* Module Name: dialog.h
*
* Externals from dialog.c
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __dialog_h__
#define __dialog_h__

#include "dlgs.h"
#include "sscommon.h"

#ifdef __cplusplus
extern "C" {
#endif

extern float fTesselFact;
extern ULONG ulJointType;
extern ULONG ulSurfStyle;
extern ULONG ulTexQuality;
extern TEXFILE gTexFile[];
extern int gnTextures;

extern BOOL  bFlexMode;
extern BOOL  bMultiPipes;

extern void getIniSettings(void);

// surface styles
enum {
    SURFSTYLE_SOLID = 0,
    SURFSTYLE_TEX,
    SURFSTYLE_WIREFRAME
};

// Resource constants

#define IDS_SAVERNAME           1002
#define IDS_JOINTTYPE           1008
#define IDS_SURFSTYLE           1009
#define IDS_TEXQUAL             1010

#define IDS_FLEX                1020
#define IDS_MULTIPIPES          1021

// should get rid of OFFSET...
#define IDS_TEXTURE_COUNT       1029
#define IDS_TEXTURE0            1030
#define IDS_TEXTURE1            1031
#define IDS_TEXTURE2            1032
#define IDS_TEXTURE3            1033
#define IDS_TEXTURE4            1034
#define IDS_TEXTURE5            1035
#define IDS_TEXTURE6            1036
#define IDS_TEXTURE7            1037
#define IDS_TEXOFFSET0          1050
#define IDS_TEXOFFSET1          1051
#define IDS_TEXOFFSET2          1052
#define IDS_TEXOFFSET3          1053
#define IDS_TEXOFFSET4          1054
#define IDS_TEXOFFSET5          1055
#define IDS_TEXOFFSET6          1056
#define IDS_TEXOFFSET7          1057

#define IDS_JOINT_ELBOW         1100
#define IDS_JOINT_BALL          1101
#define IDS_JOINT_MIXED         1102
#define IDS_JOINT_CYCLE         1103

#define DLG_SETUP_TESSEL        2001
#define DLG_SETUP_TEXTURE       2002
#define IDC_STATIC_TESS         2003
#define IDC_STATIC_TESS_MIN     2004
#define IDC_STATIC_TESS_MAX     2005
#define IDC_STATIC_TESS_GRP     2006
#define IDC_STATIC_TEXQUAL_GRP  2007

#define IDC_RADIO_SINGLE_PIPE       3000
#define IDC_RADIO_MULTIPLE_PIPES    3001

#define IDC_RADIO_NORMAL        3100
#define IDC_RADIO_FLEX          3101

#define IDC_STATIC_JOINTTYPE       2106

#define DLG_COMBO_JOINTTYPE     2200

enum {
    JOINT_ELBOW=0,
    JOINT_BALL,
    JOINT_MIXED,
    JOINT_CYCLE,
    NUM_JOINTTYPES
};

// In order for the IDC_TO_SURFSTYLE conversion macro to work, the radio buttons
// for surface styles must be kept contiguous.

#define IDC_RADIO_SOLID         2111
#define IDC_RADIO_TEX           2112
#define IDC_RADIO_WIREFRAME     2113
#define IDC_TO_SURFSTYLE(n)          ( (n) - IDC_RADIO_SOLID )

// In order for the IDC_TO_TEXQUAL conversion macro to work, the radio buttons
// for texture quality must be kept contiguous.

#define IDC_RADIO_TEXQUAL_DEFAULT   2121
#define IDC_RADIO_TEXQUAL_HIGH  2122
#define IDC_TO_TEXQUAL(n)       ( (n) - IDC_RADIO_TEXQUAL_DEFAULT )

#define TEXQUAL_DEFAULT         0
#define TEXQUAL_HIGH            1

// texture resources
#define IDB_DEFTEX              99
#define IDB_DEFTEX2            100

// multi texture dialog

#ifdef __cplusplus
}
#endif

#endif // __dialog_h__


EVAL.CXX

/******************************Module*Header*******************************\
* Module Name: eval.cxx
*
* Evaluator stuff
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include "sscommon.h"
#include "sspipes.h"
#include "eval.h"

//#define EVAL_DBG 1

typedef enum {
    X_PLANE = 0,
    Y_PLANE,
    Z_PLANE
};

#define EVAL_VSIZE 3  // vertex size in floats

#define TMAJOR_ORDER 2
#define TMINOR_ORDER 2

#define VDIM 3
#define TDIM 2

//forwards
#if EVAL_DBG
static void DrawPoints( int num, POINT3D *pts );
#endif
static void RotatePointSet( POINT3D *inPts, int numPts, float angle, int dir,
                      float radius, POINT3D *outPts );
static void ExtrudePointSetDir( POINT3D *inPts, int numPts, float *acPts,
                      int dir, POINT3D *outPts );

/**************************************************************************\
* EVAL
*
* Evaluator constructor
*
\**************************************************************************/

EVAL::EVAL( BOOL bTex )
{
    bTexture = bTex;

    // Allocate points buffer

// might want to use less than max in some cases
    int size = MAX_USECTIONS * MAX_UORDER * MAX_VORDER * sizeof(POINT3D);
    pts = (POINT3D *) LocalAlloc( LMEM_FIXED, size );
    SS_ASSERT( pts != NULL, "EVAL constructor\n" );

    // Alloc texture points buffer

    if( bTexture ) {
        size = MAX_USECTIONS * TEX_ORDER * TEX_ORDER * sizeof(TEX_POINT2D);
        texPts = (TEX_POINT2D *) LocalAlloc( LMEM_FIXED, size );
        SS_ASSERT( texPts != NULL, "EVAL constructor\n" );
    }

    ResetEvaluator( bTexture );
}

/**************************************************************************\
* ~EVAL
*
* Evaluator destructor
*
* Frees up memory
*
\**************************************************************************/

EVAL::~EVAL( )
{
    LocalFree( pts );
    if( bTexture )
        LocalFree( texPts );
}

/**************************************************************************\
* Reset
*
* Reset evaluator to generate 3d vertices and vertex normals
*
\**************************************************************************/

void
ResetEvaluator( BOOL bTexture )
{
    if( bTexture ) {
        glEnable( GL_MAP2_TEXTURE_COORD_2 );
    }
    glEnable( GL_MAP2_VERTEX_3 );
    glEnable( GL_AUTO_NORMAL );
    glFrontFace( GL_CW ); // cuz
// if mixing Normal and Flex, have to watch out for this, cuz normal
// needs CCW
}

/**************************************************************************\
* SetTextureControlPoints
*
* Set texture control point net
*
* This sets up 'numSections' sets of texture coordinate control points, based
* on starting and ending s and t values.
*
* s coords run along pipe direction, t coords run around circumference
*
\**************************************************************************/

void
EVAL::SetTextureControlPoints( float s_start, float s_end,
                              float t_start, float t_end )
{
    int i;
    TEX_POINT2D *ptexPts = texPts;
    GLfloat t_delta = (t_end - t_start) / numSections;
    GLfloat t = t_start;

    // calc ctrl pts for each quadrant
    for( i = 0; i < numSections; i++, ptexPts += (TDIM*TDIM) ) {
        // s, t coords
        ptexPts[0].t = ptexPts[2].t = t;
        t += t_delta;
        ptexPts[1].t = ptexPts[3].t = t;
        ptexPts[0].s = ptexPts[1].s = s_start;
        ptexPts[2].s = ptexPts[3].s = s_end;
    }
}

/**************************************************************************\
* SetVertexCtrlPtsXCTranslate
*
* Builds 3D control eval control net from 2 xcObjs displaced along the
* z-axis by 'length'.
*
* First xc used to generate points in z=0 plane.
* Second xc generates points in z=length plane.
* ! Replicates the last point around each u.
*
\**************************************************************************/

void
EVAL::SetVertexCtrlPtsXCTranslate( POINT3D *pts, float length,
                             XC *xcStart, XC *xcEnd )
{
    int i;
    POINT2D *ptsStart, *ptsEnd;
    POINT3D *pts1, *pts2;
    int     numPts = xcStart->numPts;

    numPts++;  // due to last point replication

    ptsStart = xcStart->pts;
    ptsEnd   = xcEnd->pts;
    pts1     = pts;
    pts2     = pts + numPts;

    for( i = 0; i < (numPts-1); i++, pts1++, pts2++ ) {
        // copy over x,y from each xc
        *( (POINT2D *) pts1) = *ptsStart++;
        *( (POINT2D *) pts2) = *ptsEnd++;
        // set z for each
        pts1->z = 0.0f;
        pts2->z = length;
    }

    // Replicate last point in each u-band
    *pts1 = *pts;
    *pts2 = *(pts + numPts);
}

/**************************************************************************\
* ProcessXCPrimLinear
*
* Processes a prim according to evaluator data
*
* - Only valid for colinear xc's (along z)
* - XC's may be identical (extrusion).  If not identical, may have
*   discontinuities at each end.
* - Converts 2D XC pts to 3D pts
*
\**************************************************************************/

void
EVAL::ProcessXCPrimLinear( XC *xcStart, XC *xcEnd, float length )
{
    if( length <= 0.0f )
        // nuttin' to do
        return;

    // Build a vertex control net from 2 xcObj's a distance 'length' apart
    // this will displace the end xcObj a distance 'length' down the z-axis
    SetVertexCtrlPtsXCTranslate( pts, length, xcStart, xcEnd );

    Evaluate( );
}

/**************************************************************************\
* ProcessXCPrimBendSimple
*
* Processes a prim by bending along dir from xcCur
*
* - dir is relative from xc in x-y plane
* - adds C2 continuity at ends
*
\**************************************************************************/

void
EVAL::ProcessXCPrimBendSimple( XC *xcCur, int dir, float radius )
{
    POINT3D *ptsSrc, *ptsDst;
    static float acPts[MAX_XC_PTS+1];
    int ptSetStride = xcCur->numPts + 1; // pt stride for output pts buffer

    // We will be creating 4 cross-sectional control point sets here.

    // Convert 2D pts in xcCur to 3D pts at z=0 for 1st point set
    xcCur->ConvertPtsZ( pts, 0.0f );

    // Calc 4th point set by rotating 1st set as per dir
    ptsDst = pts + 3*ptSetStride;
    RotatePointSet( pts, ptSetStride, 90.0f, dir, radius, ptsDst );

    // angles != 90, hard, cuz not easy to extrude 3rd set from 4th

    // Next, have to figure out ac values.  Need to extend each xc's points
    // into bend to generate ac net.  For circular bend (and later for general
    // case elliptical bend), need to know ac distance from xc for each point.
    // This is based on the point's turn radius - a function of its distance
    // from the 'hinge' of the turn.

    // Can take advantage of symmetry here.  Figure for one xc, good for 2nd.
    // This assumes 90 deg turn.  (also,last point replicated)
    xcCur->CalcArcACValues90( dir, radius, acPts );

    // 2) extrude each point's ac from xcCur (extrusion in +z)
    // apply values to 1st to get 2nd
    // MINUS_Z, cuz subtracts *back* from dir
    ExtrudePointSetDir( pts, ptSetStride, acPts, MINUS_Z,
                                                    pts + ptSetStride );

    // 3) extrude each point's ac from xcEnd (extrusion in -dir)
    ptsSrc = pts + 3*ptSetStride;
    ptsDst = pts + 2*ptSetStride;
    ExtrudePointSetDir( ptsSrc, ptSetStride, acPts, dir, ptsDst );

    Evaluate();
}

/**************************************************************************\
* eval_ProcessXCPrimSingularity
*
* Processes a prim by joining singularity to an xc
*
* - Used for closing or opening the pipe
* - If bOpening is true, starts with singularity, otherwise ends with one
* - the xc side is always in z=0 plane
* - singularity side is radius on either side of xc
* - adds C2 continuity at ends (perpendicular to +z at singularity end)
*
\**************************************************************************/

void
EVAL::ProcessXCPrimSingularity( XC *xcCur, float length, BOOL bOpening )
{
    POINT3D *ptsSing, *ptsXC;
    static float acPts[MAX_XC_PTS+1];
    float zSing; // z-value at singularity
    int ptSetStride = xcCur->numPts + 1; // pt stride for output pts buffer
    int i;
    XC xcSing(xcCur);

    // create singularity xc - which is an extremely scaled-down version
    //  of xcCur (this prevents any end-artifacts, unless of course we were
    //  to zoom it ultra-large).

    xcSing.Scale( .0005f );

    // We will be creating 4 cross-sectional control point sets here.

    if( bOpening ) {
        ptsSing = pts;
        ptsXC = pts + 3*ptSetStride;
    } else {
        ptsSing = pts + 3*ptSetStride;
        ptsXC = pts;
    }

    // Convert 2D pts in xcCur to 3D pts at 'xc' point set
    xcCur->ConvertPtsZ( ptsXC, 0.0f );

    // Set z-value for singularity point set
    zSing = bOpening ? -length : length;
    xcSing.ConvertPtsZ( ptsSing, zSing );

    // The arc control for each point is based on a radius value that is
    //  each xc point's distance from the xc center
    xcCur->CalcArcACValuesByDistance( acPts );

    // Calculate point set near xc
    if( bOpening )
        ExtrudePointSetDir( ptsXC, ptSetStride, acPts, PLUS_Z,
                                                    ptsXC - ptSetStride );
    else
        ExtrudePointSetDir( ptsXC, ptSetStride, acPts, MINUS_Z,
                                                    ptsXC + ptSetStride );

    // Point set near singularity is harder, as the points must generate
    // a curve between the singularity and each xc point
    // No, easier, just scale each point by universal arc controller !
    POINT3D *ptsDst = pts;
    ptsDst = bOpening ? ptsSing + ptSetStride : ptsSing - ptSetStride;
    for( i = 0; i < ptSetStride; i ++, ptsDst++ ) {
        ptsDst->x = EVAL_CIRC_ARC_CONTROL * ptsXC[i].x;
        ptsDst->y = EVAL_CIRC_ARC_CONTROL * ptsXC[i].y;
        ptsDst->z = zSing;
    }

    Evaluate();
}

/**************************************************************************\
* Evaluate
*
* Evaluates the EVAL object
*
* - There may be 1 or more lengthwise sections around an xc
* - u is minor, v major
* - u,t run around circumference, v,s lengthwise
* - Texture maps are 2x2 for each section
* - ! uDiv is per section !
*
\**************************************************************************/

void
EVAL::Evaluate( )
{
    int i;
    POINT3D *ppts = pts;
    TEX_POINT2D *ptexPts = texPts;
    // total # pts in cross-section:
    int xcPointCount = (uOrder-1)*numSections + 1;

    for( i = 0; i < numSections; i ++,
                                       ppts += (uOrder-1),
                                       ptexPts += (TEX_ORDER*TEX_ORDER) ) {

        // map texture coords

        if( bTexture ) {
            glMap2f(GL_MAP2_TEXTURE_COORD_2,
                    0.0f, 1.0f, TDIM, TEX_ORDER,
                    0.0f, 1.0f, TEX_ORDER*TDIM, TEX_ORDER,
                    (GLfloat *) ptexPts );
        }

        // map vertices

        glMap2f(GL_MAP2_VERTEX_3,
               0.0f, 1.0f, VDIM, uOrder,
               0.0f, 1.0f, xcPointCount*VDIM, vOrder,
               (GLfloat *) ppts );

        // evaluate

        glMapGrid2f(uDiv, 0.0f, 1.0f, vDiv, 0.0f, 1.0f);
        glEvalMesh2( GL_FILL, 0, uDiv, 0, vDiv);
    }
}

#if EVAL_DBG
/**************************************************************************\
* DrawPoints
*
* draw control points
*
\**************************************************************************/
static
void DrawPoints( int num, POINT3D *pts )
{
    GLint i;

    // draw green pts for now
    glColor3f(0.0f, 1.0f, 0.0f);
    glPointSize(2);

    glBegin(GL_POINTS);
        for (i = 0; i < num; i++, pts++) {
            glVertex3fv( (GLfloat *) pts );
        }
    glEnd();
}
#endif

/**************************************************************************\
* ExtrudePointSetDir
*
* Extrude a point set back from the current direction
*
* Generates C2 continuity at the supplied point set xc, by generating another
* point set back of the first, using supplied subtraction values.
*
\**************************************************************************/

static void
ExtrudePointSetDir( POINT3D *inPts, int numPts, float *acPts, int dir,
                    POINT3D *outPts )
{
    int i;
    float sign;
    int offset;

    switch( dir ) {
        case PLUS_X:
            offset = 0;
            sign = -1.0f;
            break;
        case MINUS_X:
            offset = 0;
            sign =  1.0f;
            break;
        case PLUS_Y:
            offset = 1;
            sign = -1.0f;
            break;
        case MINUS_Y:
            offset = 1;
            sign =  1.0f;
            break;
        case PLUS_Z:
            offset = 2;
            sign = -1.0f;
            break;
        case MINUS_Z:
            offset = 2;
            sign =  1.0f;
            break;
    }

    for( i = 0; i < numPts; i++, inPts++, outPts++, acPts++ ) {
        *outPts = *inPts;
        ((float *)outPts)[offset] = ((float *)inPts)[offset] + (sign * (*acPts));
    }
}

/**************************************************************************\
* RotatePointSet
*
* Rotate point set by angle, according to dir and radius
*
* - Put points in supplied outPts buffer
*
\**************************************************************************/

static void
RotatePointSet( POINT3D *inPts, int numPts, float angle, int dir,
                      float radius, POINT3D *outPts )
{
    MATRIX matrix1, matrix2, matrix3;
    int i;
    POINT3D rot = {0.0f};
    POINT3D anchor = {0.0f};

    /* dir      rot
       +x       90 y
       -x       -90 y
       +y       -90 x
       -y       90 x
    */

    // convert angle to radians
    //mf: as noted in objects.c, we have to take negative angle to make
    // it work in familiar 'CCW rotation is positive' mode.  The ss_* rotate
    // routines must work in the 'CW is +'ve' mode, as axis pointing at you.
    angle = SS_DEG_TO_RAD(-angle);

    // set axis rotation and anchor point

    switch( dir ) {
        case PLUS_X:
            rot.y = angle;
            anchor.x = radius;
            break;
        case MINUS_X:
            rot.y = -angle;
            anchor.x = -radius;
            break;
        case PLUS_Y:
            rot.x = -angle;
            anchor.y = radius;
            break;
        case MINUS_Y:
            rot.x = angle;
            anchor.y = -radius;
            break;
    }

    // translate anchor point to origin
    ss_matrixIdent( &matrix1 );
    ss_matrixTranslate( &matrix1, -anchor.x, -anchor.y, -anchor.z );

    // rotate
    ss_matrixIdent( &matrix2 );
    ss_matrixRotate( &matrix2, (double) rot.x, rot.y, rot.z );

    // concat these 2
    ss_matrixMult( &matrix3, &matrix2, &matrix1 );

    // translate back
    ss_matrixIdent( &matrix2 );
    ss_matrixTranslate( &matrix2,  anchor.x,  anchor.y,  anchor.z );

    // concat these 2
    ss_matrixMult( &matrix1, &matrix2, &matrix3 );

    for( i = 0; i < numPts; i ++, outPts++, inPts++ ) {
        ss_xformPoint( outPts, inPts, &matrix1 );
    }
}


EVAL.H

#ifndef __eval_h__
#define __eval_h__

#include "sscommon.h"
#include "xc.h"

#define MAX_UORDER      5 // this is per section
#define MAX_VORDER      5
#define MAX_USECTIONS   4
#define MAX_XC_PTS      (MAX_UORDER * MAX_USECTIONS)

#define TEX_ORDER       2
#define EVAL_ARC_ORDER  4
#define EVAL_CYLINDER_ORDER 2
#define EVAL_ELBOW_ORDER    4

// # of components (eg. arcs) to form a complete cross-section
#define EVAL_XC_CIRC_SECTION_COUNT 4

#define EVAL_XC_POINT_COUNT ( (EVAL_ARC_ORDER-1)*4 + 1 )

#define EVAL_CIRC_ARC_CONTROL 0.56f // for r=1

/**************************************************************************\
*
* EVAL class
*
* - Evaluator composed of one or more sections that are evaluated
*   separately with OpenGL evaluators
*
\**************************************************************************/

class EVAL {
public:
    EVAL( BOOL bTexture );
    ~EVAL();
    int         numSections;    // number of cross-sectional sections
    int         uOrder, vOrder;
        // assumed: all sections same order - uOrder is per
        // section; sections share vertex and texture control points
    int         uDiv, vDiv;    // figured out one level up ?
    POINT3D     *pts;          // vertex control points
    // - texture always order 2 for s and t (linear mapping)
    BOOL        bTexture;
    TEX_POINT2D *texPts;       // texture control points

    void        Evaluate(); // evaluate/render the object
    void        SetVertexCtrlPtsXCTranslate( POINT3D *pts, float length,
                                             XC *xcStart, XC *xcEnd );
    void        SetTextureControlPoints( float s_start, float s_end,
                                         float t_start, float t_end );
    void        ProcessXCPrimLinear( XC *xcStart, XC *xcEnd, float length );
    void        ProcessXCPrimBendSimple( XC *xcCur, int dir, float radius );
    void        ProcessXCPrimSingularity( XC *xcCur, float length,
                                          BOOL bOpening );
};

extern void ResetEvaluator( BOOL bTexture );

#endif // __eval_h__


FPIPE.CXX

/******************************Module*Header*******************************\
* Module Name: fpipe.cxx
*
* Flex pipes
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

/* Notes:

    - All Draw routines start with current xc at the beginning, and create
      a new one at the end.  Since it is common to just have 2 xc's for
      each prim, xcCur holds the current xc, and xcEnd is available
      for the draw routine to use as the end xc.
        They also reset xcCur when done
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "fpipe.h"
#include "eval.h"

// defCylNotch shows the absolute notch for the default cylinder,
// given a direction (notch is always along +x axis)

static GLint defCylNotch[NUM_DIRS] =
        { MINUS_Z, PLUS_Z, PLUS_X, PLUS_X, PLUS_X, MINUS_X };

static int GetRelativeDir( int lastDir, int notchVec, int newDir );

/**************************************************************************\
* FLEX_PIPE constructor
*
*
\**************************************************************************/

FLEX_PIPE::FLEX_PIPE( STATE *pState )
: PIPE( pState )
{
    float circ;

    // Create an EVAL object

    nSlices = pState->nSlices;

    // No XC's yet, they will be allocated at pipe Start()
    xcCur = xcEnd = NULL;

    // The EVAL will be used for all pEvals in the pipe, so should be
    // set to hold max. possible # of pts for the pipe.
    pEval = new EVAL( bTexture );

    // Determine pipe tesselation
    // For now, this is based on global tesselation factor

    // Calculate evalDivSize, a reference value for the size of a UxV division.
    // This is used later for calculating texture coords.
    circ = CIRCUMFERENCE( pState->radius );
    evalDivSize = circ / (float) nSlices;
}

/**************************************************************************\
* ~FLEX_PIPE
*
\**************************************************************************/

FLEX_PIPE::~FLEX_PIPE( )
{
    delete pEval;

    // delete any XC's
    if( xcCur != NULL ) {
        if( xcEnd == xcCur )
            xcEnd = NULL; // xcCur and xcEnd can point to same xc !
        delete xcCur;
        xcCur = NULL;
    }

    if( xcEnd != NULL ) {
        delete xcEnd;
        xcEnd = NULL;
    }
}

/**************************************************************************\
* REGULAR_FLEX_PIPE constructor
*
\**************************************************************************/

REGULAR_FLEX_PIPE::REGULAR_FLEX_PIPE( STATE *state )
: FLEX_PIPE( state )
{
    static float turnFactorRange = 0.1f;
    type = TYPE_FLEX_REGULAR;

    // figure out turning factor range (0 for min bends, 1 for max bends)
    float avgTurn = ss_fRand( 0.11f, 0.81f );
    // set min and max turn factors, and clamp to 0..1
    turnFactorMin =
                SS_CLAMP_TO_RANGE( avgTurn - turnFactorRange, 0.0f, 1.0f );
    turnFactorMax =
                SS_CLAMP_TO_RANGE( avgTurn + turnFactorRange, 0.0f, 1.0f );

    // choose straight weighting
    // mf:for now, same as npipe - if stays same, put in pipe
    if( ! ss_iRand( 20 ) )
        weightStraight = ss_iRand2( MAX_WEIGHT_STRAIGHT/4, MAX_WEIGHT_STRAIGHT );
    else
        weightStraight = ss_iRand( 4 );
}

/**************************************************************************\
* TURNING_FLEX_PIPE constructor
*
\**************************************************************************/

TURNING_FLEX_PIPE::TURNING_FLEX_PIPE( STATE *state )
: FLEX_PIPE( state )
{
    type = TYPE_FLEX_TURNING;
}

/******************************Public*Routine******************************\
* SetTexIndex
*
* Set the texture index for this pipe, and calculate texture state dependent
* on texRep values
\**************************************************************************/

void
FLEX_PIPE::SetTexParams( TEXTURE *pTex, IPOINT2D *pTexRep )
{
    if( bTexture ) {
        GLfloat t_size;
        float circ;

        t_start = (GLfloat) pTexRep->y * 1.0f;
        t_end = 0.0f;

        // calc height (t_size) of one rep of texture around circumference
        circ = CIRCUMFERENCE( radius );
        t_size = circ / pTexRep->y;

        // now calc corresponding width of the texture using its x/y ratio
        s_length = t_size / pTex->origAspectRatio;
        s_start = s_end = 0.0f;
// this means we are 'standardizing' the texture size and proportions
// on pipe of radius 1.0 for entire program.  Might want to recalc this on
// a per-pipe basis ?
    }
}

/**************************************************************************\
* ChooseXCProfile
*
* Initialize extruded pipe scheme.  This uses a randomly constructed XC, but it
* remains constant throughout the pipe
*
\**************************************************************************/

void
FLEX_PIPE::ChooseXCProfile()
{
    static float turnFactorRange = 0.1f;
    float baseRadius = pState->radius;

    // initialize evaluator elements:

    pEval->numSections = EVAL_XC_CIRC_SECTION_COUNT;
    pEval->uOrder = EVAL_ARC_ORDER;
//  watch this - maybe should ROUND_UP uDiv
    // set uDiv per section (assumed uDiv multiple of numSections)
    pEval->uDiv = nSlices / pEval->numSections;

    // Setup XC's

    // The xc profile remains constant throughout in this case,
    // so we only need one xc.

    // Choose between elliptical or random cross-sections.  Since elliptical
    //  looks a little better, make it more likely
    if( ss_iRand(4) )  // 3/4 of the time
        xcCur = new ELLIPTICAL_XC( ss_fRand(1.2f, 2.0f) * baseRadius,
                                           baseRadius );
    else
        xcCur = new RANDOM4ARC_XC( ss_fRand(1.5f, 2.0f) * baseRadius );
}

/**************************************************************************\
* REGULAR_FLEX_PIPE::Start
*
* Does startup of extruded-XC pipe drawing scheme
*
\**************************************************************************/

void
REGULAR_FLEX_PIPE::Start()
{
    NODE_ARRAY *nodes = pState->nodes;
    int newDir;

    // Set start position

    if( !SetStartPos() ) {
        status = PIPE_OUT_OF_NODES;
        return;
    }

    // set material

    ChooseMaterial();

    // set XC profile

    ChooseXCProfile();

    // push matrix with zTrans and scene rotation

    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // set random lastDir
    lastDir = ss_iRand( NUM_DIRS );

    // get a new node to draw to
    newDir = ChooseNewDirection();

    if( newDir == DIR_NONE ) {
        // draw like one of those tea-pouring thingies...
        status = PIPE_STUCK;
        DrawTeapot();
        glPopMatrix();
        return;
    } else
        status = PIPE_ACTIVE;

    align_plusz( newDir ); // get us pointed in right direction

    // draw start cap, which will end right at current node
    DrawCap( START_CAP );

    // set initial notch vector, which is just the default notch, since
    // we didn't have to spin the start cap around z
    notchVec = defCylNotch[newDir];

    zTrans = - pState->view.divSize;  // distance back from new node

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* TURNING_FLEX_PIPE::Start
*
* Does startup of turning extruded-XC pipe drawing scheme
*
\**************************************************************************/

void
TURNING_FLEX_PIPE::Start( )
{
    NODE_ARRAY *nodes = pState->nodes;

    // Set start position

    if( !SetStartPos() ) {
        status = PIPE_OUT_OF_NODES;
        return;
    }

    // Set material

    ChooseMaterial();

    // Set XC profile

    ChooseXCProfile();

    // Push matrix with zTrans and scene rotation

    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // lastDir has to be set to something valid, in case we get stuck right
    // away, cuz Draw() will be called anyways on next iteration, whereupon
    // it finds out it really is stuck, AFTER calling ChooseNewTurnDirection,
    // which requires valid lastDir. (mf: fix this)
    lastDir = ss_iRand( NUM_DIRS );

    // Pick a starting direction by finding a neihgbouring empty node
    int newDir = nodes->FindClearestDirection( &curPos );
    // We don't 'choose' it, or mark it as taken, because ChooseNewDirection
    // will always check it anyways

    if( newDir == DIR_NONE ) {
        // we can't go anywhere
        // draw like one of those tea-pouring thingies...
        status = PIPE_STUCK;
        DrawTeapot();
        glPopMatrix();
        return;
    } else
        status = PIPE_ACTIVE;

    align_plusz( newDir ); // get us pointed in right direction

    // Draw start cap, which will end right at current node
    DrawCap( START_CAP );

    // Set initial notch vector, which is just the default notch, since
    // we didn't have to spin the start cap around z
    notchVec = defCylNotch[newDir];

    zTrans = 0.0f;  // right at current node

    lastDir = newDir;
}

/**************************************************************************\
* REGULAR_FLEX_PIPE::Draw
*
* Draws the pipe using a constant random xc that is extruded
*
* Minimum turn radius can vary, since xc is not symmetrical across any
* of its axes.  Therefore here we draw using a pipe/elbow sequence, so we
* know what direction we're going in before drawing the elbow.  The current
* node is the one we will draw thru next time.  Typically, the actual end
* of the pipe is way back of this node, almost at the previous node, due
* to the variable turn radius
*
\**************************************************************************/

void
REGULAR_FLEX_PIPE::Draw( )
{
    float turnRadius, minTurnRadius;
    float pipeLen, maxPipeLen, minPipeLen;
    int newDir, relDir;
    float maxXCExtent;
    NODE_ARRAY *nodes = pState->nodes;
    float divSize = pState->view.divSize;

    // get new direction

    newDir = ChooseNewDirection();
    if( newDir == DIR_NONE ) {
        status = PIPE_STUCK;
        DrawCap( END_CAP );
        glPopMatrix();
        return;
    }

    // draw pipe, and if turning, joint

    if( newDir != lastDir ) { // turning! - we have to draw joint

        // get relative turn, to figure turn radius

        relDir = GetRelativeDir( lastDir, notchVec, newDir );
        minTurnRadius = xcCur->MinTurnRadius( relDir );

        // now calc maximum straight section we can draw before turning
        // zTrans is current pos'n of end of pipe, from current node ??
        // zTrans is current pos'n of end of pipe, from last node

        maxPipeLen = (-zTrans) - minTurnRadius;

        // there is also a minimum requirement for the length of the straight
        // section, cuz if we turn too soon with a large turn radius, we
        // will swing up too close to the next node, and won't be able to
        // make one or more of the 4 possible turns from that point

        maxXCExtent = xcCur->MaxExtent(); // in case need it again
        minPipeLen = maxXCExtent - (divSize + zTrans);
        if( minPipeLen < 0.0f )
            minPipeLen = 0.0f;

        // Choose length of straight section
        // (we are translating from turnFactor to 'straightFactor' here)
        pipeLen = minPipeLen +
            ss_fRand( 1.0f - turnFactorMax, 1.0f - turnFactorMin ) *
                        (maxPipeLen - minPipeLen);

        // turn radius is whatever's left over:
        turnRadius = maxPipeLen - pipeLen + minTurnRadius;

        // draw straight section
        DrawExtrudedXCObject( pipeLen );
        zTrans += pipeLen; // not necessary for now, since elbow no use

        // draw elbow
        // this updates axes, notchVec to position at end of elbow
        DrawXCElbow( newDir, turnRadius );

        zTrans = -(divSize - turnRadius);  // distance back from node
    }
    else {  // no turn
        // draw a straight pipe through the current node
        // length can vary according to the turnFactors (e.g. for high turn
        // factors draw a short pipe, so next turn can be as big as possible)

        minPipeLen = -zTrans; // brings us just up to last node
        maxPipeLen = minPipeLen + divSize - xcCur->MaxExtent();
        // brings us as close as possible to new node

        pipeLen = minPipeLen +
            ss_fRand( 1.0f - turnFactorMax, 1.0f - turnFactorMin ) *
                        (maxPipeLen - minPipeLen);

        // draw pipe
        DrawExtrudedXCObject( pipeLen );
        zTrans += (-divSize + pipeLen);
    }

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* DrawTurningXCPipe
*
* Draws the pipe using only turns
*
* - Go straight if no turns available
*
\**************************************************************************/

void
TURNING_FLEX_PIPE::Draw()
{
    float turnRadius;
    int newDir;
    NODE_ARRAY *nodes = pState->nodes;
    float divSize = pState->view.divSize;

    // get new direction

    // pipe may have gotten stuck on Start...(we don't check for this)

    newDir = nodes->ChooseNewTurnDirection( &curPos, lastDir );
    if( newDir == DIR_NONE ) {
        status = PIPE_STUCK;
        DrawCap( END_CAP );
        glPopMatrix();
        return;
    }

    if( newDir == DIR_STRAIGHT ) {
        // No turns available - draw straight section and hope for turns
        //  on next iteration
        DrawExtrudedXCObject( divSize );
        UpdateCurrentPosition( lastDir );
        // ! we have to mark node as taken for this case, since
        // ChooseNewTurnDirection doesn't know whether we're taking the
        // straight option or not
        nodes->NodeVisited( &curPos );
    } else {
        // draw turning pipe

        // since xc is always located right at current node, turn radius
        // stays constant at one node division

        turnRadius = divSize;

        DrawXCElbow( newDir, turnRadius );

        // (zTrans stays at 0)

        // need to update 2 nodes
        UpdateCurrentPosition( lastDir );
        UpdateCurrentPosition( newDir );

        lastDir = newDir;
    }
}

/**************************************************************************\
* DrawXCElbow
*
* Draw elbow from current position through new direction
*
* - Extends current xc around bend
* - Radius of bend is provided - this is distance from xc center to hinge
*   point, along newDir.  e.g. for 'normal pipes', radius=vc->radius
*
\**************************************************************************/

void
FLEX_PIPE::DrawXCElbow( int newDir, float radius )
{
    int relDir;  // 'relative' direction of turn
    float length;

    length = (2.0f * PI * radius) / 4.0f; // average length of elbow

    // calc vDiv, texture params based on length
    CalcEvalLengthParams( length );

    pEval->vOrder = EVAL_ARC_ORDER;

    // convert absolute dir to relative dir
    relDir = GetRelativeDir( lastDir, notchVec, newDir );

    // draw it - call simple bend function

    pEval->ProcessXCPrimBendSimple( xcCur, relDir, radius );

    // set transf. matrix to new position by translating/rotating/translating
    // ! Based on simple elbow
    glTranslatef( 0.0f, 0.0f, radius );
    switch( relDir ) {
        case PLUS_X:
            glRotatef( 90.0f, 0.0f, 1.0f, 0.0f );
            break;
        case MINUS_X:
            glRotatef( -90.0f, 0.0f, 1.0f, 0.0f );
            break;
        case PLUS_Y:
            glRotatef( -90.0f, 1.0f, 0.0f, 0.0f );
            break;
        case MINUS_Y:
            glRotatef( 90.0f, 1.0f, 0.0f, 0.0f );
            break;
    }
    glTranslatef( 0.0f, 0.0f, radius );

    // update notch vector using old function
    notchVec = notchTurn[lastDir][newDir][notchVec];
}

/**************************************************************************\
* DrawExtrudedXCObject
*
* Draws object generated by extruding the current xc
*
* Object starts at xc at origin in z=0 plane, and grows along +z axis
*
\**************************************************************************/

void
FLEX_PIPE::DrawExtrudedXCObject( float length )
{
    // calc vDiv, and texture coord stuff based on length
    // this also calcs pEval texture ctrl pt arrray now
    CalcEvalLengthParams( length );

    // we can fill in some more stuff:
    pEval->vOrder = EVAL_CYLINDER_ORDER;

    // draw it

    pEval->ProcessXCPrimLinear( xcCur, xcCur, length );

    // update state draw axes position
    glTranslatef( 0.0f, 0.0f, length );
}

/**************************************************************************\
* DrawXCCap
*
* Cap the start of the pipe
*
* Needs newDir, so it can orient itself.
* Cap ends at current position with approppriate profile, starts a distance
* 'z' back along newDir.
* Profile is a singularity at start point.
*
\**************************************************************************/

void
FLEX_PIPE::DrawCap( int type )
{
    float radius;
    XC *xc = xcCur;
    BOOL bOpening = (type == START_CAP) ? TRUE : FALSE;
    float length;

    // set radius as average of the bounding box min/max's
    radius = ((xc->xRight - xc->xLeft) + (xc->yTop - xc->yBottom)) / 4.0f;

    length = (2.0f * PI * radius) / 4.0f; // average length of arc

    // calc vDiv, and texture coord stuff based on length
    CalcEvalLengthParams( length );

    // we can fill in some more stuff:
    pEval->vOrder = EVAL_ARC_ORDER;

    // draw it

    pEval->ProcessXCPrimSingularity( xc, radius, bOpening );
}

/**************************************************************************\
* CalcEvalLengthParams
*
* Calculate pEval values that depend on the length of the extruded object
*
* - calculate vDiv, s_start, s_end, and the texture control net array
*
\**************************************************************************/

void
FLEX_PIPE::CalcEvalLengthParams( float length )
{
    pEval->vDiv = (int ) SS_ROUND_UP( length / evalDivSize );

    // calc texture start and end coords

    if( bTexture ) {
        GLfloat s_delta;

        // Don't let s_end overflow : it should stay in range (0..1.0)
        if( s_end > 1.0f )
            s_end -= (int) s_end;

        s_start = s_end;
        s_delta = (length / s_length );
        s_end = s_start + s_delta;

        // the texture ctrl point array can be calc'd here - it is always
        // a simple 2x2 array for each section
        pEval->SetTextureControlPoints( s_start, s_end, t_start, t_end );
    }
}

/**************************************************************************\
*
* GetRelativeDir
*
* Calculates relative direction of turn from lastDir, notchVec, newDir
*
* - Use look up table for now.
* - Relative direction is from xy-plane, and can be +x,-x,+y,-y
* - In current orientation, +z is along lastDir, +x along notchVec
\**************************************************************************/

// this array tells you relative turn
// format: relDir[lastDir][notchVec][newDir]
static int relDir[NUM_DIRS][NUM_DIRS][NUM_DIRS] = {
//      +x      -x      +y      -y      +z      -z (newDir)
// lastDir = +x
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    PLUS_X, MINUS_X,PLUS_Y, MINUS_Y,
        iXX,    iXX,    MINUS_X,PLUS_X, MINUS_Y,PLUS_Y,
        iXX,    iXX,    MINUS_Y,PLUS_Y, PLUS_X, MINUS_X,
        iXX,    iXX,    PLUS_Y, MINUS_Y,MINUS_X,PLUS_X,
// lastDir = -x
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    PLUS_X, MINUS_X,MINUS_Y,PLUS_Y,
        iXX,    iXX,    MINUS_X,PLUS_X, PLUS_Y, MINUS_Y,
        iXX,    iXX,    PLUS_Y, MINUS_Y,PLUS_X, MINUS_X,
        iXX,    iXX,    MINUS_Y,PLUS_Y, MINUS_X,PLUS_X,
// lastDir = +y
        PLUS_X, MINUS_X,iXX,    iXX,    MINUS_Y,PLUS_Y,
        MINUS_X,PLUS_X, iXX,    iXX,    PLUS_Y, MINUS_Y,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        PLUS_Y, MINUS_Y,iXX,    iXX,    PLUS_X, MINUS_X,
        MINUS_Y,PLUS_Y, iXX,    iXX,    MINUS_X,PLUS_X,
// lastDir = -y
        PLUS_X, MINUS_X,iXX,    iXX,    PLUS_Y, MINUS_Y,
        MINUS_X,PLUS_X, iXX,    iXX,    MINUS_Y,PLUS_Y,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        MINUS_Y,PLUS_Y, iXX,    iXX,    PLUS_X, MINUS_X,
        PLUS_Y, MINUS_Y,iXX,    iXX,    MINUS_X,PLUS_X,

// lastDir = +z
        PLUS_X, MINUS_X,PLUS_Y, MINUS_Y,iXX,    iXX,
        MINUS_X,PLUS_X, MINUS_Y,PLUS_Y, iXX,    iXX,
        MINUS_Y,PLUS_Y, PLUS_X, MINUS_X,iXX,    iXX,
        PLUS_Y, MINUS_Y,MINUS_X,PLUS_X, iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
// lastDir = -z
        PLUS_X, MINUS_X,MINUS_Y,PLUS_Y, iXX,    iXX,
        MINUS_X,PLUS_X, PLUS_Y, MINUS_Y,iXX,    iXX,
        PLUS_Y, MINUS_Y,PLUS_X, MINUS_X,iXX,    iXX,
        MINUS_Y,PLUS_Y, MINUS_X,PLUS_X, iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX
};

static int
GetRelativeDir( int lastDir, int notchVec, int newDir )
{
    return( relDir[lastDir][notchVec][newDir] );
}


FPIPE.H

/******************************Module*Header*******************************\
* Module Name: fpipe.h
*
* Flexy pipe stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __fpipe_h__
#define __fpipe_h__

#include "sscommon.h"
#include "sspipes.h"
#include "pipe.h"
#include "state.h"
#include "eval.h"
#include "xc.h"

// continuity types
enum {
    CONT_1 = 0,
    CONT_2
};

// cap types
enum {
    START_CAP = 0,
    END_CAP
};

#define CIRCUMFERENCE( r ) \
    ( 2.0f * PI * ((GLfloat) r) )

// drawing schemes
enum {
    SC_EXTRUDED_XC,
    SC_TURNOMANIA,
    SC_VARIABLE_XC,
    SC_COUNT
};

#define TURNOMANIA_PIPE_COUNT 10

/**************************************************************************\
*
* FLEX_PIPE class
*
* - Pipe has position and direction in node array
*
\**************************************************************************/

class FLEX_PIPE : public PIPE {
public:
    void        SetTexParams( TEXTURE *pTex, IPOINT2D *pTexRep );

protected:
    XC          *xcCur, *xcEnd;  // current xc, end xc
    EVAL        *pEval;
    GLfloat     zTrans;         // pos'n of pipe back along current dir,
                                // from current node
    FLEX_PIPE( STATE *state );
    ~FLEX_PIPE();
    void        ChooseXCProfile();
    void        DrawExtrudedXCObject( float length );
    void        DrawXCElbow( int newDir, float radius );
    void        DrawCap( int type );

private:
    int         nSlices;        // intended # of slices around an xc (based
                                // on tessLevel)
    int         tangent;        // current tangent at curXC (usually PLUS_Z)
    GLfloat     s_start, s_end;
    GLfloat     t_start, t_end;
    GLfloat     s_length;       // length in WC's of texture width
    GLfloat     evalDivSize;    // ~size in WC's of an eval division
    void        CalcEvalLengthParams( float length );
};

class REGULAR_FLEX_PIPE : public FLEX_PIPE {
public:
    float       turnFactorMin;  // describes degree of bend in an elbow
    float       turnFactorMax;  //  [0-1]

    REGULAR_FLEX_PIPE( STATE *state );
    void        Start();
    void        Draw();
};

class TURNING_FLEX_PIPE : public FLEX_PIPE {
public:
    TURNING_FLEX_PIPE( STATE *state );
    void        Start();
    void        Draw();
};

#endif // __fpipe_h__


FSTATE.CXX

/******************************Module*Header*******************************\
* Module Name: fstate.cxx
*
* FLEX_STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "fstate.h"
#include "nstate.h"
#include "npipe.h"
#include "fpipe.h"

/******************************Public*Routine******************************\
* FLEX_STATE constructor
*
\**************************************************************************/

FLEX_STATE::FLEX_STATE( STATE *pState )
{
    bTexture = pState->bTexture;
    Reset();
}

/**************************************************************************\
* Reset
*
* Reset a frame of normal pipes.
*
\**************************************************************************/

void
FLEX_STATE::Reset( )
{
    // Choose a random scheme for each frame

    if( ss_iRand(2) )  // 50/50
        scheme = SC_EXTRUDED_XC;
    else
        scheme = SC_TURNOMANIA;
}

/**************************************************************************\
* OKToUseChase
*
* Determines if we can use chase mode for flex pipes
*
\**************************************************************************/

BOOL
FLEX_STATE::OKToUseChase( )
{
    return scheme != SC_TURNOMANIA;
}

/**************************************************************************\
* NewPipe
*
* Create a new pipe, based on current drawing scheme
*
\**************************************************************************/

PIPE*
FLEX_STATE::NewPipe( STATE *pState )
{
    if( scheme == SC_TURNOMANIA )
        return new TURNING_FLEX_PIPE( pState );
    else
        return new REGULAR_FLEX_PIPE( pState );
}

/**************************************************************************\
* GetMaxPipesPerFrame
*
\**************************************************************************/

int
FLEX_STATE::GetMaxPipesPerFrame( )
{
    if( scheme == SC_TURNOMANIA ) {
        return TURNOMANIA_PIPE_COUNT;
        if( bTexture )
            return TURNOMANIA_PIPE_COUNT / 2;
    } else {
        return bTexture ? NORMAL_TEX_PIPE_COUNT : NORMAL_PIPE_COUNT;
    }
}


FSTATE.H

/******************************Module*Header*******************************\
* Module Name: fstate.h
*
* FLEX_STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __fstate_h__
#define __fstate_h__

#include "sscommon.h"
#include "state.h"
#include "pipe.h"

class PIPE;
class STATE;

class FLEX_STATE {
public:
    int             scheme;         // current drawing scheme (right now this
                                    // is a per-frame thing)
    BOOL            bTexture;       // mf: repetition
    FLEX_STATE( STATE *pState );
    PIPE*           NewPipe( STATE *pState );
    void            Reset();
    BOOL            OKToUseChase();
    int             GetMaxPipesPerFrame();
};

#endif // __fstate_h__


NODE.CXX

/******************************Module*Header*******************************\
* Module Name: node.cxx
*
* Pipes node array
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "node.h"

/**************************************************************************\
*
* NODE_ARRAY constructor
*
\**************************************************************************/

NODE_ARRAY::NODE_ARRAY()
{
    nodes = NULL; // allocated on Resize

    numNodes.x = 0;
    numNodes.y = 0;
    numNodes.z = 0;
}

/**************************************************************************\
*
* NODE_ARRAY destructor
*
\**************************************************************************/

NODE_ARRAY::~NODE_ARRAY( )
{
    if( nodes )
        delete nodes;
}

/**************************************************************************\
*
* Resize
*
\**************************************************************************/

void
NODE_ARRAY::Resize( IPOINT3D *pNewSize )
{
    if( (numNodes.x == pNewSize->x) &&
        (numNodes.y == pNewSize->y) &&
        (numNodes.z == pNewSize->z) )
        return;

    numNodes = *pNewSize;

    int elemCount = numNodes.x * numNodes.y * numNodes.z ;

    if( nodes )
        delete nodes;

    nodes = new Node[elemCount];

    SS_ASSERT( nodes, "NODE_ARRAY::Resize : can't alloc nodes\n" );

    // Reset the node states to empty

    int i;
    Node *pNode = nodes;
    for( i = 0; i < elemCount; i++, pNode++ )
        pNode->MarkAsEmpty();

    // precalculate direction offsets between nodes for speed
    nodeDirInc[PLUS_X] = 1;
    nodeDirInc[MINUS_X] = -1;
    nodeDirInc[PLUS_Y] = numNodes.x;
    nodeDirInc[MINUS_Y] = - nodeDirInc[PLUS_Y];
    nodeDirInc[PLUS_Z] = numNodes.x * numNodes.y;
    nodeDirInc[MINUS_Z] = - nodeDirInc[PLUS_Z];
}

/**************************************************************************\
*
* Reset
*
\**************************************************************************/

void
NODE_ARRAY::Reset( )
{
    int i;
    Node *pNode = nodes;

    // Reset the node states to empty
    for( i = 0; i < (numNodes.x)*(numNodes.y)*(numNodes.z); i++, pNode++ )
        pNode->MarkAsEmpty();
}

/**************************************************************************\
*
* GetNodeCount
*
\**************************************************************************/

void
NODE_ARRAY::GetNodeCount( IPOINT3D *count )
{
    *count = numNodes;
}

/**************************************************************************\
*
* ChooseRandomDirection
*
* Choose randomnly among the possible directions.  The likelyhood of going
* straight is controlled by weighting it.
*
\**************************************************************************/

int
NODE_ARRAY::ChooseRandomDirection( IPOINT3D *pos, int dir, int weightStraight )
{
    Node *nNode[NUM_DIRS];
    int numEmpty, newDir;
    int choice;
    Node *straightNode = NULL;
    int emptyDirs[NUM_DIRS];

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::ChooseRandomDirection: invalid dir\n" );

    // Get the neigbouring nodes
    GetNeighbours( pos, nNode );

    // Get node in straight direction if necessary
    if( weightStraight && nNode[dir] && nNode[dir]->IsEmpty() ) {
        straightNode = nNode[dir];
        // if maximum weight, choose and return
        if( weightStraight == MAX_WEIGHT_STRAIGHT ) {
            straightNode->MarkAsTaken();
            return dir;
        }
    } else
        weightStraight = 0;

    // Get directions of possible turns
    numEmpty = GetEmptyTurnNeighbours( nNode, emptyDirs, dir );

    // Make a random choice
    if( (choice = (weightStraight + numEmpty)) == 0 )
        return DIR_NONE;
    choice = ss_iRand( choice );

    if( choice < weightStraight ) {
        straightNode->MarkAsTaken();
        return dir;
    } else {
        // choose one of the turns
        newDir = emptyDirs[choice - weightStraight];
        nNode[newDir]->MarkAsTaken();
        return newDir;
    }
}

/**************************************************************************\
*
* ChoosePreferredDirection
*
* Choose randomnly from one of the supplied preferred directions.  If none
* of these are available, then try and choose any empty direction
*
\**************************************************************************/

int
NODE_ARRAY::ChoosePreferredDirection( IPOINT3D *pos, int dir, int *prefDirs,
                                      int nPrefDirs )
{
    Node *nNode[NUM_DIRS];
    int numEmpty, newDir;
    int emptyDirs[NUM_DIRS];
    int *pEmptyPrefDirs;
    int i, j;

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::ChoosePreferredDirection : invalid dir\n" );

    // Get the neigbouring nodes
    GetNeighbours( pos, nNode );

    // Create list of directions that are both preferred and empty

    pEmptyPrefDirs = emptyDirs;
    numEmpty = 0;

    for( i = 0, j = 0; (i < NUM_DIRS) && (j < nPrefDirs); i++ ) {
        if( i == *prefDirs ) {
            prefDirs++;
            j++;
            if( nNode[i] && nNode[i]->IsEmpty() ) {
                // add it to list
                *pEmptyPrefDirs++ = i;
                numEmpty++;
            }
        }
    }

    // if no empty preferred dirs, then any empty dirs become preferred

    if( !numEmpty ) {
        numEmpty = GetEmptyNeighbours( nNode, emptyDirs );
        if( numEmpty == 0 )
            return DIR_NONE;
    }

    // Pick a random dir from the empty set

    newDir = emptyDirs[ss_iRand( numEmpty )];
    nNode[newDir]->MarkAsTaken();
    return newDir;
}

/**************************************************************************\
*
* FindClearestDirection
*
* Finds the direction with the most empty nodes in a line 'searchRadius'
* long.  Does not mark any nodes as taken.
*
\**************************************************************************/

int
NODE_ARRAY::FindClearestDirection( IPOINT3D *pos )
{
    static Node *neighbNode[NUM_DIRS];
    static int emptyDirs[NUM_DIRS];
    int nEmpty, newDir;
    int maxEmpty = 0;
    int searchRadius = 3;
    int count = 0;
    int i;

    // Get ptrs to neighbour nodes

    GetNeighbours( pos, neighbNode );

    // find empty nodes in each direction

    for( i = 0; i < NUM_DIRS; i ++ ) {
        if( neighbNode[i] && neighbNode[i]->IsEmpty() )
        {
            // find number of contiguous empty nodes along this direction
            nEmpty = GetEmptyNeighboursAlongDir( pos, i, searchRadius );
            if( nEmpty > maxEmpty ) {
                // we have a new winner
                count = 0;
                maxEmpty = nEmpty;
                emptyDirs[count++] = i;
            }
            else if( nEmpty == maxEmpty ) {
                // tied with current max
                emptyDirs[count++] = i;
            }
        }
    }

    if( count == 0 )
        return DIR_NONE;

    // randomnly choose a direction
    newDir = emptyDirs[ss_iRand( count )];

    return newDir;
}
/**************************************************************************\
*
* ChooseNewTurnDirection
*
* Choose a direction to turn
*
* This requires finding a pair of nodes to turn through.  The first node
* is in the direction of the turn from the current node, and the second node
* is at right angles to this at the end position.  The prim will not draw
* through the first node, but may sweep close to it, so we have to mark it
* as taken.
*
* - if next node is free, but there are no turns available, return
*   DIR_STRAIGHT, so the caller can decide what to do in this case
* - The turn possibilities are based on the orientation of the current xc, with
*   4 relative directions to seek turns in.
\**************************************************************************/

int
NODE_ARRAY::ChooseNewTurnDirection( IPOINT3D *pos, int dir )
{
    int turns[NUM_DIRS], nTurns;
    IPOINT3D nextPos;
    int newDir;
    Node *nextNode;

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::ChooseNewTurnDirection : invalid dir\n" );

    // First, check if next node along current dir is empty

    if( ! GetNextNodePos( pos, &nextPos, dir ) )
        return DIR_NONE; // node out of bounds or not empty

    // Ok, the next node is free - check the 4 possible turns from here

    nTurns = GetBestPossibleTurns( &nextPos, dir, turns );
    if( nTurns == 0 )
        return DIR_STRAIGHT; // nowhere to turn, but could go straight

    // randomnly choose one of the possible turns
    newDir = turns[ ss_iRand( nTurns ) ];

    SS_ASSERT( (newDir >= 0) && (newDir < NUM_DIRS),
            "NODE_ARRAY::ChooseNewTurnDirection : invalid newDir\n" );

    // mark taken nodes

    nextNode = GetNode( &nextPos );
    nextNode->MarkAsTaken();

    nextNode = GetNextNode( &nextPos, newDir );
    nextNode->MarkAsTaken();

    return newDir;
}

/**************************************************************************\
*
* GetBestPossibleTurns
*
* From supplied direction and position, figure out which of 4 possible
* directions are best to turn in.
*
* Turns that have the greatest number of empty nodes after the turn are the
* best, since a pipe is less likely to hit a dead end in this case.
* - We only check as far as 'searchRadius' nodes along each dir.
* - Return direction indices of best possible turns in turnDirs, and return
*   count of these turns in fuction return value.
*
\**************************************************************************/

int
NODE_ARRAY::GetBestPossibleTurns( IPOINT3D *pos, int dir, int *turnDirs )
{
    Node *neighbNode[NUM_DIRS]; // ptrs to 6 neighbour nodes
    int i, count = 0;
    BOOL check[NUM_DIRS] = {TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
    int nEmpty, maxEmpty = 0;
    int searchRadius = 2;

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::GetBestPossibleTurns : invalid dir\n" );

    GetNeighbours( pos, neighbNode );

    switch( dir ) {
        case PLUS_X:
        case MINUS_X:
            check[PLUS_X] = FALSE;
            check[MINUS_X] = FALSE;
            break;
        case PLUS_Y:
        case MINUS_Y:
            check[PLUS_Y] = FALSE;
            check[MINUS_Y] = FALSE;
            break;
        case PLUS_Z:
        case MINUS_Z:
            check[PLUS_Z] = FALSE;
            check[MINUS_Z] = FALSE;
            break;
    }

    // check approppriate directions
    for( i = 0; i < NUM_DIRS; i ++ ) {
        if( check[i] && neighbNode[i] && neighbNode[i]->IsEmpty() )
        {
            // find number of contiguous empty nodes along this direction
            nEmpty = GetEmptyNeighboursAlongDir( pos, i, searchRadius );
            if( nEmpty > maxEmpty ) {
                // we have a new winner
                count = 0;
                maxEmpty = nEmpty;
                turnDirs[count++] = i;
            }
            else if( nEmpty == maxEmpty ) {
                // tied with current max
                turnDirs[count++] = i;
            }
        }
    }

    return count;
}

/**************************************************************************\
*
* GetNeighbours
*
* Get neigbour nodes relative to supplied position
*
*       - get addresses of the neigbour nodes,
*         and put them in supplied matrix
*       - boundary hits are returned as NULL
*
\**************************************************************************/

void
NODE_ARRAY::GetNeighbours( IPOINT3D *pos, Node **nNode )
{
    Node *centerNode = GetNode( pos );

    nNode[PLUS_X]  = pos->x == (numNodes.x - 1) ? NULL :
                                            centerNode + nodeDirInc[PLUS_X];
    nNode[PLUS_Y]  = pos->y == (numNodes.y - 1) ? NULL :
                                            centerNode + nodeDirInc[PLUS_Y];
    nNode[PLUS_Z]  = pos->z == (numNodes.z - 1) ? NULL :
                                            centerNode + nodeDirInc[PLUS_Z];

    nNode[MINUS_X] = pos->x == 0 ? NULL : centerNode + nodeDirInc[MINUS_X];
    nNode[MINUS_Y] = pos->y == 0 ? NULL : centerNode + nodeDirInc[MINUS_Y];
    nNode[MINUS_Z] = pos->z == 0 ? NULL : centerNode + nodeDirInc[MINUS_Z];
}

/**************************************************************************\
*
* NodeVisited
*
* Mark the node as non-empty
*
\**************************************************************************/

void
NODE_ARRAY::NodeVisited( IPOINT3D *pos )
{
    (GetNode( pos ))->MarkAsTaken();
}

/**************************************************************************\
*
* GetNode
*
* Get ptr to node from position
*
\**************************************************************************/

Node *
NODE_ARRAY::GetNode( IPOINT3D *pos )
{
    return nodes +
           pos->x +
           pos->y * numNodes.x +
           pos->z * numNodes.x * numNodes.y;
}

/**************************************************************************\
*
* GetNextNode
*
* Get ptr to next node from pos and dir
*
\**************************************************************************/

Node *
NODE_ARRAY::GetNextNode( IPOINT3D *pos, int dir )
{
    Node *curNode = GetNode( pos );

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::GetNextNode : invalid dir\n" );

    switch( dir ) {
        case PLUS_X:
            return( pos->x == (numNodes.x - 1) ? NULL :
                              curNode + nodeDirInc[PLUS_X]);
            break;
        case MINUS_X:
            return( pos->x == 0 ? NULL :
                              curNode + nodeDirInc[MINUS_X]);
            break;
        case PLUS_Y:
            return( pos->y == (numNodes.y - 1) ? NULL :
                              curNode + nodeDirInc[PLUS_Y]);
            break;
        case MINUS_Y:
            return( pos->y == 0 ? NULL :
                              curNode + nodeDirInc[MINUS_Y]);
            break;
        case PLUS_Z:
            return( pos->z == (numNodes.z - 1) ? NULL :
                              curNode + nodeDirInc[PLUS_Z]);
            break;
        case MINUS_Z:
            return( pos->z == 0 ? NULL :
                              curNode + nodeDirInc[MINUS_Z]);
            break;
        default:
            return NULL;
    }
}

/**************************************************************************\
*
* GetNextNodePos
*
* Get position of next node from curPos and lastDir
*
* Returns FALSE if boundary hit or node empty
*
\**************************************************************************/

BOOL
NODE_ARRAY::GetNextNodePos( IPOINT3D *curPos, IPOINT3D *nextPos, int dir )
{
    static Node *neighbNode[NUM_DIRS]; // ptrs to 6 neighbour nodes

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::GetNextNodePos : invalid dir\n" );

// don't need to get all neighbours, just one in next direction
    GetNeighbours( curPos, neighbNode );

    *nextPos = *curPos;

    // bail if boundary hit or node not empty
    if( (neighbNode[dir] == NULL) || !neighbNode[dir]->IsEmpty() )
        return FALSE;

    switch( dir ) {
        case PLUS_X:
            nextPos->x = curPos->x + 1;
            break;

        case MINUS_X:
            nextPos->x = curPos->x - 1;
            break;

        case PLUS_Y:
            nextPos->y = curPos->y + 1;
            break;

        case MINUS_Y:
            nextPos->y = curPos->y - 1;
            break;

        case PLUS_Z:
            nextPos->z = curPos->z + 1;
            break;

        case MINUS_Z:
            nextPos->z = curPos->z - 1;
            break;
    }

    return TRUE;
}

/**************************************************************************\
*
*    GetEmptyNeighbours()
*       - get list of direction indices of empty node neighbours,
*         and put them in supplied matrix
*       - return number of empty node neighbours
*
\**************************************************************************/

int
NODE_ARRAY::GetEmptyNeighbours( Node **nNode, int *nEmpty )
{
    int i, count = 0;

    for( i = 0; i < NUM_DIRS; i ++ ) {
        if( nNode[i] && nNode[i]->IsEmpty() )
            nEmpty[count++] = i;
    }
    return count;
}

/**************************************************************************\
*
*    GetEmptyTurnNeighbours()
*       - get list of direction indices of empty node neighbours,
*         and put them in supplied matrix
*       - don't include going straight
*       - return number of empty node neighbours
*
\**************************************************************************/

int
NODE_ARRAY::GetEmptyTurnNeighbours( Node **nNode, int *nEmpty, int lastDir )
{
    int i, count = 0;

    for( i = 0; i < NUM_DIRS; i ++ ) {
        if( nNode[i] && nNode[i]->IsEmpty() ) {
            if( i == lastDir )
                continue;
            nEmpty[count++] = i;
        }
    }
    return count;
}

/**************************************************************************\
* GetEmptyNeighboursAlongDir
*
* Sort of like above, but just gets one neigbour according to supplied dir
*
* Given a position and direction, find out how many contiguous empty nodes
* there are in that direction.
* - Can limit search with searchRadius parameter
* - Return contiguous empty node count
\**************************************************************************/

int
NODE_ARRAY::GetEmptyNeighboursAlongDir( IPOINT3D *pos, int dir,
                            int searchRadius )
{
    Node *curNode = GetNode( pos );
    int nodeStride;
    int maxSearch;
    int count = 0;

    SS_ASSERT( (dir >= 0) && (dir < NUM_DIRS),
            "NODE_ARRAY::GetEmptyNeighboursAlongDir : invalid dir\n" );

    nodeStride = nodeDirInc[dir];

    switch( dir ) {
        case PLUS_X:
            maxSearch = numNodes.x - pos->x - 1;
            break;
        case MINUS_X:
            maxSearch = pos->x;
            break;
        case PLUS_Y:
            maxSearch = numNodes.y - pos->y - 1;
            break;
        case MINUS_Y:
            maxSearch = pos->y;
            break;
        case PLUS_Z:
            maxSearch = numNodes.z - pos->z - 1;
            break;
        case MINUS_Z:
            maxSearch = pos->z;
            break;
    }

    if( searchRadius > maxSearch )
        searchRadius = maxSearch;

    if( !searchRadius )
        return 0;

    while( searchRadius-- ) {
        curNode += nodeStride;
        if( ! curNode->IsEmpty() )
            return count;
        count++;
    }
    return count;
}

/**************************************************************************\
* FindRandomEmptyNode
*
* - Search for an empty node to start drawing
* - Return position of empty node in supplied pos ptr
* - Returns FALSE if couldn't find a node
* - Marks node as taken (mf: renam fn to ChooseEmptyNode ?
*
\**************************************************************************/

// If random search takes longer than twice the total number
// of nodes, give up the random search.  There may not be any
// empty nodes.

#define INFINITE_LOOP   (2 * NUM_NODE * NUM_NODE * NUM_NODE)

BOOL
NODE_ARRAY::FindRandomEmptyNode( IPOINT3D *pos )
{
    int infLoopDetect = 0;

    while( TRUE ) {

        // Pick a random node.

        pos->x = ss_iRand( numNodes.x );
        pos->y = ss_iRand( numNodes.y );
        pos->z = ss_iRand( numNodes.z );

        // If its empty, we're done.

        if( GetNode(pos)->IsEmpty() ) {
            NodeVisited( pos );
            return TRUE;
        } else {
            // Watch out for infinite loops!  After trying for
            // awhile, give up on the random search and look
            // for the first empty node.

            if ( infLoopDetect++ > INFINITE_LOOP ) {

                // Search for first empty node.

                for ( pos->x = 0; pos->x < numNodes.x; pos->x++ )
                    for ( pos->y = 0; pos->y < numNodes.y; pos->y++ )
                        for ( pos->z = 0; pos->z < numNodes.z; pos->z++ )
                            if( GetNode(pos)->IsEmpty() ) {
                                NodeVisited( pos );
                                return TRUE;
                            }

                // There are no more empty nodes.
                // Reset the pipes and exit.

                return FALSE;
            }
        }
    }
}

/**************************************************************************\
* FindRandomEmptyNode2D
*
* - Like FindRandomEmptyNode, but limits search to a 2d plane of the supplied
*   box.
*
\**************************************************************************/

#define INFINITE_LOOP   (2 * NUM_NODE * NUM_NODE * NUM_NODE)
#define MIN_VAL 1
#define MAX_VAL 0

BOOL
NODE_ARRAY::FindRandomEmptyNode2D( IPOINT3D *pos, int plane, int *box )
{
    int *newx, *newy;
    int *xDim, *yDim;

    switch( plane ) {
        case PLUS_X:
        case MINUS_X:
            pos->x = box[plane];
            newx = &pos->z;
            newy = &pos->y;
            xDim = &box[PLUS_Z];
            yDim = &box[PLUS_Y];
            break;
        case PLUS_Y:
        case MINUS_Y:
            pos->y = box[plane];
            newx = &pos->x;
            newy = &pos->z;
            xDim = &box[PLUS_X];
            yDim = &box[PLUS_Z];
            break;
        case PLUS_Z:
        case MINUS_Z:
            newx = &pos->x;
            newy = &pos->y;
            pos->z = box[plane];
            xDim = &box[PLUS_X];
            yDim = &box[PLUS_Y];
            break;
    }

    int infLoop = 2 * (xDim[MAX_VAL] - xDim[MIN_VAL] + 1) *
                      (yDim[MAX_VAL] - yDim[MIN_VAL] + 1);
    int infLoopDetect = 0;

    while( TRUE ) {

        // Pick a random node.

        *newx = ss_iRand2( xDim[MIN_VAL], xDim[MAX_VAL] );
        *newy = ss_iRand2( yDim[MIN_VAL], yDim[MAX_VAL] );

        // If its empty, we're done.

        if( GetNode(pos)->IsEmpty() ) {
            NodeVisited( pos );
            return TRUE;
        } else {
            // Watch out for infinite loops!  After trying for
            // awhile, give up on the random search and look
            // for the first empty node.

            if ( ++infLoopDetect > infLoop ) {

                // Do linear search for first empty node.

                for ( *newx = xDim[MIN_VAL]; *newx <= xDim[MAX_VAL]; (*newx)++ )
                    for ( *newy = yDim[MIN_VAL]; *newy <= yDim[MAX_VAL]; (*newy)++ )
                        if( GetNode(pos)->IsEmpty() ) {
                            NodeVisited( pos );
                            return TRUE;
                        }

                // There are no empty nodes in this plane.
                return FALSE;
            }
        }
    }
}

/**************************************************************************\
* TakeClosestEmptyNode
*
* - Search for an empty node closest to supplied node position
* - Returns FALSE if couldn't find a node
* - Marks node as taken
* - not completely opimized - if when dilating the box, a side gets
*   clamped against the node array, this side will continue to be searched
*
\**************************************************************************/

static void
DilateBox( int *box, IPOINT3D *bounds );

BOOL
NODE_ARRAY::TakeClosestEmptyNode( IPOINT3D *newPos, IPOINT3D *pos )
{
    static int searchRadius = SS_MAX( numNodes.x, numNodes.y ) / 3;

    // easy out
    if( GetNode(pos)->IsEmpty() ) {
        NodeVisited( pos );
        *newPos = *pos;
        return TRUE;
    }

    int box[NUM_DIRS] = {pos->x, pos->x, pos->y, pos->y, pos->z, pos->z};
    int clip[NUM_DIRS] = {0};

    // do a random search on successively larger search boxes
    for( int i = 0; i < searchRadius; i++ ) {
        // Increase box size
        DilateBox( box, &numNodes );
        // start looking in random 2D face of the box
        int dir = ss_iRand( NUM_DIRS );
        for( int j = 0; j < NUM_DIRS; j++, dir = (++dir == NUM_DIRS) ? 0 : dir ) {
            if( FindRandomEmptyNode2D( newPos, dir, box ) )
                return TRUE;
        }
    }

    // nothing nearby - grab a random one
    return FindRandomEmptyNode( newPos );
}

/**************************************************************************\
* DilateBox
*
* - Increase box radius without exceeding bounds
*
\**************************************************************************/

static void
DilateBox( int *box, IPOINT3D *bounds )
{
    int *min = (int *) &box[MINUS_X];
    int *max = (int *) &box[PLUS_X];
    int *boundMax = (int *) bounds;
    // boundMin always 0

    for( int i = 0; i < 3; i ++, min+=2, max+=2, boundMax++ ) {
        if( *min > 0 )
            (*min)--;
        if( *max < (*boundMax - 1) )
            (*max)++;
    }
}


NODE.H

/******************************Module*Header*******************************\
* Module Name: node.h
*
* Node stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __node_h__
#define __node_h__

#include "sscommon.h"
#include "sspipes.h"

#define NUM_NODE (NUM_DIV - 1)  // num nodes in longest dimension

// maximum weighting of going straight for direction choosing functions
#define MAX_WEIGHT_STRAIGHT 100

// Node class

class Node {
public:
    void        MarkAsTaken() { empty = FALSE; }
    void        MarkAsEmpty() { empty = TRUE; }
    BOOL        IsEmpty() { return empty; }
private:
    GLboolean   empty;
};

/**************************************************************************\
*
* Node array class
*
* - 3d array of nodes
* - Functions to access node neighbours, query if taken or not, etc.
* - Not only is this the node array, but a set of methods that operates on it
*
\**************************************************************************/

class NODE_ARRAY {
public:
    NODE_ARRAY();
    ~NODE_ARRAY();
    void        Resize( IPOINT3D *pNewSize ); // new array size
    void        Reset();       // set all nodes to empty
    int         ChooseRandomDirection( IPOINT3D *pos, int dir, int weight );
    int         ChoosePreferredDirection( IPOINT3D *pos, int dir, int *prefDirs,
                                          int nPrefDirs );
    int         ChooseNewTurnDirection( IPOINT3D *pos, int dir );
    int         FindClearestDirection( IPOINT3D *pos );
    int         GetBestPossibleTurns( IPOINT3D *pos, int dir, int *turnDirs );
    BOOL        FindRandomEmptyNode( IPOINT3D *ip3dEmpty );
    BOOL        FindRandomEmptyNode2D( IPOINT3D *pos, int plane, int *box );
    BOOL        TakeClosestEmptyNode( IPOINT3D *newPos, IPOINT3D *pos );
    void        NodeVisited( IPOINT3D *pos );
    void        GetNodeCount( IPOINT3D *pos );
private:
    Node        *nodes;         // ptr to node array
    int         lock;          // semaphore lock for >1 drawing pipes
    IPOINT3D    numNodes;      // x,y,z dimensions of node array
    int         nodeDirInc[NUM_DIRS]; // array offset between nodes for each dir
    void        GetNeighbours( IPOINT3D *pos, Node **nNode );
    Node*       GetNode( IPOINT3D *pos );
    Node*       GetNextNode( IPOINT3D *pos, int dir );
    BOOL        GetNextNodePos( IPOINT3D *curPos, IPOINT3D *nextPos, int dir );
    int         GetEmptyNeighbours( Node **nNode, int *nEmpty );
    int         GetEmptyTurnNeighbours( Node **nNode, int *nEmpty, int lastDir );
    int         GetEmptyNeighboursAlongDir( IPOINT3D *pos, int dir,
                                    int searchRadius );
};

#endif // __node_h__


NPIPE.CXX

/******************************Module*Header*******************************\
* Module Name: npipe.cxx
*
* Normal pipes code
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "npipe.h"
#include "state.h"

static void align_notch( int newDir, int notch );
static void align_plusy( int oldDir, int newDir );

// defCylNotch shows where the notch for the default cylinder will be,
//  in absolute coords, once we do an align_plusz

static GLint defCylNotch[NUM_DIRS] =
        { PLUS_Y, PLUS_Y, MINUS_Z, PLUS_Z, PLUS_Y, PLUS_Y };

/**************************************************************************\
* NORMAL_PIPE constructor
*
*
\**************************************************************************/

NORMAL_PIPE::NORMAL_PIPE( STATE *pState )
: PIPE( pState )
{

    type = TYPE_NORMAL;
    pNState = pState->pNState;

    // choose weighting of going straight
    if( ! ss_iRand( 20 ) )
        weightStraight = ss_iRand2( MAX_WEIGHT_STRAIGHT/4, MAX_WEIGHT_STRAIGHT );
    else
        weightStraight = 1 + ss_iRand( 4 );
}

/**************************************************************************\
* Start
*
* Start drawing a new normal pipe
*
* - Draw a start cap and short pipe in new direction
*
\**************************************************************************/

void
NORMAL_PIPE::Start( )
{
    int newDir;

    // Set start position

    if( !SetStartPos() ) {
        status = PIPE_OUT_OF_NODES;
        return;
    }

    // set a material

    ChooseMaterial();

    // push matrix that has initial zTrans and rotation
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // Pick a random lastDir
    lastDir = ss_iRand( NUM_DIRS );

    newDir = ChooseNewDirection();

    if( newDir == DIR_NONE ) {
        // pipe is stuck at the start node, draw something
        status = PIPE_STUCK;
        DrawTeapot();
        glPopMatrix();
        return;
    } else
        status = PIPE_ACTIVE;

    // set initial notch vector
    notchVec = defCylNotch[newDir];

    DrawStartCap( newDir );

    // move ahead 1.0*r to draw pipe
    glTranslatef( 0.0f, 0.0f, radius );

    // draw short pipe
    align_notch( newDir, notchVec );
    pNState->shortPipe->Draw();

    glPopMatrix();

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* Draw
*
* - if turning, draws a joint and a short cylinder, otherwise
*   draws a long cylinder.
* - the 'current node' is set as the one we draw thru the NEXT
*   time around.
*
\**************************************************************************/

void
NORMAL_PIPE::Draw()
{
    int newDir;

    newDir = ChooseNewDirection();

    if( newDir == DIR_NONE ) {  // no empty nodes - nowhere to go
        DrawEndCap();
        status = PIPE_STUCK;
        return;
    }

    // push matrix that has initial zTrans and rotation
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // draw joint if necessary, and pipe

    if( newDir != lastDir ) { // turning! - we have to draw joint
        DrawJoint( newDir );

        // draw short pipe
        align_notch( newDir, notchVec );
        pNState->shortPipe->Draw();
    }
    else {  // no turn
        // draw long pipe, from point 1.0*r back
        align_plusz( newDir );
        align_notch( newDir, notchVec );
        glTranslatef( 0.0f, 0.0f, -radius );
        pNState->longPipe->Draw();
    }

    glPopMatrix();

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* DrawStartCap
*
* Cap the start of the pipe with a ball
*
\**************************************************************************/

void
NORMAL_PIPE::DrawStartCap( int newDir )
{
    if( bTexture ) {
        align_plusz( newDir );
        pNState->ballCap->Draw();
    }
    else {
        // draw big ball in default orientation
        pNState->bigBall->Draw();
        align_plusz( newDir );
    }
}

/**************************************************************************\
* DrawEndCap():
*
* - Draws a ball, used to cap end of a pipe
*
\**************************************************************************/

void
NORMAL_PIPE::DrawEndCap( )
{
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    if( bTexture ) {
        glPushMatrix();
        align_plusz( lastDir );
        align_notch( lastDir, notchVec );
        pNState->ballCap->Draw();
        glPopMatrix();
    }
    else
        pNState->bigBall->Draw();

    glPopMatrix();
}

/**************************************************************************\
* ChooseElbow
*
* - Decides which elbow to draw
* - The beginning of each elbow is aligned along +y, and we have
*   to choose the one with the notch in correct position
* - The 'primary' start notch (elbow[0]) is in same direction as
*   newDir, and successive elbows rotate this notch CCW around +y
*
\**************************************************************************/

// this array supplies the sequence of elbow notch vectors, given
//  oldDir and newDir  (0's are don't cares)
// it is also used to determine the ending notch of an elbow
static GLint notchElbDir[NUM_DIRS][NUM_DIRS][4] = {
// oldDir = +x
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Y,         MINUS_Z,        MINUS_Y,        PLUS_Z,
        MINUS_Y,        PLUS_Z,         PLUS_Y,         MINUS_Z,
        PLUS_Z,         PLUS_Y,         MINUS_Z,        MINUS_Y,
        MINUS_Z,        MINUS_Y,        PLUS_Z,         PLUS_Y,
// oldDir = -x
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Y,         PLUS_Z,         MINUS_Y,        MINUS_Z,
        MINUS_Y,        MINUS_Z,        PLUS_Y,         PLUS_Z,
        PLUS_Z,         MINUS_Y,        MINUS_Z,        PLUS_Y,
        MINUS_Z,        PLUS_Y,         PLUS_Z,         MINUS_Y,
// oldDir = +y
        PLUS_X,         PLUS_Z,         MINUS_X,        MINUS_Z,
        MINUS_X,        MINUS_Z,        PLUS_X,         PLUS_Z,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Z,         MINUS_X,        MINUS_Z,        PLUS_X,
        MINUS_Z,        PLUS_X,         PLUS_Z,         MINUS_X,
// oldDir = -y
        PLUS_X,         MINUS_Z,        MINUS_X,        PLUS_Z,
        MINUS_X,        PLUS_Z,         PLUS_X,         MINUS_Z,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Z,         PLUS_X,         MINUS_Z,        MINUS_X,
        MINUS_Z,        MINUS_X,        PLUS_Z,         PLUS_X,
// oldDir = +z
        PLUS_X,         MINUS_Y,        MINUS_X,        PLUS_Y,
        MINUS_X,        PLUS_Y,         PLUS_X,         MINUS_Y,
        PLUS_Y,         PLUS_X,         MINUS_Y,        MINUS_X,
        MINUS_Y,        MINUS_X,        PLUS_Y,         PLUS_X,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
// oldDir = -z
        PLUS_X,         PLUS_Y,         MINUS_X,        MINUS_Y,
        MINUS_X,        MINUS_Y,        PLUS_X,         PLUS_Y,
        PLUS_Y,         MINUS_X,        MINUS_Y,        PLUS_X,
        MINUS_Y,        PLUS_X,         PLUS_Y,         MINUS_X,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX
};

GLint
NORMAL_PIPE::ChooseElbow( int oldDir, int newDir )
{
    int i;

    // precomputed table supplies correct elbow orientation
    for( i = 0; i < 4; i ++ ) {
        if( notchElbDir[oldDir][newDir][i] == notchVec )
            return i;
    }
    // we shouldn't arrive here
    return -1;
}

/**************************************************************************\
* DrawJoint
*
* Draw a joint between 2 pipes
*
\**************************************************************************/

void
NORMAL_PIPE::DrawJoint( int newDir )
{
    int jointType;
    int iBend;

    jointType = pNState->ChooseJointType();
#if PIPES_DEBUG
    if( newDir == oppositeDir[lastDir] )
        printf( "Warning: opposite dir chosen!\n" );
#endif

    switch( jointType ) {
      case BALL_JOINT:
            if( bTexture ) {
                // use special texture-friendly ballJoints

                align_plusz( newDir );
                glPushMatrix();

                align_plusy( lastDir, newDir );

                // translate forward 1.0*r along +z to get set for drawing elbow
                glTranslatef( 0.0f, 0.0f, radius );
                // decide which elbow orientation to use
                iBend = ChooseElbow( lastDir, newDir );
                pNState->ballJoints[iBend]->Draw();

                glPopMatrix();
            }
            else {
                // draw big ball in default orientation
                pNState->bigBall->Draw();
                align_plusz( newDir );
            }
            // move ahead 1.0*r to draw pipe
            glTranslatef( 0.0f, 0.0f, radius );
        break;

      case ELBOW_JOINT:
            align_plusz( newDir );

            // the align_plusy() here will screw up our notch calcs, so
            //  we push-pop

            glPushMatrix();

            align_plusy( lastDir, newDir );

            // translate forward 1.0*r along +z to get set for drawing elbow
            glTranslatef( 0.0f, 0.0f, radius );
            // decide which elbow orientation to use
            iBend = ChooseElbow( lastDir, newDir );
            if( iBend == -1 ) {
#if PIPES_DEBUG
                printf( "ChooseElbow() screwed up\n" );
#endif
                iBend = 0; // recover
            }
            pNState->elbows[iBend]->Draw();

            glPopMatrix();

            glTranslatef( 0.0f, 0.0f, radius );
        break;

      default:
            // Horrors! It's the teapot!
            DrawTeapot();
            align_plusz( newDir );
            // move ahead 1.0*r to draw pipe
            glTranslatef( 0.0f, 0.0f, radius );
        }

        // update the current notch vector
        notchVec = notchTurn[lastDir][newDir][notchVec];
#if PIPES_DEBUG
        if( notchVec == iXX )
            printf( "notchTurn gave bad value\n" );
#endif
}

/**************************************************************************\
* Geometry functions
\**************************************************************************/

static float RotZ[NUM_DIRS][NUM_DIRS] = {
          0.0f,   0.0f,  90.0f,  90.0f,  90.0f, -90.0f,
          0.0f,   0.0f, -90.0f, -90.0f, -90.0f,  90.0f,
        180.0f, 180.0f,   0.0f,   0.0f, 180.0f, 180.0f,
          0.0f,   0.0f,   0.0f,   0.0f,   0.0f,   0.0f,
        -90.0f,  90.0f,   0.0f, 180.0f,   0.0f,   0.0f,
         90.0f, -90.0f, 180.0f,   0.0f,   0.0f,   0.0f };

/*-----------------------------------------------------------------------
|                                                                       |
|    align_plusy( int lastDir, int newDir )                             |
|       - Assuming +z axis is already aligned with newDir, align        |
|         +y axis BACK along lastDir                                    |
|                                                                       |
-----------------------------------------------------------------------*/

static void
align_plusy( int oldDir, int newDir )
{
    GLfloat rotz;

    rotz = RotZ[oldDir][newDir];
    glRotatef( rotz, 0.0f, 0.0f, 1.0f );
}

// given a dir, determine how much to rotate cylinder around z to match notches
// format is [newDir][notchVec]

static GLfloat alignNotchRot[NUM_DIRS][NUM_DIRS] = {
        fXX,    fXX,    0.0f,   180.0f,  90.0f, -90.0f,
        fXX,    fXX,    0.0f,   180.0f,  -90.0f, 90.0f,
        -90.0f, 90.0f,  fXX,    fXX,    180.0f, 0.0f,
        -90.0f, 90.0f,  fXX,    fXX,    0.0f,   180.0f,
        -90.0f, 90.0f,  0.0f,   180.0f, fXX,    fXX,
        90.0f,  -90.0f, 0.0f,   180.0f, fXX,    fXX
};

/*-----------------------------------------------------------------------
|                                                                       |
|    align_notch( int newDir )                                          |
|       - a cylinder is notched, and we have to line this up            |
|         with the previous primitive's notch which is maintained as    |
|         notchVec.                                                     |
|       - this adds a rotation around z to achieve this                 |
|                                                                       |
-----------------------------------------------------------------------*/

static void
align_notch( int newDir, int notch )
{
    GLfloat rotz;
    GLint curNotch;

    // figure out where notch is presently after +z alignment
    curNotch = defCylNotch[newDir];
    // (don't need this now we have lut)

    // look up rotation value in table
    rotz = alignNotchRot[newDir][notch];
#if PIPES_DEBUG
    if( rotz == fXX ) {
        printf( "align_notch(): unexpected value\n" );
        return;
    }
#endif

    if( rotz != 0.0f )
        glRotatef( rotz, 0.0f, 0.0f, 1.0f );
}


NPIPE.H

/******************************Module*Header*******************************\
* Module Name: npipe.h
*
* Node stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __npipe_h__
#define __npipe_h__

#include "sscommon.h"
#include "state.h"
#include "pipe.h"

class NORMAL_STATE;

class NORMAL_PIPE: public PIPE {
private:
public:
    NORMAL_STATE      *pNState;

    NORMAL_PIPE( STATE *state );
    void        Start();
    GLint       ChooseElbow( int oldDir, int newDir);
    void        DrawJoint( int newDir );
    void        Draw( ); //mf: could take param to draw n sections
    void        DrawStartCap( int newDir );
    void        DrawEndCap();
};

#endif // __npipe_h__


NSTATE.CXX

/******************************Module*Header*******************************\
* Module Name: nstate.cxx
*
* NORMAL_STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "nstate.h"
#include "objects.h"
#include "dialog.h"

/******************************Public*Routine******************************\
* NORMAL_STATE constructor
*
\**************************************************************************/

NORMAL_STATE::NORMAL_STATE( STATE *pState )
{
    // init joint types from dialog settings

    bCycleJointStyles = 0;

    switch( ulJointType ) {
        case JOINT_ELBOW:
            jointStyle = ELBOWS;
            break;
        case JOINT_BALL:
            jointStyle = BALLS;
            break;
        case JOINT_MIXED:
            jointStyle = EITHER;
            break;
        case JOINT_CYCLE:
            bCycleJointStyles = 1;
            jointStyle = EITHER;
            break;
        default:
            break;
    }

    // Build the objects

    BuildObjects( pState->radius, pState->view.divSize, pState->nSlices,
                  pState->bTexture, &pState->texRep[0] );
}

/******************************Public*Routine******************************\
* NORMAL_STATE destructor
*
* Some of the objects are always created, so don't have to check if they
* exist. Others may be NULL.
\**************************************************************************/

NORMAL_STATE::~NORMAL_STATE( )
{
    delete shortPipe;
    delete longPipe;
    delete ballCap;

    for( int i = 0; i < 4; i ++ ) {
        delete elbows[i];
        if( ballJoints[i] )
            delete ballJoints[i];
    }

    if( bigBall )
        delete bigBall;
}

/**************************************************************************\
* BuildObjects
*
* - Build all the pipe primitives
* - Different prims are built based on bTexture flag
*
\**************************************************************************/
void
NORMAL_STATE::BuildObjects( float radius, float divSize, int nSlices,
                            BOOL bTexture, IPOINT2D *texRep )
{
    OBJECT_BUILD_INFO *pBuildInfo = new OBJECT_BUILD_INFO;
    pBuildInfo->radius = radius;
    pBuildInfo->divSize = divSize;
    pBuildInfo->nSlices = nSlices;
    pBuildInfo->bTexture = bTexture;

    if( bTexture ) {
        pBuildInfo->texRep = texRep;

        // Calc s texture intersection values
        float s_max = (float) texRep->y;
        float s_trans =  s_max * 2.0f * radius / divSize;

        // Build short and long pipes
        shortPipe = new PIPE_OBJECT( pBuildInfo, divSize - 2*radius,
                                     s_trans, s_max );
        longPipe = new PIPE_OBJECT( pBuildInfo, divSize, 0.0f, s_max );

        // Build elbow and ball joints
        for( int i = 0; i < 4; i ++ ) {
            elbows[i] = new ELBOW_OBJECT( pBuildInfo, i, 0.0f, s_trans );
            ballJoints[i] = new BALLJOINT_OBJECT( pBuildInfo, i, 0.0f, s_trans );
        }

        bigBall = NULL;

        // Build end cap

        float s_start = - texRep->x * (ROOT_TWO - 1.0f) * radius / divSize;
        float s_end = texRep->x * (2.0f + (ROOT_TWO - 1.0f)) * radius / divSize;
        // calc compensation value, to prevent negative s coords
        float comp_s = (int) ( - s_start ) + 1.0f;
        s_start += comp_s;
        s_end += comp_s;
        ballCap = new SPHERE_OBJECT( pBuildInfo, ROOT_TWO*radius, s_start, s_end );

    } else {
        // Build pipes, elbows
        shortPipe = new PIPE_OBJECT( pBuildInfo, divSize - 2*radius );
        longPipe = new PIPE_OBJECT( pBuildInfo, divSize );
        for( int i = 0; i < 4; i ++ ) {
            elbows[i] = new ELBOW_OBJECT( pBuildInfo, i );
            ballJoints[i] = NULL;
        }

        // Build just one ball joint when not texturing.  It is slightly
        // larger than standard ball joint, to prevent any pipe edges from
        // 'sticking' out of the ball.
        bigBall = new SPHERE_OBJECT( pBuildInfo,
                     ROOT_TWO*radius / ((float) cos(PI/nSlices)) );

        // build end cap
        ballCap = new SPHERE_OBJECT( pBuildInfo, ROOT_TWO*radius );
    }
}

/**************************************************************************\
* Reset
*
* Reset frame attributes for normal pipes.
*
\**************************************************************************/

void
NORMAL_STATE::Reset( )
{
    // Set the joint style
    if( bCycleJointStyles ) {
        if( ++(jointStyle) >= NUM_JOINT_STYLES )
            jointStyle = 0;
    }
}

/*-----------------------------------------------------------------------
|                                                                       |
|    ChooseJointType                                                    |
|       - Decides which type of joint to draw                           |
|                                                                       |
-----------------------------------------------------------------------*/

#define BLUE_MOON 153

int
NORMAL_STATE::ChooseJointType( )
{
    switch( jointStyle ) {
        case ELBOWS:
            return ELBOW_JOINT;
        case BALLS:
            return BALL_JOINT;
        case EITHER:
            // draw a teapot once in a blue moon
            if( ss_iRand(1000) == BLUE_MOON )
                return( TEAPOT );
        default:
            // otherwise an elbow or a ball (1/3 ball)
            if( !ss_iRand(3) )
                return BALL_JOINT;
            else
                return ELBOW_JOINT;
    }
}


NSTATE.H

/******************************Module*Header*******************************\
* Module Name: nstate.h
*
* NORMAL_STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __nstate_h__
#define __nstate_h__

#include "sscommon.h"
#include "objects.h"
#include "state.h"

#define NORMAL_PIPE_COUNT       5
#define NORMAL_TEX_PIPE_COUNT   3

#define NUM_JOINT_STYLES        3

// styles for pipe joints
enum {
    ELBOWS = 0,
    BALLS,
    EITHER
};

// joint types
enum {
    ELBOW_JOINT = 0,
    BALL_JOINT
};

// shchemes for choosing directions
enum {
    NORMAL_SCHEME_CHOOSE_DIR_RANDOM,
    NORMAL_SCHEME_CHOOSE_DIR_TURN,
    NORMAL_SCHEME_CHOOSE_DIR_STRAIGHT
};

// this used for traditional pipe drawing

class PIPE_OBJECT;
class ELBOW_OBJECT;
class SPHERE_OBJECT;
class BALLJOINT_OBJECT;

class STATE;

class NORMAL_STATE {
public:
    int             jointStyle;
    int             bCycleJointStyles;

    PIPE_OBJECT     *shortPipe;
    PIPE_OBJECT     *longPipe;
    ELBOW_OBJECT    *elbows[4];
    SPHERE_OBJECT   *ballCap;
    SPHERE_OBJECT   *bigBall;
    BALLJOINT_OBJECT  *ballJoints[4];

    NORMAL_STATE( STATE *pState );
    ~NORMAL_STATE();
    void            Reset();
//    int             GetMaxPipesPerFrame();
    void            BuildObjects( float radius, float divSize, int nSlices,
                                  BOOL bTexture, IPOINT2D *pTexRep );
    int             ChooseJointType();
};

#endif // __nstate_h__


OBJECTS.CXX

/******************************Module*Header*******************************\
* Module Name: objects.cxx
*
* Creates command lists for pipe primitive objects
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>
#include <GL/gl.h>
#include "sscommon.h"
#include "objects.h"
#include "sspipes.h"

#define ROOT_TWO 1.414213562373f

/**************************************************************************\
* OBJECT constructor
*
\**************************************************************************/
OBJECT::OBJECT( )
{
    listNum = glGenLists(1);
}

/**************************************************************************\
* OBJECT destructor
*
\**************************************************************************/
OBJECT::~OBJECT( )
{
    glDeleteLists( listNum, 1 );
}

/**************************************************************************\
* Draw
*
* - Draw the object by calling its display list
*
\**************************************************************************/
void
OBJECT::Draw( )
{
    glCallList( listNum );
}

/**************************************************************************\
* PIPE_OBJECT constructors
*
\**************************************************************************/
PIPE_OBJECT::PIPE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float len )
{
    Build( pBuildInfo, len, 0.0f, 0.0f );
}

PIPE_OBJECT::PIPE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float len, float s_start, float s_end )
{
    Build( pBuildInfo, len, s_start, s_end );
}

/**************************************************************************\
* ELBOW_OBJECT constructors
*
\**************************************************************************/
ELBOW_OBJECT::ELBOW_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch )
{
    Build( pBuildInfo, notch, 0.0f, 0.0f );
}

ELBOW_OBJECT::ELBOW_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    Build( pBuildInfo, notch, s_start, s_end );
}

/**************************************************************************\
* BALLJOINT_OBJECT constructor
*
\**************************************************************************/
BALLJOINT_OBJECT::BALLJOINT_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    Build( pBuildInfo, notch, s_start, s_end );
}

/**************************************************************************\
* SPHERE_OBJECT constructors
*
\**************************************************************************/
SPHERE_OBJECT::SPHERE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float radius )
{
    Build( pBuildInfo, radius, 0.0f, 0.0f );
}

SPHERE_OBJECT::SPHERE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float radius, float s_start, float s_end )
{
    Build( pBuildInfo, radius, s_start, s_end );
}

// rotate circle around x-axis, with edge attached to anchor

static void TransformCircle(
    float angle,
    POINT3D *inPoint,
    POINT3D *outPoint,
    GLint num,
    POINT3D *anchor )
{
    MATRIX matrix1, matrix2, matrix3;
    int i;

    // translate anchor point to origin
    ss_matrixIdent( &matrix1 );
    ss_matrixTranslate( &matrix1, -anchor->x, -anchor->y, -anchor->z );

    // rotate by angle, cw around x-axis
    ss_matrixIdent( &matrix2 );
    ss_matrixRotate( &matrix2, (double) -angle, 0.0, 0.0 );

    // concat these 2
    ss_matrixMult( &matrix3, &matrix2, &matrix1 );

    // translate back
    ss_matrixIdent( &matrix2 );
    ss_matrixTranslate( &matrix2,  anchor->x,  anchor->y,  anchor->z );

    // concat these 2
    ss_matrixMult( &matrix1, &matrix2, &matrix3 );

    // transform all the points, + center
    for( i = 0; i < num; i ++, outPoint++, inPoint++ ) {
        ss_xformPoint( outPoint, inPoint, &matrix1 );
    }
}

static void CalcNormals( POINT3D *p, POINT3D *n, POINT3D *center,
                         int num )
{
    int i;

    for( i = 0; i < num; i ++, n++, p++ ) {
        n->x = p->x - center->x;
        n->y = p->y - center->y;
        n->z = p->z - center->z;
        ss_normalizeNorm( n );
    }
}

/*----------------------------------------------------------------------\
|    MakeQuadStrip()                                                    |
|       - builds quadstrip between 2 rows of points. pA points to one   |
|         row of points, and pB to the next rotated row.  Because       |
|         the rotation has previously been defined CCW around the       |
|         x-axis, using an A-B sequence will result in CCW quads        |
|                                                                       |
\----------------------------------------------------------------------*/
static void MakeQuadStrip
(
    POINT3D *pA,
    POINT3D *pB,
    POINT3D *nA,
    POINT3D *nB,
    BOOL    bTexture,
    GLfloat *tex_s,
    GLfloat *tex_t,
    GLint slices
)
{
    GLint i;

    glBegin( GL_QUAD_STRIP );

    for( i = 0; i < slices; i ++ ) {
        glNormal3fv( (GLfloat *) nA++ );
        if( bTexture )
            glTexCoord2f( tex_s[0], *tex_t );
        glVertex3fv( (GLfloat *) pA++ );
        glNormal3fv( (GLfloat *) nB++ );
        if( bTexture )
            glTexCoord2f( tex_s[1], *tex_t++ );
        glVertex3fv( (GLfloat *) pB++ );
    }

    glEnd();
}

#define CACHE_SIZE      100

/*----------------------------------------------------------------------\
|    BuildElbow()                                                      |
|       - builds elbows, by rotating a circle in the y=r plane          |
|         centered at (0,r,-r), CW around the x-axis at anchor pt.      |
|         (r = radius of the circle)                                    |
|       - rotation is 90.0 degrees, ending at circle in z=0 plane,      |
|         centered at origin.                                           |
|       - in order to 'mate' texture coords with the cylinders          |
|         generated with glu, we generate 4 elbows, each corresponding  |
|         to the 4 possible CW 90 degree orientations of the start point|
|         for each circle.                                              |
|       - We call this start point the 'notch'.  If we characterize     |
|         each notch by the axis it points down in the starting and     |
|         ending circles of the elbow, then we get the following axis   |
|         pairs for our 4 notches:                                      |
|               - +z,+y                                                 |
|               - +x,+x                                                 |
|               - -z,-y                                                 |
|               - -x,-x                                                 |
|         Since the start of the elbow always points down +y, the 4     |
|         start notches give all possible 90.0 degree orientations      |
|         around y-axis.                                                |
|       - We can keep track of the current 'notch' vector to provide    |
|         proper mating between primitives.                             |
|       - Each circle of points is described CW from the start point,   |
|         assuming looking down the +y axis(+y direction).              |
|       - texture 's' starts at 0.0, and goes to 2.0*r/divSize at       |
|         end of the elbow.  (Then a short pipe would start with this   |
|         's', and run it to 1.0).                                      |
|                                                                       |
\----------------------------------------------------------------------*/
void
ELBOW_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    GLint   stacks, slices;
    GLfloat angle, startAng;
    GLint numPoints;
    GLfloat s_delta;
    POINT3D pi[CACHE_SIZE]; // initial row of points + center
    POINT3D p0[CACHE_SIZE]; // 2 rows of points
    POINT3D p1[CACHE_SIZE];
    POINT3D n0[CACHE_SIZE]; // 2 rows of normals
    POINT3D n1[CACHE_SIZE];
    GLfloat tex_t[CACHE_SIZE];// 't' texture coords
    GLfloat tex_s[2];  // 's' texture coords
    POINT3D center;  // center of circle
    POINT3D anchor;  // where circle is anchored
    POINT3D *pA, *pB, *nA, *nB;
    int i;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = slices / 2;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    s_delta = s_end - s_start;

    if( bTexture ) {
        // calculate 't' texture coords
        for( i = 0; i <= slices; i ++ ) {
            tex_t[i] = (GLfloat) i * texRep->y / slices;
        }
    }

    numPoints = slices + 1;

        // starting angle increment 90.0 degrees each time
        startAng = notch * PI / 2;

        // calc initial circle of points for circle centered at 0,r,-r
        // points start at (0,r,0), and rotate circle CCW

        for( i = 0; i <= slices; i ++ ) {
            angle = startAng + (2 * PI * i / slices);
            pi[i].x = radius * (float) sin(angle);
            pi[i].y = radius;
            // translate z by -r, cuz these cos calcs are for circle at origin
            pi[i].z = radius * (float) cos(angle) - radius;
        }

        // center point, tacked onto end of circle of points
        pi[i].x =  0.0f;
        pi[i].y =  radius;
        pi[i].z = -radius;
        center = pi[i];

        // anchor point
        anchor.x = anchor.z = 0.0f;
        anchor.y = radius;

        // calculate initial normals
        CalcNormals( pi, n0, &center, numPoints );

        // initial 's' texture coordinate
        tex_s[0] = s_start;

        // setup pointers
        pA = pi;
        pB = p0;
        nA = n0;
        nB = n1;

        // now iterate throught the stacks

        glNewList(listNum, GL_COMPILE);

        for( i = 1; i <= stacks; i ++ ) {
            // ! this angle must be negative, for correct vertex orientation !
            angle = - 0.5f * PI * i / stacks;

            // transform to get next circle of points + center
            TransformCircle( angle, pi, pB, numPoints+1, &anchor );

            // calculate normals
            center = pB[numPoints];
            CalcNormals( pB, nB, &center, numPoints );

            // calculate next 's' texture coord
            tex_s[1] = (GLfloat) s_start + s_delta * i / stacks;

            // now we've got points and normals, ready to be quadstrip'd
            MakeQuadStrip( pA, pB, nA, nB, bTexture, tex_s, tex_t, numPoints );

            // reset pointers
            pA = pB;
            nA = nB;
            pB = (pB == p0) ? p1 : p0;
            nB = (nB == n0) ? n1 : n0;
            tex_s[0] = tex_s[1];
        }

        glEndList();
}

/*----------------------------------------------------------------------\
|    BuildBallJoint()                                                  |
|       - These are very similar to the elbows, in that the starting    |
|         and ending positions are almost identical.   The difference   |
|         here is that the circles in the sweep describe a sphere as    |
|         they are rotated.                                             |
|                                                                       |
\----------------------------------------------------------------------*/
void
BALLJOINT_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, int notch,
                         float s_start, float s_end )
{
    GLfloat ballRadius;
    GLfloat angle, delta_a, startAng, theta;
    GLint numPoints;
    GLfloat s_delta;
    POINT3D pi0[CACHE_SIZE]; // 2 circles of untransformed points
    POINT3D pi1[CACHE_SIZE];
    POINT3D p0[CACHE_SIZE]; // 2 rows of transformed points
    POINT3D p1[CACHE_SIZE];
    POINT3D n0[CACHE_SIZE]; // 2 rows of normals
    POINT3D n1[CACHE_SIZE];
    float   r[CACHE_SIZE];  // radii of the circles
    GLfloat tex_t[CACHE_SIZE];// 't' texture coords
    GLfloat tex_s[2];  // 's' texture coords
    POINT3D center;  // center of circle
    POINT3D anchor;  // where circle is anchored
    POINT3D *pA, *pB, *nA, *nB;
    int i, k;
    GLint   stacks, slices;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = slices;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    // calculate the radii for each circle in the sweep, where
    // r[i] = y = sin(angle)/r

    angle = PI / 4;  // first radius always at 45.0 degrees
    delta_a = (PI / 2.0f) / stacks;

    ballRadius = ROOT_TWO * radius;
    for( i = 0; i <= stacks; i ++, angle += delta_a ) {
        r[i] = (float) sin(angle) * ballRadius;
    }

    if( bTexture ) {
        // calculate 't' texture coords
        for( i = 0; i <= slices; i ++ ) {
            tex_t[i] = (GLfloat) i * texRep->y / slices;
        }
    }

    s_delta = s_end - s_start;

    numPoints = slices + 1;

    // unlike the elbow, the center for the ball joint is constant
    center.x = center.y = 0.0f;
    center.z = -radius;

        // starting angle along circle, increment 90.0 degrees each time
        startAng = notch * PI / 2;

        // calc initial circle of points for circle centered at 0,r,-r
        // points start at (0,r,0), and rotate circle CCW

        delta_a = 2 * PI / slices;
        for( i = 0, theta = startAng; i <= slices; i ++, theta += delta_a ) {
            pi0[i].x = r[0] * (float) sin(theta);
            pi0[i].y = radius;
            // translate z by -r, cuz these cos calcs are for circle at origin
            pi0[i].z = r[0] * (float) cos(theta) - r[0];
        }

        // anchor point
        anchor.x = anchor.z = 0.0f;
        anchor.y = radius;

        // calculate initial normals
        CalcNormals( pi0, n0, &center, numPoints );

        // initial 's' texture coordinate
        tex_s[0] = s_start;

        // setup pointers
        pA = pi0; // circles of transformed points
        pB = p0;
        nA = n0; // circles of transformed normals
        nB = n1;

        // now iterate throught the stacks

        glNewList(listNum, GL_COMPILE);

        for( i = 1; i <= stacks; i ++ ) {
            // ! this angle must be negative, for correct vertex orientation !
            angle = - 0.5f * PI * i / stacks;

            // calc the next circle of untransformed points into pi1[]

            for( k = 0, theta = startAng; k <= slices; k ++, theta+=delta_a ) {
                pi1[k].x = r[i] * (float) sin(theta);
                pi1[k].y = radius;
                // translate z by -r, cuz calcs are for circle at origin
                pi1[k].z = r[i] * (float) cos(theta) - r[i];
            }

            // rotate cirle of points to next position
            TransformCircle( angle, pi1, pB, numPoints, &anchor );

            // calculate normals
            CalcNormals( pB, nB, &center, numPoints );

            // calculate next 's' texture coord
            tex_s[1] = (GLfloat) s_start + s_delta * i / stacks;

            // now we've got points and normals, ready to be quadstrip'd
            MakeQuadStrip( pA, pB, nA, nB, bTexture, tex_s, tex_t, numPoints );

            // reset pointers
            pA = pB;
            nA = nB;
            pB = (pB == p0) ? p1 : p0;
            nB = (nB == n0) ? n1 : n0;
            tex_s[0] = tex_s[1];
        }

        glEndList();
}

// 'glu' routines

#ifdef _EXTENSIONS_
#define COS cosf
#define SIN sinf
#define SQRT sqrtf
#else
#define COS cos
#define SIN sin
#define SQRT sqrt
#endif

/**************************************************************************\
* BuildCylinder
*
\**************************************************************************/
void
PIPE_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, float length, float s_start,
                            float s_end )
{
    GLint   stacks, slices;
    GLint   i,j;
    GLfloat sinCache[CACHE_SIZE];
    GLfloat cosCache[CACHE_SIZE];
    GLfloat sinCache2[CACHE_SIZE];
    GLfloat cosCache2[CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat zNormal;
    GLfloat s_delta;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = (int) SS_ROUND_UP( (length/pBuildInfo->divSize) * (float)slices) ;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    zNormal = 0.0f;

    s_delta = s_end - s_start;

    for (i = 0; i < slices; i++) {
        angle = 2 * PI * i / slices;
        sinCache2[i] = (float) SIN(angle);
        cosCache2[i] = (float) COS(angle);
        sinCache[i] = (float) SIN(angle);
        cosCache[i] = (float) COS(angle);
    }

    sinCache[slices] = sinCache[0];
    cosCache[slices] = cosCache[0];
    sinCache2[slices] = sinCache2[0];
    cosCache2[slices] = cosCache2[0];

    glNewList(listNum, GL_COMPILE);

        for (j = 0; j < stacks; j++) {
            zLow = j * length / stacks;
            zHigh = (j + 1) * length / stacks;

            glBegin(GL_QUAD_STRIP);
            for (i = 0; i <= slices; i++) {
                    glNormal3f(sinCache2[i], cosCache2[i], zNormal);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta * j / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(radius * sinCache[i],
                            radius * cosCache[i], zLow);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta*(j+1) / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(radius * sinCache[i],
                            radius * cosCache[i], zHigh);
            }
            glEnd();
        }

    glEndList();
}

/*----------------------------------------------------------------------\
|    pipeSphere()                                                       |
|                                                                       |
\----------------------------------------------------------------------*/
void
SPHERE_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, GLfloat radius,
                      GLfloat s_start, GLfloat s_end)
{
    GLint i,j;
    GLfloat sinCache1a[CACHE_SIZE];
    GLfloat cosCache1a[CACHE_SIZE];
    GLfloat sinCache2a[CACHE_SIZE];
    GLfloat cosCache2a[CACHE_SIZE];
    GLfloat sinCache1b[CACHE_SIZE];
    GLfloat cosCache1b[CACHE_SIZE];
    GLfloat sinCache2b[CACHE_SIZE];
    GLfloat cosCache2b[CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat sintemp1, sintemp2, sintemp3, sintemp4;
    GLfloat costemp3, costemp4;
    GLfloat s_delta;
    GLint start, finish;
    GLint   stacks, slices;
    BOOL    bTexture = pBuildInfo->bTexture;
    IPOINT2D *texRep = pBuildInfo->texRep;

    slices = pBuildInfo->nSlices;
    stacks = slices;
    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    // invert sense of s - it seems the glu sphere is not built similarly
    // to the glu cylinder
    // (this probably means stacks don't grow along +z - check it out)
    s_delta = s_start;
    s_start = s_end;
    s_end = s_delta;

    s_delta = s_end - s_start;

    /* Cache is the vertex locations cache */
    /* Cache2 is the various normals at the vertices themselves */

    for (i = 0; i < slices; i++) {
        angle = 2 * PI * i / slices;
        sinCache1a[i] = (float) SIN(angle);
        cosCache1a[i] = (float) COS(angle);
            sinCache2a[i] = sinCache1a[i];
            cosCache2a[i] = cosCache1a[i];
    }

    for (j = 0; j <= stacks; j++) {
        angle = PI * j / stacks;
                sinCache2b[j] = (float) SIN(angle);
                cosCache2b[j] = (float) COS(angle);
        sinCache1b[j] = radius * (float) SIN(angle);
        cosCache1b[j] = radius * (float) COS(angle);
    }
    /* Make sure it comes to a point */
    sinCache1b[0] = 0.0f;
    sinCache1b[stacks] = 0.0f;

    sinCache1a[slices] = sinCache1a[0];
    cosCache1a[slices] = cosCache1a[0];
        sinCache2a[slices] = sinCache2a[0];
        cosCache2a[slices] = cosCache2a[0];

    glNewList(listNum, GL_COMPILE);

        /* Do ends of sphere as TRIANGLE_FAN's (if not bTexture)
        ** We don't do it when bTexture because we need to respecify the
        ** texture coordinates of the apex for every adjacent vertex (because
        ** it isn't a constant for that point)
        */
        if (!bTexture) {
            start = 1;
            finish = stacks - 1;

            /* Low end first (j == 0 iteration) */
            sintemp2 = sinCache1b[1];
            zHigh = cosCache1b[1];
                sintemp3 = sinCache2b[1];
                costemp3 = cosCache2b[1];
                glNormal3f(sinCache2a[0] * sinCache2b[0],
                        cosCache2a[0] * sinCache2b[0],
                        cosCache2b[0]);

            glBegin(GL_TRIANGLE_FAN);
            glVertex3f(0.0f, 0.0f, radius);

                for (i = slices; i >= 0; i--) {
                        glNormal3f(sinCache2a[i] * sintemp3,
                                cosCache2a[i] * sintemp3,
                                costemp3);
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                }
            glEnd();

            /* High end next (j == stacks-1 iteration) */
            sintemp2 = sinCache1b[stacks-1];
            zHigh = cosCache1b[stacks-1];
                sintemp3 = sinCache2b[stacks-1];
                costemp3 = cosCache2b[stacks-1];
                glNormal3f(sinCache2a[stacks] * sinCache2b[stacks],
                        cosCache2a[stacks] * sinCache2b[stacks],
                        cosCache2b[stacks]);
            glBegin(GL_TRIANGLE_FAN);
            glVertex3f(0.0f, 0.0f, -radius);
                for (i = 0; i <= slices; i++) {
                        glNormal3f(sinCache2a[i] * sintemp3,
                                cosCache2a[i] * sintemp3,
                                costemp3);
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                }
            glEnd();
        } else {
            start = 0;
            finish = stacks;
        }
        for (j = start; j < finish; j++) {
            zLow = cosCache1b[j];
            zHigh = cosCache1b[j+1];
            sintemp1 = sinCache1b[j];
            sintemp2 = sinCache1b[j+1];
                    sintemp3 = sinCache2b[j+1];
                    costemp3 = cosCache2b[j+1];
                    sintemp4 = sinCache2b[j];
                    costemp4 = cosCache2b[j];

            glBegin(GL_QUAD_STRIP);
            for (i = 0; i <= slices; i++) {
                    glNormal3f(sinCache2a[i] * sintemp3,
                            cosCache2a[i] * sintemp3,
                            costemp3);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta*(j+1) / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                    glNormal3f(sinCache2a[i] * sintemp4,
                            cosCache2a[i] * sintemp4,
                            costemp4);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta * j / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(sintemp1 * sinCache1a[i],
                            sintemp1 * cosCache1a[i], zLow);
            }
            glEnd();
        }

    glEndList();
}


OBJECTS.H

/******************************Module*Header*******************************\
* Module Name: objects.h
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#ifndef __objects_h__
#define __objects_h__

#include "sspipes.h"
#include "state.h"

class OBJECT_BUILD_INFO {
public:
    float radius;
    float divSize;
    int   nSlices;
    BOOL  bTexture;
    IPOINT2D *texRep;
};

/**************************************************************************\
*
* OBJECT classes
*
* - Display list objects
*
\**************************************************************************/

class OBJECT {
protected:
    int         listNum;
    int         nSlices;
public:
    void        Draw();

    OBJECT();
    ~OBJECT();
};

class PIPE_OBJECT : public OBJECT {
private:
    void Build( OBJECT_BUILD_INFO *state, float length, float start_s, float s_end );
public:
    PIPE_OBJECT( OBJECT_BUILD_INFO *state, float length );
    PIPE_OBJECT( OBJECT_BUILD_INFO *state, float length, float start_s, float end_s );
};

class ELBOW_OBJECT : public OBJECT {
private:
    void Build( OBJECT_BUILD_INFO *state, int notch, float start_s, float end_s );
public:
    ELBOW_OBJECT( OBJECT_BUILD_INFO *state, int notch );
    ELBOW_OBJECT( OBJECT_BUILD_INFO *state, int notch, float start_s, float end_s );
};

class BALLJOINT_OBJECT : public OBJECT {
private:
    void Build( OBJECT_BUILD_INFO *state, int notch, float start_s, float end_s );
public:
    // texturing version only
    BALLJOINT_OBJECT( OBJECT_BUILD_INFO *state, int notch, float start_s, float end_s );
};

class SPHERE_OBJECT : public OBJECT {
private:
    void Build( OBJECT_BUILD_INFO *state, float radius, float start_s, float end_s );
public:
    SPHERE_OBJECT( OBJECT_BUILD_INFO *state, float radius, float start_s, float end_s );
    SPHERE_OBJECT( OBJECT_BUILD_INFO *state, float radius );
};

#endif // __objects_h__


PIPE.CXX

/******************************Module*Header*******************************\
* Module Name: pipe.cxx
*
* - Pipe base class stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "state.h"
#include "pipe.h"

/******************************Public*Routine******************************\
* PIPE constructor
\**************************************************************************/

PIPE::PIPE( STATE *state )
{
    pState = state;
    bTexture = pState->bTexture;
    radius = pState->radius;
    // default direction choosing is random
    chooseDirMethod = CHOOSE_DIR_RANDOM_WEIGHTED;
    chooseStartPosMethod = CHOOSE_STARTPOS_RANDOM;
    weightStraight = 1;
}

/******************************Public*Routine******************************\
* ChooseMaterial
*
\**************************************************************************/

void
PIPE::ChooseMaterial( )
{
    if( bTexture )
        ss_RandomTexMaterial( TRUE );
    else
        ss_RandomTeaMaterial( TRUE );
}

/**************************************************************************\
*
* DrawTeapot
*
\**************************************************************************/

extern void ResetEvaluator( BOOL bTexture );

void
PIPE::DrawTeapot( )
{
    glFrontFace( GL_CW );
    glEnable( GL_NORMALIZE );
    auxSolidTeapot(2.5 * radius);
    glDisable( GL_NORMALIZE );
    glFrontFace( GL_CCW );
    if( type != TYPE_NORMAL ) {
        // Re-init flex's evaluator state (teapot uses evaluators as well,
        //  and messes up the state).
        ResetEvaluator( bTexture );
    }
}

/******************************Public*Routine******************************\
* SetChooseDirectionMethod
\**************************************************************************/

void
PIPE::SetChooseDirectionMethod( int method )
{
    chooseDirMethod = method;
}

/**************************************************************************\
*
* ChooseNewDirection
*
* Call direction-finding function based on current method
* This is a generic entry point that is used by some pipe types
*
\**************************************************************************/

int
PIPE::ChooseNewDirection()
{
    NODE_ARRAY *nodes = pState->nodes;
    int bestDirs[NUM_DIRS], nBestDirs;

    // figger out which fn to call
    switch( chooseDirMethod ) {
        case CHOOSE_DIR_CHASE:
            if( nBestDirs = GetBestDirsForChase( bestDirs ) )
                return nodes->ChoosePreferredDirection( &curPos, lastDir,
                                                        bestDirs, nBestDirs );
            // else lead pipe must have died, so fall thru:
        case CHOOSE_DIR_RANDOM_WEIGHTED :
        default:
            return nodes->ChooseRandomDirection( &curPos, lastDir, weightStraight );
    }
}

/**************************************************************************\
*
* GetBestDirsForChase
*
* Find the best directions to take to close in on the lead pipe in chase mode.
*
\**************************************************************************/

int
PIPE::GetBestDirsForChase( int *bestDirs )
{
    // Figure out best dirs to close in on leadPos

    //mf: will have to 'protect' leadPos with GetLeadPos() for multi-threading
    IPOINT3D *leadPos = &pState->pLeadPipe->curPos;
    IPOINT3D delta;
    int numDirs = 0;

    delta.x = leadPos->x - curPos.x;
    delta.y = leadPos->y - curPos.y;
    delta.z = leadPos->z - curPos.z;

    if( delta.x ) {
        numDirs++;
        *bestDirs++ = delta.x > 0 ? PLUS_X : MINUS_X;
    }
    if( delta.y ) {
        numDirs++;
        *bestDirs++ = delta.y > 0 ? PLUS_Y : MINUS_Y;
    }
    if( delta.z ) {
        numDirs++;
        *bestDirs++ = delta.z > 0 ? PLUS_Z : MINUS_Z;
    }
    // It should be impossible for numDirs = 0 (all deltas = 0), as this
    // means curPos = leadPos
    return numDirs;
}

/******************************Public*Routine******************************\
* SetChooseStartPosMethod
\**************************************************************************/

void
PIPE::SetChooseStartPosMethod( int method )
{
    chooseStartPosMethod = method;
}

/******************************Public*Routine******************************\
* PIPE::SetStartPos
*
* - Find an empty node to start the pipe on
*
\**************************************************************************/

BOOL
PIPE::SetStartPos()
{
    NODE_ARRAY *nodes = pState->nodes;

    switch( chooseStartPosMethod ) {

        case CHOOSE_STARTPOS_RANDOM:
        default:
            if( !nodes->FindRandomEmptyNode( &curPos ) ) {
                return FALSE;
            }
            return TRUE;

        case CHOOSE_STARTPOS_FURTHEST:
            // find node furthest away from curPos
            IPOINT3D refPos, numNodes;
            nodes->GetNodeCount( &numNodes );
            refPos.x = (curPos.x >= (numNodes.x / 2)) ? 0 : numNodes.x - 1;
            refPos.y = (curPos.y >= (numNodes.y / 2)) ? 0 : numNodes.y - 1;
            refPos.z = (curPos.z >= (numNodes.z / 2)) ? 0 : numNodes.z - 1;

            if( !nodes->TakeClosestEmptyNode( &curPos, &refPos ) ) {
                return FALSE;
            }
            return TRUE;
    }
}

/******************************Public*Routine******************************\
* PIPE::IsStuck
\**************************************************************************/

BOOL
PIPE::IsStuck()
{
    return status == PIPE_STUCK;
}

/******************************Public*Routine******************************\
* PIPE::TranslateToCurrentPosition
*
\**************************************************************************/

void
PIPE::TranslateToCurrentPosition()
{
    IPOINT3D numNodes;

    float divSize = pState->view.divSize;
    // this requires knowing the size of the node array
    pState->nodes->GetNodeCount( &numNodes );
    glTranslatef( (curPos.x - (numNodes.x - 1)/2.0f )*divSize,
                  (curPos.y - (numNodes.y - 1)/2.0f )*divSize,
                  (curPos.z - (numNodes.z - 1)/2.0f )*divSize );
}

/**************************************************************************\
*
* UpdateCurrentPosition
*
* Increment current position according to direction taken
\**************************************************************************/

void
PIPE::UpdateCurrentPosition( int newDir )
{
    switch( newDir ) {
        case PLUS_X:
            curPos.x += 1;
            break;
        case MINUS_X:
            curPos.x -= 1;
            break;
        case PLUS_Y:
            curPos.y += 1;
            break;
        case MINUS_Y:
            curPos.y -= 1;
            break;
        case PLUS_Z:
            curPos.z += 1;
            break;
        case MINUS_Z:
            curPos.z -= 1;
            break;
    }
}

/******************************Public*Routine******************************\
* align_plusz
*
* - Aligns the z axis along specified direction
* - Used for all types of pipes
*
\**************************************************************************/

void align_plusz( int newDir )
{
    // align +z along new direction
    switch( newDir ) {
        case PLUS_X:
            glRotatef( 90.0f, 0.0f, 1.0f, 0.0f);
            break;
        case MINUS_X:
            glRotatef( -90.0f, 0.0f, 1.0f, 0.0f);
            break;
        case PLUS_Y:
            glRotatef( -90.0f, 1.0f, 0.0f, 0.0f);
            break;
        case MINUS_Y:
            glRotatef( 90.0f, 1.0f, 0.0f, 0.0f);
            break;
        case PLUS_Z:
            glRotatef( 0.0f, 0.0f, 1.0f, 0.0f);
            break;
        case MINUS_Z:
            glRotatef( 180.0f, 0.0f, 1.0f, 0.0f);
            break;
    }

}

/**************************************************************************\
* this array tells you which way the notch will be once you make
* a turn
* format: notchTurn[oldDir][newDir][notchVec]
*
\**************************************************************************/

GLint notchTurn[NUM_DIRS][NUM_DIRS][NUM_DIRS] = {
// oldDir = +x
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    MINUS_X,PLUS_X, PLUS_Z, MINUS_Z,
        iXX,    iXX,    PLUS_X, MINUS_X,PLUS_Z, MINUS_Z,
        iXX,    iXX,    PLUS_Y, MINUS_Y,MINUS_X,PLUS_X,
        iXX,    iXX,    PLUS_Y, MINUS_Y,PLUS_X, MINUS_X,
// oldDir = -x
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    PLUS_X, MINUS_X,PLUS_Z, MINUS_Z,
        iXX,    iXX,    MINUS_X,PLUS_X, PLUS_Z, MINUS_Z,
        iXX,    iXX,    PLUS_Y, MINUS_Y,PLUS_X, MINUS_X,
        iXX,    iXX,    PLUS_Y, MINUS_Y,MINUS_X,PLUS_X,
// oldDir = +y
        MINUS_Y,PLUS_Y, iXX,    iXX,    PLUS_Z, MINUS_Z,
        PLUS_Y, MINUS_Y,iXX,    iXX,    PLUS_Z, MINUS_Z,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        PLUS_X, MINUS_X,iXX,    iXX,    MINUS_Y,PLUS_Y,
        PLUS_X, MINUS_X,iXX,    iXX,    PLUS_Y, MINUS_Y,
// oldDir = -y
        PLUS_Y, MINUS_Y,iXX,    iXX,    PLUS_Z, MINUS_Z,
        MINUS_Y,PLUS_Y, iXX,    iXX,    PLUS_Z, MINUS_Z,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        PLUS_X, MINUS_X,iXX,    iXX,    PLUS_Y, MINUS_Y,
        PLUS_X, MINUS_X,iXX,    iXX,    MINUS_Y,PLUS_Y,
// oldDir = +z
        MINUS_Z,PLUS_Z, PLUS_Y, MINUS_Y,iXX,    iXX,
        PLUS_Z, MINUS_Z,PLUS_Y, MINUS_Y,iXX,    iXX,
        PLUS_X, MINUS_X,MINUS_Z,PLUS_Z, iXX,    iXX,
        PLUS_X, MINUS_X,PLUS_Z, MINUS_Z,iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
// oldDir = -z
        PLUS_Z, MINUS_Z,PLUS_Y, MINUS_Y,iXX,    iXX,
        MINUS_Z,PLUS_Z, PLUS_Y, MINUS_Y,iXX,    iXX,
        PLUS_X, MINUS_X,PLUS_Z, MINUS_Z,iXX,    iXX,
        PLUS_X, MINUS_X,MINUS_Z,PLUS_Z, iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX,
        iXX,    iXX,    iXX,    iXX,    iXX,    iXX
};


PIPE.H

/******************************Module*Header*******************************\
* Module Name: pipe.h
*
* PIPE base class
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __pipe_h__
#define __pipe_h__

#include "sscommon.h"
#include "state.h"

// pipe drawing status
enum {
    PIPE_ACTIVE,
    PIPE_STUCK,
    PIPE_OUT_OF_NODES
};

// pipe types
enum {
    TYPE_NORMAL,
    TYPE_FLEX_REGULAR,
    TYPE_FLEX_TURNING
};

// ways pipe choose directions
enum {
    CHOOSE_DIR_RANDOM_WEIGHTED,
    CHOOSE_DIR_CHASE // when chasing a lead pipe
};

// ways pipe choose start positions
enum {
    CHOOSE_STARTPOS_RANDOM,
    CHOOSE_STARTPOS_FURTHEST // furthest from last position
};
/**************************************************************************\
*
* PIPE class
*
* - Describes a pipe that draws thru the node array
* - Could have more than one pipe drawing in each array at same time
* - Pipe has position and direction in node array
*
\**************************************************************************/

class STATE;

class PIPE {
public:
    int         type;
    IPOINT3D    curPos;         // current node position of pipe

    STATE       *pState;        // for state value access

    void        SetChooseDirectionMethod( int method );
    void        SetChooseStartPosMethod( int method );
    int         ChooseNewDirection();
    BOOL        IsStuck();      // if pipe is stuck or not
    BOOL        NowhereToRun(){ return status == PIPE_OUT_OF_NODES; }
protected:
    BOOL        bTexture;
    float       radius;         // ideal radius (fluctuates for FPIPE)
    int         status;         // ACTIVE/STUCK/STOPPED, etc.
    int         lastDir;        // last direction taken by pipe
    int         notchVec;       // current notch vector
    PIPE( STATE *state );
    int         weightStraight; // current weighting of going straight
    BOOL        SetStartPos();  // starting node position
    void        ChooseMaterial();
    void        DrawTeapot();
    void        UpdateCurrentPosition( int dir );
    void        TranslateToCurrentPosition();
private:
    int         chooseDirMethod;
    int         chooseStartPosMethod;
    int         GetBestDirsForChase( int *bestDirs );
};

extern void align_plusz( int newDir );
extern GLint notchTurn[NUM_DIRS][NUM_DIRS][NUM_DIRS];

#endif // __pipe_h__


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by sspipes.rc
//
#define IDI_ICON1                       103
#define IDC_RADIO1                      1014
#define IDC_RADIO2                      1015
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         101
#define _APS_NEXT_CONTROL_VALUE         1017
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif
#include "dialog.h"
#include "sscommon.rc"


SSPIPES.CXX

/******************************Module*Header*******************************\
* Module Name: sspipes.cxx
*
* Startup code
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "dialog.h"
#include "state.h"

#ifdef DO_TIMING
int pipeCount;
#endif

void InitPipes( void *data );

/******************************Public*Routine******************************\
* ss_Init
*
* Initialize - called on first entry into ss.
* Called BEFORE gl is initialized!
* Just do basic stuff here, like set up callbacks, verify dialog stuff, etc.
*
* Fills global SSContext structure with required data, and returns ptr
* to it.
*
\**************************************************************************/

static SSContext ssc;

SSContext *
ss_Init( void )
{
    // validate some initial dialog settings
    getIniSettings();  // also called on dialog init

    if( ulSurfStyle == SURFSTYLE_TEX ) {
        // Texture verification has to go here, before gl is loaded, in case
        // error msgs are displayed.

        for( int i = 0; i < gnTextures; i ++ ) {
            if( !ss_VerifyTextureFile( &gTexFile[i]) ) {
                // user texture is invalid - substitute resource texture ?
                // If gnTextures > nRes
                // get rid of this one - move the others up
                gnTextures--;
                for( int j = i; j < gnTextures; j++ )
                    gTexFile[j] = gTexFile[j+1];
            }
        }
    }

    ss_InitFunc( InitPipes );

    // set configuration info to return
    ssc.bDoubleBuf = FALSE;
    ssc.depthType = SS_DEPTH16;
    ssc.bFloater = FALSE;

    return &ssc;
}

static void
Draw( void *data )
{
    ((STATE *) data)->Draw(data);
}

static void
Reshape( int width, int height, void *data )
{
    ((STATE *) data)->Reshape( width, height, data );
}

static void
Repaint( LPRECT pRect, void *data )
{
    ((STATE *) data)->Repaint( pRect, data );
}

static void
Finish( void *data )
{
    ((STATE *) data)->Finish( data );
}

/******************************Public*Routine******************************\
* InitPipes
*
* - Called when GL window has been initialized
*
\**************************************************************************/
void
InitPipes( void *data )
{
    // create world of pipes

    //mf: for now, bFlexMode used to choose between normal/flex

    STATE *pPipeWorld = new STATE( bFlexMode, bMultiPipes );

    ss_UpdateFunc( Draw );
    ss_ReshapeFunc( Reshape );
    ss_RepaintFunc( Repaint );
    ss_FinishFunc( Finish );
    ss_DataPtr( pPipeWorld );
}

#ifdef DO_TIMING
void CalcPipeRate( struct _timeb baseTime, int pipeCount ) {
    static struct _timeb thisTime;
    double elapsed, pipeRate;
    char buf[100];

    _ftime( &thisTime );
    elapsed = thisTime.time + thisTime.millitm/1000.0 -
       (baseTime.time + baseTime.millitm/1000.0);

    if( elapsed == 0.0 )
        pipeRate = 0.0;
    else
        pipeRate = pipeCount / elapsed;

    sprintf( buf, "Last frame's pipe rate = %4.1f pps", pipeRate );
#ifdef SS_DEBUG
    SendMessage(ss_GetHWND(), WM_SETTEXT, 0, (LPARAM)buf);
#endif
}

void Timer( int mode )
{
    static struct _timeb baseTime;

    switch( mode ) {
        case TIMER_START:
            pipeCount = 0;
         _ftime( &baseTime );
            break;
        case TIMER_STOP:
            CalcPipeRate( baseTime, pipeCount );
            break;
        case TIMER_TIMING:
            break;
        case TIMER_RESET:
        default:
            break;
    }
}
#endif


SSPIPES.H

/******************************Module*Header*******************************\
* Module Name: sspipes.h
*
* Global header for 3D Pipes screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#ifndef __sspipes_h__
#define __sspipes_h__

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <commctrl.h>

#include "sscommon.h"

#define DO_TIMING 1
#ifdef DO_TIMING
enum {
    TIMER_START = 0,
    TIMER_STOP,
    TIMER_TIMING,
    TIMER_RESET
};
extern void Timer( int mode );
extern int pipeCount;
#endif

#define iXX -1
#define fXX -0.01f

// These are absolute directions, with origin in center of screen,
// looking down -z

enum {
    PLUS_X = 0,
    MINUS_X,
    PLUS_Y,
    MINUS_Y,
    PLUS_Z,
    MINUS_Z,
    NUM_DIRS,
    DIR_NONE,
    DIR_STRAIGHT
};

#define NUM_DIV 16              // divisions in window in longest dimension

#define MAX_TEXTURES 8

#endif // __sspipes_h__


STATE.CXX

/******************************Module*Header*******************************\
* Module Name: state.cxx
*
* STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "dialog.h"
#include "state.h"
#include "pipe.h"
#include "npipe.h"
#include "fpipe.h"
#include "eval.h"

// default texture resource(s)

#define DEF_TEX_COUNT 1
TEX_RES gTexRes[DEF_TEX_COUNT] = {
    { TEX_BMP, IDB_DEFTEX }
};

static void InitTexParams();

/******************************Public*Routine******************************\
* STATE constructor
*
* - global state init
* - translates variables set from the dialog boxes
*
\**************************************************************************/

STATE::STATE( BOOL bFlexMode, BOOL bMultiPipes )
{
    // various state values
    resetStatus = RESET_STARTUP_BIT;

    // Put initial hglrc in drawThreads[0]
    // This RC is also used for dlists and texture objects that are shared
    // by other RC's

    shareRC = wglGetCurrentContext();
    drawThreads[0].SetRCDC( shareRC, wglGetCurrentDC() );

    bTexture = FALSE;
    if( ulSurfStyle == SURFSTYLE_TEX ) {
        if( LoadTextureFiles( gTexFile, gnTextures, &gTexRes[0] ) )
            bTexture = TRUE;
    }
    else if( ulSurfStyle == SURFSTYLE_WIREFRAME ) {
        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    }

    // Initialize GL state for the initial RC (sets texture state, so
    // (must come after LoadTextureFiles())

    GLInit();

    // set 'reference' radius value

    radius = 1.0f;

    // convert tesselation from fTesselFact(0.0-2.0) to tessLevel(0-MAX_TESS)

    int tessLevel = (int) (fTesselFact * (MAX_TESS+1) / 2.0001f);
    nSlices = (tessLevel+2) * 4;

    // Allocate basic NODE_ARRAY
    // NODE_ARRAY size is determined in Reshape (based on window size)
    nodes = new NODE_ARRAY;

    // Set drawing mode, and initialize accordingly.  For now, either all normal
    // or all flex pipes are drawn, but they could be combined later.
    // Can assume here that if there's any possibility that normal pipes
    // will be drawn, NORMAL_STATE will be initialized so that dlists are
    // built

    // Again, since have either NORMAL or FLEX, set maxPipesPerFrame,
    // maxDrawThreads
    if( bMultiPipes )
        maxDrawThreads = MAX_DRAW_THREADS;
    else
        maxDrawThreads = 1;
    nDrawThreads = 0; // no active threads yet
    nPipesDrawn = 0;
    // maxPipesPerFrame is set in Reset()

    if( bFlexMode ) {
        drawMode = DRAW_FLEX;
        pFState = new FLEX_STATE( this );
        pNState = NULL;
    } else {
        drawMode = DRAW_NORMAL;
        pNState = new NORMAL_STATE( this );
        pFState = NULL;
    }

    // initialize materials

    if( bTexture )
        ss_InitTexMaterials();
    else
        ss_InitTeaMaterials();

    // default draw scheme
    drawScheme = FRAME_SCHEME_RANDOM;
}

/******************************Public*Routine******************************\
* STATE destructor
*
\**************************************************************************/

STATE::~STATE( )
{
    if( pNState )
        delete pNState;
    if( pFState )
        delete pFState;
    if( nodes )
        delete nodes;
    if( bTexture ) {
        for( int i = 0; i < nTextures; i ++ ) {
            ss_DeleteTexture( &texture[i] );
        }
    }

    // Delete any RC's - should be done by ~THREAD, but since common lib
    // deletes shareRC, have to do it here

    DRAW_THREAD *pdt = &drawThreads[0];
    for( int i = 0; i < MAX_DRAW_THREADS; i ++, pdt++ ) {
        if( pdt->hglrc && (pdt->hglrc != shareRC) ) {
            wglDeleteContext( pdt->hglrc );
        }
    }
}

/******************************Public*Routine******************************\
* CalcTexRepFactors
*
\**************************************************************************/

void
STATE::CalcTexRepFactors()
{
    ISIZE winSize;
    POINT2D texFact;

    ss_GetScreenSize( &winSize );

    // Figure out repetition factor of texture, based on bitmap size and
    // screen size.
    //
    // We arbitrarily decide to repeat textures that are smaller than
    // 1/8th of screen width or height.

    for( int i = 0; i < nTextures; i++ ) {
        texRep[i].x = texRep[i].y = 1;

        if( (texFact.x = winSize.width / texture[i].width / 8.0f) >= 1.0f)
            texRep[i].x = (int) (texFact.x+0.5f);

        if( (texFact.y = winSize.height / texture[i].height / 8.0f) >= 1.0f)
            texRep[i].y = (int) (texFact.y+0.5f);
    }

    // ! If display list based normal pipes, texture repetition is embedded
    // in the dlists and can't be changed. So use the smallest rep factors.

    if( pNState ) {
        //put smallest rep factors in texRep[0]; (mf:this is ok for now, as
        // flex pipes and normal pipes don't coexist)

        for( i = 1; i < nTextures; i++ ) {
            if( texRep[i].x < texRep[0].x )
                texRep[0].x = texRep[i].x;
            if( texRep[i].y < texRep[0].y )
                texRep[0].y = texRep[i].y;
        }
    }
}

/******************************Public*Routine******************************\
* LoadTextureFiles
*
* - Load user texture files.  If texturing on but no user textures, or
*   problems loading them, load default texture resource
*
\**************************************************************************/

BOOL
STATE::LoadTextureFiles( TEXFILE *pTexFile, int nTexFiles, TEX_RES *pTexRes )
{
    // Set pixel store state

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // Try to load the bmp or rgb file

    // i counts successfully loaded textures
    for( int i = 0; nTexFiles; nTexFiles-- ) {
        if( ss_LoadTextureFile( &pTexFile[i], &texture[i] ) )  {
            // If texture object extension, set tex params here for each object
            if( ss_TextureObjectsEnabled() )
                InitTexParams();
            i++; // count another valid texture
        }
    }

    // set number of valid textures in state
    nTextures = i;

    if( nTextures == 0 ) {
        // No user textures, or none loaded successfully
        // Load default resource texture(s)
        nTextures = DEF_TEX_COUNT;
        for( i = 0; i < nTextures; i++, pTexRes++ ) {
            if( !ss_LoadTextureResource( pTexRes, &texture[i] ) ) {
                // shouldn't happen
                return FALSE;
            }
        }
    }

    CalcTexRepFactors();

    return TRUE;
}

/******************************Public*Routine******************************\
* GLInit
*
* - Sets up GL state
* - Called once for every context (rc)
*
\**************************************************************************/

void
STATE::GLInit()
{
    static float ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
    static float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
    static float position[] = {90.0f, 90.0f, 150.0f, 0.0f};
    static float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
    static float lmodel_ambientTex[] = {0.6f, 0.6f, 0.6f, 0.0f};
    static float back_mat_diffuse[] = {0.0f, 0.0f, 1.0f};

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    glFrontFace(GL_CCW);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glEnable( GL_AUTO_NORMAL ); // needed for GL_MAP2_VERTEX (tea)

    if( bTexture )
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambientTex);
    else
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    // Set texture modes
    if( bTexture ) {
        glEnable(GL_TEXTURE_2D);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        InitTexParams();
    }
}

/**************************************************************************\
* InitTexParams
*
* Set texture parameters, globally, or per object if texture object extension
*
\**************************************************************************/

static void
InitTexParams()
{
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    switch( ulTexQuality ) {
       case TEXQUAL_HIGH:
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            break;
       case TEXQUAL_DEFAULT:
       default:
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            break;
    }
}

/**************************************************************************\
* Repaint
*
* This is called when a WM_PAINT msg has been sent to the window.   The paint
* will overwrite the frame buffer, screwing up the scene if pipes is in single
* buffer mode.  We set resetStatus accordingly to clear things up on next
* draw.
*
\**************************************************************************/

void
STATE::Repaint( LPRECT pRect, void *data)
{
    resetStatus |= RESET_REPAINT_BIT;
}

/**************************************************************************\
* Reshape
*   - called on resize, expose
*   - always called on app startup
*   - set new window size for VIEW object, and set resetStatus for validation
*     at draw time
*
\**************************************************************************/

void
STATE::Reshape(int width, int height, void *data)
{
    if( view.SetWinSize( width, height ) )
        resetStatus |= RESET_RESIZE_BIT;
}

/**************************************************************************\
* ResetView
*
* Called on FrameReset resulting from change in viewing paramters (e.g. from
* a Resize event).
\**************************************************************************/

void
STATE::ResetView()
{
    IPOINT3D numNodes;

    // Have VIEW calculate the node array size based on view params
    view.CalcNodeArraySize( &numNodes );

    // Resize the node array
    nodes->Resize( &numNodes );

    // Set GL viewing parameters for each active RC

    DRAW_THREAD *pThread = drawThreads;

    for( int i = 0; i < MAX_DRAW_THREADS; i ++, pThread++ ) {
        if( pThread->HasRC() ) {
            pThread->MakeRCCurrent();
            view.SetGLView();
        }
    }
}

/**************************************************************************\
* FrameReset
*
* Start a new frame of pipes
*
* The resetStatus parameter indicates what triggered the Reset.
*
\**************************************************************************/

static int PickRandomTexture( int i, int nTextures );

void
STATE::FrameReset()
{
    int i;
    float xRot, zRot;
    PIPE *pNewPipe;

#ifdef DO_TIMING
    Timer( TIMER_STOP );
#endif

    SS_DBGINFO( "Pipes STATE::FrameReset:\n" );

    // Kill off any active pipes ! (so they can shut down ok)

    DRAW_THREAD *pThread = drawThreads;
    for( i = 0; i < nDrawThreads; i ++, pThread++ ) {
        pThread->KillPipe();
    }
    nDrawThreads = 0;

    // Clear the screen
    Clear();

    // Check for window resize status
    if( resetStatus & RESET_RESIZE_BIT ) {
        ResetView();
    }

    // Reset the node states to empty
    nodes->Reset();

    // Call any pipe-specific state resets, and get any recommended
    // pipesPerFrame counts

    if( pNState ) {
        pNState->Reset();
    }
    if( pFState ) {
        pFState->Reset();
        xRot = ss_fRand(-5.0f, 5.0f);
        zRot = ss_fRand(-5.0f, 5.0f);
    }
    maxPipesPerFrame = CalcMaxPipesPerFrame();

    // Set new number of drawing threads

    if( maxDrawThreads > 1 ) {
        // Set maximum # of pipes per frame
        maxPipesPerFrame = (int) (maxPipesPerFrame * 1.5);

        // Set # of draw threads
        nDrawThreads = SS_MIN( maxPipesPerFrame, ss_iRand2( 2, maxDrawThreads ) );
        // Set chase mode if applicable, every now and then
        BOOL bUseChase = pNState || (pFState && pFState->OKToUseChase());
        if( bUseChase && (!ss_iRand(5)) ) {
            drawScheme = FRAME_SCHEME_CHASE;
        }
    } else {
        nDrawThreads = 1;
    }
    nPipesDrawn = 0;

    // for now, either all NORMAL or all FLEX for each frame

    pThread = drawThreads;

    for( i = 0; i < nDrawThreads; i ++, pThread++ ) {

        // Create hglrc if necessary, and init it

        if( !pThread->HasRC() ) {
            HDC hdc = wglGetCurrentDC();
            pThread->SetRCDC( wglCreateContext( hdc ), hdc );
            // also need to init each RC
            pThread->MakeRCCurrent();
            // Do GL Init for this new RC
            GLInit();

            // Set viewing params
            view.SetGLView();

            // Give this rc access to any dlists
            wglShareLists( shareRC, pThread->GetRC() );
        }
        else
            pThread->MakeRCCurrent();

        // Set up the modeling view

        glLoadIdentity();
        glTranslatef(0.0f, 0.0f, view.zTrans);

        // Rotate Scene
        glRotatef( view.yRot, 0.0f, 1.0f, 0.0f );

        // create approppriate pipe for this thread slot

        switch( drawMode ) {
            case DRAW_NORMAL:
                pNewPipe = (PIPE *) new NORMAL_PIPE(this);
                break;
            case DRAW_FLEX:
                // There are several kinds of FLEX pipes - have FLEX_STATE
                // decide which one to create
                pNewPipe = pFState->NewPipe( this );
                // rotate a bit around x and z as well
                // If combining NORMAL and FLEX, same rotations must be
                // applied to both
                glRotatef( xRot, 1.0f, 0.0f, 0.0f );
                glRotatef( zRot, 0.0f, 0.0f, 1.0f );
                break;
        }
        pThread->SetPipe( pNewPipe );

        if( drawScheme == FRAME_SCHEME_CHASE ) {
            if( i == 0 ) {
                // this will be the lead pipe
                pLeadPipe = pNewPipe;
                pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED );
            } else {
                pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_CHASE );
            }
        }

        // If texturing, pick a random texture for this thread

        if( bTexture ) {
            int index = PickRandomTexture( i, nTextures );
            pThread->SetTexture( &texture[index] );

            // Flex pipes need to be informed of the texture, so they
            // can dynamically calculate various texture params
            if( pFState )
                ((FLEX_PIPE *) pNewPipe)->SetTexParams( &texture[index],
                                                        &texRep[index] );
        }

        // Launch the pipe (assumed: always more nodes than pipes starting, so
        // StartPipe cannot fail)

        // ! All pipe setup needs to be done before we call StartPipe, as this
        // is where the pipe starts drawing

        pThread->StartPipe();

        if( (i == 0) && (drawScheme == FRAME_SCHEME_CHASE) )
            pNewPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST );

        nPipesDrawn++;
    }

    // Increment scene rotation for normal reset case
    if( resetStatus & RESET_NORMAL_BIT )
        view.IncrementSceneRotation();

    // clear reset status
    resetStatus = 0;

#ifdef DO_TIMING
    Timer( TIMER_START );
#endif
}

/**************************************************************************\
* CalcMaxPipesPerFrame
*
\**************************************************************************/

int
STATE::CalcMaxPipesPerFrame()
{
    int nCount=0, fCount=0;

    if( pFState )
        fCount = pFState->GetMaxPipesPerFrame();
    if( pNState )
        nCount = bTexture ? NORMAL_TEX_PIPE_COUNT : NORMAL_PIPE_COUNT;
    return SS_MAX( nCount, fCount );
}

/**************************************************************************\
* PickRandomTexture
*
* Pick a random texture index from a list.  Remove entry from list as it
* is picked.  Once all have been picked, or starting a new frame, reset.
*
* ! Routine not reentrant, should only be called by the main thread
* dispatcher (FrameReset)
\**************************************************************************/

static int
PickRandomTexture( int iThread, int nTextures )
{
    if( nTextures == 0 )
        return 0;

    static int pickSet[MAX_TEXTURES] = {0};
    static int nPicked = 0;
    int i, index;

    if( iThread == 0 )
        // new frame - force reset
        nPicked = nTextures;

    // reset condition
    if( ++nPicked > nTextures ) {
        for( i = 0; i < nTextures; i ++ ) pickSet[i] = 0;
        nPicked = 1; // cuz
    }

    // Pick a random texture index
    index = ss_iRand( nTextures );
    while( pickSet[index] ) {
        // this index has alread been taken, try the next one
        if( ++index >= nTextures )
            index = 0;
    }
    // Hopefully, the above loop will exit :).  This means that we have
    // found a texIndex that is available
    pickSet[index] = 1; // mark as taken
    return index;
}

/**************************************************************************\
* Clear
*
* Clear the screen.  Depending on resetStatus, use normal clear or
* fancy transitional clear.
\**************************************************************************/

void
STATE::Clear()
{
    // clear the screen - any rc will do

    glClear(GL_DEPTH_BUFFER_BIT);

    if( resetStatus & RESET_RESIZE_BIT ) {
        // new window size - recalibrate the transitional clear

        // Calibration is set after a window resize, so window is already black
        ddClear.CalibrateClear( view.winSize.width, view.winSize.height, 2.0f );
    } else if( resetStatus & RESET_NORMAL_BIT )
        // do the normal transitional clear
        ddClear.Clear( view.winSize.width, view.winSize.height );
    else {
        // do a fast one-shot clear
        glClear( GL_COLOR_BUFFER_BIT );
    }
}

/**************************************************************************\
* DrawValidate
*
* Validation done before every Draw
*
* For now, this just involves checking resetStatus
*
\**************************************************************************/

void
STATE::DrawValidate()
{
    if( ! resetStatus )
        return;

    FrameReset();
}

/**************************************************************************\
* Draw
*
* - Top-level pipe drawing routine
* - Each pipe thread keeps drawing new pipes until we reach maximum number
*   of pipes per frame - then each thread gets killed as soon as it gets
*   stuck.  Once number of drawing threads reaches 0, we start a new
*   frame
*
\**************************************************************************/

void
STATE::Draw(void *data)
{
    int nKilledThreads = 0;
    BOOL bChooseNewLead = FALSE;

    // Validate the draw state

    DrawValidate();

    // Check each pipe's status

    DRAW_THREAD *pThread = drawThreads;

    for( int i = 0; i < nDrawThreads; i++, pThread++  ) {
        if( pThread->pPipe->IsStuck() ) {
            if( ++nPipesDrawn > maxPipesPerFrame ) {
                // Reaching pipe saturation - kill this pipe thread

                if( (drawScheme == FRAME_SCHEME_CHASE) &&
                    (pThread->pPipe == pLeadPipe) )
                    bChooseNewLead = TRUE;

                pThread->KillPipe();
                nKilledThreads++;

            } else {
                // Start up another pipe
                if( ! pThread->StartPipe() )
                    // we won't be able to draw any more pipes this frame
                    // (probably out of nodes)
                    maxPipesPerFrame = nPipesDrawn;
            }
        }
    }

    // Whenever one or more pipes are killed, compact the thread list
    if( nKilledThreads ) {
        CompactThreadList();
        nDrawThreads -= nKilledThreads;
    }

    if( nDrawThreads == 0 ) {
        // This frame is finished - mark for reset on next Draw
        resetStatus |= RESET_NORMAL_BIT;
        return;
    }

    if( bChooseNewLead ) {
        // We're in 'chase mode' and need to pick a new lead pipe
        ChooseNewLeadPipe();
    }

    // Draw each pipe

    for( i = 0, pThread = drawThreads; i < nDrawThreads; i++, pThread++ ) {
        pThread->DrawPipe();
#ifdef DO_TIMING
        pipeCount++;
#endif
    }

    glFlush();
}

/**************************************************************************\
*
* CompactThreadList
*
* - Compact the thread list according to number of pipe threads killed
* - The pipes have been killed, but the RC's in each slot are still valid
*   and reusable.  So we swap up entries with valid pipes. This means that
*   the ordering of the RC's in the thread list will change during the life
*   of the program.  This should be OK.
*
\**************************************************************************/

#define SWAP_SLOT( a, b ) \
    DRAW_THREAD pTemp; \
    pTemp = *(a); \
    *(a) = *(b); \
    *(b) = pTemp;

void
STATE::CompactThreadList()
{
    if( nDrawThreads <= 1 )
        // If only one active thread, it must be in slot 0 from previous
        // compactions - so nothing to do
        return;

    int iEmpty = 0;
    DRAW_THREAD *pThread = drawThreads;

    for( int i = 0; i < nDrawThreads; i ++, pThread++ ) {
        if( pThread->pPipe ) {
            if( iEmpty < i ) {
                // swap active pipe thread and empty slot
                SWAP_SLOT( &(drawThreads[iEmpty]), pThread );
            }
            iEmpty++;
        }
    }
}

/**************************************************************************\
*
* ChooseNewLeadPipe
*
* Choose a new lead pipe for chase mode.
*
\**************************************************************************/

void
STATE::ChooseNewLeadPipe()
{
    // Pick one of the active pipes at random to become the new lead

    int iLead = ss_iRand( nDrawThreads );
    pLeadPipe = drawThreads[iLead].pPipe;
    pLeadPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST );
    pLeadPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED );
}

/******************************Public*Routine******************************\
* Finish
*
* - Called when GL window being closed
*
\**************************************************************************/
void
STATE::Finish( void *data )
{
    delete (STATE *) data;
}

/**************************************************************************\
* DRAW_THREAD constructor
*
\**************************************************************************/

DRAW_THREAD::DRAW_THREAD()
{
    hdc = 0;
    hglrc = 0;
    pPipe = NULL;
    htex = (HTEXTURE) -1;
}

/**************************************************************************\
* DRAW_THREAD destructor
*
* Delete any GL contexts
*
* - can't Delete shareRC, as this is done by common lib, so had to move
*   this up to ~STATE
*
\**************************************************************************/

DRAW_THREAD::~DRAW_THREAD()
{
}

/**************************************************************************\
* MakeRCCurrent
*
\**************************************************************************/

void
DRAW_THREAD::MakeRCCurrent()
{
    if( hglrc != wglGetCurrentContext() )
        wglMakeCurrent( hdc, hglrc );
}

/**************************************************************************\
* SetRCDC
*
\**************************************************************************/

void
DRAW_THREAD::SetRCDC( HGLRC rc, HDC Hdc )
{
    hglrc = rc;
    hdc = Hdc;
}

/**************************************************************************\
* SetPipe
*
\**************************************************************************/

void
DRAW_THREAD::SetPipe( PIPE *pipe )
{
    pPipe = pipe;
}

/**************************************************************************\
* HasRC
*
\**************************************************************************/

BOOL
DRAW_THREAD::HasRC()
{
    return( hglrc != 0 );
}

/**************************************************************************\
* GetRC
*
\**************************************************************************/

HGLRC
DRAW_THREAD::GetRC()
{
    return hglrc;
}

/**************************************************************************\
* SetTexture
*
* - Set a texture for a thread
* - Cache the texture index for performance
\**************************************************************************/

void
DRAW_THREAD::SetTexture( HTEXTURE hnewtex )
{
    if( hnewtex != htex )
    {
        htex = hnewtex;
        ss_SetTexture( htex );
    }
}

/**************************************************************************\
* DrawPipe
*
* - Draw pipe in thread slot, according to its type
*
\**************************************************************************/

void
DRAW_THREAD::DrawPipe()
{
    MakeRCCurrent();

    switch( pPipe->type ) {
        case TYPE_NORMAL:
            ( (NORMAL_PIPE *) pPipe )->Draw();
            break;
        case TYPE_FLEX_REGULAR:
            ( (REGULAR_FLEX_PIPE *) pPipe )->Draw();
            break;
        case TYPE_FLEX_TURNING:
            ( (TURNING_FLEX_PIPE *) pPipe )->Draw();
            break;
    }
    glFlush();
}
/**************************************************************************\
* StartPipe
*
* Starts up pipe of the approppriate type.  If can't find an empty node
* for the pipe to start on, returns FALSE;
*
\**************************************************************************/

BOOL
DRAW_THREAD::StartPipe()
{
    MakeRCCurrent();

    // call pipe-type specific Start function

    switch( pPipe->type ) {
        case TYPE_NORMAL:
            ( (NORMAL_PIPE *) pPipe )->Start();
            break;
        case TYPE_FLEX_REGULAR:
            ( (REGULAR_FLEX_PIPE *) pPipe )->Start();
            break;
        case TYPE_FLEX_TURNING:
            ( (TURNING_FLEX_PIPE *) pPipe )->Start();
            break;
    }
    glFlush();

    // check status
    if( pPipe->NowhereToRun() )
        return FALSE;
    else
        return TRUE;
}

/**************************************************************************\
* KillPipe
*
\**************************************************************************/

void
DRAW_THREAD::KillPipe()
{
    switch( pPipe->type ) {
        case TYPE_NORMAL:
            delete (NORMAL_PIPE *) pPipe;
            break;
        case TYPE_FLEX_REGULAR:
            delete (REGULAR_FLEX_PIPE *) pPipe;
            break;
        case TYPE_FLEX_TURNING:
            delete (TURNING_FLEX_PIPE *) pPipe;
            break;
    }
    pPipe = NULL;
}


STATE.H

/******************************Module*Header*******************************\
* Module Name: state.h
*
* STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __state_h__
#define __state_h__

#include "sscommon.hxx"
#include "pipe.h"
#include "node.h"
#include "view.h"
#include "nstate.h"
#include "fstate.h"

#define MAX_DRAW_THREADS    4

#define TEAPOT             66

#define MAX_TESS 3

// type(s) of pipes that are drawn
enum {
    DRAW_NORMAL,
    DRAW_FLEX,
    DRAW_BOTH  // not currently used
};

// Reset status

#define  RESET_STARTUP_BIT (1L << 0)
#define  RESET_NORMAL_BIT  (1L << 1)
#define  RESET_RESIZE_BIT  (1L << 2)
#define  RESET_REPAINT_BIT  (1L << 3)

// Frame draw schemes

enum {
    FRAME_SCHEME_RANDOM,  // pipes draw randomly
    FRAME_SCHEME_CHASE,   // pipes chase a lead pipe
};

class DRAW_THREAD {
private:
    HDC         hdc;
    HTEXTURE    htex;

public:
    HGLRC       hglrc;        // rc to draw with (public so STATE can delete)
    int         priority;

    DRAW_THREAD();
    ~DRAW_THREAD();
    PIPE        *pPipe;       // generic pipe ptr
    void        SetRCDC( HGLRC rc, HDC hdc );
    BOOL        HasRC();
    HGLRC       GetRC();
    void        MakeRCCurrent();
    void        SetTexture( HTEXTURE htex );
    void        SetPipe( PIPE *pipe );
    BOOL        StartPipe();
    void        DrawPipe();
    void        KillPipe();
};

// Program existence instance

class NORMAL_STATE;
class FLEX_STATE;

class STATE {
public:
    HGLRC       shareRC;        // RC that objects are shared from

    PIPE        *pLeadPipe;     // lead pipe for chase scenarios

    int         nSlices;      // reference # of slices around a pipe
    BOOL        bTexture;       // global texture enable
    int         nTextures;
    TEXTURE     texture[MAX_TEXTURES];
    IPOINT2D    texRep[MAX_TEXTURES];

    VIEW        view;           // viewing parameters
    float       radius;         // 'reference' pipe radius value
    NODE_ARRAY  *nodes;         // for keeping track of draw space
    NORMAL_STATE *pNState;
    FLEX_STATE  *pFState;

    STATE( BOOL bFlexMode, BOOL bMultiPipes );
    ~STATE();
    void        Reshape( int width, int height, void *data );
    void        Repaint( LPRECT pRect, void *data );
    void        Draw( void *data );
    void        Finish( void *data );

private:
    int         drawMode;       // drawing mode (flex or normal for now)
    int         drawScheme;     // random or chase

    int         maxPipesPerFrame; // max number of separate pipes/frame
    int         nPipesDrawn;    // number of pipes drawn or drawing in frame
    int         maxDrawThreads; // max number of concurrently drawing pipes
    int         nDrawThreads;   // number of live threads
    DRAW_THREAD drawThreads[MAX_DRAW_THREADS];

    int         resetStatus;

    SS_DIGITAL_DISSOLVE_CLEAR ddClear;
    int         bCalibrateClear;

    void        GLInit();
    void        DrawValidate();  // validation to do before each Draw
    void        ResetView();
    void        FrameReset();
    void        Clear();
    void        ChooseNewLeadPipe();
    void        CompactThreadList();
    BOOL        LoadTextureFiles();
    BOOL        LoadTextureFiles( TEXFILE *pTexFile, int nTexFiles,
                                  TEX_RES *pTexRes );
    void        CalcTexRepFactors();
    int         CalcMaxPipesPerFrame();
};

#endif // __state_h__


VIEW.CXX

/******************************Module*Header*******************************\
* Module Name: view.cxx
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "view.h"

/******************************Public*Routine******************************\
* VIEW constructor
\**************************************************************************/

VIEW::VIEW()
{
    bProjMode = GL_TRUE;

    // set some initial viewing and size params

    zTrans = -75.0f;
    viewDist = -zTrans;

    numDiv = NUM_DIV;
    SS_ASSERT( numDiv >= 2, "VIEW constructor: not enough divisions\n" );
    // Because number of nodes in a dimension is derived from (numDiv-1), and
    // can't be 0

    divSize = 7.0f;

    persp.viewAngle = 90.0f;
    persp.zNear = 1.0f;

    yRot = 0.0f;

    winSize.width = winSize.height = 0;
}

/******************************Public*Routine******************************\
* SetProjMatrix
*
* Set GL view parameters
\**************************************************************************/

void
VIEW::SetGLView()
{
    glViewport(0, 0, winSize.width, winSize.height );
    SetProjMatrix();
}

/******************************Public*Routine******************************\
* SetProjMatrix
*
* Set Projection matrix
\**************************************************************************/

void
VIEW::SetProjMatrix()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    persp.zFar = viewDist + world.z*2;
    if( bProjMode ) {
        gluPerspective( persp.viewAngle,
                        aspectRatio,
                        persp.zNear, persp.zFar );
    }
    else {
        glOrtho( -world.x/2, world.x/2, -world.y/2, world.y/2,
                          -world.z, world.z );
    }
    glMatrixMode(GL_MODELVIEW);
}

/******************************Public*Routine******************************\
* CalcNodeArraySize
*
* Based on the viewing width and height, and numDiv, calculate the x,y,z array
* node dimensions.
*
\**************************************************************************/

void
VIEW::CalcNodeArraySize( IPOINT3D *pNodeDim )
{
    // if aspect ratio deviates too much from 1, then nodes will get
    // clipped as view rotates

    if( winSize.width >= winSize.height ) {
        pNodeDim->x = numDiv - 1;
        pNodeDim->y = (int) (pNodeDim->x / aspectRatio) ;
        if( pNodeDim->y < 1 )
            pNodeDim->y = 1;
        pNodeDim->z = pNodeDim->x;
    }
    else {
        pNodeDim->y = numDiv - 1;
        pNodeDim->x = (int) (aspectRatio * pNodeDim->y);
        if( pNodeDim->x < 1 )
            pNodeDim->x = 1;
        pNodeDim->z = pNodeDim->y;
    }
}

/******************************Public*Routine******************************\
* SetWinSize
*
* Set the window size for the view, derive other view params.
*
* Return FALSE if new size same as old.
\**************************************************************************/

BOOL
VIEW::SetWinSize( int width, int height )
{
    if( (width == winSize.width) &&
        (height == winSize.height) )
        return FALSE;

    winSize.width = width;
    winSize.height = height;

    aspectRatio = winSize.height == 0 ? 1.0f : (float)winSize.width/winSize.height;

    if( winSize.width >= winSize.height ) {
        world.x = numDiv * divSize;
        world.y = world.x / aspectRatio;
        world.z = world.x;
    }
    else {
        world.y = numDiv * divSize;
        world.x = world.y * aspectRatio;
        world.z = world.y;
    }
    return TRUE;
}

/******************************Public*Routine******************************\
* SetSceneRotation
*
\**************************************************************************/

void
VIEW::IncrementSceneRotation()
{
    yRot += 9.73156f;
    if( yRot >= 360.0f )
        // prevent overflow
        yRot -= 360.0f;
}


VIEW.H

/******************************Module*Header*******************************\
* Module Name: view.h
*
* Node stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#ifndef __view_h__
#define __view_h__

#include "sscommon.h"

typedef struct {
    float viewAngle;            // field of view angle for height
    float zNear;                // near z clip value
    float zFar;                 // far z clip value
} Perspective;  // perspective view description

class VIEW {
public:
    float       zTrans;         // z translation
    float       yRot;           // current yRotation
    float       viewDist;       // viewing distance, usually -zTrans
    int         numDiv;         // # grid divisions in x,y,z
    float       divSize;        // distance between divisions
    ISIZE       winSize;        // window size in pixels

    VIEW();
    BOOL        SetWinSize( int width, int height );
    void        SetGLView();
    void        CalcNodeArraySize( IPOINT3D *pNodeDim );
    void        SetProjMatrix();
    void        IncrementSceneRotation();
private:
    BOOL        bProjMode;      // projection mode
    Perspective persp;          // perspective view description
    float       aspectRatio;    // x/y window aspect ratio
    POINT3D     world;          // view area in world space
};

#endif // __view_h__


XC.CXX

/******************************Module*Header*******************************\
* Module Name: xc.cxx
*
* Cross-section (xc) object stuff
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <float.h>

#include "sscommon.h"
#include "sspipes.h"
#include "eval.h"
#include "xc.h"

/**************************************************************************\
* XC::CalcArcACValues90
*
* Calculate arc control points for a 90 degree rotation of an xc
*
* Arc is a quarter-circle
* - 90 degree is much easier, so we special case it
* - radius is distance from xc-origin to hinge of turn
*
\**************************************************************************/

void
XC::CalcArcACValues90( int dir, float radius, float *acPts )
{
    int i;
    float sign;
    int offset;
    float *ppts = (float *) pts;

    // 1) calc 'r' values for each point (4 turn possibilities/point).  From
    //  this can determine ac, which is extrusion of point from xc face

    switch( dir ) {
        case PLUS_X:
            offset = 0;
            sign = -1.0f;
            break;
        case MINUS_X:
            offset = 0;
            sign =  1.0f;
            break;
        case PLUS_Y:
            offset = 1;
            sign = -1.0f;
            break;
        case MINUS_Y:
            offset = 1;
            sign =  1.0f;
            break;
    }

    for( i = 0; i < numPts; i++, ppts+=2, acPts++ ) {
        *acPts = EVAL_CIRC_ARC_CONTROL * (radius + (sign * ppts[offset]));
    }
    // replicate !
    *acPts = *(acPts - numPts);
}

/**************************************************************************\
* XC::CalcArcACValuesByDistance
*
* Use the distance of each xc point from the xc origin, as the radius for
* an arc control value.
*
\**************************************************************************/

void
XC::CalcArcACValuesByDistance( float *acPts )
{
    int i;
    float r;
    POINT2D *ppts =  pts;

    for( i = 0; i < numPts; i++, ppts++ ) {
        r = (float) sqrt( ppts->x*ppts->x + ppts->y*ppts->y );
        *acPts++ = EVAL_CIRC_ARC_CONTROL * r;
    }
    // replicate !
    *acPts = *(acPts - numPts);
}

/**************************************************************************\
* ELLIPTICAL_XC::SetControlPoints
*
* Set the 12 control points for a circle at origin in z=0 plane
*
* Points go CCW from +x
*
\**************************************************************************/

void
ELLIPTICAL_XC::SetControlPoints( GLfloat r1, GLfloat r2 )
{
    GLfloat ac1, ac2;

    ac1 = EVAL_CIRC_ARC_CONTROL * r2;
    ac2 = EVAL_CIRC_ARC_CONTROL * r1;

    // create 12-pt. set CCW from +x

    // last 2 points of right triplet
    pts[0].x = r1;
    pts[0].y = 0.0f;
    pts[1].x = r1;
    pts[1].y = ac1;

    // top triplet
    pts[2].x = ac2;
    pts[2].y = r2;
    pts[3].x = 0.0f;
    pts[3].y = r2;
    pts[4].x = -ac2;
    pts[4].y = r2;

    // left triplet
    pts[5].x = -r1;
    pts[5].y = ac1;
    pts[6].x = -r1;
    pts[6].y = 0.0f;
    pts[7].x = -r1;
    pts[7].y = -ac1;

    // bottom triplet
    pts[8].x = -ac2;
    pts[8].y = -r2;
    pts[9].x = 0.0f;
    pts[9].y = -r2;
    pts[10].x = ac2;
    pts[10].y = -r2;

    // first point of first triplet
    pts[11].x = r1;
    pts[11].y = -ac1;
}

/**************************************************************************\
* RANDOM4ARC_XC::SetControlPoints
*
* Set random control points for xc
* Points go CCW from +x
*
\**************************************************************************/

void
RANDOM4ARC_XC::SetControlPoints( float radius )
{
    int i;
    GLfloat r[4];
    float rMin = 0.5f * radius;
    float distx, disty;

    // figure the radius of each side first

    for( i = 0; i < 4; i ++ )
        r[i] = ss_fRand( rMin, radius );

    // The 4 r's now describe a box around the origin - this restricts stuff

    // Now need to select a point along each edge of the box as the joining
    // points for each arc (join points are at indices 0,3,6,9)

    pts[0].x = r[RIGHT];
    pts[3].y = r[TOP];
    pts[6].x = -r[LEFT];
    pts[9].y = -r[BOTTOM];

    // quarter of distance between edges
    disty = (r[TOP] - -r[BOTTOM]) / 4.0f;
    distx = (r[RIGHT] - -r[LEFT]) / 4.0f;

    // uh, put'em somwhere in the middle half of each side
    pts[0].y = ss_fRand( -r[BOTTOM] + disty, r[TOP] - disty );
    pts[6].y = ss_fRand( -r[BOTTOM] + disty, r[TOP] - disty );
    pts[3].x = ss_fRand( -r[LEFT] + distx, r[RIGHT] - distx );
    pts[9].x = ss_fRand( -r[LEFT] + distx, r[RIGHT] - distx );

    // now can calc ac's
    // easy part first:
    pts[1].x = pts[11].x = pts[0].x;
    pts[2].y = pts[4].y = pts[3].y;
    pts[5].x = pts[7].x = pts[6].x;
    pts[8].y = pts[10].y = pts[9].y;

    // right side ac's
    disty = (r[TOP] - pts[0].y) / 4.0f;
    pts[1].y = ss_fRand( pts[0].y + disty, r[TOP] );
    disty = (pts[0].y - -r[BOTTOM]) / 4.0f;
    pts[11].y = ss_fRand( -r[BOTTOM], pts[0].y - disty );

    // left side ac's
    disty = (r[TOP] - pts[6].y) / 4.0f;
    pts[5].y = ss_fRand( pts[6].y + disty, r[TOP]);
    disty = (pts[6].y - -r[BOTTOM]) / 4.0f;
    pts[7].y = ss_fRand( -r[BOTTOM], pts[6].y - disty );

    // top ac's
    distx = (r[RIGHT] - pts[3].x) / 4.0f;
    pts[2].x = ss_fRand( pts[3].x + distx, r[RIGHT] );
    distx = (pts[3].x - -r[LEFT]) / 4.0f;
    pts[4].x = ss_fRand( -r[LEFT],  pts[3].x - distx );

    // bottom ac's
    distx = (r[RIGHT] - pts[9].x) / 4.0f;
    pts[10].x = ss_fRand( pts[9].x + distx, r[RIGHT] );
    distx = (pts[9].x - -r[LEFT]) / 4.0f;
    pts[8].x = ss_fRand( -r[LEFT], pts[9].x - distx );
}

/**************************************************************************\
* ConvertPtsZ
*
* Convert the 2D pts in an xc, to 3D pts in point buffer, with z.
*
* Also replicate the last point.
\**************************************************************************/

void
XC::ConvertPtsZ( POINT3D *newpts, float z )
{
    int i;
    POINT2D *xcPts = pts;

    for( i = 0; i < numPts; i++, newpts++ ) {
        *( (POINT2D *) newpts ) = *xcPts++;
        newpts->z = z;
    }
    *newpts = *(newpts - numPts);
}

/**************************************************************************\
* XC::CalcBoundingBox
*
* Calculate bounding box in x/y plane for xc
\**************************************************************************/

void
XC::CalcBoundingBox( )
{
    POINT2D *ppts = pts;
    int i;
    float xMin, xMax, yMax, yMin;

    // initialize to really insane numbers
    xMax = yMax = -FLT_MAX;
    xMin = yMin = FLT_MAX;

    // compare with rest of points
    for( i = 0; i < numPts; i ++, ppts++ ) {
        if( ppts->x < xMin )
            xMin = ppts->x;
        else if( ppts->x > xMax )
            xMax = ppts->x;
        if( ppts->y < yMin )
            yMin = ppts->y;
        else if( ppts->y > yMax )
            yMax = ppts->y;
    }
    xLeft = xMin;
    xRight = xMax;
    yBottom = yMin;
    yTop = yMax;
}

/**************************************************************************\
*
* MinTurnRadius
*
* Get minimum radius for the xc to turn in given direction.
*
* If the turn radius is less than this minimum, then primitive will 'fold'
* over itself at the inside of the turn, creating ugliness.
*
\**************************************************************************/

float
XC::MinTurnRadius( int relDir )
{
// for now, assume xRight, yTop positive, xLeft, yBottom negative
// otherwise, might want to consider 'negative'radius
    switch( relDir ) {
        case PLUS_X:
            return( xRight );
        case MINUS_X:
            return( - xLeft );
        case PLUS_Y:
            return( yTop );
        case MINUS_Y:
            return( - yBottom );
        default:
            return(0.0f);
    }
}
/**************************************************************************\
*
* XC::MaxExtent
*
* Get maximum extent of the xc in x and y
*
\**************************************************************************/

float
XC::MaxExtent( )
{
    float max;

    max = xRight;

    if( yTop > max )
        max = yTop;
    if( -xLeft > max )
        max = -xLeft;
    if( -yBottom > max )
        max = -yBottom;

    return max;
}

/**************************************************************************\
*
* XC::Scale
*
* Scale an XC's points and extents by supplied scale value
*
\**************************************************************************/

void
XC::Scale( float scale )
{
    int i;
    POINT2D *ppts = pts;

    for( i = 0; i < numPts; i ++, ppts++ ) {
        ppts->x *= scale;
        ppts->y *= scale;
    }

    xLeft *= scale;
    xRight *= scale;
    yBottom *= scale;
    yTop *= scale;
}

/**************************************************************************\
* ~XC::XC
*
* Destructor
*
\**************************************************************************/

XC::~XC()
{
    if( pts )
        LocalFree( pts );
}

/**************************************************************************\
* XC::XC
*
* Constructor
*
* - Allocates point buffer for the xc
*
\**************************************************************************/

XC::XC( int nPts )
{
    numPts = nPts;
    pts = (POINT2D *)  LocalAlloc( LMEM_FIXED, numPts * sizeof(POINT2D) );
    SS_ASSERT( pts != 0, "XC constructor\n" );
}

XC::XC( XC *xc )
{
    numPts = xc->numPts;
    pts = (POINT2D *)  LocalAlloc( LMEM_FIXED, numPts * sizeof(POINT2D) );
    SS_ASSERT( pts != 0, "XC constructor\n" );
    RtlCopyMemory( pts, xc->pts, numPts * sizeof(POINT2D) );

    xLeft = xc->xLeft;
    xRight = xc->xRight;
    yBottom = xc->yBottom;
    yTop = xc->yTop;
}

/**************************************************************************\
*
* ELLIPTICAL_XC::ELLIPTICALXC
*
* Elliptical XC constructor

* These have 4 sections of 4 pts each, with pts shared between sections.
*
\**************************************************************************/

ELLIPTICAL_XC::ELLIPTICAL_XC( float r1, float r2 )
    // initialize base XC with numPts
    : XC( (int) EVAL_XC_CIRC_SECTION_COUNT * (EVAL_ARC_ORDER - 1))
{
    SetControlPoints( r1, r2 );
    CalcBoundingBox( );
}

/**************************************************************************\
*
* RANDOM4ARC_XC::RANDOM4ARC_XC
*
* Random 4-arc XC constructor

* The bounding box is 2*r each side
* These have 4 sections of 4 pts each, with pts shared between sections.
*
\**************************************************************************/
RANDOM4ARC_XC::RANDOM4ARC_XC( float r )
    // initialize base XC with numPts
    : XC( (int) EVAL_XC_CIRC_SECTION_COUNT * (EVAL_ARC_ORDER - 1))
{
    SetControlPoints( r );
    CalcBoundingBox( );
}


XC.H

#ifndef __xc_h__
#define __xc_h__

#include "sscommon.h"

// useful for xc-coords
enum {
    RIGHT = 0,
    TOP,
    LEFT,
    BOTTOM
};

// Cross_section (xc) class

class XC {
public:
    GLfloat     xLeft, xRight;  // bounding box
    GLfloat     yTop, yBottom;
    int         numPts;
    POINT2D     *pts;        // CW points around the xc, from +x

    XC( int numPts );
    XC( const XC& xc );
    XC( XC *xc );
    ~XC();
    void        Scale( float scale );
    float       MaxExtent();
    float       MinTurnRadius( int relDir );
    void        CalcArcACValues90( int dir, float r, float *acPts );
    void        CalcArcACValuesByDistance(  float *acPts );
    void        ConvertPtsZ( POINT3D *pts, float z );
protected:
    void        CalcBoundingBox();
};

// Specific xc's derived from base xc class

class ELLIPTICAL_XC : public XC {
public:
    ELLIPTICAL_XC( float r1, float r2 );
    ~ELLIPTICAL_XC();
private:
    void SetControlPoints( float r1, float r2 );
};

class RANDOM4ARC_XC : public XC {
public:
    RANDOM4ARC_XC( float r );
    ~RANDOM4ARC_XC();
private:
    void SetControlPoints( float radius );
};

#endif __xc_h__


DIALOG.C

/******************************Module*Header*******************************\
* Module Name: dialog.c
*
* Dialog box functions for the OpenGL-based 3D Text screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <scrnsave.h>
#include <GL\gl.h>
#include <math.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys\timeb.h>
#include <time.h>
#include "sscommon.h"
#include "sstext3d.h"

// Global screen saver settings.

static int idsStyles[NUM_DEMOS] =
    {IDS_DEMO_STRING, IDS_DEMO_CLOCK};

static int idsRotStyles[NUM_ROTSTYLES] =
{    IDS_ROTSTYLE_NONE,
     IDS_ROTSTYLE_SEESAW,
     IDS_ROTSTYLE_WOBBLE,
     IDS_ROTSTYLE_RANDOM
};

// local funtions

LONG WndProc(HWND, UINT, WPARAM, LPARAM);
static void InitAttrContext( AttrContext *pac );
static void getFont( HWND hDlg);
static void updateDialogControls(HWND hDlg);

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
\**************************************************************************/

void
getIniSettings()
{
    int    options;
    int    optMask = 1;
    TCHAR  szDefaultBitmap[MAX_PATH];
    int    iPos;

    // Initialize the global AttrContext

    InitAttrContext( &gac );

    // Load resources

    LoadString(hMainInstance, IDS_GENNAME, szScreenSaver,
               sizeof(szScreenSaver) / sizeof(TCHAR));

    // Get registry settings

    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        // get demo type

        gac.demoType = ss_GetRegistryInt( IDS_DEMOTYPE, DEMO_STRING );
        SS_CLAMP_TO_RANGE2( gac.demoType, 0, MAX_DEMO);

        // get rotation style

        gac.rotStyle = ss_GetRegistryInt( IDS_ROTSTYLE, ROTSTYLE_RANDOM );
        SS_CLAMP_TO_RANGE2( gac.rotStyle, 0, NUM_ROTSTYLES-1 );

        // get tesselation

        iPos = ss_GetRegistryInt( IDS_TESSELATION, 10 );
        SS_CLAMP_TO_RANGE2( iPos, MIN_SLIDER, MAX_SLIDER );
        gac.fTesselFact  = (float)iPos / 100.0f;

        // get size

        gac.uSize = ss_GetRegistryInt( IDS_SIZE, 50 );
        SS_CLAMP_TO_RANGE2( gac.uSize, MIN_SLIDER, MAX_SLIDER );

        // get speed

        gac.iSpeed = ss_GetRegistryInt( IDS_SPEED, 50 );
        SS_CLAMP_TO_RANGE2( gac.iSpeed, MIN_SLIDER, MAX_SLIDER );

        // get surface style

        gac.surfStyle = ss_GetRegistryInt( IDS_SURFSTYLE, SURFSTYLE_SOLID );
        SS_CLAMP_TO_RANGE2(gac.surfStyle, 0, SURFSTYLE_TEX);

        // get font, attributes, and charset

        ss_GetRegistryString( IDS_FONT, TEXT("Arial"), gac.szFontName, LF_FACESIZE);

        options = ss_GetRegistryInt( IDS_FONT_ATTRIBUTES, 0 );
        if( options >= 0 ) {
            optMask = 1;
            gac.bBold = ((options & optMask) != 0);
            optMask <<=1;
            gac.bItalic = ((options & optMask) != 0);
        }

        gac.charSet = ss_GetRegistryInt( IDS_CHARSET, 0 );

        // get display string

        ss_GetRegistryString( IDS_TEXT, TEXT("OpenGL"), gac.szText, TEXT_LIMIT+1);

        // Determine the default .bmp file

        ss_GetDefaultBmpFile( szDefaultBitmap );

        // Is there a texture specified in the registry that overrides the
        // default?

        ss_GetRegistryString( IDS_TEXTURE, szDefaultBitmap, gac.texFile.szPathName,
                              MAX_PATH);

        gac.texFile.nOffset = ss_GetRegistryInt( IDS_TEXTURE_FILE_OFFSET, 0 );
    }
}

/**************************************************************************\
* ConfigInit
*
\**************************************************************************/
BOOL
ss_ConfigInit( HWND hDlg )
{
    return TRUE;
}

/**************************************************************************\
* InitAttrContext
*
* Initialize some of the values in the attribute context
\**************************************************************************/
static void
InitAttrContext( AttrContext *pac )
{
    // set some default values

    pac->demoType = DEMO_STRING;
    pac->surfStyle = SURFSTYLE_SOLID;
    pac->fTesselFact = 1.0f;
    pac->uSize = 50;
    pac->iSpeed = 50;
    pac->texFile.szPathName[0] = '\0';
    pac->texFile.nOffset = 0;
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
\**************************************************************************/

static void
saveIniSettings(HWND hDlg)
{
    int options;
    int optMask = 1;

    GetWindowText( GetDlgItem(hDlg, DLG_TEXT_ENTER), gac.szText, TEXT_LIMIT+1);

    if( ss_RegistrySetup( hMainInstance, IDS_SAVERNAME, IDS_INIFILE ) )
    {
        ss_WriteRegistryInt( IDS_DEMOTYPE, gac.demoType );
        ss_WriteRegistryInt( IDS_ROTSTYLE, gac.rotStyle );
        ss_WriteRegistryInt( IDS_TESSELATION,
                    ss_GetTrackbarPos(hDlg, DLG_SETUP_TESSEL) );
        ss_WriteRegistryInt( IDS_SIZE,
                    ss_GetTrackbarPos(hDlg, DLG_SETUP_SIZE) );
        ss_WriteRegistryInt( IDS_SPEED,
                    ss_GetTrackbarPos(hDlg, DLG_SETUP_SPEED) );
        ss_WriteRegistryInt( IDS_SURFSTYLE, gac.surfStyle );
        ss_WriteRegistryString( IDS_FONT, gac.szFontName );

        optMask = 1;
        options = gac.bBold ? optMask : 0;
        optMask <<= 1;
        options |= gac.bItalic ? optMask : 0;
        ss_WriteRegistryInt( IDS_FONT_ATTRIBUTES, options );

        ss_WriteRegistryInt( IDS_CHARSET, gac.charSet );
        ss_WriteRegistryString( IDS_TEXT, gac.szText );
        ss_WriteRegistryString( IDS_TEXTURE, gac.texFile.szPathName );
        ss_WriteRegistryInt( IDS_TEXTURE_FILE_OFFSET, gac.texFile.nOffset );
    }
}

/******************************Public*Routine******************************\
* setupDialogControls
*
* Do initial setup of dialog controls.
\**************************************************************************/

static void
setupDialogControls(HWND hDlg)
{
    int pos;

    InitCommonControls();

    // initialize sliders

    // tesselation slider

    pos = (int)(gac.fTesselFact * 100.0f);
    ss_SetupTrackbar( hDlg, DLG_SETUP_TESSEL, MIN_SLIDER, MAX_SLIDER, 1, 9,
                      pos );

    // size slider

    ss_SetupTrackbar( hDlg, DLG_SETUP_SIZE, MIN_SLIDER, MAX_SLIDER, 1, 9,
                      gac.uSize );

    // speed slider

    ss_SetupTrackbar( hDlg, DLG_SETUP_SPEED, MIN_SLIDER, MAX_SLIDER, 1, 9,
                      gac.iSpeed);

    // set state of other controls

    updateDialogControls(hDlg);
}

/******************************Public*Routine******************************\
* updateDialogControls
*
* Updates dialog controls according to current state
\**************************************************************************/

static void
updateDialogControls(HWND hDlg)
{
    BOOL bTexSurf;
    BOOL bText;

    bTexSurf = (gac.surfStyle == SURFSTYLE_TEX );

    CheckDlgButton(hDlg, IDC_RADIO_SOLID, !bTexSurf );
    CheckDlgButton(hDlg, IDC_RADIO_TEX  , bTexSurf );

    // set up demo-specific configure button

    bText = (gac.demoType == DEMO_STRING) ? TRUE : FALSE;
    EnableWindow(GetDlgItem(hDlg, DLG_TEXT_ENTER), bText );
    CheckDlgButton(hDlg, IDC_DEMO_STRING, bText );
    CheckDlgButton(hDlg, IDC_DEMO_CLOCK, !bText );

    // texture: only enable if surfStyle is texture

    EnableWindow(GetDlgItem(hDlg, DLG_SETUP_TEX), bTexSurf );
}

/******************************Public*Routine******************************\
* getFont
*
* Calls ChooseFont dialog
\**************************************************************************/

static void
getFont( HWND hDlg)
{
    CHOOSEFONT cf = {0};
    LOGFONT    lf = {0};
    HDC   hdc;

    hdc = GetDC( hDlg );

    // Create and select a font.

    cf.lStructSize = sizeof(CHOOSEFONT);
    cf.hwndOwner = hDlg;
    cf.lpLogFont = &lf;
    cf.hInstance = hMainInstance;
    cf.lpTemplateName = (LPTSTR) MAKEINTRESOURCE(IDD_FONT);
    cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_TTONLY |
               CF_ENABLETEMPLATE | CF_NOSIMULATIONS;

    // setup logfont with current settings

    lstrcpy(lf.lfFaceName, gac.szFontName);
    lf.lfWeight = (gac.bBold) ? FW_BOLD : FW_NORMAL;
    lf.lfItalic = (gac.bItalic) ? (BYTE) 1 : 0;
    lf.lfCharSet = gac.charSet;
    lf.lfHeight = -37;  // value ???

    if( ChooseFont(&cf) ) {
        // retrieve settings into gac
        lstrcpy( gac.szFontName, lf.lfFaceName );
        gac.bBold = (lf.lfWeight == FW_NORMAL) ? FALSE : TRUE;
        gac.bItalic = (lf.lfItalic) ? TRUE : FALSE;
        gac.charSet = lf.lfCharSet;
    }
}

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

/******************************Public*Routine******************************\
* ScreenSaverConfigureDialog
*
* Processes messages for the configuration dialog box.
*
\**************************************************************************/

BOOL ScreenSaverConfigureDialog(HWND hDlg, UINT message,
                                WPARAM wParam, LPARAM lParam)
{
    int wTmp;
    TCHAR szStr[GEN_STRING_SIZE];

    switch (message) {
        case WM_INITDIALOG:
            getIniSettings();
            setupDialogControls(hDlg);

            // setup rotStyle combo box
            for (wTmp = 0; wTmp < NUM_ROTSTYLES; wTmp++) {
                LoadString(hMainInstance, idsRotStyles[wTmp], szStr,
                            GEN_STRING_SIZE);
                SendDlgItemMessage(hDlg, DLG_SETUP_ROTSTYLE, CB_ADDSTRING, 0,
                                   (LPARAM) szStr);
            }
            SendDlgItemMessage(hDlg, DLG_SETUP_ROTSTYLE, CB_SETCURSEL,
                               gac.rotStyle, 0);

            // display current string in box
            SendDlgItemMessage( hDlg, DLG_TEXT_ENTER, EM_LIMITTEXT, TEXT_LIMIT,0);
            SetWindowText( GetDlgItem(hDlg, DLG_TEXT_ENTER), gac.szText );

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DLG_SETUP_TYPES:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                            gac.demoType =
                                SendDlgItemMessage(hDlg, DLG_SETUP_TYPES,
                                                      CB_GETCURSEL, 0, 0);
                            updateDialogControls(hDlg);
                            break;
                        default:
                            break;
                    }
                    return FALSE;

                case DLG_SETUP_ROTSTYLE:
                    switch (HIWORD(wParam))
                    {
                        case CBN_EDITCHANGE:
                        case CBN_SELCHANGE:
                            gac.rotStyle =
                                SendDlgItemMessage(hDlg, DLG_SETUP_ROTSTYLE,
                                                      CB_GETCURSEL, 0, 0);
                            updateDialogControls(hDlg);
                            break;
                        default:
                            break;
                    }
                    return FALSE;

                case DLG_SETUP_TEX:
                    // Run choose texture dialog
                    ss_SelectTextureFile( hDlg, &gac.texFile );
                    break;

                case DLG_SETUP_FONT:
                    getFont(hDlg);
                    break;

                case IDC_RADIO_SOLID:
                case IDC_RADIO_TEX:
                    gac.surfStyle = IDC_TO_SURFSTYLE( LOWORD(wParam) );
                    updateDialogControls(hDlg);
                    break;

                case IDC_DEMO_STRING:
                case IDC_DEMO_CLOCK:
                    gac.demoType = IDC_TO_DEMOTYPE( LOWORD(wParam) );
                    updateDialogControls(hDlg);
                    if( LOWORD(wParam) == IDC_DEMO_STRING ) {
                        // set selected text focus
                        SetFocus( GetDlgItem(hDlg, DLG_TEXT_ENTER) );
                        SendDlgItemMessage( hDlg, DLG_TEXT_ENTER,
                                            EM_SETSEL, 0, -1 );
                    }
                    break;

                case IDOK:
                    saveIniSettings(hDlg);
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, FALSE);
                    break;

                default:
                    break;
            }
            return TRUE;
            break;

        default:
            return 0;
    }
    return 0;
}


FONT.C

/******************************Module*Header*******************************\
* Module Name: font.c
*
* Font and text handling for the OpenGL-based 3D Text screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
\**************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <float.h> // for FLT_MAX
#include <GL/gl.h>
#include "sscommon.h"
#include "sstext3d.h"

#ifndef UNICODE
// Support dbcs characters (this is a patch)
#define SS_DBCS_SUPPORT 1
#endif

static LISTENTRY* FindExtendedLUTEntry( TCHAR c, WglFontContext *pwfc );
static BOOL UpdateReducedFontList( TCHAR c, LISTENTRY *ple,
                                   WglFontContext *pwfc );
#ifdef SS_DBCS_SUPPORT
static BOOL UpdateReducedFontListDBCS( DWORD c, LISTENTRY *ple,
                                       WglFontContext *pwfc );
#endif
static void DeleteExtendedLUTTableEntries( WglFontContext *pwfc );
static void DeleteDirectLUTTableEntries( WglFontContext *pwfc );
static void DeleteLUTEntry( LISTENTRY *ple );

/******************************Public*Routine******************************\
* CreateWglFontContext
*
* Create a WglFontContext.
*
* Initializes the look-up-table memory for characters.
* The first half of this table is a direct look-up, using the char value.
* The second half is used for unicode chars that are >256, and must be
* searched sequentially.
*
\**************************************************************************/

WglFontContext *
CreateWglFontContext( HDC hdc, int type, float fExtrusion,
                      float fChordalDeviation )
{
    WglFontContext *pwfc;

    pwfc = (WglFontContext *) LocalAlloc( LMEM_FIXED, sizeof(WglFontContext) );
    if( !pwfc ) {
        SS_ALLOC_FAILURE( "CreateWglFontContext" );
        return NULL;
    }

    pwfc->hdc = hdc;
    pwfc->type = type;
    pwfc->extrusion = fExtrusion;
    pwfc->chordalDeviation = fChordalDeviation;

    pwfc->listLUT = (LISTENTRY*) LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT,
                                      SIZE_LIST_LUT * sizeof(LISTENTRY) );

    if( !pwfc->listLUT ) {
        LocalFree( pwfc );
        SS_ALLOC_FAILURE( "CreateWglFontContext" );
        return NULL;
    }

    pwfc->LUTIndex = 0;

    return pwfc;
}

/******************************Public*Routine******************************\
* DeleteWglFontContext
*
* Delete a WglFontContext.
*
\**************************************************************************/

void
DeleteWglFontContext( WglFontContext *pwfc )
{
    if( !pwfc )
        return;

    // Delete the extended lut table entries
    DeleteExtendedLUTTableEntries( pwfc );

    // Delete the direct lut table entries
    DeleteDirectLUTTableEntries( pwfc );

    if( pwfc->listLUT )
        LocalFree( pwfc->listLUT );

    LocalFree( pwfc );
}

/******************************Public*Routine******************************\
* DeleteExtendedLUTTablEntries
*
\**************************************************************************/

static void
DeleteExtendedLUTTableEntries( WglFontContext *pwfc )
{
    int i;
    LISTENTRY *ple = pwfc->listLUT + MAX_DIRECT_LUT;

    for( i = 0; i < pwfc->LUTIndex; i ++, ple++ )
        DeleteLUTEntry( ple );

    pwfc->LUTIndex = 0;
}

/******************************Public*Routine******************************\
* DeleteDirectLUTTablEntries
*
\**************************************************************************/

static void
DeleteDirectLUTTableEntries( WglFontContext *pwfc )
{
    int i;
    LISTENTRY *ple = pwfc->listLUT;

    for( i = 0; i < MAX_DIRECT_LUT; i ++, ple++ )
        DeleteLUTEntry( ple );
}

/******************************Public*Routine******************************\
* DeleteLUTEntry
*
\**************************************************************************/

static void
DeleteLUTEntry( LISTENTRY *ple )
{
    if( ple->listNum ) {
        glDeleteLists( ple->listNum, 1 );
        ple->listNum = 0;
    }
    if( ple->lpgmf ) {
        LocalFree( ple->lpgmf );
        ple->lpgmf = NULL;
    }
    ple->glyph = 0;
}

/**************************************************************************\
* ConvertStringToList
*
* - Translate the string into display list numbers
* - If no entry in the list table, update the table
*
\**************************************************************************/
void
ConvertStringToList( LPTSTR pszSrc, USHORT *usDst, WglFontContext *pwfc )
{
    int strLen;
    LISTENTRY *ple;
    TCHAR c;
    TUCHAR cindex; // use unsigned TCHAR for table indexing
#ifdef SS_DBCS_SUPPORT
    BOOL bIsLeadByte;
#endif

    if( !pwfc )
        return;

    /* Go thru the string, making sure every char has a display list
     * Copy the display list # into usDst
     */

    strLen = lstrlen( pszSrc );
    if( strLen > TEXT_BUF_SIZE )
        strLen = TEXT_BUF_SIZE;

    /* Check for possible overflow with the extended LUT section, and
     * invalidate the extended LUT if so
     */
    if( pwfc->LUTIndex ) {
        int roomLeft; // spaces left in extended lut table

        roomLeft = SIZE_LIST_LUT - (pwfc->LUTIndex + MAX_DIRECT_LUT +1);
        if( roomLeft < strLen ) {
            /* If none of the characters in this string have entries in the
             * table, we'll run out of room.  Assume the worst, invalidate
             * the whole table, and start over again.
             */
            DeleteExtendedLUTTableEntries( pwfc );
        }
    }

    for( ; strLen; strLen--, pszSrc++ ) {
        c = *pszSrc;
        cindex = (TUCHAR) c;

        // see if char already has cmd list entry - if not, create it
#ifdef SS_DBCS_SUPPORT
        bIsLeadByte = IsDBCSLeadByte(cindex);
        if( !bIsLeadByte && (cindex < MAX_DIRECT_LUT) ) {  // direct LUT
#else
        if( cindex < MAX_DIRECT_LUT ) {  // direct LUT entry
#endif
            ple = &pwfc->listLUT[cindex];
            if( !ple->listNum ) {
                // have to create new cmd list for this glyph
                if( ! UpdateReducedFontList( c, ple, pwfc ) ) {
                    DeleteLUTEntry( ple );
                    continue;
                }
            }
        } else {  // extended LUT entry
#ifdef SS_DBCS_SUPPORT
            DWORD dwChar;
            if( bIsLeadByte ) {
                // Iterate over the lead byte
                pszSrc++;
                strLen--;
                c = *pszSrc;
                // Setup the double byte word so that :
                //   High byte is the lead byte
                //   Low byte is the next byte in the byte stream (this byte
                //   will be used to access the extended lut table)
                dwChar = (DWORD) ( (cindex << 8) | ((TUCHAR) c)  );
            } else
                dwChar = (DWORD) (TUCHAR) c;

            if( !(ple = FindExtendedLUTEntry( c, pwfc )) ) {
                ple = &pwfc->listLUT[pwfc->LUTIndex+MAX_DIRECT_LUT];
                if( ! UpdateReducedFontListDBCS( dwChar, ple, pwfc ) ) {
                    DeleteLUTEntry( ple );
                    continue;
                }
                ple->glyph = c;
                pwfc->LUTIndex++;
            }
#else
            if( !(ple = FindExtendedLUTEntry( c, pwfc )) ) {
                ple = &pwfc->listLUT[pwfc->LUTIndex+MAX_DIRECT_LUT];
                if( ! UpdateReducedFontList( c, ple, pwfc ) ) {
                    DeleteLUTEntry( ple );
                    continue;
                }
                ple->glyph = c;
                pwfc->LUTIndex++;
            }
#endif
        }
        *usDst++ = ple->listNum;
    }
    // zero terminate
    *usDst = 0;
}

/******************************Public*Routine******************************\
* UpdateReducedFontList
*
* Calls wglUseFontOutlines() to create a command list for the supplied
* character.
*
* Allocates memory for the characters glyphmetrics as well
*
\**************************************************************************/
static BOOL
UpdateReducedFontList( TCHAR c, LISTENTRY *ple, WglFontContext *pwfc )
{
    ple->lpgmf = (LPGLYPHMETRICSFLOAT) LocalAlloc( LMEM_FIXED,
                                                sizeof( GLYPHMETRICSFLOAT ) );

    if( !ple->lpgmf ) {
        SS_ALLOC_FAILURE( "UpdateReducedFontList" );
        return FAILURE;
    }

    // get next list # for this glyph.
    ple->listNum = (USHORT) glGenLists( 1 );
    if( !ple->listNum ) {
        SS_WARNING( "glGenLists failed\n" );
        return FAILURE;
    }

    if( !wglUseFontOutlines(pwfc->hdc, (TUCHAR) c, 1, ple->listNum,
               pwfc->chordalDeviation,
               pwfc->extrusion,
               pwfc->type, ple->lpgmf) ) {
        SS_WARNING( "wglUseFontOutlines failed\n" );
        return FAILURE;
    }
    return SUCCESS;
}

#ifdef SS_DBCS_SUPPORT

// Modified version of UpdateReducedFontList that handles dbcs characters.
// This is a *patch* (as is all the SS_DBCS_SUPPORT stuff) to handle dbcs
// characters without jeopardizing SUR release stability.  Later, the functions
// can be consolidated.

static BOOL
UpdateReducedFontListDBCS( DWORD c, LISTENTRY *ple, WglFontContext *pwfc )
{
    ple->lpgmf = (LPGLYPHMETRICSFLOAT) LocalAlloc( LMEM_FIXED,
                                                sizeof( GLYPHMETRICSFLOAT ) );

    if( !ple->lpgmf ) {
        SS_ALLOC_FAILURE( "UpdateReducedFontList" );
        return FAILURE;
    }

    // get next list # for this glyph.
    ple->listNum = (USHORT) glGenLists( 1 );
    if( !ple->listNum ) {
        SS_WARNING( "glGenLists failed\n" );
        return FAILURE;
    }

    if( !wglUseFontOutlines(pwfc->hdc, c, 1, ple->listNum,
               pwfc->chordalDeviation,
               pwfc->extrusion,
               pwfc->type, ple->lpgmf) ) {
        SS_WARNING( "wglUseFontOutlines failed\n" );
        return FAILURE;
    }
    return SUCCESS;
}
#endif

/******************************Public*Routine******************************\
* FindExtendedLUTEntry
*
* Searches through the extended character LUT, and returns ptr to the
* char's info if found, otherwise NULL.
*
\**************************************************************************/

static LISTENTRY*
FindExtendedLUTEntry( TCHAR c, WglFontContext *pwfc )
{
    int i;
    LISTENTRY *ple = pwfc->listLUT + MAX_DIRECT_LUT;

    for( i = 0; i < pwfc->LUTIndex; i ++, ple++ ) {
        if( ple->glyph == c )
            return ple;
    }
    return NULL;
}

/******************************Public*Routine******************************\
* DrawString
*
* Draws string by calling cmd list for each char.
*
\**************************************************************************/

void
DrawString(  USHORT           *string,
             int              strLen,
             WglFontContext   *pwfc )
{
    if( !pwfc )
        return;

    glCallLists(strLen, GL_UNSIGNED_SHORT, (GLushort *) string);
}

/******************************Public*Routine******************************\
* GetStringExtent
*
* Calculate a string's origin and extent in world coordinates.
*
* The origin is determined by the first character's location, and thereafter
* each char's location is determined by adding the previous char's cellInc
* values.
*
* This will work for all string orientations.
*
\**************************************************************************/

int
GetStringExtent(  LPTSTR          szString,
                  POINTFLOAT      *extent,
                  POINTFLOAT      *origin,
                  WglFontContext  *pwfc )
{

    int len, strLen;
    TCHAR *c;
    TUCHAR cindex;
    LPGLYPHMETRICSFLOAT lpgmf;
    POINTFLOAT cellOrigin = {0.0f, 0.0f};
    POINTFLOAT extentOrigin = {FLT_MAX, -FLT_MAX};
    POINTFLOAT extentLowerRight = {-FLT_MAX, FLT_MAX};
    POINTFLOAT boxOrigin, boxLowerRight;
    LISTENTRY *listLUT, *ple;
#ifdef SS_DBCS_SUPPORT
    BOOL bIsLeadByte;
#endif

    if( !pwfc )
        return 0;

    listLUT = pwfc->listLUT;

    extent->x = extent->y = 0.0f;
    origin->x = origin->y = 0.0f;
    len = strLen = lstrlen( szString );
    if( !len )
        return 0;  // otherwise extents will be calc'd erroneously

    c = szString;

    for( ; strLen; strLen--, c++ ) {
        cindex = (TUCHAR) *c;
#ifdef SS_DBCS_SUPPORT
        if( bIsLeadByte = IsDBCSLeadByte(cindex) ) {
            // iterate over lead byte
            c++;
            strLen--;
            cindex = (TUCHAR) *c;
        }
        if( !bIsLeadByte && (cindex < MAX_DIRECT_LUT) )
#else
        if( cindex < MAX_DIRECT_LUT )
#endif
            ple = &listLUT[cindex];
        else
            ple = FindExtendedLUTEntry( *c, pwfc );

        lpgmf = ple->lpgmf;

        if( !lpgmf )
            // Memory must be running low, but keep going
            continue;

        // calc global position of this char's BlackBox (this is
        //  'upper left')
        boxOrigin.x = cellOrigin.x + lpgmf->gmfptGlyphOrigin.x;
        boxOrigin.y = cellOrigin.y + lpgmf->gmfptGlyphOrigin.y;

        // calc lower right position
        boxLowerRight.x = boxOrigin.x + lpgmf->gmfBlackBoxX;
        boxLowerRight.y = boxOrigin.y - lpgmf->gmfBlackBoxY;

        // compare against the current bounding box

        if( boxOrigin.x < extentOrigin.x )
            extentOrigin.x = boxOrigin.x;
        if( boxOrigin.y > extentOrigin.y )
            extentOrigin.y = boxOrigin.y;
        if( boxLowerRight.x > extentLowerRight.x )
            extentLowerRight.x = boxLowerRight.x;
        if( boxLowerRight.y < extentLowerRight.y )
            extentLowerRight.y = boxLowerRight.y;

        // set global position of next cell
        cellOrigin.x = cellOrigin.x + lpgmf->gmfCellIncX;
        cellOrigin.y = cellOrigin.y + lpgmf->gmfCellIncY;
    }

    // Check for possible total lack of glyphmetric info
    if( extentOrigin.x == FLT_MAX ) {
        // Can assume if this value is still maxed out that all glyphmetric
        // info was NULL
        origin->x = origin->y = 0.0f;
        extent->x = extent->y = 0.0f;
        return 0;
    }

    *origin = extentOrigin;
    extent->x = extentLowerRight.x - extentOrigin.x;
    extent->y = extentOrigin.y - extentLowerRight.y;
    return len;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by sstext3d.rc
//
#define IDI_ICON1                       103
#define IDD_DIALOG1                     104
#define IDC_BUTTON1                     1022
#define IDC_EDIT1                       1023
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        106
#define _APS_NEXT_COMMAND_VALUE         101
#define _APS_NEXT_CONTROL_VALUE         1042
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SSTEXT3D.C

/******************************Module*Header*******************************\
* Module Name: sstext3d.c
*
* Core code for text3D screen saver
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>
#include <scrnsave.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include "sscommon.h"
#include "sstext3d.h"

#define FMAX_CHORDAL_DEVIATION   0.008f

#define FMIN_DEPTH           0.15f
#define FMAX_DEPTH           0.6f

#define FMIN_VIEW_ANGLE      90.0f
#define FMAX_VIEW_ANGLE     130.0f

#define FMIN_RANDOM_ANGLE    45.0f
#define FMAX_RANDOM_ANGLE    89.0f

#define FMIN_SEESAW_ANGLE    63.0f
#define FMAX_SEESAW_ANGLE    88.0f

#define FMIN_WOBBLE_ANGLE    30.0f
#define FMAX_WOBBLE_ANGLE    55.0f
#define FMIN_WOBBLE_ANGLE2   40.0f
#define FMAX_WOBBLE_ANGLE2   80.0f

#define MIN_ROT_STEP          1
#define MAX_ROT_STEP         20

#define FMAX_ZOOM             5.0f

// globals

static FLOAT gfMinCycleTime = 10.0f;
static POINTFLOAT gTrig[360];  // pre-calculated table of sines and cosines
static POINTFLOAT gSawTooth[360]; // sawtooth table
static POINTFLOAT gInvTrig[360];  // pseudo-inverse trig table
static POINT gTrigDif[360];   // table for converting trig->invtrig
static POINT gInvTrigDif[360];   // table for converting invtrig->trig

AttrContext gac;

// Default texture resource
TEX_RES gTexRes = { TEX_BMP, IDB_DEFTEX };

typedef struct _LIST *PLIST;
typedef struct _LIST {
    PLIST pnext;
    PLIST plistComplete;
    LPTSTR pszStr;
} LIST;

PLIST gplistComplete = NULL;
PLIST gplist = NULL;
static void DeleteNameList();

void text3d_Init( void *data  );
void text3d_Reset(void *data );
void text3d_Draw(void *data );
void text3d_Reshape(int width, int height, void *data );
void text3d_Finish( void *data );
static void CalcViewParams( AttrContext *pac );
static BOOL InitFont( AttrContext *pac );
static void InitLighting( AttrContext *pac );
static void InitTexture( AttrContext *pac );
static void InitMaterials( AttrContext *pac );
static void InitView( AttrContext *pac );
static FLOAT MapValue( FLOAT fInVal,
                       FLOAT fIn1, FLOAT fIn2,
                       FLOAT fOut1, FLOAT fOut2 );
static int  MapValueI( int inVal, int in1, int in2, int out1, int out2 );
static FLOAT CalcChordalDeviation( HDC hdc, AttrContext *pac );
static void (*BoundingBoxProc)( AttrContext *pac);
static void CalcBoundingBox( AttrContext *pac );
static void CalcBoundingBoxFromSphere( AttrContext *pac );
static void CalcBoundingBoxFromSpherePlus( AttrContext *pac, FLOAT zmax );
static void CalcBoundingBoxGeneric( AttrContext *pac );
static void CalcBoundingBoxFromExtents( AttrContext *pac, POINT3D *box );
static void CalcBoundingExtent( FLOAT rot, FLOAT x, FLOAT y,
                                POINTFLOAT *extent );
static void SortBoxes( POINT3D *box, FLOAT *boxvp, int numBox );
static void (*GetNextRotProc)( AttrContext *pac );
static void GetNextRotNone( AttrContext *pac );
static void GetNextRotRandom( AttrContext *pac );
static void GetNextRotWobble( AttrContext *pac );
static void InitTrigTable();
static void text3d_UpdateTime( AttrContext *pac, BOOL bCheckBounds );
static void text3d_UpdateString( AttrContext *pac, BOOL bCheckBounds );
static BOOL VerifyString( AttrContext *pac );
static BOOL CheckKeyStrings( LPTSTR testString, PSZ psz );
static void ConvertStringAsciiToUnicode( PSZ psz, PWSTR pwstr, int len );
static void InvertBitsA( char *s, int len );
static void ReadNameList();
static PSZ ReadStringFileA( char *file );
static void CreateRandomList();
static void ResetRotationLimits( AttrContext *pac, int *reset );
static int FrameCalibration( AttrContext *pac, struct _timeb *pBaseTime, int framesPerCycle,
                             int nCycle );
static void SetTransitionPoints( AttrContext *pac, int framesPerCycle,
                                int *trans1, int *trans2, FLOAT *zTrans );
static void
AdjustRotationStep( AttrContext *pac, int *reset, POINTFLOAT *oldTrig );

/******************************Public*Routine******************************\
* SetFloaterInfo
*
* Set the size and motion of the floating window
*
* ss_SetWindowAspectRatio may be called after this, to finely crop the
* window to the text being displayed.  But we can't call it here, since this
* function is called by common when creating the floating window, before the
* text string size has been determined.
\**************************************************************************/

static void
SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild )
{
    float sizeFact;
    float sizeScale;
    int size;
    ISIZE *pChildSize = &pChild->size;
    MOTION_INFO *pMotion = &pChild->motionInfo;
    AttrContext *pac = &gac;

    sizeScale = (float)pac->uSize / 100.0f;  // range 0..1
    sizeFact = 0.25f + (0.5f * sizeScale);     // range 25-75%
    size = (int) (sizeFact *
            ( ((float)(pParentSize->width + pParentSize->height)) / 2.0f ));
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->width );
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->height );

    pChildSize->width = pChildSize->height = size;
    pMotion->posInc.x = .01f * (float) size;
    if( pMotion->posInc.x < 1.0f )
        pMotion->posInc.x = 1.0f;
    pMotion->posInc.y = pMotion->posInc.x;
    pMotion->posIncVary.x = .4f * pMotion->posInc.x;
    pMotion->posIncVary.y = pMotion->posIncVary.x;
}

/******************************Public*Routine******************************\
* Init
*
* Initialize - called on first entry into ss.
* Called BEFORE gl is initialized!
* Just do basic stuff here, like set up callbacks, verify dialog stuff, etc.
*
* Fills global SSContext structure with required data, and returns ptr
* to it.
*
\**************************************************************************/

SSContext *
ss_Init( void )
{
    // validate some initial dialog settings
    getIniSettings();  // also called on dialog init

    // must verify textures here, before GL floater windows are created
    if( gac.surfStyle == SURFSTYLE_TEX ) {
        ss_DisableTextureErrorMsgs();
        ss_VerifyTextureFile( &gac.texFile );
    }

    // set Init callback
    ss_InitFunc( text3d_Init );

    // set data ptr to be sent with callbacks
    ss_DataPtr( &gac );

    // set configuration info to return

    gac.ssc.bFloater = TRUE;
    gac.ssc.floaterInfo.bMotion = TRUE;
    gac.ssc.floaterInfo.ChildSizeFunc = SetFloaterInfo;

    gac.ssc.bDoubleBuf = TRUE;
    gac.ssc.depthType = SS_DEPTH16;

    return &gac.ssc;
}

/******************************Public*Routine******************************\
* text3d_Init
*
* Initializes OpenGL state for text3d screen saver
*
\**************************************************************************/
void
text3d_Init( void *data )
{
    AttrContext *pac = (AttrContext *) data;

    // Set any callbacks that require GL
    ss_UpdateFunc( text3d_Draw );
    ss_ReshapeFunc( text3d_Reshape );
    ss_FinishFunc( text3d_Finish );

#ifdef SS_DEBUG
    glClearColor( 0.2f, 0.2f, 0.2f, 0.0f );
#else
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
#endif

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    // this sequence must be maintained

    InitLighting( pac );

    InitFont( pac );

    InitView( pac );

    InitTexture( pac );

    InitMaterials( pac );
}

/**************************************************************************\
* InitLighting
*
* Initialize lighting, and back face culling.
*
\**************************************************************************/
static void
InitLighting( AttrContext *pac )
{
    float ambient1[] = {0.2f, 0.2f, 0.2f, 1.0f};
    float ambient2[] = {0.1f, 0.1f, 0.1f, 1.0f};
    float diffuse1[] = {0.7f, 0.7f, 0.7f, 1.0f};
    float diffuse2[] = {0.7f, 0.7f, 0.7f, 1.0f};
    float position1[] = {0.0f, 50.0f, 150.0f, 0.0f};
    float position2[] = {25.0f, 150.0f, 50.0f, 0.0f};
    float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient1);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse1);
    glLightfv(GL_LIGHT0, GL_POSITION, position1);
    glEnable(GL_LIGHT0);

    glLightfv(GL_LIGHT1, GL_AMBIENT, ambient2);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse2);
    glLightfv(GL_LIGHT1, GL_POSITION, position2);
    glEnable(GL_LIGHT1);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
    glCullFace( GL_BACK );
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
}

/**************************************************************************\
* TestFont
*
* Test that GetOutlineTextMetrics works.  If not, wglUseFontOutlines will fail.
*
* If the font tests bad, delete it and select in the previous one.
*
\**************************************************************************/

static BOOL
TestFont( HFONT hfont )
{
    OUTLINETEXTMETRIC otm;
    HFONT hfontOld;
    HDC hdc = wglGetCurrentDC();

    hfontOld = SelectObject(hdc, hfont);

    if( GetOutlineTextMetrics( hdc, sizeof(otm), &otm) <= 0 ) {
        SS_DBGPRINT( "sstext3d Init: GetOutlineTextMetrics failure\n" );
        SelectObject(hdc, hfontOld);
        DeleteObject( hfont );
        return FALSE;
    }
    return TRUE;
}

/**************************************************************************\
* CreateFont
*
* Create a true type font and test it
*
\**************************************************************************/

static HFONT
text3d_CreateFont( LOGFONT *plf )
{
    HFONT hfont;

    // Create font from LOGFONT data
    hfont = CreateFontIndirect(plf);

    if( hfont ) {
        // Test the font
        if( ! TestFont( hfont ) )
            hfont = (HFONT) 0;
    }
    return hfont;
}

/**************************************************************************\
* InitFont
*
*
\**************************************************************************/
static BOOL
InitFont( AttrContext *pac )
{
    LOGFONT lf;
    HFONT hfont;
    int type;
    float fChordalDeviation;
    HDC hdc = wglGetCurrentDC();

    // Set up the LOGFONT structure

    memset(&lf, 0, sizeof(LOGFONT));
    lstrcpy( lf.lfFaceName, pac->szFontName );
    lf.lfWeight = (pac->bBold) ? FW_BOLD : FW_NORMAL;
    lf.lfItalic = (pac->bItalic) ? (BYTE) 1 : 0;

    lf.lfHeight = 0; // shouldn't matter
    lf.lfCharSet = pac->charSet;
    lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;

    // Create the font

    if( ! (hfont = text3d_CreateFont( &lf )) ) {
        // Couldn't create a true type font with supplied data
        SS_DBGPRINT( "initial text3d_CreateFont failed: \n" );
    }

    if( !hfont && ss_fOnWin95() ) {
    // the font mapper didn't give us anything useful
    // For some reason GetOutlineTextMetrics fails for some fonts (Symbol)
    // when using lfHeight = 0 (default height value).
        lf.lfHeight = -10;
        if( ! (hfont = text3d_CreateFont( &lf )) ) {
            SS_DBGPRINT( "text3d_CreateFont with lfHeight != 0 failed: \n" );
        }
    }

    if( hfont == NULL ) {
        /* The requested font cannot be loaded.  Try to get the system to
         * load any TrueType font
         */
        hfont = CreateFont( 100, 100, 0, 0, lf.lfWeight, lf.lfItalic,
                             0, 0, 0, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
                             DEFAULT_QUALITY, DEFAULT_PITCH, NULL );
        // If hfont is still null, nothing will be displayed.
        if( !hfont || !TestFont(hfont) ) {
            SS_DBGPRINT( "text3d_InitFont failure\n" );
            return FALSE;
        }
    }

    // We have a valid font

    SelectObject(hdc, hfont);

    // Set extrusion, chordal deviation, and font type

#ifdef _PPC_
    // !!! Work around for PPC compiler bug

    // calculate chordalDeviation from input attribute fTesselFact
    fChordalDeviation = CalcChordalDeviation( hdc, pac );

    pac->fDepth = ss_fRand( FMIN_DEPTH, FMAX_DEPTH );
#else
    pac->fDepth = ss_fRand( FMIN_DEPTH, FMAX_DEPTH );

    // calculate chordalDeviation from input attribute fTesselFact
    fChordalDeviation = CalcChordalDeviation( hdc, pac );
#endif

    type = pac->surfStyle == SURFSTYLE_WIREFRAME ? WGL_FONT_LINES :
                                                   WGL_FONT_POLYGONS;

    // Create a wgl font context

    if( !(pac->pWglFontC =
            CreateWglFontContext( hdc, type, pac->fDepth, fChordalDeviation )) )
        return FALSE;

    // intialize the text that will be displayed

    if( pac->demoType == DEMO_CLOCK ) {
        text3d_UpdateTime( pac, FALSE );  // sets pac->textXXX params as well
    } else if( pac->demoType == DEMO_STRING ) {
        if( !VerifyString( pac ) ) {
            ConvertStringToList( pac->szText, pac->usText, pac->pWglFontC );
            pac->textLen = GetStringExtent( pac->szText, &pac->pfTextExtent,
                                         &pac->pfTextOrigin,
                                         pac->pWglFontC );
        }
    }
    return SUCCESS;
}

/**************************************************************************\
* InitView
*
*
\**************************************************************************/
static void
InitView( AttrContext *pac )
{
    int numRots=0, axis;
    FLOAT *p3dRotMax = (FLOAT *) &pac->p3dRotMax;
    FLOAT *p3dRotMin = (FLOAT *) &pac->p3dRotMin;
    int *ip3dRotStep = (int *) &pac->ip3dRotStep;
    POINT3D p3d_zero = {0.0f, 0.0f, 0.0f};
    int stepRange = 2; // default step range
    int reset[NUM_AXIS] = {1, 1, 1};

    // text is either xmajor or ymajor
    pac->bXMajor = pac->pfTextExtent.x >= pac->pfTextExtent.y ? TRUE : FALSE;

    /* At this point, the initial string extents will have been
     * calculated, and we can use this to determine rotational
     * characteristics
     */

    // default proc to get next rotation
    GetNextRotProc = GetNextRotRandom;

    /* convert the slider speed values to rotation steps, with
     * a steeper slope at the beginning of the scale
     */
    pac->iRotStep = MapValueI( pac->iSpeed,
                               MIN_SLIDER, MAX_SLIDER,  // slider range
                               MIN_ROT_STEP, MAX_ROT_STEP ); // step range

    // initialize rotation min/max to 0
    *( (POINT3D*)p3dRotMin ) = p3d_zero;
    *( (POINT3D*)p3dRotMax ) = p3d_zero;
    pac->p3dRot = p3d_zero;

    /* Set the MAXIMUM rotation limits.  This is required initially, in
     * order to set the bounding box
     */
    switch( pac->rotStyle ) {
        case ROTSTYLE_NONE:
            GetNextRotProc = GetNextRotNone;
            break;

        case ROTSTYLE_SEESAW:
            // rotate minor axis
            if( pac->demoType == DEMO_VSTRING )
                // always rotate around y-axis
                axis = Y_AXIS;
            else
                axis = pac->bXMajor ? Y_AXIS : X_AXIS;
            p3dRotMin[axis] = FMIN_SEESAW_ANGLE;
            p3dRotMax[axis] = FMAX_SEESAW_ANGLE;
            break;

        case ROTSTYLE_WOBBLE:
            GetNextRotProc = GetNextRotWobble;
            if( pac->demoType == DEMO_VSTRING ) {
                axis = Y_AXIS;
            }
            else {
                stepRange = 1;
                axis = pac->bXMajor ? Y_AXIS : X_AXIS;
            }
            p3dRotMin[Z_AXIS] = FMAX_WOBBLE_ANGLE;
            p3dRotMax[Z_AXIS] = FMAX_WOBBLE_ANGLE;
            p3dRotMin[axis] = FMIN_WOBBLE_ANGLE2;
            p3dRotMax[axis] = FMAX_WOBBLE_ANGLE2;
            break;

        case ROTSTYLE_RANDOM:
            // adjust stepRange based on speed
            stepRange = MapValueI( pac->iSpeed,
                               MIN_SLIDER, (MAX_SLIDER-MIN_SLIDER)/2,
                               2, 6 ); // step range
            for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) {
                p3dRotMin[axis] = FMIN_RANDOM_ANGLE;
                p3dRotMax[axis] = FMAX_RANDOM_ANGLE;
            }
            break;
    }

    // set min and max steps
    pac->iRotMinStep = pac->iRotStep >= (MIN_ROT_STEP + stepRange) ?
              pac->iRotStep - stepRange : MIN_ROT_STEP;
    pac->iRotMaxStep = pac->iRotStep + stepRange; // don't limit upper end

    for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) {
        ip3dRotStep[axis] = p3dRotMax[axis] != 0.0f ?
                ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep ) : 0;
    }

    // initialize the step iteration
    pac->ip3dRoti.x = pac->ip3dRoti.y = pac->ip3dRoti.z = 0;

    // initialize the trig table, for fast rotation calculations
    InitTrigTable();

    // set the current rotation limits
    pac->p3dRotLimit = *( (POINT3D *)p3dRotMax );
    ResetRotationLimits( pac, reset );

    // set view angle
    pac->fFovy = ss_fRand( FMIN_VIEW_ANGLE, FMAX_VIEW_ANGLE );

    for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) {
        if( p3dRotMax[axis] != 0.0f )
            numRots++;
    }

    // set BoundingBoxProc dependent on which axis are being rotated
    if( numRots <= 1 )
        BoundingBoxProc = CalcBoundingBox;
    else
        BoundingBoxProc = CalcBoundingBoxGeneric;

    (*BoundingBoxProc)( pac );
    if( pac->p3dBoundingBox.y == 0.0f )
        pac->p3dBoundingBox.y = 1.0f;
}

/**************************************************************************\
* InitMaterials
*
*
\**************************************************************************/
static void
InitMaterials( AttrContext *pac )
{
    if( pac->bTexture ) {
        ss_InitTexMaterials();
        pac->bMaterialCycle = FALSE;
        pac->pMat = ss_RandomTexMaterial( TRUE );
    } else {
        ss_InitTeaMaterials();
        pac->bMaterialCycle = TRUE;
        pac->pMat = ss_RandomTeaMaterial( TRUE );
    }
}

/**************************************************************************\
* InitTexture
*
\**************************************************************************/
static void
InitTexture( AttrContext *pac )
{
    if( pac->surfStyle != SURFSTYLE_TEX )
        return;

    // No choice for texture quality in dialog - set to HIGH
    pac->texQual = TEXQUAL_HIGH;

    // Try to load the texture file or default texture resource

    if( ss_LoadTextureFile( &pac->texFile, &pac->texture ) ||
        ss_LoadTextureResource( &gTexRes, &pac->texture ) )
    {
        pac->bTexture = 1;

        glEnable(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        ss_SetTexture( &pac->texture );

        // set auto texture coord generation
        ss_InitAutoTexture( NULL );
    }
    else {  // couldn't open .bmp file
        pac->bTexture = 0;
    }

}

/******************************Public*Routine******************************\
* text3d_Finish
*
* Handles any cleanup on program termination
*
\**************************************************************************/
void
text3d_Finish( void *data )
{
    AttrContext *pac = (AttrContext *) data;

    if( pac )
        DeleteWglFontContext( pac->pWglFontC );

    // delete any name list
    DeleteNameList();
}

/**************************************************************************\
* text3d_Reshape
*
*       - called on resize, expose
*       - always called on app startup
*
\**************************************************************************/

void
text3d_Reshape(int width, int height, void *data )
{
    AttrContext *pac = (AttrContext *) data;

    glViewport( 0, 0, width, height );

    // calculate new aspect ratio
    pac->fAspect = height == 0 ? 1.0f : (FLOAT) width / (FLOAT) height;

    CalcViewParams( pac );
    ss_SetWindowAspectRatio( pac->p3dBoundingBox.x / pac->p3dBoundingBox.y );
}

/**************************************************************************\
* CalcViewParams
*
*   Calculate viewing parameters, based on window size, bounding box, etc.
*
\**************************************************************************/

static void
CalcViewParams( AttrContext *pac )
{
    GLdouble zNear, zFar;
    FLOAT aspectBound, viewDist;
    FLOAT vHeight;
    FLOAT fovy;

    // calculate viewing distance so that front of bounding box within view

    aspectBound = pac->p3dBoundingBox.x / pac->p3dBoundingBox.y;

    // this is distance to FRONT of bounding box:
    viewDist = pac->p3dBoundingBox.y /
               ( (FLOAT) tan( deg_to_rad(pac->fFovy/2.0f) ) );

    // NOTE: these are half-widths and heights
    if( aspectBound <= pac->fAspect ) {
        // we are bound by the window's height
        fovy = pac->fFovy;
    } else {
        // we are bound by window's width
        // adjust fovy, so fovx remains the same
        vHeight = pac->p3dBoundingBox.x / pac->fAspect;
        fovy = rad_to_deg( 2.0f * (FLOAT) atan( vHeight / viewDist ) );
    }

    /* Could just use rotation sphere dimensions here, but for now
     * set clipping planes 10% beyond bounding box.
     */

    zNear = 0.9f * viewDist;
    zFar = 1.1f * (viewDist + 2.0f*pac->p3dBoundingBox.z);

    if( pac->demoType == DEMO_VSTRING )
        zFar *= FMAX_ZOOM;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective( fovy, pac->fAspect, zNear, zFar );

    // set viewing distance so that front of bounding box within view
    viewDist *= 1.01f; // pull back 1% further to be sure not off by a pixel..
    pac->fZtrans = -(viewDist + pac->p3dBoundingBox.z);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef( 0.0f, 0.0f, pac->fZtrans );

}

// number of calibration cycles
#define MAX_CALIBRATE 2

/**************************************************************************\
* text3d_Draw
*
* Draw a frame.
*
\**************************************************************************/
void
text3d_Draw( void *data )
{
    AttrContext *pac = (AttrContext *) data;
    POINT3D *rot = &pac->p3dRot;
    static BOOL bCalibrated = FALSE;
    static int nCycle = 0; // cycle count
    static int frameCount = 0, maxCount;
    static int matTransCount, matTransCount2 = 0;
    static MATERIAL transMat, transMatInc;
    static FLOAT zTrans, zTransInc;
    static MATERIAL *pNewMat;
    static struct _timeb baseTime;
    static BOOL bInit = FALSE;
    static int reset[NUM_AXIS] = {1,1,1};

    if( !bInit ) {

        // Do first time init stuff

        // Start the calibration timer
        _ftime( &baseTime );

        // set default transition points, until calibration done
        maxCount = 60;
        SetTransitionPoints( pac, maxCount, &matTransCount,
                             &matTransCount2, &zTrans );
        bInit = TRUE;
    }

    // take action based on frameCount

    if( frameCount >= matTransCount ) {

        // we are in the transition zone

        if( frameCount == matTransCount ) {

            // first transition point...

            // select new material
            if( pac->bTexture )
                pNewMat = ss_RandomTexMaterial( FALSE );
            else
                pNewMat = ss_RandomTeaMaterial( FALSE );

            // set material transition, zTrans transition
            if( pac->demoType == DEMO_VSTRING ) {
                // transition current material to black
                ss_CreateMaterialGradient( &transMatInc,
                                          pac->pMat,
                                          &ss_BlackMat,
                                          matTransCount2 - matTransCount );
                zTransInc = ((FMAX_ZOOM-1) * pac->fZtrans) /
                                        (matTransCount2 - matTransCount);
            } else {
                ss_CreateMaterialGradient( &transMatInc,
                                           pac->pMat,
                                           pNewMat,
                                           maxCount - matTransCount );
            }
            // initialize transition values to current settings
            zTrans = pac->fZtrans;
            transMat = *(pac->pMat);
            // begin transition on NEXT frame.

        } else {

            // past first transition...

            if( matTransCount2 && (frameCount == (matTransCount2+1)) ) {

                // optional second transition point...(only for vstrings)

                // transition from black to new material
                ss_CreateMaterialGradient( &transMatInc,
                                           &ss_BlackMat,
                                           pNewMat,
                                           maxCount - matTransCount2 );
                // init transition material to black
                transMat = ss_BlackMat;

                /* At this point, screen is black, so we can change strings
                 * and resize the floater without any problems.
                 */

                if( pac->demoType == DEMO_VSTRING )
                    text3d_UpdateString( pac, TRUE ); // can cause resize
                // set zTrans to furthest distance
                zTrans = (FMAX_ZOOM * pac->fZtrans);
                zTransInc = (pac->fZtrans - zTrans) /
                                            (maxCount - matTransCount2);
                // change this while string invisible
                ResetRotationLimits( pac, reset );
            }
            // set the transition material (updates transMat each time)

            ss_TransitionMaterial( &transMat, &transMatInc );
            zTrans += zTransInc;

            if( frameCount >= maxCount ) {

                // End of cycle
                nCycle++;  // 1-based

                // Calibrate on MAX_CALIBRATE cycles
                if( !bCalibrated && (nCycle >= MAX_CALIBRATE) ) {
                    maxCount = FrameCalibration( pac, &baseTime, maxCount, nCycle );

                    SetTransitionPoints( pac, maxCount, &matTransCount,
                                         &matTransCount2, &zTrans );
                    bCalibrated = TRUE;
                }

                // set, reset stuff
                pac->pMat = pNewMat;
                ss_SetMaterial( pNewMat );
                zTrans = pac->fZtrans;
                frameCount = 0;
            }
        }
    }

    if( pac->demoType == DEMO_CLOCK ) {
        // have to update the draw string with current time
        text3d_UpdateTime( pac, TRUE );
    }

    // ok, the string's setup - draw it

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    if( pac->demoType == DEMO_VSTRING )
        // use zooming zTrans
        glTranslatef( 0.0f, 0.0f, zTrans );
    else
        // use fixed zTrans
        glTranslatef( 0.0f, 0.0f, pac->fZtrans );

    /*
     * GetNextRotProc provides sinusoidal rotations
     */
    (*GetNextRotProc)( pac );  // sets pac->p3dRot, or rot

    if( pac->p3dRotMax.z != 0.0f ) {
        glRotatef( rot->z, 0.0f, 0.0f, 1.0f );
    }
    if( pac->p3dRotMax.y != 0.0f ) {
        glRotatef( rot->y, 0.0f, 1.0f, 0.0f );
    }
    if( pac->p3dRotMax.x != 0.0f ) {
        glRotatef( rot->x, 1.0f, 0.0f, 0.0f );
    }

    glTranslatef( -pac->pfTextOrigin.x - pac->pfTextExtent.x/2.0f,
                  -pac->pfTextOrigin.y + pac->pfTextExtent.y/2.0f,
                  pac->fDepth / 2.0f );

    DrawString( pac->usText, pac->textLen, pac->pWglFontC );

    glFlush();
    frameCount++;
}

/**************************************************************************\
* SetTransitionPoints
*
* Calculate draw transition points, as frame count values.
*
* If doing variable string (VSTRING), first transition point is where we
* start fading to black, and 2nd is from black to next material.  Also
* transition the z translation distance (zTrans).
* Note that trans2 indicates the frame number where the image should be
* black.  The actual transitioning may occur at trans2+1 (see text3d_draw
* above).
*
* For all other cases, set one transition point for fade to next material.

*
\**************************************************************************/
static void
SetTransitionPoints( AttrContext *pac, int framesPerCycle, int *trans1,
                     int *trans2, FLOAT *zTrans )
{
    *trans1 = (int) (0.5f * (FLOAT) framesPerCycle + 0.5f);

    if( pac->demoType == DEMO_VSTRING ) {
        *trans2 = *trans1 +
                  (int) (0.5f * (FLOAT) (framesPerCycle - *trans1) + 0.5f);
        *zTrans = pac->fZtrans;
    }
}

/**************************************************************************\
* text3d_UpdateTime
*
* Put new time string into the attribute context
*
\**************************************************************************/
static void
text3d_UpdateTime( AttrContext *pac, BOOL bCheckBounds )
{
    int oldLen;
    POINTFLOAT textExtent, textOrigin;
    POINTFLOAT textLowerRight, currentLowerRight;
    LPTSTR pszLastTime = pac->szText;
    static TCHAR szNewTime[TEXT_BUF_SIZE] = {0};

    GetLocalTime( &(pac->stTime) );
    GetTimeFormat( GetSystemDefaultLCID(), // locale id
                    0,                      // flags
                    &(pac->stTime),         // time struct
                    NULL,                   // format string
                    szNewTime,               // buffer
                    TEXT_BUF_SIZE );        // buffer size

    // Compare new time string with last one

    if( !lstrcmp( pszLastTime, szNewTime ) )
        // time string has not changed, return
        return;

    // translate the new time string into display lists in pac->usText

    ConvertStringToList( szNewTime, pac->usText, pac->pWglFontC );
    lstrcpy( pac->szText, szNewTime );

    // Check extents of new string

    // save current values
    oldLen = pac->textLen;
    textExtent = pac->pfTextExtent;
    textOrigin = pac->pfTextOrigin;

    pac->textLen = GetStringExtent( pac->szText,
                                     &textExtent,
                                     &textOrigin,
                                     pac->pWglFontC );

    if( !bCheckBounds ) {
        // just set new extents and return
        pac->pfTextExtent = textExtent;
        pac->pfTextOrigin = textOrigin;
        return;
    }

    /* only update bounding box if new extents are larger, or the number
     * of chars changes
     */
    bCheckBounds = FALSE;

    if( pac->textLen != oldLen ) {

        // recalculate everything

        bCheckBounds = TRUE;
        pac->pfTextExtent = textExtent;
        pac->pfTextOrigin = textOrigin;
    }
    else {

        // accumulate maximum bounding box in pac

        // calc current lower right limits
        textLowerRight.x = textOrigin.x + textExtent.x;
        textLowerRight.y = textOrigin.y - textExtent.y;
        currentLowerRight.x = pac->pfTextOrigin.x + pac->pfTextExtent.x;
        currentLowerRight.y = pac->pfTextOrigin.y - pac->pfTextExtent.y;

        // if new text extents extend beyond current, update

        if( textOrigin.x < pac->pfTextOrigin.x ) {
            pac->pfTextOrigin.x = textOrigin.x;
            bCheckBounds = TRUE;
        }
        if( textOrigin.y > pac->pfTextOrigin.y ) {
            pac->pfTextOrigin.y = textOrigin.y;
            bCheckBounds = TRUE;
        }
        if( textLowerRight.x > currentLowerRight.x ) {
            pac->pfTextExtent.x = textLowerRight.x - pac->pfTextOrigin.x;
            bCheckBounds = TRUE;
        }
        if( textLowerRight.y < currentLowerRight.y ) {
            pac->pfTextExtent.y = pac->pfTextOrigin.y - textLowerRight.y;
            bCheckBounds = TRUE;
        }
    }
    if( bCheckBounds ) {
        // string size has changed - recalc box and view params
        (*BoundingBoxProc)( pac );
        CalcViewParams( pac );
    }
}

/**************************************************************************\
* text3d_UpdateString
*
* Select new string to display.
* If bCheckBounds, calculate new bounds as well.
*
\**************************************************************************/
static void
text3d_UpdateString( AttrContext *pac, BOOL bCheckBounds )
{
    static int index = 0;

    // get next string to display

    if( gplist == NULL )
        CreateRandomList();

    lstrcpy( pac->szText, gplist->pszStr );
    ConvertStringToList( pac->szText, pac->usText, pac->pWglFontC );
    gplist = gplist->pnext;

    // get new extents
    pac->textLen = GetStringExtent( pac->szText,
                                     &pac->pfTextExtent,
                                     &pac->pfTextOrigin,
                                     pac->pWglFontC );

    if( !bCheckBounds )
        return;

    // calculate bounding box
    (*BoundingBoxProc)( pac );
    if( pac->p3dBoundingBox.y == 0.0f )
        // avoid /0
        pac->p3dBoundingBox.y = 1.0f;

    // Make window's aspect ratio dependent on bounding box

    /* mf: could clear buffer here, so don't get incorrect results on
     *  synchronous resize, but not necessary since we're fading
     */
    ss_SetWindowAspectRatio( pac->p3dBoundingBox.x / pac->p3dBoundingBox.y );
    CalcViewParams( pac );

    // move window to new random position
    ss_RandomWindowPos();
}

/**************************************************************************\
* GetNextRotWobble
*
*   Calculate next rotation.
*   - 'step' controls amount of rotation
*   - rotation values are scaled from -1 to 1 (trig values), and  inscribe
*     circle with r=1 in the zy plane for the ends of the string
*   - steps for both minor and major rotation axis remain in sync
*
\**************************************************************************/
static void
GetNextRotWobble( AttrContext *pac )
{
    int *step = (int *) &pac->ip3dRoti;  // use step->x
    int *rotStep = (int *) &pac->ip3dRotStep.z;
    FLOAT *rotMax = (FLOAT *) &pac->p3dRotMax;
    POINTFLOAT *pTrig = pac->pTrig;
    static int resetPoint[NUM_AXIS] = {90,90,0}; // 0 amplitude points
    int reset[NUM_AXIS] = {0}; // which axis to be reset
    int axis;

    pac->p3dRot.z = pac->p3dRotLimit.z * pTrig[ *step ].y;  // sin
    pac->p3dRot.y = pac->p3dRotLimit.y * pTrig[ *step ].x;  // cos
    pac->p3dRot.x = pac->p3dRotLimit.x * pTrig[ *step ].x;  // cos

    // check for 0 amplitude point for non-vstrings
    for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) {
        if( rotMax[axis] != 0.0f ) {
            if( (pac->demoType != DEMO_VSTRING) &&
                (*step == resetPoint[axis]) )
            {
                reset[axis] = 1;
                ResetRotationLimits( pac, reset );
                reset[axis] = 0;
            }
        }
    }

    // increment step
    if( (*step += *rotStep) >= 360 ) {
        // make the step variable
        *rotStep = ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep );
        // start step at variable index
        *step = ss_iRand( *rotStep );
    }
}

/**************************************************************************\
* GetNextRotRandom
*
* Same as above, but steps for each axis are not kept in sync
*
\**************************************************************************/
static void
GetNextRotRandom( AttrContext *pac )
{
    int *step = (int *) &pac->ip3dRoti;
    int *rotStep = (int *) &pac->ip3dRotStep;
    FLOAT *rotMax = (FLOAT *) &pac->p3dRotMax;
    POINTFLOAT *pTrig = pac->pTrig;
    static int resetPoint[NUM_AXIS] = {90,90,0}; // 0 amplitude points
    int reset[NUM_AXIS] = {0}; // which axis to be reset
    int axis;

    // set new rotation
    pac->p3dRot.z = pac->p3dRotLimit.z * pTrig[ step[Z_AXIS] ].y;  // sin
    pac->p3dRot.y = pac->p3dRotLimit.y * pTrig[ step[Y_AXIS] ].x;  // cos
    pac->p3dRot.x = pac->p3dRotLimit.x * pTrig[ step[X_AXIS] ].x;  // cos

    // for each rotation axis...

    for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) {
        if( rotMax[axis] != 0.0f ) {
            // check for 0 amplitude point for non-vstrings
            if( (pac->demoType != DEMO_VSTRING) &&
                (step[axis] == resetPoint[axis]) ) {
                reset[axis] = 1;
                ResetRotationLimits( pac, reset );
                reset[axis] = 0;
            }

            // increment rotation step and check for end of cycle
            if( (step[axis] += rotStep[axis]) >= 360 ) {
                // make the step variable
                rotStep[axis] = ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep );
                // start step at variable index
                step[axis] = ss_iRand( rotStep[axis] );
            }
        }
    }
}

/**************************************************************************\
* GetNextRotNone
*
* Null rot proc
*
\**************************************************************************/
static void
GetNextRotNone( AttrContext *pac )
{
}

/**************************************************************************\
* ResetRotationLimits
*
* Reset the maximum axis rotations.  So there won't be too much of a 'jump'
* when altering the rotation, this routine should only be called when the
* rotation of the specified axis is at zero amplitude.
*
* Also, change the rotation table if applicable.  This affects how the
* rotation 'steps' around the axis.
*
\**************************************************************************/

static void
ResetRotationLimits( AttrContext *pac, int *reset )
{
    FLOAT *p3dRot    =  (FLOAT *) &pac->p3dRot;       // current rotation
    FLOAT *p3dRotL   =  (FLOAT *) &pac->p3dRotLimit;  // new rot limit
    FLOAT *p3dRotMin =  (FLOAT *) &pac->p3dRotMin;    // max rotation
    FLOAT *p3dRotMax =  (FLOAT *) &pac->p3dRotMax;    // max rotation
    POINT3D p3dOldRotL = pac->p3dRotLimit; // save last rot limit
    POINTFLOAT *oldTrig;
    int i;

    // change rotation limits

    for( i = 0; i < NUM_AXIS; i++ ) {
        if( p3dRotMax[i] && reset[i] ) {
            p3dRotL[i] = ss_fRand( p3dRotMin[i], p3dRotMax[i] );
            // grossly modify amplitute sometimes for random
            if( pac->rotStyle == ROTSTYLE_RANDOM ) {
                if( ss_iRand(10) == 2 )
                    p3dRotL[i] = ss_fRand( 0.0f, 10.0f );
                else if( ss_iRand(10) == 2 )
                    p3dRotL[i] = ss_fRand( 90.0f, 135.0f );
            }
        }
    }

    // change rotation table

    // use i to set a frequency for choosing gInvTrig table
    i = 10;

    oldTrig = pac->pTrig;

    switch( pac->rotStyle ) {
        case ROTSTYLE_RANDOM:
            if( pac->demoType == DEMO_VSTRING )
                i = 7;
            // fall thru...
        case ROTSTYLE_SEESAW:
            // Use InvTrig table every now and then
            if( ss_iRand(i) == 2 ) {
                pac->pTrig = gInvTrig;
            }
            else
                pac->pTrig = gTrig;
            break;
        default:
            // Always use regular trig table
            pac->pTrig = gTrig;
    }

    // if trig table changed, need to adjust steps of non-zero axis rotations
    // (otherwise get 'twitch' in rotation)

    if( pac->pTrig != oldTrig ) {
        // only deal with axis which didn't have amplitudes modified
        for( i = 0; i < NUM_AXIS; i++ )
            reset[i] = ! reset[i];
        AdjustRotationStep( pac, reset, oldTrig );
    }
}

/**************************************************************************\
* AdjustRotationStep
*
* If trig table is changed in ResetRotationLimits, then axis with non-zero
* rotations will appear to jump.  This routine modifies the current step
* so this will not be apparent.
*
\**************************************************************************/

static void
AdjustRotationStep( AttrContext *pac, int *reset, POINTFLOAT *oldTrig )
{
    int *step = (int *) &pac->ip3dRoti;
    FLOAT *p3dRotMax =  (FLOAT *) &pac->p3dRotMax;
    int axis;
    POINT *trigDif;

    if( pac->demoType == DEMO_VSTRING )
        // for now doesn't matter, string is invisible at this point
        return;

    // choose diff table to use for modifying step
    trigDif = (oldTrig == gTrig) ? gTrigDif : gInvTrigDif;

    for( axis = 0; axis < NUM_AXIS; axis++ ) {
        if( p3dRotMax[axis] && reset[axis] ) {
            if( axis != Z_AXIS )
                step[axis] += trigDif[ step[axis] ].x;
            else
                step[axis] += trigDif[ step[axis] ].y;

            // check for wrap or out of bounds
            if( (step[axis] >= 360) || (step[axis] < 0) )
                step[axis] = 0;
        }
    }
}

/**************************************************************************\
* FindInvStep
*
* Finds step in invTrig table with same value as trig table at i
*
\**************************************************************************/

static int
FindInvStep( int i )
{
    FLOAT val, diff, minDiff;
    int invStep = i;

    val = gTrig[i].y;

    invStep = i;
    minDiff = val - gInvTrig[i].y;

    while( ++i <= 90 ) {
        diff = val - gInvTrig[i].y;
        if( (FLOAT) fabs(diff) < minDiff ) {
            minDiff = (FLOAT) fabs(diff);
            invStep = i;
        }
        if( diff < 0.0f )
            break;
    }

    return invStep;
}

/**************************************************************************\
* FindStep
*
* Finds step in trig table with same value as invTrig table at i
*
\**************************************************************************/

static int
FindStep( int i )
{
    FLOAT val, diff, minDiff;
    int step = i;

    val = gInvTrig[i].y;

    step = i;
    minDiff = gTrig[i].y - val;

    while( --i >= 0 ) {
        diff = val - gTrig[i].y;
        if( (FLOAT) fabs(diff) < minDiff ) {
            minDiff = (FLOAT) fabs(diff);
            step = i;
        }
        if( diff > 0.0f )
            break;
    }

    return step;
}

/**************************************************************************\
* InitTrigTable
*
* Initialize trig look-up tables
*
\**************************************************************************/
static void
InitTrigTable()
{
    int i;
    static int num = 360;
    FLOAT inc = (2.0f*PI)/((FLOAT)num);  // 360 degree range
    FLOAT angle = 0.0f;
    int newStep;

    // calc standard trig table

    for( i = 0; i < num; i ++ ) {
        gTrig[i].x = (FLOAT) cos(angle);
        gTrig[i].y = (FLOAT) sin(angle);
        angle += inc;
    }

    // Calc sawtooth and pseudo-inverse trig table, as well as a diff
    // table to convert between trig and invTrig.

    // do y, or sin values first

    for( i = 0; i <= 90; i ++ ) {
        gSawTooth[i].y = (int) i / 90.0f;
        gInvTrig[i].y = 2*gSawTooth[i].y - gTrig[i].y;
    }

    // Create tables to convert trig steps to invTrig steps, and vice-versa
    for( i = 0; i <= 90; i ++ ) {
        newStep = FindInvStep( i );
        gTrigDif[i].y = newStep - i;
        newStep = FindStep( i );
        gInvTrigDif[i].y = newStep - i; // -
    }

    // reflect 0-90 to get 90-180
    for( i = 1; i <= 90; i ++ ) {
        gSawTooth[90+i].y = gSawTooth[90-i].y;
        gInvTrig[90+i].y = gInvTrig[90-i].y;
        gTrigDif[90+i].y = -gTrigDif[90-i].y;
        gInvTrigDif[90+i].y = -gInvTrigDif[90-i].y;
    }
    // invert 0-180 to get 180-360
    for( i = 1; i < 180; i ++ ) {
        gSawTooth[180+i].y = -gSawTooth[i].y;
        gInvTrig[180+i].y = -gInvTrig[i].y;
        gTrigDif[180+i].y = gTrigDif[i].y;
        gInvTrigDif[180+i].y = gInvTrigDif[i].y;
    }

    // calc x, or cos, by phase-shifting y

    for( i = 0; i < 270; i ++ ) {
        gSawTooth[i].x = gSawTooth[i+90].y;
        gInvTrig[i].x = gInvTrig[i+90].y;
        gTrigDif[i].x = gTrigDif[i+90].y;
        gInvTrigDif[i].x = gInvTrigDif[i+90].y;
    }
    for( i = 0; i < 90; i ++ ) {
        gSawTooth[i+270].x = gSawTooth[i].y;
        gInvTrig[i+270].x = gInvTrig[i].y;
        gTrigDif[i+270].x = gTrigDif[i].y;
        gInvTrigDif[i+270].x = gInvTrigDif[i].y;
    }
}

/**************************************************************************\
* CalcChordalDeviation
*
*
\**************************************************************************/
static FLOAT
CalcChordalDeviation( HDC hdc, AttrContext *pac )
{
    OUTLINETEXTMETRIC otm;
    FLOAT       cd, mincd;  // chordal deviations

    // Query font metrics

    if( GetOutlineTextMetrics( hdc, sizeof(otm), &otm) <= 0 )
        // cmd failed, or buffer size=0
        return 1.0f;

    // minimum chordal deviation is limited by design space
    mincd = 1.0f / (FLOAT) otm.otmEMSquare;

    // now map fTesselFact to chordalDeviation
    cd = MapValue( pac->fTesselFact,
                   0.0f, 1.0f,  // fTesselFact range
                   FMAX_CHORDAL_DEVIATION, mincd );  // chordalDeviation range
    if( pac->fTesselFact == 0.0f )
        // make sure get lowest resolution
        cd = 1.0f;
    return cd;
}

/**************************************************************************\
* CalcBoundingBox
*
\**************************************************************************/
static void
CalcBoundingBox( AttrContext *pac )
{
    POINT3D box[3];  // for each axis rotation
    FLOAT viewAngle, critAngle, critAngleC, rectAngle;
    FLOAT r, rot, x, y, z, ymax, zCrit;
    FLOAT viewDist, viewDistO, xAngle[3];
    FLOAT boxvpo[3];  // viewpoint to origin distance along z for the boxes
    int n = 0;
    POINT3D pt;

    /* One thing to remember here is that box[n].z is constrained to be
     * the near clipping plane.  The boxe's x and y represent the frustum
     * cross-section at that point.
     */
    viewAngle = deg_to_rad( pac->fFovy )  / 2.0f;

    // x,y,z represent half-extents
    x = pac->pfTextExtent.x/2.0f;
    y = pac->pfTextExtent.y/2.0f;
    z = pac->fDepth/2.0f;
    // initialize box[0] with current extents
    box[0].x = x;
    box[0].y = y;
    box[0].z = z;
    boxvpo[0] = 0.0f;

    // handle rotation around x-axis

    if( pac->p3dRotMax.x != 0.0f ) {

        box[n].x = x;

        // need to determine y and z

        rot = deg_to_rad( pac->p3dRotMax.x );
        r = (FLOAT) sqrt( y*y + z*z );

        // calc incursion along z

        rectAngle = (z == 0.0f) ? PI_OVER_2 : (FLOAT) atan( y/z );
        if( rot >= rectAngle ) {
            // easy, use maximum possible extent
            box[n].z = r;
        } else {
            // rotate lower right corner of box by rot to get extent
            box[n].z = z * (FLOAT) cos( rot ) + y * (FLOAT) sin( rot );
        }

        /* figure out critical angle, where rotated rectangle would
         * be perpendicular to viewing frustum.  This indicates the max.
         * y-incursion into the frustum.
         */
        critAngle = PI_OVER_2 - viewAngle;
        ymax = r * (FLOAT) sin(critAngle);

        if( y > z ) {
            rectAngle = PI_OVER_2 - rectAngle;
            critAngleC = PI_OVER_2 - critAngle;
        } else
            critAngleC = critAngle;

        if( (rectAngle + rot) >= critAngleC ) {
            // no view reduction possible in y, use max view
            // need to calc y at box.z
            viewDistO = r / (FLOAT) cos( critAngle );
            boxvpo[n] = viewDistO;
            box[n].y = (viewDistO - box[n].z) *
                                        (FLOAT) tan( PI_OVER_2 - critAngle);
        } else {
            // we can sonic reduce it
            if( y > z )
                rot = -rot;
            // rotate front-top point by rot to get ymax, z
            ymax = z * (FLOAT) sin( rot ) + y * (FLOAT) cos( rot );
            zCrit = z * (FLOAT) cos( rot ) - y * (FLOAT) sin( rot );
            // not usin viewDistO properly here...
            viewDistO = ymax * (FLOAT) tan( viewAngle);
            boxvpo[n] = viewDistO + zCrit;
            viewDist = boxvpo[n] - box[n].z;
            box[n].y = viewDist * (FLOAT) tan( viewAngle );
        }
        n++;
    }

    if( pac->p3dRotMax.y != 0.0f ) {

        box[n].y = y;

        // need to determine x and z
        rot = deg_to_rad( pac->p3dRotMax.y );
        r = (FLOAT) sqrt( x*x + z*z );
        rectAngle = (z == 0.0f) ? PI_OVER_2 : (FLOAT) atan( x/z );

        // calc incursion along z

        if( rot >= rectAngle ) {
            // easy, use maximum possible extent
            box[n].z = r;
        } else {
            // rotate lower right corner of box by rot to get extent
            box[n].z = z * (FLOAT) cos( rot ) + x * (FLOAT) sin( rot );
        }

        // view distance to largest z
        viewDist = y / (FLOAT) tan(viewAngle);
        // make viewDist represent distance to origin
        viewDistO = viewDist + box[n].z;
        boxvpo[n] = viewDistO;

        // now minimize angle between viewpoint and rotated rect

        if( viewDistO > r ) {
            /* calc crit angle where view is maximized (line from viewpoint
             * tangent to rotation circle)
             * critAngle is between z-axis and radial line
             */
            critAngle = (FLOAT) acos( r / viewDistO );

            // critAngleC is for Comparing
            if( x > z ) {
                rectAngle = PI_OVER_2 - rectAngle;
                critAngleC = PI_OVER_2 - critAngle;
            } else
                critAngleC = critAngle;
        }

        if( (viewDistO > r) && // vp OUTSIDE circle
            ((rectAngle + rot) >= critAngleC) ) {

            /* no view reduction possible in x, use x along the max-view line
             */
            box[n].x = viewDist * (FLOAT) tan( PI_OVER_2 - critAngle );
        } else {
            // we can sonic reduce it
            if( x > z )
                rot = -rot;
            // rotate front-top point by rot to get x,z
            // pt.z not needed
            //pt.z = z * (FLOAT) cos( rot ) - x * (FLOAT) sin( rot );
            pt.x = z * (FLOAT) sin( rot ) + x * (FLOAT) cos( rot );
            box[n].x = pt.x;
        }
        n++;
    }

    if( pac->p3dRotMax.z != 0.0f ) {

        CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.z),
                            x, y,
                            (POINTFLOAT *) &box[n] );
        box[n].z = z;
        // calc viewing distance from front of box
        viewDist = box[n].y / (FLOAT) tan(viewAngle);
        // calc view distance to origin;
        boxvpo[n] = box[n].z + viewDist;
        n++;
    }

    /* Now we've got 3 rectangles in x-y plane at various depths, and
     * need to pick the shortest viewing distance that will encompass
     * all of them.  Or, don't actually have to pick the view distance
     * yet, since might want to wait for the viewport size in Reshape before
     * we do this - but in that case need to pick the rectangle that 'sticks
     * out the most', so it can be used as the bounding box.
     */
    /* The box with the furthest viewpoint will work for y.
     * But then have to check
     * this against the x's of the others.  If any stick out of the frustum,
     * then it will have to be made larger in x.  By making x larger, we
     * do not affect fovy
     */
    SortBoxes( box, boxvpo, n ); // put largest viewpoint box in box[0]

    // figure view dist to first box
    // (could maintain these as they are calculated)
    viewDist = boxvpo[0] - box[0].z;

    // compare x angles of boxes

    switch( n ) {
        FLOAT den;

        case 3:
            den = viewDist + (box[0].z - box[2].z);
            xAngle[2] = den == 0.0f ? PI_OVER_2 : (FLOAT)atan( box[2].x / den);
        case 2:
            den = viewDist + (box[0].z - box[1].z);
            xAngle[1] = den == 0.0f ? PI_OVER_2 : (FLOAT)atan( box[1].x / den);
        case 1:
            xAngle[0] = viewDist == 0.0f ? PI_OVER_2 :
                                            (FLOAT)atan( box[0].x / viewDist );
    }

    // here, just call Sort again, with list of xAngles
    SortBoxes( box, xAngle, n ); // put largest xangle box in box[0]

    // now box[0] should contain half extents of the Bounding box

    pac->p3dBoundingBox = box[0];
}

/**************************************************************************\
* CalcBoundingBoxFromSphere
*
* Calculates the bounding box from a sphere with r = diagonal of the box
*
\**************************************************************************/
static void
CalcBoundingBoxFromSphere( AttrContext *pac )
{
    FLOAT x, y, z, r;
    FLOAT viewAngle, viewDist, viewDistO;
    POINT3D box;

    // x,y,z represent half-extents
    x = pac->pfTextExtent.x/2.0f;
    y = pac->pfTextExtent.y/2.0f;
    z = pac->fDepth/2.0f;

    r = (FLOAT) sqrt( x*x + y*y +z*z );
    box.z = r;
    viewAngle = deg_to_rad( pac->fFovy )  / 2.0f;
    viewDistO = r / (FLOAT) sin( viewAngle );
    viewDist = viewDistO - r;
    box.y = viewDist * (FLOAT) tan( viewAngle );
    box.x = box.y;

    pac->p3dBoundingBox = box;
}

/**************************************************************************\
*
* CalcBoundingBoxFromSpherePlus
*
* Same as above, but tries to optimize for case when z exent is small
*
\**************************************************************************/
static void
CalcBoundingBoxFromSpherePlus( AttrContext *pac, FLOAT zmax )
{
    FLOAT x, y, z, r;
    FLOAT viewAngle, viewDist, viewDistO;
    POINT3D box;

    // x,y,z represent half-extents
    x = pac->pfTextExtent.x/2.0f;
    y = pac->pfTextExtent.y/2.0f;
    z = pac->fDepth/2.0f;

    r = (FLOAT) sqrt( x*x + y*y +z*z );
    viewAngle = deg_to_rad( pac->fFovy )  / 2.0f;

    if( zmax < r ) {
        // we can get closer !
        box.z = zmax;
        viewDistO = r / (FLOAT) sin( viewAngle );
        viewDist = viewDistO - zmax;

        // we want to move the clipping plane closer by (r-zmax)
        if( (r-zmax) > viewDist ) {
#ifdef SS_DEBUG
            glClearColor( 1.0f, 0.0f, 0.0f, 0.0f );
#endif
            // we are moving the vp inside the sphere
            box.y = (FLOAT) sqrt( r*r - box.z*box.z );
            box.x = box.y;
        } else {
            FLOAT zt; // z-point where view frustum tangent to sphere

            // vp outside sphere: can only optimize if zmax < ztangent
            zt = r * (FLOAT) cos( PI_OVER_2 - viewAngle);
            if( zmax < zt ) {
#ifdef SS_DEBUG
                // GREEN ZONE !!!
                glClearColor( 0.0f, 1.0f, 0.0f, 0.0f );
#endif
                box.y = (FLOAT) sqrt( r*r - zmax*zmax );
            } else
                // this is the same as below, but with better clipping
                box.y = (viewDist + (r-zmax)) * (FLOAT) tan( viewAngle );
            box.x = box.y;
        }
    } else {
        box.z = r;
        viewDistO = r / (FLOAT) sin( viewAngle );
        viewDist = viewDistO - r;
        box.y = viewDist * (FLOAT) tan( viewAngle );
        box.x = box.y;
    }
    pac->p3dBoundingBox = box;
}

/**************************************************************************\
* CalcBoundingBoxFromExtents
*
* Calculate bounding box for text, assuming text centered at origin, and
* using maximum possible spin angles.
*
* Rotation around any one axis will affect bounding areas in the other
* 2 directions (e.g. z-rotation affects x and y bounding values).
*
* We need to find the maxima of the rotated 2d area, while staying within
* the max spin angles.
*
\**************************************************************************/
static void
CalcBoundingBoxFromExtents( AttrContext *pac, POINT3D *box )
{
    POINTFLOAT extent;

    box->x = pac->pfTextExtent.x / 2.0f;
    box->y = pac->pfTextExtent.y / 2.0f;
    box->z = pac->fDepth / 2.0f;

    // split the 3d problem into 3 2d problems in 'x-y' plane

    if( pac->p3dRotMax.x != 0.0f ) {

        CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.x),
                            box->z, box->y, &extent );
        box->z = max( box->z, extent.x );
        box->y = max( box->y, extent.y );
    }

    if( pac->p3dRotMax.y != 0.0f ) {

        CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.y),
                            box->x, box->z, &extent );
        box->x = max( box->x, extent.x );
        box->z = max( box->z, extent.y );
    }

    if( pac->p3dRotMax.z != 0.0f ) {

        CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.z),
                            box->x, box->y, &extent );
        box->x = max( box->x, extent.x );
        box->y = max( box->y, extent.y );
    }
}

/**************************************************************************\

* CalcBoundingBoxGeneric
*
* Combines the bounding sphere with the bounding extents
* Each of these alone will guarantee no clipping.  But we can
* optimize by combining them.
*
\**************************************************************************/
static void
CalcBoundingBoxGeneric( AttrContext *pac )
{
    POINT3D extentBox;
    FLOAT x, y, z, r, d, zt, fovx;
    FLOAT viewAngle, viewDist, viewDistO;
    BOOL xIn, yIn;

    // x,y,z represent half-extents
    x = pac->pfTextExtent.x/2.0f;
    y = pac->pfTextExtent.y/2.0f;
    z = pac->fDepth/2.0f;

    // get the max extent box

    /*!!! wait, this alone doesn't guarantee no clipping?  It only
     * checks each axis-rotation separately, without combining them. This
     * is no better than calling old CalcBoundingBox ... ??  Well, I
     * can't prove why theoretically, but it works
     */
    CalcBoundingBoxFromExtents( pac, &extentBox );

    // determine whether x and y extents inside/outside bounding sphere

    r = (FLOAT) sqrt( x*x + y*y +z*z );
    // check y
    d = (FLOAT) sqrt( extentBox.y*extentBox.y + extentBox.z*extentBox.z );
    yIn = d <= r ? TRUE : FALSE;
    // check x
    d = (FLOAT) sqrt( extentBox.x*extentBox.x + extentBox.z*extentBox.z );
    xIn = d <= r ? TRUE : FALSE;

    // handle easy cases

    if( yIn && xIn ) {
        pac->p3dBoundingBox = extentBox;
        return;
    }
    if( !yIn && !xIn ) {
        CalcBoundingBoxFromSpherePlus( pac, extentBox.z );
        return;
    }

    // harder cases

    viewAngle = deg_to_rad( pac->fFovy )  / 2.0f;

    if( yIn ) {
        // figure out x
        viewDist = extentBox.y / (FLOAT) tan(viewAngle);
        /* viewDist can be inside or outside of the sphere
         * If inside - no optimization possible
         * If outside, can draw line from viewpoint tangent to sphere,
         * and use this point for x
         */
        viewDistO = extentBox.z + viewDist;
        if( viewDistO <= r ) {
            // vp inside sphere
            // set x to the point where z intersects sphere
            // this becomes a Pythagorous theorem problem:
            extentBox.x = (FLOAT) sqrt( r*r - extentBox.z*extentBox.z );
        } else {
            // vp outside sphere
            /* - figure out zt, where line tangent to circle for viewAngle
             */
            fovx = (FLOAT) asin( r / viewDistO );
            zt = r * (FLOAT) acos( PI_OVER_2 - viewAngle);
            if( extentBox.z < zt ) {
                // use x where extentBox.z intersects sphere
                extentBox.x = (FLOAT) sqrt( r*r - extentBox.z*extentBox.z );
            } else {
                // use x at tangent point
                extentBox.x = (FLOAT) sqrt( r*r - zt*zt );
            }
        }
    } else {// y out, x in
        // have to figure out whether vp inside/outside of sphere.
        /* !We can cheat a bit here.  It IS possible, with view angles > 90,
         * that the vp be inside sphere.  But since we always use 90 for
         * this app, it is safe to assume vp > r  (Fix later for general case)
         */
        // XXX: wait, if y out, isn't vp always outside sphere ?
        /* So we solve it this way:
         * - figure out line tangent to circle for viewAngle
         * - y will be where this line intersects the z=extentBox.z line
         */
        viewDistO = r / (FLOAT) sin( viewAngle );
        extentBox.y = (viewDistO - extentBox.z) * (FLOAT) tan( viewAngle );
    }
    pac->p3dBoundingBox = extentBox;
}

/**************************************************************************\
*
* Calculate the extents in x and y from rotating a rectangle in a 2d plane
*
\**************************************************************************/
static void
CalcBoundingExtent( FLOAT rot, FLOAT x, FLOAT y, POINTFLOAT *extent )
{
    FLOAT r, angleCrit;

    r = (FLOAT) sqrt( x*x + y*y );
    angleCrit = (x == 0.0f) ? PI_OVER_2 : (FLOAT) atan( y/x );

    // calc incursion in x

    if( rot >= angleCrit ) {
        // easy, use maximum possible extent
        extent->x = r;
    } else {
        // rotate lower right corner of box by rot to get extent
        extent->x = x * (FLOAT) cos( rot ) + y * (FLOAT) sin( rot );
    }

    // calc incursion in y

    angleCrit = PI/2.0f - angleCrit;

    if( rot >= angleCrit ) {
        // easy, use maximum possible extent
        extent->y = r;
    } else {
        // rotate upper right corner of box by rot to get extent
        extent->y = x * (FLOAT) sin( rot ) + y * (FLOAT) cos( rot );
    }

}

/**************************************************************************\
*
* Sorts in descending order, based on values in val array (bubble sort)
*
\**************************************************************************/
static void
SortBoxes( POINT3D *box, FLOAT *val, int numBox )
{
    int i, j, t;
    POINT3D temp;

    j = numBox;
    while( j ) {
        t = 0;
        for( i = 0; i < j-1; i++ ) {
            if( val[i] < val[i+1] ) {
                // swap'em
                temp = box[i];
                box[i] = box[i+1];
                box[i+1] = temp;
                t = i;
            }
        }
        j = t;
    }
}

#define FILE_BUF_SIZE 180

/**************************************************************************\
* VerifyString
*
* Validate the string
*
* A string can be checked against a list of 'key strings' in an ascii file.
* If there is a match between the user inputted string and one of these key
* strings, then the display mode is enhanced to show a random list of 'item
* strings' that follow the key string in the file.  To point the screen saver
* at such a file, put the filename in the registry key entry "magic", under
* the registry setting group for the text3d screen saver.  The file format is
* as follows:
*
* -key0
* item0
* item1
*  ...
* itemn
* -key1
*  ...
*
* For example:
* -color
* red
* green
* -animal
* dog
* cat
*
\**************************************************************************/
static BOOL
VerifyString( AttrContext *pac )
{
    HMODULE ghmodule;
    HRSRC hr;
    HGLOBAL hg;
    PSZ psz, pszFile = NULL;
    CHAR szSectName[30], szFileName[FILE_BUF_SIZE], szFname[30];
    BOOL bMatch = FALSE;

    // Check for string file in registry
    if (LoadStringA(hMainInstance, IDS_SAVERNAME, szSectName, 30) &&
        LoadStringA(hMainInstance, IDS_INIFILE, szFname, 30))
    {
        if( GetPrivateProfileStringA(szSectName, "magic", NULL,
                                     szFileName, FILE_BUF_SIZE, szFname) )
            pszFile = ReadStringFileA( szFileName );
    }

    // Check for key strings
    if( pszFile )
        bMatch = CheckKeyStrings( pac->szText, pszFile );

    if( !bMatch ) {
        if( (ghmodule = GetModuleHandle(NULL)) &&
            (hr = FindResource(ghmodule, MAKEINTRESOURCE(1),
                                                    MAKEINTRESOURCE(99))) &&
            (hg = LoadResource(ghmodule, hr)) &&
            (psz = (PSZ)LockResource(hg)) )
        bMatch = CheckKeyStrings( pac->szText, psz );
    }

    if( bMatch ) {
        // put first string in pac->szText
        pac->demoType = DEMO_VSTRING;
        // for now, initialize strings here
        text3d_UpdateString( pac, FALSE );

        // adjust cycle time based on rotStyle
        switch( pac->rotStyle ) {
            case ROTSTYLE_NONE:
                gfMinCycleTime = 4.0f;
                break;
            case ROTSTYLE_SEESAW:
                gfMinCycleTime = 8.0f;
                break;
            case ROTSTYLE_RANDOM:
                gfMinCycleTime = 10.0f;
                break;
            default:
                gfMinCycleTime = 9.0f;
        }
    }
    if( pszFile )
        free( pszFile );  // allocated by ReadStringFile
    return bMatch;
}

/**************************************************************************\
* CheckKeyStrings
*
* Test for match between string and any keystrings
* 'string' is user-inputted, and limited to TEXT_LIMIT chars.
*
\**************************************************************************/

static BOOL
CheckKeyStrings( LPTSTR string, PSZ psz )
{
    TCHAR szKey[TEXT_LIMIT+1], testString[TEXT_LIMIT+1] = {0};
    BOOL bMatch = FALSE;
    int nMatch = 0;
    int len;

    // make copy of test string and convert to upper case
    lstrcpy( testString, string );
#ifdef UNICODE
    _wcsupr( testString );
#else
    _strupr( testString );
#endif

    while( psz[0] != '\n' ) { // iterate keyword/data sets
        while( psz[0] != '\n' ) {  // iterate keywords
            len = strlen( psz ); // ! could be > TEXT_LIMIT if from file
            // invert keyword bits and convert to uppercase
#ifdef UNICODE
            // convert ascii keyword to unicode in szKey (inverts at same time)
            ConvertStringAsciiToUnicode( psz, szKey,
                                         len > TEXT_LIMIT ? TEXT_LIMIT : len );
            _wcsupr( szKey );
#else
            // just copy keyword to szKey, without going over TEXT_LIMIT
            strncpy( szKey, psz, TEXT_LIMIT );
            InvertBitsA( szKey, len > TEXT_LIMIT ? TEXT_LIMIT : len );
            szKey[TEXT_LIMIT] = '\0';  // in case len > TEXT_LIMIT
            _strupr( szKey );
#endif

            if( !lstrcmp( szKey, testString ) ) {
                // keyword match !
                bMatch = TRUE;
                nMatch++;
            }
            psz += len + 1;  // skip over NULL as well
        }
        psz++;  // skip over '\n' at end of keywords
        if( bMatch )
            ReadNameList( psz );

        // skip over data to get to next keyword group
        while( *psz != '\n' )
            psz++;
        psz++;  // skip over '\n' at end of data
        bMatch = FALSE; // keep searching for keyword matches
    }
    return nMatch;
}

/**************************************************************************\
* Various functions to process vstrings
*
\**************************************************************************/

static void
InvertBitsA( char *s, int len )
{
    while( len-- ) {
        *s++ = ~(*s);
    }
}

static PSZ
ReadStringFileA( LPSTR szFile )
{
    char lineBuf[180];
    PSZ buf, pBuf;
    int size, length, fdi;
    char *ps;
    char ctrl_n = '\n';
    FILE *fIn;
    BOOL bKey;

    // create buffer to hold entire file
    // mf: ! must be better way of getting file length!
    fdi = _open(szFile, O_RDONLY | O_BINARY);
    if( fdi < 0 )
        return NULL;
    size = _filelength( fdi );
    _close(fdi);
    buf= (char *) malloc( size );
    if( !buf)
        return NULL;

    // open file for ascii text reading
    fIn = fopen( szFile, "r" );
    if( !fIn )
        return NULL;

    // Read in keyword/data sequences

    bKey = TRUE;  // so '\n' not appended to file when hit first keyword
    pBuf = buf;
    while( fgets( lineBuf, 180, fIn) ) {
        ps = lineBuf;
        if( *ps == '-' ) {
            // keyword
            if( !bKey ) {
                // first key in group, append '\n' to data
                *pBuf++ = ctrl_n;
            }
            bKey = TRUE;
            ps++; // skip '-'
        } else {
            // data
            if( bKey ) {
                // first data in group, append '\n' to keywords
                *pBuf++ = ctrl_n;
            }
            bKey = FALSE;
        }
        length = strlen( ps );
        InvertBitsA( ps, length );
        *(ps+length-1) = '\0'; // convert '\n' to null
        lstrcpyA( pBuf, ps );
        pBuf += length;
    }
    fclose( fIn );

    // put 2 '\n' at end, for end condition
    *pBuf++ = ctrl_n;
    *pBuf++ = ctrl_n;
    return( buf );
}

static void
CreateRandomList()
{
   PLIST plist = gplistComplete;
   PLIST *pplist;
   int i = 0;
   int n;

   while (plist != NULL) {
       n = ss_iRand( i+1 );
       pplist = &gplist;

       while (n > 0) {
           pplist = &((*pplist)->pnext);
           n--;
       }

       plist->pnext = *pplist;
       *pplist = plist;

       plist = plist->plistComplete;
       i++;
   }
}

static void
AddName(
    LPTSTR pszStr)
{
    PLIST plist = (PLIST)LocalAlloc(LPTR, sizeof(LIST));
    if( !plist )
        return;
    plist->pszStr = pszStr;
    plist->pnext = NULL;
    plist->plistComplete = gplistComplete;
    gplistComplete = plist;
}

static void
ReadNameList( PSZ psz )
{
    int length;
    LPTSTR pszNew;

    while (psz[0] != '\n') {
        length = 0;
        while (psz[length] != 0) {
            length++;
        }
        length;
        pszNew = (LPTSTR)LocalAlloc( LPTR, (length + 1)*sizeof(TCHAR) );
        if( !pszNew )
            return;
#ifdef UNICODE
        ConvertStringAsciiToUnicode( psz, (PWSTR) pszNew, length );
#else
        strncpy( pszNew, psz, length );
        InvertBitsA( pszNew, length );
#endif
        AddName(pszNew);

        psz += length + 1;
    }
}

static void
DeleteNameList()
{
    PLIST plist = gplistComplete, plistLast;

    while( plist != NULL ) {
        LocalFree( plist->pszStr );
        plistLast = plist;
        plist = plist->plistComplete;
        LocalFree( plistLast );
    }
}

/**************************************************************************\
* ConvertStringAsciiToUnicorn
*
\**************************************************************************/
static void
ConvertStringAsciiToUnicode( PSZ psz, PWSTR pwstr, int len )
{
    while( len-- )
         *pwstr++ = ~(*psz++) & 0xFF;
    *pwstr = 0; // null terminate
}

/**************************************************************************\
* FrameCalibration
*
* Adjusts the number of frames in a cycle to conform to desired cycle time
*
\**************************************************************************/
static int
FrameCalibration( AttrContext *pac, struct _timeb *pBaseTime, int framesPerCycle, int nCycle )
{
    struct _timeb thisTime;
    FLOAT cycleTime;

    _ftime( &thisTime );
    cycleTime = thisTime.time - pBaseTime->time +
           (thisTime.millitm - pBaseTime->millitm)/1000.0f;
    cycleTime /= (FLOAT) nCycle;

    if( cycleTime < gfMinCycleTime ) {
        // need to add more frames to cycle
        if( cycleTime == 0.0f ) // very unlikely
            framesPerCycle = 800;
        else
            framesPerCycle = (int)( (FLOAT)framesPerCycle *
                            (gfMinCycleTime/cycleTime) );
    } else {
        // for vstrings, subtract frames from cycle
        if( pac->demoType == DEMO_VSTRING ) {
            framesPerCycle = (int)( (FLOAT)framesPerCycle *
                            (gfMinCycleTime/cycleTime) );
        }
    }
#define MIN_FRAMES 16
    // make sure it's not too small
    if( framesPerCycle < MIN_FRAMES )
        framesPerCycle = MIN_FRAMES;

    return framesPerCycle;
}

/**************************************************************************\
* MapValue
*
* Maps the value along an input range, to a proportional one along an
* output range.  Each range must be monotonically increasing or decreasing.
*
* NO boundary conditions checked - responsibility of caller.
*
\**************************************************************************/
FLOAT
MapValue( FLOAT fInVal,
          FLOAT fIn1, FLOAT fIn2,       // input range
          FLOAT fOut1, FLOAT fOut2 )    // output range
{
    FLOAT fDist, fOutVal;

    // how far along the input range is fInVal?, in %
    fDist = (fInVal - fIn1) / (fIn2 - fIn1);

    // use this distance to interpolate into output range
    fOutVal = fDist * (fOut2 - fOut1) + fOut1;

    return fOutVal;
}

/**************************************************************************\
* MapValueI
*
* Similar to above, but maps integer values
*
* Currently, only works for increasing ranges
*
\**************************************************************************/
int
MapValueI( int inVal,
          int in1, int in2,       // input range
          int out1, int out2 )    // output range
{
    int inDiv;
    int outVal;
    FLOAT fScale, fComp;

    if( inVal >= in2 )
        return out2;
    if( inVal <= in1 )
        return out1;

    inDiv = abs(in2 - in1) + 1;
    fScale = (FLOAT) (inDiv-1) / (FLOAT) inDiv;
    fComp = 1.0f + (1.0f / inDiv);

    outVal = (int) MapValue( (FLOAT) inVal * fComp,
                       (FLOAT) in1, (FLOAT) in2 + 0.999f,
                       (FLOAT) out1, (FLOAT) out2 + 0.999f );
    return outVal;
}


SSTEXT3D.H

/******************************Module*Header*******************************\
* Module Name: sstext3d.h
*
* Global header for text3D screen saver.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#ifndef __sstext3d_h__
#define __sstext3d_h__

#include <commctrl.h>
#include "dlgs.h"
#include "sscommon.h"

#define PI_OVER_2 (PI/2.0f)
#define PI_OVER_4 (PI/4.0f)

#define TEXT_BUF_SIZE       100     // max length of display text buffer
                                    // (including NULL termination)
#define TEXT_LIMIT          16      // max length of user-inputted display text

#define MAX_IROT            100     // max integer rotation level (from slider)

#define MIN_SLIDER          0
#define MAX_SLIDER          100

// demo types
enum {
    DEMO_STRING = 0,    // static string
    DEMO_CLOCK,         // digital clock
    DEMO_VSTRING,       // variable string (actually a subset of DEMO_STRING)
};

#define MAX_DEMO  1             // max demo index
#define NUM_DEMOS (MAX_DEMO+1)

// surface styles
enum {
    SURFSTYLE_SOLID = 0,
    SURFSTYLE_TEX,
    SURFSTYLE_WIREFRAME
};

// rotation styles
enum {
    ROTSTYLE_NONE = 0,
    ROTSTYLE_SEESAW,
    ROTSTYLE_WOBBLE,
    ROTSTYLE_RANDOM,
    NUM_ROTSTYLES
};

enum {
    X_AXIS = 0,
    Y_AXIS,
    Z_AXIS,
    NUM_AXIS
};

#if defined(max)
#undef max
#endif
#define max( a, b ) ( a >= b ? a : b )

#if defined(clamp)
#undef clamp
#endif
#define clamp( a, lo, hi ) ( (a < lo) ? lo : ((a > hi) ? hi : a) )

#define deg_to_rad( a ) ( (a*PI) / 180.0f )
#define rad_to_deg( a ) ( (a*180.0f) / PI )

typedef struct {
    USHORT listNum;             // display list number
    TCHAR     glyph;            // glyph value (for extended LUT entries)
    LPGLYPHMETRICSFLOAT lpgmf;  // ptr to glyphmetrics
} LISTENTRY;

#define SIZE_LIST_LUT  512
#define MAX_DIRECT_LUT 256

typedef struct {
    HDC     hdc;
    int     nGlyphs;            // number of glyphs
    int     firstGlyph;
    FLOAT   chordalDeviation;
    FLOAT   extrusion;
    int     type;               // WGL_FONT_LINES or WGL_FONT_POLYGONS
    LISTENTRY *listLUT;             // LUT for cmd list # from glyph
    int     LUTIndex;           // current index for new indirect look-ups
} WglFontContext;

typedef struct {
    // registry or registry-derived attributes
    int     demoType;
    BOOL    bMaterialCycle;
    int     matType;    // material type from registry
    MATERIAL *pMat;     // ptr to current material
    int     surfStyle;
    int     rotStyle;   // rotation style
    int     texQual;
    float   fTesselFact;
    float   fDepth; // extrusion
    int     iSpeed; // rotation speed
    UINT    uSize;  // window size
    TEXFILE texFile;  // texture file
    TEXTURE texture;
    TCHAR   szFontName[LF_FACESIZE];       // font face name
    BOOL    bBold;
    BOOL    bItalic;
    BYTE    charSet;
    TCHAR   szText[TEXT_BUF_SIZE+1];  // display string
    USHORT  usText[TEXT_BUF_SIZE+1];    // display string converted to cmd lists

    // internal attributes
    BOOL            bTexture;
    BOOL            bRandomMat;
    WglFontContext  *pWglFontC;
    int             textLen;
    POINTFLOAT      pfTextExtent;
    POINTFLOAT      pfTextOrigin;       // upper left corner of extents
    POINT3D         p3dBoundingBox;     // bounding box, from spin angles
    FLOAT           fFovy;              // field of view in y-dir
    FLOAT           fAspect;            // aspect ratio of GL window
    FLOAT           fViewDist;          // dist to front of bounding box
    FLOAT           fZtrans;            // translation in z
    int             iRotStep;           // rotation step from slider
    int             iRotMinStep;        // min rotation step
    int             iRotMaxStep;        // max rotation step
    IPOINT3D        ip3dRotStep;        // xyz rot step, for random rotation
    IPOINT3D        ip3dRoti;           // current xyz rot step iteration
    POINT3D         p3dRotMin;          // min rotation amplitude
    POINT3D         p3dRotMax;          // max rotation amplitude
    POINT3D         p3dRotLimit;        // current rotation amplitude
    POINT3D         p3dRot;             // current rotation
    POINTFLOAT      *pTrig;             // current trig table
    SYSTEMTIME      stTime;
    BOOL            bXMajor;            // string either x-major or y-major

    SSContext       ssc;                // screen saver configuration
} AttrContext;

// Global attribute context
extern AttrContext gac;

extern WglFontContext* CreateWglFontContext (
    HDC     hdc,
    int     type,
    float   fExtrusion,
    float   fChordalDeviation );

extern void DeleteWglFontContext(
    WglFontContext *pwfc );

extern void DrawString (
    USHORT *string,
    int     strLen,
    WglFontContext *pwfc );

extern int GetStringExtent(
    LPTSTR  pszString,
    POINTFLOAT *extent,
    POINTFLOAT *origin,
    WglFontContext *pwfc );

extern void ConvertStringToList(
    LPTSTR pszSrc,
    USHORT *usDst,
    WglFontContext *pwfc );

extern void getIniSettings(void);

// Resource constants

#define IDS_SCREENSAVERTITLE    1020
#define IDS_SAVERNAME           1002

// registry attribute strings:
#define IDS_DEMOTYPE            1100
#define IDS_SURFSTYLE           1104
#define IDS_FONT                1109
#define IDS_FONT_ATTRIBUTES     1110
#define IDS_CHARSET             1111
#define IDS_TEXT                1115
#define IDS_SPEED               1120
#define IDS_ROTSTYLE            1124

// demo type strings
#define IDS_DEMO_STRING       1200
#define IDS_DEMO_CLOCK        1201

// rotation resource strings
#define IDS_ROTSTYLE_NONE            1400
#define IDS_ROTSTYLE_SEESAW          1401
#define IDS_ROTSTYLE_WOBBLE          1402
#define IDS_ROTSTYLE_RANDOM          1403

#define DLG_SETUP_HELP          2001
#define DLG_SETUP_TYPES         2002    // object type menu
#define DLG_SETUP_BITMAP        2003
#define DLG_SETUP_ABOUT         2010
#define DLG_SETUP_TESSEL        2012    // tesselation slider
#define DLG_SETUP_SIZE          2014    // size slider
#define DLG_SETUP_TEX           2016    // texture button
#define DLG_SETUP_FONT          2022    // select font button
#define DLG_SETUP_SPEED         2023    // speed slider

// surface styles
#define IDC_RADIO_SOLID         2030
#define IDC_RADIO_TEX           2031
#define IDC_RADIO_WIREFRAME     2032    // not presently used

#define IDC_TO_SURFSTYLE(n)         ( (n) - IDC_RADIO_SOLID )
// In order for the IDC_TO_SURFSTYLE conversion macro to work, the radio buttons
// for surface styles must be kept contiguous.

// rotation styles
#define DLG_SETUP_ROTSTYLE      4100

// demo type
#define IDC_DEMO_STRING         5000
#define IDC_DEMO_CLOCK          5001
#define IDC_TO_DEMOTYPE(n)      ( (n) - IDC_DEMO_STRING )

// sliders
#define IDC_STATIC_TESS         2051    // box around slider
#define IDC_STATIC_TESS_MIN     2052    // min label
#define IDC_STATIC_TESS_MAX     2053    // max label
#define IDC_STATIC_SIZE         2054
#define IDC_STATIC_SIZE_MIN     2055
#define IDC_STATIC_SIZE_MAX     2056

// rotation sliders
#define IDC_STATIC_ROTATION_GRP 5000

// configure text dialog box stuff
#define IDS_TEXT_TITLE          3001
#define DLG_TEXT_ENTER          3020
#define DLG_TEXT_SHOW           3021

// Choose font template
#define DLG_CF_TEMPLATE         6000
#define IDD_FONT                6001

// Default texture resource
#define IDB_DEFTEX              7000

#define SHELP_CONTENTS          01
#define SHELP_SHAPES            02
#define SHELP_PASSWORD          03
#define SHELP_COLOR             04
#define SHELP_MISC              05
#define SHELP_OVERVIEW          06

//
// Neutral ANSI/UNICODE types and macros plucked from ntdef.h
//
#ifdef  UNICODE
typedef WCHAR TCHAR, *PTCHAR;
#else   /* UNICODE */
typedef unsigned char TUCHAR, *PTUCHAR;
#endif /* UNICODE */

#endif // __sstext3d_h__


README.TXT

The sample AVIEDIT is a simple AVI editing application using the editing APIs in AVIFile.


AUDIO.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

BOOL CALLBACK aviaudioPlay(HWND hwnd, PAVISTREAM pavi, LONG lStart, LONG lEnd, BOOL fWait);
void CALLBACK aviaudioMessage(HWND, UINT, WPARAM, LPARAM);
void CALLBACK aviaudioStop(void);
LONG CALLBACK aviaudioTime(void);


AVIEDIT.C

/****************************************************************************
 *
 *  AVIEDIT.C
 *
 *  Sample program using the AVIFile read/write routines
 *
 **************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define STRICT
#define INC_OLE2
#include <windows.h>
#include <shellapi.h>
#include <windowsx.h>
#include <commdlg.h>
#include "muldiv32.h"
#include <vfw.h>

#include "aviedit.h"
#include "audio.h"

#include <limits.h>

#define GlobalSizePtr(lp)   GlobalSize(GlobalPtrHandle(lp))
#define LPPAVIFILE PAVIFILE *
typedef BYTE * HPBYTE;
typedef UNALIGNED short * HPSHORT;

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static BOOL gfDefDlgEx = FALSE;         //the recursion flag for message crackers

#define BUFSIZE 260
static char gszBuffer[BUFSIZE];
static char gszFileName[BUFSIZE];
static char gszSaveFileName[BUFSIZE];

#define AVI_EDIT_CLASS  "edit"
static LPAVISTREAMINFO glpavisi;
static int gnSel;
int     gSelectedStream = -1;   // Highlight this text area when painting
RECT    grcSelectedStream;      // where to highlight

char    gszFilter[512];     // for AVIBuildFilter - more than one string!
static  HINSTANCE   ghInstApp;
static  HWND        ghwndApp;
static  HACCEL      ghAccel;
static  WNDPROC     gOldEditProc;
static  HWND        ghwndEdit;

#define SCROLLRANGE  10000
#define MAXNUMSTREAMS   25

int                 gcpavi;                     // # of streams
PAVISTREAM          gapavi[MAXNUMSTREAMS];      // the current streams
int                 gcpaviSel;                  // num of edit streams
PAVISTREAM          gapaviSel[MAXNUMSTREAMS];   // edit streams to put on clipbd
int                 gStreamTop[MAXNUMSTREAMS+1];// y position of each stream
AVICOMPRESSOPTIONS  gaAVIOptions[MAXNUMSTREAMS];// compression options
LPAVICOMPRESSOPTIONS  galpAVIOptions[MAXNUMSTREAMS];
PGETFRAME           gapgf[MAXNUMSTREAMS];       // data for decompressing video
HDRAWDIB            ghdd[MAXNUMSTREAMS];        // drawdib handles
LONG                galSelStart[MAXNUMSTREAMS];
LONG                galSelLen[MAXNUMSTREAMS];
int                 giFirstAudio = -1;          // 1st audio stream found
int                 giFirstVideo = -1;          // 1st video stream found

#define             gfVideoFound (giFirstVideo >= 0)
#define             gfAudioFound (giFirstAudio >= 0)

BOOL                gfPlaying;          // are we currently playing?
LONG                glPlayStartTime;    // When did we start playing?
LONG                glPlayStartPos;     // Where were we on the scrollbar?
LONG                timeStart;          // cached start, end, length
LONG                timeEnd;
LONG                timeLength;
LONG                timehscroll;        // how much arrows scroll HORZ bar
int                 nVertSBLen;         // vertical scroll bar
int                 nVertHeight;
DWORD               gdwMicroSecPerPixel = 1000L;        // scale for video
WORD                gwZoom = 2;         // one-half zoom (divide by 4)
HWND                ghwndMCI;

// buffer for wave data
LPVOID lpAudio;

// constants for painting
#define VSPACE  8       // some vertical spacing
#define SELECTVSPACE 4 // height of selection line
#define HSPACE  4       // space between frames for video stream
#define TSPACE  10      // space for text area about each stream
#define AUDIOVSPACE  64 // height of an audio stream at X1 zoom

#define HIGHLIGHT       (GetSysColor(COLOR_HIGHLIGHT) ? GetSysColor(COLOR_HIGHLIGHT) : GetSysColor(COLOR_ACTIVECAPTION))

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

// Macros to get and set the scroll bar to a given millisecond value in the
// movie.  Movie lengths can be DWORDS but we only have 16 bits of resolution.

#define GetScrollTime(hwnd) \
(timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))

#define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
(int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

LRESULT CALLBACK AppWndProc(HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK NewEditProc(HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK    AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int  ErrMsg (LPSTR sz,...);

BOOL MenuHandler( HWND, int );

void editPaste(HWND hwnd, PAVIFILE pfile);
void             FrameVideo(HDC hdc, RECT *rcFrame, HBRUSH hbr);

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static int gfWait = 0;

/*----------------------------------------------------------------------------*\
|    StartWait()                                                                |
|    Start a wait operation... put up the hourglass if it's the first call      |
\*----------------------------------------------------------------------------*/
void StartWait()
{
    if (gfWait++ == 0) {
SetCursor(LoadCursor(NULL,IDC_WAIT));
    }
}

/*----------------------------------------------------------------------------*\
|    EndWait()                                                                  |
|    Once every one who started a wait is finished, go back to regular cursor   |
\*----------------------------------------------------------------------------*/
void EndWait()
{
    if (--gfWait == 0) {
SetCursor(LoadCursor(NULL,IDC_ARROW));
InvalidateRect(ghwndApp, NULL, TRUE);
    }
}

/*----------------------------------------------------------------------------*\
|    WinYield()                                                                 |
|    Code to yield while we're not calling GetMessage.                          |
|    Dispatch all messages.  Pressing ESC or closing aborts.                    |
\*----------------------------------------------------------------------------*/

BOOL WinYield(void)
{
    MSG msg;
    BOOL fAbort=FALSE;

    while(gfWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    {
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
    fAbort = TRUE;
if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
    fAbort = TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
    }
    return fAbort;
}

/*----------------------------------------------------------------------------*\
|    PaintVideo()                                                               |
|    Draw a video frame in the specified rect                                   |
\*----------------------------------------------------------------------------*/

void PaintVideo(HDC hdc, RECT rcFrame, int iStream, LPBITMAPINFOHEADER lpbi, LONG lCurSamp, LONG lPos)
{
    int         iLen;
    COLORREF    nCol;
    char        szText[BUFSIZE];
    RECT        rc;

    //
    // If we have a picture, draw it
    //
    if (lpbi)
    {
//
// use the palette of the first video stream
//
DrawDibDraw(ghdd[iStream], hdc,
    rcFrame.left, rcFrame.top,
    rcFrame.right - rcFrame.left,
    rcFrame.bottom - rcFrame.top,
    lpbi, NULL,
    0, 0, -1, -1,
    (iStream == giFirstVideo) ? 0 :DDF_BACKGROUNDPAL);

iLen = wsprintf(szText, "%ld %ld.%03lds",
lCurSamp, lPos / 1000, lPos % 1000);
    }

    //
    // Before or after the movie (or read error) draw GRAY
    //
    else {
SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));

PatBlt(hdc,
       rcFrame.left, rcFrame.top,
       rcFrame.right - rcFrame.left,
       rcFrame.bottom - rcFrame.top,
       PATCOPY);
iLen = 0;
szText[0] = '\0';
    }

    //
    // print something meaningful under the frame
    //
    rc.left = rcFrame.left;
    rc.right = rcFrame.right - 2 * HSPACE;      // don't overlap text areas
    rc.top = rcFrame.bottom + HSPACE;
    rc.bottom = rc.top + TSPACE + TSPACE;       // blank out enough space
    nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
    ExtTextOut(hdc, rc.left, rc.top, ETO_CLIPPED | ETO_OPAQUE,
       &rc, szText, iLen, NULL);
    SetBkColor(hdc, nCol);
}

/*----------------------------------------------------------------------------*\
|    PaintAudio()                                                               |
|    Draw some samples of audio inside the given rectangle                      |
\*----------------------------------------------------------------------------*/

void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
{
    PCMWAVEFORMAT wf;
    int i;
    int x,y;
    int w,h;
    BYTE b;
    HBRUSH hbr;
    RECT rc = *prc;
    LONG    lBytes;
    LONG    l, lLenOrig = lLen;
    LONG    lWaveBeginTime = AVIStreamStartTime(pavi);
    LONG    lWaveEndTime   = AVIStreamEndTime(pavi);

    //
    // We can't draw before the beginning of the stream - adjust
    //
    if (lStart < lWaveBeginTime) {
lLen -= lWaveBeginTime - lStart;
lStart = lWaveBeginTime;
// right justify the legal samples in the rectangle - don't stretch
rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    //
    // We can't draw past the end of the stream
    //
    if (lStart + lLen > lWaveEndTime) {
lLenOrig = lLen;
lLen = max(0, lWaveEndTime - lStart);   // maybe nothing to draw!
// left justify the legal samples in the rectangle - don't stretch
rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    // Now start working with samples, not time
    l = lStart;
    lStart = AVIStreamTimeToSample(pavi, lStart);
    lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;

    //
    // Get the format of the wave data
    //
    l = sizeof(wf);
    AVIStreamReadFormat(pavi, lStart, &wf, &l);
    if (!l)
return;

    w = rc.right - rc.left;
    h = rc.bottom - rc.top;

    //
    // We were starting before the beginning or continuing past the end.
    // We're not painting in the whole original rect --- use a dark background
    //
    if (rc.left > prc->left) {
SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
       rc.bottom - rc.top, PATCOPY);
    }
    if (rc.right < prc->right) {
SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
       rc.bottom - rc.top, PATCOPY);
    }

#define BACKBRUSH  (GetSysColor(COLOR_BTNFACE))         // background
#define MONOBRUSH  (GetSysColor(COLOR_BTNSHADOW))       // for mono audio
#define LEFTBRUSH  (RGB(0,0,255))                       // left channel
#define RIGHTBRUSH (RGB(0,255,0))                       // right channel
#define HPOSBRUSH  (RGB(255,0,0))                       // current position

    //
    // Paint the background
    //
    hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
    PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    //
    // !!! we can only paint PCM data right now.  Sorry!
    //
    if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
return;

    //
    // How many bytes are we painting? Alloc some space for them
    //
    lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;
    if (!lpAudio)
lpAudio = GlobalAllocPtr (GHND, lBytes);
    else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);
    if (!lpAudio)
return;

    //
    // Read in the wave data
    //
    AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);
    if (l != lLen)
return;

#define MulDiv(a,b,c) (UINT)((DWORD)(UINT)(a) * (DWORD)(UINT)(b) / (UINT)(c))

    //
    // !!! Flickers less painting it NOW or LATER?
    // First show the current position as a bar
    //
    hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
    PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    //
    // Paint monochrome wave data
    //
    if (wf.wf.nChannels == 1) {

//
// Draw the x-axis
//
hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
y = rc.top + h/2;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

//
// 8 bit data is centred around 0x80
//
if (wf.wBitsPerSample == 8) {
    for (x=0; x<w; x++) {

// which byte of audio data belongs at this pixel?
b = *((HPBYTE)lpAudio + muldiv32(x, lLen, w));

if (b > 0x80) {
    i = y - MulDiv(b - 0x80, (h / 2), 128);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = y + MulDiv(0x80 - b, (h / 2), 128);
    PatBlt(hdc, rc.left + x, y, 1, i - y, PATCOPY);
}
    }
}

//
// 16 bit data is centred around 0x00
//
else if (wf.wBitsPerSample == 16) {
    for (x=0; x<w; x++) {

// which byte of audio data belongs at this pixel?
// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w));

if (i > 0) {
    i = y - (int) ((LONG)i * (h/2) / 32768);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = (int) ((LONG)i * (h/2) / 32768);
    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
    }
}
DeleteObject(SelectObject(hdc, hbr));
    } // endif mono

    //
    // Draw stereo waveform data
    //
    else if (wf.wf.nChannels == 2) {

//
// 8 bit data is centred around 0x80
//
if (wf.wBitsPerSample == 8) {

    // Left channel
    hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
    y = rc.top + h/4;
    PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

    for (x=0; x<w; x++) {
b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);

if (b > 0x80) {
    i = y - MulDiv(b-0x80,(h/4),128);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = y + MulDiv(0x80-b,(h/4),128);
    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
}
    }
    DeleteObject(SelectObject(hdc, hbr));

    // Right channel
    hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
    y = rc.top + h * 3 / 4;
    PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

    for (x=0; x<w; x++) {
b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);

if (b > 0x80) {
    i = y - MulDiv(b-0x80,(h/4),128);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = y + MulDiv(0x80-b,(h/4),128);
    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
}
    }
    DeleteObject(SelectObject(hdc, hbr));
}

//
// 16 bit data is centred around 0x00
//
else if (wf.wBitsPerSample == 16) {

    // Left channel
    hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
    y = rc.top + h/4;
    PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

    for (x=0; x<w; x++) {

// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2);
if (i > 0) {
    i = y - (int) ((LONG)i * (h/4) / 32768);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = (int) ((LONG)i * (h/4) / 32768);
    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
    }
    DeleteObject(SelectObject(hdc, hbr));

    // Right channel
    hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
    y = rc.top + h * 3 / 4;
    PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

    for (x=0; x<w; x++) {
// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
if (i > 0) {
    i = y - (int) ((LONG)i * (h/4) / 32768);
    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
    i = (int) ((LONG)i * (h/4) / 32768);
    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
    }
    DeleteObject(SelectObject(hdc, hbr));
}
    } // endif stereo
}

/*----------------------------------------------------------------------------*\
|    PaintStuff()                                                               |
|    Do our painting.  Return the height of everything painted so we know how   |
|    much room it took to set our scrollbars.  If fDrawEverything is TRUE,      |
|    we will not stop drawing just because we know we're drawing outside the    |
|    window. This is used to determine how much we would want to draw.          |
\*----------------------------------------------------------------------------*/

int PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything)
{
    int         yStreamTop;
    char        szText[BUFSIZE];
    int         iFrameWidth, iLen;
    LONG        lSamp, lCurSamp;
    int         n;
    int         nFrames;
    LPBITMAPINFOHEADER lpbi = NULL;
    LONG        l;
    LONG        lTime;
    LONG        lSize = 0;
    LONG        lAudioStart;
    LONG        lAudioLen;
    RECT        rcFrame, rcC;
    int         i;
    HBRUSH      hbr, hbrOld;
    RECT        rc;

    GetClientRect(hwnd, &rcC);

    //
    // Look at scrollbars to find current position
    //
    lTime = GetScrollTime(hwnd);
    yStreamTop = -GetScrollPos(hwnd, SB_VERT);

    //
    // Walk through all streams and draw something
    //
    for (i=0; i<gcpavi; i++) {
AVISTREAMINFO   avis;
LONG            lEnd, lEndTime;
COLORREF        nCol;

//
// Remember where this stream begins
//
gStreamTop[i] = yStreamTop + GetScrollPos(hwnd, SB_VERT);

//
// Get some info about this stream
//
AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

//
// Highlight the stream name if we're supposed to
//
if (gSelectedStream == MAXNUMSTREAMS+i) {
    hbr = CreateSolidBrush(HIGHLIGHT);
    hbrOld = SelectObject(hdc, hbr);

    PatBlt(hdc, 0, yStreamTop, rcC.right, TSPACE * 2, PATCOPY);
    SelectObject(hdc, hbrOld);
    DeleteObject(hbr);
}

// First we'll print out the stream name
nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
TextOut(hdc, HSPACE, yStreamTop, avis.szName, strlen(avis.szName));
SetBkColor(hdc, nCol);

// Skip to the next line
yStreamTop += 2*TSPACE;

if (galSelStart[i] == -1)
{
    LoadString( ghInstApp, IDS_FORMAT_1, gszBuffer, BUFSIZE );
    iLen = wsprintf(szText,
    gszBuffer,
    i,
    (LPSTR) &avis.fccType,      // compressor FOURCC
    AVIStreamStart(gapavi[i]),
    AVIStreamLength(gapavi[i]),
    (AVIStreamEndTime(gapavi[i]) -
     AVIStreamStartTime(gapavi[i])) / 1000);
}
else
{
    LoadString( ghInstApp, IDS_FORMAT_2, gszBuffer, BUFSIZE );
    iLen = wsprintf(szText,
    gszBuffer,
    i,
    (LPSTR) &avis.fccType,      // compressor FOURCC
    AVIStreamStart(gapavi[i]),
    AVIStreamLength(gapavi[i]),
    (AVIStreamEndTime(gapavi[i]) -
     AVIStreamStartTime(gapavi[i])) / 1000,
    galSelStart[i], galSelStart[i] + galSelLen[i] - 1);
}
//
// Highlight the stream info if we're supposed to
//
if (gSelectedStream == i) {
    hbr = CreateSolidBrush(HIGHLIGHT);
    hbrOld = SelectObject(hdc, hbr);

    PatBlt(hdc, 0, yStreamTop, rcC.right, TSPACE * 2, PATCOPY);
    SelectObject(hdc, hbrOld);
    DeleteObject(hbr);
}

nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
TextOut(hdc, HSPACE, yStreamTop, szText, iLen);
SetBkColor(hdc, nCol);

yStreamTop += TSPACE;
//
// Draw a VIDEO stream
//
if (avis.fccType == streamtypeVIDEO) {
    if (gapgf[i] == NULL)
continue;

    //
    // Which frame belongs at this time?
    //
    lEndTime = AVIStreamEndTime(gapavi[i]);
    if (lTime <= lEndTime)
lSamp = AVIStreamTimeToSample(gapavi[i], lTime);
    else {      // we've scrolled past the end of this stream
lEnd = AVIStreamEnd(gapavi[i]);
lSamp = lEnd + AVIStreamTimeToSample(gapavi[i],
     lTime - lEndTime);
    }

    //
    // how wide is each frame to paint?
    //
    iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
  gwZoom / 4 + HSPACE;

    //
    // how many frames can we fit on each half of the screen
    // not counting the one we'll centre?
    //
    nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
    if (nFrames < 0)
nFrames = 0;

    //
    // Step through all the frames we'll draw
    //
    for (n = -nFrames; n <= nFrames; n++) {

//
// Each video stream is drawn as a horizontal line of
// frames, very close together.
// The first video stream shows a different frame in
// each square. Thus the scale of time is determined
// by the first video stream.
// Every other video stream shows whatever
// frame belongs at the time corresponding to the mid-
// point of each square.
//
if (i == giFirstVideo) {

    //
    // by definition, we know what frame we're drawing..
    // (lSamp-n), (lSamp-(n-1)), ..., (lSamp), ...,
    // (lSamp+n) (lSamp is the one in the centre)
    //
    lCurSamp = lSamp + n;

    //
    // what time is it at that frame? This number will
    // be printed underneath the frame
    //
    l = AVIStreamSampleToTime(gapavi[i], lCurSamp);

} else {        // NOT the first video stream

    //
    // What time is it at the left hand of the square
    // we'll draw?  That's what frame we use.
    // Does the rounding with MulDiv32 have better properties
    // than muldiv32? It appears to give _slightly_ better performance

    l = lTime + MulDiv32(n * (iFrameWidth+HSPACE),
 gdwMicroSecPerPixel, 1000);
    //if (n<=0) {  // calculate the time for a frame left of centre,
    //             // don't forget the HSPACE offset.
    //    l = lTime - muldiv32(-n * (iFrameWidth + HSPACE),
    //                         gdwMicroSecPerPixel, 1000);
    //
    //}
    //else { // frame is to right of centre.
    //    l = lTime + muldiv32(n * (iFrameWidth +HSPACE),
    //                           gdwMicroSecPerPixel, 1000);
    //
    //}

    //
    // What frame belongs to that time?
    //
    lCurSamp = AVIStreamTimeToSample(gapavi[i], l);

    //
    // Use the exact time of that frame when printing
    //
    l = AVIStreamSampleToTime(gapavi[i], lCurSamp);
}

// !!!
// Could actually return an LPBI for invalid frames
// so we better force it to NULL.
//
if (gapgf[i] && lCurSamp >= AVIStreamStart(gapavi[i])) // &&
    //lCurSamp <= AVIStreamEnd(gapavi[i]))
    lpbi = AVIStreamGetFrame(gapgf[i], lCurSamp);
else
    lpbi = NULL;

//
// Figure out where to draw this frame
//
rcFrame.left   = rcC.right / 2 -
 ((avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4)
 / 2 + (n * iFrameWidth);
rcFrame.top    = yStreamTop + TSPACE;
rcFrame.right  = rcFrame.left +
 (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4;
rcFrame.bottom = rcFrame.top +
 (avis.rcFrame.bottom - avis.rcFrame.top) * gwZoom / 4;

//
// If this frame is selected, highlight it
//
if (lCurSamp >= galSelStart[i] &&
    lCurSamp < galSelStart[i] +
    galSelLen[i]) {
    hbr = CreateSolidBrush(HIGHLIGHT);
}
else { //not highlighted - but need to clear area around frame
       // of selection
    hbr = CreateSolidBrush(GetBkColor(hdc));
}

FrameVideo(hdc, &rcFrame, hbr);
DeleteObject (hbr);

//
// draw a border around the centre frame.
//
if (n == 0) {
    hbr = CreateSolidBrush(RGB(255,0,0));
    InflateRect(&rcFrame, 1, 1);
    FrameRect(hdc, &rcFrame, hbr);
    InflateRect(&rcFrame, -1, -1);
    DeleteObject (hbr);
}

//
// Now draw the video frame in the computed rectangle
//
PaintVideo(hdc, rcFrame, i, lpbi, lCurSamp, l);
    }

    //
    // Print a description of this stream
    //
    if (lpbi)
AVIStreamSampleSize(gapavi[i], lSamp, &lSize);

    //
    // Move down to where we can draw the next stream
    //
    yStreamTop += TSPACE +
  (rcFrame.bottom - rcFrame.top) +
  TSPACE;
}

//
// Draw an AUDIO stream
//
else if (avis.fccType == streamtypeAUDIO) {

    //
    // Figure out which samples are visible
    //
    lAudioStart = lTime - muldiv32(rcC.right / 2,
   gdwMicroSecPerPixel, 1000);
    lAudioLen = 2 * (lTime - lAudioStart);

    // clear the selection area

    hbr = CreateSolidBrush(GetBkColor(hdc));
    hbrOld = SelectObject(hdc, hbr);
    PatBlt(hdc,
0,
yStreamTop + TSPACE - SELECTVSPACE,
rcC.right,
SELECTVSPACE,
PATCOPY);
    PatBlt(hdc,
0,
yStreamTop + TSPACE + (AUDIOVSPACE * gwZoom/4),
rcC.right,
SELECTVSPACE,
PATCOPY);
    SelectObject(hdc, hbrOld);
    DeleteObject (hbr);

    //
    // We have a selection... Highlight it
    //
    if (galSelStart[i] != -1) {
LONG lSelSt, lSelLen;

//
// What time is our selection?
//
lSelSt = AVIStreamSampleToTime(gapavi[i], galSelStart[i]);
lSelLen = AVIStreamSampleToTime(gapavi[i], galSelLen[i]);

//
// At what pixels is our selection?
//
if (lSelSt < lTime) { //selecting to the left of the current position
    rc.left = rcC.right /2 - (int)
muldiv32((lTime - lSelSt) , 1000, gdwMicroSecPerPixel);
}
else {
    rc.left = rcC.right / 2 + (int)
muldiv32(lSelSt - lTime, 1000, gdwMicroSecPerPixel);
}
rc.right = rc.left + (int)
muldiv32(lSelLen, 1000, gdwMicroSecPerPixel);

// Selection starts way past left side of screen
if (lSelSt < lAudioStart)
    rc.left = 0;
// Selection is off, screen left
if (lSelSt + lSelLen < lAudioStart)
    rc.right = - SELECTVSPACE;

// Selection is off, screen right
if (lSelSt > lAudioStart + lAudioLen)
    rc.left = rcC.right + SELECTVSPACE;

// Selection ends past the right side of the screen
if (lSelSt + lSelLen > lAudioStart + lAudioLen)
    rc.right = rcC.right;
if (rc.right == rc.left)        // draw SOMEthing.
    rc.right++;
rc.top = yStreamTop + TSPACE;
rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

//
// Draw some indication
//
hbr = CreateSolidBrush(HIGHLIGHT);
hbrOld = SelectObject(hdc, hbr);
PatBlt(hdc,
       rc.left - SELECTVSPACE,
       rc.top - SELECTVSPACE,
       rc.right - rc.left + 2 * SELECTVSPACE,
       SELECTVSPACE,
       PATCOPY);
PatBlt(hdc,
       rc.left - SELECTVSPACE,
       rc.bottom,
       rc.right - rc.left + 2 * SELECTVSPACE,
       SELECTVSPACE,
       PATCOPY);
SelectObject(hdc, hbrOld);
DeleteObject (hbr);
    }

    //
    // Make the rectangle to draw audio into
    //
    rc.left = rcC.left;
    rc.right = rcC.right;
    rc.top = yStreamTop + TSPACE;
    rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

    //
    // Actually paint the audio
    //
    PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);

    //
    // Print the time at the centre of the audio stream
    //
    iLen = wsprintf(szText, "%ld.%03lds", lTime/1000, lTime%1000);
    rc.left = (rc.right - rc.left) / 2 - 5 * HSPACE;
    rc.right = (rc.right - rc.left) / 2;
    rc.top = rc.bottom + HSPACE; rc.bottom = rc.top + VSPACE;
    nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
    ExtTextOut(hdc, rc.left, rc.top, 0, &rc, szText, iLen, NULL);
    SetBkColor(hdc, nCol);

    //
    // Move down to where we can draw the next stream
    //
    yStreamTop += TSPACE + AUDIOVSPACE * gwZoom / 4;

}

yStreamTop += TSPACE + TSPACE;

//
// Give up once we're painting below the bottom of the window
//
if (!fDrawEverything && yStreamTop >= rcC.bottom)
    break;
    }

    // The bottom of all the streams;
    gStreamTop[gcpavi] = yStreamTop + GetScrollPos(hwnd, SB_VERT);

    //
    // How many lines did we draw?
    //
    return yStreamTop + GetScrollPos(hwnd, SB_VERT);
}

/*----------------------------------------------------------------------------*\
|    FixScrollBars()                                                            |
|    When we load a file or zoom changes, we re-set the scrollbars              |
\*----------------------------------------------------------------------------*/

void FixScrollbars(HWND hwnd)
{
    int                 nHeight = 0;
    RECT                rc;
    HDC                 hdc;

    //
    // Determine how tall our window needs to be to display everything.
    //
    hdc = GetDC(NULL);
    ExcludeClipRect(hdc, 0, 0, 32767, 32767);   // don't actually draw
    nHeight = PaintStuff(hdc, hwnd, TRUE);
    ReleaseDC(NULL, hdc);

    //
    // Set vertical scrollbar for scrolling the visible area
    //
    GetClientRect(hwnd, &rc);
    nVertHeight = nHeight;      // total height in pixels of entire display

    //
    // We won't fit in the window... need scrollbars
    //
    if (nHeight > rc.bottom)
    {
nVertSBLen = nHeight - rc.bottom;
SetScrollRange(hwnd, SB_VERT, 0, nVertSBLen, TRUE);
SetScrollPos(hwnd, SB_VERT, 0, TRUE);

//
// We will fit in the window!  No scrollbars necessary
//
    }
    else
    {
nVertSBLen = 0;
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    }
    return;
}

/*----------------------------------------------------------------------------*\
|    InitStreams()                                                              |
|    Initialize the streams of a loaded file -- the compression options, the    |
|    DrawDIB handles, and the scroll bars.                                      |
|    !!! This clears the compression options right now every time it's called!  |
\*----------------------------------------------------------------------------*/

void InitStreams(HWND hwnd)
{
    AVISTREAMINFO     avis;
    LONG        lTemp;
    int         i;

    //
    // Start with bogus times
    //
    timeStart = 0x7FFFFFFF;
    timeEnd   = 0;

    //
    // Walk through and init all streams loaded
    //
    for (i = 0; i < gcpavi; i++) {

AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

//
// Save and SaveOptions code takes a pointer to our compression opts
//
galpAVIOptions[i] = &gaAVIOptions[i];

//
// clear options structure to zeroes
//
_fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));

//
// Initialize the compression options to some default stuff
// !!! Pick something better
//
galpAVIOptions[i]->fccType = avis.fccType;

switch(avis.fccType) {

    case streamtypeVIDEO:
galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
galpAVIOptions[i]->fccHandler = 0;
galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
galpAVIOptions[i]->dwKeyFrameEvery = (DWORD)-1; // Default
galpAVIOptions[i]->dwBytesPerSecond = 0;
galpAVIOptions[i]->dwInterleaveEvery = 1;
break;

    case streamtypeAUDIO:
galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
galpAVIOptions[i]->dwInterleaveEvery = 1;
AVIStreamReadFormat(gapavi[i],
    AVIStreamStart(gapavi[i]),
    NULL,
    &lTemp);
galpAVIOptions[i]->cbFormat = lTemp;
if (lTemp)
    galpAVIOptions[i]->lpFormat = GlobalAllocPtr(GHND, lTemp);
// Use current format as default format
if (galpAVIOptions[i]->lpFormat)
    AVIStreamReadFormat(gapavi[i],
AVIStreamStart(gapavi[i]),
galpAVIOptions[i]->lpFormat,
&lTemp);
break;

    default:
break;
}

//
// We're finding the earliest and latest start and end points for
// our scrollbar.
//
timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
timeEnd   = max(timeEnd, AVIStreamEndTime(gapavi[i]));

//
// Initialize video streams for getting decompressed frames to display
//
if (avis.fccType == streamtypeVIDEO) {

    gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);

    if (gapgf[i] == NULL)
continue;

    ghdd[i] = DrawDibOpen();
    // !!! DrawDibBegin?

    if (!gfVideoFound) {
    DWORD        dw;

//
// Remember the first video stream --- treat it specially
//
giFirstVideo = i;

//
// Set the horizontal scrollbar scale to show every frame
// of the first video stream exactly once
//
dw = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 + HSPACE;

gdwMicroSecPerPixel = muldiv32(1000000,
       avis.dwScale,
       dw * avis.dwRate);
// Move one frame on the top video screen for each HSCROLL
timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
    }

} else if (avis.fccType == streamtypeAUDIO) {

    // These aren't used and better be NULL!
    gapgf[i] = ghdd[i] = NULL;

    //
    // If there are no video streams, we base everything on this
    // audio stream.
    //
    if (!gfAudioFound && !gfVideoFound) {

// Show one sample per pixel
gdwMicroSecPerPixel = muldiv32(1000000,
       avis.dwScale,
       avis.dwRate);
// Move one sample per HSCROLL
// Move at least enough to show movement
timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
    }

    //
    // Remember the first audio stream --- treat it specially
    //
    if (!gfAudioFound)
giFirstAudio = i;

}

    }

    timeLength = timeEnd - timeStart;

    if (timeLength == 0)
timeLength = 1;

    // Make sure HSCROLL scrolls enough to be noticeable.
    timehscroll = max(timehscroll, timeLength / SCROLLRANGE + 2);

    SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
    SetScrollTime(hwnd, timeStart);

    FixScrollbars(hwnd);
}

/*----------------------------------------------------------------------------*\
|    FixWindowTitle()                                                           |
|    Update the window title to reflect what's loaded                           |
\*----------------------------------------------------------------------------*/

void FixWindowTitle(HWND hwnd)
{
    char szTitle[2*BUFSIZE];

    LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );

    wsprintf(szTitle, "%s %s", (LPSTR)gszBuffer, (LPSTR)gszFileName);

    SetWindowText( hwnd, szTitle );

    InvalidateRect(hwnd, NULL, TRUE);
}

/*----------------------------------------------------------------------------*\
|    FreeDrawStuff()                                                            |
|    Free up the resources associated with DrawDIB.                             |
\*----------------------------------------------------------------------------*/

void FreeDrawStuff(HWND hwnd)
{
    int i;

    // Make sure we're not playing!
    aviaudioStop();

    for (i = 0; i < gcpavi; i++) {
if (gapgf[i]) {
    AVIStreamGetFrameClose(gapgf[i]);
    gapgf[i] = NULL;
}
if (ghdd[i]) {
    DrawDibClose(ghdd[i]);
    ghdd[i] = 0;
}
    }
    SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
    giFirstVideo = giFirstAudio = -1;
}

/*----------------------------------------------------------------------------*\
|    NukeAVIStream()                                                            |
|    Get rid of a stream in our array and compact it.                           |
\*----------------------------------------------------------------------------*/

void NukeAVIStream(int i)
{
    int j;

    //
    // Make sure it's a real stream number
    //
    if (i < 0 || i >=gcpavi)
return;

    //
    // Free all the resources associated with this stream
    //
    AVIStreamRelease(gapavi[i]);
    if (galpAVIOptions[i]->lpFormat) {
GlobalFreePtr(galpAVIOptions[i]->lpFormat);
    }
    if (gapgf[i]) {
AVIStreamGetFrameClose(gapgf[i]);
gapgf[i] = NULL;
    }
    if (ghdd[i]) {
DrawDibClose(ghdd[i]);
ghdd[i] = 0;
    }

    //
    // Compact the arrays of junk
    //
    for (j = i; j < gcpavi - 1; j++) {
gapavi[j] = gapavi[j+1];
galpAVIOptions[j] = galpAVIOptions[j+1];
gapgf[j] = gapgf[j+1];
ghdd[j] = ghdd[j+1];
    }

    gcpavi--;
}

/*----------------------------------------------------------------------------*\
|    FreeAVI()                                                                  |
|    Free the resources associated with an open file.                           |
\*----------------------------------------------------------------------------*/

void FreeAvi(HWND hwnd)
{
    int i;

    FreeDrawStuff(hwnd);

    AVISaveOptionsFree(gcpavi, galpAVIOptions);

    for (i = 0; i < gcpavi; i++) {
AVIStreamRelease(gapavi[i]);
    }

    // Good a place as any to make sure audio data gets freed
    if (lpAudio)
GlobalFreePtr(lpAudio);
    lpAudio = NULL;

    gcpavi = 0;
}

/*----------------------------------------------------------------------------*\
|    InsertAVIFile()                                                            |
|    Put a new AVI file into our internal structures.                           |
\*----------------------------------------------------------------------------*/

void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPSTR lpszFile)
{
    int         i;
    PAVISTREAM  pavi;

    for (i = gcpavi; i <= MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i - gcpavi) != AVIERR_OK)
    break;
if (i == MAXNUMSTREAMS)
{
    AVIStreamRelease(pavi);
    LoadString( ghInstApp, IDS_MAXSTREAMS, gszBuffer, BUFSIZE );
    ErrMsg(gszBuffer);
    break;
}
if (CreateEditableStream(&gapavi[i], pavi) != AVIERR_OK) {
    AVIStreamRelease(pavi);
    break;
}
AVIStreamRelease(pavi);
galSelStart[i] = galSelLen[i] = -1;
    }

    AVIFileRelease(pfile);

    if (gcpavi == i && i != MAXNUMSTREAMS)
    {

LoadString( ghInstApp, IDS_NOOPEN, gszBuffer, BUFSIZE );

ErrMsg(gszBuffer, lpszFile);
return;
    }

    FreeDrawStuff(hwnd);
    gcpavi = i;
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}

/*----------------------------------------------------------------------------*\
|    InitAVI()                                                                  |
|    Open up a file through the AVIFile handlers.                               |
\*----------------------------------------------------------------------------*/

void InitAvi(HWND hwnd, LPSTR szFile, int nMenu)
{
    HRESULT     hr;
    PAVIFILE    pfile;

    hr = AVIFileOpen(&pfile, szFile, 0, 0L);

    if (hr != 0)
    {
LoadString( ghInstApp, IDS_NOOPEN, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer, szFile);
return;
    }

    //
    // If we're opening something new, close other open files, otherwise
    // just close the draw stuff so we'll merge streams with the new file
    //
    if (nMenu == MENU_OPEN)
FreeAvi(hwnd);

    InsertAVIFile(pfile, hwnd, szFile);
}

/*----------------------------------------------------------------------------*\
|    DropAvi()                                                                  |
|    Allow a drag/drop on AVIEdit.                                              |
\*----------------------------------------------------------------------------*/
void DropAvi(HWND hwnd, HDROP hDrop)
{
    char        szPath[BUFSIZE];
    UINT        nDropped, n;
    PAVIFILE    pfile;
    HRESULT     hr;

    // Get number of files dropped
    nDropped = DragQueryFile(hDrop,0xFFFF,NULL,0);

    if (nDropped) {
SetActiveWindow(hwnd);

// If we wanted to, we could simulate a click at the position
// the drop took place....

for (n = 0; n < nDropped; n++) {
    // Get the file that was dropped....
    DragQueryFile(hDrop, n, szPath, BUFSIZE);

    hr = AVIFileOpen(&pfile, szPath, 0, 0L);

    if (hr == 0) {
// ... and paste it in.
editPaste(hwnd, pfile);
    }
}
    }
    DragFinish(hDrop);     /* Delete structure alocated */
}

/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)                                                     |
|                                                                              |
|   Description:                                                               |
|       This is called when the application is first loaded into               |
|       memory.  It performs all initialization that doesn't need to be done   |
|       once per instance.                                                     |
|                                                                              |
|   Arguments:                                                                 |
|       hInstance       instance handle of current instance                    |
|       hPrev           instance handle of previous instance                   |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if successful, FALSE if not                                       |
|                                                                              |
\*----------------------------------------------------------------------------*/

BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw, LPSTR szCmdLine)
{
    WNDCLASS cls;
    WORD        wVer;

    /* first let's make sure we are running on 1.1 */
    wVer = HIWORD(VideoForWindowsVersion());
    if (wVer < 0x010a)
    {
char szTitle[BUFSIZE];
/* oops, we are too old, blow out of here */
MessageBeep(MB_ICONHAND);
LoadString( ghInstApp, IDS_APPERR, szTitle, BUFSIZE );
LoadString( ghInstApp, IDS_OLDVFW, gszBuffer, BUFSIZE );

MessageBox(NULL, gszBuffer, szTitle, MB_OK|MB_ICONSTOP);
return FALSE;
    }

    //
    // Save instance handle for DialogBoxs
    //
    ghInstApp = hInst;

    ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

    //
    // Did we get passed a filename on the command line? We'll open it at create
    // time.
    //
    if (szCmdLine && szCmdLine[0])
lstrcpy(gszFileName, szCmdLine);
    else
lstrcpy( gszFileName, "" );

    lstrcpy( gszSaveFileName, "" );

    if (!hPrev) {
/*
 *  Register a class for the main application window
 */
cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
cls.hIcon          = LoadIcon(hInst,MAKEINTATOM(ID_APP));
cls.lpszMenuName   = MAKEINTATOM(ID_APP);
cls.lpszClassName  = MAKEINTATOM(ID_APP);
cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
cls.hInstance      = hInst;
cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW |
     CS_DBLCLKS;
cls.lpfnWndProc    = (WNDPROC)AppWndProc;
cls.cbWndExtra     = 0;
cls.cbClsExtra     = 0;

if (!RegisterClass(&cls))
    return FALSE;
    }

    //
    // Must be called before using any of the AVIFile routines
    //
    AVIFileInit();

    LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );
    //
    // Create our main application window
    //
    ghwndApp = CreateWindow (
     MAKEINTATOM(ID_APP),    // Class name
     gszBuffer,             // Caption
     WS_OVERLAPPEDWINDOW,    // Style bits
     CW_USEDEFAULT, 0,       // Position
     320,300,                // Size
     (HWND)NULL,             // Parent window (no parent)
     (HMENU)NULL,            // use class menu
     hInst,                  // handle to window instance
     (LPSTR)NULL             // no params to pass on
     );
    ShowWindow(ghwndApp,sw);

    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                              |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed). If the preview is playing it adjusts     |
|       the scrollbar appropriately.                                           |
|                                                                              |
|   Arguments:                                                                 |
|       hInst           instance handle of this instance of the app            |
|       hPrev           instance handle of previous instance, NULL if first    |
|       szCmdLine       ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG     msg;

    //
    // Call our initialization procedure
    //
    if (!AppInit(hInst, hPrev, sw, szCmdLine))
return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;)
    {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
    if (msg.message == WM_QUIT)
return msg.wParam;

    if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
continue;

    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

//
// If we have no messages to dispatch, we do our background task...
// If we're playing a file, we set the scroll bar to show the video
// frames corresponding with the current playing audio sample
//
if (gfPlaying) {
    LONG    l;

    //
    // Use the audio clock to tell how long we've been playing.  To
    // maintain sync, it's important we use this clock.
    //
    l = aviaudioTime();         // returns -1 if no audio playing

    //
    // If we can't use the audio clock to tell us how long we've been
    // playing, calculate it ourself
    //
    if (l == -1)
l = timeGetTime() - glPlayStartTime + glPlayStartPos;

    if (l != (LONG)GetScrollTime(ghwndApp)) {
if (l < timeStart)      // make sure number isn't out of bounds
    l = timeStart;
if (l > timeEnd)        // looks like we're all done!
    FORWARD_WM_COMMAND(ghwndApp, MENU_STOP, NULL, 0, SendMessage);

SetScrollTime(ghwndApp, l);
InvalidateRect(ghwndApp, NULL, FALSE);
UpdateWindow(ghwndApp);

continue;
    }
}

WaitMessage();
    }

    /* NOT REACHED */
}

/*----------------------------------------------------------------------------*\
|    SelectStream()                                                             |
|                                                                               |
|    Selects a portion of a stream i:                                           |
|                                                                               |
|    i == -1 means clear all selections                                         |
|    start == -1 means clear that individual stream's selection                 |
|    fAdd == TRUE means extend that stream's selection to include the new       |
|            range (will otherwise just replace the selection)                  |
|    fAll == TRUE means select this range in every stream, not just i           |
\*----------------------------------------------------------------------------*/

void SelectStream(HWND hwnd, int i, LONG start, LONG length, BOOL fAdd, BOOL fAll)
{
    int n, j;
    LONG mystart, mylength;
    RECT rc;

    //
    // Clear all selections
    //
    if (i == -1) {
for (n = 0; n < gcpavi; n++)
    galSelStart[n] = galSelLen[n] = -1;

    } else if (i >= 0 && i < gcpavi) {  // valid stream number

//
// We've been told to clear this selection
//
if (start == -1 || length == -1)
    galSelStart[i] = galSelLen[i] = -1;

//
// Is this a valid selection range?
//
if (start >=AVIStreamStart(gapavi[i]) &&
    start < AVIStreamEnd(gapavi[i]) &&
    length >= 1) {

    //
    // Do we select the same range in every stream or just one?
    //
    for (j = (fAll ? 0 : i); j < (fAll ? gcpavi : i+1); j++) {

//
// Translate for each stream the equivalent region to select
//
if (j == i) {
    mystart = start; mylength = length;
} else {
    mystart = AVIStreamSampleToSample(gapavi[j],
      gapavi[i], start);
    mylength = max(1, AVIStreamSampleToSample(gapavi[j],
      gapavi[i], length));    // at least 1
    // !!! Better invalidate this entire stream since we're not
    // sure what part needs repainting.
    GetClientRect(hwnd, &rc);
    rc.top = gStreamTop[j] - GetScrollPos(hwnd, SB_VERT);
    rc.bottom = gStreamTop[j+1] - GetScrollPos(hwnd, SB_VERT);
    InvalidateRect(hwnd, &rc, FALSE);
}

//
// Verify we got good selection values
//
if (mystart < AVIStreamStart(gapavi[j])) {
    mylength -= AVIStreamStart(gapavi[j]) - mystart;
    mystart = AVIStreamStart(gapavi[j]);
}
if (mystart + mylength > AVIStreamEnd(gapavi[j]))
    mylength -= mystart + mylength - AVIStreamEnd(gapavi[j]);
if (mylength <= 0)
    mystart = -1;
if (mystart == -1)      // nothing to select in this stream
    continue;

//
// Reset selection to new values
//
if (!fAdd || galSelStart[j] == -1) {
    galSelStart[j] = mystart;
    galSelLen[j] = mylength;

    //
    // extend selection to include this new range
    //
} else {
    if (mystart < galSelStart[j]) {
galSelLen[j] += galSelStart[j] - mystart;
galSelStart[j] = mystart;
    }
    if (mystart + mylength > galSelStart[j] + galSelLen[j])
galSelLen[j] = mystart + mylength - galSelStart[j];
}
    }
}
    }
}

/*----------------------------------------------------------------------------*\
|    EditStreamName()                                                           |
|                                                                               |
|    We need to edit the name of a stream. Create the edit box.                 |
\*----------------------------------------------------------------------------*/

void EditStreamName(HWND hwndParent)

{
    AVISTREAMINFO avis;

    // Get the stream info so we can get the name
    AVIStreamInfo(gapavi[gSelectedStream-MAXNUMSTREAMS], &avis, sizeof(avis));

    // Create the window;
    InflateRect(&grcSelectedStream, 0, 2);
    ghwndEdit = CreateWindow(AVI_EDIT_CLASS, NULL,
     WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
     grcSelectedStream.left,
     grcSelectedStream.top,
     grcSelectedStream.right - grcSelectedStream.left,
     grcSelectedStream.bottom - grcSelectedStream.top,
     hwndParent, (HMENU)1, ghInstApp, NULL);

    // Subclass the window so we can trap <cr> hits.
    gOldEditProc = (WNDPROC)GetWindowLong(ghwndEdit, GWL_WNDPROC);
    SetWindowLong(ghwndEdit, GWL_WNDPROC, (long)NewEditProc);

    // Set the initial text of the edit window, give focus to the
    // window and select the text.
    SetWindowText(ghwndEdit, avis.szName);
    SetFocus(ghwndEdit);
    Edit_SetSel(ghwndEdit, 0, lstrlen(avis.szName));
}

/*----------------------------------------------------------------------------*\
|    EditDone()                                                                 |
|                                                                               |
|    Done with an edit. See if we take the changes.                             |
\*----------------------------------------------------------------------------*/

void EditDone(HWND hwndParent, BOOL bAcceptChange)
{
    // Update the stream name if we're supposed to.
    if (bAcceptChange) {
char szBuff[BUFSIZE];
int n;

// Get the edited name and put into the stream header
n = GetWindowText(ghwndEdit, szBuff, BUFSIZE);
szBuff[n] = '\0';
EditStreamSetName(gapavi[gSelectedStream-MAXNUMSTREAMS], szBuff);
    }

    // Turn the selection off.
    gSelectedStream = -1;

    // Nuke the edit window.
    SetWindowLong(ghwndEdit, GWL_WNDPROC, (long)gOldEditProc);
    DestroyWindow(ghwndEdit);
    ghwndEdit = NULL;

    // Paint where window used to be.
    InvalidateRect(hwndParent, &grcSelectedStream, TRUE);
    UpdateWindow(hwndParent);

    // Give the parent the focus back.
    SetFocus(hwndParent);
}

/*----------------------------------------------------------------------------*\
|    NewEditProc()                                                              |
|                                                                               |
|    Our own home-rolled window proc for the edit window giving                 |
|    notification when <cr> is hit.                                             |
|                                                                               |
|    We trap WM_CHAR, because WM_CHAR(wParam='\r') causes a beep, which         |
|    we don't want.                                                             |
\*----------------------------------------------------------------------------*/
#define _ANSI_R    (TCHAR)'\r'
LRESULT CALLBACK NewEditProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch (msg) {
case WM_CHAR:
    // Trap a keydown for <cr>
    if (wParam == _ANSI_R) {
// Send message to parent giving ID 2 (which doesn't
// exist)
FORWARD_WM_COMMAND(GetParent(hwnd), 2, NULL, 0, PostMessage);
return 0L;
    }

    // Need normal handling of characters otherwise
    goto callDWP;

case WM_KEYUP:
    // Trap a keydown for <Esc>. This is how we get out without
    // making a modification.
    if (wParam == VK_ESCAPE) {
// Send message to parent giving ID 3 (which doesn't
// exist)
FORWARD_WM_COMMAND(GetParent(hwnd), 3, NULL, 0, PostMessage);
return 0L;
    }

    // We want to fall through so what should happen on keydown
    // does.

default:
callDWP:
    // Just call the old window proc
    return CallWindowProc(gOldEditProc, hwnd, msg, wParam, lParam);
    }
}

// ******************************************************************************
//
//      Message Handler for WM_CREATE
//
// ******************************************************************************

BOOL App_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    DragAcceptFiles(hwnd, TRUE);

    if (gszFileName[0])
InitAvi(hwnd, gszFileName, MENU_OPEN);

    return TRUE;
}

// ******************************************************************************
//
//      Message Handler for WM_COMMAND
//
// ******************************************************************************

void App_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
    if (id == 2)
EditDone(hwnd, TRUE);
// See if we are terminating editting.
    else if (id == 3)
EditDone(hwnd, FALSE);
    else
MenuHandler( hwnd, id );
}

// ******************************************************************************
//
//      Message Handler for WM_DROPFILES
//
// ******************************************************************************
//
void App_OnDropFiles(HWND hwnd, HDROP hdrop)
{
    DropAvi(hwnd, hdrop);

    return;
}

// ******************************************************************************
//
//      Message Handler for WM_INITMENU
//
// ******************************************************************************
void App_OnInitMenu(HWND hwnd, HMENU hMenu)
{
    int i;
    BOOL f;
    PAVIFILE pf;

    f = gcpavi > 0;
    EnableMenuItem(hMenu, MENU_SAVEAS, f ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(hMenu, MENU_OPTIONS,f ? MF_ENABLED : MF_GRAYED);

    EnableMenuItem(hMenu, MENU_CLOSE,  f ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(hMenu, MENU_MERGE,  f ? MF_ENABLED : MF_GRAYED);

    EnableMenuItem(hMenu, MENU_SETINFO,  f ? MF_ENABLED : MF_GRAYED);

    // !!! Why not provide UNDO while I'm at it?
    // Enable CUT/COPY/DELETE if there's something selected in a stream
    f = FALSE;
    for (i=0; i<gcpavi; i++)
if (galSelStart[i] != -1)
    f = TRUE;

    EnableMenuItem(hMenu, MENU_COPY,   f ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(hMenu, MENU_CUT,    f ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(hMenu, MENU_DELETE, f ? MF_ENABLED : MF_GRAYED);

    // If we haven't an edit window, we need to setup the "Name'
    //
    if (ghwndEdit == NULL)
    {
LoadString( ghInstApp, IDS_NAME, gszBuffer, BUFSIZE );
ModifyMenu(hMenu, MENU_NAME, MF_BYCOMMAND | MF_STRING, MENU_NAME, gszBuffer);
EnableMenuItem(hMenu, MENU_NAME,
       (gSelectedStream >= MAXNUMSTREAMS) ? MF_ENABLED : MF_GRAYED);
    }
    else
    {
LoadString( ghInstApp, IDS_ABORTNAME, gszBuffer, BUFSIZE );
ModifyMenu(hMenu, MENU_NAME, MF_BYCOMMAND | MF_STRING,MENU_NAME, gszBuffer);
EnableMenuItem(hMenu, MENU_NAME,MF_ENABLED);

}

    // See if there's anything to paste....
    f = FALSE;
    AVIGetFromClipboard(&pf);

    if (pf)
    {
f = TRUE;
AVIFileRelease(pf);
    }

    EnableMenuItem(hMenu, MENU_PASTE,  f ? MF_ENABLED : MF_GRAYED);

    f = gfAudioFound | gfVideoFound;
    EnableMenuItem(hMenu, MENU_PREVIEW, (f & !gfPlaying) ? MF_ENABLED : MF_GRAYED);
    EnableMenuItem(hMenu, MENU_STOP,    (f & gfPlaying)  ? MF_ENABLED : MF_GRAYED);

    CheckMenuItem(hMenu, MENU_ZOOMQUARTER, (gwZoom == 1)  ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem(hMenu, MENU_ZOOMHALF,    (gwZoom == 2)  ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem(hMenu, MENU_ZOOM1,       (gwZoom == 4)  ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem(hMenu, MENU_ZOOM2,       (gwZoom == 8)  ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem(hMenu, MENU_ZOOM4,       (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);

    return;
}
// ******************************************************************************
//
//      Message Handler for WM_SIZE
//
// ******************************************************************************

void App_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    RECT rc;

    GetClientRect(hwnd, &rc);
    //
    // There is not enough vertical room to show all streams. Scrollbars
    // are required.
    //
    if (nVertHeight > rc.bottom)
    {
nVertSBLen = nVertHeight - rc.bottom;
SetScrollRange(hwnd, SB_VERT, 0, nVertSBLen, TRUE);
    }
    else// Everything fits vertically. No scrollbar necessary.
    {
nVertSBLen = 0;
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    }
    return;
}
// ******************************************************************************
//
//      Message Handler for WM_DESTROY
//
// ******************************************************************************
//
void App_OnDestroy(HWND hwnd)
{
    AVIClearClipboard();
    FreeAvi(hwnd);      // close all open streams
    AVIFileExit();      // shuts down the AVIFile system
    PostQuitMessage(0);

    return;
}
// ******************************************************************************
//
//      Message Handler for WM_ENDSESSION
//
// ******************************************************************************
//
void App_OnEndSession(HWND hwnd, BOOL fEnding)
{
    if (fEnding)
    {
if (GetClipboardOwner() == hwnd)
{
    if (OpenClipboard(hwnd))
    {
EmptyClipboard();
CloseClipboard();
    }
}
FreeAvi(hwnd);
    }
    return;
}
// ******************************************************************************
//
//      Message Handler for WM_PALETTECHANGED
//
// ******************************************************************************
//
void App_OnPaletteChanged(HWND hwnd, HWND hwndPaletteChange)
{
    // It came from us.  Ignore it
    if (hwndPaletteChange == hwnd)
return;

    //if needed, insert any calls - such as RealizePallete - below

    return;
}
// ******************************************************************************
//
//      Message Handler for WM_KEYDOWN
//
// ******************************************************************************
//
void App_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
    if(fDown)
    {
switch (vk)
{
    case VK_UP:
FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEUP, 0, PostMessage);
break;
    case VK_DOWN:
FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEDOWN, 0, PostMessage);
break;
    case VK_PRIOR:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_PAGEUP, 0, PostMessage);
break;
    case VK_NEXT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_PAGEDOWN, 0, PostMessage);
break;
    case VK_HOME:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_THUMBPOSITION, 0, PostMessage);
break;
    case VK_END:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_THUMBPOSITION, 0x7FFF, PostMessage);
break;
    case VK_LEFT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEUP, 0, PostMessage);
break;
    case VK_RIGHT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEDOWN, 0, PostMessage);
break;
}
    }
    return;
}
// ******************************************************************************
//
//      Message Handler for WM_HSCROLL
//
// ******************************************************************************
//
void App_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
    LONG lScrollTime;

    lScrollTime = GetScrollTime(hwnd);

    switch (code)
    {
case SB_LINEDOWN:
    lScrollTime += timehscroll;
    break;
case SB_LINEUP:
    lScrollTime -= timehscroll;
    break;
case SB_PAGEDOWN:
    lScrollTime += timeLength/10;
    break;
case SB_PAGEUP:
    lScrollTime -= timeLength/10;
    break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
    lScrollTime = pos;
    lScrollTime = timeStart + muldiv32(lScrollTime, timeLength, SCROLLRANGE);
    break;
    }

    if (lScrollTime < timeStart)
lScrollTime = timeStart;

    if (lScrollTime > timeEnd)
lScrollTime = timeEnd;

    if (lScrollTime == (LONG)GetScrollTime(hwnd))
return;

    SetScrollTime(hwnd, lScrollTime);
    InvalidateRect(hwnd, NULL, TRUE);
    UpdateWindow(hwnd);

    return;
}
// ******************************************************************************
//
//      Message Handler for WM_VSCROLL
//
// ******************************************************************************
//
void App_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
    int nScrollPos;
    RECT rc;

    nScrollPos = GetScrollPos(hwnd, SB_VERT);

    GetClientRect(hwnd, &rc);

    switch (code)
    {
case SB_LINEDOWN:
    nScrollPos += 10;
    break;
case SB_LINEUP:
    nScrollPos -= 10;
    break;
case SB_PAGEDOWN:
    nScrollPos += rc.bottom;
    break;
case SB_PAGEUP:
    nScrollPos -= rc.bottom;
    break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
    nScrollPos = pos;
    break;
    }

    if (nScrollPos < 0)
nScrollPos = 0;

    if (nScrollPos > nVertSBLen)
nScrollPos = nVertSBLen;

    if (nScrollPos == GetScrollPos(hwnd, SB_VERT))
return;

    SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);
    InvalidateRect(hwnd, NULL, TRUE);
    UpdateWindow(hwnd);

    return;
}
// ******************************************************************************
//
//      Message Handler for WM_RBUTTONDOWN
//
// ******************************************************************************
//
void App_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
    RECT rc;
    int i;
    // Invalidate any stream that has something selected.  It needs
    // to redraw now.
    if (gSelectedStream >= MAXNUMSTREAMS) {
if (ghwndEdit)
    EditDone(hwnd, FALSE);
else {
    InvalidateRect(hwnd, &grcSelectedStream, TRUE); // needs to erase
    gSelectedStream = -1;
}
    }
    else {
GetClientRect(hwnd, &rc);
for (i = 0; i < gcpavi; i++) {
     if (galSelStart[i] != -1) {
rc.top = gStreamTop[i] - GetScrollPos(hwnd, SB_VERT);
rc.bottom =gStreamTop[i+1] -GetScrollPos(hwnd, SB_VERT);
InvalidateRect(hwnd, &rc, TRUE); // needs to erase
    }
}

// Deselect everything
SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
    }
    return;
}
// ******************************************************************************
//
//      Message Handler for WM_LBUTTONDOWN
//
// ******************************************************************************
//
void App_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
    int yTop, yBottom = 0, iFrameWidth, nFrames;
    int i,j,n;
    AVISTREAMINFO       avis;
    BOOL fShift = FALSE;
    BOOL fCtrl = FALSE;
    RECT rc, rcC;
    LONG        l, lTime, lSamp, lCurSamp;

    if( fDoubleClick ) {
// See if we get to edit a stream name
if (gSelectedStream >= MAXNUMSTREAMS)
    EditStreamName(hwnd);

return;
    }

    GetClientRect(hwnd, &rc);
    rcC = rc;
    yBottom = -GetScrollPos(hwnd, SB_VERT);     // offset for scrollbar

    // If we currently have a stream name selected, we must
    // deselect it.
    if (gSelectedStream >= MAXNUMSTREAMS) {
// Erase the select marks
if (ghwndEdit)
    EditDone(hwnd, FALSE);
else
    InvalidateRect(hwnd, &grcSelectedStream, TRUE);

// Stream no longer selected.
gSelectedStream = -1;
    }
    else {
//
// Otherwise, if the shift key isn't down,
// we deselect everything first
//
fShift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
fCtrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (!fShift) {
    // Invalidate any stream that has something selected.  It needs
    // to redraw now.
    for (j = 0; j < gcpavi; j++) {
if (galSelStart[j] != -1) {
    rc.top = gStreamTop[j] - GetScrollPos(hwnd, SB_VERT);
    rc.bottom =gStreamTop[j+1] -GetScrollPos(hwnd, SB_VERT);
    InvalidateRect(hwnd, &rc, TRUE); // needs to erase
}
    }
    // Deselect everything
    SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
}
    }

    //
    // Walk the streams and find out where we clicked
    //
    for (i=0; i<gcpavi; i++) {
AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

// See if they clicked on the name
yTop = gStreamTop[i] - GetScrollPos(hwnd, SB_VERT);
yBottom = yTop + (2 * TSPACE);  // !!! size of stream header

if (y >= yTop && y < yBottom) {

    gSelectedStream = MAXNUMSTREAMS+i;  // which stream's header it is
    rc.top = yTop;
    rc.bottom = yBottom;
    grcSelectedStream = rc;     // Invalidate this on button up

    InvalidateRect(hwnd, &rc, FALSE);   // repaint whole strip

    // No need to be here anymore
    return;
}

//*******************************************************************
// See if they clicked on the information Header
yTop = yBottom;
yBottom = yTop + (2 * TSPACE);  // !!! size of stream header

//
// If they've clicked on the header - select the whole stream
//
if (y >= yTop && y < yBottom) {

    // Maybe select everything if Ctrl is held down
    SelectStream(hwnd, i, AVIStreamStart(gapavi[i]),
 AVIStreamEnd(gapavi[i]), FALSE, fCtrl);

    // Tell paint code to highlight the text area.  Invalidate
    // the whole stream area.  When they let go of the mouse,
    // redraw the text area only.
    gSelectedStream = i;        // which stream's header it is
    rc.top = yTop;
    rc.bottom = yBottom;
    grcSelectedStream = rc;     // Invalidate this on button up
    // Now get the area of the whole stream
    rc.bottom = gStreamTop[i + 1] - GetScrollPos(hwnd, SB_VERT);
    InvalidateRect(hwnd, &rc, FALSE);   // repaint whole strip

    // Time to go
    return;
}

// *******************************************************************

//
// Now get the area of the stream data, and...
//
yTop = yBottom;
yBottom = gStreamTop[i + 1] - GetScrollPos(hwnd, SB_VERT);

//
// ... see if we clicked on a video frame, or...
//
if (avis.fccType == streamtypeVIDEO) {
    if (gapgf[i] == NULL)
continue;

    //
    // We're in the vertical range of the strip of video
    //
    if (y >= yTop && y < yBottom) {

rc.top = yTop; rc.bottom = yBottom;
// Time at the centre of the strip
lTime = GetScrollTime(hwnd);
// What frame should appear in the centre? Times that
// are too big will all return the last frame, so we
// need to calculate the hypothetical frame number
if (lTime <= AVIStreamEndTime(gapavi[i])) {
    lSamp = AVIStreamTimeToSample(gapavi[i], lTime);
}
else {
    lSamp = AVIStreamEnd(gapavi[i]) +
    AVIStreamTimeToSample(gapavi[i],
  lTime - AVIStreamEndTime(gapavi[i]));
}
// How wide is each frame?
iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
      gwZoom / 4 + HSPACE;    // !!! hacky constant
// How many frames on each half of centre?
nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
if (nFrames < 0)
    nFrames = 0;        // at least show *something*

//
// Walk all frames and find which one we're on top of
//
for (n = -nFrames; n <= nFrames; n++) {

    rc.left   = rcC.right / 2 -
(avis.rcFrame.right * gwZoom / 4) / 2 +
(n * iFrameWidth);
    rc.right = rc.left + iFrameWidth;

    //
    // We're on top of this frame!
    //
    if (x >= rc.left && x < rc.right) {
//
// For the top video stream, it's easy to tell
// which frame we're on... each frame is
// displayed in order.
//
if (i == giFirstVideo)
    SelectStream(hwnd, i, lSamp + n, 1, fShift, fCtrl);

//
// For other video streams, we need to calculate
// the time of the spot we're on, and see which
// frame is associated with it, because who
// knows what scale we're using for time.
//
else {
    l = lTime + MulDiv32(n * iFrameWidth, gdwMicroSecPerPixel, 1000);
    lCurSamp = AVIStreamTimeToSample( gapavi[i], l);
    SelectStream(hwnd, i, lCurSamp, 1, fShift, fCtrl);
}

//
// Invalidate what we'll be highlighting.
// This includes the text area above.  If
// we're adding to a selection, invalidate the
// whole strip because other frames might
// become selected by this, and the text
// changes.
//
InflateRect(&rc, HSPACE / 2, VSPACE / 2);
if (fShift) {       // could select more than this
    rc.left = 0;
    rc.right = rcC.right;
}
InvalidateRect(hwnd, &rc, FALSE);
// Now invalidate the text area
rc.bottom = rc.top;
rc.top -= (2 * TSPACE); // !!! text changes
rc.left = 0;
rc.right = rcC.right;
// If we've got selection text already, erase
InvalidateRect(hwnd, &rc, fShift);
return;
    }
}//end of inner frame walk for statement
    }
}

// ********************************************************************

//
// ... see if we clicked on an audio section
//
else if (avis.fccType == streamtypeAUDIO) {

    //
    // We clicked inside the wave!
    //
    if (y >= yTop && y < yBottom) {

//
// Get the time we clicked on, and it's sample number
//
GetClientRect(hwnd, &rcC);
lTime = GetScrollTime(hwnd);
if (x < (rcC.right/2)) { //button down to the left of current position
    l = lTime - muldiv32( (rcC.right / 2) - x, gdwMicroSecPerPixel, 1000);
}
else {
    l = lTime + muldiv32(x - rcC.right / 2, gdwMicroSecPerPixel, 1000);
}
lCurSamp = AVIStreamTimeToSample(gapavi[i], l);

SelectStream(hwnd, i, lCurSamp, 1, fShift, fCtrl);

// Invalidate what we'll need to repaint to show it
rc.left = x - 2;        // may not be exact right pixel
rc.right = x + 3;
rc.top = yTop;
rc.bottom = yBottom;
InflateRect(&rc, 0, VSPACE / 2);
// If we're adding to a selection, we better invalidate
// the whole strip.
if (fShift) {
    rc.left = 0;
    rc.right = rcC.right;
}
InvalidateRect(hwnd, &rc, FALSE);
// Now invalidate the header text
rc.bottom = rc.top;
rc.top -= (2 * TSPACE); // !!! text changes
rc.left = 0;
rc.right = rcC.right;
// If we've got selection text already, erase
InvalidateRect(hwnd, &rc, fShift);
    }
}
    }//end of stream walk for statement

    return;
}
// ****************************************************************************
//
//      Message Handler for WM_LBUTTONUP
//
// ****************************************************************************
//
void App_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
{
    //
    // If we're selecting a whole stream, stop highlighting the text area
    //
    if ((gSelectedStream >= 0) && (gSelectedStream < MAXNUMSTREAMS))
    {
InvalidateRect(hwnd, &grcSelectedStream, TRUE);
gSelectedStream = -1;
    }
    return;
}
/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )                              |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|       hwnd            window handle for the window                           |
|       uiMessage       message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LRESULT CALLBACK AppWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    BOOL        f;
    HDC         hdc;

    switch (msg) {

//
// If we passed a command line filename, open it
//
case WM_CREATE:
    return HANDLE_WM_CREATE(hwnd, wParam, lParam, App_OnCreate);

case WM_COMMAND:
    HANDLE_WM_COMMAND(hwnd, wParam, lParam, App_OnCommand);
    break;

case WM_DROPFILES:
    HANDLE_WM_DROPFILES(hwnd, wParam, lParam, App_OnDropFiles);
    break;

case WM_INITMENU:
    HANDLE_WM_INITMENU(hwnd, wParam, lParam, App_OnInitMenu);
    break;

    //
    // During a wait state (eg saving) don't let us choose any menus
    //
case WM_NCHITTEST:

    if (gfWait)
    {
// Let windows tell us where the cursor is
lParam = DefWindowProc(hwnd,msg,wParam,lParam);

// If it's over a menu, pretend it's in the client (force
// hourglass)
if (lParam == HTMENU)
    lParam = HTCLIENT;

return lParam;
    }
    break;
    //
    // Set vertical scrollbar for scrolling streams
    //
case WM_SIZE:
    HANDLE_WM_SIZE(hwnd, wParam, lParam, App_OnSize);
    break;

    //
    // During a wait state, show an hourglass over our client area
    // !!! Is this necessary?
    //
case WM_SETCURSOR:
    if (gfWait && LOWORD(lParam) == HTCLIENT)
    {
SetCursor(LoadCursor(NULL, IDC_WAIT));
return TRUE;
    }
    break;

    //
    // We're out of here!
    //
case WM_DESTROY:
    HANDLE_WM_DESTROY(hwnd, wParam, lParam, App_OnDestroy);\
    break;

case WM_ENDSESSION:
    HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, App_OnEndSession);
    break;

    //
    // Don't let us close ourselves in a wait state (eg saving)
    //
case WM_CLOSE:
    if (gfWait)
return 0;
    break;

    //
    // Block keyboard access to menus if waiting
    //
case WM_SYSCOMMAND:
    switch (wParam & 0xFFF0) {
case SC_KEYMENU:
    if (gfWait)
return 0;
    break;
    }
    break;

case WM_PALETTECHANGED:
    HANDLE_WM_PALETTECHANGED(hwnd, wParam, lParam, App_OnPaletteChanged);
    break;

case WM_QUERYNEWPALETTE:

    if (gfVideoFound) {
hdc = GetDC(hwnd);
//
// Realize the palette of the first video stream
//
if (f = DrawDibRealize(ghdd[giFirstVideo], hdc, FALSE))
    InvalidateRect(hwnd,NULL,TRUE);

ReleaseDC(hwnd,hdc);
return f;
    }
    break;

case WM_ERASEBKGND:
    break;

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

    PaintStuff(hdc, hwnd, FALSE);

    EndPaint(hwnd,&ps);
    break;

//
// handle the keyboard interface
//
case WM_KEYDOWN:
    HANDLE_WM_KEYDOWN(hwnd, wParam, lParam, App_OnKey);
    break;

case WM_HSCROLL:
    HANDLE_WM_HSCROLL(hwnd, wParam, lParam, App_OnHScroll);
    break;

case WM_VSCROLL:
    HANDLE_WM_VSCROLL(hwnd, wParam, lParam, App_OnVScroll);
    break;

//
// Deselect everything
//
case WM_RBUTTONDOWN:
    HANDLE_WM_RBUTTONDOWN(hwnd, wParam, lParam, App_OnRButtonDown);
    break;

//
// Select something
//
case WM_LBUTTONDOWN:
    HANDLE_WM_LBUTTONDOWN(hwnd, wParam, lParam, App_OnLButtonDown);
    break;

case WM_LBUTTONDBLCLK:
    HANDLE_WM_LBUTTONDBLCLK(hwnd, wParam, lParam, App_OnLButtonDown);
    break;

case WM_LBUTTONUP:
    HANDLE_WM_LBUTTONUP(hwnd, wParam, lParam, App_OnLButtonUp);
    break;

//
// Wave driver wants to tell us something.  Pass it on.
//
case MM_WOM_OPEN:
case MM_WOM_DONE:
case MM_WOM_CLOSE:
    aviaudioMessage(hwnd, msg, wParam, lParam);
    break;
    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*----------------------------------------------------------------------------*\
|    SaveCallback()                                                             |
|                                                                               |
|    Our save callback that prints our progress in our window title bar         |
\*----------------------------------------------------------------------------*/
BOOL CALLBACK SaveCallback(int iProgress)
{
    char    szText[3*BUFSIZE];
    char    szFormat[BUFSIZE];

    LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );
    LoadString( ghInstApp, IDS_SAVEFORMAT, szFormat, BUFSIZE );

    wsprintf(szText, szFormat, (LPSTR) gszBuffer, (LPSTR) gszSaveFileName, iProgress);

    SetWindowText(ghwndApp, szText);

    //
    // Give ourselves a chance to abort
    //
    return WinYield();
}

/*----------------------------------------------------------------------------*\
|    GetDlgItemLong()                                                           |
|                                                                               |
|    Get a long integer from a dialog item.                                     |
\*----------------------------------------------------------------------------*/

DWORD GetDlgItemLong (HWND hwnd,  int idCtl, LPINT lpfOK, BOOL fSigned)
{
    LONG        l;
    char        ch;
    BOOL        fNegative = FALSE;
    char        szBuf[64];
    LPSTR       pbuf = szBuf;
    BOOL        fOk;

    fOk = FALSE;
    if (lpfOK)
*lpfOK = FALSE;

    if (!GetDlgItemText(hwnd, idCtl, (LPSTR)szBuf, sizeof(szBuf)-1))
return(0);

    while (*pbuf == ' ') pbuf++;

    if (fSigned && *pbuf == '-')
    {
pbuf++;
fNegative = TRUE;
    }

    l = 0;
    while ((ch = *pbuf++) >= '0' && ch <= '9')
    {
fOk = TRUE;
if (l > (DWORD)(ULONG_MAX/10))
    return(0);
l = (l * 10) + ch - '0';
if (fSigned && l > (DWORD)(ULONG_MAX/2))
    return(0);
    }

    if (fNegative)
l = -l;

    if (lpfOK)
*lpfOK = (ch == 0 && fOk);

    return(l);
}

/*----------------------------------------------------------------------------*\
|    SetDlgItemLong()                                                           |
|                                                                               |
|    Put a long integer into a dialog item.                                     |
\*----------------------------------------------------------------------------*/

void SetDlgItemLong (HWND hwnd, int idCtl, DWORD dwValue, BOOL fSigned )
{
    char szBuf[64];

    wsprintf (szBuf, fSigned ? "%ld" : "%lu", dwValue);
    SetDlgItemText(hwnd, idCtl, szBuf);
}

/*----------------------------------------------------------------------------*\
|    DoDataExchange()                                                           |
|                                                                               |
|    Exchange data between our internal buffer and dialog controls              |
|    fDir = TRUE for DialogBox->Buffer                                          |
|         = FALSE for Buffer->DialogBox                                         |
\*----------------------------------------------------------------------------*/

void DoDataExchange (HWND hwnd, LPAVISTREAMINFO lpinfo, BOOL fDir)
{
    if (fDir) {
lpinfo->wPriority       = (WORD)GetDlgItemInt (hwnd, IDC_PRIORITY,    NULL, FALSE);
lpinfo->wLanguage       = (WORD)GetDlgItemInt (hwnd, IDC_LANGUAGE,    NULL, FALSE);
lpinfo->dwScale         = GetDlgItemLong(hwnd, IDC_SCALE,       NULL, FALSE);
lpinfo->dwRate          = GetDlgItemLong(hwnd, IDC_RATE,        NULL, FALSE);
lpinfo->dwStart         = GetDlgItemLong(hwnd, IDC_START,       NULL, FALSE);
lpinfo->dwQuality       = GetDlgItemLong(hwnd, IDC_QUALITY,     NULL, FALSE);
lpinfo->rcFrame.top     = GetDlgItemInt (hwnd, IDC_FRAMETOP,    NULL, TRUE);
lpinfo->rcFrame.bottom  = GetDlgItemInt (hwnd, IDC_FRAMEBOTTOM, NULL, TRUE);
lpinfo->rcFrame.left    = GetDlgItemInt (hwnd, IDC_FRAMELEFT,   NULL, TRUE);
lpinfo->rcFrame.right   = GetDlgItemInt (hwnd, IDC_FRAMERIGHT,  NULL, TRUE);
GetDlgItemText(hwnd, IDC_NAME, lpinfo->szName, sizeof(lpinfo->szName)-1);
    }
    else
    {
SetDlgItemInt (hwnd, IDC_PRIORITY,    lpinfo->wPriority,       FALSE);
SetDlgItemInt (hwnd, IDC_LANGUAGE,    lpinfo->wLanguage,       FALSE);
SetDlgItemLong(hwnd, IDC_SCALE,       lpinfo->dwScale,         FALSE);
SetDlgItemLong(hwnd, IDC_RATE,        lpinfo->dwRate,          FALSE);
SetDlgItemLong(hwnd, IDC_START,       lpinfo->dwStart,         FALSE);
SetDlgItemLong(hwnd, IDC_QUALITY,     lpinfo->dwQuality,       FALSE);
SetDlgItemLong(hwnd, IDC_FRAMETOP,    lpinfo->rcFrame.top,     TRUE);
SetDlgItemLong(hwnd, IDC_FRAMEBOTTOM, lpinfo->rcFrame.bottom,  TRUE);
SetDlgItemLong(hwnd, IDC_FRAMELEFT,   lpinfo->rcFrame.left,    TRUE);
SetDlgItemLong(hwnd, IDC_FRAMERIGHT,  lpinfo->rcFrame.right,   TRUE);
SetDlgItemText(hwnd, IDC_NAME, lpinfo->szName);
    }
}

// *****************************************************************************
//
//      FUNCTION: Dlg_DefProc(HWND, UINT, WPARAM, LPARAM)
//
//      PURPOSE:  Handles default messages for all dialog boxes
//
//
static LRESULT Dlg_DefProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    return DefDlgProcEx( hDlg, message, wParam, lParam, &gfDefDlgEx );
}
//
// *****************************************************************************
//
//      FUNCTION: InfoDlg_OnInitDialog(HWND, HWND, LPARAM)
//
//      PURPOSE:  Handles initialization for dialog box
//
//
static BOOL InfoDlg_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam)
{
    int i;
    glpavisi = (LPAVISTREAMINFO)
       GlobalAllocPtr (GHND, sizeof(AVISTREAMINFO)*gcpavi);

    for ( i = 0; i < gcpavi; ++i)
    {
AVIStreamInfo(gapavi[i], &glpavisi[i], sizeof(AVISTREAMINFO));
SendDlgItemMessage(hDlg, IDC_STREAMS, CB_ADDSTRING, 0, (LPARAM)(LPSTR)glpavisi[i].szName);
    }
    SendDlgItemMessage(hDlg, IDC_STREAMS, CB_SETCURSEL, 0, 0);
    gnSel = 0;
    DoDataExchange(hDlg, &glpavisi[0], FALSE);

    return (FALSE);
}
//
// *****************************************************************************
//
//      FUNCTION: InfoDlg_OnCommand(HWND, HWND, UINT)
//
//      PURPOSE:  Handles the child controls for dialog box
//
//      DIALOGBOX ID'S
//
//              IDOK            - ok button
//              IDCANCEL        - cancel button
//      IDC_STREAMS             - the dropdown listbox
//
static void InfoDlg_OnCommand(HWND hDlg, int control_source, HWND control_handle, UINT control_action )
{

    switch (control_source)
    {
case IDC_STREAMS:
{
    if (control_action != CBN_SELCHANGE)
break;
    if (gnSel != CB_ERR)
DoDataExchange (hDlg, &glpavisi[gnSel], TRUE);
    gnSel = (int)SendDlgItemMessage(hDlg, IDC_STREAMS, CB_GETCURSEL, 0, 0);
    if (gnSel != CB_ERR)
DoDataExchange(hDlg, &glpavisi[gnSel], FALSE);
}
break;

case IDOK:
case IDCANCEL:
{
    BOOL fOk = (control_source == IDOK);
    int i,ix;

    if (fOk)
    {
ix = (int)SendDlgItemMessage(hDlg, IDC_STREAMS, CB_GETCURSEL, 0, 0);
if (ix != CB_ERR)
    DoDataExchange(hDlg, &glpavisi[ix], TRUE);

for (i = 0; i < gcpavi; ++i)
    EditStreamSetInfo(gapavi[i], &glpavisi[i], sizeof(AVISTREAMINFO));
    }
    GlobalFreePtr(glpavisi);
    EndDialog(hDlg, fOk);
}
break;

    }
    return;
}
//
// *****************************************************************************
//
//      FUNCTION: SetInfoNewDlgProc(HWND, UINT, WPARAM, LPARAM)

//
//      PURPOSE:  Processes messages for dialog box using message crackers
//
//      MESSAGES:
//
//              WM_INITDIALOG   - initialize dialog box
//              WM_COMMAND              - process user input
//
// *****************************************************************************
//
static LRESULT SetInfoNewDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
HANDLE_MSG( hDlg, WM_INITDIALOG,            InfoDlg_OnInitDialog            );
HANDLE_MSG( hDlg, WM_COMMAND,                       InfoDlg_OnCommand               );
default:
    return Dlg_DefProc( hDlg, message, wParam, lParam );
    }
}
//
//
// *****************************************************************************
//
//      FUNCTION: SetInfoDlgProc(HWND, UINT, WPARAM, LPARAM)
//
//      PURPOSE:  Processes messages for info dialog box
//
//
BOOL CALLBACK SetInfoDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    CheckDefDlgRecursion( &gfDefDlgEx );
    return SetDlgMsgResult( hDlg, message, SetInfoNewDlgProc( hDlg, message, wParam, lParam ) );
}

/*----------------------------------------------------------------------------*\
|    SetStreamInfo()                                                            |
|                                                                               |
|    Bring up the dialog to allow setting the stream info                       |
\*----------------------------------------------------------------------------*/

BOOL SetStreamInfo (HWND hwnd)
{
    return(DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_STREAMINFO), hwnd, (DLGPROC)SetInfoDlgProc));
}

/*----------------------------------------------------------------------------*\
|    editPaste()                                                                |
|                                                                               |
|    PASTE the streams in this PFILE into our movie using the following logic:  |
|                                                                               |
|    Take a stream from the clipboard.  If you can find a similar type stream   |
|    in the app with a selection, paste it in before the selection.  If no      |
|    such stream exists, add it to the end.                                     |
\*----------------------------------------------------------------------------*/

void editPaste(HWND hwnd, PAVIFILE pfile)
{
    int         i, j, nVideo = 0, nAudio = 0, nStream;
    LONG        l;
    PAVISTREAM  pavi;
    AVISTREAMINFO avisClip, avis;

    FreeDrawStuff(hwnd);

    for (i=0; i<MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i) != AVIERR_OK)
    break;
AVIStreamInfo(pavi, &avisClip, sizeof(avisClip));
nStream = (avisClip.fccType == streamtypeVIDEO) ? nVideo : nAudio;
for (j=nStream; j<gcpavi; j++) {
    AVIStreamInfo(gapavi[j], &avis, sizeof(avis));
    if (avis.fccType == avisClip.fccType && galSelStart[j] != -1) {
l = AVIStreamLength(pavi);
if (EditStreamPaste(gapavi[j], &galSelStart[j], &l,
    pavi, AVIStreamStart(pavi), AVIStreamLength(pavi)) !=
    AVIERR_OK)
{
    LoadString( ghInstApp, IDS_NOPASTE, gszBuffer, BUFSIZE );
    ErrMsg(gszBuffer);
}
galSelLen[j] = AVIStreamLength(pavi);
break;
    }
}
if (j == gcpavi) {
    galSelStart[j] = AVIStreamStart(pavi);
    galSelLen[j] = AVIStreamLength(pavi);
    if (CreateEditableStream(&gapavi[j], pavi) != AVIERR_OK)
    {
LoadString( ghInstApp, IDS_PASTEERROR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
AVIStreamRelease(pavi);
break;
    }
    AVIStreamRelease(pavi);
    gcpavi++;
}
if (avisClip.fccType == streamtypeVIDEO)
    nVideo = ++j;
else
    nAudio = ++j;
    }
    AVIFileRelease(pfile);
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}
/*----------------------------------------------------------------------------*\
|    MenuHandler()                                                              |
|                                                                               |
|    Process all of our Menu messages.                                  |
\*----------------------------------------------------------------------------*/
BOOL MenuHandler( HWND hwnd, int nMenuID )
{
    OPENFILENAME ofn;

    switch(nMenuID)
    {
case MENU_ABOUT:
    //
    // Display an informative dialog box.
    //
    DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, (DLGPROC)AboutDlgProc);
    break;
    //
    // We want out of here!
    //
case MENU_EXIT:
    PostMessage(hwnd,WM_CLOSE,0,0L);
    break;

    //
    // Set the compression options for each stream - pass an array of
    // streams and an array of compression options structures
    //
case MENU_OPTIONS:
    AVISaveOptions(hwnd,
   ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE |
   ICMF_CHOOSE_PREVIEW,
   gcpavi, gapavi, galpAVIOptions);
    break;

    //
    // Save all the open streams into a file
    //
case MENU_SAVEAS:

    gszSaveFileName[0] = 0;

    //
    // prompt user for file to save
    //
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.hInstance = NULL;
    AVIBuildFilter(gszFilter, sizeof(gszFilter), TRUE);
    ofn.lpstrFilter = gszFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = gszSaveFileName;
    ofn.nMaxFile = sizeof(gszSaveFileName);
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    LoadString( ghInstApp, IDS_SAVETITLE, gszBuffer, BUFSIZE );
    ofn.lpstrTitle = gszBuffer;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    LoadString( ghInstApp, IDS_DEFEXT, gszBuffer, BUFSIZE );
    ofn.lpstrDefExt = gszBuffer;
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    //
    // If we get a filename, save it
    //
    if (GetSaveFileName(&ofn))
    {
    DWORD       fccHandler[MAXNUMSTREAMS];
    int         i;
    HRESULT     hr;

    StartWait();

    for (i = 0; i < gcpavi; i++)
fccHandler[i] = galpAVIOptions[i]->fccHandler;

    hr = AVISaveV(gszSaveFileName,
  NULL,
      (AVISAVECALLBACK) SaveCallback,
  gcpavi,
  gapavi,
  galpAVIOptions);
if (hr != AVIERR_OK) {
    switch (hr) {
    case AVIERR_FILEOPEN:
LoadString( ghInstApp, IDS_ERROVERWRITE, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
break;
    default:
LoadString( ghInstApp, IDS_SAVEERROR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
    }
}
    // Now put the video compressors back that we stole
    for (i = 0; i < gcpavi; i++)
galpAVIOptions[i]->fccHandler = fccHandler[i];

    EndWait();
    FixWindowTitle(hwnd);
    }
    break;

    //
    // Close everything
    //
case MENU_CLOSE:
    FreeAvi(hwnd);
    gszFileName[0] = '\0';
    FixWindowTitle(hwnd);
    break;

    //
    // Open a new file, or merge streams with a new file
    //
case MENU_OPEN:
case MENU_MERGE:
    gszFileName[0] = 0;

    //
    // prompt user for file to open
    //
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.hInstance = NULL;
    if (nMenuID == MENU_MERGE)
LoadString( ghInstApp, IDS_MERGETITLE, gszBuffer, BUFSIZE );
    else
LoadString( ghInstApp, IDS_OPENTITLE, gszBuffer, BUFSIZE );
    ofn.lpstrTitle = gszBuffer;
    AVIBuildFilter(gszFilter, sizeof(gszFilter), FALSE);
    ofn.lpstrFilter = gszFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = gszFileName;
    ofn.nMaxFile = sizeof(gszFileName);
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST |OFN_HIDEREADONLY;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    //
    // If we've got a filename, go open it
    //
    if (GetOpenFileNamePreview(&ofn))
InitAvi(hwnd, gszFileName, nMenuID);

    break;

case MENU_ZOOMQUARTER:
    gwZoom = 1;
    FixScrollbars(hwnd);
    InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOMHALF:
    gwZoom = 2;
    FixScrollbars(hwnd);
    InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM1:
    gwZoom = 4;
    FixScrollbars(hwnd);
    InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM2:
    gwZoom = 8;
    FixScrollbars(hwnd);
    InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM4:
    gwZoom = 16;
    FixScrollbars(hwnd);
    InvalidateRect(hwnd, NULL, TRUE);
    break;

//
// Simulate playing the file.  We just play the 1st audio stream and let
// our main message loop scroll the video by whenever it's bored.
//
case MENU_PREVIEW:
    if (gfAudioFound)
aviaudioPlay(hwnd,
     gapavi[giFirstAudio],
     AVIStreamTimeToSample(gapavi[giFirstAudio], GetScrollTime(hwnd)),
     AVIStreamEnd(gapavi[giFirstAudio]),
     FALSE);
    gfPlaying = TRUE;
    glPlayStartTime = timeGetTime();
    glPlayStartPos = GetScrollTime(hwnd);
    break;

    //
    // Stop the play preview
    //
case MENU_STOP:
    if (gfAudioFound)
aviaudioStop();
    gfPlaying = FALSE;
    break;

case MENU_SETINFO:
    if (SetStreamInfo(hwnd))
    {
FreeDrawStuff(hwnd);        // !!! in order to call InitStreams
InitStreams(hwnd);          // !!! Nukes COMP options
InvalidateRect(hwnd, NULL, TRUE);
    }
    break;

case MENU_CUT:
case MENU_COPY:
case MENU_DELETE:
{
    PAVIFILE    pf;
    int         i;

    //
    // Walk our list of selections and make streams out of each section
    //
    gcpaviSel = 0;
    for (i = 0; i < gcpavi; i++) {
if (galSelStart[i] != -1) {
    // !!! What if the start and length change?
    if (nMenuID == MENU_COPY) {
if (EditStreamCopy(gapavi[i], &galSelStart[i],
   &galSelLen[i], &gapaviSel[gcpaviSel++]) != 0) {
    --gcpaviSel;
    LoadString( ghInstApp, IDS_STRCPYERR, gszBuffer, BUFSIZE );
    ErrMsg(gszBuffer);
}
    } else {
if (EditStreamCut(gapavi[i], &galSelStart[i],
  &galSelLen[i], &gapaviSel[gcpaviSel++]) != 0) {
    --gcpaviSel;
    LoadString( ghInstApp, IDS_STRCUTERR, gszBuffer, BUFSIZE );
    ErrMsg(gszBuffer);
}
    }
}
    }

    for (i = gcpavi - 1; i >= 0; i--) {
// Check to see if any stream is entirely gone now....
if (AVIStreamLength(gapavi[i]) == 0) {
    NukeAVIStream(i);
}
    }

    //
    // Put the selected stuff up on the clipboard
    //
    if (gcpaviSel && nMenuID != MENU_DELETE) {
PAVISTREAM          gapaviTemp[MAXNUMSTREAMS];
int i;

//
// Clone the edited streams, so that if the user does
// more editing, the thing on the clipboard won't
// suddenly change....
//
for (i = 0; i < gcpaviSel; i++) {
    gapaviTemp[i] = NULL;
    // !!! error check
    EditStreamClone(gapaviSel[i], &gapaviTemp[i]);
}

AVIMakeFileFromStreams(&pf, gcpaviSel, gapaviTemp);
if (AVIPutFileOnClipboard(pf) != AVIERR_OK)
{
    LoadString( ghInstApp, IDS_NOCLIP, gszBuffer, BUFSIZE );
    ErrMsg(gszBuffer);
}
for (i = 0; i < gcpaviSel; i++) {
    AVIStreamRelease(gapaviTemp[i]);
}

AVIFileRelease(pf);
    }

    for (i = 0; i < gcpaviSel; i++)
AVIStreamRelease(gapaviSel[i]);

    //
    // If we cut out the selections, then they don't exist anymore.
    //
    if (gcpaviSel && (nMenuID == MENU_DELETE || nMenuID == MENU_CUT)) {
SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
    }

    //
    // We just changed the world!
    //
    FreeDrawStuff(hwnd);        // !!! in order to call InitStreams
    InitStreams(hwnd);          // !!! Nukes COMP options
    InvalidateRect(hwnd, NULL, TRUE);

    break;
}

case MENU_PASTE:
{
    PAVIFILE pf;

    AVIGetFromClipboard(&pf);

    if (pf) {
editPaste(hwnd, pf);
    }
    break;
}
case MENU_NAME:
    if (ghwndEdit == NULL)
EditStreamName(hwnd);
    else
EditDone(hwnd, FALSE);

    break;
    }
    return TRUE;
}
/*-----------------------------------------------------------------------------
 * FrameVideo()
 *
 * Puts a border around a video frame, the size of a selection
 */

void FrameVideo(HDC hdc, RECT *rcFrame, HBRUSH hbr)
{
   RECT rcTop,rcBottom,rcLeft,rcRight;
   HBRUSH hbrOld;

   // Calculate 4 rectangles, which 'frame' rcFrame.
   rcTop.left = rcFrame->left - HSPACE/2;
   rcTop.top = rcFrame->top - SELECTVSPACE;
   rcTop.right = rcFrame->right + HSPACE/2;
   rcTop.bottom = rcFrame->top;
   rcLeft.left = rcFrame->left - HSPACE/2;
   rcLeft.top = rcFrame->top - SELECTVSPACE;
   rcLeft.right = rcFrame->left;
   rcLeft.bottom = rcFrame->bottom + SELECTVSPACE;
   rcRight.left = rcFrame->right;
   rcRight.top = rcFrame->top - SELECTVSPACE;
   rcRight.right = rcFrame->right + HSPACE/2;
   rcRight.bottom = rcFrame->bottom + SELECTVSPACE;
   rcBottom.left = rcFrame->left - HSPACE/2;
   rcBottom.top = rcFrame->bottom;
   rcBottom.right = rcFrame->right + HSPACE/2;
   rcBottom.bottom = rcFrame->bottom + SELECTVSPACE;

   // Now put each rectangle on screen
   hbrOld = SelectObject(hdc, hbr);
   PatBlt(hdc, rcTop.left, rcTop.top, rcTop.right - rcTop.left, rcTop.bottom - rcTop.top, PATCOPY);
   PatBlt(hdc, rcLeft.left, rcLeft.top, rcLeft.right - rcLeft.left, rcLeft.bottom - rcLeft.top, PATCOPY);
   PatBlt(hdc, rcRight.left, rcRight.top, rcRight.right - rcRight.left, rcRight.bottom - rcRight.top, PATCOPY);
   PatBlt(hdc, rcBottom.left, rcBottom.top, rcBottom.right - rcBottom.left, rcBottom.bottom - rcBottom.top, PATCOPY);
   SelectObject(hdc, hbrOld);

}

/*----------------------------------------------------------------------------*\
|   ErrMsg()                                                                    |
|                                                                               |
|   Opens a Message box with a error message in it.  The user can               |
|   select the OK button to continue                                            |
\*----------------------------------------------------------------------------*/

int ErrMsg (LPSTR sz,...)
{
    static char szOutput[4*BUFSIZE];

    va_list va;

    va_start(va, sz);
    wvsprintf (szOutput,sz,va);      /* Format the string */
    va_end(va);
    MessageBox(NULL,szOutput,NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
    return FALSE;
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(
  HWND  hwnd,
  UINT  msg,
  WPARAM        wParam,
  LPARAM        lParam)
{
    switch (msg) {
case WM_COMMAND:
    EndDialog(hwnd, TRUE);
    return TRUE;
case WM_INITDIALOG:
    return TRUE;
    }
    return FALSE;
}


AVIEDIT.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define ID_APP      1000

/* Menu Items */
#define MENU_EXIT           4
#define MENU_ABOUT          5
#define MENU_OPEN           11
#define MENU_CLOSE          10
#define MENU_MERGE          17
#define MENU_SAVEAS         13
#define MENU_OPTIONS        15
#define MENU_UNDO           18
#define MENU_CUT            19
#define MENU_COPY           20
#define MENU_PASTE          21
#define MENU_DELETE         22
#define MENU_NAME           23
#define MENU_SETINFO        24

#define MENU_ZOOMQUARTER    54
#define MENU_ZOOMHALF       50
#define MENU_ZOOM1          51
#define MENU_ZOOM2          52
#define MENU_ZOOM4          53

#define MENU_PREVIEW        102
#define MENU_STOP           103

#define IDD_STREAMINFO      101
#define IDD_ABOUT           102
#define IDC_STREAMS         1000
#define IDC_PRIORITY        1001
#define IDC_LANGUAGE        1002
#define IDC_SCALE           1003
#define IDC_RATE            1004
#define IDC_START           1005
#define IDC_QUALITY         1006
#define IDC_NAME            1007
#define IDC_FRAMETOP        1008
#define IDC_FRAMELEFT       1009
#define IDC_FRAMEBOTTOM     1010
#define IDC_FRAMERIGHT      1011
#define IDC_STATIC          -1

/* resource string ID's */
#define IDS_APPNAME         1
#define IDS_DEFEXT          2
#define IDS_SAVETITLE       3
#define IDS_APPERR          4
#define IDS_OLDVFW          5
#define IDS_MAXSTREAMS      6
#define IDS_SAVEFORMAT      7
#define IDS_NOOPEN          8
#define IDS_NOPASTE         9
#define IDS_PASTEERROR      10
#define IDS_NODRAW          11
#define IDS_SAVEERROR       12
#define IDS_OPENTITLE       13
#define IDS_MERGETITLE      14
#define IDS_STRCPYERR       15
#define IDS_STRCUTERR       16
#define IDS_NOCLIP          17
#define IDS_FORMAT_1        18
#define IDS_FORMAT_2        19
#define IDS_NAME            20
#define IDS_ABORTNAME       21
#define IDS_ERROVERWRITE    22


AUDIO.C

/****************************************************************************
 *
 *  AUDIO.C
 *
 *  Simple routines to play audio using an AVIStream to get data.
 *
 *  Uses global variables, so only one instance at a time.
 *  (Usually, there's only one sound card, so this isn't so bad.
 *
 **************************************************************************/

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define STRICT
#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "muldiv32.h"
#include <vfw.h>
#include "audio.h"

/*--------------------------------------------------------------+
| ****************** AUDIO PLAYING SUPPORT ******************** |
+--------------------------------------------------------------*/

static  HWAVEOUT        shWaveOut = 0;  /* Current MCI device ID */
static  LONG            slBegin;
static  LONG            slCurrent;
static  LONG            slEnd;
static  BOOL            sfLooping;
static  BOOL            sfPlaying = FALSE;

#define MAX_AUDIO_BUFFERS       16
#define MIN_AUDIO_BUFFERS       2
#define AUDIO_BUFFER_SIZE       16384

static  WORD            swBuffers;          // total # buffers
static  WORD            swBuffersOut;       // buffers device has
static  WORD            swNextBuffer;       // next buffer to fill
static  LPWAVEHDR       salpAudioBuf[MAX_AUDIO_BUFFERS];

static  PAVISTREAM      spavi;              // stream we're playing
static  LONG            slSampleSize;       // size of an audio sample

static  LONG            sdwBytesPerSec;
static  LONG            sdwSamplesPerSec;

/*---------------------------------------------------------------+
| aviaudioCloseDevice -- close the open audio device, if any.    |
+---------------------------------------------------------------*/
void aviaudioCloseDevice(void)
{
    if (shWaveOut)
    {
while (swBuffers > 0)
{
--swBuffers;
waveOutUnprepareHeader(shWaveOut, salpAudioBuf[swBuffers],
sizeof(WAVEHDR));
GlobalFreePtr((LPSTR) salpAudioBuf[swBuffers]);
}
waveOutClose(shWaveOut);

shWaveOut = NULL;
    }
}

/*--------------------------------------------------------------+
| aviaudioOpenDevice -- get ready to play waveform data.        |
+--------------------------------------------------------------*/
BOOL CALLBACK aviaudioOpenDevice(HWND hwnd, PAVISTREAM pavi)
{
    MMRESULT            mmResult;
    LPVOID              lpFormat;
    LONG                cbFormat;
    AVISTREAMINFO       strhdr;

    if (!pavi)          // no wave data to play
return FALSE;

    if (shWaveOut)      // already something playing
return TRUE;

    spavi = pavi;

    AVIStreamInfo(pavi, &strhdr, sizeof(strhdr));

    slSampleSize = (LONG) strhdr.dwSampleSize;
    if (slSampleSize <= 0 || slSampleSize > AUDIO_BUFFER_SIZE)
return FALSE;

    AVIStreamFormatSize(pavi, 0, &cbFormat);

    lpFormat = GlobalAllocPtr(GHND, cbFormat);
    if (!lpFormat)
return FALSE;

    AVIStreamReadFormat(pavi, 0, lpFormat, &cbFormat);

    sdwSamplesPerSec = ((LPWAVEFORMAT) lpFormat)->nSamplesPerSec;
    sdwBytesPerSec = ((LPWAVEFORMAT) lpFormat)->nAvgBytesPerSec;

    mmResult = waveOutOpen(&shWaveOut, (UINT)WAVE_MAPPER, lpFormat,
(DWORD) (UINT) hwnd, 0L, CALLBACK_WINDOW);
    //
    // Maybe we failed because someone is playing sound already.
    // Shut any sound off, and try once more before giving up.
    //
    if (mmResult) {
sndPlaySound(NULL, 0);
mmResult = waveOutOpen(&shWaveOut, (UINT)WAVE_MAPPER, lpFormat,
(DWORD) (UINT)hwnd, 0L, CALLBACK_WINDOW);
    }

    if (mmResult != 0)
    {
/* Show error message here? */
return FALSE;
    }

    for (swBuffers = 0; swBuffers < MAX_AUDIO_BUFFERS; swBuffers++)
    {
if (!(salpAudioBuf[swBuffers] =
(LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
(DWORD)(sizeof(WAVEHDR) + AUDIO_BUFFER_SIZE))))
break;
salpAudioBuf[swBuffers]->dwFlags = WHDR_DONE;
salpAudioBuf[swBuffers]->lpData = (LPSTR) salpAudioBuf[swBuffers]
    + sizeof(WAVEHDR);
salpAudioBuf[swBuffers]->dwBufferLength = AUDIO_BUFFER_SIZE;
if (!waveOutPrepareHeader(shWaveOut, salpAudioBuf[swBuffers],
sizeof(WAVEHDR)))
continue;

GlobalFreePtr((LPSTR) salpAudioBuf[swBuffers]);
break;
    }

    if (swBuffers < MIN_AUDIO_BUFFERS)
    {
aviaudioCloseDevice();
return FALSE;
    }

    swBuffersOut = 0;
    swNextBuffer = 0;

    sfPlaying = FALSE;

    return TRUE;
}

/*--------------------------------------------------------------+
| aviaudioTime -                                                |
| Return the time in milliseconds corresponding to the          |
| currently playing audio sample, or -1 if no audio is playing. |
|                                                               |
| WARNING: Some sound cards are pretty inaccurate!              |
+--------------------------------------------------------------*/
LONG CALLBACK aviaudioTime(void)
{
    MMTIME      mmtime;

    if (!sfPlaying)
return -1;

    mmtime.wType = TIME_SAMPLES;

    waveOutGetPosition(shWaveOut, &mmtime, sizeof(mmtime));

    if (mmtime.wType == TIME_SAMPLES)
return AVIStreamSampleToTime(spavi, slBegin)
+ muldiv32(mmtime.u.sample, 1000, sdwSamplesPerSec);
    else if (mmtime.wType == TIME_BYTES)
return AVIStreamSampleToTime(spavi, slBegin)
+ muldiv32(mmtime.u.cb, 1000, sdwBytesPerSec);
    else
return -1;
}

/*--------------------------------------------------------------+
| aviaudioiFillBuffers -                                        |
| Fill up any empty audio buffers and ship them out to the      |
| device.                                                       |
+--------------------------------------------------------------*/
BOOL aviaudioiFillBuffers(void)
{
    LONG                lRead;
MMRESULT        mmResult;
    LONG                lSamplesToPlay;

    /* We're not playing, so do nothing. */
    if (!sfPlaying)
return TRUE;

    while (swBuffersOut < swBuffers)
    {
if (slCurrent >= slEnd)
{
if (sfLooping)
{
/* Looping, so go to the beginning. */
slCurrent = slBegin;
}
    else
break;
}

/* Figure out how much data should go in this buffer */
lSamplesToPlay = slEnd - slCurrent;
if (lSamplesToPlay > AUDIO_BUFFER_SIZE / slSampleSize)
    lSamplesToPlay = AUDIO_BUFFER_SIZE / slSampleSize;

AVIStreamRead(spavi, slCurrent, lSamplesToPlay,
      salpAudioBuf[swNextBuffer]->lpData,
      AUDIO_BUFFER_SIZE,
      (LPVOID)&salpAudioBuf[swNextBuffer]->dwBufferLength,
      &lRead);

if (lRead != lSamplesToPlay)
    return FALSE;

slCurrent += lRead;

mmResult = waveOutWrite(shWaveOut, salpAudioBuf[swNextBuffer],sizeof(WAVEHDR));

if (mmResult != 0)
    return FALSE;

++swBuffersOut;
++swNextBuffer;
if (swNextBuffer >= swBuffers)
    swNextBuffer = 0;
    }//while

    if (swBuffersOut == 0 && slCurrent >= slEnd)
aviaudioStop();

    /* We've filled all of the buffers we can or want to. */
    return TRUE;
}

/*--------------------------------------------------------------+
| aviaudioPlay -- Play audio, starting at a given frame         |
|                                                               |
+--------------------------------------------------------------*/
BOOL CALLBACK aviaudioPlay(HWND hwnd, PAVISTREAM pavi, LONG lStart, LONG lEnd, BOOL fWait)
{
    if (lStart < 0)
lStart = AVIStreamStart(pavi);

    if (lEnd < 0)
lEnd = AVIStreamEnd(pavi);

    if (lStart >= lEnd)
return FALSE;

    if (!aviaudioOpenDevice(hwnd, pavi))
return FALSE;

    if (!sfPlaying)
    {

// We're beginning play, so pause until we've filled the buffers
// for a seamless start
//
waveOutPause(shWaveOut);

slBegin = lStart;
slCurrent = lStart;
slEnd = lEnd;
sfPlaying = TRUE;
    }
    else
    {
slEnd = lEnd;
    }

    aviaudioiFillBuffers();

    //
    // Now unpause the audio and away it goes!
    //
    waveOutRestart(shWaveOut);

    //
    // Caller wants us not to return until play is finished
    //
    if(fWait)
    {
while (swBuffersOut > 0)
Yield();
    }

    return TRUE;
}

/*--------------------------------------------------------------------------------+
| aviaudioMessage                                                                 |
|                                                                                 |
| The WaveProc callback function - Handles wave messages recieved by the          |
| window controlling playback.                                                    |
+--------------------------------------------------------------------------------*/
void CALLBACK aviaudioMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == MM_WOM_DONE) {
        --swBuffersOut;
        aviaudioiFillBuffers();
    }
}

/*--------------------------------------------------------------+
| aviaudioStop -- stop playing, close the device.               |
+--------------------------------------------------------------*/
void CALLBACK aviaudioStop(void)
{
MMRESULT        mmResult;

    if (shWaveOut != 0)
    {

mmResult = waveOutReset(shWaveOut);

sfPlaying = FALSE;

aviaudioCloseDevice();
    }
}


README.TXT

The AVIVIEW sample is a simple AVI viewing application using the read/write APIs in AVIFile.


AUDPLAY.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

BOOL aviaudioPlay(HWND hwnd, PAVISTREAM pavi, LONG lStart, LONG lEnd, BOOL fWait);
void aviaudioMessage(HWND hwnd, unsigned msg, WPARAM wParam, LONG lParam);
void aviaudioStop(void);
LONG aviaudioTime(void);


AVIBALL.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  AVIBALL.C
 *
 *  Sample AVIStream handler for a bouncing ball.  This code demonstrates
 *  how to write a custom stream handler so an application can deal with
 *  your custom file/data/whatever by using the standard AVIStream functions.
 *
***************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <vfw.h>

#include "aviball.h"

///////////////////////////////////////////////////////////////////////////
//
// silly default parameters
//
///////////////////////////////////////////////////////////////////////////

#define DEFAULT_WIDTH   240
#define DEFAULT_HEIGHT  120
#define DEFAULT_LENGTH  100
#define DEFAULT_SIZE    6
#define DEFAULT_COLOR   RGB(255,0,0)
#define XSPEED7
#define YSPEED5

///////////////////////////////////////////////////////////////////////////
//
// useful macros
//
///////////////////////////////////////////////////////////////////////////

#define ALIGNULONG(i)     ((i+3)&(~3))                  /* ULONG aligned ! */
#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define DIBPTR(lpbi) ((LPBYTE)(lpbi) + \
    (int)(lpbi)->biSize + \
    (int)(lpbi)->biClrUsed * sizeof(RGBQUAD) )

///////////////////////////////////////////////////////////////////////////
//
// custom video stream instance structure
//
///////////////////////////////////////////////////////////////////////////

typedef struct {

    //
    // The Vtbl must come first
    //
    IAVIStreamVtbl * lpvtbl;

    //
    //  private ball instance data
    //
    ULONGulRefCount;

    DWORD       fccType;        // is this audio/video

    int         width;          // size in pixels of each frame
    int         height;
    int         length;         // length in frames of the pretend AVI movie
    int         size;
    COLORREF    color;          // ball color

} AVIBALL, * PAVIBALL;

///////////////////////////////////////////////////////////////////////////
//
// custom stream methods
//
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE AVIBallQueryInterface(PAVISTREAM ps, REFIID riid, LPVOID * ppvObj);
HRESULT STDMETHODCALLTYPE AVIBallCreate       (PAVISTREAM ps, LONG lParam1, LONG lParam2);
ULONG   STDMETHODCALLTYPE AVIBallAddRef       (PAVISTREAM ps);
ULONG   STDMETHODCALLTYPE AVIBallRelease      (PAVISTREAM ps);
HRESULT STDMETHODCALLTYPE AVIBallInfo         (PAVISTREAM ps, AVISTREAMINFOW * psi, LONG lSize);
LONG    STDMETHODCALLTYPE AVIBallFindSample (PAVISTREAM ps, LONG lPos, LONG lFlags);
HRESULT STDMETHODCALLTYPE AVIBallReadFormat   (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG *lpcbFormat);
HRESULT STDMETHODCALLTYPE AVIBallSetFormat    (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG cbFormat);
HRESULT STDMETHODCALLTYPE AVIBallRead         (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG * plBytes,LONG * plSamples);
HRESULT STDMETHODCALLTYPE AVIBallWrite        (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG *plSampWritten, LONG *plBytesWritten);
HRESULT STDMETHODCALLTYPE AVIBallDelete       (PAVISTREAM ps, LONG lStart, LONG lSamples);
HRESULT STDMETHODCALLTYPE AVIBallReadData     (PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG *lpcb);
HRESULT STDMETHODCALLTYPE AVIBallWriteData    (PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG cb);

IAVIStreamVtbl AVIBallHandler = {
    AVIBallQueryInterface,
    AVIBallAddRef,
    AVIBallRelease,
    AVIBallCreate,
    AVIBallInfo,
    AVIBallFindSample,
    AVIBallReadFormat,
    AVIBallSetFormat,
    AVIBallRead,
    AVIBallWrite,
    AVIBallDelete,
    AVIBallReadData,
    AVIBallWriteData
};

//
// This is the function an application would call to create a PAVISTREAM to
// reference the ball.  Then the standard AVIStream function calls can be
// used to work with this stream.
//
PAVISTREAM WINAPI NewBall(void)
{
    PAVIBALL pball;

    //
    // Create a pointer to our private structure which will act as our
    // PAVISTREAM
    //
    pball = (PAVIBALL) GlobalAllocPtr(GHND, sizeof(AVIBALL));

    if (!pball)
return 0;

    //
    // Fill the function table
    //
    pball->lpvtbl = &AVIBallHandler;

    //
    // Call our own create code to create a new instance (calls AVIBallCreate)
    // For now, don't use any lParams.
    //
    pball->lpvtbl->Create((PAVISTREAM) pball, 0, 0);

    return (PAVISTREAM) pball;
}

///////////////////////////////////////////////////////////////////////////
//
// This function is called to initialize an instance of the bouncing ball.
//
// When called, we look at the information possibly passed in <lParam1>,
// if any, and use it to determine the length of movie they want. (Not
// supported by NewBall right now, but it could be).
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallCreate(PAVISTREAM ps, LONG lParam1, LONG lParam2)
{
    PAVIBALL pball = (PAVIBALL) ps;

    //
    // what type of data are we? (audio/video/other stream)
    //
    pball->fccType = streamtypeVIDEO;

    //
    // We define lParam1 as being the length of movie they want us to pretend
    // to be.
    //
    if (lParam1)
pball->length = (int) lParam1;
    else
pball->length = DEFAULT_LENGTH;

    switch (pball->fccType) {

case streamtypeVIDEO:
    pball->color  = DEFAULT_COLOR;
    pball->width  = DEFAULT_WIDTH;
    pball->height = DEFAULT_HEIGHT;
    pball->size   = DEFAULT_SIZE;
    pball->ulRefCount = 1;// note that we are opened once
    return AVIERR_OK;           // success

case streamtypeAUDIO:
    return ResultFromScode(AVIERR_UNSUPPORTED); // we don't do audio

default:
    return ResultFromScode(AVIERR_UNSUPPORTED); // or anything else
    }
}

//
// Increment our reference count
//
ULONG STDMETHODCALLTYPE AVIBallAddRef(PAVISTREAM ps)
{
    PAVIBALL pball = (PAVIBALL) ps;
    return (++pball->ulRefCount);
}

//
// Decrement our reference count
//
ULONG STDMETHODCALLTYPE AVIBallRelease(PAVISTREAM ps)
{
    PAVIBALL pball = (PAVIBALL) ps;
    if (--pball->ulRefCount)
return pball->ulRefCount;

    // Free any data we're keeping around - like our private structure
    GlobalFreePtr(pball);

    return 0;
}

//
// Fills an AVISTREAMINFO structure
//
HRESULT STDMETHODCALLTYPE AVIBallInfo(PAVISTREAM ps, AVISTREAMINFOW * psi, LONG lSize)
{
    PAVIBALL pball = (PAVIBALL) ps;

    if (lSize < sizeof(AVISTREAMINFO))
return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    _fmemset(psi, 0, (int)lSize);

    // Fill out a stream header with information about us.
    psi->fccType                = pball->fccType;
    psi->fccHandler             = mmioFOURCC('B','a','l','l');
    psi->dwScale                = 1;
    psi->dwRate                 = 15;
    psi->dwLength               = pball->length;
    psi->dwSuggestedBufferSize  = pball->height * ALIGNULONG(pball->width);
    psi->rcFrame.right          = pball->width;
    psi->rcFrame.bottom         = pball->height;
    CopyMemory((PVOID)psi->szName,
               (PVOID)L"Bouncing ball video",
               sizeof(L"Bouncing ball video"));

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
//
// AVIBallReadFormat: needs to return the format of our data.
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallReadFormat   (PAVISTREAM ps, LONG lPos,LPVOID lpFormat,LONG *lpcbFormat)
{
    PAVIBALL pball = (PAVIBALL) ps;
    LPBITMAPINFO    lpbi = (LPBITMAPINFO) lpFormat;

    if (lpFormat == NULL || *lpcbFormat == 0) {
*lpcbFormat = sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD);
return AVIERR_OK;
    }

    if (*lpcbFormat < sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD))
return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    // This is a relatively silly example: we build up our
    // format from scratch every time.

    lpbi->bmiHeader.biSize              = sizeof(BITMAPINFOHEADER);
    lpbi->bmiHeader.biCompression       = BI_RGB;
    lpbi->bmiHeader.biWidth             = pball->width;
    lpbi->bmiHeader.biHeight            = pball->height;
    lpbi->bmiHeader.biBitCount          = 8;
    lpbi->bmiHeader.biPlanes            = 1;
    lpbi->bmiHeader.biClrUsed           = 2;
    lpbi->bmiHeader.biSizeImage         = pball->height * DIBWIDTHBYTES(lpbi->bmiHeader);

    lpbi->bmiColors[0].rgbRed           = 0;
    lpbi->bmiColors[0].rgbGreen         = 0;
    lpbi->bmiColors[0].rgbBlue          = 0;
    lpbi->bmiColors[1].rgbRed           = GetRValue(pball->color);
    lpbi->bmiColors[1].rgbGreen         = GetGValue(pball->color);
    lpbi->bmiColors[1].rgbBlue          = GetBValue(pball->color);

    *lpcbFormat = sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD);

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
//
// AVIBallRead: needs to return the data for a particular frame.
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallRead (PAVISTREAM ps, LONG lStart,LONG lSamples,LPVOID lpBuffer,LONG cbBuffer,LONG * plBytes,LONG * plSamples)
{
    PAVIBALL pball = (PAVIBALL) ps;
    LONG   lSize = pball->height * ALIGNULONG(pball->width); // size of frame
     // in bytes
    int x, y;
    HPSTR hp = lpBuffer;
    int xPos, yPos;

    // Reject out of range values
    if (lStart < 0 || lStart >= pball->length)
return ResultFromScode(AVIERR_BADPARAM);

    // Did they just want to know the size of our data?
    if (lpBuffer == NULL || cbBuffer == 0)
goto exit;

    // Will our frame fit in the buffer passed?
    if (lSize > cbBuffer)
return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    // Figure out the position of the ball.
    // It just bounces back and forth.

    xPos = 5 + XSPEED * (int) lStart;    // x = x0 + vt
    xPos = xPos % ((pball->width - pball->size) * 2);    // limit to 2xwidth
    if (xPos > (pball->width - pball->size))    // reflect if
xPos = 2 * (pball->width - pball->size) - xPos;    //   needed

    yPos = 5 + YSPEED * (int) lStart;
    yPos = yPos % ((pball->height - pball->size) * 2);
    if (yPos > (pball->height - pball->size))
yPos = 2 * (pball->height - pball->size) - yPos;

    //
    // Build a DIB from scratch by writing in 1's where the ball is, 0's
    // where it isn't.
    //
    // Notice that we just build it in the buffer we've been passed.
    //
    // This is pretty ugly, I have to admit.
    //
    for (y = 0; y < pball->height; y++)
{
if (y >= yPos && y < yPos + pball->size)
{
    for (x = 0; x < pball->width; x++)
    {
*hp++ = (BYTE) ((x >= xPos && x < xPos + pball->size) ? 1 : 0);
    }
}
else
{
    for (x = 0; x < pball->width; x++)
    {
*hp++ = 0;
    }
}

hp += pball->width - ALIGNULONG(pball->width);
    }

exit:
    // We always return exactly one frame
    if (plSamples)
*plSamples = 1;

    // Return the size of our frame
    if (plBytes)
*plBytes = lSize;

    return AVIERR_OK;
}

HRESULT STDMETHODCALLTYPE AVIBallQueryInterface(PAVISTREAM ps, REFIID riid, LPVOID * ppvObj)
{
    PAVIBALL pball = (PAVIBALL) ps;

    // We support the Unknown interface (everybody does) and our Stream
    // interface.

    if (_fmemcmp(riid, &IID_IUnknown, sizeof(GUID)) == 0)
        *ppvObj = (LPVOID)pball;

    else if (_fmemcmp(riid, &IID_IAVIStream, sizeof(GUID)) == 0)
        *ppvObj = (LPVOID)pball;

    else {
        *ppvObj = NULL;
        return ResultFromScode(E_NOINTERFACE);
    }

    AVIBallAddRef(ps);

    return AVIERR_OK;
}

LONG    STDMETHODCALLTYPE AVIBallFindSample (PAVISTREAM ps, LONG lPos, LONG lFlags)
{
    // The only format change is frame 0
    if ((lFlags & FIND_TYPE) == FIND_FORMAT) {
if ((lFlags & FIND_DIR) == FIND_NEXT && lPos > 0)
    return -1;// no more format changes
else
    return 0;

    // FIND_KEY and FIND_ANY always return the same position because
    // every frame is non-empty and a key frame
    } else
        return lPos;
}

HRESULT STDMETHODCALLTYPE AVIBallReadData     (PAVISTREAM ps, DWORD fcc, LPVOID lp, LONG *lpcb)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallSetFormat    (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG cbFormat)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallWriteData    (PAVISTREAM ps, DWORD fcc, LPVOID lp, LONG cb)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallWrite        (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG *plSampWritten, LONG *plBytesWritten)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallDelete       (PAVISTREAM ps, LONG lStart, LONG lSamples)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}


AVIBALL.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*
** AVIBall.h
*/

/*
** Use this function to create a new 'ball' stream, which can then be
** interfaced with using the standard AVI Stream interface.
*/
PAVISTREAM WINAPI NewBall(void);


AVIVIEW.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include <commdlg.h>
#include <vfw.h>

#include "muldiv32.h"

#include <mmreg.h>
#include <msacm.h>
#include "aviview.h"
#include "audplay.h"
#include "aviball.h"

#define GlobalSizePtr(lp)   GlobalSize(GlobalPtrHandle(lp))

#define FIXCC(fcc)  if (fcc == 0)       fcc = mmioFOURCC('N', 'o', 'n', 'e'); \
                    if (fcc == BI_RLE8) fcc = mmioFOURCC('R', 'l', 'e', '8');

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
typedef LONG (PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static  TCHAR   gszAppName[]=TEXT("AVIView");
static  TCHARgachFilter[512] = TEXT("");

static  HANDLEghInstApp;
static  HWNDghwndApp;
static  HACCELghAccel;
static  HANDLE  ghLib;                 // Handle to palmap32.dll
static  BOOL    gbCanPalMap = FALSE;   // Is palmap32.dll available?

#define SCROLLRANGE  10000

#define MAXNUMSTREAMS   50
PAVIFILE    gpfile;// the current file
PAVISTREAM          gapavi[MAXNUMSTREAMS];// the current streams
PGETFRAME    gapgf[MAXNUMSTREAMS];// data for decompressing
// video
HDRAWDIB    ghdd[MAXNUMSTREAMS];// drawdib handles
int    gcpavi;// # of streams

BOOL    gfPlaying = FALSE;// Are we playing right now?
LONG    glPlayStartTime;// When did we start playing?
LONG     glPlayStartPos;// From what position?

PAVISTREAM          gpaviAudio;                 // 1st audio stream found
PAVISTREAM          gpaviVideo;                 // 1st video stream found
int                 giFirstVideo;                // index of gapavi for 1st Video stream

#define             gfVideoFound (gpaviVideo != NULL)
#define             gfAudioFound (gpaviAudio != NULL)

LONG                timeStart;// cached start, end, length
LONG                timeEnd;
LONG                timeLength;
LONG    timehscroll;                // how much arrows scroll HORZ bar
LONG    vertSBLen;
LONG    vertHeight;

DWORD    gdwMicroSecPerPixel = 1000L;// scale for video

TCHAR               gachFileName[MAX_PATH] = TEXT("");
TCHAR               gachSaveFileName[MAX_PATH] = TEXT("");
UINT    gwZoom = 2;// one half zoom (divide by 4)
AVICOMPRESSOPTIONS  gaAVIOptions[MAXNUMSTREAMS];
LPAVICOMPRESSOPTIONS  galpAVIOptions[MAXNUMSTREAMS];

HFONT               hfontApp;
TEXTMETRIC          tm;
BYTE    abFormat[1024];
LPVOID    lpAudio;// buffer for painting
int                 gnColours;          // No of colours to remap palette to

                // !!! constants for painting
#define VSPACE  8                // some vertical spacing
#define HSPACE  4               // space between frames for video streams
#define TSPACE  20                // space for text area about each stream
#define AUDIOVSPACE  64                // height of an audio stream at X1 zoom

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

#define GetScrollTime(hwnd) \
    (LONG)(timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))

#define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
    (int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
long             PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything);
LONG WINAPI      AppWndProc (HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
int              ErrMsg (LPTSTR sz,...);
LONG PASCAL      AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK    AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK    GetNumberOfColorsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LONG             GetNumberOfColors(PAVISTREAM ps);
/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

HCURSOR hcurSave;
int     fWait = 0;

/*----------------------------------------------------------------------------*\
|    StartWait()
|
|    Start a wait operation... put up the hourglass if it's the first call.
\*----------------------------------------------------------------------------*/
void StartWait()
{
    if (fWait++ == 0) {
        SetCursor(LoadCursor(NULL,IDC_WAIT));
    }
}

/*----------------------------------------------------------------------------*\
|    EndWait()
|
|    Once every one who started a wait is finished, go back to regular cursor.
\*----------------------------------------------------------------------------*/
void EndWait()
{
    if (--fWait == 0) {
        SetCursor(LoadCursor(NULL,IDC_ARROW));
        InvalidateRect(ghwndApp, NULL, TRUE);
    }
}

/*----------------------------------------------------------------------------*\
|    WinYield()
|
|    Code to yield while we're not calling GetMessage.
|    Dispatch all messages.  Pressing ESC or closing aborts.
\*----------------------------------------------------------------------------*/
BOOL WinYield()
{
    MSG msg;
    BOOL fAbort=FALSE;

    while(fWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    {
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
            fAbort = TRUE;
if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
    fAbort = TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
    }
    return fAbort;
}

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*\
|    FixScrollbars()
|
|    When we load a file or zoom changes, we re-set the scrollbars.
\*----------------------------------------------------------------------------*/
void FixScrollbars(HWND hwnd)
{
    LONG                lHeight = 0;
    RECT                rc;
    HDC                 hdc;

    //
    // Pretend we're painting and count how many lines we needed
    //
    hdc = GetDC(NULL);
    ExcludeClipRect(hdc, 0, 0, 32767, 32767);   // don't actually draw
    lHeight = PaintStuff(hdc, hwnd, TRUE);
    ReleaseDC(NULL, hdc);

    //
    // Set vertical scrollbar for scrolling the visible area
    //
    GetClientRect(hwnd, &rc);
    vertHeight = lHeight;// total height in pixels of entire display

    //
    // We won't fit in the window... need scrollbars
    //
    if (lHeight > rc.bottom) {
vertSBLen = lHeight - rc.bottom;
SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
SetScrollPos(hwnd, SB_VERT, 0, TRUE);

    //
    // We will fit in the window!  No scrollbars necessary
    //
    } else {
vertSBLen = 0;
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    }
}

/*----------------------------------------------------------------------------*\
|    InitStreams()
|
|    Initialize the streams of a loaded file -- the compression options, the
|    DrawDIB handles, and the scroll bars.
\*----------------------------------------------------------------------------*/
void InitStreams(HWND hwnd)
{
    AVISTREAMINFO     avis;
    LONGlTemp;
    inti;
    DWORDdw;

    //
    // Start with bogus times
    //
    timeStart = 0x7FFFFFFF;
    timeEnd   = 0;

    //
    // Walk through and init all streams loaded
    //
    for (i = 0; i < gcpavi; i++) {

        AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

//
// Save and SaveOptions code takes a pointer to our compression opts
//
galpAVIOptions[i] = &gaAVIOptions[i];

//
// clear options structure to zeroes
//
_fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));

//
 // Initialize the compression options to some default stuff
// !!! Pick something better
//
galpAVIOptions[i]->fccType = avis.fccType;

switch(avis.fccType) {

    case streamtypeVIDEO:
galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
galpAVIOptions[i]->fccHandler = 0;
galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
galpAVIOptions[i]->dwKeyFrameEvery = (DWORD)-1; // Default
galpAVIOptions[i]->dwBytesPerSecond = 0;
galpAVIOptions[i]->dwInterleaveEvery = 1;
break;

    case streamtypeAUDIO:
galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
galpAVIOptions[i]->dwInterleaveEvery = 1;
                AVIStreamReadFormat(gapavi[i], AVIStreamStart(gapavi[i]),
                                    NULL, &lTemp);
                galpAVIOptions[i]->cbFormat = lTemp;
                if (lTemp)
                    galpAVIOptions[i]->lpFormat = GlobalAllocPtr(GHND, lTemp);
                // Use current format as default format
                if (galpAVIOptions[i]->lpFormat)
                    AVIStreamReadFormat(gapavi[i],
                                    AVIStreamStart(gapavi[i]),
    galpAVIOptions[i]->lpFormat,
    &lTemp);
break;

    default:
break;
}

//
// We're finding the earliest and latest start and end points for
// our scrollbar.
//
        timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
        timeEnd   = max(timeEnd, AVIStreamEndTime(gapavi[i]));

//
// Initialize video streams for getting decompressed frames to display
//
        if (avis.fccType == streamtypeVIDEO) {

    gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);
    if (gapgf[i] == NULL)
continue;

    ghdd[i] = DrawDibOpen();
    // !!! DrawDibBegin?

    if (gpaviVideo == NULL) {

//
// Remember the first video stream --- treat it specially
//
                gpaviVideo = gapavi[i];
                giFirstVideo = i;

                //
                // Set the horizontal scrollbar scale to show every frame
                // of the first video stream exactly once
                //
                dw = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 + HSPACE;
                gdwMicroSecPerPixel = muldiv32(1000000,
                                               avis.dwScale,
                                               dw * avis.dwRate);

                // Move one frame on the top video screen for each HSCROLL
                timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
            }

        } else if (avis.fccType == streamtypeAUDIO) {

            //
            // If there are no video streams, we base everything on this
            // audio stream.
            //
            if (gpaviAudio == NULL && gpaviVideo == NULL) {

                // Show one sample per pixel
                gdwMicroSecPerPixel = muldiv32(1000000,
                                               avis.dwScale,
                                               avis.dwRate);
                // Move one sample per HSCROLL
                // Move at least enough to show movement
                timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
            }

    //
    // Remember the first audio stream --- treat it specially
    //
    if (gpaviAudio == NULL)
        gpaviAudio = gapavi[i];

}

    }

    timeLength = timeEnd - timeStart;

    if (timeLength == 0)
        timeLength = 1;

    // Make sure HSCROLL scrolls enough to be noticeable.
    timehscroll = max(timehscroll, timeLength / SCROLLRANGE + 2);

    SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
    SetScrollTime(hwnd, timeStart);

    FixScrollbars(hwnd);
}

/*----------------------------------------------------------------------------*\
|    FixWindowTitle()
|
|    Update the window title to reflect what's loaded.
\*----------------------------------------------------------------------------*/
void FixWindowTitle(HWND hwnd)
{
    TCHAR ach[80];

    wsprintf(ach, TEXT("%s %s"),
            (LPTSTR)gszAppName,
            (LPTSTR)gachFileName);

    SetWindowText(hwnd, ach);

    InvalidateRect(hwnd, NULL, TRUE);
}

/*----------------------------------------------------------------------------*\
|    FreeDrawStuff()
|
| Free up the resources associated with DrawDIB
\*----------------------------------------------------------------------------*/
void FreeDrawStuff(HWND hwnd)
{
    inti;

    aviaudioStop();

    for (i = 0; i < gcpavi; i++) {
if (gapgf[i]) {
    AVIStreamGetFrameClose(gapgf[i]);
    gapgf[i] = NULL;
}
if (ghdd[i]) {
    DrawDibClose(ghdd[i]);
    ghdd[i] = 0;
}
    }
    SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
    gpaviVideo = gpaviAudio = NULL;
}

/*----------------------------------------------------------------------------*\
|    FreeAvi()
|
|    Free the resources associated with an open file.
\*----------------------------------------------------------------------------*/
void FreeAvi(HWND hwnd)
{
    inti;

    FreeDrawStuff(hwnd);

    AVISaveOptionsFree(gcpavi, galpAVIOptions);

    for (i = 0; i < gcpavi; i++) {
AVIStreamRelease(gapavi[i]);
    }

    if (gpfile)
AVIFileRelease(gpfile);

    gpfile = NULL;

    // Good a place as any to make sure audio data gets freed
    if (lpAudio)
        GlobalFreePtr(lpAudio);
    lpAudio = NULL;

    gcpavi = 0;
}

/*----------------------------------------------------------------------------*\
|    InitBall()
|
|    Open up our fake "ball" file as an installible stream hander
\*----------------------------------------------------------------------------*/
void InitBall(HWND hwnd)
{
    // close everything down
    FreeAvi(hwnd);

    // The NewBall() function creates a PAVISTREAM we can use as if it was
    // an AVI file.
    gapavi[0] = NewBall();

    if (gapavi[0])
gcpavi = 1;

    lstrcpy(gachFileName, TEXT("BALL"));
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}

/*----------------------------------------------------------------------------*\
|    InsertAVIFile()
|
|    Does most of the work of opening an AVI file.
\*----------------------------------------------------------------------------*/
void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPTSTR lpszFile)
{
    inti;
    PAVISTREAMpavi;

    //
    // Get all the streams from the new file
    //
    for (i = gcpavi; i <= MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i - gcpavi) != AVIERR_OK)
    break;
        if (i == MAXNUMSTREAMS) {
            AVIStreamRelease(pavi);
            ErrMsg("Exceeded maximum number of streams");
            break;
        }
gapavi[i] = pavi;
    }

    //
    // Couldn't get any streams out of this file
    //
    if (gcpavi == i && i != MAXNUMSTREAMS)
    {
        ErrMsg(TEXT("Unable to open %s"), lpszFile);
if (pfile)
    AVIFileRelease(pfile);
return;
    }

    gcpavi = i;

    if (gpfile) {
AVIFileRelease(pfile);
    } else
gpfile = pfile;

    FreeDrawStuff(hwnd);
    InitStreams(hwnd);
    FixWindowTitle(hwnd);
}

/*----------------------------------------------------------------------------*\
|    InitAvi()
|
|    Open up a file through the AVIFile handlers.
\*----------------------------------------------------------------------------*/
void InitAvi(HWND hwnd, LPTSTR szFile, UINT wMenu)
{
    HRESULThr;
    PAVIFILEpfile;

    hr = AVIFileOpen(&pfile, szFile, 0, 0L);

    if (hr != 0)
    {
        ErrMsg(TEXT("Unable to open %s"), szFile);
        return;
    }

    if (wMenu == MENU_OPEN)
FreeAvi(hwnd);

    InsertAVIFile(pfile, hwnd, szFile);
}

/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)       |
|       |
|   Description:       |
|This is called when the application is first loaded into       |
|memory.  It performs all initialization that doesn't need to be done   |
|once per instance.       |
|       |
|   Arguments:       |
|hInstanceinstance handle of current instance       |
|hPrevinstance handle of previous instance       |
|       |
|   Returns:       |
|TRUE if successful, FALSE if not       |
|       |
\*----------------------------------------------------------------------------*/
BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw,LPSTR szCmdLine)
{
    WNDCLASScls;
    HDChdc;

#ifdef BIDI
    const DWORD  dwExStyle = WS_EX_BIDI_SCROLL  | WS_EX_BIDI_MENU |WS_EX_BIDI_NOICON;
#else
    const DWORD  dwExStyle = 0;
#endif

    /* Save instance handle for DialogBoxs */
    ghInstApp = hInst;

    ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

    if (szCmdLine && szCmdLine[0]) {
#ifdef UNICODE
// convert to unicode
lstrcpy(gachFileName, GetCommandLine());
#else
    lstrcpy(gachFileName, szCmdLine);
#endif
    }

    if (!hPrev) {
/*
 *  Register a class for the main application window
 */
        cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
        cls.hIcon          = LoadIcon(hInst,MAKEINTATOM(ID_APP));
        cls.lpszMenuName   = MAKEINTATOM(ID_APP);
        cls.lpszClassName  = MAKEINTATOM(ID_APP);
        cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        cls.hInstance      = hInst;
        cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
        cls.lpfnWndProc    = (LPWNDPROC)AppWndProc;
        cls.cbWndExtra     = 0;
        cls.cbClsExtra     = 0;

        if (!RegisterClass(&cls))
    return FALSE;
    }

    //
    // Must be called before using any of the AVIFile routines
    //
    AVIFileInit();

    hfontApp = GetStockObject(ANSI_VAR_FONT);
    hdc = GetDC(NULL);
    SelectObject(hdc, hfontApp);
    GetTextMetrics(hdc, &tm);
    ReleaseDC(NULL, hdc);

    ghwndApp=CreateWindowEx(dwExStyle,
    MAKEINTATOM(ID_APP),    // Class name
                            gszAppName,             // Caption
                            WS_OVERLAPPEDWINDOW,    // Style bits
                            CW_USEDEFAULT, 0,       // Position
                            320,300,                // Size
                            (HWND)NULL,             // Parent window (no parent)
                            (HMENU)NULL,            // use class menu
                            (HANDLE)hInst,          // handle to window instance
                            (LPSTR)NULL             // no params to pass on
                           );
    ShowWindow(ghwndApp,sw);

    ghLib = LoadLibrary(TEXT("palmap32.dll"));
    if (ghLib == NULL)
        gbCanPalMap = FALSE;
    else
        gbCanPalMap = TRUE;

    return TRUE;
}

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )       |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|hInstinstance handle of this instance of the app       |
|hPrevinstance handle of previous instance, NULL if first    |
|       szCmdLine       ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG     msg;

    /* Call initialization procedure */
    if (!AppInit(hInst,hPrev,sw,szCmdLine))
        return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;)
    {
        while (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                return msg.wParam;

    if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
continue;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

//
// If we have no messages to dispatch, we do our background task...
// If we're playing a file, we set the scroll bar to show the video
// frames corresponding with the current playing audio sample
//
        if (gfPlaying) {
    LONG    l;

    //
    // Use the audio clock to tell how long we've been playing.  To
    // maintain sync, it's important we use this clock.
    //
    l = aviaudioTime(); // returns -1 if no audio playing

    //
    // If we can't use the audio clock to tell us how long we've been
    // playing, calculate it ourself
    //
    if (l == -1)
l = timeGetTime() - glPlayStartTime + glPlayStartPos;

    if (l != GetScrollTime(ghwndApp)) {
        if (l < timeStart)// make sure number isn't out of bounds
    l = timeStart;
        if (l > timeEnd)// looks like we're all done!
                    FORWARD_WM_COMMAND(ghwndApp, MENU_STOP, NULL, 0, SendMessage);

SetScrollTime(ghwndApp, l);
InvalidateRect(ghwndApp, NULL, FALSE);
UpdateWindow(ghwndApp);
continue;
    }
}

WaitMessage();
    }

    // not reached
    return msg.wParam;
}

typedef BYTE * HPBYTE;
typedef UNALIGNED short * HPSHORT;

/*----------------------------------------------------------------------------*\
|    PaintVideo()
|
|    Draw a video frame in the specified rect.
\*----------------------------------------------------------------------------*/
void PaintVideo(HDC hdc, RECT rcFrame, int iStream, LPBITMAPINFOHEADER lpbi, LONG lCurFrame, LONG lPos)
{
    intiLen;
    charach[200];
    RECTrc;
    COLORREFnCol;

    //
    // If we have a picture, draw it
    //
    if (lpbi)
    {
        //
        // use the palette of the first video stream
        //
        DrawDibDraw(ghdd[iStream],hdc,
    rcFrame.left, rcFrame.top,
    rcFrame.right - rcFrame.left,
    rcFrame.bottom - rcFrame.top,
    lpbi, NULL,
    0, 0, -1, -1,
    gapavi[iStream] == gpaviVideo ? 0 : DDF_BACKGROUNDPAL);

        iLen = wsprintf(ach, TEXT("%ld %ld.%03lds"),
    lCurFrame, lPos/1000, lPos%1000);
    }

    //
    // Before or after the movie (or read error) draw GRAY
    //
    else {
        if (gapgf[iStream])
    SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
        else
    SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));

        PatBlt(hdc,
    rcFrame.left, rcFrame.top,
    rcFrame.right - rcFrame.left,
    rcFrame.bottom - rcFrame.top,
    PATCOPY);
        iLen = 0;
        ach[0] = TEXT('\0');
    }

    //
    // print something meaningful under the frame
    //
    rc.left = rcFrame.left;
    rc.right = rcFrame.right + HSPACE;
    rc.top = rcFrame.bottom + VSPACE;
    rc.bottom = rc.top + TSPACE;
    nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
    ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE,
       &rc, ach, iLen, NULL);
    SetBkColor(hdc, nCol);
}

/*----------------------------------------------------------------------------*\
|    PaintAudio()
|
|    Draw some samples of audio inside the given rectangle.
\*----------------------------------------------------------------------------*/
void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
{
    PCMWAVEFORMAT wf;
    int i;
    int x,y;
    int w,h;
    BYTE b;
    HBRUSH hbr;
    RECT rc = *prc;
    LONG    lBytes;
    LONG    l, lLenOrig = lLen;
    LONG    lWaveBeginTime = AVIStreamStartTime(pavi);
    LONG    lWaveEndTime   = AVIStreamEndTime(pavi);

    //
    // We can't draw before the beginning of the stream - adjust
    //
    if (lStart < lWaveBeginTime) {
lLen -= lWaveBeginTime - lStart;
lStart = lWaveBeginTime;
// right justify the legal samples in the rectange - don't stretch
rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    //
    // We can't draw past the end of the stream
    //
    if (lStart + lLen > lWaveEndTime) {
lLenOrig = lLen;
lLen = max(0, lWaveEndTime - lStart);// maybe nothing to draw!
// left justify the legal samples in the rectange - don't stretch
rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
    }

    // Now change and work with samples, not time.
    l = lStart;
    lStart = AVIStreamTimeToSample(pavi, lStart);
    lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;

    //
    // Get the format of the wave data
    //
    l = sizeof(wf);
    AVIStreamReadFormat(pavi, lStart, &wf, &l);
    if (!l)
        return;

    w = rc.right - rc.left;
    h = rc.bottom - rc.top;

    //
    // We were starting before the beginning or continuing past the end.
    // We're not painting in the whole original rect --- use a dark background
    //
    if (rc.left > prc->left) {
        SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
 PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
    rc.bottom - rc.top, PATCOPY);
    }

    if (rc.right < prc->right) {
        SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
 PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
    rc.bottom - rc.top, PATCOPY);
    }

#define BACKBRUSH  (GetSysColor(COLOR_BTNFACE))
#define MONOBRUSH  (GetSysColor(COLOR_BTNSHADOW))
#define LEFTBRUSH  (RGB(0,0,255))
#define RIGHTBRUSH (RGB(0,255,0))
#define HPOSBRUSH  (RGB(255,0,0))

    //
    // Paint the background
    //
    hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
    PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    //
    // !!! we can only paint PCM data right now.  Sorry!
    //
    if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
        return;

    //
    // How many bytes are we painting? Alloc some space for them
    //
    lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;
    if (!lpAudio)
        lpAudio = GlobalAllocPtr (GHND, lBytes);
    else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
        lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);

    if (!lpAudio)
        return;

    //
    // Read in the wave data
    //
    AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);

    if (l != lLen)
return;

    //
    // !!! Flickers less painting it NOW or LATER?
    // First show the current position as a bar
    //
    hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
    PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
    DeleteObject(SelectObject(hdc, hbr));

    //
    // Paint monochrome wave data
    //
    if (wf.wf.nChannels == 1) {

//
// Draw the x-axis
//
        hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
        y = rc.top + h/2;
        PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

//
// 8 bit data is centred around 0x80
//
        if (wf.wBitsPerSample == 8) {
            for (x=0; x<w; x++) {

// which byte of audio data belongs at this pixel?
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w));

                if (b > 0x80) {
                    i = y - (int)muldiv32(b-0x80,(h/2),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }

else {
                    i = y + (int)muldiv32(0x80-b,(h/2),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }
        }

//
// 16 bit data is centred around 0x00
//
        else if (wf.wBitsPerSample == 16) {
            for (x=0; x<w; x++) {

// Don't make any assumptions about INT size !
// which byte of audio data belongs at this pixel?
                i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w));
                if (i > 0) {
                   i = y - (int) ((LONG)i * (h/2) / 32768);
                   PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                   i = (int) ((LONG)i * (h/2) / 32768);
                   PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }
        }
        DeleteObject(SelectObject(hdc, hbr));
    } // endif mono

    //
    // Draw stereo waveform data
    //
    else if (wf.wf.nChannels == 2) {

//
// 8 bit data is centred around 0x80
//
        if (wf.wBitsPerSample == 8) {

            // Left channel
            hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
            y = rc.top + h/4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);

                if (b > 0x80) {
                    i = y - (int)muldiv32(b-0x80,(h/4),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = y + (int)muldiv32(0x80-b,(h/4),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));

            // Right channel
            hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
            y = rc.top + h * 3 / 4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
                b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);

                if (b > 0x80) {
                    i = y - (int)muldiv32(b-0x80,(h/4),128);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = y + (int)muldiv32(0x80-b,(h/4),128);
                    PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));
        }

//
// 16 bit data is centred around 0x00
//
        else if (wf.wBitsPerSample == 16) {

            // Left channel
            hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
            y = rc.top + h/4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
// Don't make any assumptions about INT size !
                i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2);
                if (i > 0) {
                    i = y - (int) ((LONG)i * (h/4) / 32768);
                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                    i = (int) ((LONG)i * (h/4) / 32768);
                    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));

            // Right channel
            hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
            y = rc.top + h * 3 / 4;
            PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

            for (x=0; x<w; x++) {
// Don't make any assumptions about INT size !
                i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
                if (i > 0) {
                   i = y - (int) ((LONG)i * (h/4) / 32768);
                   PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
                }
                else {
                   i = (int) ((LONG)i * (h/4) / 32768);
                   PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
                }
            }
            DeleteObject(SelectObject(hdc, hbr));
        }
    } // endif stereo
}

/*----------------------------------------------------------------------------*\
|    PaintStuff()
|
|    Paint the screen with what we plan to show them.
\*----------------------------------------------------------------------------*/
long PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything)
{
    int         yStreamTop;
    TCHAR       ach[400];
    int         iFrameWidth, iLen;
    int         n;
    int         nFrames;
    LPBITMAPINFOHEADER lpbi;
    LONG        lTime;
    LONG        lSize = 0;
    LONG        lAudioStart;
    LONG        lAudioLen;
    RECT        rcFrame, rcC;
    int         i;
    LONG        lFrame;
    LONG        lCurFrame;
    HBRUSH      hbr;
    RECT        rc;
    COLORREFnCol;

    SelectObject(hdc, hfontApp);

    #define PRINT(sz) \
        (nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW)), \
TextOut(hdc, TSPACE, yStreamTop, sz, lstrlen(sz)), \
        SetBkColor(hdc, nCol),   \
yStreamTop += tm.tmHeight+1)

    #define PF1(sz,a)                   (wsprintf(ach, sz, a), PRINT(ach))
    #define PF2(sz,a,b)                 (wsprintf(ach, sz, a, b), PRINT(ach))
    #define PF3(sz,a,b,c)               (wsprintf(ach, sz, a, b, c), PRINT(ach))
    #define PF4(sz,a,b,c,d)             (wsprintf(ach, sz, a, b, c, d), PRINT(ach))
    #define PF5(sz,a,b,c,d,e)           (wsprintf(ach, sz, a, b, c, d, e), PRINT(ach))
    #define PF6(sz,a,b,c,d,e,f)         (wsprintf(ach, sz, a, b, c, d, e, f), PRINT(ach))
    #define PF7(sz,a,b,c,d,e,f,g)       (wsprintf(ach, sz, a, b, c, d, e, f, g), PRINT(ach))
    #define PF8(sz,a,b,c,d,e,f,g,h)     (wsprintf(ach, sz, a, b, c, d, e, f, g, h), PRINT(ach))
    #define PF9(sz,a,b,c,d,e,f,g,h,i)   (wsprintf(ach, sz, a, b, c, d, e, f, g, h, i), PRINT(ach))

    GetClientRect(hwnd, &rcC);

    //
    // Look at scrollbars to find current position
    //
    lTime = GetScrollTime(hwnd);
    yStreamTop = -GetScrollPos(hwnd, SB_VERT);

    //
    // Walk through all streams and draw something
    //
    for (i=0; i<gcpavi; i++) {
AVISTREAMINFOavis;
LONGlEnd, lEndTime, lNextFmt, lPrevFmt, l;
LONGlPos, lNextKey, lPrevKey, lNextAny, lPrevAny;

AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
FIXCC(avis.fccHandler);
FIXCC(avis.fccType);

l = sizeof(abFormat);
AVIStreamReadFormat(gapavi[i],0, &abFormat, &l);

PF7(TEXT("Stream%d [%4.4hs/%4.4hs] Start: %ld Length: %ld (%ld.%03ld sec)             "),
    i,
    (LPSTR)&avis.fccType,
    (LPSTR)&avis.fccHandler,
    AVIStreamStart(gapavi[i]),
    AVIStreamLength(gapavi[i]),
    AVIStreamLengthTime(gapavi[i]) / 1000,
    AVIStreamLengthTime(gapavi[i]) % 1000);

lPos = AVIStreamTimeToSample(gapavi[i], lTime);
AVIStreamSampleSize(gapavi[i], lPos, &lSize);

lPrevKey = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_KEY);
lPrevAny = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_ANY);
lPrevFmt = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_FORMAT);

lNextKey = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_KEY);
lNextAny = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_ANY);
lNextFmt = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_FORMAT);

PF5(TEXT("Pos:%ld Time:%ld.%03ld sec Size:%ld bytes %s                                 "),
lPos, lTime/1000, lTime%1000, lSize,
(LPTSTR)(lPos == lPrevKey ? TEXT("Key") : TEXT("")));

PF6(TEXT("PrevKey=%ld NextKey=%ld, PrevAny=%ld NextAny=%ld, PrevFmt=%ld NextFmt=%ld                      "),
    lPrevKey, lNextKey,
    lPrevAny, lNextAny,
    lPrevFmt, lNextFmt);

//
// Draw a VIDEO stream
//
if (avis.fccType == streamtypeVIDEO) {
    if (gapgf[i] == NULL)
continue;

    lpbi = (LPBITMAPINFOHEADER)abFormat;
    FIXCC(lpbi->biCompression);

    //
    // display video format
    //
    //  Video: 160x120x8 (cram)
    //
    PF4(TEXT("Format: %dx%dx%d (%4.4hs)"),
(int)lpbi->biWidth,
(int)lpbi->biHeight,
(int)lpbi->biBitCount,
(LPSTR)&lpbi->biCompression);

            //
    // Which frame belongs at this time?
    //
    lEndTime = AVIStreamEndTime(gapavi[i]);
    if (lTime <= lEndTime)
lFrame = AVIStreamTimeToSample(gapavi[i], lTime);
    else {// we've scrolled past the end of this stream
lEnd = AVIStreamEnd(gapavi[i]);
lFrame = lEnd + AVIStreamTimeToSample(
gapavi[i], lTime - lEndTime);
    }

    //
    // how wide is each frame to paint?
    //
    iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
gwZoom / 4 + HSPACE;

    //
    // how many frames can we fit on each half of the screen?
    //
    nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
    if (nFrames < 0)
nFrames = 0;    // at least draw *something*

    //
    // Step through all the frames we'll draw
    //
    for (n=-nFrames; n<=nFrames; n++)
    {

                //
                // Each video stream is drawn as a horizontal line of
                // frames, very close together.
                // The first video stream shows a different frame in
                // each square. Thus the scale of time is determined
                // by the first video stream.
                // Every other video stream shows whatever
                // frame belongs at the time corresponding to the mid-
                // point of each square.
                //
if (gapavi[i] == gpaviVideo) {

                    //
                    // by definition, we know what frame we're drawing..
                    // (lFrame-n), (lFrame-(n-1)), ..., (lFrame), ...,
                    // (lFrame+(n-1)), (lFrame+n)
                    //
    lCurFrame = lFrame + n;

    //
    // what time is it at that frame?  This number will be
    // printed underneath the frame
    //
    l = AVIStreamSampleToTime(gapavi[i], lCurFrame);

} else {// NOT the first video stream

    //
    // What time is it at the left edge of the square
    // we'll draw?  That's what frame we use.
    //
                    if (n<0) {
                        l = lTime - muldiv32(-n * iFrameWidth + HSPACE,
gdwMicroSecPerPixel, 1000);
                    }
                    else {
        l = lTime + muldiv32(n * iFrameWidth,
gdwMicroSecPerPixel, 1000);
                    }

                    //
    // What frame belongs to that time?
    //
    lCurFrame = AVIStreamTimeToSample(gapavi[i], l);

                    //
                    // what time is it at that frame? This number will
                    // be printed underneath the frame
                    //
                    l = AVIStreamSampleToTime(gapavi[i], lCurFrame);
}

// !!!
// Could actually return an LPBI for invalid frames
// so we better force it to NULL.
//
if (gapgf[i] && lCurFrame >= AVIStreamStart(gapavi[i]))
    lpbi = AVIStreamGetFrame(gapgf[i], lCurFrame);
else
    lpbi = NULL;

//
// Figure out where to draw this frame
//
rcFrame.left   = rcC.right / 2 -
    ((avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4)
    / 2 + (n * iFrameWidth);
rcFrame.top    = yStreamTop;
rcFrame.right  = rcFrame.left +
(avis.rcFrame.right - avis.rcFrame.left)*gwZoom/4;
rcFrame.bottom = rcFrame.top +
(avis.rcFrame.bottom - avis.rcFrame.top)*gwZoom/4;

//
// draw a border around the current frame.  Make the
// one around the centre frame a special colour.
//
if (n == 0)
    hbr = CreateSolidBrush(RGB(255,0,0));
else
    hbr = CreateSolidBrush(RGB(255,255,255));

InflateRect(&rcFrame, 1, 1);
FrameRect(hdc, &rcFrame, hbr);
InflateRect(&rcFrame, -1, -1);
DeleteObject (hbr);

//
// Now draw the video frame in the computed rectangle
//
PaintVideo(hdc, rcFrame, i, lpbi, lCurFrame, l);

    }

    //
    // Move down to where we can draw the next stream
    //
    yStreamTop += (rcFrame.bottom - rcFrame.top) +
TSPACE;
}

//
// Draw an AUDIO stream
//
else if (avis.fccType == streamtypeAUDIO) {
    LPWAVEFORMAT pwf = (LPWAVEFORMAT)abFormat;
    TCHAR *szFmt;

    if (pwf->wFormatTag == 1) {  // PCM
if (pwf->nChannels == 1)
    szFmt = TEXT("Format: Mono %ldHz %dbit");
else
    szFmt = TEXT("Format: Stereo %ldHz %dbit");
    }
    else if (pwf->wFormatTag == 2) {  // ADPCM
if (pwf->nChannels == 1)
    szFmt = TEXT("Format: ADPCM Mono %ldHz %dbit");
else
    szFmt = TEXT("Format: ADPCM Stereo %ldHz %dbit");
    }
    else {
if (pwf->nChannels == 1)
    szFmt = TEXT("Format: Compressed Mono %ldHz %dbit");
else
    szFmt = TEXT("Format: Compressed Stereo %ldHz %dbit");
    }

    PF2(szFmt,(LONG)pwf->nSamplesPerSec,
(int)((LONG)pwf->nAvgBytesPerSec / pwf->nSamplesPerSec /
      pwf->nChannels) * 8);

    //
    // Figure out which samples are visible
    //
    lAudioStart = lTime - muldiv32(rcC.right / 2,
gdwMicroSecPerPixel, 1000);
    lAudioLen = 2 * (lTime - lAudioStart);

    //
    // Make rectangle to draw audio into
    //
    rc.left = rcC.left;
    rc.right = rcC.right;
    rc.top = yStreamTop;
    rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

    //
    // Actually paint the audio
    //
    PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);

            //
            // Move down to where we can draw the next stream
            //
            yStreamTop += AUDIOVSPACE * gwZoom / 4;

}
else if (avis.fccType == streamtypeTEXT) {
    LONG    lPos;
    int    iLeft;

    lPos = AVIStreamTimeToSample(gapavi[i],
 lTime -
 muldiv32((rcC.right - rcC.left),
gdwMicroSecPerPixel,
1000));

    if (lPos < 0)
lPos = 0;

    PatBlt(hdc, rcC.left, yStreamTop,
   rcC.right - rcC.left, TSPACE + TSPACE,
   WHITENESS);

    while (lPos < AVIStreamEnd(gapavi[i]) - 1) {

                LONG    lStreamTime = AVIStreamSampleToTime(gapavi[i], lPos);

                // What pixel is it at this time?

                if (lStreamTime < lTime) {              // pixel will be left of centre
                    iLeft = (rcC.right + rcC.left) / 2 -
    (int) muldiv32(lTime - lStreamTime, 1000,  gdwMicroSecPerPixel);
                }
                else {                                  // pixel is at, or right of, centre
                    iLeft = (rcC.right + rcC.left) / 2 +
    (int) muldiv32(lStreamTime - lTime, 1000,  gdwMicroSecPerPixel);
                }
if (iLeft >= rcC.right)
    break;

AVIStreamRead(gapavi[i], lPos, 1, ach, sizeof(ach), &l, NULL);

nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
if (l)
    TextOut(hdc, iLeft, yStreamTop, ach, (int) l - 1);

iLen = wsprintf(ach, TEXT("%ld"), lPos);
TextOut(hdc, iLeft, yStreamTop + TSPACE, ach, iLen);
SetBkColor(hdc, nCol);

lPos += 1;
    }

    yStreamTop += TSPACE + TSPACE;
}

yStreamTop += VSPACE;

        //
        // Give up once we're painting below the bottom of the window
        //
        if (!fDrawEverything && yStreamTop >= rcC.bottom)
            break;
    }

    //
    // How many lines did we draw?
    //
    return yStreamTop + GetScrollPos(hwnd, SB_VERT);
}

/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )       |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|hwndwindow handle for the window       |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LONG WINAPI  AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    BOOL        f;
    HDChdc;
    RECTrc;
    LONGl;

    switch (msg) {

        //
        // If we passed a command line filename, open it
        //
        case WM_CREATE:
            if (gachFileName[0])
                InitAvi(hwnd, gachFileName, MENU_OPEN);
    break;

        case WM_COMMAND:
            return AppCommand(hwnd,msg,wParam,lParam);

        case WM_INITMENU:
            f = gfVideoFound || gfAudioFound;
            EnableMenuItem((HMENU)wParam, MENU_OPTIONS,f ? MF_ENABLED :
MF_GRAYED);

            f = gcpavi > 0;
            EnableMenuItem((HMENU)wParam, MENU_SAVEAS, f ? MF_ENABLED :
MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_NEW,    f ? MF_ENABLED :
MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_ADD,    f ? MF_ENABLED :
MF_GRAYED);

            f = (gpfile != 0) && gbCanPalMap && gfVideoFound;
            EnableMenuItem((HMENU)wParam, MENU_NEWPALETTE, f ? MF_ENABLED :
                        MF_GRAYED);

    f = gfVideoFound || gfAudioFound;
            EnableMenuItem((HMENU)wParam, MENU_PLAY,
(f && !gfPlaying) ? MF_ENABLED : MF_GRAYED);
            EnableMenuItem((HMENU)wParam, MENU_STOP,
                        (f && gfPlaying) ? MF_ENABLED : MF_GRAYED);

    CheckMenuItem((HMENU)wParam, MENU_ZOOMQUARTER,
    (gwZoom == 1) ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem((HMENU)wParam, MENU_ZOOMHALF,
    (gwZoom == 2) ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem((HMENU)wParam, MENU_ZOOM1,
    (gwZoom == 4) ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem((HMENU)wParam, MENU_ZOOM2,
    (gwZoom == 8) ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem((HMENU)wParam, MENU_ZOOM4,
    (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);

            break;

        //
        // During a wait state (eg saving) don't let us choose any menus
        //
case WM_NCHITTEST:
    if (fWait) {

// Let windows tell us where the cursor is
lParam = DefWindowProc(hwnd,msg,wParam,lParam);

// If it's over a menu, pretend it's in the client (force
// an hourglass)
if (lParam == HTMENU)
    lParam = HTCLIENT;

return lParam;
    }
    break;

//
// Set vertical scrollbar for scrolling streams
//
case WM_SIZE:
    GetClientRect(hwnd, &rc);

            //
            // There is not enough vertical room to show all streams. Scrollbars
            // are required.
            //
    if (vertHeight > rc.bottom) {
        vertSBLen = vertHeight - rc.bottom;
        SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);

            //
            // Everything fits vertically. No scrollbar necessary.
            //
    } else {
        vertSBLen = 0;
        SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    }
    break;

        //
        // During a wait state, show an hourglass over our client area
        //
        case WM_SETCURSOR:
            if (fWait && LOWORD(lParam) == HTCLIENT) {
                SetCursor(LoadCursor(NULL, IDC_WAIT));
                return TRUE;
            }
            break;

//
// We're out of here!
//
        case WM_DESTROY:
            FreeAvi(hwnd);// close all open streams
    AVIFileExit();// shuts down the AVIFile system
    if (ghLib)
                FreeLibrary(ghLib);
    PostQuitMessage(0);
    break;

//
// Don't let us close ourselves in a wait state (eg saving)
//
        case WM_CLOSE:
    if (fWait)
return 0;
            break;

//
// Block keyboard access to menus if waiting
//
case WM_SYSCOMMAND:
    switch (wParam & 0xFFF0) {
case SC_KEYMENU:
    if (fWait)
return 0;
    break;
    }
    break;

        case WM_PALETTECHANGED:
    // It came from us.  Ignore it
            if ((HWND)wParam == hwnd)
                break;

case WM_QUERYNEWPALETTE:
            hdc = GetDC(hwnd);

            //
            // Realize the palette of the first video stream
            // !!! Assumes the first stream is video
            //
            if (ghdd[0] && (f = DrawDibRealize(ghdd[0], hdc, FALSE)))
                InvalidateRect(hwnd,NULL,TRUE);

            ReleaseDC(hwnd,hdc);

            return f;

        case WM_ERASEBKGND:
            break;

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

    PaintStuff(hdc, hwnd, FALSE);

            EndPaint(hwnd,&ps);
            break;

//
// handle the keyboard interface
//
case WM_KEYDOWN:
            switch (wParam)
            {
                case VK_UP:    PostMessage (hwnd,WM_VSCROLL,SB_LINEUP,0L);   break;
                case VK_DOWN:  PostMessage (hwnd,WM_VSCROLL,SB_LINEDOWN,0L); break;
                case VK_PRIOR: PostMessage (hwnd,WM_HSCROLL,SB_PAGEUP,0L);   break;
                case VK_NEXT:  PostMessage (hwnd,WM_HSCROLL,SB_PAGEDOWN,0L); break;
                case VK_HOME:  PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0L);     break;
                case VK_END:   PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0x7FFF); break;
                case VK_LEFT:  PostMessage (hwnd,WM_HSCROLL,SB_LINEUP,0L);   break;
                case VK_RIGHT: PostMessage (hwnd,WM_HSCROLL,SB_LINEDOWN,0L); break;
    }
    break;

        case WM_HSCROLL:
            l = GetScrollTime(hwnd);

            switch (GET_WM_HSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:      l += timehscroll;  break;
                case SB_LINEUP:        l -= timehscroll;  break;
                case SB_PAGEDOWN:      l += timeLength/10; break;
                case SB_PAGEUP:        l -= timeLength/10; break;
                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
l = GET_WM_HSCROLL_POS(wParam, lParam);
l = timeStart + muldiv32(l, timeLength, SCROLLRANGE);
break;
            }

    if (l < timeStart)
l = timeStart;

    if (l > timeEnd)
l = timeEnd;

    if (l == (LONG)GetScrollTime(hwnd))
break;

    SetScrollTime(hwnd, l);
            InvalidateRect(hwnd, NULL, FALSE);
            UpdateWindow(hwnd);
            break;

        case WM_VSCROLL:
            l = GetScrollPos(hwnd, SB_VERT);
    GetClientRect(hwnd, &rc);

            switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
                case SB_LINEDOWN:      l += 10;  break;
                case SB_LINEUP:        l -= 10;  break;
                case SB_PAGEDOWN:      l += rc.bottom; break;
                case SB_PAGEUP:        l -= rc.bottom; break;
                case SB_THUMBTRACK:
                case SB_THUMBPOSITION:
    l = GET_WM_VSCROLL_POS(wParam, lParam);
    break;
            }

    if (l < 0)
l = 0;

    if (l > vertSBLen)
l = vertSBLen;

    if (l == GetScrollPos(hwnd, SB_VERT))
break;

    SetScrollPos(hwnd, SB_VERT, (int)l, TRUE);
            InvalidateRect(hwnd, NULL, TRUE);
            UpdateWindow(hwnd);
            break;

//
// Wave driver wants to tell us something.  Pass it on.
//
case MM_WOM_OPEN:
case MM_WOM_DONE:
case MM_WOM_CLOSE:
    aviaudioMessage(hwnd, msg, wParam, lParam);
    break;
    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*----------------------------------------------------------------------------*\
|    SaveCallback()
|
|    Our save callback that prints our progress in our window title bar.
\*----------------------------------------------------------------------------*/
BOOL PASCAL  SaveCallback(int iProgress)
{
    TCHAR    ach[128];

    wsprintf(ach, TEXT("%s - Saving %s: %d%%"),
        (LPTSTR) gszAppName, (LPTSTR) gachSaveFileName, iProgress);

    SetWindowText(ghwndApp, ach);

    //
    // Give ourselves a chance to abort
    //
    return WinYield();
}

/*----------------------------------------------------------------------------*\
|    AppCommand()
|
|    Process all of our WM_COMMAND messages.
\*----------------------------------------------------------------------------*/
LONG PASCAL AppCommand (HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
    switch(GET_WM_COMMAND_ID(wParam, lParam))
    {
//
// Our about box
//
        case MENU_ABOUT:
            DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
            break;

//
// We want out of here!
//
case MENU_EXIT:
    PostMessage(hwnd,WM_CLOSE,0,0L);
            break;

//
// Set the compression options for each stream - pass an array of
// streams and an array of compression options structures
//
        case MENU_OPTIONS:
            AVISaveOptions(hwnd, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE
| ICMF_CHOOSE_PREVIEW,
gcpavi, gapavi, galpAVIOptions);
    break;

        //
        // Use palmap to set the no of colours to save with
        //
        case MENU_NEWPALETTE:
            {
       HDC      hdc;
               PAVISTREAM     psMappedStream;
               PAVISTREAM     psCurrent;
               AVISTREAMINFO  avis;
               FARPROC        lpfnAVICreateMappedStream;
       LPBITMAPINFOHEADER lpbi;
#define AVICreateMappedStream 11       //Cardinal for procedure in palmap32.dll

               lpfnAVICreateMappedStream = GetProcAddress(ghLib,MAKEINTRESOURCE(AVICreateMappedStream));
               if (lpfnAVICreateMappedStream == NULL) {
                   ErrMsg(TEXT("Unable to find AVICreateMappedStream in palmap32.dll"));
                   gbCanPalMap = FALSE;
               }

               psCurrent = gapavi[giFirstVideo];

               AVIStreamInfo(psCurrent, &avis, sizeof(avis));

               if (avis.fccType != streamtypeVIDEO) {
                   ErrMsg("Stream is not video!");
                   break;
               }

               if (DialogBoxParam(
                             ghInstApp,
                             MAKEINTRESOURCE(IDD_NCOLORS),
                             hwnd,
                             GetNumberOfColorsDlgProc,
                             (LPARAM)GetNumberOfColors(psCurrent))
                   ) {
                      StartWait();
                      //
                      //  Find out how many colours they want to map it to
                      //

                      if (0 != (*lpfnAVICreateMappedStream)(&psMappedStream,
                                              psCurrent,
                                              gnColours)) {
                           ErrMsg(TEXT("Can't map stream palette"));
                      }
                      else {
                           gapavi[giFirstVideo] = psMappedStream;

   // We've just mapped this stream to a new palette
   // so we have to re-init the GetFrame stuff used
   // to draw this stream.
   // Kick start DrawDib to realize the new palette.
   //
   if (gapgf[giFirstVideo])
AVIStreamGetFrameClose(gapgf[giFirstVideo]);
   gapgf[giFirstVideo] =
AVIStreamGetFrameOpen(gapavi[giFirstVideo], 0);
   lpbi = AVIStreamGetFrame(gapgf[giFirstVideo], 0);
   DrawDibBegin(ghdd[giFirstVideo], NULL,
lpbi->biWidth, lpbi->biHeight,
lpbi, lpbi->biWidth, lpbi->biHeight, 0);
   hdc = GetDC(hwnd);
   DrawDibRealize(ghdd[giFirstVideo], hdc, FALSE);
   ReleaseDC(hwnd, hdc);
                      }
                      EndWait();
                   }

            }
            break;

//
// Save all the open streams into a file
//
        case MENU_SAVEAS:
            {
                OPENFILENAME ofn;

                gachSaveFileName[0] = 0;

            //
                // prompt user for file to save
            //
                ofn.lStructSize = sizeof(OPENFILENAME);
                ofn.hwndOwner = hwnd;
                ofn.hInstance = NULL;

                AVIBuildFilter(gachFilter, sizeof(gachFilter)/sizeof(TCHAR), TRUE);

                ofn.lpstrFilter = gachFilter;
                ofn.lpstrCustomFilter = NULL;

ofn.nMaxCustFilter = 0;
                ofn.nFilterIndex = 0;
                ofn.lpstrFile = gachSaveFileName;
                ofn.nMaxFile = sizeof(gachSaveFileName)/sizeof(TCHAR);
                ofn.lpstrFileTitle = NULL;
                ofn.nMaxFileTitle = 0;
                ofn.lpstrInitialDir = NULL;
                ofn.lpstrTitle = TEXT("Save AVI File");
                ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
        OFN_OVERWRITEPROMPT;
                ofn.nFileOffset = 0;
                ofn.nFileExtension = 0;
                ofn.lpstrDefExt = TEXT("avi");
                ofn.lCustData = 0;
                ofn.lpfnHook = NULL;
                ofn.lpTemplateName = NULL;

         //
         // If we get a filename, save it
         //
                if (GetSaveFileName(&ofn)) {
                    DWORD   fccHandler[MAXNUMSTREAMS];
            int    i;
            HRESULT hr;

            StartWait();

            for (i = 0; i < gcpavi; i++)
                fccHandler[i] = galpAVIOptions[i]->fccHandler;

            hr = AVISaveV(gachSaveFileName,
                          NULL,
                          (AVISAVECALLBACK) SaveCallback,
                          gcpavi,
                          gapavi,
                          galpAVIOptions);
            if (hr != AVIERR_OK) {
                        switch (hr) {
                        case AVIERR_FILEOPEN:
                            ErrMsg(TEXT("Overwriting an open AVI file is not possible"));
                            break;
                        default:
                            ErrMsg(TEXT("Error saving AVI file"));
                        }
                    }

            // Now put the video compressors back that we stole
            for (i = 0; i < gcpavi; i++)
                 galpAVIOptions[i]->fccHandler = fccHandler[i];

            EndWait();
            FixWindowTitle(hwnd);
                }
     }
    break;

//
// Close everything
//
case MENU_NEW:
    FreeAvi(hwnd);
    gachFileName[0] = TEXT('\0');
    FixWindowTitle(hwnd);
    break;

//
// Open a new file, or merge streams with a new file
//
        case MENU_OPEN:
        case MENU_ADD:
            {
                OPENFILENAME ofn;

                gachFileName[0] = 0;

            //
                // prompt user for file to open
            //
                ofn.lStructSize = sizeof(OPENFILENAME);
                ofn.hwndOwner   = hwnd;
                ofn.hInstance   = NULL;
            if (wParam == MENU_ADD)
        ofn.lpstrTitle = TEXT("Merge With");
            else
                ofn.lpstrTitle = TEXT("Open AVI");

            if (gachFilter[0] == TEXT('\0'))
        AVIBuildFilter(gachFilter, sizeof(gachFilter)/sizeof(TCHAR), FALSE);

            ofn.lpstrFilter       = gachFilter;
                ofn.lpstrCustomFilter = NULL;
                ofn.nMaxCustFilter    = 0;
                ofn.nFilterIndex      = 0;
                ofn.lpstrFile         = gachFileName;
                ofn.nMaxFile          = sizeof(gachFileName)/sizeof(TCHAR);
                ofn.lpstrFileTitle    = NULL;
                ofn.nMaxFileTitle     = 0;
                ofn.lpstrInitialDir   = NULL;
                ofn.Flags             = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
                ofn.nFileOffset       = 0;
                ofn.nFileExtension    = 0;
                ofn.lpstrDefExt       = NULL;
                ofn.lCustData         = 0;
                ofn.lpfnHook          = NULL;
                ofn.lpTemplateName    = NULL;

            //
            // If we've got a filename, go open it
            //
                if (GetOpenFileNamePreview(&ofn))
        InitAvi(hwnd, gachFileName, wParam);
            }
            break;

//
// Open the "fake" ball file as our current file
//
case MENU_BALL:
    InitBall(hwnd);
    break;

case MENU_ZOOMQUARTER:
    gwZoom = 1;
    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOMHALF:
    gwZoom = 2;
    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM1:
    gwZoom = 4;
    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM2:
    gwZoom = 8;
    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
    break;

case MENU_ZOOM4:
    gwZoom = 16;
    FixScrollbars(hwnd);
            InvalidateRect(hwnd, NULL, TRUE);
            break;

//
// Simulate playing the file.  We just play the 1st audio stream and let
// our main message loop scroll the video by whenever it's bored.
//
case MENU_PLAY:
    if (gfAudioFound)
        aviaudioPlay(hwnd,
 gpaviAudio,
 AVIStreamTimeToSample(gpaviAudio, GetScrollTime(hwnd)),
 AVIStreamEnd(gpaviAudio),
 FALSE);
            gfPlaying = TRUE;
            glPlayStartTime = timeGetTime();
            glPlayStartPos = GetScrollTime(hwnd);
            break;

//
// Stop the play preview
//
case MENU_STOP:
    if (gfAudioFound)
        aviaudioStop();
    gfPlaying = FALSE;
    break;

    }
    return 0L;
}

/*----------------------------------------------------------------------------*\
|   ErrMsg - Opens a Message box with a error message in it.  The user can     |
|            select the OK button to continue                                  |
\*----------------------------------------------------------------------------*/
int ErrMsg (LPTSTR sz,...)
{
    static TCHAR ach[2000];
    va_list va;

    va_start(va, sz);
    wvsprintf (ach,sz, va);
    va_end(va);
    MessageBox(NULL,ach,NULL,
#ifdef BIDI
MB_RTL_READING |
#endif
    MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
    return FALSE;
}

// Find the number of colours present in the stream palette
LONG GetNumberOfColors(PAVISTREAM ps)
{
    BITMAPINFOHEADER bmih;
    LONG             cbFormat = sizeof(bmih);

    bmih.biClrUsed = 0;

    AVIStreamReadFormat(ps, 0, (LPVOID)&bmih, &cbFormat);

    return (LONG)bmih.biClrUsed;
}

/* GetNumberOfColorsDlgProc()
 *
 * Dialog Procedure for getting the no of colours the user wants in the palette
 *
 */

BOOL CALLBACK GetNumberOfColorsDlgProc(
HWNDhwnd,
UINTmsg,
WPARAMwParam,
LPARAMlParam)
{
        BOOL Translated;
switch (msg) {
case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
            case IDOK:
gnColours = GetDlgItemInt(hwnd, IDC_NCOLORS, &Translated, FALSE);
                EndDialog(hwnd, TRUE);
                return TRUE;
            case IDCANCEL:
                EndDialog(hwnd, FALSE);
            }
            break;

case WM_INITDIALOG:
            {
                SetDlgItemInt(hwnd, IDC_NCOLORS, lParam, FALSE);
return TRUE;
            }
}
return FALSE;
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(
HWNDhwnd,
UINTmsg,
WPARAMwParam,
LPARAMlParam)
{
switch (msg) {
case WM_COMMAND:
EndDialog(hwnd, TRUE);
return TRUE;
        case WM_INITDIALOG:
                if (gbCanPalMap)                                         // Provide the user with
                    ShowWindow(GetDlgItem(hwnd, IDS_PALETTE), SW_HIDE);  // some information about
                else                                                     // whether palmap32.dll was
                    ShowWindow(GetDlgItem(hwnd, IDS_PALETTE), SW_SHOW);  // found.
                return TRUE;
}
return FALSE;
}


AVIVIEW.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define ID_APP      1000

/* Menu Items */
#define MENU_ABOUT          20

#define MENU_EXIT           4
#define MENU_BALL    99
#define MENU_OPEN           11
#defineMENU_NEW    10
#define MENU_ADD    17
#define MENU_NEWPALETTE     12
#define MENU_SAVEAS         13
#define MENU_OPTIONS    15

#define MENU_ZOOMQUARTER    54
#define MENU_ZOOMHALF    50
#define MENU_ZOOM1    51
#define MENU_ZOOM2    52
#define MENU_ZOOM4    53

#define MENU_PLAY    100
#define MENU_PAUSE    101
#define MENU_STOP           102

// Dialogs
#define IDD_ABOUT           301
#define IDD_NCOLORS         302
#define IDS_PALETTE         303
#define IDC_NCOLORS         400

#ifdef DEBUG
    extern void CDECL dprintf(LPSTR, ...);
    #define DPF dprintf
#else
    #define DPF / ## /
#endif


CDIB.C

/*****************************************************************************
 *
 *  CrunchDIB  - shrink a DIB down by 2 with color averaging
 *
 *****************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>

//
// support huge >64k DIBs?
//
#ifndef WIN32
#define HUGE_DIBS
#endif
#ifdef HUGE_DIBS
#define LPBYTE  BYTE _huge *
#define LPWORD  WORD _huge *
#undef FAR
#define FAR _huge
#endif

/*****************************************************************************
 *
 *  These are the standard VGA colors, we will be stuck with until the
 *  end of time!
 *
 *****************************************************************************/

static DWORD VGAColors[16] = {
     0x00000000        // 0000  black
    ,0x00800000        // 0001  dark red
    ,0x00008000        // 0010  dark green
    ,0x00808000        // 0011  mustard
    ,0x00000080        // 0100  dark blue
    ,0x00800080        // 0101  purple
    ,0x00008080        // 0110  dark turquoise
    ,0x00C0C0C0        // 1000  gray
    ,0x00808080        // 0111  dark gray
    ,0x00FF0000        // 1001  red
    ,0x0000FF00        // 1010  green
    ,0x00FFFF00        // 1011  yellow
    ,0x000000FF        // 1100  blue
    ,0x00FF00FF        // 1101  pink (magenta)
    ,0x0000FFFF        // 1110  cyan
    ,0x00FFFFFF        // 1111  white
    };

/*****************************************************************************
 *
 *  bit(b,n)   - get the Nth bit of BYTE b
 *
 *****************************************************************************/

#define bit(b,n) (BYTE)(((b) & (1 << (n))) >> (n))

/*****************************************************************************
 *
 *  SumMono
 *
 *     this routine taks four "mono" values and returns the average value.
 *
 *     ((b0) + (b1) + (b2) + (b3) >= 2)
 *
 *
 *****************************************************************************/

#define SumMono(b0,b1,b2,b3) (BYTE)(((b0) + (b1) + (b2) + (b3) + 2) / 4)

/*****************************************************************************
 *
 *  MapVGA
 *
 *     map a rgb value to a VGA index
 *
 *        0000  black
 *        0001  dark red
 *        0010  dark green
 *        0011  mustard
 *        0100  dark blue
 *        0101  purple
 *        0110  dark turquoise
 *        1000  gray
 *        0111  dark gray
 *        1001  red
 *        1010  green
 *        1011  yellow
 *        1100  blue
 *        1101  pink (magenta)
 *        1110  cyan
 *        1111  white
 *
 *****************************************************************************/

#define MapVGA(r,g,b) (((r&~3) == (g&~3) && (g&~3) == (b&~3)) ?        \
        ((r < 64) ? 0 : (r <= 128) ? 8 : (r <= 192) ? 7 : 15) :        \
        (((r>192) || (g>192) || (b>192)) ?                             \
           (((r>191) ? 1:0) | ((g>191) ? 2:0) | ((b>191) ? 4:0) | 8) : \
           (((r>64) ? 1:0) | ((g>64) ? 2:0) | ((b>64) ? 4:0))) )

/*****************************************************************************
 *
 *  SumRGB
 *
 *****************************************************************************/

#define SumRGB(b0,b1,b2,b3) RGB(\
        ((int)pal.palPalEntry[b0].peRed +        \
         (int)pal.palPalEntry[b1].peRed +        \
         (int)pal.palPalEntry[b2].peRed +        \
         (int)pal.palPalEntry[b3].peRed) >> 2,   \
                                                 \
        ((int)pal.palPalEntry[b0].peGreen +      \
         (int)pal.palPalEntry[b1].peGreen +      \
         (int)pal.palPalEntry[b2].peGreen +      \
         (int)pal.palPalEntry[b3].peGreen) >> 2, \
                                                  \
        ((int)pal.palPalEntry[b0].peBlue +       \
         (int)pal.palPalEntry[b1].peBlue +       \
         (int)pal.palPalEntry[b2].peBlue +       \
         (int)pal.palPalEntry[b3].peBlue) >> 2)

/*****************************************************************************
 *
 *  RGB16
 *
 *****************************************************************************/

typedef struct { BYTE b,g,r; } RGB24;

#define RGB16(r,g,b) (\
            (((UINT)(r) >> 3) << 10) |  \
            (((UINT)(g) >> 3) << 5)  |  \
            (((UINT)(b) >> 3) << 0)  )

#define rgb16(r,g,b) (\
            ((UINT)(r) << 10) |  \
            ((UINT)(g) << 5)  |  \
            ((UINT)(b) << 0)  )

//#define RGB16R(rgb)     ((((UINT)(rgb) >> 10) & 0x1F) * 255u / 31u)
//#define RGB16G(rgb)     ((((UINT)(rgb) >> 5)  & 0x1F) * 255u / 31u)
//#define RGB16B(rgb)     ((((UINT)(rgb) >> 0)  & 0x1F) * 255u / 31u)

#define RGB16R(rgb)     aw5to8[((UINT)(rgb) >> 10) & 0x1F]
#define RGB16G(rgb)     aw5to8[((UINT)(rgb) >> 5)  & 0x1F]
#define RGB16B(rgb)     aw5to8[((UINT)(rgb) >> 0)  & 0x1F]
#define RGB16r(rgb)     ((BYTE)((UINT)(rgb) >> 10) & 0x1F)
#define RGB16g(rgb)     ((BYTE)((UINT)(rgb) >> 5)  & 0x1F)
#define RGB16b(rgb)     ((BYTE)((UINT)(rgb) >> 0)  & 0x1F)

/*****************************************************************************
 *
 *  Pel() used for 24bit Crunch
 *
 *****************************************************************************/

#define Pel(p,x) (BYTE)(BitCount == 1 ? Pel1(p,x) : \
                        BitCount == 4 ? Pel4(p,x) : Pel8(p,x))

#define Pel1(p,x)   (BYTE)bit(((LPBYTE)(p))[(x)/8],7-((x)%8))
#define Pel4(p,x)   (BYTE)((x & 1) ? (((LPBYTE)(p))[(x)/2] & 15) : (((LPBYTE)(p))[(x)/2] >> 4))
#define Pel8(p,x)   (BYTE)(((LPBYTE)(p))[(x)])
#define Pel16(p,x)  (((LPWORD)(p))[(x)])
#define Pel24(p,x)  (((RGB24 FAR *)(p))[(x)])

/*****************************************************************************
 *
 *  CrunchDIB  - shrink a DIB down by 2 with color averaging
 *
 *     this routine works on 1,4 bpp DIBs
 *
 *     for mono DIBs it is assumed they are black and white
 *
 *     for 4bpp DIBs it is assumed they use the VGA colors
 *
 *     this routine can't be used "in place"
 *
 *****************************************************************************/

BOOL CrunchDIB(
    LPBITMAPINFOHEADER  lpbiSrc,    // BITMAPINFO of source
    LPVOID              lpSrc,      // input bits to crunch
    LPBITMAPINFOHEADER  lpbiDst,    // BITMAPINFO of dest
    LPVOID              lpDst)      // output bits to crunch
{
    LPBYTE      pbSrc;
    LPBYTE      pbDst;
    LPBYTE      pb;
    LPWORD      pw;
    BYTE        r,g,b,b0,b1,b2,b3;
    WORD        w0,w1,w2,w3;
    RGB24       rgb0,rgb1,rgb2,rgb3;
    int         WidthBytesSrc;
    int         WidthBytesDst;
    UINT        x;
    UINT        y;
    UINT        dx;
    UINT        dy;
    int         i;
    COLORREF    rgb;
    int         BitCount;
    UINT        aw5to8[32];

    struct {
        WORD         palVersion;
WORD         palNumEntries;
PALETTEENTRY palPalEntry[256];
    }   pal;

    if (lpbiSrc->biCompression != BI_RGB)
        return FALSE;

    BitCount = (int)lpbiSrc->biBitCount;

    if (BitCount == 16)
        for (i=0; i<32; i++)
            aw5to8[i] = (UINT)i * 255u / 31u;

    dx = (int)lpbiDst->biWidth;
    WidthBytesDst = (((UINT)lpbiDst->biBitCount * dx + 31)&~31) / 8;

    dy = (int)lpbiSrc->biHeight;
    dx = (int)lpbiSrc->biWidth;
    WidthBytesSrc = (((UINT)lpbiSrc->biBitCount * dx + 31)&~31) / 8;

    dx &= ~1;
    dy &= ~1;

    pbSrc = lpSrc;
    pbDst = lpDst;

    if (lpbiSrc->biClrUsed == 0 && lpbiSrc->biBitCount <= 8)
        lpbiSrc->biClrUsed = (1 << (int)lpbiSrc->biBitCount);

    pal.palVersion = 0x300;
    pal.palNumEntries = (int)lpbiSrc->biClrUsed;

    for (i=0; i<(int)pal.palNumEntries; i++)
    {
        pal.palPalEntry[i].peRed   = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbRed;
        pal.palPalEntry[i].peGreen = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbGreen;
        pal.palPalEntry[i].peBlue  = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbBlue;
        pal.palPalEntry[i].peFlags = 0;
    }

    if (lpbiDst->biBitCount == 8)
        _fmemcpy(lpbiDst+1,lpbiSrc+1,(int)lpbiSrc->biClrUsed*sizeof(RGBQUAD));

    if (lpbiDst->biBitCount == 4)
        _fmemcpy(lpbiDst+1,VGAColors,sizeof(VGAColors));

    if ((int)lpbiDst->biBitCount == (int)lpbiSrc->biBitCount)
    {
        switch((int)lpbiSrc->biBitCount)
        {
        case 1:
            dx = dx / 8;    // dx is byte count

            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

                for (x=0; x<dx; x += 2)
                {
                    b0 = pbSrc[x];
                    b1 = pbSrc[x + WidthBytesSrc];

                    b = (BYTE)(
                        (SumMono(bit(b0,7), bit(b0,6), bit(b1,7), bit(b1,6)) << 7) |
                        (SumMono(bit(b0,5), bit(b0,4), bit(b1,5), bit(b1,4)) << 6) |
                        (SumMono(bit(b0,3), bit(b0,2), bit(b1,3), bit(b1,2)) << 5) |
                        (SumMono(bit(b0,1), bit(b0,0), bit(b1,1), bit(b1,0)) << 4));

                    b0 = pbSrc[x + 1];
                    b1 = pbSrc[x + 1 + WidthBytesSrc];

                    b |=(SumMono(bit(b0,7), bit(b0,6), bit(b1,7), bit(b1,6)) << 3) |
                        (SumMono(bit(b0,5), bit(b0,4), bit(b1,5), bit(b1,4)) << 2) |
                        (SumMono(bit(b0,3), bit(b0,2), bit(b1,3), bit(b1,2)) << 1) |
                        (SumMono(bit(b0,1), bit(b0,0), bit(b1,1), bit(b1,0)) << 0);

                    *pb++ = b;
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 4:
            dx = dx / 2;    // dx is byte count

            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

                for (x=0; x<dx; x+=2)
                {
                    b0 = pbSrc[x];
                    b1 = pbSrc[x + WidthBytesSrc];

                    rgb = SumRGB((b0 >> 4),(b0 & 0x0F),
                                 (b1 >> 4),(b1 & 0x0F));

                    b = (BYTE)(MapVGA(GetRValue(rgb),GetGValue(rgb),GetBValue(rgb)) << 4);

                    b0 = pbSrc[x + 1];
                    b1 = pbSrc[x + 1 + WidthBytesSrc];

                    rgb = SumRGB((b0 >> 4),(b0 & 0x0F),
                                 (b1 >> 4),(b1 & 0x0F));

                    b |= MapVGA(GetRValue(rgb),GetGValue(rgb),GetBValue(rgb));

                    *pb++ = b;
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;
#if 0
        case 8:
            {
            HPALETTE hpal;

            hpal = CreatePalette((LPLOGPALETTE)&pal);

            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

                for (x=0; x<dx; x+=2)
                {
                    b0 = Pel8(pbSrc,x);
                    b1 = Pel8(pbSrc+WidthBytesSrc, x);
                    b2 = Pel8(pbSrc,x+1);
                    b3 = Pel8(pbSrc+WidthBytesSrc,x+1);

                    *pb++ = (BYTE)GetNearestPaletteIndex(hpal,
                        SumRGB(b0,b1,b2,b3));
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }

            DeleteObject(hpal);
            }
            break;
#endif
        case 16:
            for (y=0; y<dy; y+=2)
            {
                pw = (LPWORD)pbDst;

for (x=0; x<dx; x += 2)
                {
                    w0 = Pel16(pbSrc,x);
                    w1 = Pel16(pbSrc,x+1);
                    w2 = Pel16(pbSrc+WidthBytesSrc,x);
                    w3 = Pel16(pbSrc+WidthBytesSrc,x+1);

                    r = ((BYTE)RGB16r(w0) + RGB16r(w1) + RGB16r(w2) + RGB16r(w3)) >> 2;
                    g = ((BYTE)RGB16g(w0) + RGB16g(w1) + RGB16g(w2) + RGB16g(w3)) >> 2;
                    b = ((BYTE)RGB16b(w0) + RGB16b(w1) + RGB16b(w2) + RGB16b(w3)) >> 2;

                    *pw++ = rgb16(r,g,b);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 24:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

for (x=0; x<dx; x += 2)
                {
                    rgb0 = Pel24(pbSrc,x);
                    rgb1 = Pel24(pbSrc,x+1);
                    rgb2 = Pel24(pbSrc+WidthBytesSrc,x);
                    rgb3 = Pel24(pbSrc+WidthBytesSrc,x+1);

                    rgb = RGB(
                        ((UINT)rgb0.r + rgb1.r + rgb2.r + rgb3.r)/4,
                        ((UINT)rgb0.g + rgb1.g + rgb2.g + rgb3.g)/4,
                        ((UINT)rgb0.b + rgb1.b + rgb2.b + rgb3.b)/4);

                    *pb++ = GetBValue(rgb);
                    *pb++ = GetGValue(rgb);
                    *pb++ = GetRValue(rgb);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        default:
            return FALSE;
        }
    }
    else if ((int)lpbiDst->biBitCount == 24)
    {
        switch((int)lpbiSrc->biBitCount)
        {
        case 1:
        case 4:
        case 8:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

for (x=0; x<dx; x += 2)
                {
                    b0 = Pel(pbSrc,x);
                    b1 = Pel(pbSrc,x+1);
                    b2 = Pel(pbSrc+WidthBytesSrc,x);
                    b3 = Pel(pbSrc+WidthBytesSrc,x+1);

                    rgb = SumRGB(b0,b1,b2,b3);

                    *pb++ = GetBValue(rgb);
                    *pb++ = GetGValue(rgb);
                    *pb++ = GetRValue(rgb);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 16:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

for (x=0; x<dx; x += 2)
                {
                    w0 = Pel16(pbSrc,x);
                    w1 = Pel16(pbSrc,x+1);
                    w2 = Pel16(pbSrc+WidthBytesSrc,x);
                    w3 = Pel16(pbSrc+WidthBytesSrc,x+1);

                    r = (RGB16R(w0) + RGB16R(w1) + RGB16R(w2) + RGB16R(w3)) / 4;
                    g = (RGB16G(w0) + RGB16G(w1) + RGB16G(w2) + RGB16G(w3)) / 4;
                    b = (RGB16B(w0) + RGB16B(w1) + RGB16B(w2) + RGB16B(w3)) / 4;

                    rgb = RGB(r,g,b);

                    *pb++ = GetBValue(rgb);
                    *pb++ = GetGValue(rgb);
                    *pb++ = GetRValue(rgb);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        default:
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }

    return TRUE;
}


GUID.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#defineINITGUID
#ifndef WIN32
#include <ole2.h>
#endif
#include <vfw.h>


AUDPLAY.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*--------------------------------------------------------------+
| audplay.c Simple routines to play audio using an AVIStream to |
| get data.  Uses global variables, so only one instance at a|
| time.  (Usually, there's only one sound card, so this isn't|
| so bad).|
+--------------------------------------------------------------*/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <vfw.h>

#include "audplay.h"
#include "muldiv32.h"

/*--------------------------------------------------------------+
| ****************** AUDIO PLAYING SUPPORT ******************** |
+--------------------------------------------------------------*/

staticHWAVEOUTshWaveOut = 0;/* Current MCI device ID */
staticLONGslBegin;
staticLONGslCurrent;
staticLONGslEnd;
staticBOOLsfLooping;
staticBOOLsfPlaying = FALSE;

#define MAX_AUDIO_BUFFERS16
#define MIN_AUDIO_BUFFERS2
#define AUDIO_BUFFER_SIZE16384

staticUINTswBuffers;    // total # buffers
staticUINTswBuffersOut;    // buffers device has
staticUINTswNextBuffer;    // next buffer to fill
staticLPWAVEHDRsalpAudioBuf[MAX_AUDIO_BUFFERS];

staticPAVISTREAMspavi;    // stream we're playing
staticLONGslSampleSize;    // size of an audio sample

staticLONGsdwBytesPerSec;
staticLONGsdwSamplesPerSec;

/*---------------------------------------------------------------+
| aviaudioCloseDevice -- close the open audio device, if any.    |
+---------------------------------------------------------------*/
void aviaudioCloseDevice(void)
{
    UINTw;

    if (shWaveOut) {
while (swBuffers > 0) {
    --swBuffers;
    waveOutUnprepareHeader(shWaveOut, salpAudioBuf[swBuffers],
sizeof(WAVEHDR));
    GlobalFreePtr((LPBYTE) salpAudioBuf[swBuffers]);
}

w = waveOutClose(shWaveOut);

// DPF("AudioCloseDevice: waveOutClose returns %u\n", w);
shWaveOut = NULL;
    }
}

/*--------------------------------------------------------------+
| aviaudioOpenDevice -- get ready to play waveform data.|
+--------------------------------------------------------------*/
BOOL aviaudioOpenDevice(HWND hwnd, PAVISTREAM pavi)
{
    UINTw;
    LPVOIDlpFormat;
    LONGcbFormat;
    AVISTREAMINFOstrhdr;

    if (!pavi)// no wave data to play
return FALSE;

    if (shWaveOut)// already something playing
return TRUE;

    spavi = pavi;

    AVIStreamInfo(pavi, &strhdr, sizeof(strhdr));

    slSampleSize = (LONG) strhdr.dwSampleSize;
    if (slSampleSize <= 0 || slSampleSize > AUDIO_BUFFER_SIZE)
return FALSE;

    AVIStreamFormatSize(pavi, 0, &cbFormat);

    lpFormat = GlobalAllocPtr(GHND, cbFormat);
    if (!lpFormat)
return FALSE;

    AVIStreamReadFormat(pavi, 0, lpFormat, &cbFormat);

    sdwSamplesPerSec = ((LPWAVEFORMAT) lpFormat)->nSamplesPerSec;
    sdwBytesPerSec = ((LPWAVEFORMAT) lpFormat)->nAvgBytesPerSec;

    w = waveOutOpen(&shWaveOut, WAVE_MAPPER, lpFormat,
(DWORD) (UINT) hwnd, 0L, CALLBACK_WINDOW);

    //
    // Maybe we failed because someone is playing sound already.
    // Shut any sound off, and try once more before giving up.
    //
    if (w) {
sndPlaySound(NULL, 0);
w = waveOutOpen(&shWaveOut, WAVE_MAPPER, lpFormat,
(DWORD) (UINT) hwnd, 0L, CALLBACK_WINDOW);
    }

//    DPF("waveOutOpen returns %u, shWaveOut = %u\n", w, shWaveOut);

    if (w != 0) {
/* Show error message here? */

return FALSE;
    }

    for (swBuffers = 0; swBuffers < MAX_AUDIO_BUFFERS; swBuffers++) {
if (!(salpAudioBuf[swBuffers] =
(LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
(DWORD)(sizeof(WAVEHDR) + AUDIO_BUFFER_SIZE))))
    break;
salpAudioBuf[swBuffers]->dwFlags = WHDR_DONE;
salpAudioBuf[swBuffers]->lpData = (LPBYTE) salpAudioBuf[swBuffers]
    + sizeof(WAVEHDR);
salpAudioBuf[swBuffers]->dwBufferLength = AUDIO_BUFFER_SIZE;
if (!waveOutPrepareHeader(shWaveOut, salpAudioBuf[swBuffers],
sizeof(WAVEHDR)))
    continue;

GlobalFreePtr((LPBYTE) salpAudioBuf[swBuffers]);
break;
    }

    // DPF("Allocated %u %lu-byte buffers.\n", swBuffers, (DWORD) AUDIO_BUFFER_SIZE);

    if (swBuffers < MIN_AUDIO_BUFFERS) {
aviaudioCloseDevice();
return FALSE;
    }

    swBuffersOut = 0;
    swNextBuffer = 0;

    sfPlaying = FALSE;

    return TRUE;
}

//
// Return the time in milliseconds corresponding to the currently playing
// audio sample, or -1 if no audio is playing.
// WARNING: Some sound cards are pretty inaccurate!
//
LONG aviaudioTime(void)
{
    MMTIMEmmtime;

    if (!sfPlaying)
return -1;

    mmtime.wType = TIME_SAMPLES;

    waveOutGetPosition(shWaveOut, &mmtime, sizeof(mmtime));

    if (mmtime.wType == TIME_SAMPLES)
return AVIStreamSampleToTime(spavi, slBegin)
+ muldiv32(mmtime.u.sample, 1000, sdwSamplesPerSec);
    else if (mmtime.wType == TIME_BYTES)
return AVIStreamSampleToTime(spavi, slBegin)
+ muldiv32(mmtime.u.cb, 1000, sdwBytesPerSec);
    else
return -1;
}

//
// Fill up any empty audio buffers and ship them out to the device.
//
BOOL aviaudioiFillBuffers(void)
{
    LONGlRead;
    UINTw;
    LONGlSamplesToPlay;

    /* We're not playing, so do nothing. */
    if (!sfPlaying)
return TRUE;

    // DPF3("%u/%u (%lu-%lu)\n", swBuffersOut, swBuffers, slCurrent, slEnd);

    while (swBuffersOut < swBuffers) {
if (slCurrent >= slEnd) {
    if (sfLooping) {
/* Looping, so go to the beginning. */
slCurrent = slBegin;
    } else
break;
}

/* Figure out how much data should go in this buffer */
lSamplesToPlay = slEnd - slCurrent;
if (lSamplesToPlay > AUDIO_BUFFER_SIZE / slSampleSize)
    lSamplesToPlay = AUDIO_BUFFER_SIZE / slSampleSize;

AVIStreamRead(spavi, slCurrent, lSamplesToPlay,
      salpAudioBuf[swNextBuffer]->lpData,
      AUDIO_BUFFER_SIZE,
      &(LONG)salpAudioBuf[swNextBuffer]->dwBufferLength,
      &lRead);

if (lRead != lSamplesToPlay) {
    // DPF("Error from WAVE_READ\n");
    return FALSE;
}
slCurrent += lRead;

w = waveOutWrite(shWaveOut, salpAudioBuf[swNextBuffer],sizeof(WAVEHDR));

if (w != 0) {
    // DPF("Error from waveOutWrite\n");
    return FALSE;
}

++swBuffersOut;
++swNextBuffer;
if (swNextBuffer >= swBuffers)
    swNextBuffer = 0;
    }

    if (swBuffersOut == 0 && slCurrent >= slEnd)
aviaudioStop();

    /* We've filled all of the buffers we can or want to. */
    return TRUE;
}

/*--------------------------------------------------------------+
| aviaudioPlay -- Play audio, starting at a given frame|
||
+--------------------------------------------------------------*/
BOOL aviaudioPlay(HWND hwnd, PAVISTREAM pavi, LONG lStart, LONG lEnd, BOOL fWait)
{
    if (lStart < 0)
lStart = AVIStreamStart(pavi);

    if (lEnd < 0)
lEnd = AVIStreamEnd(pavi);

    // DPF2("Audio play%s from %ld to %ld (samples)\n", ((LPSTR) (fWait ? " wait" : "")), lStart, lEnd);

    if (lStart >= lEnd)
return FALSE;

    if (!aviaudioOpenDevice(hwnd, pavi))
return FALSE;

    if (!sfPlaying) {

//
// We're beginning play, so pause until we've filled the buffers
// for a seamless start
//
waveOutPause(shWaveOut);

slBegin = lStart;
slCurrent = lStart;
slEnd = lEnd;
sfPlaying = TRUE;
    } else {
if (lStart > slEnd) {
    // DPF("Gap in wave that is supposed to be played!\n");
}
slEnd = lEnd;
    }

//    sfLooping = fLoop;

    aviaudioiFillBuffers();

    //
    // Now unpause the audio and away it goes!
    //
    waveOutRestart(shWaveOut);

    //
    // Caller wants us not to return until play is finished
    //
    if (fWait) {
while (swBuffersOut > 0)
    Yield();
    }

    return TRUE;
}

/*--------------------------------------------------------------+
| aviaudioMessage -- handle wave messages received by|
| window controlling audio playback.  When audio buffers are|
| done, this routine calls aviaudioiFillBuffers to fill them|
| up again.|
+--------------------------------------------------------------*/
void aviaudioMessage(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == MM_WOM_DONE) {
--swBuffersOut;
aviaudioiFillBuffers();
    }
}

/*--------------------------------------------------------------+
| aviaudioStop -- stop playing, close the device.|
+--------------------------------------------------------------*/
void aviaudioStop(void)
{
    UINTw;

    if (shWaveOut != 0) {

w = waveOutReset(shWaveOut);

sfPlaying = FALSE;

// DPF("AudioStop: waveOutReset() returns %u \n", w);

aviaudioCloseDevice();
    }
}


REGOBJS.CPP

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#ifndef WIN32
#include <ole2.h>
#endif
#include <string.h>
#include <vfw.h>
#include "aviview.h"
#include "regobjs.h"

/*
** The usage counter keeps track of the overall usage of objects based on
** implementations provided by the component. This allows one to determine
** when the implementation is no longer in use.
*/

UINTuUseCount;
BOOLfLocked;

IClassFactory FAR * gpCF;
DWORD    gdwRegistered;

extern "C" BOOL RegisterObjects(void);
extern "C" void RevokeObjects(void);

BOOL RegisterObjects(void)
{
#if 0
    HRESULT hresult;
#endif

    // OleInitialize(NULL);
#if 0
    CAppCF::MakeInst(CLSID_ACMCmprsStream, IID_IClassFactory,
   (void FAR * FAR *) &gpCF);

    hresult = CoRegisterClassObject(CLSID_ACMCmprsStream,
  gpCF,
  CLSCTX_INPROC_SERVER,
  REGCLS_MULTIPLEUSE,
  &gdwRegistered);
    if (FAILED(GetScode(hresult))) {
delete gpCF;
gpCF = NULL;
return FALSE;
    }
#endif

    return TRUE;
}

void RevokeObjects(void)
{
#if 0
    CoRevokeClassObject(gdwRegistered);
#endif

    // OleUninitialize();
}
/*--------*/

HRESULT CAppCF::MakeInst(
const CLSID FAR&rclsid,
const IID FAR&riid,
void FAR* FAR*ppv)
{
CAppCF FAR*pAppCF;
IUnknown FAR*pUnknown;
HRESULT hresult;

pAppCF = new FAR CAppCF(rclsid, &pUnknown);
if (pAppCF == NULL)
return ResultFromScode(E_OUTOFMEMORY);
hresult = pUnknown->QueryInterface(riid, ppv);
if (FAILED(GetScode(hresult)))
delete pAppCF;
return hresult;
}

/*--------*/

CAppCF::CAppCF(
const CLSID FAR&rclsid,
IUnknown FAR* FAR*ppUnknown)
{
m_clsid = rclsid;
m_refs = 0;
*ppUnknown = this;
}

/*--------*/

STDMETHODIMP CAppCF::QueryInterface(
const IID FAR&iid,
void FAR* FAR*ppv)
{
if (iid == IID_IUnknown)
*ppv = this;
else if (iid == IID_IClassFactory)
*ppv = this;
else
return ResultFromScode(E_NOINTERFACE);
AddRef();
return NULL;
}

/*--------*/

STDMETHODIMP_(ULONG) CAppCF::AddRef()
{
return ++m_refs;
}

/*--------*/

STDMETHODIMP_(ULONG) CAppCF::Release()
{
if (!--m_refs) {
delete this;
return 0;
}
return m_refs;
}

/*--------*/

STDMETHODIMP CAppCF::CreateInstance(
IUnknown FAR*pUnknownOuter,
const IID FAR&riid,
void FAR* FAR*ppv)
{
#if 0
if (IsEqualCLSID(m_clsid, CLSID_ACMCmprsStream)) {
return CACMCmpStream::MakeInst(pUnknownOuter, riid, ppv);
}
else
#endif
{
return ResultFromScode(CO_E_CANTDETERMINECLASS); // !!!
}
}

/*--------*/

STDMETHODIMP CAppCF::LockServer(
BOOLfLock)
{
fLocked = fLock;
return NULL;
}

/*--------*/


REGOBJS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/*
** _StdClassImplementations
** Defines the standard implementations for a class object.
*/

#define_StdClassImplementations(Impl)\
STDMETHODIMP QueryInterface(const IID FAR& riid, void FAR* FAR* ppv);\
STDMETHODIMP_(ULONG) AddRef();\
STDMETHODIMP_(ULONG) Release()

/*--------*/

class FAR CAppCF : IClassFactory {
public:
    static HRESULT MakeInst(const CLSID FAR& rclsid, const IID FAR& riid, void FAR* FAR* ppv);
private:
    CAppCF(const CLSID FAR& rclsid, IUnknown FAR* FAR* ppUnknown);
    ULONGm_refs;

public:
    _StdClassImplementations(CAppCF);
    STDMETHODIMP CreateInstance(IUnknown FAR* pUnknownOuter, const IID FAR& riid, void FAR* FAR* ppv);
    STDMETHODIMP LockServer(BOOL fLock);
public:
    CLSIDm_clsid;
};

/*
** The usage counter keeps track of the overall usage of objects based on
** implementations provided by the component. This allows one to determine
** when the implementation is no longer in use.
*/

extern UINTuUseCount;
extern BOOLfLocked;


README.TXT

The CAPTEST sample is a sample capture application that uses the AVICap capture
window class.


CAPTEST.H

/****************************************************************************
 *
 *   captest.h: Include file for the CapTest Sample Program
 *
 *   Microsoft Video for Windows Capture Class Sample Program
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#include "dialogs.h"

//
// General purpose constants...
//
#define MAXVIDDRIVERS            10
#define MS_FOR_15FPS             66
#define DEF_PALNUMFRAMES         10
#define DEF_PALNUMCOLORS         236L
#define ONEMEG                   (1024L * 1024L)

//
// Menu Ids...
//
#define IDM_FILE                  100
#define IDM_F_SETCAPTUREFILE      101
#define IDM_F_SAVEVIDEOAS         102
#define IDM_F_ALLOCATESPACE       103
#define IDM_F_EXIT                104

#define IDM_EDIT                  110
#define IDM_E_COPY                111
#define IDM_E_PASTEPALETTE        112

#define IDM_CAPTURE               120
#define IDM_C_CAPTUREVIDEO        121
#define IDM_C_CAPTUREFRAME        122
#define IDM_C_CAPTURESETTINGS     123

#define IDM_OPTIONS               130
#define IDM_O_PREVIEW             131
#define IDM_O_OVERLAY             132
#define IDM_O_AUDIOFORMAT         133
#define IDM_O_VIDEOFORMAT         134
#define IDM_O_VIDEOSOURCE         135
#define IDM_O_VIDEODISPLAY        136
#define IDM_O_PALETTE             137
#define IDM_O_CHOOSECOMPRESSOR    140

// !!! Leave some room after this define for the drivers
#define IDM_O_DRIVERS             150
#define IDM_O_DRIVERS1            151
#define IDM_O_DRIVERS2            152
#define IDM_O_DRIVERS3            153
#define IDM_O_DRIVERS4            154
#define IDM_O_DRIVERS5            155
#define IDM_O_DRIVERS6            156
#define IDM_O_DRIVERS7            157
#define IDM_O_DRIVERS8            158
#define IDM_O_DRIVERS9            159
// The above are just reserved dummy IDs.

#define IDM_HELP                  190
#define IDM_H_ABOUT               191

#define IDS_ERR_REGISTER_CLASS   1
#define IDS_ERR_CREATE_WINDOW    2

//
// Macro Definitions...
//
#define IsDriverIndex(w) ( ((w) >= IDM_O_DRIVERS)  &&  \
                           ((w) - IDM_O_DRIVERS < MAXVIDDRIVERS) )

#define Width(X)  ((X).right - (X).left)
#define Height(X) ((X).bottom - (X).top)

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//
//  Win 16/32 portability stuff...
//
//
//
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

#ifndef WIN32
    #ifndef FNLOCAL
        #define FNLOCAL     NEAR PASCAL
        #define FNCLOCAL    NEAR _cdecl
        #define FNGLOBAL    FAR PASCAL
        #define FNCGLOBAL   FAR _cdecl
    #ifdef _WINDLL
        #define FNWCALLBACK FAR PASCAL __loadds
        #define FNEXPORT    FAR PASCAL __export
    #else
        #define FNWCALLBACK FAR PASCAL __loadds
        #define FNEXPORT    FAR PASCAL __export
    #endif
    #endif

    //
    //
    //
    //
    #ifndef FIELD_OFFSET
    #define FIELD_OFFSET(type, field)    ((LONG)&(((type *)0)->field))
    #endif

    //
    //  based code makes since only in win 16 (to try and keep stuff out of
    //  our fixed data segment...
    //
    #define BCODE           _based(_segname("_CODE"))

    #define HUGE            _huge

    //
    //  stuff for Unicode in Win 32--make it a noop in Win 16
    //
    #ifndef _TCHAR_DEFINED
        #define _TCHAR_DEFINED
        typedef char            TCHAR, *PTCHAR;
        typedef unsigned char   TBYTE, *PTUCHAR;

        typedef PSTR            PTSTR, PTCH;
        typedef LPSTR           LPTSTR, LPTCH;
        typedef LPCSTR          LPCTSTR;
    #endif

    #define TEXT(a)         a
    #define SIZEOF(x)       sizeof(x)
    #define SIZEOFACMSTR(x) sizeof(x)
#else
    #ifndef FNLOCAL
        #define FNLOCAL     _stdcall
        #define FNCLOCAL    _stdcall
        #define FNGLOBAL    _stdcall
        #define FNCGLOBAL   _stdcall
        #define FNWCALLBACK CALLBACK
        #define FNEXPORT    CALLBACK
    #endif

    #ifndef _CRTAPI1
    #define _CRTAPI1    __cdecl
    #endif
    #ifndef _CRTAPI2
    #define _CRTAPI2    __cdecl
    #endif
    #ifndef try
    #define try         __try
    #define leave       __leave
    #define except      __except
    #define finally     __finally
    #endif

    //
    //  there is no reason to have based stuff in win 32
    //
    #define BCODE

    #define HUGE
    #define HTASK                   HANDLE
    #define SELECTOROF(a)           (a)
    typedef LRESULT (CALLBACK* DRIVERPROC)(DWORD, HDRVR, UINT, LPARAM, LPARAM);

    //
    //  for compiling Unicode
    //
    #ifdef UNICODE
        #define SIZEOF(x)   (sizeof(x)/sizeof(WCHAR))
    #else
        #define SIZEOF(x)   sizeof(x)
    #endif
    #define SIZEOFACMSTR(x)(sizeof(x)/sizeof(WCHAR))
#endif

//
// Global Variables...
//
extern TCHAR           gachAppName[] ;
extern TCHAR           gachIconName[] ;
extern TCHAR           gachMenuName[] ;
extern TCHAR           gachString[] ;
extern TCHAR           gachMCIDeviceName[] ;

extern HINSTANCE      ghInstApp ;
extern HWND           ghWndMain ;
extern HWND           ghWndCap ;
extern HANDLE         ghAccel ;
extern WORD           gwDeviceIndex ;
extern WORD           gwPalFrames ;
extern WORD           gwPalColors ;
extern WORD           gwCapFileSize ;

extern CAPSTATUS      gCapStatus ;
extern CAPDRIVERCAPS  gCapDriverCaps ;
extern CAPTUREPARMS   gCapParms ;

extern LPWAVEFORMATEX glpwfex ;

//
// Dialog Box Procedures...
//
int FAR PASCAL AboutProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL AudioFormatProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL CapSetUpProc(HWND, UINT, UINT, LONG) ;
BOOL CALLBACK MakePaletteProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL AllocCapFileProc(HWND, UINT, UINT, LONG) ;


DIALOGS.C

/****************************************************************************
 *
 *   dialogs.c: Code for dialog procs of CapTest Sample Program
 *
 *   Microsoft Video for Windows Capture Class Test Program
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <vfw.h>
#include <mmreg.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <dos.h>

#include "captest.h"

static long GetFreeDiskSpace(WORD) ;
static int  CountMCIDevices(WORD) ;

//
// AboutProc: About Dialog Box Procedure
//
int FAR PASCAL AboutProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    switch (Message) {
        case WM_INITDIALOG :
             return TRUE ;

        case WM_COMMAND :
            switch (wParam) {
                case IDOK :
                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;
}

//
// AudioFormatProc: Audio Format Setting Dialog Box Procedure
//
int FAR PASCAL AudioFormatProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int                nChannels ;
    static WORD               wSample ;
    static DWORD              dwFrequency ;

    switch (Message) {
        case WM_INITDIALOG :
            nChannels = IDD_ChannelIDs + glpwfex->nChannels ;
            CheckRadioButton(hDlg, IDD_ChannelMono, IDD_ChannelStereo, nChannels) ;
            wSample = IDD_SampleIDs + glpwfex->wBitsPerSample / 8 ;
            CheckRadioButton(hDlg, IDD_Sample8Bit, IDD_Sample16Bit, wSample) ;
            dwFrequency = IDD_FreqIDs + glpwfex->nSamplesPerSec / 11025 ;
            CheckRadioButton(hDlg, IDD_Freq11kHz, IDD_Freq44kHz, (WORD)dwFrequency) ;
            return TRUE ;

        case WM_COMMAND :
            switch (wParam) {
                case IDOK :
                    if (IsDlgButtonChecked(hDlg, IDD_ChannelMono))
                        nChannels = 1 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_ChannelStereo))
                            nChannels = 2 ;
                        else {
                            MessageBeep(MB_ICONEXCLAMATION) ;
                            return FALSE ;
                        }

                    if (IsDlgButtonChecked(hDlg, IDD_Sample8Bit))
                        wSample = 8 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_Sample16Bit))
                            wSample = 16 ;
                        else {
                            MessageBeep(MB_ICONEXCLAMATION) ;
                            return FALSE ;
                        }

                    if (IsDlgButtonChecked(hDlg, IDD_Freq11kHz))
                        dwFrequency = 11025 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_Freq22kHz))
                            dwFrequency = 22050 ;
                        else
                            if (IsDlgButtonChecked(hDlg, IDD_Freq44kHz))
                                dwFrequency = 44100 ;
                            else {
                                MessageBeep(MB_ICONEXCLAMATION) ;
                                return FALSE ;
                            }

                    // All the entries verfied OK -- save them now
                    glpwfex->nChannels = nChannels ;
                    glpwfex->wBitsPerSample = wSample ;
                    glpwfex->nSamplesPerSec = dwFrequency ;
                    glpwfex->nBlockAlign =  glpwfex->nChannels * (glpwfex->wBitsPerSample / 8) ;
                    glpwfex->nAvgBytesPerSec = (long) glpwfex->nSamplesPerSec *
                                                      glpwfex->nBlockAlign ;
                    glpwfex->cbSize = 0 ;
                    glpwfex->wFormatTag = WAVE_FORMAT_PCM ;
                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;
}

//
// GetFreeDiskSpaceInKB: Function to Measure Available Disk Space
//
static long GetFreeDiskSpaceInKB(LPTSTR pFile)
{
    DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters;
    char RootName[MAX_PATH];
    LPSTR ptmp;    //required arg

    // need to find path for root directory on drive containing
    // this file.

    GetFullPathName(pFile, sizeof(RootName)/sizeof(RootName[0]), RootName, &ptmp);

    // truncate this to the name of the root directory (god how tedious)
    if ((RootName[0] == TEXT('\\')) && (RootName[1] == TEXT('\\'))) {

        // path begins with  \\server\share\path so skip the first
        // three backslashes
        ptmp = &RootName[2];
        while (*ptmp && (*ptmp != TEXT('\\'))) {
            ptmp++;
        }
        if (*ptmp) {
            // advance past the third backslash
            ptmp++;
        }
    } else {
        // path must be drv:\path
        ptmp = RootName;
    }

    // find next backslash and put a null after it
    while (*ptmp && (*ptmp != TEXT('\\'))) {
        ptmp++;
    }
    // found a backslash ?
    if (*ptmp) {
        // skip it and insert null
        ptmp++;
        *ptmp = TEXT('\0');
    }

    if (!GetDiskFreeSpace(RootName,
&dwSectorsPerCluster,
&dwBytesPerSector,
&dwFreeClusters,
&dwClusters)) {
    MessageBox(NULL, TEXT("Can't measure free disk space."), TEXT("Error"),
    MB_OK | MB_ICONINFORMATION);
    return (-1);
    }
    return(MulDiv (dwSectorsPerCluster * dwBytesPerSector,
   dwFreeClusters,
   1024));
}

//
// AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure
//
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int      nFreeMBs = 0 ;

    switch (Message) {
        case WM_INITDIALOG :
        {
            long             lFileSize = 0 ;
            long             lFreeSpaceInKB ;
            TCHAR     achCapFile[_MAX_PATH] ;
            HANDLE           hFile;

            // Get current capture file name and measure its size
            capFileGetCaptureFile(ghWndCap, achCapFile, sizeof(achCapFile) / sizeof(TCHAR)) ;
            hFile = CreateFile(
                            achCapFile,
                            GENERIC_READ,
                            0,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
            if (hFile != INVALID_HANDLE_VALUE) {
                if ((lFileSize = GetFileSize(hFile, NULL)) == -1) {
                    MessageBox(NULL, TEXT("Couldn't find size of current capture file"),
                            TEXT("ERROR"),
                            MB_OK | MB_ICONEXCLAMATION);
                    lFileSize = 0;
                }
                CloseHandle(hFile);
            }

            // Get free disk space and add current capture file size to that.
            // Convert the available space to MBs.
            if ((lFreeSpaceInKB = GetFreeDiskSpaceInKB(achCapFile)) != -1L) {
                lFreeSpaceInKB += lFileSize / 1024 ;
                nFreeMBs = lFreeSpaceInKB / 1024 ;
                SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ;
            } else {

                EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE);

            }

            gwCapFileSize = (WORD) (lFileSize / ONEMEG);

            SetDlgItemInt(hDlg, IDD_SetCapFileSize, gwCapFileSize, TRUE) ;
            return TRUE ;
        }

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK :
                {
                    int         iCapFileSize ;

                    iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ;
                    if (iCapFileSize <= 0 || iCapFileSize > nFreeMBs) {
                        // You are asking for more than we have !! Sorry, ...
                        SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    gwCapFileSize = iCapFileSize ;

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;

                case IDD_SetCapFileSize:
                {
                    long l;
                    BOOL bchanged;
                    char achBuffer[21];

                    // check that entered size is a valid number
                    GetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer, sizeof(achBuffer));
                    l = atol(achBuffer);
                    bchanged = FALSE;
                    if (l < 1) {
                        l = 1;
                        bchanged = TRUE;
                    } else if (l > nFreeMBs) {
                        l = nFreeMBs;
                        bchanged = TRUE;
                    } else {
                        // make sure there are no non-digit chars
                        // atol() will ignore trailing non-digit characters
                        int c = 0;
                        while (achBuffer[c]) {
                            if (IsCharAlpha(achBuffer[c]) ||
                                !IsCharAlphaNumeric(achBuffer[c])) {

                                // string contains non-digit chars - reset
                                l = 1;
                                bchanged = TRUE;
                                break;
                            }
                            c++;
                        }
                    }
                    if (bchanged) {
                        wsprintf(achBuffer, "%ld", l);
                        SetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer);
                    }
                    break;
                }
            }
            break ;
    }

    return FALSE ;

}

//
// MakePaletteProc: Palette Details Dialog Box Procedure
//
BOOL CALLBACK MakePaletteProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    switch (Message) {
        case WM_INITDIALOG :
            SetDlgItemInt(hDlg, IDD_MakePalColors, gwPalColors, FALSE) ;
            SetDlgItemInt(hDlg, IDD_MakePalFrames, gwPalFrames, FALSE) ;
            return TRUE ;

        case WM_COMMAND :
            switch (wParam) {
                case IDOK :
                {
                    int         iColors ;
                    int         iFrames ;

                    iColors = (int) GetDlgItemInt(hDlg, IDD_MakePalColors, NULL, TRUE) ;
                    if (! (iColors > 0 && iColors <= 236 || iColors == 256)) {
                        // invalid number of palette colors
                        SetDlgItemInt(hDlg, IDD_MakePalColors, iColors, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_MakePalColors)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    iFrames = (int) GetDlgItemInt(hDlg, IDD_MakePalFrames, NULL, TRUE) ;
                    if (iFrames <= 0 || iFrames > 10000) {
                        // no frame or way t-o-o many frames !!!
                        SetDlgItemInt(hDlg, IDD_MakePalFrames, iFrames, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_MakePalFrames)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    gwPalColors = iColors ;
                    gwPalFrames = iFrames ;

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;

}

//
// CountMCIDevices: Function to Find the Number of MCI Devices of a Type
//
static int CountMCIDevices(WORD wType)
{
    int               nTotal = 0 ;
    DWORD             dwCount ;
    MCI_SYSINFO_PARMS mciSIP ;

    mciSIP.dwCallback = 0 ;
    mciSIP.lpstrReturn = (LPTSTR)(LPVOID) &dwCount ;
    mciSIP.dwRetSize = sizeof(DWORD) ;
    mciSIP.wDeviceType = wType ;

    // Use an MCI command to get the info
    if (! mciSendCommand(0, MCI_SYSINFO, MCI_SYSINFO_QUANTITY,
                         (DWORD)(LPVOID) &mciSIP))
        nTotal = (int) *((LPDWORD) mciSIP.lpstrReturn) ;

    return nTotal ;
}

//
// CapSetUpProc: Capture SetUp Details Dialog Box Procedure
//
int FAR PASCAL CapSetUpProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    BOOL            fValue ;
    static TCHAR     achBuffer[21] ;

    switch (Message) {
        case WM_INITDIALOG :
        {
            WORD        wValue ;

            // Convert from MicroSecPerFrame to FPS -- that's easier !!
            wValue = (int) (1E+6 / gCapParms.dwRequestMicroSecPerFrame + 0.5) ;
            SetDlgItemInt(hDlg, IDD_FrameRateData, wValue, FALSE) ;
            if (gCapParms.fAbortLeftMouse)
                wValue = IDD_AbortLeftFlag ;
            else
                if (gCapParms.fAbortRightMouse)
                    wValue = IDD_AbortRightFlag ;
                else
                    wValue = 0 ;  // none of the mouse-buttons !!!
            CheckRadioButton(hDlg, IDD_AbortLeftFlag, IDD_AbortRightFlag, wValue) ;

            if (gCapParms.vKeyAbort == VK_ESCAPE)
                wValue = IDD_AbortEscFlag ;
            else
                if (gCapParms.vKeyAbort == VK_SPACE)
                    wValue = IDD_AbortSpaceFlag ;
                else
                    if (gCapParms.vKeyAbort == VK_F8)
                        wValue = IDD_AbortF8Flag ;
                    else
                        wValue = 0 ;  // none of the keys !!!
            CheckRadioButton(hDlg, IDD_AbortEscFlag, IDD_AbortF8Flag, wValue) ;

            // If time limit isn't anabled, disable the time data part
            CheckDlgButton(hDlg, IDD_TimeLimitFlag, (fValue = gCapParms.fLimitEnabled)) ;
            EnableWindow(GetDlgItem(hDlg, IDD_SecondsText), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_SecondsData), fValue) ;
            if (fValue)
                SetDlgItemInt(hDlg, IDD_SecondsData, gCapParms.wTimeLimit, FALSE) ;
            EnableWindow(GetDlgItem(hDlg, IDD_CapAudioFlag), gCapStatus.fAudioHardware) ;
            CheckDlgButton(hDlg, IDD_CapAudioFlag, gCapParms.fCaptureAudio) ;
            CheckDlgButton(hDlg, IDD_UseDOSMemFlag, gCapParms.fUsingDOSMemory) ;
            CheckDlgButton(hDlg, IDD_CaptureOKFlag, gCapParms.fMakeUserHitOKToCapture) ;

            // Find out how many MCI devices can source video
            if (CountMCIDevices(MCI_DEVTYPE_VCR) +
                CountMCIDevices(MCI_DEVTYPE_VIDEODISC) == 0)
                // if no VCRs or Videodiscs, disable the controls
                fValue = FALSE ;
            else
                fValue = TRUE ;

            // If no MCI device or MCI Control not enabled, disable MCI
            // device name, start and stop time, step capture etc
            EnableWindow(GetDlgItem(hDlg, IDD_MCIControlFlag), fValue) ;
            CheckDlgButton(hDlg, IDD_MCIControlFlag,
                           (fValue &= gCapParms.fMCIControl)) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIDeviceText), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIDeviceData), fValue) ;
            SetDlgItemText(hDlg, IDD_MCIDeviceData, (LPTSTR)gachMCIDeviceName) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIStartText), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIStartData), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIStopText), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_MCIStopData), fValue) ;
            if (fValue) {
                wsprintf((LPTSTR)achBuffer, TEXT("%lu"), gCapParms.dwMCIStartTime) ;
                SetDlgItemText(hDlg, IDD_MCIStartData, (LPTSTR)achBuffer) ;
                wsprintf((LPTSTR)achBuffer, TEXT("%lu"), gCapParms.dwMCIStopTime) ;
                SetDlgItemText(hDlg, IDD_MCIStopData, (LPTSTR)achBuffer) ;
            }
            EnableWindow(GetDlgItem(hDlg, IDD_MCIStepCapFlag), fValue) ;
            CheckDlgButton(hDlg, IDD_MCIStepCapFlag, gCapParms.fStepMCIDevice) ;
            SetDlgItemInt(hDlg, IDD_MaxDropData, gCapParms.wPercentDropForError, FALSE) ;
            SetDlgItemInt(hDlg, IDD_VideoBuffData, gCapParms.wNumVideoRequested, FALSE) ;

            return TRUE ;
        }

        case WM_COMMAND :
            switch (wParam) {
                case IDD_TimeLimitFlag :
                    // If this flag changes, en/dis-able time limit data part
                    fValue = IsDlgButtonChecked(hDlg, IDD_TimeLimitFlag) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_SecondsText), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_SecondsData), fValue) ;
                    return TRUE ;

                case IDD_MCIControlFlag :
                    // If this flag changes, en/dis-able MCI times data part
                    fValue = IsDlgButtonChecked(hDlg, IDD_MCIControlFlag) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIDeviceText), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIDeviceData), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIStartText), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIStartData), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIStopText), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIStopData), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCIStepCapFlag), fValue) ;
                    CheckDlgButton(hDlg, IDD_MCIStepCapFlag,
                            gCapParms.fStepMCIDevice |
                            IsDlgButtonChecked(hDlg, IDD_MCIStepCapFlag)) ;
                    return TRUE ;

                case IDOK :
                {
                    int         iFrameRate ;
                    int         iTimeLimit ;
                    int         iMaxDropRate ;
                    int         iVideoBuffers ;
                    long        lMCIStart ;
                    long        lMCIStop ;

                    if ((iFrameRate = (int) GetDlgItemInt(hDlg, IDD_FrameRateData, NULL,
                                      TRUE)) <= 0 || iFrameRate >= 100) {
                        // No frame at all or more than 100 FPS !!
                        SetDlgItemInt(hDlg, IDD_FrameRateData, iFrameRate, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_FrameRateData)) ;
                        MessageBeep(MB_ICONQUESTION) ;
                        return FALSE ;
                    }
                    if (IsDlgButtonChecked(hDlg, IDD_TimeLimitFlag)) {
                        if ((iTimeLimit = (int) GetDlgItemInt(hDlg, IDD_SecondsData, NULL,
                                          TRUE)) <= 0 || iTimeLimit > 3600) {
                            // No capture or more than 1 hour !!
                            SetDlgItemInt(hDlg, IDD_SecondsData, iTimeLimit, TRUE) ;
                            SetFocus(GetDlgItem(hDlg, IDD_SecondsData)) ;
                            MessageBeep(MB_ICONQUESTION) ;
                            return FALSE ;
                        }
                    }
                    if (IsDlgButtonChecked(hDlg, IDD_MCIControlFlag)) {
                        GetDlgItemText(hDlg, IDD_MCIStartData, (LPTSTR)achBuffer, 20) ;
#ifdef UNICODE
                        lMCIStart = wcstol(achBuffer, NULL, 10);
#else
                        lMCIStart = atol(achBuffer) ;
#endif
                        if (lMCIStart < 0 ||             // negative time !!!
                            lMCIStart >= 2000000000L) {  // or too high !!!
                            SetDlgItemText(hDlg, IDD_MCIStartData, achBuffer) ;
                            SetFocus(GetDlgItem(hDlg, IDD_MCIStartData)) ;
                            MessageBeep(MB_ICONQUESTION) ;
                            return FALSE ;
                        }
                        GetDlgItemText(hDlg, IDD_MCIStopData, (LPTSTR)achBuffer, 20) ;
#ifdef UNICODE
                        lMCIStop = wcstol(achBuffer, NULL, 10);
#else
                        lMCIStop = atol(achBuffer) ;
#endif
                        if (lMCIStop < 0 ||            // negative time !!!
                            lMCIStop >= 2000000000L || // or too high !!!
                            lMCIStop < lMCIStart) {    // or Stop before Start !!!
                            SetDlgItemText(hDlg, IDD_MCIStopData, achBuffer) ;
                            SetFocus(GetDlgItem(hDlg, IDD_MCIStopData)) ;
                            MessageBeep(MB_ICONQUESTION) ;
                            return FALSE ;
                        }
                    }
                    if ((iMaxDropRate = (int) GetDlgItemInt(hDlg, IDD_MaxDropData, NULL,
                                            TRUE)) < 0 || iMaxDropRate >= 100) {
                        // Negative drop limit or more than 100% !!
                        SetDlgItemInt(hDlg, IDD_MaxDropData, iMaxDropRate, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_MaxDropData)) ;
                        MessageBeep(0) ;
                        return FALSE ;
                    }
                    if ((iVideoBuffers = (int) GetDlgItemInt(hDlg, IDD_VideoBuffData, NULL,
                                             TRUE)) <= 0 || iVideoBuffers >= 32767) {
                        // Can't capture with too many or without video buffers
                        SetDlgItemInt(hDlg, IDD_VideoBuffData, iVideoBuffers, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_VideoBuffData)) ;
                        MessageBeep(MB_ICONQUESTION) ;
                        return FALSE ;
                    }

                    // All the Cap params are correct. So set them now...
                    gCapParms.dwRequestMicroSecPerFrame = (DWORD)(1E+6 / iFrameRate + 0.5) ;
                    gCapParms.fLimitEnabled = IsDlgButtonChecked(hDlg, IDD_TimeLimitFlag) ;
                    gCapParms.wTimeLimit = iTimeLimit ;
                    if (gCapParms.fMCIControl =
                            IsDlgButtonChecked(hDlg, IDD_MCIControlFlag)) {
                        gCapParms.dwMCIStartTime = lMCIStart ;
                        gCapParms.dwMCIStopTime  = lMCIStop ;
                        GetDlgItemText(hDlg, IDD_MCIDeviceData, (LPTSTR)achBuffer, 20) ;
                        lstrcpy(gachMCIDeviceName, achBuffer) ;
                        capSetMCIDeviceName(ghWndCap, gachMCIDeviceName) ;
                        gCapParms.fStepMCIDevice = IsDlgButtonChecked(hDlg, IDD_MCIStepCapFlag) ;
                    }
                    else {
                        gCapParms.dwMCIStartTime = gCapParms.dwMCIStopTime = 0 ;
                        gCapParms.fStepMCIDevice = FALSE ;
                    }
                    gCapParms.wPercentDropForError = iMaxDropRate ;
                    gCapParms.wNumVideoRequested = iVideoBuffers ;
                    gCapParms.fAbortLeftMouse = IsDlgButtonChecked(hDlg, IDD_AbortLeftFlag) ;
                    gCapParms.fAbortRightMouse = IsDlgButtonChecked(hDlg, IDD_AbortRightFlag) ;
                    if (IsDlgButtonChecked(hDlg, IDD_AbortEscFlag))
                        gCapParms.vKeyAbort = VK_ESCAPE ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_AbortSpaceFlag))
                            gCapParms.vKeyAbort = VK_SPACE ;
                        else
                            if (IsDlgButtonChecked(hDlg, IDD_AbortF8Flag))
                                gCapParms.vKeyAbort = VK_F8 ;
                            else
                                gCapParms.vKeyAbort = 0 ;
                    gCapParms.fCaptureAudio =
                                IsDlgButtonChecked(hDlg, IDD_CapAudioFlag) ;
                    gCapParms.fUsingDOSMemory =
                                IsDlgButtonChecked(hDlg, IDD_UseDOSMemFlag) ;
                    gCapParms.fMakeUserHitOKToCapture =
                                IsDlgButtonChecked(hDlg, IDD_CaptureOKFlag) ;

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;
}


DIALOGS.H

/****************************************************************************
 *
 *   dialogs.h: Symbol definitions for CapTest Dialogs
 *
 *   Microsoft Video for Windows Capture Class Test Program
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define IDD_HelpAboutBox            300
#define IDD_CapSetUp                400
#define IDD_FrameRateData           401
#define IDD_TimeLimitFlag           411
#define IDD_SecondsText             412
#define IDD_SecondsData             413
#define IDD_CapAudioFlag            421
#define IDD_MCIControlFlag          441
#define IDD_MCIDeviceText           442
#define IDD_MCIDeviceData           443
#define IDD_MCIStartText            444
#define IDD_MCIStartData            445
#define IDD_MCIStopText             446
#define IDD_MCIStopData             447
#define IDD_MCIStepCapFlag          448
#define IDD_VideoBuffData           451
#define IDD_MaxDropData             461
#define IDD_AbortEscFlag            471
#define IDD_AbortSpaceFlag          472
#define IDD_AbortF8Flag             473
#define IDD_AbortLeftFlag           481
#define IDD_AbortRightFlag          482
#define IDD_CaptureOKFlag           491
#define IDD_UseDOSMemFlag           492
#define IDD_AudioFormat             500
#define IDD_SampleIDs               510
#define IDD_Sample8Bit              511
#define IDD_Sample16Bit             512
#define IDD_ChannelIDs              520
#define IDD_ChannelMono             521
#define IDD_ChannelStereo           522
#define IDD_FreqIDs                 530
#define IDD_Freq11kHz               531
#define IDD_Freq22kHz               532
#define IDD_Freq44kHz               534
#define IDD_AllocCapFileSpace       600
#define IDD_SetCapFileFree          611
#define IDD_SetCapFileSize          621
#define IDD_MakePalette             700
#define IDD_MakePalColors           711
#define IDD_MakePalFrames           721


CAPTEST.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   captest.c: Source Code for the CapTest Sample Program
 *
 *   Microsoft Video for Windows Capture Class Sample Program
 *
 ***************************************************************************/

#define ENABLE_ERROR_CALLBACK           1
#define ENABLE_STATUS_CALLBACK          1
#define ENABLE_VIDEOFRAME_CALLBACKS     0

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <vfw.h>
#include <mmreg.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <dos.h>

#include "captest.h"

//
// Global Variables
//
TCHAR           gachAppName[]  = TEXT("CapTestApp") ;
TCHAR           gachIconName[] = TEXT("CapTestIcon") ;
TCHAR           gachMenuName[] = TEXT("CapTestMenu") ;
TCHAR           gachMCIDeviceName[21] = TEXT("VideoDisc") ;  // default MCI device
TCHAR           gachString[128] ;
TCHAR           gachBuffer[200] ;

HINSTANCE      ghInstApp ;
HWND           ghWndMain ;
HWND           ghWndCap ;
HANDLE         ghAccel ;
WORD           gwDeviceIndex ;
WORD           gwPalFrames = DEF_PALNUMFRAMES ;
WORD           gwPalColors = DEF_PALNUMCOLORS ;
WORD           gwCapFileSize ;
DWORD          gdwFrameNum ;
DWORD          gdwVideoNum ;

CAPSTATUS      gCapStatus ;
CAPDRIVERCAPS  gCapDriverCaps ;
CAPTUREPARMS   gCapParms ;

LPWAVEFORMATEX glpwfex ;

// MakeProcInstance is only required for 16-bit apps
#ifndef WIN32
 FARPROC        fpErrorCallback;
 FARPROC        fpStatusCallback;
 FARPROC        fpFrameCallback;
 FARPROC        fpVideoCallback;
#endif

// Function prototypes
//
LONG FAR PASCAL MainWndProc(HWND, UINT, UINT, LONG) ;
LRESULT FNWCALLBACK ErrorCallbackProc(HWND, int, LPTSTR) ;
LRESULT FNWCALLBACK StatusCallbackProc(HWND, int, LPTSTR) ;
LRESULT FNWCALLBACK FrameCallbackProc(HWND, LPVIDEOHDR) ;
LRESULT FNWCALLBACK VideoCallbackProc(HWND, LPVIDEOHDR) ;

//
// WinMain: Application Entry Point Function
//
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
///////////////////////////////////////////////////////////////////////
//  hInstance:      handle for this instance
//  hPrevInstance:  handle for possible previous instances
//  lpszCmdLine:    long pointer to exec command line
//  nCmdShow:       Show code for main window display
///////////////////////////////////////////////////////////////////////

    MSG          msg ;
    WNDCLASS     wc ;

    ghInstApp = hInstance ;
    if (! hPrevInstance) {
        // If it's the first instance, register the window class
        wc.lpszClassName = gachAppName ;
        wc.hInstance     = hInstance ;
        wc.lpfnWndProc   = MainWndProc ;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
        wc.hIcon         = LoadIcon(hInstance, gachIconName) ;
        wc.lpszMenuName  = gachMenuName ;
        wc.hbrBackground = GetStockObject(WHITE_BRUSH) ;
        wc.style         = CS_HREDRAW | CS_VREDRAW ;
        wc.cbClsExtra    = 0 ;
        wc.cbWndExtra    = 0 ;

        if (! RegisterClass(&wc)) {
            LoadString(ghInstApp, IDS_ERR_REGISTER_CLASS, gachString, sizeof(gachString)/sizeof(TCHAR)) ;
            MessageBox(NULL, gachString, NULL,
#ifdef BIDI
                MB_RTL_READING |
#endif

            MB_ICONEXCLAMATION) ;
            return 0 ;
        }
    }

    // Create Application's Main window
    ghWndMain =
#ifdef BIDI

//
// unfortunately you can't just #ifdef the CreateWindow line and leave
// the parameters common: on Win32, CreateWindow is a macro and does not
// expand correctly if you ifdef only the 'CreateWindow(' line.
//
        CreateWindowEx(WS_EX_BIDI_SCROLL |  WS_EX_BIDI_MENU |WS_EX_BIDI_NOICON,
                             gachAppName,
                             TEXT("Capture Test App"),
                             WS_CAPTION      |
                             WS_SYSMENU      |
                             WS_MINIMIZEBOX  |
                             WS_MAXIMIZEBOX  |
                             WS_THICKFRAME   |
                             WS_CLIPCHILDREN |
                             WS_OVERLAPPED,
                             CW_USEDEFAULT, 0,
                             320, 240,
                             NULL,
                             NULL,
                             ghInstApp,
                             NULL) ;
#else
        CreateWindow (
                             gachAppName,
                             TEXT("Capture Test App"),
                             WS_CAPTION      |
                             WS_SYSMENU      |
                             WS_MINIMIZEBOX  |
                             WS_MAXIMIZEBOX  |
                             WS_THICKFRAME   |
                             WS_CLIPCHILDREN |
                             WS_OVERLAPPED,
                             CW_USEDEFAULT, 0,
                             320, 240,
                             NULL,
                             NULL,
                             ghInstApp,
                             NULL) ;
#endif

    if (ghWndMain == NULL) {
        LoadString(ghInstApp, IDS_ERR_CREATE_WINDOW, gachString, sizeof(gachString)/sizeof(TCHAR)) ;
        MessageBox(NULL, gachString, NULL,
#ifdef BIDI
                MB_RTL_READING |
#endif

        MB_ICONEXCLAMATION | MB_OK) ;
        return IDS_ERR_CREATE_WINDOW ;
    }

    ShowWindow(ghWndMain, nCmdShow) ;
    UpdateWindow(ghWndMain) ;
    ghAccel = LoadAccelerators(ghInstApp, gachAppName) ;

    // All set; get and process messages
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (! TranslateAccelerator(ghWndMain, ghAccel, &msg)) {
            TranslateMessage(&msg) ;
            DispatchMessage(&msg) ;
        }
    }

    return msg.wParam ;
}  // End of WinMain

//
// ErrorCallbackProc: Error Callback Function
//
LRESULT FNWCALLBACK ErrorCallbackProc(HWND hWnd, int nErrID, LPTSTR lpErrorText)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:          Application main window handle
//  nErrID:        Error code for the encountered error
//  lpErrorText:   Error text string for the encountered error
////////////////////////////////////////////////////////////////////////

    if (!ghWndMain)
        return FALSE;

    if (nErrID == 0)            // Starting a new major function
        return TRUE;            // Clear out old errors...

    // Show the error ID and text
    wsprintf(gachBuffer, TEXT("Error# %d"), nErrID) ;

    MessageBox(hWnd, lpErrorText, gachBuffer,
#ifdef BIDI
                MB_RTL_READING |
#endif
                MB_OK | MB_ICONEXCLAMATION) ;

    return (LRESULT) TRUE ;
}

//
// StatusCallbackProc: Status Callback Function
//
LRESULT FNWCALLBACK StatusCallbackProc(HWND hWnd, int nID, LPTSTR lpStatusText)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:           Application main window handle
//  nID:            Status code for the current status
//  lpStatusText:   Status text string for the current status
////////////////////////////////////////////////////////////////////////

    if (!ghWndMain)
        return FALSE;

    if (nID == 0) {              // Zero means clear old status messages
        SetWindowText(ghWndMain, (LPTSTR) gachAppName) ;
        return (LRESULT) TRUE ;
    }

    // Show the status ID and status text...
    wsprintf(gachBuffer, TEXT("Status# %d: %s"), nID, lpStatusText) ;

        SetWindowText(ghWndMain, (LPTSTR)gachBuffer) ;

    return (LRESULT) TRUE ;
}

//
// FrameCallbackProc: Frame Callback Function
// Called whenever a new frame is captured but not streaming
//
LRESULT FNWCALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:       Application main window handle
//  lpVHdr:     long pointer to VideoHdr struct containing captured
//              frame information
////////////////////////////////////////////////////////////////////////

    if (!ghWndMain)
        return FALSE;

    wsprintf(gachBuffer, TEXT("Preview frame# %ld "), gdwFrameNum++) ;

    SetWindowText(ghWndMain, (LPTSTR)gachBuffer) ;
    return (LRESULT) TRUE ;
}

//
// VideoCallbackProc: Video Stream Callback Function
// Called whenever a new frame is captured while streaming
//
LRESULT FNWCALLBACK VideoCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:       Application main window handle
//  lpVHdr:     long pointer to VideoHdr struct containing captured
//              frame information
////////////////////////////////////////////////////////////////////////

    gdwVideoNum++;      // Testing:  just count the callbacks

    return (LRESULT) TRUE ;
}

//
// CenterCaptureWindow: Placess Capture Window at the Center of Main Window
//
static void CenterCaptureWindow(HWND hWndM, HWND hWndC)
{
////////////////////////////////////////////////////////////////////////
//  hWndM:      Application main window handle
//  hWndC:      Capture window handle
////////////////////////////////////////////////////////////////////////

    RECT       MainRect ;
    RECT       CapRect ;
    WORD       wCapXPos ;
    WORD       wCapYPos ;

    // Get the sizes of main and capture windows and
    // calculate the location for centering
    GetClientRect(hWndM, &MainRect) ;
    GetClientRect(hWndC, &CapRect) ;
    wCapXPos = max(0, (Width(MainRect) - Width(CapRect)) / 2) ;
    wCapYPos = max(0, (Height(MainRect) - Height(CapRect)) / 2) ;

    // Position the capture window at the required location
    MoveWindow(hWndC, wCapXPos, wCapYPos, Width(CapRect),
               Height(CapRect), TRUE) ;
}

//
// StartNewVideoChannel: Gets Selected Driver's Caps -- Updates menu,
//                       Checks Image Size -- Resizes display window,
//                       Enables Preview (at 15 FPS rate)
//
static void StartNewVideoChannel(HWND hWndM, HWND hWndC, WORD wIndex)
{
////////////////////////////////////////////////////////////////////////
//  hWndM:      Application main window handle
//  hWndC:      Capture window handle
//  wIndex:     Selected capture driver index
////////////////////////////////////////////////////////////////////////

    HMENU       hMenu = GetMenu(hWndM) ;

    // Get capture driver settings and update menu
    capDriverGetCaps(hWndC, &gCapDriverCaps, sizeof(CAPDRIVERCAPS)) ;
    EnableMenuItem(hMenu, IDM_O_OVERLAY, MF_BYCOMMAND |
                gCapDriverCaps.fHasOverlay ? MF_ENABLED : MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_O_VIDEOFORMAT, MF_BYCOMMAND |
                gCapDriverCaps.fHasDlgVideoFormat ? MF_ENABLED : MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_O_VIDEOSOURCE, MF_BYCOMMAND |
                gCapDriverCaps.fHasDlgVideoSource ? MF_ENABLED : MF_GRAYED) ;
    EnableMenuItem(hMenu, IDM_O_VIDEODISPLAY, MF_BYCOMMAND |
                gCapDriverCaps.fHasDlgVideoDisplay ? MF_ENABLED : MF_GRAYED) ;

    // Get video format and adjust capture window
    capGetStatus(hWndC, &gCapStatus, sizeof(CAPSTATUS)) ;
    SetWindowPos(hWndC, NULL, 0, 0, gCapStatus.uiImageWidth,
                 gCapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE) ;

    // Start preview by default
    capPreviewRate(hWndC, MS_FOR_15FPS) ;
    capPreview(hWndC, TRUE) ;

    // Put check mark beside appropriate menu options
    CheckMenuItem(hMenu, wIndex + IDM_O_DRIVERS, MF_BYCOMMAND | MF_CHECKED) ;
}

//
// MenuProc: Processes All Menu-based Operations
//
long FAR PASCAL MenuProc(HWND hWnd, UINT wParam, LONG lParam)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:      Application main window handle
//  hMenu:     Application menu handle
//  wParam:    Menu option
//  lParam:    Additional info for any menu option
////////////////////////////////////////////////////////////////////////

    OPENFILENAME ofn ;
    DWORD        dwError ;
    WORD         wIndex ;
    BOOL         fResult ;
    DWORD        dwSize ;
    TCHAR        achBuffer[_MAX_PATH] ;
    TCHAR        achFileName[_MAX_PATH] ;
    TCHAR        *szFileFilter = TEXT("Microsoft AVI\0")
                                 TEXT("*.avi\0")
                                 TEXT("All Files\0")
                                 TEXT("*.*\0") ;

    HMENU hMenu = GetMenu(hWnd) ;

    switch (wParam) {
        case IDM_F_SETCAPTUREFILE:
        {
            LPTSTR p;

            // Get current capture file name and
            // then try to get the new capture file name
            dwError = capFileGetCaptureFile(ghWndCap, achFileName,
                                        sizeof(achFileName)/sizeof(TCHAR));
            if (dwError) {

                // Get just the path info
                // Terminate the full path at the last backslash
                lstrcpy (achBuffer, achFileName);
                for (p = achBuffer + lstrlen(achBuffer); p > achBuffer; p--) {
                    if (*p == '\\') {
                        *(p+1) = '\0';
                        break;
                    }
                }

                _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
                ofn.lStructSize = sizeof(OPENFILENAME) ;
                ofn.hwndOwner = hWnd ;
                ofn.lpstrFilter = szFileFilter ;
                ofn.nFilterIndex = 0 ;
                ofn.lpstrFile = achFileName ;
                ofn.nMaxFile = sizeof(achFileName)/sizeof(TCHAR) ;
                ofn.lpstrFileTitle = NULL;
                ofn.lpstrTitle = TEXT("Set Capture File") ;
                ofn.nMaxFileTitle = 0 ;
                ofn.lpstrInitialDir = achBuffer;
                ofn.Flags =
#ifdef BIDI
                OFN_BIDIDIALOG |
#endif
                OFN_HIDEREADONLY |
                OFN_NOREADONLYRETURN |
                OFN_PATHMUSTEXIST ;

                if (GetOpenFileName(&ofn))
                    // If the user has hit OK then set capture file name
                    capFileSetCaptureFile(ghWndCap, achFileName) ;
            }
        }
        break;

        case IDM_F_SAVEVIDEOAS:
            // Get the current capture file name and
            // then get the substitute file name to save video in
            dwError = capFileGetCaptureFile(ghWndCap, achFileName, sizeof(achFileName)/sizeof(TCHAR));
            if (dwError) {

                _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
                ofn.lStructSize = sizeof(OPENFILENAME) ;
                ofn.hwndOwner = hWnd ;
                ofn.lpstrFilter = szFileFilter ;
                ofn.nFilterIndex = 0 ;
                ofn.lpstrFile = achFileName ;
                ofn.nMaxFile = sizeof(achFileName)/sizeof(TCHAR) ;
                ofn.lpstrFileTitle = NULL ;
                ofn.lpstrTitle = TEXT("Save Video As...") ;
                ofn.nMaxFileTitle = 0 ;
                ofn.lpstrInitialDir = NULL ;
                ofn.Flags =
#ifdef BIDI
                OFN_BIDIDIALOG |
#endif
                OFN_PATHMUSTEXIST ;

                if (GetSaveFileName(&ofn))
                    // If the user has hit OK then set save file name
                    capFileSaveAs(ghWndCap, achFileName) ;
            }
            break;

        case IDM_F_ALLOCATESPACE:
            if (DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_AllocCapFileSpace),
                          hWnd, AllocCapFileProc))
                // If user has hit OK then alloc requested capture file space
                if (! capFileAlloc(ghWndCap, (long) gwCapFileSize * ONEMEG))
                    MessageBox(NULL, TEXT("Can't pre-allocate capture file space"),
                               TEXT("Error"),
#ifdef BIDI
                                MB_RTL_READING |
#endif
                                MB_OK | MB_ICONEXCLAMATION) ;
            break ;

        case IDM_F_EXIT:
            DestroyWindow(hWnd) ;
            break;

        case IDM_E_COPY:
            capEditCopy(ghWndCap) ;
            break;

        case IDM_E_PASTEPALETTE:
            capPalettePaste(ghWndCap) ;
            break;

        case IDM_O_PREVIEW:
            // Toggle Preview
        capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
            capPreview(ghWndCap, !gCapStatus.fLiveWindow) ;
            break;

        case IDM_O_OVERLAY:
            // Toggle Overlay
        capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
            capOverlay(ghWndCap, !gCapStatus.fOverlayWindow) ;
            break ;

        case IDM_O_AUDIOFORMAT:
#ifdef  USE_ACM
            {
                ACMFORMATCHOOSE cfmt;

                // Ask the ACM what the largest wave format is.....
                acmMetrics(NULL,
                            ACM_METRIC_MAX_SIZE_FORMAT,
                            &dwSize);

                // Get the current audio format
                dwSize = max (dwSize, capGetAudioFormatSize (ghWndCap));
                glpwfex = (LPWAVEFORMATEX) GlobalAllocPtr(GHND, dwSize) ;
                capGetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;

                _fmemset (&cfmt, 0, sizeof (ACMFORMATCHOOSE));
                cfmt.cbStruct = sizeof (ACMFORMATCHOOSE);
                cfmt.fdwStyle =  ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
                cfmt.fdwEnum =   ACM_FORMATENUMF_HARDWARE |
                                 ACM_FORMATENUMF_INPUT;
                cfmt.hwndOwner = hWnd;
                cfmt.pwfx =     glpwfex;
                cfmt.cbwfx =    dwSize;
                if (!acmFormatChoose(&cfmt))
                    capSetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;

                GlobalFreePtr(glpwfex) ;
            }
#else
            // If not using ACM, remove the reference in the link line
            // of makefile.

            // Get current audio format and then find required format
            dwSize = capGetAudioFormatSize (ghWndCap);
            if(!dwSize) break;
            glpwfex = (LPWAVEFORMATEX) GlobalAllocPtr(GHND, dwSize) ;
            capGetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;

            if (DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_AudioFormat), hWnd, AudioFormatProc))
                capSetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize);  // If the user has hit OK, set the new audio format

            GlobalFreePtr(glpwfex) ;
#endif
            break ;

        case IDM_O_VIDEOFORMAT:
            if (gCapDriverCaps.fHasDlgVideoFormat) {
                // Only if the driver has a "Video Format" dialog box
                if (capDlgVideoFormat(ghWndCap)) {  // If successful,
                    // Get the new image dimension and center capture window
                    capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
                    SetWindowPos(ghWndCap, NULL, 0, 0, gCapStatus.uiImageWidth,
                        gCapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE) ;
                    CenterCaptureWindow(hWnd, ghWndCap) ;
                }
            }
            break;

        case IDM_O_VIDEOSOURCE:
            if (gCapDriverCaps.fHasDlgVideoSource) {
                // Only if the driver has a "Video Source" dialog box
                capDlgVideoSource(ghWndCap) ;
            }
            break ;

        case IDM_O_VIDEODISPLAY:
            if (gCapDriverCaps.fHasDlgVideoDisplay) {
                // Only if the driver has a "Video Display" dialog box
                capDlgVideoDisplay(ghWndCap) ;
            }
            break ;

        case IDM_O_PALETTE:
            if (DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_MakePalette), hWnd, MakePaletteProc))
                // If the user has hit OK, capture palette with the
                // specified number of colors and frames
                capPaletteAuto(ghWndCap, gwPalFrames, gwPalColors) ;
            break;

        case IDM_C_CAPTUREVIDEO:
            gdwVideoNum = 0 ;  // Start counting video frames
            // Capture video sequence
            fResult = capCaptureSequence(ghWndCap) ;
            break;

        case IDM_C_CAPTUREFRAME:
            gdwFrameNum = 0 ;  // Start counting single frames
            // Turn off overlay / preview (gets turned off by frame capture)
    capPreview(ghWndCap, FALSE);
    capOverlay(ghWndCap, FALSE);

            // Grab a frame
            fResult = capGrabFrameNoStop(ghWndCap) ;
            break;

        case IDM_C_CAPTURESETTINGS:
            // Get the current setup for video capture
            capCaptureGetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;

            // Invoke a Dlg box to setup all the params
            if (DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_CapSetUp), hWnd, CapSetUpProc))
                // If the user has hit OK, set the new setup info
                capCaptureSetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;
            break;

        case IDM_O_CHOOSECOMPRESSOR:
            capDlgVideoCompression(ghWndCap);
            break;

        case IDM_H_ABOUT:
            DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_HelpAboutBox), hWnd, AboutProc) ;
            break ;

        default:
            // There is a chance, a driver change has been requested
            if ( IsDriverIndex(wParam) ) {
                // If it's a valid driver index...
                if (wParam - IDM_O_DRIVERS != gwDeviceIndex) {
                    // and a different one too then we need to do the rest

                    // Turn off preview/overlay, uncheck current driver option
                    capPreview(ghWndCap, FALSE) ;
                    capOverlay(ghWndCap, FALSE) ;
                    CheckMenuItem(GetMenu(hWnd), gwDeviceIndex + IDM_O_DRIVERS,
                                  MF_BYCOMMAND | MF_UNCHECKED) ;

                    // Connect to requested driver
                    if ( capDriverConnect(ghWndCap, (wIndex = (WORD) (wParam - IDM_O_DRIVERS))) ) {
                        // Connect worked fine -- update menu, start new driver...
                        CheckMenuItem(GetMenu(hWnd), wParam, MF_BYCOMMAND | MF_CHECKED) ;
                        gwDeviceIndex = (WORD) (wParam - IDM_O_DRIVERS) ;
                        StartNewVideoChannel(hWnd, ghWndCap, gwDeviceIndex) ;
                        CenterCaptureWindow(hWnd, ghWndCap) ;
                    }
                    else {
                        // if connect failed, re-connect back to previous driver
                        if (! capDriverConnect(ghWndCap, gwDeviceIndex)) {
                            MessageBox(hWnd, TEXT("Now can't connect back to previous driver !!"),
                                       TEXT("Error"),
#ifdef BIDI
                            MB_RTL_READING |
#endif

                            MB_OK | MB_ICONSTOP) ;
                            return -1L ;
                        }
                        else
                            // Re-start previous driver as it was before
                            StartNewVideoChannel(hWnd, ghWndCap, gwDeviceIndex) ;
                            CenterCaptureWindow(hWnd, ghWndCap) ;
                    }
                }   // end of if ( != gwDeviceIndex)
            }   // end of if (IsDriverIndex())
            else {
                wsprintf(achBuffer, TEXT("How could you specify this (%u) Driver Index ?"),
                         wParam - IDM_O_DRIVERS) ;
                MessageBox(hWnd, achBuffer, TEXT("Oops!!"),
#ifdef BIDI
                MB_RTL_READING |
#endif
                MB_OK | MB_ICONEXCLAMATION) ;
            }

            break ;
    }

    return 0L ;
}

//
// MainWndProc: Application Main Window Procedure
//
LONG FAR PASCAL MainWndProc(HWND hWnd, UINT Message, UINT wParam, LONG lParam)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:      Application main window handle
//  Message:   Next message to be processed
//  wParam:    WORD param for the message
//  lParam:    LONG param for the message
////////////////////////////////////////////////////////////////////////

    switch (Message) {
        case WM_COMMAND:
            MenuProc(hWnd, wParam, lParam) ;
            break ;

        case WM_CREATE:
        {
            TCHAR    achDeviceName[80] ;
            TCHAR    achDeviceVersion[100] ;
            TCHAR    achBuffer[100] ;
            WORD    wDriverCount = 0 ;
            WORD    wIndex ;
            DWORD   dwError ;
            HMENU   hMenu ;

            // First create the capture window
            ghWndCap = capCreateCaptureWindow((LPTSTR)TEXT("Capture Window"),
                                              WS_CHILD | WS_VISIBLE,
                                              0, 0, 160, 120,
                                              (HWND) hWnd, (int) 0) ;

            hMenu = GetSubMenu(GetMenu(hWnd), 2) ;  // 2 for "Option"

#if ENABLE_ERROR_CALLBACK
  #ifdef WIN32
            // Register the status and error callbacks before driver connect
            capSetCallbackOnError(ghWndCap, ErrorCallbackProc) ;
  #else
            fpErrorCallback = MakeProcInstance((FARPROC)ErrorCallbackProc, ghInstApp) ;
            capSetCallbackOnError(ghWndCap, fpErrorCallback) ;
  #endif
#endif

#if ENABLE_STATUS_CALLBACK
  #ifdef WIN32
            capSetCallbackOnStatus(ghWndCap, StatusCallbackProc) ;
  #else
            fpStatusCallback = MakeProcInstance((FARPROC)StatusCallbackProc, ghInstApp) ;
            capSetCallbackOnStatus(ghWndCap, fpStatusCallback) ;
  #endif
#endif

#if ENABLE_VIDEOFRAME_CALLBACKS
  #ifdef WIN32
            capSetCallbackOnVideoStream(ghWndCap, VideoCallbackProc) ;
            capSetCallbackOnFrame(ghWndCap, FrameCallbackProc) ;
  #else
            fpVideoCallback = MakeProcInstance((FARPROC)VideoCallbackProc, ghInstApp) ;
            capSetCallbackOnVideoStream(ghWndCap, fpVideoCallback) ;

            fpFrameCallback = MakeProcInstance((FARPROC)FrameCallbackProc, ghInstApp) ;
            capSetCallbackOnFrame(ghWndCap, fpFrameCallback) ;
  #endif
#endif
            // Try to connect one of the MSVIDEO drivers
            for (wIndex = 0 ; wIndex < MAXVIDDRIVERS ; wIndex++) {
                if (capGetDriverDescription(wIndex,
                           (LPTSTR)achDeviceName, sizeof(achDeviceName)/ sizeof(TCHAR),
                           (LPTSTR)achDeviceVersion, sizeof(achDeviceVersion)/sizeof(TCHAR))) {

                    // There is such a driver in the "system.ini" file.
                    // Append driver name to "Options" list in menu
                    wsprintf(achBuffer, TEXT("&%d %s"), wIndex, (LPTSTR)achDeviceName) ;
                    AppendMenu(hMenu, MF_ENABLED, IDM_O_DRIVERS+wIndex, achBuffer) ;

                    if (wDriverCount++ == 0) {
                        // Only if no other driver is already connected
                        dwError = capDriverConnect(ghWndCap, wIndex);
                        if (dwError) {
                            CheckMenuItem(GetMenu(hWnd), IDM_O_DRIVERS+wIndex, MF_BYCOMMAND | MF_CHECKED) ;
                            gwDeviceIndex = wIndex ;
                        }
                    }
                } // end of if (capGetDriverDesc..())
            }

            // Now refresh menu, position capture window, start driver etc
            DrawMenuBar(hWnd) ;
            CenterCaptureWindow(hWnd, ghWndCap) ;
            StartNewVideoChannel(hWnd, ghWndCap, gwDeviceIndex) ;

            break ;
        }

        case WM_MOVE:
        case WM_SIZE:
            CenterCaptureWindow(hWnd, ghWndCap) ;
            break ;

        case WM_PALETTECHANGED:
        case WM_QUERYNEWPALETTE:
            // Pass the buck to Capture window proc
            PostMessage(ghWndCap, Message, wParam, lParam) ;
            break ;

        case WM_INITMENU:
        {
            BOOL          fResult ;

            // Initially check if "Options.PastePalette" should be enabled
            fResult = IsClipboardFormatAvailable(CF_PALETTE) ?
                      MF_ENABLED : MF_GRAYED ;
            EnableMenuItem((HMENU) wParam, IDM_E_PASTEPALETTE, fResult) ;

    // Check/Uncheck Preview and Overlay
        capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
        CheckMenuItem((HMENU)wParam, IDM_O_PREVIEW, gCapStatus.fLiveWindow
? MF_CHECKED : MF_UNCHECKED);

CheckMenuItem((HMENU)wParam, IDM_O_OVERLAY,gCapStatus.fOverlayWindow
? MF_CHECKED : MF_UNCHECKED);
        }

        case WM_PAINT:
        {
            HDC           hDC ;
            PAINTSTRUCT   ps ;

            hDC = BeginPaint(hWnd, &ps) ;

            // Included in case the background is not a pure color
            SetBkMode(hDC, TRANSPARENT) ;

            EndPaint(hWnd, &ps) ;
            break ;
        }

        case WM_CLOSE:
            // Disable and free all the callbacks
#if ENABLE_ERROR_CALLBACK
            capSetCallbackOnError(ghWndCap, NULL) ;
  #ifndef WIN32
            FreeProcInstance(fpErrorCallback) ;
  #endif
#endif

#if ENABLE_STATUS_CALLBACK
            capSetCallbackOnStatus(ghWndCap, NULL) ;
  #ifndef WIN32
            FreeProcInstance(fpStatusCallback) ;
  #endif
#endif

#if ENABLE_VIDEOFRAME_CALLBACKS
            capSetCallbackOnFrame(ghWndCap, NULL) ;
            capSetCallbackOnVideoStream(ghWndCap, NULL) ;
  #ifndef WIN32
            FreeProcInstance(fpFrameCallback) ;
            FreeProcInstance(fpVideoCallback) ;
  #endif
#endif
            // Destroy child windows, modeless dialogs, then this window...

            DestroyWindow(ghWndCap) ;
            DestroyWindow(hWnd) ;
            break ;

        case WM_DESTROY:
            PostQuitMessage(0) ;
            break ;

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

    return 0L;
}   // End of MainWndProc


README.TXT

The DSEQFILE sample is an AVIFile file handler for DIB sequences, implemented in
C++. To install, execute dseqfile.reg and copy dll to system dir. Can be used
with AVIEDIT or AVIVIEW.


FACTORY.CPP

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define _INC_OLE
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#include <ole2.h>

#define INITGUID
#include <initguid.h>
// Bring in the external GUIDs we need....
//DEFINE_OLEGUID(IID_IUnknown,            0x00000000L, 0, 0);
//DEFINE_OLEGUID(IID_IClassFactory,       0x00000001L, 0, 0);
//DEFINE_OLEGUID(IID_IMarshal,            0x00000003L, 0, 0);

#include <vfw.h>
#include "handler.h"

HMODULE ghModule = NULL;        // global HMODULE/HINSTANCE for resource access

/*      -       -       -       -       -       -       -       -       */
EXTERN_C BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID);
EXTERN_C BOOL APIENTRY DllMain(
HANDLE hModule,
DWORD dwReason,
LPVOID lpReserved )
{
        switch( dwReason)
        {
                case DLL_PROCESS_ATTACH:
                        if(ghModule == NULL)
                                ghModule = (HMODULE)hModule;
                        break;
                case DLL_THREAD_ATTACH:
                        break;
                case DLL_THREAD_DETACH:
                        break;
                case DLL_PROCESS_DETACH:
                        break;
        }
        return TRUE;
}

/*      -       -       -       -       -       -       -       -       */

EXTERN_C BOOL FAR PASCAL WEP(
        BOOL    fSystemExit)
{
        return TRUE;
}

/*      -       -       -       -       -       -       -       -       */

STDAPI DllCanUnloadNow(
        void)
{
        return ResultFromScode((fLocked || uUseCount) ? S_FALSE : S_OK);
}

/*      -       -       -       -       -       -       -       -       */

STDAPI DllGetClassObject(
        const CLSID FAR&        rclsid,
        const IID FAR&  riid,
        void FAR* FAR*  ppv)
{
        HRESULT hresult;

        hresult = CAVIFileCF::Create(rclsid, riid, ppv);
        return hresult;
}

/*      -       -       -       -       -       -       -       -       */

HRESULT CAVIFileCF::Create(
        const CLSID FAR&        rclsid,
        const IID FAR&  riid,
        void FAR* FAR*  ppv)
{
        CAVIFileCF FAR* pAVIFileCF;
        IUnknown FAR*   pUnknown;
        HRESULT hresult;

        pAVIFileCF = new FAR CAVIFileCF(rclsid, &pUnknown);
        if (pAVIFileCF == NULL)
                return ResultFromScode(E_OUTOFMEMORY);
        hresult = pUnknown->QueryInterface(riid, ppv);
        if (FAILED(GetScode(hresult)))
                delete pAVIFileCF;
        return hresult;
}

/*      -       -       -       -       -       -       -       -       */

CAVIFileCF::CAVIFileCF(
        const CLSID FAR&        rclsid,
        IUnknown FAR* FAR*      ppUnknown)
{
        m_clsid = rclsid;
        m_refs = 0;
        *ppUnknown = this;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFileCF::QueryInterface(
        const IID FAR&  iid,
        void FAR* FAR*  ppv)
{
        if (iid == IID_IUnknown)
                *ppv = this;
        else if (iid == IID_IClassFactory)
                *ppv = this;
        else
                return ResultFromScode(E_NOINTERFACE);
        AddRef();
        return NULL;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFileCF::AddRef()
{
        return ++m_refs;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFileCF::Release()
{
        if (!--m_refs) {
                delete this;
                return 0;
        }
        return m_refs;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFileCF::CreateInstance(
        IUnknown FAR*   pUnknownOuter,
        const IID FAR&  riid,
        void FAR* FAR*  ppv)
{
        HRESULT hresult;
        // Actually create a real object using the CAVIFile class....

        // !!! We should really make sure they're using IAVIFile or IMarshal!
        if (m_clsid == CLSID_DIBSEQFileReader) {
                hresult = CAVIFile::Create(pUnknownOuter, riid, ppv);
                return hresult;
        } else {
                return ResultFromScode(E_UNEXPECTED);
        }
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFileCF::LockServer(
        BOOL    fLock)
{
        fLocked = fLock;
        return NULL;
}

/*      -       -       -       -       -       -       -       -       */


HANDLER.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
extern HMODULE ghModule; // = NULL; // global HMODULE/HINSTANCE for resource access

// Comment out the following line to disable the custom marshalling code....
#define CUSTOMMARSHAL

/*      -       -       -       -       -       -       -       -       */

#define CFactoryImpl    CI      // MSC7 Can't handle long exported names
#define CAVIFileImpl    CF      // MSC7 Can't handle long exported names
#define CAVIStreamImpl  CS      // MSC7 Can't handle long exported names

/* Remove warning of using object during initialization. */
#pragma warning(disable:4355)

/*      -       -       -       -       -       -       -       -       */

#define implement       struct
#define implementations private

/*
** _StdClassImplementations
** Defines the standard implementations for a class object.
*/

#define _StdClassImplementations(Impl)  \
        STDMETHODIMP QueryInterface(const IID FAR& riid, void FAR* FAR* ppv);   \
        STDMETHODIMP_(ULONG) AddRef();  \
        STDMETHODIMP_(ULONG) Release()

// non-member function prototypes
EXTERN_C BOOL PASCAL FAR LibMain(
        HINSTANCE       hInstance,
        HGLOBAL segDS,
        UINT    cbHeapSize,
        LPCSTR  pszCmdLine);
EXTERN_C BOOL FAR PASCAL WEP(
        BOOL    fSystemExit);
STDAPI DllCanUnloadNow(
        void);
STDAPI DllGetClassObject(
        const CLSID FAR&        rclsid,
        const IID FAR&  riid,
        void FAR* FAR*  ppv);
LPSTR FAR FileName(
        LPCSTR lszPath);

/*      -       -       -       -       -       -       -       -       */
class FAR CAVIFileCF : IClassFactory {
public:
        static HRESULT Create(const CLSID FAR& rclsid, const IID FAR& riid, void FAR* FAR* ppv);
private:
        CAVIFileCF(const CLSID FAR& rclsid, IUnknown FAR* FAR* ppUnknown);
        _StdClassImplementations(CFactoryImpl);
        CFactoryImpl(CAVIFileCF FAR* pAVIFileCF);
        STDMETHODIMP CreateInstance(IUnknown FAR* pUnknownOuter, const IID FAR& riid, void FAR* FAR* ppv);
        STDMETHODIMP LockServer(BOOL fLock);

        ULONG   m_refs;
        CLSID   m_clsid;
};

/*      -       -       -       -       -       -       -       -       */

class FAR CAVIFile {
public:
        static HRESULT Create(IUnknown FAR* pUnknownOuter, const IID FAR& riid, void FAR* FAR* ppv);
        HRESULT NEAR PASCAL LoadFrame(LONG lPos);
        HRESULT NEAR PASCAL WriteFrame(LONG lPos, LPVOID lp, LONG cb);

private:
        CAVIFile(IUnknown FAR* pUnknownOuter, IUnknown FAR* FAR* ppUnknown);

implementations:
        implement CUnknownImpl : IUnknown {
        public:
                _StdClassImplementations(
                        CUnknownImpl);
                CUnknownImpl(
                        CAVIFile FAR* pAVIFile);
        private:
                CAVIFile FAR*   m_pAVIFile;
                ULONG   m_refs;
        };
        implement CAVIFileImpl : IAVIFile {
        public:
                _StdClassImplementations(
                        CAVIFileImpl);
                CAVIFileImpl(
                        CAVIFile FAR* pAVIFile);
                ~CAVIFileImpl();
                STDMETHODIMP Info(
                        THIS_
                        AVIFILEINFOW * pfi,
                        LONG lSize);
                STDMETHODIMP GetStream(
                        THIS_
                        PAVISTREAM FAR * ppStream,
                        DWORD fccType,
                        LONG lParam);
                STDMETHODIMP CreateStream(
                        THIS_
                        PAVISTREAM FAR * ppStream,
                        AVISTREAMINFOW * psi);
                STDMETHODIMP WriteData(
                        THIS_
                        DWORD ckid,
                        LPVOID lpData,
                        LONG cbData);
                STDMETHODIMP ReadData(
                        THIS_
                        DWORD ckid,
                        LPVOID lpData,
                        LONG FAR *lpcbData);
                STDMETHODIMP EndRecord(
                        THIS);
                STDMETHODIMP DeleteStream(THIS_
                                            DWORD fccType,
                                            LONG lParam);
        private:
                // private functions here?
                CAVIFile FAR*   m_pAVIFile;
        };
        implement CAVIStreamImpl : IAVIStream {
        public:
                _StdClassImplementations(
                        CAVIStreamImpl);
                CAVIStreamImpl(
                        CAVIFile FAR* pAVIFile);
                ~CAVIStreamImpl();
                STDMETHODIMP Create(
                        THIS_
                        LPARAM lParam1,
                        LPARAM lParam2);
                STDMETHODIMP Info(
                        THIS_
                        AVISTREAMINFOW FAR * psi,
                        LONG lSize);
                STDMETHODIMP_(LONG) FindSample(
                        THIS_
                        LONG lPos,
                        LONG lFlags);
                STDMETHODIMP ReadFormat(
                        THIS_
                        LONG lPos,
                        LPVOID lpFormat,
                        LONG FAR *cbFormat);
                STDMETHODIMP SetFormat(
                        THIS_
                        LONG lPos,
                        LPVOID lpFormat,
                        LONG cbFormat);
                STDMETHODIMP Read(
                        THIS_
                        LONG lStart,
                        LONG lSamples,
                        LPVOID lpBuffer,
                        LONG cbBuffer,
                        LONG FAR * plBytes,
                        LONG FAR * plSamples);
                STDMETHODIMP Write(
                        THIS_
                        LONG lStart,
                        LONG lSamples,
                        LPVOID lpBuffer,
                        LONG cbBuffer,
                        DWORD dwFlags,
                        LONG FAR *plSampWritten,
                        LONG FAR *plBytesWritten);
                STDMETHODIMP Delete(
                        THIS_
                        LONG lStart,
                        LONG lSamples);
                STDMETHODIMP ReadData(
                        THIS_
                        DWORD fcc,
                        LPVOID lp,
                        LONG FAR *lpcb);
                STDMETHODIMP WriteData(
                        THIS_
                        DWORD fcc,
                        LPVOID lp,
                        LONG cb);
                STDMETHODIMP SetInfo(AVISTREAMINFOW FAR * lpInfo, LONG cbInfo);
        private:
                // private functions here?
                CAVIFile FAR*   m_pAVIFile;
        };

        implement CPersistFileImpl : IPersistFile {
        public:
            CPersistFileImpl(CAVIFile FAR* pAVIFile);
            STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppv);
            STDMETHODIMP_(ULONG) AddRef();
            STDMETHODIMP_(ULONG) Release();

            // *** IPersist methods ***
            STDMETHODIMP GetClassID (LPCLSID lpClassID);

            // *** IPersistFile methods ***
            STDMETHODIMP IsDirty ();
            STDMETHODIMP Load (LPCOLESTR lpszFileName, DWORD grfMode);
            STDMETHODIMP Save (LPCOLESTR lpszFileName, BOOL fRemember);
            STDMETHODIMP SaveCompleted (LPCOLESTR lpszFileName);
            STDMETHODIMP GetCurFile (LPOLESTR FAR * lplpszFileName);

        private:
            CAVIFile FAR*       m_pAVIFile;
        };

#ifdef CUSTOMMARSHAL
        implement CMarshalImpl : IMarshal {
        public:
                _StdClassImplementations(
                        CMarshalImpl);
                CMarshalImpl(
                        CAVIFile FAR* pAVIFile);
                // *** IMarshal methods ***
                STDMETHODIMP GetUnmarshalClass (
                        THIS_
                        REFIID riid,
                        LPVOID pv,
                        DWORD dwDestContext,
                        LPVOID pvDestContext,
                        DWORD mshlflags,
                        LPCLSID pCid);
                STDMETHODIMP GetMarshalSizeMax (
                        THIS_
                        REFIID riid,
                        LPVOID pv,
                        DWORD dwDestContext,
                        LPVOID pvDestContext,
                        DWORD mshlflags,
                        LPDWORD pSize);
                STDMETHODIMP MarshalInterface(
                        THIS_
                        LPSTREAM pStm,
                        REFIID riid,
                        LPVOID pv,
                        DWORD dwDestContext,
                        LPVOID pvDestContext,
                        DWORD mshlflags);
                STDMETHODIMP UnmarshalInterface (
                        THIS_
                        LPSTREAM pStm,
                        REFIID riid,
                        LPVOID FAR* ppv);
                STDMETHODIMP ReleaseMarshalData (
                        THIS_
                        LPSTREAM pStm);
                STDMETHODIMP DisconnectObject (
                THIS_
                DWORD dwReserved);
        private:
                CAVIFile FAR*   m_pAVIFile;
        };
#endif
public:
        CUnknownImpl    m_Unknown;
        CAVIFileImpl    m_AVIFile;
        CAVIStreamImpl  m_AVIStream;
        CPersistFileImpl m_Persist;
#ifdef CUSTOMMARSHAL
        CMarshalImpl    m_Marshal;
#endif
public:
        IUnknown FAR*   m_pUnknownOuter;

        //
        // DIB Sequence instance data
        //
        DWORD   dwFirstFrame;
        DWORD   dwMaxValue;
#ifndef _MAX_PATH
        #define _MAX_PATH       260
#else
        #if _MAX_PATH !=        260     // Error check
                #error
        #endif
#endif
        char    achFilenameTemplate[_MAX_PATH];
        LONG    lCurFrame;
        AVIFILEINFO     finfo;
        AVISTREAMINFOW sinfo;

        BOOL    fStreamPresent;
        LPVOID  lpFormat;       // stream format
        LONG    cbFormat;
        LONG    cbFormatBuffer;
        LPVOID  lpFrame;
        LONG    cbFrame;
        LONG    cbFrameBuffer;
        BOOL    fDirty;
        UINT    mode;
};

/*      -       -       -       -       -       -       -       -       */

/*
** The usage counter keeps track of the overall usage of objects based on
** implementations provided by the component. This allows one to determine
** when the implementation is no longer in use.
*/

extern UINT     uUseCount;
extern BOOL     fLocked;

/*      -       -       -       -       -       -       -       -       */

DEFINE_AVIGUID(CLSID_DIBSEQFileReader, 0x00020006, 0, 0);


DSEQF.CPP

/****************************************************************************
 *
 *  DSEQF.CPP
 *
 *  routines for reading DIB sequences
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <ctype.h>
#include <vfw.h>

#include "handler.h"
#include "handler.rc"

static DWORD NEAR PASCAL dseqParseFileName(
        LPSTR lpszFileName,
        LPSTR lpszTemplate,
        DWORD FAR * lpdwMaxValue);

#ifdef DEBUG
        static void CDECL dprintf(LPSTR, ...);
        #define DPF dprintf
#else
        #define DPF ; / ## /
#endif

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/*      -       -       -       -       -       -       -       -       */

UINT    uUseCount;
BOOL    fLocked;

/*      -       -       -       -       -       -       -       -       */

//
// External function called by the Class Factory to create an instance of
// the DIB sequence reader/writer
//
HRESULT CAVIFile::Create(
IUnknown FAR*   pUnknownOuter,
const IID FAR&  riid,
void FAR* FAR*  ppv)
{
        IUnknown FAR*   pUnknown;
        CAVIFile FAR*   pAVIFile;
        HRESULT hresult;

        pAVIFile = new FAR CAVIFile(pUnknownOuter, &pUnknown);
        if (!pAVIFile)
                return ResultFromScode(E_OUTOFMEMORY);
        hresult = pUnknown->QueryInterface(riid, ppv);
        if (FAILED(GetScode(hresult)))
                delete pAVIFile;
        return hresult;
}

/*      -       -       -       -       -       -       -       -       */

//
// Random C++ stuff: constructors & such...
//
CAVIFile::CAVIFile(
IUnknown FAR*   pUnknownOuter,
IUnknown FAR* FAR*      ppUnknown) :
        m_Unknown(this),
        m_AVIFile(this),
        m_Persist(this),
#ifdef CUSTOMMARSHAL
        m_Marshal(this),
#endif
        m_AVIStream(this)
{
        if (pUnknownOuter)
                m_pUnknownOuter = pUnknownOuter;
        else
                m_pUnknownOuter = &m_Unknown;
        *ppUnknown = &m_Unknown;
}

/*      -       -       -       -       -       -       -       -       */

CAVIFile::CUnknownImpl::CUnknownImpl(
CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
        m_refs = 0;
}

/*      -       -       -       -       -       -       -       -       */

//
// This QueryInterface function allows a caller to move between the various
// interfaces the object presents
//
STDMETHODIMP CAVIFile::CUnknownImpl::QueryInterface(
const IID FAR&  iid,
void FAR* FAR*  ppv)
{
        if (iid == IID_IUnknown)
                *ppv = &m_pAVIFile->m_Unknown;
        else if (iid == IID_IAVIFile)
                *ppv = &m_pAVIFile->m_AVIFile;
        else if (iid == IID_IAVIStream)
                *ppv = &m_pAVIFile->m_AVIStream;
        else if (iid == IID_IPersistFile)
                *ppv = &m_pAVIFile->m_Persist;
#ifdef CUSTOMMARSHAL
        else if (iid == IID_IMarshal)
                *ppv = &m_pAVIFile->m_Marshal;
#endif
        else
                return ResultFromScode(E_NOINTERFACE);
        AddRef();
        return NULL;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CUnknownImpl::AddRef()
{
        uUseCount++;
        return ++m_refs;
}

/*      -       -       -       -       -       -       -       -       */

//
// All calls to AddRef, Release, QueryInterface for the file or stream
// functions are redirected to the Unknown implementation...
//
CAVIFile::CAVIFileImpl::CAVIFileImpl(
CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}

/*      -       -       -       -       -       -       -       -       */

CAVIFile::CAVIFileImpl::~CAVIFileImpl()
{
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFile::CAVIFileImpl::QueryInterface(
const IID FAR&  iid,
void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CAVIFileImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CAVIFileImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

/*      -       -       -       -       -       -       -       -       */

CAVIFile::CAVIStreamImpl::CAVIStreamImpl(
CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}

/*      -       -       -       -       -       -       -       -       */

CAVIFile::CAVIStreamImpl::~CAVIStreamImpl()
{
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFile::CAVIStreamImpl::QueryInterface(
const IID FAR&  iid,
void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CAVIStreamImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CAVIStreamImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

// --- IPersistFile implementation --------------------------------------

CAVIFile::CPersistFileImpl::CPersistFileImpl(CAVIFile FAR* pAVIFile)
{
    m_pAVIFile = pAVIFile;
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::QueryInterface(REFIID riid, LPVOID FAR* ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(riid, ppv);
}

STDMETHODIMP_(ULONG)
CAVIFile::CPersistFileImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

STDMETHODIMP_(ULONG)
CAVIFile::CPersistFileImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

// *** IPersist methods ***
STDMETHODIMP
CAVIFile::CPersistFileImpl::GetClassID (LPCLSID lpClassID)
{
        *lpClassID = CLSID_DIBSEQFileReader;

        return NOERROR;
}

// *** IPersistFile methods ***
STDMETHODIMP
CAVIFile::CPersistFileImpl::IsDirty ()
{
    if (m_pAVIFile->fDirty) {
        return NOERROR;
    } else {
        return ResultFromScode(S_FALSE);
    }
}

/*      -       -       -       -       -       -       -       -       */

//
// This function takes the name of the first file in a DIB sequence, and
// returns a printf() specifier which can be used to create the names in
// the sequence, along with minimum and maximum values that can be used.
//
//
// Examples:
//  lpszFileName = "FOO0047.DIB"
//       -> lpszTemplate = "FOO%04d.DIB", dwMaxValue = 9999, return = 47
//
//  lpszFileName = "TEST01.DIB"
//       -> lpszTemplate = "TEST%01d.DIB", dwMaxValue = 9, return = 1
//
//  lpszFileName = "TEST1.DIB"
//       -> lpszTemplate = "TEST%d.DIB", dwMaxValue = 9999, return = 1
//
//  lpszFileName = "SINGLE.DIB"
//       -> lpszTemplate = "SINGLE.DIB", dwMaxValue = 0, return = 0
//
static DWORD NEAR PASCAL dseqParseFileName(
LPSTR lpszFileName,
LPSTR lpszTemplate,
DWORD FAR * lpdwMaxValue)
{
        char    achTemp[_MAX_PATH];
        DWORD   dwFirst;
        WORD    wFieldWidth;
        DWORD   dwMult;
        BOOL    fLeadingZero = FALSE;

        LPSTR   lp;
        LPSTR   lp2;
        LPSTR   lpExt;

        /* Find end of string */
        lp2 = lpszFileName;
        lp = achTemp;
        while (*lp2)
        *lp++ = *lp2++;

        *lp = '\0';

        /* Make lp2 point at last character of base filename (w/o extension) */
        /* Make lpExt point at the extension (without the dot) */
        for (lp2 = lp; *lp2 != '.'; lp2--)
        {
                lpExt = lp2;
                if ((lp2 == achTemp) || (*lp2 == '\\')
                        || (*lp2 == ':') || (*lp2 == '!'))
                {
                        /* There is no extension */
                        lp2 = lp;
                        lpExt = lp;
                        break;
                }
        }

        lp2--;

        // Count the number of numeric characters here....
        dwFirst = 0;
        wFieldWidth = 0;
        dwMult = 1;
        while (lp2 >= achTemp && (*lp2 >= '0') && (*lp2 <= '9')) {
        fLeadingZero = (*lp2 == '0');
        dwFirst += dwMult * (*(lp2--) - '0');
        dwMult *= 10;
        wFieldWidth++;
        }

        *lpdwMaxValue = dwMult - 1;

        lp2++;
        *lp2 = '\0';

        // Make the format specifier....
        if (wFieldWidth) {
        if (fLeadingZero) {
                wsprintf((LPSTR) lpszTemplate,"%s%%0%ulu.%s",
                        (LPSTR) achTemp, wFieldWidth,(LPSTR) lpExt);
        } else {
                wsprintf((LPSTR) lpszTemplate,"%s%%lu.%s",
                        (LPSTR) achTemp, (LPSTR) lpExt);
                *lpdwMaxValue = 999999L;
                // !!! This should really be based on the number of
                // characters left after the base name....
        }
        } else
        wsprintf((LPSTR) lpszTemplate,"%s.%s",
                        (LPSTR) achTemp, (LPSTR) lpExt);
        DPF("First = %lu, Width = %u, Template = '%s'\n",dwFirst, wFieldWidth, lpszTemplate);

        return dwFirst;
}

/*      -       -       -       -       -       -       -       -       */

#define SLASH(c) ((c) == '/' || (c) == '\\')

/*--------------------------------------------------------------+
| FileName  - return a pointer to the filename part of szPath   |
|             with no preceding path.                           |
+--------------------------------------------------------------*/
LPSTR FAR FileName(
LPCSTR lszPath)
{
        LPCSTR  lszCur;

        for (lszCur = lszPath + lstrlen(lszPath); lszCur > lszPath && !SLASH(*lszCur) && *lszCur != ':';)
                lszCur = AnsiPrev(lszPath, lszCur);
        if (lszCur == lszPath)
                return (LPSTR)lszCur;
        else
                return (LPSTR)(lszCur + 1);
}

/*      -       -       -       -       -       -       -       -       */

//
// "Open" a DIB sequence, by parsing the filename and counting the number
// of frames actually present....
//
STDMETHODIMP
CAVIFile::CPersistFileImpl::Load (LPCOLESTR szFile, DWORD mode)
{
        CAVIFile FAR    *p = m_pAVIFile;
        UINT            ui;
        char            ach[80];
        char            szFileA[MAX_PATH];

        p->mode = mode;

        //
        // Parse the filename
        //
        wsprintf(szFileA, "%ls", szFile);
        p->dwFirstFrame = dseqParseFileName(szFileA,
                                p->achFilenameTemplate,
                                &p->dwMaxValue);

        //
        // Initialize the variables that keep track of what frame is cached
        //
        p->lCurFrame = -1;
        p->lpFrame = NULL;
        p->cbFrame = 0;
        p->cbFrameBuffer = 0;
        p->lpFormat = NULL;
        p->cbFormat = 0;
        p->cbFormatBuffer = 0;

        //
        // Build a stream header....
        //
        p->sinfo.fccType = streamtypeVIDEO;
        p->sinfo.fccHandler = 0;
        p->sinfo.dwFlags = 0;
        p->sinfo.wPriority = 0;
        p->sinfo.wLanguage = 0;
        p->sinfo.dwInitialFrames = 0;
        p->sinfo.dwScale = 1;
        p->sinfo.dwRate = 15;
        p->sinfo.dwStart = 0;
        p->sinfo.dwLength = 0;
        p->sinfo.dwSuggestedBufferSize = 0;
        p->sinfo.dwSampleSize = 0;

        LoadString(ghModule, IDS_STREAMNAME, ach, sizeof(ach));
        {
            char TempFileName[80];
            char TempName[80];
            wsprintf(TempFileName, "%ls", szFile);
            wsprintf(TempName, ach, FileName(TempFileName));
            wsprintfW(p->sinfo.szName, L"%hs", TempName);
        }

        //
        // ... and a file header.
        //
        _fmemset(&p->finfo, 0, sizeof(p->finfo));
        p->finfo.dwRate = 15;
        p->finfo.dwScale = 1;
        p->finfo.dwStreams = 1;
        p->finfo.dwWidth = 0;
        p->finfo.dwHeight = 0;
        LoadString(ghModule, IDS_FILETYPE,
                p->finfo.szFileType,
                sizeof(p->finfo.szFileType));

        p->finfo.dwCaps = AVIFILECAPS_CANREAD |
                                AVIFILECAPS_CANWRITE |
                                AVIFILECAPS_ALLKEYFRAMES;

        if (mode & OF_CREATE) {
        //
        // They're creating a "new" sequence
        //
        p->fStreamPresent = FALSE;
        } else {
        char            ach[_MAX_PATH];
        OFSTRUCT        of;
        DWORD           dwFrame;
        HRESULT         hr;

        //
        // They're opening an existing sequence, so we have to actually
        // count how many files are present
        //
        p->fStreamPresent = TRUE;

        ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

        for (dwFrame = 0; TRUE; dwFrame++) {
                if (dwFrame > p->dwMaxValue)
                break;

                wsprintf(ach,p->achFilenameTemplate, dwFrame + p->dwFirstFrame);

                // DPF("DIBSEQ: Checking frame %lu from '%s'\n",dwFrame,(LPSTR) ach);

/****************************************************************************/
/* DOS share has a bug.  If the file we're testing for existence is open    */
/* already by someone else, we have to give it the same flag for SHARE as   */
/* the other person is using.  So we have to try both on and off.  Only one */
/* of these will return TRUE but if one of them does, the file exists.  Also*/
/* we have to turn off the system model error box for share violations.     */
/****************************************************************************/

                if (OpenFile((LPSTR)ach, &of, OF_EXIST) == HFILE_ERROR &&
                        OpenFile((LPSTR)ach, &of, OF_EXIST | OF_SHARE_DENY_NONE) ==
                        HFILE_ERROR)
                break;
        }

        SetErrorMode(ui);

        if (dwFrame == 0)
                goto error;

        //
        // Fix up the length in the header structures
        //
        p->sinfo.dwLength = dwFrame;
        p->finfo.dwLength = dwFrame;

        //
        // Load the first frame, so we'll be ready...
        //
        hr = p->LoadFrame(0);

        if (FAILED(GetScode(hr)))
                return hr;

        p->finfo.dwSuggestedBufferSize = p->cbFrame;
        p->sinfo.dwSuggestedBufferSize = p->cbFrame;

        p->finfo.dwWidth = ((LPBITMAPINFOHEADER) p->lpFormat)->biWidth;
        p->finfo.dwHeight = ((LPBITMAPINFOHEADER) p->lpFormat)->biHeight;

        p->sinfo.dwFlags = AVISTREAMINFO_FORMATCHANGES;

        SetRect(&p->sinfo.rcFrame,
                0, 0, (int) p->finfo.dwWidth, (int) p->finfo.dwHeight);
        }

        //
        // all done return success.
        //
        return ResultFromScode(0); // success

error:
        return ResultFromScode(AVIERR_FILEREAD);

}

STDMETHODIMP
CAVIFile::CPersistFileImpl::Save (LPCOLESTR lpszFileName, BOOL fRemember)
{
        return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::SaveCompleted (LPCOLESTR lpszFileName)
{
        return NOERROR;
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::GetCurFile (LPOLESTR FAR * lplpszFileName)
{
        return ResultFromScode(AVIERR_UNSUPPORTED);
}

// -------------------- IAVIFile Implementation-----------------------

//
// The GetStream method returns an interface pointer to the video stream,
// assuming one exists.
//
STDMETHODIMP CAVIFile::CAVIFileImpl::GetStream(
PAVISTREAM FAR * ppavi,
DWORD fccType,
LONG lParam)
{
        CAVIFile FAR *  p = m_pAVIFile;
        int             iStreamWant;

        iStreamWant = (int)lParam;

        if (!p->fStreamPresent)
        return ResultFromScode(-1);

        // We only support one stream
        if (lParam != 0)
                return ResultFromScode(-1);

        // We only support a video stream
        if (fccType && fccType != streamtypeVIDEO)
        return ResultFromScode(-1);

        //
        // Be sure to keep the reference count up to date...
        //
        AddRef();

        *ppavi = (PAVISTREAM) &(p->m_AVIStream);
        return ResultFromScode(AVIERR_OK);
}

//
// If they opened the file with the OF_CREATE flag, they will use this
// method to create the video stream.
//
STDMETHODIMP CAVIFile::CAVIFileImpl::CreateStream(
PAVISTREAM FAR *ppstream,
AVISTREAMINFOW FAR *psi
)
{
        CAVIFile FAR *  p = m_pAVIFile;

        // If the stream was already there, we fail.
        if (p->fStreamPresent)
        return ResultFromScode(AVIERR_UNSUPPORTED);

        p->sinfo = *psi;
        p->sinfo.dwLength = 0;

        *ppstream = (PAVISTREAM) &(p->m_AVIStream);
        p->fStreamPresent = TRUE;

        // Keep the reference count correct
        AddRef();

        return ResultFromScode(AVIERR_OK);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::WriteData(
DWORD ckid,
LPVOID lpData,
LONG cbData)
{
        CAVIFile FAR *  p = m_pAVIFile;

        return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::ReadData(
DWORD ckid,
LPVOID lpData,
LONG FAR *lpcbData)
{
        CAVIFile FAR *  p = m_pAVIFile;

        return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::EndRecord(void)
{
        return ResultFromScode(AVIERR_OK);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::Info(AVIFILEINFOW FAR * pfi, LONG lSize)
{
        CAVIFile FAR *  p = m_pAVIFile;

        hmemcpy(pfi, &p->finfo, min(lSize,sizeof(p->finfo)));
        return 0;
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::Create(
LONG lParam1,
LONG lParam2)
{
        return ResultFromScode(AVIERR_UNSUPPORTED);
}

//
// Returns where the last key frame before the given frame is.
//
// For now, we assume each DIB is a key frame.
//
STDMETHODIMP_(LONG) CAVIFile::CAVIStreamImpl::FindSample(
LONG lPos,
LONG lFlags)
{
        CAVIFile FAR * p = m_pAVIFile;

        // some minimal error checking....
        if (lPos < 0 || lPos >= (LONG) p->sinfo.dwLength)
        return -1;

        // !!! Can we really assume every frame is non-empty and a key frame?
        // !!! Who knows where format changes are? Let's assume everywhere!

        return lPos;
}

#define WIDTHBYTES(i)           ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi)       (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define BFT_BITMAP              0x4d42   /* 'BM' */

//
// Helper function to load a given frame into our cache.
//
// This is where the actual work is done; all other functions just return
// the current format or frame out of the cache.
//
HRESULT NEAR PASCAL CAVIFile::LoadFrame(
LONG lPos)
{
        char            ach[_MAX_PATH];
        HMMIO           hmmio;
        BITMAPFILEHEADER        bfh;
        BITMAPINFOHEADER        bih;
        SCODE           sc = 0;
        UINT            ui;

        //
        // Check if we've already loaded this frame...
        //
        if (lPos == lCurFrame)
        return 0;

        //
        // Build the filename by printing using our template
        //
        wsprintf(ach, achFilenameTemplate, dwFirstFrame + lPos);

        // No system error box, please.
        ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

        //
        // Go try to read the frame... Because of SHARE we have to try
        // opening it two different ways.
        //
        hmmio = mmioOpen(ach, NULL, MMIO_READ | OF_SHARE_DENY_WRITE);
        if (!hmmio) {
                hmmio = mmioOpen(ach, NULL, MMIO_READ);
        if (!hmmio)
                return ResultFromScode(AVIERR_FILEOPEN);
        }

        SetErrorMode(ui);

        //
        // Read the BitmapFileHeader...
        //
        if (mmioRead(hmmio, (LPSTR) &bfh, sizeof(bfh)) != sizeof(bfh)) {
        sc = AVIERR_FILEREAD;
        goto error;
        }

        if (bfh.bfType != BFT_BITMAP) {
        sc = AVIERR_BADFORMAT;
        goto error;
        }

        //
        // Read the BitmapInfoHeader...
        //
        if (mmioRead(hmmio, (LPSTR) &bih, sizeof(bih)) != sizeof(bih)) {
        sc = AVIERR_FILEREAD;
        goto error;
        }

        if (bih.biSize < sizeof(bih)) {
        sc = AVIERR_BADFORMAT;
        goto error;
        }

        // Check that the width and height match....
        if ((finfo.dwWidth && finfo.dwWidth != (DWORD) bih.biWidth) ||
        (finfo.dwHeight && finfo.dwHeight != (DWORD) bih.biHeight)) {
        sc = AVIERR_BADFORMAT;
        goto error;
        }

        // Fix up some fields in the header...
        if (bih.biSizeImage == 0) {
        bih.biSizeImage = DIBWIDTHBYTES(bih) * bih.biHeight;
        }

        if (bih.biClrUsed == 0 && bih.biBitCount <= 8 && bih.biCompression <= BI_RLE8)
        bih.biClrUsed = 1 << bih.biBitCount;

        cbFormat = bih.biSize + bih.biClrUsed * sizeof(RGBQUAD);

        // Allocate space for the format
        if (cbFormat > cbFormatBuffer) {
        if (lpFormat) {
                GlobalFreePtr(lpFormat);
                lpFormat = 0;
                cbFormatBuffer = 0;
        }

        lpFormat = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, cbFormat);
        if (!lpFormat) {
                sc = AVIERR_MEMORY;
                goto error;
        }

        cbFormatBuffer = cbFormat;
        }

        *((LPBITMAPINFOHEADER) lpFormat) = bih;

        // If the format is bigger than a BITMAPINFOHEADER, read the rest....
        if (cbFormat > sizeof(bih)) {
        if (mmioRead(hmmio, (LPSTR) lpFormat + sizeof(bih),
                cbFormat - (LONG)sizeof(bih))
                != cbFormat - (LONG)sizeof(bih))
        {
                sc = AVIERR_FILEREAD;
                goto error;
        }
        }

        //
        // Allocate enough space to read the frame in...
        //
        if (bih.biSizeImage > (DWORD) cbFrameBuffer) {
        if (lpFrame) {
                GlobalFreePtr(lpFrame);
                lpFrame = 0;
                cbFrameBuffer = 0;
        }

        lpFrame = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, bih.biSizeImage);
        if (!lpFrame) {
                sc = AVIERR_MEMORY;
                goto error;
        }

        cbFrameBuffer = bih.biSizeImage;
        }

        cbFrame = bih.biSizeImage;

        //
        // and actually read the frame....
        //
        if (mmioRead(hmmio, (LPSTR) lpFrame, cbFrame) != cbFrame) {
        sc = AVIERR_FILEREAD;
        goto error;
        }

        lCurFrame = lPos;

error:
        mmioClose(hmmio, 0);

        return ResultFromScode(sc);
}

//
// The ReadFormat method returns the format of the specified frame....
//
STDMETHODIMP CAVIFile::CAVIStreamImpl::ReadFormat(
LONG lPos,
LPVOID lpFormat,
LONG FAR *lpcbFormat)
{
        CAVIFile FAR * p = m_pAVIFile;
        HRESULT hr;

        //
        // Try to get the correct frame
        //
        hr = p->LoadFrame(lPos);

        if (hr != 0)
        return hr;

        // No buffer to fill in, this means return the size needed.
        if (lpFormat == NULL || *lpcbFormat == 0) {
                *lpcbFormat = p->cbFormat;
        return 0;
        }

        //
        // and return as much of the format as will fit.
        //
        hmemcpy(lpFormat, p->lpFormat, min(*lpcbFormat, p->cbFormat));
        *lpcbFormat = p->cbFormat;
        return 0;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIStreamImpl::Info(AVISTREAMINFOW FAR * psi, LONG lSize)
{
        CAVIFile FAR * p = m_pAVIFile;

        hmemcpy(psi,&p->sinfo, min(lSize,sizeof(p->sinfo)));
//      return sizeof(p->sinfo);
        return 0;
}

STDMETHODIMP_(ULONG) CAVIFile::CUnknownImpl::Release()
{
        CAVIFile FAR * p = m_pAVIFile;

        uUseCount--;
        if (!--m_refs) {
        LONG lRet = AVIERR_OK;

        if (p->fDirty) {
        }

        goto success;

        success:
        if (p->lpFormat)
                GlobalFreePtr(p->lpFormat);

        p->lpFormat = NULL;
        p->cbFormat = 0;

        if (p->lpFrame)
                GlobalFreePtr(p->lpFrame);

        p->lpFrame = NULL;
        p->cbFrame = 0;

        delete this;
        return 0;
        }
        return m_refs;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIStreamImpl::Read(
LONG            lStart,
LONG            lSamples,
LPVOID          lpBuffer,
LONG            cbBuffer,
LONG FAR *      plBytes,
LONG FAR *      plSamples)
{
        CAVIFile FAR *  p = m_pAVIFile;
        HRESULT     hr;

        if (lStart < 0 || lStart >= (LONG) p->sinfo.dwLength) {
        if (plBytes)
            *plBytes = 0;
        if (plSamples)
            *plSamples = 0;
        return 0;
        }

        // We always read one frame at a time...
        lSamples = 1;

        // Load it into the cache....
        hr = p->LoadFrame(lStart);

        if (hr != 0)
        return hr;

        //
        // a NULL buffer means return the size buffer needed to read
        // the given sample.
        //
        if (lpBuffer == NULL || cbBuffer == 0) {
        if (plBytes)
            *plBytes =  p->cbFrame;
        if (plSamples)
            *plSamples = lSamples;
        return 0;
        }

        //
        // They didn't give us enough space for the frame, so complain
        //
        if (cbBuffer < p->cbFrame) {
        if (plBytes)
            *plBytes = p->cbFrame;

        return ResultFromScode(AVIERR_BUFFERTOOSMALL);
        }

        //
        // Copy the frame into the caller's buffer
        //
        hmemcpy(lpBuffer, p->lpFrame, p->cbFrame);

        //
        // success return number of bytes and number of samples read
        //
        if (plBytes)
                *plBytes = p->cbFrame;

        if (plSamples)
                *plSamples = lSamples;

        return ResultFromScode(AVIERR_OK);
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::SetFormat(
LONG lPos,
LPVOID lpFormat,
LONG cbFormat)
{
        CAVIFile FAR * p = m_pAVIFile;

        // Keep track of the format....
        p->cbFormat = cbFormat;
        p->lpFormat = (LPVOID) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, cbFormat);

        if (p->lpFormat == NULL)
        return ResultFromScode(AVIERR_MEMORY);

        hmemcpy(p->lpFormat, lpFormat, cbFormat);

        p->finfo.dwWidth = ((LPBITMAPINFOHEADER) p->lpFormat)->biWidth;
        p->finfo.dwHeight = ((LPBITMAPINFOHEADER) p->lpFormat)->biHeight;

        SetRect(&p->sinfo.rcFrame,
            0, 0, (int) p->finfo.dwWidth, (int) p->finfo.dwHeight);

        return 0L;
}

//
// Helper function to save a single frame
//
HRESULT NEAR PASCAL CAVIFile::WriteFrame(
LONG lPos,
LPVOID lp,
LONG cb)
{
        char            ach[_MAX_PATH];
        HMMIO           hmmio;
        BITMAPFILEHEADER        bfh;

        //
        // If they're overwriting the cached frame, invalidate the cache

//
        if (lPos == lCurFrame)
        lCurFrame = -1;

        //
        // Build the filename to write to
        //
        wsprintf(ach, achFilenameTemplate, dwFirstFrame + lPos);

        // and write it.
        hmmio = mmioOpen(ach, NULL, MMIO_WRITE | MMIO_CREATE | OF_SHARE_EXCLUSIVE);

        if (!hmmio)
        return ResultFromScode(AVIERR_FILEOPEN);

        //
        // Write the BitmapFileHeader
        //
        bfh.bfType = BFT_BITMAP;
        bfh.bfOffBits = sizeof(bfh) + cbFormat;
        bfh.bfSize = bfh.bfOffBits + cb;

        if (mmioWrite(hmmio, (LPSTR) &bfh, sizeof(bfh)) != sizeof(bfh)) {
error:
        mmioClose(hmmio, 0);
        return ResultFromScode(AVIERR_FILEWRITE);
        }

        ((LPBITMAPINFOHEADER) lpFormat)->biSizeImage = cb;

        //
        // Write the DIB format
        //
        if (mmioWrite(hmmio, (LPSTR) lpFormat, cbFormat) != cbFormat)
        goto error;

        //
        // Write the data
        //
        if (mmioWrite(hmmio, (LPSTR) lp, cb) != cb)
        goto error;

        //
        // Flush things so that we can be sure everything is written out
        //
        if (mmioFlush(hmmio, 0) != 0)
        goto error;

        mmioClose(hmmio, 0);

        return 0;
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::Write(
LONG lStart,
LONG lSamples,
LPVOID lpData,
LONG cbData,
DWORD dwFlags,
LONG FAR *plSampWritten,
LONG FAR *plBytesWritten)
{
        CAVIFile FAR *  p = m_pAVIFile;
        HRESULT     hr;

        if ((p->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

        if (p->lpFormat == NULL)
        return ResultFromScode(AVIERR_UNSUPPORTED);

        // < 0 means "at end"
        if (lStart < 0)
        lStart = p->sinfo.dwStart + p->sinfo.dwLength;

        if (lStart > (LONG) (p->sinfo.dwStart + p->sinfo.dwLength))
        return ResultFromScode(AVIERR_BADPARAM);

        // !!! Die if we've reached the limit of our numbers....
        if ((DWORD) lStart + p->dwFirstFrame > p->dwMaxValue)
        return ResultFromScode(AVIERR_FILEWRITE);

        if (lSamples != 1)
        return ResultFromScode(AVIERR_BADPARAM);

        // only allow key frames!
        if (!(dwFlags & AVIIF_KEYFRAME)) {
        DPF("Tried to write a non-key frame to a DIB sequence!\n");
        return ResultFromScode(AVIERR_UNSUPPORTED);
        }

        hr = p->WriteFrame(lStart, lpData, cbData);

        if (hr != AVIERR_OK)
        return hr;

        p->fDirty = TRUE;

        p->sinfo.dwLength =
                max((LONG) p->sinfo.dwLength,
                                  lStart + lSamples);

        p->finfo.dwLength = p->sinfo.dwLength;

        p->finfo.dwSuggestedBufferSize =
                        max(p->finfo.dwSuggestedBufferSize, (DWORD) cbData);
        p->sinfo.dwSuggestedBufferSize =
                        p->finfo.dwSuggestedBufferSize;

        if (plSampWritten)
        *plSampWritten = lSamples;

        if (plBytesWritten)
        *plBytesWritten = cbData;

        return ResultFromScode(AVIERR_OK);
}

// these both are for saving. we don't support saving

STDMETHODIMP
CAVIFile::CAVIFileImpl::DeleteStream(DWORD fccType, LONG lParam)
{
    return ResultFromScode(E_FAIL);
}

STDMETHODIMP
CAVIFile::CAVIStreamImpl::SetInfo(
                AVISTREAMINFOW FAR * lpInfo,
                LONG cbInfo
)
{
    return ResultFromScode(E_FAIL);
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::Delete(
LONG lStart,
LONG lSamples)
{
        CAVIFile FAR * p = m_pAVIFile;

        return ResultFromScode(AVIERR_UNSUPPORTED);
}

// Should these just map to Read/WriteData? !!!

STDMETHODIMP CAVIFile::CAVIStreamImpl::ReadData(
DWORD fcc,
LPVOID lp,
LONG FAR *lpcb)
{
        CAVIFile FAR * p = m_pAVIFile;

        return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::WriteData(
DWORD fcc,
LPVOID lp,
LONG cb)
{
        CAVIFile FAR * p = m_pAVIFile;

        return ResultFromScode(AVIERR_UNSUPPORTED);
}

#ifdef CUSTOMMARSHAL
// The code below supports custom marshalling.

// !!! Need good explanation here!

CAVIFile::CMarshalImpl::CMarshalImpl(
CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP CAVIFile::CMarshalImpl::QueryInterface(
const IID FAR&  iid,
void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

// *** IMarshal methods ***
STDMETHODIMP CAVIFile::CMarshalImpl::GetUnmarshalClass (
THIS_ REFIID riid, LPVOID pv,
DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags, LPCLSID pCid)
{
        HRESULT hr = NOERROR;

        DPF("UnMarshalClass called\n");

        if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL       pMarshal;

        DPF("Marshal context is %lu: delegating...\n", dwDestContext);

        hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
                        &pMarshal);

        if (hr != NOERROR)
                return hr;

        hr = pMarshal->GetUnmarshalClass(riid, pv, dwDestContext, pvDestContext,
                        mshlflags, pCid);

        pMarshal->Release();

        return hr;
        }

        *pCid = CLSID_AVISimpleUnMarshal;

        return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::GetMarshalSizeMax (
THIS_ REFIID riid, LPVOID pv,
DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags, LPDWORD pSize)
{
        HRESULT hr = NOERROR;
        IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

        if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL       pMarshal;

        hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
                &pMarshal);

        if (hr != NOERROR)
                return hr;

        hr = pMarshal->GetMarshalSizeMax(riid, pv, dwDestContext, pvDestContext,
                        mshlflags, pSize);

        pMarshal->Release();

        return hr;
        }

        *pSize = sizeof(pUnk);

        return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::MarshalInterface (
THIS_ LPSTREAM pStm, REFIID riid,
LPVOID pv, DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags)
{
        HRESULT hr = NOERROR;
        IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

        DPF("MarshalInterface called\n");

        if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL pMarshal;

        DPF("Marshal context is %lu: delegating...\n", dwDestContext);

        hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
                &pMarshal);

        if (hr != NOERROR)
                return hr;

        hr = pMarshal->MarshalInterface(pStm, riid, pv, dwDestContext, pvDestContext,
                        mshlflags);

        pMarshal->Release();

        return hr;
        }

        if ((riid != IID_IAVIStream && riid != IID_IAVIFile && riid != IID_IUnknown))
                return ResultFromScode(E_INVALIDARG);

        if ((hr = pStm->Write(&pUnk, sizeof(pUnk), NULL)) == NOERROR)
        AddRef();

        DPF("Returns %lx\n", (DWORD) (LPVOID) hr);
        return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::UnmarshalInterface (
THIS_ LPSTREAM pStm, REFIID riid,
LPVOID FAR* ppv)
{
        HRESULT hr = ResultFromScode(E_FAIL);

        DPF("UnMarshalInterface called!!!\n");
        return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::ReleaseMarshalData (
THIS_ LPSTREAM pStm)
{
        HRESULT hr = NOERROR;
        IUnknown FAR * pUnk;

        hr = pStm->Read(&pUnk,sizeof(pUnk),NULL);
        if (hr == NOERROR)
        pUnk->Release();

        return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::DisconnectObject (
THIS_ DWORD dwReserved)
{
        HRESULT hr = NOERROR;

        return hr;
}
#endif

/*****************************************************************************
 *
 * dprintf() is called by the DPF macro if DEBUG is defined at compile time.
 *
 * The messages will be send to COM1: like any debug message. To
 * enable debug output, add the following to WIN.INI :
 *
 * [debug]
 * ICSAMPLE=1
 *
 ****************************************************************************/

#ifdef DEBUG

#define MODNAME "DSEQFILE"
static BOOL fDebug = -1;

static void cdecl dprintf(
LPSTR szFormat, ...)
{
        char ach[128];

        va_list va;
        if (fDebug == -1)
                fDebug = GetProfileIntA("Debug",MODNAME, FALSE);

        if (!fDebug)
                return;

        va_start(va, szFormat);
        if (szFormat[0] == '!')
                ach[0]=0, szFormat++;
        else
                lstrcpyA(ach, MODNAME ": ");

        wvsprintfA(ach+lstrlenA(ach),szFormat,(LPSTR)va);
        va_end(va);
//      lstrcatA(ach, "\r\r\n");

        OutputDebugStringA(ach);
}

#endif


README.TXT

The LANGPLAY samples plays back multi-audio stream AVI files. MULTILNG.AVI
is an example file found on the CD.


LANGPLAY.H

/*---------------------------------------------------------------------
|
| LangPlay.h - Movie player include file
|
+-------------------------------------------------------------------*/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* constants */
#define BUFFER_LENGTH  256

/* Menu items */
/* File menu */
#define IDM_OPEN   1
#define IDM_CLOSE  2
#define IDM_EXIT   3
#define IDM_ABOUT  4

#define IDD_ABOUT  101

/* Audio menu */
#define IDM_NONE   10
#define IDM_STREAM 11


LANGPLAY.C

/*--------------------------------------------------------------------
|
| LangPlay.c - Sample Win app to play AVI movies using MCIWnd. Handles
|       multiple language track movies and lets the
|       user select the track to listen to at playback.
|
|
+--------------------------------------------------------------------*/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <string.h>
#include <stdlib.h>
#include <direct.h>
#include <digitalv.h>
#include <vfw.h>
#include "langplay.h"

/**************************************************************
************************ GLOBALS ******************************
**************************************************************/
/* AVI stuff to keep around */
HWND hwndMovie;                 /* window handle of the movie */
BOOL fMovieOpen = FALSE;        /* Open flag: TRUE == movie open, FALSE = none */
HMENU hMenuBar = NULL;          /* menu bar handle */
char szAppName [] = "LangPlay";

// struct for handling multi-language support
typedef struct langs_tag {
        WORD            wLangTag;       // language type tag
        char            achName[64];    // stream name  (limited to 64 chars by AVIStreamInfo)
} LANGS, FAR *LPLANGS;

#define         NOAUDIO         0       // no audio stream
int             iCurLang;               // current language selected (0 == NONE)

/* function declarations */
long FAR PASCAL WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void fileOpenMovie(HWND hWnd);
void menubarUpdate(HWND hWnd);
void titlebarUpdate(HWND hWnd, LPSTR lpstrMovie);
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/* language specific functions */
BOOL enumLangs(HWND hwnd, LPSTR lpstrMovie);
void buildLangMenu(HWND hwnd, LPLANGS lpLangs, DWORD dwLangs);
void switchLang(HWND hWnd, int iLangStream);

/********************************************************************
************************** FUNCTIONS ********************************
********************************************************************/

/*--------------------------------------------------------------+
| initApp - initialize the app overall.                         |
|                                                               |
| Returns the Window handle for the app on success, NULL if     |
| there is a failure.                                           |
|                                                               |
+--------------------------------------------------------------*/
HWND initApp(HINSTANCE hInstance, HINSTANCE hPrevInstance, int nCmdShow)
{
        HWND            hWnd;   /* window handle to return */
        int             iWinHeight;
        WORD    wVer;

        /* first let's make sure we are running on 1.1 */
        wVer = HIWORD(VideoForWindowsVersion());
        if (wVer < 0x010a){
                /* oops, we are too old, blow out of here */
                MessageBeep(MB_ICONHAND);
                MessageBox(NULL, "Video for Windows version is too old",
                          "LangPlay Error", MB_OK|MB_ICONSTOP);
                return FALSE;
        }

        if (!hPrevInstance){
                WNDCLASS    wndclass;

                wndclass.style         = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
                wndclass.lpfnWndProc   = WndProc;
                wndclass.cbClsExtra    = 0;
                wndclass.cbWndExtra    = 0;
                wndclass.hInstance     = hInstance;
                wndclass.hIcon         = LoadIcon (hInstance, "AppIcon");
                wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
                wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
                wndclass.lpszMenuName  = szAppName;
                wndclass.lpszClassName = szAppName;

                if (!RegisterClass(&wndclass)){
                        MessageBox(NULL, "RegisterClass failure", szAppName, MB_OK);
                        return NULL;
                }
        }

        iWinHeight = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYMENU) +
                        (GetSystemMetrics(SM_CYFRAME) * 2);

        /* create the main window for the app */
        hWnd = CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW |
                WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 180, iWinHeight,
                NULL, NULL, hInstance, NULL);

        if (hWnd == NULL){
                MessageBox(NULL, "CreateWindow failure", szAppName, MB_OK);
                return NULL;
        }

        hMenuBar = GetMenu(hWnd);       /* get the menu bar handle */
        menubarUpdate(hWnd);            /* update menu bar to disable Movie menu */

        /* Show the main window */
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);

        /* create the movie window using MCIWnd that has no file open initially */
        hwndMovie = MCIWndCreate(hWnd, hInstance, WS_CHILD |WS_VISIBLE | MCIWNDF_NOOPEN |
                                MCIWNDF_NOERRORDLG | MCIWNDF_NOTIFYSIZE, NULL);

        if (!hwndMovie){
                /* we didn't get the movie window, destroy the app's window and bail out */
                DestroyWindow(hWnd);
                return NULL;
        }
        return hWnd;
}

/*--------------------------------------------------------------+
| WinMain - main routine.                                       |
|                                                               |
+--------------------------------------------------------------*/
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                LPSTR lpszCmdParam, int nCmdShow)
{
        HWND        hWnd;
        MSG         msg;

        if ((hWnd = initApp(hInstance, hPrevInstance,nCmdShow)) == NULL)
                return 0;       /* died initializing, bail out */

        while (GetMessage(&msg, NULL, 0, 0)){
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
        return msg.wParam;
}

/*--------------------------------------------------------------+
| WndProc - window proc for the app                             |
|                                                               |
+--------------------------------------------------------------*/
long FAR PASCAL WndProc (HWND hWnd, UINT message, WPARAM wParam,
                                                LPARAM lParam)
{
        PAINTSTRUCT ps;
        WORD w;
        WORD    wMenu;
        RECT    rc;

        switch (message){
                case WM_CREATE:
                        return 0;

                case WM_INITMENUPOPUP:
                        /* be sure this isn't the system menu */
                        if (HIWORD(lParam))
                                return DefWindowProc(hWnd, WM_INITMENUPOPUP,
                                                wParam, lParam);

                        wMenu = LOWORD(lParam);
                        switch (wMenu){
                                case 0:   /* file menu */
                                        /* turn on/off CLOSE & PLAY */
                                        if (fMovieOpen) w = MF_ENABLED|MF_BYCOMMAND;
                                        else            w = MF_GRAYED|MF_BYCOMMAND;
                                        EnableMenuItem((HMENU)wParam, IDM_CLOSE, w);
                                        break;
                        } /* switch */
                        break;

                case WM_COMMAND:
                        if (wParam >= IDM_STREAM){
                                // the command is to switch the audio stream
                                switchLang(hWnd, wParam - IDM_STREAM + 1);
                                return 0;
                        }
                        /* handle the menu commands */
                        switch (wParam) {
                                /* File Menu */
                                case IDM_OPEN:
                                        fileOpenMovie(hWnd);
                                        break;
                                case IDM_CLOSE:
                                        fMovieOpen = FALSE;
                                        MCIWndClose(hwndMovie);         // close the movie
                                        ShowWindow(hwndMovie, SW_HIDE); //hide the window
                                        menubarUpdate(hWnd);
                                        titlebarUpdate(hWnd, NULL);     // title bar back to plain
                                        break;
                                case IDM_EXIT:
                                        PostMessage(hWnd, WM_CLOSE, 0, 0L);
                                        break;

                                /* audio menu */
                                case IDM_NONE:
                                        switchLang(hWnd, NOAUDIO);
                                        break;

                                case IDM_ABOUT:
                                        DialogBox((HANDLE)GetWindowInstance(hWnd),
                                                  MAKEINTRESOURCE(IDD_ABOUT),
                                                  hWnd,
                                                  AboutDlgProc);
                                        break;

                        }
                        return 0;

                case WM_PAINT:
                        BeginPaint(hWnd, &ps);
                        EndPaint(hWnd, &ps);
                        return 0;

                case WM_SIZE:
                        if (!IsIconic(hWnd) && hwndMovie && fMovieOpen)
                                MoveWindow(hwndMovie,0,0,LOWORD(lParam),HIWORD(lParam),TRUE);
                        break;

                case WM_DESTROY:
                        if (fMovieOpen)
                                MCIWndClose(hwndMovie);  // close an open movie
                        MCIWndDestroy(hwndMovie);    // now destroy the MCIWnd window
                        PostQuitMessage(0);
                        return 0;

                case MCIWNDM_NOTIFYSIZE:
if (!IsIconic(hWnd)) {
                            if (fMovieOpen){
                                /* adjust to size of the movie window */
                                GetWindowRect(hwndMovie, &rc);
                                AdjustWindowRect(&rc, GetWindowLong(hWnd, GWL_STYLE), TRUE);
                                SetWindowPos(hWnd, NULL, 0, 0, rc.right - rc.left,
                                        rc.bottom - rc.top,
                                        SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);
                            } else {
                                /* movie closed, adjust to the default size */
                                int iWinHeight;

                                iWinHeight = GetSystemMetrics(SM_CYCAPTION) +
                                                GetSystemMetrics(SM_CYMENU) +
                                                (GetSystemMetrics(SM_CYFRAME) * 2);
                                SetWindowPos(hWnd, NULL, 0, 0, 180, iWinHeight,
                                        SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);
    }
                        }
                        break;

                case WM_ACTIVATE:
                case WM_QUERYNEWPALETTE:
                case WM_PALETTECHANGED:
                        //
                        // Forward palette-related messages through to the MCIWnd,
                        // so it can do the right thing.
                        //
                        if (hwndMovie)
                                return SendMessage(hwndMovie, message, wParam, lParam);
                        break;
        } /* switch */
        return DefWindowProc(hWnd, message, wParam, lParam);
}

/*--------------------------------------------------------------+
| menubarUpdate - update the menu bar based on the <fMovieOpen> |
|                 flag value.  This will turn on/off the        |
|                 Movie menu.                                   |
|                                                               |
+--------------------------------------------------------------*/
void menubarUpdate(HWND hWnd)
{
        WORD w;

        if (fMovieOpen){
                w = MF_ENABLED|MF_BYPOSITION;
        } else {
                w = MF_GRAYED|MF_BYPOSITION;
        }
        EnableMenuItem(hMenuBar, 1, w); /* change the Movie menu (#1) */
        DrawMenuBar(hWnd);      /* re-draw the menu bar */
}

/*--------------------------------------------------------------+
| titlebarUpdate - update the title bar to include the name     |
|                  of the movie playing.                        |
|                                                               |
+--------------------------------------------------------------*/
void titlebarUpdate(HWND hWnd, LPSTR lpstrMovie)
{
        char achNewTitle[BUFFER_LENGTH];        // space for the title

        if (lpstrMovie != NULL)
                wsprintf((LPSTR)achNewTitle,"%s - %s", (LPSTR)szAppName,lpstrMovie);
        else
                lstrcpy((LPSTR)achNewTitle, (LPSTR)szAppName);
        SetWindowText(hWnd, (LPSTR)achNewTitle);
}

/*--------------------------------------------------------------+
| fileOpenMovie - open an AVI movie. Use CommDlg open box to    |
|               open and then handle the initialization to      |
|               show the movie and position it properly.  Keep  |
|               the movie paused when opened.                   |
|                                                               |
|               Sets <fMovieOpened> on success.                 |
+--------------------------------------------------------------*/
void fileOpenMovie(HWND hWnd)
{
        OPENFILENAME ofn;

        static char szFile [BUFFER_LENGTH];
        static char szFileTitle [BUFFER_LENGTH];

        /* use the OpenFile dialog to get the filename */
        memset(&ofn, 0, sizeof(ofn));
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner = hWnd;
        ofn.lpstrFilter = "Video for Windows\0*.avi\0\0";
        ofn.lpstrFile = szFile;
        ofn.nMaxFile = sizeof(szFile);
        ofn.lpstrFileTitle = szFileTitle;
        ofn.nMaxFileTitle = sizeof(szFileTitle);
        ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

        /* use CommDlg to get our filename */
        if (GetOpenFileName(&ofn)){

                /* we got a filename, now close any old movie and open */
                /* the new one.                                 */
                if (fMovieOpen)
                        MCIWndClose(hwndMovie);

                enumLangs(hWnd, ofn.lpstrFile); // find out the languages used in the file.

                /* try to open the file */
                fMovieOpen = TRUE;              // assume the best
                if (MCIWndOpen(hwndMovie, ofn.lpstrFile, 0) == 0){
                        /* we opened the file o.k., now set up to */
                        /* play it.                                */
                        ShowWindow(hwndMovie, SW_SHOW);
                } else {
                        /* generic error for open */
                        MessageBox(hWnd, "Unable to open Movie", NULL,
                              MB_ICONEXCLAMATION|MB_OK);
                        fMovieOpen = FALSE;
                }
        }
        /* update menu bar */
        menubarUpdate(hWnd);
        if (fMovieOpen)
                titlebarUpdate(hWnd, (LPSTR)ofn.lpstrFileTitle);
        else
                titlebarUpdate(hWnd, NULL);

        /* cause an update to occur */
        InvalidateRect(hWnd, NULL, FALSE);
        UpdateWindow(hWnd);
}

/*--------------------------------------------------------------+
| enumLangs - enumerate the audio streams in a file and set up  |
|             the global ghLangs as a handle to the array of    |
|             language streams.                                 |                                                               |
|                                                               |
| To do this:                                                   |
|       1. Open the file using AVIFileOpen().                   |
|       2. Open all Audio Streams, getting the pavi for it      |
|       3. Get the stream info on all audio streams to get names|
|       4. If names don't exist use "Audio Strean n"            |
|       5. Close the file                                       |
|       6. Build the audio stream menu                          |
|                                                               |
| Return TRUE if successfully set up menu, FALSE on any error   |
|                                                               |
+--------------------------------------------------------------*/
BOOL enumLangs(HWND hWnd, LPSTR lpstrMovie)
{
        PAVIFILE        pFile;
        PAVISTREAM      pStream;
        AVIFILEINFO aviInfo;
        AVISTREAMINFO aviStream;
        DWORD           dwNumStreams;
        DWORD           dwNumAudioStreams = 0L;
        LPLANGS         lpLangs;
        LPLANGS         lpLang;
        HANDLE          hLangs;

        DWORD           dw;

        AVIFileInit();

        // go open the file
        if (AVIFileOpen((PAVIFILE far *)&pFile, lpstrMovie, OF_READ, NULL) != 0)
                return FALSE;

        // get the file information
        AVIFileInfo(pFile, (LPAVIFILEINFO)&aviInfo, sizeof(aviInfo));
        dwNumStreams = aviInfo.dwStreams;               // grab the number of streams

        // loop through the streams and find the # of audio streams
        for (dw = 0L; dw < dwNumStreams; dw++){
                AVIFileGetStream(pFile, (PAVISTREAM far *)&pStream, 0, dw);
                AVIStreamInfo(pStream, (LPAVISTREAMINFO)&aviStream, sizeof(aviStream));
                if (aviStream.fccType == streamtypeAUDIO)
                        dwNumAudioStreams++;
                AVIStreamClose(pStream);

        }

        // we now know how many audio streams we are dealing with, we need to allocate
        // enough memory for the Langs array and then get all the audio stream information
        // again.
        hLangs = GlobalAlloc(GHND, (sizeof(LANGS) *  dwNumAudioStreams));
        if (hLangs == NULL)
                return FALSE;

        lpLangs = (LPLANGS)GlobalLock(hLangs);  // get the memory

        // loop through the audio streams and fill out the array
        for (dw = 0L, lpLang = lpLangs; dw < dwNumAudioStreams; dw++, lpLang++) {
                AVIFileGetStream(pFile, (PAVISTREAM far *)&pStream, streamtypeAUDIO, dw);
                AVIStreamInfo(pStream, (LPAVISTREAMINFO)&aviStream, sizeof(aviStream));
                if (aviStream.szName && *aviStream.szName != '\0')
                        lstrcpy((LPSTR)lpLang->achName, (LPSTR)aviStream.szName);
                else
                        wsprintf((LPSTR)lpLang->achName, "Audio Stream %lu",dw);
                AVIStreamClose(pStream);

        }

        // now build the menu for this
        buildLangMenu(hWnd, lpLangs, dwNumAudioStreams);

        // close up and deallocate resources
        AVIFileClose(pFile);
        AVIFileExit();

        GlobalUnlock(hLangs);
        GlobalFree(hLangs);

        return TRUE;
}

/*--------------------------------------------------------------+
| buildLangMenu - build up the language menu for the audio      |
|                                 streams available.            |
|                                                               |
| hwnd is the main application window handle                    |
| lang points to an array of LANGSTRUCT entries already filled  |
|      in by the caller.                                        |
+--------------------------------------------------------------*/
void buildLangMenu(HWND hwnd, LPLANGS lpLangs, DWORD dwLangs)
{
        UINT    i;
        HMENU   hMenu;
        LPLANGS lplang;
        UINT    uNumMenus;

        // go through menu chain and get the Audio Stream pop-up menu
        hMenu = GetMenu(hwnd);                  // get the menu bar
        hMenu = GetSubMenu(hMenu, 1);   // get the Audio Stream menu

        uNumMenus = GetMenuItemCount(hMenu);    // how many items are on this menu?
        if (uNumMenus > 1){
                // we've got a menu with items already, time to delete all of them
                // except for the first one (NONE).  NOTE: Item 0 == first item
                // be sure to delete in reverse order so you get them all.
                for ( --uNumMenus; uNumMenus; uNumMenus--) {
                        DeleteMenu(hMenu, uNumMenus, MF_BYPOSITION);
                }
        }

        // loop through the languages and add menus to the existing menu
        for (i=0, lplang = lpLangs; i<dwLangs; i++, lplang++){
                AppendMenu(hMenu, MF_ENABLED | MF_STRING, IDM_STREAM+i, lplang->achName);
        }

        // get default set up
        if (dwLangs)
                iCurLang = 1;           // use first audio stream
        else
                iCurLang = NOAUDIO;     // else none

        /* set up the checkmark initially */
        CheckMenuItem(hMenu, (iCurLang), MF_BYPOSITION | MF_CHECKED);
}

/*------------------------------------------------------------------+
| switchLang - switch audio stream playback                                                     |
|                                                                                                                                       |
| iLangStream == the audio stream to switch to (-1 == NONE)                     |
| Be sure to update iCurrLang global to be the current audio stream     |
| selected.                                                                                                                     |
|                                                                                                                                       |
+------------------------------------------------------------------*/
void switchLang(HWND hWnd, int iLangStream)
{
        HMENU                                   hMenu;
        char                                    achStrBuff[256];

        // if user just picked the same stream then just get out of here
        if (iCurLang == iLangStream)
                return;

        // go through menu chain and get the Audio Stream pop-up menu
        hMenu = GetMenu(hWnd);          // get the menu bar
        hMenu = GetSubMenu(hMenu, 1);   // get the Audio Stream menu

        // turn off the checkmark from the old item
        CheckMenuItem(hMenu, (iCurLang), MF_BYPOSITION | MF_UNCHECKED);

        // turn on the checkmark on the new item
        CheckMenuItem(hMenu, (iLangStream), MF_BYPOSITION | MF_CHECKED);

        if (iLangStream == NOAUDIO){
                // turn off all audio
                MCIWndSendString(hwndMovie, "setaudio off");
        } else {
                // turn on audio & the specific stream
                wsprintf(achStrBuff, "setaudio stream to %d", iLangStream);

                // send the command
                MCIWndSendString(hwndMovie, achStrBuff);

                if (iCurLang == NOAUDIO){
                        // audio was off, turn it on
                        MCIWndSendString(hwndMovie, "setaudio on");
                }
        }

        iCurLang = iLangStream;         // set the current stream
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(
        HWND    hwnd,
        UINT    msg,
        WPARAM  wParam,
        LPARAM  lParam)
{
        switch (msg) {
        case WM_COMMAND:
                EndDialog(hwnd, TRUE);
                return TRUE;
        case WM_INITDIALOG:
                return TRUE;
        }
        return FALSE;
}


README.TXT

The MOVPLAY sample demonstrates playing movies using MCI. Builds two versions,
one using mciSendCommand and another using mciSendString.


MOVPLAY.H

/*---------------------------------------------------------------------
|
| MovPlay.h - Movie player include file
|
+-------------------------------------------------------------------*/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

/* Menu items */
/* File menu */
#define IDM_OPEN   1
#define IDM_CLOSE  2
#define IDM_EXIT   3
#define IDM_ANOTHER 4

/* Movie menu */
#define IDM_PLAY   10
#define IDM_HOME   11
#define IDM_END    12
#define IDM_STEP   13
#define IDM_RSTEP  14
#define IDM_RPLAY  15

/* Help menu */
#define IDM_ABOUT  20

/* About Dialog */
#define IDD_ABOUT  101

//stringtable ID's
#defineIDS_APPNAME1
#defineIDS_APPERR2
#defineIDS_REGCLSFAILED3
#defineIDS_CRTWNDFAILED4
#define IDS_NOOPEN5
#define IDS_FILTERSTRING6


MOVPLAY1.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*--------------------------------------------------------------------
|
| MovPlay1.c - Sample Win app to play AVI movies using mciSendCommand
|
| Movie Functions supported:
|Play/Pause
|Home/End
|Step/ReverseStep
|
+--------------------------------------------------------------------*/

#define  STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <string.h>
#include <stdlib.h>
#include <direct.h>
#include <mmsystem.h>
#include <digitalv.h>
#include "movplay.h"

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

#define BUFSIZE 260
#define MOVPLAY_CLASS "movplay"
#define AVI_VIDEO "avivideo"

//----------------------------------------------------------------------------
//function declarations

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK    AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

static BOOL initAVI(void);
static void termAVI(void);
static HWND initApp(HINSTANCE, HINSTANCE, int);
static void menubarUpdate(HWND);
static void titlebarUpdate(HWND hWnd, LPSTR lpstrMovie);
static void positionMovie(HWND);
static void fileCloseMovie(HWND);
static void fileOpenMovie(HWND);
static void playMovie(HWND, int);
static void seekMovie(HWND, int);
static void stepMovie(HWND, int);
static BOOL FormatFilterString(UINT, LPSTR);
static void MovPlay_OnInitMenuPopup(HWND, HMENU, int, BOOL);
static void MovPlay_OnCommand(HWND, int, HWND, UINT);
static void MovPlay_OnSize(HWND, UINT, int, int);
static void MovPlay_OnDestroy(HWND);

//----------------------------------------------------------------------------
//AVI stuff to keep around

HINSTANCE   ghInst;
MCIDEVICEID gwMCIDeviceID = 0;/* MCI Device ID for the AVI file */
HWND        ghwndMovie;/* window handle of the movie */
BOOL        gfPlaying = FALSE;/* Play flag: TRUE == playing, FALSE == paused */
BOOL        gfMovieOpen = FALSE;/* Open flag: TRUE == movie open, FALSE = none */
HANDLE      ghAccel = NULL;/* accelerator table */
HMENU       ghMenuBar = NULL;/* menu bar handle */
char        gszBuffer[BUFSIZE];

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

/*--------------------------------------------------------------+
| initAVI - initialize avi libraries|
||
+--------------------------------------------------------------*/
static BOOL initAVI(
void)
{
MCI_DGV_OPEN_PARMSmciOpen;

/* set up the open parameters */
mciOpen.dwCallback = 0L;
mciOpen.wDeviceID = 0;
mciOpen.lpstrDeviceType = AVI_VIDEO;
mciOpen.lpstrElementName = NULL;
mciOpen.lpstrAlias = NULL;
mciOpen.dwStyle = 0;
mciOpen.hWndParent = NULL;

/* try to open the driver */
return (mciSendCommand(0, MCI_OPEN, (DWORD)(MCI_OPEN_TYPE),
                          (DWORD)(LPMCI_DGV_OPEN_PARMS)&mciOpen) == 0);
}

/*--------------------------------------------------------------+
| termAVI - Closes the opened AVI file and the opened device    |
|           type.                                               |
|                                                               |
+--------------------------------------------------------------*/
void termAVI(void)
{
MCIDEVICEID        mciID;
MCI_GENERIC_PARMS  mciClose;
//
// Get the device ID for the opened device type and then close
// the device type.
mciID = mciGetDeviceID(AVI_VIDEO);
mciSendCommand(mciID, MCI_CLOSE, 0L,
                   (DWORD)(LPMCI_GENERIC_PARMS)&mciClose);
}

/*--------------------------------------------------------------+
| initApp - initialize the app overall.|
||
| Returns the Window handle for the app on success, NULL if|
| there is a failure.|
||
+--------------------------------------------------------------*/
HWND initApp(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
int nCmdShow)
{
HWNDhWnd;/* window handle to return */
        char szAppName[BUFSIZE];

        ghInst = hInstance;

if (!hPrevInstance){
WNDCLASS    wndclass;

wndclass.style= CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc= WndProc;
wndclass.cbClsExtra= 0;
wndclass.cbWndExtra= 0;
wndclass.hInstance= hInstance;
wndclass.hIcon= LoadIcon (hInstance, "AppIcon");
wndclass.hCursor= LoadCursor (NULL, IDC_ARROW);
wndclass.hbrBackground= (HBRUSH) (COLOR_WINDOW + 1);
wndclass.lpszMenuName= MOVPLAY_CLASS;
wndclass.lpszClassName= MOVPLAY_CLASS;

if (!RegisterClass(&wndclass)){
        LoadString( ghInst, IDS_APPERR, szAppName, BUFSIZE );
LoadString( ghInst, IDS_REGCLSFAILED, gszBuffer, BUFSIZE );
MessageBox(NULL, gszBuffer, szAppName, MB_OK);
return NULL;
}
}

        LoadString( ghInst, IDS_APPNAME, szAppName, BUFSIZE );

/* create the main window for the app */
hWnd = CreateWindow(MOVPLAY_CLASS, szAppName, WS_OVERLAPPEDWINDOW |
WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

if (hWnd == NULL){
        LoadString( ghInst, IDS_APPERR, szAppName, BUFSIZE );
LoadString( ghInst, IDS_CRTWNDFAILED, gszBuffer, BUFSIZE );
MessageBox(NULL, gszBuffer, szAppName, MB_OK);
return NULL;
}

ghMenuBar = GetMenu(hWnd);/* get the menu bar handle */
menubarUpdate(hWnd);/* update menu bar to disable Movie menu */

/* Show the main window */
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

/* load the accelerator table */
ghAccel = LoadAccelerators(hInstance, MOVPLAY_CLASS);

return hWnd;
}

/*--------------------------------------------------------------+
| WinMain - main routine.|
||
+--------------------------------------------------------------*/
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdParam,
int nCmdShow)
{
HWND        hWnd;
MSG         msg;

if (!initAVI())
return 0;

if ((hWnd = initApp(hInstance, hPrevInstance, nCmdShow)) == NULL)
return 0;/* died initializing, bail out */

/* main message loop, be sure to handle accelerators */
while (GetMessage(&msg, NULL, 0, 0)){
if (!TranslateAccelerator(hWnd, ghAccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}

/*--------------------------------------------------------------+
| MovPlay_OnInitMenuPopup - Message handler for WM_INITMENUPOPUP|
||
+--------------------------------------------------------------*/
static void MovPlay_OnInitMenuPopup(
HWND hwnd,
HMENU hMenu,
int item,
BOOL fSystemMenu)
{
UINT uEnable;
/* be sure this isn't the system menu */
if (fSystemMenu)
return;

switch (item){
case 0: /* file menu */
/* turn on/off CLOSE & PLAY */
if(gfMovieOpen)
uEnable = MF_ENABLED|MF_BYCOMMAND;
else
uEnable = MF_GRAYED|MF_BYCOMMAND;
EnableMenuItem(hMenu, IDM_CLOSE, uEnable);
break;

case 1:/* Movie menu */
/* check or uncheck the PLAY menu item */
if (gfPlaying)
uEnable = MF_CHECKED|MF_BYCOMMAND;
else
uEnable = MF_UNCHECKED|MF_BYCOMMAND;
CheckMenuItem(hMenu, IDM_PLAY, uEnable);
break;
} /* switch */
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnCommand - Message handler for WM_COMMAND|
||
+--------------------------------------------------------------*/
static void MovPlay_OnCommand(
HWND hWnd,
int id,
HWND hwndCtl,
UINT codeNotify)
{
/* handle the menu commands */
switch (id) {
                case IDM_ABOUT:
                        DialogBox(ghInst, MAKEINTRESOURCE(IDD_ABOUT), hWnd, AboutDlgProc);
                        break;
/* File Menu */
case IDM_OPEN:
fileOpenMovie(hWnd);
break;
case IDM_CLOSE:
fileCloseMovie(hWnd);
break;
case IDM_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0L);
break;
/* Movie Menu - note some of these are by */
/* keyboard only, especially the REVERSE  */
/* commands.  */
case IDM_PLAY:
case IDM_RPLAY:
playMovie(hWnd, id);
break;
case IDM_HOME:
case IDM_END:
seekMovie(hWnd, id);
break;
case IDM_STEP:
case IDM_RSTEP:
stepMovie(hWnd,id);
break;
}
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnSize - Message handler for WM_SIZE|
||
+--------------------------------------------------------------*/
static void MovPlay_OnSize(
HWND hWnd,
UINT state,
int cx,
int cy)
{
positionMovie(hWnd);/* re-center the movie */
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnDestroy - Message handler for WM_DESTROY|
||
+--------------------------------------------------------------*/
static void MovPlay_OnDestroy(
HWND hWnd)
{
if (gfMovieOpen)
fileCloseMovie(hWnd);
termAVI();
PostQuitMessage(0);
return;
}
/*--------------------------------------------------------------+
| WndProc - window proc for the app|
||
+--------------------------------------------------------------*/
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message){
case WM_INITMENUPOPUP:
return HANDLE_WM_INITMENUPOPUP(hWnd, wParam, lParam, MovPlay_OnInitMenuPopup);
break;

case WM_COMMAND:
return HANDLE_WM_COMMAND(hWnd, wParam, lParam, MovPlay_OnCommand);
break;

case WM_SIZE:
return HANDLE_WM_SIZE(hWnd, wParam, lParam, MovPlay_OnSize);
break;

case WM_DESTROY:
return HANDLE_WM_DESTROY(hWnd, wParam, lParam, MovPlay_OnDestroy);
break;

        // We need to realize the AVI's palette
case WM_QUERYNEWPALETTE:
case WM_PALETTECHANGED:
case WM_ACTIVATE:
    {
    MCI_GENERIC_PARMSmciGeneric;

    // buggy drivers crash if we pass a null parms address
            mciSendCommand(gwMCIDeviceID, MCI_REALIZE,
                MCI_ANIM_REALIZE_NORM, (DWORD)(LPVOID)&mciGeneric);
    break;
    }

case MM_MCINOTIFY:
/* This is where we check the status of an AVI*/
/* movie that might have been playing.  We do*/
/* the play with MCI_NOTIFY on so we should get*/
/* a MCI_NOTIFY_SUCCESSFUL if the play*/
/* completes on it's own.*/
switch(wParam){
case MCI_NOTIFY_SUCCESSFUL:
/* the play finished, let's rewind */
/* and clear our flag.   */
gfPlaying = FALSE;
mciSendCommand(gwMCIDeviceID, MCI_SEEK,
MCI_SEEK_TO_START,
(DWORD)(LPVOID)NULL);
return 0;
}
} /* switch */
return DefWindowProc(hWnd, message, wParam, lParam);
}

/*--------------------------------------------------------------+
| menubarUpdate - update the menu bar based on the <gfMovieOpen> |
|  flag value.  This will turn on/off the|
|  Movie menu.|
||
+--------------------------------------------------------------*/
static void menubarUpdate(
HWND hWnd)
{
WORD w;

if (gfMovieOpen){
w = MF_ENABLED|MF_BYPOSITION;
} else {
w = MF_GRAYED|MF_BYPOSITION;
}
EnableMenuItem(ghMenuBar, 1, w);/* change the Movie menu (#1) */
DrawMenuBar(hWnd);/* re-draw the menu bar */
}

/*--------------------------------------------------------------+
| titlebarUpdate - update the title bar to include the name|
|   of the movie playing.|
||
+--------------------------------------------------------------*/
static void titlebarUpdate(
HWND hWnd,
LPSTR lpstrMovie)
{
char szNewTitle[2*BUFSIZE];// space for the title
char szAppName[BUFSIZE];

LoadString( ghInst, IDS_APPNAME, szAppName, BUFSIZE );

if (lpstrMovie != NULL)
wsprintf(szNewTitle,"%s - %s", (LPSTR)szAppName,(LPSTR)lpstrMovie);
else
lstrcpy(szNewTitle, szAppName);
SetWindowText(hWnd, szNewTitle);
}

/*--------------------------------------------------------------+
| positionMovie - sets the movie rectange <rcMovie> to be|
|centered within the app's window.|
||
+--------------------------------------------------------------*/
static VOID positionMovie(
HWND hWnd)
{
RECT rcMovie;/* the rect where the movie is positioned      */
/* for QT/W this is the movie rect, for AVI    */
/* this is the location of the playback window */
RECTrcClient, rcMovieBounds;
MCI_DGV_RECT_PARMSmciRect;

/* if there is no movie yet then just get out of here */
if (!gfMovieOpen)
return;

GetClientRect(hWnd, &rcClient);/* get the parent windows rect */

/* get the original size of the movie */
mciSendCommand(gwMCIDeviceID, MCI_WHERE,
                  (DWORD)(MCI_DGV_WHERE_SOURCE),
                  (DWORD)(LPMCI_DGV_RECT_PARMS)&mciRect);
CopyRect( &rcMovieBounds, &mciRect.rc );/* get it in the movie bounds rect */

rcMovie.left = (rcClient.right/2) - (rcMovieBounds.right / 2);
rcMovie.top = (rcClient.bottom/2) - (rcMovieBounds.bottom / 2);
rcMovie.right = rcMovie.left + rcMovieBounds.right;
rcMovie.bottom = rcMovie.top + rcMovieBounds.bottom;

/* reposition the playback (child) window */
MoveWindow(ghwndMovie, rcMovie.left, rcMovie.top,
rcMovieBounds.right, rcMovieBounds.bottom, TRUE);
}

/*--------------------------------------------------------------+
| fileCloseMovie - close the movie and anything associated|
|   with it.|
||
| This function clears the <gfPlaying> and <gfMovieOpen> flags|
||
+--------------------------------------------------------------*/
static void fileCloseMovie(HWND hWnd)
{
MCI_GENERIC_PARMS  mciGeneric;

mciSendCommand(gwMCIDeviceID, MCI_CLOSE, 0L,
                 (DWORD)(LPMCI_GENERIC_PARMS)&mciGeneric);

gfPlaying = FALSE;// can't be playing any longer
gfMovieOpen = FALSE;// no more movies open

titlebarUpdate(hWnd, NULL);// title bar back to plain
menubarUpdate(hWnd);// update menu bar

/* cause a total repaint to occur */
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
}

/*--------------------------------------------------------------+
| fileOpenMovie - open an AVI movie. Use CommDlg open box to|
|        open and then handle the initialization to|
|show the movie and position it properly.  Keep|
|the movie paused when opened.|
||
|Sets <gfMovieOpened> on success.|
+--------------------------------------------------------------*/
static void fileOpenMovie(
HWND hWnd)
{
OPENFILENAME ofn;

static char szFile [BUFSIZE];
static char szFileTitle [BUFSIZE];
static char szFilter [BUFSIZE];

FormatFilterString( IDS_FILTERSTRING, szFilter );

/* use the OpenFile dialog to get the filename */
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = szFilter;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

/* use CommDlg to get our filename */
if (GetOpenFileName(&ofn)){
MCI_DGV_OPEN_PARMSmciOpen;
MCI_DGV_WINDOW_PARMSmciWindow;
MCI_DGV_STATUS_PARMSmciStatus;

/* we got a filename, now close any old movie and open */
/* the new one.*/
if (gfMovieOpen)
fileCloseMovie(hWnd);

/* we have a .AVI movie to open, use MCI */
/* set up the open parameters */
mciOpen.dwCallback = 0L;
mciOpen.wDeviceID = 0;
mciOpen.lpstrDeviceType = NULL;
mciOpen.lpstrElementName = ofn.lpstrFile;
mciOpen.lpstrAlias = NULL;
mciOpen.dwStyle = WS_CHILD;
mciOpen.hWndParent = hWnd;

/* try to open the file */
if (mciSendCommand(0, MCI_OPEN,
(DWORD)(MCI_OPEN_ELEMENT|MCI_DGV_OPEN_PARENT|MCI_DGV_OPEN_WS),
(DWORD)(LPMCI_DGV_OPEN_PARMS)&mciOpen) == 0){

/* we opened the file o.k., now set up to */
/* play it.   */
gwMCIDeviceID = mciOpen.wDeviceID;/* save ID */
gfMovieOpen = TRUE;/* a movie was opened */

/* show the playback window */
mciWindow.dwCallback = 0L;
mciWindow.hWnd = NULL;
mciWindow.nCmdShow = SW_SHOW;
mciWindow.lpstrText = (LPSTR)NULL;
mciSendCommand(gwMCIDeviceID, MCI_WINDOW,
MCI_DGV_WINDOW_STATE,
(DWORD)(LPMCI_DGV_WINDOW_PARMS)&mciWindow);

/* get the window handle */
mciStatus.dwItem = MCI_DGV_STATUS_HWND;
mciSendCommand(gwMCIDeviceID,
MCI_STATUS, MCI_STATUS_ITEM,
(DWORD)(LPMCI_STATUS_PARMS)&mciStatus);
ghwndMovie = (HWND)mciStatus.dwReturn;

/* now get the movie centered */
positionMovie(hWnd);
} else {
/* generic error for open */
LoadString( ghInst, IDS_NOOPEN, gszBuffer, BUFSIZE );
MessageBox(hWnd, gszBuffer, NULL, MB_ICONEXCLAMATION|MB_OK);
gfMovieOpen = FALSE;
}
}
/* update menu and title bars */
if (gfMovieOpen)
titlebarUpdate(hWnd, ofn.lpstrFileTitle);
else
titlebarUpdate(hWnd, NULL);

menubarUpdate(hWnd);

/* cause an update to occur */
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
}

/*--------------------------------------------------------------+
| playMovie - play/pause the movie depending on the state|
|of the <gfPlaying> flag.|
||
| This function sets the <gfPlaying> flag appropriately when done|
||
+--------------------------------------------------------------*/
static void playMovie(
HWND hWnd,
int nDirection)
{
gfPlaying = !gfPlaying;/* swap the play flag */
if(!nDirection)
gfPlaying = FALSE;/* wDirection == NULL means PAUSE */

/* play/pause the AVI movie */
if (gfPlaying){
DWORDdwFlags;
MCI_DGV_PLAY_PARMSmciPlay;

/* init to play all */
mciPlay.dwCallback = MAKELONG(hWnd,0);
mciPlay.dwFrom = mciPlay.dwTo = 0;
dwFlags = MCI_NOTIFY;
if (nDirection == IDM_RPLAY)
dwFlags |= MCI_DGV_PLAY_REVERSE;

mciSendCommand(gwMCIDeviceID, MCI_PLAY, dwFlags,
               (DWORD)(LPMCI_DGV_PLAY_PARMS)&mciPlay);
} else {
MCI_DGV_PAUSE_PARMSmciPause;

/* tell it to pause */
mciSendCommand(gwMCIDeviceID,MCI_PAUSE,0L,
                     (DWORD)(LPMCI_DGV_PAUSE_PARMS)&mciPause);
}
}

/*--------------------------------------------------------------+
| seekMovie - seek in the movie depending on the wAction.|
|      Possible actions are IDM_HOME (start of movie) or|
|      IDM_END (end of movie)|
||
|      Always stop the play before seeking.|
||
+--------------------------------------------------------------*/
static void seekMovie(
HWND hWnd,
int nAction)
{
/* first stop the movie from playing if it is playing */
if (gfPlaying){
playMovie(hWnd, 0);
}
if (nAction == IDM_HOME){
/* home the movie */
mciSendCommand(gwMCIDeviceID, MCI_SEEK, MCI_SEEK_TO_START,
(DWORD)(LPVOID)NULL);

} else if (nAction == IDM_END){
/* go to the end of the movie */
mciSendCommand(gwMCIDeviceID, MCI_SEEK, MCI_SEEK_TO_END,
(DWORD)(LPVOID)NULL);
}
}

/*--------------------------------------------------------------+
| stepMovie - step forward or reverse in the movie.  wDirection|
|can be IDM_STEP (forward) or IDM_RSTEP (reverse)|
||
|Again, stop the play if one is in progress.|
||
+--------------------------------------------------------------*/
static void stepMovie(
HWND hWnd,
int nDirection)
{
MCI_DGV_STEP_PARMSmciStep;

if (gfPlaying)
playMovie(hWnd, 0);  /* turn off the movie */

mciStep.dwFrames = 1L;
if (nDirection == IDM_STEP)
mciSendCommand(gwMCIDeviceID, MCI_STEP, MCI_DGV_STEP_FRAMES,
(DWORD)(LPMCI_DGV_STEP_PARMS)&mciStep);
else
mciSendCommand(gwMCIDeviceID, MCI_STEP,
MCI_DGV_STEP_FRAMES|MCI_DGV_STEP_REVERSE,
(DWORD)(LPMCI_DGV_STEP_PARMS)&mciStep);
}
/*--------------------------------------------------------------+
| FormatFilterString |
||
+--------------------------------------------------------------*/
static BOOL FormatFilterString(
UINT uID,
LPSTR lpszString )
{
int nLength, nCtr = 0;
char chWildCard;

*lpszString = 0;

nLength = LoadString( ghInst, uID, lpszString, BUFSIZE );

chWildCard = lpszString[nLength-1];

while( lpszString[nCtr] ) {
if( lpszString[nCtr] == chWildCard )
lpszString[nCtr] = 0;
nCtr++;
}

return TRUE;
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(
HWNDhwnd,
UINTmsg,
WPARAMwParam,
LPARAMlParam)
{
switch (msg) {
case WM_COMMAND:
EndDialog(hwnd, TRUE);
return TRUE;
case WM_INITDIALOG:
return TRUE;
}
return FALSE;
}


MOVPLAY2.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*--------------------------------------------------------------------
|
| MovPlay2.c - Sample Win app to play AVI movies using mciSendString
|
| Movie Functions supported:
|Play/Pause
|Home/End
|Step/ReverseStep
|
+--------------------------------------------------------------------*/

#define  STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <direct.h>
#include <mmsystem.h>
#include <digitalv.h>
#include "movplay.h"

//----------------------------------------------------------------------------
#define BUFSIZE 260
#define MOVPLAY_CLASS "movplay"
#define AVI_VIDEO "avivideo"
#define OPEN_AVI_VIDEO "open avivideo"
#define CLOSE_AVI_VIDEO "close avivideo"
#define MOV_REALIZE "realize mov"
#define MOV_SEEK_TO_START "seek mov to start"
#define MOV_SEEK_TO_END "seek mov to end"
#define MOV_CLOSE "close mov"
#define MOV_SOURCE "where mov source"
#define MOV_OPEN_FORMAT "open %s alias mov style child parent %d"
#define MOV_SHOW "window mov state show"
#define MOV_HANDLE "status mov window handle"
#define MOV_REVERSE "play mov reverse notify"
#define MOV_FORWARD "play mov notify"
#define MOV_PAUSE "pause mov"
#define MOV_FORWARD_STEP "step mov by 1"
#define MOV_REVERSE_STEP "step mov reverse by 1"
//----------------------------------------------------------------------------
//function declarations

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK    AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam,LPARAM lParam);

static BOOL initAVI(void);
static void termAVI(void);
static HWND initApp(HINSTANCE, HINSTANCE, int);
static void menubarUpdate(HWND);
static void titlebarUpdate(HWND hWnd, LPSTR lpstrMovie);
static void positionMovie(HWND);
static void fileCloseMovie(HWND);
static void fileOpenMovie(HWND);
static void playMovie(HWND, int);
static void seekMovie(HWND, int);
static void stepMovie(HWND, int);
static BOOL FormatFilterString(UINT, LPSTR);
static void MovPlay_OnInitMenuPopup(HWND, HMENU, int, BOOL);
static void MovPlay_OnCommand(HWND, int, HWND, UINT);
static void MovPlay_OnSize(HWND, UINT, int, int);
static void MovPlay_OnDestroy(HWND);

//----------------------------------------------------------------------------
//AVI stuff to keep around

HINSTANCE ghInst;
WORD gwMCIDeviceID = 0;/* MCI Device ID for the AVI file */
HWND ghwndMovie;/* window handle of the movie */
BOOL gfPlaying = FALSE;/* Play flag: TRUE == playing, FALSE == paused */
BOOL gfMovieOpen = FALSE;/* Open flag: TRUE == movie open, FALSE = none */
HANDLE ghAccel = NULL;/* accelerator table */
HMENU ghMenuBar = NULL;/* menu bar handle */
char gszBuffer[BUFSIZE];

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

/*--------------------------------------------------------------+
| initAVI - initialize avi libraries|
||
+--------------------------------------------------------------*/
static BOOL initAVI(
void)
{
return mciSendString(OPEN_AVI_VIDEO, NULL, 0, NULL) == 0;
}

/*--------------------------------------------------------------+
| termAVI - Closes the opened AVI file and the opened device    |
|           type.                                               |
|                                                               |
+--------------------------------------------------------------*/
void termAVI(
void)
{
mciSendString(CLOSE_AVI_VIDEO, NULL, 0, NULL);
}
/*--------------------------------------------------------------+
| initApp - initialize the app overall.|
||
| Returns the Window handle for the app on success, NULL if|
| there is a failure.|
||
+--------------------------------------------------------------*/
HWND initApp(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
int nCmdShow)
{
HWNDhWnd;/* window handle to return */
        char szAppName[BUFSIZE];

        ghInst = hInstance;

if (!hPrevInstance){
WNDCLASS    wndclass;

wndclass.style= CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc= WndProc;
wndclass.cbClsExtra= 0;
wndclass.cbWndExtra= 0;
wndclass.hInstance= hInstance;
wndclass.hIcon= LoadIcon (hInstance, "AppIcon");
wndclass.hCursor= LoadCursor (NULL, IDC_ARROW);
wndclass.hbrBackground= (HBRUSH) (COLOR_WINDOW + 1);
wndclass.lpszMenuName= MOVPLAY_CLASS;
wndclass.lpszClassName= MOVPLAY_CLASS;

if (!RegisterClass(&wndclass)){
        LoadString( ghInst, IDS_APPERR, szAppName, BUFSIZE );
LoadString( ghInst, IDS_REGCLSFAILED, gszBuffer, BUFSIZE );
MessageBox(NULL, gszBuffer, szAppName, MB_OK);
return NULL;
}
}

        LoadString( ghInst, IDS_APPNAME, szAppName, BUFSIZE );

/* create the main window for the app */
hWnd = CreateWindow(MOVPLAY_CLASS, szAppName, WS_OVERLAPPEDWINDOW |
WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

if (hWnd == NULL){
        LoadString( ghInst, IDS_APPERR, szAppName, BUFSIZE );
LoadString( ghInst, IDS_CRTWNDFAILED, gszBuffer, BUFSIZE );
MessageBox(NULL, gszBuffer, szAppName, MB_OK);
return NULL;
}

ghMenuBar = GetMenu(hWnd);/* get the menu bar handle */
menubarUpdate(hWnd);/* update menu bar to disable Movie menu */

/* Show the main window */
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

/* load the accelerator table */
ghAccel = LoadAccelerators(hInstance, MOVPLAY_CLASS);

return hWnd;
}

/*--------------------------------------------------------------+
| WinMain - main routine.|
||
+--------------------------------------------------------------*/
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdParam,
int nCmdShow)
{
HWND        hWnd;
MSG         msg;

if (!initAVI())
return 0;

if ((hWnd = initApp(hInstance, hPrevInstance, nCmdShow)) == NULL)
return 0;/* died initializing, bail out */

/* main message loop, be sure to handle accelerators */
while (GetMessage(&msg, NULL, 0, 0)){
if (!TranslateAccelerator(hWnd, ghAccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}

/*--------------------------------------------------------------+
| MovPlay_OnInitMenuPopup - Message handler for WM_INITMENUPOPUP|
||
+--------------------------------------------------------------*/
static void MovPlay_OnInitMenuPopup(
HWND hwnd,
HMENU hMenu,
int item,
BOOL fSystemMenu)
{
UINT uEnable;
/* be sure this isn't the system menu */
if (fSystemMenu)
return;

switch (item){
case 0: /* file menu */
/* turn on/off CLOSE & PLAY */
if(gfMovieOpen)
uEnable = MF_ENABLED|MF_BYCOMMAND;
else
uEnable = MF_GRAYED|MF_BYCOMMAND;
EnableMenuItem(hMenu, IDM_CLOSE, uEnable);
break;

case 1:/* Movie menu */
/* check or uncheck the PLAY menu item */
if (gfPlaying)
uEnable = MF_CHECKED|MF_BYCOMMAND;
else
uEnable = MF_UNCHECKED|MF_BYCOMMAND;
CheckMenuItem(hMenu, IDM_PLAY, uEnable);
break;
} /* switch */
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnCommand - Message handler for WM_COMMAND|
|                                                               |
+--------------------------------------------------------------*/
static void MovPlay_OnCommand(
HWND hWnd,
int id,
HWND hwndCtl,
UINT codeNotify)
{
/* handle the menu commands */
switch (id) {
                case IDM_ABOUT:
                        DialogBox(ghInst, MAKEINTRESOURCE(IDD_ABOUT), hWnd, AboutDlgProc);
                        break;
/* File Menu */
case IDM_OPEN:
fileOpenMovie(hWnd);
break;
case IDM_CLOSE:
fileCloseMovie(hWnd);
break;
case IDM_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0L);
break;
/* Movie Menu - note some of these are by */
/* keyboard only, especially the REVERSE  */
/* commands.  */
case IDM_PLAY:
case IDM_RPLAY:
playMovie(hWnd, id);
break;
case IDM_HOME:
case IDM_END:
seekMovie(hWnd, id);
break;
case IDM_STEP:
case IDM_RSTEP:
stepMovie(hWnd,id);
break;
}
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnSize - Message handler for WM_SIZE|
||
+--------------------------------------------------------------*/
static void MovPlay_OnSize(
HWND hWnd,
UINT state,
int cx,
int cy)
{
positionMovie(hWnd);/* re-center the movie */
return;
}
/*--------------------------------------------------------------+
| MovPlay_OnDestroy - Message handler for WM_DESTROY|
||
+--------------------------------------------------------------*/
static void MovPlay_OnDestroy(
HWND hWnd)
{
if (gfMovieOpen)
fileCloseMovie(hWnd);
termAVI();
PostQuitMessage(0);
return;
}
/*--------------------------------------------------------------+
| WndProc - window proc for the app|
||
+--------------------------------------------------------------*/
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message){
case WM_INITMENUPOPUP:
return HANDLE_WM_INITMENUPOPUP(hWnd, wParam, lParam, MovPlay_OnInitMenuPopup);
break;

case WM_COMMAND:
return HANDLE_WM_COMMAND(hWnd, wParam, lParam, MovPlay_OnCommand);
break;

case WM_SIZE:
return HANDLE_WM_SIZE(hWnd, wParam, lParam, MovPlay_OnSize);
break;

case WM_DESTROY:
return HANDLE_WM_DESTROY(hWnd, wParam, lParam, MovPlay_OnDestroy);
break;

        // We need to realize the AVI's palette
case WM_QUERYNEWPALETTE:
case WM_PALETTECHANGED:
case WM_ACTIVATE:
            mciSendString(MOV_REALIZE, NULL, 0, NULL);
    break;

case MM_MCINOTIFY:
/* This is where we check the status of an AVI*/
/* movie that might have been playing.  We do*/
/* the play with MCI_NOTIFY on so we should get*/
/* a MCI_NOTIFY_SUCCESSFUL if the play*/
/* completes on it's own.*/
switch(wParam){
case MCI_NOTIFY_SUCCESSFUL:
/* the play finished, let's rewind */
/* and clear our flag.   */
gfPlaying = FALSE;
mciSendString(MOV_SEEK_TO_START, NULL, 0, NULL);
return 0;
}
} /* switch */
return DefWindowProc(hWnd, message, wParam, lParam);
}

/*--------------------------------------------------------------+
| menubarUpdate - update the menu bar based on the <gfMovieOpen> |
|  flag value.  This will turn on/off the|
|  Movie menu.|
||
+--------------------------------------------------------------*/
static void menubarUpdate(
HWND hWnd)
{
WORD w;

if (gfMovieOpen){
w = MF_ENABLED|MF_BYPOSITION;
} else {
w = MF_GRAYED|MF_BYPOSITION;
}
EnableMenuItem(ghMenuBar, 1, w);/* change the Movie menu (#1) */
DrawMenuBar(hWnd);/* re-draw the menu bar */
}

/*--------------------------------------------------------------+
| titlebarUpdate - update the title bar to include the name|
|   of the movie playing.|
||
+--------------------------------------------------------------*/
static void titlebarUpdate(
HWND hWnd,
LPSTR lpstrMovie)
{
char szNewTitle[2*BUFSIZE];// space for the title
char szAppName[BUFSIZE];

LoadString( ghInst, IDS_APPNAME, szAppName, BUFSIZE );

if (lpstrMovie != NULL)
wsprintf(szNewTitle,"%s - %s", (LPSTR)szAppName,(LPSTR)lpstrMovie);
else
lstrcpy(szNewTitle, szAppName);
SetWindowText(hWnd, szNewTitle);
}

/*--------------------------------------------------------------+
| positionMovie - sets the movie rectange <rcMovie> to be|
|centered within the app's window.|
||
+--------------------------------------------------------------*/
static VOID positionMovie(
HWND hWnd)
{
RECT rcMovie;
RECT rcClient;
RECT rcMovieBounds;
char achRect[128];
char *p;

/* if there is no movie yet then just get out of here */
if (!gfMovieOpen)
return;

GetClientRect(hWnd, &rcClient);/* get the parent windows rect */

/* get the original size of the movie */
mciSendString(MOV_SOURCE, achRect, sizeof(achRect), NULL);

SetRectEmpty(&rcMovieBounds);// zero out movie rect
p = achRect;// point to rectangle string returned by where command
while (*p == ' ') p++;// skip over starting spaces
while (*p != ' ') p++;// skip over the x (which is 0)
while (*p == ' ') p++;// skip over spaces between x and y
while (*p != ' ') p++;// skip over the y (which is 0)
while (*p == ' ') p++;// skip over the spaces between y and width

/* now find the width */
for (; *p >= '0' && *p <= '9'; p++)
rcMovieBounds.right = (10 * rcMovieBounds.right) + (*p - '0');
while (*p == ' ') p++;// skip spaces between width and height

/* now find the height */
for (; *p >= '0' && *p <= '9'; p++)
rcMovieBounds.bottom = (10 * rcMovieBounds.bottom) + (*p - '0');

/* figure out where to position the window at */
rcMovie.left = (rcClient.right/2) - (rcMovieBounds.right / 2);
rcMovie.top = (rcClient.bottom/2) - (rcMovieBounds.bottom / 2);
rcMovie.right = rcMovie.left + rcMovieBounds.right;
rcMovie.bottom = rcMovie.top + rcMovieBounds.bottom;

/* reposition the playback (child) window */
MoveWindow(ghwndMovie, rcMovie.left, rcMovie.top,
rcMovieBounds.right, rcMovieBounds.bottom, TRUE);
}

/*--------------------------------------------------------------+
| fileCloseMovie - close the movie and anything associated|
|   with it.|
||
| This function clears the <gfPlaying> and <gfMovieOpen> flags|
||
+--------------------------------------------------------------*/
static void fileCloseMovie(HWND hWnd)
{
mciSendString(MOV_CLOSE, NULL, 0, NULL);

gfPlaying = FALSE;// can't be playing any longer
gfMovieOpen = FALSE;// no more movies open

titlebarUpdate(hWnd, NULL);// title bar back to plain
menubarUpdate(hWnd);// update menu bar

/* cause a total repaint to occur */
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
}

/*--------------------------------------------------------------+
| fileOpenMovie - open an AVI movie. Use CommDlg open box to|
|        open and then handle the initialization to|
|show the movie and position it properly.  Keep|
|the movie paused when opened.|
||
|Sets <gfMovieOpened> on success.|
+--------------------------------------------------------------*/
static void fileOpenMovie(
HWND hWnd)
{
OPENFILENAME ofn;

static char szFile [BUFSIZE];
static char szFileTitle [BUFSIZE];
static int  nLastFilter = 1;  /* keep last file-type opened */
static char szFilter [BUFSIZE];

FormatFilterString( IDS_FILTERSTRING, szFilter );

/* use the OpenFile dialog to get the filename */
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = szFilter;
ofn.nFilterIndex = nLastFilter;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle;
ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

/* use CommDlg to get our filename */
if (GetOpenFileName(&ofn)){
char szCommand[BUFSIZE];
DWORD  dw;

/* we got a filename, now close any old movie and open */
/* the new one.*/
if (gfMovieOpen)
fileCloseMovie(hWnd);

/* we have a .AVI movie to open, use MCI */
/*
| build up the string -
| open alias mov parent HWND FILENAME
| to pass to mciSendString
*/
wsprintf(szCommand,MOV_OPEN_FORMAT, (LPSTR)ofn.lpstrFile, (int)hWnd);

/* try to open the file */
if (mciSendString(szCommand, NULL, 0, NULL) == 0){
gfMovieOpen = TRUE;
/* we opened the file o.k., now set up to */
/* play it.   */
mciSendString(MOV_SHOW, NULL, 0, NULL);
/* get the window handle */
if ((dw = mciSendString(MOV_HANDLE, szCommand, sizeof(szCommand),
NULL)) == 0L)
ghwndMovie = (HWND)atoi(szCommand);
else {
mciGetErrorString(dw, szCommand, sizeof(szCommand));
MessageBox(hWnd, szCommand, NULL, MB_ICONEXCLAMATION|MB_OK);
}
/* now get the movie centered */
positionMovie(hWnd);
} else {
/* generic error for open */
LoadString( ghInst, IDS_NOOPEN, gszBuffer, BUFSIZE );
MessageBox(hWnd, gszBuffer, NULL, MB_ICONEXCLAMATION|MB_OK);
gfMovieOpen = FALSE;
}
}
/* update menu and title bars */
if (gfMovieOpen)
titlebarUpdate(hWnd, (LPSTR)ofn.lpstrFileTitle);
else
titlebarUpdate(hWnd, NULL);
menubarUpdate(hWnd);

/* cause an update to occur */
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
}

/*--------------------------------------------------------------+
| playMovie - play/pause the movie depending on the state|
|of the <gfPlaying> flag.|
||
| This function sets the <gfPlaying> flag appropriately when done|
||
+--------------------------------------------------------------*/
static void playMovie(
HWND hWnd,
int nDirection)
{
gfPlaying = !gfPlaying;/* swap the play flag */
if(!nDirection)
gfPlaying = FALSE;/* nDirection == NULL means PAUSE */

/* play/pause the AVI movie */
if (gfPlaying){
if (nDirection == IDM_RPLAY){
mciSendString(MOV_REVERSE, NULL, 0, hWnd);
} else {
mciSendString(MOV_FORWARD, NULL, 0, hWnd);
}
} else {
/* tell it to pause */
mciSendString(MOV_PAUSE, NULL, 0, NULL);
}
}

/*--------------------------------------------------------------+
| seekMovie - seek in the movie depending on the wAction.|
|      Possible actions are IDM_HOME (start of movie) or|
|      IDM_END (end of movie)|
||
|      Always stop the play before seeking.|
||
+--------------------------------------------------------------*/
static void seekMovie(
HWND hWnd,
int nAction)
{
/* first stop the movie from playing if it is playing */
if (gfPlaying){
playMovie(hWnd, 0);
}

if (nAction == IDM_HOME){
/* home the movie */
mciSendString(MOV_SEEK_TO_START, NULL, 0, NULL);
} else if (nAction == IDM_END){
/* go to the end of the movie */
mciSendString(MOV_SEEK_TO_END, NULL, 0, NULL);
}
}

/*--------------------------------------------------------------+
| stepMovie - step forward or reverse in the movie.  wDirection|
|can be IDM_STEP (forward) or IDM_RSTEP (reverse)|
||
|Again, stop the play if one is in progress.|
||
+--------------------------------------------------------------*/
static void stepMovie(
HWND hWnd,
int nDirection)
{
if (gfPlaying)
playMovie(hWnd, 0);  /* turn off the movie */

if (nDirection == IDM_STEP)
mciSendString(MOV_FORWARD_STEP, NULL, 0, NULL);
else
mciSendString(MOV_REVERSE_STEP, NULL,0, NULL);
}
/*--------------------------------------------------------------+
| FormatFilterString |
||
+--------------------------------------------------------------*/
static BOOL FormatFilterString(
UINT uID,
LPSTR lpszString )
{
int nLength, nCtr = 0;
char chWildCard;

*lpszString = 0;

nLength = LoadString( ghInst, uID, lpszString, BUFSIZE );

chWildCard = lpszString[nLength-1];

while( lpszString[nCtr] ) {
if( lpszString[nCtr] == chWildCard )
lpszString[nCtr] = 0;
nCtr++;
}

return TRUE;
}

/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(
HWNDhwnd,
UINTmsg,
WPARAMwParam,
LPARAMlParam)
{
switch (msg) {
case WM_COMMAND:
EndDialog(hwnd, TRUE);
return TRUE;
case WM_INITDIALOG:
return TRUE;
}
return FALSE;
}


README.TXT

Simple AVI playback application.  Includes a subset of the features in
Media Player and uses the MCIWnd window class.


MPLAY.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/**************************************************************************
 * MPLAY.C - Movie Player App using MCIWnd window class
 *
 *************************************************************************/

#define  STRICT
#include <windows.h>
#include <mmsystem.h>
#include <vfw.h>

#include "mplay.h"

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

#define BUFSIZE 260

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

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    HWND          hwndApp;
    MSG           msg;
    WORD          wVer;
    static char   szBuffer[BUFSIZE];

    /* first let's make sure we are running on 1.1 */
    wVer = HIWORD(VideoForWindowsVersion());
    if (wVer < 0x010a) {
    char szTitle[BUFSIZE];
    char szMessage[BUFSIZE];

        LoadString( hInst, IDS_APPERR, szTitle, BUFSIZE );
    LoadString( hInst, IDS_OLDVFW, szMessage, BUFSIZE );
    /* oops, we are too old, blow out of here */
    MessageBeep(MB_ICONHAND);
    MessageBox(NULL, szMessage, szTitle, MB_OK|MB_ICONSTOP);
    return FALSE;
    }

    /* create the window */
    hwndApp = MCIWndCreate(NULL,
                           hInst,
                   MCIWNDF_SHOWNAME            |
                   MCIWNDF_SHOWMODE            |
                   WS_OVERLAPPEDWINDOW         |
                   WS_VISIBLE,
                   NULL);

    if (hwndApp == NULL)
    return -1;

    LoadString( hInst, IDS_DIALOGNAME, szBuffer, BUFSIZE );
    SetWindowText(hwndApp, szBuffer);

    //Polling messages from event queue
    while (GetMessage(&msg,NULL,0,0)) {

        TranslateMessage(&msg);
    DispatchMessage(&msg);

    if (!IsWindow(hwndApp))
        PostQuitMessage(0);
    }

    return msg.wParam;
}


MPLAY.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1993 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#defineIDS_DIALOGNAME1
#defineIDS_APPERR2
#defineIDS_OLDVFW3


README.TXT

Stream handler that translates video streams into new streams of a different
bit depth--for example, it can translate from 24-bit to 8-bit.  Copy dll to system
directory and use with AVIVIEW, 'Find Optimal Palette' menu item.


DIBMAP.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

typedef DWORD * LPHISTOGRAM;
// casting ensures values > 127 get zero-extended, not sign-extended
#define RGB16(r,g,b) (\
((WORD)(UINT)(((int)(UINT)(r) >> 3) << 10)|\
(((int)(UINT)(g) >> 3) << 5)|\
(((int)(UINT)(b) >> 3) << 0)))

LPHISTOGRAM InitHistogram(
LPHISTOGRAM lpHistogram);
void FreeHistogram(
LPHISTOGRAM lpHistogram);
HPALETTE HistogramPalette(
LPHISTOGRAM lpHistogram,
LPBYTE lp16to8,
int nColors);
BOOL DibHistogram(
LPBITMAPINFOHEADER lpbi,
LPBYTE lpBits,
int x,
int y,
int dx,
int dy,
LPHISTOGRAM lpHistogram);
LPBITMAPINFOHEADER DibReduce(
LPBITMAPINFOHEADER lpbi,
LPBYTE lpBits,
HPALETTE hpal,
LPBYTE lp16to8);
void NEAR PASCAL UseHistogram(
LPHISTOGRAM lpHistogram);
DWORD near _fastcall GetHistogram(
BYTE r,
BYTE g,
BYTE b);
void near _fastcall IncHistogram(
WORD rgb16);


PALMAP.C

/****************************************************************************
 *
 *  PALMAP.C
 *
 *  Stream handler to map to a palette.
 *
 ***************************************************************************/

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <vfw.h>
#include <string.h>

#include "dibmap.h"
#include "palmap.h"
#include "palmap.rc"

#defineINITGUID
#include <initguid.h>

// Bring in the external GUIDs we need. Apparently compobj.lib doesn't define
DEFINE_OLEGUID(IID_IUnknown,0x00000000L, 0, 0);

HINSTANCEghMod;

STDAPI AVIStreamMakePalette(
PAVISTREAM pavi,
LONG lSkip,
HPALETTE FAR *lphpal,
LPBYTE lp16to8,
int nColors)
{
LPHISTOGRAM lpHist = NULL;
LONGl, lEnd;
LONGlRet = AVIERR_OK;
PGETFRAMEpgf = NULL;

if (!pavi || !lphpal || nColors < 2 || nColors > 256)
return ResultFromScode(AVIERR_BADPARAM);

if (lSkip < 1)
lSkip = 1;

lpHist = InitHistogram(NULL);
if (!lpHist)
return ResultFromScode(AVIERR_MEMORY);

pgf = AVIStreamGetFrameOpen(pavi, NULL);

l = AVIStreamStart(pavi);
lEnd = l + AVIStreamLength(pavi);
for (l = AVIStreamStart(pavi), lEnd = l + AVIStreamLength(pavi);
l < lEnd;
l += lSkip) {
LPBITMAPINFOHEADER lpbi;

lpbi = AVIStreamGetFrame(pgf, l);

if (!lpbi) {
lRet = AVIERR_INTERNAL;
goto error;
}

DibHistogram(lpbi, NULL, 0, 0, -1, -1, lpHist);
}

*lphpal = HistogramPalette(lpHist, lp16to8, nColors);

if (!*lphpal)
lRet = AVIERR_MEMORY;

error:
if (pgf)
AVIStreamGetFrameClose(pgf);

if (lpHist)
FreeHistogram(lpHist);

return ResultFromScode(lRet);
}

typedef struct {
IAVIStreamVtbl FAR *lpVtbl;

ULONGulRefCount;

//
// instance data
//
PAVISTREAMpavi;
PGETFRAMEpgf;
AVISTREAMINFOsinfo;
HPALETTEhpal;
LPBYTElp16to8;
LONGlLastFrame;
LPBITMAPINFOHEADERlpdibLast;
} PALMAPSTREAM, FAR*PPALMAPSTREAM;

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE PalMapStreamQueryInterface(
PAVISTREAM ps,
REFIID riid,
LPVOID FAR* ppvObj);
HRESULT STDMETHODCALLTYPE PalMapStreamCreate(
PAVISTREAM ps,
LONG lParam1,
LONG lParam2);
ULONGSTDMETHODCALLTYPE PalMapStreamAddRef(
PAVISTREAM ps);
ULONGSTDMETHODCALLTYPE PalMapStreamRelease(
PAVISTREAM ps);
HRESULT STDMETHODCALLTYPE PalMapStreamInfo(
PAVISTREAM ps,
AVISTREAMINFOW FAR * psi,
LONG lSize);
LONGSTDMETHODCALLTYPE PalMapStreamFindKeyFrame(
PAVISTREAM ps,
LONG lPos,
LONG lFlags);
HRESULT STDMETHODCALLTYPE PalMapStreamReadFormat(
PAVISTREAM ps,
LONG lPos,
LPVOID lpFormat,
LONG FAR *lpcbFormat);
HRESULT STDMETHODCALLTYPE PalMapStreamSetFormat(
PAVISTREAM ps,
LONG lPos,
LPVOID lpFormat,
LONG cbFormat);
HRESULT STDMETHODCALLTYPE PalMapStreamRead(
PAVISTREAM ps,
LONG lStart,
LONG lSamples,
LPVOID lpBuffer,
LONG cbBuffer,
LONG FAR * plBytes,
LONG FAR * plSamples);
HRESULT STDMETHODCALLTYPE PalMapStreamWrite(
PAVISTREAM ps,
LONG lStart,
LONG lSamples,
LPVOID lpBuffer,
LONG cbBuffer,
DWORD dwFlags,
LONG FAR *plSampWritten,
LONG FAR *plBytesWritten);
HRESULT STDMETHODCALLTYPE PalMapStreamDelete(
PAVISTREAM ps,
LONG lStart,
LONG lSamples);
HRESULT STDMETHODCALLTYPE PalMapStreamReadData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG FAR *lpcb);
HRESULT STDMETHODCALLTYPE PalMapStreamWriteData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG cb);
HRESULT STDMETHODCALLTYPE PalMapStreamSetInfo(
        PAVISTREAM ps,
        AVISTREAMINFOW FAR * psi,
        LONG lSize);

IAVIStreamVtbl PalMapStreamHandler = {
PalMapStreamQueryInterface,
PalMapStreamAddRef,
PalMapStreamRelease,
PalMapStreamCreate,
PalMapStreamInfo,
PalMapStreamFindKeyFrame,
PalMapStreamReadFormat,
PalMapStreamSetFormat,
PalMapStreamRead,
PalMapStreamWrite,
PalMapStreamDelete,
PalMapStreamReadData,
PalMapStreamWriteData,
        PalMapStreamSetInfo
};

STDAPI AVICreateMappedStream(PAVISTREAM FAR *ppsMapped,
PAVISTREAM ps,
int nColors)
{
PPALMAPSTREAMpavi;
HRESULThr;

*ppsMapped = 0;

pavi = (PPALMAPSTREAM) GlobalAllocPtr(GHND, sizeof(PALMAPSTREAM));
if (pavi == NULL)
return ResultFromScode(AVIERR_MEMORY);

pavi->lpVtbl = &PalMapStreamHandler;

hr = (pavi->lpVtbl->Create)((PAVISTREAM) pavi, (LONG) ps, nColors);

if (hr != NOERROR) {
(pavi->lpVtbl->Release)((PAVISTREAM) pavi);
return hr;
}

*ppsMapped = (PAVISTREAM) pavi;

return AVIERR_OK;

}

///////////////////////////////////////////////////////////////////////////
//
//  PalMapStreamOpen()
//
//  open a single stream of a particular type from a AVI file.
//
//  params:
//      szFile      - PAVISTREAM
//      fccType     - must be streamtypeVIDEO
//      lParam    - nColors
//
//  returns:
//      a PAVISTREAM for the specifed stream or NULL.
//
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE PalMapStreamCreate(
PAVISTREAM ps,
LONG lParam1,
LONG lParam2)
{
PPALMAPSTREAMpavi = (PPALMAPSTREAM) ps;
TCHARachTemp[128];
TCHARachTemplate[64];

LONGlRet = AVIERR_OK;

pavi->ulRefCount = 1;

AVIStreamAddRef((PAVISTREAM) lParam1);

pavi->pavi = (PAVISTREAM) lParam1;

AVIStreamInfo(pavi->pavi, &pavi->sinfo, sizeof(pavi->sinfo));

if (pavi->sinfo.fccType != streamtypeVIDEO) {
lRet = AVIERR_INTERNAL;
goto error;
}

pavi->pgf = AVIStreamGetFrameOpen(pavi->pavi, NULL);

if (!pavi->pgf) {
lRet = AVIERR_INTERNAL;
goto error;
}

pavi->sinfo.fccHandler = 0;

// Fix up stream name
LoadString(ghMod, IDS_STREAMNAME, achTemplate,
sizeof(achTemplate) / sizeof(TCHAR));
wsprintf(achTemp, achTemplate, pavi->sinfo.szName, lParam2);
lstrcpyn(pavi->sinfo.szName, achTemp,
sizeof(pavi->sinfo.szName) / sizeof(TCHAR));
pavi->sinfo.szName[sizeof(pavi->sinfo.szName) / sizeof(TCHAR) - 1] =
                TEXT('\0');

// default to 256 colors
if (lParam2 < 2 || lParam2 > 256)
lParam2 = 256;

pavi->lp16to8 = GlobalAllocPtr(GMEM_MOVEABLE, 32768L);
if (!pavi->lp16to8) {
lRet = AVIERR_MEMORY;
goto error;
}

lRet = GetScode(AVIStreamMakePalette(pavi->pavi,
AVIStreamLength(pavi->pavi) / 30,
&pavi->hpal, pavi->lp16to8,
(int) lParam2));

pavi->lLastFrame = -1;

error:
return ResultFromScode(lRet);
}

///////////////////////////////////////////////////////////////////////////
//
//  PalMapStreamQueryInterface()
//
//      let other people know what interfaces we support
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE PalMapStreamQueryInterface(
PAVISTREAM ps,
REFIID riid,
LPVOID FAR* ppvObj)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

if (IsEqualIID(riid, &IID_IUnknown))
*ppvObj = ps;
else if (IsEqualIID(riid, &IID_IAVIStream))
*ppvObj = ps;
else
return ResultFromScode(E_NOINTERFACE);

pavi->ulRefCount++;
return NOERROR;
}

///////////////////////////////////////////////////////////////////////////
//
//  PalMapStreamAddRef()
//
//      increase the reference count of the stream
//
///////////////////////////////////////////////////////////////////////////

ULONG STDMETHODCALLTYPE PalMapStreamAddRef(
PAVISTREAM ps)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

return ++pavi->ulRefCount;
}

///////////////////////////////////////////////////////////////////////////
//
//  PalMapStreamRelease()
//
//      close a PalMapStream stream
//
///////////////////////////////////////////////////////////////////////////

ULONG STDMETHODCALLTYPE PalMapStreamRelease(
PAVISTREAM ps)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

if (!pavi)
return 0;

if (--pavi->ulRefCount)
return pavi->ulRefCount;

if (pavi->pgf)
AVIStreamGetFrameClose(pavi->pgf);
pavi->pgf = 0;

if (pavi->pavi)
AVIStreamClose(pavi->pavi);
pavi->pavi = 0;

if (pavi->lp16to8) {
GlobalFreePtr(pavi->lp16to8);
pavi->lp16to8 = 0;
}

if (pavi->hpal) {
DeletePalette(pavi->hpal);
pavi->hpal = 0;
}

if (pavi->lpdibLast) {
GlobalFreePtr(pavi->lpdibLast);
pavi->lpdibLast = 0;
}

GlobalFreePtr(pavi);

return 0;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE PalMapStreamReadFormat(
PAVISTREAM ps,
LONG lPos,
LPVOID lpFormat,
LONG FAR *lpcbFormat)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
LONGlSize;

PalMapStreamRead(ps, lPos, 1, NULL, 0, NULL, NULL);

if (pavi->lpdibLast == 0)
return ResultFromScode(AVIERR_INTERNAL);
lSize = pavi->lpdibLast->biSize
+ pavi->lpdibLast->biClrUsed * sizeof(RGBQUAD);

if (lpFormat)
hmemcpy(lpFormat, pavi->lpdibLast,
min(*lpcbFormat, lSize));

*lpcbFormat = lSize;

return 0;
}

LONG STDMETHODCALLTYPE PalMapStreamFindKeyFrame(
PAVISTREAM ps,
LONG lPos,
LONG lFlags)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

return lPos;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE PalMapStreamInfo(
PAVISTREAM ps,
AVISTREAMINFOW FAR * psi,        // OLE interfaces are ALWAYS UNICODE
LONG lSize)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

if (psi)
hmemcpy(psi, &pavi->sinfo, min(lSize, sizeof(pavi->sinfo)));

return 0;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE PalMapStreamRead(
PAVISTREAMps,
LONGlStart,
LONGlSamples,
LPVOIDlpBuffer,
LONGcbBuffer,
LONG FAR *plBytes,
LONG FAR *plSamples)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;

LPBITMAPINFOHEADERlpbi;
LPVOIDlp;

if (lStart != pavi->lLastFrame) {
pavi->lLastFrame = -1;
lpbi = AVIStreamGetFrame(pavi->pgf, lStart);

if (!lpbi)
goto ReadNothing;

if (pavi->lpdibLast) {
GlobalFreePtr(pavi->lpdibLast);
pavi->lpdibLast = 0;
}

pavi->lpdibLast = DibReduce(lpbi, NULL, pavi->hpal, pavi->lp16to8);
pavi->lLastFrame = lStart;
}

lpbi = pavi->lpdibLast;
//
// a NULL buffer means return the size buffer needed to read
// the given sample.
//
lp = (LPBYTE) lpbi + lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD);

if (plBytes)
*plBytes = lpbi->biSizeImage;

if (plSamples)
*plSamples = 1;

if (lpBuffer) {
if (cbBuffer >= (LONG) lpbi->biSizeImage)
hmemcpy(lpBuffer, lp, lpbi->biSizeImage);
else
goto ReadNothing;
}

return 0;

ReadNothing:
if (plBytes)
*plBytes = 0;

if (plSamples)
*plSamples = 0;

return ResultFromScode(AVIERR_BUFFERTOOSMALL);
}

//
//
// Extra unimplemented functions.....
//
//
//
HRESULT STDMETHODCALLTYPE PalMapStreamReadData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG FAR *lpcb)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE PalMapStreamSetFormat(
PAVISTREAM ps,
LONG lPos,
LPVOID lpFormat,
LONG cbFormat)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE PalMapStreamWriteData(
PAVISTREAM ps,
DWORD fcc,
LPVOID lp,
LONG cb)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE PalMapStreamWrite(
PAVISTREAM ps,
LONG lStart,
LONG lSamples,
LPVOID lpBuffer,
LONG cbBuffer,
DWORD dwFlags,
LONG FAR *plSampWritten,
LONG FAR *plBytesWritten)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE PalMapStreamDelete(
PAVISTREAM ps,
LONG lStart,
LONG lSamples)
{
PPALMAPSTREAM pavi = (PPALMAPSTREAM) ps;
return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE PalMapStreamSetInfo(
   PAVISTREAM ps,
   AVISTREAMINFOW FAR * psi,
   LONG lSize)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

EXTERN_C int CALLBACK WEP(
BOOL fSystemExit)
{
return TRUE;
}

EXTERN_C BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID);
EXTERN_C BOOL APIENTRY DllMain(
HANDLE hModule,
DWORD dwReason,
LPVOID lpReserved )
{
switch( dwReason)
{
case DLL_PROCESS_ATTACH:
if(ghMod == NULL)
ghMod = (HMODULE)hModule;
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


PALMAP.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

//
// To use this function:
//
// ps should be a PAVISTREAM representing a video stream that you want
// to construct a single palette for.
//
// The function will read lots of video frames from stream ps, construct
// a palette containing nColors colors, and return a new stream pointer in
// ppsMapped.
//
// The new stream will be an 8-bit video stream, which you can then
// use like any other stream.  Specifically, you can pass it to AVISave.
//
STDAPI AVICreateMappedStream(
PAVISTREAM FAR *ppsMapped,
PAVISTREAM ps,
int nColors);

//
// This function is called internally by AVICreateMappedStream, but you
// might conceivably want to call it yourself.
//
STDAPI AVIStreamMakePalette(
PAVISTREAM pavi,
LONG lSkip,
HPALETTE FAR *lphpal,
LPBYTE lp16to8,
int nColors);


DIBMAP.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include "dibmap.h"

extern NEAR PASCAL MemCopy(LPVOID,LPVOID,DWORD);
extern NEAR PASCAL MemFill(LPVOID,DWORD,BYTE);

void Histogram24(BYTE *pb, int dx, int dy, WORD WidthBytes, LPHISTOGRAM lpHistogram);
void Histogram16(BYTE *pb, int dx, int dy, WORD WidthBytes, LPHISTOGRAM lpHistogram);
void Histogram8(BYTE *pb, int dx, int dy, WORD WidthBytes, LPHISTOGRAM lpHistogram, LPWORD lpColors);
void Histogram4(BYTE *pb, int dx, int dy, WORD WidthBytes, LPHISTOGRAM lpHistogram, LPWORD lpColors);
void Histogram1(BYTE *pb, int dx, int dy, WORD WidthBytes, LPHISTOGRAM lpHistogram, LPWORD lpColors);

void Reduce24(BYTE *pbIn, int dx, int dy, WORD cbIn, BYTE *pbOut, WORD cbOut, LPBYTE lp16to8);
void Reduce16(BYTE *pbIn, int dx, int dy, WORD cbIn, BYTE *pbOut, WORD cbOut, LPBYTE lp16to8);
void Reduce8(BYTE *pbIn, int dx, int dy, WORD cbIn, BYTE *pbOut, WORD cbOut, LPBYTE lp8to8);
void Reduce4(BYTE *pbIn, int dx, int dy, WORD cbIn, BYTE *pbOut, WORD cbOut, LPBYTE lp8to8);
void Reduce1(BYTE *pbIn, int dx, int dy, WORD cbIn, BYTE *pbOut, WORD cbOut, LPBYTE lp8to8);

LPHISTOGRAM lpHistogram;

//
// InitHistogram
//
// create a zero'ed histogram table, or initialize a existing table
// to all zeros.
//
LPHISTOGRAM InitHistogram(
LPHISTOGRAM lpHistogram)
{
if (lpHistogram == NULL)
lpHistogram = (LPVOID)GlobalAllocPtr(GHND, 32768L * sizeof(DWORD));

return lpHistogram;
}

//
// FreeHistogram
//
// free a histogram table
//
void FreeHistogram(
LPHISTOGRAM lpHistogram)
{
GlobalFreePtr(lpHistogram);
}

//
// DibHistogram
//
// take all colors in a dib and increment its entry in the Histogram table
//
// supports the following DIB formats: 1,4,8,16,24
//
BOOL DibHistogram(
LPBITMAPINFOHEADER lpbi,
LPBYTE lpBits,
int x,
int y,
int dx,
int dy,
LPHISTOGRAM lpHistogram)
{
inti;
WORDWidthBytes;
RGBQUAD FAR *prgbq;
WORDargb16[256];

if (lpbi == NULL || lpHistogram == NULL)
return FALSE;

if (lpbi->biClrUsed == 0 && lpbi->biBitCount <= 8)
lpbi->biClrUsed = (1 << (int)lpbi->biBitCount);

if (lpBits == NULL)
lpBits = (LPBYTE)lpbi + (int)lpbi->biSize
+ (int)lpbi->biClrUsed*sizeof(RGBQUAD);

WidthBytes = (WORD)(((LONG)lpbi->biBitCount * lpbi->biWidth + 7) / 8 + 3) & ~3;

((BYTE *)lpBits) += (DWORD)y*WidthBytes + ((x*(int)lpbi->biBitCount)/8);

if (dx < 0 || dx > (int)lpbi->biWidth)
dx = (int)lpbi->biWidth;

if (dy < 0 || dy > (int)lpbi->biHeight)
dy = (int)lpbi->biHeight;

if ((int)lpbi->biBitCount <= 8)
{
prgbq = (LPVOID)((LPBYTE)lpbi + lpbi->biSize);

for (i=0; i<(int)lpbi->biClrUsed; i++)
{
argb16[i] = (WORD) RGB16(prgbq[i].rgbRed,prgbq[i].rgbGreen,prgbq[i].rgbBlue);
}

for (i=(int)lpbi->biClrUsed; i<256; i++)
{
argb16[i] = 0x0000;// just in case!
}
}

switch ((int)lpbi->biBitCount)
{
case 24:
Histogram24(lpBits, dx, dy, WidthBytes, lpHistogram);
break;

case 16:
Histogram16(lpBits, dx, dy, WidthBytes, lpHistogram);
break;

case 8:
Histogram8(lpBits, dx, dy, WidthBytes, lpHistogram, argb16);
break;

case 4:
Histogram4(lpBits, dx, dy, WidthBytes, lpHistogram, argb16);
break;

case 1:
Histogram1(lpBits, dx, dy, WidthBytes, lpHistogram, argb16);
break;
}
}

//
// will convert the given DIB to a 8bit DIB with the specifed palette
//
LPBITMAPINFOHEADER DibReduce(
LPBITMAPINFOHEADER lpbiIn,
LPBYTE pbIn,
HPALETTE hpal,
LPBYTE lp16to8)
{
LPBITMAPINFOHEADERlpdib;
WORDnPalColors;  // NOTE win32 GetObject ONLY returns
                                             // 2 bytes for HPALs
intnDibColors;
WORDcbOut;
WORDcbIn;
BYTExlat[256];
BYTE *pbOut;
RGBQUAD FAR *prgb;
DWORDdwSize;
inti;
intdx;
intdy;
PALETTEENTRYpe;

dx= (int)lpbiIn->biWidth;
dy= (int)lpbiIn->biHeight;
cbIn= (WORD)((((UINT)lpbiIn->biBitCount*dx+7)/8+3)&~3);
cbOut= (WORD)((dx+3)&~3);

GetObject(hpal, sizeof(WORD), (LPVOID)&nPalColors);
nDibColors = (int)lpbiIn->biClrUsed;

if (nDibColors == 0 && lpbiIn->biBitCount <= 8)
nDibColors = (1 << (int)lpbiIn->biBitCount);

if (pbIn == NULL)
pbIn = (LPBYTE)lpbiIn + (int)lpbiIn->biSize + nDibColors*sizeof(RGBQUAD);

dwSize = (DWORD)cbOut * dy;

lpdib = GlobalAllocPtr(GMEM_MOVEABLE,sizeof(BITMAPINFOHEADER)
+ nPalColors*sizeof(RGBQUAD) + dwSize);

if (!lpdib)
return NULL;

lpdib->biSize= sizeof(BITMAPINFOHEADER);
lpdib->biWidth= lpbiIn->biWidth;
lpdib->biHeight= lpbiIn->biHeight;
lpdib->biPlanes= 1;
lpdib->biBitCount= 8;
lpdib->biCompression= BI_RGB;
lpdib->biSizeImage= dwSize;
lpdib->biXPelsPerMeter= 0;
lpdib->biYPelsPerMeter= 0;
lpdib->biClrUsed= (DWORD)nPalColors;
lpdib->biClrImportant= 0;

pbOut= (LPBYTE)lpdib + (int)lpdib->biSize + nPalColors*sizeof(RGBQUAD);
prgb= (LPVOID)((LPBYTE)lpdib + (int)lpdib->biSize);

for (i=0; i<(int)nPalColors; i++)
{
GetPaletteEntries(hpal, i, 1, &pe);

prgb[i].rgbRed= pe.peRed;
prgb[i].rgbGreen= pe.peGreen;
prgb[i].rgbBlue= pe.peBlue;
prgb[i].rgbReserved= 0;
}

if ((int)lpbiIn->biBitCount <= 8)
{
prgb = (LPVOID)((LPBYTE)lpbiIn + lpbiIn->biSize);

for (i=0; i<nDibColors; i++)
xlat[i] = lp16to8[RGB16(prgb[i].rgbRed,prgb[i].rgbGreen,prgb[i].rgbBlue)];

for (; i<256; i++)
xlat[i] = 0;
}

switch ((int)lpbiIn->biBitCount)
{
case 24:
Reduce24(pbIn, dx, dy, cbIn, pbOut, cbOut, lp16to8);
break;

case 16:
Reduce16(pbIn, dx, dy, cbIn, pbOut, cbOut, lp16to8);
break;

case 8:
Reduce8(pbIn, dx, dy, cbIn, pbOut, cbOut, xlat);
break;

case 4:
Reduce4(pbIn, dx, dy, cbIn, pbOut, cbOut, xlat);
break;

case 1:
Reduce1(pbIn, dx, dy, cbIn, pbOut, cbOut, xlat);
break;
}

return lpdib;
}

///////////////////////////////////////////////////////////////////////////////
// cluster.c
///////////////////////////////////////////////////////////////////////////////

#define IN_DEPTH5// # bits/component kept from input
#define IN_SIZE(1 << IN_DEPTH)// max value of a color component

typedef enum { red, green, blue } color;

typedef struct tagCut
{
long lvariance;// for int version
int cutpoint;
unsigned long rem;// for experimental fixed point
color cutaxis;
long w1, w2;
double variance;
} Cut;

typedef struct tagColorBox// from cluster.c
{
struct tagColorBox *next;/* pointer to next box */
int rmin, rmax, gmin, gmax, bmin, bmax;/* bounding box */
long variance, wt;/* weighted variance */
long sum[3];/* sum of values */
} ColorBox;

static int InitBoxes(int nBoxes);
static void DeleteBoxes(void);
static int SplitBoxAxis(ColorBox *box, Cut cutaxis);
static void ShrinkBox(ColorBox *box);
static int ComputePalette(LPHISTOGRAM lpHistogram, LPBYTE lp16to8, LPPALETTEENTRY palette);
static COLORREF DetermineRepresentative(ColorBox *box, int palIndex);
static Cut FindSplitAxis(ColorBox *box);
static void SplitBox(ColorBox *box);
static void SortBoxes(void);

HANDLE hBoxes;
ColorBox*UsedBoxes;
ColorBox*FreeBoxes;
LPBYTEglp16to8;

//#define hist(r,g,b) ((DWORD *)glpHistogram)[(WORD)(b) | ((WORD)(g)<<IN_DEPTH) | ((WORD)(r)<<(IN_DEPTH*2))]
#define hist(r,g,b) GetHistogram((BYTE)(r),(BYTE)(g),(BYTE)(b))

#pragma optimize ("", off)
//
// set FS == lpHistogram.sel, so we can get at it quickly!
//
void NEAR PASCAL UseHistogram(
LPHISTOGRAM lpHistogramToSet)
{
        lpHistogram = lpHistogramToSet;
}

//
// get the DOWRD histogram count of a RGB
//
DWORD near _fastcall GetHistogram(
BYTE r,
BYTE g,
BYTE b)
{
        return lpHistogram[((UINT)r << 10 ) + ((UINT)g << 5) + (UINT)b];
}

//
// increment the histogram count of a RGB16
//
//
// #define IncHistogram(w) if (lpHistogram[(WORD)(w)] < 0xFFFFFFFF)
//lpHistogram[(WORD)(w)]++;
//
void near _fastcall IncHistogram(WORD rgb16)
{
    if (lpHistogram[rgb16] != (DWORD)-1)
        lpHistogram[rgb16]++;
}

#pragma optimize ("", on)

// !!! C8 generates a Jump into the middle of a 2 byte instruction
// !!! Stupid C8!
#pragma optimize ("", off)

//
// HistogramPalette
//
// given a histogram, will reduce it to 'nColors' number of colors.
// returns a optimal palette.  if specifed lp16to8 will contain the
// translate table from RGB16 to the palette index.
//
// you can specify lpHistogram as lp16to8
//
HPALETTE HistogramPalette(
LPHISTOGRAM lpHistogram,
LPBYTE lp16to8,
int nColors)
{
struct {
WORDpalVersion;
WORDpalNumEntries;
PALETTEENTRY palPalEntry[256];
} pal;

WORDw;
DWORDdwMax;
COLORREFrgb;
ColorBox*box;
inti;

//
// the 'C' code cant handle >64k histogram counts.
// !!!fix this
//
for (dwMax=0,w=0; w<0x8000; w++)
dwMax = max(dwMax,lpHistogram[w]);

while (dwMax > 0xFFFFl)
{
for (w=0; w<0x8000; w++)
lpHistogram[w] /= 2;

dwMax /= 2;
}

if (!InitBoxes(min(nColors, 236)))
return NULL;

UseHistogram(lpHistogram);
glp16to8 = lp16to8;

/* while there are free boxes left, split the largest */

i = 0;

do {
i++;
SplitBox(UsedBoxes);
}
while (FreeBoxes && UsedBoxes->variance);

SortBoxes();

i=0;

//
// add some standard colors to the histogram
//
if (nColors > 236)
{
HDC hdc;

hdc = GetDC(NULL);

if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE)
{
GetSystemPaletteEntries(hdc, 0,
10, &pal.palPalEntry[0]);
GetSystemPaletteEntries(hdc, 246,
10, &pal.palPalEntry[246]);

i = 10;
}

ReleaseDC(NULL, hdc);
}

/* Generate the representitives and the associated Palette mapping */
/* NOTE:  Might loop less than nColors times.                      */
for (box = UsedBoxes; box; box = box->next, i++)
{
rgb = DetermineRepresentative(box, i);
pal.palPalEntry[i].peRed= GetRValue(rgb);
pal.palPalEntry[i].peGreen= GetGValue(rgb);
pal.palPalEntry[i].peBlue= GetBValue(rgb);
pal.palPalEntry[i].peFlags= 0;
}

DeleteBoxes();

if (nColors > 236)
{
for (; i<246; i++)
{
pal.palPalEntry[i].peRed= 0;
pal.palPalEntry[i].peGreen= 0;
pal.palPalEntry[i].peBlue= 0;
pal.palPalEntry[i].peFlags= 0;
}

i = 256;
}

glp16to8 = NULL;

pal.palVersion= 0x300;
pal.palNumEntries= (WORD)i;
return CreatePalette((LPLOGPALETTE)&pal);
}

#pragma optimize ("", on)

static void SortBoxes(void)
{
ColorBox *box;
ColorBox *newList;
ColorBox *insBox;
ColorBox *nextBox;

newList = UsedBoxes;
nextBox = newList->next;
newList->next = NULL;

for (box = nextBox; box; box = nextBox) { // just an insertion sort...
nextBox = box->next;
if (box->wt > newList->wt) {
box->next = newList;
newList = box;
} else {
for (insBox = newList;
insBox->next && (box->wt < insBox->next->wt);
insBox = insBox->next) ;
box->next = insBox->next;
insBox->next = box;
}
}

UsedBoxes = newList;
}

/*
allocate space for nBoxes boxes, set up links.  On exit UsedBoxes
points to one box, FreeBoxes points to remaining (nBoxes-1) boxes.
return 0 if successful.
*/

static BOOL InitBoxes(
int nBoxes)
{
int i;

hBoxes = LocalAlloc(LHND, nBoxes*sizeof(ColorBox));
if (!hBoxes)
return FALSE;

UsedBoxes = (ColorBox*)LocalLock(hBoxes);
FreeBoxes = UsedBoxes + 1;
UsedBoxes->next = NULL;

for (i = 0; i < nBoxes - 1; ++i)
{
FreeBoxes[i].next = FreeBoxes + i + 1;
}
FreeBoxes[nBoxes-2].next = NULL;

/* save the bounding box */
UsedBoxes->rmin = UsedBoxes->gmin = UsedBoxes->bmin = 0;
UsedBoxes->rmax = UsedBoxes->gmax = UsedBoxes->bmax = IN_SIZE - 1;
UsedBoxes->variance = 9999999;/* arbitrary large # */

return TRUE;
}

static void DeleteBoxes(void)
{
LocalUnlock(hBoxes);
LocalFree(hBoxes);
hBoxes = NULL;
}

static void SplitBox(
ColorBox *box)
{
/*
split box into two roughly equal halves and update the data structures
appropriately.
*/
Cut cutaxis;
ColorBox *temp, *temp2, *prev;

cutaxis = FindSplitAxis(box);

/* split the box along that axis.  If rc != 0 then the box contains
one color, and should not be split
*/
if (SplitBoxAxis(box, cutaxis))
return;

/* shrink each of the boxes to fit the points they enclose */
ShrinkBox(box);
ShrinkBox(FreeBoxes);

/* move old box down in list, if necessary */
if (box->next && box->variance < box->next->variance)
{
UsedBoxes = box->next;
temp = box;
do {
prev = temp;
temp = temp->next;
} while (temp && temp->variance > box->variance);
box->next = temp;
prev->next = box;
}

/* insert the new box in sorted order (descending), removing it
from the free list.
*/
if (FreeBoxes->variance >= UsedBoxes->variance)
{
temp = FreeBoxes;
FreeBoxes = FreeBoxes->next;
temp->next = UsedBoxes;
UsedBoxes = temp;
}
else
{
temp = UsedBoxes;
do {
prev = temp;
temp = temp->next;
} while (temp && temp->variance > FreeBoxes->variance);
temp2 = FreeBoxes->next;
FreeBoxes->next = temp;
prev->next = FreeBoxes;
FreeBoxes = temp2;
}
}

static Cut FindSplitAxis(
ColorBox *box)
{
unsigned longproj_r[IN_SIZE],proj_g[IN_SIZE],proj_b[IN_SIZE];
unsigned longf;
doublecurrentMax,mean;
unsigned longw,w1,m,m1;
intr,g,b;
intbestCut;
colorbestAxis;
CutcutRet;
doubletemp1,temp2;

for (r = 0; r < IN_SIZE; r++) {
proj_r[r] = proj_g[r] = proj_b[r] = 0;
}

w = 0;

// Project contents of box down onto axes
for (r = box->rmin; r <= box->rmax; r++) {
for (g = box->gmin; g <= box->gmax; ++g) {
for (b = box->bmin; b <= box->bmax; ++b) {
f = hist(r,g,b);
proj_r[r] += f;
proj_g[g] += f;
proj_b[b] += f;
}
}
w += proj_r[r];
}

currentMax = 0.0f;

#define Check_Axis(l,color)\
m = 0;\
for (l = box->l##min; l <= box->l##max; (l)++) {\
m += l * proj_##l[l];\
}\
mean = ((double) m) / ((double) w);\
\
w1 = 0;\
m1 = 0;\
for (l = box->l##min; l <= box->l##max; l++) {\
w1 += proj_##l[l];\
if (w1 == 0)\
continue;\
if (w1 == w)\
break;\
m1 += l * proj_##l[l];\
temp1 = mean - (((double) m1) / ((double) w1));\
temp2 = (((double) w1) / ((double) (w-w1))) * temp1 * temp1; \
if (temp2 > currentMax) {\
bestCut = l;\
bestAxis = color;\
currentMax = temp2;\
}\
}

Check_Axis(r,red);
Check_Axis(g,green);
Check_Axis(b,blue);

cutRet.cutaxis = bestAxis;
cutRet.cutpoint = bestCut;

return cutRet;
}

static int SplitBoxAxis(
ColorBox *box,
Cut cutaxis)
{
/*
Split box along splitaxis into two boxes, one of which is placed
back in box, the other going in the first free box (FreeBoxes)
If the box only contains one color, return non-zero, else return 0.
*/
ColorBox *next;

if ( box->variance == 0)
return 1;

/* copy all non-link information to new box */
next = FreeBoxes->next;
*FreeBoxes = *box;
FreeBoxes->next = next;

switch (cutaxis.cutaxis)
{
case red:
box->rmax = cutaxis.cutpoint;
FreeBoxes->rmin = cutaxis.cutpoint+1;
break;
case green:
box->gmax = cutaxis.cutpoint;
FreeBoxes->gmin = cutaxis.cutpoint+1;
break;
case blue:
box->bmax = cutaxis.cutpoint;
FreeBoxes->bmin = cutaxis.cutpoint+1;
break;
}

return 0;
}

static void ShrinkBox(
ColorBox *box)
{
unsigned long n, sxx, sx2, var, quotient, remainder;
int r,g,b;
unsigned long f;
unsigned longproj_r[IN_SIZE],proj_g[IN_SIZE],proj_b[IN_SIZE];

n = 0;

for (r = 0; r < IN_SIZE; r++) {
proj_r[r] = proj_g[r] = proj_b[r] = 0;
}

// Project contents of box down onto axes
for (r = box->rmin; r <= box->rmax; r++) {
for (g = box->gmin; g <= box->gmax; ++g) {
for (b = box->bmin; b <= box->bmax; ++b) {
f = hist(r,g,b);
proj_r[r] += f;
proj_g[g] += f;
proj_b[b] += f;
}
}
n += proj_r[r];
}

box->wt = n;
var = 0;

#define AddAxisVariance(c)\
sxx = 0; sx2 = 0;\
for (c = box->c##min; c <= box->c##max; c++) {\
sxx += proj_##c[c] * c * c;\
sx2 += proj_##c[c] * c;\
}\
quotient = sx2 / n; /* This stuff avoids overflow */\
remainder = sx2 % n;\
var += sxx - quotient * sx2 - ((remainder * sx2)/n);

AddAxisVariance(r);
AddAxisVariance(g);
AddAxisVariance(b);

box->variance = var;
}

static COLORREF DetermineRepresentative(
ColorBox *box,
int palIndex)
{
/*
determines the rgb value to represent the pixels contained in
box.  nbits is the # bits/component we're allowed to return.
*/

long f;
long Rval, Gval, Bval;
unsigned long total;
int r, g, b;
int i;

/* compute the weighted sum of the elements in the box */
Rval = Gval = Bval = total = 0;
for (r = box->rmin; r <= box->rmax; ++r)
{
for (g = box->gmin; g <= box->gmax; ++g)
{
for (b = box->bmin; b <= box->bmax; ++b)
{
if (glp16to8)
{
i = (b) | (g << IN_DEPTH)
| (r << (IN_DEPTH*2));
glp16to8[i] = (BYTE)palIndex;
}

f = hist(r,g,b);
if (f == 0L)
continue;

Rval += f * (long) r;
Gval += f * (long) g;
Bval += f * (long) b;

total += f;
}
}
}

/* Bias the sum so that we round up at .5 */
Rval += total / 2;
Gval += total / 2;
Bval += total / 2;

return RGB(Rval*255/total/IN_SIZE, Gval*255/total/IN_SIZE, Bval*255/total/IN_SIZE);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//
//  write this stuff in ASM!
//
///////////////////////////////////////////////////////////////////////////////

void Histogram24(
BYTE *pb,
int dx,
int dy,
WORD WidthBytes,
LPHISTOGRAM lpHistogram)
{
int x,y;
BYTE r,g,b;
WORD w;

UseHistogram(lpHistogram);

WidthBytes -= (WORD) (dx*3);
for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
b = *pb++;
g = *pb++;
r = *pb++;
w = (WORD) RGB16(r,g,b);
IncHistogram(w);
}
pb += WidthBytes;
}
}

void Histogram16(
BYTE *pb,
int dx,
int dy,
WORD WidthBytes,
LPHISTOGRAM lpHistogram)
{
int x,y;
WORD w;

UseHistogram(lpHistogram);

WidthBytes -= (WORD)(dx*2);

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
w = *((WORD *)pb)++;
w &= 0x7FFF;
IncHistogram(w);
}
pb += WidthBytes;
}
}

void Histogram8(
BYTE *pb,
int dx,
int dy,
WORD WidthBytes,
LPHISTOGRAM lpHistogram,
LPWORD lpColors)
{
int x,y;
WORD w;

UseHistogram(lpHistogram);

WidthBytes -= (WORD)dx;

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
w = lpColors[*pb++];
IncHistogram(w);
}
pb += WidthBytes;
}
}

void Histogram4(
BYTE *pb,
int dx,
int dy,
WORD WidthBytes,
LPHISTOGRAM lpHistogram,
LPWORD lpColors)
{
int x,y;
BYTE b;
WORD w;

UseHistogram(lpHistogram);

WidthBytes -= (WORD)((dx+1)/2);

for (y=0; y<dy; y++)
{
for (x=0; x<(dx+1)/2; x++)
{
b = *pb++;

w = lpColors[b>>4];
IncHistogram(w);

w = lpColors[b&0x0F];
IncHistogram(w);
}
pb += WidthBytes;
}
}

void Histogram1(
BYTE *pb,
int dx,
int dy,
WORD WidthBytes,
LPHISTOGRAM lpHistogram,
LPWORD lpColors)
{
int x,y,i;
BYTE b;
WORD w;

UseHistogram(lpHistogram);

WidthBytes -= (WORD)((dx+7)/8);

for (y=0; y<dy; y++)
{
for (x=0; x<(dx+7)/8; x++)
{
b = *pb++;

for (i=0; i<8; i++)
{
w = lpColors[b>>7];
IncHistogram(w);
b<<=1;
}
}
pb += WidthBytes;
}
}

///////////////////////////////////////////////////////////////////////////////
//
//  write this stuff in ASM! too
//
///////////////////////////////////////////////////////////////////////////////

void Reduce24(
BYTE *pbIn,
int dx,
int dy,
WORD cbIn,
BYTE *pbOut,
WORD cbOut,
LPBYTE lp16to8)
{
int x,y;
BYTE r,g,b;

cbOut-= (WORD)dx;
cbIn-= (WORD)(dx*3);

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
b = *pbIn++;
g = *pbIn++;
r = *pbIn++;
*pbOut++ = lp16to8[RGB16(r,g,b)];
}
pbIn += cbIn;
pbOut+= cbOut;
}
}

void Reduce16(
BYTE *pbIn,
int dx,
int dy,
WORD cbIn,
BYTE *pbOut,
WORD cbOut,
LPBYTE lp16to8)
{
int x,y;
WORD w;

cbOut-= (WORD)dx;
cbIn-= (WORD)(dx*2);

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
w = *((WORD *)pbIn)++;
*pbOut++ = lp16to8[w&0x7FFF];
}
pbIn += cbIn;
pbOut+= cbOut;
}
}

void Reduce8(
BYTE *pbIn,
int dx,
int dy,
WORD cbIn,
BYTE *pbOut,
WORD cbOut,
LPBYTE lp8to8)
{
int x,y;

cbIn-= (WORD)dx;
cbOut-= (WORD)dx;

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
*pbOut++ = lp8to8[*pbIn++];
}
pbIn+= cbIn;
pbOut+= cbOut;
}
}

void Reduce4(
BYTE *pbIn,
int dx,
int dy,
WORD cbIn,
BYTE *pbOut,
WORD cbOut,
LPBYTE lp8to8)
{
int x,y;
BYTE b;

cbIn-= (WORD)((dx+1)/2);
cbOut-= (WORD)((dx+1)&~1);

for (y=0; y<dy; y++)
{
for (x=0; x<(dx+1)/2; x++)
{
b = *pbIn++;
*pbOut++ = lp8to8[b>>4];
*pbOut++ = lp8to8[b&0x0F];
}
pbIn+= cbIn;
pbOut+= cbOut;
}
}

void Reduce1(
BYTE *pbIn,
int dx,
int dy,
WORD cbIn,
BYTE *pbOut,
WORD cbOut,
LPBYTE lp8to8)
{
int x,y;
BYTE b;

cbIn-= (WORD)((dx+7)/8);
cbOut-= (WORD)dx;

for (y=0; y<dy; y++)
{
for (x=0; x<dx; x++)
{
if (x%8 == 0)
b = *pbIn++;

*pbOut++ = lp8to8[b>>7];
b<<=1;
}
pbIn+= cbIn;
pbOut+= cbOut;
}
}


README.TXT

Text stream draw renderer for rendering text data in AVI files.  To install on Win95,
copy dll to system dir and add the following line to [drivers32] section of system.ini:
txts.draw=txtout32.dll. On NT, install from control panel, drivers applet, pointing
to the sample directory. Example text stream is in OUTPUT.AVI, created by WRITEAVI sample.


DRVPROC.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   drvproc.c
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <vfw.h>

#include "TxtOut.h"

HMODULE ghModule;

/***************************************************************************
 *
 * DriverProc The entry point for an installable driver.
 *
 ***************************************************************************/

LRESULT WINAPI DriverProc(
DWORD dwDriverID,
HDRVR hDriver,
UINT uiMessage,
LPARAM lParam1,
LPARAM lParam2)
{
INSTINFO *pi = (INSTINFO *)(UINT)dwDriverID;

switch (uiMessage)
{
case DRV_LOAD:
return (LRESULT) Load();

case DRV_FREE:
Free();
return (LRESULT)1L;

case DRV_OPEN:
// if being opened with no open struct, then return a non-zero
// value without actually opening
if (lParam2 == 0L)
return 0xFFFF0000;

return (LRESULT)(DWORD)(UINT)Open((ICOPEN FAR *) lParam2);

      case DRV_CLOSE:
      if (dwDriverID != 0xFFFF0000)
          Close(pi);

return (LRESULT)1L;

/*********************************************************************

state messages

*********************************************************************/

case DRV_QUERYCONFIGURE: // configuration from drivers applet
return (LRESULT)0L;

case DRV_CONFIGURE:
return DRV_OK;

case ICM_CONFIGURE:
//
// return ICERR_OK if you will do a configure box, error otherwise
//
if (lParam1 == -1)
return QueryConfigure(pi) ? ICERR_OK : ICERR_UNSUPPORTED;
else
return Configure(pi, (HWND)lParam1);

case ICM_ABOUT:
//
// return ICERR_OK if you will do a about box, error otherwise
//
if (lParam1 == -1)
return QueryAbout(pi) ? ICERR_OK : ICERR_UNSUPPORTED;
else
return About(pi, (HWND)lParam1);

case ICM_GETSTATE:
return GetState(pi, (LPVOID)lParam1, (DWORD)lParam2);

case ICM_SETSTATE:
return SetState(pi, (LPVOID)lParam1, (DWORD)lParam2);

case ICM_GETINFO:
return GetInfo(pi, (ICINFO FAR *)lParam1, (DWORD)lParam2);

/*********************************************************************

draw messages

*********************************************************************/

case ICM_DRAW_QUERY:
return DrawQuery(pi, (LPVOID FAR *)lParam1);

case ICM_DRAW_BEGIN:
return DrawBegin(pi,(ICDRAWBEGIN FAR *)lParam1, (DWORD)lParam2);

case ICM_DRAW:
return Draw(pi,(ICDRAW FAR *)lParam1, (DWORD)lParam2);

case ICM_DRAW_END:
return DrawEnd(pi);

/*********************************************************************

standard driver messages

*********************************************************************/

case DRV_DISABLE:
case DRV_ENABLE:
return (LRESULT)1L;

case DRV_INSTALL:
case DRV_REMOVE:
return (LRESULT)DRV_OK;
}

if (uiMessage < DRV_USER)
return DefDriverProc(dwDriverID, hDriver, uiMessage,
lParam1,lParam2);
else
return ICERR_UNSUPPORTED;
}

/****************************************************************************
 *
 * DllMain Library initialization code.
 *
 ***************************************************************************/
APIENTRY DllMain(HANDLE, DWORD, LPVOID);
APIENTRY DllMain(
HANDLE hModule,
DWORD dwReason,
LPVOID lpReserved )
{
switch( dwReason)
{
case DLL_PROCESS_ATTACH:
if(ghModule == NULL)
ghModule = (HMODULE)hModule;
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


TXTOUT.C

/****************************************************************************
 *
 *   TxtOut.c
 *
 *   TXTOUT is a sample installable compressor for AVI 1.0.
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <vfw.h>
#include <stdlib.h> // for mbstowcs
#include <memory.h> // for _fmemcpy
#include <stdarg.h> // for va_list

#include "TxtOut.h"

/*****************************************************************************
 *
 *
 ****************************************************************************/

#define MODNAME         "TXTOUT"
#define FOURCC_SAMP mmioFOURCC('D','R','A','W')

#define VERSION_SAMP    0x00010000 // 1.00

extern HANDLE ghModule;

/*****************************************************************************
 *
 * DefaultState holds the compression options that will be used if the user
 * compresses an image without configuring us at all first. In the case of
 * the sample compressor, it is the pixel keep ratio.
 *
 ****************************************************************************/

ICSTATE DefaultState = {2};

/*****************************************************************************
 *
 * Load() is called from the ICM_LOAD message.
 *
 * Tasks such as allocating global memory that is non-instance specific
 * or initializing coprocessor hardware may be performed here.
 *
 * Our simple case needs none of this.
 *
 ****************************************************************************/
BOOL NEAR PASCAL Load(void)
{
    DPF2("Load()");
    return TRUE;
}

/*****************************************************************************
 *
 * Free() is called from the ICM_FREE message.
 *
 * It should totally reverse the effects of Load() in preparation for
 * the DRV being removed from memory.
 *
 ****************************************************************************/
void NEAR PASCAL Free(void)
{
    DPF2("Free()");
}

/*****************************************************************************
 *
 * Open() is called from the ICM_OPEN message
 *
 * This message will be sent for a particular compress/decompress session.
 * Our code must verify that we are indeed being called as a video
 * compressor and create/initialize a state structure. The ICM will
 * give us back the pointer to that structure on every message dealing
 * with this session.
 *
 ****************************************************************************/
INSTINFO * NEAR PASCAL Open(
    ICOPEN FAR * icinfo)
{
    INSTINFO * pinst;

    DPF2("Open('%4.4hs', '%4.4hs')", &icinfo->fccType, &icinfo->fccHandler);

    //
    // refuse to open if we are not being opened as a Video compressor
    //
    if (icinfo->fccType != streamtypeTEXT) {
        DPF2("Open rejected: not TEXT");
        return NULL;
    }

    pinst = (INSTINFO *)LocalAlloc(LPTR, sizeof(INSTINFO));

    if (!pinst) {
        icinfo->dwError = ICERR_MEMORY;
        DPF2("Open rejected: no memory");
        return NULL;
    }

    //
    // init structure
    //
    pinst->dwFlags = icinfo->dwFlags;
    pinst->nDraw = 0;
    pinst->hfont = GetStockFont(ANSI_VAR_FONT);

    //
    // set the default state.
    //
    SetState(pinst, NULL, 0);

    //
    // return success.
    //
    icinfo->dwError = ICERR_OK;

    DPF2("Open succeeded!");
    return pinst;
}

/*****************************************************************************
 *
 * Close() is called on the ICM_CLOSE message.
 *
 * This message is the complement to ICM_OPEN and marks the end
 * of a compress/decompress session. We kill any in-progress operations
 * (although this shouldn't be needed) and free our instance structure.
 *
 ****************************************************************************/
LRESULT NEAR PASCAL Close(
    INSTINFO * pinst)
{
    DPF2("Close()");

    while (pinst->nDraw > 0)
        DrawEnd(pinst);
    LocalFree((HLOCAL)pinst);
    return 1;
}

/*****************************************************************************
 *
 * QueryAbout() and About() handle the ICM_ABOUT message.
 *
 * QueryAbout() returns TRUE to indicate we support an about box.
 * About() displays the box.
 *
 ****************************************************************************/
BOOL NEAR PASCAL QueryAbout(
    INSTINFO * pinst)
{
    DPF2("QueryAbout()");

    return TRUE;
}

LRESULT NEAR PASCAL About(
    INSTINFO * pinst,
    HWND hwnd)
{
    char sztempName[50];
    char sztempDesc[50];

    LoadString(ghModule, IDS_NAME, sztempName, sizeof sztempName);
    LoadString(ghModule, IDS_DESCRIPTION, sztempDesc, sizeof sztempDesc);
    DPF2("About()");
    MessageBox(hwnd, sztempDesc, sztempName, MB_OK|MB_ICONINFORMATION);
    return ICERR_OK;
}

/*****************************************************************************
 *
 * QueryConfigure() and Configure() implement the ICM_CONFIGURE message.
 *
 * These functions put up a dialog that allows the user, if he so
 * chooses, to modify the configuration portion of our state info.
 *
 ****************************************************************************/
BOOL NEAR PASCAL QueryConfigure(
    INSTINFO * pinst)
{
    DPF2("QueryConfigure()");
    return FALSE;
}

LRESULT NEAR PASCAL Configure(
    INSTINFO * pinst,
    HWND hwnd)
{
    DPF2("Configure()");
    return DialogBoxParam(ghModule,"Configure",hwnd,ConfigureDlgProc, (LONG)(UINT)pinst);
}

/*****************************************************************************
 *
 * GetState() implements the ICM_GETSTATE message.
 *
 * We copy our configuration information and return how many bytes it took.
 *
 ****************************************************************************/
LRESULT NEAR PASCAL GetState(
    INSTINFO * pinst,
    LPVOID pv,
    DWORD dwSize)
{
    DPF2("GetState(%08lX, %ld)", pv, dwSize);

    if (pv == NULL || dwSize == 0)
        return sizeof(ICSTATE);

    if (dwSize < sizeof(ICSTATE))
        return 0;

    *((ICSTATE FAR *)pv) = pinst->CurrentState;

    // return number of bytes copied
    return sizeof(ICSTATE);
}

/*****************************************************************************
 *
 * SetState() implements the ICM_SETSTATE message.
 *
 * The ICM is giving us configuration information saved by GetState()
 * earlier.
 *
 ****************************************************************************/
LRESULT NEAR PASCAL SetState(
    INSTINFO * pinst,
    LPVOID pv,
    DWORD dwSize)
{
    DPF2("SetState(%08lX, %ld)", pv, dwSize);

    if (pv == NULL)
        pinst->CurrentState = DefaultState;
    else if (dwSize >= sizeof(ICSTATE))
        pinst->CurrentState = *((ICSTATE FAR *)pv);
    else
        return 0;

    // return number of bytes copied
    return sizeof(ICSTATE);
}

/*****************************************************************************
 *
 * GetInfo() implements the ICM_GETINFO message
 *
 * We just fill in the structure to tell the ICM what we can do. The flags
 * (none of which this sample supports) mean the following :
 *
 * VIDCF_QUALITY - we support the quality variable. This means we look at
 *                 dwQuality in the ICINFO structure when compressing and
 *                 make a concious decision to trade quality for space.
 *                 (higher values of dwQuality mean quality is more
 *                 important). dwQuality is set by the ICM.
 *
 * VIDCF_TEMPORAL - We do interframe compression. In this algorithm, not
 *                  every frame is a "key frame"; some frames depend on
 *                  other frames to be generated. An example of this might
 *                  be to store frame buffer differences until the
 *                  differences are big enough to no longer make this
 *                  worthwhile, then storing another complete frame and
 *                  starting over. In this case, the complete frames that
 *                  are stored are key frames and should be flagged as
 *                  such.
 *
 * VIDCF_DRAW -     We will draw the decompressed image on our own. This is
 *                  useful if the decompression is assisted by the video
 *                  hardware.
 *
 ****************************************************************************/
LRESULT NEAR PASCAL GetInfo(
    INSTINFO * pinst,
    ICINFO FAR *icinfo,
    DWORD dwSize)
{
    char sztempName[50];
    char sztempDesc[50];

    LoadString(ghModule, IDS_NAME, sztempName, sizeof sztempName);
    LoadString(ghModule, IDS_DESCRIPTION, sztempDesc, sizeof sztempDesc);
    DPF2("GetInfo()");

    if (icinfo == NULL)
        return sizeof(ICINFO);

    if (dwSize < sizeof(ICINFO))
        return 0;

    icinfo->dwSize      = sizeof(ICINFO);
    icinfo->fccType     = streamtypeTEXT;
    icinfo->fccHandler  = FOURCC_SAMP;
    icinfo->dwFlags     = VIDCF_DRAW;

                // VIDCF_QUALITY    // supports quality
                // VIDCF_TEMPORAL   // supports inter-frame
                // VIDCF_DRAW       // supports drawing

    icinfo->dwVersion   = VERSION_SAMP;
    icinfo->dwVersionICM    = ICVERSION;
    mbstowcs(icinfo->szDescription, sztempDesc, sizeof sztempDesc);
    mbstowcs(icinfo->szName, sztempName, sizeof sztempName);

    return sizeof(ICINFO);
}

LRESULT NEAR PASCAL DrawQuery(
    INSTINFO * pinst,
    LPVOID FAR * lpFormat)
{
    return ICERR_OK;
}

/*****************************************************************************
 *
 * DrawBegin() implements ICM_DRAW_BEGIN
 *
 * This is just like DecompressBegin() except that we also must prepare to
 * actually draw the bitmap on the screen. ICDRAWBEGIN provides info specific
 * to this task.
 *
 ****************************************************************************/
LRESULT NEAR PASCAL DrawBegin(
    INSTINFO * pinst,
    ICDRAWBEGIN FAR *icinfo,
    DWORD dwSize)
{
    DPF2("DrawBegin()");

    if (pinst->nDraw++ > 0)
        return ICERR_OK;

    //
    // get ready to draw
    //
    pinst->db = *icinfo;
    SetRect(&pinst->rc, icinfo->xDst, icinfo->yDst,
        icinfo->xDst + icinfo->dxDst, icinfo->yDst + icinfo->dyDst);

    return ICERR_OK;
}

/*****************************************************************************
 *
 * Draw implements ICM_DRAW
 *
 * Decompress and draw
 *
 ****************************************************************************/
LRESULT NEAR PASCAL Draw(
    INSTINFO * pinst,
    ICDRAW FAR *icinfo,
    DWORD dwSize)
{
    HFONT hfont;

    DPF2("Draw()");

    if (icinfo->lpData && icinfo->cbData) {
        pinst->iLen = min((int) icinfo->cbData,
                (int) _fstrlen((LPSTR) icinfo->lpData));
        pinst->iLen = min(pinst->iLen, BUFFER_SIZE - 1);
        hmemcpy(pinst->achBuffer, icinfo->lpData, pinst->iLen);
        pinst->achBuffer[pinst->iLen] = '\0';
    } else if (!(icinfo->dwFlags & ICDRAW_UPDATE))
        return ICERR_OK;
    if (pinst->nDraw == 0)
        return ICERR_ERROR;

    hfont = SelectFont(pinst->db.hdc, pinst->hfont);

    ExtTextOut(pinst->db.hdc,
        pinst->db.xDst, pinst->db.yDst,
        ETO_OPAQUE,
        &pinst->rc,
        (LPSTR) pinst->achBuffer,
        pinst->iLen,
        NULL);

    SelectFont(pinst->db.hdc, hfont);

    return ICERR_OK;
}

/*****************************************************************************
 *
 * DrawEnd() implements ICM_DRAW_END
 *
 * See DecompressEnd()
 *
 ****************************************************************************/
LRESULT NEAR PASCAL DrawEnd(
    INSTINFO * pinst)
{
    DPF2("DrawEnd()");

    if (pinst->nDraw == 0)
        return ICERR_ERROR;

    if (--pinst->nDraw > 0)
        return ICERR_OK;

    //
    // but if we did we would clean up here
    //

    return ICERR_OK;
}

/*****************************************************************************
 *
 * ConfigureDlgProc() is called by Configure
 *
 * This is a standard dialog proc which allows the user to
 * pick config options for the driver.
 *
 ****************************************************************************/
BOOL CALLBACK ConfigureDlgProc(
    HWND hdlg,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg)
    {
    case WM_COMMAND:
        switch LOWORD((wParam))
        {
            case IDOK:
                EndDialog(hdlg,TRUE);
                break;

            case IDCANCEL:
                EndDialog(hdlg,FALSE);
                break;
        }
        break;
    }
    return FALSE;
}

/*****************************************************************************
 *
 * dprintf() is called by the DPF macro if DEBUG is defined at compile time.
 *
 * The messages will be send to COM1: like any debug message. To
 * enable debug output, add the following to WIN.INI :
 *
 * [debug]
 * TXTOUT=1
 *
 ****************************************************************************/

#ifdef DEBUG
static int iDebug = -1;

void FAR cdecl dprintf(
    LPSTR szFormat,
    ...)
{
    char ach[128];

    if (iDebug == -1)
        iDebug = GetProfileInt("Debug", MODNAME, FALSE);

    if (!iDebug)
        return;

    lstrcpy(ach, MODNAME ": ");

    {
        va_list va;

        va_start(va, szFormat);
        wvsprintf(ach+lstrlen(ach), szFormat, va);
        va_end(va);

    }

    lstrcat(ach, "\r\n");
    OutputDebugString(ach);
}

void FAR cdecl dprintf2(
    LPSTR szFormat,
    ...)
{
    char ach[128];

    if (iDebug == -1)
        iDebug = GetProfileInt("Debug", MODNAME, FALSE);

    if (iDebug < 2)
        return;

    lstrcpy(ach, MODNAME ": ");

    {
        va_list va;

        va_start(va, szFormat);
        wvsprintf(ach+lstrlen(ach), szFormat, va);
        va_end(va);

    }

    lstrcat(ach, "\r\n");

    OutputDebugString(ach);
}

#endif


TXTOUT.H

/****************************************************************************
 *
 *   TxtOut.h
 *
 ***************************************************************************/
/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
typedef struct {
WORD w;
} ICSTATE;

#define BUFFER_SIZE 256

typedef struct {
DWORDdwFlags;// flags from ICOPEN
ICSTATECurrentState;// current state of compressor.
intnDraw;// count of DRAW_BEGIN calls
HFONThfont;
ICDRAWBEGINdb;
RECTrc;
intiLen;
charachBuffer[BUFFER_SIZE];
} INSTINFO, *PINSTINFO;

BOOLNEAR PASCAL Load(void);
voidNEAR PASCAL Free(void);
INSTINFO*NEAR PASCAL Open(ICOPEN FAR *icinfo);
LRESULTNEAR PASCAL Close(INSTINFO * pinst);
LRESULTNEAR PASCAL GetState(INSTINFO * pinst, LPVOID pv, DWORD dwSize);
LRESULTNEAR PASCAL SetState(INSTINFO * pinst, LPVOID pv, DWORD dwSize);
LRESULTNEAR PASCAL GetInfo(INSTINFO * pinst, ICINFO FAR *icinfo, DWORD dwSize);

BOOLNEAR PASCAL QueryAbout(INSTINFO * pinst);
LRESULTNEAR PASCAL About(INSTINFO * pinst, HWND hwnd);
BOOLNEAR PASCAL QueryConfigure(INSTINFO * pinst);
LRESULTNEAR PASCAL Configure(INSTINFO * pinst, HWND hwnd);

LRESULTNEAR PASCAL DrawQuery(INSTINFO * pinst, LPVOID FAR * lpFormat);
LRESULTNEAR PASCAL DrawBegin(INSTINFO * pinst,ICDRAWBEGIN FAR *icinfo, DWORD dwSize);
LRESULTNEAR PASCAL Draw(INSTINFO * pinst,ICDRAW FAR *icinfo, DWORD dwSize);
LRESULTNEAR PASCAL DrawEnd(INSTINFO * pinst);
BOOL CALLBACK ConfigureDlgProc(
HWND hdlg,
UINT msg,
WPARAM wParam,
LPARAM lParam);

#ifdef DEBUG
extern void FAR CDECL dprintf(LPSTR, ...);
extern void FAR CDECL dprintf2(LPSTR, ...);
#define DPF dprintf
#define DPF2 dprintf2
#else
#define DPF / ## /// Pasting 2 slashes together comments out
#define DPF2 / ## /// the rest of the line.
#endif

// string IDs
#define IDS_DESCRIPTION112// 0x70, so WIN16 rc.exe can put 16
#define IDS_NAME113// strings in a segment


README.TXT

Full-featured video capture application.


ARROW.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   arrow.h: Arrow control window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * interface definition for spin arrow class
 */

#define SPINARROW_CLASSNAME     "comarrow"

//reflect an arrow movement in the attached edit box
LONG FAR PASCAL ArrowEditChange( HWND hwndEdit, UINT wParam,
                                 LONG lMin, LONG lMax );

// call  me first (to register class)
BOOL FAR PASCAL ArrowInit(HANDLE hInst);


DIALOGS.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   dialogs.c: Dialog box processing
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <dos.h>
#include <vfw.h>

#include "arrow.h"
#include "rlmeter.h"
#include "vidcap.h"
#include "vidframe.h"
#include "help.h"

static long GetFreeDiskSpaceInKB(LPTSTR) ;
static int  CountMCIDevices(WORD) ;

BOOL FAR PASCAL MCISetupProc(HWND, unsigned, UINT, LONG);

//--- utility functions  ---------------------------------------------------

/*----------------------------------------------------------------------------*\
|   SmartWindowPosition (HWND hWndDlg, HWND hWndShow)
|                                                                              |
|   Description:                                                               |
|       This function attempts to position a dialog box so that it
|       does not obscure the hWndShow window. This function is
|       typically called during WM_INITDIALOG processing.
|                                                                              |
|   Arguments:                                                                 |
|       hWndDlg         handle of the soon to be displayed dialog
|       hWndShow        handle of the window to keep visible
|                                                                              |
|   Returns:                                                                   |
|       1 if the windows overlap and positions were adjusted
|       0 if the windows don't overlap
|                                                                              |
\*----------------------------------------------------------------------------*/
int SmartWindowPosition (HWND hWndDlg, HWND hWndShow)
{
    RECT rc, rcDlg, rcShow;
    int iHeight, iWidth;
    int iScreenHeight, iScreenWidth;

    GetWindowRect(hWndDlg, &rcDlg);
    GetWindowRect(hWndShow, &rcShow);

    iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    iScreenWidth = GetSystemMetrics(SM_CXSCREEN);

    InflateRect (&rcShow, 5, 5); // allow a small border
    if (IntersectRect(&rc, &rcDlg, &rcShow)){
        /* the two do intersect, now figure out where to place */
        /* this dialog window.  Try to go below the Show window*/
        /* first and then to the right, top and left.   */

        /* get the size of this dialog */
        iHeight = rcDlg.bottom - rcDlg.top;
        iWidth = rcDlg.right - rcDlg.left;

        if ((WORD)(rcShow.bottom + iHeight + 1) <  iScreenHeight){
                /* will fit on bottom, go for it */
                rc.top = rcShow.bottom + 1;
                rc.left = (((rcShow.right - rcShow.left)/2) + rcShow.left)
            - (iWidth/2);
        } else if ((WORD)(rcShow.right + iWidth + 1) < iScreenWidth){
                /* will fit to right, go for it */
                rc.left = rcShow.right + 1;
                rc.top = (((rcShow.bottom - rcShow.top)/2) + rcShow.top)
            - (iHeight/2);
        } else if ((WORD)(rcShow.top - iHeight - 1) > 0){
                /* will fit on top, handle that */
                rc.top = rcShow.top - iHeight - 1;
                rc.left = (((rcShow.right - rcShow.left)/2) + rcShow.left)
            - (iWidth/2);
        } else if ((WORD)(rcShow.left - iWidth - 1) > 0){
                /* will fit to left, do it */
                rc.left = rcShow.left - iWidth - 1;
                rc.top = (((rcShow.bottom - rcShow.top)/2) + rcShow.top)
            - (iHeight/2);
        } else {
                /* we are hosed, they cannot be placed so that there is */
                /* no overlap anywhere.  To minimize the damage just put*/
                /* the dialog in the lower left corner of the screen    */
                rc.top = (int)iScreenHeight - iHeight;
                rc.left = (int)iScreenWidth - iWidth;
        }

        /* make any adjustments necessary to keep it on the screen */
        if (rc.left < 0) rc.left = 0;
        else if ((WORD)(rc.left + iWidth) > iScreenWidth)
                rc.left = (int)(iScreenWidth - iWidth);

        if (rc.top < 0)  rc.top = 0;
        else if ((WORD)(rc.top + iHeight) > iScreenHeight)
                rc.top = (int)iScreenHeight - iHeight;

        SetWindowPos(hWndDlg, NULL, rc.left, rc.top, 0, 0,
            SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
        return 1;
    } // if the windows overlap by default

    return 0;
}

//
// GetFreeDiskSpace: Function to Measure Available Disk Space
//
static long GetFreeDiskSpaceInKB(LPTSTR pFile)
{
    DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters;
    char RootName[MAX_PATH];
    LPSTR ptmp;    //required arg

    // need to find path for root directory on drive containing
    // this file.

    GetFullPathName(pFile, sizeof(RootName)/sizeof(RootName[0]), RootName, &ptmp);

    // truncate this to the name of the root directory (god how tedious)
    if ((RootName[0] == TEXT('\\')) && (RootName[1] == TEXT('\\'))) {

        // path begins with  \\server\share\path so skip the first
        // three backslashes
        ptmp = &RootName[2];
        while (*ptmp && (*ptmp != TEXT('\\'))) {
            ptmp++;
        }
        if (*ptmp) {
            // advance past the third backslash
            ptmp++;
        }
    } else {
        // path must be drv:\path
        ptmp = RootName;
    }

    // find next backslash and put a null after it
    while (*ptmp && (*ptmp != TEXT('\\'))) {
        ptmp++;
    }
    // found a backslash ?
    if (*ptmp) {
        // skip it and insert null
        ptmp++;
        *ptmp = TEXT('\0');
    }

    if (!GetDiskFreeSpace(RootName,
&dwSectorsPerCluster,
&dwBytesPerSector,
&dwFreeClusters,
&dwClusters)) {
    MessageBoxID(IDS_ERR_MEASUREFREEDISK, MB_OK | MB_ICONINFORMATION);
    return (-1);
    }
    return(MulDiv (dwSectorsPerCluster * dwBytesPerSector,
   dwFreeClusters,
   1024));
}

//
// CountMCIDevices: Function to Find the Number of MCI Devices of a Type
//
static int CountMCIDevices(WORD wType)
{
    int               nTotal = 0 ;
    DWORD             dwCount ;
    MCI_SYSINFO_PARMS mciSIP ;

    mciSIP.dwCallback = 0 ;
    mciSIP.lpstrReturn = (LPSTR)(LPVOID) &dwCount ;
    mciSIP.dwRetSize = sizeof(DWORD) ;
    mciSIP.wDeviceType = wType ;

    // Use an MCI command to get the info
    if (! mciSendCommand(0, MCI_SYSINFO, MCI_SYSINFO_QUANTITY,
                         (DWORD)(LPVOID) &mciSIP))
        nTotal = (int) *((LPDWORD) mciSIP.lpstrReturn) ;

    return nTotal ;
}

/* lMicroSec = StringRateToMicroSec(szRate)
 *
 * Convert <szRate> (e.g. "3.75" representing 3.75 frames per second)
 * to microseconds (e.g. 266667L microseconds per frame).
 *
 * If the rate is close to zero or negative, then 0L is returned.
 */
DWORD StringRateToMicroSec(PSTR szRate)
{
doubledRate;

dRate = atof(szRate);

if (dRate < 0.0001) {
return 0L;
} else {
return (DWORD) /*floor*/((1e6 / dRate) + 0.5);
        }
}

/* ach = MicroSecToStringRate(achRate, lMicroSec)
 *
 * Convert <lMicroSec> (e.g. 266667L microseconds per frame) to a
 * string rate (e.g. "3.75" representing 3.75 frames per second).
 * Returns <achRate>.
 */
PSTR MicroSecToStringRate(PSTR achRate, DWORD dwMicroSec)
{
sprintf(achRate, "%.3f",
(dwMicroSec == 0L) ? 0.0 : (1e6 / (double) dwMicroSec));

return achRate;
}

/*
 * update the text of an edit field based on a comarrow up or down change
 * - write the text in N.NNN format (truncated to an integer)
 */
LONG FAR PASCAL
MilliSecVarArrowEditChange(
    HWND hwndEdit,
    UINT uCode,
    LONG lMin,
    LONG lMax,
    UINT uInc
)
{
    char achTemp[32];
    LONG l;

    GetWindowText(hwndEdit, achTemp, sizeof(achTemp));

    l = atol(achTemp);
    if(uCode == SB_LINEUP ) {

if(l + (long)uInc <= lMax ) {
    l += uInc;
    wsprintf(achTemp, "%ld.000", l );
    SetWindowText(hwndEdit, achTemp );
        } else {
    MessageBeep( 0 );
}
    } else if (uCode == SB_LINEDOWN ) {
if( l-(long)uInc >= lMin ) {
    l -= uInc;
    wsprintf( achTemp, "%ld.000", l );
    SetWindowText( hwndEdit, achTemp );
        } else {
    MessageBeep( 0 );
}
    }
    return( l );
}

BOOL MCIGetDeviceNameAndIndex (HWND hwnd, LPINT lpnIndex, LPSTR lpName)
{
    HWND hwndCB;
    char buf[160];
    char *cp;

    hwndCB = GetDlgItem( hwnd, IDD_MCI_SOURCE );
    *lpnIndex = (int)SendMessage( hwndCB, CB_GETCURSEL, 0, 0L);
    SendMessage( hwndCB, CB_GETLBTEXT, *lpnIndex,
    (LONG)(LPSTR) buf );
    // Point cp to the system name
    for (cp = buf + lstrlen(buf); cp > buf; cp--) {
        if (*cp == ' ' && *(cp-1) == ',') {
            cp++;
            break;
}
    }
    lstrcpy (lpName, cp);
    return TRUE;
}

/*--------------------------------------------------------------+
| TimeMSToHMSString() - change milliseconds into a time string   |
+--------------------------------------------------------------*/
void FAR PASCAL TimeMSToHMSString (DWORD dwMS, LPSTR lpTime)
{
DWORDdwTotalSecs;
LONGlHundredths;
WORDwSecs;
WORDwMins;
WORDwHours;

/* convert to number of seconds */
dwTotalSecs = dwMS / 1000;

/* keep the remainder part */
lHundredths = (dwMS - (dwTotalSecs * 1000)) / 10;

/* break down into other components */
wHours = (WORD)(dwTotalSecs / 3600);// get # Hours
dwTotalSecs -= (wHours * 3600);

wMins = (WORD)(dwTotalSecs / 60);// get # Mins
dwTotalSecs -= (wMins * 60);

wSecs = (WORD)dwTotalSecs;// what's left is # seconds

/* build the string */
wsprintf((char far *)lpTime, "%02u:%02u:%02u.%02lu", wHours, wMins,
    wSecs, lHundredths);
}

/*--------------------------------------------------------------+
| TimeHMSStringToMS() - change Time string to milliseconds     |
|                       returns dwMilliseconds or -1 if error  |
+--------------------------------------------------------------*/
LONG NEAR PASCAL  TimeHMSStringToMS (LPSTR lpsz)
{
    charachTime[12];// buffer for time string (input)
    DWORDdwMSecs;// total MSecs for this thing */
    char*pDelim;// pointer to next delimeter
    char*p;// general pointer
    DWORDdwHours = 0;// # of hours
    DWORDdwMins = 0;// # of minutes
    DWORDdwSecs = 0;// # of seconds
    WORDwHundredths = 0;// # hundredths

    _fstrncpy(achTime, lpsz, sizeof (achTime));

    if (achTime[0] == '\0')
        return -1;// bad char so error out

    /* rip through the whole string and look for illegal chars */
    for (p = achTime; *p ; p++){
        if (!isdigit(*p) && *p != '.' && *p != ':')
    return -1;// bad char so error out
    }

    /* go find the hundredths portion if it exists */
    pDelim = strchr(achTime, '.');
    if (*pDelim){
        p = strrchr(achTime, '.');
        if (pDelim != p) {
        return -1;// string has > 1 '.', return error
        }

        p++;// move up past delim
        if (strlen(p) > 2) {
        *(p+2) = '\0';// knock off all but hundredths
        }

        wHundredths = atoi(p);// get the fractional part

        *pDelim = '\0';// null out this terminator
    }

    /* try and find seconds */
    pDelim = strrchr(achTime, ':');// get last ':'
    if (*pDelim) {
        p = (pDelim+1);
    } else {
        // no colon - assume just seconds in string
        p = achTime;
    }
    dwSecs = atoi(p);

    if (*pDelim) {
        *pDelim = '\0';

        /* go and get the minutes part */
        pDelim = strrchr(achTime, ':');
        if (*pDelim) {
            p = (pDelim + 1);
        } else {
            // no more colons - assume remainder is just minutes
            p = achTime;
        }
        dwMins = atoi(p);

        if (*pDelim) {
            *pDelim = '\0';

            /* get the hours */
            p = achTime;
            dwHours = atoi(p);
        }
    }

    /* now we've got the hours, minutes, seconds and any*/
    /* fractional part.  Time to build up the total time*/

    dwSecs += (dwHours * 3600);// add in hours worth of seconds
    dwSecs += (dwMins * 60);// add in minutes worth of seconds
    dwMSecs = (dwSecs * 1000L);
    dwMSecs += (wHundredths * 10L);

    /* now we've got the total number of milliseconds */
    return dwMSecs;
}

/*
 *  MCIDeviceClose
 *      This routine closes the open MCI device.
 */

void MCIDeviceClose (void)
{
    mciSendString( "close mciframes", NULL, 0, NULL );
}

/*
 *  MCIDeviceOpen
 *      This routine opens the mci device for use, and sets the
 *      time format to milliseconds.
 *      Return FALSE on error;
 */

BOOL MCIDeviceOpen (LPSTR lpDevName)
{
    char        ach[160];
    DWORD dwMCIError;

    wsprintf( ach, "open %s shareable wait alias mciframes", (LPSTR) lpDevName);
    dwMCIError = mciSendString( ach, NULL, 0, NULL );
    if( dwMCIError )  {
        return(FALSE);
    }

    dwMCIError = mciSendString( "set mciframes time format milliseconds",
    NULL, 0, NULL );
    if( dwMCIError ) {
        MCIDeviceClose();
        return(FALSE);
    }
    return ( TRUE );

}

/*
 *  MCIDeviceGetPosition
 *      Stores the current device position in milliseconds in lpdwPos.
 *      Returns TRUE on success, FALSE if error.
 */
BOOL FAR PASCAL MCIDeviceGetPosition (LPDWORD lpdwPos)
{
    char        ach[80];
    DWORD dwMCIError;

    dwMCIError = mciSendString( "status mciframes position wait",
    ach, sizeof(ach), NULL );
    if( dwMCIError ) {
        *lpdwPos = 0L;
        return FALSE;
    }

    *lpdwPos = atol( ach );
    return TRUE;
}

#ifndef USE_ACM

// --- audio streaming ------------------------------------------------

// the ShowLevel dialog streams data in from the input and
// shows the current volume.

// buffers into which sound data is recorded
#define NUM_LEVEL_BUFFERS   2

// the buffer size is calculated to be about 1/20 sec
#define UPDATES_PER_SEC     20

/*
 * we save all our data in one of these, and write a pointer to it
 * into the dialog DWL_USER field.
 */

typedef struct _LevelStreamData {
    LPWAVEHDR alpWave[NUM_LEVEL_BUFFERS];
    PCMWAVEFORMAT FAR * pwf;
    HWAVEIN hwav;
    int buffersize;
} LEVELSTREAMDATA, FAR * PLEVELSTREAMDATA;

//open the wave-device in the given format, queue all the buffers and
//start data streaming. Save the wavein device to the dialog DWL_USER window
//data area so we can close it on dialog dismissal.
BOOL
OpenStream(HWND hDlg, PCMWAVEFORMAT FAR * pwf)
{
    PLEVELSTREAMDATA pInfo;
    int i;

    pInfo = (PLEVELSTREAMDATA) GlobalLock(GlobalAlloc(GHND, sizeof(LEVELSTREAMDATA)));

    if (pInfo == NULL) {
        return(FALSE);
    }

    // complete remaining areas of wf
    pwf->wf.wFormatTag = WAVE_FORMAT_PCM;
    pwf->wf.nBlockAlign = pwf->wf.nChannels * pwf->wBitsPerSample / 8;
    pwf->wf.nAvgBytesPerSec = pwf->wf.nSamplesPerSec * pwf->wf.nBlockAlign;

    // save for later use
    pInfo->pwf = pwf;

    // buffer size a fixed fraction of a second
    pInfo->buffersize = pwf->wf.nAvgBytesPerSec/UPDATES_PER_SEC;

    pInfo->hwav = NULL;

    if (waveInOpen(
        &pInfo->hwav,
        WAVE_MAPPER,
        (LPWAVEFORMATEX)pwf,
        (DWORD) hDlg,               // callback via MM_WIM_ messages to dialogproc
        0,
        CALLBACK_WINDOW)) {
            SetWindowLong(hDlg, DWL_USER, 0);
            return(FALSE);
    }

    // store the info structure in the dialog, so that even if we fail
    // on this routine we will clean up correctly
    SetWindowLong(hDlg, DWL_USER, (long) pInfo);

    // set all the wave headers to null (for cleanup if error)
    for (i = 0; i < NUM_LEVEL_BUFFERS; i++) {
        pInfo->alpWave[i] = NULL;
    }

    // alloc, prepare and add all the buffers
    for (i = 0; i < NUM_LEVEL_BUFFERS; i++) {

        pInfo->alpWave[i] = GlobalLock(GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,
                        sizeof(WAVEHDR) + pInfo->buffersize));
        if (pInfo->alpWave[i] == NULL) {
            return(FALSE);
        }

        pInfo->alpWave[i]->lpData = (LPBYTE) (pInfo->alpWave[i] + 1);
        pInfo->alpWave[i]->dwBufferLength = pInfo->buffersize;
        pInfo->alpWave[i]->dwBytesRecorded = 0;
        pInfo->alpWave[i]->dwUser = 0;
        pInfo->alpWave[i]->dwFlags = 0;
        pInfo->alpWave[i]->dwLoops = 0;

        if (waveInPrepareHeader(pInfo->hwav, pInfo->alpWave[i], sizeof(WAVEHDR))) {
            return(FALSE);
        }

        if (waveInAddBuffer(pInfo->hwav, pInfo->alpWave[i], sizeof(WAVEHDR))) {
            return(FALSE);
        }
    }

    waveInStart(pInfo->hwav);

    return(TRUE);
}

// terminate the data streaming on a wavein device associated with a
// dialog, and clean up the buffers allocated
void
CloseStream(HWND hDlg)
{
    PLEVELSTREAMDATA pInfo;
    int i;

    // pick up our info from the dialog
    pInfo = (PLEVELSTREAMDATA) GetWindowLong(hDlg, DWL_USER);
    if ((pInfo == NULL) || (pInfo->hwav == NULL)) {
        return;
    }

    // stop streaming data
    waveInStop(pInfo->hwav);

    // release all buffers
    waveInReset(pInfo->hwav);

    // unlock and free buffers
    for (i = 0; i < NUM_LEVEL_BUFFERS; i++) {
        if (pInfo->alpWave[i]) {
            waveInUnprepareHeader(pInfo->hwav, pInfo->alpWave[i], sizeof(WAVEHDR));
            GlobalFree(GlobalHandle(pInfo->alpWave[i]));
            pInfo->alpWave[i] = NULL;
        }

    }
    waveInClose(pInfo->hwav);

    GlobalFree(GlobalHandle(pInfo));

    SetWindowLong(hDlg, DWL_USER, 0);

}

// we have received a block of data. work out the level(s) and send to
// the appropriate control on the dialog, and then requeue the buffer.
// return FALSE if any error occurs, otherwise TRUE
BOOL
StreamData(HWND hDlg, HWAVEIN hwav, LPWAVEHDR pHdr)
{
    PLEVELSTREAMDATA pInfo;
    int n = 0;
    int LevelLeft = 0, LevelRight = 0;
    int i, l;

    // pick up our info from the dialog
    pInfo = (PLEVELSTREAMDATA) GetWindowLong(hDlg, DWL_USER);
    if ((pInfo == NULL) || (pInfo->hwav != hwav)) {
        return FALSE;
    }

    // go through all samples in buffer looking for maximum absolute level
    while (n < pInfo->buffersize) {

        /*
         * volumes go above and below the mean level - we are
         * interested in the absolute volume
         * 8 bit samples are in the range 0..255
         * 16-bit samples are in the range -32768..+32767
         */

        // skip the first byte if 16-bit
        // and adjust to be in range -127..+128
        if (pInfo->pwf->wBitsPerSample == 16) {
            n++;
            i = (int) (signed char) pHdr->lpData[n];
        } else {
            i = (int) ((unsigned char) pHdr->lpData[n]) - 128;
        }

        // skip past the byte we've picked up
        n++;

        // take absolute volume level
        if (i < 0) {
            i = -i;
        }

        // convert to percentage
        l = (i*100) / 128;

        // compare against current max
        if (LevelLeft < l) {
            LevelLeft = l;
        }

        // if stereo, repeat for right channel
        if (pInfo->pwf->wf.nChannels == 2) {
            // skip the first byte if 16-bit
            if (pInfo->pwf->wBitsPerSample == 16) {
                n++;
                i = (int) (signed char) pHdr->lpData[n];
            } else {
                i = (int) ((unsigned char) pHdr->lpData[n]) - 128;
            }

            // skip past the byte we've picked up
            n++;

            // take absolute volume level
            if (i < 0) {
                i = -i;
            }

            // convert to percentage
            l = (i*100) / 128;

            // compare against current max
            if (LevelRight < l) {
                LevelRight = l;
            }
        }
    }

    // put the buffer back on the queue
    if (waveInAddBuffer(pInfo->hwav, pHdr, sizeof(WAVEHDR))) {
        return(FALSE);
    }

    // send new level to dialog control
    SendDlgItemMessage(hDlg, IDRL_LEVEL1, WMRL_SETLEVEL, 0, LevelLeft);
    if (pInfo->pwf->wf.nChannels == 2) {
        SendDlgItemMessage(hDlg, IDRL_LEVEL2, WMRL_SETLEVEL, 0, LevelRight);
    }

    return(TRUE);
}

#endif  // ! USE_ACM

// --- dialog procs -----------------------------------------------------

//
// AboutProc: About Dialog Box Procedure
//
int FAR PASCAL AboutProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    switch (Message) {
        case WM_INITDIALOG :
             return TRUE ;

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK :
                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;
}

#ifndef USE_ACM

/*
 * dialog proc for IDD_RECLVLMONO and IDD_RECLVLSTEREO - show current
 * volume level
 */
int FAR PASCAL
ShowLevelProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message) {

    case WM_INITDIALOG:
        if (!OpenStream(hDlg, (PCMWAVEFORMAT FAR *) lParam)) {
            MessageBoxID(IDS_ERR_ACCESS_SOUNDDRIVER, MB_OK|MB_ICONSTOP);
            EndDialog(hDlg, FALSE);
        }
        return(TRUE);

    case WM_COMMAND:
        switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
        case IDCANCEL:

            CloseStream(hDlg);
            EndDialog(hDlg, TRUE);
            return(TRUE);
        }
        break;

    case MM_WIM_DATA:
        if (!StreamData(hDlg, (HWAVEIN)wParam, (LPWAVEHDR)lParam)) {
            MessageBoxID(IDS_ERR_ACCESS_SOUNDDRIVER, MB_OK|MB_ICONSTOP);
            CloseStream(hDlg);
            EndDialog(hDlg, FALSE);
        }
        return(TRUE);

    }
    return FALSE;
}

//
// AudioFormatProc: Audio Format Setting Dialog Box Procedure
//
int FAR PASCAL AudioFormatProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int                nChannels ;
    static WORD               wSample ;
    static DWORD              dwFrequency ;

    switch (Message) {
        case WM_INITDIALOG :
            nChannels = IDD_ChannelIDs + glpwfex->nChannels ;
            CheckRadioButton(hDlg, IDD_ChannelMono, IDD_ChannelStereo, nChannels) ;
            wSample = IDD_SampleIDs + glpwfex->wBitsPerSample / 8 ;
            CheckRadioButton(hDlg, IDD_Sample8Bit, IDD_Sample16Bit, wSample) ;
            dwFrequency = IDD_FreqIDs + glpwfex->nSamplesPerSec / 11025 ;
            CheckRadioButton(hDlg, IDD_Freq11kHz, IDD_Freq44kHz, (WORD)dwFrequency) ;
            return TRUE ;

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDD_SetLevel:
                {
                    // get the current data into a PCMWAVEFORMAT struct,
                    // and run the ShowLevel dialog
                    PCMWAVEFORMAT wf;
                    UINT dlgid;

                    if (IsDlgButtonChecked(hDlg, IDD_ChannelMono)) {
                        wf.wf.nChannels = 1;
                        dlgid = IDD_RECLVLMONO;
                    } else {
                        wf.wf.nChannels = 2;
                        dlgid = IDD_RECLVLSTEREO;
                    }

                    if (IsDlgButtonChecked(hDlg, IDD_Sample8Bit)) {
                        wf.wBitsPerSample = 8;
                    } else {
                        wf.wBitsPerSample = 16;
                    }

                    if (IsDlgButtonChecked(hDlg, IDD_Freq11kHz)) {
                        wf.wf.nSamplesPerSec = 11025 ;
                    } else if (IsDlgButtonChecked(hDlg, IDD_Freq22kHz)) {
                        wf.wf.nSamplesPerSec = 22050 ;
                    } else {
                        wf.wf.nSamplesPerSec =  44100 ;
                    }

                    DoDialog(
                        hDlg,
                        dlgid,
                        ShowLevelProc,
                        (LPARAM) &wf);
                    break;
                }

                case IDOK :
                    if (IsDlgButtonChecked(hDlg, IDD_ChannelMono))
                        nChannels = 1 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_ChannelStereo))
                            nChannels = 2 ;
                        else {
                            MessageBeep(MB_ICONEXCLAMATION) ;
                            return FALSE ;
                        }

                    if (IsDlgButtonChecked(hDlg, IDD_Sample8Bit))
                        wSample = 8 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_Sample16Bit))
                            wSample = 16 ;
                        else {
                            MessageBeep(MB_ICONEXCLAMATION) ;
                            return FALSE ;
                        }

                    if (IsDlgButtonChecked(hDlg, IDD_Freq11kHz))
                        dwFrequency = 11025 ;
                    else
                        if (IsDlgButtonChecked(hDlg, IDD_Freq22kHz))
                            dwFrequency = 22050 ;
                        else
                            if (IsDlgButtonChecked(hDlg, IDD_Freq44kHz))
                                dwFrequency = 44100 ;
                            else {
                                MessageBeep(MB_ICONEXCLAMATION) ;
                                return FALSE ;
                            }

                    // All the entries verfied OK -- save them now
                    glpwfex->nChannels = nChannels ;
                    glpwfex->wBitsPerSample = wSample ;
                    glpwfex->nSamplesPerSec = dwFrequency ;
                    glpwfex->nBlockAlign =  glpwfex->nChannels * (glpwfex->wBitsPerSample / 8) ;
                    glpwfex->nAvgBytesPerSec = (long) glpwfex->nSamplesPerSec *
                                                      glpwfex->nBlockAlign ;
                    glpwfex->cbSize = 0 ;
                    glpwfex->wFormatTag = WAVE_FORMAT_PCM ;
                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;
}

#endif // ! USE_ACM

//
// AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure
//
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int      nFreeMBs = 0 ;

    switch (Message) {
        case WM_INITDIALOG :
        {
            int              fh ;
            long             lFileSize = 0 ;
            long             lFreeSpaceInKB ;
            TCHAR     achCapFile[_MAX_PATH] ;

            // Get current capture file name and measure its size
            capFileGetCaptureFile(ghWndCap, achCapFile, sizeof(achCapFile) / sizeof(TCHAR)) ;
            if ((fh = _open(achCapFile, _O_RDONLY)) != -1) {
                if ((lFileSize = _lseek(fh, 0L, SEEK_END)) == -1L) {
                    MessageBoxID(IDS_ERR_SIZECAPFILE,
                    MB_OK | MB_ICONEXCLAMATION) ;
                    lFileSize = 0 ;
                }
                _close(fh) ;
            }

            // Get free disk space and add current capture file size to that.

// Convert the available space to MBs.
            if ((lFreeSpaceInKB = GetFreeDiskSpaceInKB(achCapFile)) != -1L) {
                lFreeSpaceInKB += lFileSize / 1024 ;
                nFreeMBs = lFreeSpaceInKB / 1024 ;
                SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ;
            } else {

                EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE);

            }

            gwCapFileSize = (WORD) (lFileSize / ONEMEG);

            SetDlgItemInt(hDlg, IDD_SetCapFileSize, gwCapFileSize, TRUE) ;
            return TRUE ;
        }

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK :
                {
                    int         iCapFileSize ;

                    iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ;
                    if (iCapFileSize <= 0 || iCapFileSize > nFreeMBs) {
                        // You are asking for more than we have !! Sorry, ...
                        SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    gwCapFileSize = iCapFileSize ;

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;

                case IDD_SetCapFileSize:
                {
                    long l;
                    BOOL bchanged;
                    char achBuffer[21];

                    // check that entered size is a valid number
                    GetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer, sizeof(achBuffer));
                    l = atol(achBuffer);
                    bchanged = FALSE;
                    if (l < 1) {
                        l = 1;
                        bchanged = TRUE;
                    } else if (l > nFreeMBs) {
                        l = nFreeMBs;
                        bchanged = TRUE;
                    } else {
                        // make sure there are no non-digit chars
                        // atol() will ignore trailing non-digit characters
                        int c = 0;
                        while (achBuffer[c]) {
                            if (IsCharAlpha(achBuffer[c]) ||
                                !IsCharAlphaNumeric(achBuffer[c])) {

                                // string contains non-digit chars - reset
                                l = 1;
                                bchanged = TRUE;
                                break;
                            }
                            c++;
                        }
                    }
                    if (bchanged) {
                        wsprintf(achBuffer, "%ld", l);
                        SetDlgItemText(hDlg, IDD_SetCapFileSize, achBuffer);
                    }
                    break;
                }
            }
            break ;
    }

    return FALSE ;

}

#if 0
//
// MakePaletteProc: Palette Details Dialog Box Procedure
//
BOOL CALLBACK MakePaletteProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    switch (Message) {
        case WM_INITDIALOG :
            SetDlgItemInt(hDlg, IDD_MakePalColors, gwPalColors, FALSE) ;
            SetDlgItemInt(hDlg, IDD_MakePalFrames, gwPalFrames, FALSE) ;
            return TRUE ;

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK :
                {
                    int         iColors ;
                    int         iFrames ;

                    iColors = (int) GetDlgItemInt(hDlg, IDD_MakePalColors, NULL, TRUE) ;
                    if (! (iColors > 0 && iColors <= 236 || iColors == 256)) {
                        // invalid number of palette colors
                        SetDlgItemInt(hDlg, IDD_MakePalColors, iColors, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_MakePalColors)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    iFrames = (int) GetDlgItemInt(hDlg, IDD_MakePalFrames, NULL, TRUE) ;
                    if (iFrames <= 0 || iFrames > 10000) {
                        // no frame or way t-o-o many frames !!!
                        SetDlgItemInt(hDlg, IDD_MakePalFrames, iFrames, TRUE) ;
                        SetFocus(GetDlgItem(hDlg, IDD_MakePalFrames)) ;
                        MessageBeep(MB_ICONEXCLAMATION) ;
                        return FALSE ;
                    }
                    gwPalColors = iColors ;
                    gwPalFrames = iFrames ;

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;
    }

    return FALSE ;

}

#endif

#define CAPPAL_TIMER    902
#define CAPTIMER_DELAY  100       // get timers as fast as possible
//
// MakePaletteProc: Palette Details Dialog Box Procedure
//
static int      siNumColors = 256;

BOOL CALLBACK MakePaletteProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    static UINT shTimer;
    static int  siNumFrames;
    UINT        w;
    char        ach[40];
    char        achFormat[40];
    int         i, k;

    switch(msg) {
        case WM_INITDIALOG:
            siNumFrames = 0;
            SetDlgItemInt(hwnd, IDD_MakePalColors, siNumColors, FALSE);
            SmartWindowPosition (hwnd, ghWndCap);
            return TRUE;
            break;

        case WM_VSCROLL:
            /* now handle the scroll */
            i = GetDlgItemInt(hwnd, IDD_MakePalColors, NULL, FALSE);
            ArrowEditChange(GetDlgItem(hwnd, IDD_MakePalColors),
                GET_WM_VSCROLL_CODE(wParam, lParam), 2, 256);
            k = GetDlgItemInt(hwnd, IDD_MakePalColors, NULL, FALSE);
            // Jump over the range 237 to 255
            if (k > 236 && k < 256) {
                if (k > i)
                   w = 256;
                else
                   w = 236;
                SetDlgItemInt (hwnd, IDD_MakePalColors, w, TRUE);
            }
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDCANCEL:
                    if (siNumFrames) {
                        // The following finishes building the new palette
                        capPaletteManual (ghWndCap, FALSE, siNumColors);
                    }

                    if (shTimer){
                        KillTimer(hwnd, CAPPAL_TIMER);
                        shTimer = 0;
                    }
                    siNumColors = GetDlgItemInt(hwnd, IDD_MakePalColors, (BOOL FAR *)ach, FALSE);
                    siNumColors = max (2, min (256, siNumColors));
                    EndDialog(hwnd, siNumFrames);
                    break;

                case IDD_MakePalStart:
                    /* see if we are in START or STOP mode at   */
                    /* this time and handle each one.           */
                    SetFocus (GetDlgItem (hwnd, IDD_MakePalStart));
                    if (!siNumFrames){
                        /* this is the first frame, change the CANCEL */
                        /* button to CLOSE                              */
                        LoadString(ghInstApp, IDS_CAPPAL_CLOSE, ach, sizeof(ach));
                        SetDlgItemText(hwnd, IDCANCEL, ach);
                    }
                    if (!shTimer) {

                        shTimer = SetTimer(hwnd, CAPPAL_TIMER, CAPTIMER_DELAY, NULL);

                        if (shTimer == 0) {
                            //!!!error message here.
                            MessageBeep(0);
                            return TRUE;
                        }

                        /* button said START, let's set up to   */
                        /* do continuous capture.  This involves*/
                        /*   1 - disabling FRAME button         */
                        /*   2 - turning myself to STOP button  */
                        /*   3 - setting up frame timer         */
                        EnableWindow(GetDlgItem(hwnd, IDD_MakePalSingleFrame), FALSE);
                        LoadString(ghInstApp, IDS_CAPPAL_STOP, ach, sizeof(ach));
                        SetDlgItemText(hwnd, IDD_MakePalStart, ach);
                    } else {
                        /* button said STOP, turn things around */
                        /* by:                                  */
                        /*   1 - killing off timers             *
                        /*   2 - turning back into START button */
                        /*   3 - re-enabling FRAME button       */
                        // "&Start"
                        LoadString(ghInstApp, IDS_CAPPAL_START, ach, sizeof(ach));
                        SetDlgItemText(hwnd, IDD_MakePalStart, ach);
                        EnableWindow(GetDlgItem(hwnd, IDD_MakePalSingleFrame), TRUE);
                        KillTimer(hwnd, CAPPAL_TIMER);
                        shTimer = 0;
                    }
                    return TRUE;
                    break;

                case IDD_MakePalSingleFrame:
                    if (!siNumFrames){
                        /* this is the first frame, change the CANCEL */
                        /* button to CLOSE                              */
                        LoadString(ghInstApp, IDS_CAPPAL_CLOSE, ach, sizeof(ach));
                        SetDlgItemText(hwnd, IDCANCEL, ach);
                        siNumColors = GetDlgItemInt(hwnd, IDD_MakePalColors, (BOOL FAR *)ach, FALSE);
                        siNumColors = max (2, min (256, siNumColors));
                    }
                    // Get the palette for a single frame
                    capPaletteManual (ghWndCap, TRUE, siNumColors);

                    siNumFrames++;
                    LoadString(ghInstApp, IDS_CAPPAL_STATUS, achFormat, sizeof(achFormat));
                    wsprintf(ach, achFormat, siNumFrames);
                    SetDlgItemText(hwnd, IDD_MakePalNumFrames, ach);
                    return TRUE;
                    break;

                case IDD_MakePalColors:
                    if (HIWORD (lParam) == EN_KILLFOCUS) {
                        w = GetDlgItemInt (hwnd, wParam, NULL, FALSE);
                        if ( w < 2) {
                            MessageBeep (0);
                            SetDlgItemInt (hwnd, wParam, 2, FALSE);
                        }
                        else if (w > 256) {
                            MessageBeep (0);
                            SetDlgItemInt (hwnd, wParam, 256, FALSE);
                        }
                    }
                    return TRUE;
                    break;

                default:
                    return FALSE;

            } // switch(wParam) on WM_COMMAND
            break;

        case WM_TIMER:
            if (wParam == CAPPAL_TIMER){
                SendMessage(hwnd, WM_COMMAND, IDD_MakePalSingleFrame, 0L);
            }
            break;
        default:
            return FALSE;

    } // switch(msg)
    return FALSE;
}

//
// CapSetUpProc: Capture SetUp Details Dialog Box Procedure
//
int FAR PASCAL CapSetUpProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static char     achBuffer[21] ;
    UINT fValue;

    switch (Message) {
        case WM_INITDIALOG :
        {

            // Convert from MicroSecPerFrame to FPS -- that's easier !!
            MicroSecToStringRate(achBuffer, gCapParms.dwRequestMicroSecPerFrame);
            SetDlgItemText(hDlg, IDD_FrameRateData, achBuffer);

            // If time limit isn't enabled, disable the time data part
            CheckDlgButton(hDlg, IDD_TimeLimitFlag, (fValue = gCapParms.fLimitEnabled)) ;
            EnableWindow(GetDlgItem(hDlg, IDD_SecondsText), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_SecondsData), fValue) ;
            EnableWindow(GetDlgItem(hDlg, IDD_SecondsArrow), fValue);

            SetDlgItemInt(hDlg, IDD_SecondsData, gCapParms.wTimeLimit, FALSE) ;

            // disable audio buttons if no audio hardware
            {
                CAPSTATUS cs;

                capGetStatus(ghWndCap, &cs, sizeof(cs));
                EnableWindow(GetDlgItem(hDlg, IDD_CapAudioFlag), cs.fAudioHardware);
                EnableWindow(GetDlgItem(hDlg, IDD_AudioConfig), cs.fAudioHardware);

                CheckDlgButton(hDlg, IDD_CapAudioFlag, gCapParms.fCaptureAudio);
            }

            /*
             * Capture To Memory means allocate as many memory buffers
             *  as possible.
             * Capture To Disk means only allocate enough buffers
             *  to get us through disk seeks and thermal recalibrations.
             */

            // The use of fUsingDOSMemory is now just a means of keeping
            // track of whether using lots of buffers.  We never actually
            // allocate exclusively from memory under 1Meg.

            CheckRadioButton(hDlg, IDD_CaptureToDisk, IDD_CaptureToMemory,
              (gCapParms.fUsingDOSMemory)? IDD_CaptureToDisk : IDD_CaptureToMemory);

            // Find out how many MCI devices can source video
            if (CountMCIDevices(MCI_DEVTYPE_VCR) +
                CountMCIDevices(MCI_DEVTYPE_VIDEODISC) == 0) {
                // if no VCRs or Videodiscs, disable the controls
                EnableWindow(GetDlgItem(hDlg, IDD_MCIControlFlag), FALSE);
                EnableWindow(GetDlgItem(hDlg, IDD_MCISetup), FALSE);
            } else {
                EnableWindow(GetDlgItem(hDlg, IDD_MCIControlFlag), TRUE);

                // if MCI Control is selected, enable the setup button
                CheckDlgButton(hDlg, IDD_MCIControlFlag,
                    gCapParms.fMCIControl);
                EnableWindow(GetDlgItem(hDlg, IDD_MCISetup), gCapParms.fMCIControl);
            }

            // place the dialog to avoid covering the capture window
            SmartWindowPosition(hDlg, ghWndCap);
            return TRUE ;
        }

        case WM_COMMAND :
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDD_TimeLimitFlag :
                    // If this flag changes, en/dis-able time limit data part
                    fValue = IsDlgButtonChecked(hDlg, IDD_TimeLimitFlag) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_SecondsText), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_SecondsData), fValue) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_SecondsArrow), fValue);
                    return TRUE ;

                case IDD_MCIControlFlag :
                    // If this flag changes, en/dis-able MCI Setup button
                    fValue = IsDlgButtonChecked(hDlg, IDD_MCIControlFlag) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_MCISetup), fValue) ;
                    return TRUE ;

                case IDD_CapAudioFlag:
                    fValue = IsDlgButtonChecked(hDlg, IDD_CapAudioFlag) ;
                    EnableWindow(GetDlgItem(hDlg, IDD_AudioConfig), fValue) ;
                    return TRUE ;

                case IDD_FrameRateData:
                    // get the requested frame rate and check it against bounds
                    if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_KILLFOCUS) {
                        long l, new_l;

                        GetDlgItemText(hDlg, IDD_FrameRateData, achBuffer, sizeof(achBuffer));
                        new_l = l = StringRateToMicroSec(achBuffer);

                        // note that the MAX rate is SMALL! hence <max, >min
                        if (l == 0) {
                            new_l = DEF_CAPTURE_RATE;
                        } else if (l < MAX_CAPTURE_RATE) {
                            new_l = MAX_CAPTURE_RATE;
                        } else if (l > MIN_CAPTURE_RATE) {
                            new_l = MIN_CAPTURE_RATE;
                        }
                        if (l != new_l) {
                            MicroSecToStringRate(achBuffer, new_l);
                            SetDlgItemText(hDlg, IDD_FrameRateData, achBuffer);
                        }
                    }
                    break;

                case IDD_SecondsData:
                {
                    long l, new_l;

                    // get requested time limit and check validity
                    GetDlgItemText(hDlg, IDD_SecondsData, achBuffer, sizeof(achBuffer));
                    new_l = l = atol(achBuffer);
                    if (l < 1) {
                        new_l = 1;
                    } else if (l > 9999) {
                        new_l = 9999;
                    } else {
                        // make sure there are no non-digit chars
                        // atol() will ignore trailing non-digit characters
                        int c = 0;
                        while (achBuffer[c]) {
                            if (IsCharAlpha(achBuffer[c]) ||
                                !IsCharAlphaNumeric(achBuffer[c])) {

                                // string contains non-digit chars - reset
                                new_l = 1;
                                break;
                            }
                            c++;
                        }
                    }
                    if (new_l != l) {
                        wsprintf(achBuffer, "%ld", new_l);
                        SetDlgItemText(hDlg, IDD_SecondsData, achBuffer);
                    }
                    break;
                }

                // show audio format setup dialog
                case IDD_AudioConfig:

                    // rather than duplicate lots of code from the
                    // main vidcap winproc, lets just ask it to show the dlg...
                    SendMessage(ghWndMain, WM_COMMAND,
                            GET_WM_COMMAND_MPS(IDM_O_AUDIOFORMAT, NULL, 0));

                    break;

                // show MCI step control dialog
                case IDD_MCISetup:
                    DoDialog(hDlg, IDD_MCISETUP, MCISetupProc, 0);
                    break;

                // show video format setup dialog
                case IDD_VideoConfig:
                    // rather than duplicate lots of code from the
                    // main vidcap winproc, lets just ask it to show the dlg...
                    SendMessage(ghWndMain, WM_COMMAND,
                            GET_WM_COMMAND_MPS(IDM_O_VIDEOFORMAT, NULL, 0));
                    break;

                // show the compressor selector dialog
                case IDD_CompConfig:
                    capDlgVideoCompression(ghWndCap);
                    break;

                case IDOK :
                {

                    gCapParms.fCaptureAudio =
                                IsDlgButtonChecked(hDlg, IDD_CapAudioFlag) ;
                    gCapParms.fMCIControl =
                            IsDlgButtonChecked(hDlg, IDD_MCIControlFlag);
                    gCapParms.fLimitEnabled = IsDlgButtonChecked(hDlg, IDD_TimeLimitFlag) ;

                    GetDlgItemText(hDlg, IDD_FrameRateData, achBuffer, sizeof(achBuffer));
                    gCapParms.dwRequestMicroSecPerFrame = StringRateToMicroSec(achBuffer);
                    if (gCapParms.dwRequestMicroSecPerFrame == 0) {
                        gCapParms.dwRequestMicroSecPerFrame = DEF_CAPTURE_RATE;
                    }

                    GetDlgItemText(hDlg, IDD_SecondsData, achBuffer, sizeof(achBuffer));
                    if (gCapParms.fLimitEnabled) {
                         gCapParms.wTimeLimit  = (UINT) atol(achBuffer);
                    }

                    // fUsingDOSMemory is archaic and is now just a flag reflecting
                    // the "CaptureToDisk" selection.
                    //
                    gCapParms.fUsingDOSMemory =
                                IsDlgButtonChecked(hDlg, IDD_CaptureToDisk);

                    EndDialog(hDlg, TRUE) ;
                    return TRUE ;
                }

                case IDCANCEL :
                    EndDialog(hDlg, FALSE) ;
                    return TRUE ;
            }
            break ;

        case WM_VSCROLL:
        // message from one of the arrow spinbuttons
        {
            UINT id;

            id = GetDlgCtrlID(GET_WM_COMMAND_HWND(wParam, lParam));
            if (id == IDD_FrameRateArrow) {
                // format n.nnn
                MilliSecVarArrowEditChange(
                    GetDlgItem(hDlg, IDD_FrameRateData),
                    GET_WM_VSCROLL_CODE(wParam, lParam),
                    1, 100, 1);
            } else {
                // simple integer format
                ArrowEditChange(
                    GetDlgItem(hDlg, IDD_SecondsData),
                    GET_WM_VSCROLL_CODE(wParam, lParam),
                    1, 30000);
            }
            break;
        }

    }

    return FALSE ;
}

/*
 * preferences dialog - sets global options about background colour,
 * presence of toolbar, status bar etc
 */
int FAR PASCAL
PrefsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    DWORD indexsz;

    switch(message) {

    case WM_INITDIALOG:
        CheckDlgButton(hDlg, IDD_PrefsStatus, gbStatusBar);
        CheckDlgButton(hDlg, IDD_PrefsToolbar, gbToolBar);
        CheckDlgButton(hDlg, IDD_PrefsCentre, gbCentre);
        CheckDlgButton(hDlg, IDD_PrefsSizeFrame, gbAutoSizeFrame);
        CheckRadioButton(hDlg, IDD_PrefsDefBackground, IDD_PrefsBlack, gBackColour);

        CheckRadioButton(hDlg, IDD_PrefsSmallIndex, IDD_PrefsBigIndex,
                    (gCapParms.dwIndexSize == CAP_LARGE_INDEX) ?
                    IDD_PrefsBigIndex : IDD_PrefsSmallIndex);

        CheckRadioButton(hDlg, IDD_PrefsMasterAudio, IDD_PrefsMasterNone,
                    gCapParms.AVStreamMaster + IDD_PrefsMasterAudio);

        return(TRUE);

    case WM_COMMAND:
        switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDCANCEL:
            EndDialog(hDlg, FALSE);
            return(TRUE);

        case IDOK:
            gbStatusBar = IsDlgButtonChecked(hDlg, IDD_PrefsStatus);
            gbToolBar = IsDlgButtonChecked(hDlg, IDD_PrefsToolbar);
            gbCentre = IsDlgButtonChecked(hDlg, IDD_PrefsCentre);
            gbAutoSizeFrame = IsDlgButtonChecked(hDlg, IDD_PrefsSizeFrame);

            if (IsDlgButtonChecked(hDlg, IDD_PrefsDefBackground)) {
                gBackColour = IDD_PrefsDefBackground;
            } else if (IsDlgButtonChecked(hDlg, IDD_PrefsLtGrey)) {
                gBackColour = IDD_PrefsLtGrey;
            } else if (IsDlgButtonChecked(hDlg, IDD_PrefsDkGrey)) {
                gBackColour = IDD_PrefsDkGrey;
            } else {
                gBackColour = IDD_PrefsBlack;
            }

            if (IsDlgButtonChecked(hDlg, IDD_PrefsSmallIndex)) {
                indexsz = CAP_SMALL_INDEX;

            } else {
                indexsz = CAP_LARGE_INDEX;
            }
            if (indexsz != gCapParms.dwIndexSize) {
                gCapParms.dwIndexSize = indexsz;
            }

            if (IsDlgButtonChecked(hDlg, IDD_PrefsMasterAudio)) {
                gCapParms.AVStreamMaster = AVSTREAMMASTER_AUDIO;
            }
            else {
                gCapParms.AVStreamMaster = AVSTREAMMASTER_NONE;
            }

            EndDialog(hDlg, TRUE);
            return(TRUE);
        }
        break;
    }
    return FALSE;
}

int FAR PASCAL
NoHardwareDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HBRUSH hbr;

    switch(message) {
    case WM_INITDIALOG:
        // lParam contains the argument to DialogBoxParam which is the
        // reason text
        SetDlgItemText(hDlg, IDD_FailReason, (LPSTR) lParam);

        hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        return TRUE;

    case WM_DESTROY:
        DeleteObject(hbr);

#ifdef _WIN32
    case WM_CTLCOLORSTATIC:
#else
    case WM_CTLCOLOR:
#endif
        if (GET_WM_CTLCOLOR_HWND(wParam, lParam, message) == GetDlgItem(hDlg, IDD_FailReason)) {

            HDC hdc;

            hdc = GET_WM_CTLCOLOR_HDC(wParam, lParam, message);

            SetTextColor(hdc, RGB(0xff, 0, 0));
            SetBkColor(hdc, GetSysColor(COLOR_WINDOW));

            // in order to ensure that the text colour we have chosen for
            // this control is used, we need to actually return a brush.
            // for win31, we also need to align the brush
#ifndef _WIN32
            {
                POINT pt;

                pt.x = 0;
                pt.y = 0;
                ClientToScreen(hDlg, &pt);
                UnrealizeObject(hbr);
                SetBrushOrg(hdc, pt.x, pt.y);
            }
#endif

            return((int) hbr);

        }
        break;

    case WM_COMMAND:
        switch(GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
            EndDialog(hDlg, TRUE);
            return(TRUE);
        case IDCANCEL:
            EndDialog(hDlg, FALSE);
            return(TRUE);
        }
        break;
    }

    return(FALSE);
}

//capture selected single frames
long
FAR PASCAL
CapFramesProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
    char ach[MAX_PATH*2];
    char achName[MAX_PATH];

    static BOOL bFirst;
    static int iFrames;

    switch(Message) {
    case WM_INITDIALOG:

        // write out the prompt message including the capture file name
        capFileGetCaptureFile(ghWndCap, achName, sizeof(achName));
        wsprintf(ach, tmpString(IDS_PROMPT_CAPFRAMES), achName);
        SetDlgItemText(hDlg, IDD_CapMessage, ach);

        bFirst = TRUE;

        //move dialog so it doesn't obscure the capture window
        SmartWindowPosition(hDlg, ghWndCap);

        return(TRUE);

    case WM_COMMAND:
        switch(GET_WM_COMMAND_ID(wParam, lParam)) {

        case IDCANCEL:
            if (!bFirst) {
                capCaptureSingleFrameClose(ghWndCap);
                EndDialog(hDlg, TRUE);
            } else {
                EndDialog(hDlg, FALSE);
            }
            return(TRUE);

        case IDOK:
            if (bFirst) {
                bFirst = FALSE;
                iFrames = 0;
                capCaptureSingleFrameOpen(ghWndCap);

                SetDlgItemText(hDlg, IDCANCEL, tmpString(IDS_CAP_CLOSE));

            }
            capCaptureSingleFrame(ghWndCap);
            iFrames++;

            wsprintf(ach, tmpString(IDS_STATUS_NUMFRAMES), iFrames);
            SetDlgItemText(hDlg, IDD_CapNumFrames, ach);
            return(TRUE);

        }
        break;
    }
    return(FALSE);
}

// enumerate all the MCI devices of a particular type and add them and
// their descriptions to a combo box list.
//
void
AddMCIDeviceNames(WORD wDeviceType, HWND hwndCB)
{
    int   nIndex;
    MCI_OPEN_PARMS mciOp;
    MCI_INFO_PARMS mciIp;
    MCI_SYSINFO_PARMS mciSIP;
    MCI_GENERIC_PARMS mciGp;
    char buf[MAXPNAMELEN + 128]; // Contains eg. Name\t\tVideodisc1
    char buf2 [64];
    int maxdevs;
    DWORD dwRet;

    // To get the user readable names of the devices, we
    // must open all appropriate devices, and then get info.

    // MCI Open structure
    mciOp.dwCallback = 0;
    mciOp.lpstrElementName = NULL;
    mciOp.lpstrAlias = NULL;

    // MCI Info structure
    mciIp.dwCallback = 0;
    mciIp.lpstrReturn = (LPSTR) buf;
    mciIp.dwRetSize = MAXPNAMELEN - 1;

    // MCI SysInfo structure
    mciSIP.dwCallback = 0;
    mciSIP.lpstrReturn = (LPSTR) buf2;
    mciSIP.dwRetSize = sizeof (buf2);
    mciSIP.wDeviceType = wDeviceType;

    // MCI Generic structure
    mciGp.dwCallback = 0;

    // Load the combobox with the product info name, followed by
    // a comma, then a space, and then the mci device name. This allows a
    // single alphabetized list to be kept.

    // eg.
    // Pioneer Laserdisc, videodisc1

    maxdevs = CountMCIDevices((WORD)mciSIP.wDeviceType);
    for (nIndex = 0; nIndex < maxdevs; nIndex++) {

       // Get the system name eg. Videodisc1
       mciSIP.dwNumber = nIndex + 1;
       dwRet = mciSendCommand (0, MCI_SYSINFO,
                    MCI_SYSINFO_NAME,
                    (DWORD) (LPVOID) &mciSIP);

       mciOp.lpstrDeviceType =

(LPSTR) MAKELONG (wDeviceType, nIndex);

       if (!(dwRet = mciSendCommand(0, MCI_OPEN,
                    MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |
                    MCI_OPEN_SHAREABLE,
                    (DWORD) (LPVOID) &mciOp))) {
            if (!(dwRet = mciSendCommand (mciOp.wDeviceID, MCI_INFO,
                            MCI_WAIT | MCI_INFO_PRODUCT,
                            (DWORD) (LPVOID) &mciIp))) {
                lstrcat (buf, ", ");         // append the delimiter
                lstrcat (buf, buf2);         // append the system name
                // Whew, finally put it in the listbox
                SendMessage( hwndCB, CB_ADDSTRING, 0,
                                (LONG)(LPSTR) buf);
            } //endif got INFO
            // Close it now
            mciSendCommand (mciOp.wDeviceID, MCI_CLOSE,
                            MCI_WAIT,
                            (DWORD) (LPVOID) &mciGp);
       } // endif OPEN
    } // endif for all devices of this type
}

//
// dialog proc to select MCI device and parameters, including start,
// stop times.
BOOL FAR PASCAL
MCISetupProc(HWND hwnd, unsigned msg, UINT wParam, LONG lParam)
{
  HWND  hwndCB;
  DWORD dw;
  char buf[MAXPNAMELEN];
  BOOL f;
  int j;
  static int nLastCBIndex = 0;
  static DWORD tdwMCIStartTime;
  static DWORD tdwMCIStopTime;

  switch (msg) {
    case WM_INITDIALOG:

      CheckRadioButton(hwnd, IDD_MCI_PLAY, IDD_MCI_STEP,
    gCapParms.fStepMCIDevice ?
                            IDD_MCI_STEP : IDD_MCI_PLAY );

        // enable averaging options only in step mode
        EnableWindow (GetDlgItem (hwnd, IDD_MCI_AVERAGE_2X), gCapParms.fStepMCIDevice);
        EnableWindow (GetDlgItem (hwnd, IDD_MCI_AVERAGE_FR), gCapParms.fStepMCIDevice);
SetDlgItemInt(hwnd, IDD_MCI_AVERAGE_FR, gCapParms.wStepCaptureAverageFrames, FALSE);
        CheckDlgButton (hwnd, IDD_MCI_AVERAGE_2X, gCapParms.fStepCaptureAt2x);

        // save current dialog time settings
        tdwMCIStartTime = gCapParms.dwMCIStartTime;
        tdwMCIStopTime  = gCapParms.dwMCIStopTime;

        TimeMSToHMSString (gCapParms.dwMCIStartTime, buf);
        SetDlgItemText (hwnd, IDD_MCI_STARTTIME, buf);
        TimeMSToHMSString (gCapParms.dwMCIStopTime, buf);
        SetDlgItemText (hwnd, IDD_MCI_STOPTIME, buf);

        // fill combo box with list of MCI devices
hwndCB = GetDlgItem( hwnd, IDD_MCI_SOURCE );
        AddMCIDeviceNames(MCI_DEVTYPE_VIDEODISC, hwndCB);
        AddMCIDeviceNames(MCI_DEVTYPE_VCR, hwndCB);

        // set the selection to whatever he chose last time through this dlg
        // default is the first entry.
       SendMessage( hwndCB, CB_SETCURSEL, nLastCBIndex, 0L);
break;

    case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
    case IDOK:
                // i think the point of this is to ensure that
                // the KILLFOCUS processing for the edit boxes has been done
                // and thus the temp times are the same as the dialog text
                SetFocus(GET_WM_COMMAND_HWND(wParam, lParam));

                MCIGetDeviceNameAndIndex (hwnd, &nLastCBIndex, gachMCIDeviceName);
                capSetMCIDeviceName(ghWndCap, gachMCIDeviceName) ;
                gCapParms.fStepMCIDevice = IsDlgButtonChecked (hwnd, IDD_MCI_STEP);

                // pick up the temp times - these were set on KILLFOCUS msgs
                // (when we did validation and string->dword conversion
                gCapParms.dwMCIStartTime = tdwMCIStartTime;
                gCapParms.dwMCIStopTime  = tdwMCIStopTime;

                gCapParms.fStepCaptureAt2x = IsDlgButtonChecked (hwnd, IDD_MCI_AVERAGE_2X);
                gCapParms.wStepCaptureAverageFrames = GetDlgItemInt (hwnd, IDD_MCI_AVERAGE_FR, NULL, FALSE);

EndDialog(hwnd, TRUE);
break;

    case IDCANCEL:
EndDialog(hwnd, 0);
break;

            case IDD_MCI_STEP:
            case IDD_MCI_PLAY:
                //averaging only enabled in play mode
                f = IsDlgButtonChecked (hwnd, IDD_MCI_STEP);
                EnableWindow (GetDlgItem (hwnd, IDD_MCI_AVERAGE_2X), f);
                EnableWindow (GetDlgItem (hwnd, IDD_MCI_AVERAGE_FR), f);
                break;

            case IDD_MCI_AVERAGE_FR:
                // validate the count of frames to average 1..100
                if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_KILLFOCUS) {
                    j = GetDlgItemInt(hwnd,
                            GET_WM_COMMAND_ID(wParam, lParam), NULL, FALSE);
                    // Limit frames to average between 1 and 100
                    if (j < 1 || j > 100) {
                SetDlgItemInt (hwnd,
                            GET_WM_COMMAND_ID(wParam, lParam), 1, FALSE);
                    }
                }
                break;

            case IDD_MCI_STARTSET:
    case IDD_MCI_STOPSET:
                // set the start or stop time to be the time
                // on the device right now.

                // MCI devices could yield and cause us to re-enter - the
                // simplest answer seems to be to disable the dialog
                EnableWindow(hwnd, FALSE);

                MCIGetDeviceNameAndIndex (hwnd, &nLastCBIndex, buf);

                if (MCIDeviceOpen (buf)) {
                    if (GET_WM_COMMAND_ID(wParam, lParam) == IDD_MCI_STARTSET) {
                        if (MCIDeviceGetPosition (&tdwMCIStartTime)) {
                           TimeMSToHMSString (tdwMCIStartTime, buf);
                           SetDlgItemText (hwnd, IDD_MCI_STARTTIME, buf);
                        }
                        else {
                            MessageBoxID(IDS_MCI_CONTROL_ERROR,
                                        MB_OK|MB_ICONEXCLAMATION);
                        }
                    }
                    else {
                        if (MCIDeviceGetPosition (&tdwMCIStopTime)) {
                            TimeMSToHMSString (tdwMCIStopTime, buf);
                            SetDlgItemText (hwnd, IDD_MCI_STOPTIME, buf);
                        }
                        else {
                            MessageBoxID(IDS_MCI_CONTROL_ERROR,
                                        MB_OK|MB_ICONEXCLAMATION);
                        }
                    }
                    MCIDeviceClose ();

                } else {
                    // cant open device
                    MessageBoxID(IDS_MCI_CONTROL_ERROR,
                                MB_OK|MB_ICONEXCLAMATION);
                }
                EnableWindow(hwnd, TRUE);
                break;

            case IDD_MCI_STARTTIME:
            case IDD_MCI_STOPTIME:
                if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_KILLFOCUS) {
                    GetDlgItemText (hwnd,
                        GET_WM_COMMAND_ID(wParam, lParam), buf, sizeof (buf));
                    if ((dw = TimeHMSStringToMS (buf)) == -1) {
                        // Error in string, reset
                        MessageBeep (0);
                        if (GET_WM_COMMAND_ID(wParam, lParam) == IDD_MCI_STARTTIME)
                            dw = tdwMCIStartTime;
                        else
                            dw = tdwMCIStopTime;
                    }
                    if (GET_WM_COMMAND_ID(wParam, lParam) == IDD_MCI_STARTTIME) {
                        tdwMCIStartTime = dw;
                        TimeMSToHMSString (tdwMCIStartTime, buf);
                        SetDlgItemText (hwnd, IDD_MCI_STARTTIME, buf);
                    }
                    else {
                        tdwMCIStopTime = dw;
                        TimeMSToHMSString (tdwMCIStopTime, buf);
                        SetDlgItemText (hwnd, IDD_MCI_STOPTIME, buf);
                    }
                }
                break;
}
break;

  }
  return FALSE;
}


DIALOGS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   dialogs.h: Dialog box include
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * IDs for the dialogs themselves (as opposed to the controls within them)
 * are also the HELP_CONTEXT ids for the helpfile that we share with the
 * original 16-bit vidcap, and are thus fixed.
 */

#define IDD_HelpAboutBox            300

#define IDD_CapSetUp                    663
#define IDD_FrameRateData           401
#define IDD_FrameRateArrow          402
#define IDD_TimeLimitFlag           403
#define IDD_SecondsText             404
#define IDD_SecondsData             405
#define IDD_SecondsArrow            406
#define IDD_CapAudioFlag            407
#define IDD_AudioConfig             408
#define IDD_CaptureToDisk           409
#define IDD_CaptureToMemory         410
#define IDD_VideoConfig             411
#define IDD_CompConfig              412
#define IDD_MCIControlFlag          413
#define IDD_MCISetup                414

#define IDD_AudioFormat                 658
#define IDD_SampleIDs               420
#define IDD_Sample8Bit              421
#define IDD_Sample16Bit             422
#define IDD_ChannelIDs              423
#define IDD_ChannelMono             424
#define IDD_ChannelStereo           425
#define IDD_FreqIDs                 426
#define IDD_Freq11kHz               427
#define IDD_Freq22kHz               428
#define IDD_Freq44kHz               429
#define IDD_SetLevel                430

#define IDD_AllocCapFileSpace           652
#define IDD_SetCapFileFree          440
#define IDD_SetCapFileSize          441

#define IDD_MakePalette                 664
#define IDD_MakePalColors           450
#define IDD_MakePalStart            451
#define IDD_MakePalSingleFrame      452
#define IDD_MakePalNumFrames        453
#define IDD_MakePalColorArrow       454

#define IDD_NoCapHardware               657
#define IDD_FailReason              460

#define IDD_Prefs                       656
#define IDD_PrefsMasterAudio        465
#define IDD_PrefsMasterNone         466

#define IDD_PrefsSizeFrame          468

#define IDD_PrefsStatus             470
#define IDD_PrefsToolbar            471
#define IDD_PrefsCentre             472
#define IDD_PrefsDefBackground      473
#define IDD_PrefsLtGrey             474
#define IDD_PrefsDkGrey             475
#define IDD_PrefsBlack              476
#define IDD_PrefsSmallIndex         477
#define IDD_PrefsBigIndex           478

#define IDD_CAPFRAMES                   662
#define IDD_CapMessage              480
#define IDD_CapNumFrames            481

#define IDD_MCISETUP                    665
#define IDD_MCI_SOURCE              490
#define IDD_MCI_PLAY                491
#define IDD_MCI_STEP                492
#define IDD_MCI_AVERAGE_2X          493
#define IDD_MCI_AVERAGE_FR          494
#define IDD_MCI_STARTTIME           495
#define IDD_MCI_STOPTIME            496
#define IDD_MCI_STARTSET            497
#define IDD_MCI_STOPSET             498

#define IDD_RECLVLMONO                  667
#define IDD_RECLVLSTEREO                668
#define IDRL_LEVEL1                 510
#define IDRL_LEVEL2                 511

// help context ids for common dialogs (GetOpenFile etc)
#define IDA_LOADPAL                     650
#define IDA_SETCAPFILE                  651
#define IDA_SAVECAPFILE                 653
#define IDA_SAVEPAL                     654
#define IDA_SAVEDIB                     655

// help contexts for dialogs put up by AVICAP
#define IDA_VIDSOURCE                   659
#define IDA_VIDFORMAT                   660
#define IDA_VIDDISPLAY                  661
#define IDA_MCIFRAMES                   662
#define IDA_COMPRESSION                 669


HELP.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   help.c: Help system interface
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * supports F1 key help in app and in dialog by installing a hook,
 *
 * Keep track of which dialog is currently displayed in a global:
 * dialog ids are also topic ids in the help file.
 */

#include <windows.h>
#include <windowsx.h>
#include <string.h>

#include "help.h"

int CurrentDialogID = 0;

// app info passed to helpinit
HINSTANCE hInstance;
char HelpFile[MAX_PATH];
HWND hwndApp;

//hook proc and old msg filter
#ifdef _WIN32
HHOOK hOurHook;
#else
FARPROC fnOldMsgFilter = NULL;
FARPROC fnMsgHook = NULL;
#endif

// call DialogBoxParam, but ensuring correct help processing:
// assumes that each Dialog Box ID is a context number in the help file.
// calls MakeProcInstance as necessary. Uses instance data passed to
// HelpInit().
int
DoDialog(
   HWND hwndParent,     // parent window
   int DialogID,        // dialog resource id
   DLGPROC fnDialog,    // dialog proc
   long lParam          // passed as lparam in WM_INITDIALOG
)
{
    int olddialog;
    DLGPROC fn;
    int result;

    // remember current id (for nested dialogs)
    olddialog = CurrentDialogID;

    // save the current id so the hook proc knows what help to display
    CurrentDialogID = DialogID;

    fn = (DLGPROC) MakeProcInstance(fnDialog, hInstance);
    result = DialogBoxParam(
                hInstance,
                MAKEINTRESOURCE(CurrentDialogID),
                hwndParent,
                fn,
                lParam);
    FreeProcInstance(fn);
    CurrentDialogID = olddialog;

    return result;
}

// set the help context id for a dialog displayed other than by DoDialog
// (eg by GetOpenFileName). Returns the old help context that you must
// restore by a further call to this function
int
SetCurrentHelpContext(int DialogID)
{
    int oldid = CurrentDialogID;
    CurrentDialogID = DialogID;
    return(oldid);
}

// return TRUE if lpMsg is a non-repeat F1 key message
BOOL
IsHelpKey(LPMSG lpMsg)
{
    return lpMsg->message == WM_KEYDOWN &&
               lpMsg->wParam == VK_F1 &&
               !(HIWORD(lpMsg->lParam) & KF_REPEAT) &&
               GetKeyState(VK_SHIFT) >= 0 &&
               GetKeyState(VK_CONTROL) >= 0 &&
               GetKeyState(VK_MENU) >= 0;
}

LRESULT CALLBACK
HelpMsgHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0) {
        if (IsHelpKey((LPMSG)lParam)) {
            if (CurrentDialogID != 0) {
                WinHelp(hwndApp, HelpFile, HELP_CONTEXT, CurrentDialogID);
            } else {
                WinHelp(hwndApp, HelpFile, HELP_CONTENTS, 0);
            }
        }
    }
#ifdef _WIN32
    return CallNextHookEx(hOurHook, nCode, wParam, lParam);
#else
    return DefHookProc(nCode, wParam, lParam, fnOldMsgFilter);
#endif

}

// help init - initialise the support for the F1 key help
BOOL
HelpInit(HINSTANCE hinstance, LPSTR helpfilepath, HWND hwnd)
{
    LPSTR pch;

    // save app details
    hwndApp = hwnd;
    hInstance = hinstance;

    // assume that the help file is in the same directory as the executable-
    // get the executable path, and replace the filename with the help
    // file name.
    GetModuleFileName(hinstance, HelpFile, sizeof(HelpFile));

    // find the final backslash, and append the help file name there
    pch = _fstrrchr(HelpFile, '\\');
    pch++;
    lstrcpy(pch, helpfilepath);

    // install a hook for msgs and save old one
#ifdef _WIN32
    hOurHook = SetWindowsHookEx(
                        WH_MSGFILTER,
                        (HOOKPROC) HelpMsgHook,
                        NULL, GetCurrentThreadId());
#else
    fnMsgHook = (FARPROC) MakeProcInstance(HelpMsgHook, hInstance);
    fnOldMsgFilter = SetWindowsHook(WH_MSGFILTER, (HOOKPROC) fnMsgHook);
#endif

    return(TRUE);
}

// shutdown the help system
void
HelpShutdown(void)
{
#ifdef _WIN32
    UnhookWindowsHookEx(hOurHook);
#else
    if (fnOldMsgFilter) {
        UnhookWindowsHook(WH_MSGFILTER, fnMsgHook);
        FreeProcInstance(fnMsgHook);
    }
#endif

    WinHelp(hwndApp, HelpFile, HELP_QUIT, 0);
}

// start help at the contents page
void
HelpContents(void)
{
    WinHelp(hwndApp, HelpFile, HELP_CONTENTS, 0);
}


HELP.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   help.h: Help system include file
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

// call DialogBoxParam, but ensuring correct help processing:
// assumes that each Dialog Box ID is a context number in the help file.
// calls MakeProcInstance as necessary. Uses instance data passed to
// HelpInit().
int
DoDialog(
   HWND hwndParent,     // parent window
   int DialogID,        // dialog resource id
   DLGPROC fnDialog,    // dialog proc
   long lParam          // passed as lparam in WM_INITDIALOG
);

// set the help context id for a dialog displayed other than by DoDialog
// (eg by GetOpenFileName). Returns the old help context that you must
// restore by a further call to this function
int SetCurrentHelpContext(int DialogID);

// help init - initialise the support for the F1 key help
BOOL HelpInit(HINSTANCE hinstance, LPSTR helpfilepath, HWND hwndApp);

// shutdown the help system
void HelpShutdown(void);

// start help at the contents page
void HelpContents(void);


PROFILE.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   profile.c: Stores profile info in the Registry
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * win32/win16 utility functions to read and write profile items
 * for multimedia tools
 */

#include <windows.h>
#include <windowsx.h>

#ifdef _WIN32
#define KEYNAME     "Software\\Microsoft\\Multimedia Tools\\"
#define ROOTKEY     HKEY_CURRENT_USER
#else
#define INIFILE    "mmtools.ini"
#endif

/*
 * read a BOOL flag from the profile, or return default if
 * not found.
 */
BOOL
mmGetProfileFlag(LPSTR appname, LPSTR valuename, BOOL bDefault)
{
#ifdef _WIN32
    char achName[MAX_PATH];
    HKEY hkey;
    DWORD dwType;
    BOOL bValue = bDefault;
    DWORD dwData;
    int cbData;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegOpenKey(ROOTKEY, achName, &hkey) != ERROR_SUCCESS) {
        return(bDefault);
    }

    cbData = sizeof(dwData);
    if (RegQueryValueEx(
        hkey,
        valuename,
        NULL,
        &dwType,
        (PBYTE) &dwData,
        &cbData) == ERROR_SUCCESS) {
            if (dwType == REG_DWORD) {
                if (dwData) {
                    bValue = TRUE;
                } else {
                    bValue = FALSE;
                }
            }
    }

    RegCloseKey(hkey);

    return(bValue);
#else
    char ach[10];

    GetPrivateProfileString(appname, valuename, "X", ach, sizeof(ach),
            INIFILE);

    switch(ach[0]) {
    case 'N':
    case 'n':
    case '0':
        return(FALSE);

    case 'Y':
    case 'y':
    case '1':
        return(TRUE);

    default:
        return(bDefault);
    }
#endif
}

/*
 * write a boolean value to the registry, if it is not the
 * same as the default or the value already there
 */
VOID
mmWriteProfileFlag(LPSTR appname, LPSTR valuename, BOOL bValue, BOOL bDefault)
{
    if (mmGetProfileFlag(appname, valuename, bDefault) == bValue) {
        return;
    }

#ifdef _WIN32
    {
        char achName[MAX_PATH];
        HKEY hkey;

        lstrcpy(achName, KEYNAME);
        lstrcat(achName, appname);
        if (RegCreateKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {
            RegSetValueEx(
                hkey,
                valuename,
                0,
                REG_DWORD,
                (PBYTE) &bValue,
                sizeof(bValue)
            );

            RegCloseKey(hkey);
        }
    }

#else
    WritePrivateProfileString(
        appname,
        valuename,
        bValue ? "1" : "0",
        INIFILE);
#endif
}

/*
 * read a UINT from the profile, or return default if
 * not found.
 */
UINT
mmGetProfileInt(LPSTR appname, LPSTR valuename, UINT uDefault)
{
#ifdef _WIN32
    char achName[MAX_PATH];
    HKEY hkey;
    DWORD dwType;
    UINT value = uDefault;
    DWORD dwData;
    int cbData;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegOpenKey(ROOTKEY, achName, &hkey) != ERROR_SUCCESS) {
        return(uDefault);
    }

    cbData = sizeof(dwData);
    if (RegQueryValueEx(
        hkey,
        valuename,
        NULL,
        &dwType,
        (PBYTE) &dwData,
        &cbData) == ERROR_SUCCESS) {
            if (dwType == REG_DWORD) {
                value = (UINT)dwData;
            }
    }

    RegCloseKey(hkey);

    return(value);
#else
    return(GetPrivateProfileInt(appname, valuename, uDefault, INIFILE);
#endif
}

/*
 * write a UINT to the profile, if it is not the
 * same as the default or the value already there
 */
VOID
mmWriteProfileInt(LPSTR appname, LPSTR valuename, UINT uValue, UINT uDefault)
{
    if (mmGetProfileInt(appname, valuename, uDefault) == uValue) {
        return;
    }

#ifdef _WIN32
    {
        char achName[MAX_PATH];
        HKEY hkey;
        DWORD dwData = uValue;

        lstrcpy(achName, KEYNAME);
        lstrcat(achName, appname);
        if (RegCreateKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {
            RegSetValueEx(
                hkey,
                valuename,
                0,
                REG_DWORD,
                (PBYTE) &dwData,
                sizeof(dwData)
            );

            RegCloseKey(hkey);
        }
    }

#else
    char ach[12];

    wsprintf(ach, "%d", uValue);

    WritePrivateProfileString(
        appname,
        valuename,
        ach,
        INIFILE);
#endif
}

/*
 * read a string from the profile into pResult.
 * result is number of bytes written into pResult
 */
DWORD
mmGetProfileString(
    LPSTR appname,
    LPSTR valuename,
    LPSTR pDefault,
    LPSTR pResult,
    int cbResult
)
{
#ifdef _WIN32
    char achName[MAX_PATH];
    HKEY hkey;
    DWORD dwType;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegOpenKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {

        if (RegQueryValueEx(
            hkey,
            valuename,
            NULL,
            &dwType,
            pResult,
            &cbResult) == ERROR_SUCCESS) {

                if (dwType == REG_SZ) {
                    // cbResult is set to the size including null
                    RegCloseKey(hkey);
                    return(cbResult - 1);
                }
        }

        RegCloseKey(hkey);
    }

    // if we got here, we didn't find it, or it was the wrong type - return
    // the default string
    lstrcpy(pResult, pDefault);
    return(lstrlen(pDefault));

#else
    return GetPrivateProfileString(
                appname,
                valuename,
                pDefault,
                pResult,
                cbResult
                INIFILE);
#endif
}

/*
 * write a string to the profile
 */
VOID
mmWriteProfileString(LPSTR appname, LPSTR valuename, LPSTR pData)
{
#ifdef _WIN32
    char achName[MAX_PATH];
    HKEY hkey;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegCreateKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {
        RegSetValueEx(
            hkey,
            valuename,
            0,
            REG_SZ,
            pData,
            lstrlen(pData) + 1
        );

        RegCloseKey(hkey);
    }

#else
    WritePrivateProfileString(
        appname,
        valuename,
        pData,
        INIFILE);
#endif
}

/*
 * read binary values from the profile into pResult.
 * result is number of bytes written into pResult
 */
DWORD
mmGetProfileBinary(
    LPSTR appname,
    LPSTR valuename,
    LPVOID pDefault,
    LPVOID pResult,   // if NULL, return the required size
    int cbSize
)
{
    char achName[MAX_PATH];
    HKEY hkey;
    DWORD dwType;
    int cbResult = cbSize;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegOpenKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {

        if (RegQueryValueEx(
            hkey,
            valuename,
            NULL,
            &dwType,
            pResult,
            &cbResult) == ERROR_SUCCESS) {

                if (dwType == REG_BINARY) {
                    // cbResult is the size
                    RegCloseKey(hkey);
                    return(cbResult);
                }
        }

        RegCloseKey(hkey);
    }

    // if we got here, we didn't find it, or it was the wrong type - return
    // the default values (use MoveMemory, since src could equal dst)
    MoveMemory (pResult, pDefault, cbSize);
    return cbSize;

}

/*
 * write binary data to the profile
 */
VOID
mmWriteProfileBinary(LPSTR appname, LPSTR valuename, LPVOID pData, int cbData)
{
    char achName[MAX_PATH];
    HKEY hkey;

    lstrcpy(achName, KEYNAME);
    lstrcat(achName, appname);
    if (RegCreateKey(ROOTKEY, achName, &hkey) == ERROR_SUCCESS) {
        RegSetValueEx(
            hkey,
            valuename,
            0,
            REG_BINARY,
            pData,
            cbData
        );

        RegCloseKey(hkey);
    }
}


PROFILE.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   profile.h: Registry access
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * utility functions to read and write values to the profile,
 * using mmtools.ini for Win16 or current user\software\microsoft\mm tools
 * in the registry for Win32
 */

/*
 * read a BOOL flag from the profile, or return default if
 * not found.
 */
BOOL mmGetProfileFlag(LPSTR appname, LPSTR valuename, BOOL bDefault);

/*
 * write a boolean value to the registry, if it is not the
 * same as the default or the value already there
 */
VOID mmWriteProfileFlag(LPSTR appname, LPSTR valuename, BOOL bValue, BOOL bDefault);

/*
 * read a UINT from the profile, or return default if
 * not found.
 */
UINT mmGetProfileInt(LPSTR appname, LPSTR valuename, UINT uDefault);

/*
 * write a UINT to the profile, if it is not the
 * same as the default or the value already there
 */
VOID mmWriteProfileInt(LPSTR appname, LPSTR valuename, UINT uValue, UINT uDefault);

/*
 * read a string from the profile into pResult.
 * result is number of bytes written into pResult
 */
DWORD
mmGetProfileString(
    LPSTR appname,
    LPSTR valuename,
    LPSTR pDefault,
    LPSTR pResult,
    int cbResult
);

/*
 * write a string to the profile
 */
VOID mmWriteProfileString(LPSTR appname, LPSTR valuename, LPSTR pData);

/*
 * read binary values from the profile into pResult.
 * result is number of bytes written into pResult
 */
DWORD
mmGetProfileBinary(
    LPSTR appname,
    LPSTR valuename,
    LPVOID pDefault,
    LPVOID pResult,   // if NULL, return the required buffer size
    int cbSize);

/*
 * write binary data to the profile
 */
VOID
mmWriteProfileBinary(LPSTR appname, LPSTR valuename, LPVOID pData, int cbData);


ARROW.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   arrow.c: Arrow control window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>

#include <stdlib.h>

#include "arrow.h"

// a few porting macros
#ifdef _WIN32
#define SENDSCROLL(hwnd, msg, a, b, h)           \
        SendMessage(hwnd, msg, (UINT)MAKELONG(a,b), (LONG)(h))

#define EXPORT

#else
#define SENDSCROLL(hwnd, msg, a, b, h)
        SendMessage(hwnd, msg, a, MAKELONG(b,h))   // handle is in HIWORD

#endif

#ifndef LONG2POINT
    #define LONG2POINT(l, pt)               ((pt).x = (SHORT)LOWORD(l), (pt).y = (SHORT)HIWORD(l))
#endif
#define GWID(hwnd)      (GetDlgCtrlID(hwnd))

#define SHIFT_TO_DOUBLE 1
#define DOUBLECLICK     0
#define POINTSPERARROW  3
#define ARROWXAXIS      15
#define ARROWYAXIS      15

POINT ArrowUp[POINTSPERARROW] = {7,1, 3,5, 11,5};
POINT ArrowDown[POINTSPERARROW] = {7,13, 3,9, 11,9};

static    BOOL      bRight;
static    RECT      rUp, rDown;
static    LPRECT    lpUpDown;
static    FARPROC   lpArrowProc;
static    HANDLE    hParent;
BOOL      fInTimer;

#define TEMP_BUFF_SIZE    32

#define SCROLLMSG(hwndTo, msg, code, hwndId)                                     \
                          SENDSCROLL(hwndTo, msg, code, GWID(hwndId), hwndId)

/*
 * @doc EXTERNAL WINCOM
 *
 * @api LONG | ArrowEditChange | This function is helps process the WM_VSCROLL
 * message when using the Arrow controlled edit box.
 * It will increment/decrement the value in the given edit box and return
 * the new value.  Increment/decrement bounds are checked and Beep 0 is produced if
 * the user attempts to go beyond the bounds.
 *
 * @parm        HWND | hwndEdit | Specifies a handle to the edit box window.
 *
 * @parm        UINT | wParam | Specifies the <p wParam> passed to the WM_VSCROLL message.
 *
 * @parm        LONG | lMin | Specifies the minimum value bound for decrements.
 *
 * @parm        LONG | lMax | Specifies the maximum value bound for increments.
 *
 * @rdesc       Returns the updated value of the edit box.
 *
 */
LONG FAR PASCAL ArrowEditChange( HWND hwndEdit, UINT wParam,
                                 LONG lMin, LONG lMax )
{
    char achTemp[TEMP_BUFF_SIZE];
    LONG l;

    GetWindowText( hwndEdit, achTemp, TEMP_BUFF_SIZE );
    l = atol(achTemp);
    if( wParam == SB_LINEUP ) {
        /* size kluge for now */
        if( l < lMax ) {
            l++;
            wsprintf( achTemp, "%ld", l );
            SetWindowText( hwndEdit, achTemp );
        } else {
        MessageBeep( 0 );
        }
    } else if( wParam == SB_LINEDOWN ) {
        if( l > lMin ) {
            l--;
            wsprintf( achTemp, "%ld", l );
            SetWindowText( hwndEdit, achTemp );
        } else {
            MessageBeep( 0 );
        }
    }
    return( l );

}

UINT NEAR PASCAL UpOrDown()
{
    LONG pos;
    UINT retval;
    POINT pt;

    pos = GetMessagePos();
    LONG2POINT(pos,pt);
    if (PtInRect((LPRECT)&rUp, pt))
        retval = SB_LINEUP;
    else if (PtInRect((LPRECT)&rDown, pt))
        retval = SB_LINEDOWN;
    else
        retval = (UINT)(-1);      /* -1, because SB_LINEUP == 0 */

    return(retval);
}

UINT FAR PASCAL ArrowTimerProc(hWnd, wMsg, nID, dwTime)
HANDLE hWnd;
UINT wMsg;
short nID;
DWORD dwTime;
{
    UINT wScroll;

    if ((wScroll = UpOrDown()) != -1)
    {
        if (bRight == WM_RBUTTONDOWN)
            wScroll += SB_PAGEUP - SB_LINEUP;
        SCROLLMSG( hParent, WM_VSCROLL, wScroll, hWnd);
    }
/* Don't need to call KillTimer(), because SetTimer will reset the right one */
    SetTimer(hWnd, nID, 50, (TIMERPROC)lpArrowProc);
    return(0);
}

void InvertArrow(HANDLE hArrow, UINT wScroll)
{
    HDC hDC;

    lpUpDown = (wScroll == SB_LINEUP) ? &rUp : &rDown;
    hDC = GetDC(hArrow);
    ScreenToClient(hArrow, (LPPOINT)&(lpUpDown->left));
    ScreenToClient(hArrow, (LPPOINT)&(lpUpDown->right));
    InvertRect(hDC, lpUpDown);
    ClientToScreen(hArrow, (LPPOINT)&(lpUpDown->left));
    ClientToScreen(hArrow, (LPPOINT)&(lpUpDown->right));
    ReleaseDC(hArrow, hDC);
    ValidateRect(hArrow, lpUpDown);
    return;
}

LONG FAR PASCAL EXPORT ArrowControlProc(HWND hArrow, unsigned message,
                                         WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    RECT        rArrow;
    HBRUSH      hbr;
    short       fUpDownOut;
    UINT        wScroll;

    switch (message) {
/*
        case WM_CREATE:
            break;

        case WM_DESTROY:
            break;
*/

        case WM_MOUSEMOVE:
            if (!bRight)  /* If not captured, don't worry about it */
                break;

            if (lpUpDown == &rUp)
                fUpDownOut = SB_LINEUP;
            else if (lpUpDown == &rDown)
                fUpDownOut = SB_LINEDOWN;
            else
                fUpDownOut = -1;

            switch (wScroll = UpOrDown()) {
                case SB_LINEUP:
                    if (fUpDownOut == SB_LINEDOWN)
                        InvertArrow(hArrow, SB_LINEDOWN);

                    if (fUpDownOut != SB_LINEUP)
                        InvertArrow(hArrow, wScroll);

                    break;

                case SB_LINEDOWN:
                    if (fUpDownOut == SB_LINEUP)
                        InvertArrow(hArrow, SB_LINEUP);

                    if (fUpDownOut != SB_LINEDOWN)
                        InvertArrow(hArrow, wScroll);

                    break;

                default:
                    if (lpUpDown) {
                        InvertArrow(hArrow, fUpDownOut);
                        lpUpDown = 0;
                    }
                }

                break;

        case WM_RBUTTONDOWN:
        case WM_LBUTTONDOWN:
            if (bRight)
                break;

            bRight = message;
            SetCapture(hArrow);
            hParent = GetParent(hArrow);
            GetWindowRect(hArrow, (LPRECT) &rUp);
            CopyRect((LPRECT)&rDown, (LPRECT) &rUp);
            rUp.bottom = (rUp.top + rUp.bottom) / 2;
            rDown.top = rUp.bottom + 1;
            wScroll = UpOrDown();
            InvertArrow(hArrow, wScroll);
#if SHIFT_TO_DOUBLE
            if (wParam & MK_SHIFT) {
                if (message != WM_RBUTTONDOWN)
                    goto ShiftLClick;
                else
                    goto ShiftRClick;
            }
#endif
            if (message == WM_RBUTTONDOWN)
                wScroll += SB_PAGEUP - SB_LINEUP;

            SCROLLMSG(hParent, WM_VSCROLL, wScroll, hArrow);

            lpArrowProc = MakeProcInstance((FARPROC) ArrowTimerProc,ghInst);
            SetTimer(hArrow, GWID(hArrow), 200, (TIMERPROC)lpArrowProc);

            break;

        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
            if ((bRight - WM_LBUTTONDOWN + WM_LBUTTONUP) == (int)message) {
                bRight = 0;
                ReleaseCapture();
                if (lpUpDown)
                    InvertArrow(hArrow,(UINT)(lpUpDown==&rUp)?
                                                        SB_LINEUP:SB_LINEDOWN);
                if (lpArrowProc) {
                    SCROLLMSG(hParent, WM_VSCROLL, SB_ENDSCROLL, hArrow);
                    KillTimer(hArrow, GWID(hArrow));

                    FreeProcInstance(lpArrowProc);
                    ReleaseCapture();
                    lpArrowProc = 0;
                }
            }
            break;

        case WM_LBUTTONDBLCLK:
ShiftLClick:
            wScroll = UpOrDown() + SB_TOP - SB_LINEUP;
            SCROLLMSG(hParent, WM_VSCROLL, wScroll, hArrow);
            SCROLLMSG(hParent, WM_VSCROLL, SB_ENDSCROLL, hArrow);

            break;

        case WM_RBUTTONDBLCLK:
ShiftRClick:
            wScroll = UpOrDown() + SB_THUMBPOSITION - SB_LINEUP;
            SCROLLMSG(hParent, WM_VSCROLL, wScroll, hArrow);
            SCROLLMSG(hParent, WM_VSCROLL, SB_ENDSCROLL, hArrow);
/*
            hDC = GetDC(hArrow);
            InvertRect(hDC, (LPRECT) &rArrow);
            ReleaseDC(hArrow, hDC);
            ValidateRect(hArrow, (LPRECT) &rArrow);
*/
            break;

        case WM_PAINT:
            BeginPaint(hArrow, &ps);
            GetClientRect(hArrow, (LPRECT) &rArrow);
            hbr = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
            FillRect(ps.hdc, (LPRECT)&rArrow, hbr);
            DeleteObject(hbr);
            hbr = SelectObject(ps.hdc, GetStockObject(BLACK_BRUSH));
            SetTextColor(ps.hdc, GetSysColor(COLOR_WINDOWFRAME));
            SetMapMode(ps.hdc, MM_ANISOTROPIC);

            SetViewportOrgEx(ps.hdc, rArrow.left, rArrow.top, NULL);

            SetViewportExtEx(ps.hdc, rArrow.right - rArrow.left,
                                                    rArrow.bottom - rArrow.top, NULL);
            SetWindowOrgEx(ps.hdc, 0, 0, NULL);
            SetWindowExtEx(ps.hdc, ARROWXAXIS, ARROWYAXIS, NULL);
            MoveToEx(ps.hdc, 0, (ARROWYAXIS / 2), NULL);
            LineTo(ps.hdc, ARROWXAXIS, (ARROWYAXIS / 2));
/*
            Polygon(ps.hdc, (LPPOINT) Arrow, 10);
*/
            Polygon(ps.hdc, (LPPOINT) ArrowUp, POINTSPERARROW);
            Polygon(ps.hdc, (LPPOINT) ArrowDown, POINTSPERARROW);
            SelectObject(ps.hdc, hbr);

            EndPaint(hArrow, &ps);

            break;

        default:
            return(DefWindowProc(hArrow, message, wParam, lParam));

            break;
        }

    return(0L);
}

#ifndef _WIN32
#pragma alloc_text(_INIT, ArrowInit)
#endif

BOOL FAR PASCAL ArrowInit(HANDLE hInst)
{
    WNDCLASS wcArrow;

    wcArrow.lpszClassName = SPINARROW_CLASSNAME;
    wcArrow.hInstance     = hInst;
    wcArrow.lpfnWndProc   = ArrowControlProc;
    wcArrow.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wcArrow.hIcon         = NULL;
    wcArrow.lpszMenuName  = NULL;
    wcArrow.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    wcArrow.style         = CS_HREDRAW | CS_VREDRAW;
#if DOUBLECLICK
    wcArrow.style         |= CS_DBLCLKS;
#endif
    wcArrow.cbClsExtra    = 0;
    wcArrow.cbWndExtra    = 0;

    if (!RegisterClass(&wcArrow))
        return FALSE;

    return TRUE;
}


RLMETER.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   rlmeter.c: Audio recording level window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * This window class acts as a 'VU Meter' showing the current and peak
 * volume. Set the volume via the WMRL_SETLEVEL message (lParam is new level).
 * The peak level will be tracked by the control by means of a 2-second timer.
 */

#include <windows.h>
#include <windowsx.h>

#include "rlmeter.h"

#ifdef _WIN32
#ifndef EXPORT
#define EXPORT
#endif
#endif

LONG FAR PASCAL EXPORT
RLMeterProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

/*
 * generic window class to support a volume level display.
 *
 * The window has a white background, and draws a black filled
 * rectangle to show the current volume level, and a red line at the
 * peak. Every two seconds on a timer we lower the peak (we set the
 * saved peak value to 0 so that at the next update we move the line to
 * whatever is the current level.
 *
 * We store the pen and brush handles and the current and maximum levels
 * as window words using SetWindowWord on win16 and SetWindowLong on win32.
 */

// window data layout
#define WD_MAX      0                           // current max
#define WD_PREVMAX  (WD_MAX + sizeof(UINT))     // currently drawn max
#define WD_PREVLVL  (WD_PREVMAX + sizeof(UINT)) // currently drawn level

#define WD_PEN      (WD_PREVLVL + sizeof(UINT)) // pen for max line

#define WDBYTES     (WD_PEN + sizeof(UINT))     // window bytes to alloc

#ifdef _WIN32
#define SetWindowUINT     SetWindowLong
#define GetWindowUINT     GetWindowLong
#else
#define SetWindowUINT     SetWindowWord
#define GetWindowUINT     GetWindowWord
#endif

// call (if first instance) to register class
BOOL
RLMeter_Register(HINSTANCE hInstance)
{
    WNDCLASS cls;

    cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
    cls.hIcon          = NULL;
    cls.lpszMenuName   = NULL;
    cls.lpszClassName  = RLMETERCLASS;
    cls.hbrBackground  = GetStockObject(WHITE_BRUSH);
    cls.hInstance      = hInstance;
    cls.style          = CS_HREDRAW | CS_VREDRAW;
    cls.lpfnWndProc    = RLMeterProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = WDBYTES;

    return RegisterClass(&cls);

}

LONG FAR PASCAL EXPORT
RLMeterProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message) {
    case WM_CREATE:
        // init current level and max to 0
        SetWindowUINT(hwnd, WD_MAX, 0);
        SetWindowUINT(hwnd, WD_PREVMAX, 0);
        SetWindowUINT(hwnd, WD_PREVLVL, 0);

        // create a red pen for the max line and store this
        SetWindowUINT(hwnd, WD_PEN,
                (UINT) CreatePen(PS_SOLID, 2, RGB(255, 0, 0)));

        break;

    case WM_DESTROY:
        // destroy the pen we created
        {
            HPEN hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
            if (hpen) {
                DeleteObject(hpen);
                SetWindowUINT(hwnd, WD_PEN, 0);
            }

            // also kill the timer we created
            KillTimer(hwnd, 0);
        }
        break;

    case WM_PAINT:
        /*
         * paint the entire control
         *
         * nb we must paint exactly as it is currently drawn because we
         * may be clipped to only part of the control. Thus we must draw
         * the max at WD_PREVMAX as it is currently drawn, since WD_MAX
         * may have been set to 0 and not yet drawn - in this case, with
         * some unfortunate timing and clipping, we would have two max lines.
         */
        {
            PAINTSTRUCT ps;
            HDC hdc;
            RECT rc, rcFill;
            HPEN hpenOld, hpen;

            hdc = BeginPaint(hwnd, &ps);

            GetClientRect(hwnd, &rc);

            // treat the level as a percentage and fill that much of the
            // control with black (from left)
            rcFill = rc;
            rcFill.right = (rc.right * GetWindowUINT(hwnd, WD_PREVLVL)) / 100;
            SetBkColor(hdc, RGB(0,0,0));
            // easy way to fill without creating a brush
            ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);

            // draw the max line
            rcFill.right = (rc.right * GetWindowUINT(hwnd, WD_PREVLVL)) / 100;
            hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
            hpenOld = SelectObject(hdc, hpen);
            MoveToEx(hdc, rcFill.right, rcFill.top, NULL);
            LineTo(hdc, rcFill.right, rcFill.bottom);
            SelectObject(hdc, hpenOld);

            EndPaint(hwnd, &ps);

        }
        break;

    case WMRL_SETLEVEL:
        // set new level, and update the displayed level block and max line
        {
            RECT rc, rcFill;
            UINT uMax, uPrevMax, uPrevLevel, uLevel;
            HDC hdc;

            // new level is lParam
            uLevel = (UINT) lParam;

            // fetch other parameters
            uMax = GetWindowUINT(hwnd, WD_MAX);
            uPrevMax = GetWindowUINT(hwnd, WD_PREVMAX);
            uPrevLevel = GetWindowUINT(hwnd, WD_PREVLVL);

            // decay the max level. This rate works best if we are called
            // to update every 1/20th sec - in this case the decay will be
            // 64% in a second.
            if (uMax > 0) {
                uMax = (uMax * 2007) / 2048;     // = 0.98 * uMax
            }

            hdc = GetDC(hwnd);

            GetClientRect(hwnd, &rc);
            rcFill = rc;

            // is the current level a new peak ?
            if (uLevel > uMax) {
                uMax = uLevel;
            }

            SetWindowUINT(hwnd, WD_MAX, uMax);

            // if the max has moved, erase the old line
            if (uMax != uPrevMax) {
                // white out the line by filling a 2-pixel wide rect
                rcFill.right = ((rc.right * uPrevMax) / 100) + 1;
                rcFill.left = rcFill.right - 2;
                SetBkColor(hdc, RGB(255, 255, 255));
                ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);
            }

            // calculate the area to update
            rcFill.right = (rc.right * uPrevLevel) / 100;
            rcFill.left = (rc.right * uLevel) / 100;

            // are we erasing (lowering level) or drawing more black?
            if (rcFill.right > rcFill.left) {

                // level has dropped - so fill with white down to new level
                SetBkColor(hdc, RGB(255, 255, 255));
            } else {
                // level has gone up so fill with black up to new level
                int t;

                t = rcFill.right;
                rcFill.right = rcFill.left;
                rcFill.left = t;

                SetBkColor(hdc, RGB(0, 0, 0));

                // fill a little extra to ensure no rounding gaps
                if (rcFill.left > 0) {
                    rcFill.left -= 1;
                }
            }
            ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);
            SetWindowUINT(hwnd, WD_PREVLVL, uLevel);

            // draw the new max line if needed
            if (uMax != uPrevMax) {
                HPEN hpen, hpenOld;

                rcFill.right = (rc.right * uMax) /100;

                hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
                hpenOld = SelectObject(hdc, hpen);
                MoveToEx(hdc, rcFill.right, rcFill.top, NULL);
                LineTo(hdc, rcFill.right, rcFill.bottom);
                SelectObject(hdc, hpenOld);

                SetWindowUINT(hwnd, WD_PREVMAX, uMax);
            }
            ReleaseDC(hwnd, hdc);
            return(0);
        }

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


RLMETER.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   rlmeter.h: Audio recording level meter
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * interface definition for rlmeter window class.
 *
 * This window class acts as a 'VU Meter' showing the current and peak
 * volume. Set the volume via the WMRL_SETLEVEL message (lParam is new level).
 * The peak level will be tracked by the control by means of a 2-second timer.
 */

// call (if first instance) to register class
BOOL RLMeter_Register(HINSTANCE hInstance);

//create a window of this class
#define RLMETERCLASS    "VCRLMeter"

//send this message to set the current level (wParam not used, lParam == level)
#define WMRL_SETLEVEL   (WM_USER+1)


STATUS.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   status.c: Status bar window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
//#include <win32.h>
#include <mmsystem.h>
#include "status.h"

/* for compiling under win3.0 - we don't have this attribute */
#ifndef COLOR_BTNHIGHLIGHT
#define COLOR_BTNHIGHLIGHT 20
#endif

#ifdef _WIN32
typedef WNDPROC LPWNDPROC;
#else
typedef long (FAR PASCAL *LPWNDPROC)();
#endif

// class names for status bar and static text windows
charszStatusClass[] = "StatusClass";
charszText[]   = "SText";
int gStatusStdHeight;   // based on font metrics

static HANDLE ghFont;
static HBRUSH ghbrHL, ghbrShadow;

/* Function Prototypes */
LONG FAR PASCAL statusWndProc(HWND hwnd, unsigned msg,
UINT wParam, LONG lParam);
LONG FAR PASCAL fnText(HWND, unsigned, UINT, LONG);
static VOID NEAR PASCAL PaintText(HWND hwnd, HDC hdc);

/*
 * create the brushes we need
 */
void
statusCreateTools(void)
{
    HDC hdc;
    TEXTMETRIC tm;
    HFONT hfont;

    ghbrHL = CreateSolidBrush(GetSysColor(COLOR_BTNHIGHLIGHT));
    ghbrShadow = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));

    /* Create the font we'll use for the status bar - use system as default */
    ghFont = CreateFont(12, 0,// height, width
                0, 0,// escapement, orientation
                FW_NORMAL,// weight,
                FALSE, FALSE, FALSE,// attributes
                ANSI_CHARSET,// charset
                OUT_DEFAULT_PRECIS,// output precision
                CLIP_DEFAULT_PRECIS,// clip precision
                DEFAULT_QUALITY,// quality
                VARIABLE_PITCH | FF_MODERN,
                "Helv");

    if (ghFont == NULL) {
        ghFont = GetStockObject(SYSTEM_FONT);
    }

    // find the char size to calc standard status bar height
    hdc = GetDC(NULL);
    hfont = SelectObject(hdc, ghFont);
    GetTextMetrics(hdc, &tm);
    SelectObject(hdc, hfont);
    ReleaseDC(NULL, hdc);

    gStatusStdHeight = tm.tmHeight * 3 / 2;

}

void
statusDeleteTools(void)
{
    DeleteObject(ghbrHL);
    DeleteObject(ghbrShadow);

    DeleteObject(ghFont);
}

/*--------------------------------------------------------------+
| statusInit - initialize for status window, register the|
|       Window's class.|
||
+--------------------------------------------------------------*/
#pragma alloc_text(INIT_TEXT, statusInit)
BOOL statusInit(HANDLE hInst, HANDLE hPrev)
{
  WNDCLASS  cls;

  statusCreateTools();

  if (!hPrev){
  cls.hCursor= LoadCursor(NULL, IDC_ARROW);
  cls.hIcon= NULL;
  cls.lpszMenuName= NULL;
  cls.lpszClassName= szStatusClass;
  cls.hbrBackground     = (HBRUSH) (COLOR_BTNFACE + 1);
  cls.hInstance= hInst;
  cls.style= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  cls.lpfnWndProc= statusWndProc;
  cls.cbClsExtra= 0;
  cls.cbWndExtra= 0;

  if (!RegisterClass(&cls))
  return FALSE;

  cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
  cls.hIcon          = NULL;
  cls.lpszMenuName   = NULL;
  cls.lpszClassName  = (LPSTR)szText;
  cls.hbrBackground  = (HBRUSH) (COLOR_BTNFACE + 1);
  cls.hInstance      = hInst;
  cls.style          = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  cls.lpfnWndProc    = (LPWNDPROC)fnText;
  cls.cbClsExtra     = 0;
  cls.cbWndExtra     = 0;
  if (!RegisterClass(&cls))
return FALSE;
  }

  return TRUE;
}

/*
 * returns the recommended height for a status bar based on the
 * character dimensions of the font used
 */
int
statusGetHeight(void)
{
    return(gStatusStdHeight);
}

/*--------------------------------------------------------------+
| statusUpdateStatus - update the status line|
||
| The argument can either be NULL, a string, or a resource ID|
| cast to a LPCSTR with MAKEINTRESOURCE.|
+--------------------------------------------------------------*/
void statusUpdateStatus(HWND hwnd, LPCSTR lpsz)
{
    charach[80];
    HWND hwndtext;

    if ((lpsz != NULL) && (HIWORD((DWORD) lpsz) == 0)) {
LoadString(GetWindowInstance(hwnd), LOWORD((DWORD) lpsz), ach, sizeof(ach));
lpsz = ach;
    }

    hwndtext = GetDlgItem(hwnd, 1);
    if (!lpsz || *lpsz == '\0') {
SetWindowText(hwndtext,"");
    } else {
SetWindowText(hwndtext, lpsz);
    }
}

/*--------------------------------------------------------------+
| statusWndProc - window proc for status window|
||
+--------------------------------------------------------------*/
LONG FAR PASCAL
statusWndProc(HWND hwnd, unsigned msg, UINT wParam, LONG lParam)
{
  PAINTSTRUCTps;
  HDChdc;
  HWND          hwndSText;

  switch(msg){
    case WM_CREATE:

    /* we need to create the static text control for the status bar */
    hwndSText = CreateWindow(
                            szText,
                            "",
                            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
            0, 0, 0, 0,
                            hwnd,
                            (HMENU) 1,  // child id
                            GetWindowInstance(hwnd),
                            NULL);

    if (!hwndSText) {
    return -1;
            }
    break;

    case WM_DESTROY:
            statusDeleteTools();
    break;

    case WM_SIZE:
        {
            RECT rc;

            GetClientRect(hwnd, &rc);

            MoveWindow(
                GetDlgItem(hwnd, 1),    // get child window handle
                2, 1,                   // xy just inside
                rc.right - 4,
                rc.bottom - 2,
                TRUE);

    break;
        }

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

            // only the background and the child window need painting

    EndPaint(hwnd, &ps);
    break;

    case WM_SYSCOLORCHANGE:
        statusDeleteTools();
        statusCreateTools();
        break;

    case WM_ERASEBKGND:
        break;

  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*
 * window proc for static text window
 */
LONG FAR PASCAL
fnText(HWND hwnd, UINT msg, UINT wParam, LONG lParam )
{
PAINTSTRUCT ps;

switch (msg) {
case WM_SETTEXT:
DefWindowProc(hwnd, msg, wParam, lParam);
InvalidateRect(hwnd,NULL,FALSE);
UpdateWindow(hwnd);
return 0L;

case WM_ERASEBKGND:
return 0L;

case WM_PAINT:
BeginPaint(hwnd, &ps);
PaintText(hwnd, ps.hdc);
EndPaint(hwnd, &ps);
return 0L;
        }

return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*--------------------------------------------------------------+
| PaintText - paint the shadowed static text field.|
||
+--------------------------------------------------------------*/
VOID NEAR PASCAL
PaintText(HWND hwnd, HDC hdc)
{
  RECT rc;
  char        ach[128];
  int  len;
  intdx, dy;
  RECTrcFill;
  HFONThfontOld;
  HBRUSH hbrSave;

  GetClientRect(hwnd, &rc);

  len = GetWindowText(hwnd,ach,sizeof(ach));

  SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
  SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));

  hfontOld = SelectObject(hdc, ghFont);

  rcFill.left = rc.left + 1;
  rcFill.right = rc.right - 1;
  rcFill.top = rc.top + 1;
  rcFill.bottom = rc.bottom - 1;

  /* move in some and do background and text in one swoosh */
  ExtTextOut(hdc,4,1,ETO_OPAQUE,&rcFill,ach,len,NULL);

  dx = rc.right - rc.left;
  dy = rc.bottom - rc.top;

  hbrSave = SelectObject(hdc, ghbrShadow);
  PatBlt(hdc, rc.left, rc.top, 1, dy, PATCOPY);
  PatBlt(hdc, rc.left, rc.top, dx, 1, PATCOPY);

  SelectObject(hdc, ghbrHL);
  PatBlt(hdc, rc.right-1, rc.top+1, 1, dy-1, PATCOPY);
  PatBlt(hdc, rc.left+1, rc.bottom -1, dx-1, 1,  PATCOPY);

  if (hfontOld)
  SelectObject(hdc, hfontOld);
  if (hbrSave)
  SelectObject(hdc, hbrSave);
  return ;
}


STATUS.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   status.h: Status bar include file
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/* Globals */
// class name of window to create
extern charszStatusClass[];

/* Function Prototypes */

BOOL  statusInit(HANDLE hInst, HANDLE hPrev);
void  statusUpdateStatus(HWND hwnd, LPCSTR lpsz);// update status line

/*
 * returns the recommended height for a status bar based on the
 * character dimensions of the font used
 */
int statusGetHeight(void);


TOOLBAR.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   toolbar.c: Toolbar control window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include <string.h>

#include <windows.h>
#include <windowsx.h>
//#include <win32.h>
#include "toolbar.h"// use this for generic app
/************************************************************************/

/* work for win3.0 */
#ifndef COLOR_BTNHIGHLIGHT
#define COLOR_BTNHIGHLIGHT 20
#endif

charszToolBarClass[] = "ToolBarClass";
HBRUSHghbrToolbar;// brush for toolbar background

//
// Window proc for buttons, THIS FUNCTION MUST BE EXPORTED
//
LONG FAR PASCAL toolbarWndProc(HWND, unsigned, UINT, LONG);

typedef long (FAR PASCAL *LPWNDPROC)();

/*
Defines
*/

#ifdef _WIN32

#define GETARRAYBUTT(hwnd)((HANDLE)GetWindowLong(hwnd,GWL_ARRAYBUTT))
#define GETNUMBUTTONS(hwnd)((int)GetWindowLong(hwnd,GWL_NUMBUTTONS))
#define GETPRESSED(hwnd)((BOOL)GetWindowLong(hwnd,GWL_PRESSED))
#define GETKEYPRESSED(hwnd)((BOOL)GetWindowLong(hwnd,GWL_KEYPRESSED))
#define GETWHICH(hwnd)((int)GetWindowLong(hwnd,GWL_WHICH))
#define GETSHIFTED(hwnd)((BOOL)GetWindowLong(hwnd,GWL_SHIFTED))
#define GETBMPHANDLE(hwnd)((HANDLE)GetWindowLong(hwnd,GWL_BMPHANDLE))
#define GETBMPINT(hwnd)((int)GetWindowLong(hwnd,GWL_BMPINT))
#define GETBUTTONSIZE(hwnd)GetWindowLong(hwnd,GWL_BUTTONSIZE)
#define GETHINST(hwnd)((HANDLE)GetWindowLong(hwnd,GWL_HINST))

#define SETARRAYBUTT(hwnd, h) SetWindowLong(hwnd, GWL_ARRAYBUTT, (UINT)h)
#define SETNUMBUTTONS(hwnd, wNumButtons) \
SetWindowLong(hwnd, GWL_NUMBUTTONS, wNumButtons)
#define SETPRESSED(hwnd, f)SetWindowLong(hwnd, GWL_PRESSED, (UINT)f)
#define SETKEYPRESSED(hwnd, f)SetWindowLong(hwnd, GWL_KEYPRESSED, (UINT)f)
#define SETWHICH(hwnd, i)SetWindowLong(hwnd, GWL_WHICH, (UINT)i)
#define SETSHIFTED(hwnd, i)SetWindowLong(hwnd, GWL_SHIFTED, (UINT)i)
#define SETBMPHANDLE(hwnd, h)SetWindowLong(hwnd, GWL_BMPHANDLE, (UINT)h)
#define SETBMPINT(hwnd, i)SetWindowLong(hwnd, GWL_BMPINT, (UINT)i)
#define SETBUTTONSIZE(hwnd, l)SetWindowLong(hwnd, GWL_BUTTONSIZE, l)
#define SETHINST(hwnd, h)SetWindowLong(hwnd, GWL_HINST, (UINT)h)

#else

#define GETARRAYBUTT(hwnd)((HANDLE)GetWindowWord(hwnd,GWW_ARRAYBUTT))
#define GETNUMBUTTONS(hwnd)((int)GetWindowWord(hwnd,GWW_NUMBUTTONS))
#define GETPRESSED(hwnd)((BOOL)GetWindowWord(hwnd,GWW_PRESSED))
#define GETKEYPRESSED(hwnd)((BOOL)GetWindowWord(hwnd,GWW_KEYPRESSED))
#define GETWHICH(hwnd)((int)GetWindowWord(hwnd,GWW_WHICH))
#define GETSHIFTED(hwnd)((BOOL)GetWindowWord(hwnd,GWW_SHIFTED))
#define GETBMPHANDLE(hwnd)((HANDLE)GetWindowWord(hwnd,GWW_BMPHANDLE))
#define GETBMPINT(hwnd)((int)GetWindowWord(hwnd,GWW_BMPINT))
#define GETBUTTONSIZE(hwnd)GetWindowLong(hwnd,GWL_BUTTONSIZE)
#define GETHINST(hwnd)((HANDLE)GetWindowWord(hwnd,GWW_HINST))

#define SETARRAYBUTT(hwnd, h) SetWindowWord(hwnd, GWW_ARRAYBUTT, (WORD)h)
#define SETNUMBUTTONS(hwnd, wNumButtons) \
SetWindowWord(hwnd, GWW_NUMBUTTONS, wNumButtons)
#define SETPRESSED(hwnd, f)SetWindowWord(hwnd, GWW_PRESSED, (WORD)f)
#define SETKEYPRESSED(hwnd, f)SetWindowWord(hwnd, GWW_KEYPRESSED, (WORD)f)
#define SETWHICH(hwnd, i)SetWindowWord(hwnd, GWW_WHICH, (WORD)i)
#define SETSHIFTED(hwnd, i)SetWindowWord(hwnd, GWW_SHIFTED, (WORD)i)
#define SETBMPHANDLE(hwnd, h)SetWindowWord(hwnd, GWW_BMPHANDLE, (WORD)h)
#define SETBMPINT(hwnd, i)SetWindowWord(hwnd, GWW_BMPINT, (WORD)i)
#define SETBUTTONSIZE(hwnd, l)SetWindowLong(hwnd, GWL_BUTTONSIZE, l)
#define SETHINST(hwnd, h)SetWindowWord(hwnd, GWW_HINST, (WORD)h)

#endif

#define lpCreate ((LPCREATESTRUCT)lParam)

/* Prototypes */

static void NEAR PASCAL NotifyParent(HWND, int);

/**************************************************************************
toolbarInit( hInst, hPrev )

Call this routine to initialize the toolbar code.

Arguments:
hPrevinstance handle of previous instance
hInstinstance handle of current instance

Returns:
TRUE if successful, FALSE if not
***************************************************************************/

BOOL FAR PASCAL toolbarInit(HANDLE hInst, HANDLE hPrev)
{
WNDCLASScls;

/* Register the tool bar window class */
if (!hPrev) {

    cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
    cls.hIcon          = NULL;
    cls.lpszMenuName   = NULL;
    cls.lpszClassName  = (LPSTR)szToolBarClass;
    cls.hbrBackground  = (HBRUSH)(COLOR_BTNFACE + 1);
    cls.hInstance      = hInst;
    cls.style          = CS_DBLCLKS;
    cls.lpfnWndProc    = toolbarWndProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = TOOLBAR_EXTRABYTES;
    if (!RegisterClass(&cls))
return FALSE;
}

return TRUE;
}

/***************************************************************************/
/* toolbarSetBitmap:  takes a resource ID and associates that bitmap with  */
/*                    a given toolbar.  Also takes the instance handle and */
/*                    the size of the buttons on the toolbar.              */
/***************************************************************************/
BOOL FAR PASCAL toolbarSetBitmap(HWND hwnd, HANDLE hInst, int ibmp, POINT ptSize)
{
SETHINST(hwnd, hInst);
SETBMPHANDLE(hwnd, NULL);
SETBMPINT(hwnd, ibmp);
SETBUTTONSIZE(hwnd, MAKELONG(ptSize.y, ptSize.x));
return (BOOL)SendMessage(hwnd, WM_SYSCOLORCHANGE, 0, 0L); // do the work
}

/***************************************************************************/
/* toolbarGetNumButtons:  return the number of buttons registered on a     */
/*                        given toolbar window.                            */
/***************************************************************************/
int FAR PASCAL toolbarGetNumButtons(HWND hwnd)
{
    return GETNUMBUTTONS(hwnd);
}

/***************************************************************************/
/* toolbarButtonFromIndex:  Given an index into the array of buttons on    */
/*                          this toolbar, return which button is there.    */
/*                          Returns -1 for an error code.                  */
/***************************************************************************/
int FAR PASCAL toolbarButtonFromIndex(HWND hwnd, int iBtnPos)
{
intiButton;
HANDLEh;
TOOLBUTTONfar *lpaButtons;

/* Get the array of buttons on this toolbar */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;

/* Validate the index passed in */
if (iBtnPos > GETNUMBUTTONS(hwnd) || iBtnPos < 0)
return -1;

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Read off the answer */
iButton = lpaButtons[iBtnPos].iButton;

GlobalUnlock(h);
return iButton;
}

/***************************************************************************/
/* toolbarIndexFromButton:  Given a button ID, return the position in the  */
/*                          array that it appears at.                      */
/*                          Returns -1 for an error code.                  */
/***************************************************************************/
int FAR PASCAL toolbarIndexFromButton(HWND hwnd, int iButton)
{
inti, iBtnPos = -1;
HANDLEh;
TOOLBUTTONfar *lpButton;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* loop through until you find it */
for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
if (lpButton->iButton == iButton) {
iBtnPos = i;
break;
}

GlobalUnlock(h);
return iBtnPos;
}

/***************************************************************************/
/* toolbarPrevStateFromButton:  Given a button ID, return the state that   */
/*                              the button was in before it was pressed    */
/*                              all the way down (for non-push buttons).   */
/*                              Return -1 for an error code.               */
/***************************************************************************/
int FAR PASCAL toolbarPrevStateFromButton(HWND hwnd, int iButton)
{
inti, iPrevState = -1;
HANDLEh;
TOOLBUTTONfar *lpButton;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* look for what we need */
for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
if (lpButton->iButton == iButton) {
iPrevState = lpButton->iPrevState;
break;
}

GlobalUnlock(h);
return iPrevState;
}

/***************************************************************************/
/* toolbarActivityFromButton:   Given a button ID, return the most recent  */
/*                              activity that happened to it. (eg DBLCLK)  */
/*                              Return -1 for an error code.               */
/***************************************************************************/
int FAR PASCAL toolbarActivityFromButton(HWND hwnd, int iButton)
{
inti, iActivity = -1;
HANDLEh;
TOOLBUTTONfar *lpButton;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* loop through until you find it */
for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
if (lpButton->iButton == iButton)
iActivity = lpButton->iActivity;

GlobalUnlock(h);
return iActivity;
}

/***************************************************************************/
/* toolbarIndexFromPoint:  Given a point in the toolbar window, return the */
/*                         index of the button beneath that point.         */
/*                         Return -1 for an error code.                    */
/***************************************************************************/
int FAR PASCAL toolbarIndexFromPoint(HWND hwnd, POINT pt)
{
inti, iBtnPos = -1;
HANDLEh;
TOOLBUTTONfar *lpButton;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* loop through until we find an intersection */
for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
if (PtInRect(&lpButton->rc, pt)) {
iBtnPos = i;
break;
}

GlobalUnlock(h);
return iBtnPos;
}

/***************************************************************************/
/* toolbarRectFromIndex:   Given an index into our array of buttons, return*/
/*                         the rect occupied by that button.               */
/*                         Return a NULL rect for an error.                */
/***************************************************************************/
BOOL FAR PASCAL toolbarRectFromIndex(HWND hwnd, int iBtnPos, LPRECT lprc)
{
HANDLEh;
TOOLBUTTONfar *lpaButtons;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
    return FALSE;

/* Validate the index passed in */
if (iBtnPos > GETNUMBUTTONS(hwnd) || iBtnPos < 0)
    return FALSE;

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Read off the rect */
*lprc = lpaButtons[iBtnPos].rc;

GlobalUnlock(h);
        return TRUE;
}

/***************************************************************************/
/* toolbarFullStateFromButton: Given a button in our array of buttons,     */
/*                             return the state of that button.            */
/*                             (including the wierd state FULLDOWN). For   */
/*                             just UP or DOWN or GRAYED,                  */
/*                             call toolbarStateFromButton.   */
/*                             Return -1 for an error.                     */
/***************************************************************************/
int FAR PASCAL toolbarFullStateFromButton(HWND hwnd, int iButton)
{
intiState, iBtnPos;
HANDLEh;
TOOLBUTTONfar *lpaButtons;

iBtnPos = toolbarIndexFromButton(hwnd, iButton);
if (iBtnPos == -1)
return -1;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Read off the state */
iState = lpaButtons[iBtnPos].iState;

GlobalUnlock(h);
return iState;
}

/***************************************************************************/
/* toolbarStateFromButton: This fn is called by the parent application     */
/*                         to get the state of a button.  It will only     */
/*                         return DOWN, or UP or GRAYED as opposed to      */
/*                         toolbarFullStateFromButton which could return   */
/*                         FULLDOWN.                                       */
/***************************************************************************/
int FAR PASCAL toolbarStateFromButton(HWND hwnd, int iButton)
{
intiState;

/* If a checkbox button is all the way down, it's previous state is */
/* the one we want.    */
if ((iState = toolbarFullStateFromButton(hwnd, iButton))
== BTNST_FULLDOWN) {
    iState = toolbarPrevStateFromButton(hwnd, iButton);
    return iState;
} else
    return iState;
}

/***************************************************************************/
/* toolbarStringFromIndex: Given an index into our array of buttons, return*/
/*                         the string resource associated with it.         */
/*                         Return -1 for an error.                         */
/***************************************************************************/
int FAR PASCAL toolbarStringFromIndex(HWND hwnd, int iBtnPos)
{
intiString;
HANDLEh;
TOOLBUTTONfar *lpaButtons;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;

/* Validate the index passed in */
if (iBtnPos > GETNUMBUTTONS(hwnd) || iBtnPos < 0)
return -1;

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Read off the ID */
iString = lpaButtons[iBtnPos].iString;

GlobalUnlock(h);
return iString;
}

/***************************************************************************/
/* toolbarTypeFromIndex:   Given an index into our array of buttons, return*/
/*                         the type of button it is (PUSH, RADIO, etc.)    */
/*                         Return -1 for an error.                         */
/***************************************************************************/
int FAR PASCAL toolbarTypeFromIndex(HWND hwnd, int iBtnPos)
{
intiType;
HANDLEh;
TOOLBUTTONfar *lpaButtons;

/* Get the Array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return -1;

/* Validate the index passed in */
if (iBtnPos > GETNUMBUTTONS(hwnd) || iBtnPos < 0)
return -1;

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Read off the type */
iType = lpaButtons[iBtnPos].iType;

GlobalUnlock(h);
return iType;
}

/***************************************************************************/
/* toolbarAddTool:  Add a button to this toolbar.  Sort them by leftmost   */
/*                  position in the window (for tabbing order).            */
/*                  Return FALSE for an error.                             */
/***************************************************************************/
BOOL FAR PASCAL toolbarAddTool(HWND hwnd, TOOLBUTTON tb)
{
HANDLEh;
TOOLBUTTON far  *lpaButtons;
intcButtons, i, j;
BOOLfInsert = FALSE;

/* We better not have this button on the toolbar already */
if (toolbarIndexFromButton(hwnd, tb.iButton) != -1)
return FALSE;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons are there already? */
cButtons = GETNUMBUTTONS(hwnd);

/* If we have filled our alloced memory for this array already, we */
/* need to re-alloc some more memory   */
if ( ((cButtons & (TOOLGROW - 1)) == 0) && (cButtons > 0) ) {

/* Re-alloc it bigger */
h = GlobalReAlloc(h,
GlobalSize(h) + TOOLGROW * sizeof(TOOLBUTTON),
GMEM_MOVEABLE | GMEM_SHARE);
if (!h)
    return FALSE;
}

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Look for the spot we need to insert this new guy at.*/
 /* Remember, we sort by left x positionbreaking ties   */
 /* with top y position.*/
for (i = 0; i < cButtons; i++) {
// Here it goes
     if (lpaButtons[i].rc.left > tb.rc.left ||
 (lpaButtons[i].rc.left == tb.rc.left &&
 lpaButtons[i].rc.top > tb.rc.top)) {
fInsert = TRUE;
/* Open up a spot in the array */
for (j = cButtons; j > i; j--)
    lpaButtons[j] = lpaButtons[j-1];
/* Add our new guy */
lpaButtons[i] = tb;// redraw now
InvalidateRect(hwnd, &(lpaButtons[i].rc), FALSE);
break;
    }
}

/* If our loop didn't insert it, we need to add it to the end */
if (!fInsert)
    lpaButtons[i] = tb;

/* If we are told that this button has the focus, we better*/
/* change the focus to it.  Then use the normal state.          */
if (tb.iState == BTNST_FOCUSUP) {
    tb.iState = BTNST_UP;
    SETWHICH(hwnd, i);
} else if (tb.iState == BTNST_FOCUSDOWN || tb.iState == BTNST_FULLDOWN){
    tb.iState = BTNST_DOWN;// nonsense to init to FULLDOWN
    SETWHICH(hwnd, i);
}

cButtons++;// one more button now.
GlobalUnlock(h);

SETNUMBUTTONS(hwnd, cButtons);// new count
SETARRAYBUTT(hwnd, h);// re-alloc might have changed it

/* Just in case no one else makes this new button draw */
InvalidateRect(hwnd, &(tb.rc), FALSE);

return TRUE;
}

 /***************************************************************************/
 /* toolbarRetrieveTool:  Get the TOOLBUTTON struct for the given button.   */
 /*                       Return FALSE for an error.                        */
 /***************************************************************************/
 BOOL FAR PASCAL toolbarRetrieveTool(HWND hwnd, int iButton, LPTOOLBUTTON lptb)
 {
 inti;
 HANDLEh;
 TOOLBUTTONfar *lpButton;
 BOOLfFound = FALSE;

 /* Get the array of buttons */
 h = GETARRAYBUTT(hwnd);
 if (!h)
 return FALSE;
 lpButton = (TOOLBUTTON far *)GlobalLock(h);

 /* look for what we need */
 for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
 if (lpButton->iButton == iButton) {
 *lptb = *lpButton;
 fFound = TRUE;
 break;
 }

 GlobalUnlock(h);
 return fFound;
 }

/***************************************************************************/
/* toolbarRemoveTool:  Remove this button ID from our array of buttons on  */
/*                    the toolbar.  (only 1 of each button ID allowed).   */
/*                     Return FALSE for an error.                          */
/***************************************************************************/
BOOL FAR PASCAL toolbarRemoveTool(HWND hwnd, int iButton)
{
HANDLEh;
TOOLBUTTON far  *lpaButtons;
intcButtons, i, j;
BOOLfFound = FALSE;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons are on there now? */
cButtons = GETNUMBUTTONS(hwnd);

lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

/* Find a match, remove it, and close the array around it. */
for (i = 0; i < cButtons; i++)
if (lpaButtons[i].iButton == iButton) {
fFound = TRUE;
// redraw now
InvalidateRect(hwnd, &(lpaButtons[i].rc), FALSE);
if (i != cButtons - 1)// Last button? Don't bother!
for (j = i; j < cButtons; j++)
lpaButtons[j] = lpaButtons[j + 1];
break;
}

GlobalUnlock(h);

/* Didn't find it! */
if (!fFound)
    return FALSE;

/* One less button */
cButtons--;

/* Every once in a while, re-alloc a smaller array chunk to*/
/* save memory.*/
if ( ((cButtons & (TOOLGROW - 1)) == 0) && (cButtons > 0) ) {

/* Re-alloc it smaller */
h = GlobalReAlloc(h,
GlobalSize(h) - TOOLGROW * sizeof(TOOLBUTTON),
GMEM_MOVEABLE | GMEM_SHARE);
if (!h)
    return FALSE;
}

SETNUMBUTTONS(hwnd, cButtons);// new count
SETARRAYBUTT(hwnd, h);// re-alloc could have changed it

return TRUE;
}

/***************************************************************************/
/* toolbarModifyString: Given a button ID on the toolbar, change it's      */
/*                      string resource associated with it.                */
/*                      returns FALSE for an error or if no such button    */
/***************************************************************************/
BOOL FAR PASCAL toolbarModifyString(HWND hwnd, int iButton, int iString)
{
HANDLEh;
TOOLBUTTON far  *lpButton;
intcButtons, i;
BOOLfFound = FALSE;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons? */
cButtons = GETNUMBUTTONS(hwnd);
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* Find that button, and change it's state */
for (i = 0; i < cButtons; i++, lpButton++)
if (lpButton->iButton == iButton) {
lpButton->iString = iString;
fFound = TRUE;// redraw now
break;
}

GlobalUnlock(h);
return fFound;
}

/***************************************************************************/
/* toolbarModifyState:  Given a button ID on the toolbar, change it's      */
/*                      state.                                             */
/*                      returns FALSE for an error or if no such button    */
/***************************************************************************/
BOOL FAR PASCAL toolbarModifyState(HWND hwnd, int iButton, int iState)
{
HANDLEh;
TOOLBUTTON far  *lpButton;
intcButtons, i;
BOOLfFound = FALSE;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons? */
cButtons = GETNUMBUTTONS(hwnd);
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* Find that button, and change it's state */
for (i = 0; i < cButtons; i++, lpButton++)
if (lpButton->iButton == iButton) {
if (lpButton->iState != iState) {
lpButton->iState = iState;
InvalidateRect(hwnd, &(lpButton->rc), FALSE);
}
fFound = TRUE;// redraw now

/* if we're pushing a radio button down, bring */
/* all others in its group up */
if (lpButton->iType >= BTNTYPE_RADIO &&
iState == BTNST_DOWN)
    toolbarExclusiveRadio(hwnd, lpButton->iType,
iButton);
break;
}

GlobalUnlock(h);
return fFound;
}

/***************************************************************************/
/* toolbarModifyPrevState: Given a button on the toolbar, change it's prev-*/
/*                      ious state. Used for non-PUSH buttons to remember  */
/*                      what state a button was in before pressed all the  */
/*                      way down, so that when you let go, you know what   */
/*                      state to set it to (the opposite of what it was).  */
/*                      returns FALSE for an error (no button array)       */
/***************************************************************************/
BOOL FAR PASCAL toolbarModifyPrevState(HWND hwnd, int iButton, int iPrevState)
{
HANDLEh;
TOOLBUTTON far  *lpButton;
intcButtons, i;

/* Get button array */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons? */
cButtons = GETNUMBUTTONS(hwnd);

lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* Find the button, change the state */
for (i = 0; i < cButtons; i++, lpButton++)
if (lpButton->iButton == iButton) {
lpButton->iPrevState = iPrevState;
break;
}

GlobalUnlock(h);
return TRUE;
}

/***************************************************************************/
/* toolbarModifyActivity: Given a button ID on the toolbar, change it's    */
/*                        activity.  This tells the app what just happened */
/*                        to the button (ie. KEYUP, MOUSEDBLCLK, etc.)     */
/*                        returns FALSE for an error or if no such button  */
/***************************************************************************/
BOOL FAR PASCAL toolbarModifyActivity(HWND hwnd, int iButton, int iActivity)
{
HANDLEh;
TOOLBUTTON far  *lpButton;
intcButtons, i;

/* Get the button array */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;

/* How many buttons */
cButtons = GETNUMBUTTONS(hwnd);

lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* loop through and change the right one */
for (i = 0; i < cButtons; i++, lpButton++)
if (lpButton->iButton == iButton) {
lpButton->iActivity = iActivity;
break;
}

GlobalUnlock(h);
return TRUE;
}

/***************************************************************************/
/* toolbarFixFocus:  SETWHICH() has been called to tell us which button    */
/*                   has the focus, but the states of all the buttons are  */
/*                   not updated (ie. take focus away from the old button) */
/*                   This routine is called from the Paint routine to fix  */
/*                   the states of all the buttons before drawing them.    */
/*                   Returns FALSE for an error.                           */
/***************************************************************************/
BOOL FAR PASCAL toolbarFixFocus(HWND hwnd)
{
intiFocus;
HANDLEh;
TOOLBUTTONfar *lpaButtons;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;
lpaButtons = (TOOLBUTTON far *)GlobalLock(h);

        /* if focus is on an illegal button, default to the first one */
iFocus = GETWHICH(hwnd);
if (iFocus < 0 || iFocus >= GETNUMBUTTONS(hwnd))
    SETWHICH(hwnd, 0);

/* First of all, make sure that the focus in not on a grayed button. */
/* if so, we advance focus.  If it runs out of buttons without       */
/* finding a non-gray one, we start back at the beginning and start  */
/* looking for a non-gray one from there.  If every button is grayed,*/
/* we leave no focus anywhere.     */
if (lpaButtons[GETWHICH(hwnd)].iState == BTNST_GRAYED) {
    if (!toolbarMoveFocus(hwnd, FALSE)) {
SETWHICH(hwnd, -1);
toolbarMoveFocus(hwnd, FALSE);
    }
}

GlobalUnlock(h);
return TRUE;
}

/***************************************************************************/
/* toolbarExclusiveRadio:  For radio buttons, we need to pop all others    */
/*                         in the group up when one goes down.  Pass the   */
/*                         button that is going down, and its group, and   */
/*                         this routine will pop all others up.            */
/*                         Returns FALSE for an error.                     */
/***************************************************************************/
BOOL FAR PASCAL toolbarExclusiveRadio(HWND hwnd, int iType, int iButton)
{
inti;
HANDLEh;
TOOLBUTTONfar *lpButton;

/* Get the array of buttons */
h = GETARRAYBUTT(hwnd);
if (!h)
return FALSE;
lpButton = (TOOLBUTTON far *)GlobalLock(h);

/* all buttons with this type that aren't this button come up*/
/* if they are not grayed*/
for(i = 0; i < GETNUMBUTTONS(hwnd); i++, lpButton++)
    if (lpButton->iType == iType)
if (lpButton->iButton != iButton &&
lpButton->iState != BTNST_GRAYED) {
    toolbarModifyState(hwnd, lpButton->iButton,BTNST_UP);
}

GlobalUnlock(h);
return TRUE;
}

/*NotifyParent()  of activity to a button  */

static void NEAR PASCAL NotifyParent(HWND hwnd, int iButton)
{
#ifdef _WIN32
        PostMessage(
            GetParent(hwnd),
            WM_COMMAND,
            GET_WM_COMMAND_MPS(GetWindowLong(hwnd, GWL_ID), hwnd, iButton));
#else
PostMessage(GetParent(hwnd),WM_COMMAND,
GetWindowWord(hwnd,GWW_ID),MAKELONG(hwnd,iButton));
#endif
}

/***************************************************************************/
/* toolbarPaintControl:  Handles paint messages by blitting each bitmap    */
/*                       that is on the toolbar to its rect.               */
/*                       First, it fixes the states of the buttons to give */
/*                       the focus to the proper button.                   */
/*                       Returns FALSE for an error.                       */
/***************************************************************************/

static BOOL NEAR PASCAL toolbarPaintControl(HWND hwnd, HDC hdc)
{
    intiBtnPos;/* 0 to toolbarGetNumButtons inclusive*/
    intiButton;/* 0 to NUMBUTTONS-1 inclusive*/
    intiState;/* 0 to NUMSTATES-1 inclusive*/
    HDChdcBtn;/* DC onto button bitmap*/

    RECTrcDest;
    POINTpt;
    longl;
    HANDLEhbm;

    /* Make a source HDC for the button pictures, and select the button */
    /* bitmap into it.*/
    hdcBtn = CreateCompatibleDC(hdc);
    if (!hdcBtn)
return FALSE;
    hbm = GETBMPHANDLE(hwnd);
    if (hbm) {
if (!SelectObject(hdcBtn, GETBMPHANDLE(hwnd))) {
    DeleteDC(hdcBtn);
    return FALSE;
}
    }

    toolbarFixFocus(hwnd);// set the focus field correctly

    /* Go through all buttons on the toolbar */
    for (iBtnPos = 0; iBtnPos < toolbarGetNumButtons(hwnd); iBtnPos++) {

iButton = toolbarButtonFromIndex(hwnd, iBtnPos);// button
iState = toolbarFullStateFromButton(hwnd, iButton);// state
toolbarRectFromIndex(hwnd, iBtnPos, &rcDest);// Dest Rect

/* If we have the focus, we should draw it that way */
        if (GetFocus() == hwnd && GETWHICH(hwnd) == iBtnPos
&& iState == BTNST_UP)
    iState = BTNST_FOCUSUP;
        if (GetFocus() == hwnd && GETWHICH(hwnd) == iBtnPos
&& iState == BTNST_DOWN)
    iState = BTNST_FOCUSDOWN;

/* If we don't have the focus, we should take it away */
        if ((GetFocus() != hwnd || GETWHICH(hwnd) != iBtnPos)
&& iState == BTNST_FOCUSUP)
    iState = BTNST_UP;
        if ((GetFocus() != hwnd || GETWHICH(hwnd) == iBtnPos)
&& iState == BTNST_FOCUSDOWN)
    iState = BTNST_DOWN;

/* The size of each button */
l = GETBUTTONSIZE(hwnd);
pt.x = HIWORD(l);
pt.y = LOWORD(l);

/* Blit from the button picture to the toolbar window */
BitBlt(hdc, rcDest.left, rcDest.top,
    rcDest.right - rcDest.left, rcDest.bottom - rcDest.top,
    hdcBtn, pt.x * iButton, pt.y * iState,
    SRCCOPY);
    }

    DeleteDC(hdcBtn);

    return TRUE;
}

/***************************************************************************/
/* toolbarMoveFocus:  Move Focus forward or backward one button.  You give */
/*                    it the direction to move the focus.  The routine will*/
/*                    stop at the end of the button list without wrapping  */
/*                    around.                                              */
/*                    Returns TRUE if focus moved, or FALSE if it ran out  */
/*                    of buttons before finding a non-grayed one.          */
/***************************************************************************/
BOOL FAR PASCAL toolbarMoveFocus(HWND hwnd, BOOL fBackward)
{
int iBtnPos, iButton, nOffset, nStopAt;
RECTrc;
int iPrevPos = GETWHICH(hwnd); /* Who used to have focus? */

/* Fix illegal value.  It's OK to be one less or greater than range */
if (iPrevPos < -1 || iPrevPos > GETNUMBUTTONS(hwnd))
    SETWHICH(hwnd, 0);// good a default as any

if (fBackward) {
    nOffset = -1;
    nStopAt = -1;
} else {
    nOffset = 1;
    nStopAt = GETNUMBUTTONS(hwnd);
}

/* look for next button that isn't grayed    */
/* DON'T wrap around - future code will pass */
/* the focus to another window (???)         */
for (iBtnPos = GETWHICH(hwnd) + nOffset;
    iBtnPos != nStopAt;
    iBtnPos += nOffset) {
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    if (toolbarStateFromButton(hwnd, iButton) !=
    BTNST_GRAYED) {
SETWHICH(hwnd, iBtnPos);// set focus

/* Redraw both old and new focused button */
toolbarRectFromIndex(hwnd, iPrevPos, &rc);
InvalidateRect(hwnd, &rc, FALSE);
toolbarRectFromIndex(hwnd, iBtnPos, &rc);
InvalidateRect(hwnd, &rc, FALSE);
break;

    }
}

if (GETWHICH(hwnd) != iPrevPos)
    return TRUE;
else
    return FALSE;
}

/***************************************************************************/
/* toolbarSetFocus :  Set the focus in the toolbar to the specified button.*/
/*                    If it's gray, it'll set focus to next ungrayed btn.  */
/*                    Returns TRUE if focus set, or FALSE if the button    */
/*                    doesn't exist or if it and all buttons after it were */
/*                    grayed...       You can use TB_FIRST or TB_LAST in   */
/*                    place of a button ID.  This uses the first or last   */
/*                    un-grayed button.                                    */
/***************************************************************************/
BOOL FAR PASCAL toolbarSetFocus(HWND hwnd, int iButton)
{
    int iBtnPos;
    RECT rc;

    /* Don't move focus while a button is down */
    if (GetCapture() != hwnd && !GETKEYPRESSED(hwnd)) {

/* redraw button with focus in case focus moves */
toolbarRectFromIndex(hwnd, GETWHICH(hwnd), &rc);
InvalidateRect(hwnd, &rc, FALSE);

if (iButton == TB_FIRST) {
    SETWHICH(hwnd, -1); // move forward to 1st button
    return toolbarMoveFocus(hwnd, FALSE);
} else if (iButton == TB_LAST) {
    SETWHICH(hwnd, GETNUMBUTTONS(hwnd));
    return toolbarMoveFocus(hwnd, TRUE);
} else {
    iBtnPos = toolbarIndexFromButton(hwnd, iButton);
    if (iBtnPos != -1) {
SETWHICH(hwnd, --iBtnPos);
return toolbarMoveFocus(hwnd, FALSE);
    } else
return FALSE;
}
return TRUE;

    } else
return FALSE;
}

//
//  LoadUIBitmap() - load a bitmap resource
//
//      load a bitmap resource from a resource file, converting all
//      the standard UI colors to the current user specifed ones.
//
//      this code is designed to load bitmaps used in "gray ui" or
//      "toolbar" code.
//
//      the bitmap must be a 4bpp windows 3.0 DIB, with the standard
//      VGA 16 colors.
//
//      the bitmap must be authored with the following colors
//
//          Button Text        Black        (index 0)
//          Button Face        lt gray      (index 7)
//          Button Shadow      gray         (index 8)
//          Button Highlight   white        (index 15)
//          Window Color       yellow       (index 11)
//          Window Frame       green        (index 10)
//
//      Example:
//
//          hbm = LoadUIBitmap(hInstance, "TestBmp",
//              GetSysColor(COLOR_BTNTEXT),
//              GetSysColor(COLOR_BTNFACE),
//              GetSysColor(COLOR_BTNSHADOW),
//              GetSysColor(COLOR_BTNHIGHLIGHT),
//              GetSysColor(COLOR_WINDOW),
//              GetSysColor(COLOR_WINDOWFRAME));
//
//      Author:     JimBov, ToddLa
//
//

HBITMAP FAR PASCAL  LoadUIBitmap(
    HANDLE      hInstance,          // EXE file to load resource from
    LPCSTR      szName,             // name of bitmap resource
    COLORREF    rgbText,            // color to use for "Button Text"
    COLORREF    rgbFace,            // color to use for "Button Face"
    COLORREF    rgbShadow,          // color to use for "Button Shadow"
    COLORREF    rgbHighlight,       // color to use for "Button Hilight"
    COLORREF    rgbWindow,          // color to use for "Window Color"
    COLORREF    rgbFrame)           // color to use for "Window Frame"
{
    LPBYTE              lpb;
    HBITMAP             hbm;
    LPBITMAPINFOHEADER  lpbi;
    HANDLE              h;
    HDC                 hdc;
    LPDWORD             lprgb;
    int isize;
    HANDLE hmem;
    LPBYTE lpCopy;

    // convert a RGB into a RGBQ
    #define RGBQ(dw) RGB(GetBValue(dw),GetGValue(dw),GetRValue(dw))

    h = LoadResource (hInstance,FindResource(hInstance, szName, RT_BITMAP));

    lpbi = (LPBITMAPINFOHEADER)LockResource(h);

    if (!lpbi)
        return(NULL);

    if (lpbi->biSize != sizeof(BITMAPINFOHEADER))
        return NULL;

    if (lpbi->biBitCount != 4)
        return NULL;

    /*
     * copy the resource since they are now loaded read-only
     */
#ifdef _WIN32
    isize = lpbi->biSize + lpbi->biSizeImage +
            ((int)lpbi->biClrUsed ?
                    (int)lpbi->biClrUsed :
                    (1 << (int)lpbi->biBitCount))
            * sizeof(RGBQUAD);
    hmem = GlobalAlloc(GHND, isize);
    lpCopy = GlobalLock(hmem);
    if ((hmem == NULL) || (lpCopy == NULL)) {
        UnlockResource(h);
        FreeResource(h);
        return(NULL);
    }

    CopyMemory(lpCopy, lpbi, isize);
    UnlockResource(h);
    FreeResource(h);

    lpbi = (LPBITMAPINFOHEADER)lpCopy;
#endif

    /* Calcluate the pointer to the Bits information */
    /* First skip over the header structure */

    lprgb = (LPDWORD)((LPBYTE)(lpbi) + lpbi->biSize);

    /* Skip the color table entries, if any */
    lpb = (LPBYTE)lprgb + ((int)lpbi->biClrUsed ? (int)lpbi->biClrUsed :
        (1 << (int)lpbi->biBitCount)) * sizeof(RGBQUAD);

    lprgb[0]  = RGBQ(rgbText);          // Black
    lprgb[7]  = RGBQ(rgbFace);          // lt gray
    lprgb[8]  = RGBQ(rgbShadow);        // gray
    lprgb[15] = RGBQ(rgbHighlight);     // white
    lprgb[11] = RGBQ(rgbWindow);        // yellow
    lprgb[10] = RGBQ(rgbFrame);         // green

    hdc = GetDC(NULL);

    hbm = CreateDIBitmap (hdc, lpbi, CBM_INIT, (LPVOID)lpb,
        (LPBITMAPINFO)lpbi, DIB_RGB_COLORS);

    ReleaseDC(NULL, hdc);
    UnlockResource(h);
    FreeResource(h);

    return(hbm);
}

/****************************************************************************
toolbarWndProc()

Window proc for toolbar.

Arguments:
Standard window proc
****************************************************************************/

LONG FAR PASCAL toolbarWndProc(HWND hwnd, unsigned message,
UINT wParam, LONG lParam)
{
    PAINTSTRUCTps;
    POINTpt;
    RECTrc;
    intiBtnPos, iButton, ibmp;
    HANDLElpaButtons, hbm, hInst;

    switch (message) {

        case WM_CREATE:// do all initialization

/* What do these do? */
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
    SWP_NOZORDER | SWP_NOSIZE |
SWP_NOMOVE | SWP_NOACTIVATE);
SetWindowLong(hwnd,GWL_STYLE,lpCreate->style & 0xFFFF00FF);

/* Alloc some space for the array of buttons on this bar */
lpaButtons = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
TOOLGROW * sizeof(TOOLBUTTON));

SETARRAYBUTT(hwnd, lpaButtons);// list of buttons on toolbar
SETNUMBUTTONS(hwnd, 0);// # buttons in toolbar
SETPRESSED(hwnd, FALSE);// mouse button being pressed?
SETKEYPRESSED(hwnd, FALSE);// is a key being pressed?
SETWHICH(hwnd, -1);// which button has the focus?
SETSHIFTED(hwnd, FALSE);// shift-click or right-click?

/* This wParam will be sent to the parent window to indentify */
/* that the toolbar sent the WM_COMMAND msg.  The hwnd of the */
/* toolbar that sent the msg will be in the lParam.      */
#ifdef _WIN32
SetWindowLong(hwnd, GWL_ID, IDC_TOOLBAR);
#else
SetWindowWord(hwnd, GWW_ID, (WORD)IDC_TOOLBAR);
#endif

/* later on, someone will set the bmp handle of the buttons */
SETBMPHANDLE(hwnd, NULL);

break;

        case WM_LBUTTONDOWN:// button goes down on a toolbar button
        case WM_RBUTTONDOWN:
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDBLCLK:

/* If we don't give ourself focus, we'll never get KEYDOWN */
/* or KEYUP messages.   */
/* Get the focus only if we're a TABSTOP and the app wants */
/* us to take focus.   */
if ( (GetWindowLong(hwnd, GWL_STYLE) & WS_TABSTOP)
&& GetFocus() != hwnd)
    SetFocus(hwnd);

/* ignore messages if window is disabled */
if (!IsWindowEnabled(hwnd))
    return 0L;

/* ignore multiple down messages (we set Capture here) */
/* also ignore if a key is down                        */
if (GetCapture() == hwnd || GETPRESSED(hwnd))
    return 0L;

/* Where did the mouse go down? */
                pt.x = (short)LOWORD(lParam);
                pt.y = (short)HIWORD(lParam);

/* which button was pressed? */
iBtnPos = toolbarIndexFromPoint(hwnd, pt);

/* If it was a valid button... */
if (iBtnPos >= 0) {
    intiOldPos;
    intiState, iType, iButton;

    /* Everything you wanted to know about this button */
    iType = toolbarTypeFromIndex(hwnd, iBtnPos);
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    iState = toolbarFullStateFromButton(hwnd, iButton);

    /* ignore downs on a grayed button, unless it's a*/
    /* custom button, then tell them anyway*/
    if (iType != BTNTYPE_CUSTOM && iState == BTNST_GRAYED)
return 0;

    /* We better get all mouse messages from now on */
    SetCapture(hwnd);

    /* Shift key or right button indicates a SHIFT down */
    SETSHIFTED(hwnd, (message == WM_RBUTTONDOWN) ||
    (wParam & MK_SHIFT));

    /* Yes, we've pressed the button down */
    SETPRESSED(hwnd, TRUE);

    /* Remember who used to have the focus, and we get it now */
    iOldPos = GETWHICH(hwnd);
    SETWHICH(hwnd, iBtnPos);

    /* For a push button, send it down */
    if (iType == BTNTYPE_PUSH)
toolbarModifyState(hwnd, iButton, BTNST_DOWN);

    /* for a checkbox or radio button (of any group),       */
    /* remember what state it was in, and send it FULL down */
    /* (with focus).    */
    if (iType == BTNTYPE_CHECKBOX || iType >= BTNTYPE_RADIO) {
toolbarModifyPrevState(hwnd, iButton, iState);
toolbarModifyState(hwnd,iButton,BTNST_FULLDOWN);
    }

    toolbarModifyActivity(hwnd, iButton, BTNACT_MOUSEDOWN);

    /* Set Double click flag appropriately */
    if (message == WM_LBUTTONDBLCLK ||
message == WM_RBUTTONDBLCLK)
NotifyParent(hwnd, (GETSHIFTED(hwnd) ? BTN_SHIFT : 0)
 + BTN_DBLCLICK + iButton);
    else
NotifyParent(hwnd, (GETSHIFTED(hwnd) ? BTN_SHIFT : 0)
 + iButton);

    /* Invalidate the Rect of the button being pressed */
    toolbarRectFromIndex(hwnd, iBtnPos, &rc);
    InvalidateRect(hwnd, &rc, FALSE);

    /* Invalidate the Rect of the button losing focus */
    toolbarRectFromIndex(hwnd, iOldPos, &rc);
    InvalidateRect(hwnd, &rc, FALSE);

    /* Force re-paint now */
    UpdateWindow(hwnd);

    /* Set a timer for repeated mouse downs */
    SetTimer(hwnd, TIMER_BUTTONREPEAT,
 MSEC_BUTTONREPEAT, NULL);
}

return 0L;

        case WM_MOUSEMOVE:

#if 0
/* This should be impossible - it means that the system lost */
/* a mouse up (maybe codeview is up?) We need to force a     */
/* mouse up at this point.     */
if (GetCapture() == hwnd &&
(wParam & (MK_LBUTTON | MK_RBUTTON) == 0))
    SendMessage(hwnd, WM_LBUTTONUP, 0, lParam);
#endif

/* Mouse moving while pressing a button?  If not, ignore. */
if (GetCapture() == hwnd) {
    intiPrevState, iState, iButton, iType;
    BOOLfPressed;

    /* Which button is being pressed down? */
    iBtnPos = GETWHICH(hwnd);

    /* Where is mouse cursor now? */
                    pt.x = (short)LOWORD(lParam);
                    pt.y = (short)HIWORD(lParam);

    /* where is button being pressed? Are we still on */
    /* top of that button or have we moved?      */
    toolbarRectFromIndex(hwnd, iBtnPos, &rc);
    fPressed = PtInRect(&rc, pt);

    /* Let go if we move off of the button, but don't */
    /* act like it was pressed.                       */
    /* Also, push it back down if we move back on top */
    /* of it (while the mouse button is STILL down).  */
    if (fPressed != GETPRESSED(hwnd)) {

/* update: is this button pressed anymore? */
SETPRESSED(hwnd, fPressed);

iType = toolbarTypeFromIndex(hwnd, iBtnPos);
iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
iState = toolbarFullStateFromButton(hwnd, iButton);

/* The mouse moved back onto the button while */
/* the mouse button was still pressed.      */
if (fPressed) {

    /* Push the push button back down again */
     if (iType == BTNTYPE_PUSH)
toolbarModifyState(hwnd, iButton,
BTNST_DOWN);

    /* Push the radio or checkbox button ALL the */
    /* way down again. */
    if (iType >= BTNTYPE_RADIO ||
iType == BTNTYPE_CHECKBOX)
toolbarModifyState(hwnd, iButton,
BTNST_FULLDOWN);

    toolbarModifyActivity(hwnd, iButton,
BTNACT_MOUSEMOVEON);
    NotifyParent(hwnd,
(GETSHIFTED(hwnd) ? BTN_SHIFT : 0) +
iButton);

/* We moved the mouse off of the toolbar button */
/* while still holding the mouse button down.   */
} else {

    /* lift the push button up */
     if (iType == BTNTYPE_PUSH)
toolbarModifyState(hwnd, iButton,
BTNST_UP);

    /* Restore radio button or checkbox button to */
    /* where it was before pressed  */
    if (iType >= BTNTYPE_RADIO ||
iType == BTNTYPE_CHECKBOX) {
iPrevState = toolbarPrevStateFromButton(hwnd,
iButton);
toolbarModifyState(hwnd, iButton, iPrevState);
    }

    toolbarModifyActivity(hwnd, iButton,
BTNACT_MOUSEMOVEOFF);
    NotifyParent(hwnd,
(GETSHIFTED(hwnd) ? BTN_SHIFT : 0) +
toolbarButtonFromIndex(hwnd, iBtnPos));
}
    }
}
return 0L;

        case WM_LBUTTONUP:
        case WM_RBUTTONUP:

/* If we don't have capture, we aren't expecting this. Ignore */
if (GetCapture() == hwnd) {
    intiPrevState, iState, iButton, iType;

    /* Who has the focus? */
    iBtnPos = GETWHICH(hwnd);

    /* Release the mouse */
    ReleaseCapture();

    /* No more repeats of the mouse button downs */
    KillTimer(hwnd, TIMER_BUTTONREPEAT);

    /* Everything you wanted to know about the button */
    toolbarRectFromIndex(hwnd, iBtnPos, &rc);
    iType = toolbarTypeFromIndex(hwnd, iBtnPos);
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    iState = toolbarFullStateFromButton(hwnd, iButton);

    /* Don't do anything if we've moved off the button */
    if (GETPRESSED(hwnd)) {

/* No longer down */
SETPRESSED(hwnd, FALSE);

/* Bring the push button up */
if (iType == BTNTYPE_PUSH)
    toolbarModifyState(hwnd, iButton, BTNST_UP);

/* Bring the checkbox to the opposite state it was in */
if (iType == BTNTYPE_CHECKBOX) {
    iPrevState = toolbarPrevStateFromButton(hwnd,
iButton);
    if (iPrevState == BTNST_DOWN)
toolbarModifyState(hwnd, iButton, BTNST_UP);
    if (iPrevState == BTNST_UP)
toolbarModifyState(hwnd, iButton, BTNST_DOWN);
}

/* Force a radio button down, and bring all   */
/* other radio buttons of this type up      */
if (iType >= BTNTYPE_RADIO) {
    toolbarModifyState(hwnd, iButton, BTNST_DOWN);
    toolbarExclusiveRadio(hwnd, iType, iButton);
}

/* Notify the parent that the mouse button came up */
/* on this button so the app can do something.     */
/* Every button should notify the app, not just a  */
/* custom button.   */
toolbarModifyActivity(hwnd, iButton, BTNACT_MOUSEUP);
NotifyParent(hwnd,
    (GETSHIFTED(hwnd) ? BTN_SHIFT : 0) + iButton);
    }
}

return 0L;

case WM_TIMER:

/* If we have a tool button down, send a repeat message */
if (GETPRESSED(hwnd)) {
    intiButton, iType;

    iBtnPos = GETWHICH(hwnd);
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    iType = toolbarTypeFromIndex(hwnd, iBtnPos);

    NotifyParent(hwnd, BTN_REPEAT +
(GETSHIFTED(hwnd) ? BTN_SHIFT : 0) +
toolbarButtonFromIndex(hwnd, iBtnPos));
}
break;

        case WM_DESTROY:
if (GETBMPHANDLE(hwnd))
    DeleteObject(GETBMPHANDLE(hwnd));
SETBMPHANDLE(hwnd, NULL);
if (GETARRAYBUTT(hwnd))
    GlobalFree(GETARRAYBUTT(hwnd));
SETARRAYBUTT(hwnd, NULL);
break;

        case WM_SETTEXT:
break;

/* MANY, MANY cases deleted */

case WM_SETFOCUS:// focus comes to toolbar window
    {
/* Remember who had the focus and give it back.  Of course, */
/* if by some wierdness that button is now grayed, give it  */
/* to the next person in line.    */
iBtnPos = GETWHICH(hwnd);
if (iBtnPos < 0 || iBtnPos >= toolbarGetNumButtons(hwnd)) {
    iBtnPos = 0;
    SETWHICH(hwnd, 0);
}

do {
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    if (toolbarFullStateFromButton(hwnd, iButton)
!= BTNST_GRAYED)
break;// give it here
    iBtnPos++;
    if (iBtnPos >= toolbarGetNumButtons(hwnd))
iBtnPos = 0;// wrap around
    if (iBtnPos == GETWHICH(hwnd))
return 0L;// uh-oh! They're all gray!
} while (iBtnPos != GETWHICH(hwnd));

SETWHICH(hwnd, iBtnPos);// give focus here

/* And redraw! */
toolbarRectFromIndex(hwnd, iBtnPos, &rc);
InvalidateRect(hwnd, &rc, FALSE);
UpdateWindow(hwnd);
return 0;
    }

case WM_KILLFOCUS:

/* Send a KEYUP if one is pending */
if (GETKEYPRESSED(hwnd))
    SendMessage(hwnd, WM_KEYUP, VK_SPACE, 0L);

/* Redraw the focused button, because now that focus is gone */
/* from our toolbar window, the focused button won't be      */
/* focused anymore, although we remember which one it was.   */
toolbarRectFromIndex(hwnd, GETWHICH(hwnd), &rc);
InvalidateRect(hwnd, &rc, FALSE);
UpdateWindow(hwnd);
return 0;

case WM_SYSKEYDOWN:
/* Send a KEYUP if one is pending */
if (GETKEYPRESSED(hwnd))
    SendMessage(hwnd, WM_KEYUP, VK_SPACE, 0L);
break;// MUST LET DEFWNDPROC RUN!!! (to handle the key)

        case WM_GETDLGCODE:
return DLGC_WANTARROWS | DLGC_WANTTAB;

case WM_KEYDOWN:

/* Window disabled or a key is already down */
if (IsWindowEnabled(hwnd) && !GETPRESSED(hwnd)) {

    /* Tab forward to next button and move focus there */
    if (wParam == VK_TAB && GetKeyState(VK_SHIFT) >= 0 ) {

/* Move Focus forward one.  If */
/* we've tabbed off of the toolbar, it's time */
/* to go on to the next control. We need to invldte */
/* because we might be the only control and we need */
/* to repaint to show the new button with highlight */
/* after it wrapped around the end of the toolbar.  */
if (!toolbarMoveFocus(hwnd, FALSE)) {
    PostMessage(GetParent(hwnd), WM_NEXTDLGCTL, 0, 0L);
    toolbarRectFromIndex(hwnd, GETWHICH(hwnd), &rc);
    InvalidateRect(hwnd, &rc, FALSE);
}

return 0L;
    }
    if (wParam == VK_TAB && GetKeyState(VK_SHIFT) < 0 ) {

/* Move focus backward one.  If */
/* We've tabbed off of the toolbar, it's time    */
/* to go on to the next control. We need to invldte */
/* because we might be the only control and we need */
/* to repaint to show the new button with highlight */
/* after it wrapped around the end of the toolbar.  */
if (!toolbarMoveFocus(hwnd, TRUE)) {
    PostMessage(GetParent(hwnd), WM_NEXTDLGCTL, 1, 0L);
    toolbarRectFromIndex(hwnd, GETWHICH(hwnd), &rc);
    InvalidateRect(hwnd, &rc, FALSE);
}

return 0L;
    }
    if ((wParam == VK_SPACE) && (GetCapture() != hwnd)) {

intiButton, iType, iState;

/* Same as mouse button down -- Press the button! */
iBtnPos = GETWHICH(hwnd);
iType = toolbarTypeFromIndex(hwnd, iBtnPos);
iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
iState = toolbarFullStateFromButton(hwnd, iButton);

/* ignore multiple key downs */
if (!GETKEYPRESSED(hwnd)) {

    SETKEYPRESSED(hwnd, TRUE);// a key is pressed

    SETSHIFTED(hwnd, FALSE);// NEVER shifted
    SETPRESSED(hwnd, TRUE);// a button is pressed

    /* Push button goes down - with focus */
    if (iType == BTNTYPE_PUSH)
toolbarModifyState(hwnd, iButton, BTNST_DOWN);

    /* Radio or checkbox button goes full down */
    /* with focus - and remember previous state*/
    if (iType >= BTNTYPE_RADIO ||
iType == BTNTYPE_CHECKBOX) {
toolbarModifyPrevState(hwnd, iButton, iState);
toolbarModifyState(hwnd, iButton,
BTNST_FULLDOWN);
    }

    toolbarModifyActivity(hwnd, iButton,
BTNACT_KEYDOWN);
    NotifyParent(hwnd, (GETSHIFTED(hwnd)
? BTN_SHIFT : 0) + iButton);

    return 0L;
}

/* If this is another KEYDOWN msg, it's a REPEAT */
/* Notify parent.                                */
NotifyParent(hwnd, BTN_REPEAT +
(GETSHIFTED(hwnd) ? BTN_SHIFT : 0) +
toolbarButtonFromIndex(hwnd,
GETWHICH(hwnd)));
    }
}
break;

case WM_KEYUP:

/* A button was pressed and should come up now */
if ((wParam == VK_SPACE) && (GETKEYPRESSED(hwnd))) {
    intiButton, iState, iType, iPrevState;

    iBtnPos = GETWHICH(hwnd);// which button?
    SETKEYPRESSED(hwnd, FALSE);// let go
    SETPRESSED(hwnd, FALSE);

    /* Everything about this button */
    toolbarRectFromIndex(hwnd, iBtnPos, &rc);
    iType = toolbarTypeFromIndex(hwnd, iBtnPos);
    iButton = toolbarButtonFromIndex(hwnd, iBtnPos);
    iState = toolbarFullStateFromButton(hwnd, iButton);

    /* Bring a push button up */
    if (iType == BTNTYPE_PUSH)
toolbarModifyState(hwnd, iButton, BTNST_UP);

    /* Bring a checkbox to the opposite state it was in */
    if (iType == BTNTYPE_CHECKBOX) {
iPrevState = toolbarPrevStateFromButton(hwnd, iButton);
if (iPrevState == BTNST_DOWN)
    toolbarModifyState(hwnd, iButton, BTNST_UP);
if (iPrevState == BTNST_UP)
    toolbarModifyState(hwnd, iButton, BTNST_DOWN);
    }

    /* Bring a radio button down, and bring all others in */
    /* its group up.  */
    if (iType >= BTNTYPE_RADIO) {
toolbarModifyState(hwnd, iButton, BTNST_DOWN);
toolbarExclusiveRadio(hwnd, iType, iButton);
    }

    toolbarModifyActivity(hwnd, iButton, BTNACT_KEYUP);
    NotifyParent(hwnd, toolbarButtonFromIndex(hwnd,
(GETSHIFTED(hwnd) ? BTN_SHIFT : 0) +
GETWHICH(hwnd)));
}
break;

case WM_SYSCOLORCHANGE:
/* load the bitmap of what all the buttons look like */
/* and change the colours to the system colours.     */
hInst = GETHINST(hwnd);
ibmp = GETBMPINT(hwnd);
hbm = GETBMPHANDLE(hwnd);
if (hbm)
    DeleteObject(hbm);
hbm = LoadUIBitmap(hInst, MAKEINTRESOURCE(ibmp),
    GetSysColor(COLOR_BTNTEXT),
    GetSysColor(COLOR_BTNFACE),
    GetSysColor(COLOR_BTNSHADOW),
    GetSysColor(COLOR_BTNHIGHLIGHT),
    GetSysColor(COLOR_BTNFACE),
    GetSysColor(COLOR_WINDOWFRAME));
SETBMPHANDLE(hwnd, hbm);
#ifdef _WIN32
return (long) hbm;
#else
return MAKELONG(hbm, 0);
#endif

        case WM_ERASEBKGND:
break;

        case WM_PAINT:

/* Call our paint code */
BeginPaint(hwnd, &ps);
toolbarPaintControl(hwnd, ps.hdc);
EndPaint(hwnd, &ps);

return 0L;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);

}


TOOLBAR.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   toolbar.h: Toolbar include file
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*****************************************************************************
*                                                                            *
*  Program Description: Implements a generic toolbar.              *
*                                                                            *
*  Here's how to use it:                                                     *
*                                                                            *
*            Include the source files "toolbar.h" and "toolbar.c" in your    *
*  application.                                                              *
*                                                                            *
*            Include a line in your application's RC file that gives a file  *
*  name with a resource id eg. IDBMP_BUTTONS.  This is a .BMP file that      *
*  contains all of the pictures of the buttons you want on your toolbar.     *
*  Also, make a define for your label with a unique value.  If your app has  *
*  more than one toolbar, and all toolbars don't share a bitmap file, then   *
*  you will need several defines.                                            *
*                                                                            *
*  e.g.IDBMP_BUTTONS     BITMAP     "buttons.bmp"                   *
*      IDBMP_ARROWS      BITMAP     "arrows.bmp"                    *
*                                                                            *
*            This file must have the different buttons across horizontally   *
*  and the different states for these buttons vertically.  Change the        *
*  defines in this header file to match the button names and state names of  *
*  your buttons.  You must include the states listed here, and actually      *
*  you probably won't need to change them at all.  The numbers for a button  *
*  or state are indexes into the bitmap, so the pictures must match.         *
*                                                                            *
*  STATE DESCRIPTIONS:                                                       *
*                       GRAYED:  The button cannot be pressed & is inactive  *
*                           UP:  The button is up                            *
*                         DOWN:  The button is down                          *
*                      FOCUSUP:  The button is up and is the one with focus  *
*                    FOCUSDOWN:  The button is down and is the one with focus*
*                     FULLDOWN:  A checkbox button has this additional state *
*                                where it is all the way down when pressed   *
*                                and when it is let go, it will go into      *
*                                either the UP or DOWN state (maybe focused) *
*                                                                            *
*  When you draw the pictures, make sure to get the right state in the right *
*  vertical position in the bitmap to match the #define's.                   *
*                                                                            *
*  A button can also have a type associated with it:                         *
*                                                                            *
*                 PUSH:  When pressed it goes down, when let go it bounces   *
*                        up.  Therefore, when you aren't currently holding   *
*                        the mouse button or space bar on it, it will        *
*                        ALWAYS be in the up position. It can be in any      *
*                        state except FULLDOWN, which is invalid.            *
*                                                                            *
*             CHECKBOX:  This button can be up or down.  When pushed, it     *
*                        toggles into the opposite state.  However, it       *
*                        is always in the FULLDOWN state when being held     *
*                        down with the mouse button or space bar, and when   *
*                        let go, it will go into the opposite state of what  *
*                        it was in before you pressed it.  E.G.  The button  *
*                        is up.  You press it, and it goes way down. You let *
*                        go, and it comes up a bit, but it's still down.  You*
*                        press it again, and it goes further down before     *
*                        popping all the way up.                             *
*                                                                            *
*                RADIO:  This is a group of buttons that can be up or down,  *
*                        and also have the intermediate step of being        *
*                        FULLDOWN when being held down.  But, when you       *
*                        push one of the radio buttons down, all other radio *
*                        buttons in its group will pop up.  Any group can    *
*                        have only 1 down at a time, and 1 must be down.     *
*                                                                            *
*                CUSTOM: If your application is wierd, you can have a custom *
*                        type button that does anything you want it to.      *
*                                                                            *
*  First, your app must call:    toolbarInit(hInst, hPrev);                  *
*  with the two instance parameters to register a toolbar window class.      *
*  Then your app is free to call CreateWindow with a class of                *
*  szToolBarClass   to create one or more toolbar windows anywhere it wants  *
*  and of any size it wants, presumably as the child window of another of the*
*  app's windows.  The file that creates the window must declare an          *
*  extern char szToolBarClass[];   All messages about activity to a toolbar  *
*  button will go to the parent window of the toolbar.                       *
*                                                                            *
*  Next, call:     toolbarSetBitmap(HWND hwnd, HANDLE hInst, int ibmp,       *
* POINT ptSize);      *
*  Pass it the resource ID (eg. IDBMP_BUTTONS) to tell the toolbar where to  *
*  find the pictures for the buttons.  Also pass a point with the width and  *
*  height of each button (eg. 24 X 22) so it knows how to find individual    *
*  buttons in the bitmap file.                                               *
*                                                                            *
*  Next, call:     toolbarAddTool(HWND hwnd, TOOLBUTTON tb);                 *
*  as many times as you want to add a button to the toolbar specified by     *
*  hwnd.  You fill in the "tb" struct with the following information:        *
*                                                                            *
*       tb.rc        = the rect in the toolbar window to place the button    *
*                      based at 0,0 and measured in pixels.                  *
*       tb.iButton   = the ID of the button you wish the add (which is       *
*                      the horizontal offset into the bitmap of buttons).    *
*                      Only one of each button allowed.  Use one of the      *
*                      defines (BTN_??????).                                 *
*       tb.iState    = the initial state of the button (GRAYED, UP, DOWN).   *
*                      If you wish, you can specify a FOCUS'ed state to give *
*                      any button you wish the focus.  By default, it's the  *
*                      one furthest left and tabbing order goes to the right.*
*                      This is the vertical offset into the bitmap.          *
*                      Use one of the defines (BTNST_?????).                 *
*       tb.iType     = The type of button (BTNTYPE_???).  Either pushbutton, *
*                      checkbox, or radio button. (or custom).  If it is a   *
*                      radio button, you can have many groups of radio btn's *
*                      on the same toolbar.  Type BTNTYPE_RADIO is one group.*
*                      Use BTNTYPE_RADIO+1 for another group, BTNTYPE_RADIO+2*
*                      for a third group, etc.  You have thousands.          *
*       tb.iString   = The resource ID of a string to be associated with     *
*                      this button (if you'd like).                          *
*                                                                            *
*                                                                            *
*   At any time in the app, you can call toolbarAddTool to add more buttons  *
*   or toolbarRemoveTool to take some away.  To take one away, identify it   *
*   with it's button ID (horizontal offset in the bitmap).                   *
*                                                                            *
*   You can also call toolbarRetrieveTool to get the TOOLBUTTON struct back  *
*   from a button that is on the toolbar.  This is the way to change a       *
*   button's position.  Change the tb.rc and then Remove and Add the button  *
*   again so that the tabbing order will be re-calculated based on the new   *
*   rect of the tool.                                                        *
*                                                                            *
*   Now, all buttons will automatically behave properly.  They'll go up and  *
*   down as you press on them, or use the keyboard, groups of radio buttons  *
*   will pop up as you press a different one down, etc. etc. etc.            *
*   You don't have to do a thing!                                            *
*                                                                            *
*   The parent of the toolbar window will get a WM_COMMAND message with      *
*   a wParam of IDC_TOOLBAR  whenever anything happens to a button.          *
*   The LOWORD of the lParam is the hwnd of the toolbar window that has the  *
*   button on it.  The (HIWORD & 0xFF) is the button ID of the button.       *
*   Remember to change IDC_TOOLBAR to something unique.                      *
*                                                                            *
*   The app can then call   toolbarIndexFromButton(hwnd, buttonID)           *
*   to get the index of the button (used for subsequent calls).              *
*                                                                            *
*   Then call:      toolbarStateFromButton(hwnd, buttonID)                   *
*                                                                            *
*                   to get either BTNST_UP or BTNST_DOWN.  This is the       *
*                   NEW state of the button since the activity on the        *
*                   button.  It can also be BTNST_GRAYED, but you won't get  *
*                   any activity messages while it's grayed, unless it is a  *
*                   cutsom button.                                           *
*                                                                            *
*             Call  toolbarFullStateFromButton(hwnd, buttonID)               *
*                                                                            *
*                   to get more detail about the state.  It can also return  *
*                   BTNST_FULLDOWN as well as the above states. In the case  *
*                   of BTNST_FULLDOWN, you'll have to call                   *
*                   toolbarPrevStateFromButton(hwnd, btn ID) to get the state*
*                   before it went full down.                                *
*                                                                            *
*                   toolbarPrevStateFromButton(hwnd, buttonID)               *
*                                                                            *
*                   is only valid when the state is BTNST_FULLDOWN.          *
*                                                                            *
*                   toolbarActivityFromIndex(hwnd, buttonID)                 *
*                                                                            *
*                   tells you what just happened to the button.              *
*                   BTNACT_KEYDOWN, BTNACT_MOUSEUP, etc. are possibilities.  *
*                   BTNACT_MOUSEMOUSEOFF means that they pressed it down and *
*                   moved the mouse off of the button (  so it was re- drawn *
*                   in its previous state before being pressed).             *
*                   BTNACT_MOUSEMOUSEON  means that the above happened and   *
*                   then the mouse moved back on top of the button again, so *
*                   the button was re-drawn as if it was pushed again.       *
*                                                                            *
*                   For any of the above activities.......                   *
*                                                                            *
*   HIWORD & BTN_SHIFT     is set if this activity involves the right mouse  *
*                          button, or else it is clear.                      *
*   HIWORD & BTN_DBLCLICK  is set means that this mouse button down activity *
*                          is really a double click (if you care).           *
*                                                                            *
*           If you are a custom button, you can also receive this message... *
*                                                                            *
*   HIWORD & BTN_REPEAT    is set means that the button or key is being held *
*                          down, and you are being sent many down messages   *
*                          in a row.  The first such message is sent with    *
*                          this flag clear, all others have this flag set.   *
*                          If you are a custom button, you will have to      *
*                          ignore messages that are repeats if you don't     *
*                          want to get many down messages in a row.          *
*                                                                            *
*                                                                            *
*                    toolbarStringFromIndex(hwnd, index)                     *
*                                                                            *
*                    will return you the string resource ID you gave when    *
*                    you registered this button.                             *
*                                                                            *
*                                                                            *
*  IMPORTANT !!!!!!!!!!!!!!!!!!!                                             *
*  =============================                                             *
*                                                                            *
*  When you get the state of a button, it's already been changed by the      *
*  activity so it's the NEW STATE!!!!!!!!!                                   *
*                                                                            *
*   EXCEPT!!!   for a custom button!  For a custom button, NOTHING WILL      *
*   happen, you have to do it all yourself!!!! So the state is going to be   *
*   the state BEFORE the activity and you have to call                       *
*   toolbarModifyState(hwnd, buttonID, newState) to change the state         *
*   yourself!!!!                                                             *
*                                                                            *
*   You also have toolbarGetNumButtons(hwnd) to tell you how many are on the *
*   the toolbar.                                                             *
*   And... you have other routines you can use if you really want.           *
*                                                                            *
*   ENJOY!!                                                                  *
*                                                                            *
*  P.S.  Don't forget to pass on WM_SYSCOLORCHANGE msgs to each toolbar.     *
*                                                                            *
*****************************************************************************/

#define TOOLGROW8// power of 2

#define IDC_TOOLBAR189// wParam sent to Parent

/* We keep an array of these around (one for each button on the toolbar) */

typedef struct {
RECTrc;// draw it at this postion in the toolbar
intiButton;// it's this button
intiState;// in this state
intiPrevState;// for non-push buttons - last state
intiType;// type of button
intiActivity;// what just happened to button
intiString;// string resource associated with button
} TOOLBUTTON, FAR *LPTOOLBUTTON;

BOOL FAR PASCAL toolbarInit(HANDLE hInst, HANDLE hPrev);
BOOL FAR PASCAL toolbarSetBitmap(HWND hwnd, HANDLE hInst, int ibmp,
POINT ptSize);
BOOL FAR PASCAL toolbarAddTool(HWND hwnd, TOOLBUTTON tb);
BOOL FAR PASCAL toolbarRetrieveTool(HWND hwnd, int iButton, LPTOOLBUTTON tb);
BOOL FAR PASCAL toolbarRemoveTool(HWND hwnd, int iButton);
int FAR PASCAL toolbarGetNumButtons(HWND hwnd);
int FAR PASCAL toolbarButtonFromIndex(HWND hwnd, int iBtnPos);
int FAR PASCAL toolbarIndexFromButton(HWND hwnd, int iButton);
int FAR PASCAL toolbarPrevStateFromButton(HWND hwnd, int iButton);
int FAR PASCAL toolbarActivityFromButton(HWND hwnd, int iButton);
int FAR PASCAL toolbarIndexFromPoint(HWND hwnd, POINT pt);
BOOL FAR PASCAL toolbarRectFromIndex(HWND hwnd, int iBtnPos, LPRECT lprc);
int FAR PASCAL toolbarStringFromIndex(HWND hwnd, int iBtnPos);
int FAR PASCAL toolbarStateFromButton(HWND hwnd, int iButton);
int FAR PASCAL toolbarFullStateFromButton(HWND hwnd, int iButton);
int FAR PASCAL toolbarTypeFromIndex(HWND hwnd, int iBtnPos);
BOOL FAR PASCAL toolbarModifyState(HWND hwnd, int iButton, int iState);
BOOL FAR PASCAL toolbarModifyString(HWND hwnd, int iButton, int iString);
BOOL FAR PASCAL toolbarModifyPrevState(HWND hwnd, int iButton, int iPrevState);
BOOL FAR PASCAL toolbarModifyActivity(HWND hwnd, int iButton, int iActivity);
BOOL FAR PASCAL toolbarExclusiveRadio(HWND hwnd, int iType, int iButton);
BOOL FAR PASCAL toolbarMoveFocus(HWND hwnd, BOOL fBackward);
BOOL FAR PASCAL toolbarSetFocus(HWND hwnd, int iButton);
HBITMAP FAR PASCAL  LoadUIBitmap(
    HANDLE      hInstance,          // EXE file to load resource from
    LPCSTR      szName,             // name of bitmap resource
    COLORREF    rgbText,            // color to use for "Button Text"
    COLORREF    rgbFace,            // color to use for "Button Face"
    COLORREF    rgbShadow,          // color to use for "Button Shadow"
    COLORREF    rgbHighlight,       // color to use for "Button Hilight"
    COLORREF    rgbWindow,          // color to use for "Window Color"
    COLORREF    rgbFrame);          // color to use for "Window Frame"

/* In a bitmap file, each button is the same size, and contains
 * the picture of a button.  Each column contains the picture of a distinct
 * button (e.g. BTN_REWIND, BTN_REVERSE, etc.) and each row contains
 * a specific button state (BTNST_UP, BTNST_DOWN,
 * BTNBAR_GRAYED, etc. just as an example).
 *
 */

#define TB_FIRST-1
#define TB_LAST-2

#define BTNST_GRAYED0//
#define BTNST_UP1//
#define BTNST_DOWN2//
#define BTNST_FOCUSUP3//
#define BTNST_FOCUSDOWN4//
#define BTNST_FULLDOWN5//

#define BTN_REPEAT0x100// add this to button index
#define BTN_SHIFT0x200
#define BTN_DBLCLICK0x400

/* Types of buttons */

#define BTNTYPE_PUSH0
#define BTNTYPE_CHECKBOX1
#define BTNTYPE_CUSTOM2
#define BTNTYPE_RADIO3// MUST BE LAST to reserve room for more
// radio groups.  (3 == one group,
// 4 == another group, etc.)

/* tells parent recent activity on button */
#define BTNACT_MOUSEDOWN0// clicked mouse button down on tool
#define BTNACT_MOUSEUP1// let go of mouse button while on tool
#define BTNACT_MOUSEMOVEOFF2// moved mouse off tool while btn down
#define BTNACT_MOUSEMOVEON3// moved back on tool (btn still down)
#define BTNACT_MOUSEDBLCLK4// dbl clicked on tool
#define BTNACT_KEYDOWN5// key down on tool
#define BTNACT_KEYUP6// key up from tool

/* constants */
#define MSEC_BUTTONREPEAT200// milliseconds for auto-repeat

/* timers */
#define TIMER_BUTTONREPEAT1// timer for button auto-repeat

// Window words for Toolbar
#ifdef _WIN32
#define GWL_ARRAYBUTT0/* Pointer to array of buttons  */
#define GWL_NUMBUTTONS4/* Number of buttons in array   */
#define GWL_PRESSED8/* Is a button currently pressed*/
#define GWL_KEYPRESSED12      /* Is a key currently pressed?  */
#define GWL_WHICH16          /* Which button has the focus?  */
#define GWL_SHIFTED20/* Is it rt-click or shift-left?*/
#define GWL_BMPHANDLE24/* handle to bmp of the buttons */
#define GWL_BMPINT28/* resource int of button bmp*/
#define GWL_BUTTONSIZE32/* a point (x=hi y=lo)*/
#define GWL_HINST36/* hinst of the app   */
#define TOOLBAR_EXTRABYTES40
#else
#define GWW_ARRAYBUTT0/* Pointer to array of buttons  */
#define GWW_NUMBUTTONS2/* Number of buttons in array   */
#define GWW_PRESSED4/* Is a button currently pressed*/
#define GWW_KEYPRESSED6/* Is a key currently pressed?  */
#define GWW_WHICH8/* Which button has the focus?  */
#define GWW_SHIFTED10/* Is it rt-click or shift-left?*/
#define GWW_BMPHANDLE12/* handle to bmp of the buttons */
#define GWW_BMPINT14/* resource int of button bmp*/
#define GWL_BUTTONSIZE16/* a point (x=hi y=lo)*/
#define GWW_HINST20/* hinst of the app   */
#define TOOLBAR_EXTRABYTES22
#endif


VIDCAP.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   vidcap.c: WinMain and command processing
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <dos.h>
#include <shellapi.h>
#include <vfw.h>

#include "vidcap.h"
#include "vidframe.h"
#include "profile.h"

// generic window control classes
#include "toolbar.h"
#include "status.h"
#include "arrow.h"
#include "rlmeter.h"
#include "help.h"

// the standard toolbar class 'exports' this but doesn't put it in the
// header file
extern char     szToolBarClass[];//HACK!

// height of the buttons on a toolbar - depends on the
// size of the bitmaps within IDBMP_TOOLBAR
#define BUTTONWIDTH     24
#define BUTTONHEIGHT    22
#define TOOLBAR_HEIGHT          BUTTONHEIGHT + 6

// description and layout of toolbar buttons within IDBMP_TOOLBAR
#define APP_NUMTOOLS 8

#define BTN_SETFILE0
#define BTN_EDITCAP1
#define BTN_LIVE2
#define BTN_CAPFRAME3
#define BTN_CAPSEL4
#define BTN_CAPAVI5
#define BTN_CAPPAL6
#define BTN_OVERLAY7

static int           aiButton[] = {BTN_SETFILE, BTN_EDITCAP,
                            BTN_LIVE, BTN_OVERLAY, BTN_CAPFRAME,
                            BTN_CAPSEL, BTN_CAPAVI, BTN_CAPPAL };
static int           aiState[] = {BTNST_FOCUSUP, BTNST_UP,
                            BTNST_UP, BTNST_UP, BTNST_UP,
                            BTNST_UP, BTNST_UP, BTNST_UP};
static int           aiType[] ={BTNTYPE_PUSH, BTNTYPE_PUSH,
                            BTNTYPE_CHECKBOX, BTNTYPE_CHECKBOX,
                            BTNTYPE_PUSH,
                            BTNTYPE_PUSH, BTNTYPE_PUSH, BTNTYPE_PUSH};
static int           aiString[] = { IDC_toolbarSETFILE,
                            IDC_toolbarEDITCAP, IDC_toolbarLIVE,
                            IDC_toolbarOVERLAY,
                            IDC_toolbarCAPFRAME, IDC_toolbarCAPSEL,
                            IDC_toolbarCAPAVI, IDC_toolbarCAPPAL };
static int           aPos[] = { 10, 35, 75, 100, 150, 175, 200, 225 };

//
// Global Variables
//

// preferences
BOOL gbCentre;
BOOL gbToolBar;
BOOL gbStatusBar;
BOOL gbAutoSizeFrame;
int gBackColour;

BOOL gbLive, gbOverlay;
BOOL gfIsRTL;

// saved window sizes
int gWinX, gWinY;
int gWinCX, gWinCY;
int gWinShow;

// command line options
int gCmdLineDeviceID = -1;

char           gachAppName[]  = "vidcapApp" ;
char           gachIconName[] = "vidcapIcon" ;
char           gachMenuName[] = "vidcapMenu" ;
char           gachAppTitle[20];    //VidCap
char           gachCaptureFile[_MAX_PATH];
char           gachMCIDeviceName[21];
char           gachString[128] ;
char           gachBuffer[200] ;
char           gachLastError[256];

HINSTANCE      ghInstApp ;
HWND           ghWndMain = NULL ;
HWND           ghWndFrame;      // child of ghWndMain  - frames and scrolls
HWND           ghWndCap  ;      // child of ghWndCap
HWND           ghWndToolBar;
HWND           ghWndStatus;

HANDLE         ghAccel ;
WORD           gwDeviceIndex ;
WORD           gwPalFrames = DEF_PALNUMFRAMES ;
WORD           gwPalColors = DEF_PALNUMCOLORS ;
WORD           gwCapFileSize ;

CAPSTATUS      gCapStatus ;
CAPDRIVERCAPS  gCapDriverCaps ;
CAPTUREPARMS   gCapParms ;
BOOL           gbHaveHardware;
UINT           gDriverCount;
BOOL           gbIsScrncap;  // For Scrncap.drv, we must yield
BOOL           gbInLayout;
UINT           gAVStreamMaster;

HANDLE         ghwfex ;
LPWAVEFORMATEX glpwfex ;

FARPROC        fpErrorCallback ;
FARPROC        fpStatusCallback ;
FARPROC        fpYieldCallback ;

// set to false when we capture a palette (or if we have warned him and
// he says its ok
BOOL bDefaultPalette = TRUE;

#ifdef DEBUG
intnTestCount;
#endif

// c-runtime cmd line
extern char ** __argv;
extern int __argc;

#define LimitRange(Val,Low,Hi) (max(Low,(min(Val,Hi))))

//
// Function prototypes
//
LONG FAR PASCAL MainWndProc(HWND, UINT, UINT, LONG) ;
LRESULT FAR PASCAL ErrorCallbackProc(HWND, int, LPSTR) ;
LRESULT FAR PASCAL StatusCallbackProc(HWND, int, LPSTR) ;
LRESULT FAR PASCAL YieldCallbackProc(HWND) ;
void vidcapSetLive(BOOL bLive);
void vidcapSetOverlay(BOOL bOverlay);
void vidcapSetCaptureFile(LPSTR pFileName);

BOOL vidcapRegisterClasses(HINSTANCE hInstance, HINSTANCE hPrevInstance);
BOOL vidcapCreateWindows(HINSTANCE hInstance, HINSTANCE hPrevInstance);
void vidcapLayout(HWND hwnd);
BOOL vidcapEnumerateDrivers(HWND hwnd);
BOOL vidcapInitHardware(HWND hwnd, HWND hwndCap, UINT uIndex);
void vidcapReadProfile(void);
void vidcapWriteProfile(void);
void vidcapReadSettingsProfile(void);
void vidcapWriteSettingsProfile(void);

/* --- initialisation -------------------------------------------------- */

//
// WinMain: Application Entry Point Function
//
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
///////////////////////////////////////////////////////////////////////
//  hInstance:      handle for this instance
//  hPrevInstance:  handle for possible previous instances
//  lpszCmdLine:    long pointer to exec command line
//  nCmdShow:       Show code for main window display
///////////////////////////////////////////////////////////////////////

    MSG          msg ;
    BOOL bValidCmdline;
    BOOL fOK;
    int i;
    char ach[2];

    ghInstApp = hInstance ;
    LoadString(hInstance, IDS_CAP_RTL, ach, sizeof(ach));
    gfIsRTL = ach[0] == '1';
    gCmdLineDeviceID = -1;

    // read the app title string - used in several message boxes
    LoadString(hInstance, IDS_APP_TITLE, gachAppTitle, sizeof(gachAppTitle));

    // read defaults out of the registry
    vidcapReadProfile();

    // look for cmd line options
    bValidCmdline = TRUE;

    for ( i = 1; (i < __argc) && bValidCmdline; i++) {
        if ((__argv[i][0] == '/') || (__argv[i][0] == '-')) {

            switch(__argv[i][1]) {
            case 'D':
            case 'd':
                if (gCmdLineDeviceID < 0) {
                    // allow "-d0" and "-d 0"
                    PSTR p = &__argv[i][2];

                    if ((*p == 0) && ((i+1) < __argc)) {
                        p = __argv[++i];
                    }

                    gCmdLineDeviceID = atoi(p);
                } else {
                    bValidCmdline = FALSE;
                }
                break;

            default:
                bValidCmdline = FALSE;
            }
        } else {
            bValidCmdline = FALSE;
        }
    }

    if (gCmdLineDeviceID == -1)
gCmdLineDeviceID = 0;

    if (!bValidCmdline) {
        MessageBoxID(IDS_ERR_CMDLINE, MB_OK|MB_ICONEXCLAMATION);
        return(0);
    }

    if (!vidcapRegisterClasses(hInstance, hPrevInstance)) {

        MessageBoxID(IDS_ERR_REGISTER_CLASS,
        MB_ICONEXCLAMATION) ;
        return 0 ;
    }

    if (!vidcapCreateWindows(hInstance, hPrevInstance)) {

        MessageBoxID(IDS_ERR_CREATE_WINDOW,
        MB_ICONEXCLAMATION | MB_OK) ;
        return IDS_ERR_CREATE_WINDOW ;
    }

    // Get the default setup for video capture from the AVICap window
    capCaptureGetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;

    // Overwrite the defaults with settings we have saved in the profile
    vidcapReadSettingsProfile();

    // Show the main window before connecting the hardware as this can be
    // time consuming and the user should see something happening first...
    ShowWindow(ghWndMain, nCmdShow) ;
    UpdateWindow(ghWndMain) ;
    ghAccel = LoadAccelerators(hInstance, "VIDCAP") ;

    // Create a list of all capture drivers and append them to the Options menu
    if (!(fOK = vidcapEnumerateDrivers(ghWndMain))) {
LoadString(ghInstApp, IDS_ERR_FIND_HARDWARE, gachLastError, sizeof(gachLastError));
    }
    // Try to connect to a capture driver
    else if (fOK = vidcapInitHardware(ghWndMain, ghWndCap,
       bValidCmdline ? gCmdLineDeviceID : 0)) {
// Hooray, we now have a capture driver connected!
        vidcapSetCaptureFile(gachCaptureFile);
    }

    if (!fOK) {
        if (!DoDialog(ghWndMain, IDD_NoCapHardware, NoHardwareDlgProc,
                        (LONG) (LPSTR) gachLastError)) {
            // The user has asked to abort, since no driver was available
            PostMessage(ghWndMain, WM_COMMAND,
                        GET_WM_COMMAND_MPS(IDM_F_EXIT, 0, 0));
        }
    }

    // All set; get and process messages
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (! TranslateAccelerator(ghWndMain, ghAccel, &msg)) {
            TranslateMessage(&msg) ;
            DispatchMessage(&msg) ;
        }
    }

    return msg.wParam;
}  // End of WinMain

BOOL
vidcapRegisterClasses(HINSTANCE hInstance, HINSTANCE hPrevInstance)
{
    WNDCLASS wc;

    if (! hPrevInstance) {
        // If it's the first instance, register the window class
        wc.lpszClassName = gachAppName ;
        wc.hInstance     = hInstance ;
        wc.lpfnWndProc   = MainWndProc ;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
        wc.hIcon         = LoadIcon(hInstance, gachIconName) ;
        wc.lpszMenuName  = gachMenuName ;
        wc.hbrBackground = GetStockObject(WHITE_BRUSH) ;
        wc.style         = CS_HREDRAW | CS_VREDRAW ;
        wc.cbClsExtra    = 0 ;
        wc.cbWndExtra    = 0 ;

        if (!RegisterClass(&wc)) {
            return(FALSE);
        }

        if (!ArrowInit(hInstance)) {
            return(FALSE);
        }

        if (!RLMeter_Register(hInstance)) {
            return(FALSE);
        }
    }

    if (!toolbarInit(hInstance, hPrevInstance)) {
        return(FALSE);
    }

    if (!statusInit(hInstance, hPrevInstance)) {
        return(FALSE);
    }
    return(TRUE);

}

BOOL
vidcapCreateWindows(HINSTANCE hInstance, HINSTANCE hPrevInstance)
{

    POINT pt;
    RECT rc;
    TOOLBUTTON tb;
    int i;

    // Create Application's Main window
    ghWndMain = CreateWindowEx(
            gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0,
            gachAppName,
            gachAppTitle,
            WS_CAPTION      |
            WS_SYSMENU      |
            WS_MINIMIZEBOX  |
            WS_MAXIMIZEBOX  |
            WS_THICKFRAME   |
            WS_CLIPCHILDREN |
            WS_OVERLAPPED,
            gWinX, gWinY,
            gWinCX, gWinCY,
            NULL,
            NULL,
            hInstance,
            0) ;

    if (ghWndMain == NULL) {
        return(FALSE);
    }

    /*
     * create a vidframe child window - this will create a child
     * AVICAP window within itself.
     *
     * Don't worry about size and position - vidcapLayout will do this
     * later (once we know the video format size).
     */
    ghWndFrame = vidframeCreate(
                    ghWndMain,
                    hInstance,
                    hPrevInstance,
                    0, 0, 0, 0,
                    &ghWndCap);

    if ((ghWndFrame == NULL) || (ghWndCap == NULL)) {
        return(FALSE);
    }

    // Register the status and error callbacks before driver connects
    // so we can get feedback about the connection process
    fpErrorCallback = MakeProcInstance((FARPROC)ErrorCallbackProc, ghInstApp) ;
    capSetCallbackOnError(ghWndCap, fpErrorCallback) ;

    fpStatusCallback = MakeProcInstance((FARPROC)StatusCallbackProc, ghInstApp) ;
    capSetCallbackOnStatus(ghWndCap, fpStatusCallback) ;

    // We'll only install a yield callback later if using Scrncap.drv
    fpYieldCallback = MakeProcInstance((FARPROC)YieldCallbackProc, ghInstApp) ;

    /*
     * CREATE THE TOOL BAR WINDOW
     */
    /* NOTE: let vidcapLayout() position it */
    ghWndToolBar = CreateWindowEx(
            gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0,
            szToolBarClass,
            NULL,
            WS_CHILD|WS_BORDER|WS_VISIBLE|WS_TABSTOP|
            WS_CLIPSIBLINGS,
            0, 0,
            0, 0,
            ghWndMain,
            NULL,
            hInstance,
            NULL);

    if (ghWndToolBar == NULL) {
        return(FALSE);
    }

    /* set the bitmap and button size to be used for this toolbar */
    pt.x = BUTTONWIDTH;
    pt.y = BUTTONHEIGHT;
    toolbarSetBitmap(ghWndToolBar, hInstance, IDBMP_TOOLBAR, pt);

    for (i = 0; i < APP_NUMTOOLS; i++) {
rc.left = aPos[i];
rc.top = 2;
rc.right = rc.left + pt.x;
rc.bottom = rc.top + pt.y;
tb.rc = rc;
tb.iButton = aiButton[i];
tb.iState = aiState[i];
tb.iType = aiType[i];
tb.iString = aiString[i];
toolbarAddTool(ghWndToolBar, tb);
    }

    // create the status bar - let vidcapLayout do the positioning
    ghWndStatus = CreateWindowEx(
                    gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0,
                    szStatusClass,
                    NULL,
                    WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS,
                    0, 0,
                    0, 0,
                    ghWndMain,
                    NULL,
                    hInstance,
                    NULL);
    if (ghWndStatus == NULL) {
        return(FALSE);
    }

    return(TRUE);

}

/*
 * Enumerate the potential capture drivers and add the list to the Options
 * menu.  This function is only called once at startup.
 * Returns FALSE if no drivers are available.
 */
BOOL
vidcapEnumerateDrivers(HWND hwnd)
{
    char    achDeviceVersion[80] ;
    char    achDeviceAndVersion[160] ;
    UINT    uIndex ;
    HMENU   hMenuSub;

    gDriverCount = 0 ;

    hMenuSub = GetSubMenu (GetMenu (hwnd), 2);  // Options menu

    for (uIndex = 0 ; uIndex < MAXVIDDRIVERS ; uIndex++) {
        if (capGetDriverDescription(uIndex,
                       (LPSTR)achDeviceAndVersion, sizeof(achDeviceAndVersion),
                       (LPSTR)achDeviceVersion, sizeof(achDeviceVersion))) {
            // Concatenate the device name and version strings
            lstrcat (achDeviceAndVersion, ",   ");
            lstrcat (achDeviceAndVersion, achDeviceVersion);

            AppendMenu (hMenuSub,
                        MF_STRING,
                        IDM_O_DRIVER0 + uIndex,
                        achDeviceAndVersion);
            gDriverCount++;
        }
        else
            break;
    }

    // Now refresh menu, position capture window, start driver etc
    DrawMenuBar(ghWndMain) ;

    return (gDriverCount);
}

/*
 * Connect the capture window to a capture driver.
 * uIndex specifies the index of the driver to use.
 * Returns TRUE on success, or FALSE if the driver connection failed.
 */
BOOL
vidcapInitHardware(HWND hwnd, HWND hwndCap, UINT uIndex)
{
    UINT    uError ;
    UINT    uI;
    HMENU   hMenu;
    char    szName[MAX_PATH];
    char    szVersion[MAX_PATH];

    // Since the driver may not provide a reliable error string
    // provide a default
    LoadString(ghInstApp, IDS_ERR_FIND_HARDWARE, gachLastError, sizeof(gachLastError));

    // Try connecting to the capture driver
    if (uError = capDriverConnect(hwndCap, uIndex)) {
        gbHaveHardware = TRUE;
        gwDeviceIndex = uIndex;
    }
    else {
        gbHaveHardware = FALSE;
        gbLive = FALSE;
        gbOverlay = FALSE;
    }

    // Get the capabilities of the capture driver
    capDriverGetCaps(hwndCap, &gCapDriverCaps, sizeof(CAPDRIVERCAPS)) ;

    // Get the settings for the capture window
    capGetStatus(hwndCap, &gCapStatus , sizeof(gCapStatus));

    // Modify the toolbar buttons
    toolbarModifyState(ghWndToolBar, BTN_CAPFRAME,
        gbHaveHardware ? BTNST_UP : BTNST_GRAYED);
    toolbarModifyState(ghWndToolBar, BTN_CAPSEL,
        gbHaveHardware ? BTNST_UP : BTNST_GRAYED);
    toolbarModifyState(ghWndToolBar, BTN_CAPAVI,
        gbHaveHardware ? BTNST_UP : BTNST_GRAYED);
    toolbarModifyState(ghWndToolBar, BTN_LIVE,
        gbHaveHardware ? BTNST_UP : BTNST_GRAYED);

    // Is overlay supported?
    toolbarModifyState(ghWndToolBar, BTN_OVERLAY,
        (gbHaveHardware && gCapDriverCaps.fHasOverlay) ?
        BTNST_UP : BTNST_GRAYED);

    // Can the device create palettes?
    toolbarModifyState(ghWndToolBar, BTN_CAPPAL,
        (gbHaveHardware && gCapDriverCaps.fDriverSuppliesPalettes) ?
        BTNST_UP : BTNST_GRAYED);

    // Check the appropriate driver in the Options menu
    hMenu = GetMenu (hwnd);
    for (uI = 0; uI < gDriverCount; uI++) {
        CheckMenuItem (hMenu, IDM_O_DRIVER0 + uI,
                MF_BYCOMMAND | ((uIndex == uI) ? MF_CHECKED : MF_UNCHECKED));
    }

    // Unlike all other capture drivers, Scrncap.drv needs to use
    // a Yield callback, and we don't want to abort on mouse clicks,
    // so determine if the current driver is Scrncap.drv
    capGetDriverDescription (uIndex,
                szName, sizeof (szName),
                szVersion, sizeof (szVersion));

    // Set a flag if we're using Scrncap.drv
    gbIsScrncap = (BOOL) _fstrstr (szName, "Screen Capture");

    // Get video format and adjust capture window
    vidcapLayout(ghWndMain);
    InvalidateRect(ghWndMain, NULL, TRUE);

    // set the preview rate (units are millisecs)
    capPreviewRate(hwndCap, gbHaveHardware ? 33 : 0);

    // set live/overlay to default
    vidcapSetLive(gbLive);
    vidcapSetOverlay(gbOverlay);

    strcat (szName, ",   ");
    strcat (szName, szVersion);

    statusUpdateStatus(ghWndStatus,
        gbHaveHardware ? szName : gachLastError);

    return gbHaveHardware;
}

/*
 * layout the main window. Put the toolbar at the top and the status
 * line at the bottom, and then give all the rest to vidframe,
 *  - it will centre or scroll the AVICAP window appropriately.
 */
void
vidcapLayout(HWND hwnd)
{
    RECT rc;
    RECT rw;
    int cy;
    int cyBorder, cxBorder;
    int cyTotal;
    int cxToolbar;
    int cyMenuAndToolbarAndCaption;

    gbInLayout = TRUE;  // So that we process WM_GETMINMAXINFO normally

    /* for both the toolbar and status bar window,
     * we want just one of the four borders. We do this
     * by setting the WS_BORDER style, and sizing and positioning
     * the window so that the 3 unwanted borders are outside the parent.
     */
    cyBorder = GetSystemMetrics(SM_CYBORDER);
    cxBorder = GetSystemMetrics(SM_CXBORDER);

    // Figure out the height of the menu, toolbar, and caption
    GetWindowRect (hwnd, &rw);
    GetClientRect (hwnd, &rc);

    ClientToScreen (hwnd, (LPPOINT) &rc);
    cyMenuAndToolbarAndCaption = (rc.top - rw.top) + TOOLBAR_HEIGHT;

    cxToolbar = aPos[APP_NUMTOOLS - 1] + BUTTONWIDTH * 3;

    if (gbAutoSizeFrame && gbHaveHardware && gCapStatus.uiImageWidth) {
        cyTotal = gCapStatus.uiImageHeight +
                cyMenuAndToolbarAndCaption +
                (gbStatusBar ? statusGetHeight() : 0) +
                cyBorder * 2 +
                12;     // vidFrame height
        // Never make the frame smaller than the toolbar
        if (gCapStatus.uiImageWidth >= (UINT) cxToolbar) {
            SetWindowPos(
                hwnd,
                0,// placement-order handle
                0,// horizontal position
                0,// vertical position
                gCapStatus.uiImageWidth + cxBorder * 24,// width
                cyTotal,// height
                SWP_NOZORDER | SWP_NOMOVE // window-positioning flags
                );
        } else {
            SetWindowPos(
                hwnd,
                0,// placement-order handle
                0,// horizontal position
                0,// vertical position
                cxToolbar,// width
                cyTotal,// height
                SWP_NOZORDER | SWP_NOMOVE // window-positioning flags
                );
        }
    }

    GetClientRect(hwnd, &rc);

    if (gbToolBar) {
        // put the toolbar at the top - in fact, just off the top so as to
        // hide it's border
        MoveWindow(
            ghWndToolBar,
            -cxBorder, -cyBorder,
            RECTWIDTH(rc)+ (cxBorder * 2),
            TOOLBAR_HEIGHT,
            TRUE);
        rc.top += (TOOLBAR_HEIGHT - cyBorder);
    } else {
        MoveWindow(ghWndToolBar, 0, 0, 0, 0, TRUE);
    }

    // status bar at the bottom
    if (gbStatusBar) {
        cy = statusGetHeight() + cyBorder;
        MoveWindow(
            ghWndStatus,
            -cxBorder, rc.bottom - cy,
            RECTWIDTH(rc) + (2 * cxBorder), cy + cyBorder,
            TRUE);
        rc.bottom -= cy;
    } else {
        MoveWindow(ghWndStatus, 0, 0, 0, 0, TRUE);
    }

    // rest of window goes to vidframe window
    MoveWindow(
        ghWndFrame,
        rc.left, rc.top,
        RECTWIDTH(rc), RECTHEIGHT(rc),
        TRUE);

    // Always layout the frame window, since it is aligned on a
    // DWORD boundary for maximum codec drawing efficiency
    vidframeLayout(ghWndFrame, ghWndCap);

    gbInLayout = FALSE;
}

/*
 * initialise settings from the profile used before window creation time
 */
void
vidcapReadProfile(void)
{
    // read defaults out of the registry
    gbCentre = mmGetProfileFlag(gachAppTitle, "CenterImage", TRUE);
    gbToolBar = mmGetProfileFlag(gachAppTitle, "ToolBar", TRUE);
    gbStatusBar = mmGetProfileFlag(gachAppTitle, "StatusBar", TRUE);
    gbAutoSizeFrame = mmGetProfileFlag(gachAppTitle, "AutoSizeFrame", TRUE);
    gBackColour = mmGetProfileInt(gachAppTitle, "BackgroundColor", IDD_PrefsLtGrey);

    gWinX = mmGetProfileInt(gachAppTitle, "WindowXPos", (UINT) CW_USEDEFAULT);
if (gWinX != (UINT) CW_USEDEFAULT)
    gWinX = LimitRange(gWinX, 0, GetSystemMetrics (SM_CXSCREEN) - 40);
    gWinY = mmGetProfileInt(gachAppTitle, "WindowYPos", 0);
    gWinY = LimitRange(gWinY, 0, GetSystemMetrics (SM_CYSCREEN) - 40);
    gWinCX = mmGetProfileInt(gachAppTitle, "WindowWidth", 320);
    gWinCX = LimitRange(gWinCX, 20, GetSystemMetrics (SM_CXSCREEN));
    gWinCY = mmGetProfileInt(gachAppTitle, "WindowHeight", 240);
    gWinCY = LimitRange(gWinCY, 20, GetSystemMetrics (SM_CYSCREEN));
    gWinShow = mmGetProfileInt(gachAppTitle, "WindowShow", SW_SHOWDEFAULT);
    gWinShow = LimitRange(gWinShow, SW_SHOWNORMAL, SW_SHOWDEFAULT);

    gbOverlay = mmGetProfileInt(gachAppTitle, "OverlayWindow", FALSE);
    gbLive = mmGetProfileInt(gachAppTitle, "LiveWindow", TRUE);
}

void
vidcapWriteProfile(void)
{
    mmWriteProfileFlag(gachAppTitle, "CenterImage", gbCentre, TRUE);
    mmWriteProfileFlag(gachAppTitle, "ToolBar", gbToolBar, TRUE);
    mmWriteProfileFlag(gachAppTitle, "StatusBar", gbStatusBar, TRUE);
    mmWriteProfileFlag(gachAppTitle, "AutoSizeFrame", gbAutoSizeFrame, TRUE);
    mmWriteProfileInt(gachAppTitle,  "BackgroundColor", gBackColour, IDD_PrefsLtGrey);

    mmWriteProfileInt(gachAppTitle, "WindowXPos", gWinX, (UINT) CW_USEDEFAULT);
    mmWriteProfileInt(gachAppTitle, "WindowYPos", gWinY, 0);
    mmWriteProfileInt(gachAppTitle, "WindowWidth", gWinCX, 320);
    mmWriteProfileInt(gachAppTitle, "WindowHeight", gWinCY, 240);
    mmWriteProfileInt(gachAppTitle, "WindowShow", gWinShow, SW_SHOWDEFAULT);

    mmWriteProfileInt(gachAppTitle, "OverlayWindow", gbOverlay, FALSE);
    mmWriteProfileInt(gachAppTitle, "LiveWindow", gbLive, TRUE);
}

/*
 * initialise settings from the profile used AFTER window creation time
 */
void
vidcapReadSettingsProfile(void)
{
    DWORD dwSize;

    mmGetProfileString(gachAppTitle, "CaptureFile", "",
        gachCaptureFile, sizeof(gachCaptureFile));

    mmGetProfileString(gachAppTitle, "MCIDevice", "VideoDisc",
                gachMCIDeviceName, sizeof(gachMCIDeviceName));

    gCapParms.dwRequestMicroSecPerFrame =
                mmGetProfileInt(gachAppTitle, "MicroSecPerFrame",
                DEF_CAPTURE_RATE);

    gCapParms.dwRequestMicroSecPerFrame =
                mmGetProfileInt(gachAppTitle, "MicroSecPerFrame",
                DEF_CAPTURE_RATE);

    gCapParms.fCaptureAudio = mmGetProfileFlag(gachAppTitle, "CaptureAudio",
                gCapStatus.fAudioHardware);

    gCapParms.fLimitEnabled = mmGetProfileFlag(gachAppTitle, "LimitEnabled",
                FALSE);

    gCapParms.wTimeLimit =
                mmGetProfileInt(gachAppTitle, "TimeLimit", 30);

    gCapParms.fMCIControl= mmGetProfileFlag(gachAppTitle, "MCIControl", FALSE);

    gCapParms.fStepMCIDevice= mmGetProfileFlag(gachAppTitle, "StepMCIDevice", FALSE);

    gCapParms.dwMCIStartTime =
                mmGetProfileInt(gachAppTitle, "MCIStartTime", 10000);

    gCapParms.dwMCIStopTime =
                mmGetProfileInt(gachAppTitle, "MCIStopTime", 20000);

    gCapParms.fStepCaptureAt2x = mmGetProfileFlag(gachAppTitle, "StepCapture2x",
                FALSE);

    gCapParms.wStepCaptureAverageFrames =
                mmGetProfileInt(gachAppTitle, "StepCaptureAverageFrames", 3);

    gCapParms.AVStreamMaster = mmGetProfileInt (gachAppTitle, "AVStreamMaster",
                AVSTREAMMASTER_AUDIO);

    gCapParms.fUsingDOSMemory = mmGetProfileFlag (gachAppTitle, "CaptureToDisk",
                TRUE);

    gCapParms.dwIndexSize =
                mmGetProfileInt(gachAppTitle, "IndexSize",
                CAP_SMALL_INDEX);

    // Retrieve the saved audio format
    // Ask the ACM what the largest known wave format is
    acmMetrics(NULL,
               ACM_METRIC_MAX_SIZE_FORMAT,
               &dwSize);

    // If a wave format was saved in the registry, use that size
    dwSize = max (dwSize, mmGetProfileBinary(gachAppTitle, "WaveFormatBinary",
   NULL,
   NULL,
   0));

    if (glpwfex = (LPWAVEFORMATEX) GlobalAllocPtr(GHND, dwSize)) {
capGetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;
mmGetProfileBinary(gachAppTitle, "WaveFormatBinary",
   glpwfex,
   glpwfex,
   dwSize);

// Do some sanity checking
if (MMSYSERR_NOERROR == waveInOpen (NULL, WAVE_MAPPER,
    glpwfex, 0, 0, WAVE_FORMAT_QUERY)) {
    capSetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;
}
GlobalFreePtr(glpwfex) ;
    }
}

void
vidcapWriteSettingsProfile(void)
{
    mmWriteProfileString(gachAppTitle, "CaptureFile", gachCaptureFile);

    mmWriteProfileString(gachAppTitle, "MCIDevice", gachMCIDeviceName);

    mmWriteProfileInt(gachAppTitle, "MicroSecPerFrame",
                gCapParms.dwRequestMicroSecPerFrame, DEF_CAPTURE_RATE);

    mmWriteProfileFlag(gachAppTitle, "CaptureAudio",
                gCapParms.fCaptureAudio, gCapStatus.fAudioHardware);

    mmWriteProfileFlag(gachAppTitle, "LimitEnabled",
                gCapParms.fLimitEnabled, FALSE);

    mmWriteProfileInt(gachAppTitle, "TimeLimit",
                gCapParms.wTimeLimit, 30);

    mmWriteProfileFlag(gachAppTitle, "MCIControl",
                gCapParms.fMCIControl, FALSE);

    mmWriteProfileFlag(gachAppTitle, "StepMCIDevice",
                gCapParms.fStepMCIDevice, FALSE);

    mmWriteProfileInt(gachAppTitle, "MCIStartTime",
                gCapParms.dwMCIStartTime, 10000);

    mmWriteProfileInt(gachAppTitle, "MCIStopTime",
                gCapParms.dwMCIStopTime, 20000);

    mmWriteProfileFlag(gachAppTitle, "StepCapture2x",
                gCapParms.fStepCaptureAt2x, FALSE);

    mmWriteProfileInt(gachAppTitle, "StepCaptureAverageFrames",
                gCapParms.wStepCaptureAverageFrames, 3);

    mmWriteProfileInt(gachAppTitle, "AVStreamMaster",
                gCapParms.AVStreamMaster, AVSTREAMMASTER_AUDIO);

    mmWriteProfileFlag(gachAppTitle, "CaptureToDisk",
                gCapParms.fUsingDOSMemory, TRUE);

    mmWriteProfileInt(gachAppTitle, "IndexSize",
                gCapParms.dwIndexSize, CAP_SMALL_INDEX);

    // The audio format is written whenever it is changed via dlg
}

/* --- error/status functions -------------------------------------------*/

/*
 * put up a message box loading a string from the
 * resource file
 */
int
MessageBoxID(UINT idString, UINT fuStyle)
{
    char achMessage[256];   // max message length

    LoadString(ghInstApp, idString, achMessage, sizeof(achMessage));

    return MessageBox(ghWndMain, achMessage, gachAppTitle, fuStyle);
}

//
// ErrorCallbackProc: Error Callback Function
//
LRESULT FAR PASCAL ErrorCallbackProc(HWND hWnd, int nErrID, LPSTR lpErrorText)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:          Application main window handle
//  nErrID:        Error code for the encountered error
//  lpErrorText:   Error text string for the encountered error
////////////////////////////////////////////////////////////////////////

    if (!ghWndMain)
        return FALSE;

    if (nErrID == 0)            // Starting a new major function
        return TRUE;            // Clear out old errors...

    // save the error message for use in NoHardwareDlgProc
    lstrcpy(gachLastError, lpErrorText);

    // Show the error ID and text

    MessageBox(hWnd, lpErrorText, gachAppTitle,
                MB_OK | MB_ICONEXCLAMATION) ;

    return (LRESULT) TRUE ;
}

//
// StatusCallbackProc: Status Callback Function
//
LRESULT FAR PASCAL StatusCallbackProc(HWND hWnd, int nID, LPSTR lpStatusText)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:           Application main window handle
//  nID:            Status code for the current status
//  lpStatusText:   Status text string for the current status
////////////////////////////////////////////////////////////////////////

    static int CurrentID;

    if (!ghWndMain) {
        return FALSE;
    }

    // the CAP_END message sometimes overwrites a useful
    // statistics message.
    if (nID == IDS_CAP_END) {
        if ((CurrentID == IDS_CAP_STAT_VIDEOAUDIO) ||
            (CurrentID == IDS_CAP_STAT_VIDEOONLY)) {

            return(TRUE);
        }
    }
    CurrentID = nID;

    statusUpdateStatus(ghWndStatus, lpStatusText);

    return (LRESULT) TRUE ;
}

//
// YieldCallbackProc: Status Callback Function
// (Only used for Scrncap.drv driver)
//
LRESULT FAR PASCAL YieldCallbackProc(HWND hWnd)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:           Application main window handle
////////////////////////////////////////////////////////////////////////

    MSG msg;

    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Return TRUE to continue capturing
    return (LRESULT) TRUE;
}

/*
 * load a string from the string table and return
 * a pointer to it for temporary use. Each call
 * overwrites the previous
 */
LPSTR
tmpString(UINT idString)
{
    static char ach[350];

    LoadString(ghInstApp, idString, ach, sizeof(ach));

    // ensure null terminated
    ach[sizeof(ach) -1] = 0;

    return(ach);
}

/* --- connect to and init hardware ------------------------------------- */

void
vidcapSetLive(BOOL bLive)
{
    capPreview(ghWndCap, bLive);
    toolbarModifyState(ghWndToolBar, BTN_LIVE, bLive? BTNST_DOWN : BTNST_UP);
    CheckMenuItem(GetMenu(ghWndMain), IDM_O_PREVIEW,
        MF_BYCOMMAND | (bLive ? MF_CHECKED : MF_UNCHECKED));

    gbLive = bLive;

    if (bLive == TRUE) {
        vidcapSetOverlay(FALSE);
    }
}

void
vidcapSetOverlay(BOOL bOverlay)
{
    if (!gCapDriverCaps.fHasOverlay) {
        CheckMenuItem(GetMenu(ghWndMain), IDM_O_OVERLAY,
            MF_BYCOMMAND | MF_UNCHECKED);
        gbOverlay = FALSE;
        return;
    }

    capOverlay(ghWndCap, bOverlay);
    toolbarModifyState(ghWndToolBar, BTN_OVERLAY, bOverlay ? BTNST_DOWN : BTNST_UP);
    CheckMenuItem(GetMenu(ghWndMain), IDM_O_OVERLAY,
        MF_BYCOMMAND | (bOverlay ? MF_CHECKED : MF_UNCHECKED));

    gbOverlay = bOverlay;

    if (bOverlay == TRUE) {
        vidcapSetLive(FALSE);
    }
}

void
vidcapSetCaptureFile(LPSTR pFileName)
{
    char achBuffer[_MAX_PATH];

    if ((pFileName != NULL) && (lstrlen(pFileName)  > 0)) {
        // record the capture filename
        if (lstrcmp(gachCaptureFile, pFileName)) {
            lstrcpy(gachCaptureFile, pFileName);
        }

        // and set window title
        wsprintf(achBuffer, "%s - %s", gachAppTitle, pFileName);
    } else {
        gachCaptureFile[0] = 0;
        lstrcpy(achBuffer, gachAppTitle);
    }

    capFileSetCaptureFile(ghWndCap, gachCaptureFile);
    SetWindowText(ghWndMain, achBuffer);
}

/* --- winproc and message handling --------------------------------------- */

/*
 * called from WM_COMMAND processing if the
 * message is from the toolbar. iButton contains the
 * button ID in the lower 8 bits, and the flags in the upper 8 bits/
 */
LONG FAR PASCAL
toolbarCommand (HWND hWnd, int iButton, HWND hwndToolbar)
{
    int iBtnPos, iState, iActivity, iString;

    // check repeat bit
    if (iButton & BTN_REPEAT) {
        return(0);
    }
    iButton &= 0xff;

    iBtnPos = toolbarIndexFromButton(hwndToolbar, iButton);
    iState = toolbarStateFromButton(hwndToolbar, iButton);
    iActivity = toolbarActivityFromButton(hwndToolbar, iButton);
    iString = toolbarStringFromIndex(hwndToolbar, iBtnPos);

    switch(iActivity) {

    case BTNACT_MOUSEDOWN:
    case BTNACT_KEYDOWN:
    case BTNACT_MOUSEMOVEON:
        statusUpdateStatus(ghWndStatus, MAKEINTRESOURCE(iString));
        break;

    case BTNACT_MOUSEMOVEOFF:
        statusUpdateStatus(ghWndStatus, NULL);
        break;

    case BTNACT_MOUSEUP:
    case BTNACT_KEYUP:

        statusUpdateStatus(ghWndStatus, NULL);
        switch(iButton) {
        case BTN_SETFILE:
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_F_SETCAPTUREFILE, NULL, 0));
                break;

        case BTN_EDITCAP:
            // edit captured video
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_F_EDITVIDEO, NULL, 0));
            break;

        case BTN_LIVE:
            SendMessage(hWnd,WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_O_PREVIEW, NULL, 0));
            break;

        case BTN_CAPFRAME:
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_C_CAPTUREFRAME, NULL, 0));
            break;

        case BTN_CAPSEL:
            // capture selected frames
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_C_CAPSEL, NULL, 0));
            break;

        case BTN_CAPAVI:
            SendMessage(hWnd,WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_C_CAPTUREVIDEO, NULL, 0));
            break;

        case BTN_CAPPAL:
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_C_PALETTE, NULL, 0));
            break;

        case BTN_OVERLAY:
            SendMessage(hWnd, WM_COMMAND,
                GET_WM_COMMAND_MPS(IDM_O_OVERLAY, NULL, 0));
            break;
        }
        break;
    }
    return(0);
}

/*
 * Put up a dialog to allow the user to select a capture file.
 */
LONG FAR PASCAL
cmdSetCaptureFile(HWND hWnd)
{
    OPENFILENAME ofn ;
    LPSTR p;
    char         achFileName[_MAX_PATH];
    char         achBuffer[_MAX_PATH] ;
    UINT         wError ;
    HANDLE hFilter;
    int oldhelpid;

    // Get current capture file name and
    // then try to get the new capture file name
    if (wError = capFileGetCaptureFile(ghWndCap, achFileName,
                                sizeof(achFileName))) {

        // Get just the path info
        // Terminate the full path at the last backslash
        lstrcpy (achBuffer, achFileName);
        for (p = achBuffer + lstrlen(achBuffer); p > achBuffer; p--) {
            if (*p == '\\') {
                *(p+1) = '\0';
                break;
            }
        }

        _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
        ofn.lStructSize = sizeof(OPENFILENAME) ;
        ofn.hwndOwner = hWnd ;

        //load filters from resource stringtable
        hFilter = FindResource(ghInstApp, MAKEINTRESOURCE(ID_FILTER_AVI), RT_RCDATA);
        if ((hFilter = LoadResource(ghInstApp, hFilter)) == NULL) {
            ofn.lpstrFilter = NULL;
        } else {
            ofn.lpstrFilter = LockResource(hFilter);
        }

        ofn.nFilterIndex = 0 ;
        ofn.lpstrFile = achFileName ;
        ofn.nMaxFile = sizeof(achFileName) ;
        ofn.lpstrFileTitle = NULL;
        ofn.lpstrTitle = tmpString(IDS_TITLE_SETCAPTUREFILE);
        ofn.nMaxFileTitle = 0 ;
        ofn.lpstrInitialDir = achBuffer;
        ofn.Flags =
        OFN_HIDEREADONLY |
        OFN_NOREADONLYRETURN |
        OFN_PATHMUSTEXIST ;

        // set help context for dialog
        oldhelpid = SetCurrentHelpContext(IDA_SETCAPFILE);

        if (GetOpenFileName(&ofn)) {
            OFSTRUCT os;

            vidcapSetCaptureFile(achFileName);

            /*
             * if this is a new file, then invite the user to
             * allocate some space
             */
            if (OpenFile(achFileName, &os, OF_EXIST) == HFILE_ERROR) {

                /*
                 * show the allocate file space dialog to encourage
                 * the user to pre-allocate space
                 */
                if (DoDialog(hWnd, IDD_AllocCapFileSpace, AllocCapFileProc, 0)) {

    // ensure repaint after dismissing dialog before
    // possibly lengthy operation
    UpdateWindow(ghWndMain);

                    // If user has hit OK then alloc requested capture file space
                    if (! capFileAlloc(ghWndCap, (long) gwCapFileSize * ONEMEG)) {
                        MessageBoxID(IDS_ERR_CANT_PREALLOC,
                                    MB_OK | MB_ICONEXCLAMATION) ;
                    }
                }
            }

        }

        // restore old help context
        SetCurrentHelpContext(oldhelpid);

        if (hFilter) {
            UnlockResource(hFilter);
        }

    }
    return(0);
}

/*
 * query the user for a filename, and then save the captured video
 * to that file
 */
LONG FAR PASCAL
cmdSaveVideoAs(HWND hWnd)
{
    OPENFILENAME ofn ;
    char         achFileName[_MAX_PATH];
    UINT         wError ;
    HANDLE       hFilter;
    int          oldhelpid;

    // Get the current capture file name and
    // then get the substitute file name to save video in
    if (wError = capFileGetCaptureFile(ghWndCap, achFileName, sizeof(achFileName))) {

        _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
        ofn.lStructSize = sizeof(OPENFILENAME) ;
        ofn.hwndOwner = hWnd ;

        //load filters from resource stringtable
        hFilter = FindResource(ghInstApp, MAKEINTRESOURCE(ID_FILTER_AVI), RT_RCDATA);
        if ((hFilter = LoadResource(ghInstApp, hFilter)) == NULL) {
            ofn.lpstrFilter = NULL;
        } else {
            ofn.lpstrFilter = LockResource(hFilter);
        }

        ofn.nFilterIndex = 0 ;
        ofn.lpstrFile = achFileName ;
        ofn.nMaxFile = sizeof(achFileName) ;
        ofn.lpstrFileTitle = NULL ;
        ofn.lpstrTitle = tmpString(IDS_TITLE_SAVEAS);
        ofn.nMaxFileTitle = 0 ;
        ofn.lpstrInitialDir = NULL ;
        ofn.Flags =
        OFN_OVERWRITEPROMPT |  OFN_PATHMUSTEXIST ;

        // set help context
        oldhelpid = SetCurrentHelpContext(IDA_SAVECAPFILE);

        if (GetSaveFileName(&ofn)) {
            // If the user has hit OK then set save file name
            capFileSaveAs(ghWndCap, achFileName) ;
        }

        SetCurrentHelpContext(oldhelpid);

        if (hFilter) {
            UnlockResource(hFilter);
        }
    }
    return(0);
}

/*
 * Put up a dialog to allow the user to select a palette file and then
 * load that palette
 */
LONG FAR PASCAL
cmdLoadPalette(HWND hWnd)
{
    OPENFILENAME ofn ;
    char         achFileName[_MAX_PATH];
    HANDLE       hFilter;
    int          oldhelpid;

    achFileName[0] = 0;

    _fmemset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hWnd;

    //load filters from resource stringtable
    hFilter = FindResource(ghInstApp, MAKEINTRESOURCE(ID_FILTER_PALETTE), RT_RCDATA);
    if ((hFilter = LoadResource(ghInstApp, hFilter)) == NULL) {
        ofn.lpstrFilter = NULL;
    } else {
        ofn.lpstrFilter = LockResource(hFilter);
    }

    ofn.nFilterIndex = 1;
    ofn.lpstrFile = achFileName;
    ofn.nMaxFile = sizeof(achFileName);
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = tmpString(IDS_TITLE_LOADPALETTE);
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags =
    OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

    // set help context id
    oldhelpid = SetCurrentHelpContext(IDA_LOADPAL);

    if (GetOpenFileName(&ofn)) {
        // If the user has hit OK then load palette
        capPaletteOpen(ghWndCap, achFileName);
    }

    SetCurrentHelpContext(oldhelpid);

    if (hFilter) {
        UnlockResource(hFilter);
    }
    return(0);
}

/*
 * query the user for a filename, and then save the current palette
 * to that file
 */
LONG FAR PASCAL
cmdSavePalette(HWND hWnd)
{
    OPENFILENAME ofn ;
    char         achFileName[_MAX_PATH];
    HANDLE       hFilter;
    int          oldhelpid;

    achFileName[0] = 0;

    _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = hWnd ;

    //load filters from resource stringtable
    hFilter = FindResource(ghInstApp, MAKEINTRESOURCE(ID_FILTER_PALETTE), RT_RCDATA);
    if ((hFilter = LoadResource(ghInstApp, hFilter)) == NULL) {
        ofn.lpstrFilter = NULL;
    } else {
        ofn.lpstrFilter = LockResource(hFilter);
    }

    ofn.nFilterIndex = 1;
    ofn.lpstrFile = achFileName;
    ofn.nMaxFile = sizeof(achFileName);
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = tmpString(IDS_TITLE_SAVEPALETTE);
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags =
    OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;

    // set help context for F1 key
    oldhelpid = SetCurrentHelpContext(IDA_SAVEPAL);

    if (GetSaveFileName(&ofn)) {
        // If the user has hit OK then set save file name
        capPaletteSave(ghWndCap, achFileName);
    }

    SetCurrentHelpContext(oldhelpid);

    if (hFilter) {
        UnlockResource(hFilter);
    }

    return(0);
}

/*
 * query the user for a filename, and then save the current frame
 * to that file
 */
LONG FAR PASCAL
cmdSaveDIB(HWND hWnd)
{
    OPENFILENAME ofn ;
    char         achFileName[_MAX_PATH];
    HANDLE       hFilter;
    int          oldhelpid;

    achFileName[0] = 0;

    _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = hWnd ;

    //load filters from resource stringtable
    hFilter = FindResource(ghInstApp, MAKEINTRESOURCE(ID_FILTER_DIB), RT_RCDATA);
    if ((hFilter = LoadResource(ghInstApp, hFilter)) == NULL) {
        ofn.lpstrFilter = NULL;
    } else {
        ofn.lpstrFilter = LockResource(hFilter);
    }

    ofn.nFilterIndex = 1;
    ofn.lpstrFile = achFileName;
    ofn.nMaxFile = sizeof(achFileName);
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = tmpString(IDS_TITLE_SAVEDIB);
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags =
    OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;

    // set help context for F1 handling
    oldhelpid = SetCurrentHelpContext(IDA_SAVEDIB);

    if (GetSaveFileName(&ofn)) {

        // If the user has hit OK then set save file name
        capFileSaveDIB(ghWndCap, achFileName);
    }

    SetCurrentHelpContext(oldhelpid);

    if (hFilter) {
        UnlockResource(hFilter);
    }

    return(0);
}

//
// MenuProc: Processes All Menu-based Operations
//
long FAR PASCAL MenuProc(HWND hWnd, UINT wParam, LONG lParam)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:      Application main window handle
//  hMenu:     Application menu handle
//  wParam:    Menu option
//  lParam:    Additional info for any menu option
////////////////////////////////////////////////////////////////////////

    BOOL         fResult ;
    DWORD        dwSize ;
    int          oldhelpid;

    HMENU hMenu = GetMenu(hWnd) ;

    switch (GET_WM_COMMAND_ID(wParam, lParam)) {

case IDC_TOOLBAR:
            return toolbarCommand(hWnd, GET_WM_COMMAND_CMD(wParam, lParam), ghWndToolBar);

/* --- file --- */
        case IDM_F_SETCAPTUREFILE:
            return cmdSetCaptureFile(hWnd);

        case IDM_F_SAVEVIDEOAS:
            return cmdSaveVideoAs(hWnd);
            break;

        case IDM_F_ALLOCATESPACE:
            if (DoDialog(hWnd, IDD_AllocCapFileSpace, AllocCapFileProc, 0)) {

// ensure repaint after dismissing dialog before
// possibly lengthy operation
UpdateWindow(ghWndMain);

                // If user has hit OK then alloc requested capture file space
                if (! capFileAlloc(ghWndCap, (long) gwCapFileSize * ONEMEG)) {
                    MessageBoxID(IDS_ERR_CANT_PREALLOC,
                                MB_OK | MB_ICONEXCLAMATION) ;
                }
            }
            break ;

        case IDM_F_EXIT:
            DestroyWindow(hWnd) ;
            break;

        case IDM_F_LOADPALETTE:
            return cmdLoadPalette(hWnd);

        case IDM_F_SAVEPALETTE:
            return cmdSavePalette(hWnd);

        case IDM_F_SAVEFRAME:
            return cmdSaveDIB(hWnd);

        case IDM_F_EDITVIDEO:
        {
            charachCmdLine[256];
            UINT        u;
            BOOLf = TRUE;/* assume the best */
            HCURSOR     hOldCursor;

            /* build up the command line "AviEdit -n filename" */
            if (lstrlen(gachCaptureFile) > 0) {
                lstrcpy(achCmdLine,"VIDEdit -n ");
                lstrcat(achCmdLine, gachCaptureFile);

                hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
                u = WinExec(achCmdLine, SW_SHOWNORMAL);
                if (u < 32){
            /* report error on forking VidEdit */
                    MessageBoxID(IDS_ERR_VIDEDIT, MB_OK|MB_ICONEXCLAMATION);
            f = FALSE;
                }

                SetCursor(hOldCursor);
            }
            return f;

        }

/* --- edit --- */

        case IDM_E_COPY:
            capEditCopy(ghWndCap) ;
            break;

        case IDM_E_PASTEPALETTE:
            capPalettePaste(ghWndCap) ;
            break;

        case IDM_E_PREFS:
            {
                if (DoDialog(hWnd, IDD_Prefs, PrefsDlgProc, 0)) {

                        // write prefs to profile

                        // force new brush
                        vidframeSetBrush(ghWndFrame, gBackColour);

                        // re-do layout
                        vidcapLayout(hWnd);

                }
            }
            break;

/* --- options --- */

        case IDM_O_PREVIEW:
            // Toggle Preview
        capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
            vidcapSetLive(!gCapStatus.fLiveWindow) ;
            break;

        case IDM_O_OVERLAY:
            // Toggle Overlay
        capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
            vidcapSetOverlay(!gCapStatus.fOverlayWindow);
            break ;

        case IDM_O_AUDIOFORMAT:
#ifdef  USE_ACM
            {
                ACMFORMATCHOOSE cfmt;
                static BOOL fDialogUp = FALSE;

                if (fDialogUp)
                    return FALSE;

                fDialogUp = TRUE;
                // Ask the ACM what the largest wave format is.....
                acmMetrics(NULL,
                            ACM_METRIC_MAX_SIZE_FORMAT,
                            &dwSize);

                // Get the current audio format
                dwSize = max (dwSize, capGetAudioFormatSize (ghWndCap));
                if (glpwfex = (LPWAVEFORMATEX) GlobalAllocPtr(GHND, dwSize)) {
                    capGetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;

    _fmemset (&cfmt, 0, sizeof (ACMFORMATCHOOSE));
    cfmt.cbStruct = sizeof (ACMFORMATCHOOSE);
    cfmt.fdwStyle =  ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
    cfmt.fdwEnum =   ACM_FORMATENUMF_HARDWARE |
     ACM_FORMATENUMF_INPUT;
    cfmt.hwndOwner = hWnd;
    cfmt.pwfx =     glpwfex;
    cfmt.cbwfx =    dwSize;

    //oldhelpid = SetCurrentHelpContext(IDA_AUDIOSETUP);
    if (!acmFormatChoose(&cfmt)) {
capSetAudioFormat(ghWndCap, glpwfex, (WORD)glpwfex->cbSize +
  sizeof (WAVEFORMATEX)) ;
mmWriteProfileBinary(gachAppTitle, "WaveFormatBinary",
     (LPVOID) glpwfex, glpwfex->cbSize +
     sizeof (WAVEFORMATEX));
    }
    //SetCurrentHelpContext(oldhelpid);

    GlobalFreePtr(glpwfex) ;
}
                fDialogUp = FALSE;
            }
#else
            {
                // Get current audio format and then find required format
                dwSize = capGetAudioFormatSize (ghWndCap);
                glpwfex = (LPWAVEFORMATEX) GlobalAllocPtr(GHND, dwSize) ;
                capGetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;

                if (DoDialog(hWnd, IDD_AudioFormat, AudioFormatProc, 0)) {
                        // If the user has hit OK, set the new audio format
                        capSetAudioFormat(ghWndCap, glpwfex, (WORD)dwSize) ;
mmWriteProfileBinary(gachAppTitle, "WaveFormatBinary",
 (LPVOID) glpwfex, dwSize);
                }
                GlobalFreePtr(glpwfex) ;
            }
#endif
            break ;

        case IDM_O_VIDEOFORMAT:
            if (gCapDriverCaps.fHasDlgVideoFormat) {
                // Only if the driver has a "Video Format" dialog box
                oldhelpid = SetCurrentHelpContext(IDA_VIDFORMAT);
                if (capDlgVideoFormat(ghWndCap)) {  // If successful,
                    // Get the new image dimension and center capture window
                    capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
                    vidcapLayout(hWnd);
                }
                SetCurrentHelpContext(oldhelpid);
            }
            break;

        case IDM_O_VIDEOSOURCE:
            if (gCapDriverCaps.fHasDlgVideoSource) {
                // Only if the driver has a "Video Source" dialog box
                oldhelpid = SetCurrentHelpContext(IDA_VIDSOURCE);
                capDlgVideoSource(ghWndCap) ;
                capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
                vidcapLayout(hWnd);
                SetCurrentHelpContext(oldhelpid);
            }
            break ;

        case IDM_O_VIDEODISPLAY:
            if (gCapDriverCaps.fHasDlgVideoDisplay) {
                // Only if the driver has a "Video Display" dialog box
                oldhelpid = SetCurrentHelpContext(IDA_VIDDISPLAY);
                capDlgVideoDisplay(ghWndCap) ;
                capGetStatus(ghWndCap, &gCapStatus, sizeof(CAPSTATUS)) ;
                SetCurrentHelpContext(oldhelpid);
            }
            break ;

        case IDM_O_CHOOSECOMPRESSOR:
            oldhelpid = SetCurrentHelpContext(IDA_COMPRESSION);
            capDlgVideoCompression(ghWndCap);
            SetCurrentHelpContext(oldhelpid);
            break;

        // Select a driver to activate
        case IDM_O_DRIVER0:
        case IDM_O_DRIVER1:
        case IDM_O_DRIVER2:
        case IDM_O_DRIVER3:
        case IDM_O_DRIVER4:
        case IDM_O_DRIVER5:
        case IDM_O_DRIVER6:
        case IDM_O_DRIVER7:
        case IDM_O_DRIVER8:
        case IDM_O_DRIVER9:
            vidcapInitHardware(ghWndMain, ghWndCap, wParam - IDM_O_DRIVER0);
            break;

/* --- capture --- */

        case IDM_C_PALETTE:
            if (DoDialog(hWnd, IDD_MakePalette, MakePaletteProc, 0)) {
                // Palette is created within the dialog
                bDefaultPalette = FALSE;
            }
            break;

        case IDM_C_CAPTUREVIDEO:

            // warn user if he is still using the default palette
            if (bDefaultPalette) {

LPBITMAPINFOHEADER lpbi;
int sz;

// fUsingDefaultPalette will be TRUE even if the
// current capture format is non-palettised. This is a
// bizarre decision of Jay's.

sz = (int)capGetVideoFormatSize(ghWndCap);
lpbi = (LPBITMAPINFOHEADER)LocalAlloc(LPTR, sz);

if (lpbi) {    // We can warn s/he
    if (capGetVideoFormat(ghWndCap, lpbi, sz) &&
(lpbi->biCompression == BI_RGB) &&
(lpbi->biBitCount <= 8)) {

CAPSTATUS cs;

// if we've warned him once, we can forget it
bDefaultPalette = FALSE;

capGetStatus(ghWndCap, &cs, sizeof(cs));

if (cs.fUsingDefaultPalette) {

    if (MessageBoxID(IDS_WARN_DEFAULT_PALETTE,
            MB_OKCANCEL| MB_ICONEXCLAMATION)== IDCANCEL) {
break;
    }
}
    }
    LocalFree(lpbi);
}
            }

            // Invoke a Dlg box to setup all the params
            if (DoDialog(hWnd, IDD_CapSetUp, CapSetUpProc, 0)) {

                // set the defaults we won't bother the user with
                gCapParms.fMakeUserHitOKToCapture = !gCapParms.fMCIControl;
                gCapParms.wPercentDropForError = 10;

                // fUsingDOSMemory is obsolete, but we use it here as
                // a flag which is TRUE if "CapturingToDisk"
                // The number of video buffers should be enough to get through
                // disk seeks and thermal recalibrations if "CapturingToDisk"
                // If "CapturingToMemory", get as many buffers as we can.

                gCapParms.wNumVideoRequested =
                        gCapParms.fUsingDOSMemory ? 32 : 1000;

                // Don't abort on the left mouse anymore!
                gCapParms.fAbortLeftMouse = FALSE;
                gCapParms.fAbortRightMouse = TRUE;

                // If the Driver is Scrncap.drv, the following values are special

                // If wChunkGranularity is zero, the granularity will be set to the
                // disk sector size.
                gCapParms.wChunkGranularity = (gbIsScrncap ? 32 : 0);

                // Scrncap requires a callback for the message pump
                capSetCallbackOnYield(ghWndCap,
                        (gbIsScrncap ? fpYieldCallback : NULL));

                // If the user has hit OK, set the new setup info
                capCaptureSetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;
            } else {
                break;
            }

            // if no capture file, get that
            if (lstrlen(gachCaptureFile) <= 0) {
                cmdSetCaptureFile(hWnd);
                if (lstrlen(gachCaptureFile) <= 0) {
                    break;
                }
            }

            // Capture video sequence
            fResult = capCaptureSequence(ghWndCap) ;
            break;

        case IDM_C_CAPTUREFRAME:
            // Turn off overlay / preview (gets turned off by frame capture)
            vidcapSetLive(FALSE);
            vidcapSetOverlay(FALSE);

            // Grab a frame
            fResult = capGrabFrameNoStop(ghWndCap) ;
            break;

        case IDM_C_CAPSEL:
            {
                FARPROC fproc;

                // if no capture file, get that
                if (lstrlen(gachCaptureFile) <= 0) {
                    cmdSetCaptureFile(hWnd);
                    if (lstrlen(gachCaptureFile) <= 0) {
                        break;
                    }
                }

                fproc = MakeProcInstance(CapFramesProc, ghInstApp);
                DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_CAPFRAMES), hWnd, (DLGPROC) fproc);
                FreeProcInstance(fproc);
            }
            break;

#ifdef DEBUG
        case IDM_C_TEST:
    nTestCount = 0;
    // Intentional fall through

        case IDM_C_TESTAGAIN:
            // set the defaults we won't bother the user with
            gCapParms.fMakeUserHitOKToCapture = FALSE;
            gCapParms.wPercentDropForError = 100;

            gCapParms.wNumVideoRequested =

gCapParms.fUsingDOSMemory ? 32 : 1000;

            // Don't abort on the left mouse anymore!
            gCapParms.fAbortLeftMouse = FALSE;
            gCapParms.fAbortRightMouse = TRUE;

            // If wChunkGranularity is zero, the granularity will be set to the
            // disk sector size.
            gCapParms.wChunkGranularity = (gbIsScrncap ? 32 : 0);

            // If the user has hit OK, set the new setup info
            capCaptureSetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;

            // if no capture file, get that
            if (lstrlen(gachCaptureFile) <= 0) {
                cmdSetCaptureFile(hWnd);
                if (lstrlen(gachCaptureFile) <= 0) {
                    break;
                }
            }

    {
char buf[80];

                gCapParms.wNumVideoRequested = 10;
                gCapParms.wNumAudioRequested = 5;
gCapParms.fLimitEnabled = TRUE;
if (gCapParms.wTimeLimit == 0)
    gCapParms.wTimeLimit = 5;
capCaptureSetSetup(ghWndCap, &gCapParms, sizeof(CAPTUREPARMS)) ;

// Capture video sequence
                fResult = capCaptureSequence(ghWndCap) ;

wsprintf (buf, "TestCount = %d", nTestCount++);
statusUpdateStatus(ghWndStatus, buf);

// Hold down the right mouse button to abort
if (!GetAsyncKeyState(VK_RBUTTON) & 0x0001)
    PostMessage (hWnd, WM_COMMAND, IDM_C_TESTAGAIN, 0L);
            }
            break;
#endif

/* --- help --- */
        case IDM_H_CONTENTS:
            HelpContents();
            break;

        case IDM_H_ABOUT:
            ShellAbout(
                hWnd,
                "VidCap",
                "Video Capture Tool",
                LoadIcon(ghInstApp,  gachIconName)
            );
            //DoDialog(hWnd, IDD_HelpAboutBox, AboutProc, 0);
            break ;

    }

    return 0L ;
}

/* --- menu help and enable/disable handling ------------------------ */

// write or clear status line help text when the user brings up or cancels a
// menu. This depends on there being strings in the string table with
// the same ID as the corresponding menu item.
// Help text for the items along the menu bar (File, Edit etc) depends
// on IDM_FILE, IDM_EDIT being defined with values 100 apart in the same
// order as their index in the menu
void
MenuSelect(HWND hwnd, UINT cmd, UINT flags, HMENU hmenu)
{
    if ((LOWORD(flags) == 0xffff) && (hmenu == NULL)) {
        //menu closing - remove message
        statusUpdateStatus(ghWndStatus, NULL);
    } else if ( (flags & (MF_SYSMENU|MF_POPUP)) == (MF_SYSMENU|MF_POPUP)) {
        // the system menu itself
        statusUpdateStatus(ghWndStatus, MAKEINTRESOURCE(IDM_SYSMENU));
    } else if ((flags & MF_POPUP) == 0) {
        // a menu command item
        statusUpdateStatus(ghWndStatus, MAKEINTRESOURCE(cmd));
    } else {
        //a popup menu - we need to search to find which one.
        // note that the cmd item in Win16 will now have a
        // menu handle, whereas in Win32 it has an index.
        // NOTE: this code assumes that the menu items
        // are #defined 100 apart in the same order, starting
        // with IDM_FILE
#ifdef _WIN32
        statusUpdateStatus(ghWndStatus, MAKEINTRESOURCE(IDM_FILE + (cmd * 100)));
#else
        int i,c;
        HMENU hmenuMain;

        hmenuMain = GetMenu(hWnd);
        c = GetMenuItemCount(hmenuMain);

        for(i = 0; i < c; i++) {
            if (hmenu == GetSubMenu(hmenuMain, i)) {
                statusUpdateStatus(MAKEINTRESOURCE(IDM_FILE + (cmd*100)));
                return(0);
            }
        }
        statusUpdateStatus(NULL);
#endif
    }
}

// a popup menu is being selected - enable or disable menu items
int
InitMenuPopup(
    HWND hwnd,
    HMENU hmenu,
    int index
)
{
    int i = MF_ENABLED;
    CAPSTATUS cs;
    BOOL bUsesPalettes;

    capGetStatus(ghWndCap, &cs, sizeof(cs));

    // try to see if the driver uses palettes
    if ((cs.hPalCurrent != NULL) || (cs.fUsingDefaultPalette)) {
        bUsesPalettes = TRUE;
    } else {
        bUsesPalettes = FALSE;
    }

    switch(index) {
    case 0:         // IDM_FILE

        if (lstrlen(gachCaptureFile) <= 0) {
            i = MF_GRAYED;
        }
        // save as enabled only if we have a capture file
        EnableMenuItem(hmenu, IDM_F_SAVEVIDEOAS, i);
        // edit video possible only if we have a capture file AND we've
        // captured something
        EnableMenuItem(hmenu, IDM_F_EDITVIDEO,
            (cs.dwCurrentVideoFrame > 0) ? i : MF_GRAYED);

        // allow save palette if there is one
        EnableMenuItem(hmenu, IDM_F_SAVEPALETTE,
            (cs.hPalCurrent != NULL) ? MF_ENABLED:MF_GRAYED);

        // allow load palette if the driver uses palettes
        EnableMenuItem(hmenu, IDM_F_LOADPALETTE,
            bUsesPalettes ? MF_ENABLED : MF_GRAYED);

        break;

    case 1:         // IDM_EDIT

        // paste palettes if driver uses them and there is one pastable
        EnableMenuItem(hmenu, IDM_E_PASTEPALETTE,
            (bUsesPalettes && IsClipboardFormatAvailable(CF_PALETTE)) ? MF_ENABLED:MF_GRAYED);

        break;

    case 2:         // IDM_OPTIONS

        EnableMenuItem(hmenu, IDM_O_AUDIOFORMAT,
            cs.fAudioHardware ? MF_ENABLED : MF_GRAYED);

        EnableMenuItem(hmenu, IDM_O_OVERLAY,
            gCapDriverCaps.fHasOverlay ? MF_ENABLED:MF_GRAYED);

        EnableMenuItem(hmenu, IDM_O_VIDEOFORMAT,
            gCapDriverCaps.fHasDlgVideoFormat ? MF_ENABLED:MF_GRAYED);

        EnableMenuItem(hmenu, IDM_O_VIDEODISPLAY,
            gCapDriverCaps.fHasDlgVideoDisplay ? MF_ENABLED:MF_GRAYED);

        EnableMenuItem(hmenu, IDM_O_VIDEOSOURCE,
            gCapDriverCaps.fHasDlgVideoSource ? MF_ENABLED:MF_GRAYED);

        EnableMenuItem(hmenu, IDM_O_PREVIEW,
                gbHaveHardware ? MF_ENABLED:MF_GRAYED);

    case 3:     // IDM_CAPTURE
        if (!gbHaveHardware) {
            i = MF_GRAYED;
        }
        EnableMenuItem(hmenu, IDM_C_CAPSEL, i);
        EnableMenuItem(hmenu, IDM_C_CAPTUREFRAME, i);
        EnableMenuItem(hmenu, IDM_C_CAPTUREVIDEO, i);
        EnableMenuItem(hmenu, IDM_C_PALETTE, (gbHaveHardware &&
            gCapDriverCaps.fDriverSuppliesPalettes) ? MF_ENABLED : MF_GRAYED);

        break;
    }
    return(0);
}

//
// MainWndProc: Application Main Window Procedure
//
LONG FAR PASCAL MainWndProc(HWND hWnd, UINT Message, UINT wParam, LONG lParam)
{
////////////////////////////////////////////////////////////////////////
//  hWnd:      Application main window handle
//  Message:   Next message to be processed
//  wParam:    WORD param for the message
//  lParam:    LONG param for the message
////////////////////////////////////////////////////////////////////////

    switch (Message) {

        static BOOL fMinimized;

        case WM_SYSCOMMAND:
    if ((wParam & 0xfff0) == SC_MAXIMIZE)
    fMinimized = FALSE;
    else if ((wParam & 0xfff0) == SC_RESTORE)
    fMinimized = FALSE;
    else if ((wParam & 0xfff0) == SC_MINIMIZE)
    fMinimized = TRUE;
    return DefWindowProc(hWnd, Message, wParam, lParam);
    break;

        case WM_COMMAND:
            MenuProc(hWnd, wParam, lParam) ;
            break ;

        case WM_CREATE:
            HelpInit(ghInstApp, "vidcap.hlp", hWnd);
            break;

        case WM_NCHITTEST:
        {
            DWORD dw;

            dw = DefWindowProc(hWnd, Message, wParam, lParam);
            // Don't allow border resize if autosizing
            if (gbAutoSizeFrame) {
                if (dw >= HTSIZEFIRST && dw <= HTSIZELAST)
                    dw = HTCAPTION;
            }
            return dw;

        }
            break;

        case WM_GETMINMAXINFO:
            // Don't allow manual sizing if window locked to the capture size
            if (gbHaveHardware && gbAutoSizeFrame && !gbInLayout) {
                RECT rW;

                LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam;

                GetWindowRect (hWnd, &rW);
                lpMMI->ptMinTrackSize.x = rW.right - rW.left;
                lpMMI->ptMinTrackSize.y = rW.bottom - rW.top;
                lpMMI->ptMaxTrackSize = lpMMI->ptMinTrackSize;
            }
            break;

        case WM_MOVE:
    if (!fMinimized) {
    vidcapLayout (hWnd);
    }
    break;

        case WM_SIZE:
    if (!fMinimized) {
    vidcapLayout (hWnd);
    }
    break;

        case WM_MENUSELECT:
            {
                UINT cmd = GET_WM_MENUSELECT_CMD(wParam, lParam);
                UINT flags = GET_WM_MENUSELECT_FLAGS(wParam, lParam);
                HMENU hmenu = GET_WM_MENUSELECT_HMENU(wParam, lParam);

                MenuSelect(hWnd, cmd, flags, hmenu);
            }
            break;

        case WM_INITMENUPOPUP:
            {
                BOOL bSystem = (BOOL) HIWORD(lParam);

                if (!bSystem) {
                    return InitMenuPopup(hWnd,
                            (HMENU) wParam, (int) LOWORD(lParam));
                } else {
                    return(DefWindowProc(hWnd, Message, wParam, lParam));
                }
            }

        case WM_SYSCOLORCHANGE:
            // we don't use this ourselves, but we should pass
            // it on to all three children
            SendMessage(ghWndFrame, Message, wParam, lParam);
            SendMessage(ghWndToolBar, Message, wParam, lParam);
            SendMessage(ghWndStatus, Message, wParam, lParam);
            return (TRUE);

        case WM_PALETTECHANGED:
        case WM_QUERYNEWPALETTE:
            // Pass the buck to Capture window proc
            return SendMessage(ghWndCap, Message, wParam, lParam) ;
            break ;

        case WM_SETFOCUS:
            // the toolbar is the only part that needs the focus
            SetFocus(ghWndToolBar);
            break;

        case WM_ACTIVATEAPP:
            if (wParam && ghWndCap)
                capPreviewRate(ghWndCap, 15); // Fast preview when active
            else
                capPreviewRate(ghWndCap, 1000); // Slow preview when inactive
            break;

        case WM_NEXTDLGCTL:
            // if anyone is tabbing about, move the focus to the
            // toolbar
            SetFocus(ghWndToolBar);

            // select the correct button to handle moving off one
            // end and back on the other end
            if (lParam == FALSE) {
                // are we moving forwards or backwards ?
                if (wParam == 0) {
                    // move to next - so select first button
                    toolbarSetFocus(ghWndToolBar, TB_FIRST);
                } else {
                    // move to previous - so select last
                    toolbarSetFocus(ghWndToolBar, TB_LAST);
                }
            }
            break;

        case WM_PAINT:
        {
            HDC           hDC ;
            PAINTSTRUCT   ps ;

            hDC = BeginPaint(hWnd, &ps) ;

            // Included in case the background is not a pure color
            SetBkMode(hDC, TRANSPARENT) ;

            EndPaint(hWnd, &ps) ;
            break ;
        }

        case WM_CLOSE:
            // Disable and free all the callbacks
            capSetCallbackOnError(ghWndCap, NULL) ;
if (fpErrorCallback) {
            FreeProcInstance(fpErrorCallback) ;
fpErrorCallback = NULL;
}

            capSetCallbackOnStatus(ghWndCap, NULL) ;
if (fpStatusCallback) {
            FreeProcInstance(fpStatusCallback) ;
fpStatusCallback = NULL;
}

            capSetCallbackOnYield(ghWndCap, NULL) ;
if (fpYieldCallback) {
            FreeProcInstance(fpYieldCallback) ;
fpYieldCallback = NULL;
}

            // Disconnect the current capture driver
            capDriverDisconnect (ghWndCap);

            // Destroy child windows, modeless dialogs, then this window...
            // DestroyWindow(ghWndCap) ;
            DestroyWindow(hWnd) ;
            break ;

        case WM_DESTROY:
            {
                // remember window size and position
                // - this will be written to the profile
                WINDOWPLACEMENT wp;

                wp.length = sizeof (WINDOWPLACEMENT);
                GetWindowPlacement(hWnd, &wp);

                gWinShow = wp.showCmd;
                gWinX = wp.rcNormalPosition.left;
                gWinY = wp.rcNormalPosition.top;
                gWinCX = RECTWIDTH(wp.rcNormalPosition);
                gWinCY = RECTHEIGHT(wp.rcNormalPosition);

                // write defaults out to the registry
                vidcapWriteProfile();
                vidcapWriteSettingsProfile();

                HelpShutdown();

            }

            PostQuitMessage(0) ;
            break ;

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

    return 0L;
}   // End of MainWndProc


VIDCAP.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   vidcap.h: Main application include file
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

#include "dialogs.h"

#define USE_ACM1        // Use ACM dialogs for audio format selection

//
// General purpose constants...
//
#define MAXVIDDRIVERS            10

#define DEF_CAPTURE_FPS          15
#define MIN_CAPTURE_FPS          (1.0 / 60)     // one frame per minute
#define MAX_CAPTURE_FPS          100

#define FPS_TO_MS(f)             ((DWORD) ((double)1.0e6 / f))

#define DEF_CAPTURE_RATE         FPS_TO_MS(DEF_CAPTURE_FPS)
#define MIN_CAPTURE_RATE         FPS_TO_MS(MIN_CAPTURE_FPS)
#define MAX_CAPTURE_RATE         FPS_TO_MS(MAX_CAPTURE_FPS)

#define DEF_PALNUMFRAMES         10
#define DEF_PALNUMCOLORS         236L
#define ONEMEG                   (1024L * 1024L)

//standard index size options
#define CAP_LARGE_INDEX          (30 * 60 * 60 * 3)     // 3 hrs @ 30fps
#define CAP_SMALL_INDEX          (30 * 60 * 15)         // 15 minutes @ 30fps

//
// Menu Ids...must not conflict with string table ids
// these are also the id of help strings in the string table
// (along with all the SC_ system menu items).
// menu popups must start 10 apart and be numbered in the same order
// as they appear if the help text is to work correctly for the
// popup heads as well as for the menu items.
//
#define IDM_SYSMENU               100

#define IDM_FILE                  200
#define IDM_F_SETCAPTUREFILE      201
#define IDM_F_SAVEVIDEOAS         202
#define IDM_F_ALLOCATESPACE       203
#define IDM_F_EXIT                204
#define IDM_F_LOADPALETTE         205
#define IDM_F_SAVEPALETTE         206
#define IDM_F_SAVEFRAME           207
#define IDM_F_EDITVIDEO           208

#define IDM_EDIT                  300
#define IDM_E_COPY                301
#define IDM_E_PASTEPALETTE        302
#define IDM_E_PREFS               303

#define IDM_CAPTURE               400
#define IDM_C_CAPTUREVIDEO        401
#define IDM_C_CAPTUREFRAME        402
#define IDM_C_PALETTE             403
#define IDM_C_CAPSEL              404
#define IDM_C_TEST                405
#define IDM_C_TESTAGAIN           406

#define IDM_OPTIONS               500
#define IDM_O_PREVIEW             501
#define IDM_O_OVERLAY             502
#define IDM_O_AUDIOFORMAT         503
#define IDM_O_VIDEOFORMAT         504
#define IDM_O_VIDEOSOURCE         505
#define IDM_O_VIDEODISPLAY        506
#define IDM_O_CHOOSECOMPRESSOR    507

#define IDM_O_DRIVER0             520
#define IDM_O_DRIVER1             521
#define IDM_O_DRIVER2             522
#define IDM_O_DRIVER3             523
#define IDM_O_DRIVER4             524
#define IDM_O_DRIVER5             525
#define IDM_O_DRIVER6             526
#define IDM_O_DRIVER7             527
#define IDM_O_DRIVER8             528
#define IDM_O_DRIVER9             529

#define IDM_HELP                  600
#define IDM_H_CONTENTS            601
#define IDM_H_ABOUT               602

// filter rcdata ids
#define ID_FILTER_AVI           900
#define ID_FILTER_PALETTE       901
#define ID_FILTER_DIB           902

/*
 * string table id
 *
 * NOTE: string table ID's must not conflict with IDM_ menu ids,
 * as there is a help string for each menu id.
 */

#define IDS_APP_TITLE            1001

#define IDS_ERR_REGISTER_CLASS   1002
#define IDS_ERR_CREATE_WINDOW    1003
#define IDS_ERR_FIND_HARDWARE    1004
#define IDS_ERR_CANT_PREALLOC    1005
#define IDS_ERR_MEASUREFREEDISK  1006
#define IDS_ERR_SIZECAPFILE      1007
#define IDS_ERR_RECONNECTDRIVER  1008
#define IDS_ERR_CMDLINE          1009
#define IDS_WARN_DEFAULT_PALETTE 1010

#define IDS_TITLE_SETCAPTUREFILE 1101
#define IDS_TITLE_SAVEAS         1102
#define IDS_TITLE_LOADPALETTE    1104
#define IDS_TITLE_SAVEPALETTE    1105
#define IDS_TITLE_SAVEDIB        1106
#define IDS_PROMPT_CAPFRAMES     1107
#define IDS_STATUS_NUMFRAMES     1108
#define IDS_CAP_CLOSE            1109
#define IDS_MCI_CONTROL_ERROR    1110
#define IDS_ERR_ACCESS_SOUNDDRIVER 1111
#define IDS_ERR_VIDEDIT          1112

#define IDC_toolbarSETFILE      1220
#define IDC_toolbarCAPFRAME     1221
#define IDC_toolbarCAPSEL       1222
#define IDC_toolbarCAPAVI       1223
#define IDC_toolbarCAPPAL       1224
#define IDC_toolbarLIVE         1225
#define IDC_toolbarEDITCAP      1226
#define IDC_toolbarOVERLAY      1227

#define IDS_CAPPAL_CLOSE        1230
#define IDS_CAPPAL_STATUS       1231
#define IDS_CAPPAL_STOP         1232
#define IDS_CAPPAL_START        1233

#defineIDS_CAP_RTL             1234

#define IDBMP_TOOLBAR100// main toolbar

//
// Macro Definitions...
//
#define IsDriverIndex(w) ( ((w) >= IDM_O_DRIVERS)  &&  \
                           ((w) - IDM_O_DRIVERS < MAXVIDDRIVERS) )

#define RECTWIDTH(rc)  ((rc).right - (rc).left)
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)

//
// Global Variables...
//

// preferences
extern BOOL gbCentre;
extern BOOL gbToolBar;
extern BOOL gbStatusBar;
extern BOOL gbAutoSizeFrame;
extern int gBackColour;
extern BOOL gfIsRTL;

extern char           gachAppName[] ;
extern char           gachAppTitle[];
extern char           gachIconName[] ;
extern char           gachMenuName[] ;
extern char           gachString[] ;
extern char           gachMCIDeviceName[] ;

extern HINSTANCE      ghInstApp ;
extern HWND           ghWndMain ;
extern HWND           ghWndCap ;
extern HWND           ghWndFrame;
extern HANDLE         ghAccel ;
extern WORD           gwDeviceIndex ;
extern WORD           gwPalFrames ;
extern WORD           gwPalColors ;
extern WORD           gwCapFileSize ;
extern BOOL           gbLive ;

extern CAPSTATUS      gCapStatus ;
extern CAPDRIVERCAPS  gCapDriverCaps ;
extern CAPTUREPARMS   gCapParms ;

extern HANDLE         ghwfex ;
extern LPWAVEFORMATEX glpwfex ;

//
// Dialog Box Procedures...
//
int FAR PASCAL AboutProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL AudioFormatProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL CapSetUpProc(HWND, UINT, UINT, LONG) ;
BOOL CALLBACK MakePaletteProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL AllocCapFileProc(HWND, UINT, UINT, LONG) ;
int FAR PASCAL PrefsDlgProc(HWND, UINT, WPARAM, LPARAM);
int FAR PASCAL NoHardwareDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
long FAR PASCAL CapFramesProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam);

// utility functions (in vidcap.c)
/*
 * put up a message box. the main window ghWndMain is used as the parent
 * window, and the app title gachAppTitle is used as the dialog title.
 * the text for the dialog -idString- is loaded from the resource string table
 */
int MessageBoxID(UINT idString, UINT fuStyle);
LPSTR tmpString(UINT idString);


VIDFRAME.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   vidframe.c: Frame for the capture window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * Window class that provides a frame for the AVICAP window in the
 * VidCap capture tool. Responsible for positioning within the
 * parent window, handling scrolling and painting a size border if
 * there is room.
 */

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <vfw.h>
#include "vidcap.h"

#include "vidframe.h"

/*
 * pixels to move when asked to scroll one line or page
 */
#define LINE_SCROLL10
#define PAGE_SCROLL50

// class name
#define VIDFRAMECLASSNAME   "vidframeClass"

/*
 * standard brushes
 */
static HBRUSH ghbrBackground = NULL, ghbrFace, ghbrHighlight, ghbrShadow;
static BOOL   fhbrBackgroundIsSysObj;

/*
 * create brushes to be used in painting
 */
void
vidframeCreateTools(HWND hwnd)
{

    vidframeSetBrush(hwnd, gBackColour);

    ghbrHighlight  = CreateSolidBrush(GetSysColor(COLOR_BTNHIGHLIGHT));
    ghbrShadow  = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
    ghbrFace  = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
}

void
vidframeDeleteTools(void)
{
    if (ghbrBackground) {
        if (!fhbrBackgroundIsSysObj) {
            DeleteObject(ghbrBackground);
            ghbrBackground = NULL;
        }
    }

    if (ghbrHighlight) {
        DeleteObject(ghbrHighlight);
        ghbrHighlight = NULL;
    }

    if (ghbrShadow) {
        DeleteObject(ghbrShadow);
        ghbrShadow = NULL;
    }

    if (ghbrFace) {
        DeleteObject(ghbrFace);
        ghbrFace = NULL;
    }
}

/*
 * change the background fill brush to be one of-
 *  IDD_PrefsDefBackground  - windows default background colour
 *  IDD_PrefsLtGrey - light grey
 *  IDD_PrefsDkGrey - dark grey
 *  IDD_PrefsBlack - black
 */
void
vidframeSetBrush(HWND hwnd, int iPref)
{
    if (ghbrBackground != NULL) {
        if (!fhbrBackgroundIsSysObj) {
            DeleteObject(ghbrBackground);
            ghbrBackground = NULL;
        }
    }

    switch(iPref) {
    case IDD_PrefsDefBackground:
        ghbrBackground = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        fhbrBackgroundIsSysObj = FALSE;
        break;

    case IDD_PrefsLtGrey:
        ghbrBackground = GetStockObject(LTGRAY_BRUSH);
        fhbrBackgroundIsSysObj = TRUE;
        break;

    case IDD_PrefsDkGrey:
        ghbrBackground = GetStockObject(DKGRAY_BRUSH);
        fhbrBackgroundIsSysObj = TRUE;
        break;

    case IDD_PrefsBlack:
        ghbrBackground = GetStockObject(BLACK_BRUSH);
        fhbrBackgroundIsSysObj = TRUE;
        break;

    default:
        return;
    }

    if (hwnd != NULL) {
#ifdef _WIN32
        SetClassLong(hwnd, GCL_HBRBACKGROUND, (LONG) ghbrBackground);
#else
        SetClassWord(hwnd, GCW_HBRBACKGROUND, (WORD) ghbrBackground);
#endif
        InvalidateRect(hwnd, NULL, TRUE);
    }
}

/*
 * layout the window  - decide if we need scrollbars or
 * not, and position the avicap window correctly
 */
void
vidframeLayout(HWND hwnd, HWND hwndCap)
{
    RECT rc;
    RECT rcCap;
    CAPSTATUS cs;
    int cx, cy;
    POINT ptScroll;

    // get the x and y scroll pos so we can reset them
    ptScroll.y = GetScrollPos(hwnd, SB_VERT);
    ptScroll.x = GetScrollPos(hwnd, SB_HORZ);

    GetClientRect(hwnd, &rc);
    if (!capGetStatus(hwndCap, &cs, sizeof(cs))) {
        // no current window? - make it 0 size
        cs.uiImageWidth = 0;
        cs.uiImageHeight = 0;

    }

    SetRect(&rcCap, 0, 0, cs.uiImageWidth, cs.uiImageHeight);

    /*
     * check which scrollbars we need - note that adding and removing
     * scrollbars affects the other dimension - so recheck client rect
     */
    if (RECTWIDTH(rcCap) < RECTWIDTH(rc)) {
        // fits horz.
        SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
    } else {
        // need horz scrollbar
        SetScrollRange(hwnd, SB_HORZ, 0, RECTWIDTH(rcCap) - RECTWIDTH(rc), FALSE);
    }

    // get client size in case shrunk/expanded
    GetClientRect(hwnd, &rc);

    // check vert scrollbar
    if (RECTHEIGHT(rcCap) < RECTHEIGHT(rc)) {
        SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
    } else {
        SetScrollRange(hwnd, SB_VERT, 0, RECTHEIGHT(rcCap) - RECTHEIGHT(rc), FALSE);

        // this may have caused the horz scrollbar to be unneeded
        GetClientRect(hwnd, &rc);
        if (RECTWIDTH(rcCap) < RECTWIDTH(rc)) {
            // fits horz.
            SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
        } else {
            // need horz scrollbar
            SetScrollRange(hwnd, SB_HORZ, 0, RECTWIDTH(rcCap) - RECTWIDTH(rc), FALSE);
        }
    }

    /*
     * be sure we don't leave any underwear showing if we have scrolled
     * back or removed the scrollbars
     */
    {
        int cmax, cmin;

        GetScrollRange(hwnd, SB_HORZ, &cmin, &cmax);
        if (ptScroll.x > cmax) {
            ptScroll.x = cmax;
        }
        GetScrollRange(hwnd, SB_VERT, &cmin, &cmax);
        if (ptScroll.y > cmax) {
            ptScroll.y = cmax;
        }
        SetScrollPos(hwnd, SB_HORZ, ptScroll.x, TRUE);
        SetScrollPos(hwnd, SB_VERT, ptScroll.y, TRUE);
        capSetScrollPos(hwndCap, &ptScroll);
    }

    // centre the window if requested and if room
    if(gbCentre) {
        GetClientRect(hwnd, &rc);
        cx = max(0, (RECTWIDTH(rc) - (int) cs.uiImageWidth)/2);
        cy = max(0, (RECTHEIGHT(rc) - (int) cs.uiImageHeight)/2);
        OffsetRect(&rcCap, cx, cy);
    }

    // DWORD align the capture window for optimal codec speed
    // during preview.
    rc = rcCap;
    MapWindowPoints (hwnd, NULL, (LPPOINT)&rc, 1);
    cx = rc.left - (rc.left & ~3);
    OffsetRect(&rcCap, -cx, 0);

    MoveWindow(hwndCap,
            rcCap.left, rcCap.top,
            RECTWIDTH(rcCap), RECTHEIGHT(rcCap),
            TRUE);

    InvalidateRect(hwnd, NULL, TRUE);
}

/*
 * paint the vidframe window. The fill colour is always selected as the
 * background brush, so all we need to do here is paint the
 * fancy border around the inner window if room.
 */
void
vidframePaint(HWND hwnd, HWND hwndCap)
{
    POINT ptInner;
    RECT rcCap;
    PAINTSTRUCT ps;
    HDC hdc;
    HBRUSH hbr;
    int cx, cy;

    hdc = BeginPaint(hwnd, &ps);

    /*
     * first calculate the location of the upper left corner
     * of the avicap window in vidframe-window client co-ordinates
     */
    ptInner.x = 0;
    ptInner.y = 0;
    MapWindowPoints(hwndCap, hwnd, &ptInner, 1);

    // width and height of cap window
    GetWindowRect(hwndCap, &rcCap);
    cx = RECTWIDTH(rcCap);
    cy = RECTHEIGHT(rcCap);

    // shadow lines
    hbr = SelectObject(hdc, ghbrShadow);
    PatBlt(hdc, ptInner.x-1, ptInner.y-1, cx + 1, 1, PATCOPY);
    PatBlt(hdc, ptInner.x-1, ptInner.y-1, 1, cy + 1, PATCOPY);
    PatBlt(hdc, ptInner.x + cx + 4, ptInner.y-5, 1, cy+10, PATCOPY);
    PatBlt(hdc, ptInner.x -5, ptInner.y+cy+4, cx+10, 1, PATCOPY);

    // hi-light lines
    SelectObject(hdc, ghbrHighlight);
    PatBlt(hdc, ptInner.x - 5, ptInner.y - 5, 1, cy+9, PATCOPY);
    PatBlt(hdc, ptInner.x - 5, ptInner.y - 5, cx+9, 1, PATCOPY);
    PatBlt(hdc, ptInner.x+cx, ptInner.y-1, 1, cy+2, PATCOPY);
    PatBlt(hdc, ptInner.x-1, ptInner.y+cy, cx, 1, PATCOPY);

    // fill bordered area with button face colour
    SelectObject(hdc, ghbrFace);
    PatBlt(hdc, ptInner.x-4, ptInner.y-4, cx+8, 3, PATCOPY);
    PatBlt(hdc, ptInner.x-4, ptInner.y+cy+1, cx+8, 3, PATCOPY);
    PatBlt(hdc, ptInner.x-4, ptInner.y-1, 3, cy+2, PATCOPY);
    PatBlt(hdc, ptInner.x+cx+1, ptInner.y-1, 3, cy+2, PATCOPY);

    SelectObject(hdc, hbr);

    EndPaint(hwnd, &ps);

}

/*
 * respond to a scrollbar message by moving the current scroll
 * position horizontally
 */
void
vidframeHScroll(HWND hwnd, HWND hwndCap, int code, int pos)
{
    POINT pt;
    int cmax, cmin;

    pt.x = GetScrollPos(hwnd, SB_HORZ);
    pt.y = GetScrollPos(hwnd, SB_VERT);
    GetScrollRange(hwnd, SB_HORZ, &cmin, &cmax);

    switch(code) {
    case SB_LINEUP:
        pt.x -= LINE_SCROLL;
        break;

    case SB_LINEDOWN:
        pt.x += LINE_SCROLL;
        break;

    case SB_PAGEUP:
        pt.x -= PAGE_SCROLL;
        break;

    case SB_PAGEDOWN:
        pt.x += PAGE_SCROLL;
        break;

    case SB_THUMBTRACK:
    case SB_THUMBPOSITION:
        pt.x = pos;
        break;
    }

    if (pt.x < cmin) {
        pt.x = cmin;
    } else if (pt.x > cmax) {
        pt.x = cmax;
    }
    SetScrollPos(hwnd, SB_HORZ, pt.x, TRUE);
    capSetScrollPos(hwndCap, &pt);

}

/*
 * respond to a scrollbar message by moving the current scroll
 * position vertically
 */
void
vidframeVScroll(HWND hwnd, HWND hwndCap, int code, int pos)
{
    POINT pt;
    int cmax, cmin;

    pt.x = GetScrollPos(hwnd, SB_HORZ);
    pt.y = GetScrollPos(hwnd, SB_VERT);
    GetScrollRange(hwnd, SB_VERT, &cmin, &cmax);

    switch(code) {
    case SB_LINEUP:
        pt.y -= LINE_SCROLL;
        break;

    case SB_LINEDOWN:
        pt.y += LINE_SCROLL;
        break;

    case SB_PAGEUP:
        pt.y -= PAGE_SCROLL;
        break;

    case SB_PAGEDOWN:
        pt.y += PAGE_SCROLL;
        break;

    case SB_THUMBTRACK:
    case SB_THUMBPOSITION:
        pt.y = pos;
        break;
    }

    if (pt.y < cmin) {
        pt.y = cmin;
    } else if (pt.y > cmax) {
        pt.y = cmax;
    }
    SetScrollPos(hwnd, SB_VERT, pt.y, TRUE);
    capSetScrollPos(hwndCap, &pt);
}

LONG FAR PASCAL
vidframeProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch(message) {

    case WM_MOVE:
    case WM_SIZE:
        if (ghWndCap) {
            vidframeLayout(hwnd, ghWndCap);
        }
        break;

    case WM_SYSCOLORCHANGE:
        // re-get brushes - we will be sent a paint message
        vidframeDeleteTools();
        vidframeCreateTools(hwnd);
        return(TRUE);

    case WM_PALETTECHANGED:
    case WM_QUERYNEWPALETTE:
        // allow the avicap window to handle this
        if (ghWndCap) {
            return SendMessage(ghWndCap, message, wParam, lParam) ;
        }

    case WM_PAINT:
        if (ghWndCap) {
            vidframePaint(hwnd, ghWndCap);
        }
        break;

    case WM_HSCROLL:
        if (ghWndCap) {
            vidframeHScroll(hwnd, ghWndCap,
                GET_WM_HSCROLL_CODE(wParam, lParam),
                GET_WM_HSCROLL_POS(wParam, lParam)
                );
        }
        break;

    case WM_VSCROLL:
        if (ghWndCap) {
            vidframeVScroll(hwnd, ghWndCap,
                GET_WM_VSCROLL_CODE(wParam, lParam),
                GET_WM_VSCROLL_POS(wParam, lParam)
                );
        }
        break;

    case WM_DESTROY:
        vidframeDeleteTools();
        break;

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

    }
    return(0);
}

/*
 * create a frame window and child capture window at the
 * given location. Initialise the class if this is the
 * first time through.
 *
 * returns the window handle of the frame window
 * (or NULL if failure). returns the window handle of the AVICAP window
 * via phwndCap.
 */
HWND
vidframeCreate(
    HWND hwndParent,
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    int x,
    int y,
    int cx,
    int cy,
    HWND FAR * phwndCap
)
{
    HWND hwnd, hwndCap;
    static BOOL bInitDone = FALSE;

    if (!bInitDone) {
        WNDCLASS wc;

        vidframeCreateTools(NULL);

        if (!hPrevInstance) {
            // If it's the first instance, register the window class
            wc.lpszClassName = VIDFRAMECLASSNAME;
            wc.hInstance     = hInstance;
            wc.lpfnWndProc   = vidframeProc;
            wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
            wc.hIcon         = NULL;
            wc.lpszMenuName  = NULL;
            wc.hbrBackground = ghbrBackground;
            wc.style         = CS_HREDRAW | CS_VREDRAW ;
            wc.cbClsExtra    = 0 ;
            wc.cbWndExtra    = 0 ;

            if(!RegisterClass(&wc)) {
                return(NULL);
            }
        }
        bInitDone = TRUE;
    }

    hwnd = CreateWindowEx(
                gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0,
                VIDFRAMECLASSNAME,
                NULL,
                WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|WS_CLIPCHILDREN,
                x, y, cx, cy,
                hwndParent,
                (HMENU) 0,
                hInstance,
                NULL);

    if (hwnd == NULL) {
        return(NULL);
    }

    /*
     * create an AVICAP window within this window. Leave vidframeLayout
     * to do the layout
     */
    hwndCap = capCreateCaptureWindow(
                    NULL,
                    WS_CHILD | WS_VISIBLE,
                    0, 0, 160, 120,
                    hwnd,               // parent window
                    1                   // child window id
              );

    if (hwndCap == NULL) {
        return(NULL);
    }

    *phwndCap = hwndCap;
    return(hwnd);
}


VIDFRAME.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   vidframe.h: Frame for capture window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * interface to vidframe window class - this window class creates a child
 * AVICAP window, and is responsible for positioning it within the vidframe
 * window, framing it, and scrolling it if it will not fit. Call
 * vidframeCreate to create the window, and vidframeLayout if the
 * video image size might have changed.
 */

/*
 * create a frame window and child capture window at the
 * given location. Initialise the class if this is the
 * first time through.
 *
 * returns the window handle of the frame window
 * (or NULL if failure). returns the window handle of the AVICAP window
 * via phwndCap.
 */
HWND vidframeCreate(
    HWND hwndParent,
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    int x,
    int y,
    int cx,
    int cy,
    HWND FAR * phwndCap
);

/*
 * layout the window  - decide if we need scrollbars or
 * not, and position the avicap window correctly
 *
 * called if the size of the vidframe window changes or can be
 * called externally whenever the video size might have changed.
 */
void vidframeLayout(HWND hwndFrame, HWND hwndCap);

/*
 * change the background fill brush to be one of-
 *  IDD_PrefsDefBackground  - windows default background colour
 *  IDD_PrefsLtGrey - light grey
 *  IDD_PrefsDkGrey - dark grey
 *  IDD_PrefsBlack - black
 */
void vidframeSetBrush(HWND hwnd, int iPref);


README.TXT

Example showing how to use the AVIFILE interface to write files such as those
that AVIView reads.


WRITEAVI.C

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  WRITEAVI.C
 *
 *  Creates the file OUTPUT.AVI, an AVI file consisting of a rotating clock
 *  face.  This program demonstrates using the functions in AVIFILE.DLL
 *  to make writing AVI files simple.
 *
 *  This is a stripped-down example; a real application would have a user
 *  interface and check for errors.
 *
 ***************************************************************************/

#define  STRICT
#define  INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <memory.h>
#include <mmsystem.h>
#include <vfw.h>

#include "writeavi.h"
//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------
// Our movie is 160x120 and 15 frames long
//
#define BITMAP_X160
#define BITMAP_Y120
#define N_FRAMES15

#define TEXT_HEIGHT20

#define AVIIF_KEYFRAME0x00000010L // this frame is a key frame.

#define BUFSIZE 260

#define LPLPBILPBITMAPINFOHEADER *

//---------------------------------------------------------------------------
// Function declarations
//---------------------------------------------------------------------------
static void FreeFrames(LPLPBI) ;
static void MakeFrames(LPLPBI, UINT, UINT, UINT) ;
static HANDLE MakeDib(HBITMAP, UINT);

//---------------------------------------------------------------------------
// A quick lookup table for Sin and Cos values
//---------------------------------------------------------------------------
//
static int aSin[N_FRAMES] = {
0,    40,    74,    95,    99,
86,    58,    20,    -20,    -58,
-86,    -99,    -95,    -74,    -40,
} ;

static int aCos[N_FRAMES] = {
100,    91,    66,    30,    -10,
-49,    -80,    -97,    -97,    -80,
-50,    -10,    30,    66,    91,
} ;

//----------------------------------------------------------------------------
//
// We don't have a window, we just pop up a dialog
// box, write the file, and quit
//
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR szCmdLine,
int sw)
{
LPBITMAPINFOHEADER alpbi[N_FRAMES];
int i;
AVISTREAMINFO strhdr;
PAVIFILE pfile = NULL;
PAVISTREAM ps = NULL, psCompressed = NULL, psText = NULL;
char szText[BUFSIZE];
int iLen;
AVICOMPRESSOPTIONS opts;
AVICOMPRESSOPTIONS FAR * aopts[1] = {&opts};
HRESULT hr;
DWORD dwTextFormat;
WORD wVer;
char szTitle[BUFSIZE];
char szMessage[BUFSIZE];

/* first let's make sure we are running on 1.1 */
wVer = HIWORD(VideoForWindowsVersion());
if (wVer < 0x010a){
 /* oops, we are too old, blow out of here */
LoadString(hInstance, IDS_APPERR, szTitle, BUFSIZE );
LoadString(hInstance, IDS_VFWTOOOLD, szMessage, BUFSIZE );
MessageBeep(MB_ICONHAND);
MessageBox(NULL, szMessage, szTitle, MB_OK|MB_ICONSTOP);
return FALSE;
}

alpbi[0] = NULL;

LoadString(hInstance, IDS_APPNAME, szTitle, BUFSIZE );
LoadString(hInstance, IDS_INTRO, szMessage, BUFSIZE );

if (MessageBox(NULL, szMessage, szTitle, MB_OKCANCEL) == IDCANCEL)
return 0;

//
// Set up the bitmaps for the file in an array
//
MakeFrames(alpbi, 8, BITMAP_X, BITMAP_Y);

AVIFileInit();

//
// Open the movie file for writing....
//
LoadString(hInstance, IDS_FILENAME, szTitle, BUFSIZE );

hr = AVIFileOpen(&pfile,    // returned file pointer
       szTitle,            // file name
       OF_WRITE | OF_CREATE,    // mode to open file with
       NULL);    // use handler determined
    // from file extension....
if (hr != AVIERR_OK)
goto error;

// Fill in the header for the video stream....

// The video stream will run in 15ths of a second....

_fmemset(&strhdr, 0, sizeof(strhdr));
strhdr.fccType                = streamtypeVIDEO;// stream type
strhdr.fccHandler             = 0;
strhdr.dwScale                = 1;
strhdr.dwRate                 = 15;    // 15 fps
strhdr.dwSuggestedBufferSize  = alpbi[0]->biSizeImage;
SetRect(&strhdr.rcFrame, 0, 0,    // rectangle for stream
    (int) alpbi[0]->biWidth,
    (int) alpbi[0]->biHeight);

// And create the stream;
hr = AVIFileCreateStream(pfile,    // file pointer
         &ps,    // returned stream pointer
         &strhdr);    // stream header
if (hr != AVIERR_OK) {
goto error;
}

_fmemset(&opts, 0, sizeof(opts));

if (!AVISaveOptions(NULL, 0, 1, &ps, (LPAVICOMPRESSOPTIONS FAR *) &aopts))
    goto error;

hr = AVIMakeCompressedStream(&psCompressed, ps, &opts, NULL);
if (hr != AVIERR_OK) {
goto error;
}

hr = AVIStreamSetFormat(psCompressed, 0,
       alpbi[0],    // stream format
       alpbi[0]->biSize +   // format size
       alpbi[0]->biClrUsed * sizeof(RGBQUAD));
if (hr != AVIERR_OK) {
goto error;
}

// Fill in the stream header for the text stream....

// The text stream is in 60ths of a second....

_fmemset(&strhdr, 0, sizeof(strhdr));
strhdr.fccType                = streamtypeTEXT;
strhdr.fccHandler             = mmioFOURCC('D', 'R', 'A', 'W');
strhdr.dwScale                = 1;
strhdr.dwRate                 = 60;
strhdr.dwSuggestedBufferSize  = sizeof(szText);
SetRect(&strhdr.rcFrame, 0, (int) alpbi[0]->biHeight,
    (int) alpbi[0]->biWidth, (int) alpbi[0]->biHeight + TEXT_HEIGHT);

// ....and create the stream.
hr = AVIFileCreateStream(pfile, &psText, &strhdr);
if (hr != AVIERR_OK) {
goto error;
}

dwTextFormat = sizeof(dwTextFormat);
hr = AVIStreamSetFormat(psText, 0, &dwTextFormat, sizeof(dwTextFormat));
if (hr != AVIERR_OK) {
goto error;
}

//
// Now write out each video frame, along with a text label.
// The video frames are 2/3 of a second apart, which is 10
// in the video time scale and 40 in the text stream's time scale.
//
for (i = 0; i < N_FRAMES; i++) {
hr = AVIStreamWrite(psCompressed,// stream pointer
i * 10,// time of this frame
1,// number to write
(LPBYTE) alpbi[i] +// pointer to data
alpbi[i]->biSize +
alpbi[i]->biClrUsed * sizeof(RGBQUAD),
alpbi[i]->biSizeImage,// size of this frame
AVIIF_KEYFRAME, // flags....
NULL,
NULL);
if (hr != AVIERR_OK)
break;

// Make some text to put in the file ...
LoadString(hInstance, IDS_TEXTFORMAT, szMessage, BUFSIZE );

iLen = wsprintf(szText, szMessage, (int)(i + 1));

// ... and write it as well.
hr = AVIStreamWrite(psText,
i * 40,
1,
szText,
iLen + 1,
AVIIF_KEYFRAME,
NULL,
NULL);
if (hr != AVIERR_OK)
break;
}

error:
//
// Now close the file
//
if (ps)
AVIStreamClose(ps);

if (psCompressed)
AVIStreamClose(psCompressed);

if (psText)
AVIStreamClose(psText);

if (pfile)
AVIFileClose(pfile);

AVIFileExit();
FreeFrames(alpbi);

if (hr != NOERROR) {
LoadString(hInstance, IDS_APPERR, szTitle, BUFSIZE );
LoadString(hInstance, IDS_WRITEERR, szMessage, BUFSIZE );

MessageBox(NULL, szMessage, szTitle, MB_OK);
}
return 0;
}

//
// Fill an array of LPBI's with the frames for this movie
//
static void MakeFrames(LPLPBI alpbi, UINT bits, UINT wXSize,UINT wYSize )
{
HDC         hdc ;
HDC         hdcMem ;
HBITMAP     hbitmap,hbitmapOld ;
HPEN        hpen3,hpen1,hpenwhite,hpenOld ;
HFONT       hfont,hfontOld ;
HBRUSH      hbrush,hbrushOld ;
RECT        rc ;
RECT        rcFrameNo ;
int         wXCent,wYCent ;
int         cxPixInch ;
int         cyPixInch ;
int         cxPixels ;
int         cyPixels ;
int         radius ;
int         x0,y0,x1,y1 ;
int         i,j ;
char        szNumber[3] ;

//
// Make sure our resources are freed
//
FreeFrames(alpbi);

//
// Find the center of the movie
//
wXCent = wXSize/2 ;
wYCent = wYSize/2 ;

hdc = GetDC(NULL) ;
hdcMem = CreateCompatibleDC(NULL) ;

//
// We need some gray and white brushes and pens, and a bitmap
//
hpen3 = CreatePen(PS_SOLID,3,RGB(128,128,128)) ;
hpen1 = CreatePen(PS_SOLID,1,RGB(64,64,64));
hpenwhite = CreatePen(PS_SOLID,1,RGB(255,255,255));
hpenOld = SelectPen(hdcMem, hpen3);
hbrush = CreateSolidBrush(RGB(192,192,192)) ;
hbrushOld = SelectBrush(hdcMem,hbrush) ;
hbitmap = CreateCompatibleBitmap(hdc,wXSize,wYSize) ;

cxPixInch = GetDeviceCaps(hdc,LOGPIXELSX) ;
cyPixInch = GetDeviceCaps(hdc,LOGPIXELSY) ;

//
// What radius of circle can we fit in this frame?  Make sure it's round
// regardless of the aspect ratio
//
radius = ( wXSize < wYSize ) ? wXSize : (wYSize*cxPixInch)/cyPixInch ;
radius = ( radius * 95 ) / 200 ;

//
// Make a Rectangle in the center where the number will go
//
/* x0 = radius / sqrt(2) */
x0 = (radius*100)/141 ;
y0 = (x0*cyPixInch)/cxPixInch ;
x0 = (x0*9)/10 ;
y0 = (y0*9)/10 ;
SetRect( &rcFrameNo,wXCent-x0,wYCent-y0,wXCent+x0,wYCent+y0 ) ;

//
// Move the rectangle in a little and make a font big enough for it
//
x0 = (x0*9)/10 ;
y0 = (y0*9)/10 ;

hfont = CreateFont(
y0*2,
x0,
0,
0,
FW_BOLD,
0,
0,
0,
ANSI_CHARSET,
OUT_DEVICE_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS,
NULL
);

hfontOld = SelectFont(hdcMem, hfont);

//
// Now walk through and make all the frames
//
for ( i=0; i<N_FRAMES; i++ ) {
hbitmapOld = SelectBitmap(hdcMem, hbitmap);

//
// Fill the whole frame with white
//
SetRect(&rc,0,0,wXSize,wYSize) ;
FillRect(hdcMem,&rc,GetStockBrush(WHITE_BRUSH)) ;

//
// Draw the circle inside the previously calculated radius
//
cxPixels = radius ;
cyPixels = (cxPixels*cyPixInch)/cxPixInch ;

SelectPen(hdcMem,hpen3) ;
Ellipse(hdcMem,wXCent-cxPixels,wYCent-cyPixels,wXCent+cxPixels,
wYCent+cyPixels) ;

SelectPen(hdcMem,hpen1) ;

//
// Draw the number in the previously calculated area
//
wsprintf(szNumber,"%02u",i+1) ;

SetBkColor(hdcMem,RGB(192,192,192)) ;
SetTextColor(hdcMem,RGB(255,255,255)) ;
ExtTextOut(
hdcMem,
rcFrameNo.left,
rcFrameNo.top+(rcFrameNo.bottom-rcFrameNo.top)/20,
ETO_CLIPPED,
&rcFrameNo,
szNumber,
2,
NULL);

//
// Draw tic marks around the inside of the circle in equal divisions
//
for ( j=0; j<N_FRAMES; j++ ) {
x0 = (radius*aSin[j])/100 ;
y0 = (radius*aCos[j])/100 ;
x1 = (((radius*aSin[j])/100)*11)/12 ;
y1 = (((radius*aCos[j])/100)*11)/12 ;

y0 = -(y0*cyPixInch)/cxPixInch ;
y1 = -(y1*cyPixInch)/cxPixInch ;

MoveToEx(hdcMem,wXCent+x0,wYCent+y0,NULL) ;
LineTo(hdcMem,wXCent+x1,wYCent+y1) ;
}

//
// Now draw the hand of the clock in the appropriate position
//
x1 = (((radius*aSin[i])/100)*5)/8 ;
y1 = (((radius*aCos[i])/100)*5)/8 ;
y1 = -(y1*cyPixInch)/cxPixInch ;

MoveToEx(hdcMem,wXCent,wYCent,NULL) ;
LineTo(hdcMem,wXCent+x1,wYCent+y1) ;

SelectBitmap(hdcMem, hbitmapOld);
//
// Make this into a DIB and stuff it into the array
//
alpbi[i] = (LPBITMAPINFOHEADER)GlobalLock(MakeDib(hbitmap, bits));

//
// For an error, just duplicate the last frame if we can
//
if (alpbi[i] == NULL && i )
alpbi[i] = alpbi[i-1] ;
}

//
// Select all the old objects back and delete resources
//
SelectPen(hdcMem, hpenOld);
SelectBrush(hdcMem,hbrushOld) ;
SelectFont(hdcMem,hfontOld) ;
DeletePen(hpen1) ;
DeletePen(hpen3) ;
DeletePen(hpenwhite) ;
DeleteBrush(hbrush) ;
DeleteBitmap(hbitmap) ;
DeleteFont(hfont) ;
DeleteObject(hdcMem) ;
ReleaseDC(NULL,hdc) ;
}

//
// Walk through our array of LPBI's and free them
//
static void FreeFrames(LPLPBI alpbi)
{
UINT        w ;

if (!alpbi[0])
return ;
//
// Don't free a frame if it's a duplicate of the previous one
//
for (w=0; w<N_FRAMES; w++)
if (alpbi[w] && alpbi[w] != alpbi[w-1])
GlobalFreePtr(alpbi[w]);
for (w=0; w<N_FRAMES; w++)
alpbi[w] = NULL;
}

/*
** MakeDib(hbitmap)
**
** Take the given bitmap and transform it into a DIB with parameters:
**
** BitsPerPixel:    8
** Colors:          palette
**
*/
static HANDLE  MakeDib( HBITMAP hbitmap, UINT bits )
{
HANDLE              hdib ;
HDC                 hdc ;
BITMAP              bitmap ;
UINT                wLineLen ;
DWORD               dwSize ;
DWORD               wColSize ;
LPBITMAPINFOHEADER  lpbi ;
LPBYTE              lpBits ;

GetObject(hbitmap,sizeof(BITMAP),&bitmap) ;

//
// DWORD align the width of the DIB
// Figure out the size of the colour table
// Calculate the size of the DIB
//
wLineLen = (bitmap.bmWidth*bits+31)/32 * 4;
wColSize = sizeof(RGBQUAD)*((bits <= 8) ? 1<<bits : 0);
dwSize = sizeof(BITMAPINFOHEADER) + wColSize +
(DWORD)(UINT)wLineLen*(DWORD)(UINT)bitmap.bmHeight;

//
// Allocate room for a DIB and set the LPBI fields
//
hdib = GlobalAlloc(GHND,dwSize);
if (!hdib)
return hdib ;

lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib) ;

lpbi->biSize = sizeof(BITMAPINFOHEADER) ;
lpbi->biWidth = bitmap.bmWidth ;
lpbi->biHeight = bitmap.bmHeight ;
lpbi->biPlanes = 1 ;
lpbi->biBitCount = (WORD) bits ;
lpbi->biCompression = BI_RGB ;
lpbi->biSizeImage = dwSize - sizeof(BITMAPINFOHEADER) - wColSize ;
lpbi->biXPelsPerMeter = 0 ;
lpbi->biYPelsPerMeter = 0 ;
lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;
lpbi->biClrImportant = 0 ;

//
// Get the bits from the bitmap and stuff them after the LPBI
//
lpBits = (LPBYTE)(lpbi+1)+wColSize ;

hdc = CreateCompatibleDC(NULL) ;

GetDIBits(hdc,hbitmap,0,bitmap.bmHeight,lpBits,(LPBITMAPINFO)lpbi, DIB_RGB_COLORS);

// Fix this if GetDIBits messed it up....
lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;

DeleteDC(hdc) ;
GlobalUnlock(hdib);

return hdib ;
}


WRITEAVI.H

/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/*--------------------------------------------------------------+
| WriteAVI.h - definitions for WriteAVI app|
||
+--------------------------------------------------------------*/

//String Table ID's

#define IDS_APPNAME1
#define IDS_APPERR2
#define IDS_VFWTOOOLD3
#define IDS_INTRO4
#define IDS_FILENAME5
#define IDS_WRITEERR6
#define IDS_TEXTFORMAT7